diff --git a/php/.env b/php/.env index 98b53b71..98a68075 100644 --- a/php/.env +++ b/php/.env @@ -1 +1 @@ -DD_TRACE_VERSION=0.30.0 +DD_TRACE_VERSION=0.34.0 diff --git a/php/Dockerfiles/nginx/Dockerfile b/php/Dockerfiles/nginx/Dockerfile new file mode 100644 index 00000000..259af396 --- /dev/null +++ b/php/Dockerfiles/nginx/Dockerfile @@ -0,0 +1,3 @@ +FROM nginx:1.17.5-alpine + +COPY ./default.conf /etc/nginx/conf.d/default.conf diff --git a/php/Dockerfiles/nginx/default.conf b/php/Dockerfiles/nginx/default.conf new file mode 100644 index 00000000..5377ed88 --- /dev/null +++ b/php/Dockerfiles/nginx/default.conf @@ -0,0 +1,33 @@ +server { + listen 80; + + access_log /dev/stdout; + error_log /dev/stdout; + + server_name localhost; + + root /var/www/public; + index index.html index.php; + + rewrite ^/(73)/(.*\.(gif|jpg|png|ico|css|js))$ /$2 last; + + location / { + add_header X-uri $uri always; + add_header X-document_root $document_root always; + add_header X-fastcgi_script_name $fastcgi_script_name always; + add_header X-fastcgi_path_info $fastcgi_path_info always; + add_header X-PHP-VERSION $1 always; + add_header X-PHP-PATH $2 always; + + location ~* ^/(73)(.+)$ { + try_files $2 $2/index.php =404; + fastcgi_split_path_info ^(.+\.php)(/.+)$; + fastcgi_pass php-fpm-73:9000; + fastcgi_index index.php; + include fastcgi_params; + include fastcgi_params_shared; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + fastcgi_param PATH_INFO $fastcgi_path_info; + } + } +} diff --git a/php/Dockerfiles/nginx/fastcgi_params_shared b/php/Dockerfiles/nginx/fastcgi_params_shared new file mode 100644 index 00000000..083b041a --- /dev/null +++ b/php/Dockerfiles/nginx/fastcgi_params_shared @@ -0,0 +1,3 @@ +fastcgi_param DD_SHARED_VALUE "Some shared value"; +fastcgi_param DD_AGENT_HOST agent; +fastcgi_param DD_TRACE_URL_AS_RESOURCE_NAMES_ENABLED true; diff --git a/php/Dockerfiles/php-fpm/Dockerfile73 b/php/Dockerfiles/php-fpm/Dockerfile73 new file mode 100644 index 00000000..adb15619 --- /dev/null +++ b/php/Dockerfiles/php-fpm/Dockerfile73 @@ -0,0 +1,20 @@ +FROM php:7.3-fpm + +RUN apt-get update \ + && apt-get install -y libmcrypt-dev \ + && pecl install mcrypt-1.0.3 \ + && docker-php-ext-enable mcrypt \ + && docker-php-ext-install mysqli \ + && rm -rf /var/lib/apt/lists/* + +# Install composer +RUN php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" \ + && php composer-setup.php --install-dir="/usr/bin" --filename=composer \ + && php -r "unlink('composer-setup.php');" \ + && composer self-update + +ARG DD_TRACE_VERSION + +# Install DDTrace deb +ADD https://github.com/DataDog/dd-trace-php/releases/download/${DD_TRACE_VERSION}/datadog-php-tracer_${DD_TRACE_VERSION}_amd64.deb datadog-php-tracer.deb +RUN dpkg -i datadog-php-tracer.deb diff --git a/php/apps/basic/index.php b/php/apps/basic/index.php new file mode 100644 index 00000000..42dff52e --- /dev/null +++ b/php/apps/basic/index.php @@ -0,0 +1,3 @@ +` +as the last line of the commit message. + +Example: `Signed-off-by: Jane Smith ` + +Note that by submitting patches with the Signed-off-by tag, you are giving +permission to license the patch as GPLv2-or-later. See [the DCO file][3] for +details. + + +[2]: https://github.com/phpmyadmin/phpmyadmin/pulls +[3]: https://github.com/phpmyadmin/phpmyadmin/blob/master/DCO +[4]: https://git-scm.com/docs/git-commit#Documentation/git-commit.txt---signoff + +## More information + +You can find more information on our website: + +https://www.phpmyadmin.net/contribute/ diff --git a/php/apps/phpmyadmin49/ChangeLog b/php/apps/phpmyadmin49/ChangeLog new file mode 100644 index 00000000..715bad4e --- /dev/null +++ b/php/apps/phpmyadmin49/ChangeLog @@ -0,0 +1,179 @@ +phpMyAdmin - ChangeLog +====================== + +4.9.1 (2019-09-20) +- issue #15313 Added support for Twig 2 +- issue #15315 Fix cannot edit or export column with default CURRENT_TIMESTAMP in MySQL >= 8.0.13 +- issue Fix a TypeError in Import class with PHP 8 +- issue #14270 Fix Middle-click on foreign key link broken +- issue #14363 Fix broken relational links in tables +- issue #14987 Fix weird error for empty collation +- issue #15334 Fix export of GIS visualisation not working (PNG, PDF, SVG) +- issue #14918 Use hex for the phpMyAdmin session token +- issue Added GB18030 Chinese collations description +- issue Added Russian, Swedish, Slovak and Chinese UCA 9.0.0 collations description +- issue Added description for the _ks (kana-sensitive) collation suffix +- issue Added description for the _nopad (NO PAD) collation suffix +- issue #15404 Remove array/string curly braces access +- issue #15427 Fixed "FilterLanguages" option does not work (configuration) +- issue #15202 Fixed creating user with single quote in password results in no password user +- issue #14950 Fixed left database overview "add column" triggers error +- issue #15363 Fix remove unexpected quotes on text fields (structure and insert) +- issue Fix NULL wrongly checked on field change +- issue #15388 Fix allow to rollback an empty statement +- issue #14291 Fixed incorrect linkage from one table's value to another table +- issue #15446 Fix tables added from other databases are not collapsing in the designer section +- issue #14945 Fix designer page save fails if dB name contains period +- issue Display an error when trying to import in designer a table that's already imported +- issue Fix many bugs when adding new tables to designer +- issue Update CodeMirror to v5.48.4 +- issue Update jQuery Migrate to v3.1.0 +- issue Update jQuery Validation to v1.19.1 +- issue Update jQuery to v3.4.1 +- issue Update js-cookie to v2.2.1 +- issue Remove fieldset closing tag when setting global privileges +- issue #15425 Fix backslash in column name resulting an error in editing +- issue #15380 Fix Status - Advisor error +- issue #15439 Fix designer page status not updated when added a new table from another database +- issue #15440 Fix page number is not being updated in the URL after saving a designer's page +- issue Fix reloading a designer's page +- issue Fix designer full screen mode button and text stuck when exiting full-screen mode +- issue Reduced possibility of causing heavy server traffic between the database and web servers +- issue Fix a situation where a server could be deleted while an administator is using the setup script + +4.9.0.1 (2019-06-04) +- issue #14478 phpMyAdmin no longer streams the export data +- issue #14514 Tables with SYSTEM VERSIONING show up as views instead of tables +- issue #14515 Values cannot be edited in SYSTEM VERSIONING tables with INVISIBLE timestamps +- issue Fix header icon on server plugins page +- issue #14298 Fixed error 500 on MultiTableQuery page when a empty query is passed +- issue #14402 Fixed fatal javascript error while adding index to a new column +- issue #14896 Fixed issue with plus/minus icon when refreshing an expanded database +- issue #14922 Fixed json encode error in export +- issue #13975 Fixed missing query time in German (fix decimal number format issue) +- issue #14503 Fixed JavaScript events not activating on input (sql bookmark issue) +- issue #14898 Fixed Bottom table is blocked in database list (left panel) +- issue #14425 Fixed Null Checkbox automatically unmarked +- issue #14870 Display correct date and time in Zip files +- issue #14763 Fixed the loading symbol not appearing when refreshing the navigation +- issue #14607 Count rows only if needed +- issue #14832 Show Designer combo boxes when adding a constraint +- issue #14948 Fix change password is not showing password strength difference at the second attempt +- issue #14868 Fix edit view +- issue #14943 Fixed loading Forever when creating new view without filling any field +- issue #14843 Fix Bookmark::get() id matching SQL +- issue #14734 Fixed invalid default value for bit field +- issue #14311 Fixed undefined index in setup script +- issue #14991 Fixed TypeError in GIS editor +- issue Fixed GIS data editor for multi server setup +- issue #14312 Fixed type error in setup script when adding new server +- issue #14053 Fix missed padding on query results +- issue #14826 Fixed javascript error PMA_messages is not defined +- issue Show error message if config-set fails and not "loading..." forever +- issue #14359 Prevent multiple error modals, and error-report request spamming from script +- issue Fixed error reporting javascript errors on multi server setup +- issue Fixed wrong property name on TableStructureController +- issue #14811 Fix SHOW FULL TABLES FROM when a table is locked +- issue #14916 Fix bug when creating or editing views +- issue #14931 Fixed php error when using a query like SELECT 1 INTO @a; SELECT @a; in inline query edit +- issue #15074 Make the server logo visible on theme "original" +- issue #15077 Fixed incorrect page numbers +- issue #14205 Fixed "No tables found in database" when you delete all tables from last page +- issue #14957 Virtuality is not selected when editing generated column (added virtuality(stored) option for mariadb) +- issue #14853 Insert page should not allow entering things into virtual columns +- issue #15110 Fixed TypeError e.preventDefaulut is not a function +- issue #15115 Improved label in Settings export, clarifying that it's a JSON file +- issue #14816 Fixed [designer] Cannot read property 'style' of null +- issue Fixed [designer] Add new tables with database/table list modal +- issue Fixed query format on multi server setup +- issue Fixed remove partitioning on multi server setup +- issue Fixed normalization +- issue Fixed 'RESET SLAVE' button on replication slave +- issue Fixed sending a php error report on multi server setup +- issue Fixed downloading of monitor parameters for IE 11, Edge, Chrome and others +- issue #15141 Fixed php notice Undefined index: designer_settings +- issue #12729 Fixed sticky table header over dropdown menu +- issue #15140 Fixed edit link does not work on failed insert +- issue #14334 Fixed export table structure shows rows fields +- issue #15010 Fixed empty SQL preview modal on tbl_relation +- issue #14673 Fixed innodb & MySQL 8: DYNAMIC & COMPRESSED ROW_FORMAT missing +- issue Fixed empty success message when adding a new INDEX from left panel +- issue #15150 Fixed generate password hidden on second open of change password modal +- issue Fixed import XML data with leading zeros +- issue #15036 Fixed missing input fields checks for MaxSizeForInputField +- issue #15119 Fixed uninterpreted HTML on Settings->Export page +- issue #15159 Fixed missing query time and database in console +- issue #13713 Fixed column comments in the floating table header +- issue #15177 Fixed label alignment on login page +- issue #15210 Fixed a typo in the english name of the Albanian language +- issue Fixed issue when resetting charset in import.php +- issue #14460 Fixed forms where submitted multiple times on CTRL + ENTER +- issue #15038 Fixed console height was allowing a negative values +- issue #15219 Fixed 'No Password' option does not switch automatically to 'Use Text Field' in add user account +- issue Fixed importing the exported config on Server status monitor page +- issue #15228 Fixed php notice 'Undefined index: foreign_keys_data' on designer when the user has column access +- issue #12900 Fixed designer page saving gives error when configuration storage is not set up +- issue #15229 Fixed php notice, added support for 'DELETE HISTORY' table privilege (MariaDB >= 10.3.4) +- issue #14527 Fixed import settings function not working +- issue #14908 Fixed uninterpreted HTML on Settings->Import (missing data error descriptions) +- issue #14800 Fixed status->Processes doesn't show full query process list page +- issue #14833 Fixed sort by Time not working in process list page +- issue #14982 Fixed setting "null" keep an "enum" value +- issue #14401 Fixed insert rows keypress Enter behavior +- issue #15146 Fixed error reports can not be sent because they are too large +- issue #15205 Fixed useless backquotes on sql preview modal when deleting an index +- issue #13178 Fixed issues with uppercase table and database names (lower_case_table_names=1) +- issue #14383 Fixed warning when browsing certain tables (GIS data) +- issue #12865 Fixed MySQL 8.0.0 issues with GIS display +- issue #15059 Fixed "Server charset" in "Database server" tab showing wrong information +- issue #14614 Fixed mysql error "#2014 - Commands out of sync; you can't run this command now" on sql query +- issue #15238 Fixed phpMyAdmin 4.8.5 doesn't show privileges of procedures (raw html displayed instead) +- issue #13726 Fixed can not copy user on Percona Server 5.7 +- issue #15239 Fixed javascript error while fetching latest version info and switching pages +- issue #14301 Fixed javascript error when editing a JSON data type column +- issue #15240 Fixed apply a Settings form with errors shows a JSON response after using return back +- issue #15043 Fixed multiple errors printing on Settings page +- issue #15037 Fixed unexpected behavior of reset button on Settings +- issue #15157 Fixed 'Settings' tab not marked as active when browsing 2FA settings +- issue #14934 Fixed all fields readonly on Edit/Insert screens +- issue #14588 Fixed export of geometry objects, GIS objects are now exported as hex +- issue #14412 Better handling of errors with Signon authentication type +- issue Added support for AUTO_INCREMENT when using ROCKSDB, on Operations page +- issue #15276 Fixed partitioning is missing in Structure page UI (MySQL 8.0) +- issue #14252 Fixed DisableIS and database tree list (new database missing when refreshing the list) +- issue #14621 Removed "Propose table structure" on MySQL 8.0 +- issue Fixed editing of virtual columns on PerconaDB +- issue #13854 Fixed column options are ignored for GENERATED/VIRTUAL/STORED columns +- issue #15262 Fixed incorrect display of charset column (raw html) +- issue Added explicit parentheses in nested ternary operators +- issue #15287 Fix auto_increment field is too small +- issue #15283 Fix tries to change collation on views when changing collation on all tables/fields +- issue Fixed empty PMA_gotoWhitelist JavaScript array +- issue #15079 Fixed responsive behaviour of instruction dialog box +- issue #10846 Fixed javascript error when renaming a table +- issue Updated sql-parser to version 4.3.2 +- issue [security] SQL injection in Designer (PMASA-2019-3) +- issue [security] CSRF attack on 'cookie' login form (PMASA-2019-4) + +4.8.5 (2019-01-25) +- issue Developer debug data was saved to the PHP error log +- issue #14217 Fix issue when adding user on MySQL 8.0.11 +- issue #13788 Exporting a view structure based on another view with a sub-query throws no database selected error +- issue #14635 Fix PHP error in GitRevision, error in processing request, error code 200 +- issue #14787 Cannot execute stored procedure +- issue Add Burmese language +- issue #14794 Not responding to click, frozen interface, plugin Text_Plain_Sql error +- issue #14786 Table level Operations functions missing +- issue #14791 PHP warning, db_export.php#L91 urldecode() +- issue #14775 Export to SQL format not available for tables +- issue #14782 Error message shown instead of two-factor QR code when adding 2fa to a user +- issue [security] Arbitrary file read/delete relating to MySQL LOAD DATA LOCAL INFILE and an evil server instance (PMASA-2019-1) +- issue [security] SQL injection in Designer (PMASA-2019-2) + + --- Older ChangeLogs can be found on our project website --- + https://www.phpmyadmin.net/old-stuff/ChangeLogs/ + +# vim: et ts=4 sw=4 sts=4 +# vim: ft=changelog fenc=utf-8 +# vim: fde=getline(v\:lnum-1)=~'^\\s*$'&&getline(v\:lnum)=~'\\S'?'>1'\:1&&v\:lnum>4&&getline(v\:lnum)!~'^#' +# vim: fdn=1 fdm=expr diff --git a/php/apps/phpmyadmin49/DCO b/php/apps/phpmyadmin49/DCO new file mode 100644 index 00000000..2ef700ef --- /dev/null +++ b/php/apps/phpmyadmin49/DCO @@ -0,0 +1,44 @@ +If you would like to make a contribution to the phpMyAdmin Project, please +certify to the following: +*** +phpMyAdmin Developer's Certificate of Origin. Version 1.0 + +By making a contribution to this project, I certify that: + +(a) The contribution was created in whole or in part by me and I have the + right to submit it under the license of "GNU General Public License or + any later version" ("GPLv2-or-later"); or + +(b) The contribution is based upon previous work that, to the best of my + knowledge, is covered under an appropriate open source license and I have + the right under that license to submit that work with modifications, + whether created in whole or in part by me, under GPLv2-or-later; or + +(c) The contribution was provided directly to me by some other person who + certified (a) or (b) and I have not modified it. + +(d) I understand and agree that this project and the contribution are public + and that a record of the contribution (including all metadata and + personal information I submit with it, including my sign-off) is + maintained indefinitely and may be redistributed consistent with + phpMyAdmin's policies and the requirements of the GPLv2-or-later where + they are relevant. + +(e) I am granting this work to this project under the terms of the + GPLv2-or-later. + + https://www.gnu.org/licenses/gpl-2.0.html + +*** +*** +And please confirm your certification to the above by adding the following +line to your patch: + + Signed-off-by: Jane Smith + +using your real name (sorry, no pseudonyms or anonymous contributions). + +If you are a developer who is authorized to contribute to phpMyAdmin on +behalf of your employer, then please use your corporate email address in the +Signed-off-by tag. If not, then please use a personal email address. + diff --git a/php/apps/phpmyadmin49/LICENSE b/php/apps/phpmyadmin49/LICENSE new file mode 100644 index 00000000..d159169d --- /dev/null +++ b/php/apps/phpmyadmin49/LICENSE @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/php/apps/phpmyadmin49/README b/php/apps/phpmyadmin49/README new file mode 100644 index 00000000..54bc5d09 --- /dev/null +++ b/php/apps/phpmyadmin49/README @@ -0,0 +1,52 @@ +phpMyAdmin - Readme +=================== + +Version 4.9.1 + +A web interface for MySQL and MariaDB. + +https://www.phpmyadmin.net/ + +Summary +------- + +phpMyAdmin is intended to handle the administration of MySQL over the web. +For a summary of features, list of requirements, and installation instructions, +please see the documentation in the ./doc/ folder or at https://docs.phpmyadmin.net/ + +Copyright +--------- + +Copyright © 1998 onwards -- the phpMyAdmin team + +Certain libraries are copyrighted by their respective authors; +see the full copyright list for details. + +For full copyright information, please see ./doc/copyright.rst + +License +------- + +This program is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License version 2, as published by the +Free Software Foundation. + +This program is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . + +Licensing of current contributions +---------------------------------- + +Beginning on 2013-12-01, new contributions to this codebase are all licensed +under terms compatible with GPLv2-or-later. phpMyAdmin is currently +transitioning older code to GPLv2-or-later, but work is not yet complete. + +Enjoy! +------ + +The phpMyAdmin team diff --git a/php/apps/phpmyadmin49/RELEASE-DATE-4.9.1 b/php/apps/phpmyadmin49/RELEASE-DATE-4.9.1 new file mode 100644 index 00000000..6ab1f0b0 --- /dev/null +++ b/php/apps/phpmyadmin49/RELEASE-DATE-4.9.1 @@ -0,0 +1 @@ +Sat Sep 21 03:54:19 UTC 2019 diff --git a/php/apps/phpmyadmin49/ajax.php b/php/apps/phpmyadmin49/ajax.php new file mode 100644 index 00000000..7f615a01 --- /dev/null +++ b/php/apps/phpmyadmin49/ajax.php @@ -0,0 +1,51 @@ +setAJAX(true); + +if (empty($_POST['type'])) { + Core::fatalError(__('Bad type!')); +} + +switch ($_POST['type']) { + case 'list-databases': + $response->addJSON('databases', $GLOBALS['dblist']->databases); + break; + case 'list-tables': + Util::checkParameters(array('db'), true); + $response->addJSON('tables', $GLOBALS['dbi']->getTables($_POST['db'])); + break; + case 'list-columns': + Util::checkParameters(array('db', 'table'), true); + $response->addJSON('columns', $GLOBALS['dbi']->getColumnNames($_POST['db'], $_POST['table'])); + break; + case 'config-get': + Util::checkParameters(array('key'), true); + $response->addJSON('value', $GLOBALS['PMA_Config']->get($_POST['key'])); + break; + case 'config-set': + Util::checkParameters(array('key', 'value'), true); + $result = $GLOBALS['PMA_Config']->setUserValue(null, $_POST['key'], json_decode($_POST['value'])); + if ($result !== true) { + $response = Response::getInstance(); + $response->setRequestStatus(false); + $response->addJSON('message', $result); + } + break; + default: + Core::fatalError(__('Bad type!')); +} diff --git a/php/apps/phpmyadmin49/browse_foreigners.php b/php/apps/phpmyadmin49/browse_foreigners.php new file mode 100644 index 00000000..f3c3fab4 --- /dev/null +++ b/php/apps/phpmyadmin49/browse_foreigners.php @@ -0,0 +1,74 @@ +getFooter()->setMinimal(); +$header = $response->getHeader(); +$header->disableMenuAndConsole(); +$header->setBodyId('body_browse_foreigners'); + +$relation = new Relation(); + +/** + * Displays the frame + */ +$foreigners = $relation->getForeigners($db, $table); +$browseForeigners = new BrowseForeigners( + $GLOBALS['cfg']['LimitChars'], + $GLOBALS['cfg']['MaxRows'], + $GLOBALS['cfg']['RepeatCells'], + $GLOBALS['cfg']['ShowAll'], + $GLOBALS['pmaThemeImage'] +); +$foreign_limit = $browseForeigners->getForeignLimit( + isset($_POST['foreign_showAll']) ? $_POST['foreign_showAll'] : null +); + +$foreignData = $relation->getForeignData( + $foreigners, $_POST['field'], true, + isset($_POST['foreign_filter']) + ? $_POST['foreign_filter'] + : '', + isset($foreign_limit) ? $foreign_limit : null, + true // for getting value in $foreignData['the_total'] +); + +// HTML output +$html = $browseForeigners->getHtmlForRelationalFieldSelection( + $db, + $table, + $_POST['field'], + $foreignData, + isset($fieldkey) ? $fieldkey : null, + isset($data) ? $data : null +); + +$response->addHtml($html); diff --git a/php/apps/phpmyadmin49/changelog.php b/php/apps/phpmyadmin49/changelog.php new file mode 100644 index 00000000..b0f8b012 --- /dev/null +++ b/php/apps/phpmyadmin49/changelog.php @@ -0,0 +1,117 @@ +disable(); +$response->getHeader()->sendHttpHeaders(); + +$filename = CHANGELOG_FILE; + +/** + * Read changelog. + */ +// Check if the file is available, some distributions remove these. +if (@is_readable($filename)) { + + // Test if the if is in a compressed format + if (substr($filename, -3) == '.gz') { + ob_start(); + readgzfile($filename); + $changelog = ob_get_contents(); + ob_end_clean(); + } else { + $changelog = file_get_contents($filename); + } +} else { + printf( + __( + 'The %s file is not available on this system, please visit ' . + '%s for more information.' + ), + $filename, + 'phpmyadmin.net' + ); + exit; +} + +/** + * Whole changelog in variable. + */ +$changelog = htmlspecialchars($changelog); + +$github_url = 'https://github.com/phpmyadmin/phpmyadmin/'; +$faq_url = 'https://docs.phpmyadmin.net/en/latest/faq.html'; + +$replaces = array( + '@(https?://[./a-zA-Z0-9.-_-]*[/a-zA-Z0-9_])@' + => '\\1', + + // mail address + '/([0-9]{4}-[0-9]{2}-[0-9]{2}) (.+[^ ]) +<(.*@.*)>/i' + => '\\1 \\2', + + // FAQ entries + '/FAQ ([0-9]+)\.([0-9a-z]+)/i' + => 'FAQ \\1.\\2', + + // GitHub issues + '/issue\s*#?([0-9]{4,5}) /i' + => 'issue #\\1 ', + + // CVE/CAN entries + '/((CAN|CVE)-[0-9]+-[0-9]+)/' + => '\\1', + + // PMASAentries + '/(PMASA-[0-9]+-[0-9]+)/' + => '\\1', + + // Highlight releases (with links) + '/([0-9]+)\.([0-9]+)\.([0-9]+)\.0 (\([0-9-]+\))/' + => '' + . '' + . '\\1.\\2.\\3.0 \\4', + '/([0-9]+)\.([0-9]+)\.([0-9]+)\.([1-9][0-9]*) (\([0-9-]+\))/' + => '' + . '' + . '\\1.\\2.\\3.\\4 \\5', + + // Highlight releases (not linkable) + '/( ### )(.*)/' + => '\\1\\2', + + // Links target and rel + '/a href="/' => 'a target="_blank" rel="noopener noreferrer" href="' + +); + +header('Content-type: text/html; charset=utf-8'); +?> + + + + + + phpMyAdmin - ChangeLog + + + +

phpMyAdmin - ChangeLog

+'; +echo preg_replace(array_keys($replaces), $replaces, $changelog); +echo ''; +?> + + diff --git a/php/apps/phpmyadmin49/chk_rel.php b/php/apps/phpmyadmin49/chk_rel.php new file mode 100644 index 00000000..fe639537 --- /dev/null +++ b/php/apps/phpmyadmin49/chk_rel.php @@ -0,0 +1,36 @@ +createPmaDatabase()) { + $relation->fixPmaTables('phpmyadmin'); + } +} + +// If request for creating all PMA tables. +if (isset($_POST['fixall_pmadb'])) { + $relation->fixPmaTables($GLOBALS['db']); +} + +$cfgRelation = $relation->getRelationsParam(); +// If request for creating missing PMA tables. +if (isset($_POST['fix_pmadb'])) { + $relation->fixPmaTables($cfgRelation['db']); +} + +$response = Response::getInstance(); +$response->addHTML( + $relation->getRelationsParamDiagnostic($cfgRelation) +); diff --git a/php/apps/phpmyadmin49/composer.json b/php/apps/phpmyadmin49/composer.json new file mode 100644 index 00000000..5653a0d8 --- /dev/null +++ b/php/apps/phpmyadmin49/composer.json @@ -0,0 +1,97 @@ +{ + "name": "phpmyadmin/phpmyadmin", + "type": "project", + "description": "MySQL web administration tool", + "keywords": ["phpmyadmin","mysql","web"], + "homepage": "https://www.phpmyadmin.net/", + "support": { + "forum": "https://www.phpmyadmin.net/support/", + "issues": "https://github.com/phpmyadmin/phpmyadmin/issues", + "wiki": "https://wiki.phpmyadmin.net/", + "docs": "https://docs.phpmyadmin.net/", + "source": "https://github.com/phpmyadmin/phpmyadmin" + }, + "license": "GPL-2.0-only", + "authors": [ + { + "name": "The phpMyAdmin Team", + "email": "developers@phpmyadmin.net", + "homepage": "https://www.phpmyadmin.net/team/" + } + ], + "non-feature-branches": ["RELEASE_.*"], + "autoload": { + "psr-4": { + "PhpMyAdmin\\": "libraries/classes", + "PhpMyAdmin\\Setup\\": "setup/lib" + } + }, + "autoload-dev": { + "psr-4": { + "PhpMyAdmin\\Tests\\": "test/classes", + "PhpMyAdmin\\Tests\\Selenium\\": "test/selenium/" + } + }, + "repositories": [ + { + "type": "composer", + "url": "https://www.phpmyadmin.net" + } + ], + "require": { + "php": ">=5.5.0", + "ext-mysqli": "*", + "ext-xml": "*", + "ext-pcre": "*", + "ext-json": "*", + "ext-ctype": "*", + "ext-hash": "*", + "phpmyadmin/sql-parser": "^4.3.2", + "phpmyadmin/motranslator": "^4.0", + "phpmyadmin/shapefile": "^2.0", + "phpseclib/phpseclib": "^2.0", + "google/recaptcha": "^1.1", + "psr/container": "^1.0", + "twig/twig": "^1.34 || ^2.0", + "twig/extensions": "~1.5.1", + "symfony/expression-language": "^3.2 || ^2.8", + "symfony/polyfill-mbstring": "^1.3" + }, + "conflict": { + "phpseclib/phpseclib": "2.0.8", + "tecnickcom/tcpdf": "<6.2", + "pragmarx/google2fa": "<3.0.1", + "bacon/bacon-qr-code": "<1.0", + "samyoul/u2f-php-server": "<1.1" + }, + "suggest": { + "ext-openssl": "Cookie encryption", + "ext-curl": "Updates checking", + "ext-opcache": "Better performance", + "ext-zlib": "For gz import and export", + "ext-bz2": "For bzip2 import and export", + "ext-zip": "For zip import and export", + "ext-gd2": "For image transformations", + "ext-mbstring": "For best performance", + "tecnickcom/tcpdf": "For PDF support", + "pragmarx/google2fa": "For 2FA authentication", + "bacon/bacon-qr-code": "For 2FA authentication", + "samyoul/u2f-php-server": "For FIDO U2F authentication" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.36 || ^5.7", + "codacy/coverage": "^1.3.0", + "phpunit/phpunit-selenium": "~1.2 || ^3.0", + "squizlabs/php_codesniffer": "^3.0", + "tecnickcom/tcpdf": "^6.3", + "pragmarx/google2fa": "^3.0", + "bacon/bacon-qr-code": "^1.0", + "samyoul/u2f-php-server": "^1.1", + "phpmyadmin/coding-standard": "^0.3" + }, + "extra": { + "branch-alias": { + "dev-master": "4.9.x-dev" + } + } +} diff --git a/php/apps/phpmyadmin49/composer.lock b/php/apps/phpmyadmin49/composer.lock new file mode 100644 index 00000000..9cff71d0 --- /dev/null +++ b/php/apps/phpmyadmin49/composer.lock @@ -0,0 +1,3209 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "4d2c130b0c88b124aea75bbd5bd6432b", + "packages": [ + { + "name": "google/recaptcha", + "version": "1.2.3", + "source": { + "type": "git", + "url": "https://github.com/google/recaptcha.git", + "reference": "98c4a6573b27e8b0990ea8789c74ea378795134c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/google/recaptcha/zipball/98c4a6573b27e8b0990ea8789c74ea378795134c", + "reference": "98c4a6573b27e8b0990ea8789c74ea378795134c", + "shasum": "" + }, + "require": { + "php": ">=5.5" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^2.2.20|^2.15", + "php-coveralls/php-coveralls": "^2.1", + "phpunit/phpunit": "^4.8.36|^5.7.27|^6.59|^7.5.11" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2.x-dev" + } + }, + "autoload": { + "psr-4": { + "ReCaptcha\\": "src/ReCaptcha" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "Client library for reCAPTCHA, a free service that protects websites from spam and abuse.", + "homepage": "https://www.google.com/recaptcha/", + "keywords": [ + "Abuse", + "captcha", + "recaptcha", + "spam" + ], + "time": "2019-08-16T15:48:25+00:00" + }, + { + "name": "phpmyadmin/motranslator", + "version": "4.0", + "source": { + "type": "git", + "url": "https://github.com/phpmyadmin/motranslator.git", + "reference": "fcb370254998fda7eeccfd7c787b4deb71b0d77c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpmyadmin/motranslator/zipball/fcb370254998fda7eeccfd7c787b4deb71b0d77c", + "reference": "fcb370254998fda7eeccfd7c787b4deb71b0d77c", + "shasum": "" + }, + "require": { + "php": ">=5.3.0", + "symfony/expression-language": "^4.0 || ^3.2 || ^2.8" + }, + "require-dev": { + "apigen/apigen": "^4.1", + "phpunit/php-code-coverage": "*", + "phpunit/phpunit": "~4.8 || ~5.7 || ~6.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "PhpMyAdmin\\MoTranslator\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "GPL-2.0-or-later" + ], + "authors": [ + { + "name": "The phpMyAdmin Team", + "email": "developers@phpmyadmin.net", + "homepage": "https://www.phpmyadmin.net/team/" + } + ], + "description": "Translation API for PHP using Gettext MO files", + "homepage": "https://github.com/phpmyadmin/motranslator", + "keywords": [ + "gettext", + "i18n", + "mo", + "translator" + ], + "time": "2018-02-12T13:22:52+00:00" + }, + { + "name": "phpmyadmin/shapefile", + "version": "2.1", + "source": { + "type": "git", + "url": "https://github.com/phpmyadmin/shapefile.git", + "reference": "e23b767f2a81f61fee3fc09fc062879985f3e224" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpmyadmin/shapefile/zipball/e23b767f2a81f61fee3fc09fc062879985f3e224", + "reference": "e23b767f2a81f61fee3fc09fc062879985f3e224", + "shasum": "" + }, + "require": { + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/php-code-coverage": "*", + "phpunit/phpunit": "~4.8 || ~5.7" + }, + "suggest": { + "ext-dbase": "For dbf files parsing" + }, + "type": "library", + "autoload": { + "psr-4": { + "PhpMyAdmin\\ShapeFile\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "GPL-2.0+" + ], + "authors": [ + { + "name": "The phpMyAdmin Team", + "email": "developers@phpmyadmin.net", + "homepage": "https://www.phpmyadmin.net/team/" + } + ], + "description": "ESRI ShapeFile library for PHP", + "homepage": "https://github.com/phpmyadmin/shapefile", + "keywords": [ + "ESRI", + "Shapefile", + "dbf", + "geo", + "geospatial", + "shape", + "shp" + ], + "time": "2017-05-15T08:31:47+00:00" + }, + { + "name": "phpmyadmin/sql-parser", + "version": "4.3.2", + "source": { + "type": "git", + "url": "https://github.com/phpmyadmin/sql-parser.git", + "reference": "b35c21f82e7202d739f6349a583b11e6d32b2b64" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpmyadmin/sql-parser/zipball/b35c21f82e7202d739f6349a583b11e6d32b2b64", + "reference": "b35c21f82e7202d739f6349a583b11e6d32b2b64", + "shasum": "" + }, + "require": { + "php": ">=5.3.0", + "symfony/polyfill-mbstring": "^1.3" + }, + "conflict": { + "phpmyadmin/motranslator": "<3.0" + }, + "require-dev": { + "phpunit/php-code-coverage": "*", + "phpunit/phpunit": "~4.8 || ~5.7 || ~6.5", + "sami/sami": "^4.0", + "squizlabs/php_codesniffer": "^2.9" + }, + "suggest": { + "ext-mbstring": "For best performance", + "phpmyadmin/motranslator": "Translate messages to your favorite locale" + }, + "bin": [ + "bin/highlight-query", + "bin/lint-query", + "bin/tokenize-query" + ], + "type": "library", + "autoload": { + "psr-4": { + "PhpMyAdmin\\SqlParser\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "GPL-2.0-or-later" + ], + "authors": [ + { + "name": "The phpMyAdmin Team", + "email": "developers@phpmyadmin.net", + "homepage": "https://www.phpmyadmin.net/team/" + } + ], + "description": "A validating SQL lexer and parser with a focus on MySQL dialect.", + "homepage": "https://github.com/phpmyadmin/sql-parser", + "keywords": [ + "analysis", + "lexer", + "parser", + "sql" + ], + "time": "2019-06-03T12:32:07+00:00" + }, + { + "name": "phpseclib/phpseclib", + "version": "2.0.23", + "source": { + "type": "git", + "url": "https://github.com/phpseclib/phpseclib.git", + "reference": "c78eb5058d5bb1a183133c36d4ba5b6675dfa099" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/c78eb5058d5bb1a183133c36d4ba5b6675dfa099", + "reference": "c78eb5058d5bb1a183133c36d4ba5b6675dfa099", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phing/phing": "~2.7", + "phpunit/phpunit": "^4.8.35|^5.7|^6.0", + "sami/sami": "~2.0", + "squizlabs/php_codesniffer": "~2.0" + }, + "suggest": { + "ext-gmp": "Install the GMP (GNU Multiple Precision) extension in order to speed up arbitrary precision integer arithmetic operations.", + "ext-libsodium": "SSH2/SFTP can make use of some algorithms provided by the libsodium-php extension.", + "ext-mcrypt": "Install the Mcrypt extension in order to speed up a few other cryptographic operations.", + "ext-openssl": "Install the OpenSSL extension in order to speed up a wide variety of cryptographic operations." + }, + "type": "library", + "autoload": { + "files": [ + "phpseclib/bootstrap.php" + ], + "psr-4": { + "phpseclib\\": "phpseclib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jim Wigginton", + "email": "terrafrost@php.net", + "role": "Lead Developer" + }, + { + "name": "Patrick Monnerat", + "email": "pm@datasphere.ch", + "role": "Developer" + }, + { + "name": "Andreas Fischer", + "email": "bantu@phpbb.com", + "role": "Developer" + }, + { + "name": "Hans-Jürgen Petrich", + "email": "petrich@tronic-media.com", + "role": "Developer" + }, + { + "name": "Graham Campbell", + "email": "graham@alt-three.com", + "role": "Developer" + } + ], + "description": "PHP Secure Communications Library - Pure-PHP implementations of RSA, AES, SSH2, SFTP, X.509 etc.", + "homepage": "http://phpseclib.sourceforge.net", + "keywords": [ + "BigInteger", + "aes", + "asn.1", + "asn1", + "blowfish", + "crypto", + "cryptography", + "encryption", + "rsa", + "security", + "sftp", + "signature", + "signing", + "ssh", + "twofish", + "x.509", + "x509" + ], + "time": "2019-09-17T03:41:22+00:00" + }, + { + "name": "psr/cache", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/cache.git", + "reference": "d11b50ad223250cf17b86e38383413f5a6764bf8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/cache/zipball/d11b50ad223250cf17b86e38383413f5a6764bf8", + "reference": "d11b50ad223250cf17b86e38383413f5a6764bf8", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Cache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for caching libraries", + "keywords": [ + "cache", + "psr", + "psr-6" + ], + "time": "2016-08-06T20:24:11+00:00" + }, + { + "name": "psr/container", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/b7ce3b176482dbbc1245ebf52b181af44c2cf55f", + "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "time": "2017-02-14T16:28:37+00:00" + }, + { + "name": "psr/log", + "version": "1.1.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "bf73deb2b3b896a9d9c75f3f0d88185d2faa27e2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/bf73deb2b3b896a9d9c75f3f0d88185d2faa27e2", + "reference": "bf73deb2b3b896a9d9c75f3f0d88185d2faa27e2", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "Psr/Log/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "time": "2019-10-25T08:06:51+00:00" + }, + { + "name": "symfony/cache", + "version": "v4.3.5", + "source": { + "type": "git", + "url": "https://github.com/symfony/cache.git", + "reference": "40c62600ebad1ed2defbf7d35523d918a73ab330" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/cache/zipball/40c62600ebad1ed2defbf7d35523d918a73ab330", + "reference": "40c62600ebad1ed2defbf7d35523d918a73ab330", + "shasum": "" + }, + "require": { + "php": "^7.1.3", + "psr/cache": "~1.0", + "psr/log": "~1.0", + "symfony/cache-contracts": "^1.1", + "symfony/service-contracts": "^1.1", + "symfony/var-exporter": "^4.2" + }, + "conflict": { + "doctrine/dbal": "<2.5", + "symfony/dependency-injection": "<3.4", + "symfony/var-dumper": "<3.4" + }, + "provide": { + "psr/cache-implementation": "1.0", + "psr/simple-cache-implementation": "1.0", + "symfony/cache-implementation": "1.0" + }, + "require-dev": { + "cache/integration-tests": "dev-master", + "doctrine/cache": "~1.6", + "doctrine/dbal": "~2.5", + "predis/predis": "~1.1", + "psr/simple-cache": "^1.0", + "symfony/config": "~4.2", + "symfony/dependency-injection": "~3.4|~4.1", + "symfony/var-dumper": "^4.1.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.3-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Cache\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Cache component with PSR-6, PSR-16, and tags", + "homepage": "https://symfony.com", + "keywords": [ + "caching", + "psr6" + ], + "time": "2019-10-04T10:57:53+00:00" + }, + { + "name": "symfony/cache-contracts", + "version": "v1.1.7", + "source": { + "type": "git", + "url": "https://github.com/symfony/cache-contracts.git", + "reference": "af50d14ada9e4e82cfabfabdc502d144f89be0a1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/cache-contracts/zipball/af50d14ada9e4e82cfabfabdc502d144f89be0a1", + "reference": "af50d14ada9e4e82cfabfabdc502d144f89be0a1", + "shasum": "" + }, + "require": { + "php": "^7.1.3", + "psr/cache": "^1.0" + }, + "suggest": { + "symfony/cache-implementation": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Cache\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to caching", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "time": "2019-10-04T21:43:27+00:00" + }, + { + "name": "symfony/expression-language", + "version": "v3.4.32", + "source": { + "type": "git", + "url": "https://github.com/symfony/expression-language.git", + "reference": "8404252a6d0cbdff9657e1d67d291593c5cfef44" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/expression-language/zipball/8404252a6d0cbdff9657e1d67d291593c5cfef44", + "reference": "8404252a6d0cbdff9657e1d67d291593c5cfef44", + "shasum": "" + }, + "require": { + "php": "^5.5.9|>=7.0.8", + "symfony/cache": "~3.1|~4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.4-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\ExpressionLanguage\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony ExpressionLanguage Component", + "homepage": "https://symfony.com", + "time": "2019-08-07T07:23:44+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.12.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "550ebaac289296ce228a706d0867afc34687e3f4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/550ebaac289296ce228a706d0867afc34687e3f4", + "reference": "550ebaac289296ce228a706d0867afc34687e3f4", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.12-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "time": "2019-08-06T08:03:45+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.12.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "b42a2f66e8f1b15ccf25652c3424265923eb4f17" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/b42a2f66e8f1b15ccf25652c3424265923eb4f17", + "reference": "b42a2f66e8f1b15ccf25652c3424265923eb4f17", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.12-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "time": "2019-08-06T08:03:45+00:00" + }, + { + "name": "symfony/service-contracts", + "version": "v1.1.7", + "source": { + "type": "git", + "url": "https://github.com/symfony/service-contracts.git", + "reference": "ffcde9615dc5bb4825b9f6aed07716f1f57faae0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/ffcde9615dc5bb4825b9f6aed07716f1f57faae0", + "reference": "ffcde9615dc5bb4825b9f6aed07716f1f57faae0", + "shasum": "" + }, + "require": { + "php": "^7.1.3", + "psr/container": "^1.0" + }, + "suggest": { + "symfony/service-implementation": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Service\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to writing services", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "time": "2019-09-17T11:12:18+00:00" + }, + { + "name": "symfony/var-exporter", + "version": "v4.3.5", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-exporter.git", + "reference": "d5b4e2d334c1d80e42876c7d489896cfd37562f2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-exporter/zipball/d5b4e2d334c1d80e42876c7d489896cfd37562f2", + "reference": "d5b4e2d334c1d80e42876c7d489896cfd37562f2", + "shasum": "" + }, + "require": { + "php": "^7.1.3" + }, + "require-dev": { + "symfony/var-dumper": "^4.1.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.3-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\VarExporter\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A blend of var_export() + serialize() to turn any serializable data structure to plain PHP code", + "homepage": "https://symfony.com", + "keywords": [ + "clone", + "construct", + "export", + "hydrate", + "instantiate", + "serialize" + ], + "time": "2019-08-22T07:33:08+00:00" + }, + { + "name": "twig/extensions", + "version": "v1.5.4", + "source": { + "type": "git", + "url": "https://github.com/twigphp/Twig-extensions.git", + "reference": "57873c8b0c1be51caa47df2cdb824490beb16202" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/twigphp/Twig-extensions/zipball/57873c8b0c1be51caa47df2cdb824490beb16202", + "reference": "57873c8b0c1be51caa47df2cdb824490beb16202", + "shasum": "" + }, + "require": { + "twig/twig": "^1.27|^2.0" + }, + "require-dev": { + "symfony/phpunit-bridge": "^3.4", + "symfony/translation": "^2.7|^3.4" + }, + "suggest": { + "symfony/translation": "Allow the time_diff output to be translated" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.5-dev" + } + }, + "autoload": { + "psr-0": { + "Twig_Extensions_": "lib/" + }, + "psr-4": { + "Twig\\Extensions\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + } + ], + "description": "Common additional features for Twig that do not directly belong in core", + "keywords": [ + "i18n", + "text" + ], + "time": "2018-12-05T18:34:18+00:00" + }, + { + "name": "twig/twig", + "version": "v2.12.1", + "source": { + "type": "git", + "url": "https://github.com/twigphp/Twig.git", + "reference": "ddd4134af9bfc6dba4eff7c8447444ecc45b9ee5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/twigphp/Twig/zipball/ddd4134af9bfc6dba4eff7c8447444ecc45b9ee5", + "reference": "ddd4134af9bfc6dba4eff7c8447444ecc45b9ee5", + "shasum": "" + }, + "require": { + "php": "^7.0", + "symfony/polyfill-ctype": "^1.8", + "symfony/polyfill-mbstring": "^1.3" + }, + "require-dev": { + "psr/container": "^1.0", + "symfony/debug": "^3.4|^4.2", + "symfony/phpunit-bridge": "^4.4@dev|^5.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.12-dev" + } + }, + "autoload": { + "psr-0": { + "Twig_": "lib/" + }, + "psr-4": { + "Twig\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com", + "homepage": "http://fabien.potencier.org", + "role": "Lead Developer" + }, + { + "name": "Twig Team", + "homepage": "https://twig.symfony.com/contributors", + "role": "Contributors" + }, + { + "name": "Armin Ronacher", + "email": "armin.ronacher@active-4.com", + "role": "Project Founder" + } + ], + "description": "Twig, the flexible, fast, and secure template language for PHP", + "homepage": "https://twig.symfony.com", + "keywords": [ + "templating" + ], + "time": "2019-10-17T07:34:53+00:00" + } + ], + "packages-dev": [ + { + "name": "bacon/bacon-qr-code", + "version": "1.0.3", + "source": { + "type": "git", + "url": "https://github.com/Bacon/BaconQrCode.git", + "reference": "5a91b62b9d37cee635bbf8d553f4546057250bee" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Bacon/BaconQrCode/zipball/5a91b62b9d37cee635bbf8d553f4546057250bee", + "reference": "5a91b62b9d37cee635bbf8d553f4546057250bee", + "shasum": "" + }, + "require": { + "ext-iconv": "*", + "php": "^5.4|^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8" + }, + "suggest": { + "ext-gd": "to generate QR code images" + }, + "type": "library", + "autoload": { + "psr-0": { + "BaconQrCode": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-2-Clause" + ], + "authors": [ + { + "name": "Ben Scholzen 'DASPRiD'", + "email": "mail@dasprids.de", + "homepage": "http://www.dasprids.de", + "role": "Developer" + } + ], + "description": "BaconQrCode is a QR code generator for PHP.", + "homepage": "https://github.com/Bacon/BaconQrCode", + "time": "2017-10-17T09:59:25+00:00" + }, + { + "name": "codacy/coverage", + "version": "1.4.2", + "source": { + "type": "git", + "url": "https://github.com/codacy/php-codacy-coverage.git", + "reference": "4988cd098db4d578681bfd3176071931ad475150" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/codacy/php-codacy-coverage/zipball/4988cd098db4d578681bfd3176071931ad475150", + "reference": "4988cd098db4d578681bfd3176071931ad475150", + "shasum": "" + }, + "require": { + "gitonomy/gitlib": ">=1.0", + "php": ">=5.3.3", + "symfony/console": "~2.5|~3.0|~4.0" + }, + "require-dev": { + "phpunit/phpunit": "~6.5" + }, + "bin": [ + "bin/codacycoverage" + ], + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jakob Pupke", + "email": "jakob.pupke@gmail.com" + } + ], + "description": "Sends PHP test coverage information to Codacy.", + "homepage": "https://github.com/codacy/php-codacy-coverage", + "time": "2018-03-22T16:43:39+00:00" + }, + { + "name": "doctrine/instantiator", + "version": "1.2.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/instantiator.git", + "reference": "a2c590166b2133a4633738648b6b064edae0814a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/a2c590166b2133a4633738648b6b064edae0814a", + "reference": "a2c590166b2133a4633738648b6b064edae0814a", + "shasum": "" + }, + "require": { + "php": "^7.1" + }, + "require-dev": { + "doctrine/coding-standard": "^6.0", + "ext-pdo": "*", + "ext-phar": "*", + "phpbench/phpbench": "^0.13", + "phpstan/phpstan-phpunit": "^0.11", + "phpstan/phpstan-shim": "^0.11", + "phpunit/phpunit": "^7.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2.x-dev" + } + }, + "autoload": { + "psr-4": { + "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com", + "homepage": "http://ocramius.github.com/" + } + ], + "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", + "homepage": "https://www.doctrine-project.org/projects/instantiator.html", + "keywords": [ + "constructor", + "instantiate" + ], + "time": "2019-03-17T17:37:11+00:00" + }, + { + "name": "gitonomy/gitlib", + "version": "v1.1.0", + "source": { + "type": "git", + "url": "https://github.com/gitonomy/gitlib.git", + "reference": "49e599915eae04b734f31e6e88f773d32d921e2e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/gitonomy/gitlib/zipball/49e599915eae04b734f31e6e88f773d32d921e2e", + "reference": "49e599915eae04b734f31e6e88f773d32d921e2e", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.0", + "symfony/process": "^3.4|^4.0" + }, + "require-dev": { + "phpunit/phpunit": "^5.7|^6.5", + "psr/log": "^1.0" + }, + "suggest": { + "psr/log": "Required to use loggers for reporting of execution" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "autoload": { + "psr-4": { + "Gitonomy\\Git\\": "src/Gitonomy/Git/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Alexandre Salomé", + "email": "alexandre.salome@gmail.com", + "homepage": "http://alexandre-salome.fr" + }, + { + "name": "Julien DIDIER", + "email": "genzo.wm@gmail.com", + "homepage": "http://www.jdidier.net" + } + ], + "description": "Library for accessing git", + "homepage": "http://gitonomy.com", + "time": "2019-06-23T09:49:01+00:00" + }, + { + "name": "myclabs/deep-copy", + "version": "1.9.3", + "source": { + "type": "git", + "url": "https://github.com/myclabs/DeepCopy.git", + "reference": "007c053ae6f31bba39dfa19a7726f56e9763bbea" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/007c053ae6f31bba39dfa19a7726f56e9763bbea", + "reference": "007c053ae6f31bba39dfa19a7726f56e9763bbea", + "shasum": "" + }, + "require": { + "php": "^7.1" + }, + "replace": { + "myclabs/deep-copy": "self.version" + }, + "require-dev": { + "doctrine/collections": "^1.0", + "doctrine/common": "^2.6", + "phpunit/phpunit": "^7.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "DeepCopy\\": "src/DeepCopy/" + }, + "files": [ + "src/DeepCopy/deep_copy.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Create deep copies (clones) of your objects", + "keywords": [ + "clone", + "copy", + "duplicate", + "object", + "object graph" + ], + "time": "2019-08-09T12:45:53+00:00" + }, + { + "name": "paragonie/constant_time_encoding", + "version": "v2.2.3", + "source": { + "type": "git", + "url": "https://github.com/paragonie/constant_time_encoding.git", + "reference": "55af0dc01992b4d0da7f6372e2eac097bbbaffdb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/paragonie/constant_time_encoding/zipball/55af0dc01992b4d0da7f6372e2eac097bbbaffdb", + "reference": "55af0dc01992b4d0da7f6372e2eac097bbbaffdb", + "shasum": "" + }, + "require": { + "php": "^7" + }, + "require-dev": { + "phpunit/phpunit": "^6|^7", + "vimeo/psalm": "^1|^2" + }, + "type": "library", + "autoload": { + "psr-4": { + "ParagonIE\\ConstantTime\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Paragon Initiative Enterprises", + "email": "security@paragonie.com", + "homepage": "https://paragonie.com", + "role": "Maintainer" + }, + { + "name": "Steve 'Sc00bz' Thomas", + "email": "steve@tobtu.com", + "homepage": "https://www.tobtu.com", + "role": "Original Developer" + } + ], + "description": "Constant-time Implementations of RFC 4648 Encoding (Base-64, Base-32, Base-16)", + "keywords": [ + "base16", + "base32", + "base32_decode", + "base32_encode", + "base64", + "base64_decode", + "base64_encode", + "bin2hex", + "encoding", + "hex", + "hex2bin", + "rfc4648" + ], + "time": "2019-01-03T20:26:31+00:00" + }, + { + "name": "paragonie/random_compat", + "version": "v9.99.99", + "source": { + "type": "git", + "url": "https://github.com/paragonie/random_compat.git", + "reference": "84b4dfb120c6f9b4ff7b3685f9b8f1aa365a0c95" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/paragonie/random_compat/zipball/84b4dfb120c6f9b4ff7b3685f9b8f1aa365a0c95", + "reference": "84b4dfb120c6f9b4ff7b3685f9b8f1aa365a0c95", + "shasum": "" + }, + "require": { + "php": "^7" + }, + "require-dev": { + "phpunit/phpunit": "4.*|5.*", + "vimeo/psalm": "^1" + }, + "suggest": { + "ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes." + }, + "type": "library", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Paragon Initiative Enterprises", + "email": "security@paragonie.com", + "homepage": "https://paragonie.com" + } + ], + "description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7", + "keywords": [ + "csprng", + "polyfill", + "pseudorandom", + "random" + ], + "time": "2018-07-02T15:55:56+00:00" + }, + { + "name": "phpdocumentor/reflection-common", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionCommon.git", + "reference": "63a995caa1ca9e5590304cd845c15ad6d482a62a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/63a995caa1ca9e5590304cd845c15ad6d482a62a", + "reference": "63a995caa1ca9e5590304cd845c15ad6d482a62a", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "require-dev": { + "phpunit/phpunit": "~6" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jaap van Otterdijk", + "email": "opensource@ijaap.nl" + } + ], + "description": "Common reflection classes used by phpdocumentor to reflect the code structure", + "homepage": "http://www.phpdoc.org", + "keywords": [ + "FQSEN", + "phpDocumentor", + "phpdoc", + "reflection", + "static analysis" + ], + "time": "2018-08-07T13:53:10+00:00" + }, + { + "name": "phpdocumentor/reflection-docblock", + "version": "4.3.2", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", + "reference": "b83ff7cfcfee7827e1e78b637a5904fe6a96698e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/b83ff7cfcfee7827e1e78b637a5904fe6a96698e", + "reference": "b83ff7cfcfee7827e1e78b637a5904fe6a96698e", + "shasum": "" + }, + "require": { + "php": "^7.0", + "phpdocumentor/reflection-common": "^1.0.0 || ^2.0.0", + "phpdocumentor/type-resolver": "~0.4 || ^1.0.0", + "webmozart/assert": "^1.0" + }, + "require-dev": { + "doctrine/instantiator": "^1.0.5", + "mockery/mockery": "^1.0", + "phpunit/phpunit": "^6.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + } + ], + "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", + "time": "2019-09-12T14:27:41+00:00" + }, + { + "name": "phpdocumentor/type-resolver", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/TypeResolver.git", + "reference": "2e32a6d48972b2c1976ed5d8967145b6cec4a4a9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/2e32a6d48972b2c1976ed5d8967145b6cec4a4a9", + "reference": "2e32a6d48972b2c1976ed5d8967145b6cec4a4a9", + "shasum": "" + }, + "require": { + "php": "^7.1", + "phpdocumentor/reflection-common": "^2.0" + }, + "require-dev": { + "ext-tokenizer": "^7.1", + "mockery/mockery": "~1", + "phpunit/phpunit": "^7.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + } + ], + "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", + "time": "2019-08-22T18:11:29+00:00" + }, + { + "name": "phpmyadmin/coding-standard", + "version": "0.3", + "source": { + "type": "git", + "url": "https://github.com/phpmyadmin/coding-standard.git", + "reference": "5ae123e27140a1e16be005432e26a8233524eaf5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpmyadmin/coding-standard/zipball/5ae123e27140a1e16be005432e26a8233524eaf5", + "reference": "5ae123e27140a1e16be005432e26a8233524eaf5", + "shasum": "" + }, + "require": { + "squizlabs/php_codesniffer": "^3.0" + }, + "type": "phpcodesniffer-standard", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "The phpMyAdmin Team", + "email": "developers@phpmyadmin.net", + "homepage": "https://www.phpmyadmin.net/team/" + } + ], + "description": "phpMyAdmin PHP CodeSniffer Coding Standard", + "keywords": [ + "codesniffer", + "phpcs", + "phpmyadmin" + ], + "time": "2017-09-28T09:13:00+00:00" + }, + { + "name": "phpspec/prophecy", + "version": "1.9.0", + "source": { + "type": "git", + "url": "https://github.com/phpspec/prophecy.git", + "reference": "f6811d96d97bdf400077a0cc100ae56aa32b9203" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/f6811d96d97bdf400077a0cc100ae56aa32b9203", + "reference": "f6811d96d97bdf400077a0cc100ae56aa32b9203", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.0.2", + "php": "^5.3|^7.0", + "phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0|^5.0", + "sebastian/comparator": "^1.1|^2.0|^3.0", + "sebastian/recursion-context": "^1.0|^2.0|^3.0" + }, + "require-dev": { + "phpspec/phpspec": "^2.5|^3.2", + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5 || ^7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.8.x-dev" + } + }, + "autoload": { + "psr-4": { + "Prophecy\\": "src/Prophecy" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Konstantin Kudryashov", + "email": "ever.zet@gmail.com", + "homepage": "http://everzet.com" + }, + { + "name": "Marcello Duarte", + "email": "marcello.duarte@gmail.com" + } + ], + "description": "Highly opinionated mocking framework for PHP 5.3+", + "homepage": "https://github.com/phpspec/prophecy", + "keywords": [ + "Double", + "Dummy", + "fake", + "mock", + "spy", + "stub" + ], + "time": "2019-10-03T11:07:50+00:00" + }, + { + "name": "phpunit/php-code-coverage", + "version": "4.0.8", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "ef7b2f56815df854e66ceaee8ebe9393ae36a40d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/ef7b2f56815df854e66ceaee8ebe9393ae36a40d", + "reference": "ef7b2f56815df854e66ceaee8ebe9393ae36a40d", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-xmlwriter": "*", + "php": "^5.6 || ^7.0", + "phpunit/php-file-iterator": "^1.3", + "phpunit/php-text-template": "^1.2", + "phpunit/php-token-stream": "^1.4.2 || ^2.0", + "sebastian/code-unit-reverse-lookup": "^1.0", + "sebastian/environment": "^1.3.2 || ^2.0", + "sebastian/version": "^1.0 || ^2.0" + }, + "require-dev": { + "ext-xdebug": "^2.1.4", + "phpunit/phpunit": "^5.7" + }, + "suggest": { + "ext-xdebug": "^2.5.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "keywords": [ + "coverage", + "testing", + "xunit" + ], + "time": "2017-04-02T07:44:40+00:00" + }, + { + "name": "phpunit/php-file-iterator", + "version": "1.4.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "730b01bc3e867237eaac355e06a36b85dd93a8b4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/730b01bc3e867237eaac355e06a36b85dd93a8b4", + "reference": "730b01bc3e867237eaac355e06a36b85dd93a8b4", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "keywords": [ + "filesystem", + "iterator" + ], + "time": "2017-11-27T13:52:08+00:00" + }, + { + "name": "phpunit/php-text-template", + "version": "1.2.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686", + "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "keywords": [ + "template" + ], + "time": "2015-06-21T13:50:34+00:00" + }, + { + "name": "phpunit/php-timer", + "version": "1.0.9", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3dcf38ca72b158baf0bc245e9184d3fdffa9c46f", + "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f", + "shasum": "" + }, + "require": { + "php": "^5.3.3 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], + "time": "2017-02-26T11:10:40+00:00" + }, + { + "name": "phpunit/php-token-stream", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-token-stream.git", + "reference": "791198a2c6254db10131eecfe8c06670700904db" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/791198a2c6254db10131eecfe8c06670700904db", + "reference": "791198a2c6254db10131eecfe8c06670700904db", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": "^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.2.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Wrapper around PHP's tokenizer extension.", + "homepage": "https://github.com/sebastianbergmann/php-token-stream/", + "keywords": [ + "tokenizer" + ], + "time": "2017-11-27T05:48:46+00:00" + }, + { + "name": "phpunit/phpunit", + "version": "5.7.27", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "b7803aeca3ccb99ad0a506fa80b64cd6a56bbc0c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/b7803aeca3ccb99ad0a506fa80b64cd6a56bbc0c", + "reference": "b7803aeca3ccb99ad0a506fa80b64cd6a56bbc0c", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-xml": "*", + "myclabs/deep-copy": "~1.3", + "php": "^5.6 || ^7.0", + "phpspec/prophecy": "^1.6.2", + "phpunit/php-code-coverage": "^4.0.4", + "phpunit/php-file-iterator": "~1.4", + "phpunit/php-text-template": "~1.2", + "phpunit/php-timer": "^1.0.6", + "phpunit/phpunit-mock-objects": "^3.2", + "sebastian/comparator": "^1.2.4", + "sebastian/diff": "^1.4.3", + "sebastian/environment": "^1.3.4 || ^2.0", + "sebastian/exporter": "~2.0", + "sebastian/global-state": "^1.1", + "sebastian/object-enumerator": "~2.0", + "sebastian/resource-operations": "~1.0", + "sebastian/version": "^1.0.6|^2.0.1", + "symfony/yaml": "~2.1|~3.0|~4.0" + }, + "conflict": { + "phpdocumentor/reflection-docblock": "3.0.2" + }, + "require-dev": { + "ext-pdo": "*" + }, + "suggest": { + "ext-xdebug": "*", + "phpunit/php-invoker": "~1.1" + }, + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.7.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", + "keywords": [ + "phpunit", + "testing", + "xunit" + ], + "time": "2018-02-01T05:50:59+00:00" + }, + { + "name": "phpunit/phpunit-mock-objects", + "version": "3.4.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git", + "reference": "a23b761686d50a560cc56233b9ecf49597cc9118" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/a23b761686d50a560cc56233b9ecf49597cc9118", + "reference": "a23b761686d50a560cc56233b9ecf49597cc9118", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.0.2", + "php": "^5.6 || ^7.0", + "phpunit/php-text-template": "^1.2", + "sebastian/exporter": "^1.2 || ^2.0" + }, + "conflict": { + "phpunit/phpunit": "<5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^5.4" + }, + "suggest": { + "ext-soap": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.2.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Mock Object library for PHPUnit", + "homepage": "https://github.com/sebastianbergmann/phpunit-mock-objects/", + "keywords": [ + "mock", + "xunit" + ], + "abandoned": true, + "time": "2017-06-30T09:13:00+00:00" + }, + { + "name": "phpunit/phpunit-selenium", + "version": "3.0.3", + "source": { + "type": "git", + "url": "https://github.com/giorgiosironi/phpunit-selenium.git", + "reference": "343ba4e389ad97046c78fb2c7111e199795e7a80" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/giorgiosironi/phpunit-selenium/zipball/343ba4e389ad97046c78fb2c7111e199795e7a80", + "reference": "343ba4e389ad97046c78fb2c7111e199795e7a80", + "shasum": "" + }, + "require": { + "ext-curl": "*", + "ext-dom": "*", + "php": ">=5.6", + "phpunit/phpunit": "~5.0", + "sebastian/comparator": "~1.0" + }, + "require-dev": { + "phing/phing": "2.*" + }, + "type": "library", + "autoload": { + "classmap": [ + "PHPUnit/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "include-path": [ + "" + ], + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Giorgio Sironi", + "email": "info@giorgiosironi.com", + "role": "developer" + }, + { + "name": "Ivan Kurnosov", + "email": "zerkms@zerkms.com", + "role": "developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "original developer" + }, + { + "name": "Paul Briton", + "role": "developer" + } + ], + "description": "Selenium Server integration for PHPUnit", + "homepage": "http://www.phpunit.de/", + "keywords": [ + "phpunit", + "selenium", + "testing", + "xunit" + ], + "time": "2017-01-23T22:12:35+00:00" + }, + { + "name": "pragmarx/google2fa", + "version": "v3.0.3", + "source": { + "type": "git", + "url": "https://github.com/antonioribeiro/google2fa.git", + "reference": "6949226739e4424f40031e6f1c96b1fd64047335" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/antonioribeiro/google2fa/zipball/6949226739e4424f40031e6f1c96b1fd64047335", + "reference": "6949226739e4424f40031e6f1c96b1fd64047335", + "shasum": "" + }, + "require": { + "paragonie/constant_time_encoding": "~1.0|~2.0", + "paragonie/random_compat": ">=1", + "php": ">=5.4", + "symfony/polyfill-php56": "~1.2" + }, + "require-dev": { + "bacon/bacon-qr-code": "~1.0", + "phpunit/phpunit": "~4|~5|~6" + }, + "suggest": { + "bacon/bacon-qr-code": "Required to generate inline QR Codes." + }, + "type": "library", + "extra": { + "component": "package", + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "psr-4": { + "PragmaRX\\Google2FA\\": "src/", + "PragmaRX\\Google2FA\\Tests\\": "tests/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Antonio Carlos Ribeiro", + "email": "acr@antoniocarlosribeiro.com", + "role": "Creator & Designer" + } + ], + "description": "A One Time Password Authentication package, compatible with Google Authenticator.", + "keywords": [ + "2fa", + "Authentication", + "Two Factor Authentication", + "google2fa", + "laravel" + ], + "time": "2018-08-29T13:28:06+00:00" + }, + { + "name": "samyoul/u2f-php-server", + "version": "v1.1.4", + "source": { + "type": "git", + "url": "https://github.com/Samyoul/U2F-php-server.git", + "reference": "0625202c79d570e58525ed6c4ae38500ea3f0883" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Samyoul/U2F-php-server/zipball/0625202c79d570e58525ed6c4ae38500ea3f0883", + "reference": "0625202c79d570e58525ed6c4ae38500ea3f0883", + "shasum": "" + }, + "require": { + "ext-openssl": "*" + }, + "type": "library", + "autoload": { + "psr-4": { + "Samyoul\\U2F\\U2FServer\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-2-Clause" + ], + "authors": [ + { + "name": "Samuel Hawksby-Robinson", + "email": "samuel@samyoul.com" + } + ], + "description": "Server side handling class for FIDO U2F registration and authentication", + "time": "2018-10-26T12:43:39+00:00" + }, + { + "name": "sebastian/code-unit-reverse-lookup", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", + "reference": "4419fcdb5eabb9caa61a27c7a1db532a6b55dd18" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/4419fcdb5eabb9caa61a27c7a1db532a6b55dd18", + "reference": "4419fcdb5eabb9caa61a27c7a1db532a6b55dd18", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^5.7 || ^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Looks up which function or method a line of code belongs to", + "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", + "time": "2017-03-04T06:30:41+00:00" + }, + { + "name": "sebastian/comparator", + "version": "1.2.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "2b7424b55f5047b47ac6e5ccb20b2aea4011d9be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/2b7424b55f5047b47ac6e5ccb20b2aea4011d9be", + "reference": "2b7424b55f5047b47ac6e5ccb20b2aea4011d9be", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "sebastian/diff": "~1.2", + "sebastian/exporter": "~1.2 || ~2.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides the functionality to compare PHP values for equality", + "homepage": "http://www.github.com/sebastianbergmann/comparator", + "keywords": [ + "comparator", + "compare", + "equality" + ], + "time": "2017-01-29T09:50:25+00:00" + }, + { + "name": "sebastian/diff", + "version": "1.4.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "7f066a26a962dbe58ddea9f72a4e82874a3975a4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/7f066a26a962dbe58ddea9f72a4e82874a3975a4", + "reference": "7f066a26a962dbe58ddea9f72a4e82874a3975a4", + "shasum": "" + }, + "require": { + "php": "^5.3.3 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", + "keywords": [ + "diff" + ], + "time": "2017-05-22T07:24:03+00:00" + }, + { + "name": "sebastian/environment", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "5795ffe5dc5b02460c3e34222fee8cbe245d8fac" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/5795ffe5dc5b02460c3e34222fee8cbe245d8fac", + "reference": "5795ffe5dc5b02460c3e34222fee8cbe245d8fac", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^5.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides functionality to handle HHVM/PHP environments", + "homepage": "http://www.github.com/sebastianbergmann/environment", + "keywords": [ + "Xdebug", + "environment", + "hhvm" + ], + "time": "2016-11-26T07:53:53+00:00" + }, + { + "name": "sebastian/exporter", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "ce474bdd1a34744d7ac5d6aad3a46d48d9bac4c4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/ce474bdd1a34744d7ac5d6aad3a46d48d9bac4c4", + "reference": "ce474bdd1a34744d7ac5d6aad3a46d48d9bac4c4", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "sebastian/recursion-context": "~2.0" + }, + "require-dev": { + "ext-mbstring": "*", + "phpunit/phpunit": "~4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides the functionality to export PHP variables for visualization", + "homepage": "http://www.github.com/sebastianbergmann/exporter", + "keywords": [ + "export", + "exporter" + ], + "time": "2016-11-19T08:54:04+00:00" + }, + { + "name": "sebastian/global-state", + "version": "1.1.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bc37d50fea7d017d3d340f230811c9f1d7280af4", + "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.2" + }, + "suggest": { + "ext-uopz": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Snapshotting of global state", + "homepage": "http://www.github.com/sebastianbergmann/global-state", + "keywords": [ + "global state" + ], + "time": "2015-10-12T03:26:01+00:00" + }, + { + "name": "sebastian/object-enumerator", + "version": "2.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-enumerator.git", + "reference": "1311872ac850040a79c3c058bea3e22d0f09cbb7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/1311872ac850040a79c3c058bea3e22d0f09cbb7", + "reference": "1311872ac850040a79c3c058bea3e22d0f09cbb7", + "shasum": "" + }, + "require": { + "php": ">=5.6", + "sebastian/recursion-context": "~2.0" + }, + "require-dev": { + "phpunit/phpunit": "~5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Traverses array structures and object graphs to enumerate all referenced objects", + "homepage": "https://github.com/sebastianbergmann/object-enumerator/", + "time": "2017-02-18T15:18:39+00:00" + }, + { + "name": "sebastian/recursion-context", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "2c3ba150cbec723aa057506e73a8d33bdb286c9a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/2c3ba150cbec723aa057506e73a8d33bdb286c9a", + "reference": "2c3ba150cbec723aa057506e73a8d33bdb286c9a", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides functionality to recursively process PHP variables", + "homepage": "http://www.github.com/sebastianbergmann/recursion-context", + "time": "2016-11-19T07:33:16+00:00" + }, + { + "name": "sebastian/resource-operations", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/resource-operations.git", + "reference": "ce990bb21759f94aeafd30209e8cfcdfa8bc3f52" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/ce990bb21759f94aeafd30209e8cfcdfa8bc3f52", + "reference": "ce990bb21759f94aeafd30209e8cfcdfa8bc3f52", + "shasum": "" + }, + "require": { + "php": ">=5.6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides a list of PHP built-in functions that operate on resources", + "homepage": "https://www.github.com/sebastianbergmann/resource-operations", + "time": "2015-07-28T20:34:47+00:00" + }, + { + "name": "sebastian/version", + "version": "2.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/99732be0ddb3361e16ad77b68ba41efc8e979019", + "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019", + "shasum": "" + }, + "require": { + "php": ">=5.6" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that helps with managing the version number of Git-hosted PHP projects", + "homepage": "https://github.com/sebastianbergmann/version", + "time": "2016-10-03T07:35:21+00:00" + }, + { + "name": "squizlabs/php_codesniffer", + "version": "3.5.2", + "source": { + "type": "git", + "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", + "reference": "65b12cdeaaa6cd276d4c3033a95b9b88b12701e7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/65b12cdeaaa6cd276d4c3033a95b9b88b12701e7", + "reference": "65b12cdeaaa6cd276d4c3033a95b9b88b12701e7", + "shasum": "" + }, + "require": { + "ext-simplexml": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0" + }, + "bin": [ + "bin/phpcs", + "bin/phpcbf" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Greg Sherwood", + "role": "lead" + } + ], + "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", + "homepage": "https://github.com/squizlabs/PHP_CodeSniffer", + "keywords": [ + "phpcs", + "standards" + ], + "time": "2019-10-28T04:36:32+00:00" + }, + { + "name": "symfony/console", + "version": "v4.3.5", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "929ddf360d401b958f611d44e726094ab46a7369" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/929ddf360d401b958f611d44e726094ab46a7369", + "reference": "929ddf360d401b958f611d44e726094ab46a7369", + "shasum": "" + }, + "require": { + "php": "^7.1.3", + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php73": "^1.8", + "symfony/service-contracts": "^1.1" + }, + "conflict": { + "symfony/dependency-injection": "<3.4", + "symfony/event-dispatcher": "<4.3", + "symfony/process": "<3.3" + }, + "provide": { + "psr/log-implementation": "1.0" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/config": "~3.4|~4.0", + "symfony/dependency-injection": "~3.4|~4.0", + "symfony/event-dispatcher": "^4.3", + "symfony/lock": "~3.4|~4.0", + "symfony/process": "~3.4|~4.0", + "symfony/var-dumper": "^4.3" + }, + "suggest": { + "psr/log": "For using the console logger", + "symfony/event-dispatcher": "", + "symfony/lock": "", + "symfony/process": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.3-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Console\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Console Component", + "homepage": "https://symfony.com", + "time": "2019-10-07T12:36:49+00:00" + }, + { + "name": "symfony/polyfill-php56", + "version": "v1.12.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php56.git", + "reference": "0e3b212e96a51338639d8ce175c046d7729c3403" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php56/zipball/0e3b212e96a51338639d8ce175c046d7729c3403", + "reference": "0e3b212e96a51338639d8ce175c046d7729c3403", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "symfony/polyfill-util": "~1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.12-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Php56\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 5.6+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "time": "2019-08-06T08:03:45+00:00" + }, + { + "name": "symfony/polyfill-php73", + "version": "v1.12.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php73.git", + "reference": "2ceb49eaccb9352bff54d22570276bb75ba4a188" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/2ceb49eaccb9352bff54d22570276bb75ba4a188", + "reference": "2ceb49eaccb9352bff54d22570276bb75ba4a188", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.12-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Php73\\": "" + }, + "files": [ + "bootstrap.php" + ], + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "time": "2019-08-06T08:03:45+00:00" + }, + { + "name": "symfony/polyfill-util", + "version": "v1.12.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-util.git", + "reference": "4317de1386717b4c22caed7725350a8887ab205c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-util/zipball/4317de1386717b4c22caed7725350a8887ab205c", + "reference": "4317de1386717b4c22caed7725350a8887ab205c", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.12-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Util\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony utilities for portability of PHP codes", + "homepage": "https://symfony.com", + "keywords": [ + "compat", + "compatibility", + "polyfill", + "shim" + ], + "time": "2019-08-06T08:03:45+00:00" + }, + { + "name": "symfony/process", + "version": "v4.3.5", + "source": { + "type": "git", + "url": "https://github.com/symfony/process.git", + "reference": "50556892f3cc47d4200bfd1075314139c4c9ff4b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/process/zipball/50556892f3cc47d4200bfd1075314139c4c9ff4b", + "reference": "50556892f3cc47d4200bfd1075314139c4c9ff4b", + "shasum": "" + }, + "require": { + "php": "^7.1.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.3-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Process\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Process Component", + "homepage": "https://symfony.com", + "time": "2019-09-26T21:17:10+00:00" + }, + { + "name": "symfony/yaml", + "version": "v4.3.5", + "source": { + "type": "git", + "url": "https://github.com/symfony/yaml.git", + "reference": "41e16350a2a1c7383c4735aa2f9fce74cf3d1178" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/yaml/zipball/41e16350a2a1c7383c4735aa2f9fce74cf3d1178", + "reference": "41e16350a2a1c7383c4735aa2f9fce74cf3d1178", + "shasum": "" + }, + "require": { + "php": "^7.1.3", + "symfony/polyfill-ctype": "~1.8" + }, + "conflict": { + "symfony/console": "<3.4" + }, + "require-dev": { + "symfony/console": "~3.4|~4.0" + }, + "suggest": { + "symfony/console": "For validating YAML files using the lint command" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.3-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Yaml\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Yaml Component", + "homepage": "https://symfony.com", + "time": "2019-09-11T15:41:19+00:00" + }, + { + "name": "tecnickcom/tcpdf", + "version": "6.3.2", + "source": { + "type": "git", + "url": "https://github.com/tecnickcom/TCPDF.git", + "reference": "9fde7bb9b404b945e7ea88fb7eccd23d9a4e324b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/tecnickcom/TCPDF/zipball/9fde7bb9b404b945e7ea88fb7eccd23d9a4e324b", + "reference": "9fde7bb9b404b945e7ea88fb7eccd23d9a4e324b", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "config", + "include", + "tcpdf.php", + "tcpdf_parser.php", + "tcpdf_import.php", + "tcpdf_barcodes_1d.php", + "tcpdf_barcodes_2d.php", + "include/tcpdf_colors.php", + "include/tcpdf_filters.php", + "include/tcpdf_font_data.php", + "include/tcpdf_fonts.php", + "include/tcpdf_images.php", + "include/tcpdf_static.php", + "include/barcodes/datamatrix.php", + "include/barcodes/pdf417.php", + "include/barcodes/qrcode.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-3.0" + ], + "authors": [ + { + "name": "Nicola Asuni", + "email": "info@tecnick.com", + "role": "lead" + } + ], + "description": "TCPDF is a PHP class for generating PDF documents and barcodes.", + "homepage": "http://www.tcpdf.org/", + "keywords": [ + "PDFD32000-2008", + "TCPDF", + "barcodes", + "datamatrix", + "pdf", + "pdf417", + "qrcode" + ], + "time": "2019-09-20T09:35:01+00:00" + }, + { + "name": "webmozart/assert", + "version": "1.5.0", + "source": { + "type": "git", + "url": "https://github.com/webmozart/assert.git", + "reference": "88e6d84706d09a236046d686bbea96f07b3a34f4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webmozart/assert/zipball/88e6d84706d09a236046d686bbea96f07b3a34f4", + "reference": "88e6d84706d09a236046d686bbea96f07b3a34f4", + "shasum": "" + }, + "require": { + "php": "^5.3.3 || ^7.0", + "symfony/polyfill-ctype": "^1.8" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.36 || ^7.5.13" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3-dev" + } + }, + "autoload": { + "psr-4": { + "Webmozart\\Assert\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Assertions to validate method input/output with nice error messages.", + "keywords": [ + "assert", + "check", + "validate" + ], + "time": "2019-08-24T08:43:50+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": ">=5.5.0", + "ext-mysqli": "*", + "ext-xml": "*", + "ext-pcre": "*", + "ext-json": "*", + "ext-ctype": "*", + "ext-hash": "*" + }, + "platform-dev": [] +} diff --git a/php/apps/phpmyadmin49/config.inc.php b/php/apps/phpmyadmin49/config.inc.php new file mode 100644 index 00000000..bc4b4775 --- /dev/null +++ b/php/apps/phpmyadmin49/config.inc.php @@ -0,0 +1,159 @@ +. + * + * @package PhpMyAdmin + */ + +/** + * This is needed for cookie based authentication to encrypt password in + * cookie. Needs to be 32 chars long. + */ +$cfg['blowfish_secret'] = '1{dd0`. + */ diff --git a/php/apps/phpmyadmin49/config.sample.inc.php b/php/apps/phpmyadmin49/config.sample.inc.php new file mode 100644 index 00000000..5eede6dd --- /dev/null +++ b/php/apps/phpmyadmin49/config.sample.inc.php @@ -0,0 +1,154 @@ +. + * + * @package PhpMyAdmin + */ + +/** + * This is needed for cookie based authentication to encrypt password in + * cookie. Needs to be 32 chars long. + */ +$cfg['blowfish_secret'] = ''; /* YOU MUST FILL IN THIS FOR COOKIE AUTH! */ + +/** + * Servers configuration + */ +$i = 0; + +/** + * First server + */ +$i++; +/* Authentication type */ +$cfg['Servers'][$i]['auth_type'] = 'cookie'; +/* Server parameters */ +$cfg['Servers'][$i]['host'] = 'localhost'; +$cfg['Servers'][$i]['compress'] = false; +$cfg['Servers'][$i]['AllowNoPassword'] = false; + +/** + * phpMyAdmin configuration storage settings. + */ + +/* User used to manipulate with storage */ +// $cfg['Servers'][$i]['controlhost'] = ''; +// $cfg['Servers'][$i]['controlport'] = ''; +// $cfg['Servers'][$i]['controluser'] = 'pma'; +// $cfg['Servers'][$i]['controlpass'] = 'pmapass'; + +/* Storage database and tables */ +// $cfg['Servers'][$i]['pmadb'] = 'phpmyadmin'; +// $cfg['Servers'][$i]['bookmarktable'] = 'pma__bookmark'; +// $cfg['Servers'][$i]['relation'] = 'pma__relation'; +// $cfg['Servers'][$i]['table_info'] = 'pma__table_info'; +// $cfg['Servers'][$i]['table_coords'] = 'pma__table_coords'; +// $cfg['Servers'][$i]['pdf_pages'] = 'pma__pdf_pages'; +// $cfg['Servers'][$i]['column_info'] = 'pma__column_info'; +// $cfg['Servers'][$i]['history'] = 'pma__history'; +// $cfg['Servers'][$i]['table_uiprefs'] = 'pma__table_uiprefs'; +// $cfg['Servers'][$i]['tracking'] = 'pma__tracking'; +// $cfg['Servers'][$i]['userconfig'] = 'pma__userconfig'; +// $cfg['Servers'][$i]['recent'] = 'pma__recent'; +// $cfg['Servers'][$i]['favorite'] = 'pma__favorite'; +// $cfg['Servers'][$i]['users'] = 'pma__users'; +// $cfg['Servers'][$i]['usergroups'] = 'pma__usergroups'; +// $cfg['Servers'][$i]['navigationhiding'] = 'pma__navigationhiding'; +// $cfg['Servers'][$i]['savedsearches'] = 'pma__savedsearches'; +// $cfg['Servers'][$i]['central_columns'] = 'pma__central_columns'; +// $cfg['Servers'][$i]['designer_settings'] = 'pma__designer_settings'; +// $cfg['Servers'][$i]['export_templates'] = 'pma__export_templates'; + +/** + * End of servers configuration + */ + +/** + * Directories for saving/loading files from server + */ +$cfg['UploadDir'] = ''; +$cfg['SaveDir'] = ''; + +/** + * Whether to display icons or text or both icons and text in table row + * action segment. Value can be either of 'icons', 'text' or 'both'. + * default = 'both' + */ +//$cfg['RowActionType'] = 'icons'; + +/** + * Defines whether a user should be displayed a "show all (records)" + * button in browse mode or not. + * default = false + */ +//$cfg['ShowAll'] = true; + +/** + * Number of rows displayed when browsing a result set. If the result + * set contains more rows, "Previous" and "Next". + * Possible values: 25, 50, 100, 250, 500 + * default = 25 + */ +//$cfg['MaxRows'] = 50; + +/** + * Disallow editing of binary fields + * valid values are: + * false allow editing + * 'blob' allow editing except for BLOB fields + * 'noblob' disallow editing except for BLOB fields + * 'all' disallow editing + * default = 'blob' + */ +//$cfg['ProtectBinary'] = false; + +/** + * Default language to use, if not browser-defined or user-defined + * (you find all languages in the locale folder) + * uncomment the desired line: + * default = 'en' + */ +//$cfg['DefaultLang'] = 'en'; +//$cfg['DefaultLang'] = 'de'; + +/** + * How many columns should be used for table display of a database? + * (a value larger than 1 results in some information being hidden) + * default = 1 + */ +//$cfg['PropertiesNumColumns'] = 2; + +/** + * Set to true if you want DB-based query history.If false, this utilizes + * JS-routines to display query history (lost by window close) + * + * This requires configuration storage enabled, see above. + * default = false + */ +//$cfg['QueryHistoryDB'] = true; + +/** + * When using DB-based query history, how many entries should be kept? + * default = 25 + */ +//$cfg['QueryHistoryMax'] = 100; + +/** + * Whether or not to query the user before sending the error report to + * the phpMyAdmin team when a JavaScript error occurs + * + * Available options + * ('ask' | 'always' | 'never') + * default = 'ask' + */ +//$cfg['SendErrorReports'] = 'always'; + +/** + * You can find more configuration options in the documentation + * in the doc/ folder or at . + */ diff --git a/php/apps/phpmyadmin49/db_central_columns.php b/php/apps/phpmyadmin49/db_central_columns.php new file mode 100644 index 00000000..6eebb40a --- /dev/null +++ b/php/apps/phpmyadmin49/db_central_columns.php @@ -0,0 +1,181 @@ +updateOneColumn( + $db, $orig_col_name, $col_name, $col_type, $col_attribute, + $col_length, $col_isNull, $collation, $col_extra, $col_default + ); + exit; + } else { + $tmp_msg = $centralColumns->updateOneColumn( + $db, "", $col_name, $col_type, $col_attribute, + $col_length, $col_isNull, $collation, $col_extra, $col_default + ); + } +} +if (isset($_POST['populateColumns'])) { + $selected_tbl = $_POST['selectedTable']; + echo $centralColumns->getHtmlForColumnDropdown( + $db, + $selected_tbl + ); + exit; +} +if (isset($_POST['getColumnList'])) { + echo $centralColumns->getListRaw( + $db, + $_POST['cur_table'] + ); + exit; +} +if (isset($_POST['add_column'])) { + $selected_col = array(); + $selected_tbl = $_POST['table-select']; + $selected_col[] = $_POST['column-select']; + $tmp_msg = $centralColumns->syncUniqueColumns( + $selected_col, + false, + $selected_tbl + ); +} +$response = Response::getInstance(); +$header = $response->getHeader(); +$scripts = $header->getScripts(); +$scripts->addFile('vendor/jquery/jquery.uitablefilter.js'); +$scripts->addFile('vendor/jquery/jquery.tablesorter.js'); +$scripts->addFile('db_central_columns.js'); +$cfgCentralColumns = $centralColumns->getParams(); +$pmadb = $cfgCentralColumns['db']; +$pmatable = $cfgCentralColumns['table']; +$max_rows = intval($GLOBALS['cfg']['MaxRows']); + +if (isset($_POST['edit_central_columns_page'])) { + $selected_fld = $_POST['selected_fld']; + $selected_db = $_POST['db']; + $edit_central_column_page = $centralColumns->getHtmlForEditingPage( + $selected_fld, + $selected_db + ); + $response->addHTML($edit_central_column_page); + exit; +} +if (isset($_POST['multi_edit_central_column_save'])) { + $message = $centralColumns->updateMultipleColumn(); + if (!is_bool($message)) { + $response->setRequestStatus(false); + $response->addJSON('message', $message); + } +} +if (isset($_POST['delete_save'])) { + $col_name = array(); + parse_str($_POST['col_name'], $col_name); + $tmp_msg = $centralColumns->deleteColumnsFromList( + $col_name['selected_fld'], + false + ); +} +if (!empty($_POST['total_rows']) + && Core::isValid($_POST['total_rows'], 'integer') +) { + $total_rows = $_POST['total_rows']; +} else { + $total_rows = $centralColumns->getCount($db); +} +if (Core::isValid($_POST['pos'], 'integer')) { + $pos = intval($_POST['pos']); +} else { + $pos = 0; +} +$addNewColumn = $centralColumns->getHtmlForAddNewColumn($db, $total_rows); +$response->addHTML($addNewColumn); +if ($total_rows <= 0) { + $response->addHTML( + '
' . __( + 'The central list of columns for the current database is empty.' + ) . '
' + ); + $columnAdd = $centralColumns->getHtmlForAddColumn($total_rows, $pos, $db); + $response->addHTML($columnAdd); + exit; +} +$table_navigation_html = $centralColumns->getHtmlForTableNavigation( + $total_rows, + $pos, + $db +); +$response->addHTML($table_navigation_html); +$columnAdd = $centralColumns->getHtmlForAddColumn($total_rows, $pos, $db); +$response->addHTML($columnAdd); +$deleteRowForm = '
' + . Url::getHiddenInputs( + $db + ) + . '' + . '' + . '
'; +$response->addHTML($deleteRowForm); +$table_struct = '
' + . '
' + . ''; +$response->addHTML($table_struct); +$tableheader = $centralColumns->getTableHeader( + 'column_heading', __('Click to sort.'), 2 +); +$response->addHTML($tableheader); +$result = $centralColumns->getColumnsList($db, $pos, $max_rows); +$row_num = 0; +foreach ($result as $row) { + $tableHtmlRow = $centralColumns->getHtmlForTableRow( + $row, + $row_num, + $db + ); + $response->addHTML($tableHtmlRow); + $row_num++; +} +$response->addHTML('
'); +$tablefooter = $centralColumns->getTableFooter($pmaThemeImage, $text_dir); +$response->addHTML($tablefooter); +$response->addHTML('
'); +$message = Message::success( + sprintf(__('Showing rows %1$s - %2$s.'), ($pos + 1), ($pos + count($result))) +); +if (isset($tmp_msg) && $tmp_msg !== true) { + $message = $tmp_msg; +} diff --git a/php/apps/phpmyadmin49/db_datadict.php b/php/apps/phpmyadmin49/db_datadict.php new file mode 100644 index 00000000..7a5d7f4f --- /dev/null +++ b/php/apps/phpmyadmin49/db_datadict.php @@ -0,0 +1,215 @@ +getHeader(); +$header->enablePrintView(); + +$relation = new Relation(); + +/** + * Gets the relations settings + */ +$cfgRelation = $relation->getRelationsParam(); + +/** + * Check parameters + */ +PhpMyAdmin\Util::checkParameters(array('db')); + +/** + * Defines the url to return to in case of error in a sql statement + */ +$err_url = 'db_sql.php' . Url::getCommon(array('db' => $db)); + +if ($cfgRelation['commwork']) { + $comment = $relation->getDbComment($db); + + /** + * Displays DB comment + */ + if ($comment) { + echo '

' , __('Database comment') + , '
' , htmlspecialchars($comment) , '

'; + } // end if +} + +/** + * Selects the database and gets tables names + */ +$GLOBALS['dbi']->selectDb($db); +$tables = $GLOBALS['dbi']->getTables($db); + +$count = 0; +foreach ($tables as $table) { + $comments = $relation->getComments($db, $table); + + echo '
' , "\n"; + + echo '

' , htmlspecialchars($table) , '

' , "\n"; + + /** + * Gets table information + */ + $show_comment = $GLOBALS['dbi']->getTable($db, $table) + ->getStatusInfo('TABLE_COMMENT'); + + /** + * Gets table keys and retains them + */ + $GLOBALS['dbi']->selectDb($db); + $indexes = $GLOBALS['dbi']->getTableIndexes($db, $table); + list($primary, $pk_array, $indexes_info, $indexes_data) + = PhpMyAdmin\Util::processIndexData($indexes); + + /** + * Gets columns properties + */ + $columns = $GLOBALS['dbi']->getColumns($db, $table); + + // Check if we can use Relations + list($res_rel, $have_rel) = $relation->getRelationsAndStatus( + ! empty($cfgRelation['relation']), $db, $table + ); + + /** + * Displays the comments of the table if MySQL >= 3.23 + */ + if (!empty($show_comment)) { + echo __('Table comments:') , ' '; + echo htmlspecialchars($show_comment) , '

'; + } + + /** + * Displays the table structure + */ + + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + if ($have_rel) { + echo ' ' , "\n"; + } + echo ' ' , "\n"; + if ($cfgRelation['mimework']) { + echo ' ' , "\n"; + } + echo ''; + foreach ($columns as $row) { + + if ($row['Null'] == '') { + $row['Null'] = 'NO'; + } + $extracted_columnspec + = PhpMyAdmin\Util::extractColumnSpec($row['Type']); + + // reformat mysql query output + // set or enum types: slashes single quotes inside options + + $type = htmlspecialchars($extracted_columnspec['print_type']); + $attribute = $extracted_columnspec['attribute']; + if (! isset($row['Default'])) { + if ($row['Null'] != 'NO') { + $row['Default'] = 'NULL'; + } + } else { + $row['Default'] = htmlspecialchars($row['Default']); + } + $column_name = $row['Field']; + + echo ''; + echo ''; + echo '' , $type , ''; + + echo ''; + echo ''; + + if ($have_rel) { + echo ' ' , "\n"; + } + echo ' ' , "\n"; + if ($cfgRelation['mimework']) { + $mime_map = Transformations::getMIME($db, $table, true); + + echo ' ' , "\n"; + } + echo ''; + } // end foreach + $count++; + echo '
' , __('Column') , '' , __('Type') , '' , __('Null') , '' , __('Default') , '' , __('Links to') , '' , __('Comments') , 'MIME
'; + echo htmlspecialchars($column_name); + + if (isset($pk_array[$row['Field']])) { + echo ' (' , __('Primary') , ')'; + } + echo ''; + echo (($row['Null'] == 'NO') ? __('No') : __('Yes')); + echo ''; + if (isset($row['Default'])) { + echo $row['Default']; + } + echo ''; + if ($foreigner = $relation->searchColumnInForeigners($res_rel, $column_name)) { + echo htmlspecialchars( + $foreigner['foreign_table'] + . ' -> ' + . $foreigner['foreign_field'] + ); + } + echo ''; + if (isset($comments[$column_name])) { + echo htmlspecialchars($comments[$column_name]); + } + echo ''; + if (isset($mime_map[$column_name])) { + echo htmlspecialchars( + str_replace('_', '/', $mime_map[$column_name]['mimetype']) + ); + } + echo '
'; + // display indexes information + if (count(PhpMyAdmin\Index::getFromTable($table, $db)) > 0) { + echo PhpMyAdmin\Index::getHtmlForIndexes($table, $db, true); + } + echo '
'; +} //ends main while + +/** + * Displays the footer + */ +echo PhpMyAdmin\Util::getButton(); diff --git a/php/apps/phpmyadmin49/db_designer.php b/php/apps/phpmyadmin49/db_designer.php new file mode 100644 index 00000000..e26f4b47 --- /dev/null +++ b/php/apps/phpmyadmin49/db_designer.php @@ -0,0 +1,228 @@ +getHtmlForEditOrDeletePages($_POST['db'], 'editPage'); + } elseif ($_POST['dialog'] == 'delete') { + $html = $databaseDesigner->getHtmlForEditOrDeletePages($_POST['db'], 'deletePage'); + } elseif ($_POST['dialog'] == 'save_as') { + $html = $databaseDesigner->getHtmlForPageSaveAs($_POST['db']); + } elseif ($_POST['dialog'] == 'export') { + $html = $databaseDesigner->getHtmlForSchemaExport( + $_POST['db'], $_POST['selected_page'] + ); + } elseif ($_POST['dialog'] == 'add_table') { + // Pass the db and table to the getTablesInfo so we only have the table we asked for + $script_display_field = $designerCommon->getTablesInfo($_POST['db'], $_POST['table']); + $tab_column = $designerCommon->getColumnsInfo($script_display_field); + $tables_all_keys = $designerCommon->getAllKeys($script_display_field); + $tables_pk_or_unique_keys = $designerCommon->getPkOrUniqueKeys($script_display_field); + + $html = $databaseDesigner->getDatabaseTables( + $_POST['db'], + $script_display_field, + array(), -1, $tab_column, + $tables_all_keys, $tables_pk_or_unique_keys + ); + } + + if (! empty($html)) { + $response->addHTML($html); + } + return; +} + +if (isset($_POST['operation'])) { + + if ($_POST['operation'] == 'deletePage') { + $success = $designerCommon->deletePage($_POST['selected_page']); + $response->setRequestStatus($success); + } elseif ($_POST['operation'] == 'savePage') { + if ($_POST['save_page'] == 'same') { + $page = $_POST['selected_page']; + } else { // new + $page = $designerCommon->createNewPage($_POST['selected_value'], $_POST['db']); + $response->addJSON('id', $page); + } + $success = $designerCommon->saveTablePositions($page); + $response->setRequestStatus($success); + } elseif ($_POST['operation'] == 'setDisplayField') { + $designerCommon->saveDisplayField( + $_POST['db'], $_POST['table'], $_POST['field'] + ); + $response->setRequestStatus(true); + } elseif ($_POST['operation'] == 'addNewRelation') { + list($success, $message) = $designerCommon->addNewRelation( + $_POST['db'], + $_POST['T1'], + $_POST['F1'], + $_POST['T2'], + $_POST['F2'], + $_POST['on_delete'], + $_POST['on_update'], + $_POST['DB1'], + $_POST['DB2'] + ); + $response->setRequestStatus($success); + $response->addJSON('message', $message); + } elseif ($_POST['operation'] == 'removeRelation') { + list($success, $message) = $designerCommon->removeRelation( + $_POST['T1'], + $_POST['F1'], + $_POST['T2'], + $_POST['F2'] + ); + $response->setRequestStatus($success); + $response->addJSON('message', $message); + } elseif ($_POST['operation'] == 'save_setting_value') { + $success = $designerCommon->saveSetting($_POST['index'], $_POST['value']); + $response->setRequestStatus($success); + } + + return; +} + +require 'libraries/db_common.inc.php'; + +$script_display_field = $designerCommon->getTablesInfo(); + +$display_page = -1; +$selected_page = null; + +if (isset($_GET['query'])) { + $display_page = $designerCommon->getDefaultPage($_GET['db']); +} else { + if (! empty($_GET['page'])) { + $display_page = $_GET['page']; + } else { + $display_page = $designerCommon->getLoadingPage($_GET['db']); + } +} +if ($display_page != -1) { + $selected_page = $designerCommon->getPageName($display_page); +} +$tab_pos = $designerCommon->getTablePositions($display_page); + +$fullTableNames = []; + +foreach($script_display_field as $designerTable) { + $fullTableNames[] = $designerTable->getDbTableString(); +} + +foreach($tab_pos as $position) { + if (! in_array($position['dbName'] . '.' . $position['tableName'], $fullTableNames)) { + foreach($designerCommon->getTablesInfo($position['dbName'], $position['tableName']) as $designerTable) { + $script_display_field[] = $designerTable; + } + } +} + + +$tab_column = $designerCommon->getColumnsInfo($script_display_field); +$script_tables = $designerCommon->getScriptTabs($script_display_field); +$tables_pk_or_unique_keys = $designerCommon->getPkOrUniqueKeys($script_display_field); +$tables_all_keys = $designerCommon->getAllKeys($script_display_field); +$classes_side_menu = $databaseDesigner->returnClassNamesFromMenuButtons(); + + +$script_contr = $designerCommon->getScriptContr($script_display_field); + +$params = array('lang' => $GLOBALS['lang']); +if (isset($_GET['db'])) { + $params['db'] = $_GET['db']; +} + +$response = Response::getInstance(); +$response->getFooter()->setMinimal(); +$header = $response->getHeader(); +$header->setBodyId('designer_body'); + +$scripts = $header->getScripts(); +$scripts->addFile('vendor/jquery/jquery.fullscreen.js'); +$scripts->addFile('designer/database.js'); +$scripts->addFile('designer/objects.js'); +$scripts->addFile('designer/page.js'); +$scripts->addFile('designer/history.js'); +$scripts->addFile('designer/move.js'); +$scripts->addFile('designer/init.js'); + +list( + $tables, + $num_tables, + $total_num_tables, + $sub_part, + $is_show_stats, + $db_is_system_schema, + $tooltip_truename, + $tooltip_aliasname, + $pos +) = PhpMyAdmin\Util::getDbInfo($db, isset($sub_part) ? $sub_part : ''); + +// Embed some data into HTML, later it will be read +// by designer/init.js and converted to JS variables. +$response->addHTML( + $databaseDesigner->getHtmlForJsFields( + $script_tables, $script_contr, $script_display_field, $display_page + ) +); +$response->addHTML( + $databaseDesigner->getPageMenu( + isset($_GET['query']), + $selected_page, + $classes_side_menu + ) +); + + + +$response->addHTML('
'); +$response->addHTML( + '
' +); + +$response->addHTML($databaseDesigner->getHtmlCanvas()); +$response->addHTML($databaseDesigner->getHtmlTableList()); + +$response->addHTML( + $databaseDesigner->getDatabaseTables( + $_GET['db'], + $script_display_field, + $tab_pos, $display_page, $tab_column, + $tables_all_keys, $tables_pk_or_unique_keys + ) +); +$response->addHTML('
'); +$response->addHTML('
'); // end canvas_outer + +$response->addHTML('
'); + +$response->addHTML($databaseDesigner->getNewRelationPanel()); +$response->addHTML($databaseDesigner->getDeleteRelationPanel()); + +if (isset($_GET['query'])) { + $response->addHTML($databaseDesigner->getOptionsPanel()); + $response->addHTML($databaseDesigner->getRenameToPanel()); + $response->addHTML($databaseDesigner->getHavingQueryPanel()); + $response->addHTML($databaseDesigner->getAggregateQueryPanel()); + $response->addHTML($databaseDesigner->getWhereQueryPanel()); + $response->addHTML($databaseDesigner->getQueryDetails($_GET['db'])); +} + +$response->addHTML('
'); diff --git a/php/apps/phpmyadmin49/db_events.php b/php/apps/phpmyadmin49/db_events.php new file mode 100644 index 00000000..e13935fc --- /dev/null +++ b/php/apps/phpmyadmin49/db_events.php @@ -0,0 +1,18 @@ +getHeader(); +$scripts = $header->getScripts(); +$scripts->addFile('export.js'); + +// $sub_part is used in Util::getDbInfo() to see if we are coming from +// db_export.php, in which case we don't obey $cfg['MaxTableList'] +$sub_part = '_export'; +require_once 'libraries/db_common.inc.php'; +$url_query .= '&goto=db_export.php'; + +list( + $tables, + $num_tables, + $total_num_tables, + $sub_part, + $is_show_stats, + $db_is_system_schema, + $tooltip_truename, + $tooltip_aliasname, + $pos +) = Util::getDbInfo($db, isset($sub_part) ? $sub_part : ''); + +/** + * Displays the form + */ +$export_page_title = __('View dump (schema) of database'); + +// exit if no tables in db found +if ($num_tables < 1) { + $response->addHTML( + Message::error(__('No tables found in database.'))->getDisplay() + ); + exit; +} // end if + +$multi_values = '
'; +if (isset($_POST['structure_or_data_forced'])) { + $force_val = htmlspecialchars($_POST['structure_or_data_forced']); +} else { + $force_val = 0; +} +$multi_values .= ''; +$multi_values .= '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . ''; +$multi_values .= "\n"; + +// when called by libraries/mult_submits.inc.php +if (!empty($_POST['selected_tbl']) && empty($table_select)) { + $table_select = $_POST['selected_tbl']; +} + +foreach ($tables as $each_table) { + if (isset($_POST['table_select']) && is_array($_POST['table_select'])) { + $is_checked = Export::getCheckedClause( + $each_table['Name'], $_POST['table_select'] + ); + } elseif (isset($table_select)) { + $is_checked = Export::getCheckedClause( + $each_table['Name'], $table_select + ); + } else { + $is_checked = ' checked="checked"'; + } + if (isset($_POST['table_structure']) && is_array($_POST['table_structure'])) { + $structure_checked = Export::getCheckedClause( + $each_table['Name'], $_POST['table_structure'] + ); + } else { + $structure_checked = $is_checked; + } + if (isset($_POST['table_data']) && is_array($_POST['table_data'])) { + $data_checked = Export::getCheckedClause( + $each_table['Name'], $_POST['table_data'] + ); + } else { + $data_checked = $is_checked; + } + $table_html = htmlspecialchars($each_table['Name']); + $multi_values .= ''; + $multi_values .= ''; + $multi_values .= ''; + $multi_values .= ''; + $multi_values .= ''; + $multi_values .= ''; +} // end for + +$multi_values .= "\n"; +$multi_values .= '
' . __('Tables') . '' . __('Structure') . '' . __('Data') . '
' . __('Select all') . '' + . '' + . '
' + . str_replace(' ', ' ', $table_html) . '' + . '' + . '
'; + +if (! isset($sql_query)) { + $sql_query = ''; +} +if (! isset($num_tables)) { + $num_tables = 0; +} +if (! isset($unlim_num_rows)) { + $unlim_num_rows = 0; +} +if (! isset($multi_values)) { + $multi_values = ''; +} +$response = Response::getInstance(); +$displayExport = new DisplayExport(); +$response->addHTML( + $displayExport->getDisplay( + 'database', $db, $table, $sql_query, $num_tables, + $unlim_num_rows, $multi_values + ) +); diff --git a/php/apps/phpmyadmin49/db_import.php b/php/apps/phpmyadmin49/db_import.php new file mode 100644 index 00000000..0ec1d49e --- /dev/null +++ b/php/apps/phpmyadmin49/db_import.php @@ -0,0 +1,44 @@ +getHeader(); +$scripts = $header->getScripts(); +$scripts->addFile('import.js'); + +/** + * Gets tables information and displays top links + */ +require 'libraries/db_common.inc.php'; + +list( + $tables, + $num_tables, + $total_num_tables, + $sub_part, + $is_show_stats, + $db_is_system_schema, + $tooltip_truename, + $tooltip_aliasname, + $pos +) = PhpMyAdmin\Util::getDbInfo($db, isset($sub_part) ? $sub_part : ''); + +$response = Response::getInstance(); +$response->addHTML( + Import::get( + 'database', $db, $table, $max_upload_size + ) +); diff --git a/php/apps/phpmyadmin49/db_multi_table_query.php b/php/apps/phpmyadmin49/db_multi_table_query.php new file mode 100644 index 00000000..e30f15d3 --- /dev/null +++ b/php/apps/phpmyadmin49/db_multi_table_query.php @@ -0,0 +1,38 @@ +getForeignKeyConstrains( + $_REQUEST['db'], + $_GET['tables'] + ); + $response = Response::getInstance(); + $response->addJSON('foreignKeyConstrains',$constrains); +} else { + $response = Response::getInstance(); + + $header = $response->getHeader(); + $scripts = $header->getScripts(); + $scripts->addFile('vendor/jquery/jquery.md5.js'); + $scripts->addFile('db_multi_table_query.js'); + $scripts->addFile('db_query_generator.js'); + + $queryInstance = new MultiTableQuery($GLOBALS['dbi'], $db); + + $response->addHTML($queryInstance->getFormHtml()); +} diff --git a/php/apps/phpmyadmin49/db_operations.php b/php/apps/phpmyadmin49/db_operations.php new file mode 100644 index 00000000..c5e99783 --- /dev/null +++ b/php/apps/phpmyadmin49/db_operations.php @@ -0,0 +1,308 @@ +getHeader(); +$scripts = $header->getScripts(); +$scripts->addFile('db_operations.js'); + +$sql_query = ''; + +$operations = new Operations(); + +/** + * Rename/move or copy database + */ +if (strlen($GLOBALS['db']) > 0 + && (! empty($_POST['db_rename']) || ! empty($_POST['db_copy'])) +) { + if (! empty($_POST['db_rename'])) { + $move = true; + } else { + $move = false; + } + + if (! isset($_POST['newname']) || strlen($_POST['newname']) === 0) { + $message = Message::error(__('The database name is empty!')); + } else { + // lower_case_table_names=1 `DB` becomes `db` + if ($GLOBALS['dbi']->getLowerCaseNames() === '1') { + $_POST['newname'] = mb_strtolower( + $_POST['newname'] + ); + } + + if ($_POST['newname'] === $_REQUEST['db']) { + $message = Message::error( + __('Cannot copy database to the same name. Change the name and try again.') + ); + } else { + $_error = false; + if ($move || ! empty($_POST['create_database_before_copying'])) { + $operations->createDbBeforeCopy(); + } + + // here I don't use DELIMITER because it's not part of the + // language; I have to send each statement one by one + + // to avoid selecting alternatively the current and new db + // we would need to modify the CREATE definitions to qualify + // the db name + $operations->runProcedureAndFunctionDefinitions($GLOBALS['db']); + + // go back to current db, just in case + $GLOBALS['dbi']->selectDb($GLOBALS['db']); + + $tables_full = $GLOBALS['dbi']->getTablesFull($GLOBALS['db']); + + // remove all foreign key constraints, otherwise we can get errors + /* @var $export_sql_plugin ExportSql */ + $export_sql_plugin = Plugins::getPlugin( + "export", + "sql", + 'libraries/classes/Plugins/Export/', + array( + 'single_table' => isset($single_table), + 'export_type' => 'database' + ) + ); + + // create stand-in tables for views + $views = $operations->getViewsAndCreateSqlViewStandIn( + $tables_full, $export_sql_plugin, $GLOBALS['db'] + ); + + // copy tables + $sqlConstratints = $operations->copyTables( + $tables_full, $move, $GLOBALS['db'] + ); + + // handle the views + if (! $_error) { + $operations->handleTheViews($views, $move, $GLOBALS['db']); + } + unset($views); + + // now that all tables exist, create all the accumulated constraints + if (! $_error && count($sqlConstratints) > 0) { + $operations->createAllAccumulatedConstraints($sqlConstratints); + } + unset($sqlConstratints); + + if ($GLOBALS['dbi']->getVersion() >= 50100) { + // here DELIMITER is not used because it's not part of the + // language; each statement is sent one by one + + $operations->runEventDefinitionsForDb($GLOBALS['db']); + } + + // go back to current db, just in case + $GLOBALS['dbi']->selectDb($GLOBALS['db']); + + // Duplicate the bookmarks for this db (done once for each db) + $operations->duplicateBookmarks($_error, $GLOBALS['db']); + + if (! $_error && $move) { + if (isset($_POST['adjust_privileges']) + && ! empty($_POST['adjust_privileges']) + ) { + $operations->adjustPrivilegesMoveDb($GLOBALS['db'], $_POST['newname']); + } + + /** + * cleanup pmadb stuff for this db + */ + RelationCleanup::database($GLOBALS['db']); + + // if someday the RENAME DATABASE reappears, do not DROP + $local_query = 'DROP DATABASE ' + . Util::backquote($GLOBALS['db']) . ';'; + $sql_query .= "\n" . $local_query; + $GLOBALS['dbi']->query($local_query); + + $message = Message::success( + __('Database %1$s has been renamed to %2$s.') + ); + $message->addParam($GLOBALS['db']); + $message->addParam($_POST['newname']); + } elseif (! $_error) { + if (isset($_POST['adjust_privileges']) + && ! empty($_POST['adjust_privileges']) + ) { + $operations->adjustPrivilegesCopyDb($GLOBALS['db'], $_POST['newname']); + } + + $message = Message::success( + __('Database %1$s has been copied to %2$s.') + ); + $message->addParam($GLOBALS['db']); + $message->addParam($_POST['newname']); + } else { + $message = Message::error(); + } + $reload = true; + + /* Change database to be used */ + if (! $_error && $move) { + $GLOBALS['db'] = $_POST['newname']; + } elseif (! $_error) { + if (isset($_POST['switch_to_new']) + && $_POST['switch_to_new'] == 'true' + ) { + $_SESSION['pma_switch_to_new'] = true; + $GLOBALS['db'] = $_POST['newname']; + } else { + $_SESSION['pma_switch_to_new'] = false; + } + } + } + } + + /** + * Database has been successfully renamed/moved. If in an Ajax request, + * generate the output with {@link PhpMyAdmin\Response} and exit + */ + if ($response->isAjax()) { + $response->setRequestStatus($message->isSuccess()); + $response->addJSON('message', $message); + $response->addJSON('newname', $_POST['newname']); + $response->addJSON( + 'sql_query', + Util::getMessage(null, $sql_query) + ); + $response->addJSON('db', $GLOBALS['db']); + exit; + } +} + +/** + * Settings for relations stuff + */ +$relation = new Relation(); + +$cfgRelation = $relation->getRelationsParam(); + +/** + * Check if comments were updated + * (must be done before displaying the menu tabs) + */ +if (isset($_POST['comment'])) { + $relation->setDbComment($GLOBALS['db'], $_POST['comment']); +} + +require 'libraries/db_common.inc.php'; +$url_query .= '&goto=db_operations.php'; + +// Gets the database structure +$sub_part = '_structure'; + +list( + $tables, + $num_tables, + $total_num_tables, + $sub_part, + $is_show_stats, + $db_is_system_schema, + $tooltip_truename, + $tooltip_aliasname, + $pos +) = Util::getDbInfo($db, isset($sub_part) ? $sub_part : ''); + +echo "\n"; + +if (isset($message)) { + echo Util::getMessage($message, $sql_query); + unset($message); +} + +$db_collation = $GLOBALS['dbi']->getDbCollation($GLOBALS['db']); +$is_information_schema = $GLOBALS['dbi']->isSystemSchema($GLOBALS['db']); + +if (!$is_information_schema) { + if ($cfgRelation['commwork']) { + /** + * database comment + */ + $response->addHTML($operations->getHtmlForDatabaseComment($GLOBALS['db'])); + } + + $response->addHTML('
'); + $response->addHTML(CreateTable::getHtml($db)); + $response->addHTML('
'); + + /** + * rename database + */ + if ($GLOBALS['db'] != 'mysql') { + $response->addHTML($operations->getHtmlForRenameDatabase($GLOBALS['db'], $db_collation)); + } + + // Drop link if allowed + // Don't even try to drop information_schema. + // You won't be able to. Believe me. You won't. + // Don't allow to easily drop mysql database, RFE #1327514. + if (($GLOBALS['dbi']->isSuperuser() || $GLOBALS['cfg']['AllowUserDropDatabase']) + && ! $db_is_system_schema + && $GLOBALS['db'] != 'mysql' + ) { + $response->addHTML($operations->getHtmlForDropDatabaseLink($GLOBALS['db'])); + } + /** + * Copy database + */ + $response->addHTML($operations->getHtmlForCopyDatabase($GLOBALS['db'], $db_collation)); + + /** + * Change database charset + */ + $response->addHTML($operations->getHtmlForChangeDatabaseCharset($GLOBALS['db'], $db_collation)); + + if (! $cfgRelation['allworks'] + && $cfg['PmaNoRelation_DisableWarning'] == false + ) { + $message = Message::notice( + __( + 'The phpMyAdmin configuration storage has been deactivated. ' . + '%sFind out why%s.' + ) + ); + $message->addParamHtml(''); + $message->addParamHtml(''); + /* Show error if user has configured something, notice elsewhere */ + if (!empty($cfg['Servers'][$server]['pmadb'])) { + $message->isError(true); + } + } // end if +} // end if (!$is_information_schema) diff --git a/php/apps/phpmyadmin49/db_qbe.php b/php/apps/phpmyadmin49/db_qbe.php new file mode 100644 index 00000000..e2526876 --- /dev/null +++ b/php/apps/phpmyadmin49/db_qbe.php @@ -0,0 +1,178 @@ +getRelationsParam(); + +$savedSearchList = array(); +$savedSearch = null; +$currentSearchId = null; +if ($cfgRelation['savedsearcheswork']) { + $header = $response->getHeader(); + $scripts = $header->getScripts(); + $scripts->addFile('db_qbe.js'); + + //Get saved search list. + $savedSearch = new SavedSearches($GLOBALS); + $savedSearch->setUsername($GLOBALS['cfg']['Server']['user']) + ->setDbname($GLOBALS['db']); + + if (!empty($_POST['searchId'])) { + $savedSearch->setId($_POST['searchId']); + } + + //Action field is sent. + if (isset($_POST['action'])) { + $savedSearch->setSearchName($_POST['searchName']); + if ('create' === $_POST['action']) { + $saveResult = $savedSearch->setId(null) + ->setCriterias($_POST) + ->save(); + } elseif ('update' === $_POST['action']) { + $saveResult = $savedSearch->setCriterias($_POST) + ->save(); + } elseif ('delete' === $_POST['action']) { + $deleteResult = $savedSearch->delete(); + //After deletion, reset search. + $savedSearch = new SavedSearches($GLOBALS); + $savedSearch->setUsername($GLOBALS['cfg']['Server']['user']) + ->setDbname($GLOBALS['db']); + $_POST = array(); + } elseif ('load' === $_POST['action']) { + if (empty($_POST['searchId'])) { + //when not loading a search, reset the object. + $savedSearch = new SavedSearches($GLOBALS); + $savedSearch->setUsername($GLOBALS['cfg']['Server']['user']) + ->setDbname($GLOBALS['db']); + $_POST = array(); + } else { + $loadResult = $savedSearch->load(); + } + } + //Else, it's an "update query" + } + + $savedSearchList = $savedSearch->getList(); + $currentSearchId = $savedSearch->getId(); +} + +/** + * A query has been submitted -> (maybe) execute it + */ +$message_to_display = false; +if (isset($_POST['submit_sql']) && ! empty($sql_query)) { + if (! preg_match('@^SELECT@i', $sql_query)) { + $message_to_display = true; + } else { + $goto = 'db_sql.php'; + $sql = new Sql(); + $sql->executeQueryAndSendQueryResponse( + null, // analyzed_sql_results + false, // is_gotofile + $_POST['db'], // db + null, // table + false, // find_real_end + null, // sql_query_for_bookmark + null, // extra_data + null, // message_to_show + null, // message + null, // sql_data + $goto, // goto + $pmaThemeImage, // pmaThemeImage + null, // disp_query + null, // disp_message + null, // query_type + $sql_query, // sql_query + null, // selectedTables + null // complete_query + ); + } +} + +$sub_part = '_qbe'; +require 'libraries/db_common.inc.php'; +$url_query .= '&goto=db_qbe.php'; +$url_params['goto'] = 'db_qbe.php'; + +list( + $tables, + $num_tables, + $total_num_tables, + $sub_part, + $is_show_stats, + $db_is_system_schema, + $tooltip_truename, + $tooltip_aliasname, + $pos +) = Util::getDbInfo($db, isset($sub_part) ? $sub_part : ''); + +if ($message_to_display) { + Message::error( + __('You have to choose at least one column to display!') + ) + ->display(); +} +unset($message_to_display); + +// create new qbe search instance +$db_qbe = new Qbe($GLOBALS['db'], $savedSearchList, $savedSearch); + +$secondaryTabs = [ + 'multi' => [ + 'link' => 'db_multi_table_query.php', + 'text' => __('Multi-table query'), + ], + 'qbe' => [ + 'link' => 'db_qbe.php', + 'text' => __('Query by example'), + ], +]; +$response->addHTML( + Template::get('secondary_tabs')->render([ + 'url_params' => $url_params, + 'sub_tabs' => $secondaryTabs, + ]) +); + +$url = 'db_designer.php' . Url::getCommon( + array_merge( + $url_params, + array('query' => 1) + ) +); +$response->addHTML( + Message::notice( + sprintf( + __('Switch to %svisual builder%s'), + '', + '' + ) + ) +); + +/** + * Displays the Query by example form + */ +$response->addHTML($db_qbe->getSelectionForm()); diff --git a/php/apps/phpmyadmin49/db_routines.php b/php/apps/phpmyadmin49/db_routines.php new file mode 100644 index 00000000..5740c35a --- /dev/null +++ b/php/apps/phpmyadmin49/db_routines.php @@ -0,0 +1,23 @@ +getHeader(); +$scripts = $header->getScripts(); +$scripts->addFile('db_search.js'); +$scripts->addFile('sql.js'); +$scripts->addFile('makegrid.js'); + +require 'libraries/db_common.inc.php'; + +// If config variable $GLOBALS['cfg']['UseDbSearch'] is on false : exit. +if (! $GLOBALS['cfg']['UseDbSearch']) { + Util::mysqlDie( + __('Access denied!'), '', false, $err_url + ); +} // end if +$url_query .= '&goto=db_search.php'; +$url_params['goto'] = 'db_search.php'; + +// Create a database search instance +$db_search = new Search($GLOBALS['db']); + +// Display top links if we are not in an Ajax request +if (! $response->isAjax()) { + list( + $tables, + $num_tables, + $total_num_tables, + $sub_part, + $is_show_stats, + $db_is_system_schema, + $tooltip_truename, + $tooltip_aliasname, + $pos + ) = Util::getDbInfo($db, isset($sub_part) ? $sub_part : ''); +} + +// Main search form has been submitted, get results +if (isset($_POST['submit_search'])) { + $response->addHTML($db_search->getSearchResults()); +} + +// If we are in an Ajax request, we need to exit after displaying all the HTML +if ($response->isAjax() && empty($_REQUEST['ajax_page_request'])) { + exit; +} + +// Display the search form +$response->addHTML($db_search->getSelectionForm()); +$response->addHTML('
'); +$response->addHTML( + '
' +); +$response->addHTML('
'); +$response->addHTML($db_search->getResultDivs()); diff --git a/php/apps/phpmyadmin49/db_sql.php b/php/apps/phpmyadmin49/db_sql.php new file mode 100644 index 00000000..aa4d0b37 --- /dev/null +++ b/php/apps/phpmyadmin49/db_sql.php @@ -0,0 +1,46 @@ +getHeader(); +$scripts = $header->getScripts(); +$scripts->addFile('makegrid.js'); +$scripts->addFile('vendor/jquery/jquery.uitablefilter.js'); +$scripts->addFile('sql.js'); + +require 'libraries/db_common.inc.php'; + +// After a syntax error, we return to this script +// with the typed query in the textarea. +$goto = 'db_sql.php'; +$back = 'db_sql.php'; + +/** + * Query box, bookmark, insert data from textfile + */ +$response->addHTML( + SqlQueryForm::getHtml( + true, false, + isset($_POST['delimiter']) + ? htmlspecialchars($_POST['delimiter']) + : ';' + ) +); diff --git a/php/apps/phpmyadmin49/db_sql_autocomplete.php b/php/apps/phpmyadmin49/db_sql_autocomplete.php new file mode 100644 index 00000000..0fe4ae6f --- /dev/null +++ b/php/apps/phpmyadmin49/db_sql_autocomplete.php @@ -0,0 +1,27 @@ +getTables($db); + foreach ($tableNames as $tableName) { + $sql_autocomplete[$tableName] = $GLOBALS['dbi']->getColumns( + $db, $tableName + ); + } + } +} else { + $sql_autocomplete = true; +} +$response = Response::getInstance(); +$response->addJSON("tables", json_encode($sql_autocomplete)); diff --git a/php/apps/phpmyadmin49/db_sql_format.php b/php/apps/phpmyadmin49/db_sql_format.php new file mode 100644 index 00000000..dd3c2b86 --- /dev/null +++ b/php/apps/phpmyadmin49/db_sql_format.php @@ -0,0 +1,21 @@ +addJSON("sql", $query); diff --git a/php/apps/phpmyadmin49/db_structure.php b/php/apps/phpmyadmin49/db_structure.php new file mode 100644 index 00000000..09c4aee4 --- /dev/null +++ b/php/apps/phpmyadmin49/db_structure.php @@ -0,0 +1,38 @@ +factory( + 'PhpMyAdmin\Controllers\Database\DatabaseStructureController' +); +$container->alias( + 'DatabaseStructureController', + 'PhpMyAdmin\Controllers\Database\DatabaseStructureController' +); +$container->set('PhpMyAdmin\Response', Response::getInstance()); +$container->alias('response', 'PhpMyAdmin\Response'); + +/* Define dependencies for the concerned controller */ +$dependency_definitions = array( + 'db' => $db, +); + +/** @var DatabaseStructureController $controller */ +$controller = $container->get( + 'DatabaseStructureController', + $dependency_definitions +); +$controller->indexAction(); diff --git a/php/apps/phpmyadmin49/db_tracking.php b/php/apps/phpmyadmin49/db_tracking.php new file mode 100644 index 00000000..49e40480 --- /dev/null +++ b/php/apps/phpmyadmin49/db_tracking.php @@ -0,0 +1,154 @@ +getHeader(); +$scripts = $header->getScripts(); +$scripts->addFile('vendor/jquery/jquery.tablesorter.js'); +$scripts->addFile('db_tracking.js'); + +/** + * If we are not in an Ajax request, then do the common work and show the links etc. + */ +require 'libraries/db_common.inc.php'; +$url_query .= '&goto=tbl_tracking.php&back=db_tracking.php'; +$url_params['goto'] = 'tbl_tracking.php'; +$url_params['back'] = 'db_tracking.php'; + +// Get the database structure +$sub_part = '_structure'; + +list( + $tables, + $num_tables, + $total_num_tables, + $sub_part, + $is_show_stats, + $db_is_system_schema, + $tooltip_truename, + $tooltip_aliasname, + $pos +) = Util::getDbInfo($db, isset($sub_part) ? $sub_part : ''); + +if (isset($_POST['delete_tracking']) && isset($_POST['table'])) { + + Tracker::deleteTracking($GLOBALS['db'], $_POST['table']); + Message::success( + __('Tracking data deleted successfully.') + )->display(); + +} elseif (isset($_POST['submit_create_version'])) { + + Tracking::createTrackingForMultipleTables($_POST['selected']); + Message::success( + sprintf( + __( + 'Version %1$s was created for selected tables,' + . ' tracking is active for them.' + ), + htmlspecialchars($_POST['version']) + ) + )->display(); + +} elseif (isset($_POST['submit_mult'])) { + + if (! empty($_POST['selected_tbl'])) { + if ($_POST['submit_mult'] == 'delete_tracking') { + + foreach ($_POST['selected_tbl'] as $table) { + Tracker::deleteTracking($GLOBALS['db'], $table); + } + Message::success( + __('Tracking data deleted successfully.') + )->display(); + + } elseif ($_POST['submit_mult'] == 'track') { + + echo Tracking::getHtmlForDataDefinitionAndManipulationStatements( + 'db_tracking.php' . $url_query, + 0, + $GLOBALS['db'], + $_POST['selected_tbl'] + ); + exit; + } + } else { + Message::notice( + __('No tables selected.') + )->display(); + } +} + +// Get tracked data about the database +$data = Tracker::getTrackedData($GLOBALS['db'], '', '1'); + +// No tables present and no log exist +if ($num_tables == 0 && count($data['ddlog']) == 0) { + echo '

' , __('No tables found in database.') , '

' , "\n"; + + if (empty($db_is_system_schema)) { + echo CreateTable::getHtml($db); + } + exit; +} + +// --------------------------------------------------------------------------- +$relation = new Relation(); +$cfgRelation = $relation->getRelationsParam(); + +// Prepare statement to get HEAD version +$all_tables_query = ' SELECT table_name, MAX(version) as version FROM ' . + Util::backquote($cfgRelation['db']) . '.' . + Util::backquote($cfgRelation['tracking']) . + ' WHERE db_name = \'' . $GLOBALS['dbi']->escapeString($GLOBALS['db']) . + '\' ' . + ' GROUP BY table_name' . + ' ORDER BY table_name ASC'; + +$all_tables_result = $relation->queryAsControlUser($all_tables_query); + +// If a HEAD version exists +if (is_object($all_tables_result) + && $GLOBALS['dbi']->numRows($all_tables_result) > 0 +) { + echo Tracking::getHtmlForTrackedTables( + $GLOBALS['db'], $all_tables_result, $url_query, $pmaThemeImage, + $text_dir, $cfgRelation + ); +} + +$untracked_tables = Tracking::getUntrackedTables($GLOBALS['db']); + +// If untracked tables exist +if (count($untracked_tables) > 0) { + echo Tracking::getHtmlForUntrackedTables( + $GLOBALS['db'], $untracked_tables, $url_query, $pmaThemeImage, $text_dir + ); +} +// If available print out database log +if (count($data['ddlog']) > 0) { + $log = ''; + foreach ($data['ddlog'] as $entry) { + $log .= '# ' . $entry['date'] . ' ' . $entry['username'] . "\n" + . $entry['statement'] . "\n"; + } + echo Util::getMessage(__('Database Log'), $log); +} diff --git a/php/apps/phpmyadmin49/db_triggers.php b/php/apps/phpmyadmin49/db_triggers.php new file mode 100644 index 00000000..cdd0ebe8 --- /dev/null +++ b/php/apps/phpmyadmin49/db_triggers.php @@ -0,0 +1,18 @@ +`_ jQuery library. + +Examples +-------- + +Pie chart ++++++++++ + +Query results for a simple pie chart can be generated with: + +.. code-block:: mysql + + SELECT 'Food' AS 'expense', + 1250 AS 'amount' UNION + SELECT 'Accommodation', 500 UNION + SELECT 'Travel', 720 UNION + SELECT 'Misc', 220 + +And the result of this query is: + ++---------------+--------+ +| expense | amount | ++===============+========+ +| Food | 1250 | ++---------------+--------+ +| Accommodation | 500 | ++---------------+--------+ +| Travel | 720 | ++---------------+--------+ +| Misc | 220 | ++---------------+--------+ + +Choosing expense as the X-axis and amount in series: + +.. image:: images/pie_chart.png + +Bar and column chart +++++++++++++++++++++ + +Both bar charts and column chats support stacking. Upon selecting one of these types a checkbox is displayed to select stacking. + +Query results for a simple bar or column chart can be generated with: + +.. code-block:: mysql + + SELECT + 'ACADEMY DINOSAUR' AS 'title', + 0.99 AS 'rental_rate', + 20.99 AS 'replacement_cost' UNION + SELECT 'ACE GOLDFINGER', 4.99, 12.99 UNION + SELECT 'ADAPTATION HOLES', 2.99, 18.99 UNION + SELECT 'AFFAIR PREJUDICE', 2.99, 26.99 UNION + SELECT 'AFRICAN EGG', 2.99, 22.99 + +And the result of this query is: + ++------------------+--------------+-------------------+ +| title | rental_rate | replacement_cost | ++==================+==============+===================+ +| ACADEMY DINOSAUR | 0.99 | 20.99 | ++------------------+--------------+-------------------+ +| ACE GOLDFINGER | 4.99 | 12.99 | ++------------------+--------------+-------------------+ +| ADAPTATION HOLES | 2.99 | 18.99 | ++------------------+--------------+-------------------+ +| AFFAIR PREJUDICE | 2.99 | 26.99 | ++------------------+--------------+-------------------+ +| AFRICAN EGG | 2.99 | 22.99 | ++------------------+--------------+-------------------+ + +Choosing title as the X-axis and rental_rate and replacement_cost as series: + +.. image:: images/column_chart.png + +Scatter chart ++++++++++++++ + +Scatter charts are useful in identifying the movement of one or more variable(s) compared to another variable. + +Using the same data set from bar and column charts section and choosing replacement_cost as the X-axis and rental_rate in series: + +.. image:: images/scatter_chart.png + +Line, spline and timeline charts +++++++++++++++++++++++++++++++++ + +These charts can be used to illustrate trends in underlying data. Spline charts draw smooth lines while timeline charts draw X-axis taking the distances between the dates/time into consideration. + +Query results for a simple line, spline or timeline chart can be generated with: + +.. code-block:: mysql + + SELECT + DATE('2006-01-08') AS 'date', + 2056 AS 'revenue', + 1378 AS 'cost' UNION + SELECT DATE('2006-01-09'), 1898, 2301 UNION + SELECT DATE('2006-01-15'), 1560, 600 UNION + SELECT DATE('2006-01-17'), 3457, 1565 + +And the result of this query is: + ++------------+---------+------+ +| date | revenue | cost | ++============+=========+======+ +| 2016-01-08 | 2056 | 1378 | ++------------+---------+------+ +| 2006-01-09 | 1898 | 2301 | ++------------+---------+------+ +| 2006-01-15 | 1560 | 600 | ++------------+---------+------+ +| 2006-01-17 | 3457 | 1565 | ++------------+---------+------+ + +.. image:: images/line_chart.png +.. image:: images/spline_chart.png +.. image:: images/timeline_chart.png diff --git a/php/apps/phpmyadmin49/doc/html/_sources/config.txt b/php/apps/phpmyadmin49/doc/html/_sources/config.txt new file mode 100644 index 00000000..80e47e84 --- /dev/null +++ b/php/apps/phpmyadmin49/doc/html/_sources/config.txt @@ -0,0 +1,3589 @@ +.. index:: config.inc.php + +.. _config: + +Configuration +============= + +All configurable data is placed in :file:`config.inc.php` in phpMyAdmin's +toplevel directory. If this file does not exist, please refer to the +:ref:`setup` section to create one. This file only needs to contain the +parameters you want to change from their corresponding default value in +:file:`libraries/config.default.php` (this file is not inteded for changes). + +.. seealso:: + + :ref:`config-examples` for examples of configurations + +If a directive is missing from your file, you can just add another line with +the file. This file is for over-writing the defaults; if you wish to use the +default value there's no need to add a line here. + +The parameters which relate to design (like colors) are placed in +:file:`themes/themename/layout.inc.php`. You might also want to create +:file:`config.footer.inc.php` and :file:`config.header.inc.php` files to add +your site specific code to be included on start and end of each page. + +.. note:: + + Some distributions (eg. Debian or Ubuntu) store :file:`config.inc.php` in + ``/etc/phpmyadmin`` instead of within phpMyAdmin sources. + +.. warning:: + + :term:`Mac` users should note that if you are on a version before + :term:`Mac OS X`, PHP does not seem to + like :term:`Mac` end of lines character (``\r``). So + ensure you choose the option that allows to use the \*nix end of line + character (``\n``) in your text editor before saving a script you have + modified. + +Basic settings +-------------- + +.. config:option:: $cfg['PmaAbsoluteUri'] + + :type: string + :default: ``''`` + + .. versionchanged:: 4.6.5 + + This setting was not available in phpMyAdmin 4.6.0 - 4.6.4. + + Sets here the complete :term:`URL` (with full path) to your phpMyAdmin + installation's directory. E.g. + ``https://www.example.net/path_to_your_phpMyAdmin_directory/``. Note also + that the :term:`URL` on most of web servers are case sensitive (even on + Windows). Don’t forget the trailing slash at the end. + + Starting with version 2.3.0, it is advisable to try leaving this blank. In + most cases phpMyAdmin automatically detects the proper setting. Users of + port forwarding or complex reverse proxy setup might need to set this. + + A good test is to browse a table, edit a row and save it. There should be + an error message if phpMyAdmin is having trouble auto–detecting the correct + value. If you get an error that this must be set or if the autodetect code + fails to detect your path, please post a bug report on our bug tracker so + we can improve the code. + + .. seealso:: :ref:`faq1_40`, :ref:`faq2_5`, :ref:`faq4_7`, :ref:`faq5_16` + +.. config:option:: $cfg['PmaNoRelation_DisableWarning'] + + :type: boolean + :default: false + + Starting with version 2.3.0 phpMyAdmin offers a lot of features to + work with master / foreign – tables (see :config:option:`$cfg['Servers'][$i]['pmadb']`). + + If you tried to set this + up and it does not work for you, have a look on the :guilabel:`Structure` page + of one database where you would like to use it. You will find a link + that will analyze why those features have been disabled. + + If you do not want to use those features set this variable to ``true`` to + stop this message from appearing. + +.. config:option:: $cfg['AuthLog'] + + :type: string + :default: ``'auto'`` + + .. versionadded:: 4.8.0 + + This is supported since phpMyAdmin 4.8.0. + + Configure authentication logging destination. Failed (or all, depending on + :config:option:`$cfg['AuthLogSuccess']`) authentication attempts will be + logged according to this directive: + + ``auto`` + Let phpMyAdmin automatically choose between ``syslog`` and ``php``. + ``syslog`` + Log using syslog, using AUTH facility, on most systems this ends up + in :file:`/var/log/auth.log`. + ``php`` + Log into PHP error log. + ``sapi`` + Log into PHP SAPI logging. + ``/path/to/file`` + Any other value is treated as a filename and log entries are written there. + + .. note:: + + When logging to a file, make sure its permissions are correctly set + for a web server user, the setup should closely match instructions + described in :config:option:`$cfg['TempDir']`: + +.. config:option:: $cfg['AuthLogSuccess'] + + :type: boolean + :default: false + + .. versionadded:: 4.8.0 + + This is supported since phpMyAdmin 4.8.0. + + Whether to log successful authentication attempts into + :config:option:`$cfg['AuthLog']`. + +.. config:option:: $cfg['SuhosinDisableWarning'] + + :type: boolean + :default: false + + A warning is displayed on the main page if Suhosin is detected. + + You can set this parameter to ``true`` to stop this message from appearing. + +.. config:option:: $cfg['LoginCookieValidityDisableWarning'] + + :type: boolean + :default: false + + A warning is displayed on the main page if the PHP parameter + session.gc_maxlifetime is lower than cookie validity configured in phpMyAdmin. + + You can set this parameter to ``true`` to stop this message from appearing. + +.. config:option:: $cfg['ServerLibraryDifference_DisableWarning'] + + :type: boolean + :default: false + + .. deprecated:: 4.7.0 + + This setting was removed as the warning has been removed as well. + + A warning is displayed on the main page if there is a difference + between the MySQL library and server version. + + You can set this parameter to ``true`` to stop this message from appearing. + +.. config:option:: $cfg['ReservedWordDisableWarning'] + + :type: boolean + :default: false + + This warning is displayed on the Structure page of a table if one or more + column names match with words which are MySQL reserved. + + If you want to turn off this warning, you can set it to ``true`` and + warning will no longer be displayed. + +.. config:option:: $cfg['TranslationWarningThreshold'] + + :type: integer + :default: 80 + + Show warning about incomplete translations on certain threshold. + +.. config:option:: $cfg['SendErrorReports'] + + :type: string + :default: ``'ask'`` + + Sets the default behavior for JavaScript error reporting. + + Whenever an error is detected in the JavaScript execution, an error report + may be sent to the phpMyAdmin team if the user agrees. + + The default setting of ``'ask'`` will ask the user everytime there is a new + error report. However you can set this parameter to ``'always'`` to send error + reports without asking for confirmation or you can set it to ``'never'`` to + never send error reports. + + This directive is available both in the configuration file and in users + preferences. If the person in charge of a multi-user installation prefers + to disable this feature for all users, a value of ``'never'`` should be + set, and the :config:option:`$cfg['UserprefsDisallow']` directive should + contain ``'SendErrorReports'`` in one of its array values. + +.. config:option:: $cfg['ConsoleEnterExecutes'] + + :type: boolean + :default: false + + Setting this to ``true`` allows the user to execute queries by pressing Enter + instead of Ctrl+Enter. A new line can be inserted by pressing Shift + Enter. + + The behaviour of the console can be temporarily changed using console's + settings interface. + +.. config:option:: $cfg['AllowThirdPartyFraming'] + + :type: boolean + :default: false + + Setting this to ``true`` allows phpMyAdmin to be included inside a frame, + and is a potential security hole allowing cross-frame scripting attacks or + clickjacking. + +Server connection settings +-------------------------- + +.. config:option:: $cfg['Servers'] + + :type: array + :default: one server array with settings listed below + + Since version 1.4.2, phpMyAdmin supports the administration of multiple + MySQL servers. Therefore, a :config:option:`$cfg['Servers']`-array has been + added which contains the login information for the different servers. The + first :config:option:`$cfg['Servers'][$i]['host']` contains the hostname of + the first server, the second :config:option:`$cfg['Servers'][$i]['host']` + the hostname of the second server, etc. In + :file:`libraries/config.default.php`, there is only one section for server + definition, however you can put as many as you need in + :file:`config.inc.php`, copy that block or needed parts (you don't have to + define all settings, just those you need to change). + + .. note:: + + The :config:option:`$cfg['Servers']` array starts with + $cfg['Servers'][1]. Do not use $cfg['Servers'][0]. If you want more + than one server, just copy following section (including $i + incrementation) serveral times. There is no need to define full server + array, just define values you need to change. + +.. config:option:: $cfg['Servers'][$i]['host'] + + :type: string + :default: ``'localhost'`` + + The hostname or :term:`IP` address of your $i-th MySQL-server. E.g. + ``localhost``. + + Possible values are: + + * hostname, e.g., ``'localhost'`` or ``'mydb.example.org'`` + * IP address, e.g., ``'127.0.0.1'`` or ``'192.168.10.1'`` + * IPv6 address, e.g. ``2001:cdba:0000:0000:0000:0000:3257:9652`` + * dot - ``'.'``, i.e., use named pipes on windows systems + * empty - ``''``, disables this server + + .. note:: + + The hostname ``localhost`` is handled specially by MySQL and it uses + the socket based connection protocol. To use TCP/IP networking, use an + IP address or hostname such as ``127.0.0.1`` or ``db.example.com``. You + can configure the path to the socket with + :config:option:`$cfg['Servers'][$i]['socket']`. + + .. seealso:: + + :config:option:`$cfg['Servers'][$i]['port']`, + + +.. config:option:: $cfg['Servers'][$i]['port'] + + :type: string + :default: ``''`` + + The port-number of your $i-th MySQL-server. Default is 3306 (leave + blank). + + .. note:: + + If you use ``localhost`` as the hostname, MySQL ignores this port number + and connects with the socket, so if you want to connect to a port + different from the default port, use ``127.0.0.1`` or the real hostname + in :config:option:`$cfg['Servers'][$i]['host']`. + + .. seealso:: + + :config:option:`$cfg['Servers'][$i]['host']`, + + +.. config:option:: $cfg['Servers'][$i]['socket'] + + :type: string + :default: ``''`` + + The path to the socket to use. Leave blank for default. To determine + the correct socket, check your MySQL configuration or, using the + :command:`mysql` command–line client, issue the ``status`` command. Among the + resulting information displayed will be the socket used. + + .. note:: + + This takes effect only if :config:option:`$cfg['Servers'][$i]['host']` is set + to ``localhost``. + + .. seealso:: + + :config:option:`$cfg['Servers'][$i]['host']`, + + +.. config:option:: $cfg['Servers'][$i]['ssl'] + + :type: boolean + :default: false + + Whether to enable SSL for the connection between phpMyAdmin and the MySQL + server to secure the connection. + + When using the ``'mysql'`` extension, + none of the remaining ``'ssl...'`` configuration options apply. + + We strongly recommend the ``'mysqli'`` extension when using this option. + + .. seealso:: + + :ref:`ssl`, + :ref:`example-google-ssl`, + :config:option:`$cfg['Servers'][$i]['ssl_key']`, + :config:option:`$cfg['Servers'][$i]['ssl_cert']`, + :config:option:`$cfg['Servers'][$i]['ssl_ca']`, + :config:option:`$cfg['Servers'][$i]['ssl_ca_path']`, + :config:option:`$cfg['Servers'][$i]['ssl_ciphers']`, + :config:option:`$cfg['Servers'][$i]['ssl_verify']` + +.. config:option:: $cfg['Servers'][$i]['ssl_key'] + + :type: string + :default: NULL + + Path to the client key file when using SSL for connecting to the MySQL + server. This is used to authenticate the client to the server. + + For example: + + .. code-block:: php + + $cfg['Servers'][$i]['ssl_key'] = '/etc/mysql/server-key.pem'; + + .. seealso:: + + :ref:`ssl`, + :ref:`example-google-ssl`, + :config:option:`$cfg['Servers'][$i]['ssl']`, + :config:option:`$cfg['Servers'][$i]['ssl_cert']`, + :config:option:`$cfg['Servers'][$i]['ssl_ca']`, + :config:option:`$cfg['Servers'][$i]['ssl_ca_path']`, + :config:option:`$cfg['Servers'][$i]['ssl_ciphers']`, + :config:option:`$cfg['Servers'][$i]['ssl_verify']` + +.. config:option:: $cfg['Servers'][$i]['ssl_cert'] + + :type: string + :default: NULL + + Path to the client certificate file when using SSL for connecting to the + MySQL server. This is used to authenticate the client to the server. + + .. seealso:: + + :ref:`ssl`, + :ref:`example-google-ssl`, + :config:option:`$cfg['Servers'][$i]['ssl']`, + :config:option:`$cfg['Servers'][$i]['ssl_key']`, + :config:option:`$cfg['Servers'][$i]['ssl_ca']`, + :config:option:`$cfg['Servers'][$i]['ssl_ca_path']`, + :config:option:`$cfg['Servers'][$i]['ssl_ciphers']`, + :config:option:`$cfg['Servers'][$i]['ssl_verify']` + +.. config:option:: $cfg['Servers'][$i]['ssl_ca'] + + :type: string + :default: NULL + + Path to the CA file when using SSL for connecting to the MySQL server. + + .. seealso:: + + :ref:`ssl`, + :ref:`example-google-ssl`, + :config:option:`$cfg['Servers'][$i]['ssl']`, + :config:option:`$cfg['Servers'][$i]['ssl_key']`, + :config:option:`$cfg['Servers'][$i]['ssl_cert']`, + :config:option:`$cfg['Servers'][$i]['ssl_ca_path']`, + :config:option:`$cfg['Servers'][$i]['ssl_ciphers']`, + :config:option:`$cfg['Servers'][$i]['ssl_verify']` + +.. config:option:: $cfg['Servers'][$i]['ssl_ca_path'] + + :type: string + :default: NULL + + Directory containing trusted SSL CA certificates in PEM format. + + .. seealso:: + + :ref:`ssl`, + :ref:`example-google-ssl`, + :config:option:`$cfg['Servers'][$i]['ssl']`, + :config:option:`$cfg['Servers'][$i]['ssl_key']`, + :config:option:`$cfg['Servers'][$i]['ssl_cert']`, + :config:option:`$cfg['Servers'][$i]['ssl_ca']`, + :config:option:`$cfg['Servers'][$i]['ssl_ciphers']`, + :config:option:`$cfg['Servers'][$i]['ssl_verify']` + +.. config:option:: $cfg['Servers'][$i]['ssl_ciphers'] + + :type: string + :default: NULL + + List of allowable ciphers for SSL connections to the MySQL server. + + .. seealso:: + + :ref:`ssl`, + :ref:`example-google-ssl`, + :config:option:`$cfg['Servers'][$i]['ssl']`, + :config:option:`$cfg['Servers'][$i]['ssl_key']`, + :config:option:`$cfg['Servers'][$i]['ssl_cert']`, + :config:option:`$cfg['Servers'][$i]['ssl_ca']`, + :config:option:`$cfg['Servers'][$i]['ssl_ca_path']`, + :config:option:`$cfg['Servers'][$i]['ssl_verify']` + +.. config:option:: $cfg['Servers'][$i]['ssl_verify'] + + :type: boolean + :default: true + + .. versionadded:: 4.6.0 + + This is supported since phpMyAdmin 4.6.0. + + If your PHP install uses the MySQL Native Driver (mysqlnd), your + MySQL server is 5.6 or later, and your SSL certificate is self-signed, + there is a chance your SSL connection will fail due to validation. + Setting this to ``false`` will disable the validation check. + + Since PHP 5.6.0 it also verifies whether server name matches CN of its + certificate. There is currently no way to disable just this check without + disabling complete SSL verification. + + .. warning:: + + Disabling the certificate verification defeats purpose of using SSL. + This will make the connection vulnerable to man in the middle attacks. + + .. note:: + + This flag only works with PHP 5.6.16 or later. + + .. seealso:: + + :ref:`ssl`, + :ref:`example-google-ssl`, + :config:option:`$cfg['Servers'][$i]['ssl']`, + :config:option:`$cfg['Servers'][$i]['ssl_key']`, + :config:option:`$cfg['Servers'][$i]['ssl_cert']`, + :config:option:`$cfg['Servers'][$i]['ssl_ca']`, + :config:option:`$cfg['Servers'][$i]['ssl_ca_path']`, + :config:option:`$cfg['Servers'][$i]['ssl_ciphers']`, + :config:option:`$cfg['Servers'][$i]['ssl_verify']` + +.. config:option:: $cfg['Servers'][$i]['connect_type'] + + :type: string + :default: ``'tcp'`` + + .. deprecated:: 4.7.0 + + This setting is no longer used as of 4.7.0, since MySQL decides the + connection type based on host, so it could lead to unexpected results. + Please set :config:option:`$cfg['Servers'][$i]['host']` accordingly + instead. + + What type connection to use with the MySQL server. Your options are + ``'socket'`` and ``'tcp'``. It defaults to tcp as that is nearly guaranteed + to be available on all MySQL servers, while sockets are not supported on + some platforms. To use the socket mode, your MySQL server must be on the + same machine as the Web server. + +.. config:option:: $cfg['Servers'][$i]['compress'] + + :type: boolean + :default: false + + Whether to use a compressed protocol for the MySQL server connection + or not (experimental). + +.. _controlhost: +.. config:option:: $cfg['Servers'][$i]['controlhost'] + + :type: string + :default: ``''`` + + Permits to use an alternate host to hold the configuration storage + data. + + .. seealso:: + + :config:option:`$cfg['Servers'][$i]['control_*']` + +.. _controlport: +.. config:option:: $cfg['Servers'][$i]['controlport'] + + :type: string + :default: ``''`` + + Permits to use an alternate port to connect to the host that + holds the configuration storage. + + .. seealso:: + + :config:option:`$cfg['Servers'][$i]['control_*']` + +.. _controluser: +.. config:option:: $cfg['Servers'][$i]['controluser'] + + :type: string + :default: ``''`` + +.. config:option:: $cfg['Servers'][$i]['controlpass'] + + :type: string + :default: ``''`` + + This special account is used to access :ref:`linked-tables`. + You don't need it in single user case, but if phpMyAdmin is shared it + is recommended to give access to :ref:`linked-tables` only to this user + and configure phpMyAdmin to use it. All users will then be able to use + the features without need to have direct access to :ref:`linked-tables`. + + .. versionchanged:: 2.2.5 + those were called ``stduser`` and ``stdpass`` + + .. seealso:: + + :ref:`setup`, + :ref:`authentication_modes`, + :ref:`linked-tables`, + :config:option:`$cfg['Servers'][$i]['pmadb']`, + :config:option:`$cfg['Servers'][$i]['controlhost']`, + :config:option:`$cfg['Servers'][$i]['controlport']`, + :config:option:`$cfg['Servers'][$i]['control_*']` + +.. config:option:: $cfg['Servers'][$i]['control_*'] + + :type: mixed + + .. versionadded:: 4.7.0 + + You can change any MySQL connection setting for control link (used to + access :ref:`linked-tables`) using configuration prefixed with ``control_``. + + This can be used to change any aspect of the control connection, which by + default uses same parameters as the user one. + + For example you can configure SSL for the control connection: + + .. code-block:: php + + // Enable SSL + $cfg['Servers'][$i]['control_ssl'] = true; + // Client secret key + $cfg['Servers'][$i]['control_ssl_key'] = '../client-key.pem'; + // Client certificate + $cfg['Servers'][$i]['control_ssl_cert'] = '../client-cert.pem'; + // Server certification authority + $cfg['Servers'][$i]['control_ssl_ca'] = '../server-ca.pem'; + + .. seealso:: + + :config:option:`$cfg['Servers'][$i]['ssl']`, + :config:option:`$cfg['Servers'][$i]['ssl_key']`, + :config:option:`$cfg['Servers'][$i]['ssl_cert']`, + :config:option:`$cfg['Servers'][$i]['ssl_ca']`, + :config:option:`$cfg['Servers'][$i]['ssl_ca_path']`, + :config:option:`$cfg['Servers'][$i]['ssl_ciphers']`, + :config:option:`$cfg['Servers'][$i]['ssl_verify']` + +.. config:option:: $cfg['Servers'][$i]['auth_type'] + + :type: string + :default: ``'cookie'`` + + Whether config or cookie or :term:`HTTP` or signon authentication should be + used for this server. + + * 'config' authentication (``$auth_type = 'config'``) is the plain old + way: username and password are stored in :file:`config.inc.php`. + * 'cookie' authentication mode (``$auth_type = 'cookie'``) allows you to + log in as any valid MySQL user with the help of cookies. + * 'http' authentication allows you to log in as any + valid MySQL user via HTTP-Auth. + * 'signon' authentication mode (``$auth_type = 'signon'``) allows you to + log in from prepared PHP session data or using supplied PHP script. + + .. seealso:: :ref:`authentication_modes` + +.. _servers_auth_http_realm: +.. config:option:: $cfg['Servers'][$i]['auth_http_realm'] + + :type: string + :default: ``''`` + + When using auth\_type = ``http``, this field allows to define a custom + :term:`HTTP` Basic Auth Realm which will be displayed to the user. If not + explicitly specified in your configuration, a string combined of + "phpMyAdmin " and either :config:option:`$cfg['Servers'][$i]['verbose']` or + :config:option:`$cfg['Servers'][$i]['host']` will be used. + +.. _servers_user: +.. config:option:: $cfg['Servers'][$i]['user'] + + :type: string + :default: ``'root'`` + +.. config:option:: $cfg['Servers'][$i]['password'] + + :type: string + :default: ``''`` + + When using :config:option:`$cfg['Servers'][$i]['auth_type']` set to + 'config', this is the user/password-pair which phpMyAdmin will use to + connect to the MySQL server. This user/password pair is not needed when + :term:`HTTP` or cookie authentication is used + and should be empty. + +.. _servers_nopassword: +.. config:option:: $cfg['Servers'][$i]['nopassword'] + + :type: boolean + :default: false + + .. deprecated:: 4.7.0 + + This setting was removed as it can produce unexpected results. + + Allow attempt to log in without password when a login with password + fails. This can be used together with http authentication, when + authentication is done some other way and phpMyAdmin gets user name + from auth and uses empty password for connecting to MySQL. Password + login is still tried first, but as fallback, no password method is + tried. + +.. _servers_only_db: +.. config:option:: $cfg['Servers'][$i]['only_db'] + + :type: string or array + :default: ``''`` + + If set to a (an array of) database name(s), only this (these) + database(s) will be shown to the user. Since phpMyAdmin 2.2.1, + this/these database(s) name(s) may contain MySQL wildcards characters + ("\_" and "%"): if you want to use literal instances of these + characters, escape them (I.E. use ``'my\_db'`` and not ``'my_db'``). + + This setting is an efficient way to lower the server load since the + latter does not need to send MySQL requests to build the available + database list. But **it does not replace the privileges rules of the + MySQL database server**. If set, it just means only these databases + will be displayed but **not that all other databases can't be used.** + + An example of using more that one database: + + .. code-block:: php + + $cfg['Servers'][$i]['only_db'] = array('db1', 'db2'); + + .. versionchanged:: 4.0.0 + Previous versions permitted to specify the display order of + the database names via this directive. + +.. config:option:: $cfg['Servers'][$i]['hide_db'] + + :type: string + :default: ``''`` + + Regular expression for hiding some databases from unprivileged users. + This only hides them from listing, but a user is still able to access + them (using, for example, the SQL query area). To limit access, use + the MySQL privilege system. For example, to hide all databases + starting with the letter "a", use + + .. code-block:: php + + $cfg['Servers'][$i]['hide_db'] = '^a'; + + and to hide both "db1" and "db2" use + + .. code-block:: php + + $cfg['Servers'][$i]['hide_db'] = '^(db1|db2)$'; + + More information on regular expressions can be found in the `PCRE + pattern syntax + `_ portion + of the PHP reference manual. + +.. config:option:: $cfg['Servers'][$i]['verbose'] + + :type: string + :default: ``''`` + + Only useful when using phpMyAdmin with multiple server entries. If + set, this string will be displayed instead of the hostname in the + pull-down menu on the main page. This can be useful if you want to + show only certain databases on your system, for example. For HTTP + auth, all non-US-ASCII characters will be stripped. + +.. config:option:: $cfg['Servers'][$i]['extension'] + + :type: string + :default: ``'mysqli'`` + + The PHP MySQL extension to use (``mysql`` or ``mysqli``). + + It is recommended to use ``mysqli`` in all installations. + +.. config:option:: $cfg['Servers'][$i]['pmadb'] + + :type: string + :default: ``''`` + + The name of the database containing the phpMyAdmin configuration + storage. + + See the :ref:`linked-tables` section in this document to see the benefits of + this feature, and for a quick way of creating this database and the needed + tables. + + If you are the only user of this phpMyAdmin installation, you can use your + current database to store those special tables; in this case, just put your + current database name in :config:option:`$cfg['Servers'][$i]['pmadb']`. For a + multi-user installation, set this parameter to the name of your central + database containing the phpMyAdmin configuration storage. + +.. _bookmark: +.. config:option:: $cfg['Servers'][$i]['bookmarktable'] + + :type: string or false + :default: ``''`` + + Since release 2.2.0 phpMyAdmin allows users to bookmark queries. This + can be useful for queries you often run. To allow the usage of this + functionality: + + * set up :config:option:`$cfg['Servers'][$i]['pmadb']` and the phpMyAdmin configuration storage + * enter the table name in :config:option:`$cfg['Servers'][$i]['bookmarktable']` + + This feature can be disabled by setting the configuration to ``false``. + +.. _relation: +.. config:option:: $cfg['Servers'][$i]['relation'] + + :type: string or false + :default: ``''`` + + Since release 2.2.4 you can describe, in a special 'relation' table, + which column is a key in another table (a foreign key). phpMyAdmin + currently uses this to: + + * make clickable, when you browse the master table, the data values that + point to the foreign table; + * display in an optional tool-tip the "display column" when browsing the + master table, if you move the mouse to a column containing a foreign + key (use also the 'table\_info' table); (see :ref:`faqdisplay`) + * in edit/insert mode, display a drop-down list of possible foreign keys + (key value and "display column" are shown) (see :ref:`faq6_21`) + * display links on the table properties page, to check referential + integrity (display missing foreign keys) for each described key; + * in query-by-example, create automatic joins (see :ref:`faq6_6`) + * enable you to get a :term:`PDF` schema of + your database (also uses the table\_coords table). + + The keys can be numeric or character. + + To allow the usage of this functionality: + + * set up :config:option:`$cfg['Servers'][$i]['pmadb']` and the phpMyAdmin configuration storage + * put the relation table name in :config:option:`$cfg['Servers'][$i]['relation']` + * now as normal user open phpMyAdmin and for each one of your tables + where you want to use this feature, click :guilabel:`Structure/Relation view/` + and choose foreign columns. + + This feature can be disabled by setting the configuration to ``false``. + + .. note:: + + In the current version, ``master_db`` must be the same as ``foreign_db``. + Those columns have been put in future development of the cross-db + relations. + +.. _table_info: +.. config:option:: $cfg['Servers'][$i]['table_info'] + + :type: string or false + :default: ``''`` + + Since release 2.3.0 you can describe, in a special 'table\_info' + table, which column is to be displayed as a tool-tip when moving the + cursor over the corresponding key. This configuration variable will + hold the name of this special table. To allow the usage of this + functionality: + + * set up :config:option:`$cfg['Servers'][$i]['pmadb']` and the phpMyAdmin configuration storage + * put the table name in :config:option:`$cfg['Servers'][$i]['table\_info']` (e.g. + ``pma__table_info``) + * then for each table where you want to use this feature, click + "Structure/Relation view/Choose column to display" to choose the + column. + + This feature can be disabled by setting the configuration to ``false``. + + .. seealso:: :ref:`faqdisplay` + +.. _table_coords: +.. config:option:: $cfg['Servers'][$i]['table_coords'] + + :type: string or false + :default: ``''`` + + The designer feature can save your page layout; by pressing the "Save page" or "Save page as" + button in the expanding designer menu, you can customize the layout and have it loaded the next + time you use the designer. That layout is stored in this table. Furthermore, this table is also + required for using the PDF relation export feature, see + :config:option:`$cfg['Servers'][$i]['pdf\_pages']` for additional details. + +.. config:option:: $cfg['Servers'][$i]['pdf_pages'] + + :type: string or false + :default: ``''`` + + Since release 2.3.0 you can have phpMyAdmin create :term:`PDF` pages + showing the relations between your tables. Further, the designer interface + permits visually managing the relations. To do this it needs two tables + "pdf\_pages" (storing information about the available :term:`PDF` pages) + and "table\_coords" (storing coordinates where each table will be placed on + a :term:`PDF` schema output). You must be using the "relation" feature. + + To allow the usage of this functionality: + + * set up :config:option:`$cfg['Servers'][$i]['pmadb']` and the phpMyAdmin configuration storage + * put the correct table names in + :config:option:`$cfg['Servers'][$i]['table\_coords']` and + :config:option:`$cfg['Servers'][$i]['pdf\_pages']` + + This feature can be disabled by setting either of the configurations to ``false``. + + .. seealso:: :ref:`faqpdf`. + +.. _col_com: +.. config:option:: $cfg['Servers'][$i]['column_info'] + + :type: string or false + :default: ``''`` + + This part requires a content update! Since release 2.3.0 you can + store comments to describe each column for each table. These will then + be shown on the "printview". + + Starting with release 2.5.0, comments are consequently used on the table + property pages and table browse view, showing up as tool-tips above the + column name (properties page) or embedded within the header of table in + browse view. They can also be shown in a table dump. Please see the + relevant configuration directives later on. + + Also new in release 2.5.0 is a MIME- transformation system which is also + based on the following table structure. See :ref:`transformations` for + further information. To use the MIME- transformation system, your + column\_info table has to have the three new columns 'mimetype', + 'transformation', 'transformation\_options'. + + Starting with release 4.3.0, a new input-oriented transformation system + has been introduced. Also, backward compatibility code used in the old + transformations system was removed. As a result, an update to column\_info + table is necessary for previous transformations and the new input-oriented + transformation system to work. phpMyAdmin will upgrade it automatically + for you by analyzing your current column\_info table structure. + However, if something goes wrong with the auto-upgrade then you can + use the SQL script found in ``./sql/upgrade_column_info_4_3_0+.sql`` + to upgrade it manually. + + To allow the usage of this functionality: + + * set up :config:option:`$cfg['Servers'][$i]['pmadb']` and the phpMyAdmin configuration storage + * put the table name in :config:option:`$cfg['Servers'][$i]['column\_info']` (e.g. + ``pma__column_info``) + * to update your PRE-2.5.0 Column\_comments table use this: and + remember that the Variable in :file:`config.inc.php` has been renamed from + :samp:`$cfg['Servers'][$i]['column\_comments']` to + :config:option:`$cfg['Servers'][$i]['column\_info']` + + .. code-block:: mysql + + ALTER TABLE `pma__column_comments` + ADD `mimetype` VARCHAR( 255 ) NOT NULL, + ADD `transformation` VARCHAR( 255 ) NOT NULL, + ADD `transformation_options` VARCHAR( 255 ) NOT NULL; + * to update your PRE-4.3.0 Column\_info table manually use this + ``./sql/upgrade_column_info_4_3_0+.sql`` SQL script. + + This feature can be disabled by setting the configuration to ``false``. + + .. note:: + + For auto-upgrade functionality to work, your + :config:option:`$cfg['Servers'][$i]['controluser']` must have ALTER privilege on + ``phpmyadmin`` database. See the `MySQL documentation for GRANT + `_ on how to + ``GRANT`` privileges to a user. + +.. _history: +.. config:option:: $cfg['Servers'][$i]['history'] + + :type: string or false + :default: ``''`` + + Since release 2.5.0 you can store your :term:`SQL` history, which means all + queries you entered manually into the phpMyAdmin interface. If you don't + want to use a table-based history, you can use the JavaScript-based + history. + + Using that, all your history items are deleted when closing the window. + Using :config:option:`$cfg['QueryHistoryMax']` you can specify an amount of + history items you want to have on hold. On every login, this list gets cut + to the maximum amount. + + The query history is only available if JavaScript is enabled in + your browser. + + To allow the usage of this functionality: + + * set up :config:option:`$cfg['Servers'][$i]['pmadb']` and the phpMyAdmin configuration storage + * put the table name in :config:option:`$cfg['Servers'][$i]['history']` (e.g. + ``pma__history``) + + This feature can be disabled by setting the configuration to ``false``. + +.. _recent: +.. config:option:: $cfg['Servers'][$i]['recent'] + + :type: string or false + :default: ``''`` + + Since release 3.5.0 you can show recently used tables in the + navigation panel. It helps you to jump across table directly, without + the need to select the database, and then select the table. Using + :config:option:`$cfg['NumRecentTables']` you can configure the maximum number + of recent tables shown. When you select a table from the list, it will jump to + the page specified in :config:option:`$cfg['NavigationTreeDefaultTabTable']`. + + Without configuring the storage, you can still access the recently used tables, + but it will disappear after you logout. + + To allow the usage of this functionality persistently: + + * set up :config:option:`$cfg['Servers'][$i]['pmadb']` and the phpMyAdmin configuration storage + * put the table name in :config:option:`$cfg['Servers'][$i]['recent']` (e.g. + ``pma__recent``) + + This feature can be disabled by setting the configuration to ``false``. + +.. _favorite: +.. config:option:: $cfg['Servers'][$i]['favorite'] + + :type: string or false + :default: ``''`` + + Since release 4.2.0 you can show a list of selected tables in the + navigation panel. It helps you to jump to the table directly, without + the need to select the database, and then select the table. When you + select a table from the list, it will jump to the page specified in + :config:option:`$cfg['NavigationTreeDefaultTabTable']`. + + You can add tables to this list or remove tables from it in database + structure page by clicking on the star icons next to table names. Using + :config:option:`$cfg['NumFavoriteTables']` you can configure the maximum + number of favorite tables shown. + + Without configuring the storage, you can still access the favorite tables, + but it will disappear after you logout. + + To allow the usage of this functionality persistently: + + * set up :config:option:`$cfg['Servers'][$i]['pmadb']` and the phpMyAdmin configuration storage + * put the table name in :config:option:`$cfg['Servers'][$i]['favorite']` (e.g. + ``pma__favorite``) + + This feature can be disabled by setting the configuration to ``false``. + +.. _table_uiprefs: +.. config:option:: $cfg['Servers'][$i]['table_uiprefs'] + + :type: string or false + :default: ``''`` + + Since release 3.5.0 phpMyAdmin can be configured to remember several + things (sorted column :config:option:`$cfg['RememberSorting']`, column order, + and column visibility from a database table) for browsing tables. Without + configuring the storage, these features still can be used, but the values will + disappear after you logout. + + To allow the usage of these functionality persistently: + + * set up :config:option:`$cfg['Servers'][$i]['pmadb']` and the phpMyAdmin configuration storage + * put the table name in :config:option:`$cfg['Servers'][$i]['table\_uiprefs']` (e.g. + ``pma__table_uiprefs``) + + This feature can be disabled by setting the configuration to ``false``. + +.. config:option:: $cfg['Servers'][$i]['users'] + + :type: string or false + :default: ``''`` + +.. config:option:: $cfg['Servers'][$i]['usergroups'] + + :type: string or false + :default: ``''`` + + Since release 4.1.0 you can create different user groups with menu items + attached to them. Users can be assigned to these groups and the logged in + user would only see menu items configured to the usergroup he is assigned to. + To do this it needs two tables "usergroups" (storing allowed menu items for each + user group) and "users" (storing users and their assignments to user groups). + + To allow the usage of this functionality: + + * set up :config:option:`$cfg['Servers'][$i]['pmadb']` and the phpMyAdmin configuration storage + * put the correct table names in + :config:option:`$cfg['Servers'][$i]['users']` (e.g. ``pma__users``) and + :config:option:`$cfg['Servers'][$i]['usergroups']` (e.g. ``pma__usergroups``) + + This feature can be disabled by setting either of the configurations to ``false``. + + .. seealso:: :ref:`configurablemenus` + +.. _navigationhiding: +.. config:option:: $cfg['Servers'][$i]['navigationhiding'] + + :type: string or false + :default: ``''`` + + Since release 4.1.0 you can hide/show items in the navigation tree. + + To allow the usage of this functionality: + + * set up :config:option:`$cfg['Servers'][$i]['pmadb']` and the phpMyAdmin configuration storage + * put the table name in :config:option:`$cfg['Servers'][$i]['navigationhiding']` (e.g. + ``pma__navigationhiding``) + + This feature can be disabled by setting the configuration to ``false``. + +.. _central_columns: +.. config:option:: $cfg['Servers'][$i]['central_columns'] + + :type: string or false + :default: ``''`` + + Since release 4.3.0 you can have a central list of columns per database. + You can add/remove columns to the list as per your requirement. These columns + in the central list will be available to use while you create a new column for + a table or create a table itself. You can select a column from central list + while creating a new column, it will save you from writing the same column definition + over again or from writing different names for similar column. + + To allow the usage of this functionality: + + * set up :config:option:`$cfg['Servers'][$i]['pmadb']` and the phpMyAdmin configuration storage + * put the table name in :config:option:`$cfg['Servers'][$i]['central_columns']` (e.g. + ``pma__central_columns``) + + This feature can be disabled by setting the configuration to ``false``. + +.. _designer_settings: +.. config:option:: $cfg['Servers'][$i]['designer_settings'] + + :type: string or false + :default: ``''`` + + Since release 4.5.0 your designer settings can be remembered. + Your choice regarding 'Angular/Direct Links', 'Snap to Grid', 'Toggle Relation Lines', + 'Small/Big All', 'Move Menu' and 'Pin Text' can be remembered persistently. + + To allow the usage of this functionality: + + * set up :config:option:`$cfg['Servers'][$i]['pmadb']` and the phpMyAdmin configuration storage + * put the table name in :config:option:`$cfg['Servers'][$i]['designer_settings']` (e.g. + ``pma__designer_settings``) + + This feature can be disabled by setting the configuration to ``false``. + +.. _savedsearches: +.. config:option:: $cfg['Servers'][$i]['savedsearches'] + + :type: string or false + :default: ``''`` + + Since release 4.2.0 you can save and load query-by-example searches from the Database > Query panel. + + To allow the usage of this functionality: + + * set up :config:option:`$cfg['Servers'][$i]['pmadb']` and the phpMyAdmin configuration storage + * put the table name in :config:option:`$cfg['Servers'][$i]['savedsearches']` (e.g. + ``pma__savedsearches``) + + This feature can be disabled by setting the configuration to ``false``. + +.. _export_templates: +.. config:option:: $cfg['Servers'][$i]['export_templates'] + + :type: string or false + :default: ``''`` + + Since release 4.5.0 you can save and load export templates. + + To allow the usage of this functionality: + + * set up :config:option:`$cfg['Servers'][$i]['pmadb']` and the phpMyAdmin configuration storage + * put the table name in :config:option:`$cfg['Servers'][$i]['export_templates']` (e.g. + ``pma__export_templates``) + + This feature can be disabled by setting the configuration to ``false``. + +.. _tracking: +.. config:option:: $cfg['Servers'][$i]['tracking'] + + :type: string or false + :default: ``''`` + + Since release 3.3.x a tracking mechanism is available. It helps you to + track every :term:`SQL` command which is + executed by phpMyAdmin. The mechanism supports logging of data + manipulation and data definition statements. After enabling it you can + create versions of tables. + + The creation of a version has two effects: + + * phpMyAdmin saves a snapshot of the table, including structure and + indexes. + * phpMyAdmin logs all commands which change the structure and/or data of + the table and links these commands with the version number. + + Of course you can view the tracked changes. On the :guilabel:`Tracking` + page a complete report is available for every version. For the report you + can use filters, for example you can get a list of statements within a date + range. When you want to filter usernames you can enter \* for all names or + you enter a list of names separated by ','. In addition you can export the + (filtered) report to a file or to a temporary database. + + To allow the usage of this functionality: + + * set up :config:option:`$cfg['Servers'][$i]['pmadb']` and the phpMyAdmin configuration storage + * put the table name in :config:option:`$cfg['Servers'][$i]['tracking']` (e.g. + ``pma__tracking``) + + This feature can be disabled by setting the configuration to ``false``. + +.. _tracking2: +.. config:option:: $cfg['Servers'][$i]['tracking_version_auto_create'] + + :type: boolean + :default: false + + Whether the tracking mechanism creates versions for tables and views + automatically. + + If this is set to true and you create a table or view with + + * CREATE TABLE ... + * CREATE VIEW ... + + and no version exists for it, the mechanism will create a version for + you automatically. + +.. _tracking3: +.. config:option:: $cfg['Servers'][$i]['tracking_default_statements'] + + :type: string + :default: ``'CREATE TABLE,ALTER TABLE,DROP TABLE,RENAME TABLE,CREATE INDEX,DROP INDEX,INSERT,UPDATE,DELETE,TRUNCATE,REPLACE,CREATE VIEW,ALTER VIEW,DROP VIEW,CREATE DATABASE,ALTER DATABASE,DROP DATABASE'`` + + Defines the list of statements the auto-creation uses for new + versions. + +.. _tracking4: +.. config:option:: $cfg['Servers'][$i]['tracking_add_drop_view'] + + :type: boolean + :default: true + + Whether a DROP VIEW IF EXISTS statement will be added as first line to + the log when creating a view. + +.. _tracking5: +.. config:option:: $cfg['Servers'][$i]['tracking_add_drop_table'] + + :type: boolean + :default: true + + Whether a DROP TABLE IF EXISTS statement will be added as first line + to the log when creating a table. + +.. _tracking6: +.. config:option:: $cfg['Servers'][$i]['tracking_add_drop_database'] + + :type: boolean + :default: true + + Whether a DROP DATABASE IF EXISTS statement will be added as first + line to the log when creating a database. + +.. _userconfig: +.. config:option:: $cfg['Servers'][$i]['userconfig'] + + :type: string or false + :default: ``''`` + + Since release 3.4.x phpMyAdmin allows users to set most preferences by + themselves and store them in the database. + + If you don't allow for storing preferences in + :config:option:`$cfg['Servers'][$i]['pmadb']`, users can still personalize + phpMyAdmin, but settings will be saved in browser's local storage, or, it + is is unavailable, until the end of session. + + To allow the usage of this functionality: + + * set up :config:option:`$cfg['Servers'][$i]['pmadb']` and the phpMyAdmin configuration storage + * put the table name in :config:option:`$cfg['Servers'][$i]['userconfig']` + + This feature can be disabled by setting the configuration to ``false``. + +.. config:option:: $cfg['Servers'][$i]['MaxTableUiprefs'] + + :type: integer + :default: 100 + + Maximum number of rows saved in + :config:option:`$cfg['Servers'][$i]['table_uiprefs']` table. + + When tables are dropped or renamed, + :config:option:`$cfg['Servers'][$i]['table_uiprefs']` may contain invalid data + (referring to tables which no longer exist). We only keep this number of newest + rows in :config:option:`$cfg['Servers'][$i]['table_uiprefs']` and automatically + delete older rows. + +.. config:option:: $cfg['Servers'][$i]['SessionTimeZone'] + + :type: string + :default: ``''`` + + Sets the time zone used by phpMyAdmin. Leave blank to use the time zone of your + database server. Possible values are explained at + https://dev.mysql.com/doc/refman/5.7/en/time-zone-support.html + + This is useful when your database server uses a time zone which is different from the + time zone you want to use in phpMyAdmin. + +.. config:option:: $cfg['Servers'][$i]['AllowRoot'] + + :type: boolean + :default: true + + Whether to allow root access. This is just a shortcut for the + :config:option:`$cfg['Servers'][$i]['AllowDeny']['rules']` below. + +.. config:option:: $cfg['Servers'][$i]['AllowNoPassword'] + + :type: boolean + :default: false + + Whether to allow logins without a password. The default value of + ``false`` for this parameter prevents unintended access to a MySQL + server with was left with an empty password for root or on which an + anonymous (blank) user is defined. + +.. _servers_allowdeny_order: +.. config:option:: $cfg['Servers'][$i]['AllowDeny']['order'] + + :type: string + :default: ``''`` + + If your rule order is empty, then :term:`IP` + authorization is disabled. + + If your rule order is set to + ``'deny,allow'`` then the system applies all deny rules followed by + allow rules. Access is allowed by default. Any client which does not + match a Deny command or does match an Allow command will be allowed + access to the server. + + If your rule order is set to ``'allow,deny'`` + then the system applies all allow rules followed by deny rules. Access + is denied by default. Any client which does not match an Allow + directive or does match a Deny directive will be denied access to the + server. + + If your rule order is set to ``'explicit'``, authorization is + performed in a similar fashion to rule order 'deny,allow', with the + added restriction that your host/username combination **must** be + listed in the *allow* rules, and not listed in the *deny* rules. This + is the **most** secure means of using Allow/Deny rules, and was + available in Apache by specifying allow and deny rules without setting + any order. + + Please also see :config:option:`$cfg['TrustedProxies']` for + detecting IP address behind proxies. + +.. _servers_allowdeny_rules: +.. config:option:: $cfg['Servers'][$i]['AllowDeny']['rules'] + + :type: array of strings + :default: array() + + The general format for the rules is as such: + + .. code-block:: none + + <'allow' | 'deny'> [from] + + If you wish to match all users, it is possible to use a ``'%'`` as a + wildcard in the *username* field. + + There are a few shortcuts you can + use in the *ipmask* field as well (please note that those containing + SERVER\_ADDRESS might not be available on all webservers): + + .. code-block:: none + + 'all' -> 0.0.0.0/0 + 'localhost' -> 127.0.0.1/8 + 'localnetA' -> SERVER_ADDRESS/8 + 'localnetB' -> SERVER_ADDRESS/16 + 'localnetC' -> SERVER_ADDRESS/24 + + Having an empty rule list is equivalent to either using ``'allow % + from all'`` if your rule order is set to ``'deny,allow'`` or ``'deny % + from all'`` if your rule order is set to ``'allow,deny'`` or + ``'explicit'``. + + For the :term:`IP address` matching + system, the following work: + + * ``xxx.xxx.xxx.xxx`` (an exact :term:`IP address`) + * ``xxx.xxx.xxx.[yyy-zzz]`` (an :term:`IP address` range) + * ``xxx.xxx.xxx.xxx/nn`` (CIDR, Classless Inter-Domain Routing type :term:`IP` addresses) + + But the following does not work: + + * ``xxx.xxx.xxx.xx[yyy-zzz]`` (partial :term:`IP` address range) + + For :term:`IPv6` addresses, the following work: + + * ``xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx`` (an exact :term:`IPv6` address) + * ``xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:[yyyy-zzzz]`` (an :term:`IPv6` address range) + * ``xxxx:xxxx:xxxx:xxxx/nn`` (CIDR, Classless Inter-Domain Routing type :term:`IPv6` addresses) + + But the following does not work: + + * ``xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xx[yyy-zzz]`` (partial :term:`IPv6` address range) + + Examples: + + .. code-block:: none + + $cfg['Servers'][$i]['AllowDeny']['order'] = 'allow,deny'; + $cfg['Servers'][$i]['AllowDeny']['rules'] = array('allow bob from all'); + // Allow only 'bob' to connect from any host + + $cfg['Servers'][$i]['AllowDeny']['order'] = 'allow,deny'; + $cfg['Servers'][$i]['AllowDeny']['rules'] = array('allow mary from 192.168.100.[50-100]'); + // Allow only 'mary' to connect from host 192.168.100.50 through 192.168.100.100 + + $cfg['Servers'][$i]['AllowDeny']['order'] = 'allow,deny'; + $cfg['Servers'][$i]['AllowDeny']['rules'] = array('allow % from 192.168.[5-6].10'); + // Allow any user to connect from host 192.168.5.10 or 192.168.6.10 + + $cfg['Servers'][$i]['AllowDeny']['order'] = 'allow,deny'; + $cfg['Servers'][$i]['AllowDeny']['rules'] = array('allow root from 192.168.5.50','allow % from 192.168.6.10'); + // Allow any user to connect from 192.168.6.10, and additionally allow root to connect from 192.168.5.50 + +.. config:option:: $cfg['Servers'][$i]['DisableIS'] + + :type: boolean + :default: false + + Disable using ``INFORMATION_SCHEMA`` to retrieve information (use + ``SHOW`` commands instead), because of speed issues when many + databases are present. + + .. note:: + + Enabling this option might give you a big performance boost on older + MySQL servers. + +.. config:option:: $cfg['Servers'][$i]['SignonScript'] + + :type: string + :default: ``''`` + + .. versionadded:: 3.5.0 + + Name of PHP script to be sourced and executed to obtain login + credentials. This is alternative approach to session based single + signon. The script has to provide a function called + ``get_login_credentials`` which returns list of username and + password, accepting single parameter of existing username (can be + empty). See :file:`examples/signon-script.php` for an example: + + .. literalinclude:: ../examples/signon-script.php + :language: php + + .. seealso:: :ref:`auth_signon` + +.. config:option:: $cfg['Servers'][$i]['SignonSession'] + + :type: string + :default: ``''`` + + Name of session which will be used for signon authentication method. + You should use something different than ``phpMyAdmin``, because this + is session which phpMyAdmin uses internally. Takes effect only if + :config:option:`$cfg['Servers'][$i]['SignonScript']` is not configured. + + .. seealso:: :ref:`auth_signon` + +.. config:option:: $cfg['Servers'][$i]['SignonCookieParams'] + + :type: array + :default: ``array()`` + + .. versionadded:: 4.7.0 + + An associative array of session cookie parameters of other authentication system. + It is not needed if the other system doesn't use session_set_cookie_params(). + Keys should include 'lifetime', 'path', 'domain', 'secure' or 'httponly'. + Valid values are mentioned in `session_get_cookie_params `_, they should be set to same values as the + other application uses. Takes effect only if + :config:option:`$cfg['Servers'][$i]['SignonScript']` is not configured. + + .. seealso:: :ref:`auth_signon` + +.. config:option:: $cfg['Servers'][$i]['SignonURL'] + + :type: string + :default: ``''`` + + :term:`URL` where user will be redirected + to log in for signon authentication method. Should be absolute + including protocol. + + .. seealso:: :ref:`auth_signon` + +.. config:option:: $cfg['Servers'][$i]['LogoutURL'] + + :type: string + :default: ``''`` + + :term:`URL` where user will be redirected + after logout (doesn't affect config authentication method). Should be + absolute including protocol. + +Generic settings +---------------- + +.. config:option:: $cfg['DisableShortcutKeys'] + + :type: boolean + :default: false + + You can disable phpMyAdmin shortcut keys by setting :config:option:`$cfg['DisableShortcutKeys']` to false. + +.. config:option:: $cfg['ServerDefault'] + + :type: integer + :default: 1 + + If you have more than one server configured, you can set + :config:option:`$cfg['ServerDefault']` to any one of them to autoconnect to that + server when phpMyAdmin is started, or set it to 0 to be given a list + of servers without logging in. + + If you have only one server configured, + :config:option:`$cfg['ServerDefault']` MUST be set to that server. + +.. config:option:: $cfg['VersionCheck'] + + :type: boolean + :default: true + + Enables check for latest versions using JavaScript on the main phpMyAdmin + page or by directly accessing :file:`version_check.php`. + + .. note:: + + This setting can be adjusted by your vendor. + +.. config:option:: $cfg['ProxyUrl'] + + :type: string + :default: "" + + The url of the proxy to be used when phpmyadmin needs to access the outside + internet such as when retrieving the latest version info or submitting error + reports. You need this if the server where phpMyAdmin is installed does not + have direct access to the internet. + The format is: "hostname:portnumber" + +.. config:option:: $cfg['ProxyUser'] + + :type: string + :default: "" + + The username for authenticating with the proxy. By default, no + authentication is performed. If a username is supplied, Basic + Authentication will be performed. No other types of authentication + are currently supported. + +.. config:option:: $cfg['ProxyPass'] + + :type: string + :default: "" + + The password for authenticating with the proxy. + +.. config:option:: $cfg['MaxDbList'] + + :type: integer + :default: 100 + + The maximum number of database names to be displayed in the main panel's + database list. + +.. config:option:: $cfg['MaxTableList'] + + :type: integer + :default: 250 + + The maximum number of table names to be displayed in the main panel's + list (except on the Export page). + +.. config:option:: $cfg['ShowHint'] + + :type: boolean + :default: true + + Whether or not to show hints (for example, hints when hovering over + table headers). + +.. config:option:: $cfg['MaxCharactersInDisplayedSQL'] + + :type: integer + :default: 1000 + + The maximum number of characters when a :term:`SQL` query is displayed. The + default limit of 1000 should be correct to avoid the display of tons of + hexadecimal codes that represent BLOBs, but some users have real + :term:`SQL` queries that are longer than 1000 characters. Also, if a + query's length exceeds this limit, this query is not saved in the history. + +.. config:option:: $cfg['PersistentConnections'] + + :type: boolean + :default: false + + Whether `persistent connections `_ should be used or not. Works with + following extensions: + + * mysql (`mysql\_pconnect `_), + * mysqli (requires PHP 5.3.0 or newer, `more information + `_). + +.. config:option:: $cfg['ForceSSL'] + + :type: boolean + :default: false + + .. deprecated:: 4.6.0 + + This setting is no longer available since phpMyAdmin 4.6.0. Please + adjust your webserver instead. + + Whether to force using https while accessing phpMyAdmin. In a reverse + proxy setup, setting this to ``true`` is not supported. + + .. note:: + + In some setups (like separate SSL proxy or load balancer) you might + have to set :config:option:`$cfg['PmaAbsoluteUri']` for correct + redirection. + +.. config:option:: $cfg['ExecTimeLimit'] + + :type: integer [number of seconds] + :default: 300 + + Set the number of seconds a script is allowed to run. If seconds is + set to zero, no time limit is imposed. This setting is used while + importing/exporting dump files but has + no effect when PHP is running in safe mode. + +.. config:option:: $cfg['SessionSavePath'] + + :type: string + :default: ``''`` + + Path for storing session data (`session\_save\_path PHP parameter + `_). + + .. warning:: + + This folder should not be publicly accessible through the webserver, + otherwise you risk leaking private data from your session. + +.. config:option:: $cfg['MemoryLimit'] + + :type: string [number of bytes] + :default: ``'-1'`` + + Set the number of bytes a script is allowed to allocate. If set to + ``'-1'``, no limit is imposed. If set to ``'0'``, no change of the + memory limit is attempted and the :file:`php.ini` ``memory_limit`` is + used. + + This setting is used while importing/exporting dump files + so you definitely don't want to put here a too low + value. It has no effect when PHP is running in safe mode. + + You can also use any string as in :file:`php.ini`, eg. '16M'. Ensure you + don't omit the suffix (16 means 16 bytes!) + +.. config:option:: $cfg['SkipLockedTables'] + + :type: boolean + :default: false + + Mark used tables and make it possible to show databases with locked + tables (since MySQL 3.23.30). + +.. config:option:: $cfg['ShowSQL'] + + :type: boolean + :default: true + + Defines whether :term:`SQL` queries + generated by phpMyAdmin should be displayed or not. + +.. config:option:: $cfg['RetainQueryBox'] + + :type: boolean + :default: false + + Defines whether the :term:`SQL` query box + should be kept displayed after its submission. + +.. config:option:: $cfg['CodemirrorEnable'] + + :type: boolean + :default: true + + Defines whether to use a Javascript code editor for SQL query boxes. + CodeMirror provides syntax highlighting and line numbers. However, + middle-clicking for pasting the clipboard contents in some Linux + distributions (such as Ubuntu) is not supported by all browsers. + +.. config:option:: $cfg['DefaultForeignKeyChecks'] + + :type: string + :default: ``'default'`` + + Default value of the checkbox for foreign key checks, to disable/enable + foreign key checks for certain queries. The possible values are ``'default'``, + ``'enable'`` or ``'disable'``. If set to ``'default'``, the value of the + MySQL variable ``FOREIGN_KEY_CHECKS`` is used. + +.. config:option:: $cfg['AllowUserDropDatabase'] + + :type: boolean + :default: false + + .. warning:: + + This is not a security measure as there will be always ways to + circumvent this. If you want to prohibit users from dropping databases, + revoke their corresponding DROP privilege. + + Defines whether normal users (non-administrator) are allowed to delete + their own database or not. If set as false, the link :guilabel:`Drop + Database` will not be shown, and even a ``DROP DATABASE mydatabase`` will + be rejected. Quite practical for :term:`ISP` 's with many customers. + + This limitation of :term:`SQL` queries is not as strict as when using MySQL + privileges. This is due to nature of :term:`SQL` queries which might be + quite complicated. So this choice should be viewed as help to avoid + accidental dropping rather than strict privilege limitation. + +.. config:option:: $cfg['Confirm'] + + :type: boolean + :default: true + + Whether a warning ("Are your really sure...") should be displayed when + you're about to lose data. + +.. config:option:: $cfg['UseDbSearch'] + + :type: boolean + :default: true + + Define whether the "search string inside database" is enabled or not. + +.. config:option:: $cfg['IgnoreMultiSubmitErrors'] + + :type: boolean + :default: false + + Define whether phpMyAdmin will continue executing a multi-query + statement if one of the queries fails. Default is to abort execution. + +Cookie authentication options +----------------------------- + +.. config:option:: $cfg['blowfish_secret'] + + :type: string + :default: ``''`` + + The "cookie" auth\_type uses AES algorithm to encrypt the password. If you + are using the "cookie" auth\_type, enter here a random passphrase of your + choice. It will be used internally by the AES algorithm: you won’t be + prompted for this passphrase. + + The secret should be 32 characters long. Using shorter will lead to weaker security + of encrypted cookies, using longer will cause no harm. + + .. note:: + + The configuration is called blowfish_secret for historical reasons as + Blowfish algorithm was originally used to do the encryption. + + .. versionchanged:: 3.1.0 + Since version 3.1.0 phpMyAdmin can generate this on the fly, but it + makes a bit weaker security as this generated secret is stored in + session and furthermore it makes impossible to recall user name from + cookie. + +.. config:option:: $cfg['LoginCookieRecall'] + + :type: boolean + :default: true + + Define whether the previous login should be recalled or not in cookie + authentication mode. + + This is automatically disabled if you do not have + configured :config:option:`$cfg['blowfish_secret']`. + +.. config:option:: $cfg['LoginCookieValidity'] + + :type: integer [number of seconds] + :default: 1440 + + Define how long a login cookie is valid. Please note that php + configuration option `session.gc\_maxlifetime + `_ might limit session validity and if the session is lost, + the login cookie is also invalidated. So it is a good idea to set + ``session.gc_maxlifetime`` at least to the same value of + :config:option:`$cfg['LoginCookieValidity']`. + +.. config:option:: $cfg['LoginCookieStore'] + + :type: integer [number of seconds] + :default: 0 + + Define how long login cookie should be stored in browser. Default 0 + means that it will be kept for existing session. This is recommended + for not trusted environments. + +.. config:option:: $cfg['LoginCookieDeleteAll'] + + :type: boolean + :default: true + + If enabled (default), logout deletes cookies for all servers, + otherwise only for current one. Setting this to false makes it easy to + forget to log out from other server, when you are using more of them. + +.. _AllowArbitraryServer: +.. config:option:: $cfg['AllowArbitraryServer'] + + :type: boolean + :default: false + + If enabled, allows you to log in to arbitrary servers using cookie + authentication. + + .. note:: + + Please use this carefully, as this may allow users access to MySQL servers + behind the firewall where your :term:`HTTP` server is placed. + See also :config:option:`$cfg['ArbitraryServerRegexp']`. + +.. config:option:: $cfg['ArbitraryServerRegexp'] + + :type: string + :default: ``''`` + + Restricts the MySQL servers to which the user can log in when + :config:option:`$cfg['AllowArbitraryServer']` is enabled by + matching the :term:`IP` or the hostname of the MySQL server + to the given regular expression. The regular expression must be enclosed + with a delimiter character. + + It is recommended to include start and end symbols in the regullar + expression, so that you can avoid partial matches on the string. + + **Examples:** + + .. code-block:: php + + // Allow connection to three listed servers: + $cfg['ArbitraryServerRegexp'] = '/^(server|another|yetdifferent)$/'; + + // Allow connection to range of IP addresses: + $cfg['ArbitraryServerRegexp'] = '@^192\.168\.0\.[0-9]{1,}$@'; + + // Allow connection to server name ending with -mysql: + $cfg['ArbitraryServerRegexp'] = '@^[^:]\-mysql$@'; + + .. note:: + + The whole server name is matched, it can include port as well. Due to + way MySQL is permissive in connection parameters, it is possible to use + connection strings as ```server:3306-mysql```. This can be used to + bypass regullar expression by the suffix, while connecting to another + server. + +.. config:option:: $cfg['CaptchaLoginPublicKey'] + + :type: string + :default: ``''`` + + The public key for the reCaptcha service that can be obtained from + https://www.google.com/recaptcha/intro/v3.html. + + reCaptcha will be then used in :ref:`cookie`. + +.. config:option:: $cfg['CaptchaLoginPrivateKey'] + + :type: string + :default: ``''`` + + The private key for the reCaptcha service that can be obtain from + https://www.google.com/recaptcha/intro/v3.html. + + reCaptcha will be then used in :ref:`cookie`. + +Navigation panel setup +---------------------- + +.. config:option:: $cfg['ShowDatabasesNavigationAsTree'] + + :type: boolean + :default: true + + In the navigation panel, replaces the database tree with a selector + +.. config:option:: $cfg['FirstLevelNavigationItems'] + + :type: integer + :default: 100 + + The number of first level databases that can be displayed on each page + of navigation tree. + +.. config:option:: $cfg['MaxNavigationItems'] + + :type: integer + :default: 50 + + The number of items (tables, columns, indexes) that can be displayed on each + page of the navigation tree. + +.. config:option:: $cfg['NavigationTreeEnableGrouping'] + + :type: boolean + :default: true + + Defines whether to group the databases based on a common prefix + in their name :config:option:`$cfg['NavigationTreeDbSeparator']`. + +.. config:option:: $cfg['NavigationTreeDbSeparator'] + + :type: string + :default: ``'_'`` + + The string used to separate the parts of the database name when + showing them in a tree. + +.. config:option:: $cfg['NavigationTreeTableSeparator'] + + :type: string or array + :default: ``'__'`` + + Defines a string to be used to nest table spaces. This means if you have + tables like ``first__second__third`` this will be shown as a three-level + hierarchy like: first > second > third. If set to false or empty, the + feature is disabled. NOTE: You should not use this separator at the + beginning or end of a table name or multiple times after another without + any other characters in between. + +.. config:option:: $cfg['NavigationTreeTableLevel'] + + :type: integer + :default: 1 + + Defines how many sublevels should be displayed when splitting up + tables by the above separator. + +.. config:option:: $cfg['NumRecentTables'] + + :type: integer + :default: 10 + + The maximum number of recently used tables shown in the navigation + panel. Set this to 0 (zero) to disable the listing of recent tables. + +.. config:option:: $cfg['NumFavoriteTables'] + + :type: integer + :default: 10 + + The maximum number of favorite tables shown in the navigation + panel. Set this to 0 (zero) to disable the listing of favorite tables. + +.. config:option:: $cfg['ZeroConf'] + + :type: boolean + :default: true + + Enables Zero Configuration mode in which the user will be offered a choice to + create phpMyAdmin configuration storage in the current database + or use the existing one, if already present. + + This setting has no effect if the phpMyAdmin configuration storage database + is properly created and the related configuration directives (such as + :config:option:`$cfg['Servers'][$i]['pmadb']` and so on) are configured. + +.. config:option:: $cfg['NavigationLinkWithMainPanel'] + + :type: boolean + :default: true + + Defines whether or not to link with main panel by highlighting + the current database or table. + +.. config:option:: $cfg['NavigationDisplayLogo'] + + :type: boolean + :default: true + + Defines whether or not to display the phpMyAdmin logo at the top of + the navigation panel. + +.. config:option:: $cfg['NavigationLogoLink'] + + :type: string + :default: ``'index.php'`` + + Enter :term:`URL` where logo in the navigation panel will point to. + For use especially with self made theme which changes this. + For relative/internal URLs, you need to have leading `` ./ `` or trailing characters `` ? `` such as ``'./sql.php?'``. + For external URLs, you should include URL protocol schemes (``http`` or ``https``) with absolute URLs. + +.. config:option:: $cfg['NavigationLogoLinkWindow'] + + :type: string + :default: ``'main'`` + + Whether to open the linked page in the main window (``main``) or in a + new one (``new``). Note: use ``new`` if you are linking to + ``phpmyadmin.net``. + +.. config:option:: $cfg['NavigationTreeDisplayItemFilterMinimum'] + + :type: integer + :default: 30 + + Defines the minimum number of items (tables, views, routines and + events) to display a JavaScript filter box above the list of items in + the navigation tree. + + To disable the filter completely some high number can be used (e.g. 9999) + +.. config:option:: $cfg['NavigationTreeDisplayDbFilterMinimum'] + + :type: integer + :default: 30 + + Defines the minimum number of databases to display a JavaScript filter + box above the list of databases in the navigation tree. + + To disable the filter completely some high number can be used + (e.g. 9999) + +.. config:option:: $cfg['NavigationDisplayServers'] + + :type: boolean + :default: true + + Defines whether or not to display a server choice at the top of the + navigation panel. + +.. config:option:: $cfg['DisplayServersList'] + + :type: boolean + :default: false + + Defines whether to display this server choice as links instead of in a + drop-down. + +.. config:option:: $cfg['NavigationTreeDefaultTabTable'] + + :type: string + :default: ``'structure'`` + + Defines the tab displayed by default when clicking the small icon next + to each table name in the navigation panel. The possible values are the + localized equivalent of: + + * ``structure`` + * ``sql`` + * ``search`` + * ``insert`` + * ``browse`` + +.. config:option:: $cfg['NavigationTreeDefaultTabTable2'] + + :type: string + :default: null + + Defines the tab displayed by default when clicking the second small icon next + to each table name in the navigation panel. The possible values are the + localized equivalent of: + + * ``(empty)`` + * ``structure`` + * ``sql`` + * ``search`` + * ``insert`` + * ``browse`` + +.. config:option:: $cfg['NavigationTreeEnableExpansion'] + + :type: boolean + :default: true + + Whether to offer the possibility of tree expansion in the navigation panel. + +.. config:option:: $cfg['NavigationTreeShowTables'] + + :type: boolean + :default: true + + Whether to show tables under database in the navigation panel. + +.. config:option:: $cfg['NavigationTreeShowViews'] + + :type: boolean + :default: true + + Whether to show views under database in the navigation panel. + +.. config:option:: $cfg['NavigationTreeShowFunctions'] + + :type: boolean + :default: true + + Whether to show functions under database in the navigation panel. + +.. config:option:: $cfg['NavigationTreeShowProcedures'] + + :type: boolean + :default: true + + Whether to show procedures under database in the navigation panel. + +.. config:option:: $cfg['NavigationTreeShowEvents'] + + :type: boolean + :default: true + + Whether to show events under database in the navigation panel. + +.. config:option:: $cfg['NavigationWidth'] + + :type: integer + :default: 240 + + Navigation panel width, set to 0 to collapse it by default. + +Main panel +---------- + +.. config:option:: $cfg['ShowStats'] + + :type: boolean + :default: true + + Defines whether or not to display space usage and statistics about + databases and tables. Note that statistics requires at least MySQL + 3.23.3 and that, at this date, MySQL doesn't return such information + for Berkeley DB tables. + +.. config:option:: $cfg['ShowServerInfo'] + + :type: boolean + :default: true + + Defines whether to display detailed server information on main page. + You can additionally hide more information by using + :config:option:`$cfg['Servers'][$i]['verbose']`. + +.. config:option:: $cfg['ShowPhpInfo'] + + :type: boolean + :default: false + + Defines whether to display the :guilabel:`PHP information` or not at + the starting main (right) frame. + + Please note that to block the usage of ``phpinfo()`` in scripts, you have to + put this in your :file:`php.ini`: + + .. code-block:: ini + + disable_functions = phpinfo() + + .. warning:: + + Enabling phpinfo page will leak quite a lot of information about server + setup. Is it not recommended to enable this on shared installations. + + This might also make easier some remote attacks on your installations, + so enable this only when needed. + +.. config:option:: $cfg['ShowChgPassword'] + + :type: boolean + :default: true + + Defines whether to display the :guilabel:`Change password` link or not at + the starting main (right) frame. This setting does not check MySQL commands + entered directly. + + Please note that enabling the :guilabel:`Change password` link has no effect + with config authentication mode: because of the hard coded password value + in the configuration file, end users can't be allowed to change their + passwords. + +.. config:option:: $cfg['ShowCreateDb'] + + :type: boolean + :default: true + + Defines whether to display the form for creating database or not at the + starting main (right) frame. This setting does not check MySQL commands + entered directly. + +.. config:option:: $cfg['ShowGitRevision'] + + :type: boolean + :default: true + + Defines whether to display informations about the current Git revision (if + applicable) on the main panel. + +.. config:option:: $cfg['MysqlMinVersion'] + + :type: array + + Defines the minimum supported MySQL version. The default is chosen + by the phpMyAdmin team; however this directive was asked by a developer + of the Plesk control panel to ease integration with older MySQL servers + (where most of the phpMyAdmin features work). + +Database structure +------------------ + +.. config:option:: $cfg['ShowDbStructureCreation'] + + :type: boolean + :default: false + + Defines whether the database structure page (tables list) has a + "Creation" column that displays when each table was created. + +.. config:option:: $cfg['ShowDbStructureLastUpdate'] + + :type: boolean + :default: false + + Defines whether the database structure page (tables list) has a "Last + update" column that displays when each table was last updated. + +.. config:option:: $cfg['ShowDbStructureLastCheck'] + + :type: boolean + :default: false + + Defines whether the database structure page (tables list) has a "Last + check" column that displays when each table was last checked. + +.. config:option:: $cfg['HideStructureActions'] + + :type: boolean + :default: true + + Defines whether the table structure actions are hidden under a "More" + drop-down. + +.. config:option:: $cfg['ShowColumnComments'] + + :type: boolean + :default: true + + Defines whether to show column comments as a column in the table structure view. + +Browse mode +----------- + +.. config:option:: $cfg['TableNavigationLinksMode'] + + :type: string + :default: ``'icons'`` + + Defines whether the table navigation links contain ``'icons'``, ``'text'`` + or ``'both'``. + +.. config:option:: $cfg['ActionLinksMode'] + + :type: string + :default: ``'both'`` + + If set to ``icons``, will display icons instead of text for db and table + properties links (like :guilabel:`Browse`, :guilabel:`Select`, + :guilabel:`Insert`, ...). Can be set to ``'both'`` + if you want icons AND text. When set to ``text``, will only show text. + +.. config:option:: $cfg['RowActionType'] + + :type: string + :default: ``'both'`` + + Whether to display icons or text or both icons and text in table row action + segment. Value can be either of ``'icons'``, ``'text'`` or ``'both'``. + +.. config:option:: $cfg['ShowAll'] + + :type: boolean + :default: false + + Defines whether a user should be displayed a "Show all" button in browse + mode or not in all cases. By default it is shown only on small tables (less + than 500 rows) to avoid performance issues while getting too many rows. + +.. config:option:: $cfg['MaxRows'] + + :type: integer + :default: 25 + + Number of rows displayed when browsing a result set and no LIMIT + clause is used. If the result set contains more rows, "Previous" and + "Next" links will be shown. Possible values: 25,50,100,250,500. + +.. config:option:: $cfg['Order'] + + :type: string + :default: ``'SMART'`` + + Defines whether columns are displayed in ascending (``ASC``) order, in + descending (``DESC``) order or in a "smart" (``SMART``) order - I.E. + descending order for columns of type TIME, DATE, DATETIME and + TIMESTAMP, ascending order else- by default. + + .. versionchanged:: 3.4.0 + Since phpMyAdmin 3.4.0 the default value is ``'SMART'``. + +.. config:option:: $cfg['GridEditing'] + + :type: string + :default: ``'double-click'`` + + Defines which action (``double-click`` or ``click``) triggers grid + editing. Can be deactivated with the ``disabled`` value. + +.. config:option:: $cfg['RelationalDisplay'] + + :type: string + :default: ``'K'`` + + Defines the initial behavior for Options > Relational. ``K``, which + is the default, displays the key while ``D`` shows the display column. + +.. config:option:: $cfg['SaveCellsAtOnce'] + + :type: boolean + :default: false + + Defines whether or not to save all edited cells at once for grid + editing. + +Editing mode +------------ + +.. config:option:: $cfg['ProtectBinary'] + + :type: boolean or string + :default: ``'blob'`` + + Defines whether ``BLOB`` or ``BINARY`` columns are protected from + editing when browsing a table's content. Valid values are: + + * ``false`` to allow editing of all columns; + * ``'blob'`` to allow editing of all columns except ``BLOBS``; + * ``'noblob'`` to disallow editing of all columns except ``BLOBS`` (the + opposite of ``'blob'``); + * ``'all'`` to disallow editing of all ``BINARY`` or ``BLOB`` columns. + +.. config:option:: $cfg['ShowFunctionFields'] + + :type: boolean + :default: true + + Defines whether or not MySQL functions fields should be initially + displayed in edit/insert mode. Since version 2.10, the user can toggle + this setting from the interface. + +.. config:option:: $cfg['ShowFieldTypesInDataEditView'] + + :type: boolean + :default: true + + Defines whether or not type fields should be initially displayed in + edit/insert mode. The user can toggle this setting from the interface. + +.. config:option:: $cfg['InsertRows'] + + :type: integer + :default: 2 + + Defines the default number of rows to be entered from the Insert page. + Users can manually change this from the bottom of that page to add or remove + blank rows. + +.. config:option:: $cfg['ForeignKeyMaxLimit'] + + :type: integer + :default: 100 + + If there are fewer items than this in the set of foreign keys, then a + drop-down box of foreign keys is presented, in the style described by + the :config:option:`$cfg['ForeignKeyDropdownOrder']` setting. + +.. config:option:: $cfg['ForeignKeyDropdownOrder'] + + :type: array + :default: array('content-id', 'id-content') + + For the foreign key drop-down fields, there are several methods of + display, offering both the key and value data. The contents of the + array should be one or both of the following strings: ``content-id``, + ``id-content``. + +Export and import settings +-------------------------- + +.. config:option:: $cfg['ZipDump'] + + :type: boolean + :default: true + +.. config:option:: $cfg['GZipDump'] + + :type: boolean + :default: true + +.. config:option:: $cfg['BZipDump'] + + :type: boolean + :default: true + + Defines whether to allow the use of zip/GZip/BZip2 compression when + creating a dump file + +.. config:option:: $cfg['CompressOnFly'] + + :type: boolean + :default: true + + Defines whether to allow on the fly compression for GZip/BZip2 + compressed exports. This doesn't affect smaller dumps and allows users + to create larger dumps that won't otherwise fit in memory due to php + memory limit. Produced files contain more GZip/BZip2 headers, but all + normal programs handle this correctly. + +.. config:option:: $cfg['Export'] + + :type: array + :default: array(...) + + In this array are defined default parameters for export, names of + items are similar to texts seen on export page, so you can easily + identify what they mean. + +.. config:option:: $cfg['Export']['format'] + + :type: string + :default: ``'sql'`` + + Default export format. + +.. config:option:: $cfg['Export']['method'] + + :type: string + :default: ``'quick'`` + + Defines how the export form is displayed when it loads. Valid values + are: + + * ``quick`` to display the minimum number of options to configure + * ``custom`` to display every available option to configure + * ``custom-no-form`` same as ``custom`` but does not display the option + of using quick export + +.. config:option:: $cfg['Export']['charset'] + + :type: string + :default: ``''`` + + Defines charset for generated export. By default no charset conversion is + done assuming UTF-8. + +.. config:option:: $cfg['Export']['file_template_table'] + + :type: string + :default: ``'@TABLE@'`` + + Default filename template for table exports. + + .. seealso:: :ref:`faq6_27` + +.. config:option:: $cfg['Export']['file_template_database'] + + :type: string + :default: ``'@DATABASE@'`` + + Default filename template for database exports. + + .. seealso:: :ref:`faq6_27` + +.. config:option:: $cfg['Export']['file_template_server'] + + :type: string + :default: ``'@SERVER@'`` + + Default filename template for server exports. + + .. seealso:: :ref:`faq6_27` + +.. config:option:: $cfg['Import'] + + :type: array + :default: array(...) + + In this array are defined default parameters for import, names of + items are similar to texts seen on import page, so you can easily + identify what they mean. + +.. config:option:: $cfg['Import']['charset'] + + :type: string + :default: ``''`` + + Defines charset for import. By default no charset conversion is done + assuming UTF-8. + +Tabs display settings +--------------------- + +.. config:option:: $cfg['TabsMode'] + + :type: string + :default: ``'both'`` + + Defines whether the menu tabs contain ``'icons'``, ``'text'`` or ``'both'``. + +.. config:option:: $cfg['PropertiesNumColumns'] + + :type: integer + :default: 1 + + How many columns will be utilized to display the tables on the database + property view? When setting this to a value larger than 1, the type of the + database will be omitted for more display space. + +.. config:option:: $cfg['DefaultTabServer'] + + :type: string + :default: ``'welcome'`` + + Defines the tab displayed by default on server view. The possible values + are the localized equivalent of: + + * ``welcome`` (recommended for multi-user setups) + * ``databases``, + * ``status`` + * ``variables`` + * ``privileges`` + +.. config:option:: $cfg['DefaultTabDatabase'] + + :type: string + :default: ``'structure'`` + + Defines the tab displayed by default on database view. The possible values + are the localized equivalent of: + + * ``structure`` + * ``sql`` + * ``search`` + * ``operations`` + +.. config:option:: $cfg['DefaultTabTable'] + + :type: string + :default: ``'browse'`` + + Defines the tab displayed by default on table view. The possible values + are the localized equivalent of: + + * ``structure`` + * ``sql`` + * ``search`` + * ``insert`` + * ``browse`` + +PDF Options +----------- + +.. config:option:: $cfg['PDFPageSizes'] + + :type: array + :default: ``array('A3', 'A4', 'A5', 'letter', 'legal')`` + + Array of possible paper sizes for creating PDF pages. + + You should never need to change this. + +.. config:option:: $cfg['PDFDefaultPageSize'] + + :type: string + :default: ``'A4'`` + + Default page size to use when creating PDF pages. Valid values are any + listed in :config:option:`$cfg['PDFPageSizes']`. + +Languages +--------- + +.. config:option:: $cfg['DefaultLang'] + + :type: string + :default: ``'en'`` + + Defines the default language to use, if not browser-defined or user- + defined. The corresponding language file needs to be in + locale/*code*/LC\_MESSAGES/phpmyadmin.mo. + +.. config:option:: $cfg['DefaultConnectionCollation'] + + :type: string + :default: ``'utf8mb4_general_ci'`` + + Defines the default connection collation to use, if not user-defined. + See the `MySQL documentation for charsets + `_ + for list of possible values. + +.. config:option:: $cfg['Lang'] + + :type: string + :default: not set + + Force language to use. The corresponding language file needs to be in + locale/*code*/LC\_MESSAGES/phpmyadmin.mo. + +.. config:option:: $cfg['FilterLanguages'] + + :type: string + :default: ``''`` + + Limit list of available languages to those matching the given regular + expression. For example if you want only Czech and English, you should + set filter to ``'^(cs|en)'``. + +.. config:option:: $cfg['RecodingEngine'] + + :type: string + :default: ``'auto'`` + + You can select here which functions will be used for character set + conversion. Possible values are: + + * auto - automatically use available one (first is tested iconv, then + recode) + * iconv - use iconv or libiconv functions + * recode - use recode\_string function + * mb - use :term:`mbstring` extension + * none - disable encoding conversion + + Enabled charset conversion activates a pull-down menu in the Export + and Import pages, to choose the character set when exporting a file. + The default value in this menu comes from + :config:option:`$cfg['Export']['charset']` and :config:option:`$cfg['Import']['charset']`. + +.. config:option:: $cfg['IconvExtraParams'] + + :type: string + :default: ``'//TRANSLIT'`` + + Specify some parameters for iconv used in charset conversion. See + `iconv documentation `_ for details. By default + ``//TRANSLIT`` is used, so that invalid characters will be + transliterated. + +.. config:option:: $cfg['AvailableCharsets'] + + :type: array + :default: array(...) + + Available character sets for MySQL conversion. You can add your own + (any of supported by recode/iconv) or remove these which you don't + use. Character sets will be shown in same order as here listed, so if + you frequently use some of these move them to the top. + +Web server settings +------------------- + +.. config:option:: $cfg['OBGzip'] + + :type: string/boolean + :default: ``'auto'`` + + Defines whether to use GZip output buffering for increased speed in + :term:`HTTP` transfers. Set to + true/false for enabling/disabling. When set to 'auto' (string), + phpMyAdmin tries to enable output buffering and will automatically + disable it if your browser has some problems with buffering. IE6 with + a certain patch is known to cause data corruption when having enabled + buffering. + +.. config:option:: $cfg['TrustedProxies'] + + :type: array + :default: array() + + Lists proxies and HTTP headers which are trusted for + :config:option:`$cfg['Servers'][$i]['AllowDeny']['order']`. This list is by + default empty, you need to fill in some trusted proxy servers if you + want to use rules for IP addresses behind proxy. + + The following example specifies that phpMyAdmin should trust a + HTTP\_X\_FORWARDED\_FOR (``X-Forwarded-For``) header coming from the proxy + 1.2.3.4: + + .. code-block:: php + + $cfg['TrustedProxies'] = array('1.2.3.4' => 'HTTP_X_FORWARDED_FOR'); + + The :config:option:`$cfg['Servers'][$i]['AllowDeny']['rules']` directive uses the + client's IP address as usual. + +.. config:option:: $cfg['GD2Available'] + + :type: string + :default: ``'auto'`` + + Specifies whether GD >= 2 is available. If yes it can be used for MIME + transformations. Possible values are: + + * auto - automatically detect + * yes - GD 2 functions can be used + * no - GD 2 function cannot be used + +.. config:option:: $cfg['CheckConfigurationPermissions'] + + :type: boolean + :default: true + + We normally check the permissions on the configuration file to ensure + it's not world writable. However, phpMyAdmin could be installed on a + NTFS filesystem mounted on a non-Windows server, in which case the + permissions seems wrong but in fact cannot be detected. In this case a + sysadmin would set this parameter to ``false``. + +.. config:option:: $cfg['LinkLengthLimit'] + + :type: integer + :default: 1000 + + Limit for length of :term:`URL` in links. When length would be above this + limit, it is replaced by form with button. This is required as some web + servers (:term:`IIS`) have problems with long :term:`URL` . + +.. config:option:: $cfg['CSPAllow'] + + :type: string + :default: ``''`` + + Additional string to include in allowed script and image sources in Content + Security Policy header. + + This can be useful when you want to include some external JavaScript files + in :file:`config.footer.inc.php` or :file:`config.header.inc.php`, which + would be normally not allowed by Content Security Policy. + + To allow some sites, just list them within the string: + + .. code-block:: php + + $cfg['CSPAllow'] = 'example.com example.net'; + + .. versionadded:: 4.0.4 + +.. config:option:: $cfg['DisableMultiTableMaintenance'] + + :type: boolean + :default: false + + In the database Structure page, it's possible to mark some tables then + choose an operation like optimizing for many tables. This can slow + down a server; therefore, setting this to ``true`` prevents this kind + of multiple maintenance operation. + +Theme settings +-------------- + + Please directly modify :file:`themes/themename/layout.inc.php`, although + your changes will be overwritten with the next update. + +Design customization +-------------------- + +.. config:option:: $cfg['NavigationTreePointerEnable'] + + :type: boolean + :default: true + + When set to true, hovering over an item in the navigation panel causes that item to be marked + (the background is highlighted). + +.. config:option:: $cfg['BrowsePointerEnable'] + + :type: boolean + :default: true + + When set to true, hovering over a row in the Browse page causes that row to be marked (the background + is highlighted). + +.. config:option:: $cfg['BrowseMarkerEnable'] + + :type: boolean + :default: true + + When set to true, a data row is marked (the background is highlighted) when the row is selected + with the checkbox. + +.. config:option:: $cfg['LimitChars'] + + :type: integer + :default: 50 + + Maximum number of characters shown in any non-numeric field on browse + view. Can be turned off by a toggle button on the browse page. + +.. config:option:: $cfg['RowActionLinks'] + + :type: string + :default: ``'left'`` + + Defines the place where table row links (Edit, Copy, Delete) would be + put when tables contents are displayed (you may have them displayed at + the left side, right side, both sides or nowhere). + +.. config:option:: $cfg['RowActionLinksWithoutUnique'] + + :type: boolean + :default: false + + Defines whether to show row links (Edit, Copy, Delete) and checkboxes + for multiple row operations even when the selection does not have a :term:`unique key`. + Using row actions in the absence of a unique key may result in different/more + rows being affected since there is no guaranteed way to select the exact row(s). + +.. config:option:: $cfg['RememberSorting'] + + :type: boolean + :default: true + + If enabled, remember the sorting of each table when browsing them. + +.. config:option:: $cfg['TablePrimaryKeyOrder'] + + :type: string + :default: ``'NONE'`` + + This defines the default sort order for the tables, having a :term:`primary key`, + when there is no sort order defines externally. + Acceptable values : ['NONE', 'ASC', 'DESC'] + +.. config:option:: $cfg['ShowBrowseComments'] + + :type: boolean + :default: true + +.. config:option:: $cfg['ShowPropertyComments'] + + :type: boolean + :default: true + + By setting the corresponding variable to ``true`` you can enable the + display of column comments in Browse or Property display. In browse + mode, the comments are shown inside the header. In property mode, + comments are displayed using a CSS-formatted dashed-line below the + name of the column. The comment is shown as a tool-tip for that + column. + +Text fields +----------- + +.. config:option:: $cfg['CharEditing'] + + :type: string + :default: ``'input'`` + + Defines which type of editing controls should be used for CHAR and + VARCHAR columns. Applies to data editing and also to the default values + in structure editing. Possible values are: + + * input - this allows to limit size of text to size of columns in MySQL, + but has problems with newlines in columns + * textarea - no problems with newlines in columns, but also no length + limitations + +.. config:option:: $cfg['MinSizeForInputField'] + + :type: integer + :default: 4 + + Defines the minimum size for input fields generated for CHAR and + VARCHAR columns. + +.. config:option:: $cfg['MaxSizeForInputField'] + + :type: integer + :default: 60 + + Defines the maximum size for input fields generated for CHAR and + VARCHAR columns. + +.. config:option:: $cfg['TextareaCols'] + + :type: integer + :default: 40 + +.. config:option:: $cfg['TextareaRows'] + + :type: integer + :default: 15 + +.. config:option:: $cfg['CharTextareaCols'] + + :type: integer + :default: 40 + +.. config:option:: $cfg['CharTextareaRows'] + + :type: integer + :default: 2 + + Number of columns and rows for the textareas. This value will be + emphasized (\*2) for :term:`SQL` query + textareas and (\*1.25) for :term:`SQL` + textareas inside the query window. + + The Char\* values are used for CHAR + and VARCHAR editing (if configured via :config:option:`$cfg['CharEditing']`). + +.. config:option:: $cfg['LongtextDoubleTextarea'] + + :type: boolean + :default: true + + Defines whether textarea for LONGTEXT columns should have double size. + +.. config:option:: $cfg['TextareaAutoSelect'] + + :type: boolean + :default: false + + Defines if the whole textarea of the query box will be selected on + click. + +.. config:option:: $cfg['EnableAutocompleteForTablesAndColumns'] + + :type: boolean + :default: true + + Whether to enable autocomplete for table and column names in any + SQL query box. + +SQL query box settings +---------------------- + +.. config:option:: $cfg['SQLQuery']['Edit'] + + :type: boolean + :default: true + + Whether to display an edit link to change a query in any SQL Query + box. + +.. config:option:: $cfg['SQLQuery']['Explain'] + + :type: boolean + :default: true + + Whether to display a link to explain a SELECT query in any SQL Query + box. + +.. config:option:: $cfg['SQLQuery']['ShowAsPHP'] + + :type: boolean + :default: true + + Whether to display a link to wrap a query in PHP code in any SQL Query + box. + +.. config:option:: $cfg['SQLQuery']['Refresh'] + + :type: boolean + :default: true + + Whether to display a link to refresh a query in any SQL Query box. + +.. _web-dirs: + +Web server upload/save/import directories +----------------------------------------- + +If PHP is running in safe mode, all directories must be owned by the same user +as the owner of the phpMyAdmin scripts. + +If the directory where phpMyAdmin is installed is subject to an +``open_basedir`` restriction, you need to create a temporary directory in some +directory accessible by the PHP interpreter. + +For security reasons, all directories should be outside the tree published by +webserver. If you cannot avoid having this directory published by webserver, +limit access to it either by web server configuration (for example using +.htaccess or web.config files) or place at least an empty :file:`index.html` +file there, so that directory listing is not possible. However as long as the +directory is accessible by web server, an attacker can guess filenames to download +the files. + +.. config:option:: $cfg['UploadDir'] + + :type: string + :default: ``''`` + + The name of the directory where :term:`SQL` files have been uploaded by + other means than phpMyAdmin (for example, ftp). Those files are available + under a drop-down box when you click the database or table name, then the + Import tab. + + If + you want different directory for each user, %u will be replaced with + username. + + Please note that the file names must have the suffix ".sql" + (or ".sql.bz2" or ".sql.gz" if support for compressed formats is + enabled). + + This feature is useful when your file is too big to be + uploaded via :term:`HTTP`, or when file + uploads are disabled in PHP. + + .. warning:: + + Please see top of this chapter (:ref:`web-dirs`) for instructions how + to setup this directory and how to make its usage secure. + + .. seealso:: + + See :ref:`faq1_16` for alternatives. + +.. config:option:: $cfg['SaveDir'] + + :type: string + :default: ``''`` + + The name of the webserver directory where exported files can be saved. + + If you want a different directory for each user, %u will be replaced with the + username. + + Please note that the directory must exist and has to be writable for + the user running webserver. + + .. warning:: + + Please see top of this chapter (:ref:`web-dirs`) for instructions how + to setup this directory and how to make its usage secure. + +.. config:option:: $cfg['TempDir'] + + :type: string + :default: ``'./tmp/'`` + + The name of the directory where temporary files can be stored. It is used + for several purposes, currently: + + * The templates cache which speeds up page loading. + * ESRI Shapefiles import, see :ref:`faq6_30`. + * To work around limitations of ``open_basedir`` for uploaded files, see + :ref:`faq1_11`. + + This directory should have as strict permissions as possible as the only + user required to access this directory is the one who runs the webserver. + If you have root privileges, simply make this user owner of this directory + and make it accessible only by it: + + .. code-block:: sh + + chown www-data:www-data tmp + chmod 700 tmp + + If you cannot change owner of the directory, you can achieve a similar + setup using :term:`ACL`: + + .. code-block:: sh + + chmod 700 tmp + setfacl -m "g:www-data:rwx" tmp + setfacl -d -m "g:www-data:rwx" tmp + + If neither of above works for you, you can still make the directory + :command:`chmod 777`, but it might impose risk of other users on system + reading and writing data in this directory. + + .. warning:: + + Please see top of this chapter (:ref:`web-dirs`) for instructions how + to setup this directory and how to make its usage secure. + +Various display setting +----------------------- + +.. config:option:: $cfg['RepeatCells'] + + :type: integer + :default: 100 + + Repeat the headers every X cells, or 0 to deactivate. + +.. config:option:: $cfg['QueryHistoryDB'] + + :type: boolean + :default: false + +.. config:option:: $cfg['QueryHistoryMax'] + + :type: integer + :default: 25 + + If :config:option:`$cfg['QueryHistoryDB']` is set to ``true``, all your + Queries are logged to a table, which has to be created by you (see + :config:option:`$cfg['Servers'][$i]['history']`). If set to false, all your + queries will be appended to the form, but only as long as your window is + opened they remain saved. + + When using the JavaScript based query window, it will always get updated + when you click on a new table/db to browse and will focus if you click on + :guilabel:`Edit SQL` after using a query. You can suppress updating the + query window by checking the box :guilabel:`Do not overwrite this query + from outside the window` below the query textarea. Then you can browse + tables/databases in the background without losing the contents of the + textarea, so this is especially useful when composing a query with tables + you first have to look in. The checkbox will get automatically checked + whenever you change the contents of the textarea. Please uncheck the button + whenever you definitely want the query window to get updated even though + you have made alterations. + + If :config:option:`$cfg['QueryHistoryDB']` is set to ``true`` you can + specify the amount of saved history items using + :config:option:`$cfg['QueryHistoryMax']`. + +.. config:option:: $cfg['BrowseMIME'] + + :type: boolean + :default: true + + Enable :ref:`transformations`. + +.. config:option:: $cfg['MaxExactCount'] + + :type: integer + :default: 50000 + + For InnoDB tables, determines for how large tables phpMyAdmin should + get the exact row count using ``SELECT COUNT``. If the approximate row + count as returned by ``SHOW TABLE STATUS`` is smaller than this value, + ``SELECT COUNT`` will be used, otherwise the approximate count will be + used. + + .. versionchanged:: 4.8.0 + + The default value was lowered to 50000 for performance reasons. + + .. versionchanged:: 4.2.6 + + The default value was changed to 500000. + + .. seealso:: :ref:`faq3_11` + +.. config:option:: $cfg['MaxExactCountViews'] + + :type: integer + :default: 0 + + For VIEWs, since obtaining the exact count could have an impact on + performance, this value is the maximum to be displayed, using a + ``SELECT COUNT ... LIMIT``. Setting this to 0 bypasses any row + counting. + +.. config:option:: $cfg['NaturalOrder'] + + :type: boolean + :default: true + + Sorts database and table names according to natural order (for + example, t1, t2, t10). Currently implemented in the navigation panel + and in Database view, for the table list. + +.. config:option:: $cfg['InitialSlidersState'] + + :type: string + :default: ``'closed'`` + + If set to ``'closed'``, the visual sliders are initially in a closed + state. A value of ``'open'`` does the reverse. To completely disable + all visual sliders, use ``'disabled'``. + +.. config:option:: $cfg['UserprefsDisallow'] + + :type: array + :default: array() + + Contains names of configuration options (keys in ``$cfg`` array) that + users can't set through user preferences. For possible values, refer + to clases under :file:`libraries/classes/Config/Forms/User/`. + +.. config:option:: $cfg['UserprefsDeveloperTab'] + + :type: boolean + :default: false + + Activates in the user preferences a tab containing options for + developers of phpMyAdmin. + +Page titles +----------- + +.. config:option:: $cfg['TitleTable'] + + :type: string + :default: ``'@HTTP_HOST@ / @VSERVER@ / @DATABASE@ / @TABLE@ | @PHPMYADMIN@'`` + +.. config:option:: $cfg['TitleDatabase'] + + :type: string + :default: ``'@HTTP_HOST@ / @VSERVER@ / @DATABASE@ | @PHPMYADMIN@'`` + +.. config:option:: $cfg['TitleServer'] + + :type: string + :default: ``'@HTTP_HOST@ / @VSERVER@ | @PHPMYADMIN@'`` + +.. config:option:: $cfg['TitleDefault'] + + :type: string + :default: ``'@HTTP_HOST@ | @PHPMYADMIN@'`` + + Allows you to specify window's title bar. You can use :ref:`faq6_27`. + +Theme manager settings +---------------------- + +.. config:option:: $cfg['ThemeManager'] + + :type: boolean + :default: true + + Enables user-selectable themes. See :ref:`faqthemes`. + +.. config:option:: $cfg['ThemeDefault'] + + :type: string + :default: ``'pmahomme'`` + + The default theme (a subdirectory under :file:`./themes/`). + +.. config:option:: $cfg['ThemePerServer'] + + :type: boolean + :default: false + + Whether to allow different theme for each server. + +.. config:option:: $cfg['FontSize'] + + :type: string + :default: '82%' + + Font size to use, is applied in CSS. + +Default queries +--------------- + +.. config:option:: $cfg['DefaultQueryTable'] + + :type: string + :default: ``'SELECT * FROM @TABLE@ WHERE 1'`` + +.. config:option:: $cfg['DefaultQueryDatabase'] + + :type: string + :default: ``''`` + + Default queries that will be displayed in query boxes when user didn't + specify any. You can use standard :ref:`faq6_27`. + +MySQL settings +-------------- + +.. config:option:: $cfg['DefaultFunctions'] + + :type: array + :default: array(...) + + Functions selected by default when inserting/changing row, Functions + are defined for meta types as (FUNC\_NUMBER, FUNC\_DATE, FUNC\_CHAR, + FUNC\_SPATIAL, FUNC\_UUID) and for ``first_timestamp``, which is used + for first timestamp column in table. + +Default options for Transformations +----------------------------------- + +.. config:option:: $cfg['DefaultTransformations'] + + :type: array + :default: An array with below listed key-values + +.. config:option:: $cfg['DefaultTransformations']['Substring'] + + :type: array + :default: array(0, 'all', '…') + +.. config:option:: $cfg['DefaultTransformations']['Bool2Text'] + + :type: array + :default: array('T', 'F') + +.. config:option:: $cfg['DefaultTransformations']['External'] + + :type: array + :default: array(0, '-f /dev/null -i -wrap -q', 1, 1) + +.. config:option:: $cfg['DefaultTransformations']['PreApPend'] + + :type: array + :default: array('', '') + +.. config:option:: $cfg['DefaultTransformations']['Hex'] + + :type: array + :default: array('2') + +.. config:option:: $cfg['DefaultTransformations']['DateFormat'] + + :type: array + :default: array(0, '', 'local') + +.. config:option:: $cfg['DefaultTransformations']['Inline'] + + :type: array + :default: array('100', 100) + +.. config:option:: $cfg['DefaultTransformations']['TextImageLink'] + + :type: array + :default: array('', 100, 50) + +.. config:option:: $cfg['DefaultTransformations']['TextLink'] + + :type: array + :default: array('', '', '') + +Console settings +---------------- + +.. note:: + + These settings are mostly meant to be changed by user. + +.. config:option:: $cfg['Console']['StartHistory'] + + :type: boolean + :default: false + + Show query history at start + +.. config:option:: $cfg['Console']['AlwaysExpand'] + + :type: boolean + :default: false + + Always expand query messages + +.. config:option:: $cfg['Console']['CurrentQuery'] + + :type: boolean + :default: true + + Show current browsing query + +.. config:option:: $cfg['Console']['EnterExecutes'] + + :type: boolean + :default: false + + Execute queries on Enter and insert new line with Shift + Enter + +.. config:option:: $cfg['Console']['DarkTheme'] + + :type: boolean + :default: false + + Switch to dark theme + +.. config:option:: $cfg['Console']['Mode'] + + :type: string + :default: 'info' + + Console mode + +.. config:option:: $cfg['Console']['Height'] + + :type: integer + :default: 92 + + Console height + +Developer +--------- + +.. warning:: + + These settings might have huge effect on performance or security. + +.. config:option:: $cfg['DBG'] + + :type: array + :default: array(...) + +.. config:option:: $cfg['DBG']['sql'] + + :type: boolean + :default: false + + Enable logging queries and execution times to be + displayed in the console's Debug SQL tab. + +.. config:option:: $cfg['DBG']['sqllog'] + + :type: boolean + :default: false + + Enable logging of queries and execution times to the syslog. + Requires :config:option:`$cfg['DBG']['sql']` to be enabled. + +.. config:option:: $cfg['DBG']['demo'] + + :type: boolean + :default: false + + Enable to let server present itself as demo server. + This is used for `phpMyAdmin demo server `_. + + It currently changes following behavior: + + * There is welcome message on the main page. + * There is footer information about demo server and used git revision. + * The setup script is enabled even with existing configuration. + * The setup does not try to connect to the MySQL server. + +.. config:option:: $cfg['DBG']['simple2fa'] + + :type: boolean + :default: false + + Can be used for testing two-factor authentication using :ref:`simple2fa`. + +.. _config-examples: + +Examples +-------- + +See following configuration snippets for typical setups of phpMyAdmin. + +Basic example ++++++++++++++ + +Example configuration file, which can be copied to :file:`config.inc.php` to +get some core configuration layout; it is distributed with phpMyAdmin as +:file:`config.sample.inc.php`. Please note that it does not contain all +configuration options, only the most frequently used ones. + +.. literalinclude:: ../config.sample.inc.php + :language: php + +.. warning:: + + Don't use the controluser 'pma' if it does not yet exist and don't use 'pmapass' + as password. + +.. _example-signon: + +Example for signon authentication ++++++++++++++++++++++++++++++++++ + +This example uses :file:`examples/signon.php` to demonstrate usage of :ref:`auth_signon`: + +.. code-block:: php + + ` + +Example for IP address limited autologin +++++++++++++++++++++++++++++++++++++++++ + +If you want to automatically login when accessing phpMyAdmin locally while asking +for a password when accessing remotely, you can achieve it using following snippet: + +.. code-block:: php + + if ($_SERVER["REMOTE_ADDR"] == "127.0.0.1") { + $cfg['Servers'][$i]['auth_type'] = 'config'; + $cfg['Servers'][$i]['user'] = 'root'; + $cfg['Servers'][$i]['password'] = 'yourpassword'; + } else { + $cfg['Servers'][$i]['auth_type'] = 'cookie'; + } + +.. note:: + + Filtering based on IP addresses isn't reliable over the internet, use it + only for local address. + +Example for using multiple MySQL servers +++++++++++++++++++++++++++++++++++++++++ + +You can configure any number of servers using :config:option:`$cfg['Servers']`, +following example shows two of them: + +.. code-block:: php + + + +.. _example-google-ssl: + +Google Cloud SQL with SSL ++++++++++++++++++++++++++ + +To connect to Google Could SQL, you currently need to disable certificate +verification. This is caused by the certficate being issued for CN matching +your instance name, but you connect to an IP address and PHP tries to match +these two. With verfication you end up with error message like: + +.. code-block:: text + + Peer certificate CN=`api-project-851612429544:pmatest' did not match expected CN=`8.8.8.8' + +.. warning:: + + With disabled verification your traffic is encrypted, but you're open to + man in the middle attacks. + +To connect phpMyAdmin to Google Cloud SQL using SSL download the client and +server certificates and tell phpMyAdmin to use them: + +.. code-block:: php + + // IP address of your instance + $cfg['Servers'][$i]['host'] = '8.8.8.8'; + // Use SSL for connection + $cfg['Servers'][$i]['ssl'] = true; + // Client secret key + $cfg['Servers'][$i]['ssl_key'] = '../client-key.pem'; + // Client certificate + $cfg['Servers'][$i]['ssl_cert'] = '../client-cert.pem'; + // Server certification authority + $cfg['Servers'][$i]['ssl_ca'] = '../server-ca.pem'; + // Disable SSL verification (see above note) + $cfg['Servers'][$i]['ssl_verify'] = false; + +.. seealso:: + + :ref:`ssl`, + :config:option:`$cfg['Servers'][$i]['ssl']`, + :config:option:`$cfg['Servers'][$i]['ssl_key']`, + :config:option:`$cfg['Servers'][$i]['ssl_cert']`, + :config:option:`$cfg['Servers'][$i]['ssl_ca']`, + :config:option:`$cfg['Servers'][$i]['ssl_verify']`, + diff --git a/php/apps/phpmyadmin49/doc/html/_sources/copyright.txt b/php/apps/phpmyadmin49/doc/html/_sources/copyright.txt new file mode 100644 index 00000000..f5866e8b --- /dev/null +++ b/php/apps/phpmyadmin49/doc/html/_sources/copyright.txt @@ -0,0 +1,42 @@ +.. _copyright: + +Copyright +========= + +.. code-block:: none + + Copyright (C) 1998-2000 Tobias Ratschiller + Copyright (C) 2001-2018 Marc Delisle + Olivier Müller + Robin Johnson + Alexander M. Turek + Michal Čihař + Garvin Hicking + Michael Keck + Sebastian Mendel + [check credits for more details] + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License version 2, as +published by the Free Software Foundation. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . + +Third party licenses +++++++++++++++++++++ + +phpMyAdmin includes several third party libraries which come under their +respective licenses. + +jQuery's license, which is where we got the files under js/vendor/jquery/ is +(MIT|GPL), a copy of each license is available in this repository (GPL +is available as LICENSE, MIT as js/vendor/jquery/MIT-LICENSE.txt). + +The download kit additionally includes several composer libraries. See their +licensing information in the vendor/ directory. diff --git a/php/apps/phpmyadmin49/doc/html/_sources/credits.txt b/php/apps/phpmyadmin49/doc/html/_sources/credits.txt new file mode 100644 index 00000000..4dc828c5 --- /dev/null +++ b/php/apps/phpmyadmin49/doc/html/_sources/credits.txt @@ -0,0 +1,1043 @@ +.. _credits: + +Credits +======= + +Credits, in chronological order ++++++++++++++++++++++++++++++++ + +* Tobias Ratschiller + + * creator of the phpMyAdmin project + + * maintainer from 1998 to summer 2000 + +* Marc Delisle + + * multi-language version in December 1998 + + * various fixes and improvements + + * first version of the :term:`SQL` analyser (most of it) + + * maintainer from 2001 to 2015 + +* Olivier Müller + + * started SourceForge phpMyAdmin project in March 2001 + + * sync'ed different existing CVS trees with new features and bugfixes + + * multi-language improvements, dynamic language selection + + * many bugfixes and improvements + +* Loïc Chapeaux + + * rewrote and optimized JavaScript, DHTML and DOM stuff + + * rewrote the scripts so they fit the :term:`PEAR` coding standards and + generate XHTML1.0 and CSS2 compliant codes + + * improved the language detection system + + * many bugfixes and improvements + +* Robin Johnson + + * database maintenance controls + + * table type code + + * Host authentication :term:`IP` Allow/Deny + + * DB-based configuration (Not completed) + + * :term:`SQL` parser and pretty-printer + + * :term:`SQL` validator + + * many bugfixes and improvements + +* Armel Fauveau + + * bookmarks feature + + * multiple dump feature + + * gzip dump feature + + * zip dump feature + +* Geert Lund + + * various fixes + + * moderator of the phpMyAdmin former users forum at phpwizard.net + +* Korakot Chaovavanich + + * "insert as new row" feature + +* Pete Kelly + + * rewrote and fix dump code + + * bugfixes + +* Steve Alberty + + * rewrote dump code for PHP4 + + * mySQL table statistics + + * bugfixes + +* Benjamin Gandon + + * main author of the version 2.1.0.1 + + * bugfixes + +* Alexander M. Turek + + * MySQL 4.0 / 4.1 / 5.0 compatibility + + * abstract database interface (PMA\_DBI) with MySQLi support + + * privileges administration + + * :term:`XML` exports + + * various features and fixes + + * German language file updates + +* Mike Beck + + * automatic joins in QBE + + * links column in printview + + * Relation view + +* Michal Čihař + + * enhanced index creation/display feature + + * feature to use a different charset for HTML than for MySQL + + * improvements of export feature + + * various features and fixes + + * Czech language file updates + + * created current website for phpMyAdmin + +* Christophe Gesché from the "MySQL Form Generator for PHPMyAdmin" + (https://sourceforge.net/projects/phpmysqlformgen/) + + * suggested the patch for multiple table printviews + +* Garvin Hicking + + * built the patch for vertical display of table rows + + * built the Javascript based Query window + :term:`SQL` history + + * Improvement of column/db comments + + * (MIME)-Transformations for columns + + * Use custom alias names for Databases in left frame + + * hierarchical/nested table display + + * :term:`PDF`-scratchboard for WYSIWYG- + distribution of :term:`PDF` relations + + * new icon sets + + * vertical display of column properties page + + * some bugfixes, features, support, German language additions + +* Yukihiro Kawada + + * japanese kanji encoding conversion feature + +* Piotr Roszatycki and Dan Wilson + + * the Cookie authentication mode + +* Axel Sander + + * table relation-links feature + +* Maxime Delorme + + * :term:`PDF` schema output, thanks also to + Olivier Plathey for the "FPDF" library (see ), Steven + Wittens for the "UFPDF" library and + Nicola Asuni for the "TCPDF" library (see ). + +* Olof Edlund + + * :term:`SQL` validator server + +* Ivan R. Lanin + + * phpMyAdmin logo (until June 2004) + +* Mike Cochrane + + * blowfish library from the Horde project (withdrawn in release 4.0) + +* Marcel Tschopp + + * mysqli support + + * many bugfixes and improvements + +* Nicola Asuni (Tecnick.com) + + * TCPDF library () + +* Michael Keck + + * redesign for 2.6.0 + + * phpMyAdmin sailboat logo (June 2004) + +* Mathias Landhäußer + + * Representation at conferences + +* Sebastian Mendel + + * interface improvements + + * various bugfixes + +* Ivan A Kirillov + + * new relations Designer + +* Raj Kissu Rajandran (Google Summer of Code 2008) + + * BLOBstreaming support (withdrawn in release 4.0) + +* Piotr Przybylski (Google Summer of Code 2008, 2010 and 2011) + + * improved setup script + + * user preferences + + * Drizzle support + +* Derek Schaefer (Google Summer of Code 2009) + + * Improved the import system + +* Alexander Rutkowski (Google Summer of Code 2009) + + * Tracking mechanism + +* Zahra Naeem (Google Summer of Code 2009) + + * Synchronization feature (removed in release 4.0) + +* Tomáš Srnka (Google Summer of Code 2009) + + * Replication support + +* Muhammad Adnan (Google Summer of Code 2010) + + * Relation schema export to multiple formats + +* Lori Lee (Google Summer of Code 2010) + + * User interface improvements + + * ENUM/SET editor + + * Simplified interface for export/import + +* Ninad Pundalik (Google Summer of Code 2010) + + * AJAXifying the interface + +* Martynas Mickevičius (Google Summer of Code 2010) + + * Charts + +* Barrie Leslie + + * BLOBstreaming support with PBMS PHP extension (withdrawn in release + 4.0) + +* Ankit Gupta (Google Summer of Code 2010) + + * Visual query builder + +* Madhura Jayaratne (Google Summer of Code 2011) + + * OpenGIS support + +* Ammar Yasir (Google Summer of Code 2011) + + * Zoom search + +* Aris Feryanto (Google Summer of Code 2011) + + * Browse-mode improvements + +* Thilanka Kaushalya (Google Summer of Code 2011) + + * AJAXification + +* Tyron Madlener (Google Summer of Code 2011) + + * Query statistics and charts for the status page + +* Zarubin Stas (Google Summer of Code 2011) + + * Automated testing + +* Rouslan Placella (Google Summer of Code 2011 and 2012) + + * Improved support for Stored Routines, Triggers and Events + + * Italian translation updates + + * Removal of frames, new navigation + +* Dieter Adriaenssens + + * Various bugfixes + + * Dutch translation updates + +* Alex Marin (Google Summer of Code 2012) + + * New plugins and properties system + +* Thilina Buddika Abeyrathna (Google Summer of Code 2012) + + * Refactoring + +* Atul Pratap Singh (Google Summer of Code 2012) + + * Refactoring + +* Chanaka Indrajith (Google Summer of Code 2012) + + * Refactoring + +* Yasitha Pandithawatta (Google Summer of Code 2012) + + * Automated testing + +* Jim Wigginton (phpseclib.sourceforge.net) + + * phpseclib + +* Bin Zu (Google Summer of Code 2013) + + * Refactoring + +* Supun Nakandala (Google Summer of Code 2013) + + * Refactoring + +* Mohamed Ashraf (Google Summer of Code 2013) + + * AJAX error reporting + +* Adam Kang (Google Summer of Code 2013) + + * Automated testing + +* Ayush Chaudhary (Google Summer of Code 2013) + + * Automated testing + +* Kasun Chathuranga (Google Summer of Code 2013) + + * Interface improvements + +* Hugues Peccatte + + * Load/save query by example (database search bookmarks) + +* Smita Kumari (Google Summer of Code 2014) + + * Central list of columns + + * Improve table structure (normalization) + +* Ashutosh Dhundhara (Google Summer of Code 2014) + + * Interface improvements + +* Dhananjay Nakrani (Google Summer of Code 2014) + + * PHP error reporting + +* Edward Cheng (Google Summer of Code 2014) + + * SQL Query Console + +* Kankanamge Bimal Yashodha (Google Summer of Code 2014) + + * Refactoring: Designer/schema integration + +* Chirayu Chiripal (Google Summer of Code 2014) + + * Custom field handlers (Input based MIME transformations) + + * Export with table/column name changes + +* Dan Ungureanu (Google Summer of Code 2015) + + * New parser and analyzer + +* Nisarg Jhaveri (Google Summer of Code 2015) + + * Page-related settings + + * SQL debugging integration to the Console + + * Other UI improvements + +* Deven Bansod (Google Summer of Code 2015) + + * Print view using CSS + + * Other UI improvements and new features + +* Deven Bansod (Google Summer of Code 2017) + + * Improvements to the Error Reporting Server + + * Improved Selenium testing + +* Manish Bisht (Google Summer of Code 2017) + + * Mobile user interface + + * Remove inline JavaScript code + + * Other UI improvements + +* Raghuram Vadapalli (Google Summer of Code 2017) + + * Multi-table query interface + + * Allow Designer to work with tables from other databases + + * Other UI improvements + +And also to the following people who have contributed minor changes, +enhancements, bugfixes or support for a new language since version +2.1.0: + +Bora Alioglu, Ricardo ?, Sven-Erik Andersen, Alessandro Astarita, +Péter Bakondy, Borges Botelho, Olivier Bussier, Neil Darlow, Mats +Engstrom, Ian Davidson, Laurent Dhima, Kristof Hamann, Thomas Kläger, +Lubos Klokner, Martin Marconcini, Girish Nair, David Nordenberg, +Andreas Pauley, Bernard M. Piller, Laurent Haas, "Sakamoto", Yuval +Sarna, www.securereality.com.au, Alexis Soulard, Alvar Soome, Siu Sun, +Peter Svec, Michael Tacelosky, Rachim Tamsjadi, Kositer Uros, Luís V., +Martijn W. van der Lee, Algis Vainauskas, Daniel Villanueva, Vinay, +Ignacio Vazquez-Abrams, Chee Wai, Jakub Wilk, Thomas Michael +Winningham, Vilius Zigmantas, "Manuzhai". + +Translators ++++++++++++ + +Following people have contributed to translation of phpMyAdmin: + +* Albanian + + * Arben Çokaj + +* Arabic + + * Ahmed Saleh Abd El-Raouf Ismae + * Ahmed Saad + * hassan mokhtari + +* Armenian + + * Andrey Aleksanyants + +* Azerbaijani + + * Mircəlal <01youknowme\_at\_gmail.com> + * Huseyn + * Sevdimali İsa + * Jafar + +* Belarusian + + * Viktar Palstsiuk + +* Bulgarian + + * Boyan Kehayov + * Valter Georgiev + * Valentin Mladenov + * P + * krasimir + +* Catalan + + * josep constanti + * Xavier Navarro + +* Chinese (China) + + * Vincent Lau <3092849\_at\_qq.com> + * Zheng Dan + * disorderman + * Rex Lee + * + * popcorner + * Yizhou Qiang + * zz + * Terry Weng + * whh + +* Chinese (Taiwan) + + * Albert Song + * Chien Wei Lin + * Peter Dave Hello + +* Colognian + + * Purodha + +* Czech + + * Aleš Hakl + * Dalibor Straka + * Martin Vidner + * Ondra Šimeček + * Jan Palider + * Petr Kateřiňák + +* Danish + + * Aputsiaĸ Niels Janussen + * Dennis Jakobsen + * Jonas + * Claus Svalekjaer + +* Dutch + + * A. Voogt + * dingo thirteen + * Robin van der Vliet + * Dieter Adriaenssens + * Niko Strijbol + +* English (United Kingdom) + + * Dries Verschuere + * Francisco Rocha + * Marc Delisle + * Marek Tomaštík + +* Esperanto + + * Eliovir + * Robin van der Vliet + +* Estonian + + * Kristjan Räts + +* Finnish + + * Juha Remes + * Lari Oesch + +* French + + * Marc Delisle + +* Frisian + + * Robin van der Vliet + +* Galician + + * Xosé Calvo + +* German + + * Julian Ladisch + * Jan Erik Zassenhaus + * Lasse Goericke + * Matthias Bluthardt + * Michael Koch + * Ann + J.M. + * + * Phillip Rohmberger + * Hauke Henningsen + +* Greek + + * Παναγιώτης Παπάζογλου + +* Hebrew + + * Moshe Harush + * Yaron Shahrabani + * Eyal Visoker + +* Hindi + + * Atul Pratap Singh + * Yogeshwar + * Deven Bansod + * Kushagra Pandey + * Nisarg Jhaveri + * Roohan Kazi + * Yugal Pantola + +* Hungarian + + * Akos Eros + * Dániel Tóth + * Szász Attila + * Balázs Úr + +* Indonesian + + * Deky Arifianto + * Andika Triwidada + * Dadan Setia + * Dadan Setia + * Yohanes Edwin + * Fadhiil Rachman + * Benny + * Tommy Surbakti + * Zufar Fathi Suhardi + +* Interlingua + + * Giovanni Sora + +* Italian + + * Francesco Saverio Giacobazzi + * Marco Pozzato + * Stefano Martinelli + +* Japanese + + * k725 + * Hiroshi Chiyokawa + * Masahiko HISAKAWA + * worldwideskier + +* Kannada + + * Robin van der Vliet + * Shameem Ahmed A Mulla + +* Korean + + * Bumsoo Kim + * Kyeong Su Shin + * Dongyoung Kim + * Myung-han Yu + * JongDeok + * Yong Kim + * 이경준 + * Seongki Shin + * Yoon Bum-Jong + * Koo Youngmin + +* Kurdish Sorani + + * Alan Hilal + * Aso Naderi + * muhammad + * Zrng Abdulla + +* Latvian + + * Latvian TV + * Edgars Neimanis + * Ukko + +* Limburgish + + * Robin van der Vliet + +* Lithuanian + + * Vytautas Motuzas + +* Malay + + * Amir Hamzah + * diprofinfiniti + +* Nepali + + * Nabin Ghimire + +* Norwegian Bokmål + + * Børge Holm-Wennberg + * Tor Stokkan + * Espen Frøyshov + * Kurt Eilertsen + * Christoffer Haugom + * Sebastian + * Tomas + +* Persian + + * ashkan shirian + * HM + +* Polish + + * Andrzej + * Przemo + * Krystian Biesaga + * Maciej Gryniuk + * Michał VonFlynee + +* Portuguese + + * Alexandre Badalo + * João Rodrigues + * Pedro Ribeiro + * Sandro Amaral + +* Portuguese (Brazil) + + * Alex Rohleder + * bruno mendax + * Danilo GUia + * Douglas Rafael Morais Kollar + * Douglas Eccker + * Ed Jr + * Guilherme Souza Silva + * Guilherme Seibt + * Helder Santana + * Junior Zancan + * Luis + * Marcos Algeri + * Marc Delisle + * Renato Rodrigues de Lima Júnio + * Thiago Casotti + * Victor Laureano + * Vinícius Araújo + * Washington Bruno Rodrigues Cav + * Yan Gabriel + +* Punjabi + + * Robin van der Vliet + +* Romanian + + * Alex + * Costel Cocerhan + * Ion Adrian-Ionut + * Raul Molnar + * Deleted User + * Stefan Murariu + +* Russian + + * Andrey Aleksanyants + * + * Robin van der Vliet + * Хомутов Иван Сергеевич + * Alexey Rubinov + * Олег Карпов + * Egorov Artyom + +* Serbian + + * Smart Kid + +* Sinhala + + * Madhura Jayaratne + +* Slovak + + * Martin Lacina + * Patrik Kollmann + * Jozef Pistej + +* Slovenian + + * Domen + +* Spanish + + * Luis García Sevillano + * Franco + * Luis Ruiz + * Macofe + * Matías Bellone + * Rodrigo A. + * FAMMA TV NOTICIAS MEDIOS DE CO + * Ronnie Simon + +* Swedish + + * Anders Jonsson + +* Tamil + + * கணேஷ் குமார் + * Achchuthan Yogarajah + * Rifthy Ahmed + +* Thai + + * + * Somthanat W. + +* Turkish + + * Burak Yavuz + +* Ukrainian + + * Сергій Педько + * Igor + * Vitaliy Perekupka + +* Vietnamese + + * Bao Phan + * Xuan Hung + * Bao trinh minh + +* West Flemish + + * Robin van der Vliet + +Documentation translators ++++++++++++++++++++++++++ + +Following people have contributed to translation of phpMyAdmin documentation: + +* Albanian + + * Arben Çokaj + +* Arabic + + * Ahmed El Azzabi + * Omar Essam + +* Armenian + + * Andrey Aleksanyants + +* Azerbaijani + + * Mircəlal <01youknowme\_at\_gmail.com> + * Sevdimali İsa + +* Catalan + + * josep constanti + * Joan Montané + * Xavier Navarro + +* Chinese (China) + + * Vincent Lau <3092849\_at\_qq.com> + * 罗攀登 <6375lpd\_at\_gmail.com> + * disorderman + * ITXiaoPang + * tunnel213 + * Terry Weng + * whh + +* Chinese (Taiwan) + + * Chien Wei Lin + * Peter Dave Hello + +* Czech + + * Aleš Hakl + * Michal Čihař + * Jan Palider + * Petr Kateřiňák + +* Danish + + * Aputsiaĸ Niels Janussen + * Claus Svalekjaer + +* Dutch + + * A. Voogt + * dingo thirteen + * Dries Verschuere + * Robin van der Vliet + * Stefan Koolen + * Ray Borggreve + * Dieter Adriaenssens + * Tom Hofman + +* Estonian + + * Kristjan Räts + +* Finnish + + * Juha + +* French + + * Cédric Corazza + * Étienne Gilli + * Marc Delisle + * Donavan_Martin + +* Frisian + + * Robin van der Vliet + +* Galician + + * Xosé Calvo + +* German + + * Daniel + * JH M + * Lasse Goericke + * Michael Koch + * Ann + J.M. + * Niemand Jedermann + * Phillip Rohmberger + * Hauke Henningsen + +* Greek + + * Παναγιώτης Παπάζογλου + +* Hungarian + + * Balázs Úr + +* Italian + + * Francesco Saverio Giacobazzi + * Marco Pozzato + * Stefano Martinelli + * TWS + +* Japanese + + * Eshin Kunishima + * Hiroshi Chiyokawa + +* Lithuanian + + * Jur Kis + * Dovydas + +* Norwegian Bokmål + + * Tor Stokkan + * Kurt Eilertsen + +* Portuguese (Brazil) + + * Alexandre Moretti + * Douglas Rafael Morais Kollar + * Guilherme Seibt + * Helder Santana + * Michal Čihař + * Michel Souza + * Danilo Azevedo + * Thiago Casotti + * Vinícius Araújo + * Yan Gabriel + +* Slovak + + * Martin Lacina + * Michal Čihař + * Jozef Pistej + +* Slovenian + + * Domen + +* Spanish + + * Luis García Sevillano + * Franco + * Matías Bellone + * Ronnie Simon + +* Turkish + + * Burak Yavuz + +Original Credits of Version 2.1.0 ++++++++++++++++++++++++++++++++++ + +This work is based on Peter Kuppelwieser's MySQL-Webadmin. It was his +idea to create a web-based interface to MySQL using PHP3. Although I +have not used any of his source-code, there are some concepts I've +borrowed from him. phpMyAdmin was created because Peter told me he +wasn't going to further develop his (great) tool. + +Thanks go to + +* Amalesh Kempf who contributed the + code for the check when dropping a table or database. He also + suggested that you should be able to specify the primary key on + tbl\_create.php3. To version 1.1.1 he contributed the ldi\_\*.php3-set + (Import text-files) as well as a bug-report. Plus many smaller + improvements. +* Jan Legenhausen : He made many of the changes that + were introduced in 1.3.0 (including quite significant ones like the + authentication). For 1.4.1 he enhanced the table-dump feature. Plus + bug-fixes and help. +* Marc Delisle made phpMyAdmin + language-independent by outsourcing the strings to a separate file. He + also contributed the French translation. +* Alexandr Bravo who contributed + tbl\_select.php3, a feature to display only some columns from a table. +* Chris Jackson added support for MySQL functions + in tbl\_change.php3. He also added the "Query by Example" feature in + 2.0. +* Dave Walton added support for multiple + servers and is a regular contributor for bug-fixes. +* Gabriel Ash contributed the random access + features for 2.0.6. + +The following people have contributed minor changes, enhancements, +bugfixes or support for a new language: + +Jim Kraai, Jordi Bruguera, Miquel Obrador, Geert Lund, Thomas +Kleemann, Alexander Leidinger, Kiko Albiol, Daniel C. Chao, Pavel +Piankov, Sascha Kettler, Joe Pruett, Renato Lins, Mark Kronsbein, +Jannis Hermanns, G. Wieggers. + +And thanks to everyone else who sent me email with suggestions, bug- +reports and or just some feedback. diff --git a/php/apps/phpmyadmin49/doc/html/_sources/developers.txt b/php/apps/phpmyadmin49/doc/html/_sources/developers.txt new file mode 100644 index 00000000..2b2f5868 --- /dev/null +++ b/php/apps/phpmyadmin49/doc/html/_sources/developers.txt @@ -0,0 +1,12 @@ +.. _developers: + +Developers Information +====================== + +phpMyAdmin is Open Source, so you're invited to contribute to it. Many +great features have been written by other people and you too can help +to make phpMyAdmin a useful tool. + +You can check out all the possibilities to contribute in the +`contribute section on our website +`_. diff --git a/php/apps/phpmyadmin49/doc/html/_sources/faq.txt b/php/apps/phpmyadmin49/doc/html/_sources/faq.txt new file mode 100644 index 00000000..47506e13 --- /dev/null +++ b/php/apps/phpmyadmin49/doc/html/_sources/faq.txt @@ -0,0 +1,2258 @@ +.. _faq: + +FAQ - Frequently Asked Questions +================================ + +Please have a look at our `Link section +`_ on the official +phpMyAdmin homepage for in-depth coverage of phpMyAdmin's features and +or interface. + +.. _faqserver: + +Server +++++++ + +.. _faq1_1: + +1.1 My server is crashing each time a specific action is required or phpMyAdmin sends a blank page or a page full of cryptic characters to my browser, what can I do? +--------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +Try to set the :config:option:`$cfg['OBGzip']` directive to ``false`` in your +:file:`config.inc.php` file and the ``zlib.output_compression`` directive to +``Off`` in your php configuration file. + +.. _faq1_2: + +1.2 My Apache server crashes when using phpMyAdmin. +--------------------------------------------------- + +You should first try the latest versions of Apache (and possibly MySQL). If +your server keeps crashing, please ask for help in the various Apache support +groups. + +.. seealso:: :ref:`faq1_1` + +.. _faq1_3: + +1.3 (withdrawn). +---------------- + +.. _faq1_4: + +1.4 Using phpMyAdmin on IIS, I'm displayed the error message: "The specified CGI application misbehaved by not returning a complete set of HTTP headers ...". +------------------------------------------------------------------------------------------------------------------------------------------------------------- + +You just forgot to read the *install.txt* file from the PHP +distribution. Have a look at the last message in this `PHP bug report #12061 +`_ from the official PHP bug +database. + +.. _faq1_5: + +1.5 Using phpMyAdmin on IIS, I'm facing crashes and/or many error messages with the HTTP. +----------------------------------------------------------------------------------------- + +This is a known problem with the PHP :term:`ISAPI` filter: it's not so stable. +Please use instead the cookie authentication mode. + +.. _faq1_6: + +1.6 I can't use phpMyAdmin on PWS: nothing is displayed! +-------------------------------------------------------- + +This seems to be a PWS bug. Filippo Simoncini found a workaround (at +this time there is no better fix): remove or comment the ``DOCTYPE`` +declarations (2 lines) from the scripts :file:`libraries/Header.class.php` +and :file:`index.php`. + +.. _faq1_7: + +1.7 How can I gzip a dump or a CSV export? It does not seem to work. +-------------------------------------------------------------------- + +This feature is based on the ``gzencode()`` +PHP function to be more independent of the platform (Unix/Windows, +Safe Mode or not, and so on). So, you must have Zlib support +(``--with-zlib``). + +.. _faq1_8: + +1.8 I cannot insert a text file in a table, and I get an error about safe mode being in effect. +----------------------------------------------------------------------------------------------- + +Your uploaded file is saved by PHP in the "upload dir", as defined in +:file:`php.ini` by the variable ``upload_tmp_dir`` (usually the system +default is */tmp*). We recommend the following setup for Apache +servers running in safe mode, to enable uploads of files while being +reasonably secure: + +* create a separate directory for uploads: :command:`mkdir /tmp/php` +* give ownership to the Apache server's user.group: :command:`chown + apache.apache /tmp/php` +* give proper permission: :command:`chmod 600 /tmp/php` +* put ``upload_tmp_dir = /tmp/php`` in :file:`php.ini` +* restart Apache + +.. _faq1_9: + +1.9 (withdrawn). +---------------- + +.. _faq1_10: + +1.10 I'm having troubles when uploading files with phpMyAdmin running on a secure server. My browser is Internet Explorer and I'm using the Apache server. +---------------------------------------------------------------------------------------------------------------------------------------------------------- + +As suggested by "Rob M" in the phpWizard forum, add this line to your +*httpd.conf*: + +.. code-block:: apache + + SetEnvIf User-Agent ".*MSIE.*" nokeepalive ssl-unclean-shutdown + +It seems to clear up many problems between Internet Explorer and SSL. + +.. _faq1_11: + +1.11 I get an 'open\_basedir restriction' while uploading a file from the import tab. +------------------------------------------------------------------------------------- + +Since version 2.2.4, phpMyAdmin supports servers with open\_basedir +restrictions. However you need to create temporary directory and configure it +as :config:option:`$cfg['TempDir']`. The uploaded files will be moved there, +and after execution of your :term:`SQL` commands, removed. + +.. _faq1_12: + +1.12 I have lost my MySQL root password, what can I do? +------------------------------------------------------- + +phpMyAdmin does authenticate against MySQL server you're using, so to recover +from phpMyAdmin password loss, you need to recover at MySQL level. + +The MySQL manual explains how to `reset the permissions +`_. + +If you are using MySQL server installed by your hosting provider, please +contact their support to recover the password for you. + +.. _faq1_13: + +1.13 (withdrawn). +----------------- + +.. _faq1_14: + +1.14 (withdrawn). +----------------- + +.. _faq1_15: + +1.15 I have problems with *mysql.user* column names. +---------------------------------------------------- + +In previous MySQL versions, the ``User`` and ``Password`` columns were +named ``user`` and ``password``. Please modify your column names to +align with current standards. + +.. _faq1_16: + +1.16 I cannot upload big dump files (memory, HTTP or timeout problems). +----------------------------------------------------------------------- + +Starting with version 2.7.0, the import engine has been re–written and +these problems should not occur. If possible, upgrade your phpMyAdmin +to the latest version to take advantage of the new import features. + +The first things to check (or ask your host provider to check) are the values +of ``max_execution_time``, ``upload_max_filesize``, ``memory_limit`` and +``post_max_size`` in the :file:`php.ini` configuration file. All of these three +settings limit the maximum size of data that can be submitted and handled by +PHP. Please note that ``post_max_size`` needs to be larger than +``upload_max_filesize``. There exist several workarounds if your upload is too +big or your hosting provider is unwilling to change the settings: + +* Look at the :config:option:`$cfg['UploadDir']` feature. This allows one to upload a file to the server + via scp, ftp, or your favorite file transfer method. PhpMyAdmin is + then able to import the files from the temporary directory. More + information is available in the :ref:`config` of this document. +* Using a utility (such as `BigDump + `_) to split the files before + uploading. We cannot support this or any third party applications, but + are aware of users having success with it. +* If you have shell (command line) access, use MySQL to import the files + directly. You can do this by issuing the "source" command from within + MySQL: + + .. code-block:: mysql + + source filename.sql; + +.. _faq1_17: + +1.17 Which Database versions does phpMyAdmin support? +----------------------------------------------------- + +For `MySQL `_, versions 5.5 and newer are supported. +For older MySQL versions, our `Downloads `_ page offers older phpMyAdmin versions +(which may have become unsupported). + +For `MariaDB `_, versions 5.5 and newer are supported. + +.. _faq1_17a: + +1.17a I cannot connect to the MySQL server. It always returns the error message, "Client does not support authentication protocol requested by server; consider upgrading MySQL client" +--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +You tried to access MySQL with an old MySQL client library. The +version of your MySQL client library can be checked in your phpinfo() +output. In general, it should have at least the same minor version as +your server - as mentioned in :ref:`faq1_17`. This problem is +generally caused by using MySQL version 4.1 or newer. MySQL changed +the authentication hash and your PHP is trying to use the old method. +The proper solution is to use the `mysqli extension +`_ with the proper client library to match +your MySQL installation. More +information (and several workarounds) are located in the `MySQL +Documentation `_. + +.. _faq1_18: + +1.18 (withdrawn). +----------------- + +.. _faq1_19: + +1.19 I can't run the "display relations" feature because the script seems not to know the font face I'm using! +-------------------------------------------------------------------------------------------------------------- + +The :term:`TCPDF` library we're using for this feature requires some special +files to use font faces. Please refers to the `TCPDF manual +`_ to build these files. + +.. _faqmysql: + +1.20 I receive an error about missing mysqli and mysql extensions. +------------------------------------------------------------------ + +To connect to a MySQL server, PHP needs a set of MySQL functions +called "MySQL extension". This extension may be part of the PHP +distribution (compiled-in), otherwise it needs to be loaded +dynamically. Its name is probably *mysqli.so* or *php\_mysqli.dll*. +phpMyAdmin tried to load the extension but failed. Usually, the +problem is solved by installing a software package called "PHP-MySQL" +or something similar. + +There are currently two interfaces PHP provides as MySQL extensions - ``mysql`` +and ``mysqli``. The ``mysqli`` is tried first, because it's the best one. + +This problem can be also caused by wrong paths in the :file:`php.ini` or using +wrong :file:`php.ini`. + +Make sure that the extension files do exist in the folder which the +``extension_dir`` points to and that the corresponding lines in your +:file:`php.ini` are not commented out (you can use ``phpinfo()`` to check +current setup): + +.. code-block:: ini + + [PHP] + + ; Directory in which the loadable extensions (modules) reside. + extension_dir = "C:/Apache2/modules/php/ext" + +The :file:`php.ini` can be loaded from several locations (especially on +Windows), so please check you're updating the correct one. If using Apache, you +can tell it to use specific path for this file using ``PHPIniDir`` directive: + +.. code-block:: apache + + LoadFile "C:/php/php5ts.dll" + LoadModule php5_module "C:/php/php5apache2_2.dll" + + PHPIniDir "C:/PHP" + + AddType text/html .php + AddHandler application/x-httpd-php .php + + + +In some rare cases this problem can be also caused by other extensions loaded +in PHP which prevent MySQL extensions to be loaded. If anything else fails, you +can try commenting out extensions for other databses from :file:`php.ini`. + +.. _faq1_21: + +1.21 I am running the CGI version of PHP under Unix, and I cannot log in using cookie auth. +------------------------------------------------------------------------------------------- + +In :file:`php.ini`, set ``mysql.max_links`` higher than 1. + +.. _faq1_22: + +1.22 I don't see the "Location of text file" field, so I cannot upload. +----------------------------------------------------------------------- + +This is most likely because in :file:`php.ini`, your ``file_uploads`` +parameter is not set to "on". + +.. _faq1_23: + +1.23 I'm running MySQL on a Win32 machine. Each time I create a new table the table and column names are changed to lowercase! +------------------------------------------------------------------------------------------------------------------------------ + +This happens because the MySQL directive ``lower_case_table_names`` +defaults to 1 (``ON``) in the Win32 version of MySQL. You can change +this behavior by simply changing the directive to 0 (``OFF``): Just +edit your ``my.ini`` file that should be located in your Windows +directory and add the following line to the group [mysqld]: + +.. code-block:: ini + + set-variable = lower_case_table_names=0 + +.. note:: + + Forcing this variable to 0 with --lower-case-table-names=0 on a + case-insensitive filesystem and access MyISAM tablenames using different + lettercases, index corruption may result. + +Next, save the file and restart the MySQL service. You can always +check the value of this directive using the query + +.. code-block:: mysql + + SHOW VARIABLES LIKE 'lower_case_table_names'; + +.. seealso:: `Identifier Case Sensitivity in the MySQL Reference Manual `_ + +.. _faq1_24: + +1.24 (withdrawn). +----------------- + +.. _faq1_25: + +1.25 I am running Apache with mod\_gzip-1.3.26.1a on Windows XP, and I get problems, such as undefined variables when I run a SQL query. +---------------------------------------------------------------------------------------------------------------------------------------- + +A tip from Jose Fandos: put a comment on the following two lines in +httpd.conf, like this: + +.. code-block:: apache + + # mod_gzip_item_include file \.php$ + # mod_gzip_item_include mime "application/x-httpd-php.*" + +as this version of mod\_gzip on Apache (Windows) has problems handling +PHP scripts. Of course you have to restart Apache. + +.. _faq1_26: + +1.26 I just installed phpMyAdmin in my document root of IIS but I get the error "No input file specified" when trying to run phpMyAdmin. +---------------------------------------------------------------------------------------------------------------------------------------- + +This is a permission problem. Right-click on the phpmyadmin folder and +choose properties. Under the tab Security, click on "Add" and select +the user "IUSR\_machine" from the list. Now set his permissions and it +should work. + +.. _faq1_27: + +1.27 I get empty page when I want to view huge page (eg. db\_structure.php with plenty of tables). +-------------------------------------------------------------------------------------------------- + +This was caused by a `PHP bug `_ that occur when +GZIP output buffering is enabled. If you turn off it (by +:config:option:`$cfg['OBGzip']` in :file:`config.inc.php`), it should work. +This bug will has been fixed in PHP 5.0.0. + +.. _faq1_28: + +1.28 My MySQL server sometimes refuses queries and returns the message 'Errorcode: 13'. What does this mean? +------------------------------------------------------------------------------------------------------------ + +This can happen due to a MySQL bug when having database / table names +with upper case characters although ``lower_case_table_names`` is +set to 1. To fix this, turn off this directive, convert all database +and table names to lower case and turn it on again. Alternatively, +there's a bug-fix available starting with MySQL 3.23.56 / +4.0.11-gamma. + +.. _faq1_29: + +1.29 When I create a table or modify a column, I get an error and the columns are duplicated. +--------------------------------------------------------------------------------------------- + +It is possible to configure Apache in such a way that PHP has problems +interpreting .php files. + +The problems occur when two different (and conflicting) set of +directives are used: + +.. code-block:: apache + + SetOutputFilter PHP + SetInputFilter PHP + +and + +.. code-block:: apache + + AddType application/x-httpd-php .php + +In the case we saw, one set of directives was in +``/etc/httpd/conf/httpd.conf``, while the other set was in +``/etc/httpd/conf/addon-modules/php.conf``. The recommended way is +with ``AddType``, so just comment out the first set of lines and +restart Apache: + +.. code-block:: apache + + #SetOutputFilter PHP + #SetInputFilter PHP + +.. _faq1_30: + +1.30 I get the error "navigation.php: Missing hash". +---------------------------------------------------- + +This problem is known to happen when the server is running Turck +MMCache but upgrading MMCache to version 2.3.21 solves the problem. + +.. _faq1_31: + +1.31 Which PHP versions does phpMyAdmin support? +------------------------------------------------ + +Since release 4.5, phpMyAdmin supports only PHP 5.5 and newer. Since release +4.1 phpMyAdmin supports only PHP 5.3 and newer. For PHP 5.2 you can use 4.0.x +releases. + +PHP 7 is supported since phpMyAdmin 4.6, PHP 7.1 is supported since 4.6.5, +PHP 7.2 is supported since 4.7.4. + +phpMyAdmin also works fine with HHVM. + +.. _faq1_32: + +1.32 Can I use HTTP authentication with IIS? +-------------------------------------------- + +Yes. This procedure was tested with phpMyAdmin 2.6.1, PHP 4.3.9 in +:term:`ISAPI` mode under :term:`IIS` 5.1. + +#. In your :file:`php.ini` file, set ``cgi.rfc2616_headers = 0`` +#. In ``Web Site Properties -> File/Directory Security -> Anonymous + Access`` dialog box, check the ``Anonymous access`` checkbox and + uncheck any other checkboxes (i.e. uncheck ``Basic authentication``, + ``Integrated Windows authentication``, and ``Digest`` if it's + enabled.) Click ``OK``. +#. In ``Custom Errors``, select the range of ``401;1`` through ``401;5`` + and click the ``Set to Default`` button. + +.. seealso:: :rfc:`2616` + +.. _faq1_33: + +1.33 (withdrawn). +----------------- + +.. _faq1_34: + +1.34 Can I access directly to database or table pages? +------------------------------------------------------ + +Yes. Out of the box, you can use :term:`URL` like +``http://server/phpMyAdmin/index.php?server=X&db=database&table=table&target=script``. +For ``server`` you use the server number +which refers to the order of the server paragraph in +:file:`config.inc.php`. Table and script parts are optional. If you want +``http://server/phpMyAdmin/database[/table][/script]`` :term:`URL`, you need to do some configuration. Following +lines apply only for `Apache `_ web server. +First make sure, that you have enabled some features within global +configuration. You need ``Options SymLinksIfOwnerMatch`` and ``AllowOverride +FileInfo`` enabled for directory where phpMyAdmin is installed and you +need mod\_rewrite to be enabled. Then you just need to create +following :term:`.htaccess` file in root folder of phpMyAdmin installation (don't +forget to change directory name inside of it): + +.. code-block:: apache + + RewriteEngine On + RewriteBase /path_to_phpMyAdmin + RewriteRule ^([a-zA-Z0-9_]+)/([a-zA-Z0-9_]+)/([a-z_]+\.php)$ index.php?db=$1&table=$2&target=$3 [R] + RewriteRule ^([a-zA-Z0-9_]+)/([a-z_]+\.php)$ index.php?db=$1&target=$2 [R] + RewriteRule ^([a-zA-Z0-9_]+)/([a-zA-Z0-9_]+)$ index.php?db=$1&table=$2 [R] + RewriteRule ^([a-zA-Z0-9_]+)$ index.php?db=$1 [R] + +.. _faq1_35: + +1.35 Can I use HTTP authentication with Apache CGI? +--------------------------------------------------- + +Yes. However you need to pass authentication variable to :term:`CGI` using +following rewrite rule: + +.. code-block:: apache + + RewriteEngine On + RewriteRule .* - [E=REMOTE_USER:%{HTTP:Authorization},L] + +.. _faq1_36: + +1.36 I get an error "500 Internal Server Error". +------------------------------------------------ + +There can be many explanations to this and a look at your server's +error log file might give a clue. + +.. _faq1_37: + +1.37 I run phpMyAdmin on cluster of different machines and password encryption in cookie auth doesn't work. +----------------------------------------------------------------------------------------------------------- + +If your cluster consist of different architectures, PHP code used for +encryption/decryption won't work correct. This is caused by use of +pack/unpack functions in code. Only solution is to use mcrypt +extension which works fine in this case. + +.. _faq1_38: + +1.38 Can I use phpMyAdmin on a server on which Suhosin is enabled? +------------------------------------------------------------------ + +Yes but the default configuration values of Suhosin are known to cause +problems with some operations, for example editing a table with many +columns and no :term:`primary key` or with textual :term:`primary key`. + +Suhosin configuration might lead to malfunction in some cases and it +can not be fully avoided as phpMyAdmin is kind of application which +needs to transfer big amounts of columns in single HTTP request, what +is something what Suhosin tries to prevent. Generally all +``suhosin.request.*``, ``suhosin.post.*`` and ``suhosin.get.*`` +directives can have negative effect on phpMyAdmin usability. You can +always find in your error logs which limit did cause dropping of +variable, so you can diagnose the problem and adjust matching +configuration variable. + +The default values for most Suhosin configuration options will work in +most scenarios, however you might want to adjust at least following +parameters: + +* `suhosin.request.max\_vars `_ should + be increased (eg. 2048) +* `suhosin.post.max\_vars `_ should be + increased (eg. 2048) +* `suhosin.request.max\_array\_index\_length `_ + should be increased (eg. 256) +* `suhosin.post.max\_array\_index\_length `_ + should be increased (eg. 256) +* `suhosin.request.max\_totalname\_length `_ + should be increased (eg. 8192) +* `suhosin.post.max\_totalname\_length `_ should be + increased (eg. 8192) +* `suhosin.get.max\_value\_length `_ + should be increased (eg. 1024) +* `suhosin.sql.bailout\_on\_error `_ + needs to be disabled (the default) +* `suhosin.log.\* `_ should not + include :term:`SQL`, otherwise you get big + slowdown +* `suhosin.sql.union `_ must be disabled (which is the default). +* `suhosin.sql.multiselect `_ must be disabled (which is the default). +* `suhosin.sql.comment `_ must be disabled (which is the default). + +To further improve security, we also recommend these modifications: + +* `suhosin.executor.include.max\_traversal `_ should be + enabled as a mitigation against local file inclusion attacks. We suggest + setting this to 2 as ``../`` is used with the ReCaptcha library. +* `suhosin.cookie.encrypt `_ should be enabled. +* `suhosin.executor.disable_emodifier `_ should be enabled. + +You can also disable the warning using the :config:option:`$cfg['SuhosinDisableWarning']`. + +.. _faq1_39: + +1.39 When I try to connect via https, I can log in, but then my connection is redirected back to http. What can cause this behavior? +------------------------------------------------------------------------------------------------------------------------------------ + +This is caused by the fact that PHP scripts have no knowledge that the site is +using https. Depending on used webserver, you should configure it to let PHP +know about URL and scheme used to access it. + +For example in Apache ensure that you have enabled ``SSLOptions`` and +``StdEnvVars`` in the configuration. + +.. seealso:: + +.. _faq1_40: + +1.40 When accessing phpMyAdmin via an Apache reverse proxy, cookie login does not work. +--------------------------------------------------------------------------------------- + +To be able to use cookie auth Apache must know that it has to rewrite +the set-cookie headers. Example from the Apache 2.2 documentation: + +.. code-block:: apache + + ProxyPass /mirror/foo/ http://backend.example.com/ + ProxyPassReverse /mirror/foo/ http://backend.example.com/ + ProxyPassReverseCookieDomain backend.example.com public.example.com + ProxyPassReverseCookiePath / /mirror/foo/ + +Note: if the backend url looks like ``http://server/~user/phpmyadmin``, the +tilde (~) must be url encoded as %7E in the ProxyPassReverse\* lines. +This is not specific to phpmyadmin, it's just the behavior of Apache. + +.. code-block:: apache + + ProxyPass /mirror/foo/ http://backend.example.com/~user/phpmyadmin + ProxyPassReverse /mirror/foo/ http://backend.example.com/%7Euser/phpmyadmin + ProxyPassReverseCookiePath /%7Euser/phpmyadmin /mirror/foo + +.. seealso:: , :config:option:`$cfg['PmaAbsoluteUri']` + +.. _faq1_41: + +1.41 When I view a database and ask to see its privileges, I get an error about an unknown column. +-------------------------------------------------------------------------------------------------- + +The MySQL server's privilege tables are not up to date, you need to +run the :command:`mysql_upgrade` command on the server. + +.. _faq1_42: + +1.42 How can I prevent robots from accessing phpMyAdmin? +-------------------------------------------------------- + +You can add various rules to :term:`.htaccess` to filter access based on user agent +field. This is quite easy to circumvent, but could prevent at least +some robots accessing your installation. + +.. code-block:: apache + + RewriteEngine on + + # Allow only GET and POST verbs + RewriteCond %{REQUEST_METHOD} !^(GET|POST)$ [NC,OR] + + # Ban Typical Vulnerability Scanners and others + # Kick out Script Kiddies + RewriteCond %{HTTP_USER_AGENT} ^(java|curl|wget).* [NC,OR] + RewriteCond %{HTTP_USER_AGENT} ^.*(libwww-perl|curl|wget|python|nikto|wkito|pikto|scan|acunetix).* [NC,OR] + RewriteCond %{HTTP_USER_AGENT} ^.*(winhttp|HTTrack|clshttp|archiver|loader|email|harvest|extract|grab|miner).* [NC,OR] + + # Ban Search Engines, Crawlers to your administrative panel + # No reasons to access from bots + # Ultimately Better than the useless robots.txt + # Did google respect robots.txt? + # Try google: intitle:phpMyAdmin intext:"Welcome to phpMyAdmin *.*.*" intext:"Log in" -wiki -forum -forums -questions intext:"Cookies must be enabled" + RewriteCond %{HTTP_USER_AGENT} ^.*(AdsBot-Google|ia_archiver|Scooter|Ask.Jeeves|Baiduspider|Exabot|FAST.Enterprise.Crawler|FAST-WebCrawler|www\.neomo\.de|Gigabot|Mediapartners-Google|Google.Desktop|Feedfetcher-Google|Googlebot|heise-IT-Markt-Crawler|heritrix|ibm.com\cs/crawler|ICCrawler|ichiro|MJ12bot|MetagerBot|msnbot-NewsBlogs|msnbot|msnbot-media|NG-Search|lucene.apache.org|NutchCVS|OmniExplorer_Bot|online.link.validator|psbot0|Seekbot|Sensis.Web.Crawler|SEO.search.Crawler|Seoma.\[SEO.Crawler\]|SEOsearch|Snappy|www.urltrends.com|www.tkl.iis.u-tokyo.ac.jp/~crawler|SynooBot|crawleradmin.t-info@telekom.de|TurnitinBot|voyager|W3.SiteSearch.Crawler|W3C-checklink|W3C_Validator|www.WISEnutbot.com|yacybot|Yahoo-MMCrawler|Yahoo\!.DE.Slurp|Yahoo\!.Slurp|YahooSeeker).* [NC] + RewriteRule .* - [F] + +.. _faq1_43: + +1.43 Why can't I display the structure of my table containing hundreds of columns? +---------------------------------------------------------------------------------- + +Because your PHP's ``memory_limit`` is too low; adjust it in :file:`php.ini`. + +.. _faq1:44: + +1.44 How can I reduce the installed size of phpMyAdmin on disk? +--------------------------------------------------------------- + +Some users have requested to be able to reduce the size of the phpMyAdmin installation. +This is not recommended and could lead to confusion over missing features, but can be done. +A list of files and corresponding functionality which degrade gracefully when removed include: + +* :file:`./vendor/tecnickcom/tcpdf` folder (exporting to PDF) +* :file:`./locale/` folder, or unused subfolders (interface translations) +* Any unused themes in :file:`./themes/` +* :file:`./js/vendor/jquery/src/` (included for licensing reasons) +* :file:`./js/line_counts.php` (removed in phpMyAdmin 4.8) +* :file:`./doc/` (documentation) +* :file:`./setup/` (setup script) +* :file:`./examples/` +* :file:`./sql/` (SQL scripts to configure advanced functionality) +* :file:`./js/vendor/openlayers/` (GIS visualization) + +.. _faqconfig: + +Configuration ++++++++++++++ + +.. _faq2_1: + +2.1 The error message "Warning: Cannot add header information - headers already sent by ..." is displayed, what's the problem? +------------------------------------------------------------------------------------------------------------------------------ + +Edit your :file:`config.inc.php` file and ensure there is nothing (I.E. no +blank lines, no spaces, no characters...) neither before the ```` tag at the end. + +.. _faq2_2: + +2.2 phpMyAdmin can't connect to MySQL. What's wrong? +---------------------------------------------------- + +Either there is an error with your PHP setup or your username/password +is wrong. Try to make a small script which uses mysql\_connect and see +if it works. If it doesn't, it may be you haven't even compiled MySQL +support into PHP. + +.. _faq2_3: + +2.3 The error message "Warning: MySQL Connection Failed: Can't connect to local MySQL server through socket '/tmp/mysql.sock' (111) ..." is displayed. What can I do? +--------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +The error message can also be: :guilabel:`Error #2002 - The server is not +responding (or the local MySQL server's socket is not correctly configured)`. + +First, you need to determine what socket is being used by MySQL. To do this, +connect to your server and go to the MySQL bin directory. In this directory +there should be a file named *mysqladmin*. Type ``./mysqladmin variables``, and +this should give you a bunch of info about your MySQL server, including the +socket (*/tmp/mysql.sock*, for example). You can also ask your ISP for the +connection info or, if you're hosting your own, connect from the 'mysql' +command-line client and type 'status' to get the connection type and socket or +port number. + +Then, you need to tell PHP to use this socket. You can do this for all PHP in +the :file:`php.ini` or for phpMyAdmin only in the :file:`config.inc.php`. For +example: :config:option:`$cfg['Servers'][$i]['socket']` Please also make sure +that the permissions of this file allow to be readable by your webserver. + +On my RedHat-Box the socket of MySQL is */var/lib/mysql/mysql.sock*. +In your :file:`php.ini` you will find a line + +.. code-block:: ini + + mysql.default_socket = /tmp/mysql.sock + +change it to + +.. code-block:: ini + + mysql.default_socket = /var/lib/mysql/mysql.sock + +Then restart apache and it will work. + +Have also a look at the `corresponding section of the MySQL +documentation `_. + +.. _faq2_4: + +2.4 Nothing is displayed by my browser when I try to run phpMyAdmin, what can I do? +----------------------------------------------------------------------------------- + +Try to set the :config:option:`$cfg['OBGzip']` directive to ``false`` in the phpMyAdmin configuration +file. It helps sometime. Also have a look at your PHP version number: +if it contains "b" or "alpha" it means you're running a testing +version of PHP. That's not a so good idea, please upgrade to a plain +revision. + +.. _faq2_5: + +2.5 Each time I want to insert or change a row or drop a database or a table, an error 404 (page not found) is displayed or, with HTTP or cookie authentication, I'm asked to log in again. What's wrong? +--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +Check your webserver setup if it correctly fills in either PHP_SELF or REQUEST_URI variables. + +If you are running phpMyAdmin behind reverse proxy, please set the +:config:option:`$cfg['PmaAbsoluteUri']` directive in the phpMyAdmin +configuration file to match your setup. + +.. _faq2_6: + +2.6 I get an "Access denied for user: 'root@localhost' (Using password: YES)"-error when trying to access a MySQL-Server on a host which is port-forwarded for my localhost. +---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +When you are using a port on your localhost, which you redirect via +port-forwarding to another host, MySQL is not resolving the localhost +as expected. Erik Wasser explains: The solution is: if your host is +"localhost" MySQL (the command line tool :command:`mysql` as well) always +tries to use the socket connection for speeding up things. And that +doesn't work in this configuration with port forwarding. If you enter +"127.0.0.1" as hostname, everything is right and MySQL uses the +:term:`TCP` connection. + +.. _faqthemes: + +2.7 Using and creating themes +----------------------------- + +See :ref:`themes`. + +.. _faqmissingparameters: + +2.8 I get "Missing parameters" errors, what can I do? +----------------------------------------------------- + +Here are a few points to check: + +* In :file:`config.inc.php`, try to leave the :config:option:`$cfg['PmaAbsoluteUri']` directive empty. See also + :ref:`faq4_7`. +* Maybe you have a broken PHP installation or you need to upgrade your + Zend Optimizer. See . +* If you are using Hardened PHP with the ini directive + ``varfilter.max_request_variables`` set to the default (200) or + another low value, you could get this error if your table has a high + number of columns. Adjust this setting accordingly. (Thanks to Klaus + Dorninger for the hint). +* In the :file:`php.ini` directive ``arg_separator.input``, a value of ";" + will cause this error. Replace it with "&;". +* If you are using `Suhosin `_, you + might want to increase `request limits `_. +* The directory specified in the :file:`php.ini` directive + ``session.save_path`` does not exist or is read-only (this can be caused + by `bug in the PHP installer `_). + +.. _faq2_9: + +2.9 Seeing an upload progress bar +--------------------------------- + +To be able to see a progress bar during your uploads, your server must +have the `APC `_ extension, the +`uploadprogress `_ one, or +you must be running PHP 5.4.0 or higher. Moreover, the JSON extension +has to be enabled in your PHP. + +If using APC, you must set ``apc.rfc1867`` to ``on`` in your :file:`php.ini`. + +If using PHP 5.4.0 or higher, you must set +``session.upload_progress.enabled`` to ``1`` in your :file:`php.ini`. However, +starting from phpMyAdmin version 4.0.4, session-based upload progress has +been temporarily deactivated due to its problematic behavior. + +.. seealso:: :rfc:`1867` + +.. _faqlimitations: + +Known limitations ++++++++++++++++++ + +.. _login_bug: + +3.1 When using HTTP authentication, a user who logged out can not log in again in with the same nick. +----------------------------------------------------------------------------------------------------- + +This is related to the authentication mechanism (protocol) used by +phpMyAdmin. To bypass this problem: just close all the opened browser +windows and then go back to phpMyAdmin. You should be able to log in +again. + +.. _faq3_2: + +3.2 When dumping a large table in compressed mode, I get a memory limit error or a time limit error. +---------------------------------------------------------------------------------------------------- + +Compressed dumps are built in memory and because of this are limited +to php's memory limit. For gzip/bzip2 exports this can be overcome +since 2.5.4 using :config:option:`$cfg['CompressOnFly']` (enabled by default). +zip exports can not be handled this way, so if you need zip files for larger +dump, you have to use another way. + +.. _faq3_3: + +3.3 With InnoDB tables, I lose foreign key relationships when I rename a table or a column. +------------------------------------------------------------------------------------------- + +This is an InnoDB bug, see . + +.. _faq3_4: + +3.4 I am unable to import dumps I created with the mysqldump tool bundled with the MySQL server distribution. +------------------------------------------------------------------------------------------------------------- + +The problem is that older versions of ``mysqldump`` created invalid +comments like this: + +.. code-block:: mysql + + -- MySQL dump 8.22 + -- + -- Host: localhost Database: database + --------------------------------------------------------- + -- Server version 3.23.54 + +The invalid part of the code is the horizontal line made of dashes +that appears once in every dump created with mysqldump. If you want to +run your dump you have to turn it into valid MySQL. This means, you +have to add a whitespace after the first two dashes of the line or add +a # before it: ``-- -------------------------------------------------------`` or +``#---------------------------------------------------------`` + +.. _faq3_5: + +3.5 When using nested folders, multiple hierarchies are displayed in a wrong manner. +------------------------------------------------------------------------------------ + +Please note that you should not use the separating string multiple +times without any characters between them, or at the beginning/end of +your table name. If you have to, think about using another +TableSeparator or disabling that feature. + +.. seealso:: :config:option:`$cfg['NavigationTreeTableSeparator']` + +.. _faq3_6: + +3.6 (withdrawn). +----------------- + +.. _faq3_7: + +3.7 I have table with many (100+) columns and when I try to browse table I get series of errors like "Warning: unable to parse url". How can this be fixed? +----------------------------------------------------------------------------------------------------------------------------------------------------------- + +Your table neither have a :term:`primary key` nor an :term:`unique key`, so we must +use a long expression to identify this row. This causes problems to +parse\_url function. The workaround is to create a :term:`primary key` +or :term:`unique key`. + +.. _faq3_8: + +3.8 I cannot use (clickable) HTML-forms in columns where I put a MIME-Transformation onto! +------------------------------------------------------------------------------------------ + +Due to a surrounding form-container (for multi-row delete checkboxes), +no nested forms can be put inside the table where phpMyAdmin displays +the results. You can, however, use any form inside of a table if keep +the parent form-container with the target to tbl\_row\_delete.php and +just put your own input-elements inside. If you use a custom submit +input field, the form will submit itself to the displaying page again, +where you can validate the $HTTP\_POST\_VARS in a transformation. For +a tutorial on how to effectively use transformations, see our `Link +section `_ on the +official phpMyAdmin-homepage. + +.. _faq3_9: + +3.9 I get error messages when using "--sql\_mode=ANSI" for the MySQL server. +---------------------------------------------------------------------------- + +When MySQL is running in ANSI-compatibility mode, there are some major +differences in how :term:`SQL` is structured (see +). Most important of all, the +quote-character (") is interpreted as an identifier quote character and not as +a string quote character, which makes many internal phpMyAdmin operations into +invalid :term:`SQL` statements. There is no +workaround to this behaviour. News to this item will be posted in `issue +#7383 `_. + +.. _faq3_10: + +3.10 Homonyms and no primary key: When the results of a SELECT display more that one column with the same value (for example ``SELECT lastname from employees where firstname like 'A%'`` and two "Smith" values are displayed), if I click Edit I cannot be sure that I am editing the intended row. +----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +Please make sure that your table has a :term:`primary key`, so that phpMyAdmin +can use it for the Edit and Delete links. + +.. _faq3_11: + +3.11 The number of rows for InnoDB tables is not correct. +--------------------------------------------------------- + +phpMyAdmin uses a quick method to get the row count, and this method only +returns an approximate count in the case of InnoDB tables. See +:config:option:`$cfg['MaxExactCount']` for a way to modify those results, but +this could have a serious impact on performance. +However, one can easily replace the approximate row count with exact count by +simply clicking on the approximate count. This can also be done for all tables +at once by clicking on the rows sum displayed at the bottom. + +.. seealso:: :config:option:`$cfg['MaxExactCount']` + +.. _faq3_12: + +3.12 (withdrawn). +----------------- + +.. _faq3_13: + +3.13 I get an error when entering ``USE`` followed by a db name containing an hyphen. +------------------------------------------------------------------------------------- + +The tests I have made with MySQL 5.1.49 shows that the API does not +accept this syntax for the USE command. + +.. _faq3_14: + +3.14 I am not able to browse a table when I don't have the right to SELECT one of the columns. +---------------------------------------------------------------------------------------------- + +This has been a known limitation of phpMyAdmin since the beginning and +it's not likely to be solved in the future. + +.. _faq3_15: + +3.15 (withdrawn). +----------------- + +.. _faq3_16: + +3.16 (withdrawn). +----------------- + +.. _faq3_17: + +3.17 (withdrawn). +----------------- + +.. _faq3_18: + +3.18 When I import a CSV file that contains multiple tables, they are lumped together into a single table. +---------------------------------------------------------------------------------------------------------- + +There is no reliable way to differentiate tables in :term:`CSV` format. For the +time being, you will have to break apart :term:`CSV` files containing multiple +tables. + +.. _faq3_19: + +3.19 When I import a file and have phpMyAdmin determine the appropriate data structure it only uses int, decimal, and varchar types. +------------------------------------------------------------------------------------------------------------------------------------ + +Currently, the import type-detection system can only assign these +MySQL types to columns. In future, more will likely be added but for +the time being you will have to edit the structure to your liking +post-import. Also, you should note the fact that phpMyAdmin will use +the size of the largest item in any given column as the column size +for the appropriate type. If you know you will be adding larger items +to that column then you should manually adjust the column sizes +accordingly. This is done for the sake of efficiency. + +.. _faq3_20: + +3.20 After upgrading, some bookmarks are gone or their content cannot be shown. +------------------------------------------------------------------------------- + +At some point, the character set used to store bookmark content has changed. +It's better to recreate your bookmark from the newer phpMyAdmin version. + +.. _faq3_21: + +3.21 I am unable to log in with a username containing unicode characters such as á. +----------------------------------------------------------------------------------- + +This can happen if MySQL server is not configured to use utf-8 as default +charset. This is a limitation of how PHP and the MySQL server interact; there +is no way for PHP to set the charset before authenticating. + +.. seealso:: + + `phpMyAdmin issue 12232 `_, + `MySQL documentation note `_ + +.. _faqmultiuser: + +ISPs, multi-user installations +++++++++++++++++++++++++++++++ + +.. _faq4_1: + +4.1 I'm an ISP. Can I setup one central copy of phpMyAdmin or do I need to install it for each customer? +-------------------------------------------------------------------------------------------------------- + +Since version 2.0.3, you can setup a central copy of phpMyAdmin for all your +users. The development of this feature was kindly sponsored by NetCologne GmbH. +This requires a properly setup MySQL user management and phpMyAdmin +:term:`HTTP` or cookie authentication. + +.. seealso:: :ref:`authentication_modes` + +.. _faq4_2: + +4.2 What's the preferred way of making phpMyAdmin secure against evil access? +----------------------------------------------------------------------------- + +This depends on your system. If you're running a server which cannot be +accessed by other people, it's sufficient to use the directory protection +bundled with your webserver (with Apache you can use :term:`.htaccess` files, +for example). If other people have telnet access to your server, you should use +phpMyAdmin's :term:`HTTP` or cookie authentication features. + +Suggestions: + +* Your :file:`config.inc.php` file should be ``chmod 660``. +* All your phpMyAdmin files should be chown -R phpmy.apache, where phpmy + is a user whose password is only known to you, and apache is the group + under which Apache runs. +* Follow security recommendations for PHP and your webserver. + +.. _faq4_3: + +4.3 I get errors about not being able to include a file in */lang* or in */libraries*. +-------------------------------------------------------------------------------------- + +Check :file:`php.ini`, or ask your sysadmin to check it. The +``include_path`` must contain "." somewhere in it, and +``open_basedir``, if used, must contain "." and "./lang" to allow +normal operation of phpMyAdmin. + +.. _faq4_4: + +4.4 phpMyAdmin always gives "Access denied" when using HTTP authentication. +--------------------------------------------------------------------------- + +This could happen for several reasons: + +* :config:option:`$cfg['Servers'][$i]['controluser']` and/or :config:option:`$cfg['Servers'][$i]['controlpass']` are wrong. +* The username/password you specify in the login dialog are invalid. +* You have already setup a security mechanism for the phpMyAdmin- + directory, eg. a :term:`.htaccess` file. This would interfere with phpMyAdmin's + authentication, so remove it. + +.. _faq4_5: + +4.5 Is it possible to let users create their own databases? +----------------------------------------------------------- + +Starting with 2.2.5, in the user management page, you can enter a +wildcard database name for a user (for example "joe%"), and put the +privileges you want. For example, adding ``SELECT, INSERT, UPDATE, +DELETE, CREATE, DROP, INDEX, ALTER`` would let a user create/manage +his/her database(s). + +.. _faq4_6: + +4.6 How can I use the Host-based authentication additions? +---------------------------------------------------------- + +If you have existing rules from an old :term:`.htaccess` file, you can take them and +add a username between the ``'deny'``/``'allow'`` and ``'from'`` +strings. Using the username wildcard of ``'%'`` would be a major +benefit here if your installation is suited to using it. Then you can +just add those updated lines into the +:config:option:`$cfg['Servers'][$i]['AllowDeny']['rules']` array. + +If you want a pre-made sample, you can try this fragment. It stops the +'root' user from logging in from any networks other than the private +network :term:`IP` blocks. + +.. code-block:: php + + //block root from logging in except from the private networks + $cfg['Servers'][$i]['AllowDeny']['order'] = 'deny,allow'; + $cfg['Servers'][$i]['AllowDeny']['rules'] = array( + 'deny root from all', + 'allow root from localhost', + 'allow root from 10.0.0.0/8', + 'allow root from 192.168.0.0/16', + 'allow root from 172.16.0.0/12', + ); + +.. _faq4_7: + +4.7 Authentication window is displayed more than once, why? +----------------------------------------------------------- + +This happens if you are using a :term:`URL` to start phpMyAdmin which is +different than the one set in your :config:option:`$cfg['PmaAbsoluteUri']`. For +example, a missing "www", or entering with an :term:`IP` address while a domain +name is defined in the config file. + +.. _faq4_8: + +4.8 Which parameters can I use in the URL that starts phpMyAdmin? +----------------------------------------------------------------- + +When starting phpMyAdmin, you can use the ``db`` +and ``server`` parameters. This last one can contain +either the numeric host index (from ``$i`` of the configuration file) +or one of the host names present in the configuration file. + +For example, to jump directly to a particular database, a URL can be constructed as +``https://example.com/phpmyadmin/?db=sakila``. + +.. versionchanged:: 4.9.0 + + Support for using the ``pma_username`` and ``pma_password`` parameters was removed + in phpMyAdmin 4.9.0 (see `PMASA-2019-4 `_). + +.. _faqbrowsers: + +Browsers or client OS ++++++++++++++++++++++ + +.. _faq5_1: + +5.1 I get an out of memory error, and my controls are non-functional, when trying to create a table with more than 14 columns. +------------------------------------------------------------------------------------------------------------------------------ + +We could reproduce this problem only under Win98/98SE. Testing under +WinNT4 or Win2K, we could easily create more than 60 columns. A +workaround is to create a smaller number of columns, then come back to +your table properties and add the other columns. + +.. _faq5_2: + +5.2 With Xitami 2.5b4, phpMyAdmin won't process form fields. +------------------------------------------------------------ + +This is not a phpMyAdmin problem but a Xitami known bug: you'll face +it with each script/website that use forms. Upgrade or downgrade your +Xitami server. + +.. _faq5_3: + +5.3 I have problems dumping tables with Konqueror (phpMyAdmin 2.2.2). +--------------------------------------------------------------------- + +With Konqueror 2.1.1: plain dumps, zip and gzip dumps work ok, except +that the proposed file name for the dump is always 'tbl\_dump.php'. +The bzip2 dumps don't seem to work. With Konqueror 2.2.1: plain dumps +work; zip dumps are placed into the user's temporary directory, so +they must be moved before closing Konqueror, or else they disappear. +gzip dumps give an error message. Testing needs to be done for +Konqueror 2.2.2. + +.. _faq5_4: + +5.4 I can't use the cookie authentication mode because Internet Explorer never stores the cookies. +-------------------------------------------------------------------------------------------------- + +MS Internet Explorer seems to be really buggy about cookies, at least +till version 6. + +.. _faq5_5: + +5.5 (withdrawn). +---------------------------------------------------------------------------- + +.. _faq5_6: + +5.6 (withdrawn). +----------------------------------------------------------------------------------------------------------------------------------------------------------------- + +.. _faq5_7: + +5.7 I refresh (reload) my browser, and come back to the welcome page. +--------------------------------------------------------------------- + +Some browsers support right-clicking into the frame you want to +refresh, just do this in the right frame. + +.. _faq5_8: + +5.8 With Mozilla 0.9.7 I have problems sending a query modified in the query box. +--------------------------------------------------------------------------------- + +Looks like a Mozilla bug: 0.9.6 was OK. We will keep an eye on future +Mozilla versions. + +.. _faq5_9: + +5.9 With Mozilla 0.9.? to 1.0 and Netscape 7.0-PR1 I can't type a whitespace in the SQL-Query edit area: the page scrolls down. +------------------------------------------------------------------------------------------------------------------------------- + +This is a Mozilla bug (see bug #26882 at `BugZilla +`_). + +.. _faq5_10: + +5.10 (withdrawn). +----------------------------------------------------------------------------------------- + +.. _faq5_11: + +5.11 Extended-ASCII characters like German umlauts are displayed wrong. +----------------------------------------------------------------------- + +Please ensure that you have set your browser's character set to the +one of the language file you have selected on phpMyAdmin's start page. +Alternatively, you can try the auto detection mode that is supported +by the recent versions of the most browsers. + +.. _faq5_12: + +5.12 Mac OS X Safari browser changes special characters to "?". +--------------------------------------------------------------- + +This issue has been reported by a :term:`Mac OS X` user, who adds that Chimera, +Netscape and Mozilla do not have this problem. + +.. _faq5_13: + +5.13 (withdrawn) +---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +.. _faq5_14: + +5.14 (withdrawn) +------------------------------------------------------------------------------------------------------------------ + +.. _faq5_15: + +5.15 (withdrawn) +----------------------------------------- + +.. _faq5_16: + +5.16 With Internet Explorer, I get "Access is denied" Javascript errors. Or I cannot make phpMyAdmin work under Windows. +------------------------------------------------------------------------------------------------------------------------ + +Please check the following points: + +* Maybe you have defined your :config:option:`$cfg['PmaAbsoluteUri']` setting in + :file:`config.inc.php` to an :term:`IP` address and you are starting phpMyAdmin + with a :term:`URL` containing a domain name, or the reverse situation. +* Security settings in IE and/or Microsoft Security Center are too high, + thus blocking scripts execution. +* The Windows Firewall is blocking Apache and MySQL. You must allow + :term:`HTTP` ports (80 or 443) and MySQL + port (usually 3306) in the "in" and "out" directions. + +.. _faq5_17: + +5.17 With Firefox, I cannot delete rows of data or drop a database. +------------------------------------------------------------------- + +Many users have confirmed that the Tabbrowser Extensions plugin they +installed in their Firefox is causing the problem. + +.. _faq5_18: + +5.18 (withdrawn) +----------------------------------------------------------------------------------------- + +.. _faq5_19: + +5.19 I get JavaScript errors in my browser. +------------------------------------------- + +Issues have been reported with some combinations of browser +extensions. To troubleshoot, disable all extensions then clear your +browser cache to see if the problem goes away. + +.. _faq5_20: + +5.20 I get errors about violating Content Security Policy. +---------------------------------------------------------- + +If you see errors like: + +.. code-block:: text + + Refused to apply inline style because it violates the following Content Security Policy directive + +This is usually caused by some software, which wrongly rewrites +:mailheader:`Content Security Policy` headers. Usually this is caused by +antivirus proxy or browser addons which are causing such errors. + +If you see these errors, try disabling the HTTP proxy in antivirus or disable +the :mailheader:`Content Security Policy` rewriting in it. If that doesn't +help, try disabling browser extensions. + +Alternatively it can be also server configuration issue (if the webserver is +configured to emit :mailheader:`Content Security Policy` headers, they can +override the ones from phpMyAdmin). + +Programs known to cause these kind of errors: + +* Kaspersky Internet Security + +.. _faq5_21: + +5.21 I get errors about potentially unsafe operation when browsing table or executing SQL query. +------------------------------------------------------------------------------------------------ + +If you see errors like: + +.. code-block:: text + + A potentially unsafe operation has been detected in your request to this site. + +This is usually caused by web application firewall doing requests filtering. It +tries to prevent SQL injection, however phpMyAdmin is tool designed to execute +SQL queries, thus it makes it unusable. + +Please whitelist phpMyAdmin scripts from the web application firewall settings +or disable it completely for phpMyAdmin path. + +Programs known to cause these kind of errors: + +* Wordfence Web Application Firewall + +.. _faqusing: + +Using phpMyAdmin +++++++++++++++++ + +.. _faq6_1: + +6.1 I can't insert new rows into a table / I can't create a table - MySQL brings up a SQL error. +------------------------------------------------------------------------------------------------ + +Examine the :term:`SQL` error with care. +Often the problem is caused by specifying a wrong column-type. Common +errors include: + +* Using ``VARCHAR`` without a size argument +* Using ``TEXT`` or ``BLOB`` with a size argument + +Also, look at the syntax chapter in the MySQL manual to confirm that +your syntax is correct. + +.. _faq6_2: + +6.2 When I create a table, I set an index for two columns and phpMyAdmin generates only one index with those two columns. +------------------------------------------------------------------------------------------------------------------------- + +This is the way to create a multi-columns index. If you want two +indexes, create the first one when creating the table, save, then +display the table properties and click the Index link to create the +other index. + +.. _faq6_3: + +6.3 How can I insert a null value into my table? +------------------------------------------------ + +Since version 2.2.3, you have a checkbox for each column that can be +null. Before 2.2.3, you had to enter "null", without the quotes, as +the column's value. Since version 2.5.5, you have to use the checkbox +to get a real NULL value, so if you enter "NULL" this means you want a +literal NULL in the column, and not a NULL value (this works in PHP4). + +.. _faq6_4: + +6.4 How can I backup my database or table? +------------------------------------------ + +Click on a database or table name in the navigation panel, the properties will +be displayed. Then on the menu, click "Export", you can dump the structure, the +data, or both. This will generate standard :term:`SQL` statements that can be +used to recreate your database/table. You will need to choose "Save as file", +so that phpMyAdmin can transmit the resulting dump to your station. Depending +on your PHP configuration, you will see options to compress the dump. See also +the :config:option:`$cfg['ExecTimeLimit']` configuration variable. For +additional help on this subject, look for the word "dump" in this document. + +.. _faq6_5: + +6.5 How can I restore (upload) my database or table using a dump? How can I run a ".sql" file? +---------------------------------------------------------------------------------------------- + +Click on a database name in the navigation panel, the properties will +be displayed. Select "Import" from the list of tabs in the right–hand +frame (or ":term:`SQL`" if your phpMyAdmin +version is previous to 2.7.0). In the "Location of the text file" +section, type in the path to your dump filename, or use the Browse +button. Then click Go. With version 2.7.0, the import engine has been +re–written, if possible it is suggested that you upgrade to take +advantage of the new features. For additional help on this subject, +look for the word "upload" in this document. + +Note: For errors while importing of dumps exported from older MySQL versions to newer MySQL versions, +please check :ref:`faq6_41`. + +.. _faq6_6: + +6.6 How can I use the relation table in Query-by-example? +--------------------------------------------------------- + +Here is an example with the tables persons, towns and countries, all +located in the database "mydb". If you don't have a ``pma__relation`` +table, create it as explained in the configuration section. Then +create the example tables: + +.. code-block:: mysql + + CREATE TABLE REL_countries ( + country_code char(1) NOT NULL default '', + description varchar(10) NOT NULL default '', + PRIMARY KEY (country_code) + ) ENGINE=MyISAM; + + INSERT INTO REL_countries VALUES ('C', 'Canada'); + + CREATE TABLE REL_persons ( + id tinyint(4) NOT NULL auto_increment, + person_name varchar(32) NOT NULL default '', + town_code varchar(5) default '0', + country_code char(1) NOT NULL default '', + PRIMARY KEY (id) + ) ENGINE=MyISAM; + + INSERT INTO REL_persons VALUES (11, 'Marc', 'S', 'C'); + INSERT INTO REL_persons VALUES (15, 'Paul', 'S', 'C'); + + CREATE TABLE REL_towns ( + town_code varchar(5) NOT NULL default '0', + description varchar(30) NOT NULL default '', + PRIMARY KEY (town_code) + ) ENGINE=MyISAM; + + INSERT INTO REL_towns VALUES ('S', 'Sherbrooke'); + INSERT INTO REL_towns VALUES ('M', 'Montréal'); + +To setup appropriate links and display information: + +* on table "REL\_persons" click Structure, then Relation view +* for "town\_code", choose from dropdowns, "mydb", "REL\_towns", "code" + for foreign database, table and column respectively +* for "country\_code", choose from dropdowns, "mydb", "REL\_countries", + "country\_code" for foreign database, table and column respectively +* on table "REL\_towns" click Structure, then Relation view +* in "Choose column to display", choose "description" +* repeat the two previous steps for table "REL\_countries" + +Then test like this: + +* Click on your db name in the navigation panel +* Choose "Query" +* Use tables: persons, towns, countries +* Click "Update query" +* In the columns row, choose persons.person\_name and click the "Show" + tickbox +* Do the same for towns.description and countries.descriptions in the + other 2 columns +* Click "Update query" and you will see in the query box that the + correct joins have been generated +* Click "Submit query" + +.. _faqdisplay: + +6.7 How can I use the "display column" feature? +----------------------------------------------- + +Starting from the previous example, create the ``pma__table_info`` as +explained in the configuration section, then browse your persons +table, and move the mouse over a town code or country code. See also +:ref:`faq6_21` for an additional feature that "display column" +enables: drop-down list of possible values. + +.. _faqpdf: + +6.8 How can I produce a PDF schema of my database? +-------------------------------------------------- + +First the configuration variables "relation", "table\_coords" and +"pdf\_pages" have to be filled in. Then you need to think about your +schema layout. Which tables will go on which pages? + +* Select your database in the navigation panel. +* Choose "Operations" in the navigation bar at the top. +* Choose "Edit :term:`PDF` Pages" near the + bottom of the page. +* Enter a name for the first :term:`PDF` page + and click Go. If you like, you can use the "automatic layout," which + will put all your linked tables onto the new page. +* Select the name of the new page (making sure the Edit radio button is + selected) and click Go. +* Select a table from the list, enter its coordinates and click Save. + Coordinates are relative; your diagram will be automatically scaled to + fit the page. When initially placing tables on the page, just pick any + coordinates -- say, 50x50. After clicking Save, you can then use the + :ref:`wysiwyg` to position the element correctly. +* When you'd like to look at your :term:`PDF`, first be sure to click the Save + button beneath the list of tables and coordinates, to save any changes you + made there. Then scroll all the way down, select the :term:`PDF` options you + want, and click Go. +* Internet Explorer for Windows may suggest an incorrect filename when + you try to save a generated :term:`PDF`. + When saving a generated :term:`PDF`, be + sure that the filename ends in ".pdf", for example "schema.pdf". + Browsers on other operating systems, and other browsers on Windows, do + not have this problem. + +.. seealso:: + + :ref:`relations` + +.. _faq6_9: + +6.9 phpMyAdmin is changing the type of one of my columns! +--------------------------------------------------------- + +No, it's MySQL that is doing `silent column type changing +`_. + +.. _underscore: + +6.10 When creating a privilege, what happens with underscores in the database name? +----------------------------------------------------------------------------------- + +If you do not put a backslash before the underscore, this is a +wildcard grant, and the underscore means "any character". So, if the +database name is "john\_db", the user would get rights to john1db, +john2db ... If you put a backslash before the underscore, it means +that the database name will have a real underscore. + +.. _faq6_11: + +6.11 What is the curious symbol ø in the statistics pages? +---------------------------------------------------------- + +It means "average". + +.. _faqexport: + +6.12 I want to understand some Export options. +---------------------------------------------- + +**Structure:** + +* "Add DROP TABLE" will add a line telling MySQL to `drop the table + `_, if it already + exists during the import. It does NOT drop the table after your + export, it only affects the import file. +* "If Not Exists" will only create the table if it doesn't exist. + Otherwise, you may get an error if the table name exists but has a + different structure. +* "Add AUTO\_INCREMENT value" ensures that AUTO\_INCREMENT value (if + any) will be included in backup. +* "Enclose table and column names with backquotes" ensures that column + and table names formed with special characters are protected. +* "Add into comments" includes column comments, relations, and MIME + types set in the pmadb in the dump as :term:`SQL` comments + (*/\* xxx \*/*). + +**Data:** + +* "Complete inserts" adds the column names on every INSERT command, for + better documentation (but resulting file is bigger). +* "Extended inserts" provides a shorter dump file by using only once the + INSERT verb and the table name. +* "Delayed inserts" are best explained in the `MySQL manual - INSERT DELAYED Syntax + `_. +* "Ignore inserts" treats errors as a warning instead. Again, more info + is provided in the `MySQL manual - INSERT Syntax + `_, but basically with + this selected, invalid values are adjusted and inserted rather than + causing the entire statement to fail. + +.. _faq6_13: + +6.13 I would like to create a database with a dot in its name. +-------------------------------------------------------------- + +This is a bad idea, because in MySQL the syntax "database.table" is +the normal way to reference a database and table name. Worse, MySQL +will usually let you create a database with a dot, but then you cannot +work with it, nor delete it. + +.. _faqsqlvalidator: + +6.14 (withdrawn). +----------------- + +.. _faq6_15: + +6.15 I want to add a BLOB column and put an index on it, but MySQL says "BLOB column '...' used in key specification without a key length". +------------------------------------------------------------------------------------------------------------------------------------------- + +The right way to do this, is to create the column without any indexes, +then display the table structure and use the "Create an index" dialog. +On this page, you will be able to choose your BLOB column, and set a +size to the index, which is the condition to create an index on a BLOB +column. + +.. _faq6_16: + +6.16 How can I simply move in page with plenty editing fields? +-------------------------------------------------------------- + +You can use :kbd:`Ctrl+arrows` (:kbd:`Option+Arrows` in Safari) for moving on +most pages with many editing fields (table structure changes, row editing, +etc.). + +.. _faq6_17: + +6.17 Transformations: I can't enter my own mimetype! What is this feature then useful for? +------------------------------------------------------------------------------------------ + +Defining mimetypes is of no use if you can't put +transformations on them. Otherwise you could just put a comment on the +column. Because entering your own mimetype will cause serious syntax +checking issues and validation, this introduces a high-risk false- +user-input situation. Instead you have to initialize mimetypes using +functions or empty mimetype definitions. + +Plus, you have a whole overview of available mimetypes. Who knows all those +mimetypes by heart so he/she can enter it at will? + +.. _faqbookmark: + +6.18 Bookmarks: Where can I store bookmarks? Why can't I see any bookmarks below the query box? What are these variables for? +----------------------------------------------------------------------------------------------------------------------------- + +You need to have configured the :ref:`linked-tables` for using bookmarks +feature. Once you have done that, you can use bookmarks in the :guilabel:`SQL` tab. + +.. seealso:: :ref:`bookmarks` + +.. _faq6_19: + +6.19 How can I create simple LATEX document to include exported table? +---------------------------------------------------------------------- + +You can simply include table in your LATEX documents, +minimal sample document should look like following one (assuming you +have table exported in file :file:`table.tex`): + +.. code-block:: latex + + \documentclass{article} % or any class you want + \usepackage{longtable} % for displaying table + \begin{document} % start of document + \include{table} % including exported table + \end{document} % end of document + +.. _faq6_20: + +6.20 I see a lot of databases which are not mine, and cannot access them. +------------------------------------------------------------------------- + +You have one of these global privileges: CREATE TEMPORARY TABLES, SHOW +DATABASES, LOCK TABLES. Those privileges also enable users to see all the +database names. So if your users do not need those privileges, you can remove +them and their databases list will shorten. + +.. seealso:: + +.. _faq6_21: + +6.21 In edit/insert mode, how can I see a list of possible values for a column, based on some foreign table? +------------------------------------------------------------------------------------------------------------ + +You have to setup appropriate links between the tables, and also setup +the "display column" in the foreign table. See :ref:`faq6_6` for an +example. Then, if there are 100 values or less in the foreign table, a +drop-down list of values will be available. You will see two lists of +values, the first list containing the key and the display column, the +second list containing the display column and the key. The reason for +this is to be able to type the first letter of either the key or the +display column. For 100 values or more, a distinct window will appear, +to browse foreign key values and choose one. To change the default +limit of 100, see :config:option:`$cfg['ForeignKeyMaxLimit']`. + +.. _faq6_22: + +6.22 Bookmarks: Can I execute a default bookmark automatically when entering Browse mode for a table? +----------------------------------------------------------------------------------------------------- + +Yes. If a bookmark has the same label as a table name and it's not a +public bookmark, it will be executed. + +.. seealso:: :ref:`bookmarks` + +.. _faq6_23: + +6.23 Export: I heard phpMyAdmin can export Microsoft Excel files? +----------------------------------------------------------------- + +You can use :term:`CSV` for Microsoft Excel, +which works out of the box. + +.. versionchanged:: 3.4.5 + Since phpMyAdmin 3.4.5 support for direct export to Microsoft Excel version + 97 and newer was dropped. + +.. _faq6_24: + +6.24 Now that phpMyAdmin supports native MySQL 4.1.x column comments, what happens to my column comments stored in pmadb? +------------------------------------------------------------------------------------------------------------------------- + +Automatic migration of a table's pmadb-style column comments to the +native ones is done whenever you enter Structure page for this table. + +.. _faq6_25: + +6.25 (withdrawn). +----------------- + +.. _faq6_26: + +6.26 How can I select a range of rows? +-------------------------------------- + +Click the first row of the range, hold the shift key and click the +last row of the range. This works everywhere you see rows, for example +in Browse mode or on the Structure page. + +.. _faq6_27: + +6.27 What format strings can I use? +----------------------------------- + +In all places where phpMyAdmin accepts format strings, you can use +``@VARIABLE@`` expansion and `strftime `_ +format strings. The expanded variables depend on a context (for +example, if you haven't chosen a table, you can not get the table +name), but the following variables can be used: + +``@HTTP_HOST@`` + HTTP host that runs phpMyAdmin +``@SERVER@`` + MySQL server name +``@VERBOSE@`` + Verbose MySQL server name as defined in :config:option:`$cfg['Servers'][$i]['verbose']` +``@VSERVER@`` + Verbose MySQL server name if set, otherwise normal +``@DATABASE@`` + Currently opened database +``@TABLE@`` + Currently opened table +``@COLUMNS@`` + Columns of the currently opened table +``@PHPMYADMIN@`` + phpMyAdmin with version + +.. _wysiwyg: + +6.28 How can I easily edit relational schema for export? +-------------------------------------------------------- + +By clicking on the button 'toggle scratchboard' on the page where you +edit x/y coordinates of those elements you can activate a scratchboard +where all your elements are placed. By clicking on an element, you can +move them around in the pre-defined area and the x/y coordinates will +get updated dynamically. Likewise, when entering a new position +directly into the input field, the new position in the scratchboard +changes after your cursor leaves the input field. + +You have to click on the 'OK'-button below the tables to save the new +positions. If you want to place a new element, first add it to the +table of elements and then you can drag the new element around. + +By changing the paper size and the orientation you can change the size +of the scratchboard as well. You can do so by just changing the +dropdown field below, and the scratchboard will resize automatically, +without interfering with the current placement of the elements. + +If ever an element gets out of range you can either enlarge the paper +size or click on the 'reset' button to place all elements below each +other. + +.. _faq6_29: + +6.29 Why can't I get a chart from my query result table? +-------------------------------------------------------- + +Not every table can be put to the chart. Only tables with one, two or +three columns can be visualised as a chart. Moreover the table must be +in a special format for chart script to understand it. Currently +supported formats can be found in :ref:`charts`. + +.. _faq6_30: + +6.30 Import: How can I import ESRI Shapefiles? +---------------------------------------------- + +An ESRI Shapefile is actually a set of several files, where .shp file +contains geometry data and .dbf file contains data related to those +geometry data. To read data from .dbf file you need to have PHP +compiled with the dBase extension (--enable-dbase). Otherwise only +geometry data will be imported. + +To upload these set of files you can use either of the following +methods: + +Configure upload directory with :config:option:`$cfg['UploadDir']`, upload both .shp and .dbf files with +the same filename and chose the .shp file from the import page. + +Create a zip archive with .shp and .dbf files and import it. For this +to work, you need to set :config:option:`$cfg['TempDir']` to a place where the web server user can +write (for example ``'./tmp'``). + +To create the temporary directory on a UNIX-based system, you can do: + +.. code-block:: sh + + cd phpMyAdmin + mkdir tmp + chmod o+rwx tmp + +.. _faq6_31: + +6.31 How do I create a relation in designer? +-------------------------------------------- + +To select relation, click: The display column is shown in pink. To +set/unset a column as the display column, click the "Choose column to +display" icon, then click on the appropriate column name. + +.. _faq6_32: + +6.32 How can I use the zoom search feature? +------------------------------------------- + +The Zoom search feature is an alternative to table search feature. It allows +you to explore a table by representing its data in a scatter plot. You can +locate this feature by selecting a table and clicking the :guilabel:`Search` +tab. One of the sub-tabs in the :guilabel:`Table Search` page is +:guilabel:`Zoom Search`. + +Consider the table REL\_persons in :ref:`faq6_6` for +an example. To use zoom search, two columns need to be selected, for +example, id and town\_code. The id values will be represented on one +axis and town\_code values on the other axis. Each row will be +represented as a point in a scatter plot based on its id and +town\_code. You can include two additional search criteria apart from +the two fields to display. + +You can choose which field should be +displayed as label for each point. If a display column has been set +for the table (see :ref:`faqdisplay`), it is taken as the label unless +you specify otherwise. You can also select the maximum number of rows +you want to be displayed in the plot by specifing it in the 'Max rows +to plot' field. Once you have decided over your criteria, click 'Go' +to display the plot. + +After the plot is generated, you can use the +mousewheel to zoom in and out of the plot. In addition, panning +feature is enabled to navigate through the plot. You can zoom-in to a +certain level of detail and use panning to locate your area of +interest. Clicking on a point opens a dialogue box, displaying field +values of the data row represented by the point. You can edit the +values if required and click on submit to issue an update query. Basic +instructions on how to use can be viewed by clicking the 'How to use?' +link located just above the plot. + +.. _faq6_33: + +6.33 When browsing a table, how can I copy a column name? +--------------------------------------------------------- + +Selecting the name of the column within the browse table header cell +for copying is difficult, as the columns support reordering by +dragging the header cells as well as sorting by clicking on the linked +column name. To copy a column name, double-click on the empty area +next to the column name, when the tooltip tells you to do so. This +will show you an input box with the column name. You may right-click +the column name within this input box to copy it to your clipboard. + +.. _faq6_34: + +6.34 How can I use the Favorite Tables feature? +--------------------------------------------------------- + +Favorite Tables feature is very much similar to Recent Tables feature. +It allows you to add a shortcut for the frequently used tables of any +database in the navigation panel . You can easily navigate to any table +in the list by simply choosing it from the list. These tables are stored +in your browser's local storage if you have not configured your +`phpMyAdmin Configuration Storage`. Otherwise these entries are stored in +`phpMyAdmin Configuration Storage`. + +IMPORTANT: In absence of `phpMyAdmin Configuration Storage`, your Favorite +tables may be different in different browsers based on your different +selections in them. + +To add a table to Favorite list simply click on the `Gray` star in front +of a table name in the list of tables of a Database and wait until it +turns to `Yellow`. +To remove a table from list, simply click on the `Yellow` star and +wait until it turns `Gray` again. + +Using :config:option:`$cfg['NumFavoriteTables']` in your :file:`config.inc.php` +file, you can define the maximum number of favorite tables shown in the +navigation panel. Its default value is `10`. + +.. _faq6_35: + +6.35 How can I use the Range search feature? +--------------------------------------------------------- + +With the help of range search feature, one can specify a range of values for +particular column(s) while performing search operation on a table from the `Search` +tab. + +To use this feature simply click on the `BETWEEN` or `NOT BETWEEN` operators +from the operator select list in front of the column name. On choosing one of the +above options, a dialog box will show up asking for the `Minimum` and `Maximum` +value for that column. Only the specified range of values will be included +in case of `BETWEEN` and excluded in case of `NOT BETWEEN` from the final results. + +Note: The Range search feature will work only `Numeric` and `Date` data type columns. + +.. _faq6_36: + +6.36 What is Central columns and how can I use this feature? +------------------------------------------------------------ + +As the name suggests, the Central columns feature enables to maintain a central list of +columns per database to avoid similar name for the same data element and bring consistency +of data type for the same data element. You can use the central list of columns to +add an element to any table structure in that database which will save from writing +similar column name and column definition. + +To add a column to central list, go to table structure page, check the columns you want +to include and then simply click on "Add to central columns". If you want to add all +unique columns from more than one table from a database then go to database structure page, +check the tables you want to include and then select "Add columns to central list". + +To remove a column from central list, go to Table structure page, check the columns you want +to remove and then simply click on "Remove from central columns". If you want to remove all +columns from more than one tables from a database then go to database structure page, +check the tables you want to include and then select "Remove columns from central list". + +To view and manage the central list, select the database you want to manage central columns +for then from the top menu click on "Central columns". You will be taken to a page where +you will have options to edit, delete or add new columns to central list. + +.. _faq6_37: + +6.37 How can I use Improve Table structure feature? +--------------------------------------------------------- + +Improve table structure feature helps to bring the table structure upto +Third Normal Form. A wizard is presented to user which asks questions about the +elements during the various steps for normalization and a new structure is proposed +accordingly to bring the table into the First/Second/Third Normal form. +On startup of the wizard, user gets to select upto what normal form they want to +normalize the table structure. + +Here is an example table which you can use to test all of the three First, Second and +Third Normal Form. + +.. code-block:: mysql + + CREATE TABLE `VetOffice` ( + `petName` varchar(64) NOT NULL, + `petBreed` varchar(64) NOT NULL, + `petType` varchar(64) NOT NULL, + `petDOB` date NOT NULL, + `ownerLastName` varchar(64) NOT NULL, + `ownerFirstName` varchar(64) NOT NULL, + `ownerPhone1` int(12) NOT NULL, + `ownerPhone2` int(12) NOT NULL, + `ownerEmail` varchar(64) NOT NULL, + ); + +The above table is not in First normal Form as no :term:`primary key` exists. Primary key +is supposed to be (`petName`,`ownerLastName`,`ownerFirstName`) . If the :term:`primary key` +is chosen as suggested the resultant table won't be in Second as well as Third Normal +form as the following dependencies exists. + +.. code-block:: mysql + + (OwnerLastName, OwnerFirstName) -> OwnerEmail + (OwnerLastName, OwnerFirstName) -> OwnerPhone + PetBreed -> PetType + +Which says, OwnerEmail depends on OwnerLastName and OwnerFirstName. +OwnerPhone depends on OwnerLastName and OwnerFirstName. +PetType depends on PetBreed. + +.. _faq6_38: + +6.38 How can I reassign auto-incremented values? +------------------------------------------------ + +Some users prefer their AUTO_INCREMENT values to be consecutive; this is not +always the case after row deletion. + +Here are the steps to accomplish this. These are manual steps because they +involve a manual verification at one point. + +* Ensure that you have exclusive access to the table to rearrange + +* On your :term:`primary key` column (i.e. id), remove the AUTO_INCREMENT setting + +* Delete your primary key in Structure > indexes + +* Create a new column future_id as primary key, AUTO_INCREMENT + +* Browse your table and verify that the new increments correspond to what + you're expecting + +* Drop your old id column + +* Rename the future_id column to id + +* Move the new id column via Structure > Move columns + +.. _faq6_39: + +6.39 What is the "Adjust privileges" option when renaming, copying, or moving a database, table, column, or procedure? +---------------------------------------------------------------------------------------------------------------------- + +When renaming/copying/moving a database/table/column/procedure, +MySQL does not adjust the original privileges relating to these objects +on its own. By selecting this option, phpMyAdmin will adjust the privilege +table so that users have the same privileges on the new items. + +For example: A user 'bob'@'localhost' has a 'SELECT' privilege on a +column named 'id'. Now, if this column is renamed to 'id_new', MySQL, +on its own, would **not** adjust the column privileges to the new column name. +phpMyAdmin can make this adjustment for you automatically. + +Notes: + +* While adjusting privileges for a database, the privileges of all + database-related elements (tables, columns and procedures) are also adjusted + to the database's new name. + +* Similarly, while adjusting privileges for a table, the privileges of all + the columns inside the new table are also adjusted. + +* While adjusting privileges, the user performing the operation **must** have the following + privileges: + + * SELECT, INSERT, UPDATE, DELETE privileges on following tables: + `mysql`.`db`, `mysql`.`columns_priv`, `mysql`.`tables_priv`, `mysql`.`procs_priv` + * FLUSH privilege (GLOBAL) + +Thus, if you want to replicate the database/table/column/procedure as it is +while renaming/copying/moving these objects, make sure you have checked this option. + +.. _faq6_40: + +6.40 I see "Bind parameters" checkbox in the "SQL" page. How do I write parameterized SQL queries? +-------------------------------------------------------------------------------------------------- + +From version 4.5, phpMyAdmin allows users to execute parameterized queries in the "SQL" page. +Parameters should be prefixed with a colon(:) and when the "Bind parameters" checkbox is checked +these parameters will be identified and input fields for these parameters will be presented. +Values entered in these field will be substituted in the query before being executed. + +.. _faq6_41: + +6.41 I get import errors while importing the dumps exported from older MySQL versions (pre-5.7.6) into newer MySQL versions (5.7.7+), but they work fine when imported back on same older versions ? +-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +If you get errors like *#1031 - Table storage engine for 'table_name' doesn't have this option* +while importing the dumps exported from pre-5.7.7 MySQL servers into new MySQL server versions 5.7.7+, +it might be because ROW_FORMAT=FIXED is not supported with InnoDB tables. Moreover, the value of +`innodb_strict_mode `_ would define if this would be reported as a warning or as an error. + +Since MySQL version 5.7.9, the default value for `innodb_strict_mode` is `ON` and thus would generate +an error when such a CREATE TABLE or ALTER TABLE statement is encountered. + +There are two ways of preventing such errors while importing: + +* Change the value of `innodb_strict_mode` to `OFF` before starting the import and turn it `ON` after + the import is successfully completed. +* This can be achieved in two ways: + + * Go to 'Variables' page and edit the value of `innodb_strict_mode` + * Run the query : `SET GLOBAL `innodb_strict_mode` = '[value]'` + +After the import is done, it is suggested that the value of `innodb_strict_mode` should be reset to the +original value. + +.. _faqproject: + +phpMyAdmin project +++++++++++++++++++ + +.. _faq7_1: + +7.1 I have found a bug. How do I inform developers? +--------------------------------------------------- + +Our issues tracker is located at . +For security issues, please refer to the instructions at to email +the developers directly. + +.. _faq7_2: + +7.2 I want to translate the messages to a new language or upgrade an existing language, where do I start? +--------------------------------------------------------------------------------------------------------- + +Translations are very welcome and all you need to have are the +language skills. The easiest way is to use our `online translation +service `_. You can check +out all the possibilities to translate in the `translate section on +our website `_. + +.. _faq7_3: + +7.3 I would like to help out with the development of phpMyAdmin. How should I proceed? +-------------------------------------------------------------------------------------- + +We welcome every contribution to the development of phpMyAdmin. You +can check out all the possibilities to contribute in the `contribute +section on our website +`_. + +.. seealso:: :ref:`developers` + +.. _faqsecurity: + +Security +++++++++ + +.. _faq8_1: + +8.1 Where can I get information about the security alerts issued for phpMyAdmin? +-------------------------------------------------------------------------------- + +Please refer to . + +.. _faq8_2: + +8.2 How can I protect phpMyAdmin against brute force attacks? +------------------------------------------------------------- + +If you use Apache web server, phpMyAdmin exports information about +authentication to the Apache environment and it can be used in Apache +logs. Currently there are two variables available: + +``userID`` + User name of currently active user (he does not have to be logged in). +``userStatus`` + Status of currently active user, one of ``ok`` (user is logged in), + ``mysql-denied`` (MySQL denied user login), ``allow-denied`` (user denied + by allow/deny rules), ``root-denied`` (root is denied in configuration), + ``empty-denied`` (empty password is denied). + +``LogFormat`` directive for Apache can look like following: + +.. code-block:: apache + + LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\" %{userID}n %{userStatus}n" pma_combined + +You can then use any log analyzing tools to detect possible break-in +attempts. + +.. _faq8_3: + +8.3 Why are there path disclosures when directly loading certain files? +----------------------------------------------------------------------- + +This is a server configuration problem. Never enable ``display_errors`` on a production site. + +.. _faq8_4: + +8.4 CSV files exported from phpMyAdmin could allow a formula injection attack. +------------------------------------------------------------------------------ + +It is possible to generate a :term:`CSV` file that, when imported to a spreadsheet program such as Microsoft Excel, +could potentially allow the execution of arbitrary commands. + +The CSV files generated by phpMyAdmin could potentially contain text that would be interpreted by a spreadsheet program as +a formula, but we do not believe escaping those fields is the proper behavior. There is no means to properly escape and +differentiate between a desired text output and a formula that should be escaped, and CSV is a text format where function +definitions should not be interpreted anyway. We have discussed this at length and feel it is the responsibility of the +spreadsheet program to properly parse and sanitize such data on input instead. + +Google also has a `similar view `_. + +.. _faqsynchronization: + +Synchronization ++++++++++++++++ + +.. _faq9_1: + +9.1 (withdrawn). +---------------- + +.. _faq9_2: + +9.2 (withdrawn). +---------------- diff --git a/php/apps/phpmyadmin49/doc/html/_sources/glossary.txt b/php/apps/phpmyadmin49/doc/html/_sources/glossary.txt new file mode 100644 index 00000000..cdad7ac0 --- /dev/null +++ b/php/apps/phpmyadmin49/doc/html/_sources/glossary.txt @@ -0,0 +1,426 @@ +.. _glossary: + +Glossary +======== + +From Wikipedia, the free encyclopedia + +.. glossary:: + + .htaccess + the default name of Apache's directory-level configuration file. + + .. seealso:: + + ACL + Access Contol List + + Blowfish + a keyed, symmetric block cipher, designed in 1993 by Bruce Schneier. + + .. seealso:: + + Browser + a software application that enables a user to display and interact with text, images, and other information typically located on a web page at a website on the World Wide Web. + + .. seealso:: + + bzip2 + a free software/open source data compression algorithm and program developed by Julian Seward. + + .. seealso:: + + CGI + Common Gateway Interface is an important World Wide Web technology that + enables a client web browser to request data from a program executed on + the Web server. + + .. seealso:: + + Changelog + a log or record of changes made to a project. + + .. seealso:: + + Client + a computer system that accesses a (remote) service on another computer by some kind of network. + + .. seealso:: + + column + a set of data values of a particular simple type, one for each row of the table. + + .. seealso:: + + Cookie + a packet of information sent by a server to a World Wide Web browser and then sent back by the browser each time it accesses that server. + + .. seealso:: + + CSV + Comma- separated values + + .. seealso:: + + DB + look at :term:`database` + + database + an organized collection of data. + + .. seealso:: + + Engine + look at :term:`storage engines` + + extension + a PHP module that extends PHP with additional functionality. + + .. seealso:: + + FAQ + Frequently Asked Questions is a list of commonly asked question and there + answers. + + .. seealso:: + + Field + one part of divided data/columns. + + .. seealso:: + + foreign key + a column or group of columns in a database row that point to a key column + or group of columns forming a key of another database row in some + (usually different) table. + + .. seealso:: + + GD + Graphics Library by Thomas Boutell and others for dynamically manipulating images. + + .. seealso:: + + GD2 + look at :term:`gd` + + gzip + gzip is short for GNU zip, a GNU free software file compression program. + + .. seealso:: + + host + any machine connected to a computer network, a node that has a hostname. + + .. seealso:: + + hostname + the unique name by which a network attached device is known on a network. + + .. seealso:: + + HTTP + HyperText Transfer Protocol is the primary method used to transfer or + convey information on the World Wide Web. + + .. seealso:: + + https + a :term:`HTTP`-connection with additional security measures. + + .. seealso:: + + IEC + International Electrotechnical Commission + + IIS + Internet Information Services is a set of Internet-based services for + servers using Microsoft Windows. + + .. seealso:: + + Index + a feature that allows quick access to the rows in a table. + + .. seealso:: + + IP + Internet Protocol is a data-oriented protocol used by source and + destination hosts for communicating data across a packet-switched + internetwork. + + .. seealso:: + + IP Address + a unique number that devices use in order to identify and communicate with each other on a network utilizing the Internet Protocol standard. + + .. seealso:: + + IPv6 + IPv6 (Internet Protocol version 6) is the latest revision of the + Internet Protocol (:term:`IP`), designed to deal with the + long-anticipated problem of its precedessor IPv4 running out of addresses. + + .. seealso:: + + ISAPI + Internet Server Application Programming Interface is the API of Internet Information Services (IIS). + + .. seealso:: + + ISP + Internet service provider is a business or organization that offers users + access to the Internet and related services. + + .. seealso:: + + ISO + International Standards Organisation + + JPEG + a most commonly used standard method of lossy compression for photographic images. + + .. seealso:: + + JPG + look at :term:`jpeg` + + Key + look at :term:`index` + + LATEX + a document preparation system for the TEX typesetting program. + + .. seealso:: + + Mac + Apple Macintosh is line of personal computers is designed, developed, manufactured, and marketed by Apple Computer. + + .. seealso:: + + Mac OS X + the operating system which is included with all currently shipping Apple Macintosh computers in the consumer and professional markets. + + .. seealso:: + + mbstring + The PHP `mbstring` functions provide support for languages represented by multi-byte character sets, most notably UTF-8. + + If you have troubles installing this extension, please follow :ref:`faqmysql`, it provides useful hints. + + .. seealso:: + + MCrypt + a cryptographic library. + + .. seealso:: + + mcrypt + the MCrypt PHP extension. + + .. seealso:: + + MIME + Multipurpose Internet Mail Extensions is + an Internet Standard for the format of e-mail. + + .. seealso:: + + module + some sort of extension for the Apache Webserver. + + .. seealso:: + + mod_proxy_fcgi + an Apache module implmenting a Fast CGI interface; PHP can be run as a CGI module, FastCGI, or + directly as an Apache module. + + MySQL + a multithreaded, multi-user, SQL (Structured Query Language) Database Management System (DBMS). + + .. seealso:: + + mysqli + the improved MySQL client PHP extension. + + .. seealso:: + + mysql + the MySQL client PHP extension. + + .. seealso:: + + OpenDocument + open standard for office documents. + + .. seealso:: + + OS X + look at :term:`Mac OS X`. + + .. seealso:: + + PDF + Portable Document Format is a file format developed by Adobe Systems for + representing two dimensional documents in a device independent and + resolution independent format. + + .. seealso:: + + PEAR + the PHP Extension and Application Repository. + + .. seealso:: + + PCRE + Perl Compatible Regular Expressions is the perl-compatible regular + expression functions for PHP + + .. seealso:: + + PHP + short for "PHP: Hypertext Preprocessor", is an open-source, reflective + programming language used mainly for developing server-side applications + and dynamic web content, and more recently, a broader range of software + applications. + + .. seealso:: + + port + a connection through which data is sent and received. + + .. seealso:: + + primary key + A primary key is an index over one or more fields in a table with + unique values for each single row in this table. Every table should have + a primary key for easier accessing/identifying data in this table. There + can only be one primary key per table and it is named always **PRIMARY**. + In fact a primary key is just an :term:`unique key` with the name + **PRIMARY**. If no primary key is defined MySQL will use first *unique + key* as primary key if there is one. + + You can create the primary key when creating the table (in phpMyAdmin + just check the primary key radio buttons for each field you wish to be + part of the primary key). + + You can also add a primary key to an existing table with `ALTER` `TABLE` + or `CREATE` `INDEX` (in phpMyAdmin you can just click on 'add index' on + the table structure page below the listed fields). + + RFC + Request for Comments (RFC) documents are a series of memoranda + encompassing new research, innovations, and methodologies applicable to + Internet technologies. + + .. seealso:: + + RFC 1952 + GZIP file format specification version 4.3 + + .. seealso:: :rfc:`1952` + + Row (record, tuple) + represents a single, implicitly structured data item in a table. + + .. seealso:: + + Server + a computer system that provides services to other computing systems over a network. + + .. seealso:: + + Storage Engines + MySQL can use several different formats for storing data on disk, these + are called storage engines or table types. phpMyAdmin allows a user to + change their storage engine for a particular table through the operations + tab. + + Common table types are InnoDB and MyISAM, though many others exist and + may be desirable in some situations. + + .. seealso:: + + socket + a form of inter-process communication. + + .. seealso:: + + SSL + Secure Sockets Layer is a cryptographic protocol which provides secure + communication on the Internet. + + .. seealso:: + + Stored procedure + a subroutine available to applications accessing a relational database system + + .. seealso:: + + SQL + Structured Query Language + + .. seealso:: + + table + a set of data elements (cells) that is organized, defined and stored as + horizontal rows and vertical columns where each item can be uniquely + identified by a label or key or by it?s position in relation to other + items. + + .. seealso:: + + tar + a type of archive file format: the Tape ARchive format. + + .. seealso:: + + TCP + Transmission Control Protocol is one of the core protocols of the + Internet protocol suite. + + .. seealso:: + + TCPDF + PHP library to generate PDF files. + + .. seealso:: + + trigger + a procedural code that is automatically executed in response to certain events on a particular table or view in a database + + .. seealso:: + + unique key + An unique key is an index over one or more fields in a table which has a + unique value for each row. The first unique key will be treated as + :term:`primary key` if there is no *primary key* defined. + + URL + Uniform Resource Locator is a sequence of characters, conforming to a + standardized format, that is used for referring to resources, such as + documents and images on the Internet, by their location. + + .. seealso:: + + Webserver + A computer (program) that is responsible for accepting HTTP requests from clients and serving them Web pages. + + .. seealso:: + + XML + Extensible Markup Language is a W3C-recommended general- purpose markup + language for creating special-purpose markup languages, capable of + describing many different kinds of data. + + .. seealso:: + + ZIP + a popular data compression and archival format. + + .. seealso:: + + zlib + an open-source, cross- platform data compression library by Jean-loup Gailly and Mark Adler. + + .. seealso:: diff --git a/php/apps/phpmyadmin49/doc/html/_sources/import_export.txt b/php/apps/phpmyadmin49/doc/html/_sources/import_export.txt new file mode 100644 index 00000000..31f2fa9c --- /dev/null +++ b/php/apps/phpmyadmin49/doc/html/_sources/import_export.txt @@ -0,0 +1,350 @@ +Import and export +================= + +Import +++++++ + +To import data, go to the "Import" tab in phpMyAdmin. To import data into a +specific database or table, open the database or table before going to the +"Import" tab. + +In addition to the standard Import and Export tab, you can also import an SQL +file directly by dragging and dropping it from your local file manager to the +phpMyAdmin interface in your web browser. + +If you are having troubles importing big files, please consult :ref:`faq1_16`. + +You can import using following methods: + +Form based upload + + Can be used with any supported format, also (b|g)zipped files, e.g., mydump.sql.gz . + +Form based SQL Query + + Can be used with valid SQL dumps. + +Using upload directory + + You can specify an upload directory on your web server where phpMyAdmin is installed, after uploading your file into this directory you can select this file in the import dialog of phpMyAdmin, see :config:option:`$cfg['UploadDir']`. + +phpMyAdmin can import from several various commonly used formats. + +CSV +--- + +Comma separated values format which is often used by spreadsheets or various other programs for export/import. + +.. note:: + + When importing data into a table from a CSV file where the table has an + 'auto_increment' field, make the 'auto_increment' value for each record in + the CSV field to be '0' (zero). This allows the 'auto_increment' field to + populate correctly. + +It is now possible to import a CSV file at the server or database level. +Instead of having to create a table to import the CSV file into, a best-fit +structure will be determined for you and the data imported into it, instead. +All other features, requirements, and limitations are as before. + +CSV using LOAD DATA +------------------- + +Similar to CSV, only using the internal MySQL parser and not the phpMyAdmin one. + +ESRI Shape File +--------------- + +The ESRI shapefile or simply a shapefile is a popular geospatial vector data +format for geographic information systems software. It is developed and +regulated by Esri as a (mostly) open specification for data interoperability +among Esri and other software products. + +MediaWiki +--------- + +MediaWiki files, which can be exported by phpMyAdmin (version 4.0 or later), +can now also be imported. This is the format used by Wikipedia to display +tables. + +Open Document Spreadsheet (ODS) +------------------------------- + +OpenDocument workbooks containing one or more spreadsheets can now be directly imported. + +When importing an ODS speadsheet, the spreadsheet must be named in a specific way in order to make the +import as simple as possible. + +Table name +~~~~~~~~~~ + +During import, phpMyAdmin uses the sheet name as the table name; you should rename the +sheet in your spreadsheet program in order to match your existing table name (or the table you wish to create, +though this is less of a concern since you could quickly rename the new table from the Operations tab). + +Column names +~~~~~~~~~~~~ + +You should also make the first row of your spreadsheet a header with the names of the columns (this can be +accomplished by inserting a new row at the top of your spreadsheet). When on the Import screen, select the +checkbox for "The first line of the file contains the table column names;" this way your newly imported +data will go to the proper columns. + +.. note:: + + Formulas and calculations will NOT be evaluated, rather, their value from + the most recent save will be loaded. Please ensure that all values in the + spreadsheet are as needed before importing it. + +SQL +--- + +SQL can be used to make any manipulation on data, it is also useful for restoring backed up data. + +XML +--- + +XML files exported by phpMyAdmin (version 3.3.0 or later) can now be imported. +Structures (databases, tables, views, triggers, etc.) and/or data will be +created depending on the contents of the file. + +The supported xml schemas are not yet documented in this wiki. + +Export +++++++ + +phpMyAdmin can export into text files (even compressed) on your local disk (or +a special the webserver :config:option:`$cfg['SaveDir']` folder) in various +commonly used formats: + +CodeGen +------- + +`NHibernate `_ file format. Planned +versions: Java, Hibernate, PHP PDO, JSON, etc. So the preliminary name is +codegen. + +CSV +--- + +Comma separated values format which is often used by spreadsheets or various +other programs for export/import. + +CSV for Microsoft Excel +----------------------- + +This is just preconfigured version of CSV export which can be imported into +most English versions of Microsoft Excel. Some localised versions (like +"Danish") are expecting ";" instead of "," as field separator. + +Microsoft Word 2000 +------------------- + +If you're using Microsoft Word 2000 or newer (or compatible such as +OpenOffice.org), you can use this export. + +JSON +---- + +JSON (JavaScript Object Notation) is a lightweight data-interchange format. It +is easy for humans to read and write and it is easy for machines to parse and +generate. + +.. versionchanged:: 4.7.0 + + The generated JSON structure has been changed in phpMyAdmin 4.7.0 to + produce valid JSON data. + +The generated JSON is list of objects with following attributes: + +.. js:data:: type + + Type of given object, can be one of: + + ``header`` + Export header containing comment and phpMyAdmin version. + ``database`` + Start of a database marker, containing name of database. + ``table`` + Table data export. + +.. js:data:: version + + Used in ``header`` :js:data:`type` and indicates phpMyAdmin version. + +.. js:data:: comment + + Optional textual comment. + +.. js:data:: name + + Object name - either table or database based on :js:data:`type`. + +.. js:data:: database + + Database name for ``table`` :js:data:`type`. + +.. js:data:: data + + Table content for ``table`` :js:data:`type`. + +Sample output: + +.. code-block:: json + + [ + { + "comment": "Export to JSON plugin for PHPMyAdmin", + "type": "header", + "version": "4.7.0-dev" + }, + { + "name": "cars", + "type": "database" + }, + { + "data": [ + { + "car_id": "1", + "description": "Green Chrysler 300", + "make_id": "5", + "mileage": "113688", + "price": "13545.00", + "transmission": "automatic", + "yearmade": "2007" + } + ], + "database": "cars", + "name": "cars", + "type": "table" + }, + { + "data": [ + { + "make": "Chrysler", + "make_id": "5" + } + ], + "database": "cars", + "name": "makes", + "type": "table" + } + ] + +LaTeX +----- + +If you want to embed table data or structure in LaTeX, this is right choice for you. + +LaTeX is a typesetting system that is very suitable for producing scientific +and mathematical documents of high typographical quality. It is also suitable +for producing all sorts of other documents, from simple letters to complete +books. LaTeX uses TeX as its formatting engine. Learn more about TeX and +LaTeX on `the Comprehensive TeX Archive Network `_ +also see the `short description od TeX `_. + +The output needs to be embedded into a LaTeX document before it can be +rendered, for example in following document: + +.. code-block:: latex + + \documentclass{article} + \title{phpMyAdmin SQL output} + \author{} + \usepackage{longtable,lscape} + \date{} + \setlength{\parindent}{0pt} + \usepackage[left=2cm,top=2cm,right=2cm,nohead,nofoot]{geometry} + \pdfpagewidth 210mm + \pdfpageheight 297mm + \begin{document} + \maketitle + + % insert phpMyAdmin LaTeX Dump here + + \end{document} + +MediaWiki +--------- + +Both tables and databases can be exported in the MediaWiki format, which is +used by Wikipedia to display tables. It can export structure, data or both, +including table names or headers. + +OpenDocument Spreadsheet +------------------------ + +Open standard for spreadsheet data, which is being widely adopted. Many recent +spreadsheet programs, such as LibreOffice, OpenOffice, Microsoft Office or +Google Docs can handle this format. + +OpenDocument Text +----------------- + +New standard for text data which is being widely addopted. Most recent word +processors (such as LibreOffice, OpenOffice, Microsoft Word, AbiWord or KWord) +can handle this. + +PDF +--- + +For presentation purposes, non editable PDF might be best choice for you. + +PHP Array +--------- + +You can generate a php file which will declare a multidimensional array with +the contents of the selected table or database. + +SQL +--- + +Export in SQL can be used to restore your database, thus it is useful for +backing up. + +The option 'Maximal length of created query' seems to be undocumented. But +experiments has shown that it splits large extended INSERTS so each one is no +bigger than the given number of bytes (or characters?). Thus when importing the +file, for large tables you avoid the error "Got a packet bigger than +'max_allowed_packet' bytes". + +.. seealso:: + + https://dev.mysql.com/doc/refman/5.7/en/packet-too-large.html + +Data Options +~~~~~~~~~~~~ + +**Complete inserts** adds the column names to the SQL dump. This parameter +improves the readability and reliability of the dump. Adding the column names +increases the size of the dump, but when combined with Extended inserts it's +negligible. + +**Extended inserts** combines multiple rows of data into a single INSERT query. +This will significantly decrease filesize for large SQL dumps, increases the +INSERT speed when imported, and is generally recommended. + +.. seealso:: + + http://www.scriptalicious.com/blog/2009/04/complete-inserts-or-extended-inserts-in-phpmyadmin/ + +Texy! +----- + +`Texy! `_ markup format. You can see example on `Texy! demo +`_. + +XML +--- + +Easily parsable export for use with custom scripts. + +.. versionchanged:: 3.3.0 + + The XML schema used has changed as of version 3.3.0 + +YAML +---- + +YAML is a data serialization format which is both human readable and +computationally powerful ( ). diff --git a/php/apps/phpmyadmin49/doc/html/_sources/index.txt b/php/apps/phpmyadmin49/doc/html/_sources/index.txt new file mode 100644 index 00000000..f3372413 --- /dev/null +++ b/php/apps/phpmyadmin49/doc/html/_sources/index.txt @@ -0,0 +1,32 @@ +.. phpMyAdmin documentation master file, created by + sphinx-quickstart on Wed Sep 26 14:04:48 2012. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +Welcome to phpMyAdmin's documentation! +====================================== + +Contents: + +.. toctree:: + :maxdepth: 2 + + intro + require + setup + config + user + faq + developers + security + vendors + copyright + credits + glossary + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`search` +* :ref:`glossary` diff --git a/php/apps/phpmyadmin49/doc/html/_sources/intro.txt b/php/apps/phpmyadmin49/doc/html/_sources/intro.txt new file mode 100644 index 00000000..d81b00e0 --- /dev/null +++ b/php/apps/phpmyadmin49/doc/html/_sources/intro.txt @@ -0,0 +1,79 @@ +.. _intro: + +Introduction +============ + +phpMyAdmin is a free software tool written in PHP that is intended to handle the +administration of a MySQL or MariaDB database server. You can use phpMyAdmin to +perform most administration tasks, including creating a database, running queries, +and adding user accounts. + +Supported features +------------------ + +Currently phpMyAdmin can: + +* create, browse, edit, and drop databases, tables, views, columns, and indexes +* display multiple results sets through stored procedures or queries +* create, copy, drop, rename and alter databases, tables, columns and + indexes +* maintenance server, databases and tables, with proposals on server + configuration +* execute, edit and bookmark any :term:`SQL`-statement, even batch-queries +* load text files into tables +* create [#f1]_ and read dumps of tables +* export [#f1]_ data to various formats: :term:`CSV`, :term:`XML`, :term:`PDF`, + :term:`ISO`/:term:`IEC` 26300 - :term:`OpenDocument` Text and Spreadsheet, Microsoft + Word 2000, and LATEX formats +* import data and :term:`MySQL` structures from :term:`OpenDocument` spreadsheets, as + well as :term:`XML`, :term:`CSV`, and :term:`SQL` files +* administer multiple servers +* add, edit, and remove MySQL user accounts and privileges +* check referential integrity in MyISAM tables +* using Query-by-example (QBE), create complex queries automatically + connecting required tables +* create :term:`PDF` graphics of your + database layout +* search globally in a database or a subset of it +* transform stored data into any format using a set of predefined + functions, like displaying BLOB-data as image or download-link +* track changes on databases, tables and views +* support InnoDB tables and foreign keys +* support mysqli, the improved MySQL extension see :ref:`faq1_17` +* create, edit, call, export and drop stored procedures and functions +* create, edit, export and drop events and triggers +* communicate in `80 different languages + `_ + +Shortcut keys +------------- + +Currently phpMyAdmin supports following shortcuts: + +* k - Toggle console +* h - Go to home page +* s - Open settings +* d + s - Go to database structure (Provided you are in database related page) +* d + f - Search database (Provided you are in database related page) +* t + s - Go to table structure (Provided you are in table related page) +* t + f - Search table (Provided you are in table related page) +* backspace - Takes you to older page. + +A word about users +------------------ + +Many people have difficulty understanding the concept of user +management with regards to phpMyAdmin. When a user logs in to +phpMyAdmin, that username and password are passed directly to MySQL. +phpMyAdmin does no account management on its own (other than allowing +one to manipulate the MySQL user account information); all users must +be valid MySQL users. + +.. rubric:: Footnotes + +.. [#f1] + + phpMyAdmin can compress (:term:`Zip`, :term:`GZip` or :term:`RFC 1952` + formats) dumps and :term:`CSV` exports if you use PHP with + :term:`Zlib` support (``--with-zlib``). + Proper support may also need changes in :file:`php.ini`. diff --git a/php/apps/phpmyadmin49/doc/html/_sources/other.txt b/php/apps/phpmyadmin49/doc/html/_sources/other.txt new file mode 100644 index 00000000..98d1dca1 --- /dev/null +++ b/php/apps/phpmyadmin49/doc/html/_sources/other.txt @@ -0,0 +1,32 @@ +Other sources of information +============================ + +Printed Book +------------ + +The definitive guide to using phpMyAdmin is the book Mastering phpMyAdmin for +Effective MySQL Management by Marc Delisle. You can get information on that +book and other officially endorsed `books at the phpMyAdmin site`_. + +.. _books at the phpMyAdmin site: https://www.phpmyadmin.net/docs/ + +Tutorials +--------- + +Third party tutorials and articles which you might find interesting: + +Česky (Czech) ++++++++++++++ + +- `Seriál o phpMyAdminovi `_ + +English ++++++++ + +- `Having fun with phpMyAdmin's MIME-transformations & PDF-features `_ +- `Learning SQL Using phpMyAdmin (old tutorial) `_ + +Русский (Russian) ++++++++++++++++++ + +* `Russian server about phpMyAdmin `_ diff --git a/php/apps/phpmyadmin49/doc/html/_sources/privileges.txt b/php/apps/phpmyadmin49/doc/html/_sources/privileges.txt new file mode 100644 index 00000000..f3b17791 --- /dev/null +++ b/php/apps/phpmyadmin49/doc/html/_sources/privileges.txt @@ -0,0 +1,74 @@ +User management +=============== + +User management is the process of controlling which users are allowed to +connect to the MySQL server and what permissions they have on each database. +phpMyAdmin does not handle user management, rather it passes the username and +password on to MySQL, which then determines whether a user is permitted to +perform a particular action. Within phpMyAdmin, administrators have full +control over creating users, viewing and editing privileges for existing users, +and removing users. + +Within phpMyAdmin, user management is controlled via the :guilabel:`Users` link +from the main page. Users can be created, edited, and removed. + +Creating a new user +------------------- + +To create a new user, click the :guilabel:`Add a new user` link near the bottom +of the :guilabel:`Users` page (you must be a "superuser", e.g., user "root"). +Use the textboxes and drop-downs to configure the user to your particular +needs. You can then select whether to create a database for that user and grant +specific global privileges. Once you've created the user (by clicking Go), you +can define that user's permissions on a specific database (don't grant global +privileges in that case). In general, users do not need any global privileges +(other than USAGE), only permissions for their specific database. + +Editing an existing user +------------------------ + +To edit an existing user, simply click the pencil icon to the right of that +user in the :guilabel:`Users` page. You can then edit their global- and +database-specific privileges, change their password, or even copy those +privileges to a new user. + +Deleting a user +--------------- + +From the :guilabel:`Users` page, check the checkbox for the user you wish to +remove, select whether or not to also remove any databases of the same name (if +they exist), and click Go. + +Assigning privileges to user for a specific database +---------------------------------------------------- + +Users are assigned to databases by editing the user record (from the +:guilabel:`User accounts` link on the home page). +If you are creating a user specifically for a given table +you will have to create the user first (with no global privileges) and then go +back and edit that user to add the table and privileges for the individual +table. + +.. _configurablemenus: + +Configurable menus and user groups +---------------------------------- + +By enabling :config:option:`$cfg['Servers'][$i]['usergroups']` and +:config:option:`$cfg['Servers'][$i]['usergroups']` you can customize what users +will see in the phpMyAdmin navigation. + +.. warning:: + + This feature only limits what a user sees, he is still able to use all the + functions. So this can not be considered as a security limitation. Should + you want to limit what users can do, use MySQL privileges to achieve that. + +With this feature enabled, the :guilabel:`User accounts` management interface gains +a second tab for managing :guilabel:`User groups`, where you can define what each +group will view (see image below) and you can then assign each user to one of +these groups. Users will be presented with a simplified user interface, which might be +useful for inexperienced users who could be overwhelmed by all the features +phpMyAdmin provides. + +.. image:: images/usergroups.png diff --git a/php/apps/phpmyadmin49/doc/html/_sources/relations.txt b/php/apps/phpmyadmin49/doc/html/_sources/relations.txt new file mode 100644 index 00000000..96643dee --- /dev/null +++ b/php/apps/phpmyadmin49/doc/html/_sources/relations.txt @@ -0,0 +1,84 @@ +.. _relations: + +Relations +========= + +phpMyAdmin allows relationships (similar to foreign keys) using MySQL-native +(InnoDB) methods when available and falling back on special phpMyAdmin-only +features when needed. There are two ways of editing these relations, with the +*relation view* and the drag-and-drop *designer* -- both of which are explained +on this page. + +.. note:: + + You need to have configured the :ref:`linked-tables` for using phpMyAdmin + only relations. + +Technical info +-------------- + +Currently the only MySQL table type that natively supports relationships is +InnoDB. When using an InnoDB table, phpMyAdmin will create real InnoDB +relations which will be enforced by MySQL no matter which application accesses +the database. In the case of any other table type, phpMyAdmin enforces the +relations internally and those relations are not applied to any other +application. + +Relation view +------------- + +In order to get it working, you first have to properly create the +[[pmadb|pmadb]]. Once that is setup, select a table's "Structure" page. Below +the table definition, a link called "Relation view" is shown. If you click that +link, a page will be shown that offers you to create a link to another table +for any (most) fields. Only PRIMARY KEYS are shown there, so if the field you +are referring to is not shown, you most likely are doing something wrong. The +drop-down at the bottom is the field which will be used as the name for a +record. + +Relation view example ++++++++++++++++++++++ + +.. image:: images/pma-relations-relation-view-link.png + +.. image:: images/pma-relations-relation-link.png + +Let's say you have categories and links and one category can contain several links. Your table structure would be something like this: + +- `category.category_id` (must be unique) +- `category.name` +- `link.link_id` +- `link.category_id` +- `link.uri`. + +Open the relation view (below the table structure) page for the `link` table and for `category_id` field, you select `category.category_id` as master record. + +If you now browse the link table, the `category_id` field will be a clickable hyperlink to the proper category record. But all you see is just the `category_id`, not the name of the category. + +.. image:: images/pma-relations-relation-name.png + +To fix this, open the relation view of the `category` table and in the drop down at the bottom, select "name". If you now browse the link table again and hover the mouse over the `category_id` hyperlink, the value from the related category will be shown as tooltip. + +.. image:: images/pma-relations-links.png + +Designer +-------- + +The Designer feature is a graphical way of creating, editing, and displaying +phpMyAdmin relations. These relations are compatible with those created in +phpMyAdmin's relation view. + +To use this feature, you need a properly configured :ref:`linked-tables` and +must have the :config:option:`$cfg['Servers'][$i]['table_coords']` configured. + +To use the designer, select a database's structure page, then look for the +:guilabel:`Designer` tab. + +To export the view into PDF, you have to create PDF pages first. The Designer +creates the layout, how the tables shall be displayed. To finally export the +view, you have to create this with a PDF page and select your layout, which you +have created with the designer. + +.. seealso:: + + :ref:`faqpdf` diff --git a/php/apps/phpmyadmin49/doc/html/_sources/require.txt b/php/apps/phpmyadmin49/doc/html/_sources/require.txt new file mode 100644 index 00000000..e9a48e50 --- /dev/null +++ b/php/apps/phpmyadmin49/doc/html/_sources/require.txt @@ -0,0 +1,60 @@ +.. _require: + +Requirements +============ + +Web server +---------- + +Since phpMyAdmin's interface is based entirely in your browser, you'll need a +web server (such as Apache, nginx, :term:`IIS`) to install phpMyAdmin's files into. + +PHP +--- + +* You need PHP 5.5.0 or newer, with ``session`` support, the Standard PHP Library + (SPL) extension, hash, ctype, and JSON support. + +* The ``mbstring`` extension (see :term:`mbstring`) is strongly recommended + for performance reasons. + +* To support uploading of ZIP files, you need the PHP ``zip`` extension. + +* You need GD2 support in PHP to display inline thumbnails of JPEGs + ("image/jpeg: inline") with their original aspect ratio. + +* When using the cookie authentication (the default), the `openssl + `_ extension is strongly suggested. + +* To support upload progress bars, see :ref:`faq2_9`. + +* To support XML and Open Document Spreadsheet importing, you need the + `libxml `_ extension. + +* To support reCAPTCHA on the login page, you need the + `openssl `_ extension. + +* To support displaying phpMyAdmin's latest version, you need to enable + ``allow_url_open`` in your :file:`php.ini` or to have the + `curl `_ extension. + +.. seealso:: :ref:`faq1_31`, :ref:`authentication_modes` + +Database +-------- + +phpMyAdmin supports MySQL-compatible databases. + +* MySQL 5.5 or newer +* MariaDB 5.5 or newer + +.. seealso:: :ref:`faq1_17` + +Web browser +----------- + +To access phpMyAdmin you need a web browser with cookies and JavaScript +enabled. + +You need browser which is supported by jQuery 2.0, see +. diff --git a/php/apps/phpmyadmin49/doc/html/_sources/security.txt b/php/apps/phpmyadmin49/doc/html/_sources/security.txt new file mode 100644 index 00000000..d6d93ec0 --- /dev/null +++ b/php/apps/phpmyadmin49/doc/html/_sources/security.txt @@ -0,0 +1,113 @@ +Security policy +=============== + +The phpMyAdmin developer team is putting lot of effort to make phpMyAdmin as +secure as possible. But still web application like phpMyAdmin can be vulnerable +to a number of attacks and new ways to exploit are still being explored. + +For every reported vulnerability we issue a phpMyAdmin Security Announcement +(PMASA) and it get's assigne CVE ID as well. We might group similar +vulnerabilities to one PMASA (eg. multiple XSS vulnerabilities can be announced +under one PMASA). + +If you think you've found a vulnerability, please see :ref:`reporting-security`. + +Typical vulnerabilities +----------------------- + +In this secion, we will describe typical vulnerabilities, which can appear in +our code base. This list is by no means complete, it is intended to show +typical attack surface. + +Cross-site scripting (XSS) +++++++++++++++++++++++++++ + +When phpMyAdmin shows a piece of user data, e.g. something inside a user's +database, all html special chars have to be escaped. When this escaping is +missing somewhere a malicious user might fill a database with specially crafted +content to trick an other user of that database into executing something. This +could for example be a piece of JavaScript code that would do any number of +nasty things. + +phpMyAdmin tries to escape all userdata before it is rendered into html for the +browser. + +.. seealso:: + + `Cross-site scripting on Wikipedia `_ + +Cross-site request forgery (CSRF) ++++++++++++++++++++++++++++++++++ + +An attacker would trick a phpMyAdmin user into clicking on a link to provoke +some action in phpMyAdmin. This link could either be sent via email or some +random website. If successful this the attacker would be able to perform some +action with the users privileges. + +To mitigate this phpMyAdmin requires a token to be sent on sensitive requests. +The idea is that an attacker does not poses the currently valid token to +include in the presented link. + +The token is regenerated for every login, so it's generally valid only for +limited time, what makes it harder for attacker to obtain valid one. + +.. seealso:: + + `Cross-site request forgery on Wikipedia `_ + +SQL injection ++++++++++++++ + +As the whole purpose of phpMyAdmin is to preform sql queries, this is not our +first concern. SQL injection is sensitive to us though when it concerns the +mysql control connection. This controlconnection can have additional privileges +which the logged in user does not poses. E.g. access the :ref:`linked-tables`. + +User data that is included in (administrative) queries should always be run +through DatabaseInterface::escapeSring(). + +.. seealso:: + + `SQL injection on Wikipedia `_ + +Brute force attack +++++++++++++++++++ + +phpMyAdmin on its own does not rate limit authentication attempts in any way. +This is caused by need to work in stateless environment, where there is no way +to protect against such kind of things. + +To mitigate this, you can use Captcha or utilize external tools such as +fail2ban, this is more details described in :ref:`securing`. + +.. seealso:: + + `Brute force attack on Wikipedia `_ + +.. _reporting-security: + +Reporting security issues +------------------------- + +Should you find a security issue in the phpMyAdmin programming code, please +contact the `phpMyAdmin security team `_ in +advance before publishing it. This way we can prepare a fix and release the fix together with your +announcement. You will be also given credit in our security announcement. +You can optionally encrypt your report with PGP key ID +``DA68AB39218AB947`` with following fingerprint: + +.. code-block:: console + + pub 4096R/DA68AB39218AB947 2016-08-02 + Key fingerprint = 5BAD 38CF B980 50B9 4BD7 FB5B DA68 AB39 218A B947 + uid phpMyAdmin Security Team <security@phpmyadmin.net> + sub 4096R/5E4176FB497A31F7 2016-08-02 + +The key can be either obtained from the keyserver or is available in +`phpMyAdmin keyring `_ +available on our download server or using `Keybase `_. + +Should you have suggestion on improving phpMyAdmin to make it more secure, please +report that to our `issue tracker `_. +Existing improvement suggestions can be found by +`hardening label `_. diff --git a/php/apps/phpmyadmin49/doc/html/_sources/settings.txt b/php/apps/phpmyadmin49/doc/html/_sources/settings.txt new file mode 100644 index 00000000..be36b80d --- /dev/null +++ b/php/apps/phpmyadmin49/doc/html/_sources/settings.txt @@ -0,0 +1,21 @@ +Configuring phpMyAdmin +---------------------- + +There are many configuration settings that can be used to customize the +interface. Those settings are described in +:ref:`config`. There are several layers of the configuration. + +The global settings can be configured in :file:`config.inc.php` as described in +:ref:`config`. This is only way to configure connections to databases and other +system wide settings. + +On top of this there are user settings which can be persistently stored in +:ref:`linked-tables`, possibly automatically configured through +:ref:`zeroconf`. If the :ref:`linked-tables` are not configured, the settings +are temporarily stored in the session data; these are valid only until you +logout. + +You can also save the user configuration for further use, either download them +as a file or to the browser local storage. You can find both those options in +the :guilabel:`Settings` tab. The settings stored in browser local storage will +be automatically offered for loading upon your login to phpMyAdmin. diff --git a/php/apps/phpmyadmin49/doc/html/_sources/setup.txt b/php/apps/phpmyadmin49/doc/html/_sources/setup.txt new file mode 100644 index 00000000..36e56c0e --- /dev/null +++ b/php/apps/phpmyadmin49/doc/html/_sources/setup.txt @@ -0,0 +1,1117 @@ +.. _setup: + +Installation +============ + +phpMyAdmin does not apply any special security methods to the MySQL +database server. It is still the system administrator's job to grant +permissions on the MySQL databases properly. phpMyAdmin's :guilabel:`Users` +page can be used for this. + +.. warning:: + + :term:`Mac` users should note that if you are on a version before + :term:`Mac OS X`, StuffIt unstuffs with :term:`Mac` formats. So you'll have + to resave as in BBEdit to Unix style ALL phpMyAdmin scripts before + uploading them to your server, as PHP seems not to like :term:`Mac`-style + end of lines character ("``\r``"). + +Linux distributions ++++++++++++++++++++ + +phpMyAdmin is included in most Linux distributions. It is recommended to use +distribution packages when possible - they usually provide integration to your +distribution and you will automatically get security updates from your distribution. + +.. _debian-package: + +Debian and Ubuntu +----------------- + +Debian's package repositories include a phpMyAdmin package, but be aware that +the configuration file is maintained in ``/etc/phpmyadmin`` and may differ in +some ways from the official phpMyAdmin documentation. Specifically it does: + +* Configuration of web server (works for Apache and lighttpd). +* Creating of :ref:`linked-tables` using dbconfig-common. +* Securing setup script, see :ref:`debian-setup`. + +.. seealso:: + + More information can be found in `README.Debian `_ + (it is installed as :file:`/usr/share/doc/phmyadmin/README.Debian` with the package). + +OpenSUSE +-------- + +OpenSUSE already comes with phpMyAdmin package, just install packages from +the `openSUSE Build Service `_. + +Gentoo +------ + +Gentoo ships the phpMyAdmin package, both in a near stock configuration as well +as in a ``webapp-config`` configuration. Use ``emerge dev-db/phpmyadmin`` to +install. + +Mandriva +-------- + +Mandriva ships the phpMyAdmin package in their ``contrib`` branch and can be +installed via the usual Control Center. + +Fedora +------ + +Fedora ships the phpMyAdmin package, but be aware that the configuration file +is maintained in ``/etc/phpMyAdmin/`` and may differ in some ways from the +official phpMyAdmin documentation. + +Red Hat Enterprise Linux +------------------------ + +Red Hat Enterprise Linux itself and thus derivatives like CentOS don't +ship phpMyAdmin, but the Fedora-driven repository +`Extra Packages for Enterprise Linux (EPEL) `_ +is doing so, if it's +`enabled `_. +But be aware that the configuration file is maintained in +``/etc/phpMyAdmin/`` and may differ in some ways from the +official phpMyAdmin documentation. + +Installing on Windows ++++++++++++++++++++++ + +The easiest way to get phpMyAdmin on Windows is using third party products +which include phpMyAdmin together with a database and web server such as +`XAMPP `_. + +You can find more of such options at `Wikipedia `_. + +Installing from Git ++++++++++++++++++++ + +You can clone current phpMyAdmin source from +``https://github.com/phpmyadmin/phpmyadmin.git``: + +.. code-block:: sh + + git clone https://github.com/phpmyadmin/phpmyadmin.git + +Additionally you need to install dependencies using the `Composer tool`_: + +.. code-block:: sh + + composer update + +If you do not intend to develop, you can skip the installation of developer tools +by invoking: + +.. code-block:: sh + + composer update --no-dev + +.. _composer: + +Installing using Composer ++++++++++++++++++++++++++ + +You can install phpMyAdmin using the `Composer tool`_, since 4.7.0 the releases +are automatically mirrored to the default `Packagist`_ repository. + +.. note:: + + The content of the Composer repository is automatically generated + separately from the releases, so the content doesn't have to be + 100% same as when you download the tarball. There should be no + functional differences though. + +To install phpMyAdmin simply run: + +.. code-block:: sh + + composer create-project phpmyadmin/phpmyadmin + +Alternatively you can use our own composer repository, which contains +the release tarballs and is available at +: + +.. code-block:: sh + + composer create-project phpmyadmin/phpmyadmin --repository-url=https://www.phpmyadmin.net/packages.json --no-dev + +.. _docker: + +Installing using Docker ++++++++++++++++++++++++ + +phpMyAdmin comes with a `Docker image`_, which you can easily deploy. You can +download it using: + +.. code-block:: sh + + docker pull phpmyadmin/phpmyadmin + +The phpMyAdmin server will listen on port 80. It supports several ways of +configuring the link to the database server, either by Docker's link feature +by linking your database container to ``db`` for phpMyAdmin (by specifying +``--link your_db_host:db``) or by environment variables (in this case it's up +to you to set up networking in Docker to allow the phpMyAdmin container to access +the database container over network). + +.. _docker-vars: + +Docker environment variables +---------------------------- + +You can configure several phpMyAdmin features using environment variables: + +.. envvar:: PMA_ARBITRARY + + Allows you to enter a database server hostname on login form. + + .. seealso:: :config:option:`$cfg['AllowArbitraryServer']` + +.. envvar:: PMA_HOST + + Host name or IP address of the database server to use. + + .. seealso:: :config:option:`$cfg['Servers'][$i]['host']` + +.. envvar:: PMA_HOSTS + + Comma-separated host names or IP addresses of the database servers to use. + + .. note:: Used only if :envvar:`PMA_HOST` is empty. + +.. envvar:: PMA_VERBOSE + + Verbose name of the database server. + + .. seealso:: :config:option:`$cfg['Servers'][$i]['verbose']` + +.. envvar:: PMA_VERBOSES + + Comma-separated verbose name of the database servers. + + .. note:: Used only if :envvar:`PMA_VERBOSE` is empty. + +.. envvar:: PMA_USER + + User name to use for :ref:`auth_config`. + +.. envvar:: PMA_PASSWORD + + Password to use for :ref:`auth_config`. + +.. envvar:: PMA_PORT + + Port of the database server to use. + +.. envvar:: PMA_PORTS + + Comma-separated ports of the database server to use. + + .. note:: Used only if :envvar:`PMA_PORT` is empty. + +.. envvar:: PMA_ABSOLUTE_URI + + The fully-qualified path (``https://pma.example.net/``) where the reverse + proxy makes phpMyAdmin available. + + .. seealso:: :config:option:`$cfg['PmaAbsoluteUri']` + +By default, :ref:`cookie` is used, but if :envvar:`PMA_USER` and +:envvar:`PMA_PASSWORD` are set, it is switched to :ref:`auth_config`. + +.. note:: + + The credentials you need to log in are stored in the MySQL server, in case + of Docker image there are various ways to set it (for example + :samp:`MYSQL_ROOT_PASSWORD` when starting the MySQL container). Please check + documentation for `MariaDB container `_ + or `MySQL container `_. + +.. _docker-custom: + +Customizing configuration +------------------------- + +Additionally configuration can be tweaked by :file:`/etc/phpmyadmin/config.user.inc.php`. If +this file exists, it will be loaded after configuration is generated from above +environment variables, so you can override any configuration variable. This +configuration can be added as a volume when invoking docker using +`-v /some/local/directory/config.user.inc.php:/etc/phpmyadmin/config.user.inc.php` parameters. + +Note that the supplied configuration file is applied after :ref:`docker-vars`, +but you can override any of the values. + +For example to change default behaviour of CSV export you can use following +configuration file: + +.. code-block:: php + + + +You can also use it to define server configuration instead of using the +environment variables listed in :ref:`docker-vars`: + +.. code-block:: php + + [ + 'auth_type' => 'cookie', + 'host' => 'mydb1', + 'port' => 3306, + 'verbose' => 'Verbose name 1', + ], + 2 => [ + 'auth_type' => 'cookie', + 'host' => 'mydb2', + 'port' => 3306, + 'verbose' => 'Verbose name 2', + ], + ]; + +.. seealso:: + + See :ref:`config` for detailed description of configuration options. + +Docker Volumes +-------------- + +You can use following volumes to customize image behavior: + +:file:`/etc/phpmyadmin/config.user.inc.php` + + Can be used for additional settings, see previous chapter for more details. + +:file:`/sessions/` + + Directory where PHP sessions are stored. You might want to share this + for example when using :ref:`auth_signon`. + +:file:`/www/themes/` + + Directory where phpMyAdmin looks for themes. By default only those shipped + with phpMyAdmin are included, but you can include additional phpMyAdmin + themes (see :ref:`themes`) by using Docker volumes. + +Docker Examples +--------------- + +To connect phpMyAdmin to a given server use: + +.. code-block:: sh + + docker run --name myadmin -d -e PMA_HOST=dbhost -p 8080:80 phpmyadmin/phpmyadmin + +To connect phpMyAdmin to more servers use: + +.. code-block:: sh + + docker run --name myadmin -d -e PMA_HOSTS=dbhost1,dbhost2,dbhost3 -p 8080:80 phpmyadmin/phpmyadmin + +To use arbitrary server option: + +.. code-block:: sh + + docker run --name myadmin -d --link mysql_db_server:db -p 8080:80 -e PMA_ARBITRARY=1 phpmyadmin/phpmyadmin + +You can also link the database container using Docker: + +.. code-block:: sh + + docker run --name phpmyadmin -d --link mysql_db_server:db -p 8080:80 phpmyadmin/phpmyadmin + +Running with additional configuration: + +.. code-block:: sh + + docker run --name phpmyadmin -d --link mysql_db_server:db -p 8080:80 -v /some/local/directory/config.user.inc.php:/etc/phpmyadmin/config.user.inc.php phpmyadmin/phpmyadmin + +Running with additional themes: + +.. code-block:: sh + + docker run --name phpmyadmin -d --link mysql_db_server:db -p 8080:80 -v /custom/phpmyadmin/theme/:/www/themes/theme/ phpmyadmin/phpmyadmin + +Using docker-compose +-------------------- + +Alternatively you can also use docker-compose with the docker-compose.yml from +. This will run phpMyAdmin with an +arbitrary server - allowing you to specify MySQL/MariaDB server on login page. + +.. code-block:: sh + + docker-compose up -d + +Customizing configuration file using docker-compose +--------------------------------------------------- + +You can use an external file to customize phpMyAdmin configuration and pass it +using the volumes directive: + +.. code-block:: yaml + + phpmyadmin: + image: phpmyadmin/phpmyadmin + container_name: phpmyadmin + environment: + - PMA_ARBITRARY=1 + restart: always + ports: + - 8080:80 + volumes: + - /sessions + - ~/docker/phpmyadmin/config.user.inc.php:/etc/phpmyadmin/config.user.inc.php + - /custom/phpmyadmin/theme/:/www/themes/theme/ + +.. seealso:: :ref:`docker-custom` + +Running behind haproxy in a subdirectory +---------------------------------------- + +When you want to expose phpMyAdmin running in a Docker container in a +subdirectory, you need to rewrite the request path in the server proxying the +requests. + +For example using haproxy it can be done as: + +.. code-block:: text + + frontend http + bind *:80 + option forwardfor + option http-server-close + + ### NETWORK restriction + acl LOCALNET src 10.0.0.0/8 192.168.0.0/16 172.16.0.0/12 + + # /phpmyadmin + acl phpmyadmin path_dir /phpmyadmin + use_backend phpmyadmin if phpmyadmin LOCALNET + + backend phpmyadmin + mode http + + reqirep ^(GET|POST|HEAD)\ /phpmyadmin/(.*) \1\ /\2 + + # phpMyAdmin container IP + server localhost 172.30.21.21:80 + +When using traefik, something like following should work: + +.. code-block:: text + + defaultEntryPoints = ["http"] + [entryPoints] + [entryPoints.http] + address = ":80" + [entryPoints.http.redirect] + regex = "(http:\\/\\/[^\\/]+\\/([^\\?\\.]+)[^\\/])$" + replacement = "$1/" + + [backends] + [backends.myadmin] + [backends.myadmin.servers.myadmin] + url="http://internal.address.to.pma" + + [frontends] + [frontends.myadmin] + backend = "myadmin" + passHostHeader = true + [frontends.myadmin.routes.default] + rule="PathPrefixStrip:/phpmyadmin/;AddPrefix:/" + +You then should specify :envvar:`PMA_ABSOLUTE_URI` in the docker-compose +configuration: + +.. code-block:: yaml + + version: '2' + + services: + phpmyadmin: + restart: always + image: phpmyadmin/phpmyadmin + container_name: phpmyadmin + hostname: phpmyadmin + domainname: example.com + ports: + - 8000:80 + environment: + - PMA_HOSTS=172.26.36.7,172.26.36.8,172.26.36.9,172.26.36.10 + - PMA_VERBOSES=production-db1,production-db2,dev-db1,dev-db2 + - PMA_USER=root + - PMA_PASSWORD= + - PMA_ABSOLUTE_URI=http://example.com/phpmyadmin/ + +.. _quick_install: + +Quick Install ++++++++++++++ + +#. Choose an appropriate distribution kit from the phpmyadmin.net + Downloads page. Some kits contain only the English messages, others + contain all languages. We'll assume you chose a kit whose name + looks like ``phpMyAdmin-x.x.x -all-languages.tar.gz``. +#. Ensure you have downloaded a genuine archive, see :ref:`verify`. +#. Untar or unzip the distribution (be sure to unzip the subdirectories): + ``tar -xzvf phpMyAdmin_x.x.x-all-languages.tar.gz`` in your + webserver's document root. If you don't have direct access to your + document root, put the files in a directory on your local machine, + and, after step 4, transfer the directory on your web server using, + for example, ftp. +#. Ensure that all the scripts have the appropriate owner (if PHP is + running in safe mode, having some scripts with an owner different from + the owner of other scripts will be a problem). See :ref:`faq4_2` and + :ref:`faq1_26` for suggestions. +#. Now you must configure your installation. There are two methods that + can be used. Traditionally, users have hand-edited a copy of + :file:`config.inc.php`, but now a wizard-style setup script is provided + for those who prefer a graphical installation. Creating a + :file:`config.inc.php` is still a quick way to get started and needed for + some advanced features. + +Manually creating the file +-------------------------- + +To manually create the file, simply use your text editor to create the +file :file:`config.inc.php` (you can copy :file:`config.sample.inc.php` to get +a minimal configuration file) in the main (top-level) phpMyAdmin +directory (the one that contains :file:`index.php`). phpMyAdmin first +loads :file:`libraries/config.default.php` and then overrides those values +with anything found in :file:`config.inc.php`. If the default value is +okay for a particular setting, there is no need to include it in +:file:`config.inc.php`. You'll probably need only a few directives to get going; a +simple configuration may look like this: + +.. code-block:: xml+php + + + +Or, if you prefer to not be prompted every time you log in: + +.. code-block:: xml+php + + + +.. warning:: + + Storing passwords in the configuration is insecure as anybody can then + manipulate your database. + +For a full explanation of possible configuration values, see the +:ref:`config` of this document. + +.. index:: Setup script + +.. _setup_script: + +Using Setup script +------------------ + +Instead of manually editing :file:`config.inc.php`, you can use phpMyAdmin's +setup feature. The file can be generated using the setup and you can download it +for upload to the server. + +Next, open your browser and visit the location where you installed phpMyAdmin, +with the ``/setup`` suffix. The changes are not saved to the server, you need to +use the :guilabel:`Download` button to save them to your computer and then upload +to the server. + +Now the file is ready to be used. You can choose to review or edit the +file with your favorite editor, if you prefer to set some advanced +options which the setup script does not provide. + +#. If you are using the ``auth_type`` "config", it is suggested that you + protect the phpMyAdmin installation directory because using config + does not require a user to enter a password to access the phpMyAdmin + installation. Use of an alternate authentication method is + recommended, for example with HTTP–AUTH in a :term:`.htaccess` file or switch to using + ``auth_type`` cookie or http. See the :ref:`faqmultiuser` + for additional information, especially :ref:`faq4_4`. +#. Open the main phpMyAdmin directory in your browser. + phpMyAdmin should now display a welcome screen and your databases, or + a login dialog if using :term:`HTTP` or + cookie authentication mode. + +.. _debian-setup: + +Setup script on Debian, Ubuntu and derivatives +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Debian and Ubuntu have changed way how setup is enabled and disabled, in a way +that single command has to be executed for either of these. + +To allow editing configuration invoke: + +.. code-block:: sh + + /usr/sbin/pma-configure + +To block editing configuration invoke: + +.. code-block:: sh + + /usr/sbin/pma-secure + +Setup script on openSUSE +~~~~~~~~~~~~~~~~~~~~~~~~ + +Some openSUSE releases do not include setup script in the package. In case you +want to generate configuration on these you can either download original +package from or use setup script on our demo +server: . + +.. _verify: + +Verifying phpMyAdmin releases ++++++++++++++++++++++++++++++ + +Since July 2015 all phpMyAdmin releases are cryptographically signed by the +releasing developer, who through January 2016 was Marc Delisle. His key id is +0xFEFC65D181AF644A, his PGP fingerprint is: + +.. code-block:: console + + 436F F188 4B1A 0C3F DCBF 0D79 FEFC 65D1 81AF 644A + +and you can get more identification information from . + +Beginning in January 2016, the release manager is Isaac Bennetch. His key id is +0xCE752F178259BD92, and his PGP fingerprint is: + +.. code-block:: console + + 3D06 A59E CE73 0EB7 1B51 1C17 CE75 2F17 8259 BD92 + +and you can get more identification information from . + +Some additional downloads (for example themes) might be signed by Michal Čihař. His key id is +0x9C27B31342B7511D, and his PGP fingerprint is: + +.. code-block:: console + + 63CB 1DF1 EF12 CF2A C0EE 5A32 9C27 B313 42B7 511D + +and you can get more identification information from . + +You should verify that the signature matches the archive you have downloaded. +This way you can be sure that you are using the same code that was released. +You should also verify the date of the signature to make sure that you +downloaded the latest version. + +Each archive is accompanied with ``.asc`` files which contains the PGP signature +for it. Once you have both of them in the same folder, you can verify the signature: + +.. code-block:: console + + $ gpg --verify phpMyAdmin-4.5.4.1-all-languages.zip.asc + gpg: Signature made Fri 29 Jan 2016 08:59:37 AM EST using RSA key ID 8259BD92 + gpg: Can't check signature: public key not found + +As you can see gpg complains that it does not know the public key. At this +point you should do one of the following steps: + +* Download the keyring from `our download server `_, then import it with: + +.. code-block:: console + + $ gpg --import phpmyadmin.keyring + +* Download and import the key from one of the key servers: + +.. code-block:: console + + $ gpg --keyserver hkp://pgp.mit.edu --recv-keys 3D06A59ECE730EB71B511C17CE752F178259BD92 + gpg: requesting key 8259BD92 from hkp server pgp.mit.edu + gpg: key 8259BD92: public key "Isaac Bennetch " imported + gpg: no ultimately trusted keys found + gpg: Total number processed: 1 + gpg: imported: 1 (RSA: 1) + +This will improve the situation a bit - at this point you can verify that the +signature from the given key is correct but you still can not trust the name used +in the key: + +.. code-block:: console + + $ gpg --verify phpMyAdmin-4.5.4.1-all-languages.zip.asc + gpg: Signature made Fri 29 Jan 2016 08:59:37 AM EST using RSA key ID 8259BD92 + gpg: Good signature from "Isaac Bennetch " + gpg: aka "Isaac Bennetch " + gpg: WARNING: This key is not certified with a trusted signature! + gpg: There is no indication that the signature belongs to the owner. + Primary key fingerprint: 3D06 A59E CE73 0EB7 1B51 1C17 CE75 2F17 8259 BD92 + +The problem here is that anybody could issue the key with this name. You need to +ensure that the key is actually owned by the mentioned person. The GNU Privacy +Handbook covers this topic in the chapter `Validating other keys on your public +keyring`_. The most reliable method is to meet the developer in person and +exchange key fingerprints, however you can also rely on the web of trust. This way +you can trust the key transitively though signatures of others, who have met +the developer in person. For example you can see how `Isaac's key links to +Linus's key`_. + +Once the key is trusted, the warning will not occur: + +.. code-block:: console + + $ gpg --verify phpMyAdmin-4.5.4.1-all-languages.zip.asc + gpg: Signature made Fri 29 Jan 2016 08:59:37 AM EST using RSA key ID 8259BD92 + gpg: Good signature from "Isaac Bennetch " [full] + +Should the signature be invalid (the archive has been changed), you would get a +clear error regardless of the fact that the key is trusted or not: + +.. code-block:: console + + $ gpg --verify phpMyAdmin-4.5.4.1-all-languages.zip.asc + gpg: Signature made Fri 29 Jan 2016 08:59:37 AM EST using RSA key ID 8259BD92 + gpg: BAD signature from "Isaac Bennetch " [unknown] + +.. _Validating other keys on your public keyring: https://www.gnupg.org/gph/en/manual.html#AEN335 + +.. _Isaac's key links to Linus's key: https://pgp.cs.uu.nl/paths/79be3e4300411886/to/ce752f178259bd92.html + +.. index:: + single: Configuration storage + single: phpMyAdmin configuration storage + single: pmadb + +.. _linked-tables: + +phpMyAdmin configuration storage +++++++++++++++++++++++++++++++++ + +.. versionchanged:: 3.4.0 + + Prior to phpMyAdmin 3.4.0 this was called Linked Tables Infrastructure, but + the name was changed due to extended scope of the storage. + +For a whole set of additional features (:ref:`bookmarks`, comments, :term:`SQL`-history, +tracking mechanism, :term:`PDF`-generation, :ref:`transformations`, :ref:`relations` +etc.) you need to create a set of special tables. Those tables can be located +in your own database, or in a central database for a multi-user installation +(this database would then be accessed by the controluser, so no other user +should have rights to it). + +.. _zeroconf: + +Zero configuration +------------------ + +In many cases, this database structure can be automatically created and +configured. This is called “Zero Configuration” mode and can be particularly +useful in shared hosting situations. “Zeroconf” mode is on by default, to +disable set :config:option:`$cfg['ZeroConf']` to false. + +The following three scenarios are covered by the Zero Configuration mode: + +* When entering a database where the configuration storage tables are not + present, phpMyAdmin offers to create them from the Operations tab. +* When entering a database where the tables do already exist, the software + automatically detects this and begins using them. This is the most common + situation; after the tables are initially created automatically they are + continually used without disturbing the user; this is also most useful on + shared hosting where the user is not able to edit :file:`config.inc.php` and + usually the user only has access to one database. +* When having access to multiple databases, if the user first enters the + database containing the configuration storage tables then switches to + another database, + phpMyAdmin continues to use the tables from the first database; the user is + not prompted to create more tables in the new database. + +Manual configuration +-------------------- + +Please look at your ``./sql/`` directory, where you should find a +file called *create\_tables.sql*. (If you are using a Windows server, +pay special attention to :ref:`faq1_23`). + +If you already had this infrastructure and: + +* upgraded to MySQL 4.1.2 or newer, please use + :file:`sql/upgrade_tables_mysql_4_1_2+.sql`. +* upgraded to phpMyAdmin 4.3.0 or newer from 2.5.0 or newer (<= 4.2.x), + please use :file:`sql/upgrade_column_info_4_3_0+.sql`. +* upgraded to phpMyAdmin 4.7.0 or newer from 4.3.0 or newer, + please use :file:`sql/upgrade_tables_4_7_0+.sql`. + +and then create new tables by importing :file:`sql/create_tables.sql`. + +You can use your phpMyAdmin to create the tables for you. Please be +aware that you may need special (administrator) privileges to create +the database and tables, and that the script may need some tuning, +depending on the database name. + +After having imported the :file:`sql/create_tables.sql` file, you +should specify the table names in your :file:`config.inc.php` file. The +directives used for that can be found in the :ref:`config`. + +You will also need to have a controluser +(:config:option:`$cfg['Servers'][$i]['controluser']` and +:config:option:`$cfg['Servers'][$i]['controlpass']` settings) +with the proper rights to those tables. For example you can create it +using following statement: + +.. code-block:: mysql + + GRANT SELECT, INSERT, UPDATE, DELETE ON .* TO 'pma'@'localhost' IDENTIFIED BY 'pmapass'; + +.. _upgrading: + +Upgrading from an older version ++++++++++++++++++++++++++++++++ + +.. warning:: + + **Never** extract the new version over an existing installation of + phpMyAdmin, always first remove the old files keeping just the + configuration. + + This way you will not leave old no longer working code in the directory, + which can have severe security implications or can cause various breakages. + +Simply copy :file:`config.inc.php` from your previous installation into +the newly unpacked one. Configuration files from old versions may +require some tweaking as some options have been changed or removed. +For compatibility with PHP 5.3 and later, remove a +``set_magic_quotes_runtime(0);`` statement that you might find near +the end of your configuration file. + +You should **not** copy :file:`libraries/config.default.php` over +:file:`config.inc.php` because the default configuration file is version- +specific. + +The complete upgrade can be performed in few simple steps: + +1. Download the latest phpMyAdmin version from . +2. Rename existing phpMyAdmin folder (for example to ``phpmyadmin-old``). +3. Unpack freshly donwloaded phpMyAdmin to desired location (for example ``phpmyadmin``). +4. Copy :file:`config.inc.php`` from old location (``phpmyadmin-old``) to new one (``phpmyadmin``). +5. Test that everything works properly. +6. Remove backup of previous version (``phpmyadmin-old``). + +If you have upgraded your MySQL server from a version previous to 4.1.2 to +version 5.x or newer and if you use the phpMyAdmin configuration storage, you +should run the :term:`SQL` script found in +:file:`sql/upgrade_tables_mysql_4_1_2+.sql`. + +If you have upgraded your phpMyAdmin to 4.3.0 or newer from 2.5.0 or +newer (<= 4.2.x) and if you use the phpMyAdmin configuration storage, you +should run the :term:`SQL` script found in +:file:`sql/upgrade_column_info_4_3_0+.sql`. + +Do not forget to clear the browser cache and to empty the old session by +logging out and logging in again. + +.. index:: Authentication mode + +.. _authentication_modes: + +Using authentication modes +++++++++++++++++++++++++++ + +:term:`HTTP` and cookie authentication modes are recommended in a **multi-user +environment** where you want to give users access to their own database and +don't want them to play around with others. Nevertheless be aware that MS +Internet Explorer seems to be really buggy about cookies, at least till version +6. Even in a **single-user environment**, you might prefer to use :term:`HTTP` +or cookie mode so that your user/password pair are not in clear in the +configuration file. + +:term:`HTTP` and cookie authentication +modes are more secure: the MySQL login information does not need to be +set in the phpMyAdmin configuration file (except possibly for the +:config:option:`$cfg['Servers'][$i]['controluser']`). +However, keep in mind that the password travels in plain text, unless +you are using the HTTPS protocol. In cookie mode, the password is +stored, encrypted with the AES algorithm, in a temporary cookie. + +Then each of the *true* users should be granted a set of privileges +on a set of particular databases. Normally you shouldn't give global +privileges to an ordinary user, unless you understand the impact of those +privileges (for example, you are creating a superuser). +For example, to grant the user *real_user* with all privileges on +the database *user_base*: + +.. code-block:: mysql + + GRANT ALL PRIVILEGES ON user_base.* TO 'real_user'@localhost IDENTIFIED BY 'real_password'; + +What the user may now do is controlled entirely by the MySQL user management +system. With HTTP or cookie authentication mode, you don't need to fill the +user/password fields inside the :config:option:`$cfg['Servers']`. + +.. seealso:: + + :ref:`faq1_32`, + :ref:`faq1_35`, + :ref:`faq4_1`, + :ref:`faq4_2`, + :ref:`faq4_3` + +.. index:: pair: HTTP; Authentication mode + +.. _auth_http: + +HTTP authentication mode +------------------------ + +* Uses :term:`HTTP` Basic authentication + method and allows you to log in as any valid MySQL user. +* Is supported with most PHP configurations. For :term:`IIS` (:term:`ISAPI`) + support using :term:`CGI` PHP see :ref:`faq1_32`, for using with Apache + :term:`CGI` see :ref:`faq1_35`. +* When PHP is running under Apache's :term:`mod_proxy_fcgi` (e.g. with PHP-FPM), + ``Authorization`` headers are not passed to the underlying FCGI application, + such that your credentials will not reach the application. In this case, you can + add the following configuration directive: + + .. code-block:: apache + + SetEnvIf Authorization "(.*)" HTTP_AUTHORIZATION=$1 + +* See also :ref:`faq4_4` about not using the :term:`.htaccess` mechanism along with + ':term:`HTTP`' authentication mode. + +.. note:: + + There is no way to do proper logout in HTTP authentication, most browsers + will remember credentials until there is no different successful + authentication. Because of this this method has limitation that you can not + login with same user after logout. + +.. index:: pair: Cookie; Authentication mode + +.. _cookie: + +Cookie authentication mode +-------------------------- + +* Username and password are stored in cookies during the session and password + is deleted when it ends. +* With this mode, the user can truly log out of phpMyAdmin and log + back in with the same username (this is not possible with :ref:`auth_http`). +* If you want to allow users to enter any hostname to connect (rather than only + servers that are configured in :file:`config.inc.php`), + see the :config:option:`$cfg['AllowArbitraryServer']` directive. +* As mentioned in the :ref:`require` section, having the ``openssl`` extension + will speed up access considerably, but is not required. + +.. index:: pair: Signon; Authentication mode + +.. _auth_signon: + +Signon authentication mode +-------------------------- + +* This mode is a convenient way of using credentials from another + application to authenticate to phpMyAdmin to implement single signon + solution. +* The other application has to store login information into session + data (see :config:option:`$cfg['Servers'][$i]['SignonSession']` and + :config:option:`$cfg['Servers'][$i]['SignonCookieParams']`) or you + need to implement script to return the credentials (see + :config:option:`$cfg['Servers'][$i]['SignonScript']`). +* When no credentials are available, the user is being redirected to + :config:option:`$cfg['Servers'][$i]['SignonURL']`, where you should handle + the login process. + +The very basic example of saving credentials in a session is available as +:file:`examples/signon.php`: + +.. literalinclude:: ../examples/signon.php + :language: php + +Alternatively you can also use this way to integrate with OpenID as shown +in :file:`examples/openid.php`: + +.. literalinclude:: ../examples/openid.php + :language: php + +If you intend to pass the credentials using some other means than, you have to +implement wrapper in PHP to get that data and set it to +:config:option:`$cfg['Servers'][$i]['SignonScript']`. There is very minimal example +in :file:`examples/signon-script.php`: + +.. literalinclude:: ../examples/signon-script.php + :language: php + +.. seealso:: + :config:option:`$cfg['Servers'][$i]['auth_type']`, + :config:option:`$cfg['Servers'][$i]['SignonSession']`, + :config:option:`$cfg['Servers'][$i]['SignonCookieParams']`, + :config:option:`$cfg['Servers'][$i]['SignonScript']`, + :config:option:`$cfg['Servers'][$i]['SignonURL']`, + :ref:`example-signon` + +.. index:: pair: Config; Authentication mode + +.. _auth_config: + +Config authentication mode +-------------------------- + +* This mode is sometimes the less secure one because it requires you to fill the + :config:option:`$cfg['Servers'][$i]['user']` and + :config:option:`$cfg['Servers'][$i]['password']` + fields (and as a result, anyone who can read your :file:`config.inc.php` + can discover your username and password). +* In the :ref:`faqmultiuser` section, there is an entry explaining how + to protect your configuration file. +* For additional security in this mode, you may wish to consider the + Host authentication :config:option:`$cfg['Servers'][$i]['AllowDeny']['order']` + and :config:option:`$cfg['Servers'][$i]['AllowDeny']['rules']` configuration directives. +* Unlike cookie and http, does not require a user to log in when first + loading the phpMyAdmin site. This is by design but could allow any + user to access your installation. Use of some restriction method is + suggested, perhaps a :term:`.htaccess` file with the HTTP-AUTH directive or disallowing + incoming HTTP requests at one’s router or firewall will suffice (both + of which are beyond the scope of this manual but easily searchable + with Google). + +.. _securing: + +Securing your phpMyAdmin installation ++++++++++++++++++++++++++++++++++++++ + +The phpMyAdmin team tries hard to make the application secure, however there +are always ways to make your installation more secure: + +* Follow our `Security announcements `_ and upgrade + phpMyAdmin whenever new vulnerability is published. +* Serve phpMyAdmin on HTTPS only. Preferably, you should use HSTS as well, so that + you're protected from protocol downgrade attacks. +* Ensure your PHP setup follows recommendations for production sites, for example + `display_errors `_ + should be disabled. +* Remove the ``test`` directory from phpMyAdmin, unless you are developing and need test suite. +* Remove the ``setup`` directory from phpMyAdmin, you will probably not + use it after the initial setup. +* Properly choose an authentication method - :ref:`cookie` + is probably the best choice for shared hosting. +* Deny access to auxiliary files in :file:`./libraries/` or + :file:`./templates/` subfolders in your webserver configuration. + Such configuration prevents from possible path exposure and cross side + scripting vulnerabilities that might happen to be found in that code. For the + Apache webserver, this is often accomplished with a :term:`.htaccess` file in + those directories. +* Deny access to temporary files, see :config:option:`$cfg['TempDir']` (if that + is placed inside your web root, see also :ref:`web-dirs`. +* It is generally a good idea to protect a public phpMyAdmin installation + against access by robots as they usually can not do anything good there. You + can do this using ``robots.txt`` file in root of your webserver or limit + access by web server configuration, see :ref:`faq1_42`. +* In case you don't want all MySQL users to be able to access + phpMyAdmin, you can use :config:option:`$cfg['Servers'][$i]['AllowDeny']['rules']` to limit them + or :config:option:`$cfg['Servers'][$i]['AllowRoot']` to deny root user access. +* Enable :ref:`2fa` for your account. +* Consider hiding phpMyAdmin behind an authentication proxy, so that + users need to authenticate prior to providing MySQL credentials + to phpMyAdmin. You can achieve this by configuring your web server to request + HTTP authentication. For example in Apache this can be done with: + + .. code-block:: apache + + AuthType Basic + AuthName "Restricted Access" + AuthUserFile /usr/share/phpmyadmin/passwd + Require valid-user + + Once you have changed the configuration, you need to create a list of users which + can authenticate. This can be done using the :program:`htpasswd` utility: + + .. code-block:: sh + + htpasswd -c /usr/share/phpmyadmin/passwd username + +* If you are afraid of automated attacks, enabling Captcha by + :config:option:`$cfg['CaptchaLoginPublicKey']` and + :config:option:`$cfg['CaptchaLoginPrivateKey']` might be an option. +* Failed login attemps are logged to syslog (if available, see + :config:option:`$cfg['AuthLog']`). This can allow using a tool such as + fail2ban to block brute-force attempts. Note that the log file used by syslog + is not the same as the Apache error or access log files. +* In case you're running phpMyAdmin together with other PHP applications, it is + generally advised to use separate session storage for phpMyAdmin to avoid + possible session based attacks against it. You can use + :config:option:`$cfg['SessionSavePath']` to achieve this. + +.. _ssl: + +Using SSL for connection to database server ++++++++++++++++++++++++++++++++++++++++++++ + +It is recommended to use SSL when connecting to remote database server. There +are several configuration options involved in the SSL setup: + +:config:option:`$cfg['Servers'][$i]['ssl']` + Defines whether to use SSL at all. If you enable only this, the connection + will be encrypted, but there is not authentication of the connection - you + can not verify that you are talking to the right server. +:config:option:`$cfg['Servers'][$i]['ssl_key']` and :config:option:`$cfg['Servers'][$i]['ssl_cert']` + This is used for authentication of client to the server. +:config:option:`$cfg['Servers'][$i]['ssl_ca']` and :config:option:`$cfg['Servers'][$i]['ssl_ca_path']` + The certificate authorities you trust for server certificates. + This is used to ensure that you are talking to a trusted server. +:config:option:`$cfg['Servers'][$i]['ssl_verify']` + This configuration disables server certificate verification. Use with + caution. + +.. seealso:: + + :ref:`example-google-ssl`, + :config:option:`$cfg['Servers'][$i]['ssl']`, + :config:option:`$cfg['Servers'][$i]['ssl_key']`, + :config:option:`$cfg['Servers'][$i]['ssl_cert']`, + :config:option:`$cfg['Servers'][$i]['ssl_ca']`, + :config:option:`$cfg['Servers'][$i]['ssl_ca_path']`, + :config:option:`$cfg['Servers'][$i]['ssl_ciphers']`, + :config:option:`$cfg['Servers'][$i]['ssl_verify']` + +Known issues +++++++++++++ + +Users with column-specific privileges are unable to "Browse" +------------------------------------------------------------ + +If a user has only column-specific privileges on some (but not all) columns in a table, "Browse" +will fail with an error message. + +As a workaround, a bookmarked query with the same name as the table can be created, this will +run when using the "Browse" link instead. `Issue 11922 `_. + +Trouble logging back in after logging out using 'http' authentication +---------------------------------------------------------------------- + +When using the 'http' ``auth_type``, it can be impossible to log back in (when the logout comes +manually or after a period of inactivity). `Issue 11898 `_. + +.. _Composer tool: https://getcomposer.org/ +.. _Packagist: https://packagist.org/ +.. _Docker image: https://hub.docker.com/r/phpmyadmin/phpmyadmin/ diff --git a/php/apps/phpmyadmin49/doc/html/_sources/themes.txt b/php/apps/phpmyadmin49/doc/html/_sources/themes.txt new file mode 100644 index 00000000..1f22521b --- /dev/null +++ b/php/apps/phpmyadmin49/doc/html/_sources/themes.txt @@ -0,0 +1,100 @@ +.. _themes: + +Custom Themes +============= + +phpMyAdmin comes with support for third party themes. You can download +additonal themes from our website at . + +Configuration +------------- + +Themes are configured with :config:option:`$cfg['ThemeManager']` and +:config:option:`$cfg['ThemeDefault']`. Under :file:`./themes/`, you should not +delete the directory ``pmahomme`` or its underlying structure, because this is +the system theme used by phpMyAdmin. ``pmahomme`` contains all images and +styles, for backwards compatibility and for all themes that would not include +images or css-files. If :config:option:`$cfg['ThemeManager']` is enabled, you +can select your favorite theme on the main page. Your selected theme will be +stored in a cookie. + +Creating custom theme +--------------------- + +To create a theme: + +* make a new subdirectory (for example "your\_theme\_name") under :file:`./themes/`. +* copy the files and directories from ``pmahomme`` to "your\_theme\_name" +* edit the css-files in "your\_theme\_name/css" +* put your new images in "your\_theme\_name/img" +* edit :file:`layout.inc.php` in "your\_theme\_name" +* edit :file:`theme.json` in "your\_theme\_name" to contain theme metadata (see below) +* make a new screenshot of your theme and save it under + "your\_theme\_name/screen.png" + +Theme metadata +++++++++++++++ + +.. versionchanged:: 4.8.0 + + Before 4.8.0 the theme metadata was passed in the :file:`info.inc.php` file. + It has been replaced by :file:`theme.json` to allow easier parsing (without + need to handle PHP code) and to support additional features. + +In theme directory there is file :file:`theme.json` which contains theme +metadata. Currently it consists of: + +.. describe:: name + + Display name of the theme. + + **This field is required.** + +.. describe:: version + + Theme version, can be quite arbirary and does not have to match phpMyAdmin version. + + **This field is required.** + +.. describe:: desciption + + Theme description. this will be shown on the website. + + **This field is required.** + +.. describe:: author + + Theme author name. + + **This field is required.** + +.. describe:: url + + Link to theme author website. It's good idea to have way for getting + support there. + +.. describe:: supports + + Array of supported phpMyAdmin major versions. + + **This field is required.** + +For example, the definition for Original theme shipped with phpMyAdnin 4.8: + +.. code-block:: json + + { + "name": "Original", + "version": "4.8", + "description": "Original phpMyAdmin theme", + "author": "phpMyAdmin developers", + "url": "https://www.phpmyadmin.net/", + "supports": ["4.8"] + } + +Sharing images +++++++++++++++ + +If you do not want to use your own symbols and buttons, remove the +directory "img" in "your\_theme\_name". phpMyAdmin will use the +default icons and buttons (from the system-theme ``pmahomme``). diff --git a/php/apps/phpmyadmin49/doc/html/_sources/transformations.txt b/php/apps/phpmyadmin49/doc/html/_sources/transformations.txt new file mode 100644 index 00000000..02f43c54 --- /dev/null +++ b/php/apps/phpmyadmin49/doc/html/_sources/transformations.txt @@ -0,0 +1,143 @@ +.. _transformations: + +Transformations +=============== + +.. note:: + + You need to have configured the :ref:`linked-tables` for using transformations + feature. + +.. _transformationsintro: + +Introduction +++++++++++++ + +To enable transformations, you have to setup the ``column_info`` +table and the proper directives. Please see the :ref:`config` on how to do so. + +You can apply different transformations to the contents of each +column. The transformation will take the content of each column and +transform it with certain rules defined in the selected +transformation. + +Say you have a column 'filename' which contains a filename. Normally +you would see in phpMyAdmin only this filename. Using transformations +you can transform that filename into a HTML link, so you can click +inside of the phpMyAdmin structure on the column's link and will see +the file displayed in a new browser window. Using transformation +options you can also specify strings to append/prepend to a string or +the format you want the output stored in. + +For a general overview of all available transformations and their +options, you can consult your *//transformation\_overview.php* installation. + +For a tutorial on how to effectively use transformations, see our +`Link section `_ on the +official phpMyAdmin homepage. + +.. _transformationshowto: + +Usage ++++++ + +Go to your *tbl\_structure.php* page (i.e. reached through clicking on +the 'Structure' link for a table). There click on "Change" (or change +icon) and there you will see three new fields at the end of the line. +They are called 'MIME-type', 'Browser transformation' and +'Transformation options'. + +* The field 'MIME-type' is a drop-down field. Select the MIME-type that + corresponds to the column's contents. Please note that transformations + are inactive as long as no MIME-type is selected. +* The field 'Browser transformation' is a drop-down field. You can + choose from a hopefully growing amount of pre-defined transformations. + See below for information on how to build your own transformation. + There are global transformations and mimetype-bound transformations. + Global transformations can be used for any mimetype. They will take + the mimetype, if necessary, into regard. Mimetype-bound + transformations usually only operate on a certain mimetype. There are + transformations which operate on the main mimetype (like 'image'), + which will most likely take the subtype into regard, and those who + only operate on a specific subtype (like 'image/jpeg'). You can use + transformations on mimetypes for which the function was not defined + for. There is no security check for you selected the right + transformation, so take care of what the output will be like. +* The field 'Transformation options' is a free-type textfield. You have + to enter transform-function specific options here. Usually the + transforms can operate with default options, but it is generally a + good idea to look up the overview to see which options are necessary. + Much like the ENUM/SET-Fields, you have to split up several options + using the format 'a','b','c',...(NOTE THE MISSING BLANKS). This is + because internally the options will be parsed as an array, leaving the + first value the first element in the array, and so forth. If you want + to specify a MIME character set you can define it in the + transformation\_options. You have to put that outside of the pre- + defined options of the specific mime-transform, as the last value of + the set. Use the format "'; charset=XXX'". If you use a transform, for + which you can specify 2 options and you want to append a character + set, enter "'first parameter','second parameter','charset=us-ascii'". + You can, however use the defaults for the parameters: "'','','charset + =us-ascii'". The default options can be configured using + :config:option:`$cfg['DefaultTransformations']` + +.. _transformationsfiles: + +File structure +++++++++++++++ + +All specific transformations for mimetypes are defined through class +files in the directory 'libraries/classes/Plugins/Transformations/'. Each of +them extends a certain transformation abstract class declared in +libraries/classes/Plugins/Transformations/Abs. + +They are stored in files to ease up customization and easy adding of +new transformations. + +Because the user cannot enter own mimetypes, it is kept sure that +transformations always work. It makes no sense to apply a +transformation to a mimetype the transform-function doesn't know to +handle. + +There is a file called '*transformations.lib.php*' that provides some +basic functions which can be included by any other transform function. + +The file name convention is ``[Mimetype]_[Subtype]_[Transformation +Name].class.php``, while the abtract class that it extends has the +name ``[Transformation Name]TransformationsPlugin``. All of the +methods that have to be implemented by a transformations plug-in are: + +#. getMIMEType() and getMIMESubtype() in the main class; +#. getName(), getInfo() and applyTransformation() in the abstract class + it extends. + +The getMIMEType(), getMIMESubtype() and getName() methods return the +name of the MIME type, MIME Subtype and transformation accordingly. +getInfo() returns the transformation's description and possible +options it may receive and applyTransformation() is the method that +does the actual work of the transformation plug-in. + +Please see the libraries/classes/Plugins/Transformations/TEMPLATE and +libraries/classes/Plugins/Transformations/TEMPLATE\_ABSTRACT files for adding +your own transformation plug-in. You can also generate a new +transformation plug-in (with or without the abstract transformation +class), by using +:file:`scripts/transformations_generator_plugin.sh` or +:file:`scripts/transformations_generator_main_class.sh`. + +The applyTransformation() method always gets passed three variables: + +#. **$buffer** - Contains the text inside of the column. This is the + text, you want to transform. +#. **$options** - Contains any user-passed options to a transform + function as an array. +#. **$meta** - Contains an object with information about your column. The + data is drawn from the output of the `mysql\_fetch\_field() + `_ function. This means, all + object properties described on the `manual page + `_ are available in this + variable and can be used to transform a column accordingly to + unsigned/zerofill/not\_null/... properties. The $meta->mimetype + variable contains the original MIME-type of the column (i.e. + 'text/plain', 'image/jpeg' etc.) diff --git a/php/apps/phpmyadmin49/doc/html/_sources/two_factor.txt b/php/apps/phpmyadmin49/doc/html/_sources/two_factor.txt new file mode 100644 index 00000000..fe4e6473 --- /dev/null +++ b/php/apps/phpmyadmin49/doc/html/_sources/two_factor.txt @@ -0,0 +1,69 @@ +.. _2fa: + +Two-factor authentication +========================= + +.. versionadded:: 4.8.0 + +Since phpMyAdmin 4.8.0 you can configure two-factor authentication to be +used when logging in. To use this, you first need to configure the +:ref:`linked-tables`. Once this is done, every user can opt-in for the second +authentication factor in the :guilabel:`Settings`. + +When running phpMyAdmin from the Git source repository, the dependencies must be installed +manually; the typical way of doing so is with the command: + +.. code-block:: sh + + composer require pragmarx/google2fa bacon/bacon-qr-code + +Or when using a hardware security key with FIDO U2F: + +.. code-block:: sh + + composer require samyoul/u2f-php-server + +Authentication Application (2FA) +-------------------------------- + +Using an application for authentication is a quite common approach based on HOTP and +`TOTP `_. +It is based on transmitting a private key from phpMyAdmin to the authentication +application and the application is then able to generate one time codes based +on this key. The easiest way to enter the key in to the application from phpMyAdmin is +through scanning a QR code. + +There are dozens of applications available for mobile phones to implement these +standards, the most widely used include: + +* `FreeOTP for iOS, Android and Pebble `_ +* `Authy for iOS, Android, Chrome, OS X `_ +* `Google Authenticator for iOS `_ +* `Google Authenticator for Android `_ +* `LastPass Authenticator for iOS, Android, OS X, Windows `_ + +Hardware Security Key (FIDO U2F) +-------------------------------- + +Using hardware tokens is considered to be more secure than a software based +solution. phpMyAdmin supports `FIDO U2F `_ +tokens. + +There are several manufacturers of these tokens, for example: + +* `youbico FIDO U2F Security Key `_ +* `HyperFIDO `_ +* `Trezor Hardware Wallet `_ can act as an `U2F token `_ +* `List of Two Factor Auth (2FA) Dongles `_ + +.. _simple2fa: + +Simple two-factor authentication +-------------------------------- + +This authentication is included for testing and demonstration purposes only as +it really does not provide two-factor authentication, it just asks the user to confirm login by +clicking on the button. + +It should not be used in the production and is disabled unless +:config:option:`$cfg['DBG']['simple2fa']` is set. diff --git a/php/apps/phpmyadmin49/doc/html/_sources/user.txt b/php/apps/phpmyadmin49/doc/html/_sources/user.txt new file mode 100644 index 00000000..a98e2cb8 --- /dev/null +++ b/php/apps/phpmyadmin49/doc/html/_sources/user.txt @@ -0,0 +1,16 @@ +User Guide +========== + +.. toctree:: + :maxdepth: 2 + + settings + two_factor + transformations + bookmarks + privileges + relations + charts + import_export + themes + other diff --git a/php/apps/phpmyadmin49/doc/html/_sources/vendors.txt b/php/apps/phpmyadmin49/doc/html/_sources/vendors.txt new file mode 100644 index 00000000..aaceee34 --- /dev/null +++ b/php/apps/phpmyadmin49/doc/html/_sources/vendors.txt @@ -0,0 +1,32 @@ +Distributing and packaging phpMyAdmin +===================================== + +This document is intended to give advices to people who want to +redistribute phpMyAdmin inside other software package such as Linux +distribution or some all in one package including web server and MySQL +server. + +Generally you can customize some basic aspects (paths to some files and +behavior) in :file:`libraries/vendor_config.php`. + +For example if you want setup script to generate config file in var, change +``SETUP_CONFIG_FILE`` to :file:`/var/lib/phpmyadmin/config.inc.php` and you +will also probably want to skip directory writable check, so set +``SETUP_DIR_WRITABLE`` to false. + +External libraries +------------------ + +phpMyAdmin includes several external libraries, you might want to +replace them with system ones if they are available, but please note +that you should test whether version you provide is compatible with the +one we ship. + +Currently known list of external libraries: + +js/jquery + jQuery js framework and various jQuery based libraries. + +vendor/ + The download kit includes various Composer packages as + dependencies. diff --git a/php/apps/phpmyadmin49/doc/html/_static/ajax-loader.gif b/php/apps/phpmyadmin49/doc/html/_static/ajax-loader.gif new file mode 100644 index 00000000..61faf8ca Binary files /dev/null and b/php/apps/phpmyadmin49/doc/html/_static/ajax-loader.gif differ diff --git a/php/apps/phpmyadmin49/doc/html/_static/basic.css b/php/apps/phpmyadmin49/doc/html/_static/basic.css new file mode 100644 index 00000000..0b79414a --- /dev/null +++ b/php/apps/phpmyadmin49/doc/html/_static/basic.css @@ -0,0 +1,611 @@ +/* + * basic.css + * ~~~~~~~~~ + * + * Sphinx stylesheet -- basic theme. + * + * :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ + +/* -- main layout ----------------------------------------------------------- */ + +div.clearer { + clear: both; +} + +/* -- relbar ---------------------------------------------------------------- */ + +div.related { + width: 100%; + font-size: 90%; +} + +div.related h3 { + display: none; +} + +div.related ul { + margin: 0; + padding: 0 0 0 10px; + list-style: none; +} + +div.related li { + display: inline; +} + +div.related li.right { + float: right; + margin-right: 5px; +} + +/* -- sidebar --------------------------------------------------------------- */ + +div.sphinxsidebarwrapper { + padding: 10px 5px 0 10px; +} + +div.sphinxsidebar { + float: left; + width: 230px; + margin-left: -100%; + font-size: 90%; + word-wrap: break-word; + overflow-wrap : break-word; +} + +div.sphinxsidebar ul { + list-style: none; +} + +div.sphinxsidebar ul ul, +div.sphinxsidebar ul.want-points { + margin-left: 20px; + list-style: square; +} + +div.sphinxsidebar ul ul { + margin-top: 0; + margin-bottom: 0; +} + +div.sphinxsidebar form { + margin-top: 10px; +} + +div.sphinxsidebar input { + border: 1px solid #98dbcc; + font-family: sans-serif; + font-size: 1em; +} + +div.sphinxsidebar #searchbox input[type="text"] { + width: 170px; +} + +img { + border: 0; + max-width: 100%; +} + +/* -- search page ----------------------------------------------------------- */ + +ul.search { + margin: 10px 0 0 20px; + padding: 0; +} + +ul.search li { + padding: 5px 0 5px 20px; + background-image: url(file.png); + background-repeat: no-repeat; + background-position: 0 7px; +} + +ul.search li a { + font-weight: bold; +} + +ul.search li div.context { + color: #888; + margin: 2px 0 0 30px; + text-align: left; +} + +ul.keywordmatches li.goodmatch a { + font-weight: bold; +} + +/* -- index page ------------------------------------------------------------ */ + +table.contentstable { + width: 90%; +} + +table.contentstable p.biglink { + line-height: 150%; +} + +a.biglink { + font-size: 1.3em; +} + +span.linkdescr { + font-style: italic; + padding-top: 5px; + font-size: 90%; +} + +/* -- general index --------------------------------------------------------- */ + +table.indextable { + width: 100%; +} + +table.indextable td { + text-align: left; + vertical-align: top; +} + +table.indextable dl, table.indextable dd { + margin-top: 0; + margin-bottom: 0; +} + +table.indextable tr.pcap { + height: 10px; +} + +table.indextable tr.cap { + margin-top: 10px; + background-color: #f2f2f2; +} + +img.toggler { + margin-right: 3px; + margin-top: 3px; + cursor: pointer; +} + +div.modindex-jumpbox { + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; + margin: 1em 0 1em 0; + padding: 0.4em; +} + +div.genindex-jumpbox { + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; + margin: 1em 0 1em 0; + padding: 0.4em; +} + +/* -- general body styles --------------------------------------------------- */ + +div.body p, div.body dd, div.body li, div.body blockquote { + -moz-hyphens: auto; + -ms-hyphens: auto; + -webkit-hyphens: auto; + hyphens: auto; +} + +a.headerlink { + visibility: hidden; +} + +h1:hover > a.headerlink, +h2:hover > a.headerlink, +h3:hover > a.headerlink, +h4:hover > a.headerlink, +h5:hover > a.headerlink, +h6:hover > a.headerlink, +dt:hover > a.headerlink, +caption:hover > a.headerlink, +p.caption:hover > a.headerlink, +div.code-block-caption:hover > a.headerlink { + visibility: visible; +} + +div.body p.caption { + text-align: inherit; +} + +div.body td { + text-align: left; +} + +.field-list ul { + padding-left: 1em; +} + +.first { + margin-top: 0 !important; +} + +p.rubric { + margin-top: 30px; + font-weight: bold; +} + +img.align-left, .figure.align-left, object.align-left { + clear: left; + float: left; + margin-right: 1em; +} + +img.align-right, .figure.align-right, object.align-right { + clear: right; + float: right; + margin-left: 1em; +} + +img.align-center, .figure.align-center, object.align-center { + display: block; + margin-left: auto; + margin-right: auto; +} + +.align-left { + text-align: left; +} + +.align-center { + text-align: center; +} + +.align-right { + text-align: right; +} + +/* -- sidebars -------------------------------------------------------------- */ + +div.sidebar { + margin: 0 0 0.5em 1em; + border: 1px solid #ddb; + padding: 7px 7px 0 7px; + background-color: #ffe; + width: 40%; + float: right; +} + +p.sidebar-title { + font-weight: bold; +} + +/* -- topics ---------------------------------------------------------------- */ + +div.topic { + border: 1px solid #ccc; + padding: 7px 7px 0 7px; + margin: 10px 0 10px 0; +} + +p.topic-title { + font-size: 1.1em; + font-weight: bold; + margin-top: 10px; +} + +/* -- admonitions ----------------------------------------------------------- */ + +div.admonition { + margin-top: 10px; + margin-bottom: 10px; + padding: 7px; +} + +div.admonition dt { + font-weight: bold; +} + +div.admonition dl { + margin-bottom: 0; +} + +p.admonition-title { + margin: 0px 10px 5px 0px; + font-weight: bold; +} + +div.body p.centered { + text-align: center; + margin-top: 25px; +} + +/* -- tables ---------------------------------------------------------------- */ + +table.docutils { + border: 0; + border-collapse: collapse; +} + +table caption span.caption-number { + font-style: italic; +} + +table caption span.caption-text { +} + +table.docutils td, table.docutils th { + padding: 1px 8px 1px 5px; + border-top: 0; + border-left: 0; + border-right: 0; + border-bottom: 1px solid #aaa; +} + +table.field-list td, table.field-list th { + border: 0 !important; +} + +table.footnote td, table.footnote th { + border: 0 !important; +} + +th { + text-align: left; + padding-right: 5px; +} + +table.citation { + border-left: solid 1px gray; + margin-left: 1px; +} + +table.citation td { + border-bottom: none; +} + +/* -- figures --------------------------------------------------------------- */ + +div.figure { + margin: 0.5em; + padding: 0.5em; +} + +div.figure p.caption { + padding: 0.3em; +} + +div.figure p.caption span.caption-number { + font-style: italic; +} + +div.figure p.caption span.caption-text { +} + + +/* -- other body styles ----------------------------------------------------- */ + +ol.arabic { + list-style: decimal; +} + +ol.loweralpha { + list-style: lower-alpha; +} + +ol.upperalpha { + list-style: upper-alpha; +} + +ol.lowerroman { + list-style: lower-roman; +} + +ol.upperroman { + list-style: upper-roman; +} + +dl { + margin-bottom: 15px; +} + +dd p { + margin-top: 0px; +} + +dd ul, dd table { + margin-bottom: 10px; +} + +dd { + margin-top: 3px; + margin-bottom: 10px; + margin-left: 30px; +} + +dt:target, .highlighted { + background-color: #fbe54e; +} + +dl.glossary dt { + font-weight: bold; + font-size: 1.1em; +} + +.field-list ul { + margin: 0; + padding-left: 1em; +} + +.field-list p { + margin: 0; +} + +.optional { + font-size: 1.3em; +} + +.sig-paren { + font-size: larger; +} + +.versionmodified { + font-style: italic; +} + +.system-message { + background-color: #fda; + padding: 5px; + border: 3px solid red; +} + +.footnote:target { + background-color: #ffa; +} + +.line-block { + display: block; + margin-top: 1em; + margin-bottom: 1em; +} + +.line-block .line-block { + margin-top: 0; + margin-bottom: 0; + margin-left: 1.5em; +} + +.guilabel, .menuselection { + font-family: sans-serif; +} + +.accelerator { + text-decoration: underline; +} + +.classifier { + font-style: oblique; +} + +abbr, acronym { + border-bottom: dotted 1px; + cursor: help; +} + +/* -- code displays --------------------------------------------------------- */ + +pre { + overflow: auto; + overflow-y: hidden; /* fixes display issues on Chrome browsers */ +} + +span.pre { + -moz-hyphens: none; + -ms-hyphens: none; + -webkit-hyphens: none; + hyphens: none; +} + +td.linenos pre { + padding: 5px 0px; + border: 0; + background-color: transparent; + color: #aaa; +} + +table.highlighttable { + margin-left: 0.5em; +} + +table.highlighttable td { + padding: 0 0.5em 0 0.5em; +} + +div.code-block-caption { + padding: 2px 5px; + font-size: small; +} + +div.code-block-caption code { + background-color: transparent; +} + +div.code-block-caption + div > div.highlight > pre { + margin-top: 0; +} + +div.code-block-caption span.caption-number { + padding: 0.1em 0.3em; + font-style: italic; +} + +div.code-block-caption span.caption-text { +} + +div.literal-block-wrapper { + padding: 1em 1em 0; +} + +div.literal-block-wrapper div.highlight { + margin: 0; +} + +code.descname { + background-color: transparent; + font-weight: bold; + font-size: 1.2em; +} + +code.descclassname { + background-color: transparent; +} + +code.xref, a code { + background-color: transparent; + font-weight: bold; +} + +h1 code, h2 code, h3 code, h4 code, h5 code, h6 code { + background-color: transparent; +} + +.viewcode-link { + float: right; +} + +.viewcode-back { + float: right; + font-family: sans-serif; +} + +div.viewcode-block:target { + margin: -1px -10px; + padding: 0 10px; +} + +/* -- math display ---------------------------------------------------------- */ + +img.math { + vertical-align: middle; +} + +div.body div.math p { + text-align: center; +} + +span.eqno { + float: right; +} + +/* -- printout stylesheet --------------------------------------------------- */ + +@media print { + div.document, + div.documentwrapper, + div.bodywrapper { + margin: 0 !important; + width: 100%; + } + + div.sphinxsidebar, + div.related, + div.footer, + #top-link { + display: none; + } +} \ No newline at end of file diff --git a/php/apps/phpmyadmin49/doc/html/_static/classic.css b/php/apps/phpmyadmin49/doc/html/_static/classic.css new file mode 100644 index 00000000..d98894b3 --- /dev/null +++ b/php/apps/phpmyadmin49/doc/html/_static/classic.css @@ -0,0 +1,261 @@ +/* + * default.css_t + * ~~~~~~~~~~~~~ + * + * Sphinx stylesheet -- default theme. + * + * :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ + +@import url("basic.css"); + +/* -- page layout ----------------------------------------------------------- */ + +body { + font-family: sans-serif; + font-size: 100%; + background-color: #11303d; + color: #000; + margin: 0; + padding: 0; +} + +div.document { + background-color: #1c4e63; +} + +div.documentwrapper { + float: left; + width: 100%; +} + +div.bodywrapper { + margin: 0 0 0 230px; +} + +div.body { + background-color: #ffffff; + color: #000000; + padding: 0 20px 30px 20px; +} + +div.footer { + color: #ffffff; + width: 100%; + padding: 9px 0 9px 0; + text-align: center; + font-size: 75%; +} + +div.footer a { + color: #ffffff; + text-decoration: underline; +} + +div.related { + background-color: #133f52; + line-height: 30px; + color: #ffffff; +} + +div.related a { + color: #ffffff; +} + +div.sphinxsidebar { +} + +div.sphinxsidebar h3 { + font-family: 'Trebuchet MS', sans-serif; + color: #ffffff; + font-size: 1.4em; + font-weight: normal; + margin: 0; + padding: 0; +} + +div.sphinxsidebar h3 a { + color: #ffffff; +} + +div.sphinxsidebar h4 { + font-family: 'Trebuchet MS', sans-serif; + color: #ffffff; + font-size: 1.3em; + font-weight: normal; + margin: 5px 0 0 0; + padding: 0; +} + +div.sphinxsidebar p { + color: #ffffff; +} + +div.sphinxsidebar p.topless { + margin: 5px 10px 10px 10px; +} + +div.sphinxsidebar ul { + margin: 10px; + padding: 0; + color: #ffffff; +} + +div.sphinxsidebar a { + color: #98dbcc; +} + +div.sphinxsidebar input { + border: 1px solid #98dbcc; + font-family: sans-serif; + font-size: 1em; +} + + + +/* -- hyperlink styles ------------------------------------------------------ */ + +a { + color: #355f7c; + text-decoration: none; +} + +a:visited { + color: #355f7c; + text-decoration: none; +} + +a:hover { + text-decoration: underline; +} + + + +/* -- body styles ----------------------------------------------------------- */ + +div.body h1, +div.body h2, +div.body h3, +div.body h4, +div.body h5, +div.body h6 { + font-family: 'Trebuchet MS', sans-serif; + background-color: #f2f2f2; + font-weight: normal; + color: #20435c; + border-bottom: 1px solid #ccc; + margin: 20px -20px 10px -20px; + padding: 3px 0 3px 10px; +} + +div.body h1 { margin-top: 0; font-size: 200%; } +div.body h2 { font-size: 160%; } +div.body h3 { font-size: 140%; } +div.body h4 { font-size: 120%; } +div.body h5 { font-size: 110%; } +div.body h6 { font-size: 100%; } + +a.headerlink { + color: #c60f0f; + font-size: 0.8em; + padding: 0 4px 0 4px; + text-decoration: none; +} + +a.headerlink:hover { + background-color: #c60f0f; + color: white; +} + +div.body p, div.body dd, div.body li, div.body blockquote { + text-align: justify; + line-height: 130%; +} + +div.admonition p.admonition-title + p { + display: inline; +} + +div.admonition p { + margin-bottom: 5px; +} + +div.admonition pre { + margin-bottom: 5px; +} + +div.admonition ul, div.admonition ol { + margin-bottom: 5px; +} + +div.note { + background-color: #eee; + border: 1px solid #ccc; +} + +div.seealso { + background-color: #ffc; + border: 1px solid #ff6; +} + +div.topic { + background-color: #eee; +} + +div.warning { + background-color: #ffe4e4; + border: 1px solid #f66; +} + +p.admonition-title { + display: inline; +} + +p.admonition-title:after { + content: ":"; +} + +pre { + padding: 5px; + background-color: #eeffcc; + color: #333333; + line-height: 120%; + border: 1px solid #ac9; + border-left: none; + border-right: none; +} + +code { + background-color: #ecf0f3; + padding: 0 1px 0 1px; + font-size: 0.95em; +} + +th { + background-color: #ede; +} + +.warning code { + background: #efc2c2; +} + +.note code { + background: #d6d6d6; +} + +.viewcode-back { + font-family: sans-serif; +} + +div.viewcode-block:target { + background-color: #f4debf; + border-top: 1px solid #ac9; + border-bottom: 1px solid #ac9; +} + +div.code-block-caption { + color: #efefef; + background-color: #1c4e63; +} \ No newline at end of file diff --git a/php/apps/phpmyadmin49/doc/html/_static/comment-bright.png b/php/apps/phpmyadmin49/doc/html/_static/comment-bright.png new file mode 100644 index 00000000..4a2bf8af Binary files /dev/null and b/php/apps/phpmyadmin49/doc/html/_static/comment-bright.png differ diff --git a/php/apps/phpmyadmin49/doc/html/_static/comment-close.png b/php/apps/phpmyadmin49/doc/html/_static/comment-close.png new file mode 100644 index 00000000..5fe08972 Binary files /dev/null and b/php/apps/phpmyadmin49/doc/html/_static/comment-close.png differ diff --git a/php/apps/phpmyadmin49/doc/html/_static/comment.png b/php/apps/phpmyadmin49/doc/html/_static/comment.png new file mode 100644 index 00000000..e651e983 Binary files /dev/null and b/php/apps/phpmyadmin49/doc/html/_static/comment.png differ diff --git a/php/apps/phpmyadmin49/doc/html/_static/default.css b/php/apps/phpmyadmin49/doc/html/_static/default.css new file mode 100644 index 00000000..81b93636 --- /dev/null +++ b/php/apps/phpmyadmin49/doc/html/_static/default.css @@ -0,0 +1 @@ +@import url("classic.css"); diff --git a/php/apps/phpmyadmin49/doc/html/_static/doctools.js b/php/apps/phpmyadmin49/doc/html/_static/doctools.js new file mode 100644 index 00000000..81634956 --- /dev/null +++ b/php/apps/phpmyadmin49/doc/html/_static/doctools.js @@ -0,0 +1,287 @@ +/* + * doctools.js + * ~~~~~~~~~~~ + * + * Sphinx JavaScript utilities for all documentation. + * + * :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ + +/** + * select a different prefix for underscore + */ +$u = _.noConflict(); + +/** + * make the code below compatible with browsers without + * an installed firebug like debugger +if (!window.console || !console.firebug) { + var names = ["log", "debug", "info", "warn", "error", "assert", "dir", + "dirxml", "group", "groupEnd", "time", "timeEnd", "count", "trace", + "profile", "profileEnd"]; + window.console = {}; + for (var i = 0; i < names.length; ++i) + window.console[names[i]] = function() {}; +} + */ + +/** + * small helper function to urldecode strings + */ +jQuery.urldecode = function(x) { + return decodeURIComponent(x).replace(/\+/g, ' '); +}; + +/** + * small helper function to urlencode strings + */ +jQuery.urlencode = encodeURIComponent; + +/** + * This function returns the parsed url parameters of the + * current request. Multiple values per key are supported, + * it will always return arrays of strings for the value parts. + */ +jQuery.getQueryParameters = function(s) { + if (typeof s == 'undefined') + s = document.location.search; + var parts = s.substr(s.indexOf('?') + 1).split('&'); + var result = {}; + for (var i = 0; i < parts.length; i++) { + var tmp = parts[i].split('=', 2); + var key = jQuery.urldecode(tmp[0]); + var value = jQuery.urldecode(tmp[1]); + if (key in result) + result[key].push(value); + else + result[key] = [value]; + } + return result; +}; + +/** + * highlight a given string on a jquery object by wrapping it in + * span elements with the given class name. + */ +jQuery.fn.highlightText = function(text, className) { + function highlight(node) { + if (node.nodeType == 3) { + var val = node.nodeValue; + var pos = val.toLowerCase().indexOf(text); + if (pos >= 0 && !jQuery(node.parentNode).hasClass(className)) { + var span = document.createElement("span"); + span.className = className; + span.appendChild(document.createTextNode(val.substr(pos, text.length))); + node.parentNode.insertBefore(span, node.parentNode.insertBefore( + document.createTextNode(val.substr(pos + text.length)), + node.nextSibling)); + node.nodeValue = val.substr(0, pos); + } + } + else if (!jQuery(node).is("button, select, textarea")) { + jQuery.each(node.childNodes, function() { + highlight(this); + }); + } + } + return this.each(function() { + highlight(this); + }); +}; + +/* + * backward compatibility for jQuery.browser + * This will be supported until firefox bug is fixed. + */ +if (!jQuery.browser) { + jQuery.uaMatch = function(ua) { + ua = ua.toLowerCase(); + + var match = /(chrome)[ \/]([\w.]+)/.exec(ua) || + /(webkit)[ \/]([\w.]+)/.exec(ua) || + /(opera)(?:.*version|)[ \/]([\w.]+)/.exec(ua) || + /(msie) ([\w.]+)/.exec(ua) || + ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec(ua) || + []; + + return { + browser: match[ 1 ] || "", + version: match[ 2 ] || "0" + }; + }; + jQuery.browser = {}; + jQuery.browser[jQuery.uaMatch(navigator.userAgent).browser] = true; +} + +/** + * Small JavaScript module for the documentation. + */ +var Documentation = { + + init : function() { + this.fixFirefoxAnchorBug(); + this.highlightSearchWords(); + this.initIndexTable(); + + }, + + /** + * i18n support + */ + TRANSLATIONS : {}, + PLURAL_EXPR : function(n) { return n == 1 ? 0 : 1; }, + LOCALE : 'unknown', + + // gettext and ngettext don't access this so that the functions + // can safely bound to a different name (_ = Documentation.gettext) + gettext : function(string) { + var translated = Documentation.TRANSLATIONS[string]; + if (typeof translated == 'undefined') + return string; + return (typeof translated == 'string') ? translated : translated[0]; + }, + + ngettext : function(singular, plural, n) { + var translated = Documentation.TRANSLATIONS[singular]; + if (typeof translated == 'undefined') + return (n == 1) ? singular : plural; + return translated[Documentation.PLURALEXPR(n)]; + }, + + addTranslations : function(catalog) { + for (var key in catalog.messages) + this.TRANSLATIONS[key] = catalog.messages[key]; + this.PLURAL_EXPR = new Function('n', 'return +(' + catalog.plural_expr + ')'); + this.LOCALE = catalog.locale; + }, + + /** + * add context elements like header anchor links + */ + addContextElements : function() { + $('div[id] > :header:first').each(function() { + $('\u00B6'). + attr('href', '#' + this.id). + attr('title', _('Permalink to this headline')). + appendTo(this); + }); + $('dt[id]').each(function() { + $('\u00B6'). + attr('href', '#' + this.id). + attr('title', _('Permalink to this definition')). + appendTo(this); + }); + }, + + /** + * workaround a firefox stupidity + * see: https://bugzilla.mozilla.org/show_bug.cgi?id=645075 + */ + fixFirefoxAnchorBug : function() { + if (document.location.hash) + window.setTimeout(function() { + document.location.href += ''; + }, 10); + }, + + /** + * highlight the search words provided in the url in the text + */ + highlightSearchWords : function() { + var params = $.getQueryParameters(); + var terms = (params.highlight) ? params.highlight[0].split(/\s+/) : []; + if (terms.length) { + var body = $('div.body'); + if (!body.length) { + body = $('body'); + } + window.setTimeout(function() { + $.each(terms, function() { + body.highlightText(this.toLowerCase(), 'highlighted'); + }); + }, 10); + $('') + .appendTo($('#searchbox')); + } + }, + + /** + * init the domain index toggle buttons + */ + initIndexTable : function() { + var togglers = $('img.toggler').click(function() { + var src = $(this).attr('src'); + var idnum = $(this).attr('id').substr(7); + $('tr.cg-' + idnum).toggle(); + if (src.substr(-9) == 'minus.png') + $(this).attr('src', src.substr(0, src.length-9) + 'plus.png'); + else + $(this).attr('src', src.substr(0, src.length-8) + 'minus.png'); + }).css('display', ''); + if (DOCUMENTATION_OPTIONS.COLLAPSE_INDEX) { + togglers.click(); + } + }, + + /** + * helper function to hide the search marks again + */ + hideSearchWords : function() { + $('#searchbox .highlight-link').fadeOut(300); + $('span.highlighted').removeClass('highlighted'); + }, + + /** + * make the url absolute + */ + makeURL : function(relativeURL) { + return DOCUMENTATION_OPTIONS.URL_ROOT + '/' + relativeURL; + }, + + /** + * get the current relative url + */ + getCurrentURL : function() { + var path = document.location.pathname; + var parts = path.split(/\//); + $.each(DOCUMENTATION_OPTIONS.URL_ROOT.split(/\//), function() { + if (this == '..') + parts.pop(); + }); + var url = parts.join('/'); + return path.substring(url.lastIndexOf('/') + 1, path.length - 1); + }, + + initOnKeyListeners: function() { + $(document).keyup(function(event) { + var activeElementType = document.activeElement.tagName; + // don't navigate when in search box or textarea + if (activeElementType !== 'TEXTAREA' && activeElementType !== 'INPUT' && activeElementType !== 'SELECT') { + switch (event.keyCode) { + case 37: // left + var prevHref = $('link[rel="prev"]').prop('href'); + if (prevHref) { + window.location.href = prevHref; + return false; + } + case 39: // right + var nextHref = $('link[rel="next"]').prop('href'); + if (nextHref) { + window.location.href = nextHref; + return false; + } + } + } + }); + } +}; + +// quick alias for translations +_ = Documentation.gettext; + +$(document).ready(function() { + Documentation.init(); +}); \ No newline at end of file diff --git a/php/apps/phpmyadmin49/doc/html/_static/down-pressed.png b/php/apps/phpmyadmin49/doc/html/_static/down-pressed.png new file mode 100644 index 00000000..20cd4e2b Binary files /dev/null and b/php/apps/phpmyadmin49/doc/html/_static/down-pressed.png differ diff --git a/php/apps/phpmyadmin49/doc/html/_static/down.png b/php/apps/phpmyadmin49/doc/html/_static/down.png new file mode 100644 index 00000000..b440895d Binary files /dev/null and b/php/apps/phpmyadmin49/doc/html/_static/down.png differ diff --git a/php/apps/phpmyadmin49/doc/html/_static/file.png b/php/apps/phpmyadmin49/doc/html/_static/file.png new file mode 100644 index 00000000..eedc8194 Binary files /dev/null and b/php/apps/phpmyadmin49/doc/html/_static/file.png differ diff --git a/php/apps/phpmyadmin49/doc/html/_static/jquery.js b/php/apps/phpmyadmin49/doc/html/_static/jquery.js new file mode 100644 index 00000000..6ffad14f --- /dev/null +++ b/php/apps/phpmyadmin49/doc/html/_static/jquery.js @@ -0,0 +1,10220 @@ +/*! + * jQuery JavaScript Library v3.1.1 + * https://jquery.com/ + * + * Includes Sizzle.js + * https://sizzlejs.com/ + * + * Copyright jQuery Foundation and other contributors + * Released under the MIT license + * https://jquery.org/license + * + * Date: 2019-04-18T20:57Z + */ +( function( global, factory ) { + + "use strict"; + + if ( typeof module === "object" && typeof module.exports === "object" ) { + + // For CommonJS and CommonJS-like environments where a proper `window` + // is present, execute the factory and get jQuery. + // For environments that do not have a `window` with a `document` + // (such as Node.js), expose a factory as module.exports. + // This accentuates the need for the creation of a real `window`. + // e.g. var jQuery = require("jquery")(window); + // See ticket #14549 for more info. + module.exports = global.document ? + factory( global, true ) : + function( w ) { + if ( !w.document ) { + throw new Error( "jQuery requires a window with a document" ); + } + return factory( w ); + }; + } else { + factory( global ); + } + +// Pass this if window is not defined yet +} )( typeof window !== "undefined" ? window : this, function( window, noGlobal ) { + +// Edge <= 12 - 13+, Firefox <=18 - 45+, IE 10 - 11, Safari 5.1 - 9+, iOS 6 - 9.1 +// throw exceptions when non-strict code (e.g., ASP.NET 4.5) accesses strict mode +// arguments.callee.caller (trac-13335). But as of jQuery 3.0 (2016), strict mode should be common +// enough that all such attempts are guarded in a try block. + + +var arr = []; + +var document = window.document; + +var getProto = Object.getPrototypeOf; + +var slice = arr.slice; + +var concat = arr.concat; + +var push = arr.push; + +var indexOf = arr.indexOf; + +var class2type = {}; + +var toString = class2type.toString; + +var hasOwn = class2type.hasOwnProperty; + +var fnToString = hasOwn.toString; + +var ObjectFunctionString = fnToString.call( Object ); + +var support = {}; + + + + function DOMEval( code, doc ) { + doc = doc || document; + + var script = doc.createElement( "script" ); + + script.text = code; + doc.head.appendChild( script ).parentNode.removeChild( script ); + } +/* global Symbol */ +// Defining this global in .eslintrc.json would create a danger of using the global +// unguarded in another place, it seems safer to define global only for this module + + + +var + version = "3.1.1", + + // Define a local copy of jQuery + jQuery = function( selector, context ) { + + // The jQuery object is actually just the init constructor 'enhanced' + // Need init if jQuery is called (just allow error to be thrown if not included) + return new jQuery.fn.init( selector, context ); + }, + + // Support: Android <=4.0 only + // Make sure we trim BOM and NBSP + rtrim = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, + + // Matches dashed string for camelizing + rmsPrefix = /^-ms-/, + rdashAlpha = /-([a-z])/g, + + // Used by jQuery.camelCase as callback to replace() + fcamelCase = function( all, letter ) { + return letter.toUpperCase(); + }; + +jQuery.fn = jQuery.prototype = { + + // The current version of jQuery being used + jquery: version, + + constructor: jQuery, + + // The default length of a jQuery object is 0 + length: 0, + + toArray: function() { + return slice.call( this ); + }, + + // Get the Nth element in the matched element set OR + // Get the whole matched element set as a clean array + get: function( num ) { + + // Return all the elements in a clean array + if ( num == null ) { + return slice.call( this ); + } + + // Return just the one element from the set + return num < 0 ? this[ num + this.length ] : this[ num ]; + }, + + // Take an array of elements and push it onto the stack + // (returning the new matched element set) + pushStack: function( elems ) { + + // Build a new jQuery matched element set + var ret = jQuery.merge( this.constructor(), elems ); + + // Add the old object onto the stack (as a reference) + ret.prevObject = this; + + // Return the newly-formed element set + return ret; + }, + + // Execute a callback for every element in the matched set. + each: function( callback ) { + return jQuery.each( this, callback ); + }, + + map: function( callback ) { + return this.pushStack( jQuery.map( this, function( elem, i ) { + return callback.call( elem, i, elem ); + } ) ); + }, + + slice: function() { + return this.pushStack( slice.apply( this, arguments ) ); + }, + + first: function() { + return this.eq( 0 ); + }, + + last: function() { + return this.eq( -1 ); + }, + + eq: function( i ) { + var len = this.length, + j = +i + ( i < 0 ? len : 0 ); + return this.pushStack( j >= 0 && j < len ? [ this[ j ] ] : [] ); + }, + + end: function() { + return this.prevObject || this.constructor(); + }, + + // For internal use only. + // Behaves like an Array's method, not like a jQuery method. + push: push, + sort: arr.sort, + splice: arr.splice +}; + +jQuery.extend = jQuery.fn.extend = function() { + var options, name, src, copy, copyIsArray, clone, + target = arguments[ 0 ] || {}, + i = 1, + length = arguments.length, + deep = false; + + // Handle a deep copy situation + if ( typeof target === "boolean" ) { + deep = target; + + // Skip the boolean and the target + target = arguments[ i ] || {}; + i++; + } + + // Handle case when target is a string or something (possible in deep copy) + if ( typeof target !== "object" && !jQuery.isFunction( target ) ) { + target = {}; + } + + // Extend jQuery itself if only one argument is passed + if ( i === length ) { + target = this; + i--; + } + + for ( ; i < length; i++ ) { + + // Only deal with non-null/undefined values + if ( ( options = arguments[ i ] ) != null ) { + + // Extend the base object + for ( name in options ) { + src = target[ name ]; + copy = options[ name ]; + + // Prevent Object.prototype pollution + // Prevent never-ending loop + if ( name === "__proto__" || target === copy ) { + continue; + } + + // Recurse if we're merging plain objects or arrays + if ( deep && copy && ( jQuery.isPlainObject( copy ) || + ( copyIsArray = jQuery.isArray( copy ) ) ) ) { + + if ( copyIsArray ) { + copyIsArray = false; + clone = src && jQuery.isArray( src ) ? src : []; + + } else { + clone = src && jQuery.isPlainObject( src ) ? src : {}; + } + + // Never move original objects, clone them + target[ name ] = jQuery.extend( deep, clone, copy ); + + // Don't bring in undefined values + } else if ( copy !== undefined ) { + target[ name ] = copy; + } + } + } + } + + // Return the modified object + return target; +}; + +jQuery.extend( { + + // Unique for each copy of jQuery on the page + expando: "jQuery" + ( version + Math.random() ).replace( /\D/g, "" ), + + // Assume jQuery is ready without the ready module + isReady: true, + + error: function( msg ) { + throw new Error( msg ); + }, + + noop: function() {}, + + isFunction: function( obj ) { + return jQuery.type( obj ) === "function"; + }, + + isArray: Array.isArray, + + isWindow: function( obj ) { + return obj != null && obj === obj.window; + }, + + isNumeric: function( obj ) { + + // As of jQuery 3.0, isNumeric is limited to + // strings and numbers (primitives or objects) + // that can be coerced to finite numbers (gh-2662) + var type = jQuery.type( obj ); + return ( type === "number" || type === "string" ) && + + // parseFloat NaNs numeric-cast false positives ("") + // ...but misinterprets leading-number strings, particularly hex literals ("0x...") + // subtraction forces infinities to NaN + !isNaN( obj - parseFloat( obj ) ); + }, + + isPlainObject: function( obj ) { + var proto, Ctor; + + // Detect obvious negatives + // Use toString instead of jQuery.type to catch host objects + if ( !obj || toString.call( obj ) !== "[object Object]" ) { + return false; + } + + proto = getProto( obj ); + + // Objects with no prototype (e.g., `Object.create( null )`) are plain + if ( !proto ) { + return true; + } + + // Objects with prototype are plain iff they were constructed by a global Object function + Ctor = hasOwn.call( proto, "constructor" ) && proto.constructor; + return typeof Ctor === "function" && fnToString.call( Ctor ) === ObjectFunctionString; + }, + + isEmptyObject: function( obj ) { + + /* eslint-disable no-unused-vars */ + // See https://github.com/eslint/eslint/issues/6125 + var name; + + for ( name in obj ) { + return false; + } + return true; + }, + + type: function( obj ) { + if ( obj == null ) { + return obj + ""; + } + + // Support: Android <=2.3 only (functionish RegExp) + return typeof obj === "object" || typeof obj === "function" ? + class2type[ toString.call( obj ) ] || "object" : + typeof obj; + }, + + // Evaluates a script in a global context + globalEval: function( code ) { + DOMEval( code ); + }, + + // Convert dashed to camelCase; used by the css and data modules + // Support: IE <=9 - 11, Edge 12 - 13 + // Microsoft forgot to hump their vendor prefix (#9572) + camelCase: function( string ) { + return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase ); + }, + + nodeName: function( elem, name ) { + return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase(); + }, + + each: function( obj, callback ) { + var length, i = 0; + + if ( isArrayLike( obj ) ) { + length = obj.length; + for ( ; i < length; i++ ) { + if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) { + break; + } + } + } else { + for ( i in obj ) { + if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) { + break; + } + } + } + + return obj; + }, + + // Support: Android <=4.0 only + trim: function( text ) { + return text == null ? + "" : + ( text + "" ).replace( rtrim, "" ); + }, + + // results is for internal usage only + makeArray: function( arr, results ) { + var ret = results || []; + + if ( arr != null ) { + if ( isArrayLike( Object( arr ) ) ) { + jQuery.merge( ret, + typeof arr === "string" ? + [ arr ] : arr + ); + } else { + push.call( ret, arr ); + } + } + + return ret; + }, + + inArray: function( elem, arr, i ) { + return arr == null ? -1 : indexOf.call( arr, elem, i ); + }, + + // Support: Android <=4.0 only, PhantomJS 1 only + // push.apply(_, arraylike) throws on ancient WebKit + merge: function( first, second ) { + var len = +second.length, + j = 0, + i = first.length; + + for ( ; j < len; j++ ) { + first[ i++ ] = second[ j ]; + } + + first.length = i; + + return first; + }, + + grep: function( elems, callback, invert ) { + var callbackInverse, + matches = [], + i = 0, + length = elems.length, + callbackExpect = !invert; + + // Go through the array, only saving the items + // that pass the validator function + for ( ; i < length; i++ ) { + callbackInverse = !callback( elems[ i ], i ); + if ( callbackInverse !== callbackExpect ) { + matches.push( elems[ i ] ); + } + } + + return matches; + }, + + // arg is for internal usage only + map: function( elems, callback, arg ) { + var length, value, + i = 0, + ret = []; + + // Go through the array, translating each of the items to their new values + if ( isArrayLike( elems ) ) { + length = elems.length; + for ( ; i < length; i++ ) { + value = callback( elems[ i ], i, arg ); + + if ( value != null ) { + ret.push( value ); + } + } + + // Go through every key on the object, + } else { + for ( i in elems ) { + value = callback( elems[ i ], i, arg ); + + if ( value != null ) { + ret.push( value ); + } + } + } + + // Flatten any nested arrays + return concat.apply( [], ret ); + }, + + // A global GUID counter for objects + guid: 1, + + // Bind a function to a context, optionally partially applying any + // arguments. + proxy: function( fn, context ) { + var tmp, args, proxy; + + if ( typeof context === "string" ) { + tmp = fn[ context ]; + context = fn; + fn = tmp; + } + + // Quick check to determine if target is callable, in the spec + // this throws a TypeError, but we will just return undefined. + if ( !jQuery.isFunction( fn ) ) { + return undefined; + } + + // Simulated bind + args = slice.call( arguments, 2 ); + proxy = function() { + return fn.apply( context || this, args.concat( slice.call( arguments ) ) ); + }; + + // Set the guid of unique handler to the same of original handler, so it can be removed + proxy.guid = fn.guid = fn.guid || jQuery.guid++; + + return proxy; + }, + + now: Date.now, + + // jQuery.support is not used in Core but other projects attach their + // properties to it so it needs to exist. + support: support +} ); + +if ( typeof Symbol === "function" ) { + jQuery.fn[ Symbol.iterator ] = arr[ Symbol.iterator ]; +} + +// Populate the class2type map +jQuery.each( "Boolean Number String Function Array Date RegExp Object Error Symbol".split( " " ), +function( i, name ) { + class2type[ "[object " + name + "]" ] = name.toLowerCase(); +} ); + +function isArrayLike( obj ) { + + // Support: real iOS 8.2 only (not reproducible in simulator) + // `in` check used to prevent JIT error (gh-2145) + // hasOwn isn't used here due to false negatives + // regarding Nodelist length in IE + var length = !!obj && "length" in obj && obj.length, + type = jQuery.type( obj ); + + if ( type === "function" || jQuery.isWindow( obj ) ) { + return false; + } + + return type === "array" || length === 0 || + typeof length === "number" && length > 0 && ( length - 1 ) in obj; +} +var Sizzle = +/*! + * Sizzle CSS Selector Engine v2.3.3 + * https://sizzlejs.com/ + * + * Copyright jQuery Foundation and other contributors + * Released under the MIT license + * http://jquery.org/license + * + * Date: 2016-08-08 + */ +(function( window ) { + +var i, + support, + Expr, + getText, + isXML, + tokenize, + compile, + select, + outermostContext, + sortInput, + hasDuplicate, + + // Local document vars + setDocument, + document, + docElem, + documentIsHTML, + rbuggyQSA, + rbuggyMatches, + matches, + contains, + + // Instance-specific data + expando = "sizzle" + 1 * new Date(), + preferredDoc = window.document, + dirruns = 0, + done = 0, + classCache = createCache(), + tokenCache = createCache(), + compilerCache = createCache(), + sortOrder = function( a, b ) { + if ( a === b ) { + hasDuplicate = true; + } + return 0; + }, + + // Instance methods + hasOwn = ({}).hasOwnProperty, + arr = [], + pop = arr.pop, + push_native = arr.push, + push = arr.push, + slice = arr.slice, + // Use a stripped-down indexOf as it's faster than native + // https://jsperf.com/thor-indexof-vs-for/5 + indexOf = function( list, elem ) { + var i = 0, + len = list.length; + for ( ; i < len; i++ ) { + if ( list[i] === elem ) { + return i; + } + } + return -1; + }, + + booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped", + + // Regular expressions + + // http://www.w3.org/TR/css3-selectors/#whitespace + whitespace = "[\\x20\\t\\r\\n\\f]", + + // http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier + identifier = "(?:\\\\.|[\\w-]|[^\0-\\xa0])+", + + // Attribute selectors: http://www.w3.org/TR/selectors/#attribute-selectors + attributes = "\\[" + whitespace + "*(" + identifier + ")(?:" + whitespace + + // Operator (capture 2) + "*([*^$|!~]?=)" + whitespace + + // "Attribute values must be CSS identifiers [capture 5] or strings [capture 3 or capture 4]" + "*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|(" + identifier + "))|)" + whitespace + + "*\\]", + + pseudos = ":(" + identifier + ")(?:\\((" + + // To reduce the number of selectors needing tokenize in the preFilter, prefer arguments: + // 1. quoted (capture 3; capture 4 or capture 5) + "('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|" + + // 2. simple (capture 6) + "((?:\\\\.|[^\\\\()[\\]]|" + attributes + ")*)|" + + // 3. anything else (capture 2) + ".*" + + ")\\)|)", + + // Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter + rwhitespace = new RegExp( whitespace + "+", "g" ), + rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$", "g" ), + + rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ), + rcombinators = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" + whitespace + "*" ), + + rattributeQuotes = new RegExp( "=" + whitespace + "*([^\\]'\"]*?)" + whitespace + "*\\]", "g" ), + + rpseudo = new RegExp( pseudos ), + ridentifier = new RegExp( "^" + identifier + "$" ), + + matchExpr = { + "ID": new RegExp( "^#(" + identifier + ")" ), + "CLASS": new RegExp( "^\\.(" + identifier + ")" ), + "TAG": new RegExp( "^(" + identifier + "|[*])" ), + "ATTR": new RegExp( "^" + attributes ), + "PSEUDO": new RegExp( "^" + pseudos ), + "CHILD": new RegExp( "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + whitespace + + "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + whitespace + + "*(\\d+)|))" + whitespace + "*\\)|)", "i" ), + "bool": new RegExp( "^(?:" + booleans + ")$", "i" ), + // For use in libraries implementing .is() + // We use this for POS matching in `select` + "needsContext": new RegExp( "^" + whitespace + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" + + whitespace + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" ) + }, + + rinputs = /^(?:input|select|textarea|button)$/i, + rheader = /^h\d$/i, + + rnative = /^[^{]+\{\s*\[native \w/, + + // Easily-parseable/retrievable ID or TAG or CLASS selectors + rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/, + + rsibling = /[+~]/, + + // CSS escapes + // http://www.w3.org/TR/CSS21/syndata.html#escaped-characters + runescape = new RegExp( "\\\\([\\da-f]{1,6}" + whitespace + "?|(" + whitespace + ")|.)", "ig" ), + funescape = function( _, escaped, escapedWhitespace ) { + var high = "0x" + escaped - 0x10000; + // NaN means non-codepoint + // Support: Firefox<24 + // Workaround erroneous numeric interpretation of +"0x" + return high !== high || escapedWhitespace ? + escaped : + high < 0 ? + // BMP codepoint + String.fromCharCode( high + 0x10000 ) : + // Supplemental Plane codepoint (surrogate pair) + String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 ); + }, + + // CSS string/identifier serialization + // https://drafts.csswg.org/cssom/#common-serializing-idioms + rcssescape = /([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g, + fcssescape = function( ch, asCodePoint ) { + if ( asCodePoint ) { + + // U+0000 NULL becomes U+FFFD REPLACEMENT CHARACTER + if ( ch === "\0" ) { + return "\uFFFD"; + } + + // Control characters and (dependent upon position) numbers get escaped as code points + return ch.slice( 0, -1 ) + "\\" + ch.charCodeAt( ch.length - 1 ).toString( 16 ) + " "; + } + + // Other potentially-special ASCII characters get backslash-escaped + return "\\" + ch; + }, + + // Used for iframes + // See setDocument() + // Removing the function wrapper causes a "Permission Denied" + // error in IE + unloadHandler = function() { + setDocument(); + }, + + disabledAncestor = addCombinator( + function( elem ) { + return elem.disabled === true && ("form" in elem || "label" in elem); + }, + { dir: "parentNode", next: "legend" } + ); + +// Optimize for push.apply( _, NodeList ) +try { + push.apply( + (arr = slice.call( preferredDoc.childNodes )), + preferredDoc.childNodes + ); + // Support: Android<4.0 + // Detect silently failing push.apply + arr[ preferredDoc.childNodes.length ].nodeType; +} catch ( e ) { + push = { apply: arr.length ? + + // Leverage slice if possible + function( target, els ) { + push_native.apply( target, slice.call(els) ); + } : + + // Support: IE<9 + // Otherwise append directly + function( target, els ) { + var j = target.length, + i = 0; + // Can't trust NodeList.length + while ( (target[j++] = els[i++]) ) {} + target.length = j - 1; + } + }; +} + +function Sizzle( selector, context, results, seed ) { + var m, i, elem, nid, match, groups, newSelector, + newContext = context && context.ownerDocument, + + // nodeType defaults to 9, since context defaults to document + nodeType = context ? context.nodeType : 9; + + results = results || []; + + // Return early from calls with invalid selector or context + if ( typeof selector !== "string" || !selector || + nodeType !== 1 && nodeType !== 9 && nodeType !== 11 ) { + + return results; + } + + // Try to shortcut find operations (as opposed to filters) in HTML documents + if ( !seed ) { + + if ( ( context ? context.ownerDocument || context : preferredDoc ) !== document ) { + setDocument( context ); + } + context = context || document; + + if ( documentIsHTML ) { + + // If the selector is sufficiently simple, try using a "get*By*" DOM method + // (excepting DocumentFragment context, where the methods don't exist) + if ( nodeType !== 11 && (match = rquickExpr.exec( selector )) ) { + + // ID selector + if ( (m = match[1]) ) { + + // Document context + if ( nodeType === 9 ) { + if ( (elem = context.getElementById( m )) ) { + + // Support: IE, Opera, Webkit + // TODO: identify versions + // getElementById can match elements by name instead of ID + if ( elem.id === m ) { + results.push( elem ); + return results; + } + } else { + return results; + } + + // Element context + } else { + + // Support: IE, Opera, Webkit + // TODO: identify versions + // getElementById can match elements by name instead of ID + if ( newContext && (elem = newContext.getElementById( m )) && + contains( context, elem ) && + elem.id === m ) { + + results.push( elem ); + return results; + } + } + + // Type selector + } else if ( match[2] ) { + push.apply( results, context.getElementsByTagName( selector ) ); + return results; + + // Class selector + } else if ( (m = match[3]) && support.getElementsByClassName && + context.getElementsByClassName ) { + + push.apply( results, context.getElementsByClassName( m ) ); + return results; + } + } + + // Take advantage of querySelectorAll + if ( support.qsa && + !compilerCache[ selector + " " ] && + (!rbuggyQSA || !rbuggyQSA.test( selector )) ) { + + if ( nodeType !== 1 ) { + newContext = context; + newSelector = selector; + + // qSA looks outside Element context, which is not what we want + // Thanks to Andrew Dupont for this workaround technique + // Support: IE <=8 + // Exclude object elements + } else if ( context.nodeName.toLowerCase() !== "object" ) { + + // Capture the context ID, setting it first if necessary + if ( (nid = context.getAttribute( "id" )) ) { + nid = nid.replace( rcssescape, fcssescape ); + } else { + context.setAttribute( "id", (nid = expando) ); + } + + // Prefix every selector in the list + groups = tokenize( selector ); + i = groups.length; + while ( i-- ) { + groups[i] = "#" + nid + " " + toSelector( groups[i] ); + } + newSelector = groups.join( "," ); + + // Expand context for sibling selectors + newContext = rsibling.test( selector ) && testContext( context.parentNode ) || + context; + } + + if ( newSelector ) { + try { + push.apply( results, + newContext.querySelectorAll( newSelector ) + ); + return results; + } catch ( qsaError ) { + } finally { + if ( nid === expando ) { + context.removeAttribute( "id" ); + } + } + } + } + } + } + + // All others + return select( selector.replace( rtrim, "$1" ), context, results, seed ); +} + +/** + * Create key-value caches of limited size + * @returns {function(string, object)} Returns the Object data after storing it on itself with + * property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength) + * deleting the oldest entry + */ +function createCache() { + var keys = []; + + function cache( key, value ) { + // Use (key + " ") to avoid collision with native prototype properties (see Issue #157) + if ( keys.push( key + " " ) > Expr.cacheLength ) { + // Only keep the most recent entries + delete cache[ keys.shift() ]; + } + return (cache[ key + " " ] = value); + } + return cache; +} + +/** + * Mark a function for special use by Sizzle + * @param {Function} fn The function to mark + */ +function markFunction( fn ) { + fn[ expando ] = true; + return fn; +} + +/** + * Support testing using an element + * @param {Function} fn Passed the created element and returns a boolean result + */ +function assert( fn ) { + var el = document.createElement("fieldset"); + + try { + return !!fn( el ); + } catch (e) { + return false; + } finally { + // Remove from its parent by default + if ( el.parentNode ) { + el.parentNode.removeChild( el ); + } + // release memory in IE + el = null; + } +} + +/** + * Adds the same handler for all of the specified attrs + * @param {String} attrs Pipe-separated list of attributes + * @param {Function} handler The method that will be applied + */ +function addHandle( attrs, handler ) { + var arr = attrs.split("|"), + i = arr.length; + + while ( i-- ) { + Expr.attrHandle[ arr[i] ] = handler; + } +} + +/** + * Checks document order of two siblings + * @param {Element} a + * @param {Element} b + * @returns {Number} Returns less than 0 if a precedes b, greater than 0 if a follows b + */ +function siblingCheck( a, b ) { + var cur = b && a, + diff = cur && a.nodeType === 1 && b.nodeType === 1 && + a.sourceIndex - b.sourceIndex; + + // Use IE sourceIndex if available on both nodes + if ( diff ) { + return diff; + } + + // Check if b follows a + if ( cur ) { + while ( (cur = cur.nextSibling) ) { + if ( cur === b ) { + return -1; + } + } + } + + return a ? 1 : -1; +} + +/** + * Returns a function to use in pseudos for input types + * @param {String} type + */ +function createInputPseudo( type ) { + return function( elem ) { + var name = elem.nodeName.toLowerCase(); + return name === "input" && elem.type === type; + }; +} + +/** + * Returns a function to use in pseudos for buttons + * @param {String} type + */ +function createButtonPseudo( type ) { + return function( elem ) { + var name = elem.nodeName.toLowerCase(); + return (name === "input" || name === "button") && elem.type === type; + }; +} + +/** + * Returns a function to use in pseudos for :enabled/:disabled + * @param {Boolean} disabled true for :disabled; false for :enabled + */ +function createDisabledPseudo( disabled ) { + + // Known :disabled false positives: fieldset[disabled] > legend:nth-of-type(n+2) :can-disable + return function( elem ) { + + // Only certain elements can match :enabled or :disabled + // https://html.spec.whatwg.org/multipage/scripting.html#selector-enabled + // https://html.spec.whatwg.org/multipage/scripting.html#selector-disabled + if ( "form" in elem ) { + + // Check for inherited disabledness on relevant non-disabled elements: + // * listed form-associated elements in a disabled fieldset + // https://html.spec.whatwg.org/multipage/forms.html#category-listed + // https://html.spec.whatwg.org/multipage/forms.html#concept-fe-disabled + // * option elements in a disabled optgroup + // https://html.spec.whatwg.org/multipage/forms.html#concept-option-disabled + // All such elements have a "form" property. + if ( elem.parentNode && elem.disabled === false ) { + + // Option elements defer to a parent optgroup if present + if ( "label" in elem ) { + if ( "label" in elem.parentNode ) { + return elem.parentNode.disabled === disabled; + } else { + return elem.disabled === disabled; + } + } + + // Support: IE 6 - 11 + // Use the isDisabled shortcut property to check for disabled fieldset ancestors + return elem.isDisabled === disabled || + + // Where there is no isDisabled, check manually + /* jshint -W018 */ + elem.isDisabled !== !disabled && + disabledAncestor( elem ) === disabled; + } + + return elem.disabled === disabled; + + // Try to winnow out elements that can't be disabled before trusting the disabled property. + // Some victims get caught in our net (label, legend, menu, track), but it shouldn't + // even exist on them, let alone have a boolean value. + } else if ( "label" in elem ) { + return elem.disabled === disabled; + } + + // Remaining elements are neither :enabled nor :disabled + return false; + }; +} + +/** + * Returns a function to use in pseudos for positionals + * @param {Function} fn + */ +function createPositionalPseudo( fn ) { + return markFunction(function( argument ) { + argument = +argument; + return markFunction(function( seed, matches ) { + var j, + matchIndexes = fn( [], seed.length, argument ), + i = matchIndexes.length; + + // Match elements found at the specified indexes + while ( i-- ) { + if ( seed[ (j = matchIndexes[i]) ] ) { + seed[j] = !(matches[j] = seed[j]); + } + } + }); + }); +} + +/** + * Checks a node for validity as a Sizzle context + * @param {Element|Object=} context + * @returns {Element|Object|Boolean} The input node if acceptable, otherwise a falsy value + */ +function testContext( context ) { + return context && typeof context.getElementsByTagName !== "undefined" && context; +} + +// Expose support vars for convenience +support = Sizzle.support = {}; + +/** + * Detects XML nodes + * @param {Element|Object} elem An element or a document + * @returns {Boolean} True iff elem is a non-HTML XML node + */ +isXML = Sizzle.isXML = function( elem ) { + // documentElement is verified for cases where it doesn't yet exist + // (such as loading iframes in IE - #4833) + var documentElement = elem && (elem.ownerDocument || elem).documentElement; + return documentElement ? documentElement.nodeName !== "HTML" : false; +}; + +/** + * Sets document-related variables once based on the current document + * @param {Element|Object} [doc] An element or document object to use to set the document + * @returns {Object} Returns the current document + */ +setDocument = Sizzle.setDocument = function( node ) { + var hasCompare, subWindow, + doc = node ? node.ownerDocument || node : preferredDoc; + + // Return early if doc is invalid or already selected + if ( doc === document || doc.nodeType !== 9 || !doc.documentElement ) { + return document; + } + + // Update global variables + document = doc; + docElem = document.documentElement; + documentIsHTML = !isXML( document ); + + // Support: IE 9-11, Edge + // Accessing iframe documents after unload throws "permission denied" errors (jQuery #13936) + if ( preferredDoc !== document && + (subWindow = document.defaultView) && subWindow.top !== subWindow ) { + + // Support: IE 11, Edge + if ( subWindow.addEventListener ) { + subWindow.addEventListener( "unload", unloadHandler, false ); + + // Support: IE 9 - 10 only + } else if ( subWindow.attachEvent ) { + subWindow.attachEvent( "onunload", unloadHandler ); + } + } + + /* Attributes + ---------------------------------------------------------------------- */ + + // Support: IE<8 + // Verify that getAttribute really returns attributes and not properties + // (excepting IE8 booleans) + support.attributes = assert(function( el ) { + el.className = "i"; + return !el.getAttribute("className"); + }); + + /* getElement(s)By* + ---------------------------------------------------------------------- */ + + // Check if getElementsByTagName("*") returns only elements + support.getElementsByTagName = assert(function( el ) { + el.appendChild( document.createComment("") ); + return !el.getElementsByTagName("*").length; + }); + + // Support: IE<9 + support.getElementsByClassName = rnative.test( document.getElementsByClassName ); + + // Support: IE<10 + // Check if getElementById returns elements by name + // The broken getElementById methods don't pick up programmatically-set names, + // so use a roundabout getElementsByName test + support.getById = assert(function( el ) { + docElem.appendChild( el ).id = expando; + return !document.getElementsByName || !document.getElementsByName( expando ).length; + }); + + // ID filter and find + if ( support.getById ) { + Expr.filter["ID"] = function( id ) { + var attrId = id.replace( runescape, funescape ); + return function( elem ) { + return elem.getAttribute("id") === attrId; + }; + }; + Expr.find["ID"] = function( id, context ) { + if ( typeof context.getElementById !== "undefined" && documentIsHTML ) { + var elem = context.getElementById( id ); + return elem ? [ elem ] : []; + } + }; + } else { + Expr.filter["ID"] = function( id ) { + var attrId = id.replace( runescape, funescape ); + return function( elem ) { + var node = typeof elem.getAttributeNode !== "undefined" && + elem.getAttributeNode("id"); + return node && node.value === attrId; + }; + }; + + // Support: IE 6 - 7 only + // getElementById is not reliable as a find shortcut + Expr.find["ID"] = function( id, context ) { + if ( typeof context.getElementById !== "undefined" && documentIsHTML ) { + var node, i, elems, + elem = context.getElementById( id ); + + if ( elem ) { + + // Verify the id attribute + node = elem.getAttributeNode("id"); + if ( node && node.value === id ) { + return [ elem ]; + } + + // Fall back on getElementsByName + elems = context.getElementsByName( id ); + i = 0; + while ( (elem = elems[i++]) ) { + node = elem.getAttributeNode("id"); + if ( node && node.value === id ) { + return [ elem ]; + } + } + } + + return []; + } + }; + } + + // Tag + Expr.find["TAG"] = support.getElementsByTagName ? + function( tag, context ) { + if ( typeof context.getElementsByTagName !== "undefined" ) { + return context.getElementsByTagName( tag ); + + // DocumentFragment nodes don't have gEBTN + } else if ( support.qsa ) { + return context.querySelectorAll( tag ); + } + } : + + function( tag, context ) { + var elem, + tmp = [], + i = 0, + // By happy coincidence, a (broken) gEBTN appears on DocumentFragment nodes too + results = context.getElementsByTagName( tag ); + + // Filter out possible comments + if ( tag === "*" ) { + while ( (elem = results[i++]) ) { + if ( elem.nodeType === 1 ) { + tmp.push( elem ); + } + } + + return tmp; + } + return results; + }; + + // Class + Expr.find["CLASS"] = support.getElementsByClassName && function( className, context ) { + if ( typeof context.getElementsByClassName !== "undefined" && documentIsHTML ) { + return context.getElementsByClassName( className ); + } + }; + + /* QSA/matchesSelector + ---------------------------------------------------------------------- */ + + // QSA and matchesSelector support + + // matchesSelector(:active) reports false when true (IE9/Opera 11.5) + rbuggyMatches = []; + + // qSa(:focus) reports false when true (Chrome 21) + // We allow this because of a bug in IE8/9 that throws an error + // whenever `document.activeElement` is accessed on an iframe + // So, we allow :focus to pass through QSA all the time to avoid the IE error + // See https://bugs.jquery.com/ticket/13378 + rbuggyQSA = []; + + if ( (support.qsa = rnative.test( document.querySelectorAll )) ) { + // Build QSA regex + // Regex strategy adopted from Diego Perini + assert(function( el ) { + // Select is set to empty string on purpose + // This is to test IE's treatment of not explicitly + // setting a boolean content attribute, + // since its presence should be enough + // https://bugs.jquery.com/ticket/12359 + docElem.appendChild( el ).innerHTML = "" + + ""; + + // Support: IE8, Opera 11-12.16 + // Nothing should be selected when empty strings follow ^= or $= or *= + // The test attribute must be unknown in Opera but "safe" for WinRT + // https://msdn.microsoft.com/en-us/library/ie/hh465388.aspx#attribute_section + if ( el.querySelectorAll("[msallowcapture^='']").length ) { + rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:''|\"\")" ); + } + + // Support: IE8 + // Boolean attributes and "value" are not treated correctly + if ( !el.querySelectorAll("[selected]").length ) { + rbuggyQSA.push( "\\[" + whitespace + "*(?:value|" + booleans + ")" ); + } + + // Support: Chrome<29, Android<4.4, Safari<7.0+, iOS<7.0+, PhantomJS<1.9.8+ + if ( !el.querySelectorAll( "[id~=" + expando + "-]" ).length ) { + rbuggyQSA.push("~="); + } + + // Webkit/Opera - :checked should return selected option elements + // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked + // IE8 throws error here and will not see later tests + if ( !el.querySelectorAll(":checked").length ) { + rbuggyQSA.push(":checked"); + } + + // Support: Safari 8+, iOS 8+ + // https://bugs.webkit.org/show_bug.cgi?id=136851 + // In-page `selector#id sibling-combinator selector` fails + if ( !el.querySelectorAll( "a#" + expando + "+*" ).length ) { + rbuggyQSA.push(".#.+[+~]"); + } + }); + + assert(function( el ) { + el.innerHTML = "" + + ""; + + // Support: Windows 8 Native Apps + // The type and name attributes are restricted during .innerHTML assignment + var input = document.createElement("input"); + input.setAttribute( "type", "hidden" ); + el.appendChild( input ).setAttribute( "name", "D" ); + + // Support: IE8 + // Enforce case-sensitivity of name attribute + if ( el.querySelectorAll("[name=d]").length ) { + rbuggyQSA.push( "name" + whitespace + "*[*^$|!~]?=" ); + } + + // FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled) + // IE8 throws error here and will not see later tests + if ( el.querySelectorAll(":enabled").length !== 2 ) { + rbuggyQSA.push( ":enabled", ":disabled" ); + } + + // Support: IE9-11+ + // IE's :disabled selector does not pick up the children of disabled fieldsets + docElem.appendChild( el ).disabled = true; + if ( el.querySelectorAll(":disabled").length !== 2 ) { + rbuggyQSA.push( ":enabled", ":disabled" ); + } + + // Opera 10-11 does not throw on post-comma invalid pseudos + el.querySelectorAll("*,:x"); + rbuggyQSA.push(",.*:"); + }); + } + + if ( (support.matchesSelector = rnative.test( (matches = docElem.matches || + docElem.webkitMatchesSelector || + docElem.mozMatchesSelector || + docElem.oMatchesSelector || + docElem.msMatchesSelector) )) ) { + + assert(function( el ) { + // Check to see if it's possible to do matchesSelector + // on a disconnected node (IE 9) + support.disconnectedMatch = matches.call( el, "*" ); + + // This should fail with an exception + // Gecko does not error, returns false instead + matches.call( el, "[s!='']:x" ); + rbuggyMatches.push( "!=", pseudos ); + }); + } + + rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join("|") ); + rbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join("|") ); + + /* Contains + ---------------------------------------------------------------------- */ + hasCompare = rnative.test( docElem.compareDocumentPosition ); + + // Element contains another + // Purposefully self-exclusive + // As in, an element does not contain itself + contains = hasCompare || rnative.test( docElem.contains ) ? + function( a, b ) { + var adown = a.nodeType === 9 ? a.documentElement : a, + bup = b && b.parentNode; + return a === bup || !!( bup && bup.nodeType === 1 && ( + adown.contains ? + adown.contains( bup ) : + a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16 + )); + } : + function( a, b ) { + if ( b ) { + while ( (b = b.parentNode) ) { + if ( b === a ) { + return true; + } + } + } + return false; + }; + + /* Sorting + ---------------------------------------------------------------------- */ + + // Document order sorting + sortOrder = hasCompare ? + function( a, b ) { + + // Flag for duplicate removal + if ( a === b ) { + hasDuplicate = true; + return 0; + } + + // Sort on method existence if only one input has compareDocumentPosition + var compare = !a.compareDocumentPosition - !b.compareDocumentPosition; + if ( compare ) { + return compare; + } + + // Calculate position if both inputs belong to the same document + compare = ( a.ownerDocument || a ) === ( b.ownerDocument || b ) ? + a.compareDocumentPosition( b ) : + + // Otherwise we know they are disconnected + 1; + + // Disconnected nodes + if ( compare & 1 || + (!support.sortDetached && b.compareDocumentPosition( a ) === compare) ) { + + // Choose the first element that is related to our preferred document + if ( a === document || a.ownerDocument === preferredDoc && contains(preferredDoc, a) ) { + return -1; + } + if ( b === document || b.ownerDocument === preferredDoc && contains(preferredDoc, b) ) { + return 1; + } + + // Maintain original order + return sortInput ? + ( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) : + 0; + } + + return compare & 4 ? -1 : 1; + } : + function( a, b ) { + // Exit early if the nodes are identical + if ( a === b ) { + hasDuplicate = true; + return 0; + } + + var cur, + i = 0, + aup = a.parentNode, + bup = b.parentNode, + ap = [ a ], + bp = [ b ]; + + // Parentless nodes are either documents or disconnected + if ( !aup || !bup ) { + return a === document ? -1 : + b === document ? 1 : + aup ? -1 : + bup ? 1 : + sortInput ? + ( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) : + 0; + + // If the nodes are siblings, we can do a quick check + } else if ( aup === bup ) { + return siblingCheck( a, b ); + } + + // Otherwise we need full lists of their ancestors for comparison + cur = a; + while ( (cur = cur.parentNode) ) { + ap.unshift( cur ); + } + cur = b; + while ( (cur = cur.parentNode) ) { + bp.unshift( cur ); + } + + // Walk down the tree looking for a discrepancy + while ( ap[i] === bp[i] ) { + i++; + } + + return i ? + // Do a sibling check if the nodes have a common ancestor + siblingCheck( ap[i], bp[i] ) : + + // Otherwise nodes in our document sort first + ap[i] === preferredDoc ? -1 : + bp[i] === preferredDoc ? 1 : + 0; + }; + + return document; +}; + +Sizzle.matches = function( expr, elements ) { + return Sizzle( expr, null, null, elements ); +}; + +Sizzle.matchesSelector = function( elem, expr ) { + // Set document vars if needed + if ( ( elem.ownerDocument || elem ) !== document ) { + setDocument( elem ); + } + + // Make sure that attribute selectors are quoted + expr = expr.replace( rattributeQuotes, "='$1']" ); + + if ( support.matchesSelector && documentIsHTML && + !compilerCache[ expr + " " ] && + ( !rbuggyMatches || !rbuggyMatches.test( expr ) ) && + ( !rbuggyQSA || !rbuggyQSA.test( expr ) ) ) { + + try { + var ret = matches.call( elem, expr ); + + // IE 9's matchesSelector returns false on disconnected nodes + if ( ret || support.disconnectedMatch || + // As well, disconnected nodes are said to be in a document + // fragment in IE 9 + elem.document && elem.document.nodeType !== 11 ) { + return ret; + } + } catch (e) {} + } + + return Sizzle( expr, document, null, [ elem ] ).length > 0; +}; + +Sizzle.contains = function( context, elem ) { + // Set document vars if needed + if ( ( context.ownerDocument || context ) !== document ) { + setDocument( context ); + } + return contains( context, elem ); +}; + +Sizzle.attr = function( elem, name ) { + // Set document vars if needed + if ( ( elem.ownerDocument || elem ) !== document ) { + setDocument( elem ); + } + + var fn = Expr.attrHandle[ name.toLowerCase() ], + // Don't get fooled by Object.prototype properties (jQuery #13807) + val = fn && hasOwn.call( Expr.attrHandle, name.toLowerCase() ) ? + fn( elem, name, !documentIsHTML ) : + undefined; + + return val !== undefined ? + val : + support.attributes || !documentIsHTML ? + elem.getAttribute( name ) : + (val = elem.getAttributeNode(name)) && val.specified ? + val.value : + null; +}; + +Sizzle.escape = function( sel ) { + return (sel + "").replace( rcssescape, fcssescape ); +}; + +Sizzle.error = function( msg ) { + throw new Error( "Syntax error, unrecognized expression: " + msg ); +}; + +/** + * Document sorting and removing duplicates + * @param {ArrayLike} results + */ +Sizzle.uniqueSort = function( results ) { + var elem, + duplicates = [], + j = 0, + i = 0; + + // Unless we *know* we can detect duplicates, assume their presence + hasDuplicate = !support.detectDuplicates; + sortInput = !support.sortStable && results.slice( 0 ); + results.sort( sortOrder ); + + if ( hasDuplicate ) { + while ( (elem = results[i++]) ) { + if ( elem === results[ i ] ) { + j = duplicates.push( i ); + } + } + while ( j-- ) { + results.splice( duplicates[ j ], 1 ); + } + } + + // Clear input after sorting to release objects + // See https://github.com/jquery/sizzle/pull/225 + sortInput = null; + + return results; +}; + +/** + * Utility function for retrieving the text value of an array of DOM nodes + * @param {Array|Element} elem + */ +getText = Sizzle.getText = function( elem ) { + var node, + ret = "", + i = 0, + nodeType = elem.nodeType; + + if ( !nodeType ) { + // If no nodeType, this is expected to be an array + while ( (node = elem[i++]) ) { + // Do not traverse comment nodes + ret += getText( node ); + } + } else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) { + // Use textContent for elements + // innerText usage removed for consistency of new lines (jQuery #11153) + if ( typeof elem.textContent === "string" ) { + return elem.textContent; + } else { + // Traverse its children + for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { + ret += getText( elem ); + } + } + } else if ( nodeType === 3 || nodeType === 4 ) { + return elem.nodeValue; + } + // Do not include comment or processing instruction nodes + + return ret; +}; + +Expr = Sizzle.selectors = { + + // Can be adjusted by the user + cacheLength: 50, + + createPseudo: markFunction, + + match: matchExpr, + + attrHandle: {}, + + find: {}, + + relative: { + ">": { dir: "parentNode", first: true }, + " ": { dir: "parentNode" }, + "+": { dir: "previousSibling", first: true }, + "~": { dir: "previousSibling" } + }, + + preFilter: { + "ATTR": function( match ) { + match[1] = match[1].replace( runescape, funescape ); + + // Move the given value to match[3] whether quoted or unquoted + match[3] = ( match[3] || match[4] || match[5] || "" ).replace( runescape, funescape ); + + if ( match[2] === "~=" ) { + match[3] = " " + match[3] + " "; + } + + return match.slice( 0, 4 ); + }, + + "CHILD": function( match ) { + /* matches from matchExpr["CHILD"] + 1 type (only|nth|...) + 2 what (child|of-type) + 3 argument (even|odd|\d*|\d*n([+-]\d+)?|...) + 4 xn-component of xn+y argument ([+-]?\d*n|) + 5 sign of xn-component + 6 x of xn-component + 7 sign of y-component + 8 y of y-component + */ + match[1] = match[1].toLowerCase(); + + if ( match[1].slice( 0, 3 ) === "nth" ) { + // nth-* requires argument + if ( !match[3] ) { + Sizzle.error( match[0] ); + } + + // numeric x and y parameters for Expr.filter.CHILD + // remember that false/true cast respectively to 0/1 + match[4] = +( match[4] ? match[5] + (match[6] || 1) : 2 * ( match[3] === "even" || match[3] === "odd" ) ); + match[5] = +( ( match[7] + match[8] ) || match[3] === "odd" ); + + // other types prohibit arguments + } else if ( match[3] ) { + Sizzle.error( match[0] ); + } + + return match; + }, + + "PSEUDO": function( match ) { + var excess, + unquoted = !match[6] && match[2]; + + if ( matchExpr["CHILD"].test( match[0] ) ) { + return null; + } + + // Accept quoted arguments as-is + if ( match[3] ) { + match[2] = match[4] || match[5] || ""; + + // Strip excess characters from unquoted arguments + } else if ( unquoted && rpseudo.test( unquoted ) && + // Get excess from tokenize (recursively) + (excess = tokenize( unquoted, true )) && + // advance to the next closing parenthesis + (excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length) ) { + + // excess is a negative index + match[0] = match[0].slice( 0, excess ); + match[2] = unquoted.slice( 0, excess ); + } + + // Return only captures needed by the pseudo filter method (type and argument) + return match.slice( 0, 3 ); + } + }, + + filter: { + + "TAG": function( nodeNameSelector ) { + var nodeName = nodeNameSelector.replace( runescape, funescape ).toLowerCase(); + return nodeNameSelector === "*" ? + function() { return true; } : + function( elem ) { + return elem.nodeName && elem.nodeName.toLowerCase() === nodeName; + }; + }, + + "CLASS": function( className ) { + var pattern = classCache[ className + " " ]; + + return pattern || + (pattern = new RegExp( "(^|" + whitespace + ")" + className + "(" + whitespace + "|$)" )) && + classCache( className, function( elem ) { + return pattern.test( typeof elem.className === "string" && elem.className || typeof elem.getAttribute !== "undefined" && elem.getAttribute("class") || "" ); + }); + }, + + "ATTR": function( name, operator, check ) { + return function( elem ) { + var result = Sizzle.attr( elem, name ); + + if ( result == null ) { + return operator === "!="; + } + if ( !operator ) { + return true; + } + + result += ""; + + return operator === "=" ? result === check : + operator === "!=" ? result !== check : + operator === "^=" ? check && result.indexOf( check ) === 0 : + operator === "*=" ? check && result.indexOf( check ) > -1 : + operator === "$=" ? check && result.slice( -check.length ) === check : + operator === "~=" ? ( " " + result.replace( rwhitespace, " " ) + " " ).indexOf( check ) > -1 : + operator === "|=" ? result === check || result.slice( 0, check.length + 1 ) === check + "-" : + false; + }; + }, + + "CHILD": function( type, what, argument, first, last ) { + var simple = type.slice( 0, 3 ) !== "nth", + forward = type.slice( -4 ) !== "last", + ofType = what === "of-type"; + + return first === 1 && last === 0 ? + + // Shortcut for :nth-*(n) + function( elem ) { + return !!elem.parentNode; + } : + + function( elem, context, xml ) { + var cache, uniqueCache, outerCache, node, nodeIndex, start, + dir = simple !== forward ? "nextSibling" : "previousSibling", + parent = elem.parentNode, + name = ofType && elem.nodeName.toLowerCase(), + useCache = !xml && !ofType, + diff = false; + + if ( parent ) { + + // :(first|last|only)-(child|of-type) + if ( simple ) { + while ( dir ) { + node = elem; + while ( (node = node[ dir ]) ) { + if ( ofType ? + node.nodeName.toLowerCase() === name : + node.nodeType === 1 ) { + + return false; + } + } + // Reverse direction for :only-* (if we haven't yet done so) + start = dir = type === "only" && !start && "nextSibling"; + } + return true; + } + + start = [ forward ? parent.firstChild : parent.lastChild ]; + + // non-xml :nth-child(...) stores cache data on `parent` + if ( forward && useCache ) { + + // Seek `elem` from a previously-cached index + + // ...in a gzip-friendly way + node = parent; + outerCache = node[ expando ] || (node[ expando ] = {}); + + // Support: IE <9 only + // Defend against cloned attroperties (jQuery gh-1709) + uniqueCache = outerCache[ node.uniqueID ] || + (outerCache[ node.uniqueID ] = {}); + + cache = uniqueCache[ type ] || []; + nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ]; + diff = nodeIndex && cache[ 2 ]; + node = nodeIndex && parent.childNodes[ nodeIndex ]; + + while ( (node = ++nodeIndex && node && node[ dir ] || + + // Fallback to seeking `elem` from the start + (diff = nodeIndex = 0) || start.pop()) ) { + + // When found, cache indexes on `parent` and break + if ( node.nodeType === 1 && ++diff && node === elem ) { + uniqueCache[ type ] = [ dirruns, nodeIndex, diff ]; + break; + } + } + + } else { + // Use previously-cached element index if available + if ( useCache ) { + // ...in a gzip-friendly way + node = elem; + outerCache = node[ expando ] || (node[ expando ] = {}); + + // Support: IE <9 only + // Defend against cloned attroperties (jQuery gh-1709) + uniqueCache = outerCache[ node.uniqueID ] || + (outerCache[ node.uniqueID ] = {}); + + cache = uniqueCache[ type ] || []; + nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ]; + diff = nodeIndex; + } + + // xml :nth-child(...) + // or :nth-last-child(...) or :nth(-last)?-of-type(...) + if ( diff === false ) { + // Use the same loop as above to seek `elem` from the start + while ( (node = ++nodeIndex && node && node[ dir ] || + (diff = nodeIndex = 0) || start.pop()) ) { + + if ( ( ofType ? + node.nodeName.toLowerCase() === name : + node.nodeType === 1 ) && + ++diff ) { + + // Cache the index of each encountered element + if ( useCache ) { + outerCache = node[ expando ] || (node[ expando ] = {}); + + // Support: IE <9 only + // Defend against cloned attroperties (jQuery gh-1709) + uniqueCache = outerCache[ node.uniqueID ] || + (outerCache[ node.uniqueID ] = {}); + + uniqueCache[ type ] = [ dirruns, diff ]; + } + + if ( node === elem ) { + break; + } + } + } + } + } + + // Incorporate the offset, then check against cycle size + diff -= last; + return diff === first || ( diff % first === 0 && diff / first >= 0 ); + } + }; + }, + + "PSEUDO": function( pseudo, argument ) { + // pseudo-class names are case-insensitive + // http://www.w3.org/TR/selectors/#pseudo-classes + // Prioritize by case sensitivity in case custom pseudos are added with uppercase letters + // Remember that setFilters inherits from pseudos + var args, + fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] || + Sizzle.error( "unsupported pseudo: " + pseudo ); + + // The user may use createPseudo to indicate that + // arguments are needed to create the filter function + // just as Sizzle does + if ( fn[ expando ] ) { + return fn( argument ); + } + + // But maintain support for old signatures + if ( fn.length > 1 ) { + args = [ pseudo, pseudo, "", argument ]; + return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ? + markFunction(function( seed, matches ) { + var idx, + matched = fn( seed, argument ), + i = matched.length; + while ( i-- ) { + idx = indexOf( seed, matched[i] ); + seed[ idx ] = !( matches[ idx ] = matched[i] ); + } + }) : + function( elem ) { + return fn( elem, 0, args ); + }; + } + + return fn; + } + }, + + pseudos: { + // Potentially complex pseudos + "not": markFunction(function( selector ) { + // Trim the selector passed to compile + // to avoid treating leading and trailing + // spaces as combinators + var input = [], + results = [], + matcher = compile( selector.replace( rtrim, "$1" ) ); + + return matcher[ expando ] ? + markFunction(function( seed, matches, context, xml ) { + var elem, + unmatched = matcher( seed, null, xml, [] ), + i = seed.length; + + // Match elements unmatched by `matcher` + while ( i-- ) { + if ( (elem = unmatched[i]) ) { + seed[i] = !(matches[i] = elem); + } + } + }) : + function( elem, context, xml ) { + input[0] = elem; + matcher( input, null, xml, results ); + // Don't keep the element (issue #299) + input[0] = null; + return !results.pop(); + }; + }), + + "has": markFunction(function( selector ) { + return function( elem ) { + return Sizzle( selector, elem ).length > 0; + }; + }), + + "contains": markFunction(function( text ) { + text = text.replace( runescape, funescape ); + return function( elem ) { + return ( elem.textContent || elem.innerText || getText( elem ) ).indexOf( text ) > -1; + }; + }), + + // "Whether an element is represented by a :lang() selector + // is based solely on the element's language value + // being equal to the identifier C, + // or beginning with the identifier C immediately followed by "-". + // The matching of C against the element's language value is performed case-insensitively. + // The identifier C does not have to be a valid language name." + // http://www.w3.org/TR/selectors/#lang-pseudo + "lang": markFunction( function( lang ) { + // lang value must be a valid identifier + if ( !ridentifier.test(lang || "") ) { + Sizzle.error( "unsupported lang: " + lang ); + } + lang = lang.replace( runescape, funescape ).toLowerCase(); + return function( elem ) { + var elemLang; + do { + if ( (elemLang = documentIsHTML ? + elem.lang : + elem.getAttribute("xml:lang") || elem.getAttribute("lang")) ) { + + elemLang = elemLang.toLowerCase(); + return elemLang === lang || elemLang.indexOf( lang + "-" ) === 0; + } + } while ( (elem = elem.parentNode) && elem.nodeType === 1 ); + return false; + }; + }), + + // Miscellaneous + "target": function( elem ) { + var hash = window.location && window.location.hash; + return hash && hash.slice( 1 ) === elem.id; + }, + + "root": function( elem ) { + return elem === docElem; + }, + + "focus": function( elem ) { + return elem === document.activeElement && (!document.hasFocus || document.hasFocus()) && !!(elem.type || elem.href || ~elem.tabIndex); + }, + + // Boolean properties + "enabled": createDisabledPseudo( false ), + "disabled": createDisabledPseudo( true ), + + "checked": function( elem ) { + // In CSS3, :checked should return both checked and selected elements + // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked + var nodeName = elem.nodeName.toLowerCase(); + return (nodeName === "input" && !!elem.checked) || (nodeName === "option" && !!elem.selected); + }, + + "selected": function( elem ) { + // Accessing this property makes selected-by-default + // options in Safari work properly + if ( elem.parentNode ) { + elem.parentNode.selectedIndex; + } + + return elem.selected === true; + }, + + // Contents + "empty": function( elem ) { + // http://www.w3.org/TR/selectors/#empty-pseudo + // :empty is negated by element (1) or content nodes (text: 3; cdata: 4; entity ref: 5), + // but not by others (comment: 8; processing instruction: 7; etc.) + // nodeType < 6 works because attributes (2) do not appear as children + for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { + if ( elem.nodeType < 6 ) { + return false; + } + } + return true; + }, + + "parent": function( elem ) { + return !Expr.pseudos["empty"]( elem ); + }, + + // Element/input types + "header": function( elem ) { + return rheader.test( elem.nodeName ); + }, + + "input": function( elem ) { + return rinputs.test( elem.nodeName ); + }, + + "button": function( elem ) { + var name = elem.nodeName.toLowerCase(); + return name === "input" && elem.type === "button" || name === "button"; + }, + + "text": function( elem ) { + var attr; + return elem.nodeName.toLowerCase() === "input" && + elem.type === "text" && + + // Support: IE<8 + // New HTML5 attribute values (e.g., "search") appear with elem.type === "text" + ( (attr = elem.getAttribute("type")) == null || attr.toLowerCase() === "text" ); + }, + + // Position-in-collection + "first": createPositionalPseudo(function() { + return [ 0 ]; + }), + + "last": createPositionalPseudo(function( matchIndexes, length ) { + return [ length - 1 ]; + }), + + "eq": createPositionalPseudo(function( matchIndexes, length, argument ) { + return [ argument < 0 ? argument + length : argument ]; + }), + + "even": createPositionalPseudo(function( matchIndexes, length ) { + var i = 0; + for ( ; i < length; i += 2 ) { + matchIndexes.push( i ); + } + return matchIndexes; + }), + + "odd": createPositionalPseudo(function( matchIndexes, length ) { + var i = 1; + for ( ; i < length; i += 2 ) { + matchIndexes.push( i ); + } + return matchIndexes; + }), + + "lt": createPositionalPseudo(function( matchIndexes, length, argument ) { + var i = argument < 0 ? argument + length : argument; + for ( ; --i >= 0; ) { + matchIndexes.push( i ); + } + return matchIndexes; + }), + + "gt": createPositionalPseudo(function( matchIndexes, length, argument ) { + var i = argument < 0 ? argument + length : argument; + for ( ; ++i < length; ) { + matchIndexes.push( i ); + } + return matchIndexes; + }) + } +}; + +Expr.pseudos["nth"] = Expr.pseudos["eq"]; + +// Add button/input type pseudos +for ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) { + Expr.pseudos[ i ] = createInputPseudo( i ); +} +for ( i in { submit: true, reset: true } ) { + Expr.pseudos[ i ] = createButtonPseudo( i ); +} + +// Easy API for creating new setFilters +function setFilters() {} +setFilters.prototype = Expr.filters = Expr.pseudos; +Expr.setFilters = new setFilters(); + +tokenize = Sizzle.tokenize = function( selector, parseOnly ) { + var matched, match, tokens, type, + soFar, groups, preFilters, + cached = tokenCache[ selector + " " ]; + + if ( cached ) { + return parseOnly ? 0 : cached.slice( 0 ); + } + + soFar = selector; + groups = []; + preFilters = Expr.preFilter; + + while ( soFar ) { + + // Comma and first run + if ( !matched || (match = rcomma.exec( soFar )) ) { + if ( match ) { + // Don't consume trailing commas as valid + soFar = soFar.slice( match[0].length ) || soFar; + } + groups.push( (tokens = []) ); + } + + matched = false; + + // Combinators + if ( (match = rcombinators.exec( soFar )) ) { + matched = match.shift(); + tokens.push({ + value: matched, + // Cast descendant combinators to space + type: match[0].replace( rtrim, " " ) + }); + soFar = soFar.slice( matched.length ); + } + + // Filters + for ( type in Expr.filter ) { + if ( (match = matchExpr[ type ].exec( soFar )) && (!preFilters[ type ] || + (match = preFilters[ type ]( match ))) ) { + matched = match.shift(); + tokens.push({ + value: matched, + type: type, + matches: match + }); + soFar = soFar.slice( matched.length ); + } + } + + if ( !matched ) { + break; + } + } + + // Return the length of the invalid excess + // if we're just parsing + // Otherwise, throw an error or return tokens + return parseOnly ? + soFar.length : + soFar ? + Sizzle.error( selector ) : + // Cache the tokens + tokenCache( selector, groups ).slice( 0 ); +}; + +function toSelector( tokens ) { + var i = 0, + len = tokens.length, + selector = ""; + for ( ; i < len; i++ ) { + selector += tokens[i].value; + } + return selector; +} + +function addCombinator( matcher, combinator, base ) { + var dir = combinator.dir, + skip = combinator.next, + key = skip || dir, + checkNonElements = base && key === "parentNode", + doneName = done++; + + return combinator.first ? + // Check against closest ancestor/preceding element + function( elem, context, xml ) { + while ( (elem = elem[ dir ]) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + return matcher( elem, context, xml ); + } + } + return false; + } : + + // Check against all ancestor/preceding elements + function( elem, context, xml ) { + var oldCache, uniqueCache, outerCache, + newCache = [ dirruns, doneName ]; + + // We can't set arbitrary data on XML nodes, so they don't benefit from combinator caching + if ( xml ) { + while ( (elem = elem[ dir ]) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + if ( matcher( elem, context, xml ) ) { + return true; + } + } + } + } else { + while ( (elem = elem[ dir ]) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + outerCache = elem[ expando ] || (elem[ expando ] = {}); + + // Support: IE <9 only + // Defend against cloned attroperties (jQuery gh-1709) + uniqueCache = outerCache[ elem.uniqueID ] || (outerCache[ elem.uniqueID ] = {}); + + if ( skip && skip === elem.nodeName.toLowerCase() ) { + elem = elem[ dir ] || elem; + } else if ( (oldCache = uniqueCache[ key ]) && + oldCache[ 0 ] === dirruns && oldCache[ 1 ] === doneName ) { + + // Assign to newCache so results back-propagate to previous elements + return (newCache[ 2 ] = oldCache[ 2 ]); + } else { + // Reuse newcache so results back-propagate to previous elements + uniqueCache[ key ] = newCache; + + // A match means we're done; a fail means we have to keep checking + if ( (newCache[ 2 ] = matcher( elem, context, xml )) ) { + return true; + } + } + } + } + } + return false; + }; +} + +function elementMatcher( matchers ) { + return matchers.length > 1 ? + function( elem, context, xml ) { + var i = matchers.length; + while ( i-- ) { + if ( !matchers[i]( elem, context, xml ) ) { + return false; + } + } + return true; + } : + matchers[0]; +} + +function multipleContexts( selector, contexts, results ) { + var i = 0, + len = contexts.length; + for ( ; i < len; i++ ) { + Sizzle( selector, contexts[i], results ); + } + return results; +} + +function condense( unmatched, map, filter, context, xml ) { + var elem, + newUnmatched = [], + i = 0, + len = unmatched.length, + mapped = map != null; + + for ( ; i < len; i++ ) { + if ( (elem = unmatched[i]) ) { + if ( !filter || filter( elem, context, xml ) ) { + newUnmatched.push( elem ); + if ( mapped ) { + map.push( i ); + } + } + } + } + + return newUnmatched; +} + +function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) { + if ( postFilter && !postFilter[ expando ] ) { + postFilter = setMatcher( postFilter ); + } + if ( postFinder && !postFinder[ expando ] ) { + postFinder = setMatcher( postFinder, postSelector ); + } + return markFunction(function( seed, results, context, xml ) { + var temp, i, elem, + preMap = [], + postMap = [], + preexisting = results.length, + + // Get initial elements from seed or context + elems = seed || multipleContexts( selector || "*", context.nodeType ? [ context ] : context, [] ), + + // Prefilter to get matcher input, preserving a map for seed-results synchronization + matcherIn = preFilter && ( seed || !selector ) ? + condense( elems, preMap, preFilter, context, xml ) : + elems, + + matcherOut = matcher ? + // If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results, + postFinder || ( seed ? preFilter : preexisting || postFilter ) ? + + // ...intermediate processing is necessary + [] : + + // ...otherwise use results directly + results : + matcherIn; + + // Find primary matches + if ( matcher ) { + matcher( matcherIn, matcherOut, context, xml ); + } + + // Apply postFilter + if ( postFilter ) { + temp = condense( matcherOut, postMap ); + postFilter( temp, [], context, xml ); + + // Un-match failing elements by moving them back to matcherIn + i = temp.length; + while ( i-- ) { + if ( (elem = temp[i]) ) { + matcherOut[ postMap[i] ] = !(matcherIn[ postMap[i] ] = elem); + } + } + } + + if ( seed ) { + if ( postFinder || preFilter ) { + if ( postFinder ) { + // Get the final matcherOut by condensing this intermediate into postFinder contexts + temp = []; + i = matcherOut.length; + while ( i-- ) { + if ( (elem = matcherOut[i]) ) { + // Restore matcherIn since elem is not yet a final match + temp.push( (matcherIn[i] = elem) ); + } + } + postFinder( null, (matcherOut = []), temp, xml ); + } + + // Move matched elements from seed to results to keep them synchronized + i = matcherOut.length; + while ( i-- ) { + if ( (elem = matcherOut[i]) && + (temp = postFinder ? indexOf( seed, elem ) : preMap[i]) > -1 ) { + + seed[temp] = !(results[temp] = elem); + } + } + } + + // Add elements to results, through postFinder if defined + } else { + matcherOut = condense( + matcherOut === results ? + matcherOut.splice( preexisting, matcherOut.length ) : + matcherOut + ); + if ( postFinder ) { + postFinder( null, results, matcherOut, xml ); + } else { + push.apply( results, matcherOut ); + } + } + }); +} + +function matcherFromTokens( tokens ) { + var checkContext, matcher, j, + len = tokens.length, + leadingRelative = Expr.relative[ tokens[0].type ], + implicitRelative = leadingRelative || Expr.relative[" "], + i = leadingRelative ? 1 : 0, + + // The foundational matcher ensures that elements are reachable from top-level context(s) + matchContext = addCombinator( function( elem ) { + return elem === checkContext; + }, implicitRelative, true ), + matchAnyContext = addCombinator( function( elem ) { + return indexOf( checkContext, elem ) > -1; + }, implicitRelative, true ), + matchers = [ function( elem, context, xml ) { + var ret = ( !leadingRelative && ( xml || context !== outermostContext ) ) || ( + (checkContext = context).nodeType ? + matchContext( elem, context, xml ) : + matchAnyContext( elem, context, xml ) ); + // Avoid hanging onto element (issue #299) + checkContext = null; + return ret; + } ]; + + for ( ; i < len; i++ ) { + if ( (matcher = Expr.relative[ tokens[i].type ]) ) { + matchers = [ addCombinator(elementMatcher( matchers ), matcher) ]; + } else { + matcher = Expr.filter[ tokens[i].type ].apply( null, tokens[i].matches ); + + // Return special upon seeing a positional matcher + if ( matcher[ expando ] ) { + // Find the next relative operator (if any) for proper handling + j = ++i; + for ( ; j < len; j++ ) { + if ( Expr.relative[ tokens[j].type ] ) { + break; + } + } + return setMatcher( + i > 1 && elementMatcher( matchers ), + i > 1 && toSelector( + // If the preceding token was a descendant combinator, insert an implicit any-element `*` + tokens.slice( 0, i - 1 ).concat({ value: tokens[ i - 2 ].type === " " ? "*" : "" }) + ).replace( rtrim, "$1" ), + matcher, + i < j && matcherFromTokens( tokens.slice( i, j ) ), + j < len && matcherFromTokens( (tokens = tokens.slice( j )) ), + j < len && toSelector( tokens ) + ); + } + matchers.push( matcher ); + } + } + + return elementMatcher( matchers ); +} + +function matcherFromGroupMatchers( elementMatchers, setMatchers ) { + var bySet = setMatchers.length > 0, + byElement = elementMatchers.length > 0, + superMatcher = function( seed, context, xml, results, outermost ) { + var elem, j, matcher, + matchedCount = 0, + i = "0", + unmatched = seed && [], + setMatched = [], + contextBackup = outermostContext, + // We must always have either seed elements or outermost context + elems = seed || byElement && Expr.find["TAG"]( "*", outermost ), + // Use integer dirruns iff this is the outermost matcher + dirrunsUnique = (dirruns += contextBackup == null ? 1 : Math.random() || 0.1), + len = elems.length; + + if ( outermost ) { + outermostContext = context === document || context || outermost; + } + + // Add elements passing elementMatchers directly to results + // Support: IE<9, Safari + // Tolerate NodeList properties (IE: "length"; Safari: ) matching elements by id + for ( ; i !== len && (elem = elems[i]) != null; i++ ) { + if ( byElement && elem ) { + j = 0; + if ( !context && elem.ownerDocument !== document ) { + setDocument( elem ); + xml = !documentIsHTML; + } + while ( (matcher = elementMatchers[j++]) ) { + if ( matcher( elem, context || document, xml) ) { + results.push( elem ); + break; + } + } + if ( outermost ) { + dirruns = dirrunsUnique; + } + } + + // Track unmatched elements for set filters + if ( bySet ) { + // They will have gone through all possible matchers + if ( (elem = !matcher && elem) ) { + matchedCount--; + } + + // Lengthen the array for every element, matched or not + if ( seed ) { + unmatched.push( elem ); + } + } + } + + // `i` is now the count of elements visited above, and adding it to `matchedCount` + // makes the latter nonnegative. + matchedCount += i; + + // Apply set filters to unmatched elements + // NOTE: This can be skipped if there are no unmatched elements (i.e., `matchedCount` + // equals `i`), unless we didn't visit _any_ elements in the above loop because we have + // no element matchers and no seed. + // Incrementing an initially-string "0" `i` allows `i` to remain a string only in that + // case, which will result in a "00" `matchedCount` that differs from `i` but is also + // numerically zero. + if ( bySet && i !== matchedCount ) { + j = 0; + while ( (matcher = setMatchers[j++]) ) { + matcher( unmatched, setMatched, context, xml ); + } + + if ( seed ) { + // Reintegrate element matches to eliminate the need for sorting + if ( matchedCount > 0 ) { + while ( i-- ) { + if ( !(unmatched[i] || setMatched[i]) ) { + setMatched[i] = pop.call( results ); + } + } + } + + // Discard index placeholder values to get only actual matches + setMatched = condense( setMatched ); + } + + // Add matches to results + push.apply( results, setMatched ); + + // Seedless set matches succeeding multiple successful matchers stipulate sorting + if ( outermost && !seed && setMatched.length > 0 && + ( matchedCount + setMatchers.length ) > 1 ) { + + Sizzle.uniqueSort( results ); + } + } + + // Override manipulation of globals by nested matchers + if ( outermost ) { + dirruns = dirrunsUnique; + outermostContext = contextBackup; + } + + return unmatched; + }; + + return bySet ? + markFunction( superMatcher ) : + superMatcher; +} + +compile = Sizzle.compile = function( selector, match /* Internal Use Only */ ) { + var i, + setMatchers = [], + elementMatchers = [], + cached = compilerCache[ selector + " " ]; + + if ( !cached ) { + // Generate a function of recursive functions that can be used to check each element + if ( !match ) { + match = tokenize( selector ); + } + i = match.length; + while ( i-- ) { + cached = matcherFromTokens( match[i] ); + if ( cached[ expando ] ) { + setMatchers.push( cached ); + } else { + elementMatchers.push( cached ); + } + } + + // Cache the compiled function + cached = compilerCache( selector, matcherFromGroupMatchers( elementMatchers, setMatchers ) ); + + // Save selector and tokenization + cached.selector = selector; + } + return cached; +}; + +/** + * A low-level selection function that works with Sizzle's compiled + * selector functions + * @param {String|Function} selector A selector or a pre-compiled + * selector function built with Sizzle.compile + * @param {Element} context + * @param {Array} [results] + * @param {Array} [seed] A set of elements to match against + */ +select = Sizzle.select = function( selector, context, results, seed ) { + var i, tokens, token, type, find, + compiled = typeof selector === "function" && selector, + match = !seed && tokenize( (selector = compiled.selector || selector) ); + + results = results || []; + + // Try to minimize operations if there is only one selector in the list and no seed + // (the latter of which guarantees us context) + if ( match.length === 1 ) { + + // Reduce context if the leading compound selector is an ID + tokens = match[0] = match[0].slice( 0 ); + if ( tokens.length > 2 && (token = tokens[0]).type === "ID" && + context.nodeType === 9 && documentIsHTML && Expr.relative[ tokens[1].type ] ) { + + context = ( Expr.find["ID"]( token.matches[0].replace(runescape, funescape), context ) || [] )[0]; + if ( !context ) { + return results; + + // Precompiled matchers will still verify ancestry, so step up a level + } else if ( compiled ) { + context = context.parentNode; + } + + selector = selector.slice( tokens.shift().value.length ); + } + + // Fetch a seed set for right-to-left matching + i = matchExpr["needsContext"].test( selector ) ? 0 : tokens.length; + while ( i-- ) { + token = tokens[i]; + + // Abort if we hit a combinator + if ( Expr.relative[ (type = token.type) ] ) { + break; + } + if ( (find = Expr.find[ type ]) ) { + // Search, expanding context for leading sibling combinators + if ( (seed = find( + token.matches[0].replace( runescape, funescape ), + rsibling.test( tokens[0].type ) && testContext( context.parentNode ) || context + )) ) { + + // If seed is empty or no tokens remain, we can return early + tokens.splice( i, 1 ); + selector = seed.length && toSelector( tokens ); + if ( !selector ) { + push.apply( results, seed ); + return results; + } + + break; + } + } + } + } + + // Compile and execute a filtering function if one is not provided + // Provide `match` to avoid retokenization if we modified the selector above + ( compiled || compile( selector, match ) )( + seed, + context, + !documentIsHTML, + results, + !context || rsibling.test( selector ) && testContext( context.parentNode ) || context + ); + return results; +}; + +// One-time assignments + +// Sort stability +support.sortStable = expando.split("").sort( sortOrder ).join("") === expando; + +// Support: Chrome 14-35+ +// Always assume duplicates if they aren't passed to the comparison function +support.detectDuplicates = !!hasDuplicate; + +// Initialize against the default document +setDocument(); + +// Support: Webkit<537.32 - Safari 6.0.3/Chrome 25 (fixed in Chrome 27) +// Detached nodes confoundingly follow *each other* +support.sortDetached = assert(function( el ) { + // Should return 1, but returns 4 (following) + return el.compareDocumentPosition( document.createElement("fieldset") ) & 1; +}); + +// Support: IE<8 +// Prevent attribute/property "interpolation" +// https://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx +if ( !assert(function( el ) { + el.innerHTML = ""; + return el.firstChild.getAttribute("href") === "#" ; +}) ) { + addHandle( "type|href|height|width", function( elem, name, isXML ) { + if ( !isXML ) { + return elem.getAttribute( name, name.toLowerCase() === "type" ? 1 : 2 ); + } + }); +} + +// Support: IE<9 +// Use defaultValue in place of getAttribute("value") +if ( !support.attributes || !assert(function( el ) { + el.innerHTML = ""; + el.firstChild.setAttribute( "value", "" ); + return el.firstChild.getAttribute( "value" ) === ""; +}) ) { + addHandle( "value", function( elem, name, isXML ) { + if ( !isXML && elem.nodeName.toLowerCase() === "input" ) { + return elem.defaultValue; + } + }); +} + +// Support: IE<9 +// Use getAttributeNode to fetch booleans when getAttribute lies +if ( !assert(function( el ) { + return el.getAttribute("disabled") == null; +}) ) { + addHandle( booleans, function( elem, name, isXML ) { + var val; + if ( !isXML ) { + return elem[ name ] === true ? name.toLowerCase() : + (val = elem.getAttributeNode( name )) && val.specified ? + val.value : + null; + } + }); +} + +return Sizzle; + +})( window ); + + + +jQuery.find = Sizzle; +jQuery.expr = Sizzle.selectors; + +// Deprecated +jQuery.expr[ ":" ] = jQuery.expr.pseudos; +jQuery.uniqueSort = jQuery.unique = Sizzle.uniqueSort; +jQuery.text = Sizzle.getText; +jQuery.isXMLDoc = Sizzle.isXML; +jQuery.contains = Sizzle.contains; +jQuery.escapeSelector = Sizzle.escape; + + + + +var dir = function( elem, dir, until ) { + var matched = [], + truncate = until !== undefined; + + while ( ( elem = elem[ dir ] ) && elem.nodeType !== 9 ) { + if ( elem.nodeType === 1 ) { + if ( truncate && jQuery( elem ).is( until ) ) { + break; + } + matched.push( elem ); + } + } + return matched; +}; + + +var siblings = function( n, elem ) { + var matched = []; + + for ( ; n; n = n.nextSibling ) { + if ( n.nodeType === 1 && n !== elem ) { + matched.push( n ); + } + } + + return matched; +}; + + +var rneedsContext = jQuery.expr.match.needsContext; + +var rsingleTag = ( /^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i ); + + + +var risSimple = /^.[^:#\[\.,]*$/; + +// Implement the identical functionality for filter and not +function winnow( elements, qualifier, not ) { + if ( jQuery.isFunction( qualifier ) ) { + return jQuery.grep( elements, function( elem, i ) { + return !!qualifier.call( elem, i, elem ) !== not; + } ); + } + + // Single element + if ( qualifier.nodeType ) { + return jQuery.grep( elements, function( elem ) { + return ( elem === qualifier ) !== not; + } ); + } + + // Arraylike of elements (jQuery, arguments, Array) + if ( typeof qualifier !== "string" ) { + return jQuery.grep( elements, function( elem ) { + return ( indexOf.call( qualifier, elem ) > -1 ) !== not; + } ); + } + + // Simple selector that can be filtered directly, removing non-Elements + if ( risSimple.test( qualifier ) ) { + return jQuery.filter( qualifier, elements, not ); + } + + // Complex selector, compare the two sets, removing non-Elements + qualifier = jQuery.filter( qualifier, elements ); + return jQuery.grep( elements, function( elem ) { + return ( indexOf.call( qualifier, elem ) > -1 ) !== not && elem.nodeType === 1; + } ); +} + +jQuery.filter = function( expr, elems, not ) { + var elem = elems[ 0 ]; + + if ( not ) { + expr = ":not(" + expr + ")"; + } + + if ( elems.length === 1 && elem.nodeType === 1 ) { + return jQuery.find.matchesSelector( elem, expr ) ? [ elem ] : []; + } + + return jQuery.find.matches( expr, jQuery.grep( elems, function( elem ) { + return elem.nodeType === 1; + } ) ); +}; + +jQuery.fn.extend( { + find: function( selector ) { + var i, ret, + len = this.length, + self = this; + + if ( typeof selector !== "string" ) { + return this.pushStack( jQuery( selector ).filter( function() { + for ( i = 0; i < len; i++ ) { + if ( jQuery.contains( self[ i ], this ) ) { + return true; + } + } + } ) ); + } + + ret = this.pushStack( [] ); + + for ( i = 0; i < len; i++ ) { + jQuery.find( selector, self[ i ], ret ); + } + + return len > 1 ? jQuery.uniqueSort( ret ) : ret; + }, + filter: function( selector ) { + return this.pushStack( winnow( this, selector || [], false ) ); + }, + not: function( selector ) { + return this.pushStack( winnow( this, selector || [], true ) ); + }, + is: function( selector ) { + return !!winnow( + this, + + // If this is a positional/relative selector, check membership in the returned set + // so $("p:first").is("p:last") won't return true for a doc with two "p". + typeof selector === "string" && rneedsContext.test( selector ) ? + jQuery( selector ) : + selector || [], + false + ).length; + } +} ); + + +// Initialize a jQuery object + + +// A central reference to the root jQuery(document) +var rootjQuery, + + // A simple way to check for HTML strings + // Prioritize #id over to avoid XSS via location.hash (#9521) + // Strict HTML recognition (#11290: must start with <) + // Shortcut simple #id case for speed + rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/, + + init = jQuery.fn.init = function( selector, context, root ) { + var match, elem; + + // HANDLE: $(""), $(null), $(undefined), $(false) + if ( !selector ) { + return this; + } + + // Method init() accepts an alternate rootjQuery + // so migrate can support jQuery.sub (gh-2101) + root = root || rootjQuery; + + // Handle HTML strings + if ( typeof selector === "string" ) { + if ( selector[ 0 ] === "<" && + selector[ selector.length - 1 ] === ">" && + selector.length >= 3 ) { + + // Assume that strings that start and end with <> are HTML and skip the regex check + match = [ null, selector, null ]; + + } else { + match = rquickExpr.exec( selector ); + } + + // Match html or make sure no context is specified for #id + if ( match && ( match[ 1 ] || !context ) ) { + + // HANDLE: $(html) -> $(array) + if ( match[ 1 ] ) { + context = context instanceof jQuery ? context[ 0 ] : context; + + // Option to run scripts is true for back-compat + // Intentionally let the error be thrown if parseHTML is not present + jQuery.merge( this, jQuery.parseHTML( + match[ 1 ], + context && context.nodeType ? context.ownerDocument || context : document, + true + ) ); + + // HANDLE: $(html, props) + if ( rsingleTag.test( match[ 1 ] ) && jQuery.isPlainObject( context ) ) { + for ( match in context ) { + + // Properties of context are called as methods if possible + if ( jQuery.isFunction( this[ match ] ) ) { + this[ match ]( context[ match ] ); + + // ...and otherwise set as attributes + } else { + this.attr( match, context[ match ] ); + } + } + } + + return this; + + // HANDLE: $(#id) + } else { + elem = document.getElementById( match[ 2 ] ); + + if ( elem ) { + + // Inject the element directly into the jQuery object + this[ 0 ] = elem; + this.length = 1; + } + return this; + } + + // HANDLE: $(expr, $(...)) + } else if ( !context || context.jquery ) { + return ( context || root ).find( selector ); + + // HANDLE: $(expr, context) + // (which is just equivalent to: $(context).find(expr) + } else { + return this.constructor( context ).find( selector ); + } + + // HANDLE: $(DOMElement) + } else if ( selector.nodeType ) { + this[ 0 ] = selector; + this.length = 1; + return this; + + // HANDLE: $(function) + // Shortcut for document ready + } else if ( jQuery.isFunction( selector ) ) { + return root.ready !== undefined ? + root.ready( selector ) : + + // Execute immediately if ready is not present + selector( jQuery ); + } + + return jQuery.makeArray( selector, this ); + }; + +// Give the init function the jQuery prototype for later instantiation +init.prototype = jQuery.fn; + +// Initialize central reference +rootjQuery = jQuery( document ); + + +var rparentsprev = /^(?:parents|prev(?:Until|All))/, + + // Methods guaranteed to produce a unique set when starting from a unique set + guaranteedUnique = { + children: true, + contents: true, + next: true, + prev: true + }; + +jQuery.fn.extend( { + has: function( target ) { + var targets = jQuery( target, this ), + l = targets.length; + + return this.filter( function() { + var i = 0; + for ( ; i < l; i++ ) { + if ( jQuery.contains( this, targets[ i ] ) ) { + return true; + } + } + } ); + }, + + closest: function( selectors, context ) { + var cur, + i = 0, + l = this.length, + matched = [], + targets = typeof selectors !== "string" && jQuery( selectors ); + + // Positional selectors never match, since there's no _selection_ context + if ( !rneedsContext.test( selectors ) ) { + for ( ; i < l; i++ ) { + for ( cur = this[ i ]; cur && cur !== context; cur = cur.parentNode ) { + + // Always skip document fragments + if ( cur.nodeType < 11 && ( targets ? + targets.index( cur ) > -1 : + + // Don't pass non-elements to Sizzle + cur.nodeType === 1 && + jQuery.find.matchesSelector( cur, selectors ) ) ) { + + matched.push( cur ); + break; + } + } + } + } + + return this.pushStack( matched.length > 1 ? jQuery.uniqueSort( matched ) : matched ); + }, + + // Determine the position of an element within the set + index: function( elem ) { + + // No argument, return index in parent + if ( !elem ) { + return ( this[ 0 ] && this[ 0 ].parentNode ) ? this.first().prevAll().length : -1; + } + + // Index in selector + if ( typeof elem === "string" ) { + return indexOf.call( jQuery( elem ), this[ 0 ] ); + } + + // Locate the position of the desired element + return indexOf.call( this, + + // If it receives a jQuery object, the first element is used + elem.jquery ? elem[ 0 ] : elem + ); + }, + + add: function( selector, context ) { + return this.pushStack( + jQuery.uniqueSort( + jQuery.merge( this.get(), jQuery( selector, context ) ) + ) + ); + }, + + addBack: function( selector ) { + return this.add( selector == null ? + this.prevObject : this.prevObject.filter( selector ) + ); + } +} ); + +function sibling( cur, dir ) { + while ( ( cur = cur[ dir ] ) && cur.nodeType !== 1 ) {} + return cur; +} + +jQuery.each( { + parent: function( elem ) { + var parent = elem.parentNode; + return parent && parent.nodeType !== 11 ? parent : null; + }, + parents: function( elem ) { + return dir( elem, "parentNode" ); + }, + parentsUntil: function( elem, i, until ) { + return dir( elem, "parentNode", until ); + }, + next: function( elem ) { + return sibling( elem, "nextSibling" ); + }, + prev: function( elem ) { + return sibling( elem, "previousSibling" ); + }, + nextAll: function( elem ) { + return dir( elem, "nextSibling" ); + }, + prevAll: function( elem ) { + return dir( elem, "previousSibling" ); + }, + nextUntil: function( elem, i, until ) { + return dir( elem, "nextSibling", until ); + }, + prevUntil: function( elem, i, until ) { + return dir( elem, "previousSibling", until ); + }, + siblings: function( elem ) { + return siblings( ( elem.parentNode || {} ).firstChild, elem ); + }, + children: function( elem ) { + return siblings( elem.firstChild ); + }, + contents: function( elem ) { + return elem.contentDocument || jQuery.merge( [], elem.childNodes ); + } +}, function( name, fn ) { + jQuery.fn[ name ] = function( until, selector ) { + var matched = jQuery.map( this, fn, until ); + + if ( name.slice( -5 ) !== "Until" ) { + selector = until; + } + + if ( selector && typeof selector === "string" ) { + matched = jQuery.filter( selector, matched ); + } + + if ( this.length > 1 ) { + + // Remove duplicates + if ( !guaranteedUnique[ name ] ) { + jQuery.uniqueSort( matched ); + } + + // Reverse order for parents* and prev-derivatives + if ( rparentsprev.test( name ) ) { + matched.reverse(); + } + } + + return this.pushStack( matched ); + }; +} ); +var rnothtmlwhite = ( /[^\x20\t\r\n\f]+/g ); + + + +// Convert String-formatted options into Object-formatted ones +function createOptions( options ) { + var object = {}; + jQuery.each( options.match( rnothtmlwhite ) || [], function( _, flag ) { + object[ flag ] = true; + } ); + return object; +} + +/* + * Create a callback list using the following parameters: + * + * options: an optional list of space-separated options that will change how + * the callback list behaves or a more traditional option object + * + * By default a callback list will act like an event callback list and can be + * "fired" multiple times. + * + * Possible options: + * + * once: will ensure the callback list can only be fired once (like a Deferred) + * + * memory: will keep track of previous values and will call any callback added + * after the list has been fired right away with the latest "memorized" + * values (like a Deferred) + * + * unique: will ensure a callback can only be added once (no duplicate in the list) + * + * stopOnFalse: interrupt callings when a callback returns false + * + */ +jQuery.Callbacks = function( options ) { + + // Convert options from String-formatted to Object-formatted if needed + // (we check in cache first) + options = typeof options === "string" ? + createOptions( options ) : + jQuery.extend( {}, options ); + + var // Flag to know if list is currently firing + firing, + + // Last fire value for non-forgettable lists + memory, + + // Flag to know if list was already fired + fired, + + // Flag to prevent firing + locked, + + // Actual callback list + list = [], + + // Queue of execution data for repeatable lists + queue = [], + + // Index of currently firing callback (modified by add/remove as needed) + firingIndex = -1, + + // Fire callbacks + fire = function() { + + // Enforce single-firing + locked = options.once; + + // Execute callbacks for all pending executions, + // respecting firingIndex overrides and runtime changes + fired = firing = true; + for ( ; queue.length; firingIndex = -1 ) { + memory = queue.shift(); + while ( ++firingIndex < list.length ) { + + // Run callback and check for early termination + if ( list[ firingIndex ].apply( memory[ 0 ], memory[ 1 ] ) === false && + options.stopOnFalse ) { + + // Jump to end and forget the data so .add doesn't re-fire + firingIndex = list.length; + memory = false; + } + } + } + + // Forget the data if we're done with it + if ( !options.memory ) { + memory = false; + } + + firing = false; + + // Clean up if we're done firing for good + if ( locked ) { + + // Keep an empty list if we have data for future add calls + if ( memory ) { + list = []; + + // Otherwise, this object is spent + } else { + list = ""; + } + } + }, + + // Actual Callbacks object + self = { + + // Add a callback or a collection of callbacks to the list + add: function() { + if ( list ) { + + // If we have memory from a past run, we should fire after adding + if ( memory && !firing ) { + firingIndex = list.length - 1; + queue.push( memory ); + } + + ( function add( args ) { + jQuery.each( args, function( _, arg ) { + if ( jQuery.isFunction( arg ) ) { + if ( !options.unique || !self.has( arg ) ) { + list.push( arg ); + } + } else if ( arg && arg.length && jQuery.type( arg ) !== "string" ) { + + // Inspect recursively + add( arg ); + } + } ); + } )( arguments ); + + if ( memory && !firing ) { + fire(); + } + } + return this; + }, + + // Remove a callback from the list + remove: function() { + jQuery.each( arguments, function( _, arg ) { + var index; + while ( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) { + list.splice( index, 1 ); + + // Handle firing indexes + if ( index <= firingIndex ) { + firingIndex--; + } + } + } ); + return this; + }, + + // Check if a given callback is in the list. + // If no argument is given, return whether or not list has callbacks attached. + has: function( fn ) { + return fn ? + jQuery.inArray( fn, list ) > -1 : + list.length > 0; + }, + + // Remove all callbacks from the list + empty: function() { + if ( list ) { + list = []; + } + return this; + }, + + // Disable .fire and .add + // Abort any current/pending executions + // Clear all callbacks and values + disable: function() { + locked = queue = []; + list = memory = ""; + return this; + }, + disabled: function() { + return !list; + }, + + // Disable .fire + // Also disable .add unless we have memory (since it would have no effect) + // Abort any pending executions + lock: function() { + locked = queue = []; + if ( !memory && !firing ) { + list = memory = ""; + } + return this; + }, + locked: function() { + return !!locked; + }, + + // Call all callbacks with the given context and arguments + fireWith: function( context, args ) { + if ( !locked ) { + args = args || []; + args = [ context, args.slice ? args.slice() : args ]; + queue.push( args ); + if ( !firing ) { + fire(); + } + } + return this; + }, + + // Call all the callbacks with the given arguments + fire: function() { + self.fireWith( this, arguments ); + return this; + }, + + // To know if the callbacks have already been called at least once + fired: function() { + return !!fired; + } + }; + + return self; +}; + + +function Identity( v ) { + return v; +} +function Thrower( ex ) { + throw ex; +} + +function adoptValue( value, resolve, reject ) { + var method; + + try { + + // Check for promise aspect first to privilege synchronous behavior + if ( value && jQuery.isFunction( ( method = value.promise ) ) ) { + method.call( value ).done( resolve ).fail( reject ); + + // Other thenables + } else if ( value && jQuery.isFunction( ( method = value.then ) ) ) { + method.call( value, resolve, reject ); + + // Other non-thenables + } else { + + // Support: Android 4.0 only + // Strict mode functions invoked without .call/.apply get global-object context + resolve.call( undefined, value ); + } + + // For Promises/A+, convert exceptions into rejections + // Since jQuery.when doesn't unwrap thenables, we can skip the extra checks appearing in + // Deferred#then to conditionally suppress rejection. + } catch ( value ) { + + // Support: Android 4.0 only + // Strict mode functions invoked without .call/.apply get global-object context + reject.call( undefined, value ); + } +} + +jQuery.extend( { + + Deferred: function( func ) { + var tuples = [ + + // action, add listener, callbacks, + // ... .then handlers, argument index, [final state] + [ "notify", "progress", jQuery.Callbacks( "memory" ), + jQuery.Callbacks( "memory" ), 2 ], + [ "resolve", "done", jQuery.Callbacks( "once memory" ), + jQuery.Callbacks( "once memory" ), 0, "resolved" ], + [ "reject", "fail", jQuery.Callbacks( "once memory" ), + jQuery.Callbacks( "once memory" ), 1, "rejected" ] + ], + state = "pending", + promise = { + state: function() { + return state; + }, + always: function() { + deferred.done( arguments ).fail( arguments ); + return this; + }, + "catch": function( fn ) { + return promise.then( null, fn ); + }, + + // Keep pipe for back-compat + pipe: function( /* fnDone, fnFail, fnProgress */ ) { + var fns = arguments; + + return jQuery.Deferred( function( newDefer ) { + jQuery.each( tuples, function( i, tuple ) { + + // Map tuples (progress, done, fail) to arguments (done, fail, progress) + var fn = jQuery.isFunction( fns[ tuple[ 4 ] ] ) && fns[ tuple[ 4 ] ]; + + // deferred.progress(function() { bind to newDefer or newDefer.notify }) + // deferred.done(function() { bind to newDefer or newDefer.resolve }) + // deferred.fail(function() { bind to newDefer or newDefer.reject }) + deferred[ tuple[ 1 ] ]( function() { + var returned = fn && fn.apply( this, arguments ); + if ( returned && jQuery.isFunction( returned.promise ) ) { + returned.promise() + .progress( newDefer.notify ) + .done( newDefer.resolve ) + .fail( newDefer.reject ); + } else { + newDefer[ tuple[ 0 ] + "With" ]( + this, + fn ? [ returned ] : arguments + ); + } + } ); + } ); + fns = null; + } ).promise(); + }, + then: function( onFulfilled, onRejected, onProgress ) { + var maxDepth = 0; + function resolve( depth, deferred, handler, special ) { + return function() { + var that = this, + args = arguments, + mightThrow = function() { + var returned, then; + + // Support: Promises/A+ section 2.3.3.3.3 + // https://promisesaplus.com/#point-59 + // Ignore double-resolution attempts + if ( depth < maxDepth ) { + return; + } + + returned = handler.apply( that, args ); + + // Support: Promises/A+ section 2.3.1 + // https://promisesaplus.com/#point-48 + if ( returned === deferred.promise() ) { + throw new TypeError( "Thenable self-resolution" ); + } + + // Support: Promises/A+ sections 2.3.3.1, 3.5 + // https://promisesaplus.com/#point-54 + // https://promisesaplus.com/#point-75 + // Retrieve `then` only once + then = returned && + + // Support: Promises/A+ section 2.3.4 + // https://promisesaplus.com/#point-64 + // Only check objects and functions for thenability + ( typeof returned === "object" || + typeof returned === "function" ) && + returned.then; + + // Handle a returned thenable + if ( jQuery.isFunction( then ) ) { + + // Special processors (notify) just wait for resolution + if ( special ) { + then.call( + returned, + resolve( maxDepth, deferred, Identity, special ), + resolve( maxDepth, deferred, Thrower, special ) + ); + + // Normal processors (resolve) also hook into progress + } else { + + // ...and disregard older resolution values + maxDepth++; + + then.call( + returned, + resolve( maxDepth, deferred, Identity, special ), + resolve( maxDepth, deferred, Thrower, special ), + resolve( maxDepth, deferred, Identity, + deferred.notifyWith ) + ); + } + + // Handle all other returned values + } else { + + // Only substitute handlers pass on context + // and multiple values (non-spec behavior) + if ( handler !== Identity ) { + that = undefined; + args = [ returned ]; + } + + // Process the value(s) + // Default process is resolve + ( special || deferred.resolveWith )( that, args ); + } + }, + + // Only normal processors (resolve) catch and reject exceptions + process = special ? + mightThrow : + function() { + try { + mightThrow(); + } catch ( e ) { + + if ( jQuery.Deferred.exceptionHook ) { + jQuery.Deferred.exceptionHook( e, + process.stackTrace ); + } + + // Support: Promises/A+ section 2.3.3.3.4.1 + // https://promisesaplus.com/#point-61 + // Ignore post-resolution exceptions + if ( depth + 1 >= maxDepth ) { + + // Only substitute handlers pass on context + // and multiple values (non-spec behavior) + if ( handler !== Thrower ) { + that = undefined; + args = [ e ]; + } + + deferred.rejectWith( that, args ); + } + } + }; + + // Support: Promises/A+ section 2.3.3.3.1 + // https://promisesaplus.com/#point-57 + // Re-resolve promises immediately to dodge false rejection from + // subsequent errors + if ( depth ) { + process(); + } else { + + // Call an optional hook to record the stack, in case of exception + // since it's otherwise lost when execution goes async + if ( jQuery.Deferred.getStackHook ) { + process.stackTrace = jQuery.Deferred.getStackHook(); + } + window.setTimeout( process ); + } + }; + } + + return jQuery.Deferred( function( newDefer ) { + + // progress_handlers.add( ... ) + tuples[ 0 ][ 3 ].add( + resolve( + 0, + newDefer, + jQuery.isFunction( onProgress ) ? + onProgress : + Identity, + newDefer.notifyWith + ) + ); + + // fulfilled_handlers.add( ... ) + tuples[ 1 ][ 3 ].add( + resolve( + 0, + newDefer, + jQuery.isFunction( onFulfilled ) ? + onFulfilled : + Identity + ) + ); + + // rejected_handlers.add( ... ) + tuples[ 2 ][ 3 ].add( + resolve( + 0, + newDefer, + jQuery.isFunction( onRejected ) ? + onRejected : + Thrower + ) + ); + } ).promise(); + }, + + // Get a promise for this deferred + // If obj is provided, the promise aspect is added to the object + promise: function( obj ) { + return obj != null ? jQuery.extend( obj, promise ) : promise; + } + }, + deferred = {}; + + // Add list-specific methods + jQuery.each( tuples, function( i, tuple ) { + var list = tuple[ 2 ], + stateString = tuple[ 5 ]; + + // promise.progress = list.add + // promise.done = list.add + // promise.fail = list.add + promise[ tuple[ 1 ] ] = list.add; + + // Handle state + if ( stateString ) { + list.add( + function() { + + // state = "resolved" (i.e., fulfilled) + // state = "rejected" + state = stateString; + }, + + // rejected_callbacks.disable + // fulfilled_callbacks.disable + tuples[ 3 - i ][ 2 ].disable, + + // progress_callbacks.lock + tuples[ 0 ][ 2 ].lock + ); + } + + // progress_handlers.fire + // fulfilled_handlers.fire + // rejected_handlers.fire + list.add( tuple[ 3 ].fire ); + + // deferred.notify = function() { deferred.notifyWith(...) } + // deferred.resolve = function() { deferred.resolveWith(...) } + // deferred.reject = function() { deferred.rejectWith(...) } + deferred[ tuple[ 0 ] ] = function() { + deferred[ tuple[ 0 ] + "With" ]( this === deferred ? undefined : this, arguments ); + return this; + }; + + // deferred.notifyWith = list.fireWith + // deferred.resolveWith = list.fireWith + // deferred.rejectWith = list.fireWith + deferred[ tuple[ 0 ] + "With" ] = list.fireWith; + } ); + + // Make the deferred a promise + promise.promise( deferred ); + + // Call given func if any + if ( func ) { + func.call( deferred, deferred ); + } + + // All done! + return deferred; + }, + + // Deferred helper + when: function( singleValue ) { + var + + // count of uncompleted subordinates + remaining = arguments.length, + + // count of unprocessed arguments + i = remaining, + + // subordinate fulfillment data + resolveContexts = Array( i ), + resolveValues = slice.call( arguments ), + + // the master Deferred + master = jQuery.Deferred(), + + // subordinate callback factory + updateFunc = function( i ) { + return function( value ) { + resolveContexts[ i ] = this; + resolveValues[ i ] = arguments.length > 1 ? slice.call( arguments ) : value; + if ( !( --remaining ) ) { + master.resolveWith( resolveContexts, resolveValues ); + } + }; + }; + + // Single- and empty arguments are adopted like Promise.resolve + if ( remaining <= 1 ) { + adoptValue( singleValue, master.done( updateFunc( i ) ).resolve, master.reject ); + + // Use .then() to unwrap secondary thenables (cf. gh-3000) + if ( master.state() === "pending" || + jQuery.isFunction( resolveValues[ i ] && resolveValues[ i ].then ) ) { + + return master.then(); + } + } + + // Multiple arguments are aggregated like Promise.all array elements + while ( i-- ) { + adoptValue( resolveValues[ i ], updateFunc( i ), master.reject ); + } + + return master.promise(); + } +} ); + + +// These usually indicate a programmer mistake during development, +// warn about them ASAP rather than swallowing them by default. +var rerrorNames = /^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/; + +jQuery.Deferred.exceptionHook = function( error, stack ) { + + // Support: IE 8 - 9 only + // Console exists when dev tools are open, which can happen at any time + if ( window.console && window.console.warn && error && rerrorNames.test( error.name ) ) { + window.console.warn( "jQuery.Deferred exception: " + error.message, error.stack, stack ); + } +}; + + + + +jQuery.readyException = function( error ) { + window.setTimeout( function() { + throw error; + } ); +}; + + + + +// The deferred used on DOM ready +var readyList = jQuery.Deferred(); + +jQuery.fn.ready = function( fn ) { + + readyList + .then( fn ) + + // Wrap jQuery.readyException in a function so that the lookup + // happens at the time of error handling instead of callback + // registration. + .catch( function( error ) { + jQuery.readyException( error ); + } ); + + return this; +}; + +jQuery.extend( { + + // Is the DOM ready to be used? Set to true once it occurs. + isReady: false, + + // A counter to track how many items to wait for before + // the ready event fires. See #6781 + readyWait: 1, + + // Hold (or release) the ready event + holdReady: function( hold ) { + if ( hold ) { + jQuery.readyWait++; + } else { + jQuery.ready( true ); + } + }, + + // Handle when the DOM is ready + ready: function( wait ) { + + // Abort if there are pending holds or we're already ready + if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) { + return; + } + + // Remember that the DOM is ready + jQuery.isReady = true; + + // If a normal DOM Ready event fired, decrement, and wait if need be + if ( wait !== true && --jQuery.readyWait > 0 ) { + return; + } + + // If there are functions bound, to execute + readyList.resolveWith( document, [ jQuery ] ); + } +} ); + +jQuery.ready.then = readyList.then; + +// The ready event handler and self cleanup method +function completed() { + document.removeEventListener( "DOMContentLoaded", completed ); + window.removeEventListener( "load", completed ); + jQuery.ready(); +} + +// Catch cases where $(document).ready() is called +// after the browser event has already occurred. +// Support: IE <=9 - 10 only +// Older IE sometimes signals "interactive" too soon +if ( document.readyState === "complete" || + ( document.readyState !== "loading" && !document.documentElement.doScroll ) ) { + + // Handle it asynchronously to allow scripts the opportunity to delay ready + window.setTimeout( jQuery.ready ); + +} else { + + // Use the handy event callback + document.addEventListener( "DOMContentLoaded", completed ); + + // A fallback to window.onload, that will always work + window.addEventListener( "load", completed ); +} + + + + +// Multifunctional method to get and set values of a collection +// The value/s can optionally be executed if it's a function +var access = function( elems, fn, key, value, chainable, emptyGet, raw ) { + var i = 0, + len = elems.length, + bulk = key == null; + + // Sets many values + if ( jQuery.type( key ) === "object" ) { + chainable = true; + for ( i in key ) { + access( elems, fn, i, key[ i ], true, emptyGet, raw ); + } + + // Sets one value + } else if ( value !== undefined ) { + chainable = true; + + if ( !jQuery.isFunction( value ) ) { + raw = true; + } + + if ( bulk ) { + + // Bulk operations run against the entire set + if ( raw ) { + fn.call( elems, value ); + fn = null; + + // ...except when executing function values + } else { + bulk = fn; + fn = function( elem, key, value ) { + return bulk.call( jQuery( elem ), value ); + }; + } + } + + if ( fn ) { + for ( ; i < len; i++ ) { + fn( + elems[ i ], key, raw ? + value : + value.call( elems[ i ], i, fn( elems[ i ], key ) ) + ); + } + } + } + + if ( chainable ) { + return elems; + } + + // Gets + if ( bulk ) { + return fn.call( elems ); + } + + return len ? fn( elems[ 0 ], key ) : emptyGet; +}; +var acceptData = function( owner ) { + + // Accepts only: + // - Node + // - Node.ELEMENT_NODE + // - Node.DOCUMENT_NODE + // - Object + // - Any + return owner.nodeType === 1 || owner.nodeType === 9 || !( +owner.nodeType ); +}; + + + + +function Data() { + this.expando = jQuery.expando + Data.uid++; +} + +Data.uid = 1; + +Data.prototype = { + + cache: function( owner ) { + + // Check if the owner object already has a cache + var value = owner[ this.expando ]; + + // If not, create one + if ( !value ) { + value = {}; + + // We can accept data for non-element nodes in modern browsers, + // but we should not, see #8335. + // Always return an empty object. + if ( acceptData( owner ) ) { + + // If it is a node unlikely to be stringify-ed or looped over + // use plain assignment + if ( owner.nodeType ) { + owner[ this.expando ] = value; + + // Otherwise secure it in a non-enumerable property + // configurable must be true to allow the property to be + // deleted when data is removed + } else { + Object.defineProperty( owner, this.expando, { + value: value, + configurable: true + } ); + } + } + } + + return value; + }, + set: function( owner, data, value ) { + var prop, + cache = this.cache( owner ); + + // Handle: [ owner, key, value ] args + // Always use camelCase key (gh-2257) + if ( typeof data === "string" ) { + cache[ jQuery.camelCase( data ) ] = value; + + // Handle: [ owner, { properties } ] args + } else { + + // Copy the properties one-by-one to the cache object + for ( prop in data ) { + cache[ jQuery.camelCase( prop ) ] = data[ prop ]; + } + } + return cache; + }, + get: function( owner, key ) { + return key === undefined ? + this.cache( owner ) : + + // Always use camelCase key (gh-2257) + owner[ this.expando ] && owner[ this.expando ][ jQuery.camelCase( key ) ]; + }, + access: function( owner, key, value ) { + + // In cases where either: + // + // 1. No key was specified + // 2. A string key was specified, but no value provided + // + // Take the "read" path and allow the get method to determine + // which value to return, respectively either: + // + // 1. The entire cache object + // 2. The data stored at the key + // + if ( key === undefined || + ( ( key && typeof key === "string" ) && value === undefined ) ) { + + return this.get( owner, key ); + } + + // When the key is not a string, or both a key and value + // are specified, set or extend (existing objects) with either: + // + // 1. An object of properties + // 2. A key and value + // + this.set( owner, key, value ); + + // Since the "set" path can have two possible entry points + // return the expected data based on which path was taken[*] + return value !== undefined ? value : key; + }, + remove: function( owner, key ) { + var i, + cache = owner[ this.expando ]; + + if ( cache === undefined ) { + return; + } + + if ( key !== undefined ) { + + // Support array or space separated string of keys + if ( jQuery.isArray( key ) ) { + + // If key is an array of keys... + // We always set camelCase keys, so remove that. + key = key.map( jQuery.camelCase ); + } else { + key = jQuery.camelCase( key ); + + // If a key with the spaces exists, use it. + // Otherwise, create an array by matching non-whitespace + key = key in cache ? + [ key ] : + ( key.match( rnothtmlwhite ) || [] ); + } + + i = key.length; + + while ( i-- ) { + delete cache[ key[ i ] ]; + } + } + + // Remove the expando if there's no more data + if ( key === undefined || jQuery.isEmptyObject( cache ) ) { + + // Support: Chrome <=35 - 45 + // Webkit & Blink performance suffers when deleting properties + // from DOM nodes, so set to undefined instead + // https://bugs.chromium.org/p/chromium/issues/detail?id=378607 (bug restricted) + if ( owner.nodeType ) { + owner[ this.expando ] = undefined; + } else { + delete owner[ this.expando ]; + } + } + }, + hasData: function( owner ) { + var cache = owner[ this.expando ]; + return cache !== undefined && !jQuery.isEmptyObject( cache ); + } +}; +var dataPriv = new Data(); + +var dataUser = new Data(); + + + +// Implementation Summary +// +// 1. Enforce API surface and semantic compatibility with 1.9.x branch +// 2. Improve the module's maintainability by reducing the storage +// paths to a single mechanism. +// 3. Use the same single mechanism to support "private" and "user" data. +// 4. _Never_ expose "private" data to user code (TODO: Drop _data, _removeData) +// 5. Avoid exposing implementation details on user objects (eg. expando properties) +// 6. Provide a clear path for implementation upgrade to WeakMap in 2014 + +var rbrace = /^(?:\{[\w\W]*\}|\[[\w\W]*\])$/, + rmultiDash = /[A-Z]/g; + +function getData( data ) { + if ( data === "true" ) { + return true; + } + + if ( data === "false" ) { + return false; + } + + if ( data === "null" ) { + return null; + } + + // Only convert to a number if it doesn't change the string + if ( data === +data + "" ) { + return +data; + } + + if ( rbrace.test( data ) ) { + return JSON.parse( data ); + } + + return data; +} + +function dataAttr( elem, key, data ) { + var name; + + // If nothing was found internally, try to fetch any + // data from the HTML5 data-* attribute + if ( data === undefined && elem.nodeType === 1 ) { + name = "data-" + key.replace( rmultiDash, "-$&" ).toLowerCase(); + data = elem.getAttribute( name ); + + if ( typeof data === "string" ) { + try { + data = getData( data ); + } catch ( e ) {} + + // Make sure we set the data so it isn't changed later + dataUser.set( elem, key, data ); + } else { + data = undefined; + } + } + return data; +} + +jQuery.extend( { + hasData: function( elem ) { + return dataUser.hasData( elem ) || dataPriv.hasData( elem ); + }, + + data: function( elem, name, data ) { + return dataUser.access( elem, name, data ); + }, + + removeData: function( elem, name ) { + dataUser.remove( elem, name ); + }, + + // TODO: Now that all calls to _data and _removeData have been replaced + // with direct calls to dataPriv methods, these can be deprecated. + _data: function( elem, name, data ) { + return dataPriv.access( elem, name, data ); + }, + + _removeData: function( elem, name ) { + dataPriv.remove( elem, name ); + } +} ); + +jQuery.fn.extend( { + data: function( key, value ) { + var i, name, data, + elem = this[ 0 ], + attrs = elem && elem.attributes; + + // Gets all values + if ( key === undefined ) { + if ( this.length ) { + data = dataUser.get( elem ); + + if ( elem.nodeType === 1 && !dataPriv.get( elem, "hasDataAttrs" ) ) { + i = attrs.length; + while ( i-- ) { + + // Support: IE 11 only + // The attrs elements can be null (#14894) + if ( attrs[ i ] ) { + name = attrs[ i ].name; + if ( name.indexOf( "data-" ) === 0 ) { + name = jQuery.camelCase( name.slice( 5 ) ); + dataAttr( elem, name, data[ name ] ); + } + } + } + dataPriv.set( elem, "hasDataAttrs", true ); + } + } + + return data; + } + + // Sets multiple values + if ( typeof key === "object" ) { + return this.each( function() { + dataUser.set( this, key ); + } ); + } + + return access( this, function( value ) { + var data; + + // The calling jQuery object (element matches) is not empty + // (and therefore has an element appears at this[ 0 ]) and the + // `value` parameter was not undefined. An empty jQuery object + // will result in `undefined` for elem = this[ 0 ] which will + // throw an exception if an attempt to read a data cache is made. + if ( elem && value === undefined ) { + + // Attempt to get data from the cache + // The key will always be camelCased in Data + data = dataUser.get( elem, key ); + if ( data !== undefined ) { + return data; + } + + // Attempt to "discover" the data in + // HTML5 custom data-* attrs + data = dataAttr( elem, key ); + if ( data !== undefined ) { + return data; + } + + // We tried really hard, but the data doesn't exist. + return; + } + + // Set the data... + this.each( function() { + + // We always store the camelCased key + dataUser.set( this, key, value ); + } ); + }, null, value, arguments.length > 1, null, true ); + }, + + removeData: function( key ) { + return this.each( function() { + dataUser.remove( this, key ); + } ); + } +} ); + + +jQuery.extend( { + queue: function( elem, type, data ) { + var queue; + + if ( elem ) { + type = ( type || "fx" ) + "queue"; + queue = dataPriv.get( elem, type ); + + // Speed up dequeue by getting out quickly if this is just a lookup + if ( data ) { + if ( !queue || jQuery.isArray( data ) ) { + queue = dataPriv.access( elem, type, jQuery.makeArray( data ) ); + } else { + queue.push( data ); + } + } + return queue || []; + } + }, + + dequeue: function( elem, type ) { + type = type || "fx"; + + var queue = jQuery.queue( elem, type ), + startLength = queue.length, + fn = queue.shift(), + hooks = jQuery._queueHooks( elem, type ), + next = function() { + jQuery.dequeue( elem, type ); + }; + + // If the fx queue is dequeued, always remove the progress sentinel + if ( fn === "inprogress" ) { + fn = queue.shift(); + startLength--; + } + + if ( fn ) { + + // Add a progress sentinel to prevent the fx queue from being + // automatically dequeued + if ( type === "fx" ) { + queue.unshift( "inprogress" ); + } + + // Clear up the last queue stop function + delete hooks.stop; + fn.call( elem, next, hooks ); + } + + if ( !startLength && hooks ) { + hooks.empty.fire(); + } + }, + + // Not public - generate a queueHooks object, or return the current one + _queueHooks: function( elem, type ) { + var key = type + "queueHooks"; + return dataPriv.get( elem, key ) || dataPriv.access( elem, key, { + empty: jQuery.Callbacks( "once memory" ).add( function() { + dataPriv.remove( elem, [ type + "queue", key ] ); + } ) + } ); + } +} ); + +jQuery.fn.extend( { + queue: function( type, data ) { + var setter = 2; + + if ( typeof type !== "string" ) { + data = type; + type = "fx"; + setter--; + } + + if ( arguments.length < setter ) { + return jQuery.queue( this[ 0 ], type ); + } + + return data === undefined ? + this : + this.each( function() { + var queue = jQuery.queue( this, type, data ); + + // Ensure a hooks for this queue + jQuery._queueHooks( this, type ); + + if ( type === "fx" && queue[ 0 ] !== "inprogress" ) { + jQuery.dequeue( this, type ); + } + } ); + }, + dequeue: function( type ) { + return this.each( function() { + jQuery.dequeue( this, type ); + } ); + }, + clearQueue: function( type ) { + return this.queue( type || "fx", [] ); + }, + + // Get a promise resolved when queues of a certain type + // are emptied (fx is the type by default) + promise: function( type, obj ) { + var tmp, + count = 1, + defer = jQuery.Deferred(), + elements = this, + i = this.length, + resolve = function() { + if ( !( --count ) ) { + defer.resolveWith( elements, [ elements ] ); + } + }; + + if ( typeof type !== "string" ) { + obj = type; + type = undefined; + } + type = type || "fx"; + + while ( i-- ) { + tmp = dataPriv.get( elements[ i ], type + "queueHooks" ); + if ( tmp && tmp.empty ) { + count++; + tmp.empty.add( resolve ); + } + } + resolve(); + return defer.promise( obj ); + } +} ); +var pnum = ( /[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/ ).source; + +var rcssNum = new RegExp( "^(?:([+-])=|)(" + pnum + ")([a-z%]*)$", "i" ); + + +var cssExpand = [ "Top", "Right", "Bottom", "Left" ]; + +var isHiddenWithinTree = function( elem, el ) { + + // isHiddenWithinTree might be called from jQuery#filter function; + // in that case, element will be second argument + elem = el || elem; + + // Inline style trumps all + return elem.style.display === "none" || + elem.style.display === "" && + + // Otherwise, check computed style + // Support: Firefox <=43 - 45 + // Disconnected elements can have computed display: none, so first confirm that elem is + // in the document. + jQuery.contains( elem.ownerDocument, elem ) && + + jQuery.css( elem, "display" ) === "none"; + }; + +var swap = function( elem, options, callback, args ) { + var ret, name, + old = {}; + + // Remember the old values, and insert the new ones + for ( name in options ) { + old[ name ] = elem.style[ name ]; + elem.style[ name ] = options[ name ]; + } + + ret = callback.apply( elem, args || [] ); + + // Revert the old values + for ( name in options ) { + elem.style[ name ] = old[ name ]; + } + + return ret; +}; + + + + +function adjustCSS( elem, prop, valueParts, tween ) { + var adjusted, + scale = 1, + maxIterations = 20, + currentValue = tween ? + function() { + return tween.cur(); + } : + function() { + return jQuery.css( elem, prop, "" ); + }, + initial = currentValue(), + unit = valueParts && valueParts[ 3 ] || ( jQuery.cssNumber[ prop ] ? "" : "px" ), + + // Starting value computation is required for potential unit mismatches + initialInUnit = ( jQuery.cssNumber[ prop ] || unit !== "px" && +initial ) && + rcssNum.exec( jQuery.css( elem, prop ) ); + + if ( initialInUnit && initialInUnit[ 3 ] !== unit ) { + + // Trust units reported by jQuery.css + unit = unit || initialInUnit[ 3 ]; + + // Make sure we update the tween properties later on + valueParts = valueParts || []; + + // Iteratively approximate from a nonzero starting point + initialInUnit = +initial || 1; + + do { + + // If previous iteration zeroed out, double until we get *something*. + // Use string for doubling so we don't accidentally see scale as unchanged below + scale = scale || ".5"; + + // Adjust and apply + initialInUnit = initialInUnit / scale; + jQuery.style( elem, prop, initialInUnit + unit ); + + // Update scale, tolerating zero or NaN from tween.cur() + // Break the loop if scale is unchanged or perfect, or if we've just had enough. + } while ( + scale !== ( scale = currentValue() / initial ) && scale !== 1 && --maxIterations + ); + } + + if ( valueParts ) { + initialInUnit = +initialInUnit || +initial || 0; + + // Apply relative offset (+=/-=) if specified + adjusted = valueParts[ 1 ] ? + initialInUnit + ( valueParts[ 1 ] + 1 ) * valueParts[ 2 ] : + +valueParts[ 2 ]; + if ( tween ) { + tween.unit = unit; + tween.start = initialInUnit; + tween.end = adjusted; + } + } + return adjusted; +} + + +var defaultDisplayMap = {}; + +function getDefaultDisplay( elem ) { + var temp, + doc = elem.ownerDocument, + nodeName = elem.nodeName, + display = defaultDisplayMap[ nodeName ]; + + if ( display ) { + return display; + } + + temp = doc.body.appendChild( doc.createElement( nodeName ) ); + display = jQuery.css( temp, "display" ); + + temp.parentNode.removeChild( temp ); + + if ( display === "none" ) { + display = "block"; + } + defaultDisplayMap[ nodeName ] = display; + + return display; +} + +function showHide( elements, show ) { + var display, elem, + values = [], + index = 0, + length = elements.length; + + // Determine new display value for elements that need to change + for ( ; index < length; index++ ) { + elem = elements[ index ]; + if ( !elem.style ) { + continue; + } + + display = elem.style.display; + if ( show ) { + + // Since we force visibility upon cascade-hidden elements, an immediate (and slow) + // check is required in this first loop unless we have a nonempty display value (either + // inline or about-to-be-restored) + if ( display === "none" ) { + values[ index ] = dataPriv.get( elem, "display" ) || null; + if ( !values[ index ] ) { + elem.style.display = ""; + } + } + if ( elem.style.display === "" && isHiddenWithinTree( elem ) ) { + values[ index ] = getDefaultDisplay( elem ); + } + } else { + if ( display !== "none" ) { + values[ index ] = "none"; + + // Remember what we're overwriting + dataPriv.set( elem, "display", display ); + } + } + } + + // Set the display of the elements in a second loop to avoid constant reflow + for ( index = 0; index < length; index++ ) { + if ( values[ index ] != null ) { + elements[ index ].style.display = values[ index ]; + } + } + + return elements; +} + +jQuery.fn.extend( { + show: function() { + return showHide( this, true ); + }, + hide: function() { + return showHide( this ); + }, + toggle: function( state ) { + if ( typeof state === "boolean" ) { + return state ? this.show() : this.hide(); + } + + return this.each( function() { + if ( isHiddenWithinTree( this ) ) { + jQuery( this ).show(); + } else { + jQuery( this ).hide(); + } + } ); + } +} ); +var rcheckableType = ( /^(?:checkbox|radio)$/i ); + +var rtagName = ( /<([a-z][^\/\0>\x20\t\r\n\f]+)/i ); + +var rscriptType = ( /^$|\/(?:java|ecma)script/i ); + + + +// We have to close these tags to support XHTML (#13200) +var wrapMap = { + + // Support: IE <=9 only + option: [ 1, "" ], + + // XHTML parsers do not magically insert elements in the + // same way that tag soup parsers do. So we cannot shorten + // this by omitting or other required elements. + thead: [ 1, "", "
" ], + col: [ 2, "", "
" ], + tr: [ 2, "", "
" ], + td: [ 3, "", "
" ], + + _default: [ 0, "", "" ] +}; + +// Support: IE <=9 only +wrapMap.optgroup = wrapMap.option; + +wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; +wrapMap.th = wrapMap.td; + + +function getAll( context, tag ) { + + // Support: IE <=9 - 11 only + // Use typeof to avoid zero-argument method invocation on host objects (#15151) + var ret; + + if ( typeof context.getElementsByTagName !== "undefined" ) { + ret = context.getElementsByTagName( tag || "*" ); + + } else if ( typeof context.querySelectorAll !== "undefined" ) { + ret = context.querySelectorAll( tag || "*" ); + + } else { + ret = []; + } + + if ( tag === undefined || tag && jQuery.nodeName( context, tag ) ) { + return jQuery.merge( [ context ], ret ); + } + + return ret; +} + + +// Mark scripts as having already been evaluated +function setGlobalEval( elems, refElements ) { + var i = 0, + l = elems.length; + + for ( ; i < l; i++ ) { + dataPriv.set( + elems[ i ], + "globalEval", + !refElements || dataPriv.get( refElements[ i ], "globalEval" ) + ); + } +} + + +var rhtml = /<|&#?\w+;/; + +function buildFragment( elems, context, scripts, selection, ignored ) { + var elem, tmp, tag, wrap, contains, j, + fragment = context.createDocumentFragment(), + nodes = [], + i = 0, + l = elems.length; + + for ( ; i < l; i++ ) { + elem = elems[ i ]; + + if ( elem || elem === 0 ) { + + // Add nodes directly + if ( jQuery.type( elem ) === "object" ) { + + // Support: Android <=4.0 only, PhantomJS 1 only + // push.apply(_, arraylike) throws on ancient WebKit + jQuery.merge( nodes, elem.nodeType ? [ elem ] : elem ); + + // Convert non-html into a text node + } else if ( !rhtml.test( elem ) ) { + nodes.push( context.createTextNode( elem ) ); + + // Convert html into DOM nodes + } else { + tmp = tmp || fragment.appendChild( context.createElement( "div" ) ); + + // Deserialize a standard representation + tag = ( rtagName.exec( elem ) || [ "", "" ] )[ 1 ].toLowerCase(); + wrap = wrapMap[ tag ] || wrapMap._default; + tmp.innerHTML = wrap[ 1 ] + jQuery.htmlPrefilter( elem ) + wrap[ 2 ]; + + // Descend through wrappers to the right content + j = wrap[ 0 ]; + while ( j-- ) { + tmp = tmp.lastChild; + } + + // Support: Android <=4.0 only, PhantomJS 1 only + // push.apply(_, arraylike) throws on ancient WebKit + jQuery.merge( nodes, tmp.childNodes ); + + // Remember the top-level container + tmp = fragment.firstChild; + + // Ensure the created nodes are orphaned (#12392) + tmp.textContent = ""; + } + } + } + + // Remove wrapper from fragment + fragment.textContent = ""; + + i = 0; + while ( ( elem = nodes[ i++ ] ) ) { + + // Skip elements already in the context collection (trac-4087) + if ( selection && jQuery.inArray( elem, selection ) > -1 ) { + if ( ignored ) { + ignored.push( elem ); + } + continue; + } + + contains = jQuery.contains( elem.ownerDocument, elem ); + + // Append to fragment + tmp = getAll( fragment.appendChild( elem ), "script" ); + + // Preserve script evaluation history + if ( contains ) { + setGlobalEval( tmp ); + } + + // Capture executables + if ( scripts ) { + j = 0; + while ( ( elem = tmp[ j++ ] ) ) { + if ( rscriptType.test( elem.type || "" ) ) { + scripts.push( elem ); + } + } + } + } + + return fragment; +} + + +( function() { + var fragment = document.createDocumentFragment(), + div = fragment.appendChild( document.createElement( "div" ) ), + input = document.createElement( "input" ); + + // Support: Android 4.0 - 4.3 only + // Check state lost if the name is set (#11217) + // Support: Windows Web Apps (WWA) + // `name` and `type` must use .setAttribute for WWA (#14901) + input.setAttribute( "type", "radio" ); + input.setAttribute( "checked", "checked" ); + input.setAttribute( "name", "t" ); + + div.appendChild( input ); + + // Support: Android <=4.1 only + // Older WebKit doesn't clone checked state correctly in fragments + support.checkClone = div.cloneNode( true ).cloneNode( true ).lastChild.checked; + + // Support: IE <=11 only + // Make sure textarea (and checkbox) defaultValue is properly cloned + div.innerHTML = ""; + support.noCloneChecked = !!div.cloneNode( true ).lastChild.defaultValue; +} )(); +var documentElement = document.documentElement; + + + +var + rkeyEvent = /^key/, + rmouseEvent = /^(?:mouse|pointer|contextmenu|drag|drop)|click/, + rtypenamespace = /^([^.]*)(?:\.(.+)|)/; + +function returnTrue() { + return true; +} + +function returnFalse() { + return false; +} + +// Support: IE <=9 only +// See #13393 for more info +function safeActiveElement() { + try { + return document.activeElement; + } catch ( err ) { } +} + +function on( elem, types, selector, data, fn, one ) { + var origFn, type; + + // Types can be a map of types/handlers + if ( typeof types === "object" ) { + + // ( types-Object, selector, data ) + if ( typeof selector !== "string" ) { + + // ( types-Object, data ) + data = data || selector; + selector = undefined; + } + for ( type in types ) { + on( elem, type, selector, data, types[ type ], one ); + } + return elem; + } + + if ( data == null && fn == null ) { + + // ( types, fn ) + fn = selector; + data = selector = undefined; + } else if ( fn == null ) { + if ( typeof selector === "string" ) { + + // ( types, selector, fn ) + fn = data; + data = undefined; + } else { + + // ( types, data, fn ) + fn = data; + data = selector; + selector = undefined; + } + } + if ( fn === false ) { + fn = returnFalse; + } else if ( !fn ) { + return elem; + } + + if ( one === 1 ) { + origFn = fn; + fn = function( event ) { + + // Can use an empty set, since event contains the info + jQuery().off( event ); + return origFn.apply( this, arguments ); + }; + + // Use same guid so caller can remove using origFn + fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ ); + } + return elem.each( function() { + jQuery.event.add( this, types, fn, data, selector ); + } ); +} + +/* + * Helper functions for managing events -- not part of the public interface. + * Props to Dean Edwards' addEvent library for many of the ideas. + */ +jQuery.event = { + + global: {}, + + add: function( elem, types, handler, data, selector ) { + + var handleObjIn, eventHandle, tmp, + events, t, handleObj, + special, handlers, type, namespaces, origType, + elemData = dataPriv.get( elem ); + + // Don't attach events to noData or text/comment nodes (but allow plain objects) + if ( !elemData ) { + return; + } + + // Caller can pass in an object of custom data in lieu of the handler + if ( handler.handler ) { + handleObjIn = handler; + handler = handleObjIn.handler; + selector = handleObjIn.selector; + } + + // Ensure that invalid selectors throw exceptions at attach time + // Evaluate against documentElement in case elem is a non-element node (e.g., document) + if ( selector ) { + jQuery.find.matchesSelector( documentElement, selector ); + } + + // Make sure that the handler has a unique ID, used to find/remove it later + if ( !handler.guid ) { + handler.guid = jQuery.guid++; + } + + // Init the element's event structure and main handler, if this is the first + if ( !( events = elemData.events ) ) { + events = elemData.events = {}; + } + if ( !( eventHandle = elemData.handle ) ) { + eventHandle = elemData.handle = function( e ) { + + // Discard the second event of a jQuery.event.trigger() and + // when an event is called after a page has unloaded + return typeof jQuery !== "undefined" && jQuery.event.triggered !== e.type ? + jQuery.event.dispatch.apply( elem, arguments ) : undefined; + }; + } + + // Handle multiple events separated by a space + types = ( types || "" ).match( rnothtmlwhite ) || [ "" ]; + t = types.length; + while ( t-- ) { + tmp = rtypenamespace.exec( types[ t ] ) || []; + type = origType = tmp[ 1 ]; + namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort(); + + // There *must* be a type, no attaching namespace-only handlers + if ( !type ) { + continue; + } + + // If event changes its type, use the special event handlers for the changed type + special = jQuery.event.special[ type ] || {}; + + // If selector defined, determine special event api type, otherwise given type + type = ( selector ? special.delegateType : special.bindType ) || type; + + // Update special based on newly reset type + special = jQuery.event.special[ type ] || {}; + + // handleObj is passed to all event handlers + handleObj = jQuery.extend( { + type: type, + origType: origType, + data: data, + handler: handler, + guid: handler.guid, + selector: selector, + needsContext: selector && jQuery.expr.match.needsContext.test( selector ), + namespace: namespaces.join( "." ) + }, handleObjIn ); + + // Init the event handler queue if we're the first + if ( !( handlers = events[ type ] ) ) { + handlers = events[ type ] = []; + handlers.delegateCount = 0; + + // Only use addEventListener if the special events handler returns false + if ( !special.setup || + special.setup.call( elem, data, namespaces, eventHandle ) === false ) { + + if ( elem.addEventListener ) { + elem.addEventListener( type, eventHandle ); + } + } + } + + if ( special.add ) { + special.add.call( elem, handleObj ); + + if ( !handleObj.handler.guid ) { + handleObj.handler.guid = handler.guid; + } + } + + // Add to the element's handler list, delegates in front + if ( selector ) { + handlers.splice( handlers.delegateCount++, 0, handleObj ); + } else { + handlers.push( handleObj ); + } + + // Keep track of which events have ever been used, for event optimization + jQuery.event.global[ type ] = true; + } + + }, + + // Detach an event or set of events from an element + remove: function( elem, types, handler, selector, mappedTypes ) { + + var j, origCount, tmp, + events, t, handleObj, + special, handlers, type, namespaces, origType, + elemData = dataPriv.hasData( elem ) && dataPriv.get( elem ); + + if ( !elemData || !( events = elemData.events ) ) { + return; + } + + // Once for each type.namespace in types; type may be omitted + types = ( types || "" ).match( rnothtmlwhite ) || [ "" ]; + t = types.length; + while ( t-- ) { + tmp = rtypenamespace.exec( types[ t ] ) || []; + type = origType = tmp[ 1 ]; + namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort(); + + // Unbind all events (on this namespace, if provided) for the element + if ( !type ) { + for ( type in events ) { + jQuery.event.remove( elem, type + types[ t ], handler, selector, true ); + } + continue; + } + + special = jQuery.event.special[ type ] || {}; + type = ( selector ? special.delegateType : special.bindType ) || type; + handlers = events[ type ] || []; + tmp = tmp[ 2 ] && + new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" ); + + // Remove matching events + origCount = j = handlers.length; + while ( j-- ) { + handleObj = handlers[ j ]; + + if ( ( mappedTypes || origType === handleObj.origType ) && + ( !handler || handler.guid === handleObj.guid ) && + ( !tmp || tmp.test( handleObj.namespace ) ) && + ( !selector || selector === handleObj.selector || + selector === "**" && handleObj.selector ) ) { + handlers.splice( j, 1 ); + + if ( handleObj.selector ) { + handlers.delegateCount--; + } + if ( special.remove ) { + special.remove.call( elem, handleObj ); + } + } + } + + // Remove generic event handler if we removed something and no more handlers exist + // (avoids potential for endless recursion during removal of special event handlers) + if ( origCount && !handlers.length ) { + if ( !special.teardown || + special.teardown.call( elem, namespaces, elemData.handle ) === false ) { + + jQuery.removeEvent( elem, type, elemData.handle ); + } + + delete events[ type ]; + } + } + + // Remove data and the expando if it's no longer used + if ( jQuery.isEmptyObject( events ) ) { + dataPriv.remove( elem, "handle events" ); + } + }, + + dispatch: function( nativeEvent ) { + + // Make a writable jQuery.Event from the native event object + var event = jQuery.event.fix( nativeEvent ); + + var i, j, ret, matched, handleObj, handlerQueue, + args = new Array( arguments.length ), + handlers = ( dataPriv.get( this, "events" ) || {} )[ event.type ] || [], + special = jQuery.event.special[ event.type ] || {}; + + // Use the fix-ed jQuery.Event rather than the (read-only) native event + args[ 0 ] = event; + + for ( i = 1; i < arguments.length; i++ ) { + args[ i ] = arguments[ i ]; + } + + event.delegateTarget = this; + + // Call the preDispatch hook for the mapped type, and let it bail if desired + if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) { + return; + } + + // Determine handlers + handlerQueue = jQuery.event.handlers.call( this, event, handlers ); + + // Run delegates first; they may want to stop propagation beneath us + i = 0; + while ( ( matched = handlerQueue[ i++ ] ) && !event.isPropagationStopped() ) { + event.currentTarget = matched.elem; + + j = 0; + while ( ( handleObj = matched.handlers[ j++ ] ) && + !event.isImmediatePropagationStopped() ) { + + // Triggered event must either 1) have no namespace, or 2) have namespace(s) + // a subset or equal to those in the bound event (both can have no namespace). + if ( !event.rnamespace || event.rnamespace.test( handleObj.namespace ) ) { + + event.handleObj = handleObj; + event.data = handleObj.data; + + ret = ( ( jQuery.event.special[ handleObj.origType ] || {} ).handle || + handleObj.handler ).apply( matched.elem, args ); + + if ( ret !== undefined ) { + if ( ( event.result = ret ) === false ) { + event.preventDefault(); + event.stopPropagation(); + } + } + } + } + } + + // Call the postDispatch hook for the mapped type + if ( special.postDispatch ) { + special.postDispatch.call( this, event ); + } + + return event.result; + }, + + handlers: function( event, handlers ) { + var i, handleObj, sel, matchedHandlers, matchedSelectors, + handlerQueue = [], + delegateCount = handlers.delegateCount, + cur = event.target; + + // Find delegate handlers + if ( delegateCount && + + // Support: IE <=9 + // Black-hole SVG instance trees (trac-13180) + cur.nodeType && + + // Support: Firefox <=42 + // Suppress spec-violating clicks indicating a non-primary pointer button (trac-3861) + // https://www.w3.org/TR/DOM-Level-3-Events/#event-type-click + // Support: IE 11 only + // ...but not arrow key "clicks" of radio inputs, which can have `button` -1 (gh-2343) + !( event.type === "click" && event.button >= 1 ) ) { + + for ( ; cur !== this; cur = cur.parentNode || this ) { + + // Don't check non-elements (#13208) + // Don't process clicks on disabled elements (#6911, #8165, #11382, #11764) + if ( cur.nodeType === 1 && !( event.type === "click" && cur.disabled === true ) ) { + matchedHandlers = []; + matchedSelectors = {}; + for ( i = 0; i < delegateCount; i++ ) { + handleObj = handlers[ i ]; + + // Don't conflict with Object.prototype properties (#13203) + sel = handleObj.selector + " "; + + if ( matchedSelectors[ sel ] === undefined ) { + matchedSelectors[ sel ] = handleObj.needsContext ? + jQuery( sel, this ).index( cur ) > -1 : + jQuery.find( sel, this, null, [ cur ] ).length; + } + if ( matchedSelectors[ sel ] ) { + matchedHandlers.push( handleObj ); + } + } + if ( matchedHandlers.length ) { + handlerQueue.push( { elem: cur, handlers: matchedHandlers } ); + } + } + } + } + + // Add the remaining (directly-bound) handlers + cur = this; + if ( delegateCount < handlers.length ) { + handlerQueue.push( { elem: cur, handlers: handlers.slice( delegateCount ) } ); + } + + return handlerQueue; + }, + + addProp: function( name, hook ) { + Object.defineProperty( jQuery.Event.prototype, name, { + enumerable: true, + configurable: true, + + get: jQuery.isFunction( hook ) ? + function() { + if ( this.originalEvent ) { + return hook( this.originalEvent ); + } + } : + function() { + if ( this.originalEvent ) { + return this.originalEvent[ name ]; + } + }, + + set: function( value ) { + Object.defineProperty( this, name, { + enumerable: true, + configurable: true, + writable: true, + value: value + } ); + } + } ); + }, + + fix: function( originalEvent ) { + return originalEvent[ jQuery.expando ] ? + originalEvent : + new jQuery.Event( originalEvent ); + }, + + special: { + load: { + + // Prevent triggered image.load events from bubbling to window.load + noBubble: true + }, + focus: { + + // Fire native event if possible so blur/focus sequence is correct + trigger: function() { + if ( this !== safeActiveElement() && this.focus ) { + this.focus(); + return false; + } + }, + delegateType: "focusin" + }, + blur: { + trigger: function() { + if ( this === safeActiveElement() && this.blur ) { + this.blur(); + return false; + } + }, + delegateType: "focusout" + }, + click: { + + // For checkbox, fire native event so checked state will be right + trigger: function() { + if ( this.type === "checkbox" && this.click && jQuery.nodeName( this, "input" ) ) { + this.click(); + return false; + } + }, + + // For cross-browser consistency, don't fire native .click() on links + _default: function( event ) { + return jQuery.nodeName( event.target, "a" ); + } + }, + + beforeunload: { + postDispatch: function( event ) { + + // Support: Firefox 20+ + // Firefox doesn't alert if the returnValue field is not set. + if ( event.result !== undefined && event.originalEvent ) { + event.originalEvent.returnValue = event.result; + } + } + } + } +}; + +jQuery.removeEvent = function( elem, type, handle ) { + + // This "if" is needed for plain objects + if ( elem.removeEventListener ) { + elem.removeEventListener( type, handle ); + } +}; + +jQuery.Event = function( src, props ) { + + // Allow instantiation without the 'new' keyword + if ( !( this instanceof jQuery.Event ) ) { + return new jQuery.Event( src, props ); + } + + // Event object + if ( src && src.type ) { + this.originalEvent = src; + this.type = src.type; + + // Events bubbling up the document may have been marked as prevented + // by a handler lower down the tree; reflect the correct value. + this.isDefaultPrevented = src.defaultPrevented || + src.defaultPrevented === undefined && + + // Support: Android <=2.3 only + src.returnValue === false ? + returnTrue : + returnFalse; + + // Create target properties + // Support: Safari <=6 - 7 only + // Target should not be a text node (#504, #13143) + this.target = ( src.target && src.target.nodeType === 3 ) ? + src.target.parentNode : + src.target; + + this.currentTarget = src.currentTarget; + this.relatedTarget = src.relatedTarget; + + // Event type + } else { + this.type = src; + } + + // Put explicitly provided properties onto the event object + if ( props ) { + jQuery.extend( this, props ); + } + + // Create a timestamp if incoming event doesn't have one + this.timeStamp = src && src.timeStamp || jQuery.now(); + + // Mark it as fixed + this[ jQuery.expando ] = true; +}; + +// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding +// https://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html +jQuery.Event.prototype = { + constructor: jQuery.Event, + isDefaultPrevented: returnFalse, + isPropagationStopped: returnFalse, + isImmediatePropagationStopped: returnFalse, + isSimulated: false, + + preventDefault: function() { + var e = this.originalEvent; + + this.isDefaultPrevented = returnTrue; + + if ( e && !this.isSimulated ) { + e.preventDefault(); + } + }, + stopPropagation: function() { + var e = this.originalEvent; + + this.isPropagationStopped = returnTrue; + + if ( e && !this.isSimulated ) { + e.stopPropagation(); + } + }, + stopImmediatePropagation: function() { + var e = this.originalEvent; + + this.isImmediatePropagationStopped = returnTrue; + + if ( e && !this.isSimulated ) { + e.stopImmediatePropagation(); + } + + this.stopPropagation(); + } +}; + +// Includes all common event props including KeyEvent and MouseEvent specific props +jQuery.each( { + altKey: true, + bubbles: true, + cancelable: true, + changedTouches: true, + ctrlKey: true, + detail: true, + eventPhase: true, + metaKey: true, + pageX: true, + pageY: true, + shiftKey: true, + view: true, + "char": true, + charCode: true, + key: true, + keyCode: true, + button: true, + buttons: true, + clientX: true, + clientY: true, + offsetX: true, + offsetY: true, + pointerId: true, + pointerType: true, + screenX: true, + screenY: true, + targetTouches: true, + toElement: true, + touches: true, + + which: function( event ) { + var button = event.button; + + // Add which for key events + if ( event.which == null && rkeyEvent.test( event.type ) ) { + return event.charCode != null ? event.charCode : event.keyCode; + } + + // Add which for click: 1 === left; 2 === middle; 3 === right + if ( !event.which && button !== undefined && rmouseEvent.test( event.type ) ) { + if ( button & 1 ) { + return 1; + } + + if ( button & 2 ) { + return 3; + } + + if ( button & 4 ) { + return 2; + } + + return 0; + } + + return event.which; + } +}, jQuery.event.addProp ); + +// Create mouseenter/leave events using mouseover/out and event-time checks +// so that event delegation works in jQuery. +// Do the same for pointerenter/pointerleave and pointerover/pointerout +// +// Support: Safari 7 only +// Safari sends mouseenter too often; see: +// https://bugs.chromium.org/p/chromium/issues/detail?id=470258 +// for the description of the bug (it existed in older Chrome versions as well). +jQuery.each( { + mouseenter: "mouseover", + mouseleave: "mouseout", + pointerenter: "pointerover", + pointerleave: "pointerout" +}, function( orig, fix ) { + jQuery.event.special[ orig ] = { + delegateType: fix, + bindType: fix, + + handle: function( event ) { + var ret, + target = this, + related = event.relatedTarget, + handleObj = event.handleObj; + + // For mouseenter/leave call the handler if related is outside the target. + // NB: No relatedTarget if the mouse left/entered the browser window + if ( !related || ( related !== target && !jQuery.contains( target, related ) ) ) { + event.type = handleObj.origType; + ret = handleObj.handler.apply( this, arguments ); + event.type = fix; + } + return ret; + } + }; +} ); + +jQuery.fn.extend( { + + on: function( types, selector, data, fn ) { + return on( this, types, selector, data, fn ); + }, + one: function( types, selector, data, fn ) { + return on( this, types, selector, data, fn, 1 ); + }, + off: function( types, selector, fn ) { + var handleObj, type; + if ( types && types.preventDefault && types.handleObj ) { + + // ( event ) dispatched jQuery.Event + handleObj = types.handleObj; + jQuery( types.delegateTarget ).off( + handleObj.namespace ? + handleObj.origType + "." + handleObj.namespace : + handleObj.origType, + handleObj.selector, + handleObj.handler + ); + return this; + } + if ( typeof types === "object" ) { + + // ( types-object [, selector] ) + for ( type in types ) { + this.off( type, selector, types[ type ] ); + } + return this; + } + if ( selector === false || typeof selector === "function" ) { + + // ( types [, fn] ) + fn = selector; + selector = undefined; + } + if ( fn === false ) { + fn = returnFalse; + } + return this.each( function() { + jQuery.event.remove( this, types, fn, selector ); + } ); + } +} ); + + +var + + /* eslint-disable max-len */ + + // See https://github.com/eslint/eslint/issues/3229 + rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([a-z][^\/\0>\x20\t\r\n\f]*)[^>]*)\/>/gi, + + /* eslint-enable */ + + // Support: IE <=10 - 11, Edge 12 - 13 + // In IE/Edge using regex groups here causes severe slowdowns. + // See https://connect.microsoft.com/IE/feedback/details/1736512/ + rnoInnerhtml = /\s*$/g; + +function manipulationTarget( elem, content ) { + if ( jQuery.nodeName( elem, "table" ) && + jQuery.nodeName( content.nodeType !== 11 ? content : content.firstChild, "tr" ) ) { + + return elem.getElementsByTagName( "tbody" )[ 0 ] || elem; + } + + return elem; +} + +// Replace/restore the type attribute of script elements for safe DOM manipulation +function disableScript( elem ) { + elem.type = ( elem.getAttribute( "type" ) !== null ) + "/" + elem.type; + return elem; +} +function restoreScript( elem ) { + var match = rscriptTypeMasked.exec( elem.type ); + + if ( match ) { + elem.type = match[ 1 ]; + } else { + elem.removeAttribute( "type" ); + } + + return elem; +} + +function cloneCopyEvent( src, dest ) { + var i, l, type, pdataOld, pdataCur, udataOld, udataCur, events; + + if ( dest.nodeType !== 1 ) { + return; + } + + // 1. Copy private data: events, handlers, etc. + if ( dataPriv.hasData( src ) ) { + pdataOld = dataPriv.access( src ); + pdataCur = dataPriv.set( dest, pdataOld ); + events = pdataOld.events; + + if ( events ) { + delete pdataCur.handle; + pdataCur.events = {}; + + for ( type in events ) { + for ( i = 0, l = events[ type ].length; i < l; i++ ) { + jQuery.event.add( dest, type, events[ type ][ i ] ); + } + } + } + } + + // 2. Copy user data + if ( dataUser.hasData( src ) ) { + udataOld = dataUser.access( src ); + udataCur = jQuery.extend( {}, udataOld ); + + dataUser.set( dest, udataCur ); + } +} + +// Fix IE bugs, see support tests +function fixInput( src, dest ) { + var nodeName = dest.nodeName.toLowerCase(); + + // Fails to persist the checked state of a cloned checkbox or radio button. + if ( nodeName === "input" && rcheckableType.test( src.type ) ) { + dest.checked = src.checked; + + // Fails to return the selected option to the default selected state when cloning options + } else if ( nodeName === "input" || nodeName === "textarea" ) { + dest.defaultValue = src.defaultValue; + } +} + +function domManip( collection, args, callback, ignored ) { + + // Flatten any nested arrays + args = concat.apply( [], args ); + + var fragment, first, scripts, hasScripts, node, doc, + i = 0, + l = collection.length, + iNoClone = l - 1, + value = args[ 0 ], + isFunction = jQuery.isFunction( value ); + + // We can't cloneNode fragments that contain checked, in WebKit + if ( isFunction || + ( l > 1 && typeof value === "string" && + !support.checkClone && rchecked.test( value ) ) ) { + return collection.each( function( index ) { + var self = collection.eq( index ); + if ( isFunction ) { + args[ 0 ] = value.call( this, index, self.html() ); + } + domManip( self, args, callback, ignored ); + } ); + } + + if ( l ) { + fragment = buildFragment( args, collection[ 0 ].ownerDocument, false, collection, ignored ); + first = fragment.firstChild; + + if ( fragment.childNodes.length === 1 ) { + fragment = first; + } + + // Require either new content or an interest in ignored elements to invoke the callback + if ( first || ignored ) { + scripts = jQuery.map( getAll( fragment, "script" ), disableScript ); + hasScripts = scripts.length; + + // Use the original fragment for the last item + // instead of the first because it can end up + // being emptied incorrectly in certain situations (#8070). + for ( ; i < l; i++ ) { + node = fragment; + + if ( i !== iNoClone ) { + node = jQuery.clone( node, true, true ); + + // Keep references to cloned scripts for later restoration + if ( hasScripts ) { + + // Support: Android <=4.0 only, PhantomJS 1 only + // push.apply(_, arraylike) throws on ancient WebKit + jQuery.merge( scripts, getAll( node, "script" ) ); + } + } + + callback.call( collection[ i ], node, i ); + } + + if ( hasScripts ) { + doc = scripts[ scripts.length - 1 ].ownerDocument; + + // Reenable scripts + jQuery.map( scripts, restoreScript ); + + // Evaluate executable scripts on first document insertion + for ( i = 0; i < hasScripts; i++ ) { + node = scripts[ i ]; + if ( rscriptType.test( node.type || "" ) && + !dataPriv.access( node, "globalEval" ) && + jQuery.contains( doc, node ) ) { + + if ( node.src ) { + + // Optional AJAX dependency, but won't run scripts if not present + if ( jQuery._evalUrl ) { + jQuery._evalUrl( node.src ); + } + } else { + DOMEval( node.textContent.replace( rcleanScript, "" ), doc ); + } + } + } + } + } + } + + return collection; +} + +function remove( elem, selector, keepData ) { + var node, + nodes = selector ? jQuery.filter( selector, elem ) : elem, + i = 0; + + for ( ; ( node = nodes[ i ] ) != null; i++ ) { + if ( !keepData && node.nodeType === 1 ) { + jQuery.cleanData( getAll( node ) ); + } + + if ( node.parentNode ) { + if ( keepData && jQuery.contains( node.ownerDocument, node ) ) { + setGlobalEval( getAll( node, "script" ) ); + } + node.parentNode.removeChild( node ); + } + } + + return elem; +} + +jQuery.extend( { + htmlPrefilter: function( html ) { + return html.replace( rxhtmlTag, "<$1>" ); + }, + + clone: function( elem, dataAndEvents, deepDataAndEvents ) { + var i, l, srcElements, destElements, + clone = elem.cloneNode( true ), + inPage = jQuery.contains( elem.ownerDocument, elem ); + + // Fix IE cloning issues + if ( !support.noCloneChecked && ( elem.nodeType === 1 || elem.nodeType === 11 ) && + !jQuery.isXMLDoc( elem ) ) { + + // We eschew Sizzle here for performance reasons: https://jsperf.com/getall-vs-sizzle/2 + destElements = getAll( clone ); + srcElements = getAll( elem ); + + for ( i = 0, l = srcElements.length; i < l; i++ ) { + fixInput( srcElements[ i ], destElements[ i ] ); + } + } + + // Copy the events from the original to the clone + if ( dataAndEvents ) { + if ( deepDataAndEvents ) { + srcElements = srcElements || getAll( elem ); + destElements = destElements || getAll( clone ); + + for ( i = 0, l = srcElements.length; i < l; i++ ) { + cloneCopyEvent( srcElements[ i ], destElements[ i ] ); + } + } else { + cloneCopyEvent( elem, clone ); + } + } + + // Preserve script evaluation history + destElements = getAll( clone, "script" ); + if ( destElements.length > 0 ) { + setGlobalEval( destElements, !inPage && getAll( elem, "script" ) ); + } + + // Return the cloned set + return clone; + }, + + cleanData: function( elems ) { + var data, elem, type, + special = jQuery.event.special, + i = 0; + + for ( ; ( elem = elems[ i ] ) !== undefined; i++ ) { + if ( acceptData( elem ) ) { + if ( ( data = elem[ dataPriv.expando ] ) ) { + if ( data.events ) { + for ( type in data.events ) { + if ( special[ type ] ) { + jQuery.event.remove( elem, type ); + + // This is a shortcut to avoid jQuery.event.remove's overhead + } else { + jQuery.removeEvent( elem, type, data.handle ); + } + } + } + + // Support: Chrome <=35 - 45+ + // Assign undefined instead of using delete, see Data#remove + elem[ dataPriv.expando ] = undefined; + } + if ( elem[ dataUser.expando ] ) { + + // Support: Chrome <=35 - 45+ + // Assign undefined instead of using delete, see Data#remove + elem[ dataUser.expando ] = undefined; + } + } + } + } +} ); + +jQuery.fn.extend( { + detach: function( selector ) { + return remove( this, selector, true ); + }, + + remove: function( selector ) { + return remove( this, selector ); + }, + + text: function( value ) { + return access( this, function( value ) { + return value === undefined ? + jQuery.text( this ) : + this.empty().each( function() { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + this.textContent = value; + } + } ); + }, null, value, arguments.length ); + }, + + append: function() { + return domManip( this, arguments, function( elem ) { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + var target = manipulationTarget( this, elem ); + target.appendChild( elem ); + } + } ); + }, + + prepend: function() { + return domManip( this, arguments, function( elem ) { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + var target = manipulationTarget( this, elem ); + target.insertBefore( elem, target.firstChild ); + } + } ); + }, + + before: function() { + return domManip( this, arguments, function( elem ) { + if ( this.parentNode ) { + this.parentNode.insertBefore( elem, this ); + } + } ); + }, + + after: function() { + return domManip( this, arguments, function( elem ) { + if ( this.parentNode ) { + this.parentNode.insertBefore( elem, this.nextSibling ); + } + } ); + }, + + empty: function() { + var elem, + i = 0; + + for ( ; ( elem = this[ i ] ) != null; i++ ) { + if ( elem.nodeType === 1 ) { + + // Prevent memory leaks + jQuery.cleanData( getAll( elem, false ) ); + + // Remove any remaining nodes + elem.textContent = ""; + } + } + + return this; + }, + + clone: function( dataAndEvents, deepDataAndEvents ) { + dataAndEvents = dataAndEvents == null ? false : dataAndEvents; + deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents; + + return this.map( function() { + return jQuery.clone( this, dataAndEvents, deepDataAndEvents ); + } ); + }, + + html: function( value ) { + return access( this, function( value ) { + var elem = this[ 0 ] || {}, + i = 0, + l = this.length; + + if ( value === undefined && elem.nodeType === 1 ) { + return elem.innerHTML; + } + + // See if we can take a shortcut and just use innerHTML + if ( typeof value === "string" && !rnoInnerhtml.test( value ) && + !wrapMap[ ( rtagName.exec( value ) || [ "", "" ] )[ 1 ].toLowerCase() ] ) { + + value = jQuery.htmlPrefilter( value ); + + try { + for ( ; i < l; i++ ) { + elem = this[ i ] || {}; + + // Remove element nodes and prevent memory leaks + if ( elem.nodeType === 1 ) { + jQuery.cleanData( getAll( elem, false ) ); + elem.innerHTML = value; + } + } + + elem = 0; + + // If using innerHTML throws an exception, use the fallback method + } catch ( e ) {} + } + + if ( elem ) { + this.empty().append( value ); + } + }, null, value, arguments.length ); + }, + + replaceWith: function() { + var ignored = []; + + // Make the changes, replacing each non-ignored context element with the new content + return domManip( this, arguments, function( elem ) { + var parent = this.parentNode; + + if ( jQuery.inArray( this, ignored ) < 0 ) { + jQuery.cleanData( getAll( this ) ); + if ( parent ) { + parent.replaceChild( elem, this ); + } + } + + // Force callback invocation + }, ignored ); + } +} ); + +jQuery.each( { + appendTo: "append", + prependTo: "prepend", + insertBefore: "before", + insertAfter: "after", + replaceAll: "replaceWith" +}, function( name, original ) { + jQuery.fn[ name ] = function( selector ) { + var elems, + ret = [], + insert = jQuery( selector ), + last = insert.length - 1, + i = 0; + + for ( ; i <= last; i++ ) { + elems = i === last ? this : this.clone( true ); + jQuery( insert[ i ] )[ original ]( elems ); + + // Support: Android <=4.0 only, PhantomJS 1 only + // .get() because push.apply(_, arraylike) throws on ancient WebKit + push.apply( ret, elems.get() ); + } + + return this.pushStack( ret ); + }; +} ); +var rmargin = ( /^margin/ ); + +var rnumnonpx = new RegExp( "^(" + pnum + ")(?!px)[a-z%]+$", "i" ); + +var getStyles = function( elem ) { + + // Support: IE <=11 only, Firefox <=30 (#15098, #14150) + // IE throws on elements created in popups + // FF meanwhile throws on frame elements through "defaultView.getComputedStyle" + var view = elem.ownerDocument.defaultView; + + if ( !view || !view.opener ) { + view = window; + } + + return view.getComputedStyle( elem ); + }; + + + +( function() { + + // Executing both pixelPosition & boxSizingReliable tests require only one layout + // so they're executed at the same time to save the second computation. + function computeStyleTests() { + + // This is a singleton, we need to execute it only once + if ( !div ) { + return; + } + + div.style.cssText = + "box-sizing:border-box;" + + "position:relative;display:block;" + + "margin:auto;border:1px;padding:1px;" + + "top:1%;width:50%"; + div.innerHTML = ""; + documentElement.appendChild( container ); + + var divStyle = window.getComputedStyle( div ); + pixelPositionVal = divStyle.top !== "1%"; + + // Support: Android 4.0 - 4.3 only, Firefox <=3 - 44 + reliableMarginLeftVal = divStyle.marginLeft === "2px"; + boxSizingReliableVal = divStyle.width === "4px"; + + // Support: Android 4.0 - 4.3 only + // Some styles come back with percentage values, even though they shouldn't + div.style.marginRight = "50%"; + pixelMarginRightVal = divStyle.marginRight === "4px"; + + documentElement.removeChild( container ); + + // Nullify the div so it wouldn't be stored in the memory and + // it will also be a sign that checks already performed + div = null; + } + + var pixelPositionVal, boxSizingReliableVal, pixelMarginRightVal, reliableMarginLeftVal, + container = document.createElement( "div" ), + div = document.createElement( "div" ); + + // Finish early in limited (non-browser) environments + if ( !div.style ) { + return; + } + + // Support: IE <=9 - 11 only + // Style of cloned element affects source element cloned (#8908) + div.style.backgroundClip = "content-box"; + div.cloneNode( true ).style.backgroundClip = ""; + support.clearCloneStyle = div.style.backgroundClip === "content-box"; + + container.style.cssText = "border:0;width:8px;height:0;top:0;left:-9999px;" + + "padding:0;margin-top:1px;position:absolute"; + container.appendChild( div ); + + jQuery.extend( support, { + pixelPosition: function() { + computeStyleTests(); + return pixelPositionVal; + }, + boxSizingReliable: function() { + computeStyleTests(); + return boxSizingReliableVal; + }, + pixelMarginRight: function() { + computeStyleTests(); + return pixelMarginRightVal; + }, + reliableMarginLeft: function() { + computeStyleTests(); + return reliableMarginLeftVal; + } + } ); +} )(); + + +function curCSS( elem, name, computed ) { + var width, minWidth, maxWidth, ret, + style = elem.style; + + computed = computed || getStyles( elem ); + + // Support: IE <=9 only + // getPropertyValue is only needed for .css('filter') (#12537) + if ( computed ) { + ret = computed.getPropertyValue( name ) || computed[ name ]; + + if ( ret === "" && !jQuery.contains( elem.ownerDocument, elem ) ) { + ret = jQuery.style( elem, name ); + } + + // A tribute to the "awesome hack by Dean Edwards" + // Android Browser returns percentage for some values, + // but width seems to be reliably pixels. + // This is against the CSSOM draft spec: + // https://drafts.csswg.org/cssom/#resolved-values + if ( !support.pixelMarginRight() && rnumnonpx.test( ret ) && rmargin.test( name ) ) { + + // Remember the original values + width = style.width; + minWidth = style.minWidth; + maxWidth = style.maxWidth; + + // Put in the new values to get a computed value out + style.minWidth = style.maxWidth = style.width = ret; + ret = computed.width; + + // Revert the changed values + style.width = width; + style.minWidth = minWidth; + style.maxWidth = maxWidth; + } + } + + return ret !== undefined ? + + // Support: IE <=9 - 11 only + // IE returns zIndex value as an integer. + ret + "" : + ret; +} + + +function addGetHookIf( conditionFn, hookFn ) { + + // Define the hook, we'll check on the first run if it's really needed. + return { + get: function() { + if ( conditionFn() ) { + + // Hook not needed (or it's not possible to use it due + // to missing dependency), remove it. + delete this.get; + return; + } + + // Hook needed; redefine it so that the support test is not executed again. + return ( this.get = hookFn ).apply( this, arguments ); + } + }; +} + + +var + + // Swappable if display is none or starts with table + // except "table", "table-cell", or "table-caption" + // See here for display values: https://developer.mozilla.org/en-US/docs/CSS/display + rdisplayswap = /^(none|table(?!-c[ea]).+)/, + cssShow = { position: "absolute", visibility: "hidden", display: "block" }, + cssNormalTransform = { + letterSpacing: "0", + fontWeight: "400" + }, + + cssPrefixes = [ "Webkit", "Moz", "ms" ], + emptyStyle = document.createElement( "div" ).style; + +// Return a css property mapped to a potentially vendor prefixed property +function vendorPropName( name ) { + + // Shortcut for names that are not vendor prefixed + if ( name in emptyStyle ) { + return name; + } + + // Check for vendor prefixed names + var capName = name[ 0 ].toUpperCase() + name.slice( 1 ), + i = cssPrefixes.length; + + while ( i-- ) { + name = cssPrefixes[ i ] + capName; + if ( name in emptyStyle ) { + return name; + } + } +} + +function setPositiveNumber( elem, value, subtract ) { + + // Any relative (+/-) values have already been + // normalized at this point + var matches = rcssNum.exec( value ); + return matches ? + + // Guard against undefined "subtract", e.g., when used as in cssHooks + Math.max( 0, matches[ 2 ] - ( subtract || 0 ) ) + ( matches[ 3 ] || "px" ) : + value; +} + +function augmentWidthOrHeight( elem, name, extra, isBorderBox, styles ) { + var i, + val = 0; + + // If we already have the right measurement, avoid augmentation + if ( extra === ( isBorderBox ? "border" : "content" ) ) { + i = 4; + + // Otherwise initialize for horizontal or vertical properties + } else { + i = name === "width" ? 1 : 0; + } + + for ( ; i < 4; i += 2 ) { + + // Both box models exclude margin, so add it if we want it + if ( extra === "margin" ) { + val += jQuery.css( elem, extra + cssExpand[ i ], true, styles ); + } + + if ( isBorderBox ) { + + // border-box includes padding, so remove it if we want content + if ( extra === "content" ) { + val -= jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); + } + + // At this point, extra isn't border nor margin, so remove border + if ( extra !== "margin" ) { + val -= jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); + } + } else { + + // At this point, extra isn't content, so add padding + val += jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); + + // At this point, extra isn't content nor padding, so add border + if ( extra !== "padding" ) { + val += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); + } + } + } + + return val; +} + +function getWidthOrHeight( elem, name, extra ) { + + // Start with offset property, which is equivalent to the border-box value + var val, + valueIsBorderBox = true, + styles = getStyles( elem ), + isBorderBox = jQuery.css( elem, "boxSizing", false, styles ) === "border-box"; + + // Support: IE <=11 only + // Running getBoundingClientRect on a disconnected node + // in IE throws an error. + if ( elem.getClientRects().length ) { + val = elem.getBoundingClientRect()[ name ]; + } + + // Some non-html elements return undefined for offsetWidth, so check for null/undefined + // svg - https://bugzilla.mozilla.org/show_bug.cgi?id=649285 + // MathML - https://bugzilla.mozilla.org/show_bug.cgi?id=491668 + if ( val <= 0 || val == null ) { + + // Fall back to computed then uncomputed css if necessary + val = curCSS( elem, name, styles ); + if ( val < 0 || val == null ) { + val = elem.style[ name ]; + } + + // Computed unit is not pixels. Stop here and return. + if ( rnumnonpx.test( val ) ) { + return val; + } + + // Check for style in case a browser which returns unreliable values + // for getComputedStyle silently falls back to the reliable elem.style + valueIsBorderBox = isBorderBox && + ( support.boxSizingReliable() || val === elem.style[ name ] ); + + // Normalize "", auto, and prepare for extra + val = parseFloat( val ) || 0; + } + + // Use the active box-sizing model to add/subtract irrelevant styles + return ( val + + augmentWidthOrHeight( + elem, + name, + extra || ( isBorderBox ? "border" : "content" ), + valueIsBorderBox, + styles + ) + ) + "px"; +} + +jQuery.extend( { + + // Add in style property hooks for overriding the default + // behavior of getting and setting a style property + cssHooks: { + opacity: { + get: function( elem, computed ) { + if ( computed ) { + + // We should always get a number back from opacity + var ret = curCSS( elem, "opacity" ); + return ret === "" ? "1" : ret; + } + } + } + }, + + // Don't automatically add "px" to these possibly-unitless properties + cssNumber: { + "animationIterationCount": true, + "columnCount": true, + "fillOpacity": true, + "flexGrow": true, + "flexShrink": true, + "fontWeight": true, + "lineHeight": true, + "opacity": true, + "order": true, + "orphans": true, + "widows": true, + "zIndex": true, + "zoom": true + }, + + // Add in properties whose names you wish to fix before + // setting or getting the value + cssProps: { + "float": "cssFloat" + }, + + // Get and set the style property on a DOM Node + style: function( elem, name, value, extra ) { + + // Don't set styles on text and comment nodes + if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) { + return; + } + + // Make sure that we're working with the right name + var ret, type, hooks, + origName = jQuery.camelCase( name ), + style = elem.style; + + name = jQuery.cssProps[ origName ] || + ( jQuery.cssProps[ origName ] = vendorPropName( origName ) || origName ); + + // Gets hook for the prefixed version, then unprefixed version + hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; + + // Check if we're setting a value + if ( value !== undefined ) { + type = typeof value; + + // Convert "+=" or "-=" to relative numbers (#7345) + if ( type === "string" && ( ret = rcssNum.exec( value ) ) && ret[ 1 ] ) { + value = adjustCSS( elem, name, ret ); + + // Fixes bug #9237 + type = "number"; + } + + // Make sure that null and NaN values aren't set (#7116) + if ( value == null || value !== value ) { + return; + } + + // If a number was passed in, add the unit (except for certain CSS properties) + if ( type === "number" ) { + value += ret && ret[ 3 ] || ( jQuery.cssNumber[ origName ] ? "" : "px" ); + } + + // background-* props affect original clone's values + if ( !support.clearCloneStyle && value === "" && name.indexOf( "background" ) === 0 ) { + style[ name ] = "inherit"; + } + + // If a hook was provided, use that value, otherwise just set the specified value + if ( !hooks || !( "set" in hooks ) || + ( value = hooks.set( elem, value, extra ) ) !== undefined ) { + + style[ name ] = value; + } + + } else { + + // If a hook was provided get the non-computed value from there + if ( hooks && "get" in hooks && + ( ret = hooks.get( elem, false, extra ) ) !== undefined ) { + + return ret; + } + + // Otherwise just get the value from the style object + return style[ name ]; + } + }, + + css: function( elem, name, extra, styles ) { + var val, num, hooks, + origName = jQuery.camelCase( name ); + + // Make sure that we're working with the right name + name = jQuery.cssProps[ origName ] || + ( jQuery.cssProps[ origName ] = vendorPropName( origName ) || origName ); + + // Try prefixed name followed by the unprefixed name + hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; + + // If a hook was provided get the computed value from there + if ( hooks && "get" in hooks ) { + val = hooks.get( elem, true, extra ); + } + + // Otherwise, if a way to get the computed value exists, use that + if ( val === undefined ) { + val = curCSS( elem, name, styles ); + } + + // Convert "normal" to computed value + if ( val === "normal" && name in cssNormalTransform ) { + val = cssNormalTransform[ name ]; + } + + // Make numeric if forced or a qualifier was provided and val looks numeric + if ( extra === "" || extra ) { + num = parseFloat( val ); + return extra === true || isFinite( num ) ? num || 0 : val; + } + return val; + } +} ); + +jQuery.each( [ "height", "width" ], function( i, name ) { + jQuery.cssHooks[ name ] = { + get: function( elem, computed, extra ) { + if ( computed ) { + + // Certain elements can have dimension info if we invisibly show them + // but it must have a current display style that would benefit + return rdisplayswap.test( jQuery.css( elem, "display" ) ) && + + // Support: Safari 8+ + // Table columns in Safari have non-zero offsetWidth & zero + // getBoundingClientRect().width unless display is changed. + // Support: IE <=11 only + // Running getBoundingClientRect on a disconnected node + // in IE throws an error. + ( !elem.getClientRects().length || !elem.getBoundingClientRect().width ) ? + swap( elem, cssShow, function() { + return getWidthOrHeight( elem, name, extra ); + } ) : + getWidthOrHeight( elem, name, extra ); + } + }, + + set: function( elem, value, extra ) { + var matches, + styles = extra && getStyles( elem ), + subtract = extra && augmentWidthOrHeight( + elem, + name, + extra, + jQuery.css( elem, "boxSizing", false, styles ) === "border-box", + styles + ); + + // Convert to pixels if value adjustment is needed + if ( subtract && ( matches = rcssNum.exec( value ) ) && + ( matches[ 3 ] || "px" ) !== "px" ) { + + elem.style[ name ] = value; + value = jQuery.css( elem, name ); + } + + return setPositiveNumber( elem, value, subtract ); + } + }; +} ); + +jQuery.cssHooks.marginLeft = addGetHookIf( support.reliableMarginLeft, + function( elem, computed ) { + if ( computed ) { + return ( parseFloat( curCSS( elem, "marginLeft" ) ) || + elem.getBoundingClientRect().left - + swap( elem, { marginLeft: 0 }, function() { + return elem.getBoundingClientRect().left; + } ) + ) + "px"; + } + } +); + +// These hooks are used by animate to expand properties +jQuery.each( { + margin: "", + padding: "", + border: "Width" +}, function( prefix, suffix ) { + jQuery.cssHooks[ prefix + suffix ] = { + expand: function( value ) { + var i = 0, + expanded = {}, + + // Assumes a single number if not a string + parts = typeof value === "string" ? value.split( " " ) : [ value ]; + + for ( ; i < 4; i++ ) { + expanded[ prefix + cssExpand[ i ] + suffix ] = + parts[ i ] || parts[ i - 2 ] || parts[ 0 ]; + } + + return expanded; + } + }; + + if ( !rmargin.test( prefix ) ) { + jQuery.cssHooks[ prefix + suffix ].set = setPositiveNumber; + } +} ); + +jQuery.fn.extend( { + css: function( name, value ) { + return access( this, function( elem, name, value ) { + var styles, len, + map = {}, + i = 0; + + if ( jQuery.isArray( name ) ) { + styles = getStyles( elem ); + len = name.length; + + for ( ; i < len; i++ ) { + map[ name[ i ] ] = jQuery.css( elem, name[ i ], false, styles ); + } + + return map; + } + + return value !== undefined ? + jQuery.style( elem, name, value ) : + jQuery.css( elem, name ); + }, name, value, arguments.length > 1 ); + } +} ); + + +function Tween( elem, options, prop, end, easing ) { + return new Tween.prototype.init( elem, options, prop, end, easing ); +} +jQuery.Tween = Tween; + +Tween.prototype = { + constructor: Tween, + init: function( elem, options, prop, end, easing, unit ) { + this.elem = elem; + this.prop = prop; + this.easing = easing || jQuery.easing._default; + this.options = options; + this.start = this.now = this.cur(); + this.end = end; + this.unit = unit || ( jQuery.cssNumber[ prop ] ? "" : "px" ); + }, + cur: function() { + var hooks = Tween.propHooks[ this.prop ]; + + return hooks && hooks.get ? + hooks.get( this ) : + Tween.propHooks._default.get( this ); + }, + run: function( percent ) { + var eased, + hooks = Tween.propHooks[ this.prop ]; + + if ( this.options.duration ) { + this.pos = eased = jQuery.easing[ this.easing ]( + percent, this.options.duration * percent, 0, 1, this.options.duration + ); + } else { + this.pos = eased = percent; + } + this.now = ( this.end - this.start ) * eased + this.start; + + if ( this.options.step ) { + this.options.step.call( this.elem, this.now, this ); + } + + if ( hooks && hooks.set ) { + hooks.set( this ); + } else { + Tween.propHooks._default.set( this ); + } + return this; + } +}; + +Tween.prototype.init.prototype = Tween.prototype; + +Tween.propHooks = { + _default: { + get: function( tween ) { + var result; + + // Use a property on the element directly when it is not a DOM element, + // or when there is no matching style property that exists. + if ( tween.elem.nodeType !== 1 || + tween.elem[ tween.prop ] != null && tween.elem.style[ tween.prop ] == null ) { + return tween.elem[ tween.prop ]; + } + + // Passing an empty string as a 3rd parameter to .css will automatically + // attempt a parseFloat and fallback to a string if the parse fails. + // Simple values such as "10px" are parsed to Float; + // complex values such as "rotate(1rad)" are returned as-is. + result = jQuery.css( tween.elem, tween.prop, "" ); + + // Empty strings, null, undefined and "auto" are converted to 0. + return !result || result === "auto" ? 0 : result; + }, + set: function( tween ) { + + // Use step hook for back compat. + // Use cssHook if its there. + // Use .style if available and use plain properties where available. + if ( jQuery.fx.step[ tween.prop ] ) { + jQuery.fx.step[ tween.prop ]( tween ); + } else if ( tween.elem.nodeType === 1 && + ( tween.elem.style[ jQuery.cssProps[ tween.prop ] ] != null || + jQuery.cssHooks[ tween.prop ] ) ) { + jQuery.style( tween.elem, tween.prop, tween.now + tween.unit ); + } else { + tween.elem[ tween.prop ] = tween.now; + } + } + } +}; + +// Support: IE <=9 only +// Panic based approach to setting things on disconnected nodes +Tween.propHooks.scrollTop = Tween.propHooks.scrollLeft = { + set: function( tween ) { + if ( tween.elem.nodeType && tween.elem.parentNode ) { + tween.elem[ tween.prop ] = tween.now; + } + } +}; + +jQuery.easing = { + linear: function( p ) { + return p; + }, + swing: function( p ) { + return 0.5 - Math.cos( p * Math.PI ) / 2; + }, + _default: "swing" +}; + +jQuery.fx = Tween.prototype.init; + +// Back compat <1.8 extension point +jQuery.fx.step = {}; + + + + +var + fxNow, timerId, + rfxtypes = /^(?:toggle|show|hide)$/, + rrun = /queueHooks$/; + +function raf() { + if ( timerId ) { + window.requestAnimationFrame( raf ); + jQuery.fx.tick(); + } +} + +// Animations created synchronously will run synchronously +function createFxNow() { + window.setTimeout( function() { + fxNow = undefined; + } ); + return ( fxNow = jQuery.now() ); +} + +// Generate parameters to create a standard animation +function genFx( type, includeWidth ) { + var which, + i = 0, + attrs = { height: type }; + + // If we include width, step value is 1 to do all cssExpand values, + // otherwise step value is 2 to skip over Left and Right + includeWidth = includeWidth ? 1 : 0; + for ( ; i < 4; i += 2 - includeWidth ) { + which = cssExpand[ i ]; + attrs[ "margin" + which ] = attrs[ "padding" + which ] = type; + } + + if ( includeWidth ) { + attrs.opacity = attrs.width = type; + } + + return attrs; +} + +function createTween( value, prop, animation ) { + var tween, + collection = ( Animation.tweeners[ prop ] || [] ).concat( Animation.tweeners[ "*" ] ), + index = 0, + length = collection.length; + for ( ; index < length; index++ ) { + if ( ( tween = collection[ index ].call( animation, prop, value ) ) ) { + + // We're done with this property + return tween; + } + } +} + +function defaultPrefilter( elem, props, opts ) { + var prop, value, toggle, hooks, oldfire, propTween, restoreDisplay, display, + isBox = "width" in props || "height" in props, + anim = this, + orig = {}, + style = elem.style, + hidden = elem.nodeType && isHiddenWithinTree( elem ), + dataShow = dataPriv.get( elem, "fxshow" ); + + // Queue-skipping animations hijack the fx hooks + if ( !opts.queue ) { + hooks = jQuery._queueHooks( elem, "fx" ); + if ( hooks.unqueued == null ) { + hooks.unqueued = 0; + oldfire = hooks.empty.fire; + hooks.empty.fire = function() { + if ( !hooks.unqueued ) { + oldfire(); + } + }; + } + hooks.unqueued++; + + anim.always( function() { + + // Ensure the complete handler is called before this completes + anim.always( function() { + hooks.unqueued--; + if ( !jQuery.queue( elem, "fx" ).length ) { + hooks.empty.fire(); + } + } ); + } ); + } + + // Detect show/hide animations + for ( prop in props ) { + value = props[ prop ]; + if ( rfxtypes.test( value ) ) { + delete props[ prop ]; + toggle = toggle || value === "toggle"; + if ( value === ( hidden ? "hide" : "show" ) ) { + + // Pretend to be hidden if this is a "show" and + // there is still data from a stopped show/hide + if ( value === "show" && dataShow && dataShow[ prop ] !== undefined ) { + hidden = true; + + // Ignore all other no-op show/hide data + } else { + continue; + } + } + orig[ prop ] = dataShow && dataShow[ prop ] || jQuery.style( elem, prop ); + } + } + + // Bail out if this is a no-op like .hide().hide() + propTween = !jQuery.isEmptyObject( props ); + if ( !propTween && jQuery.isEmptyObject( orig ) ) { + return; + } + + // Restrict "overflow" and "display" styles during box animations + if ( isBox && elem.nodeType === 1 ) { + + // Support: IE <=9 - 11, Edge 12 - 13 + // Record all 3 overflow attributes because IE does not infer the shorthand + // from identically-valued overflowX and overflowY + opts.overflow = [ style.overflow, style.overflowX, style.overflowY ]; + + // Identify a display type, preferring old show/hide data over the CSS cascade + restoreDisplay = dataShow && dataShow.display; + if ( restoreDisplay == null ) { + restoreDisplay = dataPriv.get( elem, "display" ); + } + display = jQuery.css( elem, "display" ); + if ( display === "none" ) { + if ( restoreDisplay ) { + display = restoreDisplay; + } else { + + // Get nonempty value(s) by temporarily forcing visibility + showHide( [ elem ], true ); + restoreDisplay = elem.style.display || restoreDisplay; + display = jQuery.css( elem, "display" ); + showHide( [ elem ] ); + } + } + + // Animate inline elements as inline-block + if ( display === "inline" || display === "inline-block" && restoreDisplay != null ) { + if ( jQuery.css( elem, "float" ) === "none" ) { + + // Restore the original display value at the end of pure show/hide animations + if ( !propTween ) { + anim.done( function() { + style.display = restoreDisplay; + } ); + if ( restoreDisplay == null ) { + display = style.display; + restoreDisplay = display === "none" ? "" : display; + } + } + style.display = "inline-block"; + } + } + } + + if ( opts.overflow ) { + style.overflow = "hidden"; + anim.always( function() { + style.overflow = opts.overflow[ 0 ]; + style.overflowX = opts.overflow[ 1 ]; + style.overflowY = opts.overflow[ 2 ]; + } ); + } + + // Implement show/hide animations + propTween = false; + for ( prop in orig ) { + + // General show/hide setup for this element animation + if ( !propTween ) { + if ( dataShow ) { + if ( "hidden" in dataShow ) { + hidden = dataShow.hidden; + } + } else { + dataShow = dataPriv.access( elem, "fxshow", { display: restoreDisplay } ); + } + + // Store hidden/visible for toggle so `.stop().toggle()` "reverses" + if ( toggle ) { + dataShow.hidden = !hidden; + } + + // Show elements before animating them + if ( hidden ) { + showHide( [ elem ], true ); + } + + /* eslint-disable no-loop-func */ + + anim.done( function() { + + /* eslint-enable no-loop-func */ + + // The final step of a "hide" animation is actually hiding the element + if ( !hidden ) { + showHide( [ elem ] ); + } + dataPriv.remove( elem, "fxshow" ); + for ( prop in orig ) { + jQuery.style( elem, prop, orig[ prop ] ); + } + } ); + } + + // Per-property setup + propTween = createTween( hidden ? dataShow[ prop ] : 0, prop, anim ); + if ( !( prop in dataShow ) ) { + dataShow[ prop ] = propTween.start; + if ( hidden ) { + propTween.end = propTween.start; + propTween.start = 0; + } + } + } +} + +function propFilter( props, specialEasing ) { + var index, name, easing, value, hooks; + + // camelCase, specialEasing and expand cssHook pass + for ( index in props ) { + name = jQuery.camelCase( index ); + easing = specialEasing[ name ]; + value = props[ index ]; + if ( jQuery.isArray( value ) ) { + easing = value[ 1 ]; + value = props[ index ] = value[ 0 ]; + } + + if ( index !== name ) { + props[ name ] = value; + delete props[ index ]; + } + + hooks = jQuery.cssHooks[ name ]; + if ( hooks && "expand" in hooks ) { + value = hooks.expand( value ); + delete props[ name ]; + + // Not quite $.extend, this won't overwrite existing keys. + // Reusing 'index' because we have the correct "name" + for ( index in value ) { + if ( !( index in props ) ) { + props[ index ] = value[ index ]; + specialEasing[ index ] = easing; + } + } + } else { + specialEasing[ name ] = easing; + } + } +} + +function Animation( elem, properties, options ) { + var result, + stopped, + index = 0, + length = Animation.prefilters.length, + deferred = jQuery.Deferred().always( function() { + + // Don't match elem in the :animated selector + delete tick.elem; + } ), + tick = function() { + if ( stopped ) { + return false; + } + var currentTime = fxNow || createFxNow(), + remaining = Math.max( 0, animation.startTime + animation.duration - currentTime ), + + // Support: Android 2.3 only + // Archaic crash bug won't allow us to use `1 - ( 0.5 || 0 )` (#12497) + temp = remaining / animation.duration || 0, + percent = 1 - temp, + index = 0, + length = animation.tweens.length; + + for ( ; index < length; index++ ) { + animation.tweens[ index ].run( percent ); + } + + deferred.notifyWith( elem, [ animation, percent, remaining ] ); + + if ( percent < 1 && length ) { + return remaining; + } else { + deferred.resolveWith( elem, [ animation ] ); + return false; + } + }, + animation = deferred.promise( { + elem: elem, + props: jQuery.extend( {}, properties ), + opts: jQuery.extend( true, { + specialEasing: {}, + easing: jQuery.easing._default + }, options ), + originalProperties: properties, + originalOptions: options, + startTime: fxNow || createFxNow(), + duration: options.duration, + tweens: [], + createTween: function( prop, end ) { + var tween = jQuery.Tween( elem, animation.opts, prop, end, + animation.opts.specialEasing[ prop ] || animation.opts.easing ); + animation.tweens.push( tween ); + return tween; + }, + stop: function( gotoEnd ) { + var index = 0, + + // If we are going to the end, we want to run all the tweens + // otherwise we skip this part + length = gotoEnd ? animation.tweens.length : 0; + if ( stopped ) { + return this; + } + stopped = true; + for ( ; index < length; index++ ) { + animation.tweens[ index ].run( 1 ); + } + + // Resolve when we played the last frame; otherwise, reject + if ( gotoEnd ) { + deferred.notifyWith( elem, [ animation, 1, 0 ] ); + deferred.resolveWith( elem, [ animation, gotoEnd ] ); + } else { + deferred.rejectWith( elem, [ animation, gotoEnd ] ); + } + return this; + } + } ), + props = animation.props; + + propFilter( props, animation.opts.specialEasing ); + + for ( ; index < length; index++ ) { + result = Animation.prefilters[ index ].call( animation, elem, props, animation.opts ); + if ( result ) { + if ( jQuery.isFunction( result.stop ) ) { + jQuery._queueHooks( animation.elem, animation.opts.queue ).stop = + jQuery.proxy( result.stop, result ); + } + return result; + } + } + + jQuery.map( props, createTween, animation ); + + if ( jQuery.isFunction( animation.opts.start ) ) { + animation.opts.start.call( elem, animation ); + } + + jQuery.fx.timer( + jQuery.extend( tick, { + elem: elem, + anim: animation, + queue: animation.opts.queue + } ) + ); + + // attach callbacks from options + return animation.progress( animation.opts.progress ) + .done( animation.opts.done, animation.opts.complete ) + .fail( animation.opts.fail ) + .always( animation.opts.always ); +} + +jQuery.Animation = jQuery.extend( Animation, { + + tweeners: { + "*": [ function( prop, value ) { + var tween = this.createTween( prop, value ); + adjustCSS( tween.elem, prop, rcssNum.exec( value ), tween ); + return tween; + } ] + }, + + tweener: function( props, callback ) { + if ( jQuery.isFunction( props ) ) { + callback = props; + props = [ "*" ]; + } else { + props = props.match( rnothtmlwhite ); + } + + var prop, + index = 0, + length = props.length; + + for ( ; index < length; index++ ) { + prop = props[ index ]; + Animation.tweeners[ prop ] = Animation.tweeners[ prop ] || []; + Animation.tweeners[ prop ].unshift( callback ); + } + }, + + prefilters: [ defaultPrefilter ], + + prefilter: function( callback, prepend ) { + if ( prepend ) { + Animation.prefilters.unshift( callback ); + } else { + Animation.prefilters.push( callback ); + } + } +} ); + +jQuery.speed = function( speed, easing, fn ) { + var opt = speed && typeof speed === "object" ? jQuery.extend( {}, speed ) : { + complete: fn || !fn && easing || + jQuery.isFunction( speed ) && speed, + duration: speed, + easing: fn && easing || easing && !jQuery.isFunction( easing ) && easing + }; + + // Go to the end state if fx are off or if document is hidden + if ( jQuery.fx.off || document.hidden ) { + opt.duration = 0; + + } else { + if ( typeof opt.duration !== "number" ) { + if ( opt.duration in jQuery.fx.speeds ) { + opt.duration = jQuery.fx.speeds[ opt.duration ]; + + } else { + opt.duration = jQuery.fx.speeds._default; + } + } + } + + // Normalize opt.queue - true/undefined/null -> "fx" + if ( opt.queue == null || opt.queue === true ) { + opt.queue = "fx"; + } + + // Queueing + opt.old = opt.complete; + + opt.complete = function() { + if ( jQuery.isFunction( opt.old ) ) { + opt.old.call( this ); + } + + if ( opt.queue ) { + jQuery.dequeue( this, opt.queue ); + } + }; + + return opt; +}; + +jQuery.fn.extend( { + fadeTo: function( speed, to, easing, callback ) { + + // Show any hidden elements after setting opacity to 0 + return this.filter( isHiddenWithinTree ).css( "opacity", 0 ).show() + + // Animate to the value specified + .end().animate( { opacity: to }, speed, easing, callback ); + }, + animate: function( prop, speed, easing, callback ) { + var empty = jQuery.isEmptyObject( prop ), + optall = jQuery.speed( speed, easing, callback ), + doAnimation = function() { + + // Operate on a copy of prop so per-property easing won't be lost + var anim = Animation( this, jQuery.extend( {}, prop ), optall ); + + // Empty animations, or finishing resolves immediately + if ( empty || dataPriv.get( this, "finish" ) ) { + anim.stop( true ); + } + }; + doAnimation.finish = doAnimation; + + return empty || optall.queue === false ? + this.each( doAnimation ) : + this.queue( optall.queue, doAnimation ); + }, + stop: function( type, clearQueue, gotoEnd ) { + var stopQueue = function( hooks ) { + var stop = hooks.stop; + delete hooks.stop; + stop( gotoEnd ); + }; + + if ( typeof type !== "string" ) { + gotoEnd = clearQueue; + clearQueue = type; + type = undefined; + } + if ( clearQueue && type !== false ) { + this.queue( type || "fx", [] ); + } + + return this.each( function() { + var dequeue = true, + index = type != null && type + "queueHooks", + timers = jQuery.timers, + data = dataPriv.get( this ); + + if ( index ) { + if ( data[ index ] && data[ index ].stop ) { + stopQueue( data[ index ] ); + } + } else { + for ( index in data ) { + if ( data[ index ] && data[ index ].stop && rrun.test( index ) ) { + stopQueue( data[ index ] ); + } + } + } + + for ( index = timers.length; index--; ) { + if ( timers[ index ].elem === this && + ( type == null || timers[ index ].queue === type ) ) { + + timers[ index ].anim.stop( gotoEnd ); + dequeue = false; + timers.splice( index, 1 ); + } + } + + // Start the next in the queue if the last step wasn't forced. + // Timers currently will call their complete callbacks, which + // will dequeue but only if they were gotoEnd. + if ( dequeue || !gotoEnd ) { + jQuery.dequeue( this, type ); + } + } ); + }, + finish: function( type ) { + if ( type !== false ) { + type = type || "fx"; + } + return this.each( function() { + var index, + data = dataPriv.get( this ), + queue = data[ type + "queue" ], + hooks = data[ type + "queueHooks" ], + timers = jQuery.timers, + length = queue ? queue.length : 0; + + // Enable finishing flag on private data + data.finish = true; + + // Empty the queue first + jQuery.queue( this, type, [] ); + + if ( hooks && hooks.stop ) { + hooks.stop.call( this, true ); + } + + // Look for any active animations, and finish them + for ( index = timers.length; index--; ) { + if ( timers[ index ].elem === this && timers[ index ].queue === type ) { + timers[ index ].anim.stop( true ); + timers.splice( index, 1 ); + } + } + + // Look for any animations in the old queue and finish them + for ( index = 0; index < length; index++ ) { + if ( queue[ index ] && queue[ index ].finish ) { + queue[ index ].finish.call( this ); + } + } + + // Turn off finishing flag + delete data.finish; + } ); + } +} ); + +jQuery.each( [ "toggle", "show", "hide" ], function( i, name ) { + var cssFn = jQuery.fn[ name ]; + jQuery.fn[ name ] = function( speed, easing, callback ) { + return speed == null || typeof speed === "boolean" ? + cssFn.apply( this, arguments ) : + this.animate( genFx( name, true ), speed, easing, callback ); + }; +} ); + +// Generate shortcuts for custom animations +jQuery.each( { + slideDown: genFx( "show" ), + slideUp: genFx( "hide" ), + slideToggle: genFx( "toggle" ), + fadeIn: { opacity: "show" }, + fadeOut: { opacity: "hide" }, + fadeToggle: { opacity: "toggle" } +}, function( name, props ) { + jQuery.fn[ name ] = function( speed, easing, callback ) { + return this.animate( props, speed, easing, callback ); + }; +} ); + +jQuery.timers = []; +jQuery.fx.tick = function() { + var timer, + i = 0, + timers = jQuery.timers; + + fxNow = jQuery.now(); + + for ( ; i < timers.length; i++ ) { + timer = timers[ i ]; + + // Checks the timer has not already been removed + if ( !timer() && timers[ i ] === timer ) { + timers.splice( i--, 1 ); + } + } + + if ( !timers.length ) { + jQuery.fx.stop(); + } + fxNow = undefined; +}; + +jQuery.fx.timer = function( timer ) { + jQuery.timers.push( timer ); + if ( timer() ) { + jQuery.fx.start(); + } else { + jQuery.timers.pop(); + } +}; + +jQuery.fx.interval = 13; +jQuery.fx.start = function() { + if ( !timerId ) { + timerId = window.requestAnimationFrame ? + window.requestAnimationFrame( raf ) : + window.setInterval( jQuery.fx.tick, jQuery.fx.interval ); + } +}; + +jQuery.fx.stop = function() { + if ( window.cancelAnimationFrame ) { + window.cancelAnimationFrame( timerId ); + } else { + window.clearInterval( timerId ); + } + + timerId = null; +}; + +jQuery.fx.speeds = { + slow: 600, + fast: 200, + + // Default speed + _default: 400 +}; + + +// Based off of the plugin by Clint Helfers, with permission. +// https://web.archive.org/web/20100324014747/http://blindsignals.com/index.php/2009/07/jquery-delay/ +jQuery.fn.delay = function( time, type ) { + time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time; + type = type || "fx"; + + return this.queue( type, function( next, hooks ) { + var timeout = window.setTimeout( next, time ); + hooks.stop = function() { + window.clearTimeout( timeout ); + }; + } ); +}; + + +( function() { + var input = document.createElement( "input" ), + select = document.createElement( "select" ), + opt = select.appendChild( document.createElement( "option" ) ); + + input.type = "checkbox"; + + // Support: Android <=4.3 only + // Default value for a checkbox should be "on" + support.checkOn = input.value !== ""; + + // Support: IE <=11 only + // Must access selectedIndex to make default options select + support.optSelected = opt.selected; + + // Support: IE <=11 only + // An input loses its value after becoming a radio + input = document.createElement( "input" ); + input.value = "t"; + input.type = "radio"; + support.radioValue = input.value === "t"; +} )(); + + +var boolHook, + attrHandle = jQuery.expr.attrHandle; + +jQuery.fn.extend( { + attr: function( name, value ) { + return access( this, jQuery.attr, name, value, arguments.length > 1 ); + }, + + removeAttr: function( name ) { + return this.each( function() { + jQuery.removeAttr( this, name ); + } ); + } +} ); + +jQuery.extend( { + attr: function( elem, name, value ) { + var ret, hooks, + nType = elem.nodeType; + + // Don't get/set attributes on text, comment and attribute nodes + if ( nType === 3 || nType === 8 || nType === 2 ) { + return; + } + + // Fallback to prop when attributes are not supported + if ( typeof elem.getAttribute === "undefined" ) { + return jQuery.prop( elem, name, value ); + } + + // Attribute hooks are determined by the lowercase version + // Grab necessary hook if one is defined + if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) { + hooks = jQuery.attrHooks[ name.toLowerCase() ] || + ( jQuery.expr.match.bool.test( name ) ? boolHook : undefined ); + } + + if ( value !== undefined ) { + if ( value === null ) { + jQuery.removeAttr( elem, name ); + return; + } + + if ( hooks && "set" in hooks && + ( ret = hooks.set( elem, value, name ) ) !== undefined ) { + return ret; + } + + elem.setAttribute( name, value + "" ); + return value; + } + + if ( hooks && "get" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) { + return ret; + } + + ret = jQuery.find.attr( elem, name ); + + // Non-existent attributes return null, we normalize to undefined + return ret == null ? undefined : ret; + }, + + attrHooks: { + type: { + set: function( elem, value ) { + if ( !support.radioValue && value === "radio" && + jQuery.nodeName( elem, "input" ) ) { + var val = elem.value; + elem.setAttribute( "type", value ); + if ( val ) { + elem.value = val; + } + return value; + } + } + } + }, + + removeAttr: function( elem, value ) { + var name, + i = 0, + + // Attribute names can contain non-HTML whitespace characters + // https://html.spec.whatwg.org/multipage/syntax.html#attributes-2 + attrNames = value && value.match( rnothtmlwhite ); + + if ( attrNames && elem.nodeType === 1 ) { + while ( ( name = attrNames[ i++ ] ) ) { + elem.removeAttribute( name ); + } + } + } +} ); + +// Hooks for boolean attributes +boolHook = { + set: function( elem, value, name ) { + if ( value === false ) { + + // Remove boolean attributes when set to false + jQuery.removeAttr( elem, name ); + } else { + elem.setAttribute( name, name ); + } + return name; + } +}; + +jQuery.each( jQuery.expr.match.bool.source.match( /\w+/g ), function( i, name ) { + var getter = attrHandle[ name ] || jQuery.find.attr; + + attrHandle[ name ] = function( elem, name, isXML ) { + var ret, handle, + lowercaseName = name.toLowerCase(); + + if ( !isXML ) { + + // Avoid an infinite loop by temporarily removing this function from the getter + handle = attrHandle[ lowercaseName ]; + attrHandle[ lowercaseName ] = ret; + ret = getter( elem, name, isXML ) != null ? + lowercaseName : + null; + attrHandle[ lowercaseName ] = handle; + } + return ret; + }; +} ); + + + + +var rfocusable = /^(?:input|select|textarea|button)$/i, + rclickable = /^(?:a|area)$/i; + +jQuery.fn.extend( { + prop: function( name, value ) { + return access( this, jQuery.prop, name, value, arguments.length > 1 ); + }, + + removeProp: function( name ) { + return this.each( function() { + delete this[ jQuery.propFix[ name ] || name ]; + } ); + } +} ); + +jQuery.extend( { + prop: function( elem, name, value ) { + var ret, hooks, + nType = elem.nodeType; + + // Don't get/set properties on text, comment and attribute nodes + if ( nType === 3 || nType === 8 || nType === 2 ) { + return; + } + + if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) { + + // Fix name and attach hooks + name = jQuery.propFix[ name ] || name; + hooks = jQuery.propHooks[ name ]; + } + + if ( value !== undefined ) { + if ( hooks && "set" in hooks && + ( ret = hooks.set( elem, value, name ) ) !== undefined ) { + return ret; + } + + return ( elem[ name ] = value ); + } + + if ( hooks && "get" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) { + return ret; + } + + return elem[ name ]; + }, + + propHooks: { + tabIndex: { + get: function( elem ) { + + // Support: IE <=9 - 11 only + // elem.tabIndex doesn't always return the + // correct value when it hasn't been explicitly set + // https://web.archive.org/web/20141116233347/http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/ + // Use proper attribute retrieval(#12072) + var tabindex = jQuery.find.attr( elem, "tabindex" ); + + if ( tabindex ) { + return parseInt( tabindex, 10 ); + } + + if ( + rfocusable.test( elem.nodeName ) || + rclickable.test( elem.nodeName ) && + elem.href + ) { + return 0; + } + + return -1; + } + } + }, + + propFix: { + "for": "htmlFor", + "class": "className" + } +} ); + +// Support: IE <=11 only +// Accessing the selectedIndex property +// forces the browser to respect setting selected +// on the option +// The getter ensures a default option is selected +// when in an optgroup +// eslint rule "no-unused-expressions" is disabled for this code +// since it considers such accessions noop +if ( !support.optSelected ) { + jQuery.propHooks.selected = { + get: function( elem ) { + + /* eslint no-unused-expressions: "off" */ + + var parent = elem.parentNode; + if ( parent && parent.parentNode ) { + parent.parentNode.selectedIndex; + } + return null; + }, + set: function( elem ) { + + /* eslint no-unused-expressions: "off" */ + + var parent = elem.parentNode; + if ( parent ) { + parent.selectedIndex; + + if ( parent.parentNode ) { + parent.parentNode.selectedIndex; + } + } + } + }; +} + +jQuery.each( [ + "tabIndex", + "readOnly", + "maxLength", + "cellSpacing", + "cellPadding", + "rowSpan", + "colSpan", + "useMap", + "frameBorder", + "contentEditable" +], function() { + jQuery.propFix[ this.toLowerCase() ] = this; +} ); + + + + + // Strip and collapse whitespace according to HTML spec + // https://html.spec.whatwg.org/multipage/infrastructure.html#strip-and-collapse-whitespace + function stripAndCollapse( value ) { + var tokens = value.match( rnothtmlwhite ) || []; + return tokens.join( " " ); + } + + +function getClass( elem ) { + return elem.getAttribute && elem.getAttribute( "class" ) || ""; +} + +jQuery.fn.extend( { + addClass: function( value ) { + var classes, elem, cur, curValue, clazz, j, finalValue, + i = 0; + + if ( jQuery.isFunction( value ) ) { + return this.each( function( j ) { + jQuery( this ).addClass( value.call( this, j, getClass( this ) ) ); + } ); + } + + if ( typeof value === "string" && value ) { + classes = value.match( rnothtmlwhite ) || []; + + while ( ( elem = this[ i++ ] ) ) { + curValue = getClass( elem ); + cur = elem.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " ); + + if ( cur ) { + j = 0; + while ( ( clazz = classes[ j++ ] ) ) { + if ( cur.indexOf( " " + clazz + " " ) < 0 ) { + cur += clazz + " "; + } + } + + // Only assign if different to avoid unneeded rendering. + finalValue = stripAndCollapse( cur ); + if ( curValue !== finalValue ) { + elem.setAttribute( "class", finalValue ); + } + } + } + } + + return this; + }, + + removeClass: function( value ) { + var classes, elem, cur, curValue, clazz, j, finalValue, + i = 0; + + if ( jQuery.isFunction( value ) ) { + return this.each( function( j ) { + jQuery( this ).removeClass( value.call( this, j, getClass( this ) ) ); + } ); + } + + if ( !arguments.length ) { + return this.attr( "class", "" ); + } + + if ( typeof value === "string" && value ) { + classes = value.match( rnothtmlwhite ) || []; + + while ( ( elem = this[ i++ ] ) ) { + curValue = getClass( elem ); + + // This expression is here for better compressibility (see addClass) + cur = elem.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " ); + + if ( cur ) { + j = 0; + while ( ( clazz = classes[ j++ ] ) ) { + + // Remove *all* instances + while ( cur.indexOf( " " + clazz + " " ) > -1 ) { + cur = cur.replace( " " + clazz + " ", " " ); + } + } + + // Only assign if different to avoid unneeded rendering. + finalValue = stripAndCollapse( cur ); + if ( curValue !== finalValue ) { + elem.setAttribute( "class", finalValue ); + } + } + } + } + + return this; + }, + + toggleClass: function( value, stateVal ) { + var type = typeof value; + + if ( typeof stateVal === "boolean" && type === "string" ) { + return stateVal ? this.addClass( value ) : this.removeClass( value ); + } + + if ( jQuery.isFunction( value ) ) { + return this.each( function( i ) { + jQuery( this ).toggleClass( + value.call( this, i, getClass( this ), stateVal ), + stateVal + ); + } ); + } + + return this.each( function() { + var className, i, self, classNames; + + if ( type === "string" ) { + + // Toggle individual class names + i = 0; + self = jQuery( this ); + classNames = value.match( rnothtmlwhite ) || []; + + while ( ( className = classNames[ i++ ] ) ) { + + // Check each className given, space separated list + if ( self.hasClass( className ) ) { + self.removeClass( className ); + } else { + self.addClass( className ); + } + } + + // Toggle whole class name + } else if ( value === undefined || type === "boolean" ) { + className = getClass( this ); + if ( className ) { + + // Store className if set + dataPriv.set( this, "__className__", className ); + } + + // If the element has a class name or if we're passed `false`, + // then remove the whole classname (if there was one, the above saved it). + // Otherwise bring back whatever was previously saved (if anything), + // falling back to the empty string if nothing was stored. + if ( this.setAttribute ) { + this.setAttribute( "class", + className || value === false ? + "" : + dataPriv.get( this, "__className__" ) || "" + ); + } + } + } ); + }, + + hasClass: function( selector ) { + var className, elem, + i = 0; + + className = " " + selector + " "; + while ( ( elem = this[ i++ ] ) ) { + if ( elem.nodeType === 1 && + ( " " + stripAndCollapse( getClass( elem ) ) + " " ).indexOf( className ) > -1 ) { + return true; + } + } + + return false; + } +} ); + + + + +var rreturn = /\r/g; + +jQuery.fn.extend( { + val: function( value ) { + var hooks, ret, isFunction, + elem = this[ 0 ]; + + if ( !arguments.length ) { + if ( elem ) { + hooks = jQuery.valHooks[ elem.type ] || + jQuery.valHooks[ elem.nodeName.toLowerCase() ]; + + if ( hooks && + "get" in hooks && + ( ret = hooks.get( elem, "value" ) ) !== undefined + ) { + return ret; + } + + ret = elem.value; + + // Handle most common string cases + if ( typeof ret === "string" ) { + return ret.replace( rreturn, "" ); + } + + // Handle cases where value is null/undef or number + return ret == null ? "" : ret; + } + + return; + } + + isFunction = jQuery.isFunction( value ); + + return this.each( function( i ) { + var val; + + if ( this.nodeType !== 1 ) { + return; + } + + if ( isFunction ) { + val = value.call( this, i, jQuery( this ).val() ); + } else { + val = value; + } + + // Treat null/undefined as ""; convert numbers to string + if ( val == null ) { + val = ""; + + } else if ( typeof val === "number" ) { + val += ""; + + } else if ( jQuery.isArray( val ) ) { + val = jQuery.map( val, function( value ) { + return value == null ? "" : value + ""; + } ); + } + + hooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ]; + + // If set returns undefined, fall back to normal setting + if ( !hooks || !( "set" in hooks ) || hooks.set( this, val, "value" ) === undefined ) { + this.value = val; + } + } ); + } +} ); + +jQuery.extend( { + valHooks: { + option: { + get: function( elem ) { + + var val = jQuery.find.attr( elem, "value" ); + return val != null ? + val : + + // Support: IE <=10 - 11 only + // option.text throws exceptions (#14686, #14858) + // Strip and collapse whitespace + // https://html.spec.whatwg.org/#strip-and-collapse-whitespace + stripAndCollapse( jQuery.text( elem ) ); + } + }, + select: { + get: function( elem ) { + var value, option, i, + options = elem.options, + index = elem.selectedIndex, + one = elem.type === "select-one", + values = one ? null : [], + max = one ? index + 1 : options.length; + + if ( index < 0 ) { + i = max; + + } else { + i = one ? index : 0; + } + + // Loop through all the selected options + for ( ; i < max; i++ ) { + option = options[ i ]; + + // Support: IE <=9 only + // IE8-9 doesn't update selected after form reset (#2551) + if ( ( option.selected || i === index ) && + + // Don't return options that are disabled or in a disabled optgroup + !option.disabled && + ( !option.parentNode.disabled || + !jQuery.nodeName( option.parentNode, "optgroup" ) ) ) { + + // Get the specific value for the option + value = jQuery( option ).val(); + + // We don't need an array for one selects + if ( one ) { + return value; + } + + // Multi-Selects return an array + values.push( value ); + } + } + + return values; + }, + + set: function( elem, value ) { + var optionSet, option, + options = elem.options, + values = jQuery.makeArray( value ), + i = options.length; + + while ( i-- ) { + option = options[ i ]; + + /* eslint-disable no-cond-assign */ + + if ( option.selected = + jQuery.inArray( jQuery.valHooks.option.get( option ), values ) > -1 + ) { + optionSet = true; + } + + /* eslint-enable no-cond-assign */ + } + + // Force browsers to behave consistently when non-matching value is set + if ( !optionSet ) { + elem.selectedIndex = -1; + } + return values; + } + } + } +} ); + +// Radios and checkboxes getter/setter +jQuery.each( [ "radio", "checkbox" ], function() { + jQuery.valHooks[ this ] = { + set: function( elem, value ) { + if ( jQuery.isArray( value ) ) { + return ( elem.checked = jQuery.inArray( jQuery( elem ).val(), value ) > -1 ); + } + } + }; + if ( !support.checkOn ) { + jQuery.valHooks[ this ].get = function( elem ) { + return elem.getAttribute( "value" ) === null ? "on" : elem.value; + }; + } +} ); + + + + +// Return jQuery for attributes-only inclusion + + +var rfocusMorph = /^(?:focusinfocus|focusoutblur)$/; + +jQuery.extend( jQuery.event, { + + trigger: function( event, data, elem, onlyHandlers ) { + + var i, cur, tmp, bubbleType, ontype, handle, special, + eventPath = [ elem || document ], + type = hasOwn.call( event, "type" ) ? event.type : event, + namespaces = hasOwn.call( event, "namespace" ) ? event.namespace.split( "." ) : []; + + cur = tmp = elem = elem || document; + + // Don't do events on text and comment nodes + if ( elem.nodeType === 3 || elem.nodeType === 8 ) { + return; + } + + // focus/blur morphs to focusin/out; ensure we're not firing them right now + if ( rfocusMorph.test( type + jQuery.event.triggered ) ) { + return; + } + + if ( type.indexOf( "." ) > -1 ) { + + // Namespaced trigger; create a regexp to match event type in handle() + namespaces = type.split( "." ); + type = namespaces.shift(); + namespaces.sort(); + } + ontype = type.indexOf( ":" ) < 0 && "on" + type; + + // Caller can pass in a jQuery.Event object, Object, or just an event type string + event = event[ jQuery.expando ] ? + event : + new jQuery.Event( type, typeof event === "object" && event ); + + // Trigger bitmask: & 1 for native handlers; & 2 for jQuery (always true) + event.isTrigger = onlyHandlers ? 2 : 3; + event.namespace = namespaces.join( "." ); + event.rnamespace = event.namespace ? + new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" ) : + null; + + // Clean up the event in case it is being reused + event.result = undefined; + if ( !event.target ) { + event.target = elem; + } + + // Clone any incoming data and prepend the event, creating the handler arg list + data = data == null ? + [ event ] : + jQuery.makeArray( data, [ event ] ); + + // Allow special events to draw outside the lines + special = jQuery.event.special[ type ] || {}; + if ( !onlyHandlers && special.trigger && special.trigger.apply( elem, data ) === false ) { + return; + } + + // Determine event propagation path in advance, per W3C events spec (#9951) + // Bubble up to document, then to window; watch for a global ownerDocument var (#9724) + if ( !onlyHandlers && !special.noBubble && !jQuery.isWindow( elem ) ) { + + bubbleType = special.delegateType || type; + if ( !rfocusMorph.test( bubbleType + type ) ) { + cur = cur.parentNode; + } + for ( ; cur; cur = cur.parentNode ) { + eventPath.push( cur ); + tmp = cur; + } + + // Only add window if we got to document (e.g., not plain obj or detached DOM) + if ( tmp === ( elem.ownerDocument || document ) ) { + eventPath.push( tmp.defaultView || tmp.parentWindow || window ); + } + } + + // Fire handlers on the event path + i = 0; + while ( ( cur = eventPath[ i++ ] ) && !event.isPropagationStopped() ) { + + event.type = i > 1 ? + bubbleType : + special.bindType || type; + + // jQuery handler + handle = ( dataPriv.get( cur, "events" ) || {} )[ event.type ] && + dataPriv.get( cur, "handle" ); + if ( handle ) { + handle.apply( cur, data ); + } + + // Native handler + handle = ontype && cur[ ontype ]; + if ( handle && handle.apply && acceptData( cur ) ) { + event.result = handle.apply( cur, data ); + if ( event.result === false ) { + event.preventDefault(); + } + } + } + event.type = type; + + // If nobody prevented the default action, do it now + if ( !onlyHandlers && !event.isDefaultPrevented() ) { + + if ( ( !special._default || + special._default.apply( eventPath.pop(), data ) === false ) && + acceptData( elem ) ) { + + // Call a native DOM method on the target with the same name as the event. + // Don't do default actions on window, that's where global variables be (#6170) + if ( ontype && jQuery.isFunction( elem[ type ] ) && !jQuery.isWindow( elem ) ) { + + // Don't re-trigger an onFOO event when we call its FOO() method + tmp = elem[ ontype ]; + + if ( tmp ) { + elem[ ontype ] = null; + } + + // Prevent re-triggering of the same event, since we already bubbled it above + jQuery.event.triggered = type; + elem[ type ](); + jQuery.event.triggered = undefined; + + if ( tmp ) { + elem[ ontype ] = tmp; + } + } + } + } + + return event.result; + }, + + // Piggyback on a donor event to simulate a different one + // Used only for `focus(in | out)` events + simulate: function( type, elem, event ) { + var e = jQuery.extend( + new jQuery.Event(), + event, + { + type: type, + isSimulated: true + } + ); + + jQuery.event.trigger( e, null, elem ); + } + +} ); + +jQuery.fn.extend( { + + trigger: function( type, data ) { + return this.each( function() { + jQuery.event.trigger( type, data, this ); + } ); + }, + triggerHandler: function( type, data ) { + var elem = this[ 0 ]; + if ( elem ) { + return jQuery.event.trigger( type, data, elem, true ); + } + } +} ); + + +jQuery.each( ( "blur focus focusin focusout resize scroll click dblclick " + + "mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " + + "change select submit keydown keypress keyup contextmenu" ).split( " " ), + function( i, name ) { + + // Handle event binding + jQuery.fn[ name ] = function( data, fn ) { + return arguments.length > 0 ? + this.on( name, null, data, fn ) : + this.trigger( name ); + }; +} ); + +jQuery.fn.extend( { + hover: function( fnOver, fnOut ) { + return this.mouseenter( fnOver ).mouseleave( fnOut || fnOver ); + } +} ); + + + + +support.focusin = "onfocusin" in window; + + +// Support: Firefox <=44 +// Firefox doesn't have focus(in | out) events +// Related ticket - https://bugzilla.mozilla.org/show_bug.cgi?id=687787 +// +// Support: Chrome <=48 - 49, Safari <=9.0 - 9.1 +// focus(in | out) events fire after focus & blur events, +// which is spec violation - http://www.w3.org/TR/DOM-Level-3-Events/#events-focusevent-event-order +// Related ticket - https://bugs.chromium.org/p/chromium/issues/detail?id=449857 +if ( !support.focusin ) { + jQuery.each( { focus: "focusin", blur: "focusout" }, function( orig, fix ) { + + // Attach a single capturing handler on the document while someone wants focusin/focusout + var handler = function( event ) { + jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ) ); + }; + + jQuery.event.special[ fix ] = { + setup: function() { + var doc = this.ownerDocument || this, + attaches = dataPriv.access( doc, fix ); + + if ( !attaches ) { + doc.addEventListener( orig, handler, true ); + } + dataPriv.access( doc, fix, ( attaches || 0 ) + 1 ); + }, + teardown: function() { + var doc = this.ownerDocument || this, + attaches = dataPriv.access( doc, fix ) - 1; + + if ( !attaches ) { + doc.removeEventListener( orig, handler, true ); + dataPriv.remove( doc, fix ); + + } else { + dataPriv.access( doc, fix, attaches ); + } + } + }; + } ); +} +var location = window.location; + +var nonce = jQuery.now(); + +var rquery = ( /\?/ ); + + + +// Cross-browser xml parsing +jQuery.parseXML = function( data ) { + var xml; + if ( !data || typeof data !== "string" ) { + return null; + } + + // Support: IE 9 - 11 only + // IE throws on parseFromString with invalid input. + try { + xml = ( new window.DOMParser() ).parseFromString( data, "text/xml" ); + } catch ( e ) { + xml = undefined; + } + + if ( !xml || xml.getElementsByTagName( "parsererror" ).length ) { + jQuery.error( "Invalid XML: " + data ); + } + return xml; +}; + + +var + rbracket = /\[\]$/, + rCRLF = /\r?\n/g, + rsubmitterTypes = /^(?:submit|button|image|reset|file)$/i, + rsubmittable = /^(?:input|select|textarea|keygen)/i; + +function buildParams( prefix, obj, traditional, add ) { + var name; + + if ( jQuery.isArray( obj ) ) { + + // Serialize array item. + jQuery.each( obj, function( i, v ) { + if ( traditional || rbracket.test( prefix ) ) { + + // Treat each array item as a scalar. + add( prefix, v ); + + } else { + + // Item is non-scalar (array or object), encode its numeric index. + buildParams( + prefix + "[" + ( typeof v === "object" && v != null ? i : "" ) + "]", + v, + traditional, + add + ); + } + } ); + + } else if ( !traditional && jQuery.type( obj ) === "object" ) { + + // Serialize object item. + for ( name in obj ) { + buildParams( prefix + "[" + name + "]", obj[ name ], traditional, add ); + } + + } else { + + // Serialize scalar item. + add( prefix, obj ); + } +} + +// Serialize an array of form elements or a set of +// key/values into a query string +jQuery.param = function( a, traditional ) { + var prefix, + s = [], + add = function( key, valueOrFunction ) { + + // If value is a function, invoke it and use its return value + var value = jQuery.isFunction( valueOrFunction ) ? + valueOrFunction() : + valueOrFunction; + + s[ s.length ] = encodeURIComponent( key ) + "=" + + encodeURIComponent( value == null ? "" : value ); + }; + + // If an array was passed in, assume that it is an array of form elements. + if ( jQuery.isArray( a ) || ( a.jquery && !jQuery.isPlainObject( a ) ) ) { + + // Serialize the form elements + jQuery.each( a, function() { + add( this.name, this.value ); + } ); + + } else { + + // If traditional, encode the "old" way (the way 1.3.2 or older + // did it), otherwise encode params recursively. + for ( prefix in a ) { + buildParams( prefix, a[ prefix ], traditional, add ); + } + } + + // Return the resulting serialization + return s.join( "&" ); +}; + +jQuery.fn.extend( { + serialize: function() { + return jQuery.param( this.serializeArray() ); + }, + serializeArray: function() { + return this.map( function() { + + // Can add propHook for "elements" to filter or add form elements + var elements = jQuery.prop( this, "elements" ); + return elements ? jQuery.makeArray( elements ) : this; + } ) + .filter( function() { + var type = this.type; + + // Use .is( ":disabled" ) so that fieldset[disabled] works + return this.name && !jQuery( this ).is( ":disabled" ) && + rsubmittable.test( this.nodeName ) && !rsubmitterTypes.test( type ) && + ( this.checked || !rcheckableType.test( type ) ); + } ) + .map( function( i, elem ) { + var val = jQuery( this ).val(); + + if ( val == null ) { + return null; + } + + if ( jQuery.isArray( val ) ) { + return jQuery.map( val, function( val ) { + return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) }; + } ); + } + + return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) }; + } ).get(); + } +} ); + + +var + r20 = /%20/g, + rhash = /#.*$/, + rantiCache = /([?&])_=[^&]*/, + rheaders = /^(.*?):[ \t]*([^\r\n]*)$/mg, + + // #7653, #8125, #8152: local protocol detection + rlocalProtocol = /^(?:about|app|app-storage|.+-extension|file|res|widget):$/, + rnoContent = /^(?:GET|HEAD)$/, + rprotocol = /^\/\//, + + /* Prefilters + * 1) They are useful to introduce custom dataTypes (see ajax/jsonp.js for an example) + * 2) These are called: + * - BEFORE asking for a transport + * - AFTER param serialization (s.data is a string if s.processData is true) + * 3) key is the dataType + * 4) the catchall symbol "*" can be used + * 5) execution will start with transport dataType and THEN continue down to "*" if needed + */ + prefilters = {}, + + /* Transports bindings + * 1) key is the dataType + * 2) the catchall symbol "*" can be used + * 3) selection will start with transport dataType and THEN go to "*" if needed + */ + transports = {}, + + // Avoid comment-prolog char sequence (#10098); must appease lint and evade compression + allTypes = "*/".concat( "*" ), + + // Anchor tag for parsing the document origin + originAnchor = document.createElement( "a" ); + originAnchor.href = location.href; + +// Base "constructor" for jQuery.ajaxPrefilter and jQuery.ajaxTransport +function addToPrefiltersOrTransports( structure ) { + + // dataTypeExpression is optional and defaults to "*" + return function( dataTypeExpression, func ) { + + if ( typeof dataTypeExpression !== "string" ) { + func = dataTypeExpression; + dataTypeExpression = "*"; + } + + var dataType, + i = 0, + dataTypes = dataTypeExpression.toLowerCase().match( rnothtmlwhite ) || []; + + if ( jQuery.isFunction( func ) ) { + + // For each dataType in the dataTypeExpression + while ( ( dataType = dataTypes[ i++ ] ) ) { + + // Prepend if requested + if ( dataType[ 0 ] === "+" ) { + dataType = dataType.slice( 1 ) || "*"; + ( structure[ dataType ] = structure[ dataType ] || [] ).unshift( func ); + + // Otherwise append + } else { + ( structure[ dataType ] = structure[ dataType ] || [] ).push( func ); + } + } + } + }; +} + +// Base inspection function for prefilters and transports +function inspectPrefiltersOrTransports( structure, options, originalOptions, jqXHR ) { + + var inspected = {}, + seekingTransport = ( structure === transports ); + + function inspect( dataType ) { + var selected; + inspected[ dataType ] = true; + jQuery.each( structure[ dataType ] || [], function( _, prefilterOrFactory ) { + var dataTypeOrTransport = prefilterOrFactory( options, originalOptions, jqXHR ); + if ( typeof dataTypeOrTransport === "string" && + !seekingTransport && !inspected[ dataTypeOrTransport ] ) { + + options.dataTypes.unshift( dataTypeOrTransport ); + inspect( dataTypeOrTransport ); + return false; + } else if ( seekingTransport ) { + return !( selected = dataTypeOrTransport ); + } + } ); + return selected; + } + + return inspect( options.dataTypes[ 0 ] ) || !inspected[ "*" ] && inspect( "*" ); +} + +// A special extend for ajax options +// that takes "flat" options (not to be deep extended) +// Fixes #9887 +function ajaxExtend( target, src ) { + var key, deep, + flatOptions = jQuery.ajaxSettings.flatOptions || {}; + + for ( key in src ) { + if ( src[ key ] !== undefined ) { + ( flatOptions[ key ] ? target : ( deep || ( deep = {} ) ) )[ key ] = src[ key ]; + } + } + if ( deep ) { + jQuery.extend( true, target, deep ); + } + + return target; +} + +/* Handles responses to an ajax request: + * - finds the right dataType (mediates between content-type and expected dataType) + * - returns the corresponding response + */ +function ajaxHandleResponses( s, jqXHR, responses ) { + + var ct, type, finalDataType, firstDataType, + contents = s.contents, + dataTypes = s.dataTypes; + + // Remove auto dataType and get content-type in the process + while ( dataTypes[ 0 ] === "*" ) { + dataTypes.shift(); + if ( ct === undefined ) { + ct = s.mimeType || jqXHR.getResponseHeader( "Content-Type" ); + } + } + + // Check if we're dealing with a known content-type + if ( ct ) { + for ( type in contents ) { + if ( contents[ type ] && contents[ type ].test( ct ) ) { + dataTypes.unshift( type ); + break; + } + } + } + + // Check to see if we have a response for the expected dataType + if ( dataTypes[ 0 ] in responses ) { + finalDataType = dataTypes[ 0 ]; + } else { + + // Try convertible dataTypes + for ( type in responses ) { + if ( !dataTypes[ 0 ] || s.converters[ type + " " + dataTypes[ 0 ] ] ) { + finalDataType = type; + break; + } + if ( !firstDataType ) { + firstDataType = type; + } + } + + // Or just use first one + finalDataType = finalDataType || firstDataType; + } + + // If we found a dataType + // We add the dataType to the list if needed + // and return the corresponding response + if ( finalDataType ) { + if ( finalDataType !== dataTypes[ 0 ] ) { + dataTypes.unshift( finalDataType ); + } + return responses[ finalDataType ]; + } +} + +/* Chain conversions given the request and the original response + * Also sets the responseXXX fields on the jqXHR instance + */ +function ajaxConvert( s, response, jqXHR, isSuccess ) { + var conv2, current, conv, tmp, prev, + converters = {}, + + // Work with a copy of dataTypes in case we need to modify it for conversion + dataTypes = s.dataTypes.slice(); + + // Create converters map with lowercased keys + if ( dataTypes[ 1 ] ) { + for ( conv in s.converters ) { + converters[ conv.toLowerCase() ] = s.converters[ conv ]; + } + } + + current = dataTypes.shift(); + + // Convert to each sequential dataType + while ( current ) { + + if ( s.responseFields[ current ] ) { + jqXHR[ s.responseFields[ current ] ] = response; + } + + // Apply the dataFilter if provided + if ( !prev && isSuccess && s.dataFilter ) { + response = s.dataFilter( response, s.dataType ); + } + + prev = current; + current = dataTypes.shift(); + + if ( current ) { + + // There's only work to do if current dataType is non-auto + if ( current === "*" ) { + + current = prev; + + // Convert response if prev dataType is non-auto and differs from current + } else if ( prev !== "*" && prev !== current ) { + + // Seek a direct converter + conv = converters[ prev + " " + current ] || converters[ "* " + current ]; + + // If none found, seek a pair + if ( !conv ) { + for ( conv2 in converters ) { + + // If conv2 outputs current + tmp = conv2.split( " " ); + if ( tmp[ 1 ] === current ) { + + // If prev can be converted to accepted input + conv = converters[ prev + " " + tmp[ 0 ] ] || + converters[ "* " + tmp[ 0 ] ]; + if ( conv ) { + + // Condense equivalence converters + if ( conv === true ) { + conv = converters[ conv2 ]; + + // Otherwise, insert the intermediate dataType + } else if ( converters[ conv2 ] !== true ) { + current = tmp[ 0 ]; + dataTypes.unshift( tmp[ 1 ] ); + } + break; + } + } + } + } + + // Apply converter (if not an equivalence) + if ( conv !== true ) { + + // Unless errors are allowed to bubble, catch and return them + if ( conv && s.throws ) { + response = conv( response ); + } else { + try { + response = conv( response ); + } catch ( e ) { + return { + state: "parsererror", + error: conv ? e : "No conversion from " + prev + " to " + current + }; + } + } + } + } + } + } + + return { state: "success", data: response }; +} + +jQuery.extend( { + + // Counter for holding the number of active queries + active: 0, + + // Last-Modified header cache for next request + lastModified: {}, + etag: {}, + + ajaxSettings: { + url: location.href, + type: "GET", + isLocal: rlocalProtocol.test( location.protocol ), + global: true, + processData: true, + async: true, + contentType: "application/x-www-form-urlencoded; charset=UTF-8", + + /* + timeout: 0, + data: null, + dataType: null, + username: null, + password: null, + cache: null, + throws: false, + traditional: false, + headers: {}, + */ + + accepts: { + "*": allTypes, + text: "text/plain", + html: "text/html", + xml: "application/xml, text/xml", + json: "application/json, text/javascript" + }, + + contents: { + xml: /\bxml\b/, + html: /\bhtml/, + json: /\bjson\b/ + }, + + responseFields: { + xml: "responseXML", + text: "responseText", + json: "responseJSON" + }, + + // Data converters + // Keys separate source (or catchall "*") and destination types with a single space + converters: { + + // Convert anything to text + "* text": String, + + // Text to html (true = no transformation) + "text html": true, + + // Evaluate text as a json expression + "text json": JSON.parse, + + // Parse text as xml + "text xml": jQuery.parseXML + }, + + // For options that shouldn't be deep extended: + // you can add your own custom options here if + // and when you create one that shouldn't be + // deep extended (see ajaxExtend) + flatOptions: { + url: true, + context: true + } + }, + + // Creates a full fledged settings object into target + // with both ajaxSettings and settings fields. + // If target is omitted, writes into ajaxSettings. + ajaxSetup: function( target, settings ) { + return settings ? + + // Building a settings object + ajaxExtend( ajaxExtend( target, jQuery.ajaxSettings ), settings ) : + + // Extending ajaxSettings + ajaxExtend( jQuery.ajaxSettings, target ); + }, + + ajaxPrefilter: addToPrefiltersOrTransports( prefilters ), + ajaxTransport: addToPrefiltersOrTransports( transports ), + + // Main method + ajax: function( url, options ) { + + // If url is an object, simulate pre-1.5 signature + if ( typeof url === "object" ) { + options = url; + url = undefined; + } + + // Force options to be an object + options = options || {}; + + var transport, + + // URL without anti-cache param + cacheURL, + + // Response headers + responseHeadersString, + responseHeaders, + + // timeout handle + timeoutTimer, + + // Url cleanup var + urlAnchor, + + // Request state (becomes false upon send and true upon completion) + completed, + + // To know if global events are to be dispatched + fireGlobals, + + // Loop variable + i, + + // uncached part of the url + uncached, + + // Create the final options object + s = jQuery.ajaxSetup( {}, options ), + + // Callbacks context + callbackContext = s.context || s, + + // Context for global events is callbackContext if it is a DOM node or jQuery collection + globalEventContext = s.context && + ( callbackContext.nodeType || callbackContext.jquery ) ? + jQuery( callbackContext ) : + jQuery.event, + + // Deferreds + deferred = jQuery.Deferred(), + completeDeferred = jQuery.Callbacks( "once memory" ), + + // Status-dependent callbacks + statusCode = s.statusCode || {}, + + // Headers (they are sent all at once) + requestHeaders = {}, + requestHeadersNames = {}, + + // Default abort message + strAbort = "canceled", + + // Fake xhr + jqXHR = { + readyState: 0, + + // Builds headers hashtable if needed + getResponseHeader: function( key ) { + var match; + if ( completed ) { + if ( !responseHeaders ) { + responseHeaders = {}; + while ( ( match = rheaders.exec( responseHeadersString ) ) ) { + responseHeaders[ match[ 1 ].toLowerCase() ] = match[ 2 ]; + } + } + match = responseHeaders[ key.toLowerCase() ]; + } + return match == null ? null : match; + }, + + // Raw string + getAllResponseHeaders: function() { + return completed ? responseHeadersString : null; + }, + + // Caches the header + setRequestHeader: function( name, value ) { + if ( completed == null ) { + name = requestHeadersNames[ name.toLowerCase() ] = + requestHeadersNames[ name.toLowerCase() ] || name; + requestHeaders[ name ] = value; + } + return this; + }, + + // Overrides response content-type header + overrideMimeType: function( type ) { + if ( completed == null ) { + s.mimeType = type; + } + return this; + }, + + // Status-dependent callbacks + statusCode: function( map ) { + var code; + if ( map ) { + if ( completed ) { + + // Execute the appropriate callbacks + jqXHR.always( map[ jqXHR.status ] ); + } else { + + // Lazy-add the new callbacks in a way that preserves old ones + for ( code in map ) { + statusCode[ code ] = [ statusCode[ code ], map[ code ] ]; + } + } + } + return this; + }, + + // Cancel the request + abort: function( statusText ) { + var finalText = statusText || strAbort; + if ( transport ) { + transport.abort( finalText ); + } + done( 0, finalText ); + return this; + } + }; + + // Attach deferreds + deferred.promise( jqXHR ); + + // Add protocol if not provided (prefilters might expect it) + // Handle falsy url in the settings object (#10093: consistency with old signature) + // We also use the url parameter if available + s.url = ( ( url || s.url || location.href ) + "" ) + .replace( rprotocol, location.protocol + "//" ); + + // Alias method option to type as per ticket #12004 + s.type = options.method || options.type || s.method || s.type; + + // Extract dataTypes list + s.dataTypes = ( s.dataType || "*" ).toLowerCase().match( rnothtmlwhite ) || [ "" ]; + + // A cross-domain request is in order when the origin doesn't match the current origin. + if ( s.crossDomain == null ) { + urlAnchor = document.createElement( "a" ); + + // Support: IE <=8 - 11, Edge 12 - 13 + // IE throws exception on accessing the href property if url is malformed, + // e.g. http://example.com:80x/ + try { + urlAnchor.href = s.url; + + // Support: IE <=8 - 11 only + // Anchor's host property isn't correctly set when s.url is relative + urlAnchor.href = urlAnchor.href; + s.crossDomain = originAnchor.protocol + "//" + originAnchor.host !== + urlAnchor.protocol + "//" + urlAnchor.host; + } catch ( e ) { + + // If there is an error parsing the URL, assume it is crossDomain, + // it can be rejected by the transport if it is invalid + s.crossDomain = true; + } + } + + // Convert data if not already a string + if ( s.data && s.processData && typeof s.data !== "string" ) { + s.data = jQuery.param( s.data, s.traditional ); + } + + // Apply prefilters + inspectPrefiltersOrTransports( prefilters, s, options, jqXHR ); + + // If request was aborted inside a prefilter, stop there + if ( completed ) { + return jqXHR; + } + + // We can fire global events as of now if asked to + // Don't fire events if jQuery.event is undefined in an AMD-usage scenario (#15118) + fireGlobals = jQuery.event && s.global; + + // Watch for a new set of requests + if ( fireGlobals && jQuery.active++ === 0 ) { + jQuery.event.trigger( "ajaxStart" ); + } + + // Uppercase the type + s.type = s.type.toUpperCase(); + + // Determine if request has content + s.hasContent = !rnoContent.test( s.type ); + + // Save the URL in case we're toying with the If-Modified-Since + // and/or If-None-Match header later on + // Remove hash to simplify url manipulation + cacheURL = s.url.replace( rhash, "" ); + + // More options handling for requests with no content + if ( !s.hasContent ) { + + // Remember the hash so we can put it back + uncached = s.url.slice( cacheURL.length ); + + // If data is available, append data to url + if ( s.data ) { + cacheURL += ( rquery.test( cacheURL ) ? "&" : "?" ) + s.data; + + // #9682: remove data so that it's not used in an eventual retry + delete s.data; + } + + // Add or update anti-cache param if needed + if ( s.cache === false ) { + cacheURL = cacheURL.replace( rantiCache, "$1" ); + uncached = ( rquery.test( cacheURL ) ? "&" : "?" ) + "_=" + ( nonce++ ) + uncached; + } + + // Put hash and anti-cache on the URL that will be requested (gh-1732) + s.url = cacheURL + uncached; + + // Change '%20' to '+' if this is encoded form body content (gh-2658) + } else if ( s.data && s.processData && + ( s.contentType || "" ).indexOf( "application/x-www-form-urlencoded" ) === 0 ) { + s.data = s.data.replace( r20, "+" ); + } + + // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. + if ( s.ifModified ) { + if ( jQuery.lastModified[ cacheURL ] ) { + jqXHR.setRequestHeader( "If-Modified-Since", jQuery.lastModified[ cacheURL ] ); + } + if ( jQuery.etag[ cacheURL ] ) { + jqXHR.setRequestHeader( "If-None-Match", jQuery.etag[ cacheURL ] ); + } + } + + // Set the correct header, if data is being sent + if ( s.data && s.hasContent && s.contentType !== false || options.contentType ) { + jqXHR.setRequestHeader( "Content-Type", s.contentType ); + } + + // Set the Accepts header for the server, depending on the dataType + jqXHR.setRequestHeader( + "Accept", + s.dataTypes[ 0 ] && s.accepts[ s.dataTypes[ 0 ] ] ? + s.accepts[ s.dataTypes[ 0 ] ] + + ( s.dataTypes[ 0 ] !== "*" ? ", " + allTypes + "; q=0.01" : "" ) : + s.accepts[ "*" ] + ); + + // Check for headers option + for ( i in s.headers ) { + jqXHR.setRequestHeader( i, s.headers[ i ] ); + } + + // Allow custom headers/mimetypes and early abort + if ( s.beforeSend && + ( s.beforeSend.call( callbackContext, jqXHR, s ) === false || completed ) ) { + + // Abort if not done already and return + return jqXHR.abort(); + } + + // Aborting is no longer a cancellation + strAbort = "abort"; + + // Install callbacks on deferreds + completeDeferred.add( s.complete ); + jqXHR.done( s.success ); + jqXHR.fail( s.error ); + + // Get transport + transport = inspectPrefiltersOrTransports( transports, s, options, jqXHR ); + + // If no transport, we auto-abort + if ( !transport ) { + done( -1, "No Transport" ); + } else { + jqXHR.readyState = 1; + + // Send global event + if ( fireGlobals ) { + globalEventContext.trigger( "ajaxSend", [ jqXHR, s ] ); + } + + // If request was aborted inside ajaxSend, stop there + if ( completed ) { + return jqXHR; + } + + // Timeout + if ( s.async && s.timeout > 0 ) { + timeoutTimer = window.setTimeout( function() { + jqXHR.abort( "timeout" ); + }, s.timeout ); + } + + try { + completed = false; + transport.send( requestHeaders, done ); + } catch ( e ) { + + // Rethrow post-completion exceptions + if ( completed ) { + throw e; + } + + // Propagate others as results + done( -1, e ); + } + } + + // Callback for when everything is done + function done( status, nativeStatusText, responses, headers ) { + var isSuccess, success, error, response, modified, + statusText = nativeStatusText; + + // Ignore repeat invocations + if ( completed ) { + return; + } + + completed = true; + + // Clear timeout if it exists + if ( timeoutTimer ) { + window.clearTimeout( timeoutTimer ); + } + + // Dereference transport for early garbage collection + // (no matter how long the jqXHR object will be used) + transport = undefined; + + // Cache response headers + responseHeadersString = headers || ""; + + // Set readyState + jqXHR.readyState = status > 0 ? 4 : 0; + + // Determine if successful + isSuccess = status >= 200 && status < 300 || status === 304; + + // Get response data + if ( responses ) { + response = ajaxHandleResponses( s, jqXHR, responses ); + } + + // Convert no matter what (that way responseXXX fields are always set) + response = ajaxConvert( s, response, jqXHR, isSuccess ); + + // If successful, handle type chaining + if ( isSuccess ) { + + // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. + if ( s.ifModified ) { + modified = jqXHR.getResponseHeader( "Last-Modified" ); + if ( modified ) { + jQuery.lastModified[ cacheURL ] = modified; + } + modified = jqXHR.getResponseHeader( "etag" ); + if ( modified ) { + jQuery.etag[ cacheURL ] = modified; + } + } + + // if no content + if ( status === 204 || s.type === "HEAD" ) { + statusText = "nocontent"; + + // if not modified + } else if ( status === 304 ) { + statusText = "notmodified"; + + // If we have data, let's convert it + } else { + statusText = response.state; + success = response.data; + error = response.error; + isSuccess = !error; + } + } else { + + // Extract error from statusText and normalize for non-aborts + error = statusText; + if ( status || !statusText ) { + statusText = "error"; + if ( status < 0 ) { + status = 0; + } + } + } + + // Set data for the fake xhr object + jqXHR.status = status; + jqXHR.statusText = ( nativeStatusText || statusText ) + ""; + + // Success/Error + if ( isSuccess ) { + deferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] ); + } else { + deferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] ); + } + + // Status-dependent callbacks + jqXHR.statusCode( statusCode ); + statusCode = undefined; + + if ( fireGlobals ) { + globalEventContext.trigger( isSuccess ? "ajaxSuccess" : "ajaxError", + [ jqXHR, s, isSuccess ? success : error ] ); + } + + // Complete + completeDeferred.fireWith( callbackContext, [ jqXHR, statusText ] ); + + if ( fireGlobals ) { + globalEventContext.trigger( "ajaxComplete", [ jqXHR, s ] ); + + // Handle the global AJAX counter + if ( !( --jQuery.active ) ) { + jQuery.event.trigger( "ajaxStop" ); + } + } + } + + return jqXHR; + }, + + getJSON: function( url, data, callback ) { + return jQuery.get( url, data, callback, "json" ); + }, + + getScript: function( url, callback ) { + return jQuery.get( url, undefined, callback, "script" ); + } +} ); + +jQuery.each( [ "get", "post" ], function( i, method ) { + jQuery[ method ] = function( url, data, callback, type ) { + + // Shift arguments if data argument was omitted + if ( jQuery.isFunction( data ) ) { + type = type || callback; + callback = data; + data = undefined; + } + + // The url can be an options object (which then must have .url) + return jQuery.ajax( jQuery.extend( { + url: url, + type: method, + dataType: type, + data: data, + success: callback + }, jQuery.isPlainObject( url ) && url ) ); + }; +} ); + + +jQuery._evalUrl = function( url ) { + return jQuery.ajax( { + url: url, + + // Make this explicit, since user can override this through ajaxSetup (#11264) + type: "GET", + dataType: "script", + cache: true, + async: false, + global: false, + "throws": true + } ); +}; + + +jQuery.fn.extend( { + wrapAll: function( html ) { + var wrap; + + if ( this[ 0 ] ) { + if ( jQuery.isFunction( html ) ) { + html = html.call( this[ 0 ] ); + } + + // The elements to wrap the target around + wrap = jQuery( html, this[ 0 ].ownerDocument ).eq( 0 ).clone( true ); + + if ( this[ 0 ].parentNode ) { + wrap.insertBefore( this[ 0 ] ); + } + + wrap.map( function() { + var elem = this; + + while ( elem.firstElementChild ) { + elem = elem.firstElementChild; + } + + return elem; + } ).append( this ); + } + + return this; + }, + + wrapInner: function( html ) { + if ( jQuery.isFunction( html ) ) { + return this.each( function( i ) { + jQuery( this ).wrapInner( html.call( this, i ) ); + } ); + } + + return this.each( function() { + var self = jQuery( this ), + contents = self.contents(); + + if ( contents.length ) { + contents.wrapAll( html ); + + } else { + self.append( html ); + } + } ); + }, + + wrap: function( html ) { + var isFunction = jQuery.isFunction( html ); + + return this.each( function( i ) { + jQuery( this ).wrapAll( isFunction ? html.call( this, i ) : html ); + } ); + }, + + unwrap: function( selector ) { + this.parent( selector ).not( "body" ).each( function() { + jQuery( this ).replaceWith( this.childNodes ); + } ); + return this; + } +} ); + + +jQuery.expr.pseudos.hidden = function( elem ) { + return !jQuery.expr.pseudos.visible( elem ); +}; +jQuery.expr.pseudos.visible = function( elem ) { + return !!( elem.offsetWidth || elem.offsetHeight || elem.getClientRects().length ); +}; + + + + +jQuery.ajaxSettings.xhr = function() { + try { + return new window.XMLHttpRequest(); + } catch ( e ) {} +}; + +var xhrSuccessStatus = { + + // File protocol always yields status code 0, assume 200 + 0: 200, + + // Support: IE <=9 only + // #1450: sometimes IE returns 1223 when it should be 204 + 1223: 204 + }, + xhrSupported = jQuery.ajaxSettings.xhr(); + +support.cors = !!xhrSupported && ( "withCredentials" in xhrSupported ); +support.ajax = xhrSupported = !!xhrSupported; + +jQuery.ajaxTransport( function( options ) { + var callback, errorCallback; + + // Cross domain only allowed if supported through XMLHttpRequest + if ( support.cors || xhrSupported && !options.crossDomain ) { + return { + send: function( headers, complete ) { + var i, + xhr = options.xhr(); + + xhr.open( + options.type, + options.url, + options.async, + options.username, + options.password + ); + + // Apply custom fields if provided + if ( options.xhrFields ) { + for ( i in options.xhrFields ) { + xhr[ i ] = options.xhrFields[ i ]; + } + } + + // Override mime type if needed + if ( options.mimeType && xhr.overrideMimeType ) { + xhr.overrideMimeType( options.mimeType ); + } + + // X-Requested-With header + // For cross-domain requests, seeing as conditions for a preflight are + // akin to a jigsaw puzzle, we simply never set it to be sure. + // (it can always be set on a per-request basis or even using ajaxSetup) + // For same-domain requests, won't change header if already provided. + if ( !options.crossDomain && !headers[ "X-Requested-With" ] ) { + headers[ "X-Requested-With" ] = "XMLHttpRequest"; + } + + // Set headers + for ( i in headers ) { + xhr.setRequestHeader( i, headers[ i ] ); + } + + // Callback + callback = function( type ) { + return function() { + if ( callback ) { + callback = errorCallback = xhr.onload = + xhr.onerror = xhr.onabort = xhr.onreadystatechange = null; + + if ( type === "abort" ) { + xhr.abort(); + } else if ( type === "error" ) { + + // Support: IE <=9 only + // On a manual native abort, IE9 throws + // errors on any property access that is not readyState + if ( typeof xhr.status !== "number" ) { + complete( 0, "error" ); + } else { + complete( + + // File: protocol always yields status 0; see #8605, #14207 + xhr.status, + xhr.statusText + ); + } + } else { + complete( + xhrSuccessStatus[ xhr.status ] || xhr.status, + xhr.statusText, + + // Support: IE <=9 only + // IE9 has no XHR2 but throws on binary (trac-11426) + // For XHR2 non-text, let the caller handle it (gh-2498) + ( xhr.responseType || "text" ) !== "text" || + typeof xhr.responseText !== "string" ? + { binary: xhr.response } : + { text: xhr.responseText }, + xhr.getAllResponseHeaders() + ); + } + } + }; + }; + + // Listen to events + xhr.onload = callback(); + errorCallback = xhr.onerror = callback( "error" ); + + // Support: IE 9 only + // Use onreadystatechange to replace onabort + // to handle uncaught aborts + if ( xhr.onabort !== undefined ) { + xhr.onabort = errorCallback; + } else { + xhr.onreadystatechange = function() { + + // Check readyState before timeout as it changes + if ( xhr.readyState === 4 ) { + + // Allow onerror to be called first, + // but that will not handle a native abort + // Also, save errorCallback to a variable + // as xhr.onerror cannot be accessed + window.setTimeout( function() { + if ( callback ) { + errorCallback(); + } + } ); + } + }; + } + + // Create the abort callback + callback = callback( "abort" ); + + try { + + // Do send the request (this may raise an exception) + xhr.send( options.hasContent && options.data || null ); + } catch ( e ) { + + // #14683: Only rethrow if this hasn't been notified as an error yet + if ( callback ) { + throw e; + } + } + }, + + abort: function() { + if ( callback ) { + callback(); + } + } + }; + } +} ); + + + + +// Prevent auto-execution of scripts when no explicit dataType was provided (See gh-2432) +jQuery.ajaxPrefilter( function( s ) { + if ( s.crossDomain ) { + s.contents.script = false; + } +} ); + +// Install script dataType +jQuery.ajaxSetup( { + accepts: { + script: "text/javascript, application/javascript, " + + "application/ecmascript, application/x-ecmascript" + }, + contents: { + script: /\b(?:java|ecma)script\b/ + }, + converters: { + "text script": function( text ) { + jQuery.globalEval( text ); + return text; + } + } +} ); + +// Handle cache's special case and crossDomain +jQuery.ajaxPrefilter( "script", function( s ) { + if ( s.cache === undefined ) { + s.cache = false; + } + if ( s.crossDomain ) { + s.type = "GET"; + } +} ); + +// Bind script tag hack transport +jQuery.ajaxTransport( "script", function( s ) { + + // This transport only deals with cross domain requests + if ( s.crossDomain ) { + var script, callback; + return { + send: function( _, complete ) { + script = jQuery( " + + + + + + + + + + + + + + +
+
+
+
+ +
+

Bookmarks

+
+

Note

+

You need to have configured the phpMyAdmin configuration storage for using bookmarks +feature.

+
+
+

Storing bookmarks

+

Any query you have executed can be stored as a bookmark on the page +where the results are displayed. You will find a button labeled +Bookmark this query just at the end of the page. As soon as you have +stored a bookmark, it is related to the database you run the query on. +You can now access a bookmark dropdown on each page, the query box +appears on for that database.

+
+
+

Variables inside bookmarks

+

You can also have, inside the query, placeholders for variables. +This is done by inserting into the query SQL comments between /* and +*/. Inside the comments, the special strings [VARIABLE{variable-number}] is used. +Be aware that the whole query minus the SQL comments must be +valid by itself, otherwise you won’t be able to store it as a bookmark. +Note also that the text ‘VARIABLE’ is case-sensitive.

+

When you execute the bookmark, everything typed into the Variables +input boxes on the query box page will replace the strings /*[VARIABLE{variable-number}]*/ in +your stored query.

+

Also remember, that everything else inside the /*[VARIABLE{variable-number}]*/ string for +your query will remain the way it is, but will be stripped of the /**/ +chars. So you can use:

+
/*, [VARIABLE1] AS myname */
+
+
+

which will be expanded to

+
, VARIABLE1 as myname
+
+
+

in your query, where VARIABLE1 is the string you entered in the Variable 1 input box.

+

A more complex example. Say you have stored +this query:

+
SELECT Name, Address FROM addresses WHERE 1 /* AND Name LIKE '%[VARIABLE1]%' */
+
+
+

Say, you now enter “phpMyAdmin” as the variable for the stored query, the full +query will be:

+
SELECT Name, Address FROM addresses WHERE 1 AND Name LIKE '%phpMyAdmin%'
+
+
+

NOTE THE ABSENCE OF SPACES inside the /**/ construct. Any spaces +inserted there will be later also inserted as spaces in your query and may lead +to unexpected results especially when using the variable expansion inside of a +“LIKE ‘’” expression.

+
+
+

Browsing a table using a bookmark

+

When a bookmark has the same name as the table, it will be used as the query when browsing +this table.

+ +
+
+ + +
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/php/apps/phpmyadmin49/doc/html/charts.html b/php/apps/phpmyadmin49/doc/html/charts.html new file mode 100644 index 00000000..d5fbf9c7 --- /dev/null +++ b/php/apps/phpmyadmin49/doc/html/charts.html @@ -0,0 +1,300 @@ + + + + + + + + Charts — phpMyAdmin 4.9.1 documentation + + + + + + + + + + + + + + + + + + + +
+
+
+
+ +
+

Charts

+
+

New in version 3.4.0.

+
+

Since phpMyAdmin version 3.4.0, you can easily generate charts from a SQL query +by clicking the “Display chart” link in the “Query results operations” area.

+_images/query_result_operations.png +

A window layer “Display chart” is shown in which you can customize the chart with the following options.

+
    +
  • Chart type: Allows you choose the type of the chart. Supported types are bar charts, column charts, line charts, spline charts, area charts, pie charts and timeline charts (only the chart types applicable for current series selection are offered).
  • +
  • X-axis: Allows to choose the field for the main axis.
  • +
  • Series: Allows to choose series for the chart. You can choose multiple series.
  • +
  • Title: Allows specifying a title for the chart which is displayed above the chart.
  • +
  • X-axis and Y-axis labels: Allows specifying labels for axes.
  • +
  • Start row and number of rows: Allows generating charts only for a specified number of rows of the results set.
  • +
+_images/chart.png +
+

Chart implementation

+

Charts in phpMyAdmin are drawn using jqPlot jQuery library.

+
+
+

Examples

+
+

Pie chart

+

Query results for a simple pie chart can be generated with:

+
SELECT 'Food' AS 'expense',
+   1250 AS 'amount' UNION
+SELECT 'Accommodation', 500 UNION
+SELECT 'Travel', 720 UNION
+SELECT 'Misc', 220
+
+
+

And the result of this query is:

+ ++++ + + + + + + + + + + + + + + + + + + + +
expenseamount
Food1250
Accommodation500
Travel720
Misc220
+

Choosing expense as the X-axis and amount in series:

+_images/pie_chart.png +
+
+

Bar and column chart

+

Both bar charts and column chats support stacking. Upon selecting one of these types a checkbox is displayed to select stacking.

+

Query results for a simple bar or column chart can be generated with:

+
SELECT
+   'ACADEMY DINOSAUR' AS 'title',
+   0.99 AS 'rental_rate',
+   20.99 AS 'replacement_cost' UNION
+SELECT 'ACE GOLDFINGER', 4.99, 12.99 UNION
+SELECT 'ADAPTATION HOLES', 2.99, 18.99 UNION
+SELECT 'AFFAIR PREJUDICE', 2.99, 26.99 UNION
+SELECT 'AFRICAN EGG', 2.99, 22.99
+
+
+

And the result of this query is:

+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
titlerental_ratereplacement_cost
ACADEMY DINOSAUR0.9920.99
ACE GOLDFINGER4.9912.99
ADAPTATION HOLES2.9918.99
AFFAIR PREJUDICE2.9926.99
AFRICAN EGG2.9922.99
+

Choosing title as the X-axis and rental_rate and replacement_cost as series:

+_images/column_chart.png +
+
+

Scatter chart

+

Scatter charts are useful in identifying the movement of one or more variable(s) compared to another variable.

+

Using the same data set from bar and column charts section and choosing replacement_cost as the X-axis and rental_rate in series:

+_images/scatter_chart.png +
+
+

Line, spline and timeline charts

+

These charts can be used to illustrate trends in underlying data. Spline charts draw smooth lines while timeline charts draw X-axis taking the distances between the dates/time into consideration.

+

Query results for a simple line, spline or timeline chart can be generated with:

+
SELECT
+   DATE('2006-01-08') AS 'date',
+   2056 AS 'revenue',
+   1378 AS 'cost' UNION
+SELECT DATE('2006-01-09'), 1898, 2301 UNION
+SELECT DATE('2006-01-15'), 1560, 600 UNION
+SELECT DATE('2006-01-17'), 3457, 1565
+
+
+

And the result of this query is:

+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + +
daterevenuecost
2016-01-0820561378
2006-01-0918982301
2006-01-151560600
2006-01-1734571565
+_images/line_chart.png +_images/spline_chart.png +_images/timeline_chart.png +
+
+
+ + +
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/php/apps/phpmyadmin49/doc/html/config.html b/php/apps/phpmyadmin49/doc/html/config.html new file mode 100644 index 00000000..e4ba653b --- /dev/null +++ b/php/apps/phpmyadmin49/doc/html/config.html @@ -0,0 +1,6211 @@ + + + + + + + + Configuration — phpMyAdmin 4.9.1 documentation + + + + + + + + + + + + + + + + + + +
+
+
+
+ +
+

Configuration

+

All configurable data is placed in config.inc.php in phpMyAdmin’s +toplevel directory. If this file does not exist, please refer to the +Installation section to create one. This file only needs to contain the +parameters you want to change from their corresponding default value in +libraries/config.default.php (this file is not inteded for changes).

+
+

See also

+

Examples for examples of configurations

+
+

If a directive is missing from your file, you can just add another line with +the file. This file is for over-writing the defaults; if you wish to use the +default value there’s no need to add a line here.

+

The parameters which relate to design (like colors) are placed in +themes/themename/layout.inc.php. You might also want to create +config.footer.inc.php and config.header.inc.php files to add +your site specific code to be included on start and end of each page.

+
+

Note

+

Some distributions (eg. Debian or Ubuntu) store config.inc.php in +/etc/phpmyadmin instead of within phpMyAdmin sources.

+
+
+

Warning

+

Mac users should note that if you are on a version before +Mac OS X, PHP does not seem to +like Mac end of lines character (\r). So +ensure you choose the option that allows to use the *nix end of line +character (\n) in your text editor before saving a script you have +modified.

+
+
+

Basic settings

+
+
+$cfg['PmaAbsoluteUri']
+
+++ + + + + + +
Type:string
Default value:''
+
+

Changed in version 4.6.5: This setting was not available in phpMyAdmin 4.6.0 - 4.6.4.

+
+

Sets here the complete URL (with full path) to your phpMyAdmin +installation’s directory. E.g. +https://www.example.net/path_to_your_phpMyAdmin_directory/. Note also +that the URL on most of web servers are case sensitive (even on +Windows). Don’t forget the trailing slash at the end.

+

Starting with version 2.3.0, it is advisable to try leaving this blank. In +most cases phpMyAdmin automatically detects the proper setting. Users of +port forwarding or complex reverse proxy setup might need to set this.

+

A good test is to browse a table, edit a row and save it. There should be +an error message if phpMyAdmin is having trouble auto–detecting the correct +value. If you get an error that this must be set or if the autodetect code +fails to detect your path, please post a bug report on our bug tracker so +we can improve the code.

+ +
+ +
+
+$cfg['PmaNoRelation_DisableWarning']
+
+++ + + + + + +
Type:boolean
Default value:false
+

Starting with version 2.3.0 phpMyAdmin offers a lot of features to +work with master / foreign – tables (see $cfg['Servers'][$i]['pmadb']).

+

If you tried to set this +up and it does not work for you, have a look on the Structure page +of one database where you would like to use it. You will find a link +that will analyze why those features have been disabled.

+

If you do not want to use those features set this variable to true to +stop this message from appearing.

+
+ +
+
+$cfg['AuthLog']
+
+++ + + + + + +
Type:string
Default value:'auto'
+
+

New in version 4.8.0: This is supported since phpMyAdmin 4.8.0.

+
+

Configure authentication logging destination. Failed (or all, depending on +$cfg['AuthLogSuccess']) authentication attempts will be +logged according to this directive:

+
+
auto
+
Let phpMyAdmin automatically choose between syslog and php.
+
syslog
+
Log using syslog, using AUTH facility, on most systems this ends up +in /var/log/auth.log.
+
php
+
Log into PHP error log.
+
sapi
+
Log into PHP SAPI logging.
+
/path/to/file
+
Any other value is treated as a filename and log entries are written there.
+
+
+

Note

+

When logging to a file, make sure its permissions are correctly set +for a web server user, the setup should closely match instructions +described in $cfg['TempDir']:

+
+
+ +
+
+$cfg['AuthLogSuccess']
+
+++ + + + + + +
Type:boolean
Default value:false
+
+

New in version 4.8.0: This is supported since phpMyAdmin 4.8.0.

+
+

Whether to log successful authentication attempts into +$cfg['AuthLog'].

+
+ +
+
+$cfg['SuhosinDisableWarning']
+
+++ + + + + + +
Type:boolean
Default value:false
+

A warning is displayed on the main page if Suhosin is detected.

+

You can set this parameter to true to stop this message from appearing.

+
+ +
+
+$cfg['LoginCookieValidityDisableWarning']
+
+++ + + + + + +
Type:boolean
Default value:false
+

A warning is displayed on the main page if the PHP parameter +session.gc_maxlifetime is lower than cookie validity configured in phpMyAdmin.

+

You can set this parameter to true to stop this message from appearing.

+
+ +
+
+$cfg['ServerLibraryDifference_DisableWarning']
+
+++ + + + + + +
Type:boolean
Default value:false
+
+

Deprecated since version 4.7.0: This setting was removed as the warning has been removed as well.

+
+

A warning is displayed on the main page if there is a difference +between the MySQL library and server version.

+

You can set this parameter to true to stop this message from appearing.

+
+ +
+
+$cfg['ReservedWordDisableWarning']
+
+++ + + + + + +
Type:boolean
Default value:false
+

This warning is displayed on the Structure page of a table if one or more +column names match with words which are MySQL reserved.

+

If you want to turn off this warning, you can set it to true and +warning will no longer be displayed.

+
+ +
+
+$cfg['TranslationWarningThreshold']
+
+++ + + + + + +
Type:integer
Default value:80
+

Show warning about incomplete translations on certain threshold.

+
+ +
+
+$cfg['SendErrorReports']
+
+++ + + + + + +
Type:string
Default value:'ask'
+

Sets the default behavior for JavaScript error reporting.

+

Whenever an error is detected in the JavaScript execution, an error report +may be sent to the phpMyAdmin team if the user agrees.

+

The default setting of 'ask' will ask the user everytime there is a new +error report. However you can set this parameter to 'always' to send error +reports without asking for confirmation or you can set it to 'never' to +never send error reports.

+

This directive is available both in the configuration file and in users +preferences. If the person in charge of a multi-user installation prefers +to disable this feature for all users, a value of 'never' should be +set, and the $cfg['UserprefsDisallow'] directive should +contain 'SendErrorReports' in one of its array values.

+
+ +
+
+$cfg['ConsoleEnterExecutes']
+
+++ + + + + + +
Type:boolean
Default value:false
+

Setting this to true allows the user to execute queries by pressing Enter +instead of Ctrl+Enter. A new line can be inserted by pressing Shift + Enter.

+

The behaviour of the console can be temporarily changed using console’s +settings interface.

+
+ +
+
+$cfg['AllowThirdPartyFraming']
+
+++ + + + + + +
Type:boolean
Default value:false
+

Setting this to true allows phpMyAdmin to be included inside a frame, +and is a potential security hole allowing cross-frame scripting attacks or +clickjacking.

+
+ +
+
+

Server connection settings

+
+
+$cfg['Servers']
+
+++ + + + + + +
Type:array
Default value:one server array with settings listed below
+

Since version 1.4.2, phpMyAdmin supports the administration of multiple +MySQL servers. Therefore, a $cfg['Servers']-array has been +added which contains the login information for the different servers. The +first $cfg['Servers'][$i]['host'] contains the hostname of +the first server, the second $cfg['Servers'][$i]['host'] +the hostname of the second server, etc. In +libraries/config.default.php, there is only one section for server +definition, however you can put as many as you need in +config.inc.php, copy that block or needed parts (you don’t have to +define all settings, just those you need to change).

+
+

Note

+

The $cfg['Servers'] array starts with +$cfg[‘Servers’][1]. Do not use $cfg[‘Servers’][0]. If you want more +than one server, just copy following section (including $i +incrementation) serveral times. There is no need to define full server +array, just define values you need to change.

+
+
+ +
+
+$cfg['Servers'][$i]['host']
+
+++ + + + + + +
Type:string
Default value:'localhost'
+

The hostname or IP address of your $i-th MySQL-server. E.g. +localhost.

+

Possible values are:

+
    +
  • hostname, e.g., 'localhost' or 'mydb.example.org'
  • +
  • IP address, e.g., '127.0.0.1' or '192.168.10.1'
  • +
  • IPv6 address, e.g. 2001:cdba:0000:0000:0000:0000:3257:9652
  • +
  • dot - '.', i.e., use named pipes on windows systems
  • +
  • empty - '', disables this server
  • +
+
+

Note

+

The hostname localhost is handled specially by MySQL and it uses +the socket based connection protocol. To use TCP/IP networking, use an +IP address or hostname such as 127.0.0.1 or db.example.com. You +can configure the path to the socket with +$cfg['Servers'][$i]['socket'].

+
+ +
+ +
+
+$cfg['Servers'][$i]['port']
+
+++ + + + + + +
Type:string
Default value:''
+

The port-number of your $i-th MySQL-server. Default is 3306 (leave +blank).

+
+

Note

+

If you use localhost as the hostname, MySQL ignores this port number +and connects with the socket, so if you want to connect to a port +different from the default port, use 127.0.0.1 or the real hostname +in $cfg['Servers'][$i]['host'].

+
+ +
+ +
+
+$cfg['Servers'][$i]['socket']
+
+++ + + + + + +
Type:string
Default value:''
+

The path to the socket to use. Leave blank for default. To determine +the correct socket, check your MySQL configuration or, using the +mysql command–line client, issue the status command. Among the +resulting information displayed will be the socket used.

+
+

Note

+

This takes effect only if $cfg['Servers'][$i]['host'] is set +to localhost.

+
+ +
+ +
+
+$cfg['Servers'][$i]['ssl']
+
+++ + + + + + +
Type:boolean
Default value:false
+

Whether to enable SSL for the connection between phpMyAdmin and the MySQL +server to secure the connection.

+

When using the 'mysql' extension, +none of the remaining 'ssl...' configuration options apply.

+

We strongly recommend the 'mysqli' extension when using this option.

+ +
+ +
+
+$cfg['Servers'][$i]['ssl_key']
+
+++ + + + + + +
Type:string
Default value:NULL
+

Path to the client key file when using SSL for connecting to the MySQL +server. This is used to authenticate the client to the server.

+

For example:

+
$cfg['Servers'][$i]['ssl_key'] = '/etc/mysql/server-key.pem';
+
+
+ +
+ +
+
+$cfg['Servers'][$i]['ssl_cert']
+
+++ + + + + + +
Type:string
Default value:NULL
+

Path to the client certificate file when using SSL for connecting to the +MySQL server. This is used to authenticate the client to the server.

+ +
+ +
+
+$cfg['Servers'][$i]['ssl_ca']
+
+++ + + + + + +
Type:string
Default value:NULL
+

Path to the CA file when using SSL for connecting to the MySQL server.

+ +
+ +
+
+$cfg['Servers'][$i]['ssl_ca_path']
+
+++ + + + + + +
Type:string
Default value:NULL
+

Directory containing trusted SSL CA certificates in PEM format.

+ +
+ +
+
+$cfg['Servers'][$i]['ssl_ciphers']
+
+++ + + + + + +
Type:string
Default value:NULL
+

List of allowable ciphers for SSL connections to the MySQL server.

+ +
+ +
+
+$cfg['Servers'][$i]['ssl_verify']
+
+++ + + + + + +
Type:boolean
Default value:true
+
+

New in version 4.6.0: This is supported since phpMyAdmin 4.6.0.

+
+

If your PHP install uses the MySQL Native Driver (mysqlnd), your +MySQL server is 5.6 or later, and your SSL certificate is self-signed, +there is a chance your SSL connection will fail due to validation. +Setting this to false will disable the validation check.

+

Since PHP 5.6.0 it also verifies whether server name matches CN of its +certificate. There is currently no way to disable just this check without +disabling complete SSL verification.

+
+

Warning

+

Disabling the certificate verification defeats purpose of using SSL. +This will make the connection vulnerable to man in the middle attacks.

+
+
+

Note

+

This flag only works with PHP 5.6.16 or later.

+
+ +
+ +
+
+$cfg['Servers'][$i]['connect_type']
+
+++ + + + + + +
Type:string
Default value:'tcp'
+
+

Deprecated since version 4.7.0: This setting is no longer used as of 4.7.0, since MySQL decides the +connection type based on host, so it could lead to unexpected results. +Please set $cfg['Servers'][$i]['host'] accordingly +instead.

+
+

What type connection to use with the MySQL server. Your options are +'socket' and 'tcp'. It defaults to tcp as that is nearly guaranteed +to be available on all MySQL servers, while sockets are not supported on +some platforms. To use the socket mode, your MySQL server must be on the +same machine as the Web server.

+
+ +
+
+$cfg['Servers'][$i]['compress']
+
+++ + + + + + +
Type:boolean
Default value:false
+

Whether to use a compressed protocol for the MySQL server connection +or not (experimental).

+
+ +
+
+$cfg['Servers'][$i]['controlhost']
+
+++ + + + + + +
Type:string
Default value:''
+

Permits to use an alternate host to hold the configuration storage +data.

+ +
+ +
+
+$cfg['Servers'][$i]['controlport']
+
+++ + + + + + +
Type:string
Default value:''
+

Permits to use an alternate port to connect to the host that +holds the configuration storage.

+ +
+ +
+
+$cfg['Servers'][$i]['controluser']
+
+++ + + + + + +
Type:string
Default value:''
+
+ +
+
+$cfg['Servers'][$i]['controlpass']
+
+++ + + + + + +
Type:string
Default value:''
+

This special account is used to access phpMyAdmin configuration storage. +You don’t need it in single user case, but if phpMyAdmin is shared it +is recommended to give access to phpMyAdmin configuration storage only to this user +and configure phpMyAdmin to use it. All users will then be able to use +the features without need to have direct access to phpMyAdmin configuration storage.

+
+

Changed in version 2.2.5: those were called stduser and stdpass

+
+ +
+ +
+
+$cfg['Servers'][$i]['control_*']
+
+++ + + + +
Type:mixed
+
+

New in version 4.7.0.

+
+

You can change any MySQL connection setting for control link (used to +access phpMyAdmin configuration storage) using configuration prefixed with control_.

+

This can be used to change any aspect of the control connection, which by +default uses same parameters as the user one.

+

For example you can configure SSL for the control connection:

+
// Enable SSL
+$cfg['Servers'][$i]['control_ssl'] = true;
+// Client secret key
+$cfg['Servers'][$i]['control_ssl_key'] = '../client-key.pem';
+// Client certificate
+$cfg['Servers'][$i]['control_ssl_cert'] = '../client-cert.pem';
+// Server certification authority
+$cfg['Servers'][$i]['control_ssl_ca'] = '../server-ca.pem';
+
+
+ +
+ +
+
+$cfg['Servers'][$i]['auth_type']
+
+++ + + + + + +
Type:string
Default value:'cookie'
+

Whether config or cookie or HTTP or signon authentication should be +used for this server.

+
    +
  • ‘config’ authentication ($auth_type = 'config') is the plain old +way: username and password are stored in config.inc.php.
  • +
  • ‘cookie’ authentication mode ($auth_type = 'cookie') allows you to +log in as any valid MySQL user with the help of cookies.
  • +
  • ‘http’ authentication allows you to log in as any +valid MySQL user via HTTP-Auth.
  • +
  • ‘signon’ authentication mode ($auth_type = 'signon') allows you to +log in from prepared PHP session data or using supplied PHP script.
  • +
+ +
+ +
+
+$cfg['Servers'][$i]['auth_http_realm']
+
+++ + + + + + +
Type:string
Default value:''
+

When using auth_type = http, this field allows to define a custom +HTTP Basic Auth Realm which will be displayed to the user. If not +explicitly specified in your configuration, a string combined of +“phpMyAdmin ” and either $cfg['Servers'][$i]['verbose'] or +$cfg['Servers'][$i]['host'] will be used.

+
+ +
+
+$cfg['Servers'][$i]['user']
+
+++ + + + + + +
Type:string
Default value:'root'
+
+ +
+
+$cfg['Servers'][$i]['password']
+
+++ + + + + + +
Type:string
Default value:''
+

When using $cfg['Servers'][$i]['auth_type'] set to +‘config’, this is the user/password-pair which phpMyAdmin will use to +connect to the MySQL server. This user/password pair is not needed when +HTTP or cookie authentication is used +and should be empty.

+
+ +
+
+$cfg['Servers'][$i]['nopassword']
+
+++ + + + + + +
Type:boolean
Default value:false
+
+

Deprecated since version 4.7.0: This setting was removed as it can produce unexpected results.

+
+

Allow attempt to log in without password when a login with password +fails. This can be used together with http authentication, when +authentication is done some other way and phpMyAdmin gets user name +from auth and uses empty password for connecting to MySQL. Password +login is still tried first, but as fallback, no password method is +tried.

+
+ +
+
+$cfg['Servers'][$i]['only_db']
+
+++ + + + + + +
Type:string or array
Default value:''
+

If set to a (an array of) database name(s), only this (these) +database(s) will be shown to the user. Since phpMyAdmin 2.2.1, +this/these database(s) name(s) may contain MySQL wildcards characters +(“_” and “%”): if you want to use literal instances of these +characters, escape them (I.E. use 'my\_db' and not 'my_db').

+

This setting is an efficient way to lower the server load since the +latter does not need to send MySQL requests to build the available +database list. But it does not replace the privileges rules of the +MySQL database server. If set, it just means only these databases +will be displayed but not that all other databases can’t be used.

+

An example of using more that one database:

+
$cfg['Servers'][$i]['only_db'] = array('db1', 'db2');
+
+
+
+

Changed in version 4.0.0: Previous versions permitted to specify the display order of +the database names via this directive.

+
+
+ +
+
+$cfg['Servers'][$i]['hide_db']
+
+++ + + + + + +
Type:string
Default value:''
+

Regular expression for hiding some databases from unprivileged users. +This only hides them from listing, but a user is still able to access +them (using, for example, the SQL query area). To limit access, use +the MySQL privilege system. For example, to hide all databases +starting with the letter “a”, use

+
$cfg['Servers'][$i]['hide_db'] = '^a';
+
+
+

and to hide both “db1” and “db2” use

+
$cfg['Servers'][$i]['hide_db'] = '^(db1|db2)$';
+
+
+

More information on regular expressions can be found in the PCRE +pattern syntax portion +of the PHP reference manual.

+
+ +
+
+$cfg['Servers'][$i]['verbose']
+
+++ + + + + + +
Type:string
Default value:''
+

Only useful when using phpMyAdmin with multiple server entries. If +set, this string will be displayed instead of the hostname in the +pull-down menu on the main page. This can be useful if you want to +show only certain databases on your system, for example. For HTTP +auth, all non-US-ASCII characters will be stripped.

+
+ +
+
+$cfg['Servers'][$i]['extension']
+
+++ + + + + + +
Type:string
Default value:'mysqli'
+

The PHP MySQL extension to use (mysql or mysqli).

+

It is recommended to use mysqli in all installations.

+
+ +
+
+$cfg['Servers'][$i]['pmadb']
+
+++ + + + + + +
Type:string
Default value:''
+

The name of the database containing the phpMyAdmin configuration +storage.

+

See the phpMyAdmin configuration storage section in this document to see the benefits of +this feature, and for a quick way of creating this database and the needed +tables.

+

If you are the only user of this phpMyAdmin installation, you can use your +current database to store those special tables; in this case, just put your +current database name in $cfg['Servers'][$i]['pmadb']. For a +multi-user installation, set this parameter to the name of your central +database containing the phpMyAdmin configuration storage.

+
+ +
+
+$cfg['Servers'][$i]['bookmarktable']
+
+++ + + + + + +
Type:string or false
Default value:''
+

Since release 2.2.0 phpMyAdmin allows users to bookmark queries. This +can be useful for queries you often run. To allow the usage of this +functionality:

+ +

This feature can be disabled by setting the configuration to false.

+
+ +
+
+$cfg['Servers'][$i]['relation']
+
+++ + + + + + +
Type:string or false
Default value:''
+

Since release 2.2.4 you can describe, in a special ‘relation’ table, +which column is a key in another table (a foreign key). phpMyAdmin +currently uses this to:

+ +

The keys can be numeric or character.

+

To allow the usage of this functionality:

+
    +
  • set up $cfg['Servers'][$i]['pmadb'] and the phpMyAdmin configuration storage
  • +
  • put the relation table name in $cfg['Servers'][$i]['relation']
  • +
  • now as normal user open phpMyAdmin and for each one of your tables +where you want to use this feature, click Structure/Relation view/ +and choose foreign columns.
  • +
+

This feature can be disabled by setting the configuration to false.

+
+

Note

+

In the current version, master_db must be the same as foreign_db. +Those columns have been put in future development of the cross-db +relations.

+
+
+ +
+
+$cfg['Servers'][$i]['table_info']
+
+++ + + + + + +
Type:string or false
Default value:''
+

Since release 2.3.0 you can describe, in a special ‘table_info’ +table, which column is to be displayed as a tool-tip when moving the +cursor over the corresponding key. This configuration variable will +hold the name of this special table. To allow the usage of this +functionality:

+ +

This feature can be disabled by setting the configuration to false.

+ +
+ +
+
+$cfg['Servers'][$i]['table_coords']
+
+++ + + + + + +
Type:string or false
Default value:''
+

The designer feature can save your page layout; by pressing the “Save page” or “Save page as” +button in the expanding designer menu, you can customize the layout and have it loaded the next +time you use the designer. That layout is stored in this table. Furthermore, this table is also +required for using the PDF relation export feature, see +$cfg['Servers'][$i]['pdf_pages'] for additional details.

+
+ +
+
+$cfg['Servers'][$i]['pdf_pages']
+
+++ + + + + + +
Type:string or false
Default value:''
+

Since release 2.3.0 you can have phpMyAdmin create PDF pages +showing the relations between your tables. Further, the designer interface +permits visually managing the relations. To do this it needs two tables +“pdf_pages” (storing information about the available PDF pages) +and “table_coords” (storing coordinates where each table will be placed on +a PDF schema output). You must be using the “relation” feature.

+

To allow the usage of this functionality:

+ +

This feature can be disabled by setting either of the configurations to false.

+ +
+ +
+
+$cfg['Servers'][$i]['column_info']
+
+++ + + + + + +
Type:string or false
Default value:''
+

This part requires a content update! Since release 2.3.0 you can +store comments to describe each column for each table. These will then +be shown on the “printview”.

+

Starting with release 2.5.0, comments are consequently used on the table +property pages and table browse view, showing up as tool-tips above the +column name (properties page) or embedded within the header of table in +browse view. They can also be shown in a table dump. Please see the +relevant configuration directives later on.

+

Also new in release 2.5.0 is a MIME- transformation system which is also +based on the following table structure. See Transformations for +further information. To use the MIME- transformation system, your +column_info table has to have the three new columns ‘mimetype’, +‘transformation’, ‘transformation_options’.

+

Starting with release 4.3.0, a new input-oriented transformation system +has been introduced. Also, backward compatibility code used in the old +transformations system was removed. As a result, an update to column_info +table is necessary for previous transformations and the new input-oriented +transformation system to work. phpMyAdmin will upgrade it automatically +for you by analyzing your current column_info table structure. +However, if something goes wrong with the auto-upgrade then you can +use the SQL script found in ./sql/upgrade_column_info_4_3_0+.sql +to upgrade it manually.

+

To allow the usage of this functionality:

+
    +
  • set up $cfg['Servers'][$i]['pmadb'] and the phpMyAdmin configuration storage

    +
  • +
  • put the table name in $cfg['Servers'][$i]['column_info'] (e.g. +pma__column_info)

    +
  • +
  • to update your PRE-2.5.0 Column_comments table use this: and +remember that the Variable in config.inc.php has been renamed from +$cfg['Servers'][$i]['column_comments'] to +$cfg['Servers'][$i]['column_info']

    +
    ALTER TABLE `pma__column_comments`
    +ADD `mimetype` VARCHAR( 255 ) NOT NULL,
    +ADD `transformation` VARCHAR( 255 ) NOT NULL,
    +ADD `transformation_options` VARCHAR( 255 ) NOT NULL;
    +
    +
    +
  • +
  • to update your PRE-4.3.0 Column_info table manually use this +./sql/upgrade_column_info_4_3_0+.sql SQL script.

    +
  • +
+

This feature can be disabled by setting the configuration to false.

+
+

Note

+

For auto-upgrade functionality to work, your +$cfg['Servers'][$i]['controluser'] must have ALTER privilege on +phpmyadmin database. See the MySQL documentation for GRANT on how to +GRANT privileges to a user.

+
+
+ +
+
+$cfg['Servers'][$i]['history']
+
+++ + + + + + +
Type:string or false
Default value:''
+

Since release 2.5.0 you can store your SQL history, which means all +queries you entered manually into the phpMyAdmin interface. If you don’t +want to use a table-based history, you can use the JavaScript-based +history.

+

Using that, all your history items are deleted when closing the window. +Using $cfg['QueryHistoryMax'] you can specify an amount of +history items you want to have on hold. On every login, this list gets cut +to the maximum amount.

+

The query history is only available if JavaScript is enabled in +your browser.

+

To allow the usage of this functionality:

+ +

This feature can be disabled by setting the configuration to false.

+
+ +
+
+$cfg['Servers'][$i]['recent']
+
+++ + + + + + +
Type:string or false
Default value:''
+

Since release 3.5.0 you can show recently used tables in the +navigation panel. It helps you to jump across table directly, without +the need to select the database, and then select the table. Using +$cfg['NumRecentTables'] you can configure the maximum number +of recent tables shown. When you select a table from the list, it will jump to +the page specified in $cfg['NavigationTreeDefaultTabTable'].

+

Without configuring the storage, you can still access the recently used tables, +but it will disappear after you logout.

+

To allow the usage of this functionality persistently:

+ +

This feature can be disabled by setting the configuration to false.

+
+ +
+
+$cfg['Servers'][$i]['favorite']
+
+++ + + + + + +
Type:string or false
Default value:''
+

Since release 4.2.0 you can show a list of selected tables in the +navigation panel. It helps you to jump to the table directly, without +the need to select the database, and then select the table. When you +select a table from the list, it will jump to the page specified in +$cfg['NavigationTreeDefaultTabTable'].

+

You can add tables to this list or remove tables from it in database +structure page by clicking on the star icons next to table names. Using +$cfg['NumFavoriteTables'] you can configure the maximum +number of favorite tables shown.

+

Without configuring the storage, you can still access the favorite tables, +but it will disappear after you logout.

+

To allow the usage of this functionality persistently:

+ +

This feature can be disabled by setting the configuration to false.

+
+ +
+
+$cfg['Servers'][$i]['table_uiprefs']
+
+++ + + + + + +
Type:string or false
Default value:''
+

Since release 3.5.0 phpMyAdmin can be configured to remember several +things (sorted column $cfg['RememberSorting'], column order, +and column visibility from a database table) for browsing tables. Without +configuring the storage, these features still can be used, but the values will +disappear after you logout.

+

To allow the usage of these functionality persistently:

+ +

This feature can be disabled by setting the configuration to false.

+
+ +
+
+$cfg['Servers'][$i]['users']
+
+++ + + + + + +
Type:string or false
Default value:''
+
+ +
+
+$cfg['Servers'][$i]['usergroups']
+
+++ + + + + + +
Type:string or false
Default value:''
+

Since release 4.1.0 you can create different user groups with menu items +attached to them. Users can be assigned to these groups and the logged in +user would only see menu items configured to the usergroup he is assigned to. +To do this it needs two tables “usergroups” (storing allowed menu items for each +user group) and “users” (storing users and their assignments to user groups).

+

To allow the usage of this functionality:

+ +

This feature can be disabled by setting either of the configurations to false.

+ +
+ +
+
+$cfg['Servers'][$i]['navigationhiding']
+
+++ + + + + + +
Type:string or false
Default value:''
+

Since release 4.1.0 you can hide/show items in the navigation tree.

+

To allow the usage of this functionality:

+ +

This feature can be disabled by setting the configuration to false.

+
+ +
+
+$cfg['Servers'][$i]['central_columns']
+
+++ + + + + + +
Type:string or false
Default value:''
+

Since release 4.3.0 you can have a central list of columns per database. +You can add/remove columns to the list as per your requirement. These columns +in the central list will be available to use while you create a new column for +a table or create a table itself. You can select a column from central list +while creating a new column, it will save you from writing the same column definition +over again or from writing different names for similar column.

+

To allow the usage of this functionality:

+ +

This feature can be disabled by setting the configuration to false.

+
+ +
+
+$cfg['Servers'][$i]['designer_settings']
+
+++ + + + + + +
Type:string or false
Default value:''
+

Since release 4.5.0 your designer settings can be remembered. +Your choice regarding ‘Angular/Direct Links’, ‘Snap to Grid’, ‘Toggle Relation Lines’, +‘Small/Big All’, ‘Move Menu’ and ‘Pin Text’ can be remembered persistently.

+

To allow the usage of this functionality:

+ +

This feature can be disabled by setting the configuration to false.

+
+ +
+
+$cfg['Servers'][$i]['savedsearches']
+
+++ + + + + + +
Type:string or false
Default value:''
+

Since release 4.2.0 you can save and load query-by-example searches from the Database > Query panel.

+

To allow the usage of this functionality:

+ +

This feature can be disabled by setting the configuration to false.

+
+ +
+
+$cfg['Servers'][$i]['export_templates']
+
+++ + + + + + +
Type:string or false
Default value:''
+

Since release 4.5.0 you can save and load export templates.

+

To allow the usage of this functionality:

+ +

This feature can be disabled by setting the configuration to false.

+
+ +
+
+$cfg['Servers'][$i]['tracking']
+
+++ + + + + + +
Type:string or false
Default value:''
+

Since release 3.3.x a tracking mechanism is available. It helps you to +track every SQL command which is +executed by phpMyAdmin. The mechanism supports logging of data +manipulation and data definition statements. After enabling it you can +create versions of tables.

+

The creation of a version has two effects:

+
    +
  • phpMyAdmin saves a snapshot of the table, including structure and +indexes.
  • +
  • phpMyAdmin logs all commands which change the structure and/or data of +the table and links these commands with the version number.
  • +
+

Of course you can view the tracked changes. On the Tracking +page a complete report is available for every version. For the report you +can use filters, for example you can get a list of statements within a date +range. When you want to filter usernames you can enter * for all names or +you enter a list of names separated by ‘,’. In addition you can export the +(filtered) report to a file or to a temporary database.

+

To allow the usage of this functionality:

+ +

This feature can be disabled by setting the configuration to false.

+
+ +
+
+$cfg['Servers'][$i]['tracking_version_auto_create']
+
+++ + + + + + +
Type:boolean
Default value:false
+

Whether the tracking mechanism creates versions for tables and views +automatically.

+

If this is set to true and you create a table or view with

+
    +
  • CREATE TABLE ...
  • +
  • CREATE VIEW ...
  • +
+

and no version exists for it, the mechanism will create a version for +you automatically.

+
+ +
+
+$cfg['Servers'][$i]['tracking_default_statements']
+
+++ + + + + + +
Type:string
Default value:'CREATE TABLE,ALTER TABLE,DROP TABLE,RENAME TABLE,CREATE INDEX,DROP INDEX,INSERT,UPDATE,DELETE,TRUNCATE,REPLACE,CREATE VIEW,ALTER VIEW,DROP VIEW,CREATE DATABASE,ALTER DATABASE,DROP DATABASE'
+

Defines the list of statements the auto-creation uses for new +versions.

+
+ +
+
+$cfg['Servers'][$i]['tracking_add_drop_view']
+
+++ + + + + + +
Type:boolean
Default value:true
+

Whether a DROP VIEW IF EXISTS statement will be added as first line to +the log when creating a view.

+
+ +
+
+$cfg['Servers'][$i]['tracking_add_drop_table']
+
+++ + + + + + +
Type:boolean
Default value:true
+

Whether a DROP TABLE IF EXISTS statement will be added as first line +to the log when creating a table.

+
+ +
+
+$cfg['Servers'][$i]['tracking_add_drop_database']
+
+++ + + + + + +
Type:boolean
Default value:true
+

Whether a DROP DATABASE IF EXISTS statement will be added as first +line to the log when creating a database.

+
+ +
+
+$cfg['Servers'][$i]['userconfig']
+
+++ + + + + + +
Type:string or false
Default value:''
+

Since release 3.4.x phpMyAdmin allows users to set most preferences by +themselves and store them in the database.

+

If you don’t allow for storing preferences in +$cfg['Servers'][$i]['pmadb'], users can still personalize +phpMyAdmin, but settings will be saved in browser’s local storage, or, it +is is unavailable, until the end of session.

+

To allow the usage of this functionality:

+ +

This feature can be disabled by setting the configuration to false.

+
+ +
+
+$cfg['Servers'][$i]['MaxTableUiprefs']
+
+++ + + + + + +
Type:integer
Default value:100
+

Maximum number of rows saved in +$cfg['Servers'][$i]['table_uiprefs'] table.

+

When tables are dropped or renamed, +$cfg['Servers'][$i]['table_uiprefs'] may contain invalid data +(referring to tables which no longer exist). We only keep this number of newest +rows in $cfg['Servers'][$i]['table_uiprefs'] and automatically +delete older rows.

+
+ +
+
+$cfg['Servers'][$i]['SessionTimeZone']
+
+++ + + + + + +
Type:string
Default value:''
+

Sets the time zone used by phpMyAdmin. Leave blank to use the time zone of your +database server. Possible values are explained at +https://dev.mysql.com/doc/refman/5.7/en/time-zone-support.html

+

This is useful when your database server uses a time zone which is different from the +time zone you want to use in phpMyAdmin.

+
+ +
+
+$cfg['Servers'][$i]['AllowRoot']
+
+++ + + + + + +
Type:boolean
Default value:true
+

Whether to allow root access. This is just a shortcut for the +$cfg['Servers'][$i]['AllowDeny']['rules'] below.

+
+ +
+
+$cfg['Servers'][$i]['AllowNoPassword']
+
+++ + + + + + +
Type:boolean
Default value:false
+

Whether to allow logins without a password. The default value of +false for this parameter prevents unintended access to a MySQL +server with was left with an empty password for root or on which an +anonymous (blank) user is defined.

+
+ +
+
+$cfg['Servers'][$i]['AllowDeny']['order']
+
+++ + + + + + +
Type:string
Default value:''
+

If your rule order is empty, then IP +authorization is disabled.

+

If your rule order is set to +'deny,allow' then the system applies all deny rules followed by +allow rules. Access is allowed by default. Any client which does not +match a Deny command or does match an Allow command will be allowed +access to the server.

+

If your rule order is set to 'allow,deny' +then the system applies all allow rules followed by deny rules. Access +is denied by default. Any client which does not match an Allow +directive or does match a Deny directive will be denied access to the +server.

+

If your rule order is set to 'explicit', authorization is +performed in a similar fashion to rule order ‘deny,allow’, with the +added restriction that your host/username combination must be +listed in the allow rules, and not listed in the deny rules. This +is the most secure means of using Allow/Deny rules, and was +available in Apache by specifying allow and deny rules without setting +any order.

+

Please also see $cfg['TrustedProxies'] for +detecting IP address behind proxies.

+
+ +
+
+$cfg['Servers'][$i]['AllowDeny']['rules']
+
+++ + + + + + +
Type:array of strings
Default value:array()
+

The general format for the rules is as such:

+
<'allow' | 'deny'> <username> [from] <ipmask>
+
+
+

If you wish to match all users, it is possible to use a '%' as a +wildcard in the username field.

+

There are a few shortcuts you can +use in the ipmask field as well (please note that those containing +SERVER_ADDRESS might not be available on all webservers):

+
'all' -> 0.0.0.0/0
+'localhost' -> 127.0.0.1/8
+'localnetA' -> SERVER_ADDRESS/8
+'localnetB' -> SERVER_ADDRESS/16
+'localnetC' -> SERVER_ADDRESS/24
+
+
+

Having an empty rule list is equivalent to either using 'allow % +from all' if your rule order is set to 'deny,allow' or 'deny % +from all' if your rule order is set to 'allow,deny' or +'explicit'.

+

For the IP address matching +system, the following work:

+
    +
  • xxx.xxx.xxx.xxx (an exact IP address)
  • +
  • xxx.xxx.xxx.[yyy-zzz] (an IP address range)
  • +
  • xxx.xxx.xxx.xxx/nn (CIDR, Classless Inter-Domain Routing type IP addresses)
  • +
+

But the following does not work:

+
    +
  • xxx.xxx.xxx.xx[yyy-zzz] (partial IP address range)
  • +
+

For IPv6 addresses, the following work:

+
    +
  • xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx (an exact IPv6 address)
  • +
  • xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:[yyyy-zzzz] (an IPv6 address range)
  • +
  • xxxx:xxxx:xxxx:xxxx/nn (CIDR, Classless Inter-Domain Routing type IPv6 addresses)
  • +
+

But the following does not work:

+
    +
  • xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xx[yyy-zzz] (partial IPv6 address range)
  • +
+

Examples:

+
$cfg['Servers'][$i]['AllowDeny']['order'] = 'allow,deny';
+$cfg['Servers'][$i]['AllowDeny']['rules'] = array('allow bob from all');
+// Allow only 'bob' to connect from any host
+
+$cfg['Servers'][$i]['AllowDeny']['order'] = 'allow,deny';
+$cfg['Servers'][$i]['AllowDeny']['rules'] = array('allow mary from 192.168.100.[50-100]');
+// Allow only 'mary' to connect from host 192.168.100.50 through 192.168.100.100
+
+$cfg['Servers'][$i]['AllowDeny']['order'] = 'allow,deny';
+$cfg['Servers'][$i]['AllowDeny']['rules'] = array('allow % from 192.168.[5-6].10');
+// Allow any user to connect from host 192.168.5.10 or 192.168.6.10
+
+$cfg['Servers'][$i]['AllowDeny']['order'] = 'allow,deny';
+$cfg['Servers'][$i]['AllowDeny']['rules'] = array('allow root from 192.168.5.50','allow % from 192.168.6.10');
+// Allow any user to connect from 192.168.6.10, and additionally allow root to connect from 192.168.5.50
+
+
+
+ +
+
+$cfg['Servers'][$i]['DisableIS']
+
+++ + + + + + +
Type:boolean
Default value:false
+

Disable using INFORMATION_SCHEMA to retrieve information (use +SHOW commands instead), because of speed issues when many +databases are present.

+
+

Note

+

Enabling this option might give you a big performance boost on older +MySQL servers.

+
+
+ +
+
+$cfg['Servers'][$i]['SignonScript']
+
+++ + + + + + +
Type:string
Default value:''
+
+

New in version 3.5.0.

+
+

Name of PHP script to be sourced and executed to obtain login +credentials. This is alternative approach to session based single +signon. The script has to provide a function called +get_login_credentials which returns list of username and +password, accepting single parameter of existing username (can be +empty). See examples/signon-script.php for an example:

+
<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Single signon for phpMyAdmin
+ *
+ * This is just example how to use script based single signon with
+ * phpMyAdmin, it is not intended to be perfect code and look, only
+ * shows how you can integrate this functionality in your application.
+ *
+ * @package    PhpMyAdmin
+ * @subpackage Example
+ */
+
+
+/**
+ * This function returns username and password.
+ *
+ * It can optionally use configured username as parameter.
+ *
+ * @param string $user User name
+ *
+ * @return array
+ */
+function get_login_credentials($user)
+{
+    /* Optionally we can use passed username */
+    if (!empty($user)) {
+        return array($user, 'password');
+    }
+
+    /* Here we would retrieve the credentials */
+    $credentials = array('root', '');
+
+    return $credentials;
+}
+
+
+ +
+ +
+
+$cfg['Servers'][$i]['SignonSession']
+
+++ + + + + + +
Type:string
Default value:''
+

Name of session which will be used for signon authentication method. +You should use something different than phpMyAdmin, because this +is session which phpMyAdmin uses internally. Takes effect only if +$cfg['Servers'][$i]['SignonScript'] is not configured.

+ +
+ +
+
+$cfg['Servers'][$i]['SignonCookieParams']
+
+++ + + + + + +
Type:array
Default value:array()
+
+

New in version 4.7.0.

+
+

An associative array of session cookie parameters of other authentication system. +It is not needed if the other system doesn’t use session_set_cookie_params(). +Keys should include ‘lifetime’, ‘path’, ‘domain’, ‘secure’ or ‘httponly’. +Valid values are mentioned in session_get_cookie_params, they should be set to same values as the +other application uses. Takes effect only if +$cfg['Servers'][$i]['SignonScript'] is not configured.

+ +
+ +
+
+$cfg['Servers'][$i]['SignonURL']
+
+++ + + + + + +
Type:string
Default value:''
+

URL where user will be redirected +to log in for signon authentication method. Should be absolute +including protocol.

+ +
+ +
+
+$cfg['Servers'][$i]['LogoutURL']
+
+++ + + + + + +
Type:string
Default value:''
+

URL where user will be redirected +after logout (doesn’t affect config authentication method). Should be +absolute including protocol.

+
+ +
+
+

Generic settings

+
+
+$cfg['DisableShortcutKeys']
+
+++ + + + + + +
Type:boolean
Default value:false
+

You can disable phpMyAdmin shortcut keys by setting $cfg['DisableShortcutKeys'] to false.

+
+ +
+
+$cfg['ServerDefault']
+
+++ + + + + + +
Type:integer
Default value:1
+

If you have more than one server configured, you can set +$cfg['ServerDefault'] to any one of them to autoconnect to that +server when phpMyAdmin is started, or set it to 0 to be given a list +of servers without logging in.

+

If you have only one server configured, +$cfg['ServerDefault'] MUST be set to that server.

+
+ +
+
+$cfg['VersionCheck']
+
+++ + + + + + +
Type:boolean
Default value:true
+

Enables check for latest versions using JavaScript on the main phpMyAdmin +page or by directly accessing version_check.php.

+
+

Note

+

This setting can be adjusted by your vendor.

+
+
+ +
+
+$cfg['ProxyUrl']
+
+++ + + + + + +
Type:string
Default value:“”
+

The url of the proxy to be used when phpmyadmin needs to access the outside +internet such as when retrieving the latest version info or submitting error +reports. You need this if the server where phpMyAdmin is installed does not +have direct access to the internet. +The format is: “hostname:portnumber”

+
+ +
+
+$cfg['ProxyUser']
+
+++ + + + + + +
Type:string
Default value:“”
+

The username for authenticating with the proxy. By default, no +authentication is performed. If a username is supplied, Basic +Authentication will be performed. No other types of authentication +are currently supported.

+
+ +
+
+$cfg['ProxyPass']
+
+++ + + + + + +
Type:string
Default value:“”
+

The password for authenticating with the proxy.

+
+ +
+
+$cfg['MaxDbList']
+
+++ + + + + + +
Type:integer
Default value:100
+

The maximum number of database names to be displayed in the main panel’s +database list.

+
+ +
+
+$cfg['MaxTableList']
+
+++ + + + + + +
Type:integer
Default value:250
+

The maximum number of table names to be displayed in the main panel’s +list (except on the Export page).

+
+ +
+
+$cfg['ShowHint']
+
+++ + + + + + +
Type:boolean
Default value:true
+

Whether or not to show hints (for example, hints when hovering over +table headers).

+
+ +
+
+$cfg['MaxCharactersInDisplayedSQL']
+
+++ + + + + + +
Type:integer
Default value:1000
+

The maximum number of characters when a SQL query is displayed. The +default limit of 1000 should be correct to avoid the display of tons of +hexadecimal codes that represent BLOBs, but some users have real +SQL queries that are longer than 1000 characters. Also, if a +query’s length exceeds this limit, this query is not saved in the history.

+
+ +
+
+$cfg['PersistentConnections']
+
+++ + + + + + +
Type:boolean
Default value:false
+

Whether persistent connections should be used or not. Works with +following extensions:

+ +
+ +
+
+$cfg['ForceSSL']
+
+++ + + + + + +
Type:boolean
Default value:false
+
+

Deprecated since version 4.6.0: This setting is no longer available since phpMyAdmin 4.6.0. Please +adjust your webserver instead.

+
+

Whether to force using https while accessing phpMyAdmin. In a reverse +proxy setup, setting this to true is not supported.

+
+

Note

+

In some setups (like separate SSL proxy or load balancer) you might +have to set $cfg['PmaAbsoluteUri'] for correct +redirection.

+
+
+ +
+
+$cfg['ExecTimeLimit']
+
+++ + + + + + +
Type:integer [number of seconds]
Default value:300
+

Set the number of seconds a script is allowed to run. If seconds is +set to zero, no time limit is imposed. This setting is used while +importing/exporting dump files but has +no effect when PHP is running in safe mode.

+
+ +
+
+$cfg['SessionSavePath']
+
+++ + + + + + +
Type:string
Default value:''
+

Path for storing session data (session_save_path PHP parameter).

+
+

Warning

+

This folder should not be publicly accessible through the webserver, +otherwise you risk leaking private data from your session.

+
+
+ +
+
+$cfg['MemoryLimit']
+
+++ + + + + + +
Type:string [number of bytes]
Default value:'-1'
+

Set the number of bytes a script is allowed to allocate. If set to +'-1', no limit is imposed. If set to '0', no change of the +memory limit is attempted and the php.ini memory_limit is +used.

+

This setting is used while importing/exporting dump files +so you definitely don’t want to put here a too low +value. It has no effect when PHP is running in safe mode.

+

You can also use any string as in php.ini, eg. ‘16M’. Ensure you +don’t omit the suffix (16 means 16 bytes!)

+
+ +
+
+$cfg['SkipLockedTables']
+
+++ + + + + + +
Type:boolean
Default value:false
+

Mark used tables and make it possible to show databases with locked +tables (since MySQL 3.23.30).

+
+ +
+
+$cfg['ShowSQL']
+
+++ + + + + + +
Type:boolean
Default value:true
+

Defines whether SQL queries +generated by phpMyAdmin should be displayed or not.

+
+ +
+
+$cfg['RetainQueryBox']
+
+++ + + + + + +
Type:boolean
Default value:false
+

Defines whether the SQL query box +should be kept displayed after its submission.

+
+ +
+
+$cfg['CodemirrorEnable']
+
+++ + + + + + +
Type:boolean
Default value:true
+

Defines whether to use a Javascript code editor for SQL query boxes. +CodeMirror provides syntax highlighting and line numbers. However, +middle-clicking for pasting the clipboard contents in some Linux +distributions (such as Ubuntu) is not supported by all browsers.

+
+ +
+
+$cfg['DefaultForeignKeyChecks']
+
+++ + + + + + +
Type:string
Default value:'default'
+

Default value of the checkbox for foreign key checks, to disable/enable +foreign key checks for certain queries. The possible values are 'default', +'enable' or 'disable'. If set to 'default', the value of the +MySQL variable FOREIGN_KEY_CHECKS is used.

+
+ +
+
+$cfg['AllowUserDropDatabase']
+
+++ + + + + + +
Type:boolean
Default value:false
+
+

Warning

+

This is not a security measure as there will be always ways to +circumvent this. If you want to prohibit users from dropping databases, +revoke their corresponding DROP privilege.

+
+

Defines whether normal users (non-administrator) are allowed to delete +their own database or not. If set as false, the link Drop +Database will not be shown, and even a DROP DATABASE mydatabase will +be rejected. Quite practical for ISP ‘s with many customers.

+

This limitation of SQL queries is not as strict as when using MySQL +privileges. This is due to nature of SQL queries which might be +quite complicated. So this choice should be viewed as help to avoid +accidental dropping rather than strict privilege limitation.

+
+ +
+
+$cfg['Confirm']
+
+++ + + + + + +
Type:boolean
Default value:true
+

Whether a warning (“Are your really sure...”) should be displayed when +you’re about to lose data.

+
+ +
+
+$cfg['UseDbSearch']
+
+++ + + + + + +
Type:boolean
Default value:true
+

Define whether the “search string inside database” is enabled or not.

+
+ +
+
+$cfg['IgnoreMultiSubmitErrors']
+
+++ + + + + + +
Type:boolean
Default value:false
+

Define whether phpMyAdmin will continue executing a multi-query +statement if one of the queries fails. Default is to abort execution.

+
+ +
+ + +
+

Main panel

+
+
+$cfg['ShowStats']
+
+++ + + + + + +
Type:boolean
Default value:true
+

Defines whether or not to display space usage and statistics about +databases and tables. Note that statistics requires at least MySQL +3.23.3 and that, at this date, MySQL doesn’t return such information +for Berkeley DB tables.

+
+ +
+
+$cfg['ShowServerInfo']
+
+++ + + + + + +
Type:boolean
Default value:true
+

Defines whether to display detailed server information on main page. +You can additionally hide more information by using +$cfg['Servers'][$i]['verbose'].

+
+ +
+
+$cfg['ShowPhpInfo']
+
+++ + + + + + +
Type:boolean
Default value:false
+

Defines whether to display the PHP information or not at +the starting main (right) frame.

+

Please note that to block the usage of phpinfo() in scripts, you have to +put this in your php.ini:

+
disable_functions = phpinfo()
+
+
+
+

Warning

+

Enabling phpinfo page will leak quite a lot of information about server +setup. Is it not recommended to enable this on shared installations.

+

This might also make easier some remote attacks on your installations, +so enable this only when needed.

+
+
+ +
+
+$cfg['ShowChgPassword']
+
+++ + + + + + +
Type:boolean
Default value:true
+

Defines whether to display the Change password link or not at +the starting main (right) frame. This setting does not check MySQL commands +entered directly.

+

Please note that enabling the Change password link has no effect +with config authentication mode: because of the hard coded password value +in the configuration file, end users can’t be allowed to change their +passwords.

+
+ +
+
+$cfg['ShowCreateDb']
+
+++ + + + + + +
Type:boolean
Default value:true
+

Defines whether to display the form for creating database or not at the +starting main (right) frame. This setting does not check MySQL commands +entered directly.

+
+ +
+
+$cfg['ShowGitRevision']
+
+++ + + + + + +
Type:boolean
Default value:true
+

Defines whether to display informations about the current Git revision (if +applicable) on the main panel.

+
+ +
+
+$cfg['MysqlMinVersion']
+
+++ + + + +
Type:array
+

Defines the minimum supported MySQL version. The default is chosen +by the phpMyAdmin team; however this directive was asked by a developer +of the Plesk control panel to ease integration with older MySQL servers +(where most of the phpMyAdmin features work).

+
+ +
+
+

Database structure

+
+
+$cfg['ShowDbStructureCreation']
+
+++ + + + + + +
Type:boolean
Default value:false
+

Defines whether the database structure page (tables list) has a +“Creation” column that displays when each table was created.

+
+ +
+
+$cfg['ShowDbStructureLastUpdate']
+
+++ + + + + + +
Type:boolean
Default value:false
+

Defines whether the database structure page (tables list) has a “Last +update” column that displays when each table was last updated.

+
+ +
+
+$cfg['ShowDbStructureLastCheck']
+
+++ + + + + + +
Type:boolean
Default value:false
+

Defines whether the database structure page (tables list) has a “Last +check” column that displays when each table was last checked.

+
+ +
+
+$cfg['HideStructureActions']
+
+++ + + + + + +
Type:boolean
Default value:true
+

Defines whether the table structure actions are hidden under a “More” +drop-down.

+
+ +
+
+$cfg['ShowColumnComments']
+
+++ + + + + + +
Type:boolean
Default value:true
+

Defines whether to show column comments as a column in the table structure view.

+
+ +
+
+

Browse mode

+
+
+$cfg['TableNavigationLinksMode']
+
+++ + + + + + +
Type:string
Default value:'icons'
+

Defines whether the table navigation links contain 'icons', 'text' +or 'both'.

+
+ +
+
+$cfg['ActionLinksMode']
+
+++ + + + + + +
Type:string
Default value:'both'
+

If set to icons, will display icons instead of text for db and table +properties links (like Browse, Select, +Insert, ...). Can be set to 'both' +if you want icons AND text. When set to text, will only show text.

+
+ +
+
+$cfg['RowActionType']
+
+++ + + + + + +
Type:string
Default value:'both'
+

Whether to display icons or text or both icons and text in table row action +segment. Value can be either of 'icons', 'text' or 'both'.

+
+ +
+
+$cfg['ShowAll']
+
+++ + + + + + +
Type:boolean
Default value:false
+

Defines whether a user should be displayed a “Show all” button in browse +mode or not in all cases. By default it is shown only on small tables (less +than 500 rows) to avoid performance issues while getting too many rows.

+
+ +
+
+$cfg['MaxRows']
+
+++ + + + + + +
Type:integer
Default value:25
+

Number of rows displayed when browsing a result set and no LIMIT +clause is used. If the result set contains more rows, “Previous” and +“Next” links will be shown. Possible values: 25,50,100,250,500.

+
+ +
+
+$cfg['Order']
+
+++ + + + + + +
Type:string
Default value:'SMART'
+

Defines whether columns are displayed in ascending (ASC) order, in +descending (DESC) order or in a “smart” (SMART) order - I.E. +descending order for columns of type TIME, DATE, DATETIME and +TIMESTAMP, ascending order else- by default.

+
+

Changed in version 3.4.0: Since phpMyAdmin 3.4.0 the default value is 'SMART'.

+
+
+ +
+
+$cfg['GridEditing']
+
+++ + + + + + +
Type:string
Default value:'double-click'
+

Defines which action (double-click or click) triggers grid +editing. Can be deactivated with the disabled value.

+
+ +
+
+$cfg['RelationalDisplay']
+
+++ + + + + + +
Type:string
Default value:'K'
+

Defines the initial behavior for Options > Relational. K, which +is the default, displays the key while D shows the display column.

+
+ +
+
+$cfg['SaveCellsAtOnce']
+
+++ + + + + + +
Type:boolean
Default value:false
+

Defines whether or not to save all edited cells at once for grid +editing.

+
+ +
+
+

Editing mode

+
+
+$cfg['ProtectBinary']
+
+++ + + + + + +
Type:boolean or string
Default value:'blob'
+

Defines whether BLOB or BINARY columns are protected from +editing when browsing a table’s content. Valid values are:

+
    +
  • false to allow editing of all columns;
  • +
  • 'blob' to allow editing of all columns except BLOBS;
  • +
  • 'noblob' to disallow editing of all columns except BLOBS (the +opposite of 'blob');
  • +
  • 'all' to disallow editing of all BINARY or BLOB columns.
  • +
+
+ +
+
+$cfg['ShowFunctionFields']
+
+++ + + + + + +
Type:boolean
Default value:true
+

Defines whether or not MySQL functions fields should be initially +displayed in edit/insert mode. Since version 2.10, the user can toggle +this setting from the interface.

+
+ +
+
+$cfg['ShowFieldTypesInDataEditView']
+
+++ + + + + + +
Type:boolean
Default value:true
+

Defines whether or not type fields should be initially displayed in +edit/insert mode. The user can toggle this setting from the interface.

+
+ +
+
+$cfg['InsertRows']
+
+++ + + + + + +
Type:integer
Default value:2
+

Defines the default number of rows to be entered from the Insert page. +Users can manually change this from the bottom of that page to add or remove +blank rows.

+
+ +
+
+$cfg['ForeignKeyMaxLimit']
+
+++ + + + + + +
Type:integer
Default value:100
+

If there are fewer items than this in the set of foreign keys, then a +drop-down box of foreign keys is presented, in the style described by +the $cfg['ForeignKeyDropdownOrder'] setting.

+
+ +
+
+$cfg['ForeignKeyDropdownOrder']
+
+++ + + + + + +
Type:array
Default value:array(‘content-id’, ‘id-content’)
+

For the foreign key drop-down fields, there are several methods of +display, offering both the key and value data. The contents of the +array should be one or both of the following strings: content-id, +id-content.

+
+ +
+
+

Export and import settings

+
+
+$cfg['ZipDump']
+
+++ + + + + + +
Type:boolean
Default value:true
+
+ +
+
+$cfg['GZipDump']
+
+++ + + + + + +
Type:boolean
Default value:true
+
+ +
+
+$cfg['BZipDump']
+
+++ + + + + + +
Type:boolean
Default value:true
+

Defines whether to allow the use of zip/GZip/BZip2 compression when +creating a dump file

+
+ +
+
+$cfg['CompressOnFly']
+
+++ + + + + + +
Type:boolean
Default value:true
+

Defines whether to allow on the fly compression for GZip/BZip2 +compressed exports. This doesn’t affect smaller dumps and allows users +to create larger dumps that won’t otherwise fit in memory due to php +memory limit. Produced files contain more GZip/BZip2 headers, but all +normal programs handle this correctly.

+
+ +
+
+$cfg['Export']
+
+++ + + + + + +
Type:array
Default value:array(...)
+

In this array are defined default parameters for export, names of +items are similar to texts seen on export page, so you can easily +identify what they mean.

+
+ +
+
+$cfg['Export']['format']
+
+++ + + + + + +
Type:string
Default value:'sql'
+

Default export format.

+
+ +
+
+$cfg['Export']['method']
+
+++ + + + + + +
Type:string
Default value:'quick'
+

Defines how the export form is displayed when it loads. Valid values +are:

+
    +
  • quick to display the minimum number of options to configure
  • +
  • custom to display every available option to configure
  • +
  • custom-no-form same as custom but does not display the option +of using quick export
  • +
+
+ +
+
+$cfg['Export']['charset']
+
+++ + + + + + +
Type:string
Default value:''
+

Defines charset for generated export. By default no charset conversion is +done assuming UTF-8.

+
+ +
+
+$cfg['Export']['file_template_table']
+
+++ + + + + + +
Type:string
Default value:'@TABLE@'
+

Default filename template for table exports.

+ +
+ +
+
+$cfg['Export']['file_template_database']
+
+++ + + + + + +
Type:string
Default value:'@DATABASE@'
+

Default filename template for database exports.

+ +
+ +
+
+$cfg['Export']['file_template_server']
+
+++ + + + + + +
Type:string
Default value:'@SERVER@'
+

Default filename template for server exports.

+ +
+ +
+
+$cfg['Import']
+
+++ + + + + + +
Type:array
Default value:array(...)
+

In this array are defined default parameters for import, names of +items are similar to texts seen on import page, so you can easily +identify what they mean.

+
+ +
+
+$cfg['Import']['charset']
+
+++ + + + + + +
Type:string
Default value:''
+

Defines charset for import. By default no charset conversion is done +assuming UTF-8.

+
+ +
+
+

Tabs display settings

+
+
+$cfg['TabsMode']
+
+++ + + + + + +
Type:string
Default value:'both'
+

Defines whether the menu tabs contain 'icons', 'text' or 'both'.

+
+ +
+
+$cfg['PropertiesNumColumns']
+
+++ + + + + + +
Type:integer
Default value:1
+

How many columns will be utilized to display the tables on the database +property view? When setting this to a value larger than 1, the type of the +database will be omitted for more display space.

+
+ +
+
+$cfg['DefaultTabServer']
+
+++ + + + + + +
Type:string
Default value:'welcome'
+

Defines the tab displayed by default on server view. The possible values +are the localized equivalent of:

+
    +
  • welcome (recommended for multi-user setups)
  • +
  • databases,
  • +
  • status
  • +
  • variables
  • +
  • privileges
  • +
+
+ +
+
+$cfg['DefaultTabDatabase']
+
+++ + + + + + +
Type:string
Default value:'structure'
+

Defines the tab displayed by default on database view. The possible values +are the localized equivalent of:

+
    +
  • structure
  • +
  • sql
  • +
  • search
  • +
  • operations
  • +
+
+ +
+
+$cfg['DefaultTabTable']
+
+++ + + + + + +
Type:string
Default value:'browse'
+

Defines the tab displayed by default on table view. The possible values +are the localized equivalent of:

+
    +
  • structure
  • +
  • sql
  • +
  • search
  • +
  • insert
  • +
  • browse
  • +
+
+ +
+
+

PDF Options

+
+
+$cfg['PDFPageSizes']
+
+++ + + + + + +
Type:array
Default value:array('A3', 'A4', 'A5', 'letter', 'legal')
+

Array of possible paper sizes for creating PDF pages.

+

You should never need to change this.

+
+ +
+
+$cfg['PDFDefaultPageSize']
+
+++ + + + + + +
Type:string
Default value:'A4'
+

Default page size to use when creating PDF pages. Valid values are any +listed in $cfg['PDFPageSizes'].

+
+ +
+
+

Languages

+
+
+$cfg['DefaultLang']
+
+++ + + + + + +
Type:string
Default value:'en'
+

Defines the default language to use, if not browser-defined or user- +defined. The corresponding language file needs to be in +locale/code/LC_MESSAGES/phpmyadmin.mo.

+
+ +
+
+$cfg['DefaultConnectionCollation']
+
+++ + + + + + +
Type:string
Default value:'utf8mb4_general_ci'
+

Defines the default connection collation to use, if not user-defined. +See the MySQL documentation for charsets +for list of possible values.

+
+ +
+
+$cfg['Lang']
+
+++ + + + + + +
Type:string
Default value:not set
+

Force language to use. The corresponding language file needs to be in +locale/code/LC_MESSAGES/phpmyadmin.mo.

+
+ +
+
+$cfg['FilterLanguages']
+
+++ + + + + + +
Type:string
Default value:''
+

Limit list of available languages to those matching the given regular +expression. For example if you want only Czech and English, you should +set filter to '^(cs|en)'.

+
+ +
+
+$cfg['RecodingEngine']
+
+++ + + + + + +
Type:string
Default value:'auto'
+

You can select here which functions will be used for character set +conversion. Possible values are:

+
    +
  • auto - automatically use available one (first is tested iconv, then +recode)
  • +
  • iconv - use iconv or libiconv functions
  • +
  • recode - use recode_string function
  • +
  • mb - use mbstring extension
  • +
  • none - disable encoding conversion
  • +
+

Enabled charset conversion activates a pull-down menu in the Export +and Import pages, to choose the character set when exporting a file. +The default value in this menu comes from +$cfg['Export']['charset'] and $cfg['Import']['charset'].

+
+ +
+
+$cfg['IconvExtraParams']
+
+++ + + + + + +
Type:string
Default value:'//TRANSLIT'
+

Specify some parameters for iconv used in charset conversion. See +iconv documentation for details. By default +//TRANSLIT is used, so that invalid characters will be +transliterated.

+
+ +
+
+$cfg['AvailableCharsets']
+
+++ + + + + + +
Type:array
Default value:array(...)
+

Available character sets for MySQL conversion. You can add your own +(any of supported by recode/iconv) or remove these which you don’t +use. Character sets will be shown in same order as here listed, so if +you frequently use some of these move them to the top.

+
+ +
+
+

Web server settings

+
+
+$cfg['OBGzip']
+
+++ + + + + + +
Type:string/boolean
Default value:'auto'
+

Defines whether to use GZip output buffering for increased speed in +HTTP transfers. Set to +true/false for enabling/disabling. When set to ‘auto’ (string), +phpMyAdmin tries to enable output buffering and will automatically +disable it if your browser has some problems with buffering. IE6 with +a certain patch is known to cause data corruption when having enabled +buffering.

+
+ +
+
+$cfg['TrustedProxies']
+
+++ + + + + + +
Type:array
Default value:array()
+

Lists proxies and HTTP headers which are trusted for +$cfg['Servers'][$i]['AllowDeny']['order']. This list is by +default empty, you need to fill in some trusted proxy servers if you +want to use rules for IP addresses behind proxy.

+

The following example specifies that phpMyAdmin should trust a +HTTP_X_FORWARDED_FOR (X-Forwarded-For) header coming from the proxy +1.2.3.4:

+
$cfg['TrustedProxies'] = array('1.2.3.4' => 'HTTP_X_FORWARDED_FOR');
+
+
+

The $cfg['Servers'][$i]['AllowDeny']['rules'] directive uses the +client’s IP address as usual.

+
+ +
+
+$cfg['GD2Available']
+
+++ + + + + + +
Type:string
Default value:'auto'
+

Specifies whether GD >= 2 is available. If yes it can be used for MIME +transformations. Possible values are:

+
    +
  • auto - automatically detect
  • +
  • yes - GD 2 functions can be used
  • +
  • no - GD 2 function cannot be used
  • +
+
+ +
+
+$cfg['CheckConfigurationPermissions']
+
+++ + + + + + +
Type:boolean
Default value:true
+

We normally check the permissions on the configuration file to ensure +it’s not world writable. However, phpMyAdmin could be installed on a +NTFS filesystem mounted on a non-Windows server, in which case the +permissions seems wrong but in fact cannot be detected. In this case a +sysadmin would set this parameter to false.

+
+ +
+
+$cfg['LinkLengthLimit']
+
+++ + + + + + +
Type:integer
Default value:1000
+

Limit for length of URL in links. When length would be above this +limit, it is replaced by form with button. This is required as some web +servers (IIS) have problems with long URL .

+
+ +
+
+$cfg['CSPAllow']
+
+++ + + + + + +
Type:string
Default value:''
+

Additional string to include in allowed script and image sources in Content +Security Policy header.

+

This can be useful when you want to include some external JavaScript files +in config.footer.inc.php or config.header.inc.php, which +would be normally not allowed by Content Security Policy.

+

To allow some sites, just list them within the string:

+
$cfg['CSPAllow'] = 'example.com example.net';
+
+
+
+

New in version 4.0.4.

+
+
+ +
+
+$cfg['DisableMultiTableMaintenance']
+
+++ + + + + + +
Type:boolean
Default value:false
+

In the database Structure page, it’s possible to mark some tables then +choose an operation like optimizing for many tables. This can slow +down a server; therefore, setting this to true prevents this kind +of multiple maintenance operation.

+
+ +
+
+

Theme settings

+
+
Please directly modify themes/themename/layout.inc.php, although +your changes will be overwritten with the next update.
+
+
+

Design customization

+
+
+$cfg['NavigationTreePointerEnable']
+
+++ + + + + + +
Type:boolean
Default value:true
+

When set to true, hovering over an item in the navigation panel causes that item to be marked +(the background is highlighted).

+
+ +
+
+$cfg['BrowsePointerEnable']
+
+++ + + + + + +
Type:boolean
Default value:true
+

When set to true, hovering over a row in the Browse page causes that row to be marked (the background +is highlighted).

+
+ +
+
+$cfg['BrowseMarkerEnable']
+
+++ + + + + + +
Type:boolean
Default value:true
+

When set to true, a data row is marked (the background is highlighted) when the row is selected +with the checkbox.

+
+ +
+
+$cfg['LimitChars']
+
+++ + + + + + +
Type:integer
Default value:50
+

Maximum number of characters shown in any non-numeric field on browse +view. Can be turned off by a toggle button on the browse page.

+
+ +
+ +
+++ + + + + + +
Type:string
Default value:'left'
+

Defines the place where table row links (Edit, Copy, Delete) would be +put when tables contents are displayed (you may have them displayed at +the left side, right side, both sides or nowhere).

+
+ +
+
+$cfg['RowActionLinksWithoutUnique']
+
+++ + + + + + +
Type:boolean
Default value:false
+

Defines whether to show row links (Edit, Copy, Delete) and checkboxes +for multiple row operations even when the selection does not have a unique key. +Using row actions in the absence of a unique key may result in different/more +rows being affected since there is no guaranteed way to select the exact row(s).

+
+ +
+
+$cfg['RememberSorting']
+
+++ + + + + + +
Type:boolean
Default value:true
+

If enabled, remember the sorting of each table when browsing them.

+
+ +
+
+$cfg['TablePrimaryKeyOrder']
+
+++ + + + + + +
Type:string
Default value:'NONE'
+

This defines the default sort order for the tables, having a primary key, +when there is no sort order defines externally. +Acceptable values : [‘NONE’, ‘ASC’, ‘DESC’]

+
+ +
+
+$cfg['ShowBrowseComments']
+
+++ + + + + + +
Type:boolean
Default value:true
+
+ +
+
+$cfg['ShowPropertyComments']
+
+++ + + + + + +
Type:boolean
Default value:true
+

By setting the corresponding variable to true you can enable the +display of column comments in Browse or Property display. In browse +mode, the comments are shown inside the header. In property mode, +comments are displayed using a CSS-formatted dashed-line below the +name of the column. The comment is shown as a tool-tip for that +column.

+
+ +
+
+

Text fields

+
+
+$cfg['CharEditing']
+
+++ + + + + + +
Type:string
Default value:'input'
+

Defines which type of editing controls should be used for CHAR and +VARCHAR columns. Applies to data editing and also to the default values +in structure editing. Possible values are:

+
    +
  • input - this allows to limit size of text to size of columns in MySQL, +but has problems with newlines in columns
  • +
  • textarea - no problems with newlines in columns, but also no length +limitations
  • +
+
+ +
+
+$cfg['MinSizeForInputField']
+
+++ + + + + + +
Type:integer
Default value:4
+

Defines the minimum size for input fields generated for CHAR and +VARCHAR columns.

+
+ +
+
+$cfg['MaxSizeForInputField']
+
+++ + + + + + +
Type:integer
Default value:60
+

Defines the maximum size for input fields generated for CHAR and +VARCHAR columns.

+
+ +
+
+$cfg['TextareaCols']
+
+++ + + + + + +
Type:integer
Default value:40
+
+ +
+
+$cfg['TextareaRows']
+
+++ + + + + + +
Type:integer
Default value:15
+
+ +
+
+$cfg['CharTextareaCols']
+
+++ + + + + + +
Type:integer
Default value:40
+
+ +
+
+$cfg['CharTextareaRows']
+
+++ + + + + + +
Type:integer
Default value:2
+

Number of columns and rows for the textareas. This value will be +emphasized (*2) for SQL query +textareas and (*1.25) for SQL +textareas inside the query window.

+

The Char* values are used for CHAR +and VARCHAR editing (if configured via $cfg['CharEditing']).

+
+ +
+
+$cfg['LongtextDoubleTextarea']
+
+++ + + + + + +
Type:boolean
Default value:true
+

Defines whether textarea for LONGTEXT columns should have double size.

+
+ +
+
+$cfg['TextareaAutoSelect']
+
+++ + + + + + +
Type:boolean
Default value:false
+

Defines if the whole textarea of the query box will be selected on +click.

+
+ +
+
+$cfg['EnableAutocompleteForTablesAndColumns']
+
+++ + + + + + +
Type:boolean
Default value:true
+

Whether to enable autocomplete for table and column names in any +SQL query box.

+
+ +
+
+

SQL query box settings

+
+
+$cfg['SQLQuery']['Edit']
+
+++ + + + + + +
Type:boolean
Default value:true
+

Whether to display an edit link to change a query in any SQL Query +box.

+
+ +
+
+$cfg['SQLQuery']['Explain']
+
+++ + + + + + +
Type:boolean
Default value:true
+

Whether to display a link to explain a SELECT query in any SQL Query +box.

+
+ +
+
+$cfg['SQLQuery']['ShowAsPHP']
+
+++ + + + + + +
Type:boolean
Default value:true
+

Whether to display a link to wrap a query in PHP code in any SQL Query +box.

+
+ +
+
+$cfg['SQLQuery']['Refresh']
+
+++ + + + + + +
Type:boolean
Default value:true
+

Whether to display a link to refresh a query in any SQL Query box.

+
+ +
+
+

Web server upload/save/import directories

+

If PHP is running in safe mode, all directories must be owned by the same user +as the owner of the phpMyAdmin scripts.

+

If the directory where phpMyAdmin is installed is subject to an +open_basedir restriction, you need to create a temporary directory in some +directory accessible by the PHP interpreter.

+

For security reasons, all directories should be outside the tree published by +webserver. If you cannot avoid having this directory published by webserver, +limit access to it either by web server configuration (for example using +.htaccess or web.config files) or place at least an empty index.html +file there, so that directory listing is not possible. However as long as the +directory is accessible by web server, an attacker can guess filenames to download +the files.

+
+
+$cfg['UploadDir']
+
+++ + + + + + +
Type:string
Default value:''
+

The name of the directory where SQL files have been uploaded by +other means than phpMyAdmin (for example, ftp). Those files are available +under a drop-down box when you click the database or table name, then the +Import tab.

+

If +you want different directory for each user, %u will be replaced with +username.

+

Please note that the file names must have the suffix ”.sql” +(or ”.sql.bz2” or ”.sql.gz” if support for compressed formats is +enabled).

+

This feature is useful when your file is too big to be +uploaded via HTTP, or when file +uploads are disabled in PHP.

+
+

Warning

+

Please see top of this chapter (Web server upload/save/import directories) for instructions how +to setup this directory and how to make its usage secure.

+
+ +
+ +
+
+$cfg['SaveDir']
+
+++ + + + + + +
Type:string
Default value:''
+

The name of the webserver directory where exported files can be saved.

+

If you want a different directory for each user, %u will be replaced with the +username.

+

Please note that the directory must exist and has to be writable for +the user running webserver.

+
+

Warning

+

Please see top of this chapter (Web server upload/save/import directories) for instructions how +to setup this directory and how to make its usage secure.

+
+
+ +
+
+$cfg['TempDir']
+
+++ + + + + + +
Type:string
Default value:'./tmp/'
+

The name of the directory where temporary files can be stored. It is used +for several purposes, currently:

+ +

This directory should have as strict permissions as possible as the only +user required to access this directory is the one who runs the webserver. +If you have root privileges, simply make this user owner of this directory +and make it accessible only by it:

+
chown www-data:www-data tmp
+chmod 700 tmp
+
+
+

If you cannot change owner of the directory, you can achieve a similar +setup using ACL:

+
chmod 700 tmp
+setfacl -m "g:www-data:rwx" tmp
+setfacl -d -m "g:www-data:rwx" tmp
+
+
+

If neither of above works for you, you can still make the directory +chmod 777, but it might impose risk of other users on system +reading and writing data in this directory.

+
+

Warning

+

Please see top of this chapter (Web server upload/save/import directories) for instructions how +to setup this directory and how to make its usage secure.

+
+
+ +
+
+

Various display setting

+
+
+$cfg['RepeatCells']
+
+++ + + + + + +
Type:integer
Default value:100
+

Repeat the headers every X cells, or 0 to deactivate.

+
+ +
+
+$cfg['QueryHistoryDB']
+
+++ + + + + + +
Type:boolean
Default value:false
+
+ +
+
+$cfg['QueryHistoryMax']
+
+++ + + + + + +
Type:integer
Default value:25
+

If $cfg['QueryHistoryDB'] is set to true, all your +Queries are logged to a table, which has to be created by you (see +$cfg['Servers'][$i]['history']). If set to false, all your +queries will be appended to the form, but only as long as your window is +opened they remain saved.

+

When using the JavaScript based query window, it will always get updated +when you click on a new table/db to browse and will focus if you click on +Edit SQL after using a query. You can suppress updating the +query window by checking the box Do not overwrite this query +from outside the window below the query textarea. Then you can browse +tables/databases in the background without losing the contents of the +textarea, so this is especially useful when composing a query with tables +you first have to look in. The checkbox will get automatically checked +whenever you change the contents of the textarea. Please uncheck the button +whenever you definitely want the query window to get updated even though +you have made alterations.

+

If $cfg['QueryHistoryDB'] is set to true you can +specify the amount of saved history items using +$cfg['QueryHistoryMax'].

+
+ +
+
+$cfg['BrowseMIME']
+
+++ + + + + + +
Type:boolean
Default value:true
+

Enable Transformations.

+
+ +
+
+$cfg['MaxExactCount']
+
+++ + + + + + +
Type:integer
Default value:50000
+

For InnoDB tables, determines for how large tables phpMyAdmin should +get the exact row count using SELECT COUNT. If the approximate row +count as returned by SHOW TABLE STATUS is smaller than this value, +SELECT COUNT will be used, otherwise the approximate count will be +used.

+
+

Changed in version 4.8.0: The default value was lowered to 50000 for performance reasons.

+
+
+

Changed in version 4.2.6: The default value was changed to 500000.

+
+ +
+ +
+
+$cfg['MaxExactCountViews']
+
+++ + + + + + +
Type:integer
Default value:0
+

For VIEWs, since obtaining the exact count could have an impact on +performance, this value is the maximum to be displayed, using a +SELECT COUNT ... LIMIT. Setting this to 0 bypasses any row +counting.

+
+ +
+
+$cfg['NaturalOrder']
+
+++ + + + + + +
Type:boolean
Default value:true
+

Sorts database and table names according to natural order (for +example, t1, t2, t10). Currently implemented in the navigation panel +and in Database view, for the table list.

+
+ +
+
+$cfg['InitialSlidersState']
+
+++ + + + + + +
Type:string
Default value:'closed'
+

If set to 'closed', the visual sliders are initially in a closed +state. A value of 'open' does the reverse. To completely disable +all visual sliders, use 'disabled'.

+
+ +
+
+$cfg['UserprefsDisallow']
+
+++ + + + + + +
Type:array
Default value:array()
+

Contains names of configuration options (keys in $cfg array) that +users can’t set through user preferences. For possible values, refer +to clases under libraries/classes/Config/Forms/User/.

+
+ +
+
+$cfg['UserprefsDeveloperTab']
+
+++ + + + + + +
Type:boolean
Default value:false
+

Activates in the user preferences a tab containing options for +developers of phpMyAdmin.

+
+ +
+
+

Page titles

+
+
+$cfg['TitleTable']
+
+++ + + + + + +
Type:string
Default value:'@HTTP_HOST@ / @VSERVER@ / @DATABASE@ / @TABLE@ | @PHPMYADMIN@'
+
+ +
+
+$cfg['TitleDatabase']
+
+++ + + + + + +
Type:string
Default value:'@HTTP_HOST@ / @VSERVER@ / @DATABASE@ | @PHPMYADMIN@'
+
+ +
+
+$cfg['TitleServer']
+
+++ + + + + + +
Type:string
Default value:'@HTTP_HOST@ / @VSERVER@ | @PHPMYADMIN@'
+
+ +
+
+$cfg['TitleDefault']
+
+++ + + + + + +
Type:string
Default value:'@HTTP_HOST@ | @PHPMYADMIN@'
+

Allows you to specify window’s title bar. You can use 6.27 What format strings can I use?.

+
+ +
+
+

Theme manager settings

+
+
+$cfg['ThemeManager']
+
+++ + + + + + +
Type:boolean
Default value:true
+

Enables user-selectable themes. See 2.7 Using and creating themes.

+
+ +
+
+$cfg['ThemeDefault']
+
+++ + + + + + +
Type:string
Default value:'pmahomme'
+

The default theme (a subdirectory under ./themes/).

+
+ +
+
+$cfg['ThemePerServer']
+
+++ + + + + + +
Type:boolean
Default value:false
+

Whether to allow different theme for each server.

+
+ +
+
+$cfg['FontSize']
+
+++ + + + + + +
Type:string
Default value:‘82%’
+

Font size to use, is applied in CSS.

+
+ +
+
+

Default queries

+
+
+$cfg['DefaultQueryTable']
+
+++ + + + + + +
Type:string
Default value:'SELECT * FROM @TABLE@ WHERE 1'
+
+ +
+
+$cfg['DefaultQueryDatabase']
+
+++ + + + + + +
Type:string
Default value:''
+

Default queries that will be displayed in query boxes when user didn’t +specify any. You can use standard 6.27 What format strings can I use?.

+
+ +
+
+

MySQL settings

+
+
+$cfg['DefaultFunctions']
+
+++ + + + + + +
Type:array
Default value:array(...)
+

Functions selected by default when inserting/changing row, Functions +are defined for meta types as (FUNC_NUMBER, FUNC_DATE, FUNC_CHAR, +FUNC_SPATIAL, FUNC_UUID) and for first_timestamp, which is used +for first timestamp column in table.

+
+ +
+
+

Default options for Transformations

+
+
+$cfg['DefaultTransformations']
+
+++ + + + + + +
Type:array
Default value:An array with below listed key-values
+
+ +
+
+$cfg['DefaultTransformations']['Substring']
+
+++ + + + + + +
Type:array
Default value:array(0, ‘all’, ‘…’)
+
+ +
+
+$cfg['DefaultTransformations']['Bool2Text']
+
+++ + + + + + +
Type:array
Default value:array(‘T’, ‘F’)
+
+ +
+
+$cfg['DefaultTransformations']['External']
+
+++ + + + + + +
Type:array
Default value:array(0, ‘-f /dev/null -i -wrap -q’, 1, 1)
+
+ +
+
+$cfg['DefaultTransformations']['PreApPend']
+
+++ + + + + + +
Type:array
Default value:array(‘’, ‘’)
+
+ +
+
+$cfg['DefaultTransformations']['Hex']
+
+++ + + + + + +
Type:array
Default value:array(‘2’)
+
+ +
+
+$cfg['DefaultTransformations']['DateFormat']
+
+++ + + + + + +
Type:array
Default value:array(0, ‘’, ‘local’)
+
+ +
+
+$cfg['DefaultTransformations']['Inline']
+
+++ + + + + + +
Type:array
Default value:array(‘100’, 100)
+
+ +
+ +
+++ + + + + + +
Type:array
Default value:array(‘’, 100, 50)
+
+ +
+ +
+++ + + + + + +
Type:array
Default value:array(‘’, ‘’, ‘’)
+
+ +
+
+

Console settings

+
+

Note

+

These settings are mostly meant to be changed by user.

+
+
+
+$cfg['Console']['StartHistory']
+
+++ + + + + + +
Type:boolean
Default value:false
+

Show query history at start

+
+ +
+
+$cfg['Console']['AlwaysExpand']
+
+++ + + + + + +
Type:boolean
Default value:false
+

Always expand query messages

+
+ +
+
+$cfg['Console']['CurrentQuery']
+
+++ + + + + + +
Type:boolean
Default value:true
+

Show current browsing query

+
+ +
+
+$cfg['Console']['EnterExecutes']
+
+++ + + + + + +
Type:boolean
Default value:false
+

Execute queries on Enter and insert new line with Shift + Enter

+
+ +
+
+$cfg['Console']['DarkTheme']
+
+++ + + + + + +
Type:boolean
Default value:false
+

Switch to dark theme

+
+ +
+
+$cfg['Console']['Mode']
+
+++ + + + + + +
Type:string
Default value:‘info’
+

Console mode

+
+ +
+
+$cfg['Console']['Height']
+
+++ + + + + + +
Type:integer
Default value:92
+

Console height

+
+ +
+
+

Developer

+
+

Warning

+

These settings might have huge effect on performance or security.

+
+
+
+$cfg['DBG']
+
+++ + + + + + +
Type:array
Default value:array(...)
+
+ +
+
+$cfg['DBG']['sql']
+
+++ + + + + + +
Type:boolean
Default value:false
+

Enable logging queries and execution times to be +displayed in the console’s Debug SQL tab.

+
+ +
+
+$cfg['DBG']['sqllog']
+
+++ + + + + + +
Type:boolean
Default value:false
+

Enable logging of queries and execution times to the syslog. +Requires $cfg['DBG']['sql'] to be enabled.

+
+ +
+
+$cfg['DBG']['demo']
+
+++ + + + + + +
Type:boolean
Default value:false
+

Enable to let server present itself as demo server. +This is used for phpMyAdmin demo server.

+

It currently changes following behavior:

+
    +
  • There is welcome message on the main page.
  • +
  • There is footer information about demo server and used git revision.
  • +
  • The setup script is enabled even with existing configuration.
  • +
  • The setup does not try to connect to the MySQL server.
  • +
+
+ +
+
+$cfg['DBG']['simple2fa']
+
+++ + + + + + +
Type:boolean
Default value:false
+

Can be used for testing two-factor authentication using Simple two-factor authentication.

+
+ +
+
+

Examples

+

See following configuration snippets for typical setups of phpMyAdmin.

+
+

Basic example

+

Example configuration file, which can be copied to config.inc.php to +get some core configuration layout; it is distributed with phpMyAdmin as +config.sample.inc.php. Please note that it does not contain all +configuration options, only the most frequently used ones.

+
<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * phpMyAdmin sample configuration, you can use it as base for
+ * manual configuration. For easier setup you can use setup/
+ *
+ * All directives are explained in documentation in the doc/ folder
+ * or at <https://docs.phpmyadmin.net/>.
+ *
+ * @package PhpMyAdmin
+ */
+
+/**
+ * This is needed for cookie based authentication to encrypt password in
+ * cookie. Needs to be 32 chars long.
+ */
+$cfg['blowfish_secret'] = ''; /* YOU MUST FILL IN THIS FOR COOKIE AUTH! */
+
+/**
+ * Servers configuration
+ */
+$i = 0;
+
+/**
+ * First server
+ */
+$i++;
+/* Authentication type */
+$cfg['Servers'][$i]['auth_type'] = 'cookie';
+/* Server parameters */
+$cfg['Servers'][$i]['host'] = 'localhost';
+$cfg['Servers'][$i]['compress'] = false;
+$cfg['Servers'][$i]['AllowNoPassword'] = false;
+
+/**
+ * phpMyAdmin configuration storage settings.
+ */
+
+/* User used to manipulate with storage */
+// $cfg['Servers'][$i]['controlhost'] = '';
+// $cfg['Servers'][$i]['controlport'] = '';
+// $cfg['Servers'][$i]['controluser'] = 'pma';
+// $cfg['Servers'][$i]['controlpass'] = 'pmapass';
+
+/* Storage database and tables */
+// $cfg['Servers'][$i]['pmadb'] = 'phpmyadmin';
+// $cfg['Servers'][$i]['bookmarktable'] = 'pma__bookmark';
+// $cfg['Servers'][$i]['relation'] = 'pma__relation';
+// $cfg['Servers'][$i]['table_info'] = 'pma__table_info';
+// $cfg['Servers'][$i]['table_coords'] = 'pma__table_coords';
+// $cfg['Servers'][$i]['pdf_pages'] = 'pma__pdf_pages';
+// $cfg['Servers'][$i]['column_info'] = 'pma__column_info';
+// $cfg['Servers'][$i]['history'] = 'pma__history';
+// $cfg['Servers'][$i]['table_uiprefs'] = 'pma__table_uiprefs';
+// $cfg['Servers'][$i]['tracking'] = 'pma__tracking';
+// $cfg['Servers'][$i]['userconfig'] = 'pma__userconfig';
+// $cfg['Servers'][$i]['recent'] = 'pma__recent';
+// $cfg['Servers'][$i]['favorite'] = 'pma__favorite';
+// $cfg['Servers'][$i]['users'] = 'pma__users';
+// $cfg['Servers'][$i]['usergroups'] = 'pma__usergroups';
+// $cfg['Servers'][$i]['navigationhiding'] = 'pma__navigationhiding';
+// $cfg['Servers'][$i]['savedsearches'] = 'pma__savedsearches';
+// $cfg['Servers'][$i]['central_columns'] = 'pma__central_columns';
+// $cfg['Servers'][$i]['designer_settings'] = 'pma__designer_settings';
+// $cfg['Servers'][$i]['export_templates'] = 'pma__export_templates';
+
+/**
+ * End of servers configuration
+ */
+
+/**
+ * Directories for saving/loading files from server
+ */
+$cfg['UploadDir'] = '';
+$cfg['SaveDir'] = '';
+
+/**
+ * Whether to display icons or text or both icons and text in table row
+ * action segment. Value can be either of 'icons', 'text' or 'both'.
+ * default = 'both'
+ */
+//$cfg['RowActionType'] = 'icons';
+
+/**
+ * Defines whether a user should be displayed a "show all (records)"
+ * button in browse mode or not.
+ * default = false
+ */
+//$cfg['ShowAll'] = true;
+
+/**
+ * Number of rows displayed when browsing a result set. If the result
+ * set contains more rows, "Previous" and "Next".
+ * Possible values: 25, 50, 100, 250, 500
+ * default = 25
+ */
+//$cfg['MaxRows'] = 50;
+
+/**
+ * Disallow editing of binary fields
+ * valid values are:
+ *   false    allow editing
+ *   'blob'   allow editing except for BLOB fields
+ *   'noblob' disallow editing except for BLOB fields
+ *   'all'    disallow editing
+ * default = 'blob'
+ */
+//$cfg['ProtectBinary'] = false;
+
+/**
+ * Default language to use, if not browser-defined or user-defined
+ * (you find all languages in the locale folder)
+ * uncomment the desired line:
+ * default = 'en'
+ */
+//$cfg['DefaultLang'] = 'en';
+//$cfg['DefaultLang'] = 'de';
+
+/**
+ * How many columns should be used for table display of a database?
+ * (a value larger than 1 results in some information being hidden)
+ * default = 1
+ */
+//$cfg['PropertiesNumColumns'] = 2;
+
+/**
+ * Set to true if you want DB-based query history.If false, this utilizes
+ * JS-routines to display query history (lost by window close)
+ *
+ * This requires configuration storage enabled, see above.
+ * default = false
+ */
+//$cfg['QueryHistoryDB'] = true;
+
+/**
+ * When using DB-based query history, how many entries should be kept?
+ * default = 25
+ */
+//$cfg['QueryHistoryMax'] = 100;
+
+/**
+ * Whether or not to query the user before sending the error report to
+ * the phpMyAdmin team when a JavaScript error occurs
+ *
+ * Available options
+ * ('ask' | 'always' | 'never')
+ * default = 'ask'
+ */
+//$cfg['SendErrorReports'] = 'always';
+
+/**
+ * You can find more configuration options in the documentation
+ * in the doc/ folder or at <https://docs.phpmyadmin.net/>.
+ */
+
+
+
+

Warning

+

Don’t use the controluser ‘pma’ if it does not yet exist and don’t use ‘pmapass’ +as password.

+
+
+
+

Example for signon authentication

+

This example uses examples/signon.php to demonstrate usage of Signon authentication mode:

+
<?php
+$i = 0;
+$i++;
+$cfg['Servers'][$i]['extension']     = 'mysqli';
+$cfg['Servers'][$i]['auth_type']     = 'signon';
+$cfg['Servers'][$i]['SignonSession'] = 'SignonSession';
+$cfg['Servers'][$i]['SignonURL']     = 'examples/signon.php';
+?>`
+
+
+
+
+

Example for IP address limited autologin

+

If you want to automatically login when accessing phpMyAdmin locally while asking +for a password when accessing remotely, you can achieve it using following snippet:

+
if ($_SERVER["REMOTE_ADDR"] == "127.0.0.1") {
+    $cfg['Servers'][$i]['auth_type'] = 'config';
+    $cfg['Servers'][$i]['user'] = 'root';
+    $cfg['Servers'][$i]['password'] = 'yourpassword';
+} else {
+    $cfg['Servers'][$i]['auth_type'] = 'cookie';
+}
+
+
+
+

Note

+

Filtering based on IP addresses isn’t reliable over the internet, use it +only for local address.

+
+
+
+

Example for using multiple MySQL servers

+

You can configure any number of servers using $cfg['Servers'], +following example shows two of them:

+
<?php
+$cfg['blowfish_secret']='multiServerExample70518';
+//any string of your choice
+$i = 0;
+
+$i++; // server 1 :
+$cfg['Servers'][$i]['auth_type'] = 'cookie';
+$cfg['Servers'][$i]['verbose']   = 'no1';
+$cfg['Servers'][$i]['host']      = 'localhost';
+$cfg['Servers'][$i]['extension'] = 'mysqli';
+// more options for #1 ...
+
+$i++; // server 2 :
+$cfg['Servers'][$i]['auth_type'] = 'cookie';
+$cfg['Servers'][$i]['verbose']   = 'no2';
+$cfg['Servers'][$i]['host']      = 'remote.host.addr';//or ip:'10.9.8.1'
+// this server must allow remote clients, e.g., host 10.9.8.%
+// not only in mysql.host but also in the startup configuration
+$cfg['Servers'][$i]['extension'] = 'mysqli';
+// more options for #2 ...
+
+// end of server sections
+$cfg['ServerDefault'] = 0; // to choose the server on startup
+
+// further general options ...
+?>
+
+
+
+
+

Google Cloud SQL with SSL

+

To connect to Google Could SQL, you currently need to disable certificate +verification. This is caused by the certficate being issued for CN matching +your instance name, but you connect to an IP address and PHP tries to match +these two. With verfication you end up with error message like:

+
Peer certificate CN=`api-project-851612429544:pmatest' did not match expected CN=`8.8.8.8'
+
+
+
+

Warning

+

With disabled verification your traffic is encrypted, but you’re open to +man in the middle attacks.

+
+

To connect phpMyAdmin to Google Cloud SQL using SSL download the client and +server certificates and tell phpMyAdmin to use them:

+
// IP address of your instance
+$cfg['Servers'][$i]['host'] = '8.8.8.8';
+// Use SSL for connection
+$cfg['Servers'][$i]['ssl'] = true;
+// Client secret key
+$cfg['Servers'][$i]['ssl_key'] = '../client-key.pem';
+// Client certificate
+$cfg['Servers'][$i]['ssl_cert'] = '../client-cert.pem';
+// Server certification authority
+$cfg['Servers'][$i]['ssl_ca'] = '../server-ca.pem';
+// Disable SSL verification (see above note)
+$cfg['Servers'][$i]['ssl_verify'] = false;
+
+
+ +
+
+
+ + +
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/php/apps/phpmyadmin49/doc/html/copyright.html b/php/apps/phpmyadmin49/doc/html/copyright.html new file mode 100644 index 00000000..8dd4c73c --- /dev/null +++ b/php/apps/phpmyadmin49/doc/html/copyright.html @@ -0,0 +1,151 @@ + + + + + + + + Copyright — phpMyAdmin 4.9.1 documentation + + + + + + + + + + + + + + + + + + +
+
+
+
+ + + + +
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/php/apps/phpmyadmin49/doc/html/credits.html b/php/apps/phpmyadmin49/doc/html/credits.html new file mode 100644 index 00000000..f64b21c4 --- /dev/null +++ b/php/apps/phpmyadmin49/doc/html/credits.html @@ -0,0 +1,1353 @@ + + + + + + + + Credits — phpMyAdmin 4.9.1 documentation + + + + + + + + + + + + + + + + + + +
+
+
+
+ +
+

Credits

+
+

Credits, in chronological order

+
    +
  • Tobias Ratschiller <tobias_at_ratschiller.com>
      +
    • creator of the phpMyAdmin project
    • +
    • maintainer from 1998 to summer 2000
    • +
    +
  • +
  • Marc Delisle <marc_at_infomarc.info>
      +
    • multi-language version in December 1998
    • +
    • various fixes and improvements
    • +
    • first version of the SQL analyser (most of it)
    • +
    • maintainer from 2001 to 2015
    • +
    +
  • +
  • Olivier Müller <om_at_omnis.ch>
      +
    • started SourceForge phpMyAdmin project in March 2001
    • +
    • sync’ed different existing CVS trees with new features and bugfixes
    • +
    • multi-language improvements, dynamic language selection
    • +
    • many bugfixes and improvements
    • +
    +
  • +
  • Loïc Chapeaux <lolo_at_phpheaven.net>
      +
    • rewrote and optimized JavaScript, DHTML and DOM stuff
    • +
    • rewrote the scripts so they fit the PEAR coding standards and +generate XHTML1.0 and CSS2 compliant codes
    • +
    • improved the language detection system
    • +
    • many bugfixes and improvements
    • +
    +
  • +
  • Robin Johnson <robbat2_at_users.sourceforge.net>
      +
    • database maintenance controls
    • +
    • table type code
    • +
    • Host authentication IP Allow/Deny
    • +
    • DB-based configuration (Not completed)
    • +
    • SQL parser and pretty-printer
    • +
    • SQL validator
    • +
    • many bugfixes and improvements
    • +
    +
  • +
  • Armel Fauveau <armel.fauveau_at_globalis-ms.com>
      +
    • bookmarks feature
    • +
    • multiple dump feature
    • +
    • gzip dump feature
    • +
    • zip dump feature
    • +
    +
  • +
  • Geert Lund <glund_at_silversoft.dk>
      +
    • various fixes
    • +
    • moderator of the phpMyAdmin former users forum at phpwizard.net
    • +
    +
  • +
  • Korakot Chaovavanich <korakot_at_iname.com>
      +
    • “insert as new row” feature
    • +
    +
  • +
  • Pete Kelly <webmaster_at_trafficg.com>
      +
    • rewrote and fix dump code
    • +
    • bugfixes
    • +
    +
  • +
  • Steve Alberty <alberty_at_neptunlabs.de>
      +
    • rewrote dump code for PHP4
    • +
    • mySQL table statistics
    • +
    • bugfixes
    • +
    +
  • +
  • Benjamin Gandon <gandon_at_isia.cma.fr>
      +
    • main author of the version 2.1.0.1
    • +
    • bugfixes
    • +
    +
  • +
  • Alexander M. Turek <me_at_derrabus.de>
      +
    • MySQL 4.0 / 4.1 / 5.0 compatibility
    • +
    • abstract database interface (PMA_DBI) with MySQLi support
    • +
    • privileges administration
    • +
    • XML exports
    • +
    • various features and fixes
    • +
    • German language file updates
    • +
    +
  • +
  • Mike Beck <mike.beck_at_web.de>
      +
    • automatic joins in QBE
    • +
    • links column in printview
    • +
    • Relation view
    • +
    +
  • +
  • Michal Čihař <michal_at_cihar.com>
      +
    • enhanced index creation/display feature
    • +
    • feature to use a different charset for HTML than for MySQL
    • +
    • improvements of export feature
    • +
    • various features and fixes
    • +
    • Czech language file updates
    • +
    • created current website for phpMyAdmin
    • +
    +
  • +
  • Christophe Gesché from the “MySQL Form Generator for PHPMyAdmin” +(https://sourceforge.net/projects/phpmysqlformgen/)
      +
    • suggested the patch for multiple table printviews
    • +
    +
  • +
  • Garvin Hicking <me_at_supergarv.de>
      +
    • built the patch for vertical display of table rows
    • +
    • built the Javascript based Query window + SQL history
    • +
    • Improvement of column/db comments
    • +
    • (MIME)-Transformations for columns
    • +
    • Use custom alias names for Databases in left frame
    • +
    • hierarchical/nested table display
    • +
    • PDF-scratchboard for WYSIWYG- +distribution of PDF relations
    • +
    • new icon sets
    • +
    • vertical display of column properties page
    • +
    • some bugfixes, features, support, German language additions
    • +
    +
  • +
  • Yukihiro Kawada <kawada_at_den.fujifilm.co.jp>
      +
    • japanese kanji encoding conversion feature
    • +
    +
  • +
  • Piotr Roszatycki <d3xter_at_users.sourceforge.net> and Dan Wilson
      +
    • the Cookie authentication mode
    • +
    +
  • +
  • Axel Sander <n8falke_at_users.sourceforge.net>
      +
    • table relation-links feature
    • +
    +
  • +
  • Maxime Delorme <delorme.maxime_at_free.fr>
      +
    • PDF schema output, thanks also to +Olivier Plathey for the “FPDF” library (see <http://www.fpdf.org/>), Steven +Wittens for the “UFPDF” library and +Nicola Asuni for the “TCPDF” library (see <https://tcpdf.org/>).
    • +
    +
  • +
  • Olof Edlund <olof.edlund_at_upright.se>
      +
    • SQL validator server
    • +
    +
  • +
  • Ivan R. Lanin <ivanlanin_at_users.sourceforge.net>
      +
    • phpMyAdmin logo (until June 2004)
    • +
    +
  • +
  • Mike Cochrane <mike_at_graftonhall.co.nz>
      +
    • blowfish library from the Horde project (withdrawn in release 4.0)
    • +
    +
  • +
  • Marcel Tschopp <ne0x_at_users.sourceforge.net>
      +
    • mysqli support
    • +
    • many bugfixes and improvements
    • +
    +
  • +
  • Nicola Asuni (Tecnick.com) +
  • +
  • Michael Keck <mkkeck_at_users.sourceforge.net>
      +
    • redesign for 2.6.0
    • +
    • phpMyAdmin sailboat logo (June 2004)
    • +
    +
  • +
  • Mathias Landhäußer
      +
    • Representation at conferences
    • +
    +
  • +
  • Sebastian Mendel <cybot_tm_at_users.sourceforge.net>
      +
    • interface improvements
    • +
    • various bugfixes
    • +
    +
  • +
  • Ivan A Kirillov
      +
    • new relations Designer
    • +
    +
  • +
  • Raj Kissu Rajandran (Google Summer of Code 2008)
      +
    • BLOBstreaming support (withdrawn in release 4.0)
    • +
    +
  • +
  • Piotr Przybylski (Google Summer of Code 2008, 2010 and 2011)
      +
    • improved setup script
    • +
    • user preferences
    • +
    • Drizzle support
    • +
    +
  • +
  • Derek Schaefer (Google Summer of Code 2009)
      +
    • Improved the import system
    • +
    +
  • +
  • Alexander Rutkowski (Google Summer of Code 2009)
      +
    • Tracking mechanism
    • +
    +
  • +
  • Zahra Naeem (Google Summer of Code 2009)
      +
    • Synchronization feature (removed in release 4.0)
    • +
    +
  • +
  • Tomáš Srnka (Google Summer of Code 2009)
      +
    • Replication support
    • +
    +
  • +
  • Muhammad Adnan (Google Summer of Code 2010)
      +
    • Relation schema export to multiple formats
    • +
    +
  • +
  • Lori Lee (Google Summer of Code 2010)
      +
    • User interface improvements
    • +
    • ENUM/SET editor
    • +
    • Simplified interface for export/import
    • +
    +
  • +
  • Ninad Pundalik (Google Summer of Code 2010)
      +
    • AJAXifying the interface
    • +
    +
  • +
  • Martynas Mickevičius (Google Summer of Code 2010)
      +
    • Charts
    • +
    +
  • +
  • Barrie Leslie
      +
    • BLOBstreaming support with PBMS PHP extension (withdrawn in release +4.0)
    • +
    +
  • +
  • Ankit Gupta (Google Summer of Code 2010)
      +
    • Visual query builder
    • +
    +
  • +
  • Madhura Jayaratne (Google Summer of Code 2011)
      +
    • OpenGIS support
    • +
    +
  • +
  • Ammar Yasir (Google Summer of Code 2011)
      +
    • Zoom search
    • +
    +
  • +
  • Aris Feryanto (Google Summer of Code 2011)
      +
    • Browse-mode improvements
    • +
    +
  • +
  • Thilanka Kaushalya (Google Summer of Code 2011)
      +
    • AJAXification
    • +
    +
  • +
  • Tyron Madlener (Google Summer of Code 2011)
      +
    • Query statistics and charts for the status page
    • +
    +
  • +
  • Zarubin Stas (Google Summer of Code 2011)
      +
    • Automated testing
    • +
    +
  • +
  • Rouslan Placella (Google Summer of Code 2011 and 2012)
      +
    • Improved support for Stored Routines, Triggers and Events
    • +
    • Italian translation updates
    • +
    • Removal of frames, new navigation
    • +
    +
  • +
  • Dieter Adriaenssens
      +
    • Various bugfixes
    • +
    • Dutch translation updates
    • +
    +
  • +
  • Alex Marin (Google Summer of Code 2012)
      +
    • New plugins and properties system
    • +
    +
  • +
  • Thilina Buddika Abeyrathna (Google Summer of Code 2012)
      +
    • Refactoring
    • +
    +
  • +
  • Atul Pratap Singh (Google Summer of Code 2012)
      +
    • Refactoring
    • +
    +
  • +
  • Chanaka Indrajith (Google Summer of Code 2012)
      +
    • Refactoring
    • +
    +
  • +
  • Yasitha Pandithawatta (Google Summer of Code 2012)
      +
    • Automated testing
    • +
    +
  • +
  • Jim Wigginton (phpseclib.sourceforge.net)
      +
    • phpseclib
    • +
    +
  • +
  • Bin Zu (Google Summer of Code 2013)
      +
    • Refactoring
    • +
    +
  • +
  • Supun Nakandala (Google Summer of Code 2013)
      +
    • Refactoring
    • +
    +
  • +
  • Mohamed Ashraf (Google Summer of Code 2013)
      +
    • AJAX error reporting
    • +
    +
  • +
  • Adam Kang (Google Summer of Code 2013)
      +
    • Automated testing
    • +
    +
  • +
  • Ayush Chaudhary (Google Summer of Code 2013)
      +
    • Automated testing
    • +
    +
  • +
  • Kasun Chathuranga (Google Summer of Code 2013)
      +
    • Interface improvements
    • +
    +
  • +
  • Hugues Peccatte
      +
    • Load/save query by example (database search bookmarks)
    • +
    +
  • +
  • Smita Kumari (Google Summer of Code 2014)
      +
    • Central list of columns
    • +
    • Improve table structure (normalization)
    • +
    +
  • +
  • Ashutosh Dhundhara (Google Summer of Code 2014)
      +
    • Interface improvements
    • +
    +
  • +
  • Dhananjay Nakrani (Google Summer of Code 2014)
      +
    • PHP error reporting
    • +
    +
  • +
  • Edward Cheng (Google Summer of Code 2014)
      +
    • SQL Query Console
    • +
    +
  • +
  • Kankanamge Bimal Yashodha (Google Summer of Code 2014)
      +
    • Refactoring: Designer/schema integration
    • +
    +
  • +
  • Chirayu Chiripal (Google Summer of Code 2014)
      +
    • Custom field handlers (Input based MIME transformations)
    • +
    • Export with table/column name changes
    • +
    +
  • +
  • Dan Ungureanu (Google Summer of Code 2015)
      +
    • New parser and analyzer
    • +
    +
  • +
  • Nisarg Jhaveri (Google Summer of Code 2015)
      +
    • Page-related settings
    • +
    • SQL debugging integration to the Console
    • +
    • Other UI improvements
    • +
    +
  • +
  • Deven Bansod (Google Summer of Code 2015)
      +
    • Print view using CSS
    • +
    • Other UI improvements and new features
    • +
    +
  • +
  • Deven Bansod (Google Summer of Code 2017)
      +
    • Improvements to the Error Reporting Server
    • +
    • Improved Selenium testing
    • +
    +
  • +
  • Manish Bisht (Google Summer of Code 2017)
      +
    • Mobile user interface
    • +
    • Remove inline JavaScript code
    • +
    • Other UI improvements
    • +
    +
  • +
  • Raghuram Vadapalli (Google Summer of Code 2017)
      +
    • Multi-table query interface
    • +
    • Allow Designer to work with tables from other databases
    • +
    • Other UI improvements
    • +
    +
  • +
+

And also to the following people who have contributed minor changes, +enhancements, bugfixes or support for a new language since version +2.1.0:

+

Bora Alioglu, Ricardo ?, Sven-Erik Andersen, Alessandro Astarita, +Péter Bakondy, Borges Botelho, Olivier Bussier, Neil Darlow, Mats +Engstrom, Ian Davidson, Laurent Dhima, Kristof Hamann, Thomas Kläger, +Lubos Klokner, Martin Marconcini, Girish Nair, David Nordenberg, +Andreas Pauley, Bernard M. Piller, Laurent Haas, “Sakamoto”, Yuval +Sarna, www.securereality.com.au, Alexis Soulard, Alvar Soome, Siu Sun, +Peter Svec, Michael Tacelosky, Rachim Tamsjadi, Kositer Uros, Luís V., +Martijn W. van der Lee, Algis Vainauskas, Daniel Villanueva, Vinay, +Ignacio Vazquez-Abrams, Chee Wai, Jakub Wilk, Thomas Michael +Winningham, Vilius Zigmantas, “Manuzhai”.

+
+
+

Translators

+

Following people have contributed to translation of phpMyAdmin:

+
    +
  • Albanian

    +
    +
      +
    • Arben Çokaj <acokaj_at_shkoder.net>
    • +
    +
    +
  • +
  • Arabic

    +
    +
      +
    • Ahmed Saleh Abd El-Raouf Ismae <a.saleh.ismael_at_gmail.com>
    • +
    • Ahmed Saad <egbrave_at_hotmail.com>
    • +
    • hassan mokhtari <persiste1_at_gmail.com>
    • +
    +
    +
  • +
  • Armenian

    +
    +
      +
    • Andrey Aleksanyants <aaleksanyants_at_yahoo.com>
    • +
    +
    +
  • +
  • Azerbaijani

    +
    +
      +
    • Mircəlal <01youknowme_at_gmail.com>
    • +
    • Huseyn <huseyn_esgerov_at_mail.ru>
    • +
    • Sevdimali İsa <sevdimaliisayev_at_mail.ru>
    • +
    • Jafar <sharifov_at_programmer.net>
    • +
    +
    +
  • +
  • Belarusian

    +
    +
      +
    • Viktar Palstsiuk <vipals_at_gmail.com>
    • +
    +
    +
  • +
  • Bulgarian

    +
    +
      +
    • Boyan Kehayov <bkehayov_at_gmail.com>
    • +
    • Valter Georgiev <blagynchy_at_gmail.com>
    • +
    • Valentin Mladenov <hudsonvsm_at_gmail.com>
    • +
    • P <plamen_mbx_at_yahoo.com>
    • +
    • krasimir <vip_at_krasio-valia.com>
    • +
    +
    +
  • +
  • Catalan

    +
    +
      +
    • josep constanti <jconstanti_at_yahoo.es>
    • +
    • Xavier Navarro <xvnavarro_at_gmail.com>
    • +
    +
    +
  • +
  • Chinese (China)

    +
    +
      +
    • Vincent Lau <3092849_at_qq.com>
    • +
    • Zheng Dan <clanboy_at_163.com>
    • +
    • disorderman <disorderman_at_qq.com>
    • +
    • Rex Lee <duguying2008_at_gmail.com>
    • +
    • <fundawang_at_gmail.com>
    • +
    • popcorner <memoword_at_163.com>
    • +
    • Yizhou Qiang <qyz.yswy_at_hotmail.com>
    • +
    • zz <tczzjin_at_gmail.com>
    • +
    • Terry Weng <wengshiyu_at_gmail.com>
    • +
    • whh <whhlcj_at_126.com>
    • +
    +
    +
  • +
  • Chinese (Taiwan)

    +
    +
      +
    • Albert Song <albb0920_at_gmail.com>
    • +
    • Chien Wei Lin <cwlin0416_at_gmail.com>
    • +
    • Peter Dave Hello <xs910203_at_gmail.com>
    • +
    +
    +
  • +
  • Colognian

    +
    +
      +
    • Purodha <publi_at_web.de>
    • +
    +
    +
  • +
  • Czech

    +
    +
      +
    • Aleš Hakl <ales_at_hakl.net>
    • +
    • Dalibor Straka <dalibor.straka3_at_gmail.com>
    • +
    • Martin Vidner <martin_at_vidner.net>
    • +
    • Ondra Šimeček <ondrasek.simecek_at_gmail.com>
    • +
    • Jan Palider <palider_at_seznam.cz>
    • +
    • Petr Kateřiňák <petr.katerinak_at_gmail.com>
    • +
    +
    +
  • +
  • Danish

    +
    +
      +
    • Aputsiaĸ Niels Janussen <aj_at_isit.gl>
    • +
    • Dennis Jakobsen <dennis.jakobsen_at_gmail.com>
    • +
    • Jonas <jonas.den.smarte_at_gmail.com>
    • +
    • Claus Svalekjaer <just.my.smtp.server_at_gmail.com>
    • +
    +
    +
  • +
  • Dutch

    +
    +
      +
      1. +
      2. Voogt <a.voogt_at_hccnet.nl>
      3. +
      +
    • +
    • dingo thirteen <dingo13_at_gmail.com>
    • +
    • Robin van der Vliet <info_at_robinvandervliet.nl>
    • +
    • Dieter Adriaenssens <ruleant_at_users.sourceforge.net>
    • +
    • Niko Strijbol <strijbol.niko_at_gmail.com>
    • +
    +
    +
  • +
  • English (United Kingdom)

    +
    +
      +
    • Dries Verschuere <dries.verschuere_at_outlook.com>
    • +
    • Francisco Rocha <j.francisco.o.rocha_at_zoho.com>
    • +
    • Marc Delisle <marc_at_infomarc.info>
    • +
    • Marek Tomaštík <tomastik.m_at_gmail.com>
    • +
    +
    +
  • +
  • Esperanto

    +
    +
      +
    • Eliovir <eliovir_at_gmail.com>
    • +
    • Robin van der Vliet <info_at_robinvandervliet.nl>
    • +
    +
    +
  • +
  • Estonian

    +
    +
      +
    • Kristjan Räts <kristjanrats_at_gmail.com>
    • +
    +
    +
  • +
  • Finnish

    +
    +
      +
    • Juha Remes <jremes_at_outlook.com>
    • +
    • Lari Oesch <lari_at_oesch.me>
    • +
    +
    +
  • +
  • French

    +
    +
      +
    • Marc Delisle <marc_at_infomarc.info>
    • +
    +
    +
  • +
  • Frisian

    +
    +
      +
    • Robin van der Vliet <info_at_robinvandervliet.nl>
    • +
    +
    +
  • +
  • Galician

    +
    +
      +
    • Xosé Calvo <xosecalvo_at_gmail.com>
    • +
    +
    +
  • +
  • German

    +
    +
      +
    • Julian Ladisch <github.com-t3if_at_ladisch.de>
    • +
    • Jan Erik Zassenhaus <jan.zassenhaus_at_jgerman.de>
    • +
    • Lasse Goericke <lasse_at_mydom.de>
    • +
    • Matthias Bluthardt <matthias_at_bluthardt.org>
    • +
    • Michael Koch <michael.koch_at_enough.de>
    • +
    • Ann + J.M. <phpMyAdmin_at_ZweiSteinSoft.de>
    • +
    • <pma_at_sebastianmendel.de>
    • +
    • Phillip Rohmberger <rohmberger_at_hotmail.de>
    • +
    • Hauke Henningsen <sqrt_at_entless.org>
    • +
    +
    +
  • +
  • Greek

    +
    +
      +
    • Παναγιώτης Παπάζογλου <papaz_p_at_yahoo.com>
    • +
    +
    +
  • +
  • Hebrew

    +
    +
      +
    • Moshe Harush <mmh15_at_windowslive.com>
    • +
    • Yaron Shahrabani <sh.yaron_at_gmail.com>
    • +
    • Eyal Visoker <visokereyal_at_gmail.com>
    • +
    +
    +
  • +
  • Hindi

    +
    +
      +
    • Atul Pratap Singh <atulpratapsingh05_at_gmail.com>
    • +
    • Yogeshwar <charanyogeshwar_at_gmail.com>
    • +
    • Deven Bansod <devenbansod.bits_at_gmail.com>
    • +
    • Kushagra Pandey <kushagra4296_at_gmail.com>
    • +
    • Nisarg Jhaveri <nisargjhaveri_at_gmail.com>
    • +
    • Roohan Kazi <roohan_cena_at_yahoo.co.in>
    • +
    • Yugal Pantola <yug.scorpio_at_gmail.com>
    • +
    +
    +
  • +
  • Hungarian

    +
    +
      +
    • Akos Eros <erosakos02_at_gmail.com>
    • +
    • Dániel Tóth <leedermeister_at_gmail.com>
    • +
    • Szász Attila <undernetangel_at_gmail.com>
    • +
    • Balázs Úr <urbalazs_at_gmail.com>
    • +
    +
    +
  • +
  • Indonesian

    +
    +
      +
    • Deky Arifianto <Deky40_at_gmail.com>
    • +
    • Andika Triwidada <andika_at_gmail.com>
    • +
    • Dadan Setia <da2n_s_at_yahoo.co.id>
    • +
    • Dadan Setia <dadan.setia_at_gmail.com>
    • +
    • Yohanes Edwin <edwin_at_yohanesedwin.com>
    • +
    • Fadhiil Rachman <fadhiilrachman_at_gmail.com>
    • +
    • Benny <tarzq28_at_gmail.com>
    • +
    • Tommy Surbakti <tommy_at_surbakti.net>
    • +
    • Zufar Fathi Suhardi <zufar.bogor_at_gmail.com>
    • +
    +
    +
  • +
  • Interlingua

    +
    +
      +
    • Giovanni Sora <g.sora_at_tiscali.it>
    • +
    +
    +
  • +
  • Italian

    +
    +
      +
    • Francesco Saverio Giacobazzi <francesco.giacobazzi_at_ferrania.it>
    • +
    • Marco Pozzato <ironpotts_at_gmail.com>
    • +
    • Stefano Martinelli <stefano.ste.martinelli_at_gmail.com>
    • +
    +
    +
  • +
  • Japanese

    +
    +
      +
    • k725 <alexalex.kobayashi_at_gmail.com>
    • +
    • Hiroshi Chiyokawa <hiroshi.chiyokawa_at_gmail.com>
    • +
    • Masahiko HISAKAWA <orzkun_at_ageage.jp>
    • +
    • worldwideskier <worldwideskier_at_yahoo.co.jp>
    • +
    +
    +
  • +
  • Kannada

    +
    +
      +
    • Robin van der Vliet <info_at_robinvandervliet.nl>
    • +
    • Shameem Ahmed A Mulla <shameem.sam_at_gmail.com>
    • +
    +
    +
  • +
  • Korean

    +
    +
      +
    • Bumsoo Kim <bskim45_at_gmail.com>
    • +
    • Kyeong Su Shin <cdac1234_at_gmail.com>
    • +
    • Dongyoung Kim <dckyoung_at_gmail.com>
    • +
    • Myung-han Yu <greatymh_at_gmail.com>
    • +
    • JongDeok <human.zion_at_gmail.com>
    • +
    • Yong Kim <kim_at_nhn.com>
    • +
    • 이경준 <kyungjun2_at_gmail.com>
    • +
    • Seongki Shin <skshin_at_gmail.com>
    • +
    • Yoon Bum-Jong <virusyoon_at_gmail.com>
    • +
    • Koo Youngmin <youngminz.kr_at_gmail.com>
    • +
    +
    +
  • +
  • Kurdish Sorani

    +
    +
      +
    • Alan Hilal <alan.hilal94_at_gmail.com>
    • +
    • Aso Naderi <aso.naderi_at_gmail.com>
    • +
    • muhammad <esy_vb_at_yahoo.com>
    • +
    • Zrng Abdulla <zhyarabdulla94_at_gmail.com>
    • +
    +
    +
  • +
  • Latvian

    +
    +
      +
    • Latvian TV <dnighttv_at_gmail.com>
    • +
    • Edgars Neimanis <edgarsneims5092_at_inbox.lv>
    • +
    • Ukko <perkontevs_at_gmail.com>
    • +
    +
    +
  • +
  • Limburgish

    +
    +
      +
    • Robin van der Vliet <info_at_robinvandervliet.nl>
    • +
    +
    +
  • +
  • Lithuanian

    +
    +
      +
    • Vytautas Motuzas <v.motuzas_at_gmail.com>
    • +
    +
    +
  • +
  • Malay

    +
    +
      +
    • Amir Hamzah <amir.overlord666_at_gmail.com>
    • +
    • diprofinfiniti <anonynuine-999_at_yahoo.com>
    • +
    +
    +
  • +
  • Nepali

    +
    +
      +
    • Nabin Ghimire <nnabinn_at_hotmail.com>
    • +
    +
    +
  • +
  • Norwegian Bokmål

    +
    +
      +
    • Børge Holm-Wennberg <borge947_at_gmail.com>
    • +
    • Tor Stokkan <danorse_at_gmail.com>
    • +
    • Espen Frøyshov <efroys_at_gmail.com>
    • +
    • Kurt Eilertsen <kurt_at_kheds.com>
    • +
    • Christoffer Haugom <ph3n1x.nobody_at_gmail.com>
    • +
    • Sebastian <sebastian_at_sgundersen.com>
    • +
    • Tomas <tomas_at_tomasruud.com>
    • +
    +
    +
  • +
  • Persian

    +
    +
      +
    • ashkan shirian <ashkan.shirian_at_gmail.com>
    • +
    • HM <goodlinuxuser_at_chmail.ir>
    • +
    +
    +
  • +
  • Polish

    +
    +
      +
    • Andrzej <andrzej_at_kynu.pl>
    • +
    • Przemo <info_at_opsbielany.waw.pl>
    • +
    • Krystian Biesaga <krystian4842_at_gmail.com>
    • +
    • Maciej Gryniuk <maciejka45_at_gmail.com>
    • +
    • Michał VonFlynee <vonflynee_at_gmail.com>
    • +
    +
    +
  • +
  • Portuguese

    +
    +
      +
    • Alexandre Badalo <alexandre.badalo_at_sapo.pt>
    • +
    • João Rodrigues <geral_at_jonilive.com>
    • +
    • Pedro Ribeiro <p.m42.ribeiro_at_gmail.com>
    • +
    • Sandro Amaral <sandro123iv_at_gmail.com>
    • +
    +
    +
  • +
  • Portuguese (Brazil)

    +
    +
      +
    • Alex Rohleder <alexrohleder96_at_outlook.com>
    • +
    • bruno mendax <brunomendax_at_gmail.com>
    • +
    • Danilo GUia <danilo.eng_at_globomail.com>
    • +
    • Douglas Rafael Morais Kollar <douglas.kollar_at_pg.df.gov.br>
    • +
    • Douglas Eccker <douglaseccker_at_hotmail.com>
    • +
    • Ed Jr <edjacobjunior_at_gmail.com>
    • +
    • Guilherme Souza Silva <g.szsilva_at_gmail.com>
    • +
    • Guilherme Seibt <gui_at_webseibt.net>
    • +
    • Helder Santana <helder.bs.santana_at_gmail.com>
    • +
    • Junior Zancan <jrzancan_at_hotmail.com>
    • +
    • Luis <luis.eduardo.braschi_at_outlook.com>
    • +
    • Marcos Algeri <malgeri_at_gmail.com>
    • +
    • Marc Delisle <marc_at_infomarc.info>
    • +
    • Renato Rodrigues de Lima Júnio <renatomdd_at_yahoo.com.br>
    • +
    • Thiago Casotti <thiago.casotti_at_uol.com.br>
    • +
    • Victor Laureano <victor.laureano_at_gmail.com>
    • +
    • Vinícius Araújo <vinipitta_at_gmail.com>
    • +
    • Washington Bruno Rodrigues Cav <washingtonbruno_at_msn.com>
    • +
    • Yan Gabriel <yansilvagabriel_at_gmail.com>
    • +
    +
    +
  • +
  • Punjabi

    +
    +
      +
    • Robin van der Vliet <info_at_robinvandervliet.nl>
    • +
    +
    +
  • +
  • Romanian

    +
    +
      +
    • Alex <amihaita_at_yahoo.com>
    • +
    • Costel Cocerhan <costa1988sv_at_gmail.com>
    • +
    • Ion Adrian-Ionut <john_at_panevo.ro>
    • +
    • Raul Molnar <molnar.raul_at_wservices.eu>
    • +
    • Deleted User <noreply_at_weblate.org>
    • +
    • Stefan Murariu <stefan.murariu_at_yahoo.com>
    • +
    +
    +
  • +
  • Russian

    +
    +
      +
    • Andrey Aleksanyants <aaleksanyants_at_yahoo.com>
    • +
    • <ddrmoscow_at_gmail.com>
    • +
    • Robin van der Vliet <info_at_robinvandervliet.nl>
    • +
    • Хомутов Иван Сергеевич <khomutov.ivan_at_mail.ru>
    • +
    • Alexey Rubinov <orion1979_at_yandex.ru>
    • +
    • Олег Карпов <salvadoporjc_at_gmail.com>
    • +
    • Egorov Artyom <unlucky_at_inbox.ru>
    • +
    +
    +
  • +
  • Serbian

    +
    +
      +
    • Smart Kid <kidsmart33_at_gmail.com>
    • +
    +
    +
  • +
  • Sinhala

    +
    +
      +
    • Madhura Jayaratne <madhura.cj_at_gmail.com>
    • +
    +
    +
  • +
  • Slovak

    +
    +
      +
    • Martin Lacina <martin_at_whistler.sk>
    • +
    • Patrik Kollmann <parkourpotex_at_gmail.com>
    • +
    • Jozef Pistej <pistej2_at_gmail.com>
    • +
    +
    +
  • +
  • Slovenian

    +
    +
      +
    • Domen <mitenem_at_outlook.com>
    • +
    +
    +
  • +
  • Spanish

    +
    +
      +
    • Luis García Sevillano <floss.dev_at_gmail.com>
    • +
    • Franco <fulanodetal.github1_at_openaliasbox.org>
    • +
    • Luis Ruiz <luisan00_at_hotmail.com>
    • +
    • Macofe <macofe.languagetool_at_gmail.com>
    • +
    • Matías Bellone <matiasbellone+weblate_at_gmail.com>
    • +
    • Rodrigo A. <ra4_at_openmailbox.org>
    • +
    • FAMMA TV NOTICIAS MEDIOS DE CO <revistafammatvmusic.oficial_at_gmail.com>
    • +
    • Ronnie Simon <ronniesimonf_at_gmail.com>
    • +
    +
    +
  • +
  • Swedish

    +
    +
      +
    • Anders Jonsson <anders.jonsson_at_norsjovallen.se>
    • +
    +
    +
  • +
  • Tamil

    +
    +
      +
    • கணேஷ் குமார் <GANESHTHEONE_at_gmail.com>
    • +
    • Achchuthan Yogarajah <achch1990_at_gmail.com>
    • +
    • Rifthy Ahmed <rifthy456_at_gmail.com>
    • +
    +
    +
  • +
  • Thai

    +
    +
      +
    • <nontawat39_at_gmail.com>
    • +
    • Somthanat W. <somthanat_at_gmail.com>
    • +
    +
    +
  • +
  • Turkish

    +
    +
      +
    • Burak Yavuz <hitowerdigit_at_hotmail.com>
    • +
    +
    +
  • +
  • Ukrainian

    +
    +
      +
    • Сергій Педько <nitrotoll_at_gmail.com>
    • +
    • Igor <vmta_at_yahoo.com>
    • +
    • Vitaliy Perekupka <vperekupka_at_gmail.com>
    • +
    +
    +
  • +
  • Vietnamese

    +
    +
      +
    • Bao Phan <baophan94_at_icloud.com>
    • +
    • Xuan Hung <mr.hungdx_at_gmail.com>
    • +
    • Bao trinh minh <trinhminhbao_at_gmail.com>
    • +
    +
    +
  • +
  • West Flemish

    +
    +
      +
    • Robin van der Vliet <info_at_robinvandervliet.nl>
    • +
    +
    +
  • +
+
+
+

Documentation translators

+

Following people have contributed to translation of phpMyAdmin documentation:

+
    +
  • Albanian

    +
    +
      +
    • Arben Çokaj <acokaj_at_shkoder.net>
    • +
    +
    +
  • +
  • Arabic

    +
    +
      +
    • Ahmed El Azzabi <ahmedtek1993_at_gmail.com>
    • +
    • Omar Essam <omar_2412_at_live.com>
    • +
    +
    +
  • +
  • Armenian

    +
    +
      +
    • Andrey Aleksanyants <aaleksanyants_at_yahoo.com>
    • +
    +
    +
  • +
  • Azerbaijani

    +
    +
      +
    • Mircəlal <01youknowme_at_gmail.com>
    • +
    • Sevdimali İsa <sevdimaliisayev_at_mail.ru>
    • +
    +
    +
  • +
  • Catalan

    +
    +
      +
    • josep constanti <jconstanti_at_yahoo.es>
    • +
    • Joan Montané <joan_at_montane.cat>
    • +
    • Xavier Navarro <xvnavarro_at_gmail.com>
    • +
    +
    +
  • +
  • Chinese (China)

    +
    +
      +
    • Vincent Lau <3092849_at_qq.com>
    • +
    • 罗攀登 <6375lpd_at_gmail.com>
    • +
    • disorderman <disorderman_at_qq.com>
    • +
    • ITXiaoPang <djh1017555_at_126.com>
    • +
    • tunnel213 <tunnel213_at_aliyun.com>
    • +
    • Terry Weng <wengshiyu_at_gmail.com>
    • +
    • whh <whhlcj_at_126.com>
    • +
    +
    +
  • +
  • Chinese (Taiwan)

    +
    +
      +
    • Chien Wei Lin <cwlin0416_at_gmail.com>
    • +
    • Peter Dave Hello <xs910203_at_gmail.com>
    • +
    +
    +
  • +
  • Czech

    +
    +
      +
    • Aleš Hakl <ales_at_hakl.net>
    • +
    • Michal Čihař <michal_at_cihar.com>
    • +
    • Jan Palider <palider_at_seznam.cz>
    • +
    • Petr Kateřiňák <petr.katerinak_at_gmail.com>
    • +
    +
    +
  • +
  • Danish

    +
    +
      +
    • Aputsiaĸ Niels Janussen <aj_at_isit.gl>
    • +
    • Claus Svalekjaer <just.my.smtp.server_at_gmail.com>
    • +
    +
    +
  • +
  • Dutch

    +
    +
      +
      1. +
      2. Voogt <a.voogt_at_hccnet.nl>
      3. +
      +
    • +
    • dingo thirteen <dingo13_at_gmail.com>
    • +
    • Dries Verschuere <dries.verschuere_at_outlook.com>
    • +
    • Robin van der Vliet <info_at_robinvandervliet.nl>
    • +
    • Stefan Koolen <nast3zz_at_gmail.com>
    • +
    • Ray Borggreve <ray_at_datahuis.net>
    • +
    • Dieter Adriaenssens <ruleant_at_users.sourceforge.net>
    • +
    • Tom Hofman <tom.hofman_at_gmail.com>
    • +
    +
    +
  • +
  • Estonian

    +
    +
      +
    • Kristjan Räts <kristjanrats_at_gmail.com>
    • +
    +
    +
  • +
  • Finnish

    +
    +
      +
    • Juha <jremes_at_outlook.com>
    • +
    +
    +
  • +
  • French

    +
    +
      +
    • Cédric Corazza <cedric.corazza_at_wanadoo.fr>
    • +
    • Étienne Gilli <etienne.gilli_at_gmail.com>
    • +
    • Marc Delisle <marc_at_infomarc.info>
    • +
    • Donavan_Martin <mart.donavan_at_hotmail.com>
    • +
    +
    +
  • +
  • Frisian

    +
    +
      +
    • Robin van der Vliet <info_at_robinvandervliet.nl>
    • +
    +
    +
  • +
  • Galician

    +
    +
      +
    • Xosé Calvo <xosecalvo_at_gmail.com>
    • +
    +
    +
  • +
  • German

    +
    +
      +
    • Daniel <d.gnauk89_at_googlemail.com>
    • +
    • JH M <janhenrikm_at_yahoo.de>
    • +
    • Lasse Goericke <lasse_at_mydom.de>
    • +
    • Michael Koch <michael.koch_at_enough.de>
    • +
    • Ann + J.M. <phpMyAdmin_at_ZweiSteinSoft.de>
    • +
    • Niemand Jedermann <predatorix_at_web.de>
    • +
    • Phillip Rohmberger <rohmberger_at_hotmail.de>
    • +
    • Hauke Henningsen <sqrt_at_entless.org>
    • +
    +
    +
  • +
  • Greek

    +
    +
      +
    • Παναγιώτης Παπάζογλου <papaz_p_at_yahoo.com>
    • +
    +
    +
  • +
  • Hungarian

    +
    +
      +
    • Balázs Úr <urbalazs_at_gmail.com>
    • +
    +
    +
  • +
  • Italian

    +
    +
      +
    • Francesco Saverio Giacobazzi <francesco.giacobazzi_at_ferrania.it>
    • +
    • Marco Pozzato <ironpotts_at_gmail.com>
    • +
    • Stefano Martinelli <stefano.ste.martinelli_at_gmail.com>
    • +
    • TWS <tablettws_at_gmail.com>
    • +
    +
    +
  • +
  • Japanese

    +
    +
      +
    • Eshin Kunishima <ek_at_luna.miko.im>
    • +
    • Hiroshi Chiyokawa <hiroshi.chiyokawa_at_gmail.com>
    • +
    +
    +
  • +
  • Lithuanian

    +
    +
      +
    • Jur Kis <atvejis_at_gmail.com>
    • +
    • Dovydas <dovy.buz_at_gmail.com>
    • +
    +
    +
  • +
  • Norwegian Bokmål

    +
    +
      +
    • Tor Stokkan <danorse_at_gmail.com>
    • +
    • Kurt Eilertsen <kurt_at_kheds.com>
    • +
    +
    +
  • +
  • Portuguese (Brazil)

    +
    +
      +
    • Alexandre Moretti <alemoretti2010_at_hotmail.com>
    • +
    • Douglas Rafael Morais Kollar <douglas.kollar_at_pg.df.gov.br>
    • +
    • Guilherme Seibt <gui_at_webseibt.net>
    • +
    • Helder Santana <helder.bs.santana_at_gmail.com>
    • +
    • Michal Čihař <michal_at_cihar.com>
    • +
    • Michel Souza <michel.ekio_at_gmail.com>
    • +
    • Danilo Azevedo <mrdaniloazevedo_at_gmail.com>
    • +
    • Thiago Casotti <thiago.casotti_at_uol.com.br>
    • +
    • Vinícius Araújo <vinipitta_at_gmail.com>
    • +
    • Yan Gabriel <yansilvagabriel_at_gmail.com>
    • +
    +
    +
  • +
  • Slovak

    +
    +
      +
    • Martin Lacina <martin_at_whistler.sk>
    • +
    • Michal Čihař <michal_at_cihar.com>
    • +
    • Jozef Pistej <pistej2_at_gmail.com>
    • +
    +
    +
  • +
  • Slovenian

    +
    +
      +
    • Domen <mitenem_at_outlook.com>
    • +
    +
    +
  • +
  • Spanish

    +
    +
      +
    • Luis García Sevillano <floss.dev_at_gmail.com>
    • +
    • Franco <fulanodetal.github1_at_openaliasbox.org>
    • +
    • Matías Bellone <matiasbellone+weblate_at_gmail.com>
    • +
    • Ronnie Simon <ronniesimonf_at_gmail.com>
    • +
    +
    +
  • +
  • Turkish

    +
    +
      +
    • Burak Yavuz <hitowerdigit_at_hotmail.com>
    • +
    +
    +
  • +
+
+
+

Original Credits of Version 2.1.0

+

This work is based on Peter Kuppelwieser’s MySQL-Webadmin. It was his +idea to create a web-based interface to MySQL using PHP3. Although I +have not used any of his source-code, there are some concepts I’ve +borrowed from him. phpMyAdmin was created because Peter told me he +wasn’t going to further develop his (great) tool.

+

Thanks go to

+
    +
  • Amalesh Kempf <ak-lsml_at_living-source.com> who contributed the +code for the check when dropping a table or database. He also +suggested that you should be able to specify the primary key on +tbl_create.php3. To version 1.1.1 he contributed the ldi_*.php3-set +(Import text-files) as well as a bug-report. Plus many smaller +improvements.
  • +
  • Jan Legenhausen <jan_at_nrw.net>: He made many of the changes that +were introduced in 1.3.0 (including quite significant ones like the +authentication). For 1.4.1 he enhanced the table-dump feature. Plus +bug-fixes and help.
  • +
  • Marc Delisle <DelislMa_at_CollegeSherbrooke.qc.ca> made phpMyAdmin +language-independent by outsourcing the strings to a separate file. He +also contributed the French translation.
  • +
  • Alexandr Bravo <abravo_at_hq.admiral.ru> who contributed +tbl_select.php3, a feature to display only some columns from a table.
  • +
  • Chris Jackson <chrisj_at_ctel.net> added support for MySQL functions +in tbl_change.php3. He also added the “Query by Example” feature in +2.0.
  • +
  • Dave Walton <walton_at_nordicdms.com> added support for multiple +servers and is a regular contributor for bug-fixes.
  • +
  • Gabriel Ash <ga244_at_is8.nyu.edu> contributed the random access +features for 2.0.6.
  • +
+

The following people have contributed minor changes, enhancements, +bugfixes or support for a new language:

+

Jim Kraai, Jordi Bruguera, Miquel Obrador, Geert Lund, Thomas +Kleemann, Alexander Leidinger, Kiko Albiol, Daniel C. Chao, Pavel +Piankov, Sascha Kettler, Joe Pruett, Renato Lins, Mark Kronsbein, +Jannis Hermanns, G. Wieggers.

+

And thanks to everyone else who sent me email with suggestions, bug- +reports and or just some feedback.

+
+
+ + +
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/php/apps/phpmyadmin49/doc/html/developers.html b/php/apps/phpmyadmin49/doc/html/developers.html new file mode 100644 index 00000000..f85ffb2c --- /dev/null +++ b/php/apps/phpmyadmin49/doc/html/developers.html @@ -0,0 +1,117 @@ + + + + + + + + Developers Information — phpMyAdmin 4.9.1 documentation + + + + + + + + + + + + + + + + + + +
+
+
+
+ +
+

Developers Information

+

phpMyAdmin is Open Source, so you’re invited to contribute to it. Many +great features have been written by other people and you too can help +to make phpMyAdmin a useful tool.

+

You can check out all the possibilities to contribute in the +contribute section on our website.

+
+ + +
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/php/apps/phpmyadmin49/doc/html/faq.html b/php/apps/phpmyadmin49/doc/html/faq.html new file mode 100644 index 00000000..31ac8348 --- /dev/null +++ b/php/apps/phpmyadmin49/doc/html/faq.html @@ -0,0 +1,2014 @@ + + + + + + + + FAQ - Frequently Asked Questions — phpMyAdmin 4.9.1 documentation + + + + + + + + + + + + + + + + + + +
+
+
+
+ +
+

FAQ - Frequently Asked Questions

+

Please have a look at our Link section on the official +phpMyAdmin homepage for in-depth coverage of phpMyAdmin’s features and +or interface.

+
+

Server

+
+

1.1 My server is crashing each time a specific action is required or phpMyAdmin sends a blank page or a page full of cryptic characters to my browser, what can I do?

+

Try to set the $cfg['OBGzip'] directive to false in your +config.inc.php file and the zlib.output_compression directive to +Off in your php configuration file.

+
+
+

1.2 My Apache server crashes when using phpMyAdmin.

+

You should first try the latest versions of Apache (and possibly MySQL). If +your server keeps crashing, please ask for help in the various Apache support +groups.

+ +
+
+

1.3 (withdrawn).

+
+
+

1.4 Using phpMyAdmin on IIS, I’m displayed the error message: “The specified CGI application misbehaved by not returning a complete set of HTTP headers ...”.

+

You just forgot to read the install.txt file from the PHP +distribution. Have a look at the last message in this PHP bug report #12061 from the official PHP bug +database.

+
+
+

1.5 Using phpMyAdmin on IIS, I’m facing crashes and/or many error messages with the HTTP.

+

This is a known problem with the PHP ISAPI filter: it’s not so stable. +Please use instead the cookie authentication mode.

+
+
+

1.6 I can’t use phpMyAdmin on PWS: nothing is displayed!

+

This seems to be a PWS bug. Filippo Simoncini found a workaround (at +this time there is no better fix): remove or comment the DOCTYPE +declarations (2 lines) from the scripts libraries/Header.class.php +and index.php.

+
+
+

1.7 How can I gzip a dump or a CSV export? It does not seem to work.

+

This feature is based on the gzencode() +PHP function to be more independent of the platform (Unix/Windows, +Safe Mode or not, and so on). So, you must have Zlib support +(--with-zlib).

+
+
+

1.8 I cannot insert a text file in a table, and I get an error about safe mode being in effect.

+

Your uploaded file is saved by PHP in the “upload dir”, as defined in +php.ini by the variable upload_tmp_dir (usually the system +default is /tmp). We recommend the following setup for Apache +servers running in safe mode, to enable uploads of files while being +reasonably secure:

+
    +
  • create a separate directory for uploads: mkdir /tmp/php
  • +
  • give ownership to the Apache server’s user.group: chown +apache.apache /tmp/php
  • +
  • give proper permission: chmod 600 /tmp/php
  • +
  • put upload_tmp_dir = /tmp/php in php.ini
  • +
  • restart Apache
  • +
+
+
+

1.9 (withdrawn).

+
+
+

1.10 I’m having troubles when uploading files with phpMyAdmin running on a secure server. My browser is Internet Explorer and I’m using the Apache server.

+

As suggested by “Rob M” in the phpWizard forum, add this line to your +httpd.conf:

+
SetEnvIf User-Agent ".*MSIE.*" nokeepalive ssl-unclean-shutdown
+
+
+

It seems to clear up many problems between Internet Explorer and SSL.

+
+
+

1.11 I get an ‘open_basedir restriction’ while uploading a file from the import tab.

+

Since version 2.2.4, phpMyAdmin supports servers with open_basedir +restrictions. However you need to create temporary directory and configure it +as $cfg['TempDir']. The uploaded files will be moved there, +and after execution of your SQL commands, removed.

+
+
+

1.12 I have lost my MySQL root password, what can I do?

+

phpMyAdmin does authenticate against MySQL server you’re using, so to recover +from phpMyAdmin password loss, you need to recover at MySQL level.

+

The MySQL manual explains how to reset the permissions.

+

If you are using MySQL server installed by your hosting provider, please +contact their support to recover the password for you.

+
+
+

1.13 (withdrawn).

+
+
+

1.14 (withdrawn).

+
+
+

1.15 I have problems with mysql.user column names.

+

In previous MySQL versions, the User and Password columns were +named user and password. Please modify your column names to +align with current standards.

+
+
+

1.16 I cannot upload big dump files (memory, HTTP or timeout problems).

+

Starting with version 2.7.0, the import engine has been re–written and +these problems should not occur. If possible, upgrade your phpMyAdmin +to the latest version to take advantage of the new import features.

+

The first things to check (or ask your host provider to check) are the values +of max_execution_time, upload_max_filesize, memory_limit and +post_max_size in the php.ini configuration file. All of these three +settings limit the maximum size of data that can be submitted and handled by +PHP. Please note that post_max_size needs to be larger than +upload_max_filesize. There exist several workarounds if your upload is too +big or your hosting provider is unwilling to change the settings:

+
    +
  • Look at the $cfg['UploadDir'] feature. This allows one to upload a file to the server +via scp, ftp, or your favorite file transfer method. PhpMyAdmin is +then able to import the files from the temporary directory. More +information is available in the Configuration of this document.

    +
  • +
  • Using a utility (such as BigDump) to split the files before +uploading. We cannot support this or any third party applications, but +are aware of users having success with it.

    +
  • +
  • If you have shell (command line) access, use MySQL to import the files +directly. You can do this by issuing the “source” command from within +MySQL:

    +
    source filename.sql;
    +
    +
    +
  • +
+
+
+

1.17 Which Database versions does phpMyAdmin support?

+

For MySQL, versions 5.5 and newer are supported. +For older MySQL versions, our Downloads page offers older phpMyAdmin versions +(which may have become unsupported).

+

For MariaDB, versions 5.5 and newer are supported.

+
+
+

1.17a I cannot connect to the MySQL server. It always returns the error message, “Client does not support authentication protocol requested by server; consider upgrading MySQL client”

+

You tried to access MySQL with an old MySQL client library. The +version of your MySQL client library can be checked in your phpinfo() +output. In general, it should have at least the same minor version as +your server - as mentioned in 1.17 Which Database versions does phpMyAdmin support?. This problem is +generally caused by using MySQL version 4.1 or newer. MySQL changed +the authentication hash and your PHP is trying to use the old method. +The proper solution is to use the mysqli extension with the proper client library to match +your MySQL installation. More +information (and several workarounds) are located in the MySQL +Documentation.

+
+
+

1.18 (withdrawn).

+
+
+

1.19 I can’t run the “display relations” feature because the script seems not to know the font face I’m using!

+

The TCPDF library we’re using for this feature requires some special +files to use font faces. Please refers to the TCPDF manual to build these files.

+
+
+

1.20 I receive an error about missing mysqli and mysql extensions.

+

To connect to a MySQL server, PHP needs a set of MySQL functions +called “MySQL extension”. This extension may be part of the PHP +distribution (compiled-in), otherwise it needs to be loaded +dynamically. Its name is probably mysqli.so or php_mysqli.dll. +phpMyAdmin tried to load the extension but failed. Usually, the +problem is solved by installing a software package called “PHP-MySQL” +or something similar.

+

There are currently two interfaces PHP provides as MySQL extensions - mysql +and mysqli. The mysqli is tried first, because it’s the best one.

+

This problem can be also caused by wrong paths in the php.ini or using +wrong php.ini.

+

Make sure that the extension files do exist in the folder which the +extension_dir points to and that the corresponding lines in your +php.ini are not commented out (you can use phpinfo() to check +current setup):

+
[PHP]
+
+; Directory in which the loadable extensions (modules) reside.
+extension_dir = "C:/Apache2/modules/php/ext"
+
+
+

The php.ini can be loaded from several locations (especially on +Windows), so please check you’re updating the correct one. If using Apache, you +can tell it to use specific path for this file using PHPIniDir directive:

+
LoadFile "C:/php/php5ts.dll"
+LoadModule php5_module "C:/php/php5apache2_2.dll"
+<IfModule php5_module>
+    PHPIniDir "C:/PHP"
+    <Location>
+       AddType text/html .php
+       AddHandler application/x-httpd-php .php
+    </Location>
+</IfModule>
+
+
+

In some rare cases this problem can be also caused by other extensions loaded +in PHP which prevent MySQL extensions to be loaded. If anything else fails, you +can try commenting out extensions for other databses from php.ini.

+
+ +
+

1.22 I don’t see the “Location of text file” field, so I cannot upload.

+

This is most likely because in php.ini, your file_uploads +parameter is not set to “on”.

+
+
+

1.23 I’m running MySQL on a Win32 machine. Each time I create a new table the table and column names are changed to lowercase!

+

This happens because the MySQL directive lower_case_table_names +defaults to 1 (ON) in the Win32 version of MySQL. You can change +this behavior by simply changing the directive to 0 (OFF): Just +edit your my.ini file that should be located in your Windows +directory and add the following line to the group [mysqld]:

+
set-variable = lower_case_table_names=0
+
+
+
+

Note

+

Forcing this variable to 0 with –lower-case-table-names=0 on a +case-insensitive filesystem and access MyISAM tablenames using different +lettercases, index corruption may result.

+
+

Next, save the file and restart the MySQL service. You can always +check the value of this directive using the query

+
SHOW VARIABLES LIKE 'lower_case_table_names';
+
+
+ +
+
+

1.24 (withdrawn).

+
+
+

1.25 I am running Apache with mod_gzip-1.3.26.1a on Windows XP, and I get problems, such as undefined variables when I run a SQL query.

+

A tip from Jose Fandos: put a comment on the following two lines in +httpd.conf, like this:

+
# mod_gzip_item_include file \.php$
+# mod_gzip_item_include mime "application/x-httpd-php.*"
+
+
+

as this version of mod_gzip on Apache (Windows) has problems handling +PHP scripts. Of course you have to restart Apache.

+
+
+

1.26 I just installed phpMyAdmin in my document root of IIS but I get the error “No input file specified” when trying to run phpMyAdmin.

+

This is a permission problem. Right-click on the phpmyadmin folder and +choose properties. Under the tab Security, click on “Add” and select +the user “IUSR_machine” from the list. Now set his permissions and it +should work.

+
+
+

1.27 I get empty page when I want to view huge page (eg. db_structure.php with plenty of tables).

+

This was caused by a PHP bug that occur when +GZIP output buffering is enabled. If you turn off it (by +$cfg['OBGzip'] in config.inc.php), it should work. +This bug will has been fixed in PHP 5.0.0.

+
+
+

1.28 My MySQL server sometimes refuses queries and returns the message ‘Errorcode: 13’. What does this mean?

+

This can happen due to a MySQL bug when having database / table names +with upper case characters although lower_case_table_names is +set to 1. To fix this, turn off this directive, convert all database +and table names to lower case and turn it on again. Alternatively, +there’s a bug-fix available starting with MySQL 3.23.56 / +4.0.11-gamma.

+
+
+

1.29 When I create a table or modify a column, I get an error and the columns are duplicated.

+

It is possible to configure Apache in such a way that PHP has problems +interpreting .php files.

+

The problems occur when two different (and conflicting) set of +directives are used:

+
SetOutputFilter PHP
+SetInputFilter PHP
+
+
+

and

+
AddType application/x-httpd-php .php
+
+
+

In the case we saw, one set of directives was in +/etc/httpd/conf/httpd.conf, while the other set was in +/etc/httpd/conf/addon-modules/php.conf. The recommended way is +with AddType, so just comment out the first set of lines and +restart Apache:

+
#SetOutputFilter PHP
+#SetInputFilter PHP
+
+
+
+
+

1.30 I get the error “navigation.php: Missing hash”.

+

This problem is known to happen when the server is running Turck +MMCache but upgrading MMCache to version 2.3.21 solves the problem.

+
+
+

1.31 Which PHP versions does phpMyAdmin support?

+

Since release 4.5, phpMyAdmin supports only PHP 5.5 and newer. Since release +4.1 phpMyAdmin supports only PHP 5.3 and newer. For PHP 5.2 you can use 4.0.x +releases.

+

PHP 7 is supported since phpMyAdmin 4.6, PHP 7.1 is supported since 4.6.5, +PHP 7.2 is supported since 4.7.4.

+

phpMyAdmin also works fine with HHVM.

+
+
+

1.32 Can I use HTTP authentication with IIS?

+

Yes. This procedure was tested with phpMyAdmin 2.6.1, PHP 4.3.9 in +ISAPI mode under IIS 5.1.

+
    +
  1. In your php.ini file, set cgi.rfc2616_headers = 0
  2. +
  3. In Web Site Properties -> File/Directory Security -> Anonymous +Access dialog box, check the Anonymous access checkbox and +uncheck any other checkboxes (i.e. uncheck Basic authentication, +Integrated Windows authentication, and Digest if it’s +enabled.) Click OK.
  4. +
  5. In Custom Errors, select the range of 401;1 through 401;5 +and click the Set to Default button.
  6. +
+
+

See also

+

RFC 2616

+
+
+
+

1.33 (withdrawn).

+
+
+

1.34 Can I access directly to database or table pages?

+

Yes. Out of the box, you can use URL like +http://server/phpMyAdmin/index.php?server=X&db=database&table=table&target=script. +For server you use the server number +which refers to the order of the server paragraph in +config.inc.php. Table and script parts are optional. If you want +http://server/phpMyAdmin/database[/table][/script] URL, you need to do some configuration. Following +lines apply only for Apache web server. +First make sure, that you have enabled some features within global +configuration. You need Options SymLinksIfOwnerMatch and AllowOverride +FileInfo enabled for directory where phpMyAdmin is installed and you +need mod_rewrite to be enabled. Then you just need to create +following .htaccess file in root folder of phpMyAdmin installation (don’t +forget to change directory name inside of it):

+
RewriteEngine On
+RewriteBase /path_to_phpMyAdmin
+RewriteRule ^([a-zA-Z0-9_]+)/([a-zA-Z0-9_]+)/([a-z_]+\.php)$ index.php?db=$1&table=$2&target=$3 [R]
+RewriteRule ^([a-zA-Z0-9_]+)/([a-z_]+\.php)$ index.php?db=$1&target=$2 [R]
+RewriteRule ^([a-zA-Z0-9_]+)/([a-zA-Z0-9_]+)$ index.php?db=$1&table=$2 [R]
+RewriteRule ^([a-zA-Z0-9_]+)$ index.php?db=$1 [R]
+
+
+
+
+

1.35 Can I use HTTP authentication with Apache CGI?

+

Yes. However you need to pass authentication variable to CGI using +following rewrite rule:

+
RewriteEngine On
+RewriteRule .* - [E=REMOTE_USER:%{HTTP:Authorization},L]
+
+
+
+
+

1.36 I get an error “500 Internal Server Error”.

+

There can be many explanations to this and a look at your server’s +error log file might give a clue.

+
+ +
+

1.38 Can I use phpMyAdmin on a server on which Suhosin is enabled?

+

Yes but the default configuration values of Suhosin are known to cause +problems with some operations, for example editing a table with many +columns and no primary key or with textual primary key.

+

Suhosin configuration might lead to malfunction in some cases and it +can not be fully avoided as phpMyAdmin is kind of application which +needs to transfer big amounts of columns in single HTTP request, what +is something what Suhosin tries to prevent. Generally all +suhosin.request.*, suhosin.post.* and suhosin.get.* +directives can have negative effect on phpMyAdmin usability. You can +always find in your error logs which limit did cause dropping of +variable, so you can diagnose the problem and adjust matching +configuration variable.

+

The default values for most Suhosin configuration options will work in +most scenarios, however you might want to adjust at least following +parameters:

+ +

To further improve security, we also recommend these modifications:

+ +

You can also disable the warning using the $cfg['SuhosinDisableWarning'].

+
+
+

1.39 When I try to connect via https, I can log in, but then my connection is redirected back to http. What can cause this behavior?

+

This is caused by the fact that PHP scripts have no knowledge that the site is +using https. Depending on used webserver, you should configure it to let PHP +know about URL and scheme used to access it.

+

For example in Apache ensure that you have enabled SSLOptions and +StdEnvVars in the configuration.

+ +
+ +
+

1.41 When I view a database and ask to see its privileges, I get an error about an unknown column.

+

The MySQL server’s privilege tables are not up to date, you need to +run the mysql_upgrade command on the server.

+
+
+

1.42 How can I prevent robots from accessing phpMyAdmin?

+

You can add various rules to .htaccess to filter access based on user agent +field. This is quite easy to circumvent, but could prevent at least +some robots accessing your installation.

+
RewriteEngine on
+
+# Allow only GET and POST verbs
+RewriteCond %{REQUEST_METHOD} !^(GET|POST)$ [NC,OR]
+
+# Ban Typical Vulnerability Scanners and others
+# Kick out Script Kiddies
+RewriteCond %{HTTP_USER_AGENT} ^(java|curl|wget).* [NC,OR]
+RewriteCond %{HTTP_USER_AGENT} ^.*(libwww-perl|curl|wget|python|nikto|wkito|pikto|scan|acunetix).* [NC,OR]
+RewriteCond %{HTTP_USER_AGENT} ^.*(winhttp|HTTrack|clshttp|archiver|loader|email|harvest|extract|grab|miner).* [NC,OR]
+
+# Ban Search Engines, Crawlers to your administrative panel
+# No reasons to access from bots
+# Ultimately Better than the useless robots.txt
+# Did google respect robots.txt?
+# Try google: intitle:phpMyAdmin intext:"Welcome to phpMyAdmin *.*.*" intext:"Log in" -wiki -forum -forums -questions intext:"Cookies must be enabled"
+RewriteCond %{HTTP_USER_AGENT} ^.*(AdsBot-Google|ia_archiver|Scooter|Ask.Jeeves|Baiduspider|Exabot|FAST.Enterprise.Crawler|FAST-WebCrawler|www\.neomo\.de|Gigabot|Mediapartners-Google|Google.Desktop|Feedfetcher-Google|Googlebot|heise-IT-Markt-Crawler|heritrix|ibm.com\cs/crawler|ICCrawler|ichiro|MJ12bot|MetagerBot|msnbot-NewsBlogs|msnbot|msnbot-media|NG-Search|lucene.apache.org|NutchCVS|OmniExplorer_Bot|online.link.validator|psbot0|Seekbot|Sensis.Web.Crawler|SEO.search.Crawler|Seoma.\[SEO.Crawler\]|SEOsearch|Snappy|www.urltrends.com|www.tkl.iis.u-tokyo.ac.jp/~crawler|SynooBot|crawleradmin.t-info@telekom.de|TurnitinBot|voyager|W3.SiteSearch.Crawler|W3C-checklink|W3C_Validator|www.WISEnutbot.com|yacybot|Yahoo-MMCrawler|Yahoo\!.DE.Slurp|Yahoo\!.Slurp|YahooSeeker).* [NC]
+RewriteRule .* - [F]
+
+
+
+
+

1.43 Why can’t I display the structure of my table containing hundreds of columns?

+

Because your PHP’s memory_limit is too low; adjust it in php.ini.

+
+
+

1.44 How can I reduce the installed size of phpMyAdmin on disk?

+

Some users have requested to be able to reduce the size of the phpMyAdmin installation. +This is not recommended and could lead to confusion over missing features, but can be done. +A list of files and corresponding functionality which degrade gracefully when removed include:

+
    +
  • ./vendor/tecnickcom/tcpdf folder (exporting to PDF)
  • +
  • ./locale/ folder, or unused subfolders (interface translations)
  • +
  • Any unused themes in ./themes/
  • +
  • ./js/vendor/jquery/src/ (included for licensing reasons)
  • +
  • ./js/line_counts.php (removed in phpMyAdmin 4.8)
  • +
  • ./doc/ (documentation)
  • +
  • ./setup/ (setup script)
  • +
  • ./examples/
  • +
  • ./sql/ (SQL scripts to configure advanced functionality)
  • +
  • ./js/vendor/openlayers/ (GIS visualization)
  • +
+
+
+
+

Configuration

+
+

2.1 The error message “Warning: Cannot add header information - headers already sent by ...” is displayed, what’s the problem?

+

Edit your config.inc.php file and ensure there is nothing (I.E. no +blank lines, no spaces, no characters...) neither before the <?php tag at +the beginning, neither after the ?> tag at the end.

+
+
+

2.2 phpMyAdmin can’t connect to MySQL. What’s wrong?

+

Either there is an error with your PHP setup or your username/password +is wrong. Try to make a small script which uses mysql_connect and see +if it works. If it doesn’t, it may be you haven’t even compiled MySQL +support into PHP.

+
+
+

2.3 The error message “Warning: MySQL Connection Failed: Can’t connect to local MySQL server through socket ‘/tmp/mysql.sock’ (111) ...” is displayed. What can I do?

+

The error message can also be: Error #2002 - The server is not +responding (or the local MySQL server’s socket is not correctly configured).

+

First, you need to determine what socket is being used by MySQL. To do this, +connect to your server and go to the MySQL bin directory. In this directory +there should be a file named mysqladmin. Type ./mysqladmin variables, and +this should give you a bunch of info about your MySQL server, including the +socket (/tmp/mysql.sock, for example). You can also ask your ISP for the +connection info or, if you’re hosting your own, connect from the ‘mysql’ +command-line client and type ‘status’ to get the connection type and socket or +port number.

+

Then, you need to tell PHP to use this socket. You can do this for all PHP in +the php.ini or for phpMyAdmin only in the config.inc.php. For +example: $cfg['Servers'][$i]['socket'] Please also make sure +that the permissions of this file allow to be readable by your webserver.

+

On my RedHat-Box the socket of MySQL is /var/lib/mysql/mysql.sock. +In your php.ini you will find a line

+
mysql.default_socket = /tmp/mysql.sock
+
+
+

change it to

+
mysql.default_socket = /var/lib/mysql/mysql.sock
+
+
+

Then restart apache and it will work.

+

Have also a look at the corresponding section of the MySQL +documentation.

+
+
+

2.4 Nothing is displayed by my browser when I try to run phpMyAdmin, what can I do?

+

Try to set the $cfg['OBGzip'] directive to false in the phpMyAdmin configuration +file. It helps sometime. Also have a look at your PHP version number: +if it contains “b” or “alpha” it means you’re running a testing +version of PHP. That’s not a so good idea, please upgrade to a plain +revision.

+
+ +
+

2.6 I get an “Access denied for user: 'root@localhost‘ (Using password: YES)”-error when trying to access a MySQL-Server on a host which is port-forwarded for my localhost.

+

When you are using a port on your localhost, which you redirect via +port-forwarding to another host, MySQL is not resolving the localhost +as expected. Erik Wasser explains: The solution is: if your host is +“localhost” MySQL (the command line tool mysql as well) always +tries to use the socket connection for speeding up things. And that +doesn’t work in this configuration with port forwarding. If you enter +“127.0.0.1” as hostname, everything is right and MySQL uses the +TCP connection.

+
+
+

2.7 Using and creating themes

+

See Custom Themes.

+
+
+

2.8 I get “Missing parameters” errors, what can I do?

+

Here are a few points to check:

+
    +
  • In config.inc.php, try to leave the $cfg['PmaAbsoluteUri'] directive empty. See also +4.7 Authentication window is displayed more than once, why?.
  • +
  • Maybe you have a broken PHP installation or you need to upgrade your +Zend Optimizer. See <https://bugs.php.net/bug.php?id=31134>.
  • +
  • If you are using Hardened PHP with the ini directive +varfilter.max_request_variables set to the default (200) or +another low value, you could get this error if your table has a high +number of columns. Adjust this setting accordingly. (Thanks to Klaus +Dorninger for the hint).
  • +
  • In the php.ini directive arg_separator.input, a value of ”;” +will cause this error. Replace it with “&;”.
  • +
  • If you are using Suhosin, you +might want to increase request limits.
  • +
  • The directory specified in the php.ini directive +session.save_path does not exist or is read-only (this can be caused +by bug in the PHP installer).
  • +
+
+
+

2.9 Seeing an upload progress bar

+

To be able to see a progress bar during your uploads, your server must +have the APC extension, the +uploadprogress one, or +you must be running PHP 5.4.0 or higher. Moreover, the JSON extension +has to be enabled in your PHP.

+

If using APC, you must set apc.rfc1867 to on in your php.ini.

+

If using PHP 5.4.0 or higher, you must set +session.upload_progress.enabled to 1 in your php.ini. However, +starting from phpMyAdmin version 4.0.4, session-based upload progress has +been temporarily deactivated due to its problematic behavior.

+
+

See also

+

RFC 1867

+
+
+
+
+

Known limitations

+
+

3.1 When using HTTP authentication, a user who logged out can not log in again in with the same nick.

+

This is related to the authentication mechanism (protocol) used by +phpMyAdmin. To bypass this problem: just close all the opened browser +windows and then go back to phpMyAdmin. You should be able to log in +again.

+
+
+

3.2 When dumping a large table in compressed mode, I get a memory limit error or a time limit error.

+

Compressed dumps are built in memory and because of this are limited +to php’s memory limit. For gzip/bzip2 exports this can be overcome +since 2.5.4 using $cfg['CompressOnFly'] (enabled by default). +zip exports can not be handled this way, so if you need zip files for larger +dump, you have to use another way.

+
+
+

3.3 With InnoDB tables, I lose foreign key relationships when I rename a table or a column.

+

This is an InnoDB bug, see <https://bugs.mysql.com/bug.php?id=21704>.

+
+
+

3.4 I am unable to import dumps I created with the mysqldump tool bundled with the MySQL server distribution.

+

The problem is that older versions of mysqldump created invalid +comments like this:

+
-- MySQL dump 8.22
+--
+-- Host: localhost Database: database
+---------------------------------------------------------
+-- Server version 3.23.54
+
+
+

The invalid part of the code is the horizontal line made of dashes +that appears once in every dump created with mysqldump. If you want to +run your dump you have to turn it into valid MySQL. This means, you +have to add a whitespace after the first two dashes of the line or add +a # before it: -- ------------------------------------------------------- or +#---------------------------------------------------------

+
+
+

3.5 When using nested folders, multiple hierarchies are displayed in a wrong manner.

+

Please note that you should not use the separating string multiple +times without any characters between them, or at the beginning/end of +your table name. If you have to, think about using another +TableSeparator or disabling that feature.

+ +
+
+

3.6 (withdrawn).

+
+
+

3.7 I have table with many (100+) columns and when I try to browse table I get series of errors like “Warning: unable to parse url”. How can this be fixed?

+

Your table neither have a primary key nor an unique key, so we must +use a long expression to identify this row. This causes problems to +parse_url function. The workaround is to create a primary key +or unique key.

+
+
+

3.8 I cannot use (clickable) HTML-forms in columns where I put a MIME-Transformation onto!

+

Due to a surrounding form-container (for multi-row delete checkboxes), +no nested forms can be put inside the table where phpMyAdmin displays +the results. You can, however, use any form inside of a table if keep +the parent form-container with the target to tbl_row_delete.php and +just put your own input-elements inside. If you use a custom submit +input field, the form will submit itself to the displaying page again, +where you can validate the $HTTP_POST_VARS in a transformation. For +a tutorial on how to effectively use transformations, see our Link +section on the +official phpMyAdmin-homepage.

+
+
+

3.9 I get error messages when using “–sql_mode=ANSI” for the MySQL server.

+

When MySQL is running in ANSI-compatibility mode, there are some major +differences in how SQL is structured (see +<https://dev.mysql.com/doc/refman/5.7/en/sql-mode.html>). Most important of all, the +quote-character (”) is interpreted as an identifier quote character and not as +a string quote character, which makes many internal phpMyAdmin operations into +invalid SQL statements. There is no +workaround to this behaviour. News to this item will be posted in issue +#7383.

+
+
+

3.10 Homonyms and no primary key: When the results of a SELECT display more that one column with the same value (for example SELECT lastname from employees where firstname like 'A%' and two “Smith” values are displayed), if I click Edit I cannot be sure that I am editing the intended row.

+

Please make sure that your table has a primary key, so that phpMyAdmin +can use it for the Edit and Delete links.

+
+
+

3.11 The number of rows for InnoDB tables is not correct.

+

phpMyAdmin uses a quick method to get the row count, and this method only +returns an approximate count in the case of InnoDB tables. See +$cfg['MaxExactCount'] for a way to modify those results, but +this could have a serious impact on performance. +However, one can easily replace the approximate row count with exact count by +simply clicking on the approximate count. This can also be done for all tables +at once by clicking on the rows sum displayed at the bottom.

+
+

See also

+

$cfg['MaxExactCount']

+
+
+
+

3.12 (withdrawn).

+
+
+

3.13 I get an error when entering USE followed by a db name containing an hyphen.

+

The tests I have made with MySQL 5.1.49 shows that the API does not +accept this syntax for the USE command.

+
+
+

3.14 I am not able to browse a table when I don’t have the right to SELECT one of the columns.

+

This has been a known limitation of phpMyAdmin since the beginning and +it’s not likely to be solved in the future.

+
+
+

3.15 (withdrawn).

+
+
+

3.16 (withdrawn).

+
+
+

3.17 (withdrawn).

+
+
+

3.18 When I import a CSV file that contains multiple tables, they are lumped together into a single table.

+

There is no reliable way to differentiate tables in CSV format. For the +time being, you will have to break apart CSV files containing multiple +tables.

+
+
+

3.19 When I import a file and have phpMyAdmin determine the appropriate data structure it only uses int, decimal, and varchar types.

+

Currently, the import type-detection system can only assign these +MySQL types to columns. In future, more will likely be added but for +the time being you will have to edit the structure to your liking +post-import. Also, you should note the fact that phpMyAdmin will use +the size of the largest item in any given column as the column size +for the appropriate type. If you know you will be adding larger items +to that column then you should manually adjust the column sizes +accordingly. This is done for the sake of efficiency.

+
+
+

3.20 After upgrading, some bookmarks are gone or their content cannot be shown.

+

At some point, the character set used to store bookmark content has changed. +It’s better to recreate your bookmark from the newer phpMyAdmin version.

+
+
+

3.21 I am unable to log in with a username containing unicode characters such as á.

+

This can happen if MySQL server is not configured to use utf-8 as default +charset. This is a limitation of how PHP and the MySQL server interact; there +is no way for PHP to set the charset before authenticating.

+ +
+
+
+

ISPs, multi-user installations

+
+

4.1 I’m an ISP. Can I setup one central copy of phpMyAdmin or do I need to install it for each customer?

+

Since version 2.0.3, you can setup a central copy of phpMyAdmin for all your +users. The development of this feature was kindly sponsored by NetCologne GmbH. +This requires a properly setup MySQL user management and phpMyAdmin +HTTP or cookie authentication.

+ +
+
+

4.2 What’s the preferred way of making phpMyAdmin secure against evil access?

+

This depends on your system. If you’re running a server which cannot be +accessed by other people, it’s sufficient to use the directory protection +bundled with your webserver (with Apache you can use .htaccess files, +for example). If other people have telnet access to your server, you should use +phpMyAdmin’s HTTP or cookie authentication features.

+

Suggestions:

+
    +
  • Your config.inc.php file should be chmod 660.
  • +
  • All your phpMyAdmin files should be chown -R phpmy.apache, where phpmy +is a user whose password is only known to you, and apache is the group +under which Apache runs.
  • +
  • Follow security recommendations for PHP and your webserver.
  • +
+
+
+

4.3 I get errors about not being able to include a file in /lang or in /libraries.

+

Check php.ini, or ask your sysadmin to check it. The +include_path must contain ”.” somewhere in it, and +open_basedir, if used, must contain ”.” and ”./lang” to allow +normal operation of phpMyAdmin.

+
+
+

4.4 phpMyAdmin always gives “Access denied” when using HTTP authentication.

+

This could happen for several reasons:

+ +
+
+

4.5 Is it possible to let users create their own databases?

+

Starting with 2.2.5, in the user management page, you can enter a +wildcard database name for a user (for example “joe%”), and put the +privileges you want. For example, adding SELECT, INSERT, UPDATE, +DELETE, CREATE, DROP, INDEX, ALTER would let a user create/manage +his/her database(s).

+
+
+

4.6 How can I use the Host-based authentication additions?

+

If you have existing rules from an old .htaccess file, you can take them and +add a username between the 'deny'/'allow' and 'from' +strings. Using the username wildcard of '%' would be a major +benefit here if your installation is suited to using it. Then you can +just add those updated lines into the +$cfg['Servers'][$i]['AllowDeny']['rules'] array.

+

If you want a pre-made sample, you can try this fragment. It stops the +‘root’ user from logging in from any networks other than the private +network IP blocks.

+
//block root from logging in except from the private networks
+$cfg['Servers'][$i]['AllowDeny']['order'] = 'deny,allow';
+$cfg['Servers'][$i]['AllowDeny']['rules'] = array(
+    'deny root from all',
+    'allow root from localhost',
+    'allow root from 10.0.0.0/8',
+    'allow root from 192.168.0.0/16',
+    'allow root from 172.16.0.0/12',
+);
+
+
+
+
+

4.7 Authentication window is displayed more than once, why?

+

This happens if you are using a URL to start phpMyAdmin which is +different than the one set in your $cfg['PmaAbsoluteUri']. For +example, a missing “www”, or entering with an IP address while a domain +name is defined in the config file.

+
+
+

4.8 Which parameters can I use in the URL that starts phpMyAdmin?

+

When starting phpMyAdmin, you can use the db +and server parameters. This last one can contain +either the numeric host index (from $i of the configuration file) +or one of the host names present in the configuration file.

+

For example, to jump directly to a particular database, a URL can be constructed as +https://example.com/phpmyadmin/?db=sakila.

+
+

Changed in version 4.9.0: Support for using the pma_username and pma_password parameters was removed +in phpMyAdmin 4.9.0 (see PMASA-2019-4).

+
+
+
+
+

Browsers or client OS

+
+

5.1 I get an out of memory error, and my controls are non-functional, when trying to create a table with more than 14 columns.

+

We could reproduce this problem only under Win98/98SE. Testing under +WinNT4 or Win2K, we could easily create more than 60 columns. A +workaround is to create a smaller number of columns, then come back to +your table properties and add the other columns.

+
+
+

5.2 With Xitami 2.5b4, phpMyAdmin won’t process form fields.

+

This is not a phpMyAdmin problem but a Xitami known bug: you’ll face +it with each script/website that use forms. Upgrade or downgrade your +Xitami server.

+
+
+

5.3 I have problems dumping tables with Konqueror (phpMyAdmin 2.2.2).

+

With Konqueror 2.1.1: plain dumps, zip and gzip dumps work ok, except +that the proposed file name for the dump is always ‘tbl_dump.php’. +The bzip2 dumps don’t seem to work. With Konqueror 2.2.1: plain dumps +work; zip dumps are placed into the user’s temporary directory, so +they must be moved before closing Konqueror, or else they disappear. +gzip dumps give an error message. Testing needs to be done for +Konqueror 2.2.2.

+
+ +
+

5.5 (withdrawn).

+
+
+

5.6 (withdrawn).

+
+
+

5.7 I refresh (reload) my browser, and come back to the welcome page.

+

Some browsers support right-clicking into the frame you want to +refresh, just do this in the right frame.

+
+
+

5.8 With Mozilla 0.9.7 I have problems sending a query modified in the query box.

+

Looks like a Mozilla bug: 0.9.6 was OK. We will keep an eye on future +Mozilla versions.

+
+
+

5.9 With Mozilla 0.9.? to 1.0 and Netscape 7.0-PR1 I can’t type a whitespace in the SQL-Query edit area: the page scrolls down.

+

This is a Mozilla bug (see bug #26882 at BugZilla).

+
+
+

5.10 (withdrawn).

+
+
+

5.11 Extended-ASCII characters like German umlauts are displayed wrong.

+

Please ensure that you have set your browser’s character set to the +one of the language file you have selected on phpMyAdmin’s start page. +Alternatively, you can try the auto detection mode that is supported +by the recent versions of the most browsers.

+
+
+

5.12 Mac OS X Safari browser changes special characters to ”?”.

+

This issue has been reported by a Mac OS X user, who adds that Chimera, +Netscape and Mozilla do not have this problem.

+
+
+

5.13 (withdrawn)

+
+
+

5.14 (withdrawn)

+
+
+

5.15 (withdrawn)

+
+
+

5.16 With Internet Explorer, I get “Access is denied” Javascript errors. Or I cannot make phpMyAdmin work under Windows.

+

Please check the following points:

+
    +
  • Maybe you have defined your $cfg['PmaAbsoluteUri'] setting in +config.inc.php to an IP address and you are starting phpMyAdmin +with a URL containing a domain name, or the reverse situation.
  • +
  • Security settings in IE and/or Microsoft Security Center are too high, +thus blocking scripts execution.
  • +
  • The Windows Firewall is blocking Apache and MySQL. You must allow +HTTP ports (80 or 443) and MySQL +port (usually 3306) in the “in” and “out” directions.
  • +
+
+
+

5.17 With Firefox, I cannot delete rows of data or drop a database.

+

Many users have confirmed that the Tabbrowser Extensions plugin they +installed in their Firefox is causing the problem.

+
+
+

5.18 (withdrawn)

+
+
+

5.19 I get JavaScript errors in my browser.

+

Issues have been reported with some combinations of browser +extensions. To troubleshoot, disable all extensions then clear your +browser cache to see if the problem goes away.

+
+
+

5.20 I get errors about violating Content Security Policy.

+

If you see errors like:

+
Refused to apply inline style because it violates the following Content Security Policy directive
+
+
+

This is usually caused by some software, which wrongly rewrites +Content Security Policy headers. Usually this is caused by +antivirus proxy or browser addons which are causing such errors.

+

If you see these errors, try disabling the HTTP proxy in antivirus or disable +the Content Security Policy rewriting in it. If that doesn’t +help, try disabling browser extensions.

+

Alternatively it can be also server configuration issue (if the webserver is +configured to emit Content Security Policy headers, they can +override the ones from phpMyAdmin).

+

Programs known to cause these kind of errors:

+
    +
  • Kaspersky Internet Security
  • +
+
+
+

5.21 I get errors about potentially unsafe operation when browsing table or executing SQL query.

+

If you see errors like:

+
A potentially unsafe operation has been detected in your request to this site.
+
+
+

This is usually caused by web application firewall doing requests filtering. It +tries to prevent SQL injection, however phpMyAdmin is tool designed to execute +SQL queries, thus it makes it unusable.

+

Please whitelist phpMyAdmin scripts from the web application firewall settings +or disable it completely for phpMyAdmin path.

+

Programs known to cause these kind of errors:

+
    +
  • Wordfence Web Application Firewall
  • +
+
+
+
+

Using phpMyAdmin

+
+

6.1 I can’t insert new rows into a table / I can’t create a table - MySQL brings up a SQL error.

+

Examine the SQL error with care. +Often the problem is caused by specifying a wrong column-type. Common +errors include:

+
    +
  • Using VARCHAR without a size argument
  • +
  • Using TEXT or BLOB with a size argument
  • +
+

Also, look at the syntax chapter in the MySQL manual to confirm that +your syntax is correct.

+
+
+

6.2 When I create a table, I set an index for two columns and phpMyAdmin generates only one index with those two columns.

+

This is the way to create a multi-columns index. If you want two +indexes, create the first one when creating the table, save, then +display the table properties and click the Index link to create the +other index.

+
+
+

6.3 How can I insert a null value into my table?

+

Since version 2.2.3, you have a checkbox for each column that can be +null. Before 2.2.3, you had to enter “null”, without the quotes, as +the column’s value. Since version 2.5.5, you have to use the checkbox +to get a real NULL value, so if you enter “NULL” this means you want a +literal NULL in the column, and not a NULL value (this works in PHP4).

+
+
+

6.4 How can I backup my database or table?

+

Click on a database or table name in the navigation panel, the properties will +be displayed. Then on the menu, click “Export”, you can dump the structure, the +data, or both. This will generate standard SQL statements that can be +used to recreate your database/table. You will need to choose “Save as file”, +so that phpMyAdmin can transmit the resulting dump to your station. Depending +on your PHP configuration, you will see options to compress the dump. See also +the $cfg['ExecTimeLimit'] configuration variable. For +additional help on this subject, look for the word “dump” in this document.

+
+
+

6.5 How can I restore (upload) my database or table using a dump? How can I run a ”.sql” file?

+

Click on a database name in the navigation panel, the properties will +be displayed. Select “Import” from the list of tabs in the right–hand +frame (or “SQL” if your phpMyAdmin +version is previous to 2.7.0). In the “Location of the text file” +section, type in the path to your dump filename, or use the Browse +button. Then click Go. With version 2.7.0, the import engine has been +re–written, if possible it is suggested that you upgrade to take +advantage of the new features. For additional help on this subject, +look for the word “upload” in this document.

+

Note: For errors while importing of dumps exported from older MySQL versions to newer MySQL versions, +please check 6.41 I get import errors while importing the dumps exported from older MySQL versions (pre-5.7.6) into newer MySQL versions (5.7.7+), but they work fine when imported back on same older versions ?.

+
+
+

6.6 How can I use the relation table in Query-by-example?

+

Here is an example with the tables persons, towns and countries, all +located in the database “mydb”. If you don’t have a pma__relation +table, create it as explained in the configuration section. Then +create the example tables:

+
CREATE TABLE REL_countries (
+country_code char(1) NOT NULL default '',
+description varchar(10) NOT NULL default '',
+PRIMARY KEY (country_code)
+) ENGINE=MyISAM;
+
+INSERT INTO REL_countries VALUES ('C', 'Canada');
+
+CREATE TABLE REL_persons (
+id tinyint(4) NOT NULL auto_increment,
+person_name varchar(32) NOT NULL default '',
+town_code varchar(5) default '0',
+country_code char(1) NOT NULL default '',
+PRIMARY KEY (id)
+) ENGINE=MyISAM;
+
+INSERT INTO REL_persons VALUES (11, 'Marc', 'S', 'C');
+INSERT INTO REL_persons VALUES (15, 'Paul', 'S', 'C');
+
+CREATE TABLE REL_towns (
+town_code varchar(5) NOT NULL default '0',
+description varchar(30) NOT NULL default '',
+PRIMARY KEY (town_code)
+) ENGINE=MyISAM;
+
+INSERT INTO REL_towns VALUES ('S', 'Sherbrooke');
+INSERT INTO REL_towns VALUES ('M', 'Montréal');
+
+
+

To setup appropriate links and display information:

+
    +
  • on table “REL_persons” click Structure, then Relation view
  • +
  • for “town_code”, choose from dropdowns, “mydb”, “REL_towns”, “code” +for foreign database, table and column respectively
  • +
  • for “country_code”, choose from dropdowns, “mydb”, “REL_countries”, +“country_code” for foreign database, table and column respectively
  • +
  • on table “REL_towns” click Structure, then Relation view
  • +
  • in “Choose column to display”, choose “description”
  • +
  • repeat the two previous steps for table “REL_countries”
  • +
+

Then test like this:

+
    +
  • Click on your db name in the navigation panel
  • +
  • Choose “Query”
  • +
  • Use tables: persons, towns, countries
  • +
  • Click “Update query”
  • +
  • In the columns row, choose persons.person_name and click the “Show” +tickbox
  • +
  • Do the same for towns.description and countries.descriptions in the +other 2 columns
  • +
  • Click “Update query” and you will see in the query box that the +correct joins have been generated
  • +
  • Click “Submit query”
  • +
+
+
+

6.7 How can I use the “display column” feature?

+

Starting from the previous example, create the pma__table_info as +explained in the configuration section, then browse your persons +table, and move the mouse over a town code or country code. See also +6.21 In edit/insert mode, how can I see a list of possible values for a column, based on some foreign table? for an additional feature that “display column” +enables: drop-down list of possible values.

+
+
+

6.8 How can I produce a PDF schema of my database?

+

First the configuration variables “relation”, “table_coords” and +“pdf_pages” have to be filled in. Then you need to think about your +schema layout. Which tables will go on which pages?

+
    +
  • Select your database in the navigation panel.
  • +
  • Choose “Operations” in the navigation bar at the top.
  • +
  • Choose “Edit PDF Pages” near the +bottom of the page.
  • +
  • Enter a name for the first PDF page +and click Go. If you like, you can use the “automatic layout,” which +will put all your linked tables onto the new page.
  • +
  • Select the name of the new page (making sure the Edit radio button is +selected) and click Go.
  • +
  • Select a table from the list, enter its coordinates and click Save. +Coordinates are relative; your diagram will be automatically scaled to +fit the page. When initially placing tables on the page, just pick any +coordinates – say, 50x50. After clicking Save, you can then use the +6.28 How can I easily edit relational schema for export? to position the element correctly.
  • +
  • When you’d like to look at your PDF, first be sure to click the Save +button beneath the list of tables and coordinates, to save any changes you +made there. Then scroll all the way down, select the PDF options you +want, and click Go.
  • +
  • Internet Explorer for Windows may suggest an incorrect filename when +you try to save a generated PDF. +When saving a generated PDF, be +sure that the filename ends in ”.pdf”, for example “schema.pdf”. +Browsers on other operating systems, and other browsers on Windows, do +not have this problem.
  • +
+
+

See also

+

Relations

+
+
+
+

6.9 phpMyAdmin is changing the type of one of my columns!

+

No, it’s MySQL that is doing silent column type changing.

+
+
+

6.10 When creating a privilege, what happens with underscores in the database name?

+

If you do not put a backslash before the underscore, this is a +wildcard grant, and the underscore means “any character”. So, if the +database name is “john_db”, the user would get rights to john1db, +john2db ... If you put a backslash before the underscore, it means +that the database name will have a real underscore.

+
+
+

6.11 What is the curious symbol ø in the statistics pages?

+

It means “average”.

+
+
+

6.12 I want to understand some Export options.

+

Structure:

+
    +
  • “Add DROP TABLE” will add a line telling MySQL to drop the table, if it already +exists during the import. It does NOT drop the table after your +export, it only affects the import file.
  • +
  • “If Not Exists” will only create the table if it doesn’t exist. +Otherwise, you may get an error if the table name exists but has a +different structure.
  • +
  • “Add AUTO_INCREMENT value” ensures that AUTO_INCREMENT value (if +any) will be included in backup.
  • +
  • “Enclose table and column names with backquotes” ensures that column +and table names formed with special characters are protected.
  • +
  • “Add into comments” includes column comments, relations, and MIME +types set in the pmadb in the dump as SQL comments +(/* xxx */).
  • +
+

Data:

+
    +
  • “Complete inserts” adds the column names on every INSERT command, for +better documentation (but resulting file is bigger).
  • +
  • “Extended inserts” provides a shorter dump file by using only once the +INSERT verb and the table name.
  • +
  • “Delayed inserts” are best explained in the MySQL manual - INSERT DELAYED Syntax.
  • +
  • “Ignore inserts” treats errors as a warning instead. Again, more info +is provided in the MySQL manual - INSERT Syntax, but basically with +this selected, invalid values are adjusted and inserted rather than +causing the entire statement to fail.
  • +
+
+
+

6.13 I would like to create a database with a dot in its name.

+

This is a bad idea, because in MySQL the syntax “database.table” is +the normal way to reference a database and table name. Worse, MySQL +will usually let you create a database with a dot, but then you cannot +work with it, nor delete it.

+
+
+

6.14 (withdrawn).

+
+
+

6.15 I want to add a BLOB column and put an index on it, but MySQL says “BLOB column ‘...’ used in key specification without a key length”.

+

The right way to do this, is to create the column without any indexes, +then display the table structure and use the “Create an index” dialog. +On this page, you will be able to choose your BLOB column, and set a +size to the index, which is the condition to create an index on a BLOB +column.

+
+
+

6.16 How can I simply move in page with plenty editing fields?

+

You can use Ctrl+arrows (Option+Arrows in Safari) for moving on +most pages with many editing fields (table structure changes, row editing, +etc.).

+
+
+

6.17 Transformations: I can’t enter my own mimetype! What is this feature then useful for?

+

Defining mimetypes is of no use if you can’t put +transformations on them. Otherwise you could just put a comment on the +column. Because entering your own mimetype will cause serious syntax +checking issues and validation, this introduces a high-risk false- +user-input situation. Instead you have to initialize mimetypes using +functions or empty mimetype definitions.

+

Plus, you have a whole overview of available mimetypes. Who knows all those +mimetypes by heart so he/she can enter it at will?

+
+
+

6.18 Bookmarks: Where can I store bookmarks? Why can’t I see any bookmarks below the query box? What are these variables for?

+

You need to have configured the phpMyAdmin configuration storage for using bookmarks +feature. Once you have done that, you can use bookmarks in the SQL tab.

+
+

See also

+

Bookmarks

+
+
+
+

6.19 How can I create simple LATEX document to include exported table?

+

You can simply include table in your LATEX documents, +minimal sample document should look like following one (assuming you +have table exported in file table.tex):

+
\documentclass{article} % or any class you want
+\usepackage{longtable}  % for displaying table
+\begin{document}        % start of document
+\include{table}         % including exported table
+\end{document}          % end of document
+
+
+
+
+

6.20 I see a lot of databases which are not mine, and cannot access them.

+

You have one of these global privileges: CREATE TEMPORARY TABLES, SHOW +DATABASES, LOCK TABLES. Those privileges also enable users to see all the +database names. So if your users do not need those privileges, you can remove +them and their databases list will shorten.

+ +
+
+

6.21 In edit/insert mode, how can I see a list of possible values for a column, based on some foreign table?

+

You have to setup appropriate links between the tables, and also setup +the “display column” in the foreign table. See 6.6 How can I use the relation table in Query-by-example? for an +example. Then, if there are 100 values or less in the foreign table, a +drop-down list of values will be available. You will see two lists of +values, the first list containing the key and the display column, the +second list containing the display column and the key. The reason for +this is to be able to type the first letter of either the key or the +display column. For 100 values or more, a distinct window will appear, +to browse foreign key values and choose one. To change the default +limit of 100, see $cfg['ForeignKeyMaxLimit'].

+
+
+

6.22 Bookmarks: Can I execute a default bookmark automatically when entering Browse mode for a table?

+

Yes. If a bookmark has the same label as a table name and it’s not a +public bookmark, it will be executed.

+
+

See also

+

Bookmarks

+
+
+
+

6.23 Export: I heard phpMyAdmin can export Microsoft Excel files?

+

You can use CSV for Microsoft Excel, +which works out of the box.

+
+

Changed in version 3.4.5: Since phpMyAdmin 3.4.5 support for direct export to Microsoft Excel version +97 and newer was dropped.

+
+
+
+

6.24 Now that phpMyAdmin supports native MySQL 4.1.x column comments, what happens to my column comments stored in pmadb?

+

Automatic migration of a table’s pmadb-style column comments to the +native ones is done whenever you enter Structure page for this table.

+
+
+

6.25 (withdrawn).

+
+
+

6.26 How can I select a range of rows?

+

Click the first row of the range, hold the shift key and click the +last row of the range. This works everywhere you see rows, for example +in Browse mode or on the Structure page.

+
+
+

6.27 What format strings can I use?

+

In all places where phpMyAdmin accepts format strings, you can use +@VARIABLE@ expansion and strftime +format strings. The expanded variables depend on a context (for +example, if you haven’t chosen a table, you can not get the table +name), but the following variables can be used:

+
+
@HTTP_HOST@
+
HTTP host that runs phpMyAdmin
+
@SERVER@
+
MySQL server name
+
@VERBOSE@
+
Verbose MySQL server name as defined in $cfg['Servers'][$i]['verbose']
+
@VSERVER@
+
Verbose MySQL server name if set, otherwise normal
+
@DATABASE@
+
Currently opened database
+
@TABLE@
+
Currently opened table
+
@COLUMNS@
+
Columns of the currently opened table
+
@PHPMYADMIN@
+
phpMyAdmin with version
+
+
+
+

6.28 How can I easily edit relational schema for export?

+

By clicking on the button ‘toggle scratchboard’ on the page where you +edit x/y coordinates of those elements you can activate a scratchboard +where all your elements are placed. By clicking on an element, you can +move them around in the pre-defined area and the x/y coordinates will +get updated dynamically. Likewise, when entering a new position +directly into the input field, the new position in the scratchboard +changes after your cursor leaves the input field.

+

You have to click on the ‘OK’-button below the tables to save the new +positions. If you want to place a new element, first add it to the +table of elements and then you can drag the new element around.

+

By changing the paper size and the orientation you can change the size +of the scratchboard as well. You can do so by just changing the +dropdown field below, and the scratchboard will resize automatically, +without interfering with the current placement of the elements.

+

If ever an element gets out of range you can either enlarge the paper +size or click on the ‘reset’ button to place all elements below each +other.

+
+
+

6.29 Why can’t I get a chart from my query result table?

+

Not every table can be put to the chart. Only tables with one, two or +three columns can be visualised as a chart. Moreover the table must be +in a special format for chart script to understand it. Currently +supported formats can be found in Charts.

+
+
+

6.30 Import: How can I import ESRI Shapefiles?

+

An ESRI Shapefile is actually a set of several files, where .shp file +contains geometry data and .dbf file contains data related to those +geometry data. To read data from .dbf file you need to have PHP +compiled with the dBase extension (–enable-dbase). Otherwise only +geometry data will be imported.

+

To upload these set of files you can use either of the following +methods:

+

Configure upload directory with $cfg['UploadDir'], upload both .shp and .dbf files with +the same filename and chose the .shp file from the import page.

+

Create a zip archive with .shp and .dbf files and import it. For this +to work, you need to set $cfg['TempDir'] to a place where the web server user can +write (for example './tmp').

+

To create the temporary directory on a UNIX-based system, you can do:

+
cd phpMyAdmin
+mkdir tmp
+chmod o+rwx tmp
+
+
+
+
+

6.31 How do I create a relation in designer?

+

To select relation, click: The display column is shown in pink. To +set/unset a column as the display column, click the “Choose column to +display” icon, then click on the appropriate column name.

+
+
+

6.32 How can I use the zoom search feature?

+

The Zoom search feature is an alternative to table search feature. It allows +you to explore a table by representing its data in a scatter plot. You can +locate this feature by selecting a table and clicking the Search +tab. One of the sub-tabs in the Table Search page is +Zoom Search.

+

Consider the table REL_persons in 6.6 How can I use the relation table in Query-by-example? for +an example. To use zoom search, two columns need to be selected, for +example, id and town_code. The id values will be represented on one +axis and town_code values on the other axis. Each row will be +represented as a point in a scatter plot based on its id and +town_code. You can include two additional search criteria apart from +the two fields to display.

+

You can choose which field should be +displayed as label for each point. If a display column has been set +for the table (see 6.7 How can I use the “display column” feature?), it is taken as the label unless +you specify otherwise. You can also select the maximum number of rows +you want to be displayed in the plot by specifing it in the ‘Max rows +to plot’ field. Once you have decided over your criteria, click ‘Go’ +to display the plot.

+

After the plot is generated, you can use the +mousewheel to zoom in and out of the plot. In addition, panning +feature is enabled to navigate through the plot. You can zoom-in to a +certain level of detail and use panning to locate your area of +interest. Clicking on a point opens a dialogue box, displaying field +values of the data row represented by the point. You can edit the +values if required and click on submit to issue an update query. Basic +instructions on how to use can be viewed by clicking the ‘How to use?’ +link located just above the plot.

+
+
+

6.33 When browsing a table, how can I copy a column name?

+

Selecting the name of the column within the browse table header cell +for copying is difficult, as the columns support reordering by +dragging the header cells as well as sorting by clicking on the linked +column name. To copy a column name, double-click on the empty area +next to the column name, when the tooltip tells you to do so. This +will show you an input box with the column name. You may right-click +the column name within this input box to copy it to your clipboard.

+
+
+

6.34 How can I use the Favorite Tables feature?

+

Favorite Tables feature is very much similar to Recent Tables feature. +It allows you to add a shortcut for the frequently used tables of any +database in the navigation panel . You can easily navigate to any table +in the list by simply choosing it from the list. These tables are stored +in your browser’s local storage if you have not configured your +phpMyAdmin Configuration Storage. Otherwise these entries are stored in +phpMyAdmin Configuration Storage.

+

IMPORTANT: In absence of phpMyAdmin Configuration Storage, your Favorite +tables may be different in different browsers based on your different +selections in them.

+

To add a table to Favorite list simply click on the Gray star in front +of a table name in the list of tables of a Database and wait until it +turns to Yellow. +To remove a table from list, simply click on the Yellow star and +wait until it turns Gray again.

+

Using $cfg['NumFavoriteTables'] in your config.inc.php +file, you can define the maximum number of favorite tables shown in the +navigation panel. Its default value is 10.

+
+
+

6.35 How can I use the Range search feature?

+

With the help of range search feature, one can specify a range of values for +particular column(s) while performing search operation on a table from the Search +tab.

+

To use this feature simply click on the BETWEEN or NOT BETWEEN operators +from the operator select list in front of the column name. On choosing one of the +above options, a dialog box will show up asking for the Minimum and Maximum +value for that column. Only the specified range of values will be included +in case of BETWEEN and excluded in case of NOT BETWEEN from the final results.

+

Note: The Range search feature will work only Numeric and Date data type columns.

+
+
+

6.36 What is Central columns and how can I use this feature?

+

As the name suggests, the Central columns feature enables to maintain a central list of +columns per database to avoid similar name for the same data element and bring consistency +of data type for the same data element. You can use the central list of columns to +add an element to any table structure in that database which will save from writing +similar column name and column definition.

+

To add a column to central list, go to table structure page, check the columns you want +to include and then simply click on “Add to central columns”. If you want to add all +unique columns from more than one table from a database then go to database structure page, +check the tables you want to include and then select “Add columns to central list”.

+

To remove a column from central list, go to Table structure page, check the columns you want +to remove and then simply click on “Remove from central columns”. If you want to remove all +columns from more than one tables from a database then go to database structure page, +check the tables you want to include and then select “Remove columns from central list”.

+

To view and manage the central list, select the database you want to manage central columns +for then from the top menu click on “Central columns”. You will be taken to a page where +you will have options to edit, delete or add new columns to central list.

+
+
+

6.37 How can I use Improve Table structure feature?

+

Improve table structure feature helps to bring the table structure upto +Third Normal Form. A wizard is presented to user which asks questions about the +elements during the various steps for normalization and a new structure is proposed +accordingly to bring the table into the First/Second/Third Normal form. +On startup of the wizard, user gets to select upto what normal form they want to +normalize the table structure.

+

Here is an example table which you can use to test all of the three First, Second and +Third Normal Form.

+
CREATE TABLE `VetOffice` (
+ `petName` varchar(64) NOT NULL,
+ `petBreed` varchar(64) NOT NULL,
+ `petType` varchar(64) NOT NULL,
+ `petDOB` date NOT NULL,
+ `ownerLastName` varchar(64) NOT NULL,
+ `ownerFirstName` varchar(64) NOT NULL,
+ `ownerPhone1` int(12) NOT NULL,
+ `ownerPhone2` int(12) NOT NULL,
+ `ownerEmail` varchar(64) NOT NULL,
+);
+
+
+

The above table is not in First normal Form as no primary key exists. Primary key +is supposed to be (petName,`ownerLastName`,`ownerFirstName`) . If the primary key +is chosen as suggested the resultant table won’t be in Second as well as Third Normal +form as the following dependencies exists.

+
(OwnerLastName, OwnerFirstName) -> OwnerEmail
+(OwnerLastName, OwnerFirstName) -> OwnerPhone
+PetBreed -> PetType
+
+
+

Which says, OwnerEmail depends on OwnerLastName and OwnerFirstName. +OwnerPhone depends on OwnerLastName and OwnerFirstName. +PetType depends on PetBreed.

+
+
+

6.38 How can I reassign auto-incremented values?

+

Some users prefer their AUTO_INCREMENT values to be consecutive; this is not +always the case after row deletion.

+

Here are the steps to accomplish this. These are manual steps because they +involve a manual verification at one point.

+
    +
  • Ensure that you have exclusive access to the table to rearrange
  • +
  • On your primary key column (i.e. id), remove the AUTO_INCREMENT setting
  • +
  • Delete your primary key in Structure > indexes
  • +
  • Create a new column future_id as primary key, AUTO_INCREMENT
  • +
  • Browse your table and verify that the new increments correspond to what +you’re expecting
  • +
  • Drop your old id column
  • +
  • Rename the future_id column to id
  • +
  • Move the new id column via Structure > Move columns
  • +
+
+
+

6.39 What is the “Adjust privileges” option when renaming, copying, or moving a database, table, column, or procedure?

+

When renaming/copying/moving a database/table/column/procedure, +MySQL does not adjust the original privileges relating to these objects +on its own. By selecting this option, phpMyAdmin will adjust the privilege +table so that users have the same privileges on the new items.

+

For example: A user 'bob'@'localhost‘ has a ‘SELECT’ privilege on a +column named ‘id’. Now, if this column is renamed to ‘id_new’, MySQL, +on its own, would not adjust the column privileges to the new column name. +phpMyAdmin can make this adjustment for you automatically.

+

Notes:

+
    +
  • While adjusting privileges for a database, the privileges of all +database-related elements (tables, columns and procedures) are also adjusted +to the database’s new name.
  • +
  • Similarly, while adjusting privileges for a table, the privileges of all +the columns inside the new table are also adjusted.
  • +
  • While adjusting privileges, the user performing the operation must have the following +privileges:
      +
    • SELECT, INSERT, UPDATE, DELETE privileges on following tables: +mysql.`db`, mysql.`columns_priv`, mysql.`tables_priv`, mysql.`procs_priv`
    • +
    • FLUSH privilege (GLOBAL)
    • +
    +
  • +
+

Thus, if you want to replicate the database/table/column/procedure as it is +while renaming/copying/moving these objects, make sure you have checked this option.

+
+
+

6.40 I see “Bind parameters” checkbox in the “SQL” page. How do I write parameterized SQL queries?

+

From version 4.5, phpMyAdmin allows users to execute parameterized queries in the “SQL” page. +Parameters should be prefixed with a colon(:) and when the “Bind parameters” checkbox is checked +these parameters will be identified and input fields for these parameters will be presented. +Values entered in these field will be substituted in the query before being executed.

+
+
+

6.41 I get import errors while importing the dumps exported from older MySQL versions (pre-5.7.6) into newer MySQL versions (5.7.7+), but they work fine when imported back on same older versions ?

+

If you get errors like #1031 - Table storage engine for ‘table_name’ doesn’t have this option +while importing the dumps exported from pre-5.7.7 MySQL servers into new MySQL server versions 5.7.7+, +it might be because ROW_FORMAT=FIXED is not supported with InnoDB tables. Moreover, the value of +innodb_strict_mode would define if this would be reported as a warning or as an error.

+

Since MySQL version 5.7.9, the default value for innodb_strict_mode is ON and thus would generate +an error when such a CREATE TABLE or ALTER TABLE statement is encountered.

+

There are two ways of preventing such errors while importing:

+
    +
  • Change the value of innodb_strict_mode to OFF before starting the import and turn it ON after +the import is successfully completed.
  • +
  • This can be achieved in two ways:
      +
    • Go to ‘Variables’ page and edit the value of innodb_strict_mode
    • +
    • Run the query : SET GLOBAL `innodb_strict_mode = ‘[value]’`
    • +
    +
  • +
+

After the import is done, it is suggested that the value of innodb_strict_mode should be reset to the +original value.

+
+
+
+

phpMyAdmin project

+
+

7.1 I have found a bug. How do I inform developers?

+

Our issues tracker is located at <https://github.com/phpmyadmin/phpmyadmin/issues>. +For security issues, please refer to the instructions at <https://www.phpmyadmin.net/security> to email +the developers directly.

+
+
+

7.2 I want to translate the messages to a new language or upgrade an existing language, where do I start?

+

Translations are very welcome and all you need to have are the +language skills. The easiest way is to use our online translation +service. You can check +out all the possibilities to translate in the translate section on +our website.

+
+
+

7.3 I would like to help out with the development of phpMyAdmin. How should I proceed?

+

We welcome every contribution to the development of phpMyAdmin. You +can check out all the possibilities to contribute in the contribute +section on our website.

+ +
+
+
+

Security

+
+

8.1 Where can I get information about the security alerts issued for phpMyAdmin?

+

Please refer to <https://www.phpmyadmin.net/security/>.

+
+
+

8.2 How can I protect phpMyAdmin against brute force attacks?

+

If you use Apache web server, phpMyAdmin exports information about +authentication to the Apache environment and it can be used in Apache +logs. Currently there are two variables available:

+
+
userID
+
User name of currently active user (he does not have to be logged in).
+
userStatus
+
Status of currently active user, one of ok (user is logged in), +mysql-denied (MySQL denied user login), allow-denied (user denied +by allow/deny rules), root-denied (root is denied in configuration), +empty-denied (empty password is denied).
+
+

LogFormat directive for Apache can look like following:

+
LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\" %{userID}n %{userStatus}n"   pma_combined
+
+
+

You can then use any log analyzing tools to detect possible break-in +attempts.

+
+
+

8.3 Why are there path disclosures when directly loading certain files?

+

This is a server configuration problem. Never enable display_errors on a production site.

+
+
+

8.4 CSV files exported from phpMyAdmin could allow a formula injection attack.

+

It is possible to generate a CSV file that, when imported to a spreadsheet program such as Microsoft Excel, +could potentially allow the execution of arbitrary commands.

+

The CSV files generated by phpMyAdmin could potentially contain text that would be interpreted by a spreadsheet program as +a formula, but we do not believe escaping those fields is the proper behavior. There is no means to properly escape and +differentiate between a desired text output and a formula that should be escaped, and CSV is a text format where function +definitions should not be interpreted anyway. We have discussed this at length and feel it is the responsibility of the +spreadsheet program to properly parse and sanitize such data on input instead.

+

Google also has a similar view.

+
+
+
+

Synchronization

+
+

9.1 (withdrawn).

+
+
+

9.2 (withdrawn).

+
+
+
+ + +
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/php/apps/phpmyadmin49/doc/html/genindex.html b/php/apps/phpmyadmin49/doc/html/genindex.html new file mode 100644 index 00000000..6ca07a2b --- /dev/null +++ b/php/apps/phpmyadmin49/doc/html/genindex.html @@ -0,0 +1,4265 @@ + + + + + + + + + Index — phpMyAdmin 4.9.1 documentation + + + + + + + + + + + + + + + + +
+
+
+
+ + +

Index

+ +
+ Symbols + | A + | B + | C + | D + | E + | F + | G + | H + | I + | J + | K + | L + | M + | N + | O + | P + | Q + | R + | S + | T + | U + | V + | W + | X + | Z + +
+

Symbols

+ + + +
+ +
$cfg['ActionLinksMode'] +
+ + +
$cfg['AllowArbitraryServer'], [1], [2], [3] +
+ + +
$cfg['AllowThirdPartyFraming'] +
+ + +
$cfg['AllowUserDropDatabase'] +
+ + +
$cfg['ArbitraryServerRegexp'], [1] +
+ + +
$cfg['AuthLog'], [1], [2] +
+ + +
$cfg['AuthLogSuccess'], [1] +
+ + +
$cfg['AvailableCharsets'] +
+ + +
$cfg['blowfish_secret'], [1] +
+ + +
$cfg['BrowseMarkerEnable'] +
+ + +
$cfg['BrowseMIME'] +
+ + +
$cfg['BrowsePointerEnable'] +
+ + +
$cfg['BZipDump'] +
+ + +
$cfg['CaptchaLoginPrivateKey'], [1] +
+ + +
$cfg['CaptchaLoginPublicKey'], [1] +
+ + +
$cfg['CharEditing'], [1] +
+ + +
$cfg['CharTextareaCols'] +
+ + +
$cfg['CharTextareaRows'] +
+ + +
$cfg['CheckConfigurationPermissions'] +
+ + +
$cfg['CodemirrorEnable'] +
+ + +
$cfg['CompressOnFly'], [1] +
+ + +
$cfg['Confirm'] +
+ + +
$cfg['Console']['AlwaysExpand'] +
+ + +
$cfg['Console']['CurrentQuery'] +
+ + +
$cfg['Console']['DarkTheme'] +
+ + +
$cfg['Console']['EnterExecutes'] +
+ + +
$cfg['Console']['Height'] +
+ + +
$cfg['Console']['Mode'] +
+ + +
$cfg['Console']['StartHistory'] +
+ + +
$cfg['ConsoleEnterExecutes'] +
+ + +
$cfg['CSPAllow'] +
+ + +
$cfg['DBG'] +
+ + +
$cfg['DBG']['demo'] +
+ + +
$cfg['DBG']['simple2fa'], [1] +
+ + +
$cfg['DBG']['sql'], [1] +
+ + +
$cfg['DBG']['sqllog'] +
+ + +
$cfg['DefaultConnectionCollation'] +
+ + +
$cfg['DefaultForeignKeyChecks'] +
+ + +
$cfg['DefaultFunctions'] +
+ + +
$cfg['DefaultLang'] +
+ + +
$cfg['DefaultQueryDatabase'] +
+ + +
$cfg['DefaultQueryTable'] +
+ + +
$cfg['DefaultTabDatabase'] +
+ + +
$cfg['DefaultTabServer'] +
+ + +
$cfg['DefaultTabTable'] +
+ + +
$cfg['DefaultTransformations'], [1] +
+ + +
$cfg['DefaultTransformations']['Bool2Text'] +
+ + +
$cfg['DefaultTransformations']['DateFormat'] +
+ + +
$cfg['DefaultTransformations']['External'] +
+ + +
$cfg['DefaultTransformations']['Hex'] +
+ + +
$cfg['DefaultTransformations']['Inline'] +
+ + +
$cfg['DefaultTransformations']['PreApPend'] +
+ + +
$cfg['DefaultTransformations']['Substring'] +
+ + +
$cfg['DefaultTransformations']['TextImageLink'] +
+ + +
$cfg['DefaultTransformations']['TextLink'] +
+ + +
$cfg['DisableMultiTableMaintenance'] +
+ + +
$cfg['DisableShortcutKeys'], [1] +
+ + +
$cfg['DisplayServersList'] +
+ + +
$cfg['EnableAutocompleteForTablesAndColumns'] +
+ + +
$cfg['ExecTimeLimit'], [1] +
+ + +
$cfg['Export'] +
+ + +
$cfg['Export']['charset'], [1] +
+ + +
$cfg['Export']['file_template_database'] +
+ + +
$cfg['Export']['file_template_server'] +
+ + +
$cfg['Export']['file_template_table'] +
+ + +
$cfg['Export']['format'] +
+ + +
$cfg['Export']['method'] +
+ + +
$cfg['FilterLanguages'] +
+ + +
$cfg['FirstLevelNavigationItems'] +
+ + +
$cfg['FontSize'] +
+ + +
$cfg['ForceSSL'] +
+ + +
$cfg['ForeignKeyDropdownOrder'], [1] +
+ + +
$cfg['ForeignKeyMaxLimit'], [1] +
+ + +
$cfg['GD2Available'] +
+ + +
$cfg['GridEditing'] +
+ + +
$cfg['GZipDump'] +
+ + +
$cfg['HideStructureActions'] +
+ + +
$cfg['IconvExtraParams'] +
+ + +
$cfg['IgnoreMultiSubmitErrors'] +
+ + +
$cfg['Import'] +
+ + +
$cfg['Import']['charset'], [1] +
+ + +
$cfg['InitialSlidersState'] +
+ + +
$cfg['InsertRows'] +
+ + +
$cfg['Lang'] +
+ + +
$cfg['LimitChars'] +
+ + +
$cfg['LinkLengthLimit'] +
+ + +
$cfg['LoginCookieDeleteAll'] +
+ + +
$cfg['LoginCookieRecall'] +
+ + +
$cfg['LoginCookieStore'] +
+ + +
$cfg['LoginCookieValidity'], [1] +
+ + +
$cfg['LoginCookieValidityDisableWarning'] +
+ + +
$cfg['LongtextDoubleTextarea'] +
+ + +
$cfg['MaxCharactersInDisplayedSQL'] +
+ + +
$cfg['MaxDbList'] +
+ + +
$cfg['MaxExactCount'], [1], [2] +
+ + +
$cfg['MaxExactCountViews'] +
+ + +
$cfg['MaxNavigationItems'] +
+ + +
$cfg['MaxRows'] +
+ + +
$cfg['MaxSizeForInputField'] +
+ + +
$cfg['MaxTableList'] +
+ + +
$cfg['MemoryLimit'] +
+ + +
$cfg['MinSizeForInputField'] +
+ + +
$cfg['MysqlMinVersion'] +
+ + +
$cfg['NaturalOrder'] +
+ + +
$cfg['NavigationDisplayLogo'] +
+ + +
$cfg['NavigationDisplayServers'] +
+ + +
$cfg['NavigationLinkWithMainPanel'] +
+ + +
$cfg['NavigationLogoLink'] +
+ + +
$cfg['NavigationLogoLinkWindow'] +
+ + +
$cfg['NavigationTreeDbSeparator'], [1] +
+ + +
$cfg['NavigationTreeDefaultTabTable'], [1], [2] +
+ + +
$cfg['NavigationTreeDefaultTabTable2'] +
+ + +
$cfg['NavigationTreeDisplayDbFilterMinimum'] +
+ + +
$cfg['NavigationTreeDisplayItemFilterMinimum'] +
+ + +
$cfg['NavigationTreeEnableExpansion'] +
+ + +
$cfg['NavigationTreeEnableGrouping'] +
+ + +
$cfg['NavigationTreePointerEnable'] +
+ + +
$cfg['NavigationTreeShowEvents'] +
+ + +
$cfg['NavigationTreeShowFunctions'] +
+ + +
$cfg['NavigationTreeShowProcedures'] +
+ + +
$cfg['NavigationTreeShowTables'] +
+ + +
$cfg['NavigationTreeShowViews'] +
+ + +
$cfg['NavigationTreeTableLevel'] +
+ + +
$cfg['NavigationTreeTableSeparator'], [1] +
+ + +
$cfg['NavigationWidth'] +
+ + +
$cfg['NumFavoriteTables'], [1], [2] +
+ + +
$cfg['NumRecentTables'], [1] +
+ + +
$cfg['OBGzip'], [1], [2], [3] +
+ + +
$cfg['Order'] +
+ + +
$cfg['PDFDefaultPageSize'] +
+ + +
$cfg['PDFPageSizes'], [1] +
+ + +
$cfg['PersistentConnections'] +
+ + +
$cfg['PmaAbsoluteUri'], [1], [2], [3], [4], [5], [6], [7] +
+ + +
$cfg['PmaNoRelation_DisableWarning'] +
+ +
+ +
$cfg['PropertiesNumColumns'] +
+ + +
$cfg['ProtectBinary'] +
+ + +
$cfg['ProxyPass'] +
+ + +
$cfg['ProxyUrl'] +
+ + +
$cfg['ProxyUser'] +
+ + +
$cfg['QueryHistoryDB'], [1], [2] +
+ + +
$cfg['QueryHistoryMax'], [1], [2] +
+ + +
$cfg['RecodingEngine'] +
+ + +
$cfg['RelationalDisplay'] +
+ + +
$cfg['RememberSorting'], [1] +
+ + +
$cfg['RepeatCells'] +
+ + +
$cfg['ReservedWordDisableWarning'] +
+ + +
$cfg['RetainQueryBox'] +
+ + +
$cfg['RowActionLinks'] +
+ + +
$cfg['RowActionLinksWithoutUnique'] +
+ + +
$cfg['RowActionType'] +
+ + +
$cfg['SaveCellsAtOnce'] +
+ + +
$cfg['SaveDir'], [1] +
+ + +
$cfg['SendErrorReports'] +
+ + +
$cfg['ServerDefault'], [1], [2] +
+ + +
$cfg['ServerLibraryDifference_DisableWarning'] +
+ + +
$cfg['Servers'], [1], [2], [3], [4] +
+ + +
$cfg['Servers'][$i]['AllowDeny']['order'], [1], [2] +
+ + +
$cfg['Servers'][$i]['AllowDeny']['rules'], [1], [2], [3], [4], [5] +
+ + +
$cfg['Servers'][$i]['AllowNoPassword'] +
+ + +
$cfg['Servers'][$i]['AllowRoot'], [1] +
+ + +
$cfg['Servers'][$i]['auth_http_realm'] +
+ + +
$cfg['Servers'][$i]['auth_type'], [1], [2] +
+ + +
$cfg['Servers'][$i]['bookmarktable'], [1] +
+ + +
$cfg['Servers'][$i]['central_columns'], [1] +
+ + +
$cfg['Servers'][$i]['column_info'], [1], [2] +
+ + +
$cfg['Servers'][$i]['compress'] +
+ + +
$cfg['Servers'][$i]['connect_type'] +
+ + +
$cfg['Servers'][$i]['control_*'], [1], [2], [3] +
+ + +
$cfg['Servers'][$i]['controlhost'], [1] +
+ + +
$cfg['Servers'][$i]['controlpass'], [1], [2] +
+ + +
$cfg['Servers'][$i]['controlport'], [1] +
+ + +
$cfg['Servers'][$i]['controluser'], [1], [2], [3], [4] +
+ + +
$cfg['Servers'][$i]['designer_settings'], [1] +
+ + +
$cfg['Servers'][$i]['DisableIS'] +
+ + +
$cfg['Servers'][$i]['export_templates'], [1] +
+ + +
$cfg['Servers'][$i]['extension'] +
+ + +
$cfg['Servers'][$i]['favorite'], [1] +
+ + +
$cfg['Servers'][$i]['hide_db'] +
+ + +
$cfg['Servers'][$i]['history'], [1], [2] +
+ + +
$cfg['Servers'][$i]['host'], [1], [2], [3], [4], [5], [6], [7], [8], [9] +
+ + +
$cfg['Servers'][$i]['LogoutURL'] +
+ + +
$cfg['Servers'][$i]['MaxTableUiprefs'] +
+ + +
$cfg['Servers'][$i]['navigationhiding'], [1] +
+ + +
$cfg['Servers'][$i]['nopassword'] +
+ + +
$cfg['Servers'][$i]['only_db'] +
+ + +
$cfg['Servers'][$i]['password'], [1] +
+ + +
$cfg['Servers'][$i]['pdf_pages'], [1], [2] +
+ + +
$cfg['Servers'][$i]['pmadb'], [1], [2], [3], [4], [5], [6], [7], [8], [9], [10], [11], [12], [13], [14], [15], [16], [17], [18], [19], [20], [21], [22] +
+ + +
$cfg['Servers'][$i]['port'], [1] +
+ + +
$cfg['Servers'][$i]['recent'], [1] +
+ + +
$cfg['Servers'][$i]['relation'], [1] +
+ + +
$cfg['Servers'][$i]['savedsearches'], [1] +
+ + +
$cfg['Servers'][$i]['SessionTimeZone'] +
+ + +
$cfg['Servers'][$i]['SignonCookieParams'], [1], [2] +
+ + +
$cfg['Servers'][$i]['SignonScript'], [1], [2], [3], [4], [5] +
+ + +
$cfg['Servers'][$i]['SignonSession'], [1], [2] +
+ + +
$cfg['Servers'][$i]['SignonURL'], [1], [2] +
+ + +
$cfg['Servers'][$i]['socket'], [1], [2] +
+ + +
$cfg['Servers'][$i]['ssl'], [1], [2], [3], [4], [5], [6], [7], [8], [9], [10] +
+ + +
$cfg['Servers'][$i]['ssl_ca'], [1], [2], [3], [4], [5], [6], [7], [8], [9], [10] +
+ + +
$cfg['Servers'][$i]['ssl_ca_path'], [1], [2], [3], [4], [5], [6], [7], [8], [9] +
+ + +
$cfg['Servers'][$i]['ssl_cert'], [1], [2], [3], [4], [5], [6], [7], [8], [9], [10] +
+ + +
$cfg['Servers'][$i]['ssl_ciphers'], [1], [2], [3], [4], [5], [6], [7], [8] +
+ + +
$cfg['Servers'][$i]['ssl_key'], [1], [2], [3], [4], [5], [6], [7], [8], [9], [10] +
+ + +
$cfg['Servers'][$i]['ssl_verify'], [1], [2], [3], [4], [5], [6], [7], [8], [9], [10], [11] +
+ + +
$cfg['Servers'][$i]['table_coords'], [1], [2] +
+ + +
$cfg['Servers'][$i]['table_info'], [1] +
+ + +
$cfg['Servers'][$i]['table_uiprefs'], [1], [2], [3], [4] +
+ + +
$cfg['Servers'][$i]['tracking'], [1] +
+ + +
$cfg['Servers'][$i]['tracking_add_drop_database'] +
+ + +
$cfg['Servers'][$i]['tracking_add_drop_table'] +
+ + +
$cfg['Servers'][$i]['tracking_add_drop_view'] +
+ + +
$cfg['Servers'][$i]['tracking_default_statements'] +
+ + +
$cfg['Servers'][$i]['tracking_version_auto_create'] +
+ + +
$cfg['Servers'][$i]['user'], [1] +
+ + +
$cfg['Servers'][$i]['userconfig'], [1] +
+ + +
$cfg['Servers'][$i]['usergroups'], [1], [2], [3] +
+ + +
$cfg['Servers'][$i]['users'], [1] +
+ + +
$cfg['Servers'][$i]['verbose'], [1], [2], [3], [4] +
+ + +
$cfg['SessionSavePath'], [1] +
+ + +
$cfg['ShowAll'] +
+ + +
$cfg['ShowBrowseComments'] +
+ + +
$cfg['ShowChgPassword'] +
+ + +
$cfg['ShowColumnComments'] +
+ + +
$cfg['ShowCreateDb'] +
+ + +
$cfg['ShowDatabasesNavigationAsTree'] +
+ + +
$cfg['ShowDbStructureCreation'] +
+ + +
$cfg['ShowDbStructureLastCheck'] +
+ + +
$cfg['ShowDbStructureLastUpdate'] +
+ + +
$cfg['ShowFieldTypesInDataEditView'] +
+ + +
$cfg['ShowFunctionFields'] +
+ + +
$cfg['ShowGitRevision'] +
+ + +
$cfg['ShowHint'] +
+ + +
$cfg['ShowPhpInfo'] +
+ + +
$cfg['ShowPropertyComments'] +
+ + +
$cfg['ShowServerInfo'] +
+ + +
$cfg['ShowSQL'] +
+ + +
$cfg['ShowStats'] +
+ + +
$cfg['SkipLockedTables'] +
+ + +
$cfg['SQLQuery']['Edit'] +
+ + +
$cfg['SQLQuery']['Explain'] +
+ + +
$cfg['SQLQuery']['Refresh'] +
+ + +
$cfg['SQLQuery']['ShowAsPHP'] +
+ + +
$cfg['SuhosinDisableWarning'], [1] +
+ + +
$cfg['TableNavigationLinksMode'] +
+ + +
$cfg['TablePrimaryKeyOrder'] +
+ + +
$cfg['TabsMode'] +
+ + +
$cfg['TempDir'], [1], [2], [3], [4] +
+ + +
$cfg['TextareaAutoSelect'] +
+ + +
$cfg['TextareaCols'] +
+ + +
$cfg['TextareaRows'] +
+ + +
$cfg['ThemeDefault'], [1] +
+ + +
$cfg['ThemeManager'], [1], [2] +
+ + +
$cfg['ThemePerServer'] +
+ + +
$cfg['TitleDatabase'] +
+ + +
$cfg['TitleDefault'] +
+ + +
$cfg['TitleServer'] +
+ + +
$cfg['TitleTable'] +
+ + +
$cfg['TranslationWarningThreshold'] +
+ + +
$cfg['TrustedProxies'], [1] +
+ + +
$cfg['UploadDir'], [1], [2], [3] +
+ + +
$cfg['UseDbSearch'] +
+ + +
$cfg['UserprefsDeveloperTab'] +
+ + +
$cfg['UserprefsDisallow'], [1] +
+ + +
$cfg['VersionCheck'] +
+ + +
$cfg['ZeroConf'], [1] +
+ + +
$cfg['ZipDump'] +
+ + +
.htaccess +
+ +
+ +

A

+ + + +
+ +
ACL +
+ + +
ActionLinksMode +
+ + +
AllowArbitraryServer +
+ + +
AllowDeny, order +
+ + +
AllowDeny, rules +
+ + +
AllowNoPassword +
+ + +
AllowRoot +
+ + +
AllowThirdPartyFraming +
+ + +
AllowUserDropDatabase +
+ + +
ArbitraryServerRegexp +
+ +
+ +
auth_http_realm +
+ + +
auth_type +
+ + +
Authentication mode +
+ +
+ +
Config +
+ + +
Cookie +
+ + +
HTTP +
+ + +
Signon +
+ +
+ +
AuthLog +
+ + +
AuthLogSuccess +
+ + +
AvailableCharsets +
+ +
+ +

B

+ + + +
+ +
Blowfish +
+ + +
blowfish_secret +
+ + +
bookmarktable +
+ + +
BrowseMarkerEnable +
+ +
+ +
BrowseMIME +
+ + +
BrowsePointerEnable +
+ + +
Browser +
+ + +
bzip2 +
+ + +
BZipDump +
+ +
+ +

C

+ + + +
+ +
CaptchaLoginPrivateKey +
+ + +
CaptchaLoginPublicKey +
+ + +
central_columns +
+ + +
CGI +
+ + +
Changelog +
+ + +
CharEditing +
+ + +
CharTextareaCols +
+ + +
CharTextareaRows +
+ + +
CheckConfigurationPermissions +
+ + +
Client +
+ + +
CodemirrorEnable +
+ + +
column +
+ + +
column_info +
+ + +
comment (global variable or constant) +
+ + +
compress +
+ + +
CompressOnFly +
+ + +
+ Config +
+ +
+ +
Authentication mode +
+ +
+ +
config.inc.php +
+ + +
+ configuration option +
+ +
+ +
$cfg['ActionLinksMode'] +
+ + +
$cfg['AllowArbitraryServer'], [1], [2], [3] +
+ + +
$cfg['AllowThirdPartyFraming'] +
+ + +
$cfg['AllowUserDropDatabase'] +
+ + +
$cfg['ArbitraryServerRegexp'], [1] +
+ + +
$cfg['AuthLog'], [1], [2] +
+ + +
$cfg['AuthLogSuccess'], [1] +
+ + +
$cfg['AvailableCharsets'] +
+ + +
$cfg['BZipDump'] +
+ + +
$cfg['BrowseMIME'] +
+ + +
$cfg['BrowseMarkerEnable'] +
+ + +
$cfg['BrowsePointerEnable'] +
+ + +
$cfg['CSPAllow'] +
+ + +
$cfg['CaptchaLoginPrivateKey'], [1] +
+ + +
$cfg['CaptchaLoginPublicKey'], [1] +
+ + +
$cfg['CharEditing'], [1] +
+ + +
$cfg['CharTextareaCols'] +
+ + +
$cfg['CharTextareaRows'] +
+ + +
$cfg['CheckConfigurationPermissions'] +
+ + +
$cfg['CodemirrorEnable'] +
+ + +
$cfg['CompressOnFly'], [1] +
+ + +
$cfg['Confirm'] +
+ + +
$cfg['Console']['AlwaysExpand'] +
+ + +
$cfg['Console']['CurrentQuery'] +
+ + +
$cfg['Console']['DarkTheme'] +
+ + +
$cfg['Console']['EnterExecutes'] +
+ + +
$cfg['Console']['Height'] +
+ + +
$cfg['Console']['Mode'] +
+ + +
$cfg['Console']['StartHistory'] +
+ + +
$cfg['ConsoleEnterExecutes'] +
+ + +
$cfg['DBG'] +
+ + +
$cfg['DBG']['demo'] +
+ + +
$cfg['DBG']['simple2fa'], [1] +
+ + +
$cfg['DBG']['sql'], [1] +
+ + +
$cfg['DBG']['sqllog'] +
+ + +
$cfg['DefaultConnectionCollation'] +
+ + +
$cfg['DefaultForeignKeyChecks'] +
+ + +
$cfg['DefaultFunctions'] +
+ + +
$cfg['DefaultLang'] +
+ + +
$cfg['DefaultQueryDatabase'] +
+ + +
$cfg['DefaultQueryTable'] +
+ + +
$cfg['DefaultTabDatabase'] +
+ + +
$cfg['DefaultTabServer'] +
+ + +
$cfg['DefaultTabTable'] +
+ + +
$cfg['DefaultTransformations'], [1] +
+ + +
$cfg['DefaultTransformations']['Bool2Text'] +
+ + +
$cfg['DefaultTransformations']['DateFormat'] +
+ + +
$cfg['DefaultTransformations']['External'] +
+ + +
$cfg['DefaultTransformations']['Hex'] +
+ + +
$cfg['DefaultTransformations']['Inline'] +
+ + +
$cfg['DefaultTransformations']['PreApPend'] +
+ + +
$cfg['DefaultTransformations']['Substring'] +
+ + +
$cfg['DefaultTransformations']['TextImageLink'] +
+ + +
$cfg['DefaultTransformations']['TextLink'] +
+ + +
$cfg['DisableMultiTableMaintenance'] +
+ + +
$cfg['DisableShortcutKeys'], [1] +
+ + +
$cfg['DisplayServersList'] +
+ + +
$cfg['EnableAutocompleteForTablesAndColumns'] +
+ + +
$cfg['ExecTimeLimit'], [1] +
+ + +
$cfg['Export'] +
+ + +
$cfg['Export']['charset'], [1] +
+ + +
$cfg['Export']['file_template_database'] +
+ + +
$cfg['Export']['file_template_server'] +
+ + +
$cfg['Export']['file_template_table'] +
+ + +
$cfg['Export']['format'] +
+ + +
$cfg['Export']['method'] +
+ + +
$cfg['FilterLanguages'] +
+ + +
$cfg['FirstLevelNavigationItems'] +
+ + +
$cfg['FontSize'] +
+ + +
$cfg['ForceSSL'] +
+ + +
$cfg['ForeignKeyDropdownOrder'], [1] +
+ + +
$cfg['ForeignKeyMaxLimit'], [1] +
+ + +
$cfg['GD2Available'] +
+ + +
$cfg['GZipDump'] +
+ + +
$cfg['GridEditing'] +
+ + +
$cfg['HideStructureActions'] +
+ + +
$cfg['IconvExtraParams'] +
+ + +
$cfg['IgnoreMultiSubmitErrors'] +
+ + +
$cfg['Import'] +
+ + +
$cfg['Import']['charset'], [1] +
+ + +
$cfg['InitialSlidersState'] +
+ + +
$cfg['InsertRows'] +
+ + +
$cfg['Lang'] +
+ + +
$cfg['LimitChars'] +
+ + +
$cfg['LinkLengthLimit'] +
+ + +
$cfg['LoginCookieDeleteAll'] +
+ + +
$cfg['LoginCookieRecall'] +
+ + +
$cfg['LoginCookieStore'] +
+ + +
$cfg['LoginCookieValidity'], [1] +
+ + +
$cfg['LoginCookieValidityDisableWarning'] +
+ + +
$cfg['LongtextDoubleTextarea'] +
+ + +
$cfg['MaxCharactersInDisplayedSQL'] +
+ + +
$cfg['MaxDbList'] +
+ + +
$cfg['MaxExactCount'], [1], [2] +
+ + +
$cfg['MaxExactCountViews'] +
+ + +
$cfg['MaxNavigationItems'] +
+ + +
$cfg['MaxRows'] +
+ + +
$cfg['MaxSizeForInputField'] +
+ + +
$cfg['MaxTableList'] +
+ + +
$cfg['MemoryLimit'] +
+ + +
$cfg['MinSizeForInputField'] +
+ + +
$cfg['MysqlMinVersion'] +
+ + +
$cfg['NaturalOrder'] +
+ + +
$cfg['NavigationDisplayLogo'] +
+ + +
$cfg['NavigationDisplayServers'] +
+ + +
$cfg['NavigationLinkWithMainPanel'] +
+ + +
$cfg['NavigationLogoLink'] +
+ + +
$cfg['NavigationLogoLinkWindow'] +
+ + +
$cfg['NavigationTreeDbSeparator'], [1] +
+ + +
$cfg['NavigationTreeDefaultTabTable'], [1], [2] +
+ + +
$cfg['NavigationTreeDefaultTabTable2'] +
+ + +
$cfg['NavigationTreeDisplayDbFilterMinimum'] +
+ + +
$cfg['NavigationTreeDisplayItemFilterMinimum'] +
+ + +
$cfg['NavigationTreeEnableExpansion'] +
+ + +
$cfg['NavigationTreeEnableGrouping'] +
+ + +
$cfg['NavigationTreePointerEnable'] +
+ + +
$cfg['NavigationTreeShowEvents'] +
+ + +
$cfg['NavigationTreeShowFunctions'] +
+ + +
$cfg['NavigationTreeShowProcedures'] +
+ + +
$cfg['NavigationTreeShowTables'] +
+ + +
$cfg['NavigationTreeShowViews'] +
+ + +
$cfg['NavigationTreeTableLevel'] +
+ + +
$cfg['NavigationTreeTableSeparator'], [1] +
+ + +
$cfg['NavigationWidth'] +
+ + +
$cfg['NumFavoriteTables'], [1], [2] +
+ + +
$cfg['NumRecentTables'], [1] +
+ + +
$cfg['OBGzip'], [1], [2], [3] +
+ + +
$cfg['Order'] +
+ + +
$cfg['PDFDefaultPageSize'] +
+ + +
$cfg['PDFPageSizes'], [1] +
+ + +
$cfg['PersistentConnections'] +
+ + +
$cfg['PmaAbsoluteUri'], [1], [2], [3], [4], [5], [6], [7] +
+ + +
$cfg['PmaNoRelation_DisableWarning'] +
+ + +
$cfg['PropertiesNumColumns'] +
+ + +
$cfg['ProtectBinary'] +
+ + +
$cfg['ProxyPass'] +
+ + +
$cfg['ProxyUrl'] +
+ + +
$cfg['ProxyUser'] +
+ + +
$cfg['QueryHistoryDB'], [1], [2] +
+ + +
$cfg['QueryHistoryMax'], [1], [2] +
+ + +
$cfg['RecodingEngine'] +
+ + +
$cfg['RelationalDisplay'] +
+ + +
$cfg['RememberSorting'], [1] +
+ + +
$cfg['RepeatCells'] +
+ + +
$cfg['ReservedWordDisableWarning'] +
+ + +
$cfg['RetainQueryBox'] +
+ + +
$cfg['RowActionLinks'] +
+ + +
$cfg['RowActionLinksWithoutUnique'] +
+ + +
$cfg['RowActionType'] +
+ + +
$cfg['SQLQuery']['Edit'] +
+ + +
$cfg['SQLQuery']['Explain'] +
+ + +
$cfg['SQLQuery']['Refresh'] +
+ + +
$cfg['SQLQuery']['ShowAsPHP'] +
+ + +
$cfg['SaveCellsAtOnce'] +
+ + +
$cfg['SaveDir'], [1] +
+ + +
$cfg['SendErrorReports'] +
+ + +
$cfg['ServerDefault'], [1], [2] +
+ + +
$cfg['ServerLibraryDifference_DisableWarning'] +
+ + +
$cfg['Servers'], [1], [2], [3], [4] +
+ + +
$cfg['Servers'][$i]['AllowDeny']['order'], [1], [2] +
+ + +
$cfg['Servers'][$i]['AllowDeny']['rules'], [1], [2], [3], [4], [5] +
+ + +
$cfg['Servers'][$i]['AllowNoPassword'] +
+ + +
$cfg['Servers'][$i]['AllowRoot'], [1] +
+ + +
$cfg['Servers'][$i]['DisableIS'] +
+ + +
$cfg['Servers'][$i]['LogoutURL'] +
+ + +
$cfg['Servers'][$i]['MaxTableUiprefs'] +
+ + +
$cfg['Servers'][$i]['SessionTimeZone'] +
+ + +
$cfg['Servers'][$i]['SignonCookieParams'], [1], [2] +
+ + +
$cfg['Servers'][$i]['SignonScript'], [1], [2], [3], [4], [5] +
+ + +
$cfg['Servers'][$i]['SignonSession'], [1], [2] +
+ + +
$cfg['Servers'][$i]['SignonURL'], [1], [2] +
+ + +
$cfg['Servers'][$i]['auth_http_realm'] +
+ + +
$cfg['Servers'][$i]['auth_type'], [1], [2] +
+ + +
$cfg['Servers'][$i]['bookmarktable'], [1] +
+ + +
$cfg['Servers'][$i]['central_columns'], [1] +
+ + +
$cfg['Servers'][$i]['column_info'], [1], [2] +
+ + +
$cfg['Servers'][$i]['compress'] +
+ + +
$cfg['Servers'][$i]['connect_type'] +
+ + +
$cfg['Servers'][$i]['control_*'], [1], [2], [3] +
+ + +
$cfg['Servers'][$i]['controlhost'], [1] +
+ + +
$cfg['Servers'][$i]['controlpass'], [1], [2] +
+ + +
$cfg['Servers'][$i]['controlport'], [1] +
+ + +
$cfg['Servers'][$i]['controluser'], [1], [2], [3], [4] +
+ + +
$cfg['Servers'][$i]['designer_settings'], [1] +
+ + +
$cfg['Servers'][$i]['export_templates'], [1] +
+ + +
$cfg['Servers'][$i]['extension'] +
+ + +
$cfg['Servers'][$i]['favorite'], [1] +
+ + +
$cfg['Servers'][$i]['hide_db'] +
+ + +
$cfg['Servers'][$i]['history'], [1], [2] +
+ + +
$cfg['Servers'][$i]['host'], [1], [2], [3], [4], [5], [6], [7], [8], [9] +
+ + +
$cfg['Servers'][$i]['navigationhiding'], [1] +
+ + +
$cfg['Servers'][$i]['nopassword'] +
+ + +
$cfg['Servers'][$i]['only_db'] +
+ + +
$cfg['Servers'][$i]['password'], [1] +
+ + +
$cfg['Servers'][$i]['pdf_pages'], [1], [2] +
+ + +
$cfg['Servers'][$i]['pmadb'], [1], [2], [3], [4], [5], [6], [7], [8], [9], [10], [11], [12], [13], [14], [15], [16], [17], [18], [19], [20], [21], [22] +
+ + +
$cfg['Servers'][$i]['port'], [1] +
+ + +
$cfg['Servers'][$i]['recent'], [1] +
+ + +
$cfg['Servers'][$i]['relation'], [1] +
+ + +
$cfg['Servers'][$i]['savedsearches'], [1] +
+ + +
$cfg['Servers'][$i]['socket'], [1], [2] +
+ + +
$cfg['Servers'][$i]['ssl'], [1], [2], [3], [4], [5], [6], [7], [8], [9], [10] +
+ + +
$cfg['Servers'][$i]['ssl_ca'], [1], [2], [3], [4], [5], [6], [7], [8], [9], [10] +
+ + +
$cfg['Servers'][$i]['ssl_ca_path'], [1], [2], [3], [4], [5], [6], [7], [8], [9] +
+ + +
$cfg['Servers'][$i]['ssl_cert'], [1], [2], [3], [4], [5], [6], [7], [8], [9], [10] +
+ + +
$cfg['Servers'][$i]['ssl_ciphers'], [1], [2], [3], [4], [5], [6], [7], [8] +
+ + +
$cfg['Servers'][$i]['ssl_key'], [1], [2], [3], [4], [5], [6], [7], [8], [9], [10] +
+ + +
$cfg['Servers'][$i]['ssl_verify'], [1], [2], [3], [4], [5], [6], [7], [8], [9], [10], [11] +
+ + +
$cfg['Servers'][$i]['table_coords'], [1], [2] +
+ + +
$cfg['Servers'][$i]['table_info'], [1] +
+ + +
$cfg['Servers'][$i]['table_uiprefs'], [1], [2], [3], [4] +
+ + +
$cfg['Servers'][$i]['tracking'], [1] +
+ + +
$cfg['Servers'][$i]['tracking_add_drop_database'] +
+ + +
$cfg['Servers'][$i]['tracking_add_drop_table'] +
+ + +
$cfg['Servers'][$i]['tracking_add_drop_view'] +
+ + +
$cfg['Servers'][$i]['tracking_default_statements'] +
+ + +
$cfg['Servers'][$i]['tracking_version_auto_create'] +
+ + +
$cfg['Servers'][$i]['user'], [1] +
+ + +
$cfg['Servers'][$i]['userconfig'], [1] +
+ + +
$cfg['Servers'][$i]['usergroups'], [1], [2], [3] +
+ + +
$cfg['Servers'][$i]['users'], [1] +
+ + +
$cfg['Servers'][$i]['verbose'], [1], [2], [3], [4] +
+ + +
$cfg['SessionSavePath'], [1] +
+ + +
$cfg['ShowAll'] +
+ + +
$cfg['ShowBrowseComments'] +
+ + +
$cfg['ShowChgPassword'] +
+ + +
$cfg['ShowColumnComments'] +
+ + +
$cfg['ShowCreateDb'] +
+ + +
$cfg['ShowDatabasesNavigationAsTree'] +
+ + +
$cfg['ShowDbStructureCreation'] +
+ + +
$cfg['ShowDbStructureLastCheck'] +
+ + +
$cfg['ShowDbStructureLastUpdate'] +
+ + +
$cfg['ShowFieldTypesInDataEditView'] +
+ + +
$cfg['ShowFunctionFields'] +
+ + +
$cfg['ShowGitRevision'] +
+ + +
$cfg['ShowHint'] +
+ + +
$cfg['ShowPhpInfo'] +
+ + +
$cfg['ShowPropertyComments'] +
+ + +
$cfg['ShowSQL'] +
+ + +
$cfg['ShowServerInfo'] +
+ + +
$cfg['ShowStats'] +
+ + +
$cfg['SkipLockedTables'] +
+ + +
$cfg['SuhosinDisableWarning'], [1] +
+ + +
$cfg['TableNavigationLinksMode'] +
+ + +
$cfg['TablePrimaryKeyOrder'] +
+ + +
$cfg['TabsMode'] +
+ + +
$cfg['TempDir'], [1], [2], [3], [4] +
+ + +
$cfg['TextareaAutoSelect'] +
+ + +
$cfg['TextareaCols'] +
+ + +
$cfg['TextareaRows'] +
+ + +
$cfg['ThemeDefault'], [1] +
+ + +
$cfg['ThemeManager'], [1], [2] +
+ + +
$cfg['ThemePerServer'] +
+ + +
$cfg['TitleDatabase'] +
+ + +
$cfg['TitleDefault'] +
+ + +
$cfg['TitleServer'] +
+ + +
$cfg['TitleTable'] +
+ + +
$cfg['TranslationWarningThreshold'] +
+ + +
$cfg['TrustedProxies'], [1] +
+ + +
$cfg['UploadDir'], [1], [2], [3] +
+ + +
$cfg['UseDbSearch'] +
+ + +
$cfg['UserprefsDeveloperTab'] +
+ + +
$cfg['UserprefsDisallow'], [1] +
+ + +
$cfg['VersionCheck'] +
+ + +
$cfg['ZeroConf'], [1] +
+ + +
$cfg['ZipDump'] +
+ + +
$cfg['blowfish_secret'], [1] +
+ +
+
+ +
Configuration storage +
+ + +
Confirm +
+ + +
connect_type +
+ + +
Console, AlwaysExpand +
+ + +
Console, CurrentQuery +
+ + +
Console, DarkTheme +
+ + +
Console, EnterExecutes +
+ + +
Console, Height +
+ + +
Console, Mode +
+ + +
Console, StartHistory +
+ + +
ConsoleEnterExecutes +
+ + +
control_* +
+ + +
controlhost +
+ + +
controlpass +
+ + +
controlport +
+ + +
controluser +
+ + +
Cookie +
+ +
+ +
Authentication mode +
+ +
+ +
CSPAllow +
+ + +
CSV +
+ +
+ +

D

+ + + +
+ +
data (global variable or constant) +
+ + +
database +
+ +
+ +
(global variable or constant) +
+ +
+ +
DB +
+ + +
DBG +
+ + +
DBG, demo +
+ + +
DBG, simple2fa +
+ + +
DBG, sql +
+ + +
DBG, sqllog +
+ + +
DefaultConnectionCollation +
+ + +
DefaultForeignKeyChecks +
+ + +
DefaultFunctions +
+ + +
DefaultLang +
+ + +
DefaultQueryDatabase +
+ + +
DefaultQueryTable +
+ + +
DefaultTabDatabase +
+ +
+ +
DefaultTabServer +
+ + +
DefaultTabTable +
+ + +
DefaultTransformations +
+ + +
DefaultTransformations, Bool2Text +
+ + +
DefaultTransformations, DateFormat +
+ + +
DefaultTransformations, External +
+ + +
DefaultTransformations, Hex +
+ + +
DefaultTransformations, Inline +
+ + +
DefaultTransformations, PreApPend +
+ + +
DefaultTransformations, Substring +
+ + +
DefaultTransformations, TextImageLink +
+ + +
DefaultTransformations, TextLink +
+ + +
designer_settings +
+ + +
DisableIS +
+ + +
DisableMultiTableMaintenance +
+ + +
DisableShortcutKeys +
+ + +
DisplayServersList +
+ +
+ +

E

+ + + +
+ +
EnableAutocompleteForTablesAndColumns +
+ + +
Engine +
+ + +
+ environment variable +
+ +
+ +
PMA_ABSOLUTE_URI, [1] +
+ + +
PMA_ARBITRARY +
+ + +
PMA_HOST, [1] +
+ + +
PMA_HOSTS +
+ + +
PMA_PASSWORD, [1] +
+ + +
PMA_PORT, [1] +
+ + +
PMA_PORTS +
+ + +
PMA_USER, [1] +
+ + +
PMA_VERBOSE, [1] +
+ + +
PMA_VERBOSES +
+ +
+
+ +
ExecTimeLimit +
+ + +
Export +
+ + +
Export, charset +
+ + +
Export, file_template_database +
+ + +
Export, file_template_server +
+ + +
Export, file_template_table +
+ + +
Export, format +
+ + +
Export, method +
+ + +
export_templates +
+ + +
extension, [1] +
+ +
+ +

F

+ + + +
+ +
FAQ +
+ + +
favorite +
+ + +
Field +
+ + +
FilterLanguages +
+ + +
FirstLevelNavigationItems +
+ +
+ +
FontSize +
+ + +
ForceSSL +
+ + +
foreign key +
+ + +
ForeignKeyDropdownOrder +
+ + +
ForeignKeyMaxLimit +
+ +
+ +

G

+ + + +
+ +
GD +
+ + +
GD2 +
+ + +
GD2Available +
+ +
+ +
GridEditing +
+ + +
gzip +
+ + +
GZipDump +
+ +
+ +

H

+ + + +
+ +
hide_db +
+ + +
HideStructureActions +
+ + +
history +
+ + +
host, [1] +
+ +
+ +
hostname +
+ + +
HTTP +
+ +
+ +
Authentication mode +
+ +
+ +
https +
+ +
+ +

I

+ + + +
+ +
IconvExtraParams +
+ + +
IEC +
+ + +
IgnoreMultiSubmitErrors +
+ + +
IIS +
+ + +
Import +
+ + +
Import, charset +
+ + +
Index +
+ +
+ +
InitialSlidersState +
+ + +
InsertRows +
+ + +
IP +
+ + +
IP Address +
+ + +
IPv6 +
+ + +
ISAPI +
+ + +
ISO +
+ + +
ISP +
+ +
+ +

J

+ + + +
+ +
JPEG +
+ +
+ +
JPG +
+ +
+ +

K

+ + +
+ +
Key +
+ +
+ +

L

+ + + +
+ +
Lang +
+ + +
LATEX +
+ + +
LimitChars +
+ + +
LinkLengthLimit +
+ + +
LoginCookieDeleteAll +
+ +
+ +
LoginCookieRecall +
+ + +
LoginCookieStore +
+ + +
LoginCookieValidity +
+ + +
LoginCookieValidityDisableWarning +
+ + +
LogoutURL +
+ + +
LongtextDoubleTextarea +
+ +
+ +

M

+ + + +
+ +
Mac +
+ + +
Mac OS X +
+ + +
MaxCharactersInDisplayedSQL +
+ + +
MaxDbList +
+ + +
MaxExactCount +
+ + +
MaxExactCountViews +
+ + +
MaxNavigationItems +
+ + +
MaxRows +
+ + +
MaxSizeForInputField +
+ + +
MaxTableList +
+ + +
MaxTableUiprefs +
+ +
+ +
mbstring +
+ + +
MCrypt +
+ + +
mcrypt +
+ + +
MemoryLimit +
+ + +
MIME +
+ + +
MinSizeForInputField +
+ + +
mod_proxy_fcgi +
+ + +
module +
+ + +
MySQL +
+ + +
mysql +
+ + +
mysqli +
+ + +
MysqlMinVersion +
+ +
+ +

N

+ + + +
+ +
name (global variable or constant) +
+ + +
NaturalOrder +
+ + +
NavigationDisplayLogo +
+ + +
NavigationDisplayServers +
+ + +
navigationhiding +
+ + +
NavigationLinkWithMainPanel +
+ + +
NavigationLogoLink +
+ + +
NavigationLogoLinkWindow +
+ + +
NavigationTreeDbSeparator +
+ + +
NavigationTreeDefaultTabTable +
+ + +
NavigationTreeDefaultTabTable2 +
+ + +
NavigationTreeDisplayDbFilterMinimum +
+ + +
NavigationTreeDisplayItemFilterMinimum +
+ +
+ +
NavigationTreeEnableExpansion +
+ + +
NavigationTreeEnableGrouping +
+ + +
NavigationTreePointerEnable +
+ + +
NavigationTreeShowEvents +
+ + +
NavigationTreeShowFunctions +
+ + +
NavigationTreeShowProcedures +
+ + +
NavigationTreeShowTables +
+ + +
NavigationTreeShowViews +
+ + +
NavigationTreeTableLevel +
+ + +
NavigationTreeTableSeparator +
+ + +
NavigationWidth +
+ + +
nopassword +
+ + +
NumFavoriteTables +
+ + +
NumRecentTables +
+ +
+ +

O

+ + + +
+ +
OBGzip +
+ + +
only_db +
+ +
+ +
OpenDocument +
+ + +
Order +
+ + +
OS X +
+ +
+ +

P

+ + + +
+ +
password +
+ + +
PCRE +
+ + +
PDF +
+ + +
pdf_pages +
+ + +
PDFDefaultPageSize +
+ + +
PDFPageSizes +
+ + +
PEAR +
+ + +
PersistentConnections +
+ + +
PHP +
+ + +
phpMyAdmin configuration storage +
+ + +
PMA_ABSOLUTE_URI +
+ + +
PMA_HOST +
+ + +
PMA_PASSWORD +
+ +
+ +
PMA_PORT +
+ + +
PMA_USER +
+ + +
PMA_VERBOSE +
+ + +
PmaAbsoluteUri +
+ + +
pmadb, [1] +
+ + +
PmaNoRelation_DisableWarning +
+ + +
port, [1] +
+ + +
primary key +
+ + +
PropertiesNumColumns +
+ + +
ProtectBinary +
+ + +
ProxyPass +
+ + +
ProxyUrl +
+ + +
ProxyUser +
+ +
+ +

Q

+ + + +
+ +
QueryHistoryDB +
+ +
+ +
QueryHistoryMax +
+ +
+ +

R

+ + + +
+ +
recent +
+ + +
RecodingEngine +
+ + +
relation +
+ + +
RelationalDisplay +
+ + +
RememberSorting +
+ + +
RepeatCells +
+ + +
ReservedWordDisableWarning +
+ + +
RetainQueryBox +
+ +
+ +
RFC +
+ +
+ +
RFC 1867 +
+ + +
RFC 1952 +
+ + +
RFC 2616 +
+ +
+ +
RFC 1952 +
+ + +
Row (record, tuple) +
+ + +
RowActionLinks +
+ + +
RowActionLinksWithoutUnique +
+ + +
RowActionType +
+ +
+ +

S

+ + + +
+ +
SaveCellsAtOnce +
+ + +
SaveDir +
+ + +
savedsearches +
+ + +
SendErrorReports +
+ + +
Server +
+ + +
+ server configuration +
+ +
+ +
AllowDeny, order +
+ + +
AllowDeny, rules +
+ + +
AllowNoPassword +
+ + +
AllowRoot +
+ + +
DisableIS +
+ + +
LogoutURL +
+ + +
MaxTableUiprefs +
+ + +
SessionTimeZone +
+ + +
SignonCookieParams +
+ + +
SignonScript +
+ + +
SignonSession +
+ + +
SignonURL +
+ + +
auth_http_realm +
+ + +
auth_type +
+ + +
bookmarktable +
+ + +
central_columns +
+ + +
column_info +
+ + +
compress +
+ + +
connect_type +
+ + +
control_* +
+ + +
controlhost +
+ + +
controlpass +
+ + +
controlport +
+ + +
controluser +
+ + +
designer_settings +
+ + +
export_templates +
+ + +
extension +
+ + +
favorite +
+ + +
hide_db +
+ + +
history +
+ + +
host +
+ + +
navigationhiding +
+ + +
nopassword +
+ + +
only_db +
+ + +
password +
+ + +
pdf_pages +
+ + +
pmadb +
+ + +
port +
+ + +
recent +
+ + +
relation +
+ + +
savedsearches +
+ + +
socket +
+ + +
ssl +
+ + +
ssl_ca +
+ + +
ssl_ca_path +
+ + +
ssl_cert +
+ + +
ssl_ciphers +
+ + +
ssl_key +
+ + +
ssl_verify +
+ + +
table_coords +
+ + +
table_info +
+ + +
table_uiprefs +
+ + +
tracking +
+ + +
tracking_add_drop_database +
+ + +
tracking_add_drop_table +
+ + +
tracking_add_drop_view +
+ + +
tracking_default_statements +
+ + +
tracking_version_auto_create +
+ + +
user +
+ + +
userconfig +
+ + +
usergroups +
+ + +
users +
+ + +
verbose +
+ +
+
+ +
ServerDefault +
+ + +
ServerLibraryDifference_DisableWarning +
+ + +
Servers +
+ + +
SessionSavePath +
+ + +
SessionTimeZone +
+ + +
Setup script +
+ + +
ShowAll +
+ + +
ShowBrowseComments +
+ + +
ShowChgPassword +
+ + +
ShowColumnComments +
+ + +
ShowCreateDb +
+ + +
ShowDatabasesNavigationAsTree +
+ + +
ShowDbStructureCreation +
+ + +
ShowDbStructureLastCheck +
+ + +
ShowDbStructureLastUpdate +
+ + +
ShowFieldTypesInDataEditView +
+ + +
ShowFunctionFields +
+ + +
ShowGitRevision +
+ + +
ShowHint +
+ + +
ShowPhpInfo +
+ + +
ShowPropertyComments +
+ + +
ShowServerInfo +
+ + +
ShowSQL +
+ + +
ShowStats +
+ + +
+ Signon +
+ +
+ +
Authentication mode +
+ +
+ +
SignonCookieParams +
+ + +
SignonScript +
+ + +
SignonSession +
+ + +
SignonURL +
+ + +
SkipLockedTables +
+ + +
socket, [1] +
+ + +
SQL +
+ + +
SQLQuery, Edit +
+ + +
SQLQuery, Explain +
+ + +
SQLQuery, Refresh +
+ + +
SQLQuery, ShowAsPHP +
+ + +
SSL +
+ + +
ssl +
+ + +
ssl_ca +
+ + +
ssl_ca_path +
+ + +
ssl_cert +
+ + +
ssl_ciphers +
+ + +
ssl_key +
+ + +
ssl_verify +
+ + +
Storage Engines +
+ + +
Stored procedure +
+ + +
SuhosinDisableWarning +
+ +
+ +

T

+ + + +
+ +
table +
+ + +
table_coords +
+ + +
table_info +
+ + +
table_uiprefs +
+ + +
TableNavigationLinksMode +
+ + +
TablePrimaryKeyOrder +
+ + +
TabsMode +
+ + +
tar +
+ + +
TCP +
+ + +
TCPDF +
+ + +
TempDir +
+ + +
TextareaAutoSelect +
+ + +
TextareaCols +
+ + +
TextareaRows +
+ + +
ThemeDefault +
+ +
+ +
ThemeManager +
+ + +
ThemePerServer +
+ + +
TitleDatabase +
+ + +
TitleDefault +
+ + +
TitleServer +
+ + +
TitleTable +
+ + +
tracking +
+ + +
tracking_add_drop_database +
+ + +
tracking_add_drop_table +
+ + +
tracking_add_drop_view +
+ + +
tracking_default_statements +
+ + +
tracking_version_auto_create +
+ + +
TranslationWarningThreshold +
+ + +
trigger +
+ + +
TrustedProxies +
+ + +
type (global variable or constant) +
+ +
+ +

U

+ + + +
+ +
unique key +
+ + +
UploadDir +
+ + +
URL +
+ + +
UseDbSearch +
+ + +
user +
+ +
+ +
userconfig +
+ + +
usergroups +
+ + +
UserprefsDeveloperTab +
+ + +
UserprefsDisallow +
+ + +
users +
+ +
+ +

V

+ + + +
+ +
verbose +
+ +
+ +
version (global variable or constant) +
+ + +
VersionCheck +
+ +
+ +

W

+ + +
+ +
Webserver +
+ +
+ +

X

+ + +
+ +
XML +
+ +
+ +

Z

+ + + +
+ +
ZeroConf +
+ + +
ZIP +
+ +
+ +
ZipDump +
+ + +
zlib +
+ +
+ + + +
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/php/apps/phpmyadmin49/doc/html/glossary.html b/php/apps/phpmyadmin49/doc/html/glossary.html new file mode 100644 index 00000000..ac7b50a5 --- /dev/null +++ b/php/apps/phpmyadmin49/doc/html/glossary.html @@ -0,0 +1,638 @@ + + + + + + + + Glossary — phpMyAdmin 4.9.1 documentation + + + + + + + + + + + + + + + + + +
+
+
+
+ +
+

Glossary

+

From Wikipedia, the free encyclopedia

+
+
.htaccess
+

the default name of Apache’s directory-level configuration file.

+ +
+
ACL
+
Access Contol List
+
Blowfish
+

a keyed, symmetric block cipher, designed in 1993 by Bruce Schneier.

+ +
+
Browser
+

a software application that enables a user to display and interact with text, images, and other information typically located on a web page at a website on the World Wide Web.

+ +
+
bzip2
+

a free software/open source data compression algorithm and program developed by Julian Seward.

+ +
+
CGI
+

Common Gateway Interface is an important World Wide Web technology that +enables a client web browser to request data from a program executed on +the Web server.

+ +
+
Changelog
+

a log or record of changes made to a project.

+ +
+
Client
+

a computer system that accesses a (remote) service on another computer by some kind of network.

+ +
+
column
+

a set of data values of a particular simple type, one for each row of the table.

+ +
+ +

a packet of information sent by a server to a World Wide Web browser and then sent back by the browser each time it accesses that server.

+ +
+
CSV
+

Comma- separated values

+ +
+
DB
+
look at database
+
database
+

an organized collection of data.

+ +
+
Engine
+
look at storage engines
+
extension
+

a PHP module that extends PHP with additional functionality.

+ +
+
FAQ
+

Frequently Asked Questions is a list of commonly asked question and there +answers.

+ +
+
Field
+

one part of divided data/columns.

+ +
+
foreign key
+

a column or group of columns in a database row that point to a key column +or group of columns forming a key of another database row in some +(usually different) table.

+ +
+
GD
+

Graphics Library by Thomas Boutell and others for dynamically manipulating images.

+ +
+
GD2
+
look at gd
+
gzip
+

gzip is short for GNU zip, a GNU free software file compression program.

+ +
+
host
+

any machine connected to a computer network, a node that has a hostname.

+ +
+
hostname
+

the unique name by which a network attached device is known on a network.

+ +
+
HTTP
+

HyperText Transfer Protocol is the primary method used to transfer or +convey information on the World Wide Web.

+ +
+
https
+

a HTTP-connection with additional security measures.

+ +
+
IEC
+
International Electrotechnical Commission
+
IIS
+

Internet Information Services is a set of Internet-based services for +servers using Microsoft Windows.

+ +
+
Index
+

a feature that allows quick access to the rows in a table.

+ +
+
IP
+

Internet Protocol is a data-oriented protocol used by source and +destination hosts for communicating data across a packet-switched +internetwork.

+ +
+
IP Address
+

a unique number that devices use in order to identify and communicate with each other on a network utilizing the Internet Protocol standard.

+ +
+
IPv6
+

IPv6 (Internet Protocol version 6) is the latest revision of the +Internet Protocol (IP), designed to deal with the +long-anticipated problem of its precedessor IPv4 running out of addresses.

+ +
+
ISAPI
+

Internet Server Application Programming Interface is the API of Internet Information Services (IIS).

+ +
+
ISP
+

Internet service provider is a business or organization that offers users +access to the Internet and related services.

+ +
+
ISO
+
International Standards Organisation
+
JPEG
+

a most commonly used standard method of lossy compression for photographic images.

+ +
+
JPG
+
look at jpeg
+
Key
+
look at index
+
LATEX
+

a document preparation system for the TEX typesetting program.

+ +
+
Mac
+

Apple Macintosh is line of personal computers is designed, developed, manufactured, and marketed by Apple Computer.

+

. seealso:: <https://en.wikipedia.org/wiki/Mac>

+
+
Mac OS X
+

the operating system which is included with all currently shipping Apple Macintosh computers in the consumer and professional markets.

+ +
+
mbstring
+

The PHP mbstring functions provide support for languages represented by multi-byte character sets, most notably UTF-8.

+

If you have troubles installing this extension, please follow 1.20 I receive an error about missing mysqli and mysql extensions., it provides useful hints.

+ +
+
MCrypt
+

a cryptographic library.

+ +
+
mcrypt
+

the MCrypt PHP extension.

+ +
+
MIME
+

Multipurpose Internet Mail Extensions is +an Internet Standard for the format of e-mail.

+ +
+
module
+

some sort of extension for the Apache Webserver.

+ +
+
mod_proxy_fcgi
+
an Apache module implmenting a Fast CGI interface; PHP can be run as a CGI module, FastCGI, or +directly as an Apache module.
+
MySQL
+

a multithreaded, multi-user, SQL (Structured Query Language) Database Management System (DBMS).

+ +
+
mysqli
+

the improved MySQL client PHP extension.

+ +
+
mysql
+

the MySQL client PHP extension.

+ +
+
OpenDocument
+

open standard for office documents.

+ +
+
OS X
+

look at Mac OS X.

+ +
+
PDF
+

Portable Document Format is a file format developed by Adobe Systems for +representing two dimensional documents in a device independent and +resolution independent format.

+ +
+
PEAR
+

the PHP Extension and Application Repository.

+
+

See also

+

<https://pear.php.net/>

+
+
+
PCRE
+

Perl Compatible Regular Expressions is the perl-compatible regular +expression functions for PHP

+ +
+
PHP
+

short for “PHP: Hypertext Preprocessor”, is an open-source, reflective +programming language used mainly for developing server-side applications +and dynamic web content, and more recently, a broader range of software +applications.

+ +
+
port
+

a connection through which data is sent and received.

+ +
+
primary key
+

A primary key is an index over one or more fields in a table with +unique values for each single row in this table. Every table should have +a primary key for easier accessing/identifying data in this table. There +can only be one primary key per table and it is named always PRIMARY. +In fact a primary key is just an unique key with the name +PRIMARY. If no primary key is defined MySQL will use first unique +key as primary key if there is one.

+

You can create the primary key when creating the table (in phpMyAdmin +just check the primary key radio buttons for each field you wish to be +part of the primary key).

+

You can also add a primary key to an existing table with ALTER TABLE +or CREATE INDEX (in phpMyAdmin you can just click on ‘add index’ on +the table structure page below the listed fields).

+
+
RFC
+

Request for Comments (RFC) documents are a series of memoranda +encompassing new research, innovations, and methodologies applicable to +Internet technologies.

+ +
+
RFC 1952
+

GZIP file format specification version 4.3

+
+

See also

+

RFC 1952

+
+
+
Row (record, tuple)
+

represents a single, implicitly structured data item in a table.

+ +
+
Server
+

a computer system that provides services to other computing systems over a network.

+ +
+
Storage Engines
+

MySQL can use several different formats for storing data on disk, these +are called storage engines or table types. phpMyAdmin allows a user to +change their storage engine for a particular table through the operations +tab.

+

Common table types are InnoDB and MyISAM, though many others exist and +may be desirable in some situations.

+ +
+
socket
+

a form of inter-process communication.

+ +
+
SSL
+

Secure Sockets Layer is a cryptographic protocol which provides secure +communication on the Internet.

+ +
+
Stored procedure
+

a subroutine available to applications accessing a relational database system

+ +
+
SQL
+

Structured Query Language

+ +
+
table
+

a set of data elements (cells) that is organized, defined and stored as +horizontal rows and vertical columns where each item can be uniquely +identified by a label or key or by it?s position in relation to other +items.

+ +
+
tar
+

a type of archive file format: the Tape ARchive format.

+ +
+
TCP
+

Transmission Control Protocol is one of the core protocols of the +Internet protocol suite.

+ +
+
TCPDF
+

PHP library to generate PDF files.

+
+

See also

+

<https://tcpdf.org/>

+
+
+
trigger
+

a procedural code that is automatically executed in response to certain events on a particular table or view in a database

+ +
+
unique key
+
An unique key is an index over one or more fields in a table which has a +unique value for each row. The first unique key will be treated as +primary key if there is no primary key defined.
+
URL
+

Uniform Resource Locator is a sequence of characters, conforming to a +standardized format, that is used for referring to resources, such as +documents and images on the Internet, by their location.

+ +
+
Webserver
+

A computer (program) that is responsible for accepting HTTP requests from clients and serving them Web pages.

+ +
+
XML
+

Extensible Markup Language is a W3C-recommended general- purpose markup +language for creating special-purpose markup languages, capable of +describing many different kinds of data.

+ +
+
ZIP
+

a popular data compression and archival format.

+ +
+
zlib
+

an open-source, cross- platform data compression library by Jean-loup Gailly and Mark Adler.

+ +
+
+
+ + +
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/php/apps/phpmyadmin49/doc/html/import_export.html b/php/apps/phpmyadmin49/doc/html/import_export.html new file mode 100644 index 00000000..d02be8b4 --- /dev/null +++ b/php/apps/phpmyadmin49/doc/html/import_export.html @@ -0,0 +1,470 @@ + + + + + + + + Import and export — phpMyAdmin 4.9.1 documentation + + + + + + + + + + + + + + + + + + + +
+
+
+
+ +
+

Import and export

+
+

Import

+

To import data, go to the “Import” tab in phpMyAdmin. To import data into a +specific database or table, open the database or table before going to the +“Import” tab.

+

In addition to the standard Import and Export tab, you can also import an SQL +file directly by dragging and dropping it from your local file manager to the +phpMyAdmin interface in your web browser.

+

If you are having troubles importing big files, please consult 1.16 I cannot upload big dump files (memory, HTTP or timeout problems)..

+

You can import using following methods:

+

Form based upload

+
+
Can be used with any supported format, also (b|g)zipped files, e.g., mydump.sql.gz .
+

Form based SQL Query

+
+
Can be used with valid SQL dumps.
+

Using upload directory

+
+
You can specify an upload directory on your web server where phpMyAdmin is installed, after uploading your file into this directory you can select this file in the import dialog of phpMyAdmin, see $cfg['UploadDir'].
+

phpMyAdmin can import from several various commonly used formats.

+
+

CSV

+

Comma separated values format which is often used by spreadsheets or various other programs for export/import.

+
+

Note

+

When importing data into a table from a CSV file where the table has an +‘auto_increment’ field, make the ‘auto_increment’ value for each record in +the CSV field to be ‘0’ (zero). This allows the ‘auto_increment’ field to +populate correctly.

+
+

It is now possible to import a CSV file at the server or database level. +Instead of having to create a table to import the CSV file into, a best-fit +structure will be determined for you and the data imported into it, instead. +All other features, requirements, and limitations are as before.

+
+
+

CSV using LOAD DATA

+

Similar to CSV, only using the internal MySQL parser and not the phpMyAdmin one.

+
+
+

ESRI Shape File

+

The ESRI shapefile or simply a shapefile is a popular geospatial vector data +format for geographic information systems software. It is developed and +regulated by Esri as a (mostly) open specification for data interoperability +among Esri and other software products.

+
+
+

MediaWiki

+

MediaWiki files, which can be exported by phpMyAdmin (version 4.0 or later), +can now also be imported. This is the format used by Wikipedia to display +tables.

+
+
+

Open Document Spreadsheet (ODS)

+

OpenDocument workbooks containing one or more spreadsheets can now be directly imported.

+

When importing an ODS speadsheet, the spreadsheet must be named in a specific way in order to make the +import as simple as possible.

+
+

Table name

+

During import, phpMyAdmin uses the sheet name as the table name; you should rename the +sheet in your spreadsheet program in order to match your existing table name (or the table you wish to create, +though this is less of a concern since you could quickly rename the new table from the Operations tab).

+
+
+

Column names

+

You should also make the first row of your spreadsheet a header with the names of the columns (this can be +accomplished by inserting a new row at the top of your spreadsheet). When on the Import screen, select the +checkbox for “The first line of the file contains the table column names;” this way your newly imported +data will go to the proper columns.

+
+

Note

+

Formulas and calculations will NOT be evaluated, rather, their value from +the most recent save will be loaded. Please ensure that all values in the +spreadsheet are as needed before importing it.

+
+
+
+
+

SQL

+

SQL can be used to make any manipulation on data, it is also useful for restoring backed up data.

+
+
+

XML

+

XML files exported by phpMyAdmin (version 3.3.0 or later) can now be imported. +Structures (databases, tables, views, triggers, etc.) and/or data will be +created depending on the contents of the file.

+

The supported xml schemas are not yet documented in this wiki.

+
+
+
+

Export

+

phpMyAdmin can export into text files (even compressed) on your local disk (or +a special the webserver $cfg['SaveDir'] folder) in various +commonly used formats:

+
+

CodeGen

+

NHibernate file format. Planned +versions: Java, Hibernate, PHP PDO, JSON, etc. So the preliminary name is +codegen.

+
+
+

CSV

+

Comma separated values format which is often used by spreadsheets or various +other programs for export/import.

+
+
+

CSV for Microsoft Excel

+

This is just preconfigured version of CSV export which can be imported into +most English versions of Microsoft Excel. Some localised versions (like +“Danish”) are expecting ”;” instead of ”,” as field separator.

+
+
+

Microsoft Word 2000

+

If you’re using Microsoft Word 2000 or newer (or compatible such as +OpenOffice.org), you can use this export.

+
+
+

JSON

+

JSON (JavaScript Object Notation) is a lightweight data-interchange format. It +is easy for humans to read and write and it is easy for machines to parse and +generate.

+
+

Changed in version 4.7.0: The generated JSON structure has been changed in phpMyAdmin 4.7.0 to +produce valid JSON data.

+
+

The generated JSON is list of objects with following attributes:

+
+
+type
+

Type of given object, can be one of:

+
+
header
+
Export header containing comment and phpMyAdmin version.
+
database
+
Start of a database marker, containing name of database.
+
table
+
Table data export.
+
+
+ +
+
+version
+

Used in header type and indicates phpMyAdmin version.

+
+ +
+
+comment
+

Optional textual comment.

+
+ +
+
+name
+

Object name - either table or database based on type.

+
+ +
+
+database
+

Database name for table type.

+
+ +
+
+data
+

Table content for table type.

+
+ +

Sample output:

+
[
+    {
+        "comment": "Export to JSON plugin for PHPMyAdmin",
+        "type": "header",
+        "version": "4.7.0-dev"
+    },
+    {
+        "name": "cars",
+        "type": "database"
+    },
+    {
+        "data": [
+            {
+                "car_id": "1",
+                "description": "Green Chrysler 300",
+                "make_id": "5",
+                "mileage": "113688",
+                "price": "13545.00",
+                "transmission": "automatic",
+                "yearmade": "2007"
+            }
+        ],
+        "database": "cars",
+        "name": "cars",
+        "type": "table"
+    },
+    {
+        "data": [
+            {
+                "make": "Chrysler",
+                "make_id": "5"
+            }
+        ],
+        "database": "cars",
+        "name": "makes",
+        "type": "table"
+    }
+]
+
+
+
+
+

LaTeX

+

If you want to embed table data or structure in LaTeX, this is right choice for you.

+

LaTeX is a typesetting system that is very suitable for producing scientific +and mathematical documents of high typographical quality. It is also suitable +for producing all sorts of other documents, from simple letters to complete +books. LaTeX uses TeX as its formatting engine. Learn more about TeX and +LaTeX on the Comprehensive TeX Archive Network +also see the short description od TeX.

+

The output needs to be embedded into a LaTeX document before it can be +rendered, for example in following document:

+
\documentclass{article}
+\title{phpMyAdmin SQL output}
+\author{}
+\usepackage{longtable,lscape}
+\date{}
+\setlength{\parindent}{0pt}
+\usepackage[left=2cm,top=2cm,right=2cm,nohead,nofoot]{geometry}
+\pdfpagewidth 210mm
+\pdfpageheight 297mm
+\begin{document}
+\maketitle
+
+% insert phpMyAdmin LaTeX Dump here
+
+\end{document}
+
+
+
+
+

MediaWiki

+

Both tables and databases can be exported in the MediaWiki format, which is +used by Wikipedia to display tables. It can export structure, data or both, +including table names or headers.

+
+
+

OpenDocument Spreadsheet

+

Open standard for spreadsheet data, which is being widely adopted. Many recent +spreadsheet programs, such as LibreOffice, OpenOffice, Microsoft Office or +Google Docs can handle this format.

+
+
+

OpenDocument Text

+

New standard for text data which is being widely addopted. Most recent word +processors (such as LibreOffice, OpenOffice, Microsoft Word, AbiWord or KWord) +can handle this.

+
+
+

PDF

+

For presentation purposes, non editable PDF might be best choice for you.

+
+
+

PHP Array

+

You can generate a php file which will declare a multidimensional array with +the contents of the selected table or database.

+
+
+

SQL

+

Export in SQL can be used to restore your database, thus it is useful for +backing up.

+

The option ‘Maximal length of created query’ seems to be undocumented. But +experiments has shown that it splits large extended INSERTS so each one is no +bigger than the given number of bytes (or characters?). Thus when importing the +file, for large tables you avoid the error “Got a packet bigger than +‘max_allowed_packet’ bytes”.

+ +
+

Data Options

+

Complete inserts adds the column names to the SQL dump. This parameter +improves the readability and reliability of the dump. Adding the column names +increases the size of the dump, but when combined with Extended inserts it’s +negligible.

+

Extended inserts combines multiple rows of data into a single INSERT query. +This will significantly decrease filesize for large SQL dumps, increases the +INSERT speed when imported, and is generally recommended.

+ +
+
+
+

Texy!

+

Texy! markup format. You can see example on Texy! demo.

+
+
+

XML

+

Easily parsable export for use with custom scripts.

+
+

Changed in version 3.3.0: The XML schema used has changed as of version 3.3.0

+
+
+
+

YAML

+

YAML is a data serialization format which is both human readable and +computationally powerful ( <https://yaml.org> ).

+
+
+
+ + +
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/php/apps/phpmyadmin49/doc/html/index.html b/php/apps/phpmyadmin49/doc/html/index.html new file mode 100644 index 00000000..bcc94084 --- /dev/null +++ b/php/apps/phpmyadmin49/doc/html/index.html @@ -0,0 +1,228 @@ + + + + + + + + Welcome to phpMyAdmin’s documentation! — phpMyAdmin 4.9.1 documentation + + + + + + + + + + + + + + + + + +
+
+
+
+ +
+

Welcome to phpMyAdmin’s documentation!

+

Contents:

+
+ +
+
+
+

Indices and tables

+ +
+ + +
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/php/apps/phpmyadmin49/doc/html/intro.html b/php/apps/phpmyadmin49/doc/html/intro.html new file mode 100644 index 00000000..5ef2faba --- /dev/null +++ b/php/apps/phpmyadmin49/doc/html/intro.html @@ -0,0 +1,195 @@ + + + + + + + + Introduction — phpMyAdmin 4.9.1 documentation + + + + + + + + + + + + + + + + + + +
+
+
+
+ +
+

Introduction

+

phpMyAdmin is a free software tool written in PHP that is intended to handle the +administration of a MySQL or MariaDB database server. You can use phpMyAdmin to +perform most administration tasks, including creating a database, running queries, +and adding user accounts.

+
+

Supported features

+

Currently phpMyAdmin can:

+
    +
  • create, browse, edit, and drop databases, tables, views, columns, and indexes
  • +
  • display multiple results sets through stored procedures or queries
  • +
  • create, copy, drop, rename and alter databases, tables, columns and +indexes
  • +
  • maintenance server, databases and tables, with proposals on server +configuration
  • +
  • execute, edit and bookmark any SQL-statement, even batch-queries
  • +
  • load text files into tables
  • +
  • create [1] and read dumps of tables
  • +
  • export [1] data to various formats: CSV, XML, PDF, +ISO/IEC 26300 - OpenDocument Text and Spreadsheet, Microsoft +Word 2000, and LATEX formats
  • +
  • import data and MySQL structures from OpenDocument spreadsheets, as +well as XML, CSV, and SQL files
  • +
  • administer multiple servers
  • +
  • add, edit, and remove MySQL user accounts and privileges
  • +
  • check referential integrity in MyISAM tables
  • +
  • using Query-by-example (QBE), create complex queries automatically +connecting required tables
  • +
  • create PDF graphics of your +database layout
  • +
  • search globally in a database or a subset of it
  • +
  • transform stored data into any format using a set of predefined +functions, like displaying BLOB-data as image or download-link
  • +
  • track changes on databases, tables and views
  • +
  • support InnoDB tables and foreign keys
  • +
  • support mysqli, the improved MySQL extension see 1.17 Which Database versions does phpMyAdmin support?
  • +
  • create, edit, call, export and drop stored procedures and functions
  • +
  • create, edit, export and drop events and triggers
  • +
  • communicate in 80 different languages
  • +
+
+
+

Shortcut keys

+

Currently phpMyAdmin supports following shortcuts:

+
    +
  • k - Toggle console
  • +
  • h - Go to home page
  • +
  • s - Open settings
  • +
  • d + s - Go to database structure (Provided you are in database related page)
  • +
  • d + f - Search database (Provided you are in database related page)
  • +
  • t + s - Go to table structure (Provided you are in table related page)
  • +
  • t + f - Search table (Provided you are in table related page)
  • +
  • backspace - Takes you to older page.
  • +
+
+
+

A word about users

+

Many people have difficulty understanding the concept of user +management with regards to phpMyAdmin. When a user logs in to +phpMyAdmin, that username and password are passed directly to MySQL. +phpMyAdmin does no account management on its own (other than allowing +one to manipulate the MySQL user account information); all users must +be valid MySQL users.

+

Footnotes

+ + + + + +
[1](1, 2) phpMyAdmin can compress (Zip, GZip or RFC 1952 +formats) dumps and CSV exports if you use PHP with +Zlib support (--with-zlib). +Proper support may also need changes in php.ini.
+
+
+ + +
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/php/apps/phpmyadmin49/doc/html/other.html b/php/apps/phpmyadmin49/doc/html/other.html new file mode 100644 index 00000000..6f06a7f6 --- /dev/null +++ b/php/apps/phpmyadmin49/doc/html/other.html @@ -0,0 +1,158 @@ + + + + + + + + Other sources of information — phpMyAdmin 4.9.1 documentation + + + + + + + + + + + + + + + + + + + +
+
+
+
+ +
+

Other sources of information

+
+

Printed Book

+

The definitive guide to using phpMyAdmin is the book Mastering phpMyAdmin for +Effective MySQL Management by Marc Delisle. You can get information on that +book and other officially endorsed books at the phpMyAdmin site.

+
+
+

Tutorials

+

Third party tutorials and articles which you might find interesting:

+
+

Česky (Czech)

+ +
+ +
+

Русский (Russian)

+ +
+
+
+ + +
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/php/apps/phpmyadmin49/doc/html/privileges.html b/php/apps/phpmyadmin49/doc/html/privileges.html new file mode 100644 index 00000000..49328a53 --- /dev/null +++ b/php/apps/phpmyadmin49/doc/html/privileges.html @@ -0,0 +1,188 @@ + + + + + + + + User management — phpMyAdmin 4.9.1 documentation + + + + + + + + + + + + + + + + + + + +
+
+
+
+ +
+

User management

+

User management is the process of controlling which users are allowed to +connect to the MySQL server and what permissions they have on each database. +phpMyAdmin does not handle user management, rather it passes the username and +password on to MySQL, which then determines whether a user is permitted to +perform a particular action. Within phpMyAdmin, administrators have full +control over creating users, viewing and editing privileges for existing users, +and removing users.

+

Within phpMyAdmin, user management is controlled via the Users link +from the main page. Users can be created, edited, and removed.

+
+

Creating a new user

+

To create a new user, click the Add a new user link near the bottom +of the Users page (you must be a “superuser”, e.g., user “root”). +Use the textboxes and drop-downs to configure the user to your particular +needs. You can then select whether to create a database for that user and grant +specific global privileges. Once you’ve created the user (by clicking Go), you +can define that user’s permissions on a specific database (don’t grant global +privileges in that case). In general, users do not need any global privileges +(other than USAGE), only permissions for their specific database.

+
+
+

Editing an existing user

+

To edit an existing user, simply click the pencil icon to the right of that +user in the Users page. You can then edit their global- and +database-specific privileges, change their password, or even copy those +privileges to a new user.

+
+
+

Deleting a user

+

From the Users page, check the checkbox for the user you wish to +remove, select whether or not to also remove any databases of the same name (if +they exist), and click Go.

+
+
+

Assigning privileges to user for a specific database

+

Users are assigned to databases by editing the user record (from the +User accounts link on the home page). +If you are creating a user specifically for a given table +you will have to create the user first (with no global privileges) and then go +back and edit that user to add the table and privileges for the individual +table.

+
+
+

Configurable menus and user groups

+

By enabling $cfg['Servers'][$i]['usergroups'] and +$cfg['Servers'][$i]['usergroups'] you can customize what users +will see in the phpMyAdmin navigation.

+
+

Warning

+

This feature only limits what a user sees, he is still able to use all the +functions. So this can not be considered as a security limitation. Should +you want to limit what users can do, use MySQL privileges to achieve that.

+
+

With this feature enabled, the User accounts management interface gains +a second tab for managing User groups, where you can define what each +group will view (see image below) and you can then assign each user to one of +these groups. Users will be presented with a simplified user interface, which might be +useful for inexperienced users who could be overwhelmed by all the features +phpMyAdmin provides.

+_images/usergroups.png +
+
+ + +
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/php/apps/phpmyadmin49/doc/html/relations.html b/php/apps/phpmyadmin49/doc/html/relations.html new file mode 100644 index 00000000..8957923e --- /dev/null +++ b/php/apps/phpmyadmin49/doc/html/relations.html @@ -0,0 +1,194 @@ + + + + + + + + Relations — phpMyAdmin 4.9.1 documentation + + + + + + + + + + + + + + + + + + + +
+
+
+
+ +
+

Relations

+

phpMyAdmin allows relationships (similar to foreign keys) using MySQL-native +(InnoDB) methods when available and falling back on special phpMyAdmin-only +features when needed. There are two ways of editing these relations, with the +relation view and the drag-and-drop designer – both of which are explained +on this page.

+
+

Note

+

You need to have configured the phpMyAdmin configuration storage for using phpMyAdmin +only relations.

+
+
+

Technical info

+

Currently the only MySQL table type that natively supports relationships is +InnoDB. When using an InnoDB table, phpMyAdmin will create real InnoDB +relations which will be enforced by MySQL no matter which application accesses +the database. In the case of any other table type, phpMyAdmin enforces the +relations internally and those relations are not applied to any other +application.

+
+
+

Relation view

+

In order to get it working, you first have to properly create the +[[pmadb|pmadb]]. Once that is setup, select a table’s “Structure” page. Below +the table definition, a link called “Relation view” is shown. If you click that +link, a page will be shown that offers you to create a link to another table +for any (most) fields. Only PRIMARY KEYS are shown there, so if the field you +are referring to is not shown, you most likely are doing something wrong. The +drop-down at the bottom is the field which will be used as the name for a +record.

+
+

Relation view example

+_images/pma-relations-relation-view-link.png +_images/pma-relations-relation-link.png +

Let’s say you have categories and links and one category can contain several links. Your table structure would be something like this:

+
    +
  • category.category_id (must be unique)
  • +
  • category.name
  • +
  • link.link_id
  • +
  • link.category_id
  • +
  • link.uri.
  • +
+

Open the relation view (below the table structure) page for the link table and for category_id field, you select category.category_id as master record.

+

If you now browse the link table, the category_id field will be a clickable hyperlink to the proper category record. But all you see is just the category_id, not the name of the category.

+_images/pma-relations-relation-name.png +

To fix this, open the relation view of the category table and in the drop down at the bottom, select “name”. If you now browse the link table again and hover the mouse over the category_id hyperlink, the value from the related category will be shown as tooltip.

+_images/pma-relations-links.png +
+
+
+

Designer

+

The Designer feature is a graphical way of creating, editing, and displaying +phpMyAdmin relations. These relations are compatible with those created in +phpMyAdmin’s relation view.

+

To use this feature, you need a properly configured phpMyAdmin configuration storage and +must have the $cfg['Servers'][$i]['table_coords'] configured.

+

To use the designer, select a database’s structure page, then look for the +Designer tab.

+

To export the view into PDF, you have to create PDF pages first. The Designer +creates the layout, how the tables shall be displayed. To finally export the +view, you have to create this with a PDF page and select your layout, which you +have created with the designer.

+ +
+
+ + +
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/php/apps/phpmyadmin49/doc/html/require.html b/php/apps/phpmyadmin49/doc/html/require.html new file mode 100644 index 00000000..5acc6719 --- /dev/null +++ b/php/apps/phpmyadmin49/doc/html/require.html @@ -0,0 +1,172 @@ + + + + + + + + Requirements — phpMyAdmin 4.9.1 documentation + + + + + + + + + + + + + + + + + + +
+
+
+
+ +
+

Requirements

+
+

Web server

+

Since phpMyAdmin’s interface is based entirely in your browser, you’ll need a +web server (such as Apache, nginx, IIS) to install phpMyAdmin’s files into.

+
+
+

PHP

+
    +
  • You need PHP 5.5.0 or newer, with session support, the Standard PHP Library +(SPL) extension, hash, ctype, and JSON support.
  • +
  • The mbstring extension (see mbstring) is strongly recommended +for performance reasons.
  • +
  • To support uploading of ZIP files, you need the PHP zip extension.
  • +
  • You need GD2 support in PHP to display inline thumbnails of JPEGs +(“image/jpeg: inline”) with their original aspect ratio.
  • +
  • When using the cookie authentication (the default), the openssl extension is strongly suggested.
  • +
  • To support upload progress bars, see 2.9 Seeing an upload progress bar.
  • +
  • To support XML and Open Document Spreadsheet importing, you need the +libxml extension.
  • +
  • To support reCAPTCHA on the login page, you need the +openssl extension.
  • +
  • To support displaying phpMyAdmin’s latest version, you need to enable +allow_url_open in your php.ini or to have the +curl extension.
  • +
+ +
+
+

Database

+

phpMyAdmin supports MySQL-compatible databases.

+
    +
  • MySQL 5.5 or newer
  • +
  • MariaDB 5.5 or newer
  • +
+ +
+
+

Web browser

+

To access phpMyAdmin you need a web browser with cookies and JavaScript +enabled.

+

You need browser which is supported by jQuery 2.0, see +<https://jquery.com/browser-support/>.

+
+
+ + +
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/php/apps/phpmyadmin49/doc/html/search.html b/php/apps/phpmyadmin49/doc/html/search.html new file mode 100644 index 00000000..45d583ba --- /dev/null +++ b/php/apps/phpmyadmin49/doc/html/search.html @@ -0,0 +1,102 @@ + + + + + + + + Search — phpMyAdmin 4.9.1 documentation + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+ +

Search

+
+ +

+ Please activate JavaScript to enable the search + functionality. +

+
+

+ From here you can search these documents. Enter your search + words into the box below and click "search". Note that the search + function will automatically search for all of the words. Pages + containing fewer words won't appear in the result list. +

+
+ + + +
+ +
+ +
+ +
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/php/apps/phpmyadmin49/doc/html/searchindex.js b/php/apps/phpmyadmin49/doc/html/searchindex.js new file mode 100644 index 00000000..441c0383 --- /dev/null +++ b/php/apps/phpmyadmin49/doc/html/searchindex.js @@ -0,0 +1 @@ +Search.setIndex({envversion:49,filenames:["bookmarks","charts","config","copyright","credits","developers","faq","glossary","import_export","index","intro","other","privileges","relations","require","security","settings","setup","themes","transformations","two_factor","user","vendors"],objects:{"":{"$cfg['ActionLinksMode']":[2,0,1,"cfg_ActionLinksMode"],"$cfg['AllowArbitraryServer']":[2,0,1,"cfg_AllowArbitraryServer"],"$cfg['AllowThirdPartyFraming']":[2,0,1,"cfg_AllowThirdPartyFraming"],"$cfg['AllowUserDropDatabase']":[2,0,1,"cfg_AllowUserDropDatabase"],"$cfg['ArbitraryServerRegexp']":[2,0,1,"cfg_ArbitraryServerRegexp"],"$cfg['AuthLog']":[2,0,1,"cfg_AuthLog"],"$cfg['AuthLogSuccess']":[2,0,1,"cfg_AuthLogSuccess"],"$cfg['AvailableCharsets']":[2,0,1,"cfg_AvailableCharsets"],"$cfg['BZipDump']":[2,0,1,"cfg_BZipDump"],"$cfg['BrowseMIME']":[2,0,1,"cfg_BrowseMIME"],"$cfg['BrowseMarkerEnable']":[2,0,1,"cfg_BrowseMarkerEnable"],"$cfg['BrowsePointerEnable']":[2,0,1,"cfg_BrowsePointerEnable"],"$cfg['CSPAllow']":[2,0,1,"cfg_CSPAllow"],"$cfg['CaptchaLoginPrivateKey']":[2,0,1,"cfg_CaptchaLoginPrivateKey"],"$cfg['CaptchaLoginPublicKey']":[2,0,1,"cfg_CaptchaLoginPublicKey"],"$cfg['CharEditing']":[2,0,1,"cfg_CharEditing"],"$cfg['CharTextareaCols']":[2,0,1,"cfg_CharTextareaCols"],"$cfg['CharTextareaRows']":[2,0,1,"cfg_CharTextareaRows"],"$cfg['CheckConfigurationPermissions']":[2,0,1,"cfg_CheckConfigurationPermissions"],"$cfg['CodemirrorEnable']":[2,0,1,"cfg_CodemirrorEnable"],"$cfg['CompressOnFly']":[2,0,1,"cfg_CompressOnFly"],"$cfg['Confirm']":[2,0,1,"cfg_Confirm"],"$cfg['Console']['AlwaysExpand']":[2,0,1,"cfg_Console_AlwaysExpand"],"$cfg['Console']['CurrentQuery']":[2,0,1,"cfg_Console_CurrentQuery"],"$cfg['Console']['DarkTheme']":[2,0,1,"cfg_Console_DarkTheme"],"$cfg['Console']['EnterExecutes']":[2,0,1,"cfg_Console_EnterExecutes"],"$cfg['Console']['Height']":[2,0,1,"cfg_Console_Height"],"$cfg['Console']['Mode']":[2,0,1,"cfg_Console_Mode"],"$cfg['Console']['StartHistory']":[2,0,1,"cfg_Console_StartHistory"],"$cfg['ConsoleEnterExecutes']":[2,0,1,"cfg_ConsoleEnterExecutes"],"$cfg['DBG']":[2,0,1,"cfg_DBG"],"$cfg['DBG']['demo']":[2,0,1,"cfg_DBG_demo"],"$cfg['DBG']['simple2fa']":[2,0,1,"cfg_DBG_simple2fa"],"$cfg['DBG']['sql']":[2,0,1,"cfg_DBG_sql"],"$cfg['DBG']['sqllog']":[2,0,1,"cfg_DBG_sqllog"],"$cfg['DefaultConnectionCollation']":[2,0,1,"cfg_DefaultConnectionCollation"],"$cfg['DefaultForeignKeyChecks']":[2,0,1,"cfg_DefaultForeignKeyChecks"],"$cfg['DefaultFunctions']":[2,0,1,"cfg_DefaultFunctions"],"$cfg['DefaultLang']":[2,0,1,"cfg_DefaultLang"],"$cfg['DefaultQueryDatabase']":[2,0,1,"cfg_DefaultQueryDatabase"],"$cfg['DefaultQueryTable']":[2,0,1,"cfg_DefaultQueryTable"],"$cfg['DefaultTabDatabase']":[2,0,1,"cfg_DefaultTabDatabase"],"$cfg['DefaultTabServer']":[2,0,1,"cfg_DefaultTabServer"],"$cfg['DefaultTabTable']":[2,0,1,"cfg_DefaultTabTable"],"$cfg['DefaultTransformations']":[2,0,1,"cfg_DefaultTransformations"],"$cfg['DefaultTransformations']['Bool2Text']":[2,0,1,"cfg_DefaultTransformations_Bool2Text"],"$cfg['DefaultTransformations']['DateFormat']":[2,0,1,"cfg_DefaultTransformations_DateFormat"],"$cfg['DefaultTransformations']['External']":[2,0,1,"cfg_DefaultTransformations_External"],"$cfg['DefaultTransformations']['Hex']":[2,0,1,"cfg_DefaultTransformations_Hex"],"$cfg['DefaultTransformations']['Inline']":[2,0,1,"cfg_DefaultTransformations_Inline"],"$cfg['DefaultTransformations']['PreApPend']":[2,0,1,"cfg_DefaultTransformations_PreApPend"],"$cfg['DefaultTransformations']['Substring']":[2,0,1,"cfg_DefaultTransformations_Substring"],"$cfg['DefaultTransformations']['TextImageLink']":[2,0,1,"cfg_DefaultTransformations_TextImageLink"],"$cfg['DefaultTransformations']['TextLink']":[2,0,1,"cfg_DefaultTransformations_TextLink"],"$cfg['DisableMultiTableMaintenance']":[2,0,1,"cfg_DisableMultiTableMaintenance"],"$cfg['DisableShortcutKeys']":[2,0,1,"cfg_DisableShortcutKeys"],"$cfg['DisplayServersList']":[2,0,1,"cfg_DisplayServersList"],"$cfg['EnableAutocompleteForTablesAndColumns']":[2,0,1,"cfg_EnableAutocompleteForTablesAndColumns"],"$cfg['ExecTimeLimit']":[2,0,1,"cfg_ExecTimeLimit"],"$cfg['Export']":[2,0,1,"cfg_Export"],"$cfg['Export']['charset']":[2,0,1,"cfg_Export_charset"],"$cfg['Export']['file_template_database']":[2,0,1,"cfg_Export_file_template_database"],"$cfg['Export']['file_template_server']":[2,0,1,"cfg_Export_file_template_server"],"$cfg['Export']['file_template_table']":[2,0,1,"cfg_Export_file_template_table"],"$cfg['Export']['format']":[2,0,1,"cfg_Export_format"],"$cfg['Export']['method']":[2,0,1,"cfg_Export_method"],"$cfg['FilterLanguages']":[2,0,1,"cfg_FilterLanguages"],"$cfg['FirstLevelNavigationItems']":[2,0,1,"cfg_FirstLevelNavigationItems"],"$cfg['FontSize']":[2,0,1,"cfg_FontSize"],"$cfg['ForceSSL']":[2,0,1,"cfg_ForceSSL"],"$cfg['ForeignKeyDropdownOrder']":[2,0,1,"cfg_ForeignKeyDropdownOrder"],"$cfg['ForeignKeyMaxLimit']":[2,0,1,"cfg_ForeignKeyMaxLimit"],"$cfg['GD2Available']":[2,0,1,"cfg_GD2Available"],"$cfg['GZipDump']":[2,0,1,"cfg_GZipDump"],"$cfg['GridEditing']":[2,0,1,"cfg_GridEditing"],"$cfg['HideStructureActions']":[2,0,1,"cfg_HideStructureActions"],"$cfg['IconvExtraParams']":[2,0,1,"cfg_IconvExtraParams"],"$cfg['IgnoreMultiSubmitErrors']":[2,0,1,"cfg_IgnoreMultiSubmitErrors"],"$cfg['Import']":[2,0,1,"cfg_Import"],"$cfg['Import']['charset']":[2,0,1,"cfg_Import_charset"],"$cfg['InitialSlidersState']":[2,0,1,"cfg_InitialSlidersState"],"$cfg['InsertRows']":[2,0,1,"cfg_InsertRows"],"$cfg['Lang']":[2,0,1,"cfg_Lang"],"$cfg['LimitChars']":[2,0,1,"cfg_LimitChars"],"$cfg['LinkLengthLimit']":[2,0,1,"cfg_LinkLengthLimit"],"$cfg['LoginCookieDeleteAll']":[2,0,1,"cfg_LoginCookieDeleteAll"],"$cfg['LoginCookieRecall']":[2,0,1,"cfg_LoginCookieRecall"],"$cfg['LoginCookieStore']":[2,0,1,"cfg_LoginCookieStore"],"$cfg['LoginCookieValidity']":[2,0,1,"cfg_LoginCookieValidity"],"$cfg['LoginCookieValidityDisableWarning']":[2,0,1,"cfg_LoginCookieValidityDisableWarning"],"$cfg['LongtextDoubleTextarea']":[2,0,1,"cfg_LongtextDoubleTextarea"],"$cfg['MaxCharactersInDisplayedSQL']":[2,0,1,"cfg_MaxCharactersInDisplayedSQL"],"$cfg['MaxDbList']":[2,0,1,"cfg_MaxDbList"],"$cfg['MaxExactCount']":[2,0,1,"cfg_MaxExactCount"],"$cfg['MaxExactCountViews']":[2,0,1,"cfg_MaxExactCountViews"],"$cfg['MaxNavigationItems']":[2,0,1,"cfg_MaxNavigationItems"],"$cfg['MaxRows']":[2,0,1,"cfg_MaxRows"],"$cfg['MaxSizeForInputField']":[2,0,1,"cfg_MaxSizeForInputField"],"$cfg['MaxTableList']":[2,0,1,"cfg_MaxTableList"],"$cfg['MemoryLimit']":[2,0,1,"cfg_MemoryLimit"],"$cfg['MinSizeForInputField']":[2,0,1,"cfg_MinSizeForInputField"],"$cfg['MysqlMinVersion']":[2,0,1,"cfg_MysqlMinVersion"],"$cfg['NaturalOrder']":[2,0,1,"cfg_NaturalOrder"],"$cfg['NavigationDisplayLogo']":[2,0,1,"cfg_NavigationDisplayLogo"],"$cfg['NavigationDisplayServers']":[2,0,1,"cfg_NavigationDisplayServers"],"$cfg['NavigationLinkWithMainPanel']":[2,0,1,"cfg_NavigationLinkWithMainPanel"],"$cfg['NavigationLogoLink']":[2,0,1,"cfg_NavigationLogoLink"],"$cfg['NavigationLogoLinkWindow']":[2,0,1,"cfg_NavigationLogoLinkWindow"],"$cfg['NavigationTreeDbSeparator']":[2,0,1,"cfg_NavigationTreeDbSeparator"],"$cfg['NavigationTreeDefaultTabTable']":[2,0,1,"cfg_NavigationTreeDefaultTabTable"],"$cfg['NavigationTreeDefaultTabTable2']":[2,0,1,"cfg_NavigationTreeDefaultTabTable2"],"$cfg['NavigationTreeDisplayDbFilterMinimum']":[2,0,1,"cfg_NavigationTreeDisplayDbFilterMinimum"],"$cfg['NavigationTreeDisplayItemFilterMinimum']":[2,0,1,"cfg_NavigationTreeDisplayItemFilterMinimum"],"$cfg['NavigationTreeEnableExpansion']":[2,0,1,"cfg_NavigationTreeEnableExpansion"],"$cfg['NavigationTreeEnableGrouping']":[2,0,1,"cfg_NavigationTreeEnableGrouping"],"$cfg['NavigationTreePointerEnable']":[2,0,1,"cfg_NavigationTreePointerEnable"],"$cfg['NavigationTreeShowEvents']":[2,0,1,"cfg_NavigationTreeShowEvents"],"$cfg['NavigationTreeShowFunctions']":[2,0,1,"cfg_NavigationTreeShowFunctions"],"$cfg['NavigationTreeShowProcedures']":[2,0,1,"cfg_NavigationTreeShowProcedures"],"$cfg['NavigationTreeShowTables']":[2,0,1,"cfg_NavigationTreeShowTables"],"$cfg['NavigationTreeShowViews']":[2,0,1,"cfg_NavigationTreeShowViews"],"$cfg['NavigationTreeTableLevel']":[2,0,1,"cfg_NavigationTreeTableLevel"],"$cfg['NavigationTreeTableSeparator']":[2,0,1,"cfg_NavigationTreeTableSeparator"],"$cfg['NavigationWidth']":[2,0,1,"cfg_NavigationWidth"],"$cfg['NumFavoriteTables']":[2,0,1,"cfg_NumFavoriteTables"],"$cfg['NumRecentTables']":[2,0,1,"cfg_NumRecentTables"],"$cfg['OBGzip']":[2,0,1,"cfg_OBGzip"],"$cfg['Order']":[2,0,1,"cfg_Order"],"$cfg['PDFDefaultPageSize']":[2,0,1,"cfg_PDFDefaultPageSize"],"$cfg['PDFPageSizes']":[2,0,1,"cfg_PDFPageSizes"],"$cfg['PersistentConnections']":[2,0,1,"cfg_PersistentConnections"],"$cfg['PmaAbsoluteUri']":[2,0,1,"cfg_PmaAbsoluteUri"],"$cfg['PmaNoRelation_DisableWarning']":[2,0,1,"cfg_PmaNoRelation_DisableWarning"],"$cfg['PropertiesNumColumns']":[2,0,1,"cfg_PropertiesNumColumns"],"$cfg['ProtectBinary']":[2,0,1,"cfg_ProtectBinary"],"$cfg['ProxyPass']":[2,0,1,"cfg_ProxyPass"],"$cfg['ProxyUrl']":[2,0,1,"cfg_ProxyUrl"],"$cfg['ProxyUser']":[2,0,1,"cfg_ProxyUser"],"$cfg['QueryHistoryDB']":[2,0,1,"cfg_QueryHistoryDB"],"$cfg['QueryHistoryMax']":[2,0,1,"cfg_QueryHistoryMax"],"$cfg['RecodingEngine']":[2,0,1,"cfg_RecodingEngine"],"$cfg['RelationalDisplay']":[2,0,1,"cfg_RelationalDisplay"],"$cfg['RememberSorting']":[2,0,1,"cfg_RememberSorting"],"$cfg['RepeatCells']":[2,0,1,"cfg_RepeatCells"],"$cfg['ReservedWordDisableWarning']":[2,0,1,"cfg_ReservedWordDisableWarning"],"$cfg['RetainQueryBox']":[2,0,1,"cfg_RetainQueryBox"],"$cfg['RowActionLinks']":[2,0,1,"cfg_RowActionLinks"],"$cfg['RowActionLinksWithoutUnique']":[2,0,1,"cfg_RowActionLinksWithoutUnique"],"$cfg['RowActionType']":[2,0,1,"cfg_RowActionType"],"$cfg['SQLQuery']['Edit']":[2,0,1,"cfg_SQLQuery_Edit"],"$cfg['SQLQuery']['Explain']":[2,0,1,"cfg_SQLQuery_Explain"],"$cfg['SQLQuery']['Refresh']":[2,0,1,"cfg_SQLQuery_Refresh"],"$cfg['SQLQuery']['ShowAsPHP']":[2,0,1,"cfg_SQLQuery_ShowAsPHP"],"$cfg['SaveCellsAtOnce']":[2,0,1,"cfg_SaveCellsAtOnce"],"$cfg['SaveDir']":[2,0,1,"cfg_SaveDir"],"$cfg['SendErrorReports']":[2,0,1,"cfg_SendErrorReports"],"$cfg['ServerDefault']":[2,0,1,"cfg_ServerDefault"],"$cfg['ServerLibraryDifference_DisableWarning']":[2,0,1,"cfg_ServerLibraryDifference_DisableWarning"],"$cfg['Servers']":[2,0,1,"cfg_Servers"],"$cfg['Servers'][$i]['AllowDeny']['order']":[2,0,1,"cfg_Servers_AllowDeny_order"],"$cfg['Servers'][$i]['AllowDeny']['rules']":[2,0,1,"cfg_Servers_AllowDeny_rules"],"$cfg['Servers'][$i]['AllowNoPassword']":[2,0,1,"cfg_Servers_AllowNoPassword"],"$cfg['Servers'][$i]['AllowRoot']":[2,0,1,"cfg_Servers_AllowRoot"],"$cfg['Servers'][$i]['DisableIS']":[2,0,1,"cfg_Servers_DisableIS"],"$cfg['Servers'][$i]['LogoutURL']":[2,0,1,"cfg_Servers_LogoutURL"],"$cfg['Servers'][$i]['MaxTableUiprefs']":[2,0,1,"cfg_Servers_MaxTableUiprefs"],"$cfg['Servers'][$i]['SessionTimeZone']":[2,0,1,"cfg_Servers_SessionTimeZone"],"$cfg['Servers'][$i]['SignonCookieParams']":[2,0,1,"cfg_Servers_SignonCookieParams"],"$cfg['Servers'][$i]['SignonScript']":[2,0,1,"cfg_Servers_SignonScript"],"$cfg['Servers'][$i]['SignonSession']":[2,0,1,"cfg_Servers_SignonSession"],"$cfg['Servers'][$i]['SignonURL']":[2,0,1,"cfg_Servers_SignonURL"],"$cfg['Servers'][$i]['auth_http_realm']":[2,0,1,"cfg_Servers_auth_http_realm"],"$cfg['Servers'][$i]['auth_type']":[2,0,1,"cfg_Servers_auth_type"],"$cfg['Servers'][$i]['bookmarktable']":[2,0,1,"cfg_Servers_bookmarktable"],"$cfg['Servers'][$i]['central_columns']":[2,0,1,"cfg_Servers_central_columns"],"$cfg['Servers'][$i]['column_info']":[2,0,1,"cfg_Servers_column_info"],"$cfg['Servers'][$i]['compress']":[2,0,1,"cfg_Servers_compress"],"$cfg['Servers'][$i]['connect_type']":[2,0,1,"cfg_Servers_connect_type"],"$cfg['Servers'][$i]['control_*']":[2,0,1,"cfg_Servers_control_*"],"$cfg['Servers'][$i]['controlhost']":[2,0,1,"cfg_Servers_controlhost"],"$cfg['Servers'][$i]['controlpass']":[2,0,1,"cfg_Servers_controlpass"],"$cfg['Servers'][$i]['controlport']":[2,0,1,"cfg_Servers_controlport"],"$cfg['Servers'][$i]['controluser']":[2,0,1,"cfg_Servers_controluser"],"$cfg['Servers'][$i]['designer_settings']":[2,0,1,"cfg_Servers_designer_settings"],"$cfg['Servers'][$i]['export_templates']":[2,0,1,"cfg_Servers_export_templates"],"$cfg['Servers'][$i]['extension']":[2,0,1,"cfg_Servers_extension"],"$cfg['Servers'][$i]['favorite']":[2,0,1,"cfg_Servers_favorite"],"$cfg['Servers'][$i]['hide_db']":[2,0,1,"cfg_Servers_hide_db"],"$cfg['Servers'][$i]['history']":[2,0,1,"cfg_Servers_history"],"$cfg['Servers'][$i]['host']":[2,0,1,"cfg_Servers_host"],"$cfg['Servers'][$i]['navigationhiding']":[2,0,1,"cfg_Servers_navigationhiding"],"$cfg['Servers'][$i]['nopassword']":[2,0,1,"cfg_Servers_nopassword"],"$cfg['Servers'][$i]['only_db']":[2,0,1,"cfg_Servers_only_db"],"$cfg['Servers'][$i]['password']":[2,0,1,"cfg_Servers_password"],"$cfg['Servers'][$i]['pdf_pages']":[2,0,1,"cfg_Servers_pdf_pages"],"$cfg['Servers'][$i]['pmadb']":[2,0,1,"cfg_Servers_pmadb"],"$cfg['Servers'][$i]['port']":[2,0,1,"cfg_Servers_port"],"$cfg['Servers'][$i]['recent']":[2,0,1,"cfg_Servers_recent"],"$cfg['Servers'][$i]['relation']":[2,0,1,"cfg_Servers_relation"],"$cfg['Servers'][$i]['savedsearches']":[2,0,1,"cfg_Servers_savedsearches"],"$cfg['Servers'][$i]['socket']":[2,0,1,"cfg_Servers_socket"],"$cfg['Servers'][$i]['ssl']":[2,0,1,"cfg_Servers_ssl"],"$cfg['Servers'][$i]['ssl_ca']":[2,0,1,"cfg_Servers_ssl_ca"],"$cfg['Servers'][$i]['ssl_ca_path']":[2,0,1,"cfg_Servers_ssl_ca_path"],"$cfg['Servers'][$i]['ssl_cert']":[2,0,1,"cfg_Servers_ssl_cert"],"$cfg['Servers'][$i]['ssl_ciphers']":[2,0,1,"cfg_Servers_ssl_ciphers"],"$cfg['Servers'][$i]['ssl_key']":[2,0,1,"cfg_Servers_ssl_key"],"$cfg['Servers'][$i]['ssl_verify']":[2,0,1,"cfg_Servers_ssl_verify"],"$cfg['Servers'][$i]['table_coords']":[2,0,1,"cfg_Servers_table_coords"],"$cfg['Servers'][$i]['table_info']":[2,0,1,"cfg_Servers_table_info"],"$cfg['Servers'][$i]['table_uiprefs']":[2,0,1,"cfg_Servers_table_uiprefs"],"$cfg['Servers'][$i]['tracking']":[2,0,1,"cfg_Servers_tracking"],"$cfg['Servers'][$i]['tracking_add_drop_database']":[2,0,1,"cfg_Servers_tracking_add_drop_database"],"$cfg['Servers'][$i]['tracking_add_drop_table']":[2,0,1,"cfg_Servers_tracking_add_drop_table"],"$cfg['Servers'][$i]['tracking_add_drop_view']":[2,0,1,"cfg_Servers_tracking_add_drop_view"],"$cfg['Servers'][$i]['tracking_default_statements']":[2,0,1,"cfg_Servers_tracking_default_statements"],"$cfg['Servers'][$i]['tracking_version_auto_create']":[2,0,1,"cfg_Servers_tracking_version_auto_create"],"$cfg['Servers'][$i]['user']":[2,0,1,"cfg_Servers_user"],"$cfg['Servers'][$i]['userconfig']":[2,0,1,"cfg_Servers_userconfig"],"$cfg['Servers'][$i]['usergroups']":[2,0,1,"cfg_Servers_usergroups"],"$cfg['Servers'][$i]['users']":[2,0,1,"cfg_Servers_users"],"$cfg['Servers'][$i]['verbose']":[2,0,1,"cfg_Servers_verbose"],"$cfg['SessionSavePath']":[2,0,1,"cfg_SessionSavePath"],"$cfg['ShowAll']":[2,0,1,"cfg_ShowAll"],"$cfg['ShowBrowseComments']":[2,0,1,"cfg_ShowBrowseComments"],"$cfg['ShowChgPassword']":[2,0,1,"cfg_ShowChgPassword"],"$cfg['ShowColumnComments']":[2,0,1,"cfg_ShowColumnComments"],"$cfg['ShowCreateDb']":[2,0,1,"cfg_ShowCreateDb"],"$cfg['ShowDatabasesNavigationAsTree']":[2,0,1,"cfg_ShowDatabasesNavigationAsTree"],"$cfg['ShowDbStructureCreation']":[2,0,1,"cfg_ShowDbStructureCreation"],"$cfg['ShowDbStructureLastCheck']":[2,0,1,"cfg_ShowDbStructureLastCheck"],"$cfg['ShowDbStructureLastUpdate']":[2,0,1,"cfg_ShowDbStructureLastUpdate"],"$cfg['ShowFieldTypesInDataEditView']":[2,0,1,"cfg_ShowFieldTypesInDataEditView"],"$cfg['ShowFunctionFields']":[2,0,1,"cfg_ShowFunctionFields"],"$cfg['ShowGitRevision']":[2,0,1,"cfg_ShowGitRevision"],"$cfg['ShowHint']":[2,0,1,"cfg_ShowHint"],"$cfg['ShowPhpInfo']":[2,0,1,"cfg_ShowPhpInfo"],"$cfg['ShowPropertyComments']":[2,0,1,"cfg_ShowPropertyComments"],"$cfg['ShowSQL']":[2,0,1,"cfg_ShowSQL"],"$cfg['ShowServerInfo']":[2,0,1,"cfg_ShowServerInfo"],"$cfg['ShowStats']":[2,0,1,"cfg_ShowStats"],"$cfg['SkipLockedTables']":[2,0,1,"cfg_SkipLockedTables"],"$cfg['SuhosinDisableWarning']":[2,0,1,"cfg_SuhosinDisableWarning"],"$cfg['TableNavigationLinksMode']":[2,0,1,"cfg_TableNavigationLinksMode"],"$cfg['TablePrimaryKeyOrder']":[2,0,1,"cfg_TablePrimaryKeyOrder"],"$cfg['TabsMode']":[2,0,1,"cfg_TabsMode"],"$cfg['TempDir']":[2,0,1,"cfg_TempDir"],"$cfg['TextareaAutoSelect']":[2,0,1,"cfg_TextareaAutoSelect"],"$cfg['TextareaCols']":[2,0,1,"cfg_TextareaCols"],"$cfg['TextareaRows']":[2,0,1,"cfg_TextareaRows"],"$cfg['ThemeDefault']":[2,0,1,"cfg_ThemeDefault"],"$cfg['ThemeManager']":[2,0,1,"cfg_ThemeManager"],"$cfg['ThemePerServer']":[2,0,1,"cfg_ThemePerServer"],"$cfg['TitleDatabase']":[2,0,1,"cfg_TitleDatabase"],"$cfg['TitleDefault']":[2,0,1,"cfg_TitleDefault"],"$cfg['TitleServer']":[2,0,1,"cfg_TitleServer"],"$cfg['TitleTable']":[2,0,1,"cfg_TitleTable"],"$cfg['TranslationWarningThreshold']":[2,0,1,"cfg_TranslationWarningThreshold"],"$cfg['TrustedProxies']":[2,0,1,"cfg_TrustedProxies"],"$cfg['UploadDir']":[2,0,1,"cfg_UploadDir"],"$cfg['UseDbSearch']":[2,0,1,"cfg_UseDbSearch"],"$cfg['UserprefsDeveloperTab']":[2,0,1,"cfg_UserprefsDeveloperTab"],"$cfg['UserprefsDisallow']":[2,0,1,"cfg_UserprefsDisallow"],"$cfg['VersionCheck']":[2,0,1,"cfg_VersionCheck"],"$cfg['ZeroConf']":[2,0,1,"cfg_ZeroConf"],"$cfg['ZipDump']":[2,0,1,"cfg_ZipDump"],"$cfg['blowfish_secret']":[2,0,1,"cfg_blowfish_secret"],PMA_ABSOLUTE_URI:[17,2,1,"-"],PMA_ARBITRARY:[17,2,1,"-"],PMA_HOST:[17,2,1,"-"],PMA_HOSTS:[17,2,1,"-"],PMA_PASSWORD:[17,2,1,"-"],PMA_PORT:[17,2,1,"-"],PMA_PORTS:[17,2,1,"-"],PMA_USER:[17,2,1,"-"],PMA_VERBOSE:[17,2,1,"-"],PMA_VERBOSES:[17,2,1,"-"],comment:[8,1,1,""],data:[8,1,1,""],database:[8,1,1,""],name:[8,1,1,""],type:[8,1,1,""],version:[8,1,1,""]}},objnames:{"0":["config","option","Config config option"],"1":["js","data","JavaScript data"],"2":["std","envvar","environment variable"]},objtypes:{"0":"config:option","1":"js:data","2":"std:envvar"},terms:{"01youknowme_at_gmail":4,"0c3f":17,"0d79":17,"0eb7":17,"0pt":8,"0x9c27b31342b7511d":17,"0xce752f178259bd92":17,"0xfefc65d181af644a":17,"16m":2,"1b51":17,"1c17":17,"1df1":17,"210mm":8,"218a":15,"297mm":8,"2cm":8,"2f17":17,"3092849_at_qq":4,"38cf":15,"3d06":17,"3d06a59ece730eb71b511c17ce752f178259bd92":17,"4096r":15,"42b7":17,"436f":17,"4b1a":17,"4bd7":15,"50b9":15,"50x50":6,"511d":17,"5a32":17,"5bad":15,"5e4176fb497a31f7":15,"5xp_":17,"6375lpd_at_gmail":4,"63cb":17,"644a":17,"65d1":17,"7euser":6,"81af":17,"8259bd92":17,"98se":6,"999_at_yahoo":4,"9c27":17,"\u00e7okaj":4,"\u00e9tienn":4,"\u010diha\u0159":[3,4,17],"\u0161ime\u010dek":4,"\u03c0\u03b1\u03bd\u03b1\u03b3\u03b9\u03ce\u03c4\u03b7\u03c2":4,"\u03c0\u03b1\u03c0\u03ac\u03b6\u03bf\u03b3\u03bb\u03bf\u03c5":4,"\u0438\u0432\u0430\u043d":4,"\u043a\u0430\u0440\u043f\u043e\u0432":4,"\u043e\u043b\u0435\u0433":4,"\u043f\u0435\u0434\u044c\u043a\u043e":4,"\u0441\u0435\u0440\u0433\u0435\u0435\u0432\u0438\u0447":4,"\u0441\u0435\u0440\u0433\u0456\u0439":4,"\u0445\u043e\u043c\u0443\u0442\u043e\u0432":4,"\u0b95":4,"\u0b95\u0ba3":4,"\u0bae":4,"\u0bb0":4,"\u0bb7":4,"\u7f57\u6500\u767b":4,"\uc774\uacbd\uc900":4,"abstract":[4,19],"ale\u0161":4,"aputsia\u0138":4,"ara\u00fajo":4,"b\u00f8rge":4,"bal\u00e1z":4,"bokm\u00e5l":4,"boolean":2,"break":6,"byte":[2,7,8],"c\u00e9dric":4,"case":[0,2,6,12,13,17],"catch":17,"char":[0,2,6,15,17],"class":[2,6,17,19],"d\u00e1niel":4,"default":0,"delete":[2,6,17],"enum":[4,19],"final":[6,13],"fr\u00f8yshov":4,"function":[2,4],"garc\u00eda":4,"gesch\u00e9":4,"j\u00fanio":4,"jo\u00e3o":4,"kate\u0159i\u0148\u00e1k":4,"kl\u00e4ger":4,"landh\u00e4u\u00df":4,"lo\u00efc":4,"long":[2,6,7,17,19],"lu\u00ed":4,"m\u00fcller":[3,4],"mat\u00eda":4,"micha\u0142":4,"mickevi\u010diu":4,"mirc\u0259lal":4,"montan\u00e9":4,"montr\u00e9al":6,"new":[1,2,4],"null":2,"p\u00e9ter":4,"public":[2,3,6,17],"r\u00e4t":4,"return":2,"seri\u00e1l":11,"short":[7,8],"switch":[2,7,17],"sz\u00e1sz":4,"t\u00f3th":4,"tom\u00e1\u0161":4,"toma\u0161t\u00edk":4,"true":[2,17],"try":2,"var":[2,6,22],"vin\u00edciu":4,"void":17,"while":[1,2],"xos\u00e9":4,_db:2,_get:17,_post:17,_server:[2,17],_session:17,_uri_scheme:7,a59e:17,aaleksanyants_at_yahoo:4,ab39:15,abd:4,abdulla:4,abeyrathna:4,abiword:8,abl:[0,2,4],abort:2,about:2,abov:[1,2,6,17],abram:4,abravo_at_hq:4,absenc:[2,6],absence:0,absolut:[2,17],abtract:19,academy:1,accept:[2,6,7],acceptabl:2,access:[0,2,4],accident:2,accommod:1,accompani:17,accomplish:[6,8,17],accord:2,accordingli:[2,6,19],account:[2,10,12,17],ace:1,achch1990_at_gmail:4,achchuthan:4,achiev:[2,6,12,17],acl:[2,7,17],acokaj_at_shkod:4,across:[2,7],act:20,action:2,actionlinksmod:2,activ:[2,6],activat:2,actual:[6,17,19],acunetix:6,adam:4,adaptation:1,add:2,addhandler:6,adding:8,addit:[2,4],addition:[2,3,17],additon:18,addon:6,addopt:8,addprefix:17,addr:2,address:0,addtype:6,adjust:2,adler:7,administ:10,administr:[2,4,6,10,12,15,17],admir:4,adnan:4,adobe:7,adopt:8,adriaenssen:4,adrian:4,adsbot:6,advanc:[6,15,17],advantag:6,advic:22,advis:[2,17],aes:[2,17],affair:1,affect:[2,6],afraid:17,african:1,after:2,again:2,agent:6,agre:2,ahmed:4,ahmedtek1993_at_gmail:4,aj_at_isit:4,ajax:4,ajaxifi:4,ajaxific:4,aka:17,ako:4,alan:4,albanian:4,albb0920_at_gmail:4,albert:4,alberti:4,alberty_at_neptunlab:4,albiol:4,aleksany:4,alemoretti2010_at_hotmail:4,ales_at_hakl:4,alessandro:4,alex:4,alexalex:4,alexand:[3,4],alexandr:4,alexei:4,alexi:4,alexrohleder96_at_outlook:4,algeri:4,algi:4,algorithm:[2,7,17],alia:4,align:6,alioglu:4,all:[2,5,6,7,8,10,12,13,15,17,18,19,22],alloc:2,allow:[1,2,4],allow_url_open:14,allowarbitraryserv:[2,17],allowdeni:[2,6,17],allownopassword:[2,17],allowoverrid:6,allowroot:[2,17],allowthirdpartyfram:2,allowuserdropdatabas:2,along:[3,17],alpha:6,alreadi:2,also:[0,2,4,6,7,8,10,12,15,16,17,19,22],alter:[2,6,7,10],altern:[2,6,17],although:[2,4,6],alvar:4,alwai:2,alwaysexpand:2,amalesh:4,amaral:4,amihaita_at_yahoo:4,amir:4,ammar:4,among:[2,8],amount:[1,2,6,19],analys:4,analyz:[2,4,6],ander:4,andersen:4,andika:4,andika_at_gmail:4,andrea:4,andrei:4,android:20,andrzej:4,andrzej_at_kynu:4,angular:2,ani:[0,2,4],ankit:4,ann:4,announc:[15,17],announcement:15,anonym:[2,6],anonynuin:4,anoth:[1,2,6,7,13,17],answer:7,anticip:7,antiviru:6,any:[0,2,3,6],anybodi:17,anyon:17,anyth:[6,17],anywai:6,apach:2,apache2:6,apache_http_serv:7,apart:6,apc:6,api:[2,6,7],appear:[0,2,6,15],append:[2,19],apple:7,appli:[2,6,13,17,19],applic:[1,2],applicat:[6,7],applytransform:19,approach:[2,20],approxim:[2,6],arabic:4,arben:4,arbirari:18,arbitrari:[2,6,17],arbitraryserverregexp:2,architectur:6,archiv:[6,7,17],archive:[7,8],area:[1,2],arg_separ:6,argument:6,ari:4,arifianto:4,armel:4,armenian:4,around:[2,6,17],arrai:[2,6],arrow:6,articl:[6,8,11],artyom:4,asc:[2,17],ascend:2,ascii:2,ash:4,ashkan:4,ashraf:4,ashutosh:4,ask:2,aso:4,aspect:[2,14,22],assign:[2,6],associ:2,assum:[2,6,17],astarita:4,asuni:4,attach:[2,7],attack:2,attemp:17,attempt:[2,6,15,17],attent:17,attila:4,attribut:8,atul:4,atulpratapsingh05_at_gmail:4,atvejis_at_gmail:4,auth:2,auth_http_realm:2,auth_map:17,auth_typ:[2,17],authi:20,authlog:[2,17],authlogsuccess:2,authnam:17,author:[2,4,6,8,17,18],authrequest:17,authtyp:17,authuserfil:17,auto:2,auto_incr:[6,8],auto_increment:6,autocomplet:2,autoconnect:2,autodetect:2,autom:[4,17],automat:[0,2,4],auxiliari:17,avail:[2,3,6,7,13,15,17,19,20,22],availabl:2,availablecharset:2,averag:6,avoid:[2,6,8,17],awai:6,awar:[0,6,17],axel:4,axi:[1,6],ayush:4,azerbaijani:4,azevedo:4,azzabi:4,b313:17,b947:15,b980:15,backend:[6,17],background:2,backquot:6,backslash:6,backspac:10,backward:[2,18],bacon:20,bad:[6,17],badalo:4,badalo_at_sapo:4,baiduspid:6,bailout_on_error:6,bakondi:4,balanc:2,ban:6,bansod:4,bao:4,baophan94_at_icloud:4,barri:4,base:[2,4],batch:10,bbedit:17,bd92:17,becaus:[2,4],beck:4,beck_at_web:4,becom:6,been:[2,5,6,8,17,18],befor:[2,6,8,15,17,18],begin:[2,6,8,17],behavior:2,behaviour:[2,6,17],behind:[2,6],belarusian:4,believ:6,bellon:4,belong:17,below:[0,2],beneath:6,benefit:[2,6],benjamin:4,bennetch:17,benni:4,berkelei:2,bernard:4,best:[6,8,17],better:6,between:[0,1,2,6],beyond:17,biesaga:4,big:2,bigdump:6,bigger:[6,8],bimal:4,bin:[4,6],binari:2,binary:2,bisht:4,bit:[2,17],bits_at_gmail:4,bkehayov_at_gmail:4,blagynchy_at_gmail:4,blank:2,blanks:19,blob:2,blobs:2,blobstream:4,block:[2,6,7,17],blog:8,blowfish:[2,4,7],blowfish_:7,blowfish_secret:[2,17],bluthardt:4,bob:[2,6],bodi:17,bogor_at_gmail:4,book:[7,8],bookmarkt:2,bool2text:2,boost:2,bora:4,borg:4,borge947_at_gmail:4,borggrev:4,borrow:4,bot:6,botelho:4,both:[1,2,6,8,13,16,17],bottom:[2,6,12,13],bound:19,boutel:7,box:0,boyan:4,branch:17,braschi_at_outlook:4,bravo:4,brazil:4,breakag:17,broader:7,broken:6,browsemarkeren:2,browsemime:2,browsepointeren:2,browser:2,bruce:7,bruguera:4,bruno:4,brunomendax_at_gmail:4,bskim45_at_gmail:4,buddika:4,buffer:[2,6,19],bug:[2,4],bugfix:4,buggi:[6,17],bugzilla:6,build:[2,6,17,19],builder:4,built:[4,6],bulgarian:4,bum:4,bumsoo:4,bunch:6,burak:4,busi:7,bussier:4,button:[0,2,6,7,17,18,20],buz_at_gmail:4,bypass:[2,6],bz2:2,bzip2:[2,6,7],bzipdump:2,c0ee:17,cach:[2,6,17],calcul:8,call:[2,6,7,10,13,17,19],calvo:4,can:[0,1,2,3,5],canada:6,cannot:2,capabl:7,captcha:[15,17],captchaloginprivatekei:[2,17],captchaloginpublickei:[2,17],car:8,car_id:8,care:[6,19],carefulli:2,casotti:4,casotti_at_uol:4,cat:4,catalan:4,categori:13,category_id:13,caus:2,caution:17,cav:4,cbb74bc:17,cdac1234_at_gmail:4,cdba:2,ce73:17,ce75:17,cedric:4,cell:[2,6,7],center:[6,17],centos:17,central:[2,4],central_column:2,cert:2,certain:2,certfic:2,certif:[2,17],certifi:17,cf2a:17,cfg:[2,6,8,12,13,17,18,19,20],chanaka:4,chanc:2,chang:[2,4],changelog:7,chao:4,chaovavanich:4,chapeaux:4,chapter:[2,6,17],charact:2,charanyogeshwar_at_gmail:4,charedit:2,charg:2,charset:[2,4,6,17,19],chartextareacol:2,chartextarearow:2,chat:1,chathuranga:4,chaudhari:4,check:[2,3,4,5,6,7,10,12,17,19,22],checkbox:[1,2],checkconfigurationpermiss:2,checklink:6,chee:4,cheng:4,chien:4,chimera:6,china:4,chines:4,chirayu:4,chirip:4,chiyokawa:4,chiyokawa_at_gmail:4,chmod:[2,6],choic:[2,8,17],choos:[1,2,6,17,19],chose:[6,17],chosen:[2,6],chown:[2,6],chri:4,chrisj_at_ctel:4,christoff:4,christoph:4,chrome:20,chrysler:8,cidr:2,cipher:[2,7],circumv:[2,6],cj_at_gmail:4,claimed_id:17,clanboy_at_163:4,clase:2,classless:2,clau:4,claus:2,clear:[6,17],click:[1,2],clickabl:2,clickjack:2,client:2,client_:7,clipboard:[2,6],clone:17,close:[2,6,17],clshttp:6,clue:6,cma:4,cocerhan:4,cochran:4,code:[2,4,6,7,15,17,18,20],codemirror:2,codemirroren:2,collaps:2,collat:2,collect:7,colognian:4,colon:6,color:2,column_:7,column_com:2,column_info:[2,19],columns:6,columns_priv:6,com:[2,3,4,6,7,8,14,17,19],combin:[2,6,8],come:[2,3],comma:[7,8,17],command:[2,6,17,20],comment:[0,2,4],commiss:7,common:[2,6,7,17,20],common_gateway_interfac:7,commonli:[7,8],commun:[7,10],compar:1,compat:[2,4,6,7,8,13,14,17,18,22],compil:6,complain:17,complet:[2,4],complex:[0,2,10],compliant:4,complic:2,compos:[2,3,9],comprehens:8,compress:2,compressonfli:[2,6],comput:[7,17],computation:8,computer_sci:7,concept:[4,10],concern:[8,15],condit:6,conf:6,confer:4,config:[2,6,16],configur:0,confirm:[2,6,20],conflict:6,conform:7,confus:6,connect_typ:2,consecut:6,consequ:2,consider:[1,17],consist:[6,18],consoleenterexecut:2,constanti:4,construct:[0,6],consult:[8,19],consum:7,contact:[6,15],contain:2,container_nam:17,content:2,context:6,continu:[2,17],contol:7,contrib:17,contribut:[4,5,6],contributor:4,control:[2,4],control_:2,control_ssl:2,control_ssl_ca:2,control_ssl_cert:2,control_ssl_kei:2,controlconnect:15,controlhost:2,controlpass:[2,6,17],controlport:2,controlus:[2,6,17],convei:7,conveni:17,convent:19,convers:[2,4],convert:6,cookie:2,coordin:[2,6],copi:[2,3],corazza:4,corazza_at_wanadoo:4,core:[2,7],correct:2,correctli:[2,6,8],correspond:[2,6,19],corrupt:[2,6],cost:1,costa1988sv_at_gmail:4,costel:4,could:2,count:[2,6,17],countri:6,country_cod:6,cours:[2,6],cover:17,coverag:6,craft:15,crawler:6,crawleradmin:6,creat:[2,4],create:[2,6,7],create_t:17,creation:[2,4],creator:4,credenti:[2,17],credit:3,criteria:6,cross:[2,7],cryptograph:[7,17],cspallow:2,css2:4,css:[2,4,18],csv_column:17,ctrl:[2,6],ctype:14,curl:[6,14],current:[1,2,4,6,7,10,13,15,17,18,22],currentqueri:2,cursor:[2,6],custom:1,cut:2,cve:15,cvs:4,cwlin0416_at_gmail:4,cybot_tm_at_us:[3,4],czech:[2,4],d3xter_at_us:4,da2n_s_at_yahoo:4,da68:15,da68ab39218ab947:15,dadan:4,dalibor:4,dan:4,daniel:4,danilo:4,danish:[4,8],danorse_at_gmail:4,dark:2,darkthem:2,darlow:4,dash:[2,6],data:[1,2],databas:0,database:[2,6],database_trigg:7,databaseinterfac:15,databases:6,databs:6,date:[1,2,6,8,17],dateformat:2,datetime:2,dave:4,david:4,davidson:4,db1:[2,17],db2:[2,17],dbase:6,dbconfig:17,dbf:6,dbg:[2,20],dbhost1:17,dbhost2:17,dbhost3:17,dbhost:17,dbms:7,dcbf:17,dckyoung_at_gmail:4,dd0:17,ddrmoscow_at_gmail:4,deactiv:[2,6],deal:7,debian:2,debug:[2,4],decemb:4,decid:[2,6],declar:[6,8,19],decreas:8,decrypt:6,default_socket:6,defaultconnectioncol:2,defaultentrypoint:17,defaultforeignkeycheck:2,defaultfunct:2,defaultlang:2,defaultquerydatabas:2,defaultqueryt:2,defaulttabdatabas:2,defaulttabserv:2,defaulttabt:2,defaulttransform:[2,19],defeat:2,defin:[2,6,7,12,17,19],definit:[2,6,11,13,18],degrad:6,deki:4,deky40_at_gmail:4,delai:6,delayed:6,delet:[2,4],delimit:2,delisl:[3,4,11,17],delislma_at_collegesherbrook:4,delorm:4,demo:[2,8,17],demonstr:[2,20],den:4,deni:[2,4],denni:4,depend:[2,6,8,17,20,22],deploi:17,deprec:2,depth:6,der:4,derek:4,desc:2,descend:2,descipt:18,describ:[2,7,15,16,19],descript:[6,8,17,18,19],designer_set:2,desir:[2,6,7,17],desktop:6,destin:[2,7],detail:[2,3,6,15,17],detect:[2,4,6,17],determin:2,dev:[2,6,7,8,17],dev_at_gmail:4,deven:4,devenbansod:4,devic:7,dhananjai:4,dhima:4,dhtml:4,dhundhara:4,diagnos:6,diagram:6,dialog:[6,8,17],dialogu:6,did:[2,6],didn:2,die_error:17,dieter:4,differ:[2,4],differenti:6,difficult:6,difficulti:10,digest:6,dimension:7,dingo13_at_gmail:4,dingo:4,dinosaur:1,diprofinfin:4,dir:[6,17,19],direct:[2,6,17,19],directli:2,dirnam:17,disabl:[2,6,17,20],disable_emodifi:6,disable_funct:2,disableis:2,disablemultitablemainten:2,disableshortcutkei:2,disallow:[2,17],disappear:[2,6],discov:17,discuss:6,disorderman:4,disorderman_at_qq:4,displai:[0,1],display_error:[6,17],displayserverslist:2,distanc:1,distinct:6,distribut:[2,3,4],disturb:17,div:17,divid:7,djh1017555_at_126:4,dll:6,dnighttv_at_gmail:4,doc:[2,6,7,8,17],docker:9,doctype:[6,17],document:2,documentclass:[6,8],doe:2,doesn:2,dom:4,domain:[2,6],domainnam:17,domen:4,don:2,donavan_at_hotmail:4,donavan_martin:4,done:[0,2,6,17,20],dongl:20,dongyoung:4,donwload:17,dorning:6,dot:2,doubl:[2,6],dougla:4,douglaseccker_at_hotmail:4,dovi:4,dovyda:4,down:2,downgrad:[6,17],download:[2,3,6,10,15,16,17,18,22],dozen:20,drag:[6,8,13],draw:1,drawn:[1,19],dri:4,driven:17,driver:2,drizzl:4,drop:[2,4],dropdown:[0,6],due:[2,6,17],duguying2008_at_gmail:4,dump:[2,4],dure:[6,8,17],dutch:4,dynam:[4,6,7],each:[0,2,3],eas:[2,19],easi:[2,6,8,19],easier:[2,7,18],easiest:[6,17,20],easili:[1,2],eccker:4,echo:17,edgar:4,edgarsneims5092_at_inbox:4,edit:2,editor:[2,4,17],edjacobjunior_at_gmail:4,edlund:4,edlund_at_upright:4,edu:[4,17],eduardo:4,edward:4,edwin:4,edwin_at_yohanesedwin:4,eecyh:17,ef12:17,effect:2,effectiv:11,effici:[2,6],effort:15,efroys_at_gmail:4,egbrave_at_hotmail:4,egg:1,egorov:4,eilertsen:4,either:[2,6,8,15,16,17],ek_at_luna:4,ekio_at_gmail:4,electrotechn:7,element:[6,7,19],eliovir:4,eliovir_at_gmail:4,els:[0,2,4,6,17],elseif:17,email:[4,6,15],emb:8,embed:[2,8],emerg:17,emit:6,emphas:2,empti:2,enabl:2,enableautocompletefortablesandcolumn:2,enclos:2,enclose:6,encod:[2,4,6,17],encompass:7,encount:6,encrypt:2,encyclopedia:7,end:[0,2,6,8,17,19],endors:11,enforc:13,eng_at_globomail:4,engin:[6,7,8],engine:[6,7],english:[2,4,8],engstrom:4,enhanc:4,enlarg:6,ensur:[2,6,8,17],ensure:[2,6,17],enter:[0,2],enterexecut:2,enterpris:6,entir:[6,14,17],entri:[2,6,17],entrypoint:17,environ:[2,6,15],epel:17,equival:2,erik:[4,6],ero:4,erosakos02_at_gmail:4,error:[2,4],escap:[2,6,15],escapesr:15,eshin:4,especi:[0,2,6,17],espen:4,esperanto:4,esri:2,essam:4,est:17,estonian:4,esy_vb_at_yahoo:4,etc:[2,6,8,17,19],etienn:4,evalu:8,even:[2,3,6,8,10,12,17],event:[2,4,7,10],ever:6,everi:[2,6,7,15,17,20],everyon:4,everyth:[0,6,17],everytim:2,everywher:6,exabot:6,exact:[2,6],examin:6,exampl:0,exce:2,except:[2,6,17],exception:17,exchang:17,exclud:6,exclus:6,exectimelimit:[2,6],execut:[0,2],executor:6,exist:[2,4],exists:2,exit:17,expand:[0,2,6],expandtab:[2,17],expans:[0,2,6],expect:[2,6,8],expens:1,experi:8,experiment:2,explain:[2,6,13,17],explan:[6,17],explicit:2,explicitli:2,explod:17,exploit:15,explor:[6,15],explorer:2,export_templ:2,expos:17,exposur:17,express:[0,2,6,7],expression:7,ext:6,extens:[2,4],extensibl:7,extension:[6,7],extension_dir:6,extern:[2,15,17,22],external:[2,9],extra:17,extract:[6,17],eyal:4,f188:17,facil:2,fact:[2,6,7,17],factor:[2,9,17],fadhiil:4,fadhiilrachman_at_gmail:4,fail2ban:[15,17],fail:2,fall:13,fallback:2,fals:[2,6,17,22],famma:4,fando:6,fashion:2,fast:[6,7],fastcgi:7,fathi:4,fauveau:4,fauveau_at_globali:4,favicon:17,favorit:2,fb5b:15,fcgi:17,featur:[0,2,4,5],feedback:4,feedfetch:6,feel:6,fefc:17,feryanto:4,few:[2,6,17],fewer:2,field:1,field_:7,file:[2,3,4],file_format:7,file_get_cont:17,file_template_databas:2,file_template_serv:2,file_template_t:2,file_upload:6,fileinfo:6,filenam:[2,6,19],files:8,filesystem:[2,6],filippo:6,fill:[2,6,15,17],filter:[2,6],filterlanguag:2,find:[0,2,6,11,15,16,17],fingerprint:[15,17],finnish:4,firewal:[2,6,17],first:[2,4,6,7,8,12,13,15,17,19,20],first__second__third:2,first_timestamp:2,firstlevelnavigationitem:2,fit:[2,4,6,8],fitness:3,fix:4,fixed:6,flag:2,flemish:4,floss:4,flush:6,fly:2,focu:2,folder:2,follow:[1,2,4],font:2,fontsiz:2,foo:6,foobar:17,food:1,footer:2,footnot:10,forc:2,forcessl:2,foreign:2,foreign_db:2,foreign_kei:7,foreign_key_checks:2,foreignkeydropdownord:2,foreignkeymaxlimit:[2,6],forget:[2,6,17],forgot:6,form:[2,4],format:[2,4],format_http:17,former:4,forth:19,forum:[4,6],forward:2,forwardfor:17,found:2,foundat:3,fpdf:4,fpm:17,fragment:6,frame:[2,4,6],framework:22,francesco:4,francisco:4,franco:4,free:[3,7,10,19],freeotp:20,french:4,frequent:2,freshli:17,fri:17,frisian:4,from:[0,1,2,4],front:6,frontend:17,ftp:[2,6,17],fujifilm:4,fulanodet:4,full:[0,2],fulli:[6,17],fun:11,func_char:2,func_date:2,func_number:2,func_spatial:2,func_uuid:2,fundawang_at_gmail:4,further:[2,4,6,16],furthermor:2,futur:[2,6],future_id:6,ga244_at_is8:4,gabriel:4,gailli:7,gain:12,galician:4,gamma:6,gandon:4,gandon_at_isia:4,ganeshtheone_at_gmail:4,garvin:[3,4],gatewai:7,gc_maxlifetim:2,gd2:[7,14],gd2availabl:2,gd_graphics_librari:7,geert:4,gener:1,genuin:17,geograph:8,geometri:[6,8],georgiev:4,geospati:8,geral_at_jonil:4,german:4,get:2,get_login_credenti:[2,17],getauthorizeurl:17,getinfo:19,getmessag:17,getmimesubtyp:19,getmimetyp:19,getnam:19,ghimir:4,giacobazzi:4,giacobazzi_at_ferrania:4,gigabot:6,gilli:4,gilli_at_gmail:4,giovanni:4,girish:4,gis:6,git:[2,9],github1_at_openaliasbox:4,github:[4,6,17],give:2,given:[2,6,8,12,15,17],global:[6,10,12,16,17,19],glund_at_silversoft:4,gmail:17,gmbh:6,gnauk89_at_googlemail:4,gnu:[3,7,17],goe:[2,6],goerick:4,goldfinger:1,good:[2,6,17,18,19],goodlinuxuser_at_chmail:4,google2fa:20,googlebot:6,got:[3,8],gov:4,gpg:17,gpl:3,grab:[6,17],gracefulli:6,grai:6,grant:[2,6,12,17],graphic:[7,10,13,17],great:[4,5],greatymh_at_gmail:4,greek:4,green:8,grid:2,gridedit:2,group:[2,6,7],grow:19,gryniuk:4,guarante:2,guess:2,gui_at_webseibt:4,guia:4,guid:[9,11],guilherm:4,gupta:4,gzencod:6,gzip:[2,4],gzipdump:2,haa:4,had:[6,17],hakl:4,hamann:4,hamzah:4,han:4,hand:[6,17],handbook:17,handl:[2,6,8,10,12,17,18,19],handler:4,hard:[2,17],harden:[6,15],harder:15,harm:2,harush:4,harvest:6,hassan:4,hate:17,haugom:4,hauk:4,have:[0,2,3,4,5],haven:6,head:17,header:2,heart:6,hebrew:4,height:2,heis:6,helder:4,hello:4,help:[2,4,5],henningsen:4,her:6,here:[2,6,8,17,19],heritrix:6,hermann:4,hex:2,hexadecim:2,hhvm:6,hibern:8,hick:[3,4],hidden:2,hide:[2,17],hide_db:2,hidestructureact:2,hierarch:4,hierarchi:2,high:[2,6,8],higher:6,highlight:2,hilal94_at_gmail:4,hilal:4,him:4,hindi:4,hint:[2,6,7],hiroshi:4,hisakawa:4,histor:2,histori:[2,4,17],hitowerdigit_at_hotmail:4,hkp:17,hofman:4,hofman_at_gmail:4,hold:[2,6],hole:2,holes:1,holm:4,home:[10,12],homepag:[6,19],hope:3,hopefulli:19,hord:4,horizont:[6,7],host:[2,4],hostnam:[2,6,7,17],hotp:20,hover:[2,13],how:2,howev:[2,6,17,19],href:17,hsts:17,htaccess:[2,6,7,17],html:[2,4],htmlspecialchar:17,htpasswd:17,http:[2,3,4],http_authorization:17,http_cooki:7,http_host:[2,6],http_post_vars:6,http_user_agent:6,http_x_forwarded_for:2,httpd:6,httponli:2,https:17,httrack:6,hudsonvsm_at_gmail:4,huge:2,hugu:4,human:[4,8],hung:4,hungarian:4,hungdx_at_gmail:4,huseyn:4,huseyn_esgerov_at_mail:4,hyperfido:20,hyperlink:13,hypertext:7,hypertext_transfer_protocol:7,ia_archiv:6,ian:4,ibennetch:17,ibm:6,iccrawler:6,ichiro:6,ico:17,icon:[2,4,6,12,17,18,19],iconv:2,iconvextraparam:2,id_new:6,idea:[2,4,6,15,17,18,19],identif:17,identifi:[1,2,6,7,17],identified:17,ie6:2,iec:[7,10],ifmodul:6,ignacio:4,ignor:2,ignore:6,ignoremultisubmiterror:2,igor:4,iis:2,illustr:1,imag:[2,7,10,12,14,17],img:18,impact:[2,6,17],impli:3,implic:17,implicitli:7,implment:7,important:6,impos:2,imposs:[2,17],improv:[2,4,6,7,8,10,15,17],improve:4,improvement:4,inact:[17,19],inc:[2,6,16,17,18,22],includ:[2,3,4],include_onc:17,include_path:6,inclus:6,incom:17,incomplet:2,incorrect:6,increas:[2,6,8],increment:2,independ:[4,6,7],index:[2,4],index_:7,indic:[8,17],individu:12,indonesian:4,indrajith:4,inexperienc:12,info:[2,3,4,6],info_at_opsbielani:4,info_at_robinvandervliet:4,inform:[2,3],information_schema:2,infrastructur:17,ini:[2,6,10,14],ini_set:17,initi:[2,6,17],initialslidersst:2,inlin:[4,6,14],inline:2,innodb:2,innodb_strict_mod:6,innov:7,input:[0,2,4],insecur:17,insensit:6,insert:[0,2,4],insertrow:2,inserts:8,inside:0,insist:17,instal:2,install:9,installat:[2,9],instanc:2,instead:[2,6,8,17],instruct:[2,6],inted:2,integ:2,integr:[2,4,10,17],integrat:6,intend:2,inter:[2,7],interact:[6,7],interchang:8,interest:[6,11],interf:6,interfac:[2,4,6,7,8,12,14,16],interfer:6,interlingua:4,intern:[2,6,8,13,17,19],internat:7,internet:2,internet_information_servic:7,internet_protocol:7,internetwork:7,interoper:8,interpret:[2,6],intext:6,intitl:6,intro:2,introduc:[2,4,6],introduct:9,invalid:[2,6,17],invit:5,invok:17,involv:[6,17],ion:4,ionut:4,ios:20,ip_address:7,ipmask:2,ipv4:7,ipv6:[2,7],ironpotts_at_gmail:4,is_str:17,isa:4,isaac:17,isapi:[6,7,17],ismae:4,ismael_at_gmail:4,isn:2,iso:[7,10],isp:2,isset:17,issu:2,issue:[6,17],italian:4,item:[2,6,7],itself:[0,2,6,17],itxiaopang:4,iusr_machin:6,ivan:4,ivan_at_mail:4,ivanlanin_at_us:4,jackson:4,jafar:4,jakobsen:4,jakobsen_at_gmail:4,jakub:4,jan:[4,17],jan_at_nrw:4,janhenrikm_at_yahoo:4,janni:4,januari:17,janussen:4,japanes:4,java:[6,8],javascript:[2,4],jayaratn:4,jconstanti_at_yahoo:4,jean:7,jedermann:4,jeev:6,jhaveri:4,jim:4,joan:4,joan_at_montan:4,job:17,joe:[4,6],john1db:6,john2db:6,john_at_panevo:4,john_db:6,johnson:[3,4],join:[2,4,6],jona:4,jong:4,jongdeok:4,jonsson:4,jonsson_at_norsjovallen:4,jordi:4,jose:6,josep:4,jozef:4,jpeg:[7,14,19],jpg:7,jqplot:1,jqueri:[1,3,6,14,22],jremes_at_outlook:4,jrzancan_at_hotmail:4,json:6,juha:4,juli:17,julian:[4,7],jump:[2,6],june:4,junior:4,jur:4,just:[0,2,4],k725:4,kang:4,kanji:4,kankanamg:4,kannada:4,kasperski:6,kasun:4,katerinak_at_gmail:4,kaushalya:4,kawada:4,kawada_at_den:4,kazi:4,keck:[3,4],keep:[2,6,17],kehayov:4,kei:[2,4],kelli:4,kempf:4,kept:[2,19],kettler:4,key:6,keybas:[15,17],keyr:[15,17],keys:13,keyserv:[15,17],khomutov:4,kick:6,kid:4,kiddi:6,kidsmart33_at_gmail:4,kiko:4,kim:4,kim_at_nhn:4,kind:[2,6,7,15],kindli:6,kingdom:4,kirillov:4,kissu:4,kit:[3,17,22],klau:6,kleemann:4,klokner:4,knowledg:6,known:2,kobayashi_at_gmail:4,koch:4,koch_at_enough:4,kollar:4,kollar_at_pg:4,kollmann:4,koo:4,koolen:4,korakot:4,korakot_at_inam:4,korean:4,kosit:4,kr_at_gmail:4,kraai:4,krasimir:4,kristjan:4,kristjanrats_at_gmail:4,kristof:4,kronsbein:4,krystian4842_at_gmail:4,krystian:4,kumari:4,kunishima:4,kuppelwies:4,kurdish:4,kurt:4,kurt_at_kh:4,kushagra4296_at_gmail:4,kushagra:4,kword:8,kyeong:4,kyungjun2_at_gmail:4,label:[0,1,6,7,15],lacina:4,ladisch:4,lang:2,languagetool_at_gmail:4,lanin:4,larg:2,larger:[2,6],largest:6,lari:4,lari_at_oesch:4,lass:4,lasse_at_mydom:4,last:[2,6,19],lastpass:20,later:[0,2,8,17],latest:[2,6,7,14,17],latter:2,latvian:4,lau:4,launchpad:17,laureano:4,laureano_at_gmail:4,laurent:4,layer:[1,7,16],layout:[2,6,10,13,18],lc_messages:2,ldi_:4,lead:[0,2,6],leak:2,learn:[8,11],least:[2,6,17],leav:[2,6,17,19],lee:4,leedermeister_at_gmail:4,left:[2,4,8],legal:2,legenhausen:4,leiding:4,lem9:17,length:2,lesli:4,less:[2,6,8,17],let:2,letter:[2,6,8],lettercas:6,level:[2,6,7,8,17],lib:[6,19,22],libiconv:2,librari:[1,2,3,4],libreoffic:8,libwww:6,libxml:14,license:3,lifetim:2,lighttpd:17,lightweight:8,like:[0,2,4],likewis:6,lima:4,limburgish:4,limitchar:2,lin:4,line_count:6,link:[1,2,4,6,10,12,13,15,17,18,19],link_id:13,linklengthlimit:2,linu:17,linux:[2,9],list:[2,4],listen:17,liter:[2,6],lithuanian:4,load:[2,4],loadabl:6,loader:6,loadfil:6,loadmodul:6,local:2,localhost:2,localis:8,localnet:17,localneta:2,localnetb:2,localnetc:2,lock:[2,6],log:2,logformat:6,login:2,logincookiedeleteal:2,logincookierecal:2,logincookiestor:2,logincookievalid:2,logincookievaliditydisablewarn:2,logo:[2,4],logout:[2,16,17],logouturl:2,lolo_at_phpheaven:4,longer:[2,17],longtabl:[6,8],longtext:2,longtextdoubletextarea:2,look:[2,6,7,13,17,19],lori:4,lose:2,loss:6,lossi:7,lost:2,lot:2,loup:7,low:[2,6],lower:[2,6],lower_case_table_nam:6,lscape:8,lsml_at_liv:4,ltr:17,lubo:4,lucen:6,lui:4,luisan00_at_hotmail:4,lund:4,m42:4,m_at_gmail:4,mac:2,mac_os_x:7,machin:2,maciej:4,maciejka45_at_gmail:4,macintosh:7,macof:4,made:[2,4,6,7,17],madhura:4,madlen:4,mai:[0,2,6,7,10,17,19],mail:7,main:1,mainli:7,maintain:[4,6,17],mainten:[2,4,10],major:[6,18],make:[2,5],make_id:8,maketitl:8,malai:4,malfunct:6,malgeri_at_gmail:4,malici:15,man:2,mani:[2,4,5],manipul:[2,7,8,10,17],manish:4,manual:[2,6,7],manufactur:[7,20],manuzhai:4,map:17,marc:[3,4,6,11,17],marc_at_infomarc:[3,4],marcel:4,march:4,marco:4,marconcini:4,marek:4,mari:2,mariadb:[6,10,14,17],marin:4,mark:[2,4,7],marker:8,market:7,markt:6,markup:[7,8],mart:4,martijn:4,martin:4,martin_at_vidn:4,martin_at_whistl:4,martinelli:4,martinelli_at_gmail:4,martyna:4,masahiko:4,master:[2,11,13,17],master_db:2,mat:4,match:[2,6,8,17,18],mathemat:8,mathia:4,matiasbellon:4,matter:13,matthia:4,matthias_at_bluthardt:4,max:6,max_allowed_packet:8,max_array_index_length:6,max_execution_tim:6,max_link:6,max_request_vari:6,max_totalname_length:6,max_travers:6,max_value_length:6,max_var:6,maxcharactersindisplayedsql:2,maxdblist:2,maxexactcount:[2,6],maxexactcountview:2,maxim:[4,8],maxime_at_fre:4,maximum:[2,6],maxnavigationitem:2,maxrow:2,maxsizeforinputfield:2,maxtablelist:2,maxtableuipref:2,mayb:6,mbstring:[2,7,14],mcrypt:[6,7],me_at_derrabu:[3,4],me_at_supergarv:[3,4],mean:2,meant:2,measur:[2,7],mechan:[2,4,6,17],media:6,mediapartn:6,medios:4,meet:17,memoranda:7,memori:2,memory_limit:[2,6],memorylimit:2,memoword_at_163:4,mendax:4,mendel:[3,4],mention:[2,6,17],menu:[2,6],merchantability:3,messag:2,met:17,meta:[2,17,19],metagerbot:6,method:[2,6,7,8,13,17,19],methodolog:7,michael:[3,4],michal:[3,4,17],michal_at_cihar:[3,4],michel:4,middl:2,might:[2,6,8,11,12,15,17,22],migrat:6,mike:4,mike_at_graftonhal:4,miko:4,mileag:8,mime:[2,4],mimetyp:2,mind:17,miner:6,minh:4,minim:[6,17],minimum:[2,6],minor:[4,6],minsizeforinputfield:2,minu:0,miquel:4,mirror:[6,17],misc:1,miss:2,missing:19,mit:[3,17],mitenem_at_outlook:4,mitig:[6,15],mix:2,mj12bot:6,mkdir:6,mkkeck_at_us:[3,4],mladenov:4,mmcach:6,mmcrawler:6,mmh15_at_windowsl:4,mobil:[4,20],mod:6,mod_gzip_item_includ:6,mod_proxi:6,mod_proxy_fcgi:[7,17],mod_rewrit:6,mod_ssl:6,mode:0,moder:4,modif:6,modifi:[2,3],modul:[6,7],moham:4,mokhtari:4,molnar:4,morai:4,more:[0,1,2,3],moreov:6,moretti:4,mosh:4,most:[2,4,6,7,8,10,13,17,19,20],mostli:[2,8],motuza:4,motuzas_at_gmail:4,mount:2,mous:[2,6,13],mousewheel:6,move:2,movement:1,mrdaniloazevedo_at_gmail:4,msie:6,msnbot:6,much:[6,19],muhammad:4,mulla:4,multi:[2,4],multidimension:8,multipl:1,multipurpos:7,multiselect:6,multiserverexample70518:2,multithread:7,murariu:4,murariu_at_yahoo:4,must:[0,2,6,8,10,12,13,17,20],my_db:2,myadmin:17,mydatabas:2,mydb1:17,mydb2:17,mydb:[2,6],mydump:8,myisam:[6,7,10],mynam:0,mysql_connect:6,mysql_db_serv:17,mysql_fetch_field:19,mysql_pconnect:2,mysql_root_password:17,mysql_upgrad:6,mysqladmin:6,mysqld:6,mysqli:[2,4],mysqlminvers:2,mysqlnd:2,myung:4,n8falke_at_us:4,nabin:4,naderi:4,naderi_at_gmail:4,naeem:4,nair:4,nakandala:4,nakrani:4,name:[0,2,4],nast3zz_at_gmail:4,nasti:15,nativ:2,natur:2,naturalord:2,navarro:4,navigationdisplaylogo:2,navigationdisplayserv:2,navigationhid:2,navigationlinkwithmainpanel:2,navigationlogolink:2,navigationlogolinkwindow:2,navigationtreedbsepar:2,navigationtreedefaulttabt:2,navigationtreedefaulttabtable2:2,navigationtreedisplaydbfilterminimum:2,navigationtreedisplayitemfilterminimum:2,navigationtreeenableexpans:2,navigationtreeenablegroup:2,navigationtreepointeren:2,navigationtreeshowev:2,navigationtreeshowfunct:2,navigationtreeshowprocedur:2,navigationtreeshowt:2,navigationtreeshowview:2,navigationtreetablelevel:2,navigationtreetablesepar:[2,6],navigationwidth:2,ne0x_at_us:4,nearli:2,necessari:[2,19],need:[0,2],neg:6,neglig:8,neil:4,neimani:4,neither:[2,6],neomo:6,nepali:4,nest:[2,4],net:[2,3,4,6,7,15,17,18],netcologn:6,network:[2,6,7,8,17],never:2,nevertheless:17,newer:2,newest:2,newli:[8,17],newlin:2,newsblog:6,next:[2,6,17],nginx:14,nhibern:8,nicola:4,niel:4,niemand:4,nijel:17,niko:4,niko_at_gmail:4,nikto:6,ninad:4,nisarg:4,nisargjhaveri_at_gmail:4,nitrotoll_at_gmail:4,nix:2,nnabinn_at_hotmail:4,no1:2,no2:2,noblob:2,nobody_at_gmail:4,node:7,nofoot:8,nohead:8,nokeepal:6,non:2,none:2,nontawat39_at_gmail:4,nopassword:2,nor:6,nordenberg:4,noreply_at_webl:4,normal:[2,4,6,17,19],norwegian:4,not_nul:19,notabl:7,notat:8,note:[0,2,6,17,19,22],noticias:4,now:[0,2],nowher:2,ntfs:2,number:[0,1,2],numer:[2,6],numfavoritet:[2,6],numrecentt:2,nutchcvs:6,nyu:4,obgzip:[2,6],object:[6,8,17,19],obrador:4,obtain:[2,15],occur:[2,6,17],oesch:4,off:[2,6],offer:[1,2,6,7,13,16,17],offic:7,office:8,offici:[6,11,17,19],oficial_at_gmail:4,often:[2,6,8,17],okai:17,old:[2,6,11,17],older:2,olivier:[3,4],olof:4,om_at_omni:[3,4],omar:4,omar_2412_at_l:4,omit:2,omniexplorer_bot:6,onc:2,once:[6,12,13,17,20],ondra:4,ondrasek:4,one:6,onli:[1,2,4],onlin:6,only:[2,6,13],only_db:2,open:[2,5,6,7],open_basedir:2,opendocu:7,opengis:4,openid:17,openid_messag:17,openid_relyingparti:17,openlay:6,openoffic:8,openssl:[14,17],oper:[1,2],operat:[6,8,17],opposit:2,opt:20,optim:[2,4],optimiz:6,option:1,optional:[2,8,17],order:2,ordinari:17,org:[2,3,4,6,7,8,17],organ:7,organis:7,orient:[2,6,7],origin:2,orion1979_at_yandex:4,orzkun_at_ageag:4,os_x:7,other:[2,4,5,6,7,8,9,10],otherwis:[0,2,6],our:[2,5,6,15,17,18,19],out:[2,5],output:[2,4,6,8,19],output_compress:6,outsid:[2,19],outsourc:4,over:[2,6,7,12,13,17],overcom:6,overlord666_at_gmail:4,overrid:[6,17],overview:[6,19],overwhelm:12,overwrit:2,overwritten:2,own:2,owner:[2,17],owneremail:6,ownerfirstnam:6,ownerlastnam:6,ownerphon:6,ownerphone1:6,ownerphone2:6,ownership:6,pack:6,packag:[2,6,9,17],packagist:17,packet:[7,8],page:0,pai:17,pair:[2,17],palid:4,palider_at_seznam:4,palstsiuk:4,pan:6,pandei:4,pandithawatta:4,pantola:4,papaz_p_at_yahoo:4,paper:[2,6],paragraph:6,param:[2,17],paramet:2,parent:[6,17],parind:8,parkourpotex_at_gmail:4,parsabl:8,parse_url:6,parser:[4,8],part:[2,6,7],partial:2,particular:[3,6,7,12,17],particularli:17,pass:[2,6,10,12,17,18,19],passhosthead:17,passphras:2,passwd:17,password:2,past:2,patch:[2,4],path:2,path_dir:17,path_to_phpmyadmin:6,path_to_your_phpmyadmin_directori:2,pathprefixstrip:17,patrik:4,pattern:2,paul:6,paulei:4,pavel:4,pbms:4,pcre:[2,7],pdf_page:[2,6],pdfdefaultpages:2,pdfpageheight:8,pdfpages:2,pdfpagewidth:8,pdo:8,pear:[4,7,17],pebbl:20,peccatt:4,pedro:4,peer:2,pem:2,pencil:12,peopl:[4,5,6,10,22],per:[2,6,7],perekupka:4,perfect:[2,17],perform:[2,6,10,12,14,15,17],perhap:17,period:17,perkontevs_at_gmail:4,perl:[6,7],permiss:[2,6,12,17],permit:[2,12],persian:4,persist:[2,16],persiste1_at_gmail:4,persistentconnect:2,person:[2,6,7,17],person_nam:6,petbre:6,petdob:6,pete:4,peter:4,petnam:6,petr:4,pettyp:6,pgp:[15,17],ph3n1x:4,phan:4,phillip:4,phmyadmin:17,phone:20,photograph:7,php3:4,php4:[4,6],php5_modul:6,php5apache2_2:6,php5t:6,php:[2,4],php_mysqli:6,php_self:[6,17],phpinfo:[2,6],phpinidir:6,phpmy:6,phpmyadmin:[0,1,2,3,4,5],phpmyadmin_at_zweisteinsoft:4,phpmyadmin_x:17,phpmyadminovi:11,phpmyadnin:18,phpmysqlformgen:4,phpseclib:4,phpwizard:[4,6],piankov:4,pick:6,piec:15,pikto:6,piller:4,pin:2,pink:6,piotr:4,pipe:2,pistej2_at_gmail:4,pistej:4,place:[2,6,17],placehold:0,placella:4,placement:6,plai:17,plain:[2,6,17,19],plamen_mbx_at_yahoo:4,plan:8,platform:[2,6,7],plathei:4,pleas:[2,6,7,8,15,17,19,22],plesk:2,plot:6,plu:[4,6],plug:19,plugin:[4,6,8,19],pma:[2,17],pma__bookmark:2,pma__central_column:2,pma__column_com:2,pma__column_info:2,pma__designer_set:2,pma__export_templ:2,pma__favorit:2,pma__histori:2,pma__navigationhid:2,pma__pdf_pag:2,pma__rec:2,pma__rel:[2,6],pma__savedsearch:2,pma__table_coord:2,pma__table_info:[2,6],pma__table_uipref:2,pma__track:2,pma__us:2,pma__userconfig:2,pma__usergroup:2,pma_absolute_uri:17,pma_arbitrary:17,pma_at_sebastianmendel:4,pma_combin:6,pma_db:17,pma_dbi:4,pma_host:17,pma_hosts:17,pma_password:[6,17],pma_port:17,pma_ports:17,pma_single_signon_cfgupd:17,pma_single_signon_error_messag:17,pma_single_signon_host:17,pma_single_signon_messag:17,pma_single_signon_password:17,pma_single_signon_port:17,pma_single_signon_us:17,pma_user:17,pma_usernam:6,pma_verbose:17,pma_verboses:17,pmaabsoluteuri:[2,6,17],pmadb:2,pmahomm:[2,18],pmanorelation_disablewarn:2,pmapass:[2,17],pmasa:[6,15],pmatest:2,png:18,point:[2,6,7,17],polici:2,polish:4,popcorn:4,popul:8,popular:[7,8],port:2,port_:7,portabl:7,portable_document_format:7,portion:2,portnumb:2,portugues:4,pose:15,posit:[6,7],possibl:[2,5],post:[2,6,17],post_max_s:6,potenti:2,power:8,pozzato:4,practic:2,pragmarx:20,pratap:4,pre:2,preappend:2,precedessor:7,preconfigur:8,predatorix_at_web:4,predefin:10,prefer:[2,4],prefix:[2,6],preform:15,prejudice:1,preliminari:8,prepar:[2,7,15,17],prepend:19,preprocessor:7,present:[2,6,8,12,15,17],press:2,pretti:4,prevent:2,previou:[2,6,17],price:8,primari:[2,4],primary:[6,7,13],print:4,printer:4,printview:[2,4],prior:17,privaci:17,privat:[2,6,20],privileg:[2,4],privileges:17,probabl:[6,17,22],problem:2,problemat:6,procedur:2,processor:8,procs_priv:6,produc:2,product:[6,8,17,20],profession:7,program:[2,3,6,7,8,15],prohibit:2,project:[2,4],prompt:[2,17],proper:[2,6,8,10,13,17,19],properli:[2,6,13,17],properti:[2,4,6,19],propertiesnumcolumn:2,propos:[6,10],protect:2,protectbinari:2,protocol:2,provid:[2,6,7,10,12,17,19,20,22],provok:15,proxi:2,proxypass:[2,6],proxypassrevers:6,proxypassreversecookiedomain:6,proxypassreversecookiepath:6,proxyurl:2,proxyus:2,pruett:4,przemo:4,przybylski:4,psbot0:6,pub:15,publi_at_web:4,publicli:2,publish:[2,3,15,17],pull:[2,17],pundalik:4,punjabi:4,purodha:4,purpos:[2,7,8,15,20],purpose:3,put:2,python:6,qbe:[4,10],qiang:4,qualifi:17,qualiti:8,queri:[0,1],queryhistorydb:2,queryhistorymax:2,querystr:17,quick:[2,6,7,9],quickli:8,quit:[2,4,6,18,20],quot:6,qyz:4,r9uk:17,ra4_at_openmailbox:4,rachim:4,rachman:4,radio:[6,7],rafael:4,raghuram:4,rai:4,raj:4,rajandran:4,random:[2,4,15],rang:2,raouf:4,rare:6,rate:15,rather:[2,6,8,12,17],ratio:14,ratschil:[3,4],raul:4,raul_at_wservic:4,ray_at_datahui:4,reach:[17,19],read:[2,6,8,10,17],readabl:[6,8],readi:17,readme:17,real:[2,6,13],real_password:17,real_us:17,realli:[2,6,17,20],realm:[2,17],rearrang:6,reason:[2,6,14],recal:2,recaptcha:[2,6,14],receiv:3,recent:[2,6,7,8],recod:2,recode_str:2,recodingengin:2,recommend:[2,6,7,8,14,17],record:[2,7,8,12,13],recov:6,recreat:6,recv:17,redesign:4,redhat:6,redirect:2,redistribut:[3,22],refactor:4,refer:[2,6,7,13],referenti:[2,10],reflect:7,refman:[2,6,7,8],refresh:2,regard:[2,10,19],regardless:17,regener:15,regex:17,regul:8,regular:[2,4,7],regullar:2,reject:2,rel:[2,6,17],rel_countri:6,rel_person:6,rel_town:6,relat:[0,2,4],relationaldisplai:2,releas:[2,4,6,9,15],relev:2,reli:17,reliabl:[2,6,8,17],relyingparti:17,relyingparty_result:17,remain:[0,2],reme:4,rememb:[0,2,17],remembersort:2,remot:[2,7,17],remote_addr:2,remote_user:6,remov:[2,4,6,10,12,17,18],renam:2,rename:2,renato:4,renatomdd_at_yahoo:4,render:[8,15],rental_r:1,reorder:6,repeat:[2,6],repeatcel:2,replac:[0,2,6,17,18,22],replace:2,replacement_cost:1,repli:17,replic:[4,6],report:[2,4,6,9],repositori:[3,7,17,20],repres:[2,6,7],represent:4,reproduc:6,reqirep:17,request:2,request_for_com:7,request_method:6,request_uri:[6,17],requir:2,resav:17,research:7,reserv:2,reservedworddisablewarn:2,reset:6,resid:6,resiz:6,resolut:7,resolv:6,resourc:7,respect:[3,6],respond:6,respons:[6,7],restart:[6,17],restrict:2,result:[0,1,2],retainquerybox:2,retriev:[2,17],return_to:17,returnto:17,revenu:1,revers:2,review:17,revis:[2,6,7],revistafammatvmus:4,revok:2,rewrit:[6,17],rewritebas:6,rewritecond:6,rewriteengin:6,rewriterul:6,rewrot:4,rex:4,rfc1867:6,rfc2616_header:6,rfc:[6,7,10],ribeiro:4,ribeiro_at_gmail:4,ricardo:4,rifthi:4,rifthy456_at_gmail:4,right:2,risk:[2,6],rob:6,robbat2_at_us:[3,4],robin:[3,4],rocha:4,rocha_at_zoho:4,rodrigo:4,rodrigu:4,rohled:4,rohmberg:4,rohmberger_at_hotmail:4,romanian:4,ronni:4,ronniesimonf_at_gmail:4,roohan:4,roohan_cena_at_yahoo:4,root:2,roszatycki:4,rouslan:4,rout:[2,17],router:17,routin:[2,4],row:[1,2,4],row_:7,row_format:6,rowactionlink:2,rowactionlinkswithoutuniqu:2,rowactiontyp:2,rsa:17,rubinov:4,ruiz:4,rule:[2,6,17,19],ruleant_at_us:4,run:[0,2],russian:4,rutkowski:4,rwx:[2,6],saad:4,safe:2,sai:0,sailboat:4,sakamoto:4,sake:6,sakila:6,saleh:4,salvadoporjc_at_gmail:4,sam_at_gmail:4,same:[0,1,2],sampl:[2,6,8,17],samyoul:20,sander:4,sandro123iv_at_gmail:4,sandro:4,sanit:6,santana:4,santana_at_gmail:4,sapi:2,sarna:4,sascha:4,save_path:6,savecellsatonc:2,savedir:[2,8],savedsearch:2,saverio:4,saw:6,sbin:17,scale:6,scan:[6,20],scanner:6,scenario:[6,17],schaefer:4,schema:[2,4],scheme:[2,6],schneier:7,scientif:8,scooter:6,scope:17,scorpio_at_gmail:4,scp:6,scratchboard:[4,6],screen:[8,17,18],screenshot:18,script:[2,4],scriptalici:8,search:[2,4],searchabl:17,sebastian:[3,4],sebastian_at_sgundersen:4,secion:15,second:[2,6,12,19,20],secret:2,section:[1,2,5,6,17,19],secur:2,secure_cooki:17,secure_sockets_lay:7,securer:4,see:[0,2,3,4],seealso:7,seekbot:6,seem:2,seen:2,segment:2,seibt:4,select:[0,1,2,4],selector:2,selenium:4,self:2,send:2,senderrorreport:2,sens:19,sensi:6,sensit:[0,2,6,15],sent:[2,4],seo:6,seoma:6,seongki:4,seosearch:6,separ:[2,4,6,7,8,17],separated_valu:7,sequenc:7,serbian:4,seri:1,serial:8,seriou:6,serv:[7,17],server_:7,server_address:2,server_at_gmail:4,server_name:17,server_port:17,serverdefault:2,serverlibrarydifference_disablewarn:2,servic:[2,6,7,17],session:[2,6,14,16,17],session_get_cookie_param:2,session_id:17,session_nam:17,session_save_path:[2,17],session_set_cookie_param:[2,17],session_start:17,session_write_clos:17,sessionsavepath:[2,17],sessiontimezon:2,set:1,set_magic_quotes_runtim:17,setenvif:[6,17],setfacl:2,setia:4,setia_at_gmail:4,setinputfilt:6,setlength:8,setoutputfilt:6,setup_config_file:22,setup_dir_writable:22,sevdimali:4,sevdimaliisayev_at_mail:4,sever:[2,3,6,7,8,13,16,17,19,20,22],sevillano:4,seward:7,shahrabani:4,shall:13,shameem:4,shapefil:2,share:[2,17],sharifov_at_programm:4,she:6,sheet:8,shell:6,sherbrook:6,shift:[2,6],shin:4,ship:[7,17,18,22],shirian:4,shirian_at_gmail:4,shortcut:[2,6,9],shorten:6,shorter:[2,6],should:[2,3,4],shouldn:17,show:[2,6,15,17],show_pag:17,showall:2,showasphp:2,showbrowsecom:2,showchgpassword:2,showcolumncom:2,showcreatedb:2,showdatabasesnavigationastre:2,showdbstructurecr:2,showdbstructurelastcheck:2,showdbstructurelastupd:2,showfieldtypesindataeditview:2,showfunctionfield:2,showgitrevis:2,showhint:2,shown:[1,2],showphpinfo:2,showpropertycom:2,showserverinfo:2,showsql:2,showstat:2,shp:6,shutdown:6,side:[2,7,17],sign:[2,17],signatur:17,signific:4,significantli:8,signoncookieparam:[2,17],signonscript:[2,17],signonsess:[2,17],signonurl:[2,17],silent:6,silva:4,simecek_at_gmail:4,similar:[2,6,8,13,15],similarli:6,simon:4,simoncini:6,simpl:[1,2],simple2fa:[2,20],simpli:2,simplifi:[4,12],sinc:[1,2,4,6,8,14,17,20],singh:4,singl:2,sinhala:4,site:[2,6,11],sitesearch:6,situat:[6,7,17],siu:4,size:2,skill:6,skip:[17,22],skiplockedt:2,skshin_at_gmail:4,slash:2,slider:2,slovak:4,slovenian:4,slow:2,slowdown:6,slurp:6,small:[2,6],smaller:[2,4,6],smart:[2,4],smarte_at_gmail:4,smita:4,smooth:1,smtp:4,snap:2,snappi:6,snapshot:2,snippet:2,socket:2,softwar:[3,6,7,8,10,17,20,22],software_extens:7,solut:[6,17,20],solv:6,some:[2,4],someth:[2,6,13,15,17],somewher:[6,15],somthanat:4,somthanat_at_gmail:4,song:4,soom:4,soon:0,sora:4,sora_at_tiscali:4,sorani:4,sort:[2,6,7,8],soulard:4,sourc:[2,4,5,6,7,9],sourceforg:[3,4],souza:4,space:[0,2,6],spaces:0,spanish:4,speadsheet:8,special:[0,2],specif:2,specifi:[1,2,4],speed:[2,6,8,17],spl:14,split:[2,6,8,19],sponsor:6,spreadsheet:6,sql:[0,1],sqllog:2,sqlqueri:2,sqrt_at_entless:4,src:[6,17],srnka:4,ssl_ca:[2,17],ssl_ca_path:[2,17],ssl_cert:[2,17],ssl_cipher:[2,17],ssl_kei:[2,17],ssl_verifi:[2,17],ssloption:6,sta:4,stabl:6,stack:1,standard:[2,4,6,7,8,14,20],star:[2,6],start:[1,2,4],starthistori:2,startup:[2,6],state:2,stateless:15,statement:[2,6,10,17],station:6,statist:[2,4],statu:[2,4,6],status:2,stdenvvar:6,stdpass:2,stduser:2,ste:4,stefan:4,stefano:4,step:[6,17],steve:4,steven:4,still:[2,12,15,17],stock:17,stokkan:4,stop:[2,6],storag:[0,2,6,7,9,13,15,16],stored_procedur:7,straka3_at_gmail:4,straka:4,strftime:6,strict:2,strijbol:4,string:[0,2,4],strip:[0,2],strlen:17,strongli:[2,14],stuff:4,stuffit:17,style:[2,6,17,18],sub:[6,15],subdirectori:2,subfold:[6,17],subject:[2,6],sublevel:2,submiss:2,submit:[2,6,17],subpackag:[2,17],subroutin:7,subset:10,substitut:6,substr:2,subtyp:19,success:[2,6,15,17],successfulli:6,suffic:17,suffici:6,suffix:[2,17],suggest:[4,6,14,15,17],suhardi:4,suhosin:2,suhosindisablewarn:[2,6],suit:[6,7,17],suitabl:8,sum:6,summer:4,sun:4,superus:[12,17],suppli:[2,17],support:[1,2,4],suppos:6,suppress:2,supun:4,surbakti:4,sure:2,surfac:15,surround:6,svalekja:4,svec:4,sven:4,swedish:4,symbol:2,symlinksifownermatch:6,symmetr:7,sync:4,synchron:4,synoobot:6,syntax:[2,6],sysadmin:[2,6],syslog:[2,17],system:[2,4,6,7,8,16,17,18,22],szsilva_at_gmail:4,t10:2,t3if_at_ladisch:4,tabbrows:6,table:[2,6,7],table_:7,table_coord:[2,6,13],table_info:2,table_nam:6,table_uipref:2,tablenam:6,tablenavigationlinksmod:2,tableprimarykeyord:2,tables:6,tables_priv:6,tablesepar:6,tablettws_at_gmail:4,tabsmod:2,taceloski:4,tag:6,taiwan:4,take:[1,2,6,10,19],taken:6,talk:17,tamil:4,tamsjadi:4,tape:7,tar:[7,17],tar_:7,tarbal:17,target:6,tarzq28_at_gmail:4,task:10,tbl_chang:4,tbl_creat:4,tbl_dump:6,tbl_row_delet:6,tbl_select:4,tbl_structur:19,tcp:[2,6,7],tcpdf:[4,6,7],tczzjin_at_gmail:4,team:[2,15,17],technolog:7,tecnick:4,tecnickcom:6,telekom:6,tell:[2,6],telnet:6,tempdir:[2,6,17],templat:[2,17],template:19,template_abstract:19,temporari:[2,6,17],temporarili:[2,6,16],temporary:6,term:3,terri:4,test:[2,4,6,17,20,22],tex:[6,7,8],text:0,textarea:2,textareaautoselect:2,textareacol:2,textarearow:2,textbox:12,textfield:19,textimagelink:2,textlink:2,textual:[6,8],thai:4,than:[2,4],thank:[4,6],thei:[2,4],them:2,themedefault:[2,18],thememanag:[2,18],themenam:2,themeperserv:2,themselv:2,therefor:2,thi:[0,1,2,3,4],thiago:4,thilanka:4,thilina:4,thing:[2,6,15],think:[6,15],third:2,thirteen:4,thoma:[4,7],those:2,though:[2,7,8,15,17],three:[2,6,17,19],threshold:2,through:2,thu:[6,8,17],thumbnail:14,tickbox:6,tild:6,till:[6,17],time:[1,2],timeout:2,timestamp:2,tinyint:6,tip:[2,6],titl:1,titledatabas:2,titledefault:2,titleserv:2,titlet:2,tkl:6,tmp:2,tobia:[3,4],tobias_at_ratschil:[3,4],togeth:2,toggl:[2,6,10],token:[15,20],tokyo:6,told:4,tom:4,toma:4,tomas_at_tomasruud:4,tomastik:4,tommi:4,tommy_at_surbakti:4,ton:2,too:[2,5,6,8],tool:[2,4,5],tooltip:[6,13],top:[2,6,8,16,17],topic:17,toplevel:2,tor:4,total:17,totp:20,town:6,town_cod:6,track:[2,4,10,17],tracker:[2,6,15],tracking_add_drop_databas:2,tracking_add_drop_t:2,tracking_add_drop_view:2,tracking_default_stat:2,tracking_version_auto_cr:2,tradition:17,traefik:17,traffic:2,trail:2,transfer:[2,6,7,17],transformation_opt:[2,19],transformation_overview:19,transformations_generator_main_class:19,transformations_generator_plugin:19,transformationsplugin:19,transit:17,translat:2,translationwarningthreshold:2,translit:2,transliter:2,transmiss:[7,8],transmit:[6,20],travel:[1,17],treat:[2,6,7],tree:[2,4],trend:1,trezor:20,tri:[2,6,15,17],trick:15,trigger:[2,4,7,8,10],trinh:4,trinhminhbao_at_gmail:4,triwidada:4,troubl:2,troubleshoot:6,truli:17,truncate:2,trust:[2,17],trustedproxi:2,tschopp:4,tune:17,tunnel213:4,tunnel213_at_aliyun:4,tupl:7,turck:6,turek:[3,4],turkish:4,turn:[2,6],turnitinbot:6,tutori:6,tweak:17,two:2,tws:4,txt:[3,6,17],type:[0,1,2,4],typeset:[7,8],typic:[2,6,7,9],typograph:8,tyron:4,ubuntu:2,ufpdf:4,uid:15,ukko:4,ukrainian:4,ultim:17,ultimat:6,unavail:2,uncheck:[2,6],unclean:6,uncom:2,uncomment:17,under:[2,3],underli:[1,17,18],undernetangel_at_gmail:4,undocu:8,unexpect:[0,2],ungureanu:4,uniform:7,unintend:2,union:[1,6],uniqu:[2,6,7,13],unite:4,unix_domain_socket:7,unless:[6,17,20],unlike:17,unlucky_at_inbox:4,unpack:[6,17],unprivileg:2,unset:[6,17],unsign:19,unstuff:17,unsupport:6,untar:17,until:[2,4,6,16,17],unus:6,unwil:6,unzip:17,updat:[2,4,6,17],update:[2,6,17],upgrad:2,upgrade:[6,9],upgrade_column_info_4_3_0:[2,17],upgrade_tables_4_7_0:17,upgrade_tables_mysql_4_1_2:17,upload_max_files:6,upload_progress:6,upload_tmp_dir:6,uploaddir:[2,6,8],uploadprogress:6,upon:[1,16],upper:6,upto:6,urbalazs_at_gmail:4,uri:13,url:2,urltrend:6,uro:4,usabl:6,usag:2,usage:12,use:[2,4],use_backend:17,use_cooki:17,used:[8,17],usedbsearch:2,useless:6,usepackag:[6,8],user:[2,4],user_bas:17,userconfig:2,userdata:15,usergroup:[2,12],userid:6,usernam:2,userprefsdevelopertab:2,userprefsdisallow:2,userstatu:6,using:[1,2],usr:17,usual:[2,6,7,17,19],usualli:[6,19],utf8mb4_general_ci:2,utf:[2,6,7,17],util:[2,6,7,15,17],vadap:4,vainauska:4,valentin:4,valia:4,valid:[0,2,4,6,8,10,15,16,17],valter:4,valu:2,values:6,van:4,varchar:2,varfilt:6,variable1:0,variable:[0,6],variables:6,vazquez:4,vector:8,vendor:[2,3,6,22],vendor_config:22,verb:6,verbos:[2,6,17],verbose:6,verfic:2,veri:[6,8,17],verif:[2,6,17],verifi:[2,6,9],verschuer:4,verschuere_at_outlook:4,version:[1,2,3],version_check:2,versioncheck:2,vertic:[4,7],vetoffic:6,via:2,victor:4,vidner:4,vietnames:4,view:[2,4],viktar:4,viliu:4,villanueva:4,vim:[2,17],vinai:4,vincent:4,vinipitta_at_gmail:4,vip_at_krasio:4,vipals_at_gmail:4,virusyoon_at_gmail:4,visibl:[2,17],visit:17,visok:4,visokereyal_at_gmail:4,visual:[2,4,6],visualis:6,vitalii:4,vliet:4,vmta_at_yahoo:4,vonflyne:4,vonflynee_at_gmail:4,voogt:4,voogt_at_hccnet:4,voyag:6,vperekupka_at_gmail:4,vserver:[2,6],vulner:[2,6,9],vytauta:4,w3c:[6,7],w3c_valid:6,wai:[0,2,4],wait:6,wallet:20,walton:4,walton_at_nordicdm:4,want:2,warn:2,warning:17,warranti:3,warranty:3,washington:4,washingtonbruno_at_msn:4,wasn:4,wasser:6,waw:4,weaker:2,web_brows:7,webadmin:4,webapp:17,webcrawl:6,weblate_at_gmail:4,webmaster_at_trafficg:4,webserv:[2,6,7,8,17],websit:[4,5,6,7,15,18],wei:4,welcom:2,well:[2,4,6,10,15,17],weng:4,wengshiyu_at_gmail:4,wennberg:4,were:[2,4,6],west:4,wget:6,what:[0,2],when:[0,2,4],whenev:[2,6,17],where:[0,2,3],whether:[2,12,17,22],whh:4,whhlcj_at_126:4,which:[0,1,2,3],whitelist:6,who:[2,4],whole:[0,2,6,15,17],whose:[6,17],why:[0,2],wide:[7,8,16,20],width:2,wiegger:4,wigginton:4,wiki:[6,7,8],wikipedia:[7,8,15,17],wildcard:[2,6],wilk:4,wilson:4,win2k:6,win98:6,window:[1,2,4],winhttp:6,winningham:4,winnt4:6,wisenutbot:6,wish:[2,7,8,12,17],withdrawn:4,within:[2,6,12],without:[2,3],witten:4,wizard:[6,17],wkito:6,won:[0,2],word:[2,6],wordfenc:6,work:[2,4],workaround:[6,17],workbook:8,world:[2,7],worldwideski:4,worldwideskier_at_yahoo:4,wors:6,would:2,wrap:2,wrapper:17,writabl:[2,22],write:2,written:[2,5,6,10],wrong:2,wrongli:6,www:[2,3,4,6,8,17,18,19],wysiwyg:4,xampp:17,xavier:4,xhtml1:4,xml:[4,7],xosecalvo_at_gmail:4,xs910203_at_gmail:4,xuan:4,xvnavarro_at_gmail:4,xxx:[2,6,19],xxxx:2,xzvf:17,yacybot:6,yahoo:6,yahooseek:6,yan:4,yansilvagabriel_at_gmail:4,yaron:4,yaron_at_gmail:4,yashodha:4,yasir:4,yasitha:4,yavuz:4,yearmad:8,yellow:6,yet:[2,8],yetdiffer:2,yizhou:4,yml:17,yogarajah:4,yogeshwar:4,yohan:4,yong:4,yoon:4,you:[0,1,2,3,4,5,6,7,8,10,11,12,13,14,15,16,17,18,19,20,22],youbico:20,youngmin:4,youngminz:4,your:[0,2,6,8,9,10,12,13,14,15,16],your_db_host:17,your_theme_nam:18,yourpassword:2,yswy_at_hotmail:4,yug:4,yugal:4,yukihiro:4,yuval:4,yyi:2,yyyi:2,zahra:4,zancan:4,zarubin:4,zassenhau:4,zassenhaus_at_jgerman:4,zend:6,zero:[2,8,16],zeroconf:[2,17],zerofil:19,zheng:4,zhyarabdulla94_at_gmail:4,zigmanta:4,zion_at_gmail:4,zip:[2,4,6,7,8,10,14,17],zip_:7,zipdump:2,zlib:[6,7,10],zone:2,zoom:4,zrng:4,zufar:4,zzz:2,zzzz:2},titles:["Bookmarks","Charts","Configuration","Copyright","Credits","Developers Information","FAQ - Frequently Asked Questions","Glossary","Import and export","Welcome to phpMyAdmin’s documentation!","Introduction","Other sources of information","User management","Relations","Requirements","Security policy","Configuring phpMyAdmin","Installation","Custom Themes","Transformations","Two-factor authentication","User Guide","Distributing and packaging phpMyAdmin"],titleterms:{"17a":6,"2fa":20,"5b4":6,"\u010deski":11,"\u0440\u0443\u0441\u0441\u043a\u0438\u0439":11,"default":[2,6],"export":[2,6,8],"function":6,"import":[2,6,8],"int":6,"new":[6,12],"null":6,"return":6,"try":6,"while":6,abl:6,about:[6,10],access:6,action:6,add:6,addit:6,address:2,adjust:6,after:[6,17],again:6,against:6,alert:6,allow:6,alreadi:6,alwai:6,ani:6,ansi:6,apach:6,applic:6,applicat:20,appropri:6,area:6,arrai:8,ascii:6,ask:6,asked:6,assign:12,attack:[6,15],auth:6,authent:[2,6,17,20],auto:6,autologin:2,automat:6,back:[6,17],backup:6,bar:[1,6],base:6,basic:2,becaus:6,behavior:6,behind:17,below:6,big:6,bind:6,blank:6,blob:6,book:11,bookmark:[0,6],box:[2,6],bring:6,brows:[0,2,6,17],browser:[6,14],brute:[6,15],bug:6,bundl:6,can:6,cannot:6,caus:6,central:6,certain:6,cgi:6,chang:6,charact:6,chart:[1,6],checkbox:6,chronolog:4,click:6,clickabl:6,client:6,cloud:2,cluster:6,codegen:8,column:[1,6,8,17],come:6,comment:6,complet:6,compos:17,compress:6,config:17,configur:[2,6,12,16,17,18],connect:[2,6,17],consid:6,consol:2,contain:6,content:6,control:6,cooki:[2,6,17],copi:6,copyright:3,correct:6,could:6,crash:6,creat:[6,12,17,18],credit:4,cross:15,cryptic:6,csrf:15,csv:[6,8],curiou:6,custom:[2,6,17,18],czech:11,data:[6,8],databas:[2,6,12,14,17],db_structur:6,debian:17,decim:6,delet:[6,12],deni:6,deriv:17,design:[2,6,13],determin:6,develop:[2,5,6],differ:6,directli:6,directori:2,disclosur:6,disk:6,displai:[2,6],distribut:[6,17,22],docker:17,document:[4,6,8,9],doe:6,doesn:6,don:6,dot:6,down:6,drop:6,dump:6,duplic:6,each:6,easili:6,edit:6,edite:[2,12],effect:6,employe:6,empti:6,enabl:6,encrypt:6,english:11,enter:6,enterpris:17,environ:17,error:6,errorcod:6,esri:[6,8],evil:6,exampl:[1,2,6,13,17],excel:[6,8],execut:6,exist:[6,12],explorer:6,extend:6,extens:6,external:22,face:6,factor:20,fail:6,faq:6,favorit:6,featur:[6,10],fedora:17,fido:20,field:[2,6],file:[6,8,17,19],fine:6,firefox:6,firstnam:6,fix:6,folder:6,follow:6,font:6,forc:[6,15],foreign:6,forgeri:15,form:6,format:6,formula:6,forward:6,found:6,frequent:6,from:[6,17],full:6,gener:[2,6],gentoo:17,german:6,get:6,git:17,give:6,glossari:7,gone:6,googl:2,group:12,guid:21,gzip:6,happen:6,haproxi:17,hardwar:20,hash:6,hat:17,have:6,header:6,heard:6,help:6,hierarchi:6,homonym:6,host:6,how:6,html:6,http:[6,17],huge:6,hundr:6,hyphen:6,iis:6,imag:18,implement:1,improve:6,includ:6,increment:6,index:6,indice:9,info:13,inform:[6,11],informat:5,inject:[6,15],innodb:6,input:6,insert:6,insid:0,instal:[6,17],install:17,installat:17,intend:6,internal:6,internet:6,introduct:[10,19],isp:6,issu:[6,15,17],javascript:6,json:8,just:6,kei:[6,10,20],know:6,known:[6,17],konqueror:6,lang:6,languag:[2,6],larg:6,lastnam:6,latex:[6,8],length:6,let:6,librari:[6,22],licens:3,like:6,limit:[2,6],line:1,linux:17,list:6,load:[6,8],local:6,localhost:6,locat:6,log:[6,17],login:6,lose:6,lost:6,lot:6,lowercas:6,lump:6,mac:6,machin:6,main:2,make:6,manag:[2,12],mandriva:17,mani:6,manner:6,manual:17,mean:6,mediawiki:8,memori:6,menu:12,messag:6,metadata:18,microsoft:[6,8],mime:6,mimetyp:6,mine:6,misbehav:6,miss:6,mod_gzip:6,mode:[2,6,17],modifi:6,more:6,move:6,mozilla:6,multi:6,multipl:[2,6],mysql:[2,6],mysqldump:6,mysqli:6,name:[6,8],nativ:6,navig:[2,6],need:6,nest:6,netscap:6,never:6,newer:6,nick:6,non:6,noth:6,now:6,number:6,ods:8,older:[6,17],onc:6,onli:6,onto:6,open:8,open_basedir:6,opendocu:8,opensuse:17,oper:6,option:[2,6,8],order:4,origin:4,other:11,out:[6,17],own:6,packag:22,page:[2,6],panel:2,paramet:6,parameter:6,pars:6,parti:3,password:6,path:6,pdf:[2,6,8],php:[6,8,14],phpmyadmin:[6,9,16,17,22],pie:1,plenti:6,pmadb:6,polici:[6,15],port:6,possibl:6,potenti:6,pr1:6,pre:6,prefer:6,prevent:6,primari:6,print:11,privileg:[6,12,17],problem:6,proce:6,procedur:6,process:6,produc:6,progress:6,project:6,protect:6,protocol:6,proxi:6,put:6,pws:6,queri:[2,6],question:6,quick:17,rang:6,reassign:6,receiv:6,red:17,redirect:6,reduc:6,refresh:6,refus:6,relat:[6,13],relationship:6,releas:17,reload:6,renam:6,report:15,request:[6,15],requir:[6,14],restor:6,restrict:6,result:6,revers:6,right:6,robot:6,root:6,row:6,run:[6,17],russian:11,safari:6,safe:6,sai:6,same:6,save:2,scatter:1,schema:6,script:[6,15,17],scroll:6,search:6,secur:[6,15,17,20],see:6,seem:6,select:6,send:6,sent:6,seri:6,server:[2,6,14,17],set:[2,6],setup:[2,6,17],shape:8,shapefil:6,share:18,shortcut:10,should:6,shown:6,signon:[2,17],simpl:[6,20],simpli:6,singl:6,site:15,size:6,smith:6,sock:6,socket:6,some:6,sometim:6,sourc:11,special:6,specif:[6,12,17],specifi:6,spline:1,spreadsheet:8,sql:[2,6,8,15],sql_mode:6,ssl:[2,17],start:6,statist:6,storag:17,store:[0,6],string:6,structur:[2,6,19],subdirectori:17,suhosin:6,support:[6,10],sure:6,symbol:6,synchron:6,tab:[2,6],tabl:[0,6,8,9],technic:13,texi:8,text:[2,6,8],than:6,thei:6,them:6,theme:[2,6,18],thi:6,third:3,those:6,through:6,time:6,timelin:1,timeout:6,titl:2,tmp:6,togeth:6,tool:6,transform:[2,6,19],translat:[4,6],troubl:[6,17],tutori:11,two:[6,20],type:6,typic:15,u2f:20,ubuntu:17,umlaut:6,unabl:[6,17],undefin:6,under:6,underscor:6,understand:6,unicod:6,unix:6,unknown:6,unsaf:6,upgrad:6,upgrade:17,upload:[2,6],url:6,usage:19,use:6,user:[6,10,12,17,21],usernam:6,using:[6,17],valu:6,varchar:6,variabl:[0,6,17],variou:2,verifi:17,version:[4,6,17],via:6,view:[6,13],violat:6,volum:17,vulner:15,wai:6,want:6,warn:6,web:[2,14],welcom:[6,9],what:6,when:6,where:6,which:6,whitespac:6,who:6,why:6,win32:6,window:[6,17],withdrawn:6,without:6,won:6,word:[8,10],work:6,would:6,write:6,wrong:6,xitami:6,xml:8,xss:15,yaml:8,yes:6,your:17,zero:17,zoom:6}}) \ No newline at end of file diff --git a/php/apps/phpmyadmin49/doc/html/security.html b/php/apps/phpmyadmin49/doc/html/security.html new file mode 100644 index 00000000..dff9d43d --- /dev/null +++ b/php/apps/phpmyadmin49/doc/html/security.html @@ -0,0 +1,219 @@ + + + + + + + + Security policy — phpMyAdmin 4.9.1 documentation + + + + + + + + + + + + + + + + + + +
+
+
+
+ +
+

Security policy

+

The phpMyAdmin developer team is putting lot of effort to make phpMyAdmin as +secure as possible. But still web application like phpMyAdmin can be vulnerable +to a number of attacks and new ways to exploit are still being explored.

+

For every reported vulnerability we issue a phpMyAdmin Security Announcement +(PMASA) and it get’s assigne CVE ID as well. We might group similar +vulnerabilities to one PMASA (eg. multiple XSS vulnerabilities can be announced +under one PMASA).

+

If you think you’ve found a vulnerability, please see Reporting security issues.

+
+

Typical vulnerabilities

+

In this secion, we will describe typical vulnerabilities, which can appear in +our code base. This list is by no means complete, it is intended to show +typical attack surface.

+
+

Cross-site scripting (XSS)

+

When phpMyAdmin shows a piece of user data, e.g. something inside a user’s +database, all html special chars have to be escaped. When this escaping is +missing somewhere a malicious user might fill a database with specially crafted +content to trick an other user of that database into executing something. This +could for example be a piece of JavaScript code that would do any number of +nasty things.

+

phpMyAdmin tries to escape all userdata before it is rendered into html for the +browser.

+ +
+
+

Cross-site request forgery (CSRF)

+

An attacker would trick a phpMyAdmin user into clicking on a link to provoke +some action in phpMyAdmin. This link could either be sent via email or some +random website. If successful this the attacker would be able to perform some +action with the users privileges.

+

To mitigate this phpMyAdmin requires a token to be sent on sensitive requests. +The idea is that an attacker does not poses the currently valid token to +include in the presented link.

+

The token is regenerated for every login, so it’s generally valid only for +limited time, what makes it harder for attacker to obtain valid one.

+ +
+
+

SQL injection

+

As the whole purpose of phpMyAdmin is to preform sql queries, this is not our +first concern. SQL injection is sensitive to us though when it concerns the +mysql control connection. This controlconnection can have additional privileges +which the logged in user does not poses. E.g. access the phpMyAdmin configuration storage.

+

User data that is included in (administrative) queries should always be run +through DatabaseInterface::escapeSring().

+ +
+
+

Brute force attack

+

phpMyAdmin on its own does not rate limit authentication attempts in any way. +This is caused by need to work in stateless environment, where there is no way +to protect against such kind of things.

+

To mitigate this, you can use Captcha or utilize external tools such as +fail2ban, this is more details described in Securing your phpMyAdmin installation.

+ +
+
+
+

Reporting security issues

+

Should you find a security issue in the phpMyAdmin programming code, please +contact the phpMyAdmin security team in +advance before publishing it. This way we can prepare a fix and release the fix together with your +announcement. You will be also given credit in our security announcement. +You can optionally encrypt your report with PGP key ID +DA68AB39218AB947 with following fingerprint:

+
pub   4096R/DA68AB39218AB947 2016-08-02
+      Key fingerprint = 5BAD 38CF B980 50B9 4BD7  FB5B DA68 AB39 218A B947
+uid                          phpMyAdmin Security Team &lt;security@phpmyadmin.net&gt;
+sub   4096R/5E4176FB497A31F7 2016-08-02
+
+
+

The key can be either obtained from the keyserver or is available in +phpMyAdmin keyring +available on our download server or using Keybase.

+

Should you have suggestion on improving phpMyAdmin to make it more secure, please +report that to our issue tracker. +Existing improvement suggestions can be found by +hardening label.

+
+
+ + +
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/php/apps/phpmyadmin49/doc/html/settings.html b/php/apps/phpmyadmin49/doc/html/settings.html new file mode 100644 index 00000000..2e2069d5 --- /dev/null +++ b/php/apps/phpmyadmin49/doc/html/settings.html @@ -0,0 +1,130 @@ + + + + + + + + Configuring phpMyAdmin — phpMyAdmin 4.9.1 documentation + + + + + + + + + + + + + + + + + + + +
+
+
+
+ +
+

Configuring phpMyAdmin

+

There are many configuration settings that can be used to customize the +interface. Those settings are described in +Configuration. There are several layers of the configuration.

+

The global settings can be configured in config.inc.php as described in +Configuration. This is only way to configure connections to databases and other +system wide settings.

+

On top of this there are user settings which can be persistently stored in +phpMyAdmin configuration storage, possibly automatically configured through +Zero configuration. If the phpMyAdmin configuration storage are not configured, the settings +are temporarily stored in the session data; these are valid only until you +logout.

+

You can also save the user configuration for further use, either download them +as a file or to the browser local storage. You can find both those options in +the Settings tab. The settings stored in browser local storage will +be automatically offered for loading upon your login to phpMyAdmin.

+
+ + +
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/php/apps/phpmyadmin49/doc/html/setup.html b/php/apps/phpmyadmin49/doc/html/setup.html new file mode 100644 index 00000000..bfeb04dd --- /dev/null +++ b/php/apps/phpmyadmin49/doc/html/setup.html @@ -0,0 +1,1442 @@ + + + + + + + + Installation — phpMyAdmin 4.9.1 documentation + + + + + + + + + + + + + + + + + + +
+
+
+
+ +
+

Installation

+

phpMyAdmin does not apply any special security methods to the MySQL +database server. It is still the system administrator’s job to grant +permissions on the MySQL databases properly. phpMyAdmin’s Users +page can be used for this.

+
+

Warning

+

Mac users should note that if you are on a version before +Mac OS X, StuffIt unstuffs with Mac formats. So you’ll have +to resave as in BBEdit to Unix style ALL phpMyAdmin scripts before +uploading them to your server, as PHP seems not to like Mac-style +end of lines character (“\r”).

+
+
+

Linux distributions

+

phpMyAdmin is included in most Linux distributions. It is recommended to use +distribution packages when possible - they usually provide integration to your +distribution and you will automatically get security updates from your distribution.

+
+

Debian and Ubuntu

+

Debian’s package repositories include a phpMyAdmin package, but be aware that +the configuration file is maintained in /etc/phpmyadmin and may differ in +some ways from the official phpMyAdmin documentation. Specifically it does:

+ +
+

See also

+

More information can be found in README.Debian +(it is installed as /usr/share/doc/phmyadmin/README.Debian with the package).

+
+
+
+

OpenSUSE

+

OpenSUSE already comes with phpMyAdmin package, just install packages from +the openSUSE Build Service.

+
+
+

Gentoo

+

Gentoo ships the phpMyAdmin package, both in a near stock configuration as well +as in a webapp-config configuration. Use emerge dev-db/phpmyadmin to +install.

+
+
+

Mandriva

+

Mandriva ships the phpMyAdmin package in their contrib branch and can be +installed via the usual Control Center.

+
+
+

Fedora

+

Fedora ships the phpMyAdmin package, but be aware that the configuration file +is maintained in /etc/phpMyAdmin/ and may differ in some ways from the +official phpMyAdmin documentation.

+
+
+

Red Hat Enterprise Linux

+

Red Hat Enterprise Linux itself and thus derivatives like CentOS don’t +ship phpMyAdmin, but the Fedora-driven repository +Extra Packages for Enterprise Linux (EPEL) +is doing so, if it’s +enabled. +But be aware that the configuration file is maintained in +/etc/phpMyAdmin/ and may differ in some ways from the +official phpMyAdmin documentation.

+
+
+
+

Installing on Windows

+

The easiest way to get phpMyAdmin on Windows is using third party products +which include phpMyAdmin together with a database and web server such as +XAMPP.

+

You can find more of such options at Wikipedia.

+
+
+

Installing from Git

+

You can clone current phpMyAdmin source from +https://github.com/phpmyadmin/phpmyadmin.git:

+
git clone https://github.com/phpmyadmin/phpmyadmin.git
+
+
+

Additionally you need to install dependencies using the Composer tool:

+
composer update
+
+
+

If you do not intend to develop, you can skip the installation of developer tools +by invoking:

+
composer update --no-dev
+
+
+
+
+

Installing using Composer

+

You can install phpMyAdmin using the Composer tool, since 4.7.0 the releases +are automatically mirrored to the default Packagist repository.

+
+

Note

+

The content of the Composer repository is automatically generated +separately from the releases, so the content doesn’t have to be +100% same as when you download the tarball. There should be no +functional differences though.

+
+

To install phpMyAdmin simply run:

+
composer create-project phpmyadmin/phpmyadmin
+
+
+

Alternatively you can use our own composer repository, which contains +the release tarballs and is available at +<https://www.phpmyadmin.net/packages.json>:

+
composer create-project phpmyadmin/phpmyadmin --repository-url=https://www.phpmyadmin.net/packages.json --no-dev
+
+
+
+
+

Installing using Docker

+

phpMyAdmin comes with a Docker image, which you can easily deploy. You can +download it using:

+
docker pull phpmyadmin/phpmyadmin
+
+
+

The phpMyAdmin server will listen on port 80. It supports several ways of +configuring the link to the database server, either by Docker’s link feature +by linking your database container to db for phpMyAdmin (by specifying +--link your_db_host:db) or by environment variables (in this case it’s up +to you to set up networking in Docker to allow the phpMyAdmin container to access +the database container over network).

+
+

Docker environment variables

+

You can configure several phpMyAdmin features using environment variables:

+
+
+PMA_ARBITRARY
+

Allows you to enter a database server hostname on login form.

+ +
+ +
+
+PMA_HOST
+

Host name or IP address of the database server to use.

+ +
+ +
+
+PMA_HOSTS
+

Comma-separated host names or IP addresses of the database servers to use.

+
+

Note

+

Used only if PMA_HOST is empty.

+
+
+ +
+
+PMA_VERBOSE
+

Verbose name of the database server.

+ +
+ +
+
+PMA_VERBOSES
+

Comma-separated verbose name of the database servers.

+
+

Note

+

Used only if PMA_VERBOSE is empty.

+
+
+ +
+
+PMA_USER
+

User name to use for Config authentication mode.

+
+ +
+
+PMA_PASSWORD
+

Password to use for Config authentication mode.

+
+ +
+
+PMA_PORT
+

Port of the database server to use.

+
+ +
+
+PMA_PORTS
+

Comma-separated ports of the database server to use.

+
+

Note

+

Used only if PMA_PORT is empty.

+
+
+ +
+
+PMA_ABSOLUTE_URI
+

The fully-qualified path (https://pma.example.net/) where the reverse +proxy makes phpMyAdmin available.

+ +
+ +

By default, Cookie authentication mode is used, but if PMA_USER and +PMA_PASSWORD are set, it is switched to Config authentication mode.

+
+

Note

+

The credentials you need to log in are stored in the MySQL server, in case +of Docker image there are various ways to set it (for example +MYSQL_ROOT_PASSWORD when starting the MySQL container). Please check +documentation for MariaDB container +or MySQL container.

+
+
+
+

Customizing configuration

+

Additionally configuration can be tweaked by /etc/phpmyadmin/config.user.inc.php. If +this file exists, it will be loaded after configuration is generated from above +environment variables, so you can override any configuration variable. This +configuration can be added as a volume when invoking docker using +-v /some/local/directory/config.user.inc.php:/etc/phpmyadmin/config.user.inc.php parameters.

+

Note that the supplied configuration file is applied after Docker environment variables, +but you can override any of the values.

+

For example to change default behaviour of CSV export you can use following +configuration file:

+
<?php
+$cfg['Export']['csv_columns'] = true;
+?>
+
+
+

You can also use it to define server configuration instead of using the +environment variables listed in Docker environment variables:

+
<?php
+/* Override Servers array */
+$cfg['Servers'] = [
+    1 => [
+        'auth_type' => 'cookie',
+        'host' => 'mydb1',
+        'port' => 3306,
+        'verbose' => 'Verbose name 1',
+    ],
+    2 => [
+        'auth_type' => 'cookie',
+        'host' => 'mydb2',
+        'port' => 3306,
+        'verbose' => 'Verbose name 2',
+    ],
+];
+
+
+
+

See also

+

See Configuration for detailed description of configuration options.

+
+
+
+

Docker Volumes

+

You can use following volumes to customize image behavior:

+

/etc/phpmyadmin/config.user.inc.php

+
+
Can be used for additional settings, see previous chapter for more details.
+

/sessions/

+
+
Directory where PHP sessions are stored. You might want to share this +for example when using Signon authentication mode.
+

/www/themes/

+
+
Directory where phpMyAdmin looks for themes. By default only those shipped +with phpMyAdmin are included, but you can include additional phpMyAdmin +themes (see Custom Themes) by using Docker volumes.
+
+
+

Docker Examples

+

To connect phpMyAdmin to a given server use:

+
docker run --name myadmin -d -e PMA_HOST=dbhost -p 8080:80 phpmyadmin/phpmyadmin
+
+
+

To connect phpMyAdmin to more servers use:

+
docker run --name myadmin -d -e PMA_HOSTS=dbhost1,dbhost2,dbhost3 -p 8080:80 phpmyadmin/phpmyadmin
+
+
+

To use arbitrary server option:

+
docker run --name myadmin -d --link mysql_db_server:db -p 8080:80 -e PMA_ARBITRARY=1 phpmyadmin/phpmyadmin
+
+
+

You can also link the database container using Docker:

+
docker run --name phpmyadmin -d --link mysql_db_server:db -p 8080:80 phpmyadmin/phpmyadmin
+
+
+

Running with additional configuration:

+
docker run --name phpmyadmin -d --link mysql_db_server:db -p 8080:80 -v /some/local/directory/config.user.inc.php:/etc/phpmyadmin/config.user.inc.php phpmyadmin/phpmyadmin
+
+
+

Running with additional themes:

+
docker run --name phpmyadmin -d --link mysql_db_server:db -p 8080:80 -v /custom/phpmyadmin/theme/:/www/themes/theme/ phpmyadmin/phpmyadmin
+
+
+
+
+

Using docker-compose

+

Alternatively you can also use docker-compose with the docker-compose.yml from +<https://github.com/phpmyadmin/docker>. This will run phpMyAdmin with an +arbitrary server - allowing you to specify MySQL/MariaDB server on login page.

+
docker-compose up -d
+
+
+
+
+

Customizing configuration file using docker-compose

+

You can use an external file to customize phpMyAdmin configuration and pass it +using the volumes directive:

+
phpmyadmin:
+    image: phpmyadmin/phpmyadmin
+    container_name: phpmyadmin
+    environment:
+     - PMA_ARBITRARY=1
+    restart: always
+    ports:
+     - 8080:80
+    volumes:
+     - /sessions
+     - ~/docker/phpmyadmin/config.user.inc.php:/etc/phpmyadmin/config.user.inc.php
+     - /custom/phpmyadmin/theme/:/www/themes/theme/
+
+
+ +
+
+

Running behind haproxy in a subdirectory

+

When you want to expose phpMyAdmin running in a Docker container in a +subdirectory, you need to rewrite the request path in the server proxying the +requests.

+

For example using haproxy it can be done as:

+
frontend http
+    bind *:80
+    option forwardfor
+    option http-server-close
+
+    ### NETWORK restriction
+    acl LOCALNET  src 10.0.0.0/8 192.168.0.0/16 172.16.0.0/12
+
+    # /phpmyadmin
+    acl phpmyadmin  path_dir /phpmyadmin
+    use_backend phpmyadmin if phpmyadmin LOCALNET
+
+backend phpmyadmin
+    mode http
+
+    reqirep  ^(GET|POST|HEAD)\ /phpmyadmin/(.*)     \1\ /\2
+
+    # phpMyAdmin container IP
+    server localhost     172.30.21.21:80
+
+
+

When using traefik, something like following should work:

+
defaultEntryPoints = ["http"]
+[entryPoints]
+  [entryPoints.http]
+  address = ":80"
+    [entryPoints.http.redirect]
+      regex = "(http:\\/\\/[^\\/]+\\/([^\\?\\.]+)[^\\/])$"
+      replacement = "$1/"
+
+[backends]
+  [backends.myadmin]
+    [backends.myadmin.servers.myadmin]
+    url="http://internal.address.to.pma"
+
+[frontends]
+   [frontends.myadmin]
+   backend = "myadmin"
+   passHostHeader = true
+     [frontends.myadmin.routes.default]
+     rule="PathPrefixStrip:/phpmyadmin/;AddPrefix:/"
+
+
+

You then should specify PMA_ABSOLUTE_URI in the docker-compose +configuration:

+
version: '2'
+
+services:
+  phpmyadmin:
+    restart: always
+    image: phpmyadmin/phpmyadmin
+    container_name: phpmyadmin
+    hostname: phpmyadmin
+    domainname: example.com
+    ports:
+      - 8000:80
+    environment:
+      - PMA_HOSTS=172.26.36.7,172.26.36.8,172.26.36.9,172.26.36.10
+      - PMA_VERBOSES=production-db1,production-db2,dev-db1,dev-db2
+      - PMA_USER=root
+      - PMA_PASSWORD=
+      - PMA_ABSOLUTE_URI=http://example.com/phpmyadmin/
+
+
+
+
+
+

Quick Install

+
    +
  1. Choose an appropriate distribution kit from the phpmyadmin.net +Downloads page. Some kits contain only the English messages, others +contain all languages. We’ll assume you chose a kit whose name +looks like phpMyAdmin-x.x.x -all-languages.tar.gz.
  2. +
  3. Ensure you have downloaded a genuine archive, see Verifying phpMyAdmin releases.
  4. +
  5. Untar or unzip the distribution (be sure to unzip the subdirectories): +tar -xzvf phpMyAdmin_x.x.x-all-languages.tar.gz in your +webserver’s document root. If you don’t have direct access to your +document root, put the files in a directory on your local machine, +and, after step 4, transfer the directory on your web server using, +for example, ftp.
  6. +
  7. Ensure that all the scripts have the appropriate owner (if PHP is +running in safe mode, having some scripts with an owner different from +the owner of other scripts will be a problem). See 4.2 What’s the preferred way of making phpMyAdmin secure against evil access? and +1.26 I just installed phpMyAdmin in my document root of IIS but I get the error “No input file specified” when trying to run phpMyAdmin. for suggestions.
  8. +
  9. Now you must configure your installation. There are two methods that +can be used. Traditionally, users have hand-edited a copy of +config.inc.php, but now a wizard-style setup script is provided +for those who prefer a graphical installation. Creating a +config.inc.php is still a quick way to get started and needed for +some advanced features.
  10. +
+
+

Manually creating the file

+

To manually create the file, simply use your text editor to create the +file config.inc.php (you can copy config.sample.inc.php to get +a minimal configuration file) in the main (top-level) phpMyAdmin +directory (the one that contains index.php). phpMyAdmin first +loads libraries/config.default.php and then overrides those values +with anything found in config.inc.php. If the default value is +okay for a particular setting, there is no need to include it in +config.inc.php. You’ll probably need only a few directives to get going; a +simple configuration may look like this:

+
<?php
+// use here a value of your choice at least 32 chars long
+$cfg['blowfish_secret'] = '1{dd0`<Q),5XP_:R9UK%%8\"EEcyH#{o';
+
+$i=0;
+$i++;
+$cfg['Servers'][$i]['auth_type']     = 'cookie';
+// if you insist on "root" having no password:
+// $cfg['Servers'][$i]['AllowNoPassword'] = true; `
+?>
+
+
+

Or, if you prefer to not be prompted every time you log in:

+
<?php
+
+$i=0;
+$i++;
+$cfg['Servers'][$i]['user']          = 'root';
+$cfg['Servers'][$i]['password']      = 'cbb74bc'; // use here your password
+$cfg['Servers'][$i]['auth_type']     = 'config';
+?>
+
+
+
+

Warning

+

Storing passwords in the configuration is insecure as anybody can then +manipulate your database.

+
+

For a full explanation of possible configuration values, see the +Configuration of this document.

+
+
+

Using Setup script

+

Instead of manually editing config.inc.php, you can use phpMyAdmin’s +setup feature. The file can be generated using the setup and you can download it +for upload to the server.

+

Next, open your browser and visit the location where you installed phpMyAdmin, +with the /setup suffix. The changes are not saved to the server, you need to +use the Download button to save them to your computer and then upload +to the server.

+

Now the file is ready to be used. You can choose to review or edit the +file with your favorite editor, if you prefer to set some advanced +options which the setup script does not provide.

+
    +
  1. If you are using the auth_type “config”, it is suggested that you +protect the phpMyAdmin installation directory because using config +does not require a user to enter a password to access the phpMyAdmin +installation. Use of an alternate authentication method is +recommended, for example with HTTP–AUTH in a .htaccess file or switch to using +auth_type cookie or http. See the ISPs, multi-user installations +for additional information, especially 4.4 phpMyAdmin always gives “Access denied” when using HTTP authentication..
  2. +
  3. Open the main phpMyAdmin directory in your browser. +phpMyAdmin should now display a welcome screen and your databases, or +a login dialog if using HTTP or +cookie authentication mode.
  4. +
+
+

Setup script on Debian, Ubuntu and derivatives

+

Debian and Ubuntu have changed way how setup is enabled and disabled, in a way +that single command has to be executed for either of these.

+

To allow editing configuration invoke:

+
/usr/sbin/pma-configure
+
+
+

To block editing configuration invoke:

+
/usr/sbin/pma-secure
+
+
+
+
+

Setup script on openSUSE

+

Some openSUSE releases do not include setup script in the package. In case you +want to generate configuration on these you can either download original +package from <https://www.phpmyadmin.net/> or use setup script on our demo +server: <https://demo.phpmyadmin.net/master/setup/>.

+
+
+
+
+

Verifying phpMyAdmin releases

+

Since July 2015 all phpMyAdmin releases are cryptographically signed by the +releasing developer, who through January 2016 was Marc Delisle. His key id is +0xFEFC65D181AF644A, his PGP fingerprint is:

+
436F F188 4B1A 0C3F DCBF 0D79 FEFC 65D1 81AF 644A
+
+
+

and you can get more identification information from <https://keybase.io/lem9>.

+

Beginning in January 2016, the release manager is Isaac Bennetch. His key id is +0xCE752F178259BD92, and his PGP fingerprint is:

+
3D06 A59E CE73 0EB7 1B51 1C17 CE75 2F17 8259 BD92
+
+
+

and you can get more identification information from <https://keybase.io/ibennetch>.

+

Some additional downloads (for example themes) might be signed by Michal Čihař. His key id is +0x9C27B31342B7511D, and his PGP fingerprint is:

+
63CB 1DF1 EF12 CF2A C0EE 5A32 9C27 B313 42B7 511D
+
+
+

and you can get more identification information from <https://keybase.io/nijel>.

+

You should verify that the signature matches the archive you have downloaded. +This way you can be sure that you are using the same code that was released. +You should also verify the date of the signature to make sure that you +downloaded the latest version.

+

Each archive is accompanied with .asc files which contains the PGP signature +for it. Once you have both of them in the same folder, you can verify the signature:

+
$ gpg --verify phpMyAdmin-4.5.4.1-all-languages.zip.asc
+gpg: Signature made Fri 29 Jan 2016 08:59:37 AM EST using RSA key ID 8259BD92
+gpg: Can't check signature: public key not found
+
+
+

As you can see gpg complains that it does not know the public key. At this +point you should do one of the following steps:

+ +
$ gpg --import phpmyadmin.keyring
+
+
+
    +
  • Download and import the key from one of the key servers:
  • +
+
$ gpg --keyserver hkp://pgp.mit.edu --recv-keys 3D06A59ECE730EB71B511C17CE752F178259BD92
+gpg: requesting key 8259BD92 from hkp server pgp.mit.edu
+gpg: key 8259BD92: public key "Isaac Bennetch <bennetch@gmail.com>" imported
+gpg: no ultimately trusted keys found
+gpg: Total number processed: 1
+gpg:               imported: 1  (RSA: 1)
+
+
+

This will improve the situation a bit - at this point you can verify that the +signature from the given key is correct but you still can not trust the name used +in the key:

+
$ gpg --verify phpMyAdmin-4.5.4.1-all-languages.zip.asc
+gpg: Signature made Fri 29 Jan 2016 08:59:37 AM EST using RSA key ID 8259BD92
+gpg: Good signature from "Isaac Bennetch <bennetch@gmail.com>"
+gpg:                 aka "Isaac Bennetch <isaac@bennetch.org>"
+gpg: WARNING: This key is not certified with a trusted signature!
+gpg:          There is no indication that the signature belongs to the owner.
+Primary key fingerprint: 3D06 A59E CE73 0EB7 1B51  1C17 CE75 2F17 8259 BD92
+
+
+

The problem here is that anybody could issue the key with this name. You need to +ensure that the key is actually owned by the mentioned person. The GNU Privacy +Handbook covers this topic in the chapter Validating other keys on your public +keyring. The most reliable method is to meet the developer in person and +exchange key fingerprints, however you can also rely on the web of trust. This way +you can trust the key transitively though signatures of others, who have met +the developer in person. For example you can see how Isaac’s key links to +Linus’s key.

+

Once the key is trusted, the warning will not occur:

+
$ gpg --verify phpMyAdmin-4.5.4.1-all-languages.zip.asc
+gpg: Signature made Fri 29 Jan 2016 08:59:37 AM EST using RSA key ID 8259BD92
+gpg: Good signature from "Isaac Bennetch <bennetch@gmail.com>" [full]
+
+
+

Should the signature be invalid (the archive has been changed), you would get a +clear error regardless of the fact that the key is trusted or not:

+
$ gpg --verify phpMyAdmin-4.5.4.1-all-languages.zip.asc
+gpg: Signature made Fri 29 Jan 2016 08:59:37 AM EST using RSA key ID 8259BD92
+gpg: BAD signature from "Isaac Bennetch <bennetch@gmail.com>" [unknown]
+
+
+
+
+

phpMyAdmin configuration storage

+
+

Changed in version 3.4.0: Prior to phpMyAdmin 3.4.0 this was called Linked Tables Infrastructure, but +the name was changed due to extended scope of the storage.

+
+

For a whole set of additional features (Bookmarks, comments, SQL-history, +tracking mechanism, PDF-generation, Transformations, Relations +etc.) you need to create a set of special tables. Those tables can be located +in your own database, or in a central database for a multi-user installation +(this database would then be accessed by the controluser, so no other user +should have rights to it).

+
+

Zero configuration

+

In many cases, this database structure can be automatically created and +configured. This is called “Zero Configuration” mode and can be particularly +useful in shared hosting situations. “Zeroconf” mode is on by default, to +disable set $cfg['ZeroConf'] to false.

+

The following three scenarios are covered by the Zero Configuration mode:

+
    +
  • When entering a database where the configuration storage tables are not +present, phpMyAdmin offers to create them from the Operations tab.
  • +
  • When entering a database where the tables do already exist, the software +automatically detects this and begins using them. This is the most common +situation; after the tables are initially created automatically they are +continually used without disturbing the user; this is also most useful on +shared hosting where the user is not able to edit config.inc.php and +usually the user only has access to one database.
  • +
  • When having access to multiple databases, if the user first enters the +database containing the configuration storage tables then switches to +another database, +phpMyAdmin continues to use the tables from the first database; the user is +not prompted to create more tables in the new database.
  • +
+
+
+

Manual configuration

+

Please look at your ./sql/ directory, where you should find a +file called create_tables.sql. (If you are using a Windows server, +pay special attention to 1.23 I’m running MySQL on a Win32 machine. Each time I create a new table the table and column names are changed to lowercase!).

+

If you already had this infrastructure and:

+
    +
  • upgraded to MySQL 4.1.2 or newer, please use +sql/upgrade_tables_mysql_4_1_2+.sql.
  • +
  • upgraded to phpMyAdmin 4.3.0 or newer from 2.5.0 or newer (<= 4.2.x), +please use sql/upgrade_column_info_4_3_0+.sql.
  • +
  • upgraded to phpMyAdmin 4.7.0 or newer from 4.3.0 or newer, +please use sql/upgrade_tables_4_7_0+.sql.
  • +
+

and then create new tables by importing sql/create_tables.sql.

+

You can use your phpMyAdmin to create the tables for you. Please be +aware that you may need special (administrator) privileges to create +the database and tables, and that the script may need some tuning, +depending on the database name.

+

After having imported the sql/create_tables.sql file, you +should specify the table names in your config.inc.php file. The +directives used for that can be found in the Configuration.

+

You will also need to have a controluser +($cfg['Servers'][$i]['controluser'] and +$cfg['Servers'][$i]['controlpass'] settings) +with the proper rights to those tables. For example you can create it +using following statement:

+
GRANT SELECT, INSERT, UPDATE, DELETE ON <pma_db>.* TO 'pma'@'localhost'  IDENTIFIED BY 'pmapass';
+
+
+
+
+
+

Upgrading from an older version

+
+

Warning

+

Never extract the new version over an existing installation of +phpMyAdmin, always first remove the old files keeping just the +configuration.

+

This way you will not leave old no longer working code in the directory, +which can have severe security implications or can cause various breakages.

+
+

Simply copy config.inc.php from your previous installation into +the newly unpacked one. Configuration files from old versions may +require some tweaking as some options have been changed or removed. +For compatibility with PHP 5.3 and later, remove a +set_magic_quotes_runtime(0); statement that you might find near +the end of your configuration file.

+

You should not copy libraries/config.default.php over +config.inc.php because the default configuration file is version- +specific.

+

The complete upgrade can be performed in few simple steps:

+
    +
  1. Download the latest phpMyAdmin version from <https://www.phpmyadmin.net/downloads/>.
  2. +
  3. Rename existing phpMyAdmin folder (for example to phpmyadmin-old).
  4. +
  5. Unpack freshly donwloaded phpMyAdmin to desired location (for example phpmyadmin).
  6. +
  7. Copy config.inc.php` from old location (phpmyadmin-old) to new one (phpmyadmin).
  8. +
  9. Test that everything works properly.
  10. +
  11. Remove backup of previous version (phpmyadmin-old).
  12. +
+

If you have upgraded your MySQL server from a version previous to 4.1.2 to +version 5.x or newer and if you use the phpMyAdmin configuration storage, you +should run the SQL script found in +sql/upgrade_tables_mysql_4_1_2+.sql.

+

If you have upgraded your phpMyAdmin to 4.3.0 or newer from 2.5.0 or +newer (<= 4.2.x) and if you use the phpMyAdmin configuration storage, you +should run the SQL script found in +sql/upgrade_column_info_4_3_0+.sql.

+

Do not forget to clear the browser cache and to empty the old session by +logging out and logging in again.

+
+
+

Using authentication modes

+

HTTP and cookie authentication modes are recommended in a multi-user +environment where you want to give users access to their own database and +don’t want them to play around with others. Nevertheless be aware that MS +Internet Explorer seems to be really buggy about cookies, at least till version +6. Even in a single-user environment, you might prefer to use HTTP +or cookie mode so that your user/password pair are not in clear in the +configuration file.

+

HTTP and cookie authentication +modes are more secure: the MySQL login information does not need to be +set in the phpMyAdmin configuration file (except possibly for the +$cfg['Servers'][$i]['controluser']). +However, keep in mind that the password travels in plain text, unless +you are using the HTTPS protocol. In cookie mode, the password is +stored, encrypted with the AES algorithm, in a temporary cookie.

+

Then each of the true users should be granted a set of privileges +on a set of particular databases. Normally you shouldn’t give global +privileges to an ordinary user, unless you understand the impact of those +privileges (for example, you are creating a superuser). +For example, to grant the user real_user with all privileges on +the database user_base:

+
GRANT ALL PRIVILEGES ON user_base.* TO 'real_user'@localhost IDENTIFIED BY 'real_password';
+
+
+

What the user may now do is controlled entirely by the MySQL user management +system. With HTTP or cookie authentication mode, you don’t need to fill the +user/password fields inside the $cfg['Servers'].

+ +
+

HTTP authentication mode

+ +
+

Note

+

There is no way to do proper logout in HTTP authentication, most browsers +will remember credentials until there is no different successful +authentication. Because of this this method has limitation that you can not +login with same user after logout.

+
+
+ +
+

Signon authentication mode

+ +

The very basic example of saving credentials in a session is available as +examples/signon.php:

+
<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Single signon for phpMyAdmin
+ *
+ * This is just example how to use session based single signon with
+ * phpMyAdmin, it is not intended to be perfect code and look, only
+ * shows how you can integrate this functionality in your application.
+ *
+ * @package    PhpMyAdmin
+ * @subpackage Example
+ */
+
+/* Use cookies for session */
+ini_set('session.use_cookies', 'true');
+/* Change this to true if using phpMyAdmin over https */
+$secure_cookie = false;
+/* Need to have cookie visible from parent directory */
+session_set_cookie_params(0, '/', '', $secure_cookie, true);
+/* Create signon session */
+$session_name = 'SignonSession';
+session_name($session_name);
+// Uncomment and change the following line to match your $cfg['SessionSavePath']
+//session_save_path('/foobar');
+@session_start();
+
+/* Was data posted? */
+if (isset($_POST['user'])) {
+    /* Store there credentials */
+    $_SESSION['PMA_single_signon_user'] = $_POST['user'];
+    $_SESSION['PMA_single_signon_password'] = $_POST['password'];
+    $_SESSION['PMA_single_signon_host'] = $_POST['host'];
+    $_SESSION['PMA_single_signon_port'] = $_POST['port'];
+    /* Update another field of server configuration */
+    $_SESSION['PMA_single_signon_cfgupdate'] = array('verbose' => 'Signon test');
+    $id = session_id();
+    /* Close that session */
+    @session_write_close();
+    /* Redirect to phpMyAdmin (should use absolute URL here!) */
+    header('Location: ../index.php');
+} else {
+    /* Show simple form */
+    header('Content-Type: text/html; charset=utf-8');
+    echo '<?xml version="1.0" encoding="utf-8"?>' , "\n";
+    ?>
+    <!DOCTYPE HTML>
+    <html lang="en" dir="ltr">
+    <head>
+    <link rel="icon" href="../favicon.ico" type="image/x-icon" />
+    <link rel="shortcut icon" href="../favicon.ico" type="image/x-icon" />
+    <meta charset="utf-8" />
+    <title>phpMyAdmin single signon example</title>
+    </head>
+    <body>
+    <?php
+    if (isset($_SESSION['PMA_single_signon_error_message'])) {
+        echo '<p class="error">';
+        echo $_SESSION['PMA_single_signon_error_message'];
+        echo '</p>';
+    }
+    ?>
+    <form action="signon.php" method="post">
+    Username: <input type="text" name="user" /><br />
+    Password: <input type="password" name="password" /><br />
+    Host: (will use the one from config.inc.php by default)
+    <input type="text" name="host" /><br />
+    Port: (will use the one from config.inc.php by default)
+    <input type="text" name="port" /><br />
+    <input type="submit" />
+    </form>
+    </body>
+    </html>
+    <?php
+}
+?>
+
+
+

Alternatively you can also use this way to integrate with OpenID as shown +in examples/openid.php:

+
<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Single signon for phpMyAdmin using OpenID
+ *
+ * This is just example how to use single signon with phpMyAdmin, it is
+ * not intended to be perfect code and look, only shows how you can
+ * integrate this functionality in your application.
+ *
+ * It uses OpenID pear package, see https://pear.php.net/package/OpenID
+ *
+ * User first authenticates using OpenID and based on content of $AUTH_MAP
+ * the login information is passed to phpMyAdmin in session data.
+ *
+ * @package    PhpMyAdmin
+ * @subpackage Example
+ */
+
+if (false === @include_once 'OpenID/RelyingParty.php') {
+    exit;
+}
+
+/* Change this to true if using phpMyAdmin over https */
+$secure_cookie = false;
+
+/**
+ * Map of authenticated users to MySQL user/password pairs.
+ */
+$AUTH_MAP = array(
+    'https://launchpad.net/~username' => array(
+        'user' => 'root',
+        'password' => '',
+        ),
+    );
+
+/**
+ * Simple function to show HTML page with given content.
+ *
+ * @param string $contents Content to include in page
+ *
+ * @return void
+ */
+function Show_page($contents)
+{
+    header('Content-Type: text/html; charset=utf-8');
+    echo '<?xml version="1.0" encoding="utf-8"?>' , "\n";
+    ?>
+    <!DOCTYPE HTML>
+    <html lang="en" dir="ltr">
+    <head>
+    <link rel="icon" href="../favicon.ico" type="image/x-icon" />
+    <link rel="shortcut icon" href="../favicon.ico" type="image/x-icon" />
+    <meta charset="utf-8" />
+    <title>phpMyAdmin OpenID signon example</title>
+    </head>
+    <body>
+    <?php
+    if (isset($_SESSION) && isset($_SESSION['PMA_single_signon_error_message'])) {
+        echo '<p class="error">' , $_SESSION['PMA_single_signon_message'] , '</p>';
+        unset($_SESSION['PMA_single_signon_message']);
+    }
+    echo $contents;
+    ?>
+    </body>
+    </html>
+    <?php
+}
+
+/**
+ * Display error and exit
+ *
+ * @param Exception $e Exception object
+ *
+ * @return void
+ */
+function Die_error($e)
+{
+    $contents = "<div class='relyingparty_results'>\n";
+    $contents .= "<pre>" . htmlspecialchars($e->getMessage()) . "</pre>\n";
+    $contents .= "</div class='relyingparty_results'>";
+    Show_page($contents);
+    exit;
+}
+
+
+/* Need to have cookie visible from parent directory */
+session_set_cookie_params(0, '/', '', $secure_cookie, true);
+/* Create signon session */
+$session_name = 'SignonSession';
+session_name($session_name);
+@session_start();
+
+// Determine realm and return_to
+$base = 'http';
+if (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') {
+    $base .= 's';
+}
+$base .= '://' . $_SERVER['SERVER_NAME'] . ':' . $_SERVER['SERVER_PORT'];
+
+$realm = $base . '/';
+$returnTo = $base . dirname($_SERVER['PHP_SELF']);
+if ($returnTo[strlen($returnTo) - 1] != '/') {
+    $returnTo .= '/';
+}
+$returnTo .= 'openid.php';
+
+/* Display form */
+if (!count($_GET) && !count($_POST) || isset($_GET['phpMyAdmin'])) {
+    /* Show simple form */
+    $content = '<form action="openid.php" method="post">
+OpenID: <input type="text" name="identifier" /><br />
+<input type="submit" name="start" />
+</form>
+</body>
+</html>';
+    Show_page($content);
+    exit;
+}
+
+/* Grab identifier */
+if (isset($_POST['identifier']) && is_string($_POST['identifier'])) {
+    $identifier = $_POST['identifier'];
+} elseif (isset($_SESSION['identifier']) && is_string($_SESSION['identifier'])) {
+    $identifier = $_SESSION['identifier'];
+} else {
+    $identifier = null;
+}
+
+/* Create OpenID object */
+try {
+    $o = new OpenID_RelyingParty($returnTo, $realm, $identifier);
+} catch (Exception $e) {
+    Die_error($e);
+}
+
+/* Redirect to OpenID provider */
+if (isset($_POST['start'])) {
+    try {
+        $authRequest = $o->prepare();
+    } catch (Exception $e) {
+        Die_error($e);
+    }
+
+    $url = $authRequest->getAuthorizeURL();
+
+    header("Location: $url");
+    exit;
+} else {
+    /* Grab query string */
+    if (!count($_POST)) {
+        list(, $queryString) = explode('?', $_SERVER['REQUEST_URI']);
+    } else {
+        // I hate php sometimes
+        $queryString = file_get_contents('php://input');
+    }
+
+    /* Check reply */
+    try {
+        $message = new OpenID_Message($queryString, OpenID_Message::FORMAT_HTTP);
+    } catch (Exception $e) {
+        Die_error($e);
+    }
+
+    $id = $message->get('openid.claimed_id');
+
+    if (!empty($id) && isset($AUTH_MAP[$id])) {
+        $_SESSION['PMA_single_signon_user'] = $AUTH_MAP[$id]['user'];
+        $_SESSION['PMA_single_signon_password'] = $AUTH_MAP[$id]['password'];
+        session_write_close();
+        /* Redirect to phpMyAdmin (should use absolute URL here!) */
+        header('Location: ../index.php');
+    } else {
+        Show_page('<p>User not allowed!</p>');
+        exit;
+    }
+}
+
+
+

If you intend to pass the credentials using some other means than, you have to +implement wrapper in PHP to get that data and set it to +$cfg['Servers'][$i]['SignonScript']. There is very minimal example +in examples/signon-script.php:

+
<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Single signon for phpMyAdmin
+ *
+ * This is just example how to use script based single signon with
+ * phpMyAdmin, it is not intended to be perfect code and look, only
+ * shows how you can integrate this functionality in your application.
+ *
+ * @package    PhpMyAdmin
+ * @subpackage Example
+ */
+
+
+/**
+ * This function returns username and password.
+ *
+ * It can optionally use configured username as parameter.
+ *
+ * @param string $user User name
+ *
+ * @return array
+ */
+function get_login_credentials($user)
+{
+    /* Optionally we can use passed username */
+    if (!empty($user)) {
+        return array($user, 'password');
+    }
+
+    /* Here we would retrieve the credentials */
+    $credentials = array('root', '');
+
+    return $credentials;
+}
+
+
+ +
+
+

Config authentication mode

+
    +
  • This mode is sometimes the less secure one because it requires you to fill the +$cfg['Servers'][$i]['user'] and +$cfg['Servers'][$i]['password'] +fields (and as a result, anyone who can read your config.inc.php +can discover your username and password).
  • +
  • In the ISPs, multi-user installations section, there is an entry explaining how +to protect your configuration file.
  • +
  • For additional security in this mode, you may wish to consider the +Host authentication $cfg['Servers'][$i]['AllowDeny']['order'] +and $cfg['Servers'][$i]['AllowDeny']['rules'] configuration directives.
  • +
  • Unlike cookie and http, does not require a user to log in when first +loading the phpMyAdmin site. This is by design but could allow any +user to access your installation. Use of some restriction method is +suggested, perhaps a .htaccess file with the HTTP-AUTH directive or disallowing +incoming HTTP requests at one’s router or firewall will suffice (both +of which are beyond the scope of this manual but easily searchable +with Google).
  • +
+
+
+
+

Securing your phpMyAdmin installation

+

The phpMyAdmin team tries hard to make the application secure, however there +are always ways to make your installation more secure:

+
    +
  • Follow our Security announcements and upgrade +phpMyAdmin whenever new vulnerability is published.

    +
  • +
  • Serve phpMyAdmin on HTTPS only. Preferably, you should use HSTS as well, so that +you’re protected from protocol downgrade attacks.

    +
  • +
  • Ensure your PHP setup follows recommendations for production sites, for example +display_errors +should be disabled.

    +
  • +
  • Remove the test directory from phpMyAdmin, unless you are developing and need test suite.

    +
  • +
  • Remove the setup directory from phpMyAdmin, you will probably not +use it after the initial setup.

    +
  • +
  • Properly choose an authentication method - Cookie authentication mode +is probably the best choice for shared hosting.

    +
  • +
  • Deny access to auxiliary files in ./libraries/ or +./templates/ subfolders in your webserver configuration. +Such configuration prevents from possible path exposure and cross side +scripting vulnerabilities that might happen to be found in that code. For the +Apache webserver, this is often accomplished with a .htaccess file in +those directories.

    +
  • +
  • Deny access to temporary files, see $cfg['TempDir'] (if that +is placed inside your web root, see also Web server upload/save/import directories.

    +
  • +
  • It is generally a good idea to protect a public phpMyAdmin installation +against access by robots as they usually can not do anything good there. You +can do this using robots.txt file in root of your webserver or limit +access by web server configuration, see 1.42 How can I prevent robots from accessing phpMyAdmin?.

    +
  • +
  • In case you don’t want all MySQL users to be able to access +phpMyAdmin, you can use $cfg['Servers'][$i]['AllowDeny']['rules'] to limit them +or $cfg['Servers'][$i]['AllowRoot'] to deny root user access.

    +
  • +
  • Enable Two-factor authentication for your account.

    +
  • +
  • Consider hiding phpMyAdmin behind an authentication proxy, so that +users need to authenticate prior to providing MySQL credentials +to phpMyAdmin. You can achieve this by configuring your web server to request +HTTP authentication. For example in Apache this can be done with:

    +
    AuthType Basic
    +AuthName "Restricted Access"
    +AuthUserFile /usr/share/phpmyadmin/passwd
    +Require valid-user
    +
    +
    +

    Once you have changed the configuration, you need to create a list of users which +can authenticate. This can be done using the htpasswd utility:

    +
    htpasswd -c /usr/share/phpmyadmin/passwd username
    +
    +
    +
  • +
  • If you are afraid of automated attacks, enabling Captcha by +$cfg['CaptchaLoginPublicKey'] and +$cfg['CaptchaLoginPrivateKey'] might be an option.

    +
  • +
  • Failed login attemps are logged to syslog (if available, see +$cfg['AuthLog']). This can allow using a tool such as +fail2ban to block brute-force attempts. Note that the log file used by syslog +is not the same as the Apache error or access log files.

    +
  • +
  • In case you’re running phpMyAdmin together with other PHP applications, it is +generally advised to use separate session storage for phpMyAdmin to avoid +possible session based attacks against it. You can use +$cfg['SessionSavePath'] to achieve this.

    +
  • +
+
+
+

Using SSL for connection to database server

+

It is recommended to use SSL when connecting to remote database server. There +are several configuration options involved in the SSL setup:

+
+
$cfg['Servers'][$i]['ssl']
+
Defines whether to use SSL at all. If you enable only this, the connection +will be encrypted, but there is not authentication of the connection - you +can not verify that you are talking to the right server.
+
$cfg['Servers'][$i]['ssl_key'] and $cfg['Servers'][$i]['ssl_cert']
+
This is used for authentication of client to the server.
+
$cfg['Servers'][$i]['ssl_ca'] and $cfg['Servers'][$i]['ssl_ca_path']
+
The certificate authorities you trust for server certificates. +This is used to ensure that you are talking to a trusted server.
+
$cfg['Servers'][$i]['ssl_verify']
+
This configuration disables server certificate verification. Use with +caution.
+
+ +
+
+

Known issues

+
+

Users with column-specific privileges are unable to “Browse”

+

If a user has only column-specific privileges on some (but not all) columns in a table, “Browse” +will fail with an error message.

+

As a workaround, a bookmarked query with the same name as the table can be created, this will +run when using the “Browse” link instead. Issue 11922.

+
+
+

Trouble logging back in after logging out using ‘http’ authentication

+

When using the ‘http’ auth_type, it can be impossible to log back in (when the logout comes +manually or after a period of inactivity). Issue 11898.

+
+
+
+ + +
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/php/apps/phpmyadmin49/doc/html/themes.html b/php/apps/phpmyadmin49/doc/html/themes.html new file mode 100644 index 00000000..1ce4b1d8 --- /dev/null +++ b/php/apps/phpmyadmin49/doc/html/themes.html @@ -0,0 +1,224 @@ + + + + + + + + Custom Themes — phpMyAdmin 4.9.1 documentation + + + + + + + + + + + + + + + + + + + +
+
+
+
+ +
+

Custom Themes

+

phpMyAdmin comes with support for third party themes. You can download +additonal themes from our website at <https://www.phpmyadmin.net/themes/>.

+
+

Configuration

+

Themes are configured with $cfg['ThemeManager'] and +$cfg['ThemeDefault']. Under ./themes/, you should not +delete the directory pmahomme or its underlying structure, because this is +the system theme used by phpMyAdmin. pmahomme contains all images and +styles, for backwards compatibility and for all themes that would not include +images or css-files. If $cfg['ThemeManager'] is enabled, you +can select your favorite theme on the main page. Your selected theme will be +stored in a cookie.

+
+
+

Creating custom theme

+

To create a theme:

+
    +
  • make a new subdirectory (for example “your_theme_name”) under ./themes/.
  • +
  • copy the files and directories from pmahomme to “your_theme_name”
  • +
  • edit the css-files in “your_theme_name/css”
  • +
  • put your new images in “your_theme_name/img”
  • +
  • edit layout.inc.php in “your_theme_name”
  • +
  • edit theme.json in “your_theme_name” to contain theme metadata (see below)
  • +
  • make a new screenshot of your theme and save it under +“your_theme_name/screen.png”
  • +
+
+

Theme metadata

+
+

Changed in version 4.8.0: Before 4.8.0 the theme metadata was passed in the info.inc.php file. +It has been replaced by theme.json to allow easier parsing (without +need to handle PHP code) and to support additional features.

+
+

In theme directory there is file theme.json which contains theme +metadata. Currently it consists of:

+
+
+name
+

Display name of the theme.

+

This field is required.

+
+ +
+
+version
+

Theme version, can be quite arbirary and does not have to match phpMyAdmin version.

+

This field is required.

+
+ +
+
+desciption
+

Theme description. this will be shown on the website.

+

This field is required.

+
+ +
+
+author
+

Theme author name.

+

This field is required.

+
+ +
+
+url
+

Link to theme author website. It’s good idea to have way for getting +support there.

+
+ +
+
+supports
+

Array of supported phpMyAdmin major versions.

+

This field is required.

+
+ +

For example, the definition for Original theme shipped with phpMyAdnin 4.8:

+
{
+    "name": "Original",
+    "version": "4.8",
+    "description": "Original phpMyAdmin theme",
+    "author": "phpMyAdmin developers",
+    "url": "https://www.phpmyadmin.net/",
+    "supports": ["4.8"]
+}
+
+
+
+
+

Sharing images

+

If you do not want to use your own symbols and buttons, remove the +directory “img” in “your_theme_name”. phpMyAdmin will use the +default icons and buttons (from the system-theme pmahomme).

+
+
+
+ + +
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/php/apps/phpmyadmin49/doc/html/transformations.html b/php/apps/phpmyadmin49/doc/html/transformations.html new file mode 100644 index 00000000..a187f05d --- /dev/null +++ b/php/apps/phpmyadmin49/doc/html/transformations.html @@ -0,0 +1,245 @@ + + + + + + + + Transformations — phpMyAdmin 4.9.1 documentation + + + + + + + + + + + + + + + + + + + +
+
+
+
+ +
+

Transformations

+
+

Note

+

You need to have configured the phpMyAdmin configuration storage for using transformations +feature.

+
+
+

Introduction

+

To enable transformations, you have to setup the column_info +table and the proper directives. Please see the Configuration on how to do so.

+

You can apply different transformations to the contents of each +column. The transformation will take the content of each column and +transform it with certain rules defined in the selected +transformation.

+

Say you have a column ‘filename’ which contains a filename. Normally +you would see in phpMyAdmin only this filename. Using transformations +you can transform that filename into a HTML link, so you can click +inside of the phpMyAdmin structure on the column’s link and will see +the file displayed in a new browser window. Using transformation +options you can also specify strings to append/prepend to a string or +the format you want the output stored in.

+

For a general overview of all available transformations and their +options, you can consult your <www.your-host.com>/<your-install- +dir>/transformation_overview.php installation.

+

For a tutorial on how to effectively use transformations, see our +Link section on the +official phpMyAdmin homepage.

+
+
+

Usage

+

Go to your tbl_structure.php page (i.e. reached through clicking on +the ‘Structure’ link for a table). There click on “Change” (or change +icon) and there you will see three new fields at the end of the line. +They are called ‘MIME-type’, ‘Browser transformation’ and +‘Transformation options’.

+
    +
  • The field ‘MIME-type’ is a drop-down field. Select the MIME-type that +corresponds to the column’s contents. Please note that transformations +are inactive as long as no MIME-type is selected.
  • +
  • The field ‘Browser transformation’ is a drop-down field. You can +choose from a hopefully growing amount of pre-defined transformations. +See below for information on how to build your own transformation. +There are global transformations and mimetype-bound transformations. +Global transformations can be used for any mimetype. They will take +the mimetype, if necessary, into regard. Mimetype-bound +transformations usually only operate on a certain mimetype. There are +transformations which operate on the main mimetype (like ‘image’), +which will most likely take the subtype into regard, and those who +only operate on a specific subtype (like ‘image/jpeg’). You can use +transformations on mimetypes for which the function was not defined +for. There is no security check for you selected the right +transformation, so take care of what the output will be like.
  • +
  • The field ‘Transformation options’ is a free-type textfield. You have +to enter transform-function specific options here. Usually the +transforms can operate with default options, but it is generally a +good idea to look up the overview to see which options are necessary. +Much like the ENUM/SET-Fields, you have to split up several options +using the format ‘a’,’b’,’c’,...(NOTE THE MISSING BLANKS). This is +because internally the options will be parsed as an array, leaving the +first value the first element in the array, and so forth. If you want +to specify a MIME character set you can define it in the +transformation_options. You have to put that outside of the pre- +defined options of the specific mime-transform, as the last value of +the set. Use the format “’; charset=XXX’”. If you use a transform, for +which you can specify 2 options and you want to append a character +set, enter “‘first parameter’,’second parameter’,’charset=us-ascii’”. +You can, however use the defaults for the parameters: “’‘,’‘,’charset +=us-ascii’”. The default options can be configured using +$cfg['DefaultTransformations']
  • +
+
+
+

File structure

+

All specific transformations for mimetypes are defined through class +files in the directory ‘libraries/classes/Plugins/Transformations/’. Each of +them extends a certain transformation abstract class declared in +libraries/classes/Plugins/Transformations/Abs.

+

They are stored in files to ease up customization and easy adding of +new transformations.

+

Because the user cannot enter own mimetypes, it is kept sure that +transformations always work. It makes no sense to apply a +transformation to a mimetype the transform-function doesn’t know to +handle.

+

There is a file called ‘transformations.lib.php‘ that provides some +basic functions which can be included by any other transform function.

+

The file name convention is [Mimetype]_[Subtype]_[Transformation +Name].class.php, while the abtract class that it extends has the +name [Transformation Name]TransformationsPlugin. All of the +methods that have to be implemented by a transformations plug-in are:

+
    +
  1. getMIMEType() and getMIMESubtype() in the main class;
  2. +
  3. getName(), getInfo() and applyTransformation() in the abstract class +it extends.
  4. +
+

The getMIMEType(), getMIMESubtype() and getName() methods return the +name of the MIME type, MIME Subtype and transformation accordingly. +getInfo() returns the transformation’s description and possible +options it may receive and applyTransformation() is the method that +does the actual work of the transformation plug-in.

+

Please see the libraries/classes/Plugins/Transformations/TEMPLATE and +libraries/classes/Plugins/Transformations/TEMPLATE_ABSTRACT files for adding +your own transformation plug-in. You can also generate a new +transformation plug-in (with or without the abstract transformation +class), by using +scripts/transformations_generator_plugin.sh or +scripts/transformations_generator_main_class.sh.

+

The applyTransformation() method always gets passed three variables:

+
    +
  1. $buffer - Contains the text inside of the column. This is the +text, you want to transform.
  2. +
  3. $options - Contains any user-passed options to a transform +function as an array.
  4. +
  5. $meta - Contains an object with information about your column. The +data is drawn from the output of the mysql_fetch_field() function. This means, all +object properties described on the manual page are available in this +variable and can be used to transform a column accordingly to +unsigned/zerofill/not_null/... properties. The $meta->mimetype +variable contains the original MIME-type of the column (i.e. +‘text/plain’, ‘image/jpeg’ etc.)
  6. +
+
+
+ + +
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/php/apps/phpmyadmin49/doc/html/two_factor.html b/php/apps/phpmyadmin49/doc/html/two_factor.html new file mode 100644 index 00000000..c33287e3 --- /dev/null +++ b/php/apps/phpmyadmin49/doc/html/two_factor.html @@ -0,0 +1,180 @@ + + + + + + + + Two-factor authentication — phpMyAdmin 4.9.1 documentation + + + + + + + + + + + + + + + + + + + +
+
+
+
+ +
+

Two-factor authentication

+
+

New in version 4.8.0.

+
+

Since phpMyAdmin 4.8.0 you can configure two-factor authentication to be +used when logging in. To use this, you first need to configure the +phpMyAdmin configuration storage. Once this is done, every user can opt-in for the second +authentication factor in the Settings.

+

When running phpMyAdmin from the Git source repository, the dependencies must be installed +manually; the typical way of doing so is with the command:

+
composer require pragmarx/google2fa bacon/bacon-qr-code
+
+
+

Or when using a hardware security key with FIDO U2F:

+
composer require samyoul/u2f-php-server
+
+
+
+

Authentication Application (2FA)

+

Using an application for authentication is a quite common approach based on HOTP and +TOTP. +It is based on transmitting a private key from phpMyAdmin to the authentication +application and the application is then able to generate one time codes based +on this key. The easiest way to enter the key in to the application from phpMyAdmin is +through scanning a QR code.

+

There are dozens of applications available for mobile phones to implement these +standards, the most widely used include:

+ +
+
+

Hardware Security Key (FIDO U2F)

+

Using hardware tokens is considered to be more secure than a software based +solution. phpMyAdmin supports FIDO U2F +tokens.

+

There are several manufacturers of these tokens, for example:

+ +
+
+

Simple two-factor authentication

+

This authentication is included for testing and demonstration purposes only as +it really does not provide two-factor authentication, it just asks the user to confirm login by +clicking on the button.

+

It should not be used in the production and is disabled unless +$cfg['DBG']['simple2fa'] is set.

+
+
+ + +
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/php/apps/phpmyadmin49/doc/html/user.html b/php/apps/phpmyadmin49/doc/html/user.html new file mode 100644 index 00000000..a02328bd --- /dev/null +++ b/php/apps/phpmyadmin49/doc/html/user.html @@ -0,0 +1,169 @@ + + + + + + + + User Guide — phpMyAdmin 4.9.1 documentation + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/php/apps/phpmyadmin49/doc/html/vendors.html b/php/apps/phpmyadmin49/doc/html/vendors.html new file mode 100644 index 00000000..72af36a1 --- /dev/null +++ b/php/apps/phpmyadmin49/doc/html/vendors.html @@ -0,0 +1,145 @@ + + + + + + + + Distributing and packaging phpMyAdmin — phpMyAdmin 4.9.1 documentation + + + + + + + + + + + + + + + + + + +
+
+
+
+ +
+

Distributing and packaging phpMyAdmin

+

This document is intended to give advices to people who want to +redistribute phpMyAdmin inside other software package such as Linux +distribution or some all in one package including web server and MySQL +server.

+

Generally you can customize some basic aspects (paths to some files and +behavior) in libraries/vendor_config.php.

+

For example if you want setup script to generate config file in var, change +SETUP_CONFIG_FILE to /var/lib/phpmyadmin/config.inc.php and you +will also probably want to skip directory writable check, so set +SETUP_DIR_WRITABLE to false.

+
+

External libraries

+

phpMyAdmin includes several external libraries, you might want to +replace them with system ones if they are available, but please note +that you should test whether version you provide is compatible with the +one we ship.

+

Currently known list of external libraries:

+
+
js/jquery
+
jQuery js framework and various jQuery based libraries.
+
vendor/
+
The download kit includes various Composer packages as +dependencies.
+
+
+
+ + +
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/php/apps/phpmyadmin49/error_report.php b/php/apps/phpmyadmin49/error_report.php new file mode 100644 index 00000000..3e9c9a6b --- /dev/null +++ b/php/apps/phpmyadmin49/error_report.php @@ -0,0 +1,135 @@ += 3 + && ($_SESSION['prev_error_subm_time']-time()) <= 3000 + ) { + $_SESSION['error_subm_count'] = 0; + $_SESSION['prev_errors'] = ''; + $response->addJSON('_stopErrorReportLoop', '1'); + } else { + $_SESSION['prev_error_subm_time'] = time(); + $_SESSION['error_subm_count'] = ( + (isset($_SESSION['error_subm_count'])) + ? ($_SESSION['error_subm_count']+1) + : (0) + ); + } + } + $reportData = $errorReport->getData($_POST['exception_type']); + // report if and only if there were 'actual' errors. + if (count($reportData) > 0) { + $server_response = $errorReport->send($reportData); + if (! is_string($server_response)) { + $success = false; + } else { + $decoded_response = json_decode($server_response, true); + $success = !empty($decoded_response) ? + $decoded_response["success"] : false; + } + + /* Message to show to the user */ + if ($success) { + if ((isset($_POST['automatic']) + && $_POST['automatic'] === "true") + || $GLOBALS['cfg']['SendErrorReports'] == 'always' + ) { + $msg = __( + 'An error has been detected and an error report has been ' + . 'automatically submitted based on your settings.' + ); + } else { + $msg = __('Thank you for submitting this report.'); + } + } else { + $msg = __( + 'An error has been detected and an error report has been ' + . 'generated but failed to be sent.' + ) + . ' ' + . __( + 'If you experience any ' + . 'problems please submit a bug report manually.' + ); + } + $msg .= ' ' . __('You may want to refresh the page.'); + + /* Create message object */ + if ($success) { + $msg = Message::notice($msg); + } else { + $msg = Message::error($msg); + } + + /* Add message to response */ + if ($response->isAjax()) { + if ($_POST['exception_type'] == 'js') { + $response->addJSON('message', $msg); + } else { + $response->addJSON('_errSubmitMsg', $msg); + } + } elseif ($_POST['exception_type'] == 'php') { + $jsCode = 'PMA_ajaxShowMessage("
' + . $msg + . '
", false);'; + $response->getFooter()->getScripts()->addCode($jsCode); + } + + if ($_POST['exception_type'] == 'php') { + // clear previous errors & save new ones. + $GLOBALS['error_handler']->savePreviousErrors(); + } + + /* Persist always send settings */ + if (isset($_POST['always_send']) + && $_POST['always_send'] === "true" + ) { + $userPreferences = new UserPreferences(); + $userPreferences->persistOption("SendErrorReports", "always", "ask"); + } + } +} elseif (! empty($_POST['get_settings'])) { + $response->addJSON('report_setting', $GLOBALS['cfg']['SendErrorReports']); +} else { + if ($_POST['exception_type'] == 'js') { + $response->addHTML($errorReport->getForm()); + } else { + // clear previous errors & save new ones. + $GLOBALS['error_handler']->savePreviousErrors(); + } +} diff --git a/php/apps/phpmyadmin49/examples/config.manyhosts.inc.php b/php/apps/phpmyadmin49/examples/config.manyhosts.inc.php new file mode 100644 index 00000000..b6a6a127 --- /dev/null +++ b/php/apps/phpmyadmin49/examples/config.manyhosts.inc.php @@ -0,0 +1,52 @@ + array( + 'user' => 'root', + 'password' => '', + ), + ); + +/** + * Simple function to show HTML page with given content. + * + * @param string $contents Content to include in page + * + * @return void + */ +function Show_page($contents) +{ + header('Content-Type: text/html; charset=utf-8'); + echo '' , "\n"; + ?> + + + + + + + phpMyAdmin OpenID signon example + + + ' , $_SESSION['PMA_single_signon_message'] , '

'; + unset($_SESSION['PMA_single_signon_message']); + } + echo $contents; + ?> + + + \n"; + $contents .= "
" . htmlspecialchars($e->getMessage()) . "
\n"; + $contents .= ""; + Show_page($contents); + exit; +} + + +/* Need to have cookie visible from parent directory */ +session_set_cookie_params(0, '/', '', $secure_cookie, true); +/* Create signon session */ +$session_name = 'SignonSession'; +session_name($session_name); +@session_start(); + +// Determine realm and return_to +$base = 'http'; +if (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') { + $base .= 's'; +} +$base .= '://' . $_SERVER['SERVER_NAME'] . ':' . $_SERVER['SERVER_PORT']; + +$realm = $base . '/'; +$returnTo = $base . dirname($_SERVER['PHP_SELF']); +if ($returnTo[strlen($returnTo) - 1] != '/') { + $returnTo .= '/'; +} +$returnTo .= 'openid.php'; + +/* Display form */ +if (!count($_GET) && !count($_POST) || isset($_GET['phpMyAdmin'])) { + /* Show simple form */ + $content = '
+OpenID:
+ +
+ +'; + Show_page($content); + exit; +} + +/* Grab identifier */ +if (isset($_POST['identifier']) && is_string($_POST['identifier'])) { + $identifier = $_POST['identifier']; +} elseif (isset($_SESSION['identifier']) && is_string($_SESSION['identifier'])) { + $identifier = $_SESSION['identifier']; +} else { + $identifier = null; +} + +/* Create OpenID object */ +try { + $o = new OpenID_RelyingParty($returnTo, $realm, $identifier); +} catch (Exception $e) { + Die_error($e); +} + +/* Redirect to OpenID provider */ +if (isset($_POST['start'])) { + try { + $authRequest = $o->prepare(); + } catch (Exception $e) { + Die_error($e); + } + + $url = $authRequest->getAuthorizeURL(); + + header("Location: $url"); + exit; +} else { + /* Grab query string */ + if (!count($_POST)) { + list(, $queryString) = explode('?', $_SERVER['REQUEST_URI']); + } else { + // I hate php sometimes + $queryString = file_get_contents('php://input'); + } + + /* Check reply */ + try { + $message = new OpenID_Message($queryString, OpenID_Message::FORMAT_HTTP); + } catch (Exception $e) { + Die_error($e); + } + + $id = $message->get('openid.claimed_id'); + + if (!empty($id) && isset($AUTH_MAP[$id])) { + $_SESSION['PMA_single_signon_user'] = $AUTH_MAP[$id]['user']; + $_SESSION['PMA_single_signon_password'] = $AUTH_MAP[$id]['password']; + session_write_close(); + /* Redirect to phpMyAdmin (should use absolute URL here!) */ + header('Location: ../index.php'); + } else { + Show_page('

User not allowed!

'); + exit; + } +} diff --git a/php/apps/phpmyadmin49/examples/signon-script.php b/php/apps/phpmyadmin49/examples/signon-script.php new file mode 100644 index 00000000..7a2b8aa2 --- /dev/null +++ b/php/apps/phpmyadmin49/examples/signon-script.php @@ -0,0 +1,35 @@ + 'Signon test'); + $id = session_id(); + /* Close that session */ + @session_write_close(); + /* Redirect to phpMyAdmin (should use absolute URL here!) */ + header('Location: ../index.php'); +} else { + /* Show simple form */ + header('Content-Type: text/html; charset=utf-8'); + echo '' , "\n"; + ?> + + + + + + + phpMyAdmin single signon example + + + '; + echo $_SESSION['PMA_single_signon_error_message']; + echo '

'; + } + ?> +
+ Username:
+ Password:
+ Host: (will use the one from config.inc.php by default) +
+ Port: (will use the one from config.inc.php by default) +
+ +
+ + + diff --git a/php/apps/phpmyadmin49/export.php b/php/apps/phpmyadmin49/export.php new file mode 100644 index 00000000..c9338333 --- /dev/null +++ b/php/apps/phpmyadmin49/export.php @@ -0,0 +1,555 @@ +getHeader(); +$scripts = $header->getScripts(); +$scripts->addFile('export_output.js'); + +//check if it's the GET request to check export time out +if (isset($_GET['check_time_out'])) { + if (isset($_SESSION['pma_export_error'])) { + $err = $_SESSION['pma_export_error']; + unset($_SESSION['pma_export_error']); + echo "timeout"; + } else { + echo "success"; + } + exit; +} + +/** + * Sets globals from $_POST + * + * - Please keep the parameters in order of their appearance in the form + * - Some of these parameters are not used, as the code below directly + * verifies from the superglobal $_POST or $_REQUEST + * TODO: this should be removed to avoid passing user input to GLOBALS + * without checking + */ +$post_params = array( + 'db', + 'table', + 'what', + 'single_table', + 'export_type', + 'export_method', + 'quick_or_custom', + 'db_select', + 'table_select', + 'table_structure', + 'table_data', + 'limit_to', + 'limit_from', + 'allrows', + 'lock_tables', + 'output_format', + 'filename_template', + 'maxsize', + 'remember_template', + 'charset', + 'compression', + 'as_separate_files', + 'knjenc', + 'xkana', + 'htmlword_structure_or_data', + 'htmlword_null', + 'htmlword_columns', + 'mediawiki_headers', + 'mediawiki_structure_or_data', + 'mediawiki_caption', + 'pdf_structure_or_data', + 'odt_structure_or_data', + 'odt_relation', + 'odt_comments', + 'odt_mime', + 'odt_columns', + 'odt_null', + 'codegen_structure_or_data', + 'codegen_format', + 'excel_null', + 'excel_removeCRLF', + 'excel_columns', + 'excel_edition', + 'excel_structure_or_data', + 'yaml_structure_or_data', + 'ods_null', + 'ods_structure_or_data', + 'ods_columns', + 'json_structure_or_data', + 'json_pretty_print', + 'json_unicode', + 'xml_structure_or_data', + 'xml_export_events', + 'xml_export_functions', + 'xml_export_procedures', + 'xml_export_tables', + 'xml_export_triggers', + 'xml_export_views', + 'xml_export_contents', + 'texytext_structure_or_data', + 'texytext_columns', + 'texytext_null', + 'phparray_structure_or_data', + 'sql_include_comments', + 'sql_header_comment', + 'sql_dates', + 'sql_relation', + 'sql_mime', + 'sql_use_transaction', + 'sql_disable_fk', + 'sql_compatibility', + 'sql_structure_or_data', + 'sql_create_database', + 'sql_drop_table', + 'sql_procedure_function', + 'sql_create_table', + 'sql_create_view', + 'sql_create_trigger', + 'sql_if_not_exists', + 'sql_auto_increment', + 'sql_backquotes', + 'sql_truncate', + 'sql_delayed', + 'sql_ignore', + 'sql_type', + 'sql_insert_syntax', + 'sql_max_query_size', + 'sql_hex_for_binary', + 'sql_utc_time', + 'sql_drop_database', + 'sql_views_as_tables', + 'sql_metadata', + 'csv_separator', + 'csv_enclosed', + 'csv_escaped', + 'csv_terminated', + 'csv_null', + 'csv_removeCRLF', + 'csv_columns', + 'csv_structure_or_data', + // csv_replace should have been here but we use it directly from $_POST + 'latex_caption', + 'latex_structure_or_data', + 'latex_structure_caption', + 'latex_structure_continued_caption', + 'latex_structure_label', + 'latex_relation', + 'latex_comments', + 'latex_mime', + 'latex_columns', + 'latex_data_caption', + 'latex_data_continued_caption', + 'latex_data_label', + 'latex_null', + 'aliases' +); + +foreach ($post_params as $one_post_param) { + if (isset($_POST[$one_post_param])) { + $GLOBALS[$one_post_param] = $_POST[$one_post_param]; + } +} + +$table = $GLOBALS['table']; + +PhpMyAdmin\Util::checkParameters(array('what', 'export_type')); + +// sanitize this parameter which will be used below in a file inclusion +$what = Core::securePath($_POST['what']); + +// export class instance, not array of properties, as before +/* @var $export_plugin ExportPlugin */ +$export_plugin = Plugins::getPlugin( + "export", + $what, + 'libraries/classes/Plugins/Export/', + array( + 'export_type' => $export_type, + 'single_table' => isset($single_table) + ) +); + +// Check export type +if (empty($export_plugin)) { + Core::fatalError(__('Bad type!')); +} + +/** + * valid compression methods + */ +$compression_methods = array( + 'zip', + 'gzip' +); + +/** + * init and variable checking + */ +$compression = false; +$onserver = false; +$save_on_server = false; +$buffer_needed = false; +$back_button = ''; +$refreshButton = ''; +$save_filename = ''; +$file_handle = ''; +$err_url = ''; +$filename = ''; +$separate_files = ''; + +// Is it a quick or custom export? +if (isset($_POST['quick_or_custom']) + && $_POST['quick_or_custom'] == 'quick' +) { + $quick_export = true; +} else { + $quick_export = false; +} + +if ($_POST['output_format'] == 'astext') { + $asfile = false; +} else { + $asfile = true; + if (isset($_POST['as_separate_files']) + && ! empty($_POST['as_separate_files']) + ) { + if (isset($_POST['compression']) + && ! empty($_POST['compression']) + && $_POST['compression'] == 'zip' + ) { + $separate_files = $_POST['as_separate_files']; + } + } + if (in_array($_POST['compression'], $compression_methods)) { + $compression = $_POST['compression']; + $buffer_needed = true; + } + if (($quick_export && ! empty($_POST['quick_export_onserver'])) + || (! $quick_export && ! empty($_POST['onserver'])) + ) { + if ($quick_export) { + $onserver = $_POST['quick_export_onserver']; + } else { + $onserver = $_POST['onserver']; + } + // Will we save dump on server? + $save_on_server = ! empty($cfg['SaveDir']) && $onserver; + } +} + +/** + * If we are sending the export file (as opposed to just displaying it + * as text), we have to bypass the usual PhpMyAdmin\Response mechanism + */ +if (isset($_POST['output_format']) && $_POST['output_format'] == 'sendit' && ! $save_on_server) { + $response->disable(); + //Disable all active buffers (see: ob_get_status(true) at this point) + while (@ob_end_clean()); +} + +// Generate error url and check for needed variables +if ($export_type == 'server') { + $err_url = 'server_export.php' . Url::getCommon(); +} elseif ($export_type == 'database' && strlen($db) > 0) { + $err_url = 'db_export.php' . Url::getCommon(array('db' => $db)); + // Check if we have something to export + if (isset($table_select)) { + $tables = $table_select; + } else { + $tables = array(); + } +} elseif ($export_type == 'table' && strlen($db) > 0 && strlen($table) > 0) { + $err_url = 'tbl_export.php' . Url::getCommon( + array( + 'db' => $db, 'table' => $table + ) + ); +} else { + Core::fatalError(__('Bad parameters!')); +} + +// Merge SQL Query aliases with Export aliases from +// export page, Export page aliases are given more +// preference over SQL Query aliases. +$parser = new \PhpMyAdmin\SqlParser\Parser($sql_query); +$aliases = array(); +if ((!empty($parser->statements[0])) + && ($parser->statements[0] instanceof \PhpMyAdmin\SqlParser\Statements\SelectStatement) +) { + $aliases = \PhpMyAdmin\SqlParser\Utils\Misc::getAliases($parser->statements[0], $db); +} +if (!empty($_POST['aliases'])) { + $aliases = Export::mergeAliases($aliases, $_POST['aliases']); + $_SESSION['tmpval']['aliases'] = $_POST['aliases']; +} + +/** + * Increase time limit for script execution and initializes some variables + */ +Util::setTimeLimit(); +if (! empty($cfg['MemoryLimit'])) { + ini_set('memory_limit', $cfg['MemoryLimit']); +} +register_shutdown_function('PhpMyAdmin\Export::shutdown'); +// Start with empty buffer +$dump_buffer = ''; +$dump_buffer_len = 0; + +// Array of dump_buffers - used in separate file exports +$dump_buffer_objects = array(); + +// We send fake headers to avoid browser timeout when buffering +$time_start = time(); + +// Defines the default format. +// For SQL always use \n as MySQL wants this on all platforms. +if ($what == 'sql') { + $crlf = "\n"; +} else { + $crlf = PHP_EOL; +} + +$output_kanji_conversion = Encoding::canConvertKanji(); + +// Do we need to convert charset? +$output_charset_conversion = $asfile + && Encoding::isSupported() + && isset($charset) && $charset != 'utf-8'; + +// Use on the fly compression? +$GLOBALS['onfly_compression'] = $GLOBALS['cfg']['CompressOnFly'] + && $compression == 'gzip'; +if ($GLOBALS['onfly_compression']) { + $GLOBALS['memory_limit'] = Export::getMemoryLimit(); +} + +// Generate filename and mime type if needed +if ($asfile) { + if (empty($remember_template)) { + $remember_template = ''; + } + list($filename, $mime_type) = Export::getFilenameAndMimetype( + $export_type, $remember_template, $export_plugin, $compression, + $filename_template + ); +} else { + $mime_type = ''; +} + +// Open file on server if needed +if ($save_on_server) { + list($save_filename, $message, $file_handle) = Export::openFile( + $filename, $quick_export + ); + + // problem opening export file on server? + if (! empty($message)) { + Export::showPage($db, $table, $export_type); + } +} else { + /** + * Send headers depending on whether the user chose to download a dump file + * or not + */ + if ($asfile) { + // Download + // (avoid rewriting data containing HTML with anchors and forms; + // this was reported to happen under Plesk) + ini_set('url_rewriter.tags', ''); + $filename = Sanitize::sanitizeFilename($filename); + + Core::downloadHeader($filename, $mime_type); + } else { + // HTML + if ($export_type == 'database') { + $num_tables = count($tables); + if ($num_tables == 0) { + $message = PhpMyAdmin\Message::error( + __('No tables found in database.') + ); + $active_page = 'db_export.php'; + include 'db_export.php'; + exit(); + } + } + list($html, $back_button, $refreshButton) = Export::getHtmlForDisplayedExportHeader( + $export_type, $db, $table + ); + echo $html; + unset($html); + } // end download +} + +$relation = new Relation(); + +// Fake loop just to allow skip of remain of this code by break, I'd really +// need exceptions here :-) +do { + // Re - initialize + $dump_buffer = ''; + $dump_buffer_len = 0; + + // Add possibly some comments to export + if (! $export_plugin->exportHeader()) { + break; + } + + // Will we need relation & co. setup? + $do_relation = isset($GLOBALS[$what . '_relation']); + $do_comments = isset($GLOBALS[$what . '_include_comments']) + || isset($GLOBALS[$what . '_comments']); + $do_mime = isset($GLOBALS[$what . '_mime']); + if ($do_relation || $do_comments || $do_mime) { + $cfgRelation = $relation->getRelationsParam(); + } + + // Include dates in export? + $do_dates = isset($GLOBALS[$what . '_dates']); + + $whatStrucOrData = $GLOBALS[$what . '_structure_or_data']; + + /** + * Builds the dump + */ + if ($export_type == 'server') { + if (! isset($db_select)) { + $db_select = ''; + } + Export::exportServer( + $db_select, $whatStrucOrData, $export_plugin, $crlf, $err_url, + $export_type, $do_relation, $do_comments, $do_mime, $do_dates, + $aliases, $separate_files + ); + } elseif ($export_type == 'database') { + if (!isset($table_structure) || !is_array($table_structure)) { + $table_structure = array(); + } + if (!isset($table_data) || !is_array($table_data)) { + $table_data = array(); + } + if (!empty($_POST['structure_or_data_forced'])) { + $table_structure = $tables; + $table_data = $tables; + } + if (isset($lock_tables)) { + Export::lockTables($db, $tables, "READ"); + try { + Export::exportDatabase( + $db, $tables, $whatStrucOrData, $table_structure, + $table_data, $export_plugin, $crlf, $err_url, $export_type, + $do_relation, $do_comments, $do_mime, $do_dates, $aliases, + $separate_files + ); + } finally { + Export::unlockTables(); + } + } else { + Export::exportDatabase( + $db, $tables, $whatStrucOrData, $table_structure, $table_data, + $export_plugin, $crlf, $err_url, $export_type, $do_relation, + $do_comments, $do_mime, $do_dates, $aliases, $separate_files + ); + } + } else { + // We export just one table + // $allrows comes from the form when "Dump all rows" has been selected + if (! isset($allrows)) { + $allrows = ''; + } + if (! isset($limit_to)) { + $limit_to = 0; + } + if (! isset($limit_from)) { + $limit_from = 0; + } + if (isset($lock_tables)) { + try { + Export::lockTables($db, array($table), "READ"); + Export::exportTable( + $db, $table, $whatStrucOrData, $export_plugin, $crlf, + $err_url, $export_type, $do_relation, $do_comments, + $do_mime, $do_dates, $allrows, $limit_to, $limit_from, + $sql_query, $aliases + ); + } finally { + Export::unlockTables(); + } + } else { + Export::exportTable( + $db, $table, $whatStrucOrData, $export_plugin, $crlf, $err_url, + $export_type, $do_relation, $do_comments, $do_mime, $do_dates, + $allrows, $limit_to, $limit_from, $sql_query, $aliases + ); + } + } + if (! $export_plugin->exportFooter()) { + break; + } + +} while (false); +// End of fake loop + +if ($save_on_server && ! empty($message)) { + Export::showPage($db, $table, $export_type); +} + +/** + * Send the dump as a file... + */ +if (empty($asfile)) { + echo Export::getHtmlForDisplayedExportFooter($back_button, $refreshButton); + return; +} // end if + +// Convert the charset if required. +if ($output_charset_conversion) { + $dump_buffer = Encoding::convertString( + 'utf-8', + $GLOBALS['charset'], + $dump_buffer + ); +} + +// Compression needed? +if ($compression) { + if (! empty($separate_files)) { + $dump_buffer = Export::compress( + $dump_buffer_objects, $compression, $filename + ); + } else { + $dump_buffer = Export::compress($dump_buffer, $compression, $filename); + } + +} + +/* If we saved on server, we have to close file now */ +if ($save_on_server) { + $message = Export::closeFile( + $file_handle, $dump_buffer, $save_filename + ); + Export::showPage($db, $table, $export_type); +} else { + echo $dump_buffer; +} diff --git a/php/apps/phpmyadmin49/favicon.ico b/php/apps/phpmyadmin49/favicon.ico new file mode 100644 index 00000000..fb156b22 Binary files /dev/null and b/php/apps/phpmyadmin49/favicon.ico differ diff --git a/php/apps/phpmyadmin49/gis_data_editor.php b/php/apps/phpmyadmin49/gis_data_editor.php new file mode 100644 index 00000000..f8403f16 --- /dev/null +++ b/php/apps/phpmyadmin49/gis_data_editor.php @@ -0,0 +1,436 @@ +generateParams($_POST['value']) + ); +} + +// Generate Well Known Text +$srid = (isset($gis_data['srid']) && $gis_data['srid'] != '') + ? htmlspecialchars($gis_data['srid']) : 0; +$wkt = $gis_obj->generateWkt($gis_data, 0); +$wkt_with_zero = $gis_obj->generateWkt($gis_data, 0, '0'); +$result = "'" . $wkt . "'," . $srid; + +// Generate SVG based visualization +$visualizationSettings = array( + 'width' => 450, + 'height' => 300, + 'spatialColumn' => 'wkt', + 'mysqlVersion' => $GLOBALS['dbi']->getVersion() +); +$data = array(array('wkt' => $wkt_with_zero, 'srid' => $srid)); +$visualization = GisVisualization::getByData($data, $visualizationSettings) + ->toImage('svg'); + +$open_layers = GisVisualization::getByData($data, $visualizationSettings) + ->asOl(); + +// If the call is to update the WKT and visualization make an AJAX response +if (isset($_POST['generate']) && $_POST['generate'] == true) { + $extra_data = array( + 'result' => $result, + 'visualization' => $visualization, + 'openLayers' => $open_layers, + ); + $response = Response::getInstance(); + $response->addJSON($extra_data); + exit; +} + +ob_start(); + +echo '
'; +echo ''; +echo '
'; + +echo '

'; +printf( + __('Value for the column "%s"'), + htmlspecialchars($_POST['field']) +); +echo '

'; + +echo ''; +// The input field to which the final result should be added +// and corresponding null checkbox +if (isset($_POST['input_name'])) { + echo ''; +} +echo Url::getHiddenInputs(); + +echo ''; +echo '
'; +echo $visualization; +echo '
'; + +// No not remove inline style or it will cause "Cannot read property 'w' of null" on GIS editor map init +echo '
'; +echo '
'; + +echo '
'; +echo ''; +echo ''; +echo '
'; + +echo ''; +echo ''; + + +echo ''; +echo '
'; +echo ''; +echo '    '; +/* l10n: Spatial Reference System Identifier */ +echo ''; +echo ''; +echo '
'; +echo ''; + +echo ''; +echo '
'; + +$geom_count = 1; +if ($geom_type == 'GEOMETRYCOLLECTION') { + $geom_count = (isset($gis_data[$geom_type]['geom_count'])) + ? intval($gis_data[$geom_type]['geom_count']) : 1; + if (isset($gis_data[$geom_type]['add_geom'])) { + $geom_count++; + } + echo ''; +} + +for ($a = 0; $a < $geom_count; $a++) { + if (! isset($gis_data[$a])) { + continue; + } + + if ($geom_type == 'GEOMETRYCOLLECTION') { + echo '

'; + printf(__('Geometry %d:'), $a + 1); + echo '
'; + if (isset($gis_data[$a]['gis_type'])) { + $type = htmlspecialchars($gis_data[$a]['gis_type']); + } else { + $type = $gis_types[0]; + } + echo ''; + } else { + $type = $geom_type; + } + + if ($type == 'POINT') { + echo '
'; + echo __('Point:'); + echo ''; + echo ''; + echo ''; + echo ''; + + } elseif ($type == 'MULTIPOINT' || $type == 'LINESTRING') { + $no_of_points = isset($gis_data[$a][$type]['no_of_points']) + ? intval($gis_data[$a][$type]['no_of_points']) : 1; + if ($type == 'LINESTRING' && $no_of_points < 2) { + $no_of_points = 2; + } + if ($type == 'MULTIPOINT' && $no_of_points < 1) { + $no_of_points = 1; + } + + if (isset($gis_data[$a][$type]['add_point'])) { + $no_of_points++; + } + echo ''; + + for ($i = 0; $i < $no_of_points; $i++) { + echo '
'; + printf(__('Point %d'), $i + 1); + echo ': '; + echo ''; + echo ''; + echo ''; + echo ''; + } + echo ''; + + } elseif ($type == 'MULTILINESTRING' || $type == 'POLYGON') { + $no_of_lines = isset($gis_data[$a][$type]['no_of_lines']) + ? intval($gis_data[$a][$type]['no_of_lines']) : 1; + if ($no_of_lines < 1) { + $no_of_lines = 1; + } + if (isset($gis_data[$a][$type]['add_line'])) { + $no_of_lines++; + } + echo ''; + + for ($i = 0; $i < $no_of_lines; $i++) { + echo '
'; + if ($type == 'MULTILINESTRING') { + printf(__('Linestring %d:'), $i + 1); + } else { + if ($i == 0) { + echo __('Outer ring:'); + } else { + printf(__('Inner ring %d:'), $i); + } + } + + $no_of_points = isset($gis_data[$a][$type][$i]['no_of_points']) + ? intval($gis_data[$a][$type][$i]['no_of_points']) : 2; + if ($type == 'MULTILINESTRING' && $no_of_points < 2) { + $no_of_points = 2; + } + if ($type == 'POLYGON' && $no_of_points < 4) { + $no_of_points = 4; + } + if (isset($gis_data[$a][$type][$i]['add_point'])) { + $no_of_points++; + } + echo ''; + + for ($j = 0; $j < $no_of_points; $j++) { + echo('
'); + printf(__('Point %d'), $j + 1); + echo ': '; + echo ''; + echo ''; + echo ''; + echo ''; + } + echo ''; + } + $caption = ($type == 'MULTILINESTRING') + ? __('Add a linestring') + : __('Add an inner ring'); + echo '
'; + echo ''; + + } elseif ($type == 'MULTIPOLYGON') { + $no_of_polygons = isset($gis_data[$a][$type]['no_of_polygons']) + ? intval($gis_data[$a][$type]['no_of_polygons']) : 1; + if ($no_of_polygons < 1) { + $no_of_polygons = 1; + } + if (isset($gis_data[$a][$type]['add_polygon'])) { + $no_of_polygons++; + } + echo ''; + + for ($k = 0; $k < $no_of_polygons; $k++) { + echo '
'; + printf(__('Polygon %d:'), $k + 1); + $no_of_lines = isset($gis_data[$a][$type][$k]['no_of_lines']) + ? intval($gis_data[$a][$type][$k]['no_of_lines']) : 1; + if ($no_of_lines < 1) { + $no_of_lines = 1; + } + if (isset($gis_data[$a][$type][$k]['add_line'])) { + $no_of_lines++; + } + echo ''; + + for ($i = 0; $i < $no_of_lines; $i++) { + echo '

'; + if ($i == 0) { + echo __('Outer ring:'); + } else { + printf(__('Inner ring %d:'), $i); + } + + $no_of_points = isset($gis_data[$a][$type][$k][$i]['no_of_points']) + ? intval($gis_data[$a][$type][$k][$i]['no_of_points']) : 4; + if ($no_of_points < 4) { + $no_of_points = 4; + } + if (isset($gis_data[$a][$type][$k][$i]['add_point'])) { + $no_of_points++; + } + echo ''; + + for ($j = 0; $j < $no_of_points; $j++) { + echo '
'; + printf(__('Point %d'), $j + 1); + echo ': '; + echo ''; + echo ''; + echo ''; + echo ''; + } + echo ''; + } + echo '
'; + echo ''; + echo '
'; + } + echo '
'; + echo ''; + } +} +if ($geom_type == 'GEOMETRYCOLLECTION') { + echo '

'; + echo ''; +} +echo '
'; +echo ''; + +echo '
'; +echo ''; + +echo '
'; +echo '

' , __('Output') , '

'; +echo '

'; +echo __( + 'Choose "ST_GeomFromText" from the "Function" column and paste the' + . ' string below into the "Value" field.' +); +echo '

'; +echo ''; +echo '
'; + +echo '
'; +echo '
'; + +Response::getInstance()->addJSON('gis_editor', ob_get_contents()); +ob_end_clean(); diff --git a/php/apps/phpmyadmin49/html/CODE_OF_CONDUCT.md b/php/apps/phpmyadmin49/html/CODE_OF_CONDUCT.md new file mode 100644 index 00000000..dbaab727 --- /dev/null +++ b/php/apps/phpmyadmin49/html/CODE_OF_CONDUCT.md @@ -0,0 +1,46 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at info@phpmyadmin.net. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] + +[homepage]: http://contributor-covenant.org +[version]: http://contributor-covenant.org/version/1/4/ diff --git a/php/apps/phpmyadmin49/html/CONTRIBUTING.md b/php/apps/phpmyadmin49/html/CONTRIBUTING.md new file mode 100644 index 00000000..36638099 --- /dev/null +++ b/php/apps/phpmyadmin49/html/CONTRIBUTING.md @@ -0,0 +1,46 @@ +# Contributing to phpMyAdmin + +As an open source project, phpMyAdmin welcomes contributions of many forms. + +## Bug reporting + +We appreciate your effort to improve phpMyAdmin by submitting a bug report. Before doing so, please check the following things: + +1. Check whether the bug you face **hasn't been already reported**. Duplicate reports takes us time, that we could be used to fix other bugs or make improvements. +2. Specify the phpMyAdmin, server, PHP, MySQL and browser information that may be helpful to fix the problem, especially exact **version numbers**. +3. If you got some error, please **describe what happened** and add error message. Reports like "I get error when I clicked on some link." are useless. +4. Provide easy steps to reproduce and if possible include your table structure (``SHOW CREATE TABLE `tbl_name```); if your problem implies specific data, attach a small export file for sample rows. +5. **Security problems** should not be reported here. See [our security page](https://www.phpmyadmin.net/security/). +Thanks for your help! + +Please report [bugs on GitHub][1]. + +[1]: https://github.com/phpmyadmin/phpmyadmin/issues/new + +## Patches submission + +Patches are welcome as [pull requests on GitHub][2]. Please include a +Signed-off-by tag in the commit message (you can do this by passing [`--signoff`][4] +parameter to Git). + +When creating the commit on GitHub or using some other tool which does not have +direct support for this, it is the same as adding +`Signed-off-by: Your name ` +as the last line of the commit message. + +Example: `Signed-off-by: Jane Smith ` + +Note that by submitting patches with the Signed-off-by tag, you are giving +permission to license the patch as GPLv2-or-later. See [the DCO file][3] for +details. + + +[2]: https://github.com/phpmyadmin/phpmyadmin/pulls +[3]: https://github.com/phpmyadmin/phpmyadmin/blob/master/DCO +[4]: https://git-scm.com/docs/git-commit#Documentation/git-commit.txt---signoff + +## More information + +You can find more information on our website: + +https://www.phpmyadmin.net/contribute/ diff --git a/php/apps/phpmyadmin49/html/ChangeLog b/php/apps/phpmyadmin49/html/ChangeLog new file mode 100644 index 00000000..715bad4e --- /dev/null +++ b/php/apps/phpmyadmin49/html/ChangeLog @@ -0,0 +1,179 @@ +phpMyAdmin - ChangeLog +====================== + +4.9.1 (2019-09-20) +- issue #15313 Added support for Twig 2 +- issue #15315 Fix cannot edit or export column with default CURRENT_TIMESTAMP in MySQL >= 8.0.13 +- issue Fix a TypeError in Import class with PHP 8 +- issue #14270 Fix Middle-click on foreign key link broken +- issue #14363 Fix broken relational links in tables +- issue #14987 Fix weird error for empty collation +- issue #15334 Fix export of GIS visualisation not working (PNG, PDF, SVG) +- issue #14918 Use hex for the phpMyAdmin session token +- issue Added GB18030 Chinese collations description +- issue Added Russian, Swedish, Slovak and Chinese UCA 9.0.0 collations description +- issue Added description for the _ks (kana-sensitive) collation suffix +- issue Added description for the _nopad (NO PAD) collation suffix +- issue #15404 Remove array/string curly braces access +- issue #15427 Fixed "FilterLanguages" option does not work (configuration) +- issue #15202 Fixed creating user with single quote in password results in no password user +- issue #14950 Fixed left database overview "add column" triggers error +- issue #15363 Fix remove unexpected quotes on text fields (structure and insert) +- issue Fix NULL wrongly checked on field change +- issue #15388 Fix allow to rollback an empty statement +- issue #14291 Fixed incorrect linkage from one table's value to another table +- issue #15446 Fix tables added from other databases are not collapsing in the designer section +- issue #14945 Fix designer page save fails if dB name contains period +- issue Display an error when trying to import in designer a table that's already imported +- issue Fix many bugs when adding new tables to designer +- issue Update CodeMirror to v5.48.4 +- issue Update jQuery Migrate to v3.1.0 +- issue Update jQuery Validation to v1.19.1 +- issue Update jQuery to v3.4.1 +- issue Update js-cookie to v2.2.1 +- issue Remove fieldset closing tag when setting global privileges +- issue #15425 Fix backslash in column name resulting an error in editing +- issue #15380 Fix Status - Advisor error +- issue #15439 Fix designer page status not updated when added a new table from another database +- issue #15440 Fix page number is not being updated in the URL after saving a designer's page +- issue Fix reloading a designer's page +- issue Fix designer full screen mode button and text stuck when exiting full-screen mode +- issue Reduced possibility of causing heavy server traffic between the database and web servers +- issue Fix a situation where a server could be deleted while an administator is using the setup script + +4.9.0.1 (2019-06-04) +- issue #14478 phpMyAdmin no longer streams the export data +- issue #14514 Tables with SYSTEM VERSIONING show up as views instead of tables +- issue #14515 Values cannot be edited in SYSTEM VERSIONING tables with INVISIBLE timestamps +- issue Fix header icon on server plugins page +- issue #14298 Fixed error 500 on MultiTableQuery page when a empty query is passed +- issue #14402 Fixed fatal javascript error while adding index to a new column +- issue #14896 Fixed issue with plus/minus icon when refreshing an expanded database +- issue #14922 Fixed json encode error in export +- issue #13975 Fixed missing query time in German (fix decimal number format issue) +- issue #14503 Fixed JavaScript events not activating on input (sql bookmark issue) +- issue #14898 Fixed Bottom table is blocked in database list (left panel) +- issue #14425 Fixed Null Checkbox automatically unmarked +- issue #14870 Display correct date and time in Zip files +- issue #14763 Fixed the loading symbol not appearing when refreshing the navigation +- issue #14607 Count rows only if needed +- issue #14832 Show Designer combo boxes when adding a constraint +- issue #14948 Fix change password is not showing password strength difference at the second attempt +- issue #14868 Fix edit view +- issue #14943 Fixed loading Forever when creating new view without filling any field +- issue #14843 Fix Bookmark::get() id matching SQL +- issue #14734 Fixed invalid default value for bit field +- issue #14311 Fixed undefined index in setup script +- issue #14991 Fixed TypeError in GIS editor +- issue Fixed GIS data editor for multi server setup +- issue #14312 Fixed type error in setup script when adding new server +- issue #14053 Fix missed padding on query results +- issue #14826 Fixed javascript error PMA_messages is not defined +- issue Show error message if config-set fails and not "loading..." forever +- issue #14359 Prevent multiple error modals, and error-report request spamming from script +- issue Fixed error reporting javascript errors on multi server setup +- issue Fixed wrong property name on TableStructureController +- issue #14811 Fix SHOW FULL TABLES FROM when a table is locked +- issue #14916 Fix bug when creating or editing views +- issue #14931 Fixed php error when using a query like SELECT 1 INTO @a; SELECT @a; in inline query edit +- issue #15074 Make the server logo visible on theme "original" +- issue #15077 Fixed incorrect page numbers +- issue #14205 Fixed "No tables found in database" when you delete all tables from last page +- issue #14957 Virtuality is not selected when editing generated column (added virtuality(stored) option for mariadb) +- issue #14853 Insert page should not allow entering things into virtual columns +- issue #15110 Fixed TypeError e.preventDefaulut is not a function +- issue #15115 Improved label in Settings export, clarifying that it's a JSON file +- issue #14816 Fixed [designer] Cannot read property 'style' of null +- issue Fixed [designer] Add new tables with database/table list modal +- issue Fixed query format on multi server setup +- issue Fixed remove partitioning on multi server setup +- issue Fixed normalization +- issue Fixed 'RESET SLAVE' button on replication slave +- issue Fixed sending a php error report on multi server setup +- issue Fixed downloading of monitor parameters for IE 11, Edge, Chrome and others +- issue #15141 Fixed php notice Undefined index: designer_settings +- issue #12729 Fixed sticky table header over dropdown menu +- issue #15140 Fixed edit link does not work on failed insert +- issue #14334 Fixed export table structure shows rows fields +- issue #15010 Fixed empty SQL preview modal on tbl_relation +- issue #14673 Fixed innodb & MySQL 8: DYNAMIC & COMPRESSED ROW_FORMAT missing +- issue Fixed empty success message when adding a new INDEX from left panel +- issue #15150 Fixed generate password hidden on second open of change password modal +- issue Fixed import XML data with leading zeros +- issue #15036 Fixed missing input fields checks for MaxSizeForInputField +- issue #15119 Fixed uninterpreted HTML on Settings->Export page +- issue #15159 Fixed missing query time and database in console +- issue #13713 Fixed column comments in the floating table header +- issue #15177 Fixed label alignment on login page +- issue #15210 Fixed a typo in the english name of the Albanian language +- issue Fixed issue when resetting charset in import.php +- issue #14460 Fixed forms where submitted multiple times on CTRL + ENTER +- issue #15038 Fixed console height was allowing a negative values +- issue #15219 Fixed 'No Password' option does not switch automatically to 'Use Text Field' in add user account +- issue Fixed importing the exported config on Server status monitor page +- issue #15228 Fixed php notice 'Undefined index: foreign_keys_data' on designer when the user has column access +- issue #12900 Fixed designer page saving gives error when configuration storage is not set up +- issue #15229 Fixed php notice, added support for 'DELETE HISTORY' table privilege (MariaDB >= 10.3.4) +- issue #14527 Fixed import settings function not working +- issue #14908 Fixed uninterpreted HTML on Settings->Import (missing data error descriptions) +- issue #14800 Fixed status->Processes doesn't show full query process list page +- issue #14833 Fixed sort by Time not working in process list page +- issue #14982 Fixed setting "null" keep an "enum" value +- issue #14401 Fixed insert rows keypress Enter behavior +- issue #15146 Fixed error reports can not be sent because they are too large +- issue #15205 Fixed useless backquotes on sql preview modal when deleting an index +- issue #13178 Fixed issues with uppercase table and database names (lower_case_table_names=1) +- issue #14383 Fixed warning when browsing certain tables (GIS data) +- issue #12865 Fixed MySQL 8.0.0 issues with GIS display +- issue #15059 Fixed "Server charset" in "Database server" tab showing wrong information +- issue #14614 Fixed mysql error "#2014 - Commands out of sync; you can't run this command now" on sql query +- issue #15238 Fixed phpMyAdmin 4.8.5 doesn't show privileges of procedures (raw html displayed instead) +- issue #13726 Fixed can not copy user on Percona Server 5.7 +- issue #15239 Fixed javascript error while fetching latest version info and switching pages +- issue #14301 Fixed javascript error when editing a JSON data type column +- issue #15240 Fixed apply a Settings form with errors shows a JSON response after using return back +- issue #15043 Fixed multiple errors printing on Settings page +- issue #15037 Fixed unexpected behavior of reset button on Settings +- issue #15157 Fixed 'Settings' tab not marked as active when browsing 2FA settings +- issue #14934 Fixed all fields readonly on Edit/Insert screens +- issue #14588 Fixed export of geometry objects, GIS objects are now exported as hex +- issue #14412 Better handling of errors with Signon authentication type +- issue Added support for AUTO_INCREMENT when using ROCKSDB, on Operations page +- issue #15276 Fixed partitioning is missing in Structure page UI (MySQL 8.0) +- issue #14252 Fixed DisableIS and database tree list (new database missing when refreshing the list) +- issue #14621 Removed "Propose table structure" on MySQL 8.0 +- issue Fixed editing of virtual columns on PerconaDB +- issue #13854 Fixed column options are ignored for GENERATED/VIRTUAL/STORED columns +- issue #15262 Fixed incorrect display of charset column (raw html) +- issue Added explicit parentheses in nested ternary operators +- issue #15287 Fix auto_increment field is too small +- issue #15283 Fix tries to change collation on views when changing collation on all tables/fields +- issue Fixed empty PMA_gotoWhitelist JavaScript array +- issue #15079 Fixed responsive behaviour of instruction dialog box +- issue #10846 Fixed javascript error when renaming a table +- issue Updated sql-parser to version 4.3.2 +- issue [security] SQL injection in Designer (PMASA-2019-3) +- issue [security] CSRF attack on 'cookie' login form (PMASA-2019-4) + +4.8.5 (2019-01-25) +- issue Developer debug data was saved to the PHP error log +- issue #14217 Fix issue when adding user on MySQL 8.0.11 +- issue #13788 Exporting a view structure based on another view with a sub-query throws no database selected error +- issue #14635 Fix PHP error in GitRevision, error in processing request, error code 200 +- issue #14787 Cannot execute stored procedure +- issue Add Burmese language +- issue #14794 Not responding to click, frozen interface, plugin Text_Plain_Sql error +- issue #14786 Table level Operations functions missing +- issue #14791 PHP warning, db_export.php#L91 urldecode() +- issue #14775 Export to SQL format not available for tables +- issue #14782 Error message shown instead of two-factor QR code when adding 2fa to a user +- issue [security] Arbitrary file read/delete relating to MySQL LOAD DATA LOCAL INFILE and an evil server instance (PMASA-2019-1) +- issue [security] SQL injection in Designer (PMASA-2019-2) + + --- Older ChangeLogs can be found on our project website --- + https://www.phpmyadmin.net/old-stuff/ChangeLogs/ + +# vim: et ts=4 sw=4 sts=4 +# vim: ft=changelog fenc=utf-8 +# vim: fde=getline(v\:lnum-1)=~'^\\s*$'&&getline(v\:lnum)=~'\\S'?'>1'\:1&&v\:lnum>4&&getline(v\:lnum)!~'^#' +# vim: fdn=1 fdm=expr diff --git a/php/apps/phpmyadmin49/html/DCO b/php/apps/phpmyadmin49/html/DCO new file mode 100644 index 00000000..2ef700ef --- /dev/null +++ b/php/apps/phpmyadmin49/html/DCO @@ -0,0 +1,44 @@ +If you would like to make a contribution to the phpMyAdmin Project, please +certify to the following: +*** +phpMyAdmin Developer's Certificate of Origin. Version 1.0 + +By making a contribution to this project, I certify that: + +(a) The contribution was created in whole or in part by me and I have the + right to submit it under the license of "GNU General Public License or + any later version" ("GPLv2-or-later"); or + +(b) The contribution is based upon previous work that, to the best of my + knowledge, is covered under an appropriate open source license and I have + the right under that license to submit that work with modifications, + whether created in whole or in part by me, under GPLv2-or-later; or + +(c) The contribution was provided directly to me by some other person who + certified (a) or (b) and I have not modified it. + +(d) I understand and agree that this project and the contribution are public + and that a record of the contribution (including all metadata and + personal information I submit with it, including my sign-off) is + maintained indefinitely and may be redistributed consistent with + phpMyAdmin's policies and the requirements of the GPLv2-or-later where + they are relevant. + +(e) I am granting this work to this project under the terms of the + GPLv2-or-later. + + https://www.gnu.org/licenses/gpl-2.0.html + +*** +*** +And please confirm your certification to the above by adding the following +line to your patch: + + Signed-off-by: Jane Smith + +using your real name (sorry, no pseudonyms or anonymous contributions). + +If you are a developer who is authorized to contribute to phpMyAdmin on +behalf of your employer, then please use your corporate email address in the +Signed-off-by tag. If not, then please use a personal email address. + diff --git a/php/apps/phpmyadmin49/html/LICENSE b/php/apps/phpmyadmin49/html/LICENSE new file mode 100644 index 00000000..d159169d --- /dev/null +++ b/php/apps/phpmyadmin49/html/LICENSE @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/php/apps/phpmyadmin49/html/README b/php/apps/phpmyadmin49/html/README new file mode 100644 index 00000000..54bc5d09 --- /dev/null +++ b/php/apps/phpmyadmin49/html/README @@ -0,0 +1,52 @@ +phpMyAdmin - Readme +=================== + +Version 4.9.1 + +A web interface for MySQL and MariaDB. + +https://www.phpmyadmin.net/ + +Summary +------- + +phpMyAdmin is intended to handle the administration of MySQL over the web. +For a summary of features, list of requirements, and installation instructions, +please see the documentation in the ./doc/ folder or at https://docs.phpmyadmin.net/ + +Copyright +--------- + +Copyright © 1998 onwards -- the phpMyAdmin team + +Certain libraries are copyrighted by their respective authors; +see the full copyright list for details. + +For full copyright information, please see ./doc/copyright.rst + +License +------- + +This program is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License version 2, as published by the +Free Software Foundation. + +This program is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . + +Licensing of current contributions +---------------------------------- + +Beginning on 2013-12-01, new contributions to this codebase are all licensed +under terms compatible with GPLv2-or-later. phpMyAdmin is currently +transitioning older code to GPLv2-or-later, but work is not yet complete. + +Enjoy! +------ + +The phpMyAdmin team diff --git a/php/apps/phpmyadmin49/html/ajax.php b/php/apps/phpmyadmin49/html/ajax.php new file mode 100644 index 00000000..7f615a01 --- /dev/null +++ b/php/apps/phpmyadmin49/html/ajax.php @@ -0,0 +1,51 @@ +setAJAX(true); + +if (empty($_POST['type'])) { + Core::fatalError(__('Bad type!')); +} + +switch ($_POST['type']) { + case 'list-databases': + $response->addJSON('databases', $GLOBALS['dblist']->databases); + break; + case 'list-tables': + Util::checkParameters(array('db'), true); + $response->addJSON('tables', $GLOBALS['dbi']->getTables($_POST['db'])); + break; + case 'list-columns': + Util::checkParameters(array('db', 'table'), true); + $response->addJSON('columns', $GLOBALS['dbi']->getColumnNames($_POST['db'], $_POST['table'])); + break; + case 'config-get': + Util::checkParameters(array('key'), true); + $response->addJSON('value', $GLOBALS['PMA_Config']->get($_POST['key'])); + break; + case 'config-set': + Util::checkParameters(array('key', 'value'), true); + $result = $GLOBALS['PMA_Config']->setUserValue(null, $_POST['key'], json_decode($_POST['value'])); + if ($result !== true) { + $response = Response::getInstance(); + $response->setRequestStatus(false); + $response->addJSON('message', $result); + } + break; + default: + Core::fatalError(__('Bad type!')); +} diff --git a/php/apps/phpmyadmin49/html/browse_foreigners.php b/php/apps/phpmyadmin49/html/browse_foreigners.php new file mode 100644 index 00000000..f3c3fab4 --- /dev/null +++ b/php/apps/phpmyadmin49/html/browse_foreigners.php @@ -0,0 +1,74 @@ +getFooter()->setMinimal(); +$header = $response->getHeader(); +$header->disableMenuAndConsole(); +$header->setBodyId('body_browse_foreigners'); + +$relation = new Relation(); + +/** + * Displays the frame + */ +$foreigners = $relation->getForeigners($db, $table); +$browseForeigners = new BrowseForeigners( + $GLOBALS['cfg']['LimitChars'], + $GLOBALS['cfg']['MaxRows'], + $GLOBALS['cfg']['RepeatCells'], + $GLOBALS['cfg']['ShowAll'], + $GLOBALS['pmaThemeImage'] +); +$foreign_limit = $browseForeigners->getForeignLimit( + isset($_POST['foreign_showAll']) ? $_POST['foreign_showAll'] : null +); + +$foreignData = $relation->getForeignData( + $foreigners, $_POST['field'], true, + isset($_POST['foreign_filter']) + ? $_POST['foreign_filter'] + : '', + isset($foreign_limit) ? $foreign_limit : null, + true // for getting value in $foreignData['the_total'] +); + +// HTML output +$html = $browseForeigners->getHtmlForRelationalFieldSelection( + $db, + $table, + $_POST['field'], + $foreignData, + isset($fieldkey) ? $fieldkey : null, + isset($data) ? $data : null +); + +$response->addHtml($html); diff --git a/php/apps/phpmyadmin49/html/changelog.php b/php/apps/phpmyadmin49/html/changelog.php new file mode 100644 index 00000000..b0f8b012 --- /dev/null +++ b/php/apps/phpmyadmin49/html/changelog.php @@ -0,0 +1,117 @@ +disable(); +$response->getHeader()->sendHttpHeaders(); + +$filename = CHANGELOG_FILE; + +/** + * Read changelog. + */ +// Check if the file is available, some distributions remove these. +if (@is_readable($filename)) { + + // Test if the if is in a compressed format + if (substr($filename, -3) == '.gz') { + ob_start(); + readgzfile($filename); + $changelog = ob_get_contents(); + ob_end_clean(); + } else { + $changelog = file_get_contents($filename); + } +} else { + printf( + __( + 'The %s file is not available on this system, please visit ' . + '%s for more information.' + ), + $filename, + 'phpmyadmin.net' + ); + exit; +} + +/** + * Whole changelog in variable. + */ +$changelog = htmlspecialchars($changelog); + +$github_url = 'https://github.com/phpmyadmin/phpmyadmin/'; +$faq_url = 'https://docs.phpmyadmin.net/en/latest/faq.html'; + +$replaces = array( + '@(https?://[./a-zA-Z0-9.-_-]*[/a-zA-Z0-9_])@' + => '\\1', + + // mail address + '/([0-9]{4}-[0-9]{2}-[0-9]{2}) (.+[^ ]) +<(.*@.*)>/i' + => '\\1 \\2', + + // FAQ entries + '/FAQ ([0-9]+)\.([0-9a-z]+)/i' + => 'FAQ \\1.\\2', + + // GitHub issues + '/issue\s*#?([0-9]{4,5}) /i' + => 'issue #\\1 ', + + // CVE/CAN entries + '/((CAN|CVE)-[0-9]+-[0-9]+)/' + => '\\1', + + // PMASAentries + '/(PMASA-[0-9]+-[0-9]+)/' + => '\\1', + + // Highlight releases (with links) + '/([0-9]+)\.([0-9]+)\.([0-9]+)\.0 (\([0-9-]+\))/' + => '' + . '' + . '\\1.\\2.\\3.0 \\4', + '/([0-9]+)\.([0-9]+)\.([0-9]+)\.([1-9][0-9]*) (\([0-9-]+\))/' + => '' + . '' + . '\\1.\\2.\\3.\\4 \\5', + + // Highlight releases (not linkable) + '/( ### )(.*)/' + => '\\1\\2', + + // Links target and rel + '/a href="/' => 'a target="_blank" rel="noopener noreferrer" href="' + +); + +header('Content-type: text/html; charset=utf-8'); +?> + + + + + + phpMyAdmin - ChangeLog + + + +

phpMyAdmin - ChangeLog

+'; +echo preg_replace(array_keys($replaces), $replaces, $changelog); +echo ''; +?> + + diff --git a/php/apps/phpmyadmin49/html/chk_rel.php b/php/apps/phpmyadmin49/html/chk_rel.php new file mode 100644 index 00000000..fe639537 --- /dev/null +++ b/php/apps/phpmyadmin49/html/chk_rel.php @@ -0,0 +1,36 @@ +createPmaDatabase()) { + $relation->fixPmaTables('phpmyadmin'); + } +} + +// If request for creating all PMA tables. +if (isset($_POST['fixall_pmadb'])) { + $relation->fixPmaTables($GLOBALS['db']); +} + +$cfgRelation = $relation->getRelationsParam(); +// If request for creating missing PMA tables. +if (isset($_POST['fix_pmadb'])) { + $relation->fixPmaTables($cfgRelation['db']); +} + +$response = Response::getInstance(); +$response->addHTML( + $relation->getRelationsParamDiagnostic($cfgRelation) +); diff --git a/php/apps/phpmyadmin49/html/composer.lock b/php/apps/phpmyadmin49/html/composer.lock new file mode 100644 index 00000000..85c9c226 --- /dev/null +++ b/php/apps/phpmyadmin49/html/composer.lock @@ -0,0 +1,2669 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "fcc1bc864c141a58b0bbb7e52b156ef1", + "packages": [ + { + "name": "bacon/bacon-qr-code", + "version": "1.0.3", + "source": { + "type": "git", + "url": "https://github.com/Bacon/BaconQrCode.git", + "reference": "5a91b62b9d37cee635bbf8d553f4546057250bee" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Bacon/BaconQrCode/zipball/5a91b62b9d37cee635bbf8d553f4546057250bee", + "reference": "5a91b62b9d37cee635bbf8d553f4546057250bee", + "shasum": "" + }, + "require": { + "ext-iconv": "*", + "php": "^5.4|^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8" + }, + "suggest": { + "ext-gd": "to generate QR code images" + }, + "type": "library", + "autoload": { + "psr-0": { + "BaconQrCode": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-2-Clause" + ], + "authors": [ + { + "name": "Ben Scholzen 'DASPRiD'", + "email": "mail@dasprids.de", + "homepage": "http://www.dasprids.de", + "role": "Developer" + } + ], + "description": "BaconQrCode is a QR code generator for PHP.", + "homepage": "https://github.com/Bacon/BaconQrCode", + "time": "2017-10-17T09:59:25+00:00" + }, + { + "name": "google/recaptcha", + "version": "1.2.3", + "source": { + "type": "git", + "url": "https://github.com/google/recaptcha.git", + "reference": "98c4a6573b27e8b0990ea8789c74ea378795134c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/google/recaptcha/zipball/98c4a6573b27e8b0990ea8789c74ea378795134c", + "reference": "98c4a6573b27e8b0990ea8789c74ea378795134c", + "shasum": "" + }, + "require": { + "php": ">=5.5" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^2.2.20|^2.15", + "php-coveralls/php-coveralls": "^2.1", + "phpunit/phpunit": "^4.8.36|^5.7.27|^6.59|^7.5.11" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2.x-dev" + } + }, + "autoload": { + "psr-4": { + "ReCaptcha\\": "src/ReCaptcha" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "Client library for reCAPTCHA, a free service that protects websites from spam and abuse.", + "homepage": "https://www.google.com/recaptcha/", + "keywords": [ + "Abuse", + "captcha", + "recaptcha", + "spam" + ], + "time": "2019-08-16T15:48:25+00:00" + }, + { + "name": "paragonie/constant_time_encoding", + "version": "v1.0.4", + "source": { + "type": "git", + "url": "https://github.com/paragonie/constant_time_encoding.git", + "reference": "2132f0f293d856026d7d11bd81b9f4a23a1dc1f6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/paragonie/constant_time_encoding/zipball/2132f0f293d856026d7d11bd81b9f4a23a1dc1f6", + "reference": "2132f0f293d856026d7d11bd81b9f4a23a1dc1f6", + "shasum": "" + }, + "require": { + "php": "^5.3|^7" + }, + "require-dev": { + "paragonie/random_compat": "^1.4|^2", + "phpunit/phpunit": "4.*|5.*", + "vimeo/psalm": "^0.3|^1" + }, + "type": "library", + "autoload": { + "psr-4": { + "ParagonIE\\ConstantTime\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Paragon Initiative Enterprises", + "email": "security@paragonie.com", + "homepage": "https://paragonie.com", + "role": "Maintainer" + }, + { + "name": "Steve 'Sc00bz' Thomas", + "email": "steve@tobtu.com", + "homepage": "https://www.tobtu.com", + "role": "Original Developer" + } + ], + "description": "Constant-time Implementations of RFC 4648 Encoding (Base-64, Base-32, Base-16)", + "keywords": [ + "base16", + "base32", + "base32_decode", + "base32_encode", + "base64", + "base64_decode", + "base64_encode", + "bin2hex", + "encoding", + "hex", + "hex2bin", + "rfc4648" + ], + "time": "2018-04-30T17:57:16+00:00" + }, + { + "name": "paragonie/random_compat", + "version": "v2.0.18", + "source": { + "type": "git", + "url": "https://github.com/paragonie/random_compat.git", + "reference": "0a58ef6e3146256cc3dc7cc393927bcc7d1b72db" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/paragonie/random_compat/zipball/0a58ef6e3146256cc3dc7cc393927bcc7d1b72db", + "reference": "0a58ef6e3146256cc3dc7cc393927bcc7d1b72db", + "shasum": "" + }, + "require": { + "php": ">=5.2.0" + }, + "require-dev": { + "phpunit/phpunit": "4.*|5.*" + }, + "suggest": { + "ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes." + }, + "type": "library", + "autoload": { + "files": [ + "lib/random.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Paragon Initiative Enterprises", + "email": "security@paragonie.com", + "homepage": "https://paragonie.com" + } + ], + "description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7", + "keywords": [ + "csprng", + "polyfill", + "pseudorandom", + "random" + ], + "time": "2019-01-03T20:59:08+00:00" + }, + { + "name": "phpmyadmin/motranslator", + "version": "4.0", + "source": { + "type": "git", + "url": "https://github.com/phpmyadmin/motranslator.git", + "reference": "fcb370254998fda7eeccfd7c787b4deb71b0d77c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpmyadmin/motranslator/zipball/fcb370254998fda7eeccfd7c787b4deb71b0d77c", + "reference": "fcb370254998fda7eeccfd7c787b4deb71b0d77c", + "shasum": "" + }, + "require": { + "php": ">=5.3.0", + "symfony/expression-language": "^4.0 || ^3.2 || ^2.8" + }, + "require-dev": { + "apigen/apigen": "^4.1", + "phpunit/php-code-coverage": "*", + "phpunit/phpunit": "~4.8 || ~5.7 || ~6.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "PhpMyAdmin\\MoTranslator\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "GPL-2.0-or-later" + ], + "authors": [ + { + "name": "The phpMyAdmin Team", + "email": "developers@phpmyadmin.net", + "homepage": "https://www.phpmyadmin.net/team/" + } + ], + "description": "Translation API for PHP using Gettext MO files", + "homepage": "https://github.com/phpmyadmin/motranslator", + "keywords": [ + "gettext", + "i18n", + "mo", + "translator" + ], + "time": "2018-02-12T13:22:52+00:00" + }, + { + "name": "phpmyadmin/shapefile", + "version": "2.1", + "source": { + "type": "git", + "url": "https://github.com/phpmyadmin/shapefile.git", + "reference": "e23b767f2a81f61fee3fc09fc062879985f3e224" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpmyadmin/shapefile/zipball/e23b767f2a81f61fee3fc09fc062879985f3e224", + "reference": "e23b767f2a81f61fee3fc09fc062879985f3e224", + "shasum": "" + }, + "require": { + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/php-code-coverage": "*", + "phpunit/phpunit": "~4.8 || ~5.7" + }, + "suggest": { + "ext-dbase": "For dbf files parsing" + }, + "type": "library", + "autoload": { + "psr-4": { + "PhpMyAdmin\\ShapeFile\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "GPL-2.0+" + ], + "authors": [ + { + "name": "The phpMyAdmin Team", + "email": "developers@phpmyadmin.net", + "homepage": "https://www.phpmyadmin.net/team/" + } + ], + "description": "ESRI ShapeFile library for PHP", + "homepage": "https://github.com/phpmyadmin/shapefile", + "keywords": [ + "ESRI", + "Shapefile", + "dbf", + "geo", + "geospatial", + "shape", + "shp" + ], + "time": "2017-05-15T08:31:47+00:00" + }, + { + "name": "phpmyadmin/sql-parser", + "version": "4.3.2", + "source": { + "type": "git", + "url": "https://github.com/phpmyadmin/sql-parser.git", + "reference": "b35c21f82e7202d739f6349a583b11e6d32b2b64" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpmyadmin/sql-parser/zipball/b35c21f82e7202d739f6349a583b11e6d32b2b64", + "reference": "b35c21f82e7202d739f6349a583b11e6d32b2b64", + "shasum": "" + }, + "require": { + "php": ">=5.3.0", + "symfony/polyfill-mbstring": "^1.3" + }, + "conflict": { + "phpmyadmin/motranslator": "<3.0" + }, + "require-dev": { + "phpunit/php-code-coverage": "*", + "phpunit/phpunit": "~4.8 || ~5.7 || ~6.5", + "sami/sami": "^4.0", + "squizlabs/php_codesniffer": "^2.9" + }, + "suggest": { + "ext-mbstring": "For best performance", + "phpmyadmin/motranslator": "Translate messages to your favorite locale" + }, + "bin": [ + "bin/highlight-query", + "bin/lint-query", + "bin/tokenize-query" + ], + "type": "library", + "autoload": { + "psr-4": { + "PhpMyAdmin\\SqlParser\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "GPL-2.0-or-later" + ], + "authors": [ + { + "name": "The phpMyAdmin Team", + "email": "developers@phpmyadmin.net", + "homepage": "https://www.phpmyadmin.net/team/" + } + ], + "description": "A validating SQL lexer and parser with a focus on MySQL dialect.", + "homepage": "https://github.com/phpmyadmin/sql-parser", + "keywords": [ + "analysis", + "lexer", + "parser", + "sql" + ], + "time": "2019-06-03T12:32:07+00:00" + }, + { + "name": "phpseclib/phpseclib", + "version": "2.0.23", + "source": { + "type": "git", + "url": "https://github.com/phpseclib/phpseclib.git", + "reference": "c78eb5058d5bb1a183133c36d4ba5b6675dfa099" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/c78eb5058d5bb1a183133c36d4ba5b6675dfa099", + "reference": "c78eb5058d5bb1a183133c36d4ba5b6675dfa099", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phing/phing": "~2.7", + "phpunit/phpunit": "^4.8.35|^5.7|^6.0", + "sami/sami": "~2.0", + "squizlabs/php_codesniffer": "~2.0" + }, + "suggest": { + "ext-gmp": "Install the GMP (GNU Multiple Precision) extension in order to speed up arbitrary precision integer arithmetic operations.", + "ext-libsodium": "SSH2/SFTP can make use of some algorithms provided by the libsodium-php extension.", + "ext-mcrypt": "Install the Mcrypt extension in order to speed up a few other cryptographic operations.", + "ext-openssl": "Install the OpenSSL extension in order to speed up a wide variety of cryptographic operations." + }, + "type": "library", + "autoload": { + "files": [ + "phpseclib/bootstrap.php" + ], + "psr-4": { + "phpseclib\\": "phpseclib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jim Wigginton", + "email": "terrafrost@php.net", + "role": "Lead Developer" + }, + { + "name": "Patrick Monnerat", + "email": "pm@datasphere.ch", + "role": "Developer" + }, + { + "name": "Andreas Fischer", + "email": "bantu@phpbb.com", + "role": "Developer" + }, + { + "name": "Hans-Jürgen Petrich", + "email": "petrich@tronic-media.com", + "role": "Developer" + }, + { + "name": "Graham Campbell", + "email": "graham@alt-three.com", + "role": "Developer" + } + ], + "description": "PHP Secure Communications Library - Pure-PHP implementations of RSA, AES, SSH2, SFTP, X.509 etc.", + "homepage": "http://phpseclib.sourceforge.net", + "keywords": [ + "BigInteger", + "aes", + "asn.1", + "asn1", + "blowfish", + "crypto", + "cryptography", + "encryption", + "rsa", + "security", + "sftp", + "signature", + "signing", + "ssh", + "twofish", + "x.509", + "x509" + ], + "time": "2019-09-17T03:41:22+00:00" + }, + { + "name": "pragmarx/google2fa", + "version": "v3.0.3", + "source": { + "type": "git", + "url": "https://github.com/antonioribeiro/google2fa.git", + "reference": "6949226739e4424f40031e6f1c96b1fd64047335" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/antonioribeiro/google2fa/zipball/6949226739e4424f40031e6f1c96b1fd64047335", + "reference": "6949226739e4424f40031e6f1c96b1fd64047335", + "shasum": "" + }, + "require": { + "paragonie/constant_time_encoding": "~1.0|~2.0", + "paragonie/random_compat": ">=1", + "php": ">=5.4", + "symfony/polyfill-php56": "~1.2" + }, + "require-dev": { + "bacon/bacon-qr-code": "~1.0", + "phpunit/phpunit": "~4|~5|~6" + }, + "suggest": { + "bacon/bacon-qr-code": "Required to generate inline QR Codes." + }, + "type": "library", + "extra": { + "component": "package", + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "psr-4": { + "PragmaRX\\Google2FA\\": "src/", + "PragmaRX\\Google2FA\\Tests\\": "tests/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Antonio Carlos Ribeiro", + "email": "acr@antoniocarlosribeiro.com", + "role": "Creator & Designer" + } + ], + "description": "A One Time Password Authentication package, compatible with Google Authenticator.", + "keywords": [ + "2fa", + "Authentication", + "Two Factor Authentication", + "google2fa", + "laravel" + ], + "time": "2018-08-29T13:28:06+00:00" + }, + { + "name": "psr/container", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/b7ce3b176482dbbc1245ebf52b181af44c2cf55f", + "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "time": "2017-02-14T16:28:37+00:00" + }, + { + "name": "samyoul/u2f-php-server", + "version": "v1.1.4", + "source": { + "type": "git", + "url": "https://github.com/Samyoul/U2F-php-server.git", + "reference": "0625202c79d570e58525ed6c4ae38500ea3f0883" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Samyoul/U2F-php-server/zipball/0625202c79d570e58525ed6c4ae38500ea3f0883", + "reference": "0625202c79d570e58525ed6c4ae38500ea3f0883", + "shasum": "" + }, + "require": { + "ext-openssl": "*" + }, + "type": "library", + "autoload": { + "psr-4": { + "Samyoul\\U2F\\U2FServer\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-2-Clause" + ], + "authors": [ + { + "name": "Samuel Hawksby-Robinson", + "email": "samuel@samyoul.com" + } + ], + "description": "Server side handling class for FIDO U2F registration and authentication", + "time": "2018-10-26T12:43:39+00:00" + }, + { + "name": "symfony/expression-language", + "version": "v2.8.50", + "source": { + "type": "git", + "url": "https://github.com/symfony/expression-language.git", + "reference": "fa9be1b831859b56d244137fabbfd01a46dbdb36" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/expression-language/zipball/fa9be1b831859b56d244137fabbfd01a46dbdb36", + "reference": "fa9be1b831859b56d244137fabbfd01a46dbdb36", + "shasum": "" + }, + "require": { + "php": ">=5.3.9" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.8-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\ExpressionLanguage\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony ExpressionLanguage Component", + "homepage": "https://symfony.com", + "time": "2018-11-11T11:18:13+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.12.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "550ebaac289296ce228a706d0867afc34687e3f4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/550ebaac289296ce228a706d0867afc34687e3f4", + "reference": "550ebaac289296ce228a706d0867afc34687e3f4", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.12-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "time": "2019-08-06T08:03:45+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.12.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "b42a2f66e8f1b15ccf25652c3424265923eb4f17" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/b42a2f66e8f1b15ccf25652c3424265923eb4f17", + "reference": "b42a2f66e8f1b15ccf25652c3424265923eb4f17", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.12-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "time": "2019-08-06T08:03:45+00:00" + }, + { + "name": "symfony/polyfill-php56", + "version": "v1.12.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php56.git", + "reference": "0e3b212e96a51338639d8ce175c046d7729c3403" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php56/zipball/0e3b212e96a51338639d8ce175c046d7729c3403", + "reference": "0e3b212e96a51338639d8ce175c046d7729c3403", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "symfony/polyfill-util": "~1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.12-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Php56\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 5.6+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "time": "2019-08-06T08:03:45+00:00" + }, + { + "name": "symfony/polyfill-util", + "version": "v1.12.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-util.git", + "reference": "4317de1386717b4c22caed7725350a8887ab205c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-util/zipball/4317de1386717b4c22caed7725350a8887ab205c", + "reference": "4317de1386717b4c22caed7725350a8887ab205c", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.12-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Util\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony utilities for portability of PHP codes", + "homepage": "https://symfony.com", + "keywords": [ + "compat", + "compatibility", + "polyfill", + "shim" + ], + "time": "2019-08-06T08:03:45+00:00" + }, + { + "name": "tecnickcom/tcpdf", + "version": "6.3.2", + "source": { + "type": "git", + "url": "https://github.com/tecnickcom/TCPDF.git", + "reference": "9fde7bb9b404b945e7ea88fb7eccd23d9a4e324b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/tecnickcom/TCPDF/zipball/9fde7bb9b404b945e7ea88fb7eccd23d9a4e324b", + "reference": "9fde7bb9b404b945e7ea88fb7eccd23d9a4e324b", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "config", + "include", + "tcpdf.php", + "tcpdf_parser.php", + "tcpdf_import.php", + "tcpdf_barcodes_1d.php", + "tcpdf_barcodes_2d.php", + "include/tcpdf_colors.php", + "include/tcpdf_filters.php", + "include/tcpdf_font_data.php", + "include/tcpdf_fonts.php", + "include/tcpdf_images.php", + "include/tcpdf_static.php", + "include/barcodes/datamatrix.php", + "include/barcodes/pdf417.php", + "include/barcodes/qrcode.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-3.0" + ], + "authors": [ + { + "name": "Nicola Asuni", + "email": "info@tecnick.com", + "role": "lead" + } + ], + "description": "TCPDF is a PHP class for generating PDF documents and barcodes.", + "homepage": "http://www.tcpdf.org/", + "keywords": [ + "PDFD32000-2008", + "TCPDF", + "barcodes", + "datamatrix", + "pdf", + "pdf417", + "qrcode" + ], + "time": "2019-09-20T09:35:01+00:00" + }, + { + "name": "twig/extensions", + "version": "v1.5.4", + "source": { + "type": "git", + "url": "https://github.com/twigphp/Twig-extensions.git", + "reference": "57873c8b0c1be51caa47df2cdb824490beb16202" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/twigphp/Twig-extensions/zipball/57873c8b0c1be51caa47df2cdb824490beb16202", + "reference": "57873c8b0c1be51caa47df2cdb824490beb16202", + "shasum": "" + }, + "require": { + "twig/twig": "^1.27|^2.0" + }, + "require-dev": { + "symfony/phpunit-bridge": "^3.4", + "symfony/translation": "^2.7|^3.4" + }, + "suggest": { + "symfony/translation": "Allow the time_diff output to be translated" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.5-dev" + } + }, + "autoload": { + "psr-0": { + "Twig_Extensions_": "lib/" + }, + "psr-4": { + "Twig\\Extensions\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + } + ], + "description": "Common additional features for Twig that do not directly belong in core", + "keywords": [ + "i18n", + "text" + ], + "time": "2018-12-05T18:34:18+00:00" + }, + { + "name": "twig/twig", + "version": "v1.42.3", + "source": { + "type": "git", + "url": "https://github.com/twigphp/Twig.git", + "reference": "201baee843e0ffe8b0b956f336dd42b2a92fae4e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/twigphp/Twig/zipball/201baee843e0ffe8b0b956f336dd42b2a92fae4e", + "reference": "201baee843e0ffe8b0b956f336dd42b2a92fae4e", + "shasum": "" + }, + "require": { + "php": ">=5.5.0", + "symfony/polyfill-ctype": "^1.8" + }, + "require-dev": { + "psr/container": "^1.0", + "symfony/debug": "^3.4|^4.2", + "symfony/phpunit-bridge": "^4.4@dev|^5.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.42-dev" + } + }, + "autoload": { + "psr-0": { + "Twig_": "lib/" + }, + "psr-4": { + "Twig\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com", + "homepage": "http://fabien.potencier.org", + "role": "Lead Developer" + }, + { + "name": "Twig Team", + "homepage": "https://twig.symfony.com/contributors", + "role": "Contributors" + }, + { + "name": "Armin Ronacher", + "email": "armin.ronacher@active-4.com", + "role": "Project Founder" + } + ], + "description": "Twig, the flexible, fast, and secure template language for PHP", + "homepage": "https://twig.symfony.com", + "keywords": [ + "templating" + ], + "time": "2019-08-24T12:51:03+00:00" + } + ], + "packages-dev": [ + { + "name": "codacy/coverage", + "version": "1.4.2", + "source": { + "type": "git", + "url": "https://github.com/codacy/php-codacy-coverage.git", + "reference": "4988cd098db4d578681bfd3176071931ad475150" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/codacy/php-codacy-coverage/zipball/4988cd098db4d578681bfd3176071931ad475150", + "reference": "4988cd098db4d578681bfd3176071931ad475150", + "shasum": "" + }, + "require": { + "gitonomy/gitlib": ">=1.0", + "php": ">=5.3.3", + "symfony/console": "~2.5|~3.0|~4.0" + }, + "require-dev": { + "phpunit/phpunit": "~6.5" + }, + "bin": [ + "bin/codacycoverage" + ], + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jakob Pupke", + "email": "jakob.pupke@gmail.com" + } + ], + "description": "Sends PHP test coverage information to Codacy.", + "homepage": "https://github.com/codacy/php-codacy-coverage", + "time": "2018-03-22T16:43:39+00:00" + }, + { + "name": "doctrine/instantiator", + "version": "1.0.5", + "source": { + "type": "git", + "url": "https://github.com/doctrine/instantiator.git", + "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/8e884e78f9f0eb1329e445619e04456e64d8051d", + "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d", + "shasum": "" + }, + "require": { + "php": ">=5.3,<8.0-DEV" + }, + "require-dev": { + "athletic/athletic": "~0.1.8", + "ext-pdo": "*", + "ext-phar": "*", + "phpunit/phpunit": "~4.0", + "squizlabs/php_codesniffer": "~2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com", + "homepage": "http://ocramius.github.com/" + } + ], + "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", + "homepage": "https://github.com/doctrine/instantiator", + "keywords": [ + "constructor", + "instantiate" + ], + "time": "2015-06-14T21:17:01+00:00" + }, + { + "name": "gitonomy/gitlib", + "version": "v1.0.4", + "source": { + "type": "git", + "url": "https://github.com/gitonomy/gitlib.git", + "reference": "932a960221ae3484a3e82553b3be478e56beb68d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/gitonomy/gitlib/zipball/932a960221ae3484a3e82553b3be478e56beb68d", + "reference": "932a960221ae3484a3e82553b3be478e56beb68d", + "shasum": "" + }, + "require": { + "php": "^5.3 || ^7.0", + "symfony/process": "^2.3|^3.0|^4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35|^5.7", + "psr/log": "^1.0" + }, + "suggest": { + "psr/log": "Add some log" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "psr-4": { + "Gitonomy\\Git\\": "src/Gitonomy/Git/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Alexandre Salomé", + "email": "alexandre.salome@gmail.com", + "homepage": "http://alexandre-salome.fr" + }, + { + "name": "Julien DIDIER", + "email": "genzo.wm@gmail.com", + "homepage": "http://www.jdidier.net" + } + ], + "description": "Library for accessing git", + "homepage": "http://gitonomy.com", + "time": "2018-04-22T19:55:36+00:00" + }, + { + "name": "phpdocumentor/reflection-common", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionCommon.git", + "reference": "21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6", + "reference": "21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6", + "shasum": "" + }, + "require": { + "php": ">=5.5" + }, + "require-dev": { + "phpunit/phpunit": "^4.6" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": [ + "src" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jaap van Otterdijk", + "email": "opensource@ijaap.nl" + } + ], + "description": "Common reflection classes used by phpdocumentor to reflect the code structure", + "homepage": "http://www.phpdoc.org", + "keywords": [ + "FQSEN", + "phpDocumentor", + "phpdoc", + "reflection", + "static analysis" + ], + "time": "2017-09-11T18:02:19+00:00" + }, + { + "name": "phpdocumentor/reflection-docblock", + "version": "3.2.2", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", + "reference": "4aada1f93c72c35e22fb1383b47fee43b8f1d157" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/4aada1f93c72c35e22fb1383b47fee43b8f1d157", + "reference": "4aada1f93c72c35e22fb1383b47fee43b8f1d157", + "shasum": "" + }, + "require": { + "php": ">=5.5", + "phpdocumentor/reflection-common": "^1.0@dev", + "phpdocumentor/type-resolver": "^0.3.0", + "webmozart/assert": "^1.0" + }, + "require-dev": { + "mockery/mockery": "^0.9.4", + "phpunit/phpunit": "^4.4" + }, + "type": "library", + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + } + ], + "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", + "time": "2017-08-08T06:39:58+00:00" + }, + { + "name": "phpdocumentor/type-resolver", + "version": "0.3.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/TypeResolver.git", + "reference": "fb3933512008d8162b3cdf9e18dba9309b7c3773" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/fb3933512008d8162b3cdf9e18dba9309b7c3773", + "reference": "fb3933512008d8162b3cdf9e18dba9309b7c3773", + "shasum": "" + }, + "require": { + "php": "^5.5 || ^7.0", + "phpdocumentor/reflection-common": "^1.0" + }, + "require-dev": { + "mockery/mockery": "^0.9.4", + "phpunit/phpunit": "^5.2||^4.8.24" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + } + ], + "time": "2017-06-03T08:32:36+00:00" + }, + { + "name": "phpmyadmin/coding-standard", + "version": "0.3", + "source": { + "type": "git", + "url": "https://github.com/phpmyadmin/coding-standard.git", + "reference": "5ae123e27140a1e16be005432e26a8233524eaf5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpmyadmin/coding-standard/zipball/5ae123e27140a1e16be005432e26a8233524eaf5", + "reference": "5ae123e27140a1e16be005432e26a8233524eaf5", + "shasum": "" + }, + "require": { + "squizlabs/php_codesniffer": "^3.0" + }, + "type": "phpcodesniffer-standard", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "The phpMyAdmin Team", + "email": "developers@phpmyadmin.net", + "homepage": "https://www.phpmyadmin.net/team/" + } + ], + "description": "phpMyAdmin PHP CodeSniffer Coding Standard", + "keywords": [ + "codesniffer", + "phpcs", + "phpmyadmin" + ], + "time": "2017-09-28T09:13:00+00:00" + }, + { + "name": "phpspec/prophecy", + "version": "1.8.1", + "source": { + "type": "git", + "url": "https://github.com/phpspec/prophecy.git", + "reference": "1927e75f4ed19131ec9bcc3b002e07fb1173ee76" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/1927e75f4ed19131ec9bcc3b002e07fb1173ee76", + "reference": "1927e75f4ed19131ec9bcc3b002e07fb1173ee76", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.0.2", + "php": "^5.3|^7.0", + "phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0", + "sebastian/comparator": "^1.1|^2.0|^3.0", + "sebastian/recursion-context": "^1.0|^2.0|^3.0" + }, + "require-dev": { + "phpspec/phpspec": "^2.5|^3.2", + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5 || ^7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.8.x-dev" + } + }, + "autoload": { + "psr-4": { + "Prophecy\\": "src/Prophecy" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Konstantin Kudryashov", + "email": "ever.zet@gmail.com", + "homepage": "http://everzet.com" + }, + { + "name": "Marcello Duarte", + "email": "marcello.duarte@gmail.com" + } + ], + "description": "Highly opinionated mocking framework for PHP 5.3+", + "homepage": "https://github.com/phpspec/prophecy", + "keywords": [ + "Double", + "Dummy", + "fake", + "mock", + "spy", + "stub" + ], + "time": "2019-06-13T12:50:23+00:00" + }, + { + "name": "phpunit/php-code-coverage", + "version": "2.2.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "eabf68b476ac7d0f73793aada060f1c1a9bf8979" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/eabf68b476ac7d0f73793aada060f1c1a9bf8979", + "reference": "eabf68b476ac7d0f73793aada060f1c1a9bf8979", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "phpunit/php-file-iterator": "~1.3", + "phpunit/php-text-template": "~1.2", + "phpunit/php-token-stream": "~1.3", + "sebastian/environment": "^1.3.2", + "sebastian/version": "~1.0" + }, + "require-dev": { + "ext-xdebug": ">=2.1.4", + "phpunit/phpunit": "~4" + }, + "suggest": { + "ext-dom": "*", + "ext-xdebug": ">=2.2.1", + "ext-xmlwriter": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.2.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "keywords": [ + "coverage", + "testing", + "xunit" + ], + "time": "2015-10-06T15:47:00+00:00" + }, + { + "name": "phpunit/php-file-iterator", + "version": "1.4.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "730b01bc3e867237eaac355e06a36b85dd93a8b4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/730b01bc3e867237eaac355e06a36b85dd93a8b4", + "reference": "730b01bc3e867237eaac355e06a36b85dd93a8b4", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "keywords": [ + "filesystem", + "iterator" + ], + "time": "2017-11-27T13:52:08+00:00" + }, + { + "name": "phpunit/php-text-template", + "version": "1.2.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686", + "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "keywords": [ + "template" + ], + "time": "2015-06-21T13:50:34+00:00" + }, + { + "name": "phpunit/php-timer", + "version": "1.0.9", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3dcf38ca72b158baf0bc245e9184d3fdffa9c46f", + "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f", + "shasum": "" + }, + "require": { + "php": "^5.3.3 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], + "time": "2017-02-26T11:10:40+00:00" + }, + { + "name": "phpunit/php-token-stream", + "version": "1.4.12", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-token-stream.git", + "reference": "1ce90ba27c42e4e44e6d8458241466380b51fa16" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/1ce90ba27c42e4e44e6d8458241466380b51fa16", + "reference": "1ce90ba27c42e4e44e6d8458241466380b51fa16", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Wrapper around PHP's tokenizer extension.", + "homepage": "https://github.com/sebastianbergmann/php-token-stream/", + "keywords": [ + "tokenizer" + ], + "time": "2017-12-04T08:55:13+00:00" + }, + { + "name": "phpunit/phpunit", + "version": "4.8.36", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "46023de9a91eec7dfb06cc56cb4e260017298517" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/46023de9a91eec7dfb06cc56cb4e260017298517", + "reference": "46023de9a91eec7dfb06cc56cb4e260017298517", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-json": "*", + "ext-pcre": "*", + "ext-reflection": "*", + "ext-spl": "*", + "php": ">=5.3.3", + "phpspec/prophecy": "^1.3.1", + "phpunit/php-code-coverage": "~2.1", + "phpunit/php-file-iterator": "~1.4", + "phpunit/php-text-template": "~1.2", + "phpunit/php-timer": "^1.0.6", + "phpunit/phpunit-mock-objects": "~2.3", + "sebastian/comparator": "~1.2.2", + "sebastian/diff": "~1.2", + "sebastian/environment": "~1.3", + "sebastian/exporter": "~1.2", + "sebastian/global-state": "~1.0", + "sebastian/version": "~1.0", + "symfony/yaml": "~2.1|~3.0" + }, + "suggest": { + "phpunit/php-invoker": "~1.1" + }, + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.8.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", + "keywords": [ + "phpunit", + "testing", + "xunit" + ], + "time": "2017-06-21T08:07:12+00:00" + }, + { + "name": "phpunit/phpunit-mock-objects", + "version": "2.3.8", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git", + "reference": "ac8e7a3db35738d56ee9a76e78a4e03d97628983" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/ac8e7a3db35738d56ee9a76e78a4e03d97628983", + "reference": "ac8e7a3db35738d56ee9a76e78a4e03d97628983", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.0.2", + "php": ">=5.3.3", + "phpunit/php-text-template": "~1.2", + "sebastian/exporter": "~1.2" + }, + "require-dev": { + "phpunit/phpunit": "~4.4" + }, + "suggest": { + "ext-soap": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.3.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Mock Object library for PHPUnit", + "homepage": "https://github.com/sebastianbergmann/phpunit-mock-objects/", + "keywords": [ + "mock", + "xunit" + ], + "abandoned": true, + "time": "2015-10-02T06:51:40+00:00" + }, + { + "name": "phpunit/phpunit-selenium", + "version": "1.4.2", + "source": { + "type": "git", + "url": "https://github.com/giorgiosironi/phpunit-selenium.git", + "reference": "c84dd7ca214563868ce216123b7ae9c792beb262" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/giorgiosironi/phpunit-selenium/zipball/c84dd7ca214563868ce216123b7ae9c792beb262", + "reference": "c84dd7ca214563868ce216123b7ae9c792beb262", + "shasum": "" + }, + "require": { + "ext-curl": "*", + "ext-dom": "*", + "php": ">=5.3.3", + "phpunit/phpunit": "~3.7|~4.0", + "sebastian/comparator": "~1.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "PHPUnit/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "include-path": [ + "" + ], + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + }, + { + "name": "Giorgio Sironi", + "email": "info@giorgiosironi.com", + "role": "developer" + } + ], + "description": "Selenium Server integration for PHPUnit", + "homepage": "http://www.phpunit.de/", + "keywords": [ + "selenium", + "testing", + "xunit" + ], + "time": "2014-11-02T09:23:27+00:00" + }, + { + "name": "psr/log", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd", + "reference": "6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "Psr/Log/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "time": "2018-11-20T15:27:04+00:00" + }, + { + "name": "sebastian/comparator", + "version": "1.2.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "2b7424b55f5047b47ac6e5ccb20b2aea4011d9be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/2b7424b55f5047b47ac6e5ccb20b2aea4011d9be", + "reference": "2b7424b55f5047b47ac6e5ccb20b2aea4011d9be", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "sebastian/diff": "~1.2", + "sebastian/exporter": "~1.2 || ~2.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides the functionality to compare PHP values for equality", + "homepage": "http://www.github.com/sebastianbergmann/comparator", + "keywords": [ + "comparator", + "compare", + "equality" + ], + "time": "2017-01-29T09:50:25+00:00" + }, + { + "name": "sebastian/diff", + "version": "1.4.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "7f066a26a962dbe58ddea9f72a4e82874a3975a4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/7f066a26a962dbe58ddea9f72a4e82874a3975a4", + "reference": "7f066a26a962dbe58ddea9f72a4e82874a3975a4", + "shasum": "" + }, + "require": { + "php": "^5.3.3 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", + "keywords": [ + "diff" + ], + "time": "2017-05-22T07:24:03+00:00" + }, + { + "name": "sebastian/environment", + "version": "1.3.8", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "be2c607e43ce4c89ecd60e75c6a85c126e754aea" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/be2c607e43ce4c89ecd60e75c6a85c126e754aea", + "reference": "be2c607e43ce4c89ecd60e75c6a85c126e754aea", + "shasum": "" + }, + "require": { + "php": "^5.3.3 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8 || ^5.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides functionality to handle HHVM/PHP environments", + "homepage": "http://www.github.com/sebastianbergmann/environment", + "keywords": [ + "Xdebug", + "environment", + "hhvm" + ], + "time": "2016-08-18T05:49:44+00:00" + }, + { + "name": "sebastian/exporter", + "version": "1.2.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "42c4c2eec485ee3e159ec9884f95b431287edde4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/42c4c2eec485ee3e159ec9884f95b431287edde4", + "reference": "42c4c2eec485ee3e159ec9884f95b431287edde4", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "sebastian/recursion-context": "~1.0" + }, + "require-dev": { + "ext-mbstring": "*", + "phpunit/phpunit": "~4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides the functionality to export PHP variables for visualization", + "homepage": "http://www.github.com/sebastianbergmann/exporter", + "keywords": [ + "export", + "exporter" + ], + "time": "2016-06-17T09:04:28+00:00" + }, + { + "name": "sebastian/global-state", + "version": "1.1.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bc37d50fea7d017d3d340f230811c9f1d7280af4", + "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.2" + }, + "suggest": { + "ext-uopz": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Snapshotting of global state", + "homepage": "http://www.github.com/sebastianbergmann/global-state", + "keywords": [ + "global state" + ], + "time": "2015-10-12T03:26:01+00:00" + }, + { + "name": "sebastian/recursion-context", + "version": "1.0.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "b19cc3298482a335a95f3016d2f8a6950f0fbcd7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/b19cc3298482a335a95f3016d2f8a6950f0fbcd7", + "reference": "b19cc3298482a335a95f3016d2f8a6950f0fbcd7", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides functionality to recursively process PHP variables", + "homepage": "http://www.github.com/sebastianbergmann/recursion-context", + "time": "2016-10-03T07:41:43+00:00" + }, + { + "name": "sebastian/version", + "version": "1.0.6", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "58b3a85e7999757d6ad81c787a1fbf5ff6c628c6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/58b3a85e7999757d6ad81c787a1fbf5ff6c628c6", + "reference": "58b3a85e7999757d6ad81c787a1fbf5ff6c628c6", + "shasum": "" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that helps with managing the version number of Git-hosted PHP projects", + "homepage": "https://github.com/sebastianbergmann/version", + "time": "2015-06-21T13:59:46+00:00" + }, + { + "name": "squizlabs/php_codesniffer", + "version": "3.4.2", + "source": { + "type": "git", + "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", + "reference": "b8a7362af1cc1aadb5bd36c3defc4dda2cf5f0a8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/b8a7362af1cc1aadb5bd36c3defc4dda2cf5f0a8", + "reference": "b8a7362af1cc1aadb5bd36c3defc4dda2cf5f0a8", + "shasum": "" + }, + "require": { + "ext-simplexml": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0" + }, + "bin": [ + "bin/phpcs", + "bin/phpcbf" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Greg Sherwood", + "role": "lead" + } + ], + "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", + "homepage": "https://github.com/squizlabs/PHP_CodeSniffer", + "keywords": [ + "phpcs", + "standards" + ], + "time": "2019-04-10T23:49:02+00:00" + }, + { + "name": "symfony/console", + "version": "v2.8.50", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "cbcf4b5e233af15cd2bbd50dee1ccc9b7927dc12" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/cbcf4b5e233af15cd2bbd50dee1ccc9b7927dc12", + "reference": "cbcf4b5e233af15cd2bbd50dee1ccc9b7927dc12", + "shasum": "" + }, + "require": { + "php": ">=5.3.9", + "symfony/debug": "^2.7.2|~3.0.0", + "symfony/polyfill-mbstring": "~1.0" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/event-dispatcher": "~2.1|~3.0.0", + "symfony/process": "~2.1|~3.0.0" + }, + "suggest": { + "psr/log-implementation": "For using the console logger", + "symfony/event-dispatcher": "", + "symfony/process": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.8-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Console\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Console Component", + "homepage": "https://symfony.com", + "time": "2018-11-20T15:55:20+00:00" + }, + { + "name": "symfony/debug", + "version": "v2.8.50", + "source": { + "type": "git", + "url": "https://github.com/symfony/debug.git", + "reference": "74251c8d50dd3be7c4ce0c7b862497cdc641a5d0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/debug/zipball/74251c8d50dd3be7c4ce0c7b862497cdc641a5d0", + "reference": "74251c8d50dd3be7c4ce0c7b862497cdc641a5d0", + "shasum": "" + }, + "require": { + "php": ">=5.3.9", + "psr/log": "~1.0" + }, + "conflict": { + "symfony/http-kernel": ">=2.3,<2.3.24|~2.4.0|>=2.5,<2.5.9|>=2.6,<2.6.2" + }, + "require-dev": { + "symfony/class-loader": "~2.2|~3.0.0", + "symfony/http-kernel": "~2.3.24|~2.5.9|^2.6.2|~3.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.8-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Debug\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Debug Component", + "homepage": "https://symfony.com", + "time": "2018-11-11T11:18:13+00:00" + }, + { + "name": "symfony/process", + "version": "v2.8.50", + "source": { + "type": "git", + "url": "https://github.com/symfony/process.git", + "reference": "c3591a09c78639822b0b290d44edb69bf9f05dc8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/process/zipball/c3591a09c78639822b0b290d44edb69bf9f05dc8", + "reference": "c3591a09c78639822b0b290d44edb69bf9f05dc8", + "shasum": "" + }, + "require": { + "php": ">=5.3.9" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.8-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Process\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Process Component", + "homepage": "https://symfony.com", + "time": "2018-11-11T11:18:13+00:00" + }, + { + "name": "symfony/yaml", + "version": "v2.8.50", + "source": { + "type": "git", + "url": "https://github.com/symfony/yaml.git", + "reference": "02c1859112aa779d9ab394ae4f3381911d84052b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/yaml/zipball/02c1859112aa779d9ab394ae4f3381911d84052b", + "reference": "02c1859112aa779d9ab394ae4f3381911d84052b", + "shasum": "" + }, + "require": { + "php": ">=5.3.9", + "symfony/polyfill-ctype": "~1.8" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.8-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Yaml\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Yaml Component", + "homepage": "https://symfony.com", + "time": "2018-11-11T11:18:13+00:00" + }, + { + "name": "webmozart/assert", + "version": "1.5.0", + "source": { + "type": "git", + "url": "https://github.com/webmozart/assert.git", + "reference": "88e6d84706d09a236046d686bbea96f07b3a34f4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webmozart/assert/zipball/88e6d84706d09a236046d686bbea96f07b3a34f4", + "reference": "88e6d84706d09a236046d686bbea96f07b3a34f4", + "shasum": "" + }, + "require": { + "php": "^5.3.3 || ^7.0", + "symfony/polyfill-ctype": "^1.8" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.36 || ^7.5.13" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3-dev" + } + }, + "autoload": { + "psr-4": { + "Webmozart\\Assert\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Assertions to validate method input/output with nice error messages.", + "keywords": [ + "assert", + "check", + "validate" + ], + "time": "2019-08-24T08:43:50+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": ">=5.5.0", + "ext-mysqli": "*", + "ext-xml": "*", + "ext-pcre": "*", + "ext-json": "*", + "ext-ctype": "*", + "ext-hash": "*" + }, + "platform-dev": [], + "platform-overrides": { + "php": "5.5" + } +} diff --git a/php/apps/phpmyadmin49/html/config.sample.inc.php b/php/apps/phpmyadmin49/html/config.sample.inc.php new file mode 100644 index 00000000..5eede6dd --- /dev/null +++ b/php/apps/phpmyadmin49/html/config.sample.inc.php @@ -0,0 +1,154 @@ +. + * + * @package PhpMyAdmin + */ + +/** + * This is needed for cookie based authentication to encrypt password in + * cookie. Needs to be 32 chars long. + */ +$cfg['blowfish_secret'] = ''; /* YOU MUST FILL IN THIS FOR COOKIE AUTH! */ + +/** + * Servers configuration + */ +$i = 0; + +/** + * First server + */ +$i++; +/* Authentication type */ +$cfg['Servers'][$i]['auth_type'] = 'cookie'; +/* Server parameters */ +$cfg['Servers'][$i]['host'] = 'localhost'; +$cfg['Servers'][$i]['compress'] = false; +$cfg['Servers'][$i]['AllowNoPassword'] = false; + +/** + * phpMyAdmin configuration storage settings. + */ + +/* User used to manipulate with storage */ +// $cfg['Servers'][$i]['controlhost'] = ''; +// $cfg['Servers'][$i]['controlport'] = ''; +// $cfg['Servers'][$i]['controluser'] = 'pma'; +// $cfg['Servers'][$i]['controlpass'] = 'pmapass'; + +/* Storage database and tables */ +// $cfg['Servers'][$i]['pmadb'] = 'phpmyadmin'; +// $cfg['Servers'][$i]['bookmarktable'] = 'pma__bookmark'; +// $cfg['Servers'][$i]['relation'] = 'pma__relation'; +// $cfg['Servers'][$i]['table_info'] = 'pma__table_info'; +// $cfg['Servers'][$i]['table_coords'] = 'pma__table_coords'; +// $cfg['Servers'][$i]['pdf_pages'] = 'pma__pdf_pages'; +// $cfg['Servers'][$i]['column_info'] = 'pma__column_info'; +// $cfg['Servers'][$i]['history'] = 'pma__history'; +// $cfg['Servers'][$i]['table_uiprefs'] = 'pma__table_uiprefs'; +// $cfg['Servers'][$i]['tracking'] = 'pma__tracking'; +// $cfg['Servers'][$i]['userconfig'] = 'pma__userconfig'; +// $cfg['Servers'][$i]['recent'] = 'pma__recent'; +// $cfg['Servers'][$i]['favorite'] = 'pma__favorite'; +// $cfg['Servers'][$i]['users'] = 'pma__users'; +// $cfg['Servers'][$i]['usergroups'] = 'pma__usergroups'; +// $cfg['Servers'][$i]['navigationhiding'] = 'pma__navigationhiding'; +// $cfg['Servers'][$i]['savedsearches'] = 'pma__savedsearches'; +// $cfg['Servers'][$i]['central_columns'] = 'pma__central_columns'; +// $cfg['Servers'][$i]['designer_settings'] = 'pma__designer_settings'; +// $cfg['Servers'][$i]['export_templates'] = 'pma__export_templates'; + +/** + * End of servers configuration + */ + +/** + * Directories for saving/loading files from server + */ +$cfg['UploadDir'] = ''; +$cfg['SaveDir'] = ''; + +/** + * Whether to display icons or text or both icons and text in table row + * action segment. Value can be either of 'icons', 'text' or 'both'. + * default = 'both' + */ +//$cfg['RowActionType'] = 'icons'; + +/** + * Defines whether a user should be displayed a "show all (records)" + * button in browse mode or not. + * default = false + */ +//$cfg['ShowAll'] = true; + +/** + * Number of rows displayed when browsing a result set. If the result + * set contains more rows, "Previous" and "Next". + * Possible values: 25, 50, 100, 250, 500 + * default = 25 + */ +//$cfg['MaxRows'] = 50; + +/** + * Disallow editing of binary fields + * valid values are: + * false allow editing + * 'blob' allow editing except for BLOB fields + * 'noblob' disallow editing except for BLOB fields + * 'all' disallow editing + * default = 'blob' + */ +//$cfg['ProtectBinary'] = false; + +/** + * Default language to use, if not browser-defined or user-defined + * (you find all languages in the locale folder) + * uncomment the desired line: + * default = 'en' + */ +//$cfg['DefaultLang'] = 'en'; +//$cfg['DefaultLang'] = 'de'; + +/** + * How many columns should be used for table display of a database? + * (a value larger than 1 results in some information being hidden) + * default = 1 + */ +//$cfg['PropertiesNumColumns'] = 2; + +/** + * Set to true if you want DB-based query history.If false, this utilizes + * JS-routines to display query history (lost by window close) + * + * This requires configuration storage enabled, see above. + * default = false + */ +//$cfg['QueryHistoryDB'] = true; + +/** + * When using DB-based query history, how many entries should be kept? + * default = 25 + */ +//$cfg['QueryHistoryMax'] = 100; + +/** + * Whether or not to query the user before sending the error report to + * the phpMyAdmin team when a JavaScript error occurs + * + * Available options + * ('ask' | 'always' | 'never') + * default = 'ask' + */ +//$cfg['SendErrorReports'] = 'always'; + +/** + * You can find more configuration options in the documentation + * in the doc/ folder or at . + */ diff --git a/php/apps/phpmyadmin49/html/db_central_columns.php b/php/apps/phpmyadmin49/html/db_central_columns.php new file mode 100644 index 00000000..6eebb40a --- /dev/null +++ b/php/apps/phpmyadmin49/html/db_central_columns.php @@ -0,0 +1,181 @@ +updateOneColumn( + $db, $orig_col_name, $col_name, $col_type, $col_attribute, + $col_length, $col_isNull, $collation, $col_extra, $col_default + ); + exit; + } else { + $tmp_msg = $centralColumns->updateOneColumn( + $db, "", $col_name, $col_type, $col_attribute, + $col_length, $col_isNull, $collation, $col_extra, $col_default + ); + } +} +if (isset($_POST['populateColumns'])) { + $selected_tbl = $_POST['selectedTable']; + echo $centralColumns->getHtmlForColumnDropdown( + $db, + $selected_tbl + ); + exit; +} +if (isset($_POST['getColumnList'])) { + echo $centralColumns->getListRaw( + $db, + $_POST['cur_table'] + ); + exit; +} +if (isset($_POST['add_column'])) { + $selected_col = array(); + $selected_tbl = $_POST['table-select']; + $selected_col[] = $_POST['column-select']; + $tmp_msg = $centralColumns->syncUniqueColumns( + $selected_col, + false, + $selected_tbl + ); +} +$response = Response::getInstance(); +$header = $response->getHeader(); +$scripts = $header->getScripts(); +$scripts->addFile('vendor/jquery/jquery.uitablefilter.js'); +$scripts->addFile('vendor/jquery/jquery.tablesorter.js'); +$scripts->addFile('db_central_columns.js'); +$cfgCentralColumns = $centralColumns->getParams(); +$pmadb = $cfgCentralColumns['db']; +$pmatable = $cfgCentralColumns['table']; +$max_rows = intval($GLOBALS['cfg']['MaxRows']); + +if (isset($_POST['edit_central_columns_page'])) { + $selected_fld = $_POST['selected_fld']; + $selected_db = $_POST['db']; + $edit_central_column_page = $centralColumns->getHtmlForEditingPage( + $selected_fld, + $selected_db + ); + $response->addHTML($edit_central_column_page); + exit; +} +if (isset($_POST['multi_edit_central_column_save'])) { + $message = $centralColumns->updateMultipleColumn(); + if (!is_bool($message)) { + $response->setRequestStatus(false); + $response->addJSON('message', $message); + } +} +if (isset($_POST['delete_save'])) { + $col_name = array(); + parse_str($_POST['col_name'], $col_name); + $tmp_msg = $centralColumns->deleteColumnsFromList( + $col_name['selected_fld'], + false + ); +} +if (!empty($_POST['total_rows']) + && Core::isValid($_POST['total_rows'], 'integer') +) { + $total_rows = $_POST['total_rows']; +} else { + $total_rows = $centralColumns->getCount($db); +} +if (Core::isValid($_POST['pos'], 'integer')) { + $pos = intval($_POST['pos']); +} else { + $pos = 0; +} +$addNewColumn = $centralColumns->getHtmlForAddNewColumn($db, $total_rows); +$response->addHTML($addNewColumn); +if ($total_rows <= 0) { + $response->addHTML( + '
' . __( + 'The central list of columns for the current database is empty.' + ) . '
' + ); + $columnAdd = $centralColumns->getHtmlForAddColumn($total_rows, $pos, $db); + $response->addHTML($columnAdd); + exit; +} +$table_navigation_html = $centralColumns->getHtmlForTableNavigation( + $total_rows, + $pos, + $db +); +$response->addHTML($table_navigation_html); +$columnAdd = $centralColumns->getHtmlForAddColumn($total_rows, $pos, $db); +$response->addHTML($columnAdd); +$deleteRowForm = '
' + . Url::getHiddenInputs( + $db + ) + . '' + . '' + . '
'; +$response->addHTML($deleteRowForm); +$table_struct = '
' + . '
' + . ''; +$response->addHTML($table_struct); +$tableheader = $centralColumns->getTableHeader( + 'column_heading', __('Click to sort.'), 2 +); +$response->addHTML($tableheader); +$result = $centralColumns->getColumnsList($db, $pos, $max_rows); +$row_num = 0; +foreach ($result as $row) { + $tableHtmlRow = $centralColumns->getHtmlForTableRow( + $row, + $row_num, + $db + ); + $response->addHTML($tableHtmlRow); + $row_num++; +} +$response->addHTML('
'); +$tablefooter = $centralColumns->getTableFooter($pmaThemeImage, $text_dir); +$response->addHTML($tablefooter); +$response->addHTML('
'); +$message = Message::success( + sprintf(__('Showing rows %1$s - %2$s.'), ($pos + 1), ($pos + count($result))) +); +if (isset($tmp_msg) && $tmp_msg !== true) { + $message = $tmp_msg; +} diff --git a/php/apps/phpmyadmin49/html/db_datadict.php b/php/apps/phpmyadmin49/html/db_datadict.php new file mode 100644 index 00000000..7a5d7f4f --- /dev/null +++ b/php/apps/phpmyadmin49/html/db_datadict.php @@ -0,0 +1,215 @@ +getHeader(); +$header->enablePrintView(); + +$relation = new Relation(); + +/** + * Gets the relations settings + */ +$cfgRelation = $relation->getRelationsParam(); + +/** + * Check parameters + */ +PhpMyAdmin\Util::checkParameters(array('db')); + +/** + * Defines the url to return to in case of error in a sql statement + */ +$err_url = 'db_sql.php' . Url::getCommon(array('db' => $db)); + +if ($cfgRelation['commwork']) { + $comment = $relation->getDbComment($db); + + /** + * Displays DB comment + */ + if ($comment) { + echo '

' , __('Database comment') + , '
' , htmlspecialchars($comment) , '

'; + } // end if +} + +/** + * Selects the database and gets tables names + */ +$GLOBALS['dbi']->selectDb($db); +$tables = $GLOBALS['dbi']->getTables($db); + +$count = 0; +foreach ($tables as $table) { + $comments = $relation->getComments($db, $table); + + echo '
' , "\n"; + + echo '

' , htmlspecialchars($table) , '

' , "\n"; + + /** + * Gets table information + */ + $show_comment = $GLOBALS['dbi']->getTable($db, $table) + ->getStatusInfo('TABLE_COMMENT'); + + /** + * Gets table keys and retains them + */ + $GLOBALS['dbi']->selectDb($db); + $indexes = $GLOBALS['dbi']->getTableIndexes($db, $table); + list($primary, $pk_array, $indexes_info, $indexes_data) + = PhpMyAdmin\Util::processIndexData($indexes); + + /** + * Gets columns properties + */ + $columns = $GLOBALS['dbi']->getColumns($db, $table); + + // Check if we can use Relations + list($res_rel, $have_rel) = $relation->getRelationsAndStatus( + ! empty($cfgRelation['relation']), $db, $table + ); + + /** + * Displays the comments of the table if MySQL >= 3.23 + */ + if (!empty($show_comment)) { + echo __('Table comments:') , ' '; + echo htmlspecialchars($show_comment) , '

'; + } + + /** + * Displays the table structure + */ + + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + if ($have_rel) { + echo ' ' , "\n"; + } + echo ' ' , "\n"; + if ($cfgRelation['mimework']) { + echo ' ' , "\n"; + } + echo ''; + foreach ($columns as $row) { + + if ($row['Null'] == '') { + $row['Null'] = 'NO'; + } + $extracted_columnspec + = PhpMyAdmin\Util::extractColumnSpec($row['Type']); + + // reformat mysql query output + // set or enum types: slashes single quotes inside options + + $type = htmlspecialchars($extracted_columnspec['print_type']); + $attribute = $extracted_columnspec['attribute']; + if (! isset($row['Default'])) { + if ($row['Null'] != 'NO') { + $row['Default'] = 'NULL'; + } + } else { + $row['Default'] = htmlspecialchars($row['Default']); + } + $column_name = $row['Field']; + + echo ''; + echo ''; + echo '' , $type , ''; + + echo ''; + echo ''; + + if ($have_rel) { + echo ' ' , "\n"; + } + echo ' ' , "\n"; + if ($cfgRelation['mimework']) { + $mime_map = Transformations::getMIME($db, $table, true); + + echo ' ' , "\n"; + } + echo ''; + } // end foreach + $count++; + echo '
' , __('Column') , '' , __('Type') , '' , __('Null') , '' , __('Default') , '' , __('Links to') , '' , __('Comments') , 'MIME
'; + echo htmlspecialchars($column_name); + + if (isset($pk_array[$row['Field']])) { + echo ' (' , __('Primary') , ')'; + } + echo ''; + echo (($row['Null'] == 'NO') ? __('No') : __('Yes')); + echo ''; + if (isset($row['Default'])) { + echo $row['Default']; + } + echo ''; + if ($foreigner = $relation->searchColumnInForeigners($res_rel, $column_name)) { + echo htmlspecialchars( + $foreigner['foreign_table'] + . ' -> ' + . $foreigner['foreign_field'] + ); + } + echo ''; + if (isset($comments[$column_name])) { + echo htmlspecialchars($comments[$column_name]); + } + echo ''; + if (isset($mime_map[$column_name])) { + echo htmlspecialchars( + str_replace('_', '/', $mime_map[$column_name]['mimetype']) + ); + } + echo '
'; + // display indexes information + if (count(PhpMyAdmin\Index::getFromTable($table, $db)) > 0) { + echo PhpMyAdmin\Index::getHtmlForIndexes($table, $db, true); + } + echo '
'; +} //ends main while + +/** + * Displays the footer + */ +echo PhpMyAdmin\Util::getButton(); diff --git a/php/apps/phpmyadmin49/html/db_designer.php b/php/apps/phpmyadmin49/html/db_designer.php new file mode 100644 index 00000000..e26f4b47 --- /dev/null +++ b/php/apps/phpmyadmin49/html/db_designer.php @@ -0,0 +1,228 @@ +getHtmlForEditOrDeletePages($_POST['db'], 'editPage'); + } elseif ($_POST['dialog'] == 'delete') { + $html = $databaseDesigner->getHtmlForEditOrDeletePages($_POST['db'], 'deletePage'); + } elseif ($_POST['dialog'] == 'save_as') { + $html = $databaseDesigner->getHtmlForPageSaveAs($_POST['db']); + } elseif ($_POST['dialog'] == 'export') { + $html = $databaseDesigner->getHtmlForSchemaExport( + $_POST['db'], $_POST['selected_page'] + ); + } elseif ($_POST['dialog'] == 'add_table') { + // Pass the db and table to the getTablesInfo so we only have the table we asked for + $script_display_field = $designerCommon->getTablesInfo($_POST['db'], $_POST['table']); + $tab_column = $designerCommon->getColumnsInfo($script_display_field); + $tables_all_keys = $designerCommon->getAllKeys($script_display_field); + $tables_pk_or_unique_keys = $designerCommon->getPkOrUniqueKeys($script_display_field); + + $html = $databaseDesigner->getDatabaseTables( + $_POST['db'], + $script_display_field, + array(), -1, $tab_column, + $tables_all_keys, $tables_pk_or_unique_keys + ); + } + + if (! empty($html)) { + $response->addHTML($html); + } + return; +} + +if (isset($_POST['operation'])) { + + if ($_POST['operation'] == 'deletePage') { + $success = $designerCommon->deletePage($_POST['selected_page']); + $response->setRequestStatus($success); + } elseif ($_POST['operation'] == 'savePage') { + if ($_POST['save_page'] == 'same') { + $page = $_POST['selected_page']; + } else { // new + $page = $designerCommon->createNewPage($_POST['selected_value'], $_POST['db']); + $response->addJSON('id', $page); + } + $success = $designerCommon->saveTablePositions($page); + $response->setRequestStatus($success); + } elseif ($_POST['operation'] == 'setDisplayField') { + $designerCommon->saveDisplayField( + $_POST['db'], $_POST['table'], $_POST['field'] + ); + $response->setRequestStatus(true); + } elseif ($_POST['operation'] == 'addNewRelation') { + list($success, $message) = $designerCommon->addNewRelation( + $_POST['db'], + $_POST['T1'], + $_POST['F1'], + $_POST['T2'], + $_POST['F2'], + $_POST['on_delete'], + $_POST['on_update'], + $_POST['DB1'], + $_POST['DB2'] + ); + $response->setRequestStatus($success); + $response->addJSON('message', $message); + } elseif ($_POST['operation'] == 'removeRelation') { + list($success, $message) = $designerCommon->removeRelation( + $_POST['T1'], + $_POST['F1'], + $_POST['T2'], + $_POST['F2'] + ); + $response->setRequestStatus($success); + $response->addJSON('message', $message); + } elseif ($_POST['operation'] == 'save_setting_value') { + $success = $designerCommon->saveSetting($_POST['index'], $_POST['value']); + $response->setRequestStatus($success); + } + + return; +} + +require 'libraries/db_common.inc.php'; + +$script_display_field = $designerCommon->getTablesInfo(); + +$display_page = -1; +$selected_page = null; + +if (isset($_GET['query'])) { + $display_page = $designerCommon->getDefaultPage($_GET['db']); +} else { + if (! empty($_GET['page'])) { + $display_page = $_GET['page']; + } else { + $display_page = $designerCommon->getLoadingPage($_GET['db']); + } +} +if ($display_page != -1) { + $selected_page = $designerCommon->getPageName($display_page); +} +$tab_pos = $designerCommon->getTablePositions($display_page); + +$fullTableNames = []; + +foreach($script_display_field as $designerTable) { + $fullTableNames[] = $designerTable->getDbTableString(); +} + +foreach($tab_pos as $position) { + if (! in_array($position['dbName'] . '.' . $position['tableName'], $fullTableNames)) { + foreach($designerCommon->getTablesInfo($position['dbName'], $position['tableName']) as $designerTable) { + $script_display_field[] = $designerTable; + } + } +} + + +$tab_column = $designerCommon->getColumnsInfo($script_display_field); +$script_tables = $designerCommon->getScriptTabs($script_display_field); +$tables_pk_or_unique_keys = $designerCommon->getPkOrUniqueKeys($script_display_field); +$tables_all_keys = $designerCommon->getAllKeys($script_display_field); +$classes_side_menu = $databaseDesigner->returnClassNamesFromMenuButtons(); + + +$script_contr = $designerCommon->getScriptContr($script_display_field); + +$params = array('lang' => $GLOBALS['lang']); +if (isset($_GET['db'])) { + $params['db'] = $_GET['db']; +} + +$response = Response::getInstance(); +$response->getFooter()->setMinimal(); +$header = $response->getHeader(); +$header->setBodyId('designer_body'); + +$scripts = $header->getScripts(); +$scripts->addFile('vendor/jquery/jquery.fullscreen.js'); +$scripts->addFile('designer/database.js'); +$scripts->addFile('designer/objects.js'); +$scripts->addFile('designer/page.js'); +$scripts->addFile('designer/history.js'); +$scripts->addFile('designer/move.js'); +$scripts->addFile('designer/init.js'); + +list( + $tables, + $num_tables, + $total_num_tables, + $sub_part, + $is_show_stats, + $db_is_system_schema, + $tooltip_truename, + $tooltip_aliasname, + $pos +) = PhpMyAdmin\Util::getDbInfo($db, isset($sub_part) ? $sub_part : ''); + +// Embed some data into HTML, later it will be read +// by designer/init.js and converted to JS variables. +$response->addHTML( + $databaseDesigner->getHtmlForJsFields( + $script_tables, $script_contr, $script_display_field, $display_page + ) +); +$response->addHTML( + $databaseDesigner->getPageMenu( + isset($_GET['query']), + $selected_page, + $classes_side_menu + ) +); + + + +$response->addHTML('
'); +$response->addHTML( + '
' +); + +$response->addHTML($databaseDesigner->getHtmlCanvas()); +$response->addHTML($databaseDesigner->getHtmlTableList()); + +$response->addHTML( + $databaseDesigner->getDatabaseTables( + $_GET['db'], + $script_display_field, + $tab_pos, $display_page, $tab_column, + $tables_all_keys, $tables_pk_or_unique_keys + ) +); +$response->addHTML('
'); +$response->addHTML('
'); // end canvas_outer + +$response->addHTML('
'); + +$response->addHTML($databaseDesigner->getNewRelationPanel()); +$response->addHTML($databaseDesigner->getDeleteRelationPanel()); + +if (isset($_GET['query'])) { + $response->addHTML($databaseDesigner->getOptionsPanel()); + $response->addHTML($databaseDesigner->getRenameToPanel()); + $response->addHTML($databaseDesigner->getHavingQueryPanel()); + $response->addHTML($databaseDesigner->getAggregateQueryPanel()); + $response->addHTML($databaseDesigner->getWhereQueryPanel()); + $response->addHTML($databaseDesigner->getQueryDetails($_GET['db'])); +} + +$response->addHTML('
'); diff --git a/php/apps/phpmyadmin49/html/db_events.php b/php/apps/phpmyadmin49/html/db_events.php new file mode 100644 index 00000000..e13935fc --- /dev/null +++ b/php/apps/phpmyadmin49/html/db_events.php @@ -0,0 +1,18 @@ +getHeader(); +$scripts = $header->getScripts(); +$scripts->addFile('export.js'); + +// $sub_part is used in Util::getDbInfo() to see if we are coming from +// db_export.php, in which case we don't obey $cfg['MaxTableList'] +$sub_part = '_export'; +require_once 'libraries/db_common.inc.php'; +$url_query .= '&goto=db_export.php'; + +list( + $tables, + $num_tables, + $total_num_tables, + $sub_part, + $is_show_stats, + $db_is_system_schema, + $tooltip_truename, + $tooltip_aliasname, + $pos +) = Util::getDbInfo($db, isset($sub_part) ? $sub_part : ''); + +/** + * Displays the form + */ +$export_page_title = __('View dump (schema) of database'); + +// exit if no tables in db found +if ($num_tables < 1) { + $response->addHTML( + Message::error(__('No tables found in database.'))->getDisplay() + ); + exit; +} // end if + +$multi_values = '
'; +if (isset($_POST['structure_or_data_forced'])) { + $force_val = htmlspecialchars($_POST['structure_or_data_forced']); +} else { + $force_val = 0; +} +$multi_values .= ''; +$multi_values .= '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . ''; +$multi_values .= "\n"; + +// when called by libraries/mult_submits.inc.php +if (!empty($_POST['selected_tbl']) && empty($table_select)) { + $table_select = $_POST['selected_tbl']; +} + +foreach ($tables as $each_table) { + if (isset($_POST['table_select']) && is_array($_POST['table_select'])) { + $is_checked = Export::getCheckedClause( + $each_table['Name'], $_POST['table_select'] + ); + } elseif (isset($table_select)) { + $is_checked = Export::getCheckedClause( + $each_table['Name'], $table_select + ); + } else { + $is_checked = ' checked="checked"'; + } + if (isset($_POST['table_structure']) && is_array($_POST['table_structure'])) { + $structure_checked = Export::getCheckedClause( + $each_table['Name'], $_POST['table_structure'] + ); + } else { + $structure_checked = $is_checked; + } + if (isset($_POST['table_data']) && is_array($_POST['table_data'])) { + $data_checked = Export::getCheckedClause( + $each_table['Name'], $_POST['table_data'] + ); + } else { + $data_checked = $is_checked; + } + $table_html = htmlspecialchars($each_table['Name']); + $multi_values .= ''; + $multi_values .= ''; + $multi_values .= ''; + $multi_values .= ''; + $multi_values .= ''; + $multi_values .= ''; +} // end for + +$multi_values .= "\n"; +$multi_values .= '
' . __('Tables') . '' . __('Structure') . '' . __('Data') . '
' . __('Select all') . '' + . '' + . '
' + . str_replace(' ', ' ', $table_html) . '' + . '' + . '
'; + +if (! isset($sql_query)) { + $sql_query = ''; +} +if (! isset($num_tables)) { + $num_tables = 0; +} +if (! isset($unlim_num_rows)) { + $unlim_num_rows = 0; +} +if (! isset($multi_values)) { + $multi_values = ''; +} +$response = Response::getInstance(); +$displayExport = new DisplayExport(); +$response->addHTML( + $displayExport->getDisplay( + 'database', $db, $table, $sql_query, $num_tables, + $unlim_num_rows, $multi_values + ) +); diff --git a/php/apps/phpmyadmin49/html/db_import.php b/php/apps/phpmyadmin49/html/db_import.php new file mode 100644 index 00000000..0ec1d49e --- /dev/null +++ b/php/apps/phpmyadmin49/html/db_import.php @@ -0,0 +1,44 @@ +getHeader(); +$scripts = $header->getScripts(); +$scripts->addFile('import.js'); + +/** + * Gets tables information and displays top links + */ +require 'libraries/db_common.inc.php'; + +list( + $tables, + $num_tables, + $total_num_tables, + $sub_part, + $is_show_stats, + $db_is_system_schema, + $tooltip_truename, + $tooltip_aliasname, + $pos +) = PhpMyAdmin\Util::getDbInfo($db, isset($sub_part) ? $sub_part : ''); + +$response = Response::getInstance(); +$response->addHTML( + Import::get( + 'database', $db, $table, $max_upload_size + ) +); diff --git a/php/apps/phpmyadmin49/html/db_multi_table_query.php b/php/apps/phpmyadmin49/html/db_multi_table_query.php new file mode 100644 index 00000000..e30f15d3 --- /dev/null +++ b/php/apps/phpmyadmin49/html/db_multi_table_query.php @@ -0,0 +1,38 @@ +getForeignKeyConstrains( + $_REQUEST['db'], + $_GET['tables'] + ); + $response = Response::getInstance(); + $response->addJSON('foreignKeyConstrains',$constrains); +} else { + $response = Response::getInstance(); + + $header = $response->getHeader(); + $scripts = $header->getScripts(); + $scripts->addFile('vendor/jquery/jquery.md5.js'); + $scripts->addFile('db_multi_table_query.js'); + $scripts->addFile('db_query_generator.js'); + + $queryInstance = new MultiTableQuery($GLOBALS['dbi'], $db); + + $response->addHTML($queryInstance->getFormHtml()); +} diff --git a/php/apps/phpmyadmin49/html/db_operations.php b/php/apps/phpmyadmin49/html/db_operations.php new file mode 100644 index 00000000..c5e99783 --- /dev/null +++ b/php/apps/phpmyadmin49/html/db_operations.php @@ -0,0 +1,308 @@ +getHeader(); +$scripts = $header->getScripts(); +$scripts->addFile('db_operations.js'); + +$sql_query = ''; + +$operations = new Operations(); + +/** + * Rename/move or copy database + */ +if (strlen($GLOBALS['db']) > 0 + && (! empty($_POST['db_rename']) || ! empty($_POST['db_copy'])) +) { + if (! empty($_POST['db_rename'])) { + $move = true; + } else { + $move = false; + } + + if (! isset($_POST['newname']) || strlen($_POST['newname']) === 0) { + $message = Message::error(__('The database name is empty!')); + } else { + // lower_case_table_names=1 `DB` becomes `db` + if ($GLOBALS['dbi']->getLowerCaseNames() === '1') { + $_POST['newname'] = mb_strtolower( + $_POST['newname'] + ); + } + + if ($_POST['newname'] === $_REQUEST['db']) { + $message = Message::error( + __('Cannot copy database to the same name. Change the name and try again.') + ); + } else { + $_error = false; + if ($move || ! empty($_POST['create_database_before_copying'])) { + $operations->createDbBeforeCopy(); + } + + // here I don't use DELIMITER because it's not part of the + // language; I have to send each statement one by one + + // to avoid selecting alternatively the current and new db + // we would need to modify the CREATE definitions to qualify + // the db name + $operations->runProcedureAndFunctionDefinitions($GLOBALS['db']); + + // go back to current db, just in case + $GLOBALS['dbi']->selectDb($GLOBALS['db']); + + $tables_full = $GLOBALS['dbi']->getTablesFull($GLOBALS['db']); + + // remove all foreign key constraints, otherwise we can get errors + /* @var $export_sql_plugin ExportSql */ + $export_sql_plugin = Plugins::getPlugin( + "export", + "sql", + 'libraries/classes/Plugins/Export/', + array( + 'single_table' => isset($single_table), + 'export_type' => 'database' + ) + ); + + // create stand-in tables for views + $views = $operations->getViewsAndCreateSqlViewStandIn( + $tables_full, $export_sql_plugin, $GLOBALS['db'] + ); + + // copy tables + $sqlConstratints = $operations->copyTables( + $tables_full, $move, $GLOBALS['db'] + ); + + // handle the views + if (! $_error) { + $operations->handleTheViews($views, $move, $GLOBALS['db']); + } + unset($views); + + // now that all tables exist, create all the accumulated constraints + if (! $_error && count($sqlConstratints) > 0) { + $operations->createAllAccumulatedConstraints($sqlConstratints); + } + unset($sqlConstratints); + + if ($GLOBALS['dbi']->getVersion() >= 50100) { + // here DELIMITER is not used because it's not part of the + // language; each statement is sent one by one + + $operations->runEventDefinitionsForDb($GLOBALS['db']); + } + + // go back to current db, just in case + $GLOBALS['dbi']->selectDb($GLOBALS['db']); + + // Duplicate the bookmarks for this db (done once for each db) + $operations->duplicateBookmarks($_error, $GLOBALS['db']); + + if (! $_error && $move) { + if (isset($_POST['adjust_privileges']) + && ! empty($_POST['adjust_privileges']) + ) { + $operations->adjustPrivilegesMoveDb($GLOBALS['db'], $_POST['newname']); + } + + /** + * cleanup pmadb stuff for this db + */ + RelationCleanup::database($GLOBALS['db']); + + // if someday the RENAME DATABASE reappears, do not DROP + $local_query = 'DROP DATABASE ' + . Util::backquote($GLOBALS['db']) . ';'; + $sql_query .= "\n" . $local_query; + $GLOBALS['dbi']->query($local_query); + + $message = Message::success( + __('Database %1$s has been renamed to %2$s.') + ); + $message->addParam($GLOBALS['db']); + $message->addParam($_POST['newname']); + } elseif (! $_error) { + if (isset($_POST['adjust_privileges']) + && ! empty($_POST['adjust_privileges']) + ) { + $operations->adjustPrivilegesCopyDb($GLOBALS['db'], $_POST['newname']); + } + + $message = Message::success( + __('Database %1$s has been copied to %2$s.') + ); + $message->addParam($GLOBALS['db']); + $message->addParam($_POST['newname']); + } else { + $message = Message::error(); + } + $reload = true; + + /* Change database to be used */ + if (! $_error && $move) { + $GLOBALS['db'] = $_POST['newname']; + } elseif (! $_error) { + if (isset($_POST['switch_to_new']) + && $_POST['switch_to_new'] == 'true' + ) { + $_SESSION['pma_switch_to_new'] = true; + $GLOBALS['db'] = $_POST['newname']; + } else { + $_SESSION['pma_switch_to_new'] = false; + } + } + } + } + + /** + * Database has been successfully renamed/moved. If in an Ajax request, + * generate the output with {@link PhpMyAdmin\Response} and exit + */ + if ($response->isAjax()) { + $response->setRequestStatus($message->isSuccess()); + $response->addJSON('message', $message); + $response->addJSON('newname', $_POST['newname']); + $response->addJSON( + 'sql_query', + Util::getMessage(null, $sql_query) + ); + $response->addJSON('db', $GLOBALS['db']); + exit; + } +} + +/** + * Settings for relations stuff + */ +$relation = new Relation(); + +$cfgRelation = $relation->getRelationsParam(); + +/** + * Check if comments were updated + * (must be done before displaying the menu tabs) + */ +if (isset($_POST['comment'])) { + $relation->setDbComment($GLOBALS['db'], $_POST['comment']); +} + +require 'libraries/db_common.inc.php'; +$url_query .= '&goto=db_operations.php'; + +// Gets the database structure +$sub_part = '_structure'; + +list( + $tables, + $num_tables, + $total_num_tables, + $sub_part, + $is_show_stats, + $db_is_system_schema, + $tooltip_truename, + $tooltip_aliasname, + $pos +) = Util::getDbInfo($db, isset($sub_part) ? $sub_part : ''); + +echo "\n"; + +if (isset($message)) { + echo Util::getMessage($message, $sql_query); + unset($message); +} + +$db_collation = $GLOBALS['dbi']->getDbCollation($GLOBALS['db']); +$is_information_schema = $GLOBALS['dbi']->isSystemSchema($GLOBALS['db']); + +if (!$is_information_schema) { + if ($cfgRelation['commwork']) { + /** + * database comment + */ + $response->addHTML($operations->getHtmlForDatabaseComment($GLOBALS['db'])); + } + + $response->addHTML('
'); + $response->addHTML(CreateTable::getHtml($db)); + $response->addHTML('
'); + + /** + * rename database + */ + if ($GLOBALS['db'] != 'mysql') { + $response->addHTML($operations->getHtmlForRenameDatabase($GLOBALS['db'], $db_collation)); + } + + // Drop link if allowed + // Don't even try to drop information_schema. + // You won't be able to. Believe me. You won't. + // Don't allow to easily drop mysql database, RFE #1327514. + if (($GLOBALS['dbi']->isSuperuser() || $GLOBALS['cfg']['AllowUserDropDatabase']) + && ! $db_is_system_schema + && $GLOBALS['db'] != 'mysql' + ) { + $response->addHTML($operations->getHtmlForDropDatabaseLink($GLOBALS['db'])); + } + /** + * Copy database + */ + $response->addHTML($operations->getHtmlForCopyDatabase($GLOBALS['db'], $db_collation)); + + /** + * Change database charset + */ + $response->addHTML($operations->getHtmlForChangeDatabaseCharset($GLOBALS['db'], $db_collation)); + + if (! $cfgRelation['allworks'] + && $cfg['PmaNoRelation_DisableWarning'] == false + ) { + $message = Message::notice( + __( + 'The phpMyAdmin configuration storage has been deactivated. ' . + '%sFind out why%s.' + ) + ); + $message->addParamHtml(''); + $message->addParamHtml(''); + /* Show error if user has configured something, notice elsewhere */ + if (!empty($cfg['Servers'][$server]['pmadb'])) { + $message->isError(true); + } + } // end if +} // end if (!$is_information_schema) diff --git a/php/apps/phpmyadmin49/html/db_qbe.php b/php/apps/phpmyadmin49/html/db_qbe.php new file mode 100644 index 00000000..e2526876 --- /dev/null +++ b/php/apps/phpmyadmin49/html/db_qbe.php @@ -0,0 +1,178 @@ +getRelationsParam(); + +$savedSearchList = array(); +$savedSearch = null; +$currentSearchId = null; +if ($cfgRelation['savedsearcheswork']) { + $header = $response->getHeader(); + $scripts = $header->getScripts(); + $scripts->addFile('db_qbe.js'); + + //Get saved search list. + $savedSearch = new SavedSearches($GLOBALS); + $savedSearch->setUsername($GLOBALS['cfg']['Server']['user']) + ->setDbname($GLOBALS['db']); + + if (!empty($_POST['searchId'])) { + $savedSearch->setId($_POST['searchId']); + } + + //Action field is sent. + if (isset($_POST['action'])) { + $savedSearch->setSearchName($_POST['searchName']); + if ('create' === $_POST['action']) { + $saveResult = $savedSearch->setId(null) + ->setCriterias($_POST) + ->save(); + } elseif ('update' === $_POST['action']) { + $saveResult = $savedSearch->setCriterias($_POST) + ->save(); + } elseif ('delete' === $_POST['action']) { + $deleteResult = $savedSearch->delete(); + //After deletion, reset search. + $savedSearch = new SavedSearches($GLOBALS); + $savedSearch->setUsername($GLOBALS['cfg']['Server']['user']) + ->setDbname($GLOBALS['db']); + $_POST = array(); + } elseif ('load' === $_POST['action']) { + if (empty($_POST['searchId'])) { + //when not loading a search, reset the object. + $savedSearch = new SavedSearches($GLOBALS); + $savedSearch->setUsername($GLOBALS['cfg']['Server']['user']) + ->setDbname($GLOBALS['db']); + $_POST = array(); + } else { + $loadResult = $savedSearch->load(); + } + } + //Else, it's an "update query" + } + + $savedSearchList = $savedSearch->getList(); + $currentSearchId = $savedSearch->getId(); +} + +/** + * A query has been submitted -> (maybe) execute it + */ +$message_to_display = false; +if (isset($_POST['submit_sql']) && ! empty($sql_query)) { + if (! preg_match('@^SELECT@i', $sql_query)) { + $message_to_display = true; + } else { + $goto = 'db_sql.php'; + $sql = new Sql(); + $sql->executeQueryAndSendQueryResponse( + null, // analyzed_sql_results + false, // is_gotofile + $_POST['db'], // db + null, // table + false, // find_real_end + null, // sql_query_for_bookmark + null, // extra_data + null, // message_to_show + null, // message + null, // sql_data + $goto, // goto + $pmaThemeImage, // pmaThemeImage + null, // disp_query + null, // disp_message + null, // query_type + $sql_query, // sql_query + null, // selectedTables + null // complete_query + ); + } +} + +$sub_part = '_qbe'; +require 'libraries/db_common.inc.php'; +$url_query .= '&goto=db_qbe.php'; +$url_params['goto'] = 'db_qbe.php'; + +list( + $tables, + $num_tables, + $total_num_tables, + $sub_part, + $is_show_stats, + $db_is_system_schema, + $tooltip_truename, + $tooltip_aliasname, + $pos +) = Util::getDbInfo($db, isset($sub_part) ? $sub_part : ''); + +if ($message_to_display) { + Message::error( + __('You have to choose at least one column to display!') + ) + ->display(); +} +unset($message_to_display); + +// create new qbe search instance +$db_qbe = new Qbe($GLOBALS['db'], $savedSearchList, $savedSearch); + +$secondaryTabs = [ + 'multi' => [ + 'link' => 'db_multi_table_query.php', + 'text' => __('Multi-table query'), + ], + 'qbe' => [ + 'link' => 'db_qbe.php', + 'text' => __('Query by example'), + ], +]; +$response->addHTML( + Template::get('secondary_tabs')->render([ + 'url_params' => $url_params, + 'sub_tabs' => $secondaryTabs, + ]) +); + +$url = 'db_designer.php' . Url::getCommon( + array_merge( + $url_params, + array('query' => 1) + ) +); +$response->addHTML( + Message::notice( + sprintf( + __('Switch to %svisual builder%s'), + '', + '' + ) + ) +); + +/** + * Displays the Query by example form + */ +$response->addHTML($db_qbe->getSelectionForm()); diff --git a/php/apps/phpmyadmin49/html/db_routines.php b/php/apps/phpmyadmin49/html/db_routines.php new file mode 100644 index 00000000..5740c35a --- /dev/null +++ b/php/apps/phpmyadmin49/html/db_routines.php @@ -0,0 +1,23 @@ +getHeader(); +$scripts = $header->getScripts(); +$scripts->addFile('db_search.js'); +$scripts->addFile('sql.js'); +$scripts->addFile('makegrid.js'); + +require 'libraries/db_common.inc.php'; + +// If config variable $GLOBALS['cfg']['UseDbSearch'] is on false : exit. +if (! $GLOBALS['cfg']['UseDbSearch']) { + Util::mysqlDie( + __('Access denied!'), '', false, $err_url + ); +} // end if +$url_query .= '&goto=db_search.php'; +$url_params['goto'] = 'db_search.php'; + +// Create a database search instance +$db_search = new Search($GLOBALS['db']); + +// Display top links if we are not in an Ajax request +if (! $response->isAjax()) { + list( + $tables, + $num_tables, + $total_num_tables, + $sub_part, + $is_show_stats, + $db_is_system_schema, + $tooltip_truename, + $tooltip_aliasname, + $pos + ) = Util::getDbInfo($db, isset($sub_part) ? $sub_part : ''); +} + +// Main search form has been submitted, get results +if (isset($_POST['submit_search'])) { + $response->addHTML($db_search->getSearchResults()); +} + +// If we are in an Ajax request, we need to exit after displaying all the HTML +if ($response->isAjax() && empty($_REQUEST['ajax_page_request'])) { + exit; +} + +// Display the search form +$response->addHTML($db_search->getSelectionForm()); +$response->addHTML('
'); +$response->addHTML( + '
' +); +$response->addHTML('
'); +$response->addHTML($db_search->getResultDivs()); diff --git a/php/apps/phpmyadmin49/html/db_sql.php b/php/apps/phpmyadmin49/html/db_sql.php new file mode 100644 index 00000000..aa4d0b37 --- /dev/null +++ b/php/apps/phpmyadmin49/html/db_sql.php @@ -0,0 +1,46 @@ +getHeader(); +$scripts = $header->getScripts(); +$scripts->addFile('makegrid.js'); +$scripts->addFile('vendor/jquery/jquery.uitablefilter.js'); +$scripts->addFile('sql.js'); + +require 'libraries/db_common.inc.php'; + +// After a syntax error, we return to this script +// with the typed query in the textarea. +$goto = 'db_sql.php'; +$back = 'db_sql.php'; + +/** + * Query box, bookmark, insert data from textfile + */ +$response->addHTML( + SqlQueryForm::getHtml( + true, false, + isset($_POST['delimiter']) + ? htmlspecialchars($_POST['delimiter']) + : ';' + ) +); diff --git a/php/apps/phpmyadmin49/html/db_sql_autocomplete.php b/php/apps/phpmyadmin49/html/db_sql_autocomplete.php new file mode 100644 index 00000000..0fe4ae6f --- /dev/null +++ b/php/apps/phpmyadmin49/html/db_sql_autocomplete.php @@ -0,0 +1,27 @@ +getTables($db); + foreach ($tableNames as $tableName) { + $sql_autocomplete[$tableName] = $GLOBALS['dbi']->getColumns( + $db, $tableName + ); + } + } +} else { + $sql_autocomplete = true; +} +$response = Response::getInstance(); +$response->addJSON("tables", json_encode($sql_autocomplete)); diff --git a/php/apps/phpmyadmin49/html/db_sql_format.php b/php/apps/phpmyadmin49/html/db_sql_format.php new file mode 100644 index 00000000..dd3c2b86 --- /dev/null +++ b/php/apps/phpmyadmin49/html/db_sql_format.php @@ -0,0 +1,21 @@ +addJSON("sql", $query); diff --git a/php/apps/phpmyadmin49/html/db_structure.php b/php/apps/phpmyadmin49/html/db_structure.php new file mode 100644 index 00000000..09c4aee4 --- /dev/null +++ b/php/apps/phpmyadmin49/html/db_structure.php @@ -0,0 +1,38 @@ +factory( + 'PhpMyAdmin\Controllers\Database\DatabaseStructureController' +); +$container->alias( + 'DatabaseStructureController', + 'PhpMyAdmin\Controllers\Database\DatabaseStructureController' +); +$container->set('PhpMyAdmin\Response', Response::getInstance()); +$container->alias('response', 'PhpMyAdmin\Response'); + +/* Define dependencies for the concerned controller */ +$dependency_definitions = array( + 'db' => $db, +); + +/** @var DatabaseStructureController $controller */ +$controller = $container->get( + 'DatabaseStructureController', + $dependency_definitions +); +$controller->indexAction(); diff --git a/php/apps/phpmyadmin49/html/db_tracking.php b/php/apps/phpmyadmin49/html/db_tracking.php new file mode 100644 index 00000000..49e40480 --- /dev/null +++ b/php/apps/phpmyadmin49/html/db_tracking.php @@ -0,0 +1,154 @@ +getHeader(); +$scripts = $header->getScripts(); +$scripts->addFile('vendor/jquery/jquery.tablesorter.js'); +$scripts->addFile('db_tracking.js'); + +/** + * If we are not in an Ajax request, then do the common work and show the links etc. + */ +require 'libraries/db_common.inc.php'; +$url_query .= '&goto=tbl_tracking.php&back=db_tracking.php'; +$url_params['goto'] = 'tbl_tracking.php'; +$url_params['back'] = 'db_tracking.php'; + +// Get the database structure +$sub_part = '_structure'; + +list( + $tables, + $num_tables, + $total_num_tables, + $sub_part, + $is_show_stats, + $db_is_system_schema, + $tooltip_truename, + $tooltip_aliasname, + $pos +) = Util::getDbInfo($db, isset($sub_part) ? $sub_part : ''); + +if (isset($_POST['delete_tracking']) && isset($_POST['table'])) { + + Tracker::deleteTracking($GLOBALS['db'], $_POST['table']); + Message::success( + __('Tracking data deleted successfully.') + )->display(); + +} elseif (isset($_POST['submit_create_version'])) { + + Tracking::createTrackingForMultipleTables($_POST['selected']); + Message::success( + sprintf( + __( + 'Version %1$s was created for selected tables,' + . ' tracking is active for them.' + ), + htmlspecialchars($_POST['version']) + ) + )->display(); + +} elseif (isset($_POST['submit_mult'])) { + + if (! empty($_POST['selected_tbl'])) { + if ($_POST['submit_mult'] == 'delete_tracking') { + + foreach ($_POST['selected_tbl'] as $table) { + Tracker::deleteTracking($GLOBALS['db'], $table); + } + Message::success( + __('Tracking data deleted successfully.') + )->display(); + + } elseif ($_POST['submit_mult'] == 'track') { + + echo Tracking::getHtmlForDataDefinitionAndManipulationStatements( + 'db_tracking.php' . $url_query, + 0, + $GLOBALS['db'], + $_POST['selected_tbl'] + ); + exit; + } + } else { + Message::notice( + __('No tables selected.') + )->display(); + } +} + +// Get tracked data about the database +$data = Tracker::getTrackedData($GLOBALS['db'], '', '1'); + +// No tables present and no log exist +if ($num_tables == 0 && count($data['ddlog']) == 0) { + echo '

' , __('No tables found in database.') , '

' , "\n"; + + if (empty($db_is_system_schema)) { + echo CreateTable::getHtml($db); + } + exit; +} + +// --------------------------------------------------------------------------- +$relation = new Relation(); +$cfgRelation = $relation->getRelationsParam(); + +// Prepare statement to get HEAD version +$all_tables_query = ' SELECT table_name, MAX(version) as version FROM ' . + Util::backquote($cfgRelation['db']) . '.' . + Util::backquote($cfgRelation['tracking']) . + ' WHERE db_name = \'' . $GLOBALS['dbi']->escapeString($GLOBALS['db']) . + '\' ' . + ' GROUP BY table_name' . + ' ORDER BY table_name ASC'; + +$all_tables_result = $relation->queryAsControlUser($all_tables_query); + +// If a HEAD version exists +if (is_object($all_tables_result) + && $GLOBALS['dbi']->numRows($all_tables_result) > 0 +) { + echo Tracking::getHtmlForTrackedTables( + $GLOBALS['db'], $all_tables_result, $url_query, $pmaThemeImage, + $text_dir, $cfgRelation + ); +} + +$untracked_tables = Tracking::getUntrackedTables($GLOBALS['db']); + +// If untracked tables exist +if (count($untracked_tables) > 0) { + echo Tracking::getHtmlForUntrackedTables( + $GLOBALS['db'], $untracked_tables, $url_query, $pmaThemeImage, $text_dir + ); +} +// If available print out database log +if (count($data['ddlog']) > 0) { + $log = ''; + foreach ($data['ddlog'] as $entry) { + $log .= '# ' . $entry['date'] . ' ' . $entry['username'] . "\n" + . $entry['statement'] . "\n"; + } + echo Util::getMessage(__('Database Log'), $log); +} diff --git a/php/apps/phpmyadmin49/html/db_triggers.php b/php/apps/phpmyadmin49/html/db_triggers.php new file mode 100644 index 00000000..cdd0ebe8 --- /dev/null +++ b/php/apps/phpmyadmin49/html/db_triggers.php @@ -0,0 +1,18 @@ +`_ jQuery library. + +Examples +-------- + +Pie chart ++++++++++ + +Query results for a simple pie chart can be generated with: + +.. code-block:: mysql + + SELECT 'Food' AS 'expense', + 1250 AS 'amount' UNION + SELECT 'Accommodation', 500 UNION + SELECT 'Travel', 720 UNION + SELECT 'Misc', 220 + +And the result of this query is: + ++---------------+--------+ +| expense | amount | ++===============+========+ +| Food | 1250 | ++---------------+--------+ +| Accommodation | 500 | ++---------------+--------+ +| Travel | 720 | ++---------------+--------+ +| Misc | 220 | ++---------------+--------+ + +Choosing expense as the X-axis and amount in series: + +.. image:: images/pie_chart.png + +Bar and column chart +++++++++++++++++++++ + +Both bar charts and column chats support stacking. Upon selecting one of these types a checkbox is displayed to select stacking. + +Query results for a simple bar or column chart can be generated with: + +.. code-block:: mysql + + SELECT + 'ACADEMY DINOSAUR' AS 'title', + 0.99 AS 'rental_rate', + 20.99 AS 'replacement_cost' UNION + SELECT 'ACE GOLDFINGER', 4.99, 12.99 UNION + SELECT 'ADAPTATION HOLES', 2.99, 18.99 UNION + SELECT 'AFFAIR PREJUDICE', 2.99, 26.99 UNION + SELECT 'AFRICAN EGG', 2.99, 22.99 + +And the result of this query is: + ++------------------+--------------+-------------------+ +| title | rental_rate | replacement_cost | ++==================+==============+===================+ +| ACADEMY DINOSAUR | 0.99 | 20.99 | ++------------------+--------------+-------------------+ +| ACE GOLDFINGER | 4.99 | 12.99 | ++------------------+--------------+-------------------+ +| ADAPTATION HOLES | 2.99 | 18.99 | ++------------------+--------------+-------------------+ +| AFFAIR PREJUDICE | 2.99 | 26.99 | ++------------------+--------------+-------------------+ +| AFRICAN EGG | 2.99 | 22.99 | ++------------------+--------------+-------------------+ + +Choosing title as the X-axis and rental_rate and replacement_cost as series: + +.. image:: images/column_chart.png + +Scatter chart ++++++++++++++ + +Scatter charts are useful in identifying the movement of one or more variable(s) compared to another variable. + +Using the same data set from bar and column charts section and choosing replacement_cost as the X-axis and rental_rate in series: + +.. image:: images/scatter_chart.png + +Line, spline and timeline charts +++++++++++++++++++++++++++++++++ + +These charts can be used to illustrate trends in underlying data. Spline charts draw smooth lines while timeline charts draw X-axis taking the distances between the dates/time into consideration. + +Query results for a simple line, spline or timeline chart can be generated with: + +.. code-block:: mysql + + SELECT + DATE('2006-01-08') AS 'date', + 2056 AS 'revenue', + 1378 AS 'cost' UNION + SELECT DATE('2006-01-09'), 1898, 2301 UNION + SELECT DATE('2006-01-15'), 1560, 600 UNION + SELECT DATE('2006-01-17'), 3457, 1565 + +And the result of this query is: + ++------------+---------+------+ +| date | revenue | cost | ++============+=========+======+ +| 2016-01-08 | 2056 | 1378 | ++------------+---------+------+ +| 2006-01-09 | 1898 | 2301 | ++------------+---------+------+ +| 2006-01-15 | 1560 | 600 | ++------------+---------+------+ +| 2006-01-17 | 3457 | 1565 | ++------------+---------+------+ + +.. image:: images/line_chart.png +.. image:: images/spline_chart.png +.. image:: images/timeline_chart.png diff --git a/php/apps/phpmyadmin49/html/doc/html/_sources/config.txt b/php/apps/phpmyadmin49/html/doc/html/_sources/config.txt new file mode 100644 index 00000000..80e47e84 --- /dev/null +++ b/php/apps/phpmyadmin49/html/doc/html/_sources/config.txt @@ -0,0 +1,3589 @@ +.. index:: config.inc.php + +.. _config: + +Configuration +============= + +All configurable data is placed in :file:`config.inc.php` in phpMyAdmin's +toplevel directory. If this file does not exist, please refer to the +:ref:`setup` section to create one. This file only needs to contain the +parameters you want to change from their corresponding default value in +:file:`libraries/config.default.php` (this file is not inteded for changes). + +.. seealso:: + + :ref:`config-examples` for examples of configurations + +If a directive is missing from your file, you can just add another line with +the file. This file is for over-writing the defaults; if you wish to use the +default value there's no need to add a line here. + +The parameters which relate to design (like colors) are placed in +:file:`themes/themename/layout.inc.php`. You might also want to create +:file:`config.footer.inc.php` and :file:`config.header.inc.php` files to add +your site specific code to be included on start and end of each page. + +.. note:: + + Some distributions (eg. Debian or Ubuntu) store :file:`config.inc.php` in + ``/etc/phpmyadmin`` instead of within phpMyAdmin sources. + +.. warning:: + + :term:`Mac` users should note that if you are on a version before + :term:`Mac OS X`, PHP does not seem to + like :term:`Mac` end of lines character (``\r``). So + ensure you choose the option that allows to use the \*nix end of line + character (``\n``) in your text editor before saving a script you have + modified. + +Basic settings +-------------- + +.. config:option:: $cfg['PmaAbsoluteUri'] + + :type: string + :default: ``''`` + + .. versionchanged:: 4.6.5 + + This setting was not available in phpMyAdmin 4.6.0 - 4.6.4. + + Sets here the complete :term:`URL` (with full path) to your phpMyAdmin + installation's directory. E.g. + ``https://www.example.net/path_to_your_phpMyAdmin_directory/``. Note also + that the :term:`URL` on most of web servers are case sensitive (even on + Windows). Don’t forget the trailing slash at the end. + + Starting with version 2.3.0, it is advisable to try leaving this blank. In + most cases phpMyAdmin automatically detects the proper setting. Users of + port forwarding or complex reverse proxy setup might need to set this. + + A good test is to browse a table, edit a row and save it. There should be + an error message if phpMyAdmin is having trouble auto–detecting the correct + value. If you get an error that this must be set or if the autodetect code + fails to detect your path, please post a bug report on our bug tracker so + we can improve the code. + + .. seealso:: :ref:`faq1_40`, :ref:`faq2_5`, :ref:`faq4_7`, :ref:`faq5_16` + +.. config:option:: $cfg['PmaNoRelation_DisableWarning'] + + :type: boolean + :default: false + + Starting with version 2.3.0 phpMyAdmin offers a lot of features to + work with master / foreign – tables (see :config:option:`$cfg['Servers'][$i]['pmadb']`). + + If you tried to set this + up and it does not work for you, have a look on the :guilabel:`Structure` page + of one database where you would like to use it. You will find a link + that will analyze why those features have been disabled. + + If you do not want to use those features set this variable to ``true`` to + stop this message from appearing. + +.. config:option:: $cfg['AuthLog'] + + :type: string + :default: ``'auto'`` + + .. versionadded:: 4.8.0 + + This is supported since phpMyAdmin 4.8.0. + + Configure authentication logging destination. Failed (or all, depending on + :config:option:`$cfg['AuthLogSuccess']`) authentication attempts will be + logged according to this directive: + + ``auto`` + Let phpMyAdmin automatically choose between ``syslog`` and ``php``. + ``syslog`` + Log using syslog, using AUTH facility, on most systems this ends up + in :file:`/var/log/auth.log`. + ``php`` + Log into PHP error log. + ``sapi`` + Log into PHP SAPI logging. + ``/path/to/file`` + Any other value is treated as a filename and log entries are written there. + + .. note:: + + When logging to a file, make sure its permissions are correctly set + for a web server user, the setup should closely match instructions + described in :config:option:`$cfg['TempDir']`: + +.. config:option:: $cfg['AuthLogSuccess'] + + :type: boolean + :default: false + + .. versionadded:: 4.8.0 + + This is supported since phpMyAdmin 4.8.0. + + Whether to log successful authentication attempts into + :config:option:`$cfg['AuthLog']`. + +.. config:option:: $cfg['SuhosinDisableWarning'] + + :type: boolean + :default: false + + A warning is displayed on the main page if Suhosin is detected. + + You can set this parameter to ``true`` to stop this message from appearing. + +.. config:option:: $cfg['LoginCookieValidityDisableWarning'] + + :type: boolean + :default: false + + A warning is displayed on the main page if the PHP parameter + session.gc_maxlifetime is lower than cookie validity configured in phpMyAdmin. + + You can set this parameter to ``true`` to stop this message from appearing. + +.. config:option:: $cfg['ServerLibraryDifference_DisableWarning'] + + :type: boolean + :default: false + + .. deprecated:: 4.7.0 + + This setting was removed as the warning has been removed as well. + + A warning is displayed on the main page if there is a difference + between the MySQL library and server version. + + You can set this parameter to ``true`` to stop this message from appearing. + +.. config:option:: $cfg['ReservedWordDisableWarning'] + + :type: boolean + :default: false + + This warning is displayed on the Structure page of a table if one or more + column names match with words which are MySQL reserved. + + If you want to turn off this warning, you can set it to ``true`` and + warning will no longer be displayed. + +.. config:option:: $cfg['TranslationWarningThreshold'] + + :type: integer + :default: 80 + + Show warning about incomplete translations on certain threshold. + +.. config:option:: $cfg['SendErrorReports'] + + :type: string + :default: ``'ask'`` + + Sets the default behavior for JavaScript error reporting. + + Whenever an error is detected in the JavaScript execution, an error report + may be sent to the phpMyAdmin team if the user agrees. + + The default setting of ``'ask'`` will ask the user everytime there is a new + error report. However you can set this parameter to ``'always'`` to send error + reports without asking for confirmation or you can set it to ``'never'`` to + never send error reports. + + This directive is available both in the configuration file and in users + preferences. If the person in charge of a multi-user installation prefers + to disable this feature for all users, a value of ``'never'`` should be + set, and the :config:option:`$cfg['UserprefsDisallow']` directive should + contain ``'SendErrorReports'`` in one of its array values. + +.. config:option:: $cfg['ConsoleEnterExecutes'] + + :type: boolean + :default: false + + Setting this to ``true`` allows the user to execute queries by pressing Enter + instead of Ctrl+Enter. A new line can be inserted by pressing Shift + Enter. + + The behaviour of the console can be temporarily changed using console's + settings interface. + +.. config:option:: $cfg['AllowThirdPartyFraming'] + + :type: boolean + :default: false + + Setting this to ``true`` allows phpMyAdmin to be included inside a frame, + and is a potential security hole allowing cross-frame scripting attacks or + clickjacking. + +Server connection settings +-------------------------- + +.. config:option:: $cfg['Servers'] + + :type: array + :default: one server array with settings listed below + + Since version 1.4.2, phpMyAdmin supports the administration of multiple + MySQL servers. Therefore, a :config:option:`$cfg['Servers']`-array has been + added which contains the login information for the different servers. The + first :config:option:`$cfg['Servers'][$i]['host']` contains the hostname of + the first server, the second :config:option:`$cfg['Servers'][$i]['host']` + the hostname of the second server, etc. In + :file:`libraries/config.default.php`, there is only one section for server + definition, however you can put as many as you need in + :file:`config.inc.php`, copy that block or needed parts (you don't have to + define all settings, just those you need to change). + + .. note:: + + The :config:option:`$cfg['Servers']` array starts with + $cfg['Servers'][1]. Do not use $cfg['Servers'][0]. If you want more + than one server, just copy following section (including $i + incrementation) serveral times. There is no need to define full server + array, just define values you need to change. + +.. config:option:: $cfg['Servers'][$i]['host'] + + :type: string + :default: ``'localhost'`` + + The hostname or :term:`IP` address of your $i-th MySQL-server. E.g. + ``localhost``. + + Possible values are: + + * hostname, e.g., ``'localhost'`` or ``'mydb.example.org'`` + * IP address, e.g., ``'127.0.0.1'`` or ``'192.168.10.1'`` + * IPv6 address, e.g. ``2001:cdba:0000:0000:0000:0000:3257:9652`` + * dot - ``'.'``, i.e., use named pipes on windows systems + * empty - ``''``, disables this server + + .. note:: + + The hostname ``localhost`` is handled specially by MySQL and it uses + the socket based connection protocol. To use TCP/IP networking, use an + IP address or hostname such as ``127.0.0.1`` or ``db.example.com``. You + can configure the path to the socket with + :config:option:`$cfg['Servers'][$i]['socket']`. + + .. seealso:: + + :config:option:`$cfg['Servers'][$i]['port']`, + + +.. config:option:: $cfg['Servers'][$i]['port'] + + :type: string + :default: ``''`` + + The port-number of your $i-th MySQL-server. Default is 3306 (leave + blank). + + .. note:: + + If you use ``localhost`` as the hostname, MySQL ignores this port number + and connects with the socket, so if you want to connect to a port + different from the default port, use ``127.0.0.1`` or the real hostname + in :config:option:`$cfg['Servers'][$i]['host']`. + + .. seealso:: + + :config:option:`$cfg['Servers'][$i]['host']`, + + +.. config:option:: $cfg['Servers'][$i]['socket'] + + :type: string + :default: ``''`` + + The path to the socket to use. Leave blank for default. To determine + the correct socket, check your MySQL configuration or, using the + :command:`mysql` command–line client, issue the ``status`` command. Among the + resulting information displayed will be the socket used. + + .. note:: + + This takes effect only if :config:option:`$cfg['Servers'][$i]['host']` is set + to ``localhost``. + + .. seealso:: + + :config:option:`$cfg['Servers'][$i]['host']`, + + +.. config:option:: $cfg['Servers'][$i]['ssl'] + + :type: boolean + :default: false + + Whether to enable SSL for the connection between phpMyAdmin and the MySQL + server to secure the connection. + + When using the ``'mysql'`` extension, + none of the remaining ``'ssl...'`` configuration options apply. + + We strongly recommend the ``'mysqli'`` extension when using this option. + + .. seealso:: + + :ref:`ssl`, + :ref:`example-google-ssl`, + :config:option:`$cfg['Servers'][$i]['ssl_key']`, + :config:option:`$cfg['Servers'][$i]['ssl_cert']`, + :config:option:`$cfg['Servers'][$i]['ssl_ca']`, + :config:option:`$cfg['Servers'][$i]['ssl_ca_path']`, + :config:option:`$cfg['Servers'][$i]['ssl_ciphers']`, + :config:option:`$cfg['Servers'][$i]['ssl_verify']` + +.. config:option:: $cfg['Servers'][$i]['ssl_key'] + + :type: string + :default: NULL + + Path to the client key file when using SSL for connecting to the MySQL + server. This is used to authenticate the client to the server. + + For example: + + .. code-block:: php + + $cfg['Servers'][$i]['ssl_key'] = '/etc/mysql/server-key.pem'; + + .. seealso:: + + :ref:`ssl`, + :ref:`example-google-ssl`, + :config:option:`$cfg['Servers'][$i]['ssl']`, + :config:option:`$cfg['Servers'][$i]['ssl_cert']`, + :config:option:`$cfg['Servers'][$i]['ssl_ca']`, + :config:option:`$cfg['Servers'][$i]['ssl_ca_path']`, + :config:option:`$cfg['Servers'][$i]['ssl_ciphers']`, + :config:option:`$cfg['Servers'][$i]['ssl_verify']` + +.. config:option:: $cfg['Servers'][$i]['ssl_cert'] + + :type: string + :default: NULL + + Path to the client certificate file when using SSL for connecting to the + MySQL server. This is used to authenticate the client to the server. + + .. seealso:: + + :ref:`ssl`, + :ref:`example-google-ssl`, + :config:option:`$cfg['Servers'][$i]['ssl']`, + :config:option:`$cfg['Servers'][$i]['ssl_key']`, + :config:option:`$cfg['Servers'][$i]['ssl_ca']`, + :config:option:`$cfg['Servers'][$i]['ssl_ca_path']`, + :config:option:`$cfg['Servers'][$i]['ssl_ciphers']`, + :config:option:`$cfg['Servers'][$i]['ssl_verify']` + +.. config:option:: $cfg['Servers'][$i]['ssl_ca'] + + :type: string + :default: NULL + + Path to the CA file when using SSL for connecting to the MySQL server. + + .. seealso:: + + :ref:`ssl`, + :ref:`example-google-ssl`, + :config:option:`$cfg['Servers'][$i]['ssl']`, + :config:option:`$cfg['Servers'][$i]['ssl_key']`, + :config:option:`$cfg['Servers'][$i]['ssl_cert']`, + :config:option:`$cfg['Servers'][$i]['ssl_ca_path']`, + :config:option:`$cfg['Servers'][$i]['ssl_ciphers']`, + :config:option:`$cfg['Servers'][$i]['ssl_verify']` + +.. config:option:: $cfg['Servers'][$i]['ssl_ca_path'] + + :type: string + :default: NULL + + Directory containing trusted SSL CA certificates in PEM format. + + .. seealso:: + + :ref:`ssl`, + :ref:`example-google-ssl`, + :config:option:`$cfg['Servers'][$i]['ssl']`, + :config:option:`$cfg['Servers'][$i]['ssl_key']`, + :config:option:`$cfg['Servers'][$i]['ssl_cert']`, + :config:option:`$cfg['Servers'][$i]['ssl_ca']`, + :config:option:`$cfg['Servers'][$i]['ssl_ciphers']`, + :config:option:`$cfg['Servers'][$i]['ssl_verify']` + +.. config:option:: $cfg['Servers'][$i]['ssl_ciphers'] + + :type: string + :default: NULL + + List of allowable ciphers for SSL connections to the MySQL server. + + .. seealso:: + + :ref:`ssl`, + :ref:`example-google-ssl`, + :config:option:`$cfg['Servers'][$i]['ssl']`, + :config:option:`$cfg['Servers'][$i]['ssl_key']`, + :config:option:`$cfg['Servers'][$i]['ssl_cert']`, + :config:option:`$cfg['Servers'][$i]['ssl_ca']`, + :config:option:`$cfg['Servers'][$i]['ssl_ca_path']`, + :config:option:`$cfg['Servers'][$i]['ssl_verify']` + +.. config:option:: $cfg['Servers'][$i]['ssl_verify'] + + :type: boolean + :default: true + + .. versionadded:: 4.6.0 + + This is supported since phpMyAdmin 4.6.0. + + If your PHP install uses the MySQL Native Driver (mysqlnd), your + MySQL server is 5.6 or later, and your SSL certificate is self-signed, + there is a chance your SSL connection will fail due to validation. + Setting this to ``false`` will disable the validation check. + + Since PHP 5.6.0 it also verifies whether server name matches CN of its + certificate. There is currently no way to disable just this check without + disabling complete SSL verification. + + .. warning:: + + Disabling the certificate verification defeats purpose of using SSL. + This will make the connection vulnerable to man in the middle attacks. + + .. note:: + + This flag only works with PHP 5.6.16 or later. + + .. seealso:: + + :ref:`ssl`, + :ref:`example-google-ssl`, + :config:option:`$cfg['Servers'][$i]['ssl']`, + :config:option:`$cfg['Servers'][$i]['ssl_key']`, + :config:option:`$cfg['Servers'][$i]['ssl_cert']`, + :config:option:`$cfg['Servers'][$i]['ssl_ca']`, + :config:option:`$cfg['Servers'][$i]['ssl_ca_path']`, + :config:option:`$cfg['Servers'][$i]['ssl_ciphers']`, + :config:option:`$cfg['Servers'][$i]['ssl_verify']` + +.. config:option:: $cfg['Servers'][$i]['connect_type'] + + :type: string + :default: ``'tcp'`` + + .. deprecated:: 4.7.0 + + This setting is no longer used as of 4.7.0, since MySQL decides the + connection type based on host, so it could lead to unexpected results. + Please set :config:option:`$cfg['Servers'][$i]['host']` accordingly + instead. + + What type connection to use with the MySQL server. Your options are + ``'socket'`` and ``'tcp'``. It defaults to tcp as that is nearly guaranteed + to be available on all MySQL servers, while sockets are not supported on + some platforms. To use the socket mode, your MySQL server must be on the + same machine as the Web server. + +.. config:option:: $cfg['Servers'][$i]['compress'] + + :type: boolean + :default: false + + Whether to use a compressed protocol for the MySQL server connection + or not (experimental). + +.. _controlhost: +.. config:option:: $cfg['Servers'][$i]['controlhost'] + + :type: string + :default: ``''`` + + Permits to use an alternate host to hold the configuration storage + data. + + .. seealso:: + + :config:option:`$cfg['Servers'][$i]['control_*']` + +.. _controlport: +.. config:option:: $cfg['Servers'][$i]['controlport'] + + :type: string + :default: ``''`` + + Permits to use an alternate port to connect to the host that + holds the configuration storage. + + .. seealso:: + + :config:option:`$cfg['Servers'][$i]['control_*']` + +.. _controluser: +.. config:option:: $cfg['Servers'][$i]['controluser'] + + :type: string + :default: ``''`` + +.. config:option:: $cfg['Servers'][$i]['controlpass'] + + :type: string + :default: ``''`` + + This special account is used to access :ref:`linked-tables`. + You don't need it in single user case, but if phpMyAdmin is shared it + is recommended to give access to :ref:`linked-tables` only to this user + and configure phpMyAdmin to use it. All users will then be able to use + the features without need to have direct access to :ref:`linked-tables`. + + .. versionchanged:: 2.2.5 + those were called ``stduser`` and ``stdpass`` + + .. seealso:: + + :ref:`setup`, + :ref:`authentication_modes`, + :ref:`linked-tables`, + :config:option:`$cfg['Servers'][$i]['pmadb']`, + :config:option:`$cfg['Servers'][$i]['controlhost']`, + :config:option:`$cfg['Servers'][$i]['controlport']`, + :config:option:`$cfg['Servers'][$i]['control_*']` + +.. config:option:: $cfg['Servers'][$i]['control_*'] + + :type: mixed + + .. versionadded:: 4.7.0 + + You can change any MySQL connection setting for control link (used to + access :ref:`linked-tables`) using configuration prefixed with ``control_``. + + This can be used to change any aspect of the control connection, which by + default uses same parameters as the user one. + + For example you can configure SSL for the control connection: + + .. code-block:: php + + // Enable SSL + $cfg['Servers'][$i]['control_ssl'] = true; + // Client secret key + $cfg['Servers'][$i]['control_ssl_key'] = '../client-key.pem'; + // Client certificate + $cfg['Servers'][$i]['control_ssl_cert'] = '../client-cert.pem'; + // Server certification authority + $cfg['Servers'][$i]['control_ssl_ca'] = '../server-ca.pem'; + + .. seealso:: + + :config:option:`$cfg['Servers'][$i]['ssl']`, + :config:option:`$cfg['Servers'][$i]['ssl_key']`, + :config:option:`$cfg['Servers'][$i]['ssl_cert']`, + :config:option:`$cfg['Servers'][$i]['ssl_ca']`, + :config:option:`$cfg['Servers'][$i]['ssl_ca_path']`, + :config:option:`$cfg['Servers'][$i]['ssl_ciphers']`, + :config:option:`$cfg['Servers'][$i]['ssl_verify']` + +.. config:option:: $cfg['Servers'][$i]['auth_type'] + + :type: string + :default: ``'cookie'`` + + Whether config or cookie or :term:`HTTP` or signon authentication should be + used for this server. + + * 'config' authentication (``$auth_type = 'config'``) is the plain old + way: username and password are stored in :file:`config.inc.php`. + * 'cookie' authentication mode (``$auth_type = 'cookie'``) allows you to + log in as any valid MySQL user with the help of cookies. + * 'http' authentication allows you to log in as any + valid MySQL user via HTTP-Auth. + * 'signon' authentication mode (``$auth_type = 'signon'``) allows you to + log in from prepared PHP session data or using supplied PHP script. + + .. seealso:: :ref:`authentication_modes` + +.. _servers_auth_http_realm: +.. config:option:: $cfg['Servers'][$i]['auth_http_realm'] + + :type: string + :default: ``''`` + + When using auth\_type = ``http``, this field allows to define a custom + :term:`HTTP` Basic Auth Realm which will be displayed to the user. If not + explicitly specified in your configuration, a string combined of + "phpMyAdmin " and either :config:option:`$cfg['Servers'][$i]['verbose']` or + :config:option:`$cfg['Servers'][$i]['host']` will be used. + +.. _servers_user: +.. config:option:: $cfg['Servers'][$i]['user'] + + :type: string + :default: ``'root'`` + +.. config:option:: $cfg['Servers'][$i]['password'] + + :type: string + :default: ``''`` + + When using :config:option:`$cfg['Servers'][$i]['auth_type']` set to + 'config', this is the user/password-pair which phpMyAdmin will use to + connect to the MySQL server. This user/password pair is not needed when + :term:`HTTP` or cookie authentication is used + and should be empty. + +.. _servers_nopassword: +.. config:option:: $cfg['Servers'][$i]['nopassword'] + + :type: boolean + :default: false + + .. deprecated:: 4.7.0 + + This setting was removed as it can produce unexpected results. + + Allow attempt to log in without password when a login with password + fails. This can be used together with http authentication, when + authentication is done some other way and phpMyAdmin gets user name + from auth and uses empty password for connecting to MySQL. Password + login is still tried first, but as fallback, no password method is + tried. + +.. _servers_only_db: +.. config:option:: $cfg['Servers'][$i]['only_db'] + + :type: string or array + :default: ``''`` + + If set to a (an array of) database name(s), only this (these) + database(s) will be shown to the user. Since phpMyAdmin 2.2.1, + this/these database(s) name(s) may contain MySQL wildcards characters + ("\_" and "%"): if you want to use literal instances of these + characters, escape them (I.E. use ``'my\_db'`` and not ``'my_db'``). + + This setting is an efficient way to lower the server load since the + latter does not need to send MySQL requests to build the available + database list. But **it does not replace the privileges rules of the + MySQL database server**. If set, it just means only these databases + will be displayed but **not that all other databases can't be used.** + + An example of using more that one database: + + .. code-block:: php + + $cfg['Servers'][$i]['only_db'] = array('db1', 'db2'); + + .. versionchanged:: 4.0.0 + Previous versions permitted to specify the display order of + the database names via this directive. + +.. config:option:: $cfg['Servers'][$i]['hide_db'] + + :type: string + :default: ``''`` + + Regular expression for hiding some databases from unprivileged users. + This only hides them from listing, but a user is still able to access + them (using, for example, the SQL query area). To limit access, use + the MySQL privilege system. For example, to hide all databases + starting with the letter "a", use + + .. code-block:: php + + $cfg['Servers'][$i]['hide_db'] = '^a'; + + and to hide both "db1" and "db2" use + + .. code-block:: php + + $cfg['Servers'][$i]['hide_db'] = '^(db1|db2)$'; + + More information on regular expressions can be found in the `PCRE + pattern syntax + `_ portion + of the PHP reference manual. + +.. config:option:: $cfg['Servers'][$i]['verbose'] + + :type: string + :default: ``''`` + + Only useful when using phpMyAdmin with multiple server entries. If + set, this string will be displayed instead of the hostname in the + pull-down menu on the main page. This can be useful if you want to + show only certain databases on your system, for example. For HTTP + auth, all non-US-ASCII characters will be stripped. + +.. config:option:: $cfg['Servers'][$i]['extension'] + + :type: string + :default: ``'mysqli'`` + + The PHP MySQL extension to use (``mysql`` or ``mysqli``). + + It is recommended to use ``mysqli`` in all installations. + +.. config:option:: $cfg['Servers'][$i]['pmadb'] + + :type: string + :default: ``''`` + + The name of the database containing the phpMyAdmin configuration + storage. + + See the :ref:`linked-tables` section in this document to see the benefits of + this feature, and for a quick way of creating this database and the needed + tables. + + If you are the only user of this phpMyAdmin installation, you can use your + current database to store those special tables; in this case, just put your + current database name in :config:option:`$cfg['Servers'][$i]['pmadb']`. For a + multi-user installation, set this parameter to the name of your central + database containing the phpMyAdmin configuration storage. + +.. _bookmark: +.. config:option:: $cfg['Servers'][$i]['bookmarktable'] + + :type: string or false + :default: ``''`` + + Since release 2.2.0 phpMyAdmin allows users to bookmark queries. This + can be useful for queries you often run. To allow the usage of this + functionality: + + * set up :config:option:`$cfg['Servers'][$i]['pmadb']` and the phpMyAdmin configuration storage + * enter the table name in :config:option:`$cfg['Servers'][$i]['bookmarktable']` + + This feature can be disabled by setting the configuration to ``false``. + +.. _relation: +.. config:option:: $cfg['Servers'][$i]['relation'] + + :type: string or false + :default: ``''`` + + Since release 2.2.4 you can describe, in a special 'relation' table, + which column is a key in another table (a foreign key). phpMyAdmin + currently uses this to: + + * make clickable, when you browse the master table, the data values that + point to the foreign table; + * display in an optional tool-tip the "display column" when browsing the + master table, if you move the mouse to a column containing a foreign + key (use also the 'table\_info' table); (see :ref:`faqdisplay`) + * in edit/insert mode, display a drop-down list of possible foreign keys + (key value and "display column" are shown) (see :ref:`faq6_21`) + * display links on the table properties page, to check referential + integrity (display missing foreign keys) for each described key; + * in query-by-example, create automatic joins (see :ref:`faq6_6`) + * enable you to get a :term:`PDF` schema of + your database (also uses the table\_coords table). + + The keys can be numeric or character. + + To allow the usage of this functionality: + + * set up :config:option:`$cfg['Servers'][$i]['pmadb']` and the phpMyAdmin configuration storage + * put the relation table name in :config:option:`$cfg['Servers'][$i]['relation']` + * now as normal user open phpMyAdmin and for each one of your tables + where you want to use this feature, click :guilabel:`Structure/Relation view/` + and choose foreign columns. + + This feature can be disabled by setting the configuration to ``false``. + + .. note:: + + In the current version, ``master_db`` must be the same as ``foreign_db``. + Those columns have been put in future development of the cross-db + relations. + +.. _table_info: +.. config:option:: $cfg['Servers'][$i]['table_info'] + + :type: string or false + :default: ``''`` + + Since release 2.3.0 you can describe, in a special 'table\_info' + table, which column is to be displayed as a tool-tip when moving the + cursor over the corresponding key. This configuration variable will + hold the name of this special table. To allow the usage of this + functionality: + + * set up :config:option:`$cfg['Servers'][$i]['pmadb']` and the phpMyAdmin configuration storage + * put the table name in :config:option:`$cfg['Servers'][$i]['table\_info']` (e.g. + ``pma__table_info``) + * then for each table where you want to use this feature, click + "Structure/Relation view/Choose column to display" to choose the + column. + + This feature can be disabled by setting the configuration to ``false``. + + .. seealso:: :ref:`faqdisplay` + +.. _table_coords: +.. config:option:: $cfg['Servers'][$i]['table_coords'] + + :type: string or false + :default: ``''`` + + The designer feature can save your page layout; by pressing the "Save page" or "Save page as" + button in the expanding designer menu, you can customize the layout and have it loaded the next + time you use the designer. That layout is stored in this table. Furthermore, this table is also + required for using the PDF relation export feature, see + :config:option:`$cfg['Servers'][$i]['pdf\_pages']` for additional details. + +.. config:option:: $cfg['Servers'][$i]['pdf_pages'] + + :type: string or false + :default: ``''`` + + Since release 2.3.0 you can have phpMyAdmin create :term:`PDF` pages + showing the relations between your tables. Further, the designer interface + permits visually managing the relations. To do this it needs two tables + "pdf\_pages" (storing information about the available :term:`PDF` pages) + and "table\_coords" (storing coordinates where each table will be placed on + a :term:`PDF` schema output). You must be using the "relation" feature. + + To allow the usage of this functionality: + + * set up :config:option:`$cfg['Servers'][$i]['pmadb']` and the phpMyAdmin configuration storage + * put the correct table names in + :config:option:`$cfg['Servers'][$i]['table\_coords']` and + :config:option:`$cfg['Servers'][$i]['pdf\_pages']` + + This feature can be disabled by setting either of the configurations to ``false``. + + .. seealso:: :ref:`faqpdf`. + +.. _col_com: +.. config:option:: $cfg['Servers'][$i]['column_info'] + + :type: string or false + :default: ``''`` + + This part requires a content update! Since release 2.3.0 you can + store comments to describe each column for each table. These will then + be shown on the "printview". + + Starting with release 2.5.0, comments are consequently used on the table + property pages and table browse view, showing up as tool-tips above the + column name (properties page) or embedded within the header of table in + browse view. They can also be shown in a table dump. Please see the + relevant configuration directives later on. + + Also new in release 2.5.0 is a MIME- transformation system which is also + based on the following table structure. See :ref:`transformations` for + further information. To use the MIME- transformation system, your + column\_info table has to have the three new columns 'mimetype', + 'transformation', 'transformation\_options'. + + Starting with release 4.3.0, a new input-oriented transformation system + has been introduced. Also, backward compatibility code used in the old + transformations system was removed. As a result, an update to column\_info + table is necessary for previous transformations and the new input-oriented + transformation system to work. phpMyAdmin will upgrade it automatically + for you by analyzing your current column\_info table structure. + However, if something goes wrong with the auto-upgrade then you can + use the SQL script found in ``./sql/upgrade_column_info_4_3_0+.sql`` + to upgrade it manually. + + To allow the usage of this functionality: + + * set up :config:option:`$cfg['Servers'][$i]['pmadb']` and the phpMyAdmin configuration storage + * put the table name in :config:option:`$cfg['Servers'][$i]['column\_info']` (e.g. + ``pma__column_info``) + * to update your PRE-2.5.0 Column\_comments table use this: and + remember that the Variable in :file:`config.inc.php` has been renamed from + :samp:`$cfg['Servers'][$i]['column\_comments']` to + :config:option:`$cfg['Servers'][$i]['column\_info']` + + .. code-block:: mysql + + ALTER TABLE `pma__column_comments` + ADD `mimetype` VARCHAR( 255 ) NOT NULL, + ADD `transformation` VARCHAR( 255 ) NOT NULL, + ADD `transformation_options` VARCHAR( 255 ) NOT NULL; + * to update your PRE-4.3.0 Column\_info table manually use this + ``./sql/upgrade_column_info_4_3_0+.sql`` SQL script. + + This feature can be disabled by setting the configuration to ``false``. + + .. note:: + + For auto-upgrade functionality to work, your + :config:option:`$cfg['Servers'][$i]['controluser']` must have ALTER privilege on + ``phpmyadmin`` database. See the `MySQL documentation for GRANT + `_ on how to + ``GRANT`` privileges to a user. + +.. _history: +.. config:option:: $cfg['Servers'][$i]['history'] + + :type: string or false + :default: ``''`` + + Since release 2.5.0 you can store your :term:`SQL` history, which means all + queries you entered manually into the phpMyAdmin interface. If you don't + want to use a table-based history, you can use the JavaScript-based + history. + + Using that, all your history items are deleted when closing the window. + Using :config:option:`$cfg['QueryHistoryMax']` you can specify an amount of + history items you want to have on hold. On every login, this list gets cut + to the maximum amount. + + The query history is only available if JavaScript is enabled in + your browser. + + To allow the usage of this functionality: + + * set up :config:option:`$cfg['Servers'][$i]['pmadb']` and the phpMyAdmin configuration storage + * put the table name in :config:option:`$cfg['Servers'][$i]['history']` (e.g. + ``pma__history``) + + This feature can be disabled by setting the configuration to ``false``. + +.. _recent: +.. config:option:: $cfg['Servers'][$i]['recent'] + + :type: string or false + :default: ``''`` + + Since release 3.5.0 you can show recently used tables in the + navigation panel. It helps you to jump across table directly, without + the need to select the database, and then select the table. Using + :config:option:`$cfg['NumRecentTables']` you can configure the maximum number + of recent tables shown. When you select a table from the list, it will jump to + the page specified in :config:option:`$cfg['NavigationTreeDefaultTabTable']`. + + Without configuring the storage, you can still access the recently used tables, + but it will disappear after you logout. + + To allow the usage of this functionality persistently: + + * set up :config:option:`$cfg['Servers'][$i]['pmadb']` and the phpMyAdmin configuration storage + * put the table name in :config:option:`$cfg['Servers'][$i]['recent']` (e.g. + ``pma__recent``) + + This feature can be disabled by setting the configuration to ``false``. + +.. _favorite: +.. config:option:: $cfg['Servers'][$i]['favorite'] + + :type: string or false + :default: ``''`` + + Since release 4.2.0 you can show a list of selected tables in the + navigation panel. It helps you to jump to the table directly, without + the need to select the database, and then select the table. When you + select a table from the list, it will jump to the page specified in + :config:option:`$cfg['NavigationTreeDefaultTabTable']`. + + You can add tables to this list or remove tables from it in database + structure page by clicking on the star icons next to table names. Using + :config:option:`$cfg['NumFavoriteTables']` you can configure the maximum + number of favorite tables shown. + + Without configuring the storage, you can still access the favorite tables, + but it will disappear after you logout. + + To allow the usage of this functionality persistently: + + * set up :config:option:`$cfg['Servers'][$i]['pmadb']` and the phpMyAdmin configuration storage + * put the table name in :config:option:`$cfg['Servers'][$i]['favorite']` (e.g. + ``pma__favorite``) + + This feature can be disabled by setting the configuration to ``false``. + +.. _table_uiprefs: +.. config:option:: $cfg['Servers'][$i]['table_uiprefs'] + + :type: string or false + :default: ``''`` + + Since release 3.5.0 phpMyAdmin can be configured to remember several + things (sorted column :config:option:`$cfg['RememberSorting']`, column order, + and column visibility from a database table) for browsing tables. Without + configuring the storage, these features still can be used, but the values will + disappear after you logout. + + To allow the usage of these functionality persistently: + + * set up :config:option:`$cfg['Servers'][$i]['pmadb']` and the phpMyAdmin configuration storage + * put the table name in :config:option:`$cfg['Servers'][$i]['table\_uiprefs']` (e.g. + ``pma__table_uiprefs``) + + This feature can be disabled by setting the configuration to ``false``. + +.. config:option:: $cfg['Servers'][$i]['users'] + + :type: string or false + :default: ``''`` + +.. config:option:: $cfg['Servers'][$i]['usergroups'] + + :type: string or false + :default: ``''`` + + Since release 4.1.0 you can create different user groups with menu items + attached to them. Users can be assigned to these groups and the logged in + user would only see menu items configured to the usergroup he is assigned to. + To do this it needs two tables "usergroups" (storing allowed menu items for each + user group) and "users" (storing users and their assignments to user groups). + + To allow the usage of this functionality: + + * set up :config:option:`$cfg['Servers'][$i]['pmadb']` and the phpMyAdmin configuration storage + * put the correct table names in + :config:option:`$cfg['Servers'][$i]['users']` (e.g. ``pma__users``) and + :config:option:`$cfg['Servers'][$i]['usergroups']` (e.g. ``pma__usergroups``) + + This feature can be disabled by setting either of the configurations to ``false``. + + .. seealso:: :ref:`configurablemenus` + +.. _navigationhiding: +.. config:option:: $cfg['Servers'][$i]['navigationhiding'] + + :type: string or false + :default: ``''`` + + Since release 4.1.0 you can hide/show items in the navigation tree. + + To allow the usage of this functionality: + + * set up :config:option:`$cfg['Servers'][$i]['pmadb']` and the phpMyAdmin configuration storage + * put the table name in :config:option:`$cfg['Servers'][$i]['navigationhiding']` (e.g. + ``pma__navigationhiding``) + + This feature can be disabled by setting the configuration to ``false``. + +.. _central_columns: +.. config:option:: $cfg['Servers'][$i]['central_columns'] + + :type: string or false + :default: ``''`` + + Since release 4.3.0 you can have a central list of columns per database. + You can add/remove columns to the list as per your requirement. These columns + in the central list will be available to use while you create a new column for + a table or create a table itself. You can select a column from central list + while creating a new column, it will save you from writing the same column definition + over again or from writing different names for similar column. + + To allow the usage of this functionality: + + * set up :config:option:`$cfg['Servers'][$i]['pmadb']` and the phpMyAdmin configuration storage + * put the table name in :config:option:`$cfg['Servers'][$i]['central_columns']` (e.g. + ``pma__central_columns``) + + This feature can be disabled by setting the configuration to ``false``. + +.. _designer_settings: +.. config:option:: $cfg['Servers'][$i]['designer_settings'] + + :type: string or false + :default: ``''`` + + Since release 4.5.0 your designer settings can be remembered. + Your choice regarding 'Angular/Direct Links', 'Snap to Grid', 'Toggle Relation Lines', + 'Small/Big All', 'Move Menu' and 'Pin Text' can be remembered persistently. + + To allow the usage of this functionality: + + * set up :config:option:`$cfg['Servers'][$i]['pmadb']` and the phpMyAdmin configuration storage + * put the table name in :config:option:`$cfg['Servers'][$i]['designer_settings']` (e.g. + ``pma__designer_settings``) + + This feature can be disabled by setting the configuration to ``false``. + +.. _savedsearches: +.. config:option:: $cfg['Servers'][$i]['savedsearches'] + + :type: string or false + :default: ``''`` + + Since release 4.2.0 you can save and load query-by-example searches from the Database > Query panel. + + To allow the usage of this functionality: + + * set up :config:option:`$cfg['Servers'][$i]['pmadb']` and the phpMyAdmin configuration storage + * put the table name in :config:option:`$cfg['Servers'][$i]['savedsearches']` (e.g. + ``pma__savedsearches``) + + This feature can be disabled by setting the configuration to ``false``. + +.. _export_templates: +.. config:option:: $cfg['Servers'][$i]['export_templates'] + + :type: string or false + :default: ``''`` + + Since release 4.5.0 you can save and load export templates. + + To allow the usage of this functionality: + + * set up :config:option:`$cfg['Servers'][$i]['pmadb']` and the phpMyAdmin configuration storage + * put the table name in :config:option:`$cfg['Servers'][$i]['export_templates']` (e.g. + ``pma__export_templates``) + + This feature can be disabled by setting the configuration to ``false``. + +.. _tracking: +.. config:option:: $cfg['Servers'][$i]['tracking'] + + :type: string or false + :default: ``''`` + + Since release 3.3.x a tracking mechanism is available. It helps you to + track every :term:`SQL` command which is + executed by phpMyAdmin. The mechanism supports logging of data + manipulation and data definition statements. After enabling it you can + create versions of tables. + + The creation of a version has two effects: + + * phpMyAdmin saves a snapshot of the table, including structure and + indexes. + * phpMyAdmin logs all commands which change the structure and/or data of + the table and links these commands with the version number. + + Of course you can view the tracked changes. On the :guilabel:`Tracking` + page a complete report is available for every version. For the report you + can use filters, for example you can get a list of statements within a date + range. When you want to filter usernames you can enter \* for all names or + you enter a list of names separated by ','. In addition you can export the + (filtered) report to a file or to a temporary database. + + To allow the usage of this functionality: + + * set up :config:option:`$cfg['Servers'][$i]['pmadb']` and the phpMyAdmin configuration storage + * put the table name in :config:option:`$cfg['Servers'][$i]['tracking']` (e.g. + ``pma__tracking``) + + This feature can be disabled by setting the configuration to ``false``. + +.. _tracking2: +.. config:option:: $cfg['Servers'][$i]['tracking_version_auto_create'] + + :type: boolean + :default: false + + Whether the tracking mechanism creates versions for tables and views + automatically. + + If this is set to true and you create a table or view with + + * CREATE TABLE ... + * CREATE VIEW ... + + and no version exists for it, the mechanism will create a version for + you automatically. + +.. _tracking3: +.. config:option:: $cfg['Servers'][$i]['tracking_default_statements'] + + :type: string + :default: ``'CREATE TABLE,ALTER TABLE,DROP TABLE,RENAME TABLE,CREATE INDEX,DROP INDEX,INSERT,UPDATE,DELETE,TRUNCATE,REPLACE,CREATE VIEW,ALTER VIEW,DROP VIEW,CREATE DATABASE,ALTER DATABASE,DROP DATABASE'`` + + Defines the list of statements the auto-creation uses for new + versions. + +.. _tracking4: +.. config:option:: $cfg['Servers'][$i]['tracking_add_drop_view'] + + :type: boolean + :default: true + + Whether a DROP VIEW IF EXISTS statement will be added as first line to + the log when creating a view. + +.. _tracking5: +.. config:option:: $cfg['Servers'][$i]['tracking_add_drop_table'] + + :type: boolean + :default: true + + Whether a DROP TABLE IF EXISTS statement will be added as first line + to the log when creating a table. + +.. _tracking6: +.. config:option:: $cfg['Servers'][$i]['tracking_add_drop_database'] + + :type: boolean + :default: true + + Whether a DROP DATABASE IF EXISTS statement will be added as first + line to the log when creating a database. + +.. _userconfig: +.. config:option:: $cfg['Servers'][$i]['userconfig'] + + :type: string or false + :default: ``''`` + + Since release 3.4.x phpMyAdmin allows users to set most preferences by + themselves and store them in the database. + + If you don't allow for storing preferences in + :config:option:`$cfg['Servers'][$i]['pmadb']`, users can still personalize + phpMyAdmin, but settings will be saved in browser's local storage, or, it + is is unavailable, until the end of session. + + To allow the usage of this functionality: + + * set up :config:option:`$cfg['Servers'][$i]['pmadb']` and the phpMyAdmin configuration storage + * put the table name in :config:option:`$cfg['Servers'][$i]['userconfig']` + + This feature can be disabled by setting the configuration to ``false``. + +.. config:option:: $cfg['Servers'][$i]['MaxTableUiprefs'] + + :type: integer + :default: 100 + + Maximum number of rows saved in + :config:option:`$cfg['Servers'][$i]['table_uiprefs']` table. + + When tables are dropped or renamed, + :config:option:`$cfg['Servers'][$i]['table_uiprefs']` may contain invalid data + (referring to tables which no longer exist). We only keep this number of newest + rows in :config:option:`$cfg['Servers'][$i]['table_uiprefs']` and automatically + delete older rows. + +.. config:option:: $cfg['Servers'][$i]['SessionTimeZone'] + + :type: string + :default: ``''`` + + Sets the time zone used by phpMyAdmin. Leave blank to use the time zone of your + database server. Possible values are explained at + https://dev.mysql.com/doc/refman/5.7/en/time-zone-support.html + + This is useful when your database server uses a time zone which is different from the + time zone you want to use in phpMyAdmin. + +.. config:option:: $cfg['Servers'][$i]['AllowRoot'] + + :type: boolean + :default: true + + Whether to allow root access. This is just a shortcut for the + :config:option:`$cfg['Servers'][$i]['AllowDeny']['rules']` below. + +.. config:option:: $cfg['Servers'][$i]['AllowNoPassword'] + + :type: boolean + :default: false + + Whether to allow logins without a password. The default value of + ``false`` for this parameter prevents unintended access to a MySQL + server with was left with an empty password for root or on which an + anonymous (blank) user is defined. + +.. _servers_allowdeny_order: +.. config:option:: $cfg['Servers'][$i]['AllowDeny']['order'] + + :type: string + :default: ``''`` + + If your rule order is empty, then :term:`IP` + authorization is disabled. + + If your rule order is set to + ``'deny,allow'`` then the system applies all deny rules followed by + allow rules. Access is allowed by default. Any client which does not + match a Deny command or does match an Allow command will be allowed + access to the server. + + If your rule order is set to ``'allow,deny'`` + then the system applies all allow rules followed by deny rules. Access + is denied by default. Any client which does not match an Allow + directive or does match a Deny directive will be denied access to the + server. + + If your rule order is set to ``'explicit'``, authorization is + performed in a similar fashion to rule order 'deny,allow', with the + added restriction that your host/username combination **must** be + listed in the *allow* rules, and not listed in the *deny* rules. This + is the **most** secure means of using Allow/Deny rules, and was + available in Apache by specifying allow and deny rules without setting + any order. + + Please also see :config:option:`$cfg['TrustedProxies']` for + detecting IP address behind proxies. + +.. _servers_allowdeny_rules: +.. config:option:: $cfg['Servers'][$i]['AllowDeny']['rules'] + + :type: array of strings + :default: array() + + The general format for the rules is as such: + + .. code-block:: none + + <'allow' | 'deny'> [from] + + If you wish to match all users, it is possible to use a ``'%'`` as a + wildcard in the *username* field. + + There are a few shortcuts you can + use in the *ipmask* field as well (please note that those containing + SERVER\_ADDRESS might not be available on all webservers): + + .. code-block:: none + + 'all' -> 0.0.0.0/0 + 'localhost' -> 127.0.0.1/8 + 'localnetA' -> SERVER_ADDRESS/8 + 'localnetB' -> SERVER_ADDRESS/16 + 'localnetC' -> SERVER_ADDRESS/24 + + Having an empty rule list is equivalent to either using ``'allow % + from all'`` if your rule order is set to ``'deny,allow'`` or ``'deny % + from all'`` if your rule order is set to ``'allow,deny'`` or + ``'explicit'``. + + For the :term:`IP address` matching + system, the following work: + + * ``xxx.xxx.xxx.xxx`` (an exact :term:`IP address`) + * ``xxx.xxx.xxx.[yyy-zzz]`` (an :term:`IP address` range) + * ``xxx.xxx.xxx.xxx/nn`` (CIDR, Classless Inter-Domain Routing type :term:`IP` addresses) + + But the following does not work: + + * ``xxx.xxx.xxx.xx[yyy-zzz]`` (partial :term:`IP` address range) + + For :term:`IPv6` addresses, the following work: + + * ``xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx`` (an exact :term:`IPv6` address) + * ``xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:[yyyy-zzzz]`` (an :term:`IPv6` address range) + * ``xxxx:xxxx:xxxx:xxxx/nn`` (CIDR, Classless Inter-Domain Routing type :term:`IPv6` addresses) + + But the following does not work: + + * ``xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xx[yyy-zzz]`` (partial :term:`IPv6` address range) + + Examples: + + .. code-block:: none + + $cfg['Servers'][$i]['AllowDeny']['order'] = 'allow,deny'; + $cfg['Servers'][$i]['AllowDeny']['rules'] = array('allow bob from all'); + // Allow only 'bob' to connect from any host + + $cfg['Servers'][$i]['AllowDeny']['order'] = 'allow,deny'; + $cfg['Servers'][$i]['AllowDeny']['rules'] = array('allow mary from 192.168.100.[50-100]'); + // Allow only 'mary' to connect from host 192.168.100.50 through 192.168.100.100 + + $cfg['Servers'][$i]['AllowDeny']['order'] = 'allow,deny'; + $cfg['Servers'][$i]['AllowDeny']['rules'] = array('allow % from 192.168.[5-6].10'); + // Allow any user to connect from host 192.168.5.10 or 192.168.6.10 + + $cfg['Servers'][$i]['AllowDeny']['order'] = 'allow,deny'; + $cfg['Servers'][$i]['AllowDeny']['rules'] = array('allow root from 192.168.5.50','allow % from 192.168.6.10'); + // Allow any user to connect from 192.168.6.10, and additionally allow root to connect from 192.168.5.50 + +.. config:option:: $cfg['Servers'][$i]['DisableIS'] + + :type: boolean + :default: false + + Disable using ``INFORMATION_SCHEMA`` to retrieve information (use + ``SHOW`` commands instead), because of speed issues when many + databases are present. + + .. note:: + + Enabling this option might give you a big performance boost on older + MySQL servers. + +.. config:option:: $cfg['Servers'][$i]['SignonScript'] + + :type: string + :default: ``''`` + + .. versionadded:: 3.5.0 + + Name of PHP script to be sourced and executed to obtain login + credentials. This is alternative approach to session based single + signon. The script has to provide a function called + ``get_login_credentials`` which returns list of username and + password, accepting single parameter of existing username (can be + empty). See :file:`examples/signon-script.php` for an example: + + .. literalinclude:: ../examples/signon-script.php + :language: php + + .. seealso:: :ref:`auth_signon` + +.. config:option:: $cfg['Servers'][$i]['SignonSession'] + + :type: string + :default: ``''`` + + Name of session which will be used for signon authentication method. + You should use something different than ``phpMyAdmin``, because this + is session which phpMyAdmin uses internally. Takes effect only if + :config:option:`$cfg['Servers'][$i]['SignonScript']` is not configured. + + .. seealso:: :ref:`auth_signon` + +.. config:option:: $cfg['Servers'][$i]['SignonCookieParams'] + + :type: array + :default: ``array()`` + + .. versionadded:: 4.7.0 + + An associative array of session cookie parameters of other authentication system. + It is not needed if the other system doesn't use session_set_cookie_params(). + Keys should include 'lifetime', 'path', 'domain', 'secure' or 'httponly'. + Valid values are mentioned in `session_get_cookie_params `_, they should be set to same values as the + other application uses. Takes effect only if + :config:option:`$cfg['Servers'][$i]['SignonScript']` is not configured. + + .. seealso:: :ref:`auth_signon` + +.. config:option:: $cfg['Servers'][$i]['SignonURL'] + + :type: string + :default: ``''`` + + :term:`URL` where user will be redirected + to log in for signon authentication method. Should be absolute + including protocol. + + .. seealso:: :ref:`auth_signon` + +.. config:option:: $cfg['Servers'][$i]['LogoutURL'] + + :type: string + :default: ``''`` + + :term:`URL` where user will be redirected + after logout (doesn't affect config authentication method). Should be + absolute including protocol. + +Generic settings +---------------- + +.. config:option:: $cfg['DisableShortcutKeys'] + + :type: boolean + :default: false + + You can disable phpMyAdmin shortcut keys by setting :config:option:`$cfg['DisableShortcutKeys']` to false. + +.. config:option:: $cfg['ServerDefault'] + + :type: integer + :default: 1 + + If you have more than one server configured, you can set + :config:option:`$cfg['ServerDefault']` to any one of them to autoconnect to that + server when phpMyAdmin is started, or set it to 0 to be given a list + of servers without logging in. + + If you have only one server configured, + :config:option:`$cfg['ServerDefault']` MUST be set to that server. + +.. config:option:: $cfg['VersionCheck'] + + :type: boolean + :default: true + + Enables check for latest versions using JavaScript on the main phpMyAdmin + page or by directly accessing :file:`version_check.php`. + + .. note:: + + This setting can be adjusted by your vendor. + +.. config:option:: $cfg['ProxyUrl'] + + :type: string + :default: "" + + The url of the proxy to be used when phpmyadmin needs to access the outside + internet such as when retrieving the latest version info or submitting error + reports. You need this if the server where phpMyAdmin is installed does not + have direct access to the internet. + The format is: "hostname:portnumber" + +.. config:option:: $cfg['ProxyUser'] + + :type: string + :default: "" + + The username for authenticating with the proxy. By default, no + authentication is performed. If a username is supplied, Basic + Authentication will be performed. No other types of authentication + are currently supported. + +.. config:option:: $cfg['ProxyPass'] + + :type: string + :default: "" + + The password for authenticating with the proxy. + +.. config:option:: $cfg['MaxDbList'] + + :type: integer + :default: 100 + + The maximum number of database names to be displayed in the main panel's + database list. + +.. config:option:: $cfg['MaxTableList'] + + :type: integer + :default: 250 + + The maximum number of table names to be displayed in the main panel's + list (except on the Export page). + +.. config:option:: $cfg['ShowHint'] + + :type: boolean + :default: true + + Whether or not to show hints (for example, hints when hovering over + table headers). + +.. config:option:: $cfg['MaxCharactersInDisplayedSQL'] + + :type: integer + :default: 1000 + + The maximum number of characters when a :term:`SQL` query is displayed. The + default limit of 1000 should be correct to avoid the display of tons of + hexadecimal codes that represent BLOBs, but some users have real + :term:`SQL` queries that are longer than 1000 characters. Also, if a + query's length exceeds this limit, this query is not saved in the history. + +.. config:option:: $cfg['PersistentConnections'] + + :type: boolean + :default: false + + Whether `persistent connections `_ should be used or not. Works with + following extensions: + + * mysql (`mysql\_pconnect `_), + * mysqli (requires PHP 5.3.0 or newer, `more information + `_). + +.. config:option:: $cfg['ForceSSL'] + + :type: boolean + :default: false + + .. deprecated:: 4.6.0 + + This setting is no longer available since phpMyAdmin 4.6.0. Please + adjust your webserver instead. + + Whether to force using https while accessing phpMyAdmin. In a reverse + proxy setup, setting this to ``true`` is not supported. + + .. note:: + + In some setups (like separate SSL proxy or load balancer) you might + have to set :config:option:`$cfg['PmaAbsoluteUri']` for correct + redirection. + +.. config:option:: $cfg['ExecTimeLimit'] + + :type: integer [number of seconds] + :default: 300 + + Set the number of seconds a script is allowed to run. If seconds is + set to zero, no time limit is imposed. This setting is used while + importing/exporting dump files but has + no effect when PHP is running in safe mode. + +.. config:option:: $cfg['SessionSavePath'] + + :type: string + :default: ``''`` + + Path for storing session data (`session\_save\_path PHP parameter + `_). + + .. warning:: + + This folder should not be publicly accessible through the webserver, + otherwise you risk leaking private data from your session. + +.. config:option:: $cfg['MemoryLimit'] + + :type: string [number of bytes] + :default: ``'-1'`` + + Set the number of bytes a script is allowed to allocate. If set to + ``'-1'``, no limit is imposed. If set to ``'0'``, no change of the + memory limit is attempted and the :file:`php.ini` ``memory_limit`` is + used. + + This setting is used while importing/exporting dump files + so you definitely don't want to put here a too low + value. It has no effect when PHP is running in safe mode. + + You can also use any string as in :file:`php.ini`, eg. '16M'. Ensure you + don't omit the suffix (16 means 16 bytes!) + +.. config:option:: $cfg['SkipLockedTables'] + + :type: boolean + :default: false + + Mark used tables and make it possible to show databases with locked + tables (since MySQL 3.23.30). + +.. config:option:: $cfg['ShowSQL'] + + :type: boolean + :default: true + + Defines whether :term:`SQL` queries + generated by phpMyAdmin should be displayed or not. + +.. config:option:: $cfg['RetainQueryBox'] + + :type: boolean + :default: false + + Defines whether the :term:`SQL` query box + should be kept displayed after its submission. + +.. config:option:: $cfg['CodemirrorEnable'] + + :type: boolean + :default: true + + Defines whether to use a Javascript code editor for SQL query boxes. + CodeMirror provides syntax highlighting and line numbers. However, + middle-clicking for pasting the clipboard contents in some Linux + distributions (such as Ubuntu) is not supported by all browsers. + +.. config:option:: $cfg['DefaultForeignKeyChecks'] + + :type: string + :default: ``'default'`` + + Default value of the checkbox for foreign key checks, to disable/enable + foreign key checks for certain queries. The possible values are ``'default'``, + ``'enable'`` or ``'disable'``. If set to ``'default'``, the value of the + MySQL variable ``FOREIGN_KEY_CHECKS`` is used. + +.. config:option:: $cfg['AllowUserDropDatabase'] + + :type: boolean + :default: false + + .. warning:: + + This is not a security measure as there will be always ways to + circumvent this. If you want to prohibit users from dropping databases, + revoke their corresponding DROP privilege. + + Defines whether normal users (non-administrator) are allowed to delete + their own database or not. If set as false, the link :guilabel:`Drop + Database` will not be shown, and even a ``DROP DATABASE mydatabase`` will + be rejected. Quite practical for :term:`ISP` 's with many customers. + + This limitation of :term:`SQL` queries is not as strict as when using MySQL + privileges. This is due to nature of :term:`SQL` queries which might be + quite complicated. So this choice should be viewed as help to avoid + accidental dropping rather than strict privilege limitation. + +.. config:option:: $cfg['Confirm'] + + :type: boolean + :default: true + + Whether a warning ("Are your really sure...") should be displayed when + you're about to lose data. + +.. config:option:: $cfg['UseDbSearch'] + + :type: boolean + :default: true + + Define whether the "search string inside database" is enabled or not. + +.. config:option:: $cfg['IgnoreMultiSubmitErrors'] + + :type: boolean + :default: false + + Define whether phpMyAdmin will continue executing a multi-query + statement if one of the queries fails. Default is to abort execution. + +Cookie authentication options +----------------------------- + +.. config:option:: $cfg['blowfish_secret'] + + :type: string + :default: ``''`` + + The "cookie" auth\_type uses AES algorithm to encrypt the password. If you + are using the "cookie" auth\_type, enter here a random passphrase of your + choice. It will be used internally by the AES algorithm: you won’t be + prompted for this passphrase. + + The secret should be 32 characters long. Using shorter will lead to weaker security + of encrypted cookies, using longer will cause no harm. + + .. note:: + + The configuration is called blowfish_secret for historical reasons as + Blowfish algorithm was originally used to do the encryption. + + .. versionchanged:: 3.1.0 + Since version 3.1.0 phpMyAdmin can generate this on the fly, but it + makes a bit weaker security as this generated secret is stored in + session and furthermore it makes impossible to recall user name from + cookie. + +.. config:option:: $cfg['LoginCookieRecall'] + + :type: boolean + :default: true + + Define whether the previous login should be recalled or not in cookie + authentication mode. + + This is automatically disabled if you do not have + configured :config:option:`$cfg['blowfish_secret']`. + +.. config:option:: $cfg['LoginCookieValidity'] + + :type: integer [number of seconds] + :default: 1440 + + Define how long a login cookie is valid. Please note that php + configuration option `session.gc\_maxlifetime + `_ might limit session validity and if the session is lost, + the login cookie is also invalidated. So it is a good idea to set + ``session.gc_maxlifetime`` at least to the same value of + :config:option:`$cfg['LoginCookieValidity']`. + +.. config:option:: $cfg['LoginCookieStore'] + + :type: integer [number of seconds] + :default: 0 + + Define how long login cookie should be stored in browser. Default 0 + means that it will be kept for existing session. This is recommended + for not trusted environments. + +.. config:option:: $cfg['LoginCookieDeleteAll'] + + :type: boolean + :default: true + + If enabled (default), logout deletes cookies for all servers, + otherwise only for current one. Setting this to false makes it easy to + forget to log out from other server, when you are using more of them. + +.. _AllowArbitraryServer: +.. config:option:: $cfg['AllowArbitraryServer'] + + :type: boolean + :default: false + + If enabled, allows you to log in to arbitrary servers using cookie + authentication. + + .. note:: + + Please use this carefully, as this may allow users access to MySQL servers + behind the firewall where your :term:`HTTP` server is placed. + See also :config:option:`$cfg['ArbitraryServerRegexp']`. + +.. config:option:: $cfg['ArbitraryServerRegexp'] + + :type: string + :default: ``''`` + + Restricts the MySQL servers to which the user can log in when + :config:option:`$cfg['AllowArbitraryServer']` is enabled by + matching the :term:`IP` or the hostname of the MySQL server + to the given regular expression. The regular expression must be enclosed + with a delimiter character. + + It is recommended to include start and end symbols in the regullar + expression, so that you can avoid partial matches on the string. + + **Examples:** + + .. code-block:: php + + // Allow connection to three listed servers: + $cfg['ArbitraryServerRegexp'] = '/^(server|another|yetdifferent)$/'; + + // Allow connection to range of IP addresses: + $cfg['ArbitraryServerRegexp'] = '@^192\.168\.0\.[0-9]{1,}$@'; + + // Allow connection to server name ending with -mysql: + $cfg['ArbitraryServerRegexp'] = '@^[^:]\-mysql$@'; + + .. note:: + + The whole server name is matched, it can include port as well. Due to + way MySQL is permissive in connection parameters, it is possible to use + connection strings as ```server:3306-mysql```. This can be used to + bypass regullar expression by the suffix, while connecting to another + server. + +.. config:option:: $cfg['CaptchaLoginPublicKey'] + + :type: string + :default: ``''`` + + The public key for the reCaptcha service that can be obtained from + https://www.google.com/recaptcha/intro/v3.html. + + reCaptcha will be then used in :ref:`cookie`. + +.. config:option:: $cfg['CaptchaLoginPrivateKey'] + + :type: string + :default: ``''`` + + The private key for the reCaptcha service that can be obtain from + https://www.google.com/recaptcha/intro/v3.html. + + reCaptcha will be then used in :ref:`cookie`. + +Navigation panel setup +---------------------- + +.. config:option:: $cfg['ShowDatabasesNavigationAsTree'] + + :type: boolean + :default: true + + In the navigation panel, replaces the database tree with a selector + +.. config:option:: $cfg['FirstLevelNavigationItems'] + + :type: integer + :default: 100 + + The number of first level databases that can be displayed on each page + of navigation tree. + +.. config:option:: $cfg['MaxNavigationItems'] + + :type: integer + :default: 50 + + The number of items (tables, columns, indexes) that can be displayed on each + page of the navigation tree. + +.. config:option:: $cfg['NavigationTreeEnableGrouping'] + + :type: boolean + :default: true + + Defines whether to group the databases based on a common prefix + in their name :config:option:`$cfg['NavigationTreeDbSeparator']`. + +.. config:option:: $cfg['NavigationTreeDbSeparator'] + + :type: string + :default: ``'_'`` + + The string used to separate the parts of the database name when + showing them in a tree. + +.. config:option:: $cfg['NavigationTreeTableSeparator'] + + :type: string or array + :default: ``'__'`` + + Defines a string to be used to nest table spaces. This means if you have + tables like ``first__second__third`` this will be shown as a three-level + hierarchy like: first > second > third. If set to false or empty, the + feature is disabled. NOTE: You should not use this separator at the + beginning or end of a table name or multiple times after another without + any other characters in between. + +.. config:option:: $cfg['NavigationTreeTableLevel'] + + :type: integer + :default: 1 + + Defines how many sublevels should be displayed when splitting up + tables by the above separator. + +.. config:option:: $cfg['NumRecentTables'] + + :type: integer + :default: 10 + + The maximum number of recently used tables shown in the navigation + panel. Set this to 0 (zero) to disable the listing of recent tables. + +.. config:option:: $cfg['NumFavoriteTables'] + + :type: integer + :default: 10 + + The maximum number of favorite tables shown in the navigation + panel. Set this to 0 (zero) to disable the listing of favorite tables. + +.. config:option:: $cfg['ZeroConf'] + + :type: boolean + :default: true + + Enables Zero Configuration mode in which the user will be offered a choice to + create phpMyAdmin configuration storage in the current database + or use the existing one, if already present. + + This setting has no effect if the phpMyAdmin configuration storage database + is properly created and the related configuration directives (such as + :config:option:`$cfg['Servers'][$i]['pmadb']` and so on) are configured. + +.. config:option:: $cfg['NavigationLinkWithMainPanel'] + + :type: boolean + :default: true + + Defines whether or not to link with main panel by highlighting + the current database or table. + +.. config:option:: $cfg['NavigationDisplayLogo'] + + :type: boolean + :default: true + + Defines whether or not to display the phpMyAdmin logo at the top of + the navigation panel. + +.. config:option:: $cfg['NavigationLogoLink'] + + :type: string + :default: ``'index.php'`` + + Enter :term:`URL` where logo in the navigation panel will point to. + For use especially with self made theme which changes this. + For relative/internal URLs, you need to have leading `` ./ `` or trailing characters `` ? `` such as ``'./sql.php?'``. + For external URLs, you should include URL protocol schemes (``http`` or ``https``) with absolute URLs. + +.. config:option:: $cfg['NavigationLogoLinkWindow'] + + :type: string + :default: ``'main'`` + + Whether to open the linked page in the main window (``main``) or in a + new one (``new``). Note: use ``new`` if you are linking to + ``phpmyadmin.net``. + +.. config:option:: $cfg['NavigationTreeDisplayItemFilterMinimum'] + + :type: integer + :default: 30 + + Defines the minimum number of items (tables, views, routines and + events) to display a JavaScript filter box above the list of items in + the navigation tree. + + To disable the filter completely some high number can be used (e.g. 9999) + +.. config:option:: $cfg['NavigationTreeDisplayDbFilterMinimum'] + + :type: integer + :default: 30 + + Defines the minimum number of databases to display a JavaScript filter + box above the list of databases in the navigation tree. + + To disable the filter completely some high number can be used + (e.g. 9999) + +.. config:option:: $cfg['NavigationDisplayServers'] + + :type: boolean + :default: true + + Defines whether or not to display a server choice at the top of the + navigation panel. + +.. config:option:: $cfg['DisplayServersList'] + + :type: boolean + :default: false + + Defines whether to display this server choice as links instead of in a + drop-down. + +.. config:option:: $cfg['NavigationTreeDefaultTabTable'] + + :type: string + :default: ``'structure'`` + + Defines the tab displayed by default when clicking the small icon next + to each table name in the navigation panel. The possible values are the + localized equivalent of: + + * ``structure`` + * ``sql`` + * ``search`` + * ``insert`` + * ``browse`` + +.. config:option:: $cfg['NavigationTreeDefaultTabTable2'] + + :type: string + :default: null + + Defines the tab displayed by default when clicking the second small icon next + to each table name in the navigation panel. The possible values are the + localized equivalent of: + + * ``(empty)`` + * ``structure`` + * ``sql`` + * ``search`` + * ``insert`` + * ``browse`` + +.. config:option:: $cfg['NavigationTreeEnableExpansion'] + + :type: boolean + :default: true + + Whether to offer the possibility of tree expansion in the navigation panel. + +.. config:option:: $cfg['NavigationTreeShowTables'] + + :type: boolean + :default: true + + Whether to show tables under database in the navigation panel. + +.. config:option:: $cfg['NavigationTreeShowViews'] + + :type: boolean + :default: true + + Whether to show views under database in the navigation panel. + +.. config:option:: $cfg['NavigationTreeShowFunctions'] + + :type: boolean + :default: true + + Whether to show functions under database in the navigation panel. + +.. config:option:: $cfg['NavigationTreeShowProcedures'] + + :type: boolean + :default: true + + Whether to show procedures under database in the navigation panel. + +.. config:option:: $cfg['NavigationTreeShowEvents'] + + :type: boolean + :default: true + + Whether to show events under database in the navigation panel. + +.. config:option:: $cfg['NavigationWidth'] + + :type: integer + :default: 240 + + Navigation panel width, set to 0 to collapse it by default. + +Main panel +---------- + +.. config:option:: $cfg['ShowStats'] + + :type: boolean + :default: true + + Defines whether or not to display space usage and statistics about + databases and tables. Note that statistics requires at least MySQL + 3.23.3 and that, at this date, MySQL doesn't return such information + for Berkeley DB tables. + +.. config:option:: $cfg['ShowServerInfo'] + + :type: boolean + :default: true + + Defines whether to display detailed server information on main page. + You can additionally hide more information by using + :config:option:`$cfg['Servers'][$i]['verbose']`. + +.. config:option:: $cfg['ShowPhpInfo'] + + :type: boolean + :default: false + + Defines whether to display the :guilabel:`PHP information` or not at + the starting main (right) frame. + + Please note that to block the usage of ``phpinfo()`` in scripts, you have to + put this in your :file:`php.ini`: + + .. code-block:: ini + + disable_functions = phpinfo() + + .. warning:: + + Enabling phpinfo page will leak quite a lot of information about server + setup. Is it not recommended to enable this on shared installations. + + This might also make easier some remote attacks on your installations, + so enable this only when needed. + +.. config:option:: $cfg['ShowChgPassword'] + + :type: boolean + :default: true + + Defines whether to display the :guilabel:`Change password` link or not at + the starting main (right) frame. This setting does not check MySQL commands + entered directly. + + Please note that enabling the :guilabel:`Change password` link has no effect + with config authentication mode: because of the hard coded password value + in the configuration file, end users can't be allowed to change their + passwords. + +.. config:option:: $cfg['ShowCreateDb'] + + :type: boolean + :default: true + + Defines whether to display the form for creating database or not at the + starting main (right) frame. This setting does not check MySQL commands + entered directly. + +.. config:option:: $cfg['ShowGitRevision'] + + :type: boolean + :default: true + + Defines whether to display informations about the current Git revision (if + applicable) on the main panel. + +.. config:option:: $cfg['MysqlMinVersion'] + + :type: array + + Defines the minimum supported MySQL version. The default is chosen + by the phpMyAdmin team; however this directive was asked by a developer + of the Plesk control panel to ease integration with older MySQL servers + (where most of the phpMyAdmin features work). + +Database structure +------------------ + +.. config:option:: $cfg['ShowDbStructureCreation'] + + :type: boolean + :default: false + + Defines whether the database structure page (tables list) has a + "Creation" column that displays when each table was created. + +.. config:option:: $cfg['ShowDbStructureLastUpdate'] + + :type: boolean + :default: false + + Defines whether the database structure page (tables list) has a "Last + update" column that displays when each table was last updated. + +.. config:option:: $cfg['ShowDbStructureLastCheck'] + + :type: boolean + :default: false + + Defines whether the database structure page (tables list) has a "Last + check" column that displays when each table was last checked. + +.. config:option:: $cfg['HideStructureActions'] + + :type: boolean + :default: true + + Defines whether the table structure actions are hidden under a "More" + drop-down. + +.. config:option:: $cfg['ShowColumnComments'] + + :type: boolean + :default: true + + Defines whether to show column comments as a column in the table structure view. + +Browse mode +----------- + +.. config:option:: $cfg['TableNavigationLinksMode'] + + :type: string + :default: ``'icons'`` + + Defines whether the table navigation links contain ``'icons'``, ``'text'`` + or ``'both'``. + +.. config:option:: $cfg['ActionLinksMode'] + + :type: string + :default: ``'both'`` + + If set to ``icons``, will display icons instead of text for db and table + properties links (like :guilabel:`Browse`, :guilabel:`Select`, + :guilabel:`Insert`, ...). Can be set to ``'both'`` + if you want icons AND text. When set to ``text``, will only show text. + +.. config:option:: $cfg['RowActionType'] + + :type: string + :default: ``'both'`` + + Whether to display icons or text or both icons and text in table row action + segment. Value can be either of ``'icons'``, ``'text'`` or ``'both'``. + +.. config:option:: $cfg['ShowAll'] + + :type: boolean + :default: false + + Defines whether a user should be displayed a "Show all" button in browse + mode or not in all cases. By default it is shown only on small tables (less + than 500 rows) to avoid performance issues while getting too many rows. + +.. config:option:: $cfg['MaxRows'] + + :type: integer + :default: 25 + + Number of rows displayed when browsing a result set and no LIMIT + clause is used. If the result set contains more rows, "Previous" and + "Next" links will be shown. Possible values: 25,50,100,250,500. + +.. config:option:: $cfg['Order'] + + :type: string + :default: ``'SMART'`` + + Defines whether columns are displayed in ascending (``ASC``) order, in + descending (``DESC``) order or in a "smart" (``SMART``) order - I.E. + descending order for columns of type TIME, DATE, DATETIME and + TIMESTAMP, ascending order else- by default. + + .. versionchanged:: 3.4.0 + Since phpMyAdmin 3.4.0 the default value is ``'SMART'``. + +.. config:option:: $cfg['GridEditing'] + + :type: string + :default: ``'double-click'`` + + Defines which action (``double-click`` or ``click``) triggers grid + editing. Can be deactivated with the ``disabled`` value. + +.. config:option:: $cfg['RelationalDisplay'] + + :type: string + :default: ``'K'`` + + Defines the initial behavior for Options > Relational. ``K``, which + is the default, displays the key while ``D`` shows the display column. + +.. config:option:: $cfg['SaveCellsAtOnce'] + + :type: boolean + :default: false + + Defines whether or not to save all edited cells at once for grid + editing. + +Editing mode +------------ + +.. config:option:: $cfg['ProtectBinary'] + + :type: boolean or string + :default: ``'blob'`` + + Defines whether ``BLOB`` or ``BINARY`` columns are protected from + editing when browsing a table's content. Valid values are: + + * ``false`` to allow editing of all columns; + * ``'blob'`` to allow editing of all columns except ``BLOBS``; + * ``'noblob'`` to disallow editing of all columns except ``BLOBS`` (the + opposite of ``'blob'``); + * ``'all'`` to disallow editing of all ``BINARY`` or ``BLOB`` columns. + +.. config:option:: $cfg['ShowFunctionFields'] + + :type: boolean + :default: true + + Defines whether or not MySQL functions fields should be initially + displayed in edit/insert mode. Since version 2.10, the user can toggle + this setting from the interface. + +.. config:option:: $cfg['ShowFieldTypesInDataEditView'] + + :type: boolean + :default: true + + Defines whether or not type fields should be initially displayed in + edit/insert mode. The user can toggle this setting from the interface. + +.. config:option:: $cfg['InsertRows'] + + :type: integer + :default: 2 + + Defines the default number of rows to be entered from the Insert page. + Users can manually change this from the bottom of that page to add or remove + blank rows. + +.. config:option:: $cfg['ForeignKeyMaxLimit'] + + :type: integer + :default: 100 + + If there are fewer items than this in the set of foreign keys, then a + drop-down box of foreign keys is presented, in the style described by + the :config:option:`$cfg['ForeignKeyDropdownOrder']` setting. + +.. config:option:: $cfg['ForeignKeyDropdownOrder'] + + :type: array + :default: array('content-id', 'id-content') + + For the foreign key drop-down fields, there are several methods of + display, offering both the key and value data. The contents of the + array should be one or both of the following strings: ``content-id``, + ``id-content``. + +Export and import settings +-------------------------- + +.. config:option:: $cfg['ZipDump'] + + :type: boolean + :default: true + +.. config:option:: $cfg['GZipDump'] + + :type: boolean + :default: true + +.. config:option:: $cfg['BZipDump'] + + :type: boolean + :default: true + + Defines whether to allow the use of zip/GZip/BZip2 compression when + creating a dump file + +.. config:option:: $cfg['CompressOnFly'] + + :type: boolean + :default: true + + Defines whether to allow on the fly compression for GZip/BZip2 + compressed exports. This doesn't affect smaller dumps and allows users + to create larger dumps that won't otherwise fit in memory due to php + memory limit. Produced files contain more GZip/BZip2 headers, but all + normal programs handle this correctly. + +.. config:option:: $cfg['Export'] + + :type: array + :default: array(...) + + In this array are defined default parameters for export, names of + items are similar to texts seen on export page, so you can easily + identify what they mean. + +.. config:option:: $cfg['Export']['format'] + + :type: string + :default: ``'sql'`` + + Default export format. + +.. config:option:: $cfg['Export']['method'] + + :type: string + :default: ``'quick'`` + + Defines how the export form is displayed when it loads. Valid values + are: + + * ``quick`` to display the minimum number of options to configure + * ``custom`` to display every available option to configure + * ``custom-no-form`` same as ``custom`` but does not display the option + of using quick export + +.. config:option:: $cfg['Export']['charset'] + + :type: string + :default: ``''`` + + Defines charset for generated export. By default no charset conversion is + done assuming UTF-8. + +.. config:option:: $cfg['Export']['file_template_table'] + + :type: string + :default: ``'@TABLE@'`` + + Default filename template for table exports. + + .. seealso:: :ref:`faq6_27` + +.. config:option:: $cfg['Export']['file_template_database'] + + :type: string + :default: ``'@DATABASE@'`` + + Default filename template for database exports. + + .. seealso:: :ref:`faq6_27` + +.. config:option:: $cfg['Export']['file_template_server'] + + :type: string + :default: ``'@SERVER@'`` + + Default filename template for server exports. + + .. seealso:: :ref:`faq6_27` + +.. config:option:: $cfg['Import'] + + :type: array + :default: array(...) + + In this array are defined default parameters for import, names of + items are similar to texts seen on import page, so you can easily + identify what they mean. + +.. config:option:: $cfg['Import']['charset'] + + :type: string + :default: ``''`` + + Defines charset for import. By default no charset conversion is done + assuming UTF-8. + +Tabs display settings +--------------------- + +.. config:option:: $cfg['TabsMode'] + + :type: string + :default: ``'both'`` + + Defines whether the menu tabs contain ``'icons'``, ``'text'`` or ``'both'``. + +.. config:option:: $cfg['PropertiesNumColumns'] + + :type: integer + :default: 1 + + How many columns will be utilized to display the tables on the database + property view? When setting this to a value larger than 1, the type of the + database will be omitted for more display space. + +.. config:option:: $cfg['DefaultTabServer'] + + :type: string + :default: ``'welcome'`` + + Defines the tab displayed by default on server view. The possible values + are the localized equivalent of: + + * ``welcome`` (recommended for multi-user setups) + * ``databases``, + * ``status`` + * ``variables`` + * ``privileges`` + +.. config:option:: $cfg['DefaultTabDatabase'] + + :type: string + :default: ``'structure'`` + + Defines the tab displayed by default on database view. The possible values + are the localized equivalent of: + + * ``structure`` + * ``sql`` + * ``search`` + * ``operations`` + +.. config:option:: $cfg['DefaultTabTable'] + + :type: string + :default: ``'browse'`` + + Defines the tab displayed by default on table view. The possible values + are the localized equivalent of: + + * ``structure`` + * ``sql`` + * ``search`` + * ``insert`` + * ``browse`` + +PDF Options +----------- + +.. config:option:: $cfg['PDFPageSizes'] + + :type: array + :default: ``array('A3', 'A4', 'A5', 'letter', 'legal')`` + + Array of possible paper sizes for creating PDF pages. + + You should never need to change this. + +.. config:option:: $cfg['PDFDefaultPageSize'] + + :type: string + :default: ``'A4'`` + + Default page size to use when creating PDF pages. Valid values are any + listed in :config:option:`$cfg['PDFPageSizes']`. + +Languages +--------- + +.. config:option:: $cfg['DefaultLang'] + + :type: string + :default: ``'en'`` + + Defines the default language to use, if not browser-defined or user- + defined. The corresponding language file needs to be in + locale/*code*/LC\_MESSAGES/phpmyadmin.mo. + +.. config:option:: $cfg['DefaultConnectionCollation'] + + :type: string + :default: ``'utf8mb4_general_ci'`` + + Defines the default connection collation to use, if not user-defined. + See the `MySQL documentation for charsets + `_ + for list of possible values. + +.. config:option:: $cfg['Lang'] + + :type: string + :default: not set + + Force language to use. The corresponding language file needs to be in + locale/*code*/LC\_MESSAGES/phpmyadmin.mo. + +.. config:option:: $cfg['FilterLanguages'] + + :type: string + :default: ``''`` + + Limit list of available languages to those matching the given regular + expression. For example if you want only Czech and English, you should + set filter to ``'^(cs|en)'``. + +.. config:option:: $cfg['RecodingEngine'] + + :type: string + :default: ``'auto'`` + + You can select here which functions will be used for character set + conversion. Possible values are: + + * auto - automatically use available one (first is tested iconv, then + recode) + * iconv - use iconv or libiconv functions + * recode - use recode\_string function + * mb - use :term:`mbstring` extension + * none - disable encoding conversion + + Enabled charset conversion activates a pull-down menu in the Export + and Import pages, to choose the character set when exporting a file. + The default value in this menu comes from + :config:option:`$cfg['Export']['charset']` and :config:option:`$cfg['Import']['charset']`. + +.. config:option:: $cfg['IconvExtraParams'] + + :type: string + :default: ``'//TRANSLIT'`` + + Specify some parameters for iconv used in charset conversion. See + `iconv documentation `_ for details. By default + ``//TRANSLIT`` is used, so that invalid characters will be + transliterated. + +.. config:option:: $cfg['AvailableCharsets'] + + :type: array + :default: array(...) + + Available character sets for MySQL conversion. You can add your own + (any of supported by recode/iconv) or remove these which you don't + use. Character sets will be shown in same order as here listed, so if + you frequently use some of these move them to the top. + +Web server settings +------------------- + +.. config:option:: $cfg['OBGzip'] + + :type: string/boolean + :default: ``'auto'`` + + Defines whether to use GZip output buffering for increased speed in + :term:`HTTP` transfers. Set to + true/false for enabling/disabling. When set to 'auto' (string), + phpMyAdmin tries to enable output buffering and will automatically + disable it if your browser has some problems with buffering. IE6 with + a certain patch is known to cause data corruption when having enabled + buffering. + +.. config:option:: $cfg['TrustedProxies'] + + :type: array + :default: array() + + Lists proxies and HTTP headers which are trusted for + :config:option:`$cfg['Servers'][$i]['AllowDeny']['order']`. This list is by + default empty, you need to fill in some trusted proxy servers if you + want to use rules for IP addresses behind proxy. + + The following example specifies that phpMyAdmin should trust a + HTTP\_X\_FORWARDED\_FOR (``X-Forwarded-For``) header coming from the proxy + 1.2.3.4: + + .. code-block:: php + + $cfg['TrustedProxies'] = array('1.2.3.4' => 'HTTP_X_FORWARDED_FOR'); + + The :config:option:`$cfg['Servers'][$i]['AllowDeny']['rules']` directive uses the + client's IP address as usual. + +.. config:option:: $cfg['GD2Available'] + + :type: string + :default: ``'auto'`` + + Specifies whether GD >= 2 is available. If yes it can be used for MIME + transformations. Possible values are: + + * auto - automatically detect + * yes - GD 2 functions can be used + * no - GD 2 function cannot be used + +.. config:option:: $cfg['CheckConfigurationPermissions'] + + :type: boolean + :default: true + + We normally check the permissions on the configuration file to ensure + it's not world writable. However, phpMyAdmin could be installed on a + NTFS filesystem mounted on a non-Windows server, in which case the + permissions seems wrong but in fact cannot be detected. In this case a + sysadmin would set this parameter to ``false``. + +.. config:option:: $cfg['LinkLengthLimit'] + + :type: integer + :default: 1000 + + Limit for length of :term:`URL` in links. When length would be above this + limit, it is replaced by form with button. This is required as some web + servers (:term:`IIS`) have problems with long :term:`URL` . + +.. config:option:: $cfg['CSPAllow'] + + :type: string + :default: ``''`` + + Additional string to include in allowed script and image sources in Content + Security Policy header. + + This can be useful when you want to include some external JavaScript files + in :file:`config.footer.inc.php` or :file:`config.header.inc.php`, which + would be normally not allowed by Content Security Policy. + + To allow some sites, just list them within the string: + + .. code-block:: php + + $cfg['CSPAllow'] = 'example.com example.net'; + + .. versionadded:: 4.0.4 + +.. config:option:: $cfg['DisableMultiTableMaintenance'] + + :type: boolean + :default: false + + In the database Structure page, it's possible to mark some tables then + choose an operation like optimizing for many tables. This can slow + down a server; therefore, setting this to ``true`` prevents this kind + of multiple maintenance operation. + +Theme settings +-------------- + + Please directly modify :file:`themes/themename/layout.inc.php`, although + your changes will be overwritten with the next update. + +Design customization +-------------------- + +.. config:option:: $cfg['NavigationTreePointerEnable'] + + :type: boolean + :default: true + + When set to true, hovering over an item in the navigation panel causes that item to be marked + (the background is highlighted). + +.. config:option:: $cfg['BrowsePointerEnable'] + + :type: boolean + :default: true + + When set to true, hovering over a row in the Browse page causes that row to be marked (the background + is highlighted). + +.. config:option:: $cfg['BrowseMarkerEnable'] + + :type: boolean + :default: true + + When set to true, a data row is marked (the background is highlighted) when the row is selected + with the checkbox. + +.. config:option:: $cfg['LimitChars'] + + :type: integer + :default: 50 + + Maximum number of characters shown in any non-numeric field on browse + view. Can be turned off by a toggle button on the browse page. + +.. config:option:: $cfg['RowActionLinks'] + + :type: string + :default: ``'left'`` + + Defines the place where table row links (Edit, Copy, Delete) would be + put when tables contents are displayed (you may have them displayed at + the left side, right side, both sides or nowhere). + +.. config:option:: $cfg['RowActionLinksWithoutUnique'] + + :type: boolean + :default: false + + Defines whether to show row links (Edit, Copy, Delete) and checkboxes + for multiple row operations even when the selection does not have a :term:`unique key`. + Using row actions in the absence of a unique key may result in different/more + rows being affected since there is no guaranteed way to select the exact row(s). + +.. config:option:: $cfg['RememberSorting'] + + :type: boolean + :default: true + + If enabled, remember the sorting of each table when browsing them. + +.. config:option:: $cfg['TablePrimaryKeyOrder'] + + :type: string + :default: ``'NONE'`` + + This defines the default sort order for the tables, having a :term:`primary key`, + when there is no sort order defines externally. + Acceptable values : ['NONE', 'ASC', 'DESC'] + +.. config:option:: $cfg['ShowBrowseComments'] + + :type: boolean + :default: true + +.. config:option:: $cfg['ShowPropertyComments'] + + :type: boolean + :default: true + + By setting the corresponding variable to ``true`` you can enable the + display of column comments in Browse or Property display. In browse + mode, the comments are shown inside the header. In property mode, + comments are displayed using a CSS-formatted dashed-line below the + name of the column. The comment is shown as a tool-tip for that + column. + +Text fields +----------- + +.. config:option:: $cfg['CharEditing'] + + :type: string + :default: ``'input'`` + + Defines which type of editing controls should be used for CHAR and + VARCHAR columns. Applies to data editing and also to the default values + in structure editing. Possible values are: + + * input - this allows to limit size of text to size of columns in MySQL, + but has problems with newlines in columns + * textarea - no problems with newlines in columns, but also no length + limitations + +.. config:option:: $cfg['MinSizeForInputField'] + + :type: integer + :default: 4 + + Defines the minimum size for input fields generated for CHAR and + VARCHAR columns. + +.. config:option:: $cfg['MaxSizeForInputField'] + + :type: integer + :default: 60 + + Defines the maximum size for input fields generated for CHAR and + VARCHAR columns. + +.. config:option:: $cfg['TextareaCols'] + + :type: integer + :default: 40 + +.. config:option:: $cfg['TextareaRows'] + + :type: integer + :default: 15 + +.. config:option:: $cfg['CharTextareaCols'] + + :type: integer + :default: 40 + +.. config:option:: $cfg['CharTextareaRows'] + + :type: integer + :default: 2 + + Number of columns and rows for the textareas. This value will be + emphasized (\*2) for :term:`SQL` query + textareas and (\*1.25) for :term:`SQL` + textareas inside the query window. + + The Char\* values are used for CHAR + and VARCHAR editing (if configured via :config:option:`$cfg['CharEditing']`). + +.. config:option:: $cfg['LongtextDoubleTextarea'] + + :type: boolean + :default: true + + Defines whether textarea for LONGTEXT columns should have double size. + +.. config:option:: $cfg['TextareaAutoSelect'] + + :type: boolean + :default: false + + Defines if the whole textarea of the query box will be selected on + click. + +.. config:option:: $cfg['EnableAutocompleteForTablesAndColumns'] + + :type: boolean + :default: true + + Whether to enable autocomplete for table and column names in any + SQL query box. + +SQL query box settings +---------------------- + +.. config:option:: $cfg['SQLQuery']['Edit'] + + :type: boolean + :default: true + + Whether to display an edit link to change a query in any SQL Query + box. + +.. config:option:: $cfg['SQLQuery']['Explain'] + + :type: boolean + :default: true + + Whether to display a link to explain a SELECT query in any SQL Query + box. + +.. config:option:: $cfg['SQLQuery']['ShowAsPHP'] + + :type: boolean + :default: true + + Whether to display a link to wrap a query in PHP code in any SQL Query + box. + +.. config:option:: $cfg['SQLQuery']['Refresh'] + + :type: boolean + :default: true + + Whether to display a link to refresh a query in any SQL Query box. + +.. _web-dirs: + +Web server upload/save/import directories +----------------------------------------- + +If PHP is running in safe mode, all directories must be owned by the same user +as the owner of the phpMyAdmin scripts. + +If the directory where phpMyAdmin is installed is subject to an +``open_basedir`` restriction, you need to create a temporary directory in some +directory accessible by the PHP interpreter. + +For security reasons, all directories should be outside the tree published by +webserver. If you cannot avoid having this directory published by webserver, +limit access to it either by web server configuration (for example using +.htaccess or web.config files) or place at least an empty :file:`index.html` +file there, so that directory listing is not possible. However as long as the +directory is accessible by web server, an attacker can guess filenames to download +the files. + +.. config:option:: $cfg['UploadDir'] + + :type: string + :default: ``''`` + + The name of the directory where :term:`SQL` files have been uploaded by + other means than phpMyAdmin (for example, ftp). Those files are available + under a drop-down box when you click the database or table name, then the + Import tab. + + If + you want different directory for each user, %u will be replaced with + username. + + Please note that the file names must have the suffix ".sql" + (or ".sql.bz2" or ".sql.gz" if support for compressed formats is + enabled). + + This feature is useful when your file is too big to be + uploaded via :term:`HTTP`, or when file + uploads are disabled in PHP. + + .. warning:: + + Please see top of this chapter (:ref:`web-dirs`) for instructions how + to setup this directory and how to make its usage secure. + + .. seealso:: + + See :ref:`faq1_16` for alternatives. + +.. config:option:: $cfg['SaveDir'] + + :type: string + :default: ``''`` + + The name of the webserver directory where exported files can be saved. + + If you want a different directory for each user, %u will be replaced with the + username. + + Please note that the directory must exist and has to be writable for + the user running webserver. + + .. warning:: + + Please see top of this chapter (:ref:`web-dirs`) for instructions how + to setup this directory and how to make its usage secure. + +.. config:option:: $cfg['TempDir'] + + :type: string + :default: ``'./tmp/'`` + + The name of the directory where temporary files can be stored. It is used + for several purposes, currently: + + * The templates cache which speeds up page loading. + * ESRI Shapefiles import, see :ref:`faq6_30`. + * To work around limitations of ``open_basedir`` for uploaded files, see + :ref:`faq1_11`. + + This directory should have as strict permissions as possible as the only + user required to access this directory is the one who runs the webserver. + If you have root privileges, simply make this user owner of this directory + and make it accessible only by it: + + .. code-block:: sh + + chown www-data:www-data tmp + chmod 700 tmp + + If you cannot change owner of the directory, you can achieve a similar + setup using :term:`ACL`: + + .. code-block:: sh + + chmod 700 tmp + setfacl -m "g:www-data:rwx" tmp + setfacl -d -m "g:www-data:rwx" tmp + + If neither of above works for you, you can still make the directory + :command:`chmod 777`, but it might impose risk of other users on system + reading and writing data in this directory. + + .. warning:: + + Please see top of this chapter (:ref:`web-dirs`) for instructions how + to setup this directory and how to make its usage secure. + +Various display setting +----------------------- + +.. config:option:: $cfg['RepeatCells'] + + :type: integer + :default: 100 + + Repeat the headers every X cells, or 0 to deactivate. + +.. config:option:: $cfg['QueryHistoryDB'] + + :type: boolean + :default: false + +.. config:option:: $cfg['QueryHistoryMax'] + + :type: integer + :default: 25 + + If :config:option:`$cfg['QueryHistoryDB']` is set to ``true``, all your + Queries are logged to a table, which has to be created by you (see + :config:option:`$cfg['Servers'][$i]['history']`). If set to false, all your + queries will be appended to the form, but only as long as your window is + opened they remain saved. + + When using the JavaScript based query window, it will always get updated + when you click on a new table/db to browse and will focus if you click on + :guilabel:`Edit SQL` after using a query. You can suppress updating the + query window by checking the box :guilabel:`Do not overwrite this query + from outside the window` below the query textarea. Then you can browse + tables/databases in the background without losing the contents of the + textarea, so this is especially useful when composing a query with tables + you first have to look in. The checkbox will get automatically checked + whenever you change the contents of the textarea. Please uncheck the button + whenever you definitely want the query window to get updated even though + you have made alterations. + + If :config:option:`$cfg['QueryHistoryDB']` is set to ``true`` you can + specify the amount of saved history items using + :config:option:`$cfg['QueryHistoryMax']`. + +.. config:option:: $cfg['BrowseMIME'] + + :type: boolean + :default: true + + Enable :ref:`transformations`. + +.. config:option:: $cfg['MaxExactCount'] + + :type: integer + :default: 50000 + + For InnoDB tables, determines for how large tables phpMyAdmin should + get the exact row count using ``SELECT COUNT``. If the approximate row + count as returned by ``SHOW TABLE STATUS`` is smaller than this value, + ``SELECT COUNT`` will be used, otherwise the approximate count will be + used. + + .. versionchanged:: 4.8.0 + + The default value was lowered to 50000 for performance reasons. + + .. versionchanged:: 4.2.6 + + The default value was changed to 500000. + + .. seealso:: :ref:`faq3_11` + +.. config:option:: $cfg['MaxExactCountViews'] + + :type: integer + :default: 0 + + For VIEWs, since obtaining the exact count could have an impact on + performance, this value is the maximum to be displayed, using a + ``SELECT COUNT ... LIMIT``. Setting this to 0 bypasses any row + counting. + +.. config:option:: $cfg['NaturalOrder'] + + :type: boolean + :default: true + + Sorts database and table names according to natural order (for + example, t1, t2, t10). Currently implemented in the navigation panel + and in Database view, for the table list. + +.. config:option:: $cfg['InitialSlidersState'] + + :type: string + :default: ``'closed'`` + + If set to ``'closed'``, the visual sliders are initially in a closed + state. A value of ``'open'`` does the reverse. To completely disable + all visual sliders, use ``'disabled'``. + +.. config:option:: $cfg['UserprefsDisallow'] + + :type: array + :default: array() + + Contains names of configuration options (keys in ``$cfg`` array) that + users can't set through user preferences. For possible values, refer + to clases under :file:`libraries/classes/Config/Forms/User/`. + +.. config:option:: $cfg['UserprefsDeveloperTab'] + + :type: boolean + :default: false + + Activates in the user preferences a tab containing options for + developers of phpMyAdmin. + +Page titles +----------- + +.. config:option:: $cfg['TitleTable'] + + :type: string + :default: ``'@HTTP_HOST@ / @VSERVER@ / @DATABASE@ / @TABLE@ | @PHPMYADMIN@'`` + +.. config:option:: $cfg['TitleDatabase'] + + :type: string + :default: ``'@HTTP_HOST@ / @VSERVER@ / @DATABASE@ | @PHPMYADMIN@'`` + +.. config:option:: $cfg['TitleServer'] + + :type: string + :default: ``'@HTTP_HOST@ / @VSERVER@ | @PHPMYADMIN@'`` + +.. config:option:: $cfg['TitleDefault'] + + :type: string + :default: ``'@HTTP_HOST@ | @PHPMYADMIN@'`` + + Allows you to specify window's title bar. You can use :ref:`faq6_27`. + +Theme manager settings +---------------------- + +.. config:option:: $cfg['ThemeManager'] + + :type: boolean + :default: true + + Enables user-selectable themes. See :ref:`faqthemes`. + +.. config:option:: $cfg['ThemeDefault'] + + :type: string + :default: ``'pmahomme'`` + + The default theme (a subdirectory under :file:`./themes/`). + +.. config:option:: $cfg['ThemePerServer'] + + :type: boolean + :default: false + + Whether to allow different theme for each server. + +.. config:option:: $cfg['FontSize'] + + :type: string + :default: '82%' + + Font size to use, is applied in CSS. + +Default queries +--------------- + +.. config:option:: $cfg['DefaultQueryTable'] + + :type: string + :default: ``'SELECT * FROM @TABLE@ WHERE 1'`` + +.. config:option:: $cfg['DefaultQueryDatabase'] + + :type: string + :default: ``''`` + + Default queries that will be displayed in query boxes when user didn't + specify any. You can use standard :ref:`faq6_27`. + +MySQL settings +-------------- + +.. config:option:: $cfg['DefaultFunctions'] + + :type: array + :default: array(...) + + Functions selected by default when inserting/changing row, Functions + are defined for meta types as (FUNC\_NUMBER, FUNC\_DATE, FUNC\_CHAR, + FUNC\_SPATIAL, FUNC\_UUID) and for ``first_timestamp``, which is used + for first timestamp column in table. + +Default options for Transformations +----------------------------------- + +.. config:option:: $cfg['DefaultTransformations'] + + :type: array + :default: An array with below listed key-values + +.. config:option:: $cfg['DefaultTransformations']['Substring'] + + :type: array + :default: array(0, 'all', '…') + +.. config:option:: $cfg['DefaultTransformations']['Bool2Text'] + + :type: array + :default: array('T', 'F') + +.. config:option:: $cfg['DefaultTransformations']['External'] + + :type: array + :default: array(0, '-f /dev/null -i -wrap -q', 1, 1) + +.. config:option:: $cfg['DefaultTransformations']['PreApPend'] + + :type: array + :default: array('', '') + +.. config:option:: $cfg['DefaultTransformations']['Hex'] + + :type: array + :default: array('2') + +.. config:option:: $cfg['DefaultTransformations']['DateFormat'] + + :type: array + :default: array(0, '', 'local') + +.. config:option:: $cfg['DefaultTransformations']['Inline'] + + :type: array + :default: array('100', 100) + +.. config:option:: $cfg['DefaultTransformations']['TextImageLink'] + + :type: array + :default: array('', 100, 50) + +.. config:option:: $cfg['DefaultTransformations']['TextLink'] + + :type: array + :default: array('', '', '') + +Console settings +---------------- + +.. note:: + + These settings are mostly meant to be changed by user. + +.. config:option:: $cfg['Console']['StartHistory'] + + :type: boolean + :default: false + + Show query history at start + +.. config:option:: $cfg['Console']['AlwaysExpand'] + + :type: boolean + :default: false + + Always expand query messages + +.. config:option:: $cfg['Console']['CurrentQuery'] + + :type: boolean + :default: true + + Show current browsing query + +.. config:option:: $cfg['Console']['EnterExecutes'] + + :type: boolean + :default: false + + Execute queries on Enter and insert new line with Shift + Enter + +.. config:option:: $cfg['Console']['DarkTheme'] + + :type: boolean + :default: false + + Switch to dark theme + +.. config:option:: $cfg['Console']['Mode'] + + :type: string + :default: 'info' + + Console mode + +.. config:option:: $cfg['Console']['Height'] + + :type: integer + :default: 92 + + Console height + +Developer +--------- + +.. warning:: + + These settings might have huge effect on performance or security. + +.. config:option:: $cfg['DBG'] + + :type: array + :default: array(...) + +.. config:option:: $cfg['DBG']['sql'] + + :type: boolean + :default: false + + Enable logging queries and execution times to be + displayed in the console's Debug SQL tab. + +.. config:option:: $cfg['DBG']['sqllog'] + + :type: boolean + :default: false + + Enable logging of queries and execution times to the syslog. + Requires :config:option:`$cfg['DBG']['sql']` to be enabled. + +.. config:option:: $cfg['DBG']['demo'] + + :type: boolean + :default: false + + Enable to let server present itself as demo server. + This is used for `phpMyAdmin demo server `_. + + It currently changes following behavior: + + * There is welcome message on the main page. + * There is footer information about demo server and used git revision. + * The setup script is enabled even with existing configuration. + * The setup does not try to connect to the MySQL server. + +.. config:option:: $cfg['DBG']['simple2fa'] + + :type: boolean + :default: false + + Can be used for testing two-factor authentication using :ref:`simple2fa`. + +.. _config-examples: + +Examples +-------- + +See following configuration snippets for typical setups of phpMyAdmin. + +Basic example ++++++++++++++ + +Example configuration file, which can be copied to :file:`config.inc.php` to +get some core configuration layout; it is distributed with phpMyAdmin as +:file:`config.sample.inc.php`. Please note that it does not contain all +configuration options, only the most frequently used ones. + +.. literalinclude:: ../config.sample.inc.php + :language: php + +.. warning:: + + Don't use the controluser 'pma' if it does not yet exist and don't use 'pmapass' + as password. + +.. _example-signon: + +Example for signon authentication ++++++++++++++++++++++++++++++++++ + +This example uses :file:`examples/signon.php` to demonstrate usage of :ref:`auth_signon`: + +.. code-block:: php + + ` + +Example for IP address limited autologin +++++++++++++++++++++++++++++++++++++++++ + +If you want to automatically login when accessing phpMyAdmin locally while asking +for a password when accessing remotely, you can achieve it using following snippet: + +.. code-block:: php + + if ($_SERVER["REMOTE_ADDR"] == "127.0.0.1") { + $cfg['Servers'][$i]['auth_type'] = 'config'; + $cfg['Servers'][$i]['user'] = 'root'; + $cfg['Servers'][$i]['password'] = 'yourpassword'; + } else { + $cfg['Servers'][$i]['auth_type'] = 'cookie'; + } + +.. note:: + + Filtering based on IP addresses isn't reliable over the internet, use it + only for local address. + +Example for using multiple MySQL servers +++++++++++++++++++++++++++++++++++++++++ + +You can configure any number of servers using :config:option:`$cfg['Servers']`, +following example shows two of them: + +.. code-block:: php + + + +.. _example-google-ssl: + +Google Cloud SQL with SSL ++++++++++++++++++++++++++ + +To connect to Google Could SQL, you currently need to disable certificate +verification. This is caused by the certficate being issued for CN matching +your instance name, but you connect to an IP address and PHP tries to match +these two. With verfication you end up with error message like: + +.. code-block:: text + + Peer certificate CN=`api-project-851612429544:pmatest' did not match expected CN=`8.8.8.8' + +.. warning:: + + With disabled verification your traffic is encrypted, but you're open to + man in the middle attacks. + +To connect phpMyAdmin to Google Cloud SQL using SSL download the client and +server certificates and tell phpMyAdmin to use them: + +.. code-block:: php + + // IP address of your instance + $cfg['Servers'][$i]['host'] = '8.8.8.8'; + // Use SSL for connection + $cfg['Servers'][$i]['ssl'] = true; + // Client secret key + $cfg['Servers'][$i]['ssl_key'] = '../client-key.pem'; + // Client certificate + $cfg['Servers'][$i]['ssl_cert'] = '../client-cert.pem'; + // Server certification authority + $cfg['Servers'][$i]['ssl_ca'] = '../server-ca.pem'; + // Disable SSL verification (see above note) + $cfg['Servers'][$i]['ssl_verify'] = false; + +.. seealso:: + + :ref:`ssl`, + :config:option:`$cfg['Servers'][$i]['ssl']`, + :config:option:`$cfg['Servers'][$i]['ssl_key']`, + :config:option:`$cfg['Servers'][$i]['ssl_cert']`, + :config:option:`$cfg['Servers'][$i]['ssl_ca']`, + :config:option:`$cfg['Servers'][$i]['ssl_verify']`, + diff --git a/php/apps/phpmyadmin49/html/doc/html/_sources/copyright.txt b/php/apps/phpmyadmin49/html/doc/html/_sources/copyright.txt new file mode 100644 index 00000000..f5866e8b --- /dev/null +++ b/php/apps/phpmyadmin49/html/doc/html/_sources/copyright.txt @@ -0,0 +1,42 @@ +.. _copyright: + +Copyright +========= + +.. code-block:: none + + Copyright (C) 1998-2000 Tobias Ratschiller + Copyright (C) 2001-2018 Marc Delisle + Olivier Müller + Robin Johnson + Alexander M. Turek + Michal Čihař + Garvin Hicking + Michael Keck + Sebastian Mendel + [check credits for more details] + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License version 2, as +published by the Free Software Foundation. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . + +Third party licenses +++++++++++++++++++++ + +phpMyAdmin includes several third party libraries which come under their +respective licenses. + +jQuery's license, which is where we got the files under js/vendor/jquery/ is +(MIT|GPL), a copy of each license is available in this repository (GPL +is available as LICENSE, MIT as js/vendor/jquery/MIT-LICENSE.txt). + +The download kit additionally includes several composer libraries. See their +licensing information in the vendor/ directory. diff --git a/php/apps/phpmyadmin49/html/doc/html/_sources/credits.txt b/php/apps/phpmyadmin49/html/doc/html/_sources/credits.txt new file mode 100644 index 00000000..4dc828c5 --- /dev/null +++ b/php/apps/phpmyadmin49/html/doc/html/_sources/credits.txt @@ -0,0 +1,1043 @@ +.. _credits: + +Credits +======= + +Credits, in chronological order ++++++++++++++++++++++++++++++++ + +* Tobias Ratschiller + + * creator of the phpMyAdmin project + + * maintainer from 1998 to summer 2000 + +* Marc Delisle + + * multi-language version in December 1998 + + * various fixes and improvements + + * first version of the :term:`SQL` analyser (most of it) + + * maintainer from 2001 to 2015 + +* Olivier Müller + + * started SourceForge phpMyAdmin project in March 2001 + + * sync'ed different existing CVS trees with new features and bugfixes + + * multi-language improvements, dynamic language selection + + * many bugfixes and improvements + +* Loïc Chapeaux + + * rewrote and optimized JavaScript, DHTML and DOM stuff + + * rewrote the scripts so they fit the :term:`PEAR` coding standards and + generate XHTML1.0 and CSS2 compliant codes + + * improved the language detection system + + * many bugfixes and improvements + +* Robin Johnson + + * database maintenance controls + + * table type code + + * Host authentication :term:`IP` Allow/Deny + + * DB-based configuration (Not completed) + + * :term:`SQL` parser and pretty-printer + + * :term:`SQL` validator + + * many bugfixes and improvements + +* Armel Fauveau + + * bookmarks feature + + * multiple dump feature + + * gzip dump feature + + * zip dump feature + +* Geert Lund + + * various fixes + + * moderator of the phpMyAdmin former users forum at phpwizard.net + +* Korakot Chaovavanich + + * "insert as new row" feature + +* Pete Kelly + + * rewrote and fix dump code + + * bugfixes + +* Steve Alberty + + * rewrote dump code for PHP4 + + * mySQL table statistics + + * bugfixes + +* Benjamin Gandon + + * main author of the version 2.1.0.1 + + * bugfixes + +* Alexander M. Turek + + * MySQL 4.0 / 4.1 / 5.0 compatibility + + * abstract database interface (PMA\_DBI) with MySQLi support + + * privileges administration + + * :term:`XML` exports + + * various features and fixes + + * German language file updates + +* Mike Beck + + * automatic joins in QBE + + * links column in printview + + * Relation view + +* Michal Čihař + + * enhanced index creation/display feature + + * feature to use a different charset for HTML than for MySQL + + * improvements of export feature + + * various features and fixes + + * Czech language file updates + + * created current website for phpMyAdmin + +* Christophe Gesché from the "MySQL Form Generator for PHPMyAdmin" + (https://sourceforge.net/projects/phpmysqlformgen/) + + * suggested the patch for multiple table printviews + +* Garvin Hicking + + * built the patch for vertical display of table rows + + * built the Javascript based Query window + :term:`SQL` history + + * Improvement of column/db comments + + * (MIME)-Transformations for columns + + * Use custom alias names for Databases in left frame + + * hierarchical/nested table display + + * :term:`PDF`-scratchboard for WYSIWYG- + distribution of :term:`PDF` relations + + * new icon sets + + * vertical display of column properties page + + * some bugfixes, features, support, German language additions + +* Yukihiro Kawada + + * japanese kanji encoding conversion feature + +* Piotr Roszatycki and Dan Wilson + + * the Cookie authentication mode + +* Axel Sander + + * table relation-links feature + +* Maxime Delorme + + * :term:`PDF` schema output, thanks also to + Olivier Plathey for the "FPDF" library (see ), Steven + Wittens for the "UFPDF" library and + Nicola Asuni for the "TCPDF" library (see ). + +* Olof Edlund + + * :term:`SQL` validator server + +* Ivan R. Lanin + + * phpMyAdmin logo (until June 2004) + +* Mike Cochrane + + * blowfish library from the Horde project (withdrawn in release 4.0) + +* Marcel Tschopp + + * mysqli support + + * many bugfixes and improvements + +* Nicola Asuni (Tecnick.com) + + * TCPDF library () + +* Michael Keck + + * redesign for 2.6.0 + + * phpMyAdmin sailboat logo (June 2004) + +* Mathias Landhäußer + + * Representation at conferences + +* Sebastian Mendel + + * interface improvements + + * various bugfixes + +* Ivan A Kirillov + + * new relations Designer + +* Raj Kissu Rajandran (Google Summer of Code 2008) + + * BLOBstreaming support (withdrawn in release 4.0) + +* Piotr Przybylski (Google Summer of Code 2008, 2010 and 2011) + + * improved setup script + + * user preferences + + * Drizzle support + +* Derek Schaefer (Google Summer of Code 2009) + + * Improved the import system + +* Alexander Rutkowski (Google Summer of Code 2009) + + * Tracking mechanism + +* Zahra Naeem (Google Summer of Code 2009) + + * Synchronization feature (removed in release 4.0) + +* Tomáš Srnka (Google Summer of Code 2009) + + * Replication support + +* Muhammad Adnan (Google Summer of Code 2010) + + * Relation schema export to multiple formats + +* Lori Lee (Google Summer of Code 2010) + + * User interface improvements + + * ENUM/SET editor + + * Simplified interface for export/import + +* Ninad Pundalik (Google Summer of Code 2010) + + * AJAXifying the interface + +* Martynas Mickevičius (Google Summer of Code 2010) + + * Charts + +* Barrie Leslie + + * BLOBstreaming support with PBMS PHP extension (withdrawn in release + 4.0) + +* Ankit Gupta (Google Summer of Code 2010) + + * Visual query builder + +* Madhura Jayaratne (Google Summer of Code 2011) + + * OpenGIS support + +* Ammar Yasir (Google Summer of Code 2011) + + * Zoom search + +* Aris Feryanto (Google Summer of Code 2011) + + * Browse-mode improvements + +* Thilanka Kaushalya (Google Summer of Code 2011) + + * AJAXification + +* Tyron Madlener (Google Summer of Code 2011) + + * Query statistics and charts for the status page + +* Zarubin Stas (Google Summer of Code 2011) + + * Automated testing + +* Rouslan Placella (Google Summer of Code 2011 and 2012) + + * Improved support for Stored Routines, Triggers and Events + + * Italian translation updates + + * Removal of frames, new navigation + +* Dieter Adriaenssens + + * Various bugfixes + + * Dutch translation updates + +* Alex Marin (Google Summer of Code 2012) + + * New plugins and properties system + +* Thilina Buddika Abeyrathna (Google Summer of Code 2012) + + * Refactoring + +* Atul Pratap Singh (Google Summer of Code 2012) + + * Refactoring + +* Chanaka Indrajith (Google Summer of Code 2012) + + * Refactoring + +* Yasitha Pandithawatta (Google Summer of Code 2012) + + * Automated testing + +* Jim Wigginton (phpseclib.sourceforge.net) + + * phpseclib + +* Bin Zu (Google Summer of Code 2013) + + * Refactoring + +* Supun Nakandala (Google Summer of Code 2013) + + * Refactoring + +* Mohamed Ashraf (Google Summer of Code 2013) + + * AJAX error reporting + +* Adam Kang (Google Summer of Code 2013) + + * Automated testing + +* Ayush Chaudhary (Google Summer of Code 2013) + + * Automated testing + +* Kasun Chathuranga (Google Summer of Code 2013) + + * Interface improvements + +* Hugues Peccatte + + * Load/save query by example (database search bookmarks) + +* Smita Kumari (Google Summer of Code 2014) + + * Central list of columns + + * Improve table structure (normalization) + +* Ashutosh Dhundhara (Google Summer of Code 2014) + + * Interface improvements + +* Dhananjay Nakrani (Google Summer of Code 2014) + + * PHP error reporting + +* Edward Cheng (Google Summer of Code 2014) + + * SQL Query Console + +* Kankanamge Bimal Yashodha (Google Summer of Code 2014) + + * Refactoring: Designer/schema integration + +* Chirayu Chiripal (Google Summer of Code 2014) + + * Custom field handlers (Input based MIME transformations) + + * Export with table/column name changes + +* Dan Ungureanu (Google Summer of Code 2015) + + * New parser and analyzer + +* Nisarg Jhaveri (Google Summer of Code 2015) + + * Page-related settings + + * SQL debugging integration to the Console + + * Other UI improvements + +* Deven Bansod (Google Summer of Code 2015) + + * Print view using CSS + + * Other UI improvements and new features + +* Deven Bansod (Google Summer of Code 2017) + + * Improvements to the Error Reporting Server + + * Improved Selenium testing + +* Manish Bisht (Google Summer of Code 2017) + + * Mobile user interface + + * Remove inline JavaScript code + + * Other UI improvements + +* Raghuram Vadapalli (Google Summer of Code 2017) + + * Multi-table query interface + + * Allow Designer to work with tables from other databases + + * Other UI improvements + +And also to the following people who have contributed minor changes, +enhancements, bugfixes or support for a new language since version +2.1.0: + +Bora Alioglu, Ricardo ?, Sven-Erik Andersen, Alessandro Astarita, +Péter Bakondy, Borges Botelho, Olivier Bussier, Neil Darlow, Mats +Engstrom, Ian Davidson, Laurent Dhima, Kristof Hamann, Thomas Kläger, +Lubos Klokner, Martin Marconcini, Girish Nair, David Nordenberg, +Andreas Pauley, Bernard M. Piller, Laurent Haas, "Sakamoto", Yuval +Sarna, www.securereality.com.au, Alexis Soulard, Alvar Soome, Siu Sun, +Peter Svec, Michael Tacelosky, Rachim Tamsjadi, Kositer Uros, Luís V., +Martijn W. van der Lee, Algis Vainauskas, Daniel Villanueva, Vinay, +Ignacio Vazquez-Abrams, Chee Wai, Jakub Wilk, Thomas Michael +Winningham, Vilius Zigmantas, "Manuzhai". + +Translators ++++++++++++ + +Following people have contributed to translation of phpMyAdmin: + +* Albanian + + * Arben Çokaj + +* Arabic + + * Ahmed Saleh Abd El-Raouf Ismae + * Ahmed Saad + * hassan mokhtari + +* Armenian + + * Andrey Aleksanyants + +* Azerbaijani + + * Mircəlal <01youknowme\_at\_gmail.com> + * Huseyn + * Sevdimali İsa + * Jafar + +* Belarusian + + * Viktar Palstsiuk + +* Bulgarian + + * Boyan Kehayov + * Valter Georgiev + * Valentin Mladenov + * P + * krasimir + +* Catalan + + * josep constanti + * Xavier Navarro + +* Chinese (China) + + * Vincent Lau <3092849\_at\_qq.com> + * Zheng Dan + * disorderman + * Rex Lee + * + * popcorner + * Yizhou Qiang + * zz + * Terry Weng + * whh + +* Chinese (Taiwan) + + * Albert Song + * Chien Wei Lin + * Peter Dave Hello + +* Colognian + + * Purodha + +* Czech + + * Aleš Hakl + * Dalibor Straka + * Martin Vidner + * Ondra Šimeček + * Jan Palider + * Petr Kateřiňák + +* Danish + + * Aputsiaĸ Niels Janussen + * Dennis Jakobsen + * Jonas + * Claus Svalekjaer + +* Dutch + + * A. Voogt + * dingo thirteen + * Robin van der Vliet + * Dieter Adriaenssens + * Niko Strijbol + +* English (United Kingdom) + + * Dries Verschuere + * Francisco Rocha + * Marc Delisle + * Marek Tomaštík + +* Esperanto + + * Eliovir + * Robin van der Vliet + +* Estonian + + * Kristjan Räts + +* Finnish + + * Juha Remes + * Lari Oesch + +* French + + * Marc Delisle + +* Frisian + + * Robin van der Vliet + +* Galician + + * Xosé Calvo + +* German + + * Julian Ladisch + * Jan Erik Zassenhaus + * Lasse Goericke + * Matthias Bluthardt + * Michael Koch + * Ann + J.M. + * + * Phillip Rohmberger + * Hauke Henningsen + +* Greek + + * Παναγιώτης Παπάζογλου + +* Hebrew + + * Moshe Harush + * Yaron Shahrabani + * Eyal Visoker + +* Hindi + + * Atul Pratap Singh + * Yogeshwar + * Deven Bansod + * Kushagra Pandey + * Nisarg Jhaveri + * Roohan Kazi + * Yugal Pantola + +* Hungarian + + * Akos Eros + * Dániel Tóth + * Szász Attila + * Balázs Úr + +* Indonesian + + * Deky Arifianto + * Andika Triwidada + * Dadan Setia + * Dadan Setia + * Yohanes Edwin + * Fadhiil Rachman + * Benny + * Tommy Surbakti + * Zufar Fathi Suhardi + +* Interlingua + + * Giovanni Sora + +* Italian + + * Francesco Saverio Giacobazzi + * Marco Pozzato + * Stefano Martinelli + +* Japanese + + * k725 + * Hiroshi Chiyokawa + * Masahiko HISAKAWA + * worldwideskier + +* Kannada + + * Robin van der Vliet + * Shameem Ahmed A Mulla + +* Korean + + * Bumsoo Kim + * Kyeong Su Shin + * Dongyoung Kim + * Myung-han Yu + * JongDeok + * Yong Kim + * 이경준 + * Seongki Shin + * Yoon Bum-Jong + * Koo Youngmin + +* Kurdish Sorani + + * Alan Hilal + * Aso Naderi + * muhammad + * Zrng Abdulla + +* Latvian + + * Latvian TV + * Edgars Neimanis + * Ukko + +* Limburgish + + * Robin van der Vliet + +* Lithuanian + + * Vytautas Motuzas + +* Malay + + * Amir Hamzah + * diprofinfiniti + +* Nepali + + * Nabin Ghimire + +* Norwegian Bokmål + + * Børge Holm-Wennberg + * Tor Stokkan + * Espen Frøyshov + * Kurt Eilertsen + * Christoffer Haugom + * Sebastian + * Tomas + +* Persian + + * ashkan shirian + * HM + +* Polish + + * Andrzej + * Przemo + * Krystian Biesaga + * Maciej Gryniuk + * Michał VonFlynee + +* Portuguese + + * Alexandre Badalo + * João Rodrigues + * Pedro Ribeiro + * Sandro Amaral + +* Portuguese (Brazil) + + * Alex Rohleder + * bruno mendax + * Danilo GUia + * Douglas Rafael Morais Kollar + * Douglas Eccker + * Ed Jr + * Guilherme Souza Silva + * Guilherme Seibt + * Helder Santana + * Junior Zancan + * Luis + * Marcos Algeri + * Marc Delisle + * Renato Rodrigues de Lima Júnio + * Thiago Casotti + * Victor Laureano + * Vinícius Araújo + * Washington Bruno Rodrigues Cav + * Yan Gabriel + +* Punjabi + + * Robin van der Vliet + +* Romanian + + * Alex + * Costel Cocerhan + * Ion Adrian-Ionut + * Raul Molnar + * Deleted User + * Stefan Murariu + +* Russian + + * Andrey Aleksanyants + * + * Robin van der Vliet + * Хомутов Иван Сергеевич + * Alexey Rubinov + * Олег Карпов + * Egorov Artyom + +* Serbian + + * Smart Kid + +* Sinhala + + * Madhura Jayaratne + +* Slovak + + * Martin Lacina + * Patrik Kollmann + * Jozef Pistej + +* Slovenian + + * Domen + +* Spanish + + * Luis García Sevillano + * Franco + * Luis Ruiz + * Macofe + * Matías Bellone + * Rodrigo A. + * FAMMA TV NOTICIAS MEDIOS DE CO + * Ronnie Simon + +* Swedish + + * Anders Jonsson + +* Tamil + + * கணேஷ் குமார் + * Achchuthan Yogarajah + * Rifthy Ahmed + +* Thai + + * + * Somthanat W. + +* Turkish + + * Burak Yavuz + +* Ukrainian + + * Сергій Педько + * Igor + * Vitaliy Perekupka + +* Vietnamese + + * Bao Phan + * Xuan Hung + * Bao trinh minh + +* West Flemish + + * Robin van der Vliet + +Documentation translators ++++++++++++++++++++++++++ + +Following people have contributed to translation of phpMyAdmin documentation: + +* Albanian + + * Arben Çokaj + +* Arabic + + * Ahmed El Azzabi + * Omar Essam + +* Armenian + + * Andrey Aleksanyants + +* Azerbaijani + + * Mircəlal <01youknowme\_at\_gmail.com> + * Sevdimali İsa + +* Catalan + + * josep constanti + * Joan Montané + * Xavier Navarro + +* Chinese (China) + + * Vincent Lau <3092849\_at\_qq.com> + * 罗攀登 <6375lpd\_at\_gmail.com> + * disorderman + * ITXiaoPang + * tunnel213 + * Terry Weng + * whh + +* Chinese (Taiwan) + + * Chien Wei Lin + * Peter Dave Hello + +* Czech + + * Aleš Hakl + * Michal Čihař + * Jan Palider + * Petr Kateřiňák + +* Danish + + * Aputsiaĸ Niels Janussen + * Claus Svalekjaer + +* Dutch + + * A. Voogt + * dingo thirteen + * Dries Verschuere + * Robin van der Vliet + * Stefan Koolen + * Ray Borggreve + * Dieter Adriaenssens + * Tom Hofman + +* Estonian + + * Kristjan Räts + +* Finnish + + * Juha + +* French + + * Cédric Corazza + * Étienne Gilli + * Marc Delisle + * Donavan_Martin + +* Frisian + + * Robin van der Vliet + +* Galician + + * Xosé Calvo + +* German + + * Daniel + * JH M + * Lasse Goericke + * Michael Koch + * Ann + J.M. + * Niemand Jedermann + * Phillip Rohmberger + * Hauke Henningsen + +* Greek + + * Παναγιώτης Παπάζογλου + +* Hungarian + + * Balázs Úr + +* Italian + + * Francesco Saverio Giacobazzi + * Marco Pozzato + * Stefano Martinelli + * TWS + +* Japanese + + * Eshin Kunishima + * Hiroshi Chiyokawa + +* Lithuanian + + * Jur Kis + * Dovydas + +* Norwegian Bokmål + + * Tor Stokkan + * Kurt Eilertsen + +* Portuguese (Brazil) + + * Alexandre Moretti + * Douglas Rafael Morais Kollar + * Guilherme Seibt + * Helder Santana + * Michal Čihař + * Michel Souza + * Danilo Azevedo + * Thiago Casotti + * Vinícius Araújo + * Yan Gabriel + +* Slovak + + * Martin Lacina + * Michal Čihař + * Jozef Pistej + +* Slovenian + + * Domen + +* Spanish + + * Luis García Sevillano + * Franco + * Matías Bellone + * Ronnie Simon + +* Turkish + + * Burak Yavuz + +Original Credits of Version 2.1.0 ++++++++++++++++++++++++++++++++++ + +This work is based on Peter Kuppelwieser's MySQL-Webadmin. It was his +idea to create a web-based interface to MySQL using PHP3. Although I +have not used any of his source-code, there are some concepts I've +borrowed from him. phpMyAdmin was created because Peter told me he +wasn't going to further develop his (great) tool. + +Thanks go to + +* Amalesh Kempf who contributed the + code for the check when dropping a table or database. He also + suggested that you should be able to specify the primary key on + tbl\_create.php3. To version 1.1.1 he contributed the ldi\_\*.php3-set + (Import text-files) as well as a bug-report. Plus many smaller + improvements. +* Jan Legenhausen : He made many of the changes that + were introduced in 1.3.0 (including quite significant ones like the + authentication). For 1.4.1 he enhanced the table-dump feature. Plus + bug-fixes and help. +* Marc Delisle made phpMyAdmin + language-independent by outsourcing the strings to a separate file. He + also contributed the French translation. +* Alexandr Bravo who contributed + tbl\_select.php3, a feature to display only some columns from a table. +* Chris Jackson added support for MySQL functions + in tbl\_change.php3. He also added the "Query by Example" feature in + 2.0. +* Dave Walton added support for multiple + servers and is a regular contributor for bug-fixes. +* Gabriel Ash contributed the random access + features for 2.0.6. + +The following people have contributed minor changes, enhancements, +bugfixes or support for a new language: + +Jim Kraai, Jordi Bruguera, Miquel Obrador, Geert Lund, Thomas +Kleemann, Alexander Leidinger, Kiko Albiol, Daniel C. Chao, Pavel +Piankov, Sascha Kettler, Joe Pruett, Renato Lins, Mark Kronsbein, +Jannis Hermanns, G. Wieggers. + +And thanks to everyone else who sent me email with suggestions, bug- +reports and or just some feedback. diff --git a/php/apps/phpmyadmin49/html/doc/html/_sources/developers.txt b/php/apps/phpmyadmin49/html/doc/html/_sources/developers.txt new file mode 100644 index 00000000..2b2f5868 --- /dev/null +++ b/php/apps/phpmyadmin49/html/doc/html/_sources/developers.txt @@ -0,0 +1,12 @@ +.. _developers: + +Developers Information +====================== + +phpMyAdmin is Open Source, so you're invited to contribute to it. Many +great features have been written by other people and you too can help +to make phpMyAdmin a useful tool. + +You can check out all the possibilities to contribute in the +`contribute section on our website +`_. diff --git a/php/apps/phpmyadmin49/html/doc/html/_sources/faq.txt b/php/apps/phpmyadmin49/html/doc/html/_sources/faq.txt new file mode 100644 index 00000000..47506e13 --- /dev/null +++ b/php/apps/phpmyadmin49/html/doc/html/_sources/faq.txt @@ -0,0 +1,2258 @@ +.. _faq: + +FAQ - Frequently Asked Questions +================================ + +Please have a look at our `Link section +`_ on the official +phpMyAdmin homepage for in-depth coverage of phpMyAdmin's features and +or interface. + +.. _faqserver: + +Server +++++++ + +.. _faq1_1: + +1.1 My server is crashing each time a specific action is required or phpMyAdmin sends a blank page or a page full of cryptic characters to my browser, what can I do? +--------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +Try to set the :config:option:`$cfg['OBGzip']` directive to ``false`` in your +:file:`config.inc.php` file and the ``zlib.output_compression`` directive to +``Off`` in your php configuration file. + +.. _faq1_2: + +1.2 My Apache server crashes when using phpMyAdmin. +--------------------------------------------------- + +You should first try the latest versions of Apache (and possibly MySQL). If +your server keeps crashing, please ask for help in the various Apache support +groups. + +.. seealso:: :ref:`faq1_1` + +.. _faq1_3: + +1.3 (withdrawn). +---------------- + +.. _faq1_4: + +1.4 Using phpMyAdmin on IIS, I'm displayed the error message: "The specified CGI application misbehaved by not returning a complete set of HTTP headers ...". +------------------------------------------------------------------------------------------------------------------------------------------------------------- + +You just forgot to read the *install.txt* file from the PHP +distribution. Have a look at the last message in this `PHP bug report #12061 +`_ from the official PHP bug +database. + +.. _faq1_5: + +1.5 Using phpMyAdmin on IIS, I'm facing crashes and/or many error messages with the HTTP. +----------------------------------------------------------------------------------------- + +This is a known problem with the PHP :term:`ISAPI` filter: it's not so stable. +Please use instead the cookie authentication mode. + +.. _faq1_6: + +1.6 I can't use phpMyAdmin on PWS: nothing is displayed! +-------------------------------------------------------- + +This seems to be a PWS bug. Filippo Simoncini found a workaround (at +this time there is no better fix): remove or comment the ``DOCTYPE`` +declarations (2 lines) from the scripts :file:`libraries/Header.class.php` +and :file:`index.php`. + +.. _faq1_7: + +1.7 How can I gzip a dump or a CSV export? It does not seem to work. +-------------------------------------------------------------------- + +This feature is based on the ``gzencode()`` +PHP function to be more independent of the platform (Unix/Windows, +Safe Mode or not, and so on). So, you must have Zlib support +(``--with-zlib``). + +.. _faq1_8: + +1.8 I cannot insert a text file in a table, and I get an error about safe mode being in effect. +----------------------------------------------------------------------------------------------- + +Your uploaded file is saved by PHP in the "upload dir", as defined in +:file:`php.ini` by the variable ``upload_tmp_dir`` (usually the system +default is */tmp*). We recommend the following setup for Apache +servers running in safe mode, to enable uploads of files while being +reasonably secure: + +* create a separate directory for uploads: :command:`mkdir /tmp/php` +* give ownership to the Apache server's user.group: :command:`chown + apache.apache /tmp/php` +* give proper permission: :command:`chmod 600 /tmp/php` +* put ``upload_tmp_dir = /tmp/php`` in :file:`php.ini` +* restart Apache + +.. _faq1_9: + +1.9 (withdrawn). +---------------- + +.. _faq1_10: + +1.10 I'm having troubles when uploading files with phpMyAdmin running on a secure server. My browser is Internet Explorer and I'm using the Apache server. +---------------------------------------------------------------------------------------------------------------------------------------------------------- + +As suggested by "Rob M" in the phpWizard forum, add this line to your +*httpd.conf*: + +.. code-block:: apache + + SetEnvIf User-Agent ".*MSIE.*" nokeepalive ssl-unclean-shutdown + +It seems to clear up many problems between Internet Explorer and SSL. + +.. _faq1_11: + +1.11 I get an 'open\_basedir restriction' while uploading a file from the import tab. +------------------------------------------------------------------------------------- + +Since version 2.2.4, phpMyAdmin supports servers with open\_basedir +restrictions. However you need to create temporary directory and configure it +as :config:option:`$cfg['TempDir']`. The uploaded files will be moved there, +and after execution of your :term:`SQL` commands, removed. + +.. _faq1_12: + +1.12 I have lost my MySQL root password, what can I do? +------------------------------------------------------- + +phpMyAdmin does authenticate against MySQL server you're using, so to recover +from phpMyAdmin password loss, you need to recover at MySQL level. + +The MySQL manual explains how to `reset the permissions +`_. + +If you are using MySQL server installed by your hosting provider, please +contact their support to recover the password for you. + +.. _faq1_13: + +1.13 (withdrawn). +----------------- + +.. _faq1_14: + +1.14 (withdrawn). +----------------- + +.. _faq1_15: + +1.15 I have problems with *mysql.user* column names. +---------------------------------------------------- + +In previous MySQL versions, the ``User`` and ``Password`` columns were +named ``user`` and ``password``. Please modify your column names to +align with current standards. + +.. _faq1_16: + +1.16 I cannot upload big dump files (memory, HTTP or timeout problems). +----------------------------------------------------------------------- + +Starting with version 2.7.0, the import engine has been re–written and +these problems should not occur. If possible, upgrade your phpMyAdmin +to the latest version to take advantage of the new import features. + +The first things to check (or ask your host provider to check) are the values +of ``max_execution_time``, ``upload_max_filesize``, ``memory_limit`` and +``post_max_size`` in the :file:`php.ini` configuration file. All of these three +settings limit the maximum size of data that can be submitted and handled by +PHP. Please note that ``post_max_size`` needs to be larger than +``upload_max_filesize``. There exist several workarounds if your upload is too +big or your hosting provider is unwilling to change the settings: + +* Look at the :config:option:`$cfg['UploadDir']` feature. This allows one to upload a file to the server + via scp, ftp, or your favorite file transfer method. PhpMyAdmin is + then able to import the files from the temporary directory. More + information is available in the :ref:`config` of this document. +* Using a utility (such as `BigDump + `_) to split the files before + uploading. We cannot support this or any third party applications, but + are aware of users having success with it. +* If you have shell (command line) access, use MySQL to import the files + directly. You can do this by issuing the "source" command from within + MySQL: + + .. code-block:: mysql + + source filename.sql; + +.. _faq1_17: + +1.17 Which Database versions does phpMyAdmin support? +----------------------------------------------------- + +For `MySQL `_, versions 5.5 and newer are supported. +For older MySQL versions, our `Downloads `_ page offers older phpMyAdmin versions +(which may have become unsupported). + +For `MariaDB `_, versions 5.5 and newer are supported. + +.. _faq1_17a: + +1.17a I cannot connect to the MySQL server. It always returns the error message, "Client does not support authentication protocol requested by server; consider upgrading MySQL client" +--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +You tried to access MySQL with an old MySQL client library. The +version of your MySQL client library can be checked in your phpinfo() +output. In general, it should have at least the same minor version as +your server - as mentioned in :ref:`faq1_17`. This problem is +generally caused by using MySQL version 4.1 or newer. MySQL changed +the authentication hash and your PHP is trying to use the old method. +The proper solution is to use the `mysqli extension +`_ with the proper client library to match +your MySQL installation. More +information (and several workarounds) are located in the `MySQL +Documentation `_. + +.. _faq1_18: + +1.18 (withdrawn). +----------------- + +.. _faq1_19: + +1.19 I can't run the "display relations" feature because the script seems not to know the font face I'm using! +-------------------------------------------------------------------------------------------------------------- + +The :term:`TCPDF` library we're using for this feature requires some special +files to use font faces. Please refers to the `TCPDF manual +`_ to build these files. + +.. _faqmysql: + +1.20 I receive an error about missing mysqli and mysql extensions. +------------------------------------------------------------------ + +To connect to a MySQL server, PHP needs a set of MySQL functions +called "MySQL extension". This extension may be part of the PHP +distribution (compiled-in), otherwise it needs to be loaded +dynamically. Its name is probably *mysqli.so* or *php\_mysqli.dll*. +phpMyAdmin tried to load the extension but failed. Usually, the +problem is solved by installing a software package called "PHP-MySQL" +or something similar. + +There are currently two interfaces PHP provides as MySQL extensions - ``mysql`` +and ``mysqli``. The ``mysqli`` is tried first, because it's the best one. + +This problem can be also caused by wrong paths in the :file:`php.ini` or using +wrong :file:`php.ini`. + +Make sure that the extension files do exist in the folder which the +``extension_dir`` points to and that the corresponding lines in your +:file:`php.ini` are not commented out (you can use ``phpinfo()`` to check +current setup): + +.. code-block:: ini + + [PHP] + + ; Directory in which the loadable extensions (modules) reside. + extension_dir = "C:/Apache2/modules/php/ext" + +The :file:`php.ini` can be loaded from several locations (especially on +Windows), so please check you're updating the correct one. If using Apache, you +can tell it to use specific path for this file using ``PHPIniDir`` directive: + +.. code-block:: apache + + LoadFile "C:/php/php5ts.dll" + LoadModule php5_module "C:/php/php5apache2_2.dll" + + PHPIniDir "C:/PHP" + + AddType text/html .php + AddHandler application/x-httpd-php .php + + + +In some rare cases this problem can be also caused by other extensions loaded +in PHP which prevent MySQL extensions to be loaded. If anything else fails, you +can try commenting out extensions for other databses from :file:`php.ini`. + +.. _faq1_21: + +1.21 I am running the CGI version of PHP under Unix, and I cannot log in using cookie auth. +------------------------------------------------------------------------------------------- + +In :file:`php.ini`, set ``mysql.max_links`` higher than 1. + +.. _faq1_22: + +1.22 I don't see the "Location of text file" field, so I cannot upload. +----------------------------------------------------------------------- + +This is most likely because in :file:`php.ini`, your ``file_uploads`` +parameter is not set to "on". + +.. _faq1_23: + +1.23 I'm running MySQL on a Win32 machine. Each time I create a new table the table and column names are changed to lowercase! +------------------------------------------------------------------------------------------------------------------------------ + +This happens because the MySQL directive ``lower_case_table_names`` +defaults to 1 (``ON``) in the Win32 version of MySQL. You can change +this behavior by simply changing the directive to 0 (``OFF``): Just +edit your ``my.ini`` file that should be located in your Windows +directory and add the following line to the group [mysqld]: + +.. code-block:: ini + + set-variable = lower_case_table_names=0 + +.. note:: + + Forcing this variable to 0 with --lower-case-table-names=0 on a + case-insensitive filesystem and access MyISAM tablenames using different + lettercases, index corruption may result. + +Next, save the file and restart the MySQL service. You can always +check the value of this directive using the query + +.. code-block:: mysql + + SHOW VARIABLES LIKE 'lower_case_table_names'; + +.. seealso:: `Identifier Case Sensitivity in the MySQL Reference Manual `_ + +.. _faq1_24: + +1.24 (withdrawn). +----------------- + +.. _faq1_25: + +1.25 I am running Apache with mod\_gzip-1.3.26.1a on Windows XP, and I get problems, such as undefined variables when I run a SQL query. +---------------------------------------------------------------------------------------------------------------------------------------- + +A tip from Jose Fandos: put a comment on the following two lines in +httpd.conf, like this: + +.. code-block:: apache + + # mod_gzip_item_include file \.php$ + # mod_gzip_item_include mime "application/x-httpd-php.*" + +as this version of mod\_gzip on Apache (Windows) has problems handling +PHP scripts. Of course you have to restart Apache. + +.. _faq1_26: + +1.26 I just installed phpMyAdmin in my document root of IIS but I get the error "No input file specified" when trying to run phpMyAdmin. +---------------------------------------------------------------------------------------------------------------------------------------- + +This is a permission problem. Right-click on the phpmyadmin folder and +choose properties. Under the tab Security, click on "Add" and select +the user "IUSR\_machine" from the list. Now set his permissions and it +should work. + +.. _faq1_27: + +1.27 I get empty page when I want to view huge page (eg. db\_structure.php with plenty of tables). +-------------------------------------------------------------------------------------------------- + +This was caused by a `PHP bug `_ that occur when +GZIP output buffering is enabled. If you turn off it (by +:config:option:`$cfg['OBGzip']` in :file:`config.inc.php`), it should work. +This bug will has been fixed in PHP 5.0.0. + +.. _faq1_28: + +1.28 My MySQL server sometimes refuses queries and returns the message 'Errorcode: 13'. What does this mean? +------------------------------------------------------------------------------------------------------------ + +This can happen due to a MySQL bug when having database / table names +with upper case characters although ``lower_case_table_names`` is +set to 1. To fix this, turn off this directive, convert all database +and table names to lower case and turn it on again. Alternatively, +there's a bug-fix available starting with MySQL 3.23.56 / +4.0.11-gamma. + +.. _faq1_29: + +1.29 When I create a table or modify a column, I get an error and the columns are duplicated. +--------------------------------------------------------------------------------------------- + +It is possible to configure Apache in such a way that PHP has problems +interpreting .php files. + +The problems occur when two different (and conflicting) set of +directives are used: + +.. code-block:: apache + + SetOutputFilter PHP + SetInputFilter PHP + +and + +.. code-block:: apache + + AddType application/x-httpd-php .php + +In the case we saw, one set of directives was in +``/etc/httpd/conf/httpd.conf``, while the other set was in +``/etc/httpd/conf/addon-modules/php.conf``. The recommended way is +with ``AddType``, so just comment out the first set of lines and +restart Apache: + +.. code-block:: apache + + #SetOutputFilter PHP + #SetInputFilter PHP + +.. _faq1_30: + +1.30 I get the error "navigation.php: Missing hash". +---------------------------------------------------- + +This problem is known to happen when the server is running Turck +MMCache but upgrading MMCache to version 2.3.21 solves the problem. + +.. _faq1_31: + +1.31 Which PHP versions does phpMyAdmin support? +------------------------------------------------ + +Since release 4.5, phpMyAdmin supports only PHP 5.5 and newer. Since release +4.1 phpMyAdmin supports only PHP 5.3 and newer. For PHP 5.2 you can use 4.0.x +releases. + +PHP 7 is supported since phpMyAdmin 4.6, PHP 7.1 is supported since 4.6.5, +PHP 7.2 is supported since 4.7.4. + +phpMyAdmin also works fine with HHVM. + +.. _faq1_32: + +1.32 Can I use HTTP authentication with IIS? +-------------------------------------------- + +Yes. This procedure was tested with phpMyAdmin 2.6.1, PHP 4.3.9 in +:term:`ISAPI` mode under :term:`IIS` 5.1. + +#. In your :file:`php.ini` file, set ``cgi.rfc2616_headers = 0`` +#. In ``Web Site Properties -> File/Directory Security -> Anonymous + Access`` dialog box, check the ``Anonymous access`` checkbox and + uncheck any other checkboxes (i.e. uncheck ``Basic authentication``, + ``Integrated Windows authentication``, and ``Digest`` if it's + enabled.) Click ``OK``. +#. In ``Custom Errors``, select the range of ``401;1`` through ``401;5`` + and click the ``Set to Default`` button. + +.. seealso:: :rfc:`2616` + +.. _faq1_33: + +1.33 (withdrawn). +----------------- + +.. _faq1_34: + +1.34 Can I access directly to database or table pages? +------------------------------------------------------ + +Yes. Out of the box, you can use :term:`URL` like +``http://server/phpMyAdmin/index.php?server=X&db=database&table=table&target=script``. +For ``server`` you use the server number +which refers to the order of the server paragraph in +:file:`config.inc.php`. Table and script parts are optional. If you want +``http://server/phpMyAdmin/database[/table][/script]`` :term:`URL`, you need to do some configuration. Following +lines apply only for `Apache `_ web server. +First make sure, that you have enabled some features within global +configuration. You need ``Options SymLinksIfOwnerMatch`` and ``AllowOverride +FileInfo`` enabled for directory where phpMyAdmin is installed and you +need mod\_rewrite to be enabled. Then you just need to create +following :term:`.htaccess` file in root folder of phpMyAdmin installation (don't +forget to change directory name inside of it): + +.. code-block:: apache + + RewriteEngine On + RewriteBase /path_to_phpMyAdmin + RewriteRule ^([a-zA-Z0-9_]+)/([a-zA-Z0-9_]+)/([a-z_]+\.php)$ index.php?db=$1&table=$2&target=$3 [R] + RewriteRule ^([a-zA-Z0-9_]+)/([a-z_]+\.php)$ index.php?db=$1&target=$2 [R] + RewriteRule ^([a-zA-Z0-9_]+)/([a-zA-Z0-9_]+)$ index.php?db=$1&table=$2 [R] + RewriteRule ^([a-zA-Z0-9_]+)$ index.php?db=$1 [R] + +.. _faq1_35: + +1.35 Can I use HTTP authentication with Apache CGI? +--------------------------------------------------- + +Yes. However you need to pass authentication variable to :term:`CGI` using +following rewrite rule: + +.. code-block:: apache + + RewriteEngine On + RewriteRule .* - [E=REMOTE_USER:%{HTTP:Authorization},L] + +.. _faq1_36: + +1.36 I get an error "500 Internal Server Error". +------------------------------------------------ + +There can be many explanations to this and a look at your server's +error log file might give a clue. + +.. _faq1_37: + +1.37 I run phpMyAdmin on cluster of different machines and password encryption in cookie auth doesn't work. +----------------------------------------------------------------------------------------------------------- + +If your cluster consist of different architectures, PHP code used for +encryption/decryption won't work correct. This is caused by use of +pack/unpack functions in code. Only solution is to use mcrypt +extension which works fine in this case. + +.. _faq1_38: + +1.38 Can I use phpMyAdmin on a server on which Suhosin is enabled? +------------------------------------------------------------------ + +Yes but the default configuration values of Suhosin are known to cause +problems with some operations, for example editing a table with many +columns and no :term:`primary key` or with textual :term:`primary key`. + +Suhosin configuration might lead to malfunction in some cases and it +can not be fully avoided as phpMyAdmin is kind of application which +needs to transfer big amounts of columns in single HTTP request, what +is something what Suhosin tries to prevent. Generally all +``suhosin.request.*``, ``suhosin.post.*`` and ``suhosin.get.*`` +directives can have negative effect on phpMyAdmin usability. You can +always find in your error logs which limit did cause dropping of +variable, so you can diagnose the problem and adjust matching +configuration variable. + +The default values for most Suhosin configuration options will work in +most scenarios, however you might want to adjust at least following +parameters: + +* `suhosin.request.max\_vars `_ should + be increased (eg. 2048) +* `suhosin.post.max\_vars `_ should be + increased (eg. 2048) +* `suhosin.request.max\_array\_index\_length `_ + should be increased (eg. 256) +* `suhosin.post.max\_array\_index\_length `_ + should be increased (eg. 256) +* `suhosin.request.max\_totalname\_length `_ + should be increased (eg. 8192) +* `suhosin.post.max\_totalname\_length `_ should be + increased (eg. 8192) +* `suhosin.get.max\_value\_length `_ + should be increased (eg. 1024) +* `suhosin.sql.bailout\_on\_error `_ + needs to be disabled (the default) +* `suhosin.log.\* `_ should not + include :term:`SQL`, otherwise you get big + slowdown +* `suhosin.sql.union `_ must be disabled (which is the default). +* `suhosin.sql.multiselect `_ must be disabled (which is the default). +* `suhosin.sql.comment `_ must be disabled (which is the default). + +To further improve security, we also recommend these modifications: + +* `suhosin.executor.include.max\_traversal `_ should be + enabled as a mitigation against local file inclusion attacks. We suggest + setting this to 2 as ``../`` is used with the ReCaptcha library. +* `suhosin.cookie.encrypt `_ should be enabled. +* `suhosin.executor.disable_emodifier `_ should be enabled. + +You can also disable the warning using the :config:option:`$cfg['SuhosinDisableWarning']`. + +.. _faq1_39: + +1.39 When I try to connect via https, I can log in, but then my connection is redirected back to http. What can cause this behavior? +------------------------------------------------------------------------------------------------------------------------------------ + +This is caused by the fact that PHP scripts have no knowledge that the site is +using https. Depending on used webserver, you should configure it to let PHP +know about URL and scheme used to access it. + +For example in Apache ensure that you have enabled ``SSLOptions`` and +``StdEnvVars`` in the configuration. + +.. seealso:: + +.. _faq1_40: + +1.40 When accessing phpMyAdmin via an Apache reverse proxy, cookie login does not work. +--------------------------------------------------------------------------------------- + +To be able to use cookie auth Apache must know that it has to rewrite +the set-cookie headers. Example from the Apache 2.2 documentation: + +.. code-block:: apache + + ProxyPass /mirror/foo/ http://backend.example.com/ + ProxyPassReverse /mirror/foo/ http://backend.example.com/ + ProxyPassReverseCookieDomain backend.example.com public.example.com + ProxyPassReverseCookiePath / /mirror/foo/ + +Note: if the backend url looks like ``http://server/~user/phpmyadmin``, the +tilde (~) must be url encoded as %7E in the ProxyPassReverse\* lines. +This is not specific to phpmyadmin, it's just the behavior of Apache. + +.. code-block:: apache + + ProxyPass /mirror/foo/ http://backend.example.com/~user/phpmyadmin + ProxyPassReverse /mirror/foo/ http://backend.example.com/%7Euser/phpmyadmin + ProxyPassReverseCookiePath /%7Euser/phpmyadmin /mirror/foo + +.. seealso:: , :config:option:`$cfg['PmaAbsoluteUri']` + +.. _faq1_41: + +1.41 When I view a database and ask to see its privileges, I get an error about an unknown column. +-------------------------------------------------------------------------------------------------- + +The MySQL server's privilege tables are not up to date, you need to +run the :command:`mysql_upgrade` command on the server. + +.. _faq1_42: + +1.42 How can I prevent robots from accessing phpMyAdmin? +-------------------------------------------------------- + +You can add various rules to :term:`.htaccess` to filter access based on user agent +field. This is quite easy to circumvent, but could prevent at least +some robots accessing your installation. + +.. code-block:: apache + + RewriteEngine on + + # Allow only GET and POST verbs + RewriteCond %{REQUEST_METHOD} !^(GET|POST)$ [NC,OR] + + # Ban Typical Vulnerability Scanners and others + # Kick out Script Kiddies + RewriteCond %{HTTP_USER_AGENT} ^(java|curl|wget).* [NC,OR] + RewriteCond %{HTTP_USER_AGENT} ^.*(libwww-perl|curl|wget|python|nikto|wkito|pikto|scan|acunetix).* [NC,OR] + RewriteCond %{HTTP_USER_AGENT} ^.*(winhttp|HTTrack|clshttp|archiver|loader|email|harvest|extract|grab|miner).* [NC,OR] + + # Ban Search Engines, Crawlers to your administrative panel + # No reasons to access from bots + # Ultimately Better than the useless robots.txt + # Did google respect robots.txt? + # Try google: intitle:phpMyAdmin intext:"Welcome to phpMyAdmin *.*.*" intext:"Log in" -wiki -forum -forums -questions intext:"Cookies must be enabled" + RewriteCond %{HTTP_USER_AGENT} ^.*(AdsBot-Google|ia_archiver|Scooter|Ask.Jeeves|Baiduspider|Exabot|FAST.Enterprise.Crawler|FAST-WebCrawler|www\.neomo\.de|Gigabot|Mediapartners-Google|Google.Desktop|Feedfetcher-Google|Googlebot|heise-IT-Markt-Crawler|heritrix|ibm.com\cs/crawler|ICCrawler|ichiro|MJ12bot|MetagerBot|msnbot-NewsBlogs|msnbot|msnbot-media|NG-Search|lucene.apache.org|NutchCVS|OmniExplorer_Bot|online.link.validator|psbot0|Seekbot|Sensis.Web.Crawler|SEO.search.Crawler|Seoma.\[SEO.Crawler\]|SEOsearch|Snappy|www.urltrends.com|www.tkl.iis.u-tokyo.ac.jp/~crawler|SynooBot|crawleradmin.t-info@telekom.de|TurnitinBot|voyager|W3.SiteSearch.Crawler|W3C-checklink|W3C_Validator|www.WISEnutbot.com|yacybot|Yahoo-MMCrawler|Yahoo\!.DE.Slurp|Yahoo\!.Slurp|YahooSeeker).* [NC] + RewriteRule .* - [F] + +.. _faq1_43: + +1.43 Why can't I display the structure of my table containing hundreds of columns? +---------------------------------------------------------------------------------- + +Because your PHP's ``memory_limit`` is too low; adjust it in :file:`php.ini`. + +.. _faq1:44: + +1.44 How can I reduce the installed size of phpMyAdmin on disk? +--------------------------------------------------------------- + +Some users have requested to be able to reduce the size of the phpMyAdmin installation. +This is not recommended and could lead to confusion over missing features, but can be done. +A list of files and corresponding functionality which degrade gracefully when removed include: + +* :file:`./vendor/tecnickcom/tcpdf` folder (exporting to PDF) +* :file:`./locale/` folder, or unused subfolders (interface translations) +* Any unused themes in :file:`./themes/` +* :file:`./js/vendor/jquery/src/` (included for licensing reasons) +* :file:`./js/line_counts.php` (removed in phpMyAdmin 4.8) +* :file:`./doc/` (documentation) +* :file:`./setup/` (setup script) +* :file:`./examples/` +* :file:`./sql/` (SQL scripts to configure advanced functionality) +* :file:`./js/vendor/openlayers/` (GIS visualization) + +.. _faqconfig: + +Configuration ++++++++++++++ + +.. _faq2_1: + +2.1 The error message "Warning: Cannot add header information - headers already sent by ..." is displayed, what's the problem? +------------------------------------------------------------------------------------------------------------------------------ + +Edit your :file:`config.inc.php` file and ensure there is nothing (I.E. no +blank lines, no spaces, no characters...) neither before the ```` tag at the end. + +.. _faq2_2: + +2.2 phpMyAdmin can't connect to MySQL. What's wrong? +---------------------------------------------------- + +Either there is an error with your PHP setup or your username/password +is wrong. Try to make a small script which uses mysql\_connect and see +if it works. If it doesn't, it may be you haven't even compiled MySQL +support into PHP. + +.. _faq2_3: + +2.3 The error message "Warning: MySQL Connection Failed: Can't connect to local MySQL server through socket '/tmp/mysql.sock' (111) ..." is displayed. What can I do? +--------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +The error message can also be: :guilabel:`Error #2002 - The server is not +responding (or the local MySQL server's socket is not correctly configured)`. + +First, you need to determine what socket is being used by MySQL. To do this, +connect to your server and go to the MySQL bin directory. In this directory +there should be a file named *mysqladmin*. Type ``./mysqladmin variables``, and +this should give you a bunch of info about your MySQL server, including the +socket (*/tmp/mysql.sock*, for example). You can also ask your ISP for the +connection info or, if you're hosting your own, connect from the 'mysql' +command-line client and type 'status' to get the connection type and socket or +port number. + +Then, you need to tell PHP to use this socket. You can do this for all PHP in +the :file:`php.ini` or for phpMyAdmin only in the :file:`config.inc.php`. For +example: :config:option:`$cfg['Servers'][$i]['socket']` Please also make sure +that the permissions of this file allow to be readable by your webserver. + +On my RedHat-Box the socket of MySQL is */var/lib/mysql/mysql.sock*. +In your :file:`php.ini` you will find a line + +.. code-block:: ini + + mysql.default_socket = /tmp/mysql.sock + +change it to + +.. code-block:: ini + + mysql.default_socket = /var/lib/mysql/mysql.sock + +Then restart apache and it will work. + +Have also a look at the `corresponding section of the MySQL +documentation `_. + +.. _faq2_4: + +2.4 Nothing is displayed by my browser when I try to run phpMyAdmin, what can I do? +----------------------------------------------------------------------------------- + +Try to set the :config:option:`$cfg['OBGzip']` directive to ``false`` in the phpMyAdmin configuration +file. It helps sometime. Also have a look at your PHP version number: +if it contains "b" or "alpha" it means you're running a testing +version of PHP. That's not a so good idea, please upgrade to a plain +revision. + +.. _faq2_5: + +2.5 Each time I want to insert or change a row or drop a database or a table, an error 404 (page not found) is displayed or, with HTTP or cookie authentication, I'm asked to log in again. What's wrong? +--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +Check your webserver setup if it correctly fills in either PHP_SELF or REQUEST_URI variables. + +If you are running phpMyAdmin behind reverse proxy, please set the +:config:option:`$cfg['PmaAbsoluteUri']` directive in the phpMyAdmin +configuration file to match your setup. + +.. _faq2_6: + +2.6 I get an "Access denied for user: 'root@localhost' (Using password: YES)"-error when trying to access a MySQL-Server on a host which is port-forwarded for my localhost. +---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +When you are using a port on your localhost, which you redirect via +port-forwarding to another host, MySQL is not resolving the localhost +as expected. Erik Wasser explains: The solution is: if your host is +"localhost" MySQL (the command line tool :command:`mysql` as well) always +tries to use the socket connection for speeding up things. And that +doesn't work in this configuration with port forwarding. If you enter +"127.0.0.1" as hostname, everything is right and MySQL uses the +:term:`TCP` connection. + +.. _faqthemes: + +2.7 Using and creating themes +----------------------------- + +See :ref:`themes`. + +.. _faqmissingparameters: + +2.8 I get "Missing parameters" errors, what can I do? +----------------------------------------------------- + +Here are a few points to check: + +* In :file:`config.inc.php`, try to leave the :config:option:`$cfg['PmaAbsoluteUri']` directive empty. See also + :ref:`faq4_7`. +* Maybe you have a broken PHP installation or you need to upgrade your + Zend Optimizer. See . +* If you are using Hardened PHP with the ini directive + ``varfilter.max_request_variables`` set to the default (200) or + another low value, you could get this error if your table has a high + number of columns. Adjust this setting accordingly. (Thanks to Klaus + Dorninger for the hint). +* In the :file:`php.ini` directive ``arg_separator.input``, a value of ";" + will cause this error. Replace it with "&;". +* If you are using `Suhosin `_, you + might want to increase `request limits `_. +* The directory specified in the :file:`php.ini` directive + ``session.save_path`` does not exist or is read-only (this can be caused + by `bug in the PHP installer `_). + +.. _faq2_9: + +2.9 Seeing an upload progress bar +--------------------------------- + +To be able to see a progress bar during your uploads, your server must +have the `APC `_ extension, the +`uploadprogress `_ one, or +you must be running PHP 5.4.0 or higher. Moreover, the JSON extension +has to be enabled in your PHP. + +If using APC, you must set ``apc.rfc1867`` to ``on`` in your :file:`php.ini`. + +If using PHP 5.4.0 or higher, you must set +``session.upload_progress.enabled`` to ``1`` in your :file:`php.ini`. However, +starting from phpMyAdmin version 4.0.4, session-based upload progress has +been temporarily deactivated due to its problematic behavior. + +.. seealso:: :rfc:`1867` + +.. _faqlimitations: + +Known limitations ++++++++++++++++++ + +.. _login_bug: + +3.1 When using HTTP authentication, a user who logged out can not log in again in with the same nick. +----------------------------------------------------------------------------------------------------- + +This is related to the authentication mechanism (protocol) used by +phpMyAdmin. To bypass this problem: just close all the opened browser +windows and then go back to phpMyAdmin. You should be able to log in +again. + +.. _faq3_2: + +3.2 When dumping a large table in compressed mode, I get a memory limit error or a time limit error. +---------------------------------------------------------------------------------------------------- + +Compressed dumps are built in memory and because of this are limited +to php's memory limit. For gzip/bzip2 exports this can be overcome +since 2.5.4 using :config:option:`$cfg['CompressOnFly']` (enabled by default). +zip exports can not be handled this way, so if you need zip files for larger +dump, you have to use another way. + +.. _faq3_3: + +3.3 With InnoDB tables, I lose foreign key relationships when I rename a table or a column. +------------------------------------------------------------------------------------------- + +This is an InnoDB bug, see . + +.. _faq3_4: + +3.4 I am unable to import dumps I created with the mysqldump tool bundled with the MySQL server distribution. +------------------------------------------------------------------------------------------------------------- + +The problem is that older versions of ``mysqldump`` created invalid +comments like this: + +.. code-block:: mysql + + -- MySQL dump 8.22 + -- + -- Host: localhost Database: database + --------------------------------------------------------- + -- Server version 3.23.54 + +The invalid part of the code is the horizontal line made of dashes +that appears once in every dump created with mysqldump. If you want to +run your dump you have to turn it into valid MySQL. This means, you +have to add a whitespace after the first two dashes of the line or add +a # before it: ``-- -------------------------------------------------------`` or +``#---------------------------------------------------------`` + +.. _faq3_5: + +3.5 When using nested folders, multiple hierarchies are displayed in a wrong manner. +------------------------------------------------------------------------------------ + +Please note that you should not use the separating string multiple +times without any characters between them, or at the beginning/end of +your table name. If you have to, think about using another +TableSeparator or disabling that feature. + +.. seealso:: :config:option:`$cfg['NavigationTreeTableSeparator']` + +.. _faq3_6: + +3.6 (withdrawn). +----------------- + +.. _faq3_7: + +3.7 I have table with many (100+) columns and when I try to browse table I get series of errors like "Warning: unable to parse url". How can this be fixed? +----------------------------------------------------------------------------------------------------------------------------------------------------------- + +Your table neither have a :term:`primary key` nor an :term:`unique key`, so we must +use a long expression to identify this row. This causes problems to +parse\_url function. The workaround is to create a :term:`primary key` +or :term:`unique key`. + +.. _faq3_8: + +3.8 I cannot use (clickable) HTML-forms in columns where I put a MIME-Transformation onto! +------------------------------------------------------------------------------------------ + +Due to a surrounding form-container (for multi-row delete checkboxes), +no nested forms can be put inside the table where phpMyAdmin displays +the results. You can, however, use any form inside of a table if keep +the parent form-container with the target to tbl\_row\_delete.php and +just put your own input-elements inside. If you use a custom submit +input field, the form will submit itself to the displaying page again, +where you can validate the $HTTP\_POST\_VARS in a transformation. For +a tutorial on how to effectively use transformations, see our `Link +section `_ on the +official phpMyAdmin-homepage. + +.. _faq3_9: + +3.9 I get error messages when using "--sql\_mode=ANSI" for the MySQL server. +---------------------------------------------------------------------------- + +When MySQL is running in ANSI-compatibility mode, there are some major +differences in how :term:`SQL` is structured (see +). Most important of all, the +quote-character (") is interpreted as an identifier quote character and not as +a string quote character, which makes many internal phpMyAdmin operations into +invalid :term:`SQL` statements. There is no +workaround to this behaviour. News to this item will be posted in `issue +#7383 `_. + +.. _faq3_10: + +3.10 Homonyms and no primary key: When the results of a SELECT display more that one column with the same value (for example ``SELECT lastname from employees where firstname like 'A%'`` and two "Smith" values are displayed), if I click Edit I cannot be sure that I am editing the intended row. +----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +Please make sure that your table has a :term:`primary key`, so that phpMyAdmin +can use it for the Edit and Delete links. + +.. _faq3_11: + +3.11 The number of rows for InnoDB tables is not correct. +--------------------------------------------------------- + +phpMyAdmin uses a quick method to get the row count, and this method only +returns an approximate count in the case of InnoDB tables. See +:config:option:`$cfg['MaxExactCount']` for a way to modify those results, but +this could have a serious impact on performance. +However, one can easily replace the approximate row count with exact count by +simply clicking on the approximate count. This can also be done for all tables +at once by clicking on the rows sum displayed at the bottom. + +.. seealso:: :config:option:`$cfg['MaxExactCount']` + +.. _faq3_12: + +3.12 (withdrawn). +----------------- + +.. _faq3_13: + +3.13 I get an error when entering ``USE`` followed by a db name containing an hyphen. +------------------------------------------------------------------------------------- + +The tests I have made with MySQL 5.1.49 shows that the API does not +accept this syntax for the USE command. + +.. _faq3_14: + +3.14 I am not able to browse a table when I don't have the right to SELECT one of the columns. +---------------------------------------------------------------------------------------------- + +This has been a known limitation of phpMyAdmin since the beginning and +it's not likely to be solved in the future. + +.. _faq3_15: + +3.15 (withdrawn). +----------------- + +.. _faq3_16: + +3.16 (withdrawn). +----------------- + +.. _faq3_17: + +3.17 (withdrawn). +----------------- + +.. _faq3_18: + +3.18 When I import a CSV file that contains multiple tables, they are lumped together into a single table. +---------------------------------------------------------------------------------------------------------- + +There is no reliable way to differentiate tables in :term:`CSV` format. For the +time being, you will have to break apart :term:`CSV` files containing multiple +tables. + +.. _faq3_19: + +3.19 When I import a file and have phpMyAdmin determine the appropriate data structure it only uses int, decimal, and varchar types. +------------------------------------------------------------------------------------------------------------------------------------ + +Currently, the import type-detection system can only assign these +MySQL types to columns. In future, more will likely be added but for +the time being you will have to edit the structure to your liking +post-import. Also, you should note the fact that phpMyAdmin will use +the size of the largest item in any given column as the column size +for the appropriate type. If you know you will be adding larger items +to that column then you should manually adjust the column sizes +accordingly. This is done for the sake of efficiency. + +.. _faq3_20: + +3.20 After upgrading, some bookmarks are gone or their content cannot be shown. +------------------------------------------------------------------------------- + +At some point, the character set used to store bookmark content has changed. +It's better to recreate your bookmark from the newer phpMyAdmin version. + +.. _faq3_21: + +3.21 I am unable to log in with a username containing unicode characters such as á. +----------------------------------------------------------------------------------- + +This can happen if MySQL server is not configured to use utf-8 as default +charset. This is a limitation of how PHP and the MySQL server interact; there +is no way for PHP to set the charset before authenticating. + +.. seealso:: + + `phpMyAdmin issue 12232 `_, + `MySQL documentation note `_ + +.. _faqmultiuser: + +ISPs, multi-user installations +++++++++++++++++++++++++++++++ + +.. _faq4_1: + +4.1 I'm an ISP. Can I setup one central copy of phpMyAdmin or do I need to install it for each customer? +-------------------------------------------------------------------------------------------------------- + +Since version 2.0.3, you can setup a central copy of phpMyAdmin for all your +users. The development of this feature was kindly sponsored by NetCologne GmbH. +This requires a properly setup MySQL user management and phpMyAdmin +:term:`HTTP` or cookie authentication. + +.. seealso:: :ref:`authentication_modes` + +.. _faq4_2: + +4.2 What's the preferred way of making phpMyAdmin secure against evil access? +----------------------------------------------------------------------------- + +This depends on your system. If you're running a server which cannot be +accessed by other people, it's sufficient to use the directory protection +bundled with your webserver (with Apache you can use :term:`.htaccess` files, +for example). If other people have telnet access to your server, you should use +phpMyAdmin's :term:`HTTP` or cookie authentication features. + +Suggestions: + +* Your :file:`config.inc.php` file should be ``chmod 660``. +* All your phpMyAdmin files should be chown -R phpmy.apache, where phpmy + is a user whose password is only known to you, and apache is the group + under which Apache runs. +* Follow security recommendations for PHP and your webserver. + +.. _faq4_3: + +4.3 I get errors about not being able to include a file in */lang* or in */libraries*. +-------------------------------------------------------------------------------------- + +Check :file:`php.ini`, or ask your sysadmin to check it. The +``include_path`` must contain "." somewhere in it, and +``open_basedir``, if used, must contain "." and "./lang" to allow +normal operation of phpMyAdmin. + +.. _faq4_4: + +4.4 phpMyAdmin always gives "Access denied" when using HTTP authentication. +--------------------------------------------------------------------------- + +This could happen for several reasons: + +* :config:option:`$cfg['Servers'][$i]['controluser']` and/or :config:option:`$cfg['Servers'][$i]['controlpass']` are wrong. +* The username/password you specify in the login dialog are invalid. +* You have already setup a security mechanism for the phpMyAdmin- + directory, eg. a :term:`.htaccess` file. This would interfere with phpMyAdmin's + authentication, so remove it. + +.. _faq4_5: + +4.5 Is it possible to let users create their own databases? +----------------------------------------------------------- + +Starting with 2.2.5, in the user management page, you can enter a +wildcard database name for a user (for example "joe%"), and put the +privileges you want. For example, adding ``SELECT, INSERT, UPDATE, +DELETE, CREATE, DROP, INDEX, ALTER`` would let a user create/manage +his/her database(s). + +.. _faq4_6: + +4.6 How can I use the Host-based authentication additions? +---------------------------------------------------------- + +If you have existing rules from an old :term:`.htaccess` file, you can take them and +add a username between the ``'deny'``/``'allow'`` and ``'from'`` +strings. Using the username wildcard of ``'%'`` would be a major +benefit here if your installation is suited to using it. Then you can +just add those updated lines into the +:config:option:`$cfg['Servers'][$i]['AllowDeny']['rules']` array. + +If you want a pre-made sample, you can try this fragment. It stops the +'root' user from logging in from any networks other than the private +network :term:`IP` blocks. + +.. code-block:: php + + //block root from logging in except from the private networks + $cfg['Servers'][$i]['AllowDeny']['order'] = 'deny,allow'; + $cfg['Servers'][$i]['AllowDeny']['rules'] = array( + 'deny root from all', + 'allow root from localhost', + 'allow root from 10.0.0.0/8', + 'allow root from 192.168.0.0/16', + 'allow root from 172.16.0.0/12', + ); + +.. _faq4_7: + +4.7 Authentication window is displayed more than once, why? +----------------------------------------------------------- + +This happens if you are using a :term:`URL` to start phpMyAdmin which is +different than the one set in your :config:option:`$cfg['PmaAbsoluteUri']`. For +example, a missing "www", or entering with an :term:`IP` address while a domain +name is defined in the config file. + +.. _faq4_8: + +4.8 Which parameters can I use in the URL that starts phpMyAdmin? +----------------------------------------------------------------- + +When starting phpMyAdmin, you can use the ``db`` +and ``server`` parameters. This last one can contain +either the numeric host index (from ``$i`` of the configuration file) +or one of the host names present in the configuration file. + +For example, to jump directly to a particular database, a URL can be constructed as +``https://example.com/phpmyadmin/?db=sakila``. + +.. versionchanged:: 4.9.0 + + Support for using the ``pma_username`` and ``pma_password`` parameters was removed + in phpMyAdmin 4.9.0 (see `PMASA-2019-4 `_). + +.. _faqbrowsers: + +Browsers or client OS ++++++++++++++++++++++ + +.. _faq5_1: + +5.1 I get an out of memory error, and my controls are non-functional, when trying to create a table with more than 14 columns. +------------------------------------------------------------------------------------------------------------------------------ + +We could reproduce this problem only under Win98/98SE. Testing under +WinNT4 or Win2K, we could easily create more than 60 columns. A +workaround is to create a smaller number of columns, then come back to +your table properties and add the other columns. + +.. _faq5_2: + +5.2 With Xitami 2.5b4, phpMyAdmin won't process form fields. +------------------------------------------------------------ + +This is not a phpMyAdmin problem but a Xitami known bug: you'll face +it with each script/website that use forms. Upgrade or downgrade your +Xitami server. + +.. _faq5_3: + +5.3 I have problems dumping tables with Konqueror (phpMyAdmin 2.2.2). +--------------------------------------------------------------------- + +With Konqueror 2.1.1: plain dumps, zip and gzip dumps work ok, except +that the proposed file name for the dump is always 'tbl\_dump.php'. +The bzip2 dumps don't seem to work. With Konqueror 2.2.1: plain dumps +work; zip dumps are placed into the user's temporary directory, so +they must be moved before closing Konqueror, or else they disappear. +gzip dumps give an error message. Testing needs to be done for +Konqueror 2.2.2. + +.. _faq5_4: + +5.4 I can't use the cookie authentication mode because Internet Explorer never stores the cookies. +-------------------------------------------------------------------------------------------------- + +MS Internet Explorer seems to be really buggy about cookies, at least +till version 6. + +.. _faq5_5: + +5.5 (withdrawn). +---------------------------------------------------------------------------- + +.. _faq5_6: + +5.6 (withdrawn). +----------------------------------------------------------------------------------------------------------------------------------------------------------------- + +.. _faq5_7: + +5.7 I refresh (reload) my browser, and come back to the welcome page. +--------------------------------------------------------------------- + +Some browsers support right-clicking into the frame you want to +refresh, just do this in the right frame. + +.. _faq5_8: + +5.8 With Mozilla 0.9.7 I have problems sending a query modified in the query box. +--------------------------------------------------------------------------------- + +Looks like a Mozilla bug: 0.9.6 was OK. We will keep an eye on future +Mozilla versions. + +.. _faq5_9: + +5.9 With Mozilla 0.9.? to 1.0 and Netscape 7.0-PR1 I can't type a whitespace in the SQL-Query edit area: the page scrolls down. +------------------------------------------------------------------------------------------------------------------------------- + +This is a Mozilla bug (see bug #26882 at `BugZilla +`_). + +.. _faq5_10: + +5.10 (withdrawn). +----------------------------------------------------------------------------------------- + +.. _faq5_11: + +5.11 Extended-ASCII characters like German umlauts are displayed wrong. +----------------------------------------------------------------------- + +Please ensure that you have set your browser's character set to the +one of the language file you have selected on phpMyAdmin's start page. +Alternatively, you can try the auto detection mode that is supported +by the recent versions of the most browsers. + +.. _faq5_12: + +5.12 Mac OS X Safari browser changes special characters to "?". +--------------------------------------------------------------- + +This issue has been reported by a :term:`Mac OS X` user, who adds that Chimera, +Netscape and Mozilla do not have this problem. + +.. _faq5_13: + +5.13 (withdrawn) +---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +.. _faq5_14: + +5.14 (withdrawn) +------------------------------------------------------------------------------------------------------------------ + +.. _faq5_15: + +5.15 (withdrawn) +----------------------------------------- + +.. _faq5_16: + +5.16 With Internet Explorer, I get "Access is denied" Javascript errors. Or I cannot make phpMyAdmin work under Windows. +------------------------------------------------------------------------------------------------------------------------ + +Please check the following points: + +* Maybe you have defined your :config:option:`$cfg['PmaAbsoluteUri']` setting in + :file:`config.inc.php` to an :term:`IP` address and you are starting phpMyAdmin + with a :term:`URL` containing a domain name, or the reverse situation. +* Security settings in IE and/or Microsoft Security Center are too high, + thus blocking scripts execution. +* The Windows Firewall is blocking Apache and MySQL. You must allow + :term:`HTTP` ports (80 or 443) and MySQL + port (usually 3306) in the "in" and "out" directions. + +.. _faq5_17: + +5.17 With Firefox, I cannot delete rows of data or drop a database. +------------------------------------------------------------------- + +Many users have confirmed that the Tabbrowser Extensions plugin they +installed in their Firefox is causing the problem. + +.. _faq5_18: + +5.18 (withdrawn) +----------------------------------------------------------------------------------------- + +.. _faq5_19: + +5.19 I get JavaScript errors in my browser. +------------------------------------------- + +Issues have been reported with some combinations of browser +extensions. To troubleshoot, disable all extensions then clear your +browser cache to see if the problem goes away. + +.. _faq5_20: + +5.20 I get errors about violating Content Security Policy. +---------------------------------------------------------- + +If you see errors like: + +.. code-block:: text + + Refused to apply inline style because it violates the following Content Security Policy directive + +This is usually caused by some software, which wrongly rewrites +:mailheader:`Content Security Policy` headers. Usually this is caused by +antivirus proxy or browser addons which are causing such errors. + +If you see these errors, try disabling the HTTP proxy in antivirus or disable +the :mailheader:`Content Security Policy` rewriting in it. If that doesn't +help, try disabling browser extensions. + +Alternatively it can be also server configuration issue (if the webserver is +configured to emit :mailheader:`Content Security Policy` headers, they can +override the ones from phpMyAdmin). + +Programs known to cause these kind of errors: + +* Kaspersky Internet Security + +.. _faq5_21: + +5.21 I get errors about potentially unsafe operation when browsing table or executing SQL query. +------------------------------------------------------------------------------------------------ + +If you see errors like: + +.. code-block:: text + + A potentially unsafe operation has been detected in your request to this site. + +This is usually caused by web application firewall doing requests filtering. It +tries to prevent SQL injection, however phpMyAdmin is tool designed to execute +SQL queries, thus it makes it unusable. + +Please whitelist phpMyAdmin scripts from the web application firewall settings +or disable it completely for phpMyAdmin path. + +Programs known to cause these kind of errors: + +* Wordfence Web Application Firewall + +.. _faqusing: + +Using phpMyAdmin +++++++++++++++++ + +.. _faq6_1: + +6.1 I can't insert new rows into a table / I can't create a table - MySQL brings up a SQL error. +------------------------------------------------------------------------------------------------ + +Examine the :term:`SQL` error with care. +Often the problem is caused by specifying a wrong column-type. Common +errors include: + +* Using ``VARCHAR`` without a size argument +* Using ``TEXT`` or ``BLOB`` with a size argument + +Also, look at the syntax chapter in the MySQL manual to confirm that +your syntax is correct. + +.. _faq6_2: + +6.2 When I create a table, I set an index for two columns and phpMyAdmin generates only one index with those two columns. +------------------------------------------------------------------------------------------------------------------------- + +This is the way to create a multi-columns index. If you want two +indexes, create the first one when creating the table, save, then +display the table properties and click the Index link to create the +other index. + +.. _faq6_3: + +6.3 How can I insert a null value into my table? +------------------------------------------------ + +Since version 2.2.3, you have a checkbox for each column that can be +null. Before 2.2.3, you had to enter "null", without the quotes, as +the column's value. Since version 2.5.5, you have to use the checkbox +to get a real NULL value, so if you enter "NULL" this means you want a +literal NULL in the column, and not a NULL value (this works in PHP4). + +.. _faq6_4: + +6.4 How can I backup my database or table? +------------------------------------------ + +Click on a database or table name in the navigation panel, the properties will +be displayed. Then on the menu, click "Export", you can dump the structure, the +data, or both. This will generate standard :term:`SQL` statements that can be +used to recreate your database/table. You will need to choose "Save as file", +so that phpMyAdmin can transmit the resulting dump to your station. Depending +on your PHP configuration, you will see options to compress the dump. See also +the :config:option:`$cfg['ExecTimeLimit']` configuration variable. For +additional help on this subject, look for the word "dump" in this document. + +.. _faq6_5: + +6.5 How can I restore (upload) my database or table using a dump? How can I run a ".sql" file? +---------------------------------------------------------------------------------------------- + +Click on a database name in the navigation panel, the properties will +be displayed. Select "Import" from the list of tabs in the right–hand +frame (or ":term:`SQL`" if your phpMyAdmin +version is previous to 2.7.0). In the "Location of the text file" +section, type in the path to your dump filename, or use the Browse +button. Then click Go. With version 2.7.0, the import engine has been +re–written, if possible it is suggested that you upgrade to take +advantage of the new features. For additional help on this subject, +look for the word "upload" in this document. + +Note: For errors while importing of dumps exported from older MySQL versions to newer MySQL versions, +please check :ref:`faq6_41`. + +.. _faq6_6: + +6.6 How can I use the relation table in Query-by-example? +--------------------------------------------------------- + +Here is an example with the tables persons, towns and countries, all +located in the database "mydb". If you don't have a ``pma__relation`` +table, create it as explained in the configuration section. Then +create the example tables: + +.. code-block:: mysql + + CREATE TABLE REL_countries ( + country_code char(1) NOT NULL default '', + description varchar(10) NOT NULL default '', + PRIMARY KEY (country_code) + ) ENGINE=MyISAM; + + INSERT INTO REL_countries VALUES ('C', 'Canada'); + + CREATE TABLE REL_persons ( + id tinyint(4) NOT NULL auto_increment, + person_name varchar(32) NOT NULL default '', + town_code varchar(5) default '0', + country_code char(1) NOT NULL default '', + PRIMARY KEY (id) + ) ENGINE=MyISAM; + + INSERT INTO REL_persons VALUES (11, 'Marc', 'S', 'C'); + INSERT INTO REL_persons VALUES (15, 'Paul', 'S', 'C'); + + CREATE TABLE REL_towns ( + town_code varchar(5) NOT NULL default '0', + description varchar(30) NOT NULL default '', + PRIMARY KEY (town_code) + ) ENGINE=MyISAM; + + INSERT INTO REL_towns VALUES ('S', 'Sherbrooke'); + INSERT INTO REL_towns VALUES ('M', 'Montréal'); + +To setup appropriate links and display information: + +* on table "REL\_persons" click Structure, then Relation view +* for "town\_code", choose from dropdowns, "mydb", "REL\_towns", "code" + for foreign database, table and column respectively +* for "country\_code", choose from dropdowns, "mydb", "REL\_countries", + "country\_code" for foreign database, table and column respectively +* on table "REL\_towns" click Structure, then Relation view +* in "Choose column to display", choose "description" +* repeat the two previous steps for table "REL\_countries" + +Then test like this: + +* Click on your db name in the navigation panel +* Choose "Query" +* Use tables: persons, towns, countries +* Click "Update query" +* In the columns row, choose persons.person\_name and click the "Show" + tickbox +* Do the same for towns.description and countries.descriptions in the + other 2 columns +* Click "Update query" and you will see in the query box that the + correct joins have been generated +* Click "Submit query" + +.. _faqdisplay: + +6.7 How can I use the "display column" feature? +----------------------------------------------- + +Starting from the previous example, create the ``pma__table_info`` as +explained in the configuration section, then browse your persons +table, and move the mouse over a town code or country code. See also +:ref:`faq6_21` for an additional feature that "display column" +enables: drop-down list of possible values. + +.. _faqpdf: + +6.8 How can I produce a PDF schema of my database? +-------------------------------------------------- + +First the configuration variables "relation", "table\_coords" and +"pdf\_pages" have to be filled in. Then you need to think about your +schema layout. Which tables will go on which pages? + +* Select your database in the navigation panel. +* Choose "Operations" in the navigation bar at the top. +* Choose "Edit :term:`PDF` Pages" near the + bottom of the page. +* Enter a name for the first :term:`PDF` page + and click Go. If you like, you can use the "automatic layout," which + will put all your linked tables onto the new page. +* Select the name of the new page (making sure the Edit radio button is + selected) and click Go. +* Select a table from the list, enter its coordinates and click Save. + Coordinates are relative; your diagram will be automatically scaled to + fit the page. When initially placing tables on the page, just pick any + coordinates -- say, 50x50. After clicking Save, you can then use the + :ref:`wysiwyg` to position the element correctly. +* When you'd like to look at your :term:`PDF`, first be sure to click the Save + button beneath the list of tables and coordinates, to save any changes you + made there. Then scroll all the way down, select the :term:`PDF` options you + want, and click Go. +* Internet Explorer for Windows may suggest an incorrect filename when + you try to save a generated :term:`PDF`. + When saving a generated :term:`PDF`, be + sure that the filename ends in ".pdf", for example "schema.pdf". + Browsers on other operating systems, and other browsers on Windows, do + not have this problem. + +.. seealso:: + + :ref:`relations` + +.. _faq6_9: + +6.9 phpMyAdmin is changing the type of one of my columns! +--------------------------------------------------------- + +No, it's MySQL that is doing `silent column type changing +`_. + +.. _underscore: + +6.10 When creating a privilege, what happens with underscores in the database name? +----------------------------------------------------------------------------------- + +If you do not put a backslash before the underscore, this is a +wildcard grant, and the underscore means "any character". So, if the +database name is "john\_db", the user would get rights to john1db, +john2db ... If you put a backslash before the underscore, it means +that the database name will have a real underscore. + +.. _faq6_11: + +6.11 What is the curious symbol ø in the statistics pages? +---------------------------------------------------------- + +It means "average". + +.. _faqexport: + +6.12 I want to understand some Export options. +---------------------------------------------- + +**Structure:** + +* "Add DROP TABLE" will add a line telling MySQL to `drop the table + `_, if it already + exists during the import. It does NOT drop the table after your + export, it only affects the import file. +* "If Not Exists" will only create the table if it doesn't exist. + Otherwise, you may get an error if the table name exists but has a + different structure. +* "Add AUTO\_INCREMENT value" ensures that AUTO\_INCREMENT value (if + any) will be included in backup. +* "Enclose table and column names with backquotes" ensures that column + and table names formed with special characters are protected. +* "Add into comments" includes column comments, relations, and MIME + types set in the pmadb in the dump as :term:`SQL` comments + (*/\* xxx \*/*). + +**Data:** + +* "Complete inserts" adds the column names on every INSERT command, for + better documentation (but resulting file is bigger). +* "Extended inserts" provides a shorter dump file by using only once the + INSERT verb and the table name. +* "Delayed inserts" are best explained in the `MySQL manual - INSERT DELAYED Syntax + `_. +* "Ignore inserts" treats errors as a warning instead. Again, more info + is provided in the `MySQL manual - INSERT Syntax + `_, but basically with + this selected, invalid values are adjusted and inserted rather than + causing the entire statement to fail. + +.. _faq6_13: + +6.13 I would like to create a database with a dot in its name. +-------------------------------------------------------------- + +This is a bad idea, because in MySQL the syntax "database.table" is +the normal way to reference a database and table name. Worse, MySQL +will usually let you create a database with a dot, but then you cannot +work with it, nor delete it. + +.. _faqsqlvalidator: + +6.14 (withdrawn). +----------------- + +.. _faq6_15: + +6.15 I want to add a BLOB column and put an index on it, but MySQL says "BLOB column '...' used in key specification without a key length". +------------------------------------------------------------------------------------------------------------------------------------------- + +The right way to do this, is to create the column without any indexes, +then display the table structure and use the "Create an index" dialog. +On this page, you will be able to choose your BLOB column, and set a +size to the index, which is the condition to create an index on a BLOB +column. + +.. _faq6_16: + +6.16 How can I simply move in page with plenty editing fields? +-------------------------------------------------------------- + +You can use :kbd:`Ctrl+arrows` (:kbd:`Option+Arrows` in Safari) for moving on +most pages with many editing fields (table structure changes, row editing, +etc.). + +.. _faq6_17: + +6.17 Transformations: I can't enter my own mimetype! What is this feature then useful for? +------------------------------------------------------------------------------------------ + +Defining mimetypes is of no use if you can't put +transformations on them. Otherwise you could just put a comment on the +column. Because entering your own mimetype will cause serious syntax +checking issues and validation, this introduces a high-risk false- +user-input situation. Instead you have to initialize mimetypes using +functions or empty mimetype definitions. + +Plus, you have a whole overview of available mimetypes. Who knows all those +mimetypes by heart so he/she can enter it at will? + +.. _faqbookmark: + +6.18 Bookmarks: Where can I store bookmarks? Why can't I see any bookmarks below the query box? What are these variables for? +----------------------------------------------------------------------------------------------------------------------------- + +You need to have configured the :ref:`linked-tables` for using bookmarks +feature. Once you have done that, you can use bookmarks in the :guilabel:`SQL` tab. + +.. seealso:: :ref:`bookmarks` + +.. _faq6_19: + +6.19 How can I create simple LATEX document to include exported table? +---------------------------------------------------------------------- + +You can simply include table in your LATEX documents, +minimal sample document should look like following one (assuming you +have table exported in file :file:`table.tex`): + +.. code-block:: latex + + \documentclass{article} % or any class you want + \usepackage{longtable} % for displaying table + \begin{document} % start of document + \include{table} % including exported table + \end{document} % end of document + +.. _faq6_20: + +6.20 I see a lot of databases which are not mine, and cannot access them. +------------------------------------------------------------------------- + +You have one of these global privileges: CREATE TEMPORARY TABLES, SHOW +DATABASES, LOCK TABLES. Those privileges also enable users to see all the +database names. So if your users do not need those privileges, you can remove +them and their databases list will shorten. + +.. seealso:: + +.. _faq6_21: + +6.21 In edit/insert mode, how can I see a list of possible values for a column, based on some foreign table? +------------------------------------------------------------------------------------------------------------ + +You have to setup appropriate links between the tables, and also setup +the "display column" in the foreign table. See :ref:`faq6_6` for an +example. Then, if there are 100 values or less in the foreign table, a +drop-down list of values will be available. You will see two lists of +values, the first list containing the key and the display column, the +second list containing the display column and the key. The reason for +this is to be able to type the first letter of either the key or the +display column. For 100 values or more, a distinct window will appear, +to browse foreign key values and choose one. To change the default +limit of 100, see :config:option:`$cfg['ForeignKeyMaxLimit']`. + +.. _faq6_22: + +6.22 Bookmarks: Can I execute a default bookmark automatically when entering Browse mode for a table? +----------------------------------------------------------------------------------------------------- + +Yes. If a bookmark has the same label as a table name and it's not a +public bookmark, it will be executed. + +.. seealso:: :ref:`bookmarks` + +.. _faq6_23: + +6.23 Export: I heard phpMyAdmin can export Microsoft Excel files? +----------------------------------------------------------------- + +You can use :term:`CSV` for Microsoft Excel, +which works out of the box. + +.. versionchanged:: 3.4.5 + Since phpMyAdmin 3.4.5 support for direct export to Microsoft Excel version + 97 and newer was dropped. + +.. _faq6_24: + +6.24 Now that phpMyAdmin supports native MySQL 4.1.x column comments, what happens to my column comments stored in pmadb? +------------------------------------------------------------------------------------------------------------------------- + +Automatic migration of a table's pmadb-style column comments to the +native ones is done whenever you enter Structure page for this table. + +.. _faq6_25: + +6.25 (withdrawn). +----------------- + +.. _faq6_26: + +6.26 How can I select a range of rows? +-------------------------------------- + +Click the first row of the range, hold the shift key and click the +last row of the range. This works everywhere you see rows, for example +in Browse mode or on the Structure page. + +.. _faq6_27: + +6.27 What format strings can I use? +----------------------------------- + +In all places where phpMyAdmin accepts format strings, you can use +``@VARIABLE@`` expansion and `strftime `_ +format strings. The expanded variables depend on a context (for +example, if you haven't chosen a table, you can not get the table +name), but the following variables can be used: + +``@HTTP_HOST@`` + HTTP host that runs phpMyAdmin +``@SERVER@`` + MySQL server name +``@VERBOSE@`` + Verbose MySQL server name as defined in :config:option:`$cfg['Servers'][$i]['verbose']` +``@VSERVER@`` + Verbose MySQL server name if set, otherwise normal +``@DATABASE@`` + Currently opened database +``@TABLE@`` + Currently opened table +``@COLUMNS@`` + Columns of the currently opened table +``@PHPMYADMIN@`` + phpMyAdmin with version + +.. _wysiwyg: + +6.28 How can I easily edit relational schema for export? +-------------------------------------------------------- + +By clicking on the button 'toggle scratchboard' on the page where you +edit x/y coordinates of those elements you can activate a scratchboard +where all your elements are placed. By clicking on an element, you can +move them around in the pre-defined area and the x/y coordinates will +get updated dynamically. Likewise, when entering a new position +directly into the input field, the new position in the scratchboard +changes after your cursor leaves the input field. + +You have to click on the 'OK'-button below the tables to save the new +positions. If you want to place a new element, first add it to the +table of elements and then you can drag the new element around. + +By changing the paper size and the orientation you can change the size +of the scratchboard as well. You can do so by just changing the +dropdown field below, and the scratchboard will resize automatically, +without interfering with the current placement of the elements. + +If ever an element gets out of range you can either enlarge the paper +size or click on the 'reset' button to place all elements below each +other. + +.. _faq6_29: + +6.29 Why can't I get a chart from my query result table? +-------------------------------------------------------- + +Not every table can be put to the chart. Only tables with one, two or +three columns can be visualised as a chart. Moreover the table must be +in a special format for chart script to understand it. Currently +supported formats can be found in :ref:`charts`. + +.. _faq6_30: + +6.30 Import: How can I import ESRI Shapefiles? +---------------------------------------------- + +An ESRI Shapefile is actually a set of several files, where .shp file +contains geometry data and .dbf file contains data related to those +geometry data. To read data from .dbf file you need to have PHP +compiled with the dBase extension (--enable-dbase). Otherwise only +geometry data will be imported. + +To upload these set of files you can use either of the following +methods: + +Configure upload directory with :config:option:`$cfg['UploadDir']`, upload both .shp and .dbf files with +the same filename and chose the .shp file from the import page. + +Create a zip archive with .shp and .dbf files and import it. For this +to work, you need to set :config:option:`$cfg['TempDir']` to a place where the web server user can +write (for example ``'./tmp'``). + +To create the temporary directory on a UNIX-based system, you can do: + +.. code-block:: sh + + cd phpMyAdmin + mkdir tmp + chmod o+rwx tmp + +.. _faq6_31: + +6.31 How do I create a relation in designer? +-------------------------------------------- + +To select relation, click: The display column is shown in pink. To +set/unset a column as the display column, click the "Choose column to +display" icon, then click on the appropriate column name. + +.. _faq6_32: + +6.32 How can I use the zoom search feature? +------------------------------------------- + +The Zoom search feature is an alternative to table search feature. It allows +you to explore a table by representing its data in a scatter plot. You can +locate this feature by selecting a table and clicking the :guilabel:`Search` +tab. One of the sub-tabs in the :guilabel:`Table Search` page is +:guilabel:`Zoom Search`. + +Consider the table REL\_persons in :ref:`faq6_6` for +an example. To use zoom search, two columns need to be selected, for +example, id and town\_code. The id values will be represented on one +axis and town\_code values on the other axis. Each row will be +represented as a point in a scatter plot based on its id and +town\_code. You can include two additional search criteria apart from +the two fields to display. + +You can choose which field should be +displayed as label for each point. If a display column has been set +for the table (see :ref:`faqdisplay`), it is taken as the label unless +you specify otherwise. You can also select the maximum number of rows +you want to be displayed in the plot by specifing it in the 'Max rows +to plot' field. Once you have decided over your criteria, click 'Go' +to display the plot. + +After the plot is generated, you can use the +mousewheel to zoom in and out of the plot. In addition, panning +feature is enabled to navigate through the plot. You can zoom-in to a +certain level of detail and use panning to locate your area of +interest. Clicking on a point opens a dialogue box, displaying field +values of the data row represented by the point. You can edit the +values if required and click on submit to issue an update query. Basic +instructions on how to use can be viewed by clicking the 'How to use?' +link located just above the plot. + +.. _faq6_33: + +6.33 When browsing a table, how can I copy a column name? +--------------------------------------------------------- + +Selecting the name of the column within the browse table header cell +for copying is difficult, as the columns support reordering by +dragging the header cells as well as sorting by clicking on the linked +column name. To copy a column name, double-click on the empty area +next to the column name, when the tooltip tells you to do so. This +will show you an input box with the column name. You may right-click +the column name within this input box to copy it to your clipboard. + +.. _faq6_34: + +6.34 How can I use the Favorite Tables feature? +--------------------------------------------------------- + +Favorite Tables feature is very much similar to Recent Tables feature. +It allows you to add a shortcut for the frequently used tables of any +database in the navigation panel . You can easily navigate to any table +in the list by simply choosing it from the list. These tables are stored +in your browser's local storage if you have not configured your +`phpMyAdmin Configuration Storage`. Otherwise these entries are stored in +`phpMyAdmin Configuration Storage`. + +IMPORTANT: In absence of `phpMyAdmin Configuration Storage`, your Favorite +tables may be different in different browsers based on your different +selections in them. + +To add a table to Favorite list simply click on the `Gray` star in front +of a table name in the list of tables of a Database and wait until it +turns to `Yellow`. +To remove a table from list, simply click on the `Yellow` star and +wait until it turns `Gray` again. + +Using :config:option:`$cfg['NumFavoriteTables']` in your :file:`config.inc.php` +file, you can define the maximum number of favorite tables shown in the +navigation panel. Its default value is `10`. + +.. _faq6_35: + +6.35 How can I use the Range search feature? +--------------------------------------------------------- + +With the help of range search feature, one can specify a range of values for +particular column(s) while performing search operation on a table from the `Search` +tab. + +To use this feature simply click on the `BETWEEN` or `NOT BETWEEN` operators +from the operator select list in front of the column name. On choosing one of the +above options, a dialog box will show up asking for the `Minimum` and `Maximum` +value for that column. Only the specified range of values will be included +in case of `BETWEEN` and excluded in case of `NOT BETWEEN` from the final results. + +Note: The Range search feature will work only `Numeric` and `Date` data type columns. + +.. _faq6_36: + +6.36 What is Central columns and how can I use this feature? +------------------------------------------------------------ + +As the name suggests, the Central columns feature enables to maintain a central list of +columns per database to avoid similar name for the same data element and bring consistency +of data type for the same data element. You can use the central list of columns to +add an element to any table structure in that database which will save from writing +similar column name and column definition. + +To add a column to central list, go to table structure page, check the columns you want +to include and then simply click on "Add to central columns". If you want to add all +unique columns from more than one table from a database then go to database structure page, +check the tables you want to include and then select "Add columns to central list". + +To remove a column from central list, go to Table structure page, check the columns you want +to remove and then simply click on "Remove from central columns". If you want to remove all +columns from more than one tables from a database then go to database structure page, +check the tables you want to include and then select "Remove columns from central list". + +To view and manage the central list, select the database you want to manage central columns +for then from the top menu click on "Central columns". You will be taken to a page where +you will have options to edit, delete or add new columns to central list. + +.. _faq6_37: + +6.37 How can I use Improve Table structure feature? +--------------------------------------------------------- + +Improve table structure feature helps to bring the table structure upto +Third Normal Form. A wizard is presented to user which asks questions about the +elements during the various steps for normalization and a new structure is proposed +accordingly to bring the table into the First/Second/Third Normal form. +On startup of the wizard, user gets to select upto what normal form they want to +normalize the table structure. + +Here is an example table which you can use to test all of the three First, Second and +Third Normal Form. + +.. code-block:: mysql + + CREATE TABLE `VetOffice` ( + `petName` varchar(64) NOT NULL, + `petBreed` varchar(64) NOT NULL, + `petType` varchar(64) NOT NULL, + `petDOB` date NOT NULL, + `ownerLastName` varchar(64) NOT NULL, + `ownerFirstName` varchar(64) NOT NULL, + `ownerPhone1` int(12) NOT NULL, + `ownerPhone2` int(12) NOT NULL, + `ownerEmail` varchar(64) NOT NULL, + ); + +The above table is not in First normal Form as no :term:`primary key` exists. Primary key +is supposed to be (`petName`,`ownerLastName`,`ownerFirstName`) . If the :term:`primary key` +is chosen as suggested the resultant table won't be in Second as well as Third Normal +form as the following dependencies exists. + +.. code-block:: mysql + + (OwnerLastName, OwnerFirstName) -> OwnerEmail + (OwnerLastName, OwnerFirstName) -> OwnerPhone + PetBreed -> PetType + +Which says, OwnerEmail depends on OwnerLastName and OwnerFirstName. +OwnerPhone depends on OwnerLastName and OwnerFirstName. +PetType depends on PetBreed. + +.. _faq6_38: + +6.38 How can I reassign auto-incremented values? +------------------------------------------------ + +Some users prefer their AUTO_INCREMENT values to be consecutive; this is not +always the case after row deletion. + +Here are the steps to accomplish this. These are manual steps because they +involve a manual verification at one point. + +* Ensure that you have exclusive access to the table to rearrange + +* On your :term:`primary key` column (i.e. id), remove the AUTO_INCREMENT setting + +* Delete your primary key in Structure > indexes + +* Create a new column future_id as primary key, AUTO_INCREMENT + +* Browse your table and verify that the new increments correspond to what + you're expecting + +* Drop your old id column + +* Rename the future_id column to id + +* Move the new id column via Structure > Move columns + +.. _faq6_39: + +6.39 What is the "Adjust privileges" option when renaming, copying, or moving a database, table, column, or procedure? +---------------------------------------------------------------------------------------------------------------------- + +When renaming/copying/moving a database/table/column/procedure, +MySQL does not adjust the original privileges relating to these objects +on its own. By selecting this option, phpMyAdmin will adjust the privilege +table so that users have the same privileges on the new items. + +For example: A user 'bob'@'localhost' has a 'SELECT' privilege on a +column named 'id'. Now, if this column is renamed to 'id_new', MySQL, +on its own, would **not** adjust the column privileges to the new column name. +phpMyAdmin can make this adjustment for you automatically. + +Notes: + +* While adjusting privileges for a database, the privileges of all + database-related elements (tables, columns and procedures) are also adjusted + to the database's new name. + +* Similarly, while adjusting privileges for a table, the privileges of all + the columns inside the new table are also adjusted. + +* While adjusting privileges, the user performing the operation **must** have the following + privileges: + + * SELECT, INSERT, UPDATE, DELETE privileges on following tables: + `mysql`.`db`, `mysql`.`columns_priv`, `mysql`.`tables_priv`, `mysql`.`procs_priv` + * FLUSH privilege (GLOBAL) + +Thus, if you want to replicate the database/table/column/procedure as it is +while renaming/copying/moving these objects, make sure you have checked this option. + +.. _faq6_40: + +6.40 I see "Bind parameters" checkbox in the "SQL" page. How do I write parameterized SQL queries? +-------------------------------------------------------------------------------------------------- + +From version 4.5, phpMyAdmin allows users to execute parameterized queries in the "SQL" page. +Parameters should be prefixed with a colon(:) and when the "Bind parameters" checkbox is checked +these parameters will be identified and input fields for these parameters will be presented. +Values entered in these field will be substituted in the query before being executed. + +.. _faq6_41: + +6.41 I get import errors while importing the dumps exported from older MySQL versions (pre-5.7.6) into newer MySQL versions (5.7.7+), but they work fine when imported back on same older versions ? +-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +If you get errors like *#1031 - Table storage engine for 'table_name' doesn't have this option* +while importing the dumps exported from pre-5.7.7 MySQL servers into new MySQL server versions 5.7.7+, +it might be because ROW_FORMAT=FIXED is not supported with InnoDB tables. Moreover, the value of +`innodb_strict_mode `_ would define if this would be reported as a warning or as an error. + +Since MySQL version 5.7.9, the default value for `innodb_strict_mode` is `ON` and thus would generate +an error when such a CREATE TABLE or ALTER TABLE statement is encountered. + +There are two ways of preventing such errors while importing: + +* Change the value of `innodb_strict_mode` to `OFF` before starting the import and turn it `ON` after + the import is successfully completed. +* This can be achieved in two ways: + + * Go to 'Variables' page and edit the value of `innodb_strict_mode` + * Run the query : `SET GLOBAL `innodb_strict_mode` = '[value]'` + +After the import is done, it is suggested that the value of `innodb_strict_mode` should be reset to the +original value. + +.. _faqproject: + +phpMyAdmin project +++++++++++++++++++ + +.. _faq7_1: + +7.1 I have found a bug. How do I inform developers? +--------------------------------------------------- + +Our issues tracker is located at . +For security issues, please refer to the instructions at to email +the developers directly. + +.. _faq7_2: + +7.2 I want to translate the messages to a new language or upgrade an existing language, where do I start? +--------------------------------------------------------------------------------------------------------- + +Translations are very welcome and all you need to have are the +language skills. The easiest way is to use our `online translation +service `_. You can check +out all the possibilities to translate in the `translate section on +our website `_. + +.. _faq7_3: + +7.3 I would like to help out with the development of phpMyAdmin. How should I proceed? +-------------------------------------------------------------------------------------- + +We welcome every contribution to the development of phpMyAdmin. You +can check out all the possibilities to contribute in the `contribute +section on our website +`_. + +.. seealso:: :ref:`developers` + +.. _faqsecurity: + +Security +++++++++ + +.. _faq8_1: + +8.1 Where can I get information about the security alerts issued for phpMyAdmin? +-------------------------------------------------------------------------------- + +Please refer to . + +.. _faq8_2: + +8.2 How can I protect phpMyAdmin against brute force attacks? +------------------------------------------------------------- + +If you use Apache web server, phpMyAdmin exports information about +authentication to the Apache environment and it can be used in Apache +logs. Currently there are two variables available: + +``userID`` + User name of currently active user (he does not have to be logged in). +``userStatus`` + Status of currently active user, one of ``ok`` (user is logged in), + ``mysql-denied`` (MySQL denied user login), ``allow-denied`` (user denied + by allow/deny rules), ``root-denied`` (root is denied in configuration), + ``empty-denied`` (empty password is denied). + +``LogFormat`` directive for Apache can look like following: + +.. code-block:: apache + + LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\" %{userID}n %{userStatus}n" pma_combined + +You can then use any log analyzing tools to detect possible break-in +attempts. + +.. _faq8_3: + +8.3 Why are there path disclosures when directly loading certain files? +----------------------------------------------------------------------- + +This is a server configuration problem. Never enable ``display_errors`` on a production site. + +.. _faq8_4: + +8.4 CSV files exported from phpMyAdmin could allow a formula injection attack. +------------------------------------------------------------------------------ + +It is possible to generate a :term:`CSV` file that, when imported to a spreadsheet program such as Microsoft Excel, +could potentially allow the execution of arbitrary commands. + +The CSV files generated by phpMyAdmin could potentially contain text that would be interpreted by a spreadsheet program as +a formula, but we do not believe escaping those fields is the proper behavior. There is no means to properly escape and +differentiate between a desired text output and a formula that should be escaped, and CSV is a text format where function +definitions should not be interpreted anyway. We have discussed this at length and feel it is the responsibility of the +spreadsheet program to properly parse and sanitize such data on input instead. + +Google also has a `similar view `_. + +.. _faqsynchronization: + +Synchronization ++++++++++++++++ + +.. _faq9_1: + +9.1 (withdrawn). +---------------- + +.. _faq9_2: + +9.2 (withdrawn). +---------------- diff --git a/php/apps/phpmyadmin49/html/doc/html/_sources/glossary.txt b/php/apps/phpmyadmin49/html/doc/html/_sources/glossary.txt new file mode 100644 index 00000000..cdad7ac0 --- /dev/null +++ b/php/apps/phpmyadmin49/html/doc/html/_sources/glossary.txt @@ -0,0 +1,426 @@ +.. _glossary: + +Glossary +======== + +From Wikipedia, the free encyclopedia + +.. glossary:: + + .htaccess + the default name of Apache's directory-level configuration file. + + .. seealso:: + + ACL + Access Contol List + + Blowfish + a keyed, symmetric block cipher, designed in 1993 by Bruce Schneier. + + .. seealso:: + + Browser + a software application that enables a user to display and interact with text, images, and other information typically located on a web page at a website on the World Wide Web. + + .. seealso:: + + bzip2 + a free software/open source data compression algorithm and program developed by Julian Seward. + + .. seealso:: + + CGI + Common Gateway Interface is an important World Wide Web technology that + enables a client web browser to request data from a program executed on + the Web server. + + .. seealso:: + + Changelog + a log or record of changes made to a project. + + .. seealso:: + + Client + a computer system that accesses a (remote) service on another computer by some kind of network. + + .. seealso:: + + column + a set of data values of a particular simple type, one for each row of the table. + + .. seealso:: + + Cookie + a packet of information sent by a server to a World Wide Web browser and then sent back by the browser each time it accesses that server. + + .. seealso:: + + CSV + Comma- separated values + + .. seealso:: + + DB + look at :term:`database` + + database + an organized collection of data. + + .. seealso:: + + Engine + look at :term:`storage engines` + + extension + a PHP module that extends PHP with additional functionality. + + .. seealso:: + + FAQ + Frequently Asked Questions is a list of commonly asked question and there + answers. + + .. seealso:: + + Field + one part of divided data/columns. + + .. seealso:: + + foreign key + a column or group of columns in a database row that point to a key column + or group of columns forming a key of another database row in some + (usually different) table. + + .. seealso:: + + GD + Graphics Library by Thomas Boutell and others for dynamically manipulating images. + + .. seealso:: + + GD2 + look at :term:`gd` + + gzip + gzip is short for GNU zip, a GNU free software file compression program. + + .. seealso:: + + host + any machine connected to a computer network, a node that has a hostname. + + .. seealso:: + + hostname + the unique name by which a network attached device is known on a network. + + .. seealso:: + + HTTP + HyperText Transfer Protocol is the primary method used to transfer or + convey information on the World Wide Web. + + .. seealso:: + + https + a :term:`HTTP`-connection with additional security measures. + + .. seealso:: + + IEC + International Electrotechnical Commission + + IIS + Internet Information Services is a set of Internet-based services for + servers using Microsoft Windows. + + .. seealso:: + + Index + a feature that allows quick access to the rows in a table. + + .. seealso:: + + IP + Internet Protocol is a data-oriented protocol used by source and + destination hosts for communicating data across a packet-switched + internetwork. + + .. seealso:: + + IP Address + a unique number that devices use in order to identify and communicate with each other on a network utilizing the Internet Protocol standard. + + .. seealso:: + + IPv6 + IPv6 (Internet Protocol version 6) is the latest revision of the + Internet Protocol (:term:`IP`), designed to deal with the + long-anticipated problem of its precedessor IPv4 running out of addresses. + + .. seealso:: + + ISAPI + Internet Server Application Programming Interface is the API of Internet Information Services (IIS). + + .. seealso:: + + ISP + Internet service provider is a business or organization that offers users + access to the Internet and related services. + + .. seealso:: + + ISO + International Standards Organisation + + JPEG + a most commonly used standard method of lossy compression for photographic images. + + .. seealso:: + + JPG + look at :term:`jpeg` + + Key + look at :term:`index` + + LATEX + a document preparation system for the TEX typesetting program. + + .. seealso:: + + Mac + Apple Macintosh is line of personal computers is designed, developed, manufactured, and marketed by Apple Computer. + + .. seealso:: + + Mac OS X + the operating system which is included with all currently shipping Apple Macintosh computers in the consumer and professional markets. + + .. seealso:: + + mbstring + The PHP `mbstring` functions provide support for languages represented by multi-byte character sets, most notably UTF-8. + + If you have troubles installing this extension, please follow :ref:`faqmysql`, it provides useful hints. + + .. seealso:: + + MCrypt + a cryptographic library. + + .. seealso:: + + mcrypt + the MCrypt PHP extension. + + .. seealso:: + + MIME + Multipurpose Internet Mail Extensions is + an Internet Standard for the format of e-mail. + + .. seealso:: + + module + some sort of extension for the Apache Webserver. + + .. seealso:: + + mod_proxy_fcgi + an Apache module implmenting a Fast CGI interface; PHP can be run as a CGI module, FastCGI, or + directly as an Apache module. + + MySQL + a multithreaded, multi-user, SQL (Structured Query Language) Database Management System (DBMS). + + .. seealso:: + + mysqli + the improved MySQL client PHP extension. + + .. seealso:: + + mysql + the MySQL client PHP extension. + + .. seealso:: + + OpenDocument + open standard for office documents. + + .. seealso:: + + OS X + look at :term:`Mac OS X`. + + .. seealso:: + + PDF + Portable Document Format is a file format developed by Adobe Systems for + representing two dimensional documents in a device independent and + resolution independent format. + + .. seealso:: + + PEAR + the PHP Extension and Application Repository. + + .. seealso:: + + PCRE + Perl Compatible Regular Expressions is the perl-compatible regular + expression functions for PHP + + .. seealso:: + + PHP + short for "PHP: Hypertext Preprocessor", is an open-source, reflective + programming language used mainly for developing server-side applications + and dynamic web content, and more recently, a broader range of software + applications. + + .. seealso:: + + port + a connection through which data is sent and received. + + .. seealso:: + + primary key + A primary key is an index over one or more fields in a table with + unique values for each single row in this table. Every table should have + a primary key for easier accessing/identifying data in this table. There + can only be one primary key per table and it is named always **PRIMARY**. + In fact a primary key is just an :term:`unique key` with the name + **PRIMARY**. If no primary key is defined MySQL will use first *unique + key* as primary key if there is one. + + You can create the primary key when creating the table (in phpMyAdmin + just check the primary key radio buttons for each field you wish to be + part of the primary key). + + You can also add a primary key to an existing table with `ALTER` `TABLE` + or `CREATE` `INDEX` (in phpMyAdmin you can just click on 'add index' on + the table structure page below the listed fields). + + RFC + Request for Comments (RFC) documents are a series of memoranda + encompassing new research, innovations, and methodologies applicable to + Internet technologies. + + .. seealso:: + + RFC 1952 + GZIP file format specification version 4.3 + + .. seealso:: :rfc:`1952` + + Row (record, tuple) + represents a single, implicitly structured data item in a table. + + .. seealso:: + + Server + a computer system that provides services to other computing systems over a network. + + .. seealso:: + + Storage Engines + MySQL can use several different formats for storing data on disk, these + are called storage engines or table types. phpMyAdmin allows a user to + change their storage engine for a particular table through the operations + tab. + + Common table types are InnoDB and MyISAM, though many others exist and + may be desirable in some situations. + + .. seealso:: + + socket + a form of inter-process communication. + + .. seealso:: + + SSL + Secure Sockets Layer is a cryptographic protocol which provides secure + communication on the Internet. + + .. seealso:: + + Stored procedure + a subroutine available to applications accessing a relational database system + + .. seealso:: + + SQL + Structured Query Language + + .. seealso:: + + table + a set of data elements (cells) that is organized, defined and stored as + horizontal rows and vertical columns where each item can be uniquely + identified by a label or key or by it?s position in relation to other + items. + + .. seealso:: + + tar + a type of archive file format: the Tape ARchive format. + + .. seealso:: + + TCP + Transmission Control Protocol is one of the core protocols of the + Internet protocol suite. + + .. seealso:: + + TCPDF + PHP library to generate PDF files. + + .. seealso:: + + trigger + a procedural code that is automatically executed in response to certain events on a particular table or view in a database + + .. seealso:: + + unique key + An unique key is an index over one or more fields in a table which has a + unique value for each row. The first unique key will be treated as + :term:`primary key` if there is no *primary key* defined. + + URL + Uniform Resource Locator is a sequence of characters, conforming to a + standardized format, that is used for referring to resources, such as + documents and images on the Internet, by their location. + + .. seealso:: + + Webserver + A computer (program) that is responsible for accepting HTTP requests from clients and serving them Web pages. + + .. seealso:: + + XML + Extensible Markup Language is a W3C-recommended general- purpose markup + language for creating special-purpose markup languages, capable of + describing many different kinds of data. + + .. seealso:: + + ZIP + a popular data compression and archival format. + + .. seealso:: + + zlib + an open-source, cross- platform data compression library by Jean-loup Gailly and Mark Adler. + + .. seealso:: diff --git a/php/apps/phpmyadmin49/html/doc/html/_sources/import_export.txt b/php/apps/phpmyadmin49/html/doc/html/_sources/import_export.txt new file mode 100644 index 00000000..31f2fa9c --- /dev/null +++ b/php/apps/phpmyadmin49/html/doc/html/_sources/import_export.txt @@ -0,0 +1,350 @@ +Import and export +================= + +Import +++++++ + +To import data, go to the "Import" tab in phpMyAdmin. To import data into a +specific database or table, open the database or table before going to the +"Import" tab. + +In addition to the standard Import and Export tab, you can also import an SQL +file directly by dragging and dropping it from your local file manager to the +phpMyAdmin interface in your web browser. + +If you are having troubles importing big files, please consult :ref:`faq1_16`. + +You can import using following methods: + +Form based upload + + Can be used with any supported format, also (b|g)zipped files, e.g., mydump.sql.gz . + +Form based SQL Query + + Can be used with valid SQL dumps. + +Using upload directory + + You can specify an upload directory on your web server where phpMyAdmin is installed, after uploading your file into this directory you can select this file in the import dialog of phpMyAdmin, see :config:option:`$cfg['UploadDir']`. + +phpMyAdmin can import from several various commonly used formats. + +CSV +--- + +Comma separated values format which is often used by spreadsheets or various other programs for export/import. + +.. note:: + + When importing data into a table from a CSV file where the table has an + 'auto_increment' field, make the 'auto_increment' value for each record in + the CSV field to be '0' (zero). This allows the 'auto_increment' field to + populate correctly. + +It is now possible to import a CSV file at the server or database level. +Instead of having to create a table to import the CSV file into, a best-fit +structure will be determined for you and the data imported into it, instead. +All other features, requirements, and limitations are as before. + +CSV using LOAD DATA +------------------- + +Similar to CSV, only using the internal MySQL parser and not the phpMyAdmin one. + +ESRI Shape File +--------------- + +The ESRI shapefile or simply a shapefile is a popular geospatial vector data +format for geographic information systems software. It is developed and +regulated by Esri as a (mostly) open specification for data interoperability +among Esri and other software products. + +MediaWiki +--------- + +MediaWiki files, which can be exported by phpMyAdmin (version 4.0 or later), +can now also be imported. This is the format used by Wikipedia to display +tables. + +Open Document Spreadsheet (ODS) +------------------------------- + +OpenDocument workbooks containing one or more spreadsheets can now be directly imported. + +When importing an ODS speadsheet, the spreadsheet must be named in a specific way in order to make the +import as simple as possible. + +Table name +~~~~~~~~~~ + +During import, phpMyAdmin uses the sheet name as the table name; you should rename the +sheet in your spreadsheet program in order to match your existing table name (or the table you wish to create, +though this is less of a concern since you could quickly rename the new table from the Operations tab). + +Column names +~~~~~~~~~~~~ + +You should also make the first row of your spreadsheet a header with the names of the columns (this can be +accomplished by inserting a new row at the top of your spreadsheet). When on the Import screen, select the +checkbox for "The first line of the file contains the table column names;" this way your newly imported +data will go to the proper columns. + +.. note:: + + Formulas and calculations will NOT be evaluated, rather, their value from + the most recent save will be loaded. Please ensure that all values in the + spreadsheet are as needed before importing it. + +SQL +--- + +SQL can be used to make any manipulation on data, it is also useful for restoring backed up data. + +XML +--- + +XML files exported by phpMyAdmin (version 3.3.0 or later) can now be imported. +Structures (databases, tables, views, triggers, etc.) and/or data will be +created depending on the contents of the file. + +The supported xml schemas are not yet documented in this wiki. + +Export +++++++ + +phpMyAdmin can export into text files (even compressed) on your local disk (or +a special the webserver :config:option:`$cfg['SaveDir']` folder) in various +commonly used formats: + +CodeGen +------- + +`NHibernate `_ file format. Planned +versions: Java, Hibernate, PHP PDO, JSON, etc. So the preliminary name is +codegen. + +CSV +--- + +Comma separated values format which is often used by spreadsheets or various +other programs for export/import. + +CSV for Microsoft Excel +----------------------- + +This is just preconfigured version of CSV export which can be imported into +most English versions of Microsoft Excel. Some localised versions (like +"Danish") are expecting ";" instead of "," as field separator. + +Microsoft Word 2000 +------------------- + +If you're using Microsoft Word 2000 or newer (or compatible such as +OpenOffice.org), you can use this export. + +JSON +---- + +JSON (JavaScript Object Notation) is a lightweight data-interchange format. It +is easy for humans to read and write and it is easy for machines to parse and +generate. + +.. versionchanged:: 4.7.0 + + The generated JSON structure has been changed in phpMyAdmin 4.7.0 to + produce valid JSON data. + +The generated JSON is list of objects with following attributes: + +.. js:data:: type + + Type of given object, can be one of: + + ``header`` + Export header containing comment and phpMyAdmin version. + ``database`` + Start of a database marker, containing name of database. + ``table`` + Table data export. + +.. js:data:: version + + Used in ``header`` :js:data:`type` and indicates phpMyAdmin version. + +.. js:data:: comment + + Optional textual comment. + +.. js:data:: name + + Object name - either table or database based on :js:data:`type`. + +.. js:data:: database + + Database name for ``table`` :js:data:`type`. + +.. js:data:: data + + Table content for ``table`` :js:data:`type`. + +Sample output: + +.. code-block:: json + + [ + { + "comment": "Export to JSON plugin for PHPMyAdmin", + "type": "header", + "version": "4.7.0-dev" + }, + { + "name": "cars", + "type": "database" + }, + { + "data": [ + { + "car_id": "1", + "description": "Green Chrysler 300", + "make_id": "5", + "mileage": "113688", + "price": "13545.00", + "transmission": "automatic", + "yearmade": "2007" + } + ], + "database": "cars", + "name": "cars", + "type": "table" + }, + { + "data": [ + { + "make": "Chrysler", + "make_id": "5" + } + ], + "database": "cars", + "name": "makes", + "type": "table" + } + ] + +LaTeX +----- + +If you want to embed table data or structure in LaTeX, this is right choice for you. + +LaTeX is a typesetting system that is very suitable for producing scientific +and mathematical documents of high typographical quality. It is also suitable +for producing all sorts of other documents, from simple letters to complete +books. LaTeX uses TeX as its formatting engine. Learn more about TeX and +LaTeX on `the Comprehensive TeX Archive Network `_ +also see the `short description od TeX `_. + +The output needs to be embedded into a LaTeX document before it can be +rendered, for example in following document: + +.. code-block:: latex + + \documentclass{article} + \title{phpMyAdmin SQL output} + \author{} + \usepackage{longtable,lscape} + \date{} + \setlength{\parindent}{0pt} + \usepackage[left=2cm,top=2cm,right=2cm,nohead,nofoot]{geometry} + \pdfpagewidth 210mm + \pdfpageheight 297mm + \begin{document} + \maketitle + + % insert phpMyAdmin LaTeX Dump here + + \end{document} + +MediaWiki +--------- + +Both tables and databases can be exported in the MediaWiki format, which is +used by Wikipedia to display tables. It can export structure, data or both, +including table names or headers. + +OpenDocument Spreadsheet +------------------------ + +Open standard for spreadsheet data, which is being widely adopted. Many recent +spreadsheet programs, such as LibreOffice, OpenOffice, Microsoft Office or +Google Docs can handle this format. + +OpenDocument Text +----------------- + +New standard for text data which is being widely addopted. Most recent word +processors (such as LibreOffice, OpenOffice, Microsoft Word, AbiWord or KWord) +can handle this. + +PDF +--- + +For presentation purposes, non editable PDF might be best choice for you. + +PHP Array +--------- + +You can generate a php file which will declare a multidimensional array with +the contents of the selected table or database. + +SQL +--- + +Export in SQL can be used to restore your database, thus it is useful for +backing up. + +The option 'Maximal length of created query' seems to be undocumented. But +experiments has shown that it splits large extended INSERTS so each one is no +bigger than the given number of bytes (or characters?). Thus when importing the +file, for large tables you avoid the error "Got a packet bigger than +'max_allowed_packet' bytes". + +.. seealso:: + + https://dev.mysql.com/doc/refman/5.7/en/packet-too-large.html + +Data Options +~~~~~~~~~~~~ + +**Complete inserts** adds the column names to the SQL dump. This parameter +improves the readability and reliability of the dump. Adding the column names +increases the size of the dump, but when combined with Extended inserts it's +negligible. + +**Extended inserts** combines multiple rows of data into a single INSERT query. +This will significantly decrease filesize for large SQL dumps, increases the +INSERT speed when imported, and is generally recommended. + +.. seealso:: + + http://www.scriptalicious.com/blog/2009/04/complete-inserts-or-extended-inserts-in-phpmyadmin/ + +Texy! +----- + +`Texy! `_ markup format. You can see example on `Texy! demo +`_. + +XML +--- + +Easily parsable export for use with custom scripts. + +.. versionchanged:: 3.3.0 + + The XML schema used has changed as of version 3.3.0 + +YAML +---- + +YAML is a data serialization format which is both human readable and +computationally powerful ( ). diff --git a/php/apps/phpmyadmin49/html/doc/html/_sources/index.txt b/php/apps/phpmyadmin49/html/doc/html/_sources/index.txt new file mode 100644 index 00000000..f3372413 --- /dev/null +++ b/php/apps/phpmyadmin49/html/doc/html/_sources/index.txt @@ -0,0 +1,32 @@ +.. phpMyAdmin documentation master file, created by + sphinx-quickstart on Wed Sep 26 14:04:48 2012. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +Welcome to phpMyAdmin's documentation! +====================================== + +Contents: + +.. toctree:: + :maxdepth: 2 + + intro + require + setup + config + user + faq + developers + security + vendors + copyright + credits + glossary + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`search` +* :ref:`glossary` diff --git a/php/apps/phpmyadmin49/html/doc/html/_sources/intro.txt b/php/apps/phpmyadmin49/html/doc/html/_sources/intro.txt new file mode 100644 index 00000000..d81b00e0 --- /dev/null +++ b/php/apps/phpmyadmin49/html/doc/html/_sources/intro.txt @@ -0,0 +1,79 @@ +.. _intro: + +Introduction +============ + +phpMyAdmin is a free software tool written in PHP that is intended to handle the +administration of a MySQL or MariaDB database server. You can use phpMyAdmin to +perform most administration tasks, including creating a database, running queries, +and adding user accounts. + +Supported features +------------------ + +Currently phpMyAdmin can: + +* create, browse, edit, and drop databases, tables, views, columns, and indexes +* display multiple results sets through stored procedures or queries +* create, copy, drop, rename and alter databases, tables, columns and + indexes +* maintenance server, databases and tables, with proposals on server + configuration +* execute, edit and bookmark any :term:`SQL`-statement, even batch-queries +* load text files into tables +* create [#f1]_ and read dumps of tables +* export [#f1]_ data to various formats: :term:`CSV`, :term:`XML`, :term:`PDF`, + :term:`ISO`/:term:`IEC` 26300 - :term:`OpenDocument` Text and Spreadsheet, Microsoft + Word 2000, and LATEX formats +* import data and :term:`MySQL` structures from :term:`OpenDocument` spreadsheets, as + well as :term:`XML`, :term:`CSV`, and :term:`SQL` files +* administer multiple servers +* add, edit, and remove MySQL user accounts and privileges +* check referential integrity in MyISAM tables +* using Query-by-example (QBE), create complex queries automatically + connecting required tables +* create :term:`PDF` graphics of your + database layout +* search globally in a database or a subset of it +* transform stored data into any format using a set of predefined + functions, like displaying BLOB-data as image or download-link +* track changes on databases, tables and views +* support InnoDB tables and foreign keys +* support mysqli, the improved MySQL extension see :ref:`faq1_17` +* create, edit, call, export and drop stored procedures and functions +* create, edit, export and drop events and triggers +* communicate in `80 different languages + `_ + +Shortcut keys +------------- + +Currently phpMyAdmin supports following shortcuts: + +* k - Toggle console +* h - Go to home page +* s - Open settings +* d + s - Go to database structure (Provided you are in database related page) +* d + f - Search database (Provided you are in database related page) +* t + s - Go to table structure (Provided you are in table related page) +* t + f - Search table (Provided you are in table related page) +* backspace - Takes you to older page. + +A word about users +------------------ + +Many people have difficulty understanding the concept of user +management with regards to phpMyAdmin. When a user logs in to +phpMyAdmin, that username and password are passed directly to MySQL. +phpMyAdmin does no account management on its own (other than allowing +one to manipulate the MySQL user account information); all users must +be valid MySQL users. + +.. rubric:: Footnotes + +.. [#f1] + + phpMyAdmin can compress (:term:`Zip`, :term:`GZip` or :term:`RFC 1952` + formats) dumps and :term:`CSV` exports if you use PHP with + :term:`Zlib` support (``--with-zlib``). + Proper support may also need changes in :file:`php.ini`. diff --git a/php/apps/phpmyadmin49/html/doc/html/_sources/other.txt b/php/apps/phpmyadmin49/html/doc/html/_sources/other.txt new file mode 100644 index 00000000..98d1dca1 --- /dev/null +++ b/php/apps/phpmyadmin49/html/doc/html/_sources/other.txt @@ -0,0 +1,32 @@ +Other sources of information +============================ + +Printed Book +------------ + +The definitive guide to using phpMyAdmin is the book Mastering phpMyAdmin for +Effective MySQL Management by Marc Delisle. You can get information on that +book and other officially endorsed `books at the phpMyAdmin site`_. + +.. _books at the phpMyAdmin site: https://www.phpmyadmin.net/docs/ + +Tutorials +--------- + +Third party tutorials and articles which you might find interesting: + +Česky (Czech) ++++++++++++++ + +- `Seriál o phpMyAdminovi `_ + +English ++++++++ + +- `Having fun with phpMyAdmin's MIME-transformations & PDF-features `_ +- `Learning SQL Using phpMyAdmin (old tutorial) `_ + +Русский (Russian) ++++++++++++++++++ + +* `Russian server about phpMyAdmin `_ diff --git a/php/apps/phpmyadmin49/html/doc/html/_sources/privileges.txt b/php/apps/phpmyadmin49/html/doc/html/_sources/privileges.txt new file mode 100644 index 00000000..f3b17791 --- /dev/null +++ b/php/apps/phpmyadmin49/html/doc/html/_sources/privileges.txt @@ -0,0 +1,74 @@ +User management +=============== + +User management is the process of controlling which users are allowed to +connect to the MySQL server and what permissions they have on each database. +phpMyAdmin does not handle user management, rather it passes the username and +password on to MySQL, which then determines whether a user is permitted to +perform a particular action. Within phpMyAdmin, administrators have full +control over creating users, viewing and editing privileges for existing users, +and removing users. + +Within phpMyAdmin, user management is controlled via the :guilabel:`Users` link +from the main page. Users can be created, edited, and removed. + +Creating a new user +------------------- + +To create a new user, click the :guilabel:`Add a new user` link near the bottom +of the :guilabel:`Users` page (you must be a "superuser", e.g., user "root"). +Use the textboxes and drop-downs to configure the user to your particular +needs. You can then select whether to create a database for that user and grant +specific global privileges. Once you've created the user (by clicking Go), you +can define that user's permissions on a specific database (don't grant global +privileges in that case). In general, users do not need any global privileges +(other than USAGE), only permissions for their specific database. + +Editing an existing user +------------------------ + +To edit an existing user, simply click the pencil icon to the right of that +user in the :guilabel:`Users` page. You can then edit their global- and +database-specific privileges, change their password, or even copy those +privileges to a new user. + +Deleting a user +--------------- + +From the :guilabel:`Users` page, check the checkbox for the user you wish to +remove, select whether or not to also remove any databases of the same name (if +they exist), and click Go. + +Assigning privileges to user for a specific database +---------------------------------------------------- + +Users are assigned to databases by editing the user record (from the +:guilabel:`User accounts` link on the home page). +If you are creating a user specifically for a given table +you will have to create the user first (with no global privileges) and then go +back and edit that user to add the table and privileges for the individual +table. + +.. _configurablemenus: + +Configurable menus and user groups +---------------------------------- + +By enabling :config:option:`$cfg['Servers'][$i]['usergroups']` and +:config:option:`$cfg['Servers'][$i]['usergroups']` you can customize what users +will see in the phpMyAdmin navigation. + +.. warning:: + + This feature only limits what a user sees, he is still able to use all the + functions. So this can not be considered as a security limitation. Should + you want to limit what users can do, use MySQL privileges to achieve that. + +With this feature enabled, the :guilabel:`User accounts` management interface gains +a second tab for managing :guilabel:`User groups`, where you can define what each +group will view (see image below) and you can then assign each user to one of +these groups. Users will be presented with a simplified user interface, which might be +useful for inexperienced users who could be overwhelmed by all the features +phpMyAdmin provides. + +.. image:: images/usergroups.png diff --git a/php/apps/phpmyadmin49/html/doc/html/_sources/relations.txt b/php/apps/phpmyadmin49/html/doc/html/_sources/relations.txt new file mode 100644 index 00000000..96643dee --- /dev/null +++ b/php/apps/phpmyadmin49/html/doc/html/_sources/relations.txt @@ -0,0 +1,84 @@ +.. _relations: + +Relations +========= + +phpMyAdmin allows relationships (similar to foreign keys) using MySQL-native +(InnoDB) methods when available and falling back on special phpMyAdmin-only +features when needed. There are two ways of editing these relations, with the +*relation view* and the drag-and-drop *designer* -- both of which are explained +on this page. + +.. note:: + + You need to have configured the :ref:`linked-tables` for using phpMyAdmin + only relations. + +Technical info +-------------- + +Currently the only MySQL table type that natively supports relationships is +InnoDB. When using an InnoDB table, phpMyAdmin will create real InnoDB +relations which will be enforced by MySQL no matter which application accesses +the database. In the case of any other table type, phpMyAdmin enforces the +relations internally and those relations are not applied to any other +application. + +Relation view +------------- + +In order to get it working, you first have to properly create the +[[pmadb|pmadb]]. Once that is setup, select a table's "Structure" page. Below +the table definition, a link called "Relation view" is shown. If you click that +link, a page will be shown that offers you to create a link to another table +for any (most) fields. Only PRIMARY KEYS are shown there, so if the field you +are referring to is not shown, you most likely are doing something wrong. The +drop-down at the bottom is the field which will be used as the name for a +record. + +Relation view example ++++++++++++++++++++++ + +.. image:: images/pma-relations-relation-view-link.png + +.. image:: images/pma-relations-relation-link.png + +Let's say you have categories and links and one category can contain several links. Your table structure would be something like this: + +- `category.category_id` (must be unique) +- `category.name` +- `link.link_id` +- `link.category_id` +- `link.uri`. + +Open the relation view (below the table structure) page for the `link` table and for `category_id` field, you select `category.category_id` as master record. + +If you now browse the link table, the `category_id` field will be a clickable hyperlink to the proper category record. But all you see is just the `category_id`, not the name of the category. + +.. image:: images/pma-relations-relation-name.png + +To fix this, open the relation view of the `category` table and in the drop down at the bottom, select "name". If you now browse the link table again and hover the mouse over the `category_id` hyperlink, the value from the related category will be shown as tooltip. + +.. image:: images/pma-relations-links.png + +Designer +-------- + +The Designer feature is a graphical way of creating, editing, and displaying +phpMyAdmin relations. These relations are compatible with those created in +phpMyAdmin's relation view. + +To use this feature, you need a properly configured :ref:`linked-tables` and +must have the :config:option:`$cfg['Servers'][$i]['table_coords']` configured. + +To use the designer, select a database's structure page, then look for the +:guilabel:`Designer` tab. + +To export the view into PDF, you have to create PDF pages first. The Designer +creates the layout, how the tables shall be displayed. To finally export the +view, you have to create this with a PDF page and select your layout, which you +have created with the designer. + +.. seealso:: + + :ref:`faqpdf` diff --git a/php/apps/phpmyadmin49/html/doc/html/_sources/require.txt b/php/apps/phpmyadmin49/html/doc/html/_sources/require.txt new file mode 100644 index 00000000..e9a48e50 --- /dev/null +++ b/php/apps/phpmyadmin49/html/doc/html/_sources/require.txt @@ -0,0 +1,60 @@ +.. _require: + +Requirements +============ + +Web server +---------- + +Since phpMyAdmin's interface is based entirely in your browser, you'll need a +web server (such as Apache, nginx, :term:`IIS`) to install phpMyAdmin's files into. + +PHP +--- + +* You need PHP 5.5.0 or newer, with ``session`` support, the Standard PHP Library + (SPL) extension, hash, ctype, and JSON support. + +* The ``mbstring`` extension (see :term:`mbstring`) is strongly recommended + for performance reasons. + +* To support uploading of ZIP files, you need the PHP ``zip`` extension. + +* You need GD2 support in PHP to display inline thumbnails of JPEGs + ("image/jpeg: inline") with their original aspect ratio. + +* When using the cookie authentication (the default), the `openssl + `_ extension is strongly suggested. + +* To support upload progress bars, see :ref:`faq2_9`. + +* To support XML and Open Document Spreadsheet importing, you need the + `libxml `_ extension. + +* To support reCAPTCHA on the login page, you need the + `openssl `_ extension. + +* To support displaying phpMyAdmin's latest version, you need to enable + ``allow_url_open`` in your :file:`php.ini` or to have the + `curl `_ extension. + +.. seealso:: :ref:`faq1_31`, :ref:`authentication_modes` + +Database +-------- + +phpMyAdmin supports MySQL-compatible databases. + +* MySQL 5.5 or newer +* MariaDB 5.5 or newer + +.. seealso:: :ref:`faq1_17` + +Web browser +----------- + +To access phpMyAdmin you need a web browser with cookies and JavaScript +enabled. + +You need browser which is supported by jQuery 2.0, see +. diff --git a/php/apps/phpmyadmin49/html/doc/html/_sources/security.txt b/php/apps/phpmyadmin49/html/doc/html/_sources/security.txt new file mode 100644 index 00000000..d6d93ec0 --- /dev/null +++ b/php/apps/phpmyadmin49/html/doc/html/_sources/security.txt @@ -0,0 +1,113 @@ +Security policy +=============== + +The phpMyAdmin developer team is putting lot of effort to make phpMyAdmin as +secure as possible. But still web application like phpMyAdmin can be vulnerable +to a number of attacks and new ways to exploit are still being explored. + +For every reported vulnerability we issue a phpMyAdmin Security Announcement +(PMASA) and it get's assigne CVE ID as well. We might group similar +vulnerabilities to one PMASA (eg. multiple XSS vulnerabilities can be announced +under one PMASA). + +If you think you've found a vulnerability, please see :ref:`reporting-security`. + +Typical vulnerabilities +----------------------- + +In this secion, we will describe typical vulnerabilities, which can appear in +our code base. This list is by no means complete, it is intended to show +typical attack surface. + +Cross-site scripting (XSS) +++++++++++++++++++++++++++ + +When phpMyAdmin shows a piece of user data, e.g. something inside a user's +database, all html special chars have to be escaped. When this escaping is +missing somewhere a malicious user might fill a database with specially crafted +content to trick an other user of that database into executing something. This +could for example be a piece of JavaScript code that would do any number of +nasty things. + +phpMyAdmin tries to escape all userdata before it is rendered into html for the +browser. + +.. seealso:: + + `Cross-site scripting on Wikipedia `_ + +Cross-site request forgery (CSRF) ++++++++++++++++++++++++++++++++++ + +An attacker would trick a phpMyAdmin user into clicking on a link to provoke +some action in phpMyAdmin. This link could either be sent via email or some +random website. If successful this the attacker would be able to perform some +action with the users privileges. + +To mitigate this phpMyAdmin requires a token to be sent on sensitive requests. +The idea is that an attacker does not poses the currently valid token to +include in the presented link. + +The token is regenerated for every login, so it's generally valid only for +limited time, what makes it harder for attacker to obtain valid one. + +.. seealso:: + + `Cross-site request forgery on Wikipedia `_ + +SQL injection ++++++++++++++ + +As the whole purpose of phpMyAdmin is to preform sql queries, this is not our +first concern. SQL injection is sensitive to us though when it concerns the +mysql control connection. This controlconnection can have additional privileges +which the logged in user does not poses. E.g. access the :ref:`linked-tables`. + +User data that is included in (administrative) queries should always be run +through DatabaseInterface::escapeSring(). + +.. seealso:: + + `SQL injection on Wikipedia `_ + +Brute force attack +++++++++++++++++++ + +phpMyAdmin on its own does not rate limit authentication attempts in any way. +This is caused by need to work in stateless environment, where there is no way +to protect against such kind of things. + +To mitigate this, you can use Captcha or utilize external tools such as +fail2ban, this is more details described in :ref:`securing`. + +.. seealso:: + + `Brute force attack on Wikipedia `_ + +.. _reporting-security: + +Reporting security issues +------------------------- + +Should you find a security issue in the phpMyAdmin programming code, please +contact the `phpMyAdmin security team `_ in +advance before publishing it. This way we can prepare a fix and release the fix together with your +announcement. You will be also given credit in our security announcement. +You can optionally encrypt your report with PGP key ID +``DA68AB39218AB947`` with following fingerprint: + +.. code-block:: console + + pub 4096R/DA68AB39218AB947 2016-08-02 + Key fingerprint = 5BAD 38CF B980 50B9 4BD7 FB5B DA68 AB39 218A B947 + uid phpMyAdmin Security Team <security@phpmyadmin.net> + sub 4096R/5E4176FB497A31F7 2016-08-02 + +The key can be either obtained from the keyserver or is available in +`phpMyAdmin keyring `_ +available on our download server or using `Keybase `_. + +Should you have suggestion on improving phpMyAdmin to make it more secure, please +report that to our `issue tracker `_. +Existing improvement suggestions can be found by +`hardening label `_. diff --git a/php/apps/phpmyadmin49/html/doc/html/_sources/settings.txt b/php/apps/phpmyadmin49/html/doc/html/_sources/settings.txt new file mode 100644 index 00000000..be36b80d --- /dev/null +++ b/php/apps/phpmyadmin49/html/doc/html/_sources/settings.txt @@ -0,0 +1,21 @@ +Configuring phpMyAdmin +---------------------- + +There are many configuration settings that can be used to customize the +interface. Those settings are described in +:ref:`config`. There are several layers of the configuration. + +The global settings can be configured in :file:`config.inc.php` as described in +:ref:`config`. This is only way to configure connections to databases and other +system wide settings. + +On top of this there are user settings which can be persistently stored in +:ref:`linked-tables`, possibly automatically configured through +:ref:`zeroconf`. If the :ref:`linked-tables` are not configured, the settings +are temporarily stored in the session data; these are valid only until you +logout. + +You can also save the user configuration for further use, either download them +as a file or to the browser local storage. You can find both those options in +the :guilabel:`Settings` tab. The settings stored in browser local storage will +be automatically offered for loading upon your login to phpMyAdmin. diff --git a/php/apps/phpmyadmin49/html/doc/html/_sources/setup.txt b/php/apps/phpmyadmin49/html/doc/html/_sources/setup.txt new file mode 100644 index 00000000..36e56c0e --- /dev/null +++ b/php/apps/phpmyadmin49/html/doc/html/_sources/setup.txt @@ -0,0 +1,1117 @@ +.. _setup: + +Installation +============ + +phpMyAdmin does not apply any special security methods to the MySQL +database server. It is still the system administrator's job to grant +permissions on the MySQL databases properly. phpMyAdmin's :guilabel:`Users` +page can be used for this. + +.. warning:: + + :term:`Mac` users should note that if you are on a version before + :term:`Mac OS X`, StuffIt unstuffs with :term:`Mac` formats. So you'll have + to resave as in BBEdit to Unix style ALL phpMyAdmin scripts before + uploading them to your server, as PHP seems not to like :term:`Mac`-style + end of lines character ("``\r``"). + +Linux distributions ++++++++++++++++++++ + +phpMyAdmin is included in most Linux distributions. It is recommended to use +distribution packages when possible - they usually provide integration to your +distribution and you will automatically get security updates from your distribution. + +.. _debian-package: + +Debian and Ubuntu +----------------- + +Debian's package repositories include a phpMyAdmin package, but be aware that +the configuration file is maintained in ``/etc/phpmyadmin`` and may differ in +some ways from the official phpMyAdmin documentation. Specifically it does: + +* Configuration of web server (works for Apache and lighttpd). +* Creating of :ref:`linked-tables` using dbconfig-common. +* Securing setup script, see :ref:`debian-setup`. + +.. seealso:: + + More information can be found in `README.Debian `_ + (it is installed as :file:`/usr/share/doc/phmyadmin/README.Debian` with the package). + +OpenSUSE +-------- + +OpenSUSE already comes with phpMyAdmin package, just install packages from +the `openSUSE Build Service `_. + +Gentoo +------ + +Gentoo ships the phpMyAdmin package, both in a near stock configuration as well +as in a ``webapp-config`` configuration. Use ``emerge dev-db/phpmyadmin`` to +install. + +Mandriva +-------- + +Mandriva ships the phpMyAdmin package in their ``contrib`` branch and can be +installed via the usual Control Center. + +Fedora +------ + +Fedora ships the phpMyAdmin package, but be aware that the configuration file +is maintained in ``/etc/phpMyAdmin/`` and may differ in some ways from the +official phpMyAdmin documentation. + +Red Hat Enterprise Linux +------------------------ + +Red Hat Enterprise Linux itself and thus derivatives like CentOS don't +ship phpMyAdmin, but the Fedora-driven repository +`Extra Packages for Enterprise Linux (EPEL) `_ +is doing so, if it's +`enabled `_. +But be aware that the configuration file is maintained in +``/etc/phpMyAdmin/`` and may differ in some ways from the +official phpMyAdmin documentation. + +Installing on Windows ++++++++++++++++++++++ + +The easiest way to get phpMyAdmin on Windows is using third party products +which include phpMyAdmin together with a database and web server such as +`XAMPP `_. + +You can find more of such options at `Wikipedia `_. + +Installing from Git ++++++++++++++++++++ + +You can clone current phpMyAdmin source from +``https://github.com/phpmyadmin/phpmyadmin.git``: + +.. code-block:: sh + + git clone https://github.com/phpmyadmin/phpmyadmin.git + +Additionally you need to install dependencies using the `Composer tool`_: + +.. code-block:: sh + + composer update + +If you do not intend to develop, you can skip the installation of developer tools +by invoking: + +.. code-block:: sh + + composer update --no-dev + +.. _composer: + +Installing using Composer ++++++++++++++++++++++++++ + +You can install phpMyAdmin using the `Composer tool`_, since 4.7.0 the releases +are automatically mirrored to the default `Packagist`_ repository. + +.. note:: + + The content of the Composer repository is automatically generated + separately from the releases, so the content doesn't have to be + 100% same as when you download the tarball. There should be no + functional differences though. + +To install phpMyAdmin simply run: + +.. code-block:: sh + + composer create-project phpmyadmin/phpmyadmin + +Alternatively you can use our own composer repository, which contains +the release tarballs and is available at +: + +.. code-block:: sh + + composer create-project phpmyadmin/phpmyadmin --repository-url=https://www.phpmyadmin.net/packages.json --no-dev + +.. _docker: + +Installing using Docker ++++++++++++++++++++++++ + +phpMyAdmin comes with a `Docker image`_, which you can easily deploy. You can +download it using: + +.. code-block:: sh + + docker pull phpmyadmin/phpmyadmin + +The phpMyAdmin server will listen on port 80. It supports several ways of +configuring the link to the database server, either by Docker's link feature +by linking your database container to ``db`` for phpMyAdmin (by specifying +``--link your_db_host:db``) or by environment variables (in this case it's up +to you to set up networking in Docker to allow the phpMyAdmin container to access +the database container over network). + +.. _docker-vars: + +Docker environment variables +---------------------------- + +You can configure several phpMyAdmin features using environment variables: + +.. envvar:: PMA_ARBITRARY + + Allows you to enter a database server hostname on login form. + + .. seealso:: :config:option:`$cfg['AllowArbitraryServer']` + +.. envvar:: PMA_HOST + + Host name or IP address of the database server to use. + + .. seealso:: :config:option:`$cfg['Servers'][$i]['host']` + +.. envvar:: PMA_HOSTS + + Comma-separated host names or IP addresses of the database servers to use. + + .. note:: Used only if :envvar:`PMA_HOST` is empty. + +.. envvar:: PMA_VERBOSE + + Verbose name of the database server. + + .. seealso:: :config:option:`$cfg['Servers'][$i]['verbose']` + +.. envvar:: PMA_VERBOSES + + Comma-separated verbose name of the database servers. + + .. note:: Used only if :envvar:`PMA_VERBOSE` is empty. + +.. envvar:: PMA_USER + + User name to use for :ref:`auth_config`. + +.. envvar:: PMA_PASSWORD + + Password to use for :ref:`auth_config`. + +.. envvar:: PMA_PORT + + Port of the database server to use. + +.. envvar:: PMA_PORTS + + Comma-separated ports of the database server to use. + + .. note:: Used only if :envvar:`PMA_PORT` is empty. + +.. envvar:: PMA_ABSOLUTE_URI + + The fully-qualified path (``https://pma.example.net/``) where the reverse + proxy makes phpMyAdmin available. + + .. seealso:: :config:option:`$cfg['PmaAbsoluteUri']` + +By default, :ref:`cookie` is used, but if :envvar:`PMA_USER` and +:envvar:`PMA_PASSWORD` are set, it is switched to :ref:`auth_config`. + +.. note:: + + The credentials you need to log in are stored in the MySQL server, in case + of Docker image there are various ways to set it (for example + :samp:`MYSQL_ROOT_PASSWORD` when starting the MySQL container). Please check + documentation for `MariaDB container `_ + or `MySQL container `_. + +.. _docker-custom: + +Customizing configuration +------------------------- + +Additionally configuration can be tweaked by :file:`/etc/phpmyadmin/config.user.inc.php`. If +this file exists, it will be loaded after configuration is generated from above +environment variables, so you can override any configuration variable. This +configuration can be added as a volume when invoking docker using +`-v /some/local/directory/config.user.inc.php:/etc/phpmyadmin/config.user.inc.php` parameters. + +Note that the supplied configuration file is applied after :ref:`docker-vars`, +but you can override any of the values. + +For example to change default behaviour of CSV export you can use following +configuration file: + +.. code-block:: php + + + +You can also use it to define server configuration instead of using the +environment variables listed in :ref:`docker-vars`: + +.. code-block:: php + + [ + 'auth_type' => 'cookie', + 'host' => 'mydb1', + 'port' => 3306, + 'verbose' => 'Verbose name 1', + ], + 2 => [ + 'auth_type' => 'cookie', + 'host' => 'mydb2', + 'port' => 3306, + 'verbose' => 'Verbose name 2', + ], + ]; + +.. seealso:: + + See :ref:`config` for detailed description of configuration options. + +Docker Volumes +-------------- + +You can use following volumes to customize image behavior: + +:file:`/etc/phpmyadmin/config.user.inc.php` + + Can be used for additional settings, see previous chapter for more details. + +:file:`/sessions/` + + Directory where PHP sessions are stored. You might want to share this + for example when using :ref:`auth_signon`. + +:file:`/www/themes/` + + Directory where phpMyAdmin looks for themes. By default only those shipped + with phpMyAdmin are included, but you can include additional phpMyAdmin + themes (see :ref:`themes`) by using Docker volumes. + +Docker Examples +--------------- + +To connect phpMyAdmin to a given server use: + +.. code-block:: sh + + docker run --name myadmin -d -e PMA_HOST=dbhost -p 8080:80 phpmyadmin/phpmyadmin + +To connect phpMyAdmin to more servers use: + +.. code-block:: sh + + docker run --name myadmin -d -e PMA_HOSTS=dbhost1,dbhost2,dbhost3 -p 8080:80 phpmyadmin/phpmyadmin + +To use arbitrary server option: + +.. code-block:: sh + + docker run --name myadmin -d --link mysql_db_server:db -p 8080:80 -e PMA_ARBITRARY=1 phpmyadmin/phpmyadmin + +You can also link the database container using Docker: + +.. code-block:: sh + + docker run --name phpmyadmin -d --link mysql_db_server:db -p 8080:80 phpmyadmin/phpmyadmin + +Running with additional configuration: + +.. code-block:: sh + + docker run --name phpmyadmin -d --link mysql_db_server:db -p 8080:80 -v /some/local/directory/config.user.inc.php:/etc/phpmyadmin/config.user.inc.php phpmyadmin/phpmyadmin + +Running with additional themes: + +.. code-block:: sh + + docker run --name phpmyadmin -d --link mysql_db_server:db -p 8080:80 -v /custom/phpmyadmin/theme/:/www/themes/theme/ phpmyadmin/phpmyadmin + +Using docker-compose +-------------------- + +Alternatively you can also use docker-compose with the docker-compose.yml from +. This will run phpMyAdmin with an +arbitrary server - allowing you to specify MySQL/MariaDB server on login page. + +.. code-block:: sh + + docker-compose up -d + +Customizing configuration file using docker-compose +--------------------------------------------------- + +You can use an external file to customize phpMyAdmin configuration and pass it +using the volumes directive: + +.. code-block:: yaml + + phpmyadmin: + image: phpmyadmin/phpmyadmin + container_name: phpmyadmin + environment: + - PMA_ARBITRARY=1 + restart: always + ports: + - 8080:80 + volumes: + - /sessions + - ~/docker/phpmyadmin/config.user.inc.php:/etc/phpmyadmin/config.user.inc.php + - /custom/phpmyadmin/theme/:/www/themes/theme/ + +.. seealso:: :ref:`docker-custom` + +Running behind haproxy in a subdirectory +---------------------------------------- + +When you want to expose phpMyAdmin running in a Docker container in a +subdirectory, you need to rewrite the request path in the server proxying the +requests. + +For example using haproxy it can be done as: + +.. code-block:: text + + frontend http + bind *:80 + option forwardfor + option http-server-close + + ### NETWORK restriction + acl LOCALNET src 10.0.0.0/8 192.168.0.0/16 172.16.0.0/12 + + # /phpmyadmin + acl phpmyadmin path_dir /phpmyadmin + use_backend phpmyadmin if phpmyadmin LOCALNET + + backend phpmyadmin + mode http + + reqirep ^(GET|POST|HEAD)\ /phpmyadmin/(.*) \1\ /\2 + + # phpMyAdmin container IP + server localhost 172.30.21.21:80 + +When using traefik, something like following should work: + +.. code-block:: text + + defaultEntryPoints = ["http"] + [entryPoints] + [entryPoints.http] + address = ":80" + [entryPoints.http.redirect] + regex = "(http:\\/\\/[^\\/]+\\/([^\\?\\.]+)[^\\/])$" + replacement = "$1/" + + [backends] + [backends.myadmin] + [backends.myadmin.servers.myadmin] + url="http://internal.address.to.pma" + + [frontends] + [frontends.myadmin] + backend = "myadmin" + passHostHeader = true + [frontends.myadmin.routes.default] + rule="PathPrefixStrip:/phpmyadmin/;AddPrefix:/" + +You then should specify :envvar:`PMA_ABSOLUTE_URI` in the docker-compose +configuration: + +.. code-block:: yaml + + version: '2' + + services: + phpmyadmin: + restart: always + image: phpmyadmin/phpmyadmin + container_name: phpmyadmin + hostname: phpmyadmin + domainname: example.com + ports: + - 8000:80 + environment: + - PMA_HOSTS=172.26.36.7,172.26.36.8,172.26.36.9,172.26.36.10 + - PMA_VERBOSES=production-db1,production-db2,dev-db1,dev-db2 + - PMA_USER=root + - PMA_PASSWORD= + - PMA_ABSOLUTE_URI=http://example.com/phpmyadmin/ + +.. _quick_install: + +Quick Install ++++++++++++++ + +#. Choose an appropriate distribution kit from the phpmyadmin.net + Downloads page. Some kits contain only the English messages, others + contain all languages. We'll assume you chose a kit whose name + looks like ``phpMyAdmin-x.x.x -all-languages.tar.gz``. +#. Ensure you have downloaded a genuine archive, see :ref:`verify`. +#. Untar or unzip the distribution (be sure to unzip the subdirectories): + ``tar -xzvf phpMyAdmin_x.x.x-all-languages.tar.gz`` in your + webserver's document root. If you don't have direct access to your + document root, put the files in a directory on your local machine, + and, after step 4, transfer the directory on your web server using, + for example, ftp. +#. Ensure that all the scripts have the appropriate owner (if PHP is + running in safe mode, having some scripts with an owner different from + the owner of other scripts will be a problem). See :ref:`faq4_2` and + :ref:`faq1_26` for suggestions. +#. Now you must configure your installation. There are two methods that + can be used. Traditionally, users have hand-edited a copy of + :file:`config.inc.php`, but now a wizard-style setup script is provided + for those who prefer a graphical installation. Creating a + :file:`config.inc.php` is still a quick way to get started and needed for + some advanced features. + +Manually creating the file +-------------------------- + +To manually create the file, simply use your text editor to create the +file :file:`config.inc.php` (you can copy :file:`config.sample.inc.php` to get +a minimal configuration file) in the main (top-level) phpMyAdmin +directory (the one that contains :file:`index.php`). phpMyAdmin first +loads :file:`libraries/config.default.php` and then overrides those values +with anything found in :file:`config.inc.php`. If the default value is +okay for a particular setting, there is no need to include it in +:file:`config.inc.php`. You'll probably need only a few directives to get going; a +simple configuration may look like this: + +.. code-block:: xml+php + + + +Or, if you prefer to not be prompted every time you log in: + +.. code-block:: xml+php + + + +.. warning:: + + Storing passwords in the configuration is insecure as anybody can then + manipulate your database. + +For a full explanation of possible configuration values, see the +:ref:`config` of this document. + +.. index:: Setup script + +.. _setup_script: + +Using Setup script +------------------ + +Instead of manually editing :file:`config.inc.php`, you can use phpMyAdmin's +setup feature. The file can be generated using the setup and you can download it +for upload to the server. + +Next, open your browser and visit the location where you installed phpMyAdmin, +with the ``/setup`` suffix. The changes are not saved to the server, you need to +use the :guilabel:`Download` button to save them to your computer and then upload +to the server. + +Now the file is ready to be used. You can choose to review or edit the +file with your favorite editor, if you prefer to set some advanced +options which the setup script does not provide. + +#. If you are using the ``auth_type`` "config", it is suggested that you + protect the phpMyAdmin installation directory because using config + does not require a user to enter a password to access the phpMyAdmin + installation. Use of an alternate authentication method is + recommended, for example with HTTP–AUTH in a :term:`.htaccess` file or switch to using + ``auth_type`` cookie or http. See the :ref:`faqmultiuser` + for additional information, especially :ref:`faq4_4`. +#. Open the main phpMyAdmin directory in your browser. + phpMyAdmin should now display a welcome screen and your databases, or + a login dialog if using :term:`HTTP` or + cookie authentication mode. + +.. _debian-setup: + +Setup script on Debian, Ubuntu and derivatives +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Debian and Ubuntu have changed way how setup is enabled and disabled, in a way +that single command has to be executed for either of these. + +To allow editing configuration invoke: + +.. code-block:: sh + + /usr/sbin/pma-configure + +To block editing configuration invoke: + +.. code-block:: sh + + /usr/sbin/pma-secure + +Setup script on openSUSE +~~~~~~~~~~~~~~~~~~~~~~~~ + +Some openSUSE releases do not include setup script in the package. In case you +want to generate configuration on these you can either download original +package from or use setup script on our demo +server: . + +.. _verify: + +Verifying phpMyAdmin releases ++++++++++++++++++++++++++++++ + +Since July 2015 all phpMyAdmin releases are cryptographically signed by the +releasing developer, who through January 2016 was Marc Delisle. His key id is +0xFEFC65D181AF644A, his PGP fingerprint is: + +.. code-block:: console + + 436F F188 4B1A 0C3F DCBF 0D79 FEFC 65D1 81AF 644A + +and you can get more identification information from . + +Beginning in January 2016, the release manager is Isaac Bennetch. His key id is +0xCE752F178259BD92, and his PGP fingerprint is: + +.. code-block:: console + + 3D06 A59E CE73 0EB7 1B51 1C17 CE75 2F17 8259 BD92 + +and you can get more identification information from . + +Some additional downloads (for example themes) might be signed by Michal Čihař. His key id is +0x9C27B31342B7511D, and his PGP fingerprint is: + +.. code-block:: console + + 63CB 1DF1 EF12 CF2A C0EE 5A32 9C27 B313 42B7 511D + +and you can get more identification information from . + +You should verify that the signature matches the archive you have downloaded. +This way you can be sure that you are using the same code that was released. +You should also verify the date of the signature to make sure that you +downloaded the latest version. + +Each archive is accompanied with ``.asc`` files which contains the PGP signature +for it. Once you have both of them in the same folder, you can verify the signature: + +.. code-block:: console + + $ gpg --verify phpMyAdmin-4.5.4.1-all-languages.zip.asc + gpg: Signature made Fri 29 Jan 2016 08:59:37 AM EST using RSA key ID 8259BD92 + gpg: Can't check signature: public key not found + +As you can see gpg complains that it does not know the public key. At this +point you should do one of the following steps: + +* Download the keyring from `our download server `_, then import it with: + +.. code-block:: console + + $ gpg --import phpmyadmin.keyring + +* Download and import the key from one of the key servers: + +.. code-block:: console + + $ gpg --keyserver hkp://pgp.mit.edu --recv-keys 3D06A59ECE730EB71B511C17CE752F178259BD92 + gpg: requesting key 8259BD92 from hkp server pgp.mit.edu + gpg: key 8259BD92: public key "Isaac Bennetch " imported + gpg: no ultimately trusted keys found + gpg: Total number processed: 1 + gpg: imported: 1 (RSA: 1) + +This will improve the situation a bit - at this point you can verify that the +signature from the given key is correct but you still can not trust the name used +in the key: + +.. code-block:: console + + $ gpg --verify phpMyAdmin-4.5.4.1-all-languages.zip.asc + gpg: Signature made Fri 29 Jan 2016 08:59:37 AM EST using RSA key ID 8259BD92 + gpg: Good signature from "Isaac Bennetch " + gpg: aka "Isaac Bennetch " + gpg: WARNING: This key is not certified with a trusted signature! + gpg: There is no indication that the signature belongs to the owner. + Primary key fingerprint: 3D06 A59E CE73 0EB7 1B51 1C17 CE75 2F17 8259 BD92 + +The problem here is that anybody could issue the key with this name. You need to +ensure that the key is actually owned by the mentioned person. The GNU Privacy +Handbook covers this topic in the chapter `Validating other keys on your public +keyring`_. The most reliable method is to meet the developer in person and +exchange key fingerprints, however you can also rely on the web of trust. This way +you can trust the key transitively though signatures of others, who have met +the developer in person. For example you can see how `Isaac's key links to +Linus's key`_. + +Once the key is trusted, the warning will not occur: + +.. code-block:: console + + $ gpg --verify phpMyAdmin-4.5.4.1-all-languages.zip.asc + gpg: Signature made Fri 29 Jan 2016 08:59:37 AM EST using RSA key ID 8259BD92 + gpg: Good signature from "Isaac Bennetch " [full] + +Should the signature be invalid (the archive has been changed), you would get a +clear error regardless of the fact that the key is trusted or not: + +.. code-block:: console + + $ gpg --verify phpMyAdmin-4.5.4.1-all-languages.zip.asc + gpg: Signature made Fri 29 Jan 2016 08:59:37 AM EST using RSA key ID 8259BD92 + gpg: BAD signature from "Isaac Bennetch " [unknown] + +.. _Validating other keys on your public keyring: https://www.gnupg.org/gph/en/manual.html#AEN335 + +.. _Isaac's key links to Linus's key: https://pgp.cs.uu.nl/paths/79be3e4300411886/to/ce752f178259bd92.html + +.. index:: + single: Configuration storage + single: phpMyAdmin configuration storage + single: pmadb + +.. _linked-tables: + +phpMyAdmin configuration storage +++++++++++++++++++++++++++++++++ + +.. versionchanged:: 3.4.0 + + Prior to phpMyAdmin 3.4.0 this was called Linked Tables Infrastructure, but + the name was changed due to extended scope of the storage. + +For a whole set of additional features (:ref:`bookmarks`, comments, :term:`SQL`-history, +tracking mechanism, :term:`PDF`-generation, :ref:`transformations`, :ref:`relations` +etc.) you need to create a set of special tables. Those tables can be located +in your own database, or in a central database for a multi-user installation +(this database would then be accessed by the controluser, so no other user +should have rights to it). + +.. _zeroconf: + +Zero configuration +------------------ + +In many cases, this database structure can be automatically created and +configured. This is called “Zero Configuration” mode and can be particularly +useful in shared hosting situations. “Zeroconf” mode is on by default, to +disable set :config:option:`$cfg['ZeroConf']` to false. + +The following three scenarios are covered by the Zero Configuration mode: + +* When entering a database where the configuration storage tables are not + present, phpMyAdmin offers to create them from the Operations tab. +* When entering a database where the tables do already exist, the software + automatically detects this and begins using them. This is the most common + situation; after the tables are initially created automatically they are + continually used without disturbing the user; this is also most useful on + shared hosting where the user is not able to edit :file:`config.inc.php` and + usually the user only has access to one database. +* When having access to multiple databases, if the user first enters the + database containing the configuration storage tables then switches to + another database, + phpMyAdmin continues to use the tables from the first database; the user is + not prompted to create more tables in the new database. + +Manual configuration +-------------------- + +Please look at your ``./sql/`` directory, where you should find a +file called *create\_tables.sql*. (If you are using a Windows server, +pay special attention to :ref:`faq1_23`). + +If you already had this infrastructure and: + +* upgraded to MySQL 4.1.2 or newer, please use + :file:`sql/upgrade_tables_mysql_4_1_2+.sql`. +* upgraded to phpMyAdmin 4.3.0 or newer from 2.5.0 or newer (<= 4.2.x), + please use :file:`sql/upgrade_column_info_4_3_0+.sql`. +* upgraded to phpMyAdmin 4.7.0 or newer from 4.3.0 or newer, + please use :file:`sql/upgrade_tables_4_7_0+.sql`. + +and then create new tables by importing :file:`sql/create_tables.sql`. + +You can use your phpMyAdmin to create the tables for you. Please be +aware that you may need special (administrator) privileges to create +the database and tables, and that the script may need some tuning, +depending on the database name. + +After having imported the :file:`sql/create_tables.sql` file, you +should specify the table names in your :file:`config.inc.php` file. The +directives used for that can be found in the :ref:`config`. + +You will also need to have a controluser +(:config:option:`$cfg['Servers'][$i]['controluser']` and +:config:option:`$cfg['Servers'][$i]['controlpass']` settings) +with the proper rights to those tables. For example you can create it +using following statement: + +.. code-block:: mysql + + GRANT SELECT, INSERT, UPDATE, DELETE ON .* TO 'pma'@'localhost' IDENTIFIED BY 'pmapass'; + +.. _upgrading: + +Upgrading from an older version ++++++++++++++++++++++++++++++++ + +.. warning:: + + **Never** extract the new version over an existing installation of + phpMyAdmin, always first remove the old files keeping just the + configuration. + + This way you will not leave old no longer working code in the directory, + which can have severe security implications or can cause various breakages. + +Simply copy :file:`config.inc.php` from your previous installation into +the newly unpacked one. Configuration files from old versions may +require some tweaking as some options have been changed or removed. +For compatibility with PHP 5.3 and later, remove a +``set_magic_quotes_runtime(0);`` statement that you might find near +the end of your configuration file. + +You should **not** copy :file:`libraries/config.default.php` over +:file:`config.inc.php` because the default configuration file is version- +specific. + +The complete upgrade can be performed in few simple steps: + +1. Download the latest phpMyAdmin version from . +2. Rename existing phpMyAdmin folder (for example to ``phpmyadmin-old``). +3. Unpack freshly donwloaded phpMyAdmin to desired location (for example ``phpmyadmin``). +4. Copy :file:`config.inc.php`` from old location (``phpmyadmin-old``) to new one (``phpmyadmin``). +5. Test that everything works properly. +6. Remove backup of previous version (``phpmyadmin-old``). + +If you have upgraded your MySQL server from a version previous to 4.1.2 to +version 5.x or newer and if you use the phpMyAdmin configuration storage, you +should run the :term:`SQL` script found in +:file:`sql/upgrade_tables_mysql_4_1_2+.sql`. + +If you have upgraded your phpMyAdmin to 4.3.0 or newer from 2.5.0 or +newer (<= 4.2.x) and if you use the phpMyAdmin configuration storage, you +should run the :term:`SQL` script found in +:file:`sql/upgrade_column_info_4_3_0+.sql`. + +Do not forget to clear the browser cache and to empty the old session by +logging out and logging in again. + +.. index:: Authentication mode + +.. _authentication_modes: + +Using authentication modes +++++++++++++++++++++++++++ + +:term:`HTTP` and cookie authentication modes are recommended in a **multi-user +environment** where you want to give users access to their own database and +don't want them to play around with others. Nevertheless be aware that MS +Internet Explorer seems to be really buggy about cookies, at least till version +6. Even in a **single-user environment**, you might prefer to use :term:`HTTP` +or cookie mode so that your user/password pair are not in clear in the +configuration file. + +:term:`HTTP` and cookie authentication +modes are more secure: the MySQL login information does not need to be +set in the phpMyAdmin configuration file (except possibly for the +:config:option:`$cfg['Servers'][$i]['controluser']`). +However, keep in mind that the password travels in plain text, unless +you are using the HTTPS protocol. In cookie mode, the password is +stored, encrypted with the AES algorithm, in a temporary cookie. + +Then each of the *true* users should be granted a set of privileges +on a set of particular databases. Normally you shouldn't give global +privileges to an ordinary user, unless you understand the impact of those +privileges (for example, you are creating a superuser). +For example, to grant the user *real_user* with all privileges on +the database *user_base*: + +.. code-block:: mysql + + GRANT ALL PRIVILEGES ON user_base.* TO 'real_user'@localhost IDENTIFIED BY 'real_password'; + +What the user may now do is controlled entirely by the MySQL user management +system. With HTTP or cookie authentication mode, you don't need to fill the +user/password fields inside the :config:option:`$cfg['Servers']`. + +.. seealso:: + + :ref:`faq1_32`, + :ref:`faq1_35`, + :ref:`faq4_1`, + :ref:`faq4_2`, + :ref:`faq4_3` + +.. index:: pair: HTTP; Authentication mode + +.. _auth_http: + +HTTP authentication mode +------------------------ + +* Uses :term:`HTTP` Basic authentication + method and allows you to log in as any valid MySQL user. +* Is supported with most PHP configurations. For :term:`IIS` (:term:`ISAPI`) + support using :term:`CGI` PHP see :ref:`faq1_32`, for using with Apache + :term:`CGI` see :ref:`faq1_35`. +* When PHP is running under Apache's :term:`mod_proxy_fcgi` (e.g. with PHP-FPM), + ``Authorization`` headers are not passed to the underlying FCGI application, + such that your credentials will not reach the application. In this case, you can + add the following configuration directive: + + .. code-block:: apache + + SetEnvIf Authorization "(.*)" HTTP_AUTHORIZATION=$1 + +* See also :ref:`faq4_4` about not using the :term:`.htaccess` mechanism along with + ':term:`HTTP`' authentication mode. + +.. note:: + + There is no way to do proper logout in HTTP authentication, most browsers + will remember credentials until there is no different successful + authentication. Because of this this method has limitation that you can not + login with same user after logout. + +.. index:: pair: Cookie; Authentication mode + +.. _cookie: + +Cookie authentication mode +-------------------------- + +* Username and password are stored in cookies during the session and password + is deleted when it ends. +* With this mode, the user can truly log out of phpMyAdmin and log + back in with the same username (this is not possible with :ref:`auth_http`). +* If you want to allow users to enter any hostname to connect (rather than only + servers that are configured in :file:`config.inc.php`), + see the :config:option:`$cfg['AllowArbitraryServer']` directive. +* As mentioned in the :ref:`require` section, having the ``openssl`` extension + will speed up access considerably, but is not required. + +.. index:: pair: Signon; Authentication mode + +.. _auth_signon: + +Signon authentication mode +-------------------------- + +* This mode is a convenient way of using credentials from another + application to authenticate to phpMyAdmin to implement single signon + solution. +* The other application has to store login information into session + data (see :config:option:`$cfg['Servers'][$i]['SignonSession']` and + :config:option:`$cfg['Servers'][$i]['SignonCookieParams']`) or you + need to implement script to return the credentials (see + :config:option:`$cfg['Servers'][$i]['SignonScript']`). +* When no credentials are available, the user is being redirected to + :config:option:`$cfg['Servers'][$i]['SignonURL']`, where you should handle + the login process. + +The very basic example of saving credentials in a session is available as +:file:`examples/signon.php`: + +.. literalinclude:: ../examples/signon.php + :language: php + +Alternatively you can also use this way to integrate with OpenID as shown +in :file:`examples/openid.php`: + +.. literalinclude:: ../examples/openid.php + :language: php + +If you intend to pass the credentials using some other means than, you have to +implement wrapper in PHP to get that data and set it to +:config:option:`$cfg['Servers'][$i]['SignonScript']`. There is very minimal example +in :file:`examples/signon-script.php`: + +.. literalinclude:: ../examples/signon-script.php + :language: php + +.. seealso:: + :config:option:`$cfg['Servers'][$i]['auth_type']`, + :config:option:`$cfg['Servers'][$i]['SignonSession']`, + :config:option:`$cfg['Servers'][$i]['SignonCookieParams']`, + :config:option:`$cfg['Servers'][$i]['SignonScript']`, + :config:option:`$cfg['Servers'][$i]['SignonURL']`, + :ref:`example-signon` + +.. index:: pair: Config; Authentication mode + +.. _auth_config: + +Config authentication mode +-------------------------- + +* This mode is sometimes the less secure one because it requires you to fill the + :config:option:`$cfg['Servers'][$i]['user']` and + :config:option:`$cfg['Servers'][$i]['password']` + fields (and as a result, anyone who can read your :file:`config.inc.php` + can discover your username and password). +* In the :ref:`faqmultiuser` section, there is an entry explaining how + to protect your configuration file. +* For additional security in this mode, you may wish to consider the + Host authentication :config:option:`$cfg['Servers'][$i]['AllowDeny']['order']` + and :config:option:`$cfg['Servers'][$i]['AllowDeny']['rules']` configuration directives. +* Unlike cookie and http, does not require a user to log in when first + loading the phpMyAdmin site. This is by design but could allow any + user to access your installation. Use of some restriction method is + suggested, perhaps a :term:`.htaccess` file with the HTTP-AUTH directive or disallowing + incoming HTTP requests at one’s router or firewall will suffice (both + of which are beyond the scope of this manual but easily searchable + with Google). + +.. _securing: + +Securing your phpMyAdmin installation ++++++++++++++++++++++++++++++++++++++ + +The phpMyAdmin team tries hard to make the application secure, however there +are always ways to make your installation more secure: + +* Follow our `Security announcements `_ and upgrade + phpMyAdmin whenever new vulnerability is published. +* Serve phpMyAdmin on HTTPS only. Preferably, you should use HSTS as well, so that + you're protected from protocol downgrade attacks. +* Ensure your PHP setup follows recommendations for production sites, for example + `display_errors `_ + should be disabled. +* Remove the ``test`` directory from phpMyAdmin, unless you are developing and need test suite. +* Remove the ``setup`` directory from phpMyAdmin, you will probably not + use it after the initial setup. +* Properly choose an authentication method - :ref:`cookie` + is probably the best choice for shared hosting. +* Deny access to auxiliary files in :file:`./libraries/` or + :file:`./templates/` subfolders in your webserver configuration. + Such configuration prevents from possible path exposure and cross side + scripting vulnerabilities that might happen to be found in that code. For the + Apache webserver, this is often accomplished with a :term:`.htaccess` file in + those directories. +* Deny access to temporary files, see :config:option:`$cfg['TempDir']` (if that + is placed inside your web root, see also :ref:`web-dirs`. +* It is generally a good idea to protect a public phpMyAdmin installation + against access by robots as they usually can not do anything good there. You + can do this using ``robots.txt`` file in root of your webserver or limit + access by web server configuration, see :ref:`faq1_42`. +* In case you don't want all MySQL users to be able to access + phpMyAdmin, you can use :config:option:`$cfg['Servers'][$i]['AllowDeny']['rules']` to limit them + or :config:option:`$cfg['Servers'][$i]['AllowRoot']` to deny root user access. +* Enable :ref:`2fa` for your account. +* Consider hiding phpMyAdmin behind an authentication proxy, so that + users need to authenticate prior to providing MySQL credentials + to phpMyAdmin. You can achieve this by configuring your web server to request + HTTP authentication. For example in Apache this can be done with: + + .. code-block:: apache + + AuthType Basic + AuthName "Restricted Access" + AuthUserFile /usr/share/phpmyadmin/passwd + Require valid-user + + Once you have changed the configuration, you need to create a list of users which + can authenticate. This can be done using the :program:`htpasswd` utility: + + .. code-block:: sh + + htpasswd -c /usr/share/phpmyadmin/passwd username + +* If you are afraid of automated attacks, enabling Captcha by + :config:option:`$cfg['CaptchaLoginPublicKey']` and + :config:option:`$cfg['CaptchaLoginPrivateKey']` might be an option. +* Failed login attemps are logged to syslog (if available, see + :config:option:`$cfg['AuthLog']`). This can allow using a tool such as + fail2ban to block brute-force attempts. Note that the log file used by syslog + is not the same as the Apache error or access log files. +* In case you're running phpMyAdmin together with other PHP applications, it is + generally advised to use separate session storage for phpMyAdmin to avoid + possible session based attacks against it. You can use + :config:option:`$cfg['SessionSavePath']` to achieve this. + +.. _ssl: + +Using SSL for connection to database server ++++++++++++++++++++++++++++++++++++++++++++ + +It is recommended to use SSL when connecting to remote database server. There +are several configuration options involved in the SSL setup: + +:config:option:`$cfg['Servers'][$i]['ssl']` + Defines whether to use SSL at all. If you enable only this, the connection + will be encrypted, but there is not authentication of the connection - you + can not verify that you are talking to the right server. +:config:option:`$cfg['Servers'][$i]['ssl_key']` and :config:option:`$cfg['Servers'][$i]['ssl_cert']` + This is used for authentication of client to the server. +:config:option:`$cfg['Servers'][$i]['ssl_ca']` and :config:option:`$cfg['Servers'][$i]['ssl_ca_path']` + The certificate authorities you trust for server certificates. + This is used to ensure that you are talking to a trusted server. +:config:option:`$cfg['Servers'][$i]['ssl_verify']` + This configuration disables server certificate verification. Use with + caution. + +.. seealso:: + + :ref:`example-google-ssl`, + :config:option:`$cfg['Servers'][$i]['ssl']`, + :config:option:`$cfg['Servers'][$i]['ssl_key']`, + :config:option:`$cfg['Servers'][$i]['ssl_cert']`, + :config:option:`$cfg['Servers'][$i]['ssl_ca']`, + :config:option:`$cfg['Servers'][$i]['ssl_ca_path']`, + :config:option:`$cfg['Servers'][$i]['ssl_ciphers']`, + :config:option:`$cfg['Servers'][$i]['ssl_verify']` + +Known issues +++++++++++++ + +Users with column-specific privileges are unable to "Browse" +------------------------------------------------------------ + +If a user has only column-specific privileges on some (but not all) columns in a table, "Browse" +will fail with an error message. + +As a workaround, a bookmarked query with the same name as the table can be created, this will +run when using the "Browse" link instead. `Issue 11922 `_. + +Trouble logging back in after logging out using 'http' authentication +---------------------------------------------------------------------- + +When using the 'http' ``auth_type``, it can be impossible to log back in (when the logout comes +manually or after a period of inactivity). `Issue 11898 `_. + +.. _Composer tool: https://getcomposer.org/ +.. _Packagist: https://packagist.org/ +.. _Docker image: https://hub.docker.com/r/phpmyadmin/phpmyadmin/ diff --git a/php/apps/phpmyadmin49/html/doc/html/_sources/themes.txt b/php/apps/phpmyadmin49/html/doc/html/_sources/themes.txt new file mode 100644 index 00000000..1f22521b --- /dev/null +++ b/php/apps/phpmyadmin49/html/doc/html/_sources/themes.txt @@ -0,0 +1,100 @@ +.. _themes: + +Custom Themes +============= + +phpMyAdmin comes with support for third party themes. You can download +additonal themes from our website at . + +Configuration +------------- + +Themes are configured with :config:option:`$cfg['ThemeManager']` and +:config:option:`$cfg['ThemeDefault']`. Under :file:`./themes/`, you should not +delete the directory ``pmahomme`` or its underlying structure, because this is +the system theme used by phpMyAdmin. ``pmahomme`` contains all images and +styles, for backwards compatibility and for all themes that would not include +images or css-files. If :config:option:`$cfg['ThemeManager']` is enabled, you +can select your favorite theme on the main page. Your selected theme will be +stored in a cookie. + +Creating custom theme +--------------------- + +To create a theme: + +* make a new subdirectory (for example "your\_theme\_name") under :file:`./themes/`. +* copy the files and directories from ``pmahomme`` to "your\_theme\_name" +* edit the css-files in "your\_theme\_name/css" +* put your new images in "your\_theme\_name/img" +* edit :file:`layout.inc.php` in "your\_theme\_name" +* edit :file:`theme.json` in "your\_theme\_name" to contain theme metadata (see below) +* make a new screenshot of your theme and save it under + "your\_theme\_name/screen.png" + +Theme metadata +++++++++++++++ + +.. versionchanged:: 4.8.0 + + Before 4.8.0 the theme metadata was passed in the :file:`info.inc.php` file. + It has been replaced by :file:`theme.json` to allow easier parsing (without + need to handle PHP code) and to support additional features. + +In theme directory there is file :file:`theme.json` which contains theme +metadata. Currently it consists of: + +.. describe:: name + + Display name of the theme. + + **This field is required.** + +.. describe:: version + + Theme version, can be quite arbirary and does not have to match phpMyAdmin version. + + **This field is required.** + +.. describe:: desciption + + Theme description. this will be shown on the website. + + **This field is required.** + +.. describe:: author + + Theme author name. + + **This field is required.** + +.. describe:: url + + Link to theme author website. It's good idea to have way for getting + support there. + +.. describe:: supports + + Array of supported phpMyAdmin major versions. + + **This field is required.** + +For example, the definition for Original theme shipped with phpMyAdnin 4.8: + +.. code-block:: json + + { + "name": "Original", + "version": "4.8", + "description": "Original phpMyAdmin theme", + "author": "phpMyAdmin developers", + "url": "https://www.phpmyadmin.net/", + "supports": ["4.8"] + } + +Sharing images +++++++++++++++ + +If you do not want to use your own symbols and buttons, remove the +directory "img" in "your\_theme\_name". phpMyAdmin will use the +default icons and buttons (from the system-theme ``pmahomme``). diff --git a/php/apps/phpmyadmin49/html/doc/html/_sources/transformations.txt b/php/apps/phpmyadmin49/html/doc/html/_sources/transformations.txt new file mode 100644 index 00000000..02f43c54 --- /dev/null +++ b/php/apps/phpmyadmin49/html/doc/html/_sources/transformations.txt @@ -0,0 +1,143 @@ +.. _transformations: + +Transformations +=============== + +.. note:: + + You need to have configured the :ref:`linked-tables` for using transformations + feature. + +.. _transformationsintro: + +Introduction +++++++++++++ + +To enable transformations, you have to setup the ``column_info`` +table and the proper directives. Please see the :ref:`config` on how to do so. + +You can apply different transformations to the contents of each +column. The transformation will take the content of each column and +transform it with certain rules defined in the selected +transformation. + +Say you have a column 'filename' which contains a filename. Normally +you would see in phpMyAdmin only this filename. Using transformations +you can transform that filename into a HTML link, so you can click +inside of the phpMyAdmin structure on the column's link and will see +the file displayed in a new browser window. Using transformation +options you can also specify strings to append/prepend to a string or +the format you want the output stored in. + +For a general overview of all available transformations and their +options, you can consult your *//transformation\_overview.php* installation. + +For a tutorial on how to effectively use transformations, see our +`Link section `_ on the +official phpMyAdmin homepage. + +.. _transformationshowto: + +Usage ++++++ + +Go to your *tbl\_structure.php* page (i.e. reached through clicking on +the 'Structure' link for a table). There click on "Change" (or change +icon) and there you will see three new fields at the end of the line. +They are called 'MIME-type', 'Browser transformation' and +'Transformation options'. + +* The field 'MIME-type' is a drop-down field. Select the MIME-type that + corresponds to the column's contents. Please note that transformations + are inactive as long as no MIME-type is selected. +* The field 'Browser transformation' is a drop-down field. You can + choose from a hopefully growing amount of pre-defined transformations. + See below for information on how to build your own transformation. + There are global transformations and mimetype-bound transformations. + Global transformations can be used for any mimetype. They will take + the mimetype, if necessary, into regard. Mimetype-bound + transformations usually only operate on a certain mimetype. There are + transformations which operate on the main mimetype (like 'image'), + which will most likely take the subtype into regard, and those who + only operate on a specific subtype (like 'image/jpeg'). You can use + transformations on mimetypes for which the function was not defined + for. There is no security check for you selected the right + transformation, so take care of what the output will be like. +* The field 'Transformation options' is a free-type textfield. You have + to enter transform-function specific options here. Usually the + transforms can operate with default options, but it is generally a + good idea to look up the overview to see which options are necessary. + Much like the ENUM/SET-Fields, you have to split up several options + using the format 'a','b','c',...(NOTE THE MISSING BLANKS). This is + because internally the options will be parsed as an array, leaving the + first value the first element in the array, and so forth. If you want + to specify a MIME character set you can define it in the + transformation\_options. You have to put that outside of the pre- + defined options of the specific mime-transform, as the last value of + the set. Use the format "'; charset=XXX'". If you use a transform, for + which you can specify 2 options and you want to append a character + set, enter "'first parameter','second parameter','charset=us-ascii'". + You can, however use the defaults for the parameters: "'','','charset + =us-ascii'". The default options can be configured using + :config:option:`$cfg['DefaultTransformations']` + +.. _transformationsfiles: + +File structure +++++++++++++++ + +All specific transformations for mimetypes are defined through class +files in the directory 'libraries/classes/Plugins/Transformations/'. Each of +them extends a certain transformation abstract class declared in +libraries/classes/Plugins/Transformations/Abs. + +They are stored in files to ease up customization and easy adding of +new transformations. + +Because the user cannot enter own mimetypes, it is kept sure that +transformations always work. It makes no sense to apply a +transformation to a mimetype the transform-function doesn't know to +handle. + +There is a file called '*transformations.lib.php*' that provides some +basic functions which can be included by any other transform function. + +The file name convention is ``[Mimetype]_[Subtype]_[Transformation +Name].class.php``, while the abtract class that it extends has the +name ``[Transformation Name]TransformationsPlugin``. All of the +methods that have to be implemented by a transformations plug-in are: + +#. getMIMEType() and getMIMESubtype() in the main class; +#. getName(), getInfo() and applyTransformation() in the abstract class + it extends. + +The getMIMEType(), getMIMESubtype() and getName() methods return the +name of the MIME type, MIME Subtype and transformation accordingly. +getInfo() returns the transformation's description and possible +options it may receive and applyTransformation() is the method that +does the actual work of the transformation plug-in. + +Please see the libraries/classes/Plugins/Transformations/TEMPLATE and +libraries/classes/Plugins/Transformations/TEMPLATE\_ABSTRACT files for adding +your own transformation plug-in. You can also generate a new +transformation plug-in (with or without the abstract transformation +class), by using +:file:`scripts/transformations_generator_plugin.sh` or +:file:`scripts/transformations_generator_main_class.sh`. + +The applyTransformation() method always gets passed three variables: + +#. **$buffer** - Contains the text inside of the column. This is the + text, you want to transform. +#. **$options** - Contains any user-passed options to a transform + function as an array. +#. **$meta** - Contains an object with information about your column. The + data is drawn from the output of the `mysql\_fetch\_field() + `_ function. This means, all + object properties described on the `manual page + `_ are available in this + variable and can be used to transform a column accordingly to + unsigned/zerofill/not\_null/... properties. The $meta->mimetype + variable contains the original MIME-type of the column (i.e. + 'text/plain', 'image/jpeg' etc.) diff --git a/php/apps/phpmyadmin49/html/doc/html/_sources/two_factor.txt b/php/apps/phpmyadmin49/html/doc/html/_sources/two_factor.txt new file mode 100644 index 00000000..fe4e6473 --- /dev/null +++ b/php/apps/phpmyadmin49/html/doc/html/_sources/two_factor.txt @@ -0,0 +1,69 @@ +.. _2fa: + +Two-factor authentication +========================= + +.. versionadded:: 4.8.0 + +Since phpMyAdmin 4.8.0 you can configure two-factor authentication to be +used when logging in. To use this, you first need to configure the +:ref:`linked-tables`. Once this is done, every user can opt-in for the second +authentication factor in the :guilabel:`Settings`. + +When running phpMyAdmin from the Git source repository, the dependencies must be installed +manually; the typical way of doing so is with the command: + +.. code-block:: sh + + composer require pragmarx/google2fa bacon/bacon-qr-code + +Or when using a hardware security key with FIDO U2F: + +.. code-block:: sh + + composer require samyoul/u2f-php-server + +Authentication Application (2FA) +-------------------------------- + +Using an application for authentication is a quite common approach based on HOTP and +`TOTP `_. +It is based on transmitting a private key from phpMyAdmin to the authentication +application and the application is then able to generate one time codes based +on this key. The easiest way to enter the key in to the application from phpMyAdmin is +through scanning a QR code. + +There are dozens of applications available for mobile phones to implement these +standards, the most widely used include: + +* `FreeOTP for iOS, Android and Pebble `_ +* `Authy for iOS, Android, Chrome, OS X `_ +* `Google Authenticator for iOS `_ +* `Google Authenticator for Android `_ +* `LastPass Authenticator for iOS, Android, OS X, Windows `_ + +Hardware Security Key (FIDO U2F) +-------------------------------- + +Using hardware tokens is considered to be more secure than a software based +solution. phpMyAdmin supports `FIDO U2F `_ +tokens. + +There are several manufacturers of these tokens, for example: + +* `youbico FIDO U2F Security Key `_ +* `HyperFIDO `_ +* `Trezor Hardware Wallet `_ can act as an `U2F token `_ +* `List of Two Factor Auth (2FA) Dongles `_ + +.. _simple2fa: + +Simple two-factor authentication +-------------------------------- + +This authentication is included for testing and demonstration purposes only as +it really does not provide two-factor authentication, it just asks the user to confirm login by +clicking on the button. + +It should not be used in the production and is disabled unless +:config:option:`$cfg['DBG']['simple2fa']` is set. diff --git a/php/apps/phpmyadmin49/html/doc/html/_sources/user.txt b/php/apps/phpmyadmin49/html/doc/html/_sources/user.txt new file mode 100644 index 00000000..a98e2cb8 --- /dev/null +++ b/php/apps/phpmyadmin49/html/doc/html/_sources/user.txt @@ -0,0 +1,16 @@ +User Guide +========== + +.. toctree:: + :maxdepth: 2 + + settings + two_factor + transformations + bookmarks + privileges + relations + charts + import_export + themes + other diff --git a/php/apps/phpmyadmin49/html/doc/html/_sources/vendors.txt b/php/apps/phpmyadmin49/html/doc/html/_sources/vendors.txt new file mode 100644 index 00000000..aaceee34 --- /dev/null +++ b/php/apps/phpmyadmin49/html/doc/html/_sources/vendors.txt @@ -0,0 +1,32 @@ +Distributing and packaging phpMyAdmin +===================================== + +This document is intended to give advices to people who want to +redistribute phpMyAdmin inside other software package such as Linux +distribution or some all in one package including web server and MySQL +server. + +Generally you can customize some basic aspects (paths to some files and +behavior) in :file:`libraries/vendor_config.php`. + +For example if you want setup script to generate config file in var, change +``SETUP_CONFIG_FILE`` to :file:`/var/lib/phpmyadmin/config.inc.php` and you +will also probably want to skip directory writable check, so set +``SETUP_DIR_WRITABLE`` to false. + +External libraries +------------------ + +phpMyAdmin includes several external libraries, you might want to +replace them with system ones if they are available, but please note +that you should test whether version you provide is compatible with the +one we ship. + +Currently known list of external libraries: + +js/jquery + jQuery js framework and various jQuery based libraries. + +vendor/ + The download kit includes various Composer packages as + dependencies. diff --git a/php/apps/phpmyadmin49/html/doc/html/_static/ajax-loader.gif b/php/apps/phpmyadmin49/html/doc/html/_static/ajax-loader.gif new file mode 100644 index 00000000..61faf8ca Binary files /dev/null and b/php/apps/phpmyadmin49/html/doc/html/_static/ajax-loader.gif differ diff --git a/php/apps/phpmyadmin49/html/doc/html/_static/basic.css b/php/apps/phpmyadmin49/html/doc/html/_static/basic.css new file mode 100644 index 00000000..0b79414a --- /dev/null +++ b/php/apps/phpmyadmin49/html/doc/html/_static/basic.css @@ -0,0 +1,611 @@ +/* + * basic.css + * ~~~~~~~~~ + * + * Sphinx stylesheet -- basic theme. + * + * :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ + +/* -- main layout ----------------------------------------------------------- */ + +div.clearer { + clear: both; +} + +/* -- relbar ---------------------------------------------------------------- */ + +div.related { + width: 100%; + font-size: 90%; +} + +div.related h3 { + display: none; +} + +div.related ul { + margin: 0; + padding: 0 0 0 10px; + list-style: none; +} + +div.related li { + display: inline; +} + +div.related li.right { + float: right; + margin-right: 5px; +} + +/* -- sidebar --------------------------------------------------------------- */ + +div.sphinxsidebarwrapper { + padding: 10px 5px 0 10px; +} + +div.sphinxsidebar { + float: left; + width: 230px; + margin-left: -100%; + font-size: 90%; + word-wrap: break-word; + overflow-wrap : break-word; +} + +div.sphinxsidebar ul { + list-style: none; +} + +div.sphinxsidebar ul ul, +div.sphinxsidebar ul.want-points { + margin-left: 20px; + list-style: square; +} + +div.sphinxsidebar ul ul { + margin-top: 0; + margin-bottom: 0; +} + +div.sphinxsidebar form { + margin-top: 10px; +} + +div.sphinxsidebar input { + border: 1px solid #98dbcc; + font-family: sans-serif; + font-size: 1em; +} + +div.sphinxsidebar #searchbox input[type="text"] { + width: 170px; +} + +img { + border: 0; + max-width: 100%; +} + +/* -- search page ----------------------------------------------------------- */ + +ul.search { + margin: 10px 0 0 20px; + padding: 0; +} + +ul.search li { + padding: 5px 0 5px 20px; + background-image: url(file.png); + background-repeat: no-repeat; + background-position: 0 7px; +} + +ul.search li a { + font-weight: bold; +} + +ul.search li div.context { + color: #888; + margin: 2px 0 0 30px; + text-align: left; +} + +ul.keywordmatches li.goodmatch a { + font-weight: bold; +} + +/* -- index page ------------------------------------------------------------ */ + +table.contentstable { + width: 90%; +} + +table.contentstable p.biglink { + line-height: 150%; +} + +a.biglink { + font-size: 1.3em; +} + +span.linkdescr { + font-style: italic; + padding-top: 5px; + font-size: 90%; +} + +/* -- general index --------------------------------------------------------- */ + +table.indextable { + width: 100%; +} + +table.indextable td { + text-align: left; + vertical-align: top; +} + +table.indextable dl, table.indextable dd { + margin-top: 0; + margin-bottom: 0; +} + +table.indextable tr.pcap { + height: 10px; +} + +table.indextable tr.cap { + margin-top: 10px; + background-color: #f2f2f2; +} + +img.toggler { + margin-right: 3px; + margin-top: 3px; + cursor: pointer; +} + +div.modindex-jumpbox { + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; + margin: 1em 0 1em 0; + padding: 0.4em; +} + +div.genindex-jumpbox { + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; + margin: 1em 0 1em 0; + padding: 0.4em; +} + +/* -- general body styles --------------------------------------------------- */ + +div.body p, div.body dd, div.body li, div.body blockquote { + -moz-hyphens: auto; + -ms-hyphens: auto; + -webkit-hyphens: auto; + hyphens: auto; +} + +a.headerlink { + visibility: hidden; +} + +h1:hover > a.headerlink, +h2:hover > a.headerlink, +h3:hover > a.headerlink, +h4:hover > a.headerlink, +h5:hover > a.headerlink, +h6:hover > a.headerlink, +dt:hover > a.headerlink, +caption:hover > a.headerlink, +p.caption:hover > a.headerlink, +div.code-block-caption:hover > a.headerlink { + visibility: visible; +} + +div.body p.caption { + text-align: inherit; +} + +div.body td { + text-align: left; +} + +.field-list ul { + padding-left: 1em; +} + +.first { + margin-top: 0 !important; +} + +p.rubric { + margin-top: 30px; + font-weight: bold; +} + +img.align-left, .figure.align-left, object.align-left { + clear: left; + float: left; + margin-right: 1em; +} + +img.align-right, .figure.align-right, object.align-right { + clear: right; + float: right; + margin-left: 1em; +} + +img.align-center, .figure.align-center, object.align-center { + display: block; + margin-left: auto; + margin-right: auto; +} + +.align-left { + text-align: left; +} + +.align-center { + text-align: center; +} + +.align-right { + text-align: right; +} + +/* -- sidebars -------------------------------------------------------------- */ + +div.sidebar { + margin: 0 0 0.5em 1em; + border: 1px solid #ddb; + padding: 7px 7px 0 7px; + background-color: #ffe; + width: 40%; + float: right; +} + +p.sidebar-title { + font-weight: bold; +} + +/* -- topics ---------------------------------------------------------------- */ + +div.topic { + border: 1px solid #ccc; + padding: 7px 7px 0 7px; + margin: 10px 0 10px 0; +} + +p.topic-title { + font-size: 1.1em; + font-weight: bold; + margin-top: 10px; +} + +/* -- admonitions ----------------------------------------------------------- */ + +div.admonition { + margin-top: 10px; + margin-bottom: 10px; + padding: 7px; +} + +div.admonition dt { + font-weight: bold; +} + +div.admonition dl { + margin-bottom: 0; +} + +p.admonition-title { + margin: 0px 10px 5px 0px; + font-weight: bold; +} + +div.body p.centered { + text-align: center; + margin-top: 25px; +} + +/* -- tables ---------------------------------------------------------------- */ + +table.docutils { + border: 0; + border-collapse: collapse; +} + +table caption span.caption-number { + font-style: italic; +} + +table caption span.caption-text { +} + +table.docutils td, table.docutils th { + padding: 1px 8px 1px 5px; + border-top: 0; + border-left: 0; + border-right: 0; + border-bottom: 1px solid #aaa; +} + +table.field-list td, table.field-list th { + border: 0 !important; +} + +table.footnote td, table.footnote th { + border: 0 !important; +} + +th { + text-align: left; + padding-right: 5px; +} + +table.citation { + border-left: solid 1px gray; + margin-left: 1px; +} + +table.citation td { + border-bottom: none; +} + +/* -- figures --------------------------------------------------------------- */ + +div.figure { + margin: 0.5em; + padding: 0.5em; +} + +div.figure p.caption { + padding: 0.3em; +} + +div.figure p.caption span.caption-number { + font-style: italic; +} + +div.figure p.caption span.caption-text { +} + + +/* -- other body styles ----------------------------------------------------- */ + +ol.arabic { + list-style: decimal; +} + +ol.loweralpha { + list-style: lower-alpha; +} + +ol.upperalpha { + list-style: upper-alpha; +} + +ol.lowerroman { + list-style: lower-roman; +} + +ol.upperroman { + list-style: upper-roman; +} + +dl { + margin-bottom: 15px; +} + +dd p { + margin-top: 0px; +} + +dd ul, dd table { + margin-bottom: 10px; +} + +dd { + margin-top: 3px; + margin-bottom: 10px; + margin-left: 30px; +} + +dt:target, .highlighted { + background-color: #fbe54e; +} + +dl.glossary dt { + font-weight: bold; + font-size: 1.1em; +} + +.field-list ul { + margin: 0; + padding-left: 1em; +} + +.field-list p { + margin: 0; +} + +.optional { + font-size: 1.3em; +} + +.sig-paren { + font-size: larger; +} + +.versionmodified { + font-style: italic; +} + +.system-message { + background-color: #fda; + padding: 5px; + border: 3px solid red; +} + +.footnote:target { + background-color: #ffa; +} + +.line-block { + display: block; + margin-top: 1em; + margin-bottom: 1em; +} + +.line-block .line-block { + margin-top: 0; + margin-bottom: 0; + margin-left: 1.5em; +} + +.guilabel, .menuselection { + font-family: sans-serif; +} + +.accelerator { + text-decoration: underline; +} + +.classifier { + font-style: oblique; +} + +abbr, acronym { + border-bottom: dotted 1px; + cursor: help; +} + +/* -- code displays --------------------------------------------------------- */ + +pre { + overflow: auto; + overflow-y: hidden; /* fixes display issues on Chrome browsers */ +} + +span.pre { + -moz-hyphens: none; + -ms-hyphens: none; + -webkit-hyphens: none; + hyphens: none; +} + +td.linenos pre { + padding: 5px 0px; + border: 0; + background-color: transparent; + color: #aaa; +} + +table.highlighttable { + margin-left: 0.5em; +} + +table.highlighttable td { + padding: 0 0.5em 0 0.5em; +} + +div.code-block-caption { + padding: 2px 5px; + font-size: small; +} + +div.code-block-caption code { + background-color: transparent; +} + +div.code-block-caption + div > div.highlight > pre { + margin-top: 0; +} + +div.code-block-caption span.caption-number { + padding: 0.1em 0.3em; + font-style: italic; +} + +div.code-block-caption span.caption-text { +} + +div.literal-block-wrapper { + padding: 1em 1em 0; +} + +div.literal-block-wrapper div.highlight { + margin: 0; +} + +code.descname { + background-color: transparent; + font-weight: bold; + font-size: 1.2em; +} + +code.descclassname { + background-color: transparent; +} + +code.xref, a code { + background-color: transparent; + font-weight: bold; +} + +h1 code, h2 code, h3 code, h4 code, h5 code, h6 code { + background-color: transparent; +} + +.viewcode-link { + float: right; +} + +.viewcode-back { + float: right; + font-family: sans-serif; +} + +div.viewcode-block:target { + margin: -1px -10px; + padding: 0 10px; +} + +/* -- math display ---------------------------------------------------------- */ + +img.math { + vertical-align: middle; +} + +div.body div.math p { + text-align: center; +} + +span.eqno { + float: right; +} + +/* -- printout stylesheet --------------------------------------------------- */ + +@media print { + div.document, + div.documentwrapper, + div.bodywrapper { + margin: 0 !important; + width: 100%; + } + + div.sphinxsidebar, + div.related, + div.footer, + #top-link { + display: none; + } +} \ No newline at end of file diff --git a/php/apps/phpmyadmin49/html/doc/html/_static/classic.css b/php/apps/phpmyadmin49/html/doc/html/_static/classic.css new file mode 100644 index 00000000..d98894b3 --- /dev/null +++ b/php/apps/phpmyadmin49/html/doc/html/_static/classic.css @@ -0,0 +1,261 @@ +/* + * default.css_t + * ~~~~~~~~~~~~~ + * + * Sphinx stylesheet -- default theme. + * + * :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ + +@import url("basic.css"); + +/* -- page layout ----------------------------------------------------------- */ + +body { + font-family: sans-serif; + font-size: 100%; + background-color: #11303d; + color: #000; + margin: 0; + padding: 0; +} + +div.document { + background-color: #1c4e63; +} + +div.documentwrapper { + float: left; + width: 100%; +} + +div.bodywrapper { + margin: 0 0 0 230px; +} + +div.body { + background-color: #ffffff; + color: #000000; + padding: 0 20px 30px 20px; +} + +div.footer { + color: #ffffff; + width: 100%; + padding: 9px 0 9px 0; + text-align: center; + font-size: 75%; +} + +div.footer a { + color: #ffffff; + text-decoration: underline; +} + +div.related { + background-color: #133f52; + line-height: 30px; + color: #ffffff; +} + +div.related a { + color: #ffffff; +} + +div.sphinxsidebar { +} + +div.sphinxsidebar h3 { + font-family: 'Trebuchet MS', sans-serif; + color: #ffffff; + font-size: 1.4em; + font-weight: normal; + margin: 0; + padding: 0; +} + +div.sphinxsidebar h3 a { + color: #ffffff; +} + +div.sphinxsidebar h4 { + font-family: 'Trebuchet MS', sans-serif; + color: #ffffff; + font-size: 1.3em; + font-weight: normal; + margin: 5px 0 0 0; + padding: 0; +} + +div.sphinxsidebar p { + color: #ffffff; +} + +div.sphinxsidebar p.topless { + margin: 5px 10px 10px 10px; +} + +div.sphinxsidebar ul { + margin: 10px; + padding: 0; + color: #ffffff; +} + +div.sphinxsidebar a { + color: #98dbcc; +} + +div.sphinxsidebar input { + border: 1px solid #98dbcc; + font-family: sans-serif; + font-size: 1em; +} + + + +/* -- hyperlink styles ------------------------------------------------------ */ + +a { + color: #355f7c; + text-decoration: none; +} + +a:visited { + color: #355f7c; + text-decoration: none; +} + +a:hover { + text-decoration: underline; +} + + + +/* -- body styles ----------------------------------------------------------- */ + +div.body h1, +div.body h2, +div.body h3, +div.body h4, +div.body h5, +div.body h6 { + font-family: 'Trebuchet MS', sans-serif; + background-color: #f2f2f2; + font-weight: normal; + color: #20435c; + border-bottom: 1px solid #ccc; + margin: 20px -20px 10px -20px; + padding: 3px 0 3px 10px; +} + +div.body h1 { margin-top: 0; font-size: 200%; } +div.body h2 { font-size: 160%; } +div.body h3 { font-size: 140%; } +div.body h4 { font-size: 120%; } +div.body h5 { font-size: 110%; } +div.body h6 { font-size: 100%; } + +a.headerlink { + color: #c60f0f; + font-size: 0.8em; + padding: 0 4px 0 4px; + text-decoration: none; +} + +a.headerlink:hover { + background-color: #c60f0f; + color: white; +} + +div.body p, div.body dd, div.body li, div.body blockquote { + text-align: justify; + line-height: 130%; +} + +div.admonition p.admonition-title + p { + display: inline; +} + +div.admonition p { + margin-bottom: 5px; +} + +div.admonition pre { + margin-bottom: 5px; +} + +div.admonition ul, div.admonition ol { + margin-bottom: 5px; +} + +div.note { + background-color: #eee; + border: 1px solid #ccc; +} + +div.seealso { + background-color: #ffc; + border: 1px solid #ff6; +} + +div.topic { + background-color: #eee; +} + +div.warning { + background-color: #ffe4e4; + border: 1px solid #f66; +} + +p.admonition-title { + display: inline; +} + +p.admonition-title:after { + content: ":"; +} + +pre { + padding: 5px; + background-color: #eeffcc; + color: #333333; + line-height: 120%; + border: 1px solid #ac9; + border-left: none; + border-right: none; +} + +code { + background-color: #ecf0f3; + padding: 0 1px 0 1px; + font-size: 0.95em; +} + +th { + background-color: #ede; +} + +.warning code { + background: #efc2c2; +} + +.note code { + background: #d6d6d6; +} + +.viewcode-back { + font-family: sans-serif; +} + +div.viewcode-block:target { + background-color: #f4debf; + border-top: 1px solid #ac9; + border-bottom: 1px solid #ac9; +} + +div.code-block-caption { + color: #efefef; + background-color: #1c4e63; +} \ No newline at end of file diff --git a/php/apps/phpmyadmin49/html/doc/html/_static/comment-bright.png b/php/apps/phpmyadmin49/html/doc/html/_static/comment-bright.png new file mode 100644 index 00000000..4a2bf8af Binary files /dev/null and b/php/apps/phpmyadmin49/html/doc/html/_static/comment-bright.png differ diff --git a/php/apps/phpmyadmin49/html/doc/html/_static/comment-close.png b/php/apps/phpmyadmin49/html/doc/html/_static/comment-close.png new file mode 100644 index 00000000..5fe08972 Binary files /dev/null and b/php/apps/phpmyadmin49/html/doc/html/_static/comment-close.png differ diff --git a/php/apps/phpmyadmin49/html/doc/html/_static/comment.png b/php/apps/phpmyadmin49/html/doc/html/_static/comment.png new file mode 100644 index 00000000..e651e983 Binary files /dev/null and b/php/apps/phpmyadmin49/html/doc/html/_static/comment.png differ diff --git a/php/apps/phpmyadmin49/html/doc/html/_static/default.css b/php/apps/phpmyadmin49/html/doc/html/_static/default.css new file mode 100644 index 00000000..81b93636 --- /dev/null +++ b/php/apps/phpmyadmin49/html/doc/html/_static/default.css @@ -0,0 +1 @@ +@import url("classic.css"); diff --git a/php/apps/phpmyadmin49/html/doc/html/_static/doctools.js b/php/apps/phpmyadmin49/html/doc/html/_static/doctools.js new file mode 100644 index 00000000..81634956 --- /dev/null +++ b/php/apps/phpmyadmin49/html/doc/html/_static/doctools.js @@ -0,0 +1,287 @@ +/* + * doctools.js + * ~~~~~~~~~~~ + * + * Sphinx JavaScript utilities for all documentation. + * + * :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ + +/** + * select a different prefix for underscore + */ +$u = _.noConflict(); + +/** + * make the code below compatible with browsers without + * an installed firebug like debugger +if (!window.console || !console.firebug) { + var names = ["log", "debug", "info", "warn", "error", "assert", "dir", + "dirxml", "group", "groupEnd", "time", "timeEnd", "count", "trace", + "profile", "profileEnd"]; + window.console = {}; + for (var i = 0; i < names.length; ++i) + window.console[names[i]] = function() {}; +} + */ + +/** + * small helper function to urldecode strings + */ +jQuery.urldecode = function(x) { + return decodeURIComponent(x).replace(/\+/g, ' '); +}; + +/** + * small helper function to urlencode strings + */ +jQuery.urlencode = encodeURIComponent; + +/** + * This function returns the parsed url parameters of the + * current request. Multiple values per key are supported, + * it will always return arrays of strings for the value parts. + */ +jQuery.getQueryParameters = function(s) { + if (typeof s == 'undefined') + s = document.location.search; + var parts = s.substr(s.indexOf('?') + 1).split('&'); + var result = {}; + for (var i = 0; i < parts.length; i++) { + var tmp = parts[i].split('=', 2); + var key = jQuery.urldecode(tmp[0]); + var value = jQuery.urldecode(tmp[1]); + if (key in result) + result[key].push(value); + else + result[key] = [value]; + } + return result; +}; + +/** + * highlight a given string on a jquery object by wrapping it in + * span elements with the given class name. + */ +jQuery.fn.highlightText = function(text, className) { + function highlight(node) { + if (node.nodeType == 3) { + var val = node.nodeValue; + var pos = val.toLowerCase().indexOf(text); + if (pos >= 0 && !jQuery(node.parentNode).hasClass(className)) { + var span = document.createElement("span"); + span.className = className; + span.appendChild(document.createTextNode(val.substr(pos, text.length))); + node.parentNode.insertBefore(span, node.parentNode.insertBefore( + document.createTextNode(val.substr(pos + text.length)), + node.nextSibling)); + node.nodeValue = val.substr(0, pos); + } + } + else if (!jQuery(node).is("button, select, textarea")) { + jQuery.each(node.childNodes, function() { + highlight(this); + }); + } + } + return this.each(function() { + highlight(this); + }); +}; + +/* + * backward compatibility for jQuery.browser + * This will be supported until firefox bug is fixed. + */ +if (!jQuery.browser) { + jQuery.uaMatch = function(ua) { + ua = ua.toLowerCase(); + + var match = /(chrome)[ \/]([\w.]+)/.exec(ua) || + /(webkit)[ \/]([\w.]+)/.exec(ua) || + /(opera)(?:.*version|)[ \/]([\w.]+)/.exec(ua) || + /(msie) ([\w.]+)/.exec(ua) || + ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec(ua) || + []; + + return { + browser: match[ 1 ] || "", + version: match[ 2 ] || "0" + }; + }; + jQuery.browser = {}; + jQuery.browser[jQuery.uaMatch(navigator.userAgent).browser] = true; +} + +/** + * Small JavaScript module for the documentation. + */ +var Documentation = { + + init : function() { + this.fixFirefoxAnchorBug(); + this.highlightSearchWords(); + this.initIndexTable(); + + }, + + /** + * i18n support + */ + TRANSLATIONS : {}, + PLURAL_EXPR : function(n) { return n == 1 ? 0 : 1; }, + LOCALE : 'unknown', + + // gettext and ngettext don't access this so that the functions + // can safely bound to a different name (_ = Documentation.gettext) + gettext : function(string) { + var translated = Documentation.TRANSLATIONS[string]; + if (typeof translated == 'undefined') + return string; + return (typeof translated == 'string') ? translated : translated[0]; + }, + + ngettext : function(singular, plural, n) { + var translated = Documentation.TRANSLATIONS[singular]; + if (typeof translated == 'undefined') + return (n == 1) ? singular : plural; + return translated[Documentation.PLURALEXPR(n)]; + }, + + addTranslations : function(catalog) { + for (var key in catalog.messages) + this.TRANSLATIONS[key] = catalog.messages[key]; + this.PLURAL_EXPR = new Function('n', 'return +(' + catalog.plural_expr + ')'); + this.LOCALE = catalog.locale; + }, + + /** + * add context elements like header anchor links + */ + addContextElements : function() { + $('div[id] > :header:first').each(function() { + $('\u00B6'). + attr('href', '#' + this.id). + attr('title', _('Permalink to this headline')). + appendTo(this); + }); + $('dt[id]').each(function() { + $('\u00B6'). + attr('href', '#' + this.id). + attr('title', _('Permalink to this definition')). + appendTo(this); + }); + }, + + /** + * workaround a firefox stupidity + * see: https://bugzilla.mozilla.org/show_bug.cgi?id=645075 + */ + fixFirefoxAnchorBug : function() { + if (document.location.hash) + window.setTimeout(function() { + document.location.href += ''; + }, 10); + }, + + /** + * highlight the search words provided in the url in the text + */ + highlightSearchWords : function() { + var params = $.getQueryParameters(); + var terms = (params.highlight) ? params.highlight[0].split(/\s+/) : []; + if (terms.length) { + var body = $('div.body'); + if (!body.length) { + body = $('body'); + } + window.setTimeout(function() { + $.each(terms, function() { + body.highlightText(this.toLowerCase(), 'highlighted'); + }); + }, 10); + $('') + .appendTo($('#searchbox')); + } + }, + + /** + * init the domain index toggle buttons + */ + initIndexTable : function() { + var togglers = $('img.toggler').click(function() { + var src = $(this).attr('src'); + var idnum = $(this).attr('id').substr(7); + $('tr.cg-' + idnum).toggle(); + if (src.substr(-9) == 'minus.png') + $(this).attr('src', src.substr(0, src.length-9) + 'plus.png'); + else + $(this).attr('src', src.substr(0, src.length-8) + 'minus.png'); + }).css('display', ''); + if (DOCUMENTATION_OPTIONS.COLLAPSE_INDEX) { + togglers.click(); + } + }, + + /** + * helper function to hide the search marks again + */ + hideSearchWords : function() { + $('#searchbox .highlight-link').fadeOut(300); + $('span.highlighted').removeClass('highlighted'); + }, + + /** + * make the url absolute + */ + makeURL : function(relativeURL) { + return DOCUMENTATION_OPTIONS.URL_ROOT + '/' + relativeURL; + }, + + /** + * get the current relative url + */ + getCurrentURL : function() { + var path = document.location.pathname; + var parts = path.split(/\//); + $.each(DOCUMENTATION_OPTIONS.URL_ROOT.split(/\//), function() { + if (this == '..') + parts.pop(); + }); + var url = parts.join('/'); + return path.substring(url.lastIndexOf('/') + 1, path.length - 1); + }, + + initOnKeyListeners: function() { + $(document).keyup(function(event) { + var activeElementType = document.activeElement.tagName; + // don't navigate when in search box or textarea + if (activeElementType !== 'TEXTAREA' && activeElementType !== 'INPUT' && activeElementType !== 'SELECT') { + switch (event.keyCode) { + case 37: // left + var prevHref = $('link[rel="prev"]').prop('href'); + if (prevHref) { + window.location.href = prevHref; + return false; + } + case 39: // right + var nextHref = $('link[rel="next"]').prop('href'); + if (nextHref) { + window.location.href = nextHref; + return false; + } + } + } + }); + } +}; + +// quick alias for translations +_ = Documentation.gettext; + +$(document).ready(function() { + Documentation.init(); +}); \ No newline at end of file diff --git a/php/apps/phpmyadmin49/html/doc/html/_static/down-pressed.png b/php/apps/phpmyadmin49/html/doc/html/_static/down-pressed.png new file mode 100644 index 00000000..20cd4e2b Binary files /dev/null and b/php/apps/phpmyadmin49/html/doc/html/_static/down-pressed.png differ diff --git a/php/apps/phpmyadmin49/html/doc/html/_static/down.png b/php/apps/phpmyadmin49/html/doc/html/_static/down.png new file mode 100644 index 00000000..b440895d Binary files /dev/null and b/php/apps/phpmyadmin49/html/doc/html/_static/down.png differ diff --git a/php/apps/phpmyadmin49/html/doc/html/_static/file.png b/php/apps/phpmyadmin49/html/doc/html/_static/file.png new file mode 100644 index 00000000..eedc8194 Binary files /dev/null and b/php/apps/phpmyadmin49/html/doc/html/_static/file.png differ diff --git a/php/apps/phpmyadmin49/html/doc/html/_static/jquery.js b/php/apps/phpmyadmin49/html/doc/html/_static/jquery.js new file mode 100644 index 00000000..6ffad14f --- /dev/null +++ b/php/apps/phpmyadmin49/html/doc/html/_static/jquery.js @@ -0,0 +1,10220 @@ +/*! + * jQuery JavaScript Library v3.1.1 + * https://jquery.com/ + * + * Includes Sizzle.js + * https://sizzlejs.com/ + * + * Copyright jQuery Foundation and other contributors + * Released under the MIT license + * https://jquery.org/license + * + * Date: 2019-04-18T20:57Z + */ +( function( global, factory ) { + + "use strict"; + + if ( typeof module === "object" && typeof module.exports === "object" ) { + + // For CommonJS and CommonJS-like environments where a proper `window` + // is present, execute the factory and get jQuery. + // For environments that do not have a `window` with a `document` + // (such as Node.js), expose a factory as module.exports. + // This accentuates the need for the creation of a real `window`. + // e.g. var jQuery = require("jquery")(window); + // See ticket #14549 for more info. + module.exports = global.document ? + factory( global, true ) : + function( w ) { + if ( !w.document ) { + throw new Error( "jQuery requires a window with a document" ); + } + return factory( w ); + }; + } else { + factory( global ); + } + +// Pass this if window is not defined yet +} )( typeof window !== "undefined" ? window : this, function( window, noGlobal ) { + +// Edge <= 12 - 13+, Firefox <=18 - 45+, IE 10 - 11, Safari 5.1 - 9+, iOS 6 - 9.1 +// throw exceptions when non-strict code (e.g., ASP.NET 4.5) accesses strict mode +// arguments.callee.caller (trac-13335). But as of jQuery 3.0 (2016), strict mode should be common +// enough that all such attempts are guarded in a try block. + + +var arr = []; + +var document = window.document; + +var getProto = Object.getPrototypeOf; + +var slice = arr.slice; + +var concat = arr.concat; + +var push = arr.push; + +var indexOf = arr.indexOf; + +var class2type = {}; + +var toString = class2type.toString; + +var hasOwn = class2type.hasOwnProperty; + +var fnToString = hasOwn.toString; + +var ObjectFunctionString = fnToString.call( Object ); + +var support = {}; + + + + function DOMEval( code, doc ) { + doc = doc || document; + + var script = doc.createElement( "script" ); + + script.text = code; + doc.head.appendChild( script ).parentNode.removeChild( script ); + } +/* global Symbol */ +// Defining this global in .eslintrc.json would create a danger of using the global +// unguarded in another place, it seems safer to define global only for this module + + + +var + version = "3.1.1", + + // Define a local copy of jQuery + jQuery = function( selector, context ) { + + // The jQuery object is actually just the init constructor 'enhanced' + // Need init if jQuery is called (just allow error to be thrown if not included) + return new jQuery.fn.init( selector, context ); + }, + + // Support: Android <=4.0 only + // Make sure we trim BOM and NBSP + rtrim = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, + + // Matches dashed string for camelizing + rmsPrefix = /^-ms-/, + rdashAlpha = /-([a-z])/g, + + // Used by jQuery.camelCase as callback to replace() + fcamelCase = function( all, letter ) { + return letter.toUpperCase(); + }; + +jQuery.fn = jQuery.prototype = { + + // The current version of jQuery being used + jquery: version, + + constructor: jQuery, + + // The default length of a jQuery object is 0 + length: 0, + + toArray: function() { + return slice.call( this ); + }, + + // Get the Nth element in the matched element set OR + // Get the whole matched element set as a clean array + get: function( num ) { + + // Return all the elements in a clean array + if ( num == null ) { + return slice.call( this ); + } + + // Return just the one element from the set + return num < 0 ? this[ num + this.length ] : this[ num ]; + }, + + // Take an array of elements and push it onto the stack + // (returning the new matched element set) + pushStack: function( elems ) { + + // Build a new jQuery matched element set + var ret = jQuery.merge( this.constructor(), elems ); + + // Add the old object onto the stack (as a reference) + ret.prevObject = this; + + // Return the newly-formed element set + return ret; + }, + + // Execute a callback for every element in the matched set. + each: function( callback ) { + return jQuery.each( this, callback ); + }, + + map: function( callback ) { + return this.pushStack( jQuery.map( this, function( elem, i ) { + return callback.call( elem, i, elem ); + } ) ); + }, + + slice: function() { + return this.pushStack( slice.apply( this, arguments ) ); + }, + + first: function() { + return this.eq( 0 ); + }, + + last: function() { + return this.eq( -1 ); + }, + + eq: function( i ) { + var len = this.length, + j = +i + ( i < 0 ? len : 0 ); + return this.pushStack( j >= 0 && j < len ? [ this[ j ] ] : [] ); + }, + + end: function() { + return this.prevObject || this.constructor(); + }, + + // For internal use only. + // Behaves like an Array's method, not like a jQuery method. + push: push, + sort: arr.sort, + splice: arr.splice +}; + +jQuery.extend = jQuery.fn.extend = function() { + var options, name, src, copy, copyIsArray, clone, + target = arguments[ 0 ] || {}, + i = 1, + length = arguments.length, + deep = false; + + // Handle a deep copy situation + if ( typeof target === "boolean" ) { + deep = target; + + // Skip the boolean and the target + target = arguments[ i ] || {}; + i++; + } + + // Handle case when target is a string or something (possible in deep copy) + if ( typeof target !== "object" && !jQuery.isFunction( target ) ) { + target = {}; + } + + // Extend jQuery itself if only one argument is passed + if ( i === length ) { + target = this; + i--; + } + + for ( ; i < length; i++ ) { + + // Only deal with non-null/undefined values + if ( ( options = arguments[ i ] ) != null ) { + + // Extend the base object + for ( name in options ) { + src = target[ name ]; + copy = options[ name ]; + + // Prevent Object.prototype pollution + // Prevent never-ending loop + if ( name === "__proto__" || target === copy ) { + continue; + } + + // Recurse if we're merging plain objects or arrays + if ( deep && copy && ( jQuery.isPlainObject( copy ) || + ( copyIsArray = jQuery.isArray( copy ) ) ) ) { + + if ( copyIsArray ) { + copyIsArray = false; + clone = src && jQuery.isArray( src ) ? src : []; + + } else { + clone = src && jQuery.isPlainObject( src ) ? src : {}; + } + + // Never move original objects, clone them + target[ name ] = jQuery.extend( deep, clone, copy ); + + // Don't bring in undefined values + } else if ( copy !== undefined ) { + target[ name ] = copy; + } + } + } + } + + // Return the modified object + return target; +}; + +jQuery.extend( { + + // Unique for each copy of jQuery on the page + expando: "jQuery" + ( version + Math.random() ).replace( /\D/g, "" ), + + // Assume jQuery is ready without the ready module + isReady: true, + + error: function( msg ) { + throw new Error( msg ); + }, + + noop: function() {}, + + isFunction: function( obj ) { + return jQuery.type( obj ) === "function"; + }, + + isArray: Array.isArray, + + isWindow: function( obj ) { + return obj != null && obj === obj.window; + }, + + isNumeric: function( obj ) { + + // As of jQuery 3.0, isNumeric is limited to + // strings and numbers (primitives or objects) + // that can be coerced to finite numbers (gh-2662) + var type = jQuery.type( obj ); + return ( type === "number" || type === "string" ) && + + // parseFloat NaNs numeric-cast false positives ("") + // ...but misinterprets leading-number strings, particularly hex literals ("0x...") + // subtraction forces infinities to NaN + !isNaN( obj - parseFloat( obj ) ); + }, + + isPlainObject: function( obj ) { + var proto, Ctor; + + // Detect obvious negatives + // Use toString instead of jQuery.type to catch host objects + if ( !obj || toString.call( obj ) !== "[object Object]" ) { + return false; + } + + proto = getProto( obj ); + + // Objects with no prototype (e.g., `Object.create( null )`) are plain + if ( !proto ) { + return true; + } + + // Objects with prototype are plain iff they were constructed by a global Object function + Ctor = hasOwn.call( proto, "constructor" ) && proto.constructor; + return typeof Ctor === "function" && fnToString.call( Ctor ) === ObjectFunctionString; + }, + + isEmptyObject: function( obj ) { + + /* eslint-disable no-unused-vars */ + // See https://github.com/eslint/eslint/issues/6125 + var name; + + for ( name in obj ) { + return false; + } + return true; + }, + + type: function( obj ) { + if ( obj == null ) { + return obj + ""; + } + + // Support: Android <=2.3 only (functionish RegExp) + return typeof obj === "object" || typeof obj === "function" ? + class2type[ toString.call( obj ) ] || "object" : + typeof obj; + }, + + // Evaluates a script in a global context + globalEval: function( code ) { + DOMEval( code ); + }, + + // Convert dashed to camelCase; used by the css and data modules + // Support: IE <=9 - 11, Edge 12 - 13 + // Microsoft forgot to hump their vendor prefix (#9572) + camelCase: function( string ) { + return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase ); + }, + + nodeName: function( elem, name ) { + return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase(); + }, + + each: function( obj, callback ) { + var length, i = 0; + + if ( isArrayLike( obj ) ) { + length = obj.length; + for ( ; i < length; i++ ) { + if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) { + break; + } + } + } else { + for ( i in obj ) { + if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) { + break; + } + } + } + + return obj; + }, + + // Support: Android <=4.0 only + trim: function( text ) { + return text == null ? + "" : + ( text + "" ).replace( rtrim, "" ); + }, + + // results is for internal usage only + makeArray: function( arr, results ) { + var ret = results || []; + + if ( arr != null ) { + if ( isArrayLike( Object( arr ) ) ) { + jQuery.merge( ret, + typeof arr === "string" ? + [ arr ] : arr + ); + } else { + push.call( ret, arr ); + } + } + + return ret; + }, + + inArray: function( elem, arr, i ) { + return arr == null ? -1 : indexOf.call( arr, elem, i ); + }, + + // Support: Android <=4.0 only, PhantomJS 1 only + // push.apply(_, arraylike) throws on ancient WebKit + merge: function( first, second ) { + var len = +second.length, + j = 0, + i = first.length; + + for ( ; j < len; j++ ) { + first[ i++ ] = second[ j ]; + } + + first.length = i; + + return first; + }, + + grep: function( elems, callback, invert ) { + var callbackInverse, + matches = [], + i = 0, + length = elems.length, + callbackExpect = !invert; + + // Go through the array, only saving the items + // that pass the validator function + for ( ; i < length; i++ ) { + callbackInverse = !callback( elems[ i ], i ); + if ( callbackInverse !== callbackExpect ) { + matches.push( elems[ i ] ); + } + } + + return matches; + }, + + // arg is for internal usage only + map: function( elems, callback, arg ) { + var length, value, + i = 0, + ret = []; + + // Go through the array, translating each of the items to their new values + if ( isArrayLike( elems ) ) { + length = elems.length; + for ( ; i < length; i++ ) { + value = callback( elems[ i ], i, arg ); + + if ( value != null ) { + ret.push( value ); + } + } + + // Go through every key on the object, + } else { + for ( i in elems ) { + value = callback( elems[ i ], i, arg ); + + if ( value != null ) { + ret.push( value ); + } + } + } + + // Flatten any nested arrays + return concat.apply( [], ret ); + }, + + // A global GUID counter for objects + guid: 1, + + // Bind a function to a context, optionally partially applying any + // arguments. + proxy: function( fn, context ) { + var tmp, args, proxy; + + if ( typeof context === "string" ) { + tmp = fn[ context ]; + context = fn; + fn = tmp; + } + + // Quick check to determine if target is callable, in the spec + // this throws a TypeError, but we will just return undefined. + if ( !jQuery.isFunction( fn ) ) { + return undefined; + } + + // Simulated bind + args = slice.call( arguments, 2 ); + proxy = function() { + return fn.apply( context || this, args.concat( slice.call( arguments ) ) ); + }; + + // Set the guid of unique handler to the same of original handler, so it can be removed + proxy.guid = fn.guid = fn.guid || jQuery.guid++; + + return proxy; + }, + + now: Date.now, + + // jQuery.support is not used in Core but other projects attach their + // properties to it so it needs to exist. + support: support +} ); + +if ( typeof Symbol === "function" ) { + jQuery.fn[ Symbol.iterator ] = arr[ Symbol.iterator ]; +} + +// Populate the class2type map +jQuery.each( "Boolean Number String Function Array Date RegExp Object Error Symbol".split( " " ), +function( i, name ) { + class2type[ "[object " + name + "]" ] = name.toLowerCase(); +} ); + +function isArrayLike( obj ) { + + // Support: real iOS 8.2 only (not reproducible in simulator) + // `in` check used to prevent JIT error (gh-2145) + // hasOwn isn't used here due to false negatives + // regarding Nodelist length in IE + var length = !!obj && "length" in obj && obj.length, + type = jQuery.type( obj ); + + if ( type === "function" || jQuery.isWindow( obj ) ) { + return false; + } + + return type === "array" || length === 0 || + typeof length === "number" && length > 0 && ( length - 1 ) in obj; +} +var Sizzle = +/*! + * Sizzle CSS Selector Engine v2.3.3 + * https://sizzlejs.com/ + * + * Copyright jQuery Foundation and other contributors + * Released under the MIT license + * http://jquery.org/license + * + * Date: 2016-08-08 + */ +(function( window ) { + +var i, + support, + Expr, + getText, + isXML, + tokenize, + compile, + select, + outermostContext, + sortInput, + hasDuplicate, + + // Local document vars + setDocument, + document, + docElem, + documentIsHTML, + rbuggyQSA, + rbuggyMatches, + matches, + contains, + + // Instance-specific data + expando = "sizzle" + 1 * new Date(), + preferredDoc = window.document, + dirruns = 0, + done = 0, + classCache = createCache(), + tokenCache = createCache(), + compilerCache = createCache(), + sortOrder = function( a, b ) { + if ( a === b ) { + hasDuplicate = true; + } + return 0; + }, + + // Instance methods + hasOwn = ({}).hasOwnProperty, + arr = [], + pop = arr.pop, + push_native = arr.push, + push = arr.push, + slice = arr.slice, + // Use a stripped-down indexOf as it's faster than native + // https://jsperf.com/thor-indexof-vs-for/5 + indexOf = function( list, elem ) { + var i = 0, + len = list.length; + for ( ; i < len; i++ ) { + if ( list[i] === elem ) { + return i; + } + } + return -1; + }, + + booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped", + + // Regular expressions + + // http://www.w3.org/TR/css3-selectors/#whitespace + whitespace = "[\\x20\\t\\r\\n\\f]", + + // http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier + identifier = "(?:\\\\.|[\\w-]|[^\0-\\xa0])+", + + // Attribute selectors: http://www.w3.org/TR/selectors/#attribute-selectors + attributes = "\\[" + whitespace + "*(" + identifier + ")(?:" + whitespace + + // Operator (capture 2) + "*([*^$|!~]?=)" + whitespace + + // "Attribute values must be CSS identifiers [capture 5] or strings [capture 3 or capture 4]" + "*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|(" + identifier + "))|)" + whitespace + + "*\\]", + + pseudos = ":(" + identifier + ")(?:\\((" + + // To reduce the number of selectors needing tokenize in the preFilter, prefer arguments: + // 1. quoted (capture 3; capture 4 or capture 5) + "('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|" + + // 2. simple (capture 6) + "((?:\\\\.|[^\\\\()[\\]]|" + attributes + ")*)|" + + // 3. anything else (capture 2) + ".*" + + ")\\)|)", + + // Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter + rwhitespace = new RegExp( whitespace + "+", "g" ), + rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$", "g" ), + + rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ), + rcombinators = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" + whitespace + "*" ), + + rattributeQuotes = new RegExp( "=" + whitespace + "*([^\\]'\"]*?)" + whitespace + "*\\]", "g" ), + + rpseudo = new RegExp( pseudos ), + ridentifier = new RegExp( "^" + identifier + "$" ), + + matchExpr = { + "ID": new RegExp( "^#(" + identifier + ")" ), + "CLASS": new RegExp( "^\\.(" + identifier + ")" ), + "TAG": new RegExp( "^(" + identifier + "|[*])" ), + "ATTR": new RegExp( "^" + attributes ), + "PSEUDO": new RegExp( "^" + pseudos ), + "CHILD": new RegExp( "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + whitespace + + "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + whitespace + + "*(\\d+)|))" + whitespace + "*\\)|)", "i" ), + "bool": new RegExp( "^(?:" + booleans + ")$", "i" ), + // For use in libraries implementing .is() + // We use this for POS matching in `select` + "needsContext": new RegExp( "^" + whitespace + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" + + whitespace + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" ) + }, + + rinputs = /^(?:input|select|textarea|button)$/i, + rheader = /^h\d$/i, + + rnative = /^[^{]+\{\s*\[native \w/, + + // Easily-parseable/retrievable ID or TAG or CLASS selectors + rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/, + + rsibling = /[+~]/, + + // CSS escapes + // http://www.w3.org/TR/CSS21/syndata.html#escaped-characters + runescape = new RegExp( "\\\\([\\da-f]{1,6}" + whitespace + "?|(" + whitespace + ")|.)", "ig" ), + funescape = function( _, escaped, escapedWhitespace ) { + var high = "0x" + escaped - 0x10000; + // NaN means non-codepoint + // Support: Firefox<24 + // Workaround erroneous numeric interpretation of +"0x" + return high !== high || escapedWhitespace ? + escaped : + high < 0 ? + // BMP codepoint + String.fromCharCode( high + 0x10000 ) : + // Supplemental Plane codepoint (surrogate pair) + String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 ); + }, + + // CSS string/identifier serialization + // https://drafts.csswg.org/cssom/#common-serializing-idioms + rcssescape = /([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g, + fcssescape = function( ch, asCodePoint ) { + if ( asCodePoint ) { + + // U+0000 NULL becomes U+FFFD REPLACEMENT CHARACTER + if ( ch === "\0" ) { + return "\uFFFD"; + } + + // Control characters and (dependent upon position) numbers get escaped as code points + return ch.slice( 0, -1 ) + "\\" + ch.charCodeAt( ch.length - 1 ).toString( 16 ) + " "; + } + + // Other potentially-special ASCII characters get backslash-escaped + return "\\" + ch; + }, + + // Used for iframes + // See setDocument() + // Removing the function wrapper causes a "Permission Denied" + // error in IE + unloadHandler = function() { + setDocument(); + }, + + disabledAncestor = addCombinator( + function( elem ) { + return elem.disabled === true && ("form" in elem || "label" in elem); + }, + { dir: "parentNode", next: "legend" } + ); + +// Optimize for push.apply( _, NodeList ) +try { + push.apply( + (arr = slice.call( preferredDoc.childNodes )), + preferredDoc.childNodes + ); + // Support: Android<4.0 + // Detect silently failing push.apply + arr[ preferredDoc.childNodes.length ].nodeType; +} catch ( e ) { + push = { apply: arr.length ? + + // Leverage slice if possible + function( target, els ) { + push_native.apply( target, slice.call(els) ); + } : + + // Support: IE<9 + // Otherwise append directly + function( target, els ) { + var j = target.length, + i = 0; + // Can't trust NodeList.length + while ( (target[j++] = els[i++]) ) {} + target.length = j - 1; + } + }; +} + +function Sizzle( selector, context, results, seed ) { + var m, i, elem, nid, match, groups, newSelector, + newContext = context && context.ownerDocument, + + // nodeType defaults to 9, since context defaults to document + nodeType = context ? context.nodeType : 9; + + results = results || []; + + // Return early from calls with invalid selector or context + if ( typeof selector !== "string" || !selector || + nodeType !== 1 && nodeType !== 9 && nodeType !== 11 ) { + + return results; + } + + // Try to shortcut find operations (as opposed to filters) in HTML documents + if ( !seed ) { + + if ( ( context ? context.ownerDocument || context : preferredDoc ) !== document ) { + setDocument( context ); + } + context = context || document; + + if ( documentIsHTML ) { + + // If the selector is sufficiently simple, try using a "get*By*" DOM method + // (excepting DocumentFragment context, where the methods don't exist) + if ( nodeType !== 11 && (match = rquickExpr.exec( selector )) ) { + + // ID selector + if ( (m = match[1]) ) { + + // Document context + if ( nodeType === 9 ) { + if ( (elem = context.getElementById( m )) ) { + + // Support: IE, Opera, Webkit + // TODO: identify versions + // getElementById can match elements by name instead of ID + if ( elem.id === m ) { + results.push( elem ); + return results; + } + } else { + return results; + } + + // Element context + } else { + + // Support: IE, Opera, Webkit + // TODO: identify versions + // getElementById can match elements by name instead of ID + if ( newContext && (elem = newContext.getElementById( m )) && + contains( context, elem ) && + elem.id === m ) { + + results.push( elem ); + return results; + } + } + + // Type selector + } else if ( match[2] ) { + push.apply( results, context.getElementsByTagName( selector ) ); + return results; + + // Class selector + } else if ( (m = match[3]) && support.getElementsByClassName && + context.getElementsByClassName ) { + + push.apply( results, context.getElementsByClassName( m ) ); + return results; + } + } + + // Take advantage of querySelectorAll + if ( support.qsa && + !compilerCache[ selector + " " ] && + (!rbuggyQSA || !rbuggyQSA.test( selector )) ) { + + if ( nodeType !== 1 ) { + newContext = context; + newSelector = selector; + + // qSA looks outside Element context, which is not what we want + // Thanks to Andrew Dupont for this workaround technique + // Support: IE <=8 + // Exclude object elements + } else if ( context.nodeName.toLowerCase() !== "object" ) { + + // Capture the context ID, setting it first if necessary + if ( (nid = context.getAttribute( "id" )) ) { + nid = nid.replace( rcssescape, fcssescape ); + } else { + context.setAttribute( "id", (nid = expando) ); + } + + // Prefix every selector in the list + groups = tokenize( selector ); + i = groups.length; + while ( i-- ) { + groups[i] = "#" + nid + " " + toSelector( groups[i] ); + } + newSelector = groups.join( "," ); + + // Expand context for sibling selectors + newContext = rsibling.test( selector ) && testContext( context.parentNode ) || + context; + } + + if ( newSelector ) { + try { + push.apply( results, + newContext.querySelectorAll( newSelector ) + ); + return results; + } catch ( qsaError ) { + } finally { + if ( nid === expando ) { + context.removeAttribute( "id" ); + } + } + } + } + } + } + + // All others + return select( selector.replace( rtrim, "$1" ), context, results, seed ); +} + +/** + * Create key-value caches of limited size + * @returns {function(string, object)} Returns the Object data after storing it on itself with + * property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength) + * deleting the oldest entry + */ +function createCache() { + var keys = []; + + function cache( key, value ) { + // Use (key + " ") to avoid collision with native prototype properties (see Issue #157) + if ( keys.push( key + " " ) > Expr.cacheLength ) { + // Only keep the most recent entries + delete cache[ keys.shift() ]; + } + return (cache[ key + " " ] = value); + } + return cache; +} + +/** + * Mark a function for special use by Sizzle + * @param {Function} fn The function to mark + */ +function markFunction( fn ) { + fn[ expando ] = true; + return fn; +} + +/** + * Support testing using an element + * @param {Function} fn Passed the created element and returns a boolean result + */ +function assert( fn ) { + var el = document.createElement("fieldset"); + + try { + return !!fn( el ); + } catch (e) { + return false; + } finally { + // Remove from its parent by default + if ( el.parentNode ) { + el.parentNode.removeChild( el ); + } + // release memory in IE + el = null; + } +} + +/** + * Adds the same handler for all of the specified attrs + * @param {String} attrs Pipe-separated list of attributes + * @param {Function} handler The method that will be applied + */ +function addHandle( attrs, handler ) { + var arr = attrs.split("|"), + i = arr.length; + + while ( i-- ) { + Expr.attrHandle[ arr[i] ] = handler; + } +} + +/** + * Checks document order of two siblings + * @param {Element} a + * @param {Element} b + * @returns {Number} Returns less than 0 if a precedes b, greater than 0 if a follows b + */ +function siblingCheck( a, b ) { + var cur = b && a, + diff = cur && a.nodeType === 1 && b.nodeType === 1 && + a.sourceIndex - b.sourceIndex; + + // Use IE sourceIndex if available on both nodes + if ( diff ) { + return diff; + } + + // Check if b follows a + if ( cur ) { + while ( (cur = cur.nextSibling) ) { + if ( cur === b ) { + return -1; + } + } + } + + return a ? 1 : -1; +} + +/** + * Returns a function to use in pseudos for input types + * @param {String} type + */ +function createInputPseudo( type ) { + return function( elem ) { + var name = elem.nodeName.toLowerCase(); + return name === "input" && elem.type === type; + }; +} + +/** + * Returns a function to use in pseudos for buttons + * @param {String} type + */ +function createButtonPseudo( type ) { + return function( elem ) { + var name = elem.nodeName.toLowerCase(); + return (name === "input" || name === "button") && elem.type === type; + }; +} + +/** + * Returns a function to use in pseudos for :enabled/:disabled + * @param {Boolean} disabled true for :disabled; false for :enabled + */ +function createDisabledPseudo( disabled ) { + + // Known :disabled false positives: fieldset[disabled] > legend:nth-of-type(n+2) :can-disable + return function( elem ) { + + // Only certain elements can match :enabled or :disabled + // https://html.spec.whatwg.org/multipage/scripting.html#selector-enabled + // https://html.spec.whatwg.org/multipage/scripting.html#selector-disabled + if ( "form" in elem ) { + + // Check for inherited disabledness on relevant non-disabled elements: + // * listed form-associated elements in a disabled fieldset + // https://html.spec.whatwg.org/multipage/forms.html#category-listed + // https://html.spec.whatwg.org/multipage/forms.html#concept-fe-disabled + // * option elements in a disabled optgroup + // https://html.spec.whatwg.org/multipage/forms.html#concept-option-disabled + // All such elements have a "form" property. + if ( elem.parentNode && elem.disabled === false ) { + + // Option elements defer to a parent optgroup if present + if ( "label" in elem ) { + if ( "label" in elem.parentNode ) { + return elem.parentNode.disabled === disabled; + } else { + return elem.disabled === disabled; + } + } + + // Support: IE 6 - 11 + // Use the isDisabled shortcut property to check for disabled fieldset ancestors + return elem.isDisabled === disabled || + + // Where there is no isDisabled, check manually + /* jshint -W018 */ + elem.isDisabled !== !disabled && + disabledAncestor( elem ) === disabled; + } + + return elem.disabled === disabled; + + // Try to winnow out elements that can't be disabled before trusting the disabled property. + // Some victims get caught in our net (label, legend, menu, track), but it shouldn't + // even exist on them, let alone have a boolean value. + } else if ( "label" in elem ) { + return elem.disabled === disabled; + } + + // Remaining elements are neither :enabled nor :disabled + return false; + }; +} + +/** + * Returns a function to use in pseudos for positionals + * @param {Function} fn + */ +function createPositionalPseudo( fn ) { + return markFunction(function( argument ) { + argument = +argument; + return markFunction(function( seed, matches ) { + var j, + matchIndexes = fn( [], seed.length, argument ), + i = matchIndexes.length; + + // Match elements found at the specified indexes + while ( i-- ) { + if ( seed[ (j = matchIndexes[i]) ] ) { + seed[j] = !(matches[j] = seed[j]); + } + } + }); + }); +} + +/** + * Checks a node for validity as a Sizzle context + * @param {Element|Object=} context + * @returns {Element|Object|Boolean} The input node if acceptable, otherwise a falsy value + */ +function testContext( context ) { + return context && typeof context.getElementsByTagName !== "undefined" && context; +} + +// Expose support vars for convenience +support = Sizzle.support = {}; + +/** + * Detects XML nodes + * @param {Element|Object} elem An element or a document + * @returns {Boolean} True iff elem is a non-HTML XML node + */ +isXML = Sizzle.isXML = function( elem ) { + // documentElement is verified for cases where it doesn't yet exist + // (such as loading iframes in IE - #4833) + var documentElement = elem && (elem.ownerDocument || elem).documentElement; + return documentElement ? documentElement.nodeName !== "HTML" : false; +}; + +/** + * Sets document-related variables once based on the current document + * @param {Element|Object} [doc] An element or document object to use to set the document + * @returns {Object} Returns the current document + */ +setDocument = Sizzle.setDocument = function( node ) { + var hasCompare, subWindow, + doc = node ? node.ownerDocument || node : preferredDoc; + + // Return early if doc is invalid or already selected + if ( doc === document || doc.nodeType !== 9 || !doc.documentElement ) { + return document; + } + + // Update global variables + document = doc; + docElem = document.documentElement; + documentIsHTML = !isXML( document ); + + // Support: IE 9-11, Edge + // Accessing iframe documents after unload throws "permission denied" errors (jQuery #13936) + if ( preferredDoc !== document && + (subWindow = document.defaultView) && subWindow.top !== subWindow ) { + + // Support: IE 11, Edge + if ( subWindow.addEventListener ) { + subWindow.addEventListener( "unload", unloadHandler, false ); + + // Support: IE 9 - 10 only + } else if ( subWindow.attachEvent ) { + subWindow.attachEvent( "onunload", unloadHandler ); + } + } + + /* Attributes + ---------------------------------------------------------------------- */ + + // Support: IE<8 + // Verify that getAttribute really returns attributes and not properties + // (excepting IE8 booleans) + support.attributes = assert(function( el ) { + el.className = "i"; + return !el.getAttribute("className"); + }); + + /* getElement(s)By* + ---------------------------------------------------------------------- */ + + // Check if getElementsByTagName("*") returns only elements + support.getElementsByTagName = assert(function( el ) { + el.appendChild( document.createComment("") ); + return !el.getElementsByTagName("*").length; + }); + + // Support: IE<9 + support.getElementsByClassName = rnative.test( document.getElementsByClassName ); + + // Support: IE<10 + // Check if getElementById returns elements by name + // The broken getElementById methods don't pick up programmatically-set names, + // so use a roundabout getElementsByName test + support.getById = assert(function( el ) { + docElem.appendChild( el ).id = expando; + return !document.getElementsByName || !document.getElementsByName( expando ).length; + }); + + // ID filter and find + if ( support.getById ) { + Expr.filter["ID"] = function( id ) { + var attrId = id.replace( runescape, funescape ); + return function( elem ) { + return elem.getAttribute("id") === attrId; + }; + }; + Expr.find["ID"] = function( id, context ) { + if ( typeof context.getElementById !== "undefined" && documentIsHTML ) { + var elem = context.getElementById( id ); + return elem ? [ elem ] : []; + } + }; + } else { + Expr.filter["ID"] = function( id ) { + var attrId = id.replace( runescape, funescape ); + return function( elem ) { + var node = typeof elem.getAttributeNode !== "undefined" && + elem.getAttributeNode("id"); + return node && node.value === attrId; + }; + }; + + // Support: IE 6 - 7 only + // getElementById is not reliable as a find shortcut + Expr.find["ID"] = function( id, context ) { + if ( typeof context.getElementById !== "undefined" && documentIsHTML ) { + var node, i, elems, + elem = context.getElementById( id ); + + if ( elem ) { + + // Verify the id attribute + node = elem.getAttributeNode("id"); + if ( node && node.value === id ) { + return [ elem ]; + } + + // Fall back on getElementsByName + elems = context.getElementsByName( id ); + i = 0; + while ( (elem = elems[i++]) ) { + node = elem.getAttributeNode("id"); + if ( node && node.value === id ) { + return [ elem ]; + } + } + } + + return []; + } + }; + } + + // Tag + Expr.find["TAG"] = support.getElementsByTagName ? + function( tag, context ) { + if ( typeof context.getElementsByTagName !== "undefined" ) { + return context.getElementsByTagName( tag ); + + // DocumentFragment nodes don't have gEBTN + } else if ( support.qsa ) { + return context.querySelectorAll( tag ); + } + } : + + function( tag, context ) { + var elem, + tmp = [], + i = 0, + // By happy coincidence, a (broken) gEBTN appears on DocumentFragment nodes too + results = context.getElementsByTagName( tag ); + + // Filter out possible comments + if ( tag === "*" ) { + while ( (elem = results[i++]) ) { + if ( elem.nodeType === 1 ) { + tmp.push( elem ); + } + } + + return tmp; + } + return results; + }; + + // Class + Expr.find["CLASS"] = support.getElementsByClassName && function( className, context ) { + if ( typeof context.getElementsByClassName !== "undefined" && documentIsHTML ) { + return context.getElementsByClassName( className ); + } + }; + + /* QSA/matchesSelector + ---------------------------------------------------------------------- */ + + // QSA and matchesSelector support + + // matchesSelector(:active) reports false when true (IE9/Opera 11.5) + rbuggyMatches = []; + + // qSa(:focus) reports false when true (Chrome 21) + // We allow this because of a bug in IE8/9 that throws an error + // whenever `document.activeElement` is accessed on an iframe + // So, we allow :focus to pass through QSA all the time to avoid the IE error + // See https://bugs.jquery.com/ticket/13378 + rbuggyQSA = []; + + if ( (support.qsa = rnative.test( document.querySelectorAll )) ) { + // Build QSA regex + // Regex strategy adopted from Diego Perini + assert(function( el ) { + // Select is set to empty string on purpose + // This is to test IE's treatment of not explicitly + // setting a boolean content attribute, + // since its presence should be enough + // https://bugs.jquery.com/ticket/12359 + docElem.appendChild( el ).innerHTML = "" + + ""; + + // Support: IE8, Opera 11-12.16 + // Nothing should be selected when empty strings follow ^= or $= or *= + // The test attribute must be unknown in Opera but "safe" for WinRT + // https://msdn.microsoft.com/en-us/library/ie/hh465388.aspx#attribute_section + if ( el.querySelectorAll("[msallowcapture^='']").length ) { + rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:''|\"\")" ); + } + + // Support: IE8 + // Boolean attributes and "value" are not treated correctly + if ( !el.querySelectorAll("[selected]").length ) { + rbuggyQSA.push( "\\[" + whitespace + "*(?:value|" + booleans + ")" ); + } + + // Support: Chrome<29, Android<4.4, Safari<7.0+, iOS<7.0+, PhantomJS<1.9.8+ + if ( !el.querySelectorAll( "[id~=" + expando + "-]" ).length ) { + rbuggyQSA.push("~="); + } + + // Webkit/Opera - :checked should return selected option elements + // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked + // IE8 throws error here and will not see later tests + if ( !el.querySelectorAll(":checked").length ) { + rbuggyQSA.push(":checked"); + } + + // Support: Safari 8+, iOS 8+ + // https://bugs.webkit.org/show_bug.cgi?id=136851 + // In-page `selector#id sibling-combinator selector` fails + if ( !el.querySelectorAll( "a#" + expando + "+*" ).length ) { + rbuggyQSA.push(".#.+[+~]"); + } + }); + + assert(function( el ) { + el.innerHTML = "" + + ""; + + // Support: Windows 8 Native Apps + // The type and name attributes are restricted during .innerHTML assignment + var input = document.createElement("input"); + input.setAttribute( "type", "hidden" ); + el.appendChild( input ).setAttribute( "name", "D" ); + + // Support: IE8 + // Enforce case-sensitivity of name attribute + if ( el.querySelectorAll("[name=d]").length ) { + rbuggyQSA.push( "name" + whitespace + "*[*^$|!~]?=" ); + } + + // FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled) + // IE8 throws error here and will not see later tests + if ( el.querySelectorAll(":enabled").length !== 2 ) { + rbuggyQSA.push( ":enabled", ":disabled" ); + } + + // Support: IE9-11+ + // IE's :disabled selector does not pick up the children of disabled fieldsets + docElem.appendChild( el ).disabled = true; + if ( el.querySelectorAll(":disabled").length !== 2 ) { + rbuggyQSA.push( ":enabled", ":disabled" ); + } + + // Opera 10-11 does not throw on post-comma invalid pseudos + el.querySelectorAll("*,:x"); + rbuggyQSA.push(",.*:"); + }); + } + + if ( (support.matchesSelector = rnative.test( (matches = docElem.matches || + docElem.webkitMatchesSelector || + docElem.mozMatchesSelector || + docElem.oMatchesSelector || + docElem.msMatchesSelector) )) ) { + + assert(function( el ) { + // Check to see if it's possible to do matchesSelector + // on a disconnected node (IE 9) + support.disconnectedMatch = matches.call( el, "*" ); + + // This should fail with an exception + // Gecko does not error, returns false instead + matches.call( el, "[s!='']:x" ); + rbuggyMatches.push( "!=", pseudos ); + }); + } + + rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join("|") ); + rbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join("|") ); + + /* Contains + ---------------------------------------------------------------------- */ + hasCompare = rnative.test( docElem.compareDocumentPosition ); + + // Element contains another + // Purposefully self-exclusive + // As in, an element does not contain itself + contains = hasCompare || rnative.test( docElem.contains ) ? + function( a, b ) { + var adown = a.nodeType === 9 ? a.documentElement : a, + bup = b && b.parentNode; + return a === bup || !!( bup && bup.nodeType === 1 && ( + adown.contains ? + adown.contains( bup ) : + a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16 + )); + } : + function( a, b ) { + if ( b ) { + while ( (b = b.parentNode) ) { + if ( b === a ) { + return true; + } + } + } + return false; + }; + + /* Sorting + ---------------------------------------------------------------------- */ + + // Document order sorting + sortOrder = hasCompare ? + function( a, b ) { + + // Flag for duplicate removal + if ( a === b ) { + hasDuplicate = true; + return 0; + } + + // Sort on method existence if only one input has compareDocumentPosition + var compare = !a.compareDocumentPosition - !b.compareDocumentPosition; + if ( compare ) { + return compare; + } + + // Calculate position if both inputs belong to the same document + compare = ( a.ownerDocument || a ) === ( b.ownerDocument || b ) ? + a.compareDocumentPosition( b ) : + + // Otherwise we know they are disconnected + 1; + + // Disconnected nodes + if ( compare & 1 || + (!support.sortDetached && b.compareDocumentPosition( a ) === compare) ) { + + // Choose the first element that is related to our preferred document + if ( a === document || a.ownerDocument === preferredDoc && contains(preferredDoc, a) ) { + return -1; + } + if ( b === document || b.ownerDocument === preferredDoc && contains(preferredDoc, b) ) { + return 1; + } + + // Maintain original order + return sortInput ? + ( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) : + 0; + } + + return compare & 4 ? -1 : 1; + } : + function( a, b ) { + // Exit early if the nodes are identical + if ( a === b ) { + hasDuplicate = true; + return 0; + } + + var cur, + i = 0, + aup = a.parentNode, + bup = b.parentNode, + ap = [ a ], + bp = [ b ]; + + // Parentless nodes are either documents or disconnected + if ( !aup || !bup ) { + return a === document ? -1 : + b === document ? 1 : + aup ? -1 : + bup ? 1 : + sortInput ? + ( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) : + 0; + + // If the nodes are siblings, we can do a quick check + } else if ( aup === bup ) { + return siblingCheck( a, b ); + } + + // Otherwise we need full lists of their ancestors for comparison + cur = a; + while ( (cur = cur.parentNode) ) { + ap.unshift( cur ); + } + cur = b; + while ( (cur = cur.parentNode) ) { + bp.unshift( cur ); + } + + // Walk down the tree looking for a discrepancy + while ( ap[i] === bp[i] ) { + i++; + } + + return i ? + // Do a sibling check if the nodes have a common ancestor + siblingCheck( ap[i], bp[i] ) : + + // Otherwise nodes in our document sort first + ap[i] === preferredDoc ? -1 : + bp[i] === preferredDoc ? 1 : + 0; + }; + + return document; +}; + +Sizzle.matches = function( expr, elements ) { + return Sizzle( expr, null, null, elements ); +}; + +Sizzle.matchesSelector = function( elem, expr ) { + // Set document vars if needed + if ( ( elem.ownerDocument || elem ) !== document ) { + setDocument( elem ); + } + + // Make sure that attribute selectors are quoted + expr = expr.replace( rattributeQuotes, "='$1']" ); + + if ( support.matchesSelector && documentIsHTML && + !compilerCache[ expr + " " ] && + ( !rbuggyMatches || !rbuggyMatches.test( expr ) ) && + ( !rbuggyQSA || !rbuggyQSA.test( expr ) ) ) { + + try { + var ret = matches.call( elem, expr ); + + // IE 9's matchesSelector returns false on disconnected nodes + if ( ret || support.disconnectedMatch || + // As well, disconnected nodes are said to be in a document + // fragment in IE 9 + elem.document && elem.document.nodeType !== 11 ) { + return ret; + } + } catch (e) {} + } + + return Sizzle( expr, document, null, [ elem ] ).length > 0; +}; + +Sizzle.contains = function( context, elem ) { + // Set document vars if needed + if ( ( context.ownerDocument || context ) !== document ) { + setDocument( context ); + } + return contains( context, elem ); +}; + +Sizzle.attr = function( elem, name ) { + // Set document vars if needed + if ( ( elem.ownerDocument || elem ) !== document ) { + setDocument( elem ); + } + + var fn = Expr.attrHandle[ name.toLowerCase() ], + // Don't get fooled by Object.prototype properties (jQuery #13807) + val = fn && hasOwn.call( Expr.attrHandle, name.toLowerCase() ) ? + fn( elem, name, !documentIsHTML ) : + undefined; + + return val !== undefined ? + val : + support.attributes || !documentIsHTML ? + elem.getAttribute( name ) : + (val = elem.getAttributeNode(name)) && val.specified ? + val.value : + null; +}; + +Sizzle.escape = function( sel ) { + return (sel + "").replace( rcssescape, fcssescape ); +}; + +Sizzle.error = function( msg ) { + throw new Error( "Syntax error, unrecognized expression: " + msg ); +}; + +/** + * Document sorting and removing duplicates + * @param {ArrayLike} results + */ +Sizzle.uniqueSort = function( results ) { + var elem, + duplicates = [], + j = 0, + i = 0; + + // Unless we *know* we can detect duplicates, assume their presence + hasDuplicate = !support.detectDuplicates; + sortInput = !support.sortStable && results.slice( 0 ); + results.sort( sortOrder ); + + if ( hasDuplicate ) { + while ( (elem = results[i++]) ) { + if ( elem === results[ i ] ) { + j = duplicates.push( i ); + } + } + while ( j-- ) { + results.splice( duplicates[ j ], 1 ); + } + } + + // Clear input after sorting to release objects + // See https://github.com/jquery/sizzle/pull/225 + sortInput = null; + + return results; +}; + +/** + * Utility function for retrieving the text value of an array of DOM nodes + * @param {Array|Element} elem + */ +getText = Sizzle.getText = function( elem ) { + var node, + ret = "", + i = 0, + nodeType = elem.nodeType; + + if ( !nodeType ) { + // If no nodeType, this is expected to be an array + while ( (node = elem[i++]) ) { + // Do not traverse comment nodes + ret += getText( node ); + } + } else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) { + // Use textContent for elements + // innerText usage removed for consistency of new lines (jQuery #11153) + if ( typeof elem.textContent === "string" ) { + return elem.textContent; + } else { + // Traverse its children + for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { + ret += getText( elem ); + } + } + } else if ( nodeType === 3 || nodeType === 4 ) { + return elem.nodeValue; + } + // Do not include comment or processing instruction nodes + + return ret; +}; + +Expr = Sizzle.selectors = { + + // Can be adjusted by the user + cacheLength: 50, + + createPseudo: markFunction, + + match: matchExpr, + + attrHandle: {}, + + find: {}, + + relative: { + ">": { dir: "parentNode", first: true }, + " ": { dir: "parentNode" }, + "+": { dir: "previousSibling", first: true }, + "~": { dir: "previousSibling" } + }, + + preFilter: { + "ATTR": function( match ) { + match[1] = match[1].replace( runescape, funescape ); + + // Move the given value to match[3] whether quoted or unquoted + match[3] = ( match[3] || match[4] || match[5] || "" ).replace( runescape, funescape ); + + if ( match[2] === "~=" ) { + match[3] = " " + match[3] + " "; + } + + return match.slice( 0, 4 ); + }, + + "CHILD": function( match ) { + /* matches from matchExpr["CHILD"] + 1 type (only|nth|...) + 2 what (child|of-type) + 3 argument (even|odd|\d*|\d*n([+-]\d+)?|...) + 4 xn-component of xn+y argument ([+-]?\d*n|) + 5 sign of xn-component + 6 x of xn-component + 7 sign of y-component + 8 y of y-component + */ + match[1] = match[1].toLowerCase(); + + if ( match[1].slice( 0, 3 ) === "nth" ) { + // nth-* requires argument + if ( !match[3] ) { + Sizzle.error( match[0] ); + } + + // numeric x and y parameters for Expr.filter.CHILD + // remember that false/true cast respectively to 0/1 + match[4] = +( match[4] ? match[5] + (match[6] || 1) : 2 * ( match[3] === "even" || match[3] === "odd" ) ); + match[5] = +( ( match[7] + match[8] ) || match[3] === "odd" ); + + // other types prohibit arguments + } else if ( match[3] ) { + Sizzle.error( match[0] ); + } + + return match; + }, + + "PSEUDO": function( match ) { + var excess, + unquoted = !match[6] && match[2]; + + if ( matchExpr["CHILD"].test( match[0] ) ) { + return null; + } + + // Accept quoted arguments as-is + if ( match[3] ) { + match[2] = match[4] || match[5] || ""; + + // Strip excess characters from unquoted arguments + } else if ( unquoted && rpseudo.test( unquoted ) && + // Get excess from tokenize (recursively) + (excess = tokenize( unquoted, true )) && + // advance to the next closing parenthesis + (excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length) ) { + + // excess is a negative index + match[0] = match[0].slice( 0, excess ); + match[2] = unquoted.slice( 0, excess ); + } + + // Return only captures needed by the pseudo filter method (type and argument) + return match.slice( 0, 3 ); + } + }, + + filter: { + + "TAG": function( nodeNameSelector ) { + var nodeName = nodeNameSelector.replace( runescape, funescape ).toLowerCase(); + return nodeNameSelector === "*" ? + function() { return true; } : + function( elem ) { + return elem.nodeName && elem.nodeName.toLowerCase() === nodeName; + }; + }, + + "CLASS": function( className ) { + var pattern = classCache[ className + " " ]; + + return pattern || + (pattern = new RegExp( "(^|" + whitespace + ")" + className + "(" + whitespace + "|$)" )) && + classCache( className, function( elem ) { + return pattern.test( typeof elem.className === "string" && elem.className || typeof elem.getAttribute !== "undefined" && elem.getAttribute("class") || "" ); + }); + }, + + "ATTR": function( name, operator, check ) { + return function( elem ) { + var result = Sizzle.attr( elem, name ); + + if ( result == null ) { + return operator === "!="; + } + if ( !operator ) { + return true; + } + + result += ""; + + return operator === "=" ? result === check : + operator === "!=" ? result !== check : + operator === "^=" ? check && result.indexOf( check ) === 0 : + operator === "*=" ? check && result.indexOf( check ) > -1 : + operator === "$=" ? check && result.slice( -check.length ) === check : + operator === "~=" ? ( " " + result.replace( rwhitespace, " " ) + " " ).indexOf( check ) > -1 : + operator === "|=" ? result === check || result.slice( 0, check.length + 1 ) === check + "-" : + false; + }; + }, + + "CHILD": function( type, what, argument, first, last ) { + var simple = type.slice( 0, 3 ) !== "nth", + forward = type.slice( -4 ) !== "last", + ofType = what === "of-type"; + + return first === 1 && last === 0 ? + + // Shortcut for :nth-*(n) + function( elem ) { + return !!elem.parentNode; + } : + + function( elem, context, xml ) { + var cache, uniqueCache, outerCache, node, nodeIndex, start, + dir = simple !== forward ? "nextSibling" : "previousSibling", + parent = elem.parentNode, + name = ofType && elem.nodeName.toLowerCase(), + useCache = !xml && !ofType, + diff = false; + + if ( parent ) { + + // :(first|last|only)-(child|of-type) + if ( simple ) { + while ( dir ) { + node = elem; + while ( (node = node[ dir ]) ) { + if ( ofType ? + node.nodeName.toLowerCase() === name : + node.nodeType === 1 ) { + + return false; + } + } + // Reverse direction for :only-* (if we haven't yet done so) + start = dir = type === "only" && !start && "nextSibling"; + } + return true; + } + + start = [ forward ? parent.firstChild : parent.lastChild ]; + + // non-xml :nth-child(...) stores cache data on `parent` + if ( forward && useCache ) { + + // Seek `elem` from a previously-cached index + + // ...in a gzip-friendly way + node = parent; + outerCache = node[ expando ] || (node[ expando ] = {}); + + // Support: IE <9 only + // Defend against cloned attroperties (jQuery gh-1709) + uniqueCache = outerCache[ node.uniqueID ] || + (outerCache[ node.uniqueID ] = {}); + + cache = uniqueCache[ type ] || []; + nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ]; + diff = nodeIndex && cache[ 2 ]; + node = nodeIndex && parent.childNodes[ nodeIndex ]; + + while ( (node = ++nodeIndex && node && node[ dir ] || + + // Fallback to seeking `elem` from the start + (diff = nodeIndex = 0) || start.pop()) ) { + + // When found, cache indexes on `parent` and break + if ( node.nodeType === 1 && ++diff && node === elem ) { + uniqueCache[ type ] = [ dirruns, nodeIndex, diff ]; + break; + } + } + + } else { + // Use previously-cached element index if available + if ( useCache ) { + // ...in a gzip-friendly way + node = elem; + outerCache = node[ expando ] || (node[ expando ] = {}); + + // Support: IE <9 only + // Defend against cloned attroperties (jQuery gh-1709) + uniqueCache = outerCache[ node.uniqueID ] || + (outerCache[ node.uniqueID ] = {}); + + cache = uniqueCache[ type ] || []; + nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ]; + diff = nodeIndex; + } + + // xml :nth-child(...) + // or :nth-last-child(...) or :nth(-last)?-of-type(...) + if ( diff === false ) { + // Use the same loop as above to seek `elem` from the start + while ( (node = ++nodeIndex && node && node[ dir ] || + (diff = nodeIndex = 0) || start.pop()) ) { + + if ( ( ofType ? + node.nodeName.toLowerCase() === name : + node.nodeType === 1 ) && + ++diff ) { + + // Cache the index of each encountered element + if ( useCache ) { + outerCache = node[ expando ] || (node[ expando ] = {}); + + // Support: IE <9 only + // Defend against cloned attroperties (jQuery gh-1709) + uniqueCache = outerCache[ node.uniqueID ] || + (outerCache[ node.uniqueID ] = {}); + + uniqueCache[ type ] = [ dirruns, diff ]; + } + + if ( node === elem ) { + break; + } + } + } + } + } + + // Incorporate the offset, then check against cycle size + diff -= last; + return diff === first || ( diff % first === 0 && diff / first >= 0 ); + } + }; + }, + + "PSEUDO": function( pseudo, argument ) { + // pseudo-class names are case-insensitive + // http://www.w3.org/TR/selectors/#pseudo-classes + // Prioritize by case sensitivity in case custom pseudos are added with uppercase letters + // Remember that setFilters inherits from pseudos + var args, + fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] || + Sizzle.error( "unsupported pseudo: " + pseudo ); + + // The user may use createPseudo to indicate that + // arguments are needed to create the filter function + // just as Sizzle does + if ( fn[ expando ] ) { + return fn( argument ); + } + + // But maintain support for old signatures + if ( fn.length > 1 ) { + args = [ pseudo, pseudo, "", argument ]; + return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ? + markFunction(function( seed, matches ) { + var idx, + matched = fn( seed, argument ), + i = matched.length; + while ( i-- ) { + idx = indexOf( seed, matched[i] ); + seed[ idx ] = !( matches[ idx ] = matched[i] ); + } + }) : + function( elem ) { + return fn( elem, 0, args ); + }; + } + + return fn; + } + }, + + pseudos: { + // Potentially complex pseudos + "not": markFunction(function( selector ) { + // Trim the selector passed to compile + // to avoid treating leading and trailing + // spaces as combinators + var input = [], + results = [], + matcher = compile( selector.replace( rtrim, "$1" ) ); + + return matcher[ expando ] ? + markFunction(function( seed, matches, context, xml ) { + var elem, + unmatched = matcher( seed, null, xml, [] ), + i = seed.length; + + // Match elements unmatched by `matcher` + while ( i-- ) { + if ( (elem = unmatched[i]) ) { + seed[i] = !(matches[i] = elem); + } + } + }) : + function( elem, context, xml ) { + input[0] = elem; + matcher( input, null, xml, results ); + // Don't keep the element (issue #299) + input[0] = null; + return !results.pop(); + }; + }), + + "has": markFunction(function( selector ) { + return function( elem ) { + return Sizzle( selector, elem ).length > 0; + }; + }), + + "contains": markFunction(function( text ) { + text = text.replace( runescape, funescape ); + return function( elem ) { + return ( elem.textContent || elem.innerText || getText( elem ) ).indexOf( text ) > -1; + }; + }), + + // "Whether an element is represented by a :lang() selector + // is based solely on the element's language value + // being equal to the identifier C, + // or beginning with the identifier C immediately followed by "-". + // The matching of C against the element's language value is performed case-insensitively. + // The identifier C does not have to be a valid language name." + // http://www.w3.org/TR/selectors/#lang-pseudo + "lang": markFunction( function( lang ) { + // lang value must be a valid identifier + if ( !ridentifier.test(lang || "") ) { + Sizzle.error( "unsupported lang: " + lang ); + } + lang = lang.replace( runescape, funescape ).toLowerCase(); + return function( elem ) { + var elemLang; + do { + if ( (elemLang = documentIsHTML ? + elem.lang : + elem.getAttribute("xml:lang") || elem.getAttribute("lang")) ) { + + elemLang = elemLang.toLowerCase(); + return elemLang === lang || elemLang.indexOf( lang + "-" ) === 0; + } + } while ( (elem = elem.parentNode) && elem.nodeType === 1 ); + return false; + }; + }), + + // Miscellaneous + "target": function( elem ) { + var hash = window.location && window.location.hash; + return hash && hash.slice( 1 ) === elem.id; + }, + + "root": function( elem ) { + return elem === docElem; + }, + + "focus": function( elem ) { + return elem === document.activeElement && (!document.hasFocus || document.hasFocus()) && !!(elem.type || elem.href || ~elem.tabIndex); + }, + + // Boolean properties + "enabled": createDisabledPseudo( false ), + "disabled": createDisabledPseudo( true ), + + "checked": function( elem ) { + // In CSS3, :checked should return both checked and selected elements + // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked + var nodeName = elem.nodeName.toLowerCase(); + return (nodeName === "input" && !!elem.checked) || (nodeName === "option" && !!elem.selected); + }, + + "selected": function( elem ) { + // Accessing this property makes selected-by-default + // options in Safari work properly + if ( elem.parentNode ) { + elem.parentNode.selectedIndex; + } + + return elem.selected === true; + }, + + // Contents + "empty": function( elem ) { + // http://www.w3.org/TR/selectors/#empty-pseudo + // :empty is negated by element (1) or content nodes (text: 3; cdata: 4; entity ref: 5), + // but not by others (comment: 8; processing instruction: 7; etc.) + // nodeType < 6 works because attributes (2) do not appear as children + for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { + if ( elem.nodeType < 6 ) { + return false; + } + } + return true; + }, + + "parent": function( elem ) { + return !Expr.pseudos["empty"]( elem ); + }, + + // Element/input types + "header": function( elem ) { + return rheader.test( elem.nodeName ); + }, + + "input": function( elem ) { + return rinputs.test( elem.nodeName ); + }, + + "button": function( elem ) { + var name = elem.nodeName.toLowerCase(); + return name === "input" && elem.type === "button" || name === "button"; + }, + + "text": function( elem ) { + var attr; + return elem.nodeName.toLowerCase() === "input" && + elem.type === "text" && + + // Support: IE<8 + // New HTML5 attribute values (e.g., "search") appear with elem.type === "text" + ( (attr = elem.getAttribute("type")) == null || attr.toLowerCase() === "text" ); + }, + + // Position-in-collection + "first": createPositionalPseudo(function() { + return [ 0 ]; + }), + + "last": createPositionalPseudo(function( matchIndexes, length ) { + return [ length - 1 ]; + }), + + "eq": createPositionalPseudo(function( matchIndexes, length, argument ) { + return [ argument < 0 ? argument + length : argument ]; + }), + + "even": createPositionalPseudo(function( matchIndexes, length ) { + var i = 0; + for ( ; i < length; i += 2 ) { + matchIndexes.push( i ); + } + return matchIndexes; + }), + + "odd": createPositionalPseudo(function( matchIndexes, length ) { + var i = 1; + for ( ; i < length; i += 2 ) { + matchIndexes.push( i ); + } + return matchIndexes; + }), + + "lt": createPositionalPseudo(function( matchIndexes, length, argument ) { + var i = argument < 0 ? argument + length : argument; + for ( ; --i >= 0; ) { + matchIndexes.push( i ); + } + return matchIndexes; + }), + + "gt": createPositionalPseudo(function( matchIndexes, length, argument ) { + var i = argument < 0 ? argument + length : argument; + for ( ; ++i < length; ) { + matchIndexes.push( i ); + } + return matchIndexes; + }) + } +}; + +Expr.pseudos["nth"] = Expr.pseudos["eq"]; + +// Add button/input type pseudos +for ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) { + Expr.pseudos[ i ] = createInputPseudo( i ); +} +for ( i in { submit: true, reset: true } ) { + Expr.pseudos[ i ] = createButtonPseudo( i ); +} + +// Easy API for creating new setFilters +function setFilters() {} +setFilters.prototype = Expr.filters = Expr.pseudos; +Expr.setFilters = new setFilters(); + +tokenize = Sizzle.tokenize = function( selector, parseOnly ) { + var matched, match, tokens, type, + soFar, groups, preFilters, + cached = tokenCache[ selector + " " ]; + + if ( cached ) { + return parseOnly ? 0 : cached.slice( 0 ); + } + + soFar = selector; + groups = []; + preFilters = Expr.preFilter; + + while ( soFar ) { + + // Comma and first run + if ( !matched || (match = rcomma.exec( soFar )) ) { + if ( match ) { + // Don't consume trailing commas as valid + soFar = soFar.slice( match[0].length ) || soFar; + } + groups.push( (tokens = []) ); + } + + matched = false; + + // Combinators + if ( (match = rcombinators.exec( soFar )) ) { + matched = match.shift(); + tokens.push({ + value: matched, + // Cast descendant combinators to space + type: match[0].replace( rtrim, " " ) + }); + soFar = soFar.slice( matched.length ); + } + + // Filters + for ( type in Expr.filter ) { + if ( (match = matchExpr[ type ].exec( soFar )) && (!preFilters[ type ] || + (match = preFilters[ type ]( match ))) ) { + matched = match.shift(); + tokens.push({ + value: matched, + type: type, + matches: match + }); + soFar = soFar.slice( matched.length ); + } + } + + if ( !matched ) { + break; + } + } + + // Return the length of the invalid excess + // if we're just parsing + // Otherwise, throw an error or return tokens + return parseOnly ? + soFar.length : + soFar ? + Sizzle.error( selector ) : + // Cache the tokens + tokenCache( selector, groups ).slice( 0 ); +}; + +function toSelector( tokens ) { + var i = 0, + len = tokens.length, + selector = ""; + for ( ; i < len; i++ ) { + selector += tokens[i].value; + } + return selector; +} + +function addCombinator( matcher, combinator, base ) { + var dir = combinator.dir, + skip = combinator.next, + key = skip || dir, + checkNonElements = base && key === "parentNode", + doneName = done++; + + return combinator.first ? + // Check against closest ancestor/preceding element + function( elem, context, xml ) { + while ( (elem = elem[ dir ]) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + return matcher( elem, context, xml ); + } + } + return false; + } : + + // Check against all ancestor/preceding elements + function( elem, context, xml ) { + var oldCache, uniqueCache, outerCache, + newCache = [ dirruns, doneName ]; + + // We can't set arbitrary data on XML nodes, so they don't benefit from combinator caching + if ( xml ) { + while ( (elem = elem[ dir ]) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + if ( matcher( elem, context, xml ) ) { + return true; + } + } + } + } else { + while ( (elem = elem[ dir ]) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + outerCache = elem[ expando ] || (elem[ expando ] = {}); + + // Support: IE <9 only + // Defend against cloned attroperties (jQuery gh-1709) + uniqueCache = outerCache[ elem.uniqueID ] || (outerCache[ elem.uniqueID ] = {}); + + if ( skip && skip === elem.nodeName.toLowerCase() ) { + elem = elem[ dir ] || elem; + } else if ( (oldCache = uniqueCache[ key ]) && + oldCache[ 0 ] === dirruns && oldCache[ 1 ] === doneName ) { + + // Assign to newCache so results back-propagate to previous elements + return (newCache[ 2 ] = oldCache[ 2 ]); + } else { + // Reuse newcache so results back-propagate to previous elements + uniqueCache[ key ] = newCache; + + // A match means we're done; a fail means we have to keep checking + if ( (newCache[ 2 ] = matcher( elem, context, xml )) ) { + return true; + } + } + } + } + } + return false; + }; +} + +function elementMatcher( matchers ) { + return matchers.length > 1 ? + function( elem, context, xml ) { + var i = matchers.length; + while ( i-- ) { + if ( !matchers[i]( elem, context, xml ) ) { + return false; + } + } + return true; + } : + matchers[0]; +} + +function multipleContexts( selector, contexts, results ) { + var i = 0, + len = contexts.length; + for ( ; i < len; i++ ) { + Sizzle( selector, contexts[i], results ); + } + return results; +} + +function condense( unmatched, map, filter, context, xml ) { + var elem, + newUnmatched = [], + i = 0, + len = unmatched.length, + mapped = map != null; + + for ( ; i < len; i++ ) { + if ( (elem = unmatched[i]) ) { + if ( !filter || filter( elem, context, xml ) ) { + newUnmatched.push( elem ); + if ( mapped ) { + map.push( i ); + } + } + } + } + + return newUnmatched; +} + +function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) { + if ( postFilter && !postFilter[ expando ] ) { + postFilter = setMatcher( postFilter ); + } + if ( postFinder && !postFinder[ expando ] ) { + postFinder = setMatcher( postFinder, postSelector ); + } + return markFunction(function( seed, results, context, xml ) { + var temp, i, elem, + preMap = [], + postMap = [], + preexisting = results.length, + + // Get initial elements from seed or context + elems = seed || multipleContexts( selector || "*", context.nodeType ? [ context ] : context, [] ), + + // Prefilter to get matcher input, preserving a map for seed-results synchronization + matcherIn = preFilter && ( seed || !selector ) ? + condense( elems, preMap, preFilter, context, xml ) : + elems, + + matcherOut = matcher ? + // If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results, + postFinder || ( seed ? preFilter : preexisting || postFilter ) ? + + // ...intermediate processing is necessary + [] : + + // ...otherwise use results directly + results : + matcherIn; + + // Find primary matches + if ( matcher ) { + matcher( matcherIn, matcherOut, context, xml ); + } + + // Apply postFilter + if ( postFilter ) { + temp = condense( matcherOut, postMap ); + postFilter( temp, [], context, xml ); + + // Un-match failing elements by moving them back to matcherIn + i = temp.length; + while ( i-- ) { + if ( (elem = temp[i]) ) { + matcherOut[ postMap[i] ] = !(matcherIn[ postMap[i] ] = elem); + } + } + } + + if ( seed ) { + if ( postFinder || preFilter ) { + if ( postFinder ) { + // Get the final matcherOut by condensing this intermediate into postFinder contexts + temp = []; + i = matcherOut.length; + while ( i-- ) { + if ( (elem = matcherOut[i]) ) { + // Restore matcherIn since elem is not yet a final match + temp.push( (matcherIn[i] = elem) ); + } + } + postFinder( null, (matcherOut = []), temp, xml ); + } + + // Move matched elements from seed to results to keep them synchronized + i = matcherOut.length; + while ( i-- ) { + if ( (elem = matcherOut[i]) && + (temp = postFinder ? indexOf( seed, elem ) : preMap[i]) > -1 ) { + + seed[temp] = !(results[temp] = elem); + } + } + } + + // Add elements to results, through postFinder if defined + } else { + matcherOut = condense( + matcherOut === results ? + matcherOut.splice( preexisting, matcherOut.length ) : + matcherOut + ); + if ( postFinder ) { + postFinder( null, results, matcherOut, xml ); + } else { + push.apply( results, matcherOut ); + } + } + }); +} + +function matcherFromTokens( tokens ) { + var checkContext, matcher, j, + len = tokens.length, + leadingRelative = Expr.relative[ tokens[0].type ], + implicitRelative = leadingRelative || Expr.relative[" "], + i = leadingRelative ? 1 : 0, + + // The foundational matcher ensures that elements are reachable from top-level context(s) + matchContext = addCombinator( function( elem ) { + return elem === checkContext; + }, implicitRelative, true ), + matchAnyContext = addCombinator( function( elem ) { + return indexOf( checkContext, elem ) > -1; + }, implicitRelative, true ), + matchers = [ function( elem, context, xml ) { + var ret = ( !leadingRelative && ( xml || context !== outermostContext ) ) || ( + (checkContext = context).nodeType ? + matchContext( elem, context, xml ) : + matchAnyContext( elem, context, xml ) ); + // Avoid hanging onto element (issue #299) + checkContext = null; + return ret; + } ]; + + for ( ; i < len; i++ ) { + if ( (matcher = Expr.relative[ tokens[i].type ]) ) { + matchers = [ addCombinator(elementMatcher( matchers ), matcher) ]; + } else { + matcher = Expr.filter[ tokens[i].type ].apply( null, tokens[i].matches ); + + // Return special upon seeing a positional matcher + if ( matcher[ expando ] ) { + // Find the next relative operator (if any) for proper handling + j = ++i; + for ( ; j < len; j++ ) { + if ( Expr.relative[ tokens[j].type ] ) { + break; + } + } + return setMatcher( + i > 1 && elementMatcher( matchers ), + i > 1 && toSelector( + // If the preceding token was a descendant combinator, insert an implicit any-element `*` + tokens.slice( 0, i - 1 ).concat({ value: tokens[ i - 2 ].type === " " ? "*" : "" }) + ).replace( rtrim, "$1" ), + matcher, + i < j && matcherFromTokens( tokens.slice( i, j ) ), + j < len && matcherFromTokens( (tokens = tokens.slice( j )) ), + j < len && toSelector( tokens ) + ); + } + matchers.push( matcher ); + } + } + + return elementMatcher( matchers ); +} + +function matcherFromGroupMatchers( elementMatchers, setMatchers ) { + var bySet = setMatchers.length > 0, + byElement = elementMatchers.length > 0, + superMatcher = function( seed, context, xml, results, outermost ) { + var elem, j, matcher, + matchedCount = 0, + i = "0", + unmatched = seed && [], + setMatched = [], + contextBackup = outermostContext, + // We must always have either seed elements or outermost context + elems = seed || byElement && Expr.find["TAG"]( "*", outermost ), + // Use integer dirruns iff this is the outermost matcher + dirrunsUnique = (dirruns += contextBackup == null ? 1 : Math.random() || 0.1), + len = elems.length; + + if ( outermost ) { + outermostContext = context === document || context || outermost; + } + + // Add elements passing elementMatchers directly to results + // Support: IE<9, Safari + // Tolerate NodeList properties (IE: "length"; Safari: ) matching elements by id + for ( ; i !== len && (elem = elems[i]) != null; i++ ) { + if ( byElement && elem ) { + j = 0; + if ( !context && elem.ownerDocument !== document ) { + setDocument( elem ); + xml = !documentIsHTML; + } + while ( (matcher = elementMatchers[j++]) ) { + if ( matcher( elem, context || document, xml) ) { + results.push( elem ); + break; + } + } + if ( outermost ) { + dirruns = dirrunsUnique; + } + } + + // Track unmatched elements for set filters + if ( bySet ) { + // They will have gone through all possible matchers + if ( (elem = !matcher && elem) ) { + matchedCount--; + } + + // Lengthen the array for every element, matched or not + if ( seed ) { + unmatched.push( elem ); + } + } + } + + // `i` is now the count of elements visited above, and adding it to `matchedCount` + // makes the latter nonnegative. + matchedCount += i; + + // Apply set filters to unmatched elements + // NOTE: This can be skipped if there are no unmatched elements (i.e., `matchedCount` + // equals `i`), unless we didn't visit _any_ elements in the above loop because we have + // no element matchers and no seed. + // Incrementing an initially-string "0" `i` allows `i` to remain a string only in that + // case, which will result in a "00" `matchedCount` that differs from `i` but is also + // numerically zero. + if ( bySet && i !== matchedCount ) { + j = 0; + while ( (matcher = setMatchers[j++]) ) { + matcher( unmatched, setMatched, context, xml ); + } + + if ( seed ) { + // Reintegrate element matches to eliminate the need for sorting + if ( matchedCount > 0 ) { + while ( i-- ) { + if ( !(unmatched[i] || setMatched[i]) ) { + setMatched[i] = pop.call( results ); + } + } + } + + // Discard index placeholder values to get only actual matches + setMatched = condense( setMatched ); + } + + // Add matches to results + push.apply( results, setMatched ); + + // Seedless set matches succeeding multiple successful matchers stipulate sorting + if ( outermost && !seed && setMatched.length > 0 && + ( matchedCount + setMatchers.length ) > 1 ) { + + Sizzle.uniqueSort( results ); + } + } + + // Override manipulation of globals by nested matchers + if ( outermost ) { + dirruns = dirrunsUnique; + outermostContext = contextBackup; + } + + return unmatched; + }; + + return bySet ? + markFunction( superMatcher ) : + superMatcher; +} + +compile = Sizzle.compile = function( selector, match /* Internal Use Only */ ) { + var i, + setMatchers = [], + elementMatchers = [], + cached = compilerCache[ selector + " " ]; + + if ( !cached ) { + // Generate a function of recursive functions that can be used to check each element + if ( !match ) { + match = tokenize( selector ); + } + i = match.length; + while ( i-- ) { + cached = matcherFromTokens( match[i] ); + if ( cached[ expando ] ) { + setMatchers.push( cached ); + } else { + elementMatchers.push( cached ); + } + } + + // Cache the compiled function + cached = compilerCache( selector, matcherFromGroupMatchers( elementMatchers, setMatchers ) ); + + // Save selector and tokenization + cached.selector = selector; + } + return cached; +}; + +/** + * A low-level selection function that works with Sizzle's compiled + * selector functions + * @param {String|Function} selector A selector or a pre-compiled + * selector function built with Sizzle.compile + * @param {Element} context + * @param {Array} [results] + * @param {Array} [seed] A set of elements to match against + */ +select = Sizzle.select = function( selector, context, results, seed ) { + var i, tokens, token, type, find, + compiled = typeof selector === "function" && selector, + match = !seed && tokenize( (selector = compiled.selector || selector) ); + + results = results || []; + + // Try to minimize operations if there is only one selector in the list and no seed + // (the latter of which guarantees us context) + if ( match.length === 1 ) { + + // Reduce context if the leading compound selector is an ID + tokens = match[0] = match[0].slice( 0 ); + if ( tokens.length > 2 && (token = tokens[0]).type === "ID" && + context.nodeType === 9 && documentIsHTML && Expr.relative[ tokens[1].type ] ) { + + context = ( Expr.find["ID"]( token.matches[0].replace(runescape, funescape), context ) || [] )[0]; + if ( !context ) { + return results; + + // Precompiled matchers will still verify ancestry, so step up a level + } else if ( compiled ) { + context = context.parentNode; + } + + selector = selector.slice( tokens.shift().value.length ); + } + + // Fetch a seed set for right-to-left matching + i = matchExpr["needsContext"].test( selector ) ? 0 : tokens.length; + while ( i-- ) { + token = tokens[i]; + + // Abort if we hit a combinator + if ( Expr.relative[ (type = token.type) ] ) { + break; + } + if ( (find = Expr.find[ type ]) ) { + // Search, expanding context for leading sibling combinators + if ( (seed = find( + token.matches[0].replace( runescape, funescape ), + rsibling.test( tokens[0].type ) && testContext( context.parentNode ) || context + )) ) { + + // If seed is empty or no tokens remain, we can return early + tokens.splice( i, 1 ); + selector = seed.length && toSelector( tokens ); + if ( !selector ) { + push.apply( results, seed ); + return results; + } + + break; + } + } + } + } + + // Compile and execute a filtering function if one is not provided + // Provide `match` to avoid retokenization if we modified the selector above + ( compiled || compile( selector, match ) )( + seed, + context, + !documentIsHTML, + results, + !context || rsibling.test( selector ) && testContext( context.parentNode ) || context + ); + return results; +}; + +// One-time assignments + +// Sort stability +support.sortStable = expando.split("").sort( sortOrder ).join("") === expando; + +// Support: Chrome 14-35+ +// Always assume duplicates if they aren't passed to the comparison function +support.detectDuplicates = !!hasDuplicate; + +// Initialize against the default document +setDocument(); + +// Support: Webkit<537.32 - Safari 6.0.3/Chrome 25 (fixed in Chrome 27) +// Detached nodes confoundingly follow *each other* +support.sortDetached = assert(function( el ) { + // Should return 1, but returns 4 (following) + return el.compareDocumentPosition( document.createElement("fieldset") ) & 1; +}); + +// Support: IE<8 +// Prevent attribute/property "interpolation" +// https://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx +if ( !assert(function( el ) { + el.innerHTML = ""; + return el.firstChild.getAttribute("href") === "#" ; +}) ) { + addHandle( "type|href|height|width", function( elem, name, isXML ) { + if ( !isXML ) { + return elem.getAttribute( name, name.toLowerCase() === "type" ? 1 : 2 ); + } + }); +} + +// Support: IE<9 +// Use defaultValue in place of getAttribute("value") +if ( !support.attributes || !assert(function( el ) { + el.innerHTML = ""; + el.firstChild.setAttribute( "value", "" ); + return el.firstChild.getAttribute( "value" ) === ""; +}) ) { + addHandle( "value", function( elem, name, isXML ) { + if ( !isXML && elem.nodeName.toLowerCase() === "input" ) { + return elem.defaultValue; + } + }); +} + +// Support: IE<9 +// Use getAttributeNode to fetch booleans when getAttribute lies +if ( !assert(function( el ) { + return el.getAttribute("disabled") == null; +}) ) { + addHandle( booleans, function( elem, name, isXML ) { + var val; + if ( !isXML ) { + return elem[ name ] === true ? name.toLowerCase() : + (val = elem.getAttributeNode( name )) && val.specified ? + val.value : + null; + } + }); +} + +return Sizzle; + +})( window ); + + + +jQuery.find = Sizzle; +jQuery.expr = Sizzle.selectors; + +// Deprecated +jQuery.expr[ ":" ] = jQuery.expr.pseudos; +jQuery.uniqueSort = jQuery.unique = Sizzle.uniqueSort; +jQuery.text = Sizzle.getText; +jQuery.isXMLDoc = Sizzle.isXML; +jQuery.contains = Sizzle.contains; +jQuery.escapeSelector = Sizzle.escape; + + + + +var dir = function( elem, dir, until ) { + var matched = [], + truncate = until !== undefined; + + while ( ( elem = elem[ dir ] ) && elem.nodeType !== 9 ) { + if ( elem.nodeType === 1 ) { + if ( truncate && jQuery( elem ).is( until ) ) { + break; + } + matched.push( elem ); + } + } + return matched; +}; + + +var siblings = function( n, elem ) { + var matched = []; + + for ( ; n; n = n.nextSibling ) { + if ( n.nodeType === 1 && n !== elem ) { + matched.push( n ); + } + } + + return matched; +}; + + +var rneedsContext = jQuery.expr.match.needsContext; + +var rsingleTag = ( /^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i ); + + + +var risSimple = /^.[^:#\[\.,]*$/; + +// Implement the identical functionality for filter and not +function winnow( elements, qualifier, not ) { + if ( jQuery.isFunction( qualifier ) ) { + return jQuery.grep( elements, function( elem, i ) { + return !!qualifier.call( elem, i, elem ) !== not; + } ); + } + + // Single element + if ( qualifier.nodeType ) { + return jQuery.grep( elements, function( elem ) { + return ( elem === qualifier ) !== not; + } ); + } + + // Arraylike of elements (jQuery, arguments, Array) + if ( typeof qualifier !== "string" ) { + return jQuery.grep( elements, function( elem ) { + return ( indexOf.call( qualifier, elem ) > -1 ) !== not; + } ); + } + + // Simple selector that can be filtered directly, removing non-Elements + if ( risSimple.test( qualifier ) ) { + return jQuery.filter( qualifier, elements, not ); + } + + // Complex selector, compare the two sets, removing non-Elements + qualifier = jQuery.filter( qualifier, elements ); + return jQuery.grep( elements, function( elem ) { + return ( indexOf.call( qualifier, elem ) > -1 ) !== not && elem.nodeType === 1; + } ); +} + +jQuery.filter = function( expr, elems, not ) { + var elem = elems[ 0 ]; + + if ( not ) { + expr = ":not(" + expr + ")"; + } + + if ( elems.length === 1 && elem.nodeType === 1 ) { + return jQuery.find.matchesSelector( elem, expr ) ? [ elem ] : []; + } + + return jQuery.find.matches( expr, jQuery.grep( elems, function( elem ) { + return elem.nodeType === 1; + } ) ); +}; + +jQuery.fn.extend( { + find: function( selector ) { + var i, ret, + len = this.length, + self = this; + + if ( typeof selector !== "string" ) { + return this.pushStack( jQuery( selector ).filter( function() { + for ( i = 0; i < len; i++ ) { + if ( jQuery.contains( self[ i ], this ) ) { + return true; + } + } + } ) ); + } + + ret = this.pushStack( [] ); + + for ( i = 0; i < len; i++ ) { + jQuery.find( selector, self[ i ], ret ); + } + + return len > 1 ? jQuery.uniqueSort( ret ) : ret; + }, + filter: function( selector ) { + return this.pushStack( winnow( this, selector || [], false ) ); + }, + not: function( selector ) { + return this.pushStack( winnow( this, selector || [], true ) ); + }, + is: function( selector ) { + return !!winnow( + this, + + // If this is a positional/relative selector, check membership in the returned set + // so $("p:first").is("p:last") won't return true for a doc with two "p". + typeof selector === "string" && rneedsContext.test( selector ) ? + jQuery( selector ) : + selector || [], + false + ).length; + } +} ); + + +// Initialize a jQuery object + + +// A central reference to the root jQuery(document) +var rootjQuery, + + // A simple way to check for HTML strings + // Prioritize #id over to avoid XSS via location.hash (#9521) + // Strict HTML recognition (#11290: must start with <) + // Shortcut simple #id case for speed + rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/, + + init = jQuery.fn.init = function( selector, context, root ) { + var match, elem; + + // HANDLE: $(""), $(null), $(undefined), $(false) + if ( !selector ) { + return this; + } + + // Method init() accepts an alternate rootjQuery + // so migrate can support jQuery.sub (gh-2101) + root = root || rootjQuery; + + // Handle HTML strings + if ( typeof selector === "string" ) { + if ( selector[ 0 ] === "<" && + selector[ selector.length - 1 ] === ">" && + selector.length >= 3 ) { + + // Assume that strings that start and end with <> are HTML and skip the regex check + match = [ null, selector, null ]; + + } else { + match = rquickExpr.exec( selector ); + } + + // Match html or make sure no context is specified for #id + if ( match && ( match[ 1 ] || !context ) ) { + + // HANDLE: $(html) -> $(array) + if ( match[ 1 ] ) { + context = context instanceof jQuery ? context[ 0 ] : context; + + // Option to run scripts is true for back-compat + // Intentionally let the error be thrown if parseHTML is not present + jQuery.merge( this, jQuery.parseHTML( + match[ 1 ], + context && context.nodeType ? context.ownerDocument || context : document, + true + ) ); + + // HANDLE: $(html, props) + if ( rsingleTag.test( match[ 1 ] ) && jQuery.isPlainObject( context ) ) { + for ( match in context ) { + + // Properties of context are called as methods if possible + if ( jQuery.isFunction( this[ match ] ) ) { + this[ match ]( context[ match ] ); + + // ...and otherwise set as attributes + } else { + this.attr( match, context[ match ] ); + } + } + } + + return this; + + // HANDLE: $(#id) + } else { + elem = document.getElementById( match[ 2 ] ); + + if ( elem ) { + + // Inject the element directly into the jQuery object + this[ 0 ] = elem; + this.length = 1; + } + return this; + } + + // HANDLE: $(expr, $(...)) + } else if ( !context || context.jquery ) { + return ( context || root ).find( selector ); + + // HANDLE: $(expr, context) + // (which is just equivalent to: $(context).find(expr) + } else { + return this.constructor( context ).find( selector ); + } + + // HANDLE: $(DOMElement) + } else if ( selector.nodeType ) { + this[ 0 ] = selector; + this.length = 1; + return this; + + // HANDLE: $(function) + // Shortcut for document ready + } else if ( jQuery.isFunction( selector ) ) { + return root.ready !== undefined ? + root.ready( selector ) : + + // Execute immediately if ready is not present + selector( jQuery ); + } + + return jQuery.makeArray( selector, this ); + }; + +// Give the init function the jQuery prototype for later instantiation +init.prototype = jQuery.fn; + +// Initialize central reference +rootjQuery = jQuery( document ); + + +var rparentsprev = /^(?:parents|prev(?:Until|All))/, + + // Methods guaranteed to produce a unique set when starting from a unique set + guaranteedUnique = { + children: true, + contents: true, + next: true, + prev: true + }; + +jQuery.fn.extend( { + has: function( target ) { + var targets = jQuery( target, this ), + l = targets.length; + + return this.filter( function() { + var i = 0; + for ( ; i < l; i++ ) { + if ( jQuery.contains( this, targets[ i ] ) ) { + return true; + } + } + } ); + }, + + closest: function( selectors, context ) { + var cur, + i = 0, + l = this.length, + matched = [], + targets = typeof selectors !== "string" && jQuery( selectors ); + + // Positional selectors never match, since there's no _selection_ context + if ( !rneedsContext.test( selectors ) ) { + for ( ; i < l; i++ ) { + for ( cur = this[ i ]; cur && cur !== context; cur = cur.parentNode ) { + + // Always skip document fragments + if ( cur.nodeType < 11 && ( targets ? + targets.index( cur ) > -1 : + + // Don't pass non-elements to Sizzle + cur.nodeType === 1 && + jQuery.find.matchesSelector( cur, selectors ) ) ) { + + matched.push( cur ); + break; + } + } + } + } + + return this.pushStack( matched.length > 1 ? jQuery.uniqueSort( matched ) : matched ); + }, + + // Determine the position of an element within the set + index: function( elem ) { + + // No argument, return index in parent + if ( !elem ) { + return ( this[ 0 ] && this[ 0 ].parentNode ) ? this.first().prevAll().length : -1; + } + + // Index in selector + if ( typeof elem === "string" ) { + return indexOf.call( jQuery( elem ), this[ 0 ] ); + } + + // Locate the position of the desired element + return indexOf.call( this, + + // If it receives a jQuery object, the first element is used + elem.jquery ? elem[ 0 ] : elem + ); + }, + + add: function( selector, context ) { + return this.pushStack( + jQuery.uniqueSort( + jQuery.merge( this.get(), jQuery( selector, context ) ) + ) + ); + }, + + addBack: function( selector ) { + return this.add( selector == null ? + this.prevObject : this.prevObject.filter( selector ) + ); + } +} ); + +function sibling( cur, dir ) { + while ( ( cur = cur[ dir ] ) && cur.nodeType !== 1 ) {} + return cur; +} + +jQuery.each( { + parent: function( elem ) { + var parent = elem.parentNode; + return parent && parent.nodeType !== 11 ? parent : null; + }, + parents: function( elem ) { + return dir( elem, "parentNode" ); + }, + parentsUntil: function( elem, i, until ) { + return dir( elem, "parentNode", until ); + }, + next: function( elem ) { + return sibling( elem, "nextSibling" ); + }, + prev: function( elem ) { + return sibling( elem, "previousSibling" ); + }, + nextAll: function( elem ) { + return dir( elem, "nextSibling" ); + }, + prevAll: function( elem ) { + return dir( elem, "previousSibling" ); + }, + nextUntil: function( elem, i, until ) { + return dir( elem, "nextSibling", until ); + }, + prevUntil: function( elem, i, until ) { + return dir( elem, "previousSibling", until ); + }, + siblings: function( elem ) { + return siblings( ( elem.parentNode || {} ).firstChild, elem ); + }, + children: function( elem ) { + return siblings( elem.firstChild ); + }, + contents: function( elem ) { + return elem.contentDocument || jQuery.merge( [], elem.childNodes ); + } +}, function( name, fn ) { + jQuery.fn[ name ] = function( until, selector ) { + var matched = jQuery.map( this, fn, until ); + + if ( name.slice( -5 ) !== "Until" ) { + selector = until; + } + + if ( selector && typeof selector === "string" ) { + matched = jQuery.filter( selector, matched ); + } + + if ( this.length > 1 ) { + + // Remove duplicates + if ( !guaranteedUnique[ name ] ) { + jQuery.uniqueSort( matched ); + } + + // Reverse order for parents* and prev-derivatives + if ( rparentsprev.test( name ) ) { + matched.reverse(); + } + } + + return this.pushStack( matched ); + }; +} ); +var rnothtmlwhite = ( /[^\x20\t\r\n\f]+/g ); + + + +// Convert String-formatted options into Object-formatted ones +function createOptions( options ) { + var object = {}; + jQuery.each( options.match( rnothtmlwhite ) || [], function( _, flag ) { + object[ flag ] = true; + } ); + return object; +} + +/* + * Create a callback list using the following parameters: + * + * options: an optional list of space-separated options that will change how + * the callback list behaves or a more traditional option object + * + * By default a callback list will act like an event callback list and can be + * "fired" multiple times. + * + * Possible options: + * + * once: will ensure the callback list can only be fired once (like a Deferred) + * + * memory: will keep track of previous values and will call any callback added + * after the list has been fired right away with the latest "memorized" + * values (like a Deferred) + * + * unique: will ensure a callback can only be added once (no duplicate in the list) + * + * stopOnFalse: interrupt callings when a callback returns false + * + */ +jQuery.Callbacks = function( options ) { + + // Convert options from String-formatted to Object-formatted if needed + // (we check in cache first) + options = typeof options === "string" ? + createOptions( options ) : + jQuery.extend( {}, options ); + + var // Flag to know if list is currently firing + firing, + + // Last fire value for non-forgettable lists + memory, + + // Flag to know if list was already fired + fired, + + // Flag to prevent firing + locked, + + // Actual callback list + list = [], + + // Queue of execution data for repeatable lists + queue = [], + + // Index of currently firing callback (modified by add/remove as needed) + firingIndex = -1, + + // Fire callbacks + fire = function() { + + // Enforce single-firing + locked = options.once; + + // Execute callbacks for all pending executions, + // respecting firingIndex overrides and runtime changes + fired = firing = true; + for ( ; queue.length; firingIndex = -1 ) { + memory = queue.shift(); + while ( ++firingIndex < list.length ) { + + // Run callback and check for early termination + if ( list[ firingIndex ].apply( memory[ 0 ], memory[ 1 ] ) === false && + options.stopOnFalse ) { + + // Jump to end and forget the data so .add doesn't re-fire + firingIndex = list.length; + memory = false; + } + } + } + + // Forget the data if we're done with it + if ( !options.memory ) { + memory = false; + } + + firing = false; + + // Clean up if we're done firing for good + if ( locked ) { + + // Keep an empty list if we have data for future add calls + if ( memory ) { + list = []; + + // Otherwise, this object is spent + } else { + list = ""; + } + } + }, + + // Actual Callbacks object + self = { + + // Add a callback or a collection of callbacks to the list + add: function() { + if ( list ) { + + // If we have memory from a past run, we should fire after adding + if ( memory && !firing ) { + firingIndex = list.length - 1; + queue.push( memory ); + } + + ( function add( args ) { + jQuery.each( args, function( _, arg ) { + if ( jQuery.isFunction( arg ) ) { + if ( !options.unique || !self.has( arg ) ) { + list.push( arg ); + } + } else if ( arg && arg.length && jQuery.type( arg ) !== "string" ) { + + // Inspect recursively + add( arg ); + } + } ); + } )( arguments ); + + if ( memory && !firing ) { + fire(); + } + } + return this; + }, + + // Remove a callback from the list + remove: function() { + jQuery.each( arguments, function( _, arg ) { + var index; + while ( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) { + list.splice( index, 1 ); + + // Handle firing indexes + if ( index <= firingIndex ) { + firingIndex--; + } + } + } ); + return this; + }, + + // Check if a given callback is in the list. + // If no argument is given, return whether or not list has callbacks attached. + has: function( fn ) { + return fn ? + jQuery.inArray( fn, list ) > -1 : + list.length > 0; + }, + + // Remove all callbacks from the list + empty: function() { + if ( list ) { + list = []; + } + return this; + }, + + // Disable .fire and .add + // Abort any current/pending executions + // Clear all callbacks and values + disable: function() { + locked = queue = []; + list = memory = ""; + return this; + }, + disabled: function() { + return !list; + }, + + // Disable .fire + // Also disable .add unless we have memory (since it would have no effect) + // Abort any pending executions + lock: function() { + locked = queue = []; + if ( !memory && !firing ) { + list = memory = ""; + } + return this; + }, + locked: function() { + return !!locked; + }, + + // Call all callbacks with the given context and arguments + fireWith: function( context, args ) { + if ( !locked ) { + args = args || []; + args = [ context, args.slice ? args.slice() : args ]; + queue.push( args ); + if ( !firing ) { + fire(); + } + } + return this; + }, + + // Call all the callbacks with the given arguments + fire: function() { + self.fireWith( this, arguments ); + return this; + }, + + // To know if the callbacks have already been called at least once + fired: function() { + return !!fired; + } + }; + + return self; +}; + + +function Identity( v ) { + return v; +} +function Thrower( ex ) { + throw ex; +} + +function adoptValue( value, resolve, reject ) { + var method; + + try { + + // Check for promise aspect first to privilege synchronous behavior + if ( value && jQuery.isFunction( ( method = value.promise ) ) ) { + method.call( value ).done( resolve ).fail( reject ); + + // Other thenables + } else if ( value && jQuery.isFunction( ( method = value.then ) ) ) { + method.call( value, resolve, reject ); + + // Other non-thenables + } else { + + // Support: Android 4.0 only + // Strict mode functions invoked without .call/.apply get global-object context + resolve.call( undefined, value ); + } + + // For Promises/A+, convert exceptions into rejections + // Since jQuery.when doesn't unwrap thenables, we can skip the extra checks appearing in + // Deferred#then to conditionally suppress rejection. + } catch ( value ) { + + // Support: Android 4.0 only + // Strict mode functions invoked without .call/.apply get global-object context + reject.call( undefined, value ); + } +} + +jQuery.extend( { + + Deferred: function( func ) { + var tuples = [ + + // action, add listener, callbacks, + // ... .then handlers, argument index, [final state] + [ "notify", "progress", jQuery.Callbacks( "memory" ), + jQuery.Callbacks( "memory" ), 2 ], + [ "resolve", "done", jQuery.Callbacks( "once memory" ), + jQuery.Callbacks( "once memory" ), 0, "resolved" ], + [ "reject", "fail", jQuery.Callbacks( "once memory" ), + jQuery.Callbacks( "once memory" ), 1, "rejected" ] + ], + state = "pending", + promise = { + state: function() { + return state; + }, + always: function() { + deferred.done( arguments ).fail( arguments ); + return this; + }, + "catch": function( fn ) { + return promise.then( null, fn ); + }, + + // Keep pipe for back-compat + pipe: function( /* fnDone, fnFail, fnProgress */ ) { + var fns = arguments; + + return jQuery.Deferred( function( newDefer ) { + jQuery.each( tuples, function( i, tuple ) { + + // Map tuples (progress, done, fail) to arguments (done, fail, progress) + var fn = jQuery.isFunction( fns[ tuple[ 4 ] ] ) && fns[ tuple[ 4 ] ]; + + // deferred.progress(function() { bind to newDefer or newDefer.notify }) + // deferred.done(function() { bind to newDefer or newDefer.resolve }) + // deferred.fail(function() { bind to newDefer or newDefer.reject }) + deferred[ tuple[ 1 ] ]( function() { + var returned = fn && fn.apply( this, arguments ); + if ( returned && jQuery.isFunction( returned.promise ) ) { + returned.promise() + .progress( newDefer.notify ) + .done( newDefer.resolve ) + .fail( newDefer.reject ); + } else { + newDefer[ tuple[ 0 ] + "With" ]( + this, + fn ? [ returned ] : arguments + ); + } + } ); + } ); + fns = null; + } ).promise(); + }, + then: function( onFulfilled, onRejected, onProgress ) { + var maxDepth = 0; + function resolve( depth, deferred, handler, special ) { + return function() { + var that = this, + args = arguments, + mightThrow = function() { + var returned, then; + + // Support: Promises/A+ section 2.3.3.3.3 + // https://promisesaplus.com/#point-59 + // Ignore double-resolution attempts + if ( depth < maxDepth ) { + return; + } + + returned = handler.apply( that, args ); + + // Support: Promises/A+ section 2.3.1 + // https://promisesaplus.com/#point-48 + if ( returned === deferred.promise() ) { + throw new TypeError( "Thenable self-resolution" ); + } + + // Support: Promises/A+ sections 2.3.3.1, 3.5 + // https://promisesaplus.com/#point-54 + // https://promisesaplus.com/#point-75 + // Retrieve `then` only once + then = returned && + + // Support: Promises/A+ section 2.3.4 + // https://promisesaplus.com/#point-64 + // Only check objects and functions for thenability + ( typeof returned === "object" || + typeof returned === "function" ) && + returned.then; + + // Handle a returned thenable + if ( jQuery.isFunction( then ) ) { + + // Special processors (notify) just wait for resolution + if ( special ) { + then.call( + returned, + resolve( maxDepth, deferred, Identity, special ), + resolve( maxDepth, deferred, Thrower, special ) + ); + + // Normal processors (resolve) also hook into progress + } else { + + // ...and disregard older resolution values + maxDepth++; + + then.call( + returned, + resolve( maxDepth, deferred, Identity, special ), + resolve( maxDepth, deferred, Thrower, special ), + resolve( maxDepth, deferred, Identity, + deferred.notifyWith ) + ); + } + + // Handle all other returned values + } else { + + // Only substitute handlers pass on context + // and multiple values (non-spec behavior) + if ( handler !== Identity ) { + that = undefined; + args = [ returned ]; + } + + // Process the value(s) + // Default process is resolve + ( special || deferred.resolveWith )( that, args ); + } + }, + + // Only normal processors (resolve) catch and reject exceptions + process = special ? + mightThrow : + function() { + try { + mightThrow(); + } catch ( e ) { + + if ( jQuery.Deferred.exceptionHook ) { + jQuery.Deferred.exceptionHook( e, + process.stackTrace ); + } + + // Support: Promises/A+ section 2.3.3.3.4.1 + // https://promisesaplus.com/#point-61 + // Ignore post-resolution exceptions + if ( depth + 1 >= maxDepth ) { + + // Only substitute handlers pass on context + // and multiple values (non-spec behavior) + if ( handler !== Thrower ) { + that = undefined; + args = [ e ]; + } + + deferred.rejectWith( that, args ); + } + } + }; + + // Support: Promises/A+ section 2.3.3.3.1 + // https://promisesaplus.com/#point-57 + // Re-resolve promises immediately to dodge false rejection from + // subsequent errors + if ( depth ) { + process(); + } else { + + // Call an optional hook to record the stack, in case of exception + // since it's otherwise lost when execution goes async + if ( jQuery.Deferred.getStackHook ) { + process.stackTrace = jQuery.Deferred.getStackHook(); + } + window.setTimeout( process ); + } + }; + } + + return jQuery.Deferred( function( newDefer ) { + + // progress_handlers.add( ... ) + tuples[ 0 ][ 3 ].add( + resolve( + 0, + newDefer, + jQuery.isFunction( onProgress ) ? + onProgress : + Identity, + newDefer.notifyWith + ) + ); + + // fulfilled_handlers.add( ... ) + tuples[ 1 ][ 3 ].add( + resolve( + 0, + newDefer, + jQuery.isFunction( onFulfilled ) ? + onFulfilled : + Identity + ) + ); + + // rejected_handlers.add( ... ) + tuples[ 2 ][ 3 ].add( + resolve( + 0, + newDefer, + jQuery.isFunction( onRejected ) ? + onRejected : + Thrower + ) + ); + } ).promise(); + }, + + // Get a promise for this deferred + // If obj is provided, the promise aspect is added to the object + promise: function( obj ) { + return obj != null ? jQuery.extend( obj, promise ) : promise; + } + }, + deferred = {}; + + // Add list-specific methods + jQuery.each( tuples, function( i, tuple ) { + var list = tuple[ 2 ], + stateString = tuple[ 5 ]; + + // promise.progress = list.add + // promise.done = list.add + // promise.fail = list.add + promise[ tuple[ 1 ] ] = list.add; + + // Handle state + if ( stateString ) { + list.add( + function() { + + // state = "resolved" (i.e., fulfilled) + // state = "rejected" + state = stateString; + }, + + // rejected_callbacks.disable + // fulfilled_callbacks.disable + tuples[ 3 - i ][ 2 ].disable, + + // progress_callbacks.lock + tuples[ 0 ][ 2 ].lock + ); + } + + // progress_handlers.fire + // fulfilled_handlers.fire + // rejected_handlers.fire + list.add( tuple[ 3 ].fire ); + + // deferred.notify = function() { deferred.notifyWith(...) } + // deferred.resolve = function() { deferred.resolveWith(...) } + // deferred.reject = function() { deferred.rejectWith(...) } + deferred[ tuple[ 0 ] ] = function() { + deferred[ tuple[ 0 ] + "With" ]( this === deferred ? undefined : this, arguments ); + return this; + }; + + // deferred.notifyWith = list.fireWith + // deferred.resolveWith = list.fireWith + // deferred.rejectWith = list.fireWith + deferred[ tuple[ 0 ] + "With" ] = list.fireWith; + } ); + + // Make the deferred a promise + promise.promise( deferred ); + + // Call given func if any + if ( func ) { + func.call( deferred, deferred ); + } + + // All done! + return deferred; + }, + + // Deferred helper + when: function( singleValue ) { + var + + // count of uncompleted subordinates + remaining = arguments.length, + + // count of unprocessed arguments + i = remaining, + + // subordinate fulfillment data + resolveContexts = Array( i ), + resolveValues = slice.call( arguments ), + + // the master Deferred + master = jQuery.Deferred(), + + // subordinate callback factory + updateFunc = function( i ) { + return function( value ) { + resolveContexts[ i ] = this; + resolveValues[ i ] = arguments.length > 1 ? slice.call( arguments ) : value; + if ( !( --remaining ) ) { + master.resolveWith( resolveContexts, resolveValues ); + } + }; + }; + + // Single- and empty arguments are adopted like Promise.resolve + if ( remaining <= 1 ) { + adoptValue( singleValue, master.done( updateFunc( i ) ).resolve, master.reject ); + + // Use .then() to unwrap secondary thenables (cf. gh-3000) + if ( master.state() === "pending" || + jQuery.isFunction( resolveValues[ i ] && resolveValues[ i ].then ) ) { + + return master.then(); + } + } + + // Multiple arguments are aggregated like Promise.all array elements + while ( i-- ) { + adoptValue( resolveValues[ i ], updateFunc( i ), master.reject ); + } + + return master.promise(); + } +} ); + + +// These usually indicate a programmer mistake during development, +// warn about them ASAP rather than swallowing them by default. +var rerrorNames = /^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/; + +jQuery.Deferred.exceptionHook = function( error, stack ) { + + // Support: IE 8 - 9 only + // Console exists when dev tools are open, which can happen at any time + if ( window.console && window.console.warn && error && rerrorNames.test( error.name ) ) { + window.console.warn( "jQuery.Deferred exception: " + error.message, error.stack, stack ); + } +}; + + + + +jQuery.readyException = function( error ) { + window.setTimeout( function() { + throw error; + } ); +}; + + + + +// The deferred used on DOM ready +var readyList = jQuery.Deferred(); + +jQuery.fn.ready = function( fn ) { + + readyList + .then( fn ) + + // Wrap jQuery.readyException in a function so that the lookup + // happens at the time of error handling instead of callback + // registration. + .catch( function( error ) { + jQuery.readyException( error ); + } ); + + return this; +}; + +jQuery.extend( { + + // Is the DOM ready to be used? Set to true once it occurs. + isReady: false, + + // A counter to track how many items to wait for before + // the ready event fires. See #6781 + readyWait: 1, + + // Hold (or release) the ready event + holdReady: function( hold ) { + if ( hold ) { + jQuery.readyWait++; + } else { + jQuery.ready( true ); + } + }, + + // Handle when the DOM is ready + ready: function( wait ) { + + // Abort if there are pending holds or we're already ready + if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) { + return; + } + + // Remember that the DOM is ready + jQuery.isReady = true; + + // If a normal DOM Ready event fired, decrement, and wait if need be + if ( wait !== true && --jQuery.readyWait > 0 ) { + return; + } + + // If there are functions bound, to execute + readyList.resolveWith( document, [ jQuery ] ); + } +} ); + +jQuery.ready.then = readyList.then; + +// The ready event handler and self cleanup method +function completed() { + document.removeEventListener( "DOMContentLoaded", completed ); + window.removeEventListener( "load", completed ); + jQuery.ready(); +} + +// Catch cases where $(document).ready() is called +// after the browser event has already occurred. +// Support: IE <=9 - 10 only +// Older IE sometimes signals "interactive" too soon +if ( document.readyState === "complete" || + ( document.readyState !== "loading" && !document.documentElement.doScroll ) ) { + + // Handle it asynchronously to allow scripts the opportunity to delay ready + window.setTimeout( jQuery.ready ); + +} else { + + // Use the handy event callback + document.addEventListener( "DOMContentLoaded", completed ); + + // A fallback to window.onload, that will always work + window.addEventListener( "load", completed ); +} + + + + +// Multifunctional method to get and set values of a collection +// The value/s can optionally be executed if it's a function +var access = function( elems, fn, key, value, chainable, emptyGet, raw ) { + var i = 0, + len = elems.length, + bulk = key == null; + + // Sets many values + if ( jQuery.type( key ) === "object" ) { + chainable = true; + for ( i in key ) { + access( elems, fn, i, key[ i ], true, emptyGet, raw ); + } + + // Sets one value + } else if ( value !== undefined ) { + chainable = true; + + if ( !jQuery.isFunction( value ) ) { + raw = true; + } + + if ( bulk ) { + + // Bulk operations run against the entire set + if ( raw ) { + fn.call( elems, value ); + fn = null; + + // ...except when executing function values + } else { + bulk = fn; + fn = function( elem, key, value ) { + return bulk.call( jQuery( elem ), value ); + }; + } + } + + if ( fn ) { + for ( ; i < len; i++ ) { + fn( + elems[ i ], key, raw ? + value : + value.call( elems[ i ], i, fn( elems[ i ], key ) ) + ); + } + } + } + + if ( chainable ) { + return elems; + } + + // Gets + if ( bulk ) { + return fn.call( elems ); + } + + return len ? fn( elems[ 0 ], key ) : emptyGet; +}; +var acceptData = function( owner ) { + + // Accepts only: + // - Node + // - Node.ELEMENT_NODE + // - Node.DOCUMENT_NODE + // - Object + // - Any + return owner.nodeType === 1 || owner.nodeType === 9 || !( +owner.nodeType ); +}; + + + + +function Data() { + this.expando = jQuery.expando + Data.uid++; +} + +Data.uid = 1; + +Data.prototype = { + + cache: function( owner ) { + + // Check if the owner object already has a cache + var value = owner[ this.expando ]; + + // If not, create one + if ( !value ) { + value = {}; + + // We can accept data for non-element nodes in modern browsers, + // but we should not, see #8335. + // Always return an empty object. + if ( acceptData( owner ) ) { + + // If it is a node unlikely to be stringify-ed or looped over + // use plain assignment + if ( owner.nodeType ) { + owner[ this.expando ] = value; + + // Otherwise secure it in a non-enumerable property + // configurable must be true to allow the property to be + // deleted when data is removed + } else { + Object.defineProperty( owner, this.expando, { + value: value, + configurable: true + } ); + } + } + } + + return value; + }, + set: function( owner, data, value ) { + var prop, + cache = this.cache( owner ); + + // Handle: [ owner, key, value ] args + // Always use camelCase key (gh-2257) + if ( typeof data === "string" ) { + cache[ jQuery.camelCase( data ) ] = value; + + // Handle: [ owner, { properties } ] args + } else { + + // Copy the properties one-by-one to the cache object + for ( prop in data ) { + cache[ jQuery.camelCase( prop ) ] = data[ prop ]; + } + } + return cache; + }, + get: function( owner, key ) { + return key === undefined ? + this.cache( owner ) : + + // Always use camelCase key (gh-2257) + owner[ this.expando ] && owner[ this.expando ][ jQuery.camelCase( key ) ]; + }, + access: function( owner, key, value ) { + + // In cases where either: + // + // 1. No key was specified + // 2. A string key was specified, but no value provided + // + // Take the "read" path and allow the get method to determine + // which value to return, respectively either: + // + // 1. The entire cache object + // 2. The data stored at the key + // + if ( key === undefined || + ( ( key && typeof key === "string" ) && value === undefined ) ) { + + return this.get( owner, key ); + } + + // When the key is not a string, or both a key and value + // are specified, set or extend (existing objects) with either: + // + // 1. An object of properties + // 2. A key and value + // + this.set( owner, key, value ); + + // Since the "set" path can have two possible entry points + // return the expected data based on which path was taken[*] + return value !== undefined ? value : key; + }, + remove: function( owner, key ) { + var i, + cache = owner[ this.expando ]; + + if ( cache === undefined ) { + return; + } + + if ( key !== undefined ) { + + // Support array or space separated string of keys + if ( jQuery.isArray( key ) ) { + + // If key is an array of keys... + // We always set camelCase keys, so remove that. + key = key.map( jQuery.camelCase ); + } else { + key = jQuery.camelCase( key ); + + // If a key with the spaces exists, use it. + // Otherwise, create an array by matching non-whitespace + key = key in cache ? + [ key ] : + ( key.match( rnothtmlwhite ) || [] ); + } + + i = key.length; + + while ( i-- ) { + delete cache[ key[ i ] ]; + } + } + + // Remove the expando if there's no more data + if ( key === undefined || jQuery.isEmptyObject( cache ) ) { + + // Support: Chrome <=35 - 45 + // Webkit & Blink performance suffers when deleting properties + // from DOM nodes, so set to undefined instead + // https://bugs.chromium.org/p/chromium/issues/detail?id=378607 (bug restricted) + if ( owner.nodeType ) { + owner[ this.expando ] = undefined; + } else { + delete owner[ this.expando ]; + } + } + }, + hasData: function( owner ) { + var cache = owner[ this.expando ]; + return cache !== undefined && !jQuery.isEmptyObject( cache ); + } +}; +var dataPriv = new Data(); + +var dataUser = new Data(); + + + +// Implementation Summary +// +// 1. Enforce API surface and semantic compatibility with 1.9.x branch +// 2. Improve the module's maintainability by reducing the storage +// paths to a single mechanism. +// 3. Use the same single mechanism to support "private" and "user" data. +// 4. _Never_ expose "private" data to user code (TODO: Drop _data, _removeData) +// 5. Avoid exposing implementation details on user objects (eg. expando properties) +// 6. Provide a clear path for implementation upgrade to WeakMap in 2014 + +var rbrace = /^(?:\{[\w\W]*\}|\[[\w\W]*\])$/, + rmultiDash = /[A-Z]/g; + +function getData( data ) { + if ( data === "true" ) { + return true; + } + + if ( data === "false" ) { + return false; + } + + if ( data === "null" ) { + return null; + } + + // Only convert to a number if it doesn't change the string + if ( data === +data + "" ) { + return +data; + } + + if ( rbrace.test( data ) ) { + return JSON.parse( data ); + } + + return data; +} + +function dataAttr( elem, key, data ) { + var name; + + // If nothing was found internally, try to fetch any + // data from the HTML5 data-* attribute + if ( data === undefined && elem.nodeType === 1 ) { + name = "data-" + key.replace( rmultiDash, "-$&" ).toLowerCase(); + data = elem.getAttribute( name ); + + if ( typeof data === "string" ) { + try { + data = getData( data ); + } catch ( e ) {} + + // Make sure we set the data so it isn't changed later + dataUser.set( elem, key, data ); + } else { + data = undefined; + } + } + return data; +} + +jQuery.extend( { + hasData: function( elem ) { + return dataUser.hasData( elem ) || dataPriv.hasData( elem ); + }, + + data: function( elem, name, data ) { + return dataUser.access( elem, name, data ); + }, + + removeData: function( elem, name ) { + dataUser.remove( elem, name ); + }, + + // TODO: Now that all calls to _data and _removeData have been replaced + // with direct calls to dataPriv methods, these can be deprecated. + _data: function( elem, name, data ) { + return dataPriv.access( elem, name, data ); + }, + + _removeData: function( elem, name ) { + dataPriv.remove( elem, name ); + } +} ); + +jQuery.fn.extend( { + data: function( key, value ) { + var i, name, data, + elem = this[ 0 ], + attrs = elem && elem.attributes; + + // Gets all values + if ( key === undefined ) { + if ( this.length ) { + data = dataUser.get( elem ); + + if ( elem.nodeType === 1 && !dataPriv.get( elem, "hasDataAttrs" ) ) { + i = attrs.length; + while ( i-- ) { + + // Support: IE 11 only + // The attrs elements can be null (#14894) + if ( attrs[ i ] ) { + name = attrs[ i ].name; + if ( name.indexOf( "data-" ) === 0 ) { + name = jQuery.camelCase( name.slice( 5 ) ); + dataAttr( elem, name, data[ name ] ); + } + } + } + dataPriv.set( elem, "hasDataAttrs", true ); + } + } + + return data; + } + + // Sets multiple values + if ( typeof key === "object" ) { + return this.each( function() { + dataUser.set( this, key ); + } ); + } + + return access( this, function( value ) { + var data; + + // The calling jQuery object (element matches) is not empty + // (and therefore has an element appears at this[ 0 ]) and the + // `value` parameter was not undefined. An empty jQuery object + // will result in `undefined` for elem = this[ 0 ] which will + // throw an exception if an attempt to read a data cache is made. + if ( elem && value === undefined ) { + + // Attempt to get data from the cache + // The key will always be camelCased in Data + data = dataUser.get( elem, key ); + if ( data !== undefined ) { + return data; + } + + // Attempt to "discover" the data in + // HTML5 custom data-* attrs + data = dataAttr( elem, key ); + if ( data !== undefined ) { + return data; + } + + // We tried really hard, but the data doesn't exist. + return; + } + + // Set the data... + this.each( function() { + + // We always store the camelCased key + dataUser.set( this, key, value ); + } ); + }, null, value, arguments.length > 1, null, true ); + }, + + removeData: function( key ) { + return this.each( function() { + dataUser.remove( this, key ); + } ); + } +} ); + + +jQuery.extend( { + queue: function( elem, type, data ) { + var queue; + + if ( elem ) { + type = ( type || "fx" ) + "queue"; + queue = dataPriv.get( elem, type ); + + // Speed up dequeue by getting out quickly if this is just a lookup + if ( data ) { + if ( !queue || jQuery.isArray( data ) ) { + queue = dataPriv.access( elem, type, jQuery.makeArray( data ) ); + } else { + queue.push( data ); + } + } + return queue || []; + } + }, + + dequeue: function( elem, type ) { + type = type || "fx"; + + var queue = jQuery.queue( elem, type ), + startLength = queue.length, + fn = queue.shift(), + hooks = jQuery._queueHooks( elem, type ), + next = function() { + jQuery.dequeue( elem, type ); + }; + + // If the fx queue is dequeued, always remove the progress sentinel + if ( fn === "inprogress" ) { + fn = queue.shift(); + startLength--; + } + + if ( fn ) { + + // Add a progress sentinel to prevent the fx queue from being + // automatically dequeued + if ( type === "fx" ) { + queue.unshift( "inprogress" ); + } + + // Clear up the last queue stop function + delete hooks.stop; + fn.call( elem, next, hooks ); + } + + if ( !startLength && hooks ) { + hooks.empty.fire(); + } + }, + + // Not public - generate a queueHooks object, or return the current one + _queueHooks: function( elem, type ) { + var key = type + "queueHooks"; + return dataPriv.get( elem, key ) || dataPriv.access( elem, key, { + empty: jQuery.Callbacks( "once memory" ).add( function() { + dataPriv.remove( elem, [ type + "queue", key ] ); + } ) + } ); + } +} ); + +jQuery.fn.extend( { + queue: function( type, data ) { + var setter = 2; + + if ( typeof type !== "string" ) { + data = type; + type = "fx"; + setter--; + } + + if ( arguments.length < setter ) { + return jQuery.queue( this[ 0 ], type ); + } + + return data === undefined ? + this : + this.each( function() { + var queue = jQuery.queue( this, type, data ); + + // Ensure a hooks for this queue + jQuery._queueHooks( this, type ); + + if ( type === "fx" && queue[ 0 ] !== "inprogress" ) { + jQuery.dequeue( this, type ); + } + } ); + }, + dequeue: function( type ) { + return this.each( function() { + jQuery.dequeue( this, type ); + } ); + }, + clearQueue: function( type ) { + return this.queue( type || "fx", [] ); + }, + + // Get a promise resolved when queues of a certain type + // are emptied (fx is the type by default) + promise: function( type, obj ) { + var tmp, + count = 1, + defer = jQuery.Deferred(), + elements = this, + i = this.length, + resolve = function() { + if ( !( --count ) ) { + defer.resolveWith( elements, [ elements ] ); + } + }; + + if ( typeof type !== "string" ) { + obj = type; + type = undefined; + } + type = type || "fx"; + + while ( i-- ) { + tmp = dataPriv.get( elements[ i ], type + "queueHooks" ); + if ( tmp && tmp.empty ) { + count++; + tmp.empty.add( resolve ); + } + } + resolve(); + return defer.promise( obj ); + } +} ); +var pnum = ( /[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/ ).source; + +var rcssNum = new RegExp( "^(?:([+-])=|)(" + pnum + ")([a-z%]*)$", "i" ); + + +var cssExpand = [ "Top", "Right", "Bottom", "Left" ]; + +var isHiddenWithinTree = function( elem, el ) { + + // isHiddenWithinTree might be called from jQuery#filter function; + // in that case, element will be second argument + elem = el || elem; + + // Inline style trumps all + return elem.style.display === "none" || + elem.style.display === "" && + + // Otherwise, check computed style + // Support: Firefox <=43 - 45 + // Disconnected elements can have computed display: none, so first confirm that elem is + // in the document. + jQuery.contains( elem.ownerDocument, elem ) && + + jQuery.css( elem, "display" ) === "none"; + }; + +var swap = function( elem, options, callback, args ) { + var ret, name, + old = {}; + + // Remember the old values, and insert the new ones + for ( name in options ) { + old[ name ] = elem.style[ name ]; + elem.style[ name ] = options[ name ]; + } + + ret = callback.apply( elem, args || [] ); + + // Revert the old values + for ( name in options ) { + elem.style[ name ] = old[ name ]; + } + + return ret; +}; + + + + +function adjustCSS( elem, prop, valueParts, tween ) { + var adjusted, + scale = 1, + maxIterations = 20, + currentValue = tween ? + function() { + return tween.cur(); + } : + function() { + return jQuery.css( elem, prop, "" ); + }, + initial = currentValue(), + unit = valueParts && valueParts[ 3 ] || ( jQuery.cssNumber[ prop ] ? "" : "px" ), + + // Starting value computation is required for potential unit mismatches + initialInUnit = ( jQuery.cssNumber[ prop ] || unit !== "px" && +initial ) && + rcssNum.exec( jQuery.css( elem, prop ) ); + + if ( initialInUnit && initialInUnit[ 3 ] !== unit ) { + + // Trust units reported by jQuery.css + unit = unit || initialInUnit[ 3 ]; + + // Make sure we update the tween properties later on + valueParts = valueParts || []; + + // Iteratively approximate from a nonzero starting point + initialInUnit = +initial || 1; + + do { + + // If previous iteration zeroed out, double until we get *something*. + // Use string for doubling so we don't accidentally see scale as unchanged below + scale = scale || ".5"; + + // Adjust and apply + initialInUnit = initialInUnit / scale; + jQuery.style( elem, prop, initialInUnit + unit ); + + // Update scale, tolerating zero or NaN from tween.cur() + // Break the loop if scale is unchanged or perfect, or if we've just had enough. + } while ( + scale !== ( scale = currentValue() / initial ) && scale !== 1 && --maxIterations + ); + } + + if ( valueParts ) { + initialInUnit = +initialInUnit || +initial || 0; + + // Apply relative offset (+=/-=) if specified + adjusted = valueParts[ 1 ] ? + initialInUnit + ( valueParts[ 1 ] + 1 ) * valueParts[ 2 ] : + +valueParts[ 2 ]; + if ( tween ) { + tween.unit = unit; + tween.start = initialInUnit; + tween.end = adjusted; + } + } + return adjusted; +} + + +var defaultDisplayMap = {}; + +function getDefaultDisplay( elem ) { + var temp, + doc = elem.ownerDocument, + nodeName = elem.nodeName, + display = defaultDisplayMap[ nodeName ]; + + if ( display ) { + return display; + } + + temp = doc.body.appendChild( doc.createElement( nodeName ) ); + display = jQuery.css( temp, "display" ); + + temp.parentNode.removeChild( temp ); + + if ( display === "none" ) { + display = "block"; + } + defaultDisplayMap[ nodeName ] = display; + + return display; +} + +function showHide( elements, show ) { + var display, elem, + values = [], + index = 0, + length = elements.length; + + // Determine new display value for elements that need to change + for ( ; index < length; index++ ) { + elem = elements[ index ]; + if ( !elem.style ) { + continue; + } + + display = elem.style.display; + if ( show ) { + + // Since we force visibility upon cascade-hidden elements, an immediate (and slow) + // check is required in this first loop unless we have a nonempty display value (either + // inline or about-to-be-restored) + if ( display === "none" ) { + values[ index ] = dataPriv.get( elem, "display" ) || null; + if ( !values[ index ] ) { + elem.style.display = ""; + } + } + if ( elem.style.display === "" && isHiddenWithinTree( elem ) ) { + values[ index ] = getDefaultDisplay( elem ); + } + } else { + if ( display !== "none" ) { + values[ index ] = "none"; + + // Remember what we're overwriting + dataPriv.set( elem, "display", display ); + } + } + } + + // Set the display of the elements in a second loop to avoid constant reflow + for ( index = 0; index < length; index++ ) { + if ( values[ index ] != null ) { + elements[ index ].style.display = values[ index ]; + } + } + + return elements; +} + +jQuery.fn.extend( { + show: function() { + return showHide( this, true ); + }, + hide: function() { + return showHide( this ); + }, + toggle: function( state ) { + if ( typeof state === "boolean" ) { + return state ? this.show() : this.hide(); + } + + return this.each( function() { + if ( isHiddenWithinTree( this ) ) { + jQuery( this ).show(); + } else { + jQuery( this ).hide(); + } + } ); + } +} ); +var rcheckableType = ( /^(?:checkbox|radio)$/i ); + +var rtagName = ( /<([a-z][^\/\0>\x20\t\r\n\f]+)/i ); + +var rscriptType = ( /^$|\/(?:java|ecma)script/i ); + + + +// We have to close these tags to support XHTML (#13200) +var wrapMap = { + + // Support: IE <=9 only + option: [ 1, "" ], + + // XHTML parsers do not magically insert elements in the + // same way that tag soup parsers do. So we cannot shorten + // this by omitting or other required elements. + thead: [ 1, "", "
" ], + col: [ 2, "", "
" ], + tr: [ 2, "", "
" ], + td: [ 3, "", "
" ], + + _default: [ 0, "", "" ] +}; + +// Support: IE <=9 only +wrapMap.optgroup = wrapMap.option; + +wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; +wrapMap.th = wrapMap.td; + + +function getAll( context, tag ) { + + // Support: IE <=9 - 11 only + // Use typeof to avoid zero-argument method invocation on host objects (#15151) + var ret; + + if ( typeof context.getElementsByTagName !== "undefined" ) { + ret = context.getElementsByTagName( tag || "*" ); + + } else if ( typeof context.querySelectorAll !== "undefined" ) { + ret = context.querySelectorAll( tag || "*" ); + + } else { + ret = []; + } + + if ( tag === undefined || tag && jQuery.nodeName( context, tag ) ) { + return jQuery.merge( [ context ], ret ); + } + + return ret; +} + + +// Mark scripts as having already been evaluated +function setGlobalEval( elems, refElements ) { + var i = 0, + l = elems.length; + + for ( ; i < l; i++ ) { + dataPriv.set( + elems[ i ], + "globalEval", + !refElements || dataPriv.get( refElements[ i ], "globalEval" ) + ); + } +} + + +var rhtml = /<|&#?\w+;/; + +function buildFragment( elems, context, scripts, selection, ignored ) { + var elem, tmp, tag, wrap, contains, j, + fragment = context.createDocumentFragment(), + nodes = [], + i = 0, + l = elems.length; + + for ( ; i < l; i++ ) { + elem = elems[ i ]; + + if ( elem || elem === 0 ) { + + // Add nodes directly + if ( jQuery.type( elem ) === "object" ) { + + // Support: Android <=4.0 only, PhantomJS 1 only + // push.apply(_, arraylike) throws on ancient WebKit + jQuery.merge( nodes, elem.nodeType ? [ elem ] : elem ); + + // Convert non-html into a text node + } else if ( !rhtml.test( elem ) ) { + nodes.push( context.createTextNode( elem ) ); + + // Convert html into DOM nodes + } else { + tmp = tmp || fragment.appendChild( context.createElement( "div" ) ); + + // Deserialize a standard representation + tag = ( rtagName.exec( elem ) || [ "", "" ] )[ 1 ].toLowerCase(); + wrap = wrapMap[ tag ] || wrapMap._default; + tmp.innerHTML = wrap[ 1 ] + jQuery.htmlPrefilter( elem ) + wrap[ 2 ]; + + // Descend through wrappers to the right content + j = wrap[ 0 ]; + while ( j-- ) { + tmp = tmp.lastChild; + } + + // Support: Android <=4.0 only, PhantomJS 1 only + // push.apply(_, arraylike) throws on ancient WebKit + jQuery.merge( nodes, tmp.childNodes ); + + // Remember the top-level container + tmp = fragment.firstChild; + + // Ensure the created nodes are orphaned (#12392) + tmp.textContent = ""; + } + } + } + + // Remove wrapper from fragment + fragment.textContent = ""; + + i = 0; + while ( ( elem = nodes[ i++ ] ) ) { + + // Skip elements already in the context collection (trac-4087) + if ( selection && jQuery.inArray( elem, selection ) > -1 ) { + if ( ignored ) { + ignored.push( elem ); + } + continue; + } + + contains = jQuery.contains( elem.ownerDocument, elem ); + + // Append to fragment + tmp = getAll( fragment.appendChild( elem ), "script" ); + + // Preserve script evaluation history + if ( contains ) { + setGlobalEval( tmp ); + } + + // Capture executables + if ( scripts ) { + j = 0; + while ( ( elem = tmp[ j++ ] ) ) { + if ( rscriptType.test( elem.type || "" ) ) { + scripts.push( elem ); + } + } + } + } + + return fragment; +} + + +( function() { + var fragment = document.createDocumentFragment(), + div = fragment.appendChild( document.createElement( "div" ) ), + input = document.createElement( "input" ); + + // Support: Android 4.0 - 4.3 only + // Check state lost if the name is set (#11217) + // Support: Windows Web Apps (WWA) + // `name` and `type` must use .setAttribute for WWA (#14901) + input.setAttribute( "type", "radio" ); + input.setAttribute( "checked", "checked" ); + input.setAttribute( "name", "t" ); + + div.appendChild( input ); + + // Support: Android <=4.1 only + // Older WebKit doesn't clone checked state correctly in fragments + support.checkClone = div.cloneNode( true ).cloneNode( true ).lastChild.checked; + + // Support: IE <=11 only + // Make sure textarea (and checkbox) defaultValue is properly cloned + div.innerHTML = ""; + support.noCloneChecked = !!div.cloneNode( true ).lastChild.defaultValue; +} )(); +var documentElement = document.documentElement; + + + +var + rkeyEvent = /^key/, + rmouseEvent = /^(?:mouse|pointer|contextmenu|drag|drop)|click/, + rtypenamespace = /^([^.]*)(?:\.(.+)|)/; + +function returnTrue() { + return true; +} + +function returnFalse() { + return false; +} + +// Support: IE <=9 only +// See #13393 for more info +function safeActiveElement() { + try { + return document.activeElement; + } catch ( err ) { } +} + +function on( elem, types, selector, data, fn, one ) { + var origFn, type; + + // Types can be a map of types/handlers + if ( typeof types === "object" ) { + + // ( types-Object, selector, data ) + if ( typeof selector !== "string" ) { + + // ( types-Object, data ) + data = data || selector; + selector = undefined; + } + for ( type in types ) { + on( elem, type, selector, data, types[ type ], one ); + } + return elem; + } + + if ( data == null && fn == null ) { + + // ( types, fn ) + fn = selector; + data = selector = undefined; + } else if ( fn == null ) { + if ( typeof selector === "string" ) { + + // ( types, selector, fn ) + fn = data; + data = undefined; + } else { + + // ( types, data, fn ) + fn = data; + data = selector; + selector = undefined; + } + } + if ( fn === false ) { + fn = returnFalse; + } else if ( !fn ) { + return elem; + } + + if ( one === 1 ) { + origFn = fn; + fn = function( event ) { + + // Can use an empty set, since event contains the info + jQuery().off( event ); + return origFn.apply( this, arguments ); + }; + + // Use same guid so caller can remove using origFn + fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ ); + } + return elem.each( function() { + jQuery.event.add( this, types, fn, data, selector ); + } ); +} + +/* + * Helper functions for managing events -- not part of the public interface. + * Props to Dean Edwards' addEvent library for many of the ideas. + */ +jQuery.event = { + + global: {}, + + add: function( elem, types, handler, data, selector ) { + + var handleObjIn, eventHandle, tmp, + events, t, handleObj, + special, handlers, type, namespaces, origType, + elemData = dataPriv.get( elem ); + + // Don't attach events to noData or text/comment nodes (but allow plain objects) + if ( !elemData ) { + return; + } + + // Caller can pass in an object of custom data in lieu of the handler + if ( handler.handler ) { + handleObjIn = handler; + handler = handleObjIn.handler; + selector = handleObjIn.selector; + } + + // Ensure that invalid selectors throw exceptions at attach time + // Evaluate against documentElement in case elem is a non-element node (e.g., document) + if ( selector ) { + jQuery.find.matchesSelector( documentElement, selector ); + } + + // Make sure that the handler has a unique ID, used to find/remove it later + if ( !handler.guid ) { + handler.guid = jQuery.guid++; + } + + // Init the element's event structure and main handler, if this is the first + if ( !( events = elemData.events ) ) { + events = elemData.events = {}; + } + if ( !( eventHandle = elemData.handle ) ) { + eventHandle = elemData.handle = function( e ) { + + // Discard the second event of a jQuery.event.trigger() and + // when an event is called after a page has unloaded + return typeof jQuery !== "undefined" && jQuery.event.triggered !== e.type ? + jQuery.event.dispatch.apply( elem, arguments ) : undefined; + }; + } + + // Handle multiple events separated by a space + types = ( types || "" ).match( rnothtmlwhite ) || [ "" ]; + t = types.length; + while ( t-- ) { + tmp = rtypenamespace.exec( types[ t ] ) || []; + type = origType = tmp[ 1 ]; + namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort(); + + // There *must* be a type, no attaching namespace-only handlers + if ( !type ) { + continue; + } + + // If event changes its type, use the special event handlers for the changed type + special = jQuery.event.special[ type ] || {}; + + // If selector defined, determine special event api type, otherwise given type + type = ( selector ? special.delegateType : special.bindType ) || type; + + // Update special based on newly reset type + special = jQuery.event.special[ type ] || {}; + + // handleObj is passed to all event handlers + handleObj = jQuery.extend( { + type: type, + origType: origType, + data: data, + handler: handler, + guid: handler.guid, + selector: selector, + needsContext: selector && jQuery.expr.match.needsContext.test( selector ), + namespace: namespaces.join( "." ) + }, handleObjIn ); + + // Init the event handler queue if we're the first + if ( !( handlers = events[ type ] ) ) { + handlers = events[ type ] = []; + handlers.delegateCount = 0; + + // Only use addEventListener if the special events handler returns false + if ( !special.setup || + special.setup.call( elem, data, namespaces, eventHandle ) === false ) { + + if ( elem.addEventListener ) { + elem.addEventListener( type, eventHandle ); + } + } + } + + if ( special.add ) { + special.add.call( elem, handleObj ); + + if ( !handleObj.handler.guid ) { + handleObj.handler.guid = handler.guid; + } + } + + // Add to the element's handler list, delegates in front + if ( selector ) { + handlers.splice( handlers.delegateCount++, 0, handleObj ); + } else { + handlers.push( handleObj ); + } + + // Keep track of which events have ever been used, for event optimization + jQuery.event.global[ type ] = true; + } + + }, + + // Detach an event or set of events from an element + remove: function( elem, types, handler, selector, mappedTypes ) { + + var j, origCount, tmp, + events, t, handleObj, + special, handlers, type, namespaces, origType, + elemData = dataPriv.hasData( elem ) && dataPriv.get( elem ); + + if ( !elemData || !( events = elemData.events ) ) { + return; + } + + // Once for each type.namespace in types; type may be omitted + types = ( types || "" ).match( rnothtmlwhite ) || [ "" ]; + t = types.length; + while ( t-- ) { + tmp = rtypenamespace.exec( types[ t ] ) || []; + type = origType = tmp[ 1 ]; + namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort(); + + // Unbind all events (on this namespace, if provided) for the element + if ( !type ) { + for ( type in events ) { + jQuery.event.remove( elem, type + types[ t ], handler, selector, true ); + } + continue; + } + + special = jQuery.event.special[ type ] || {}; + type = ( selector ? special.delegateType : special.bindType ) || type; + handlers = events[ type ] || []; + tmp = tmp[ 2 ] && + new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" ); + + // Remove matching events + origCount = j = handlers.length; + while ( j-- ) { + handleObj = handlers[ j ]; + + if ( ( mappedTypes || origType === handleObj.origType ) && + ( !handler || handler.guid === handleObj.guid ) && + ( !tmp || tmp.test( handleObj.namespace ) ) && + ( !selector || selector === handleObj.selector || + selector === "**" && handleObj.selector ) ) { + handlers.splice( j, 1 ); + + if ( handleObj.selector ) { + handlers.delegateCount--; + } + if ( special.remove ) { + special.remove.call( elem, handleObj ); + } + } + } + + // Remove generic event handler if we removed something and no more handlers exist + // (avoids potential for endless recursion during removal of special event handlers) + if ( origCount && !handlers.length ) { + if ( !special.teardown || + special.teardown.call( elem, namespaces, elemData.handle ) === false ) { + + jQuery.removeEvent( elem, type, elemData.handle ); + } + + delete events[ type ]; + } + } + + // Remove data and the expando if it's no longer used + if ( jQuery.isEmptyObject( events ) ) { + dataPriv.remove( elem, "handle events" ); + } + }, + + dispatch: function( nativeEvent ) { + + // Make a writable jQuery.Event from the native event object + var event = jQuery.event.fix( nativeEvent ); + + var i, j, ret, matched, handleObj, handlerQueue, + args = new Array( arguments.length ), + handlers = ( dataPriv.get( this, "events" ) || {} )[ event.type ] || [], + special = jQuery.event.special[ event.type ] || {}; + + // Use the fix-ed jQuery.Event rather than the (read-only) native event + args[ 0 ] = event; + + for ( i = 1; i < arguments.length; i++ ) { + args[ i ] = arguments[ i ]; + } + + event.delegateTarget = this; + + // Call the preDispatch hook for the mapped type, and let it bail if desired + if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) { + return; + } + + // Determine handlers + handlerQueue = jQuery.event.handlers.call( this, event, handlers ); + + // Run delegates first; they may want to stop propagation beneath us + i = 0; + while ( ( matched = handlerQueue[ i++ ] ) && !event.isPropagationStopped() ) { + event.currentTarget = matched.elem; + + j = 0; + while ( ( handleObj = matched.handlers[ j++ ] ) && + !event.isImmediatePropagationStopped() ) { + + // Triggered event must either 1) have no namespace, or 2) have namespace(s) + // a subset or equal to those in the bound event (both can have no namespace). + if ( !event.rnamespace || event.rnamespace.test( handleObj.namespace ) ) { + + event.handleObj = handleObj; + event.data = handleObj.data; + + ret = ( ( jQuery.event.special[ handleObj.origType ] || {} ).handle || + handleObj.handler ).apply( matched.elem, args ); + + if ( ret !== undefined ) { + if ( ( event.result = ret ) === false ) { + event.preventDefault(); + event.stopPropagation(); + } + } + } + } + } + + // Call the postDispatch hook for the mapped type + if ( special.postDispatch ) { + special.postDispatch.call( this, event ); + } + + return event.result; + }, + + handlers: function( event, handlers ) { + var i, handleObj, sel, matchedHandlers, matchedSelectors, + handlerQueue = [], + delegateCount = handlers.delegateCount, + cur = event.target; + + // Find delegate handlers + if ( delegateCount && + + // Support: IE <=9 + // Black-hole SVG instance trees (trac-13180) + cur.nodeType && + + // Support: Firefox <=42 + // Suppress spec-violating clicks indicating a non-primary pointer button (trac-3861) + // https://www.w3.org/TR/DOM-Level-3-Events/#event-type-click + // Support: IE 11 only + // ...but not arrow key "clicks" of radio inputs, which can have `button` -1 (gh-2343) + !( event.type === "click" && event.button >= 1 ) ) { + + for ( ; cur !== this; cur = cur.parentNode || this ) { + + // Don't check non-elements (#13208) + // Don't process clicks on disabled elements (#6911, #8165, #11382, #11764) + if ( cur.nodeType === 1 && !( event.type === "click" && cur.disabled === true ) ) { + matchedHandlers = []; + matchedSelectors = {}; + for ( i = 0; i < delegateCount; i++ ) { + handleObj = handlers[ i ]; + + // Don't conflict with Object.prototype properties (#13203) + sel = handleObj.selector + " "; + + if ( matchedSelectors[ sel ] === undefined ) { + matchedSelectors[ sel ] = handleObj.needsContext ? + jQuery( sel, this ).index( cur ) > -1 : + jQuery.find( sel, this, null, [ cur ] ).length; + } + if ( matchedSelectors[ sel ] ) { + matchedHandlers.push( handleObj ); + } + } + if ( matchedHandlers.length ) { + handlerQueue.push( { elem: cur, handlers: matchedHandlers } ); + } + } + } + } + + // Add the remaining (directly-bound) handlers + cur = this; + if ( delegateCount < handlers.length ) { + handlerQueue.push( { elem: cur, handlers: handlers.slice( delegateCount ) } ); + } + + return handlerQueue; + }, + + addProp: function( name, hook ) { + Object.defineProperty( jQuery.Event.prototype, name, { + enumerable: true, + configurable: true, + + get: jQuery.isFunction( hook ) ? + function() { + if ( this.originalEvent ) { + return hook( this.originalEvent ); + } + } : + function() { + if ( this.originalEvent ) { + return this.originalEvent[ name ]; + } + }, + + set: function( value ) { + Object.defineProperty( this, name, { + enumerable: true, + configurable: true, + writable: true, + value: value + } ); + } + } ); + }, + + fix: function( originalEvent ) { + return originalEvent[ jQuery.expando ] ? + originalEvent : + new jQuery.Event( originalEvent ); + }, + + special: { + load: { + + // Prevent triggered image.load events from bubbling to window.load + noBubble: true + }, + focus: { + + // Fire native event if possible so blur/focus sequence is correct + trigger: function() { + if ( this !== safeActiveElement() && this.focus ) { + this.focus(); + return false; + } + }, + delegateType: "focusin" + }, + blur: { + trigger: function() { + if ( this === safeActiveElement() && this.blur ) { + this.blur(); + return false; + } + }, + delegateType: "focusout" + }, + click: { + + // For checkbox, fire native event so checked state will be right + trigger: function() { + if ( this.type === "checkbox" && this.click && jQuery.nodeName( this, "input" ) ) { + this.click(); + return false; + } + }, + + // For cross-browser consistency, don't fire native .click() on links + _default: function( event ) { + return jQuery.nodeName( event.target, "a" ); + } + }, + + beforeunload: { + postDispatch: function( event ) { + + // Support: Firefox 20+ + // Firefox doesn't alert if the returnValue field is not set. + if ( event.result !== undefined && event.originalEvent ) { + event.originalEvent.returnValue = event.result; + } + } + } + } +}; + +jQuery.removeEvent = function( elem, type, handle ) { + + // This "if" is needed for plain objects + if ( elem.removeEventListener ) { + elem.removeEventListener( type, handle ); + } +}; + +jQuery.Event = function( src, props ) { + + // Allow instantiation without the 'new' keyword + if ( !( this instanceof jQuery.Event ) ) { + return new jQuery.Event( src, props ); + } + + // Event object + if ( src && src.type ) { + this.originalEvent = src; + this.type = src.type; + + // Events bubbling up the document may have been marked as prevented + // by a handler lower down the tree; reflect the correct value. + this.isDefaultPrevented = src.defaultPrevented || + src.defaultPrevented === undefined && + + // Support: Android <=2.3 only + src.returnValue === false ? + returnTrue : + returnFalse; + + // Create target properties + // Support: Safari <=6 - 7 only + // Target should not be a text node (#504, #13143) + this.target = ( src.target && src.target.nodeType === 3 ) ? + src.target.parentNode : + src.target; + + this.currentTarget = src.currentTarget; + this.relatedTarget = src.relatedTarget; + + // Event type + } else { + this.type = src; + } + + // Put explicitly provided properties onto the event object + if ( props ) { + jQuery.extend( this, props ); + } + + // Create a timestamp if incoming event doesn't have one + this.timeStamp = src && src.timeStamp || jQuery.now(); + + // Mark it as fixed + this[ jQuery.expando ] = true; +}; + +// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding +// https://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html +jQuery.Event.prototype = { + constructor: jQuery.Event, + isDefaultPrevented: returnFalse, + isPropagationStopped: returnFalse, + isImmediatePropagationStopped: returnFalse, + isSimulated: false, + + preventDefault: function() { + var e = this.originalEvent; + + this.isDefaultPrevented = returnTrue; + + if ( e && !this.isSimulated ) { + e.preventDefault(); + } + }, + stopPropagation: function() { + var e = this.originalEvent; + + this.isPropagationStopped = returnTrue; + + if ( e && !this.isSimulated ) { + e.stopPropagation(); + } + }, + stopImmediatePropagation: function() { + var e = this.originalEvent; + + this.isImmediatePropagationStopped = returnTrue; + + if ( e && !this.isSimulated ) { + e.stopImmediatePropagation(); + } + + this.stopPropagation(); + } +}; + +// Includes all common event props including KeyEvent and MouseEvent specific props +jQuery.each( { + altKey: true, + bubbles: true, + cancelable: true, + changedTouches: true, + ctrlKey: true, + detail: true, + eventPhase: true, + metaKey: true, + pageX: true, + pageY: true, + shiftKey: true, + view: true, + "char": true, + charCode: true, + key: true, + keyCode: true, + button: true, + buttons: true, + clientX: true, + clientY: true, + offsetX: true, + offsetY: true, + pointerId: true, + pointerType: true, + screenX: true, + screenY: true, + targetTouches: true, + toElement: true, + touches: true, + + which: function( event ) { + var button = event.button; + + // Add which for key events + if ( event.which == null && rkeyEvent.test( event.type ) ) { + return event.charCode != null ? event.charCode : event.keyCode; + } + + // Add which for click: 1 === left; 2 === middle; 3 === right + if ( !event.which && button !== undefined && rmouseEvent.test( event.type ) ) { + if ( button & 1 ) { + return 1; + } + + if ( button & 2 ) { + return 3; + } + + if ( button & 4 ) { + return 2; + } + + return 0; + } + + return event.which; + } +}, jQuery.event.addProp ); + +// Create mouseenter/leave events using mouseover/out and event-time checks +// so that event delegation works in jQuery. +// Do the same for pointerenter/pointerleave and pointerover/pointerout +// +// Support: Safari 7 only +// Safari sends mouseenter too often; see: +// https://bugs.chromium.org/p/chromium/issues/detail?id=470258 +// for the description of the bug (it existed in older Chrome versions as well). +jQuery.each( { + mouseenter: "mouseover", + mouseleave: "mouseout", + pointerenter: "pointerover", + pointerleave: "pointerout" +}, function( orig, fix ) { + jQuery.event.special[ orig ] = { + delegateType: fix, + bindType: fix, + + handle: function( event ) { + var ret, + target = this, + related = event.relatedTarget, + handleObj = event.handleObj; + + // For mouseenter/leave call the handler if related is outside the target. + // NB: No relatedTarget if the mouse left/entered the browser window + if ( !related || ( related !== target && !jQuery.contains( target, related ) ) ) { + event.type = handleObj.origType; + ret = handleObj.handler.apply( this, arguments ); + event.type = fix; + } + return ret; + } + }; +} ); + +jQuery.fn.extend( { + + on: function( types, selector, data, fn ) { + return on( this, types, selector, data, fn ); + }, + one: function( types, selector, data, fn ) { + return on( this, types, selector, data, fn, 1 ); + }, + off: function( types, selector, fn ) { + var handleObj, type; + if ( types && types.preventDefault && types.handleObj ) { + + // ( event ) dispatched jQuery.Event + handleObj = types.handleObj; + jQuery( types.delegateTarget ).off( + handleObj.namespace ? + handleObj.origType + "." + handleObj.namespace : + handleObj.origType, + handleObj.selector, + handleObj.handler + ); + return this; + } + if ( typeof types === "object" ) { + + // ( types-object [, selector] ) + for ( type in types ) { + this.off( type, selector, types[ type ] ); + } + return this; + } + if ( selector === false || typeof selector === "function" ) { + + // ( types [, fn] ) + fn = selector; + selector = undefined; + } + if ( fn === false ) { + fn = returnFalse; + } + return this.each( function() { + jQuery.event.remove( this, types, fn, selector ); + } ); + } +} ); + + +var + + /* eslint-disable max-len */ + + // See https://github.com/eslint/eslint/issues/3229 + rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([a-z][^\/\0>\x20\t\r\n\f]*)[^>]*)\/>/gi, + + /* eslint-enable */ + + // Support: IE <=10 - 11, Edge 12 - 13 + // In IE/Edge using regex groups here causes severe slowdowns. + // See https://connect.microsoft.com/IE/feedback/details/1736512/ + rnoInnerhtml = /\s*$/g; + +function manipulationTarget( elem, content ) { + if ( jQuery.nodeName( elem, "table" ) && + jQuery.nodeName( content.nodeType !== 11 ? content : content.firstChild, "tr" ) ) { + + return elem.getElementsByTagName( "tbody" )[ 0 ] || elem; + } + + return elem; +} + +// Replace/restore the type attribute of script elements for safe DOM manipulation +function disableScript( elem ) { + elem.type = ( elem.getAttribute( "type" ) !== null ) + "/" + elem.type; + return elem; +} +function restoreScript( elem ) { + var match = rscriptTypeMasked.exec( elem.type ); + + if ( match ) { + elem.type = match[ 1 ]; + } else { + elem.removeAttribute( "type" ); + } + + return elem; +} + +function cloneCopyEvent( src, dest ) { + var i, l, type, pdataOld, pdataCur, udataOld, udataCur, events; + + if ( dest.nodeType !== 1 ) { + return; + } + + // 1. Copy private data: events, handlers, etc. + if ( dataPriv.hasData( src ) ) { + pdataOld = dataPriv.access( src ); + pdataCur = dataPriv.set( dest, pdataOld ); + events = pdataOld.events; + + if ( events ) { + delete pdataCur.handle; + pdataCur.events = {}; + + for ( type in events ) { + for ( i = 0, l = events[ type ].length; i < l; i++ ) { + jQuery.event.add( dest, type, events[ type ][ i ] ); + } + } + } + } + + // 2. Copy user data + if ( dataUser.hasData( src ) ) { + udataOld = dataUser.access( src ); + udataCur = jQuery.extend( {}, udataOld ); + + dataUser.set( dest, udataCur ); + } +} + +// Fix IE bugs, see support tests +function fixInput( src, dest ) { + var nodeName = dest.nodeName.toLowerCase(); + + // Fails to persist the checked state of a cloned checkbox or radio button. + if ( nodeName === "input" && rcheckableType.test( src.type ) ) { + dest.checked = src.checked; + + // Fails to return the selected option to the default selected state when cloning options + } else if ( nodeName === "input" || nodeName === "textarea" ) { + dest.defaultValue = src.defaultValue; + } +} + +function domManip( collection, args, callback, ignored ) { + + // Flatten any nested arrays + args = concat.apply( [], args ); + + var fragment, first, scripts, hasScripts, node, doc, + i = 0, + l = collection.length, + iNoClone = l - 1, + value = args[ 0 ], + isFunction = jQuery.isFunction( value ); + + // We can't cloneNode fragments that contain checked, in WebKit + if ( isFunction || + ( l > 1 && typeof value === "string" && + !support.checkClone && rchecked.test( value ) ) ) { + return collection.each( function( index ) { + var self = collection.eq( index ); + if ( isFunction ) { + args[ 0 ] = value.call( this, index, self.html() ); + } + domManip( self, args, callback, ignored ); + } ); + } + + if ( l ) { + fragment = buildFragment( args, collection[ 0 ].ownerDocument, false, collection, ignored ); + first = fragment.firstChild; + + if ( fragment.childNodes.length === 1 ) { + fragment = first; + } + + // Require either new content or an interest in ignored elements to invoke the callback + if ( first || ignored ) { + scripts = jQuery.map( getAll( fragment, "script" ), disableScript ); + hasScripts = scripts.length; + + // Use the original fragment for the last item + // instead of the first because it can end up + // being emptied incorrectly in certain situations (#8070). + for ( ; i < l; i++ ) { + node = fragment; + + if ( i !== iNoClone ) { + node = jQuery.clone( node, true, true ); + + // Keep references to cloned scripts for later restoration + if ( hasScripts ) { + + // Support: Android <=4.0 only, PhantomJS 1 only + // push.apply(_, arraylike) throws on ancient WebKit + jQuery.merge( scripts, getAll( node, "script" ) ); + } + } + + callback.call( collection[ i ], node, i ); + } + + if ( hasScripts ) { + doc = scripts[ scripts.length - 1 ].ownerDocument; + + // Reenable scripts + jQuery.map( scripts, restoreScript ); + + // Evaluate executable scripts on first document insertion + for ( i = 0; i < hasScripts; i++ ) { + node = scripts[ i ]; + if ( rscriptType.test( node.type || "" ) && + !dataPriv.access( node, "globalEval" ) && + jQuery.contains( doc, node ) ) { + + if ( node.src ) { + + // Optional AJAX dependency, but won't run scripts if not present + if ( jQuery._evalUrl ) { + jQuery._evalUrl( node.src ); + } + } else { + DOMEval( node.textContent.replace( rcleanScript, "" ), doc ); + } + } + } + } + } + } + + return collection; +} + +function remove( elem, selector, keepData ) { + var node, + nodes = selector ? jQuery.filter( selector, elem ) : elem, + i = 0; + + for ( ; ( node = nodes[ i ] ) != null; i++ ) { + if ( !keepData && node.nodeType === 1 ) { + jQuery.cleanData( getAll( node ) ); + } + + if ( node.parentNode ) { + if ( keepData && jQuery.contains( node.ownerDocument, node ) ) { + setGlobalEval( getAll( node, "script" ) ); + } + node.parentNode.removeChild( node ); + } + } + + return elem; +} + +jQuery.extend( { + htmlPrefilter: function( html ) { + return html.replace( rxhtmlTag, "<$1>" ); + }, + + clone: function( elem, dataAndEvents, deepDataAndEvents ) { + var i, l, srcElements, destElements, + clone = elem.cloneNode( true ), + inPage = jQuery.contains( elem.ownerDocument, elem ); + + // Fix IE cloning issues + if ( !support.noCloneChecked && ( elem.nodeType === 1 || elem.nodeType === 11 ) && + !jQuery.isXMLDoc( elem ) ) { + + // We eschew Sizzle here for performance reasons: https://jsperf.com/getall-vs-sizzle/2 + destElements = getAll( clone ); + srcElements = getAll( elem ); + + for ( i = 0, l = srcElements.length; i < l; i++ ) { + fixInput( srcElements[ i ], destElements[ i ] ); + } + } + + // Copy the events from the original to the clone + if ( dataAndEvents ) { + if ( deepDataAndEvents ) { + srcElements = srcElements || getAll( elem ); + destElements = destElements || getAll( clone ); + + for ( i = 0, l = srcElements.length; i < l; i++ ) { + cloneCopyEvent( srcElements[ i ], destElements[ i ] ); + } + } else { + cloneCopyEvent( elem, clone ); + } + } + + // Preserve script evaluation history + destElements = getAll( clone, "script" ); + if ( destElements.length > 0 ) { + setGlobalEval( destElements, !inPage && getAll( elem, "script" ) ); + } + + // Return the cloned set + return clone; + }, + + cleanData: function( elems ) { + var data, elem, type, + special = jQuery.event.special, + i = 0; + + for ( ; ( elem = elems[ i ] ) !== undefined; i++ ) { + if ( acceptData( elem ) ) { + if ( ( data = elem[ dataPriv.expando ] ) ) { + if ( data.events ) { + for ( type in data.events ) { + if ( special[ type ] ) { + jQuery.event.remove( elem, type ); + + // This is a shortcut to avoid jQuery.event.remove's overhead + } else { + jQuery.removeEvent( elem, type, data.handle ); + } + } + } + + // Support: Chrome <=35 - 45+ + // Assign undefined instead of using delete, see Data#remove + elem[ dataPriv.expando ] = undefined; + } + if ( elem[ dataUser.expando ] ) { + + // Support: Chrome <=35 - 45+ + // Assign undefined instead of using delete, see Data#remove + elem[ dataUser.expando ] = undefined; + } + } + } + } +} ); + +jQuery.fn.extend( { + detach: function( selector ) { + return remove( this, selector, true ); + }, + + remove: function( selector ) { + return remove( this, selector ); + }, + + text: function( value ) { + return access( this, function( value ) { + return value === undefined ? + jQuery.text( this ) : + this.empty().each( function() { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + this.textContent = value; + } + } ); + }, null, value, arguments.length ); + }, + + append: function() { + return domManip( this, arguments, function( elem ) { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + var target = manipulationTarget( this, elem ); + target.appendChild( elem ); + } + } ); + }, + + prepend: function() { + return domManip( this, arguments, function( elem ) { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + var target = manipulationTarget( this, elem ); + target.insertBefore( elem, target.firstChild ); + } + } ); + }, + + before: function() { + return domManip( this, arguments, function( elem ) { + if ( this.parentNode ) { + this.parentNode.insertBefore( elem, this ); + } + } ); + }, + + after: function() { + return domManip( this, arguments, function( elem ) { + if ( this.parentNode ) { + this.parentNode.insertBefore( elem, this.nextSibling ); + } + } ); + }, + + empty: function() { + var elem, + i = 0; + + for ( ; ( elem = this[ i ] ) != null; i++ ) { + if ( elem.nodeType === 1 ) { + + // Prevent memory leaks + jQuery.cleanData( getAll( elem, false ) ); + + // Remove any remaining nodes + elem.textContent = ""; + } + } + + return this; + }, + + clone: function( dataAndEvents, deepDataAndEvents ) { + dataAndEvents = dataAndEvents == null ? false : dataAndEvents; + deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents; + + return this.map( function() { + return jQuery.clone( this, dataAndEvents, deepDataAndEvents ); + } ); + }, + + html: function( value ) { + return access( this, function( value ) { + var elem = this[ 0 ] || {}, + i = 0, + l = this.length; + + if ( value === undefined && elem.nodeType === 1 ) { + return elem.innerHTML; + } + + // See if we can take a shortcut and just use innerHTML + if ( typeof value === "string" && !rnoInnerhtml.test( value ) && + !wrapMap[ ( rtagName.exec( value ) || [ "", "" ] )[ 1 ].toLowerCase() ] ) { + + value = jQuery.htmlPrefilter( value ); + + try { + for ( ; i < l; i++ ) { + elem = this[ i ] || {}; + + // Remove element nodes and prevent memory leaks + if ( elem.nodeType === 1 ) { + jQuery.cleanData( getAll( elem, false ) ); + elem.innerHTML = value; + } + } + + elem = 0; + + // If using innerHTML throws an exception, use the fallback method + } catch ( e ) {} + } + + if ( elem ) { + this.empty().append( value ); + } + }, null, value, arguments.length ); + }, + + replaceWith: function() { + var ignored = []; + + // Make the changes, replacing each non-ignored context element with the new content + return domManip( this, arguments, function( elem ) { + var parent = this.parentNode; + + if ( jQuery.inArray( this, ignored ) < 0 ) { + jQuery.cleanData( getAll( this ) ); + if ( parent ) { + parent.replaceChild( elem, this ); + } + } + + // Force callback invocation + }, ignored ); + } +} ); + +jQuery.each( { + appendTo: "append", + prependTo: "prepend", + insertBefore: "before", + insertAfter: "after", + replaceAll: "replaceWith" +}, function( name, original ) { + jQuery.fn[ name ] = function( selector ) { + var elems, + ret = [], + insert = jQuery( selector ), + last = insert.length - 1, + i = 0; + + for ( ; i <= last; i++ ) { + elems = i === last ? this : this.clone( true ); + jQuery( insert[ i ] )[ original ]( elems ); + + // Support: Android <=4.0 only, PhantomJS 1 only + // .get() because push.apply(_, arraylike) throws on ancient WebKit + push.apply( ret, elems.get() ); + } + + return this.pushStack( ret ); + }; +} ); +var rmargin = ( /^margin/ ); + +var rnumnonpx = new RegExp( "^(" + pnum + ")(?!px)[a-z%]+$", "i" ); + +var getStyles = function( elem ) { + + // Support: IE <=11 only, Firefox <=30 (#15098, #14150) + // IE throws on elements created in popups + // FF meanwhile throws on frame elements through "defaultView.getComputedStyle" + var view = elem.ownerDocument.defaultView; + + if ( !view || !view.opener ) { + view = window; + } + + return view.getComputedStyle( elem ); + }; + + + +( function() { + + // Executing both pixelPosition & boxSizingReliable tests require only one layout + // so they're executed at the same time to save the second computation. + function computeStyleTests() { + + // This is a singleton, we need to execute it only once + if ( !div ) { + return; + } + + div.style.cssText = + "box-sizing:border-box;" + + "position:relative;display:block;" + + "margin:auto;border:1px;padding:1px;" + + "top:1%;width:50%"; + div.innerHTML = ""; + documentElement.appendChild( container ); + + var divStyle = window.getComputedStyle( div ); + pixelPositionVal = divStyle.top !== "1%"; + + // Support: Android 4.0 - 4.3 only, Firefox <=3 - 44 + reliableMarginLeftVal = divStyle.marginLeft === "2px"; + boxSizingReliableVal = divStyle.width === "4px"; + + // Support: Android 4.0 - 4.3 only + // Some styles come back with percentage values, even though they shouldn't + div.style.marginRight = "50%"; + pixelMarginRightVal = divStyle.marginRight === "4px"; + + documentElement.removeChild( container ); + + // Nullify the div so it wouldn't be stored in the memory and + // it will also be a sign that checks already performed + div = null; + } + + var pixelPositionVal, boxSizingReliableVal, pixelMarginRightVal, reliableMarginLeftVal, + container = document.createElement( "div" ), + div = document.createElement( "div" ); + + // Finish early in limited (non-browser) environments + if ( !div.style ) { + return; + } + + // Support: IE <=9 - 11 only + // Style of cloned element affects source element cloned (#8908) + div.style.backgroundClip = "content-box"; + div.cloneNode( true ).style.backgroundClip = ""; + support.clearCloneStyle = div.style.backgroundClip === "content-box"; + + container.style.cssText = "border:0;width:8px;height:0;top:0;left:-9999px;" + + "padding:0;margin-top:1px;position:absolute"; + container.appendChild( div ); + + jQuery.extend( support, { + pixelPosition: function() { + computeStyleTests(); + return pixelPositionVal; + }, + boxSizingReliable: function() { + computeStyleTests(); + return boxSizingReliableVal; + }, + pixelMarginRight: function() { + computeStyleTests(); + return pixelMarginRightVal; + }, + reliableMarginLeft: function() { + computeStyleTests(); + return reliableMarginLeftVal; + } + } ); +} )(); + + +function curCSS( elem, name, computed ) { + var width, minWidth, maxWidth, ret, + style = elem.style; + + computed = computed || getStyles( elem ); + + // Support: IE <=9 only + // getPropertyValue is only needed for .css('filter') (#12537) + if ( computed ) { + ret = computed.getPropertyValue( name ) || computed[ name ]; + + if ( ret === "" && !jQuery.contains( elem.ownerDocument, elem ) ) { + ret = jQuery.style( elem, name ); + } + + // A tribute to the "awesome hack by Dean Edwards" + // Android Browser returns percentage for some values, + // but width seems to be reliably pixels. + // This is against the CSSOM draft spec: + // https://drafts.csswg.org/cssom/#resolved-values + if ( !support.pixelMarginRight() && rnumnonpx.test( ret ) && rmargin.test( name ) ) { + + // Remember the original values + width = style.width; + minWidth = style.minWidth; + maxWidth = style.maxWidth; + + // Put in the new values to get a computed value out + style.minWidth = style.maxWidth = style.width = ret; + ret = computed.width; + + // Revert the changed values + style.width = width; + style.minWidth = minWidth; + style.maxWidth = maxWidth; + } + } + + return ret !== undefined ? + + // Support: IE <=9 - 11 only + // IE returns zIndex value as an integer. + ret + "" : + ret; +} + + +function addGetHookIf( conditionFn, hookFn ) { + + // Define the hook, we'll check on the first run if it's really needed. + return { + get: function() { + if ( conditionFn() ) { + + // Hook not needed (or it's not possible to use it due + // to missing dependency), remove it. + delete this.get; + return; + } + + // Hook needed; redefine it so that the support test is not executed again. + return ( this.get = hookFn ).apply( this, arguments ); + } + }; +} + + +var + + // Swappable if display is none or starts with table + // except "table", "table-cell", or "table-caption" + // See here for display values: https://developer.mozilla.org/en-US/docs/CSS/display + rdisplayswap = /^(none|table(?!-c[ea]).+)/, + cssShow = { position: "absolute", visibility: "hidden", display: "block" }, + cssNormalTransform = { + letterSpacing: "0", + fontWeight: "400" + }, + + cssPrefixes = [ "Webkit", "Moz", "ms" ], + emptyStyle = document.createElement( "div" ).style; + +// Return a css property mapped to a potentially vendor prefixed property +function vendorPropName( name ) { + + // Shortcut for names that are not vendor prefixed + if ( name in emptyStyle ) { + return name; + } + + // Check for vendor prefixed names + var capName = name[ 0 ].toUpperCase() + name.slice( 1 ), + i = cssPrefixes.length; + + while ( i-- ) { + name = cssPrefixes[ i ] + capName; + if ( name in emptyStyle ) { + return name; + } + } +} + +function setPositiveNumber( elem, value, subtract ) { + + // Any relative (+/-) values have already been + // normalized at this point + var matches = rcssNum.exec( value ); + return matches ? + + // Guard against undefined "subtract", e.g., when used as in cssHooks + Math.max( 0, matches[ 2 ] - ( subtract || 0 ) ) + ( matches[ 3 ] || "px" ) : + value; +} + +function augmentWidthOrHeight( elem, name, extra, isBorderBox, styles ) { + var i, + val = 0; + + // If we already have the right measurement, avoid augmentation + if ( extra === ( isBorderBox ? "border" : "content" ) ) { + i = 4; + + // Otherwise initialize for horizontal or vertical properties + } else { + i = name === "width" ? 1 : 0; + } + + for ( ; i < 4; i += 2 ) { + + // Both box models exclude margin, so add it if we want it + if ( extra === "margin" ) { + val += jQuery.css( elem, extra + cssExpand[ i ], true, styles ); + } + + if ( isBorderBox ) { + + // border-box includes padding, so remove it if we want content + if ( extra === "content" ) { + val -= jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); + } + + // At this point, extra isn't border nor margin, so remove border + if ( extra !== "margin" ) { + val -= jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); + } + } else { + + // At this point, extra isn't content, so add padding + val += jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); + + // At this point, extra isn't content nor padding, so add border + if ( extra !== "padding" ) { + val += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); + } + } + } + + return val; +} + +function getWidthOrHeight( elem, name, extra ) { + + // Start with offset property, which is equivalent to the border-box value + var val, + valueIsBorderBox = true, + styles = getStyles( elem ), + isBorderBox = jQuery.css( elem, "boxSizing", false, styles ) === "border-box"; + + // Support: IE <=11 only + // Running getBoundingClientRect on a disconnected node + // in IE throws an error. + if ( elem.getClientRects().length ) { + val = elem.getBoundingClientRect()[ name ]; + } + + // Some non-html elements return undefined for offsetWidth, so check for null/undefined + // svg - https://bugzilla.mozilla.org/show_bug.cgi?id=649285 + // MathML - https://bugzilla.mozilla.org/show_bug.cgi?id=491668 + if ( val <= 0 || val == null ) { + + // Fall back to computed then uncomputed css if necessary + val = curCSS( elem, name, styles ); + if ( val < 0 || val == null ) { + val = elem.style[ name ]; + } + + // Computed unit is not pixels. Stop here and return. + if ( rnumnonpx.test( val ) ) { + return val; + } + + // Check for style in case a browser which returns unreliable values + // for getComputedStyle silently falls back to the reliable elem.style + valueIsBorderBox = isBorderBox && + ( support.boxSizingReliable() || val === elem.style[ name ] ); + + // Normalize "", auto, and prepare for extra + val = parseFloat( val ) || 0; + } + + // Use the active box-sizing model to add/subtract irrelevant styles + return ( val + + augmentWidthOrHeight( + elem, + name, + extra || ( isBorderBox ? "border" : "content" ), + valueIsBorderBox, + styles + ) + ) + "px"; +} + +jQuery.extend( { + + // Add in style property hooks for overriding the default + // behavior of getting and setting a style property + cssHooks: { + opacity: { + get: function( elem, computed ) { + if ( computed ) { + + // We should always get a number back from opacity + var ret = curCSS( elem, "opacity" ); + return ret === "" ? "1" : ret; + } + } + } + }, + + // Don't automatically add "px" to these possibly-unitless properties + cssNumber: { + "animationIterationCount": true, + "columnCount": true, + "fillOpacity": true, + "flexGrow": true, + "flexShrink": true, + "fontWeight": true, + "lineHeight": true, + "opacity": true, + "order": true, + "orphans": true, + "widows": true, + "zIndex": true, + "zoom": true + }, + + // Add in properties whose names you wish to fix before + // setting or getting the value + cssProps: { + "float": "cssFloat" + }, + + // Get and set the style property on a DOM Node + style: function( elem, name, value, extra ) { + + // Don't set styles on text and comment nodes + if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) { + return; + } + + // Make sure that we're working with the right name + var ret, type, hooks, + origName = jQuery.camelCase( name ), + style = elem.style; + + name = jQuery.cssProps[ origName ] || + ( jQuery.cssProps[ origName ] = vendorPropName( origName ) || origName ); + + // Gets hook for the prefixed version, then unprefixed version + hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; + + // Check if we're setting a value + if ( value !== undefined ) { + type = typeof value; + + // Convert "+=" or "-=" to relative numbers (#7345) + if ( type === "string" && ( ret = rcssNum.exec( value ) ) && ret[ 1 ] ) { + value = adjustCSS( elem, name, ret ); + + // Fixes bug #9237 + type = "number"; + } + + // Make sure that null and NaN values aren't set (#7116) + if ( value == null || value !== value ) { + return; + } + + // If a number was passed in, add the unit (except for certain CSS properties) + if ( type === "number" ) { + value += ret && ret[ 3 ] || ( jQuery.cssNumber[ origName ] ? "" : "px" ); + } + + // background-* props affect original clone's values + if ( !support.clearCloneStyle && value === "" && name.indexOf( "background" ) === 0 ) { + style[ name ] = "inherit"; + } + + // If a hook was provided, use that value, otherwise just set the specified value + if ( !hooks || !( "set" in hooks ) || + ( value = hooks.set( elem, value, extra ) ) !== undefined ) { + + style[ name ] = value; + } + + } else { + + // If a hook was provided get the non-computed value from there + if ( hooks && "get" in hooks && + ( ret = hooks.get( elem, false, extra ) ) !== undefined ) { + + return ret; + } + + // Otherwise just get the value from the style object + return style[ name ]; + } + }, + + css: function( elem, name, extra, styles ) { + var val, num, hooks, + origName = jQuery.camelCase( name ); + + // Make sure that we're working with the right name + name = jQuery.cssProps[ origName ] || + ( jQuery.cssProps[ origName ] = vendorPropName( origName ) || origName ); + + // Try prefixed name followed by the unprefixed name + hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; + + // If a hook was provided get the computed value from there + if ( hooks && "get" in hooks ) { + val = hooks.get( elem, true, extra ); + } + + // Otherwise, if a way to get the computed value exists, use that + if ( val === undefined ) { + val = curCSS( elem, name, styles ); + } + + // Convert "normal" to computed value + if ( val === "normal" && name in cssNormalTransform ) { + val = cssNormalTransform[ name ]; + } + + // Make numeric if forced or a qualifier was provided and val looks numeric + if ( extra === "" || extra ) { + num = parseFloat( val ); + return extra === true || isFinite( num ) ? num || 0 : val; + } + return val; + } +} ); + +jQuery.each( [ "height", "width" ], function( i, name ) { + jQuery.cssHooks[ name ] = { + get: function( elem, computed, extra ) { + if ( computed ) { + + // Certain elements can have dimension info if we invisibly show them + // but it must have a current display style that would benefit + return rdisplayswap.test( jQuery.css( elem, "display" ) ) && + + // Support: Safari 8+ + // Table columns in Safari have non-zero offsetWidth & zero + // getBoundingClientRect().width unless display is changed. + // Support: IE <=11 only + // Running getBoundingClientRect on a disconnected node + // in IE throws an error. + ( !elem.getClientRects().length || !elem.getBoundingClientRect().width ) ? + swap( elem, cssShow, function() { + return getWidthOrHeight( elem, name, extra ); + } ) : + getWidthOrHeight( elem, name, extra ); + } + }, + + set: function( elem, value, extra ) { + var matches, + styles = extra && getStyles( elem ), + subtract = extra && augmentWidthOrHeight( + elem, + name, + extra, + jQuery.css( elem, "boxSizing", false, styles ) === "border-box", + styles + ); + + // Convert to pixels if value adjustment is needed + if ( subtract && ( matches = rcssNum.exec( value ) ) && + ( matches[ 3 ] || "px" ) !== "px" ) { + + elem.style[ name ] = value; + value = jQuery.css( elem, name ); + } + + return setPositiveNumber( elem, value, subtract ); + } + }; +} ); + +jQuery.cssHooks.marginLeft = addGetHookIf( support.reliableMarginLeft, + function( elem, computed ) { + if ( computed ) { + return ( parseFloat( curCSS( elem, "marginLeft" ) ) || + elem.getBoundingClientRect().left - + swap( elem, { marginLeft: 0 }, function() { + return elem.getBoundingClientRect().left; + } ) + ) + "px"; + } + } +); + +// These hooks are used by animate to expand properties +jQuery.each( { + margin: "", + padding: "", + border: "Width" +}, function( prefix, suffix ) { + jQuery.cssHooks[ prefix + suffix ] = { + expand: function( value ) { + var i = 0, + expanded = {}, + + // Assumes a single number if not a string + parts = typeof value === "string" ? value.split( " " ) : [ value ]; + + for ( ; i < 4; i++ ) { + expanded[ prefix + cssExpand[ i ] + suffix ] = + parts[ i ] || parts[ i - 2 ] || parts[ 0 ]; + } + + return expanded; + } + }; + + if ( !rmargin.test( prefix ) ) { + jQuery.cssHooks[ prefix + suffix ].set = setPositiveNumber; + } +} ); + +jQuery.fn.extend( { + css: function( name, value ) { + return access( this, function( elem, name, value ) { + var styles, len, + map = {}, + i = 0; + + if ( jQuery.isArray( name ) ) { + styles = getStyles( elem ); + len = name.length; + + for ( ; i < len; i++ ) { + map[ name[ i ] ] = jQuery.css( elem, name[ i ], false, styles ); + } + + return map; + } + + return value !== undefined ? + jQuery.style( elem, name, value ) : + jQuery.css( elem, name ); + }, name, value, arguments.length > 1 ); + } +} ); + + +function Tween( elem, options, prop, end, easing ) { + return new Tween.prototype.init( elem, options, prop, end, easing ); +} +jQuery.Tween = Tween; + +Tween.prototype = { + constructor: Tween, + init: function( elem, options, prop, end, easing, unit ) { + this.elem = elem; + this.prop = prop; + this.easing = easing || jQuery.easing._default; + this.options = options; + this.start = this.now = this.cur(); + this.end = end; + this.unit = unit || ( jQuery.cssNumber[ prop ] ? "" : "px" ); + }, + cur: function() { + var hooks = Tween.propHooks[ this.prop ]; + + return hooks && hooks.get ? + hooks.get( this ) : + Tween.propHooks._default.get( this ); + }, + run: function( percent ) { + var eased, + hooks = Tween.propHooks[ this.prop ]; + + if ( this.options.duration ) { + this.pos = eased = jQuery.easing[ this.easing ]( + percent, this.options.duration * percent, 0, 1, this.options.duration + ); + } else { + this.pos = eased = percent; + } + this.now = ( this.end - this.start ) * eased + this.start; + + if ( this.options.step ) { + this.options.step.call( this.elem, this.now, this ); + } + + if ( hooks && hooks.set ) { + hooks.set( this ); + } else { + Tween.propHooks._default.set( this ); + } + return this; + } +}; + +Tween.prototype.init.prototype = Tween.prototype; + +Tween.propHooks = { + _default: { + get: function( tween ) { + var result; + + // Use a property on the element directly when it is not a DOM element, + // or when there is no matching style property that exists. + if ( tween.elem.nodeType !== 1 || + tween.elem[ tween.prop ] != null && tween.elem.style[ tween.prop ] == null ) { + return tween.elem[ tween.prop ]; + } + + // Passing an empty string as a 3rd parameter to .css will automatically + // attempt a parseFloat and fallback to a string if the parse fails. + // Simple values such as "10px" are parsed to Float; + // complex values such as "rotate(1rad)" are returned as-is. + result = jQuery.css( tween.elem, tween.prop, "" ); + + // Empty strings, null, undefined and "auto" are converted to 0. + return !result || result === "auto" ? 0 : result; + }, + set: function( tween ) { + + // Use step hook for back compat. + // Use cssHook if its there. + // Use .style if available and use plain properties where available. + if ( jQuery.fx.step[ tween.prop ] ) { + jQuery.fx.step[ tween.prop ]( tween ); + } else if ( tween.elem.nodeType === 1 && + ( tween.elem.style[ jQuery.cssProps[ tween.prop ] ] != null || + jQuery.cssHooks[ tween.prop ] ) ) { + jQuery.style( tween.elem, tween.prop, tween.now + tween.unit ); + } else { + tween.elem[ tween.prop ] = tween.now; + } + } + } +}; + +// Support: IE <=9 only +// Panic based approach to setting things on disconnected nodes +Tween.propHooks.scrollTop = Tween.propHooks.scrollLeft = { + set: function( tween ) { + if ( tween.elem.nodeType && tween.elem.parentNode ) { + tween.elem[ tween.prop ] = tween.now; + } + } +}; + +jQuery.easing = { + linear: function( p ) { + return p; + }, + swing: function( p ) { + return 0.5 - Math.cos( p * Math.PI ) / 2; + }, + _default: "swing" +}; + +jQuery.fx = Tween.prototype.init; + +// Back compat <1.8 extension point +jQuery.fx.step = {}; + + + + +var + fxNow, timerId, + rfxtypes = /^(?:toggle|show|hide)$/, + rrun = /queueHooks$/; + +function raf() { + if ( timerId ) { + window.requestAnimationFrame( raf ); + jQuery.fx.tick(); + } +} + +// Animations created synchronously will run synchronously +function createFxNow() { + window.setTimeout( function() { + fxNow = undefined; + } ); + return ( fxNow = jQuery.now() ); +} + +// Generate parameters to create a standard animation +function genFx( type, includeWidth ) { + var which, + i = 0, + attrs = { height: type }; + + // If we include width, step value is 1 to do all cssExpand values, + // otherwise step value is 2 to skip over Left and Right + includeWidth = includeWidth ? 1 : 0; + for ( ; i < 4; i += 2 - includeWidth ) { + which = cssExpand[ i ]; + attrs[ "margin" + which ] = attrs[ "padding" + which ] = type; + } + + if ( includeWidth ) { + attrs.opacity = attrs.width = type; + } + + return attrs; +} + +function createTween( value, prop, animation ) { + var tween, + collection = ( Animation.tweeners[ prop ] || [] ).concat( Animation.tweeners[ "*" ] ), + index = 0, + length = collection.length; + for ( ; index < length; index++ ) { + if ( ( tween = collection[ index ].call( animation, prop, value ) ) ) { + + // We're done with this property + return tween; + } + } +} + +function defaultPrefilter( elem, props, opts ) { + var prop, value, toggle, hooks, oldfire, propTween, restoreDisplay, display, + isBox = "width" in props || "height" in props, + anim = this, + orig = {}, + style = elem.style, + hidden = elem.nodeType && isHiddenWithinTree( elem ), + dataShow = dataPriv.get( elem, "fxshow" ); + + // Queue-skipping animations hijack the fx hooks + if ( !opts.queue ) { + hooks = jQuery._queueHooks( elem, "fx" ); + if ( hooks.unqueued == null ) { + hooks.unqueued = 0; + oldfire = hooks.empty.fire; + hooks.empty.fire = function() { + if ( !hooks.unqueued ) { + oldfire(); + } + }; + } + hooks.unqueued++; + + anim.always( function() { + + // Ensure the complete handler is called before this completes + anim.always( function() { + hooks.unqueued--; + if ( !jQuery.queue( elem, "fx" ).length ) { + hooks.empty.fire(); + } + } ); + } ); + } + + // Detect show/hide animations + for ( prop in props ) { + value = props[ prop ]; + if ( rfxtypes.test( value ) ) { + delete props[ prop ]; + toggle = toggle || value === "toggle"; + if ( value === ( hidden ? "hide" : "show" ) ) { + + // Pretend to be hidden if this is a "show" and + // there is still data from a stopped show/hide + if ( value === "show" && dataShow && dataShow[ prop ] !== undefined ) { + hidden = true; + + // Ignore all other no-op show/hide data + } else { + continue; + } + } + orig[ prop ] = dataShow && dataShow[ prop ] || jQuery.style( elem, prop ); + } + } + + // Bail out if this is a no-op like .hide().hide() + propTween = !jQuery.isEmptyObject( props ); + if ( !propTween && jQuery.isEmptyObject( orig ) ) { + return; + } + + // Restrict "overflow" and "display" styles during box animations + if ( isBox && elem.nodeType === 1 ) { + + // Support: IE <=9 - 11, Edge 12 - 13 + // Record all 3 overflow attributes because IE does not infer the shorthand + // from identically-valued overflowX and overflowY + opts.overflow = [ style.overflow, style.overflowX, style.overflowY ]; + + // Identify a display type, preferring old show/hide data over the CSS cascade + restoreDisplay = dataShow && dataShow.display; + if ( restoreDisplay == null ) { + restoreDisplay = dataPriv.get( elem, "display" ); + } + display = jQuery.css( elem, "display" ); + if ( display === "none" ) { + if ( restoreDisplay ) { + display = restoreDisplay; + } else { + + // Get nonempty value(s) by temporarily forcing visibility + showHide( [ elem ], true ); + restoreDisplay = elem.style.display || restoreDisplay; + display = jQuery.css( elem, "display" ); + showHide( [ elem ] ); + } + } + + // Animate inline elements as inline-block + if ( display === "inline" || display === "inline-block" && restoreDisplay != null ) { + if ( jQuery.css( elem, "float" ) === "none" ) { + + // Restore the original display value at the end of pure show/hide animations + if ( !propTween ) { + anim.done( function() { + style.display = restoreDisplay; + } ); + if ( restoreDisplay == null ) { + display = style.display; + restoreDisplay = display === "none" ? "" : display; + } + } + style.display = "inline-block"; + } + } + } + + if ( opts.overflow ) { + style.overflow = "hidden"; + anim.always( function() { + style.overflow = opts.overflow[ 0 ]; + style.overflowX = opts.overflow[ 1 ]; + style.overflowY = opts.overflow[ 2 ]; + } ); + } + + // Implement show/hide animations + propTween = false; + for ( prop in orig ) { + + // General show/hide setup for this element animation + if ( !propTween ) { + if ( dataShow ) { + if ( "hidden" in dataShow ) { + hidden = dataShow.hidden; + } + } else { + dataShow = dataPriv.access( elem, "fxshow", { display: restoreDisplay } ); + } + + // Store hidden/visible for toggle so `.stop().toggle()` "reverses" + if ( toggle ) { + dataShow.hidden = !hidden; + } + + // Show elements before animating them + if ( hidden ) { + showHide( [ elem ], true ); + } + + /* eslint-disable no-loop-func */ + + anim.done( function() { + + /* eslint-enable no-loop-func */ + + // The final step of a "hide" animation is actually hiding the element + if ( !hidden ) { + showHide( [ elem ] ); + } + dataPriv.remove( elem, "fxshow" ); + for ( prop in orig ) { + jQuery.style( elem, prop, orig[ prop ] ); + } + } ); + } + + // Per-property setup + propTween = createTween( hidden ? dataShow[ prop ] : 0, prop, anim ); + if ( !( prop in dataShow ) ) { + dataShow[ prop ] = propTween.start; + if ( hidden ) { + propTween.end = propTween.start; + propTween.start = 0; + } + } + } +} + +function propFilter( props, specialEasing ) { + var index, name, easing, value, hooks; + + // camelCase, specialEasing and expand cssHook pass + for ( index in props ) { + name = jQuery.camelCase( index ); + easing = specialEasing[ name ]; + value = props[ index ]; + if ( jQuery.isArray( value ) ) { + easing = value[ 1 ]; + value = props[ index ] = value[ 0 ]; + } + + if ( index !== name ) { + props[ name ] = value; + delete props[ index ]; + } + + hooks = jQuery.cssHooks[ name ]; + if ( hooks && "expand" in hooks ) { + value = hooks.expand( value ); + delete props[ name ]; + + // Not quite $.extend, this won't overwrite existing keys. + // Reusing 'index' because we have the correct "name" + for ( index in value ) { + if ( !( index in props ) ) { + props[ index ] = value[ index ]; + specialEasing[ index ] = easing; + } + } + } else { + specialEasing[ name ] = easing; + } + } +} + +function Animation( elem, properties, options ) { + var result, + stopped, + index = 0, + length = Animation.prefilters.length, + deferred = jQuery.Deferred().always( function() { + + // Don't match elem in the :animated selector + delete tick.elem; + } ), + tick = function() { + if ( stopped ) { + return false; + } + var currentTime = fxNow || createFxNow(), + remaining = Math.max( 0, animation.startTime + animation.duration - currentTime ), + + // Support: Android 2.3 only + // Archaic crash bug won't allow us to use `1 - ( 0.5 || 0 )` (#12497) + temp = remaining / animation.duration || 0, + percent = 1 - temp, + index = 0, + length = animation.tweens.length; + + for ( ; index < length; index++ ) { + animation.tweens[ index ].run( percent ); + } + + deferred.notifyWith( elem, [ animation, percent, remaining ] ); + + if ( percent < 1 && length ) { + return remaining; + } else { + deferred.resolveWith( elem, [ animation ] ); + return false; + } + }, + animation = deferred.promise( { + elem: elem, + props: jQuery.extend( {}, properties ), + opts: jQuery.extend( true, { + specialEasing: {}, + easing: jQuery.easing._default + }, options ), + originalProperties: properties, + originalOptions: options, + startTime: fxNow || createFxNow(), + duration: options.duration, + tweens: [], + createTween: function( prop, end ) { + var tween = jQuery.Tween( elem, animation.opts, prop, end, + animation.opts.specialEasing[ prop ] || animation.opts.easing ); + animation.tweens.push( tween ); + return tween; + }, + stop: function( gotoEnd ) { + var index = 0, + + // If we are going to the end, we want to run all the tweens + // otherwise we skip this part + length = gotoEnd ? animation.tweens.length : 0; + if ( stopped ) { + return this; + } + stopped = true; + for ( ; index < length; index++ ) { + animation.tweens[ index ].run( 1 ); + } + + // Resolve when we played the last frame; otherwise, reject + if ( gotoEnd ) { + deferred.notifyWith( elem, [ animation, 1, 0 ] ); + deferred.resolveWith( elem, [ animation, gotoEnd ] ); + } else { + deferred.rejectWith( elem, [ animation, gotoEnd ] ); + } + return this; + } + } ), + props = animation.props; + + propFilter( props, animation.opts.specialEasing ); + + for ( ; index < length; index++ ) { + result = Animation.prefilters[ index ].call( animation, elem, props, animation.opts ); + if ( result ) { + if ( jQuery.isFunction( result.stop ) ) { + jQuery._queueHooks( animation.elem, animation.opts.queue ).stop = + jQuery.proxy( result.stop, result ); + } + return result; + } + } + + jQuery.map( props, createTween, animation ); + + if ( jQuery.isFunction( animation.opts.start ) ) { + animation.opts.start.call( elem, animation ); + } + + jQuery.fx.timer( + jQuery.extend( tick, { + elem: elem, + anim: animation, + queue: animation.opts.queue + } ) + ); + + // attach callbacks from options + return animation.progress( animation.opts.progress ) + .done( animation.opts.done, animation.opts.complete ) + .fail( animation.opts.fail ) + .always( animation.opts.always ); +} + +jQuery.Animation = jQuery.extend( Animation, { + + tweeners: { + "*": [ function( prop, value ) { + var tween = this.createTween( prop, value ); + adjustCSS( tween.elem, prop, rcssNum.exec( value ), tween ); + return tween; + } ] + }, + + tweener: function( props, callback ) { + if ( jQuery.isFunction( props ) ) { + callback = props; + props = [ "*" ]; + } else { + props = props.match( rnothtmlwhite ); + } + + var prop, + index = 0, + length = props.length; + + for ( ; index < length; index++ ) { + prop = props[ index ]; + Animation.tweeners[ prop ] = Animation.tweeners[ prop ] || []; + Animation.tweeners[ prop ].unshift( callback ); + } + }, + + prefilters: [ defaultPrefilter ], + + prefilter: function( callback, prepend ) { + if ( prepend ) { + Animation.prefilters.unshift( callback ); + } else { + Animation.prefilters.push( callback ); + } + } +} ); + +jQuery.speed = function( speed, easing, fn ) { + var opt = speed && typeof speed === "object" ? jQuery.extend( {}, speed ) : { + complete: fn || !fn && easing || + jQuery.isFunction( speed ) && speed, + duration: speed, + easing: fn && easing || easing && !jQuery.isFunction( easing ) && easing + }; + + // Go to the end state if fx are off or if document is hidden + if ( jQuery.fx.off || document.hidden ) { + opt.duration = 0; + + } else { + if ( typeof opt.duration !== "number" ) { + if ( opt.duration in jQuery.fx.speeds ) { + opt.duration = jQuery.fx.speeds[ opt.duration ]; + + } else { + opt.duration = jQuery.fx.speeds._default; + } + } + } + + // Normalize opt.queue - true/undefined/null -> "fx" + if ( opt.queue == null || opt.queue === true ) { + opt.queue = "fx"; + } + + // Queueing + opt.old = opt.complete; + + opt.complete = function() { + if ( jQuery.isFunction( opt.old ) ) { + opt.old.call( this ); + } + + if ( opt.queue ) { + jQuery.dequeue( this, opt.queue ); + } + }; + + return opt; +}; + +jQuery.fn.extend( { + fadeTo: function( speed, to, easing, callback ) { + + // Show any hidden elements after setting opacity to 0 + return this.filter( isHiddenWithinTree ).css( "opacity", 0 ).show() + + // Animate to the value specified + .end().animate( { opacity: to }, speed, easing, callback ); + }, + animate: function( prop, speed, easing, callback ) { + var empty = jQuery.isEmptyObject( prop ), + optall = jQuery.speed( speed, easing, callback ), + doAnimation = function() { + + // Operate on a copy of prop so per-property easing won't be lost + var anim = Animation( this, jQuery.extend( {}, prop ), optall ); + + // Empty animations, or finishing resolves immediately + if ( empty || dataPriv.get( this, "finish" ) ) { + anim.stop( true ); + } + }; + doAnimation.finish = doAnimation; + + return empty || optall.queue === false ? + this.each( doAnimation ) : + this.queue( optall.queue, doAnimation ); + }, + stop: function( type, clearQueue, gotoEnd ) { + var stopQueue = function( hooks ) { + var stop = hooks.stop; + delete hooks.stop; + stop( gotoEnd ); + }; + + if ( typeof type !== "string" ) { + gotoEnd = clearQueue; + clearQueue = type; + type = undefined; + } + if ( clearQueue && type !== false ) { + this.queue( type || "fx", [] ); + } + + return this.each( function() { + var dequeue = true, + index = type != null && type + "queueHooks", + timers = jQuery.timers, + data = dataPriv.get( this ); + + if ( index ) { + if ( data[ index ] && data[ index ].stop ) { + stopQueue( data[ index ] ); + } + } else { + for ( index in data ) { + if ( data[ index ] && data[ index ].stop && rrun.test( index ) ) { + stopQueue( data[ index ] ); + } + } + } + + for ( index = timers.length; index--; ) { + if ( timers[ index ].elem === this && + ( type == null || timers[ index ].queue === type ) ) { + + timers[ index ].anim.stop( gotoEnd ); + dequeue = false; + timers.splice( index, 1 ); + } + } + + // Start the next in the queue if the last step wasn't forced. + // Timers currently will call their complete callbacks, which + // will dequeue but only if they were gotoEnd. + if ( dequeue || !gotoEnd ) { + jQuery.dequeue( this, type ); + } + } ); + }, + finish: function( type ) { + if ( type !== false ) { + type = type || "fx"; + } + return this.each( function() { + var index, + data = dataPriv.get( this ), + queue = data[ type + "queue" ], + hooks = data[ type + "queueHooks" ], + timers = jQuery.timers, + length = queue ? queue.length : 0; + + // Enable finishing flag on private data + data.finish = true; + + // Empty the queue first + jQuery.queue( this, type, [] ); + + if ( hooks && hooks.stop ) { + hooks.stop.call( this, true ); + } + + // Look for any active animations, and finish them + for ( index = timers.length; index--; ) { + if ( timers[ index ].elem === this && timers[ index ].queue === type ) { + timers[ index ].anim.stop( true ); + timers.splice( index, 1 ); + } + } + + // Look for any animations in the old queue and finish them + for ( index = 0; index < length; index++ ) { + if ( queue[ index ] && queue[ index ].finish ) { + queue[ index ].finish.call( this ); + } + } + + // Turn off finishing flag + delete data.finish; + } ); + } +} ); + +jQuery.each( [ "toggle", "show", "hide" ], function( i, name ) { + var cssFn = jQuery.fn[ name ]; + jQuery.fn[ name ] = function( speed, easing, callback ) { + return speed == null || typeof speed === "boolean" ? + cssFn.apply( this, arguments ) : + this.animate( genFx( name, true ), speed, easing, callback ); + }; +} ); + +// Generate shortcuts for custom animations +jQuery.each( { + slideDown: genFx( "show" ), + slideUp: genFx( "hide" ), + slideToggle: genFx( "toggle" ), + fadeIn: { opacity: "show" }, + fadeOut: { opacity: "hide" }, + fadeToggle: { opacity: "toggle" } +}, function( name, props ) { + jQuery.fn[ name ] = function( speed, easing, callback ) { + return this.animate( props, speed, easing, callback ); + }; +} ); + +jQuery.timers = []; +jQuery.fx.tick = function() { + var timer, + i = 0, + timers = jQuery.timers; + + fxNow = jQuery.now(); + + for ( ; i < timers.length; i++ ) { + timer = timers[ i ]; + + // Checks the timer has not already been removed + if ( !timer() && timers[ i ] === timer ) { + timers.splice( i--, 1 ); + } + } + + if ( !timers.length ) { + jQuery.fx.stop(); + } + fxNow = undefined; +}; + +jQuery.fx.timer = function( timer ) { + jQuery.timers.push( timer ); + if ( timer() ) { + jQuery.fx.start(); + } else { + jQuery.timers.pop(); + } +}; + +jQuery.fx.interval = 13; +jQuery.fx.start = function() { + if ( !timerId ) { + timerId = window.requestAnimationFrame ? + window.requestAnimationFrame( raf ) : + window.setInterval( jQuery.fx.tick, jQuery.fx.interval ); + } +}; + +jQuery.fx.stop = function() { + if ( window.cancelAnimationFrame ) { + window.cancelAnimationFrame( timerId ); + } else { + window.clearInterval( timerId ); + } + + timerId = null; +}; + +jQuery.fx.speeds = { + slow: 600, + fast: 200, + + // Default speed + _default: 400 +}; + + +// Based off of the plugin by Clint Helfers, with permission. +// https://web.archive.org/web/20100324014747/http://blindsignals.com/index.php/2009/07/jquery-delay/ +jQuery.fn.delay = function( time, type ) { + time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time; + type = type || "fx"; + + return this.queue( type, function( next, hooks ) { + var timeout = window.setTimeout( next, time ); + hooks.stop = function() { + window.clearTimeout( timeout ); + }; + } ); +}; + + +( function() { + var input = document.createElement( "input" ), + select = document.createElement( "select" ), + opt = select.appendChild( document.createElement( "option" ) ); + + input.type = "checkbox"; + + // Support: Android <=4.3 only + // Default value for a checkbox should be "on" + support.checkOn = input.value !== ""; + + // Support: IE <=11 only + // Must access selectedIndex to make default options select + support.optSelected = opt.selected; + + // Support: IE <=11 only + // An input loses its value after becoming a radio + input = document.createElement( "input" ); + input.value = "t"; + input.type = "radio"; + support.radioValue = input.value === "t"; +} )(); + + +var boolHook, + attrHandle = jQuery.expr.attrHandle; + +jQuery.fn.extend( { + attr: function( name, value ) { + return access( this, jQuery.attr, name, value, arguments.length > 1 ); + }, + + removeAttr: function( name ) { + return this.each( function() { + jQuery.removeAttr( this, name ); + } ); + } +} ); + +jQuery.extend( { + attr: function( elem, name, value ) { + var ret, hooks, + nType = elem.nodeType; + + // Don't get/set attributes on text, comment and attribute nodes + if ( nType === 3 || nType === 8 || nType === 2 ) { + return; + } + + // Fallback to prop when attributes are not supported + if ( typeof elem.getAttribute === "undefined" ) { + return jQuery.prop( elem, name, value ); + } + + // Attribute hooks are determined by the lowercase version + // Grab necessary hook if one is defined + if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) { + hooks = jQuery.attrHooks[ name.toLowerCase() ] || + ( jQuery.expr.match.bool.test( name ) ? boolHook : undefined ); + } + + if ( value !== undefined ) { + if ( value === null ) { + jQuery.removeAttr( elem, name ); + return; + } + + if ( hooks && "set" in hooks && + ( ret = hooks.set( elem, value, name ) ) !== undefined ) { + return ret; + } + + elem.setAttribute( name, value + "" ); + return value; + } + + if ( hooks && "get" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) { + return ret; + } + + ret = jQuery.find.attr( elem, name ); + + // Non-existent attributes return null, we normalize to undefined + return ret == null ? undefined : ret; + }, + + attrHooks: { + type: { + set: function( elem, value ) { + if ( !support.radioValue && value === "radio" && + jQuery.nodeName( elem, "input" ) ) { + var val = elem.value; + elem.setAttribute( "type", value ); + if ( val ) { + elem.value = val; + } + return value; + } + } + } + }, + + removeAttr: function( elem, value ) { + var name, + i = 0, + + // Attribute names can contain non-HTML whitespace characters + // https://html.spec.whatwg.org/multipage/syntax.html#attributes-2 + attrNames = value && value.match( rnothtmlwhite ); + + if ( attrNames && elem.nodeType === 1 ) { + while ( ( name = attrNames[ i++ ] ) ) { + elem.removeAttribute( name ); + } + } + } +} ); + +// Hooks for boolean attributes +boolHook = { + set: function( elem, value, name ) { + if ( value === false ) { + + // Remove boolean attributes when set to false + jQuery.removeAttr( elem, name ); + } else { + elem.setAttribute( name, name ); + } + return name; + } +}; + +jQuery.each( jQuery.expr.match.bool.source.match( /\w+/g ), function( i, name ) { + var getter = attrHandle[ name ] || jQuery.find.attr; + + attrHandle[ name ] = function( elem, name, isXML ) { + var ret, handle, + lowercaseName = name.toLowerCase(); + + if ( !isXML ) { + + // Avoid an infinite loop by temporarily removing this function from the getter + handle = attrHandle[ lowercaseName ]; + attrHandle[ lowercaseName ] = ret; + ret = getter( elem, name, isXML ) != null ? + lowercaseName : + null; + attrHandle[ lowercaseName ] = handle; + } + return ret; + }; +} ); + + + + +var rfocusable = /^(?:input|select|textarea|button)$/i, + rclickable = /^(?:a|area)$/i; + +jQuery.fn.extend( { + prop: function( name, value ) { + return access( this, jQuery.prop, name, value, arguments.length > 1 ); + }, + + removeProp: function( name ) { + return this.each( function() { + delete this[ jQuery.propFix[ name ] || name ]; + } ); + } +} ); + +jQuery.extend( { + prop: function( elem, name, value ) { + var ret, hooks, + nType = elem.nodeType; + + // Don't get/set properties on text, comment and attribute nodes + if ( nType === 3 || nType === 8 || nType === 2 ) { + return; + } + + if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) { + + // Fix name and attach hooks + name = jQuery.propFix[ name ] || name; + hooks = jQuery.propHooks[ name ]; + } + + if ( value !== undefined ) { + if ( hooks && "set" in hooks && + ( ret = hooks.set( elem, value, name ) ) !== undefined ) { + return ret; + } + + return ( elem[ name ] = value ); + } + + if ( hooks && "get" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) { + return ret; + } + + return elem[ name ]; + }, + + propHooks: { + tabIndex: { + get: function( elem ) { + + // Support: IE <=9 - 11 only + // elem.tabIndex doesn't always return the + // correct value when it hasn't been explicitly set + // https://web.archive.org/web/20141116233347/http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/ + // Use proper attribute retrieval(#12072) + var tabindex = jQuery.find.attr( elem, "tabindex" ); + + if ( tabindex ) { + return parseInt( tabindex, 10 ); + } + + if ( + rfocusable.test( elem.nodeName ) || + rclickable.test( elem.nodeName ) && + elem.href + ) { + return 0; + } + + return -1; + } + } + }, + + propFix: { + "for": "htmlFor", + "class": "className" + } +} ); + +// Support: IE <=11 only +// Accessing the selectedIndex property +// forces the browser to respect setting selected +// on the option +// The getter ensures a default option is selected +// when in an optgroup +// eslint rule "no-unused-expressions" is disabled for this code +// since it considers such accessions noop +if ( !support.optSelected ) { + jQuery.propHooks.selected = { + get: function( elem ) { + + /* eslint no-unused-expressions: "off" */ + + var parent = elem.parentNode; + if ( parent && parent.parentNode ) { + parent.parentNode.selectedIndex; + } + return null; + }, + set: function( elem ) { + + /* eslint no-unused-expressions: "off" */ + + var parent = elem.parentNode; + if ( parent ) { + parent.selectedIndex; + + if ( parent.parentNode ) { + parent.parentNode.selectedIndex; + } + } + } + }; +} + +jQuery.each( [ + "tabIndex", + "readOnly", + "maxLength", + "cellSpacing", + "cellPadding", + "rowSpan", + "colSpan", + "useMap", + "frameBorder", + "contentEditable" +], function() { + jQuery.propFix[ this.toLowerCase() ] = this; +} ); + + + + + // Strip and collapse whitespace according to HTML spec + // https://html.spec.whatwg.org/multipage/infrastructure.html#strip-and-collapse-whitespace + function stripAndCollapse( value ) { + var tokens = value.match( rnothtmlwhite ) || []; + return tokens.join( " " ); + } + + +function getClass( elem ) { + return elem.getAttribute && elem.getAttribute( "class" ) || ""; +} + +jQuery.fn.extend( { + addClass: function( value ) { + var classes, elem, cur, curValue, clazz, j, finalValue, + i = 0; + + if ( jQuery.isFunction( value ) ) { + return this.each( function( j ) { + jQuery( this ).addClass( value.call( this, j, getClass( this ) ) ); + } ); + } + + if ( typeof value === "string" && value ) { + classes = value.match( rnothtmlwhite ) || []; + + while ( ( elem = this[ i++ ] ) ) { + curValue = getClass( elem ); + cur = elem.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " ); + + if ( cur ) { + j = 0; + while ( ( clazz = classes[ j++ ] ) ) { + if ( cur.indexOf( " " + clazz + " " ) < 0 ) { + cur += clazz + " "; + } + } + + // Only assign if different to avoid unneeded rendering. + finalValue = stripAndCollapse( cur ); + if ( curValue !== finalValue ) { + elem.setAttribute( "class", finalValue ); + } + } + } + } + + return this; + }, + + removeClass: function( value ) { + var classes, elem, cur, curValue, clazz, j, finalValue, + i = 0; + + if ( jQuery.isFunction( value ) ) { + return this.each( function( j ) { + jQuery( this ).removeClass( value.call( this, j, getClass( this ) ) ); + } ); + } + + if ( !arguments.length ) { + return this.attr( "class", "" ); + } + + if ( typeof value === "string" && value ) { + classes = value.match( rnothtmlwhite ) || []; + + while ( ( elem = this[ i++ ] ) ) { + curValue = getClass( elem ); + + // This expression is here for better compressibility (see addClass) + cur = elem.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " ); + + if ( cur ) { + j = 0; + while ( ( clazz = classes[ j++ ] ) ) { + + // Remove *all* instances + while ( cur.indexOf( " " + clazz + " " ) > -1 ) { + cur = cur.replace( " " + clazz + " ", " " ); + } + } + + // Only assign if different to avoid unneeded rendering. + finalValue = stripAndCollapse( cur ); + if ( curValue !== finalValue ) { + elem.setAttribute( "class", finalValue ); + } + } + } + } + + return this; + }, + + toggleClass: function( value, stateVal ) { + var type = typeof value; + + if ( typeof stateVal === "boolean" && type === "string" ) { + return stateVal ? this.addClass( value ) : this.removeClass( value ); + } + + if ( jQuery.isFunction( value ) ) { + return this.each( function( i ) { + jQuery( this ).toggleClass( + value.call( this, i, getClass( this ), stateVal ), + stateVal + ); + } ); + } + + return this.each( function() { + var className, i, self, classNames; + + if ( type === "string" ) { + + // Toggle individual class names + i = 0; + self = jQuery( this ); + classNames = value.match( rnothtmlwhite ) || []; + + while ( ( className = classNames[ i++ ] ) ) { + + // Check each className given, space separated list + if ( self.hasClass( className ) ) { + self.removeClass( className ); + } else { + self.addClass( className ); + } + } + + // Toggle whole class name + } else if ( value === undefined || type === "boolean" ) { + className = getClass( this ); + if ( className ) { + + // Store className if set + dataPriv.set( this, "__className__", className ); + } + + // If the element has a class name or if we're passed `false`, + // then remove the whole classname (if there was one, the above saved it). + // Otherwise bring back whatever was previously saved (if anything), + // falling back to the empty string if nothing was stored. + if ( this.setAttribute ) { + this.setAttribute( "class", + className || value === false ? + "" : + dataPriv.get( this, "__className__" ) || "" + ); + } + } + } ); + }, + + hasClass: function( selector ) { + var className, elem, + i = 0; + + className = " " + selector + " "; + while ( ( elem = this[ i++ ] ) ) { + if ( elem.nodeType === 1 && + ( " " + stripAndCollapse( getClass( elem ) ) + " " ).indexOf( className ) > -1 ) { + return true; + } + } + + return false; + } +} ); + + + + +var rreturn = /\r/g; + +jQuery.fn.extend( { + val: function( value ) { + var hooks, ret, isFunction, + elem = this[ 0 ]; + + if ( !arguments.length ) { + if ( elem ) { + hooks = jQuery.valHooks[ elem.type ] || + jQuery.valHooks[ elem.nodeName.toLowerCase() ]; + + if ( hooks && + "get" in hooks && + ( ret = hooks.get( elem, "value" ) ) !== undefined + ) { + return ret; + } + + ret = elem.value; + + // Handle most common string cases + if ( typeof ret === "string" ) { + return ret.replace( rreturn, "" ); + } + + // Handle cases where value is null/undef or number + return ret == null ? "" : ret; + } + + return; + } + + isFunction = jQuery.isFunction( value ); + + return this.each( function( i ) { + var val; + + if ( this.nodeType !== 1 ) { + return; + } + + if ( isFunction ) { + val = value.call( this, i, jQuery( this ).val() ); + } else { + val = value; + } + + // Treat null/undefined as ""; convert numbers to string + if ( val == null ) { + val = ""; + + } else if ( typeof val === "number" ) { + val += ""; + + } else if ( jQuery.isArray( val ) ) { + val = jQuery.map( val, function( value ) { + return value == null ? "" : value + ""; + } ); + } + + hooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ]; + + // If set returns undefined, fall back to normal setting + if ( !hooks || !( "set" in hooks ) || hooks.set( this, val, "value" ) === undefined ) { + this.value = val; + } + } ); + } +} ); + +jQuery.extend( { + valHooks: { + option: { + get: function( elem ) { + + var val = jQuery.find.attr( elem, "value" ); + return val != null ? + val : + + // Support: IE <=10 - 11 only + // option.text throws exceptions (#14686, #14858) + // Strip and collapse whitespace + // https://html.spec.whatwg.org/#strip-and-collapse-whitespace + stripAndCollapse( jQuery.text( elem ) ); + } + }, + select: { + get: function( elem ) { + var value, option, i, + options = elem.options, + index = elem.selectedIndex, + one = elem.type === "select-one", + values = one ? null : [], + max = one ? index + 1 : options.length; + + if ( index < 0 ) { + i = max; + + } else { + i = one ? index : 0; + } + + // Loop through all the selected options + for ( ; i < max; i++ ) { + option = options[ i ]; + + // Support: IE <=9 only + // IE8-9 doesn't update selected after form reset (#2551) + if ( ( option.selected || i === index ) && + + // Don't return options that are disabled or in a disabled optgroup + !option.disabled && + ( !option.parentNode.disabled || + !jQuery.nodeName( option.parentNode, "optgroup" ) ) ) { + + // Get the specific value for the option + value = jQuery( option ).val(); + + // We don't need an array for one selects + if ( one ) { + return value; + } + + // Multi-Selects return an array + values.push( value ); + } + } + + return values; + }, + + set: function( elem, value ) { + var optionSet, option, + options = elem.options, + values = jQuery.makeArray( value ), + i = options.length; + + while ( i-- ) { + option = options[ i ]; + + /* eslint-disable no-cond-assign */ + + if ( option.selected = + jQuery.inArray( jQuery.valHooks.option.get( option ), values ) > -1 + ) { + optionSet = true; + } + + /* eslint-enable no-cond-assign */ + } + + // Force browsers to behave consistently when non-matching value is set + if ( !optionSet ) { + elem.selectedIndex = -1; + } + return values; + } + } + } +} ); + +// Radios and checkboxes getter/setter +jQuery.each( [ "radio", "checkbox" ], function() { + jQuery.valHooks[ this ] = { + set: function( elem, value ) { + if ( jQuery.isArray( value ) ) { + return ( elem.checked = jQuery.inArray( jQuery( elem ).val(), value ) > -1 ); + } + } + }; + if ( !support.checkOn ) { + jQuery.valHooks[ this ].get = function( elem ) { + return elem.getAttribute( "value" ) === null ? "on" : elem.value; + }; + } +} ); + + + + +// Return jQuery for attributes-only inclusion + + +var rfocusMorph = /^(?:focusinfocus|focusoutblur)$/; + +jQuery.extend( jQuery.event, { + + trigger: function( event, data, elem, onlyHandlers ) { + + var i, cur, tmp, bubbleType, ontype, handle, special, + eventPath = [ elem || document ], + type = hasOwn.call( event, "type" ) ? event.type : event, + namespaces = hasOwn.call( event, "namespace" ) ? event.namespace.split( "." ) : []; + + cur = tmp = elem = elem || document; + + // Don't do events on text and comment nodes + if ( elem.nodeType === 3 || elem.nodeType === 8 ) { + return; + } + + // focus/blur morphs to focusin/out; ensure we're not firing them right now + if ( rfocusMorph.test( type + jQuery.event.triggered ) ) { + return; + } + + if ( type.indexOf( "." ) > -1 ) { + + // Namespaced trigger; create a regexp to match event type in handle() + namespaces = type.split( "." ); + type = namespaces.shift(); + namespaces.sort(); + } + ontype = type.indexOf( ":" ) < 0 && "on" + type; + + // Caller can pass in a jQuery.Event object, Object, or just an event type string + event = event[ jQuery.expando ] ? + event : + new jQuery.Event( type, typeof event === "object" && event ); + + // Trigger bitmask: & 1 for native handlers; & 2 for jQuery (always true) + event.isTrigger = onlyHandlers ? 2 : 3; + event.namespace = namespaces.join( "." ); + event.rnamespace = event.namespace ? + new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" ) : + null; + + // Clean up the event in case it is being reused + event.result = undefined; + if ( !event.target ) { + event.target = elem; + } + + // Clone any incoming data and prepend the event, creating the handler arg list + data = data == null ? + [ event ] : + jQuery.makeArray( data, [ event ] ); + + // Allow special events to draw outside the lines + special = jQuery.event.special[ type ] || {}; + if ( !onlyHandlers && special.trigger && special.trigger.apply( elem, data ) === false ) { + return; + } + + // Determine event propagation path in advance, per W3C events spec (#9951) + // Bubble up to document, then to window; watch for a global ownerDocument var (#9724) + if ( !onlyHandlers && !special.noBubble && !jQuery.isWindow( elem ) ) { + + bubbleType = special.delegateType || type; + if ( !rfocusMorph.test( bubbleType + type ) ) { + cur = cur.parentNode; + } + for ( ; cur; cur = cur.parentNode ) { + eventPath.push( cur ); + tmp = cur; + } + + // Only add window if we got to document (e.g., not plain obj or detached DOM) + if ( tmp === ( elem.ownerDocument || document ) ) { + eventPath.push( tmp.defaultView || tmp.parentWindow || window ); + } + } + + // Fire handlers on the event path + i = 0; + while ( ( cur = eventPath[ i++ ] ) && !event.isPropagationStopped() ) { + + event.type = i > 1 ? + bubbleType : + special.bindType || type; + + // jQuery handler + handle = ( dataPriv.get( cur, "events" ) || {} )[ event.type ] && + dataPriv.get( cur, "handle" ); + if ( handle ) { + handle.apply( cur, data ); + } + + // Native handler + handle = ontype && cur[ ontype ]; + if ( handle && handle.apply && acceptData( cur ) ) { + event.result = handle.apply( cur, data ); + if ( event.result === false ) { + event.preventDefault(); + } + } + } + event.type = type; + + // If nobody prevented the default action, do it now + if ( !onlyHandlers && !event.isDefaultPrevented() ) { + + if ( ( !special._default || + special._default.apply( eventPath.pop(), data ) === false ) && + acceptData( elem ) ) { + + // Call a native DOM method on the target with the same name as the event. + // Don't do default actions on window, that's where global variables be (#6170) + if ( ontype && jQuery.isFunction( elem[ type ] ) && !jQuery.isWindow( elem ) ) { + + // Don't re-trigger an onFOO event when we call its FOO() method + tmp = elem[ ontype ]; + + if ( tmp ) { + elem[ ontype ] = null; + } + + // Prevent re-triggering of the same event, since we already bubbled it above + jQuery.event.triggered = type; + elem[ type ](); + jQuery.event.triggered = undefined; + + if ( tmp ) { + elem[ ontype ] = tmp; + } + } + } + } + + return event.result; + }, + + // Piggyback on a donor event to simulate a different one + // Used only for `focus(in | out)` events + simulate: function( type, elem, event ) { + var e = jQuery.extend( + new jQuery.Event(), + event, + { + type: type, + isSimulated: true + } + ); + + jQuery.event.trigger( e, null, elem ); + } + +} ); + +jQuery.fn.extend( { + + trigger: function( type, data ) { + return this.each( function() { + jQuery.event.trigger( type, data, this ); + } ); + }, + triggerHandler: function( type, data ) { + var elem = this[ 0 ]; + if ( elem ) { + return jQuery.event.trigger( type, data, elem, true ); + } + } +} ); + + +jQuery.each( ( "blur focus focusin focusout resize scroll click dblclick " + + "mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " + + "change select submit keydown keypress keyup contextmenu" ).split( " " ), + function( i, name ) { + + // Handle event binding + jQuery.fn[ name ] = function( data, fn ) { + return arguments.length > 0 ? + this.on( name, null, data, fn ) : + this.trigger( name ); + }; +} ); + +jQuery.fn.extend( { + hover: function( fnOver, fnOut ) { + return this.mouseenter( fnOver ).mouseleave( fnOut || fnOver ); + } +} ); + + + + +support.focusin = "onfocusin" in window; + + +// Support: Firefox <=44 +// Firefox doesn't have focus(in | out) events +// Related ticket - https://bugzilla.mozilla.org/show_bug.cgi?id=687787 +// +// Support: Chrome <=48 - 49, Safari <=9.0 - 9.1 +// focus(in | out) events fire after focus & blur events, +// which is spec violation - http://www.w3.org/TR/DOM-Level-3-Events/#events-focusevent-event-order +// Related ticket - https://bugs.chromium.org/p/chromium/issues/detail?id=449857 +if ( !support.focusin ) { + jQuery.each( { focus: "focusin", blur: "focusout" }, function( orig, fix ) { + + // Attach a single capturing handler on the document while someone wants focusin/focusout + var handler = function( event ) { + jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ) ); + }; + + jQuery.event.special[ fix ] = { + setup: function() { + var doc = this.ownerDocument || this, + attaches = dataPriv.access( doc, fix ); + + if ( !attaches ) { + doc.addEventListener( orig, handler, true ); + } + dataPriv.access( doc, fix, ( attaches || 0 ) + 1 ); + }, + teardown: function() { + var doc = this.ownerDocument || this, + attaches = dataPriv.access( doc, fix ) - 1; + + if ( !attaches ) { + doc.removeEventListener( orig, handler, true ); + dataPriv.remove( doc, fix ); + + } else { + dataPriv.access( doc, fix, attaches ); + } + } + }; + } ); +} +var location = window.location; + +var nonce = jQuery.now(); + +var rquery = ( /\?/ ); + + + +// Cross-browser xml parsing +jQuery.parseXML = function( data ) { + var xml; + if ( !data || typeof data !== "string" ) { + return null; + } + + // Support: IE 9 - 11 only + // IE throws on parseFromString with invalid input. + try { + xml = ( new window.DOMParser() ).parseFromString( data, "text/xml" ); + } catch ( e ) { + xml = undefined; + } + + if ( !xml || xml.getElementsByTagName( "parsererror" ).length ) { + jQuery.error( "Invalid XML: " + data ); + } + return xml; +}; + + +var + rbracket = /\[\]$/, + rCRLF = /\r?\n/g, + rsubmitterTypes = /^(?:submit|button|image|reset|file)$/i, + rsubmittable = /^(?:input|select|textarea|keygen)/i; + +function buildParams( prefix, obj, traditional, add ) { + var name; + + if ( jQuery.isArray( obj ) ) { + + // Serialize array item. + jQuery.each( obj, function( i, v ) { + if ( traditional || rbracket.test( prefix ) ) { + + // Treat each array item as a scalar. + add( prefix, v ); + + } else { + + // Item is non-scalar (array or object), encode its numeric index. + buildParams( + prefix + "[" + ( typeof v === "object" && v != null ? i : "" ) + "]", + v, + traditional, + add + ); + } + } ); + + } else if ( !traditional && jQuery.type( obj ) === "object" ) { + + // Serialize object item. + for ( name in obj ) { + buildParams( prefix + "[" + name + "]", obj[ name ], traditional, add ); + } + + } else { + + // Serialize scalar item. + add( prefix, obj ); + } +} + +// Serialize an array of form elements or a set of +// key/values into a query string +jQuery.param = function( a, traditional ) { + var prefix, + s = [], + add = function( key, valueOrFunction ) { + + // If value is a function, invoke it and use its return value + var value = jQuery.isFunction( valueOrFunction ) ? + valueOrFunction() : + valueOrFunction; + + s[ s.length ] = encodeURIComponent( key ) + "=" + + encodeURIComponent( value == null ? "" : value ); + }; + + // If an array was passed in, assume that it is an array of form elements. + if ( jQuery.isArray( a ) || ( a.jquery && !jQuery.isPlainObject( a ) ) ) { + + // Serialize the form elements + jQuery.each( a, function() { + add( this.name, this.value ); + } ); + + } else { + + // If traditional, encode the "old" way (the way 1.3.2 or older + // did it), otherwise encode params recursively. + for ( prefix in a ) { + buildParams( prefix, a[ prefix ], traditional, add ); + } + } + + // Return the resulting serialization + return s.join( "&" ); +}; + +jQuery.fn.extend( { + serialize: function() { + return jQuery.param( this.serializeArray() ); + }, + serializeArray: function() { + return this.map( function() { + + // Can add propHook for "elements" to filter or add form elements + var elements = jQuery.prop( this, "elements" ); + return elements ? jQuery.makeArray( elements ) : this; + } ) + .filter( function() { + var type = this.type; + + // Use .is( ":disabled" ) so that fieldset[disabled] works + return this.name && !jQuery( this ).is( ":disabled" ) && + rsubmittable.test( this.nodeName ) && !rsubmitterTypes.test( type ) && + ( this.checked || !rcheckableType.test( type ) ); + } ) + .map( function( i, elem ) { + var val = jQuery( this ).val(); + + if ( val == null ) { + return null; + } + + if ( jQuery.isArray( val ) ) { + return jQuery.map( val, function( val ) { + return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) }; + } ); + } + + return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) }; + } ).get(); + } +} ); + + +var + r20 = /%20/g, + rhash = /#.*$/, + rantiCache = /([?&])_=[^&]*/, + rheaders = /^(.*?):[ \t]*([^\r\n]*)$/mg, + + // #7653, #8125, #8152: local protocol detection + rlocalProtocol = /^(?:about|app|app-storage|.+-extension|file|res|widget):$/, + rnoContent = /^(?:GET|HEAD)$/, + rprotocol = /^\/\//, + + /* Prefilters + * 1) They are useful to introduce custom dataTypes (see ajax/jsonp.js for an example) + * 2) These are called: + * - BEFORE asking for a transport + * - AFTER param serialization (s.data is a string if s.processData is true) + * 3) key is the dataType + * 4) the catchall symbol "*" can be used + * 5) execution will start with transport dataType and THEN continue down to "*" if needed + */ + prefilters = {}, + + /* Transports bindings + * 1) key is the dataType + * 2) the catchall symbol "*" can be used + * 3) selection will start with transport dataType and THEN go to "*" if needed + */ + transports = {}, + + // Avoid comment-prolog char sequence (#10098); must appease lint and evade compression + allTypes = "*/".concat( "*" ), + + // Anchor tag for parsing the document origin + originAnchor = document.createElement( "a" ); + originAnchor.href = location.href; + +// Base "constructor" for jQuery.ajaxPrefilter and jQuery.ajaxTransport +function addToPrefiltersOrTransports( structure ) { + + // dataTypeExpression is optional and defaults to "*" + return function( dataTypeExpression, func ) { + + if ( typeof dataTypeExpression !== "string" ) { + func = dataTypeExpression; + dataTypeExpression = "*"; + } + + var dataType, + i = 0, + dataTypes = dataTypeExpression.toLowerCase().match( rnothtmlwhite ) || []; + + if ( jQuery.isFunction( func ) ) { + + // For each dataType in the dataTypeExpression + while ( ( dataType = dataTypes[ i++ ] ) ) { + + // Prepend if requested + if ( dataType[ 0 ] === "+" ) { + dataType = dataType.slice( 1 ) || "*"; + ( structure[ dataType ] = structure[ dataType ] || [] ).unshift( func ); + + // Otherwise append + } else { + ( structure[ dataType ] = structure[ dataType ] || [] ).push( func ); + } + } + } + }; +} + +// Base inspection function for prefilters and transports +function inspectPrefiltersOrTransports( structure, options, originalOptions, jqXHR ) { + + var inspected = {}, + seekingTransport = ( structure === transports ); + + function inspect( dataType ) { + var selected; + inspected[ dataType ] = true; + jQuery.each( structure[ dataType ] || [], function( _, prefilterOrFactory ) { + var dataTypeOrTransport = prefilterOrFactory( options, originalOptions, jqXHR ); + if ( typeof dataTypeOrTransport === "string" && + !seekingTransport && !inspected[ dataTypeOrTransport ] ) { + + options.dataTypes.unshift( dataTypeOrTransport ); + inspect( dataTypeOrTransport ); + return false; + } else if ( seekingTransport ) { + return !( selected = dataTypeOrTransport ); + } + } ); + return selected; + } + + return inspect( options.dataTypes[ 0 ] ) || !inspected[ "*" ] && inspect( "*" ); +} + +// A special extend for ajax options +// that takes "flat" options (not to be deep extended) +// Fixes #9887 +function ajaxExtend( target, src ) { + var key, deep, + flatOptions = jQuery.ajaxSettings.flatOptions || {}; + + for ( key in src ) { + if ( src[ key ] !== undefined ) { + ( flatOptions[ key ] ? target : ( deep || ( deep = {} ) ) )[ key ] = src[ key ]; + } + } + if ( deep ) { + jQuery.extend( true, target, deep ); + } + + return target; +} + +/* Handles responses to an ajax request: + * - finds the right dataType (mediates between content-type and expected dataType) + * - returns the corresponding response + */ +function ajaxHandleResponses( s, jqXHR, responses ) { + + var ct, type, finalDataType, firstDataType, + contents = s.contents, + dataTypes = s.dataTypes; + + // Remove auto dataType and get content-type in the process + while ( dataTypes[ 0 ] === "*" ) { + dataTypes.shift(); + if ( ct === undefined ) { + ct = s.mimeType || jqXHR.getResponseHeader( "Content-Type" ); + } + } + + // Check if we're dealing with a known content-type + if ( ct ) { + for ( type in contents ) { + if ( contents[ type ] && contents[ type ].test( ct ) ) { + dataTypes.unshift( type ); + break; + } + } + } + + // Check to see if we have a response for the expected dataType + if ( dataTypes[ 0 ] in responses ) { + finalDataType = dataTypes[ 0 ]; + } else { + + // Try convertible dataTypes + for ( type in responses ) { + if ( !dataTypes[ 0 ] || s.converters[ type + " " + dataTypes[ 0 ] ] ) { + finalDataType = type; + break; + } + if ( !firstDataType ) { + firstDataType = type; + } + } + + // Or just use first one + finalDataType = finalDataType || firstDataType; + } + + // If we found a dataType + // We add the dataType to the list if needed + // and return the corresponding response + if ( finalDataType ) { + if ( finalDataType !== dataTypes[ 0 ] ) { + dataTypes.unshift( finalDataType ); + } + return responses[ finalDataType ]; + } +} + +/* Chain conversions given the request and the original response + * Also sets the responseXXX fields on the jqXHR instance + */ +function ajaxConvert( s, response, jqXHR, isSuccess ) { + var conv2, current, conv, tmp, prev, + converters = {}, + + // Work with a copy of dataTypes in case we need to modify it for conversion + dataTypes = s.dataTypes.slice(); + + // Create converters map with lowercased keys + if ( dataTypes[ 1 ] ) { + for ( conv in s.converters ) { + converters[ conv.toLowerCase() ] = s.converters[ conv ]; + } + } + + current = dataTypes.shift(); + + // Convert to each sequential dataType + while ( current ) { + + if ( s.responseFields[ current ] ) { + jqXHR[ s.responseFields[ current ] ] = response; + } + + // Apply the dataFilter if provided + if ( !prev && isSuccess && s.dataFilter ) { + response = s.dataFilter( response, s.dataType ); + } + + prev = current; + current = dataTypes.shift(); + + if ( current ) { + + // There's only work to do if current dataType is non-auto + if ( current === "*" ) { + + current = prev; + + // Convert response if prev dataType is non-auto and differs from current + } else if ( prev !== "*" && prev !== current ) { + + // Seek a direct converter + conv = converters[ prev + " " + current ] || converters[ "* " + current ]; + + // If none found, seek a pair + if ( !conv ) { + for ( conv2 in converters ) { + + // If conv2 outputs current + tmp = conv2.split( " " ); + if ( tmp[ 1 ] === current ) { + + // If prev can be converted to accepted input + conv = converters[ prev + " " + tmp[ 0 ] ] || + converters[ "* " + tmp[ 0 ] ]; + if ( conv ) { + + // Condense equivalence converters + if ( conv === true ) { + conv = converters[ conv2 ]; + + // Otherwise, insert the intermediate dataType + } else if ( converters[ conv2 ] !== true ) { + current = tmp[ 0 ]; + dataTypes.unshift( tmp[ 1 ] ); + } + break; + } + } + } + } + + // Apply converter (if not an equivalence) + if ( conv !== true ) { + + // Unless errors are allowed to bubble, catch and return them + if ( conv && s.throws ) { + response = conv( response ); + } else { + try { + response = conv( response ); + } catch ( e ) { + return { + state: "parsererror", + error: conv ? e : "No conversion from " + prev + " to " + current + }; + } + } + } + } + } + } + + return { state: "success", data: response }; +} + +jQuery.extend( { + + // Counter for holding the number of active queries + active: 0, + + // Last-Modified header cache for next request + lastModified: {}, + etag: {}, + + ajaxSettings: { + url: location.href, + type: "GET", + isLocal: rlocalProtocol.test( location.protocol ), + global: true, + processData: true, + async: true, + contentType: "application/x-www-form-urlencoded; charset=UTF-8", + + /* + timeout: 0, + data: null, + dataType: null, + username: null, + password: null, + cache: null, + throws: false, + traditional: false, + headers: {}, + */ + + accepts: { + "*": allTypes, + text: "text/plain", + html: "text/html", + xml: "application/xml, text/xml", + json: "application/json, text/javascript" + }, + + contents: { + xml: /\bxml\b/, + html: /\bhtml/, + json: /\bjson\b/ + }, + + responseFields: { + xml: "responseXML", + text: "responseText", + json: "responseJSON" + }, + + // Data converters + // Keys separate source (or catchall "*") and destination types with a single space + converters: { + + // Convert anything to text + "* text": String, + + // Text to html (true = no transformation) + "text html": true, + + // Evaluate text as a json expression + "text json": JSON.parse, + + // Parse text as xml + "text xml": jQuery.parseXML + }, + + // For options that shouldn't be deep extended: + // you can add your own custom options here if + // and when you create one that shouldn't be + // deep extended (see ajaxExtend) + flatOptions: { + url: true, + context: true + } + }, + + // Creates a full fledged settings object into target + // with both ajaxSettings and settings fields. + // If target is omitted, writes into ajaxSettings. + ajaxSetup: function( target, settings ) { + return settings ? + + // Building a settings object + ajaxExtend( ajaxExtend( target, jQuery.ajaxSettings ), settings ) : + + // Extending ajaxSettings + ajaxExtend( jQuery.ajaxSettings, target ); + }, + + ajaxPrefilter: addToPrefiltersOrTransports( prefilters ), + ajaxTransport: addToPrefiltersOrTransports( transports ), + + // Main method + ajax: function( url, options ) { + + // If url is an object, simulate pre-1.5 signature + if ( typeof url === "object" ) { + options = url; + url = undefined; + } + + // Force options to be an object + options = options || {}; + + var transport, + + // URL without anti-cache param + cacheURL, + + // Response headers + responseHeadersString, + responseHeaders, + + // timeout handle + timeoutTimer, + + // Url cleanup var + urlAnchor, + + // Request state (becomes false upon send and true upon completion) + completed, + + // To know if global events are to be dispatched + fireGlobals, + + // Loop variable + i, + + // uncached part of the url + uncached, + + // Create the final options object + s = jQuery.ajaxSetup( {}, options ), + + // Callbacks context + callbackContext = s.context || s, + + // Context for global events is callbackContext if it is a DOM node or jQuery collection + globalEventContext = s.context && + ( callbackContext.nodeType || callbackContext.jquery ) ? + jQuery( callbackContext ) : + jQuery.event, + + // Deferreds + deferred = jQuery.Deferred(), + completeDeferred = jQuery.Callbacks( "once memory" ), + + // Status-dependent callbacks + statusCode = s.statusCode || {}, + + // Headers (they are sent all at once) + requestHeaders = {}, + requestHeadersNames = {}, + + // Default abort message + strAbort = "canceled", + + // Fake xhr + jqXHR = { + readyState: 0, + + // Builds headers hashtable if needed + getResponseHeader: function( key ) { + var match; + if ( completed ) { + if ( !responseHeaders ) { + responseHeaders = {}; + while ( ( match = rheaders.exec( responseHeadersString ) ) ) { + responseHeaders[ match[ 1 ].toLowerCase() ] = match[ 2 ]; + } + } + match = responseHeaders[ key.toLowerCase() ]; + } + return match == null ? null : match; + }, + + // Raw string + getAllResponseHeaders: function() { + return completed ? responseHeadersString : null; + }, + + // Caches the header + setRequestHeader: function( name, value ) { + if ( completed == null ) { + name = requestHeadersNames[ name.toLowerCase() ] = + requestHeadersNames[ name.toLowerCase() ] || name; + requestHeaders[ name ] = value; + } + return this; + }, + + // Overrides response content-type header + overrideMimeType: function( type ) { + if ( completed == null ) { + s.mimeType = type; + } + return this; + }, + + // Status-dependent callbacks + statusCode: function( map ) { + var code; + if ( map ) { + if ( completed ) { + + // Execute the appropriate callbacks + jqXHR.always( map[ jqXHR.status ] ); + } else { + + // Lazy-add the new callbacks in a way that preserves old ones + for ( code in map ) { + statusCode[ code ] = [ statusCode[ code ], map[ code ] ]; + } + } + } + return this; + }, + + // Cancel the request + abort: function( statusText ) { + var finalText = statusText || strAbort; + if ( transport ) { + transport.abort( finalText ); + } + done( 0, finalText ); + return this; + } + }; + + // Attach deferreds + deferred.promise( jqXHR ); + + // Add protocol if not provided (prefilters might expect it) + // Handle falsy url in the settings object (#10093: consistency with old signature) + // We also use the url parameter if available + s.url = ( ( url || s.url || location.href ) + "" ) + .replace( rprotocol, location.protocol + "//" ); + + // Alias method option to type as per ticket #12004 + s.type = options.method || options.type || s.method || s.type; + + // Extract dataTypes list + s.dataTypes = ( s.dataType || "*" ).toLowerCase().match( rnothtmlwhite ) || [ "" ]; + + // A cross-domain request is in order when the origin doesn't match the current origin. + if ( s.crossDomain == null ) { + urlAnchor = document.createElement( "a" ); + + // Support: IE <=8 - 11, Edge 12 - 13 + // IE throws exception on accessing the href property if url is malformed, + // e.g. http://example.com:80x/ + try { + urlAnchor.href = s.url; + + // Support: IE <=8 - 11 only + // Anchor's host property isn't correctly set when s.url is relative + urlAnchor.href = urlAnchor.href; + s.crossDomain = originAnchor.protocol + "//" + originAnchor.host !== + urlAnchor.protocol + "//" + urlAnchor.host; + } catch ( e ) { + + // If there is an error parsing the URL, assume it is crossDomain, + // it can be rejected by the transport if it is invalid + s.crossDomain = true; + } + } + + // Convert data if not already a string + if ( s.data && s.processData && typeof s.data !== "string" ) { + s.data = jQuery.param( s.data, s.traditional ); + } + + // Apply prefilters + inspectPrefiltersOrTransports( prefilters, s, options, jqXHR ); + + // If request was aborted inside a prefilter, stop there + if ( completed ) { + return jqXHR; + } + + // We can fire global events as of now if asked to + // Don't fire events if jQuery.event is undefined in an AMD-usage scenario (#15118) + fireGlobals = jQuery.event && s.global; + + // Watch for a new set of requests + if ( fireGlobals && jQuery.active++ === 0 ) { + jQuery.event.trigger( "ajaxStart" ); + } + + // Uppercase the type + s.type = s.type.toUpperCase(); + + // Determine if request has content + s.hasContent = !rnoContent.test( s.type ); + + // Save the URL in case we're toying with the If-Modified-Since + // and/or If-None-Match header later on + // Remove hash to simplify url manipulation + cacheURL = s.url.replace( rhash, "" ); + + // More options handling for requests with no content + if ( !s.hasContent ) { + + // Remember the hash so we can put it back + uncached = s.url.slice( cacheURL.length ); + + // If data is available, append data to url + if ( s.data ) { + cacheURL += ( rquery.test( cacheURL ) ? "&" : "?" ) + s.data; + + // #9682: remove data so that it's not used in an eventual retry + delete s.data; + } + + // Add or update anti-cache param if needed + if ( s.cache === false ) { + cacheURL = cacheURL.replace( rantiCache, "$1" ); + uncached = ( rquery.test( cacheURL ) ? "&" : "?" ) + "_=" + ( nonce++ ) + uncached; + } + + // Put hash and anti-cache on the URL that will be requested (gh-1732) + s.url = cacheURL + uncached; + + // Change '%20' to '+' if this is encoded form body content (gh-2658) + } else if ( s.data && s.processData && + ( s.contentType || "" ).indexOf( "application/x-www-form-urlencoded" ) === 0 ) { + s.data = s.data.replace( r20, "+" ); + } + + // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. + if ( s.ifModified ) { + if ( jQuery.lastModified[ cacheURL ] ) { + jqXHR.setRequestHeader( "If-Modified-Since", jQuery.lastModified[ cacheURL ] ); + } + if ( jQuery.etag[ cacheURL ] ) { + jqXHR.setRequestHeader( "If-None-Match", jQuery.etag[ cacheURL ] ); + } + } + + // Set the correct header, if data is being sent + if ( s.data && s.hasContent && s.contentType !== false || options.contentType ) { + jqXHR.setRequestHeader( "Content-Type", s.contentType ); + } + + // Set the Accepts header for the server, depending on the dataType + jqXHR.setRequestHeader( + "Accept", + s.dataTypes[ 0 ] && s.accepts[ s.dataTypes[ 0 ] ] ? + s.accepts[ s.dataTypes[ 0 ] ] + + ( s.dataTypes[ 0 ] !== "*" ? ", " + allTypes + "; q=0.01" : "" ) : + s.accepts[ "*" ] + ); + + // Check for headers option + for ( i in s.headers ) { + jqXHR.setRequestHeader( i, s.headers[ i ] ); + } + + // Allow custom headers/mimetypes and early abort + if ( s.beforeSend && + ( s.beforeSend.call( callbackContext, jqXHR, s ) === false || completed ) ) { + + // Abort if not done already and return + return jqXHR.abort(); + } + + // Aborting is no longer a cancellation + strAbort = "abort"; + + // Install callbacks on deferreds + completeDeferred.add( s.complete ); + jqXHR.done( s.success ); + jqXHR.fail( s.error ); + + // Get transport + transport = inspectPrefiltersOrTransports( transports, s, options, jqXHR ); + + // If no transport, we auto-abort + if ( !transport ) { + done( -1, "No Transport" ); + } else { + jqXHR.readyState = 1; + + // Send global event + if ( fireGlobals ) { + globalEventContext.trigger( "ajaxSend", [ jqXHR, s ] ); + } + + // If request was aborted inside ajaxSend, stop there + if ( completed ) { + return jqXHR; + } + + // Timeout + if ( s.async && s.timeout > 0 ) { + timeoutTimer = window.setTimeout( function() { + jqXHR.abort( "timeout" ); + }, s.timeout ); + } + + try { + completed = false; + transport.send( requestHeaders, done ); + } catch ( e ) { + + // Rethrow post-completion exceptions + if ( completed ) { + throw e; + } + + // Propagate others as results + done( -1, e ); + } + } + + // Callback for when everything is done + function done( status, nativeStatusText, responses, headers ) { + var isSuccess, success, error, response, modified, + statusText = nativeStatusText; + + // Ignore repeat invocations + if ( completed ) { + return; + } + + completed = true; + + // Clear timeout if it exists + if ( timeoutTimer ) { + window.clearTimeout( timeoutTimer ); + } + + // Dereference transport for early garbage collection + // (no matter how long the jqXHR object will be used) + transport = undefined; + + // Cache response headers + responseHeadersString = headers || ""; + + // Set readyState + jqXHR.readyState = status > 0 ? 4 : 0; + + // Determine if successful + isSuccess = status >= 200 && status < 300 || status === 304; + + // Get response data + if ( responses ) { + response = ajaxHandleResponses( s, jqXHR, responses ); + } + + // Convert no matter what (that way responseXXX fields are always set) + response = ajaxConvert( s, response, jqXHR, isSuccess ); + + // If successful, handle type chaining + if ( isSuccess ) { + + // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. + if ( s.ifModified ) { + modified = jqXHR.getResponseHeader( "Last-Modified" ); + if ( modified ) { + jQuery.lastModified[ cacheURL ] = modified; + } + modified = jqXHR.getResponseHeader( "etag" ); + if ( modified ) { + jQuery.etag[ cacheURL ] = modified; + } + } + + // if no content + if ( status === 204 || s.type === "HEAD" ) { + statusText = "nocontent"; + + // if not modified + } else if ( status === 304 ) { + statusText = "notmodified"; + + // If we have data, let's convert it + } else { + statusText = response.state; + success = response.data; + error = response.error; + isSuccess = !error; + } + } else { + + // Extract error from statusText and normalize for non-aborts + error = statusText; + if ( status || !statusText ) { + statusText = "error"; + if ( status < 0 ) { + status = 0; + } + } + } + + // Set data for the fake xhr object + jqXHR.status = status; + jqXHR.statusText = ( nativeStatusText || statusText ) + ""; + + // Success/Error + if ( isSuccess ) { + deferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] ); + } else { + deferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] ); + } + + // Status-dependent callbacks + jqXHR.statusCode( statusCode ); + statusCode = undefined; + + if ( fireGlobals ) { + globalEventContext.trigger( isSuccess ? "ajaxSuccess" : "ajaxError", + [ jqXHR, s, isSuccess ? success : error ] ); + } + + // Complete + completeDeferred.fireWith( callbackContext, [ jqXHR, statusText ] ); + + if ( fireGlobals ) { + globalEventContext.trigger( "ajaxComplete", [ jqXHR, s ] ); + + // Handle the global AJAX counter + if ( !( --jQuery.active ) ) { + jQuery.event.trigger( "ajaxStop" ); + } + } + } + + return jqXHR; + }, + + getJSON: function( url, data, callback ) { + return jQuery.get( url, data, callback, "json" ); + }, + + getScript: function( url, callback ) { + return jQuery.get( url, undefined, callback, "script" ); + } +} ); + +jQuery.each( [ "get", "post" ], function( i, method ) { + jQuery[ method ] = function( url, data, callback, type ) { + + // Shift arguments if data argument was omitted + if ( jQuery.isFunction( data ) ) { + type = type || callback; + callback = data; + data = undefined; + } + + // The url can be an options object (which then must have .url) + return jQuery.ajax( jQuery.extend( { + url: url, + type: method, + dataType: type, + data: data, + success: callback + }, jQuery.isPlainObject( url ) && url ) ); + }; +} ); + + +jQuery._evalUrl = function( url ) { + return jQuery.ajax( { + url: url, + + // Make this explicit, since user can override this through ajaxSetup (#11264) + type: "GET", + dataType: "script", + cache: true, + async: false, + global: false, + "throws": true + } ); +}; + + +jQuery.fn.extend( { + wrapAll: function( html ) { + var wrap; + + if ( this[ 0 ] ) { + if ( jQuery.isFunction( html ) ) { + html = html.call( this[ 0 ] ); + } + + // The elements to wrap the target around + wrap = jQuery( html, this[ 0 ].ownerDocument ).eq( 0 ).clone( true ); + + if ( this[ 0 ].parentNode ) { + wrap.insertBefore( this[ 0 ] ); + } + + wrap.map( function() { + var elem = this; + + while ( elem.firstElementChild ) { + elem = elem.firstElementChild; + } + + return elem; + } ).append( this ); + } + + return this; + }, + + wrapInner: function( html ) { + if ( jQuery.isFunction( html ) ) { + return this.each( function( i ) { + jQuery( this ).wrapInner( html.call( this, i ) ); + } ); + } + + return this.each( function() { + var self = jQuery( this ), + contents = self.contents(); + + if ( contents.length ) { + contents.wrapAll( html ); + + } else { + self.append( html ); + } + } ); + }, + + wrap: function( html ) { + var isFunction = jQuery.isFunction( html ); + + return this.each( function( i ) { + jQuery( this ).wrapAll( isFunction ? html.call( this, i ) : html ); + } ); + }, + + unwrap: function( selector ) { + this.parent( selector ).not( "body" ).each( function() { + jQuery( this ).replaceWith( this.childNodes ); + } ); + return this; + } +} ); + + +jQuery.expr.pseudos.hidden = function( elem ) { + return !jQuery.expr.pseudos.visible( elem ); +}; +jQuery.expr.pseudos.visible = function( elem ) { + return !!( elem.offsetWidth || elem.offsetHeight || elem.getClientRects().length ); +}; + + + + +jQuery.ajaxSettings.xhr = function() { + try { + return new window.XMLHttpRequest(); + } catch ( e ) {} +}; + +var xhrSuccessStatus = { + + // File protocol always yields status code 0, assume 200 + 0: 200, + + // Support: IE <=9 only + // #1450: sometimes IE returns 1223 when it should be 204 + 1223: 204 + }, + xhrSupported = jQuery.ajaxSettings.xhr(); + +support.cors = !!xhrSupported && ( "withCredentials" in xhrSupported ); +support.ajax = xhrSupported = !!xhrSupported; + +jQuery.ajaxTransport( function( options ) { + var callback, errorCallback; + + // Cross domain only allowed if supported through XMLHttpRequest + if ( support.cors || xhrSupported && !options.crossDomain ) { + return { + send: function( headers, complete ) { + var i, + xhr = options.xhr(); + + xhr.open( + options.type, + options.url, + options.async, + options.username, + options.password + ); + + // Apply custom fields if provided + if ( options.xhrFields ) { + for ( i in options.xhrFields ) { + xhr[ i ] = options.xhrFields[ i ]; + } + } + + // Override mime type if needed + if ( options.mimeType && xhr.overrideMimeType ) { + xhr.overrideMimeType( options.mimeType ); + } + + // X-Requested-With header + // For cross-domain requests, seeing as conditions for a preflight are + // akin to a jigsaw puzzle, we simply never set it to be sure. + // (it can always be set on a per-request basis or even using ajaxSetup) + // For same-domain requests, won't change header if already provided. + if ( !options.crossDomain && !headers[ "X-Requested-With" ] ) { + headers[ "X-Requested-With" ] = "XMLHttpRequest"; + } + + // Set headers + for ( i in headers ) { + xhr.setRequestHeader( i, headers[ i ] ); + } + + // Callback + callback = function( type ) { + return function() { + if ( callback ) { + callback = errorCallback = xhr.onload = + xhr.onerror = xhr.onabort = xhr.onreadystatechange = null; + + if ( type === "abort" ) { + xhr.abort(); + } else if ( type === "error" ) { + + // Support: IE <=9 only + // On a manual native abort, IE9 throws + // errors on any property access that is not readyState + if ( typeof xhr.status !== "number" ) { + complete( 0, "error" ); + } else { + complete( + + // File: protocol always yields status 0; see #8605, #14207 + xhr.status, + xhr.statusText + ); + } + } else { + complete( + xhrSuccessStatus[ xhr.status ] || xhr.status, + xhr.statusText, + + // Support: IE <=9 only + // IE9 has no XHR2 but throws on binary (trac-11426) + // For XHR2 non-text, let the caller handle it (gh-2498) + ( xhr.responseType || "text" ) !== "text" || + typeof xhr.responseText !== "string" ? + { binary: xhr.response } : + { text: xhr.responseText }, + xhr.getAllResponseHeaders() + ); + } + } + }; + }; + + // Listen to events + xhr.onload = callback(); + errorCallback = xhr.onerror = callback( "error" ); + + // Support: IE 9 only + // Use onreadystatechange to replace onabort + // to handle uncaught aborts + if ( xhr.onabort !== undefined ) { + xhr.onabort = errorCallback; + } else { + xhr.onreadystatechange = function() { + + // Check readyState before timeout as it changes + if ( xhr.readyState === 4 ) { + + // Allow onerror to be called first, + // but that will not handle a native abort + // Also, save errorCallback to a variable + // as xhr.onerror cannot be accessed + window.setTimeout( function() { + if ( callback ) { + errorCallback(); + } + } ); + } + }; + } + + // Create the abort callback + callback = callback( "abort" ); + + try { + + // Do send the request (this may raise an exception) + xhr.send( options.hasContent && options.data || null ); + } catch ( e ) { + + // #14683: Only rethrow if this hasn't been notified as an error yet + if ( callback ) { + throw e; + } + } + }, + + abort: function() { + if ( callback ) { + callback(); + } + } + }; + } +} ); + + + + +// Prevent auto-execution of scripts when no explicit dataType was provided (See gh-2432) +jQuery.ajaxPrefilter( function( s ) { + if ( s.crossDomain ) { + s.contents.script = false; + } +} ); + +// Install script dataType +jQuery.ajaxSetup( { + accepts: { + script: "text/javascript, application/javascript, " + + "application/ecmascript, application/x-ecmascript" + }, + contents: { + script: /\b(?:java|ecma)script\b/ + }, + converters: { + "text script": function( text ) { + jQuery.globalEval( text ); + return text; + } + } +} ); + +// Handle cache's special case and crossDomain +jQuery.ajaxPrefilter( "script", function( s ) { + if ( s.cache === undefined ) { + s.cache = false; + } + if ( s.crossDomain ) { + s.type = "GET"; + } +} ); + +// Bind script tag hack transport +jQuery.ajaxTransport( "script", function( s ) { + + // This transport only deals with cross domain requests + if ( s.crossDomain ) { + var script, callback; + return { + send: function( _, complete ) { + script = jQuery( " + + + + + + + + + + + + + + +
+
+
+
+ +
+

Bookmarks

+
+

Note

+

You need to have configured the phpMyAdmin configuration storage for using bookmarks +feature.

+
+
+

Storing bookmarks

+

Any query you have executed can be stored as a bookmark on the page +where the results are displayed. You will find a button labeled +Bookmark this query just at the end of the page. As soon as you have +stored a bookmark, it is related to the database you run the query on. +You can now access a bookmark dropdown on each page, the query box +appears on for that database.

+
+
+

Variables inside bookmarks

+

You can also have, inside the query, placeholders for variables. +This is done by inserting into the query SQL comments between /* and +*/. Inside the comments, the special strings [VARIABLE{variable-number}] is used. +Be aware that the whole query minus the SQL comments must be +valid by itself, otherwise you won’t be able to store it as a bookmark. +Note also that the text ‘VARIABLE’ is case-sensitive.

+

When you execute the bookmark, everything typed into the Variables +input boxes on the query box page will replace the strings /*[VARIABLE{variable-number}]*/ in +your stored query.

+

Also remember, that everything else inside the /*[VARIABLE{variable-number}]*/ string for +your query will remain the way it is, but will be stripped of the /**/ +chars. So you can use:

+
/*, [VARIABLE1] AS myname */
+
+
+

which will be expanded to

+
, VARIABLE1 as myname
+
+
+

in your query, where VARIABLE1 is the string you entered in the Variable 1 input box.

+

A more complex example. Say you have stored +this query:

+
SELECT Name, Address FROM addresses WHERE 1 /* AND Name LIKE '%[VARIABLE1]%' */
+
+
+

Say, you now enter “phpMyAdmin” as the variable for the stored query, the full +query will be:

+
SELECT Name, Address FROM addresses WHERE 1 AND Name LIKE '%phpMyAdmin%'
+
+
+

NOTE THE ABSENCE OF SPACES inside the /**/ construct. Any spaces +inserted there will be later also inserted as spaces in your query and may lead +to unexpected results especially when using the variable expansion inside of a +“LIKE ‘’” expression.

+
+
+

Browsing a table using a bookmark

+

When a bookmark has the same name as the table, it will be used as the query when browsing +this table.

+ +
+
+ + +
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/php/apps/phpmyadmin49/html/doc/html/charts.html b/php/apps/phpmyadmin49/html/doc/html/charts.html new file mode 100644 index 00000000..d5fbf9c7 --- /dev/null +++ b/php/apps/phpmyadmin49/html/doc/html/charts.html @@ -0,0 +1,300 @@ + + + + + + + + Charts — phpMyAdmin 4.9.1 documentation + + + + + + + + + + + + + + + + + + + +
+
+
+
+ +
+

Charts

+
+

New in version 3.4.0.

+
+

Since phpMyAdmin version 3.4.0, you can easily generate charts from a SQL query +by clicking the “Display chart” link in the “Query results operations” area.

+_images/query_result_operations.png +

A window layer “Display chart” is shown in which you can customize the chart with the following options.

+
    +
  • Chart type: Allows you choose the type of the chart. Supported types are bar charts, column charts, line charts, spline charts, area charts, pie charts and timeline charts (only the chart types applicable for current series selection are offered).
  • +
  • X-axis: Allows to choose the field for the main axis.
  • +
  • Series: Allows to choose series for the chart. You can choose multiple series.
  • +
  • Title: Allows specifying a title for the chart which is displayed above the chart.
  • +
  • X-axis and Y-axis labels: Allows specifying labels for axes.
  • +
  • Start row and number of rows: Allows generating charts only for a specified number of rows of the results set.
  • +
+_images/chart.png +
+

Chart implementation

+

Charts in phpMyAdmin are drawn using jqPlot jQuery library.

+
+
+

Examples

+
+

Pie chart

+

Query results for a simple pie chart can be generated with:

+
SELECT 'Food' AS 'expense',
+   1250 AS 'amount' UNION
+SELECT 'Accommodation', 500 UNION
+SELECT 'Travel', 720 UNION
+SELECT 'Misc', 220
+
+
+

And the result of this query is:

+ ++++ + + + + + + + + + + + + + + + + + + + +
expenseamount
Food1250
Accommodation500
Travel720
Misc220
+

Choosing expense as the X-axis and amount in series:

+_images/pie_chart.png +
+
+

Bar and column chart

+

Both bar charts and column chats support stacking. Upon selecting one of these types a checkbox is displayed to select stacking.

+

Query results for a simple bar or column chart can be generated with:

+
SELECT
+   'ACADEMY DINOSAUR' AS 'title',
+   0.99 AS 'rental_rate',
+   20.99 AS 'replacement_cost' UNION
+SELECT 'ACE GOLDFINGER', 4.99, 12.99 UNION
+SELECT 'ADAPTATION HOLES', 2.99, 18.99 UNION
+SELECT 'AFFAIR PREJUDICE', 2.99, 26.99 UNION
+SELECT 'AFRICAN EGG', 2.99, 22.99
+
+
+

And the result of this query is:

+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
titlerental_ratereplacement_cost
ACADEMY DINOSAUR0.9920.99
ACE GOLDFINGER4.9912.99
ADAPTATION HOLES2.9918.99
AFFAIR PREJUDICE2.9926.99
AFRICAN EGG2.9922.99
+

Choosing title as the X-axis and rental_rate and replacement_cost as series:

+_images/column_chart.png +
+
+

Scatter chart

+

Scatter charts are useful in identifying the movement of one or more variable(s) compared to another variable.

+

Using the same data set from bar and column charts section and choosing replacement_cost as the X-axis and rental_rate in series:

+_images/scatter_chart.png +
+
+

Line, spline and timeline charts

+

These charts can be used to illustrate trends in underlying data. Spline charts draw smooth lines while timeline charts draw X-axis taking the distances between the dates/time into consideration.

+

Query results for a simple line, spline or timeline chart can be generated with:

+
SELECT
+   DATE('2006-01-08') AS 'date',
+   2056 AS 'revenue',
+   1378 AS 'cost' UNION
+SELECT DATE('2006-01-09'), 1898, 2301 UNION
+SELECT DATE('2006-01-15'), 1560, 600 UNION
+SELECT DATE('2006-01-17'), 3457, 1565
+
+
+

And the result of this query is:

+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + +
daterevenuecost
2016-01-0820561378
2006-01-0918982301
2006-01-151560600
2006-01-1734571565
+_images/line_chart.png +_images/spline_chart.png +_images/timeline_chart.png +
+
+
+ + +
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/php/apps/phpmyadmin49/html/doc/html/config.html b/php/apps/phpmyadmin49/html/doc/html/config.html new file mode 100644 index 00000000..e4ba653b --- /dev/null +++ b/php/apps/phpmyadmin49/html/doc/html/config.html @@ -0,0 +1,6211 @@ + + + + + + + + Configuration — phpMyAdmin 4.9.1 documentation + + + + + + + + + + + + + + + + + + +
+
+
+
+ +
+

Configuration

+

All configurable data is placed in config.inc.php in phpMyAdmin’s +toplevel directory. If this file does not exist, please refer to the +Installation section to create one. This file only needs to contain the +parameters you want to change from their corresponding default value in +libraries/config.default.php (this file is not inteded for changes).

+
+

See also

+

Examples for examples of configurations

+
+

If a directive is missing from your file, you can just add another line with +the file. This file is for over-writing the defaults; if you wish to use the +default value there’s no need to add a line here.

+

The parameters which relate to design (like colors) are placed in +themes/themename/layout.inc.php. You might also want to create +config.footer.inc.php and config.header.inc.php files to add +your site specific code to be included on start and end of each page.

+
+

Note

+

Some distributions (eg. Debian or Ubuntu) store config.inc.php in +/etc/phpmyadmin instead of within phpMyAdmin sources.

+
+
+

Warning

+

Mac users should note that if you are on a version before +Mac OS X, PHP does not seem to +like Mac end of lines character (\r). So +ensure you choose the option that allows to use the *nix end of line +character (\n) in your text editor before saving a script you have +modified.

+
+
+

Basic settings

+
+
+$cfg['PmaAbsoluteUri']
+
+++ + + + + + +
Type:string
Default value:''
+
+

Changed in version 4.6.5: This setting was not available in phpMyAdmin 4.6.0 - 4.6.4.

+
+

Sets here the complete URL (with full path) to your phpMyAdmin +installation’s directory. E.g. +https://www.example.net/path_to_your_phpMyAdmin_directory/. Note also +that the URL on most of web servers are case sensitive (even on +Windows). Don’t forget the trailing slash at the end.

+

Starting with version 2.3.0, it is advisable to try leaving this blank. In +most cases phpMyAdmin automatically detects the proper setting. Users of +port forwarding or complex reverse proxy setup might need to set this.

+

A good test is to browse a table, edit a row and save it. There should be +an error message if phpMyAdmin is having trouble auto–detecting the correct +value. If you get an error that this must be set or if the autodetect code +fails to detect your path, please post a bug report on our bug tracker so +we can improve the code.

+ +
+ +
+
+$cfg['PmaNoRelation_DisableWarning']
+
+++ + + + + + +
Type:boolean
Default value:false
+

Starting with version 2.3.0 phpMyAdmin offers a lot of features to +work with master / foreign – tables (see $cfg['Servers'][$i]['pmadb']).

+

If you tried to set this +up and it does not work for you, have a look on the Structure page +of one database where you would like to use it. You will find a link +that will analyze why those features have been disabled.

+

If you do not want to use those features set this variable to true to +stop this message from appearing.

+
+ +
+
+$cfg['AuthLog']
+
+++ + + + + + +
Type:string
Default value:'auto'
+
+

New in version 4.8.0: This is supported since phpMyAdmin 4.8.0.

+
+

Configure authentication logging destination. Failed (or all, depending on +$cfg['AuthLogSuccess']) authentication attempts will be +logged according to this directive:

+
+
auto
+
Let phpMyAdmin automatically choose between syslog and php.
+
syslog
+
Log using syslog, using AUTH facility, on most systems this ends up +in /var/log/auth.log.
+
php
+
Log into PHP error log.
+
sapi
+
Log into PHP SAPI logging.
+
/path/to/file
+
Any other value is treated as a filename and log entries are written there.
+
+
+

Note

+

When logging to a file, make sure its permissions are correctly set +for a web server user, the setup should closely match instructions +described in $cfg['TempDir']:

+
+
+ +
+
+$cfg['AuthLogSuccess']
+
+++ + + + + + +
Type:boolean
Default value:false
+
+

New in version 4.8.0: This is supported since phpMyAdmin 4.8.0.

+
+

Whether to log successful authentication attempts into +$cfg['AuthLog'].

+
+ +
+
+$cfg['SuhosinDisableWarning']
+
+++ + + + + + +
Type:boolean
Default value:false
+

A warning is displayed on the main page if Suhosin is detected.

+

You can set this parameter to true to stop this message from appearing.

+
+ +
+
+$cfg['LoginCookieValidityDisableWarning']
+
+++ + + + + + +
Type:boolean
Default value:false
+

A warning is displayed on the main page if the PHP parameter +session.gc_maxlifetime is lower than cookie validity configured in phpMyAdmin.

+

You can set this parameter to true to stop this message from appearing.

+
+ +
+
+$cfg['ServerLibraryDifference_DisableWarning']
+
+++ + + + + + +
Type:boolean
Default value:false
+
+

Deprecated since version 4.7.0: This setting was removed as the warning has been removed as well.

+
+

A warning is displayed on the main page if there is a difference +between the MySQL library and server version.

+

You can set this parameter to true to stop this message from appearing.

+
+ +
+
+$cfg['ReservedWordDisableWarning']
+
+++ + + + + + +
Type:boolean
Default value:false
+

This warning is displayed on the Structure page of a table if one or more +column names match with words which are MySQL reserved.

+

If you want to turn off this warning, you can set it to true and +warning will no longer be displayed.

+
+ +
+
+$cfg['TranslationWarningThreshold']
+
+++ + + + + + +
Type:integer
Default value:80
+

Show warning about incomplete translations on certain threshold.

+
+ +
+
+$cfg['SendErrorReports']
+
+++ + + + + + +
Type:string
Default value:'ask'
+

Sets the default behavior for JavaScript error reporting.

+

Whenever an error is detected in the JavaScript execution, an error report +may be sent to the phpMyAdmin team if the user agrees.

+

The default setting of 'ask' will ask the user everytime there is a new +error report. However you can set this parameter to 'always' to send error +reports without asking for confirmation or you can set it to 'never' to +never send error reports.

+

This directive is available both in the configuration file and in users +preferences. If the person in charge of a multi-user installation prefers +to disable this feature for all users, a value of 'never' should be +set, and the $cfg['UserprefsDisallow'] directive should +contain 'SendErrorReports' in one of its array values.

+
+ +
+
+$cfg['ConsoleEnterExecutes']
+
+++ + + + + + +
Type:boolean
Default value:false
+

Setting this to true allows the user to execute queries by pressing Enter +instead of Ctrl+Enter. A new line can be inserted by pressing Shift + Enter.

+

The behaviour of the console can be temporarily changed using console’s +settings interface.

+
+ +
+
+$cfg['AllowThirdPartyFraming']
+
+++ + + + + + +
Type:boolean
Default value:false
+

Setting this to true allows phpMyAdmin to be included inside a frame, +and is a potential security hole allowing cross-frame scripting attacks or +clickjacking.

+
+ +
+
+

Server connection settings

+
+
+$cfg['Servers']
+
+++ + + + + + +
Type:array
Default value:one server array with settings listed below
+

Since version 1.4.2, phpMyAdmin supports the administration of multiple +MySQL servers. Therefore, a $cfg['Servers']-array has been +added which contains the login information for the different servers. The +first $cfg['Servers'][$i]['host'] contains the hostname of +the first server, the second $cfg['Servers'][$i]['host'] +the hostname of the second server, etc. In +libraries/config.default.php, there is only one section for server +definition, however you can put as many as you need in +config.inc.php, copy that block or needed parts (you don’t have to +define all settings, just those you need to change).

+
+

Note

+

The $cfg['Servers'] array starts with +$cfg[‘Servers’][1]. Do not use $cfg[‘Servers’][0]. If you want more +than one server, just copy following section (including $i +incrementation) serveral times. There is no need to define full server +array, just define values you need to change.

+
+
+ +
+
+$cfg['Servers'][$i]['host']
+
+++ + + + + + +
Type:string
Default value:'localhost'
+

The hostname or IP address of your $i-th MySQL-server. E.g. +localhost.

+

Possible values are:

+
    +
  • hostname, e.g., 'localhost' or 'mydb.example.org'
  • +
  • IP address, e.g., '127.0.0.1' or '192.168.10.1'
  • +
  • IPv6 address, e.g. 2001:cdba:0000:0000:0000:0000:3257:9652
  • +
  • dot - '.', i.e., use named pipes on windows systems
  • +
  • empty - '', disables this server
  • +
+
+

Note

+

The hostname localhost is handled specially by MySQL and it uses +the socket based connection protocol. To use TCP/IP networking, use an +IP address or hostname such as 127.0.0.1 or db.example.com. You +can configure the path to the socket with +$cfg['Servers'][$i]['socket'].

+
+ +
+ +
+
+$cfg['Servers'][$i]['port']
+
+++ + + + + + +
Type:string
Default value:''
+

The port-number of your $i-th MySQL-server. Default is 3306 (leave +blank).

+
+

Note

+

If you use localhost as the hostname, MySQL ignores this port number +and connects with the socket, so if you want to connect to a port +different from the default port, use 127.0.0.1 or the real hostname +in $cfg['Servers'][$i]['host'].

+
+ +
+ +
+
+$cfg['Servers'][$i]['socket']
+
+++ + + + + + +
Type:string
Default value:''
+

The path to the socket to use. Leave blank for default. To determine +the correct socket, check your MySQL configuration or, using the +mysql command–line client, issue the status command. Among the +resulting information displayed will be the socket used.

+
+

Note

+

This takes effect only if $cfg['Servers'][$i]['host'] is set +to localhost.

+
+ +
+ +
+
+$cfg['Servers'][$i]['ssl']
+
+++ + + + + + +
Type:boolean
Default value:false
+

Whether to enable SSL for the connection between phpMyAdmin and the MySQL +server to secure the connection.

+

When using the 'mysql' extension, +none of the remaining 'ssl...' configuration options apply.

+

We strongly recommend the 'mysqli' extension when using this option.

+ +
+ +
+
+$cfg['Servers'][$i]['ssl_key']
+
+++ + + + + + +
Type:string
Default value:NULL
+

Path to the client key file when using SSL for connecting to the MySQL +server. This is used to authenticate the client to the server.

+

For example:

+
$cfg['Servers'][$i]['ssl_key'] = '/etc/mysql/server-key.pem';
+
+
+ +
+ +
+
+$cfg['Servers'][$i]['ssl_cert']
+
+++ + + + + + +
Type:string
Default value:NULL
+

Path to the client certificate file when using SSL for connecting to the +MySQL server. This is used to authenticate the client to the server.

+ +
+ +
+
+$cfg['Servers'][$i]['ssl_ca']
+
+++ + + + + + +
Type:string
Default value:NULL
+

Path to the CA file when using SSL for connecting to the MySQL server.

+ +
+ +
+
+$cfg['Servers'][$i]['ssl_ca_path']
+
+++ + + + + + +
Type:string
Default value:NULL
+

Directory containing trusted SSL CA certificates in PEM format.

+ +
+ +
+
+$cfg['Servers'][$i]['ssl_ciphers']
+
+++ + + + + + +
Type:string
Default value:NULL
+

List of allowable ciphers for SSL connections to the MySQL server.

+ +
+ +
+
+$cfg['Servers'][$i]['ssl_verify']
+
+++ + + + + + +
Type:boolean
Default value:true
+
+

New in version 4.6.0: This is supported since phpMyAdmin 4.6.0.

+
+

If your PHP install uses the MySQL Native Driver (mysqlnd), your +MySQL server is 5.6 or later, and your SSL certificate is self-signed, +there is a chance your SSL connection will fail due to validation. +Setting this to false will disable the validation check.

+

Since PHP 5.6.0 it also verifies whether server name matches CN of its +certificate. There is currently no way to disable just this check without +disabling complete SSL verification.

+
+

Warning

+

Disabling the certificate verification defeats purpose of using SSL. +This will make the connection vulnerable to man in the middle attacks.

+
+
+

Note

+

This flag only works with PHP 5.6.16 or later.

+
+ +
+ +
+
+$cfg['Servers'][$i]['connect_type']
+
+++ + + + + + +
Type:string
Default value:'tcp'
+
+

Deprecated since version 4.7.0: This setting is no longer used as of 4.7.0, since MySQL decides the +connection type based on host, so it could lead to unexpected results. +Please set $cfg['Servers'][$i]['host'] accordingly +instead.

+
+

What type connection to use with the MySQL server. Your options are +'socket' and 'tcp'. It defaults to tcp as that is nearly guaranteed +to be available on all MySQL servers, while sockets are not supported on +some platforms. To use the socket mode, your MySQL server must be on the +same machine as the Web server.

+
+ +
+
+$cfg['Servers'][$i]['compress']
+
+++ + + + + + +
Type:boolean
Default value:false
+

Whether to use a compressed protocol for the MySQL server connection +or not (experimental).

+
+ +
+
+$cfg['Servers'][$i]['controlhost']
+
+++ + + + + + +
Type:string
Default value:''
+

Permits to use an alternate host to hold the configuration storage +data.

+ +
+ +
+
+$cfg['Servers'][$i]['controlport']
+
+++ + + + + + +
Type:string
Default value:''
+

Permits to use an alternate port to connect to the host that +holds the configuration storage.

+ +
+ +
+
+$cfg['Servers'][$i]['controluser']
+
+++ + + + + + +
Type:string
Default value:''
+
+ +
+
+$cfg['Servers'][$i]['controlpass']
+
+++ + + + + + +
Type:string
Default value:''
+

This special account is used to access phpMyAdmin configuration storage. +You don’t need it in single user case, but if phpMyAdmin is shared it +is recommended to give access to phpMyAdmin configuration storage only to this user +and configure phpMyAdmin to use it. All users will then be able to use +the features without need to have direct access to phpMyAdmin configuration storage.

+
+

Changed in version 2.2.5: those were called stduser and stdpass

+
+ +
+ +
+
+$cfg['Servers'][$i]['control_*']
+
+++ + + + +
Type:mixed
+
+

New in version 4.7.0.

+
+

You can change any MySQL connection setting for control link (used to +access phpMyAdmin configuration storage) using configuration prefixed with control_.

+

This can be used to change any aspect of the control connection, which by +default uses same parameters as the user one.

+

For example you can configure SSL for the control connection:

+
// Enable SSL
+$cfg['Servers'][$i]['control_ssl'] = true;
+// Client secret key
+$cfg['Servers'][$i]['control_ssl_key'] = '../client-key.pem';
+// Client certificate
+$cfg['Servers'][$i]['control_ssl_cert'] = '../client-cert.pem';
+// Server certification authority
+$cfg['Servers'][$i]['control_ssl_ca'] = '../server-ca.pem';
+
+
+ +
+ +
+
+$cfg['Servers'][$i]['auth_type']
+
+++ + + + + + +
Type:string
Default value:'cookie'
+

Whether config or cookie or HTTP or signon authentication should be +used for this server.

+
    +
  • ‘config’ authentication ($auth_type = 'config') is the plain old +way: username and password are stored in config.inc.php.
  • +
  • ‘cookie’ authentication mode ($auth_type = 'cookie') allows you to +log in as any valid MySQL user with the help of cookies.
  • +
  • ‘http’ authentication allows you to log in as any +valid MySQL user via HTTP-Auth.
  • +
  • ‘signon’ authentication mode ($auth_type = 'signon') allows you to +log in from prepared PHP session data or using supplied PHP script.
  • +
+ +
+ +
+
+$cfg['Servers'][$i]['auth_http_realm']
+
+++ + + + + + +
Type:string
Default value:''
+

When using auth_type = http, this field allows to define a custom +HTTP Basic Auth Realm which will be displayed to the user. If not +explicitly specified in your configuration, a string combined of +“phpMyAdmin ” and either $cfg['Servers'][$i]['verbose'] or +$cfg['Servers'][$i]['host'] will be used.

+
+ +
+
+$cfg['Servers'][$i]['user']
+
+++ + + + + + +
Type:string
Default value:'root'
+
+ +
+
+$cfg['Servers'][$i]['password']
+
+++ + + + + + +
Type:string
Default value:''
+

When using $cfg['Servers'][$i]['auth_type'] set to +‘config’, this is the user/password-pair which phpMyAdmin will use to +connect to the MySQL server. This user/password pair is not needed when +HTTP or cookie authentication is used +and should be empty.

+
+ +
+
+$cfg['Servers'][$i]['nopassword']
+
+++ + + + + + +
Type:boolean
Default value:false
+
+

Deprecated since version 4.7.0: This setting was removed as it can produce unexpected results.

+
+

Allow attempt to log in without password when a login with password +fails. This can be used together with http authentication, when +authentication is done some other way and phpMyAdmin gets user name +from auth and uses empty password for connecting to MySQL. Password +login is still tried first, but as fallback, no password method is +tried.

+
+ +
+
+$cfg['Servers'][$i]['only_db']
+
+++ + + + + + +
Type:string or array
Default value:''
+

If set to a (an array of) database name(s), only this (these) +database(s) will be shown to the user. Since phpMyAdmin 2.2.1, +this/these database(s) name(s) may contain MySQL wildcards characters +(“_” and “%”): if you want to use literal instances of these +characters, escape them (I.E. use 'my\_db' and not 'my_db').

+

This setting is an efficient way to lower the server load since the +latter does not need to send MySQL requests to build the available +database list. But it does not replace the privileges rules of the +MySQL database server. If set, it just means only these databases +will be displayed but not that all other databases can’t be used.

+

An example of using more that one database:

+
$cfg['Servers'][$i]['only_db'] = array('db1', 'db2');
+
+
+
+

Changed in version 4.0.0: Previous versions permitted to specify the display order of +the database names via this directive.

+
+
+ +
+
+$cfg['Servers'][$i]['hide_db']
+
+++ + + + + + +
Type:string
Default value:''
+

Regular expression for hiding some databases from unprivileged users. +This only hides them from listing, but a user is still able to access +them (using, for example, the SQL query area). To limit access, use +the MySQL privilege system. For example, to hide all databases +starting with the letter “a”, use

+
$cfg['Servers'][$i]['hide_db'] = '^a';
+
+
+

and to hide both “db1” and “db2” use

+
$cfg['Servers'][$i]['hide_db'] = '^(db1|db2)$';
+
+
+

More information on regular expressions can be found in the PCRE +pattern syntax portion +of the PHP reference manual.

+
+ +
+
+$cfg['Servers'][$i]['verbose']
+
+++ + + + + + +
Type:string
Default value:''
+

Only useful when using phpMyAdmin with multiple server entries. If +set, this string will be displayed instead of the hostname in the +pull-down menu on the main page. This can be useful if you want to +show only certain databases on your system, for example. For HTTP +auth, all non-US-ASCII characters will be stripped.

+
+ +
+
+$cfg['Servers'][$i]['extension']
+
+++ + + + + + +
Type:string
Default value:'mysqli'
+

The PHP MySQL extension to use (mysql or mysqli).

+

It is recommended to use mysqli in all installations.

+
+ +
+
+$cfg['Servers'][$i]['pmadb']
+
+++ + + + + + +
Type:string
Default value:''
+

The name of the database containing the phpMyAdmin configuration +storage.

+

See the phpMyAdmin configuration storage section in this document to see the benefits of +this feature, and for a quick way of creating this database and the needed +tables.

+

If you are the only user of this phpMyAdmin installation, you can use your +current database to store those special tables; in this case, just put your +current database name in $cfg['Servers'][$i]['pmadb']. For a +multi-user installation, set this parameter to the name of your central +database containing the phpMyAdmin configuration storage.

+
+ +
+
+$cfg['Servers'][$i]['bookmarktable']
+
+++ + + + + + +
Type:string or false
Default value:''
+

Since release 2.2.0 phpMyAdmin allows users to bookmark queries. This +can be useful for queries you often run. To allow the usage of this +functionality:

+ +

This feature can be disabled by setting the configuration to false.

+
+ +
+
+$cfg['Servers'][$i]['relation']
+
+++ + + + + + +
Type:string or false
Default value:''
+

Since release 2.2.4 you can describe, in a special ‘relation’ table, +which column is a key in another table (a foreign key). phpMyAdmin +currently uses this to:

+ +

The keys can be numeric or character.

+

To allow the usage of this functionality:

+
    +
  • set up $cfg['Servers'][$i]['pmadb'] and the phpMyAdmin configuration storage
  • +
  • put the relation table name in $cfg['Servers'][$i]['relation']
  • +
  • now as normal user open phpMyAdmin and for each one of your tables +where you want to use this feature, click Structure/Relation view/ +and choose foreign columns.
  • +
+

This feature can be disabled by setting the configuration to false.

+
+

Note

+

In the current version, master_db must be the same as foreign_db. +Those columns have been put in future development of the cross-db +relations.

+
+
+ +
+
+$cfg['Servers'][$i]['table_info']
+
+++ + + + + + +
Type:string or false
Default value:''
+

Since release 2.3.0 you can describe, in a special ‘table_info’ +table, which column is to be displayed as a tool-tip when moving the +cursor over the corresponding key. This configuration variable will +hold the name of this special table. To allow the usage of this +functionality:

+ +

This feature can be disabled by setting the configuration to false.

+ +
+ +
+
+$cfg['Servers'][$i]['table_coords']
+
+++ + + + + + +
Type:string or false
Default value:''
+

The designer feature can save your page layout; by pressing the “Save page” or “Save page as” +button in the expanding designer menu, you can customize the layout and have it loaded the next +time you use the designer. That layout is stored in this table. Furthermore, this table is also +required for using the PDF relation export feature, see +$cfg['Servers'][$i]['pdf_pages'] for additional details.

+
+ +
+
+$cfg['Servers'][$i]['pdf_pages']
+
+++ + + + + + +
Type:string or false
Default value:''
+

Since release 2.3.0 you can have phpMyAdmin create PDF pages +showing the relations between your tables. Further, the designer interface +permits visually managing the relations. To do this it needs two tables +“pdf_pages” (storing information about the available PDF pages) +and “table_coords” (storing coordinates where each table will be placed on +a PDF schema output). You must be using the “relation” feature.

+

To allow the usage of this functionality:

+ +

This feature can be disabled by setting either of the configurations to false.

+ +
+ +
+
+$cfg['Servers'][$i]['column_info']
+
+++ + + + + + +
Type:string or false
Default value:''
+

This part requires a content update! Since release 2.3.0 you can +store comments to describe each column for each table. These will then +be shown on the “printview”.

+

Starting with release 2.5.0, comments are consequently used on the table +property pages and table browse view, showing up as tool-tips above the +column name (properties page) or embedded within the header of table in +browse view. They can also be shown in a table dump. Please see the +relevant configuration directives later on.

+

Also new in release 2.5.0 is a MIME- transformation system which is also +based on the following table structure. See Transformations for +further information. To use the MIME- transformation system, your +column_info table has to have the three new columns ‘mimetype’, +‘transformation’, ‘transformation_options’.

+

Starting with release 4.3.0, a new input-oriented transformation system +has been introduced. Also, backward compatibility code used in the old +transformations system was removed. As a result, an update to column_info +table is necessary for previous transformations and the new input-oriented +transformation system to work. phpMyAdmin will upgrade it automatically +for you by analyzing your current column_info table structure. +However, if something goes wrong with the auto-upgrade then you can +use the SQL script found in ./sql/upgrade_column_info_4_3_0+.sql +to upgrade it manually.

+

To allow the usage of this functionality:

+
    +
  • set up $cfg['Servers'][$i]['pmadb'] and the phpMyAdmin configuration storage

    +
  • +
  • put the table name in $cfg['Servers'][$i]['column_info'] (e.g. +pma__column_info)

    +
  • +
  • to update your PRE-2.5.0 Column_comments table use this: and +remember that the Variable in config.inc.php has been renamed from +$cfg['Servers'][$i]['column_comments'] to +$cfg['Servers'][$i]['column_info']

    +
    ALTER TABLE `pma__column_comments`
    +ADD `mimetype` VARCHAR( 255 ) NOT NULL,
    +ADD `transformation` VARCHAR( 255 ) NOT NULL,
    +ADD `transformation_options` VARCHAR( 255 ) NOT NULL;
    +
    +
    +
  • +
  • to update your PRE-4.3.0 Column_info table manually use this +./sql/upgrade_column_info_4_3_0+.sql SQL script.

    +
  • +
+

This feature can be disabled by setting the configuration to false.

+
+

Note

+

For auto-upgrade functionality to work, your +$cfg['Servers'][$i]['controluser'] must have ALTER privilege on +phpmyadmin database. See the MySQL documentation for GRANT on how to +GRANT privileges to a user.

+
+
+ +
+
+$cfg['Servers'][$i]['history']
+
+++ + + + + + +
Type:string or false
Default value:''
+

Since release 2.5.0 you can store your SQL history, which means all +queries you entered manually into the phpMyAdmin interface. If you don’t +want to use a table-based history, you can use the JavaScript-based +history.

+

Using that, all your history items are deleted when closing the window. +Using $cfg['QueryHistoryMax'] you can specify an amount of +history items you want to have on hold. On every login, this list gets cut +to the maximum amount.

+

The query history is only available if JavaScript is enabled in +your browser.

+

To allow the usage of this functionality:

+ +

This feature can be disabled by setting the configuration to false.

+
+ +
+
+$cfg['Servers'][$i]['recent']
+
+++ + + + + + +
Type:string or false
Default value:''
+

Since release 3.5.0 you can show recently used tables in the +navigation panel. It helps you to jump across table directly, without +the need to select the database, and then select the table. Using +$cfg['NumRecentTables'] you can configure the maximum number +of recent tables shown. When you select a table from the list, it will jump to +the page specified in $cfg['NavigationTreeDefaultTabTable'].

+

Without configuring the storage, you can still access the recently used tables, +but it will disappear after you logout.

+

To allow the usage of this functionality persistently:

+ +

This feature can be disabled by setting the configuration to false.

+
+ +
+
+$cfg['Servers'][$i]['favorite']
+
+++ + + + + + +
Type:string or false
Default value:''
+

Since release 4.2.0 you can show a list of selected tables in the +navigation panel. It helps you to jump to the table directly, without +the need to select the database, and then select the table. When you +select a table from the list, it will jump to the page specified in +$cfg['NavigationTreeDefaultTabTable'].

+

You can add tables to this list or remove tables from it in database +structure page by clicking on the star icons next to table names. Using +$cfg['NumFavoriteTables'] you can configure the maximum +number of favorite tables shown.

+

Without configuring the storage, you can still access the favorite tables, +but it will disappear after you logout.

+

To allow the usage of this functionality persistently:

+ +

This feature can be disabled by setting the configuration to false.

+
+ +
+
+$cfg['Servers'][$i]['table_uiprefs']
+
+++ + + + + + +
Type:string or false
Default value:''
+

Since release 3.5.0 phpMyAdmin can be configured to remember several +things (sorted column $cfg['RememberSorting'], column order, +and column visibility from a database table) for browsing tables. Without +configuring the storage, these features still can be used, but the values will +disappear after you logout.

+

To allow the usage of these functionality persistently:

+ +

This feature can be disabled by setting the configuration to false.

+
+ +
+
+$cfg['Servers'][$i]['users']
+
+++ + + + + + +
Type:string or false
Default value:''
+
+ +
+
+$cfg['Servers'][$i]['usergroups']
+
+++ + + + + + +
Type:string or false
Default value:''
+

Since release 4.1.0 you can create different user groups with menu items +attached to them. Users can be assigned to these groups and the logged in +user would only see menu items configured to the usergroup he is assigned to. +To do this it needs two tables “usergroups” (storing allowed menu items for each +user group) and “users” (storing users and their assignments to user groups).

+

To allow the usage of this functionality:

+ +

This feature can be disabled by setting either of the configurations to false.

+ +
+ +
+
+$cfg['Servers'][$i]['navigationhiding']
+
+++ + + + + + +
Type:string or false
Default value:''
+

Since release 4.1.0 you can hide/show items in the navigation tree.

+

To allow the usage of this functionality:

+ +

This feature can be disabled by setting the configuration to false.

+
+ +
+
+$cfg['Servers'][$i]['central_columns']
+
+++ + + + + + +
Type:string or false
Default value:''
+

Since release 4.3.0 you can have a central list of columns per database. +You can add/remove columns to the list as per your requirement. These columns +in the central list will be available to use while you create a new column for +a table or create a table itself. You can select a column from central list +while creating a new column, it will save you from writing the same column definition +over again or from writing different names for similar column.

+

To allow the usage of this functionality:

+ +

This feature can be disabled by setting the configuration to false.

+
+ +
+
+$cfg['Servers'][$i]['designer_settings']
+
+++ + + + + + +
Type:string or false
Default value:''
+

Since release 4.5.0 your designer settings can be remembered. +Your choice regarding ‘Angular/Direct Links’, ‘Snap to Grid’, ‘Toggle Relation Lines’, +‘Small/Big All’, ‘Move Menu’ and ‘Pin Text’ can be remembered persistently.

+

To allow the usage of this functionality:

+ +

This feature can be disabled by setting the configuration to false.

+
+ +
+
+$cfg['Servers'][$i]['savedsearches']
+
+++ + + + + + +
Type:string or false
Default value:''
+

Since release 4.2.0 you can save and load query-by-example searches from the Database > Query panel.

+

To allow the usage of this functionality:

+ +

This feature can be disabled by setting the configuration to false.

+
+ +
+
+$cfg['Servers'][$i]['export_templates']
+
+++ + + + + + +
Type:string or false
Default value:''
+

Since release 4.5.0 you can save and load export templates.

+

To allow the usage of this functionality:

+ +

This feature can be disabled by setting the configuration to false.

+
+ +
+
+$cfg['Servers'][$i]['tracking']
+
+++ + + + + + +
Type:string or false
Default value:''
+

Since release 3.3.x a tracking mechanism is available. It helps you to +track every SQL command which is +executed by phpMyAdmin. The mechanism supports logging of data +manipulation and data definition statements. After enabling it you can +create versions of tables.

+

The creation of a version has two effects:

+
    +
  • phpMyAdmin saves a snapshot of the table, including structure and +indexes.
  • +
  • phpMyAdmin logs all commands which change the structure and/or data of +the table and links these commands with the version number.
  • +
+

Of course you can view the tracked changes. On the Tracking +page a complete report is available for every version. For the report you +can use filters, for example you can get a list of statements within a date +range. When you want to filter usernames you can enter * for all names or +you enter a list of names separated by ‘,’. In addition you can export the +(filtered) report to a file or to a temporary database.

+

To allow the usage of this functionality:

+ +

This feature can be disabled by setting the configuration to false.

+
+ +
+
+$cfg['Servers'][$i]['tracking_version_auto_create']
+
+++ + + + + + +
Type:boolean
Default value:false
+

Whether the tracking mechanism creates versions for tables and views +automatically.

+

If this is set to true and you create a table or view with

+
    +
  • CREATE TABLE ...
  • +
  • CREATE VIEW ...
  • +
+

and no version exists for it, the mechanism will create a version for +you automatically.

+
+ +
+
+$cfg['Servers'][$i]['tracking_default_statements']
+
+++ + + + + + +
Type:string
Default value:'CREATE TABLE,ALTER TABLE,DROP TABLE,RENAME TABLE,CREATE INDEX,DROP INDEX,INSERT,UPDATE,DELETE,TRUNCATE,REPLACE,CREATE VIEW,ALTER VIEW,DROP VIEW,CREATE DATABASE,ALTER DATABASE,DROP DATABASE'
+

Defines the list of statements the auto-creation uses for new +versions.

+
+ +
+
+$cfg['Servers'][$i]['tracking_add_drop_view']
+
+++ + + + + + +
Type:boolean
Default value:true
+

Whether a DROP VIEW IF EXISTS statement will be added as first line to +the log when creating a view.

+
+ +
+
+$cfg['Servers'][$i]['tracking_add_drop_table']
+
+++ + + + + + +
Type:boolean
Default value:true
+

Whether a DROP TABLE IF EXISTS statement will be added as first line +to the log when creating a table.

+
+ +
+
+$cfg['Servers'][$i]['tracking_add_drop_database']
+
+++ + + + + + +
Type:boolean
Default value:true
+

Whether a DROP DATABASE IF EXISTS statement will be added as first +line to the log when creating a database.

+
+ +
+
+$cfg['Servers'][$i]['userconfig']
+
+++ + + + + + +
Type:string or false
Default value:''
+

Since release 3.4.x phpMyAdmin allows users to set most preferences by +themselves and store them in the database.

+

If you don’t allow for storing preferences in +$cfg['Servers'][$i]['pmadb'], users can still personalize +phpMyAdmin, but settings will be saved in browser’s local storage, or, it +is is unavailable, until the end of session.

+

To allow the usage of this functionality:

+ +

This feature can be disabled by setting the configuration to false.

+
+ +
+
+$cfg['Servers'][$i]['MaxTableUiprefs']
+
+++ + + + + + +
Type:integer
Default value:100
+

Maximum number of rows saved in +$cfg['Servers'][$i]['table_uiprefs'] table.

+

When tables are dropped or renamed, +$cfg['Servers'][$i]['table_uiprefs'] may contain invalid data +(referring to tables which no longer exist). We only keep this number of newest +rows in $cfg['Servers'][$i]['table_uiprefs'] and automatically +delete older rows.

+
+ +
+
+$cfg['Servers'][$i]['SessionTimeZone']
+
+++ + + + + + +
Type:string
Default value:''
+

Sets the time zone used by phpMyAdmin. Leave blank to use the time zone of your +database server. Possible values are explained at +https://dev.mysql.com/doc/refman/5.7/en/time-zone-support.html

+

This is useful when your database server uses a time zone which is different from the +time zone you want to use in phpMyAdmin.

+
+ +
+
+$cfg['Servers'][$i]['AllowRoot']
+
+++ + + + + + +
Type:boolean
Default value:true
+

Whether to allow root access. This is just a shortcut for the +$cfg['Servers'][$i]['AllowDeny']['rules'] below.

+
+ +
+
+$cfg['Servers'][$i]['AllowNoPassword']
+
+++ + + + + + +
Type:boolean
Default value:false
+

Whether to allow logins without a password. The default value of +false for this parameter prevents unintended access to a MySQL +server with was left with an empty password for root or on which an +anonymous (blank) user is defined.

+
+ +
+
+$cfg['Servers'][$i]['AllowDeny']['order']
+
+++ + + + + + +
Type:string
Default value:''
+

If your rule order is empty, then IP +authorization is disabled.

+

If your rule order is set to +'deny,allow' then the system applies all deny rules followed by +allow rules. Access is allowed by default. Any client which does not +match a Deny command or does match an Allow command will be allowed +access to the server.

+

If your rule order is set to 'allow,deny' +then the system applies all allow rules followed by deny rules. Access +is denied by default. Any client which does not match an Allow +directive or does match a Deny directive will be denied access to the +server.

+

If your rule order is set to 'explicit', authorization is +performed in a similar fashion to rule order ‘deny,allow’, with the +added restriction that your host/username combination must be +listed in the allow rules, and not listed in the deny rules. This +is the most secure means of using Allow/Deny rules, and was +available in Apache by specifying allow and deny rules without setting +any order.

+

Please also see $cfg['TrustedProxies'] for +detecting IP address behind proxies.

+
+ +
+
+$cfg['Servers'][$i]['AllowDeny']['rules']
+
+++ + + + + + +
Type:array of strings
Default value:array()
+

The general format for the rules is as such:

+
<'allow' | 'deny'> <username> [from] <ipmask>
+
+
+

If you wish to match all users, it is possible to use a '%' as a +wildcard in the username field.

+

There are a few shortcuts you can +use in the ipmask field as well (please note that those containing +SERVER_ADDRESS might not be available on all webservers):

+
'all' -> 0.0.0.0/0
+'localhost' -> 127.0.0.1/8
+'localnetA' -> SERVER_ADDRESS/8
+'localnetB' -> SERVER_ADDRESS/16
+'localnetC' -> SERVER_ADDRESS/24
+
+
+

Having an empty rule list is equivalent to either using 'allow % +from all' if your rule order is set to 'deny,allow' or 'deny % +from all' if your rule order is set to 'allow,deny' or +'explicit'.

+

For the IP address matching +system, the following work:

+
    +
  • xxx.xxx.xxx.xxx (an exact IP address)
  • +
  • xxx.xxx.xxx.[yyy-zzz] (an IP address range)
  • +
  • xxx.xxx.xxx.xxx/nn (CIDR, Classless Inter-Domain Routing type IP addresses)
  • +
+

But the following does not work:

+
    +
  • xxx.xxx.xxx.xx[yyy-zzz] (partial IP address range)
  • +
+

For IPv6 addresses, the following work:

+
    +
  • xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx (an exact IPv6 address)
  • +
  • xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:[yyyy-zzzz] (an IPv6 address range)
  • +
  • xxxx:xxxx:xxxx:xxxx/nn (CIDR, Classless Inter-Domain Routing type IPv6 addresses)
  • +
+

But the following does not work:

+
    +
  • xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xx[yyy-zzz] (partial IPv6 address range)
  • +
+

Examples:

+
$cfg['Servers'][$i]['AllowDeny']['order'] = 'allow,deny';
+$cfg['Servers'][$i]['AllowDeny']['rules'] = array('allow bob from all');
+// Allow only 'bob' to connect from any host
+
+$cfg['Servers'][$i]['AllowDeny']['order'] = 'allow,deny';
+$cfg['Servers'][$i]['AllowDeny']['rules'] = array('allow mary from 192.168.100.[50-100]');
+// Allow only 'mary' to connect from host 192.168.100.50 through 192.168.100.100
+
+$cfg['Servers'][$i]['AllowDeny']['order'] = 'allow,deny';
+$cfg['Servers'][$i]['AllowDeny']['rules'] = array('allow % from 192.168.[5-6].10');
+// Allow any user to connect from host 192.168.5.10 or 192.168.6.10
+
+$cfg['Servers'][$i]['AllowDeny']['order'] = 'allow,deny';
+$cfg['Servers'][$i]['AllowDeny']['rules'] = array('allow root from 192.168.5.50','allow % from 192.168.6.10');
+// Allow any user to connect from 192.168.6.10, and additionally allow root to connect from 192.168.5.50
+
+
+
+ +
+
+$cfg['Servers'][$i]['DisableIS']
+
+++ + + + + + +
Type:boolean
Default value:false
+

Disable using INFORMATION_SCHEMA to retrieve information (use +SHOW commands instead), because of speed issues when many +databases are present.

+
+

Note

+

Enabling this option might give you a big performance boost on older +MySQL servers.

+
+
+ +
+
+$cfg['Servers'][$i]['SignonScript']
+
+++ + + + + + +
Type:string
Default value:''
+
+

New in version 3.5.0.

+
+

Name of PHP script to be sourced and executed to obtain login +credentials. This is alternative approach to session based single +signon. The script has to provide a function called +get_login_credentials which returns list of username and +password, accepting single parameter of existing username (can be +empty). See examples/signon-script.php for an example:

+
<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Single signon for phpMyAdmin
+ *
+ * This is just example how to use script based single signon with
+ * phpMyAdmin, it is not intended to be perfect code and look, only
+ * shows how you can integrate this functionality in your application.
+ *
+ * @package    PhpMyAdmin
+ * @subpackage Example
+ */
+
+
+/**
+ * This function returns username and password.
+ *
+ * It can optionally use configured username as parameter.
+ *
+ * @param string $user User name
+ *
+ * @return array
+ */
+function get_login_credentials($user)
+{
+    /* Optionally we can use passed username */
+    if (!empty($user)) {
+        return array($user, 'password');
+    }
+
+    /* Here we would retrieve the credentials */
+    $credentials = array('root', '');
+
+    return $credentials;
+}
+
+
+ +
+ +
+
+$cfg['Servers'][$i]['SignonSession']
+
+++ + + + + + +
Type:string
Default value:''
+

Name of session which will be used for signon authentication method. +You should use something different than phpMyAdmin, because this +is session which phpMyAdmin uses internally. Takes effect only if +$cfg['Servers'][$i]['SignonScript'] is not configured.

+ +
+ +
+
+$cfg['Servers'][$i]['SignonCookieParams']
+
+++ + + + + + +
Type:array
Default value:array()
+
+

New in version 4.7.0.

+
+

An associative array of session cookie parameters of other authentication system. +It is not needed if the other system doesn’t use session_set_cookie_params(). +Keys should include ‘lifetime’, ‘path’, ‘domain’, ‘secure’ or ‘httponly’. +Valid values are mentioned in session_get_cookie_params, they should be set to same values as the +other application uses. Takes effect only if +$cfg['Servers'][$i]['SignonScript'] is not configured.

+ +
+ +
+
+$cfg['Servers'][$i]['SignonURL']
+
+++ + + + + + +
Type:string
Default value:''
+

URL where user will be redirected +to log in for signon authentication method. Should be absolute +including protocol.

+ +
+ +
+
+$cfg['Servers'][$i]['LogoutURL']
+
+++ + + + + + +
Type:string
Default value:''
+

URL where user will be redirected +after logout (doesn’t affect config authentication method). Should be +absolute including protocol.

+
+ +
+
+

Generic settings

+
+
+$cfg['DisableShortcutKeys']
+
+++ + + + + + +
Type:boolean
Default value:false
+

You can disable phpMyAdmin shortcut keys by setting $cfg['DisableShortcutKeys'] to false.

+
+ +
+
+$cfg['ServerDefault']
+
+++ + + + + + +
Type:integer
Default value:1
+

If you have more than one server configured, you can set +$cfg['ServerDefault'] to any one of them to autoconnect to that +server when phpMyAdmin is started, or set it to 0 to be given a list +of servers without logging in.

+

If you have only one server configured, +$cfg['ServerDefault'] MUST be set to that server.

+
+ +
+
+$cfg['VersionCheck']
+
+++ + + + + + +
Type:boolean
Default value:true
+

Enables check for latest versions using JavaScript on the main phpMyAdmin +page or by directly accessing version_check.php.

+
+

Note

+

This setting can be adjusted by your vendor.

+
+
+ +
+
+$cfg['ProxyUrl']
+
+++ + + + + + +
Type:string
Default value:“”
+

The url of the proxy to be used when phpmyadmin needs to access the outside +internet such as when retrieving the latest version info or submitting error +reports. You need this if the server where phpMyAdmin is installed does not +have direct access to the internet. +The format is: “hostname:portnumber”

+
+ +
+
+$cfg['ProxyUser']
+
+++ + + + + + +
Type:string
Default value:“”
+

The username for authenticating with the proxy. By default, no +authentication is performed. If a username is supplied, Basic +Authentication will be performed. No other types of authentication +are currently supported.

+
+ +
+
+$cfg['ProxyPass']
+
+++ + + + + + +
Type:string
Default value:“”
+

The password for authenticating with the proxy.

+
+ +
+
+$cfg['MaxDbList']
+
+++ + + + + + +
Type:integer
Default value:100
+

The maximum number of database names to be displayed in the main panel’s +database list.

+
+ +
+
+$cfg['MaxTableList']
+
+++ + + + + + +
Type:integer
Default value:250
+

The maximum number of table names to be displayed in the main panel’s +list (except on the Export page).

+
+ +
+
+$cfg['ShowHint']
+
+++ + + + + + +
Type:boolean
Default value:true
+

Whether or not to show hints (for example, hints when hovering over +table headers).

+
+ +
+
+$cfg['MaxCharactersInDisplayedSQL']
+
+++ + + + + + +
Type:integer
Default value:1000
+

The maximum number of characters when a SQL query is displayed. The +default limit of 1000 should be correct to avoid the display of tons of +hexadecimal codes that represent BLOBs, but some users have real +SQL queries that are longer than 1000 characters. Also, if a +query’s length exceeds this limit, this query is not saved in the history.

+
+ +
+
+$cfg['PersistentConnections']
+
+++ + + + + + +
Type:boolean
Default value:false
+

Whether persistent connections should be used or not. Works with +following extensions:

+ +
+ +
+
+$cfg['ForceSSL']
+
+++ + + + + + +
Type:boolean
Default value:false
+
+

Deprecated since version 4.6.0: This setting is no longer available since phpMyAdmin 4.6.0. Please +adjust your webserver instead.

+
+

Whether to force using https while accessing phpMyAdmin. In a reverse +proxy setup, setting this to true is not supported.

+
+

Note

+

In some setups (like separate SSL proxy or load balancer) you might +have to set $cfg['PmaAbsoluteUri'] for correct +redirection.

+
+
+ +
+
+$cfg['ExecTimeLimit']
+
+++ + + + + + +
Type:integer [number of seconds]
Default value:300
+

Set the number of seconds a script is allowed to run. If seconds is +set to zero, no time limit is imposed. This setting is used while +importing/exporting dump files but has +no effect when PHP is running in safe mode.

+
+ +
+
+$cfg['SessionSavePath']
+
+++ + + + + + +
Type:string
Default value:''
+

Path for storing session data (session_save_path PHP parameter).

+
+

Warning

+

This folder should not be publicly accessible through the webserver, +otherwise you risk leaking private data from your session.

+
+
+ +
+
+$cfg['MemoryLimit']
+
+++ + + + + + +
Type:string [number of bytes]
Default value:'-1'
+

Set the number of bytes a script is allowed to allocate. If set to +'-1', no limit is imposed. If set to '0', no change of the +memory limit is attempted and the php.ini memory_limit is +used.

+

This setting is used while importing/exporting dump files +so you definitely don’t want to put here a too low +value. It has no effect when PHP is running in safe mode.

+

You can also use any string as in php.ini, eg. ‘16M’. Ensure you +don’t omit the suffix (16 means 16 bytes!)

+
+ +
+
+$cfg['SkipLockedTables']
+
+++ + + + + + +
Type:boolean
Default value:false
+

Mark used tables and make it possible to show databases with locked +tables (since MySQL 3.23.30).

+
+ +
+
+$cfg['ShowSQL']
+
+++ + + + + + +
Type:boolean
Default value:true
+

Defines whether SQL queries +generated by phpMyAdmin should be displayed or not.

+
+ +
+
+$cfg['RetainQueryBox']
+
+++ + + + + + +
Type:boolean
Default value:false
+

Defines whether the SQL query box +should be kept displayed after its submission.

+
+ +
+
+$cfg['CodemirrorEnable']
+
+++ + + + + + +
Type:boolean
Default value:true
+

Defines whether to use a Javascript code editor for SQL query boxes. +CodeMirror provides syntax highlighting and line numbers. However, +middle-clicking for pasting the clipboard contents in some Linux +distributions (such as Ubuntu) is not supported by all browsers.

+
+ +
+
+$cfg['DefaultForeignKeyChecks']
+
+++ + + + + + +
Type:string
Default value:'default'
+

Default value of the checkbox for foreign key checks, to disable/enable +foreign key checks for certain queries. The possible values are 'default', +'enable' or 'disable'. If set to 'default', the value of the +MySQL variable FOREIGN_KEY_CHECKS is used.

+
+ +
+
+$cfg['AllowUserDropDatabase']
+
+++ + + + + + +
Type:boolean
Default value:false
+
+

Warning

+

This is not a security measure as there will be always ways to +circumvent this. If you want to prohibit users from dropping databases, +revoke their corresponding DROP privilege.

+
+

Defines whether normal users (non-administrator) are allowed to delete +their own database or not. If set as false, the link Drop +Database will not be shown, and even a DROP DATABASE mydatabase will +be rejected. Quite practical for ISP ‘s with many customers.

+

This limitation of SQL queries is not as strict as when using MySQL +privileges. This is due to nature of SQL queries which might be +quite complicated. So this choice should be viewed as help to avoid +accidental dropping rather than strict privilege limitation.

+
+ +
+
+$cfg['Confirm']
+
+++ + + + + + +
Type:boolean
Default value:true
+

Whether a warning (“Are your really sure...”) should be displayed when +you’re about to lose data.

+
+ +
+
+$cfg['UseDbSearch']
+
+++ + + + + + +
Type:boolean
Default value:true
+

Define whether the “search string inside database” is enabled or not.

+
+ +
+
+$cfg['IgnoreMultiSubmitErrors']
+
+++ + + + + + +
Type:boolean
Default value:false
+

Define whether phpMyAdmin will continue executing a multi-query +statement if one of the queries fails. Default is to abort execution.

+
+ +
+ + +
+

Main panel

+
+
+$cfg['ShowStats']
+
+++ + + + + + +
Type:boolean
Default value:true
+

Defines whether or not to display space usage and statistics about +databases and tables. Note that statistics requires at least MySQL +3.23.3 and that, at this date, MySQL doesn’t return such information +for Berkeley DB tables.

+
+ +
+
+$cfg['ShowServerInfo']
+
+++ + + + + + +
Type:boolean
Default value:true
+

Defines whether to display detailed server information on main page. +You can additionally hide more information by using +$cfg['Servers'][$i]['verbose'].

+
+ +
+
+$cfg['ShowPhpInfo']
+
+++ + + + + + +
Type:boolean
Default value:false
+

Defines whether to display the PHP information or not at +the starting main (right) frame.

+

Please note that to block the usage of phpinfo() in scripts, you have to +put this in your php.ini:

+
disable_functions = phpinfo()
+
+
+
+

Warning

+

Enabling phpinfo page will leak quite a lot of information about server +setup. Is it not recommended to enable this on shared installations.

+

This might also make easier some remote attacks on your installations, +so enable this only when needed.

+
+
+ +
+
+$cfg['ShowChgPassword']
+
+++ + + + + + +
Type:boolean
Default value:true
+

Defines whether to display the Change password link or not at +the starting main (right) frame. This setting does not check MySQL commands +entered directly.

+

Please note that enabling the Change password link has no effect +with config authentication mode: because of the hard coded password value +in the configuration file, end users can’t be allowed to change their +passwords.

+
+ +
+
+$cfg['ShowCreateDb']
+
+++ + + + + + +
Type:boolean
Default value:true
+

Defines whether to display the form for creating database or not at the +starting main (right) frame. This setting does not check MySQL commands +entered directly.

+
+ +
+
+$cfg['ShowGitRevision']
+
+++ + + + + + +
Type:boolean
Default value:true
+

Defines whether to display informations about the current Git revision (if +applicable) on the main panel.

+
+ +
+
+$cfg['MysqlMinVersion']
+
+++ + + + +
Type:array
+

Defines the minimum supported MySQL version. The default is chosen +by the phpMyAdmin team; however this directive was asked by a developer +of the Plesk control panel to ease integration with older MySQL servers +(where most of the phpMyAdmin features work).

+
+ +
+
+

Database structure

+
+
+$cfg['ShowDbStructureCreation']
+
+++ + + + + + +
Type:boolean
Default value:false
+

Defines whether the database structure page (tables list) has a +“Creation” column that displays when each table was created.

+
+ +
+
+$cfg['ShowDbStructureLastUpdate']
+
+++ + + + + + +
Type:boolean
Default value:false
+

Defines whether the database structure page (tables list) has a “Last +update” column that displays when each table was last updated.

+
+ +
+
+$cfg['ShowDbStructureLastCheck']
+
+++ + + + + + +
Type:boolean
Default value:false
+

Defines whether the database structure page (tables list) has a “Last +check” column that displays when each table was last checked.

+
+ +
+
+$cfg['HideStructureActions']
+
+++ + + + + + +
Type:boolean
Default value:true
+

Defines whether the table structure actions are hidden under a “More” +drop-down.

+
+ +
+
+$cfg['ShowColumnComments']
+
+++ + + + + + +
Type:boolean
Default value:true
+

Defines whether to show column comments as a column in the table structure view.

+
+ +
+
+

Browse mode

+
+
+$cfg['TableNavigationLinksMode']
+
+++ + + + + + +
Type:string
Default value:'icons'
+

Defines whether the table navigation links contain 'icons', 'text' +or 'both'.

+
+ +
+
+$cfg['ActionLinksMode']
+
+++ + + + + + +
Type:string
Default value:'both'
+

If set to icons, will display icons instead of text for db and table +properties links (like Browse, Select, +Insert, ...). Can be set to 'both' +if you want icons AND text. When set to text, will only show text.

+
+ +
+
+$cfg['RowActionType']
+
+++ + + + + + +
Type:string
Default value:'both'
+

Whether to display icons or text or both icons and text in table row action +segment. Value can be either of 'icons', 'text' or 'both'.

+
+ +
+
+$cfg['ShowAll']
+
+++ + + + + + +
Type:boolean
Default value:false
+

Defines whether a user should be displayed a “Show all” button in browse +mode or not in all cases. By default it is shown only on small tables (less +than 500 rows) to avoid performance issues while getting too many rows.

+
+ +
+
+$cfg['MaxRows']
+
+++ + + + + + +
Type:integer
Default value:25
+

Number of rows displayed when browsing a result set and no LIMIT +clause is used. If the result set contains more rows, “Previous” and +“Next” links will be shown. Possible values: 25,50,100,250,500.

+
+ +
+
+$cfg['Order']
+
+++ + + + + + +
Type:string
Default value:'SMART'
+

Defines whether columns are displayed in ascending (ASC) order, in +descending (DESC) order or in a “smart” (SMART) order - I.E. +descending order for columns of type TIME, DATE, DATETIME and +TIMESTAMP, ascending order else- by default.

+
+

Changed in version 3.4.0: Since phpMyAdmin 3.4.0 the default value is 'SMART'.

+
+
+ +
+
+$cfg['GridEditing']
+
+++ + + + + + +
Type:string
Default value:'double-click'
+

Defines which action (double-click or click) triggers grid +editing. Can be deactivated with the disabled value.

+
+ +
+
+$cfg['RelationalDisplay']
+
+++ + + + + + +
Type:string
Default value:'K'
+

Defines the initial behavior for Options > Relational. K, which +is the default, displays the key while D shows the display column.

+
+ +
+
+$cfg['SaveCellsAtOnce']
+
+++ + + + + + +
Type:boolean
Default value:false
+

Defines whether or not to save all edited cells at once for grid +editing.

+
+ +
+
+

Editing mode

+
+
+$cfg['ProtectBinary']
+
+++ + + + + + +
Type:boolean or string
Default value:'blob'
+

Defines whether BLOB or BINARY columns are protected from +editing when browsing a table’s content. Valid values are:

+
    +
  • false to allow editing of all columns;
  • +
  • 'blob' to allow editing of all columns except BLOBS;
  • +
  • 'noblob' to disallow editing of all columns except BLOBS (the +opposite of 'blob');
  • +
  • 'all' to disallow editing of all BINARY or BLOB columns.
  • +
+
+ +
+
+$cfg['ShowFunctionFields']
+
+++ + + + + + +
Type:boolean
Default value:true
+

Defines whether or not MySQL functions fields should be initially +displayed in edit/insert mode. Since version 2.10, the user can toggle +this setting from the interface.

+
+ +
+
+$cfg['ShowFieldTypesInDataEditView']
+
+++ + + + + + +
Type:boolean
Default value:true
+

Defines whether or not type fields should be initially displayed in +edit/insert mode. The user can toggle this setting from the interface.

+
+ +
+
+$cfg['InsertRows']
+
+++ + + + + + +
Type:integer
Default value:2
+

Defines the default number of rows to be entered from the Insert page. +Users can manually change this from the bottom of that page to add or remove +blank rows.

+
+ +
+
+$cfg['ForeignKeyMaxLimit']
+
+++ + + + + + +
Type:integer
Default value:100
+

If there are fewer items than this in the set of foreign keys, then a +drop-down box of foreign keys is presented, in the style described by +the $cfg['ForeignKeyDropdownOrder'] setting.

+
+ +
+
+$cfg['ForeignKeyDropdownOrder']
+
+++ + + + + + +
Type:array
Default value:array(‘content-id’, ‘id-content’)
+

For the foreign key drop-down fields, there are several methods of +display, offering both the key and value data. The contents of the +array should be one or both of the following strings: content-id, +id-content.

+
+ +
+
+

Export and import settings

+
+
+$cfg['ZipDump']
+
+++ + + + + + +
Type:boolean
Default value:true
+
+ +
+
+$cfg['GZipDump']
+
+++ + + + + + +
Type:boolean
Default value:true
+
+ +
+
+$cfg['BZipDump']
+
+++ + + + + + +
Type:boolean
Default value:true
+

Defines whether to allow the use of zip/GZip/BZip2 compression when +creating a dump file

+
+ +
+
+$cfg['CompressOnFly']
+
+++ + + + + + +
Type:boolean
Default value:true
+

Defines whether to allow on the fly compression for GZip/BZip2 +compressed exports. This doesn’t affect smaller dumps and allows users +to create larger dumps that won’t otherwise fit in memory due to php +memory limit. Produced files contain more GZip/BZip2 headers, but all +normal programs handle this correctly.

+
+ +
+
+$cfg['Export']
+
+++ + + + + + +
Type:array
Default value:array(...)
+

In this array are defined default parameters for export, names of +items are similar to texts seen on export page, so you can easily +identify what they mean.

+
+ +
+
+$cfg['Export']['format']
+
+++ + + + + + +
Type:string
Default value:'sql'
+

Default export format.

+
+ +
+
+$cfg['Export']['method']
+
+++ + + + + + +
Type:string
Default value:'quick'
+

Defines how the export form is displayed when it loads. Valid values +are:

+
    +
  • quick to display the minimum number of options to configure
  • +
  • custom to display every available option to configure
  • +
  • custom-no-form same as custom but does not display the option +of using quick export
  • +
+
+ +
+
+$cfg['Export']['charset']
+
+++ + + + + + +
Type:string
Default value:''
+

Defines charset for generated export. By default no charset conversion is +done assuming UTF-8.

+
+ +
+
+$cfg['Export']['file_template_table']
+
+++ + + + + + +
Type:string
Default value:'@TABLE@'
+

Default filename template for table exports.

+ +
+ +
+
+$cfg['Export']['file_template_database']
+
+++ + + + + + +
Type:string
Default value:'@DATABASE@'
+

Default filename template for database exports.

+ +
+ +
+
+$cfg['Export']['file_template_server']
+
+++ + + + + + +
Type:string
Default value:'@SERVER@'
+

Default filename template for server exports.

+ +
+ +
+
+$cfg['Import']
+
+++ + + + + + +
Type:array
Default value:array(...)
+

In this array are defined default parameters for import, names of +items are similar to texts seen on import page, so you can easily +identify what they mean.

+
+ +
+
+$cfg['Import']['charset']
+
+++ + + + + + +
Type:string
Default value:''
+

Defines charset for import. By default no charset conversion is done +assuming UTF-8.

+
+ +
+
+

Tabs display settings

+
+
+$cfg['TabsMode']
+
+++ + + + + + +
Type:string
Default value:'both'
+

Defines whether the menu tabs contain 'icons', 'text' or 'both'.

+
+ +
+
+$cfg['PropertiesNumColumns']
+
+++ + + + + + +
Type:integer
Default value:1
+

How many columns will be utilized to display the tables on the database +property view? When setting this to a value larger than 1, the type of the +database will be omitted for more display space.

+
+ +
+
+$cfg['DefaultTabServer']
+
+++ + + + + + +
Type:string
Default value:'welcome'
+

Defines the tab displayed by default on server view. The possible values +are the localized equivalent of:

+
    +
  • welcome (recommended for multi-user setups)
  • +
  • databases,
  • +
  • status
  • +
  • variables
  • +
  • privileges
  • +
+
+ +
+
+$cfg['DefaultTabDatabase']
+
+++ + + + + + +
Type:string
Default value:'structure'
+

Defines the tab displayed by default on database view. The possible values +are the localized equivalent of:

+
    +
  • structure
  • +
  • sql
  • +
  • search
  • +
  • operations
  • +
+
+ +
+
+$cfg['DefaultTabTable']
+
+++ + + + + + +
Type:string
Default value:'browse'
+

Defines the tab displayed by default on table view. The possible values +are the localized equivalent of:

+
    +
  • structure
  • +
  • sql
  • +
  • search
  • +
  • insert
  • +
  • browse
  • +
+
+ +
+
+

PDF Options

+
+
+$cfg['PDFPageSizes']
+
+++ + + + + + +
Type:array
Default value:array('A3', 'A4', 'A5', 'letter', 'legal')
+

Array of possible paper sizes for creating PDF pages.

+

You should never need to change this.

+
+ +
+
+$cfg['PDFDefaultPageSize']
+
+++ + + + + + +
Type:string
Default value:'A4'
+

Default page size to use when creating PDF pages. Valid values are any +listed in $cfg['PDFPageSizes'].

+
+ +
+
+

Languages

+
+
+$cfg['DefaultLang']
+
+++ + + + + + +
Type:string
Default value:'en'
+

Defines the default language to use, if not browser-defined or user- +defined. The corresponding language file needs to be in +locale/code/LC_MESSAGES/phpmyadmin.mo.

+
+ +
+
+$cfg['DefaultConnectionCollation']
+
+++ + + + + + +
Type:string
Default value:'utf8mb4_general_ci'
+

Defines the default connection collation to use, if not user-defined. +See the MySQL documentation for charsets +for list of possible values.

+
+ +
+
+$cfg['Lang']
+
+++ + + + + + +
Type:string
Default value:not set
+

Force language to use. The corresponding language file needs to be in +locale/code/LC_MESSAGES/phpmyadmin.mo.

+
+ +
+
+$cfg['FilterLanguages']
+
+++ + + + + + +
Type:string
Default value:''
+

Limit list of available languages to those matching the given regular +expression. For example if you want only Czech and English, you should +set filter to '^(cs|en)'.

+
+ +
+
+$cfg['RecodingEngine']
+
+++ + + + + + +
Type:string
Default value:'auto'
+

You can select here which functions will be used for character set +conversion. Possible values are:

+
    +
  • auto - automatically use available one (first is tested iconv, then +recode)
  • +
  • iconv - use iconv or libiconv functions
  • +
  • recode - use recode_string function
  • +
  • mb - use mbstring extension
  • +
  • none - disable encoding conversion
  • +
+

Enabled charset conversion activates a pull-down menu in the Export +and Import pages, to choose the character set when exporting a file. +The default value in this menu comes from +$cfg['Export']['charset'] and $cfg['Import']['charset'].

+
+ +
+
+$cfg['IconvExtraParams']
+
+++ + + + + + +
Type:string
Default value:'//TRANSLIT'
+

Specify some parameters for iconv used in charset conversion. See +iconv documentation for details. By default +//TRANSLIT is used, so that invalid characters will be +transliterated.

+
+ +
+
+$cfg['AvailableCharsets']
+
+++ + + + + + +
Type:array
Default value:array(...)
+

Available character sets for MySQL conversion. You can add your own +(any of supported by recode/iconv) or remove these which you don’t +use. Character sets will be shown in same order as here listed, so if +you frequently use some of these move them to the top.

+
+ +
+
+

Web server settings

+
+
+$cfg['OBGzip']
+
+++ + + + + + +
Type:string/boolean
Default value:'auto'
+

Defines whether to use GZip output buffering for increased speed in +HTTP transfers. Set to +true/false for enabling/disabling. When set to ‘auto’ (string), +phpMyAdmin tries to enable output buffering and will automatically +disable it if your browser has some problems with buffering. IE6 with +a certain patch is known to cause data corruption when having enabled +buffering.

+
+ +
+
+$cfg['TrustedProxies']
+
+++ + + + + + +
Type:array
Default value:array()
+

Lists proxies and HTTP headers which are trusted for +$cfg['Servers'][$i]['AllowDeny']['order']. This list is by +default empty, you need to fill in some trusted proxy servers if you +want to use rules for IP addresses behind proxy.

+

The following example specifies that phpMyAdmin should trust a +HTTP_X_FORWARDED_FOR (X-Forwarded-For) header coming from the proxy +1.2.3.4:

+
$cfg['TrustedProxies'] = array('1.2.3.4' => 'HTTP_X_FORWARDED_FOR');
+
+
+

The $cfg['Servers'][$i]['AllowDeny']['rules'] directive uses the +client’s IP address as usual.

+
+ +
+
+$cfg['GD2Available']
+
+++ + + + + + +
Type:string
Default value:'auto'
+

Specifies whether GD >= 2 is available. If yes it can be used for MIME +transformations. Possible values are:

+
    +
  • auto - automatically detect
  • +
  • yes - GD 2 functions can be used
  • +
  • no - GD 2 function cannot be used
  • +
+
+ +
+
+$cfg['CheckConfigurationPermissions']
+
+++ + + + + + +
Type:boolean
Default value:true
+

We normally check the permissions on the configuration file to ensure +it’s not world writable. However, phpMyAdmin could be installed on a +NTFS filesystem mounted on a non-Windows server, in which case the +permissions seems wrong but in fact cannot be detected. In this case a +sysadmin would set this parameter to false.

+
+ +
+
+$cfg['LinkLengthLimit']
+
+++ + + + + + +
Type:integer
Default value:1000
+

Limit for length of URL in links. When length would be above this +limit, it is replaced by form with button. This is required as some web +servers (IIS) have problems with long URL .

+
+ +
+
+$cfg['CSPAllow']
+
+++ + + + + + +
Type:string
Default value:''
+

Additional string to include in allowed script and image sources in Content +Security Policy header.

+

This can be useful when you want to include some external JavaScript files +in config.footer.inc.php or config.header.inc.php, which +would be normally not allowed by Content Security Policy.

+

To allow some sites, just list them within the string:

+
$cfg['CSPAllow'] = 'example.com example.net';
+
+
+
+

New in version 4.0.4.

+
+
+ +
+
+$cfg['DisableMultiTableMaintenance']
+
+++ + + + + + +
Type:boolean
Default value:false
+

In the database Structure page, it’s possible to mark some tables then +choose an operation like optimizing for many tables. This can slow +down a server; therefore, setting this to true prevents this kind +of multiple maintenance operation.

+
+ +
+
+

Theme settings

+
+
Please directly modify themes/themename/layout.inc.php, although +your changes will be overwritten with the next update.
+
+
+

Design customization

+
+
+$cfg['NavigationTreePointerEnable']
+
+++ + + + + + +
Type:boolean
Default value:true
+

When set to true, hovering over an item in the navigation panel causes that item to be marked +(the background is highlighted).

+
+ +
+
+$cfg['BrowsePointerEnable']
+
+++ + + + + + +
Type:boolean
Default value:true
+

When set to true, hovering over a row in the Browse page causes that row to be marked (the background +is highlighted).

+
+ +
+
+$cfg['BrowseMarkerEnable']
+
+++ + + + + + +
Type:boolean
Default value:true
+

When set to true, a data row is marked (the background is highlighted) when the row is selected +with the checkbox.

+
+ +
+
+$cfg['LimitChars']
+
+++ + + + + + +
Type:integer
Default value:50
+

Maximum number of characters shown in any non-numeric field on browse +view. Can be turned off by a toggle button on the browse page.

+
+ +
+ +
+++ + + + + + +
Type:string
Default value:'left'
+

Defines the place where table row links (Edit, Copy, Delete) would be +put when tables contents are displayed (you may have them displayed at +the left side, right side, both sides or nowhere).

+
+ +
+
+$cfg['RowActionLinksWithoutUnique']
+
+++ + + + + + +
Type:boolean
Default value:false
+

Defines whether to show row links (Edit, Copy, Delete) and checkboxes +for multiple row operations even when the selection does not have a unique key. +Using row actions in the absence of a unique key may result in different/more +rows being affected since there is no guaranteed way to select the exact row(s).

+
+ +
+
+$cfg['RememberSorting']
+
+++ + + + + + +
Type:boolean
Default value:true
+

If enabled, remember the sorting of each table when browsing them.

+
+ +
+
+$cfg['TablePrimaryKeyOrder']
+
+++ + + + + + +
Type:string
Default value:'NONE'
+

This defines the default sort order for the tables, having a primary key, +when there is no sort order defines externally. +Acceptable values : [‘NONE’, ‘ASC’, ‘DESC’]

+
+ +
+
+$cfg['ShowBrowseComments']
+
+++ + + + + + +
Type:boolean
Default value:true
+
+ +
+
+$cfg['ShowPropertyComments']
+
+++ + + + + + +
Type:boolean
Default value:true
+

By setting the corresponding variable to true you can enable the +display of column comments in Browse or Property display. In browse +mode, the comments are shown inside the header. In property mode, +comments are displayed using a CSS-formatted dashed-line below the +name of the column. The comment is shown as a tool-tip for that +column.

+
+ +
+
+

Text fields

+
+
+$cfg['CharEditing']
+
+++ + + + + + +
Type:string
Default value:'input'
+

Defines which type of editing controls should be used for CHAR and +VARCHAR columns. Applies to data editing and also to the default values +in structure editing. Possible values are:

+
    +
  • input - this allows to limit size of text to size of columns in MySQL, +but has problems with newlines in columns
  • +
  • textarea - no problems with newlines in columns, but also no length +limitations
  • +
+
+ +
+
+$cfg['MinSizeForInputField']
+
+++ + + + + + +
Type:integer
Default value:4
+

Defines the minimum size for input fields generated for CHAR and +VARCHAR columns.

+
+ +
+
+$cfg['MaxSizeForInputField']
+
+++ + + + + + +
Type:integer
Default value:60
+

Defines the maximum size for input fields generated for CHAR and +VARCHAR columns.

+
+ +
+
+$cfg['TextareaCols']
+
+++ + + + + + +
Type:integer
Default value:40
+
+ +
+
+$cfg['TextareaRows']
+
+++ + + + + + +
Type:integer
Default value:15
+
+ +
+
+$cfg['CharTextareaCols']
+
+++ + + + + + +
Type:integer
Default value:40
+
+ +
+
+$cfg['CharTextareaRows']
+
+++ + + + + + +
Type:integer
Default value:2
+

Number of columns and rows for the textareas. This value will be +emphasized (*2) for SQL query +textareas and (*1.25) for SQL +textareas inside the query window.

+

The Char* values are used for CHAR +and VARCHAR editing (if configured via $cfg['CharEditing']).

+
+ +
+
+$cfg['LongtextDoubleTextarea']
+
+++ + + + + + +
Type:boolean
Default value:true
+

Defines whether textarea for LONGTEXT columns should have double size.

+
+ +
+
+$cfg['TextareaAutoSelect']
+
+++ + + + + + +
Type:boolean
Default value:false
+

Defines if the whole textarea of the query box will be selected on +click.

+
+ +
+
+$cfg['EnableAutocompleteForTablesAndColumns']
+
+++ + + + + + +
Type:boolean
Default value:true
+

Whether to enable autocomplete for table and column names in any +SQL query box.

+
+ +
+
+

SQL query box settings

+
+
+$cfg['SQLQuery']['Edit']
+
+++ + + + + + +
Type:boolean
Default value:true
+

Whether to display an edit link to change a query in any SQL Query +box.

+
+ +
+
+$cfg['SQLQuery']['Explain']
+
+++ + + + + + +
Type:boolean
Default value:true
+

Whether to display a link to explain a SELECT query in any SQL Query +box.

+
+ +
+
+$cfg['SQLQuery']['ShowAsPHP']
+
+++ + + + + + +
Type:boolean
Default value:true
+

Whether to display a link to wrap a query in PHP code in any SQL Query +box.

+
+ +
+
+$cfg['SQLQuery']['Refresh']
+
+++ + + + + + +
Type:boolean
Default value:true
+

Whether to display a link to refresh a query in any SQL Query box.

+
+ +
+
+

Web server upload/save/import directories

+

If PHP is running in safe mode, all directories must be owned by the same user +as the owner of the phpMyAdmin scripts.

+

If the directory where phpMyAdmin is installed is subject to an +open_basedir restriction, you need to create a temporary directory in some +directory accessible by the PHP interpreter.

+

For security reasons, all directories should be outside the tree published by +webserver. If you cannot avoid having this directory published by webserver, +limit access to it either by web server configuration (for example using +.htaccess or web.config files) or place at least an empty index.html +file there, so that directory listing is not possible. However as long as the +directory is accessible by web server, an attacker can guess filenames to download +the files.

+
+
+$cfg['UploadDir']
+
+++ + + + + + +
Type:string
Default value:''
+

The name of the directory where SQL files have been uploaded by +other means than phpMyAdmin (for example, ftp). Those files are available +under a drop-down box when you click the database or table name, then the +Import tab.

+

If +you want different directory for each user, %u will be replaced with +username.

+

Please note that the file names must have the suffix ”.sql” +(or ”.sql.bz2” or ”.sql.gz” if support for compressed formats is +enabled).

+

This feature is useful when your file is too big to be +uploaded via HTTP, or when file +uploads are disabled in PHP.

+
+

Warning

+

Please see top of this chapter (Web server upload/save/import directories) for instructions how +to setup this directory and how to make its usage secure.

+
+ +
+ +
+
+$cfg['SaveDir']
+
+++ + + + + + +
Type:string
Default value:''
+

The name of the webserver directory where exported files can be saved.

+

If you want a different directory for each user, %u will be replaced with the +username.

+

Please note that the directory must exist and has to be writable for +the user running webserver.

+
+

Warning

+

Please see top of this chapter (Web server upload/save/import directories) for instructions how +to setup this directory and how to make its usage secure.

+
+
+ +
+
+$cfg['TempDir']
+
+++ + + + + + +
Type:string
Default value:'./tmp/'
+

The name of the directory where temporary files can be stored. It is used +for several purposes, currently:

+ +

This directory should have as strict permissions as possible as the only +user required to access this directory is the one who runs the webserver. +If you have root privileges, simply make this user owner of this directory +and make it accessible only by it:

+
chown www-data:www-data tmp
+chmod 700 tmp
+
+
+

If you cannot change owner of the directory, you can achieve a similar +setup using ACL:

+
chmod 700 tmp
+setfacl -m "g:www-data:rwx" tmp
+setfacl -d -m "g:www-data:rwx" tmp
+
+
+

If neither of above works for you, you can still make the directory +chmod 777, but it might impose risk of other users on system +reading and writing data in this directory.

+
+

Warning

+

Please see top of this chapter (Web server upload/save/import directories) for instructions how +to setup this directory and how to make its usage secure.

+
+
+ +
+
+

Various display setting

+
+
+$cfg['RepeatCells']
+
+++ + + + + + +
Type:integer
Default value:100
+

Repeat the headers every X cells, or 0 to deactivate.

+
+ +
+
+$cfg['QueryHistoryDB']
+
+++ + + + + + +
Type:boolean
Default value:false
+
+ +
+
+$cfg['QueryHistoryMax']
+
+++ + + + + + +
Type:integer
Default value:25
+

If $cfg['QueryHistoryDB'] is set to true, all your +Queries are logged to a table, which has to be created by you (see +$cfg['Servers'][$i]['history']). If set to false, all your +queries will be appended to the form, but only as long as your window is +opened they remain saved.

+

When using the JavaScript based query window, it will always get updated +when you click on a new table/db to browse and will focus if you click on +Edit SQL after using a query. You can suppress updating the +query window by checking the box Do not overwrite this query +from outside the window below the query textarea. Then you can browse +tables/databases in the background without losing the contents of the +textarea, so this is especially useful when composing a query with tables +you first have to look in. The checkbox will get automatically checked +whenever you change the contents of the textarea. Please uncheck the button +whenever you definitely want the query window to get updated even though +you have made alterations.

+

If $cfg['QueryHistoryDB'] is set to true you can +specify the amount of saved history items using +$cfg['QueryHistoryMax'].

+
+ +
+
+$cfg['BrowseMIME']
+
+++ + + + + + +
Type:boolean
Default value:true
+

Enable Transformations.

+
+ +
+
+$cfg['MaxExactCount']
+
+++ + + + + + +
Type:integer
Default value:50000
+

For InnoDB tables, determines for how large tables phpMyAdmin should +get the exact row count using SELECT COUNT. If the approximate row +count as returned by SHOW TABLE STATUS is smaller than this value, +SELECT COUNT will be used, otherwise the approximate count will be +used.

+
+

Changed in version 4.8.0: The default value was lowered to 50000 for performance reasons.

+
+
+

Changed in version 4.2.6: The default value was changed to 500000.

+
+ +
+ +
+
+$cfg['MaxExactCountViews']
+
+++ + + + + + +
Type:integer
Default value:0
+

For VIEWs, since obtaining the exact count could have an impact on +performance, this value is the maximum to be displayed, using a +SELECT COUNT ... LIMIT. Setting this to 0 bypasses any row +counting.

+
+ +
+
+$cfg['NaturalOrder']
+
+++ + + + + + +
Type:boolean
Default value:true
+

Sorts database and table names according to natural order (for +example, t1, t2, t10). Currently implemented in the navigation panel +and in Database view, for the table list.

+
+ +
+
+$cfg['InitialSlidersState']
+
+++ + + + + + +
Type:string
Default value:'closed'
+

If set to 'closed', the visual sliders are initially in a closed +state. A value of 'open' does the reverse. To completely disable +all visual sliders, use 'disabled'.

+
+ +
+
+$cfg['UserprefsDisallow']
+
+++ + + + + + +
Type:array
Default value:array()
+

Contains names of configuration options (keys in $cfg array) that +users can’t set through user preferences. For possible values, refer +to clases under libraries/classes/Config/Forms/User/.

+
+ +
+
+$cfg['UserprefsDeveloperTab']
+
+++ + + + + + +
Type:boolean
Default value:false
+

Activates in the user preferences a tab containing options for +developers of phpMyAdmin.

+
+ +
+
+

Page titles

+
+
+$cfg['TitleTable']
+
+++ + + + + + +
Type:string
Default value:'@HTTP_HOST@ / @VSERVER@ / @DATABASE@ / @TABLE@ | @PHPMYADMIN@'
+
+ +
+
+$cfg['TitleDatabase']
+
+++ + + + + + +
Type:string
Default value:'@HTTP_HOST@ / @VSERVER@ / @DATABASE@ | @PHPMYADMIN@'
+
+ +
+
+$cfg['TitleServer']
+
+++ + + + + + +
Type:string
Default value:'@HTTP_HOST@ / @VSERVER@ | @PHPMYADMIN@'
+
+ +
+
+$cfg['TitleDefault']
+
+++ + + + + + +
Type:string
Default value:'@HTTP_HOST@ | @PHPMYADMIN@'
+

Allows you to specify window’s title bar. You can use 6.27 What format strings can I use?.

+
+ +
+
+

Theme manager settings

+
+
+$cfg['ThemeManager']
+
+++ + + + + + +
Type:boolean
Default value:true
+

Enables user-selectable themes. See 2.7 Using and creating themes.

+
+ +
+
+$cfg['ThemeDefault']
+
+++ + + + + + +
Type:string
Default value:'pmahomme'
+

The default theme (a subdirectory under ./themes/).

+
+ +
+
+$cfg['ThemePerServer']
+
+++ + + + + + +
Type:boolean
Default value:false
+

Whether to allow different theme for each server.

+
+ +
+
+$cfg['FontSize']
+
+++ + + + + + +
Type:string
Default value:‘82%’
+

Font size to use, is applied in CSS.

+
+ +
+
+

Default queries

+
+
+$cfg['DefaultQueryTable']
+
+++ + + + + + +
Type:string
Default value:'SELECT * FROM @TABLE@ WHERE 1'
+
+ +
+
+$cfg['DefaultQueryDatabase']
+
+++ + + + + + +
Type:string
Default value:''
+

Default queries that will be displayed in query boxes when user didn’t +specify any. You can use standard 6.27 What format strings can I use?.

+
+ +
+
+

MySQL settings

+
+
+$cfg['DefaultFunctions']
+
+++ + + + + + +
Type:array
Default value:array(...)
+

Functions selected by default when inserting/changing row, Functions +are defined for meta types as (FUNC_NUMBER, FUNC_DATE, FUNC_CHAR, +FUNC_SPATIAL, FUNC_UUID) and for first_timestamp, which is used +for first timestamp column in table.

+
+ +
+
+

Default options for Transformations

+
+
+$cfg['DefaultTransformations']
+
+++ + + + + + +
Type:array
Default value:An array with below listed key-values
+
+ +
+
+$cfg['DefaultTransformations']['Substring']
+
+++ + + + + + +
Type:array
Default value:array(0, ‘all’, ‘…’)
+
+ +
+
+$cfg['DefaultTransformations']['Bool2Text']
+
+++ + + + + + +
Type:array
Default value:array(‘T’, ‘F’)
+
+ +
+
+$cfg['DefaultTransformations']['External']
+
+++ + + + + + +
Type:array
Default value:array(0, ‘-f /dev/null -i -wrap -q’, 1, 1)
+
+ +
+
+$cfg['DefaultTransformations']['PreApPend']
+
+++ + + + + + +
Type:array
Default value:array(‘’, ‘’)
+
+ +
+
+$cfg['DefaultTransformations']['Hex']
+
+++ + + + + + +
Type:array
Default value:array(‘2’)
+
+ +
+
+$cfg['DefaultTransformations']['DateFormat']
+
+++ + + + + + +
Type:array
Default value:array(0, ‘’, ‘local’)
+
+ +
+
+$cfg['DefaultTransformations']['Inline']
+
+++ + + + + + +
Type:array
Default value:array(‘100’, 100)
+
+ +
+ +
+++ + + + + + +
Type:array
Default value:array(‘’, 100, 50)
+
+ +
+ +
+++ + + + + + +
Type:array
Default value:array(‘’, ‘’, ‘’)
+
+ +
+
+

Console settings

+
+

Note

+

These settings are mostly meant to be changed by user.

+
+
+
+$cfg['Console']['StartHistory']
+
+++ + + + + + +
Type:boolean
Default value:false
+

Show query history at start

+
+ +
+
+$cfg['Console']['AlwaysExpand']
+
+++ + + + + + +
Type:boolean
Default value:false
+

Always expand query messages

+
+ +
+
+$cfg['Console']['CurrentQuery']
+
+++ + + + + + +
Type:boolean
Default value:true
+

Show current browsing query

+
+ +
+
+$cfg['Console']['EnterExecutes']
+
+++ + + + + + +
Type:boolean
Default value:false
+

Execute queries on Enter and insert new line with Shift + Enter

+
+ +
+
+$cfg['Console']['DarkTheme']
+
+++ + + + + + +
Type:boolean
Default value:false
+

Switch to dark theme

+
+ +
+
+$cfg['Console']['Mode']
+
+++ + + + + + +
Type:string
Default value:‘info’
+

Console mode

+
+ +
+
+$cfg['Console']['Height']
+
+++ + + + + + +
Type:integer
Default value:92
+

Console height

+
+ +
+
+

Developer

+
+

Warning

+

These settings might have huge effect on performance or security.

+
+
+
+$cfg['DBG']
+
+++ + + + + + +
Type:array
Default value:array(...)
+
+ +
+
+$cfg['DBG']['sql']
+
+++ + + + + + +
Type:boolean
Default value:false
+

Enable logging queries and execution times to be +displayed in the console’s Debug SQL tab.

+
+ +
+
+$cfg['DBG']['sqllog']
+
+++ + + + + + +
Type:boolean
Default value:false
+

Enable logging of queries and execution times to the syslog. +Requires $cfg['DBG']['sql'] to be enabled.

+
+ +
+
+$cfg['DBG']['demo']
+
+++ + + + + + +
Type:boolean
Default value:false
+

Enable to let server present itself as demo server. +This is used for phpMyAdmin demo server.

+

It currently changes following behavior:

+
    +
  • There is welcome message on the main page.
  • +
  • There is footer information about demo server and used git revision.
  • +
  • The setup script is enabled even with existing configuration.
  • +
  • The setup does not try to connect to the MySQL server.
  • +
+
+ +
+
+$cfg['DBG']['simple2fa']
+
+++ + + + + + +
Type:boolean
Default value:false
+

Can be used for testing two-factor authentication using Simple two-factor authentication.

+
+ +
+
+

Examples

+

See following configuration snippets for typical setups of phpMyAdmin.

+
+

Basic example

+

Example configuration file, which can be copied to config.inc.php to +get some core configuration layout; it is distributed with phpMyAdmin as +config.sample.inc.php. Please note that it does not contain all +configuration options, only the most frequently used ones.

+
<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * phpMyAdmin sample configuration, you can use it as base for
+ * manual configuration. For easier setup you can use setup/
+ *
+ * All directives are explained in documentation in the doc/ folder
+ * or at <https://docs.phpmyadmin.net/>.
+ *
+ * @package PhpMyAdmin
+ */
+
+/**
+ * This is needed for cookie based authentication to encrypt password in
+ * cookie. Needs to be 32 chars long.
+ */
+$cfg['blowfish_secret'] = ''; /* YOU MUST FILL IN THIS FOR COOKIE AUTH! */
+
+/**
+ * Servers configuration
+ */
+$i = 0;
+
+/**
+ * First server
+ */
+$i++;
+/* Authentication type */
+$cfg['Servers'][$i]['auth_type'] = 'cookie';
+/* Server parameters */
+$cfg['Servers'][$i]['host'] = 'localhost';
+$cfg['Servers'][$i]['compress'] = false;
+$cfg['Servers'][$i]['AllowNoPassword'] = false;
+
+/**
+ * phpMyAdmin configuration storage settings.
+ */
+
+/* User used to manipulate with storage */
+// $cfg['Servers'][$i]['controlhost'] = '';
+// $cfg['Servers'][$i]['controlport'] = '';
+// $cfg['Servers'][$i]['controluser'] = 'pma';
+// $cfg['Servers'][$i]['controlpass'] = 'pmapass';
+
+/* Storage database and tables */
+// $cfg['Servers'][$i]['pmadb'] = 'phpmyadmin';
+// $cfg['Servers'][$i]['bookmarktable'] = 'pma__bookmark';
+// $cfg['Servers'][$i]['relation'] = 'pma__relation';
+// $cfg['Servers'][$i]['table_info'] = 'pma__table_info';
+// $cfg['Servers'][$i]['table_coords'] = 'pma__table_coords';
+// $cfg['Servers'][$i]['pdf_pages'] = 'pma__pdf_pages';
+// $cfg['Servers'][$i]['column_info'] = 'pma__column_info';
+// $cfg['Servers'][$i]['history'] = 'pma__history';
+// $cfg['Servers'][$i]['table_uiprefs'] = 'pma__table_uiprefs';
+// $cfg['Servers'][$i]['tracking'] = 'pma__tracking';
+// $cfg['Servers'][$i]['userconfig'] = 'pma__userconfig';
+// $cfg['Servers'][$i]['recent'] = 'pma__recent';
+// $cfg['Servers'][$i]['favorite'] = 'pma__favorite';
+// $cfg['Servers'][$i]['users'] = 'pma__users';
+// $cfg['Servers'][$i]['usergroups'] = 'pma__usergroups';
+// $cfg['Servers'][$i]['navigationhiding'] = 'pma__navigationhiding';
+// $cfg['Servers'][$i]['savedsearches'] = 'pma__savedsearches';
+// $cfg['Servers'][$i]['central_columns'] = 'pma__central_columns';
+// $cfg['Servers'][$i]['designer_settings'] = 'pma__designer_settings';
+// $cfg['Servers'][$i]['export_templates'] = 'pma__export_templates';
+
+/**
+ * End of servers configuration
+ */
+
+/**
+ * Directories for saving/loading files from server
+ */
+$cfg['UploadDir'] = '';
+$cfg['SaveDir'] = '';
+
+/**
+ * Whether to display icons or text or both icons and text in table row
+ * action segment. Value can be either of 'icons', 'text' or 'both'.
+ * default = 'both'
+ */
+//$cfg['RowActionType'] = 'icons';
+
+/**
+ * Defines whether a user should be displayed a "show all (records)"
+ * button in browse mode or not.
+ * default = false
+ */
+//$cfg['ShowAll'] = true;
+
+/**
+ * Number of rows displayed when browsing a result set. If the result
+ * set contains more rows, "Previous" and "Next".
+ * Possible values: 25, 50, 100, 250, 500
+ * default = 25
+ */
+//$cfg['MaxRows'] = 50;
+
+/**
+ * Disallow editing of binary fields
+ * valid values are:
+ *   false    allow editing
+ *   'blob'   allow editing except for BLOB fields
+ *   'noblob' disallow editing except for BLOB fields
+ *   'all'    disallow editing
+ * default = 'blob'
+ */
+//$cfg['ProtectBinary'] = false;
+
+/**
+ * Default language to use, if not browser-defined or user-defined
+ * (you find all languages in the locale folder)
+ * uncomment the desired line:
+ * default = 'en'
+ */
+//$cfg['DefaultLang'] = 'en';
+//$cfg['DefaultLang'] = 'de';
+
+/**
+ * How many columns should be used for table display of a database?
+ * (a value larger than 1 results in some information being hidden)
+ * default = 1
+ */
+//$cfg['PropertiesNumColumns'] = 2;
+
+/**
+ * Set to true if you want DB-based query history.If false, this utilizes
+ * JS-routines to display query history (lost by window close)
+ *
+ * This requires configuration storage enabled, see above.
+ * default = false
+ */
+//$cfg['QueryHistoryDB'] = true;
+
+/**
+ * When using DB-based query history, how many entries should be kept?
+ * default = 25
+ */
+//$cfg['QueryHistoryMax'] = 100;
+
+/**
+ * Whether or not to query the user before sending the error report to
+ * the phpMyAdmin team when a JavaScript error occurs
+ *
+ * Available options
+ * ('ask' | 'always' | 'never')
+ * default = 'ask'
+ */
+//$cfg['SendErrorReports'] = 'always';
+
+/**
+ * You can find more configuration options in the documentation
+ * in the doc/ folder or at <https://docs.phpmyadmin.net/>.
+ */
+
+
+
+

Warning

+

Don’t use the controluser ‘pma’ if it does not yet exist and don’t use ‘pmapass’ +as password.

+
+
+
+

Example for signon authentication

+

This example uses examples/signon.php to demonstrate usage of Signon authentication mode:

+
<?php
+$i = 0;
+$i++;
+$cfg['Servers'][$i]['extension']     = 'mysqli';
+$cfg['Servers'][$i]['auth_type']     = 'signon';
+$cfg['Servers'][$i]['SignonSession'] = 'SignonSession';
+$cfg['Servers'][$i]['SignonURL']     = 'examples/signon.php';
+?>`
+
+
+
+
+

Example for IP address limited autologin

+

If you want to automatically login when accessing phpMyAdmin locally while asking +for a password when accessing remotely, you can achieve it using following snippet:

+
if ($_SERVER["REMOTE_ADDR"] == "127.0.0.1") {
+    $cfg['Servers'][$i]['auth_type'] = 'config';
+    $cfg['Servers'][$i]['user'] = 'root';
+    $cfg['Servers'][$i]['password'] = 'yourpassword';
+} else {
+    $cfg['Servers'][$i]['auth_type'] = 'cookie';
+}
+
+
+
+

Note

+

Filtering based on IP addresses isn’t reliable over the internet, use it +only for local address.

+
+
+
+

Example for using multiple MySQL servers

+

You can configure any number of servers using $cfg['Servers'], +following example shows two of them:

+
<?php
+$cfg['blowfish_secret']='multiServerExample70518';
+//any string of your choice
+$i = 0;
+
+$i++; // server 1 :
+$cfg['Servers'][$i]['auth_type'] = 'cookie';
+$cfg['Servers'][$i]['verbose']   = 'no1';
+$cfg['Servers'][$i]['host']      = 'localhost';
+$cfg['Servers'][$i]['extension'] = 'mysqli';
+// more options for #1 ...
+
+$i++; // server 2 :
+$cfg['Servers'][$i]['auth_type'] = 'cookie';
+$cfg['Servers'][$i]['verbose']   = 'no2';
+$cfg['Servers'][$i]['host']      = 'remote.host.addr';//or ip:'10.9.8.1'
+// this server must allow remote clients, e.g., host 10.9.8.%
+// not only in mysql.host but also in the startup configuration
+$cfg['Servers'][$i]['extension'] = 'mysqli';
+// more options for #2 ...
+
+// end of server sections
+$cfg['ServerDefault'] = 0; // to choose the server on startup
+
+// further general options ...
+?>
+
+
+
+
+

Google Cloud SQL with SSL

+

To connect to Google Could SQL, you currently need to disable certificate +verification. This is caused by the certficate being issued for CN matching +your instance name, but you connect to an IP address and PHP tries to match +these two. With verfication you end up with error message like:

+
Peer certificate CN=`api-project-851612429544:pmatest' did not match expected CN=`8.8.8.8'
+
+
+
+

Warning

+

With disabled verification your traffic is encrypted, but you’re open to +man in the middle attacks.

+
+

To connect phpMyAdmin to Google Cloud SQL using SSL download the client and +server certificates and tell phpMyAdmin to use them:

+
// IP address of your instance
+$cfg['Servers'][$i]['host'] = '8.8.8.8';
+// Use SSL for connection
+$cfg['Servers'][$i]['ssl'] = true;
+// Client secret key
+$cfg['Servers'][$i]['ssl_key'] = '../client-key.pem';
+// Client certificate
+$cfg['Servers'][$i]['ssl_cert'] = '../client-cert.pem';
+// Server certification authority
+$cfg['Servers'][$i]['ssl_ca'] = '../server-ca.pem';
+// Disable SSL verification (see above note)
+$cfg['Servers'][$i]['ssl_verify'] = false;
+
+
+ +
+
+
+ + +
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/php/apps/phpmyadmin49/html/doc/html/copyright.html b/php/apps/phpmyadmin49/html/doc/html/copyright.html new file mode 100644 index 00000000..8dd4c73c --- /dev/null +++ b/php/apps/phpmyadmin49/html/doc/html/copyright.html @@ -0,0 +1,151 @@ + + + + + + + + Copyright — phpMyAdmin 4.9.1 documentation + + + + + + + + + + + + + + + + + + +
+
+
+
+ + + + +
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/php/apps/phpmyadmin49/html/doc/html/credits.html b/php/apps/phpmyadmin49/html/doc/html/credits.html new file mode 100644 index 00000000..f64b21c4 --- /dev/null +++ b/php/apps/phpmyadmin49/html/doc/html/credits.html @@ -0,0 +1,1353 @@ + + + + + + + + Credits — phpMyAdmin 4.9.1 documentation + + + + + + + + + + + + + + + + + + +
+
+
+
+ +
+

Credits

+
+

Credits, in chronological order

+
    +
  • Tobias Ratschiller <tobias_at_ratschiller.com>
      +
    • creator of the phpMyAdmin project
    • +
    • maintainer from 1998 to summer 2000
    • +
    +
  • +
  • Marc Delisle <marc_at_infomarc.info>
      +
    • multi-language version in December 1998
    • +
    • various fixes and improvements
    • +
    • first version of the SQL analyser (most of it)
    • +
    • maintainer from 2001 to 2015
    • +
    +
  • +
  • Olivier Müller <om_at_omnis.ch>
      +
    • started SourceForge phpMyAdmin project in March 2001
    • +
    • sync’ed different existing CVS trees with new features and bugfixes
    • +
    • multi-language improvements, dynamic language selection
    • +
    • many bugfixes and improvements
    • +
    +
  • +
  • Loïc Chapeaux <lolo_at_phpheaven.net>
      +
    • rewrote and optimized JavaScript, DHTML and DOM stuff
    • +
    • rewrote the scripts so they fit the PEAR coding standards and +generate XHTML1.0 and CSS2 compliant codes
    • +
    • improved the language detection system
    • +
    • many bugfixes and improvements
    • +
    +
  • +
  • Robin Johnson <robbat2_at_users.sourceforge.net>
      +
    • database maintenance controls
    • +
    • table type code
    • +
    • Host authentication IP Allow/Deny
    • +
    • DB-based configuration (Not completed)
    • +
    • SQL parser and pretty-printer
    • +
    • SQL validator
    • +
    • many bugfixes and improvements
    • +
    +
  • +
  • Armel Fauveau <armel.fauveau_at_globalis-ms.com>
      +
    • bookmarks feature
    • +
    • multiple dump feature
    • +
    • gzip dump feature
    • +
    • zip dump feature
    • +
    +
  • +
  • Geert Lund <glund_at_silversoft.dk>
      +
    • various fixes
    • +
    • moderator of the phpMyAdmin former users forum at phpwizard.net
    • +
    +
  • +
  • Korakot Chaovavanich <korakot_at_iname.com>
      +
    • “insert as new row” feature
    • +
    +
  • +
  • Pete Kelly <webmaster_at_trafficg.com>
      +
    • rewrote and fix dump code
    • +
    • bugfixes
    • +
    +
  • +
  • Steve Alberty <alberty_at_neptunlabs.de>
      +
    • rewrote dump code for PHP4
    • +
    • mySQL table statistics
    • +
    • bugfixes
    • +
    +
  • +
  • Benjamin Gandon <gandon_at_isia.cma.fr>
      +
    • main author of the version 2.1.0.1
    • +
    • bugfixes
    • +
    +
  • +
  • Alexander M. Turek <me_at_derrabus.de>
      +
    • MySQL 4.0 / 4.1 / 5.0 compatibility
    • +
    • abstract database interface (PMA_DBI) with MySQLi support
    • +
    • privileges administration
    • +
    • XML exports
    • +
    • various features and fixes
    • +
    • German language file updates
    • +
    +
  • +
  • Mike Beck <mike.beck_at_web.de>
      +
    • automatic joins in QBE
    • +
    • links column in printview
    • +
    • Relation view
    • +
    +
  • +
  • Michal Čihař <michal_at_cihar.com>
      +
    • enhanced index creation/display feature
    • +
    • feature to use a different charset for HTML than for MySQL
    • +
    • improvements of export feature
    • +
    • various features and fixes
    • +
    • Czech language file updates
    • +
    • created current website for phpMyAdmin
    • +
    +
  • +
  • Christophe Gesché from the “MySQL Form Generator for PHPMyAdmin” +(https://sourceforge.net/projects/phpmysqlformgen/)
      +
    • suggested the patch for multiple table printviews
    • +
    +
  • +
  • Garvin Hicking <me_at_supergarv.de>
      +
    • built the patch for vertical display of table rows
    • +
    • built the Javascript based Query window + SQL history
    • +
    • Improvement of column/db comments
    • +
    • (MIME)-Transformations for columns
    • +
    • Use custom alias names for Databases in left frame
    • +
    • hierarchical/nested table display
    • +
    • PDF-scratchboard for WYSIWYG- +distribution of PDF relations
    • +
    • new icon sets
    • +
    • vertical display of column properties page
    • +
    • some bugfixes, features, support, German language additions
    • +
    +
  • +
  • Yukihiro Kawada <kawada_at_den.fujifilm.co.jp>
      +
    • japanese kanji encoding conversion feature
    • +
    +
  • +
  • Piotr Roszatycki <d3xter_at_users.sourceforge.net> and Dan Wilson
      +
    • the Cookie authentication mode
    • +
    +
  • +
  • Axel Sander <n8falke_at_users.sourceforge.net>
      +
    • table relation-links feature
    • +
    +
  • +
  • Maxime Delorme <delorme.maxime_at_free.fr>
      +
    • PDF schema output, thanks also to +Olivier Plathey for the “FPDF” library (see <http://www.fpdf.org/>), Steven +Wittens for the “UFPDF” library and +Nicola Asuni for the “TCPDF” library (see <https://tcpdf.org/>).
    • +
    +
  • +
  • Olof Edlund <olof.edlund_at_upright.se>
      +
    • SQL validator server
    • +
    +
  • +
  • Ivan R. Lanin <ivanlanin_at_users.sourceforge.net>
      +
    • phpMyAdmin logo (until June 2004)
    • +
    +
  • +
  • Mike Cochrane <mike_at_graftonhall.co.nz>
      +
    • blowfish library from the Horde project (withdrawn in release 4.0)
    • +
    +
  • +
  • Marcel Tschopp <ne0x_at_users.sourceforge.net>
      +
    • mysqli support
    • +
    • many bugfixes and improvements
    • +
    +
  • +
  • Nicola Asuni (Tecnick.com) +
  • +
  • Michael Keck <mkkeck_at_users.sourceforge.net>
      +
    • redesign for 2.6.0
    • +
    • phpMyAdmin sailboat logo (June 2004)
    • +
    +
  • +
  • Mathias Landhäußer
      +
    • Representation at conferences
    • +
    +
  • +
  • Sebastian Mendel <cybot_tm_at_users.sourceforge.net>
      +
    • interface improvements
    • +
    • various bugfixes
    • +
    +
  • +
  • Ivan A Kirillov
      +
    • new relations Designer
    • +
    +
  • +
  • Raj Kissu Rajandran (Google Summer of Code 2008)
      +
    • BLOBstreaming support (withdrawn in release 4.0)
    • +
    +
  • +
  • Piotr Przybylski (Google Summer of Code 2008, 2010 and 2011)
      +
    • improved setup script
    • +
    • user preferences
    • +
    • Drizzle support
    • +
    +
  • +
  • Derek Schaefer (Google Summer of Code 2009)
      +
    • Improved the import system
    • +
    +
  • +
  • Alexander Rutkowski (Google Summer of Code 2009)
      +
    • Tracking mechanism
    • +
    +
  • +
  • Zahra Naeem (Google Summer of Code 2009)
      +
    • Synchronization feature (removed in release 4.0)
    • +
    +
  • +
  • Tomáš Srnka (Google Summer of Code 2009)
      +
    • Replication support
    • +
    +
  • +
  • Muhammad Adnan (Google Summer of Code 2010)
      +
    • Relation schema export to multiple formats
    • +
    +
  • +
  • Lori Lee (Google Summer of Code 2010)
      +
    • User interface improvements
    • +
    • ENUM/SET editor
    • +
    • Simplified interface for export/import
    • +
    +
  • +
  • Ninad Pundalik (Google Summer of Code 2010)
      +
    • AJAXifying the interface
    • +
    +
  • +
  • Martynas Mickevičius (Google Summer of Code 2010)
      +
    • Charts
    • +
    +
  • +
  • Barrie Leslie
      +
    • BLOBstreaming support with PBMS PHP extension (withdrawn in release +4.0)
    • +
    +
  • +
  • Ankit Gupta (Google Summer of Code 2010)
      +
    • Visual query builder
    • +
    +
  • +
  • Madhura Jayaratne (Google Summer of Code 2011)
      +
    • OpenGIS support
    • +
    +
  • +
  • Ammar Yasir (Google Summer of Code 2011)
      +
    • Zoom search
    • +
    +
  • +
  • Aris Feryanto (Google Summer of Code 2011)
      +
    • Browse-mode improvements
    • +
    +
  • +
  • Thilanka Kaushalya (Google Summer of Code 2011)
      +
    • AJAXification
    • +
    +
  • +
  • Tyron Madlener (Google Summer of Code 2011)
      +
    • Query statistics and charts for the status page
    • +
    +
  • +
  • Zarubin Stas (Google Summer of Code 2011)
      +
    • Automated testing
    • +
    +
  • +
  • Rouslan Placella (Google Summer of Code 2011 and 2012)
      +
    • Improved support for Stored Routines, Triggers and Events
    • +
    • Italian translation updates
    • +
    • Removal of frames, new navigation
    • +
    +
  • +
  • Dieter Adriaenssens
      +
    • Various bugfixes
    • +
    • Dutch translation updates
    • +
    +
  • +
  • Alex Marin (Google Summer of Code 2012)
      +
    • New plugins and properties system
    • +
    +
  • +
  • Thilina Buddika Abeyrathna (Google Summer of Code 2012)
      +
    • Refactoring
    • +
    +
  • +
  • Atul Pratap Singh (Google Summer of Code 2012)
      +
    • Refactoring
    • +
    +
  • +
  • Chanaka Indrajith (Google Summer of Code 2012)
      +
    • Refactoring
    • +
    +
  • +
  • Yasitha Pandithawatta (Google Summer of Code 2012)
      +
    • Automated testing
    • +
    +
  • +
  • Jim Wigginton (phpseclib.sourceforge.net)
      +
    • phpseclib
    • +
    +
  • +
  • Bin Zu (Google Summer of Code 2013)
      +
    • Refactoring
    • +
    +
  • +
  • Supun Nakandala (Google Summer of Code 2013)
      +
    • Refactoring
    • +
    +
  • +
  • Mohamed Ashraf (Google Summer of Code 2013)
      +
    • AJAX error reporting
    • +
    +
  • +
  • Adam Kang (Google Summer of Code 2013)
      +
    • Automated testing
    • +
    +
  • +
  • Ayush Chaudhary (Google Summer of Code 2013)
      +
    • Automated testing
    • +
    +
  • +
  • Kasun Chathuranga (Google Summer of Code 2013)
      +
    • Interface improvements
    • +
    +
  • +
  • Hugues Peccatte
      +
    • Load/save query by example (database search bookmarks)
    • +
    +
  • +
  • Smita Kumari (Google Summer of Code 2014)
      +
    • Central list of columns
    • +
    • Improve table structure (normalization)
    • +
    +
  • +
  • Ashutosh Dhundhara (Google Summer of Code 2014)
      +
    • Interface improvements
    • +
    +
  • +
  • Dhananjay Nakrani (Google Summer of Code 2014)
      +
    • PHP error reporting
    • +
    +
  • +
  • Edward Cheng (Google Summer of Code 2014)
      +
    • SQL Query Console
    • +
    +
  • +
  • Kankanamge Bimal Yashodha (Google Summer of Code 2014)
      +
    • Refactoring: Designer/schema integration
    • +
    +
  • +
  • Chirayu Chiripal (Google Summer of Code 2014)
      +
    • Custom field handlers (Input based MIME transformations)
    • +
    • Export with table/column name changes
    • +
    +
  • +
  • Dan Ungureanu (Google Summer of Code 2015)
      +
    • New parser and analyzer
    • +
    +
  • +
  • Nisarg Jhaveri (Google Summer of Code 2015)
      +
    • Page-related settings
    • +
    • SQL debugging integration to the Console
    • +
    • Other UI improvements
    • +
    +
  • +
  • Deven Bansod (Google Summer of Code 2015)
      +
    • Print view using CSS
    • +
    • Other UI improvements and new features
    • +
    +
  • +
  • Deven Bansod (Google Summer of Code 2017)
      +
    • Improvements to the Error Reporting Server
    • +
    • Improved Selenium testing
    • +
    +
  • +
  • Manish Bisht (Google Summer of Code 2017)
      +
    • Mobile user interface
    • +
    • Remove inline JavaScript code
    • +
    • Other UI improvements
    • +
    +
  • +
  • Raghuram Vadapalli (Google Summer of Code 2017)
      +
    • Multi-table query interface
    • +
    • Allow Designer to work with tables from other databases
    • +
    • Other UI improvements
    • +
    +
  • +
+

And also to the following people who have contributed minor changes, +enhancements, bugfixes or support for a new language since version +2.1.0:

+

Bora Alioglu, Ricardo ?, Sven-Erik Andersen, Alessandro Astarita, +Péter Bakondy, Borges Botelho, Olivier Bussier, Neil Darlow, Mats +Engstrom, Ian Davidson, Laurent Dhima, Kristof Hamann, Thomas Kläger, +Lubos Klokner, Martin Marconcini, Girish Nair, David Nordenberg, +Andreas Pauley, Bernard M. Piller, Laurent Haas, “Sakamoto”, Yuval +Sarna, www.securereality.com.au, Alexis Soulard, Alvar Soome, Siu Sun, +Peter Svec, Michael Tacelosky, Rachim Tamsjadi, Kositer Uros, Luís V., +Martijn W. van der Lee, Algis Vainauskas, Daniel Villanueva, Vinay, +Ignacio Vazquez-Abrams, Chee Wai, Jakub Wilk, Thomas Michael +Winningham, Vilius Zigmantas, “Manuzhai”.

+
+
+

Translators

+

Following people have contributed to translation of phpMyAdmin:

+
    +
  • Albanian

    +
    +
      +
    • Arben Çokaj <acokaj_at_shkoder.net>
    • +
    +
    +
  • +
  • Arabic

    +
    +
      +
    • Ahmed Saleh Abd El-Raouf Ismae <a.saleh.ismael_at_gmail.com>
    • +
    • Ahmed Saad <egbrave_at_hotmail.com>
    • +
    • hassan mokhtari <persiste1_at_gmail.com>
    • +
    +
    +
  • +
  • Armenian

    +
    +
      +
    • Andrey Aleksanyants <aaleksanyants_at_yahoo.com>
    • +
    +
    +
  • +
  • Azerbaijani

    +
    +
      +
    • Mircəlal <01youknowme_at_gmail.com>
    • +
    • Huseyn <huseyn_esgerov_at_mail.ru>
    • +
    • Sevdimali İsa <sevdimaliisayev_at_mail.ru>
    • +
    • Jafar <sharifov_at_programmer.net>
    • +
    +
    +
  • +
  • Belarusian

    +
    +
      +
    • Viktar Palstsiuk <vipals_at_gmail.com>
    • +
    +
    +
  • +
  • Bulgarian

    +
    +
      +
    • Boyan Kehayov <bkehayov_at_gmail.com>
    • +
    • Valter Georgiev <blagynchy_at_gmail.com>
    • +
    • Valentin Mladenov <hudsonvsm_at_gmail.com>
    • +
    • P <plamen_mbx_at_yahoo.com>
    • +
    • krasimir <vip_at_krasio-valia.com>
    • +
    +
    +
  • +
  • Catalan

    +
    +
      +
    • josep constanti <jconstanti_at_yahoo.es>
    • +
    • Xavier Navarro <xvnavarro_at_gmail.com>
    • +
    +
    +
  • +
  • Chinese (China)

    +
    +
      +
    • Vincent Lau <3092849_at_qq.com>
    • +
    • Zheng Dan <clanboy_at_163.com>
    • +
    • disorderman <disorderman_at_qq.com>
    • +
    • Rex Lee <duguying2008_at_gmail.com>
    • +
    • <fundawang_at_gmail.com>
    • +
    • popcorner <memoword_at_163.com>
    • +
    • Yizhou Qiang <qyz.yswy_at_hotmail.com>
    • +
    • zz <tczzjin_at_gmail.com>
    • +
    • Terry Weng <wengshiyu_at_gmail.com>
    • +
    • whh <whhlcj_at_126.com>
    • +
    +
    +
  • +
  • Chinese (Taiwan)

    +
    +
      +
    • Albert Song <albb0920_at_gmail.com>
    • +
    • Chien Wei Lin <cwlin0416_at_gmail.com>
    • +
    • Peter Dave Hello <xs910203_at_gmail.com>
    • +
    +
    +
  • +
  • Colognian

    +
    +
      +
    • Purodha <publi_at_web.de>
    • +
    +
    +
  • +
  • Czech

    +
    +
      +
    • Aleš Hakl <ales_at_hakl.net>
    • +
    • Dalibor Straka <dalibor.straka3_at_gmail.com>
    • +
    • Martin Vidner <martin_at_vidner.net>
    • +
    • Ondra Šimeček <ondrasek.simecek_at_gmail.com>
    • +
    • Jan Palider <palider_at_seznam.cz>
    • +
    • Petr Kateřiňák <petr.katerinak_at_gmail.com>
    • +
    +
    +
  • +
  • Danish

    +
    +
      +
    • Aputsiaĸ Niels Janussen <aj_at_isit.gl>
    • +
    • Dennis Jakobsen <dennis.jakobsen_at_gmail.com>
    • +
    • Jonas <jonas.den.smarte_at_gmail.com>
    • +
    • Claus Svalekjaer <just.my.smtp.server_at_gmail.com>
    • +
    +
    +
  • +
  • Dutch

    +
    +
      +
      1. +
      2. Voogt <a.voogt_at_hccnet.nl>
      3. +
      +
    • +
    • dingo thirteen <dingo13_at_gmail.com>
    • +
    • Robin van der Vliet <info_at_robinvandervliet.nl>
    • +
    • Dieter Adriaenssens <ruleant_at_users.sourceforge.net>
    • +
    • Niko Strijbol <strijbol.niko_at_gmail.com>
    • +
    +
    +
  • +
  • English (United Kingdom)

    +
    +
      +
    • Dries Verschuere <dries.verschuere_at_outlook.com>
    • +
    • Francisco Rocha <j.francisco.o.rocha_at_zoho.com>
    • +
    • Marc Delisle <marc_at_infomarc.info>
    • +
    • Marek Tomaštík <tomastik.m_at_gmail.com>
    • +
    +
    +
  • +
  • Esperanto

    +
    +
      +
    • Eliovir <eliovir_at_gmail.com>
    • +
    • Robin van der Vliet <info_at_robinvandervliet.nl>
    • +
    +
    +
  • +
  • Estonian

    +
    +
      +
    • Kristjan Räts <kristjanrats_at_gmail.com>
    • +
    +
    +
  • +
  • Finnish

    +
    +
      +
    • Juha Remes <jremes_at_outlook.com>
    • +
    • Lari Oesch <lari_at_oesch.me>
    • +
    +
    +
  • +
  • French

    +
    +
      +
    • Marc Delisle <marc_at_infomarc.info>
    • +
    +
    +
  • +
  • Frisian

    +
    +
      +
    • Robin van der Vliet <info_at_robinvandervliet.nl>
    • +
    +
    +
  • +
  • Galician

    +
    +
      +
    • Xosé Calvo <xosecalvo_at_gmail.com>
    • +
    +
    +
  • +
  • German

    +
    +
      +
    • Julian Ladisch <github.com-t3if_at_ladisch.de>
    • +
    • Jan Erik Zassenhaus <jan.zassenhaus_at_jgerman.de>
    • +
    • Lasse Goericke <lasse_at_mydom.de>
    • +
    • Matthias Bluthardt <matthias_at_bluthardt.org>
    • +
    • Michael Koch <michael.koch_at_enough.de>
    • +
    • Ann + J.M. <phpMyAdmin_at_ZweiSteinSoft.de>
    • +
    • <pma_at_sebastianmendel.de>
    • +
    • Phillip Rohmberger <rohmberger_at_hotmail.de>
    • +
    • Hauke Henningsen <sqrt_at_entless.org>
    • +
    +
    +
  • +
  • Greek

    +
    +
      +
    • Παναγιώτης Παπάζογλου <papaz_p_at_yahoo.com>
    • +
    +
    +
  • +
  • Hebrew

    +
    +
      +
    • Moshe Harush <mmh15_at_windowslive.com>
    • +
    • Yaron Shahrabani <sh.yaron_at_gmail.com>
    • +
    • Eyal Visoker <visokereyal_at_gmail.com>
    • +
    +
    +
  • +
  • Hindi

    +
    +
      +
    • Atul Pratap Singh <atulpratapsingh05_at_gmail.com>
    • +
    • Yogeshwar <charanyogeshwar_at_gmail.com>
    • +
    • Deven Bansod <devenbansod.bits_at_gmail.com>
    • +
    • Kushagra Pandey <kushagra4296_at_gmail.com>
    • +
    • Nisarg Jhaveri <nisargjhaveri_at_gmail.com>
    • +
    • Roohan Kazi <roohan_cena_at_yahoo.co.in>
    • +
    • Yugal Pantola <yug.scorpio_at_gmail.com>
    • +
    +
    +
  • +
  • Hungarian

    +
    +
      +
    • Akos Eros <erosakos02_at_gmail.com>
    • +
    • Dániel Tóth <leedermeister_at_gmail.com>
    • +
    • Szász Attila <undernetangel_at_gmail.com>
    • +
    • Balázs Úr <urbalazs_at_gmail.com>
    • +
    +
    +
  • +
  • Indonesian

    +
    +
      +
    • Deky Arifianto <Deky40_at_gmail.com>
    • +
    • Andika Triwidada <andika_at_gmail.com>
    • +
    • Dadan Setia <da2n_s_at_yahoo.co.id>
    • +
    • Dadan Setia <dadan.setia_at_gmail.com>
    • +
    • Yohanes Edwin <edwin_at_yohanesedwin.com>
    • +
    • Fadhiil Rachman <fadhiilrachman_at_gmail.com>
    • +
    • Benny <tarzq28_at_gmail.com>
    • +
    • Tommy Surbakti <tommy_at_surbakti.net>
    • +
    • Zufar Fathi Suhardi <zufar.bogor_at_gmail.com>
    • +
    +
    +
  • +
  • Interlingua

    +
    +
      +
    • Giovanni Sora <g.sora_at_tiscali.it>
    • +
    +
    +
  • +
  • Italian

    +
    +
      +
    • Francesco Saverio Giacobazzi <francesco.giacobazzi_at_ferrania.it>
    • +
    • Marco Pozzato <ironpotts_at_gmail.com>
    • +
    • Stefano Martinelli <stefano.ste.martinelli_at_gmail.com>
    • +
    +
    +
  • +
  • Japanese

    +
    +
      +
    • k725 <alexalex.kobayashi_at_gmail.com>
    • +
    • Hiroshi Chiyokawa <hiroshi.chiyokawa_at_gmail.com>
    • +
    • Masahiko HISAKAWA <orzkun_at_ageage.jp>
    • +
    • worldwideskier <worldwideskier_at_yahoo.co.jp>
    • +
    +
    +
  • +
  • Kannada

    +
    +
      +
    • Robin van der Vliet <info_at_robinvandervliet.nl>
    • +
    • Shameem Ahmed A Mulla <shameem.sam_at_gmail.com>
    • +
    +
    +
  • +
  • Korean

    +
    +
      +
    • Bumsoo Kim <bskim45_at_gmail.com>
    • +
    • Kyeong Su Shin <cdac1234_at_gmail.com>
    • +
    • Dongyoung Kim <dckyoung_at_gmail.com>
    • +
    • Myung-han Yu <greatymh_at_gmail.com>
    • +
    • JongDeok <human.zion_at_gmail.com>
    • +
    • Yong Kim <kim_at_nhn.com>
    • +
    • 이경준 <kyungjun2_at_gmail.com>
    • +
    • Seongki Shin <skshin_at_gmail.com>
    • +
    • Yoon Bum-Jong <virusyoon_at_gmail.com>
    • +
    • Koo Youngmin <youngminz.kr_at_gmail.com>
    • +
    +
    +
  • +
  • Kurdish Sorani

    +
    +
      +
    • Alan Hilal <alan.hilal94_at_gmail.com>
    • +
    • Aso Naderi <aso.naderi_at_gmail.com>
    • +
    • muhammad <esy_vb_at_yahoo.com>
    • +
    • Zrng Abdulla <zhyarabdulla94_at_gmail.com>
    • +
    +
    +
  • +
  • Latvian

    +
    +
      +
    • Latvian TV <dnighttv_at_gmail.com>
    • +
    • Edgars Neimanis <edgarsneims5092_at_inbox.lv>
    • +
    • Ukko <perkontevs_at_gmail.com>
    • +
    +
    +
  • +
  • Limburgish

    +
    +
      +
    • Robin van der Vliet <info_at_robinvandervliet.nl>
    • +
    +
    +
  • +
  • Lithuanian

    +
    +
      +
    • Vytautas Motuzas <v.motuzas_at_gmail.com>
    • +
    +
    +
  • +
  • Malay

    +
    +
      +
    • Amir Hamzah <amir.overlord666_at_gmail.com>
    • +
    • diprofinfiniti <anonynuine-999_at_yahoo.com>
    • +
    +
    +
  • +
  • Nepali

    +
    +
      +
    • Nabin Ghimire <nnabinn_at_hotmail.com>
    • +
    +
    +
  • +
  • Norwegian Bokmål

    +
    +
      +
    • Børge Holm-Wennberg <borge947_at_gmail.com>
    • +
    • Tor Stokkan <danorse_at_gmail.com>
    • +
    • Espen Frøyshov <efroys_at_gmail.com>
    • +
    • Kurt Eilertsen <kurt_at_kheds.com>
    • +
    • Christoffer Haugom <ph3n1x.nobody_at_gmail.com>
    • +
    • Sebastian <sebastian_at_sgundersen.com>
    • +
    • Tomas <tomas_at_tomasruud.com>
    • +
    +
    +
  • +
  • Persian

    +
    +
      +
    • ashkan shirian <ashkan.shirian_at_gmail.com>
    • +
    • HM <goodlinuxuser_at_chmail.ir>
    • +
    +
    +
  • +
  • Polish

    +
    +
      +
    • Andrzej <andrzej_at_kynu.pl>
    • +
    • Przemo <info_at_opsbielany.waw.pl>
    • +
    • Krystian Biesaga <krystian4842_at_gmail.com>
    • +
    • Maciej Gryniuk <maciejka45_at_gmail.com>
    • +
    • Michał VonFlynee <vonflynee_at_gmail.com>
    • +
    +
    +
  • +
  • Portuguese

    +
    +
      +
    • Alexandre Badalo <alexandre.badalo_at_sapo.pt>
    • +
    • João Rodrigues <geral_at_jonilive.com>
    • +
    • Pedro Ribeiro <p.m42.ribeiro_at_gmail.com>
    • +
    • Sandro Amaral <sandro123iv_at_gmail.com>
    • +
    +
    +
  • +
  • Portuguese (Brazil)

    +
    +
      +
    • Alex Rohleder <alexrohleder96_at_outlook.com>
    • +
    • bruno mendax <brunomendax_at_gmail.com>
    • +
    • Danilo GUia <danilo.eng_at_globomail.com>
    • +
    • Douglas Rafael Morais Kollar <douglas.kollar_at_pg.df.gov.br>
    • +
    • Douglas Eccker <douglaseccker_at_hotmail.com>
    • +
    • Ed Jr <edjacobjunior_at_gmail.com>
    • +
    • Guilherme Souza Silva <g.szsilva_at_gmail.com>
    • +
    • Guilherme Seibt <gui_at_webseibt.net>
    • +
    • Helder Santana <helder.bs.santana_at_gmail.com>
    • +
    • Junior Zancan <jrzancan_at_hotmail.com>
    • +
    • Luis <luis.eduardo.braschi_at_outlook.com>
    • +
    • Marcos Algeri <malgeri_at_gmail.com>
    • +
    • Marc Delisle <marc_at_infomarc.info>
    • +
    • Renato Rodrigues de Lima Júnio <renatomdd_at_yahoo.com.br>
    • +
    • Thiago Casotti <thiago.casotti_at_uol.com.br>
    • +
    • Victor Laureano <victor.laureano_at_gmail.com>
    • +
    • Vinícius Araújo <vinipitta_at_gmail.com>
    • +
    • Washington Bruno Rodrigues Cav <washingtonbruno_at_msn.com>
    • +
    • Yan Gabriel <yansilvagabriel_at_gmail.com>
    • +
    +
    +
  • +
  • Punjabi

    +
    +
      +
    • Robin van der Vliet <info_at_robinvandervliet.nl>
    • +
    +
    +
  • +
  • Romanian

    +
    +
      +
    • Alex <amihaita_at_yahoo.com>
    • +
    • Costel Cocerhan <costa1988sv_at_gmail.com>
    • +
    • Ion Adrian-Ionut <john_at_panevo.ro>
    • +
    • Raul Molnar <molnar.raul_at_wservices.eu>
    • +
    • Deleted User <noreply_at_weblate.org>
    • +
    • Stefan Murariu <stefan.murariu_at_yahoo.com>
    • +
    +
    +
  • +
  • Russian

    +
    +
      +
    • Andrey Aleksanyants <aaleksanyants_at_yahoo.com>
    • +
    • <ddrmoscow_at_gmail.com>
    • +
    • Robin van der Vliet <info_at_robinvandervliet.nl>
    • +
    • Хомутов Иван Сергеевич <khomutov.ivan_at_mail.ru>
    • +
    • Alexey Rubinov <orion1979_at_yandex.ru>
    • +
    • Олег Карпов <salvadoporjc_at_gmail.com>
    • +
    • Egorov Artyom <unlucky_at_inbox.ru>
    • +
    +
    +
  • +
  • Serbian

    +
    +
      +
    • Smart Kid <kidsmart33_at_gmail.com>
    • +
    +
    +
  • +
  • Sinhala

    +
    +
      +
    • Madhura Jayaratne <madhura.cj_at_gmail.com>
    • +
    +
    +
  • +
  • Slovak

    +
    +
      +
    • Martin Lacina <martin_at_whistler.sk>
    • +
    • Patrik Kollmann <parkourpotex_at_gmail.com>
    • +
    • Jozef Pistej <pistej2_at_gmail.com>
    • +
    +
    +
  • +
  • Slovenian

    +
    +
      +
    • Domen <mitenem_at_outlook.com>
    • +
    +
    +
  • +
  • Spanish

    +
    +
      +
    • Luis García Sevillano <floss.dev_at_gmail.com>
    • +
    • Franco <fulanodetal.github1_at_openaliasbox.org>
    • +
    • Luis Ruiz <luisan00_at_hotmail.com>
    • +
    • Macofe <macofe.languagetool_at_gmail.com>
    • +
    • Matías Bellone <matiasbellone+weblate_at_gmail.com>
    • +
    • Rodrigo A. <ra4_at_openmailbox.org>
    • +
    • FAMMA TV NOTICIAS MEDIOS DE CO <revistafammatvmusic.oficial_at_gmail.com>
    • +
    • Ronnie Simon <ronniesimonf_at_gmail.com>
    • +
    +
    +
  • +
  • Swedish

    +
    +
      +
    • Anders Jonsson <anders.jonsson_at_norsjovallen.se>
    • +
    +
    +
  • +
  • Tamil

    +
    +
      +
    • கணேஷ் குமார் <GANESHTHEONE_at_gmail.com>
    • +
    • Achchuthan Yogarajah <achch1990_at_gmail.com>
    • +
    • Rifthy Ahmed <rifthy456_at_gmail.com>
    • +
    +
    +
  • +
  • Thai

    +
    +
      +
    • <nontawat39_at_gmail.com>
    • +
    • Somthanat W. <somthanat_at_gmail.com>
    • +
    +
    +
  • +
  • Turkish

    +
    +
      +
    • Burak Yavuz <hitowerdigit_at_hotmail.com>
    • +
    +
    +
  • +
  • Ukrainian

    +
    +
      +
    • Сергій Педько <nitrotoll_at_gmail.com>
    • +
    • Igor <vmta_at_yahoo.com>
    • +
    • Vitaliy Perekupka <vperekupka_at_gmail.com>
    • +
    +
    +
  • +
  • Vietnamese

    +
    +
      +
    • Bao Phan <baophan94_at_icloud.com>
    • +
    • Xuan Hung <mr.hungdx_at_gmail.com>
    • +
    • Bao trinh minh <trinhminhbao_at_gmail.com>
    • +
    +
    +
  • +
  • West Flemish

    +
    +
      +
    • Robin van der Vliet <info_at_robinvandervliet.nl>
    • +
    +
    +
  • +
+
+
+

Documentation translators

+

Following people have contributed to translation of phpMyAdmin documentation:

+
    +
  • Albanian

    +
    +
      +
    • Arben Çokaj <acokaj_at_shkoder.net>
    • +
    +
    +
  • +
  • Arabic

    +
    +
      +
    • Ahmed El Azzabi <ahmedtek1993_at_gmail.com>
    • +
    • Omar Essam <omar_2412_at_live.com>
    • +
    +
    +
  • +
  • Armenian

    +
    +
      +
    • Andrey Aleksanyants <aaleksanyants_at_yahoo.com>
    • +
    +
    +
  • +
  • Azerbaijani

    +
    +
      +
    • Mircəlal <01youknowme_at_gmail.com>
    • +
    • Sevdimali İsa <sevdimaliisayev_at_mail.ru>
    • +
    +
    +
  • +
  • Catalan

    +
    +
      +
    • josep constanti <jconstanti_at_yahoo.es>
    • +
    • Joan Montané <joan_at_montane.cat>
    • +
    • Xavier Navarro <xvnavarro_at_gmail.com>
    • +
    +
    +
  • +
  • Chinese (China)

    +
    +
      +
    • Vincent Lau <3092849_at_qq.com>
    • +
    • 罗攀登 <6375lpd_at_gmail.com>
    • +
    • disorderman <disorderman_at_qq.com>
    • +
    • ITXiaoPang <djh1017555_at_126.com>
    • +
    • tunnel213 <tunnel213_at_aliyun.com>
    • +
    • Terry Weng <wengshiyu_at_gmail.com>
    • +
    • whh <whhlcj_at_126.com>
    • +
    +
    +
  • +
  • Chinese (Taiwan)

    +
    +
      +
    • Chien Wei Lin <cwlin0416_at_gmail.com>
    • +
    • Peter Dave Hello <xs910203_at_gmail.com>
    • +
    +
    +
  • +
  • Czech

    +
    +
      +
    • Aleš Hakl <ales_at_hakl.net>
    • +
    • Michal Čihař <michal_at_cihar.com>
    • +
    • Jan Palider <palider_at_seznam.cz>
    • +
    • Petr Kateřiňák <petr.katerinak_at_gmail.com>
    • +
    +
    +
  • +
  • Danish

    +
    +
      +
    • Aputsiaĸ Niels Janussen <aj_at_isit.gl>
    • +
    • Claus Svalekjaer <just.my.smtp.server_at_gmail.com>
    • +
    +
    +
  • +
  • Dutch

    +
    +
      +
      1. +
      2. Voogt <a.voogt_at_hccnet.nl>
      3. +
      +
    • +
    • dingo thirteen <dingo13_at_gmail.com>
    • +
    • Dries Verschuere <dries.verschuere_at_outlook.com>
    • +
    • Robin van der Vliet <info_at_robinvandervliet.nl>
    • +
    • Stefan Koolen <nast3zz_at_gmail.com>
    • +
    • Ray Borggreve <ray_at_datahuis.net>
    • +
    • Dieter Adriaenssens <ruleant_at_users.sourceforge.net>
    • +
    • Tom Hofman <tom.hofman_at_gmail.com>
    • +
    +
    +
  • +
  • Estonian

    +
    +
      +
    • Kristjan Räts <kristjanrats_at_gmail.com>
    • +
    +
    +
  • +
  • Finnish

    +
    +
      +
    • Juha <jremes_at_outlook.com>
    • +
    +
    +
  • +
  • French

    +
    +
      +
    • Cédric Corazza <cedric.corazza_at_wanadoo.fr>
    • +
    • Étienne Gilli <etienne.gilli_at_gmail.com>
    • +
    • Marc Delisle <marc_at_infomarc.info>
    • +
    • Donavan_Martin <mart.donavan_at_hotmail.com>
    • +
    +
    +
  • +
  • Frisian

    +
    +
      +
    • Robin van der Vliet <info_at_robinvandervliet.nl>
    • +
    +
    +
  • +
  • Galician

    +
    +
      +
    • Xosé Calvo <xosecalvo_at_gmail.com>
    • +
    +
    +
  • +
  • German

    +
    +
      +
    • Daniel <d.gnauk89_at_googlemail.com>
    • +
    • JH M <janhenrikm_at_yahoo.de>
    • +
    • Lasse Goericke <lasse_at_mydom.de>
    • +
    • Michael Koch <michael.koch_at_enough.de>
    • +
    • Ann + J.M. <phpMyAdmin_at_ZweiSteinSoft.de>
    • +
    • Niemand Jedermann <predatorix_at_web.de>
    • +
    • Phillip Rohmberger <rohmberger_at_hotmail.de>
    • +
    • Hauke Henningsen <sqrt_at_entless.org>
    • +
    +
    +
  • +
  • Greek

    +
    +
      +
    • Παναγιώτης Παπάζογλου <papaz_p_at_yahoo.com>
    • +
    +
    +
  • +
  • Hungarian

    +
    +
      +
    • Balázs Úr <urbalazs_at_gmail.com>
    • +
    +
    +
  • +
  • Italian

    +
    +
      +
    • Francesco Saverio Giacobazzi <francesco.giacobazzi_at_ferrania.it>
    • +
    • Marco Pozzato <ironpotts_at_gmail.com>
    • +
    • Stefano Martinelli <stefano.ste.martinelli_at_gmail.com>
    • +
    • TWS <tablettws_at_gmail.com>
    • +
    +
    +
  • +
  • Japanese

    +
    +
      +
    • Eshin Kunishima <ek_at_luna.miko.im>
    • +
    • Hiroshi Chiyokawa <hiroshi.chiyokawa_at_gmail.com>
    • +
    +
    +
  • +
  • Lithuanian

    +
    +
      +
    • Jur Kis <atvejis_at_gmail.com>
    • +
    • Dovydas <dovy.buz_at_gmail.com>
    • +
    +
    +
  • +
  • Norwegian Bokmål

    +
    +
      +
    • Tor Stokkan <danorse_at_gmail.com>
    • +
    • Kurt Eilertsen <kurt_at_kheds.com>
    • +
    +
    +
  • +
  • Portuguese (Brazil)

    +
    +
      +
    • Alexandre Moretti <alemoretti2010_at_hotmail.com>
    • +
    • Douglas Rafael Morais Kollar <douglas.kollar_at_pg.df.gov.br>
    • +
    • Guilherme Seibt <gui_at_webseibt.net>
    • +
    • Helder Santana <helder.bs.santana_at_gmail.com>
    • +
    • Michal Čihař <michal_at_cihar.com>
    • +
    • Michel Souza <michel.ekio_at_gmail.com>
    • +
    • Danilo Azevedo <mrdaniloazevedo_at_gmail.com>
    • +
    • Thiago Casotti <thiago.casotti_at_uol.com.br>
    • +
    • Vinícius Araújo <vinipitta_at_gmail.com>
    • +
    • Yan Gabriel <yansilvagabriel_at_gmail.com>
    • +
    +
    +
  • +
  • Slovak

    +
    +
      +
    • Martin Lacina <martin_at_whistler.sk>
    • +
    • Michal Čihař <michal_at_cihar.com>
    • +
    • Jozef Pistej <pistej2_at_gmail.com>
    • +
    +
    +
  • +
  • Slovenian

    +
    +
      +
    • Domen <mitenem_at_outlook.com>
    • +
    +
    +
  • +
  • Spanish

    +
    +
      +
    • Luis García Sevillano <floss.dev_at_gmail.com>
    • +
    • Franco <fulanodetal.github1_at_openaliasbox.org>
    • +
    • Matías Bellone <matiasbellone+weblate_at_gmail.com>
    • +
    • Ronnie Simon <ronniesimonf_at_gmail.com>
    • +
    +
    +
  • +
  • Turkish

    +
    +
      +
    • Burak Yavuz <hitowerdigit_at_hotmail.com>
    • +
    +
    +
  • +
+
+
+

Original Credits of Version 2.1.0

+

This work is based on Peter Kuppelwieser’s MySQL-Webadmin. It was his +idea to create a web-based interface to MySQL using PHP3. Although I +have not used any of his source-code, there are some concepts I’ve +borrowed from him. phpMyAdmin was created because Peter told me he +wasn’t going to further develop his (great) tool.

+

Thanks go to

+
    +
  • Amalesh Kempf <ak-lsml_at_living-source.com> who contributed the +code for the check when dropping a table or database. He also +suggested that you should be able to specify the primary key on +tbl_create.php3. To version 1.1.1 he contributed the ldi_*.php3-set +(Import text-files) as well as a bug-report. Plus many smaller +improvements.
  • +
  • Jan Legenhausen <jan_at_nrw.net>: He made many of the changes that +were introduced in 1.3.0 (including quite significant ones like the +authentication). For 1.4.1 he enhanced the table-dump feature. Plus +bug-fixes and help.
  • +
  • Marc Delisle <DelislMa_at_CollegeSherbrooke.qc.ca> made phpMyAdmin +language-independent by outsourcing the strings to a separate file. He +also contributed the French translation.
  • +
  • Alexandr Bravo <abravo_at_hq.admiral.ru> who contributed +tbl_select.php3, a feature to display only some columns from a table.
  • +
  • Chris Jackson <chrisj_at_ctel.net> added support for MySQL functions +in tbl_change.php3. He also added the “Query by Example” feature in +2.0.
  • +
  • Dave Walton <walton_at_nordicdms.com> added support for multiple +servers and is a regular contributor for bug-fixes.
  • +
  • Gabriel Ash <ga244_at_is8.nyu.edu> contributed the random access +features for 2.0.6.
  • +
+

The following people have contributed minor changes, enhancements, +bugfixes or support for a new language:

+

Jim Kraai, Jordi Bruguera, Miquel Obrador, Geert Lund, Thomas +Kleemann, Alexander Leidinger, Kiko Albiol, Daniel C. Chao, Pavel +Piankov, Sascha Kettler, Joe Pruett, Renato Lins, Mark Kronsbein, +Jannis Hermanns, G. Wieggers.

+

And thanks to everyone else who sent me email with suggestions, bug- +reports and or just some feedback.

+
+
+ + +
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/php/apps/phpmyadmin49/html/doc/html/developers.html b/php/apps/phpmyadmin49/html/doc/html/developers.html new file mode 100644 index 00000000..f85ffb2c --- /dev/null +++ b/php/apps/phpmyadmin49/html/doc/html/developers.html @@ -0,0 +1,117 @@ + + + + + + + + Developers Information — phpMyAdmin 4.9.1 documentation + + + + + + + + + + + + + + + + + + +
+
+
+
+ +
+

Developers Information

+

phpMyAdmin is Open Source, so you’re invited to contribute to it. Many +great features have been written by other people and you too can help +to make phpMyAdmin a useful tool.

+

You can check out all the possibilities to contribute in the +contribute section on our website.

+
+ + +
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/php/apps/phpmyadmin49/html/doc/html/faq.html b/php/apps/phpmyadmin49/html/doc/html/faq.html new file mode 100644 index 00000000..31ac8348 --- /dev/null +++ b/php/apps/phpmyadmin49/html/doc/html/faq.html @@ -0,0 +1,2014 @@ + + + + + + + + FAQ - Frequently Asked Questions — phpMyAdmin 4.9.1 documentation + + + + + + + + + + + + + + + + + + +
+
+
+
+ +
+

FAQ - Frequently Asked Questions

+

Please have a look at our Link section on the official +phpMyAdmin homepage for in-depth coverage of phpMyAdmin’s features and +or interface.

+
+

Server

+
+

1.1 My server is crashing each time a specific action is required or phpMyAdmin sends a blank page or a page full of cryptic characters to my browser, what can I do?

+

Try to set the $cfg['OBGzip'] directive to false in your +config.inc.php file and the zlib.output_compression directive to +Off in your php configuration file.

+
+
+

1.2 My Apache server crashes when using phpMyAdmin.

+

You should first try the latest versions of Apache (and possibly MySQL). If +your server keeps crashing, please ask for help in the various Apache support +groups.

+ +
+
+

1.3 (withdrawn).

+
+
+

1.4 Using phpMyAdmin on IIS, I’m displayed the error message: “The specified CGI application misbehaved by not returning a complete set of HTTP headers ...”.

+

You just forgot to read the install.txt file from the PHP +distribution. Have a look at the last message in this PHP bug report #12061 from the official PHP bug +database.

+
+
+

1.5 Using phpMyAdmin on IIS, I’m facing crashes and/or many error messages with the HTTP.

+

This is a known problem with the PHP ISAPI filter: it’s not so stable. +Please use instead the cookie authentication mode.

+
+
+

1.6 I can’t use phpMyAdmin on PWS: nothing is displayed!

+

This seems to be a PWS bug. Filippo Simoncini found a workaround (at +this time there is no better fix): remove or comment the DOCTYPE +declarations (2 lines) from the scripts libraries/Header.class.php +and index.php.

+
+
+

1.7 How can I gzip a dump or a CSV export? It does not seem to work.

+

This feature is based on the gzencode() +PHP function to be more independent of the platform (Unix/Windows, +Safe Mode or not, and so on). So, you must have Zlib support +(--with-zlib).

+
+
+

1.8 I cannot insert a text file in a table, and I get an error about safe mode being in effect.

+

Your uploaded file is saved by PHP in the “upload dir”, as defined in +php.ini by the variable upload_tmp_dir (usually the system +default is /tmp). We recommend the following setup for Apache +servers running in safe mode, to enable uploads of files while being +reasonably secure:

+
    +
  • create a separate directory for uploads: mkdir /tmp/php
  • +
  • give ownership to the Apache server’s user.group: chown +apache.apache /tmp/php
  • +
  • give proper permission: chmod 600 /tmp/php
  • +
  • put upload_tmp_dir = /tmp/php in php.ini
  • +
  • restart Apache
  • +
+
+
+

1.9 (withdrawn).

+
+
+

1.10 I’m having troubles when uploading files with phpMyAdmin running on a secure server. My browser is Internet Explorer and I’m using the Apache server.

+

As suggested by “Rob M” in the phpWizard forum, add this line to your +httpd.conf:

+
SetEnvIf User-Agent ".*MSIE.*" nokeepalive ssl-unclean-shutdown
+
+
+

It seems to clear up many problems between Internet Explorer and SSL.

+
+
+

1.11 I get an ‘open_basedir restriction’ while uploading a file from the import tab.

+

Since version 2.2.4, phpMyAdmin supports servers with open_basedir +restrictions. However you need to create temporary directory and configure it +as $cfg['TempDir']. The uploaded files will be moved there, +and after execution of your SQL commands, removed.

+
+
+

1.12 I have lost my MySQL root password, what can I do?

+

phpMyAdmin does authenticate against MySQL server you’re using, so to recover +from phpMyAdmin password loss, you need to recover at MySQL level.

+

The MySQL manual explains how to reset the permissions.

+

If you are using MySQL server installed by your hosting provider, please +contact their support to recover the password for you.

+
+
+

1.13 (withdrawn).

+
+
+

1.14 (withdrawn).

+
+
+

1.15 I have problems with mysql.user column names.

+

In previous MySQL versions, the User and Password columns were +named user and password. Please modify your column names to +align with current standards.

+
+
+

1.16 I cannot upload big dump files (memory, HTTP or timeout problems).

+

Starting with version 2.7.0, the import engine has been re–written and +these problems should not occur. If possible, upgrade your phpMyAdmin +to the latest version to take advantage of the new import features.

+

The first things to check (or ask your host provider to check) are the values +of max_execution_time, upload_max_filesize, memory_limit and +post_max_size in the php.ini configuration file. All of these three +settings limit the maximum size of data that can be submitted and handled by +PHP. Please note that post_max_size needs to be larger than +upload_max_filesize. There exist several workarounds if your upload is too +big or your hosting provider is unwilling to change the settings:

+
    +
  • Look at the $cfg['UploadDir'] feature. This allows one to upload a file to the server +via scp, ftp, or your favorite file transfer method. PhpMyAdmin is +then able to import the files from the temporary directory. More +information is available in the Configuration of this document.

    +
  • +
  • Using a utility (such as BigDump) to split the files before +uploading. We cannot support this or any third party applications, but +are aware of users having success with it.

    +
  • +
  • If you have shell (command line) access, use MySQL to import the files +directly. You can do this by issuing the “source” command from within +MySQL:

    +
    source filename.sql;
    +
    +
    +
  • +
+
+
+

1.17 Which Database versions does phpMyAdmin support?

+

For MySQL, versions 5.5 and newer are supported. +For older MySQL versions, our Downloads page offers older phpMyAdmin versions +(which may have become unsupported).

+

For MariaDB, versions 5.5 and newer are supported.

+
+
+

1.17a I cannot connect to the MySQL server. It always returns the error message, “Client does not support authentication protocol requested by server; consider upgrading MySQL client”

+

You tried to access MySQL with an old MySQL client library. The +version of your MySQL client library can be checked in your phpinfo() +output. In general, it should have at least the same minor version as +your server - as mentioned in 1.17 Which Database versions does phpMyAdmin support?. This problem is +generally caused by using MySQL version 4.1 or newer. MySQL changed +the authentication hash and your PHP is trying to use the old method. +The proper solution is to use the mysqli extension with the proper client library to match +your MySQL installation. More +information (and several workarounds) are located in the MySQL +Documentation.

+
+
+

1.18 (withdrawn).

+
+
+

1.19 I can’t run the “display relations” feature because the script seems not to know the font face I’m using!

+

The TCPDF library we’re using for this feature requires some special +files to use font faces. Please refers to the TCPDF manual to build these files.

+
+
+

1.20 I receive an error about missing mysqli and mysql extensions.

+

To connect to a MySQL server, PHP needs a set of MySQL functions +called “MySQL extension”. This extension may be part of the PHP +distribution (compiled-in), otherwise it needs to be loaded +dynamically. Its name is probably mysqli.so or php_mysqli.dll. +phpMyAdmin tried to load the extension but failed. Usually, the +problem is solved by installing a software package called “PHP-MySQL” +or something similar.

+

There are currently two interfaces PHP provides as MySQL extensions - mysql +and mysqli. The mysqli is tried first, because it’s the best one.

+

This problem can be also caused by wrong paths in the php.ini or using +wrong php.ini.

+

Make sure that the extension files do exist in the folder which the +extension_dir points to and that the corresponding lines in your +php.ini are not commented out (you can use phpinfo() to check +current setup):

+
[PHP]
+
+; Directory in which the loadable extensions (modules) reside.
+extension_dir = "C:/Apache2/modules/php/ext"
+
+
+

The php.ini can be loaded from several locations (especially on +Windows), so please check you’re updating the correct one. If using Apache, you +can tell it to use specific path for this file using PHPIniDir directive:

+
LoadFile "C:/php/php5ts.dll"
+LoadModule php5_module "C:/php/php5apache2_2.dll"
+<IfModule php5_module>
+    PHPIniDir "C:/PHP"
+    <Location>
+       AddType text/html .php
+       AddHandler application/x-httpd-php .php
+    </Location>
+</IfModule>
+
+
+

In some rare cases this problem can be also caused by other extensions loaded +in PHP which prevent MySQL extensions to be loaded. If anything else fails, you +can try commenting out extensions for other databses from php.ini.

+
+ +
+

1.22 I don’t see the “Location of text file” field, so I cannot upload.

+

This is most likely because in php.ini, your file_uploads +parameter is not set to “on”.

+
+
+

1.23 I’m running MySQL on a Win32 machine. Each time I create a new table the table and column names are changed to lowercase!

+

This happens because the MySQL directive lower_case_table_names +defaults to 1 (ON) in the Win32 version of MySQL. You can change +this behavior by simply changing the directive to 0 (OFF): Just +edit your my.ini file that should be located in your Windows +directory and add the following line to the group [mysqld]:

+
set-variable = lower_case_table_names=0
+
+
+
+

Note

+

Forcing this variable to 0 with –lower-case-table-names=0 on a +case-insensitive filesystem and access MyISAM tablenames using different +lettercases, index corruption may result.

+
+

Next, save the file and restart the MySQL service. You can always +check the value of this directive using the query

+
SHOW VARIABLES LIKE 'lower_case_table_names';
+
+
+ +
+
+

1.24 (withdrawn).

+
+
+

1.25 I am running Apache with mod_gzip-1.3.26.1a on Windows XP, and I get problems, such as undefined variables when I run a SQL query.

+

A tip from Jose Fandos: put a comment on the following two lines in +httpd.conf, like this:

+
# mod_gzip_item_include file \.php$
+# mod_gzip_item_include mime "application/x-httpd-php.*"
+
+
+

as this version of mod_gzip on Apache (Windows) has problems handling +PHP scripts. Of course you have to restart Apache.

+
+
+

1.26 I just installed phpMyAdmin in my document root of IIS but I get the error “No input file specified” when trying to run phpMyAdmin.

+

This is a permission problem. Right-click on the phpmyadmin folder and +choose properties. Under the tab Security, click on “Add” and select +the user “IUSR_machine” from the list. Now set his permissions and it +should work.

+
+
+

1.27 I get empty page when I want to view huge page (eg. db_structure.php with plenty of tables).

+

This was caused by a PHP bug that occur when +GZIP output buffering is enabled. If you turn off it (by +$cfg['OBGzip'] in config.inc.php), it should work. +This bug will has been fixed in PHP 5.0.0.

+
+
+

1.28 My MySQL server sometimes refuses queries and returns the message ‘Errorcode: 13’. What does this mean?

+

This can happen due to a MySQL bug when having database / table names +with upper case characters although lower_case_table_names is +set to 1. To fix this, turn off this directive, convert all database +and table names to lower case and turn it on again. Alternatively, +there’s a bug-fix available starting with MySQL 3.23.56 / +4.0.11-gamma.

+
+
+

1.29 When I create a table or modify a column, I get an error and the columns are duplicated.

+

It is possible to configure Apache in such a way that PHP has problems +interpreting .php files.

+

The problems occur when two different (and conflicting) set of +directives are used:

+
SetOutputFilter PHP
+SetInputFilter PHP
+
+
+

and

+
AddType application/x-httpd-php .php
+
+
+

In the case we saw, one set of directives was in +/etc/httpd/conf/httpd.conf, while the other set was in +/etc/httpd/conf/addon-modules/php.conf. The recommended way is +with AddType, so just comment out the first set of lines and +restart Apache:

+
#SetOutputFilter PHP
+#SetInputFilter PHP
+
+
+
+
+

1.30 I get the error “navigation.php: Missing hash”.

+

This problem is known to happen when the server is running Turck +MMCache but upgrading MMCache to version 2.3.21 solves the problem.

+
+
+

1.31 Which PHP versions does phpMyAdmin support?

+

Since release 4.5, phpMyAdmin supports only PHP 5.5 and newer. Since release +4.1 phpMyAdmin supports only PHP 5.3 and newer. For PHP 5.2 you can use 4.0.x +releases.

+

PHP 7 is supported since phpMyAdmin 4.6, PHP 7.1 is supported since 4.6.5, +PHP 7.2 is supported since 4.7.4.

+

phpMyAdmin also works fine with HHVM.

+
+
+

1.32 Can I use HTTP authentication with IIS?

+

Yes. This procedure was tested with phpMyAdmin 2.6.1, PHP 4.3.9 in +ISAPI mode under IIS 5.1.

+
    +
  1. In your php.ini file, set cgi.rfc2616_headers = 0
  2. +
  3. In Web Site Properties -> File/Directory Security -> Anonymous +Access dialog box, check the Anonymous access checkbox and +uncheck any other checkboxes (i.e. uncheck Basic authentication, +Integrated Windows authentication, and Digest if it’s +enabled.) Click OK.
  4. +
  5. In Custom Errors, select the range of 401;1 through 401;5 +and click the Set to Default button.
  6. +
+
+

See also

+

RFC 2616

+
+
+
+

1.33 (withdrawn).

+
+
+

1.34 Can I access directly to database or table pages?

+

Yes. Out of the box, you can use URL like +http://server/phpMyAdmin/index.php?server=X&db=database&table=table&target=script. +For server you use the server number +which refers to the order of the server paragraph in +config.inc.php. Table and script parts are optional. If you want +http://server/phpMyAdmin/database[/table][/script] URL, you need to do some configuration. Following +lines apply only for Apache web server. +First make sure, that you have enabled some features within global +configuration. You need Options SymLinksIfOwnerMatch and AllowOverride +FileInfo enabled for directory where phpMyAdmin is installed and you +need mod_rewrite to be enabled. Then you just need to create +following .htaccess file in root folder of phpMyAdmin installation (don’t +forget to change directory name inside of it):

+
RewriteEngine On
+RewriteBase /path_to_phpMyAdmin
+RewriteRule ^([a-zA-Z0-9_]+)/([a-zA-Z0-9_]+)/([a-z_]+\.php)$ index.php?db=$1&table=$2&target=$3 [R]
+RewriteRule ^([a-zA-Z0-9_]+)/([a-z_]+\.php)$ index.php?db=$1&target=$2 [R]
+RewriteRule ^([a-zA-Z0-9_]+)/([a-zA-Z0-9_]+)$ index.php?db=$1&table=$2 [R]
+RewriteRule ^([a-zA-Z0-9_]+)$ index.php?db=$1 [R]
+
+
+
+
+

1.35 Can I use HTTP authentication with Apache CGI?

+

Yes. However you need to pass authentication variable to CGI using +following rewrite rule:

+
RewriteEngine On
+RewriteRule .* - [E=REMOTE_USER:%{HTTP:Authorization},L]
+
+
+
+
+

1.36 I get an error “500 Internal Server Error”.

+

There can be many explanations to this and a look at your server’s +error log file might give a clue.

+
+ +
+

1.38 Can I use phpMyAdmin on a server on which Suhosin is enabled?

+

Yes but the default configuration values of Suhosin are known to cause +problems with some operations, for example editing a table with many +columns and no primary key or with textual primary key.

+

Suhosin configuration might lead to malfunction in some cases and it +can not be fully avoided as phpMyAdmin is kind of application which +needs to transfer big amounts of columns in single HTTP request, what +is something what Suhosin tries to prevent. Generally all +suhosin.request.*, suhosin.post.* and suhosin.get.* +directives can have negative effect on phpMyAdmin usability. You can +always find in your error logs which limit did cause dropping of +variable, so you can diagnose the problem and adjust matching +configuration variable.

+

The default values for most Suhosin configuration options will work in +most scenarios, however you might want to adjust at least following +parameters:

+ +

To further improve security, we also recommend these modifications:

+ +

You can also disable the warning using the $cfg['SuhosinDisableWarning'].

+
+
+

1.39 When I try to connect via https, I can log in, but then my connection is redirected back to http. What can cause this behavior?

+

This is caused by the fact that PHP scripts have no knowledge that the site is +using https. Depending on used webserver, you should configure it to let PHP +know about URL and scheme used to access it.

+

For example in Apache ensure that you have enabled SSLOptions and +StdEnvVars in the configuration.

+ +
+ +
+

1.41 When I view a database and ask to see its privileges, I get an error about an unknown column.

+

The MySQL server’s privilege tables are not up to date, you need to +run the mysql_upgrade command on the server.

+
+
+

1.42 How can I prevent robots from accessing phpMyAdmin?

+

You can add various rules to .htaccess to filter access based on user agent +field. This is quite easy to circumvent, but could prevent at least +some robots accessing your installation.

+
RewriteEngine on
+
+# Allow only GET and POST verbs
+RewriteCond %{REQUEST_METHOD} !^(GET|POST)$ [NC,OR]
+
+# Ban Typical Vulnerability Scanners and others
+# Kick out Script Kiddies
+RewriteCond %{HTTP_USER_AGENT} ^(java|curl|wget).* [NC,OR]
+RewriteCond %{HTTP_USER_AGENT} ^.*(libwww-perl|curl|wget|python|nikto|wkito|pikto|scan|acunetix).* [NC,OR]
+RewriteCond %{HTTP_USER_AGENT} ^.*(winhttp|HTTrack|clshttp|archiver|loader|email|harvest|extract|grab|miner).* [NC,OR]
+
+# Ban Search Engines, Crawlers to your administrative panel
+# No reasons to access from bots
+# Ultimately Better than the useless robots.txt
+# Did google respect robots.txt?
+# Try google: intitle:phpMyAdmin intext:"Welcome to phpMyAdmin *.*.*" intext:"Log in" -wiki -forum -forums -questions intext:"Cookies must be enabled"
+RewriteCond %{HTTP_USER_AGENT} ^.*(AdsBot-Google|ia_archiver|Scooter|Ask.Jeeves|Baiduspider|Exabot|FAST.Enterprise.Crawler|FAST-WebCrawler|www\.neomo\.de|Gigabot|Mediapartners-Google|Google.Desktop|Feedfetcher-Google|Googlebot|heise-IT-Markt-Crawler|heritrix|ibm.com\cs/crawler|ICCrawler|ichiro|MJ12bot|MetagerBot|msnbot-NewsBlogs|msnbot|msnbot-media|NG-Search|lucene.apache.org|NutchCVS|OmniExplorer_Bot|online.link.validator|psbot0|Seekbot|Sensis.Web.Crawler|SEO.search.Crawler|Seoma.\[SEO.Crawler\]|SEOsearch|Snappy|www.urltrends.com|www.tkl.iis.u-tokyo.ac.jp/~crawler|SynooBot|crawleradmin.t-info@telekom.de|TurnitinBot|voyager|W3.SiteSearch.Crawler|W3C-checklink|W3C_Validator|www.WISEnutbot.com|yacybot|Yahoo-MMCrawler|Yahoo\!.DE.Slurp|Yahoo\!.Slurp|YahooSeeker).* [NC]
+RewriteRule .* - [F]
+
+
+
+
+

1.43 Why can’t I display the structure of my table containing hundreds of columns?

+

Because your PHP’s memory_limit is too low; adjust it in php.ini.

+
+
+

1.44 How can I reduce the installed size of phpMyAdmin on disk?

+

Some users have requested to be able to reduce the size of the phpMyAdmin installation. +This is not recommended and could lead to confusion over missing features, but can be done. +A list of files and corresponding functionality which degrade gracefully when removed include:

+
    +
  • ./vendor/tecnickcom/tcpdf folder (exporting to PDF)
  • +
  • ./locale/ folder, or unused subfolders (interface translations)
  • +
  • Any unused themes in ./themes/
  • +
  • ./js/vendor/jquery/src/ (included for licensing reasons)
  • +
  • ./js/line_counts.php (removed in phpMyAdmin 4.8)
  • +
  • ./doc/ (documentation)
  • +
  • ./setup/ (setup script)
  • +
  • ./examples/
  • +
  • ./sql/ (SQL scripts to configure advanced functionality)
  • +
  • ./js/vendor/openlayers/ (GIS visualization)
  • +
+
+
+
+

Configuration

+
+

2.1 The error message “Warning: Cannot add header information - headers already sent by ...” is displayed, what’s the problem?

+

Edit your config.inc.php file and ensure there is nothing (I.E. no +blank lines, no spaces, no characters...) neither before the <?php tag at +the beginning, neither after the ?> tag at the end.

+
+
+

2.2 phpMyAdmin can’t connect to MySQL. What’s wrong?

+

Either there is an error with your PHP setup or your username/password +is wrong. Try to make a small script which uses mysql_connect and see +if it works. If it doesn’t, it may be you haven’t even compiled MySQL +support into PHP.

+
+
+

2.3 The error message “Warning: MySQL Connection Failed: Can’t connect to local MySQL server through socket ‘/tmp/mysql.sock’ (111) ...” is displayed. What can I do?

+

The error message can also be: Error #2002 - The server is not +responding (or the local MySQL server’s socket is not correctly configured).

+

First, you need to determine what socket is being used by MySQL. To do this, +connect to your server and go to the MySQL bin directory. In this directory +there should be a file named mysqladmin. Type ./mysqladmin variables, and +this should give you a bunch of info about your MySQL server, including the +socket (/tmp/mysql.sock, for example). You can also ask your ISP for the +connection info or, if you’re hosting your own, connect from the ‘mysql’ +command-line client and type ‘status’ to get the connection type and socket or +port number.

+

Then, you need to tell PHP to use this socket. You can do this for all PHP in +the php.ini or for phpMyAdmin only in the config.inc.php. For +example: $cfg['Servers'][$i]['socket'] Please also make sure +that the permissions of this file allow to be readable by your webserver.

+

On my RedHat-Box the socket of MySQL is /var/lib/mysql/mysql.sock. +In your php.ini you will find a line

+
mysql.default_socket = /tmp/mysql.sock
+
+
+

change it to

+
mysql.default_socket = /var/lib/mysql/mysql.sock
+
+
+

Then restart apache and it will work.

+

Have also a look at the corresponding section of the MySQL +documentation.

+
+
+

2.4 Nothing is displayed by my browser when I try to run phpMyAdmin, what can I do?

+

Try to set the $cfg['OBGzip'] directive to false in the phpMyAdmin configuration +file. It helps sometime. Also have a look at your PHP version number: +if it contains “b” or “alpha” it means you’re running a testing +version of PHP. That’s not a so good idea, please upgrade to a plain +revision.

+
+ +
+

2.6 I get an “Access denied for user: 'root@localhost‘ (Using password: YES)”-error when trying to access a MySQL-Server on a host which is port-forwarded for my localhost.

+

When you are using a port on your localhost, which you redirect via +port-forwarding to another host, MySQL is not resolving the localhost +as expected. Erik Wasser explains: The solution is: if your host is +“localhost” MySQL (the command line tool mysql as well) always +tries to use the socket connection for speeding up things. And that +doesn’t work in this configuration with port forwarding. If you enter +“127.0.0.1” as hostname, everything is right and MySQL uses the +TCP connection.

+
+
+

2.7 Using and creating themes

+

See Custom Themes.

+
+
+

2.8 I get “Missing parameters” errors, what can I do?

+

Here are a few points to check:

+
    +
  • In config.inc.php, try to leave the $cfg['PmaAbsoluteUri'] directive empty. See also +4.7 Authentication window is displayed more than once, why?.
  • +
  • Maybe you have a broken PHP installation or you need to upgrade your +Zend Optimizer. See <https://bugs.php.net/bug.php?id=31134>.
  • +
  • If you are using Hardened PHP with the ini directive +varfilter.max_request_variables set to the default (200) or +another low value, you could get this error if your table has a high +number of columns. Adjust this setting accordingly. (Thanks to Klaus +Dorninger for the hint).
  • +
  • In the php.ini directive arg_separator.input, a value of ”;” +will cause this error. Replace it with “&;”.
  • +
  • If you are using Suhosin, you +might want to increase request limits.
  • +
  • The directory specified in the php.ini directive +session.save_path does not exist or is read-only (this can be caused +by bug in the PHP installer).
  • +
+
+
+

2.9 Seeing an upload progress bar

+

To be able to see a progress bar during your uploads, your server must +have the APC extension, the +uploadprogress one, or +you must be running PHP 5.4.0 or higher. Moreover, the JSON extension +has to be enabled in your PHP.

+

If using APC, you must set apc.rfc1867 to on in your php.ini.

+

If using PHP 5.4.0 or higher, you must set +session.upload_progress.enabled to 1 in your php.ini. However, +starting from phpMyAdmin version 4.0.4, session-based upload progress has +been temporarily deactivated due to its problematic behavior.

+
+

See also

+

RFC 1867

+
+
+
+
+

Known limitations

+
+

3.1 When using HTTP authentication, a user who logged out can not log in again in with the same nick.

+

This is related to the authentication mechanism (protocol) used by +phpMyAdmin. To bypass this problem: just close all the opened browser +windows and then go back to phpMyAdmin. You should be able to log in +again.

+
+
+

3.2 When dumping a large table in compressed mode, I get a memory limit error or a time limit error.

+

Compressed dumps are built in memory and because of this are limited +to php’s memory limit. For gzip/bzip2 exports this can be overcome +since 2.5.4 using $cfg['CompressOnFly'] (enabled by default). +zip exports can not be handled this way, so if you need zip files for larger +dump, you have to use another way.

+
+
+

3.3 With InnoDB tables, I lose foreign key relationships when I rename a table or a column.

+

This is an InnoDB bug, see <https://bugs.mysql.com/bug.php?id=21704>.

+
+
+

3.4 I am unable to import dumps I created with the mysqldump tool bundled with the MySQL server distribution.

+

The problem is that older versions of mysqldump created invalid +comments like this:

+
-- MySQL dump 8.22
+--
+-- Host: localhost Database: database
+---------------------------------------------------------
+-- Server version 3.23.54
+
+
+

The invalid part of the code is the horizontal line made of dashes +that appears once in every dump created with mysqldump. If you want to +run your dump you have to turn it into valid MySQL. This means, you +have to add a whitespace after the first two dashes of the line or add +a # before it: -- ------------------------------------------------------- or +#---------------------------------------------------------

+
+
+

3.5 When using nested folders, multiple hierarchies are displayed in a wrong manner.

+

Please note that you should not use the separating string multiple +times without any characters between them, or at the beginning/end of +your table name. If you have to, think about using another +TableSeparator or disabling that feature.

+ +
+
+

3.6 (withdrawn).

+
+
+

3.7 I have table with many (100+) columns and when I try to browse table I get series of errors like “Warning: unable to parse url”. How can this be fixed?

+

Your table neither have a primary key nor an unique key, so we must +use a long expression to identify this row. This causes problems to +parse_url function. The workaround is to create a primary key +or unique key.

+
+
+

3.8 I cannot use (clickable) HTML-forms in columns where I put a MIME-Transformation onto!

+

Due to a surrounding form-container (for multi-row delete checkboxes), +no nested forms can be put inside the table where phpMyAdmin displays +the results. You can, however, use any form inside of a table if keep +the parent form-container with the target to tbl_row_delete.php and +just put your own input-elements inside. If you use a custom submit +input field, the form will submit itself to the displaying page again, +where you can validate the $HTTP_POST_VARS in a transformation. For +a tutorial on how to effectively use transformations, see our Link +section on the +official phpMyAdmin-homepage.

+
+
+

3.9 I get error messages when using “–sql_mode=ANSI” for the MySQL server.

+

When MySQL is running in ANSI-compatibility mode, there are some major +differences in how SQL is structured (see +<https://dev.mysql.com/doc/refman/5.7/en/sql-mode.html>). Most important of all, the +quote-character (”) is interpreted as an identifier quote character and not as +a string quote character, which makes many internal phpMyAdmin operations into +invalid SQL statements. There is no +workaround to this behaviour. News to this item will be posted in issue +#7383.

+
+
+

3.10 Homonyms and no primary key: When the results of a SELECT display more that one column with the same value (for example SELECT lastname from employees where firstname like 'A%' and two “Smith” values are displayed), if I click Edit I cannot be sure that I am editing the intended row.

+

Please make sure that your table has a primary key, so that phpMyAdmin +can use it for the Edit and Delete links.

+
+
+

3.11 The number of rows for InnoDB tables is not correct.

+

phpMyAdmin uses a quick method to get the row count, and this method only +returns an approximate count in the case of InnoDB tables. See +$cfg['MaxExactCount'] for a way to modify those results, but +this could have a serious impact on performance. +However, one can easily replace the approximate row count with exact count by +simply clicking on the approximate count. This can also be done for all tables +at once by clicking on the rows sum displayed at the bottom.

+
+

See also

+

$cfg['MaxExactCount']

+
+
+
+

3.12 (withdrawn).

+
+
+

3.13 I get an error when entering USE followed by a db name containing an hyphen.

+

The tests I have made with MySQL 5.1.49 shows that the API does not +accept this syntax for the USE command.

+
+
+

3.14 I am not able to browse a table when I don’t have the right to SELECT one of the columns.

+

This has been a known limitation of phpMyAdmin since the beginning and +it’s not likely to be solved in the future.

+
+
+

3.15 (withdrawn).

+
+
+

3.16 (withdrawn).

+
+
+

3.17 (withdrawn).

+
+
+

3.18 When I import a CSV file that contains multiple tables, they are lumped together into a single table.

+

There is no reliable way to differentiate tables in CSV format. For the +time being, you will have to break apart CSV files containing multiple +tables.

+
+
+

3.19 When I import a file and have phpMyAdmin determine the appropriate data structure it only uses int, decimal, and varchar types.

+

Currently, the import type-detection system can only assign these +MySQL types to columns. In future, more will likely be added but for +the time being you will have to edit the structure to your liking +post-import. Also, you should note the fact that phpMyAdmin will use +the size of the largest item in any given column as the column size +for the appropriate type. If you know you will be adding larger items +to that column then you should manually adjust the column sizes +accordingly. This is done for the sake of efficiency.

+
+
+

3.20 After upgrading, some bookmarks are gone or their content cannot be shown.

+

At some point, the character set used to store bookmark content has changed. +It’s better to recreate your bookmark from the newer phpMyAdmin version.

+
+
+

3.21 I am unable to log in with a username containing unicode characters such as á.

+

This can happen if MySQL server is not configured to use utf-8 as default +charset. This is a limitation of how PHP and the MySQL server interact; there +is no way for PHP to set the charset before authenticating.

+ +
+
+
+

ISPs, multi-user installations

+
+

4.1 I’m an ISP. Can I setup one central copy of phpMyAdmin or do I need to install it for each customer?

+

Since version 2.0.3, you can setup a central copy of phpMyAdmin for all your +users. The development of this feature was kindly sponsored by NetCologne GmbH. +This requires a properly setup MySQL user management and phpMyAdmin +HTTP or cookie authentication.

+ +
+
+

4.2 What’s the preferred way of making phpMyAdmin secure against evil access?

+

This depends on your system. If you’re running a server which cannot be +accessed by other people, it’s sufficient to use the directory protection +bundled with your webserver (with Apache you can use .htaccess files, +for example). If other people have telnet access to your server, you should use +phpMyAdmin’s HTTP or cookie authentication features.

+

Suggestions:

+
    +
  • Your config.inc.php file should be chmod 660.
  • +
  • All your phpMyAdmin files should be chown -R phpmy.apache, where phpmy +is a user whose password is only known to you, and apache is the group +under which Apache runs.
  • +
  • Follow security recommendations for PHP and your webserver.
  • +
+
+
+

4.3 I get errors about not being able to include a file in /lang or in /libraries.

+

Check php.ini, or ask your sysadmin to check it. The +include_path must contain ”.” somewhere in it, and +open_basedir, if used, must contain ”.” and ”./lang” to allow +normal operation of phpMyAdmin.

+
+
+

4.4 phpMyAdmin always gives “Access denied” when using HTTP authentication.

+

This could happen for several reasons:

+ +
+
+

4.5 Is it possible to let users create their own databases?

+

Starting with 2.2.5, in the user management page, you can enter a +wildcard database name for a user (for example “joe%”), and put the +privileges you want. For example, adding SELECT, INSERT, UPDATE, +DELETE, CREATE, DROP, INDEX, ALTER would let a user create/manage +his/her database(s).

+
+
+

4.6 How can I use the Host-based authentication additions?

+

If you have existing rules from an old .htaccess file, you can take them and +add a username between the 'deny'/'allow' and 'from' +strings. Using the username wildcard of '%' would be a major +benefit here if your installation is suited to using it. Then you can +just add those updated lines into the +$cfg['Servers'][$i]['AllowDeny']['rules'] array.

+

If you want a pre-made sample, you can try this fragment. It stops the +‘root’ user from logging in from any networks other than the private +network IP blocks.

+
//block root from logging in except from the private networks
+$cfg['Servers'][$i]['AllowDeny']['order'] = 'deny,allow';
+$cfg['Servers'][$i]['AllowDeny']['rules'] = array(
+    'deny root from all',
+    'allow root from localhost',
+    'allow root from 10.0.0.0/8',
+    'allow root from 192.168.0.0/16',
+    'allow root from 172.16.0.0/12',
+);
+
+
+
+
+

4.7 Authentication window is displayed more than once, why?

+

This happens if you are using a URL to start phpMyAdmin which is +different than the one set in your $cfg['PmaAbsoluteUri']. For +example, a missing “www”, or entering with an IP address while a domain +name is defined in the config file.

+
+
+

4.8 Which parameters can I use in the URL that starts phpMyAdmin?

+

When starting phpMyAdmin, you can use the db +and server parameters. This last one can contain +either the numeric host index (from $i of the configuration file) +or one of the host names present in the configuration file.

+

For example, to jump directly to a particular database, a URL can be constructed as +https://example.com/phpmyadmin/?db=sakila.

+
+

Changed in version 4.9.0: Support for using the pma_username and pma_password parameters was removed +in phpMyAdmin 4.9.0 (see PMASA-2019-4).

+
+
+
+
+

Browsers or client OS

+
+

5.1 I get an out of memory error, and my controls are non-functional, when trying to create a table with more than 14 columns.

+

We could reproduce this problem only under Win98/98SE. Testing under +WinNT4 or Win2K, we could easily create more than 60 columns. A +workaround is to create a smaller number of columns, then come back to +your table properties and add the other columns.

+
+
+

5.2 With Xitami 2.5b4, phpMyAdmin won’t process form fields.

+

This is not a phpMyAdmin problem but a Xitami known bug: you’ll face +it with each script/website that use forms. Upgrade or downgrade your +Xitami server.

+
+
+

5.3 I have problems dumping tables with Konqueror (phpMyAdmin 2.2.2).

+

With Konqueror 2.1.1: plain dumps, zip and gzip dumps work ok, except +that the proposed file name for the dump is always ‘tbl_dump.php’. +The bzip2 dumps don’t seem to work. With Konqueror 2.2.1: plain dumps +work; zip dumps are placed into the user’s temporary directory, so +they must be moved before closing Konqueror, or else they disappear. +gzip dumps give an error message. Testing needs to be done for +Konqueror 2.2.2.

+
+ +
+

5.5 (withdrawn).

+
+
+

5.6 (withdrawn).

+
+
+

5.7 I refresh (reload) my browser, and come back to the welcome page.

+

Some browsers support right-clicking into the frame you want to +refresh, just do this in the right frame.

+
+
+

5.8 With Mozilla 0.9.7 I have problems sending a query modified in the query box.

+

Looks like a Mozilla bug: 0.9.6 was OK. We will keep an eye on future +Mozilla versions.

+
+
+

5.9 With Mozilla 0.9.? to 1.0 and Netscape 7.0-PR1 I can’t type a whitespace in the SQL-Query edit area: the page scrolls down.

+

This is a Mozilla bug (see bug #26882 at BugZilla).

+
+
+

5.10 (withdrawn).

+
+
+

5.11 Extended-ASCII characters like German umlauts are displayed wrong.

+

Please ensure that you have set your browser’s character set to the +one of the language file you have selected on phpMyAdmin’s start page. +Alternatively, you can try the auto detection mode that is supported +by the recent versions of the most browsers.

+
+
+

5.12 Mac OS X Safari browser changes special characters to ”?”.

+

This issue has been reported by a Mac OS X user, who adds that Chimera, +Netscape and Mozilla do not have this problem.

+
+
+

5.13 (withdrawn)

+
+
+

5.14 (withdrawn)

+
+
+

5.15 (withdrawn)

+
+
+

5.16 With Internet Explorer, I get “Access is denied” Javascript errors. Or I cannot make phpMyAdmin work under Windows.

+

Please check the following points:

+
    +
  • Maybe you have defined your $cfg['PmaAbsoluteUri'] setting in +config.inc.php to an IP address and you are starting phpMyAdmin +with a URL containing a domain name, or the reverse situation.
  • +
  • Security settings in IE and/or Microsoft Security Center are too high, +thus blocking scripts execution.
  • +
  • The Windows Firewall is blocking Apache and MySQL. You must allow +HTTP ports (80 or 443) and MySQL +port (usually 3306) in the “in” and “out” directions.
  • +
+
+
+

5.17 With Firefox, I cannot delete rows of data or drop a database.

+

Many users have confirmed that the Tabbrowser Extensions plugin they +installed in their Firefox is causing the problem.

+
+
+

5.18 (withdrawn)

+
+
+

5.19 I get JavaScript errors in my browser.

+

Issues have been reported with some combinations of browser +extensions. To troubleshoot, disable all extensions then clear your +browser cache to see if the problem goes away.

+
+
+

5.20 I get errors about violating Content Security Policy.

+

If you see errors like:

+
Refused to apply inline style because it violates the following Content Security Policy directive
+
+
+

This is usually caused by some software, which wrongly rewrites +Content Security Policy headers. Usually this is caused by +antivirus proxy or browser addons which are causing such errors.

+

If you see these errors, try disabling the HTTP proxy in antivirus or disable +the Content Security Policy rewriting in it. If that doesn’t +help, try disabling browser extensions.

+

Alternatively it can be also server configuration issue (if the webserver is +configured to emit Content Security Policy headers, they can +override the ones from phpMyAdmin).

+

Programs known to cause these kind of errors:

+
    +
  • Kaspersky Internet Security
  • +
+
+
+

5.21 I get errors about potentially unsafe operation when browsing table or executing SQL query.

+

If you see errors like:

+
A potentially unsafe operation has been detected in your request to this site.
+
+
+

This is usually caused by web application firewall doing requests filtering. It +tries to prevent SQL injection, however phpMyAdmin is tool designed to execute +SQL queries, thus it makes it unusable.

+

Please whitelist phpMyAdmin scripts from the web application firewall settings +or disable it completely for phpMyAdmin path.

+

Programs known to cause these kind of errors:

+
    +
  • Wordfence Web Application Firewall
  • +
+
+
+
+

Using phpMyAdmin

+
+

6.1 I can’t insert new rows into a table / I can’t create a table - MySQL brings up a SQL error.

+

Examine the SQL error with care. +Often the problem is caused by specifying a wrong column-type. Common +errors include:

+
    +
  • Using VARCHAR without a size argument
  • +
  • Using TEXT or BLOB with a size argument
  • +
+

Also, look at the syntax chapter in the MySQL manual to confirm that +your syntax is correct.

+
+
+

6.2 When I create a table, I set an index for two columns and phpMyAdmin generates only one index with those two columns.

+

This is the way to create a multi-columns index. If you want two +indexes, create the first one when creating the table, save, then +display the table properties and click the Index link to create the +other index.

+
+
+

6.3 How can I insert a null value into my table?

+

Since version 2.2.3, you have a checkbox for each column that can be +null. Before 2.2.3, you had to enter “null”, without the quotes, as +the column’s value. Since version 2.5.5, you have to use the checkbox +to get a real NULL value, so if you enter “NULL” this means you want a +literal NULL in the column, and not a NULL value (this works in PHP4).

+
+
+

6.4 How can I backup my database or table?

+

Click on a database or table name in the navigation panel, the properties will +be displayed. Then on the menu, click “Export”, you can dump the structure, the +data, or both. This will generate standard SQL statements that can be +used to recreate your database/table. You will need to choose “Save as file”, +so that phpMyAdmin can transmit the resulting dump to your station. Depending +on your PHP configuration, you will see options to compress the dump. See also +the $cfg['ExecTimeLimit'] configuration variable. For +additional help on this subject, look for the word “dump” in this document.

+
+
+

6.5 How can I restore (upload) my database or table using a dump? How can I run a ”.sql” file?

+

Click on a database name in the navigation panel, the properties will +be displayed. Select “Import” from the list of tabs in the right–hand +frame (or “SQL” if your phpMyAdmin +version is previous to 2.7.0). In the “Location of the text file” +section, type in the path to your dump filename, or use the Browse +button. Then click Go. With version 2.7.0, the import engine has been +re–written, if possible it is suggested that you upgrade to take +advantage of the new features. For additional help on this subject, +look for the word “upload” in this document.

+

Note: For errors while importing of dumps exported from older MySQL versions to newer MySQL versions, +please check 6.41 I get import errors while importing the dumps exported from older MySQL versions (pre-5.7.6) into newer MySQL versions (5.7.7+), but they work fine when imported back on same older versions ?.

+
+
+

6.6 How can I use the relation table in Query-by-example?

+

Here is an example with the tables persons, towns and countries, all +located in the database “mydb”. If you don’t have a pma__relation +table, create it as explained in the configuration section. Then +create the example tables:

+
CREATE TABLE REL_countries (
+country_code char(1) NOT NULL default '',
+description varchar(10) NOT NULL default '',
+PRIMARY KEY (country_code)
+) ENGINE=MyISAM;
+
+INSERT INTO REL_countries VALUES ('C', 'Canada');
+
+CREATE TABLE REL_persons (
+id tinyint(4) NOT NULL auto_increment,
+person_name varchar(32) NOT NULL default '',
+town_code varchar(5) default '0',
+country_code char(1) NOT NULL default '',
+PRIMARY KEY (id)
+) ENGINE=MyISAM;
+
+INSERT INTO REL_persons VALUES (11, 'Marc', 'S', 'C');
+INSERT INTO REL_persons VALUES (15, 'Paul', 'S', 'C');
+
+CREATE TABLE REL_towns (
+town_code varchar(5) NOT NULL default '0',
+description varchar(30) NOT NULL default '',
+PRIMARY KEY (town_code)
+) ENGINE=MyISAM;
+
+INSERT INTO REL_towns VALUES ('S', 'Sherbrooke');
+INSERT INTO REL_towns VALUES ('M', 'Montréal');
+
+
+

To setup appropriate links and display information:

+
    +
  • on table “REL_persons” click Structure, then Relation view
  • +
  • for “town_code”, choose from dropdowns, “mydb”, “REL_towns”, “code” +for foreign database, table and column respectively
  • +
  • for “country_code”, choose from dropdowns, “mydb”, “REL_countries”, +“country_code” for foreign database, table and column respectively
  • +
  • on table “REL_towns” click Structure, then Relation view
  • +
  • in “Choose column to display”, choose “description”
  • +
  • repeat the two previous steps for table “REL_countries”
  • +
+

Then test like this:

+
    +
  • Click on your db name in the navigation panel
  • +
  • Choose “Query”
  • +
  • Use tables: persons, towns, countries
  • +
  • Click “Update query”
  • +
  • In the columns row, choose persons.person_name and click the “Show” +tickbox
  • +
  • Do the same for towns.description and countries.descriptions in the +other 2 columns
  • +
  • Click “Update query” and you will see in the query box that the +correct joins have been generated
  • +
  • Click “Submit query”
  • +
+
+
+

6.7 How can I use the “display column” feature?

+

Starting from the previous example, create the pma__table_info as +explained in the configuration section, then browse your persons +table, and move the mouse over a town code or country code. See also +6.21 In edit/insert mode, how can I see a list of possible values for a column, based on some foreign table? for an additional feature that “display column” +enables: drop-down list of possible values.

+
+
+

6.8 How can I produce a PDF schema of my database?

+

First the configuration variables “relation”, “table_coords” and +“pdf_pages” have to be filled in. Then you need to think about your +schema layout. Which tables will go on which pages?

+
    +
  • Select your database in the navigation panel.
  • +
  • Choose “Operations” in the navigation bar at the top.
  • +
  • Choose “Edit PDF Pages” near the +bottom of the page.
  • +
  • Enter a name for the first PDF page +and click Go. If you like, you can use the “automatic layout,” which +will put all your linked tables onto the new page.
  • +
  • Select the name of the new page (making sure the Edit radio button is +selected) and click Go.
  • +
  • Select a table from the list, enter its coordinates and click Save. +Coordinates are relative; your diagram will be automatically scaled to +fit the page. When initially placing tables on the page, just pick any +coordinates – say, 50x50. After clicking Save, you can then use the +6.28 How can I easily edit relational schema for export? to position the element correctly.
  • +
  • When you’d like to look at your PDF, first be sure to click the Save +button beneath the list of tables and coordinates, to save any changes you +made there. Then scroll all the way down, select the PDF options you +want, and click Go.
  • +
  • Internet Explorer for Windows may suggest an incorrect filename when +you try to save a generated PDF. +When saving a generated PDF, be +sure that the filename ends in ”.pdf”, for example “schema.pdf”. +Browsers on other operating systems, and other browsers on Windows, do +not have this problem.
  • +
+
+

See also

+

Relations

+
+
+
+

6.9 phpMyAdmin is changing the type of one of my columns!

+

No, it’s MySQL that is doing silent column type changing.

+
+
+

6.10 When creating a privilege, what happens with underscores in the database name?

+

If you do not put a backslash before the underscore, this is a +wildcard grant, and the underscore means “any character”. So, if the +database name is “john_db”, the user would get rights to john1db, +john2db ... If you put a backslash before the underscore, it means +that the database name will have a real underscore.

+
+
+

6.11 What is the curious symbol ø in the statistics pages?

+

It means “average”.

+
+
+

6.12 I want to understand some Export options.

+

Structure:

+
    +
  • “Add DROP TABLE” will add a line telling MySQL to drop the table, if it already +exists during the import. It does NOT drop the table after your +export, it only affects the import file.
  • +
  • “If Not Exists” will only create the table if it doesn’t exist. +Otherwise, you may get an error if the table name exists but has a +different structure.
  • +
  • “Add AUTO_INCREMENT value” ensures that AUTO_INCREMENT value (if +any) will be included in backup.
  • +
  • “Enclose table and column names with backquotes” ensures that column +and table names formed with special characters are protected.
  • +
  • “Add into comments” includes column comments, relations, and MIME +types set in the pmadb in the dump as SQL comments +(/* xxx */).
  • +
+

Data:

+
    +
  • “Complete inserts” adds the column names on every INSERT command, for +better documentation (but resulting file is bigger).
  • +
  • “Extended inserts” provides a shorter dump file by using only once the +INSERT verb and the table name.
  • +
  • “Delayed inserts” are best explained in the MySQL manual - INSERT DELAYED Syntax.
  • +
  • “Ignore inserts” treats errors as a warning instead. Again, more info +is provided in the MySQL manual - INSERT Syntax, but basically with +this selected, invalid values are adjusted and inserted rather than +causing the entire statement to fail.
  • +
+
+
+

6.13 I would like to create a database with a dot in its name.

+

This is a bad idea, because in MySQL the syntax “database.table” is +the normal way to reference a database and table name. Worse, MySQL +will usually let you create a database with a dot, but then you cannot +work with it, nor delete it.

+
+
+

6.14 (withdrawn).

+
+
+

6.15 I want to add a BLOB column and put an index on it, but MySQL says “BLOB column ‘...’ used in key specification without a key length”.

+

The right way to do this, is to create the column without any indexes, +then display the table structure and use the “Create an index” dialog. +On this page, you will be able to choose your BLOB column, and set a +size to the index, which is the condition to create an index on a BLOB +column.

+
+
+

6.16 How can I simply move in page with plenty editing fields?

+

You can use Ctrl+arrows (Option+Arrows in Safari) for moving on +most pages with many editing fields (table structure changes, row editing, +etc.).

+
+
+

6.17 Transformations: I can’t enter my own mimetype! What is this feature then useful for?

+

Defining mimetypes is of no use if you can’t put +transformations on them. Otherwise you could just put a comment on the +column. Because entering your own mimetype will cause serious syntax +checking issues and validation, this introduces a high-risk false- +user-input situation. Instead you have to initialize mimetypes using +functions or empty mimetype definitions.

+

Plus, you have a whole overview of available mimetypes. Who knows all those +mimetypes by heart so he/she can enter it at will?

+
+
+

6.18 Bookmarks: Where can I store bookmarks? Why can’t I see any bookmarks below the query box? What are these variables for?

+

You need to have configured the phpMyAdmin configuration storage for using bookmarks +feature. Once you have done that, you can use bookmarks in the SQL tab.

+
+

See also

+

Bookmarks

+
+
+
+

6.19 How can I create simple LATEX document to include exported table?

+

You can simply include table in your LATEX documents, +minimal sample document should look like following one (assuming you +have table exported in file table.tex):

+
\documentclass{article} % or any class you want
+\usepackage{longtable}  % for displaying table
+\begin{document}        % start of document
+\include{table}         % including exported table
+\end{document}          % end of document
+
+
+
+
+

6.20 I see a lot of databases which are not mine, and cannot access them.

+

You have one of these global privileges: CREATE TEMPORARY TABLES, SHOW +DATABASES, LOCK TABLES. Those privileges also enable users to see all the +database names. So if your users do not need those privileges, you can remove +them and their databases list will shorten.

+ +
+
+

6.21 In edit/insert mode, how can I see a list of possible values for a column, based on some foreign table?

+

You have to setup appropriate links between the tables, and also setup +the “display column” in the foreign table. See 6.6 How can I use the relation table in Query-by-example? for an +example. Then, if there are 100 values or less in the foreign table, a +drop-down list of values will be available. You will see two lists of +values, the first list containing the key and the display column, the +second list containing the display column and the key. The reason for +this is to be able to type the first letter of either the key or the +display column. For 100 values or more, a distinct window will appear, +to browse foreign key values and choose one. To change the default +limit of 100, see $cfg['ForeignKeyMaxLimit'].

+
+
+

6.22 Bookmarks: Can I execute a default bookmark automatically when entering Browse mode for a table?

+

Yes. If a bookmark has the same label as a table name and it’s not a +public bookmark, it will be executed.

+
+

See also

+

Bookmarks

+
+
+
+

6.23 Export: I heard phpMyAdmin can export Microsoft Excel files?

+

You can use CSV for Microsoft Excel, +which works out of the box.

+
+

Changed in version 3.4.5: Since phpMyAdmin 3.4.5 support for direct export to Microsoft Excel version +97 and newer was dropped.

+
+
+
+

6.24 Now that phpMyAdmin supports native MySQL 4.1.x column comments, what happens to my column comments stored in pmadb?

+

Automatic migration of a table’s pmadb-style column comments to the +native ones is done whenever you enter Structure page for this table.

+
+
+

6.25 (withdrawn).

+
+
+

6.26 How can I select a range of rows?

+

Click the first row of the range, hold the shift key and click the +last row of the range. This works everywhere you see rows, for example +in Browse mode or on the Structure page.

+
+
+

6.27 What format strings can I use?

+

In all places where phpMyAdmin accepts format strings, you can use +@VARIABLE@ expansion and strftime +format strings. The expanded variables depend on a context (for +example, if you haven’t chosen a table, you can not get the table +name), but the following variables can be used:

+
+
@HTTP_HOST@
+
HTTP host that runs phpMyAdmin
+
@SERVER@
+
MySQL server name
+
@VERBOSE@
+
Verbose MySQL server name as defined in $cfg['Servers'][$i]['verbose']
+
@VSERVER@
+
Verbose MySQL server name if set, otherwise normal
+
@DATABASE@
+
Currently opened database
+
@TABLE@
+
Currently opened table
+
@COLUMNS@
+
Columns of the currently opened table
+
@PHPMYADMIN@
+
phpMyAdmin with version
+
+
+
+

6.28 How can I easily edit relational schema for export?

+

By clicking on the button ‘toggle scratchboard’ on the page where you +edit x/y coordinates of those elements you can activate a scratchboard +where all your elements are placed. By clicking on an element, you can +move them around in the pre-defined area and the x/y coordinates will +get updated dynamically. Likewise, when entering a new position +directly into the input field, the new position in the scratchboard +changes after your cursor leaves the input field.

+

You have to click on the ‘OK’-button below the tables to save the new +positions. If you want to place a new element, first add it to the +table of elements and then you can drag the new element around.

+

By changing the paper size and the orientation you can change the size +of the scratchboard as well. You can do so by just changing the +dropdown field below, and the scratchboard will resize automatically, +without interfering with the current placement of the elements.

+

If ever an element gets out of range you can either enlarge the paper +size or click on the ‘reset’ button to place all elements below each +other.

+
+
+

6.29 Why can’t I get a chart from my query result table?

+

Not every table can be put to the chart. Only tables with one, two or +three columns can be visualised as a chart. Moreover the table must be +in a special format for chart script to understand it. Currently +supported formats can be found in Charts.

+
+
+

6.30 Import: How can I import ESRI Shapefiles?

+

An ESRI Shapefile is actually a set of several files, where .shp file +contains geometry data and .dbf file contains data related to those +geometry data. To read data from .dbf file you need to have PHP +compiled with the dBase extension (–enable-dbase). Otherwise only +geometry data will be imported.

+

To upload these set of files you can use either of the following +methods:

+

Configure upload directory with $cfg['UploadDir'], upload both .shp and .dbf files with +the same filename and chose the .shp file from the import page.

+

Create a zip archive with .shp and .dbf files and import it. For this +to work, you need to set $cfg['TempDir'] to a place where the web server user can +write (for example './tmp').

+

To create the temporary directory on a UNIX-based system, you can do:

+
cd phpMyAdmin
+mkdir tmp
+chmod o+rwx tmp
+
+
+
+
+

6.31 How do I create a relation in designer?

+

To select relation, click: The display column is shown in pink. To +set/unset a column as the display column, click the “Choose column to +display” icon, then click on the appropriate column name.

+
+
+

6.32 How can I use the zoom search feature?

+

The Zoom search feature is an alternative to table search feature. It allows +you to explore a table by representing its data in a scatter plot. You can +locate this feature by selecting a table and clicking the Search +tab. One of the sub-tabs in the Table Search page is +Zoom Search.

+

Consider the table REL_persons in 6.6 How can I use the relation table in Query-by-example? for +an example. To use zoom search, two columns need to be selected, for +example, id and town_code. The id values will be represented on one +axis and town_code values on the other axis. Each row will be +represented as a point in a scatter plot based on its id and +town_code. You can include two additional search criteria apart from +the two fields to display.

+

You can choose which field should be +displayed as label for each point. If a display column has been set +for the table (see 6.7 How can I use the “display column” feature?), it is taken as the label unless +you specify otherwise. You can also select the maximum number of rows +you want to be displayed in the plot by specifing it in the ‘Max rows +to plot’ field. Once you have decided over your criteria, click ‘Go’ +to display the plot.

+

After the plot is generated, you can use the +mousewheel to zoom in and out of the plot. In addition, panning +feature is enabled to navigate through the plot. You can zoom-in to a +certain level of detail and use panning to locate your area of +interest. Clicking on a point opens a dialogue box, displaying field +values of the data row represented by the point. You can edit the +values if required and click on submit to issue an update query. Basic +instructions on how to use can be viewed by clicking the ‘How to use?’ +link located just above the plot.

+
+
+

6.33 When browsing a table, how can I copy a column name?

+

Selecting the name of the column within the browse table header cell +for copying is difficult, as the columns support reordering by +dragging the header cells as well as sorting by clicking on the linked +column name. To copy a column name, double-click on the empty area +next to the column name, when the tooltip tells you to do so. This +will show you an input box with the column name. You may right-click +the column name within this input box to copy it to your clipboard.

+
+
+

6.34 How can I use the Favorite Tables feature?

+

Favorite Tables feature is very much similar to Recent Tables feature. +It allows you to add a shortcut for the frequently used tables of any +database in the navigation panel . You can easily navigate to any table +in the list by simply choosing it from the list. These tables are stored +in your browser’s local storage if you have not configured your +phpMyAdmin Configuration Storage. Otherwise these entries are stored in +phpMyAdmin Configuration Storage.

+

IMPORTANT: In absence of phpMyAdmin Configuration Storage, your Favorite +tables may be different in different browsers based on your different +selections in them.

+

To add a table to Favorite list simply click on the Gray star in front +of a table name in the list of tables of a Database and wait until it +turns to Yellow. +To remove a table from list, simply click on the Yellow star and +wait until it turns Gray again.

+

Using $cfg['NumFavoriteTables'] in your config.inc.php +file, you can define the maximum number of favorite tables shown in the +navigation panel. Its default value is 10.

+
+
+

6.35 How can I use the Range search feature?

+

With the help of range search feature, one can specify a range of values for +particular column(s) while performing search operation on a table from the Search +tab.

+

To use this feature simply click on the BETWEEN or NOT BETWEEN operators +from the operator select list in front of the column name. On choosing one of the +above options, a dialog box will show up asking for the Minimum and Maximum +value for that column. Only the specified range of values will be included +in case of BETWEEN and excluded in case of NOT BETWEEN from the final results.

+

Note: The Range search feature will work only Numeric and Date data type columns.

+
+
+

6.36 What is Central columns and how can I use this feature?

+

As the name suggests, the Central columns feature enables to maintain a central list of +columns per database to avoid similar name for the same data element and bring consistency +of data type for the same data element. You can use the central list of columns to +add an element to any table structure in that database which will save from writing +similar column name and column definition.

+

To add a column to central list, go to table structure page, check the columns you want +to include and then simply click on “Add to central columns”. If you want to add all +unique columns from more than one table from a database then go to database structure page, +check the tables you want to include and then select “Add columns to central list”.

+

To remove a column from central list, go to Table structure page, check the columns you want +to remove and then simply click on “Remove from central columns”. If you want to remove all +columns from more than one tables from a database then go to database structure page, +check the tables you want to include and then select “Remove columns from central list”.

+

To view and manage the central list, select the database you want to manage central columns +for then from the top menu click on “Central columns”. You will be taken to a page where +you will have options to edit, delete or add new columns to central list.

+
+
+

6.37 How can I use Improve Table structure feature?

+

Improve table structure feature helps to bring the table structure upto +Third Normal Form. A wizard is presented to user which asks questions about the +elements during the various steps for normalization and a new structure is proposed +accordingly to bring the table into the First/Second/Third Normal form. +On startup of the wizard, user gets to select upto what normal form they want to +normalize the table structure.

+

Here is an example table which you can use to test all of the three First, Second and +Third Normal Form.

+
CREATE TABLE `VetOffice` (
+ `petName` varchar(64) NOT NULL,
+ `petBreed` varchar(64) NOT NULL,
+ `petType` varchar(64) NOT NULL,
+ `petDOB` date NOT NULL,
+ `ownerLastName` varchar(64) NOT NULL,
+ `ownerFirstName` varchar(64) NOT NULL,
+ `ownerPhone1` int(12) NOT NULL,
+ `ownerPhone2` int(12) NOT NULL,
+ `ownerEmail` varchar(64) NOT NULL,
+);
+
+
+

The above table is not in First normal Form as no primary key exists. Primary key +is supposed to be (petName,`ownerLastName`,`ownerFirstName`) . If the primary key +is chosen as suggested the resultant table won’t be in Second as well as Third Normal +form as the following dependencies exists.

+
(OwnerLastName, OwnerFirstName) -> OwnerEmail
+(OwnerLastName, OwnerFirstName) -> OwnerPhone
+PetBreed -> PetType
+
+
+

Which says, OwnerEmail depends on OwnerLastName and OwnerFirstName. +OwnerPhone depends on OwnerLastName and OwnerFirstName. +PetType depends on PetBreed.

+
+
+

6.38 How can I reassign auto-incremented values?

+

Some users prefer their AUTO_INCREMENT values to be consecutive; this is not +always the case after row deletion.

+

Here are the steps to accomplish this. These are manual steps because they +involve a manual verification at one point.

+
    +
  • Ensure that you have exclusive access to the table to rearrange
  • +
  • On your primary key column (i.e. id), remove the AUTO_INCREMENT setting
  • +
  • Delete your primary key in Structure > indexes
  • +
  • Create a new column future_id as primary key, AUTO_INCREMENT
  • +
  • Browse your table and verify that the new increments correspond to what +you’re expecting
  • +
  • Drop your old id column
  • +
  • Rename the future_id column to id
  • +
  • Move the new id column via Structure > Move columns
  • +
+
+
+

6.39 What is the “Adjust privileges” option when renaming, copying, or moving a database, table, column, or procedure?

+

When renaming/copying/moving a database/table/column/procedure, +MySQL does not adjust the original privileges relating to these objects +on its own. By selecting this option, phpMyAdmin will adjust the privilege +table so that users have the same privileges on the new items.

+

For example: A user 'bob'@'localhost‘ has a ‘SELECT’ privilege on a +column named ‘id’. Now, if this column is renamed to ‘id_new’, MySQL, +on its own, would not adjust the column privileges to the new column name. +phpMyAdmin can make this adjustment for you automatically.

+

Notes:

+
    +
  • While adjusting privileges for a database, the privileges of all +database-related elements (tables, columns and procedures) are also adjusted +to the database’s new name.
  • +
  • Similarly, while adjusting privileges for a table, the privileges of all +the columns inside the new table are also adjusted.
  • +
  • While adjusting privileges, the user performing the operation must have the following +privileges:
      +
    • SELECT, INSERT, UPDATE, DELETE privileges on following tables: +mysql.`db`, mysql.`columns_priv`, mysql.`tables_priv`, mysql.`procs_priv`
    • +
    • FLUSH privilege (GLOBAL)
    • +
    +
  • +
+

Thus, if you want to replicate the database/table/column/procedure as it is +while renaming/copying/moving these objects, make sure you have checked this option.

+
+
+

6.40 I see “Bind parameters” checkbox in the “SQL” page. How do I write parameterized SQL queries?

+

From version 4.5, phpMyAdmin allows users to execute parameterized queries in the “SQL” page. +Parameters should be prefixed with a colon(:) and when the “Bind parameters” checkbox is checked +these parameters will be identified and input fields for these parameters will be presented. +Values entered in these field will be substituted in the query before being executed.

+
+
+

6.41 I get import errors while importing the dumps exported from older MySQL versions (pre-5.7.6) into newer MySQL versions (5.7.7+), but they work fine when imported back on same older versions ?

+

If you get errors like #1031 - Table storage engine for ‘table_name’ doesn’t have this option +while importing the dumps exported from pre-5.7.7 MySQL servers into new MySQL server versions 5.7.7+, +it might be because ROW_FORMAT=FIXED is not supported with InnoDB tables. Moreover, the value of +innodb_strict_mode would define if this would be reported as a warning or as an error.

+

Since MySQL version 5.7.9, the default value for innodb_strict_mode is ON and thus would generate +an error when such a CREATE TABLE or ALTER TABLE statement is encountered.

+

There are two ways of preventing such errors while importing:

+
    +
  • Change the value of innodb_strict_mode to OFF before starting the import and turn it ON after +the import is successfully completed.
  • +
  • This can be achieved in two ways:
      +
    • Go to ‘Variables’ page and edit the value of innodb_strict_mode
    • +
    • Run the query : SET GLOBAL `innodb_strict_mode = ‘[value]’`
    • +
    +
  • +
+

After the import is done, it is suggested that the value of innodb_strict_mode should be reset to the +original value.

+
+
+
+

phpMyAdmin project

+
+

7.1 I have found a bug. How do I inform developers?

+

Our issues tracker is located at <https://github.com/phpmyadmin/phpmyadmin/issues>. +For security issues, please refer to the instructions at <https://www.phpmyadmin.net/security> to email +the developers directly.

+
+
+

7.2 I want to translate the messages to a new language or upgrade an existing language, where do I start?

+

Translations are very welcome and all you need to have are the +language skills. The easiest way is to use our online translation +service. You can check +out all the possibilities to translate in the translate section on +our website.

+
+
+

7.3 I would like to help out with the development of phpMyAdmin. How should I proceed?

+

We welcome every contribution to the development of phpMyAdmin. You +can check out all the possibilities to contribute in the contribute +section on our website.

+ +
+
+
+

Security

+
+

8.1 Where can I get information about the security alerts issued for phpMyAdmin?

+

Please refer to <https://www.phpmyadmin.net/security/>.

+
+
+

8.2 How can I protect phpMyAdmin against brute force attacks?

+

If you use Apache web server, phpMyAdmin exports information about +authentication to the Apache environment and it can be used in Apache +logs. Currently there are two variables available:

+
+
userID
+
User name of currently active user (he does not have to be logged in).
+
userStatus
+
Status of currently active user, one of ok (user is logged in), +mysql-denied (MySQL denied user login), allow-denied (user denied +by allow/deny rules), root-denied (root is denied in configuration), +empty-denied (empty password is denied).
+
+

LogFormat directive for Apache can look like following:

+
LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\" %{userID}n %{userStatus}n"   pma_combined
+
+
+

You can then use any log analyzing tools to detect possible break-in +attempts.

+
+
+

8.3 Why are there path disclosures when directly loading certain files?

+

This is a server configuration problem. Never enable display_errors on a production site.

+
+
+

8.4 CSV files exported from phpMyAdmin could allow a formula injection attack.

+

It is possible to generate a CSV file that, when imported to a spreadsheet program such as Microsoft Excel, +could potentially allow the execution of arbitrary commands.

+

The CSV files generated by phpMyAdmin could potentially contain text that would be interpreted by a spreadsheet program as +a formula, but we do not believe escaping those fields is the proper behavior. There is no means to properly escape and +differentiate between a desired text output and a formula that should be escaped, and CSV is a text format where function +definitions should not be interpreted anyway. We have discussed this at length and feel it is the responsibility of the +spreadsheet program to properly parse and sanitize such data on input instead.

+

Google also has a similar view.

+
+
+
+

Synchronization

+
+

9.1 (withdrawn).

+
+
+

9.2 (withdrawn).

+
+
+
+ + +
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/php/apps/phpmyadmin49/html/doc/html/genindex.html b/php/apps/phpmyadmin49/html/doc/html/genindex.html new file mode 100644 index 00000000..6ca07a2b --- /dev/null +++ b/php/apps/phpmyadmin49/html/doc/html/genindex.html @@ -0,0 +1,4265 @@ + + + + + + + + + Index — phpMyAdmin 4.9.1 documentation + + + + + + + + + + + + + + + + +
+
+
+
+ + +

Index

+ +
+ Symbols + | A + | B + | C + | D + | E + | F + | G + | H + | I + | J + | K + | L + | M + | N + | O + | P + | Q + | R + | S + | T + | U + | V + | W + | X + | Z + +
+

Symbols

+ + + +
+ +
$cfg['ActionLinksMode'] +
+ + +
$cfg['AllowArbitraryServer'], [1], [2], [3] +
+ + +
$cfg['AllowThirdPartyFraming'] +
+ + +
$cfg['AllowUserDropDatabase'] +
+ + +
$cfg['ArbitraryServerRegexp'], [1] +
+ + +
$cfg['AuthLog'], [1], [2] +
+ + +
$cfg['AuthLogSuccess'], [1] +
+ + +
$cfg['AvailableCharsets'] +
+ + +
$cfg['blowfish_secret'], [1] +
+ + +
$cfg['BrowseMarkerEnable'] +
+ + +
$cfg['BrowseMIME'] +
+ + +
$cfg['BrowsePointerEnable'] +
+ + +
$cfg['BZipDump'] +
+ + +
$cfg['CaptchaLoginPrivateKey'], [1] +
+ + +
$cfg['CaptchaLoginPublicKey'], [1] +
+ + +
$cfg['CharEditing'], [1] +
+ + +
$cfg['CharTextareaCols'] +
+ + +
$cfg['CharTextareaRows'] +
+ + +
$cfg['CheckConfigurationPermissions'] +
+ + +
$cfg['CodemirrorEnable'] +
+ + +
$cfg['CompressOnFly'], [1] +
+ + +
$cfg['Confirm'] +
+ + +
$cfg['Console']['AlwaysExpand'] +
+ + +
$cfg['Console']['CurrentQuery'] +
+ + +
$cfg['Console']['DarkTheme'] +
+ + +
$cfg['Console']['EnterExecutes'] +
+ + +
$cfg['Console']['Height'] +
+ + +
$cfg['Console']['Mode'] +
+ + +
$cfg['Console']['StartHistory'] +
+ + +
$cfg['ConsoleEnterExecutes'] +
+ + +
$cfg['CSPAllow'] +
+ + +
$cfg['DBG'] +
+ + +
$cfg['DBG']['demo'] +
+ + +
$cfg['DBG']['simple2fa'], [1] +
+ + +
$cfg['DBG']['sql'], [1] +
+ + +
$cfg['DBG']['sqllog'] +
+ + +
$cfg['DefaultConnectionCollation'] +
+ + +
$cfg['DefaultForeignKeyChecks'] +
+ + +
$cfg['DefaultFunctions'] +
+ + +
$cfg['DefaultLang'] +
+ + +
$cfg['DefaultQueryDatabase'] +
+ + +
$cfg['DefaultQueryTable'] +
+ + +
$cfg['DefaultTabDatabase'] +
+ + +
$cfg['DefaultTabServer'] +
+ + +
$cfg['DefaultTabTable'] +
+ + +
$cfg['DefaultTransformations'], [1] +
+ + +
$cfg['DefaultTransformations']['Bool2Text'] +
+ + +
$cfg['DefaultTransformations']['DateFormat'] +
+ + +
$cfg['DefaultTransformations']['External'] +
+ + +
$cfg['DefaultTransformations']['Hex'] +
+ + +
$cfg['DefaultTransformations']['Inline'] +
+ + +
$cfg['DefaultTransformations']['PreApPend'] +
+ + +
$cfg['DefaultTransformations']['Substring'] +
+ + +
$cfg['DefaultTransformations']['TextImageLink'] +
+ + +
$cfg['DefaultTransformations']['TextLink'] +
+ + +
$cfg['DisableMultiTableMaintenance'] +
+ + +
$cfg['DisableShortcutKeys'], [1] +
+ + +
$cfg['DisplayServersList'] +
+ + +
$cfg['EnableAutocompleteForTablesAndColumns'] +
+ + +
$cfg['ExecTimeLimit'], [1] +
+ + +
$cfg['Export'] +
+ + +
$cfg['Export']['charset'], [1] +
+ + +
$cfg['Export']['file_template_database'] +
+ + +
$cfg['Export']['file_template_server'] +
+ + +
$cfg['Export']['file_template_table'] +
+ + +
$cfg['Export']['format'] +
+ + +
$cfg['Export']['method'] +
+ + +
$cfg['FilterLanguages'] +
+ + +
$cfg['FirstLevelNavigationItems'] +
+ + +
$cfg['FontSize'] +
+ + +
$cfg['ForceSSL'] +
+ + +
$cfg['ForeignKeyDropdownOrder'], [1] +
+ + +
$cfg['ForeignKeyMaxLimit'], [1] +
+ + +
$cfg['GD2Available'] +
+ + +
$cfg['GridEditing'] +
+ + +
$cfg['GZipDump'] +
+ + +
$cfg['HideStructureActions'] +
+ + +
$cfg['IconvExtraParams'] +
+ + +
$cfg['IgnoreMultiSubmitErrors'] +
+ + +
$cfg['Import'] +
+ + +
$cfg['Import']['charset'], [1] +
+ + +
$cfg['InitialSlidersState'] +
+ + +
$cfg['InsertRows'] +
+ + +
$cfg['Lang'] +
+ + +
$cfg['LimitChars'] +
+ + +
$cfg['LinkLengthLimit'] +
+ + +
$cfg['LoginCookieDeleteAll'] +
+ + +
$cfg['LoginCookieRecall'] +
+ + +
$cfg['LoginCookieStore'] +
+ + +
$cfg['LoginCookieValidity'], [1] +
+ + +
$cfg['LoginCookieValidityDisableWarning'] +
+ + +
$cfg['LongtextDoubleTextarea'] +
+ + +
$cfg['MaxCharactersInDisplayedSQL'] +
+ + +
$cfg['MaxDbList'] +
+ + +
$cfg['MaxExactCount'], [1], [2] +
+ + +
$cfg['MaxExactCountViews'] +
+ + +
$cfg['MaxNavigationItems'] +
+ + +
$cfg['MaxRows'] +
+ + +
$cfg['MaxSizeForInputField'] +
+ + +
$cfg['MaxTableList'] +
+ + +
$cfg['MemoryLimit'] +
+ + +
$cfg['MinSizeForInputField'] +
+ + +
$cfg['MysqlMinVersion'] +
+ + +
$cfg['NaturalOrder'] +
+ + +
$cfg['NavigationDisplayLogo'] +
+ + +
$cfg['NavigationDisplayServers'] +
+ + +
$cfg['NavigationLinkWithMainPanel'] +
+ + +
$cfg['NavigationLogoLink'] +
+ + +
$cfg['NavigationLogoLinkWindow'] +
+ + +
$cfg['NavigationTreeDbSeparator'], [1] +
+ + +
$cfg['NavigationTreeDefaultTabTable'], [1], [2] +
+ + +
$cfg['NavigationTreeDefaultTabTable2'] +
+ + +
$cfg['NavigationTreeDisplayDbFilterMinimum'] +
+ + +
$cfg['NavigationTreeDisplayItemFilterMinimum'] +
+ + +
$cfg['NavigationTreeEnableExpansion'] +
+ + +
$cfg['NavigationTreeEnableGrouping'] +
+ + +
$cfg['NavigationTreePointerEnable'] +
+ + +
$cfg['NavigationTreeShowEvents'] +
+ + +
$cfg['NavigationTreeShowFunctions'] +
+ + +
$cfg['NavigationTreeShowProcedures'] +
+ + +
$cfg['NavigationTreeShowTables'] +
+ + +
$cfg['NavigationTreeShowViews'] +
+ + +
$cfg['NavigationTreeTableLevel'] +
+ + +
$cfg['NavigationTreeTableSeparator'], [1] +
+ + +
$cfg['NavigationWidth'] +
+ + +
$cfg['NumFavoriteTables'], [1], [2] +
+ + +
$cfg['NumRecentTables'], [1] +
+ + +
$cfg['OBGzip'], [1], [2], [3] +
+ + +
$cfg['Order'] +
+ + +
$cfg['PDFDefaultPageSize'] +
+ + +
$cfg['PDFPageSizes'], [1] +
+ + +
$cfg['PersistentConnections'] +
+ + +
$cfg['PmaAbsoluteUri'], [1], [2], [3], [4], [5], [6], [7] +
+ + +
$cfg['PmaNoRelation_DisableWarning'] +
+ +
+ +
$cfg['PropertiesNumColumns'] +
+ + +
$cfg['ProtectBinary'] +
+ + +
$cfg['ProxyPass'] +
+ + +
$cfg['ProxyUrl'] +
+ + +
$cfg['ProxyUser'] +
+ + +
$cfg['QueryHistoryDB'], [1], [2] +
+ + +
$cfg['QueryHistoryMax'], [1], [2] +
+ + +
$cfg['RecodingEngine'] +
+ + +
$cfg['RelationalDisplay'] +
+ + +
$cfg['RememberSorting'], [1] +
+ + +
$cfg['RepeatCells'] +
+ + +
$cfg['ReservedWordDisableWarning'] +
+ + +
$cfg['RetainQueryBox'] +
+ + +
$cfg['RowActionLinks'] +
+ + +
$cfg['RowActionLinksWithoutUnique'] +
+ + +
$cfg['RowActionType'] +
+ + +
$cfg['SaveCellsAtOnce'] +
+ + +
$cfg['SaveDir'], [1] +
+ + +
$cfg['SendErrorReports'] +
+ + +
$cfg['ServerDefault'], [1], [2] +
+ + +
$cfg['ServerLibraryDifference_DisableWarning'] +
+ + +
$cfg['Servers'], [1], [2], [3], [4] +
+ + +
$cfg['Servers'][$i]['AllowDeny']['order'], [1], [2] +
+ + +
$cfg['Servers'][$i]['AllowDeny']['rules'], [1], [2], [3], [4], [5] +
+ + +
$cfg['Servers'][$i]['AllowNoPassword'] +
+ + +
$cfg['Servers'][$i]['AllowRoot'], [1] +
+ + +
$cfg['Servers'][$i]['auth_http_realm'] +
+ + +
$cfg['Servers'][$i]['auth_type'], [1], [2] +
+ + +
$cfg['Servers'][$i]['bookmarktable'], [1] +
+ + +
$cfg['Servers'][$i]['central_columns'], [1] +
+ + +
$cfg['Servers'][$i]['column_info'], [1], [2] +
+ + +
$cfg['Servers'][$i]['compress'] +
+ + +
$cfg['Servers'][$i]['connect_type'] +
+ + +
$cfg['Servers'][$i]['control_*'], [1], [2], [3] +
+ + +
$cfg['Servers'][$i]['controlhost'], [1] +
+ + +
$cfg['Servers'][$i]['controlpass'], [1], [2] +
+ + +
$cfg['Servers'][$i]['controlport'], [1] +
+ + +
$cfg['Servers'][$i]['controluser'], [1], [2], [3], [4] +
+ + +
$cfg['Servers'][$i]['designer_settings'], [1] +
+ + +
$cfg['Servers'][$i]['DisableIS'] +
+ + +
$cfg['Servers'][$i]['export_templates'], [1] +
+ + +
$cfg['Servers'][$i]['extension'] +
+ + +
$cfg['Servers'][$i]['favorite'], [1] +
+ + +
$cfg['Servers'][$i]['hide_db'] +
+ + +
$cfg['Servers'][$i]['history'], [1], [2] +
+ + +
$cfg['Servers'][$i]['host'], [1], [2], [3], [4], [5], [6], [7], [8], [9] +
+ + +
$cfg['Servers'][$i]['LogoutURL'] +
+ + +
$cfg['Servers'][$i]['MaxTableUiprefs'] +
+ + +
$cfg['Servers'][$i]['navigationhiding'], [1] +
+ + +
$cfg['Servers'][$i]['nopassword'] +
+ + +
$cfg['Servers'][$i]['only_db'] +
+ + +
$cfg['Servers'][$i]['password'], [1] +
+ + +
$cfg['Servers'][$i]['pdf_pages'], [1], [2] +
+ + +
$cfg['Servers'][$i]['pmadb'], [1], [2], [3], [4], [5], [6], [7], [8], [9], [10], [11], [12], [13], [14], [15], [16], [17], [18], [19], [20], [21], [22] +
+ + +
$cfg['Servers'][$i]['port'], [1] +
+ + +
$cfg['Servers'][$i]['recent'], [1] +
+ + +
$cfg['Servers'][$i]['relation'], [1] +
+ + +
$cfg['Servers'][$i]['savedsearches'], [1] +
+ + +
$cfg['Servers'][$i]['SessionTimeZone'] +
+ + +
$cfg['Servers'][$i]['SignonCookieParams'], [1], [2] +
+ + +
$cfg['Servers'][$i]['SignonScript'], [1], [2], [3], [4], [5] +
+ + +
$cfg['Servers'][$i]['SignonSession'], [1], [2] +
+ + +
$cfg['Servers'][$i]['SignonURL'], [1], [2] +
+ + +
$cfg['Servers'][$i]['socket'], [1], [2] +
+ + +
$cfg['Servers'][$i]['ssl'], [1], [2], [3], [4], [5], [6], [7], [8], [9], [10] +
+ + +
$cfg['Servers'][$i]['ssl_ca'], [1], [2], [3], [4], [5], [6], [7], [8], [9], [10] +
+ + +
$cfg['Servers'][$i]['ssl_ca_path'], [1], [2], [3], [4], [5], [6], [7], [8], [9] +
+ + +
$cfg['Servers'][$i]['ssl_cert'], [1], [2], [3], [4], [5], [6], [7], [8], [9], [10] +
+ + +
$cfg['Servers'][$i]['ssl_ciphers'], [1], [2], [3], [4], [5], [6], [7], [8] +
+ + +
$cfg['Servers'][$i]['ssl_key'], [1], [2], [3], [4], [5], [6], [7], [8], [9], [10] +
+ + +
$cfg['Servers'][$i]['ssl_verify'], [1], [2], [3], [4], [5], [6], [7], [8], [9], [10], [11] +
+ + +
$cfg['Servers'][$i]['table_coords'], [1], [2] +
+ + +
$cfg['Servers'][$i]['table_info'], [1] +
+ + +
$cfg['Servers'][$i]['table_uiprefs'], [1], [2], [3], [4] +
+ + +
$cfg['Servers'][$i]['tracking'], [1] +
+ + +
$cfg['Servers'][$i]['tracking_add_drop_database'] +
+ + +
$cfg['Servers'][$i]['tracking_add_drop_table'] +
+ + +
$cfg['Servers'][$i]['tracking_add_drop_view'] +
+ + +
$cfg['Servers'][$i]['tracking_default_statements'] +
+ + +
$cfg['Servers'][$i]['tracking_version_auto_create'] +
+ + +
$cfg['Servers'][$i]['user'], [1] +
+ + +
$cfg['Servers'][$i]['userconfig'], [1] +
+ + +
$cfg['Servers'][$i]['usergroups'], [1], [2], [3] +
+ + +
$cfg['Servers'][$i]['users'], [1] +
+ + +
$cfg['Servers'][$i]['verbose'], [1], [2], [3], [4] +
+ + +
$cfg['SessionSavePath'], [1] +
+ + +
$cfg['ShowAll'] +
+ + +
$cfg['ShowBrowseComments'] +
+ + +
$cfg['ShowChgPassword'] +
+ + +
$cfg['ShowColumnComments'] +
+ + +
$cfg['ShowCreateDb'] +
+ + +
$cfg['ShowDatabasesNavigationAsTree'] +
+ + +
$cfg['ShowDbStructureCreation'] +
+ + +
$cfg['ShowDbStructureLastCheck'] +
+ + +
$cfg['ShowDbStructureLastUpdate'] +
+ + +
$cfg['ShowFieldTypesInDataEditView'] +
+ + +
$cfg['ShowFunctionFields'] +
+ + +
$cfg['ShowGitRevision'] +
+ + +
$cfg['ShowHint'] +
+ + +
$cfg['ShowPhpInfo'] +
+ + +
$cfg['ShowPropertyComments'] +
+ + +
$cfg['ShowServerInfo'] +
+ + +
$cfg['ShowSQL'] +
+ + +
$cfg['ShowStats'] +
+ + +
$cfg['SkipLockedTables'] +
+ + +
$cfg['SQLQuery']['Edit'] +
+ + +
$cfg['SQLQuery']['Explain'] +
+ + +
$cfg['SQLQuery']['Refresh'] +
+ + +
$cfg['SQLQuery']['ShowAsPHP'] +
+ + +
$cfg['SuhosinDisableWarning'], [1] +
+ + +
$cfg['TableNavigationLinksMode'] +
+ + +
$cfg['TablePrimaryKeyOrder'] +
+ + +
$cfg['TabsMode'] +
+ + +
$cfg['TempDir'], [1], [2], [3], [4] +
+ + +
$cfg['TextareaAutoSelect'] +
+ + +
$cfg['TextareaCols'] +
+ + +
$cfg['TextareaRows'] +
+ + +
$cfg['ThemeDefault'], [1] +
+ + +
$cfg['ThemeManager'], [1], [2] +
+ + +
$cfg['ThemePerServer'] +
+ + +
$cfg['TitleDatabase'] +
+ + +
$cfg['TitleDefault'] +
+ + +
$cfg['TitleServer'] +
+ + +
$cfg['TitleTable'] +
+ + +
$cfg['TranslationWarningThreshold'] +
+ + +
$cfg['TrustedProxies'], [1] +
+ + +
$cfg['UploadDir'], [1], [2], [3] +
+ + +
$cfg['UseDbSearch'] +
+ + +
$cfg['UserprefsDeveloperTab'] +
+ + +
$cfg['UserprefsDisallow'], [1] +
+ + +
$cfg['VersionCheck'] +
+ + +
$cfg['ZeroConf'], [1] +
+ + +
$cfg['ZipDump'] +
+ + +
.htaccess +
+ +
+ +

A

+ + + +
+ +
ACL +
+ + +
ActionLinksMode +
+ + +
AllowArbitraryServer +
+ + +
AllowDeny, order +
+ + +
AllowDeny, rules +
+ + +
AllowNoPassword +
+ + +
AllowRoot +
+ + +
AllowThirdPartyFraming +
+ + +
AllowUserDropDatabase +
+ + +
ArbitraryServerRegexp +
+ +
+ +
auth_http_realm +
+ + +
auth_type +
+ + +
Authentication mode +
+ +
+ +
Config +
+ + +
Cookie +
+ + +
HTTP +
+ + +
Signon +
+ +
+ +
AuthLog +
+ + +
AuthLogSuccess +
+ + +
AvailableCharsets +
+ +
+ +

B

+ + + +
+ +
Blowfish +
+ + +
blowfish_secret +
+ + +
bookmarktable +
+ + +
BrowseMarkerEnable +
+ +
+ +
BrowseMIME +
+ + +
BrowsePointerEnable +
+ + +
Browser +
+ + +
bzip2 +
+ + +
BZipDump +
+ +
+ +

C

+ + + +
+ +
CaptchaLoginPrivateKey +
+ + +
CaptchaLoginPublicKey +
+ + +
central_columns +
+ + +
CGI +
+ + +
Changelog +
+ + +
CharEditing +
+ + +
CharTextareaCols +
+ + +
CharTextareaRows +
+ + +
CheckConfigurationPermissions +
+ + +
Client +
+ + +
CodemirrorEnable +
+ + +
column +
+ + +
column_info +
+ + +
comment (global variable or constant) +
+ + +
compress +
+ + +
CompressOnFly +
+ + +
+ Config +
+ +
+ +
Authentication mode +
+ +
+ +
config.inc.php +
+ + +
+ configuration option +
+ +
+ +
$cfg['ActionLinksMode'] +
+ + +
$cfg['AllowArbitraryServer'], [1], [2], [3] +
+ + +
$cfg['AllowThirdPartyFraming'] +
+ + +
$cfg['AllowUserDropDatabase'] +
+ + +
$cfg['ArbitraryServerRegexp'], [1] +
+ + +
$cfg['AuthLog'], [1], [2] +
+ + +
$cfg['AuthLogSuccess'], [1] +
+ + +
$cfg['AvailableCharsets'] +
+ + +
$cfg['BZipDump'] +
+ + +
$cfg['BrowseMIME'] +
+ + +
$cfg['BrowseMarkerEnable'] +
+ + +
$cfg['BrowsePointerEnable'] +
+ + +
$cfg['CSPAllow'] +
+ + +
$cfg['CaptchaLoginPrivateKey'], [1] +
+ + +
$cfg['CaptchaLoginPublicKey'], [1] +
+ + +
$cfg['CharEditing'], [1] +
+ + +
$cfg['CharTextareaCols'] +
+ + +
$cfg['CharTextareaRows'] +
+ + +
$cfg['CheckConfigurationPermissions'] +
+ + +
$cfg['CodemirrorEnable'] +
+ + +
$cfg['CompressOnFly'], [1] +
+ + +
$cfg['Confirm'] +
+ + +
$cfg['Console']['AlwaysExpand'] +
+ + +
$cfg['Console']['CurrentQuery'] +
+ + +
$cfg['Console']['DarkTheme'] +
+ + +
$cfg['Console']['EnterExecutes'] +
+ + +
$cfg['Console']['Height'] +
+ + +
$cfg['Console']['Mode'] +
+ + +
$cfg['Console']['StartHistory'] +
+ + +
$cfg['ConsoleEnterExecutes'] +
+ + +
$cfg['DBG'] +
+ + +
$cfg['DBG']['demo'] +
+ + +
$cfg['DBG']['simple2fa'], [1] +
+ + +
$cfg['DBG']['sql'], [1] +
+ + +
$cfg['DBG']['sqllog'] +
+ + +
$cfg['DefaultConnectionCollation'] +
+ + +
$cfg['DefaultForeignKeyChecks'] +
+ + +
$cfg['DefaultFunctions'] +
+ + +
$cfg['DefaultLang'] +
+ + +
$cfg['DefaultQueryDatabase'] +
+ + +
$cfg['DefaultQueryTable'] +
+ + +
$cfg['DefaultTabDatabase'] +
+ + +
$cfg['DefaultTabServer'] +
+ + +
$cfg['DefaultTabTable'] +
+ + +
$cfg['DefaultTransformations'], [1] +
+ + +
$cfg['DefaultTransformations']['Bool2Text'] +
+ + +
$cfg['DefaultTransformations']['DateFormat'] +
+ + +
$cfg['DefaultTransformations']['External'] +
+ + +
$cfg['DefaultTransformations']['Hex'] +
+ + +
$cfg['DefaultTransformations']['Inline'] +
+ + +
$cfg['DefaultTransformations']['PreApPend'] +
+ + +
$cfg['DefaultTransformations']['Substring'] +
+ + +
$cfg['DefaultTransformations']['TextImageLink'] +
+ + +
$cfg['DefaultTransformations']['TextLink'] +
+ + +
$cfg['DisableMultiTableMaintenance'] +
+ + +
$cfg['DisableShortcutKeys'], [1] +
+ + +
$cfg['DisplayServersList'] +
+ + +
$cfg['EnableAutocompleteForTablesAndColumns'] +
+ + +
$cfg['ExecTimeLimit'], [1] +
+ + +
$cfg['Export'] +
+ + +
$cfg['Export']['charset'], [1] +
+ + +
$cfg['Export']['file_template_database'] +
+ + +
$cfg['Export']['file_template_server'] +
+ + +
$cfg['Export']['file_template_table'] +
+ + +
$cfg['Export']['format'] +
+ + +
$cfg['Export']['method'] +
+ + +
$cfg['FilterLanguages'] +
+ + +
$cfg['FirstLevelNavigationItems'] +
+ + +
$cfg['FontSize'] +
+ + +
$cfg['ForceSSL'] +
+ + +
$cfg['ForeignKeyDropdownOrder'], [1] +
+ + +
$cfg['ForeignKeyMaxLimit'], [1] +
+ + +
$cfg['GD2Available'] +
+ + +
$cfg['GZipDump'] +
+ + +
$cfg['GridEditing'] +
+ + +
$cfg['HideStructureActions'] +
+ + +
$cfg['IconvExtraParams'] +
+ + +
$cfg['IgnoreMultiSubmitErrors'] +
+ + +
$cfg['Import'] +
+ + +
$cfg['Import']['charset'], [1] +
+ + +
$cfg['InitialSlidersState'] +
+ + +
$cfg['InsertRows'] +
+ + +
$cfg['Lang'] +
+ + +
$cfg['LimitChars'] +
+ + +
$cfg['LinkLengthLimit'] +
+ + +
$cfg['LoginCookieDeleteAll'] +
+ + +
$cfg['LoginCookieRecall'] +
+ + +
$cfg['LoginCookieStore'] +
+ + +
$cfg['LoginCookieValidity'], [1] +
+ + +
$cfg['LoginCookieValidityDisableWarning'] +
+ + +
$cfg['LongtextDoubleTextarea'] +
+ + +
$cfg['MaxCharactersInDisplayedSQL'] +
+ + +
$cfg['MaxDbList'] +
+ + +
$cfg['MaxExactCount'], [1], [2] +
+ + +
$cfg['MaxExactCountViews'] +
+ + +
$cfg['MaxNavigationItems'] +
+ + +
$cfg['MaxRows'] +
+ + +
$cfg['MaxSizeForInputField'] +
+ + +
$cfg['MaxTableList'] +
+ + +
$cfg['MemoryLimit'] +
+ + +
$cfg['MinSizeForInputField'] +
+ + +
$cfg['MysqlMinVersion'] +
+ + +
$cfg['NaturalOrder'] +
+ + +
$cfg['NavigationDisplayLogo'] +
+ + +
$cfg['NavigationDisplayServers'] +
+ + +
$cfg['NavigationLinkWithMainPanel'] +
+ + +
$cfg['NavigationLogoLink'] +
+ + +
$cfg['NavigationLogoLinkWindow'] +
+ + +
$cfg['NavigationTreeDbSeparator'], [1] +
+ + +
$cfg['NavigationTreeDefaultTabTable'], [1], [2] +
+ + +
$cfg['NavigationTreeDefaultTabTable2'] +
+ + +
$cfg['NavigationTreeDisplayDbFilterMinimum'] +
+ + +
$cfg['NavigationTreeDisplayItemFilterMinimum'] +
+ + +
$cfg['NavigationTreeEnableExpansion'] +
+ + +
$cfg['NavigationTreeEnableGrouping'] +
+ + +
$cfg['NavigationTreePointerEnable'] +
+ + +
$cfg['NavigationTreeShowEvents'] +
+ + +
$cfg['NavigationTreeShowFunctions'] +
+ + +
$cfg['NavigationTreeShowProcedures'] +
+ + +
$cfg['NavigationTreeShowTables'] +
+ + +
$cfg['NavigationTreeShowViews'] +
+ + +
$cfg['NavigationTreeTableLevel'] +
+ + +
$cfg['NavigationTreeTableSeparator'], [1] +
+ + +
$cfg['NavigationWidth'] +
+ + +
$cfg['NumFavoriteTables'], [1], [2] +
+ + +
$cfg['NumRecentTables'], [1] +
+ + +
$cfg['OBGzip'], [1], [2], [3] +
+ + +
$cfg['Order'] +
+ + +
$cfg['PDFDefaultPageSize'] +
+ + +
$cfg['PDFPageSizes'], [1] +
+ + +
$cfg['PersistentConnections'] +
+ + +
$cfg['PmaAbsoluteUri'], [1], [2], [3], [4], [5], [6], [7] +
+ + +
$cfg['PmaNoRelation_DisableWarning'] +
+ + +
$cfg['PropertiesNumColumns'] +
+ + +
$cfg['ProtectBinary'] +
+ + +
$cfg['ProxyPass'] +
+ + +
$cfg['ProxyUrl'] +
+ + +
$cfg['ProxyUser'] +
+ + +
$cfg['QueryHistoryDB'], [1], [2] +
+ + +
$cfg['QueryHistoryMax'], [1], [2] +
+ + +
$cfg['RecodingEngine'] +
+ + +
$cfg['RelationalDisplay'] +
+ + +
$cfg['RememberSorting'], [1] +
+ + +
$cfg['RepeatCells'] +
+ + +
$cfg['ReservedWordDisableWarning'] +
+ + +
$cfg['RetainQueryBox'] +
+ + +
$cfg['RowActionLinks'] +
+ + +
$cfg['RowActionLinksWithoutUnique'] +
+ + +
$cfg['RowActionType'] +
+ + +
$cfg['SQLQuery']['Edit'] +
+ + +
$cfg['SQLQuery']['Explain'] +
+ + +
$cfg['SQLQuery']['Refresh'] +
+ + +
$cfg['SQLQuery']['ShowAsPHP'] +
+ + +
$cfg['SaveCellsAtOnce'] +
+ + +
$cfg['SaveDir'], [1] +
+ + +
$cfg['SendErrorReports'] +
+ + +
$cfg['ServerDefault'], [1], [2] +
+ + +
$cfg['ServerLibraryDifference_DisableWarning'] +
+ + +
$cfg['Servers'], [1], [2], [3], [4] +
+ + +
$cfg['Servers'][$i]['AllowDeny']['order'], [1], [2] +
+ + +
$cfg['Servers'][$i]['AllowDeny']['rules'], [1], [2], [3], [4], [5] +
+ + +
$cfg['Servers'][$i]['AllowNoPassword'] +
+ + +
$cfg['Servers'][$i]['AllowRoot'], [1] +
+ + +
$cfg['Servers'][$i]['DisableIS'] +
+ + +
$cfg['Servers'][$i]['LogoutURL'] +
+ + +
$cfg['Servers'][$i]['MaxTableUiprefs'] +
+ + +
$cfg['Servers'][$i]['SessionTimeZone'] +
+ + +
$cfg['Servers'][$i]['SignonCookieParams'], [1], [2] +
+ + +
$cfg['Servers'][$i]['SignonScript'], [1], [2], [3], [4], [5] +
+ + +
$cfg['Servers'][$i]['SignonSession'], [1], [2] +
+ + +
$cfg['Servers'][$i]['SignonURL'], [1], [2] +
+ + +
$cfg['Servers'][$i]['auth_http_realm'] +
+ + +
$cfg['Servers'][$i]['auth_type'], [1], [2] +
+ + +
$cfg['Servers'][$i]['bookmarktable'], [1] +
+ + +
$cfg['Servers'][$i]['central_columns'], [1] +
+ + +
$cfg['Servers'][$i]['column_info'], [1], [2] +
+ + +
$cfg['Servers'][$i]['compress'] +
+ + +
$cfg['Servers'][$i]['connect_type'] +
+ + +
$cfg['Servers'][$i]['control_*'], [1], [2], [3] +
+ + +
$cfg['Servers'][$i]['controlhost'], [1] +
+ + +
$cfg['Servers'][$i]['controlpass'], [1], [2] +
+ + +
$cfg['Servers'][$i]['controlport'], [1] +
+ + +
$cfg['Servers'][$i]['controluser'], [1], [2], [3], [4] +
+ + +
$cfg['Servers'][$i]['designer_settings'], [1] +
+ + +
$cfg['Servers'][$i]['export_templates'], [1] +
+ + +
$cfg['Servers'][$i]['extension'] +
+ + +
$cfg['Servers'][$i]['favorite'], [1] +
+ + +
$cfg['Servers'][$i]['hide_db'] +
+ + +
$cfg['Servers'][$i]['history'], [1], [2] +
+ + +
$cfg['Servers'][$i]['host'], [1], [2], [3], [4], [5], [6], [7], [8], [9] +
+ + +
$cfg['Servers'][$i]['navigationhiding'], [1] +
+ + +
$cfg['Servers'][$i]['nopassword'] +
+ + +
$cfg['Servers'][$i]['only_db'] +
+ + +
$cfg['Servers'][$i]['password'], [1] +
+ + +
$cfg['Servers'][$i]['pdf_pages'], [1], [2] +
+ + +
$cfg['Servers'][$i]['pmadb'], [1], [2], [3], [4], [5], [6], [7], [8], [9], [10], [11], [12], [13], [14], [15], [16], [17], [18], [19], [20], [21], [22] +
+ + +
$cfg['Servers'][$i]['port'], [1] +
+ + +
$cfg['Servers'][$i]['recent'], [1] +
+ + +
$cfg['Servers'][$i]['relation'], [1] +
+ + +
$cfg['Servers'][$i]['savedsearches'], [1] +
+ + +
$cfg['Servers'][$i]['socket'], [1], [2] +
+ + +
$cfg['Servers'][$i]['ssl'], [1], [2], [3], [4], [5], [6], [7], [8], [9], [10] +
+ + +
$cfg['Servers'][$i]['ssl_ca'], [1], [2], [3], [4], [5], [6], [7], [8], [9], [10] +
+ + +
$cfg['Servers'][$i]['ssl_ca_path'], [1], [2], [3], [4], [5], [6], [7], [8], [9] +
+ + +
$cfg['Servers'][$i]['ssl_cert'], [1], [2], [3], [4], [5], [6], [7], [8], [9], [10] +
+ + +
$cfg['Servers'][$i]['ssl_ciphers'], [1], [2], [3], [4], [5], [6], [7], [8] +
+ + +
$cfg['Servers'][$i]['ssl_key'], [1], [2], [3], [4], [5], [6], [7], [8], [9], [10] +
+ + +
$cfg['Servers'][$i]['ssl_verify'], [1], [2], [3], [4], [5], [6], [7], [8], [9], [10], [11] +
+ + +
$cfg['Servers'][$i]['table_coords'], [1], [2] +
+ + +
$cfg['Servers'][$i]['table_info'], [1] +
+ + +
$cfg['Servers'][$i]['table_uiprefs'], [1], [2], [3], [4] +
+ + +
$cfg['Servers'][$i]['tracking'], [1] +
+ + +
$cfg['Servers'][$i]['tracking_add_drop_database'] +
+ + +
$cfg['Servers'][$i]['tracking_add_drop_table'] +
+ + +
$cfg['Servers'][$i]['tracking_add_drop_view'] +
+ + +
$cfg['Servers'][$i]['tracking_default_statements'] +
+ + +
$cfg['Servers'][$i]['tracking_version_auto_create'] +
+ + +
$cfg['Servers'][$i]['user'], [1] +
+ + +
$cfg['Servers'][$i]['userconfig'], [1] +
+ + +
$cfg['Servers'][$i]['usergroups'], [1], [2], [3] +
+ + +
$cfg['Servers'][$i]['users'], [1] +
+ + +
$cfg['Servers'][$i]['verbose'], [1], [2], [3], [4] +
+ + +
$cfg['SessionSavePath'], [1] +
+ + +
$cfg['ShowAll'] +
+ + +
$cfg['ShowBrowseComments'] +
+ + +
$cfg['ShowChgPassword'] +
+ + +
$cfg['ShowColumnComments'] +
+ + +
$cfg['ShowCreateDb'] +
+ + +
$cfg['ShowDatabasesNavigationAsTree'] +
+ + +
$cfg['ShowDbStructureCreation'] +
+ + +
$cfg['ShowDbStructureLastCheck'] +
+ + +
$cfg['ShowDbStructureLastUpdate'] +
+ + +
$cfg['ShowFieldTypesInDataEditView'] +
+ + +
$cfg['ShowFunctionFields'] +
+ + +
$cfg['ShowGitRevision'] +
+ + +
$cfg['ShowHint'] +
+ + +
$cfg['ShowPhpInfo'] +
+ + +
$cfg['ShowPropertyComments'] +
+ + +
$cfg['ShowSQL'] +
+ + +
$cfg['ShowServerInfo'] +
+ + +
$cfg['ShowStats'] +
+ + +
$cfg['SkipLockedTables'] +
+ + +
$cfg['SuhosinDisableWarning'], [1] +
+ + +
$cfg['TableNavigationLinksMode'] +
+ + +
$cfg['TablePrimaryKeyOrder'] +
+ + +
$cfg['TabsMode'] +
+ + +
$cfg['TempDir'], [1], [2], [3], [4] +
+ + +
$cfg['TextareaAutoSelect'] +
+ + +
$cfg['TextareaCols'] +
+ + +
$cfg['TextareaRows'] +
+ + +
$cfg['ThemeDefault'], [1] +
+ + +
$cfg['ThemeManager'], [1], [2] +
+ + +
$cfg['ThemePerServer'] +
+ + +
$cfg['TitleDatabase'] +
+ + +
$cfg['TitleDefault'] +
+ + +
$cfg['TitleServer'] +
+ + +
$cfg['TitleTable'] +
+ + +
$cfg['TranslationWarningThreshold'] +
+ + +
$cfg['TrustedProxies'], [1] +
+ + +
$cfg['UploadDir'], [1], [2], [3] +
+ + +
$cfg['UseDbSearch'] +
+ + +
$cfg['UserprefsDeveloperTab'] +
+ + +
$cfg['UserprefsDisallow'], [1] +
+ + +
$cfg['VersionCheck'] +
+ + +
$cfg['ZeroConf'], [1] +
+ + +
$cfg['ZipDump'] +
+ + +
$cfg['blowfish_secret'], [1] +
+ +
+
+ +
Configuration storage +
+ + +
Confirm +
+ + +
connect_type +
+ + +
Console, AlwaysExpand +
+ + +
Console, CurrentQuery +
+ + +
Console, DarkTheme +
+ + +
Console, EnterExecutes +
+ + +
Console, Height +
+ + +
Console, Mode +
+ + +
Console, StartHistory +
+ + +
ConsoleEnterExecutes +
+ + +
control_* +
+ + +
controlhost +
+ + +
controlpass +
+ + +
controlport +
+ + +
controluser +
+ + +
Cookie +
+ +
+ +
Authentication mode +
+ +
+ +
CSPAllow +
+ + +
CSV +
+ +
+ +

D

+ + + +
+ +
data (global variable or constant) +
+ + +
database +
+ +
+ +
(global variable or constant) +
+ +
+ +
DB +
+ + +
DBG +
+ + +
DBG, demo +
+ + +
DBG, simple2fa +
+ + +
DBG, sql +
+ + +
DBG, sqllog +
+ + +
DefaultConnectionCollation +
+ + +
DefaultForeignKeyChecks +
+ + +
DefaultFunctions +
+ + +
DefaultLang +
+ + +
DefaultQueryDatabase +
+ + +
DefaultQueryTable +
+ + +
DefaultTabDatabase +
+ +
+ +
DefaultTabServer +
+ + +
DefaultTabTable +
+ + +
DefaultTransformations +
+ + +
DefaultTransformations, Bool2Text +
+ + +
DefaultTransformations, DateFormat +
+ + +
DefaultTransformations, External +
+ + +
DefaultTransformations, Hex +
+ + +
DefaultTransformations, Inline +
+ + +
DefaultTransformations, PreApPend +
+ + +
DefaultTransformations, Substring +
+ + +
DefaultTransformations, TextImageLink +
+ + +
DefaultTransformations, TextLink +
+ + +
designer_settings +
+ + +
DisableIS +
+ + +
DisableMultiTableMaintenance +
+ + +
DisableShortcutKeys +
+ + +
DisplayServersList +
+ +
+ +

E

+ + + +
+ +
EnableAutocompleteForTablesAndColumns +
+ + +
Engine +
+ + +
+ environment variable +
+ +
+ +
PMA_ABSOLUTE_URI, [1] +
+ + +
PMA_ARBITRARY +
+ + +
PMA_HOST, [1] +
+ + +
PMA_HOSTS +
+ + +
PMA_PASSWORD, [1] +
+ + +
PMA_PORT, [1] +
+ + +
PMA_PORTS +
+ + +
PMA_USER, [1] +
+ + +
PMA_VERBOSE, [1] +
+ + +
PMA_VERBOSES +
+ +
+
+ +
ExecTimeLimit +
+ + +
Export +
+ + +
Export, charset +
+ + +
Export, file_template_database +
+ + +
Export, file_template_server +
+ + +
Export, file_template_table +
+ + +
Export, format +
+ + +
Export, method +
+ + +
export_templates +
+ + +
extension, [1] +
+ +
+ +

F

+ + + +
+ +
FAQ +
+ + +
favorite +
+ + +
Field +
+ + +
FilterLanguages +
+ + +
FirstLevelNavigationItems +
+ +
+ +
FontSize +
+ + +
ForceSSL +
+ + +
foreign key +
+ + +
ForeignKeyDropdownOrder +
+ + +
ForeignKeyMaxLimit +
+ +
+ +

G

+ + + +
+ +
GD +
+ + +
GD2 +
+ + +
GD2Available +
+ +
+ +
GridEditing +
+ + +
gzip +
+ + +
GZipDump +
+ +
+ +

H

+ + + +
+ +
hide_db +
+ + +
HideStructureActions +
+ + +
history +
+ + +
host, [1] +
+ +
+ +
hostname +
+ + +
HTTP +
+ +
+ +
Authentication mode +
+ +
+ +
https +
+ +
+ +

I

+ + + +
+ +
IconvExtraParams +
+ + +
IEC +
+ + +
IgnoreMultiSubmitErrors +
+ + +
IIS +
+ + +
Import +
+ + +
Import, charset +
+ + +
Index +
+ +
+ +
InitialSlidersState +
+ + +
InsertRows +
+ + +
IP +
+ + +
IP Address +
+ + +
IPv6 +
+ + +
ISAPI +
+ + +
ISO +
+ + +
ISP +
+ +
+ +

J

+ + + +
+ +
JPEG +
+ +
+ +
JPG +
+ +
+ +

K

+ + +
+ +
Key +
+ +
+ +

L

+ + + +
+ +
Lang +
+ + +
LATEX +
+ + +
LimitChars +
+ + +
LinkLengthLimit +
+ + +
LoginCookieDeleteAll +
+ +
+ +
LoginCookieRecall +
+ + +
LoginCookieStore +
+ + +
LoginCookieValidity +
+ + +
LoginCookieValidityDisableWarning +
+ + +
LogoutURL +
+ + +
LongtextDoubleTextarea +
+ +
+ +

M

+ + + +
+ +
Mac +
+ + +
Mac OS X +
+ + +
MaxCharactersInDisplayedSQL +
+ + +
MaxDbList +
+ + +
MaxExactCount +
+ + +
MaxExactCountViews +
+ + +
MaxNavigationItems +
+ + +
MaxRows +
+ + +
MaxSizeForInputField +
+ + +
MaxTableList +
+ + +
MaxTableUiprefs +
+ +
+ +
mbstring +
+ + +
MCrypt +
+ + +
mcrypt +
+ + +
MemoryLimit +
+ + +
MIME +
+ + +
MinSizeForInputField +
+ + +
mod_proxy_fcgi +
+ + +
module +
+ + +
MySQL +
+ + +
mysql +
+ + +
mysqli +
+ + +
MysqlMinVersion +
+ +
+ +

N

+ + + +
+ +
name (global variable or constant) +
+ + +
NaturalOrder +
+ + +
NavigationDisplayLogo +
+ + +
NavigationDisplayServers +
+ + +
navigationhiding +
+ + +
NavigationLinkWithMainPanel +
+ + +
NavigationLogoLink +
+ + +
NavigationLogoLinkWindow +
+ + +
NavigationTreeDbSeparator +
+ + +
NavigationTreeDefaultTabTable +
+ + +
NavigationTreeDefaultTabTable2 +
+ + +
NavigationTreeDisplayDbFilterMinimum +
+ + +
NavigationTreeDisplayItemFilterMinimum +
+ +
+ +
NavigationTreeEnableExpansion +
+ + +
NavigationTreeEnableGrouping +
+ + +
NavigationTreePointerEnable +
+ + +
NavigationTreeShowEvents +
+ + +
NavigationTreeShowFunctions +
+ + +
NavigationTreeShowProcedures +
+ + +
NavigationTreeShowTables +
+ + +
NavigationTreeShowViews +
+ + +
NavigationTreeTableLevel +
+ + +
NavigationTreeTableSeparator +
+ + +
NavigationWidth +
+ + +
nopassword +
+ + +
NumFavoriteTables +
+ + +
NumRecentTables +
+ +
+ +

O

+ + + +
+ +
OBGzip +
+ + +
only_db +
+ +
+ +
OpenDocument +
+ + +
Order +
+ + +
OS X +
+ +
+ +

P

+ + + +
+ +
password +
+ + +
PCRE +
+ + +
PDF +
+ + +
pdf_pages +
+ + +
PDFDefaultPageSize +
+ + +
PDFPageSizes +
+ + +
PEAR +
+ + +
PersistentConnections +
+ + +
PHP +
+ + +
phpMyAdmin configuration storage +
+ + +
PMA_ABSOLUTE_URI +
+ + +
PMA_HOST +
+ + +
PMA_PASSWORD +
+ +
+ +
PMA_PORT +
+ + +
PMA_USER +
+ + +
PMA_VERBOSE +
+ + +
PmaAbsoluteUri +
+ + +
pmadb, [1] +
+ + +
PmaNoRelation_DisableWarning +
+ + +
port, [1] +
+ + +
primary key +
+ + +
PropertiesNumColumns +
+ + +
ProtectBinary +
+ + +
ProxyPass +
+ + +
ProxyUrl +
+ + +
ProxyUser +
+ +
+ +

Q

+ + + +
+ +
QueryHistoryDB +
+ +
+ +
QueryHistoryMax +
+ +
+ +

R

+ + + +
+ +
recent +
+ + +
RecodingEngine +
+ + +
relation +
+ + +
RelationalDisplay +
+ + +
RememberSorting +
+ + +
RepeatCells +
+ + +
ReservedWordDisableWarning +
+ + +
RetainQueryBox +
+ +
+ +
RFC +
+ +
+ +
RFC 1867 +
+ + +
RFC 1952 +
+ + +
RFC 2616 +
+ +
+ +
RFC 1952 +
+ + +
Row (record, tuple) +
+ + +
RowActionLinks +
+ + +
RowActionLinksWithoutUnique +
+ + +
RowActionType +
+ +
+ +

S

+ + + +
+ +
SaveCellsAtOnce +
+ + +
SaveDir +
+ + +
savedsearches +
+ + +
SendErrorReports +
+ + +
Server +
+ + +
+ server configuration +
+ +
+ +
AllowDeny, order +
+ + +
AllowDeny, rules +
+ + +
AllowNoPassword +
+ + +
AllowRoot +
+ + +
DisableIS +
+ + +
LogoutURL +
+ + +
MaxTableUiprefs +
+ + +
SessionTimeZone +
+ + +
SignonCookieParams +
+ + +
SignonScript +
+ + +
SignonSession +
+ + +
SignonURL +
+ + +
auth_http_realm +
+ + +
auth_type +
+ + +
bookmarktable +
+ + +
central_columns +
+ + +
column_info +
+ + +
compress +
+ + +
connect_type +
+ + +
control_* +
+ + +
controlhost +
+ + +
controlpass +
+ + +
controlport +
+ + +
controluser +
+ + +
designer_settings +
+ + +
export_templates +
+ + +
extension +
+ + +
favorite +
+ + +
hide_db +
+ + +
history +
+ + +
host +
+ + +
navigationhiding +
+ + +
nopassword +
+ + +
only_db +
+ + +
password +
+ + +
pdf_pages +
+ + +
pmadb +
+ + +
port +
+ + +
recent +
+ + +
relation +
+ + +
savedsearches +
+ + +
socket +
+ + +
ssl +
+ + +
ssl_ca +
+ + +
ssl_ca_path +
+ + +
ssl_cert +
+ + +
ssl_ciphers +
+ + +
ssl_key +
+ + +
ssl_verify +
+ + +
table_coords +
+ + +
table_info +
+ + +
table_uiprefs +
+ + +
tracking +
+ + +
tracking_add_drop_database +
+ + +
tracking_add_drop_table +
+ + +
tracking_add_drop_view +
+ + +
tracking_default_statements +
+ + +
tracking_version_auto_create +
+ + +
user +
+ + +
userconfig +
+ + +
usergroups +
+ + +
users +
+ + +
verbose +
+ +
+
+ +
ServerDefault +
+ + +
ServerLibraryDifference_DisableWarning +
+ + +
Servers +
+ + +
SessionSavePath +
+ + +
SessionTimeZone +
+ + +
Setup script +
+ + +
ShowAll +
+ + +
ShowBrowseComments +
+ + +
ShowChgPassword +
+ + +
ShowColumnComments +
+ + +
ShowCreateDb +
+ + +
ShowDatabasesNavigationAsTree +
+ + +
ShowDbStructureCreation +
+ + +
ShowDbStructureLastCheck +
+ + +
ShowDbStructureLastUpdate +
+ + +
ShowFieldTypesInDataEditView +
+ + +
ShowFunctionFields +
+ + +
ShowGitRevision +
+ + +
ShowHint +
+ + +
ShowPhpInfo +
+ + +
ShowPropertyComments +
+ + +
ShowServerInfo +
+ + +
ShowSQL +
+ + +
ShowStats +
+ + +
+ Signon +
+ +
+ +
Authentication mode +
+ +
+ +
SignonCookieParams +
+ + +
SignonScript +
+ + +
SignonSession +
+ + +
SignonURL +
+ + +
SkipLockedTables +
+ + +
socket, [1] +
+ + +
SQL +
+ + +
SQLQuery, Edit +
+ + +
SQLQuery, Explain +
+ + +
SQLQuery, Refresh +
+ + +
SQLQuery, ShowAsPHP +
+ + +
SSL +
+ + +
ssl +
+ + +
ssl_ca +
+ + +
ssl_ca_path +
+ + +
ssl_cert +
+ + +
ssl_ciphers +
+ + +
ssl_key +
+ + +
ssl_verify +
+ + +
Storage Engines +
+ + +
Stored procedure +
+ + +
SuhosinDisableWarning +
+ +
+ +

T

+ + + +
+ +
table +
+ + +
table_coords +
+ + +
table_info +
+ + +
table_uiprefs +
+ + +
TableNavigationLinksMode +
+ + +
TablePrimaryKeyOrder +
+ + +
TabsMode +
+ + +
tar +
+ + +
TCP +
+ + +
TCPDF +
+ + +
TempDir +
+ + +
TextareaAutoSelect +
+ + +
TextareaCols +
+ + +
TextareaRows +
+ + +
ThemeDefault +
+ +
+ +
ThemeManager +
+ + +
ThemePerServer +
+ + +
TitleDatabase +
+ + +
TitleDefault +
+ + +
TitleServer +
+ + +
TitleTable +
+ + +
tracking +
+ + +
tracking_add_drop_database +
+ + +
tracking_add_drop_table +
+ + +
tracking_add_drop_view +
+ + +
tracking_default_statements +
+ + +
tracking_version_auto_create +
+ + +
TranslationWarningThreshold +
+ + +
trigger +
+ + +
TrustedProxies +
+ + +
type (global variable or constant) +
+ +
+ +

U

+ + + +
+ +
unique key +
+ + +
UploadDir +
+ + +
URL +
+ + +
UseDbSearch +
+ + +
user +
+ +
+ +
userconfig +
+ + +
usergroups +
+ + +
UserprefsDeveloperTab +
+ + +
UserprefsDisallow +
+ + +
users +
+ +
+ +

V

+ + + +
+ +
verbose +
+ +
+ +
version (global variable or constant) +
+ + +
VersionCheck +
+ +
+ +

W

+ + +
+ +
Webserver +
+ +
+ +

X

+ + +
+ +
XML +
+ +
+ +

Z

+ + + +
+ +
ZeroConf +
+ + +
ZIP +
+ +
+ +
ZipDump +
+ + +
zlib +
+ +
+ + + +
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/php/apps/phpmyadmin49/html/doc/html/glossary.html b/php/apps/phpmyadmin49/html/doc/html/glossary.html new file mode 100644 index 00000000..ac7b50a5 --- /dev/null +++ b/php/apps/phpmyadmin49/html/doc/html/glossary.html @@ -0,0 +1,638 @@ + + + + + + + + Glossary — phpMyAdmin 4.9.1 documentation + + + + + + + + + + + + + + + + + +
+
+
+
+ +
+

Glossary

+

From Wikipedia, the free encyclopedia

+
+
.htaccess
+

the default name of Apache’s directory-level configuration file.

+ +
+
ACL
+
Access Contol List
+
Blowfish
+

a keyed, symmetric block cipher, designed in 1993 by Bruce Schneier.

+ +
+
Browser
+

a software application that enables a user to display and interact with text, images, and other information typically located on a web page at a website on the World Wide Web.

+ +
+
bzip2
+

a free software/open source data compression algorithm and program developed by Julian Seward.

+ +
+
CGI
+

Common Gateway Interface is an important World Wide Web technology that +enables a client web browser to request data from a program executed on +the Web server.

+ +
+
Changelog
+

a log or record of changes made to a project.

+ +
+
Client
+

a computer system that accesses a (remote) service on another computer by some kind of network.

+ +
+
column
+

a set of data values of a particular simple type, one for each row of the table.

+ +
+ +

a packet of information sent by a server to a World Wide Web browser and then sent back by the browser each time it accesses that server.

+ +
+
CSV
+

Comma- separated values

+ +
+
DB
+
look at database
+
database
+

an organized collection of data.

+ +
+
Engine
+
look at storage engines
+
extension
+

a PHP module that extends PHP with additional functionality.

+ +
+
FAQ
+

Frequently Asked Questions is a list of commonly asked question and there +answers.

+ +
+
Field
+

one part of divided data/columns.

+ +
+
foreign key
+

a column or group of columns in a database row that point to a key column +or group of columns forming a key of another database row in some +(usually different) table.

+ +
+
GD
+

Graphics Library by Thomas Boutell and others for dynamically manipulating images.

+ +
+
GD2
+
look at gd
+
gzip
+

gzip is short for GNU zip, a GNU free software file compression program.

+ +
+
host
+

any machine connected to a computer network, a node that has a hostname.

+ +
+
hostname
+

the unique name by which a network attached device is known on a network.

+ +
+
HTTP
+

HyperText Transfer Protocol is the primary method used to transfer or +convey information on the World Wide Web.

+ +
+
https
+

a HTTP-connection with additional security measures.

+ +
+
IEC
+
International Electrotechnical Commission
+
IIS
+

Internet Information Services is a set of Internet-based services for +servers using Microsoft Windows.

+ +
+
Index
+

a feature that allows quick access to the rows in a table.

+ +
+
IP
+

Internet Protocol is a data-oriented protocol used by source and +destination hosts for communicating data across a packet-switched +internetwork.

+ +
+
IP Address
+

a unique number that devices use in order to identify and communicate with each other on a network utilizing the Internet Protocol standard.

+ +
+
IPv6
+

IPv6 (Internet Protocol version 6) is the latest revision of the +Internet Protocol (IP), designed to deal with the +long-anticipated problem of its precedessor IPv4 running out of addresses.

+ +
+
ISAPI
+

Internet Server Application Programming Interface is the API of Internet Information Services (IIS).

+ +
+
ISP
+

Internet service provider is a business or organization that offers users +access to the Internet and related services.

+ +
+
ISO
+
International Standards Organisation
+
JPEG
+

a most commonly used standard method of lossy compression for photographic images.

+ +
+
JPG
+
look at jpeg
+
Key
+
look at index
+
LATEX
+

a document preparation system for the TEX typesetting program.

+ +
+
Mac
+

Apple Macintosh is line of personal computers is designed, developed, manufactured, and marketed by Apple Computer.

+

. seealso:: <https://en.wikipedia.org/wiki/Mac>

+
+
Mac OS X
+

the operating system which is included with all currently shipping Apple Macintosh computers in the consumer and professional markets.

+ +
+
mbstring
+

The PHP mbstring functions provide support for languages represented by multi-byte character sets, most notably UTF-8.

+

If you have troubles installing this extension, please follow 1.20 I receive an error about missing mysqli and mysql extensions., it provides useful hints.

+ +
+
MCrypt
+

a cryptographic library.

+ +
+
mcrypt
+

the MCrypt PHP extension.

+ +
+
MIME
+

Multipurpose Internet Mail Extensions is +an Internet Standard for the format of e-mail.

+ +
+
module
+

some sort of extension for the Apache Webserver.

+ +
+
mod_proxy_fcgi
+
an Apache module implmenting a Fast CGI interface; PHP can be run as a CGI module, FastCGI, or +directly as an Apache module.
+
MySQL
+

a multithreaded, multi-user, SQL (Structured Query Language) Database Management System (DBMS).

+ +
+
mysqli
+

the improved MySQL client PHP extension.

+ +
+
mysql
+

the MySQL client PHP extension.

+ +
+
OpenDocument
+

open standard for office documents.

+ +
+
OS X
+

look at Mac OS X.

+ +
+
PDF
+

Portable Document Format is a file format developed by Adobe Systems for +representing two dimensional documents in a device independent and +resolution independent format.

+ +
+
PEAR
+

the PHP Extension and Application Repository.

+
+

See also

+

<https://pear.php.net/>

+
+
+
PCRE
+

Perl Compatible Regular Expressions is the perl-compatible regular +expression functions for PHP

+ +
+
PHP
+

short for “PHP: Hypertext Preprocessor”, is an open-source, reflective +programming language used mainly for developing server-side applications +and dynamic web content, and more recently, a broader range of software +applications.

+ +
+
port
+

a connection through which data is sent and received.

+ +
+
primary key
+

A primary key is an index over one or more fields in a table with +unique values for each single row in this table. Every table should have +a primary key for easier accessing/identifying data in this table. There +can only be one primary key per table and it is named always PRIMARY. +In fact a primary key is just an unique key with the name +PRIMARY. If no primary key is defined MySQL will use first unique +key as primary key if there is one.

+

You can create the primary key when creating the table (in phpMyAdmin +just check the primary key radio buttons for each field you wish to be +part of the primary key).

+

You can also add a primary key to an existing table with ALTER TABLE +or CREATE INDEX (in phpMyAdmin you can just click on ‘add index’ on +the table structure page below the listed fields).

+
+
RFC
+

Request for Comments (RFC) documents are a series of memoranda +encompassing new research, innovations, and methodologies applicable to +Internet technologies.

+ +
+
RFC 1952
+

GZIP file format specification version 4.3

+
+

See also

+

RFC 1952

+
+
+
Row (record, tuple)
+

represents a single, implicitly structured data item in a table.

+ +
+
Server
+

a computer system that provides services to other computing systems over a network.

+ +
+
Storage Engines
+

MySQL can use several different formats for storing data on disk, these +are called storage engines or table types. phpMyAdmin allows a user to +change their storage engine for a particular table through the operations +tab.

+

Common table types are InnoDB and MyISAM, though many others exist and +may be desirable in some situations.

+ +
+
socket
+

a form of inter-process communication.

+ +
+
SSL
+

Secure Sockets Layer is a cryptographic protocol which provides secure +communication on the Internet.

+ +
+
Stored procedure
+

a subroutine available to applications accessing a relational database system

+ +
+
SQL
+

Structured Query Language

+ +
+
table
+

a set of data elements (cells) that is organized, defined and stored as +horizontal rows and vertical columns where each item can be uniquely +identified by a label or key or by it?s position in relation to other +items.

+ +
+
tar
+

a type of archive file format: the Tape ARchive format.

+ +
+
TCP
+

Transmission Control Protocol is one of the core protocols of the +Internet protocol suite.

+ +
+
TCPDF
+

PHP library to generate PDF files.

+
+

See also

+

<https://tcpdf.org/>

+
+
+
trigger
+

a procedural code that is automatically executed in response to certain events on a particular table or view in a database

+ +
+
unique key
+
An unique key is an index over one or more fields in a table which has a +unique value for each row. The first unique key will be treated as +primary key if there is no primary key defined.
+
URL
+

Uniform Resource Locator is a sequence of characters, conforming to a +standardized format, that is used for referring to resources, such as +documents and images on the Internet, by their location.

+ +
+
Webserver
+

A computer (program) that is responsible for accepting HTTP requests from clients and serving them Web pages.

+ +
+
XML
+

Extensible Markup Language is a W3C-recommended general- purpose markup +language for creating special-purpose markup languages, capable of +describing many different kinds of data.

+ +
+
ZIP
+

a popular data compression and archival format.

+ +
+
zlib
+

an open-source, cross- platform data compression library by Jean-loup Gailly and Mark Adler.

+ +
+
+
+ + +
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/php/apps/phpmyadmin49/html/doc/html/import_export.html b/php/apps/phpmyadmin49/html/doc/html/import_export.html new file mode 100644 index 00000000..d02be8b4 --- /dev/null +++ b/php/apps/phpmyadmin49/html/doc/html/import_export.html @@ -0,0 +1,470 @@ + + + + + + + + Import and export — phpMyAdmin 4.9.1 documentation + + + + + + + + + + + + + + + + + + + +
+
+
+
+ +
+

Import and export

+
+

Import

+

To import data, go to the “Import” tab in phpMyAdmin. To import data into a +specific database or table, open the database or table before going to the +“Import” tab.

+

In addition to the standard Import and Export tab, you can also import an SQL +file directly by dragging and dropping it from your local file manager to the +phpMyAdmin interface in your web browser.

+

If you are having troubles importing big files, please consult 1.16 I cannot upload big dump files (memory, HTTP or timeout problems)..

+

You can import using following methods:

+

Form based upload

+
+
Can be used with any supported format, also (b|g)zipped files, e.g., mydump.sql.gz .
+

Form based SQL Query

+
+
Can be used with valid SQL dumps.
+

Using upload directory

+
+
You can specify an upload directory on your web server where phpMyAdmin is installed, after uploading your file into this directory you can select this file in the import dialog of phpMyAdmin, see $cfg['UploadDir'].
+

phpMyAdmin can import from several various commonly used formats.

+
+

CSV

+

Comma separated values format which is often used by spreadsheets or various other programs for export/import.

+
+

Note

+

When importing data into a table from a CSV file where the table has an +‘auto_increment’ field, make the ‘auto_increment’ value for each record in +the CSV field to be ‘0’ (zero). This allows the ‘auto_increment’ field to +populate correctly.

+
+

It is now possible to import a CSV file at the server or database level. +Instead of having to create a table to import the CSV file into, a best-fit +structure will be determined for you and the data imported into it, instead. +All other features, requirements, and limitations are as before.

+
+
+

CSV using LOAD DATA

+

Similar to CSV, only using the internal MySQL parser and not the phpMyAdmin one.

+
+
+

ESRI Shape File

+

The ESRI shapefile or simply a shapefile is a popular geospatial vector data +format for geographic information systems software. It is developed and +regulated by Esri as a (mostly) open specification for data interoperability +among Esri and other software products.

+
+
+

MediaWiki

+

MediaWiki files, which can be exported by phpMyAdmin (version 4.0 or later), +can now also be imported. This is the format used by Wikipedia to display +tables.

+
+
+

Open Document Spreadsheet (ODS)

+

OpenDocument workbooks containing one or more spreadsheets can now be directly imported.

+

When importing an ODS speadsheet, the spreadsheet must be named in a specific way in order to make the +import as simple as possible.

+
+

Table name

+

During import, phpMyAdmin uses the sheet name as the table name; you should rename the +sheet in your spreadsheet program in order to match your existing table name (or the table you wish to create, +though this is less of a concern since you could quickly rename the new table from the Operations tab).

+
+
+

Column names

+

You should also make the first row of your spreadsheet a header with the names of the columns (this can be +accomplished by inserting a new row at the top of your spreadsheet). When on the Import screen, select the +checkbox for “The first line of the file contains the table column names;” this way your newly imported +data will go to the proper columns.

+
+

Note

+

Formulas and calculations will NOT be evaluated, rather, their value from +the most recent save will be loaded. Please ensure that all values in the +spreadsheet are as needed before importing it.

+
+
+
+
+

SQL

+

SQL can be used to make any manipulation on data, it is also useful for restoring backed up data.

+
+
+

XML

+

XML files exported by phpMyAdmin (version 3.3.0 or later) can now be imported. +Structures (databases, tables, views, triggers, etc.) and/or data will be +created depending on the contents of the file.

+

The supported xml schemas are not yet documented in this wiki.

+
+
+
+

Export

+

phpMyAdmin can export into text files (even compressed) on your local disk (or +a special the webserver $cfg['SaveDir'] folder) in various +commonly used formats:

+
+

CodeGen

+

NHibernate file format. Planned +versions: Java, Hibernate, PHP PDO, JSON, etc. So the preliminary name is +codegen.

+
+
+

CSV

+

Comma separated values format which is often used by spreadsheets or various +other programs for export/import.

+
+
+

CSV for Microsoft Excel

+

This is just preconfigured version of CSV export which can be imported into +most English versions of Microsoft Excel. Some localised versions (like +“Danish”) are expecting ”;” instead of ”,” as field separator.

+
+
+

Microsoft Word 2000

+

If you’re using Microsoft Word 2000 or newer (or compatible such as +OpenOffice.org), you can use this export.

+
+
+

JSON

+

JSON (JavaScript Object Notation) is a lightweight data-interchange format. It +is easy for humans to read and write and it is easy for machines to parse and +generate.

+
+

Changed in version 4.7.0: The generated JSON structure has been changed in phpMyAdmin 4.7.0 to +produce valid JSON data.

+
+

The generated JSON is list of objects with following attributes:

+
+
+type
+

Type of given object, can be one of:

+
+
header
+
Export header containing comment and phpMyAdmin version.
+
database
+
Start of a database marker, containing name of database.
+
table
+
Table data export.
+
+
+ +
+
+version
+

Used in header type and indicates phpMyAdmin version.

+
+ +
+
+comment
+

Optional textual comment.

+
+ +
+
+name
+

Object name - either table or database based on type.

+
+ +
+
+database
+

Database name for table type.

+
+ +
+
+data
+

Table content for table type.

+
+ +

Sample output:

+
[
+    {
+        "comment": "Export to JSON plugin for PHPMyAdmin",
+        "type": "header",
+        "version": "4.7.0-dev"
+    },
+    {
+        "name": "cars",
+        "type": "database"
+    },
+    {
+        "data": [
+            {
+                "car_id": "1",
+                "description": "Green Chrysler 300",
+                "make_id": "5",
+                "mileage": "113688",
+                "price": "13545.00",
+                "transmission": "automatic",
+                "yearmade": "2007"
+            }
+        ],
+        "database": "cars",
+        "name": "cars",
+        "type": "table"
+    },
+    {
+        "data": [
+            {
+                "make": "Chrysler",
+                "make_id": "5"
+            }
+        ],
+        "database": "cars",
+        "name": "makes",
+        "type": "table"
+    }
+]
+
+
+
+
+

LaTeX

+

If you want to embed table data or structure in LaTeX, this is right choice for you.

+

LaTeX is a typesetting system that is very suitable for producing scientific +and mathematical documents of high typographical quality. It is also suitable +for producing all sorts of other documents, from simple letters to complete +books. LaTeX uses TeX as its formatting engine. Learn more about TeX and +LaTeX on the Comprehensive TeX Archive Network +also see the short description od TeX.

+

The output needs to be embedded into a LaTeX document before it can be +rendered, for example in following document:

+
\documentclass{article}
+\title{phpMyAdmin SQL output}
+\author{}
+\usepackage{longtable,lscape}
+\date{}
+\setlength{\parindent}{0pt}
+\usepackage[left=2cm,top=2cm,right=2cm,nohead,nofoot]{geometry}
+\pdfpagewidth 210mm
+\pdfpageheight 297mm
+\begin{document}
+\maketitle
+
+% insert phpMyAdmin LaTeX Dump here
+
+\end{document}
+
+
+
+
+

MediaWiki

+

Both tables and databases can be exported in the MediaWiki format, which is +used by Wikipedia to display tables. It can export structure, data or both, +including table names or headers.

+
+
+

OpenDocument Spreadsheet

+

Open standard for spreadsheet data, which is being widely adopted. Many recent +spreadsheet programs, such as LibreOffice, OpenOffice, Microsoft Office or +Google Docs can handle this format.

+
+
+

OpenDocument Text

+

New standard for text data which is being widely addopted. Most recent word +processors (such as LibreOffice, OpenOffice, Microsoft Word, AbiWord or KWord) +can handle this.

+
+
+

PDF

+

For presentation purposes, non editable PDF might be best choice for you.

+
+
+

PHP Array

+

You can generate a php file which will declare a multidimensional array with +the contents of the selected table or database.

+
+
+

SQL

+

Export in SQL can be used to restore your database, thus it is useful for +backing up.

+

The option ‘Maximal length of created query’ seems to be undocumented. But +experiments has shown that it splits large extended INSERTS so each one is no +bigger than the given number of bytes (or characters?). Thus when importing the +file, for large tables you avoid the error “Got a packet bigger than +‘max_allowed_packet’ bytes”.

+ +
+

Data Options

+

Complete inserts adds the column names to the SQL dump. This parameter +improves the readability and reliability of the dump. Adding the column names +increases the size of the dump, but when combined with Extended inserts it’s +negligible.

+

Extended inserts combines multiple rows of data into a single INSERT query. +This will significantly decrease filesize for large SQL dumps, increases the +INSERT speed when imported, and is generally recommended.

+ +
+
+
+

Texy!

+

Texy! markup format. You can see example on Texy! demo.

+
+
+

XML

+

Easily parsable export for use with custom scripts.

+
+

Changed in version 3.3.0: The XML schema used has changed as of version 3.3.0

+
+
+
+

YAML

+

YAML is a data serialization format which is both human readable and +computationally powerful ( <https://yaml.org> ).

+
+
+
+ + +
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/php/apps/phpmyadmin49/html/doc/html/index.html b/php/apps/phpmyadmin49/html/doc/html/index.html new file mode 100644 index 00000000..bcc94084 --- /dev/null +++ b/php/apps/phpmyadmin49/html/doc/html/index.html @@ -0,0 +1,228 @@ + + + + + + + + Welcome to phpMyAdmin’s documentation! — phpMyAdmin 4.9.1 documentation + + + + + + + + + + + + + + + + + +
+
+
+
+ +
+

Welcome to phpMyAdmin’s documentation!

+

Contents:

+
+ +
+
+
+

Indices and tables

+ +
+ + +
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/php/apps/phpmyadmin49/html/doc/html/intro.html b/php/apps/phpmyadmin49/html/doc/html/intro.html new file mode 100644 index 00000000..5ef2faba --- /dev/null +++ b/php/apps/phpmyadmin49/html/doc/html/intro.html @@ -0,0 +1,195 @@ + + + + + + + + Introduction — phpMyAdmin 4.9.1 documentation + + + + + + + + + + + + + + + + + + +
+
+
+
+ +
+

Introduction

+

phpMyAdmin is a free software tool written in PHP that is intended to handle the +administration of a MySQL or MariaDB database server. You can use phpMyAdmin to +perform most administration tasks, including creating a database, running queries, +and adding user accounts.

+
+

Supported features

+

Currently phpMyAdmin can:

+
    +
  • create, browse, edit, and drop databases, tables, views, columns, and indexes
  • +
  • display multiple results sets through stored procedures or queries
  • +
  • create, copy, drop, rename and alter databases, tables, columns and +indexes
  • +
  • maintenance server, databases and tables, with proposals on server +configuration
  • +
  • execute, edit and bookmark any SQL-statement, even batch-queries
  • +
  • load text files into tables
  • +
  • create [1] and read dumps of tables
  • +
  • export [1] data to various formats: CSV, XML, PDF, +ISO/IEC 26300 - OpenDocument Text and Spreadsheet, Microsoft +Word 2000, and LATEX formats
  • +
  • import data and MySQL structures from OpenDocument spreadsheets, as +well as XML, CSV, and SQL files
  • +
  • administer multiple servers
  • +
  • add, edit, and remove MySQL user accounts and privileges
  • +
  • check referential integrity in MyISAM tables
  • +
  • using Query-by-example (QBE), create complex queries automatically +connecting required tables
  • +
  • create PDF graphics of your +database layout
  • +
  • search globally in a database or a subset of it
  • +
  • transform stored data into any format using a set of predefined +functions, like displaying BLOB-data as image or download-link
  • +
  • track changes on databases, tables and views
  • +
  • support InnoDB tables and foreign keys
  • +
  • support mysqli, the improved MySQL extension see 1.17 Which Database versions does phpMyAdmin support?
  • +
  • create, edit, call, export and drop stored procedures and functions
  • +
  • create, edit, export and drop events and triggers
  • +
  • communicate in 80 different languages
  • +
+
+
+

Shortcut keys

+

Currently phpMyAdmin supports following shortcuts:

+
    +
  • k - Toggle console
  • +
  • h - Go to home page
  • +
  • s - Open settings
  • +
  • d + s - Go to database structure (Provided you are in database related page)
  • +
  • d + f - Search database (Provided you are in database related page)
  • +
  • t + s - Go to table structure (Provided you are in table related page)
  • +
  • t + f - Search table (Provided you are in table related page)
  • +
  • backspace - Takes you to older page.
  • +
+
+
+

A word about users

+

Many people have difficulty understanding the concept of user +management with regards to phpMyAdmin. When a user logs in to +phpMyAdmin, that username and password are passed directly to MySQL. +phpMyAdmin does no account management on its own (other than allowing +one to manipulate the MySQL user account information); all users must +be valid MySQL users.

+

Footnotes

+ + + + + +
[1](1, 2) phpMyAdmin can compress (Zip, GZip or RFC 1952 +formats) dumps and CSV exports if you use PHP with +Zlib support (--with-zlib). +Proper support may also need changes in php.ini.
+
+
+ + +
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/php/apps/phpmyadmin49/html/doc/html/other.html b/php/apps/phpmyadmin49/html/doc/html/other.html new file mode 100644 index 00000000..6f06a7f6 --- /dev/null +++ b/php/apps/phpmyadmin49/html/doc/html/other.html @@ -0,0 +1,158 @@ + + + + + + + + Other sources of information — phpMyAdmin 4.9.1 documentation + + + + + + + + + + + + + + + + + + + +
+
+
+
+ +
+

Other sources of information

+
+

Printed Book

+

The definitive guide to using phpMyAdmin is the book Mastering phpMyAdmin for +Effective MySQL Management by Marc Delisle. You can get information on that +book and other officially endorsed books at the phpMyAdmin site.

+
+
+

Tutorials

+

Third party tutorials and articles which you might find interesting:

+
+

Česky (Czech)

+ +
+ +
+

Русский (Russian)

+ +
+
+
+ + +
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/php/apps/phpmyadmin49/html/doc/html/privileges.html b/php/apps/phpmyadmin49/html/doc/html/privileges.html new file mode 100644 index 00000000..49328a53 --- /dev/null +++ b/php/apps/phpmyadmin49/html/doc/html/privileges.html @@ -0,0 +1,188 @@ + + + + + + + + User management — phpMyAdmin 4.9.1 documentation + + + + + + + + + + + + + + + + + + + +
+
+
+
+ +
+

User management

+

User management is the process of controlling which users are allowed to +connect to the MySQL server and what permissions they have on each database. +phpMyAdmin does not handle user management, rather it passes the username and +password on to MySQL, which then determines whether a user is permitted to +perform a particular action. Within phpMyAdmin, administrators have full +control over creating users, viewing and editing privileges for existing users, +and removing users.

+

Within phpMyAdmin, user management is controlled via the Users link +from the main page. Users can be created, edited, and removed.

+
+

Creating a new user

+

To create a new user, click the Add a new user link near the bottom +of the Users page (you must be a “superuser”, e.g., user “root”). +Use the textboxes and drop-downs to configure the user to your particular +needs. You can then select whether to create a database for that user and grant +specific global privileges. Once you’ve created the user (by clicking Go), you +can define that user’s permissions on a specific database (don’t grant global +privileges in that case). In general, users do not need any global privileges +(other than USAGE), only permissions for their specific database.

+
+
+

Editing an existing user

+

To edit an existing user, simply click the pencil icon to the right of that +user in the Users page. You can then edit their global- and +database-specific privileges, change their password, or even copy those +privileges to a new user.

+
+
+

Deleting a user

+

From the Users page, check the checkbox for the user you wish to +remove, select whether or not to also remove any databases of the same name (if +they exist), and click Go.

+
+
+

Assigning privileges to user for a specific database

+

Users are assigned to databases by editing the user record (from the +User accounts link on the home page). +If you are creating a user specifically for a given table +you will have to create the user first (with no global privileges) and then go +back and edit that user to add the table and privileges for the individual +table.

+
+
+

Configurable menus and user groups

+

By enabling $cfg['Servers'][$i]['usergroups'] and +$cfg['Servers'][$i]['usergroups'] you can customize what users +will see in the phpMyAdmin navigation.

+
+

Warning

+

This feature only limits what a user sees, he is still able to use all the +functions. So this can not be considered as a security limitation. Should +you want to limit what users can do, use MySQL privileges to achieve that.

+
+

With this feature enabled, the User accounts management interface gains +a second tab for managing User groups, where you can define what each +group will view (see image below) and you can then assign each user to one of +these groups. Users will be presented with a simplified user interface, which might be +useful for inexperienced users who could be overwhelmed by all the features +phpMyAdmin provides.

+_images/usergroups.png +
+
+ + +
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/php/apps/phpmyadmin49/html/doc/html/relations.html b/php/apps/phpmyadmin49/html/doc/html/relations.html new file mode 100644 index 00000000..8957923e --- /dev/null +++ b/php/apps/phpmyadmin49/html/doc/html/relations.html @@ -0,0 +1,194 @@ + + + + + + + + Relations — phpMyAdmin 4.9.1 documentation + + + + + + + + + + + + + + + + + + + +
+
+
+
+ +
+

Relations

+

phpMyAdmin allows relationships (similar to foreign keys) using MySQL-native +(InnoDB) methods when available and falling back on special phpMyAdmin-only +features when needed. There are two ways of editing these relations, with the +relation view and the drag-and-drop designer – both of which are explained +on this page.

+
+

Note

+

You need to have configured the phpMyAdmin configuration storage for using phpMyAdmin +only relations.

+
+
+

Technical info

+

Currently the only MySQL table type that natively supports relationships is +InnoDB. When using an InnoDB table, phpMyAdmin will create real InnoDB +relations which will be enforced by MySQL no matter which application accesses +the database. In the case of any other table type, phpMyAdmin enforces the +relations internally and those relations are not applied to any other +application.

+
+
+

Relation view

+

In order to get it working, you first have to properly create the +[[pmadb|pmadb]]. Once that is setup, select a table’s “Structure” page. Below +the table definition, a link called “Relation view” is shown. If you click that +link, a page will be shown that offers you to create a link to another table +for any (most) fields. Only PRIMARY KEYS are shown there, so if the field you +are referring to is not shown, you most likely are doing something wrong. The +drop-down at the bottom is the field which will be used as the name for a +record.

+
+

Relation view example

+_images/pma-relations-relation-view-link.png +_images/pma-relations-relation-link.png +

Let’s say you have categories and links and one category can contain several links. Your table structure would be something like this:

+
    +
  • category.category_id (must be unique)
  • +
  • category.name
  • +
  • link.link_id
  • +
  • link.category_id
  • +
  • link.uri.
  • +
+

Open the relation view (below the table structure) page for the link table and for category_id field, you select category.category_id as master record.

+

If you now browse the link table, the category_id field will be a clickable hyperlink to the proper category record. But all you see is just the category_id, not the name of the category.

+_images/pma-relations-relation-name.png +

To fix this, open the relation view of the category table and in the drop down at the bottom, select “name”. If you now browse the link table again and hover the mouse over the category_id hyperlink, the value from the related category will be shown as tooltip.

+_images/pma-relations-links.png +
+
+
+

Designer

+

The Designer feature is a graphical way of creating, editing, and displaying +phpMyAdmin relations. These relations are compatible with those created in +phpMyAdmin’s relation view.

+

To use this feature, you need a properly configured phpMyAdmin configuration storage and +must have the $cfg['Servers'][$i]['table_coords'] configured.

+

To use the designer, select a database’s structure page, then look for the +Designer tab.

+

To export the view into PDF, you have to create PDF pages first. The Designer +creates the layout, how the tables shall be displayed. To finally export the +view, you have to create this with a PDF page and select your layout, which you +have created with the designer.

+ +
+
+ + +
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/php/apps/phpmyadmin49/html/doc/html/require.html b/php/apps/phpmyadmin49/html/doc/html/require.html new file mode 100644 index 00000000..5acc6719 --- /dev/null +++ b/php/apps/phpmyadmin49/html/doc/html/require.html @@ -0,0 +1,172 @@ + + + + + + + + Requirements — phpMyAdmin 4.9.1 documentation + + + + + + + + + + + + + + + + + + +
+
+
+
+ +
+

Requirements

+
+

Web server

+

Since phpMyAdmin’s interface is based entirely in your browser, you’ll need a +web server (such as Apache, nginx, IIS) to install phpMyAdmin’s files into.

+
+
+

PHP

+
    +
  • You need PHP 5.5.0 or newer, with session support, the Standard PHP Library +(SPL) extension, hash, ctype, and JSON support.
  • +
  • The mbstring extension (see mbstring) is strongly recommended +for performance reasons.
  • +
  • To support uploading of ZIP files, you need the PHP zip extension.
  • +
  • You need GD2 support in PHP to display inline thumbnails of JPEGs +(“image/jpeg: inline”) with their original aspect ratio.
  • +
  • When using the cookie authentication (the default), the openssl extension is strongly suggested.
  • +
  • To support upload progress bars, see 2.9 Seeing an upload progress bar.
  • +
  • To support XML and Open Document Spreadsheet importing, you need the +libxml extension.
  • +
  • To support reCAPTCHA on the login page, you need the +openssl extension.
  • +
  • To support displaying phpMyAdmin’s latest version, you need to enable +allow_url_open in your php.ini or to have the +curl extension.
  • +
+ +
+
+

Database

+

phpMyAdmin supports MySQL-compatible databases.

+
    +
  • MySQL 5.5 or newer
  • +
  • MariaDB 5.5 or newer
  • +
+ +
+
+

Web browser

+

To access phpMyAdmin you need a web browser with cookies and JavaScript +enabled.

+

You need browser which is supported by jQuery 2.0, see +<https://jquery.com/browser-support/>.

+
+
+ + +
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/php/apps/phpmyadmin49/html/doc/html/search.html b/php/apps/phpmyadmin49/html/doc/html/search.html new file mode 100644 index 00000000..45d583ba --- /dev/null +++ b/php/apps/phpmyadmin49/html/doc/html/search.html @@ -0,0 +1,102 @@ + + + + + + + + Search — phpMyAdmin 4.9.1 documentation + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+ +

Search

+
+ +

+ Please activate JavaScript to enable the search + functionality. +

+
+

+ From here you can search these documents. Enter your search + words into the box below and click "search". Note that the search + function will automatically search for all of the words. Pages + containing fewer words won't appear in the result list. +

+
+ + + +
+ +
+ +
+ +
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/php/apps/phpmyadmin49/html/doc/html/searchindex.js b/php/apps/phpmyadmin49/html/doc/html/searchindex.js new file mode 100644 index 00000000..441c0383 --- /dev/null +++ b/php/apps/phpmyadmin49/html/doc/html/searchindex.js @@ -0,0 +1 @@ +Search.setIndex({envversion:49,filenames:["bookmarks","charts","config","copyright","credits","developers","faq","glossary","import_export","index","intro","other","privileges","relations","require","security","settings","setup","themes","transformations","two_factor","user","vendors"],objects:{"":{"$cfg['ActionLinksMode']":[2,0,1,"cfg_ActionLinksMode"],"$cfg['AllowArbitraryServer']":[2,0,1,"cfg_AllowArbitraryServer"],"$cfg['AllowThirdPartyFraming']":[2,0,1,"cfg_AllowThirdPartyFraming"],"$cfg['AllowUserDropDatabase']":[2,0,1,"cfg_AllowUserDropDatabase"],"$cfg['ArbitraryServerRegexp']":[2,0,1,"cfg_ArbitraryServerRegexp"],"$cfg['AuthLog']":[2,0,1,"cfg_AuthLog"],"$cfg['AuthLogSuccess']":[2,0,1,"cfg_AuthLogSuccess"],"$cfg['AvailableCharsets']":[2,0,1,"cfg_AvailableCharsets"],"$cfg['BZipDump']":[2,0,1,"cfg_BZipDump"],"$cfg['BrowseMIME']":[2,0,1,"cfg_BrowseMIME"],"$cfg['BrowseMarkerEnable']":[2,0,1,"cfg_BrowseMarkerEnable"],"$cfg['BrowsePointerEnable']":[2,0,1,"cfg_BrowsePointerEnable"],"$cfg['CSPAllow']":[2,0,1,"cfg_CSPAllow"],"$cfg['CaptchaLoginPrivateKey']":[2,0,1,"cfg_CaptchaLoginPrivateKey"],"$cfg['CaptchaLoginPublicKey']":[2,0,1,"cfg_CaptchaLoginPublicKey"],"$cfg['CharEditing']":[2,0,1,"cfg_CharEditing"],"$cfg['CharTextareaCols']":[2,0,1,"cfg_CharTextareaCols"],"$cfg['CharTextareaRows']":[2,0,1,"cfg_CharTextareaRows"],"$cfg['CheckConfigurationPermissions']":[2,0,1,"cfg_CheckConfigurationPermissions"],"$cfg['CodemirrorEnable']":[2,0,1,"cfg_CodemirrorEnable"],"$cfg['CompressOnFly']":[2,0,1,"cfg_CompressOnFly"],"$cfg['Confirm']":[2,0,1,"cfg_Confirm"],"$cfg['Console']['AlwaysExpand']":[2,0,1,"cfg_Console_AlwaysExpand"],"$cfg['Console']['CurrentQuery']":[2,0,1,"cfg_Console_CurrentQuery"],"$cfg['Console']['DarkTheme']":[2,0,1,"cfg_Console_DarkTheme"],"$cfg['Console']['EnterExecutes']":[2,0,1,"cfg_Console_EnterExecutes"],"$cfg['Console']['Height']":[2,0,1,"cfg_Console_Height"],"$cfg['Console']['Mode']":[2,0,1,"cfg_Console_Mode"],"$cfg['Console']['StartHistory']":[2,0,1,"cfg_Console_StartHistory"],"$cfg['ConsoleEnterExecutes']":[2,0,1,"cfg_ConsoleEnterExecutes"],"$cfg['DBG']":[2,0,1,"cfg_DBG"],"$cfg['DBG']['demo']":[2,0,1,"cfg_DBG_demo"],"$cfg['DBG']['simple2fa']":[2,0,1,"cfg_DBG_simple2fa"],"$cfg['DBG']['sql']":[2,0,1,"cfg_DBG_sql"],"$cfg['DBG']['sqllog']":[2,0,1,"cfg_DBG_sqllog"],"$cfg['DefaultConnectionCollation']":[2,0,1,"cfg_DefaultConnectionCollation"],"$cfg['DefaultForeignKeyChecks']":[2,0,1,"cfg_DefaultForeignKeyChecks"],"$cfg['DefaultFunctions']":[2,0,1,"cfg_DefaultFunctions"],"$cfg['DefaultLang']":[2,0,1,"cfg_DefaultLang"],"$cfg['DefaultQueryDatabase']":[2,0,1,"cfg_DefaultQueryDatabase"],"$cfg['DefaultQueryTable']":[2,0,1,"cfg_DefaultQueryTable"],"$cfg['DefaultTabDatabase']":[2,0,1,"cfg_DefaultTabDatabase"],"$cfg['DefaultTabServer']":[2,0,1,"cfg_DefaultTabServer"],"$cfg['DefaultTabTable']":[2,0,1,"cfg_DefaultTabTable"],"$cfg['DefaultTransformations']":[2,0,1,"cfg_DefaultTransformations"],"$cfg['DefaultTransformations']['Bool2Text']":[2,0,1,"cfg_DefaultTransformations_Bool2Text"],"$cfg['DefaultTransformations']['DateFormat']":[2,0,1,"cfg_DefaultTransformations_DateFormat"],"$cfg['DefaultTransformations']['External']":[2,0,1,"cfg_DefaultTransformations_External"],"$cfg['DefaultTransformations']['Hex']":[2,0,1,"cfg_DefaultTransformations_Hex"],"$cfg['DefaultTransformations']['Inline']":[2,0,1,"cfg_DefaultTransformations_Inline"],"$cfg['DefaultTransformations']['PreApPend']":[2,0,1,"cfg_DefaultTransformations_PreApPend"],"$cfg['DefaultTransformations']['Substring']":[2,0,1,"cfg_DefaultTransformations_Substring"],"$cfg['DefaultTransformations']['TextImageLink']":[2,0,1,"cfg_DefaultTransformations_TextImageLink"],"$cfg['DefaultTransformations']['TextLink']":[2,0,1,"cfg_DefaultTransformations_TextLink"],"$cfg['DisableMultiTableMaintenance']":[2,0,1,"cfg_DisableMultiTableMaintenance"],"$cfg['DisableShortcutKeys']":[2,0,1,"cfg_DisableShortcutKeys"],"$cfg['DisplayServersList']":[2,0,1,"cfg_DisplayServersList"],"$cfg['EnableAutocompleteForTablesAndColumns']":[2,0,1,"cfg_EnableAutocompleteForTablesAndColumns"],"$cfg['ExecTimeLimit']":[2,0,1,"cfg_ExecTimeLimit"],"$cfg['Export']":[2,0,1,"cfg_Export"],"$cfg['Export']['charset']":[2,0,1,"cfg_Export_charset"],"$cfg['Export']['file_template_database']":[2,0,1,"cfg_Export_file_template_database"],"$cfg['Export']['file_template_server']":[2,0,1,"cfg_Export_file_template_server"],"$cfg['Export']['file_template_table']":[2,0,1,"cfg_Export_file_template_table"],"$cfg['Export']['format']":[2,0,1,"cfg_Export_format"],"$cfg['Export']['method']":[2,0,1,"cfg_Export_method"],"$cfg['FilterLanguages']":[2,0,1,"cfg_FilterLanguages"],"$cfg['FirstLevelNavigationItems']":[2,0,1,"cfg_FirstLevelNavigationItems"],"$cfg['FontSize']":[2,0,1,"cfg_FontSize"],"$cfg['ForceSSL']":[2,0,1,"cfg_ForceSSL"],"$cfg['ForeignKeyDropdownOrder']":[2,0,1,"cfg_ForeignKeyDropdownOrder"],"$cfg['ForeignKeyMaxLimit']":[2,0,1,"cfg_ForeignKeyMaxLimit"],"$cfg['GD2Available']":[2,0,1,"cfg_GD2Available"],"$cfg['GZipDump']":[2,0,1,"cfg_GZipDump"],"$cfg['GridEditing']":[2,0,1,"cfg_GridEditing"],"$cfg['HideStructureActions']":[2,0,1,"cfg_HideStructureActions"],"$cfg['IconvExtraParams']":[2,0,1,"cfg_IconvExtraParams"],"$cfg['IgnoreMultiSubmitErrors']":[2,0,1,"cfg_IgnoreMultiSubmitErrors"],"$cfg['Import']":[2,0,1,"cfg_Import"],"$cfg['Import']['charset']":[2,0,1,"cfg_Import_charset"],"$cfg['InitialSlidersState']":[2,0,1,"cfg_InitialSlidersState"],"$cfg['InsertRows']":[2,0,1,"cfg_InsertRows"],"$cfg['Lang']":[2,0,1,"cfg_Lang"],"$cfg['LimitChars']":[2,0,1,"cfg_LimitChars"],"$cfg['LinkLengthLimit']":[2,0,1,"cfg_LinkLengthLimit"],"$cfg['LoginCookieDeleteAll']":[2,0,1,"cfg_LoginCookieDeleteAll"],"$cfg['LoginCookieRecall']":[2,0,1,"cfg_LoginCookieRecall"],"$cfg['LoginCookieStore']":[2,0,1,"cfg_LoginCookieStore"],"$cfg['LoginCookieValidity']":[2,0,1,"cfg_LoginCookieValidity"],"$cfg['LoginCookieValidityDisableWarning']":[2,0,1,"cfg_LoginCookieValidityDisableWarning"],"$cfg['LongtextDoubleTextarea']":[2,0,1,"cfg_LongtextDoubleTextarea"],"$cfg['MaxCharactersInDisplayedSQL']":[2,0,1,"cfg_MaxCharactersInDisplayedSQL"],"$cfg['MaxDbList']":[2,0,1,"cfg_MaxDbList"],"$cfg['MaxExactCount']":[2,0,1,"cfg_MaxExactCount"],"$cfg['MaxExactCountViews']":[2,0,1,"cfg_MaxExactCountViews"],"$cfg['MaxNavigationItems']":[2,0,1,"cfg_MaxNavigationItems"],"$cfg['MaxRows']":[2,0,1,"cfg_MaxRows"],"$cfg['MaxSizeForInputField']":[2,0,1,"cfg_MaxSizeForInputField"],"$cfg['MaxTableList']":[2,0,1,"cfg_MaxTableList"],"$cfg['MemoryLimit']":[2,0,1,"cfg_MemoryLimit"],"$cfg['MinSizeForInputField']":[2,0,1,"cfg_MinSizeForInputField"],"$cfg['MysqlMinVersion']":[2,0,1,"cfg_MysqlMinVersion"],"$cfg['NaturalOrder']":[2,0,1,"cfg_NaturalOrder"],"$cfg['NavigationDisplayLogo']":[2,0,1,"cfg_NavigationDisplayLogo"],"$cfg['NavigationDisplayServers']":[2,0,1,"cfg_NavigationDisplayServers"],"$cfg['NavigationLinkWithMainPanel']":[2,0,1,"cfg_NavigationLinkWithMainPanel"],"$cfg['NavigationLogoLink']":[2,0,1,"cfg_NavigationLogoLink"],"$cfg['NavigationLogoLinkWindow']":[2,0,1,"cfg_NavigationLogoLinkWindow"],"$cfg['NavigationTreeDbSeparator']":[2,0,1,"cfg_NavigationTreeDbSeparator"],"$cfg['NavigationTreeDefaultTabTable']":[2,0,1,"cfg_NavigationTreeDefaultTabTable"],"$cfg['NavigationTreeDefaultTabTable2']":[2,0,1,"cfg_NavigationTreeDefaultTabTable2"],"$cfg['NavigationTreeDisplayDbFilterMinimum']":[2,0,1,"cfg_NavigationTreeDisplayDbFilterMinimum"],"$cfg['NavigationTreeDisplayItemFilterMinimum']":[2,0,1,"cfg_NavigationTreeDisplayItemFilterMinimum"],"$cfg['NavigationTreeEnableExpansion']":[2,0,1,"cfg_NavigationTreeEnableExpansion"],"$cfg['NavigationTreeEnableGrouping']":[2,0,1,"cfg_NavigationTreeEnableGrouping"],"$cfg['NavigationTreePointerEnable']":[2,0,1,"cfg_NavigationTreePointerEnable"],"$cfg['NavigationTreeShowEvents']":[2,0,1,"cfg_NavigationTreeShowEvents"],"$cfg['NavigationTreeShowFunctions']":[2,0,1,"cfg_NavigationTreeShowFunctions"],"$cfg['NavigationTreeShowProcedures']":[2,0,1,"cfg_NavigationTreeShowProcedures"],"$cfg['NavigationTreeShowTables']":[2,0,1,"cfg_NavigationTreeShowTables"],"$cfg['NavigationTreeShowViews']":[2,0,1,"cfg_NavigationTreeShowViews"],"$cfg['NavigationTreeTableLevel']":[2,0,1,"cfg_NavigationTreeTableLevel"],"$cfg['NavigationTreeTableSeparator']":[2,0,1,"cfg_NavigationTreeTableSeparator"],"$cfg['NavigationWidth']":[2,0,1,"cfg_NavigationWidth"],"$cfg['NumFavoriteTables']":[2,0,1,"cfg_NumFavoriteTables"],"$cfg['NumRecentTables']":[2,0,1,"cfg_NumRecentTables"],"$cfg['OBGzip']":[2,0,1,"cfg_OBGzip"],"$cfg['Order']":[2,0,1,"cfg_Order"],"$cfg['PDFDefaultPageSize']":[2,0,1,"cfg_PDFDefaultPageSize"],"$cfg['PDFPageSizes']":[2,0,1,"cfg_PDFPageSizes"],"$cfg['PersistentConnections']":[2,0,1,"cfg_PersistentConnections"],"$cfg['PmaAbsoluteUri']":[2,0,1,"cfg_PmaAbsoluteUri"],"$cfg['PmaNoRelation_DisableWarning']":[2,0,1,"cfg_PmaNoRelation_DisableWarning"],"$cfg['PropertiesNumColumns']":[2,0,1,"cfg_PropertiesNumColumns"],"$cfg['ProtectBinary']":[2,0,1,"cfg_ProtectBinary"],"$cfg['ProxyPass']":[2,0,1,"cfg_ProxyPass"],"$cfg['ProxyUrl']":[2,0,1,"cfg_ProxyUrl"],"$cfg['ProxyUser']":[2,0,1,"cfg_ProxyUser"],"$cfg['QueryHistoryDB']":[2,0,1,"cfg_QueryHistoryDB"],"$cfg['QueryHistoryMax']":[2,0,1,"cfg_QueryHistoryMax"],"$cfg['RecodingEngine']":[2,0,1,"cfg_RecodingEngine"],"$cfg['RelationalDisplay']":[2,0,1,"cfg_RelationalDisplay"],"$cfg['RememberSorting']":[2,0,1,"cfg_RememberSorting"],"$cfg['RepeatCells']":[2,0,1,"cfg_RepeatCells"],"$cfg['ReservedWordDisableWarning']":[2,0,1,"cfg_ReservedWordDisableWarning"],"$cfg['RetainQueryBox']":[2,0,1,"cfg_RetainQueryBox"],"$cfg['RowActionLinks']":[2,0,1,"cfg_RowActionLinks"],"$cfg['RowActionLinksWithoutUnique']":[2,0,1,"cfg_RowActionLinksWithoutUnique"],"$cfg['RowActionType']":[2,0,1,"cfg_RowActionType"],"$cfg['SQLQuery']['Edit']":[2,0,1,"cfg_SQLQuery_Edit"],"$cfg['SQLQuery']['Explain']":[2,0,1,"cfg_SQLQuery_Explain"],"$cfg['SQLQuery']['Refresh']":[2,0,1,"cfg_SQLQuery_Refresh"],"$cfg['SQLQuery']['ShowAsPHP']":[2,0,1,"cfg_SQLQuery_ShowAsPHP"],"$cfg['SaveCellsAtOnce']":[2,0,1,"cfg_SaveCellsAtOnce"],"$cfg['SaveDir']":[2,0,1,"cfg_SaveDir"],"$cfg['SendErrorReports']":[2,0,1,"cfg_SendErrorReports"],"$cfg['ServerDefault']":[2,0,1,"cfg_ServerDefault"],"$cfg['ServerLibraryDifference_DisableWarning']":[2,0,1,"cfg_ServerLibraryDifference_DisableWarning"],"$cfg['Servers']":[2,0,1,"cfg_Servers"],"$cfg['Servers'][$i]['AllowDeny']['order']":[2,0,1,"cfg_Servers_AllowDeny_order"],"$cfg['Servers'][$i]['AllowDeny']['rules']":[2,0,1,"cfg_Servers_AllowDeny_rules"],"$cfg['Servers'][$i]['AllowNoPassword']":[2,0,1,"cfg_Servers_AllowNoPassword"],"$cfg['Servers'][$i]['AllowRoot']":[2,0,1,"cfg_Servers_AllowRoot"],"$cfg['Servers'][$i]['DisableIS']":[2,0,1,"cfg_Servers_DisableIS"],"$cfg['Servers'][$i]['LogoutURL']":[2,0,1,"cfg_Servers_LogoutURL"],"$cfg['Servers'][$i]['MaxTableUiprefs']":[2,0,1,"cfg_Servers_MaxTableUiprefs"],"$cfg['Servers'][$i]['SessionTimeZone']":[2,0,1,"cfg_Servers_SessionTimeZone"],"$cfg['Servers'][$i]['SignonCookieParams']":[2,0,1,"cfg_Servers_SignonCookieParams"],"$cfg['Servers'][$i]['SignonScript']":[2,0,1,"cfg_Servers_SignonScript"],"$cfg['Servers'][$i]['SignonSession']":[2,0,1,"cfg_Servers_SignonSession"],"$cfg['Servers'][$i]['SignonURL']":[2,0,1,"cfg_Servers_SignonURL"],"$cfg['Servers'][$i]['auth_http_realm']":[2,0,1,"cfg_Servers_auth_http_realm"],"$cfg['Servers'][$i]['auth_type']":[2,0,1,"cfg_Servers_auth_type"],"$cfg['Servers'][$i]['bookmarktable']":[2,0,1,"cfg_Servers_bookmarktable"],"$cfg['Servers'][$i]['central_columns']":[2,0,1,"cfg_Servers_central_columns"],"$cfg['Servers'][$i]['column_info']":[2,0,1,"cfg_Servers_column_info"],"$cfg['Servers'][$i]['compress']":[2,0,1,"cfg_Servers_compress"],"$cfg['Servers'][$i]['connect_type']":[2,0,1,"cfg_Servers_connect_type"],"$cfg['Servers'][$i]['control_*']":[2,0,1,"cfg_Servers_control_*"],"$cfg['Servers'][$i]['controlhost']":[2,0,1,"cfg_Servers_controlhost"],"$cfg['Servers'][$i]['controlpass']":[2,0,1,"cfg_Servers_controlpass"],"$cfg['Servers'][$i]['controlport']":[2,0,1,"cfg_Servers_controlport"],"$cfg['Servers'][$i]['controluser']":[2,0,1,"cfg_Servers_controluser"],"$cfg['Servers'][$i]['designer_settings']":[2,0,1,"cfg_Servers_designer_settings"],"$cfg['Servers'][$i]['export_templates']":[2,0,1,"cfg_Servers_export_templates"],"$cfg['Servers'][$i]['extension']":[2,0,1,"cfg_Servers_extension"],"$cfg['Servers'][$i]['favorite']":[2,0,1,"cfg_Servers_favorite"],"$cfg['Servers'][$i]['hide_db']":[2,0,1,"cfg_Servers_hide_db"],"$cfg['Servers'][$i]['history']":[2,0,1,"cfg_Servers_history"],"$cfg['Servers'][$i]['host']":[2,0,1,"cfg_Servers_host"],"$cfg['Servers'][$i]['navigationhiding']":[2,0,1,"cfg_Servers_navigationhiding"],"$cfg['Servers'][$i]['nopassword']":[2,0,1,"cfg_Servers_nopassword"],"$cfg['Servers'][$i]['only_db']":[2,0,1,"cfg_Servers_only_db"],"$cfg['Servers'][$i]['password']":[2,0,1,"cfg_Servers_password"],"$cfg['Servers'][$i]['pdf_pages']":[2,0,1,"cfg_Servers_pdf_pages"],"$cfg['Servers'][$i]['pmadb']":[2,0,1,"cfg_Servers_pmadb"],"$cfg['Servers'][$i]['port']":[2,0,1,"cfg_Servers_port"],"$cfg['Servers'][$i]['recent']":[2,0,1,"cfg_Servers_recent"],"$cfg['Servers'][$i]['relation']":[2,0,1,"cfg_Servers_relation"],"$cfg['Servers'][$i]['savedsearches']":[2,0,1,"cfg_Servers_savedsearches"],"$cfg['Servers'][$i]['socket']":[2,0,1,"cfg_Servers_socket"],"$cfg['Servers'][$i]['ssl']":[2,0,1,"cfg_Servers_ssl"],"$cfg['Servers'][$i]['ssl_ca']":[2,0,1,"cfg_Servers_ssl_ca"],"$cfg['Servers'][$i]['ssl_ca_path']":[2,0,1,"cfg_Servers_ssl_ca_path"],"$cfg['Servers'][$i]['ssl_cert']":[2,0,1,"cfg_Servers_ssl_cert"],"$cfg['Servers'][$i]['ssl_ciphers']":[2,0,1,"cfg_Servers_ssl_ciphers"],"$cfg['Servers'][$i]['ssl_key']":[2,0,1,"cfg_Servers_ssl_key"],"$cfg['Servers'][$i]['ssl_verify']":[2,0,1,"cfg_Servers_ssl_verify"],"$cfg['Servers'][$i]['table_coords']":[2,0,1,"cfg_Servers_table_coords"],"$cfg['Servers'][$i]['table_info']":[2,0,1,"cfg_Servers_table_info"],"$cfg['Servers'][$i]['table_uiprefs']":[2,0,1,"cfg_Servers_table_uiprefs"],"$cfg['Servers'][$i]['tracking']":[2,0,1,"cfg_Servers_tracking"],"$cfg['Servers'][$i]['tracking_add_drop_database']":[2,0,1,"cfg_Servers_tracking_add_drop_database"],"$cfg['Servers'][$i]['tracking_add_drop_table']":[2,0,1,"cfg_Servers_tracking_add_drop_table"],"$cfg['Servers'][$i]['tracking_add_drop_view']":[2,0,1,"cfg_Servers_tracking_add_drop_view"],"$cfg['Servers'][$i]['tracking_default_statements']":[2,0,1,"cfg_Servers_tracking_default_statements"],"$cfg['Servers'][$i]['tracking_version_auto_create']":[2,0,1,"cfg_Servers_tracking_version_auto_create"],"$cfg['Servers'][$i]['user']":[2,0,1,"cfg_Servers_user"],"$cfg['Servers'][$i]['userconfig']":[2,0,1,"cfg_Servers_userconfig"],"$cfg['Servers'][$i]['usergroups']":[2,0,1,"cfg_Servers_usergroups"],"$cfg['Servers'][$i]['users']":[2,0,1,"cfg_Servers_users"],"$cfg['Servers'][$i]['verbose']":[2,0,1,"cfg_Servers_verbose"],"$cfg['SessionSavePath']":[2,0,1,"cfg_SessionSavePath"],"$cfg['ShowAll']":[2,0,1,"cfg_ShowAll"],"$cfg['ShowBrowseComments']":[2,0,1,"cfg_ShowBrowseComments"],"$cfg['ShowChgPassword']":[2,0,1,"cfg_ShowChgPassword"],"$cfg['ShowColumnComments']":[2,0,1,"cfg_ShowColumnComments"],"$cfg['ShowCreateDb']":[2,0,1,"cfg_ShowCreateDb"],"$cfg['ShowDatabasesNavigationAsTree']":[2,0,1,"cfg_ShowDatabasesNavigationAsTree"],"$cfg['ShowDbStructureCreation']":[2,0,1,"cfg_ShowDbStructureCreation"],"$cfg['ShowDbStructureLastCheck']":[2,0,1,"cfg_ShowDbStructureLastCheck"],"$cfg['ShowDbStructureLastUpdate']":[2,0,1,"cfg_ShowDbStructureLastUpdate"],"$cfg['ShowFieldTypesInDataEditView']":[2,0,1,"cfg_ShowFieldTypesInDataEditView"],"$cfg['ShowFunctionFields']":[2,0,1,"cfg_ShowFunctionFields"],"$cfg['ShowGitRevision']":[2,0,1,"cfg_ShowGitRevision"],"$cfg['ShowHint']":[2,0,1,"cfg_ShowHint"],"$cfg['ShowPhpInfo']":[2,0,1,"cfg_ShowPhpInfo"],"$cfg['ShowPropertyComments']":[2,0,1,"cfg_ShowPropertyComments"],"$cfg['ShowSQL']":[2,0,1,"cfg_ShowSQL"],"$cfg['ShowServerInfo']":[2,0,1,"cfg_ShowServerInfo"],"$cfg['ShowStats']":[2,0,1,"cfg_ShowStats"],"$cfg['SkipLockedTables']":[2,0,1,"cfg_SkipLockedTables"],"$cfg['SuhosinDisableWarning']":[2,0,1,"cfg_SuhosinDisableWarning"],"$cfg['TableNavigationLinksMode']":[2,0,1,"cfg_TableNavigationLinksMode"],"$cfg['TablePrimaryKeyOrder']":[2,0,1,"cfg_TablePrimaryKeyOrder"],"$cfg['TabsMode']":[2,0,1,"cfg_TabsMode"],"$cfg['TempDir']":[2,0,1,"cfg_TempDir"],"$cfg['TextareaAutoSelect']":[2,0,1,"cfg_TextareaAutoSelect"],"$cfg['TextareaCols']":[2,0,1,"cfg_TextareaCols"],"$cfg['TextareaRows']":[2,0,1,"cfg_TextareaRows"],"$cfg['ThemeDefault']":[2,0,1,"cfg_ThemeDefault"],"$cfg['ThemeManager']":[2,0,1,"cfg_ThemeManager"],"$cfg['ThemePerServer']":[2,0,1,"cfg_ThemePerServer"],"$cfg['TitleDatabase']":[2,0,1,"cfg_TitleDatabase"],"$cfg['TitleDefault']":[2,0,1,"cfg_TitleDefault"],"$cfg['TitleServer']":[2,0,1,"cfg_TitleServer"],"$cfg['TitleTable']":[2,0,1,"cfg_TitleTable"],"$cfg['TranslationWarningThreshold']":[2,0,1,"cfg_TranslationWarningThreshold"],"$cfg['TrustedProxies']":[2,0,1,"cfg_TrustedProxies"],"$cfg['UploadDir']":[2,0,1,"cfg_UploadDir"],"$cfg['UseDbSearch']":[2,0,1,"cfg_UseDbSearch"],"$cfg['UserprefsDeveloperTab']":[2,0,1,"cfg_UserprefsDeveloperTab"],"$cfg['UserprefsDisallow']":[2,0,1,"cfg_UserprefsDisallow"],"$cfg['VersionCheck']":[2,0,1,"cfg_VersionCheck"],"$cfg['ZeroConf']":[2,0,1,"cfg_ZeroConf"],"$cfg['ZipDump']":[2,0,1,"cfg_ZipDump"],"$cfg['blowfish_secret']":[2,0,1,"cfg_blowfish_secret"],PMA_ABSOLUTE_URI:[17,2,1,"-"],PMA_ARBITRARY:[17,2,1,"-"],PMA_HOST:[17,2,1,"-"],PMA_HOSTS:[17,2,1,"-"],PMA_PASSWORD:[17,2,1,"-"],PMA_PORT:[17,2,1,"-"],PMA_PORTS:[17,2,1,"-"],PMA_USER:[17,2,1,"-"],PMA_VERBOSE:[17,2,1,"-"],PMA_VERBOSES:[17,2,1,"-"],comment:[8,1,1,""],data:[8,1,1,""],database:[8,1,1,""],name:[8,1,1,""],type:[8,1,1,""],version:[8,1,1,""]}},objnames:{"0":["config","option","Config config option"],"1":["js","data","JavaScript data"],"2":["std","envvar","environment variable"]},objtypes:{"0":"config:option","1":"js:data","2":"std:envvar"},terms:{"01youknowme_at_gmail":4,"0c3f":17,"0d79":17,"0eb7":17,"0pt":8,"0x9c27b31342b7511d":17,"0xce752f178259bd92":17,"0xfefc65d181af644a":17,"16m":2,"1b51":17,"1c17":17,"1df1":17,"210mm":8,"218a":15,"297mm":8,"2cm":8,"2f17":17,"3092849_at_qq":4,"38cf":15,"3d06":17,"3d06a59ece730eb71b511c17ce752f178259bd92":17,"4096r":15,"42b7":17,"436f":17,"4b1a":17,"4bd7":15,"50b9":15,"50x50":6,"511d":17,"5a32":17,"5bad":15,"5e4176fb497a31f7":15,"5xp_":17,"6375lpd_at_gmail":4,"63cb":17,"644a":17,"65d1":17,"7euser":6,"81af":17,"8259bd92":17,"98se":6,"999_at_yahoo":4,"9c27":17,"\u00e7okaj":4,"\u00e9tienn":4,"\u010diha\u0159":[3,4,17],"\u0161ime\u010dek":4,"\u03c0\u03b1\u03bd\u03b1\u03b3\u03b9\u03ce\u03c4\u03b7\u03c2":4,"\u03c0\u03b1\u03c0\u03ac\u03b6\u03bf\u03b3\u03bb\u03bf\u03c5":4,"\u0438\u0432\u0430\u043d":4,"\u043a\u0430\u0440\u043f\u043e\u0432":4,"\u043e\u043b\u0435\u0433":4,"\u043f\u0435\u0434\u044c\u043a\u043e":4,"\u0441\u0435\u0440\u0433\u0435\u0435\u0432\u0438\u0447":4,"\u0441\u0435\u0440\u0433\u0456\u0439":4,"\u0445\u043e\u043c\u0443\u0442\u043e\u0432":4,"\u0b95":4,"\u0b95\u0ba3":4,"\u0bae":4,"\u0bb0":4,"\u0bb7":4,"\u7f57\u6500\u767b":4,"\uc774\uacbd\uc900":4,"abstract":[4,19],"ale\u0161":4,"aputsia\u0138":4,"ara\u00fajo":4,"b\u00f8rge":4,"bal\u00e1z":4,"bokm\u00e5l":4,"boolean":2,"break":6,"byte":[2,7,8],"c\u00e9dric":4,"case":[0,2,6,12,13,17],"catch":17,"char":[0,2,6,15,17],"class":[2,6,17,19],"d\u00e1niel":4,"default":0,"delete":[2,6,17],"enum":[4,19],"final":[6,13],"fr\u00f8yshov":4,"function":[2,4],"garc\u00eda":4,"gesch\u00e9":4,"j\u00fanio":4,"jo\u00e3o":4,"kate\u0159i\u0148\u00e1k":4,"kl\u00e4ger":4,"landh\u00e4u\u00df":4,"lo\u00efc":4,"long":[2,6,7,17,19],"lu\u00ed":4,"m\u00fcller":[3,4],"mat\u00eda":4,"micha\u0142":4,"mickevi\u010diu":4,"mirc\u0259lal":4,"montan\u00e9":4,"montr\u00e9al":6,"new":[1,2,4],"null":2,"p\u00e9ter":4,"public":[2,3,6,17],"r\u00e4t":4,"return":2,"seri\u00e1l":11,"short":[7,8],"switch":[2,7,17],"sz\u00e1sz":4,"t\u00f3th":4,"tom\u00e1\u0161":4,"toma\u0161t\u00edk":4,"true":[2,17],"try":2,"var":[2,6,22],"vin\u00edciu":4,"void":17,"while":[1,2],"xos\u00e9":4,_db:2,_get:17,_post:17,_server:[2,17],_session:17,_uri_scheme:7,a59e:17,aaleksanyants_at_yahoo:4,ab39:15,abd:4,abdulla:4,abeyrathna:4,abiword:8,abl:[0,2,4],abort:2,about:2,abov:[1,2,6,17],abram:4,abravo_at_hq:4,absenc:[2,6],absence:0,absolut:[2,17],abtract:19,academy:1,accept:[2,6,7],acceptabl:2,access:[0,2,4],accident:2,accommod:1,accompani:17,accomplish:[6,8,17],accord:2,accordingli:[2,6,19],account:[2,10,12,17],ace:1,achch1990_at_gmail:4,achchuthan:4,achiev:[2,6,12,17],acl:[2,7,17],acokaj_at_shkod:4,across:[2,7],act:20,action:2,actionlinksmod:2,activ:[2,6],activat:2,actual:[6,17,19],acunetix:6,adam:4,adaptation:1,add:2,addhandler:6,adding:8,addit:[2,4],addition:[2,3,17],additon:18,addon:6,addopt:8,addprefix:17,addr:2,address:0,addtype:6,adjust:2,adler:7,administ:10,administr:[2,4,6,10,12,15,17],admir:4,adnan:4,adobe:7,adopt:8,adriaenssen:4,adrian:4,adsbot:6,advanc:[6,15,17],advantag:6,advic:22,advis:[2,17],aes:[2,17],affair:1,affect:[2,6],afraid:17,african:1,after:2,again:2,agent:6,agre:2,ahmed:4,ahmedtek1993_at_gmail:4,aj_at_isit:4,ajax:4,ajaxifi:4,ajaxific:4,aka:17,ako:4,alan:4,albanian:4,albb0920_at_gmail:4,albert:4,alberti:4,alberty_at_neptunlab:4,albiol:4,aleksany:4,alemoretti2010_at_hotmail:4,ales_at_hakl:4,alessandro:4,alex:4,alexalex:4,alexand:[3,4],alexandr:4,alexei:4,alexi:4,alexrohleder96_at_outlook:4,algeri:4,algi:4,algorithm:[2,7,17],alia:4,align:6,alioglu:4,all:[2,5,6,7,8,10,12,13,15,17,18,19,22],alloc:2,allow:[1,2,4],allow_url_open:14,allowarbitraryserv:[2,17],allowdeni:[2,6,17],allownopassword:[2,17],allowoverrid:6,allowroot:[2,17],allowthirdpartyfram:2,allowuserdropdatabas:2,along:[3,17],alpha:6,alreadi:2,also:[0,2,4,6,7,8,10,12,15,16,17,19,22],alter:[2,6,7,10],altern:[2,6,17],although:[2,4,6],alvar:4,alwai:2,alwaysexpand:2,amalesh:4,amaral:4,amihaita_at_yahoo:4,amir:4,ammar:4,among:[2,8],amount:[1,2,6,19],analys:4,analyz:[2,4,6],ander:4,andersen:4,andika:4,andika_at_gmail:4,andrea:4,andrei:4,android:20,andrzej:4,andrzej_at_kynu:4,angular:2,ani:[0,2,4],ankit:4,ann:4,announc:[15,17],announcement:15,anonym:[2,6],anonynuin:4,anoth:[1,2,6,7,13,17],answer:7,anticip:7,antiviru:6,any:[0,2,3,6],anybodi:17,anyon:17,anyth:[6,17],anywai:6,apach:2,apache2:6,apache_http_serv:7,apart:6,apc:6,api:[2,6,7],appear:[0,2,6,15],append:[2,19],apple:7,appli:[2,6,13,17,19],applic:[1,2],applicat:[6,7],applytransform:19,approach:[2,20],approxim:[2,6],arabic:4,arben:4,arbirari:18,arbitrari:[2,6,17],arbitraryserverregexp:2,architectur:6,archiv:[6,7,17],archive:[7,8],area:[1,2],arg_separ:6,argument:6,ari:4,arifianto:4,armel:4,armenian:4,around:[2,6,17],arrai:[2,6],arrow:6,articl:[6,8,11],artyom:4,asc:[2,17],ascend:2,ascii:2,ash:4,ashkan:4,ashraf:4,ashutosh:4,ask:2,aso:4,aspect:[2,14,22],assign:[2,6],associ:2,assum:[2,6,17],astarita:4,asuni:4,attach:[2,7],attack:2,attemp:17,attempt:[2,6,15,17],attent:17,attila:4,attribut:8,atul:4,atulpratapsingh05_at_gmail:4,atvejis_at_gmail:4,auth:2,auth_http_realm:2,auth_map:17,auth_typ:[2,17],authi:20,authlog:[2,17],authlogsuccess:2,authnam:17,author:[2,4,6,8,17,18],authrequest:17,authtyp:17,authuserfil:17,auto:2,auto_incr:[6,8],auto_increment:6,autocomplet:2,autoconnect:2,autodetect:2,autom:[4,17],automat:[0,2,4],auxiliari:17,avail:[2,3,6,7,13,15,17,19,20,22],availabl:2,availablecharset:2,averag:6,avoid:[2,6,8,17],awai:6,awar:[0,6,17],axel:4,axi:[1,6],ayush:4,azerbaijani:4,azevedo:4,azzabi:4,b313:17,b947:15,b980:15,backend:[6,17],background:2,backquot:6,backslash:6,backspac:10,backward:[2,18],bacon:20,bad:[6,17],badalo:4,badalo_at_sapo:4,baiduspid:6,bailout_on_error:6,bakondi:4,balanc:2,ban:6,bansod:4,bao:4,baophan94_at_icloud:4,barri:4,base:[2,4],batch:10,bbedit:17,bd92:17,becaus:[2,4],beck:4,beck_at_web:4,becom:6,been:[2,5,6,8,17,18],befor:[2,6,8,15,17,18],begin:[2,6,8,17],behavior:2,behaviour:[2,6,17],behind:[2,6],belarusian:4,believ:6,bellon:4,belong:17,below:[0,2],beneath:6,benefit:[2,6],benjamin:4,bennetch:17,benni:4,berkelei:2,bernard:4,best:[6,8,17],better:6,between:[0,1,2,6],beyond:17,biesaga:4,big:2,bigdump:6,bigger:[6,8],bimal:4,bin:[4,6],binari:2,binary:2,bisht:4,bit:[2,17],bits_at_gmail:4,bkehayov_at_gmail:4,blagynchy_at_gmail:4,blank:2,blanks:19,blob:2,blobs:2,blobstream:4,block:[2,6,7,17],blog:8,blowfish:[2,4,7],blowfish_:7,blowfish_secret:[2,17],bluthardt:4,bob:[2,6],bodi:17,bogor_at_gmail:4,book:[7,8],bookmarkt:2,bool2text:2,boost:2,bora:4,borg:4,borge947_at_gmail:4,borggrev:4,borrow:4,bot:6,botelho:4,both:[1,2,6,8,13,16,17],bottom:[2,6,12,13],bound:19,boutel:7,box:0,boyan:4,branch:17,braschi_at_outlook:4,bravo:4,brazil:4,breakag:17,broader:7,broken:6,browsemarkeren:2,browsemime:2,browsepointeren:2,browser:2,bruce:7,bruguera:4,bruno:4,brunomendax_at_gmail:4,bskim45_at_gmail:4,buddika:4,buffer:[2,6,19],bug:[2,4],bugfix:4,buggi:[6,17],bugzilla:6,build:[2,6,17,19],builder:4,built:[4,6],bulgarian:4,bum:4,bumsoo:4,bunch:6,burak:4,busi:7,bussier:4,button:[0,2,6,7,17,18,20],buz_at_gmail:4,bypass:[2,6],bz2:2,bzip2:[2,6,7],bzipdump:2,c0ee:17,cach:[2,6,17],calcul:8,call:[2,6,7,10,13,17,19],calvo:4,can:[0,1,2,3,5],canada:6,cannot:2,capabl:7,captcha:[15,17],captchaloginprivatekei:[2,17],captchaloginpublickei:[2,17],car:8,car_id:8,care:[6,19],carefulli:2,casotti:4,casotti_at_uol:4,cat:4,catalan:4,categori:13,category_id:13,caus:2,caution:17,cav:4,cbb74bc:17,cdac1234_at_gmail:4,cdba:2,ce73:17,ce75:17,cedric:4,cell:[2,6,7],center:[6,17],centos:17,central:[2,4],central_column:2,cert:2,certain:2,certfic:2,certif:[2,17],certifi:17,cf2a:17,cfg:[2,6,8,12,13,17,18,19,20],chanaka:4,chanc:2,chang:[2,4],changelog:7,chao:4,chaovavanich:4,chapeaux:4,chapter:[2,6,17],charact:2,charanyogeshwar_at_gmail:4,charedit:2,charg:2,charset:[2,4,6,17,19],chartextareacol:2,chartextarearow:2,chat:1,chathuranga:4,chaudhari:4,check:[2,3,4,5,6,7,10,12,17,19,22],checkbox:[1,2],checkconfigurationpermiss:2,checklink:6,chee:4,cheng:4,chien:4,chimera:6,china:4,chines:4,chirayu:4,chirip:4,chiyokawa:4,chiyokawa_at_gmail:4,chmod:[2,6],choic:[2,8,17],choos:[1,2,6,17,19],chose:[6,17],chosen:[2,6],chown:[2,6],chri:4,chrisj_at_ctel:4,christoff:4,christoph:4,chrome:20,chrysler:8,cidr:2,cipher:[2,7],circumv:[2,6],cj_at_gmail:4,claimed_id:17,clanboy_at_163:4,clase:2,classless:2,clau:4,claus:2,clear:[6,17],click:[1,2],clickabl:2,clickjack:2,client:2,client_:7,clipboard:[2,6],clone:17,close:[2,6,17],clshttp:6,clue:6,cma:4,cocerhan:4,cochran:4,code:[2,4,6,7,15,17,18,20],codemirror:2,codemirroren:2,collaps:2,collat:2,collect:7,colognian:4,colon:6,color:2,column_:7,column_com:2,column_info:[2,19],columns:6,columns_priv:6,com:[2,3,4,6,7,8,14,17,19],combin:[2,6,8],come:[2,3],comma:[7,8,17],command:[2,6,17,20],comment:[0,2,4],commiss:7,common:[2,6,7,17,20],common_gateway_interfac:7,commonli:[7,8],commun:[7,10],compar:1,compat:[2,4,6,7,8,13,14,17,18,22],compil:6,complain:17,complet:[2,4],complex:[0,2,10],compliant:4,complic:2,compos:[2,3,9],comprehens:8,compress:2,compressonfli:[2,6],comput:[7,17],computation:8,computer_sci:7,concept:[4,10],concern:[8,15],condit:6,conf:6,confer:4,config:[2,6,16],configur:0,confirm:[2,6,20],conflict:6,conform:7,confus:6,connect_typ:2,consecut:6,consequ:2,consider:[1,17],consist:[6,18],consoleenterexecut:2,constanti:4,construct:[0,6],consult:[8,19],consum:7,contact:[6,15],contain:2,container_nam:17,content:2,context:6,continu:[2,17],contol:7,contrib:17,contribut:[4,5,6],contributor:4,control:[2,4],control_:2,control_ssl:2,control_ssl_ca:2,control_ssl_cert:2,control_ssl_kei:2,controlconnect:15,controlhost:2,controlpass:[2,6,17],controlport:2,controlus:[2,6,17],convei:7,conveni:17,convent:19,convers:[2,4],convert:6,cookie:2,coordin:[2,6],copi:[2,3],corazza:4,corazza_at_wanadoo:4,core:[2,7],correct:2,correctli:[2,6,8],correspond:[2,6,19],corrupt:[2,6],cost:1,costa1988sv_at_gmail:4,costel:4,could:2,count:[2,6,17],countri:6,country_cod:6,cours:[2,6],cover:17,coverag:6,craft:15,crawler:6,crawleradmin:6,creat:[2,4],create:[2,6,7],create_t:17,creation:[2,4],creator:4,credenti:[2,17],credit:3,criteria:6,cross:[2,7],cryptograph:[7,17],cspallow:2,css2:4,css:[2,4,18],csv_column:17,ctrl:[2,6],ctype:14,curl:[6,14],current:[1,2,4,6,7,10,13,15,17,18,22],currentqueri:2,cursor:[2,6],custom:1,cut:2,cve:15,cvs:4,cwlin0416_at_gmail:4,cybot_tm_at_us:[3,4],czech:[2,4],d3xter_at_us:4,da2n_s_at_yahoo:4,da68:15,da68ab39218ab947:15,dadan:4,dalibor:4,dan:4,daniel:4,danilo:4,danish:[4,8],danorse_at_gmail:4,dark:2,darkthem:2,darlow:4,dash:[2,6],data:[1,2],databas:0,database:[2,6],database_trigg:7,databaseinterfac:15,databases:6,databs:6,date:[1,2,6,8,17],dateformat:2,datetime:2,dave:4,david:4,davidson:4,db1:[2,17],db2:[2,17],dbase:6,dbconfig:17,dbf:6,dbg:[2,20],dbhost1:17,dbhost2:17,dbhost3:17,dbhost:17,dbms:7,dcbf:17,dckyoung_at_gmail:4,dd0:17,ddrmoscow_at_gmail:4,deactiv:[2,6],deal:7,debian:2,debug:[2,4],decemb:4,decid:[2,6],declar:[6,8,19],decreas:8,decrypt:6,default_socket:6,defaultconnectioncol:2,defaultentrypoint:17,defaultforeignkeycheck:2,defaultfunct:2,defaultlang:2,defaultquerydatabas:2,defaultqueryt:2,defaulttabdatabas:2,defaulttabserv:2,defaulttabt:2,defaulttransform:[2,19],defeat:2,defin:[2,6,7,12,17,19],definit:[2,6,11,13,18],degrad:6,deki:4,deky40_at_gmail:4,delai:6,delayed:6,delet:[2,4],delimit:2,delisl:[3,4,11,17],delislma_at_collegesherbrook:4,delorm:4,demo:[2,8,17],demonstr:[2,20],den:4,deni:[2,4],denni:4,depend:[2,6,8,17,20,22],deploi:17,deprec:2,depth:6,der:4,derek:4,desc:2,descend:2,descipt:18,describ:[2,7,15,16,19],descript:[6,8,17,18,19],designer_set:2,desir:[2,6,7,17],desktop:6,destin:[2,7],detail:[2,3,6,15,17],detect:[2,4,6,17],determin:2,dev:[2,6,7,8,17],dev_at_gmail:4,deven:4,devenbansod:4,devic:7,dhananjai:4,dhima:4,dhtml:4,dhundhara:4,diagnos:6,diagram:6,dialog:[6,8,17],dialogu:6,did:[2,6],didn:2,die_error:17,dieter:4,differ:[2,4],differenti:6,difficult:6,difficulti:10,digest:6,dimension:7,dingo13_at_gmail:4,dingo:4,dinosaur:1,diprofinfin:4,dir:[6,17,19],direct:[2,6,17,19],directli:2,dirnam:17,disabl:[2,6,17,20],disable_emodifi:6,disable_funct:2,disableis:2,disablemultitablemainten:2,disableshortcutkei:2,disallow:[2,17],disappear:[2,6],discov:17,discuss:6,disorderman:4,disorderman_at_qq:4,displai:[0,1],display_error:[6,17],displayserverslist:2,distanc:1,distinct:6,distribut:[2,3,4],disturb:17,div:17,divid:7,djh1017555_at_126:4,dll:6,dnighttv_at_gmail:4,doc:[2,6,7,8,17],docker:9,doctype:[6,17],document:2,documentclass:[6,8],doe:2,doesn:2,dom:4,domain:[2,6],domainnam:17,domen:4,don:2,donavan_at_hotmail:4,donavan_martin:4,done:[0,2,6,17,20],dongl:20,dongyoung:4,donwload:17,dorning:6,dot:2,doubl:[2,6],dougla:4,douglaseccker_at_hotmail:4,dovi:4,dovyda:4,down:2,downgrad:[6,17],download:[2,3,6,10,15,16,17,18,22],dozen:20,drag:[6,8,13],draw:1,drawn:[1,19],dri:4,driven:17,driver:2,drizzl:4,drop:[2,4],dropdown:[0,6],due:[2,6,17],duguying2008_at_gmail:4,dump:[2,4],dure:[6,8,17],dutch:4,dynam:[4,6,7],each:[0,2,3],eas:[2,19],easi:[2,6,8,19],easier:[2,7,18],easiest:[6,17,20],easili:[1,2],eccker:4,echo:17,edgar:4,edgarsneims5092_at_inbox:4,edit:2,editor:[2,4,17],edjacobjunior_at_gmail:4,edlund:4,edlund_at_upright:4,edu:[4,17],eduardo:4,edward:4,edwin:4,edwin_at_yohanesedwin:4,eecyh:17,ef12:17,effect:2,effectiv:11,effici:[2,6],effort:15,efroys_at_gmail:4,egbrave_at_hotmail:4,egg:1,egorov:4,eilertsen:4,either:[2,6,8,15,16,17],ek_at_luna:4,ekio_at_gmail:4,electrotechn:7,element:[6,7,19],eliovir:4,eliovir_at_gmail:4,els:[0,2,4,6,17],elseif:17,email:[4,6,15],emb:8,embed:[2,8],emerg:17,emit:6,emphas:2,empti:2,enabl:2,enableautocompletefortablesandcolumn:2,enclos:2,enclose:6,encod:[2,4,6,17],encompass:7,encount:6,encrypt:2,encyclopedia:7,end:[0,2,6,8,17,19],endors:11,enforc:13,eng_at_globomail:4,engin:[6,7,8],engine:[6,7],english:[2,4,8],engstrom:4,enhanc:4,enlarg:6,ensur:[2,6,8,17],ensure:[2,6,17],enter:[0,2],enterexecut:2,enterpris:6,entir:[6,14,17],entri:[2,6,17],entrypoint:17,environ:[2,6,15],epel:17,equival:2,erik:[4,6],ero:4,erosakos02_at_gmail:4,error:[2,4],escap:[2,6,15],escapesr:15,eshin:4,especi:[0,2,6,17],espen:4,esperanto:4,esri:2,essam:4,est:17,estonian:4,esy_vb_at_yahoo:4,etc:[2,6,8,17,19],etienn:4,evalu:8,even:[2,3,6,8,10,12,17],event:[2,4,7,10],ever:6,everi:[2,6,7,15,17,20],everyon:4,everyth:[0,6,17],everytim:2,everywher:6,exabot:6,exact:[2,6],examin:6,exampl:0,exce:2,except:[2,6,17],exception:17,exchang:17,exclud:6,exclus:6,exectimelimit:[2,6],execut:[0,2],executor:6,exist:[2,4],exists:2,exit:17,expand:[0,2,6],expandtab:[2,17],expans:[0,2,6],expect:[2,6,8],expens:1,experi:8,experiment:2,explain:[2,6,13,17],explan:[6,17],explicit:2,explicitli:2,explod:17,exploit:15,explor:[6,15],explorer:2,export_templ:2,expos:17,exposur:17,express:[0,2,6,7],expression:7,ext:6,extens:[2,4],extensibl:7,extension:[6,7],extension_dir:6,extern:[2,15,17,22],external:[2,9],extra:17,extract:[6,17],eyal:4,f188:17,facil:2,fact:[2,6,7,17],factor:[2,9,17],fadhiil:4,fadhiilrachman_at_gmail:4,fail2ban:[15,17],fail:2,fall:13,fallback:2,fals:[2,6,17,22],famma:4,fando:6,fashion:2,fast:[6,7],fastcgi:7,fathi:4,fauveau:4,fauveau_at_globali:4,favicon:17,favorit:2,fb5b:15,fcgi:17,featur:[0,2,4,5],feedback:4,feedfetch:6,feel:6,fefc:17,feryanto:4,few:[2,6,17],fewer:2,field:1,field_:7,file:[2,3,4],file_format:7,file_get_cont:17,file_template_databas:2,file_template_serv:2,file_template_t:2,file_upload:6,fileinfo:6,filenam:[2,6,19],files:8,filesystem:[2,6],filippo:6,fill:[2,6,15,17],filter:[2,6],filterlanguag:2,find:[0,2,6,11,15,16,17],fingerprint:[15,17],finnish:4,firewal:[2,6,17],first:[2,4,6,7,8,12,13,15,17,19,20],first__second__third:2,first_timestamp:2,firstlevelnavigationitem:2,fit:[2,4,6,8],fitness:3,fix:4,fixed:6,flag:2,flemish:4,floss:4,flush:6,fly:2,focu:2,folder:2,follow:[1,2,4],font:2,fontsiz:2,foo:6,foobar:17,food:1,footer:2,footnot:10,forc:2,forcessl:2,foreign:2,foreign_db:2,foreign_kei:7,foreign_key_checks:2,foreignkeydropdownord:2,foreignkeymaxlimit:[2,6],forget:[2,6,17],forgot:6,form:[2,4],format:[2,4],format_http:17,former:4,forth:19,forum:[4,6],forward:2,forwardfor:17,found:2,foundat:3,fpdf:4,fpm:17,fragment:6,frame:[2,4,6],framework:22,francesco:4,francisco:4,franco:4,free:[3,7,10,19],freeotp:20,french:4,frequent:2,freshli:17,fri:17,frisian:4,from:[0,1,2,4],front:6,frontend:17,ftp:[2,6,17],fujifilm:4,fulanodet:4,full:[0,2],fulli:[6,17],fun:11,func_char:2,func_date:2,func_number:2,func_spatial:2,func_uuid:2,fundawang_at_gmail:4,further:[2,4,6,16],furthermor:2,futur:[2,6],future_id:6,ga244_at_is8:4,gabriel:4,gailli:7,gain:12,galician:4,gamma:6,gandon:4,gandon_at_isia:4,ganeshtheone_at_gmail:4,garvin:[3,4],gatewai:7,gc_maxlifetim:2,gd2:[7,14],gd2availabl:2,gd_graphics_librari:7,geert:4,gener:1,genuin:17,geograph:8,geometri:[6,8],georgiev:4,geospati:8,geral_at_jonil:4,german:4,get:2,get_login_credenti:[2,17],getauthorizeurl:17,getinfo:19,getmessag:17,getmimesubtyp:19,getmimetyp:19,getnam:19,ghimir:4,giacobazzi:4,giacobazzi_at_ferrania:4,gigabot:6,gilli:4,gilli_at_gmail:4,giovanni:4,girish:4,gis:6,git:[2,9],github1_at_openaliasbox:4,github:[4,6,17],give:2,given:[2,6,8,12,15,17],global:[6,10,12,16,17,19],glund_at_silversoft:4,gmail:17,gmbh:6,gnauk89_at_googlemail:4,gnu:[3,7,17],goe:[2,6],goerick:4,goldfinger:1,good:[2,6,17,18,19],goodlinuxuser_at_chmail:4,google2fa:20,googlebot:6,got:[3,8],gov:4,gpg:17,gpl:3,grab:[6,17],gracefulli:6,grai:6,grant:[2,6,12,17],graphic:[7,10,13,17],great:[4,5],greatymh_at_gmail:4,greek:4,green:8,grid:2,gridedit:2,group:[2,6,7],grow:19,gryniuk:4,guarante:2,guess:2,gui_at_webseibt:4,guia:4,guid:[9,11],guilherm:4,gupta:4,gzencod:6,gzip:[2,4],gzipdump:2,haa:4,had:[6,17],hakl:4,hamann:4,hamzah:4,han:4,hand:[6,17],handbook:17,handl:[2,6,8,10,12,17,18,19],handler:4,hard:[2,17],harden:[6,15],harder:15,harm:2,harush:4,harvest:6,hassan:4,hate:17,haugom:4,hauk:4,have:[0,2,3,4,5],haven:6,head:17,header:2,heart:6,hebrew:4,height:2,heis:6,helder:4,hello:4,help:[2,4,5],henningsen:4,her:6,here:[2,6,8,17,19],heritrix:6,hermann:4,hex:2,hexadecim:2,hhvm:6,hibern:8,hick:[3,4],hidden:2,hide:[2,17],hide_db:2,hidestructureact:2,hierarch:4,hierarchi:2,high:[2,6,8],higher:6,highlight:2,hilal94_at_gmail:4,hilal:4,him:4,hindi:4,hint:[2,6,7],hiroshi:4,hisakawa:4,histor:2,histori:[2,4,17],hitowerdigit_at_hotmail:4,hkp:17,hofman:4,hofman_at_gmail:4,hold:[2,6],hole:2,holes:1,holm:4,home:[10,12],homepag:[6,19],hope:3,hopefulli:19,hord:4,horizont:[6,7],host:[2,4],hostnam:[2,6,7,17],hotp:20,hover:[2,13],how:2,howev:[2,6,17,19],href:17,hsts:17,htaccess:[2,6,7,17],html:[2,4],htmlspecialchar:17,htpasswd:17,http:[2,3,4],http_authorization:17,http_cooki:7,http_host:[2,6],http_post_vars:6,http_user_agent:6,http_x_forwarded_for:2,httpd:6,httponli:2,https:17,httrack:6,hudsonvsm_at_gmail:4,huge:2,hugu:4,human:[4,8],hung:4,hungarian:4,hungdx_at_gmail:4,huseyn:4,huseyn_esgerov_at_mail:4,hyperfido:20,hyperlink:13,hypertext:7,hypertext_transfer_protocol:7,ia_archiv:6,ian:4,ibennetch:17,ibm:6,iccrawler:6,ichiro:6,ico:17,icon:[2,4,6,12,17,18,19],iconv:2,iconvextraparam:2,id_new:6,idea:[2,4,6,15,17,18,19],identif:17,identifi:[1,2,6,7,17],identified:17,ie6:2,iec:[7,10],ifmodul:6,ignacio:4,ignor:2,ignore:6,ignoremultisubmiterror:2,igor:4,iis:2,illustr:1,imag:[2,7,10,12,14,17],img:18,impact:[2,6,17],impli:3,implic:17,implicitli:7,implment:7,important:6,impos:2,imposs:[2,17],improv:[2,4,6,7,8,10,15,17],improve:4,improvement:4,inact:[17,19],inc:[2,6,16,17,18,22],includ:[2,3,4],include_onc:17,include_path:6,inclus:6,incom:17,incomplet:2,incorrect:6,increas:[2,6,8],increment:2,independ:[4,6,7],index:[2,4],index_:7,indic:[8,17],individu:12,indonesian:4,indrajith:4,inexperienc:12,info:[2,3,4,6],info_at_opsbielani:4,info_at_robinvandervliet:4,inform:[2,3],information_schema:2,infrastructur:17,ini:[2,6,10,14],ini_set:17,initi:[2,6,17],initialslidersst:2,inlin:[4,6,14],inline:2,innodb:2,innodb_strict_mod:6,innov:7,input:[0,2,4],insecur:17,insensit:6,insert:[0,2,4],insertrow:2,inserts:8,inside:0,insist:17,instal:2,install:9,installat:[2,9],instanc:2,instead:[2,6,8,17],instruct:[2,6],inted:2,integ:2,integr:[2,4,10,17],integrat:6,intend:2,inter:[2,7],interact:[6,7],interchang:8,interest:[6,11],interf:6,interfac:[2,4,6,7,8,12,14,16],interfer:6,interlingua:4,intern:[2,6,8,13,17,19],internat:7,internet:2,internet_information_servic:7,internet_protocol:7,internetwork:7,interoper:8,interpret:[2,6],intext:6,intitl:6,intro:2,introduc:[2,4,6],introduct:9,invalid:[2,6,17],invit:5,invok:17,involv:[6,17],ion:4,ionut:4,ios:20,ip_address:7,ipmask:2,ipv4:7,ipv6:[2,7],ironpotts_at_gmail:4,is_str:17,isa:4,isaac:17,isapi:[6,7,17],ismae:4,ismael_at_gmail:4,isn:2,iso:[7,10],isp:2,isset:17,issu:2,issue:[6,17],italian:4,item:[2,6,7],itself:[0,2,6,17],itxiaopang:4,iusr_machin:6,ivan:4,ivan_at_mail:4,ivanlanin_at_us:4,jackson:4,jafar:4,jakobsen:4,jakobsen_at_gmail:4,jakub:4,jan:[4,17],jan_at_nrw:4,janhenrikm_at_yahoo:4,janni:4,januari:17,janussen:4,japanes:4,java:[6,8],javascript:[2,4],jayaratn:4,jconstanti_at_yahoo:4,jean:7,jedermann:4,jeev:6,jhaveri:4,jim:4,joan:4,joan_at_montan:4,job:17,joe:[4,6],john1db:6,john2db:6,john_at_panevo:4,john_db:6,johnson:[3,4],join:[2,4,6],jona:4,jong:4,jongdeok:4,jonsson:4,jonsson_at_norsjovallen:4,jordi:4,jose:6,josep:4,jozef:4,jpeg:[7,14,19],jpg:7,jqplot:1,jqueri:[1,3,6,14,22],jremes_at_outlook:4,jrzancan_at_hotmail:4,json:6,juha:4,juli:17,julian:[4,7],jump:[2,6],june:4,junior:4,jur:4,just:[0,2,4],k725:4,kang:4,kanji:4,kankanamg:4,kannada:4,kasperski:6,kasun:4,katerinak_at_gmail:4,kaushalya:4,kawada:4,kawada_at_den:4,kazi:4,keck:[3,4],keep:[2,6,17],kehayov:4,kei:[2,4],kelli:4,kempf:4,kept:[2,19],kettler:4,key:6,keybas:[15,17],keyr:[15,17],keys:13,keyserv:[15,17],khomutov:4,kick:6,kid:4,kiddi:6,kidsmart33_at_gmail:4,kiko:4,kim:4,kim_at_nhn:4,kind:[2,6,7,15],kindli:6,kingdom:4,kirillov:4,kissu:4,kit:[3,17,22],klau:6,kleemann:4,klokner:4,knowledg:6,known:2,kobayashi_at_gmail:4,koch:4,koch_at_enough:4,kollar:4,kollar_at_pg:4,kollmann:4,koo:4,koolen:4,korakot:4,korakot_at_inam:4,korean:4,kosit:4,kr_at_gmail:4,kraai:4,krasimir:4,kristjan:4,kristjanrats_at_gmail:4,kristof:4,kronsbein:4,krystian4842_at_gmail:4,krystian:4,kumari:4,kunishima:4,kuppelwies:4,kurdish:4,kurt:4,kurt_at_kh:4,kushagra4296_at_gmail:4,kushagra:4,kword:8,kyeong:4,kyungjun2_at_gmail:4,label:[0,1,6,7,15],lacina:4,ladisch:4,lang:2,languagetool_at_gmail:4,lanin:4,larg:2,larger:[2,6],largest:6,lari:4,lari_at_oesch:4,lass:4,lasse_at_mydom:4,last:[2,6,19],lastpass:20,later:[0,2,8,17],latest:[2,6,7,14,17],latter:2,latvian:4,lau:4,launchpad:17,laureano:4,laureano_at_gmail:4,laurent:4,layer:[1,7,16],layout:[2,6,10,13,18],lc_messages:2,ldi_:4,lead:[0,2,6],leak:2,learn:[8,11],least:[2,6,17],leav:[2,6,17,19],lee:4,leedermeister_at_gmail:4,left:[2,4,8],legal:2,legenhausen:4,leiding:4,lem9:17,length:2,lesli:4,less:[2,6,8,17],let:2,letter:[2,6,8],lettercas:6,level:[2,6,7,8,17],lib:[6,19,22],libiconv:2,librari:[1,2,3,4],libreoffic:8,libwww:6,libxml:14,license:3,lifetim:2,lighttpd:17,lightweight:8,like:[0,2,4],likewis:6,lima:4,limburgish:4,limitchar:2,lin:4,line_count:6,link:[1,2,4,6,10,12,13,15,17,18,19],link_id:13,linklengthlimit:2,linu:17,linux:[2,9],list:[2,4],listen:17,liter:[2,6],lithuanian:4,load:[2,4],loadabl:6,loader:6,loadfil:6,loadmodul:6,local:2,localhost:2,localis:8,localnet:17,localneta:2,localnetb:2,localnetc:2,lock:[2,6],log:2,logformat:6,login:2,logincookiedeleteal:2,logincookierecal:2,logincookiestor:2,logincookievalid:2,logincookievaliditydisablewarn:2,logo:[2,4],logout:[2,16,17],logouturl:2,lolo_at_phpheaven:4,longer:[2,17],longtabl:[6,8],longtext:2,longtextdoubletextarea:2,look:[2,6,7,13,17,19],lori:4,lose:2,loss:6,lossi:7,lost:2,lot:2,loup:7,low:[2,6],lower:[2,6],lower_case_table_nam:6,lscape:8,lsml_at_liv:4,ltr:17,lubo:4,lucen:6,lui:4,luisan00_at_hotmail:4,lund:4,m42:4,m_at_gmail:4,mac:2,mac_os_x:7,machin:2,maciej:4,maciejka45_at_gmail:4,macintosh:7,macof:4,made:[2,4,6,7,17],madhura:4,madlen:4,mai:[0,2,6,7,10,17,19],mail:7,main:1,mainli:7,maintain:[4,6,17],mainten:[2,4,10],major:[6,18],make:[2,5],make_id:8,maketitl:8,malai:4,malfunct:6,malgeri_at_gmail:4,malici:15,man:2,mani:[2,4,5],manipul:[2,7,8,10,17],manish:4,manual:[2,6,7],manufactur:[7,20],manuzhai:4,map:17,marc:[3,4,6,11,17],marc_at_infomarc:[3,4],marcel:4,march:4,marco:4,marconcini:4,marek:4,mari:2,mariadb:[6,10,14,17],marin:4,mark:[2,4,7],marker:8,market:7,markt:6,markup:[7,8],mart:4,martijn:4,martin:4,martin_at_vidn:4,martin_at_whistl:4,martinelli:4,martinelli_at_gmail:4,martyna:4,masahiko:4,master:[2,11,13,17],master_db:2,mat:4,match:[2,6,8,17,18],mathemat:8,mathia:4,matiasbellon:4,matter:13,matthia:4,matthias_at_bluthardt:4,max:6,max_allowed_packet:8,max_array_index_length:6,max_execution_tim:6,max_link:6,max_request_vari:6,max_totalname_length:6,max_travers:6,max_value_length:6,max_var:6,maxcharactersindisplayedsql:2,maxdblist:2,maxexactcount:[2,6],maxexactcountview:2,maxim:[4,8],maxime_at_fre:4,maximum:[2,6],maxnavigationitem:2,maxrow:2,maxsizeforinputfield:2,maxtablelist:2,maxtableuipref:2,mayb:6,mbstring:[2,7,14],mcrypt:[6,7],me_at_derrabu:[3,4],me_at_supergarv:[3,4],mean:2,meant:2,measur:[2,7],mechan:[2,4,6,17],media:6,mediapartn:6,medios:4,meet:17,memoranda:7,memori:2,memory_limit:[2,6],memorylimit:2,memoword_at_163:4,mendax:4,mendel:[3,4],mention:[2,6,17],menu:[2,6],merchantability:3,messag:2,met:17,meta:[2,17,19],metagerbot:6,method:[2,6,7,8,13,17,19],methodolog:7,michael:[3,4],michal:[3,4,17],michal_at_cihar:[3,4],michel:4,middl:2,might:[2,6,8,11,12,15,17,22],migrat:6,mike:4,mike_at_graftonhal:4,miko:4,mileag:8,mime:[2,4],mimetyp:2,mind:17,miner:6,minh:4,minim:[6,17],minimum:[2,6],minor:[4,6],minsizeforinputfield:2,minu:0,miquel:4,mirror:[6,17],misc:1,miss:2,missing:19,mit:[3,17],mitenem_at_outlook:4,mitig:[6,15],mix:2,mj12bot:6,mkdir:6,mkkeck_at_us:[3,4],mladenov:4,mmcach:6,mmcrawler:6,mmh15_at_windowsl:4,mobil:[4,20],mod:6,mod_gzip_item_includ:6,mod_proxi:6,mod_proxy_fcgi:[7,17],mod_rewrit:6,mod_ssl:6,mode:0,moder:4,modif:6,modifi:[2,3],modul:[6,7],moham:4,mokhtari:4,molnar:4,morai:4,more:[0,1,2,3],moreov:6,moretti:4,mosh:4,most:[2,4,6,7,8,10,13,17,19,20],mostli:[2,8],motuza:4,motuzas_at_gmail:4,mount:2,mous:[2,6,13],mousewheel:6,move:2,movement:1,mrdaniloazevedo_at_gmail:4,msie:6,msnbot:6,much:[6,19],muhammad:4,mulla:4,multi:[2,4],multidimension:8,multipl:1,multipurpos:7,multiselect:6,multiserverexample70518:2,multithread:7,murariu:4,murariu_at_yahoo:4,must:[0,2,6,8,10,12,13,17,20],my_db:2,myadmin:17,mydatabas:2,mydb1:17,mydb2:17,mydb:[2,6],mydump:8,myisam:[6,7,10],mynam:0,mysql_connect:6,mysql_db_serv:17,mysql_fetch_field:19,mysql_pconnect:2,mysql_root_password:17,mysql_upgrad:6,mysqladmin:6,mysqld:6,mysqli:[2,4],mysqlminvers:2,mysqlnd:2,myung:4,n8falke_at_us:4,nabin:4,naderi:4,naderi_at_gmail:4,naeem:4,nair:4,nakandala:4,nakrani:4,name:[0,2,4],nast3zz_at_gmail:4,nasti:15,nativ:2,natur:2,naturalord:2,navarro:4,navigationdisplaylogo:2,navigationdisplayserv:2,navigationhid:2,navigationlinkwithmainpanel:2,navigationlogolink:2,navigationlogolinkwindow:2,navigationtreedbsepar:2,navigationtreedefaulttabt:2,navigationtreedefaulttabtable2:2,navigationtreedisplaydbfilterminimum:2,navigationtreedisplayitemfilterminimum:2,navigationtreeenableexpans:2,navigationtreeenablegroup:2,navigationtreepointeren:2,navigationtreeshowev:2,navigationtreeshowfunct:2,navigationtreeshowprocedur:2,navigationtreeshowt:2,navigationtreeshowview:2,navigationtreetablelevel:2,navigationtreetablesepar:[2,6],navigationwidth:2,ne0x_at_us:4,nearli:2,necessari:[2,19],need:[0,2],neg:6,neglig:8,neil:4,neimani:4,neither:[2,6],neomo:6,nepali:4,nest:[2,4],net:[2,3,4,6,7,15,17,18],netcologn:6,network:[2,6,7,8,17],never:2,nevertheless:17,newer:2,newest:2,newli:[8,17],newlin:2,newsblog:6,next:[2,6,17],nginx:14,nhibern:8,nicola:4,niel:4,niemand:4,nijel:17,niko:4,niko_at_gmail:4,nikto:6,ninad:4,nisarg:4,nisargjhaveri_at_gmail:4,nitrotoll_at_gmail:4,nix:2,nnabinn_at_hotmail:4,no1:2,no2:2,noblob:2,nobody_at_gmail:4,node:7,nofoot:8,nohead:8,nokeepal:6,non:2,none:2,nontawat39_at_gmail:4,nopassword:2,nor:6,nordenberg:4,noreply_at_webl:4,normal:[2,4,6,17,19],norwegian:4,not_nul:19,notabl:7,notat:8,note:[0,2,6,17,19,22],noticias:4,now:[0,2],nowher:2,ntfs:2,number:[0,1,2],numer:[2,6],numfavoritet:[2,6],numrecentt:2,nutchcvs:6,nyu:4,obgzip:[2,6],object:[6,8,17,19],obrador:4,obtain:[2,15],occur:[2,6,17],oesch:4,off:[2,6],offer:[1,2,6,7,13,16,17],offic:7,office:8,offici:[6,11,17,19],oficial_at_gmail:4,often:[2,6,8,17],okai:17,old:[2,6,11,17],older:2,olivier:[3,4],olof:4,om_at_omni:[3,4],omar:4,omar_2412_at_l:4,omit:2,omniexplorer_bot:6,onc:2,once:[6,12,13,17,20],ondra:4,ondrasek:4,one:6,onli:[1,2,4],onlin:6,only:[2,6,13],only_db:2,open:[2,5,6,7],open_basedir:2,opendocu:7,opengis:4,openid:17,openid_messag:17,openid_relyingparti:17,openlay:6,openoffic:8,openssl:[14,17],oper:[1,2],operat:[6,8,17],opposit:2,opt:20,optim:[2,4],optimiz:6,option:1,optional:[2,8,17],order:2,ordinari:17,org:[2,3,4,6,7,8,17],organ:7,organis:7,orient:[2,6,7],origin:2,orion1979_at_yandex:4,orzkun_at_ageag:4,os_x:7,other:[2,4,5,6,7,8,9,10],otherwis:[0,2,6],our:[2,5,6,15,17,18,19],out:[2,5],output:[2,4,6,8,19],output_compress:6,outsid:[2,19],outsourc:4,over:[2,6,7,12,13,17],overcom:6,overlord666_at_gmail:4,overrid:[6,17],overview:[6,19],overwhelm:12,overwrit:2,overwritten:2,own:2,owner:[2,17],owneremail:6,ownerfirstnam:6,ownerlastnam:6,ownerphon:6,ownerphone1:6,ownerphone2:6,ownership:6,pack:6,packag:[2,6,9,17],packagist:17,packet:[7,8],page:0,pai:17,pair:[2,17],palid:4,palider_at_seznam:4,palstsiuk:4,pan:6,pandei:4,pandithawatta:4,pantola:4,papaz_p_at_yahoo:4,paper:[2,6],paragraph:6,param:[2,17],paramet:2,parent:[6,17],parind:8,parkourpotex_at_gmail:4,parsabl:8,parse_url:6,parser:[4,8],part:[2,6,7],partial:2,particular:[3,6,7,12,17],particularli:17,pass:[2,6,10,12,17,18,19],passhosthead:17,passphras:2,passwd:17,password:2,past:2,patch:[2,4],path:2,path_dir:17,path_to_phpmyadmin:6,path_to_your_phpmyadmin_directori:2,pathprefixstrip:17,patrik:4,pattern:2,paul:6,paulei:4,pavel:4,pbms:4,pcre:[2,7],pdf_page:[2,6],pdfdefaultpages:2,pdfpageheight:8,pdfpages:2,pdfpagewidth:8,pdo:8,pear:[4,7,17],pebbl:20,peccatt:4,pedro:4,peer:2,pem:2,pencil:12,peopl:[4,5,6,10,22],per:[2,6,7],perekupka:4,perfect:[2,17],perform:[2,6,10,12,14,15,17],perhap:17,period:17,perkontevs_at_gmail:4,perl:[6,7],permiss:[2,6,12,17],permit:[2,12],persian:4,persist:[2,16],persiste1_at_gmail:4,persistentconnect:2,person:[2,6,7,17],person_nam:6,petbre:6,petdob:6,pete:4,peter:4,petnam:6,petr:4,pettyp:6,pgp:[15,17],ph3n1x:4,phan:4,phillip:4,phmyadmin:17,phone:20,photograph:7,php3:4,php4:[4,6],php5_modul:6,php5apache2_2:6,php5t:6,php:[2,4],php_mysqli:6,php_self:[6,17],phpinfo:[2,6],phpinidir:6,phpmy:6,phpmyadmin:[0,1,2,3,4,5],phpmyadmin_at_zweisteinsoft:4,phpmyadmin_x:17,phpmyadminovi:11,phpmyadnin:18,phpmysqlformgen:4,phpseclib:4,phpwizard:[4,6],piankov:4,pick:6,piec:15,pikto:6,piller:4,pin:2,pink:6,piotr:4,pipe:2,pistej2_at_gmail:4,pistej:4,place:[2,6,17],placehold:0,placella:4,placement:6,plai:17,plain:[2,6,17,19],plamen_mbx_at_yahoo:4,plan:8,platform:[2,6,7],plathei:4,pleas:[2,6,7,8,15,17,19,22],plesk:2,plot:6,plu:[4,6],plug:19,plugin:[4,6,8,19],pma:[2,17],pma__bookmark:2,pma__central_column:2,pma__column_com:2,pma__column_info:2,pma__designer_set:2,pma__export_templ:2,pma__favorit:2,pma__histori:2,pma__navigationhid:2,pma__pdf_pag:2,pma__rec:2,pma__rel:[2,6],pma__savedsearch:2,pma__table_coord:2,pma__table_info:[2,6],pma__table_uipref:2,pma__track:2,pma__us:2,pma__userconfig:2,pma__usergroup:2,pma_absolute_uri:17,pma_arbitrary:17,pma_at_sebastianmendel:4,pma_combin:6,pma_db:17,pma_dbi:4,pma_host:17,pma_hosts:17,pma_password:[6,17],pma_port:17,pma_ports:17,pma_single_signon_cfgupd:17,pma_single_signon_error_messag:17,pma_single_signon_host:17,pma_single_signon_messag:17,pma_single_signon_password:17,pma_single_signon_port:17,pma_single_signon_us:17,pma_user:17,pma_usernam:6,pma_verbose:17,pma_verboses:17,pmaabsoluteuri:[2,6,17],pmadb:2,pmahomm:[2,18],pmanorelation_disablewarn:2,pmapass:[2,17],pmasa:[6,15],pmatest:2,png:18,point:[2,6,7,17],polici:2,polish:4,popcorn:4,popul:8,popular:[7,8],port:2,port_:7,portabl:7,portable_document_format:7,portion:2,portnumb:2,portugues:4,pose:15,posit:[6,7],possibl:[2,5],post:[2,6,17],post_max_s:6,potenti:2,power:8,pozzato:4,practic:2,pragmarx:20,pratap:4,pre:2,preappend:2,precedessor:7,preconfigur:8,predatorix_at_web:4,predefin:10,prefer:[2,4],prefix:[2,6],preform:15,prejudice:1,preliminari:8,prepar:[2,7,15,17],prepend:19,preprocessor:7,present:[2,6,8,12,15,17],press:2,pretti:4,prevent:2,previou:[2,6,17],price:8,primari:[2,4],primary:[6,7,13],print:4,printer:4,printview:[2,4],prior:17,privaci:17,privat:[2,6,20],privileg:[2,4],privileges:17,probabl:[6,17,22],problem:2,problemat:6,procedur:2,processor:8,procs_priv:6,produc:2,product:[6,8,17,20],profession:7,program:[2,3,6,7,8,15],prohibit:2,project:[2,4],prompt:[2,17],proper:[2,6,8,10,13,17,19],properli:[2,6,13,17],properti:[2,4,6,19],propertiesnumcolumn:2,propos:[6,10],protect:2,protectbinari:2,protocol:2,provid:[2,6,7,10,12,17,19,20,22],provok:15,proxi:2,proxypass:[2,6],proxypassrevers:6,proxypassreversecookiedomain:6,proxypassreversecookiepath:6,proxyurl:2,proxyus:2,pruett:4,przemo:4,przybylski:4,psbot0:6,pub:15,publi_at_web:4,publicli:2,publish:[2,3,15,17],pull:[2,17],pundalik:4,punjabi:4,purodha:4,purpos:[2,7,8,15,20],purpose:3,put:2,python:6,qbe:[4,10],qiang:4,qualifi:17,qualiti:8,queri:[0,1],queryhistorydb:2,queryhistorymax:2,querystr:17,quick:[2,6,7,9],quickli:8,quit:[2,4,6,18,20],quot:6,qyz:4,r9uk:17,ra4_at_openmailbox:4,rachim:4,rachman:4,radio:[6,7],rafael:4,raghuram:4,rai:4,raj:4,rajandran:4,random:[2,4,15],rang:2,raouf:4,rare:6,rate:15,rather:[2,6,8,12,17],ratio:14,ratschil:[3,4],raul:4,raul_at_wservic:4,ray_at_datahui:4,reach:[17,19],read:[2,6,8,10,17],readabl:[6,8],readi:17,readme:17,real:[2,6,13],real_password:17,real_us:17,realli:[2,6,17,20],realm:[2,17],rearrang:6,reason:[2,6,14],recal:2,recaptcha:[2,6,14],receiv:3,recent:[2,6,7,8],recod:2,recode_str:2,recodingengin:2,recommend:[2,6,7,8,14,17],record:[2,7,8,12,13],recov:6,recreat:6,recv:17,redesign:4,redhat:6,redirect:2,redistribut:[3,22],refactor:4,refer:[2,6,7,13],referenti:[2,10],reflect:7,refman:[2,6,7,8],refresh:2,regard:[2,10,19],regardless:17,regener:15,regex:17,regul:8,regular:[2,4,7],regullar:2,reject:2,rel:[2,6,17],rel_countri:6,rel_person:6,rel_town:6,relat:[0,2,4],relationaldisplai:2,releas:[2,4,6,9,15],relev:2,reli:17,reliabl:[2,6,8,17],relyingparti:17,relyingparty_result:17,remain:[0,2],reme:4,rememb:[0,2,17],remembersort:2,remot:[2,7,17],remote_addr:2,remote_user:6,remov:[2,4,6,10,12,17,18],renam:2,rename:2,renato:4,renatomdd_at_yahoo:4,render:[8,15],rental_r:1,reorder:6,repeat:[2,6],repeatcel:2,replac:[0,2,6,17,18,22],replace:2,replacement_cost:1,repli:17,replic:[4,6],report:[2,4,6,9],repositori:[3,7,17,20],repres:[2,6,7],represent:4,reproduc:6,reqirep:17,request:2,request_for_com:7,request_method:6,request_uri:[6,17],requir:2,resav:17,research:7,reserv:2,reservedworddisablewarn:2,reset:6,resid:6,resiz:6,resolut:7,resolv:6,resourc:7,respect:[3,6],respond:6,respons:[6,7],restart:[6,17],restrict:2,result:[0,1,2],retainquerybox:2,retriev:[2,17],return_to:17,returnto:17,revenu:1,revers:2,review:17,revis:[2,6,7],revistafammatvmus:4,revok:2,rewrit:[6,17],rewritebas:6,rewritecond:6,rewriteengin:6,rewriterul:6,rewrot:4,rex:4,rfc1867:6,rfc2616_header:6,rfc:[6,7,10],ribeiro:4,ribeiro_at_gmail:4,ricardo:4,rifthi:4,rifthy456_at_gmail:4,right:2,risk:[2,6],rob:6,robbat2_at_us:[3,4],robin:[3,4],rocha:4,rocha_at_zoho:4,rodrigo:4,rodrigu:4,rohled:4,rohmberg:4,rohmberger_at_hotmail:4,romanian:4,ronni:4,ronniesimonf_at_gmail:4,roohan:4,roohan_cena_at_yahoo:4,root:2,roszatycki:4,rouslan:4,rout:[2,17],router:17,routin:[2,4],row:[1,2,4],row_:7,row_format:6,rowactionlink:2,rowactionlinkswithoutuniqu:2,rowactiontyp:2,rsa:17,rubinov:4,ruiz:4,rule:[2,6,17,19],ruleant_at_us:4,run:[0,2],russian:4,rutkowski:4,rwx:[2,6],saad:4,safe:2,sai:0,sailboat:4,sakamoto:4,sake:6,sakila:6,saleh:4,salvadoporjc_at_gmail:4,sam_at_gmail:4,same:[0,1,2],sampl:[2,6,8,17],samyoul:20,sander:4,sandro123iv_at_gmail:4,sandro:4,sanit:6,santana:4,santana_at_gmail:4,sapi:2,sarna:4,sascha:4,save_path:6,savecellsatonc:2,savedir:[2,8],savedsearch:2,saverio:4,saw:6,sbin:17,scale:6,scan:[6,20],scanner:6,scenario:[6,17],schaefer:4,schema:[2,4],scheme:[2,6],schneier:7,scientif:8,scooter:6,scope:17,scorpio_at_gmail:4,scp:6,scratchboard:[4,6],screen:[8,17,18],screenshot:18,script:[2,4],scriptalici:8,search:[2,4],searchabl:17,sebastian:[3,4],sebastian_at_sgundersen:4,secion:15,second:[2,6,12,19,20],secret:2,section:[1,2,5,6,17,19],secur:2,secure_cooki:17,secure_sockets_lay:7,securer:4,see:[0,2,3,4],seealso:7,seekbot:6,seem:2,seen:2,segment:2,seibt:4,select:[0,1,2,4],selector:2,selenium:4,self:2,send:2,senderrorreport:2,sens:19,sensi:6,sensit:[0,2,6,15],sent:[2,4],seo:6,seoma:6,seongki:4,seosearch:6,separ:[2,4,6,7,8,17],separated_valu:7,sequenc:7,serbian:4,seri:1,serial:8,seriou:6,serv:[7,17],server_:7,server_address:2,server_at_gmail:4,server_name:17,server_port:17,serverdefault:2,serverlibrarydifference_disablewarn:2,servic:[2,6,7,17],session:[2,6,14,16,17],session_get_cookie_param:2,session_id:17,session_nam:17,session_save_path:[2,17],session_set_cookie_param:[2,17],session_start:17,session_write_clos:17,sessionsavepath:[2,17],sessiontimezon:2,set:1,set_magic_quotes_runtim:17,setenvif:[6,17],setfacl:2,setia:4,setia_at_gmail:4,setinputfilt:6,setlength:8,setoutputfilt:6,setup_config_file:22,setup_dir_writable:22,sevdimali:4,sevdimaliisayev_at_mail:4,sever:[2,3,6,7,8,13,16,17,19,20,22],sevillano:4,seward:7,shahrabani:4,shall:13,shameem:4,shapefil:2,share:[2,17],sharifov_at_programm:4,she:6,sheet:8,shell:6,sherbrook:6,shift:[2,6],shin:4,ship:[7,17,18,22],shirian:4,shirian_at_gmail:4,shortcut:[2,6,9],shorten:6,shorter:[2,6],should:[2,3,4],shouldn:17,show:[2,6,15,17],show_pag:17,showall:2,showasphp:2,showbrowsecom:2,showchgpassword:2,showcolumncom:2,showcreatedb:2,showdatabasesnavigationastre:2,showdbstructurecr:2,showdbstructurelastcheck:2,showdbstructurelastupd:2,showfieldtypesindataeditview:2,showfunctionfield:2,showgitrevis:2,showhint:2,shown:[1,2],showphpinfo:2,showpropertycom:2,showserverinfo:2,showsql:2,showstat:2,shp:6,shutdown:6,side:[2,7,17],sign:[2,17],signatur:17,signific:4,significantli:8,signoncookieparam:[2,17],signonscript:[2,17],signonsess:[2,17],signonurl:[2,17],silent:6,silva:4,simecek_at_gmail:4,similar:[2,6,8,13,15],similarli:6,simon:4,simoncini:6,simpl:[1,2],simple2fa:[2,20],simpli:2,simplifi:[4,12],sinc:[1,2,4,6,8,14,17,20],singh:4,singl:2,sinhala:4,site:[2,6,11],sitesearch:6,situat:[6,7,17],siu:4,size:2,skill:6,skip:[17,22],skiplockedt:2,skshin_at_gmail:4,slash:2,slider:2,slovak:4,slovenian:4,slow:2,slowdown:6,slurp:6,small:[2,6],smaller:[2,4,6],smart:[2,4],smarte_at_gmail:4,smita:4,smooth:1,smtp:4,snap:2,snappi:6,snapshot:2,snippet:2,socket:2,softwar:[3,6,7,8,10,17,20,22],software_extens:7,solut:[6,17,20],solv:6,some:[2,4],someth:[2,6,13,15,17],somewher:[6,15],somthanat:4,somthanat_at_gmail:4,song:4,soom:4,soon:0,sora:4,sora_at_tiscali:4,sorani:4,sort:[2,6,7,8],soulard:4,sourc:[2,4,5,6,7,9],sourceforg:[3,4],souza:4,space:[0,2,6],spaces:0,spanish:4,speadsheet:8,special:[0,2],specif:2,specifi:[1,2,4],speed:[2,6,8,17],spl:14,split:[2,6,8,19],sponsor:6,spreadsheet:6,sql:[0,1],sqllog:2,sqlqueri:2,sqrt_at_entless:4,src:[6,17],srnka:4,ssl_ca:[2,17],ssl_ca_path:[2,17],ssl_cert:[2,17],ssl_cipher:[2,17],ssl_kei:[2,17],ssl_verifi:[2,17],ssloption:6,sta:4,stabl:6,stack:1,standard:[2,4,6,7,8,14,20],star:[2,6],start:[1,2,4],starthistori:2,startup:[2,6],state:2,stateless:15,statement:[2,6,10,17],station:6,statist:[2,4],statu:[2,4,6],status:2,stdenvvar:6,stdpass:2,stduser:2,ste:4,stefan:4,stefano:4,step:[6,17],steve:4,steven:4,still:[2,12,15,17],stock:17,stokkan:4,stop:[2,6],storag:[0,2,6,7,9,13,15,16],stored_procedur:7,straka3_at_gmail:4,straka:4,strftime:6,strict:2,strijbol:4,string:[0,2,4],strip:[0,2],strlen:17,strongli:[2,14],stuff:4,stuffit:17,style:[2,6,17,18],sub:[6,15],subdirectori:2,subfold:[6,17],subject:[2,6],sublevel:2,submiss:2,submit:[2,6,17],subpackag:[2,17],subroutin:7,subset:10,substitut:6,substr:2,subtyp:19,success:[2,6,15,17],successfulli:6,suffic:17,suffici:6,suffix:[2,17],suggest:[4,6,14,15,17],suhardi:4,suhosin:2,suhosindisablewarn:[2,6],suit:[6,7,17],suitabl:8,sum:6,summer:4,sun:4,superus:[12,17],suppli:[2,17],support:[1,2,4],suppos:6,suppress:2,supun:4,surbakti:4,sure:2,surfac:15,surround:6,svalekja:4,svec:4,sven:4,swedish:4,symbol:2,symlinksifownermatch:6,symmetr:7,sync:4,synchron:4,synoobot:6,syntax:[2,6],sysadmin:[2,6],syslog:[2,17],system:[2,4,6,7,8,16,17,18,22],szsilva_at_gmail:4,t10:2,t3if_at_ladisch:4,tabbrows:6,table:[2,6,7],table_:7,table_coord:[2,6,13],table_info:2,table_nam:6,table_uipref:2,tablenam:6,tablenavigationlinksmod:2,tableprimarykeyord:2,tables:6,tables_priv:6,tablesepar:6,tablettws_at_gmail:4,tabsmod:2,taceloski:4,tag:6,taiwan:4,take:[1,2,6,10,19],taken:6,talk:17,tamil:4,tamsjadi:4,tape:7,tar:[7,17],tar_:7,tarbal:17,target:6,tarzq28_at_gmail:4,task:10,tbl_chang:4,tbl_creat:4,tbl_dump:6,tbl_row_delet:6,tbl_select:4,tbl_structur:19,tcp:[2,6,7],tcpdf:[4,6,7],tczzjin_at_gmail:4,team:[2,15,17],technolog:7,tecnick:4,tecnickcom:6,telekom:6,tell:[2,6],telnet:6,tempdir:[2,6,17],templat:[2,17],template:19,template_abstract:19,temporari:[2,6,17],temporarili:[2,6,16],temporary:6,term:3,terri:4,test:[2,4,6,17,20,22],tex:[6,7,8],text:0,textarea:2,textareaautoselect:2,textareacol:2,textarearow:2,textbox:12,textfield:19,textimagelink:2,textlink:2,textual:[6,8],thai:4,than:[2,4],thank:[4,6],thei:[2,4],them:2,themedefault:[2,18],thememanag:[2,18],themenam:2,themeperserv:2,themselv:2,therefor:2,thi:[0,1,2,3,4],thiago:4,thilanka:4,thilina:4,thing:[2,6,15],think:[6,15],third:2,thirteen:4,thoma:[4,7],those:2,though:[2,7,8,15,17],three:[2,6,17,19],threshold:2,through:2,thu:[6,8,17],thumbnail:14,tickbox:6,tild:6,till:[6,17],time:[1,2],timeout:2,timestamp:2,tinyint:6,tip:[2,6],titl:1,titledatabas:2,titledefault:2,titleserv:2,titlet:2,tkl:6,tmp:2,tobia:[3,4],tobias_at_ratschil:[3,4],togeth:2,toggl:[2,6,10],token:[15,20],tokyo:6,told:4,tom:4,toma:4,tomas_at_tomasruud:4,tomastik:4,tommi:4,tommy_at_surbakti:4,ton:2,too:[2,5,6,8],tool:[2,4,5],tooltip:[6,13],top:[2,6,8,16,17],topic:17,toplevel:2,tor:4,total:17,totp:20,town:6,town_cod:6,track:[2,4,10,17],tracker:[2,6,15],tracking_add_drop_databas:2,tracking_add_drop_t:2,tracking_add_drop_view:2,tracking_default_stat:2,tracking_version_auto_cr:2,tradition:17,traefik:17,traffic:2,trail:2,transfer:[2,6,7,17],transformation_opt:[2,19],transformation_overview:19,transformations_generator_main_class:19,transformations_generator_plugin:19,transformationsplugin:19,transit:17,translat:2,translationwarningthreshold:2,translit:2,transliter:2,transmiss:[7,8],transmit:[6,20],travel:[1,17],treat:[2,6,7],tree:[2,4],trend:1,trezor:20,tri:[2,6,15,17],trick:15,trigger:[2,4,7,8,10],trinh:4,trinhminhbao_at_gmail:4,triwidada:4,troubl:2,troubleshoot:6,truli:17,truncate:2,trust:[2,17],trustedproxi:2,tschopp:4,tune:17,tunnel213:4,tunnel213_at_aliyun:4,tupl:7,turck:6,turek:[3,4],turkish:4,turn:[2,6],turnitinbot:6,tutori:6,tweak:17,two:2,tws:4,txt:[3,6,17],type:[0,1,2,4],typeset:[7,8],typic:[2,6,7,9],typograph:8,tyron:4,ubuntu:2,ufpdf:4,uid:15,ukko:4,ukrainian:4,ultim:17,ultimat:6,unavail:2,uncheck:[2,6],unclean:6,uncom:2,uncomment:17,under:[2,3],underli:[1,17,18],undernetangel_at_gmail:4,undocu:8,unexpect:[0,2],ungureanu:4,uniform:7,unintend:2,union:[1,6],uniqu:[2,6,7,13],unite:4,unix_domain_socket:7,unless:[6,17,20],unlike:17,unlucky_at_inbox:4,unpack:[6,17],unprivileg:2,unset:[6,17],unsign:19,unstuff:17,unsupport:6,untar:17,until:[2,4,6,16,17],unus:6,unwil:6,unzip:17,updat:[2,4,6,17],update:[2,6,17],upgrad:2,upgrade:[6,9],upgrade_column_info_4_3_0:[2,17],upgrade_tables_4_7_0:17,upgrade_tables_mysql_4_1_2:17,upload_max_files:6,upload_progress:6,upload_tmp_dir:6,uploaddir:[2,6,8],uploadprogress:6,upon:[1,16],upper:6,upto:6,urbalazs_at_gmail:4,uri:13,url:2,urltrend:6,uro:4,usabl:6,usag:2,usage:12,use:[2,4],use_backend:17,use_cooki:17,used:[8,17],usedbsearch:2,useless:6,usepackag:[6,8],user:[2,4],user_bas:17,userconfig:2,userdata:15,usergroup:[2,12],userid:6,usernam:2,userprefsdevelopertab:2,userprefsdisallow:2,userstatu:6,using:[1,2],usr:17,usual:[2,6,7,17,19],usualli:[6,19],utf8mb4_general_ci:2,utf:[2,6,7,17],util:[2,6,7,15,17],vadap:4,vainauska:4,valentin:4,valia:4,valid:[0,2,4,6,8,10,15,16,17],valter:4,valu:2,values:6,van:4,varchar:2,varfilt:6,variable1:0,variable:[0,6],variables:6,vazquez:4,vector:8,vendor:[2,3,6,22],vendor_config:22,verb:6,verbos:[2,6,17],verbose:6,verfic:2,veri:[6,8,17],verif:[2,6,17],verifi:[2,6,9],verschuer:4,verschuere_at_outlook:4,version:[1,2,3],version_check:2,versioncheck:2,vertic:[4,7],vetoffic:6,via:2,victor:4,vidner:4,vietnames:4,view:[2,4],viktar:4,viliu:4,villanueva:4,vim:[2,17],vinai:4,vincent:4,vinipitta_at_gmail:4,vip_at_krasio:4,vipals_at_gmail:4,virusyoon_at_gmail:4,visibl:[2,17],visit:17,visok:4,visokereyal_at_gmail:4,visual:[2,4,6],visualis:6,vitalii:4,vliet:4,vmta_at_yahoo:4,vonflyne:4,vonflynee_at_gmail:4,voogt:4,voogt_at_hccnet:4,voyag:6,vperekupka_at_gmail:4,vserver:[2,6],vulner:[2,6,9],vytauta:4,w3c:[6,7],w3c_valid:6,wai:[0,2,4],wait:6,wallet:20,walton:4,walton_at_nordicdm:4,want:2,warn:2,warning:17,warranti:3,warranty:3,washington:4,washingtonbruno_at_msn:4,wasn:4,wasser:6,waw:4,weaker:2,web_brows:7,webadmin:4,webapp:17,webcrawl:6,weblate_at_gmail:4,webmaster_at_trafficg:4,webserv:[2,6,7,8,17],websit:[4,5,6,7,15,18],wei:4,welcom:2,well:[2,4,6,10,15,17],weng:4,wengshiyu_at_gmail:4,wennberg:4,were:[2,4,6],west:4,wget:6,what:[0,2],when:[0,2,4],whenev:[2,6,17],where:[0,2,3],whether:[2,12,17,22],whh:4,whhlcj_at_126:4,which:[0,1,2,3],whitelist:6,who:[2,4],whole:[0,2,6,15,17],whose:[6,17],why:[0,2],wide:[7,8,16,20],width:2,wiegger:4,wigginton:4,wiki:[6,7,8],wikipedia:[7,8,15,17],wildcard:[2,6],wilk:4,wilson:4,win2k:6,win98:6,window:[1,2,4],winhttp:6,winningham:4,winnt4:6,wisenutbot:6,wish:[2,7,8,12,17],withdrawn:4,within:[2,6,12],without:[2,3],witten:4,wizard:[6,17],wkito:6,won:[0,2],word:[2,6],wordfenc:6,work:[2,4],workaround:[6,17],workbook:8,world:[2,7],worldwideski:4,worldwideskier_at_yahoo:4,wors:6,would:2,wrap:2,wrapper:17,writabl:[2,22],write:2,written:[2,5,6,10],wrong:2,wrongli:6,www:[2,3,4,6,8,17,18,19],wysiwyg:4,xampp:17,xavier:4,xhtml1:4,xml:[4,7],xosecalvo_at_gmail:4,xs910203_at_gmail:4,xuan:4,xvnavarro_at_gmail:4,xxx:[2,6,19],xxxx:2,xzvf:17,yacybot:6,yahoo:6,yahooseek:6,yan:4,yansilvagabriel_at_gmail:4,yaron:4,yaron_at_gmail:4,yashodha:4,yasir:4,yasitha:4,yavuz:4,yearmad:8,yellow:6,yet:[2,8],yetdiffer:2,yizhou:4,yml:17,yogarajah:4,yogeshwar:4,yohan:4,yong:4,yoon:4,you:[0,1,2,3,4,5,6,7,8,10,11,12,13,14,15,16,17,18,19,20,22],youbico:20,youngmin:4,youngminz:4,your:[0,2,6,8,9,10,12,13,14,15,16],your_db_host:17,your_theme_nam:18,yourpassword:2,yswy_at_hotmail:4,yug:4,yugal:4,yukihiro:4,yuval:4,yyi:2,yyyi:2,zahra:4,zancan:4,zarubin:4,zassenhau:4,zassenhaus_at_jgerman:4,zend:6,zero:[2,8,16],zeroconf:[2,17],zerofil:19,zheng:4,zhyarabdulla94_at_gmail:4,zigmanta:4,zion_at_gmail:4,zip:[2,4,6,7,8,10,14,17],zip_:7,zipdump:2,zlib:[6,7,10],zone:2,zoom:4,zrng:4,zufar:4,zzz:2,zzzz:2},titles:["Bookmarks","Charts","Configuration","Copyright","Credits","Developers Information","FAQ - Frequently Asked Questions","Glossary","Import and export","Welcome to phpMyAdmin’s documentation!","Introduction","Other sources of information","User management","Relations","Requirements","Security policy","Configuring phpMyAdmin","Installation","Custom Themes","Transformations","Two-factor authentication","User Guide","Distributing and packaging phpMyAdmin"],titleterms:{"17a":6,"2fa":20,"5b4":6,"\u010deski":11,"\u0440\u0443\u0441\u0441\u043a\u0438\u0439":11,"default":[2,6],"export":[2,6,8],"function":6,"import":[2,6,8],"int":6,"new":[6,12],"null":6,"return":6,"try":6,"while":6,abl:6,about:[6,10],access:6,action:6,add:6,addit:6,address:2,adjust:6,after:[6,17],again:6,against:6,alert:6,allow:6,alreadi:6,alwai:6,ani:6,ansi:6,apach:6,applic:6,applicat:20,appropri:6,area:6,arrai:8,ascii:6,ask:6,asked:6,assign:12,attack:[6,15],auth:6,authent:[2,6,17,20],auto:6,autologin:2,automat:6,back:[6,17],backup:6,bar:[1,6],base:6,basic:2,becaus:6,behavior:6,behind:17,below:6,big:6,bind:6,blank:6,blob:6,book:11,bookmark:[0,6],box:[2,6],bring:6,brows:[0,2,6,17],browser:[6,14],brute:[6,15],bug:6,bundl:6,can:6,cannot:6,caus:6,central:6,certain:6,cgi:6,chang:6,charact:6,chart:[1,6],checkbox:6,chronolog:4,click:6,clickabl:6,client:6,cloud:2,cluster:6,codegen:8,column:[1,6,8,17],come:6,comment:6,complet:6,compos:17,compress:6,config:17,configur:[2,6,12,16,17,18],connect:[2,6,17],consid:6,consol:2,contain:6,content:6,control:6,cooki:[2,6,17],copi:6,copyright:3,correct:6,could:6,crash:6,creat:[6,12,17,18],credit:4,cross:15,cryptic:6,csrf:15,csv:[6,8],curiou:6,custom:[2,6,17,18],czech:11,data:[6,8],databas:[2,6,12,14,17],db_structur:6,debian:17,decim:6,delet:[6,12],deni:6,deriv:17,design:[2,6,13],determin:6,develop:[2,5,6],differ:6,directli:6,directori:2,disclosur:6,disk:6,displai:[2,6],distribut:[6,17,22],docker:17,document:[4,6,8,9],doe:6,doesn:6,don:6,dot:6,down:6,drop:6,dump:6,duplic:6,each:6,easili:6,edit:6,edite:[2,12],effect:6,employe:6,empti:6,enabl:6,encrypt:6,english:11,enter:6,enterpris:17,environ:17,error:6,errorcod:6,esri:[6,8],evil:6,exampl:[1,2,6,13,17],excel:[6,8],execut:6,exist:[6,12],explorer:6,extend:6,extens:6,external:22,face:6,factor:20,fail:6,faq:6,favorit:6,featur:[6,10],fedora:17,fido:20,field:[2,6],file:[6,8,17,19],fine:6,firefox:6,firstnam:6,fix:6,folder:6,follow:6,font:6,forc:[6,15],foreign:6,forgeri:15,form:6,format:6,formula:6,forward:6,found:6,frequent:6,from:[6,17],full:6,gener:[2,6],gentoo:17,german:6,get:6,git:17,give:6,glossari:7,gone:6,googl:2,group:12,guid:21,gzip:6,happen:6,haproxi:17,hardwar:20,hash:6,hat:17,have:6,header:6,heard:6,help:6,hierarchi:6,homonym:6,host:6,how:6,html:6,http:[6,17],huge:6,hundr:6,hyphen:6,iis:6,imag:18,implement:1,improve:6,includ:6,increment:6,index:6,indice:9,info:13,inform:[6,11],informat:5,inject:[6,15],innodb:6,input:6,insert:6,insid:0,instal:[6,17],install:17,installat:17,intend:6,internal:6,internet:6,introduct:[10,19],isp:6,issu:[6,15,17],javascript:6,json:8,just:6,kei:[6,10,20],know:6,known:[6,17],konqueror:6,lang:6,languag:[2,6],larg:6,lastnam:6,latex:[6,8],length:6,let:6,librari:[6,22],licens:3,like:6,limit:[2,6],line:1,linux:17,list:6,load:[6,8],local:6,localhost:6,locat:6,log:[6,17],login:6,lose:6,lost:6,lot:6,lowercas:6,lump:6,mac:6,machin:6,main:2,make:6,manag:[2,12],mandriva:17,mani:6,manner:6,manual:17,mean:6,mediawiki:8,memori:6,menu:12,messag:6,metadata:18,microsoft:[6,8],mime:6,mimetyp:6,mine:6,misbehav:6,miss:6,mod_gzip:6,mode:[2,6,17],modifi:6,more:6,move:6,mozilla:6,multi:6,multipl:[2,6],mysql:[2,6],mysqldump:6,mysqli:6,name:[6,8],nativ:6,navig:[2,6],need:6,nest:6,netscap:6,never:6,newer:6,nick:6,non:6,noth:6,now:6,number:6,ods:8,older:[6,17],onc:6,onli:6,onto:6,open:8,open_basedir:6,opendocu:8,opensuse:17,oper:6,option:[2,6,8],order:4,origin:4,other:11,out:[6,17],own:6,packag:22,page:[2,6],panel:2,paramet:6,parameter:6,pars:6,parti:3,password:6,path:6,pdf:[2,6,8],php:[6,8,14],phpmyadmin:[6,9,16,17,22],pie:1,plenti:6,pmadb:6,polici:[6,15],port:6,possibl:6,potenti:6,pr1:6,pre:6,prefer:6,prevent:6,primari:6,print:11,privileg:[6,12,17],problem:6,proce:6,procedur:6,process:6,produc:6,progress:6,project:6,protect:6,protocol:6,proxi:6,put:6,pws:6,queri:[2,6],question:6,quick:17,rang:6,reassign:6,receiv:6,red:17,redirect:6,reduc:6,refresh:6,refus:6,relat:[6,13],relationship:6,releas:17,reload:6,renam:6,report:15,request:[6,15],requir:[6,14],restor:6,restrict:6,result:6,revers:6,right:6,robot:6,root:6,row:6,run:[6,17],russian:11,safari:6,safe:6,sai:6,same:6,save:2,scatter:1,schema:6,script:[6,15,17],scroll:6,search:6,secur:[6,15,17,20],see:6,seem:6,select:6,send:6,sent:6,seri:6,server:[2,6,14,17],set:[2,6],setup:[2,6,17],shape:8,shapefil:6,share:18,shortcut:10,should:6,shown:6,signon:[2,17],simpl:[6,20],simpli:6,singl:6,site:15,size:6,smith:6,sock:6,socket:6,some:6,sometim:6,sourc:11,special:6,specif:[6,12,17],specifi:6,spline:1,spreadsheet:8,sql:[2,6,8,15],sql_mode:6,ssl:[2,17],start:6,statist:6,storag:17,store:[0,6],string:6,structur:[2,6,19],subdirectori:17,suhosin:6,support:[6,10],sure:6,symbol:6,synchron:6,tab:[2,6],tabl:[0,6,8,9],technic:13,texi:8,text:[2,6,8],than:6,thei:6,them:6,theme:[2,6,18],thi:6,third:3,those:6,through:6,time:6,timelin:1,timeout:6,titl:2,tmp:6,togeth:6,tool:6,transform:[2,6,19],translat:[4,6],troubl:[6,17],tutori:11,two:[6,20],type:6,typic:15,u2f:20,ubuntu:17,umlaut:6,unabl:[6,17],undefin:6,under:6,underscor:6,understand:6,unicod:6,unix:6,unknown:6,unsaf:6,upgrad:6,upgrade:17,upload:[2,6],url:6,usage:19,use:6,user:[6,10,12,17,21],usernam:6,using:[6,17],valu:6,varchar:6,variabl:[0,6,17],variou:2,verifi:17,version:[4,6,17],via:6,view:[6,13],violat:6,volum:17,vulner:15,wai:6,want:6,warn:6,web:[2,14],welcom:[6,9],what:6,when:6,where:6,which:6,whitespac:6,who:6,why:6,win32:6,window:[6,17],withdrawn:6,without:6,won:6,word:[8,10],work:6,would:6,write:6,wrong:6,xitami:6,xml:8,xss:15,yaml:8,yes:6,your:17,zero:17,zoom:6}}) \ No newline at end of file diff --git a/php/apps/phpmyadmin49/html/doc/html/security.html b/php/apps/phpmyadmin49/html/doc/html/security.html new file mode 100644 index 00000000..dff9d43d --- /dev/null +++ b/php/apps/phpmyadmin49/html/doc/html/security.html @@ -0,0 +1,219 @@ + + + + + + + + Security policy — phpMyAdmin 4.9.1 documentation + + + + + + + + + + + + + + + + + + +
+
+
+
+ +
+

Security policy

+

The phpMyAdmin developer team is putting lot of effort to make phpMyAdmin as +secure as possible. But still web application like phpMyAdmin can be vulnerable +to a number of attacks and new ways to exploit are still being explored.

+

For every reported vulnerability we issue a phpMyAdmin Security Announcement +(PMASA) and it get’s assigne CVE ID as well. We might group similar +vulnerabilities to one PMASA (eg. multiple XSS vulnerabilities can be announced +under one PMASA).

+

If you think you’ve found a vulnerability, please see Reporting security issues.

+
+

Typical vulnerabilities

+

In this secion, we will describe typical vulnerabilities, which can appear in +our code base. This list is by no means complete, it is intended to show +typical attack surface.

+
+

Cross-site scripting (XSS)

+

When phpMyAdmin shows a piece of user data, e.g. something inside a user’s +database, all html special chars have to be escaped. When this escaping is +missing somewhere a malicious user might fill a database with specially crafted +content to trick an other user of that database into executing something. This +could for example be a piece of JavaScript code that would do any number of +nasty things.

+

phpMyAdmin tries to escape all userdata before it is rendered into html for the +browser.

+ +
+
+

Cross-site request forgery (CSRF)

+

An attacker would trick a phpMyAdmin user into clicking on a link to provoke +some action in phpMyAdmin. This link could either be sent via email or some +random website. If successful this the attacker would be able to perform some +action with the users privileges.

+

To mitigate this phpMyAdmin requires a token to be sent on sensitive requests. +The idea is that an attacker does not poses the currently valid token to +include in the presented link.

+

The token is regenerated for every login, so it’s generally valid only for +limited time, what makes it harder for attacker to obtain valid one.

+ +
+
+

SQL injection

+

As the whole purpose of phpMyAdmin is to preform sql queries, this is not our +first concern. SQL injection is sensitive to us though when it concerns the +mysql control connection. This controlconnection can have additional privileges +which the logged in user does not poses. E.g. access the phpMyAdmin configuration storage.

+

User data that is included in (administrative) queries should always be run +through DatabaseInterface::escapeSring().

+ +
+
+

Brute force attack

+

phpMyAdmin on its own does not rate limit authentication attempts in any way. +This is caused by need to work in stateless environment, where there is no way +to protect against such kind of things.

+

To mitigate this, you can use Captcha or utilize external tools such as +fail2ban, this is more details described in Securing your phpMyAdmin installation.

+ +
+
+
+

Reporting security issues

+

Should you find a security issue in the phpMyAdmin programming code, please +contact the phpMyAdmin security team in +advance before publishing it. This way we can prepare a fix and release the fix together with your +announcement. You will be also given credit in our security announcement. +You can optionally encrypt your report with PGP key ID +DA68AB39218AB947 with following fingerprint:

+
pub   4096R/DA68AB39218AB947 2016-08-02
+      Key fingerprint = 5BAD 38CF B980 50B9 4BD7  FB5B DA68 AB39 218A B947
+uid                          phpMyAdmin Security Team &lt;security@phpmyadmin.net&gt;
+sub   4096R/5E4176FB497A31F7 2016-08-02
+
+
+

The key can be either obtained from the keyserver or is available in +phpMyAdmin keyring +available on our download server or using Keybase.

+

Should you have suggestion on improving phpMyAdmin to make it more secure, please +report that to our issue tracker. +Existing improvement suggestions can be found by +hardening label.

+
+
+ + +
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/php/apps/phpmyadmin49/html/doc/html/settings.html b/php/apps/phpmyadmin49/html/doc/html/settings.html new file mode 100644 index 00000000..2e2069d5 --- /dev/null +++ b/php/apps/phpmyadmin49/html/doc/html/settings.html @@ -0,0 +1,130 @@ + + + + + + + + Configuring phpMyAdmin — phpMyAdmin 4.9.1 documentation + + + + + + + + + + + + + + + + + + + +
+
+
+
+ +
+

Configuring phpMyAdmin

+

There are many configuration settings that can be used to customize the +interface. Those settings are described in +Configuration. There are several layers of the configuration.

+

The global settings can be configured in config.inc.php as described in +Configuration. This is only way to configure connections to databases and other +system wide settings.

+

On top of this there are user settings which can be persistently stored in +phpMyAdmin configuration storage, possibly automatically configured through +Zero configuration. If the phpMyAdmin configuration storage are not configured, the settings +are temporarily stored in the session data; these are valid only until you +logout.

+

You can also save the user configuration for further use, either download them +as a file or to the browser local storage. You can find both those options in +the Settings tab. The settings stored in browser local storage will +be automatically offered for loading upon your login to phpMyAdmin.

+
+ + +
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/php/apps/phpmyadmin49/html/doc/html/setup.html b/php/apps/phpmyadmin49/html/doc/html/setup.html new file mode 100644 index 00000000..bfeb04dd --- /dev/null +++ b/php/apps/phpmyadmin49/html/doc/html/setup.html @@ -0,0 +1,1442 @@ + + + + + + + + Installation — phpMyAdmin 4.9.1 documentation + + + + + + + + + + + + + + + + + + +
+
+
+
+ +
+

Installation

+

phpMyAdmin does not apply any special security methods to the MySQL +database server. It is still the system administrator’s job to grant +permissions on the MySQL databases properly. phpMyAdmin’s Users +page can be used for this.

+
+

Warning

+

Mac users should note that if you are on a version before +Mac OS X, StuffIt unstuffs with Mac formats. So you’ll have +to resave as in BBEdit to Unix style ALL phpMyAdmin scripts before +uploading them to your server, as PHP seems not to like Mac-style +end of lines character (“\r”).

+
+
+

Linux distributions

+

phpMyAdmin is included in most Linux distributions. It is recommended to use +distribution packages when possible - they usually provide integration to your +distribution and you will automatically get security updates from your distribution.

+
+

Debian and Ubuntu

+

Debian’s package repositories include a phpMyAdmin package, but be aware that +the configuration file is maintained in /etc/phpmyadmin and may differ in +some ways from the official phpMyAdmin documentation. Specifically it does:

+ +
+

See also

+

More information can be found in README.Debian +(it is installed as /usr/share/doc/phmyadmin/README.Debian with the package).

+
+
+
+

OpenSUSE

+

OpenSUSE already comes with phpMyAdmin package, just install packages from +the openSUSE Build Service.

+
+
+

Gentoo

+

Gentoo ships the phpMyAdmin package, both in a near stock configuration as well +as in a webapp-config configuration. Use emerge dev-db/phpmyadmin to +install.

+
+
+

Mandriva

+

Mandriva ships the phpMyAdmin package in their contrib branch and can be +installed via the usual Control Center.

+
+
+

Fedora

+

Fedora ships the phpMyAdmin package, but be aware that the configuration file +is maintained in /etc/phpMyAdmin/ and may differ in some ways from the +official phpMyAdmin documentation.

+
+
+

Red Hat Enterprise Linux

+

Red Hat Enterprise Linux itself and thus derivatives like CentOS don’t +ship phpMyAdmin, but the Fedora-driven repository +Extra Packages for Enterprise Linux (EPEL) +is doing so, if it’s +enabled. +But be aware that the configuration file is maintained in +/etc/phpMyAdmin/ and may differ in some ways from the +official phpMyAdmin documentation.

+
+
+
+

Installing on Windows

+

The easiest way to get phpMyAdmin on Windows is using third party products +which include phpMyAdmin together with a database and web server such as +XAMPP.

+

You can find more of such options at Wikipedia.

+
+
+

Installing from Git

+

You can clone current phpMyAdmin source from +https://github.com/phpmyadmin/phpmyadmin.git:

+
git clone https://github.com/phpmyadmin/phpmyadmin.git
+
+
+

Additionally you need to install dependencies using the Composer tool:

+
composer update
+
+
+

If you do not intend to develop, you can skip the installation of developer tools +by invoking:

+
composer update --no-dev
+
+
+
+
+

Installing using Composer

+

You can install phpMyAdmin using the Composer tool, since 4.7.0 the releases +are automatically mirrored to the default Packagist repository.

+
+

Note

+

The content of the Composer repository is automatically generated +separately from the releases, so the content doesn’t have to be +100% same as when you download the tarball. There should be no +functional differences though.

+
+

To install phpMyAdmin simply run:

+
composer create-project phpmyadmin/phpmyadmin
+
+
+

Alternatively you can use our own composer repository, which contains +the release tarballs and is available at +<https://www.phpmyadmin.net/packages.json>:

+
composer create-project phpmyadmin/phpmyadmin --repository-url=https://www.phpmyadmin.net/packages.json --no-dev
+
+
+
+
+

Installing using Docker

+

phpMyAdmin comes with a Docker image, which you can easily deploy. You can +download it using:

+
docker pull phpmyadmin/phpmyadmin
+
+
+

The phpMyAdmin server will listen on port 80. It supports several ways of +configuring the link to the database server, either by Docker’s link feature +by linking your database container to db for phpMyAdmin (by specifying +--link your_db_host:db) or by environment variables (in this case it’s up +to you to set up networking in Docker to allow the phpMyAdmin container to access +the database container over network).

+
+

Docker environment variables

+

You can configure several phpMyAdmin features using environment variables:

+
+
+PMA_ARBITRARY
+

Allows you to enter a database server hostname on login form.

+ +
+ +
+
+PMA_HOST
+

Host name or IP address of the database server to use.

+ +
+ +
+
+PMA_HOSTS
+

Comma-separated host names or IP addresses of the database servers to use.

+
+

Note

+

Used only if PMA_HOST is empty.

+
+
+ +
+
+PMA_VERBOSE
+

Verbose name of the database server.

+ +
+ +
+
+PMA_VERBOSES
+

Comma-separated verbose name of the database servers.

+
+

Note

+

Used only if PMA_VERBOSE is empty.

+
+
+ +
+
+PMA_USER
+

User name to use for Config authentication mode.

+
+ +
+
+PMA_PASSWORD
+

Password to use for Config authentication mode.

+
+ +
+
+PMA_PORT
+

Port of the database server to use.

+
+ +
+
+PMA_PORTS
+

Comma-separated ports of the database server to use.

+
+

Note

+

Used only if PMA_PORT is empty.

+
+
+ +
+
+PMA_ABSOLUTE_URI
+

The fully-qualified path (https://pma.example.net/) where the reverse +proxy makes phpMyAdmin available.

+ +
+ +

By default, Cookie authentication mode is used, but if PMA_USER and +PMA_PASSWORD are set, it is switched to Config authentication mode.

+
+

Note

+

The credentials you need to log in are stored in the MySQL server, in case +of Docker image there are various ways to set it (for example +MYSQL_ROOT_PASSWORD when starting the MySQL container). Please check +documentation for MariaDB container +or MySQL container.

+
+
+
+

Customizing configuration

+

Additionally configuration can be tweaked by /etc/phpmyadmin/config.user.inc.php. If +this file exists, it will be loaded after configuration is generated from above +environment variables, so you can override any configuration variable. This +configuration can be added as a volume when invoking docker using +-v /some/local/directory/config.user.inc.php:/etc/phpmyadmin/config.user.inc.php parameters.

+

Note that the supplied configuration file is applied after Docker environment variables, +but you can override any of the values.

+

For example to change default behaviour of CSV export you can use following +configuration file:

+
<?php
+$cfg['Export']['csv_columns'] = true;
+?>
+
+
+

You can also use it to define server configuration instead of using the +environment variables listed in Docker environment variables:

+
<?php
+/* Override Servers array */
+$cfg['Servers'] = [
+    1 => [
+        'auth_type' => 'cookie',
+        'host' => 'mydb1',
+        'port' => 3306,
+        'verbose' => 'Verbose name 1',
+    ],
+    2 => [
+        'auth_type' => 'cookie',
+        'host' => 'mydb2',
+        'port' => 3306,
+        'verbose' => 'Verbose name 2',
+    ],
+];
+
+
+
+

See also

+

See Configuration for detailed description of configuration options.

+
+
+
+

Docker Volumes

+

You can use following volumes to customize image behavior:

+

/etc/phpmyadmin/config.user.inc.php

+
+
Can be used for additional settings, see previous chapter for more details.
+

/sessions/

+
+
Directory where PHP sessions are stored. You might want to share this +for example when using Signon authentication mode.
+

/www/themes/

+
+
Directory where phpMyAdmin looks for themes. By default only those shipped +with phpMyAdmin are included, but you can include additional phpMyAdmin +themes (see Custom Themes) by using Docker volumes.
+
+
+

Docker Examples

+

To connect phpMyAdmin to a given server use:

+
docker run --name myadmin -d -e PMA_HOST=dbhost -p 8080:80 phpmyadmin/phpmyadmin
+
+
+

To connect phpMyAdmin to more servers use:

+
docker run --name myadmin -d -e PMA_HOSTS=dbhost1,dbhost2,dbhost3 -p 8080:80 phpmyadmin/phpmyadmin
+
+
+

To use arbitrary server option:

+
docker run --name myadmin -d --link mysql_db_server:db -p 8080:80 -e PMA_ARBITRARY=1 phpmyadmin/phpmyadmin
+
+
+

You can also link the database container using Docker:

+
docker run --name phpmyadmin -d --link mysql_db_server:db -p 8080:80 phpmyadmin/phpmyadmin
+
+
+

Running with additional configuration:

+
docker run --name phpmyadmin -d --link mysql_db_server:db -p 8080:80 -v /some/local/directory/config.user.inc.php:/etc/phpmyadmin/config.user.inc.php phpmyadmin/phpmyadmin
+
+
+

Running with additional themes:

+
docker run --name phpmyadmin -d --link mysql_db_server:db -p 8080:80 -v /custom/phpmyadmin/theme/:/www/themes/theme/ phpmyadmin/phpmyadmin
+
+
+
+
+

Using docker-compose

+

Alternatively you can also use docker-compose with the docker-compose.yml from +<https://github.com/phpmyadmin/docker>. This will run phpMyAdmin with an +arbitrary server - allowing you to specify MySQL/MariaDB server on login page.

+
docker-compose up -d
+
+
+
+
+

Customizing configuration file using docker-compose

+

You can use an external file to customize phpMyAdmin configuration and pass it +using the volumes directive:

+
phpmyadmin:
+    image: phpmyadmin/phpmyadmin
+    container_name: phpmyadmin
+    environment:
+     - PMA_ARBITRARY=1
+    restart: always
+    ports:
+     - 8080:80
+    volumes:
+     - /sessions
+     - ~/docker/phpmyadmin/config.user.inc.php:/etc/phpmyadmin/config.user.inc.php
+     - /custom/phpmyadmin/theme/:/www/themes/theme/
+
+
+ +
+
+

Running behind haproxy in a subdirectory

+

When you want to expose phpMyAdmin running in a Docker container in a +subdirectory, you need to rewrite the request path in the server proxying the +requests.

+

For example using haproxy it can be done as:

+
frontend http
+    bind *:80
+    option forwardfor
+    option http-server-close
+
+    ### NETWORK restriction
+    acl LOCALNET  src 10.0.0.0/8 192.168.0.0/16 172.16.0.0/12
+
+    # /phpmyadmin
+    acl phpmyadmin  path_dir /phpmyadmin
+    use_backend phpmyadmin if phpmyadmin LOCALNET
+
+backend phpmyadmin
+    mode http
+
+    reqirep  ^(GET|POST|HEAD)\ /phpmyadmin/(.*)     \1\ /\2
+
+    # phpMyAdmin container IP
+    server localhost     172.30.21.21:80
+
+
+

When using traefik, something like following should work:

+
defaultEntryPoints = ["http"]
+[entryPoints]
+  [entryPoints.http]
+  address = ":80"
+    [entryPoints.http.redirect]
+      regex = "(http:\\/\\/[^\\/]+\\/([^\\?\\.]+)[^\\/])$"
+      replacement = "$1/"
+
+[backends]
+  [backends.myadmin]
+    [backends.myadmin.servers.myadmin]
+    url="http://internal.address.to.pma"
+
+[frontends]
+   [frontends.myadmin]
+   backend = "myadmin"
+   passHostHeader = true
+     [frontends.myadmin.routes.default]
+     rule="PathPrefixStrip:/phpmyadmin/;AddPrefix:/"
+
+
+

You then should specify PMA_ABSOLUTE_URI in the docker-compose +configuration:

+
version: '2'
+
+services:
+  phpmyadmin:
+    restart: always
+    image: phpmyadmin/phpmyadmin
+    container_name: phpmyadmin
+    hostname: phpmyadmin
+    domainname: example.com
+    ports:
+      - 8000:80
+    environment:
+      - PMA_HOSTS=172.26.36.7,172.26.36.8,172.26.36.9,172.26.36.10
+      - PMA_VERBOSES=production-db1,production-db2,dev-db1,dev-db2
+      - PMA_USER=root
+      - PMA_PASSWORD=
+      - PMA_ABSOLUTE_URI=http://example.com/phpmyadmin/
+
+
+
+
+
+

Quick Install

+
    +
  1. Choose an appropriate distribution kit from the phpmyadmin.net +Downloads page. Some kits contain only the English messages, others +contain all languages. We’ll assume you chose a kit whose name +looks like phpMyAdmin-x.x.x -all-languages.tar.gz.
  2. +
  3. Ensure you have downloaded a genuine archive, see Verifying phpMyAdmin releases.
  4. +
  5. Untar or unzip the distribution (be sure to unzip the subdirectories): +tar -xzvf phpMyAdmin_x.x.x-all-languages.tar.gz in your +webserver’s document root. If you don’t have direct access to your +document root, put the files in a directory on your local machine, +and, after step 4, transfer the directory on your web server using, +for example, ftp.
  6. +
  7. Ensure that all the scripts have the appropriate owner (if PHP is +running in safe mode, having some scripts with an owner different from +the owner of other scripts will be a problem). See 4.2 What’s the preferred way of making phpMyAdmin secure against evil access? and +1.26 I just installed phpMyAdmin in my document root of IIS but I get the error “No input file specified” when trying to run phpMyAdmin. for suggestions.
  8. +
  9. Now you must configure your installation. There are two methods that +can be used. Traditionally, users have hand-edited a copy of +config.inc.php, but now a wizard-style setup script is provided +for those who prefer a graphical installation. Creating a +config.inc.php is still a quick way to get started and needed for +some advanced features.
  10. +
+
+

Manually creating the file

+

To manually create the file, simply use your text editor to create the +file config.inc.php (you can copy config.sample.inc.php to get +a minimal configuration file) in the main (top-level) phpMyAdmin +directory (the one that contains index.php). phpMyAdmin first +loads libraries/config.default.php and then overrides those values +with anything found in config.inc.php. If the default value is +okay for a particular setting, there is no need to include it in +config.inc.php. You’ll probably need only a few directives to get going; a +simple configuration may look like this:

+
<?php
+// use here a value of your choice at least 32 chars long
+$cfg['blowfish_secret'] = '1{dd0`<Q),5XP_:R9UK%%8\"EEcyH#{o';
+
+$i=0;
+$i++;
+$cfg['Servers'][$i]['auth_type']     = 'cookie';
+// if you insist on "root" having no password:
+// $cfg['Servers'][$i]['AllowNoPassword'] = true; `
+?>
+
+
+

Or, if you prefer to not be prompted every time you log in:

+
<?php
+
+$i=0;
+$i++;
+$cfg['Servers'][$i]['user']          = 'root';
+$cfg['Servers'][$i]['password']      = 'cbb74bc'; // use here your password
+$cfg['Servers'][$i]['auth_type']     = 'config';
+?>
+
+
+
+

Warning

+

Storing passwords in the configuration is insecure as anybody can then +manipulate your database.

+
+

For a full explanation of possible configuration values, see the +Configuration of this document.

+
+
+

Using Setup script

+

Instead of manually editing config.inc.php, you can use phpMyAdmin’s +setup feature. The file can be generated using the setup and you can download it +for upload to the server.

+

Next, open your browser and visit the location where you installed phpMyAdmin, +with the /setup suffix. The changes are not saved to the server, you need to +use the Download button to save them to your computer and then upload +to the server.

+

Now the file is ready to be used. You can choose to review or edit the +file with your favorite editor, if you prefer to set some advanced +options which the setup script does not provide.

+
    +
  1. If you are using the auth_type “config”, it is suggested that you +protect the phpMyAdmin installation directory because using config +does not require a user to enter a password to access the phpMyAdmin +installation. Use of an alternate authentication method is +recommended, for example with HTTP–AUTH in a .htaccess file or switch to using +auth_type cookie or http. See the ISPs, multi-user installations +for additional information, especially 4.4 phpMyAdmin always gives “Access denied” when using HTTP authentication..
  2. +
  3. Open the main phpMyAdmin directory in your browser. +phpMyAdmin should now display a welcome screen and your databases, or +a login dialog if using HTTP or +cookie authentication mode.
  4. +
+
+

Setup script on Debian, Ubuntu and derivatives

+

Debian and Ubuntu have changed way how setup is enabled and disabled, in a way +that single command has to be executed for either of these.

+

To allow editing configuration invoke:

+
/usr/sbin/pma-configure
+
+
+

To block editing configuration invoke:

+
/usr/sbin/pma-secure
+
+
+
+
+

Setup script on openSUSE

+

Some openSUSE releases do not include setup script in the package. In case you +want to generate configuration on these you can either download original +package from <https://www.phpmyadmin.net/> or use setup script on our demo +server: <https://demo.phpmyadmin.net/master/setup/>.

+
+
+
+
+

Verifying phpMyAdmin releases

+

Since July 2015 all phpMyAdmin releases are cryptographically signed by the +releasing developer, who through January 2016 was Marc Delisle. His key id is +0xFEFC65D181AF644A, his PGP fingerprint is:

+
436F F188 4B1A 0C3F DCBF 0D79 FEFC 65D1 81AF 644A
+
+
+

and you can get more identification information from <https://keybase.io/lem9>.

+

Beginning in January 2016, the release manager is Isaac Bennetch. His key id is +0xCE752F178259BD92, and his PGP fingerprint is:

+
3D06 A59E CE73 0EB7 1B51 1C17 CE75 2F17 8259 BD92
+
+
+

and you can get more identification information from <https://keybase.io/ibennetch>.

+

Some additional downloads (for example themes) might be signed by Michal Čihař. His key id is +0x9C27B31342B7511D, and his PGP fingerprint is:

+
63CB 1DF1 EF12 CF2A C0EE 5A32 9C27 B313 42B7 511D
+
+
+

and you can get more identification information from <https://keybase.io/nijel>.

+

You should verify that the signature matches the archive you have downloaded. +This way you can be sure that you are using the same code that was released. +You should also verify the date of the signature to make sure that you +downloaded the latest version.

+

Each archive is accompanied with .asc files which contains the PGP signature +for it. Once you have both of them in the same folder, you can verify the signature:

+
$ gpg --verify phpMyAdmin-4.5.4.1-all-languages.zip.asc
+gpg: Signature made Fri 29 Jan 2016 08:59:37 AM EST using RSA key ID 8259BD92
+gpg: Can't check signature: public key not found
+
+
+

As you can see gpg complains that it does not know the public key. At this +point you should do one of the following steps:

+ +
$ gpg --import phpmyadmin.keyring
+
+
+
    +
  • Download and import the key from one of the key servers:
  • +
+
$ gpg --keyserver hkp://pgp.mit.edu --recv-keys 3D06A59ECE730EB71B511C17CE752F178259BD92
+gpg: requesting key 8259BD92 from hkp server pgp.mit.edu
+gpg: key 8259BD92: public key "Isaac Bennetch <bennetch@gmail.com>" imported
+gpg: no ultimately trusted keys found
+gpg: Total number processed: 1
+gpg:               imported: 1  (RSA: 1)
+
+
+

This will improve the situation a bit - at this point you can verify that the +signature from the given key is correct but you still can not trust the name used +in the key:

+
$ gpg --verify phpMyAdmin-4.5.4.1-all-languages.zip.asc
+gpg: Signature made Fri 29 Jan 2016 08:59:37 AM EST using RSA key ID 8259BD92
+gpg: Good signature from "Isaac Bennetch <bennetch@gmail.com>"
+gpg:                 aka "Isaac Bennetch <isaac@bennetch.org>"
+gpg: WARNING: This key is not certified with a trusted signature!
+gpg:          There is no indication that the signature belongs to the owner.
+Primary key fingerprint: 3D06 A59E CE73 0EB7 1B51  1C17 CE75 2F17 8259 BD92
+
+
+

The problem here is that anybody could issue the key with this name. You need to +ensure that the key is actually owned by the mentioned person. The GNU Privacy +Handbook covers this topic in the chapter Validating other keys on your public +keyring. The most reliable method is to meet the developer in person and +exchange key fingerprints, however you can also rely on the web of trust. This way +you can trust the key transitively though signatures of others, who have met +the developer in person. For example you can see how Isaac’s key links to +Linus’s key.

+

Once the key is trusted, the warning will not occur:

+
$ gpg --verify phpMyAdmin-4.5.4.1-all-languages.zip.asc
+gpg: Signature made Fri 29 Jan 2016 08:59:37 AM EST using RSA key ID 8259BD92
+gpg: Good signature from "Isaac Bennetch <bennetch@gmail.com>" [full]
+
+
+

Should the signature be invalid (the archive has been changed), you would get a +clear error regardless of the fact that the key is trusted or not:

+
$ gpg --verify phpMyAdmin-4.5.4.1-all-languages.zip.asc
+gpg: Signature made Fri 29 Jan 2016 08:59:37 AM EST using RSA key ID 8259BD92
+gpg: BAD signature from "Isaac Bennetch <bennetch@gmail.com>" [unknown]
+
+
+
+
+

phpMyAdmin configuration storage

+
+

Changed in version 3.4.0: Prior to phpMyAdmin 3.4.0 this was called Linked Tables Infrastructure, but +the name was changed due to extended scope of the storage.

+
+

For a whole set of additional features (Bookmarks, comments, SQL-history, +tracking mechanism, PDF-generation, Transformations, Relations +etc.) you need to create a set of special tables. Those tables can be located +in your own database, or in a central database for a multi-user installation +(this database would then be accessed by the controluser, so no other user +should have rights to it).

+
+

Zero configuration

+

In many cases, this database structure can be automatically created and +configured. This is called “Zero Configuration” mode and can be particularly +useful in shared hosting situations. “Zeroconf” mode is on by default, to +disable set $cfg['ZeroConf'] to false.

+

The following three scenarios are covered by the Zero Configuration mode:

+
    +
  • When entering a database where the configuration storage tables are not +present, phpMyAdmin offers to create them from the Operations tab.
  • +
  • When entering a database where the tables do already exist, the software +automatically detects this and begins using them. This is the most common +situation; after the tables are initially created automatically they are +continually used without disturbing the user; this is also most useful on +shared hosting where the user is not able to edit config.inc.php and +usually the user only has access to one database.
  • +
  • When having access to multiple databases, if the user first enters the +database containing the configuration storage tables then switches to +another database, +phpMyAdmin continues to use the tables from the first database; the user is +not prompted to create more tables in the new database.
  • +
+
+
+

Manual configuration

+

Please look at your ./sql/ directory, where you should find a +file called create_tables.sql. (If you are using a Windows server, +pay special attention to 1.23 I’m running MySQL on a Win32 machine. Each time I create a new table the table and column names are changed to lowercase!).

+

If you already had this infrastructure and:

+
    +
  • upgraded to MySQL 4.1.2 or newer, please use +sql/upgrade_tables_mysql_4_1_2+.sql.
  • +
  • upgraded to phpMyAdmin 4.3.0 or newer from 2.5.0 or newer (<= 4.2.x), +please use sql/upgrade_column_info_4_3_0+.sql.
  • +
  • upgraded to phpMyAdmin 4.7.0 or newer from 4.3.0 or newer, +please use sql/upgrade_tables_4_7_0+.sql.
  • +
+

and then create new tables by importing sql/create_tables.sql.

+

You can use your phpMyAdmin to create the tables for you. Please be +aware that you may need special (administrator) privileges to create +the database and tables, and that the script may need some tuning, +depending on the database name.

+

After having imported the sql/create_tables.sql file, you +should specify the table names in your config.inc.php file. The +directives used for that can be found in the Configuration.

+

You will also need to have a controluser +($cfg['Servers'][$i]['controluser'] and +$cfg['Servers'][$i]['controlpass'] settings) +with the proper rights to those tables. For example you can create it +using following statement:

+
GRANT SELECT, INSERT, UPDATE, DELETE ON <pma_db>.* TO 'pma'@'localhost'  IDENTIFIED BY 'pmapass';
+
+
+
+
+
+

Upgrading from an older version

+
+

Warning

+

Never extract the new version over an existing installation of +phpMyAdmin, always first remove the old files keeping just the +configuration.

+

This way you will not leave old no longer working code in the directory, +which can have severe security implications or can cause various breakages.

+
+

Simply copy config.inc.php from your previous installation into +the newly unpacked one. Configuration files from old versions may +require some tweaking as some options have been changed or removed. +For compatibility with PHP 5.3 and later, remove a +set_magic_quotes_runtime(0); statement that you might find near +the end of your configuration file.

+

You should not copy libraries/config.default.php over +config.inc.php because the default configuration file is version- +specific.

+

The complete upgrade can be performed in few simple steps:

+
    +
  1. Download the latest phpMyAdmin version from <https://www.phpmyadmin.net/downloads/>.
  2. +
  3. Rename existing phpMyAdmin folder (for example to phpmyadmin-old).
  4. +
  5. Unpack freshly donwloaded phpMyAdmin to desired location (for example phpmyadmin).
  6. +
  7. Copy config.inc.php` from old location (phpmyadmin-old) to new one (phpmyadmin).
  8. +
  9. Test that everything works properly.
  10. +
  11. Remove backup of previous version (phpmyadmin-old).
  12. +
+

If you have upgraded your MySQL server from a version previous to 4.1.2 to +version 5.x or newer and if you use the phpMyAdmin configuration storage, you +should run the SQL script found in +sql/upgrade_tables_mysql_4_1_2+.sql.

+

If you have upgraded your phpMyAdmin to 4.3.0 or newer from 2.5.0 or +newer (<= 4.2.x) and if you use the phpMyAdmin configuration storage, you +should run the SQL script found in +sql/upgrade_column_info_4_3_0+.sql.

+

Do not forget to clear the browser cache and to empty the old session by +logging out and logging in again.

+
+
+

Using authentication modes

+

HTTP and cookie authentication modes are recommended in a multi-user +environment where you want to give users access to their own database and +don’t want them to play around with others. Nevertheless be aware that MS +Internet Explorer seems to be really buggy about cookies, at least till version +6. Even in a single-user environment, you might prefer to use HTTP +or cookie mode so that your user/password pair are not in clear in the +configuration file.

+

HTTP and cookie authentication +modes are more secure: the MySQL login information does not need to be +set in the phpMyAdmin configuration file (except possibly for the +$cfg['Servers'][$i]['controluser']). +However, keep in mind that the password travels in plain text, unless +you are using the HTTPS protocol. In cookie mode, the password is +stored, encrypted with the AES algorithm, in a temporary cookie.

+

Then each of the true users should be granted a set of privileges +on a set of particular databases. Normally you shouldn’t give global +privileges to an ordinary user, unless you understand the impact of those +privileges (for example, you are creating a superuser). +For example, to grant the user real_user with all privileges on +the database user_base:

+
GRANT ALL PRIVILEGES ON user_base.* TO 'real_user'@localhost IDENTIFIED BY 'real_password';
+
+
+

What the user may now do is controlled entirely by the MySQL user management +system. With HTTP or cookie authentication mode, you don’t need to fill the +user/password fields inside the $cfg['Servers'].

+ +
+

HTTP authentication mode

+ +
+

Note

+

There is no way to do proper logout in HTTP authentication, most browsers +will remember credentials until there is no different successful +authentication. Because of this this method has limitation that you can not +login with same user after logout.

+
+
+ +
+

Signon authentication mode

+ +

The very basic example of saving credentials in a session is available as +examples/signon.php:

+
<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Single signon for phpMyAdmin
+ *
+ * This is just example how to use session based single signon with
+ * phpMyAdmin, it is not intended to be perfect code and look, only
+ * shows how you can integrate this functionality in your application.
+ *
+ * @package    PhpMyAdmin
+ * @subpackage Example
+ */
+
+/* Use cookies for session */
+ini_set('session.use_cookies', 'true');
+/* Change this to true if using phpMyAdmin over https */
+$secure_cookie = false;
+/* Need to have cookie visible from parent directory */
+session_set_cookie_params(0, '/', '', $secure_cookie, true);
+/* Create signon session */
+$session_name = 'SignonSession';
+session_name($session_name);
+// Uncomment and change the following line to match your $cfg['SessionSavePath']
+//session_save_path('/foobar');
+@session_start();
+
+/* Was data posted? */
+if (isset($_POST['user'])) {
+    /* Store there credentials */
+    $_SESSION['PMA_single_signon_user'] = $_POST['user'];
+    $_SESSION['PMA_single_signon_password'] = $_POST['password'];
+    $_SESSION['PMA_single_signon_host'] = $_POST['host'];
+    $_SESSION['PMA_single_signon_port'] = $_POST['port'];
+    /* Update another field of server configuration */
+    $_SESSION['PMA_single_signon_cfgupdate'] = array('verbose' => 'Signon test');
+    $id = session_id();
+    /* Close that session */
+    @session_write_close();
+    /* Redirect to phpMyAdmin (should use absolute URL here!) */
+    header('Location: ../index.php');
+} else {
+    /* Show simple form */
+    header('Content-Type: text/html; charset=utf-8');
+    echo '<?xml version="1.0" encoding="utf-8"?>' , "\n";
+    ?>
+    <!DOCTYPE HTML>
+    <html lang="en" dir="ltr">
+    <head>
+    <link rel="icon" href="../favicon.ico" type="image/x-icon" />
+    <link rel="shortcut icon" href="../favicon.ico" type="image/x-icon" />
+    <meta charset="utf-8" />
+    <title>phpMyAdmin single signon example</title>
+    </head>
+    <body>
+    <?php
+    if (isset($_SESSION['PMA_single_signon_error_message'])) {
+        echo '<p class="error">';
+        echo $_SESSION['PMA_single_signon_error_message'];
+        echo '</p>';
+    }
+    ?>
+    <form action="signon.php" method="post">
+    Username: <input type="text" name="user" /><br />
+    Password: <input type="password" name="password" /><br />
+    Host: (will use the one from config.inc.php by default)
+    <input type="text" name="host" /><br />
+    Port: (will use the one from config.inc.php by default)
+    <input type="text" name="port" /><br />
+    <input type="submit" />
+    </form>
+    </body>
+    </html>
+    <?php
+}
+?>
+
+
+

Alternatively you can also use this way to integrate with OpenID as shown +in examples/openid.php:

+
<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Single signon for phpMyAdmin using OpenID
+ *
+ * This is just example how to use single signon with phpMyAdmin, it is
+ * not intended to be perfect code and look, only shows how you can
+ * integrate this functionality in your application.
+ *
+ * It uses OpenID pear package, see https://pear.php.net/package/OpenID
+ *
+ * User first authenticates using OpenID and based on content of $AUTH_MAP
+ * the login information is passed to phpMyAdmin in session data.
+ *
+ * @package    PhpMyAdmin
+ * @subpackage Example
+ */
+
+if (false === @include_once 'OpenID/RelyingParty.php') {
+    exit;
+}
+
+/* Change this to true if using phpMyAdmin over https */
+$secure_cookie = false;
+
+/**
+ * Map of authenticated users to MySQL user/password pairs.
+ */
+$AUTH_MAP = array(
+    'https://launchpad.net/~username' => array(
+        'user' => 'root',
+        'password' => '',
+        ),
+    );
+
+/**
+ * Simple function to show HTML page with given content.
+ *
+ * @param string $contents Content to include in page
+ *
+ * @return void
+ */
+function Show_page($contents)
+{
+    header('Content-Type: text/html; charset=utf-8');
+    echo '<?xml version="1.0" encoding="utf-8"?>' , "\n";
+    ?>
+    <!DOCTYPE HTML>
+    <html lang="en" dir="ltr">
+    <head>
+    <link rel="icon" href="../favicon.ico" type="image/x-icon" />
+    <link rel="shortcut icon" href="../favicon.ico" type="image/x-icon" />
+    <meta charset="utf-8" />
+    <title>phpMyAdmin OpenID signon example</title>
+    </head>
+    <body>
+    <?php
+    if (isset($_SESSION) && isset($_SESSION['PMA_single_signon_error_message'])) {
+        echo '<p class="error">' , $_SESSION['PMA_single_signon_message'] , '</p>';
+        unset($_SESSION['PMA_single_signon_message']);
+    }
+    echo $contents;
+    ?>
+    </body>
+    </html>
+    <?php
+}
+
+/**
+ * Display error and exit
+ *
+ * @param Exception $e Exception object
+ *
+ * @return void
+ */
+function Die_error($e)
+{
+    $contents = "<div class='relyingparty_results'>\n";
+    $contents .= "<pre>" . htmlspecialchars($e->getMessage()) . "</pre>\n";
+    $contents .= "</div class='relyingparty_results'>";
+    Show_page($contents);
+    exit;
+}
+
+
+/* Need to have cookie visible from parent directory */
+session_set_cookie_params(0, '/', '', $secure_cookie, true);
+/* Create signon session */
+$session_name = 'SignonSession';
+session_name($session_name);
+@session_start();
+
+// Determine realm and return_to
+$base = 'http';
+if (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') {
+    $base .= 's';
+}
+$base .= '://' . $_SERVER['SERVER_NAME'] . ':' . $_SERVER['SERVER_PORT'];
+
+$realm = $base . '/';
+$returnTo = $base . dirname($_SERVER['PHP_SELF']);
+if ($returnTo[strlen($returnTo) - 1] != '/') {
+    $returnTo .= '/';
+}
+$returnTo .= 'openid.php';
+
+/* Display form */
+if (!count($_GET) && !count($_POST) || isset($_GET['phpMyAdmin'])) {
+    /* Show simple form */
+    $content = '<form action="openid.php" method="post">
+OpenID: <input type="text" name="identifier" /><br />
+<input type="submit" name="start" />
+</form>
+</body>
+</html>';
+    Show_page($content);
+    exit;
+}
+
+/* Grab identifier */
+if (isset($_POST['identifier']) && is_string($_POST['identifier'])) {
+    $identifier = $_POST['identifier'];
+} elseif (isset($_SESSION['identifier']) && is_string($_SESSION['identifier'])) {
+    $identifier = $_SESSION['identifier'];
+} else {
+    $identifier = null;
+}
+
+/* Create OpenID object */
+try {
+    $o = new OpenID_RelyingParty($returnTo, $realm, $identifier);
+} catch (Exception $e) {
+    Die_error($e);
+}
+
+/* Redirect to OpenID provider */
+if (isset($_POST['start'])) {
+    try {
+        $authRequest = $o->prepare();
+    } catch (Exception $e) {
+        Die_error($e);
+    }
+
+    $url = $authRequest->getAuthorizeURL();
+
+    header("Location: $url");
+    exit;
+} else {
+    /* Grab query string */
+    if (!count($_POST)) {
+        list(, $queryString) = explode('?', $_SERVER['REQUEST_URI']);
+    } else {
+        // I hate php sometimes
+        $queryString = file_get_contents('php://input');
+    }
+
+    /* Check reply */
+    try {
+        $message = new OpenID_Message($queryString, OpenID_Message::FORMAT_HTTP);
+    } catch (Exception $e) {
+        Die_error($e);
+    }
+
+    $id = $message->get('openid.claimed_id');
+
+    if (!empty($id) && isset($AUTH_MAP[$id])) {
+        $_SESSION['PMA_single_signon_user'] = $AUTH_MAP[$id]['user'];
+        $_SESSION['PMA_single_signon_password'] = $AUTH_MAP[$id]['password'];
+        session_write_close();
+        /* Redirect to phpMyAdmin (should use absolute URL here!) */
+        header('Location: ../index.php');
+    } else {
+        Show_page('<p>User not allowed!</p>');
+        exit;
+    }
+}
+
+
+

If you intend to pass the credentials using some other means than, you have to +implement wrapper in PHP to get that data and set it to +$cfg['Servers'][$i]['SignonScript']. There is very minimal example +in examples/signon-script.php:

+
<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Single signon for phpMyAdmin
+ *
+ * This is just example how to use script based single signon with
+ * phpMyAdmin, it is not intended to be perfect code and look, only
+ * shows how you can integrate this functionality in your application.
+ *
+ * @package    PhpMyAdmin
+ * @subpackage Example
+ */
+
+
+/**
+ * This function returns username and password.
+ *
+ * It can optionally use configured username as parameter.
+ *
+ * @param string $user User name
+ *
+ * @return array
+ */
+function get_login_credentials($user)
+{
+    /* Optionally we can use passed username */
+    if (!empty($user)) {
+        return array($user, 'password');
+    }
+
+    /* Here we would retrieve the credentials */
+    $credentials = array('root', '');
+
+    return $credentials;
+}
+
+
+ +
+
+

Config authentication mode

+
    +
  • This mode is sometimes the less secure one because it requires you to fill the +$cfg['Servers'][$i]['user'] and +$cfg['Servers'][$i]['password'] +fields (and as a result, anyone who can read your config.inc.php +can discover your username and password).
  • +
  • In the ISPs, multi-user installations section, there is an entry explaining how +to protect your configuration file.
  • +
  • For additional security in this mode, you may wish to consider the +Host authentication $cfg['Servers'][$i]['AllowDeny']['order'] +and $cfg['Servers'][$i]['AllowDeny']['rules'] configuration directives.
  • +
  • Unlike cookie and http, does not require a user to log in when first +loading the phpMyAdmin site. This is by design but could allow any +user to access your installation. Use of some restriction method is +suggested, perhaps a .htaccess file with the HTTP-AUTH directive or disallowing +incoming HTTP requests at one’s router or firewall will suffice (both +of which are beyond the scope of this manual but easily searchable +with Google).
  • +
+
+
+
+

Securing your phpMyAdmin installation

+

The phpMyAdmin team tries hard to make the application secure, however there +are always ways to make your installation more secure:

+
    +
  • Follow our Security announcements and upgrade +phpMyAdmin whenever new vulnerability is published.

    +
  • +
  • Serve phpMyAdmin on HTTPS only. Preferably, you should use HSTS as well, so that +you’re protected from protocol downgrade attacks.

    +
  • +
  • Ensure your PHP setup follows recommendations for production sites, for example +display_errors +should be disabled.

    +
  • +
  • Remove the test directory from phpMyAdmin, unless you are developing and need test suite.

    +
  • +
  • Remove the setup directory from phpMyAdmin, you will probably not +use it after the initial setup.

    +
  • +
  • Properly choose an authentication method - Cookie authentication mode +is probably the best choice for shared hosting.

    +
  • +
  • Deny access to auxiliary files in ./libraries/ or +./templates/ subfolders in your webserver configuration. +Such configuration prevents from possible path exposure and cross side +scripting vulnerabilities that might happen to be found in that code. For the +Apache webserver, this is often accomplished with a .htaccess file in +those directories.

    +
  • +
  • Deny access to temporary files, see $cfg['TempDir'] (if that +is placed inside your web root, see also Web server upload/save/import directories.

    +
  • +
  • It is generally a good idea to protect a public phpMyAdmin installation +against access by robots as they usually can not do anything good there. You +can do this using robots.txt file in root of your webserver or limit +access by web server configuration, see 1.42 How can I prevent robots from accessing phpMyAdmin?.

    +
  • +
  • In case you don’t want all MySQL users to be able to access +phpMyAdmin, you can use $cfg['Servers'][$i]['AllowDeny']['rules'] to limit them +or $cfg['Servers'][$i]['AllowRoot'] to deny root user access.

    +
  • +
  • Enable Two-factor authentication for your account.

    +
  • +
  • Consider hiding phpMyAdmin behind an authentication proxy, so that +users need to authenticate prior to providing MySQL credentials +to phpMyAdmin. You can achieve this by configuring your web server to request +HTTP authentication. For example in Apache this can be done with:

    +
    AuthType Basic
    +AuthName "Restricted Access"
    +AuthUserFile /usr/share/phpmyadmin/passwd
    +Require valid-user
    +
    +
    +

    Once you have changed the configuration, you need to create a list of users which +can authenticate. This can be done using the htpasswd utility:

    +
    htpasswd -c /usr/share/phpmyadmin/passwd username
    +
    +
    +
  • +
  • If you are afraid of automated attacks, enabling Captcha by +$cfg['CaptchaLoginPublicKey'] and +$cfg['CaptchaLoginPrivateKey'] might be an option.

    +
  • +
  • Failed login attemps are logged to syslog (if available, see +$cfg['AuthLog']). This can allow using a tool such as +fail2ban to block brute-force attempts. Note that the log file used by syslog +is not the same as the Apache error or access log files.

    +
  • +
  • In case you’re running phpMyAdmin together with other PHP applications, it is +generally advised to use separate session storage for phpMyAdmin to avoid +possible session based attacks against it. You can use +$cfg['SessionSavePath'] to achieve this.

    +
  • +
+
+
+

Using SSL for connection to database server

+

It is recommended to use SSL when connecting to remote database server. There +are several configuration options involved in the SSL setup:

+
+
$cfg['Servers'][$i]['ssl']
+
Defines whether to use SSL at all. If you enable only this, the connection +will be encrypted, but there is not authentication of the connection - you +can not verify that you are talking to the right server.
+
$cfg['Servers'][$i]['ssl_key'] and $cfg['Servers'][$i]['ssl_cert']
+
This is used for authentication of client to the server.
+
$cfg['Servers'][$i]['ssl_ca'] and $cfg['Servers'][$i]['ssl_ca_path']
+
The certificate authorities you trust for server certificates. +This is used to ensure that you are talking to a trusted server.
+
$cfg['Servers'][$i]['ssl_verify']
+
This configuration disables server certificate verification. Use with +caution.
+
+ +
+
+

Known issues

+
+

Users with column-specific privileges are unable to “Browse”

+

If a user has only column-specific privileges on some (but not all) columns in a table, “Browse” +will fail with an error message.

+

As a workaround, a bookmarked query with the same name as the table can be created, this will +run when using the “Browse” link instead. Issue 11922.

+
+
+

Trouble logging back in after logging out using ‘http’ authentication

+

When using the ‘http’ auth_type, it can be impossible to log back in (when the logout comes +manually or after a period of inactivity). Issue 11898.

+
+
+
+ + +
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/php/apps/phpmyadmin49/html/doc/html/themes.html b/php/apps/phpmyadmin49/html/doc/html/themes.html new file mode 100644 index 00000000..1ce4b1d8 --- /dev/null +++ b/php/apps/phpmyadmin49/html/doc/html/themes.html @@ -0,0 +1,224 @@ + + + + + + + + Custom Themes — phpMyAdmin 4.9.1 documentation + + + + + + + + + + + + + + + + + + + +
+
+
+
+ +
+

Custom Themes

+

phpMyAdmin comes with support for third party themes. You can download +additonal themes from our website at <https://www.phpmyadmin.net/themes/>.

+
+

Configuration

+

Themes are configured with $cfg['ThemeManager'] and +$cfg['ThemeDefault']. Under ./themes/, you should not +delete the directory pmahomme or its underlying structure, because this is +the system theme used by phpMyAdmin. pmahomme contains all images and +styles, for backwards compatibility and for all themes that would not include +images or css-files. If $cfg['ThemeManager'] is enabled, you +can select your favorite theme on the main page. Your selected theme will be +stored in a cookie.

+
+
+

Creating custom theme

+

To create a theme:

+
    +
  • make a new subdirectory (for example “your_theme_name”) under ./themes/.
  • +
  • copy the files and directories from pmahomme to “your_theme_name”
  • +
  • edit the css-files in “your_theme_name/css”
  • +
  • put your new images in “your_theme_name/img”
  • +
  • edit layout.inc.php in “your_theme_name”
  • +
  • edit theme.json in “your_theme_name” to contain theme metadata (see below)
  • +
  • make a new screenshot of your theme and save it under +“your_theme_name/screen.png”
  • +
+
+

Theme metadata

+
+

Changed in version 4.8.0: Before 4.8.0 the theme metadata was passed in the info.inc.php file. +It has been replaced by theme.json to allow easier parsing (without +need to handle PHP code) and to support additional features.

+
+

In theme directory there is file theme.json which contains theme +metadata. Currently it consists of:

+
+
+name
+

Display name of the theme.

+

This field is required.

+
+ +
+
+version
+

Theme version, can be quite arbirary and does not have to match phpMyAdmin version.

+

This field is required.

+
+ +
+
+desciption
+

Theme description. this will be shown on the website.

+

This field is required.

+
+ +
+
+author
+

Theme author name.

+

This field is required.

+
+ +
+
+url
+

Link to theme author website. It’s good idea to have way for getting +support there.

+
+ +
+
+supports
+

Array of supported phpMyAdmin major versions.

+

This field is required.

+
+ +

For example, the definition for Original theme shipped with phpMyAdnin 4.8:

+
{
+    "name": "Original",
+    "version": "4.8",
+    "description": "Original phpMyAdmin theme",
+    "author": "phpMyAdmin developers",
+    "url": "https://www.phpmyadmin.net/",
+    "supports": ["4.8"]
+}
+
+
+
+
+

Sharing images

+

If you do not want to use your own symbols and buttons, remove the +directory “img” in “your_theme_name”. phpMyAdmin will use the +default icons and buttons (from the system-theme pmahomme).

+
+
+
+ + +
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/php/apps/phpmyadmin49/html/doc/html/transformations.html b/php/apps/phpmyadmin49/html/doc/html/transformations.html new file mode 100644 index 00000000..a187f05d --- /dev/null +++ b/php/apps/phpmyadmin49/html/doc/html/transformations.html @@ -0,0 +1,245 @@ + + + + + + + + Transformations — phpMyAdmin 4.9.1 documentation + + + + + + + + + + + + + + + + + + + +
+
+
+
+ +
+

Transformations

+
+

Note

+

You need to have configured the phpMyAdmin configuration storage for using transformations +feature.

+
+
+

Introduction

+

To enable transformations, you have to setup the column_info +table and the proper directives. Please see the Configuration on how to do so.

+

You can apply different transformations to the contents of each +column. The transformation will take the content of each column and +transform it with certain rules defined in the selected +transformation.

+

Say you have a column ‘filename’ which contains a filename. Normally +you would see in phpMyAdmin only this filename. Using transformations +you can transform that filename into a HTML link, so you can click +inside of the phpMyAdmin structure on the column’s link and will see +the file displayed in a new browser window. Using transformation +options you can also specify strings to append/prepend to a string or +the format you want the output stored in.

+

For a general overview of all available transformations and their +options, you can consult your <www.your-host.com>/<your-install- +dir>/transformation_overview.php installation.

+

For a tutorial on how to effectively use transformations, see our +Link section on the +official phpMyAdmin homepage.

+
+
+

Usage

+

Go to your tbl_structure.php page (i.e. reached through clicking on +the ‘Structure’ link for a table). There click on “Change” (or change +icon) and there you will see three new fields at the end of the line. +They are called ‘MIME-type’, ‘Browser transformation’ and +‘Transformation options’.

+
    +
  • The field ‘MIME-type’ is a drop-down field. Select the MIME-type that +corresponds to the column’s contents. Please note that transformations +are inactive as long as no MIME-type is selected.
  • +
  • The field ‘Browser transformation’ is a drop-down field. You can +choose from a hopefully growing amount of pre-defined transformations. +See below for information on how to build your own transformation. +There are global transformations and mimetype-bound transformations. +Global transformations can be used for any mimetype. They will take +the mimetype, if necessary, into regard. Mimetype-bound +transformations usually only operate on a certain mimetype. There are +transformations which operate on the main mimetype (like ‘image’), +which will most likely take the subtype into regard, and those who +only operate on a specific subtype (like ‘image/jpeg’). You can use +transformations on mimetypes for which the function was not defined +for. There is no security check for you selected the right +transformation, so take care of what the output will be like.
  • +
  • The field ‘Transformation options’ is a free-type textfield. You have +to enter transform-function specific options here. Usually the +transforms can operate with default options, but it is generally a +good idea to look up the overview to see which options are necessary. +Much like the ENUM/SET-Fields, you have to split up several options +using the format ‘a’,’b’,’c’,...(NOTE THE MISSING BLANKS). This is +because internally the options will be parsed as an array, leaving the +first value the first element in the array, and so forth. If you want +to specify a MIME character set you can define it in the +transformation_options. You have to put that outside of the pre- +defined options of the specific mime-transform, as the last value of +the set. Use the format “’; charset=XXX’”. If you use a transform, for +which you can specify 2 options and you want to append a character +set, enter “‘first parameter’,’second parameter’,’charset=us-ascii’”. +You can, however use the defaults for the parameters: “’‘,’‘,’charset +=us-ascii’”. The default options can be configured using +$cfg['DefaultTransformations']
  • +
+
+
+

File structure

+

All specific transformations for mimetypes are defined through class +files in the directory ‘libraries/classes/Plugins/Transformations/’. Each of +them extends a certain transformation abstract class declared in +libraries/classes/Plugins/Transformations/Abs.

+

They are stored in files to ease up customization and easy adding of +new transformations.

+

Because the user cannot enter own mimetypes, it is kept sure that +transformations always work. It makes no sense to apply a +transformation to a mimetype the transform-function doesn’t know to +handle.

+

There is a file called ‘transformations.lib.php‘ that provides some +basic functions which can be included by any other transform function.

+

The file name convention is [Mimetype]_[Subtype]_[Transformation +Name].class.php, while the abtract class that it extends has the +name [Transformation Name]TransformationsPlugin. All of the +methods that have to be implemented by a transformations plug-in are:

+
    +
  1. getMIMEType() and getMIMESubtype() in the main class;
  2. +
  3. getName(), getInfo() and applyTransformation() in the abstract class +it extends.
  4. +
+

The getMIMEType(), getMIMESubtype() and getName() methods return the +name of the MIME type, MIME Subtype and transformation accordingly. +getInfo() returns the transformation’s description and possible +options it may receive and applyTransformation() is the method that +does the actual work of the transformation plug-in.

+

Please see the libraries/classes/Plugins/Transformations/TEMPLATE and +libraries/classes/Plugins/Transformations/TEMPLATE_ABSTRACT files for adding +your own transformation plug-in. You can also generate a new +transformation plug-in (with or without the abstract transformation +class), by using +scripts/transformations_generator_plugin.sh or +scripts/transformations_generator_main_class.sh.

+

The applyTransformation() method always gets passed three variables:

+
    +
  1. $buffer - Contains the text inside of the column. This is the +text, you want to transform.
  2. +
  3. $options - Contains any user-passed options to a transform +function as an array.
  4. +
  5. $meta - Contains an object with information about your column. The +data is drawn from the output of the mysql_fetch_field() function. This means, all +object properties described on the manual page are available in this +variable and can be used to transform a column accordingly to +unsigned/zerofill/not_null/... properties. The $meta->mimetype +variable contains the original MIME-type of the column (i.e. +‘text/plain’, ‘image/jpeg’ etc.)
  6. +
+
+
+ + +
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/php/apps/phpmyadmin49/html/doc/html/two_factor.html b/php/apps/phpmyadmin49/html/doc/html/two_factor.html new file mode 100644 index 00000000..c33287e3 --- /dev/null +++ b/php/apps/phpmyadmin49/html/doc/html/two_factor.html @@ -0,0 +1,180 @@ + + + + + + + + Two-factor authentication — phpMyAdmin 4.9.1 documentation + + + + + + + + + + + + + + + + + + + +
+
+
+
+ +
+

Two-factor authentication

+
+

New in version 4.8.0.

+
+

Since phpMyAdmin 4.8.0 you can configure two-factor authentication to be +used when logging in. To use this, you first need to configure the +phpMyAdmin configuration storage. Once this is done, every user can opt-in for the second +authentication factor in the Settings.

+

When running phpMyAdmin from the Git source repository, the dependencies must be installed +manually; the typical way of doing so is with the command:

+
composer require pragmarx/google2fa bacon/bacon-qr-code
+
+
+

Or when using a hardware security key with FIDO U2F:

+
composer require samyoul/u2f-php-server
+
+
+
+

Authentication Application (2FA)

+

Using an application for authentication is a quite common approach based on HOTP and +TOTP. +It is based on transmitting a private key from phpMyAdmin to the authentication +application and the application is then able to generate one time codes based +on this key. The easiest way to enter the key in to the application from phpMyAdmin is +through scanning a QR code.

+

There are dozens of applications available for mobile phones to implement these +standards, the most widely used include:

+ +
+
+

Hardware Security Key (FIDO U2F)

+

Using hardware tokens is considered to be more secure than a software based +solution. phpMyAdmin supports FIDO U2F +tokens.

+

There are several manufacturers of these tokens, for example:

+ +
+
+

Simple two-factor authentication

+

This authentication is included for testing and demonstration purposes only as +it really does not provide two-factor authentication, it just asks the user to confirm login by +clicking on the button.

+

It should not be used in the production and is disabled unless +$cfg['DBG']['simple2fa'] is set.

+
+
+ + +
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/php/apps/phpmyadmin49/html/doc/html/user.html b/php/apps/phpmyadmin49/html/doc/html/user.html new file mode 100644 index 00000000..a02328bd --- /dev/null +++ b/php/apps/phpmyadmin49/html/doc/html/user.html @@ -0,0 +1,169 @@ + + + + + + + + User Guide — phpMyAdmin 4.9.1 documentation + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/php/apps/phpmyadmin49/html/doc/html/vendors.html b/php/apps/phpmyadmin49/html/doc/html/vendors.html new file mode 100644 index 00000000..72af36a1 --- /dev/null +++ b/php/apps/phpmyadmin49/html/doc/html/vendors.html @@ -0,0 +1,145 @@ + + + + + + + + Distributing and packaging phpMyAdmin — phpMyAdmin 4.9.1 documentation + + + + + + + + + + + + + + + + + + +
+
+
+
+ +
+

Distributing and packaging phpMyAdmin

+

This document is intended to give advices to people who want to +redistribute phpMyAdmin inside other software package such as Linux +distribution or some all in one package including web server and MySQL +server.

+

Generally you can customize some basic aspects (paths to some files and +behavior) in libraries/vendor_config.php.

+

For example if you want setup script to generate config file in var, change +SETUP_CONFIG_FILE to /var/lib/phpmyadmin/config.inc.php and you +will also probably want to skip directory writable check, so set +SETUP_DIR_WRITABLE to false.

+
+

External libraries

+

phpMyAdmin includes several external libraries, you might want to +replace them with system ones if they are available, but please note +that you should test whether version you provide is compatible with the +one we ship.

+

Currently known list of external libraries:

+
+
js/jquery
+
jQuery js framework and various jQuery based libraries.
+
vendor/
+
The download kit includes various Composer packages as +dependencies.
+
+
+
+ + +
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/php/apps/phpmyadmin49/html/error_report.php b/php/apps/phpmyadmin49/html/error_report.php new file mode 100644 index 00000000..3e9c9a6b --- /dev/null +++ b/php/apps/phpmyadmin49/html/error_report.php @@ -0,0 +1,135 @@ += 3 + && ($_SESSION['prev_error_subm_time']-time()) <= 3000 + ) { + $_SESSION['error_subm_count'] = 0; + $_SESSION['prev_errors'] = ''; + $response->addJSON('_stopErrorReportLoop', '1'); + } else { + $_SESSION['prev_error_subm_time'] = time(); + $_SESSION['error_subm_count'] = ( + (isset($_SESSION['error_subm_count'])) + ? ($_SESSION['error_subm_count']+1) + : (0) + ); + } + } + $reportData = $errorReport->getData($_POST['exception_type']); + // report if and only if there were 'actual' errors. + if (count($reportData) > 0) { + $server_response = $errorReport->send($reportData); + if (! is_string($server_response)) { + $success = false; + } else { + $decoded_response = json_decode($server_response, true); + $success = !empty($decoded_response) ? + $decoded_response["success"] : false; + } + + /* Message to show to the user */ + if ($success) { + if ((isset($_POST['automatic']) + && $_POST['automatic'] === "true") + || $GLOBALS['cfg']['SendErrorReports'] == 'always' + ) { + $msg = __( + 'An error has been detected and an error report has been ' + . 'automatically submitted based on your settings.' + ); + } else { + $msg = __('Thank you for submitting this report.'); + } + } else { + $msg = __( + 'An error has been detected and an error report has been ' + . 'generated but failed to be sent.' + ) + . ' ' + . __( + 'If you experience any ' + . 'problems please submit a bug report manually.' + ); + } + $msg .= ' ' . __('You may want to refresh the page.'); + + /* Create message object */ + if ($success) { + $msg = Message::notice($msg); + } else { + $msg = Message::error($msg); + } + + /* Add message to response */ + if ($response->isAjax()) { + if ($_POST['exception_type'] == 'js') { + $response->addJSON('message', $msg); + } else { + $response->addJSON('_errSubmitMsg', $msg); + } + } elseif ($_POST['exception_type'] == 'php') { + $jsCode = 'PMA_ajaxShowMessage("
' + . $msg + . '
", false);'; + $response->getFooter()->getScripts()->addCode($jsCode); + } + + if ($_POST['exception_type'] == 'php') { + // clear previous errors & save new ones. + $GLOBALS['error_handler']->savePreviousErrors(); + } + + /* Persist always send settings */ + if (isset($_POST['always_send']) + && $_POST['always_send'] === "true" + ) { + $userPreferences = new UserPreferences(); + $userPreferences->persistOption("SendErrorReports", "always", "ask"); + } + } +} elseif (! empty($_POST['get_settings'])) { + $response->addJSON('report_setting', $GLOBALS['cfg']['SendErrorReports']); +} else { + if ($_POST['exception_type'] == 'js') { + $response->addHTML($errorReport->getForm()); + } else { + // clear previous errors & save new ones. + $GLOBALS['error_handler']->savePreviousErrors(); + } +} diff --git a/php/apps/phpmyadmin49/html/export.php b/php/apps/phpmyadmin49/html/export.php new file mode 100644 index 00000000..c9338333 --- /dev/null +++ b/php/apps/phpmyadmin49/html/export.php @@ -0,0 +1,555 @@ +getHeader(); +$scripts = $header->getScripts(); +$scripts->addFile('export_output.js'); + +//check if it's the GET request to check export time out +if (isset($_GET['check_time_out'])) { + if (isset($_SESSION['pma_export_error'])) { + $err = $_SESSION['pma_export_error']; + unset($_SESSION['pma_export_error']); + echo "timeout"; + } else { + echo "success"; + } + exit; +} + +/** + * Sets globals from $_POST + * + * - Please keep the parameters in order of their appearance in the form + * - Some of these parameters are not used, as the code below directly + * verifies from the superglobal $_POST or $_REQUEST + * TODO: this should be removed to avoid passing user input to GLOBALS + * without checking + */ +$post_params = array( + 'db', + 'table', + 'what', + 'single_table', + 'export_type', + 'export_method', + 'quick_or_custom', + 'db_select', + 'table_select', + 'table_structure', + 'table_data', + 'limit_to', + 'limit_from', + 'allrows', + 'lock_tables', + 'output_format', + 'filename_template', + 'maxsize', + 'remember_template', + 'charset', + 'compression', + 'as_separate_files', + 'knjenc', + 'xkana', + 'htmlword_structure_or_data', + 'htmlword_null', + 'htmlword_columns', + 'mediawiki_headers', + 'mediawiki_structure_or_data', + 'mediawiki_caption', + 'pdf_structure_or_data', + 'odt_structure_or_data', + 'odt_relation', + 'odt_comments', + 'odt_mime', + 'odt_columns', + 'odt_null', + 'codegen_structure_or_data', + 'codegen_format', + 'excel_null', + 'excel_removeCRLF', + 'excel_columns', + 'excel_edition', + 'excel_structure_or_data', + 'yaml_structure_or_data', + 'ods_null', + 'ods_structure_or_data', + 'ods_columns', + 'json_structure_or_data', + 'json_pretty_print', + 'json_unicode', + 'xml_structure_or_data', + 'xml_export_events', + 'xml_export_functions', + 'xml_export_procedures', + 'xml_export_tables', + 'xml_export_triggers', + 'xml_export_views', + 'xml_export_contents', + 'texytext_structure_or_data', + 'texytext_columns', + 'texytext_null', + 'phparray_structure_or_data', + 'sql_include_comments', + 'sql_header_comment', + 'sql_dates', + 'sql_relation', + 'sql_mime', + 'sql_use_transaction', + 'sql_disable_fk', + 'sql_compatibility', + 'sql_structure_or_data', + 'sql_create_database', + 'sql_drop_table', + 'sql_procedure_function', + 'sql_create_table', + 'sql_create_view', + 'sql_create_trigger', + 'sql_if_not_exists', + 'sql_auto_increment', + 'sql_backquotes', + 'sql_truncate', + 'sql_delayed', + 'sql_ignore', + 'sql_type', + 'sql_insert_syntax', + 'sql_max_query_size', + 'sql_hex_for_binary', + 'sql_utc_time', + 'sql_drop_database', + 'sql_views_as_tables', + 'sql_metadata', + 'csv_separator', + 'csv_enclosed', + 'csv_escaped', + 'csv_terminated', + 'csv_null', + 'csv_removeCRLF', + 'csv_columns', + 'csv_structure_or_data', + // csv_replace should have been here but we use it directly from $_POST + 'latex_caption', + 'latex_structure_or_data', + 'latex_structure_caption', + 'latex_structure_continued_caption', + 'latex_structure_label', + 'latex_relation', + 'latex_comments', + 'latex_mime', + 'latex_columns', + 'latex_data_caption', + 'latex_data_continued_caption', + 'latex_data_label', + 'latex_null', + 'aliases' +); + +foreach ($post_params as $one_post_param) { + if (isset($_POST[$one_post_param])) { + $GLOBALS[$one_post_param] = $_POST[$one_post_param]; + } +} + +$table = $GLOBALS['table']; + +PhpMyAdmin\Util::checkParameters(array('what', 'export_type')); + +// sanitize this parameter which will be used below in a file inclusion +$what = Core::securePath($_POST['what']); + +// export class instance, not array of properties, as before +/* @var $export_plugin ExportPlugin */ +$export_plugin = Plugins::getPlugin( + "export", + $what, + 'libraries/classes/Plugins/Export/', + array( + 'export_type' => $export_type, + 'single_table' => isset($single_table) + ) +); + +// Check export type +if (empty($export_plugin)) { + Core::fatalError(__('Bad type!')); +} + +/** + * valid compression methods + */ +$compression_methods = array( + 'zip', + 'gzip' +); + +/** + * init and variable checking + */ +$compression = false; +$onserver = false; +$save_on_server = false; +$buffer_needed = false; +$back_button = ''; +$refreshButton = ''; +$save_filename = ''; +$file_handle = ''; +$err_url = ''; +$filename = ''; +$separate_files = ''; + +// Is it a quick or custom export? +if (isset($_POST['quick_or_custom']) + && $_POST['quick_or_custom'] == 'quick' +) { + $quick_export = true; +} else { + $quick_export = false; +} + +if ($_POST['output_format'] == 'astext') { + $asfile = false; +} else { + $asfile = true; + if (isset($_POST['as_separate_files']) + && ! empty($_POST['as_separate_files']) + ) { + if (isset($_POST['compression']) + && ! empty($_POST['compression']) + && $_POST['compression'] == 'zip' + ) { + $separate_files = $_POST['as_separate_files']; + } + } + if (in_array($_POST['compression'], $compression_methods)) { + $compression = $_POST['compression']; + $buffer_needed = true; + } + if (($quick_export && ! empty($_POST['quick_export_onserver'])) + || (! $quick_export && ! empty($_POST['onserver'])) + ) { + if ($quick_export) { + $onserver = $_POST['quick_export_onserver']; + } else { + $onserver = $_POST['onserver']; + } + // Will we save dump on server? + $save_on_server = ! empty($cfg['SaveDir']) && $onserver; + } +} + +/** + * If we are sending the export file (as opposed to just displaying it + * as text), we have to bypass the usual PhpMyAdmin\Response mechanism + */ +if (isset($_POST['output_format']) && $_POST['output_format'] == 'sendit' && ! $save_on_server) { + $response->disable(); + //Disable all active buffers (see: ob_get_status(true) at this point) + while (@ob_end_clean()); +} + +// Generate error url and check for needed variables +if ($export_type == 'server') { + $err_url = 'server_export.php' . Url::getCommon(); +} elseif ($export_type == 'database' && strlen($db) > 0) { + $err_url = 'db_export.php' . Url::getCommon(array('db' => $db)); + // Check if we have something to export + if (isset($table_select)) { + $tables = $table_select; + } else { + $tables = array(); + } +} elseif ($export_type == 'table' && strlen($db) > 0 && strlen($table) > 0) { + $err_url = 'tbl_export.php' . Url::getCommon( + array( + 'db' => $db, 'table' => $table + ) + ); +} else { + Core::fatalError(__('Bad parameters!')); +} + +// Merge SQL Query aliases with Export aliases from +// export page, Export page aliases are given more +// preference over SQL Query aliases. +$parser = new \PhpMyAdmin\SqlParser\Parser($sql_query); +$aliases = array(); +if ((!empty($parser->statements[0])) + && ($parser->statements[0] instanceof \PhpMyAdmin\SqlParser\Statements\SelectStatement) +) { + $aliases = \PhpMyAdmin\SqlParser\Utils\Misc::getAliases($parser->statements[0], $db); +} +if (!empty($_POST['aliases'])) { + $aliases = Export::mergeAliases($aliases, $_POST['aliases']); + $_SESSION['tmpval']['aliases'] = $_POST['aliases']; +} + +/** + * Increase time limit for script execution and initializes some variables + */ +Util::setTimeLimit(); +if (! empty($cfg['MemoryLimit'])) { + ini_set('memory_limit', $cfg['MemoryLimit']); +} +register_shutdown_function('PhpMyAdmin\Export::shutdown'); +// Start with empty buffer +$dump_buffer = ''; +$dump_buffer_len = 0; + +// Array of dump_buffers - used in separate file exports +$dump_buffer_objects = array(); + +// We send fake headers to avoid browser timeout when buffering +$time_start = time(); + +// Defines the default format. +// For SQL always use \n as MySQL wants this on all platforms. +if ($what == 'sql') { + $crlf = "\n"; +} else { + $crlf = PHP_EOL; +} + +$output_kanji_conversion = Encoding::canConvertKanji(); + +// Do we need to convert charset? +$output_charset_conversion = $asfile + && Encoding::isSupported() + && isset($charset) && $charset != 'utf-8'; + +// Use on the fly compression? +$GLOBALS['onfly_compression'] = $GLOBALS['cfg']['CompressOnFly'] + && $compression == 'gzip'; +if ($GLOBALS['onfly_compression']) { + $GLOBALS['memory_limit'] = Export::getMemoryLimit(); +} + +// Generate filename and mime type if needed +if ($asfile) { + if (empty($remember_template)) { + $remember_template = ''; + } + list($filename, $mime_type) = Export::getFilenameAndMimetype( + $export_type, $remember_template, $export_plugin, $compression, + $filename_template + ); +} else { + $mime_type = ''; +} + +// Open file on server if needed +if ($save_on_server) { + list($save_filename, $message, $file_handle) = Export::openFile( + $filename, $quick_export + ); + + // problem opening export file on server? + if (! empty($message)) { + Export::showPage($db, $table, $export_type); + } +} else { + /** + * Send headers depending on whether the user chose to download a dump file + * or not + */ + if ($asfile) { + // Download + // (avoid rewriting data containing HTML with anchors and forms; + // this was reported to happen under Plesk) + ini_set('url_rewriter.tags', ''); + $filename = Sanitize::sanitizeFilename($filename); + + Core::downloadHeader($filename, $mime_type); + } else { + // HTML + if ($export_type == 'database') { + $num_tables = count($tables); + if ($num_tables == 0) { + $message = PhpMyAdmin\Message::error( + __('No tables found in database.') + ); + $active_page = 'db_export.php'; + include 'db_export.php'; + exit(); + } + } + list($html, $back_button, $refreshButton) = Export::getHtmlForDisplayedExportHeader( + $export_type, $db, $table + ); + echo $html; + unset($html); + } // end download +} + +$relation = new Relation(); + +// Fake loop just to allow skip of remain of this code by break, I'd really +// need exceptions here :-) +do { + // Re - initialize + $dump_buffer = ''; + $dump_buffer_len = 0; + + // Add possibly some comments to export + if (! $export_plugin->exportHeader()) { + break; + } + + // Will we need relation & co. setup? + $do_relation = isset($GLOBALS[$what . '_relation']); + $do_comments = isset($GLOBALS[$what . '_include_comments']) + || isset($GLOBALS[$what . '_comments']); + $do_mime = isset($GLOBALS[$what . '_mime']); + if ($do_relation || $do_comments || $do_mime) { + $cfgRelation = $relation->getRelationsParam(); + } + + // Include dates in export? + $do_dates = isset($GLOBALS[$what . '_dates']); + + $whatStrucOrData = $GLOBALS[$what . '_structure_or_data']; + + /** + * Builds the dump + */ + if ($export_type == 'server') { + if (! isset($db_select)) { + $db_select = ''; + } + Export::exportServer( + $db_select, $whatStrucOrData, $export_plugin, $crlf, $err_url, + $export_type, $do_relation, $do_comments, $do_mime, $do_dates, + $aliases, $separate_files + ); + } elseif ($export_type == 'database') { + if (!isset($table_structure) || !is_array($table_structure)) { + $table_structure = array(); + } + if (!isset($table_data) || !is_array($table_data)) { + $table_data = array(); + } + if (!empty($_POST['structure_or_data_forced'])) { + $table_structure = $tables; + $table_data = $tables; + } + if (isset($lock_tables)) { + Export::lockTables($db, $tables, "READ"); + try { + Export::exportDatabase( + $db, $tables, $whatStrucOrData, $table_structure, + $table_data, $export_plugin, $crlf, $err_url, $export_type, + $do_relation, $do_comments, $do_mime, $do_dates, $aliases, + $separate_files + ); + } finally { + Export::unlockTables(); + } + } else { + Export::exportDatabase( + $db, $tables, $whatStrucOrData, $table_structure, $table_data, + $export_plugin, $crlf, $err_url, $export_type, $do_relation, + $do_comments, $do_mime, $do_dates, $aliases, $separate_files + ); + } + } else { + // We export just one table + // $allrows comes from the form when "Dump all rows" has been selected + if (! isset($allrows)) { + $allrows = ''; + } + if (! isset($limit_to)) { + $limit_to = 0; + } + if (! isset($limit_from)) { + $limit_from = 0; + } + if (isset($lock_tables)) { + try { + Export::lockTables($db, array($table), "READ"); + Export::exportTable( + $db, $table, $whatStrucOrData, $export_plugin, $crlf, + $err_url, $export_type, $do_relation, $do_comments, + $do_mime, $do_dates, $allrows, $limit_to, $limit_from, + $sql_query, $aliases + ); + } finally { + Export::unlockTables(); + } + } else { + Export::exportTable( + $db, $table, $whatStrucOrData, $export_plugin, $crlf, $err_url, + $export_type, $do_relation, $do_comments, $do_mime, $do_dates, + $allrows, $limit_to, $limit_from, $sql_query, $aliases + ); + } + } + if (! $export_plugin->exportFooter()) { + break; + } + +} while (false); +// End of fake loop + +if ($save_on_server && ! empty($message)) { + Export::showPage($db, $table, $export_type); +} + +/** + * Send the dump as a file... + */ +if (empty($asfile)) { + echo Export::getHtmlForDisplayedExportFooter($back_button, $refreshButton); + return; +} // end if + +// Convert the charset if required. +if ($output_charset_conversion) { + $dump_buffer = Encoding::convertString( + 'utf-8', + $GLOBALS['charset'], + $dump_buffer + ); +} + +// Compression needed? +if ($compression) { + if (! empty($separate_files)) { + $dump_buffer = Export::compress( + $dump_buffer_objects, $compression, $filename + ); + } else { + $dump_buffer = Export::compress($dump_buffer, $compression, $filename); + } + +} + +/* If we saved on server, we have to close file now */ +if ($save_on_server) { + $message = Export::closeFile( + $file_handle, $dump_buffer, $save_filename + ); + Export::showPage($db, $table, $export_type); +} else { + echo $dump_buffer; +} diff --git a/php/apps/phpmyadmin49/html/favicon.ico b/php/apps/phpmyadmin49/html/favicon.ico new file mode 100644 index 00000000..fb156b22 Binary files /dev/null and b/php/apps/phpmyadmin49/html/favicon.ico differ diff --git a/php/apps/phpmyadmin49/html/gis_data_editor.php b/php/apps/phpmyadmin49/html/gis_data_editor.php new file mode 100644 index 00000000..f8403f16 --- /dev/null +++ b/php/apps/phpmyadmin49/html/gis_data_editor.php @@ -0,0 +1,436 @@ +generateParams($_POST['value']) + ); +} + +// Generate Well Known Text +$srid = (isset($gis_data['srid']) && $gis_data['srid'] != '') + ? htmlspecialchars($gis_data['srid']) : 0; +$wkt = $gis_obj->generateWkt($gis_data, 0); +$wkt_with_zero = $gis_obj->generateWkt($gis_data, 0, '0'); +$result = "'" . $wkt . "'," . $srid; + +// Generate SVG based visualization +$visualizationSettings = array( + 'width' => 450, + 'height' => 300, + 'spatialColumn' => 'wkt', + 'mysqlVersion' => $GLOBALS['dbi']->getVersion() +); +$data = array(array('wkt' => $wkt_with_zero, 'srid' => $srid)); +$visualization = GisVisualization::getByData($data, $visualizationSettings) + ->toImage('svg'); + +$open_layers = GisVisualization::getByData($data, $visualizationSettings) + ->asOl(); + +// If the call is to update the WKT and visualization make an AJAX response +if (isset($_POST['generate']) && $_POST['generate'] == true) { + $extra_data = array( + 'result' => $result, + 'visualization' => $visualization, + 'openLayers' => $open_layers, + ); + $response = Response::getInstance(); + $response->addJSON($extra_data); + exit; +} + +ob_start(); + +echo '
'; +echo ''; +echo '
'; + +echo '

'; +printf( + __('Value for the column "%s"'), + htmlspecialchars($_POST['field']) +); +echo '

'; + +echo ''; +// The input field to which the final result should be added +// and corresponding null checkbox +if (isset($_POST['input_name'])) { + echo ''; +} +echo Url::getHiddenInputs(); + +echo ''; +echo '
'; +echo $visualization; +echo '
'; + +// No not remove inline style or it will cause "Cannot read property 'w' of null" on GIS editor map init +echo '
'; +echo '
'; + +echo '
'; +echo ''; +echo ''; +echo '
'; + +echo ''; +echo ''; + + +echo ''; +echo '
'; +echo ''; +echo '    '; +/* l10n: Spatial Reference System Identifier */ +echo ''; +echo ''; +echo '
'; +echo ''; + +echo ''; +echo '
'; + +$geom_count = 1; +if ($geom_type == 'GEOMETRYCOLLECTION') { + $geom_count = (isset($gis_data[$geom_type]['geom_count'])) + ? intval($gis_data[$geom_type]['geom_count']) : 1; + if (isset($gis_data[$geom_type]['add_geom'])) { + $geom_count++; + } + echo ''; +} + +for ($a = 0; $a < $geom_count; $a++) { + if (! isset($gis_data[$a])) { + continue; + } + + if ($geom_type == 'GEOMETRYCOLLECTION') { + echo '

'; + printf(__('Geometry %d:'), $a + 1); + echo '
'; + if (isset($gis_data[$a]['gis_type'])) { + $type = htmlspecialchars($gis_data[$a]['gis_type']); + } else { + $type = $gis_types[0]; + } + echo ''; + } else { + $type = $geom_type; + } + + if ($type == 'POINT') { + echo '
'; + echo __('Point:'); + echo ''; + echo ''; + echo ''; + echo ''; + + } elseif ($type == 'MULTIPOINT' || $type == 'LINESTRING') { + $no_of_points = isset($gis_data[$a][$type]['no_of_points']) + ? intval($gis_data[$a][$type]['no_of_points']) : 1; + if ($type == 'LINESTRING' && $no_of_points < 2) { + $no_of_points = 2; + } + if ($type == 'MULTIPOINT' && $no_of_points < 1) { + $no_of_points = 1; + } + + if (isset($gis_data[$a][$type]['add_point'])) { + $no_of_points++; + } + echo ''; + + for ($i = 0; $i < $no_of_points; $i++) { + echo '
'; + printf(__('Point %d'), $i + 1); + echo ': '; + echo ''; + echo ''; + echo ''; + echo ''; + } + echo ''; + + } elseif ($type == 'MULTILINESTRING' || $type == 'POLYGON') { + $no_of_lines = isset($gis_data[$a][$type]['no_of_lines']) + ? intval($gis_data[$a][$type]['no_of_lines']) : 1; + if ($no_of_lines < 1) { + $no_of_lines = 1; + } + if (isset($gis_data[$a][$type]['add_line'])) { + $no_of_lines++; + } + echo ''; + + for ($i = 0; $i < $no_of_lines; $i++) { + echo '
'; + if ($type == 'MULTILINESTRING') { + printf(__('Linestring %d:'), $i + 1); + } else { + if ($i == 0) { + echo __('Outer ring:'); + } else { + printf(__('Inner ring %d:'), $i); + } + } + + $no_of_points = isset($gis_data[$a][$type][$i]['no_of_points']) + ? intval($gis_data[$a][$type][$i]['no_of_points']) : 2; + if ($type == 'MULTILINESTRING' && $no_of_points < 2) { + $no_of_points = 2; + } + if ($type == 'POLYGON' && $no_of_points < 4) { + $no_of_points = 4; + } + if (isset($gis_data[$a][$type][$i]['add_point'])) { + $no_of_points++; + } + echo ''; + + for ($j = 0; $j < $no_of_points; $j++) { + echo('
'); + printf(__('Point %d'), $j + 1); + echo ': '; + echo ''; + echo ''; + echo ''; + echo ''; + } + echo ''; + } + $caption = ($type == 'MULTILINESTRING') + ? __('Add a linestring') + : __('Add an inner ring'); + echo '
'; + echo ''; + + } elseif ($type == 'MULTIPOLYGON') { + $no_of_polygons = isset($gis_data[$a][$type]['no_of_polygons']) + ? intval($gis_data[$a][$type]['no_of_polygons']) : 1; + if ($no_of_polygons < 1) { + $no_of_polygons = 1; + } + if (isset($gis_data[$a][$type]['add_polygon'])) { + $no_of_polygons++; + } + echo ''; + + for ($k = 0; $k < $no_of_polygons; $k++) { + echo '
'; + printf(__('Polygon %d:'), $k + 1); + $no_of_lines = isset($gis_data[$a][$type][$k]['no_of_lines']) + ? intval($gis_data[$a][$type][$k]['no_of_lines']) : 1; + if ($no_of_lines < 1) { + $no_of_lines = 1; + } + if (isset($gis_data[$a][$type][$k]['add_line'])) { + $no_of_lines++; + } + echo ''; + + for ($i = 0; $i < $no_of_lines; $i++) { + echo '

'; + if ($i == 0) { + echo __('Outer ring:'); + } else { + printf(__('Inner ring %d:'), $i); + } + + $no_of_points = isset($gis_data[$a][$type][$k][$i]['no_of_points']) + ? intval($gis_data[$a][$type][$k][$i]['no_of_points']) : 4; + if ($no_of_points < 4) { + $no_of_points = 4; + } + if (isset($gis_data[$a][$type][$k][$i]['add_point'])) { + $no_of_points++; + } + echo ''; + + for ($j = 0; $j < $no_of_points; $j++) { + echo '
'; + printf(__('Point %d'), $j + 1); + echo ': '; + echo ''; + echo ''; + echo ''; + echo ''; + } + echo ''; + } + echo '
'; + echo ''; + echo '
'; + } + echo '
'; + echo ''; + } +} +if ($geom_type == 'GEOMETRYCOLLECTION') { + echo '

'; + echo ''; +} +echo '
'; +echo ''; + +echo '
'; +echo ''; + +echo '
'; +echo '

' , __('Output') , '

'; +echo '

'; +echo __( + 'Choose "ST_GeomFromText" from the "Function" column and paste the' + . ' string below into the "Value" field.' +); +echo '

'; +echo ''; +echo '
'; + +echo '
'; +echo '
'; + +Response::getInstance()->addJSON('gis_editor', ob_get_contents()); +ob_end_clean(); diff --git a/php/apps/phpmyadmin49/html/import.php b/php/apps/phpmyadmin49/html/import.php new file mode 100644 index 00000000..7bd3b8f2 --- /dev/null +++ b/php/apps/phpmyadmin49/html/import.php @@ -0,0 +1,784 @@ +addJSON( + 'console_message_bookmark', PhpMyAdmin\Console::getBookmarkContent() + ); + exit; +} +// If it's a console bookmark add request +if (isset($_POST['console_bookmark_add'])) { + if (isset($_POST['label']) && isset($_POST['db']) + && isset($_POST['bookmark_query']) && isset($_POST['shared']) + ) { + $cfgBookmark = Bookmark::getParams($GLOBALS['cfg']['Server']['user']); + $bookmarkFields = array( + 'bkm_database' => $_POST['db'], + 'bkm_user' => $cfgBookmark['user'], + 'bkm_sql_query' => $_POST['bookmark_query'], + 'bkm_label' => $_POST['label'] + ); + $isShared = ($_POST['shared'] == 'true' ? true : false); + $bookmark = Bookmark::createBookmark( + $GLOBALS['dbi'], + $GLOBALS['cfg']['Server']['user'], + $bookmarkFields, + $isShared + ); + if ($bookmark !== false && $bookmark->save()) { + $response->addJSON('message', __('Succeeded')); + $response->addJSON('data', $bookmarkFields); + $response->addJSON('isShared', $isShared); + } else { + $response->addJSON('message', __('Failed')); + } + die(); + } else { + $response->addJSON('message', __('Incomplete params')); + die(); + } +} + +$format = ''; + +/** + * Sets globals from $_POST + */ +$post_params = array( + 'charset_of_file', + 'format', + 'import_type', + 'is_js_confirmed', + 'MAX_FILE_SIZE', + 'message_to_show', + 'noplugin', + 'skip_queries', + 'local_import_file' +); + +foreach ($post_params as $one_post_param) { + if (isset($_POST[$one_post_param])) { + $GLOBALS[$one_post_param] = $_POST[$one_post_param]; + } +} + +// reset import messages for ajax request +$_SESSION['Import_message']['message'] = null; +$_SESSION['Import_message']['go_back_url'] = null; +// default values +$GLOBALS['reload'] = false; + +// Use to identify current cycle is executing +// a multiquery statement or stored routine +if (!isset($_SESSION['is_multi_query'])) { + $_SESSION['is_multi_query'] = false; +} + +$ajax_reload = array(); +// Are we just executing plain query or sql file? +// (eg. non import, but query box/window run) +if (! empty($sql_query)) { + + // apply values for parameters + if (! empty($_POST['parameterized']) + && ! empty($_POST['parameters']) + && is_array($_POST['parameters']) + ) { + $parameters = $_POST['parameters']; + foreach ($parameters as $parameter => $replacement) { + $quoted = preg_quote($parameter, '/'); + // making sure that :param does not apply values to :param1 + $sql_query = preg_replace( + '/' . $quoted . '([^a-zA-Z0-9_])/', + $GLOBALS['dbi']->escapeString($replacement) . '${1}', + $sql_query + ); + // for parameters the appear at the end of the string + $sql_query = preg_replace( + '/' . $quoted . '$/', + $GLOBALS['dbi']->escapeString($replacement), + $sql_query + ); + } + } + + // run SQL query + $import_text = $sql_query; + $import_type = 'query'; + $format = 'sql'; + $_SESSION['sql_from_query_box'] = true; + + // If there is a request to ROLLBACK when finished. + if (isset($_POST['rollback_query'])) { + Import::handleRollbackRequest($import_text); + } + + // refresh navigation and main panels + if (preg_match('/^(DROP)\s+(VIEW|TABLE|DATABASE|SCHEMA)\s+/i', $sql_query)) { + $GLOBALS['reload'] = true; + $ajax_reload['reload'] = true; + } + + // refresh navigation panel only + if (preg_match( + '/^(CREATE|ALTER)\s+(VIEW|TABLE|DATABASE|SCHEMA)\s+/i', + $sql_query + )) { + $ajax_reload['reload'] = true; + } + + // do a dynamic reload if table is RENAMED + // (by sending the instruction to the AJAX response handler) + if (preg_match( + '/^RENAME\s+TABLE\s+(.*?)\s+TO\s+(.*?)($|;|\s)/i', + $sql_query, + $rename_table_names + )) { + $ajax_reload['reload'] = true; + $ajax_reload['table_name'] = PhpMyAdmin\Util::unQuote( + $rename_table_names[2] + ); + } + + $sql_query = ''; +} elseif (! empty($sql_file)) { + // run uploaded SQL file + $import_file = $sql_file; + $import_type = 'queryfile'; + $format = 'sql'; + unset($sql_file); +} elseif (! empty($_POST['id_bookmark'])) { + // run bookmark + $import_type = 'query'; + $format = 'sql'; +} + +// If we didn't get any parameters, either user called this directly, or +// upload limit has been reached, let's assume the second possibility. +if ($_POST == array() && $_GET == array()) { + $message = PhpMyAdmin\Message::error( + __( + 'You probably tried to upload a file that is too large. Please refer ' . + 'to %sdocumentation%s for a workaround for this limit.' + ) + ); + $message->addParam('[doc@faq1-16]'); + $message->addParam('[/doc]'); + + // so we can obtain the message + $_SESSION['Import_message']['message'] = $message->getDisplay(); + $_SESSION['Import_message']['go_back_url'] = $GLOBALS['goto']; + + $response->setRequestStatus(false); + $response->addJSON('message', $message); + + exit; // the footer is displayed automatically +} + +// Add console message id to response output +if (isset($_POST['console_message_id'])) { + $response->addJSON('console_message_id', $_POST['console_message_id']); +} + +/** + * Sets globals from $_POST patterns, for import plugins + * We only need to load the selected plugin + */ + +if (! in_array( + $format, + array( + 'csv', + 'ldi', + 'mediawiki', + 'ods', + 'shp', + 'sql', + 'xml' + ) +) +) { + // this should not happen for a normal user + // but only during an attack + Core::fatalError('Incorrect format parameter'); +} + +$post_patterns = array( + '/^force_file_/', + '/^' . $format . '_/' +); + +Core::setPostAsGlobal($post_patterns); + +// Check needed parameters +PhpMyAdmin\Util::checkParameters(array('import_type', 'format')); + +// We don't want anything special in format +$format = Core::securePath($format); + +if (strlen($table) > 0 && strlen($db) > 0) { + $urlparams = array('db' => $db, 'table' => $table); +} elseif (strlen($db) > 0) { + $urlparams = array('db' => $db); +} else { + $urlparams = array(); +} + +// Create error and goto url +if ($import_type == 'table') { + $goto = 'tbl_import.php'; +} elseif ($import_type == 'database') { + $goto = 'db_import.php'; +} elseif ($import_type == 'server') { + $goto = 'server_import.php'; +} else { + if (empty($goto) || !preg_match('@^(server|db|tbl)(_[a-z]*)*\.php$@i', $goto)) { + if (strlen($table) > 0 && strlen($db) > 0) { + $goto = 'tbl_structure.php'; + } elseif (strlen($db) > 0) { + $goto = 'db_structure.php'; + } else { + $goto = 'server_sql.php'; + } + } +} +$err_url = $goto . Url::getCommon($urlparams); +$_SESSION['Import_message']['go_back_url'] = $err_url; +// Avoid setting selflink to 'import.php' +// problem similar to bug 4276 +if (basename($_SERVER['SCRIPT_NAME']) === 'import.php') { + $_SERVER['SCRIPT_NAME'] = $goto; +} + + +if (strlen($db) > 0) { + $GLOBALS['dbi']->selectDb($db); +} + +Util::setTimeLimit(); +if (! empty($cfg['MemoryLimit'])) { + ini_set('memory_limit', $cfg['MemoryLimit']); +} + +$timestamp = time(); +if (isset($_POST['allow_interrupt'])) { + $maximum_time = ini_get('max_execution_time'); +} else { + $maximum_time = 0; +} + +// set default values +$timeout_passed = false; +$error = false; +$read_multiply = 1; +$finished = false; +$offset = 0; +$max_sql_len = 0; +$file_to_unlink = ''; +$sql_query = ''; +$sql_query_disabled = false; +$go_sql = false; +$executed_queries = 0; +$run_query = true; +$charset_conversion = false; +$reset_charset = false; +$bookmark_created = false; +$result = false; +$msg = 'Sorry an unexpected error happened!'; + +// Bookmark Support: get a query back from bookmark if required +if (! empty($_POST['id_bookmark'])) { + $id_bookmark = (int)$_POST['id_bookmark']; + switch ($_POST['action_bookmark']) { + case 0: // bookmarked query that have to be run + $bookmark = Bookmark::get( + $GLOBALS['dbi'], + $GLOBALS['cfg']['Server']['user'], + $db, + $id_bookmark, + 'id', + isset($_POST['action_bookmark_all']) + ); + + if (! empty($_POST['bookmark_variable'])) { + $import_text = $bookmark->applyVariables( + $_POST['bookmark_variable'] + ); + } else { + $import_text = $bookmark->getQuery(); + } + + // refresh navigation and main panels + if (preg_match( + '/^(DROP)\s+(VIEW|TABLE|DATABASE|SCHEMA)\s+/i', + $import_text + )) { + $GLOBALS['reload'] = true; + $ajax_reload['reload'] = true; + } + + // refresh navigation panel only + if (preg_match( + '/^(CREATE|ALTER)\s+(VIEW|TABLE|DATABASE|SCHEMA)\s+/i', + $import_text + ) + ) { + $ajax_reload['reload'] = true; + } + break; + case 1: // bookmarked query that have to be displayed + $bookmark = Bookmark::get( + $GLOBALS['dbi'], + $GLOBALS['cfg']['Server']['user'], + $db, + $id_bookmark + ); + $import_text = $bookmark->getQuery(); + if ($response->isAjax()) { + $message = PhpMyAdmin\Message::success(__('Showing bookmark')); + $response->setRequestStatus($message->isSuccess()); + $response->addJSON('message', $message); + $response->addJSON('sql_query', $import_text); + $response->addJSON('action_bookmark', $_POST['action_bookmark']); + exit; + } else { + $run_query = false; + } + break; + case 2: // bookmarked query that have to be deleted + $bookmark = Bookmark::get( + $GLOBALS['dbi'], + $GLOBALS['cfg']['Server']['user'], + $db, + $id_bookmark + ); + if (! empty($bookmark)) { + $bookmark->delete(); + if ($response->isAjax()) { + $message = PhpMyAdmin\Message::success( + __('The bookmark has been deleted.') + ); + $response->setRequestStatus($message->isSuccess()); + $response->addJSON('message', $message); + $response->addJSON('action_bookmark', $_POST['action_bookmark']); + $response->addJSON('id_bookmark', $id_bookmark); + exit; + } else { + $run_query = false; + $error = true; // this is kind of hack to skip processing the query + } + } + + break; + } +} // end bookmarks reading + +// Do no run query if we show PHP code +if (isset($GLOBALS['show_as_php'])) { + $run_query = false; + $go_sql = true; +} + +// We can not read all at once, otherwise we can run out of memory +$memory_limit = trim(ini_get('memory_limit')); +// 2 MB as default +if (empty($memory_limit)) { + $memory_limit = 2 * 1024 * 1024; +} +// In case no memory limit we work on 10MB chunks +if ($memory_limit == -1) { + $memory_limit = 10 * 1024 * 1024; +} + +// Calculate value of the limit +$memoryUnit = mb_strtolower(substr($memory_limit, -1)); +if ('m' == $memoryUnit) { + $memory_limit = (int)substr($memory_limit, 0, -1) * 1024 * 1024; +} elseif ('k' == $memoryUnit) { + $memory_limit = (int)substr($memory_limit, 0, -1) * 1024; +} elseif ('g' == $memoryUnit) { + $memory_limit = (int)substr($memory_limit, 0, -1) * 1024 * 1024 * 1024; +} else { + $memory_limit = (int)$memory_limit; +} + +// Just to be sure, there might be lot of memory needed for uncompression +$read_limit = $memory_limit / 8; + +// handle filenames +if (isset($_FILES['import_file'])) { + $import_file = $_FILES['import_file']['tmp_name']; +} +if (! empty($local_import_file) && ! empty($cfg['UploadDir'])) { + + // sanitize $local_import_file as it comes from a POST + $local_import_file = Core::securePath($local_import_file); + + $import_file = PhpMyAdmin\Util::userDir($cfg['UploadDir']) + . $local_import_file; + + /* + * Do not allow symlinks to avoid security issues + * (user can create symlink to file he can not access, + * but phpMyAdmin can). + */ + if (@is_link($import_file)) { + $import_file = 'none'; + } + +} elseif (empty($import_file) || ! is_uploaded_file($import_file)) { + $import_file = 'none'; +} + +// Do we have file to import? + +if ($import_file != 'none' && ! $error) { + /** + * Handle file compression + */ + $import_handle = new File($import_file); + $import_handle->checkUploadedFile(); + if ($import_handle->isError()) { + Import::stop($import_handle->getError()); + } + $import_handle->setDecompressContent(true); + $import_handle->open(); + if ($import_handle->isError()) { + Import::stop($import_handle->getError()); + } +} elseif (! $error) { + if (! isset($import_text) || empty($import_text)) { + $message = PhpMyAdmin\Message::error( + __( + 'No data was received to import. Either no file name was ' . + 'submitted, or the file size exceeded the maximum size permitted ' . + 'by your PHP configuration. See [doc@faq1-16]FAQ 1.16[/doc].' + ) + ); + Import::stop($message); + } +} + +// so we can obtain the message +//$_SESSION['Import_message'] = $message->getDisplay(); + +// Convert the file's charset if necessary +if (Encoding::isSupported() && isset($charset_of_file)) { + if ($charset_of_file != 'utf-8') { + $charset_conversion = true; + } +} elseif (isset($charset_of_file) && $charset_of_file != 'utf-8') { + $GLOBALS['dbi']->query('SET NAMES \'' . $charset_of_file . '\''); + // We can not show query in this case, it is in different charset + $sql_query_disabled = true; + $reset_charset = true; +} + +// Something to skip? (because timeout has passed) +if (! $error && isset($_POST['skip'])) { + $original_skip = $skip = intval($_POST['skip']); + while ($skip > 0 && ! $finished) { + Import::getNextChunk($skip < $read_limit ? $skip : $read_limit); + // Disable read progressivity, otherwise we eat all memory! + $read_multiply = 1; + $skip -= $read_limit; + } + unset($skip); +} + +// This array contain the data like numberof valid sql queries in the statement +// and complete valid sql statement (which affected for rows) +$sql_data = array('valid_sql' => array(), 'valid_queries' => 0); + +if (! $error) { + /* @var $import_plugin ImportPlugin */ + $import_plugin = Plugins::getPlugin( + "import", + $format, + 'libraries/classes/Plugins/Import/', + $import_type + ); + if ($import_plugin == null) { + $message = PhpMyAdmin\Message::error( + __('Could not load import plugins, please check your installation!') + ); + Import::stop($message); + } else { + // Do the real import + try { + $default_fk_check = PhpMyAdmin\Util::handleDisableFKCheckInit(); + $import_plugin->doImport($sql_data); + PhpMyAdmin\Util::handleDisableFKCheckCleanup($default_fk_check); + } catch (Exception $e) { + PhpMyAdmin\Util::handleDisableFKCheckCleanup($default_fk_check); + throw $e; + } + } +} + +if (isset($import_handle)) { + $import_handle->close(); +} + +// Cleanup temporary file +if ($file_to_unlink != '') { + unlink($file_to_unlink); +} + +// Reset charset back, if we did some changes +if ($reset_charset) { + $GLOBALS['dbi']->query('SET CHARACTER SET ' . $GLOBALS['charset_connection']); + $GLOBALS['dbi']->setCollation($collation_connection); +} + +// Show correct message +if (! empty($id_bookmark) && $_POST['action_bookmark'] == 2) { + $message = PhpMyAdmin\Message::success(__('The bookmark has been deleted.')); + $display_query = $import_text; + $error = false; // unset error marker, it was used just to skip processing +} elseif (! empty($id_bookmark) && $_POST['action_bookmark'] == 1) { + $message = PhpMyAdmin\Message::notice(__('Showing bookmark')); +} elseif ($bookmark_created) { + $special_message = '[br]' . sprintf( + __('Bookmark %s has been created.'), + htmlspecialchars($_POST['bkm_label']) + ); +} elseif ($finished && ! $error) { + // Do not display the query with message, we do it separately + $display_query = ';'; + if ($import_type != 'query') { + $message = PhpMyAdmin\Message::success( + '' + . _ngettext( + 'Import has been successfully finished, %d query executed.', + 'Import has been successfully finished, %d queries executed.', + $executed_queries + ) + . '' + ); + $message->addParam($executed_queries); + + if (! empty($import_notice)) { + $message->addHtml($import_notice); + } + if (! empty($local_import_file)) { + $message->addText('(' . $local_import_file . ')'); + } else { + $message->addText('(' . $_FILES['import_file']['name'] . ')'); + } + } +} + +// Did we hit timeout? Tell it user. +if ($timeout_passed) { + $urlparams['timeout_passed'] = '1'; + $urlparams['offset'] = $GLOBALS['offset']; + if (isset($local_import_file)) { + $urlparams['local_import_file'] = $local_import_file; + } + + $importUrl = $err_url = $goto . Url::getCommon($urlparams); + + $message = PhpMyAdmin\Message::error( + __( + 'Script timeout passed, if you want to finish import,' + . ' please %sresubmit the same file%s and import will resume.' + ) + ); + $message->addParamHtml(''); + $message->addParamHtml(''); + + if ($offset == 0 || (isset($original_skip) && $original_skip == $offset)) { + $message->addText( + __( + 'However on last run no data has been parsed,' + . ' this usually means phpMyAdmin won\'t be able to' + . ' finish this import unless you increase php time limits.' + ) + ); + } +} + +// if there is any message, copy it into $_SESSION as well, +// so we can obtain it by AJAX call +if (isset($message)) { + $_SESSION['Import_message']['message'] = $message->getDisplay(); +} +// Parse and analyze the query, for correct db and table name +// in case of a query typed in the query window +// (but if the query is too large, in case of an imported file, the parser +// can choke on it so avoid parsing) +$sqlLength = mb_strlen($sql_query); +if ($sqlLength <= $GLOBALS['cfg']['MaxCharactersInDisplayedSQL']) { + list( + $analyzed_sql_results, + $db, + $table_from_sql + ) = ParseAnalyze::sqlQuery($sql_query, $db); + // @todo: possibly refactor + extract($analyzed_sql_results); + + if ($table != $table_from_sql && !empty($table_from_sql)) { + $table = $table_from_sql; + } +} + +// There was an error? +if (isset($my_die)) { + foreach ($my_die as $key => $die) { + PhpMyAdmin\Util::mysqlDie( + $die['error'], $die['sql'], false, $err_url, $error + ); + } +} + +if ($go_sql) { + + if (! empty($sql_data) && ($sql_data['valid_queries'] > 1)) { + $_SESSION['is_multi_query'] = true; + $sql_queries = $sql_data['valid_sql']; + } else { + $sql_queries = array($sql_query); + } + + $html_output = ''; + + foreach ($sql_queries as $sql_query) { + + // parse sql query + list( + $analyzed_sql_results, + $db, + $table_from_sql + ) = ParseAnalyze::sqlQuery($sql_query, $db); + // @todo: possibly refactor + extract($analyzed_sql_results); + + // Check if User is allowed to issue a 'DROP DATABASE' Statement + if ($sql->hasNoRightsToDropDatabase( + $analyzed_sql_results, $cfg['AllowUserDropDatabase'], $GLOBALS['dbi']->isSuperuser() + )) { + PhpMyAdmin\Util::mysqlDie( + __('"DROP DATABASE" statements are disabled.'), + '', + false, + $_SESSION['Import_message']['go_back_url'] + ); + return; + } // end if + + if ($table != $table_from_sql && !empty($table_from_sql)) { + $table = $table_from_sql; + } + + $html_output .= $sql->executeQueryAndGetQueryResponse( + $analyzed_sql_results, // analyzed_sql_results + false, // is_gotofile + $db, // db + $table, // table + null, // find_real_end + null, // sql_query_for_bookmark - see below + null, // extra_data + null, // message_to_show + null, // message + null, // sql_data + $goto, // goto + $pmaThemeImage, // pmaThemeImage + null, // disp_query + null, // disp_message + null, // query_type + $sql_query, // sql_query + null, // selectedTables + null // complete_query + ); + } + + // sql_query_for_bookmark is not included in Sql::executeQueryAndGetQueryResponse + // since only one bookmark has to be added for all the queries submitted through + // the SQL tab + if (! empty($_POST['bkm_label']) && ! empty($import_text)) { + $cfgBookmark = Bookmark::getParams($GLOBALS['cfg']['Server']['user']); + $sql->storeTheQueryAsBookmark( + $db, $cfgBookmark['user'], + $_POST['sql_query'], $_POST['bkm_label'], + isset($_POST['bkm_replace']) ? $_POST['bkm_replace'] : null + ); + } + + $response->addJSON('ajax_reload', $ajax_reload); + $response->addHTML($html_output); + exit(); + +} elseif ($result) { + // Save a Bookmark with more than one queries (if Bookmark label given). + if (! empty($_POST['bkm_label']) && ! empty($import_text)) { + $cfgBookmark = Bookmark::getParams($GLOBALS['cfg']['Server']['user']); + $sql->storeTheQueryAsBookmark( + $db, $cfgBookmark['user'], + $_POST['sql_query'], $_POST['bkm_label'], + isset($_POST['bkm_replace']) ? $_POST['bkm_replace'] : null + ); + } + + $response->setRequestStatus(true); + $response->addJSON('message', PhpMyAdmin\Message::success($msg)); + $response->addJSON( + 'sql_query', + PhpMyAdmin\Util::getMessage($msg, $sql_query, 'success') + ); +} elseif ($result == false) { + $response->setRequestStatus(false); + $response->addJSON('message', PhpMyAdmin\Message::error($msg)); +} else { + $active_page = $goto; + include '' . $goto; +} + +// If there is request for ROLLBACK in the end. +if (isset($_POST['rollback_query'])) { + $GLOBALS['dbi']->query('ROLLBACK'); +} diff --git a/php/apps/phpmyadmin49/html/import_status.php b/php/apps/phpmyadmin49/html/import_status.php new file mode 100644 index 00000000..02e39d45 --- /dev/null +++ b/php/apps/phpmyadmin49/html/import_status.php @@ -0,0 +1,121 @@ + $value) { + // only copy session-prefixed data + if (substr($key, 0, strlen(UPLOAD_PREFIX)) + == UPLOAD_PREFIX) { + $sessionupload[$key] = $value; + } + } + // PMA will kill all variables, so let's use a constant + define('SESSIONUPLOAD', serialize($sessionupload)); + session_write_close(); + + session_name('phpMyAdmin'); + session_id($_COOKIE['phpMyAdmin']); +} + */ + +define('PMA_MINIMUM_COMMON', 1); + +require_once 'libraries/common.inc.php'; +list( + $SESSION_KEY, + $upload_id, + $plugins +) = ImportAjax::uploadProgressSetup(); + +/* +if (defined('SESSIONUPLOAD')) { + // write sessionupload back into the loaded PMA session + + $sessionupload = unserialize(SESSIONUPLOAD); + foreach ($sessionupload as $key => $value) { + $_SESSION[$key] = $value; + } + + // remove session upload data that are not set anymore + foreach ($_SESSION as $key => $value) { + if (substr($key, 0, strlen(UPLOAD_PREFIX)) + == UPLOAD_PREFIX + && ! isset($sessionupload[$key]) + ) { + unset($_SESSION[$key]); + } + } +} + */ + +// $_GET["message"] is used for asking for an import message +if (isset($_GET["message"]) && $_GET["message"]) { + + // AJAX requests can't be cached! + Core::noCacheHeader(); + + header('Content-type: text/html'); + + // wait 0.3 sec before we check for $_SESSION variable, + // which is set inside import.php + usleep(300000); + + $maximumTime = ini_get('max_execution_time'); + $timestamp = time(); + // wait until message is available + while ($_SESSION['Import_message']['message'] == null) { + // close session before sleeping + session_write_close(); + // sleep + usleep(250000); // 0.25 sec + // reopen session + session_start(); + + if ((time() - $timestamp) > $maximumTime) { + $_SESSION['Import_message']['message'] = PhpMyAdmin\Message::error( + __('Could not load the progress of the import.') + )->getDisplay(); + break; + } + } + + echo $_SESSION['Import_message']['message']; + echo '
' , "\n"; + echo ' [ ' , __('Back') , ' ]' , "\n"; + echo '
' , "\n"; + +} else { + ImportAjax::status($_GET["id"]); +} diff --git a/php/apps/phpmyadmin49/html/index.php b/php/apps/phpmyadmin49/html/index.php new file mode 100644 index 00000000..ef276476 --- /dev/null +++ b/php/apps/phpmyadmin49/html/index.php @@ -0,0 +1,691 @@ +setUserValue( + null, + 'FontSize', + $_POST['set_fontsize'], + '82%' + ); + header('Location: index.php' . Url::getCommonRaw()); + exit(); +} +// if user selected a theme +if (isset($_POST['set_theme'])) { + $tmanager = ThemeManager::getInstance(); + $tmanager->setActiveTheme($_POST['set_theme']); + $tmanager->setThemeCookie(); + + $userPreferences = new UserPreferences(); + $prefs = $userPreferences->load(); + $prefs["config_data"]["ThemeDefault"] = $_POST['set_theme']; + $userPreferences->save($prefs["config_data"]); + + header('Location: index.php' . Url::getCommonRaw()); + exit(); +} +// Change collation connection +if (isset($_POST['collation_connection'])) { + $GLOBALS['PMA_Config']->setUserValue( + null, + 'DefaultConnectionCollation', + $_POST['collation_connection'], + 'utf8mb4_unicode_ci' + ); + header('Location: index.php' . Url::getCommonRaw()); + exit(); +} + + +// See FAQ 1.34 +if (! empty($_REQUEST['db'])) { + $page = null; + if (! empty($_REQUEST['table'])) { + $page = Util::getScriptNameForOption( + $GLOBALS['cfg']['DefaultTabTable'], 'table' + ); + } else { + $page = Util::getScriptNameForOption( + $GLOBALS['cfg']['DefaultTabDatabase'], 'database' + ); + } + include $page; + exit; +} + +$response = Response::getInstance(); +/** + * Check if it is an ajax request to reload the recent tables list. + */ +if ($response->isAjax() && ! empty($_REQUEST['recent_table'])) { + $response->addJSON( + 'list', + RecentFavoriteTable::getInstance('recent')->getHtmlList() + ); + exit; +} + +if ($GLOBALS['PMA_Config']->isGitRevision()) { + // If ajax request to get revision + if (isset($_REQUEST['git_revision']) && $response->isAjax()) { + GitRevision::display(); + exit; + } + // Else show empty html + echo '
'; +} + +// Handles some variables that may have been sent by the calling script +$GLOBALS['db'] = ''; +$GLOBALS['table'] = ''; +$show_query = '1'; + +// Any message to display? +if (! empty($message)) { + echo Util::getMessage($message); + unset($message); +} +if (isset($_SESSION['partial_logout'])) { + Message::success( + __('You were logged out from one server, to logout completely from phpMyAdmin, you need to logout from all servers.') + )->display(); + unset($_SESSION['partial_logout']); +} + +$common_url_query = Url::getCommon(); +$mysql_cur_user_and_host = ''; + +// when $server > 0, a server has been chosen so we can display +// all MySQL-related information +if ($server > 0) { + include 'libraries/server_common.inc.php'; + + // Use the verbose name of the server instead of the hostname + // if a value is set + $server_info = ''; + if (! empty($cfg['Server']['verbose'])) { + $server_info .= htmlspecialchars($cfg['Server']['verbose']); + if ($GLOBALS['cfg']['ShowServerInfo']) { + $server_info .= ' ('; + } + } + if ($GLOBALS['cfg']['ShowServerInfo'] || empty($cfg['Server']['verbose'])) { + $server_info .= $GLOBALS['dbi']->getHostInfo(); + } + if (! empty($cfg['Server']['verbose']) && $GLOBALS['cfg']['ShowServerInfo']) { + $server_info .= ')'; + } + $mysql_cur_user_and_host = $GLOBALS['dbi']->fetchValue('SELECT USER();'); + + // should we add the port info here? + $short_server_info = (!empty($GLOBALS['cfg']['Server']['verbose']) + ? $GLOBALS['cfg']['Server']['verbose'] + : $GLOBALS['cfg']['Server']['host']); +} + +echo '
' , "\n"; +// Anchor for favorite tables synchronization. +echo RecentFavoriteTable::getInstance('favorite')->getHtmlSyncFavoriteTables(); +echo '
'; +if ($server > 0 || count($cfg['Servers']) > 1 +) { + if ($cfg['DBG']['demo']) { + echo '
'; + echo '

' , __('phpMyAdmin Demo Server') , '

'; + echo '

'; + printf( + __( + 'You are using the demo server. You can do anything here, but ' + . 'please do not change root, debian-sys-maint and pma users. ' + . 'More information is available at %s.' + ), + 'demo.phpmyadmin.net' + ); + echo '

'; + echo '
'; + } + echo '
'; + echo '

' , __('General settings') , '

'; + echo '
    '; + + /** + * Displays the MySQL servers choice form + */ + if ($cfg['ServerDefault'] == 0 + || (! $cfg['NavigationDisplayServers'] + && (count($cfg['Servers']) > 1 + || ($server == 0 && count($cfg['Servers']) == 1))) + ) { + echo '
  • '; + echo Util::getImage('s_host') , " " + , Select::render(true, true); + echo '
  • '; + } + + /** + * Displays the mysql server related links + */ + if ($server > 0) { + include_once 'libraries/check_user_privileges.inc.php'; + + // Logout for advanced authentication + if ($cfg['Server']['auth_type'] != 'config') { + if ($cfg['ShowChgPassword']) { + $conditional_class = 'ajax'; + Core::printListItem( + Util::getImage('s_passwd') . " " . __( + 'Change password' + ), + 'li_change_password', + 'user_password.php' . $common_url_query, + null, + null, + 'change_password_anchor', + "no_bullets", + $conditional_class + ); + } + } // end if + echo '
  • '; + echo '
    ' , "\n" + . Url::getHiddenInputs(null, null, 4, 'collation_connection') + . ' ' . "\n" + + . Charsets::getCollationDropdownBox( + $GLOBALS['dbi'], + $GLOBALS['cfg']['Server']['DisableIS'], + 'collation_connection', + 'select_collation_connection', + $collation_connection, + true, + true + ) + . '
    ' . "\n" + . '
  • ' . "\n"; + } // end of if ($server > 0) + echo '
'; + echo '
'; +} + +echo '
'; +echo '

' , __('Appearance settings') , '

'; +echo '
    '; + +// Displays language selection combo +$language_manager = LanguageManager::getInstance(); +if (empty($cfg['Lang']) && $language_manager->hasChoice()) { + echo '
  • '; + + echo Util::getImage('s_lang') , " " + , $language_manager->getSelectorDisplay(); + echo '
  • '; +} + +// ThemeManager if available + +if ($GLOBALS['cfg']['ThemeManager']) { + echo '
  • '; + echo Util::getImage('s_theme') , " " + , ThemeManager::getInstance()->getHtmlSelectBox(); + echo '
  • '; +} +echo '
  • '; +echo Config::getFontsizeForm(); +echo '
  • '; + +echo '
'; + +// User preferences + +if ($server > 0) { + echo '
    '; + Core::printListItem( + Util::getImage('b_tblops') . " " . __( + 'More settings' + ), + 'li_user_preferences', + 'prefs_manage.php' . $common_url_query, + null, + null, + null, + "no_bullets" + ); + echo '
'; +} + +echo '
'; + + +echo '
'; +echo '
'; + + +if ($server > 0 && $GLOBALS['cfg']['ShowServerInfo']) { + + echo '
'; + echo '

' , __('Database server') , '

'; + echo '
    ' , "\n"; + Core::printListItem( + __('Server:') . ' ' . $server_info, + 'li_server_info' + ); + Core::printListItem( + __('Server type:') . ' ' . Util::getServerType(), + 'li_server_type' + ); + Core::printListItem( + __('Server connection:') . ' ' . Util::getServerSSL(), + 'li_server_type' + ); + Core::printListItem( + __('Server version:') + . ' ' + . $GLOBALS['dbi']->getVersionString() . ' - ' . $GLOBALS['dbi']->getVersionComment(), + 'li_server_version' + ); + Core::printListItem( + __('Protocol version:') . ' ' . $GLOBALS['dbi']->getProtoInfo(), + 'li_mysql_proto' + ); + Core::printListItem( + __('User:') . ' ' . htmlspecialchars($mysql_cur_user_and_host), + 'li_user_info' + ); + + echo '
  • '; + echo ' ' , __('Server charset:') , ' ' + . ' '; + + $charset = Charsets::getServerCharset($GLOBALS['dbi']); + $charsets = Charsets::getMySQLCharsetsDescriptions( + $GLOBALS['dbi'], + $GLOBALS['cfg']['Server']['DisableIS'] + ); + + echo ' ' , $charsets[$charset], ' (' . $charset, ')'; + echo ' ' + . '
  • ' + . '
' + . '
'; +} + +if ($GLOBALS['cfg']['ShowServerInfo'] || $GLOBALS['cfg']['ShowPhpInfo']) { + echo '
'; + echo '

' , __('Web server') , '

'; + echo '
    '; + if ($GLOBALS['cfg']['ShowServerInfo']) { + Core::printListItem($_SERVER['SERVER_SOFTWARE'], 'li_web_server_software'); + + if ($server > 0) { + $client_version_str = $GLOBALS['dbi']->getClientInfo(); + if (preg_match('#\d+\.\d+\.\d+#', $client_version_str)) { + $client_version_str = 'libmysql - ' . $client_version_str; + } + Core::printListItem( + __('Database client version:') . ' ' . $client_version_str, + 'li_mysql_client_version' + ); + + $php_ext_string = __('PHP extension:') . ' '; + + $extensions = Util::listPHPExtensions(); + + foreach ($extensions as $extension) { + $php_ext_string .= ' ' . $extension + . Util::showPHPDocu('book.' . $extension . '.php'); + } + + Core::printListItem( + $php_ext_string, + 'li_used_php_extension' + ); + + $php_version_string = __('PHP version:') . ' ' . phpversion(); + + Core::printListItem( + $php_version_string, + 'li_used_php_version' + ); + } + } + + if ($cfg['ShowPhpInfo']) { + Core::printListItem( + __('Show PHP information'), + 'li_phpinfo', + 'phpinfo.php' . $common_url_query, + null, + '_blank' + ); + } + echo '
'; + echo '
'; +} + +echo '
'; +echo '

phpMyAdmin

'; +echo '
    '; +$class = null; +if ($GLOBALS['cfg']['VersionCheck']) { + $class = 'jsversioncheck'; +} +Core::printListItem( + __('Version information:') . ' ' . PMA_VERSION . '', + 'li_pma_version', + null, + null, + null, + null, + $class +); +Core::printListItem( + __('Documentation'), + 'li_pma_docs', + Util::getDocuLink('index'), + null, + '_blank' +); + +// does not work if no target specified, don't know why +Core::printListItem( + __('Official Homepage'), + 'li_pma_homepage', + Core::linkURL('https://www.phpmyadmin.net/'), + null, + '_blank' +); +Core::printListItem( + __('Contribute'), + 'li_pma_contribute', + Core::linkURL('https://www.phpmyadmin.net/contribute/'), + null, + '_blank' +); +Core::printListItem( + __('Get support'), + 'li_pma_support', + Core::linkURL('https://www.phpmyadmin.net/support/'), + null, + '_blank' +); +Core::printListItem( + __('List of changes'), + 'li_pma_changes', + 'changelog.php' . Url::getCommon(), + null, + '_blank' +); +Core::printListItem( + __('License'), + 'li_pma_license', + 'license.php' . Url::getCommon(), + null, + '_blank' +); +echo '
'; +echo '
'; + +echo '
'; + +echo '
'; + +/** + * mbstring is used for handling multibytes inside parser, so it is good + * to tell user something might be broken without it, see bug #1063149. + */ +if (! extension_loaded('mbstring')) { + trigger_error( + __( + 'The mbstring PHP extension was not found and you seem to be using' + . ' a multibyte charset. Without the mbstring extension phpMyAdmin' + . ' is unable to split strings correctly and it may result in' + . ' unexpected results.' + ), + E_USER_WARNING + ); +} + +/** + * Missing functionality + */ +if (! extension_loaded('curl') && ! ini_get('allow_url_fopen')) { + trigger_error( + __( + 'The curl extension was not found and allow_url_fopen is ' + . 'disabled. Due to this some features such as error reporting ' + . 'or version check are disabled.' + ) + ); +} + +if ($cfg['LoginCookieValidityDisableWarning'] == false) { + /** + * Check whether session.gc_maxlifetime limits session validity. + */ + $gc_time = (int)ini_get('session.gc_maxlifetime'); + if ($gc_time < $GLOBALS['cfg']['LoginCookieValidity'] ) { + trigger_error( + __( + 'Your PHP parameter [a@https://secure.php.net/manual/en/session.' . + 'configuration.php#ini.session.gc-maxlifetime@_blank]session.' . + 'gc_maxlifetime[/a] is lower than cookie validity configured ' . + 'in phpMyAdmin, because of this, your login might expire sooner ' . + 'than configured in phpMyAdmin.' + ), + E_USER_WARNING + ); + } +} + +/** + * Check whether LoginCookieValidity is limited by LoginCookieStore. + */ +if ($GLOBALS['cfg']['LoginCookieStore'] != 0 + && $GLOBALS['cfg']['LoginCookieStore'] < $GLOBALS['cfg']['LoginCookieValidity'] +) { + trigger_error( + __( + 'Login cookie store is lower than cookie validity configured in ' . + 'phpMyAdmin, because of this, your login will expire sooner than ' . + 'configured in phpMyAdmin.' + ), + E_USER_WARNING + ); +} + +/** + * Check if user does not have defined blowfish secret and it is being used. + */ +if (! empty($_SESSION['encryption_key'])) { + if (empty($GLOBALS['cfg']['blowfish_secret'])) { + trigger_error( + __( + 'The configuration file now needs a secret passphrase (blowfish_secret).' + ), + E_USER_WARNING + ); + } elseif (strlen($GLOBALS['cfg']['blowfish_secret']) < 32) { + trigger_error( + __( + 'The secret passphrase in configuration (blowfish_secret) is too short.' + ), + E_USER_WARNING + ); + } +} + +/** + * Check for existence of config directory which should not exist in + * production environment. + */ +if (@file_exists('config')) { + trigger_error( + __( + 'Directory [code]config[/code], which is used by the setup script, ' . + 'still exists in your phpMyAdmin directory. It is strongly ' . + 'recommended to remove it once phpMyAdmin has been configured. ' . + 'Otherwise the security of your server may be compromised by ' . + 'unauthorized people downloading your configuration.' + ), + E_USER_WARNING + ); +} + +$relation = new Relation(); + +if ($server > 0) { + $cfgRelation = $relation->getRelationsParam(); + if (! $cfgRelation['allworks'] + && $cfg['PmaNoRelation_DisableWarning'] == false + ) { + $msg_text = __( + 'The phpMyAdmin configuration storage is not completely ' + . 'configured, some extended features have been deactivated. ' + . '%sFind out why%s. ' + ); + if ($cfg['ZeroConf'] == true) { + $msg_text .= '
' . + __( + 'Or alternately go to \'Operations\' tab of any database ' + . 'to set it up there.' + ); + } + $msg = Message::notice($msg_text); + $msg->addParamHtml(''); + $msg->addParamHtml(''); + /* Show error if user has configured something, notice elsewhere */ + if (!empty($cfg['Servers'][$server]['pmadb'])) { + $msg->isError(true); + } + $msg->display(); + } // end if +} + +/** + * Warning about Suhosin only if its simulation mode is not enabled + */ +if ($cfg['SuhosinDisableWarning'] == false + && ini_get('suhosin.request.max_value_length') + && ini_get('suhosin.simulation') == '0' +) { + trigger_error( + sprintf( + __( + 'Server running with Suhosin. Please refer to %sdocumentation%s ' . + 'for possible issues.' + ), + '[doc@faq1-38]', + '[/doc]' + ), + E_USER_WARNING + ); +} + +/* Missing template cache */ +if (is_null($GLOBALS['PMA_Config']->getTempDir('twig'))) { + trigger_error( + sprintf( + __('The $cfg[\'TempDir\'] (%s) is not accessible. phpMyAdmin is not able to cache templates and will be slow because of this.'), + $GLOBALS['PMA_Config']->get('TempDir') + ), + E_USER_WARNING + ); +} + +/** + * Warning about incomplete translations. + * + * The data file is created while creating release by ./scripts/remove-incomplete-mo + */ +if (@file_exists('libraries/language_stats.inc.php')) { + include 'libraries/language_stats.inc.php'; + /* + * This message is intentionally not translated, because we're + * handling incomplete translations here and focus on english + * speaking users. + */ + if (isset($GLOBALS['language_stats'][$lang]) + && $GLOBALS['language_stats'][$lang] < $cfg['TranslationWarningThreshold'] + ) { + trigger_error( + 'You are using an incomplete translation, please help to make it ' + . 'better by [a@https://www.phpmyadmin.net/translate/' + . '@_blank]contributing[/a].', + E_USER_NOTICE + ); + } +} diff --git a/php/apps/phpmyadmin49/html/js/ajax.js b/php/apps/phpmyadmin49/html/js/ajax.js new file mode 100644 index 00000000..81976128 --- /dev/null +++ b/php/apps/phpmyadmin49/html/js/ajax.js @@ -0,0 +1,834 @@ +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * This object handles ajax requests for pages. It also + * handles the reloading of the main menu and scripts. + */ +var AJAX = { + /** + * @var bool active Whether we are busy + */ + active: false, + /** + * @var object source The object whose event initialized the request + */ + source: null, + /** + * @var object xhr A reference to the ajax request that is currently running + */ + xhr: null, + /** + * @var object lockedTargets, list of locked targets + */ + lockedTargets: {}, + /** + * @var function Callback to execute after a successful request + * Used by PMA_commonFunctions from common.js + */ + _callback: function () {}, + /** + * @var bool _debug Makes noise in your Firebug console + */ + _debug: false, + /** + * @var object $msgbox A reference to a jQuery object that links to a message + * box that is generated by PMA_ajaxShowMessage() + */ + $msgbox: null, + /** + * Given the filename of a script, returns a hash to be + * used to refer to all the events registered for the file + * + * @param key string key The filename for which to get the event name + * + * @return int + */ + hash: function (key) { + /* http://burtleburtle.net/bob/hash/doobs.html#one */ + key += ''; + var len = key.length; + var hash = 0; + var i = 0; + for (; i < len; ++i) { + hash += key.charCodeAt(i); + hash += (hash << 10); + hash ^= (hash >> 6); + } + hash += (hash << 3); + hash ^= (hash >> 11); + hash += (hash << 15); + return Math.abs(hash); + }, + /** + * Registers an onload event for a file + * + * @param file string file The filename for which to register the event + * @param func function func The function to execute when the page is ready + * + * @return self For chaining + */ + registerOnload: function (file, func) { + var eventName = 'onload_' + AJAX.hash(file); + $(document).on(eventName, func); + if (this._debug) { + console.log( + // no need to translate + 'Registered event ' + eventName + ' for file ' + file + ); + } + return this; + }, + /** + * Registers a teardown event for a file. This is useful to execute functions + * that unbind events for page elements that are about to be removed. + * + * @param string file The filename for which to register the event + * @param function func The function to execute when + * the page is about to be torn down + * + * @return self For chaining + */ + registerTeardown: function (file, func) { + var eventName = 'teardown_' + AJAX.hash(file); + $(document).on(eventName, func); + if (this._debug) { + console.log( + // no need to translate + 'Registered event ' + eventName + ' for file ' + file + ); + } + return this; + }, + /** + * Called when a page has finished loading, once for every + * file that registered to the onload event of that file. + * + * @param string file The filename for which to fire the event + * + * @return void + */ + fireOnload: function (file) { + var eventName = 'onload_' + AJAX.hash(file); + $(document).trigger(eventName); + if (this._debug) { + console.log( + // no need to translate + 'Fired event ' + eventName + ' for file ' + file + ); + } + }, + /** + * Called just before a page is torn down, once for every + * file that registered to the teardown event of that file. + * + * @param string file The filename for which to fire the event + * + * @return void + */ + fireTeardown: function (file) { + var eventName = 'teardown_' + AJAX.hash(file); + $(document).triggerHandler(eventName); + if (this._debug) { + console.log( + // no need to translate + 'Fired event ' + eventName + ' for file ' + file + ); + } + }, + /** + * function to handle lock page mechanism + * + * @param event the event object + * + * @return void + */ + lockPageHandler: function (event) { + var newHash = null; + var oldHash = null; + var lockId; + // CodeMirror lock + if (event.data.value === 3) { + newHash = event.data.content; + oldHash = true; + lockId = 'cm'; + } else { + // Don't lock on enter. + if (0 === event.charCode) { + return; + } + + lockId = $(this).data('lock-id'); + if (typeof lockId === 'undefined') { + return; + } + /* + * @todo Fix Code mirror does not give correct full value (query) + * in textarea, it returns only the change in content. + */ + if (event.data.value === 1) { + newHash = AJAX.hash($(this).val()); + } else { + newHash = AJAX.hash($(this).is(':checked')); + } + oldHash = $(this).data('val-hash'); + } + // Set lock if old value !== new value + // otherwise release lock + if (oldHash !== newHash) { + AJAX.lockedTargets[lockId] = true; + } else { + delete AJAX.lockedTargets[lockId]; + } + // Show lock icon if locked targets is not empty. + // otherwise remove lock icon + if (!jQuery.isEmptyObject(AJAX.lockedTargets)) { + $('#lock_page_icon').html(PMA_getImage('s_lock', PMA_messages.strLockToolTip).toString()); + } else { + $('#lock_page_icon').html(''); + } + }, + /** + * resets the lock + * + * @return void + */ + resetLock: function () { + AJAX.lockedTargets = {}; + $('#lock_page_icon').html(''); + }, + handleMenu: { + replace: function (content) { + $('#floating_menubar').html(content) + // Remove duplicate wrapper + // TODO: don't send it in the response + .children().first().remove(); + $('#topmenu').menuResizer(PMA_mainMenuResizerCallback); + } + }, + /** + * Event handler for clicks on links and form submissions + * + * @param object e Event data + * + * @return void + */ + requestHandler: function (event) { + // In some cases we don't want to handle the request here and either + // leave the browser deal with it natively (e.g: file download) + // or leave an existing ajax event handler present elsewhere deal with it + var href = $(this).attr('href'); + if (typeof event !== 'undefined' && (event.shiftKey || event.ctrlKey)) { + return true; + } else if ($(this).attr('target')) { + return true; + } else if ($(this).hasClass('ajax') || $(this).hasClass('disableAjax')) { + // reset the lockedTargets object, as specified AJAX operation has finished + AJAX.resetLock(); + return true; + } else if (href && href.match(/^#/)) { + return true; + } else if (href && href.match(/^mailto/)) { + return true; + } else if ($(this).hasClass('ui-datepicker-next') || + $(this).hasClass('ui-datepicker-prev') + ) { + return true; + } + + if (typeof event !== 'undefined') { + event.preventDefault(); + event.stopImmediatePropagation(); + } + + // triggers a confirm dialog if: + // the user has performed some operations on loaded page + // the user clicks on some link, (won't trigger for buttons) + // the click event is not triggered by script + if (typeof event !== 'undefined' && event.type === 'click' && + event.isTrigger !== true && + !jQuery.isEmptyObject(AJAX.lockedTargets) && + confirm(PMA_messages.strConfirmNavigation) === false + ) { + return false; + } + AJAX.resetLock(); + var isLink = !! href || false; + var previousLinkAborted = false; + + if (AJAX.active === true) { + // Cancel the old request if abortable, when the user requests + // something else. Otherwise silently bail out, as there is already + // a request well in progress. + if (AJAX.xhr) { + // In case of a link request, attempt aborting + AJAX.xhr.abort(); + if (AJAX.xhr.status === 0 && AJAX.xhr.statusText === 'abort') { + // If aborted + AJAX.$msgbox = PMA_ajaxShowMessage(PMA_messages.strAbortedRequest); + AJAX.active = false; + AJAX.xhr = null; + previousLinkAborted = true; + } else { + // If can't abort + return false; + } + } else { + // In case submitting a form, don't attempt aborting + return false; + } + } + + AJAX.source = $(this); + + $('html, body').animate({ scrollTop: 0 }, 'fast'); + + var url = isLink ? href : $(this).attr('action'); + var argsep = PMA_commonParams.get('arg_separator'); + var params = 'ajax_request=true' + argsep + 'ajax_page_request=true'; + var dataPost = AJAX.source.getPostData(); + if (! isLink) { + params += argsep + $(this).serialize(); + } else if (dataPost) { + params += argsep + dataPost; + isLink = false; + } + if (! (history && history.pushState)) { + // Add a list of menu hashes that we have in the cache to the request + params += PMA_MicroHistory.menus.getRequestParam(); + } + + if (AJAX._debug) { + console.log('Loading: ' + url); // no need to translate + } + + if (isLink) { + AJAX.active = true; + AJAX.$msgbox = PMA_ajaxShowMessage(); + // Save reference for the new link request + AJAX.xhr = $.get(url, params, AJAX.responseHandler); + if (history && history.pushState) { + var state = { + url : href + }; + if (previousLinkAborted) { + // hack: there is already an aborted entry on stack + // so just modify the aborted one + history.replaceState(state, null, href); + } else { + history.pushState(state, null, href); + } + } + } else { + /** + * Manually fire the onsubmit event for the form, if any. + * The event was saved in the jQuery data object by an onload + * handler defined below. Workaround for bug #3583316 + */ + var onsubmit = $(this).data('onsubmit'); + // Submit the request if there is no onsubmit handler + // or if it returns a value that evaluates to true + if (typeof onsubmit !== 'function' || onsubmit.apply(this, [event])) { + AJAX.active = true; + AJAX.$msgbox = PMA_ajaxShowMessage(); + $.post(url, params, AJAX.responseHandler); + } + } + }, + /** + * Called after the request that was initiated by this.requestHandler() + * has completed successfully or with a caught error. For completely + * failed requests or requests with uncaught errors, see the .ajaxError + * handler at the bottom of this file. + * + * To refer to self use 'AJAX', instead of 'this' as this function + * is called in the jQuery context. + * + * @param object e Event data + * + * @return void + */ + responseHandler: function (data) { + if (typeof data === 'undefined' || data === null) { + return; + } + if (typeof data.success !== 'undefined' && data.success) { + $('html, body').animate({ scrollTop: 0 }, 'fast'); + PMA_ajaxRemoveMessage(AJAX.$msgbox); + + if (data._redirect) { + PMA_ajaxShowMessage(data._redirect, false); + AJAX.active = false; + AJAX.xhr = null; + return; + } + + AJAX.scriptHandler.reset(function () { + if (data._reloadNavigation) { + PMA_reloadNavigation(); + } + if (data._title) { + $('title').replaceWith(data._title); + } + if (data._menu) { + if (history && history.pushState) { + var state = { + url : data._selflink, + menu : data._menu + }; + history.replaceState(state, null); + AJAX.handleMenu.replace(data._menu); + } else { + PMA_MicroHistory.menus.replace(data._menu); + PMA_MicroHistory.menus.add(data._menuHash, data._menu); + } + } else if (data._menuHash) { + if (! (history && history.pushState)) { + PMA_MicroHistory.menus.replace(PMA_MicroHistory.menus.get(data._menuHash)); + } + } + if (data._disableNaviSettings) { + PMA_disableNaviSettings(); + } else { + PMA_ensureNaviSettings(data._selflink); + } + + // Remove all containers that may have + // been added outside of #page_content + $('body').children() + .not('#pma_navigation') + .not('#floating_menubar') + .not('#page_nav_icons') + .not('#page_content') + .not('#selflink') + .not('#pma_header') + .not('#pma_footer') + .not('#pma_demo') + .not('#pma_console_container') + .not('#prefs_autoload') + .remove(); + // Replace #page_content with new content + if (data.message && data.message.length > 0) { + $('#page_content').replaceWith( + '
' + data.message + '
' + ); + PMA_highlightSQL($('#page_content')); + checkNumberOfFields(); + } + + if (data._selflink) { + var source = data._selflink.split('?')[0]; + // Check for faulty links + $selflink_replace = { + 'import.php': 'tbl_sql.php', + 'tbl_chart.php': 'sql.php', + 'tbl_gis_visualization.php': 'sql.php' + }; + if ($selflink_replace[source]) { + var replacement = $selflink_replace[source]; + data._selflink = data._selflink.replace(source, replacement); + } + $('#selflink').find('> a').attr('href', data._selflink); + } + if (data._params) { + PMA_commonParams.setAll(data._params); + } + if (data._scripts) { + AJAX.scriptHandler.load(data._scripts); + } + if (data._selflink && data._scripts && data._menuHash && data._params) { + if (! (history && history.pushState)) { + PMA_MicroHistory.add( + data._selflink, + data._scripts, + data._menuHash, + data._params, + AJAX.source.attr('rel') + ); + } + } + if (data._displayMessage) { + $('#page_content').prepend(data._displayMessage); + PMA_highlightSQL($('#page_content')); + } + + $('#pma_errors').remove(); + + var msg = ''; + if (data._errSubmitMsg) { + msg = data._errSubmitMsg; + } + if (data._errors) { + $('
', { id : 'pma_errors', class : 'clearfloat' }) + .insertAfter('#selflink') + .append(data._errors); + // bind for php error reporting forms (bottom) + $('#pma_ignore_errors_bottom').on('click', function (e) { + e.preventDefault(); + PMA_ignorePhpErrors(); + }); + $('#pma_ignore_all_errors_bottom').on('click', function (e) { + e.preventDefault(); + PMA_ignorePhpErrors(false); + }); + // In case of 'sendErrorReport'='always' + // submit the hidden error reporting form. + if (data._sendErrorAlways === '1' && + data._stopErrorReportLoop !== '1' + ) { + $('#pma_report_errors_form').submit(); + PMA_ajaxShowMessage(PMA_messages.phpErrorsBeingSubmitted, false); + $('html, body').animate({ scrollTop:$(document).height() }, 'slow'); + } else if (data._promptPhpErrors) { + // otherwise just prompt user if it is set so. + msg = msg + PMA_messages.phpErrorsFound; + // scroll to bottom where all the errors are displayed. + $('html, body').animate({ scrollTop:$(document).height() }, 'slow'); + } + } + PMA_ajaxShowMessage(msg, false); + // bind for php error reporting forms (popup) + $('#pma_ignore_errors_popup').on('click', function () { + PMA_ignorePhpErrors(); + }); + $('#pma_ignore_all_errors_popup').on('click', function () { + PMA_ignorePhpErrors(false); + }); + + if (typeof AJAX._callback === 'function') { + AJAX._callback.call(); + } + AJAX._callback = function () {}; + }); + } else { + PMA_ajaxShowMessage(data.error, false); + AJAX.active = false; + AJAX.xhr = null; + PMA_handleRedirectAndReload(data); + if (data.fieldWithError) { + $(':input.error').removeClass('error'); + $('#' + data.fieldWithError).addClass('error'); + } + } + }, + /** + * This object is in charge of downloading scripts, + * keeping track of what's downloaded and firing + * the onload event for them when the page is ready. + */ + scriptHandler: { + /** + * @var array _scripts The list of files already downloaded + */ + _scripts: [], + /** + * @var string _scriptsVersion version of phpMyAdmin from which the + * scripts have been loaded + */ + _scriptsVersion: null, + /** + * @var array _scriptsToBeLoaded The list of files that + * need to be downloaded + */ + _scriptsToBeLoaded: [], + /** + * @var array _scriptsToBeFired The list of files for which + * to fire the onload and unload events + */ + _scriptsToBeFired: [], + _scriptsCompleted: false, + /** + * Records that a file has been downloaded + * + * @param string file The filename + * @param string fire Whether this file will be registering + * onload/teardown events + * + * @return self For chaining + */ + add: function (file, fire) { + this._scripts.push(file); + if (fire) { + // Record whether to fire any events for the file + // This is necessary to correctly tear down the initial page + this._scriptsToBeFired.push(file); + } + return this; + }, + /** + * Download a list of js files in one request + * + * @param array files An array of filenames and flags + * + * @return void + */ + load: function (files, callback) { + var self = this; + var i; + // Clear loaded scripts if they are from another version of phpMyAdmin. + // Depends on common params being set before loading scripts in responseHandler + if (self._scriptsVersion === null) { + self._scriptsVersion = PMA_commonParams.get('PMA_VERSION'); + } else if (self._scriptsVersion !== PMA_commonParams.get('PMA_VERSION')) { + self._scripts = []; + self._scriptsVersion = PMA_commonParams.get('PMA_VERSION'); + } + self._scriptsCompleted = false; + self._scriptsToBeFired = []; + // We need to first complete list of files to load + // as next loop will directly fire requests to load them + // and that triggers removal of them from + // self._scriptsToBeLoaded + for (i in files) { + self._scriptsToBeLoaded.push(files[i].name); + if (files[i].fire) { + self._scriptsToBeFired.push(files[i].name); + } + } + for (i in files) { + var script = files[i].name; + // Only for scripts that we don't already have + if ($.inArray(script, self._scripts) === -1) { + this.add(script); + this.appendScript(script, callback); + } else { + self.done(script, callback); + } + } + // Trigger callback if there is nothing else to load + self.done(null, callback); + }, + /** + * Called whenever all files are loaded + * + * @return void + */ + done: function (script, callback) { + if (typeof ErrorReport !== 'undefined') { + ErrorReport.wrap_global_functions(); + } + if ($.inArray(script, this._scriptsToBeFired)) { + AJAX.fireOnload(script); + } + if ($.inArray(script, this._scriptsToBeLoaded)) { + this._scriptsToBeLoaded.splice($.inArray(script, this._scriptsToBeLoaded), 1); + } + if (script === null) { + this._scriptsCompleted = true; + } + /* We need to wait for last signal (with null) or last script load */ + AJAX.active = (this._scriptsToBeLoaded.length > 0) || ! this._scriptsCompleted; + /* Run callback on last script */ + if (! AJAX.active && $.isFunction(callback)) { + callback(); + } + }, + /** + * Appends a script element to the head to load the scripts + * + * @return void + */ + appendScript: function (name, callback) { + var head = document.head || document.getElementsByTagName('head')[0]; + var script = document.createElement('script'); + var self = this; + + script.type = 'text/javascript'; + script.src = 'js/' + name + '?' + 'v=' + encodeURIComponent(PMA_commonParams.get('PMA_VERSION')); + script.async = false; + script.onload = function () { + self.done(name, callback); + }; + head.appendChild(script); + }, + /** + * Fires all the teardown event handlers for the current page + * and rebinds all forms and links to the request handler + * + * @param function callback The callback to call after resetting + * + * @return void + */ + reset: function (callback) { + for (var i in this._scriptsToBeFired) { + AJAX.fireTeardown(this._scriptsToBeFired[i]); + } + this._scriptsToBeFired = []; + /** + * Re-attach a generic event handler to clicks + * on pages and submissions of forms + */ + $(document).off('click', 'a').on('click', 'a', AJAX.requestHandler); + $(document).off('submit', 'form').on('submit', 'form', AJAX.requestHandler); + if (! (history && history.pushState)) { + PMA_MicroHistory.update(); + } + callback(); + } + } +}; + +/** + * Here we register a function that will remove the onsubmit event from all + * forms that will be handled by the generic page loader. We then save this + * event handler in the "jQuery data", so that we can fire it up later in + * AJAX.requestHandler(). + * + * See bug #3583316 + */ +AJAX.registerOnload('functions.js', function () { + // Registering the onload event for functions.js + // ensures that it will be fired for all pages + $('form').not('.ajax').not('.disableAjax').each(function () { + if ($(this).attr('onsubmit')) { + $(this).data('onsubmit', this.onsubmit).attr('onsubmit', ''); + } + }); + + var $page_content = $('#page_content'); + /** + * Workaround for passing submit button name,value on ajax form submit + * by appending hidden element with submit button name and value. + */ + $page_content.on('click', 'form input[type=submit]', function () { + var buttonName = $(this).attr('name'); + if (typeof buttonName === 'undefined') { + return; + } + $(this).closest('form').append($('', { + 'type' : 'hidden', + 'name' : buttonName, + 'value': $(this).val() + })); + }); + + /** + * Attach event listener to events when user modify visible + * Input,Textarea and select fields to make changes in forms + */ + $page_content.on( + 'keyup change', + 'form.lock-page textarea, ' + + 'form.lock-page input[type="text"], ' + + 'form.lock-page input[type="number"], ' + + 'form.lock-page select', + { value:1 }, + AJAX.lockPageHandler + ); + $page_content.on( + 'change', + 'form.lock-page input[type="checkbox"], ' + + 'form.lock-page input[type="radio"]', + { value:2 }, + AJAX.lockPageHandler + ); + /** + * Reset lock when lock-page form reset event is fired + * Note: reset does not bubble in all browser so attach to + * form directly. + */ + $('form.lock-page').on('reset', function (event) { + AJAX.resetLock(); + }); +}); + +/** + * Page load event handler + */ +$(function () { + var menuContent = $('
') + .append($('#serverinfo').clone()) + .append($('#topmenucontainer').clone()) + .html(); + if (history && history.pushState) { + // set initial state reload + var initState = ('state' in window.history && window.history.state !== null); + var initURL = $('#selflink').find('> a').attr('href') || location.href; + var state = { + url : initURL, + menu : menuContent + }; + history.replaceState(state, null); + + $(window).on('popstate', function (event) { + var initPop = (! initState && location.href === initURL); + initState = true; + // check if popstate fired on first page itself + if (initPop) { + return; + } + var state = event.originalEvent.state; + if (state && state.menu) { + AJAX.$msgbox = PMA_ajaxShowMessage(); + var params = 'ajax_request=true' + PMA_commonParams.get('arg_separator') + 'ajax_page_request=true'; + var url = state.url || location.href; + $.get(url, params, AJAX.responseHandler); + // TODO: Check if sometimes menu is not retrieved from server, + // Not sure but it seems menu was missing only for printview which + // been removed lately, so if it's right some dead menu checks/fallbacks + // may need to be removed from this file and Header.php + // AJAX.handleMenu.replace(event.originalEvent.state.menu); + } + }); + } else { + // Fallback to microhistory mechanism + AJAX.scriptHandler + .load([{ 'name' : 'microhistory.js', 'fire' : 1 }], function () { + // The cache primer is set by the footer class + if (PMA_MicroHistory.primer.url) { + PMA_MicroHistory.menus.add( + PMA_MicroHistory.primer.menuHash, + menuContent + ); + } + $(function () { + // Queue up this event twice to make sure that we get a copy + // of the page after all other onload events have been fired + if (PMA_MicroHistory.primer.url) { + PMA_MicroHistory.add( + PMA_MicroHistory.primer.url, + PMA_MicroHistory.primer.scripts, + PMA_MicroHistory.primer.menuHash + ); + } + }); + }); + } +}); + +/** + * Attach a generic event handler to clicks + * on pages and submissions of forms + */ +$(document).on('click', 'a', AJAX.requestHandler); +$(document).on('submit', 'form', AJAX.requestHandler); + +/** + * Gracefully handle fatal server errors + * (e.g: 500 - Internal server error) + */ +$(document).ajaxError(function (event, request, settings) { + if (AJAX._debug) { + console.log('AJAX error: status=' + request.status + ', text=' + request.statusText); + } + // Don't handle aborted requests + if (request.status !== 0 || request.statusText !== 'abort') { + var details = ''; + var state = request.state(); + + if (request.status !== 0) { + details += '
' + escapeHtml(PMA_sprintf(PMA_messages.strErrorCode, request.status)) + '
'; + } + details += '
' + escapeHtml(PMA_sprintf(PMA_messages.strErrorText, request.statusText + ' (' + state + ')')) + '
'; + if (state === 'rejected' || state === 'timeout') { + details += '
' + escapeHtml(PMA_messages.strErrorConnection) + '
'; + } + PMA_ajaxShowMessage( + '
' + + PMA_messages.strErrorProcessingRequest + + details + + '
', + false + ); + AJAX.active = false; + AJAX.xhr = null; + } +}); diff --git a/php/apps/phpmyadmin49/html/js/chart.js b/php/apps/phpmyadmin49/html/js/chart.js new file mode 100644 index 00000000..8145dac2 --- /dev/null +++ b/php/apps/phpmyadmin49/html/js/chart.js @@ -0,0 +1,676 @@ +/** + * Chart type enumerations + */ +var ChartType = { + LINE : 'line', + SPLINE : 'spline', + AREA : 'area', + BAR : 'bar', + COLUMN : 'column', + PIE : 'pie', + TIMELINE: 'timeline', + SCATTER: 'scatter' +}; + +/** + * Column type enumeration + */ +var ColumnType = { + STRING : 'string', + NUMBER : 'number', + BOOLEAN : 'boolean', + DATE : 'date' +}; + +/** + * Abstract chart factory which defines the contract for chart factories + */ +var ChartFactory = function () { +}; +ChartFactory.prototype = { + createChart : function (type, options) { + throw new Error('createChart must be implemented by a subclass'); + } +}; + +/** + * Abstract chart which defines the contract for charts + * + * @param elementId + * id of the div element the chart is drawn in + */ +var Chart = function (elementId) { + this.elementId = elementId; +}; +Chart.prototype = { + draw : function (data, options) { + throw new Error('draw must be implemented by a subclass'); + }, + redraw : function (options) { + throw new Error('redraw must be implemented by a subclass'); + }, + destroy : function () { + throw new Error('destroy must be implemented by a subclass'); + }, + toImageString : function () { + throw new Error('toImageString must be implemented by a subclass'); + } +}; + +/** + * Abstract representation of charts that operates on DataTable where,
+ *
    + *
  • First column provides index to the data.
  • + *
  • Each subsequent columns are of type + * ColumnType.NUMBER and represents a data series.
  • + *
+ * Line chart, area chart, bar chart, column chart are typical examples. + * + * @param elementId + * id of the div element the chart is drawn in + */ +var BaseChart = function (elementId) { + Chart.call(this, elementId); +}; +BaseChart.prototype = new Chart(); +BaseChart.prototype.constructor = BaseChart; +BaseChart.prototype.validateColumns = function (dataTable) { + var columns = dataTable.getColumns(); + if (columns.length < 2) { + throw new Error('Minimum of two columns are required for this chart'); + } + for (var i = 1; i < columns.length; i++) { + if (columns[i].type !== ColumnType.NUMBER) { + throw new Error('Column ' + (i + 1) + ' should be of type \'Number\''); + } + } + return true; +}; + +/** + * Abstract pie chart + * + * @param elementId + * id of the div element the chart is drawn in + */ +var PieChart = function (elementId) { + BaseChart.call(this, elementId); +}; +PieChart.prototype = new BaseChart(); +PieChart.prototype.constructor = PieChart; +PieChart.prototype.validateColumns = function (dataTable) { + var columns = dataTable.getColumns(); + if (columns.length > 2) { + throw new Error('Pie charts can draw only one series'); + } + return BaseChart.prototype.validateColumns.call(this, dataTable); +}; + +/** + * Abstract timeline chart + * + * @param elementId + * id of the div element the chart is drawn in + */ +var TimelineChart = function (elementId) { + BaseChart.call(this, elementId); +}; +TimelineChart.prototype = new BaseChart(); +TimelineChart.prototype.constructor = TimelineChart; +TimelineChart.prototype.validateColumns = function (dataTable) { + var result = BaseChart.prototype.validateColumns.call(this, dataTable); + if (result) { + var columns = dataTable.getColumns(); + if (columns[0].type !== ColumnType.DATE) { + throw new Error('First column of timeline chart need to be a date column'); + } + } + return result; +}; + +/** + * Abstract scatter chart + * + * @param elementId + * id of the div element the chart is drawn in + */ +var ScatterChart = function (elementId) { + BaseChart.call(this, elementId); +}; +ScatterChart.prototype = new BaseChart(); +ScatterChart.prototype.constructor = ScatterChart; +ScatterChart.prototype.validateColumns = function (dataTable) { + var result = BaseChart.prototype.validateColumns.call(this, dataTable); + if (result) { + var columns = dataTable.getColumns(); + if (columns[0].type !== ColumnType.NUMBER) { + throw new Error('First column of scatter chart need to be a numeric column'); + } + } + return result; +}; + +/** + * The data table contains column information and data for the chart. + */ +var DataTable = function () { + var columns = []; + var data = null; + + this.addColumn = function (type, name) { + columns.push({ + 'type' : type, + 'name' : name + }); + }; + + this.getColumns = function () { + return columns; + }; + + this.setData = function (rows) { + data = rows; + fillMissingValues(); + }; + + this.getData = function () { + return data; + }; + + var fillMissingValues = function () { + if (columns.length === 0) { + throw new Error('Set columns first'); + } + var row; + for (var i = 0; i < data.length; i++) { + row = data[i]; + if (row.length > columns.length) { + row.splice(columns.length - 1, row.length - columns.length); + } else if (row.length < columns.length) { + for (var j = row.length; j < columns.length; j++) { + row.push(null); + } + } + } + }; +}; + +/** ***************************************************************************** + * JQPlot specific code + ******************************************************************************/ + +/** + * Abstract JQplot chart + * + * @param elementId + * id of the div element the chart is drawn in + */ +var JQPlotChart = function (elementId) { + Chart.call(this, elementId); + this.plot = null; + this.validator = null; +}; +JQPlotChart.prototype = new Chart(); +JQPlotChart.prototype.constructor = JQPlotChart; +JQPlotChart.prototype.draw = function (data, options) { + if (this.validator.validateColumns(data)) { + this.plot = $.jqplot(this.elementId, this.prepareData(data), this + .populateOptions(data, options)); + } +}; +JQPlotChart.prototype.destroy = function () { + if (this.plot !== null) { + this.plot.destroy(); + } +}; +JQPlotChart.prototype.redraw = function (options) { + if (this.plot !== null) { + this.plot.replot(options); + } +}; +JQPlotChart.prototype.toImageString = function (options) { + if (this.plot !== null) { + return $('#' + this.elementId).jqplotToImageStr({}); + } +}; +JQPlotChart.prototype.populateOptions = function (dataTable, options) { + throw new Error('populateOptions must be implemented by a subclass'); +}; +JQPlotChart.prototype.prepareData = function (dataTable) { + throw new Error('prepareData must be implemented by a subclass'); +}; + +/** + * JQPlot line chart + * + * @param elementId + * id of the div element the chart is drawn in + */ +var JQPlotLineChart = function (elementId) { + JQPlotChart.call(this, elementId); + this.validator = BaseChart.prototype; +}; +JQPlotLineChart.prototype = new JQPlotChart(); +JQPlotLineChart.prototype.constructor = JQPlotLineChart; + +JQPlotLineChart.prototype.populateOptions = function (dataTable, options) { + var columns = dataTable.getColumns(); + var optional = { + axes : { + xaxis : { + label : columns[0].name, + renderer : $.jqplot.CategoryAxisRenderer, + ticks : [] + }, + yaxis : { + label : (columns.length === 2 ? columns[1].name : 'Values'), + labelRenderer : $.jqplot.CanvasAxisLabelRenderer + } + }, + highlighter: { + show: true, + tooltipAxes: 'y', + formatString:'%d' + }, + series : [] + }; + $.extend(true, optional, options); + + if (optional.series.length === 0) { + for (var i = 1; i < columns.length; i++) { + optional.series.push({ + label : columns[i].name.toString() + }); + } + } + if (optional.axes.xaxis.ticks.length === 0) { + var data = dataTable.getData(); + for (var j = 0; j < data.length; j++) { + optional.axes.xaxis.ticks.push(data[j][0].toString()); + } + } + return optional; +}; + +JQPlotLineChart.prototype.prepareData = function (dataTable) { + var data = dataTable.getData(); + var row; + var retData = []; + var retRow; + for (var i = 0; i < data.length; i++) { + row = data[i]; + for (var j = 1; j < row.length; j++) { + retRow = retData[j - 1]; + if (retRow === undefined) { + retRow = []; + retData[j - 1] = retRow; + } + retRow.push(row[j]); + } + } + return retData; +}; + +/** + * JQPlot spline chart + * + * @param elementId + * id of the div element the chart is drawn in + */ +var JQPlotSplineChart = function (elementId) { + JQPlotLineChart.call(this, elementId); +}; +JQPlotSplineChart.prototype = new JQPlotLineChart(); +JQPlotSplineChart.prototype.constructor = JQPlotSplineChart; + +JQPlotSplineChart.prototype.populateOptions = function (dataTable, options) { + var optional = {}; + var opt = JQPlotLineChart.prototype.populateOptions.call(this, dataTable, + options); + var compulsory = { + seriesDefaults : { + rendererOptions : { + smooth : true + } + } + }; + $.extend(true, optional, opt, compulsory); + return optional; +}; + +/** + * JQPlot scatter chart + * + * @param elementId + * id of the div element the chart is drawn in + */ +var JQPlotScatterChart = function (elementId) { + JQPlotChart.call(this, elementId); + this.validator = ScatterChart.prototype; +}; +JQPlotScatterChart.prototype = new JQPlotChart(); +JQPlotScatterChart.prototype.constructor = JQPlotScatterChart; + +JQPlotScatterChart.prototype.populateOptions = function (dataTable, options) { + var columns = dataTable.getColumns(); + var optional = { + axes : { + xaxis : { + label : columns[0].name + }, + yaxis : { + label : (columns.length === 2 ? columns[1].name : 'Values'), + labelRenderer : $.jqplot.CanvasAxisLabelRenderer + } + }, + highlighter: { + show: true, + tooltipAxes: 'xy', + formatString:'%d, %d' + }, + series : [] + }; + for (var i = 1; i < columns.length; i++) { + optional.series.push({ + label : columns[i].name.toString() + }); + } + + var compulsory = { + seriesDefaults : { + showLine: false, + markerOptions: { + size: 7, + style: 'x' + } + } + }; + + $.extend(true, optional, options, compulsory); + return optional; +}; + +JQPlotScatterChart.prototype.prepareData = function (dataTable) { + var data = dataTable.getData(); + var row; + var retData = []; + var retRow; + for (var i = 0; i < data.length; i++) { + row = data[i]; + if (row[0]) { + for (var j = 1; j < row.length; j++) { + retRow = retData[j - 1]; + if (retRow === undefined) { + retRow = []; + retData[j - 1] = retRow; + } + retRow.push([row[0], row[j]]); + } + } + } + return retData; +}; + +/** + * JQPlot timeline chart + * + * @param elementId + * id of the div element the chart is drawn in + */ +var JQPlotTimelineChart = function (elementId) { + JQPlotLineChart.call(this, elementId); + this.validator = TimelineChart.prototype; +}; +JQPlotTimelineChart.prototype = new JQPlotLineChart(); +JQPlotTimelineChart.prototype.constructor = JQPlotTimelineChart; + +JQPlotTimelineChart.prototype.populateOptions = function (dataTable, options) { + var optional = { + axes : { + xaxis : { + tickOptions : { + formatString: '%b %#d, %y' + } + } + } + }; + var opt = JQPlotLineChart.prototype.populateOptions.call(this, dataTable, options); + var compulsory = { + axes : { + xaxis : { + renderer : $.jqplot.DateAxisRenderer + } + } + }; + $.extend(true, optional, opt, compulsory); + return optional; +}; + +JQPlotTimelineChart.prototype.prepareData = function (dataTable) { + var data = dataTable.getData(); + var row; + var d; + var retData = []; + var retRow; + for (var i = 0; i < data.length; i++) { + row = data[i]; + d = row[0]; + for (var j = 1; j < row.length; j++) { + retRow = retData[j - 1]; + if (retRow === undefined) { + retRow = []; + retData[j - 1] = retRow; + } + if (d !== null) { + retRow.push([d.getTime(), row[j]]); + } + } + } + return retData; +}; + +/** + * JQPlot area chart + * + * @param elementId + * id of the div element the chart is drawn in + */ +var JQPlotAreaChart = function (elementId) { + JQPlotLineChart.call(this, elementId); +}; +JQPlotAreaChart.prototype = new JQPlotLineChart(); +JQPlotAreaChart.prototype.constructor = JQPlotAreaChart; + +JQPlotAreaChart.prototype.populateOptions = function (dataTable, options) { + var optional = { + seriesDefaults : { + fillToZero : true + } + }; + var opt = JQPlotLineChart.prototype.populateOptions.call(this, dataTable, + options); + var compulsory = { + seriesDefaults : { + fill : true + } + }; + $.extend(true, optional, opt, compulsory); + return optional; +}; + +/** + * JQPlot column chart + * + * @param elementId + * id of the div element the chart is drawn in + */ +var JQPlotColumnChart = function (elementId) { + JQPlotLineChart.call(this, elementId); +}; +JQPlotColumnChart.prototype = new JQPlotLineChart(); +JQPlotColumnChart.prototype.constructor = JQPlotColumnChart; + +JQPlotColumnChart.prototype.populateOptions = function (dataTable, options) { + var optional = { + seriesDefaults : { + fillToZero : true + } + }; + var opt = JQPlotLineChart.prototype.populateOptions.call(this, dataTable, + options); + var compulsory = { + seriesDefaults : { + renderer : $.jqplot.BarRenderer + } + }; + $.extend(true, optional, opt, compulsory); + return optional; +}; + +/** + * JQPlot bar chart + * + * @param elementId + * id of the div element the chart is drawn in + */ +var JQPlotBarChart = function (elementId) { + JQPlotLineChart.call(this, elementId); +}; +JQPlotBarChart.prototype = new JQPlotLineChart(); +JQPlotBarChart.prototype.constructor = JQPlotBarChart; + +JQPlotBarChart.prototype.populateOptions = function (dataTable, options) { + var columns = dataTable.getColumns(); + var optional = { + axes : { + yaxis : { + label : columns[0].name, + labelRenderer : $.jqplot.CanvasAxisLabelRenderer, + renderer : $.jqplot.CategoryAxisRenderer, + ticks : [] + }, + xaxis : { + label : (columns.length === 2 ? columns[1].name : 'Values'), + labelRenderer : $.jqplot.CanvasAxisLabelRenderer + } + }, + highlighter: { + show: true, + tooltipAxes: 'x', + formatString:'%d' + }, + series : [], + seriesDefaults : { + fillToZero : true + } + }; + var compulsory = { + seriesDefaults : { + renderer : $.jqplot.BarRenderer, + rendererOptions : { + barDirection : 'horizontal' + } + } + }; + $.extend(true, optional, options, compulsory); + + if (optional.axes.yaxis.ticks.length === 0) { + var data = dataTable.getData(); + for (var i = 0; i < data.length; i++) { + optional.axes.yaxis.ticks.push(data[i][0].toString()); + } + } + if (optional.series.length === 0) { + for (var j = 1; j < columns.length; j++) { + optional.series.push({ + label : columns[j].name.toString() + }); + } + } + return optional; +}; + +/** + * JQPlot pie chart + * + * @param elementId + * id of the div element the chart is drawn in + */ +var JQPlotPieChart = function (elementId) { + JQPlotChart.call(this, elementId); + this.validator = PieChart.prototype; +}; +JQPlotPieChart.prototype = new JQPlotChart(); +JQPlotPieChart.prototype.constructor = JQPlotPieChart; + +JQPlotPieChart.prototype.populateOptions = function (dataTable, options) { + var optional = { + highlighter: { + show: true, + tooltipAxes: 'xy', + formatString:'%s, %d', + useAxesFormatters: false + }, + legend: { + renderer: $.jqplot.EnhancedPieLegendRenderer, + }, + }; + var compulsory = { + seriesDefaults : { + shadow: false, + renderer : $.jqplot.PieRenderer, + rendererOptions: { sliceMargin: 1, showDataLabels: true } + } + }; + $.extend(true, optional, options, compulsory); + return optional; +}; + +JQPlotPieChart.prototype.prepareData = function (dataTable) { + var data = dataTable.getData(); + var row; + var retData = []; + for (var i = 0; i < data.length; i++) { + row = data[i]; + retData.push([row[0], row[1]]); + } + return [retData]; +}; + +/** + * Chart factory that returns JQPlotCharts + */ +var JQPlotChartFactory = function () { +}; +JQPlotChartFactory.prototype = new ChartFactory(); +JQPlotChartFactory.prototype.createChart = function (type, elementId) { + var chart = null; + switch (type) { + case ChartType.LINE: + chart = new JQPlotLineChart(elementId); + break; + case ChartType.SPLINE: + chart = new JQPlotSplineChart(elementId); + break; + case ChartType.TIMELINE: + chart = new JQPlotTimelineChart(elementId); + break; + case ChartType.AREA: + chart = new JQPlotAreaChart(elementId); + break; + case ChartType.BAR: + chart = new JQPlotBarChart(elementId); + break; + case ChartType.COLUMN: + chart = new JQPlotColumnChart(elementId); + break; + case ChartType.PIE: + chart = new JQPlotPieChart(elementId); + break; + case ChartType.SCATTER: + chart = new JQPlotScatterChart(elementId); + break; + } + + return chart; +}; diff --git a/php/apps/phpmyadmin49/html/js/codemirror/addon/lint/sql-lint.js b/php/apps/phpmyadmin49/html/js/codemirror/addon/lint/sql-lint.js new file mode 100644 index 00000000..800c8d7a --- /dev/null +++ b/php/apps/phpmyadmin49/html/js/codemirror/addon/lint/sql-lint.js @@ -0,0 +1,38 @@ +CodeMirror.sqlLint = function (text, updateLinting, options, cm) { + // Skipping check if text box is empty. + if (text.trim() === '') { + updateLinting(cm, []); + return; + } + + function handleResponse (response) { + var found = []; + for (var idx in response) { + found.push({ + from: CodeMirror.Pos( + response[idx].fromLine, response[idx].fromColumn + ), + to: CodeMirror.Pos( + response[idx].toLine, response[idx].toColumn + ), + messageHTML: response[idx].message, + severity : response[idx].severity + }); + } + + updateLinting(cm, found); + } + + $.ajax({ + method: 'POST', + url: 'lint.php', + dataType: 'json', + data: { + sql_query: text, + server: PMA_commonParams.get('server'), + options: options.lintOptions, + no_history: true, + }, + success: handleResponse + }); +}; diff --git a/php/apps/phpmyadmin49/html/js/common.js b/php/apps/phpmyadmin49/html/js/common.js new file mode 100644 index 00000000..28b394b5 --- /dev/null +++ b/php/apps/phpmyadmin49/html/js/common.js @@ -0,0 +1,550 @@ +/* vim: set expandtab sw=4 ts=4 sts=4: */ + +$(function () { + checkNumberOfFields(); +}); + +/** + * Holds common parameters such as server, db, table, etc + * + * The content for this is normally loaded from Header.php or + * Response.php and executed by ajax.js + */ +var PMA_commonParams = (function () { + /** + * @var hash params An associative array of key value pairs + * @access private + */ + var params = {}; + // The returned object is the public part of the module + return { + /** + * Saves all the key value pair that + * are provided in the input array + * + * @param obj hash The input array + * + * @return void + */ + setAll: function (obj) { + var reload = false; + var updateNavigation = false; + for (var i in obj) { + if (params[i] !== undefined && params[i] !== obj[i]) { + if (i === 'db' || i === 'table') { + updateNavigation = true; + } + reload = true; + } + params[i] = obj[i]; + } + if (updateNavigation && + $('#pma_navigation_tree').hasClass('synced') + ) { + PMA_showCurrentNavigation(); + } + }, + /** + * Retrieves a value given its key + * Returns empty string for undefined values + * + * @param name string The key + * + * @return string + */ + get: function (name) { + return params[name]; + }, + /** + * Saves a single key value pair + * + * @param name string The key + * @param value string The value + * + * @return self For chainability + */ + set: function (name, value) { + var updateNavigation = false; + if (name === 'db' || name === 'table' && + params[name] !== value + ) { + updateNavigation = true; + } + params[name] = value; + if (updateNavigation && + $('#pma_navigation_tree').hasClass('synced') + ) { + PMA_showCurrentNavigation(); + } + return this; + }, + /** + * Returns the url query string using the saved parameters + * + * @return string + */ + getUrlQuery: function () { + var common = this.get('common_query'); + var separator = '?'; + var argsep = PMA_commonParams.get('arg_separator'); + if (common.length > 0) { + separator = argsep; + } + return PMA_sprintf( + '%s%sserver=%s' + argsep + 'db=%s' + argsep + 'table=%s', + this.get('common_query'), + separator, + encodeURIComponent(this.get('server')), + encodeURIComponent(this.get('db')), + encodeURIComponent(this.get('table')) + ); + } + }; +}()); + +/** + * Holds common parameters such as server, db, table, etc + * + * The content for this is normally loaded from Header.php or + * Response.php and executed by ajax.js + */ +var PMA_commonActions = { + /** + * Saves the database name when it's changed + * and reloads the query window, if necessary + * + * @param new_db string new_db The name of the new database + * + * @return void + */ + setDb: function (new_db) { + if (new_db !== PMA_commonParams.get('db')) { + PMA_commonParams.setAll({ 'db': new_db, 'table': '' }); + } + }, + /** + * Opens a database in the main part of the page + * + * @param new_db string The name of the new database + * + * @return void + */ + openDb: function (new_db) { + PMA_commonParams + .set('db', new_db) + .set('table', ''); + this.refreshMain( + PMA_commonParams.get('opendb_url') + ); + }, + /** + * Refreshes the main frame + * + * @param mixed url Undefined to refresh to the same page + * String to go to a different page, e.g: 'index.php' + * + * @return void + */ + refreshMain: function (url, callback) { + if (! url) { + url = $('#selflink').find('a').attr('href') || window.location.pathname; + url = url.substring(0, url.indexOf('?')); + } + url += PMA_commonParams.getUrlQuery(); + $('', { href: url }) + .appendTo('body') + .click() + .remove(); + AJAX._callback = callback; + } +}; + +/** + * Class to handle PMA Drag and Drop Import + * feature + */ +PMA_DROP_IMPORT = { + /** + * @var int, count of total uploads in this view + */ + uploadCount: 0, + /** + * @var int, count of live uploads + */ + liveUploadCount: 0, + /** + * @var string array, allowed extensions + */ + allowedExtensions: ['sql', 'xml', 'ldi', 'mediawiki', 'shp'], + /** + * @var string array, allowed extensions for compressed files + */ + allowedCompressedExtensions: ['gz', 'bz2', 'zip'], + /** + * @var obj array to store message returned by import_status.php + */ + importStatus: [], + /** + * Checks if any dropped file has valid extension or not + * + * @param file filename + * + * @return string, extension for valid extension, '' otherwise + */ + _getExtension: function (file) { + var arr = file.split('.'); + ext = arr[arr.length - 1]; + + // check if compressed + if (jQuery.inArray(ext.toLowerCase(), + PMA_DROP_IMPORT.allowedCompressedExtensions) !== -1) { + ext = arr[arr.length - 2]; + } + + // Now check for extension + if (jQuery.inArray(ext.toLowerCase(), + PMA_DROP_IMPORT.allowedExtensions) !== -1) { + return ext; + } + return ''; + }, + /** + * Shows upload progress for different sql uploads + * + * @param: hash (string), hash for specific file upload + * @param: percent (float), file upload percentage + * + * @return void + */ + _setProgress: function (hash, percent) { + $('.pma_sql_import_status div li[data-hash="' + hash + '"]') + .children('progress').val(percent); + }, + /** + * Function to upload the file asynchronously + * + * @param formData FormData object for a specific file + * @param hash hash of the current file upload + * + * @return void + */ + _sendFileToServer: function (formData, hash) { + var uploadURL = './import.php'; // Upload URL + var extraData = {}; + var jqXHR = $.ajax({ + xhr: function () { + var xhrobj = $.ajaxSettings.xhr(); + if (xhrobj.upload) { + xhrobj.upload.addEventListener('progress', function (event) { + var percent = 0; + var position = event.loaded || event.position; + var total = event.total; + if (event.lengthComputable) { + percent = Math.ceil(position / total * 100); + } + // Set progress + PMA_DROP_IMPORT._setProgress(hash, percent); + }, false); + } + return xhrobj; + }, + url: uploadURL, + type: 'POST', + contentType:false, + processData: false, + cache: false, + data: formData, + success: function (data) { + PMA_DROP_IMPORT._importFinished(hash, false, data.success); + if (!data.success) { + PMA_DROP_IMPORT.importStatus[PMA_DROP_IMPORT.importStatus.length] = { + hash: hash, + message: data.error + }; + } + } + }); + + // -- provide link to cancel the upload + $('.pma_sql_import_status div li[data-hash="' + hash + + '"] span.filesize').html('' + + PMA_messages.dropImportMessageCancel + ''); + + // -- add event listener to this link to abort upload operation + $('.pma_sql_import_status div li[data-hash="' + hash + + '"] span.filesize span.pma_drop_file_status') + .on('click', function () { + if ($(this).attr('task') === 'cancel') { + jqXHR.abort(); + $(this).html('' + PMA_messages.dropImportMessageAborted + ''); + PMA_DROP_IMPORT._importFinished(hash, true, false); + } else if ($(this).children('span').html() === + PMA_messages.dropImportMessageFailed) { + // -- view information + var $this = $(this); + $.each(PMA_DROP_IMPORT.importStatus, + function (key, value) { + if (value.hash === hash) { + $('.pma_drop_result:visible').remove(); + var filename = $this.parent('span').attr('data-filename'); + $('body').append('

' + + PMA_messages.dropImportImportResultHeader + ' - ' + + filename + 'x

' + value.message + '
'); + $('.pma_drop_result').draggable(); // to make this dialog draggable + } + }); + } + }); + }, + /** + * Triggered when an object is dragged into the PMA UI + * + * @param event obj + * + * @return void + */ + _dragenter : function (event) { + // We don't want to prevent users from using + // browser's default drag-drop feature on some page(s) + if ($('.noDragDrop').length !== 0) { + return; + } + + event.stopPropagation(); + event.preventDefault(); + if (!PMA_DROP_IMPORT._hasFiles(event)) { + return; + } + if (PMA_commonParams.get('db') === '') { + $('.pma_drop_handler').html(PMA_messages.dropImportSelectDB); + } else { + $('.pma_drop_handler').html(PMA_messages.dropImportDropFiles); + } + $('.pma_drop_handler').fadeIn(); + }, + /** + * Check if dragged element contains Files + * + * @param event the event object + * + * @return bool + */ + _hasFiles: function (event) { + return !(typeof event.originalEvent.dataTransfer.types === 'undefined' || + $.inArray('Files', event.originalEvent.dataTransfer.types) < 0 || + $.inArray( + 'application/x-moz-nativeimage', + event.originalEvent.dataTransfer.types + ) >= 0); + }, + /** + * Triggered when dragged file is being dragged over PMA UI + * + * @param event obj + * + * @return void + */ + _dragover: function (event) { + // We don't want to prevent users from using + // browser's default drag-drop feature on some page(s) + if ($('.noDragDrop').length !== 0) { + return; + } + + event.stopPropagation(); + event.preventDefault(); + if (!PMA_DROP_IMPORT._hasFiles(event)) { + return; + } + $('.pma_drop_handler').fadeIn(); + }, + /** + * Triggered when dragged objects are left + * + * @param event obj + * + * @return void + */ + _dragleave: function (event) { + // We don't want to prevent users from using + // browser's default drag-drop feature on some page(s) + if ($('.noDragDrop').length !== 0) { + return; + } + event.stopPropagation(); + event.preventDefault(); + var $pma_drop_handler = $('.pma_drop_handler'); + $pma_drop_handler.clearQueue().stop(); + $pma_drop_handler.fadeOut(); + $pma_drop_handler.html(PMA_messages.dropImportDropFiles); + }, + /** + * Called when upload has finished + * + * @param string, unique hash for a certain upload + * @param bool, true if upload was aborted + * @param bool, status of sql upload, as sent by server + * + * @return void + */ + _importFinished: function (hash, aborted, status) { + $('.pma_sql_import_status div li[data-hash="' + hash + '"]') + .children('progress').hide(); + var icon = 'icon ic_s_success'; + // -- provide link to view upload status + if (!aborted) { + if (status) { + $('.pma_sql_import_status div li[data-hash="' + hash + + '"] span.filesize span.pma_drop_file_status') + .html('' + PMA_messages.dropImportMessageSuccess + '
'); + } else { + $('.pma_sql_import_status div li[data-hash="' + hash + + '"] span.filesize span.pma_drop_file_status') + .html('' + PMA_messages.dropImportMessageFailed + + ''); + icon = 'icon ic_s_error'; + } + } else { + icon = 'icon ic_s_notice'; + } + $('.pma_sql_import_status div li[data-hash="' + hash + + '"] span.filesize span.pma_drop_file_status') + .attr('task', 'info'); + + // Set icon + $('.pma_sql_import_status div li[data-hash="' + hash + '"]') + .prepend(' '); + + // Decrease liveUploadCount by one + $('.pma_import_count').html(--PMA_DROP_IMPORT.liveUploadCount); + if (!PMA_DROP_IMPORT.liveUploadCount) { + $('.pma_sql_import_status h2 .close').fadeIn(); + } + }, + /** + * Triggered when dragged objects are dropped to UI + * From this function, the AJAX Upload operation is initiated + * + * @param event object + * + * @return void + */ + _drop: function (event) { + // We don't want to prevent users from using + // browser's default drag-drop feature on some page(s) + if ($('.noDragDrop').length !== 0) { + return; + } + + var dbname = PMA_commonParams.get('db'); + var server = PMA_commonParams.get('server'); + + // if no database is selected -- no + if (dbname !== '') { + var files = event.originalEvent.dataTransfer.files; + if (!files || files.length === 0) { + // No files actually transferred + $('.pma_drop_handler').fadeOut(); + event.stopPropagation(); + event.preventDefault(); + return; + } + $('.pma_sql_import_status').slideDown(); + for (var i = 0; i < files.length; i++) { + var ext = (PMA_DROP_IMPORT._getExtension(files[i].name)); + var hash = AJAX.hash(++PMA_DROP_IMPORT.uploadCount); + + var $pma_sql_import_status_div = $('.pma_sql_import_status div'); + $pma_sql_import_status_div.append('
  • ' + + ((ext !== '') ? '' : ' ') + + escapeHtml(files[i].name) + '' + (files[i].size / 1024).toFixed(2) + + ' kb
  • '); + + // scroll the UI to bottom + $pma_sql_import_status_div.scrollTop( + $pma_sql_import_status_div.scrollTop() + 50 + ); // 50 hardcoded for now + + if (ext !== '') { + // Increment liveUploadCount by one + $('.pma_import_count').html(++PMA_DROP_IMPORT.liveUploadCount); + $('.pma_sql_import_status h2 .close').fadeOut(); + + $('.pma_sql_import_status div li[data-hash="' + hash + '"]') + .append('
    '); + + // uploading + var fd = new FormData(); + fd.append('import_file', files[i]); + fd.append('noplugin', Math.random().toString(36).substring(2, 12)); + fd.append('db', dbname); + fd.append('server', server); + fd.append('token', PMA_commonParams.get('token')); + fd.append('import_type', 'database'); + // todo: method to find the value below + fd.append('MAX_FILE_SIZE', '4194304'); + // todo: method to find the value below + fd.append('charset_of_file','utf-8'); + // todo: method to find the value below + fd.append('allow_interrupt', 'yes'); + fd.append('skip_queries', '0'); + fd.append('format',ext); + fd.append('sql_compatibility','NONE'); + fd.append('sql_no_auto_value_on_zero','something'); + fd.append('ajax_request','true'); + fd.append('hash', hash); + + // init uploading + PMA_DROP_IMPORT._sendFileToServer(fd, hash); + } else if (!PMA_DROP_IMPORT.liveUploadCount) { + $('.pma_sql_import_status h2 .close').fadeIn(); + } + } + } + $('.pma_drop_handler').fadeOut(); + event.stopPropagation(); + event.preventDefault(); + } +}; + +/** + * Called when some user drags, dragover, leave + * a file to the PMA UI + * @param object Event data + * @return void + */ +$(document).on('dragenter', PMA_DROP_IMPORT._dragenter); +$(document).on('dragover', PMA_DROP_IMPORT._dragover); +$(document).on('dragleave', '.pma_drop_handler', PMA_DROP_IMPORT._dragleave); + +// when file is dropped to PMA UI +$(document).on('drop', 'body', PMA_DROP_IMPORT._drop); + +// minimizing-maximising the sql ajax upload status +$(document).on('click', '.pma_sql_import_status h2 .minimize', function () { + if ($(this).attr('toggle') === 'off') { + $('.pma_sql_import_status div').css('height','270px'); + $(this).attr('toggle','on'); + $(this).html('-'); // to minimize + } else { + $('.pma_sql_import_status div').css('height','0px'); + $(this).attr('toggle','off'); + $(this).html('+'); // to maximise + } +}); + +// closing sql ajax upload status +$(document).on('click', '.pma_sql_import_status h2 .close', function () { + $('.pma_sql_import_status').fadeOut(function () { + $('.pma_sql_import_status div').html(''); + PMA_DROP_IMPORT.importStatus = []; // clear the message array + }); +}); + +// Closing the import result box +$(document).on('click', '.pma_drop_result h2 .close', function () { + $(this).parent('h2').parent('div').remove(); +}); diff --git a/php/apps/phpmyadmin49/html/js/config.js b/php/apps/phpmyadmin49/html/js/config.js new file mode 100644 index 00000000..bd0eab46 --- /dev/null +++ b/php/apps/phpmyadmin49/html/js/config.js @@ -0,0 +1,886 @@ +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * Functions used in configuration forms and on user preferences pages + */ + +/** + * checks whether browser supports web storage + * + * @param type the type of storage i.e. localStorage or sessionStorage + * + * @returns bool + */ +function isStorageSupported (type, warn) { + try { + window[type].setItem('PMATest', 'test'); + // Check whether key-value pair was set successfully + if (window[type].getItem('PMATest') === 'test') { + // Supported, remove test variable from storage + window[type].removeItem('PMATest'); + return true; + } + } catch (error) { + // Not supported + if (warn) { + PMA_ajaxShowMessage(PMA_messages.strNoLocalStorage, false); + } + } + return false; +} + +/** + * Unbind all event handlers before tearing down a page + */ +AJAX.registerTeardown('config.js', function () { + $('.optbox input[id], .optbox select[id], .optbox textarea[id]').off('change').off('keyup'); + $('.optbox input[type=button][name=submit_reset]').off('click'); + $('div.tabs_contents').off(); + $('#import_local_storage, #export_local_storage').off('click'); + $('form.prefs-form').off('change').off('submit'); + $(document).off('click', 'div.click-hide-message'); + $('#prefs_autoload').find('a').off('click'); +}); + +AJAX.registerOnload('config.js', function () { + var $topmenu_upt = $('#topmenu2.user_prefs_tabs'); + $topmenu_upt.find('li.active a').attr('rel', 'samepage'); + $topmenu_upt.find('li:not(.active) a').attr('rel', 'newpage'); +}); + +// default values for fields +var defaultValues = {}; + +/** + * Returns field type + * + * @param {Element} field + */ +function getFieldType (field) { + var $field = $(field); + var tagName = $field.prop('tagName'); + if (tagName === 'INPUT') { + return $field.attr('type'); + } else if (tagName === 'SELECT') { + return 'select'; + } else if (tagName === 'TEXTAREA') { + return 'text'; + } + return ''; +} + +/** + * Enables or disables the "restore default value" button + * + * @param {Element} field + * @param {boolean} display + */ +function setRestoreDefaultBtn (field, display) { + var $el = $(field).closest('td').find('.restore-default img'); + $el[display ? 'show' : 'hide'](); +} + +/** + * Marks field depending on its value (system default or custom) + * + * @param {Element} field + */ +function markField (field) { + var $field = $(field); + var type = getFieldType($field); + var isDefault = checkFieldDefault($field, type); + + // checkboxes uses parent for marking + var $fieldMarker = (type === 'checkbox') ? $field.parent() : $field; + setRestoreDefaultBtn($field, !isDefault); + $fieldMarker[isDefault ? 'removeClass' : 'addClass']('custom'); +} + +/** + * Sets field value + * + * value must be of type: + * o undefined (omitted) - restore default value (form default, not PMA default) + * o String - if field_type is 'text' + * o boolean - if field_type is 'checkbox' + * o Array of values - if field_type is 'select' + * + * @param {Element} field + * @param {String} field_type see {@link #getFieldType} + * @param {String|Boolean} value + */ +function setFieldValue (field, field_type, value) { + var $field = $(field); + switch (field_type) { + case 'text': + case 'number': + $field.val(value); + break; + case 'checkbox': + $field.prop('checked', value); + break; + case 'select': + var options = $field.prop('options'); + var i; + var imax = options.length; + var i, imax = options.length; + for (i = 0; i < imax; i++) { + options[i].selected = (value.indexOf(options[i].value) != -1); + } + break; + } + markField($field); +} + +/** + * Gets field value + * + * Will return one of: + * o String - if type is 'text' + * o boolean - if type is 'checkbox' + * o Array of values - if type is 'select' + * + * @param {Element} field + * @param {String} field_type returned by {@link #getFieldType} + * @type Boolean|String|String[] + */ +function getFieldValue (field, field_type) { + var $field = $(field); + switch (field_type) { + case 'text': + case 'number': + return $field.prop('value'); + case 'checkbox': + return $field.prop('checked'); + case 'select': + var options = $field.prop('options'); + var i; + var imax = options.length; + var items = []; + for (i = 0; i < imax; i++) { + if (options[i].selected) { + items.push(options[i].value); + } + } + return items; + } + return null; +} + +/** + * Returns values for all fields in fieldsets + */ +function getAllValues () { + var $elements = $('fieldset input, fieldset select, fieldset textarea'); + var values = {}; + var type; + var value; + for (var i = 0; i < $elements.length; i++) { + type = getFieldType($elements[i]); + value = getFieldValue($elements[i], type); + if (typeof value !== 'undefined') { + // we only have single selects, fatten array + if (type === 'select') { + value = value[0]; + } + values[$elements[i].name] = value; + } + } + return values; +} + +/** + * Checks whether field has its default value + * + * @param {Element} field + * @param {String} type + * @return boolean + */ +function checkFieldDefault (field, type) { + var $field = $(field); + var field_id = $field.attr('id'); + if (typeof defaultValues[field_id] === 'undefined') { + return true; + } + var isDefault = true; + var currentValue = getFieldValue($field, type); + if (type !== 'select') { + isDefault = currentValue === defaultValues[field_id]; + } else { + // compare arrays, will work for our representation of select values + if (currentValue.length !== defaultValues[field_id].length) { + isDefault = false; + } else { + for (var i = 0; i < currentValue.length; i++) { + if (currentValue[i] !== defaultValues[field_id][i]) { + isDefault = false; + break; + } + } + } + } + return isDefault; +} + +/** + * Returns element's id prefix + * @param {Element} element + */ +function getIdPrefix (element) { + return $(element).attr('id').replace(/[^-]+$/, ''); +} + +// ------------------------------------------------------------------ +// Form validation and field operations +// + +// form validator assignments +var validate = {}; + +// form validator list +var validators = { + // regexp: numeric value + _regexp_numeric: /^[0-9]+$/, + // regexp: extract parts from PCRE expression + _regexp_pcre_extract: /(.)(.*)\1(.*)?/, + /** + * Validates positive number + * + * @param {boolean} isKeyUp + */ + PMA_validatePositiveNumber: function (isKeyUp) { + if (isKeyUp && this.value === '') { + return true; + } + var result = this.value !== '0' && validators._regexp_numeric.test(this.value); + return result ? true : PMA_messages.error_nan_p; + }, + /** + * Validates non-negative number + * + * @param {boolean} isKeyUp + */ + PMA_validateNonNegativeNumber: function (isKeyUp) { + if (isKeyUp && this.value === '') { + return true; + } + var result = validators._regexp_numeric.test(this.value); + return result ? true : PMA_messages.error_nan_nneg; + }, + /** + * Validates port number + * + * @param {boolean} isKeyUp + */ + PMA_validatePortNumber: function (isKeyUp) { + if (this.value === '') { + return true; + } + var result = validators._regexp_numeric.test(this.value) && this.value !== '0'; + return result && this.value <= 65535 ? true : PMA_messages.error_incorrect_port; + }, + /** + * Validates value according to given regular expression + * + * @param {boolean} isKeyUp + * @param {string} regexp + */ + PMA_validateByRegex: function (isKeyUp, regexp) { + if (isKeyUp && this.value === '') { + return true; + } + // convert PCRE regexp + var parts = regexp.match(validators._regexp_pcre_extract); + var valid = this.value.match(new RegExp(parts[2], parts[3])) !== null; + return valid ? true : PMA_messages.error_invalid_value; + }, + /** + * Validates upper bound for numeric inputs + * + * @param {boolean} isKeyUp + * @param {int} max_value + */ + PMA_validateUpperBound: function (isKeyUp, max_value) { + var val = parseInt(this.value, 10); + if (isNaN(val)) { + return true; + } + return val <= max_value ? true : PMA_sprintf(PMA_messages.error_value_lte, max_value); + }, + // field validators + _field: { + }, + // fieldset validators + _fieldset: { + } +}; + +/** + * Registers validator for given field + * + * @param {String} id field id + * @param {String} type validator (key in validators object) + * @param {boolean} onKeyUp whether fire on key up + * @param {Array} params validation function parameters + */ +function validateField (id, type, onKeyUp, params) { + if (typeof validators[type] === 'undefined') { + return; + } + if (typeof validate[id] === 'undefined') { + validate[id] = []; + } + if (validate[id].length === 0) { + validate[id].push([type, params, onKeyUp]); + } +} + +/** + * Returns validation functions associated with form field + * + * @param {String} field_id form field id + * @param {boolean} onKeyUpOnly see validateField + * @type Array + * @return array of [function, parameters to be passed to function] + */ +function getFieldValidators (field_id, onKeyUpOnly) { + // look for field bound validator + var name = field_id && field_id.match(/[^-]+$/)[0]; + if (typeof validators._field[name] !== 'undefined') { + return [[validators._field[name], null]]; + } + + // look for registered validators + var functions = []; + if (typeof validate[field_id] !== 'undefined') { + // validate[field_id]: array of [type, params, onKeyUp] + for (var i = 0, imax = validate[field_id].length; i < imax; i++) { + if (onKeyUpOnly && !validate[field_id][i][2]) { + continue; + } + functions.push([validators[validate[field_id][i][0]], validate[field_id][i][1]]); + } + } + + return functions; +} + +/** + * Displays errors for given form fields + * + * WARNING: created DOM elements must be identical with the ones made by + * PhpMyAdmin\Config\FormDisplayTemplate::displayInput()! + * + * @param {Object} error_list list of errors in the form {field id: error array} + */ +function displayErrors (error_list) { + var tempIsEmpty = function (item) { + return item !== ''; + }; + + for (var field_id in error_list) { + var errors = error_list[field_id]; + var $field = $('#' + field_id); + var isFieldset = $field.attr('tagName') === 'FIELDSET'; + var $errorCnt; + if (isFieldset) { + $errorCnt = $field.find('dl.errors'); + } else { + $errorCnt = $field.siblings('.inline_errors'); + } + + // remove empty errors (used to clear error list) + errors = $.grep(errors, tempIsEmpty); + + // CSS error class + if (!isFieldset) { + // checkboxes uses parent for marking + var $fieldMarker = ($field.attr('type') === 'checkbox') ? $field.parent() : $field; + $fieldMarker[errors.length ? 'addClass' : 'removeClass']('field-error'); + } + + if (errors.length) { + // if error container doesn't exist, create it + if ($errorCnt.length === 0) { + if (isFieldset) { + $errorCnt = $('
    '); + $field.find('table').before($errorCnt); + } else { + $errorCnt = $('
    '); + $field.closest('td').append($errorCnt); + } + } + + var html = ''; + for (var i = 0, imax = errors.length; i < imax; i++) { + html += '
    ' + errors[i] + '
    '; + } + $errorCnt.html(html); + } else if ($errorCnt !== null) { + // remove useless error container + $errorCnt.remove(); + } + } +} + +/** + * Validates fields and fieldsets and call displayError function as required + */ +function setDisplayError() { + var elements = $('.optbox input[id], .optbox select[id], .optbox textarea[id]'); + // run all field validators + var errors = {}; + for (var i = 0; i < elements.length; i++) { + validate_field(elements[i], false, errors); + } + // run all fieldset validators + $('fieldset.optbox').each(function () { + validate_fieldset(this, false, errors); + }); + displayErrors(errors); +} + +/** + * Validates fieldset and puts errors in 'errors' object + * + * @param {Element} fieldset + * @param {boolean} isKeyUp + * @param {Object} errors + */ +function validate_fieldset (fieldset, isKeyUp, errors) { + var $fieldset = $(fieldset); + if ($fieldset.length && typeof validators._fieldset[$fieldset.attr('id')] !== 'undefined') { + var fieldset_errors = validators._fieldset[$fieldset.attr('id')].apply($fieldset[0], [isKeyUp]); + for (var field_id in fieldset_errors) { + if (typeof errors[field_id] === 'undefined') { + errors[field_id] = []; + } + if (typeof fieldset_errors[field_id] === 'string') { + fieldset_errors[field_id] = [fieldset_errors[field_id]]; + } + $.merge(errors[field_id], fieldset_errors[field_id]); + } + } +} + +/** + * Validates form field and puts errors in 'errors' object + * + * @param {Element} field + * @param {boolean} isKeyUp + * @param {Object} errors + */ +function validate_field (field, isKeyUp, errors) { + var args; + var result; + var $field = $(field); + var field_id = $field.attr('id'); + errors[field_id] = []; + var functions = getFieldValidators(field_id, isKeyUp); + for (var i = 0; i < functions.length; i++) { + if (typeof functions[i][1] !== 'undefined' && functions[i][1] !== null) { + args = functions[i][1].slice(0); + } else { + args = []; + } + args.unshift(isKeyUp); + result = functions[i][0].apply($field[0], args); + if (result !== true) { + if (typeof result === 'string') { + result = [result]; + } + $.merge(errors[field_id], result); + } + } +} + +/** + * Validates form field and parent fieldset + * + * @param {Element} field + * @param {boolean} isKeyUp + */ +function validate_field_and_fieldset (field, isKeyUp) { + var $field = $(field); + var errors = {}; + validate_field($field, isKeyUp, errors); + validate_fieldset($field.closest('fieldset.optbox'), isKeyUp, errors); + displayErrors(errors); +} + +function loadInlineConfig () { + if (!Array.isArray(configInlineParams)) { + return; + } + for (var i = 0; i < configInlineParams.length; ++i) { + if (typeof configInlineParams[i] === 'function') { + configInlineParams[i](); + } + } +} + +function setupValidation () { + validate = {}; + configScriptLoaded = true; + if (configScriptLoaded && typeof configInlineParams !== 'undefined') { + loadInlineConfig(); + } + // register validators and mark custom values + var $elements = $('.optbox input[id], .optbox select[id], .optbox textarea[id]'); + $elements.each(function () { + markField(this); + var $el = $(this); + $el.on('change', function () { + validate_field_and_fieldset(this, false); + markField(this); + }); + var tagName = $el.attr('tagName'); + // text fields can be validated after each change + if (tagName === 'INPUT' && $el.attr('type') === 'text') { + $el.keyup(function () { + validate_field_and_fieldset($el, true); + markField($el); + }); + } + // disable textarea spellcheck + if (tagName === 'TEXTAREA') { + $el.attr('spellcheck', false); + } + }); + + // check whether we've refreshed a page and browser remembered modified + // form values + var $check_page_refresh = $('#check_page_refresh'); + if ($check_page_refresh.length === 0 || $check_page_refresh.val() === '1') { + // run all field validators + var errors = {}; + for (var i = 0; i < $elements.length; i++) { + validate_field($elements[i], false, errors); + } + // run all fieldset validators + $('fieldset.optbox').each(function () { + validate_fieldset(this, false, errors); + }); + + displayErrors(errors); + } else if ($check_page_refresh) { + $check_page_refresh.val('1'); + } +} + +AJAX.registerOnload('config.js', function () { + setupValidation(); +}); + +// +// END: Form validation and field operations +// ------------------------------------------------------------------ + +// ------------------------------------------------------------------ +// Tabbed forms +// + +/** + * Sets active tab + * + * @param {String} tab_id + */ +function setTab (tab_id) { + $('ul.tabs').each(function () { + var $this = $(this); + if (!$this.find('li a[href="#' + tab_id + '"]').length) { + return; + } + $this.find('li').removeClass('active').find('a[href="#' + tab_id + '"]').parent().addClass('active'); + $this.parent().find('div.tabs_contents fieldset').hide().filter('#' + tab_id).show(); + var hashValue = 'tab_' + tab_id; + location.hash = hashValue; + $this.parent().find('input[name=tab_hash]').val(hashValue); + }); +} + +function setupConfigTabs () { + var forms = $('form.config-form'); + forms.each(function () { + var $this = $(this); + var $tabs = $this.find('ul.tabs'); + if (!$tabs.length) { + return; + } + // add tabs events and activate one tab (the first one or indicated by location hash) + $tabs.find('li').removeClass('active'); + $tabs.find('a') + .click(function (e) { + e.preventDefault(); + setTab($(this).attr('href').substr(1)); + }) + .filter(':first') + .parent() + .addClass('active'); + $this.find('div.tabs_contents fieldset').hide().filter(':first').show(); + }); +} + +function adjustPrefsNotification () { + var $prefsAutoLoad = $('#prefs_autoload'); + var $tableNameControl = $('#table_name_col_no'); + var $prefsAutoShowing = ($prefsAutoLoad.css('display') !== 'none'); + + if ($prefsAutoShowing && $tableNameControl.length) { + $tableNameControl.css('top', '55px'); + } +} + +AJAX.registerOnload('config.js', function () { + setupConfigTabs(); + adjustPrefsNotification(); + + // tab links handling, check each 200ms + // (works with history in FF, further browser support here would be an overkill) + var prev_hash; + var tab_check_fnc = function () { + if (location.hash !== prev_hash) { + prev_hash = location.hash; + if (prev_hash.match(/^#tab_[a-zA-Z0-9_]+$/)) { + // session ID is sometimes appended here + var hash = prev_hash.substr(5).split('&')[0]; + if ($('#' + hash).length) { + setTab(hash); + } + } + } + }; + tab_check_fnc(); + setInterval(tab_check_fnc, 200); +}); + +// +// END: Tabbed forms +// ------------------------------------------------------------------ + +// ------------------------------------------------------------------ +// Form reset buttons +// + +AJAX.registerOnload('config.js', function () { + $('.optbox input[type=button][name=submit_reset]').on('click', function () { + var fields = $(this).closest('fieldset').find('input, select, textarea'); + for (var i = 0, imax = fields.length; i < imax; i++) { + setFieldValue(fields[i], getFieldType(fields[i]), defaultValues[fields[i].id]); + } + setDisplayError(); + }); +}); + +// +// END: Form reset buttons +// ------------------------------------------------------------------ + +// ------------------------------------------------------------------ +// "Restore default" and "set value" buttons +// + +/** + * Restores field's default value + * + * @param {String} field_id + */ +function restoreField (field_id) { + var $field = $('#' + field_id); + if ($field.length === 0 || defaultValues[field_id] === undefined) { + return; + } + setFieldValue($field, getFieldType($field), defaultValues[field_id]); +} + +function setupRestoreField () { + $('div.tabs_contents') + .on('mouseenter', '.restore-default, .set-value', function () { + $(this).css('opacity', 1); + }) + .on('mouseleave', '.restore-default, .set-value', function () { + $(this).css('opacity', 0.25); + }) + .on('click', '.restore-default, .set-value', function (e) { + e.preventDefault(); + var href = $(this).attr('href'); + var field_sel; + if ($(this).hasClass('restore-default')) { + field_sel = href; + restoreField(field_sel.substr(1)); + } else { + field_sel = href.match(/^[^=]+/)[0]; + var value = href.match(/\=(.+)$/)[1]; + setFieldValue($(field_sel), 'text', value); + } + $(field_sel).trigger('change'); + }) + .find('.restore-default, .set-value') + // inline-block for IE so opacity inheritance works + .css({ display: 'inline-block', opacity: 0.25 }); +} + +AJAX.registerOnload('config.js', function () { + setupRestoreField(); +}); + +// +// END: "Restore default" and "set value" buttons +// ------------------------------------------------------------------ + +// ------------------------------------------------------------------ +// User preferences import/export +// + +AJAX.registerOnload('config.js', function () { + offerPrefsAutoimport(); + var $radios = $('#import_local_storage, #export_local_storage'); + if (!$radios.length) { + return; + } + + // enable JavaScript dependent fields + $radios + .prop('disabled', false) + .add('#export_text_file, #import_text_file') + .click(function () { + var enable_id = $(this).attr('id'); + var disable_id; + if (enable_id.match(/local_storage$/)) { + disable_id = enable_id.replace(/local_storage$/, 'text_file'); + } else { + disable_id = enable_id.replace(/text_file$/, 'local_storage'); + } + $('#opts_' + disable_id).addClass('disabled').find('input').prop('disabled', true); + $('#opts_' + enable_id).removeClass('disabled').find('input').prop('disabled', false); + }); + + // detect localStorage state + var ls_supported = isStorageSupported('localStorage', true); + var ls_exists = ls_supported ? (window.localStorage.config || false) : false; + $('div.localStorage-' + (ls_supported ? 'un' : '') + 'supported').hide(); + $('div.localStorage-' + (ls_exists ? 'empty' : 'exists')).hide(); + if (ls_exists) { + updatePrefsDate(); + } + $('form.prefs-form').change(function () { + var $form = $(this); + var disabled = false; + if (!ls_supported) { + disabled = $form.find('input[type=radio][value$=local_storage]').prop('checked'); + } else if (!ls_exists && $form.attr('name') === 'prefs_import' && + $('#import_local_storage')[0].checked + ) { + disabled = true; + } + $form.find('input[type=submit]').prop('disabled', disabled); + }).submit(function (e) { + var $form = $(this); + if ($form.attr('name') === 'prefs_export' && $('#export_local_storage')[0].checked) { + e.preventDefault(); + // use AJAX to read JSON settings and save them + savePrefsToLocalStorage($form); + } else if ($form.attr('name') === 'prefs_import' && $('#import_local_storage')[0].checked) { + // set 'json' input and submit form + $form.find('input[name=json]').val(window.localStorage.config); + } + }); + + $(document).on('click', 'div.click-hide-message', function () { + $(this) + .hide() + .parent('.group') + .css('height', '') + .next('form') + .show(); + }); +}); + +/** + * Saves user preferences to localStorage + * + * @param {Element} form + */ +function savePrefsToLocalStorage (form) { + $form = $(form); + var submit = $form.find('input[type=submit]'); + submit.prop('disabled', true); + $.ajax({ + url: 'prefs_manage.php', + cache: false, + type: 'POST', + data: { + ajax_request: true, + server: PMA_commonParams.get('server'), + submit_get_json: true + }, + success: function (data) { + if (typeof data !== 'undefined' && data.success === true) { + window.localStorage.config = data.prefs; + window.localStorage.config_mtime = data.mtime; + window.localStorage.config_mtime_local = (new Date()).toUTCString(); + updatePrefsDate(); + $('div.localStorage-empty').hide(); + $('div.localStorage-exists').show(); + var group = $form.parent('.group'); + group.css('height', group.height() + 'px'); + $form.hide('fast'); + $form.prev('.click-hide-message').show('fast'); + } else { + PMA_ajaxShowMessage(data.error); + } + }, + complete: function () { + submit.prop('disabled', false); + } + }); +} + +/** + * Updates preferences timestamp in Import form + */ +function updatePrefsDate () { + var d = new Date(window.localStorage.config_mtime_local); + var msg = PMA_messages.strSavedOn.replace( + '@DATE@', + PMA_formatDateTime(d) + ); + $('#opts_import_local_storage').find('div.localStorage-exists').html(msg); +} + +/** + * Prepares message which informs that localStorage preferences are available and can be imported or deleted + */ +function offerPrefsAutoimport () { + var has_config = (isStorageSupported('localStorage')) && (window.localStorage.config || false); + var $cnt = $('#prefs_autoload'); + if (!$cnt.length || !has_config) { + return; + } + $cnt.find('a').click(function (e) { + e.preventDefault(); + var $a = $(this); + if ($a.attr('href') === '#no') { + $cnt.remove(); + $.post('index.php', { + server: PMA_commonParams.get('server'), + prefs_autoload: 'hide' + }, null, 'html'); + return; + } else if ($a.attr('href') === '#delete') { + $cnt.remove(); + localStorage.clear(); + $.post('index.php', { + server: PMA_commonParams.get('server'), + prefs_autoload: 'hide' + }, null, 'html'); + return; + } + $cnt.find('input[name=json]').val(window.localStorage.config); + $cnt.find('form').submit(); + }); + $cnt.show(); +} + +// +// END: User preferences import/export +// ------------------------------------------------------------------ diff --git a/php/apps/phpmyadmin49/html/js/console.js b/php/apps/phpmyadmin49/html/js/console.js new file mode 100644 index 00000000..74ab018f --- /dev/null +++ b/php/apps/phpmyadmin49/html/js/console.js @@ -0,0 +1,1495 @@ +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * Used in or for console + * + * @package phpMyAdmin-Console + */ + +/** + * Console object + */ +var PMA_console = { + /** + * @var object, jQuery object, selector is '#pma_console>.content' + * @access private + */ + $consoleContent: null, + /** + * @var object, jQuery object, selector is '#pma_console .content', + * used for resizer + * @access private + */ + $consoleAllContents: null, + /** + * @var object, jQuery object, selector is '#pma_console .toolbar' + * @access private + */ + $consoleToolbar: null, + /** + * @var object, jQuery object, selector is '#pma_console .template' + * @access private + */ + $consoleTemplates: null, + /** + * @var object, jQuery object, form for submit + * @access private + */ + $requestForm: null, + /** + * @var object, contain console config + * @access private + */ + config: null, + /** + * @var bool, if console element exist, it'll be true + * @access public + */ + isEnabled: false, + /** + * @var bool, make sure console events bind only once + * @access private + */ + isInitialized: false, + /** + * Used for console initialize, reinit is ok, just some variable assignment + * + * @return void + */ + initialize: function () { + if ($('#pma_console').length === 0) { + return; + } + + PMA_console.config = configGet('Console', false); + + PMA_console.isEnabled = true; + + // Vars init + PMA_console.$consoleToolbar = $('#pma_console').find('>.toolbar'); + PMA_console.$consoleContent = $('#pma_console').find('>.content'); + PMA_console.$consoleAllContents = $('#pma_console').find('.content'); + PMA_console.$consoleTemplates = $('#pma_console').find('>.templates'); + + // Generate a from for post + PMA_console.$requestForm = $('
    ' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '
    ' + ); + PMA_console.$requestForm.children('[name=token]').val(PMA_commonParams.get('token')); + PMA_console.$requestForm.on('submit', AJAX.requestHandler); + + // Event binds shouldn't run again + if (PMA_console.isInitialized === false) { + // Load config first + if (PMA_console.config.AlwaysExpand === true) { + $('#pma_console_options input[name=always_expand]').prop('checked', true); + } + if (PMA_console.config.StartHistory === true) { + $('#pma_console_options').find('input[name=start_history]').prop('checked', true); + } + if (PMA_console.config.CurrentQuery === true) { + $('#pma_console_options').find('input[name=current_query]').prop('checked', true); + } + if (PMA_console.config.EnterExecutes === true) { + $('#pma_console_options').find('input[name=enter_executes]').prop('checked', true); + } + if (PMA_console.config.DarkTheme === true) { + $('#pma_console_options').find('input[name=dark_theme]').prop('checked', true); + $('#pma_console').find('>.content').addClass('console_dark_theme'); + } + + PMA_consoleResizer.initialize(); + PMA_consoleInput.initialize(); + PMA_consoleMessages.initialize(); + PMA_consoleBookmarks.initialize(); + PMA_consoleDebug.initialize(); + + PMA_console.$consoleToolbar.children('.console_switch').click(PMA_console.toggle); + + $('#pma_console').find('.toolbar').children().mousedown(function (event) { + event.preventDefault(); + event.stopImmediatePropagation(); + }); + + $('#pma_console').find('.button.clear').click(function () { + PMA_consoleMessages.clear(); + }); + + $('#pma_console').find('.button.history').click(function () { + PMA_consoleMessages.showHistory(); + }); + + $('#pma_console').find('.button.options').click(function () { + PMA_console.showCard('#pma_console_options'); + }); + + $('#pma_console').find('.button.debug').click(function () { + PMA_console.showCard('#debug_console'); + }); + + PMA_console.$consoleContent.click(function (event) { + if (event.target === this) { + PMA_consoleInput.focus(); + } + }); + + $('#pma_console').find('.mid_layer').click(function () { + PMA_console.hideCard($(this).parent().children('.card')); + }); + $('#debug_console').find('.switch_button').click(function () { + PMA_console.hideCard($(this).closest('.card')); + }); + $('#pma_bookmarks').find('.switch_button').click(function () { + PMA_console.hideCard($(this).closest('.card')); + }); + $('#pma_console_options').find('.switch_button').click(function () { + PMA_console.hideCard($(this).closest('.card')); + }); + + $('#pma_console_options').find('input[type=checkbox]').change(function () { + PMA_console.updateConfig(); + }); + + $('#pma_console_options').find('.button.default').click(function () { + $('#pma_console_options input[name=always_expand]').prop('checked', false); + $('#pma_console_options').find('input[name=start_history]').prop('checked', false); + $('#pma_console_options').find('input[name=current_query]').prop('checked', true); + $('#pma_console_options').find('input[name=enter_executes]').prop('checked', false); + $('#pma_console_options').find('input[name=dark_theme]').prop('checked', false); + PMA_console.updateConfig(); + }); + + $('#pma_console_options').find('input[name=enter_executes]').change(function () { + PMA_consoleMessages.showInstructions(PMA_console.config.EnterExecutes); + }); + + $(document).ajaxComplete(function (event, xhr, ajaxOptions) { + if (ajaxOptions.dataType && ajaxOptions.dataType.indexOf('json') !== -1) { + return; + } + if (xhr.status !== 200) { + return; + } + try { + var data = JSON.parse(xhr.responseText); + PMA_console.ajaxCallback(data); + } catch (e) { + console.trace(); + console.log('Failed to parse JSON: ' + e.message); + } + }); + + PMA_console.isInitialized = true; + } + + // Change console mode from cookie + switch (PMA_console.config.Mode) { + case 'collapse': + PMA_console.collapse(); + break; + /* jshint -W086 */// no break needed in default section + default: + PMA_console.setConfig('Mode', 'info'); + case 'info': + /* jshint +W086 */ + PMA_console.info(); + break; + case 'show': + PMA_console.show(true); + PMA_console.scrollBottom(); + break; + } + }, + /** + * Execute query and show results in console + * + * @return void + */ + execute: function (queryString, options) { + if (typeof(queryString) !== 'string' || ! /[a-z]|[A-Z]/.test(queryString)) { + return; + } + PMA_console.$requestForm.children('textarea').val(queryString); + PMA_console.$requestForm.children('[name=server]').attr('value', PMA_commonParams.get('server')); + if (options && options.db) { + PMA_console.$requestForm.children('[name=db]').val(options.db); + if (options.table) { + PMA_console.$requestForm.children('[name=table]').val(options.table); + } else { + PMA_console.$requestForm.children('[name=table]').val(''); + } + } else { + PMA_console.$requestForm.children('[name=db]').val( + (PMA_commonParams.get('db').length > 0 ? PMA_commonParams.get('db') : '')); + } + PMA_console.$requestForm.find('[name=profiling]').remove(); + if (options && options.profiling === true) { + PMA_console.$requestForm.append(''); + } + if (! confirmQuery(PMA_console.$requestForm[0], PMA_console.$requestForm.children('textarea')[0].value)) { + return; + } + PMA_console.$requestForm.children('[name=console_message_id]') + .val(PMA_consoleMessages.appendQuery({ sql_query: queryString }).message_id); + PMA_console.$requestForm.trigger('submit'); + PMA_consoleInput.clear(); + PMA_reloadNavigation(); + }, + ajaxCallback: function (data) { + if (data && data.console_message_id) { + PMA_consoleMessages.updateQuery(data.console_message_id, data.success, + (data._reloadQuerywindow ? data._reloadQuerywindow : false)); + } else if (data && data._reloadQuerywindow) { + if (data._reloadQuerywindow.sql_query.length > 0) { + PMA_consoleMessages.appendQuery(data._reloadQuerywindow, 'successed') + .$message.addClass(PMA_console.config.CurrentQuery ? '' : 'hide'); + } + } + }, + /** + * Change console to collapse mode + * + * @return void + */ + collapse: function () { + PMA_console.setConfig('Mode', 'collapse'); + var pmaConsoleHeight = Math.max(92, PMA_console.config.Height); + + PMA_console.$consoleToolbar.addClass('collapsed'); + PMA_console.$consoleAllContents.height(pmaConsoleHeight); + PMA_console.$consoleContent.stop(); + PMA_console.$consoleContent.animate({ 'margin-bottom': -1 * PMA_console.$consoleContent.outerHeight() + 'px' }, + 'fast', 'easeOutQuart', function () { + PMA_console.$consoleContent.css({ display:'none' }); + $(window).trigger('resize'); + }); + PMA_console.hideCard(); + }, + /** + * Show console + * + * @param bool inputFocus If true, focus the input line after show() + * @return void + */ + show: function (inputFocus) { + PMA_console.setConfig('Mode', 'show'); + + var pmaConsoleHeight = Math.max(92, PMA_console.config.Height); + pmaConsoleHeight = Math.min(PMA_console.config.Height, (window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight) - 25); + PMA_console.$consoleContent.css({ display:'block' }); + if (PMA_console.$consoleToolbar.hasClass('collapsed')) { + PMA_console.$consoleToolbar.removeClass('collapsed'); + } + PMA_console.$consoleAllContents.height(pmaConsoleHeight); + PMA_console.$consoleContent.stop(); + PMA_console.$consoleContent.animate({ 'margin-bottom': 0 }, + 'fast', 'easeOutQuart', function () { + $(window).trigger('resize'); + if (inputFocus) { + PMA_consoleInput.focus(); + } + }); + }, + /** + * Change console to SQL information mode + * this mode shows current SQL query + * This mode is the default mode + * + * @return void + */ + info: function () { + // Under construction + PMA_console.collapse(); + }, + /** + * Toggle console mode between collapse/show + * Used for toggle buttons and shortcuts + * + * @return void + */ + toggle: function () { + switch (PMA_console.config.Mode) { + case 'collapse': + case 'info': + PMA_console.show(true); + break; + case 'show': + PMA_console.collapse(); + break; + default: + PMA_consoleInitialize(); + } + }, + /** + * Scroll console to bottom + * + * @return void + */ + scrollBottom: function () { + PMA_console.$consoleContent.scrollTop(PMA_console.$consoleContent.prop('scrollHeight')); + }, + /** + * Show card + * + * @param string cardSelector Selector, select string will be "#pma_console " + cardSelector + * this param also can be JQuery object, if you need. + * + * @return void + */ + showCard: function (cardSelector) { + var $card = null; + if (typeof(cardSelector) !== 'string') { + if (cardSelector.length > 0) { + $card = cardSelector; + } else { + return; + } + } else { + $card = $('#pma_console ' + cardSelector); + } + if ($card.length === 0) { + return; + } + $card.parent().children('.mid_layer').show().fadeTo(0, 0.15); + $card.addClass('show'); + PMA_consoleInput.blur(); + if ($card.parents('.card').length > 0) { + PMA_console.showCard($card.parents('.card')); + } + }, + /** + * Scroll console to bottom + * + * @param object $targetCard Target card JQuery object, if it's empty, function will hide all cards + * @return void + */ + hideCard: function ($targetCard) { + if (! $targetCard) { + $('#pma_console').find('.mid_layer').fadeOut(140); + $('#pma_console').find('.card').removeClass('show'); + } else if ($targetCard.length > 0) { + $targetCard.parent().find('.mid_layer').fadeOut(140); + $targetCard.find('.card').removeClass('show'); + $targetCard.removeClass('show'); + } + }, + /** + * Used for update console config + * + * @return void + */ + updateConfig: function () { + PMA_console.setConfig('AlwaysExpand', $('#pma_console_options input[name=always_expand]').prop('checked')); + PMA_console.setConfig('StartHistory', $('#pma_console_options').find('input[name=start_history]').prop('checked')); + PMA_console.setConfig('CurrentQuery', $('#pma_console_options').find('input[name=current_query]').prop('checked')); + PMA_console.setConfig('EnterExecutes', $('#pma_console_options').find('input[name=enter_executes]').prop('checked')); + PMA_console.setConfig('DarkTheme', $('#pma_console_options').find('input[name=dark_theme]').prop('checked')); + /* Setting the dark theme of the console*/ + if (PMA_console.config.DarkTheme) { + $('#pma_console').find('>.content').addClass('console_dark_theme'); + } else { + $('#pma_console').find('>.content').removeClass('console_dark_theme'); + } + }, + setConfig: function (key, value) { + PMA_console.config[key] = value; + configSet('Console/' + key, value); + }, + isSelect: function (queryString) { + var reg_exp = /^SELECT\s+/i; + return reg_exp.test(queryString); + } +}; + +/** + * Resizer object + * Careful: this object UI logics highly related with functions under PMA_console + * Resizing min-height is 32, if small than it, console will collapse + */ +var PMA_consoleResizer = { + _posY: 0, + _height: 0, + _resultHeight: 0, + /** + * Mousedown event handler for bind to resizer + * + * @return void + */ + _mousedown: function (event) { + if (PMA_console.config.Mode !== 'show') { + return; + } + PMA_consoleResizer._posY = event.pageY; + PMA_consoleResizer._height = PMA_console.$consoleContent.height(); + $(document).mousemove(PMA_consoleResizer._mousemove); + $(document).mouseup(PMA_consoleResizer._mouseup); + // Disable text selection while resizing + $(document).on('selectstart', function () { + return false; + }); + }, + /** + * Mousemove event handler for bind to resizer + * + * @return void + */ + _mousemove: function (event) { + if (event.pageY < 35) { + event.pageY = 35; + } + PMA_consoleResizer._resultHeight = PMA_consoleResizer._height + (PMA_consoleResizer._posY - event.pageY); + // Content min-height is 32, if adjusting height small than it we'll move it out of the page + if (PMA_consoleResizer._resultHeight <= 32) { + PMA_console.$consoleAllContents.height(32); + PMA_console.$consoleContent.css('margin-bottom', PMA_consoleResizer._resultHeight - 32); + } else { + // Logic below makes viewable area always at bottom when adjusting height and content already at bottom + if (PMA_console.$consoleContent.scrollTop() + PMA_console.$consoleContent.innerHeight() + 16 + >= PMA_console.$consoleContent.prop('scrollHeight')) { + PMA_console.$consoleAllContents.height(PMA_consoleResizer._resultHeight); + PMA_console.scrollBottom(); + } else { + PMA_console.$consoleAllContents.height(PMA_consoleResizer._resultHeight); + } + } + }, + /** + * Mouseup event handler for bind to resizer + * + * @return void + */ + _mouseup: function () { + PMA_console.setConfig('Height', PMA_consoleResizer._resultHeight); + PMA_console.show(); + $(document).off('mousemove'); + $(document).off('mouseup'); + $(document).off('selectstart'); + }, + /** + * Used for console resizer initialize + * + * @return void + */ + initialize: function () { + $('#pma_console').find('.toolbar').off('mousedown'); + $('#pma_console').find('.toolbar').mousedown(PMA_consoleResizer._mousedown); + } +}; + + +/** + * Console input object + */ +var PMA_consoleInput = { + /** + * @var array, contains Codemirror objects or input jQuery objects + * @access private + */ + _inputs: null, + /** + * @var bool, if codemirror enabled + * @access private + */ + _codemirror: false, + /** + * @var int, count for history navigation, 0 for current input + * @access private + */ + _historyCount: 0, + /** + * @var string, current input when navigating through history + * @access private + */ + _historyPreserveCurrent: null, + /** + * Used for console input initialize + * + * @return void + */ + initialize: function () { + // _cm object can't be reinitialize + if (PMA_consoleInput._inputs !== null) { + return; + } + if (typeof CodeMirror !== 'undefined') { + PMA_consoleInput._codemirror = true; + } + PMA_consoleInput._inputs = []; + if (PMA_consoleInput._codemirror) { + PMA_consoleInput._inputs.console = CodeMirror($('#pma_console').find('.console_query_input')[0], { + theme: 'pma', + mode: 'text/x-sql', + lineWrapping: true, + extraKeys: { 'Ctrl-Space': 'autocomplete' }, + hintOptions: { 'completeSingle': false, 'completeOnSingleClick': true }, + gutters: ['CodeMirror-lint-markers'], + lint: { + 'getAnnotations': CodeMirror.sqlLint, + 'async': true, + } + }); + PMA_consoleInput._inputs.console.on('inputRead', codemirrorAutocompleteOnInputRead); + PMA_consoleInput._inputs.console.on('keydown', function (instance, event) { + PMA_consoleInput._historyNavigate(event); + }); + if ($('#pma_bookmarks').length !== 0) { + PMA_consoleInput._inputs.bookmark = CodeMirror($('#pma_console').find('.bookmark_add_input')[0], { + theme: 'pma', + mode: 'text/x-sql', + lineWrapping: true, + extraKeys: { 'Ctrl-Space': 'autocomplete' }, + hintOptions: { 'completeSingle': false, 'completeOnSingleClick': true }, + gutters: ['CodeMirror-lint-markers'], + lint: { + 'getAnnotations': CodeMirror.sqlLint, + 'async': true, + } + }); + PMA_consoleInput._inputs.bookmark.on('inputRead', codemirrorAutocompleteOnInputRead); + } + } else { + PMA_consoleInput._inputs.console = + $('\n'; + new_content += getForeignKeyCheckboxLoader(); + new_content += '\n'; + new_content += '\n'; + var $editor_area = $('div#inline_editor'); + if ($editor_area.length === 0) { + $editor_area = $('
    '); + $editor_area.insertBefore($inner_sql); + } + $editor_area.html(new_content); + loadForeignKeyCheckbox(); + $inner_sql.hide(); + + bindCodeMirrorToInlineEditor(); + return false; + }); + + $(document).on('click', 'input#sql_query_edit_save', function () { + // hide already existing success message + var sql_query; + if (codemirror_inline_editor) { + codemirror_inline_editor.save(); + sql_query = codemirror_inline_editor.getValue(); + } else { + sql_query = $(this).parent().find('#sql_query_edit').val(); + } + var fk_check = $(this).parent().find('#fk_checks').is(':checked'); + + var $form = $('a.inline_edit_sql').prev('form'); + var $fake_form = $('
    ', { action: 'import.php', method: 'post' }) + .append($form.find('input[name=server], input[name=db], input[name=table], input[name=token]').clone()) + .append($('', { type: 'hidden', name: 'show_query', value: 1 })) + .append($('', { type: 'hidden', name: 'is_js_confirmed', value: 0 })) + .append($('', { type: 'hidden', name: 'sql_query', value: sql_query })) + .append($('', { type: 'hidden', name: 'fk_checks', value: fk_check ? 1 : 0 })); + if (! checkSqlQuery($fake_form[0])) { + return false; + } + $('.success').hide(); + $fake_form.appendTo($('body')).submit(); + }); + + $(document).on('click', 'input#sql_query_edit_discard', function () { + var $divEditor = $('div#inline_editor_outer'); + $divEditor.siblings('code.sql').show(); + $divEditor.remove(); + }); + + $(document).on('click', 'input.sqlbutton', function (evt) { + insertQuery(evt.target.id); + PMA_handleSimulateQueryButton(); + return false; + }); + + $(document).on('change', '#parameterized', updateQueryParameters); + + var $inputUsername = $('#input_username'); + if ($inputUsername) { + if ($inputUsername.val() === '') { + $inputUsername.trigger('focus'); + } else { + $('#input_password').trigger('focus'); + } + } +}); + +/** + * "inputRead" event handler for CodeMirror SQL query editors for autocompletion + */ +function codemirrorAutocompleteOnInputRead (instance) { + if (!sql_autocomplete_in_progress + && (!instance.options.hintOptions.tables || !sql_autocomplete)) { + if (!sql_autocomplete) { + // Reset after teardown + instance.options.hintOptions.tables = false; + instance.options.hintOptions.defaultTable = ''; + + sql_autocomplete_in_progress = true; + + var href = 'db_sql_autocomplete.php'; + var params = { + 'ajax_request': true, + 'server': PMA_commonParams.get('server'), + 'db': PMA_commonParams.get('db'), + 'no_debug': true + }; + + var columnHintRender = function (elem, self, data) { + $('
    ') + .text(data.columnName) + .appendTo(elem); + $('
    ') + .text(data.columnHint) + .appendTo(elem); + }; + + $.ajax({ + type: 'POST', + url: href, + data: params, + success: function (data) { + if (data.success) { + var tables = JSON.parse(data.tables); + sql_autocomplete_default_table = PMA_commonParams.get('table'); + sql_autocomplete = []; + for (var table in tables) { + if (tables.hasOwnProperty(table)) { + var columns = tables[table]; + table = { + text: table, + columns: [] + }; + for (var column in columns) { + if (columns.hasOwnProperty(column)) { + var displayText = columns[column].Type; + if (columns[column].Key === 'PRI') { + displayText += ' | Primary'; + } else if (columns[column].Key === 'UNI') { + displayText += ' | Unique'; + } + table.columns.push({ + text: column, + displayText: column + ' | ' + displayText, + columnName: column, + columnHint: displayText, + render: columnHintRender + }); + } + } + } + sql_autocomplete.push(table); + } + instance.options.hintOptions.tables = sql_autocomplete; + instance.options.hintOptions.defaultTable = sql_autocomplete_default_table; + } + }, + complete: function () { + sql_autocomplete_in_progress = false; + } + }); + } else { + instance.options.hintOptions.tables = sql_autocomplete; + instance.options.hintOptions.defaultTable = sql_autocomplete_default_table; + } + } + if (instance.state.completionActive) { + return; + } + var cur = instance.getCursor(); + var token = instance.getTokenAt(cur); + var string = ''; + if (token.string.match(/^[.`\w@]\w*$/)) { + string = token.string; + } + if (string.length > 0) { + CodeMirror.commands.autocomplete(instance); + } +} + +/** + * Remove autocomplete information before tearing down a page + */ +AJAX.registerTeardown('functions.js', function () { + sql_autocomplete = false; + sql_autocomplete_default_table = ''; +}); + +/** + * Binds the CodeMirror to the text area used to inline edit a query. + */ +function bindCodeMirrorToInlineEditor () { + var $inline_editor = $('#sql_query_edit'); + if ($inline_editor.length > 0) { + if (typeof CodeMirror !== 'undefined') { + var height = $inline_editor.css('height'); + codemirror_inline_editor = PMA_getSQLEditor($inline_editor); + codemirror_inline_editor.getWrapperElement().style.height = height; + codemirror_inline_editor.refresh(); + codemirror_inline_editor.focus(); + $(codemirror_inline_editor.getWrapperElement()) + .on('keydown', catchKeypressesFromSqlInlineEdit); + } else { + $inline_editor + .focus() + .on('keydown', catchKeypressesFromSqlInlineEdit); + } + } +} + +function catchKeypressesFromSqlInlineEdit (event) { + // ctrl-enter is 10 in chrome and ie, but 13 in ff + if ((event.ctrlKey || event.metaKey) && (event.keyCode === 13 || event.keyCode === 10)) { + $('#sql_query_edit_save').trigger('click'); + } +} + +/** + * Adds doc link to single highlighted SQL element + */ +function PMA_doc_add ($elm, params) { + if (typeof mysql_doc_template === 'undefined') { + return; + } + + var url = PMA_sprintf( + decodeURIComponent(mysql_doc_template), + params[0] + ); + if (params.length > 1) { + url += '#' + params[1]; + } + var content = $elm.text(); + $elm.text(''); + $elm.append('' + content + ''); +} + +/** + * Generates doc links for keywords inside highlighted SQL + */ +function PMA_doc_keyword (idx, elm) { + var $elm = $(elm); + /* Skip already processed ones */ + if ($elm.find('a').length > 0) { + return; + } + var keyword = $elm.text().toUpperCase(); + var $next = $elm.next('.cm-keyword'); + if ($next) { + var next_keyword = $next.text().toUpperCase(); + var full = keyword + ' ' + next_keyword; + + var $next2 = $next.next('.cm-keyword'); + if ($next2) { + var next2_keyword = $next2.text().toUpperCase(); + var full2 = full + ' ' + next2_keyword; + if (full2 in mysql_doc_keyword) { + PMA_doc_add($elm, mysql_doc_keyword[full2]); + PMA_doc_add($next, mysql_doc_keyword[full2]); + PMA_doc_add($next2, mysql_doc_keyword[full2]); + return; + } + } + if (full in mysql_doc_keyword) { + PMA_doc_add($elm, mysql_doc_keyword[full]); + PMA_doc_add($next, mysql_doc_keyword[full]); + return; + } + } + if (keyword in mysql_doc_keyword) { + PMA_doc_add($elm, mysql_doc_keyword[keyword]); + } +} + +/** + * Generates doc links for builtins inside highlighted SQL + */ +function PMA_doc_builtin (idx, elm) { + var $elm = $(elm); + var builtin = $elm.text().toUpperCase(); + if (builtin in mysql_doc_builtin) { + PMA_doc_add($elm, mysql_doc_builtin[builtin]); + } +} + +/** + * Higlights SQL using CodeMirror. + */ +function PMA_highlightSQL ($base) { + var $elm = $base.find('code.sql'); + $elm.each(function () { + var $sql = $(this); + var $pre = $sql.find('pre'); + /* We only care about visible elements to avoid double processing */ + if ($pre.is(':visible')) { + var $highlight = $('
    '); + $sql.append($highlight); + if (typeof CodeMirror !== 'undefined') { + CodeMirror.runMode($sql.text(), 'text/x-mysql', $highlight[0]); + $pre.hide(); + $highlight.find('.cm-keyword').each(PMA_doc_keyword); + $highlight.find('.cm-builtin').each(PMA_doc_builtin); + } + } + }); +} + +/** + * Updates an element containing code. + * + * @param jQuery Object $base base element which contains the raw and the + * highlighted code. + * + * @param string htmlValue code in HTML format, displayed if code cannot be + * highlighted + * + * @param string rawValue raw code, used as a parameter for highlighter + * + * @return bool whether content was updated or not + */ +function PMA_updateCode ($base, htmlValue, rawValue) { + var $code = $base.find('code'); + if ($code.length === 0) { + return false; + } + + // Determines the type of the content and appropriate CodeMirror mode. + var type = ''; + var mode = ''; + if ($code.hasClass('json')) { + type = 'json'; + mode = 'application/json'; + } else if ($code.hasClass('sql')) { + type = 'sql'; + mode = 'text/x-mysql'; + } else if ($code.hasClass('xml')) { + type = 'xml'; + mode = 'application/xml'; + } else { + return false; + } + + // Element used to display unhighlighted code. + var $notHighlighted = $('
    ' + htmlValue + '
    '); + + // Tries to highlight code using CodeMirror. + if (typeof CodeMirror !== 'undefined') { + var $highlighted = $('
    '); + CodeMirror.runMode(rawValue, mode, $highlighted[0]); + $notHighlighted.hide(); + $code.html('').append($notHighlighted, $highlighted[0]); + } else { + $code.html('').append($notHighlighted); + } + + return true; +} + +/** + * Show a message on the top of the page for an Ajax request + * + * Sample usage: + * + * 1) var $msg = PMA_ajaxShowMessage(); + * This will show a message that reads "Loading...". Such a message will not + * disappear automatically and cannot be dismissed by the user. To remove this + * message either the PMA_ajaxRemoveMessage($msg) function must be called or + * another message must be show with PMA_ajaxShowMessage() function. + * + * 2) var $msg = PMA_ajaxShowMessage(PMA_messages.strProcessingRequest); + * This is a special case. The behaviour is same as above, + * just with a different message + * + * 3) var $msg = PMA_ajaxShowMessage('The operation was successful'); + * This will show a message that will disappear automatically and it can also + * be dismissed by the user. + * + * 4) var $msg = PMA_ajaxShowMessage('Some error', false); + * This will show a message that will not disappear automatically, but it + * can be dismissed by the user after he has finished reading it. + * + * @param string message string containing the message to be shown. + * optional, defaults to 'Loading...' + * @param mixed timeout number of milliseconds for the message to be visible + * optional, defaults to 5000. If set to 'false', the + * notification will never disappear + * @param string type string to dictate the type of message shown. + * optional, defaults to normal notification. + * If set to 'error', the notification will show message + * with red background. + * If set to 'success', the notification will show with + * a green background. + * @return jQuery object jQuery Element that holds the message div + * this object can be passed to PMA_ajaxRemoveMessage() + * to remove the notification + */ +function PMA_ajaxShowMessage (message, timeout, type) { + /** + * @var self_closing Whether the notification will automatically disappear + */ + var self_closing = true; + /** + * @var dismissable Whether the user will be able to remove + * the notification by clicking on it + */ + var dismissable = true; + // Handle the case when a empty data.message is passed. + // We don't want the empty message + if (message === '') { + return true; + } else if (! message) { + // If the message is undefined, show the default + message = PMA_messages.strLoading; + dismissable = false; + self_closing = false; + } else if (message === PMA_messages.strProcessingRequest) { + // This is another case where the message should not disappear + dismissable = false; + self_closing = false; + } + // Figure out whether (or after how long) to remove the notification + if (timeout === undefined) { + timeout = 5000; + } else if (timeout === false) { + self_closing = false; + } + // Determine type of message, add styling as required + if (type === 'error') { + message = '
    ' + message + '
    '; + } else if (type === 'success') { + message = '
    ' + message + '
    '; + } + // Create a parent element for the AJAX messages, if necessary + if ($('#loading_parent').length === 0) { + $('
    ') + .prependTo('#page_content'); + } + // Update message count to create distinct message elements every time + ajax_message_count++; + // Remove all old messages, if any + $('span.ajax_notification[id^=ajax_message_num]').remove(); + /** + * @var $retval a jQuery object containing the reference + * to the created AJAX message + */ + var $retval = $( + '' + ) + .hide() + .appendTo('#loading_parent') + .html(message) + .show(); + // If the notification is self-closing we should create a callback to remove it + if (self_closing) { + $retval + .delay(timeout) + .fadeOut('medium', function () { + if ($(this).is(':data(tooltip)')) { + $(this).tooltip('destroy'); + } + // Remove the notification + $(this).remove(); + }); + } + // If the notification is dismissable we need to add the relevant class to it + // and add a tooltip so that the users know that it can be removed + if (dismissable) { + $retval.addClass('dismissable').css('cursor', 'pointer'); + /** + * Add a tooltip to the notification to let the user know that (s)he + * can dismiss the ajax notification by clicking on it. + */ + PMA_tooltip( + $retval, + 'span', + PMA_messages.strDismiss + ); + } + PMA_highlightSQL($retval); + + return $retval; +} + +/** + * Removes the message shown for an Ajax operation when it's completed + * + * @param jQuery object jQuery Element that holds the notification + * + * @return nothing + */ +function PMA_ajaxRemoveMessage ($this_msgbox) { + if ($this_msgbox !== undefined && $this_msgbox instanceof jQuery) { + $this_msgbox + .stop(true, true) + .fadeOut('medium'); + if ($this_msgbox.is(':data(tooltip)')) { + $this_msgbox.tooltip('destroy'); + } else { + $this_msgbox.remove(); + } + } +} + +/** + * Requests SQL for previewing before executing. + * + * @param jQuery Object $form Form containing query data + * + * @return void + */ +function PMA_previewSQL ($form) { + var form_url = $form.attr('action'); + var sep = PMA_commonParams.get('arg_separator'); + var form_data = $form.serialize() + + sep + 'do_save_data=1' + + sep + 'preview_sql=1' + + sep + 'ajax_request=1'; + var $msgbox = PMA_ajaxShowMessage(); + $.ajax({ + type: 'POST', + url: form_url, + data: form_data, + success: function (response) { + PMA_ajaxRemoveMessage($msgbox); + if (response.success) { + var $dialog_content = $('
    ') + .append(response.sql_data); + var button_options = {}; + button_options[PMA_messages.strClose] = function () { + $(this).dialog('close'); + }; + var $response_dialog = $dialog_content.dialog({ + minWidth: 550, + maxHeight: 400, + modal: true, + buttons: button_options, + title: PMA_messages.strPreviewSQL, + close: function () { + $(this).remove(); + }, + open: function () { + // Pretty SQL printing. + PMA_highlightSQL($(this)); + } + }); + } else { + PMA_ajaxShowMessage(response.message); + } + }, + error: function () { + PMA_ajaxShowMessage(PMA_messages.strErrorProcessingRequest); + } + }); +} + +/** + * Callback called when submit/"OK" is clicked on sql preview/confirm modal + * + * @callback onSubmitCallback + * @param {string} url The url + */ + +/** + * + * @param {string} sql_data Sql query to preview + * @param {string} url Url to be sent to callback + * @param {onSubmitCallback} callback On submit callback function + * + * @return void + */ +function PMA_confirmPreviewSQL (sql_data, url, callback) { + var $dialog_content = $('
    '
    +        + sql_data
    +        + '
    ' + ); + var button_options = [ + { + text: PMA_messages.strOK, + class: 'submitOK', + click: function () { + callback(url); + } + }, + { + text: PMA_messages.strCancel, + class: 'submitCancel', + click: function () { + $(this).dialog('close'); + } + } + ]; + var $response_dialog = $dialog_content.dialog({ + minWidth: 550, + maxHeight: 400, + modal: true, + buttons: button_options, + title: PMA_messages.strPreviewSQL, + close: function () { + $(this).remove(); + }, + open: function () { + // Pretty SQL printing. + PMA_highlightSQL($(this)); + } + }); +} + +/** + * check for reserved keyword column name + * + * @param jQuery Object $form Form + * + * @returns true|false + */ + +function PMA_checkReservedWordColumns ($form) { + var is_confirmed = true; + $.ajax({ + type: 'POST', + url: 'tbl_structure.php', + data: $form.serialize() + PMA_commonParams.get('arg_separator') + 'reserved_word_check=1', + success: function (data) { + if (typeof data.success !== 'undefined' && data.success === true) { + is_confirmed = confirm(data.message); + } + }, + async:false + }); + return is_confirmed; +} + +// This event only need to be fired once after the initial page load +$(function () { + /** + * Allows the user to dismiss a notification + * created with PMA_ajaxShowMessage() + */ + $(document).on('click', 'span.ajax_notification.dismissable', function () { + PMA_ajaxRemoveMessage($(this)); + }); + /** + * The below two functions hide the "Dismiss notification" tooltip when a user + * is hovering a link or button that is inside an ajax message + */ + $(document).on('mouseover', 'span.ajax_notification a, span.ajax_notification button, span.ajax_notification input', function () { + if ($(this).parents('span.ajax_notification').is(':data(tooltip)')) { + $(this).parents('span.ajax_notification').tooltip('disable'); + } + }); + $(document).on('mouseout', 'span.ajax_notification a, span.ajax_notification button, span.ajax_notification input', function () { + if ($(this).parents('span.ajax_notification').is(':data(tooltip)')) { + $(this).parents('span.ajax_notification').tooltip('enable'); + } + }); +}); + +/** + * Hides/shows the "Open in ENUM/SET editor" message, depending on the data type of the column currently selected + */ +function PMA_showNoticeForEnum (selectElement) { + var enum_notice_id = selectElement.attr('id').split('_')[1]; + enum_notice_id += '_' + (parseInt(selectElement.attr('id').split('_')[2], 10) + 1); + var selectedType = selectElement.val(); + if (selectedType === 'ENUM' || selectedType === 'SET') { + $('p#enum_notice_' + enum_notice_id).show(); + } else { + $('p#enum_notice_' + enum_notice_id).hide(); + } +} + +/** + * Creates a Profiling Chart. Used in sql.js + * and in server_status_monitor.js + */ +function PMA_createProfilingChart (target, data) { + // create the chart + var factory = new JQPlotChartFactory(); + var chart = factory.createChart(ChartType.PIE, target); + + // create the data table and add columns + var dataTable = new DataTable(); + dataTable.addColumn(ColumnType.STRING, ''); + dataTable.addColumn(ColumnType.NUMBER, ''); + dataTable.setData(data); + + var windowWidth = $(window).width(); + var location = 's'; + if (windowWidth > 768) { + var location = 'se'; + } + + // draw the chart and return the chart object + chart.draw(dataTable, { + seriesDefaults: { + rendererOptions: { + showDataLabels: true + } + }, + highlighter: { + tooltipLocation: 'se', + sizeAdjust: 0, + tooltipAxes: 'pieref', + formatString: '%s, %.9Ps' + }, + legend: { + show: true, + location: location, + rendererOptions: { + numberColumns: 2 + } + }, + // from http://tango.freedesktop.org/Tango_Icon_Theme_Guidelines#Color_Palette + seriesColors: [ + '#fce94f', + '#fcaf3e', + '#e9b96e', + '#8ae234', + '#729fcf', + '#ad7fa8', + '#ef2929', + '#888a85', + '#c4a000', + '#ce5c00', + '#8f5902', + '#4e9a06', + '#204a87', + '#5c3566', + '#a40000', + '#babdb6', + '#2e3436' + ] + }); + return chart; +} + +/** + * Formats a profiling duration nicely (in us and ms time). + * Used in server_status_monitor.js + * + * @param integer Number to be formatted, should be in the range of microsecond to second + * @param integer Accuracy, how many numbers right to the comma should be + * @return string The formatted number + */ +function PMA_prettyProfilingNum (num, acc) { + if (!acc) { + acc = 2; + } + acc = Math.pow(10, acc); + if (num * 1000 < 0.1) { + num = Math.round(acc * (num * 1000 * 1000)) / acc + 'µ'; + } else if (num < 0.1) { + num = Math.round(acc * (num * 1000)) / acc + 'm'; + } else { + num = Math.round(acc * num) / acc; + } + + return num + 's'; +} + + +/** + * Formats a SQL Query nicely with newlines and indentation. Depends on Codemirror and MySQL Mode! + * + * @param string Query to be formatted + * @return string The formatted query + */ +function PMA_SQLPrettyPrint (string) { + if (typeof CodeMirror === 'undefined') { + return string; + } + + var mode = CodeMirror.getMode({}, 'text/x-mysql'); + var stream = new CodeMirror.StringStream(string); + var state = mode.startState(); + var token; + var tokens = []; + var output = ''; + var tabs = function (cnt) { + var ret = ''; + for (var i = 0; i < 4 * cnt; i++) { + ret += ' '; + } + return ret; + }; + + // "root-level" statements + var statements = { + 'select': ['select', 'from', 'on', 'where', 'having', 'limit', 'order by', 'group by'], + 'update': ['update', 'set', 'where'], + 'insert into': ['insert into', 'values'] + }; + // don't put spaces before these tokens + var spaceExceptionsBefore = { ';': true, ',': true, '.': true, '(': true }; + // don't put spaces after these tokens + var spaceExceptionsAfter = { '.': true }; + + // Populate tokens array + var str = ''; + while (! stream.eol()) { + stream.start = stream.pos; + token = mode.token(stream, state); + if (token !== null) { + tokens.push([token, stream.current().toLowerCase()]); + } + } + + var currentStatement = tokens[0][1]; + + if (! statements[currentStatement]) { + return string; + } + // Holds all currently opened code blocks (statement, function or generic) + var blockStack = []; + // Holds the type of block from last iteration (the current is in blockStack[0]) + var previousBlock; + // If a new code block is found, newBlock contains its type for one iteration and vice versa for endBlock + var newBlock; + var endBlock; + // How much to indent in the current line + var indentLevel = 0; + // Holds the "root-level" statements + var statementPart; + var lastStatementPart = statements[currentStatement][0]; + + blockStack.unshift('statement'); + + // Iterate through every token and format accordingly + for (var i = 0; i < tokens.length; i++) { + previousBlock = blockStack[0]; + + // New block => push to stack + if (tokens[i][1] === '(') { + if (i < tokens.length - 1 && tokens[i + 1][0] === 'statement-verb') { + blockStack.unshift(newBlock = 'statement'); + } else if (i > 0 && tokens[i - 1][0] === 'builtin') { + blockStack.unshift(newBlock = 'function'); + } else { + blockStack.unshift(newBlock = 'generic'); + } + } else { + newBlock = null; + } + + // Block end => pop from stack + if (tokens[i][1] === ')') { + endBlock = blockStack[0]; + blockStack.shift(); + } else { + endBlock = null; + } + + // A subquery is starting + if (i > 0 && newBlock === 'statement') { + indentLevel++; + output += '\n' + tabs(indentLevel) + tokens[i][1] + ' ' + tokens[i + 1][1].toUpperCase() + '\n' + tabs(indentLevel + 1); + currentStatement = tokens[i + 1][1]; + i++; + continue; + } + + // A subquery is ending + if (endBlock === 'statement' && indentLevel > 0) { + output += '\n' + tabs(indentLevel); + indentLevel--; + } + + // One less indentation for statement parts (from, where, order by, etc.) and a newline + statementPart = statements[currentStatement].indexOf(tokens[i][1]); + if (statementPart !== -1) { + if (i > 0) { + output += '\n'; + } + output += tabs(indentLevel) + tokens[i][1].toUpperCase(); + output += '\n' + tabs(indentLevel + 1); + lastStatementPart = tokens[i][1]; + // Normal indentation and spaces for everything else + } else { + if (! spaceExceptionsBefore[tokens[i][1]] && + ! (i > 0 && spaceExceptionsAfter[tokens[i - 1][1]]) && + output.charAt(output.length - 1) !== ' ') { + output += ' '; + } + if (tokens[i][0] === 'keyword') { + output += tokens[i][1].toUpperCase(); + } else { + output += tokens[i][1]; + } + } + + // split columns in select and 'update set' clauses, but only inside statements blocks + if ((lastStatementPart === 'select' || lastStatementPart === 'where' || lastStatementPart === 'set') && + tokens[i][1] === ',' && blockStack[0] === 'statement') { + output += '\n' + tabs(indentLevel + 1); + } + + // split conditions in where clauses, but only inside statements blocks + if (lastStatementPart === 'where' && + (tokens[i][1] === 'and' || tokens[i][1] === 'or' || tokens[i][1] === 'xor')) { + if (blockStack[0] === 'statement') { + output += '\n' + tabs(indentLevel + 1); + } + // Todo: Also split and or blocks in newlines & indentation++ + // if (blockStack[0] === 'generic') + // output += ... + } + } + return output; +} + +/** + * jQuery function that uses jQueryUI's dialogs to confirm with user. Does not + * return a jQuery object yet and hence cannot be chained + * + * @param string question + * @param string url URL to be passed to the callbackFn to make + * an Ajax call to + * @param function callbackFn callback to execute after user clicks on OK + * @param function openCallback optional callback to run when dialog is shown + */ + +jQuery.fn.PMA_confirm = function (question, url, callbackFn, openCallback) { + var confirmState = PMA_commonParams.get('confirm'); + if (! confirmState) { + // user does not want to confirm + if ($.isFunction(callbackFn)) { + callbackFn.call(this, url); + return true; + } + } + if (PMA_messages.strDoYouReally === '') { + return true; + } + + /** + * @var button_options Object that stores the options passed to jQueryUI + * dialog + */ + var button_options = [ + { + text: PMA_messages.strOK, + 'class': 'submitOK', + click: function () { + $(this).dialog('close'); + if ($.isFunction(callbackFn)) { + callbackFn.call(this, url); + } + } + }, + { + text: PMA_messages.strCancel, + 'class': 'submitCancel', + click: function () { + $(this).dialog('close'); + } + } + ]; + + $('
    ', { 'id': 'confirm_dialog', 'title': PMA_messages.strConfirm }) + .prepend(question) + .dialog({ + buttons: button_options, + close: function () { + $(this).remove(); + }, + open: openCallback, + modal: true + }); +}; + +/** + * jQuery function to sort a table's body after a new row has been appended to it. + * + * @param string text_selector string to select the sortKey's text + * + * @return jQuery Object for chaining purposes + */ +jQuery.fn.PMA_sort_table = function (text_selector) { + return this.each(function () { + /** + * @var table_body Object referring to the table's element + */ + var table_body = $(this); + /** + * @var rows Object referring to the collection of rows in {@link table_body} + */ + var rows = $(this).find('tr').get(); + + // get the text of the field that we will sort by + $.each(rows, function (index, row) { + row.sortKey = $.trim($(row).find(text_selector).text().toLowerCase()); + }); + + // get the sorted order + rows.sort(function (a, b) { + if (a.sortKey < b.sortKey) { + return -1; + } + if (a.sortKey > b.sortKey) { + return 1; + } + return 0; + }); + + // pull out each row from the table and then append it according to it's order + $.each(rows, function (index, row) { + $(table_body).append(row); + row.sortKey = null; + }); + }); +}; + +/** + * Unbind all event handlers before tearing down a page + */ +AJAX.registerTeardown('functions.js', function () { + $(document).off('submit', '#create_table_form_minimal.ajax'); + $(document).off('submit', 'form.create_table_form.ajax'); + $(document).off('click', 'form.create_table_form.ajax input[name=submit_num_fields]'); + $(document).off('keyup', 'form.create_table_form.ajax input'); + $(document).off('change', 'input[name=partition_count],input[name=subpartition_count],select[name=partition_by]'); +}); + +/** + * jQuery coding for 'Create Table'. Used on db_operations.php, + * db_structure.php and db_tracking.php (i.e., wherever + * PhpMyAdmin\Display\CreateTable is used) + * + * Attach Ajax Event handlers for Create Table + */ +AJAX.registerOnload('functions.js', function () { + /** + * Attach event handler for submission of create table form (save) + */ + $(document).on('submit', 'form.create_table_form.ajax', function (event) { + event.preventDefault(); + + /** + * @var the_form object referring to the create table form + */ + var $form = $(this); + + /* + * First validate the form; if there is a problem, avoid submitting it + * + * checkTableEditForm() needs a pure element and not a jQuery object, + * this is why we pass $form[0] as a parameter (the jQuery object + * is actually an array of DOM elements) + */ + + if (checkTableEditForm($form[0], $form.find('input[name=orig_num_fields]').val())) { + PMA_prepareForAjaxRequest($form); + if (PMA_checkReservedWordColumns($form)) { + PMA_ajaxShowMessage(PMA_messages.strProcessingRequest); + // User wants to submit the form + $.post($form.attr('action'), $form.serialize() + PMA_commonParams.get('arg_separator') + 'do_save_data=1', function (data) { + if (typeof data !== 'undefined' && data.success === true) { + $('#properties_message') + .removeClass('error') + .html(''); + PMA_ajaxShowMessage(data.message); + // Only if the create table dialog (distinct panel) exists + var $createTableDialog = $('#create_table_dialog'); + if ($createTableDialog.length > 0) { + $createTableDialog.dialog('close').remove(); + } + $('#tableslistcontainer').before(data.formatted_sql); + + /** + * @var tables_table Object referring to the element that holds the list of tables + */ + var tables_table = $('#tablesForm').find('tbody').not('#tbl_summary_row'); + // this is the first table created in this db + if (tables_table.length === 0) { + PMA_commonActions.refreshMain( + PMA_commonParams.get('opendb_url') + ); + } else { + /** + * @var curr_last_row Object referring to the last element in {@link tables_table} + */ + var curr_last_row = $(tables_table).find('tr:last'); + /** + * @var curr_last_row_index_string String containing the index of {@link curr_last_row} + */ + var curr_last_row_index_string = $(curr_last_row).find('input:checkbox').attr('id').match(/\d+/)[0]; + /** + * @var curr_last_row_index Index of {@link curr_last_row} + */ + var curr_last_row_index = parseFloat(curr_last_row_index_string); + /** + * @var new_last_row_index Index of the new row to be appended to {@link tables_table} + */ + var new_last_row_index = curr_last_row_index + 1; + /** + * @var new_last_row_id String containing the id of the row to be appended to {@link tables_table} + */ + var new_last_row_id = 'checkbox_tbl_' + new_last_row_index; + + data.new_table_string = data.new_table_string.replace(/checkbox_tbl_/, new_last_row_id); + // append to table + $(data.new_table_string) + .appendTo(tables_table); + + // Sort the table + $(tables_table).PMA_sort_table('th'); + + // Adjust summary row + PMA_adjustTotals(); + } + + // Refresh navigation as a new table has been added + PMA_reloadNavigation(); + // Redirect to table structure page on creation of new table + var argsep = PMA_commonParams.get('arg_separator'); + var params_12 = 'ajax_request=true' + argsep + 'ajax_page_request=true'; + if (! (history && history.pushState)) { + params_12 += PMA_MicroHistory.menus.getRequestParam(); + } + tblStruct_url = 'tbl_structure.php?server=' + data._params.server + + argsep + 'db=' + data._params.db + argsep + 'token=' + data._params.token + + argsep + 'goto=db_structure.php' + argsep + 'table=' + data._params.table + ''; + $.get(tblStruct_url, params_12, AJAX.responseHandler); + } else { + PMA_ajaxShowMessage( + '
    ' + data.error + '
    ', + false + ); + } + }); // end $.post() + } + } // end if (checkTableEditForm() ) + }); // end create table form (save) + + /** + * Submits the intermediate changes in the table creation form + * to refresh the UI accordingly + */ + function submitChangesInCreateTableForm (actionParam) { + /** + * @var the_form object referring to the create table form + */ + var $form = $('form.create_table_form.ajax'); + + var $msgbox = PMA_ajaxShowMessage(PMA_messages.strProcessingRequest); + PMA_prepareForAjaxRequest($form); + + // User wants to add more fields to the table + $.post($form.attr('action'), $form.serialize() + '&' + actionParam, function (data) { + if (typeof data !== 'undefined' && data.success) { + var $pageContent = $('#page_content'); + $pageContent.html(data.message); + PMA_highlightSQL($pageContent); + PMA_verifyColumnsProperties(); + PMA_hideShowConnection($('.create_table_form select[name=tbl_storage_engine]')); + PMA_ajaxRemoveMessage($msgbox); + } else { + PMA_ajaxShowMessage(data.error); + } + }); // end $.post() + } + + /** + * Attach event handler for create table form (add fields) + */ + $(document).on('click', 'form.create_table_form.ajax input[name=submit_num_fields]', function (event) { + event.preventDefault(); + submitChangesInCreateTableForm('submit_num_fields=1'); + }); // end create table form (add fields) + + $(document).on('keydown', 'form.create_table_form.ajax input[name=added_fields]', function (event) { + if (event.keyCode === 13) { + event.preventDefault(); + event.stopImmediatePropagation(); + $(this) + .closest('form') + .find('input[name=submit_num_fields]') + .click(); + } + }); + + /** + * Attach event handler to manage changes in number of partitions and subpartitions + */ + $(document).on('change', 'input[name=partition_count],input[name=subpartition_count],select[name=partition_by]', function (event) { + $this = $(this); + $form = $this.parents('form'); + if ($form.is('.create_table_form.ajax')) { + submitChangesInCreateTableForm('submit_partition_change=1'); + } else { + $form.submit(); + } + }); + + $(document).on('change', 'input[value=AUTO_INCREMENT]', function () { + if (this.checked) { + var col = /\d/.exec($(this).attr('name')); + col = col[0]; + var $selectFieldKey = $('select[name="field_key[' + col + ']"]'); + if ($selectFieldKey.val() === 'none_' + col) { + $selectFieldKey.val('primary_' + col).change(); + } + } + }); + $('body') + .off('click', 'input.preview_sql') + .on('click', 'input.preview_sql', function () { + var $form = $(this).closest('form'); + PMA_previewSQL($form); + }); +}); + + +/** + * Validates the password field in a form + * + * @see PMA_messages.strPasswordEmpty + * @see PMA_messages.strPasswordNotSame + * @param object $the_form The form to be validated + * @return bool + */ +function PMA_checkPassword ($the_form) { + // Did the user select 'no password'? + if ($the_form.find('#nopass_1').is(':checked')) { + return true; + } else { + var $pred = $the_form.find('#select_pred_password'); + if ($pred.length && ($pred.val() === 'none' || $pred.val() === 'keep')) { + return true; + } + } + + var $password = $the_form.find('input[name=pma_pw]'); + var $password_repeat = $the_form.find('input[name=pma_pw2]'); + var alert_msg = false; + + if ($password.val() === '') { + alert_msg = PMA_messages.strPasswordEmpty; + } else if ($password.val() !== $password_repeat.val()) { + alert_msg = PMA_messages.strPasswordNotSame; + } + + if (alert_msg) { + alert(alert_msg); + $password.val(''); + $password_repeat.val(''); + $password.focus(); + return false; + } + return true; +} + +/** + * Attach Ajax event handlers for 'Change Password' on index.php + */ +AJAX.registerOnload('functions.js', function () { + /* Handler for hostname type */ + $(document).on('change', '#select_pred_hostname', function () { + var hostname = $('#pma_hostname'); + if (this.value === 'any') { + hostname.val('%'); + } else if (this.value === 'localhost') { + hostname.val('localhost'); + } else if (this.value === 'thishost' && $(this).data('thishost')) { + hostname.val($(this).data('thishost')); + } else if (this.value === 'hosttable') { + hostname.val('').prop('required', false); + } else if (this.value === 'userdefined') { + hostname.focus().select().prop('required', true); + } + }); + + /* Handler for editing hostname */ + $(document).on('change', '#pma_hostname', function () { + $('#select_pred_hostname').val('userdefined'); + $('#pma_hostname').prop('required', true); + }); + + /* Handler for username type */ + $(document).on('change', '#select_pred_username', function () { + if (this.value === 'any') { + $('#pma_username').val('').prop('required', false); + $('#user_exists_warning').css('display', 'none'); + } else if (this.value === 'userdefined') { + $('#pma_username').focus().select().prop('required', true); + } + }); + + /* Handler for editing username */ + $(document).on('change', '#pma_username', function () { + $('#select_pred_username').val('userdefined'); + $('#pma_username').prop('required', true); + }); + + /* Handler for password type */ + $(document).on('change', '#select_pred_password', function () { + if (this.value === 'none') { + $('#text_pma_pw2').prop('required', false).val(''); + $('#text_pma_pw').prop('required', false).val(''); + } else if (this.value === 'userdefined') { + $('#text_pma_pw2').prop('required', true); + $('#text_pma_pw').prop('required', true).focus().select(); + } else { + $('#text_pma_pw2').prop('required', false); + $('#text_pma_pw').prop('required', false); + } + }); + + /* Handler for editing password */ + $(document).on('change', '#text_pma_pw,#text_pma_pw2', function () { + $('#select_pred_password').val('userdefined'); + $('#text_pma_pw2').prop('required', true); + $('#text_pma_pw').prop('required', true); + }); + + /** + * Unbind all event handlers before tearing down a page + */ + $(document).off('click', '#change_password_anchor.ajax'); + + /** + * Attach Ajax event handler on the change password anchor + */ + + $(document).on('click', '#change_password_anchor.ajax', function (event) { + event.preventDefault(); + + var $msgbox = PMA_ajaxShowMessage(); + + /** + * @var button_options Object containing options to be passed to jQueryUI's dialog + */ + var button_options = {}; + button_options[PMA_messages.strGo] = function () { + event.preventDefault(); + + /** + * @var $the_form Object referring to the change password form + */ + var $the_form = $('#change_password_form'); + + if (! PMA_checkPassword($the_form)) { + return false; + } + + /** + * @var this_value String containing the value of the submit button. + * Need to append this for the change password form on Server Privileges + * page to work + */ + var this_value = $(this).val(); + + var $msgbox = PMA_ajaxShowMessage(PMA_messages.strProcessingRequest); + $the_form.append(''); + + $.post($the_form.attr('action'), $the_form.serialize() + PMA_commonParams.get('arg_separator') + 'change_pw=' + this_value, function (data) { + if (typeof data === 'undefined' || data.success !== true) { + PMA_ajaxShowMessage(data.error, false); + return; + } + + var $pageContent = $('#page_content'); + $pageContent.prepend(data.message); + PMA_highlightSQL($pageContent); + $('#change_password_dialog').hide().remove(); + $('#edit_user_dialog').dialog('close').remove(); + PMA_ajaxRemoveMessage($msgbox); + }); // end $.post() + }; + + button_options[PMA_messages.strCancel] = function () { + $(this).dialog('close'); + }; + $.get($(this).attr('href'), { 'ajax_request': true }, function (data) { + if (typeof data === 'undefined' || !data.success) { + PMA_ajaxShowMessage(data.error, false); + return; + } + + if (data._scripts) { + AJAX.scriptHandler.load(data._scripts); + } + + $('
    ') + .dialog({ + title: PMA_messages.strChangePassword, + width: 600, + close: function (ev, ui) { + $(this).remove(); + }, + buttons: button_options, + modal: true + }) + .append(data.message); + // for this dialog, we remove the fieldset wrapping due to double headings + $('fieldset#fieldset_change_password') + .find('legend').remove().end() + .find('table.noclick').unwrap().addClass('some-margin') + .find('input#text_pma_pw').focus(); + $('#fieldset_change_password_footer').hide(); + PMA_ajaxRemoveMessage($msgbox); + displayPasswordGenerateButton(); + $('#change_password_form').on('submit', function (e) { + e.preventDefault(); + $(this) + .closest('.ui-dialog') + .find('.ui-dialog-buttonpane .ui-button') + .first() + .click(); + }); + }); // end $.get() + }); // end handler for change password anchor +}); // end $() for Change Password + +/** + * Unbind all event handlers before tearing down a page + */ +AJAX.registerTeardown('functions.js', function () { + $(document).off('change', 'select.column_type'); + $(document).off('change', 'select.default_type'); + $(document).off('change', 'select.virtuality'); + $(document).off('change', 'input.allow_null'); + $(document).off('change', '.create_table_form select[name=tbl_storage_engine]'); +}); +/** + * Toggle the hiding/showing of the "Open in ENUM/SET editor" message when + * the page loads and when the selected data type changes + */ +AJAX.registerOnload('functions.js', function () { + // is called here for normal page loads and also when opening + // the Create table dialog + PMA_verifyColumnsProperties(); + // + // needs on() to work also in the Create Table dialog + $(document).on('change', 'select.column_type', function () { + PMA_showNoticeForEnum($(this)); + }); + $(document).on('change', 'select.default_type', function () { + PMA_hideShowDefaultValue($(this)); + }); + $(document).on('change', 'select.virtuality', function () { + PMA_hideShowExpression($(this)); + }); + $(document).on('change', 'input.allow_null', function () { + PMA_validateDefaultValue($(this)); + }); + $(document).on('change', '.create_table_form select[name=tbl_storage_engine]', function () { + PMA_hideShowConnection($(this)); + }); +}); + +/** + * If the chosen storage engine is FEDERATED show connection field. Hide otherwise + * + * @param $engine_selector storage engine selector + */ +function PMA_hideShowConnection ($engine_selector) { + var $connection = $('.create_table_form input[name=connection]'); + var index = $connection.parent('td').index() + 1; + var $labelTh = $connection.parents('tr').prev('tr').children('th:nth-child(' + index + ')'); + if ($engine_selector.val() !== 'FEDERATED') { + $connection + .prop('disabled', true) + .parent('td').hide(); + $labelTh.hide(); + } else { + $connection + .prop('disabled', false) + .parent('td').show(); + $labelTh.show(); + } +} + +/** + * If the column does not allow NULL values, makes sure that default is not NULL + */ +function PMA_validateDefaultValue ($null_checkbox) { + if (! $null_checkbox.prop('checked')) { + var $default = $null_checkbox.closest('tr').find('.default_type'); + if ($default.val() === 'NULL') { + $default.val('NONE'); + } + } +} + +/** + * function to populate the input fields on picking a column from central list + * + * @param string input_id input id of the name field for the column to be populated + * @param integer offset of the selected column in central list of columns + */ +function autoPopulate (input_id, offset) { + var db = PMA_commonParams.get('db'); + var table = PMA_commonParams.get('table'); + input_id = input_id.substring(0, input_id.length - 1); + $('#' + input_id + '1').val(central_column_list[db + '_' + table][offset].col_name); + var col_type = central_column_list[db + '_' + table][offset].col_type.toUpperCase(); + $('#' + input_id + '2').val(col_type); + var $input3 = $('#' + input_id + '3'); + $input3.val(central_column_list[db + '_' + table][offset].col_length); + if (col_type === 'ENUM' || col_type === 'SET') { + $input3.next().show(); + } else { + $input3.next().hide(); + } + var col_default = central_column_list[db + '_' + table][offset].col_default.toUpperCase(); + var $input4 = $('#' + input_id + '4'); + if (col_default !== '' && col_default !== 'NULL' && col_default !== 'CURRENT_TIMESTAMP' && col_default !== 'CURRENT_TIMESTAMP()') { + $input4.val('USER_DEFINED'); + $input4.next().next().show(); + $input4.next().next().val(central_column_list[db + '_' + table][offset].col_default); + } else { + $input4.val(central_column_list[db + '_' + table][offset].col_default); + $input4.next().next().hide(); + } + $('#' + input_id + '5').val(central_column_list[db + '_' + table][offset].col_collation); + var $input6 = $('#' + input_id + '6'); + $input6.val(central_column_list[db + '_' + table][offset].col_attribute); + if (central_column_list[db + '_' + table][offset].col_extra === 'on update CURRENT_TIMESTAMP') { + $input6.val(central_column_list[db + '_' + table][offset].col_extra); + } + if (central_column_list[db + '_' + table][offset].col_extra.toUpperCase() === 'AUTO_INCREMENT') { + $('#' + input_id + '9').prop('checked',true).change(); + } else { + $('#' + input_id + '9').prop('checked',false); + } + if (central_column_list[db + '_' + table][offset].col_isNull !== '0') { + $('#' + input_id + '7').prop('checked',true); + } else { + $('#' + input_id + '7').prop('checked',false); + } +} + +/** + * Unbind all event handlers before tearing down a page + */ +AJAX.registerTeardown('functions.js', function () { + $(document).off('click', 'a.open_enum_editor'); + $(document).off('click', 'input.add_value'); + $(document).off('click', '#enum_editor td.drop'); + $(document).off('click', 'a.central_columns_dialog'); +}); +/** + * @var $enum_editor_dialog An object that points to the jQuery + * dialog of the ENUM/SET editor + */ +var $enum_editor_dialog = null; +/** + * Opens the ENUM/SET editor and controls its functions + */ +AJAX.registerOnload('functions.js', function () { + $(document).on('click', 'a.open_enum_editor', function () { + // Get the name of the column that is being edited + var colname = $(this).closest('tr').find('input:first').val(); + var title; + var i; + // And use it to make up a title for the page + if (colname.length < 1) { + title = PMA_messages.enum_newColumnVals; + } else { + title = PMA_messages.enum_columnVals.replace( + /%s/, + '"' + escapeHtml(decodeURIComponent(colname)) + '"' + ); + } + // Get the values as a string + var inputstring = $(this) + .closest('td') + .find('input') + .val(); + // Escape html entities + inputstring = $('
    ') + .text(inputstring) + .html(); + // Parse the values, escaping quotes and + // slashes on the fly, into an array + var values = []; + var in_string = false; + var curr; + var next; + var buffer = ''; + for (i = 0; i < inputstring.length; i++) { + curr = inputstring.charAt(i); + next = i === inputstring.length ? '' : inputstring.charAt(i + 1); + if (! in_string && curr === '\'') { + in_string = true; + } else if (in_string && curr === '\\' && next === '\\') { + buffer += '\'; + i++; + } else if (in_string && next === '\'' && (curr === '\'' || curr === '\\')) { + buffer += '''; + i++; + } else if (in_string && curr === '\'') { + in_string = false; + values.push(buffer); + buffer = ''; + } else if (in_string) { + buffer += curr; + } + } + if (buffer.length > 0) { + // The leftovers in the buffer are the last value (if any) + values.push(buffer); + } + var fields = ''; + // If there are no values, maybe the user is about to make a + // new list so we add a few for him/her to get started with. + if (values.length === 0) { + values.push('', '', '', ''); + } + // Add the parsed values to the editor + var drop_icon = PMA_getImage('b_drop'); + for (i = 0; i < values.length; i++) { + fields += '' + + '' + + '' + + drop_icon + + ''; + } + /** + * @var dialog HTML code for the ENUM/SET dialog + */ + var dialog = '
    ' + + '
    ' + + '' + title + '' + + '

    ' + PMA_getImage('s_notice') + + PMA_messages.enum_hint + '

    ' + + '' + fields + '
    ' + + '
    ' + + '
    ' + + '
    ' + + '
    ' + + '
    ' + + '
    ' + + '' + + '
    ' + + '
    '; + /** + * @var Defines functions to be called when the buttons in + * the buttonOptions jQuery dialog bar are pressed + */ + var buttonOptions = {}; + buttonOptions[PMA_messages.strGo] = function () { + // When the submit button is clicked, + // put the data back into the original form + var value_array = []; + $(this).find('.values input').each(function (index, elm) { + var val = elm.value.replace(/\\/g, '\\\\').replace(/'/g, '\'\''); + value_array.push('\'' + val + '\''); + }); + // get the Length/Values text field where this value belongs + var values_id = $(this).find('input[type=\'hidden\']').val(); + $('input#' + values_id).val(value_array.join(',')); + $(this).dialog('close'); + }; + buttonOptions[PMA_messages.strClose] = function () { + $(this).dialog('close'); + }; + // Show the dialog + var width = parseInt( + (parseInt($('html').css('font-size'), 10) / 13) * 340, + 10 + ); + if (! width) { + width = 340; + } + $enum_editor_dialog = $(dialog).dialog({ + minWidth: width, + maxHeight: 450, + modal: true, + title: PMA_messages.enum_editor, + buttons: buttonOptions, + open: function () { + // Focus the "Go" button after opening the dialog + $(this).closest('.ui-dialog').find('.ui-dialog-buttonpane button:first').focus(); + }, + close: function () { + $(this).remove(); + } + }); + // slider for choosing how many fields to add + $enum_editor_dialog.find('.slider').slider({ + animate: true, + range: 'min', + value: 1, + min: 1, + max: 9, + slide: function (event, ui) { + $(this).closest('table').find('input[type=submit]').val( + PMA_sprintf(PMA_messages.enum_addValue, ui.value) + ); + } + }); + // Focus the slider, otherwise it looks nearly transparent + $('a.ui-slider-handle').addClass('ui-state-focus'); + return false; + }); + + $(document).on('click', 'a.central_columns_dialog', function (e) { + var href = 'db_central_columns.php'; + var db = PMA_commonParams.get('db'); + var table = PMA_commonParams.get('table'); + var maxRows = $(this).data('maxrows'); + var pick = $(this).data('pick'); + if (pick !== false) { + pick = true; + } + var params = { + 'ajax_request' : true, + 'server' : PMA_commonParams.get('server'), + 'db' : PMA_commonParams.get('db'), + 'cur_table' : PMA_commonParams.get('table'), + 'getColumnList':true + }; + var colid = $(this).closest('td').find('input').attr('id'); + var fields = ''; + if (! (db + '_' + table in central_column_list)) { + central_column_list.push(db + '_' + table); + $.ajax({ + type: 'POST', + url: href, + data: params, + success: function (data) { + central_column_list[db + '_' + table] = JSON.parse(data.message); + }, + async:false + }); + } + var i = 0; + var list_size = central_column_list[db + '_' + table].length; + var min = (list_size <= maxRows) ? list_size : maxRows; + for (i = 0; i < min; i++) { + fields += '
    ' + + escapeHtml(central_column_list[db + '_' + table][i].col_name) + + '
    ' + central_column_list[db + '_' + table][i].col_type; + + if (central_column_list[db + '_' + table][i].col_attribute !== '') { + fields += '(' + escapeHtml(central_column_list[db + '_' + table][i].col_attribute) + ') '; + } + if (central_column_list[db + '_' + table][i].col_length !== '') { + fields += '(' + escapeHtml(central_column_list[db + '_' + table][i].col_length) + ') '; + } + fields += escapeHtml(central_column_list[db + '_' + table][i].col_extra) + '' + + '
    '; + if (pick) { + fields += ''; + } + fields += ''; + } + var result_pointer = i; + var search_in = ''; + if (fields === '') { + fields = PMA_sprintf(PMA_messages.strEmptyCentralList, '\'' + escapeHtml(db) + '\''); + search_in = ''; + } + var seeMore = ''; + if (list_size > maxRows) { + seeMore = '
    ' + + '' + PMA_messages.seeMore + '
    '; + } + var central_columns_dialog = '
    ' + + '
    ' + + search_in + + '' + fields + '
    ' + + '
    ' + + seeMore + + '
    '; + + var width = parseInt( + (parseInt($('html').css('font-size'), 10) / 13) * 500, + 10 + ); + if (! width) { + width = 500; + } + var buttonOptions = {}; + var $central_columns_dialog = $(central_columns_dialog).dialog({ + minWidth: width, + maxHeight: 450, + modal: true, + title: PMA_messages.pickColumnTitle, + buttons: buttonOptions, + open: function () { + $('#col_list').on('click', '.pick', function () { + $central_columns_dialog.remove(); + }); + $('.filter_rows').on('keyup', function () { + $.uiTableFilter($('#col_list'), $(this).val()); + }); + $('#seeMore').click(function () { + fields = ''; + min = (list_size <= maxRows + result_pointer) ? list_size : maxRows + result_pointer; + for (i = result_pointer; i < min; i++) { + fields += '
    ' + + central_column_list[db + '_' + table][i].col_name + + '
    ' + + central_column_list[db + '_' + table][i].col_type; + + if (central_column_list[db + '_' + table][i].col_attribute !== '') { + fields += '(' + central_column_list[db + '_' + table][i].col_attribute + ') '; + } + if (central_column_list[db + '_' + table][i].col_length !== '') { + fields += '(' + central_column_list[db + '_' + table][i].col_length + ') '; + } + fields += central_column_list[db + '_' + table][i].col_extra + '' + + '
    '; + if (pick) { + fields += ''; + } + fields += ''; + } + $('#col_list').append(fields); + result_pointer = i; + if (result_pointer === list_size) { + $('.tblFooters').hide(); + } + return false; + }); + $(this).closest('.ui-dialog').find('.ui-dialog-buttonpane button:first').focus(); + }, + close: function () { + $('#col_list').off('click', '.pick'); + $('.filter_rows').off('keyup'); + $(this).remove(); + } + }); + return false; + }); + + // $(document).on('click', 'a.show_central_list',function(e) { + + // }); + // When "add a new value" is clicked, append an empty text field + $(document).on('click', 'input.add_value', function (e) { + e.preventDefault(); + var num_new_rows = $enum_editor_dialog.find('div.slider').slider('value'); + while (num_new_rows--) { + $enum_editor_dialog.find('.values') + .append( + '' + + '' + + '' + + PMA_getImage('b_drop') + + '' + ) + .find('tr:last') + .show('fast'); + } + }); + + // Removes the specified row from the enum editor + $(document).on('click', '#enum_editor td.drop', function () { + $(this).closest('tr').hide('fast', function () { + $(this).remove(); + }); + }); +}); + +/** + * Ensures indexes names are valid according to their type and, for a primary + * key, lock index name to 'PRIMARY' + * @param string form_id Variable which parses the form name as + * the input + * @return boolean false if there is no index form, true else + */ +function checkIndexName (form_id) { + if ($('#' + form_id).length === 0) { + return false; + } + + // Gets the elements pointers + var $the_idx_name = $('#input_index_name'); + var $the_idx_choice = $('#select_index_choice'); + + // Index is a primary key + if ($the_idx_choice.find('option:selected').val() === 'PRIMARY') { + $the_idx_name.val('PRIMARY'); + $the_idx_name.prop('disabled', true); + } else { + if ($the_idx_name.val() === 'PRIMARY') { + $the_idx_name.val(''); + } + $the_idx_name.prop('disabled', false); + } + + return true; +} // end of the 'checkIndexName()' function + +AJAX.registerTeardown('functions.js', function () { + $(document).off('click', '#index_frm input[type=submit]'); +}); +AJAX.registerOnload('functions.js', function () { + /** + * Handler for adding more columns to an index in the editor + */ + $(document).on('click', '#index_frm input[type=submit]', function (event) { + event.preventDefault(); + var rows_to_add = $(this) + .closest('fieldset') + .find('.slider') + .slider('value'); + + var tempEmptyVal = function () { + $(this).val(''); + }; + + var tempSetFocus = function () { + if ($(this).find('option:selected').val() === '') { + return true; + } + $(this).closest('tr').find('input').focus(); + }; + + while (rows_to_add--) { + var $indexColumns = $('#index_columns'); + var $newrow = $indexColumns + .find('tbody > tr:first') + .clone() + .appendTo( + $indexColumns.find('tbody') + ); + $newrow.find(':input').each(tempEmptyVal); + // focus index size input on column picked + $newrow.find('select').change(tempSetFocus); + } + }); +}); + +function indexEditorDialog (url, title, callback_success, callback_failure) { + /* Remove the hidden dialogs if there are*/ + var $editIndexDialog = $('#edit_index_dialog'); + if ($editIndexDialog.length !== 0) { + $editIndexDialog.remove(); + } + var $div = $('
    '); + + /** + * @var button_options Object that stores the options + * passed to jQueryUI dialog + */ + var button_options = {}; + button_options[PMA_messages.strGo] = function () { + /** + * @var the_form object referring to the export form + */ + var $form = $('#index_frm'); + var $msgbox = PMA_ajaxShowMessage(PMA_messages.strProcessingRequest); + PMA_prepareForAjaxRequest($form); + // User wants to submit the form + $.post($form.attr('action'), $form.serialize() + PMA_commonParams.get('arg_separator') + 'do_save_data=1', function (data) { + var $sqlqueryresults = $('.sqlqueryresults'); + if ($sqlqueryresults.length !== 0) { + $sqlqueryresults.remove(); + } + if (typeof data !== 'undefined' && data.success === true) { + PMA_ajaxShowMessage(data.message); + PMA_highlightSQL($('.result_query')); + $('.result_query .notice').remove(); + /* Reload the field form*/ + $('#table_index').remove(); + $('
    ') + .append(data.index_table) + .find('#table_index') + .insertAfter('#index_header'); + var $editIndexDialog = $('#edit_index_dialog'); + if ($editIndexDialog.length > 0) { + $editIndexDialog.dialog('close'); + } + $('div.no_indexes_defined').hide(); + if (callback_success) { + callback_success(); + } + PMA_reloadNavigation(); + } else { + var $temp_div = $('
    ').append(data.error); + var $error; + if ($temp_div.find('.error code').length !== 0) { + $error = $temp_div.find('.error code').addClass('error'); + } else { + $error = $temp_div; + } + if (callback_failure) { + callback_failure(); + } + PMA_ajaxShowMessage($error, false); + } + }); // end $.post() + }; + button_options[PMA_messages.strPreviewSQL] = function () { + // Function for Previewing SQL + var $form = $('#index_frm'); + PMA_previewSQL($form); + }; + button_options[PMA_messages.strCancel] = function () { + $(this).dialog('close'); + }; + var $msgbox = PMA_ajaxShowMessage(); + $.post('tbl_indexes.php', url, function (data) { + if (typeof data !== 'undefined' && data.success === false) { + // in the case of an error, show the error message returned. + PMA_ajaxShowMessage(data.error, false); + } else { + PMA_ajaxRemoveMessage($msgbox); + // Show dialog if the request was successful + $div + .append(data.message) + .dialog({ + title: title, + width: 'auto', + open: PMA_verifyColumnsProperties, + modal: true, + buttons: button_options, + close: function () { + $(this).remove(); + } + }); + $div.find('.tblFooters').remove(); + showIndexEditDialog($div); + } + }); // end $.get() +} + +function showIndexEditDialog ($outer) { + checkIndexType(); + checkIndexName('index_frm'); + var $indexColumns = $('#index_columns'); + $indexColumns.find('td').each(function () { + $(this).css('width', $(this).width() + 'px'); + }); + $indexColumns.find('tbody').sortable({ + axis: 'y', + containment: $indexColumns.find('tbody'), + tolerance: 'pointer' + }); + PMA_showHints($outer); + PMA_init_slider(); + // Add a slider for selecting how many columns to add to the index + $outer.find('.slider').slider({ + animate: true, + value: 1, + min: 1, + max: 16, + slide: function (event, ui) { + $(this).closest('fieldset').find('input[type=submit]').val( + PMA_sprintf(PMA_messages.strAddToIndex, ui.value) + ); + } + }); + $('div.add_fields').removeClass('hide'); + // focus index size input on column picked + $outer.find('table#index_columns select').change(function () { + if ($(this).find('option:selected').val() === '') { + return true; + } + $(this).closest('tr').find('input').focus(); + }); + // Focus the slider, otherwise it looks nearly transparent + $('a.ui-slider-handle').addClass('ui-state-focus'); + // set focus on index name input, if empty + var input = $outer.find('input#input_index_name'); + if (! input.val()) { + input.focus(); + } +} + +/** + * Function to display tooltips that were + * generated on the PHP side by PhpMyAdmin\Util::showHint() + * + * @param object $div a div jquery object which specifies the + * domain for searching for tooltips. If we + * omit this parameter the function searches + * in the whole body + **/ +function PMA_showHints ($div) { + if ($div === undefined || ! $div instanceof jQuery || $div.length === 0) { + $div = $('body'); + } + $div.find('.pma_hint').each(function () { + PMA_tooltip( + $(this).children('img'), + 'img', + $(this).children('span').html() + ); + }); +} + +AJAX.registerOnload('functions.js', function () { + PMA_showHints(); +}); + +function PMA_mainMenuResizerCallback () { + // 5 px margin for jumping menu in Chrome + return $(document.body).width() - 5; +} +// This must be fired only once after the initial page load +$(function () { + // Initialise the menu resize plugin + $('#topmenu').menuResizer(PMA_mainMenuResizerCallback); + // register resize event + $(window).on('resize', function () { + $('#topmenu').menuResizer('resize'); + }); +}); + +/** + * Get the row number from the classlist (for example, row_1) + */ +function PMA_getRowNumber (classlist) { + return parseInt(classlist.split(/\s+row_/)[1], 10); +} + +/** + * Changes status of slider + */ +function PMA_set_status_label ($element) { + var text; + if ($element.css('display') === 'none') { + text = '+ '; + } else { + text = '- '; + } + $element.closest('.slide-wrapper').prev().find('span').text(text); +} + +/** + * var toggleButton This is a function that creates a toggle + * sliding button given a jQuery reference + * to the correct DOM element + */ +var toggleButton = function ($obj) { + // In rtl mode the toggle switch is flipped horizontally + // so we need to take that into account + var right; + if ($('span.text_direction', $obj).text() === 'ltr') { + right = 'right'; + } else { + right = 'left'; + } + /** + * var h Height of the button, used to scale the + * background image and position the layers + */ + var h = $obj.height(); + $('img', $obj).height(h); + $('table', $obj).css('bottom', h - 1); + /** + * var on Width of the "ON" part of the toggle switch + * var off Width of the "OFF" part of the toggle switch + */ + var on = $('td.toggleOn', $obj).width(); + var off = $('td.toggleOff', $obj).width(); + // Make the "ON" and "OFF" parts of the switch the same size + // + 2 pixels to avoid overflowed + $('td.toggleOn > div', $obj).width(Math.max(on, off) + 2); + $('td.toggleOff > div', $obj).width(Math.max(on, off) + 2); + /** + * var w Width of the central part of the switch + */ + var w = parseInt(($('img', $obj).height() / 16) * 22, 10); + // Resize the central part of the switch on the top + // layer to match the background + $('table td:nth-child(2) > div', $obj).width(w); + /** + * var imgw Width of the background image + * var tblw Width of the foreground layer + * var offset By how many pixels to move the background + * image, so that it matches the top layer + */ + var imgw = $('img', $obj).width(); + var tblw = $('table', $obj).width(); + var offset = parseInt(((imgw - tblw) / 2), 10); + // Move the background to match the layout of the top layer + $obj.find('img').css(right, offset); + /** + * var offw Outer width of the "ON" part of the toggle switch + * var btnw Outer width of the central part of the switch + */ + var offw = $('td.toggleOff', $obj).outerWidth(); + var btnw = $('table td:nth-child(2)', $obj).outerWidth(); + // Resize the main div so that exactly one side of + // the switch plus the central part fit into it. + $obj.width(offw + btnw + 2); + /** + * var move How many pixels to move the + * switch by when toggling + */ + var move = $('td.toggleOff', $obj).outerWidth(); + // If the switch is initialized to the + // OFF state we need to move it now. + if ($('div.container', $obj).hasClass('off')) { + if (right === 'right') { + $('div.container', $obj).animate({ 'left': '-=' + move + 'px' }, 0); + } else { + $('div.container', $obj).animate({ 'left': '+=' + move + 'px' }, 0); + } + } + // Attach an 'onclick' event to the switch + $('div.container', $obj).click(function () { + if ($(this).hasClass('isActive')) { + return false; + } else { + $(this).addClass('isActive'); + } + var $msg = PMA_ajaxShowMessage(); + var $container = $(this); + var callback = $('span.callback', this).text(); + var operator; + var url; + var removeClass; + var addClass; + // Perform the actual toggle + if ($(this).hasClass('on')) { + if (right === 'right') { + operator = '-='; + } else { + operator = '+='; + } + url = $(this).find('td.toggleOff > span').text(); + removeClass = 'on'; + addClass = 'off'; + } else { + if (right === 'right') { + operator = '+='; + } else { + operator = '-='; + } + url = $(this).find('td.toggleOn > span').text(); + removeClass = 'off'; + addClass = 'on'; + } + + var parts = url.split('?'); + $.post(parts[0], parts[1] + '&ajax_request=true', function (data) { + if (typeof data !== 'undefined' && data.success === true) { + PMA_ajaxRemoveMessage($msg); + $container + .removeClass(removeClass) + .addClass(addClass) + .animate({ 'left': operator + move + 'px' }, function () { + $container.removeClass('isActive'); + }); + eval(callback); + } else { + PMA_ajaxShowMessage(data.error, false); + $container.removeClass('isActive'); + } + }); + }); +}; + +/** + * Unbind all event handlers before tearing down a page + */ +AJAX.registerTeardown('functions.js', function () { + $('div.container').off('click'); +}); +/** + * Initialise all toggle buttons + */ +AJAX.registerOnload('functions.js', function () { + $('div.toggleAjax').each(function () { + var $button = $(this).show(); + $button.find('img').each(function () { + if (this.complete) { + toggleButton($button); + } else { + $(this).load(function () { + toggleButton($button); + }); + } + }); + }); +}); + +/** + * Unbind all event handlers before tearing down a page + */ +AJAX.registerTeardown('functions.js', function () { + $(document).off('change', 'select.pageselector'); + $('#update_recent_tables').off('ready'); + $('#sync_favorite_tables').off('ready'); +}); + +AJAX.registerOnload('functions.js', function () { + /** + * Autosubmit page selector + */ + $(document).on('change', 'select.pageselector', function (event) { + event.stopPropagation(); + // Check where to load the new content + if ($(this).closest('#pma_navigation').length === 0) { + // For the main page we don't need to do anything, + $(this).closest('form').submit(); + } else { + // but for the navigation we need to manually replace the content + PMA_navigationTreePagination($(this)); + } + }); + + /** + * Load version information asynchronously. + */ + if ($('li.jsversioncheck').length > 0) { + $.ajax({ + dataType: 'json', + url: 'version_check.php', + method: 'POST', + data: { + 'server': PMA_commonParams.get('server') + }, + success: PMA_current_version + }); + } + + if ($('#is_git_revision').length > 0) { + setTimeout(PMA_display_git_revision, 10); + } + + /** + * Slider effect. + */ + PMA_init_slider(); + + var $updateRecentTables = $('#update_recent_tables'); + if ($updateRecentTables.length) { + $.get( + $updateRecentTables.attr('href'), + { no_debug: true }, + function (data) { + if (typeof data !== 'undefined' && data.success === true) { + $('#pma_recent_list').html(data.list); + } + } + ); + } + + // Sync favorite tables from localStorage to pmadb. + if ($('#sync_favorite_tables').length) { + $.ajax({ + url: $('#sync_favorite_tables').attr('href'), + cache: false, + type: 'POST', + data: { + favorite_tables: (isStorageSupported('localStorage') && typeof window.localStorage.favorite_tables !== 'undefined') + ? window.localStorage.favorite_tables + : '', + server: PMA_commonParams.get('server'), + no_debug: true + }, + success: function (data) { + // Update localStorage. + if (isStorageSupported('localStorage')) { + window.localStorage.favorite_tables = data.favorite_tables; + } + $('#pma_favorite_list').html(data.list); + } + }); + } +}); // end of $() + +/** + * Submits the form placed in place of a link due to the excessive url length + * + * @param $link anchor + * @returns {Boolean} + */ +function submitFormLink ($link) { + if ($link.attr('href').indexOf('=') !== -1) { + var data = $link.attr('href').substr($link.attr('href').indexOf('#') + 1).split('=', 2); + $link.parents('form').append(''); + } + $link.parents('form').submit(); +} + +/** + * Initializes slider effect. + */ +function PMA_init_slider () { + $('div.pma_auto_slider').each(function () { + var $this = $(this); + if ($this.data('slider_init_done')) { + return; + } + var $wrapper = $('
    ', { 'class': 'slide-wrapper' }); + $wrapper.toggle($this.is(':visible')); + $('', { href: '#' + this.id, 'class': 'ajax' }) + .text($this.attr('title')) + .prepend($('')) + .insertBefore($this) + .click(function () { + var $wrapper = $this.closest('.slide-wrapper'); + var visible = $this.is(':visible'); + if (!visible) { + $wrapper.show(); + } + $this[visible ? 'hide' : 'show']('blind', function () { + $wrapper.toggle(!visible); + $wrapper.parent().toggleClass('print_ignore', visible); + PMA_set_status_label($this); + }); + return false; + }); + $this.wrap($wrapper); + $this.removeAttr('title'); + PMA_set_status_label($this); + $this.data('slider_init_done', 1); + }); +} + +/** + * Initializes slider effect. + */ +AJAX.registerOnload('functions.js', function () { + PMA_init_slider(); +}); + +/** + * Restores sliders to the state they were in before initialisation. + */ +AJAX.registerTeardown('functions.js', function () { + $('div.pma_auto_slider').each(function () { + var $this = $(this); + $this.removeData(); + $this.parent().replaceWith($this); + $this.parent().children('a').remove(); + }); +}); + +/** + * Creates a message inside an object with a sliding effect + * + * @param msg A string containing the text to display + * @param $obj a jQuery object containing the reference + * to the element where to put the message + * This is optional, if no element is + * provided, one will be created below the + * navigation links at the top of the page + * + * @return bool True on success, false on failure + */ +function PMA_slidingMessage (msg, $obj) { + if (msg === undefined || msg.length === 0) { + // Don't show an empty message + return false; + } + if ($obj === undefined || ! $obj instanceof jQuery || $obj.length === 0) { + // If the second argument was not supplied, + // we might have to create a new DOM node. + if ($('#PMA_slidingMessage').length === 0) { + $('#page_content').prepend( + '' + ); + } + $obj = $('#PMA_slidingMessage'); + } + if ($obj.has('div').length > 0) { + // If there already is a message inside the + // target object, we must get rid of it + $obj + .find('div') + .first() + .fadeOut(function () { + $obj + .children() + .remove(); + $obj + .append('
    ' + msg + '
    '); + // highlight any sql before taking height; + PMA_highlightSQL($obj); + $obj.find('div') + .first() + .hide(); + $obj + .animate({ + height: $obj.find('div').first().height() + }) + .find('div') + .first() + .fadeIn(); + }); + } else { + // Object does not already have a message + // inside it, so we simply slide it down + $obj.width('100%') + .html('
    ' + msg + '
    '); + // highlight any sql before taking height; + PMA_highlightSQL($obj); + var h = $obj + .find('div') + .first() + .hide() + .height(); + $obj + .find('div') + .first() + .css('height', 0) + .show() + .animate({ + height: h + }, function () { + // Set the height of the parent + // to the height of the child + $obj + .height( + $obj + .find('div') + .first() + .height() + ); + }); + } + return true; +} // end PMA_slidingMessage() + +/** + * Attach CodeMirror2 editor to SQL edit area. + */ +AJAX.registerOnload('functions.js', function () { + var $elm = $('#sqlquery'); + if ($elm.length > 0) { + if (typeof CodeMirror !== 'undefined') { + codemirror_editor = PMA_getSQLEditor($elm); + codemirror_editor.focus(); + codemirror_editor.on('blur', updateQueryParameters); + } else { + // without codemirror + $elm.focus().on('blur', updateQueryParameters); + } + } + PMA_highlightSQL($('body')); +}); +AJAX.registerTeardown('functions.js', function () { + if (codemirror_editor) { + $('#sqlquery').text(codemirror_editor.getValue()); + codemirror_editor.toTextArea(); + codemirror_editor = false; + } +}); +AJAX.registerOnload('functions.js', function () { + // initializes all lock-page elements lock-id and + // val-hash data property + $('#page_content form.lock-page textarea, ' + + '#page_content form.lock-page input[type="text"], ' + + '#page_content form.lock-page input[type="number"], ' + + '#page_content form.lock-page select').each(function (i) { + $(this).data('lock-id', i); + // val-hash is the hash of default value of the field + // so that it can be compared with new value hash + // to check whether field was modified or not. + $(this).data('val-hash', AJAX.hash($(this).val())); + }); + + // initializes lock-page elements (input types checkbox and radio buttons) + // lock-id and val-hash data property + $('#page_content form.lock-page input[type="checkbox"], ' + + '#page_content form.lock-page input[type="radio"]').each(function (i) { + $(this).data('lock-id', i); + $(this).data('val-hash', AJAX.hash($(this).is(':checked'))); + }); +}); + +/** + * jQuery plugin to correctly filter input fields by value, needed + * because some nasty values may break selector syntax + */ +(function ($) { + $.fn.filterByValue = function (value) { + return this.filter(function () { + return $(this).val() === value; + }); + }; +}(jQuery)); + +/** + * Return value of a cell in a table. + */ +function PMA_getCellValue (td) { + var $td = $(td); + if ($td.is('.null')) { + return ''; + } else if ((! $td.is('.to_be_saved') + || $td.is('.set')) + && $td.data('original_data') + ) { + return $td.data('original_data'); + } else { + return $td.text(); + } +} + +$(window).on('popstate', function (event, data) { + $('#printcss').attr('media','print'); + return true; +}); + +/** + * Unbind all event handlers before tearing down a page + */ +AJAX.registerTeardown('functions.js', function () { + $(document).off('click', 'a.themeselect'); + $(document).off('change', '.autosubmit'); + $('a.take_theme').off('click'); +}); + +AJAX.registerOnload('functions.js', function () { + /** + * Theme selector. + */ + $(document).on('click', 'a.themeselect', function (e) { + window.open( + e.target, + 'themes', + 'left=10,top=20,width=510,height=350,scrollbars=yes,status=yes,resizable=yes' + ); + return false; + }); + + /** + * Automatic form submission on change. + */ + $(document).on('change', '.autosubmit', function (e) { + $(this).closest('form').submit(); + }); + + /** + * Theme changer. + */ + $('a.take_theme').click(function (e) { + var what = this.name; + if (window.opener && window.opener.document.forms.setTheme.elements.set_theme) { + window.opener.document.forms.setTheme.elements.set_theme.value = what; + window.opener.document.forms.setTheme.submit(); + window.close(); + return false; + } + return true; + }); +}); + +/** + * Produce print preview + */ +function printPreview () { + $('#printcss').attr('media','all'); + createPrintAndBackButtons(); +} + +/** + * Create print and back buttons in preview page + */ +function createPrintAndBackButtons () { + var back_button = $('',{ + type: 'button', + value: PMA_messages.back, + id: 'back_button_print_view' + }); + back_button.click(removePrintAndBackButton); + back_button.appendTo('#page_content'); + var print_button = $('',{ + type: 'button', + value: PMA_messages.print, + id: 'print_button_print_view' + }); + print_button.click(printPage); + print_button.appendTo('#page_content'); +} + +/** + * Remove print and back buttons and revert to normal view + */ +function removePrintAndBackButton () { + $('#printcss').attr('media','print'); + $('#back_button_print_view').remove(); + $('#print_button_print_view').remove(); +} + +/** + * Print page + */ +function printPage () { + if (typeof(window.print) !== 'undefined') { + window.print(); + } +} + +/** + * Unbind all event handlers before tearing down a page + */ +AJAX.registerTeardown('functions.js', function () { + $('input#print').off('click'); + $(document).off('click', 'a.create_view.ajax'); + $(document).off('keydown', '#createViewDialog input, #createViewDialog select'); + $(document).off('change', '#fkc_checkbox'); +}); + +AJAX.registerOnload('functions.js', function () { + $('input#print').click(printPage); + $('.logout').click(function () { + var form = $( + '
    ' + + '' + + '
    ' + ); + $('body').append(form); + form.submit(); + return false; + }); + /** + * Ajaxification for the "Create View" action + */ + $(document).on('click', 'a.create_view.ajax', function (e) { + e.preventDefault(); + PMA_createViewDialog($(this)); + }); + /** + * Attach Ajax event handlers for input fields in the editor + * and used to submit the Ajax request when the ENTER key is pressed. + */ + if ($('#createViewDialog').length !== 0) { + $(document).on('keydown', '#createViewDialog input, #createViewDialog select', function (e) { + if (e.which === 13) { // 13 is the ENTER key + e.preventDefault(); + + // with preventing default, selection by ' + + '' + + ''; +} + +/** + * Initialize the visualization in the GIS data editor. + */ +function initGISEditorVisualization () { + // Loads either SVG or OSM visualization based on the choice + selectVisualization(); + // Adds necessary styles to the div that coontains the openStreetMap + styleOSM(); + // Loads the SVG element and make a reference to it + loadSVG(); + // Adds controllers for zooming and panning + addZoomPanControllers(); + zoomAndPan(); +} + +/** + * Loads JavaScript files and the GIS editor. + * + * @param value current value of the geometry field + * @param field field name + * @param type geometry type + * @param input_name name of the input field + * @param token token + */ +function loadJSAndGISEditor (value, field, type, input_name) { + var head = document.getElementsByTagName('head')[0]; + var script; + + // Loads a set of small JS file needed for the GIS editor + var smallScripts = ['js/vendor/jquery/jquery.svg.js', + 'js/vendor/jquery/jquery.mousewheel.js', + 'js/vendor/jquery/jquery.event.drag-2.2.js', + 'js/tbl_gis_visualization.js']; + + for (var i = 0; i < smallScripts.length; i++) { + script = document.createElement('script'); + script.type = 'text/javascript'; + script.src = smallScripts[i]; + head.appendChild(script); + } + + // OpenLayers.js is BIG and takes time. So asynchronous loading would not work. + // Load the JS and do a callback to load the content for the GIS Editor. + script = document.createElement('script'); + script.type = 'text/javascript'; + + script.onreadystatechange = function () { + if (this.readyState === 'complete') { + loadGISEditor(value, field, type, input_name); + } + }; + script.onload = function () { + loadGISEditor(value, field, type, input_name); + }; + script.onerror = function () { + loadGISEditor(value, field, type, input_name); + }; + + script.src = 'js/vendor/openlayers/OpenLayers.js'; + head.appendChild(script); + + gisEditorLoaded = true; +} + +/** + * Loads the GIS editor via AJAX + * + * @param value current value of the geometry field + * @param field field name + * @param type geometry type + * @param input_name name of the input field + */ +function loadGISEditor (value, field, type, input_name) { + var $gis_editor = $('#gis_editor'); + $.post('gis_data_editor.php', { + 'field' : field, + 'value' : value, + 'type' : type, + 'input_name' : input_name, + 'get_gis_editor' : true, + 'ajax_request': true, + 'server': PMA_commonParams.get('server') + }, function (data) { + if (typeof data !== 'undefined' && data.success === true) { + $gis_editor.html(data.gis_editor); + initGISEditorVisualization(); + prepareJSVersion(); + } else { + PMA_ajaxShowMessage(data.error, false); + } + }, 'json'); +} + +/** + * Opens up the dialog for the GIS data editor. + */ +function openGISEditor () { + // Center the popup + var windowWidth = document.documentElement.clientWidth; + var windowHeight = document.documentElement.clientHeight; + var popupWidth = windowWidth * 0.9; + var popupHeight = windowHeight * 0.9; + var popupOffsetTop = windowHeight / 2 - popupHeight / 2; + var popupOffsetLeft = windowWidth / 2 - popupWidth / 2; + + var $gis_editor = $('#gis_editor'); + var $backgrouond = $('#popup_background'); + + $gis_editor.css({ 'top': popupOffsetTop, 'left': popupOffsetLeft, 'width': popupWidth, 'height': popupHeight }); + $backgrouond.css({ 'opacity' : '0.7' }); + + $gis_editor.append( + '
    ' + + '' + + '
    ' + ); + + // Make it appear + $backgrouond.fadeIn('fast'); + $gis_editor.fadeIn('fast'); +} + +/** + * Prepare and insert the GIS data in Well Known Text format + * to the input field. + */ +function insertDataAndClose () { + var $form = $('form#gis_data_editor_form'); + var input_name = $form.find('input[name=\'input_name\']').val(); + + var argsep = PMA_commonParams.get('arg_separator'); + $.post('gis_data_editor.php', $form.serialize() + argsep + 'generate=true' + argsep + 'ajax_request=true', function (data) { + if (typeof data !== 'undefined' && data.success === true) { + $('input[name=\'' + input_name + '\']').val(data.result); + } else { + PMA_ajaxShowMessage(data.error, false); + } + }, 'json'); + closeGISEditor(); +} + +/** + * Unbind all event handlers before tearing down a page + */ +AJAX.registerTeardown('gis_data_editor.js', function () { + $(document).off('click', '#gis_editor input[name=\'gis_data[save]\']'); + $(document).off('submit', '#gis_editor'); + $(document).off('change', '#gis_editor input[type=\'text\']'); + $(document).off('change', '#gis_editor select.gis_type'); + $(document).off('click', '#gis_editor a.close_gis_editor, #gis_editor a.cancel_gis_editor'); + $(document).off('click', '#gis_editor a.addJs.addPoint'); + $(document).off('click', '#gis_editor a.addLine.addJs'); + $(document).off('click', '#gis_editor a.addJs.addPolygon'); + $(document).off('click', '#gis_editor a.addJs.addGeom'); +}); + +AJAX.registerOnload('gis_data_editor.js', function () { + /** + * Prepares and insert the GIS data to the input field on clicking 'copy'. + */ + $(document).on('click', '#gis_editor input[name=\'gis_data[save]\']', function (event) { + event.preventDefault(); + insertDataAndClose(); + }); + + /** + * Prepares and insert the GIS data to the input field on pressing 'enter'. + */ + $(document).on('submit', '#gis_editor', function (event) { + event.preventDefault(); + insertDataAndClose(); + }); + + /** + * Trigger asynchronous calls on data change and update the output. + */ + $(document).on('change', '#gis_editor input[type=\'text\']', function () { + var $form = $('form#gis_data_editor_form'); + var argsep = PMA_commonParams.get('arg_separator'); + $.post('gis_data_editor.php', $form.serialize() + argsep + 'generate=true' + argsep + 'ajax_request=true', function (data) { + if (typeof data !== 'undefined' && data.success === true) { + $('#gis_data_textarea').val(data.result); + $('#placeholder').empty().removeClass('hasSVG').html(data.visualization); + $('#openlayersmap').empty(); + /* TODO: the gis_data_editor should rather return JSON than JS code to eval */ + eval(data.openLayers); + initGISEditorVisualization(); + } else { + PMA_ajaxShowMessage(data.error, false); + } + }, 'json'); + }); + + /** + * Update the form on change of the GIS type. + */ + $(document).on('change', '#gis_editor select.gis_type', function (event) { + var $gis_editor = $('#gis_editor'); + var $form = $('form#gis_data_editor_form'); + + var argsep = PMA_commonParams.get('arg_separator'); + $.post('gis_data_editor.php', $form.serialize() + argsep + 'get_gis_editor=true' + argsep + 'ajax_request=true', function (data) { + if (typeof data !== 'undefined' && data.success === true) { + $gis_editor.html(data.gis_editor); + initGISEditorVisualization(); + prepareJSVersion(); + } else { + PMA_ajaxShowMessage(data.error, false); + } + }, 'json'); + }); + + /** + * Handles closing of the GIS data editor. + */ + $(document).on('click', '#gis_editor a.close_gis_editor, #gis_editor a.cancel_gis_editor', function () { + closeGISEditor(); + }); + + /** + * Handles adding data points + */ + $(document).on('click', '#gis_editor a.addJs.addPoint', function () { + var $a = $(this); + var name = $a.attr('name'); + // Eg. name = gis_data[0][MULTIPOINT][add_point] => prefix = gis_data[0][MULTIPOINT] + var prefix = name.substr(0, name.length - 11); + // Find the number of points + var $noOfPointsInput = $('input[name=\'' + prefix + '[no_of_points]' + '\']'); + var noOfPoints = parseInt($noOfPointsInput.val(), 10); + // Add the new data point + var html = addDataPoint(noOfPoints, prefix); + $a.before(html); + $noOfPointsInput.val(noOfPoints + 1); + }); + + /** + * Handles adding linestrings and inner rings + */ + $(document).on('click', '#gis_editor a.addLine.addJs', function () { + var $a = $(this); + var name = $a.attr('name'); + + // Eg. name = gis_data[0][MULTILINESTRING][add_line] => prefix = gis_data[0][MULTILINESTRING] + var prefix = name.substr(0, name.length - 10); + var type = prefix.slice(prefix.lastIndexOf('[') + 1, prefix.lastIndexOf(']')); + + // Find the number of lines + var $noOfLinesInput = $('input[name=\'' + prefix + '[no_of_lines]' + '\']'); + var noOfLines = parseInt($noOfLinesInput.val(), 10); + + // Add the new linesting of inner ring based on the type + var html = '
    '; + var noOfPoints; + if (type === 'MULTILINESTRING') { + html += PMA_messages.strLineString + ' ' + (noOfLines + 1) + ':'; + noOfPoints = 2; + } else { + html += PMA_messages.strInnerRing + ' ' + noOfLines + ':'; + noOfPoints = 4; + } + html += ''; + for (var i = 0; i < noOfPoints; i++) { + html += addDataPoint(i, (prefix + '[' + noOfLines + ']')); + } + html += '
    + ' + + PMA_messages.strAddPoint + '
    '; + + $a.before(html); + $noOfLinesInput.val(noOfLines + 1); + }); + + /** + * Handles adding polygons + */ + $(document).on('click', '#gis_editor a.addJs.addPolygon', function () { + var $a = $(this); + var name = $a.attr('name'); + // Eg. name = gis_data[0][MULTIPOLYGON][add_polygon] => prefix = gis_data[0][MULTIPOLYGON] + var prefix = name.substr(0, name.length - 13); + // Find the number of polygons + var $noOfPolygonsInput = $('input[name=\'' + prefix + '[no_of_polygons]' + '\']'); + var noOfPolygons = parseInt($noOfPolygonsInput.val(), 10); + + // Add the new polygon + var html = PMA_messages.strPolygon + ' ' + (noOfPolygons + 1) + ':
    '; + html += '' + + '
    ' + PMA_messages.strOuterRing + ':' + + ''; + for (var i = 0; i < 4; i++) { + html += addDataPoint(i, (prefix + '[' + noOfPolygons + '][0]')); + } + html += '+ ' + + PMA_messages.strAddPoint + '
    ' + + '+ ' + + PMA_messages.strAddInnerRing + '

    '; + + $a.before(html); + $noOfPolygonsInput.val(noOfPolygons + 1); + }); + + /** + * Handles adding geoms + */ + $(document).on('click', '#gis_editor a.addJs.addGeom', function () { + var $a = $(this); + var prefix = 'gis_data[GEOMETRYCOLLECTION]'; + // Find the number of geoms + var $noOfGeomsInput = $('input[name=\'' + prefix + '[geom_count]' + '\']'); + var noOfGeoms = parseInt($noOfGeomsInput.val(), 10); + + var html1 = PMA_messages.strGeometry + ' ' + (noOfGeoms + 1) + ':
    '; + var $geomType = $('select[name=\'gis_data[' + (noOfGeoms - 1) + '][gis_type]\']').clone(); + $geomType.attr('name', 'gis_data[' + noOfGeoms + '][gis_type]').val('POINT'); + var html2 = '
    ' + PMA_messages.strPoint + ' :' + + '' + + '' + + '' + + '' + + '

    '; + + $a.before(html1); + $geomType.insertBefore($a); + $a.before(html2); + $noOfGeomsInput.val(noOfGeoms + 1); + }); +}); diff --git a/php/apps/phpmyadmin49/html/js/import.js b/php/apps/phpmyadmin49/html/js/import.js new file mode 100644 index 00000000..7bd91a2e --- /dev/null +++ b/php/apps/phpmyadmin49/html/js/import.js @@ -0,0 +1,155 @@ +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * Functions used in the import tab + * + */ + + +/** + * Toggles the hiding and showing of each plugin's options + * according to the currently selected plugin from the dropdown list + */ +function changePluginOpts () { + $('#format_specific_opts').find('div.format_specific_options').each(function () { + $(this).hide(); + }); + var selected_plugin_name = $('#plugins').find('option:selected').val(); + $('#' + selected_plugin_name + '_options').fadeIn('slow'); + if (selected_plugin_name === 'csv') { + $('#import_notification').text(PMA_messages.strImportCSV); + } else { + $('#import_notification').text(''); + } +} + +/** + * Toggles the hiding and showing of each plugin's options and sets the selected value + * in the plugin dropdown list according to the format of the selected file + */ +function matchFile (fname) { + var fname_array = fname.toLowerCase().split('.'); + var len = fname_array.length; + if (len !== 0) { + var extension = fname_array[len - 1]; + if (extension === 'gz' || extension === 'bz2' || extension === 'zip') { + len--; + } + // Only toggle if the format of the file can be imported + if ($('select[name=\'format\'] option').filterByValue(fname_array[len - 1]).length === 1) { + $('select[name=\'format\'] option').filterByValue(fname_array[len - 1]).prop('selected', true); + changePluginOpts(); + } + } +} + +/** + * Unbind all event handlers before tearing down a page + */ +AJAX.registerTeardown('import.js', function () { + $('#plugins').off('change'); + $('#input_import_file').off('change'); + $('#select_local_import_file').off('change'); + $('#input_import_file').off('change').off('focus'); + $('#select_local_import_file').off('focus'); + $('#text_csv_enclosed').add('#text_csv_escaped').off('keyup'); +}); + +AJAX.registerOnload('import.js', function () { + // import_file_form validation. + $(document).on('submit', '#import_file_form', function (event) { + var radioLocalImport = $('#radio_local_import_file'); + var radioImport = $('#radio_import_file'); + var fileMsg = '
    ' + PMA_messages.strImportDialogMessage + '
    '; + + if (radioLocalImport.length !== 0) { + // remote upload. + + if (radioImport.is(':checked') && $('#input_import_file').val() === '') { + $('#input_import_file').focus(); + PMA_ajaxShowMessage(fileMsg, false); + return false; + } + + if (radioLocalImport.is(':checked')) { + if ($('#select_local_import_file').length === 0) { + PMA_ajaxShowMessage('
    ' + PMA_messages.strNoImportFile + '
    ', false); + return false; + } + + if ($('#select_local_import_file').val() === '') { + $('#select_local_import_file').focus(); + PMA_ajaxShowMessage(fileMsg, false); + return false; + } + } + } else { + // local upload. + if ($('#input_import_file').val() === '') { + $('#input_import_file').focus(); + PMA_ajaxShowMessage(fileMsg, false); + return false; + } + } + + // show progress bar. + $('#upload_form_status').css('display', 'inline'); + $('#upload_form_status_info').css('display', 'inline'); + }); + + // Initially display the options for the selected plugin + changePluginOpts(); + + // Whenever the selected plugin changes, change the options displayed + $('#plugins').change(function () { + changePluginOpts(); + }); + + $('#input_import_file').change(function () { + matchFile($(this).val()); + }); + + $('#select_local_import_file').change(function () { + matchFile($(this).val()); + }); + + /* + * When the "Browse the server" form is clicked or the "Select from the web server upload directory" + * form is clicked, the radio button beside it becomes selected and the other form becomes disabled. + */ + $('#input_import_file').on('focus change', function () { + $('#radio_import_file').prop('checked', true); + $('#radio_local_import_file').prop('checked', false); + }); + $('#select_local_import_file').focus(function () { + $('#radio_local_import_file').prop('checked', true); + $('#radio_import_file').prop('checked', false); + }); + + /** + * Set up the interface for Javascript-enabled browsers since the default is for + * Javascript-disabled browsers + */ + $('#scroll_to_options_msg').hide(); + $('#format_specific_opts').find('div.format_specific_options') + .css({ + 'border': 0, + 'margin': 0, + 'padding': 0 + }) + .find('h3') + .remove(); + // $("form[name=import] *").unwrap(); + + /** + * for input element text_csv_enclosed and text_csv_escaped allow just one character to enter. + * as mysql allows just one character for these fields, + * if first character is escape then allow two including escape character. + */ + $('#text_csv_enclosed').add('#text_csv_escaped').on('keyup', function () { + if ($(this).val().length === 2 && $(this).val().charAt(0) !== '\\') { + $(this).val($(this).val().substring(0, 1)); + return false; + } + return true; + }); +}); diff --git a/php/apps/phpmyadmin49/html/js/indexes.js b/php/apps/phpmyadmin49/html/js/indexes.js new file mode 100644 index 00000000..65baec1c --- /dev/null +++ b/php/apps/phpmyadmin49/html/js/indexes.js @@ -0,0 +1,761 @@ +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * @fileoverview function used for index manipulation pages + * @name Table Structure + * + * @requires jQuery + * @requires jQueryUI + * @required js/functions.js + */ + +/** + * Returns the array of indexes based on the index choice + * + * @param index_choice index choice + */ +function PMA_getIndexArray (index_choice) { + var source_array = null; + + switch (index_choice.toLowerCase()) { + case 'primary': + source_array = primary_indexes; + break; + case 'unique': + source_array = unique_indexes; + break; + case 'index': + source_array = indexes; + break; + case 'fulltext': + source_array = fulltext_indexes; + break; + case 'spatial': + source_array = spatial_indexes; + break; + default: + return null; + } + return source_array; +} + +/** + * Hides/shows the inputs and submits appropriately depending + * on whether the index type chosen is 'SPATIAL' or not. + */ +function checkIndexType () { + /** + * @var Object Dropdown to select the index choice. + */ + var $select_index_choice = $('#select_index_choice'); + /** + * @var Object Dropdown to select the index type. + */ + var $select_index_type = $('#select_index_type'); + /** + * @var Object Table header for the size column. + */ + var $size_header = $('#index_columns').find('thead tr th:nth-child(2)'); + /** + * @var Object Inputs to specify the columns for the index. + */ + var $column_inputs = $('select[name="index[columns][names][]"]'); + /** + * @var Object Inputs to specify sizes for columns of the index. + */ + var $size_inputs = $('input[name="index[columns][sub_parts][]"]'); + /** + * @var Object Footer containg the controllers to add more columns + */ + var $add_more = $('#index_frm').find('.add_more'); + + if ($select_index_choice.val() === 'SPATIAL') { + // Disable and hide the size column + $size_header.hide(); + $size_inputs.each(function () { + $(this) + .prop('disabled', true) + .parent('td').hide(); + }); + + // Disable and hide the columns of the index other than the first one + var initial = true; + $column_inputs.each(function () { + $column_input = $(this); + if (! initial) { + $column_input + .prop('disabled', true) + .parent('td').hide(); + } else { + initial = false; + } + }); + + // Hide controllers to add more columns + $add_more.hide(); + } else { + // Enable and show the size column + $size_header.show(); + $size_inputs.each(function () { + $(this) + .prop('disabled', false) + .parent('td').show(); + }); + + // Enable and show the columns of the index + $column_inputs.each(function () { + $(this) + .prop('disabled', false) + .parent('td').show(); + }); + + // Show controllers to add more columns + $add_more.show(); + } + + if ($select_index_choice.val() === 'SPATIAL' || + $select_index_choice.val() === 'FULLTEXT') { + $select_index_type.val('').prop('disabled', true); + } else { + $select_index_type.prop('disabled', false); + } +} + +/** + * Sets current index information into form parameters. + * + * @param array source_array Array containing index columns + * @param string index_choice Choice of index + * + * @return void + */ +function PMA_setIndexFormParameters (source_array, index_choice) { + if (index_choice === 'index') { + $('input[name="indexes"]').val(JSON.stringify(source_array)); + } else { + $('input[name="' + index_choice + '_indexes"]').val(JSON.stringify(source_array)); + } +} + +/** + * Removes a column from an Index. + * + * @param string col_index Index of column in form + * + * @return void + */ +function PMA_removeColumnFromIndex (col_index) { + // Get previous index details. + var previous_index = $('select[name="field_key[' + col_index + ']"]') + .attr('data-index'); + if (previous_index.length) { + previous_index = previous_index.split(','); + var source_array = PMA_getIndexArray(previous_index[0]); + if (source_array === null) { + return; + } + + // Remove column from index array. + var source_length = source_array[previous_index[1]].columns.length; + for (var i = 0; i < source_length; i++) { + if (source_array[previous_index[1]].columns[i].col_index === col_index) { + source_array[previous_index[1]].columns.splice(i, 1); + } + } + + // Remove index completely if no columns left. + if (source_array[previous_index[1]].columns.length === 0) { + source_array.splice(previous_index[1], 1); + } + + // Update current index details. + $('select[name="field_key[' + col_index + ']"]').attr('data-index', ''); + // Update form index parameters. + PMA_setIndexFormParameters(source_array, previous_index[0].toLowerCase()); + } +} + +/** + * Adds a column to an Index. + * + * @param array source_array Array holding corresponding indexes + * @param string array_index Index of an INDEX in array + * @param string index_choice Choice of Index + * @param string col_index Index of column on form + * + * @return void + */ +function PMA_addColumnToIndex (source_array, array_index, index_choice, col_index) { + if (col_index >= 0) { + // Remove column from other indexes (if any). + PMA_removeColumnFromIndex(col_index); + } + var index_name = $('input[name="index[Key_name]"]').val(); + var index_comment = $('input[name="index[Index_comment]"]').val(); + var key_block_size = $('input[name="index[Key_block_size]"]').val(); + var parser = $('input[name="index[Parser]"]').val(); + var index_type = $('select[name="index[Index_type]"]').val(); + var columns = []; + $('#index_columns').find('tbody').find('tr').each(function () { + // Get columns in particular order. + var col_index = $(this).find('select[name="index[columns][names][]"]').val(); + var size = $(this).find('input[name="index[columns][sub_parts][]"]').val(); + columns.push({ + 'col_index': col_index, + 'size': size + }); + }); + + // Update or create an index. + source_array[array_index] = { + 'Key_name': index_name, + 'Index_comment': index_comment, + 'Index_choice': index_choice.toUpperCase(), + 'Key_block_size': key_block_size, + 'Parser': parser, + 'Index_type': index_type, + 'columns': columns + }; + + // Display index name (or column list) + var displayName = index_name; + if (displayName === '') { + var columnNames = []; + $.each(columns, function () { + columnNames.push($('input[name="field_name[' + this.col_index + ']"]').val()); + }); + displayName = '[' + columnNames.join(', ') + ']'; + } + $.each(columns, function () { + var id = 'index_name_' + this.col_index + '_8'; + var $name = $('#' + id); + if ($name.length === 0) { + $name = $(''); + $name.insertAfter($('select[name="field_key[' + this.col_index + ']"]')); + } + var $text = $('').text(displayName); + $name.html($text); + }); + + if (col_index >= 0) { + // Update index details on form. + $('select[name="field_key[' + col_index + ']"]') + .attr('data-index', index_choice + ',' + array_index); + } + PMA_setIndexFormParameters(source_array, index_choice.toLowerCase()); +} + +/** + * Get choices list for a column to create a composite index with. + * + * @param string index_choice Choice of index + * @param array source_array Array hodling columns for particular index + * + * @return jQuery Object + */ +function PMA_getCompositeIndexList (source_array, col_index) { + // Remove any previous list. + if ($('#composite_index_list').length) { + $('#composite_index_list').remove(); + } + + // Html list. + var $composite_index_list = $( + '
      ' + + '
      ' + PMA_messages.strCompositeWith + '
      ' + + '
    ' + ); + + // Add each column to list available for composite index. + var source_length = source_array.length; + var already_present = false; + for (var i = 0; i < source_length; i++) { + var sub_array_len = source_array[i].columns.length; + var column_names = []; + for (var j = 0; j < sub_array_len; j++) { + column_names.push( + $('input[name="field_name[' + source_array[i].columns[j].col_index + ']"]').val() + ); + + if (col_index === source_array[i].columns[j].col_index) { + already_present = true; + } + } + + $composite_index_list.append( + '
  • ' + + '' + + '
  • ' + ); + } + + return $composite_index_list; +} + +/** + * Shows 'Add Index' dialog. + * + * @param array source_array Array holding particluar index + * @param string array_index Index of an INDEX in array + * @param array target_columns Columns for an INDEX + * @param string col_index Index of column on form + * @param object index Index detail object + * + * @return void + */ +function PMA_showAddIndexDialog (source_array, array_index, target_columns, col_index, index) { + // Prepare post-data. + var $table = $('input[name="table"]'); + var table = $table.length > 0 ? $table.val() : ''; + var post_data = { + server: PMA_commonParams.get('server'), + db: $('input[name="db"]').val(), + table: table, + ajax_request: 1, + create_edit_table: 1, + index: index + }; + + var columns = {}; + for (var i = 0; i < target_columns.length; i++) { + var column_name = $('input[name="field_name[' + target_columns[i] + ']"]').val(); + var column_type = $('select[name="field_type[' + target_columns[i] + ']"]').val().toLowerCase(); + columns[column_name] = [column_type, target_columns[i]]; + } + post_data.columns = JSON.stringify(columns); + + var button_options = {}; + button_options[PMA_messages.strGo] = function () { + var is_missing_value = false; + $('select[name="index[columns][names][]"]').each(function () { + if ($(this).val() === '') { + is_missing_value = true; + } + }); + + if (! is_missing_value) { + PMA_addColumnToIndex( + source_array, + array_index, + index.Index_choice, + col_index + ); + } else { + PMA_ajaxShowMessage( + '
    ' + PMA_messages.strMissingColumn + + '
    ', false + ); + + return false; + } + + $(this).dialog('close'); + }; + button_options[PMA_messages.strCancel] = function () { + if (col_index >= 0) { + // Handle state on 'Cancel'. + var $select_list = $('select[name="field_key[' + col_index + ']"]'); + if (! $select_list.attr('data-index').length) { + $select_list.find('option[value*="none"]').attr('selected', 'selected'); + } else { + var previous_index = $select_list.attr('data-index').split(','); + $select_list.find('option[value*="' + previous_index[0].toLowerCase() + '"]') + .attr('selected', 'selected'); + } + } + $(this).dialog('close'); + }; + var $msgbox = PMA_ajaxShowMessage(); + $.post('tbl_indexes.php', post_data, function (data) { + if (data.success === false) { + // in the case of an error, show the error message returned. + PMA_ajaxShowMessage(data.error, false); + } else { + PMA_ajaxRemoveMessage($msgbox); + // Show dialog if the request was successful + var $div = $('
    '); + $div + .append(data.message) + .dialog({ + title: PMA_messages.strAddIndex, + width: 450, + minHeight: 250, + open: function () { + checkIndexName('index_frm'); + PMA_showHints($div); + PMA_init_slider(); + $('#index_columns').find('td').each(function () { + $(this).css('width', $(this).width() + 'px'); + }); + $('#index_columns').find('tbody').sortable({ + axis: 'y', + containment: $('#index_columns').find('tbody'), + tolerance: 'pointer' + }); + // We dont need the slider at this moment. + $(this).find('fieldset.tblFooters').remove(); + }, + modal: true, + buttons: button_options, + close: function () { + $(this).remove(); + } + }); + } + }); +} + +/** + * Creates a advanced index type selection dialog. + * + * @param array source_array Array holding a particular type of indexes + * @param string index_choice Choice of index + * @param string col_index Index of new column on form + * + * @return void + */ +function PMA_indexTypeSelectionDialog (source_array, index_choice, col_index) { + var $single_column_radio = $('' + + ''); + var $composite_index_radio = $('' + + ''); + var $dialog_content = $('
    '); + $dialog_content.append('' + index_choice.toUpperCase() + ''); + + + // For UNIQUE/INDEX type, show choice for single-column and composite index. + $dialog_content.append($single_column_radio); + $dialog_content.append($composite_index_radio); + + var button_options = {}; + // 'OK' operation. + button_options[PMA_messages.strGo] = function () { + if ($('#single_column').is(':checked')) { + var index = { + 'Key_name': (index_choice === 'primary' ? 'PRIMARY' : ''), + 'Index_choice': index_choice.toUpperCase() + }; + PMA_showAddIndexDialog(source_array, (source_array.length), [col_index], col_index, index); + } + + if ($('#composite_index').is(':checked')) { + if ($('input[name="composite_with"]').length !== 0 && $('input[name="composite_with"]:checked').length === 0 + ) { + PMA_ajaxShowMessage( + '
    ' + + PMA_messages.strFormEmpty + + '
    ', + false + ); + return false; + } + + var array_index = $('input[name="composite_with"]:checked').val(); + var source_length = source_array[array_index].columns.length; + var target_columns = []; + for (var i = 0; i < source_length; i++) { + target_columns.push(source_array[array_index].columns[i].col_index); + } + target_columns.push(col_index); + + PMA_showAddIndexDialog(source_array, array_index, target_columns, col_index, + source_array[array_index]); + } + + $(this).remove(); + }; + button_options[PMA_messages.strCancel] = function () { + // Handle state on 'Cancel'. + var $select_list = $('select[name="field_key[' + col_index + ']"]'); + if (! $select_list.attr('data-index').length) { + $select_list.find('option[value*="none"]').attr('selected', 'selected'); + } else { + var previous_index = $select_list.attr('data-index').split(','); + $select_list.find('option[value*="' + previous_index[0].toLowerCase() + '"]') + .attr('selected', 'selected'); + } + $(this).remove(); + }; + var $dialog = $('
    ').append($dialog_content).dialog({ + minWidth: 525, + minHeight: 200, + modal: true, + title: PMA_messages.strAddIndex, + resizable: false, + buttons: button_options, + open: function () { + $('#composite_index').on('change', function () { + if ($(this).is(':checked')) { + $dialog_content.append(PMA_getCompositeIndexList(source_array, col_index)); + } + }); + $('#single_column').on('change', function () { + if ($(this).is(':checked')) { + if ($('#composite_index_list').length) { + $('#composite_index_list').remove(); + } + } + }); + }, + close: function () { + $('#composite_index').off('change'); + $('#single_column').off('change'); + $(this).remove(); + } + }); +} + +/** + * Unbind all event handlers before tearing down a page + */ +AJAX.registerTeardown('indexes.js', function () { + $(document).off('click', '#save_index_frm'); + $(document).off('click', '#preview_index_frm'); + $(document).off('change', '#select_index_choice'); + $(document).off('click', 'a.drop_primary_key_index_anchor.ajax'); + $(document).off('click', '#table_index tbody tr td.edit_index.ajax, #index_div .add_index.ajax'); + $(document).off('click', '#index_frm input[type=submit]'); + $('body').off('change', 'select[name*="field_key"]'); + $(document).off('click', '.show_index_dialog'); +}); + +/** + * @description

    Ajax scripts for table index page

    + * + * Actions ajaxified here: + *
      + *
    • Showing/hiding inputs depending on the index type chosen
    • + *
    • create/edit/drop indexes
    • + *
    + */ +AJAX.registerOnload('indexes.js', function () { + // Re-initialize variables. + primary_indexes = []; + unique_indexes = []; + indexes = []; + fulltext_indexes = []; + spatial_indexes = []; + + // for table creation form + var $engine_selector = $('.create_table_form select[name=tbl_storage_engine]'); + if ($engine_selector.length) { + PMA_hideShowConnection($engine_selector); + } + + var $form = $('#index_frm'); + if ($form.length > 0) { + showIndexEditDialog($form); + } + + $(document).on('click', '#save_index_frm', function (event) { + event.preventDefault(); + var $form = $('#index_frm'); + var argsep = PMA_commonParams.get('arg_separator'); + var submitData = $form.serialize() + argsep + 'do_save_data=1' + argsep + 'ajax_request=true' + argsep + 'ajax_page_request=true'; + var $msgbox = PMA_ajaxShowMessage(PMA_messages.strProcessingRequest); + AJAX.source = $form; + $.post($form.attr('action'), submitData, AJAX.responseHandler); + }); + + $(document).on('click', '#preview_index_frm', function (event) { + event.preventDefault(); + PMA_previewSQL($('#index_frm')); + }); + + $(document).on('change', '#select_index_choice', function (event) { + event.preventDefault(); + checkIndexType(); + checkIndexName('index_frm'); + }); + + /** + * Ajax Event handler for 'Drop Index' + */ + $(document).on('click', 'a.drop_primary_key_index_anchor.ajax', function (event) { + event.preventDefault(); + var $anchor = $(this); + /** + * @var $curr_row Object containing reference to the current field's row + */ + var $curr_row = $anchor.parents('tr'); + /** @var Number of columns in the key */ + var rows = $anchor.parents('td').attr('rowspan') || 1; + /** @var Rows that should be hidden */ + var $rows_to_hide = $curr_row; + for (var i = 1, $last_row = $curr_row.next(); i < rows; i++, $last_row = $last_row.next()) { + $rows_to_hide = $rows_to_hide.add($last_row); + } + + var question = escapeHtml( + $curr_row.children('td') + .children('.drop_primary_key_index_msg') + .val() + ); + + PMA_confirmPreviewSQL(question, $anchor.attr('href'), function (url) { + var $msg = PMA_ajaxShowMessage(PMA_messages.strDroppingPrimaryKeyIndex, false); + var params = getJSConfirmCommonParam(this, $anchor.getPostData()); + $.post(url, params, function (data) { + if (typeof data !== 'undefined' && data.success === true) { + PMA_ajaxRemoveMessage($msg); + var $table_ref = $rows_to_hide.closest('table'); + if ($rows_to_hide.length === $table_ref.find('tbody > tr').length) { + // We are about to remove all rows from the table + $table_ref.hide('medium', function () { + $('div.no_indexes_defined').show('medium'); + $rows_to_hide.remove(); + }); + $table_ref.siblings('div.notice').hide('medium'); + } else { + // We are removing some of the rows only + $rows_to_hide.hide('medium', function () { + $(this).remove(); + }); + } + if ($('.result_query').length) { + $('.result_query').remove(); + } + if (data.sql_query) { + $('
    ') + .html(data.sql_query) + .prependTo('#structure_content'); + PMA_highlightSQL($('#page_content')); + } + PMA_commonActions.refreshMain(false, function () { + $('a.ajax[href^=#indexes]').click(); + }); + PMA_reloadNavigation(); + } else { + PMA_ajaxShowMessage(PMA_messages.strErrorProcessingRequest + ' : ' + data.error, false); + } + }); // end $.post() + }); // end $.PMA_confirm() + }); // end Drop Primary Key/Index + + /** + *Ajax event handler for index edit + **/ + $(document).on('click', '#table_index tbody tr td.edit_index.ajax, #index_div .add_index.ajax', function (event) { + event.preventDefault(); + var url; + var title; + if ($(this).find('a').length === 0) { + // Add index + var valid = checkFormElementInRange( + $(this).closest('form')[0], + 'added_fields', + 'Column count has to be larger than zero.' + ); + if (! valid) { + return; + } + url = $(this).closest('form').serialize(); + title = PMA_messages.strAddIndex; + } else { + // Edit index + url = $(this).find('a').getPostData(); + title = PMA_messages.strEditIndex; + } + url += PMA_commonParams.get('arg_separator') + 'ajax_request=true'; + indexEditorDialog(url, title, function () { + // refresh the page using ajax + PMA_commonActions.refreshMain(false, function () { + $('a.ajax[href^=#indexes]').click(); + }); + }); + }); + + /** + * Ajax event handler for advanced index creation during table creation + * and column addition. + */ + $('body').on('change', 'select[name*="field_key"]', function () { + // Index of column on Table edit and create page. + var col_index = /\d+/.exec($(this).attr('name')); + col_index = col_index[0]; + // Choice of selected index. + var index_choice = /[a-z]+/.exec($(this).val()); + index_choice = index_choice[0]; + // Array containing corresponding indexes. + var source_array = null; + + if (index_choice === 'none') { + PMA_removeColumnFromIndex(col_index); + var id = 'index_name_' + '0' + '_8'; + var $name = $('#' + id); + if ($name.length === 0) { + $name = $(''); + $name.insertAfter($('select[name="field_key[' + '0' + ']"]')); + } + $name.html(''); + return false; + } + + // Select a source array. + source_array = PMA_getIndexArray(index_choice); + if (source_array === null) { + return; + } + + if (source_array.length === 0) { + var index = { + 'Key_name': (index_choice === 'primary' ? 'PRIMARY' : ''), + 'Index_choice': index_choice.toUpperCase() + }; + PMA_showAddIndexDialog(source_array, 0, [col_index], col_index, index); + } else { + if (index_choice === 'primary') { + var array_index = 0; + var source_length = source_array[array_index].columns.length; + var target_columns = []; + for (var i = 0; i < source_length; i++) { + target_columns.push(source_array[array_index].columns[i].col_index); + } + target_columns.push(col_index); + + PMA_showAddIndexDialog(source_array, array_index, target_columns, col_index, + source_array[array_index]); + } else { + // If there are multiple columns selected for an index, show advanced dialog. + PMA_indexTypeSelectionDialog(source_array, index_choice, col_index); + } + } + }); + + $(document).on('click', '.show_index_dialog', function (e) { + e.preventDefault(); + + // Get index details. + var previous_index = $(this).prev('select') + .attr('data-index') + .split(','); + + var index_choice = previous_index[0]; + var array_index = previous_index[1]; + + var source_array = PMA_getIndexArray(index_choice); + if (source_array !== null) { + var source_length = source_array[array_index].columns.length; + + var target_columns = []; + for (var i = 0; i < source_length; i++) { + target_columns.push(source_array[array_index].columns[i].col_index); + } + + PMA_showAddIndexDialog(source_array, array_index, target_columns, -1, source_array[array_index]); + } + }); + + $('#index_frm').on('submit', function () { + if (typeof(this.elements['index[Key_name]'].disabled) !== 'undefined') { + this.elements['index[Key_name]'].disabled = false; + } + }); +}); diff --git a/php/apps/phpmyadmin49/html/js/jqplot/plugins/jqplot.byteFormatter.js b/php/apps/phpmyadmin49/html/js/jqplot/plugins/jqplot.byteFormatter.js new file mode 100644 index 00000000..610692d3 --- /dev/null +++ b/php/apps/phpmyadmin49/html/js/jqplot/plugins/jqplot.byteFormatter.js @@ -0,0 +1,46 @@ +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * jqplot formatter for byte values + * + * @package phpMyAdmin + */ +(function ($) { + 'use strict'; + var formatByte = function (val, index) { + var units = [ + PMA_messages.strB, + PMA_messages.strKiB, + PMA_messages.strMiB, + PMA_messages.strGiB, + PMA_messages.strTiB, + PMA_messages.strPiB, + PMA_messages.strEiB + ]; + while (val >= 1024 && index <= 6) { + val /= 1024; + index++; + } + var format = '%.1f'; + if (Math.floor(val) === val) { + format = '%.0f'; + } + return $.jqplot.sprintf( + format + ' ' + units[index], val + ); + }; + /** + * The index indicates what unit the incoming data will be in. + * 0 for bytes, 1 for kilobytes and so on... + */ + $.jqplot.byteFormatter = function (index) { + index = index || 0; + return function (format, val) { + if (typeof val === 'number') { + val = parseFloat(val) || 0; + return formatByte(val, index); + } else { + return String(val); + } + }; + }; +}(jQuery)); diff --git a/php/apps/phpmyadmin49/html/js/keyhandler.js b/php/apps/phpmyadmin49/html/js/keyhandler.js new file mode 100644 index 00000000..a5e459fa --- /dev/null +++ b/php/apps/phpmyadmin49/html/js/keyhandler.js @@ -0,0 +1,140 @@ +/* vim: set expandtab sw=4 ts=4 sts=4: */ + +// global var that holds: 0- if ctrl key is not pressed 1- if ctrl key is pressed +var ctrlKeyHistory = 0; + +/** + * Allows moving around inputs/select by Ctrl+arrows + * + * @param object event data + */ +function onKeyDownArrowsHandler (e) { + e = e || window.event; + + var o = (e.srcElement || e.target); + if (!o) { + return; + } + if (o.tagName !== 'TEXTAREA' && o.tagName !== 'INPUT' && o.tagName !== 'SELECT') { + return; + } + if ((e.which !== 17) && (e.which !== 37) && (e.which !== 38) && (e.which !== 39) && (e.which !== 40)) { + return; + } + if (!o.id) { + return; + } + + if (e.type === 'keyup') { + if (e.which === 17) { + ctrlKeyHistory = 0; + } + return; + } else if (e.type === 'keydown') { + if (e.which === 17) { + ctrlKeyHistory = 1; + } + } + + if (ctrlKeyHistory !== 1) { + return; + } + + e.preventDefault(); + + var pos = o.id.split('_'); + if (pos[0] !== 'field' || typeof pos[2] === 'undefined') { + return; + } + + var x = pos[2]; + var y = pos[1]; + + switch (e.keyCode) { + case 38: + // up + y--; + break; + case 40: + // down + y++; + break; + case 37: + // left + x--; + break; + case 39: + // right + x++; + break; + default: + return; + } + + var is_firefox = navigator.userAgent.toLowerCase().indexOf('firefox/') > -1; + + var id = 'field_' + y + '_' + x; + + var nO = document.getElementById(id); + if (! nO) { + id = 'field_' + y + '_' + x + '_0'; + nO = document.getElementById(id); + } + + // skip non existent fields + if (! nO) { + return; + } + + // for firefox select tag + var lvalue = o.selectedIndex; + var nOvalue = nO.selectedIndex; + + nO.focus(); + + if (is_firefox) { + var ffcheck = 0; + var ffversion; + for (ffversion = 3 ; ffversion < 25 ; ffversion++) { + var is_firefox_v_24 = navigator.userAgent.toLowerCase().indexOf('firefox/' + ffversion) > -1; + if (is_firefox_v_24) { + ffcheck = 1; + break; + } + } + if (ffcheck === 1) { + if (e.which === 38 || e.which === 37) { + nOvalue++; + } else if (e.which === 40 || e.which === 39) { + nOvalue--; + } + nO.selectedIndex = nOvalue; + } else { + if (e.which === 38 || e.which === 37) { + lvalue++; + } else if (e.which === 40 || e.which === 39) { + lvalue--; + } + o.selectedIndex = lvalue; + } + } + + if (nO.tagName !== 'SELECT') { + nO.select(); + } + e.returnValue = false; +} + +AJAX.registerTeardown('keyhandler.js', function () { + $(document).off('keydown keyup', '#table_columns'); + $(document).off('keydown keyup', 'table.insertRowTable'); +}); + +AJAX.registerOnload('keyhandler.js', function () { + $(document).on('keydown keyup', '#table_columns', function (event) { + onKeyDownArrowsHandler(event.originalEvent); + }); + $(document).on('keydown keyup', 'table.insertRowTable', function (event) { + onKeyDownArrowsHandler(event.originalEvent); + }); +}); diff --git a/php/apps/phpmyadmin49/html/js/makegrid.js b/php/apps/phpmyadmin49/html/js/makegrid.js new file mode 100644 index 00000000..08157bcd --- /dev/null +++ b/php/apps/phpmyadmin49/html/js/makegrid.js @@ -0,0 +1,2301 @@ +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * Create advanced table (resize, reorder, and show/hide columns; and also grid editing). + * This function is designed mainly for table DOM generated from browsing a table in the database. + * For using this function in other table DOM, you may need to: + * - add "draggable" class in the table header , in order to make it resizable, sortable or hidable + * - have at least one non-"draggable" header in the table DOM for placing column visibility drop-down arrow + * - pass the value "false" for the parameter "enableGridEdit" + * - adjust other parameter value, to select which features that will be enabled + * + * @param t the table DOM element + * @param enableResize Optional, if false, column resizing feature will be disabled + * @param enableReorder Optional, if false, column reordering feature will be disabled + * @param enableVisib Optional, if false, show/hide column feature will be disabled + * @param enableGridEdit Optional, if false, grid editing feature will be disabled + */ +function PMA_makegrid (t, enableResize, enableReorder, enableVisib, enableGridEdit) { + var g = { + /** ********* + * Constant + ***********/ + minColWidth: 15, + + + /** ********* + * Variables, assigned with default value, changed later + ***********/ + actionSpan: 5, // number of colspan in Actions header in a table + tableCreateTime: null, // table creation time, used for saving column order and visibility to server, only available in "Browse tab" + + // Column reordering variables + colOrder: [], // array of column order + + // Column visibility variables + colVisib: [], // array of column visibility + showAllColText: '', // string, text for "show all" button under column visibility list + visibleHeadersCount: 0, // number of visible data headers + + // Table hint variables + reorderHint: '', // string, hint for column reordering + sortHint: '', // string, hint for column sorting + markHint: '', // string, hint for column marking + copyHint: '', // string, hint for copy column name + showReorderHint: false, + showSortHint: false, + showMarkHint: false, + + // Grid editing + isCellEditActive: false, // true if current focus is in edit cell + isEditCellTextEditable: false, // true if current edit cell is editable in the text input box (not textarea) + currentEditCell: null, // reference to that currently being edited + cellEditHint: '', // hint shown when doing grid edit + gotoLinkText: '', // "Go to link" text + wasEditedCellNull: false, // true if last value of the edited cell was NULL + maxTruncatedLen: 0, // number of characters that can be displayed in a cell + saveCellsAtOnce: false, // $cfg[saveCellsAtOnce] + isCellEdited: false, // true if at least one cell has been edited + saveCellWarning: '', // string, warning text when user want to leave a page with unsaved edited data + lastXHR : null, // last XHR object used in AJAX request + isSaving: false, // true when currently saving edited data, used to handle double posting caused by pressing ENTER in grid edit text box in Chrome browser + alertNonUnique: '', // string, alert shown when saving edited nonunique table + + // Common hidden inputs + token: null, + server: null, + db: null, + table: null, + + + /** ********** + * Functions + ************/ + + /** + * Start to resize column. Called when clicking on column separator. + * + * @param e event + * @param obj dragged div object + */ + dragStartRsz: function (e, obj) { + var n = $(g.cRsz).find('div').index(obj); // get the index of separator (i.e., column index) + $(obj).addClass('colborder_active'); + g.colRsz = { + x0: e.pageX, + n: n, + obj: obj, + objLeft: $(obj).position().left, + objWidth: $(g.t).find('th.draggable:visible:eq(' + n + ') span').outerWidth() + }; + $(document.body).css('cursor', 'col-resize').noSelect(); + if (g.isCellEditActive) { + g.hideEditCell(); + } + }, + + /** + * Start to reorder column. Called when clicking on table header. + * + * @param e event + * @param obj table header object + */ + dragStartReorder: function (e, obj) { + // prepare the cCpy (column copy) and cPointer (column pointer) from the dragged column + $(g.cCpy).text($(obj).text()); + var objPos = $(obj).position(); + $(g.cCpy).css({ + top: objPos.top + 20, + left: objPos.left, + height: $(obj).height(), + width: $(obj).width() + }); + $(g.cPointer).css({ + top: objPos.top + }); + + // get the column index, zero-based + var n = g.getHeaderIdx(obj); + + g.colReorder = { + x0: e.pageX, + y0: e.pageY, + n: n, + newn: n, + obj: obj, + objTop: objPos.top, + objLeft: objPos.left + }; + + $(document.body).css('cursor', 'move').noSelect(); + if (g.isCellEditActive) { + g.hideEditCell(); + } + }, + + /** + * Handle mousemove event when dragging. + * + * @param e event + */ + dragMove: function (e) { + if (g.colRsz) { + var dx = e.pageX - g.colRsz.x0; + if (g.colRsz.objWidth + dx > g.minColWidth) { + $(g.colRsz.obj).css('left', g.colRsz.objLeft + dx + 'px'); + } + } else if (g.colReorder) { + // dragged column animation + var dx = e.pageX - g.colReorder.x0; + $(g.cCpy) + .css('left', g.colReorder.objLeft + dx) + .show(); + + // pointer animation + var hoveredCol = g.getHoveredCol(e); + if (hoveredCol) { + var newn = g.getHeaderIdx(hoveredCol); + g.colReorder.newn = newn; + if (newn !== g.colReorder.n) { + // show the column pointer in the right place + var colPos = $(hoveredCol).position(); + var newleft = newn < g.colReorder.n ? + colPos.left : + colPos.left + $(hoveredCol).outerWidth(); + $(g.cPointer) + .css({ + left: newleft, + visibility: 'visible' + }); + } else { + // no movement to other column, hide the column pointer + $(g.cPointer).css('visibility', 'hidden'); + } + } + } + }, + + /** + * Stop the dragging action. + * + * @param e event + */ + dragEnd: function (e) { + if (g.colRsz) { + var dx = e.pageX - g.colRsz.x0; + var nw = g.colRsz.objWidth + dx; + if (nw < g.minColWidth) { + nw = g.minColWidth; + } + var n = g.colRsz.n; + // do the resizing + g.resize(n, nw); + + g.reposRsz(); + g.reposDrop(); + g.colRsz = false; + $(g.cRsz).find('div').removeClass('colborder_active'); + rearrangeStickyColumns($(t).prev('.sticky_columns'), $(t)); + } else if (g.colReorder) { + // shift columns + if (g.colReorder.newn !== g.colReorder.n) { + g.shiftCol(g.colReorder.n, g.colReorder.newn); + // assign new position + var objPos = $(g.colReorder.obj).position(); + g.colReorder.objTop = objPos.top; + g.colReorder.objLeft = objPos.left; + g.colReorder.n = g.colReorder.newn; + // send request to server to remember the column order + if (g.tableCreateTime) { + g.sendColPrefs(); + } + g.refreshRestoreButton(); + } + + // animate new column position + $(g.cCpy).stop(true, true) + .animate({ + top: g.colReorder.objTop, + left: g.colReorder.objLeft + }, 'fast') + .fadeOut(); + $(g.cPointer).css('visibility', 'hidden'); + + g.colReorder = false; + rearrangeStickyColumns($(t).prev('.sticky_columns'), $(t)); + } + $(document.body).css('cursor', 'inherit').noSelect(false); + }, + + /** + * Resize column n to new width "nw" + * + * @param n zero-based column index + * @param nw new width of the column in pixel + */ + resize: function (n, nw) { + $(g.t).find('tr').each(function () { + $(this).find('th.draggable:visible:eq(' + n + ') span,' + + 'td:visible:eq(' + (g.actionSpan + n) + ') span') + .css('width', nw); + }); + }, + + /** + * Reposition column resize bars. + */ + reposRsz: function () { + $(g.cRsz).find('div').hide(); + var $firstRowCols = $(g.t).find('tr:first th.draggable:visible'); + var $resizeHandles = $(g.cRsz).find('div').removeClass('condition'); + $(g.t).find('table.pma_table').find('thead th:first').removeClass('before-condition'); + for (var n = 0, l = $firstRowCols.length; n < l; n++) { + var $col = $($firstRowCols[n]); + var colWidth; + if (navigator.userAgent.toLowerCase().indexOf('safari') !== -1) { + colWidth = $col.outerWidth(); + } else { + colWidth = $col.outerWidth(true); + } + $($resizeHandles[n]).css('left', $col.position().left + colWidth) + .show(); + if ($col.hasClass('condition')) { + $($resizeHandles[n]).addClass('condition'); + if (n > 0) { + $($resizeHandles[n - 1]).addClass('condition'); + } + } + } + if ($($resizeHandles[0]).hasClass('condition')) { + $(g.t).find('thead th:first').addClass('before-condition'); + } + $(g.cRsz).css('height', $(g.t).height()); + }, + + /** + * Shift column from index oldn to newn. + * + * @param oldn old zero-based column index + * @param newn new zero-based column index + */ + shiftCol: function (oldn, newn) { + $(g.t).find('tr').each(function () { + if (newn < oldn) { + $(this).find('th.draggable:eq(' + newn + '),' + + 'td:eq(' + (g.actionSpan + newn) + ')') + .before($(this).find('th.draggable:eq(' + oldn + '),' + + 'td:eq(' + (g.actionSpan + oldn) + ')')); + } else { + $(this).find('th.draggable:eq(' + newn + '),' + + 'td:eq(' + (g.actionSpan + newn) + ')') + .after($(this).find('th.draggable:eq(' + oldn + '),' + + 'td:eq(' + (g.actionSpan + oldn) + ')')); + } + }); + // reposition the column resize bars + g.reposRsz(); + + // adjust the column visibility list + if (newn < oldn) { + $(g.cList).find('.lDiv div:eq(' + newn + ')') + .before($(g.cList).find('.lDiv div:eq(' + oldn + ')')); + } else { + $(g.cList).find('.lDiv div:eq(' + newn + ')') + .after($(g.cList).find('.lDiv div:eq(' + oldn + ')')); + } + // adjust the colOrder + var tmp = g.colOrder[oldn]; + g.colOrder.splice(oldn, 1); + g.colOrder.splice(newn, 0, tmp); + // adjust the colVisib + if (g.colVisib.length > 0) { + tmp = g.colVisib[oldn]; + g.colVisib.splice(oldn, 1); + g.colVisib.splice(newn, 0, tmp); + } + }, + + /** + * Find currently hovered table column's header (excluding actions column). + * + * @param e event + * @return the hovered column's th object or undefined if no hovered column found. + */ + getHoveredCol: function (e) { + var hoveredCol; + $headers = $(g.t).find('th.draggable:visible'); + $headers.each(function () { + var left = $(this).offset().left; + var right = left + $(this).outerWidth(); + if (left <= e.pageX && e.pageX <= right) { + hoveredCol = this; + } + }); + return hoveredCol; + }, + + /** + * Get a zero-based index from a tag in a table. + * + * @param obj table header object + * @return zero-based index of the specified table header in the set of table headers (visible or not) + */ + getHeaderIdx: function (obj) { + return $(obj).parents('tr').find('th.draggable').index(obj); + }, + + /** + * Reposition the columns back to normal order. + */ + restoreColOrder: function () { + // use insertion sort, since we already have shiftCol function + for (var i = 1; i < g.colOrder.length; i++) { + var x = g.colOrder[i]; + var j = i - 1; + while (j >= 0 && x < g.colOrder[j]) { + j--; + } + if (j !== i - 1) { + g.shiftCol(i, j + 1); + } + } + if (g.tableCreateTime) { + // send request to server to remember the column order + g.sendColPrefs(); + } + g.refreshRestoreButton(); + }, + + /** + * Send column preferences (column order and visibility) to the server. + */ + sendColPrefs: function () { + if ($(g.t).is('.ajax')) { // only send preferences if ajax class + var post_params = { + ajax_request: true, + db: g.db, + table: g.table, + token: g.token, + server: g.server, + set_col_prefs: true, + table_create_time: g.tableCreateTime + }; + if (g.colOrder.length > 0) { + $.extend(post_params, { col_order: g.colOrder.toString() }); + } + if (g.colVisib.length > 0) { + $.extend(post_params, { col_visib: g.colVisib.toString() }); + } + $.post('sql.php', post_params, function (data) { + if (data.success !== true) { + var $temp_div = $(document.createElement('div')); + $temp_div.html(data.error); + $temp_div.addClass('error'); + PMA_ajaxShowMessage($temp_div, false); + } + }); + } + }, + + /** + * Refresh restore button state. + * Make restore button disabled if the table is similar with initial state. + */ + refreshRestoreButton: function () { + // check if table state is as initial state + var isInitial = true; + for (var i = 0; i < g.colOrder.length; i++) { + if (g.colOrder[i] !== i) { + isInitial = false; + break; + } + } + // check if only one visible column left + var isOneColumn = g.visibleHeadersCount === 1; + // enable or disable restore button + if (isInitial || isOneColumn) { + $(g.o).find('div.restore_column').hide(); + } else { + $(g.o).find('div.restore_column').show(); + } + }, + + /** + * Update current hint using the boolean values (showReorderHint, showSortHint, etc.). + * + */ + updateHint: function () { + var text = ''; + if (!g.colRsz && !g.colReorder) { // if not resizing or dragging + if (g.visibleHeadersCount > 1) { + g.showReorderHint = true; + } + if ($(t).find('th.marker').length > 0) { + g.showMarkHint = true; + } + if (g.showSortHint && g.sortHint) { + text += text.length > 0 ? '
    ' : ''; + text += '- ' + g.sortHint; + } + if (g.showMultiSortHint && g.strMultiSortHint) { + text += text.length > 0 ? '
    ' : ''; + text += '- ' + g.strMultiSortHint; + } + if (g.showMarkHint && + g.markHint && + ! g.showSortHint && // we do not show mark hint, when sort hint is shown + g.showReorderHint && + g.reorderHint + ) { + text += text.length > 0 ? '
    ' : ''; + text += '- ' + g.reorderHint; + text += text.length > 0 ? '
    ' : ''; + text += '- ' + g.markHint; + text += text.length > 0 ? '
    ' : ''; + text += '- ' + g.copyHint; + } + } + return text; + }, + + /** + * Toggle column's visibility. + * After calling this function and it returns true, afterToggleCol() must be called. + * + * @return boolean True if the column is toggled successfully. + */ + toggleCol: function (n) { + if (g.colVisib[n]) { + // can hide if more than one column is visible + if (g.visibleHeadersCount > 1) { + $(g.t).find('tr').each(function () { + $(this).find('th.draggable:eq(' + n + '),' + + 'td:eq(' + (g.actionSpan + n) + ')') + .hide(); + }); + g.colVisib[n] = 0; + $(g.cList).find('.lDiv div:eq(' + n + ') input').prop('checked', false); + } else { + // cannot hide, force the checkbox to stay checked + $(g.cList).find('.lDiv div:eq(' + n + ') input').prop('checked', true); + return false; + } + } else { // column n is not visible + $(g.t).find('tr').each(function () { + $(this).find('th.draggable:eq(' + n + '),' + + 'td:eq(' + (g.actionSpan + n) + ')') + .show(); + }); + g.colVisib[n] = 1; + $(g.cList).find('.lDiv div:eq(' + n + ') input').prop('checked', true); + } + return true; + }, + + /** + * This must be called if toggleCol() returns is true. + * + * This function is separated from toggleCol because, sometimes, we want to toggle + * some columns together at one time and do just one adjustment after it, e.g. in showAllColumns(). + */ + afterToggleCol: function () { + // some adjustments after hiding column + g.reposRsz(); + g.reposDrop(); + g.sendColPrefs(); + + // check visible first row headers count + g.visibleHeadersCount = $(g.t).find('tr:first th.draggable:visible').length; + g.refreshRestoreButton(); + }, + + /** + * Show columns' visibility list. + * + * @param obj The drop down arrow of column visibility list + */ + showColList: function (obj) { + // only show when not resizing or reordering + if (!g.colRsz && !g.colReorder) { + var pos = $(obj).position(); + // check if the list position is too right + if (pos.left + $(g.cList).outerWidth(true) > $(document).width()) { + pos.left = $(document).width() - $(g.cList).outerWidth(true); + } + $(g.cList).css({ + left: pos.left, + top: pos.top + $(obj).outerHeight(true) + }) + .show(); + $(obj).addClass('coldrop-hover'); + } + }, + + /** + * Hide columns' visibility list. + */ + hideColList: function () { + $(g.cList).hide(); + $(g.cDrop).find('.coldrop-hover').removeClass('coldrop-hover'); + }, + + /** + * Reposition the column visibility drop-down arrow. + */ + reposDrop: function () { + var $th = $(t).find('th:not(.draggable)'); + for (var i = 0; i < $th.length; i++) { + var $cd = $(g.cDrop).find('div:eq(' + i + ')'); // column drop-down arrow + var pos = $($th[i]).position(); + $cd.css({ + left: pos.left + $($th[i]).width() - $cd.width(), + top: pos.top + }); + } + }, + + /** + * Show all hidden columns. + */ + showAllColumns: function () { + for (var i = 0; i < g.colVisib.length; i++) { + if (!g.colVisib[i]) { + g.toggleCol(i); + } + } + g.afterToggleCol(); + }, + + /** + * Show edit cell, if it can be shown + * + * @param cell element to be edited + */ + showEditCell: function (cell) { + if ($(cell).is('.grid_edit') && + !g.colRsz && !g.colReorder) { + if (!g.isCellEditActive) { + var $cell = $(cell); + + if ('string' === $cell.attr('data-type') || + 'blob' === $cell.attr('data-type') || + 'json' === $cell.attr('data-type') + ) { + g.cEdit = g.cEditTextarea; + } else { + g.cEdit = g.cEditStd; + } + + // remove all edit area and hide it + $(g.cEdit).find('.edit_area').empty().hide(); + // reposition the cEdit element + $(g.cEdit).css({ + top: $cell.position().top, + left: $cell.position().left + }) + .show() + .find('.edit_box') + .css({ + width: $cell.outerWidth(), + height: $cell.outerHeight() + }); + // fill the cell edit with text from + var value = PMA_getCellValue(cell); + if ($cell.attr('data-type') === 'json' && $cell.is('.truncated') === false) { + try { + value = JSON.stringify(JSON.parse(value), null, 4); + } catch (e) { + // Show as is + } + } + $(g.cEdit).find('.edit_box').val(value); + + g.currentEditCell = cell; + $(g.cEdit).find('.edit_box').focus(); + moveCursorToEnd($(g.cEdit).find('.edit_box')); + $(g.cEdit).find('*').prop('disabled', false); + } + } + + function moveCursorToEnd (input) { + var originalValue = input.val(); + var originallength = originalValue.length; + input.val(''); + input.blur().focus().val(originalValue); + input[0].setSelectionRange(originallength, originallength); + } + }, + + /** + * Remove edit cell and the edit area, if it is shown. + * + * @param force Optional, force to hide edit cell without saving edited field. + * @param data Optional, data from the POST AJAX request to save the edited field + * or just specify "true", if we want to replace the edited field with the new value. + * @param field Optional, the edited . If not specified, the function will + * use currently edited from g.currentEditCell. + * @param field Optional, this object contains a boolean named move (true, if called from move* functions) + * and a to which the grid_edit should move + */ + hideEditCell: function (force, data, field, options) { + if (g.isCellEditActive && !force) { + // cell is being edited, save or post the edited data + if (options !== undefined) { + g.saveOrPostEditedCell(options); + } else { + g.saveOrPostEditedCell(); + } + return; + } + + // cancel any previous request + if (g.lastXHR !== null) { + g.lastXHR.abort(); + g.lastXHR = null; + } + + if (data) { + if (g.currentEditCell) { // save value of currently edited cell + // replace current edited field with the new value + var $this_field = $(g.currentEditCell); + var is_null = $this_field.data('value') === null; + if (is_null) { + $this_field.find('span').html('NULL'); + $this_field.addClass('null'); + } else { + $this_field.removeClass('null'); + var value = data.isNeedToRecheck + ? data.truncatableFieldValue + : $this_field.data('value'); + + // Truncates the text. + $this_field.removeClass('truncated'); + if (PMA_commonParams.get('pftext') === 'P' && value.length > g.maxTruncatedLen) { + $this_field.addClass('truncated'); + value = value.substring(0, g.maxTruncatedLen) + '...'; + } + + // Add
    before carriage return. + new_html = escapeHtml(value); + new_html = new_html.replace(/\n/g, '
    \n'); + + // remove decimal places if column type not supported + if (($this_field.attr('data-decimals') === 0) && ($this_field.attr('data-type').indexOf('time') !== -1)) { + new_html = new_html.substring(0, new_html.indexOf('.')); + } + + // remove addtional decimal places + if (($this_field.attr('data-decimals') > 0) && ($this_field.attr('data-type').indexOf('time') !== -1)) { + new_html = new_html.substring(0, new_html.length - (6 - $this_field.attr('data-decimals'))); + } + + var selector = 'span'; + if ($this_field.hasClass('hex') && $this_field.find('a').length) { + selector = 'a'; + } + + // Updates the code keeping highlighting (if any). + var $target = $this_field.find(selector); + if (!PMA_updateCode($target, new_html, value)) { + $target.html(new_html); + } + } + if ($this_field.is('.bit')) { + $this_field.find('span').text($this_field.data('value')); + } + } + if (data.transformations !== undefined) { + $.each(data.transformations, function (cell_index, value) { + var $this_field = $(g.t).find('.to_be_saved:eq(' + cell_index + ')'); + $this_field.find('span').html(value); + }); + } + if (data.relations !== undefined) { + $.each(data.relations, function (cell_index, value) { + var $this_field = $(g.t).find('.to_be_saved:eq(' + cell_index + ')'); + $this_field.find('span').html(value); + }); + } + + // refresh the grid + g.reposRsz(); + g.reposDrop(); + } + + // hide the cell editing area + $(g.cEdit).hide(); + $(g.cEdit).find('.edit_box').blur(); + g.isCellEditActive = false; + g.currentEditCell = null; + // destroy datepicker in edit area, if exist + var $dp = $(g.cEdit).find('.hasDatepicker'); + if ($dp.length > 0) { + $(document).bind('mousedown', $.datepicker._checkExternalClick); + $dp.datepicker('destroy'); + // change the cursor in edit box back to normal + // (the cursor become a hand pointer when we add datepicker) + $(g.cEdit).find('.edit_box').css('cursor', 'inherit'); + } + }, + + /** + * Show drop-down edit area when edit cell is focused. + */ + showEditArea: function () { + if (!g.isCellEditActive) { // make sure the edit area has not been shown + g.isCellEditActive = true; + g.isEditCellTextEditable = false; + /** + * @var $td current edited cell + */ + var $td = $(g.currentEditCell); + /** + * @var $editArea the editing area + */ + var $editArea = $(g.cEdit).find('.edit_area'); + /** + * @var where_clause WHERE clause for the edited cell + */ + var where_clause = $td.parent('tr').find('.where_clause').val(); + /** + * @var field_name String containing the name of this field. + * @see getFieldName() + */ + var field_name = getFieldName($(t), $td); + /** + * @var relation_curr_value String current value of the field (for fields that are foreign keyed). + */ + var relation_curr_value = $td.text(); + /** + * @var relation_key_or_display_column String relational key if in 'Relational display column' mode, + * relational display column if in 'Relational key' mode (for fields that are foreign keyed). + */ + var relation_key_or_display_column = $td.find('a').attr('title'); + /** + * @var curr_value String current value of the field (for fields that are of type enum or set). + */ + var curr_value = $td.find('span').text(); + + // empty all edit area, then rebuild it based on $td classes + $editArea.empty(); + + // remember this instead of testing more than once + var is_null = $td.is('.null'); + + // add goto link, if this cell contains a link + if ($td.find('a').length > 0) { + var gotoLink = document.createElement('div'); + gotoLink.className = 'goto_link'; + $(gotoLink).append(g.gotoLinkText + ' ').append($td.find('a').clone()); + $editArea.append(gotoLink); + } + + g.wasEditedCellNull = false; + if ($td.is(':not(.not_null)')) { + // append a null checkbox + $editArea.append('
    '); + + var $checkbox = $editArea.find('.null_div input'); + // check if current is NULL + if (is_null) { + $checkbox.prop('checked', true); + g.wasEditedCellNull = true; + } + + // if the select/editor is changed un-check the 'checkbox_null__'. + if ($td.is('.enum, .set')) { + $editArea.on('change', 'select', function () { + $checkbox.prop('checked', false); + }); + } else if ($td.is('.relation')) { + $editArea.on('change', 'select', function () { + $checkbox.prop('checked', false); + }); + $editArea.on('click', '.browse_foreign', function () { + $checkbox.prop('checked', false); + }); + } else { + $(g.cEdit).on('keypress change paste', '.edit_box', function () { + $checkbox.prop('checked', false); + }); + // Capture ctrl+v (on IE and Chrome) + $(g.cEdit).on('keydown', '.edit_box', function (e) { + if (e.ctrlKey && e.which === 86) { + $checkbox.prop('checked', false); + } + }); + $editArea.on('keydown', 'textarea', function () { + $checkbox.prop('checked', false); + }); + } + // if some text is written in textbox automatically unmark the null checkbox and if it is emptied again mark the checkbox. + $(g.cEdit).find('.edit_box').on('input', function () { + if ($(g.cEdit).find('.edit_box').val() !== '') { + $checkbox.prop('checked', false); + } else { + $checkbox.prop('checked', true); + } + }); + // if null checkbox is clicked empty the corresponding select/editor. + $checkbox.click(function () { + if ($td.is('.enum')) { + $editArea.find('select').val(''); + } else if ($td.is('.set')) { + $editArea.find('select').find('option').each(function () { + var $option = $(this); + $option.prop('selected', false); + }); + } else if ($td.is('.relation')) { + // if the dropdown is there to select the foreign value + if ($editArea.find('select').length > 0) { + $editArea.find('select').val(''); + } + } else { + $editArea.find('textarea').val(''); + } + $(g.cEdit).find('.edit_box').val(''); + }); + } + + // reset the position of the edit_area div after closing datetime picker + $(g.cEdit).find('.edit_area').css({ 'top' :'0','position':'' }); + + if ($td.is('.relation')) { + // handle relations + $editArea.addClass('edit_area_loading'); + + // initialize the original data + $td.data('original_data', null); + + /** + * @var post_params Object containing parameters for the POST request + */ + var post_params = { + 'ajax_request' : true, + 'get_relational_values' : true, + 'server' : g.server, + 'db' : g.db, + 'table' : g.table, + 'column' : field_name, + 'curr_value' : relation_curr_value, + 'relation_key_or_display_column' : relation_key_or_display_column + }; + + g.lastXHR = $.post('sql.php', post_params, function (data) { + g.lastXHR = null; + $editArea.removeClass('edit_area_loading'); + if ($(data.dropdown).is('select')) { + // save original_data + var value = $(data.dropdown).val(); + $td.data('original_data', value); + // update the text input field, in case where the "Relational display column" is checked + $(g.cEdit).find('.edit_box').val(value); + } + + $editArea.append(data.dropdown); + $editArea.append('
    ' + g.cellEditHint + '
    '); + + // for 'Browse foreign values' options, + // hide the value next to 'Browse foreign values' link + $editArea.find('span.curr_value').hide(); + // handle update for new values selected from new window + $editArea.find('span.curr_value').change(function () { + $(g.cEdit).find('.edit_box').val($(this).text()); + }); + }); // end $.post() + + $editArea.show(); + $editArea.on('change', 'select', function () { + $(g.cEdit).find('.edit_box').val($(this).val()); + }); + g.isEditCellTextEditable = true; + } else if ($td.is('.enum')) { + // handle enum fields + $editArea.addClass('edit_area_loading'); + + /** + * @var post_params Object containing parameters for the POST request + */ + var post_params = { + 'ajax_request' : true, + 'get_enum_values' : true, + 'server' : g.server, + 'db' : g.db, + 'table' : g.table, + 'column' : field_name, + 'curr_value' : curr_value + }; + g.lastXHR = $.post('sql.php', post_params, function (data) { + g.lastXHR = null; + $editArea.removeClass('edit_area_loading'); + $editArea.append(data.dropdown); + $editArea.append('
    ' + g.cellEditHint + '
    '); + }); // end $.post() + + $editArea.show(); + $editArea.on('change', 'select', function () { + $(g.cEdit).find('.edit_box').val($(this).val()); + }); + } else if ($td.is('.set')) { + // handle set fields + $editArea.addClass('edit_area_loading'); + + /** + * @var post_params Object containing parameters for the POST request + */ + var post_params = { + 'ajax_request' : true, + 'get_set_values' : true, + 'server' : g.server, + 'db' : g.db, + 'table' : g.table, + 'column' : field_name, + 'curr_value' : curr_value + }; + + // if the data is truncated, get the full data + if ($td.is('.truncated')) { + post_params.get_full_values = true; + post_params.where_clause = where_clause; + } + + g.lastXHR = $.post('sql.php', post_params, function (data) { + g.lastXHR = null; + $editArea.removeClass('edit_area_loading'); + $editArea.append(data.select); + $td.data('original_data', $(data.select).val().join()); + $editArea.append('
    ' + g.cellEditHint + '
    '); + }); // end $.post() + + $editArea.show(); + $editArea.on('change', 'select', function () { + $(g.cEdit).find('.edit_box').val($(this).val()); + }); + } else if ($td.is('.truncated, .transformed')) { + if ($td.is('.to_be_saved')) { // cell has been edited + var value = $td.data('value'); + $(g.cEdit).find('.edit_box').val(value); + $editArea.append(''); + $editArea.find('textarea').val(value); + $editArea + .on('keyup', 'textarea', function () { + $(g.cEdit).find('.edit_box').val($(this).val()); + }); + $(g.cEdit).on('keyup', '.edit_box', function () { + $editArea.find('textarea').val($(this).val()); + }); + $editArea.append('
    ' + g.cellEditHint + '
    '); + } else { + // handle truncated/transformed values values + $editArea.addClass('edit_area_loading'); + + // initialize the original data + $td.data('original_data', null); + + /** + * @var sql_query String containing the SQL query used to retrieve value of truncated/transformed data + */ + var sql_query = 'SELECT `' + field_name + '` FROM `' + g.table + '` WHERE ' + where_clause; + + // Make the Ajax call and get the data, wrap it and insert it + g.lastXHR = $.post('sql.php', { + 'server' : g.server, + 'db' : g.db, + 'ajax_request' : true, + 'sql_query' : sql_query, + 'grid_edit' : true + }, function (data) { + g.lastXHR = null; + $editArea.removeClass('edit_area_loading'); + if (typeof data !== 'undefined' && data.success === true) { + if ($td.attr('data-type') === 'json') { + try { + data.value = JSON.stringify(JSON.parse(data.value), null, 4); + } catch (e) { + // Show as is + } + } + $td.data('original_data', data.value); + $(g.cEdit).find('.edit_box').val(data.value); + $editArea.append(''); + $editArea.find('textarea').val(data.value); + $editArea.on('keyup', 'textarea', function () { + $(g.cEdit).find('.edit_box').val($(this).val()); + }); + $(g.cEdit).on('keyup', '.edit_box', function () { + $editArea.find('textarea').val($(this).val()); + }); + $editArea.append('
    ' + g.cellEditHint + '
    '); + $editArea.show(); + } else { + PMA_ajaxShowMessage(data.error, false); + } + }); // end $.post() + } + g.isEditCellTextEditable = true; + } else if ($td.is('.timefield, .datefield, .datetimefield, .timestampfield')) { + var $input_field = $(g.cEdit).find('.edit_box'); + + // remember current datetime value in $input_field, if it is not null + var datetime_value = !is_null ? $input_field.val() : ''; + + var showMillisec = false; + var showMicrosec = false; + var timeFormat = 'HH:mm:ss'; + // check for decimal places of seconds + if (($td.attr('data-decimals') > 0) && ($td.attr('data-type').indexOf('time') !== -1)) { + if (datetime_value && datetime_value.indexOf('.') === false) { + datetime_value += '.'; + } + if ($td.attr('data-decimals') > 3) { + showMillisec = true; + showMicrosec = true; + timeFormat = 'HH:mm:ss.lc'; + + if (datetime_value) { + datetime_value += '000000'; + var datetime_value = datetime_value.substring(0, datetime_value.indexOf('.') + 7); + $input_field.val(datetime_value); + } + } else { + showMillisec = true; + timeFormat = 'HH:mm:ss.l'; + + if (datetime_value) { + datetime_value += '000'; + var datetime_value = datetime_value.substring(0, datetime_value.indexOf('.') + 4); + $input_field.val(datetime_value); + } + } + } + + // add datetime picker + PMA_addDatepicker($input_field, $td.attr('data-type'), { + showMillisec: showMillisec, + showMicrosec: showMicrosec, + timeFormat: timeFormat + }); + + $input_field.on('keyup', function (e) { + if (e.which === 13) { + // post on pressing "Enter" + e.preventDefault(); + e.stopPropagation(); + g.saveOrPostEditedCell(); + } else if (e.which === 27) { + } else { + toggleDatepickerIfInvalid($td, $input_field); + } + }); + + $input_field.datepicker('show'); + toggleDatepickerIfInvalid($td, $input_field); + + // unbind the mousedown event to prevent the problem of + // datepicker getting closed, needs to be checked for any + // change in names when updating + $(document).off('mousedown', $.datepicker._checkExternalClick); + + // move ui-datepicker-div inside cEdit div + var datepicker_div = $('#ui-datepicker-div'); + datepicker_div.css({ 'top': 0, 'left': 0, 'position': 'relative' }); + $(g.cEdit).append(datepicker_div); + + // cancel any click on the datepicker element + $editArea.find('> *').click(function (e) { + e.stopPropagation(); + }); + + g.isEditCellTextEditable = true; + } else { + g.isEditCellTextEditable = true; + // only append edit area hint if there is a null checkbox + if ($editArea.children().length > 0) { + $editArea.append('
    ' + g.cellEditHint + '
    '); + } + } + if ($editArea.children().length > 0) { + $editArea.show(); + } + } + }, + + /** + * Post the content of edited cell. + * + * @param field Optional, this object contains a boolean named move (true, if called from move* functions) + * and a to which the grid_edit should move + */ + postEditedCell: function (options) { + if (g.isSaving) { + return; + } + g.isSaving = true; + /** + * @var relation_fields Array containing the name/value pairs of relational fields + */ + var relation_fields = {}; + /** + * @var relational_display string 'K' if relational key, 'D' if relational display column + */ + var relational_display = $(g.o).find('input[name=relational_display]:checked').val(); + /** + * @var transform_fields Array containing the name/value pairs for transformed fields + */ + var transform_fields = {}; + /** + * @var transformation_fields Boolean, if there are any transformed fields in the edited cells + */ + var transformation_fields = false; + /** + * @var full_sql_query String containing the complete SQL query to update this table + */ + var full_sql_query = ''; + /** + * @var rel_fields_list String, url encoded representation of {@link relations_fields} + */ + var rel_fields_list = ''; + /** + * @var transform_fields_list String, url encoded representation of {@link transform_fields} + */ + var transform_fields_list = ''; + /** + * @var where_clause Array containing where clause for updated fields + */ + var full_where_clause = []; + /** + * @var is_unique Boolean, whether the rows in this table is unique or not + */ + var is_unique = $(g.t).find('td.edit_row_anchor').is('.nonunique') ? 0 : 1; + /** + * multi edit variables + */ + var me_fields_name = []; + var me_fields_type = []; + var me_fields = []; + var me_fields_null = []; + + // alert user if edited table is not unique + if (!is_unique) { + alert(g.alertNonUnique); + } + + // loop each edited row + $(g.t).find('td.to_be_saved').parents('tr').each(function () { + var $tr = $(this); + var where_clause = $tr.find('.where_clause').val(); + if (typeof where_clause === 'undefined') { + where_clause = ''; + } + full_where_clause.push(where_clause); + var condition_array = JSON.parse($tr.find('.condition_array').val()); + + /** + * multi edit variables, for current row + * @TODO array indices are still not correct, they should be md5 of field's name + */ + var fields_name = []; + var fields_type = []; + var fields = []; + var fields_null = []; + + // loop each edited cell in a row + $tr.find('.to_be_saved').each(function () { + /** + * @var $this_field Object referring to the td that is being edited + */ + var $this_field = $(this); + + /** + * @var field_name String containing the name of this field. + * @see getFieldName() + */ + var field_name = getFieldName($(g.t), $this_field); + + /** + * @var this_field_params Array temporary storage for the name/value of current field + */ + var this_field_params = {}; + + if ($this_field.is('.transformed')) { + transformation_fields = true; + } + this_field_params[field_name] = $this_field.data('value'); + + /** + * @var is_null String capturing whether 'checkbox_null__' is checked. + */ + var is_null = this_field_params[field_name] === null; + + fields_name.push(field_name); + + if (is_null) { + fields_null.push('on'); + fields.push(''); + } else { + if ($this_field.is('.bit')) { + fields_type.push('bit'); + } else if ($this_field.hasClass('hex')) { + fields_type.push('hex'); + } + fields_null.push(''); + // Convert \n to \r\n to be consistent with form submitted value. + // The internal browser representation has to be just \n + // while form submitted value \r\n, see specification: + // https://www.w3.org/TR/html5/forms.html#the-textarea-element + fields.push($this_field.data('value').replace(/\n/g, '\r\n')); + + var cell_index = $this_field.index('.to_be_saved'); + if ($this_field.is(':not(.relation, .enum, .set, .bit)')) { + if ($this_field.is('.transformed')) { + transform_fields[cell_index] = {}; + $.extend(transform_fields[cell_index], this_field_params); + } + } else if ($this_field.is('.relation')) { + relation_fields[cell_index] = {}; + $.extend(relation_fields[cell_index], this_field_params); + } + } + // check if edited field appears in WHERE clause + if (where_clause.indexOf(PMA_urlencode(field_name)) > -1) { + var field_str = '`' + g.table + '`.' + '`' + field_name + '`'; + for (var field in condition_array) { + if (field.indexOf(field_str) > -1) { + condition_array[field] = is_null ? 'IS NULL' : '= \'' + this_field_params[field_name].replace(/'/g, '\'\'') + '\''; + break; + } + } + } + }); // end of loop for every edited cells in a row + + // save new_clause + var new_clause = ''; + for (var field in condition_array) { + new_clause += field + ' ' + condition_array[field] + ' AND '; + } + new_clause = new_clause.substring(0, new_clause.length - 5); // remove the last AND + $tr.data('new_clause', new_clause); + // save condition_array + $tr.find('.condition_array').val(JSON.stringify(condition_array)); + + me_fields_name.push(fields_name); + me_fields_type.push(fields_type); + me_fields.push(fields); + me_fields_null.push(fields_null); + }); // end of loop for every edited rows + + rel_fields_list = $.param(relation_fields); + transform_fields_list = $.param(transform_fields); + + // Make the Ajax post after setting all parameters + /** + * @var post_params Object containing parameters for the POST request + */ + var post_params = { 'ajax_request' : true, + 'sql_query' : full_sql_query, + 'server' : g.server, + 'db' : g.db, + 'table' : g.table, + 'clause_is_unique' : is_unique, + 'where_clause' : full_where_clause, + 'fields[multi_edit]' : me_fields, + 'fields_name[multi_edit]' : me_fields_name, + 'fields_type[multi_edit]' : me_fields_type, + 'fields_null[multi_edit]' : me_fields_null, + 'rel_fields_list' : rel_fields_list, + 'do_transformations' : transformation_fields, + 'transform_fields_list' : transform_fields_list, + 'relational_display' : relational_display, + 'goto' : 'sql.php', + 'submit_type' : 'save' + }; + + if (!g.saveCellsAtOnce) { + $(g.cEdit).find('*').prop('disabled', true); + $(g.cEdit).find('.edit_box').addClass('edit_box_posting'); + } else { + $(g.o).find('div.save_edited').addClass('saving_edited_data') + .find('input').prop('disabled', true); // disable the save button + } + + $.ajax({ + type: 'POST', + url: 'tbl_replace.php', + data: post_params, + success: + function (data) { + g.isSaving = false; + if (!g.saveCellsAtOnce) { + $(g.cEdit).find('*').prop('disabled', false); + $(g.cEdit).find('.edit_box').removeClass('edit_box_posting'); + } else { + $(g.o).find('div.save_edited').removeClass('saving_edited_data') + .find('input').prop('disabled', false); // enable the save button back + } + if (typeof data !== 'undefined' && data.success === true) { + if (typeof options === 'undefined' || ! options.move) { + PMA_ajaxShowMessage(data.message); + } + + // update where_clause related data in each edited row + $(g.t).find('td.to_be_saved').parents('tr').each(function () { + var new_clause = $(this).data('new_clause'); + var $where_clause = $(this).find('.where_clause'); + var old_clause = $where_clause.val(); + var decoded_old_clause = old_clause; + var decoded_new_clause = new_clause; + + $where_clause.val(new_clause); + // update Edit, Copy, and Delete links also + $(this).find('a').each(function () { + $(this).attr('href', $(this).attr('href').replace(old_clause, new_clause)); + // update delete confirmation in Delete link + if ($(this).attr('href').indexOf('DELETE') > -1) { + $(this).removeAttr('onclick') + .off('click') + .on('click', function () { + return confirmLink(this, 'DELETE FROM `' + g.db + '`.`' + g.table + '` WHERE ' + + decoded_new_clause + (is_unique ? '' : ' LIMIT 1')); + }); + } + }); + // update the multi edit checkboxes + $(this).find('input[type=checkbox]').each(function () { + var $checkbox = $(this); + var checkbox_name = $checkbox.attr('name'); + var checkbox_value = $checkbox.val(); + + $checkbox.attr('name', checkbox_name.replace(old_clause, new_clause)); + $checkbox.val(checkbox_value.replace(decoded_old_clause, decoded_new_clause)); + }); + }); + // update the display of executed SQL query command + if (typeof data.sql_query !== 'undefined') { + // extract query box + var $result_query = $($.parseHTML(data.sql_query)); + var sqlOuter = $result_query.find('.sqlOuter').wrap('

    ').parent().html(); + var tools = $result_query.find('.tools').wrap('

    ').parent().html(); + // sqlOuter and tools will not be present if 'Show SQL queries' configuration is off + if (typeof sqlOuter !== 'undefined' && typeof tools !== 'undefined') { + $(g.o).find('.result_query:not(:last)').remove(); + var $existing_query = $(g.o).find('.result_query'); + // If two query box exists update query in second else add a second box + if ($existing_query.find('div.sqlOuter').length > 1) { + $existing_query.children(':nth-child(4)').remove(); + $existing_query.children(':nth-child(4)').remove(); + $existing_query.append(sqlOuter + tools); + } else { + $existing_query.append(sqlOuter + tools); + } + PMA_highlightSQL($existing_query); + } + } + // hide and/or update the successfully saved cells + g.hideEditCell(true, data); + + // remove the "Save edited cells" button + $(g.o).find('div.save_edited').hide(); + // update saved fields + $(g.t).find('.to_be_saved') + .removeClass('to_be_saved') + .data('value', null) + .data('original_data', null); + + g.isCellEdited = false; + } else { + PMA_ajaxShowMessage(data.error, false); + if (!g.saveCellsAtOnce) { + $(g.t).find('.to_be_saved') + .removeClass('to_be_saved'); + } + } + } + }).done(function () { + if (options !== undefined && options.move) { + g.showEditCell(options.cell); + } + }); // end $.ajax() + }, + + /** + * Save edited cell, so it can be posted later. + */ + saveEditedCell: function () { + /** + * @var $this_field Object referring to the td that is being edited + */ + var $this_field = $(g.currentEditCell); + var $test_element = ''; // to test the presence of a element + + var need_to_post = false; + + /** + * @var field_name String containing the name of this field. + * @see getFieldName() + */ + var field_name = getFieldName($(g.t), $this_field); + + /** + * @var this_field_params Array temporary storage for the name/value of current field + */ + var this_field_params = {}; + + /** + * @var is_null String capturing whether 'checkbox_null__' is checked. + */ + var is_null = $(g.cEdit).find('input:checkbox').is(':checked'); + + if ($(g.cEdit).find('.edit_area').is('.edit_area_loading')) { + // the edit area is still loading (retrieving cell data), no need to post + need_to_post = false; + } else if (is_null) { + if (!g.wasEditedCellNull) { + this_field_params[field_name] = null; + need_to_post = true; + } + } else { + if ($this_field.is('.bit')) { + this_field_params[field_name] = $(g.cEdit).find('.edit_box').val(); + } else if ($this_field.is('.set')) { + $test_element = $(g.cEdit).find('select'); + this_field_params[field_name] = $test_element.map(function () { + return $(this).val(); + }).get().join(','); + } else if ($this_field.is('.relation, .enum')) { + // for relation and enumeration, take the results from edit box value, + // because selected value from drop-down, new window or multiple + // selection list will always be updated to the edit box + this_field_params[field_name] = $(g.cEdit).find('.edit_box').val(); + } else if ($this_field.hasClass('hex')) { + if ($(g.cEdit).find('.edit_box').val().match(/^(0x)?[a-f0-9]*$/i) !== null) { + this_field_params[field_name] = $(g.cEdit).find('.edit_box').val(); + } else { + var hexError = '

    ' + PMA_messages.strEnterValidHex + '
    '; + PMA_ajaxShowMessage(hexError, false); + this_field_params[field_name] = PMA_getCellValue(g.currentEditCell); + } + } else { + this_field_params[field_name] = $(g.cEdit).find('.edit_box').val(); + } + if (g.wasEditedCellNull || this_field_params[field_name] !== PMA_getCellValue(g.currentEditCell)) { + need_to_post = true; + } + } + + if (need_to_post) { + $(g.currentEditCell).addClass('to_be_saved') + .data('value', this_field_params[field_name]); + if (g.saveCellsAtOnce) { + $(g.o).find('div.save_edited').show(); + } + g.isCellEdited = true; + } + + return need_to_post; + }, + + /** + * Save or post currently edited cell, depending on the "saveCellsAtOnce" configuration. + * + * @param field Optional, this object contains a boolean named move (true, if called from move* functions) + * and a to which the grid_edit should move + */ + saveOrPostEditedCell: function (options) { + var saved = g.saveEditedCell(); + // Check if $cfg['SaveCellsAtOnce'] is false + if (!g.saveCellsAtOnce) { + // Check if need_to_post is true + if (saved) { + // Check if this function called from 'move' functions + if (options !== undefined && options.move) { + g.postEditedCell(options); + } else { + g.postEditedCell(); + } + // need_to_post is false + } else { + // Check if this function called from 'move' functions + if (options !== undefined && options.move) { + g.hideEditCell(true); + g.showEditCell(options.cell); + // NOT called from 'move' functions + } else { + g.hideEditCell(true); + } + } + // $cfg['SaveCellsAtOnce'] is true + } else { + // If need_to_post + if (saved) { + // If this function called from 'move' functions + if (options !== undefined && options.move) { + g.hideEditCell(true, true, false, options); + g.showEditCell(options.cell); + // NOT called from 'move' functions + } else { + g.hideEditCell(true, true); + } + } else { + // If this function called from 'move' functions + if (options !== undefined && options.move) { + g.hideEditCell(true, false, false, options); + g.showEditCell(options.cell); + // NOT called from 'move' functions + } else { + g.hideEditCell(true); + } + } + } + }, + + /** + * Initialize column resize feature. + */ + initColResize: function () { + // create column resizer div + g.cRsz = document.createElement('div'); + g.cRsz.className = 'cRsz'; + + // get data columns in the first row of the table + var $firstRowCols = $(g.t).find('tr:first th.draggable'); + + // create column borders + $firstRowCols.each(function () { + var cb = document.createElement('div'); // column border + $(cb).addClass('colborder') + .mousedown(function (e) { + g.dragStartRsz(e, this); + }); + $(g.cRsz).append(cb); + }); + g.reposRsz(); + + // attach to global div + $(g.gDiv).prepend(g.cRsz); + }, + + /** + * Initialize column reordering feature. + */ + initColReorder: function () { + g.cCpy = document.createElement('div'); // column copy, to store copy of dragged column header + g.cPointer = document.createElement('div'); // column pointer, used when reordering column + + // adjust g.cCpy + g.cCpy.className = 'cCpy'; + $(g.cCpy).hide(); + + // adjust g.cPointer + g.cPointer.className = 'cPointer'; + $(g.cPointer).css('visibility', 'hidden'); // set visibility to hidden instead of calling hide() to force browsers to cache the image in cPointer class + + // assign column reordering hint + g.reorderHint = PMA_messages.strColOrderHint; + + // get data columns in the first row of the table + var $firstRowCols = $(g.t).find('tr:first th.draggable'); + + // initialize column order + $col_order = $(g.o).find('.col_order'); // check if column order is passed from PHP + if ($col_order.length > 0) { + g.colOrder = $col_order.val().split(','); + for (var i = 0; i < g.colOrder.length; i++) { + g.colOrder[i] = parseInt(g.colOrder[i], 10); + } + } else { + g.colOrder = []; + for (var i = 0; i < $firstRowCols.length; i++) { + g.colOrder.push(i); + } + } + + // register events + $(g.t).find('th.draggable') + .mousedown(function (e) { + $(g.o).addClass('turnOffSelect'); + if (g.visibleHeadersCount > 1) { + g.dragStartReorder(e, this); + } + }) + .mouseenter(function () { + if (g.visibleHeadersCount > 1) { + $(this).css('cursor', 'move'); + } else { + $(this).css('cursor', 'inherit'); + } + }) + .mouseleave(function () { + g.showReorderHint = false; + $(this).tooltip('option', { + content: g.updateHint() + }); + }) + .dblclick(function (e) { + e.preventDefault(); + $('
    ') + .prop('title', PMA_messages.strColNameCopyTitle) + .addClass('modal-copy') + .text(PMA_messages.strColNameCopyText) + .append( + $('') + .prop('readonly', true) + .val($(this).data('column')) + ) + .dialog({ + resizable: false, + modal: true + }) + .find('input').focus().select(); + }); + $(g.t).find('th.draggable a') + .dblclick(function (e) { + e.stopPropagation(); + }); + // restore column order when the restore button is clicked + $(g.o).find('div.restore_column').click(function () { + g.restoreColOrder(); + }); + + // attach to global div + $(g.gDiv).append(g.cPointer); + $(g.gDiv).append(g.cCpy); + + // prevent default "dragstart" event when dragging a link + $(g.t).find('th a').on('dragstart', function () { + return false; + }); + + // refresh the restore column button state + g.refreshRestoreButton(); + }, + + /** + * Initialize column visibility feature. + */ + initColVisib: function () { + g.cDrop = document.createElement('div'); // column drop-down arrows + g.cList = document.createElement('div'); // column visibility list + + // adjust g.cDrop + g.cDrop.className = 'cDrop'; + + // adjust g.cList + g.cList.className = 'cList'; + $(g.cList).hide(); + + // assign column visibility related hints + g.showAllColText = PMA_messages.strShowAllCol; + + // get data columns in the first row of the table + var $firstRowCols = $(g.t).find('tr:first th.draggable'); + + var i; + // initialize column visibility + var $col_visib = $(g.o).find('.col_visib'); // check if column visibility is passed from PHP + if ($col_visib.length > 0) { + g.colVisib = $col_visib.val().split(','); + for (i = 0; i < g.colVisib.length; i++) { + g.colVisib[i] = parseInt(g.colVisib[i], 10); + } + } else { + g.colVisib = []; + for (i = 0; i < $firstRowCols.length; i++) { + g.colVisib.push(1); + } + } + + // make sure we have more than one column + if ($firstRowCols.length > 1) { + var $colVisibTh = $(g.t).find('th:not(.draggable)'); + PMA_tooltip( + $colVisibTh, + 'th', + PMA_messages.strColVisibHint + ); + + // create column visibility drop-down arrow(s) + $colVisibTh.each(function () { + var $th = $(this); + var cd = document.createElement('div'); // column drop-down arrow + var pos = $th.position(); + $(cd).addClass('coldrop') + .click(function () { + if (g.cList.style.display === 'none') { + g.showColList(this); + } else { + g.hideColList(); + } + }); + $(g.cDrop).append(cd); + }); + + // add column visibility control + g.cList.innerHTML = '
    '; + var $listDiv = $(g.cList).find('div'); + + var tempClick = function () { + if (g.toggleCol($(this).index())) { + g.afterToggleCol(); + } + }; + + for (i = 0; i < $firstRowCols.length; i++) { + var currHeader = $firstRowCols[i]; + var listElmt = document.createElement('div'); + $(listElmt).text($(currHeader).text()) + .prepend(''); + $listDiv.append(listElmt); + // add event on click + $(listElmt).click(tempClick); + } + // add "show all column" button + var showAll = document.createElement('div'); + $(showAll).addClass('showAllColBtn') + .text(g.showAllColText); + $(g.cList).append(showAll); + $(showAll).click(function () { + g.showAllColumns(); + }); + // prepend "show all column" button at top if the list is too long + if ($firstRowCols.length > 10) { + var clone = showAll.cloneNode(true); + $(g.cList).prepend(clone); + $(clone).click(function () { + g.showAllColumns(); + }); + } + } + + // hide column visibility list if we move outside the list + $(g.t).find('td, th.draggable').mouseenter(function () { + g.hideColList(); + }); + + // attach to global div + $(g.gDiv).append(g.cDrop); + $(g.gDiv).append(g.cList); + + // some adjustment + g.reposDrop(); + }, + + /** + * Move currently Editing Cell to Up + */ + moveUp: function (e) { + e.preventDefault(); + var $this_field = $(g.currentEditCell); + var field_name = getFieldName($(g.t), $this_field); + + var where_clause = $this_field.parents('tr').first().find('.where_clause').val(); + if (typeof where_clause === 'undefined') { + where_clause = ''; + } + var found = false; + var $found_row; + var $prev_row; + var j = 0; + + $this_field.parents('tr').first().parents('tbody').children().each(function () { + if ($(this).find('.where_clause').val() === where_clause) { + found = true; + $found_row = $(this); + } + if (!found) { + $prev_row = $(this); + } + }); + + var new_cell; + + if (found && $prev_row) { + $prev_row.children('td').each(function () { + if (getFieldName($(g.t), $(this)) === field_name) { + new_cell = this; + } + }); + } + + if (new_cell) { + g.hideEditCell(false, false, false, { move : true, cell : new_cell }); + } + }, + + /** + * Move currently Editing Cell to Down + */ + moveDown: function (e) { + e.preventDefault(); + + var $this_field = $(g.currentEditCell); + var field_name = getFieldName($(g.t), $this_field); + + var where_clause = $this_field.parents('tr').first().find('.where_clause').val(); + if (typeof where_clause === 'undefined') { + where_clause = ''; + } + var found = false; + var $found_row; + var $next_row; + var j = 0; + var next_row_found = false; + $this_field.parents('tr').first().parents('tbody').children().each(function () { + if ($(this).find('.where_clause').val() === where_clause) { + found = true; + $found_row = $(this); + } + if (found) { + if (j >= 1 && ! next_row_found) { + $next_row = $(this); + next_row_found = true; + } else { + j++; + } + } + }); + + var new_cell; + if (found && $next_row) { + $next_row.children('td').each(function () { + if (getFieldName($(g.t), $(this)) === field_name) { + new_cell = this; + } + }); + } + + if (new_cell) { + g.hideEditCell(false, false, false, { move : true, cell : new_cell }); + } + }, + + /** + * Move currently Editing Cell to Left + */ + moveLeft: function (e) { + e.preventDefault(); + + var $this_field = $(g.currentEditCell); + var field_name = getFieldName($(g.t), $this_field); + + var where_clause = $this_field.parents('tr').first().find('.where_clause').val(); + if (typeof where_clause === 'undefined') { + where_clause = ''; + } + var found = false; + var $found_row; + var j = 0; + $this_field.parents('tr').first().parents('tbody').children().each(function () { + if ($(this).find('.where_clause').val() === where_clause) { + found = true; + $found_row = $(this); + } + }); + + var left_cell; + var cell_found = false; + if (found) { + $found_row.children('td.grid_edit').each(function () { + if (getFieldName($(g.t), $(this)) === field_name) { + cell_found = true; + } + if (!cell_found) { + left_cell = this; + } + }); + } + + if (left_cell) { + g.hideEditCell(false, false, false, { move : true, cell : left_cell }); + } + }, + + /** + * Move currently Editing Cell to Right + */ + moveRight: function (e) { + e.preventDefault(); + + var $this_field = $(g.currentEditCell); + var field_name = getFieldName($(g.t), $this_field); + + var where_clause = $this_field.parents('tr').first().find('.where_clause').val(); + if (typeof where_clause === 'undefined') { + where_clause = ''; + } + var found = false; + var $found_row; + var j = 0; + $this_field.parents('tr').first().parents('tbody').children().each(function () { + if ($(this).find('.where_clause').val() === where_clause) { + found = true; + $found_row = $(this); + } + }); + + var right_cell; + var cell_found = false; + var next_cell_found = false; + if (found) { + $found_row.children('td.grid_edit').each(function () { + if (getFieldName($(g.t), $(this)) === field_name) { + cell_found = true; + } + if (cell_found) { + if (j >= 1 && ! next_cell_found) { + right_cell = this; + next_cell_found = true; + } else { + j++; + } + } + }); + } + + if (right_cell) { + g.hideEditCell(false, false, false, { move : true, cell : right_cell }); + } + }, + + /** + * Initialize grid editing feature. + */ + initGridEdit: function () { + function startGridEditing (e, cell) { + if (g.isCellEditActive) { + g.saveOrPostEditedCell(); + } else { + g.showEditCell(cell); + } + e.stopPropagation(); + } + + function handleCtrlNavigation (e) { + if ((e.ctrlKey && e.which === 38) || (e.altKey && e.which === 38)) { + g.moveUp(e); + } else if ((e.ctrlKey && e.which === 40) || (e.altKey && e.which === 40)) { + g.moveDown(e); + } else if ((e.ctrlKey && e.which === 37) || (e.altKey && e.which === 37)) { + g.moveLeft(e); + } else if ((e.ctrlKey && e.which === 39) || (e.altKey && e.which === 39)) { + g.moveRight(e); + } + } + + // create cell edit wrapper element + g.cEditStd = document.createElement('div'); + g.cEdit = g.cEditStd; + g.cEditTextarea = document.createElement('div'); + + // adjust g.cEditStd + g.cEditStd.className = 'cEdit'; + $(g.cEditStd).html('
    '); + $(g.cEditStd).hide(); + + // adjust g.cEdit + g.cEditTextarea.className = 'cEdit'; + $(g.cEditTextarea).html('
    '); + $(g.cEditTextarea).hide(); + + // assign cell editing hint + g.cellEditHint = PMA_messages.strCellEditHint; + g.saveCellWarning = PMA_messages.strSaveCellWarning; + g.alertNonUnique = PMA_messages.strAlertNonUnique; + g.gotoLinkText = PMA_messages.strGoToLink; + + // initialize cell editing configuration + g.saveCellsAtOnce = $(g.o).find('.save_cells_at_once').val(); + g.maxTruncatedLen = PMA_commonParams.get('LimitChars'); + + // register events + $(g.t).find('td.data.click1') + .click(function (e) { + startGridEditing(e, this); + // prevent default action when clicking on "link" in a table + if ($(e.target).is('.grid_edit a')) { + e.preventDefault(); + } + }); + + $(g.t).find('td.data.click2') + .click(function (e) { + var $cell = $(this); + // In the case of relational link, We want single click on the link + // to goto the link and double click to start grid-editing. + var $link = $(e.target); + if ($link.is('.grid_edit.relation a')) { + e.preventDefault(); + // get the click count and increase + var clicks = $cell.data('clicks'); + clicks = (typeof clicks === 'undefined') ? 1 : clicks + 1; + + if (clicks === 1) { + // if there are no previous clicks, + // start the single click timer + var timer = setTimeout(function () { + // temporarily remove ajax class so the page loader will not handle it, + // submit and then add it back + $link.removeClass('ajax'); + AJAX.requestHandler.call($link[0]); + $link.addClass('ajax'); + $cell.data('clicks', 0); + }, 700); + $cell.data('clicks', clicks); + $cell.data('timer', timer); + } else { + // this is a double click, cancel the single click timer + // and make the click count 0 + clearTimeout($cell.data('timer')); + $cell.data('clicks', 0); + // start grid-editing + startGridEditing(e, this); + } + } + }) + .dblclick(function (e) { + if ($(e.target).is('.grid_edit a')) { + e.preventDefault(); + } else { + startGridEditing(e, this); + } + }); + + $(g.cEditStd).on('keydown', 'input.edit_box, select', handleCtrlNavigation); + + $(g.cEditStd).find('.edit_box').focus(function () { + g.showEditArea(); + }); + $(g.cEditStd).on('keydown', '.edit_box, select', function (e) { + if (e.which === 13) { + // post on pressing "Enter" + e.preventDefault(); + g.saveOrPostEditedCell(); + } + }); + $(g.cEditStd).keydown(function (e) { + if (!g.isEditCellTextEditable) { + // prevent text editing + e.preventDefault(); + } + }); + + $(g.cEditTextarea).on('keydown', 'textarea.edit_box, select', handleCtrlNavigation); + + $(g.cEditTextarea).find('.edit_box').focus(function () { + g.showEditArea(); + }); + $(g.cEditTextarea).on('keydown', '.edit_box, select', function (e) { + if (e.which === 13 && !e.shiftKey) { + // post on pressing "Enter" + e.preventDefault(); + g.saveOrPostEditedCell(); + } + }); + $(g.cEditTextarea).keydown(function (e) { + if (!g.isEditCellTextEditable) { + // prevent text editing + e.preventDefault(); + } + }); + $('html').click(function (e) { + // hide edit cell if the click is not fromDat edit area + if ($(e.target).parents().index($(g.cEdit)) === -1 && + !$(e.target).parents('.ui-datepicker-header').length && + !$('.browse_foreign_modal.ui-dialog:visible').length && + !$(e.target).closest('.dismissable').length + ) { + g.hideEditCell(); + } + }).keydown(function (e) { + if (e.which === 27 && g.isCellEditActive) { + // cancel on pressing "Esc" + g.hideEditCell(true); + } + }); + $(g.o).find('div.save_edited').click(function () { + g.hideEditCell(); + g.postEditedCell(); + }); + $(window).on('beforeunload', function () { + if (g.isCellEdited) { + return g.saveCellWarning; + } + }); + + // attach to global div + $(g.gDiv).append(g.cEditStd); + $(g.gDiv).append(g.cEditTextarea); + + // add hint for grid editing feature when hovering "Edit" link in each table row + if (PMA_messages.strGridEditFeatureHint !== undefined) { + PMA_tooltip( + $(g.t).find('.edit_row_anchor a'), + 'a', + PMA_messages.strGridEditFeatureHint + ); + } + } + }; + + /** **************** + * Initialize grid + ******************/ + + // wrap all truncated data cells with span indicating the original length + // todo update the original length after a grid edit + $(t).find('td.data.truncated:not(:has(span))') + .wrapInner(function () { + return ''; + }); + + // wrap remaining cells, except actions cell, with span + $(t).find('th, td:not(:has(span))') + .wrapInner(''); + + // create grid elements + g.gDiv = document.createElement('div'); // create global div + + // initialize the table variable + g.t = t; + + // enclosing .sqlqueryresults div + g.o = $(t).parents('.sqlqueryresults'); + + // get data columns in the first row of the table + var $firstRowCols = $(t).find('tr:first th.draggable'); + + // initialize visible headers count + g.visibleHeadersCount = $firstRowCols.filter(':visible').length; + + // assign first column (actions) span + if (! $(t).find('tr:first th:first').hasClass('draggable')) { // action header exist + g.actionSpan = $(t).find('tr:first th:first').prop('colspan'); + } else { + g.actionSpan = 0; + } + + // assign table create time + // table_create_time will only available if we are in "Browse" tab + g.tableCreateTime = $(g.o).find('.table_create_time').val(); + + // assign the hints + g.sortHint = PMA_messages.strSortHint; + g.strMultiSortHint = PMA_messages.strMultiSortHint; + g.markHint = PMA_messages.strColMarkHint; + g.copyHint = PMA_messages.strColNameCopyHint; + + // assign common hidden inputs + var $common_hidden_inputs = $(g.o).find('div.common_hidden_inputs'); + g.server = $common_hidden_inputs.find('input[name=server]').val(); + g.db = $common_hidden_inputs.find('input[name=db]').val(); + g.table = $common_hidden_inputs.find('input[name=table]').val(); + + // add table class + $(t).addClass('pma_table'); + + // add relative position to global div so that resize handlers are correctly positioned + $(g.gDiv).css('position', 'relative'); + + // link the global div + $(t).before(g.gDiv); + $(g.gDiv).append(t); + + // FEATURES + enableResize = enableResize === undefined ? true : enableResize; + enableReorder = enableReorder === undefined ? true : enableReorder; + enableVisib = enableVisib === undefined ? true : enableVisib; + enableGridEdit = enableGridEdit === undefined ? true : enableGridEdit; + if (enableResize) { + g.initColResize(); + } + // disable reordering for result from EXPLAIN or SHOW syntax, which do not have a table navigation panel + if (enableReorder && + $(g.o).find('table.navigation').length > 0) { + g.initColReorder(); + } + if (enableVisib) { + g.initColVisib(); + } + // make sure we have the ajax class + if (enableGridEdit && + $(t).is('.ajax')) { + g.initGridEdit(); + } + + // create tooltip for each with draggable class + PMA_tooltip( + $(t).find('th.draggable'), + 'th', + g.updateHint() + ); + + // register events for hint tooltip (anchors inside draggable th) + $(t).find('th.draggable a') + .mouseenter(function () { + g.showSortHint = true; + g.showMultiSortHint = true; + $(t).find('th.draggable').tooltip('option', { + content: g.updateHint() + }); + }) + .mouseleave(function () { + g.showSortHint = false; + g.showMultiSortHint = false; + $(t).find('th.draggable').tooltip('option', { + content: g.updateHint() + }); + }); + + // register events for dragging-related feature + if (enableResize || enableReorder) { + $(document).mousemove(function (e) { + g.dragMove(e); + }); + $(document).mouseup(function (e) { + $(g.o).removeClass('turnOffSelect'); + g.dragEnd(e); + }); + } + + // some adjustment + $(t).removeClass('data'); + $(g.gDiv).addClass('data'); +} + +/** + * jQuery plugin to cancel selection in HTML code. + */ +(function ($) { + $.fn.noSelect = function (p) { // no select plugin by Paulo P.Marinas + var prevent = (p === null) ? true : p; + var is_msie = navigator.userAgent.indexOf('MSIE') > -1 || !!window.navigator.userAgent.match(/Trident.*rv\:11\./); + var is_firefox = navigator.userAgent.indexOf('Firefox') > -1; + var is_safari = navigator.userAgent.indexOf('Safari') > -1; + var is_opera = navigator.userAgent.indexOf('Presto') > -1; + if (prevent) { + return this.each(function () { + if (is_msie || is_safari) { + $(this).on('selectstart', false); + } else if (is_firefox) { + $(this).css('MozUserSelect', 'none'); + $('body').trigger('focus'); + } else if (is_opera) { + $(this).on('mousedown', false); + } else { + $(this).attr('unselectable', 'on'); + } + }); + } else { + return this.each(function () { + if (is_msie || is_safari) { + $(this).off('selectstart'); + } else if (is_firefox) { + $(this).css('MozUserSelect', 'inherit'); + } else if (is_opera) { + $(this).off('mousedown'); + } else { + $(this).removeAttr('unselectable'); + } + }); + } + }; // end noSelect +}(jQuery)); diff --git a/php/apps/phpmyadmin49/html/js/menu-resizer.js b/php/apps/phpmyadmin49/html/js/menu-resizer.js new file mode 100644 index 00000000..d5462fe0 --- /dev/null +++ b/php/apps/phpmyadmin49/html/js/menu-resizer.js @@ -0,0 +1,222 @@ +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * Handles the resizing of a menu according to the available screen width + * + * Uses themes/original/css/resizable-menu.css.php + * + * To initialise: + * $('#myMenu').menuResizer(function () { + * // This function will be called to find out how much + * // available horizontal space there is for the menu + * return $('body').width() - 5; // Some extra margin for good measure + * }); + * + * To trigger a resize operation: + * $('#myMenu').menuResizer('resize'); // Bind this to $(window).resize() + * + * To restore the menu to a state like before it was initialized: + * $('#myMenu').menuResizer('destroy'); + * + * @package PhpMyAdmin + */ +(function ($) { + function MenuResizer ($container, widthCalculator) { + var self = this; + self.$container = $container; + self.widthCalculator = widthCalculator; + var windowWidth = $(window).width(); + + if (windowWidth < 768) { + $('#pma_navigation_resizer').css({ 'width': '0px' }); + } + // Sets the image for the left and right scroll indicator + $('.scrollindicator--left').html($(PMA_getImage('b_left').toString())); + $('.scrollindicator--right').html($(PMA_getImage('b_right').toString())); + + // Set the width of the navigation bar without scroll indicator + $('.navigationbar').css({ 'width': widthCalculator.call($container) - 60 }); + + // Scroll the navigation bar on click + $('.scrollindicator--right').on('click', function () { + $('.navigationbar').scrollLeft($('.navigationbar').scrollLeft() + 70); + }); + $('.scrollindicator--left').on('click', function () { + $('.navigationbar').scrollLeft($('.navigationbar').scrollLeft() - 70); + }); + + // create submenu container + var link = $('', { href: '#', 'class': 'tab nowrap' }) + .text(PMA_messages.strMore) + .on('click', false); // same as event.preventDefault() + var img = $container.find('li img'); + if (img.length) { + $(PMA_getImage('b_more').toString()).prependTo(link); + } + var $submenu = $('
  • ', { 'class': 'submenu' }) + .append(link) + .append($('
      ')) + .on('mouseenter', function () { + if ($(this).find('ul .tabactive').length === 0) { + $(this) + .addClass('submenuhover') + .find('> a') + .addClass('tabactive'); + } + }) + .on('mouseleave', function () { + if ($(this).find('ul .tabactive').length === 0) { + $(this) + .removeClass('submenuhover') + .find('> a') + .removeClass('tabactive'); + } + }); + $container.children('.clearfloat').remove(); + $container.append($submenu).append('
      '); + setTimeout(function () { + self.resize(); + }, 4); + } + MenuResizer.prototype.resize = function () { + var wmax = this.widthCalculator.call(this.$container); + var windowWidth = $(window).width(); + var $submenu = this.$container.find('.submenu:last'); + var submenu_w = $submenu.outerWidth(true); + var $submenu_ul = $submenu.find('ul'); + var $li = this.$container.find('> li'); + var $li2 = $submenu_ul.find('li'); + var more_shown = $li2.length > 0; + // Calculate the total width used by all the shown tabs + var total_len = more_shown ? submenu_w : 0; + var l = $li.length - 1; + var i; + for (i = 0; i < l; i++) { + total_len += $($li[i]).outerWidth(true); + } + + var hasVScroll = document.body.scrollHeight > document.body.clientHeight; + if (hasVScroll) { + windowWidth += 15; + } + var navigationwidth = wmax; + if (windowWidth < 768) { + wmax = 2000; + } + + // Now hide menu elements that don't fit into the menubar + var hidden = false; // Whether we have hidden any tabs + while (total_len >= wmax && --l >= 0) { // Process the tabs backwards + hidden = true; + var el = $($li[l]); + var el_width = el.outerWidth(true); + el.data('width', el_width); + if (! more_shown) { + total_len -= el_width; + el.prependTo($submenu_ul); + total_len += submenu_w; + more_shown = true; + } else { + total_len -= el_width; + el.prependTo($submenu_ul); + } + } + // If we didn't hide any tabs, then there might be some space to show some + if (! hidden) { + // Show menu elements that do fit into the menubar + for (i = 0, l = $li2.length; i < l; i++) { + total_len += $($li2[i]).data('width'); + // item fits or (it is the last item + // and it would fit if More got removed) + if (total_len < wmax || + (i === $li2.length - 1 && total_len - submenu_w < wmax) + ) { + $($li2[i]).insertBefore($submenu); + } else { + break; + } + } + } + // Show/hide the "More" tab as needed + if (windowWidth < 768) { + $('.navigationbar').css({ 'width': windowWidth - 80 - $('#pma_navigation').width() }); + $submenu.removeClass('shown'); + $('.navigationbar').css({ 'overflow': 'hidden' }); + } else { + $('.navigationbar').css({ 'width': 'auto' }); + $('.navigationbar').css({ 'overflow': 'visible' }); + if ($submenu_ul.find('li').length > 0) { + $submenu.addClass('shown'); + } else { + $submenu.removeClass('shown'); + } + } + if (this.$container.find('> li').length === 1) { + // If there is only the "More" tab left, then we need + // to align the submenu to the left edge of the tab + $submenu_ul.removeClass().addClass('only'); + } else { + // Otherwise we align the submenu to the right edge of the tab + $submenu_ul.removeClass().addClass('notonly'); + } + if ($submenu.find('.tabactive').length) { + $submenu + .addClass('active') + .find('> a') + .removeClass('tab') + .addClass('tabactive'); + } else { + $submenu + .removeClass('active') + .find('> a') + .addClass('tab') + .removeClass('tabactive'); + } + }; + MenuResizer.prototype.destroy = function () { + var $submenu = this.$container.find('li.submenu').removeData(); + $submenu.find('li').appendTo(this.$container); + $submenu.remove(); + }; + + /** Public API */ + var methods = { + init: function (widthCalculator) { + return this.each(function () { + var $this = $(this); + if (! $this.data('menuResizer')) { + $this.data( + 'menuResizer', + new MenuResizer($this, widthCalculator) + ); + } + }); + }, + resize: function () { + return this.each(function () { + var self = $(this).data('menuResizer'); + if (self) { + self.resize(); + } + }); + }, + destroy: function () { + return this.each(function () { + var self = $(this).data('menuResizer'); + if (self) { + self.destroy(); + } + }); + } + }; + + /** Extend jQuery */ + $.fn.menuResizer = function (method) { + if (methods[method]) { + return methods[method].call(this); + } else if (typeof method === 'function') { + return methods.init.apply(this, [method]); + } else { + $.error('Method ' + method + ' does not exist on jQuery.menuResizer'); + } + }; +}(jQuery)); diff --git a/php/apps/phpmyadmin49/html/js/messages.php b/php/apps/phpmyadmin49/html/js/messages.php new file mode 100644 index 00000000..f5b81675 --- /dev/null +++ b/php/apps/phpmyadmin49/html/js/messages.php @@ -0,0 +1,995 @@ +start(); +if (!defined('TESTSUITE')) { + register_shutdown_function( + function () { + echo PhpMyAdmin\OutputBuffering::getInstance()->getContents(); + } + ); +} + +/* For confirmations */ +$js_messages['strConfirm'] = __('Confirm'); +$js_messages['strDoYouReally'] = __('Do you really want to execute "%s"?'); +$js_messages['strDropDatabaseStrongWarning'] + = __('You are about to DESTROY a complete database!'); +$js_messages['strDatabaseRenameToSameName'] + = __('Cannot rename database to the same name. Change the name and try again'); +$js_messages['strDropTableStrongWarning'] + = __('You are about to DESTROY a complete table!'); +$js_messages['strTruncateTableStrongWarning'] + = __('You are about to TRUNCATE a complete table!'); +$js_messages['strDeleteTrackingData'] = __('Delete tracking data for this table?'); +$js_messages['strDeleteTrackingDataMultiple'] + = __('Delete tracking data for these tables?'); +$js_messages['strDeleteTrackingVersion'] + = __('Delete tracking data for this version?'); +$js_messages['strDeleteTrackingVersionMultiple'] + = __('Delete tracking data for these versions?'); +$js_messages['strDeletingTrackingEntry'] = __('Delete entry from tracking report?'); +$js_messages['strDeletingTrackingData'] = __('Deleting tracking data'); +$js_messages['strDroppingPrimaryKeyIndex'] = __('Dropping Primary Key/Index'); +$js_messages['strDroppingForeignKey'] = __('Dropping Foreign key.'); +$js_messages['strOperationTakesLongTime'] + = __('This operation could take a long time. Proceed anyway?'); +$js_messages['strDropUserGroupWarning'] + = __('Do you really want to delete user group "%s"?'); +$js_messages['strConfirmDeleteQBESearch'] + = __('Do you really want to delete the search "%s"?'); +$js_messages['strConfirmNavigation'] + = __('You have unsaved changes; are you sure you want to leave this page?'); +$js_messages['strConfirmRowChange'] + = __('You are trying to reduce the number of rows, but have already entered data in those rows which will be lost. Do you wish to continue?'); +$js_messages['strDropUserWarning'] + = __('Do you really want to revoke the selected user(s) ?'); +$js_messages['strDeleteCentralColumnWarning'] + = __('Do you really want to delete this central column?'); +$js_messages['strDropRTEitems'] + = __('Do you really want to delete the selected items?'); +$js_messages['strDropPartitionWarning'] = __( + 'Do you really want to DROP the selected partition(s)? This will also DELETE ' . + 'the data related to the selected partition(s)!' +); +$js_messages['strTruncatePartitionWarning'] + = __('Do you really want to TRUNCATE the selected partition(s)?'); +$js_messages['strRemovePartitioningWarning'] + = __('Do you really want to remove partitioning?'); +$js_messages['strResetSlaveWarning'] = __('Do you really want to RESET SLAVE?'); +$js_messages['strChangeColumnCollation'] = __( + 'This operation will attempt to convert your data to the new collation. In ' + . 'rare cases, especially where a character doesn\'t exist in the new ' + . 'collation, this process could cause the data to appear incorrectly under ' + . 'the new collation; in this case we suggest you revert to the original ' + . 'collation and refer to the tips at ' +) + . '
      ' . __('Garbled Data') . '.' + . '

      ' + . __('Are you sure you wish to change the collation and convert the data?'); +$js_messages['strChangeAllColumnCollationsWarning'] = __( + 'Through this operation, MySQL attempts to map the data values between ' + . 'collations. If the character sets are incompatible, there may be data loss ' + . 'and this lost data may NOT be recoverable simply by changing back the ' + . 'column collation(s). To convert existing data, it is suggested to use the ' + . 'column(s) editing feature (the "Change" Link) on the table structure page. ' + . '' +) +. '

      ' +. __( + 'Are you sure you wish to change all the column collations and convert the data?' +); + +/* For modal dialog buttons */ +$js_messages['strSaveAndClose'] = __('Save & close'); +$js_messages['strReset'] = __('Reset'); +$js_messages['strResetAll'] = __('Reset all'); + +/* For indexes */ +$js_messages['strFormEmpty'] = __('Missing value in the form!'); +$js_messages['strRadioUnchecked'] = __('Select at least one of the options!'); +$js_messages['strEnterValidNumber'] = __('Please enter a valid number!'); +$js_messages['strEnterValidLength'] = __('Please enter a valid length!'); +$js_messages['strAddIndex'] = __('Add index'); +$js_messages['strEditIndex'] = __('Edit index'); +$js_messages['strAddToIndex'] = __('Add %s column(s) to index'); +$js_messages['strCreateSingleColumnIndex'] = __('Create single-column index'); +$js_messages['strCreateCompositeIndex'] = __('Create composite index'); +$js_messages['strCompositeWith'] = __('Composite with:'); +$js_messages['strMissingColumn'] = __('Please select column(s) for the index.'); + +/* For Preview SQL*/ +$js_messages['strPreviewSQL'] = __('Preview SQL'); + +/* For Simulate DML*/ +$js_messages['strSimulateDML'] = __('Simulate query'); +$js_messages['strMatchedRows'] = __('Matched rows:'); +$js_messages['strSQLQuery'] = __('SQL query:'); + +/* Charts */ +/* l10n: Default label for the y-Axis of Charts */ +$js_messages['strYValues'] = __('Y values'); + +/* Database multi-table query */ +$js_messages['strEmptyQuery'] = __('Please enter the SQL query first.'); + +/* For server_privileges.js */ +$js_messages['strHostEmpty'] = __('The host name is empty!'); +$js_messages['strUserEmpty'] = __('The user name is empty!'); +$js_messages['strPasswordEmpty'] = __('The password is empty!'); +$js_messages['strPasswordNotSame'] = __('The passwords aren\'t the same!'); +$js_messages['strRemovingSelectedUsers'] = __('Removing Selected Users'); +$js_messages['strClose'] = __('Close'); + +/* For export.js */ +$js_messages['strTemplateCreated'] = __('Template was created.'); +$js_messages['strTemplateLoaded'] = __('Template was loaded.'); +$js_messages['strTemplateUpdated'] = __('Template was updated.'); +$js_messages['strTemplateDeleted'] = __('Template was deleted.'); + +/* l10n: Other, small valued, queries */ +$js_messages['strOther'] = __('Other'); +/* l10n: Thousands separator */ +$js_messages['strThousandsSeparator'] = __(','); +/* l10n: Decimal separator */ +$js_messages['strDecimalSeparator'] = __('.'); + +$js_messages['strChartConnectionsTitle'] = __('Connections / Processes'); + +/* server status monitor */ +$js_messages['strIncompatibleMonitorConfig'] + = __('Local monitor configuration incompatible!'); +$js_messages['strIncompatibleMonitorConfigDescription'] = __( + 'The chart arrangement configuration in your browsers local storage is not ' + . 'compatible anymore to the newer version of the monitor dialog. It is very ' + . 'likely that your current configuration will not work anymore. Please reset ' + . 'your configuration to default in the Settings menu.' +); + +$js_messages['strQueryCacheEfficiency'] = __('Query cache efficiency'); +$js_messages['strQueryCacheUsage'] = __('Query cache usage'); +$js_messages['strQueryCacheUsed'] = __('Query cache used'); + +$js_messages['strSystemCPUUsage'] = __('System CPU usage'); +$js_messages['strSystemMemory'] = __('System memory'); +$js_messages['strSystemSwap'] = __('System swap'); + +$js_messages['strAverageLoad'] = __('Average load'); +$js_messages['strTotalMemory'] = __('Total memory'); +$js_messages['strCachedMemory'] = __('Cached memory'); +$js_messages['strBufferedMemory'] = __('Buffered memory'); +$js_messages['strFreeMemory'] = __('Free memory'); +$js_messages['strUsedMemory'] = __('Used memory'); + +$js_messages['strTotalSwap'] = __('Total swap'); +$js_messages['strCachedSwap'] = __('Cached swap'); +$js_messages['strUsedSwap'] = __('Used swap'); +$js_messages['strFreeSwap'] = __('Free swap'); + +$js_messages['strBytesSent'] = __('Bytes sent'); +$js_messages['strBytesReceived'] = __('Bytes received'); +$js_messages['strConnections'] = __('Connections'); +$js_messages['strProcesses'] = __('Processes'); + +/* summary row */ +$js_messages['strB'] = __('B'); +$js_messages['strKiB'] = __('KiB'); +$js_messages['strMiB'] = __('MiB'); +$js_messages['strGiB'] = __('GiB'); +$js_messages['strTiB'] = __('TiB'); +$js_messages['strPiB'] = __('PiB'); +$js_messages['strEiB'] = __('EiB'); +$js_messages['strNTables'] = __('%d table(s)'); + +/* l10n: Questions is the name of a MySQL Status variable */ +$js_messages['strQuestions'] = __('Questions'); +$js_messages['strTraffic'] = __('Traffic'); +$js_messages['strSettings'] = __('Settings'); +$js_messages['strAddChart'] = __('Add chart to grid'); +$js_messages['strClose'] = __('Close'); +$js_messages['strAddOneSeriesWarning'] + = __('Please add at least one variable to the series!'); +$js_messages['strNone'] = __('None'); +$js_messages['strResumeMonitor'] = __('Resume monitor'); +$js_messages['strPauseMonitor'] = __('Pause monitor'); +$js_messages['strStartRefresh'] = __('Start auto refresh'); +$js_messages['strStopRefresh'] = __('Stop auto refresh'); +/* Monitor: Instructions Dialog */ +$js_messages['strBothLogOn'] = __('general_log and slow_query_log are enabled.'); +$js_messages['strGenLogOn'] = __('general_log is enabled.'); +$js_messages['strSlowLogOn'] = __('slow_query_log is enabled.'); +$js_messages['strBothLogOff'] = __('slow_query_log and general_log are disabled.'); +$js_messages['strLogOutNotTable'] = __('log_output is not set to TABLE.'); +$js_messages['strLogOutIsTable'] = __('log_output is set to TABLE.'); +$js_messages['strSmallerLongQueryTimeAdvice'] = __( + 'slow_query_log is enabled, but the server logs only queries that take longer ' + . 'than %d seconds. It is advisable to set this long_query_time 0-2 seconds, ' + . 'depending on your system.' +); +$js_messages['strLongQueryTimeSet'] = __('long_query_time is set to %d second(s).'); +$js_messages['strSettingsAppliedGlobal'] = __( + 'Following settings will be applied globally and reset to default on server ' + . 'restart:' +); +/* l10n: %s is FILE or TABLE */ +$js_messages['strSetLogOutput'] = __('Set log_output to %s'); +/* l10n: Enable in this context means setting a status variable to ON */ +$js_messages['strEnableVar'] = __('Enable %s'); +/* l10n: Disable in this context means setting a status variable to OFF */ +$js_messages['strDisableVar'] = __('Disable %s'); +/* l10n: %d seconds */ +$js_messages['setSetLongQueryTime'] = __('Set long_query_time to %d seconds.'); +$js_messages['strNoSuperUser'] = __( + 'You can\'t change these variables. Please log in as root or contact' + . ' your database administrator.' +); +$js_messages['strChangeSettings'] = __('Change settings'); +$js_messages['strCurrentSettings'] = __('Current settings'); + +$js_messages['strChartTitle'] = __('Chart title'); +/* l10n: As in differential values */ +$js_messages['strDifferential'] = __('Differential'); +$js_messages['strDividedBy'] = __('Divided by %s'); +$js_messages['strUnit'] = __('Unit'); + +$js_messages['strFromSlowLog'] = __('From slow log'); +$js_messages['strFromGeneralLog'] = __('From general log'); +$js_messages['strServerLogError'] = __( + 'The database name is not known for this query in the server\'s logs.' +); +$js_messages['strAnalysingLogsTitle'] = __('Analysing logs'); +$js_messages['strAnalysingLogs'] + = __('Analysing & loading logs. This may take a while.'); +$js_messages['strCancelRequest'] = __('Cancel request'); +$js_messages['strCountColumnExplanation'] = __( + 'This column shows the amount of identical queries that are grouped together. ' + . 'However only the SQL query itself has been used as a grouping criteria, so ' + . 'the other attributes of queries, such as start time, may differ.' +); +$js_messages['strMoreCountColumnExplanation'] = __( + 'Since grouping of INSERTs queries has been selected, INSERT queries into the ' + . 'same table are also being grouped together, disregarding of the inserted ' + . 'data.' +); +$js_messages['strLogDataLoaded'] + = __('Log data loaded. Queries executed in this time span:'); + +$js_messages['strJumpToTable'] = __('Jump to Log table'); +$js_messages['strNoDataFoundTitle'] = __('No data found'); +$js_messages['strNoDataFound'] + = __('Log analysed, but no data found in this time span.'); + +$js_messages['strAnalyzing'] = __('Analyzing…'); +$js_messages['strExplainOutput'] = __('Explain output'); +$js_messages['strStatus'] = __('Status'); +$js_messages['strTime'] = __('Time'); +$js_messages['strTotalTime'] = __('Total time:'); +$js_messages['strProfilingResults'] = __('Profiling results'); +$js_messages['strTable'] = _pgettext('Display format', 'Table'); +$js_messages['strChart'] = __('Chart'); + +$js_messages['strAliasDatabase'] = _pgettext('Alias', 'Database'); +$js_messages['strAliasTable'] = _pgettext('Alias', 'Table'); +$js_messages['strAliasColumn'] = _pgettext('Alias', 'Column'); + +/* l10n: A collection of available filters */ +$js_messages['strFiltersForLogTable'] = __('Log table filter options'); +/* l10n: Filter as in "Start Filtering" */ +$js_messages['strFilter'] = __('Filter'); +$js_messages['strFilterByWordRegexp'] = __('Filter queries by word/regexp:'); +$js_messages['strIgnoreWhereAndGroup'] + = __('Group queries, ignoring variable data in WHERE clauses'); +$js_messages['strSumRows'] = __('Sum of grouped rows:'); +$js_messages['strTotal'] = __('Total:'); + +$js_messages['strLoadingLogs'] = __('Loading logs'); +$js_messages['strRefreshFailed'] = __('Monitor refresh failed'); +$js_messages['strInvalidResponseExplanation'] = __( + 'While requesting new chart data the server returned an invalid response. This ' + . 'is most likely because your session expired. Reloading the page and ' + . 'reentering your credentials should help.' +); +$js_messages['strReloadPage'] = __('Reload page'); + +$js_messages['strAffectedRows'] = __('Affected rows:'); + +$js_messages['strFailedParsingConfig'] = __( + 'Failed parsing config file. It doesn\'t seem to be valid JSON code.' +); +$js_messages['strFailedBuildingGrid'] = __( + 'Failed building chart grid with imported config. Resetting to default config…' +); +$js_messages['strImport'] = __('Import'); +$js_messages['strImportDialogTitle'] = __('Import monitor configuration'); +$js_messages['strImportDialogMessage'] + = __('Please select the file you want to import.'); +$js_messages['strNoImportFile'] = __('No files available on server for import!'); + +$js_messages['strAnalyzeQuery'] = __('Analyse query'); + +/* Server status advisor */ + +$js_messages['strAdvisorSystem'] = __('Advisor system'); +$js_messages['strPerformanceIssues'] = __('Possible performance issues'); +$js_messages['strIssuse'] = __('Issue'); +$js_messages['strRecommendation'] = __('Recommendation'); +$js_messages['strRuleDetails'] = __('Rule details'); +$js_messages['strJustification'] = __('Justification'); +$js_messages['strFormula'] = __('Used variable / formula'); +$js_messages['strTest'] = __('Test'); + +/* For query editor */ +$js_messages['strFormatting'] = __('Formatting SQL…'); +$js_messages['strNoParam'] = __('No parameters found!'); + +/* For inline query editing */ +$js_messages['strGo'] = __('Go'); +$js_messages['strCancel'] = __('Cancel'); + +/* For page-related settings */ +$js_messages['strPageSettings'] = __('Page-related settings'); +$js_messages['strApply'] = __('Apply'); + +/* For Ajax Notifications */ +$js_messages['strLoading'] = __('Loading…'); +$js_messages['strAbortedRequest'] = __('Request aborted!!'); +$js_messages['strProcessingRequest'] = __('Processing request'); +$js_messages['strRequestFailed'] = __('Request failed!!'); +$js_messages['strErrorProcessingRequest'] = __('Error in processing request'); +$js_messages['strErrorCode'] = __('Error code: %s'); +$js_messages['strErrorText'] = __('Error text: %s'); +$js_messages['strErrorConnection'] = __( + 'It seems that the connection to server has been lost. Please check your ' . + 'network connectivity and server status.' +); +$js_messages['strNoDatabasesSelected'] = __('No databases selected.'); +$js_messages['strNoAccountSelected'] = __('No accounts selected.'); +$js_messages['strDroppingColumn'] = __('Dropping column'); +$js_messages['strAddingPrimaryKey'] = __('Adding primary key'); +$js_messages['strOK'] = __('OK'); +$js_messages['strDismiss'] = __('Click to dismiss this notification'); + +/* For db_operations.js */ +$js_messages['strRenamingDatabases'] = __('Renaming databases'); +$js_messages['strCopyingDatabase'] = __('Copying database'); +$js_messages['strChangingCharset'] = __('Changing charset'); +$js_messages['strNo'] = __('No'); + +/* For Foreign key checks */ +$js_messages['strForeignKeyCheck'] = __('Enable foreign key checks'); + +/* For db_stucture.js */ +$js_messages['strErrorRealRowCount'] = __('Failed to get real row count.'); + +/* For db_search.js */ +$js_messages['strSearching'] = __('Searching'); +$js_messages['strHideSearchResults'] = __('Hide search results'); +$js_messages['strShowSearchResults'] = __('Show search results'); +$js_messages['strBrowsing'] = __('Browsing'); +$js_messages['strDeleting'] = __('Deleting'); +$js_messages['strConfirmDeleteResults'] = __('Delete the matches for the %s table?'); + +/* For db_routines.js */ +$js_messages['MissingReturn'] + = __('The definition of a stored function must contain a RETURN statement!'); +$js_messages['strExport'] = __('Export'); +$js_messages['NoExportable'] + = __('No routine is exportable. Required privileges may be lacking.'); + +/* For ENUM/SET editor*/ +$js_messages['enum_editor'] = __('ENUM/SET editor'); +$js_messages['enum_columnVals'] =__('Values for column %s'); +$js_messages['enum_newColumnVals'] = __('Values for a new column'); +$js_messages['enum_hint'] =__('Enter each value in a separate field.'); +$js_messages['enum_addValue'] =__('Add %d value(s)'); + +/* For import.js */ +$js_messages['strImportCSV'] = __( + 'Note: If the file contains multiple tables, they will be combined into one.' +); + +/* For sql.js */ +$js_messages['strHideQueryBox'] = __('Hide query box'); +$js_messages['strShowQueryBox'] = __('Show query box'); +$js_messages['strEdit'] = __('Edit'); +$js_messages['strDelete'] = __('Delete'); +$js_messages['strNotValidRowNumber'] = __('%d is not valid row number.'); +$js_messages['strBrowseForeignValues'] = __('Browse foreign values'); +$js_messages['strNoAutoSavedQuery'] = __('No auto-saved query'); +$js_messages['strBookmarkVariable'] = __('Variable %d:'); + +/* For Central list of columns */ +$js_messages['pickColumn'] = __('Pick'); +$js_messages['pickColumnTitle'] = __('Column selector'); +$js_messages['searchList'] = __('Search this list'); +$js_messages['strEmptyCentralList'] = __( + 'No columns in the central list. Make sure the Central columns list for ' + . 'database %s has columns that are not present in the current table.' +); +$js_messages['seeMore'] = __('See more'); +$js_messages['confirmTitle'] = __('Are you sure?'); +$js_messages['makeConsistentMessage'] = __( + 'This action may change some of the columns definition.
      Are you sure you ' + . 'want to continue?' +); +$js_messages['strContinue'] = __('Continue'); + +/** For normalization */ +$js_messages['strAddPrimaryKey'] = __('Add primary key'); +$js_messages['strPrimaryKeyAdded'] = __('Primary key added.'); +$js_messages['strToNextStep'] = __('Taking you to next step…'); +$js_messages['strFinishMsg'] + = __("The first step of normalization is complete for table '%s'."); +$js_messages['strEndStep'] = __("End of step"); +$js_messages['str2NFNormalization'] = __('Second step of normalization (2NF)'); +$js_messages['strDone'] = __('Done'); +$js_messages['strConfirmPd'] = __('Confirm partial dependencies'); +$js_messages['strSelectedPd'] = __('Selected partial dependencies are as follows:'); +$js_messages['strPdHintNote'] = __( + 'Note: a, b -> d,f implies values of columns a and b combined together can ' + . 'determine values of column d and column f.' +); +$js_messages['strNoPdSelected'] = __('No partial dependencies selected!'); +$js_messages['strBack'] = __('Back'); +$js_messages['strShowPossiblePd'] + = __('Show me the possible partial dependencies based on data in the table'); +$js_messages['strHidePd'] = __('Hide partial dependencies list'); +$js_messages['strWaitForPd'] = __( + 'Sit tight! It may take few seconds depending on data size and column count of ' + . 'the table.' +); +$js_messages['strStep'] = __('Step'); +$js_messages['strMoveRepeatingGroup'] + = '
        ' . __('The following actions will be performed:') . '' + . '
      1. ' . __('DROP columns %s from the table %s') . '
      2. ' + . '
      3. ' . __('Create the following table') . '
      4. '; +$js_messages['strNewTablePlaceholder'] = 'Enter new table name'; +$js_messages['strNewColumnPlaceholder'] = 'Enter column name'; +$js_messages['str3NFNormalization'] = __('Third step of normalization (3NF)'); +$js_messages['strConfirmTd'] = __('Confirm transitive dependencies'); +$js_messages['strSelectedTd'] = __('Selected dependencies are as follows:'); +$js_messages['strNoTdSelected'] = __('No dependencies selected!'); + +/* For server_variables.js */ +$js_messages['strSave'] = __('Save'); + +/* For tbl_select.js */ +$js_messages['strHideSearchCriteria'] = __('Hide search criteria'); +$js_messages['strShowSearchCriteria'] = __('Show search criteria'); +$js_messages['strRangeSearch'] = __('Range search'); +$js_messages['strColumnMax'] = __('Column maximum:'); +$js_messages['strColumnMin'] = __('Column minimum:'); +$js_messages['strMinValue'] = __('Minimum value:'); +$js_messages['strMaxValue'] = __('Maximum value:'); + +/* For tbl_find_replace.js */ +$js_messages['strHideFindNReplaceCriteria'] = __('Hide find and replace criteria'); +$js_messages['strShowFindNReplaceCriteria'] = __('Show find and replace criteria'); + +/* For tbl_zoom_plot_jqplot.js */ +$js_messages['strDisplayHelp'] = '
        • ' + . __('Each point represents a data row.') + . '
        • ' + . __('Hovering over a point will show its label.') + . '
        • ' + . __('To zoom in, select a section of the plot with the mouse.') + . '
        • ' + . __('Click reset zoom button to come back to original state.') + . '
        • ' + . __('Click a data point to view and possibly edit the data row.') + . '
        • ' + . __('The plot can be resized by dragging it along the bottom right corner.') + . '
        '; +$js_messages['strHelpTitle'] = 'Zoom search instructions'; +$js_messages['strInputNull'] = '' . __('Select two columns') . ''; +$js_messages['strSameInputs'] = '' + . __('Select two different columns') + . ''; +$js_messages['strDataPointContent'] = __('Data point content'); + +/* For tbl_change.js */ +$js_messages['strIgnore'] = __('Ignore'); +$js_messages['strCopy'] = __('Copy'); +$js_messages['strX'] = __('X'); +$js_messages['strY'] = __('Y'); +$js_messages['strPoint'] = __('Point'); +$js_messages['strPointN'] = __('Point %d'); +$js_messages['strLineString'] = __('Linestring'); +$js_messages['strPolygon'] = __('Polygon'); +$js_messages['strGeometry'] = __('Geometry'); +$js_messages['strInnerRing'] = __('Inner ring'); +$js_messages['strOuterRing'] = __('Outer ring'); +$js_messages['strAddPoint'] = __('Add a point'); +$js_messages['strAddInnerRing'] = __('Add an inner ring'); +$js_messages['strYes'] = __('Yes'); +$js_messages['strCopyEncryptionKey'] = __('Do you want to copy encryption key?'); +$js_messages['strEncryptionKey'] = __('Encryption key'); + +/* For Tip to be shown on Time field */ +$js_messages['strMysqlAllowedValuesTipTime'] = __( + 'MySQL accepts additional values not selectable by the slider;' + . ' key in those values directly if desired' +); + +/* For Tip to be shown on Date field */ +$js_messages['strMysqlAllowedValuesTipDate'] = __( + 'MySQL accepts additional values not selectable by the datepicker;' + . ' key in those values directly if desired' +); + +/* For Lock symbol Tooltip */ +$js_messages['strLockToolTip'] = __( + 'Indicates that you have made changes to this page;' + . ' you will be prompted for confirmation before abandoning changes' +); + +/* Designer (js/designer/move.js) */ +$js_messages['strSelectReferencedKey'] = __('Select referenced key'); +$js_messages['strSelectForeignKey'] = __('Select Foreign Key'); +$js_messages['strPleaseSelectPrimaryOrUniqueKey'] + = __('Please select the primary key or a unique key!'); +$js_messages['strChangeDisplay'] = __('Choose column to display'); +$js_messages['strLeavingDesigner'] = __( + 'You haven\'t saved the changes in the layout. They will be lost if you' + . ' don\'t save them. Do you want to continue?' +); +$js_messages['strQueryEmpty'] = __('value/subQuery is empty'); +$js_messages['strAddTables'] = __('Add tables from other databases'); +$js_messages['strPageName'] = __('Page name'); +$js_messages['strSavePage'] = __('Save page'); +$js_messages['strSavePageAs'] = __('Save page as'); +$js_messages['strOpenPage'] = __('Open page'); +$js_messages['strDeletePage'] = __('Delete page'); +$js_messages['strUntitled'] = __('Untitled'); +$js_messages['strSelectPage'] = __('Please select a page to continue'); +$js_messages['strEnterValidPageName'] = __('Please enter a valid page name'); +$js_messages['strLeavingPage'] + = __('Do you want to save the changes to the current page?'); +$js_messages['strSuccessfulPageDelete'] = __('Successfully deleted the page'); +$js_messages['strExportRelationalSchema'] = __('Export relational schema'); +$js_messages['strModificationSaved'] = __('Modifications have been saved'); + +/* Visual query builder (js/designer/move.js) */ +$js_messages['strAddOption'] = __('Add an option for column "%s".'); +$js_messages['strObjectsCreated'] = __('%d object(s) created.'); +$js_messages['strSubmit'] = __('Submit'); + +/* For makegrid.js (column reordering, show/hide column, grid editing) */ +$js_messages['strCellEditHint'] = __('Press escape to cancel editing.'); +$js_messages['strSaveCellWarning'] = __( + 'You have edited some data and they have not been saved. Are you sure you want ' + . 'to leave this page before saving the data?' +); +$js_messages['strColOrderHint'] = __('Drag to reorder.'); +$js_messages['strSortHint'] = __('Click to sort results by this column.'); +$js_messages['strMultiSortHint'] = __( + 'Shift+Click to add this column to ORDER BY clause or to toggle ASC/DESC.' + . '
        - Ctrl+Click or Alt+Click (Mac: Shift+Option+Click) to remove column ' + . 'from ORDER BY clause' +); +$js_messages['strColMarkHint'] = __('Click to mark/unmark.'); +$js_messages['strColNameCopyHint'] = __('Double-click to copy column name.'); +$js_messages['strColVisibHint'] = __( + 'Click the drop-down arrow
        to toggle column\'s visibility.' +); +$js_messages['strShowAllCol'] = __('Show all'); +$js_messages['strAlertNonUnique'] = __( + 'This table does not contain a unique column. Features related to the grid ' + . 'edit, checkbox, Edit, Copy and Delete links may not work after saving.' +); +$js_messages['strEnterValidHex'] + = __('Please enter a valid hexadecimal string. Valid characters are 0-9, A-F.'); +$js_messages['strShowAllRowsWarning'] = __( + 'Do you really want to see all of the rows? For a big table this could crash ' + . 'the browser.' +); +$js_messages['strOriginalLength'] = __('Original length'); + +/** Drag & Drop sql import messages */ +$js_messages['dropImportMessageCancel'] = __('cancel'); +$js_messages['dropImportMessageAborted'] = __('Aborted'); +$js_messages['dropImportMessageFailed'] = __('Failed'); +$js_messages['dropImportMessageSuccess'] = __('Success'); +$js_messages['dropImportImportResultHeader'] = __('Import status'); +$js_messages['dropImportDropFiles'] = __('Drop files here'); +$js_messages['dropImportSelectDB'] = __('Select database first'); + +/* For Print view */ +$js_messages['print'] = __('Print'); +$js_messages['back'] = __('Back'); + +// this approach does not work when the parameter is changed via user prefs +switch ($GLOBALS['cfg']['GridEditing']) { +case 'double-click': + $js_messages['strGridEditFeatureHint'] = __( + 'You can also edit most values
        by double-clicking directly on them.' + ); + break; +case 'click': + $js_messages['strGridEditFeatureHint'] = __( + 'You can also edit most values
        by clicking directly on them.' + ); + break; +default: + break; +} +$js_messages['strGoToLink'] = __('Go to link:'); +$js_messages['strColNameCopyTitle'] = __('Copy column name.'); +$js_messages['strColNameCopyText'] + = __('Right-click the column name to copy it to your clipboard.'); + +/* password generation */ +$js_messages['strGeneratePassword'] = __('Generate password'); +$js_messages['strGenerate'] = __('Generate'); +$js_messages['strChangePassword'] = __('Change password'); + +/* navigation tabs */ +$js_messages['strMore'] = __('More'); + +/* navigation panel */ +$js_messages['strShowPanel'] = __('Show panel'); +$js_messages['strHidePanel'] = __('Hide panel'); +$js_messages['strUnhideNavItem'] = __('Show hidden navigation tree items.'); +$js_messages['linkWithMain'] = __('Link with main panel'); +$js_messages['unlinkWithMain'] = __('Unlink from main panel'); + +/* microhistory */ +$js_messages['strInvalidPage'] + = __('The requested page was not found in the history, it may have expired.'); + +/* update */ +$js_messages['strNewerVersion'] = __( + 'A newer version of phpMyAdmin is available and you should consider upgrading. ' + . 'The newest version is %s, released on %s.' +); +/* l10n: Latest available phpMyAdmin version */ +$js_messages['strLatestAvailable'] = __(', latest stable version:'); +$js_messages['strUpToDate'] = __('up to date'); + +$js_messages['strCreateView'] = __('Create view'); + +/* Error Reporting */ +$js_messages['strSendErrorReport'] = __("Send error report"); +$js_messages['strSubmitErrorReport'] = __("Submit error report"); +$js_messages['strErrorOccurred'] = __( + "A fatal JavaScript error has occurred. Would you like to send an error report?" +); +$js_messages['strChangeReportSettings'] = __("Change report settings"); +$js_messages['strShowReportDetails'] = __("Show report details"); +$js_messages['strIgnore'] = __("Ignore"); +$js_messages['strTimeOutError'] = __( + "Your export is incomplete, due to a low execution time limit at the PHP level!" +); + +$js_messages['strTooManyInputs'] = __( + "Warning: a form on this page has more than %d fields. On submission, " + . "some of the fields might be ignored, due to PHP's " + . "max_input_vars configuration." +); + +$js_messages['phpErrorsFound'] = '
        ' + . __('Some errors have been detected on the server!') + . '
        ' + . __('Please look at the bottom of this window.') + . '
        ' + . '' + . '' + . '
        '; + +$js_messages['phpErrorsBeingSubmitted'] = '
        ' + . __('Some errors have been detected on the server!') + . '
        ' + . __( + 'As per your settings, they are being submitted currently, please be ' + . 'patient.' + ) + . '
        ' + . 'ajax clock' + . '
        '; + +// For console +$js_messages['strConsoleRequeryConfirm'] = __('Execute this query again?'); +$js_messages['strConsoleDeleteBookmarkConfirm'] + = __('Do you really want to delete this bookmark?'); +$js_messages['strConsoleDebugError'] + = __('Some error occurred while getting SQL debug info.'); +$js_messages['strConsoleDebugSummary'] + = __('%s queries executed %s times in %s seconds.'); +$js_messages['strConsoleDebugArgsSummary'] = __('%s argument(s) passed'); +$js_messages['strConsoleDebugShowArgs'] = __('Show arguments'); +$js_messages['strConsoleDebugHideArgs'] = __('Hide arguments'); +$js_messages['strConsoleDebugTimeTaken'] = __('Time taken:'); +$js_messages['strNoLocalStorage'] = __('There was a problem accessing your browser storage, some features may not work properly for you. It is likely that the browser doesn\'t support storage or the quota limit has been reached. In Firefox, corrupted storage can also cause such a problem, clearing your "Offline Website Data" might help. In Safari, such problem is commonly caused by "Private Mode Browsing".'); +// For modals in db_structure.php +$js_messages['strCopyTablesTo'] = __('Copy tables to'); +$js_messages['strAddPrefix'] = __('Add table prefix'); +$js_messages['strReplacePrefix'] = __('Replace table with prefix'); +$js_messages['strCopyPrefix'] = __('Copy table with prefix'); + +/* For password strength simulation */ +$js_messages['strExtrWeak'] = __('Extremely weak'); +$js_messages['strVeryWeak'] = __('Very weak'); +$js_messages['strWeak'] = __('Weak'); +$js_messages['strGood'] = __('Good'); +$js_messages['strStrong'] = __('Strong'); + +/* U2F errors */ +$js_messages['strU2FTimeout'] = __('Timed out waiting for security key activation.'); +$js_messages['strU2FError'] = __('Failed security key activation (%s).'); + +/* Designer */ +$js_messages['strTableAlreadyExists'] = _pgettext('The table already exists in the designer and can not be added once more.', 'Table %s already exists!'); +$js_messages['strHide'] = __('Hide'); +$js_messages['strStructure'] = __('Structure'); + +echo "var PMA_messages = new Array();\n"; +foreach ($js_messages as $name => $js_message) { + Sanitize::printJsValue("PMA_messages['" . $name . "']", $js_message); +} + +/* Calendar */ +echo "var themeCalendarImage = '" , $GLOBALS['pmaThemeImage'] + , 'b_calendar.png' , "';\n"; + +/* Image path */ +echo "var pmaThemeImage = '" , $GLOBALS['pmaThemeImage'] , "';\n"; + +echo "var mysql_doc_template = '" , PhpMyAdmin\Util::getMySQLDocuURL('%s') + , "';\n"; + +//Max input vars allowed by PHP. +$maxInputVars = ini_get('max_input_vars'); +echo 'var maxInputVars = ' + , (false === $maxInputVars || '' == $maxInputVars ? 'false' : (int)$maxInputVars) + , ';' . "\n"; + +echo "if ($.datepicker) {\n"; +/* l10n: Display text for calendar close link */ +Sanitize::printJsValue("$.datepicker.regional['']['closeText']", __('Done')); +/* l10n: Display text for previous month link in calendar */ +Sanitize::printJsValue( + "$.datepicker.regional['']['prevText']", + _pgettext('Previous month', 'Prev') +); +/* l10n: Display text for next month link in calendar */ +Sanitize::printJsValue( + "$.datepicker.regional['']['nextText']", + _pgettext('Next month', 'Next') +); +/* l10n: Display text for current month link in calendar */ +Sanitize::printJsValue("$.datepicker.regional['']['currentText']", __('Today')); +Sanitize::printJsValue( + "$.datepicker.regional['']['monthNames']", + array( + __('January'), + __('February'), + __('March'), + __('April'), + __('May'), + __('June'), + __('July'), + __('August'), + __('September'), + __('October'), + __('November'), + __('December') + ) +); +Sanitize::printJsValue( + "$.datepicker.regional['']['monthNamesShort']", + array( + /* l10n: Short month name */ + __('Jan'), + /* l10n: Short month name */ + __('Feb'), + /* l10n: Short month name */ + __('Mar'), + /* l10n: Short month name */ + __('Apr'), + /* l10n: Short month name */ + _pgettext('Short month name', 'May'), + /* l10n: Short month name */ + __('Jun'), + /* l10n: Short month name */ + __('Jul'), + /* l10n: Short month name */ + __('Aug'), + /* l10n: Short month name */ + __('Sep'), + /* l10n: Short month name */ + __('Oct'), + /* l10n: Short month name */ + __('Nov'), + /* l10n: Short month name */ + __('Dec') + ) +); +Sanitize::printJsValue( + "$.datepicker.regional['']['dayNames']", + array( + __('Sunday'), + __('Monday'), + __('Tuesday'), + __('Wednesday'), + __('Thursday'), + __('Friday'), + __('Saturday') + ) +); +Sanitize::printJsValue( + "$.datepicker.regional['']['dayNamesShort']", + array( + /* l10n: Short week day name */ + __('Sun'), + /* l10n: Short week day name */ + __('Mon'), + /* l10n: Short week day name */ + __('Tue'), + /* l10n: Short week day name */ + __('Wed'), + /* l10n: Short week day name */ + __('Thu'), + /* l10n: Short week day name */ + __('Fri'), + /* l10n: Short week day name */ + __('Sat') + ) +); +Sanitize::printJsValue( + "$.datepicker.regional['']['dayNamesMin']", + array( + /* l10n: Minimal week day name */ + __('Su'), + /* l10n: Minimal week day name */ + __('Mo'), + /* l10n: Minimal week day name */ + __('Tu'), + /* l10n: Minimal week day name */ + __('We'), + /* l10n: Minimal week day name */ + __('Th'), + /* l10n: Minimal week day name */ + __('Fr'), + /* l10n: Minimal week day name */ + __('Sa') + ) +); +/* l10n: Column header for week of the year in calendar */ +Sanitize::printJsValue("$.datepicker.regional['']['weekHeader']", __('Wk')); + +Sanitize::printJsValue( + "$.datepicker.regional['']['showMonthAfterYear']", + /* l10n: Month-year order for calendar, use either "calendar-month-year" + * or "calendar-year-month". + */ + (__('calendar-month-year') == 'calendar-year-month') +); +/* l10n: Year suffix for calendar, "none" is empty. */ +$year_suffix = _pgettext('Year suffix', 'none'); +Sanitize::printJsValue( + "$.datepicker.regional['']['yearSuffix']", + ($year_suffix == 'none' ? '' : $year_suffix) +); +?> +$.extend($.datepicker._defaults, $.datepicker.regional['']); +} /* if ($.datepicker) */ + + +$.extend($.timepicker._defaults, $.timepicker.regional['']); +} /* if ($.timepicker) */ + + diff --git a/php/apps/phpmyadmin49/html/js/microhistory.js b/php/apps/phpmyadmin49/html/js/microhistory.js new file mode 100644 index 00000000..46c98a88 --- /dev/null +++ b/php/apps/phpmyadmin49/html/js/microhistory.js @@ -0,0 +1,335 @@ +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * An implementation of a client-side page cache. + * This object also uses the cache to provide a simple microhistory, + * that is the ability to use the back and forward buttons in the browser + */ +PMA_MicroHistory = { + /** + * @var int The maximum number of pages to keep in the cache + */ + MAX: 6, + /** + * @var object A hash used to prime the cache with data about the initially + * loaded page. This is set in the footer, and then loaded + * by a double-queued event further down this file. + */ + primer: {}, + /** + * @var array Stores the content of the cached pages + */ + pages: [], + /** + * @var int The index of the currently loaded page + * This is used to know at which point in the history we are + */ + current: 0, + /** + * Saves a new page in the cache + * + * @param string hash The hash part of the url that is being loaded + * @param array scripts A list of scripts that is required for the page + * @param string menu A hash that links to a menu stored + * in a dedicated menu cache + * @param array params A list of parameters used by PMA_commonParams() + * @param string rel A relationship to the current page: + * 'samepage': Forces the response to be treated as + * the same page as the current one + * 'newpage': Forces the response to be treated as + * a new page + * undefined: Default behaviour, 'samepage' if the + * selflinks of the two pages are the same. + * 'newpage' otherwise + * + * @return void + */ + add: function (hash, scripts, menu, params, rel) { + if (this.pages.length > PMA_MicroHistory.MAX) { + // Trim the cache, to the maximum number of allowed entries + // This way we will have a cached menu for every page + for (var i = 0; i < this.pages.length - this.MAX; i++) { + delete this.pages[i]; + } + } + while (this.current < this.pages.length) { + // trim the cache if we went back in the history + // and are now going forward again + this.pages.pop(); + } + if (rel === 'newpage' || + ( + typeof rel === 'undefined' && ( + typeof this.pages[this.current - 1] === 'undefined' || + this.pages[this.current - 1].hash !== hash + ) + ) + ) { + this.pages.push({ + hash: hash, + content: $('#page_content').html(), + scripts: scripts, + selflink: $('#selflink').html(), + menu: menu, + params: params + }); + PMA_SetUrlHash(this.current, hash); + this.current++; + } + }, + /** + * Restores a page from the cache. This is called when the hash + * part of the url changes and it's structure appears to be valid + * + * @param string index Which page from the history to load + * + * @return void + */ + navigate: function (index) { + if (typeof this.pages[index] === 'undefined' || + typeof this.pages[index].content === 'undefined' || + typeof this.pages[index].menu === 'undefined' || + ! PMA_MicroHistory.menus.get(this.pages[index].menu) + ) { + PMA_ajaxShowMessage( + '
        ' + PMA_messages.strInvalidPage + '
        ', + false + ); + } else { + AJAX.active = true; + var record = this.pages[index]; + AJAX.scriptHandler.reset(function () { + $('#page_content').html(record.content); + $('#selflink').html(record.selflink); + PMA_MicroHistory.menus.replace(PMA_MicroHistory.menus.get(record.menu)); + PMA_commonParams.setAll(record.params); + AJAX.scriptHandler.load(record.scripts); + PMA_MicroHistory.current = ++index; + }); + } + }, + /** + * Resaves the content of the current page in the cache. + * Necessary in order not to show the user some outdated version of the page + * + * @return void + */ + update: function () { + var page = this.pages[this.current - 1]; + if (page) { + page.content = $('#page_content').html(); + } + }, + /** + * @var object Dedicated menu cache + */ + menus: { + /** + * Returns the number of items in an associative array + * + * @return int + */ + size: function (obj) { + var size = 0; + var key; + for (key in obj) { + if (obj.hasOwnProperty(key)) { + size++; + } + } + return size; + }, + /** + * @var hash Stores the content of the cached menus + */ + data: {}, + /** + * Saves a new menu in the cache + * + * @param string hash The hash (trimmed md5) of the menu to be saved + * @param string content The HTML code of the menu to be saved + * + * @return void + */ + add: function (hash, content) { + if (this.size(this.data) > PMA_MicroHistory.MAX) { + // when the cache grows, we remove the oldest entry + var oldest; + var key; + var init = 0; + for (var i in this.data) { + if (this.data[i]) { + if (! init || this.data[i].timestamp.getTime() < oldest.getTime()) { + oldest = this.data[i].timestamp; + key = i; + init = 1; + } + } + } + delete this.data[key]; + } + this.data[hash] = { + content: content, + timestamp: new Date() + }; + }, + /** + * Retrieves a menu given its hash + * + * @param string hash The hash of the menu to be retrieved + * + * @return string + */ + get: function (hash) { + if (this.data[hash]) { + return this.data[hash].content; + } else { + // This should never happen as long as the number of stored menus + // is larger or equal to the number of pages in the page cache + return ''; + } + }, + /** + * Prepares part of the parameter string used during page requests, + * this is necessary to tell the server which menus we have in the cache + * + * @return string + */ + getRequestParam: function () { + var param = ''; + var menuHashes = []; + for (var i in this.data) { + menuHashes.push(i); + } + var menuHashesParam = menuHashes.join('-'); + if (menuHashesParam) { + param = PMA_commonParams.get('arg_separator') + 'menuHashes=' + menuHashesParam; + } + return param; + }, + /** + * Replaces the menu with new content + * + * @return void + */ + replace: function (content) { + $('#floating_menubar').html(content) + // Remove duplicate wrapper + // TODO: don't send it in the response + .children().first().remove(); + $('#topmenu').menuResizer(PMA_mainMenuResizerCallback); + } + } +}; + +/** + * URL hash management module. + * Allows direct bookmarking and microhistory. + */ +PMA_SetUrlHash = (function (jQuery, window) { + 'use strict'; + /** + * Indictaes whether we have already completed + * the initialisation of the hash + * + * @access private + */ + var ready = false; + /** + * Stores a hash that needed to be set when we were not ready + * + * @access private + */ + var savedHash = ''; + /** + * Flag to indicate if the change of hash was triggered + * by a user pressing the back/forward button or if + * the change was triggered internally + * + * @access private + */ + var userChange = true; + + // Fix favicon disappearing in Firefox when setting location.hash + function resetFavicon () { + if (navigator.userAgent.indexOf('Firefox') > -1) { + // Move the link tags for the favicon to the bottom + // of the head element to force a reload of the favicon + $('head > link[href="favicon\\.ico"]').appendTo('head'); + } + } + + /** + * Sets the hash part of the URL + * + * @access public + */ + function setUrlHash (index, hash) { + /* + * Known problem: + * Setting hash leads to reload in webkit: + * http://www.quirksmode.org/bugreports/archives/2005/05/Safari_13_visual_anomaly_with_windowlocationhref.html + * + * so we expect that users are not running an ancient Safari version + */ + + userChange = false; + if (ready) { + window.location.hash = 'PMAURL-' + index + ':' + hash; + resetFavicon(); + } else { + savedHash = 'PMAURL-' + index + ':' + hash; + } + } + /** + * Start initialisation + */ + var urlhash = window.location.hash; + if (urlhash.substring(0, 8) === '#PMAURL-') { + // We have a valid hash, let's redirect the user + // to the page that it's pointing to + var colon_position = urlhash.indexOf(':'); + var questionmark_position = urlhash.indexOf('?'); + if (colon_position !== -1 && questionmark_position !== -1 && colon_position < questionmark_position) { + var hash_url = urlhash.substring(colon_position + 1, questionmark_position); + if (PMA_gotoWhitelist.indexOf(hash_url) !== -1) { + window.location = urlhash.substring( + colon_position + 1 + ); + } + } + } else { + // We don't have a valid hash, so we'll set it up + // when the page finishes loading + jQuery(function () { + /* Check if we should set URL */ + if (savedHash !== '') { + window.location.hash = savedHash; + savedHash = ''; + resetFavicon(); + } + // Indicate that we're done initialising + ready = true; + }); + } + /** + * Register an event handler for when the url hash changes + */ + jQuery(function () { + jQuery(window).hashchange(function () { + if (userChange === false) { + // Ignore internally triggered hash changes + userChange = true; + } else if (/^#PMAURL-\d+:/.test(window.location.hash)) { + // Change page if the hash changed was triggered by a user action + var index = window.location.hash.substring( + 8, window.location.hash.indexOf(':') + ); + PMA_MicroHistory.navigate(index); + } + }); + }); + /** + * Publicly exposes a reference to the otherwise private setUrlHash function + */ + return setUrlHash; +}(jQuery, window)); diff --git a/php/apps/phpmyadmin49/html/js/multi_column_sort.js b/php/apps/phpmyadmin49/html/js/multi_column_sort.js new file mode 100644 index 00000000..cc9b9215 --- /dev/null +++ b/php/apps/phpmyadmin49/html/js/multi_column_sort.js @@ -0,0 +1,84 @@ +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * @fileoverview Implements the shiftkey + click remove column + * from order by clause funcationality + * @name columndelete + * + * @requires jQuery + */ + +function captureURL (url) { + var URL = {}; + url = '' + url; + // Exclude the url part till HTTP + url = url.substr(url.search('sql.php'), url.length); + // The url part between ORDER BY and &session_max_rows needs to be replaced. + URL.head = url.substr(0, url.indexOf('ORDER+BY') + 9); + URL.tail = url.substr(url.indexOf('&session_max_rows'), url.length); + return URL; +} + +/** + * This function is for navigating to the generated URL + * + * @param object target HTMLAnchor element + * @param object parent HTMLDom Object + */ + +function removeColumnFromMultiSort (target, parent) { + var URL = captureURL(target); + var begin = target.indexOf('ORDER+BY') + 8; + var end = target.indexOf(PMA_commonParams.get('arg_separator') + 'session_max_rows'); + // get the names of the columns involved + var between_part = target.substr(begin, end - begin); + var columns = between_part.split('%2C+'); + // If the given column is not part of the order clause exit from this function + var index = parent.find('small').length ? parent.find('small').text() : ''; + if (index === '') { + return ''; + } + // Remove the current clicked column + columns.splice(index - 1, 1); + // If all the columns have been removed dont submit a query with nothing + // After order by clause. + if (columns.length === 0) { + var head = URL.head; + head = head.slice(0,head.indexOf('ORDER+BY')); + URL.head = head; + // removing the last sort order should have priority over what + // is remembered via the RememberSorting directive + URL.tail += PMA_commonParams.get('arg_separator') + 'discard_remembered_sort=1'; + } + URL.head = URL.head.substring(URL.head.indexOf('?') + 1); + var middle_part = columns.join('%2C+'); + params = URL.head + middle_part + URL.tail; + return params; +} + +AJAX.registerOnload('keyhandler.js', function () { + $('th.draggable.column_heading.pointer.marker a').on('click', function (event) { + var url = $(this).parent().find('input').val(); + var argsep = PMA_commonParams.get('arg_separator'); + if (event.ctrlKey || event.altKey) { + event.preventDefault(); + var params = removeColumnFromMultiSort(url, $(this).parent()); + if (params) { + AJAX.source = $(this); + PMA_ajaxShowMessage(); + params += argsep + 'ajax_request=true' + argsep + 'ajax_page_request=true'; + $.post('sql.php', params, AJAX.responseHandler); + } + } else if (event.shiftKey) { + event.preventDefault(); + AJAX.source = $(this); + PMA_ajaxShowMessage(); + var params = url.substring(url.indexOf('?') + 1); + params += argsep + 'ajax_request=true' + argsep + 'ajax_page_request=true'; + $.post('sql.php', params, AJAX.responseHandler); + } + }); +}); + +AJAX.registerTeardown('keyhandler.js', function () { + $(document).off('click', 'th.draggable.column_heading.pointer.marker a'); +}); diff --git a/php/apps/phpmyadmin49/html/js/navigation.js b/php/apps/phpmyadmin49/html/js/navigation.js new file mode 100644 index 00000000..6f0d05b8 --- /dev/null +++ b/php/apps/phpmyadmin49/html/js/navigation.js @@ -0,0 +1,1661 @@ +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * function used in or for navigation panel + * + * @package phpMyAdmin-Navigation + */ + +/** + * updates the tree state in sessionStorage + * + * @returns void + */ +function navTreeStateUpdate () { + // update if session storage is supported + if (isStorageSupported('sessionStorage')) { + var storage = window.sessionStorage; + // try catch necessary here to detect whether + // content to be stored exceeds storage capacity + try { + storage.setItem('navTreePaths', JSON.stringify(traverseNavigationForPaths())); + storage.setItem('server', PMA_commonParams.get('server')); + storage.setItem('token', PMA_commonParams.get('token')); + } catch (error) { + // storage capacity exceeded & old navigation tree + // state is no more valid, so remove it + storage.removeItem('navTreePaths'); + storage.removeItem('server'); + storage.removeItem('token'); + } + } +} + + +/** + * updates the filter state in sessionStorage + * + * @returns void + */ +function navFilterStateUpdate (filterName, filterValue) { + if (isStorageSupported('sessionStorage')) { + var storage = window.sessionStorage; + try { + var currentFilter = $.extend({}, JSON.parse(storage.getItem('navTreeSearchFilters'))); + var filter = {}; + filter[filterName] = filterValue; + currentFilter = $.extend(currentFilter, filter); + storage.setItem('navTreeSearchFilters', JSON.stringify(currentFilter)); + } catch (error) { + storage.removeItem('navTreeSearchFilters'); + } + } +} + + +/** + * restores the filter state on navigation reload + * + * @returns void + */ +function navFilterStateRestore () { + if (isStorageSupported('sessionStorage') + && typeof window.sessionStorage.navTreeSearchFilters !== 'undefined' + ) { + var searchClauses = JSON.parse(window.sessionStorage.navTreeSearchFilters); + if (Object.keys(searchClauses).length < 1) { + return; + } + // restore database filter if present and not empty + if (searchClauses.hasOwnProperty('dbFilter') + && searchClauses.dbFilter.length + ) { + $obj = $('#pma_navigation_tree'); + if (! $obj.data('fastFilter')) { + $obj.data( + 'fastFilter', + new PMA_fastFilter.filter($obj, '') + ); + } + $obj.find('li.fast_filter.db_fast_filter input.searchClause') + .val(searchClauses.dbFilter) + .trigger('keyup'); + } + // find all table filters present in the tree + $tableFilters = $('#pma_navigation_tree li.database') + .children('div.list_container') + .find('li.fast_filter input.searchClause'); + // restore table filters + $tableFilters.each(function () { + $obj = $(this).closest('div.list_container'); + // aPath associated with this filter + var filterName = $(this).siblings('input[name=aPath]').val(); + // if this table's filter has a state stored in storage + if (searchClauses.hasOwnProperty(filterName) + && searchClauses[filterName].length + ) { + // clear state if item is not visible, + // happens when table filter becomes invisible + // as db filter has already been applied + if (! $obj.is(':visible')) { + navFilterStateUpdate(filterName, ''); + return true; + } + if (! $obj.data('fastFilter')) { + $obj.data( + 'fastFilter', + new PMA_fastFilter.filter($obj, '') + ); + } + $(this).val(searchClauses[filterName]) + .trigger('keyup'); + } + }); + } +} + +/** + * Loads child items of a node and executes a given callback + * + * @param isNode + * @param $expandElem expander + * @param callback callback function + * + * @returns void + */ +function loadChildNodes (isNode, $expandElem, callback) { + var $destination = null; + var params = null; + + if (isNode) { + if (!$expandElem.hasClass('expander')) { + return; + } + $destination = $expandElem.closest('li'); + params = { + aPath: $expandElem.find('span.aPath').text(), + vPath: $expandElem.find('span.vPath').text(), + pos: $expandElem.find('span.pos').text(), + pos2_name: $expandElem.find('span.pos2_name').text(), + pos2_value: $expandElem.find('span.pos2_value').text(), + searchClause: '', + searchClause2: '' + }; + if ($expandElem.closest('ul').hasClass('search_results')) { + params.searchClause = PMA_fastFilter.getSearchClause(); + params.searchClause2 = PMA_fastFilter.getSearchClause2($expandElem); + } + } else { + $destination = $('#pma_navigation_tree_content'); + params = { + aPath: $expandElem.attr('aPath'), + vPath: $expandElem.attr('vPath'), + pos: $expandElem.attr('pos'), + pos2_name: '', + pos2_value: '', + searchClause: '', + searchClause2: '' + }; + } + + var url = $('#pma_navigation').find('a.navigation_url').attr('href'); + $.get(url, params, function (data) { + if (typeof data !== 'undefined' && data.success === true) { + $destination.find('div.list_container').remove(); // FIXME: Hack, there shouldn't be a list container there + if (isNode) { + $destination.append(data.message); + $expandElem.addClass('loaded'); + } else { + $destination.html(data.message); + $destination.children() + .first() + .css({ + border: '0px', + margin: '0em', + padding : '0em' + }) + .slideDown('slow'); + } + if (data._errors) { + var $errors = $(data._errors); + if ($errors.children().length > 0) { + $('#pma_errors').replaceWith(data._errors); + } + } + if (callback && typeof callback === 'function') { + callback(data); + } + } else if (data.redirect_flag === '1') { + if (window.location.href.indexOf('?') === -1) { + window.location.href += '?session_expired=1'; + } else { + window.location.href += PMA_commonParams.get('arg_separator') + 'session_expired=1'; + } + window.location.reload(); + } else { + var $throbber = $expandElem.find('img.throbber'); + $throbber.hide(); + var $icon = $expandElem.find('img.ic_b_plus'); + $icon.show(); + PMA_ajaxShowMessage(data.error, false); + } + }); +} + +/** + * Collapses a node in navigation tree. + * + * @param $expandElem expander + * + * @returns void + */ +function collapseTreeNode ($expandElem) { + var $children = $expandElem.closest('li').children('div.list_container'); + var $icon = $expandElem.find('img'); + if ($expandElem.hasClass('loaded')) { + if ($icon.is('.ic_b_minus')) { + $icon.removeClass('ic_b_minus').addClass('ic_b_plus'); + $children.slideUp('fast'); + } + } + $expandElem.blur(); + $children.promise().done(navTreeStateUpdate); +} + +/** + * Traverse the navigation tree backwards to generate all the actual + * and virtual paths, as well as the positions in the pagination at + * various levels, if necessary. + * + * @return Object + */ +function traverseNavigationForPaths () { + var params = { + pos: $('#pma_navigation_tree').find('div.dbselector select').val() + }; + if ($('#navi_db_select').length) { + return params; + } + var count = 0; + $('#pma_navigation_tree').find('a.expander:visible').each(function () { + if ($(this).find('img').is('.ic_b_minus') && + $(this).closest('li').find('div.list_container .ic_b_minus').length === 0 + ) { + params['n' + count + '_aPath'] = $(this).find('span.aPath').text(); + params['n' + count + '_vPath'] = $(this).find('span.vPath').text(); + + var pos2_name = $(this).find('span.pos2_name').text(); + if (! pos2_name) { + pos2_name = $(this) + .parent() + .parent() + .find('span.pos2_name:last') + .text(); + } + var pos2_value = $(this).find('span.pos2_value').text(); + if (! pos2_value) { + pos2_value = $(this) + .parent() + .parent() + .find('span.pos2_value:last') + .text(); + } + + params['n' + count + '_pos2_name'] = pos2_name; + params['n' + count + '_pos2_value'] = pos2_value; + + params['n' + count + '_pos3_name'] = $(this).find('span.pos3_name').text(); + params['n' + count + '_pos3_value'] = $(this).find('span.pos3_value').text(); + count++; + } + }); + return params; +} + +/** + * Executed on page load + */ +$(function () { + if (! $('#pma_navigation').length) { + // Don't bother running any code if the navigation is not even on the page + return; + } + + // Do not let the page reload on submitting the fast filter + $(document).on('submit', '.fast_filter', function (event) { + event.preventDefault(); + }); + + // Fire up the resize handlers + new ResizeHandler(); + + /** + * opens/closes (hides/shows) tree elements + * loads data via ajax + */ + $(document).on('click', '#pma_navigation_tree a.expander', function (event) { + event.preventDefault(); + event.stopImmediatePropagation(); + var $icon = $(this).find('img'); + if ($icon.is('.ic_b_plus')) { + expandTreeNode($(this)); + } else { + collapseTreeNode($(this)); + } + }); + + /** + * Register event handler for click on the reload + * navigation icon at the top of the panel + */ + $(document).on('click', '#pma_navigation_reload', function (event) { + event.preventDefault(); + + // Find the loading symbol and show it + var $icon_throbber_src = $('#pma_navigation').find('.throbber'); + $icon_throbber_src.show(); + // TODO Why is a loading symbol both hidden, and invisible? + $icon_throbber_src.css('visibility', ''); + + // Callback to be used to hide the loading symbol when done reloading + function hideNav () { + $icon_throbber_src.hide(); + } + + // Reload the navigation + PMA_reloadNavigation(hideNav); + }); + + $(document).on('change', '#navi_db_select', function (event) { + if (! $(this).val()) { + PMA_commonParams.set('db', ''); + PMA_reloadNavigation(); + } + $(this).closest('form').trigger('submit'); + }); + + /** + * Register event handler for click on the collapse all + * navigation icon at the top of the navigation tree + */ + $(document).on('click', '#pma_navigation_collapse', function (event) { + event.preventDefault(); + $('#pma_navigation_tree').find('a.expander').each(function () { + var $icon = $(this).find('img'); + if ($icon.is('.ic_b_minus')) { + $(this).click(); + } + }); + }); + + /** + * Register event handler to toggle + * the 'link with main panel' icon on mouseenter. + */ + $(document).on('mouseenter', '#pma_navigation_sync', function (event) { + event.preventDefault(); + var synced = $('#pma_navigation_tree').hasClass('synced'); + var $img = $('#pma_navigation_sync').children('img'); + if (synced) { + $img.removeClass('ic_s_link').addClass('ic_s_unlink'); + } else { + $img.removeClass('ic_s_unlink').addClass('ic_s_link'); + } + }); + + /** + * Register event handler to toggle + * the 'link with main panel' icon on mouseout. + */ + $(document).on('mouseout', '#pma_navigation_sync', function (event) { + event.preventDefault(); + var synced = $('#pma_navigation_tree').hasClass('synced'); + var $img = $('#pma_navigation_sync').children('img'); + if (synced) { + $img.removeClass('ic_s_unlink').addClass('ic_s_link'); + } else { + $img.removeClass('ic_s_link').addClass('ic_s_unlink'); + } + }); + + /** + * Register event handler to toggle + * the linking with main panel behavior + */ + $(document).on('click', '#pma_navigation_sync', function (event) { + event.preventDefault(); + var synced = $('#pma_navigation_tree').hasClass('synced'); + var $img = $('#pma_navigation_sync').children('img'); + if (synced) { + $img + .removeClass('ic_s_unlink') + .addClass('ic_s_link') + .attr('alt', PMA_messages.linkWithMain) + .attr('title', PMA_messages.linkWithMain); + $('#pma_navigation_tree') + .removeClass('synced') + .find('li.selected') + .removeClass('selected'); + } else { + $img + .removeClass('ic_s_link') + .addClass('ic_s_unlink') + .attr('alt', PMA_messages.unlinkWithMain) + .attr('title', PMA_messages.unlinkWithMain); + $('#pma_navigation_tree').addClass('synced'); + PMA_showCurrentNavigation(); + } + }); + + /** + * Bind all "fast filter" events + */ + $(document).on('click', '#pma_navigation_tree li.fast_filter span', PMA_fastFilter.events.clear); + $(document).on('focus', '#pma_navigation_tree li.fast_filter input.searchClause', PMA_fastFilter.events.focus); + $(document).on('blur', '#pma_navigation_tree li.fast_filter input.searchClause', PMA_fastFilter.events.blur); + $(document).on('keyup', '#pma_navigation_tree li.fast_filter input.searchClause', PMA_fastFilter.events.keyup); + + /** + * Ajax handler for pagination + */ + $(document).on('click', '#pma_navigation_tree div.pageselector a.ajax', function (event) { + event.preventDefault(); + PMA_navigationTreePagination($(this)); + }); + + /** + * Node highlighting + */ + $(document).on( + 'mouseover', + '#pma_navigation_tree.highlight li:not(.fast_filter)', + function () { + if ($('li:visible', this).length === 0) { + $(this).addClass('activePointer'); + } + } + ); + $(document).on( + 'mouseout', + '#pma_navigation_tree.highlight li:not(.fast_filter)', + function () { + $(this).removeClass('activePointer'); + } + ); + + /** Create a Routine, Trigger or Event */ + $(document).on('click', 'li.new_procedure a.ajax, li.new_function a.ajax', function (event) { + event.preventDefault(); + var dialog = new RTE.object('routine'); + dialog.editorDialog(1, $(this)); + }); + $(document).on('click', 'li.new_trigger a.ajax', function (event) { + event.preventDefault(); + var dialog = new RTE.object('trigger'); + dialog.editorDialog(1, $(this)); + }); + $(document).on('click', 'li.new_event a.ajax', function (event) { + event.preventDefault(); + var dialog = new RTE.object('event'); + dialog.editorDialog(1, $(this)); + }); + + /** Edit Routines, Triggers or Events */ + $(document).on('click', 'li.procedure > a.ajax, li.function > a.ajax', function (event) { + event.preventDefault(); + var dialog = new RTE.object('routine'); + dialog.editorDialog(0, $(this)); + }); + $(document).on('click', 'li.trigger > a.ajax', function (event) { + event.preventDefault(); + var dialog = new RTE.object('trigger'); + dialog.editorDialog(0, $(this)); + }); + $(document).on('click', 'li.event > a.ajax', function (event) { + event.preventDefault(); + var dialog = new RTE.object('event'); + dialog.editorDialog(0, $(this)); + }); + + /** Execute Routines */ + $(document).on('click', 'li.procedure div a.ajax img,' + + ' li.function div a.ajax img', function (event) { + event.preventDefault(); + var dialog = new RTE.object('routine'); + dialog.executeDialog($(this).parent()); + }); + /** Export Triggers and Events */ + $(document).on('click', 'li.trigger div:eq(1) a.ajax img,' + + ' li.event div:eq(1) a.ajax img', function (event) { + event.preventDefault(); + var dialog = new RTE.object(); + dialog.exportDialog($(this).parent()); + }); + + /** New index */ + $(document).on('click', '#pma_navigation_tree li.new_index a.ajax', function (event) { + event.preventDefault(); + var url = $(this).attr('href').substr( + $(this).attr('href').indexOf('?') + 1 + ) + PMA_commonParams.get('arg_separator') + 'ajax_request=true'; + var title = PMA_messages.strAddIndex; + indexEditorDialog(url, title); + }); + + /** Edit index */ + $(document).on('click', 'li.index a.ajax', function (event) { + event.preventDefault(); + var url = $(this).attr('href').substr( + $(this).attr('href').indexOf('?') + 1 + ) + PMA_commonParams.get('arg_separator') + 'ajax_request=true'; + var title = PMA_messages.strEditIndex; + indexEditorDialog(url, title); + }); + + /** New view */ + $(document).on('click', 'li.new_view a.ajax', function (event) { + event.preventDefault(); + PMA_createViewDialog($(this)); + }); + + /** Hide navigation tree item */ + $(document).on('click', 'a.hideNavItem.ajax', function (event) { + event.preventDefault(); + var argSep = PMA_commonParams.get('arg_separator'); + var params = $(this).getPostData(); + params += argSep + 'ajax_request=true' + argSep + 'server=' + PMA_commonParams.get('server'); + $.ajax({ + type: 'POST', + data: params, + url: $(this).attr('href'), + success: function (data) { + if (typeof data !== 'undefined' && data.success === true) { + PMA_reloadNavigation(); + } else { + PMA_ajaxShowMessage(data.error); + } + } + }); + }); + + /** Display a dialog to choose hidden navigation items to show */ + $(document).on('click', 'a.showUnhide.ajax', function (event) { + event.preventDefault(); + var $msg = PMA_ajaxShowMessage(); + var argSep = PMA_commonParams.get('arg_separator'); + var params = $(this).getPostData(); + params += argSep + 'ajax_request=true'; + $.post($(this).attr('href'), params, function (data) { + if (typeof data !== 'undefined' && data.success === true) { + PMA_ajaxRemoveMessage($msg); + var buttonOptions = {}; + buttonOptions[PMA_messages.strClose] = function () { + $(this).dialog('close'); + }; + $('
        ') + .attr('id', 'unhideNavItemDialog') + .append(data.message) + .dialog({ + width: 400, + minWidth: 200, + modal: true, + buttons: buttonOptions, + title: PMA_messages.strUnhideNavItem, + close: function () { + $(this).remove(); + } + }); + } else { + PMA_ajaxShowMessage(data.error); + } + }); + }); + + /** Show a hidden navigation tree item */ + $(document).on('click', 'a.unhideNavItem.ajax', function (event) { + event.preventDefault(); + var $tr = $(this).parents('tr'); + var $msg = PMA_ajaxShowMessage(); + var argSep = PMA_commonParams.get('arg_separator'); + var params = $(this).getPostData(); + params += argSep + 'ajax_request=true' + argSep + 'server=' + PMA_commonParams.get('server'); + $.ajax({ + type: 'POST', + data: params, + url: $(this).attr('href'), + success: function (data) { + PMA_ajaxRemoveMessage($msg); + if (typeof data !== 'undefined' && data.success === true) { + $tr.remove(); + PMA_reloadNavigation(); + } else { + PMA_ajaxShowMessage(data.error); + } + } + }); + }); + + // Add/Remove favorite table using Ajax. + $(document).on('click', '.favorite_table_anchor', function (event) { + event.preventDefault(); + $self = $(this); + var anchor_id = $self.attr('id'); + if ($self.data('favtargetn') !== null) { + if ($('a[data-favtargets="' + $self.data('favtargetn') + '"]').length > 0) { + $('a[data-favtargets="' + $self.data('favtargetn') + '"]').trigger('click'); + return; + } + } + + $.ajax({ + url: $self.attr('href'), + cache: false, + type: 'POST', + data: { + favorite_tables: (isStorageSupported('localStorage') && typeof window.localStorage.favorite_tables !== 'undefined') + ? window.localStorage.favorite_tables + : '', + server: PMA_commonParams.get('server'), + }, + success: function (data) { + if (data.changes) { + $('#pma_favorite_list').html(data.list); + $('#' + anchor_id).parent().html(data.anchor); + PMA_tooltip( + $('#' + anchor_id), + 'a', + $('#' + anchor_id).attr('title') + ); + // Update localStorage. + if (isStorageSupported('localStorage')) { + window.localStorage.favorite_tables = data.favorite_tables; + } + } else { + PMA_ajaxShowMessage(data.message); + } + } + }); + }); + // Check if session storage is supported + if (isStorageSupported('sessionStorage')) { + var storage = window.sessionStorage; + // remove tree from storage if Navi_panel config form is submitted + $(document).on('submit', 'form.config-form', function (event) { + storage.removeItem('navTreePaths'); + }); + // Initialize if no previous state is defined + if ($('#pma_navigation_tree_content').length && + typeof storage.navTreePaths === 'undefined' + ) { + PMA_reloadNavigation(); + } else if (PMA_commonParams.get('server') === storage.server && + PMA_commonParams.get('token') === storage.token + ) { + // Reload the tree to the state before page refresh + PMA_reloadNavigation(navFilterStateRestore, JSON.parse(storage.navTreePaths)); + } else { + // If the user is different + navTreeStateUpdate(); + } + } +}); + +/** + * Expands a node in navigation tree. + * + * @param $expandElem expander + * @param callback callback function + * + * @returns void + */ +function expandTreeNode ($expandElem, callback) { + var $children = $expandElem.closest('li').children('div.list_container'); + var $icon = $expandElem.find('img'); + if ($expandElem.hasClass('loaded')) { + if ($icon.is('.ic_b_plus')) { + $icon.removeClass('ic_b_plus').addClass('ic_b_minus'); + $children.slideDown('fast'); + } + if (callback && typeof callback === 'function') { + callback.call(); + } + $children.promise().done(navTreeStateUpdate); + } else { + var $throbber = $('#pma_navigation').find('.throbber') + .first() + .clone() + .css({ visibility: 'visible', display: 'block' }) + .click(false); + $icon.hide(); + $throbber.insertBefore($icon); + + loadChildNodes(true, $expandElem, function (data) { + if (typeof data !== 'undefined' && data.success === true) { + var $destination = $expandElem.closest('li'); + $icon.removeClass('ic_b_plus').addClass('ic_b_minus'); + $children = $destination.children('div.list_container'); + $children.slideDown('fast'); + if ($destination.find('ul > li').length === 1) { + $destination.find('ul > li') + .find('a.expander.container') + .click(); + } + if (callback && typeof callback === 'function') { + callback.call(); + } + PMA_showFullName($destination); + } else { + PMA_ajaxShowMessage(data.error, false); + } + $icon.show(); + $throbber.remove(); + $children.promise().done(navTreeStateUpdate); + }); + } + $expandElem.blur(); +} + +/** + * Auto-scrolls the newly chosen database + * + * @param object $element The element to set to view + * @param boolean $forceToTop Whether to force scroll to top + * + */ +function scrollToView ($element, $forceToTop) { + navFilterStateRestore(); + var $container = $('#pma_navigation_tree_content'); + var elemTop = $element.offset().top - $container.offset().top; + var textHeight = 20; + var scrollPadding = 20; // extra padding from top of bottom when scrolling to view + if (elemTop < 0 || $forceToTop) { + $container.stop().animate({ + scrollTop: elemTop + $container.scrollTop() - scrollPadding + }); + } else if (elemTop + textHeight > $container.height()) { + $container.stop().animate({ + scrollTop: elemTop + textHeight - $container.height() + $container.scrollTop() + scrollPadding + }); + } +} + +/** + * Expand the navigation and highlight the current database or table/view + * + * @returns void + */ +function PMA_showCurrentNavigation () { + var db = PMA_commonParams.get('db'); + var table = PMA_commonParams.get('table'); + $('#pma_navigation_tree') + .find('li.selected') + .removeClass('selected'); + if (db) { + var $dbItem = findLoadedItem( + $('#pma_navigation_tree').find('> div'), db, 'database', !table + ); + if ($('#navi_db_select').length && + $('option:selected', $('#navi_db_select')).length + ) { + if (! PMA_selectCurrentDb()) { + return; + } + // If loaded database in navigation is not same as current one + if ($('#pma_navigation_tree_content').find('span.loaded_db:first').text() + !== $('#navi_db_select').val() + ) { + loadChildNodes(false, $('option:selected', $('#navi_db_select')), function (data) { + handleTableOrDb(table, $('#pma_navigation_tree_content')); + var $children = $('#pma_navigation_tree_content').children('div.list_container'); + $children.promise().done(navTreeStateUpdate); + }); + } else { + handleTableOrDb(table, $('#pma_navigation_tree_content')); + } + } else if ($dbItem) { + var $expander = $dbItem.children('div:first').children('a.expander'); + // if not loaded or loaded but collapsed + if (! $expander.hasClass('loaded') || + $expander.find('img').is('.ic_b_plus') + ) { + expandTreeNode($expander, function () { + handleTableOrDb(table, $dbItem); + }); + } else { + handleTableOrDb(table, $dbItem); + } + } + } else if ($('#navi_db_select').length && $('#navi_db_select').val()) { + $('#navi_db_select').val('').hide().trigger('change'); + } + PMA_showFullName($('#pma_navigation_tree')); + + function handleTableOrDb (table, $dbItem) { + if (table) { + loadAndHighlightTableOrView($dbItem, table); + } else { + var $container = $dbItem.children('div.list_container'); + var $tableContainer = $container.children('ul').children('li.tableContainer'); + if ($tableContainer.length > 0) { + var $expander = $tableContainer.children('div:first').children('a.expander'); + $tableContainer.addClass('selected'); + expandTreeNode($expander, function () { + scrollToView($dbItem, true); + }); + } else { + scrollToView($dbItem, true); + } + } + } + + function findLoadedItem ($container, name, clazz, doSelect) { + var ret = false; + $container.children('ul').children('li').each(function () { + var $li = $(this); + // this is a navigation group, recurse + if ($li.is('.navGroup')) { + var $container = $li.children('div.list_container'); + var $childRet = findLoadedItem( + $container, name, clazz, doSelect + ); + if ($childRet) { + ret = $childRet; + return false; + } + } else { // this is a real navigation item + // name and class matches + if (((clazz && $li.is('.' + clazz)) || ! clazz) && + $li.children('a').text() === name) { + if (doSelect) { + $li.addClass('selected'); + } + // taverse up and expand and parent navigation groups + $li.parents('.navGroup').each(function () { + var $cont = $(this).children('div.list_container'); + if (! $cont.is(':visible')) { + $(this) + .children('div:first') + .children('a.expander') + .click(); + } + }); + ret = $li; + return false; + } + } + }); + return ret; + } + + function loadAndHighlightTableOrView ($dbItem, itemName) { + var $container = $dbItem.children('div.list_container'); + var $expander; + var $whichItem = isItemInContainer($container, itemName, 'li.table, li.view'); + // If item already there in some container + if ($whichItem) { + // get the relevant container while may also be a subcontainer + var $relatedContainer = $whichItem.closest('li.subContainer').length + ? $whichItem.closest('li.subContainer') + : $dbItem; + $whichItem = findLoadedItem( + $relatedContainer.children('div.list_container'), + itemName, null, true + ); + // Show directly + showTableOrView($whichItem, $relatedContainer.children('div:first').children('a.expander')); + // else if item not there, try loading once + } else { + var $sub_containers = $dbItem.find('.subContainer'); + // If there are subContainers i.e. tableContainer or viewContainer + if ($sub_containers.length > 0) { + var $containers = []; + $sub_containers.each(function (index) { + $containers[index] = $(this); + $expander = $containers[index] + .children('div:first') + .children('a.expander'); + if (! $expander.hasClass('loaded')) { + loadAndShowTableOrView($expander, $containers[index], itemName); + } + }); + // else if no subContainers + } else { + $expander = $dbItem + .children('div:first') + .children('a.expander'); + if (! $expander.hasClass('loaded')) { + loadAndShowTableOrView($expander, $dbItem, itemName); + } + } + } + } + + function loadAndShowTableOrView ($expander, $relatedContainer, itemName) { + loadChildNodes(true, $expander, function (data) { + var $whichItem = findLoadedItem( + $relatedContainer.children('div.list_container'), + itemName, null, true + ); + if ($whichItem) { + showTableOrView($whichItem, $expander); + } + }); + } + + function showTableOrView ($whichItem, $expander) { + expandTreeNode($expander, function (data) { + if ($whichItem) { + scrollToView($whichItem, false); + } + }); + } + + function isItemInContainer ($container, name, clazz) { + var $whichItem = null; + $items = $container.find(clazz); + var found = false; + $items.each(function () { + if ($(this).children('a').text() === name) { + $whichItem = $(this); + return false; + } + }); + return $whichItem; + } +} + +/** + * Disable navigation panel settings + * + * @return void + */ +function PMA_disableNaviSettings () { + $('#pma_navigation_settings_icon').addClass('hide'); + $('#pma_navigation_settings').remove(); +} + +/** + * Ensure that navigation panel settings is properly setup. + * If not, set it up + * + * @return void + */ +function PMA_ensureNaviSettings (selflink) { + $('#pma_navigation_settings_icon').removeClass('hide'); + + if (!$('#pma_navigation_settings').length) { + var params = { + getNaviSettings: true, + server: PMA_commonParams.get('server'), + }; + var url = $('#pma_navigation').find('a.navigation_url').attr('href'); + $.post(url, params, function (data) { + if (typeof data !== 'undefined' && data.success) { + $('#pma_navi_settings_container').html(data.message); + setupRestoreField(); + setupValidation(); + setupConfigTabs(); + $('#pma_navigation_settings').find('form').attr('action', selflink); + } else { + PMA_ajaxShowMessage(data.error); + } + }); + } else { + $('#pma_navigation_settings').find('form').attr('action', selflink); + } +} + +/** + * Reloads the whole navigation tree while preserving its state + * + * @param function the callback function + * @param Object stored navigation paths + * + * @return void + */ +function PMA_reloadNavigation (callback, paths) { + var params = { + reload: true, + no_debug: true, + server: PMA_commonParams.get('server'), + }; + paths = paths || traverseNavigationForPaths(); + $.extend(params, paths); + if ($('#navi_db_select').length) { + params.db = PMA_commonParams.get('db'); + requestNaviReload(params); + return; + } + requestNaviReload(params); + + function requestNaviReload (params) { + var url = $('#pma_navigation').find('a.navigation_url').attr('href'); + $.post(url, params, function (data) { + if (typeof data !== 'undefined' && data.success) { + $('#pma_navigation_tree').html(data.message).children('div').show(); + if ($('#pma_navigation_tree').hasClass('synced')) { + PMA_selectCurrentDb(); + PMA_showCurrentNavigation(); + } + // Fire the callback, if any + if (typeof callback === 'function') { + callback.call(); + } + navTreeStateUpdate(); + } else { + PMA_ajaxShowMessage(data.error); + } + }); + } +} + +function PMA_selectCurrentDb () { + var $naviDbSelect = $('#navi_db_select'); + + if (!$naviDbSelect.length) { + return false; + } + + if (PMA_commonParams.get('db')) { // db selected + $naviDbSelect.show(); + } + + $naviDbSelect.val(PMA_commonParams.get('db')); + return $naviDbSelect.val() === PMA_commonParams.get('db'); +} + +/** + * Handles any requests to change the page in a branch of a tree + * + * This can be called from link click or select change event handlers + * + * @param object $this A jQuery object that points to the element that + * initiated the action of changing the page + * + * @return void + */ +function PMA_navigationTreePagination ($this) { + var $msgbox = PMA_ajaxShowMessage(); + var isDbSelector = $this.closest('div.pageselector').is('.dbselector'); + var url; + var params; + if ($this[0].tagName === 'A') { + url = $this.attr('href'); + params = 'ajax_request=true'; + } else { // tagName === 'SELECT' + url = 'navigation.php'; + params = $this.closest('form').serialize() + PMA_commonParams.get('arg_separator') + 'ajax_request=true'; + } + var searchClause = PMA_fastFilter.getSearchClause(); + if (searchClause) { + params += PMA_commonParams.get('arg_separator') + 'searchClause=' + encodeURIComponent(searchClause); + } + if (isDbSelector) { + params += PMA_commonParams.get('arg_separator') + 'full=true'; + } else { + var searchClause2 = PMA_fastFilter.getSearchClause2($this); + if (searchClause2) { + params += PMA_commonParams.get('arg_separator') + 'searchClause2=' + encodeURIComponent(searchClause2); + } + } + $.post(url, params, function (data) { + if (typeof data !== 'undefined' && data.success) { + PMA_ajaxRemoveMessage($msgbox); + if (isDbSelector) { + var val = PMA_fastFilter.getSearchClause(); + $('#pma_navigation_tree') + .html(data.message) + .children('div') + .show(); + if (val) { + $('#pma_navigation_tree') + .find('li.fast_filter input.searchClause') + .val(val); + } + } else { + var $parent = $this.closest('div.list_container').parent(); + var val = PMA_fastFilter.getSearchClause2($this); + $this.closest('div.list_container').html( + $(data.message).children().show() + ); + if (val) { + $parent.find('li.fast_filter input.searchClause').val(val); + } + $parent.find('span.pos2_value:first').text( + $parent.find('span.pos2_value:last').text() + ); + $parent.find('span.pos3_value:first').text( + $parent.find('span.pos3_value:last').text() + ); + } + } else { + PMA_ajaxShowMessage(data.error); + PMA_handleRedirectAndReload(data); + } + navTreeStateUpdate(); + }); +} + +/** + * @var ResizeHandler Custom object that manages the resizing of the navigation + * + * XXX: Must only be ever instanciated once + * XXX: Inside event handlers the 'this' object is accessed as 'event.data.resize_handler' + */ +var ResizeHandler = function () { + /** + * @var int panel_width Used by the collapser to know where to go + * back to when uncollapsing the panel + */ + this.panel_width = 0; + /** + * @var string left Used to provide support for RTL languages + */ + this.left = $('html').attr('dir') === 'ltr' ? 'left' : 'right'; + /** + * Adjusts the width of the navigation panel to the specified value + * + * @param int pos Navigation width in pixels + * + * @return void + */ + this.setWidth = function (pos) { + if (typeof pos !== 'number') { + pos = 240; + } + var $resizer = $('#pma_navigation_resizer'); + var resizer_width = $resizer.width(); + var $collapser = $('#pma_navigation_collapser'); + var windowWidth = $(window).width(); + $('#pma_navigation').width(pos); + $('body').css('margin-' + this.left, pos + 'px'); + // Issue #15127 + $('#floating_menubar, #pma_console') + .css('margin-' + this.left, (pos + resizer_width) + 'px'); + $resizer.css(this.left, pos + 'px'); + if (pos === 0) { + $collapser + .css(this.left, pos + resizer_width) + .html(this.getSymbol(pos)) + .prop('title', PMA_messages.strShowPanel); + } else if (windowWidth > 768) { + $collapser + .css(this.left, pos) + .html(this.getSymbol(pos)) + .prop('title', PMA_messages.strHidePanel); + $('#pma_navigation_resizer').css({ 'width': '3px' }); + } else { + $collapser + .css(this.left, windowWidth - 22) + .html(this.getSymbol(100)) + .prop('title', PMA_messages.strHidePanel); + $('#pma_navigation').width(windowWidth); + $('body').css('margin-' + this.left, '0px'); + $('#pma_navigation_resizer').css({ 'width': '0px' }); + } + setTimeout(function () { + $(window).trigger('resize'); + }, 4); + }; + /** + * Returns the horizontal position of the mouse, + * relative to the outer side of the navigation panel + * + * @param int pos Navigation width in pixels + * + * @return void + */ + this.getPos = function (event) { + var pos = event.pageX; + var windowWidth = $(window).width(); + var windowScroll = $(window).scrollLeft(); + pos = pos - windowScroll; + if (this.left !== 'left') { + pos = windowWidth - event.pageX; + } + if (pos < 0) { + pos = 0; + } else if (pos + 100 >= windowWidth) { + pos = windowWidth - 100; + } else { + this.panel_width = 0; + } + return pos; + }; + /** + * Returns the HTML code for the arrow symbol used in the collapser + * + * @param int width The width of the panel + * + * @return string + */ + this.getSymbol = function (width) { + if (this.left === 'left') { + if (width === 0) { + return '→'; + } else { + return '←'; + } + } else { + if (width === 0) { + return '←'; + } else { + return '→'; + } + } + }; + /** + * Event handler for initiating a resize of the panel + * + * @param object e Event data (contains a reference to resizeHandler) + * + * @return void + */ + this.mousedown = function (event) { + event.preventDefault(); + $(document) + .on('mousemove', { 'resize_handler': event.data.resize_handler }, + $.throttle(event.data.resize_handler.mousemove, 4)) + .on('mouseup', { 'resize_handler': event.data.resize_handler }, + event.data.resize_handler.mouseup); + $('body').css('cursor', 'col-resize'); + }; + /** + * Event handler for terminating a resize of the panel + * + * @param object e Event data (contains a reference to resizeHandler) + * + * @return void + */ + this.mouseup = function (event) { + $('body').css('cursor', ''); + configSet('NavigationWidth', event.data.resize_handler.getPos(event)); + $('#topmenu').menuResizer('resize'); + $(document) + .off('mousemove') + .off('mouseup'); + }; + /** + * Event handler for updating the panel during a resize operation + * + * @param object e Event data (contains a reference to resizeHandler) + * + * @return void + */ + this.mousemove = function (event) { + event.preventDefault(); + var pos = event.data.resize_handler.getPos(event); + event.data.resize_handler.setWidth(pos); + if ($('.sticky_columns').length !== 0) { + handleAllStickyColumns(); + } + }; + /** + * Event handler for collapsing the panel + * + * @param object e Event data (contains a reference to resizeHandler) + * + * @return void + */ + this.collapse = function (event) { + event.preventDefault(); + var panel_width = event.data.resize_handler.panel_width; + var width = $('#pma_navigation').width(); + if (width === 0 && panel_width === 0) { + panel_width = 240; + } + configSet('NavigationWidth', panel_width); + event.data.resize_handler.setWidth(panel_width); + event.data.resize_handler.panel_width = width; + }; + /** + * Event handler for resizing the navigation tree height on window resize + * + * @return void + */ + this.treeResize = function (event) { + var $nav = $('#pma_navigation'); + var $nav_tree = $('#pma_navigation_tree'); + var $nav_header = $('#pma_navigation_header'); + var $nav_tree_content = $('#pma_navigation_tree_content'); + $nav_tree.height($nav.height() - $nav_header.height()); + if ($nav_tree_content.length > 0) { + $nav_tree_content.height($nav_tree.height() - $nav_tree_content.position().top); + } else { + // TODO: in fast filter search response there is no #pma_navigation_tree_content, needs to be added in php + $nav_tree.css({ + 'overflow-y': 'auto' + }); + } + // Set content bottom space beacuse of console + $('body').css('margin-bottom', $('#pma_console').height() + 'px'); + }; + // Hide the pma_navigation initially when loaded on mobile + if ($(window).width() < 768) { + this.setWidth(0); + } else { + this.setWidth(configGet('NavigationWidth', false)); + $('#topmenu').menuResizer('resize'); + } + // Register the events for the resizer and the collapser + $(document).on('mousedown', '#pma_navigation_resizer', { 'resize_handler': this }, this.mousedown); + $(document).on('click', '#pma_navigation_collapser', { 'resize_handler': this }, this.collapse); + + // Add the correct arrow symbol to the collapser + $('#pma_navigation_collapser').html(this.getSymbol($('#pma_navigation').width())); + // Fix navigation tree height + $(window).on('resize', this.treeResize); + // need to call this now and then, browser might decide + // to show/hide horizontal scrollbars depending on page content width + setInterval(this.treeResize, 2000); + this.treeResize(); +}; // End of ResizeHandler + +/** + * @var object PMA_fastFilter Handles the functionality that allows filtering + * of the items in a branch of the navigation tree + */ +var PMA_fastFilter = { + /** + * Construct for the asynchronous fast filter functionality + * + * @param object $this A jQuery object pointing to the list container + * which is the nearest parent of the fast filter + * @param string searchClause The query string for the filter + * + * @return new PMA_fastFilter.filter object + */ + filter: function ($this, searchClause) { + /** + * @var object $this A jQuery object pointing to the list container + * which is the nearest parent of the fast filter + */ + this.$this = $this; + /** + * @var bool searchClause The query string for the filter + */ + this.searchClause = searchClause; + /** + * @var object $clone A clone of the original contents + * of the navigation branch before + * the fast filter was applied + */ + this.$clone = $this.clone(); + /** + * @var object xhr A reference to the ajax request that is currently running + */ + this.xhr = null; + /** + * @var int timeout Used to delay the request for asynchronous search + */ + this.timeout = null; + + var $filterInput = $this.find('li.fast_filter input.searchClause'); + if ($filterInput.length !== 0 && + $filterInput.val() !== '' && + $filterInput.val() !== $filterInput[0].defaultValue + ) { + this.request(); + } + }, + /** + * Gets the query string from the database fast filter form + * + * @return string + */ + getSearchClause: function () { + var retval = ''; + var $input = $('#pma_navigation_tree') + .find('li.fast_filter.db_fast_filter input.searchClause'); + if ($input.length && $input.val() !== $input[0].defaultValue) { + retval = $input.val(); + } + return retval; + }, + /** + * Gets the query string from a second level item's fast filter form + * The retrieval is done by trasversing the navigation tree backwards + * + * @return string + */ + getSearchClause2: function ($this) { + var $filterContainer = $this.closest('div.list_container'); + var $filterInput = $([]); + if ($filterContainer + .find('li.fast_filter:not(.db_fast_filter) input.searchClause') + .length !== 0) { + $filterInput = $filterContainer + .find('li.fast_filter:not(.db_fast_filter) input.searchClause'); + } + var searchClause2 = ''; + if ($filterInput.length !== 0 && + $filterInput.first().val() !== $filterInput[0].defaultValue + ) { + searchClause2 = $filterInput.val(); + } + return searchClause2; + }, + /** + * @var hash events A list of functions that are bound to DOM events + * at the top of this file + */ + events: { + focus: function (event) { + var $obj = $(this).closest('div.list_container'); + if (! $obj.data('fastFilter')) { + $obj.data( + 'fastFilter', + new PMA_fastFilter.filter($obj, $(this).val()) + ); + } + if ($(this).val() === this.defaultValue) { + $(this).val(''); + } else { + $(this).select(); + } + }, + blur: function (event) { + if ($(this).val() === '') { + $(this).val(this.defaultValue); + } + var $obj = $(this).closest('div.list_container'); + if ($(this).val() === this.defaultValue && $obj.data('fastFilter')) { + $obj.data('fastFilter').restore(); + } + }, + keyup: function (event) { + var $obj = $(this).closest('div.list_container'); + var str = ''; + if ($(this).val() !== this.defaultValue && $(this).val() !== '') { + $obj.find('div.pageselector').hide(); + str = $(this).val(); + } + + /** + * FIXME at the server level a value match is done while on + * the client side it is a regex match. These two should be aligned + */ + + // regex used for filtering. + var regex; + try { + regex = new RegExp(str, 'i'); + } catch (err) { + return; + } + + // this is the div that houses the items to be filtered by this filter. + var outerContainer; + if ($(this).closest('li.fast_filter').is('.db_fast_filter')) { + outerContainer = $('#pma_navigation_tree_content'); + } else { + outerContainer = $obj; + } + + // filters items that are directly under the div as well as grouped in + // groups. Does not filter child items (i.e. a database search does + // not filter tables) + var item_filter = function ($curr) { + $curr.children('ul').children('li.navGroup').each(function () { + $(this).children('div.list_container').each(function () { + item_filter($(this)); // recursive + }); + }); + $curr.children('ul').children('li').children('a').not('.container').each(function () { + if (regex.test($(this).text())) { + $(this).parent().show().removeClass('hidden'); + } else { + $(this).parent().hide().addClass('hidden'); + } + }); + }; + item_filter(outerContainer); + + // hides containers that does not have any visible children + var container_filter = function ($curr) { + $curr.children('ul').children('li.navGroup').each(function () { + var $group = $(this); + $group.children('div.list_container').each(function () { + container_filter($(this)); // recursive + }); + $group.show().removeClass('hidden'); + if ($group.children('div.list_container').children('ul') + .children('li').not('.hidden').length === 0) { + $group.hide().addClass('hidden'); + } + }); + }; + container_filter(outerContainer); + + if ($(this).val() !== this.defaultValue && $(this).val() !== '') { + if (! $obj.data('fastFilter')) { + $obj.data( + 'fastFilter', + new PMA_fastFilter.filter($obj, $(this).val()) + ); + } else { + if (event.keyCode === 13) { + $obj.data('fastFilter').update($(this).val()); + } + } + } else if ($obj.data('fastFilter')) { + $obj.data('fastFilter').restore(true); + } + // update filter state + var filterName; + if ($(this).attr('name') === 'searchClause2') { + filterName = $(this).siblings('input[name=aPath]').val(); + } else { + filterName = 'dbFilter'; + } + navFilterStateUpdate(filterName, $(this).val()); + }, + clear: function (event) { + event.stopPropagation(); + // Clear the input and apply the fast filter with empty input + var filter = $(this).closest('div.list_container').data('fastFilter'); + if (filter) { + filter.restore(); + } + var value = $(this).prev()[0].defaultValue; + $(this).prev().val(value).trigger('keyup'); + } + } +}; +/** + * Handles a change in the search clause + * + * @param string searchClause The query string for the filter + * + * @return void + */ +PMA_fastFilter.filter.prototype.update = function (searchClause) { + if (this.searchClause !== searchClause) { + this.searchClause = searchClause; + this.request(); + } +}; +/** + * After a delay of 250mS, initiates a request to retrieve search results + * Multiple calls to this function will always abort the previous request + * + * @return void + */ +PMA_fastFilter.filter.prototype.request = function () { + var self = this; + if (self.$this.find('li.fast_filter').find('img.throbber').length === 0) { + self.$this.find('li.fast_filter').append( + $('
        ').append( + $('#pma_navigation_content') + .find('img.throbber') + .clone() + .css({ visibility: 'visible', display: 'block' }) + ) + ); + } + if (self.xhr) { + self.xhr.abort(); + } + var url = $('#pma_navigation').find('a.navigation_url').attr('href'); + var params = self.$this.find('> ul > li > form.fast_filter').first().serialize(); + + if (self.$this.find('> ul > li > form.fast_filter:first input[name=searchClause]').length === 0) { + var $input = $('#pma_navigation_tree').find('li.fast_filter.db_fast_filter input.searchClause'); + if ($input.length && $input.val() !== $input[0].defaultValue) { + params += PMA_commonParams.get('arg_separator') + 'searchClause=' + encodeURIComponent($input.val()); + } + } + self.xhr = $.ajax({ + url: url, + type: 'post', + dataType: 'json', + data: params, + complete: function (jqXHR, status) { + if (status !== 'abort') { + var data = JSON.parse(jqXHR.responseText); + self.$this.find('li.fast_filter').find('div.throbber').remove(); + if (data && data.results) { + self.swap.apply(self, [data.message]); + } + } + } + }); +}; +/** + * Replaces the contents of the navigation branch with the search results + * + * @param string list The search results + * + * @return void + */ +PMA_fastFilter.filter.prototype.swap = function (list) { + this.$this + .html($(list).html()) + .children() + .show() + .end() + .find('li.fast_filter input.searchClause') + .val(this.searchClause); + this.$this.data('fastFilter', this); +}; +/** + * Restores the navigation to the original state after the fast filter is cleared + * + * @param bool focus Whether to also focus the input box of the fast filter + * + * @return void + */ +PMA_fastFilter.filter.prototype.restore = function (focus) { + if (this.$this.children('ul').first().hasClass('search_results')) { + this.$this.html(this.$clone.html()).children().show(); + this.$this.data('fastFilter', this); + if (focus) { + this.$this.find('li.fast_filter input.searchClause').focus(); + } + } + this.searchClause = ''; + this.$this.find('div.pageselector').show(); + this.$this.find('div.throbber').remove(); +}; + +/** + * Show full name when cursor hover and name not shown completely + * + * @param object $containerELem Container element + * + * @return void + */ +function PMA_showFullName ($containerELem) { + $containerELem.find('.hover_show_full').mouseenter(function () { + /** mouseenter */ + var $this = $(this); + var thisOffset = $this.offset(); + if ($this.text() === '') { + return; + } + var $parent = $this.parent(); + if (($parent.offset().left + $parent.outerWidth()) + < (thisOffset.left + $this.outerWidth())) { + var $fullNameLayer = $('#full_name_layer'); + if ($fullNameLayer.length === 0) { + $('body').append('
        '); + $('#full_name_layer').mouseleave(function () { + /** mouseleave */ + $(this).addClass('hide') + .removeClass('hovering'); + }).mouseenter(function () { + /** mouseenter */ + $(this).addClass('hovering'); + }); + $fullNameLayer = $('#full_name_layer'); + } + $fullNameLayer.removeClass('hide'); + $fullNameLayer.css({ left: thisOffset.left, top: thisOffset.top }); + $fullNameLayer.html($this.clone()); + setTimeout(function () { + if (! $fullNameLayer.hasClass('hovering')) { + $fullNameLayer.trigger('mouseleave'); + } + }, 200); + } + }); +} diff --git a/php/apps/phpmyadmin49/html/js/normalization.js b/php/apps/phpmyadmin49/html/js/normalization.js new file mode 100644 index 00000000..fa486d2e --- /dev/null +++ b/php/apps/phpmyadmin49/html/js/normalization.js @@ -0,0 +1,745 @@ +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * @fileoverview events handling from normalization page + * @name normalization + * + * @requires jQuery + */ + +/** + * AJAX scripts for normalization.php + * + */ + +var normalizeto = '1nf'; +var primary_key; +var data_parsed = null; +function appendHtmlColumnsList () { + $.post( + 'normalization.php', + { + 'ajax_request': true, + 'db': PMA_commonParams.get('db'), + 'table': PMA_commonParams.get('table'), + 'server': PMA_commonParams.get('server'), + 'getColumns': true + }, + function (data) { + if (data.success === true) { + $('select[name=makeAtomic]').html(data.message); + } + } + ); +} +function goTo3NFStep1 (newTables) { + if (Object.keys(newTables).length === 1) { + newTables = [PMA_commonParams.get('table')]; + } + $.post( + 'normalization.php', + { + 'ajax_request': true, + 'db': PMA_commonParams.get('db'), + 'server': PMA_commonParams.get('server'), + 'tables': newTables, + 'step': '3.1' + }, function (data) { + $('#page_content').find('h3').html(PMA_messages.str3NFNormalization); + $('#mainContent').find('legend').html(data.legendText); + $('#mainContent').find('h4').html(data.headText); + $('#mainContent').find('p').html(data.subText); + $('#mainContent').find('#extra').html(data.extra); + $('#extra').find('form').each(function () { + var form_id = $(this).attr('id'); + var colname = $(this).data('colname'); + $('#' + form_id + ' input[value=\'' + colname + '\']').next().remove(); + $('#' + form_id + ' input[value=\'' + colname + '\']').remove(); + }); + $('#mainContent').find('#newCols').html(''); + $('.tblFooters').html(''); + + if (data.subText !== '') { + $('') + .attr({ type: 'button', value: PMA_messages.strDone }) + .on('click', function () { + processDependencies('', true); + }) + .appendTo('.tblFooters'); + } + } + ); +} +function goTo2NFStep1 () { + $.post( + 'normalization.php', + { + 'ajax_request': true, + 'db': PMA_commonParams.get('db'), + 'table': PMA_commonParams.get('table'), + 'server': PMA_commonParams.get('server'), + 'step': '2.1' + }, function (data) { + $('#page_content h3').html(PMA_messages.str2NFNormalization); + $('#mainContent legend').html(data.legendText); + $('#mainContent h4').html(data.headText); + $('#mainContent p').html(data.subText); + $('#mainContent #extra').html(data.extra); + $('#mainContent #newCols').html(''); + if (data.subText !== '') { + var doneButton = $('') + .attr({ type: 'submit', value: PMA_messages.strDone, }) + .on('click', function () { + processDependencies(data.primary_key); + }) + .appendTo('.tblFooters'); + } else { + if (normalizeto === '3nf') { + $('#mainContent #newCols').html(PMA_messages.strToNextStep); + setTimeout(function () { + goTo3NFStep1([PMA_commonParams.get('table')]); + }, 3000); + } + } + }); +} + +function goToFinish1NF () { + if (normalizeto !== '1nf') { + goTo2NFStep1(); + return true; + } + $('#mainContent legend').html(PMA_messages.strEndStep); + $('#mainContent h4').html( + '

        ' + PMA_sprintf(PMA_messages.strFinishMsg, escapeHtml(PMA_commonParams.get('table'))) + '

        ' + ); + $('#mainContent p').html(''); + $('#mainContent #extra').html(''); + $('#mainContent #newCols').html(''); + $('.tblFooters').html(''); +} + +function goToStep4 () { + $.post( + 'normalization.php', + { + 'ajax_request': true, + 'db': PMA_commonParams.get('db'), + 'table': PMA_commonParams.get('table'), + 'server': PMA_commonParams.get('server'), + 'step4': true + }, function (data) { + $('#mainContent legend').html(data.legendText); + $('#mainContent h4').html(data.headText); + $('#mainContent p').html(data.subText); + $('#mainContent #extra').html(data.extra); + $('#mainContent #newCols').html(''); + $('.tblFooters').html(''); + for (var pk in primary_key) { + $('#extra input[value=\'' + escapeJsString(primary_key[pk]) + '\']').attr('disabled','disabled'); + } + } + ); +} + +function goToStep3 () { + $.post( + 'normalization.php', + { + 'ajax_request': true, + 'db': PMA_commonParams.get('db'), + 'table': PMA_commonParams.get('table'), + 'server': PMA_commonParams.get('server'), + 'step3': true + }, function (data) { + $('#mainContent legend').html(data.legendText); + $('#mainContent h4').html(data.headText); + $('#mainContent p').html(data.subText); + $('#mainContent #extra').html(data.extra); + $('#mainContent #newCols').html(''); + $('.tblFooters').html(''); + primary_key = JSON.parse(data.primary_key); + for (var pk in primary_key) { + $('#extra input[value=\'' + escapeJsString(primary_key[pk]) + '\']').attr('disabled','disabled'); + } + } + ); +} + +function goToStep2 (extra) { + $.post( + 'normalization.php', + { + 'ajax_request': true, + 'db': PMA_commonParams.get('db'), + 'table': PMA_commonParams.get('table'), + 'server': PMA_commonParams.get('server'), + 'step2': true + }, function (data) { + $('#mainContent legend').html(data.legendText); + $('#mainContent h4').html(data.headText); + $('#mainContent p').html(data.subText); + $('#mainContent #extra,#mainContent #newCols').html(''); + $('.tblFooters').html(''); + if (data.hasPrimaryKey === '1') { + if (extra === 'goToStep3') { + $('#mainContent h4').html(PMA_messages.strPrimaryKeyAdded); + $('#mainContent p').html(PMA_messages.strToNextStep); + } + if (extra === 'goToFinish1NF') { + goToFinish1NF(); + } else { + setTimeout(function () { + goToStep3(); + }, 3000); + } + } else { + // form to select columns to make primary + $('#mainContent #extra').html(data.extra); + } + } + ); +} + +function goTo2NFFinish (pd) { + var tables = {}; + for (var dependson in pd) { + tables[dependson] = $('#extra input[name="' + dependson + '"]').val(); + } + datastring = { + 'ajax_request': true, + 'db': PMA_commonParams.get('db'), + 'table': PMA_commonParams.get('table'), + 'server': PMA_commonParams.get('server'), + 'pd': JSON.stringify(pd), + 'newTablesName':JSON.stringify(tables), + 'createNewTables2NF':1 }; + $.ajax({ + type: 'POST', + url: 'normalization.php', + data: datastring, + async:false, + success: function (data) { + if (data.success === true) { + if (data.queryError === false) { + if (normalizeto === '3nf') { + $('#pma_navigation_reload').click(); + goTo3NFStep1(tables); + return true; + } + $('#mainContent legend').html(data.legendText); + $('#mainContent h4').html(data.headText); + $('#mainContent p').html(''); + $('#mainContent #extra').html(''); + $('.tblFooters').html(''); + } else { + PMA_ajaxShowMessage(data.extra, false); + } + $('#pma_navigation_reload').click(); + } else { + PMA_ajaxShowMessage(data.error, false); + } + } + }); +} + +function goTo3NFFinish (newTables) { + for (var table in newTables) { + for (var newtbl in newTables[table]) { + var updatedname = $('#extra input[name="' + newtbl + '"]').val(); + newTables[table][updatedname] = newTables[table][newtbl]; + if (updatedname !== newtbl) { + delete newTables[table][newtbl]; + } + } + } + datastring = { + 'ajax_request': true, + 'db': PMA_commonParams.get('db'), + 'server': PMA_commonParams.get('server'), + 'newTables':JSON.stringify(newTables), + 'createNewTables3NF':1 }; + $.ajax({ + type: 'POST', + url: 'normalization.php', + data: datastring, + async:false, + success: function (data) { + if (data.success === true) { + if (data.queryError === false) { + $('#mainContent legend').html(data.legendText); + $('#mainContent h4').html(data.headText); + $('#mainContent p').html(''); + $('#mainContent #extra').html(''); + $('.tblFooters').html(''); + } else { + PMA_ajaxShowMessage(data.extra, false); + } + $('#pma_navigation_reload').click(); + } else { + PMA_ajaxShowMessage(data.error, false); + } + } + }); +} +var backup = ''; +function goTo2NFStep2 (pd, primary_key) { + $('#newCols').html(''); + $('#mainContent legend').html(PMA_messages.strStep + ' 2.2 ' + PMA_messages.strConfirmPd); + $('#mainContent h4').html(PMA_messages.strSelectedPd); + $('#mainContent p').html(PMA_messages.strPdHintNote); + var extra = '
        '; + var pdFound = false; + for (var dependson in pd) { + if (dependson !== primary_key) { + pdFound = true; + extra += '

        ' + escapeHtml(dependson) + ' -> ' + escapeHtml(pd[dependson].toString()) + '

        '; + } + } + if (!pdFound) { + extra += '

        ' + PMA_messages.strNoPdSelected + '

        '; + extra += '
        '; + } else { + extra += '
        '; + datastring = { + 'ajax_request': true, + 'db': PMA_commonParams.get('db'), + 'table': PMA_commonParams.get('table'), + 'server': PMA_commonParams.get('server'), + 'pd': JSON.stringify(pd), + 'getNewTables2NF':1 }; + $.ajax({ + type: 'POST', + url: 'normalization.php', + data: datastring, + async:false, + success: function (data) { + if (data.success === true) { + extra += data.message; + } else { + PMA_ajaxShowMessage(data.error, false); + } + } + }); + } + $('#mainContent #extra').html(extra); + $('.tblFooters').html(''); + $('#goTo2NFFinish').click(function () { + goTo2NFFinish(pd); + }); +} + +function goTo3NFStep2 (pd, tablesTds) { + $('#newCols').html(''); + $('#mainContent legend').html(PMA_messages.strStep + ' 3.2 ' + PMA_messages.strConfirmTd); + $('#mainContent h4').html(PMA_messages.strSelectedTd); + $('#mainContent p').html(PMA_messages.strPdHintNote); + var extra = '
        '; + var pdFound = false; + for (var table in tablesTds) { + for (var i in tablesTds[table]) { + dependson = tablesTds[table][i]; + if (dependson !== '' && dependson !== table) { + pdFound = true; + extra += '

        ' + escapeHtml(dependson) + ' -> ' + escapeHtml(pd[dependson].toString()) + '

        '; + } + } + } + if (!pdFound) { + extra += '

        ' + PMA_messages.strNoTdSelected + '

        '; + extra += '
        '; + } else { + extra += '
  • '; + datastring = { + 'ajax_request': true, + 'db': PMA_commonParams.get('db'), + 'tables': JSON.stringify(tablesTds), + 'server': PMA_commonParams.get('server'), + 'pd': JSON.stringify(pd), + 'getNewTables3NF':1 }; + $.ajax({ + type: 'POST', + url: 'normalization.php', + data: datastring, + async:false, + success: function (data) { + data_parsed = data; + if (data.success === true) { + extra += data_parsed.html; + } else { + PMA_ajaxShowMessage(data.error, false); + } + } + }); + } + $('#mainContent #extra').html(extra); + $('.tblFooters').html(''); + $('#goTo3NFFinish').click(function () { + if (!pdFound) { + goTo3NFFinish([]); + } else { + goTo3NFFinish(data_parsed.newTables); + } + }); +} +function processDependencies (primary_key, isTransitive) { + var pd = {}; + var tablesTds = {}; + var dependsOn; + pd[primary_key] = []; + $('#extra form').each(function () { + var tblname; + if (isTransitive === true) { + tblname = $(this).data('tablename'); + primary_key = tblname; + if (!(tblname in tablesTds)) { + tablesTds[tblname] = []; + } + tablesTds[tblname].push(primary_key); + } + var form_id = $(this).attr('id'); + $('#' + form_id + ' input[type=checkbox]:not(:checked)').prop('checked', false); + dependsOn = ''; + $('#' + form_id + ' input[type=checkbox]:checked').each(function () { + dependsOn += $(this).val() + ', '; + $(this).attr('checked','checked'); + }); + if (dependsOn === '') { + dependsOn = primary_key; + } else { + dependsOn = dependsOn.slice(0, -2); + } + if (! (dependsOn in pd)) { + pd[dependsOn] = []; + } + pd[dependsOn].push($(this).data('colname')); + if (isTransitive === true) { + if (!(tblname in tablesTds)) { + tablesTds[tblname] = []; + } + if ($.inArray(dependsOn, tablesTds[tblname]) === -1) { + tablesTds[tblname].push(dependsOn); + } + } + }); + backup = $('#mainContent').html(); + if (isTransitive === true) { + goTo3NFStep2(pd, tablesTds); + } else { + goTo2NFStep2(pd, primary_key); + } + return false; +} + +function moveRepeatingGroup (repeatingCols) { + var newTable = $('input[name=repeatGroupTable]').val(); + var newColumn = $('input[name=repeatGroupColumn]').val(); + if (!newTable) { + $('input[name=repeatGroupTable]').focus(); + return false; + } + if (!newColumn) { + $('input[name=repeatGroupColumn]').focus(); + return false; + } + datastring = { + 'ajax_request': true, + 'db': PMA_commonParams.get('db'), + 'table': PMA_commonParams.get('table'), + 'server': PMA_commonParams.get('server'), + 'repeatingColumns': repeatingCols, + 'newTable':newTable, + 'newColumn':newColumn, + 'primary_columns':primary_key.toString() + }; + $.ajax({ + type: 'POST', + url: 'normalization.php', + data: datastring, + async:false, + success: function (data) { + if (data.success === true) { + if (data.queryError === false) { + goToStep3(); + } + PMA_ajaxShowMessage(data.message, false); + $('#pma_navigation_reload').click(); + } else { + PMA_ajaxShowMessage(data.error, false); + } + } + }); +} +AJAX.registerTeardown('normalization.js', function () { + $('#extra').off('click', '#selectNonAtomicCol'); + $('#splitGo').off('click'); + $('.tblFooters').off('click', '#saveSplit'); + $('#extra').off('click', '#addNewPrimary'); + $('.tblFooters').off('click', '#saveNewPrimary'); + $('#extra').off('click', '#removeRedundant'); + $('#mainContent p').off('click', '#createPrimaryKey'); + $('#mainContent').off('click', '#backEditPd'); + $('#mainContent').off('click', '#showPossiblePd'); + $('#mainContent').off('click', '.pickPd'); +}); + +AJAX.registerOnload('normalization.js', function () { + var selectedCol; + normalizeto = $('#mainContent').data('normalizeto'); + $('#extra').on('click', '#selectNonAtomicCol', function () { + if ($(this).val() === 'no_such_col') { + goToStep2(); + } else { + selectedCol = $(this).val(); + } + }); + + $('#splitGo').click(function () { + if (!selectedCol || selectedCol === '') { + return false; + } + var numField = $('#numField').val(); + $.post( + 'normalization.php', + { + 'ajax_request': true, + 'db': PMA_commonParams.get('db'), + 'table': PMA_commonParams.get('table'), + 'server': PMA_commonParams.get('server'), + 'splitColumn': true, + 'numFields': numField + }, + function (data) { + if (data.success === true) { + $('#newCols').html(data.message); + $('.default_value').hide(); + $('.enum_notice').hide(); + + $('') + .attr({ type: 'submit', id: 'saveSplit', value: PMA_messages.strSave }) + .appendTo('.tblFooters'); + + var cancelSplitButton = $('') + .attr({ type: 'submit', id: 'cancelSplit', value: PMA_messages.strCancel }) + .on('click', function () { + $('#newCols').html(''); + $(this).parent().html(''); + }) + .appendTo('.tblFooters'); + } + } + ); + return false; + }); + $('.tblFooters').on('click','#saveSplit', function () { + central_column_list = []; + if ($('#newCols #field_0_1').val() === '') { + $('#newCols #field_0_1').focus(); + return false; + } + var argsep = PMA_commonParams.get('arg_separator'); + datastring = $('#newCols :input').serialize(); + datastring += argsep + 'ajax_request=1' + argsep + 'do_save_data=1' + argsep + 'field_where=last'; + $.post('tbl_addfield.php', datastring, function (data) { + if (data.success) { + $.post( + 'sql.php', + { + 'ajax_request': true, + 'db': PMA_commonParams.get('db'), + 'table': PMA_commonParams.get('table'), + 'server': PMA_commonParams.get('server'), + 'dropped_column': selectedCol, + 'purge' : 1, + 'sql_query': 'ALTER TABLE `' + PMA_commonParams.get('table') + '` DROP `' + selectedCol + '`;', + 'is_js_confirmed': 1 + }, + function (data) { + if (data.success === true) { + appendHtmlColumnsList(); + $('#newCols').html(''); + $('.tblFooters').html(''); + } else { + PMA_ajaxShowMessage(data.error, false); + } + selectedCol = ''; + } + ); + } else { + PMA_ajaxShowMessage(data.error, false); + } + }); + }); + + $('#extra').on('click', '#addNewPrimary', function () { + $.post( + 'normalization.php', + { + 'ajax_request': true, + 'db': PMA_commonParams.get('db'), + 'table': PMA_commonParams.get('table'), + 'server': PMA_commonParams.get('server'), + 'addNewPrimary': true + }, + function (data) { + if (data.success === true) { + $('#newCols').html(data.message); + $('.default_value').hide(); + $('.enum_notice').hide(); + + $('') + .attr({ type: 'submit', id: 'saveNewPrimary', value: PMA_messages.strSave }) + .appendTo('.tblFooters'); + $('') + .attr({ type: 'submit', id: 'cancelSplit', value: PMA_messages.strCancel }) + .on('click', function () { + $('#newCols').html(''); + $(this).parent().html(''); + }) + .appendTo('.tblFooters'); + } else { + PMA_ajaxShowMessage(data.error, false); + } + } + ); + return false; + }); + $('.tblFooters').on('click', '#saveNewPrimary', function () { + var datastring = $('#newCols :input').serialize(); + var argsep = PMA_commonParams.get('arg_separator'); + datastring += argsep + 'field_key[0]=primary_0' + argsep + 'ajax_request=1' + argsep + 'do_save_data=1' + argsep + 'field_where=last'; + $.post('tbl_addfield.php', datastring, function (data) { + if (data.success === true) { + $('#mainContent h4').html(PMA_messages.strPrimaryKeyAdded); + $('#mainContent p').html(PMA_messages.strToNextStep); + $('#mainContent #extra').html(''); + $('#mainContent #newCols').html(''); + $('.tblFooters').html(''); + setTimeout(function () { + goToStep3(); + }, 2000); + } else { + PMA_ajaxShowMessage(data.error, false); + } + }); + }); + $('#extra').on('click', '#removeRedundant', function () { + var dropQuery = 'ALTER TABLE `' + PMA_commonParams.get('table') + '` '; + $('#extra input[type=checkbox]:checked').each(function () { + dropQuery += 'DROP `' + $(this).val() + '`, '; + }); + dropQuery = dropQuery.slice(0, -2); + $.post( + 'sql.php', + { + 'ajax_request': true, + 'db': PMA_commonParams.get('db'), + 'table': PMA_commonParams.get('table'), + 'server': PMA_commonParams.get('server'), + 'sql_query': dropQuery, + 'is_js_confirmed': 1 + }, + function (data) { + if (data.success === true) { + goToStep2('goToFinish1NF'); + } else { + PMA_ajaxShowMessage(data.error, false); + } + } + ); + }); + $('#extra').on('click', '#moveRepeatingGroup', function () { + var repeatingCols = ''; + $('#extra input[type=checkbox]:checked').each(function () { + repeatingCols += $(this).val() + ', '; + }); + + if (repeatingCols !== '') { + var newColName = $('#extra input[type=checkbox]:checked:first').val(); + repeatingCols = repeatingCols.slice(0, -2); + var confirmStr = PMA_sprintf(PMA_messages.strMoveRepeatingGroup, escapeHtml(repeatingCols), escapeHtml(PMA_commonParams.get('table'))); + confirmStr += '' + + '( ' + escapeHtml(primary_key.toString()) + ', )' + + ''; + $('#newCols').html(confirmStr); + + $('') + .attr({ type: 'submit', value: PMA_messages.strCancel }) + .on('click', function () { + $('#newCols').html(''); + $('#extra input[type=checkbox]').prop('checked', false); + }) + .appendTo('.tblFooters'); + $('') + .attr({ type: 'submit', value: PMA_messages.strGo }) + .on('click', function () { + moveRepeatingGroup(repeatingCols); + }) + .appendTo('.tblFooters'); + } + }); + $('#mainContent p').on('click', '#createPrimaryKey', function (event) { + event.preventDefault(); + var url = { create_index: 1, + server: PMA_commonParams.get('server'), + db: PMA_commonParams.get('db'), + table: PMA_commonParams.get('table'), + added_fields: 1, + add_fields:1, + index: { Key_name:'PRIMARY' }, + ajax_request: true + }; + var title = PMA_messages.strAddPrimaryKey; + indexEditorDialog(url, title, function () { + // on success + $('.sqlqueryresults').remove(); + $('.result_query').remove(); + $('.tblFooters').html(''); + goToStep2('goToStep3'); + }); + return false; + }); + $('#mainContent').on('click', '#backEditPd', function () { + $('#mainContent').html(backup); + }); + $('#mainContent').on('click', '#showPossiblePd', function () { + if ($(this).hasClass('hideList')) { + $(this).html('+ ' + PMA_messages.strShowPossiblePd); + $(this).removeClass('hideList'); + $('#newCols').slideToggle('slow'); + return false; + } + if ($('#newCols').html() !== '') { + $('#showPossiblePd').html('- ' + PMA_messages.strHidePd); + $('#showPossiblePd').addClass('hideList'); + $('#newCols').slideToggle('slow'); + return false; + } + $('#newCols').insertAfter('#mainContent h4'); + $('#newCols').html('
    ' + PMA_messages.strLoading + '
    ' + PMA_messages.strWaitForPd + '
    '); + $.post( + 'normalization.php', + { + 'ajax_request': true, + 'db': PMA_commonParams.get('db'), + 'table': PMA_commonParams.get('table'), + 'server': PMA_commonParams.get('server'), + 'findPdl': true + }, function (data) { + $('#showPossiblePd').html('- ' + PMA_messages.strHidePd); + $('#showPossiblePd').addClass('hideList'); + $('#newCols').html(data.message); + }); + }); + $('#mainContent').on('click', '.pickPd', function () { + var strColsLeft = $(this).next('.determinants').html(); + var colsLeft = strColsLeft.split(','); + var strColsRight = $(this).next().next().html(); + var colsRight = strColsRight.split(','); + for (var i in colsRight) { + $('form[data-colname="' + colsRight[i].trim() + '"] input[type="checkbox"]').prop('checked', false); + for (var j in colsLeft) { + $('form[data-colname="' + colsRight[i].trim() + '"] input[value="' + colsLeft[j].trim() + '"]').prop('checked', true); + } + } + }); +}); diff --git a/php/apps/phpmyadmin49/html/js/page_settings.js b/php/apps/phpmyadmin49/html/js/page_settings.js new file mode 100644 index 00000000..7de9c038 --- /dev/null +++ b/php/apps/phpmyadmin49/html/js/page_settings.js @@ -0,0 +1,59 @@ +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * @fileoverview function used for page-related settings + * @name Page-related settings + * + * @requires jQuery + * @requires jQueryUI + * @required js/functions.js + */ + +function showSettings (selector) { + var buttons = {}; + buttons[PMA_messages.strApply] = function () { + $('.config-form').submit(); + }; + + buttons[PMA_messages.strCancel] = function () { + $(this).dialog('close'); + }; + + // Keeping a clone to restore in case the user cancels the operation + var $clone = $(selector + ' .page_settings').clone(true); + $(selector) + .dialog({ + title: PMA_messages.strPageSettings, + width: 700, + minHeight: 250, + modal: true, + open: function () { + $(this).dialog('option', 'maxHeight', $(window).height() - $(this).offset().top); + }, + close: function () { + $(selector + ' .page_settings').replaceWith($clone); + }, + buttons: buttons + }); +} + +function showPageSettings () { + showSettings('#page_settings_modal'); +} + +function showNaviSettings () { + showSettings('#pma_navigation_settings'); +} + +AJAX.registerTeardown('page_settings.js', function () { + $('#page_settings_icon').css('display', 'none'); + $('#page_settings_icon').off('click'); + $('#pma_navigation_settings_icon').off('click'); +}); + +AJAX.registerOnload('page_settings.js', function () { + if ($('#page_settings_modal').length) { + $('#page_settings_icon').css('display', 'inline'); + $('#page_settings_icon').on('click', showPageSettings); + } + $('#pma_navigation_settings_icon').on('click', showNaviSettings); +}); diff --git a/php/apps/phpmyadmin49/html/js/replication.js b/php/apps/phpmyadmin49/html/js/replication.js new file mode 100644 index 00000000..602a5a5e --- /dev/null +++ b/php/apps/phpmyadmin49/html/js/replication.js @@ -0,0 +1,92 @@ +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * for server_replication.php + * + */ + +var random_server_id = Math.floor(Math.random() * 10000000); +var conf_prefix = 'server-id=' + random_server_id + '\nlog_bin=mysql-bin\nlog_error=mysql-bin.err\n'; + +function update_config () { + var conf_ignore = 'binlog_ignore_db='; + var conf_do = 'binlog_do_db='; + var database_list = ''; + + if ($('#db_select option:selected').size() === 0) { + $('#rep').text(conf_prefix); + } else if ($('#db_type option:selected').val() === 'all') { + $('#db_select option:selected').each(function () { + database_list += conf_ignore + $(this).val() + '\n'; + }); + $('#rep').text(conf_prefix + database_list); + } else { + $('#db_select option:selected').each(function () { + database_list += conf_do + $(this).val() + '\n'; + }); + $('#rep').text(conf_prefix + database_list); + } +} + +/** + * Unbind all event handlers before tearing down a page + */ +AJAX.registerTeardown('replication.js', function () { + $('#db_type').off('change'); + $('#db_select').off('change'); + $('#master_status_href').off('click'); + $('#master_slaves_href').off('click'); + $('#slave_status_href').off('click'); + $('#slave_control_href').off('click'); + $('#slave_errormanagement_href').off('click'); + $('#slave_synchronization_href').off('click'); + $('#db_reset_href').off('click'); + $('#db_select_href').off('click'); + $('#reset_slave').off('click'); +}); + +AJAX.registerOnload('replication.js', function () { + $('#rep').text(conf_prefix); + $('#db_type').change(update_config); + $('#db_select').change(update_config); + + $('#master_status_href').click(function () { + $('#replication_master_section').toggle(); + }); + $('#master_slaves_href').click(function () { + $('#replication_slaves_section').toggle(); + }); + $('#slave_status_href').click(function () { + $('#replication_slave_section').toggle(); + }); + $('#slave_control_href').click(function () { + $('#slave_control_gui').toggle(); + }); + $('#slave_errormanagement_href').click(function () { + $('#slave_errormanagement_gui').toggle(); + }); + $('#slave_synchronization_href').click(function () { + $('#slave_synchronization_gui').toggle(); + }); + $('#db_reset_href').click(function () { + $('#db_select option:selected').prop('selected', false); + $('#db_select').trigger('change'); + }); + $('#db_select_href').click(function () { + $('#db_select option').prop('selected', true); + $('#db_select').trigger('change'); + }); + $('#reset_slave').click(function (e) { + e.preventDefault(); + var $anchor = $(this); + var question = PMA_messages.strResetSlaveWarning; + $anchor.PMA_confirm(question, $anchor.attr('href'), function (url) { + PMA_ajaxShowMessage(); + AJAX.source = $anchor; + var params = getJSConfirmCommonParam({ + 'ajax_page_request': true, + 'ajax_request': true + }, $anchor.getPostData()); + $.post(url, params, AJAX.responseHandler); + }); + }); +}); diff --git a/php/apps/phpmyadmin49/html/js/rte.js b/php/apps/phpmyadmin49/html/js/rte.js new file mode 100644 index 00000000..d51538b4 --- /dev/null +++ b/php/apps/phpmyadmin49/html/js/rte.js @@ -0,0 +1,1075 @@ +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * JavaScript functionality for Routines, Triggers and Events. + * + * @package PhpMyadmin + */ +/** + * @var RTE Contains all the JavaScript functionality + * for Routines, Triggers and Events + */ +var RTE = { + /** + * Construct for the object that provides the + * functionality for Routines, Triggers and Events + */ + object: function (type) { + $.extend(this, RTE.COMMON); + this.editorType = type; + + switch (type) { + case 'routine': + $.extend(this, RTE.ROUTINE); + break; + case 'trigger': + // nothing extra yet for triggers + break; + case 'event': + $.extend(this, RTE.EVENT); + break; + default: + break; + } + }, + /** + * @var string param_template Template for a row in the routine editor + */ + param_template: '' +}; + +/** + * @var RTE.COMMON a JavaScript namespace containing the functionality + * for Routines, Triggers and Events + * + * This namespace is extended by the functionality required + * to handle a specific item (a routine, trigger or event) + * in the relevant javascript files in this folder + */ +RTE.COMMON = { + /** + * @var $ajaxDialog Query object containing the reference to the + * dialog that contains the editor + */ + $ajaxDialog: null, + /** + * @var syntaxHiglighter Reference to the codemirror editor + */ + syntaxHiglighter: null, + /** + * @var buttonOptions Object containing options for + * the jQueryUI dialog buttons + */ + buttonOptions: {}, + /** + * @var editorType Type of the editor + */ + editorType: null, + /** + * Validate editor form fields. + */ + validate: function () { + /** + * @var $elm a jQuery object containing the reference + * to an element that is being validated + */ + var $elm = null; + // Common validation. At the very least the name + // and the definition must be provided for an item + $elm = $('table.rte_table').last().find('input[name=item_name]'); + if ($elm.val() === '') { + $elm.focus(); + alert(PMA_messages.strFormEmpty); + return false; + } + $elm = $('table.rte_table').find('textarea[name=item_definition]'); + if ($elm.val() === '') { + if (this.syntaxHiglighter !== null) { + this.syntaxHiglighter.focus(); + } else { + $('textarea[name=item_definition]').last().focus(); + } + alert(PMA_messages.strFormEmpty); + return false; + } + // The validation has so far passed, so now + // we can validate item-specific fields. + return this.validateCustom(); + }, // end validate() + /** + * Validate custom editor form fields. + * This function can be overridden by + * other files in this folder + */ + validateCustom: function () { + return true; + }, // end validateCustom() + /** + * Execute some code after the ajax + * dialog for the editor is shown. + * This function can be overridden by + * other files in this folder + */ + postDialogShow: function () { + // Nothing by default + }, // end postDialogShow() + + exportDialog: function ($this) { + var $msg = PMA_ajaxShowMessage(); + if ($this.hasClass('mult_submit')) { + var combined = { + success: true, + title: PMA_messages.strExport, + message: '', + error: '' + }; + // export anchors of all selected rows + var export_anchors = $('input.checkall:checked').parents('tr').find('.export_anchor'); + var count = export_anchors.length; + var returnCount = 0; + + // No routine is exportable (due to privilege issues) + if (count === 0) { + PMA_ajaxShowMessage(PMA_messages.NoExportable); + } + + export_anchors.each(function () { + $.get($(this).attr('href'), { 'ajax_request': true }, function (data) { + returnCount++; + if (data.success === true) { + combined.message += '\n' + data.message + '\n'; + if (returnCount === count) { + showExport(combined); + } + } else { + // complain even if one export is failing + combined.success = false; + combined.error += '\n' + data.error + '\n'; + if (returnCount === count) { + showExport(combined); + } + } + }); + }); + } else { + $.get($this.attr('href'), { 'ajax_request': true }, showExport); + } + PMA_ajaxRemoveMessage($msg); + + function showExport (data) { + if (data.success === true) { + PMA_ajaxRemoveMessage($msg); + /** + * @var button_options Object containing options + * for jQueryUI dialog buttons + */ + var button_options = {}; + button_options[PMA_messages.strClose] = function () { + $(this).dialog('close').remove(); + }; + /** + * Display the dialog to the user + */ + data.message = ''; + var $ajaxDialog = $('
    ' + data.message + '
    ').dialog({ + width: 500, + buttons: button_options, + title: data.title + }); + // Attach syntax highlighted editor to export dialog + /** + * @var $elm jQuery object containing the reference + * to the Export textarea. + */ + var $elm = $ajaxDialog.find('textarea'); + PMA_getSQLEditor($elm); + } else { + PMA_ajaxShowMessage(data.error, false); + } + } // end showExport() + }, // end exportDialog() + editorDialog: function (is_new, $this) { + var that = this; + /** + * @var $edit_row jQuery object containing the reference to + * the row of the the item being edited + * from the list of items + */ + var $edit_row = null; + if ($this.hasClass('edit_anchor')) { + // Remeber the row of the item being edited for later, + // so that if the edit is successful, we can replace the + // row with info about the modified item. + $edit_row = $this.parents('tr'); + } + /** + * @var $msg jQuery object containing the reference to + * the AJAX message shown to the user + */ + var $msg = PMA_ajaxShowMessage(); + $.get($this.attr('href'), { 'ajax_request': true }, function (data) { + if (data.success === true) { + // We have successfully fetched the editor form + PMA_ajaxRemoveMessage($msg); + // Now define the function that is called when + // the user presses the "Go" button + that.buttonOptions[PMA_messages.strGo] = function () { + // Move the data from the codemirror editor back to the + // textarea, where it can be used in the form submission. + if (typeof CodeMirror !== 'undefined') { + that.syntaxHiglighter.save(); + } + // Validate editor and submit request, if passed. + if (that.validate()) { + /** + * @var data Form data to be sent in the AJAX request + */ + var data = $('form.rte_form').last().serialize(); + $msg = PMA_ajaxShowMessage( + PMA_messages.strProcessingRequest + ); + var url = $('form.rte_form').last().attr('action'); + $.post(url, data, function (data) { + if (data.success === true) { + // Item created successfully + PMA_ajaxRemoveMessage($msg); + PMA_slidingMessage(data.message); + that.$ajaxDialog.dialog('close'); + // If we are in 'edit' mode, we must + // remove the reference to the old row. + if (mode === 'edit' && $edit_row !== null) { + $edit_row.remove(); + } + // Sometimes, like when moving a trigger from + // a table to another one, the new row should + // not be inserted into the list. In this case + // "data.insert" will be set to false. + if (data.insert) { + // Insert the new row at the correct + // location in the list of items + /** + * @var text Contains the name of an item from + * the list that is used in comparisons + * to find the correct location where + * to insert a new row. + */ + var text = ''; + /** + * @var inserted Whether a new item has been + * inserted in the list or not + */ + var inserted = false; + $('table.data').find('tr').each(function () { + text = $(this) + .children('td') + .eq(0) + .find('strong') + .text() + .toUpperCase(); + text = $.trim(text); + if (text !== '' && text > data.name) { + $(this).before(data.new_row); + inserted = true; + return false; + } + }); + if (! inserted) { + // If we didn't manage to insert the row yet, + // it must belong at the end of the list, + // so we insert it there. + $('table.data').append(data.new_row); + } + // Fade-in the new row + $('tr.ajaxInsert') + .show('slow') + .removeClass('ajaxInsert'); + } else if ($('table.data').find('tr').has('td').length === 0) { + // If we are not supposed to insert the new row, + // we will now check if the table is empty and + // needs to be hidden. This will be the case if + // we were editing the only item in the list, + // which we removed and will not be inserting + // something else in its place. + $('table.data').hide('slow', function () { + $('#nothing2display').show('slow'); + }); + } + // Now we have inserted the row at the correct + // position, but surely at least some row classes + // are wrong now. So we will itirate throught + // all rows and assign correct classes to them + /** + * @var ct Count of processed rows + */ + var ct = 0; + /** + * @var rowclass Class to be attached to the row + * that is being processed + */ + var rowclass = ''; + $('table.data').find('tr').has('td').each(function () { + rowclass = (ct % 2 === 0) ? 'odd' : 'even'; + $(this).removeClass().addClass(rowclass); + ct++; + }); + // If this is the first item being added, remove + // the "No items" message and show the list. + if ($('table.data').find('tr').has('td').length > 0 && + $('#nothing2display').is(':visible') + ) { + $('#nothing2display').hide('slow', function () { + $('table.data').show('slow'); + }); + } + PMA_reloadNavigation(); + } else { + PMA_ajaxShowMessage(data.error, false); + } + }); // end $.post() + } // end "if (that.validate())" + }; // end of function that handles the submission of the Editor + that.buttonOptions[PMA_messages.strClose] = function () { + $(this).dialog('close'); + }; + /** + * Display the dialog to the user + */ + that.$ajaxDialog = $('
    ' + data.message + '
    ').dialog({ + width: 700, + minWidth: 500, + maxHeight: $(window).height(), + buttons: that.buttonOptions, + title: data.title, + modal: true, + open: function () { + if ($('#rteDialog').parents('.ui-dialog').height() > $(window).height()) { + $('#rteDialog').dialog('option', 'height', $(window).height()); + } + $(this).find('input[name=item_name]').focus(); + $(this).find('input.datefield').each(function () { + PMA_addDatepicker($(this).css('width', '95%'), 'date'); + }); + $(this).find('input.datetimefield').each(function () { + PMA_addDatepicker($(this).css('width', '95%'), 'datetime'); + }); + $.datepicker.initialized = false; + }, + close: function () { + $(this).remove(); + } + }); + /** + * @var mode Used to remeber whether the editor is in + * "Edit" or "Add" mode + */ + var mode = 'add'; + if ($('input[name=editor_process_edit]').length > 0) { + mode = 'edit'; + } + // Attach syntax highlighted editor to the definition + /** + * @var elm jQuery object containing the reference to + * the Definition textarea. + */ + var $elm = $('textarea[name=item_definition]').last(); + var linterOptions = {}; + linterOptions[that.editorType + '_editor'] = true; + that.syntaxHiglighter = PMA_getSQLEditor($elm, {}, null, linterOptions); + + // Execute item-specific code + that.postDialogShow(data); + } else { + PMA_ajaxShowMessage(data.error, false); + } + }); // end $.get() + }, + + dropDialog: function ($this) { + /** + * @var $curr_row Object containing reference to the current row + */ + var $curr_row = $this.parents('tr'); + /** + * @var question String containing the question to be asked for confirmation + */ + var question = $('
    ').text( + $curr_row.children('td').children('.drop_sql').html() + ); + // We ask for confirmation first here, before submitting the ajax request + $this.PMA_confirm(question, $this.attr('href'), function (url) { + /** + * @var msg jQuery object containing the reference to + * the AJAX message shown to the user + */ + var $msg = PMA_ajaxShowMessage(PMA_messages.strProcessingRequest); + var params = getJSConfirmCommonParam(this, $this.getPostData()); + $.post(url, params, function (data) { + if (data.success === true) { + /** + * @var $table Object containing reference + * to the main list of elements + */ + var $table = $curr_row.parent(); + // Check how many rows will be left after we remove + // the one that the user has requested us to remove + if ($table.find('tr').length === 3) { + // If there are two rows left, it means that they are + // the header of the table and the rows that we are + // about to remove, so after the removal there will be + // nothing to show in the table, so we hide it. + $table.hide('slow', function () { + $(this).find('tr.even, tr.odd').remove(); + $('.withSelected').remove(); + $('#nothing2display').show('slow'); + }); + } else { + $curr_row.hide('slow', function () { + $(this).remove(); + // Now we have removed the row from the list, but maybe + // some row classes are wrong now. So we will itirate + // throught all rows and assign correct classes to them. + /** + * @var ct Count of processed rows + */ + var ct = 0; + /** + * @var rowclass Class to be attached to the row + * that is being processed + */ + var rowclass = ''; + $table.find('tr').has('td').each(function () { + rowclass = (ct % 2 === 1) ? 'odd' : 'even'; + $(this).removeClass().addClass(rowclass); + ct++; + }); + }); + } + // Get rid of the "Loading" message + PMA_ajaxRemoveMessage($msg); + // Show the query that we just executed + PMA_slidingMessage(data.sql_query); + PMA_reloadNavigation(); + } else { + PMA_ajaxShowMessage(data.error, false); + } + }); // end $.post() + }); // end $.PMA_confirm() + }, + + dropMultipleDialog: function ($this) { + // We ask for confirmation here + $this.PMA_confirm(PMA_messages.strDropRTEitems, '', function (url) { + /** + * @var msg jQuery object containing the reference to + * the AJAX message shown to the user + */ + var $msg = PMA_ajaxShowMessage(PMA_messages.strProcessingRequest); + + // drop anchors of all selected rows + var drop_anchors = $('input.checkall:checked').parents('tr').find('.drop_anchor'); + var success = true; + var count = drop_anchors.length; + var returnCount = 0; + + drop_anchors.each(function () { + var $anchor = $(this); + /** + * @var $curr_row Object containing reference to the current row + */ + var $curr_row = $anchor.parents('tr'); + var params = getJSConfirmCommonParam(this, $anchor.getPostData()); + $.post($anchor.attr('href'), params, function (data) { + returnCount++; + if (data.success === true) { + /** + * @var $table Object containing reference + * to the main list of elements + */ + var $table = $curr_row.parent(); + // Check how many rows will be left after we remove + // the one that the user has requested us to remove + if ($table.find('tr').length === 3) { + // If there are two rows left, it means that they are + // the header of the table and the rows that we are + // about to remove, so after the removal there will be + // nothing to show in the table, so we hide it. + $table.hide('slow', function () { + $(this).find('tr.even, tr.odd').remove(); + $('.withSelected').remove(); + $('#nothing2display').show('slow'); + }); + } else { + $curr_row.hide('fast', function () { + $(this).remove(); + // Now we have removed the row from the list, but maybe + // some row classes are wrong now. So we will itirate + // throught all rows and assign correct classes to them. + /** + * @var ct Count of processed rows + */ + var ct = 0; + /** + * @var rowclass Class to be attached to the row + * that is being processed + */ + var rowclass = ''; + $table.find('tr').has('td').each(function () { + rowclass = (ct % 2 === 1) ? 'odd' : 'even'; + $(this).removeClass().addClass(rowclass); + ct++; + }); + }); + } + if (returnCount === count) { + if (success) { + // Get rid of the "Loading" message + PMA_ajaxRemoveMessage($msg); + $('#rteListForm_checkall').prop({ checked: false, indeterminate: false }); + } + PMA_reloadNavigation(); + } + } else { + PMA_ajaxShowMessage(data.error, false); + success = false; + if (returnCount === count) { + PMA_reloadNavigation(); + } + } + }); // end $.post() + }); // end drop_anchors.each() + }); // end $.PMA_confirm() + } +}; // end RTE namespace + +/** + * @var RTE.EVENT JavaScript functionality for events + */ +RTE.EVENT = { + validateCustom: function () { + /** + * @var elm a jQuery object containing the reference + * to an element that is being validated + */ + var $elm = null; + if (this.$ajaxDialog.find('select[name=item_type]').find(':selected').val() === 'RECURRING') { + // The interval field must not be empty for recurring events + $elm = this.$ajaxDialog.find('input[name=item_interval_value]'); + if ($elm.val() === '') { + $elm.focus(); + alert(PMA_messages.strFormEmpty); + return false; + } + } else { + // The execute_at field must not be empty for "once off" events + $elm = this.$ajaxDialog.find('input[name=item_execute_at]'); + if ($elm.val() === '') { + $elm.focus(); + alert(PMA_messages.strFormEmpty); + return false; + } + } + return true; + } +}; + +/** + * @var RTE.ROUTINE JavaScript functionality for routines + */ +RTE.ROUTINE = { + /** + * Overriding the postDialogShow() function defined in common.js + * + * @param data JSON-encoded data from the ajax request + */ + postDialogShow: function (data) { + // Cache the template for a parameter table row + RTE.param_template = data.param_template; + var that = this; + // Make adjustments in the dialog to make it AJAX compatible + $('td.routine_param_remove').show(); + $('input[name=routine_removeparameter]').remove(); + $('input[name=routine_addparameter]').css('width', '100%'); + // Enable/disable the 'options' dropdowns for parameters as necessary + $('table.routine_params_table').last().find('th[colspan=2]').attr('colspan', '1'); + $('table.routine_params_table').last().find('tr').has('td').each(function () { + that.setOptionsForParameter( + $(this).find('select[name^=item_param_type]'), + $(this).find('input[name^=item_param_length]'), + $(this).find('select[name^=item_param_opts_text]'), + $(this).find('select[name^=item_param_opts_num]') + ); + }); + // Enable/disable the 'options' dropdowns for + // function return value as necessary + this.setOptionsForParameter( + $('table.rte_table').last().find('select[name=item_returntype]'), + $('table.rte_table').last().find('input[name=item_returnlength]'), + $('table.rte_table').last().find('select[name=item_returnopts_text]'), + $('table.rte_table').last().find('select[name=item_returnopts_num]') + ); + // Allow changing parameter order + $('.routine_params_table tbody').sortable({ + containment: '.routine_params_table tbody', + handle: '.dragHandle', + stop: function (event, ui) { + that.reindexParameters(); + }, + }); + }, + /** + * Reindexes the parameters after dropping a parameter or reordering parameters + */ + reindexParameters: function () { + /** + * @var index Counter used for reindexing the input + * fields in the routine parameters table + */ + var index = 0; + $('table.routine_params_table tbody').find('tr').each(function () { + $(this).find(':input').each(function () { + /** + * @var inputname The value of the name attribute of + * the input field being reindexed + */ + var inputname = $(this).attr('name'); + if (inputname.substr(0, 14) === 'item_param_dir') { + $(this).attr('name', inputname.substr(0, 14) + '[' + index + ']'); + } else if (inputname.substr(0, 15) === 'item_param_name') { + $(this).attr('name', inputname.substr(0, 15) + '[' + index + ']'); + } else if (inputname.substr(0, 15) === 'item_param_type') { + $(this).attr('name', inputname.substr(0, 15) + '[' + index + ']'); + } else if (inputname.substr(0, 17) === 'item_param_length') { + $(this).attr('name', inputname.substr(0, 17) + '[' + index + ']'); + $(this).attr('id', 'item_param_length_' + index); + } else if (inputname.substr(0, 20) === 'item_param_opts_text') { + $(this).attr('name', inputname.substr(0, 20) + '[' + index + ']'); + } else if (inputname.substr(0, 19) === 'item_param_opts_num') { + $(this).attr('name', inputname.substr(0, 19) + '[' + index + ']'); + } + }); + index++; + }); + }, + /** + * Overriding the validateCustom() function defined in common.js + */ + validateCustom: function () { + /** + * @var isSuccess Stores the outcome of the validation + */ + var isSuccess = true; + /** + * @var inputname The value of the "name" attribute for + * the field that is being processed + */ + var inputname = ''; + this.$ajaxDialog.find('table.routine_params_table').last().find('tr').each(function () { + // Every parameter of a routine must have + // a non-empty direction, name and type + if (isSuccess) { + $(this).find(':input').each(function () { + inputname = $(this).attr('name'); + if (inputname.substr(0, 14) === 'item_param_dir' || + inputname.substr(0, 15) === 'item_param_name' || + inputname.substr(0, 15) === 'item_param_type') { + if ($(this).val() === '') { + $(this).focus(); + isSuccess = false; + return false; + } + } + }); + } else { + return false; + } + }); + if (! isSuccess) { + alert(PMA_messages.strFormEmpty); + return false; + } + this.$ajaxDialog.find('table.routine_params_table').last().find('tr').each(function () { + // SET, ENUM, VARCHAR and VARBINARY fields must have length/values + var $inputtyp = $(this).find('select[name^=item_param_type]'); + var $inputlen = $(this).find('input[name^=item_param_length]'); + if ($inputtyp.length && $inputlen.length) { + if (($inputtyp.val() === 'ENUM' || $inputtyp.val() === 'SET' || $inputtyp.val().substr(0, 3) === 'VAR') && + $inputlen.val() === '' + ) { + $inputlen.focus(); + isSuccess = false; + return false; + } + } + }); + if (! isSuccess) { + alert(PMA_messages.strFormEmpty); + return false; + } + if (this.$ajaxDialog.find('select[name=item_type]').find(':selected').val() === 'FUNCTION') { + // The length/values of return variable for functions must + // be set, if the type is SET, ENUM, VARCHAR or VARBINARY. + var $returntyp = this.$ajaxDialog.find('select[name=item_returntype]'); + var $returnlen = this.$ajaxDialog.find('input[name=item_returnlength]'); + if (($returntyp.val() === 'ENUM' || $returntyp.val() === 'SET' || $returntyp.val().substr(0, 3) === 'VAR') && + $returnlen.val() === '' + ) { + $returnlen.focus(); + alert(PMA_messages.strFormEmpty); + return false; + } + } + if ($('select[name=item_type]').find(':selected').val() === 'FUNCTION') { + // A function must contain a RETURN statement in its definition + if (this.$ajaxDialog.find('table.rte_table').find('textarea[name=item_definition]').val().toUpperCase().indexOf('RETURN') < 0) { + this.syntaxHiglighter.focus(); + alert(PMA_messages.MissingReturn); + return false; + } + } + return true; + }, + /** + * Enable/disable the "options" dropdown and "length" input for + * parameters and the return variable in the routine editor + * as necessary. + * + * @param type a jQuery object containing the reference + * to the "Type" dropdown box + * @param len a jQuery object containing the reference + * to the "Length" input box + * @param text a jQuery object containing the reference + * to the dropdown box with options for + * parameters of text type + * @param num a jQuery object containing the reference + * to the dropdown box with options for + * parameters of numeric type + */ + setOptionsForParameter: function ($type, $len, $text, $num) { + /** + * @var no_opts a jQuery object containing the reference + * to an element to be displayed when no + * options are available + */ + var $no_opts = $text.parent().parent().find('.no_opts'); + /** + * @var no_len a jQuery object containing the reference + * to an element to be displayed when no + * "length/values" field is available + */ + var $no_len = $len.parent().parent().find('.no_len'); + + // Process for parameter options + switch ($type.val()) { + case 'TINYINT': + case 'SMALLINT': + case 'MEDIUMINT': + case 'INT': + case 'BIGINT': + case 'DECIMAL': + case 'FLOAT': + case 'DOUBLE': + case 'REAL': + $text.parent().hide(); + $num.parent().show(); + $no_opts.hide(); + break; + case 'TINYTEXT': + case 'TEXT': + case 'MEDIUMTEXT': + case 'LONGTEXT': + case 'CHAR': + case 'VARCHAR': + case 'SET': + case 'ENUM': + $text.parent().show(); + $num.parent().hide(); + $no_opts.hide(); + break; + default: + $text.parent().hide(); + $num.parent().hide(); + $no_opts.show(); + break; + } + // Process for parameter length + switch ($type.val()) { + case 'DATE': + case 'TINYBLOB': + case 'TINYTEXT': + case 'BLOB': + case 'TEXT': + case 'MEDIUMBLOB': + case 'MEDIUMTEXT': + case 'LONGBLOB': + case 'LONGTEXT': + $text.closest('tr').find('a:first').hide(); + $len.parent().hide(); + $no_len.show(); + break; + default: + if ($type.val() === 'ENUM' || $type.val() === 'SET') { + $text.closest('tr').find('a:first').show(); + } else { + $text.closest('tr').find('a:first').hide(); + } + $len.parent().show(); + $no_len.hide(); + break; + } + }, + executeDialog: function ($this) { + var that = this; + /** + * @var msg jQuery object containing the reference to + * the AJAX message shown to the user + */ + var $msg = PMA_ajaxShowMessage(); + var params = getJSConfirmCommonParam($this[0], $this.getPostData()); + $.post($this.attr('href'), params, function (data) { + if (data.success === true) { + PMA_ajaxRemoveMessage($msg); + // If 'data.dialog' is true we show a dialog with a form + // to get the input parameters for routine, otherwise + // we just show the results of the query + if (data.dialog) { + // Define the function that is called when + // the user presses the "Go" button + that.buttonOptions[PMA_messages.strGo] = function () { + /** + * @var data Form data to be sent in the AJAX request + */ + var data = $('form.rte_form').last().serialize(); + $msg = PMA_ajaxShowMessage( + PMA_messages.strProcessingRequest + ); + $.post('db_routines.php', data, function (data) { + if (data.success === true) { + // Routine executed successfully + PMA_ajaxRemoveMessage($msg); + PMA_slidingMessage(data.message); + $ajaxDialog.dialog('close'); + } else { + PMA_ajaxShowMessage(data.error, false); + } + }); + }; + that.buttonOptions[PMA_messages.strClose] = function () { + $(this).dialog('close'); + }; + /** + * Display the dialog to the user + */ + var $ajaxDialog = $('
    ' + data.message + '
    ').dialog({ + width: 650, + buttons: that.buttonOptions, + title: data.title, + modal: true, + close: function () { + $(this).remove(); + } + }); + $ajaxDialog.find('input[name^=params]').first().focus(); + /** + * Attach the datepickers to the relevant form fields + */ + $ajaxDialog.find('input.datefield, input.datetimefield').each(function () { + PMA_addDatepicker($(this).css('width', '95%')); + }); + /* + * Define the function if the user presses enter + */ + $('form.rte_form').on('keyup', function (event) { + event.preventDefault(); + if (event.keyCode === 13) { + /** + * @var data Form data to be sent in the AJAX request + */ + var data = $(this).serialize(); + $msg = PMA_ajaxShowMessage( + PMA_messages.strProcessingRequest + ); + var url = $(this).attr('action'); + $.post(url, data, function (data) { + if (data.success === true) { + // Routine executed successfully + PMA_ajaxRemoveMessage($msg); + PMA_slidingMessage(data.message); + $('form.rte_form').off('keyup'); + $ajaxDialog.remove(); + } else { + PMA_ajaxShowMessage(data.error, false); + } + }); + } + }); + } else { + // Routine executed successfully + PMA_slidingMessage(data.message); + } + } else { + PMA_ajaxShowMessage(data.error, false); + } + }); // end $.post() + } +}; + +/** + * Attach Ajax event handlers for the Routines, Triggers and Events editor + */ +$(function () { + /** + * Attach Ajax event handlers for the Add/Edit functionality. + */ + $(document).on('click', 'a.ajax.add_anchor, a.ajax.edit_anchor', function (event) { + event.preventDefault(); + var type = $(this).attr('href').substr(0, $(this).attr('href').indexOf('?')); + if (type.indexOf('routine') !== -1) { + type = 'routine'; + } else if (type.indexOf('trigger') !== -1) { + type = 'trigger'; + } else if (type.indexOf('event') !== -1) { + type = 'event'; + } else { + type = ''; + } + var dialog = new RTE.object(type); + dialog.editorDialog($(this).hasClass('add_anchor'), $(this)); + }); // end $(document).on() + + /** + * Attach Ajax event handlers for the Execute routine functionality + */ + $(document).on('click', 'a.ajax.exec_anchor', function (event) { + event.preventDefault(); + var dialog = new RTE.object('routine'); + dialog.executeDialog($(this)); + }); // end $(document).on() + + /** + * Attach Ajax event handlers for Export of Routines, Triggers and Events + */ + $(document).on('click', 'a.ajax.export_anchor', function (event) { + event.preventDefault(); + var dialog = new RTE.object(); + dialog.exportDialog($(this)); + }); // end $(document).on() + + $(document).on('click', '#rteListForm.ajax .mult_submit[value="export"]', function (event) { + event.preventDefault(); + var dialog = new RTE.object(); + dialog.exportDialog($(this)); + }); // end $(document).on() + + /** + * Attach Ajax event handlers for Drop functionality + * of Routines, Triggers and Events. + */ + $(document).on('click', 'a.ajax.drop_anchor', function (event) { + event.preventDefault(); + var dialog = new RTE.object(); + dialog.dropDialog($(this)); + }); // end $(document).on() + + $(document).on('click', '#rteListForm.ajax .mult_submit[value="drop"]', function (event) { + event.preventDefault(); + var dialog = new RTE.object(); + dialog.dropMultipleDialog($(this)); + }); // end $(document).on() + + /** + * Attach Ajax event handlers for the "Change event/routine type" + * functionality in the events editor, so that the correct + * rows are shown in the editor when changing the event type + */ + $(document).on('change', 'select[name=item_type]', function () { + $(this) + .closest('table') + .find('tr.recurring_event_row, tr.onetime_event_row, tr.routine_return_row, .routine_direction_cell') + .toggle(); + }); // end $(document).on() + + /** + * Attach Ajax event handlers for the "Change parameter type" + * functionality in the routines editor, so that the correct + * option/length fields, if any, are shown when changing + * a parameter type + */ + $(document).on('change', 'select[name^=item_param_type]', function () { + /** + * @var row jQuery object containing the reference to + * a row in the routine parameters table + */ + var $row = $(this).parents('tr').first(); + var rte = new RTE.object('routine'); + rte.setOptionsForParameter( + $row.find('select[name^=item_param_type]'), + $row.find('input[name^=item_param_length]'), + $row.find('select[name^=item_param_opts_text]'), + $row.find('select[name^=item_param_opts_num]') + ); + }); // end $(document).on() + + /** + * Attach Ajax event handlers for the "Change the type of return + * variable of function" functionality, so that the correct fields, + * if any, are shown when changing the function return type type + */ + $(document).on('change', 'select[name=item_returntype]', function () { + var rte = new RTE.object('routine'); + var $table = $(this).closest('table.rte_table'); + rte.setOptionsForParameter( + $table.find('select[name=item_returntype]'), + $table.find('input[name=item_returnlength]'), + $table.find('select[name=item_returnopts_text]'), + $table.find('select[name=item_returnopts_num]') + ); + }); // end $(document).on() + + /** + * Attach Ajax event handlers for the "Add parameter to routine" functionality + */ + $(document).on('click', 'input[name=routine_addparameter]', function (event) { + event.preventDefault(); + /** + * @var routine_params_table jQuery object containing the reference + * to the routine parameters table + */ + var $routine_params_table = $(this).closest('div.ui-dialog').find('.routine_params_table'); + /** + * @var new_param_row A string containing the HTML code for the + * new row for the routine parameters table + */ + var new_param_row = RTE.param_template.replace(/%s/g, $routine_params_table.find('tr').length - 1); + // Append the new row to the parameters table + $routine_params_table.append(new_param_row); + // Make sure that the row is correctly shown according to the type of routine + if ($(this).closest('div.ui-dialog').find('table.rte_table select[name=item_type]').val() === 'FUNCTION') { + $('tr.routine_return_row').show(); + $('td.routine_direction_cell').hide(); + } + /** + * @var newrow jQuery object containing the reference to the newly + * inserted row in the routine parameters table + */ + var $newrow = $(this).closest('div.ui-dialog').find('table.routine_params_table').find('tr').has('td').last(); + // Enable/disable the 'options' dropdowns for parameters as necessary + var rte = new RTE.object('routine'); + rte.setOptionsForParameter( + $newrow.find('select[name^=item_param_type]'), + $newrow.find('input[name^=item_param_length]'), + $newrow.find('select[name^=item_param_opts_text]'), + $newrow.find('select[name^=item_param_opts_num]') + ); + }); // end $(document).on() + + /** + * Attach Ajax event handlers for the + * "Remove parameter from routine" functionality + */ + $(document).on('click', 'a.routine_param_remove_anchor', function (event) { + event.preventDefault(); + $(this).parent().parent().remove(); + // After removing a parameter, the indices of the name attributes in + // the input fields lose the correct order and need to be reordered. + RTE.ROUTINE.reindexParameters(); + }); // end $(document).on() +}); // end of $() diff --git a/php/apps/phpmyadmin49/html/js/server_databases.js b/php/apps/phpmyadmin49/html/js/server_databases.js new file mode 100644 index 00000000..a892ca8b --- /dev/null +++ b/php/apps/phpmyadmin49/html/js/server_databases.js @@ -0,0 +1,150 @@ +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * @fileoverview functions used on the server databases list page + * @name Server Databases + * + * @requires jQuery + * @requires jQueryUI + * @required js/functions.js + */ + +/** + * Unbind all event handlers before tearing down a page + */ +AJAX.registerTeardown('server_databases.js', function () { + $(document).off('submit', '#dbStatsForm'); + $(document).off('submit', '#create_database_form.ajax'); +}); + +/** + * AJAX scripts for server_databases.php + * + * Actions ajaxified here: + * Drop Databases + * + */ +AJAX.registerOnload('server_databases.js', function () { + /** + * Attach Event Handler for 'Drop Databases' + */ + $(document).on('submit', '#dbStatsForm', function (event) { + event.preventDefault(); + + var $form = $(this); + + /** + * @var selected_dbs Array containing the names of the checked databases + */ + var selected_dbs = []; + // loop over all checked checkboxes, except the .checkall_box checkbox + $form.find('input:checkbox:checked:not(.checkall_box)').each(function () { + $(this).closest('tr').addClass('removeMe'); + selected_dbs[selected_dbs.length] = 'DROP DATABASE `' + escapeHtml($(this).val()) + '`;'; + }); + if (! selected_dbs.length) { + PMA_ajaxShowMessage( + $('
    ').text( + PMA_messages.strNoDatabasesSelected + ), + 2000 + ); + return; + } + /** + * @var question String containing the question to be asked for confirmation + */ + var question = PMA_messages.strDropDatabaseStrongWarning + ' ' + + PMA_sprintf(PMA_messages.strDoYouReally, selected_dbs.join('
    ')); + + var argsep = PMA_commonParams.get('arg_separator'); + $(this).PMA_confirm( + question, + $form.prop('action') + '?' + $(this).serialize() + + argsep + 'drop_selected_dbs=1', + function (url) { + PMA_ajaxShowMessage(PMA_messages.strProcessingRequest, false); + + var parts = url.split('?'); + var params = getJSConfirmCommonParam(this, parts[1]); + + $.post(parts[0], params, function (data) { + if (typeof data !== 'undefined' && data.success === true) { + PMA_ajaxShowMessage(data.message); + + var $rowsToRemove = $form.find('tr.removeMe'); + var $databasesCount = $('#filter-rows-count'); + var newCount = parseInt($databasesCount.text(), 10) - $rowsToRemove.length; + $databasesCount.text(newCount); + + $rowsToRemove.remove(); + $form.find('tbody').PMA_sort_table('.name'); + if ($form.find('tbody').find('tr').length === 0) { + // user just dropped the last db on this page + PMA_commonActions.refreshMain(); + } + PMA_reloadNavigation(); + } else { + $form.find('tr.removeMe').removeClass('removeMe'); + PMA_ajaxShowMessage(data.error, false); + } + }); // end $.post() + } + ); // end $.PMA_confirm() + }); // end of Drop Database action + + /** + * Attach Ajax event handlers for 'Create Database'. + */ + $(document).on('submit', '#create_database_form.ajax', function (event) { + event.preventDefault(); + + var $form = $(this); + + // TODO Remove this section when all browsers support HTML5 "required" property + var newDbNameInput = $form.find('input[name=new_db]'); + if (newDbNameInput.val() === '') { + newDbNameInput.focus(); + alert(PMA_messages.strFormEmpty); + return; + } + // end remove + + PMA_ajaxShowMessage(PMA_messages.strProcessingRequest); + PMA_prepareForAjaxRequest($form); + + $.post($form.attr('action'), $form.serialize(), function (data) { + if (typeof data !== 'undefined' && data.success === true) { + PMA_ajaxShowMessage(data.message); + + var $databases_count_object = $('#filter-rows-count'); + var databases_count = parseInt($databases_count_object.text(), 10) + 1; + $databases_count_object.text(databases_count); + PMA_reloadNavigation(); + + // make ajax request to load db structure page - taken from ajax.js + var dbStruct_url = data.url_query; + dbStruct_url = dbStruct_url.replace(/amp;/ig, ''); + var params = 'ajax_request=true' + PMA_commonParams.get('arg_separator') + 'ajax_page_request=true'; + if (! (history && history.pushState)) { + params += PMA_MicroHistory.menus.getRequestParam(); + } + $.get(dbStruct_url, params, AJAX.responseHandler); + } else { + PMA_ajaxShowMessage(data.error, false); + } + }); // end $.post() + }); // end $(document).on() + + /* Don't show filter if number of databases are very few */ + var databasesCount = $('#filter-rows-count').html(); + if (databasesCount <= 10) { + $('#tableFilter').hide(); + } + + var tableRows = $('.server_databases'); + $.each(tableRows, function (index, item) { + $(this).click(function () { + PMA_commonActions.setDb($(this).attr('data')); + }); + }); +}); // end $() diff --git a/php/apps/phpmyadmin49/html/js/server_plugins.js b/php/apps/phpmyadmin49/html/js/server_plugins.js new file mode 100644 index 00000000..7baadabd --- /dev/null +++ b/php/apps/phpmyadmin49/html/js/server_plugins.js @@ -0,0 +1,16 @@ +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * Functions used in server plugins pages + */ +AJAX.registerOnload('server_plugins.js', function () { + // Make columns sortable, but only for tables with more than 1 data row + var $tables = $('#plugins_plugins table:has(tbody tr + tr)'); + $tables.tablesorter({ + sortList: [[0, 0]], + headers: { + 1: { sorter: false } + } + }); + $tables.find('thead th') + .append('
    '); +}); diff --git a/php/apps/phpmyadmin49/html/js/server_privileges.js b/php/apps/phpmyadmin49/html/js/server_privileges.js new file mode 100644 index 00000000..e8013d41 --- /dev/null +++ b/php/apps/phpmyadmin49/html/js/server_privileges.js @@ -0,0 +1,487 @@ +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * @fileoverview functions used in server privilege pages + * @name Server Privileges + * + * @requires jQuery + * @requires jQueryUI + * @requires js/functions.js + * + */ + +/** + * Validates the "add a user" form + * + * @return boolean whether the form is validated or not + */ +function checkAddUser (the_form) { + if (the_form.elements.pred_hostname.value === 'userdefined' && the_form.elements.hostname.value === '') { + alert(PMA_messages.strHostEmpty); + the_form.elements.hostname.focus(); + return false; + } + + if (the_form.elements.pred_username.value === 'userdefined' && the_form.elements.username.value === '') { + alert(PMA_messages.strUserEmpty); + the_form.elements.username.focus(); + return false; + } + + return PMA_checkPassword($(the_form)); +} // end of the 'checkAddUser()' function + +function checkPasswordStrength (value, meter_obj, meter_object_label, username) { + // List of words we don't want to appear in the password + customDict = [ + 'phpmyadmin', + 'mariadb', + 'mysql', + 'php', + 'my', + 'admin', + ]; + if (username !== null) { + customDict.push(username); + } + var zxcvbn_obj = zxcvbn(value, customDict); + var strength = zxcvbn_obj.score; + strength = parseInt(strength); + meter_obj.val(strength); + switch (strength) { + case 0: meter_obj_label.html(PMA_messages.strExtrWeak); + break; + case 1: meter_obj_label.html(PMA_messages.strVeryWeak); + break; + case 2: meter_obj_label.html(PMA_messages.strWeak); + break; + case 3: meter_obj_label.html(PMA_messages.strGood); + break; + case 4: meter_obj_label.html(PMA_messages.strStrong); + } +} + +/** + * AJAX scripts for server_privileges page. + * + * Actions ajaxified here: + * Add user + * Revoke a user + * Edit privileges + * Export privileges + * Paginate table of users + * Flush privileges + * + * @memberOf jQuery + * @name document.ready + */ + + +/** + * Unbind all event handlers before tearing down a page + */ +AJAX.registerTeardown('server_privileges.js', function () { + $('#fieldset_add_user_login').off('change', 'input[name=\'username\']'); + $(document).off('click', '#fieldset_delete_user_footer #buttonGo.ajax'); + $(document).off('click', 'a.edit_user_group_anchor.ajax'); + $(document).off('click', 'button.mult_submit[value=export]'); + $(document).off('click', 'a.export_user_anchor.ajax'); + $(document).off('click', '#initials_table a.ajax'); + $('#checkbox_drop_users_db').off('click'); + $(document).off('click', '.checkall_box'); + $(document).off('change', '#checkbox_SSL_priv'); + $(document).off('change', 'input[name="ssl_type"]'); + $(document).off('change', '#select_authentication_plugin'); +}); + +AJAX.registerOnload('server_privileges.js', function () { + /** + * Display a warning if there is already a user by the name entered as the username. + */ + $('#fieldset_add_user_login').on('change', 'input[name=\'username\']', function () { + var username = $(this).val(); + var $warning = $('#user_exists_warning'); + if ($('#select_pred_username').val() === 'userdefined' && username !== '') { + var href = $('form[name=\'usersForm\']').attr('action'); + var params = { + 'ajax_request' : true, + 'server' : PMA_commonParams.get('server'), + 'validate_username' : true, + 'username' : username + }; + $.get(href, params, function (data) { + if (data.user_exists) { + $warning.show(); + } else { + $warning.hide(); + } + }); + } else { + $warning.hide(); + } + }); + + /** + * Indicating password strength + */ + $('#text_pma_pw').on('keyup', function () { + meter_obj = $('#password_strength_meter'); + meter_obj_label = $('#password_strength'); + username = $('input[name="username"]'); + username = username.val(); + checkPasswordStrength($(this).val(), meter_obj, meter_obj_label, username); + }); + + /** + * Automatically switching to 'Use Text field' from 'No password' once start writing in text area + */ + $('#text_pma_pw').on('input', function () { + if ($('#text_pma_pw').val() !== '') { + $('#select_pred_password').val('userdefined'); + } + }); + + $('#text_pma_change_pw').on('keyup', function () { + meter_obj = $('#change_password_strength_meter'); + meter_obj_label = $('#change_password_strength'); + checkPasswordStrength($(this).val(), meter_obj, meter_obj_label, PMA_commonParams.get('user')); + }); + + /** + * Display a notice if sha256_password is selected + */ + $(document).on('change', '#select_authentication_plugin', function () { + var selected_plugin = $(this).val(); + if (selected_plugin === 'sha256_password') { + $('#ssl_reqd_warning').show(); + } else { + $('#ssl_reqd_warning').hide(); + } + }); + + /** + * AJAX handler for 'Revoke User' + * + * @see PMA_ajaxShowMessage() + * @memberOf jQuery + * @name revoke_user_click + */ + $(document).on('click', '#fieldset_delete_user_footer #buttonGo.ajax', function (event) { + event.preventDefault(); + + var $thisButton = $(this); + var $form = $('#usersForm'); + + $thisButton.PMA_confirm(PMA_messages.strDropUserWarning, $form.attr('action'), function (url) { + var $drop_users_db_checkbox = $('#checkbox_drop_users_db'); + if ($drop_users_db_checkbox.is(':checked')) { + var is_confirmed = confirm(PMA_messages.strDropDatabaseStrongWarning + '\n' + PMA_sprintf(PMA_messages.strDoYouReally, 'DROP DATABASE')); + if (! is_confirmed) { + // Uncheck the drop users database checkbox + $drop_users_db_checkbox.prop('checked', false); + } + } + + PMA_ajaxShowMessage(PMA_messages.strRemovingSelectedUsers); + + var argsep = PMA_commonParams.get('arg_separator'); + $.post(url, $form.serialize() + argsep + 'delete=' + $thisButton.val() + argsep + 'ajax_request=true', function (data) { + if (typeof data !== 'undefined' && data.success === true) { + PMA_ajaxShowMessage(data.message); + // Refresh navigation, if we droppped some databases with the name + // that is the same as the username of the deleted user + if ($('#checkbox_drop_users_db:checked').length) { + PMA_reloadNavigation(); + } + // Remove the revoked user from the users list + $form.find('input:checkbox:checked').parents('tr').slideUp('medium', function () { + var this_user_initial = $(this).find('input:checkbox').val().charAt(0).toUpperCase(); + $(this).remove(); + + // If this is the last user with this_user_initial, remove the link from #initials_table + if ($('#tableuserrights').find('input:checkbox[value^="' + this_user_initial + '"], input:checkbox[value^="' + this_user_initial.toLowerCase() + '"]').length === 0) { + $('#initials_table').find('td > a:contains(' + this_user_initial + ')').parent('td').html(this_user_initial); + } + + // Re-check the classes of each row + $form + .find('tbody').find('tr:odd') + .removeClass('even').addClass('odd') + .end() + .find('tr:even') + .removeClass('odd').addClass('even'); + + // update the checkall checkbox + $(checkboxes_sel).trigger('change'); + }); + } else { + PMA_ajaxShowMessage(data.error, false); + } + }); // end $.post() + }); + }); // end Revoke User + + $(document).on('click', 'a.edit_user_group_anchor.ajax', function (event) { + event.preventDefault(); + $(this).parents('tr').addClass('current_row'); + var $msg = PMA_ajaxShowMessage(); + $.get( + $(this).attr('href'), + { + 'ajax_request': true, + 'edit_user_group_dialog': true + }, + function (data) { + if (typeof data !== 'undefined' && data.success === true) { + PMA_ajaxRemoveMessage($msg); + var buttonOptions = {}; + buttonOptions[PMA_messages.strGo] = function () { + var usrGroup = $('#changeUserGroupDialog') + .find('select[name="userGroup"]') + .val(); + var $message = PMA_ajaxShowMessage(); + var argsep = PMA_commonParams.get('arg_separator'); + $.post( + 'server_privileges.php', + $('#changeUserGroupDialog').find('form').serialize() + argsep + 'ajax_request=1', + function (data) { + PMA_ajaxRemoveMessage($message); + if (typeof data !== 'undefined' && data.success === true) { + $('#usersForm') + .find('.current_row') + .removeClass('current_row') + .find('.usrGroup') + .text(usrGroup); + } else { + PMA_ajaxShowMessage(data.error, false); + $('#usersForm') + .find('.current_row') + .removeClass('current_row'); + } + } + ); + $(this).dialog('close'); + }; + buttonOptions[PMA_messages.strClose] = function () { + $(this).dialog('close'); + }; + var $dialog = $('
    ') + .attr('id', 'changeUserGroupDialog') + .append(data.message) + .dialog({ + width: 500, + minWidth: 300, + modal: true, + buttons: buttonOptions, + title: $('legend', $(data.message)).text(), + close: function () { + $(this).remove(); + } + }); + $dialog.find('legend').remove(); + } else { + PMA_ajaxShowMessage(data.error, false); + $('#usersForm') + .find('.current_row') + .removeClass('current_row'); + } + } + ); + }); + + /** + * AJAX handler for 'Export Privileges' + * + * @see PMA_ajaxShowMessage() + * @memberOf jQuery + * @name export_user_click + */ + $(document).on('click', 'button.mult_submit[value=export]', function (event) { + event.preventDefault(); + // can't export if no users checked + if ($(this.form).find('input:checked').length === 0) { + PMA_ajaxShowMessage(PMA_messages.strNoAccountSelected, 2000, 'success'); + return; + } + var $msgbox = PMA_ajaxShowMessage(); + var button_options = {}; + button_options[PMA_messages.strClose] = function () { + $(this).dialog('close'); + }; + var argsep = PMA_commonParams.get('arg_separator'); + $.post( + $(this.form).prop('action'), + $(this.form).serialize() + argsep + 'submit_mult=export' + argsep + 'ajax_request=true', + function (data) { + if (typeof data !== 'undefined' && data.success === true) { + var $ajaxDialog = $('
    ') + .append(data.message) + .dialog({ + title: data.title, + width: 500, + buttons: button_options, + close: function () { + $(this).remove(); + } + }); + PMA_ajaxRemoveMessage($msgbox); + // Attach syntax highlighted editor to export dialog + PMA_getSQLEditor($ajaxDialog.find('textarea')); + } else { + PMA_ajaxShowMessage(data.error, false); + } + } + ); // end $.post + }); + // if exporting non-ajax, highlight anyways + PMA_getSQLEditor($('textarea.export')); + + $(document).on('click', 'a.export_user_anchor.ajax', function (event) { + event.preventDefault(); + var $msgbox = PMA_ajaxShowMessage(); + /** + * @var button_options Object containing options for jQueryUI dialog buttons + */ + var button_options = {}; + button_options[PMA_messages.strClose] = function () { + $(this).dialog('close'); + }; + $.get($(this).attr('href'), { 'ajax_request': true }, function (data) { + if (typeof data !== 'undefined' && data.success === true) { + var $ajaxDialog = $('
    ') + .append(data.message) + .dialog({ + title: data.title, + width: 500, + buttons: button_options, + close: function () { + $(this).remove(); + } + }); + PMA_ajaxRemoveMessage($msgbox); + // Attach syntax highlighted editor to export dialog + PMA_getSQLEditor($ajaxDialog.find('textarea')); + } else { + PMA_ajaxShowMessage(data.error, false); + } + }); // end $.get + }); // end export privileges + + /** + * AJAX handler to Paginate the Users Table + * + * @see PMA_ajaxShowMessage() + * @name paginate_users_table_click + * @memberOf jQuery + */ + $(document).on('click', '#initials_table a.ajax', function (event) { + event.preventDefault(); + var $msgbox = PMA_ajaxShowMessage(); + $.get($(this).attr('href'), { 'ajax_request' : true }, function (data) { + if (typeof data !== 'undefined' && data.success === true) { + PMA_ajaxRemoveMessage($msgbox); + // This form is not on screen when first entering Privileges + // if there are more than 50 users + $('div.notice').remove(); + $('#usersForm').hide('medium').remove(); + $('#fieldset_add_user').hide('medium').remove(); + $('#initials_table') + .prop('id', 'initials_table_old') + .after(data.message).show('medium') + .siblings('h2').not(':first').remove(); + // prevent double initials table + $('#initials_table_old').remove(); + } else { + PMA_ajaxShowMessage(data.error, false); + } + }); // end $.get + }); // end of the paginate users table + + $(document).on('change', 'input[name="ssl_type"]', function (e) { + var $div = $('#specified_div'); + if ($('#ssl_type_SPECIFIED').is(':checked')) { + $div.find('input').prop('disabled', false); + } else { + $div.find('input').prop('disabled', true); + } + }); + + $(document).on('change', '#checkbox_SSL_priv', function (e) { + var $div = $('#require_ssl_div'); + if ($(this).is(':checked')) { + $div.find('input').prop('disabled', false); + $('#ssl_type_SPECIFIED').trigger('change'); + } else { + $div.find('input').prop('disabled', true); + } + }); + + $('#checkbox_SSL_priv').trigger('change'); + + /* + * Create submenu for simpler interface + */ + var addOrUpdateSubmenu = function () { + var $topmenu2 = $('#topmenu2'); + var $edit_user_dialog = $('#edit_user_dialog'); + var submenu_label; + var submenu_link; + var link_number; + + // if submenu exists yet, remove it first + if ($topmenu2.length > 0) { + $topmenu2.remove(); + } + + // construct a submenu from the existing fieldsets + $topmenu2 = $('
      ').prop('id', 'topmenu2'); + + $('#edit_user_dialog .submenu-item').each(function () { + submenu_label = $(this).find('legend[data-submenu-label]').data('submenu-label'); + + submenu_link = $('') + .prop('href', '#') + .html(submenu_label); + + $('
    • ') + .append(submenu_link) + .appendTo($topmenu2); + }); + + // click handlers for submenu + $topmenu2.find('a').click(function (e) { + e.preventDefault(); + // if already active, ignore click + if ($(this).hasClass('tabactive')) { + return; + } + $topmenu2.find('a').removeClass('tabactive'); + $(this).addClass('tabactive'); + + // which section to show now? + link_number = $topmenu2.find('a').index($(this)); + // hide all sections but the one to show + $('#edit_user_dialog .submenu-item').hide().eq(link_number).show(); + }); + + // make first menu item active + // TODO: support URL hash history + $topmenu2.find('> :first-child a').addClass('tabactive'); + $edit_user_dialog.prepend($topmenu2); + + // hide all sections but the first + $('#edit_user_dialog .submenu-item').hide().eq(0).show(); + + // scroll to the top + $('html, body').animate({ scrollTop: 0 }, 'fast'); + }; + + $('input.autofocus').focus(); + $(checkboxes_sel).trigger('change'); + displayPasswordGenerateButton(); + if ($('#edit_user_dialog').length > 0) { + addOrUpdateSubmenu(); + } + + var windowwidth = $(window).width(); + $('.jsresponsive').css('max-width', (windowwidth - 35) + 'px'); +}); diff --git a/php/apps/phpmyadmin49/html/js/server_status_advisor.js b/php/apps/phpmyadmin49/html/js/server_status_advisor.js new file mode 100644 index 00000000..a9f68863 --- /dev/null +++ b/php/apps/phpmyadmin49/html/js/server_status_advisor.js @@ -0,0 +1,101 @@ +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * Server Status Advisor + * + * @package PhpMyAdmin + */ + +/** + * Unbind all event handlers before tearing down a page + */ +AJAX.registerTeardown('server_status_advisor.js', function () { + $('a[href="#openAdvisorInstructions"]').off('click'); + $('#statustabs_advisor').html(''); + $('#advisorDialog').remove(); + $('#instructionsDialog').remove(); +}); + +AJAX.registerOnload('server_status_advisor.js', function () { + // if no advisor is loaded + if ($('#advisorData').length === 0) { + return; + } + + /** ** Server config advisor ****/ + var $dialog = $('
      ').attr('id', 'advisorDialog'); + var $instructionsDialog = $('
      ') + .attr('id', 'instructionsDialog') + .html($('#advisorInstructionsDialog').html()); + + $('a[href="#openAdvisorInstructions"]').click(function () { + var dlgBtns = {}; + dlgBtns[PMA_messages.strClose] = function () { + $(this).dialog('close'); + }; + $instructionsDialog.dialog({ + title: PMA_messages.strAdvisorSystem, + width: '60%', + buttons: dlgBtns + }); + }); + + var $cnt = $('#statustabs_advisor'); + var $tbody; + var $tr; + var str; + var even = true; + + data = JSON.parse($('#advisorData').text()); + $cnt.html(''); + + if (data.parse.errors.length > 0) { + $cnt.append('Rules file not well formed, following errors were found:
      - '); + $cnt.append(data.parse.errors.join('
      - ')); + $cnt.append('

      '); + } + + if (data.run.errors.length > 0) { + $cnt.append('Errors occurred while executing rule expressions:
      - '); + $cnt.append(data.run.errors.join('
      - ')); + $cnt.append('

      '); + } + + if (data.run.fired.length > 0) { + $cnt.append('

      ' + PMA_messages.strPerformanceIssues + '

      '); + $cnt.append('' + + '
      ' + PMA_messages.strIssuse + '' + PMA_messages.strRecommendation + + '
      '); + $tbody = $cnt.find('table#rulesFired'); + + var rc_stripped; + + $.each(data.run.fired, function (key, value) { + // recommendation may contain links, don't show those in overview table (clicking on them redirects the user) + rc_stripped = $.trim($('
      ').html(value.recommendation).text()); + $tbody.append($tr = $('' + + value.issue + '' + rc_stripped + ' ')); + even = !even; + $tr.data('rule', value); + + $tr.click(function () { + var rule = $(this).data('rule'); + $dialog + .dialog({ title: PMA_messages.strRuleDetails }) + .html( + '

      ' + PMA_messages.strIssuse + ':
      ' + rule.issue + '

      ' + + '

      ' + PMA_messages.strRecommendation + ':
      ' + rule.recommendation + '

      ' + + '

      ' + PMA_messages.strJustification + ':
      ' + rule.justification + '

      ' + + '

      ' + PMA_messages.strFormula + ':
      ' + rule.formula + '

      ' + + '

      ' + PMA_messages.strTest + ':
      ' + rule.test + '

      ' + ); + + var dlgBtns = {}; + dlgBtns[PMA_messages.strClose] = function () { + $(this).dialog('close'); + }; + + $dialog.dialog({ width: 600, buttons: dlgBtns }); + }); + }); + } +}); diff --git a/php/apps/phpmyadmin49/html/js/server_status_monitor.js b/php/apps/phpmyadmin49/html/js/server_status_monitor.js new file mode 100644 index 00000000..8bff1cea --- /dev/null +++ b/php/apps/phpmyadmin49/html/js/server_status_monitor.js @@ -0,0 +1,2197 @@ +/* vim: set expandtab sw=4 ts=4 sts=4: */ +var runtime = {}; +var server_time_diff; +var server_os; +var is_superuser; +var server_db_isLocal; +var chartSize; +AJAX.registerOnload('server_status_monitor.js', function () { + var $js_data_form = $('#js_data'); + server_time_diff = new Date().getTime() - $js_data_form.find('input[name=server_time]').val(); + server_os = $js_data_form.find('input[name=server_os]').val(); + is_superuser = $js_data_form.find('input[name=is_superuser]').val(); + server_db_isLocal = $js_data_form.find('input[name=server_db_isLocal]').val(); +}); + +/** + * Unbind all event handlers before tearing down a page + */ +AJAX.registerTeardown('server_status_monitor.js', function () { + $('#emptyDialog').remove(); + $('#addChartDialog').remove(); + $('a.popupLink').off('click'); + $('body').off('click'); +}); +/** + * Popup behaviour + */ +AJAX.registerOnload('server_status_monitor.js', function () { + $('
      ') + .attr('id', 'emptyDialog') + .appendTo('#page_content'); + $('#addChartDialog') + .appendTo('#page_content'); + + $('a.popupLink').click(function () { + var $link = $(this); + $('div.' + $link.attr('href').substr(1)) + .show() + .offset({ top: $link.offset().top + $link.height() + 5, left: $link.offset().left }) + .addClass('openedPopup'); + + return false; + }); + $('body').click(function (event) { + $('div.openedPopup').each(function () { + var $cnt = $(this); + var pos = $cnt.offset(); + // Hide if the mouseclick is outside the popupcontent + if (event.pageX < pos.left || + event.pageY < pos.top || + event.pageX > pos.left + $cnt.outerWidth() || + event.pageY > pos.top + $cnt.outerHeight() + ) { + $cnt.hide().removeClass('openedPopup'); + } + }); + }); +}); + +AJAX.registerTeardown('server_status_monitor.js', function () { + $('a[href="#rearrangeCharts"], a[href="#endChartEditMode"]').off('click'); + $('div.popupContent select[name="chartColumns"]').off('change'); + $('div.popupContent select[name="gridChartRefresh"]').off('change'); + $('a[href="#addNewChart"]').off('click'); + $('a[href="#exportMonitorConfig"]').off('click'); + $('a[href="#importMonitorConfig"]').off('click'); + $('a[href="#clearMonitorConfig"]').off('click'); + $('a[href="#pauseCharts"]').off('click'); + $('a[href="#monitorInstructionsDialog"]').off('click'); + $('input[name="chartType"]').off('click'); + $('input[name="useDivisor"]').off('click'); + $('input[name="useUnit"]').off('click'); + $('select[name="varChartList"]').off('click'); + $('a[href="#kibDivisor"]').off('click'); + $('a[href="#mibDivisor"]').off('click'); + $('a[href="#submitClearSeries"]').off('click'); + $('a[href="#submitAddSeries"]').off('click'); + // $("input#variableInput").destroy(); + $('#chartPreset').off('click'); + $('#chartStatusVar').off('click'); + destroyGrid(); +}); + +AJAX.registerOnload('server_status_monitor.js', function () { + // Show tab links + $('div.tabLinks').show(); + $('#loadingMonitorIcon').remove(); + // Codemirror is loaded on demand so we might need to initialize it + if (! codemirror_editor) { + var $elm = $('#sqlquery'); + if ($elm.length > 0 && typeof CodeMirror !== 'undefined') { + codemirror_editor = CodeMirror.fromTextArea( + $elm[0], + { + lineNumbers: true, + matchBrackets: true, + indentUnit: 4, + mode: 'text/x-mysql', + lineWrapping: true + } + ); + } + } + // Timepicker is loaded on demand so we need to initialize + // datetime fields from the 'load log' dialog + $('#logAnalyseDialog').find('.datetimefield').each(function () { + PMA_addDatepicker($(this)); + }); + + /** ** Monitor charting implementation ****/ + /* Saves the previous ajax response for differential values */ + var oldChartData = null; + // Holds about to be created chart + var newChart = null; + var chartSpacing; + + // Whenever the monitor object (runtime.charts) or the settings object + // (monitorSettings) changes in a way incompatible to the previous version, + // increase this number. It will reset the users monitor and settings object + // in his localStorage to the default configuration + var monitorProtocolVersion = '1.0'; + + // Runtime parameter of the monitor, is being fully set in initGrid() + runtime = { + // Holds all visible charts in the grid + charts: null, + // Stores the timeout handler so it can be cleared + refreshTimeout: null, + // Stores the GET request to refresh the charts + refreshRequest: null, + // Chart auto increment + chartAI: 0, + // To play/pause the monitor + redrawCharts: false, + // Object that contains a list of nodes that need to be retrieved + // from the server for chart updates + dataList: [], + // Current max points per chart (needed for auto calculation) + gridMaxPoints: 20, + // displayed time frame + xmin: -1, + xmax: -1 + }; + var monitorSettings = null; + + var defaultMonitorSettings = { + columns: 3, + chartSize: { width: 295, height: 250 }, + // Max points in each chart. Settings it to 'auto' sets + // gridMaxPoints to (chartwidth - 40) / 12 + gridMaxPoints: 'auto', + /* Refresh rate of all grid charts in ms */ + gridRefresh: 5000 + }; + + // Allows drag and drop rearrange and print/edit icons on charts + var editMode = false; + + /* List of preconfigured charts that the user may select */ + var presetCharts = { + // Query cache efficiency + 'qce': { + title: PMA_messages.strQueryCacheEfficiency, + series: [{ + label: PMA_messages.strQueryCacheEfficiency + }], + nodes: [{ + dataPoints: [{ type: 'statusvar', name: 'Qcache_hits' }, { type: 'statusvar', name: 'Com_select' }], + transformFn: 'qce' + }], + maxYLabel: 0 + }, + // Query cache usage + 'qcu': { + title: PMA_messages.strQueryCacheUsage, + series: [{ + label: PMA_messages.strQueryCacheUsed + }], + nodes: [{ + dataPoints: [{ type: 'statusvar', name: 'Qcache_free_memory' }, { type: 'servervar', name: 'query_cache_size' }], + transformFn: 'qcu' + }], + maxYLabel: 0 + } + }; + + // time span selection + var selectionTimeDiff = []; + var selectionStartX; + var selectionStartY; + var selectionEndX; + var selectionEndY; + var drawTimeSpan = false; + + // chart tooltip + var tooltipBox; + + /* Add OS specific system info charts to the preset chart list */ + switch (server_os) { + case 'WINNT': + $.extend(presetCharts, { + 'cpu': { + title: PMA_messages.strSystemCPUUsage, + series: [{ + label: PMA_messages.strAverageLoad + }], + nodes: [{ + dataPoints: [{ type: 'cpu', name: 'loadavg' }] + }], + maxYLabel: 100 + }, + + 'memory': { + title: PMA_messages.strSystemMemory, + series: [{ + label: PMA_messages.strTotalMemory, + fill: true + }, { + dataType: 'memory', + label: PMA_messages.strUsedMemory, + fill: true + }], + nodes: [{ dataPoints: [{ type: 'memory', name: 'MemTotal' }], valueDivisor: 1024 }, + { dataPoints: [{ type: 'memory', name: 'MemUsed' }], valueDivisor: 1024 } + ], + maxYLabel: 0 + }, + + 'swap': { + title: PMA_messages.strSystemSwap, + series: [{ + label: PMA_messages.strTotalSwap, + fill: true + }, { + label: PMA_messages.strUsedSwap, + fill: true + }], + nodes: [{ dataPoints: [{ type: 'memory', name: 'SwapTotal' }] }, + { dataPoints: [{ type: 'memory', name: 'SwapUsed' }] } + ], + maxYLabel: 0 + } + }); + break; + + case 'Linux': + $.extend(presetCharts, { + 'cpu': { + title: PMA_messages.strSystemCPUUsage, + series: [{ + label: PMA_messages.strAverageLoad + }], + nodes: [{ dataPoints: [{ type: 'cpu', name: 'irrelevant' }], transformFn: 'cpu-linux' }], + maxYLabel: 0 + }, + 'memory': { + title: PMA_messages.strSystemMemory, + series: [ + { label: PMA_messages.strBufferedMemory, fill: true }, + { label: PMA_messages.strUsedMemory, fill: true }, + { label: PMA_messages.strCachedMemory, fill: true }, + { label: PMA_messages.strFreeMemory, fill: true } + ], + nodes: [ + { dataPoints: [{ type: 'memory', name: 'Buffers' }], valueDivisor: 1024 }, + { dataPoints: [{ type: 'memory', name: 'MemUsed' }], valueDivisor: 1024 }, + { dataPoints: [{ type: 'memory', name: 'Cached' }], valueDivisor: 1024 }, + { dataPoints: [{ type: 'memory', name: 'MemFree' }], valueDivisor: 1024 } + ], + maxYLabel: 0 + }, + 'swap': { + title: PMA_messages.strSystemSwap, + series: [ + { label: PMA_messages.strCachedSwap, fill: true }, + { label: PMA_messages.strUsedSwap, fill: true }, + { label: PMA_messages.strFreeSwap, fill: true } + ], + nodes: [ + { dataPoints: [{ type: 'memory', name: 'SwapCached' }], valueDivisor: 1024 }, + { dataPoints: [{ type: 'memory', name: 'SwapUsed' }], valueDivisor: 1024 }, + { dataPoints: [{ type: 'memory', name: 'SwapFree' }], valueDivisor: 1024 } + ], + maxYLabel: 0 + } + }); + break; + + case 'SunOS': + $.extend(presetCharts, { + 'cpu': { + title: PMA_messages.strSystemCPUUsage, + series: [{ + label: PMA_messages.strAverageLoad + }], + nodes: [{ + dataPoints: [{ type: 'cpu', name: 'loadavg' }] + }], + maxYLabel: 0 + }, + 'memory': { + title: PMA_messages.strSystemMemory, + series: [ + { label: PMA_messages.strUsedMemory, fill: true }, + { label: PMA_messages.strFreeMemory, fill: true } + ], + nodes: [ + { dataPoints: [{ type: 'memory', name: 'MemUsed' }], valueDivisor: 1024 }, + { dataPoints: [{ type: 'memory', name: 'MemFree' }], valueDivisor: 1024 } + ], + maxYLabel: 0 + }, + 'swap': { + title: PMA_messages.strSystemSwap, + series: [ + { label: PMA_messages.strUsedSwap, fill: true }, + { label: PMA_messages.strFreeSwap, fill: true } + ], + nodes: [ + { dataPoints: [{ type: 'memory', name: 'SwapUsed' }], valueDivisor: 1024 }, + { dataPoints: [{ type: 'memory', name: 'SwapFree' }], valueDivisor: 1024 } + ], + maxYLabel: 0 + } + }); + break; + } + + // Default setting for the chart grid + var defaultChartGrid = { + 'c0': { + title: PMA_messages.strQuestions, + series: [ + { label: PMA_messages.strQuestions } + ], + nodes: [ + { dataPoints: [{ type: 'statusvar', name: 'Questions' }], display: 'differential' } + ], + maxYLabel: 0 + }, + 'c1': { + title: PMA_messages.strChartConnectionsTitle, + series: [ + { label: PMA_messages.strConnections }, + { label: PMA_messages.strProcesses } + ], + nodes: [ + { dataPoints: [{ type: 'statusvar', name: 'Connections' }], display: 'differential' }, + { dataPoints: [{ type: 'proc', name: 'processes' }] } + ], + maxYLabel: 0 + }, + 'c2': { + title: PMA_messages.strTraffic, + series: [ + { label: PMA_messages.strBytesSent }, + { label: PMA_messages.strBytesReceived } + ], + nodes: [ + { dataPoints: [{ type: 'statusvar', name: 'Bytes_sent' }], display: 'differential', valueDivisor: 1024 }, + { dataPoints: [{ type: 'statusvar', name: 'Bytes_received' }], display: 'differential', valueDivisor: 1024 } + ], + maxYLabel: 0 + } + }; + + // Server is localhost => We can add cpu/memory/swap to the default chart + if (server_db_isLocal && typeof presetCharts.cpu !== 'undefined') { + defaultChartGrid.c3 = presetCharts.cpu; + defaultChartGrid.c4 = presetCharts.memory; + defaultChartGrid.c5 = presetCharts.swap; + } + + $('a[href="#rearrangeCharts"], a[href="#endChartEditMode"]').click(function (event) { + event.preventDefault(); + editMode = !editMode; + if ($(this).attr('href') === '#endChartEditMode') { + editMode = false; + } + + $('a[href="#endChartEditMode"]').toggle(editMode); + + if (editMode) { + // Close the settings popup + $('div.popupContent').hide().removeClass('openedPopup'); + + $('#chartGrid').sortableTable({ + ignoreRect: { + top: 8, + left: chartSize.width - 63, + width: 54, + height: 24 + } + }); + } else { + $('#chartGrid').sortableTable('destroy'); + } + saveMonitor(); // Save settings + return false; + }); + + // global settings + $('div.popupContent select[name="chartColumns"]').change(function () { + monitorSettings.columns = parseInt(this.value, 10); + + calculateChartSize(); + // Empty cells should keep their size so you can drop onto them + $('#chartGrid').find('tr td').css('width', chartSize.width + 'px'); + $('#chartGrid').find('.monitorChart').css({ + width: chartSize.width + 'px', + height: chartSize.height + 'px' + }); + + /* Reorder all charts that it fills all column cells */ + var numColumns; + var $tr = $('#chartGrid').find('tr:first'); + var row = 0; + + var tempManageCols = function () { + if (numColumns > monitorSettings.columns) { + if ($tr.next().length === 0) { + $tr.after(''); + } + $tr.next().prepend($(this)); + } + numColumns++; + }; + + var tempAddCol = function () { + if ($(this).next().length !== 0) { + $(this).append($(this).next().find('td:first')); + } + }; + + while ($tr.length !== 0) { + numColumns = 1; + // To many cells in one row => put into next row + $tr.find('td').each(tempManageCols); + + // To little cells in one row => for each cell to little, + // move all cells backwards by 1 + if ($tr.next().length > 0) { + var cnt = monitorSettings.columns - $tr.find('td').length; + for (var i = 0; i < cnt; i++) { + $tr.append($tr.next().find('td:first')); + $tr.nextAll().each(tempAddCol); + } + } + + $tr = $tr.next(); + row++; + } + + if (monitorSettings.gridMaxPoints === 'auto') { + runtime.gridMaxPoints = Math.round((chartSize.width - 40) / 12); + } + + runtime.xmin = new Date().getTime() - server_time_diff - runtime.gridMaxPoints * monitorSettings.gridRefresh; + runtime.xmax = new Date().getTime() - server_time_diff + monitorSettings.gridRefresh; + + if (editMode) { + $('#chartGrid').sortableTable('refresh'); + } + + refreshChartGrid(); + saveMonitor(); // Save settings + }); + + $('div.popupContent select[name="gridChartRefresh"]').change(function () { + monitorSettings.gridRefresh = parseInt(this.value, 10) * 1000; + clearTimeout(runtime.refreshTimeout); + + if (runtime.refreshRequest) { + runtime.refreshRequest.abort(); + } + + runtime.xmin = new Date().getTime() - server_time_diff - runtime.gridMaxPoints * monitorSettings.gridRefresh; + // fixing chart shift towards left on refresh rate change + // runtime.xmax = new Date().getTime() - server_time_diff + monitorSettings.gridRefresh; + runtime.refreshTimeout = setTimeout(refreshChartGrid, monitorSettings.gridRefresh); + + saveMonitor(); // Save settings + }); + + $('a[href="#addNewChart"]').click(function (event) { + event.preventDefault(); + var dlgButtons = { }; + + dlgButtons[PMA_messages.strAddChart] = function () { + var type = $('input[name="chartType"]:checked').val(); + + if (type === 'preset') { + newChart = presetCharts[$('#addChartDialog').find('select[name="presetCharts"]').prop('value')]; + } else { + // If user builds his own chart, it's being set/updated + // each time he adds a series + // So here we only warn if he didn't add a series yet + if (! newChart || ! newChart.nodes || newChart.nodes.length === 0) { + alert(PMA_messages.strAddOneSeriesWarning); + return; + } + } + + newChart.title = $('input[name="chartTitle"]').val(); + // Add a cloned object to the chart grid + addChart($.extend(true, {}, newChart)); + + newChart = null; + + saveMonitor(); // Save settings + + $(this).dialog('close'); + }; + + dlgButtons[PMA_messages.strClose] = function () { + newChart = null; + $('span#clearSeriesLink').hide(); + $('#seriesPreview').html(''); + $(this).dialog('close'); + }; + + var $presetList = $('#addChartDialog').find('select[name="presetCharts"]'); + if ($presetList.html().length === 0) { + $.each(presetCharts, function (key, value) { + $presetList.append(''); + }); + $presetList.change(function () { + $('input[name="chartTitle"]').val( + $presetList.find(':selected').text() + ); + $('#chartPreset').prop('checked', true); + }); + $('#chartPreset').click(function () { + $('input[name="chartTitle"]').val( + $presetList.find(':selected').text() + ); + }); + $('#chartStatusVar').click(function () { + $('input[name="chartTitle"]').val( + $('#chartSeries').find(':selected').text().replace(/_/g, ' ') + ); + }); + $('#chartSeries').change(function () { + $('input[name="chartTitle"]').val( + $('#chartSeries').find(':selected').text().replace(/_/g, ' ') + ); + }); + } + + $('#addChartDialog').dialog({ + width: 'auto', + height: 'auto', + buttons: dlgButtons + }); + + $('#seriesPreview').html('' + PMA_messages.strNone + ''); + + return false; + }); + + $('a[href="#exportMonitorConfig"]').click(function (event) { + event.preventDefault(); + var gridCopy = {}; + $.each(runtime.charts, function (key, elem) { + gridCopy[key] = {}; + gridCopy[key].nodes = elem.nodes; + gridCopy[key].series = elem.series; + gridCopy[key].settings = elem.settings; + gridCopy[key].title = elem.title; + gridCopy[key].maxYLabel = elem.maxYLabel; + }); + var exportData = { + monitorCharts: gridCopy, + monitorSettings: monitorSettings + }; + + var blob = new Blob([JSON.stringify(exportData)], { type: 'application/octet-stream' }); + var url = null; + var fileName = 'monitor-config.json'; + if (window.navigator && window.navigator.msSaveOrOpenBlob) { + window.navigator.msSaveOrOpenBlob(blob, fileName); + } else { + url = URL.createObjectURL(blob); + window.location.href = url; + } + setTimeout(function () { + // For some browsers it is necessary to delay revoking the ObjectURL + if (url !== null) { + window.URL.revokeObjectURL(url); + } + url = undefined; + blob = undefined; + }, 100); + }); + + $('a[href="#importMonitorConfig"]').click(function (event) { + event.preventDefault(); + $('#emptyDialog').dialog({ title: PMA_messages.strImportDialogTitle }); + $('#emptyDialog').html(PMA_messages.strImportDialogMessage + ':
      ' + + '
      '); + + var dlgBtns = {}; + + dlgBtns[PMA_messages.strImport] = function () { + var input = $('#emptyDialog').find('#import_file')[0]; + var reader = new FileReader(); + + reader.onerror = function (event) { + alert(PMA_messages.strFailedParsingConfig + '\n' + event.target.error.code); + }; + reader.onload = function (e) { + var data = e.target.result; + var json = null; + // Try loading config + try { + json = JSON.parse(data); + } catch (err) { + alert(PMA_messages.strFailedParsingConfig); + $('#emptyDialog').dialog('close'); + return; + } + + // Basic check, is this a monitor config json? + if (!json || ! json.monitorCharts || ! json.monitorCharts) { + alert(PMA_messages.strFailedParsingConfig); + $('#emptyDialog').dialog('close'); + return; + } + + // If json ok, try applying config + try { + if (isStorageSupported('localStorage')) { + window.localStorage.monitorCharts = JSON.stringify(json.monitorCharts); + window.localStorage.monitorSettings = JSON.stringify(json.monitorSettings); + } + rebuildGrid(); + } catch (err) { + alert(PMA_messages.strFailedBuildingGrid); + // If an exception is thrown, load default again + if (isStorageSupported('localStorage')) { + window.localStorage.removeItem('monitorCharts'); + window.localStorage.removeItem('monitorSettings'); + } + rebuildGrid(); + } + + $('#emptyDialog').dialog('close'); + }; + reader.readAsText(input.files[0]); + }; + + dlgBtns[PMA_messages.strCancel] = function () { + $(this).dialog('close'); + }; + + $('#emptyDialog').dialog({ + width: 'auto', + height: 'auto', + buttons: dlgBtns + }); + }); + + $('a[href="#clearMonitorConfig"]').click(function (event) { + event.preventDefault(); + if (isStorageSupported('localStorage')) { + window.localStorage.removeItem('monitorCharts'); + window.localStorage.removeItem('monitorSettings'); + window.localStorage.removeItem('monitorVersion'); + } + $(this).hide(); + rebuildGrid(); + }); + + $('a[href="#pauseCharts"]').click(function (event) { + event.preventDefault(); + runtime.redrawCharts = ! runtime.redrawCharts; + if (! runtime.redrawCharts) { + $(this).html(PMA_getImage('play') + PMA_messages.strResumeMonitor); + } else { + $(this).html(PMA_getImage('pause') + PMA_messages.strPauseMonitor); + if (! runtime.charts) { + initGrid(); + $('a[href="#settingsPopup"]').show(); + } + } + return false; + }); + + $('a[href="#monitorInstructionsDialog"]').click(function (event) { + event.preventDefault(); + + var $dialog = $('#monitorInstructionsDialog'); + + $dialog.dialog({ + width: '60%', + height: 'auto' + }).find('img.ajaxIcon').show(); + + var loadLogVars = function (getvars) { + var vars = { ajax_request: true, logging_vars: true }; + if (getvars) { + $.extend(vars, getvars); + } + + $.post('server_status_monitor.php' + PMA_commonParams.get('common_query'), vars, + function (data) { + var logVars; + if (typeof data !== 'undefined' && data.success === true) { + logVars = data.message; + } else { + return serverResponseError(); + } + var icon = PMA_getImage('s_success'); + var msg = ''; + var str = ''; + + if (logVars.general_log === 'ON') { + if (logVars.slow_query_log === 'ON') { + msg = PMA_messages.strBothLogOn; + } else { + msg = PMA_messages.strGenLogOn; + } + } + + if (msg.length === 0 && logVars.slow_query_log === 'ON') { + msg = PMA_messages.strSlowLogOn; + } + + if (msg.length === 0) { + icon = PMA_getImage('s_error'); + msg = PMA_messages.strBothLogOff; + } + + str = '' + PMA_messages.strCurrentSettings + '
      '; + str += icon + msg + '
      '; + + if (logVars.log_output !== 'TABLE') { + str += PMA_getImage('s_error') + ' ' + PMA_messages.strLogOutNotTable + '
      '; + } else { + str += PMA_getImage('s_success') + ' ' + PMA_messages.strLogOutIsTable + '
      '; + } + + if (logVars.slow_query_log === 'ON') { + if (logVars.long_query_time > 2) { + str += PMA_getImage('s_attention') + ' '; + str += PMA_sprintf(PMA_messages.strSmallerLongQueryTimeAdvice, logVars.long_query_time); + str += '
      '; + } + + if (logVars.long_query_time < 2) { + str += PMA_getImage('s_success') + ' '; + str += PMA_sprintf(PMA_messages.strLongQueryTimeSet, logVars.long_query_time); + str += '
      '; + } + } + + str += '
      '; + + if (is_superuser) { + str += '

      ' + PMA_messages.strChangeSettings + ''; + str += '
      '; + + $dialog.find('div.monitorUse').toggle( + logVars.log_output === 'TABLE' && (logVars.slow_query_log === 'ON' || logVars.general_log === 'ON') + ); + + $dialog.find('div.ajaxContent').html(str); + $dialog.find('img.ajaxIcon').hide(); + $dialog.find('a.set').click(function () { + var nameValue = $(this).attr('href').split('-'); + loadLogVars({ varName: nameValue[0].substr(1), varValue: nameValue[1] }); + $dialog.find('img.ajaxIcon').show(); + }); + } + ); + }; + + + loadLogVars(); + + return false; + }); + + $('input[name="chartType"]').change(function () { + $('#chartVariableSettings').toggle(this.checked && this.value === 'variable'); + var title = $('input[name="chartTitle"]').val(); + if (title === PMA_messages.strChartTitle || + title === $('label[for="' + $('input[name="chartTitle"]').data('lastRadio') + '"]').text() + ) { + $('input[name="chartTitle"]') + .data('lastRadio', $(this).attr('id')) + .val($('label[for="' + $(this).attr('id') + '"]').text()); + } + }); + + $('input[name="useDivisor"]').change(function () { + $('span.divisorInput').toggle(this.checked); + }); + + $('input[name="useUnit"]').change(function () { + $('span.unitInput').toggle(this.checked); + }); + + $('select[name="varChartList"]').change(function () { + if (this.selectedIndex !== 0) { + $('#variableInput').val(this.value); + } + }); + + $('a[href="#kibDivisor"]').click(function (event) { + event.preventDefault(); + $('input[name="valueDivisor"]').val(1024); + $('input[name="valueUnit"]').val(PMA_messages.strKiB); + $('span.unitInput').toggle(true); + $('input[name="useUnit"]').prop('checked', true); + return false; + }); + + $('a[href="#mibDivisor"]').click(function (event) { + event.preventDefault(); + $('input[name="valueDivisor"]').val(1024 * 1024); + $('input[name="valueUnit"]').val(PMA_messages.strMiB); + $('span.unitInput').toggle(true); + $('input[name="useUnit"]').prop('checked', true); + return false; + }); + + $('a[href="#submitClearSeries"]').click(function (event) { + event.preventDefault(); + $('#seriesPreview').html('' + PMA_messages.strNone + ''); + newChart = null; + $('#clearSeriesLink').hide(); + }); + + $('a[href="#submitAddSeries"]').click(function (event) { + event.preventDefault(); + if ($('#variableInput').val() === '') { + return false; + } + + if (newChart === null) { + $('#seriesPreview').html(''); + + newChart = { + title: $('input[name="chartTitle"]').val(), + nodes: [], + series: [], + maxYLabel: 0 + }; + } + + var serie = { + dataPoints: [{ type: 'statusvar', name: $('#variableInput').val() }], + display: $('input[name="differentialValue"]').prop('checked') ? 'differential' : '' + }; + + if (serie.dataPoints[0].name === 'Processes') { + serie.dataPoints[0].type = 'proc'; + } + + if ($('input[name="useDivisor"]').prop('checked')) { + serie.valueDivisor = parseInt($('input[name="valueDivisor"]').val(), 10); + } + + if ($('input[name="useUnit"]').prop('checked')) { + serie.unit = $('input[name="valueUnit"]').val(); + } + + var str = serie.display === 'differential' ? ', ' + PMA_messages.strDifferential : ''; + str += serie.valueDivisor ? (', ' + PMA_sprintf(PMA_messages.strDividedBy, serie.valueDivisor)) : ''; + str += serie.unit ? (', ' + PMA_messages.strUnit + ': ' + serie.unit) : ''; + + var newSeries = { + label: $('#variableInput').val().replace(/_/g, ' ') + }; + newChart.series.push(newSeries); + $('#seriesPreview').append('- ' + escapeHtml(newSeries.label + str) + '
      '); + newChart.nodes.push(serie); + $('#variableInput').val(''); + $('input[name="differentialValue"]').prop('checked', true); + $('input[name="useDivisor"]').prop('checked', false); + $('input[name="useUnit"]').prop('checked', false); + $('input[name="useDivisor"]').trigger('change'); + $('input[name="useUnit"]').trigger('change'); + $('select[name="varChartList"]').get(0).selectedIndex = 0; + + $('#clearSeriesLink').show(); + + return false; + }); + + $('#variableInput').autocomplete({ + source: variableNames + }); + + /* Initializes the monitor, called only once */ + function initGrid () { + var i; + + /* Apply default values & config */ + if (isStorageSupported('localStorage')) { + if (typeof window.localStorage.monitorCharts !== 'undefined') { + runtime.charts = JSON.parse(window.localStorage.monitorCharts); + } + if (typeof window.localStorage.monitorSettings !== 'undefined') { + monitorSettings = JSON.parse(window.localStorage.monitorSettings); + } + + $('a[href="#clearMonitorConfig"]').toggle(runtime.charts !== null); + + if (runtime.charts !== null + && typeof window.localStorage.monitorVersion !== 'undefined' + && monitorProtocolVersion !== window.localStorage.monitorVersion + ) { + $('#emptyDialog').dialog({ title: PMA_messages.strIncompatibleMonitorConfig }); + $('#emptyDialog').html(PMA_messages.strIncompatibleMonitorConfigDescription); + + var dlgBtns = {}; + dlgBtns[PMA_messages.strClose] = function () { + $(this).dialog('close'); + }; + + $('#emptyDialog').dialog({ + width: 400, + buttons: dlgBtns + }); + } + } + + if (runtime.charts === null) { + runtime.charts = defaultChartGrid; + } + if (monitorSettings === null) { + monitorSettings = defaultMonitorSettings; + } + + $('select[name="gridChartRefresh"]').val(monitorSettings.gridRefresh / 1000); + $('select[name="chartColumns"]').val(monitorSettings.columns); + + if (monitorSettings.gridMaxPoints === 'auto') { + runtime.gridMaxPoints = Math.round((monitorSettings.chartSize.width - 40) / 12); + } else { + runtime.gridMaxPoints = monitorSettings.gridMaxPoints; + } + + runtime.xmin = new Date().getTime() - server_time_diff - runtime.gridMaxPoints * monitorSettings.gridRefresh; + runtime.xmax = new Date().getTime() - server_time_diff + monitorSettings.gridRefresh; + + /* Calculate how much spacing there is between each chart */ + $('#chartGrid').html(''); + chartSpacing = { + width: $('#chartGrid').find('td:nth-child(2)').offset().left - + $('#chartGrid').find('td:nth-child(1)').offset().left, + height: $('#chartGrid').find('tr:nth-child(2) td:nth-child(2)').offset().top - + $('#chartGrid').find('tr:nth-child(1) td:nth-child(1)').offset().top + }; + $('#chartGrid').html(''); + + /* Add all charts - in correct order */ + var keys = []; + $.each(runtime.charts, function (key, value) { + keys.push(key); + }); + keys.sort(); + for (i = 0; i < keys.length; i++) { + addChart(runtime.charts[keys[i]], true); + } + + /* Fill in missing cells */ + var numCharts = $('#chartGrid').find('.monitorChart').length; + var numMissingCells = (monitorSettings.columns - numCharts % monitorSettings.columns) % monitorSettings.columns; + for (i = 0; i < numMissingCells; i++) { + $('#chartGrid').find('tr:last').append(''); + } + + // Empty cells should keep their size so you can drop onto them + calculateChartSize(); + $('#chartGrid').find('tr td').css('width', chartSize.width + 'px'); + + buildRequiredDataList(); + refreshChartGrid(); + } + + /* Calls destroyGrid() and initGrid(), but before doing so it saves the chart + * data from each chart and restores it after the monitor is initialized again */ + function rebuildGrid () { + var oldData = null; + if (runtime.charts) { + oldData = {}; + $.each(runtime.charts, function (key, chartObj) { + for (var i = 0, l = chartObj.nodes.length; i < l; i++) { + oldData[chartObj.nodes[i].dataPoint] = []; + for (var j = 0, ll = chartObj.chart.series[i].data.length; j < ll; j++) { + oldData[chartObj.nodes[i].dataPoint].push([chartObj.chart.series[i].data[j].x, chartObj.chart.series[i].data[j].y]); + } + } + }); + } + + destroyGrid(); + initGrid(); + } + + /* Calculactes the dynamic chart size that depends on the column width */ + function calculateChartSize () { + var panelWidth; + if ($('body').height() > $(window).height()) { // has vertical scroll bar + panelWidth = $('#logTable').innerWidth(); + } else { + panelWidth = $('#logTable').innerWidth() - 10; // leave some space for vertical scroll bar + } + + var wdt = panelWidth; + var windowWidth = $(window).width(); + + if (windowWidth > 768) { + wdt = (panelWidth - monitorSettings.columns * chartSpacing.width) / monitorSettings.columns; + } + + chartSize = { + width: Math.floor(wdt), + height: Math.floor(0.75 * wdt) + }; + } + + /* Adds a chart to the chart grid */ + function addChart (chartObj, initialize) { + var i; + var settings = { + title: escapeHtml(chartObj.title), + grid: { + drawBorder: false, + shadow: false, + background: 'rgba(0,0,0,0)' + }, + axes: { + xaxis: { + renderer: $.jqplot.DateAxisRenderer, + tickOptions: { + formatString: '%H:%M:%S', + showGridline: false + }, + min: runtime.xmin, + max: runtime.xmax + }, + yaxis: { + min: 0, + max: 100, + tickInterval: 20 + } + }, + seriesDefaults: { + rendererOptions: { + smooth: true + }, + showLine: true, + lineWidth: 2, + markerOptions: { + size: 6 + } + }, + highlighter: { + show: true + } + }; + + if (settings.title === PMA_messages.strSystemCPUUsage || + settings.title === PMA_messages.strQueryCacheEfficiency + ) { + settings.axes.yaxis.tickOptions = { + formatString: '%d %%' + }; + } else if (settings.title === PMA_messages.strSystemMemory || + settings.title === PMA_messages.strSystemSwap + ) { + settings.stackSeries = true; + settings.axes.yaxis.tickOptions = { + formatter: $.jqplot.byteFormatter(2) // MiB + }; + } else if (settings.title === PMA_messages.strTraffic) { + settings.axes.yaxis.tickOptions = { + formatter: $.jqplot.byteFormatter(1) // KiB + }; + } else if (settings.title === PMA_messages.strQuestions || + settings.title === PMA_messages.strConnections + ) { + settings.axes.yaxis.tickOptions = { + formatter: function (format, val) { + if (Math.abs(val) >= 1000000) { + return $.jqplot.sprintf('%.3g M', val / 1000000); + } else if (Math.abs(val) >= 1000) { + return $.jqplot.sprintf('%.3g k', val / 1000); + } else { + return $.jqplot.sprintf('%d', val); + } + } + }; + } + + settings.series = chartObj.series; + + if ($('#' + 'gridchart' + runtime.chartAI).length === 0) { + var numCharts = $('#chartGrid').find('.monitorChart').length; + + if (numCharts === 0 || (numCharts % monitorSettings.columns === 0)) { + $('#chartGrid').append(''); + } + + if (!chartSize) { + calculateChartSize(); + } + $('#chartGrid').find('tr:last').append( + '
      ' + + '
      ' + + '
      ' + ); + } + + // Set series' data as [0,0], smooth lines won't plot with data array having null values. + // also chart won't plot initially with no data and data comes on refreshChartGrid() + var series = []; + for (i in chartObj.series) { + series.push([[0, 0]]); + } + + var tempTooltipContentEditor = function (str, seriesIndex, pointIndex, plot) { + var j; + // TODO: move style to theme CSS + var tooltipHtml = '
      '; + // x value i.e. time + var timeValue = str.split(',')[0]; + var seriesValue; + tooltipHtml += 'Time: ' + timeValue; + tooltipHtml += ''; + // Add y values to the tooltip per series + for (j in plot.series) { + // get y value if present + if (plot.series[j].data.length > pointIndex) { + seriesValue = plot.series[j].data[pointIndex][1]; + } else { + return; + } + var seriesLabel = plot.series[j].label; + var seriesColor = plot.series[j].color; + // format y value + if (plot.series[0]._yaxis.tickOptions.formatter) { + // using formatter function + seriesValue = plot.series[0]._yaxis.tickOptions.formatter('%s', seriesValue); + } else if (plot.series[0]._yaxis.tickOptions.formatString) { + // using format string + seriesValue = PMA_sprintf(plot.series[0]._yaxis.tickOptions.formatString, seriesValue); + } + tooltipHtml += '
      ' + + seriesLabel + ': ' + seriesValue + ''; + } + tooltipHtml += '
      '; + return tooltipHtml; + }; + + // set Tooltip for each series + for (i in settings.series) { + settings.series[i].highlighter = { + show: true, + tooltipContentEditor: tempTooltipContentEditor + }; + } + + chartObj.chart = $.jqplot('gridchart' + runtime.chartAI, series, settings); + // remove [0,0] after plotting + for (i in chartObj.chart.series) { + chartObj.chart.series[i].data.shift(); + } + + var $legend = $('
      ').css('padding', '0.5em'); + for (i in chartObj.chart.series) { + $legend.append( + $('
      ').append( + $('
      ').css({ + width: '1em', + height: '1em', + background: chartObj.chart.seriesColors[i] + }).addClass('floatleft') + ).append( + $('
      ').text( + chartObj.chart.series[i].label + ).addClass('floatleft') + ).append( + $('
      ') + ).addClass('floatleft') + ); + } + $('#gridchart' + runtime.chartAI) + .parent() + .append($legend); + + if (initialize !== true) { + runtime.charts['c' + runtime.chartAI] = chartObj; + buildRequiredDataList(); + } + + // time span selection + $('#gridchart' + runtime.chartAI).on('jqplotMouseDown', function (ev, gridpos, datapos, neighbor, plot) { + drawTimeSpan = true; + selectionTimeDiff.push(datapos.xaxis); + if ($('#selection_box').length) { + $('#selection_box').remove(); + } + var selectionBox = $('
      '); + $(document.body).append(selectionBox); + selectionStartX = ev.pageX; + selectionStartY = ev.pageY; + selectionBox + .attr({ id: 'selection_box' }) + .css({ + top: selectionStartY - gridpos.y, + left: selectionStartX + }) + .fadeIn(); + }); + + $('#gridchart' + runtime.chartAI).on('jqplotMouseUp', function (ev, gridpos, datapos, neighbor, plot) { + if (! drawTimeSpan || editMode) { + return; + } + + selectionTimeDiff.push(datapos.xaxis); + + if (selectionTimeDiff[1] <= selectionTimeDiff[0]) { + selectionTimeDiff = []; + return; + } + // get date from timestamp + var min = new Date(Math.ceil(selectionTimeDiff[0])); + var max = new Date(Math.ceil(selectionTimeDiff[1])); + PMA_getLogAnalyseDialog(min, max); + selectionTimeDiff = []; + drawTimeSpan = false; + }); + + $('#gridchart' + runtime.chartAI).on('jqplotMouseMove', function (ev, gridpos, datapos, neighbor, plot) { + if (! drawTimeSpan || editMode) { + return; + } + if (selectionStartX !== undefined) { + $('#selection_box') + .css({ + width: Math.ceil(ev.pageX - selectionStartX) + }) + .fadeIn(); + } + }); + + $('#gridchart' + runtime.chartAI).on('jqplotMouseLeave', function (ev, gridpos, datapos, neighbor, plot) { + drawTimeSpan = false; + }); + + $(document.body).mouseup(function () { + if ($('#selection_box').length) { + $('#selection_box').remove(); + } + }); + + // Edit, Print icon only in edit mode + $('#chartGrid').find('div svg').find('*[zIndex=20], *[zIndex=21], *[zIndex=19]').toggle(editMode); + + runtime.chartAI++; + } + + function PMA_getLogAnalyseDialog (min, max) { + var $logAnalyseDialog = $('#logAnalyseDialog'); + var $dateStart = $logAnalyseDialog.find('input[name="dateStart"]'); + var $dateEnd = $logAnalyseDialog.find('input[name="dateEnd"]'); + $dateStart.prop('readonly', true); + $dateEnd.prop('readonly', true); + + var dlgBtns = { }; + + dlgBtns[PMA_messages.strFromSlowLog] = function () { + loadLog('slow', min, max); + $(this).dialog('close'); + }; + + dlgBtns[PMA_messages.strFromGeneralLog] = function () { + loadLog('general', min, max); + $(this).dialog('close'); + }; + + $logAnalyseDialog.dialog({ + width: 'auto', + height: 'auto', + buttons: dlgBtns + }); + + PMA_addDatepicker($dateStart, 'datetime', { + showMillisec: false, + showMicrosec: false, + timeFormat: 'HH:mm:ss' + }); + PMA_addDatepicker($dateEnd, 'datetime', { + showMillisec: false, + showMicrosec: false, + timeFormat: 'HH:mm:ss' + }); + $dateStart.datepicker('setDate', min); + $dateEnd.datepicker('setDate', max); + } + + function loadLog (type, min, max) { + var dateStart = Date.parse($('#logAnalyseDialog').find('input[name="dateStart"]').datepicker('getDate')) || min; + var dateEnd = Date.parse($('#logAnalyseDialog').find('input[name="dateEnd"]').datepicker('getDate')) || max; + + loadLogStatistics({ + src: type, + start: dateStart, + end: dateEnd, + removeVariables: $('#removeVariables').prop('checked'), + limitTypes: $('#limitTypes').prop('checked') + }); + } + + /* Called in regular intervals, this function updates the values of each chart in the grid */ + function refreshChartGrid () { + /* Send to server */ + runtime.refreshRequest = $.post('server_status_monitor.php' + PMA_commonParams.get('common_query'), { + ajax_request: true, + chart_data: 1, + type: 'chartgrid', + requiredData: JSON.stringify(runtime.dataList), + server: PMA_commonParams.get('server') + }, function (data) { + var chartData; + if (typeof data !== 'undefined' && data.success === true) { + chartData = data.message; + } else { + return serverResponseError(); + } + var value; + var i = 0; + var diff; + var total; + + /* Update values in each graph */ + $.each(runtime.charts, function (orderKey, elem) { + var key = elem.chartID; + // If newly added chart, we have no data for it yet + if (! chartData[key]) { + return; + } + // Draw all series + total = 0; + for (var j = 0; j < elem.nodes.length; j++) { + // Update x-axis + if (i === 0 && j === 0) { + if (oldChartData === null) { + diff = chartData.x - runtime.xmax; + } else { + diff = parseInt(chartData.x - oldChartData.x, 10); + } + + runtime.xmin += diff; + runtime.xmax += diff; + } + + // elem.chart.xAxis[0].setExtremes(runtime.xmin, runtime.xmax, false); + /* Calculate y value */ + + // If transform function given, use it + if (elem.nodes[j].transformFn) { + value = chartValueTransform( + elem.nodes[j].transformFn, + chartData[key][j], + // Check if first iteration (oldChartData==null), or if newly added chart oldChartData[key]==null + ( + oldChartData === null || + oldChartData[key] === null || + oldChartData[key] === undefined ? null : oldChartData[key][j] + ) + ); + + // Otherwise use original value and apply differential and divisor if given, + // in this case we have only one data point per series - located at chartData[key][j][0] + } else { + value = parseFloat(chartData[key][j][0].value); + + if (elem.nodes[j].display === 'differential') { + if (oldChartData === null || + oldChartData[key] === null || + oldChartData[key] === undefined + ) { + continue; + } + value -= oldChartData[key][j][0].value; + } + + if (elem.nodes[j].valueDivisor) { + value = value / elem.nodes[j].valueDivisor; + } + } + + // Set y value, if defined + if (value !== undefined) { + elem.chart.series[j].data.push([chartData.x, value]); + if (value > elem.maxYLabel) { + elem.maxYLabel = value; + } else if (elem.maxYLabel === 0) { + elem.maxYLabel = 0.5; + } + // free old data point values and update maxYLabel + if (elem.chart.series[j].data.length > runtime.gridMaxPoints && + elem.chart.series[j].data[0][0] < runtime.xmin + ) { + // check if the next freeable point is highest + if (elem.maxYLabel <= elem.chart.series[j].data[0][1]) { + elem.chart.series[j].data.splice(0, elem.chart.series[j].data.length - runtime.gridMaxPoints); + elem.maxYLabel = getMaxYLabel(elem.chart.series[j].data); + } else { + elem.chart.series[j].data.splice(0, elem.chart.series[j].data.length - runtime.gridMaxPoints); + } + } + if (elem.title === PMA_messages.strSystemMemory || + elem.title === PMA_messages.strSystemSwap + ) { + total += value; + } + } + } + + // update chart options + // keep ticks number/positioning consistent while refreshrate changes + var tickInterval = (runtime.xmax - runtime.xmin) / 5; + elem.chart.axes.xaxis.ticks = [(runtime.xmax - tickInterval * 4), + (runtime.xmax - tickInterval * 3), (runtime.xmax - tickInterval * 2), + (runtime.xmax - tickInterval), runtime.xmax]; + + if (elem.title !== PMA_messages.strSystemCPUUsage && + elem.title !== PMA_messages.strQueryCacheEfficiency && + elem.title !== PMA_messages.strSystemMemory && + elem.title !== PMA_messages.strSystemSwap + ) { + elem.chart.axes.yaxis.max = Math.ceil(elem.maxYLabel * 1.1); + elem.chart.axes.yaxis.tickInterval = Math.ceil(elem.maxYLabel * 1.1 / 5); + } else if (elem.title === PMA_messages.strSystemMemory || + elem.title === PMA_messages.strSystemSwap + ) { + elem.chart.axes.yaxis.max = Math.ceil(total * 1.1 / 100) * 100; + elem.chart.axes.yaxis.tickInterval = Math.ceil(total * 1.1 / 5); + } + i++; + + if (runtime.redrawCharts) { + elem.chart.replot(); + } + }); + + oldChartData = chartData; + + runtime.refreshTimeout = setTimeout(refreshChartGrid, monitorSettings.gridRefresh); + }); + } + + /* Function to get highest plotted point's y label, to scale the chart, + * TODO: make jqplot's autoscale:true work here + */ + function getMaxYLabel (dataValues) { + var maxY = dataValues[0][1]; + $.each(dataValues, function (k, v) { + maxY = (v[1] > maxY) ? v[1] : maxY; + }); + return maxY; + } + + /* Function that supplies special value transform functions for chart values */ + function chartValueTransform (name, cur, prev) { + switch (name) { + case 'cpu-linux': + if (prev === null) { + return undefined; + } + // cur and prev are datapoint arrays, but containing + // only 1 element for cpu-linux + cur = cur[0]; + prev = prev[0]; + + var diff_total = cur.busy + cur.idle - (prev.busy + prev.idle); + var diff_idle = cur.idle - prev.idle; + return 100 * (diff_total - diff_idle) / diff_total; + + // Query cache efficiency (%) + case 'qce': + if (prev === null) { + return undefined; + } + // cur[0].value is Qcache_hits, cur[1].value is Com_select + var diffQHits = cur[0].value - prev[0].value; + // No NaN please :-) + if (cur[1].value - prev[1].value === 0) { + return 0; + } + + return diffQHits / (cur[1].value - prev[1].value + diffQHits) * 100; + + // Query cache usage (%) + case 'qcu': + if (cur[1].value === 0) { + return 0; + } + // cur[0].value is Qcache_free_memory, cur[1].value is query_cache_size + return 100 - cur[0].value / cur[1].value * 100; + } + return undefined; + } + + /* Build list of nodes that need to be retrieved from server. + * It creates something like a stripped down version of the runtime.charts object. + */ + function buildRequiredDataList () { + runtime.dataList = {}; + // Store an own id, because the property name is subject of reordering, + // thus destroying our mapping with runtime.charts <=> runtime.dataList + var chartID = 0; + $.each(runtime.charts, function (key, chart) { + runtime.dataList[chartID] = []; + for (var i = 0, l = chart.nodes.length; i < l; i++) { + runtime.dataList[chartID][i] = chart.nodes[i].dataPoints; + } + runtime.charts[key].chartID = chartID; + chartID++; + }); + } + + /* Loads the log table data, generates the table and handles the filters */ + function loadLogStatistics (opts) { + var tableStr = ''; + var logRequest = null; + + if (! opts.removeVariables) { + opts.removeVariables = false; + } + if (! opts.limitTypes) { + opts.limitTypes = false; + } + + $('#emptyDialog').dialog({ title: PMA_messages.strAnalysingLogsTitle }); + $('#emptyDialog').html(PMA_messages.strAnalysingLogs + + ' '); + var dlgBtns = {}; + + dlgBtns[PMA_messages.strCancelRequest] = function () { + if (logRequest !== null) { + logRequest.abort(); + } + + $(this).dialog('close'); + }; + + $('#emptyDialog').dialog({ + width: 'auto', + height: 'auto', + buttons: dlgBtns + }); + + logRequest = $.post( + 'server_status_monitor.php' + PMA_commonParams.get('common_query'), + { + ajax_request: true, + log_data: 1, + type: opts.src, + time_start: Math.round(opts.start / 1000), + time_end: Math.round(opts.end / 1000), + removeVariables: opts.removeVariables, + limitTypes: opts.limitTypes + }, + function (data) { + var logData; + var dlgBtns = {}; + if (typeof data !== 'undefined' && data.success === true) { + logData = data.message; + } else { + return serverResponseError(); + } + + if (logData.rows.length === 0) { + $('#emptyDialog').dialog({ title: PMA_messages.strNoDataFoundTitle }); + $('#emptyDialog').html('

      ' + PMA_messages.strNoDataFound + '

      '); + + dlgBtns[PMA_messages.strClose] = function () { + $(this).dialog('close'); + }; + + $('#emptyDialog').dialog('option', 'buttons', dlgBtns); + return; + } + + runtime.logDataCols = buildLogTable(logData, opts.removeVariables); + + /* Show some stats in the dialog */ + $('#emptyDialog').dialog({ title: PMA_messages.strLoadingLogs }); + $('#emptyDialog').html('

      ' + PMA_messages.strLogDataLoaded + '

      '); + $.each(logData.sum, function (key, value) { + key = key.charAt(0).toUpperCase() + key.slice(1).toLowerCase(); + if (key === 'Total') { + key = '' + key + ''; + } + $('#emptyDialog').append(key + ': ' + value + '
      '); + }); + + /* Add filter options if more than a bunch of rows there to filter */ + if (logData.numRows > 12) { + $('#logTable').prepend( + '
      ' + + ' ' + PMA_messages.strFiltersForLogTable + '' + + '
      ' + + ' ' + + ' ' + + '
      ' + + ((logData.numRows > 250) ? '
      ' : '') + + '
      ' + + ' ' + + ' ' + + ' ' + ); + + $('#noWHEREData').change(function () { + filterQueries(true); + }); + + if (logData.numRows > 250) { + $('#startFilterQueryText').click(filterQueries); + } else { + $('#filterQueryText').keyup(filterQueries); + } + } + + dlgBtns[PMA_messages.strJumpToTable] = function () { + $(this).dialog('close'); + $(document).scrollTop($('#logTable').offset().top); + }; + + $('#emptyDialog').dialog('option', 'buttons', dlgBtns); + } + ); + + /* Handles the actions performed when the user uses any of the + * log table filters which are the filter by name and grouping + * with ignoring data in WHERE clauses + * + * @param boolean Should be true when the users enabled or disabled + * to group queries ignoring data in WHERE clauses + */ + function filterQueries (varFilterChange) { + var cell; + var textFilter; + var val = $('#filterQueryText').val(); + + if (val.length === 0) { + textFilter = null; + } else { + try { + textFilter = new RegExp(val, 'i'); + $('#filterQueryText').removeClass('error'); + } catch (e) { + if (e instanceof SyntaxError) { + $('#filterQueryText').addClass('error'); + textFilter = null; + } + } + } + + var rowSum = 0; + var totalSum = 0; + var i = 0; + var q; + var noVars = $('#noWHEREData').prop('checked'); + var equalsFilter = /([^=]+)=(\d+|((\'|"|).*?[^\\])\4((\s+)|$))/gi; + var functionFilter = /([a-z0-9_]+)\(.+?\)/gi; + var filteredQueries = {}; + var filteredQueriesLines = {}; + var hide = false; + var rowData; + var queryColumnName = runtime.logDataCols[runtime.logDataCols.length - 2]; + var sumColumnName = runtime.logDataCols[runtime.logDataCols.length - 1]; + var isSlowLog = opts.src === 'slow'; + var columnSums = {}; + + // For the slow log we have to count many columns (query_time, lock_time, rows_examined, rows_sent, etc.) + var countRow = function (query, row) { + var cells = row.match(/(.*?)<\/td>/gi); + if (!columnSums[query]) { + columnSums[query] = [0, 0, 0, 0]; + } + + // lock_time and query_time and displayed in timespan format + columnSums[query][0] += timeToSec(cells[2].replace(/(|<\/td>)/gi, '')); + columnSums[query][1] += timeToSec(cells[3].replace(/(|<\/td>)/gi, '')); + // rows_examind and rows_sent are just numbers + columnSums[query][2] += parseInt(cells[4].replace(/(|<\/td>)/gi, ''), 10); + columnSums[query][3] += parseInt(cells[5].replace(/(|<\/td>)/gi, ''), 10); + }; + + // We just assume the sql text is always in the second last column, and that the total count is right of it + $('#logTable').find('table tbody tr td:nth-child(' + (runtime.logDataCols.length - 1) + ')').each(function () { + var $t = $(this); + // If query is a SELECT and user enabled or disabled to group + // queries ignoring data in where statements, we + // need to re-calculate the sums of each row + if (varFilterChange && $t.html().match(/^SELECT/i)) { + if (noVars) { + // Group on => Sum up identical columns, and hide all but 1 + + q = $t.text().replace(equalsFilter, '$1=...$6').trim(); + q = q.replace(functionFilter, ' $1(...)'); + + // Js does not specify a limit on property name length, + // so we can abuse it as index :-) + if (filteredQueries[q]) { + filteredQueries[q] += parseInt($t.next().text(), 10); + totalSum += parseInt($t.next().text(), 10); + hide = true; + } else { + filteredQueries[q] = parseInt($t.next().text(), 10); + filteredQueriesLines[q] = i; + $t.text(q); + } + if (isSlowLog) { + countRow(q, $t.parent().html()); + } + } else { + // Group off: Restore original columns + + rowData = $t.parent().data('query'); + // Restore SQL text + $t.text(rowData[queryColumnName]); + // Restore total count + $t.next().text(rowData[sumColumnName]); + // Restore slow log columns + if (isSlowLog) { + $t.parent().children('td:nth-child(3)').text(rowData.query_time); + $t.parent().children('td:nth-child(4)').text(rowData.lock_time); + $t.parent().children('td:nth-child(5)').text(rowData.rows_sent); + $t.parent().children('td:nth-child(6)').text(rowData.rows_examined); + } + } + } + + // If not required to be hidden, do we need + // to hide because of a not matching text filter? + if (! hide && (textFilter !== null && ! textFilter.exec($t.text()))) { + hide = true; + } + + // Now display or hide this column + if (hide) { + $t.parent().css('display', 'none'); + } else { + totalSum += parseInt($t.next().text(), 10); + rowSum++; + $t.parent().css('display', ''); + } + + hide = false; + i++; + }); + + // We finished summarizing counts => Update count values of all grouped entries + if (varFilterChange) { + if (noVars) { + var numCol; + var row; + var $table = $('#logTable').find('table tbody'); + $.each(filteredQueriesLines, function (key, value) { + if (filteredQueries[key] <= 1) { + return; + } + + row = $table.children('tr:nth-child(' + (value + 1) + ')'); + numCol = row.children(':nth-child(' + (runtime.logDataCols.length) + ')'); + numCol.text(filteredQueries[key]); + + if (isSlowLog) { + row.children('td:nth-child(3)').text(secToTime(columnSums[key][0])); + row.children('td:nth-child(4)').text(secToTime(columnSums[key][1])); + row.children('td:nth-child(5)').text(columnSums[key][2]); + row.children('td:nth-child(6)').text(columnSums[key][3]); + } + }); + } + + $('#logTable').find('table').trigger('update'); + setTimeout(function () { + $('#logTable').find('table').trigger('sorton', [[[runtime.logDataCols.length - 1, 1]]]); + }, 0); + } + + // Display some stats at the bottom of the table + $('#logTable').find('table tfoot tr') + .html('' + + PMA_messages.strSumRows + ' ' + rowSum + '' + + PMA_messages.strTotal + '' + totalSum + ''); + } + } + + /* Turns a timespan (12:12:12) into a number */ + function timeToSec (timeStr) { + var time = timeStr.split(':'); + return (parseInt(time[0], 10) * 3600) + (parseInt(time[1], 10) * 60) + parseInt(time[2], 10); + } + + /* Turns a number into a timespan (100 into 00:01:40) */ + function secToTime (timeInt) { + var hours = Math.floor(timeInt / 3600); + timeInt -= hours * 3600; + var minutes = Math.floor(timeInt / 60); + timeInt -= minutes * 60; + + if (hours < 10) { + hours = '0' + hours; + } + if (minutes < 10) { + minutes = '0' + minutes; + } + if (timeInt < 10) { + timeInt = '0' + timeInt; + } + + return hours + ':' + minutes + ':' + timeInt; + } + + /* Constructs the log table out of the retrieved server data */ + function buildLogTable (data, groupInserts) { + var rows = data.rows; + var cols = []; + var $table = $('
      '); + var $tBody; + var $tRow; + var $tCell; + + $('#logTable').html($table); + + var tempPushKey = function (key, value) { + cols.push(key); + }; + + var formatValue = function (name, value) { + if (name === 'user_host') { + return value.replace(/(\[.*?\])+/g, ''); + } + return escapeHtml(value); + }; + + for (var i = 0, l = rows.length; i < l; i++) { + if (i === 0) { + $.each(rows[0], tempPushKey); + $table.append('' + + '' + cols.join('') + '' + + '' + ); + + $table.append($tBody = $('')); + } + + $tBody.append($tRow = $('')); + var cl = ''; + for (var j = 0, ll = cols.length; j < ll; j++) { + // Assuming the query column is the second last + if (j === cols.length - 2 && rows[i][cols[j]].match(/^SELECT/i)) { + $tRow.append($tCell = $('' + formatValue(cols[j], rows[i][cols[j]]) + '')); + $tCell.click(openQueryAnalyzer); + } else { + $tRow.append('' + formatValue(cols[j], rows[i][cols[j]]) + ''); + } + + $tRow.data('query', rows[i]); + } + } + + $table.append('' + + '' + PMA_messages.strSumRows + + ' ' + data.numRows + '' + PMA_messages.strTotal + + '' + data.sum.TOTAL + ''); + + // Append a tooltip to the count column, if there exist one + if ($('#logTable').find('tr:first th:last').text().indexOf('#') > -1) { + $('#logTable').find('tr:first th:last').append(' ' + PMA_getImage('b_help', '', { 'class': 'qroupedQueryInfoIcon' })); + + var tooltipContent = PMA_messages.strCountColumnExplanation; + if (groupInserts) { + tooltipContent += '

      ' + PMA_messages.strMoreCountColumnExplanation + '

      '; + } + + PMA_tooltip( + $('img.qroupedQueryInfoIcon'), + 'img', + tooltipContent + ); + } + + $('#logTable').find('table').tablesorter({ + sortList: [[cols.length - 1, 1]], + widgets: ['fast-zebra'] + }); + + $('#logTable').find('table thead th') + .append('
      '); + + return cols; + } + + /* Opens the query analyzer dialog */ + function openQueryAnalyzer () { + var rowData = $(this).parent().data('query'); + var query = rowData.argument || rowData.sql_text; + + if (codemirror_editor) { + // TODO: somehow PMA_SQLPrettyPrint messes up the query, needs be fixed + // query = PMA_SQLPrettyPrint(query); + codemirror_editor.setValue(query); + // Codemirror is bugged, it doesn't refresh properly sometimes. + // Following lines seem to fix that + setTimeout(function () { + codemirror_editor.refresh(); + }, 50); + } else { + $('#sqlquery').val(query); + } + + var profilingChart = null; + var dlgBtns = {}; + + dlgBtns[PMA_messages.strAnalyzeQuery] = function () { + loadQueryAnalysis(rowData); + }; + dlgBtns[PMA_messages.strClose] = function () { + $(this).dialog('close'); + }; + + $('#queryAnalyzerDialog').dialog({ + width: 'auto', + height: 'auto', + resizable: false, + buttons: dlgBtns, + close: function () { + if (profilingChart !== null) { + profilingChart.destroy(); + } + $('#queryAnalyzerDialog').find('div.placeHolder').html(''); + if (codemirror_editor) { + codemirror_editor.setValue(''); + } else { + $('#sqlquery').val(''); + } + } + }); + } + + /* Loads and displays the analyzed query data */ + function loadQueryAnalysis (rowData) { + var db = rowData.db || ''; + + $('#queryAnalyzerDialog').find('div.placeHolder').html( + PMA_messages.strAnalyzing + ' '); + + $.post('server_status_monitor.php' + PMA_commonParams.get('common_query'), { + ajax_request: true, + query_analyzer: true, + query: codemirror_editor ? codemirror_editor.getValue() : $('#sqlquery').val(), + database: db, + server: PMA_commonParams.get('server') + }, function (data) { + var i; + var l; + if (typeof data !== 'undefined' && data.success === true) { + data = data.message; + } + if (data.error) { + if (data.error.indexOf('1146') !== -1 || data.error.indexOf('1046') !== -1) { + data.error = PMA_messages.strServerLogError; + } + $('#queryAnalyzerDialog').find('div.placeHolder').html('
      ' + data.error + '
      '); + return; + } + var totalTime = 0; + // Float sux, I'll use table :( + $('#queryAnalyzerDialog').find('div.placeHolder') + .html('
      '); + + var explain = '' + PMA_messages.strExplainOutput + ' ' + $('#explain_docu').html(); + if (data.explain.length > 1) { + explain += ' ('; + for (i = 0; i < data.explain.length; i++) { + if (i > 0) { + explain += ', '; + } + explain += '' + i + ''; + } + explain += ')'; + } + explain += '

      '; + + var tempExplain = function (key, value) { + value = (value === null) ? 'null' : escapeHtml(value); + + if (key === 'type' && value.toLowerCase() === 'all') { + value = '' + value + ''; + } + if (key === 'Extra') { + value = value.replace(/(using (temporary|filesort))/gi, '$1'); + } + explain += key + ': ' + value + '
      '; + }; + + for (i = 0, l = data.explain.length; i < l; i++) { + explain += '
      0 ? 'style="display:none;"' : '') + '>'; + $.each(data.explain[i], tempExplain); + explain += '
      '; + } + + explain += '

      ' + PMA_messages.strAffectedRows + ' ' + data.affectedRows; + + $('#queryAnalyzerDialog').find('div.placeHolder td.explain').append(explain); + + $('#queryAnalyzerDialog').find('div.placeHolder a[href*="#showExplain"]').click(function () { + var id = $(this).attr('href').split('-')[1]; + $(this).parent().find('div[class*="explain"]').hide(); + $(this).parent().find('div[class*="explain-' + id + '"]').show(); + }); + + if (data.profiling) { + var chartData = []; + var numberTable = ''; + var duration; + var otherTime = 0; + + for (i = 0, l = data.profiling.length; i < l; i++) { + duration = parseFloat(data.profiling[i].duration); + + totalTime += duration; + + numberTable += ''; + } + + // Only put those values in the pie which are > 2% + for (i = 0, l = data.profiling.length; i < l; i++) { + duration = parseFloat(data.profiling[i].duration); + + if (duration / totalTime > 0.02) { + chartData.push([PMA_prettyProfilingNum(duration, 2) + ' ' + data.profiling[i].state, duration]); + } else { + otherTime += duration; + } + } + + if (otherTime > 0) { + chartData.push([PMA_prettyProfilingNum(otherTime, 2) + ' ' + PMA_messages.strOther, otherTime]); + } + + numberTable += ''; + numberTable += '
      ' + PMA_messages.strStatus + '' + PMA_messages.strTime + '
      ' + data.profiling[i].state + ' ' + PMA_prettyProfilingNum(duration, 2) + '
      ' + PMA_messages.strTotalTime + '' + PMA_prettyProfilingNum(totalTime, 2) + '
      '; + + $('#queryAnalyzerDialog').find('div.placeHolder td.chart').append( + '' + PMA_messages.strProfilingResults + ' ' + $('#profiling_docu').html() + ' ' + + '(' + PMA_messages.strTable + ', ' + PMA_messages.strChart + ')
      ' + + numberTable + '

      '); + + $('#queryAnalyzerDialog').find('div.placeHolder a[href="#showNums"]').click(function () { + $('#queryAnalyzerDialog').find('#queryProfiling').hide(); + $('#queryAnalyzerDialog').find('table.queryNums').show(); + return false; + }); + + $('#queryAnalyzerDialog').find('div.placeHolder a[href="#showChart"]').click(function () { + $('#queryAnalyzerDialog').find('#queryProfiling').show(); + $('#queryAnalyzerDialog').find('table.queryNums').hide(); + return false; + }); + + profilingChart = PMA_createProfilingChart( + 'queryProfiling', + chartData + ); + + // $('#queryProfiling').resizable(); + } + }); + } + + /* Saves the monitor to localstorage */ + function saveMonitor () { + var gridCopy = {}; + + $.each(runtime.charts, function (key, elem) { + gridCopy[key] = {}; + gridCopy[key].nodes = elem.nodes; + gridCopy[key].settings = elem.settings; + gridCopy[key].title = elem.title; + gridCopy[key].series = elem.series; + gridCopy[key].maxYLabel = elem.maxYLabel; + }); + + if (isStorageSupported('localStorage')) { + window.localStorage.monitorCharts = JSON.stringify(gridCopy); + window.localStorage.monitorSettings = JSON.stringify(monitorSettings); + window.localStorage.monitorVersion = monitorProtocolVersion; + } + + $('a[href="#clearMonitorConfig"]').show(); + } +}); + +// Run the monitor once loaded +AJAX.registerOnload('server_status_monitor.js', function () { + $('a[href="#pauseCharts"]').trigger('click'); +}); + +function serverResponseError () { + var btns = {}; + btns[PMA_messages.strReloadPage] = function () { + window.location.reload(); + }; + $('#emptyDialog').dialog({ title: PMA_messages.strRefreshFailed }); + $('#emptyDialog').html( + PMA_getImage('s_attention') + + PMA_messages.strInvalidResponseExplanation + ); + $('#emptyDialog').dialog({ buttons: btns }); +} + +/* Destroys all monitor related resources */ +function destroyGrid () { + if (runtime.charts) { + $.each(runtime.charts, function (key, value) { + try { + value.chart.destroy(); + } catch (err) {} + }); + } + + try { + runtime.refreshRequest.abort(); + } catch (err) {} + try { + clearTimeout(runtime.refreshTimeout); + } catch (err) {} + $('#chartGrid').html(''); + runtime.charts = null; + runtime.chartAI = 0; + monitorSettings = null; // TODO:this not global variable +} diff --git a/php/apps/phpmyadmin49/html/js/server_status_processes.js b/php/apps/phpmyadmin49/html/js/server_status_processes.js new file mode 100644 index 00000000..2865bca9 --- /dev/null +++ b/php/apps/phpmyadmin49/html/js/server_status_processes.js @@ -0,0 +1,189 @@ +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * Server Status Processes + * + * @package PhpMyAdmin + */ + +// object to store process list state information +var processList = { + + // denotes whether auto refresh is on or off + autoRefresh: false, + // stores the GET request which refresh process list + refreshRequest: null, + // stores the timeout id returned by setTimeout + refreshTimeout: null, + // the refresh interval in seconds + refreshInterval: null, + // the refresh URL (required to save last used option) + // i.e. full or sorting url + refreshUrl: null, + + /** + * Handles killing of a process + * + * @return void + */ + init: function () { + processList.setRefreshLabel(); + if (processList.refreshUrl === null) { + processList.refreshUrl = 'server_status_processes.php' + + PMA_commonParams.get('common_query'); + } + if (processList.refreshInterval === null) { + processList.refreshInterval = $('#id_refreshRate').val(); + } else { + $('#id_refreshRate').val(processList.refreshInterval); + } + }, + + /** + * Handles killing of a process + * + * @param object the event object + * + * @return void + */ + killProcessHandler: function (event) { + event.preventDefault(); + var argSep = PMA_commonParams.get('arg_separator'); + var params = $(this).getPostData(); + params += argSep + 'ajax_request=1' + argSep + 'server=' + PMA_commonParams.get('server'); + // Get row element of the process to be killed. + var $tr = $(this).closest('tr'); + $.post($(this).attr('href'), params, function (data) { + // Check if process was killed or not. + if (data.hasOwnProperty('success') && data.success) { + // remove the row of killed process. + $tr.remove(); + // As we just removed a row, reapply odd-even classes + // to keep table stripes consistent + var $tableProcessListTr = $('#tableprocesslist').find('> tbody > tr'); + $tableProcessListTr.filter(':even').removeClass('odd').addClass('even'); + $tableProcessListTr.filter(':odd').removeClass('even').addClass('odd'); + // Show process killed message + PMA_ajaxShowMessage(data.message, false); + } else { + // Show process error message + PMA_ajaxShowMessage(data.error, false); + } + }, 'json'); + }, + + /** + * Handles Auto Refreshing + * + * @param object the event object + * + * @return void + */ + refresh: function (event) { + // abort any previous pending requests + // this is necessary, it may go into + // multiple loops causing unnecessary + // requests even after leaving the page. + processList.abortRefresh(); + // if auto refresh is enabled + if (processList.autoRefresh) { + var interval = parseInt(processList.refreshInterval, 10) * 1000; + var urlParams = processList.getUrlParams(); + processList.refreshRequest = $.post(processList.refreshUrl, + urlParams, + function (data) { + if (data.hasOwnProperty('success') && data.success) { + $newTable = $(data.message); + $('#tableprocesslist').html($newTable.html()); + PMA_highlightSQL($('#tableprocesslist')); + } + processList.refreshTimeout = setTimeout( + processList.refresh, + interval + ); + }); + } + }, + + /** + * Stop current request and clears timeout + * + * @return void + */ + abortRefresh: function () { + if (processList.refreshRequest !== null) { + processList.refreshRequest.abort(); + processList.refreshRequest = null; + } + clearTimeout(processList.refreshTimeout); + }, + + /** + * Set label of refresh button + * change between play & pause + * + * @return void + */ + setRefreshLabel: function () { + var img = 'play'; + var label = PMA_messages.strStartRefresh; + if (processList.autoRefresh) { + img = 'pause'; + label = PMA_messages.strStopRefresh; + processList.refresh(); + } + $('a#toggleRefresh').html(PMA_getImage(img) + escapeHtml(label)); + }, + + /** + * Return the Url Parameters + * for autorefresh request, + * includes showExecuting if the filter is checked + * + * @return urlParams - url parameters with autoRefresh request + */ + getUrlParams: function () { + var urlParams = { 'ajax_request': true, 'refresh': true }; + if ($('#showExecuting').is(':checked')) { + urlParams.showExecuting = true; + return urlParams; + } + return urlParams; + } +}; + +AJAX.registerOnload('server_status_processes.js', function () { + processList.init(); + // Bind event handler for kill_process + $('#tableprocesslist').on( + 'click', + 'a.kill_process', + processList.killProcessHandler + ); + // Bind event handler for toggling refresh of process list + $('a#toggleRefresh').on('click', function (event) { + event.preventDefault(); + processList.autoRefresh = !processList.autoRefresh; + processList.setRefreshLabel(); + }); + // Bind event handler for change in refresh rate + $('#id_refreshRate').on('change', function (event) { + processList.refreshInterval = $(this).val(); + processList.refresh(); + }); + // Bind event handler for table header links + $('#tableprocesslist').on('click', 'thead a', function () { + processList.refreshUrl = $(this).attr('href'); + }); +}); + +/** + * Unbind all event handlers before tearing down a page + */ +AJAX.registerTeardown('server_status_processes.js', function () { + $('#tableprocesslist').off('click', 'a.kill_process'); + $('a#toggleRefresh').off('click'); + $('#id_refreshRate').off('change'); + $('#tableprocesslist').off('click', 'thead a'); + // stop refreshing further + processList.abortRefresh(); +}); diff --git a/php/apps/phpmyadmin49/html/js/server_status_queries.js b/php/apps/phpmyadmin49/html/js/server_status_queries.js new file mode 100644 index 00000000..056cffeb --- /dev/null +++ b/php/apps/phpmyadmin49/html/js/server_status_queries.js @@ -0,0 +1,34 @@ +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + */ + +/** + * Unbind all event handlers before tearing down a page + */ +AJAX.registerTeardown('server_status_queries.js', function () { + var queryPieChart = $('#serverstatusquerieschart').data('queryPieChart'); + if (queryPieChart) { + queryPieChart.destroy(); + } +}); + +AJAX.registerOnload('server_status_queries.js', function () { + // Build query statistics chart + var cdata = []; + try { + $.each($('#serverstatusquerieschart').data('chart'), function (key, value) { + cdata.push([key, parseInt(value, 10)]); + }); + $('#serverstatusquerieschart').data( + 'queryPieChart', + PMA_createProfilingChart( + 'serverstatusquerieschart', + cdata + ) + ); + } catch (exception) { + // Could not load chart, no big deal... + } + + initTableSorter('statustabs_queries'); +}); diff --git a/php/apps/phpmyadmin49/html/js/server_status_sorter.js b/php/apps/phpmyadmin49/html/js/server_status_sorter.js new file mode 100644 index 00000000..36c918a8 --- /dev/null +++ b/php/apps/phpmyadmin49/html/js/server_status_sorter.js @@ -0,0 +1,70 @@ +// TODO: tablesorter shouldn't sort already sorted columns +function initTableSorter (tabid) { + var $table; + var opts; + switch (tabid) { + case 'statustabs_queries': + $table = $('#serverstatusqueriesdetails'); + opts = { + sortList: [[3, 1]], + headers: { + 1: { sorter: 'fancyNumber' }, + 2: { sorter: 'fancyNumber' } + } + }; + break; + } + $table.tablesorter(opts); + $table.find('tr:first th') + .append('
      '); +} + +$(function () { + $.tablesorter.addParser({ + id: 'fancyNumber', + is: function (s) { + return (/^[0-9]?[0-9,\.]*\s?(k|M|G|T|%)?$/).test(s); + }, + format: function (s) { + var num = jQuery.tablesorter.formatFloat( + s.replace(PMA_messages.strThousandsSeparator, '') + .replace(PMA_messages.strDecimalSeparator, '.') + ); + + var factor = 1; + switch (s.charAt(s.length - 1)) { + case '%': + factor = -2; + break; + // Todo: Complete this list (as well as in the regexp a few lines up) + case 'k': + factor = 3; + break; + case 'M': + factor = 6; + break; + case 'G': + factor = 9; + break; + case 'T': + factor = 12; + break; + } + + return num * Math.pow(10, factor); + }, + type: 'numeric' + }); + + $.tablesorter.addParser({ + id: 'withinSpanNumber', + is: function (s) { + return (/(.*)?<\/span>/); + return (res && res.length >= 3) ? res[2] : 0; + }, + type: 'numeric' + }); +}); diff --git a/php/apps/phpmyadmin49/html/js/server_status_variables.js b/php/apps/phpmyadmin49/html/js/server_status_variables.js new file mode 100644 index 00000000..4028bc75 --- /dev/null +++ b/php/apps/phpmyadmin49/html/js/server_status_variables.js @@ -0,0 +1,100 @@ +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * + * + * @package PhpMyAdmin + */ + +/** + * Unbind all event handlers before tearing down a page + */ +AJAX.registerTeardown('server_status_variables.js', function () { + $('#filterAlert').off('change'); + $('#filterText').off('keyup'); + $('#filterCategory').off('change'); + $('#dontFormat').off('change'); +}); + +AJAX.registerOnload('server_status_variables.js', function () { + // Filters for status variables + var textFilter = null; + var alertFilter = $('#filterAlert').prop('checked'); + var categoryFilter = $('#filterCategory').find(':selected').val(); + var text = ''; // Holds filter text + + /* 3 Filtering functions */ + $('#filterAlert').change(function () { + alertFilter = this.checked; + filterVariables(); + }); + + $('#filterCategory').change(function () { + categoryFilter = $(this).val(); + filterVariables(); + }); + + $('#dontFormat').change(function () { + // Hiding the table while changing values speeds up the process a lot + $('#serverstatusvariables').hide(); + $('#serverstatusvariables').find('td.value span.original').toggle(this.checked); + $('#serverstatusvariables').find('td.value span.formatted').toggle(! this.checked); + $('#serverstatusvariables').show(); + }).trigger('change'); + + $('#filterText').keyup(function (e) { + var word = $(this).val().replace(/_/g, ' '); + if (word.length === 0) { + textFilter = null; + } else { + try { + textFilter = new RegExp('(^| )' + word, 'i'); + $(this).removeClass('error'); + } catch (e) { + if (e instanceof SyntaxError) { + $(this).addClass('error'); + textFilter = null; + } + } + } + text = word; + filterVariables(); + }).trigger('keyup'); + + /* Filters the status variables by name/category/alert in the variables tab */ + function filterVariables () { + var useful_links = 0; + var section = text; + + if (categoryFilter.length > 0) { + section = categoryFilter; + } + + if (section.length > 1) { + $('#linkSuggestions').find('span').each(function () { + if ($(this).attr('class').indexOf('status_' + section) !== -1) { + useful_links++; + $(this).css('display', ''); + } else { + $(this).css('display', 'none'); + } + }); + } + + if (useful_links > 0) { + $('#linkSuggestions').css('display', ''); + } else { + $('#linkSuggestions').css('display', 'none'); + } + + $('#serverstatusvariables').find('th.name').each(function () { + if ((textFilter === null || textFilter.exec($(this).text())) && + (! alertFilter || $(this).next().find('span.attention').length > 0) && + (categoryFilter.length === 0 || $(this).parent().hasClass('s_' + categoryFilter)) + ) { + $(this).parent().css('display', ''); + } else { + $(this).parent().css('display', 'none'); + } + }); + } +}); diff --git a/php/apps/phpmyadmin49/html/js/server_user_groups.js b/php/apps/phpmyadmin49/html/js/server_user_groups.js new file mode 100644 index 00000000..513777a9 --- /dev/null +++ b/php/apps/phpmyadmin49/html/js/server_user_groups.js @@ -0,0 +1,41 @@ +/** + * Unbind all event handlers before tearing down a page + */ +AJAX.registerTeardown('server_user_groups.js', function () { + $(document).off('click', 'a.deleteUserGroup.ajax'); +}); + +/** + * Bind event handlers + */ +AJAX.registerOnload('server_user_groups.js', function () { + // update the checkall checkbox on Edit user group page + $(checkboxes_sel).trigger('change'); + + $(document).on('click', 'a.deleteUserGroup.ajax', function (event) { + event.preventDefault(); + var $link = $(this); + var groupName = $link.parents('tr').find('td:first').text(); + var buttonOptions = {}; + buttonOptions[PMA_messages.strGo] = function () { + $(this).dialog('close'); + $link.removeClass('ajax').trigger('click'); + }; + buttonOptions[PMA_messages.strClose] = function () { + $(this).dialog('close'); + }; + $('
      ') + .attr('id', 'confirmUserGroupDeleteDialog') + .append(PMA_sprintf(PMA_messages.strDropUserGroupWarning, escapeHtml(groupName))) + .dialog({ + width: 300, + minWidth: 200, + modal: true, + buttons: buttonOptions, + title: PMA_messages.strConfirm, + close: function () { + $(this).remove(); + } + }); + }); +}); diff --git a/php/apps/phpmyadmin49/html/js/server_variables.js b/php/apps/phpmyadmin49/html/js/server_variables.js new file mode 100644 index 00000000..6b7f01fa --- /dev/null +++ b/php/apps/phpmyadmin49/html/js/server_variables.js @@ -0,0 +1,112 @@ +/* vim: set expandtab sw=4 ts=4 sts=4: */ + +/** + * Unbind all event handlers before tearing down a page + */ +AJAX.registerTeardown('server_variables.js', function () { + $(document).off('click', 'a.editLink'); + $('#serverVariables').find('.var-name').find('a img').remove(); +}); + +AJAX.registerOnload('server_variables.js', function () { + var $editLink = $('a.editLink'); + var $saveLink = $('a.saveLink'); + var $cancelLink = $('a.cancelLink'); + + $('#serverVariables').find('.var-name').find('a').append( + $('#docImage').clone().css('display', 'inline-block') + ); + + /* Launches the variable editor */ + $(document).on('click', 'a.editLink', function (event) { + event.preventDefault(); + editVariable(this); + }); + + /* Allows the user to edit a server variable */ + function editVariable (link) { + var $link = $(link); + var $cell = $link.parent(); + var $valueCell = $link.parents('.var-row').find('.var-value'); + var varName = $link.data('variable'); + + var $mySaveLink = $saveLink.clone().css('display', 'inline-block'); + var $myCancelLink = $cancelLink.clone().css('display', 'inline-block'); + var $msgbox = PMA_ajaxShowMessage(); + var $myEditLink = $cell.find('a.editLink'); + $cell.addClass('edit'); // variable is being edited + $myEditLink.remove(); // remove edit link + + $mySaveLink.click(function () { + var $msgbox = PMA_ajaxShowMessage(PMA_messages.strProcessingRequest); + $.post($(this).attr('href'), { + ajax_request: true, + type: 'setval', + varName: varName, + varValue: $valueCell.find('input').val() + }, function (data) { + if (data.success) { + $valueCell + .html(data.variable) + .data('content', data.variable); + PMA_ajaxRemoveMessage($msgbox); + } else { + if (data.error === '') { + PMA_ajaxShowMessage(PMA_messages.strRequestFailed, false); + } else { + PMA_ajaxShowMessage(data.error, false); + } + $valueCell.html($valueCell.data('content')); + } + $cell.removeClass('edit').html($myEditLink); + }); + return false; + }); + + $myCancelLink.click(function () { + $valueCell.html($valueCell.data('content')); + $cell.removeClass('edit').html($myEditLink); + return false; + }); + + $.get($mySaveLink.attr('href'), { + ajax_request: true, + type: 'getval', + varName: varName + }, function (data) { + if (typeof data !== 'undefined' && data.success === true) { + var $links = $('
      ') + .append($myCancelLink) + .append('   ') + .append($mySaveLink); + var $editor = $('
      ', { 'class': 'serverVariableEditor' }) + .append( + $('
      ').append( + $('', { type: 'text' }).val(data.message) + ) + ); + // Save and replace content + $cell + .html($links) + .children() + .css('display', 'flex'); + $valueCell + .data('content', $valueCell.html()) + .html($editor) + .find('input') + .focus() + .keydown(function (event) { // Keyboard shortcuts + if (event.keyCode === 13) { // Enter key + $mySaveLink.trigger('click'); + } else if (event.keyCode === 27) { // Escape key + $myCancelLink.trigger('click'); + } + }); + PMA_ajaxRemoveMessage($msgbox); + } else { + $cell.removeClass('edit').html($myEditLink); + PMA_ajaxShowMessage(data.error); + } + }); + } +}); diff --git a/php/apps/phpmyadmin49/html/js/shortcuts_handler.js b/php/apps/phpmyadmin49/html/js/shortcuts_handler.js new file mode 100644 index 00000000..4fc3c59a --- /dev/null +++ b/php/apps/phpmyadmin49/html/js/shortcuts_handler.js @@ -0,0 +1,101 @@ +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * @fileoverview Handle shortcuts in various pages + * @name Shortcuts handler + * + * @requires jQuery + * @requires jQueryUI + */ + +/** + * Register key events on load + */ +$(document).ready(function () { + var databaseOp = false; + var tableOp = false; + var keyD = 68; + var keyT = 84; + var keyK = 75; + var keyS = 83; + var keyF = 70; + var keyE = 69; + var keyH = 72; + var keyC = 67; + var keyBackSpace = 8; + $(document).on('keyup', function (e) { + if (e.target.nodeName === 'INPUT' || e.target.nodeName === 'TEXTAREA' || e.target.nodeName === 'SELECT') { + return; + } + + if (e.keyCode === keyD) { + setTimeout(function () { + databaseOp = false; + }, 2000); + } else if (e.keyCode === keyT) { + setTimeout(function () { + tableOp = false; + }, 2000); + } + }); + $(document).on('keydown', function (e) { + if (e.ctrlKey && e.altKey && e.keyCode === keyC) { + PMA_console.toggle(); + } + + if (e.ctrlKey && e.keyCode === keyK) { + e.preventDefault(); + PMA_console.toggle(); + } + + if (e.target.nodeName === 'INPUT' || e.target.nodeName === 'TEXTAREA' || e.target.nodeName === 'SELECT') { + return; + } + + var isTable; + var isDb; + if (e.keyCode === keyD) { + databaseOp = true; + } else if (e.keyCode === keyK) { + e.preventDefault(); + PMA_console.toggle(); + } else if (e.keyCode === keyS) { + if (databaseOp === true) { + isTable = PMA_commonParams.get('table'); + isDb = PMA_commonParams.get('db'); + if (isDb && ! isTable) { + $('.tab .ic_b_props').first().trigger('click'); + } + } else if (tableOp === true) { + isTable = PMA_commonParams.get('table'); + isDb = PMA_commonParams.get('db'); + if (isDb && isTable) { + $('.tab .ic_b_props').first().trigger('click'); + } + } else { + $('#pma_navigation_settings_icon').trigger('click'); + } + } else if (e.keyCode === keyF) { + if (databaseOp === true) { + isTable = PMA_commonParams.get('table'); + isDb = PMA_commonParams.get('db'); + if (isDb && ! isTable) { + $('.tab .ic_b_search').first().trigger('click'); + } + } else if (tableOp === true) { + isTable = PMA_commonParams.get('table'); + isDb = PMA_commonParams.get('db'); + if (isDb && isTable) { + $('.tab .ic_b_search').first().trigger('click'); + } + } + } else if (e.keyCode === keyT) { + tableOp = true; + } else if (e.keyCode === keyE) { + $('.ic_b_export').first().trigger('click'); + } else if (e.keyCode === keyBackSpace) { + window.history.back(); + } else if (e.keyCode === keyH) { + $('.ic_b_home').first().trigger('click'); + } + }); +}); diff --git a/php/apps/phpmyadmin49/html/js/sql.js b/php/apps/phpmyadmin49/html/js/sql.js new file mode 100644 index 00000000..d5e80ed4 --- /dev/null +++ b/php/apps/phpmyadmin49/html/js/sql.js @@ -0,0 +1,1023 @@ +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * @fileoverview functions used wherever an sql query form is used + * + * @requires jQuery + * @requires js/functions.js + * + */ + +var $data_a; +var prevScrollX = 0; + +/** + * decode a string URL_encoded + * + * @param string str + * @return string the URL-decoded string + */ +function PMA_urldecode (str) { + if (typeof str !== 'undefined') { + return decodeURIComponent(str.replace(/\+/g, '%20')); + } +} + +/** + * endecode a string URL_decoded + * + * @param string str + * @return string the URL-encoded string + */ +function PMA_urlencode (str) { + if (typeof str !== 'undefined') { + return encodeURIComponent(str).replace(/\%20/g, '+'); + } +} + +/** + * Saves SQL query in local storage or cookie + * + * @param string SQL query + * @return void + */ +function PMA_autosaveSQL (query) { + if (query) { + if (isStorageSupported('localStorage')) { + window.localStorage.auto_saved_sql = query; + } else { + Cookies.set('auto_saved_sql', query); + } + } +} + +/** + * Saves SQL query with sort in local storage or cookie + * + * @param string SQL query + * @return void + */ +function PMA_autosaveSQLSort (query) { + if (query) { + if (isStorageSupported('localStorage')) { + window.localStorage.auto_saved_sql_sort = query; + } else { + Cookies.set('auto_saved_sql_sort', query); + } + } +} + +/** + * Get the field name for the current field. Required to construct the query + * for grid editing + * + * @param $table_results enclosing results table + * @param $this_field jQuery object that points to the current field's tr + */ +function getFieldName ($table_results, $this_field) { + var this_field_index = $this_field.index(); + // ltr or rtl direction does not impact how the DOM was generated + // check if the action column in the left exist + var left_action_exist = !$table_results.find('th:first').hasClass('draggable'); + // number of column span for checkbox and Actions + var left_action_skip = left_action_exist ? $table_results.find('th:first').attr('colspan') - 1 : 0; + + // If this column was sorted, the text of the a element contains something + // like 1 that is useful to indicate the order in case + // of a sort on multiple columns; however, we dont want this as part + // of the column name so we strip it ( .clone() to .end() ) + var field_name = $table_results + .find('thead') + .find('th:eq(' + (this_field_index - left_action_skip) + ') a') + .clone() // clone the element + .children() // select all the children + .remove() // remove all of them + .end() // go back to the selected element + .text(); // grab the text + // happens when just one row (headings contain no a) + if (field_name === '') { + var $heading = $table_results.find('thead').find('th:eq(' + (this_field_index - left_action_skip) + ')').children('span'); + // may contain column comment enclosed in a span - detach it temporarily to read the column name + var $tempColComment = $heading.children().detach(); + field_name = $heading.text(); + // re-attach the column comment + $heading.append($tempColComment); + } + + field_name = $.trim(field_name); + + return field_name; +} + +/** + * Unbind all event handlers before tearing down a page + */ +AJAX.registerTeardown('sql.js', function () { + $(document).off('click', 'a.delete_row.ajax'); + $(document).off('submit', '.bookmarkQueryForm'); + $('input#bkm_label').off('input'); + $(document).off('makegrid', '.sqlqueryresults'); + $(document).off('stickycolumns', '.sqlqueryresults'); + $('#togglequerybox').off('click'); + $(document).off('click', '#button_submit_query'); + $(document).off('change', '#id_bookmark'); + $('input[name=\'bookmark_variable\']').off('keypress'); + $(document).off('submit', '#sqlqueryform.ajax'); + $(document).off('click', 'input[name=navig].ajax'); + $(document).off('submit', 'form[name=\'displayOptionsForm\'].ajax'); + $(document).off('mouseenter', 'th.column_heading.pointer'); + $(document).off('mouseleave', 'th.column_heading.pointer'); + $(document).off('click', 'th.column_heading.marker'); + $(window).off('scroll'); + $(document).off('keyup', '.filter_rows'); + $(document).off('click', '#printView'); + if (codemirror_editor) { + codemirror_editor.off('change'); + } else { + $('#sqlquery').off('input propertychange'); + } + $('body').off('click', '.navigation .showAllRows'); + $('body').off('click', 'a.browse_foreign'); + $('body').off('click', '#simulate_dml'); + $('body').off('keyup', '#sqlqueryform'); + $('body').off('click', 'form[name="resultsForm"].ajax button[name="submit_mult"], form[name="resultsForm"].ajax input[name="submit_mult"]'); +}); + +/** + * @description

      Ajax scripts for sql and browse pages

      + * + * Actions ajaxified here: + *
        + *
      • Retrieve results of an SQL query
      • + *
      • Paginate the results table
      • + *
      • Sort the results table
      • + *
      • Change table according to display options
      • + *
      • Grid editing of data
      • + *
      • Saving a bookmark
      • + *
      + * + * @name document.ready + * @memberOf jQuery + */ +AJAX.registerOnload('sql.js', function () { + $(function () { + if (codemirror_editor) { + codemirror_editor.on('change', function () { + PMA_autosaveSQL(codemirror_editor.getValue()); + }); + } else { + $('#sqlquery').on('input propertychange', function () { + PMA_autosaveSQL($('#sqlquery').val()); + }); + // Save sql query with sort + if ($('#RememberSorting') !== undefined && $('#RememberSorting').is(':checked')) { + $('select[name="sql_query"]').on('change', function () { + PMA_autosaveSQLSort($('select[name="sql_query"]').val()); + }); + } else { + if (isStorageSupported('localStorage') && window.localStorage.auto_saved_sql_sort !== undefined) { + window.localStorage.removeItem('auto_saved_sql_sort'); + } else { + Cookies.set('auto_saved_sql_sort', ''); + } + } + // If sql query with sort for current table is stored, change sort by key select value + var sortStoredQuery = (isStorageSupported('localStorage') && typeof window.localStorage.auto_saved_sql_sort !== 'undefined') ? window.localStorage.auto_saved_sql_sort : Cookies.get('auto_saved_sql_sort'); + if (typeof sortStoredQuery !== 'undefined' && sortStoredQuery !== $('select[name="sql_query"]').val() && $('select[name="sql_query"] option[value="' + sortStoredQuery + '"]').length !== 0) { + $('select[name="sql_query"]').val(sortStoredQuery).change(); + } + } + }); + + // Delete row from SQL results + $(document).on('click', 'a.delete_row.ajax', function (e) { + e.preventDefault(); + var question = PMA_sprintf(PMA_messages.strDoYouReally, escapeHtml($(this).closest('td').find('div').text())); + var $link = $(this); + $link.PMA_confirm(question, $link.attr('href'), function (url) { + $msgbox = PMA_ajaxShowMessage(); + var argsep = PMA_commonParams.get('arg_separator'); + var params = 'ajax_request=1' + argsep + 'is_js_confirmed=1'; + var postData = $link.getPostData(); + if (postData) { + params += argsep + postData; + } + $.post(url, params, function (data) { + if (data.success) { + PMA_ajaxShowMessage(data.message); + $link.closest('tr').remove(); + } else { + PMA_ajaxShowMessage(data.error, false); + } + }); + }); + }); + + // Ajaxification for 'Bookmark this SQL query' + $(document).on('submit', '.bookmarkQueryForm', function (e) { + e.preventDefault(); + PMA_ajaxShowMessage(); + var argsep = PMA_commonParams.get('arg_separator'); + $.post($(this).attr('action'), 'ajax_request=1' + argsep + $(this).serialize(), function (data) { + if (data.success) { + PMA_ajaxShowMessage(data.message); + } else { + PMA_ajaxShowMessage(data.error, false); + } + }); + }); + + /* Hides the bookmarkoptions checkboxes when the bookmark label is empty */ + $('input#bkm_label').on('input', function () { + $('input#id_bkm_all_users, input#id_bkm_replace') + .parent() + .toggle($(this).val().length > 0); + }).trigger('input'); + + /** + * Attach Event Handler for 'Copy to clipbpard + */ + $(document).on('click', '#copyToClipBoard', function (event) { + event.preventDefault(); + + var textArea = document.createElement('textarea'); + + // + // *** This styling is an extra step which is likely not required. *** + // + // Why is it here? To ensure: + // 1. the element is able to have focus and selection. + // 2. if element was to flash render it has minimal visual impact. + // 3. less flakyness with selection and copying which **might** occur if + // the textarea element is not visible. + // + // The likelihood is the element won't even render, not even a flash, + // so some of these are just precautions. However in IE the element + // is visible whilst the popup box asking the user for permission for + // the web page to copy to the clipboard. + // + + // Place in top-left corner of screen regardless of scroll position. + textArea.style.position = 'fixed'; + textArea.style.top = 0; + textArea.style.left = 0; + + // Ensure it has a small width and height. Setting to 1px / 1em + // doesn't work as this gives a negative w/h on some browsers. + textArea.style.width = '2em'; + textArea.style.height = '2em'; + + // We don't need padding, reducing the size if it does flash render. + textArea.style.padding = 0; + + // Clean up any borders. + textArea.style.border = 'none'; + textArea.style.outline = 'none'; + textArea.style.boxShadow = 'none'; + + // Avoid flash of white box if rendered for any reason. + textArea.style.background = 'transparent'; + + textArea.value = ''; + + $('#serverinfo a').each(function () { + textArea.value += $(this).text().split(':')[1].trim() + '/'; + }); + textArea.value += '\t\t' + window.location.href; + textArea.value += '\n'; + $('.success').each(function () { + textArea.value += $(this).text() + '\n\n'; + }); + + $('.sql pre').each(function () { + textArea.value += $(this).text() + '\n\n'; + }); + + $('.table_results .column_heading a').each(function () { + // Don't copy ordering number text within tag + textArea.value += $(this).clone().find('small').remove().end().text() + '\t'; + }); + + textArea.value += '\n'; + $('.table_results tbody tr').each(function () { + $(this).find('.data span').each(function () { + textArea.value += $(this).text() + '\t'; + }); + textArea.value += '\n'; + }); + + document.body.appendChild(textArea); + + textArea.select(); + + try { + document.execCommand('copy'); + } catch (err) { + alert('Sorry! Unable to copy'); + } + + document.body.removeChild(textArea); + }); // end of Copy to Clipboard action + + /** + * Attach Event Handler for 'Print' link + */ + $(document).on('click', '#printView', function (event) { + event.preventDefault(); + + // Take to preview mode + printPreview(); + }); // end of 'Print' action + + /** + * Attach the {@link makegrid} function to a custom event, which will be + * triggered manually everytime the table of results is reloaded + * @memberOf jQuery + */ + $(document).on('makegrid', '.sqlqueryresults', function () { + $('.table_results').each(function () { + PMA_makegrid(this); + }); + }); + + /* + * Attach a custom event for sticky column headings which will be + * triggered manually everytime the table of results is reloaded + * @memberOf jQuery + */ + $(document).on('stickycolumns', '.sqlqueryresults', function () { + $('.sticky_columns').remove(); + $('.table_results').each(function () { + var $table_results = $(this); + // add sticky columns div + var $stick_columns = initStickyColumns($table_results); + rearrangeStickyColumns($stick_columns, $table_results); + // adjust sticky columns on scroll + $(window).on('scroll', function () { + handleStickyColumns($stick_columns, $table_results); + }); + }); + }); + + /** + * Append the "Show/Hide query box" message to the query input form + * + * @memberOf jQuery + * @name appendToggleSpan + */ + // do not add this link more than once + if (! $('#sqlqueryform').find('a').is('#togglequerybox')) { + $('') + .html(PMA_messages.strHideQueryBox) + .appendTo('#sqlqueryform') + // initially hidden because at this point, nothing else + // appears under the link + .hide(); + + // Attach the toggling of the query box visibility to a click + $('#togglequerybox').bind('click', function () { + var $link = $(this); + $link.siblings().slideToggle('fast'); + if ($link.text() === PMA_messages.strHideQueryBox) { + $link.text(PMA_messages.strShowQueryBox); + // cheap trick to add a spacer between the menu tabs + // and "Show query box"; feel free to improve! + $('#togglequerybox_spacer').remove(); + $link.before('
      '); + } else { + $link.text(PMA_messages.strHideQueryBox); + } + // avoid default click action + return false; + }); + } + + + /** + * Event handler for sqlqueryform.ajax button_submit_query + * + * @memberOf jQuery + */ + $(document).on('click', '#button_submit_query', function (event) { + $('.success,.error').hide(); + // hide already existing error or success message + var $form = $(this).closest('form'); + // the Go button related to query submission was clicked, + // instead of the one related to Bookmarks, so empty the + // id_bookmark selector to avoid misinterpretation in + // import.php about what needs to be done + $form.find('select[name=id_bookmark]').val(''); + // let normal event propagation happen + }); + + /** + * Event handler to show appropiate number of variable boxes + * based on the bookmarked query + */ + $(document).on('change', '#id_bookmark', function (event) { + var varCount = $(this).find('option:selected').data('varcount'); + if (typeof varCount === 'undefined') { + varCount = 0; + } + + var $varDiv = $('#bookmark_variables'); + $varDiv.empty(); + for (var i = 1; i <= varCount; i++) { + $varDiv.append($('')); + $varDiv.append($('')); + } + + if (varCount === 0) { + $varDiv.parent('.formelement').hide(); + } else { + $varDiv.parent('.formelement').show(); + } + }); + + /** + * Event handler for hitting enter on sqlqueryform bookmark_variable + * (the Variable textfield in Bookmarked SQL query section) + * + * @memberOf jQuery + */ + $('input[name=bookmark_variable]').on('keypress', function (event) { + // force the 'Enter Key' to implicitly click the #button_submit_bookmark + var keycode = (event.keyCode ? event.keyCode : (event.which ? event.which : event.charCode)); + if (keycode === 13) { // keycode for enter key + // When you press enter in the sqlqueryform, which + // has 2 submit buttons, the default is to run the + // #button_submit_query, because of the tabindex + // attribute. + // This submits #button_submit_bookmark instead, + // because when you are in the Bookmarked SQL query + // section and hit enter, you expect it to do the + // same action as the Go button in that section. + $('#button_submit_bookmark').click(); + return false; + } else { + return true; + } + }); + + /** + * Ajax Event handler for 'SQL Query Submit' + * + * @see PMA_ajaxShowMessage() + * @memberOf jQuery + * @name sqlqueryform_submit + */ + $(document).on('submit', '#sqlqueryform.ajax', function (event) { + event.preventDefault(); + + var $form = $(this); + if (codemirror_editor) { + $form[0].elements.sql_query.value = codemirror_editor.getValue(); + } + if (! checkSqlQuery($form[0])) { + return false; + } + + // remove any div containing a previous error message + $('div.error').remove(); + + var $msgbox = PMA_ajaxShowMessage(); + var $sqlqueryresultsouter = $('#sqlqueryresultsouter'); + + PMA_prepareForAjaxRequest($form); + + var argsep = PMA_commonParams.get('arg_separator'); + $.post($form.attr('action'), $form.serialize() + argsep + 'ajax_page_request=true', function (data) { + if (typeof data !== 'undefined' && data.success === true) { + // success happens if the query returns rows or not + + // show a message that stays on screen + if (typeof data.action_bookmark !== 'undefined') { + // view only + if ('1' === data.action_bookmark) { + $('#sqlquery').text(data.sql_query); + // send to codemirror if possible + setQuery(data.sql_query); + } + // delete + if ('2' === data.action_bookmark) { + $('#id_bookmark option[value=\'' + data.id_bookmark + '\']').remove(); + // if there are no bookmarked queries now (only the empty option), + // remove the bookmark section + if ($('#id_bookmark option').length === 1) { + $('#fieldsetBookmarkOptions').hide(); + $('#fieldsetBookmarkOptionsFooter').hide(); + } + } + } + $sqlqueryresultsouter + .show() + .html(data.message); + PMA_highlightSQL($sqlqueryresultsouter); + + if (data._menu) { + if (history && history.pushState) { + history.replaceState({ + menu : data._menu + }, + null + ); + AJAX.handleMenu.replace(data._menu); + } else { + PMA_MicroHistory.menus.replace(data._menu); + PMA_MicroHistory.menus.add(data._menuHash, data._menu); + } + } else if (data._menuHash) { + if (! (history && history.pushState)) { + PMA_MicroHistory.menus.replace(PMA_MicroHistory.menus.get(data._menuHash)); + } + } + + if (data._params) { + PMA_commonParams.setAll(data._params); + } + + if (typeof data.ajax_reload !== 'undefined') { + if (data.ajax_reload.reload) { + if (data.ajax_reload.table_name) { + PMA_commonParams.set('table', data.ajax_reload.table_name); + PMA_commonActions.refreshMain(); + } else { + PMA_reloadNavigation(); + } + } + } else if (typeof data.reload !== 'undefined') { + // this happens if a USE or DROP command was typed + PMA_commonActions.setDb(data.db); + var url; + if (data.db) { + if (data.table) { + url = 'table_sql.php'; + } else { + url = 'db_sql.php'; + } + } else { + url = 'server_sql.php'; + } + PMA_commonActions.refreshMain(url, function () { + $('#sqlqueryresultsouter') + .show() + .html(data.message); + PMA_highlightSQL($('#sqlqueryresultsouter')); + }); + } + + $('.sqlqueryresults').trigger('makegrid').trigger('stickycolumns'); + $('#togglequerybox').show(); + PMA_init_slider(); + + if (typeof data.action_bookmark === 'undefined') { + if ($('#sqlqueryform input[name="retain_query_box"]').is(':checked') !== true) { + if ($('#togglequerybox').siblings(':visible').length > 0) { + $('#togglequerybox').trigger('click'); + } + } + } + } else if (typeof data !== 'undefined' && data.success === false) { + // show an error message that stays on screen + $sqlqueryresultsouter + .show() + .html(data.error); + } + PMA_ajaxRemoveMessage($msgbox); + }); // end $.post() + }); // end SQL Query submit + + /** + * Ajax Event handler for the display options + * @memberOf jQuery + * @name displayOptionsForm_submit + */ + $(document).on('submit', 'form[name=\'displayOptionsForm\'].ajax', function (event) { + event.preventDefault(); + + $form = $(this); + + var $msgbox = PMA_ajaxShowMessage(); + var argsep = PMA_commonParams.get('arg_separator'); + $.post($form.attr('action'), $form.serialize() + argsep + 'ajax_request=true', function (data) { + PMA_ajaxRemoveMessage($msgbox); + var $sqlqueryresults = $form.parents('.sqlqueryresults'); + $sqlqueryresults + .html(data.message) + .trigger('makegrid') + .trigger('stickycolumns'); + PMA_init_slider(); + PMA_highlightSQL($sqlqueryresults); + }); // end $.post() + }); // end displayOptionsForm handler + + // Filter row handling. --STARTS-- + $(document).on('keyup', '.filter_rows', function () { + var unique_id = $(this).data('for'); + var $target_table = $('.table_results[data-uniqueId=\'' + unique_id + '\']'); + var $header_cells = $target_table.find('th[data-column]'); + var target_columns = Array(); + // To handle colspan=4, in case of edit,copy etc options. + var dummy_th = ($('.edit_row_anchor').length !== 0 ? + '' + : ''); + // Selecting columns that will be considered for filtering and searching. + $header_cells.each(function () { + target_columns.push($.trim($(this).text())); + }); + + var phrase = $(this).val(); + // Set same value to both Filter rows fields. + $('.filter_rows[data-for=\'' + unique_id + '\']').not(this).val(phrase); + // Handle colspan. + $target_table.find('thead > tr').prepend(dummy_th); + $.uiTableFilter($target_table, phrase, target_columns); + $target_table.find('th.dummy_th').remove(); + }); + // Filter row handling. --ENDS-- + + // Prompt to confirm on Show All + $('body').on('click', '.navigation .showAllRows', function (e) { + e.preventDefault(); + var $form = $(this).parents('form'); + + if (! $(this).is(':checked')) { // already showing all rows + submitShowAllForm(); + } else { + $form.PMA_confirm(PMA_messages.strShowAllRowsWarning, $form.attr('action'), function (url) { + submitShowAllForm(); + }); + } + + function submitShowAllForm () { + var argsep = PMA_commonParams.get('arg_separator'); + var submitData = $form.serialize() + argsep + 'ajax_request=true' + argsep + 'ajax_page_request=true'; + PMA_ajaxShowMessage(); + AJAX.source = $form; + $.post($form.attr('action'), submitData, AJAX.responseHandler); + } + }); + + $('body').on('keyup', '#sqlqueryform', function () { + PMA_handleSimulateQueryButton(); + }); + + /** + * Ajax event handler for 'Simulate DML'. + */ + $('body').on('click', '#simulate_dml', function () { + var $form = $('#sqlqueryform'); + var query = ''; + var delimiter = $('#id_sql_delimiter').val(); + var db_name = $form.find('input[name="db"]').val(); + + if (codemirror_editor) { + query = codemirror_editor.getValue(); + } else { + query = $('#sqlquery').val(); + } + + if (query.length === 0) { + alert(PMA_messages.strFormEmpty); + $('#sqlquery').focus(); + return false; + } + + var $msgbox = PMA_ajaxShowMessage(); + $.ajax({ + type: 'POST', + url: $form.attr('action'), + data: { + server: PMA_commonParams.get('server'), + db: db_name, + ajax_request: '1', + simulate_dml: '1', + sql_query: query, + sql_delimiter: delimiter + }, + success: function (response) { + PMA_ajaxRemoveMessage($msgbox); + if (response.success) { + var dialog_content = '
      '; + if (response.sql_data) { + var len = response.sql_data.length; + for (var i = 0; i < len; i++) { + dialog_content += '' + PMA_messages.strSQLQuery + + '' + response.sql_data[i].sql_query + + PMA_messages.strMatchedRows + + ' ' + response.sql_data[i].matched_rows + '
      '; + if (i < len - 1) { + dialog_content += '
      '; + } + } + } else { + dialog_content += response.message; + } + dialog_content += '
      '; + var $dialog_content = $(dialog_content); + var button_options = {}; + button_options[PMA_messages.strClose] = function () { + $(this).dialog('close'); + }; + var $response_dialog = $('
      ').append($dialog_content).dialog({ + minWidth: 540, + maxHeight: 400, + modal: true, + buttons: button_options, + title: PMA_messages.strSimulateDML, + open: function () { + PMA_highlightSQL($(this)); + }, + close: function () { + $(this).remove(); + } + }); + } else { + PMA_ajaxShowMessage(response.error); + } + }, + error: function (response) { + PMA_ajaxShowMessage(PMA_messages.strErrorProcessingRequest); + } + }); + }); + + /** + * Handles multi submits of results browsing page such as edit, delete and export + */ + $('body').on('click', 'form[name="resultsForm"].ajax button[name="submit_mult"], form[name="resultsForm"].ajax input[name="submit_mult"]', function (e) { + e.preventDefault(); + var $button = $(this); + var $form = $button.closest('form'); + var argsep = PMA_commonParams.get('arg_separator'); + var submitData = $form.serialize() + argsep + 'ajax_request=true' + argsep + 'ajax_page_request=true' + argsep + 'submit_mult=' + $button.val(); + PMA_ajaxShowMessage(); + AJAX.source = $form; + $.post($form.attr('action'), submitData, AJAX.responseHandler); + }); +}); // end $() + +/** + * Starting from some th, change the class of all td under it. + * If isAddClass is specified, it will be used to determine whether to add or remove the class. + */ +function PMA_changeClassForColumn ($this_th, newclass, isAddClass) { + // index 0 is the th containing the big T + var th_index = $this_th.index(); + var has_big_t = $this_th.closest('tr').children(':first').hasClass('column_action'); + // .eq() is zero-based + if (has_big_t) { + th_index--; + } + var $table = $this_th.parents('.table_results'); + if (! $table.length) { + $table = $this_th.parents('table').siblings('.table_results'); + } + var $tds = $table.find('tbody tr').find('td.data:eq(' + th_index + ')'); + if (isAddClass === undefined) { + $tds.toggleClass(newclass); + } else { + $tds.toggleClass(newclass, isAddClass); + } +} + +/** + * Handles browse foreign values modal dialog + * + * @param object $this_a reference to the browse foreign value link + */ +function browseForeignDialog ($this_a) { + var formId = '#browse_foreign_form'; + var showAllId = '#foreign_showAll'; + var tableId = '#browse_foreign_table'; + var filterId = '#input_foreign_filter'; + var $dialog = null; + var argSep = PMA_commonParams.get('arg_separator'); + var params = $this_a.getPostData(); + params += argSep + 'ajax_request=true'; + $.post($this_a.attr('href'), params, function (data) { + // Creates browse foreign value dialog + $dialog = $('
      ').append(data.message).dialog({ + title: PMA_messages.strBrowseForeignValues, + width: Math.min($(window).width() - 100, 700), + maxHeight: $(window).height() - 100, + dialogClass: 'browse_foreign_modal', + close: function (ev, ui) { + // remove event handlers attached to elements related to dialog + $(tableId).off('click', 'td a.foreign_value'); + $(formId).off('click', showAllId); + $(formId).off('submit'); + // remove dialog itself + $(this).remove(); + }, + modal: true + }); + }).done(function () { + var showAll = false; + $(tableId).on('click', 'td a.foreign_value', function (e) { + e.preventDefault(); + var $input = $this_a.prev('input[type=text]'); + // Check if input exists or get CEdit edit_box + if ($input.length === 0) { + $input = $this_a.closest('.edit_area').prev('.edit_box'); + } + // Set selected value as input value + $input.val($(this).data('key')); + $dialog.dialog('close'); + }); + $(formId).on('click', showAllId, function () { + showAll = true; + }); + $(formId).on('submit', function (e) { + e.preventDefault(); + // if filter value is not equal to old value + // then reset page number to 1 + if ($(filterId).val() !== $(filterId).data('old')) { + $(formId).find('select[name=pos]').val('0'); + } + var postParams = $(this).serializeArray(); + // if showAll button was clicked to submit form then + // add showAll button parameter to form + if (showAll) { + postParams.push({ + name: $(showAllId).attr('name'), + value: $(showAllId).val() + }); + } + // updates values in dialog + $.post($(this).attr('action') + '?ajax_request=1', postParams, function (data) { + var $obj = $('
      ').html(data.message); + $(formId).html($obj.find(formId).html()); + $(tableId).html($obj.find(tableId).html()); + }); + showAll = false; + }); + }); +} + +AJAX.registerOnload('sql.js', function () { + $('body').on('click', 'a.browse_foreign', function (e) { + e.preventDefault(); + browseForeignDialog($(this)); + }); + + /** + * vertical column highlighting in horizontal mode when hovering over the column header + */ + $(document).on('mouseenter', 'th.column_heading.pointer', function (e) { + PMA_changeClassForColumn($(this), 'hover', true); + }); + $(document).on('mouseleave', 'th.column_heading.pointer', function (e) { + PMA_changeClassForColumn($(this), 'hover', false); + }); + + /** + * vertical column marking in horizontal mode when clicking the column header + */ + $(document).on('click', 'th.column_heading.marker', function () { + PMA_changeClassForColumn($(this), 'marked'); + }); + + /** + * create resizable table + */ + $('.sqlqueryresults').trigger('makegrid').trigger('stickycolumns'); +}); + +/* + * Profiling Chart + */ +function makeProfilingChart () { + if ($('#profilingchart').length === 0 || + $('#profilingchart').html().length !== 0 || + !$.jqplot || !$.jqplot.Highlighter || !$.jqplot.PieRenderer + ) { + return; + } + + var data = []; + $.each(JSON.parse($('#profilingChartData').html()), function (key, value) { + data.push([key, parseFloat(value)]); + }); + + // Remove chart and data divs contents + $('#profilingchart').html('').show(); + $('#profilingChartData').html(''); + + PMA_createProfilingChart('profilingchart', data); +} + +/* + * initialize profiling data tables + */ +function initProfilingTables () { + if (!$.tablesorter) { + return; + } + + $('#profiletable').tablesorter({ + widgets: ['zebra'], + sortList: [[0, 0]], + textExtraction: function (node) { + if (node.children.length > 0) { + return node.children[0].innerHTML; + } else { + return node.innerHTML; + } + } + }); + + $('#profilesummarytable').tablesorter({ + widgets: ['zebra'], + sortList: [[1, 1]], + textExtraction: function (node) { + if (node.children.length > 0) { + return node.children[0].innerHTML; + } else { + return node.innerHTML; + } + } + }); +} + +/* + * Set position, left, top, width of sticky_columns div + */ +function setStickyColumnsPosition ($sticky_columns, $table_results, position, top, left, margin_left) { + $sticky_columns + .css('position', position) + .css('top', top) + .css('left', left ? left : 'auto') + .css('margin-left', margin_left ? margin_left : '0px') + .css('width', $table_results.width()); +} + +/* + * Initialize sticky columns + */ +function initStickyColumns ($table_results) { + return $('
      ') + .insertBefore($table_results) + .css('position', 'fixed') + .css('z-index', '98') + .css('width', $table_results.width()) + .css('margin-left', $('#page_content').css('margin-left')) + .css('top', $('#floating_menubar').height()) + .css('display', 'none'); +} + +/* + * Arrange/Rearrange columns in sticky header + */ +function rearrangeStickyColumns ($sticky_columns, $table_results) { + var $originalHeader = $table_results.find('thead'); + var $originalColumns = $originalHeader.find('tr:first').children(); + var $clonedHeader = $originalHeader.clone(); + // clone width per cell + $clonedHeader.find('tr:first').children().width(function (i,val) { + var width = $originalColumns.eq(i).width(); + var is_firefox = navigator.userAgent.indexOf('Firefox') > -1; + if (! is_firefox) { + width += 1; + } + return width; + }); + $sticky_columns.empty().append($clonedHeader); +} + +/* + * Adjust sticky columns on horizontal/vertical scroll for all tables + */ +function handleAllStickyColumns () { + $('.sticky_columns').each(function () { + handleStickyColumns($(this), $(this).next('.table_results')); + }); +} + +/* + * Adjust sticky columns on horizontal/vertical scroll + */ +function handleStickyColumns ($sticky_columns, $table_results) { + var currentScrollX = $(window).scrollLeft(); + var windowOffset = $(window).scrollTop(); + var tableStartOffset = $table_results.offset().top; + var tableEndOffset = tableStartOffset + $table_results.height(); + if (windowOffset >= tableStartOffset && windowOffset <= tableEndOffset) { + // for horizontal scrolling + if (prevScrollX !== currentScrollX) { + prevScrollX = currentScrollX; + setStickyColumnsPosition($sticky_columns, $table_results, 'absolute', $('#floating_menubar').height() + windowOffset - tableStartOffset); + // for vertical scrolling + } else { + setStickyColumnsPosition($sticky_columns, $table_results, 'fixed', $('#floating_menubar').height(), $('#pma_navigation').width() - currentScrollX, $('#page_content').css('margin-left')); + } + $sticky_columns.show(); + } else { + $sticky_columns.hide(); + } +} + +AJAX.registerOnload('sql.js', function () { + makeProfilingChart(); + initProfilingTables(); +}); diff --git a/php/apps/phpmyadmin49/html/js/tbl_change.js b/php/apps/phpmyadmin49/html/js/tbl_change.js new file mode 100644 index 00000000..18742fd1 --- /dev/null +++ b/php/apps/phpmyadmin49/html/js/tbl_change.js @@ -0,0 +1,720 @@ +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * @fileoverview function used in table data manipulation pages + * + * @requires jQuery + * @requires jQueryUI + * @requires js/functions.js + * + */ + +/** + * Modify form controls when the "NULL" checkbox is checked + * + * @param theType string the MySQL field type + * @param urlField string the urlencoded field name - OBSOLETE + * @param md5Field string the md5 hashed field name + * @param multi_edit string the multi_edit row sequence number + * + * @return boolean always true + */ +function nullify (theType, urlField, md5Field, multi_edit) { + var rowForm = document.forms.insertForm; + + if (typeof(rowForm.elements['funcs' + multi_edit + '[' + md5Field + ']']) !== 'undefined') { + rowForm.elements['funcs' + multi_edit + '[' + md5Field + ']'].selectedIndex = -1; + } + + // "ENUM" field with more than 20 characters + if (Number(theType) === 1) { + rowForm.elements['fields' + multi_edit + '[' + md5Field + ']'][1].selectedIndex = -1; + // Other "ENUM" field + } else if (Number(theType) === 2) { + var elts = rowForm.elements['fields' + multi_edit + '[' + md5Field + ']']; + // when there is just one option in ENUM: + if (elts.checked) { + elts.checked = false; + } else { + var elts_cnt = elts.length; + for (var i = 0; i < elts_cnt; i++) { + elts[i].checked = false; + } // end for + } // end if + // "SET" field + } else if (Number(theType) === 3) { + rowForm.elements['fields' + multi_edit + '[' + md5Field + '][]'].selectedIndex = -1; + // Foreign key field (drop-down) + } else if (Number(theType) === 4) { + rowForm.elements['fields' + multi_edit + '[' + md5Field + ']'].selectedIndex = -1; + // foreign key field (with browsing icon for foreign values) + } else if (Number(theType) === 6) { + rowForm.elements['fields' + multi_edit + '[' + md5Field + ']'].value = ''; + // Other field types + } else /* if (theType === 5)*/ { + rowForm.elements['fields' + multi_edit + '[' + md5Field + ']'].value = ''; + } // end if... else if... else + + return true; +} // end of the 'nullify()' function + + +/** + * javascript DateTime format validation. + * its used to prevent adding default (0000-00-00 00:00:00) to database when user enter wrong values + * Start of validation part + */ +// function checks the number of days in febuary +function daysInFebruary (year) { + return (((year % 4 === 0) && (((year % 100 !== 0)) || (year % 400 === 0))) ? 29 : 28); +} +// function to convert single digit to double digit +function fractionReplace (num) { + num = parseInt(num, 10); + return num >= 1 && num <= 9 ? '0' + num : '00'; +} + +/* function to check the validity of date +* The following patterns are accepted in this validation (accepted in mysql as well) +* 1) 2001-12-23 +* 2) 2001-1-2 +* 3) 02-12-23 +* 4) And instead of using '-' the following punctuations can be used (+,.,*,^,@,/) All these are accepted by mysql as well. Therefore no issues +*/ +function isDate (val, tmstmp) { + val = val.replace(/[.|*|^|+|//|@]/g, '-'); + var arrayVal = val.split('-'); + for (var a = 0; a < arrayVal.length; a++) { + if (arrayVal[a].length === 1) { + arrayVal[a] = fractionReplace(arrayVal[a]); + } + } + val = arrayVal.join('-'); + var pos = 2; + var dtexp = new RegExp(/^([0-9]{4})-(((01|03|05|07|08|10|12)-((0[0-9])|([1-2][0-9])|(3[0-1])))|((02|04|06|09|11)-((0[0-9])|([1-2][0-9])|30))|((00)-(00)))$/); + if (val.length === 8) { + pos = 0; + } + if (dtexp.test(val)) { + var month = parseInt(val.substring(pos + 3, pos + 5), 10); + var day = parseInt(val.substring(pos + 6, pos + 8), 10); + var year = parseInt(val.substring(0, pos + 2), 10); + if (month === 2 && day > daysInFebruary(year)) { + return false; + } + if (val.substring(0, pos + 2).length === 2) { + year = parseInt('20' + val.substring(0, pos + 2), 10); + } + if (tmstmp === true) { + if (year < 1978) { + return false; + } + if (year > 2038 || (year > 2037 && day > 19 && month >= 1) || (year > 2037 && month > 1)) { + return false; + } + } + } else { + return false; + } + return true; +} + +/* function to check the validity of time +* The following patterns are accepted in this validation (accepted in mysql as well) +* 1) 2:3:4 +* 2) 2:23:43 +* 3) 2:23:43.123456 +*/ +function isTime (val) { + var arrayVal = val.split(':'); + for (var a = 0, l = arrayVal.length; a < l; a++) { + if (arrayVal[a].length === 1) { + arrayVal[a] = fractionReplace(arrayVal[a]); + } + } + val = arrayVal.join(':'); + var tmexp = new RegExp(/^(-)?(([0-7]?[0-9][0-9])|(8[0-2][0-9])|(83[0-8])):((0[0-9])|([1-5][0-9])):((0[0-9])|([1-5][0-9]))(\.[0-9]{1,6}){0,1}$/); + return tmexp.test(val); +} + +/** + * To check whether insert section is ignored or not + */ +function checkForCheckbox (multi_edit) { + if ($('#insert_ignore_' + multi_edit).length) { + return $('#insert_ignore_' + multi_edit).is(':unchecked'); + } + return true; +} + +function verificationsAfterFieldChange (urlField, multi_edit, theType) { + var evt = window.event || arguments.callee.caller.arguments[0]; + var target = evt.target || evt.srcElement; + var $this_input = $(':input[name^=\'fields[multi_edit][' + multi_edit + '][' + + urlField + ']\']'); + // the function drop-down that corresponds to this input field + var $this_function = $('select[name=\'funcs[multi_edit][' + multi_edit + '][' + + urlField + ']\']'); + var function_selected = false; + if (typeof $this_function.val() !== 'undefined' && + $this_function.val() !== null && + $this_function.val().length > 0 + ) { + function_selected = true; + } + + // To generate the textbox that can take the salt + var new_salt_box = '
      '; + + // If encrypting or decrypting functions that take salt as input is selected append the new textbox for salt + if (target.value === 'AES_ENCRYPT' || + target.value === 'AES_DECRYPT' || + target.value === 'DES_ENCRYPT' || + target.value === 'DES_DECRYPT' || + target.value === 'ENCRYPT') { + if (!($('#salt_' + target.id).length)) { + $this_input.after(new_salt_box); + } + } else { + // Remove the textbox for salt + $('#salt_' + target.id).prev('br').remove(); + $('#salt_' + target.id).remove(); + } + + if (target.value === 'AES_DECRYPT' + || target.value === 'AES_ENCRYPT' + || target.value === 'MD5') { + $('#' + target.id).rules('add', { + validationFunctionForFuns: { + param: $this_input, + depends: function () { + return checkForCheckbox(multi_edit); + } + } + }); + } + + // Unchecks the corresponding "NULL" control + $('input[name=\'fields_null[multi_edit][' + multi_edit + '][' + urlField + ']\']').prop('checked', false); + + // Unchecks the Ignore checkbox for the current row + $('input[name=\'insert_ignore_' + multi_edit + '\']').prop('checked', false); + + var charExceptionHandling; + if (theType.substring(0,4) === 'char') { + charExceptionHandling = theType.substring(5,6); + } else if (theType.substring(0,7) === 'varchar') { + charExceptionHandling = theType.substring(8,9); + } + if (function_selected) { + $this_input.removeAttr('min'); + $this_input.removeAttr('max'); + // @todo: put back attributes if corresponding function is deselected + } + + if ($this_input.data('rulesadded') === null && ! function_selected) { + // call validate before adding rules + $($this_input[0].form).validate(); + // validate for date time + if (theType === 'datetime' || theType === 'time' || theType === 'date' || theType === 'timestamp') { + $this_input.rules('add', { + validationFunctionForDateTime: { + param: theType, + depends: function () { + return checkForCheckbox(multi_edit); + } + } + }); + } + // validation for integer type + if ($this_input.data('type') === 'INT') { + var mini = parseInt($this_input.attr('min')); + var maxi = parseInt($this_input.attr('max')); + $this_input.rules('add', { + number: { + param : true, + depends: function () { + return checkForCheckbox(multi_edit); + } + }, + min: { + param: mini, + depends: function () { + if (isNaN($this_input.val())) { + return false; + } else { + return checkForCheckbox(multi_edit); + } + } + }, + max: { + param: maxi, + depends: function () { + if (isNaN($this_input.val())) { + return false; + } else { + return checkForCheckbox(multi_edit); + } + } + } + }); + // validation for CHAR types + } else if ($this_input.data('type') === 'CHAR') { + var maxlen = $this_input.data('maxlength'); + if (typeof maxlen !== 'undefined') { + if (maxlen <= 4) { + maxlen = charExceptionHandling; + } + $this_input.rules('add', { + maxlength: { + param: maxlen, + depends: function () { + return checkForCheckbox(multi_edit); + } + } + }); + } + // validate binary & blob types + } else if ($this_input.data('type') === 'HEX') { + $this_input.rules('add', { + validationFunctionForHex: { + param: true, + depends: function () { + return checkForCheckbox(multi_edit); + } + } + }); + } + $this_input.data('rulesadded', true); + } else if ($this_input.data('rulesadded') === true && function_selected) { + // remove any rules added + $this_input.rules('remove'); + // remove any error messages + $this_input + .removeClass('error') + .removeAttr('aria-invalid') + .siblings('.error') + .remove(); + $this_input.data('rulesadded', null); + } +} +/* End of fields validation*/ + +/** + * Unbind all event handlers before tearing down a page + */ +AJAX.registerTeardown('tbl_change.js', function () { + $(document).off('click', 'span.open_gis_editor'); + $(document).off('click', 'input[name^=\'insert_ignore_\']'); + $(document).off('click', 'input[name=\'gis_data[save]\']'); + $(document).off('click', 'input.checkbox_null'); + $('select[name="submit_type"]').off('change'); + $(document).off('change', '#insert_rows'); +}); + +/** + * Ajax handlers for Change Table page + * + * Actions Ajaxified here: + * Submit Data to be inserted into the table. + * Restart insertion with 'N' rows. + */ +AJAX.registerOnload('tbl_change.js', function () { + if ($('#insertForm').length) { + // validate the comment form when it is submitted + $('#insertForm').validate(); + jQuery.validator.addMethod('validationFunctionForHex', function (value, element) { + return value.match(/^[a-f0-9]*$/i) !== null; + }); + + jQuery.validator.addMethod('validationFunctionForFuns', function (value, element, options) { + if (value.substring(0, 3) === 'AES' && options.data('type') !== 'HEX') { + return false; + } + + return !(value.substring(0, 3) === 'MD5' && + typeof options.data('maxlength') !== 'undefined' && + options.data('maxlength') < 32); + }); + + jQuery.validator.addMethod('validationFunctionForDateTime', function (value, element, options) { + var dt_value = value; + var theType = options; + if (theType === 'date') { + return isDate(dt_value); + } else if (theType === 'time') { + return isTime(dt_value); + } else if (theType === 'datetime' || theType === 'timestamp') { + var tmstmp = false; + dt_value = dt_value.trim(); + if (dt_value === 'CURRENT_TIMESTAMP' || dt_value === 'current_timestamp()') { + return true; + } + if (theType === 'timestamp') { + tmstmp = true; + } + if (dt_value === '0000-00-00 00:00:00') { + return true; + } + var dv = dt_value.indexOf(' '); + if (dv === -1) { // Only the date component, which is valid + return isDate(dt_value, tmstmp); + } + + return isDate(dt_value.substring(0, dv), tmstmp) && + isTime(dt_value.substring(dv + 1)); + } + }); + /* + * message extending script must be run + * after initiation of functions + */ + extendingValidatorMessages(); + } + + $.datepicker.initialized = false; + + $(document).on('click', 'span.open_gis_editor', function (event) { + event.preventDefault(); + + var $span = $(this); + // Current value + var value = $span.parent('td').children('input[type=\'text\']').val(); + // Field name + var field = $span.parents('tr').children('td:first').find('input[type=\'hidden\']').val(); + // Column type + var type = $span.parents('tr').find('span.column_type').text(); + // Names of input field and null checkbox + var input_name = $span.parent('td').children('input[type=\'text\']').attr('name'); + + openGISEditor(); + if (!gisEditorLoaded) { + loadJSAndGISEditor(value, field, type, input_name); + } else { + loadGISEditor(value, field, type, input_name); + } + }); + + /** + * Forced validation check of fields + */ + $(document).on('click','input[name^=\'insert_ignore_\']', function (event) { + $('#insertForm').valid(); + }); + + /** + * Uncheck the null checkbox as geometry data is placed on the input field + */ + $(document).on('click', 'input[name=\'gis_data[save]\']', function (event) { + var input_name = $('form#gis_data_editor_form').find('input[name=\'input_name\']').val(); + var $null_checkbox = $('input[name=\'' + input_name + '\']').parents('tr').find('.checkbox_null'); + $null_checkbox.prop('checked', false); + }); + + /** + * Handles all current checkboxes for Null; this only takes care of the + * checkboxes on currently displayed rows as the rows generated by + * "Continue insertion" are handled in the "Continue insertion" code + * + */ + $(document).on('click', 'input.checkbox_null', function () { + nullify( + // use hidden fields populated by tbl_change.php + $(this).siblings('.nullify_code').val(), + $(this).closest('tr').find('input:hidden').first().val(), + $(this).siblings('.hashed_field').val(), + $(this).siblings('.multi_edit').val() + ); + }); + + /** + * Reset the auto_increment column to 0 when selecting any of the + * insert options in submit_type-dropdown. Only perform the reset + * when we are in edit-mode, and not in insert-mode(no previous value + * available). + */ + $('select[name="submit_type"]').on('change', function () { + var thisElemSubmitTypeVal = $(this).val(); + var $table = $('table.insertRowTable'); + var auto_increment_column = $table.find('input[name^="auto_increment"]'); + auto_increment_column.each(function () { + var $thisElemAIField = $(this); + var thisElemName = $thisElemAIField.attr('name'); + + var prev_value_field = $table.find('input[name="' + thisElemName.replace('auto_increment', 'fields_prev') + '"]'); + var value_field = $table.find('input[name="' + thisElemName.replace('auto_increment', 'fields') + '"]'); + var previous_value = $(prev_value_field).val(); + if (previous_value !== undefined) { + if (thisElemSubmitTypeVal === 'insert' + || thisElemSubmitTypeVal === 'insertignore' + || thisElemSubmitTypeVal === 'showinsert' + ) { + $(value_field).val(0); + } else { + $(value_field).val(previous_value); + } + } + }); + }); + + /** + * Handle ENTER key when press on Continue insert with field + */ + $('#insert_rows').keypress(function (e) { + var key = e.which; + if (key === 13) { + addNewContinueInsertionFiels(e); + } + }); + + /** + * Continue Insertion form + */ + $(document).on('change', '#insert_rows', addNewContinueInsertionFiels); +}); + +function addNewContinueInsertionFiels (event) { + event.preventDefault(); + /** + * @var columnCount Number of number of columns table has. + */ + var columnCount = $('table.insertRowTable:first').find('tr').has('input[name*=\'fields_name\']').length; + /** + * @var curr_rows Number of current insert rows already on page + */ + var curr_rows = $('table.insertRowTable').length; + /** + * @var target_rows Number of rows the user wants + */ + var target_rows = $('#insert_rows').val(); + + // remove all datepickers + $('input.datefield, input.datetimefield').each(function () { + $(this).datepicker('destroy'); + }); + + if (curr_rows < target_rows) { + var tempIncrementIndex = function () { + var $this_element = $(this); + /** + * Extract the index from the name attribute for all input/select fields and increment it + * name is of format funcs[multi_edit][10][] + */ + + /** + * @var this_name String containing name of the input/select elements + */ + var this_name = $this_element.attr('name'); + /** split {@link this_name} at [10], so we have the parts that can be concatenated later */ + var name_parts = this_name.split(/\[\d+\]/); + /** extract the [10] from {@link name_parts} */ + var old_row_index_string = this_name.match(/\[\d+\]/)[0]; + /** extract 10 - had to split into two steps to accomodate double digits */ + var old_row_index = parseInt(old_row_index_string.match(/\d+/)[0], 10); + + /** calculate next index i.e. 11 */ + new_row_index = old_row_index + 1; + /** generate the new name i.e. funcs[multi_edit][11][foobarbaz] */ + var new_name = name_parts[0] + '[' + new_row_index + ']' + name_parts[1]; + + var hashed_field = name_parts[1].match(/\[(.+)\]/)[1]; + $this_element.attr('name', new_name); + + /** If element is select[name*='funcs'], update id */ + if ($this_element.is('select[name*=\'funcs\']')) { + var this_id = $this_element.attr('id'); + var id_parts = this_id.split(/\_/); + var old_id_index = id_parts[1]; + var prevSelectedValue = $('#field_' + old_id_index + '_1').val(); + var new_id_index = parseInt(old_id_index) + columnCount; + var new_id = 'field_' + new_id_index + '_1'; + $this_element.attr('id', new_id); + $this_element.find('option').filter(function () { + return $(this).text() === prevSelectedValue; + }).attr('selected','selected'); + + // If salt field is there then update its id. + var nextSaltInput = $this_element.parent().next('td').next('td').find('input[name*=\'salt\']'); + if (nextSaltInput.length !== 0) { + nextSaltInput.attr('id', 'salt_' + new_id); + } + } + + // handle input text fields and textareas + if ($this_element.is('.textfield') || $this_element.is('.char') || $this_element.is('textarea')) { + // do not remove the 'value' attribute for ENUM columns + // special handling for radio fields after updating ids to unique - see below + if ($this_element.closest('tr').find('span.column_type').html() !== 'enum') { + $this_element.val($this_element.closest('tr').find('span.default_value').html()); + } + $this_element + .off('change') + // Remove onchange attribute that was placed + // by tbl_change.php; it refers to the wrong row index + .attr('onchange', null) + // Keep these values to be used when the element + // will change + .data('hashed_field', hashed_field) + .data('new_row_index', new_row_index) + .on('change', function () { + var $changed_element = $(this); + verificationsAfterFieldChange( + $changed_element.data('hashed_field'), + $changed_element.data('new_row_index'), + $changed_element.closest('tr').find('span.column_type').html() + ); + }); + } + + if ($this_element.is('.checkbox_null')) { + $this_element + // this event was bound earlier by jQuery but + // to the original row, not the cloned one, so unbind() + .off('click') + // Keep these values to be used when the element + // will be clicked + .data('hashed_field', hashed_field) + .data('new_row_index', new_row_index) + .on('click', function () { + var $changed_element = $(this); + nullify( + $changed_element.siblings('.nullify_code').val(), + $this_element.closest('tr').find('input:hidden').first().val(), + $changed_element.data('hashed_field'), + '[multi_edit][' + $changed_element.data('new_row_index') + ']' + ); + }); + } + }; + + var tempReplaceAnchor = function () { + var $anchor = $(this); + var new_value = 'rownumber=' + new_row_index; + // needs improvement in case something else inside + // the href contains this pattern + var new_href = $anchor.attr('href').replace(/rownumber=\d+/, new_value); + $anchor.attr('href', new_href); + }; + + while (curr_rows < target_rows) { + /** + * @var $last_row Object referring to the last row + */ + var $last_row = $('#insertForm').find('.insertRowTable:last'); + + // need to access this at more than one level + // (also needs improvement because it should be calculated + // just once per cloned row, not once per column) + var new_row_index = 0; + + // Clone the insert tables + $last_row + .clone(true, true) + .insertBefore('#actions_panel') + .find('input[name*=multi_edit],select[name*=multi_edit],textarea[name*=multi_edit]') + .each(tempIncrementIndex) + .end() + .find('.foreign_values_anchor') + .each(tempReplaceAnchor); + + // Insert/Clone the ignore checkboxes + if (curr_rows === 1) { + $('') + .insertBefore('table.insertRowTable:last') + .after(''); + } else { + /** + * @var $last_checkbox Object reference to the last checkbox in #insertForm + */ + var $last_checkbox = $('#insertForm').children('input:checkbox:last'); + + /** name of {@link $last_checkbox} */ + var last_checkbox_name = $last_checkbox.attr('name'); + /** index of {@link $last_checkbox} */ + var last_checkbox_index = parseInt(last_checkbox_name.match(/\d+/), 10); + /** name of new {@link $last_checkbox} */ + var new_name = last_checkbox_name.replace(/\d+/, last_checkbox_index + 1); + + $('
      ') + .insertBefore('table.insertRowTable:last'); + + $last_checkbox + .clone() + .attr({ 'id': new_name, 'name': new_name }) + .prop('checked', true) + .insertBefore('table.insertRowTable:last'); + + $('label[for^=insert_ignore]:last') + .clone() + .attr('for', new_name) + .insertBefore('table.insertRowTable:last'); + + $('
      ') + .insertBefore('table.insertRowTable:last'); + } + curr_rows++; + } + // recompute tabindex for text fields and other controls at footer; + // IMO it's not really important to handle the tabindex for + // function and Null + var tabindex = 0; + $('.textfield, .char, textarea') + .each(function () { + tabindex++; + $(this).attr('tabindex', tabindex); + // update the IDs of textfields to ensure that they are unique + $(this).attr('id', 'field_' + tabindex + '_3'); + + // special handling for radio fields after updating ids to unique + if ($(this).closest('tr').find('span.column_type').html() === 'enum') { + if ($(this).val() === $(this).closest('tr').find('span.default_value').html()) { + $(this).prop('checked', true); + } else { + $(this).prop('checked', false); + } + } + }); + $('.control_at_footer') + .each(function () { + tabindex++; + $(this).attr('tabindex', tabindex); + }); + } else if (curr_rows > target_rows) { + /** + * Displays alert if data loss possible on decrease + * of rows. + */ + var checkLock = jQuery.isEmptyObject(AJAX.lockedTargets); + if (checkLock || confirm(PMA_messages.strConfirmRowChange) === true) { + while (curr_rows > target_rows) { + $('input[id^=insert_ignore]:last') + .nextUntil('fieldset') + .addBack() + .remove(); + curr_rows--; + } + } else { + document.getElementById('insert_rows').value = curr_rows; + } + } + // Add all the required datepickers back + addDateTimePicker(); +} + +function changeValueFieldType (elem, searchIndex) { + var fieldsValue = $('select#fieldID_' + searchIndex); + if (0 === fieldsValue.size()) { + return; + } + + var type = $(elem).val(); + if ('IN (...)' === type || + 'NOT IN (...)' === type || + 'BETWEEN' === type || + 'NOT BETWEEN' === type + ) { + $('#fieldID_' + searchIndex).attr('multiple', ''); + } else { + $('#fieldID_' + searchIndex).removeAttr('multiple'); + } +} diff --git a/php/apps/phpmyadmin49/html/js/tbl_chart.js b/php/apps/phpmyadmin49/html/js/tbl_chart.js new file mode 100644 index 00000000..b43a2215 --- /dev/null +++ b/php/apps/phpmyadmin49/html/js/tbl_chart.js @@ -0,0 +1,423 @@ +/* vim: set expandtab sw=4 ts=4 sts=4: */ + +var chart_data = {}; +var temp_chart_title; + +var currentChart = null; +var currentSettings = null; + +var dateTimeCols = []; +var numericCols = []; + +function extractDate (dateString) { + var matches; + var match; + var dateTimeRegExp = /[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}/; + var dateRegExp = /[0-9]{4}-[0-9]{2}-[0-9]{2}/; + + matches = dateTimeRegExp.exec(dateString); + if (matches !== null && matches.length > 0) { + match = matches[0]; + return new Date(match.substr(0, 4), parseInt(match.substr(5, 2), 10) - 1, match.substr(8, 2), match.substr(11, 2), match.substr(14, 2), match.substr(17, 2)); + } else { + matches = dateRegExp.exec(dateString); + if (matches !== null && matches.length > 0) { + match = matches[0]; + return new Date(match.substr(0, 4), parseInt(match.substr(5, 2), 10) - 1, match.substr(8, 2)); + } + } + return null; +} + +function PMA_queryChart (data, columnNames, settings) { + if ($('#querychart').length === 0) { + return; + } + + var plotSettings = { + title : { + text : settings.title, + escapeHtml: true + }, + grid : { + drawBorder : false, + shadow : false, + background : 'rgba(0,0,0,0)' + }, + legend : { + show : true, + placement : 'outsideGrid', + location : 'e', + rendererOptions: { + numberColumns: 2 + } + }, + axes : { + xaxis : { + label : escapeHtml(settings.xaxisLabel) + }, + yaxis : { + label : settings.yaxisLabel + } + }, + stackSeries : settings.stackSeries + }; + + // create the chart + var factory = new JQPlotChartFactory(); + var chart = factory.createChart(settings.type, 'querychart'); + + // create the data table and add columns + var dataTable = new DataTable(); + if (settings.type === 'timeline') { + dataTable.addColumn(ColumnType.DATE, columnNames[settings.mainAxis]); + } else if (settings.type === 'scatter') { + dataTable.addColumn(ColumnType.NUMBER, columnNames[settings.mainAxis]); + } else { + dataTable.addColumn(ColumnType.STRING, columnNames[settings.mainAxis]); + } + + var i; + if (settings.seriesColumn === null) { + $.each(settings.selectedSeries, function (index, element) { + dataTable.addColumn(ColumnType.NUMBER, columnNames[element]); + }); + + // set data to the data table + var columnsToExtract = [settings.mainAxis]; + $.each(settings.selectedSeries, function (index, element) { + columnsToExtract.push(element); + }); + var values = []; + var newRow; + var row; + var col; + for (i = 0; i < data.length; i++) { + row = data[i]; + newRow = []; + for (var j = 0; j < columnsToExtract.length; j++) { + col = columnNames[columnsToExtract[j]]; + if (j === 0) { + if (settings.type === 'timeline') { // first column is date type + newRow.push(extractDate(row[col])); + } else if (settings.type === 'scatter') { + newRow.push(parseFloat(row[col])); + } else { // first column is string type + newRow.push(row[col]); + } + } else { // subsequent columns are of type, number + newRow.push(parseFloat(row[col])); + } + } + values.push(newRow); + } + dataTable.setData(values); + } else { + var seriesNames = {}; + var seriesNumber = 1; + var seriesColumnName = columnNames[settings.seriesColumn]; + for (i = 0; i < data.length; i++) { + if (! seriesNames[data[i][seriesColumnName]]) { + seriesNames[data[i][seriesColumnName]] = seriesNumber; + seriesNumber++; + } + } + + $.each(seriesNames, function (seriesName, seriesNumber) { + dataTable.addColumn(ColumnType.NUMBER, seriesName); + }); + + var valueMap = {}; + var xValue; + var value; + var mainAxisName = columnNames[settings.mainAxis]; + var valueColumnName = columnNames[settings.valueColumn]; + for (i = 0; i < data.length; i++) { + xValue = data[i][mainAxisName]; + value = valueMap[xValue]; + if (! value) { + value = [xValue]; + valueMap[xValue] = value; + } + seriesNumber = seriesNames[data[i][seriesColumnName]]; + value[seriesNumber] = parseFloat(data[i][valueColumnName]); + } + + var values = []; + $.each(valueMap, function (index, value) { + values.push(value); + }); + dataTable.setData(values); + } + + // draw the chart and return the chart object + chart.draw(dataTable, plotSettings); + return chart; +} + +function drawChart () { + currentSettings.width = $('#resizer').width() - 20; + currentSettings.height = $('#resizer').height() - 20; + + // TODO: a better way using .redraw() ? + if (currentChart !== null) { + currentChart.destroy(); + } + + var columnNames = []; + $('select[name="chartXAxis"] option').each(function () { + columnNames.push(escapeHtml($(this).text())); + }); + try { + currentChart = PMA_queryChart(chart_data, columnNames, currentSettings); + if (currentChart !== null) { + $('#saveChart').attr('href', currentChart.toImageString()); + } + } catch (err) { + PMA_ajaxShowMessage(err.message, false); + } +} + +function getSelectedSeries () { + var val = $('select[name="chartSeries"]').val() || []; + var ret = []; + $.each(val, function (i, v) { + ret.push(parseInt(v, 10)); + }); + return ret; +} + +function onXAxisChange () { + var $xAxisSelect = $('select[name="chartXAxis"]'); + currentSettings.mainAxis = parseInt($xAxisSelect.val(), 10); + if (dateTimeCols.indexOf(currentSettings.mainAxis) !== -1) { + $('span.span_timeline').show(); + } else { + $('span.span_timeline').hide(); + if (currentSettings.type === 'timeline') { + $('input#radio_line').prop('checked', true); + currentSettings.type = 'line'; + } + } + if (numericCols.indexOf(currentSettings.mainAxis) !== -1) { + $('span.span_scatter').show(); + } else { + $('span.span_scatter').hide(); + if (currentSettings.type === 'scatter') { + $('input#radio_line').prop('checked', true); + currentSettings.type = 'line'; + } + } + var xaxis_title = $xAxisSelect.children('option:selected').text(); + $('input[name="xaxis_label"]').val(xaxis_title); + currentSettings.xaxisLabel = xaxis_title; +} + +function onDataSeriesChange () { + var $seriesSelect = $('select[name="chartSeries"]'); + currentSettings.selectedSeries = getSelectedSeries(); + var yaxis_title; + if (currentSettings.selectedSeries.length === 1) { + $('span.span_pie').show(); + yaxis_title = $seriesSelect.children('option:selected').text(); + } else { + $('span.span_pie').hide(); + if (currentSettings.type === 'pie') { + $('input#radio_line').prop('checked', true); + currentSettings.type = 'line'; + } + yaxis_title = PMA_messages.strYValues; + } + $('input[name="yaxis_label"]').val(yaxis_title); + currentSettings.yaxisLabel = yaxis_title; +} + +/** + * Unbind all event handlers before tearing down a page + */ +AJAX.registerTeardown('tbl_chart.js', function () { + $('input[name="chartType"]').off('click'); + $('input[name="barStacked"]').off('click'); + $('input[name="chkAlternative"]').off('click'); + $('input[name="chartTitle"]').off('focus').off('keyup').off('blur'); + $('select[name="chartXAxis"]').off('change'); + $('select[name="chartSeries"]').off('change'); + $('select[name="chartSeriesColumn"]').off('change'); + $('select[name="chartValueColumn"]').off('change'); + $('input[name="xaxis_label"]').off('keyup'); + $('input[name="yaxis_label"]').off('keyup'); + $('#resizer').off('resizestop'); + $('#tblchartform').off('submit'); +}); + +AJAX.registerOnload('tbl_chart.js', function () { + // handle manual resize + $('#resizer').on('resizestop', function (event, ui) { + // make room so that the handle will still appear + $('#querychart').height($('#resizer').height() * 0.96); + $('#querychart').width($('#resizer').width() * 0.96); + if (currentChart !== null) { + currentChart.redraw({ + resetAxes : true + }); + } + }); + + // handle chart type changes + $('input[name="chartType"]').click(function () { + var type = currentSettings.type = $(this).val(); + if (type === 'bar' || type === 'column' || type === 'area') { + $('span.barStacked').show(); + } else { + $('input[name="barStacked"]').prop('checked', false); + $.extend(true, currentSettings, { stackSeries : false }); + $('span.barStacked').hide(); + } + drawChart(); + }); + + // handle chosing alternative data format + $('input[name="chkAlternative"]').click(function () { + var $seriesColumn = $('select[name="chartSeriesColumn"]'); + var $valueColumn = $('select[name="chartValueColumn"]'); + var $chartSeries = $('select[name="chartSeries"]'); + if ($(this).is(':checked')) { + $seriesColumn.prop('disabled', false); + $valueColumn.prop('disabled', false); + $chartSeries.prop('disabled', true); + currentSettings.seriesColumn = parseInt($seriesColumn.val(), 10); + currentSettings.valueColumn = parseInt($valueColumn.val(), 10); + } else { + $seriesColumn.prop('disabled', true); + $valueColumn.prop('disabled', true); + $chartSeries.prop('disabled', false); + currentSettings.seriesColumn = null; + currentSettings.valueColumn = null; + } + drawChart(); + }); + + // handle stacking for bar, column and area charts + $('input[name="barStacked"]').click(function () { + if ($(this).is(':checked')) { + $.extend(true, currentSettings, { stackSeries : true }); + } else { + $.extend(true, currentSettings, { stackSeries : false }); + } + drawChart(); + }); + + // handle changes in chart title + $('input[name="chartTitle"]') + .focus(function () { + temp_chart_title = $(this).val(); + }) + .keyup(function () { + currentSettings.title = $('input[name="chartTitle"]').val(); + drawChart(); + }) + .blur(function () { + if ($(this).val() !== temp_chart_title) { + drawChart(); + } + }); + + // handle changing the x-axis + $('select[name="chartXAxis"]').change(function () { + onXAxisChange(); + drawChart(); + }); + + // handle changing the selected data series + $('select[name="chartSeries"]').change(function () { + onDataSeriesChange(); + drawChart(); + }); + + // handle changing the series column + $('select[name="chartSeriesColumn"]').change(function () { + currentSettings.seriesColumn = parseInt($(this).val(), 10); + drawChart(); + }); + + // handle changing the value column + $('select[name="chartValueColumn"]').change(function () { + currentSettings.valueColumn = parseInt($(this).val(), 10); + drawChart(); + }); + + // handle manual changes to the chart x-axis labels + $('input[name="xaxis_label"]').keyup(function () { + currentSettings.xaxisLabel = $(this).val(); + drawChart(); + }); + + // handle manual changes to the chart y-axis labels + $('input[name="yaxis_label"]').keyup(function () { + currentSettings.yaxisLabel = $(this).val(); + drawChart(); + }); + + // handler for ajax form submission + $('#tblchartform').submit(function (event) { + var $form = $(this); + if (codemirror_editor) { + $form[0].elements.sql_query.value = codemirror_editor.getValue(); + } + if (!checkSqlQuery($form[0])) { + return false; + } + + var $msgbox = PMA_ajaxShowMessage(); + PMA_prepareForAjaxRequest($form); + $.post($form.attr('action'), $form.serialize(), function (data) { + if (typeof data !== 'undefined' && + data.success === true && + typeof data.chartData !== 'undefined') { + chart_data = JSON.parse(data.chartData); + drawChart(); + PMA_ajaxRemoveMessage($msgbox); + } else { + PMA_ajaxShowMessage(data.error, false); + } + }, 'json'); // end $.post() + + return false; + }); + + // from jQuery UI + $('#resizer').resizable({ + minHeight: 240, + minWidth: 300 + }) + .width($('#div_view_options').width() - 50) + .trigger('resizestop'); + + currentSettings = { + type : 'line', + width : $('#resizer').width() - 20, + height : $('#resizer').height() - 20, + xaxisLabel : $('input[name="xaxis_label"]').val(), + yaxisLabel : $('input[name="yaxis_label"]').val(), + title : $('input[name="chartTitle"]').val(), + stackSeries : false, + mainAxis : parseInt($('select[name="chartXAxis"]').val(), 10), + selectedSeries : getSelectedSeries(), + seriesColumn : null + }; + + var vals = $('input[name="dateTimeCols"]').val().split(' '); + $.each(vals, function (i, v) { + dateTimeCols.push(parseInt(v, 10)); + }); + + vals = $('input[name="numericCols"]').val().split(' '); + $.each(vals, function (i, v) { + numericCols.push(parseInt(v, 10)); + }); + + onXAxisChange(); + onDataSeriesChange(); + + $('#tblchartform').submit(); +}); diff --git a/php/apps/phpmyadmin49/html/js/tbl_find_replace.js b/php/apps/phpmyadmin49/html/js/tbl_find_replace.js new file mode 100644 index 00000000..8b48e53e --- /dev/null +++ b/php/apps/phpmyadmin49/html/js/tbl_find_replace.js @@ -0,0 +1,46 @@ +/** + * Unbind all event handlers before tearing down a page + */ +AJAX.registerTeardown('tbl_find_replace.js', function () { + $('#find_replace_form').off('submit'); + $('#toggle_find').off('click'); +}); + +/** + * Bind events + */ +AJAX.registerOnload('tbl_find_replace.js', function () { + $('
      ') + .insertAfter('#find_replace_form') + .hide(); + + $('#toggle_find') + .html(PMA_messages.strHideFindNReplaceCriteria) + .click(function () { + var $link = $(this); + $('#find_replace_form').slideToggle(); + if ($link.text() === PMA_messages.strHideFindNReplaceCriteria) { + $link.text(PMA_messages.strShowFindNReplaceCriteria); + } else { + $link.text(PMA_messages.strHideFindNReplaceCriteria); + } + return false; + }); + + $('#find_replace_form').submit(function (e) { + e.preventDefault(); + var findReplaceForm = $('#find_replace_form'); + PMA_prepareForAjaxRequest(findReplaceForm); + var $msgbox = PMA_ajaxShowMessage(); + $.post(findReplaceForm.attr('action'), findReplaceForm.serialize(), function (data) { + PMA_ajaxRemoveMessage($msgbox); + if (data.success === true) { + $('#toggle_find_div').show(); + $('#toggle_find').click(); + $('#sqlqueryresultsouter').html(data.preview); + } else { + $('#sqlqueryresultsouter').html(data.error); + } + }); + }); +}); diff --git a/php/apps/phpmyadmin49/html/js/tbl_gis_visualization.js b/php/apps/phpmyadmin49/html/js/tbl_gis_visualization.js new file mode 100644 index 00000000..0cb70ab3 --- /dev/null +++ b/php/apps/phpmyadmin49/html/js/tbl_gis_visualization.js @@ -0,0 +1,365 @@ +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * @fileoverview functions used for visualizing GIS data + * + * @requires jquery + * @requires vendor/jquery/jquery.svg.js + * @requires vendor/jquery/jquery.mousewheel.js + * @requires vendor/jquery/jquery.event.drag-2.2.js + */ + +// Constants +var zoomFactor = 1.5; +var defaultX = 0; +var defaultY = 0; + +// Variables +var x = 0; +var y = 0; +var scale = 1; + +var svg; + +/** + * Zooms and pans the visualization. + */ +function zoomAndPan () { + var g = svg.getElementById('groupPanel'); + if (!g) { + return; + } + + g.setAttribute('transform', 'translate(' + x + ', ' + y + ') scale(' + scale + ')'); + var id; + var circle; + $('circle.vector').each(function () { + id = $(this).attr('id'); + circle = svg.getElementById(id); + $(svg).change(circle, { + r : (3 / scale), + 'stroke-width' : (2 / scale) + }); + }); + + var line; + $('polyline.vector').each(function () { + id = $(this).attr('id'); + line = svg.getElementById(id); + $(svg).change(line, { + 'stroke-width' : (2 / scale) + }); + }); + + var polygon; + $('path.vector').each(function () { + id = $(this).attr('id'); + polygon = svg.getElementById(id); + $(svg).change(polygon, { + 'stroke-width' : (0.5 / scale) + }); + }); +} + +/** + * Initially loads either SVG or OSM visualization based on the choice. + */ +function selectVisualization () { + if ($('#choice').prop('checked') !== true) { + $('#openlayersmap').hide(); + } else { + $('#placeholder').hide(); + } +} + +/** + * Adds necessary styles to the div that coontains the openStreetMap. + */ +function styleOSM () { + var $placeholder = $('#placeholder'); + var cssObj = { + 'border' : '1px solid #aaa', + 'width' : $placeholder.width(), + 'height' : $placeholder.height(), + 'float' : 'right' + }; + $('#openlayersmap').css(cssObj); +} + +/** + * Loads the SVG element and make a reference to it. + */ +function loadSVG () { + var $placeholder = $('#placeholder'); + + $placeholder.svg({ + onLoad: function (svg_ref) { + svg = svg_ref; + } + }); + + // Removes the second SVG element unnecessarily added due to the above command + $placeholder.find('svg:nth-child(2)').remove(); +} + +/** + * Adds controllers for zooming and panning. + */ +function addZoomPanControllers () { + var $placeholder = $('#placeholder'); + if ($('#placeholder').find('svg').length > 0) { + var pmaThemeImage = $('#pmaThemeImage').val(); + // add panning arrows + $('').appendTo($placeholder); + $('').appendTo($placeholder); + $('').appendTo($placeholder); + $('').appendTo($placeholder); + // add zooming controls + $('').appendTo($placeholder); + $('').appendTo($placeholder); + $('').appendTo($placeholder); + } +} + +/** + * Resizes the GIS visualization to fit into the space available. + */ +function resizeGISVisualization () { + var $placeholder = $('#placeholder'); + var old_width = $placeholder.width(); + var visWidth = $('#div_view_options').width() - 48; + + // Assign new value for width + $placeholder.width(visWidth); + $('svg').attr('width', visWidth); + + // Assign the offset created due to resizing to defaultX and center the svg. + defaultX = (visWidth - old_width) / 2; + x = defaultX; + y = 0; + scale = 1; +} + +/** + * Initialize the GIS visualization. + */ +function initGISVisualization () { + // Loads either SVG or OSM visualization based on the choice + selectVisualization(); + // Resizes the GIS visualization to fit into the space available + resizeGISVisualization(); + if (typeof OpenLayers !== 'undefined') { + // Configure OpenLayers + OpenLayers._getScriptLocation = function () { + return './js/vendor/openlayers/'; + }; + // Adds necessary styles to the div that coontains the openStreetMap + styleOSM(); + // Draws openStreetMap with openLayers + drawOpenLayers(); + } + // Loads the SVG element and make a reference to it + loadSVG(); + // Adds controllers for zooming and panning + addZoomPanControllers(); + zoomAndPan(); +} + +function getRelativeCoords (e) { + var position = $('#placeholder').offset(); + return { + x : e.pageX - position.left, + y : e.pageY - position.top + }; +} + +/** + * Ajax handlers for GIS visualization page + * + * Actions Ajaxified here: + * + * Zooming in and zooming out on mousewheel movement. + * Panning the visualization on dragging. + * Zooming in on double clicking. + * Zooming out on clicking the zoom out button. + * Panning on clicking the arrow buttons. + * Displaying tooltips for GIS objects. + */ + +/** + * Unbind all event handlers before tearing down a page + */ +AJAX.registerTeardown('tbl_gis_visualization.js', function () { + $(document).off('click', '#choice'); + $(document).off('mousewheel', '#placeholder'); + $(document).off('dragstart', 'svg'); + $(document).off('mouseup', 'svg'); + $(document).off('drag', 'svg'); + $(document).off('dblclick', '#placeholder'); + $(document).off('click', '#zoom_in'); + $(document).off('click', '#zoom_world'); + $(document).off('click', '#zoom_out'); + $(document).off('click', '#left_arrow'); + $(document).off('click', '#right_arrow'); + $(document).off('click', '#up_arrow'); + $(document).off('click', '#down_arrow'); + $('.vector').off('mousemove').off('mouseout'); +}); + +AJAX.registerOnload('tbl_gis_visualization.js', function () { + // If we are in GIS visualization, initialize it + if ($('#gis_div').length > 0) { + initGISVisualization(); + } + + if (typeof OpenLayers === 'undefined') { + $('#choice, #labelChoice').hide(); + } + $(document).on('click', '#choice', function () { + if ($(this).prop('checked') === false) { + $('#placeholder').show(); + $('#openlayersmap').hide(); + } else { + $('#placeholder').hide(); + $('#openlayersmap').show(); + } + }); + + $(document).on('mousewheel', '#placeholder', function (event, delta) { + event.preventDefault(); + var relCoords = getRelativeCoords(event); + if (delta > 0) { + // zoom in + scale *= zoomFactor; + // zooming in keeping the position under mouse pointer unmoved. + x = relCoords.x - (relCoords.x - x) * zoomFactor; + y = relCoords.y - (relCoords.y - y) * zoomFactor; + zoomAndPan(); + } else { + // zoom out + scale /= zoomFactor; + // zooming out keeping the position under mouse pointer unmoved. + x = relCoords.x - (relCoords.x - x) / zoomFactor; + y = relCoords.y - (relCoords.y - y) / zoomFactor; + zoomAndPan(); + } + return true; + }); + + var dragX = 0; + var dragY = 0; + + $(document).on('dragstart', 'svg', function (event, dd) { + $('#placeholder').addClass('placeholderDrag'); + dragX = Math.round(dd.offsetX); + dragY = Math.round(dd.offsetY); + }); + + $(document).on('mouseup', 'svg', function (event) { + $('#placeholder').removeClass('placeholderDrag'); + }); + + $(document).on('drag', 'svg', function (event, dd) { + var newX = Math.round(dd.offsetX); + x += newX - dragX; + dragX = newX; + var newY = Math.round(dd.offsetY); + y += newY - dragY; + dragY = newY; + zoomAndPan(); + }); + + $(document).on('dblclick', '#placeholder', function (event) { + scale *= zoomFactor; + // zooming in keeping the position under mouse pointer unmoved. + var relCoords = getRelativeCoords(event); + x = relCoords.x - (relCoords.x - x) * zoomFactor; + y = relCoords.y - (relCoords.y - y) * zoomFactor; + zoomAndPan(); + }); + + $(document).on('click', '#zoom_in', function (e) { + e.preventDefault(); + // zoom in + scale *= zoomFactor; + + var $placeholder = $('#placeholder').find('svg'); + width = $placeholder.attr('width'); + height = $placeholder.attr('height'); + // zooming in keeping the center unmoved. + x = width / 2 - (width / 2 - x) * zoomFactor; + y = height / 2 - (height / 2 - y) * zoomFactor; + zoomAndPan(); + }); + + $(document).on('click', '#zoom_world', function (e) { + e.preventDefault(); + scale = 1; + x = defaultX; + y = defaultY; + zoomAndPan(); + }); + + $(document).on('click', '#zoom_out', function (e) { + e.preventDefault(); + // zoom out + scale /= zoomFactor; + + var $placeholder = $('#placeholder').find('svg'); + width = $placeholder.attr('width'); + height = $placeholder.attr('height'); + // zooming out keeping the center unmoved. + x = width / 2 - (width / 2 - x) / zoomFactor; + y = height / 2 - (height / 2 - y) / zoomFactor; + zoomAndPan(); + }); + + $(document).on('click', '#left_arrow', function (e) { + e.preventDefault(); + x += 100; + zoomAndPan(); + }); + + $(document).on('click', '#right_arrow', function (e) { + e.preventDefault(); + x -= 100; + zoomAndPan(); + }); + + $(document).on('click', '#up_arrow', function (e) { + e.preventDefault(); + y += 100; + zoomAndPan(); + }); + + $(document).on('click', '#down_arrow', function (e) { + e.preventDefault(); + y -= 100; + zoomAndPan(); + }); + + /** + * Detect the mousemove event and show tooltips. + */ + $('.vector').on('mousemove', function (event) { + var contents = $.trim(escapeHtml($(this).attr('name'))); + $('#tooltip').remove(); + if (contents !== '') { + $('
      ' + contents + '
      ').css({ + position : 'absolute', + top : event.pageY + 10, + left : event.pageX + 10, + border : '1px solid #fdd', + padding : '2px', + 'background-color' : '#fee', + opacity : 0.90 + }).appendTo('body').fadeIn(200); + } + }); + + /** + * Detect the mouseout event and hide tooltips. + */ + $('.vector').on('mouseout', function (event) { + $('#tooltip').remove(); + }); +}); diff --git a/php/apps/phpmyadmin49/html/js/tbl_operations.js b/php/apps/phpmyadmin49/html/js/tbl_operations.js new file mode 100644 index 00000000..cb818a29 --- /dev/null +++ b/php/apps/phpmyadmin49/html/js/tbl_operations.js @@ -0,0 +1,323 @@ +/** + * Unbind all event handlers before tearing down a page + */ +AJAX.registerTeardown('tbl_operations.js', function () { + $(document).off('submit', '#copyTable.ajax'); + $(document).off('submit', '#moveTableForm'); + $(document).off('submit', '#tableOptionsForm'); + $(document).off('submit', '#partitionsForm'); + $(document).off('click', '#tbl_maintenance li a.maintain_action.ajax'); + $(document).off('click', '#drop_tbl_anchor.ajax'); + $(document).off('click', '#drop_view_anchor.ajax'); + $(document).off('click', '#truncate_tbl_anchor.ajax'); +}); + +/** + * jQuery coding for 'Table operations'. Used on tbl_operations.php + * Attach Ajax Event handlers for Table operations + */ +AJAX.registerOnload('tbl_operations.js', function () { + /** + *Ajax action for submitting the "Copy table" + **/ + $(document).on('submit', '#copyTable.ajax', function (event) { + event.preventDefault(); + var $form = $(this); + PMA_prepareForAjaxRequest($form); + var argsep = PMA_commonParams.get('arg_separator'); + $.post($form.attr('action'), $form.serialize() + argsep + 'submit_copy=Go', function (data) { + if (typeof data !== 'undefined' && data.success === true) { + if ($form.find('input[name=\'switch_to_new\']').prop('checked')) { + PMA_commonParams.set( + 'db', + $form.find('select[name=\'target_db\']').val() + ); + PMA_commonParams.set( + 'table', + $form.find('input[name=\'new_name\']').val() + ); + PMA_commonActions.refreshMain(false, function () { + PMA_ajaxShowMessage(data.message); + }); + } else { + PMA_ajaxShowMessage(data.message); + } + // Refresh navigation when the table is copied + PMA_reloadNavigation(); + } else { + PMA_ajaxShowMessage(data.error, false); + } + }); // end $.post() + });// end of copyTable ajax submit + + /** + *Ajax action for submitting the "Move table" + */ + $(document).on('submit', '#moveTableForm', function (event) { + event.preventDefault(); + var $form = $(this); + PMA_prepareForAjaxRequest($form); + var argsep = PMA_commonParams.get('arg_separator'); + $.post($form.attr('action'), $form.serialize() + argsep + 'submit_move=1', function (data) { + if (typeof data !== 'undefined' && data.success === true) { + PMA_commonParams.set('db', data._params.db); + PMA_commonParams.set('table', data._params.table); + PMA_commonActions.refreshMain('tbl_sql.php', function () { + PMA_ajaxShowMessage(data.message); + }); + // Refresh navigation when the table is copied + PMA_reloadNavigation(); + } else { + PMA_ajaxShowMessage(data.error, false); + } + }); // end $.post() + }); + + /** + * Ajax action for submitting the "Table options" + */ + $(document).on('submit', '#tableOptionsForm', function (event) { + event.preventDefault(); + event.stopPropagation(); + var $form = $(this); + var $tblNameField = $form.find('input[name=new_name]'); + var $tblCollationField = $form.find('select[name=tbl_collation]'); + var collationOrigValue = $('select[name="tbl_collation"] option[selected]').val(); + var $changeAllColumnCollationsCheckBox = $('#checkbox_change_all_collations'); + var question = PMA_messages.strChangeAllColumnCollationsWarning; + + if ($tblNameField.val() !== $tblNameField[0].defaultValue) { + // reload page and navigation if the table has been renamed + PMA_prepareForAjaxRequest($form); + + if ($tblCollationField.val() !== collationOrigValue && $changeAllColumnCollationsCheckBox.is(':checked')) { + $form.PMA_confirm(question, $form.attr('action'), function (url) { + submitOptionsForm(); + }); + } else { + submitOptionsForm(); + } + } else { + if ($tblCollationField.val() !== collationOrigValue && $changeAllColumnCollationsCheckBox.is(':checked')) { + $form.PMA_confirm(question, $form.attr('action'), function (url) { + $form.removeClass('ajax').submit().addClass('ajax'); + }); + } else { + $form.removeClass('ajax').submit().addClass('ajax'); + } + } + + function submitOptionsForm () { + $.post($form.attr('action'), $form.serialize(), function (data) { + if (typeof data !== 'undefined' && data.success === true) { + PMA_commonParams.set('table', data._params.table); + PMA_commonActions.refreshMain(false, function () { + $('#page_content').html(data.message); + PMA_highlightSQL($('#page_content')); + }); + // Refresh navigation when the table is renamed + PMA_reloadNavigation(); + } else { + PMA_ajaxShowMessage(data.error, false); + } + }); // end $.post() + } + }); + + /** + *Ajax events for actions in the "Table maintenance" + **/ + $(document).on('click', '#tbl_maintenance li a.maintain_action.ajax', function (event) { + event.preventDefault(); + var $link = $(this); + + if ($('.sqlqueryresults').length !== 0) { + $('.sqlqueryresults').remove(); + } + if ($('.result_query').length !== 0) { + $('.result_query').remove(); + } + // variables which stores the common attributes + var params = $.param({ + ajax_request: 1, + server: PMA_commonParams.get('server') + }); + var postData = $link.getPostData(); + if (postData) { + params += PMA_commonParams.get('arg_separator') + postData; + } + + $.post($link.attr('href'), params, function (data) { + function scrollToTop () { + $('html, body').animate({ scrollTop: 0 }); + } + var $temp_div; + if (typeof data !== 'undefined' && data.success === true && data.sql_query !== undefined) { + PMA_ajaxShowMessage(data.message); + $('
      ').prependTo('#page_content'); + $('.sqlqueryresults').html(data.sql_query); + PMA_highlightSQL($('#page_content')); + scrollToTop(); + } else if (typeof data !== 'undefined' && data.success === true) { + $temp_div = $('
      '); + $temp_div.html(data.message); + var $success = $temp_div.find('.result_query .success'); + PMA_ajaxShowMessage($success); + $('
      ').prependTo('#page_content'); + $('.sqlqueryresults').html(data.message); + PMA_highlightSQL($('#page_content')); + PMA_init_slider(); + $('.sqlqueryresults').children('fieldset,br').remove(); + scrollToTop(); + } else { + $temp_div = $('
      '); + $temp_div.html(data.error); + + var $error; + if ($temp_div.find('.error code').length !== 0) { + $error = $temp_div.find('.error code').addClass('error'); + } else { + $error = $temp_div; + } + + PMA_ajaxShowMessage($error, false); + } + }); // end $.post() + });// end of table maintenance ajax click + + /** + * Ajax action for submitting the "Partition Maintenance" + * Also, asks for confirmation when DROP partition is submitted + */ + $(document).on('submit', '#partitionsForm', function (event) { + event.preventDefault(); + var $form = $(this); + + function submitPartitionMaintenance () { + var argsep = PMA_commonParams.get('arg_separator'); + var submitData = $form.serialize() + argsep + 'ajax_request=true' + argsep + 'ajax_page_request=true'; + PMA_ajaxShowMessage(PMA_messages.strProcessingRequest); + AJAX.source = $form; + $.post($form.attr('action'), submitData, AJAX.responseHandler); + } + + if ($('#partition_operation_DROP').is(':checked')) { + var question = PMA_messages.strDropPartitionWarning; + $form.PMA_confirm(question, $form.attr('action'), function (url) { + submitPartitionMaintenance(); + }); + } else if ($('#partition_operation_TRUNCATE').is(':checked')) { + var question = PMA_messages.strTruncatePartitionWarning; + $form.PMA_confirm(question, $form.attr('action'), function (url) { + submitPartitionMaintenance(); + }); + } else { + submitPartitionMaintenance(); + } + }); + + $(document).on('click', '#drop_tbl_anchor.ajax', function (event) { + event.preventDefault(); + var $link = $(this); + /** + * @var question String containing the question to be asked for confirmation + */ + var question = PMA_messages.strDropTableStrongWarning + ' '; + question += PMA_sprintf( + PMA_messages.strDoYouReally, + 'DROP TABLE `' + escapeHtml(PMA_commonParams.get('db')) + '`.`' + escapeHtml(PMA_commonParams.get('table') + '`') + ) + getForeignKeyCheckboxLoader(); + + $(this).PMA_confirm(question, $(this).attr('href'), function (url) { + var $msgbox = PMA_ajaxShowMessage(PMA_messages.strProcessingRequest); + + var params = getJSConfirmCommonParam(this, $link.getPostData()); + + $.post(url, params, function (data) { + if (typeof data !== 'undefined' && data.success === true) { + PMA_ajaxRemoveMessage($msgbox); + // Table deleted successfully, refresh both the frames + PMA_reloadNavigation(); + PMA_commonParams.set('table', ''); + PMA_commonActions.refreshMain( + PMA_commonParams.get('opendb_url'), + function () { + PMA_ajaxShowMessage(data.message); + } + ); + } else { + PMA_ajaxShowMessage(data.error, false); + } + }); // end $.post() + }, loadForeignKeyCheckbox); // end $.PMA_confirm() + }); // end of Drop Table Ajax action + + $(document).on('click', '#drop_view_anchor.ajax', function (event) { + event.preventDefault(); + var $link = $(this); + /** + * @var question String containing the question to be asked for confirmation + */ + var question = PMA_messages.strDropTableStrongWarning + ' '; + question += PMA_sprintf( + PMA_messages.strDoYouReally, + 'DROP VIEW `' + escapeHtml(PMA_commonParams.get('table') + '`') + ); + + $(this).PMA_confirm(question, $(this).attr('href'), function (url) { + var $msgbox = PMA_ajaxShowMessage(PMA_messages.strProcessingRequest); + var params = getJSConfirmCommonParam(this, $link.getPostData()); + $.post(url, params, function (data) { + if (typeof data !== 'undefined' && data.success === true) { + PMA_ajaxRemoveMessage($msgbox); + // Table deleted successfully, refresh both the frames + PMA_reloadNavigation(); + PMA_commonParams.set('table', ''); + PMA_commonActions.refreshMain( + PMA_commonParams.get('opendb_url'), + function () { + PMA_ajaxShowMessage(data.message); + } + ); + } else { + PMA_ajaxShowMessage(data.error, false); + } + }); // end $.post() + }); // end $.PMA_confirm() + }); // end of Drop View Ajax action + + $(document).on('click', '#truncate_tbl_anchor.ajax', function (event) { + event.preventDefault(); + var $link = $(this); + /** + * @var question String containing the question to be asked for confirmation + */ + var question = PMA_messages.strTruncateTableStrongWarning + ' '; + question += PMA_sprintf( + PMA_messages.strDoYouReally, + 'TRUNCATE `' + escapeHtml(PMA_commonParams.get('db')) + '`.`' + escapeHtml(PMA_commonParams.get('table') + '`') + ) + getForeignKeyCheckboxLoader(); + $(this).PMA_confirm(question, $(this).attr('href'), function (url) { + PMA_ajaxShowMessage(PMA_messages.strProcessingRequest); + + var params = getJSConfirmCommonParam(this, $link.getPostData()); + + $.post(url, params, function (data) { + if ($('.sqlqueryresults').length !== 0) { + $('.sqlqueryresults').remove(); + } + if ($('.result_query').length !== 0) { + $('.result_query').remove(); + } + if (typeof data !== 'undefined' && data.success === true) { + PMA_ajaxShowMessage(data.message); + $('
      ').prependTo('#page_content'); + $('.sqlqueryresults').html(data.sql_query); + PMA_highlightSQL($('#page_content')); + } else { + PMA_ajaxShowMessage(data.error, false); + } + }); // end $.post() + }, loadForeignKeyCheckbox); // end $.PMA_confirm() + }); // end of Truncate Table Ajax action +}); // end $(document).ready for 'Table operations' diff --git a/php/apps/phpmyadmin49/html/js/tbl_relation.js b/php/apps/phpmyadmin49/html/js/tbl_relation.js new file mode 100644 index 00000000..ffc0aff4 --- /dev/null +++ b/php/apps/phpmyadmin49/html/js/tbl_relation.js @@ -0,0 +1,243 @@ +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * for tbl_relation.php + * + */ +function show_hide_clauses ($thisDropdown) { + if ($thisDropdown.val() === '') { + $thisDropdown.parent().nextAll('span').hide(); + } else { + if ($thisDropdown.is('select[name^="destination_foreign_column"]')) { + $thisDropdown.parent().nextAll('span').show(); + } + } +} + +/** + * Sets dropdown options to values + */ +function setDropdownValues ($dropdown, values, selectedValue) { + $dropdown.empty(); + var optionsAsString = ''; + // add an empty string to the beginning for empty selection + values.unshift(''); + $.each(values, function () { + optionsAsString += ''; + }); + $dropdown.append($(optionsAsString)); +} + +/** + * Retrieves and populates dropdowns to the left based on the selected value + * + * @param $dropdown the dropdown whose value got changed + */ +function getDropdownValues ($dropdown) { + var foreignDb = null; + var foreignTable = null; + var $databaseDd; + var $tableDd; + var $columnDd; + var foreign = ''; + // if the changed dropdown is for foreign key constraints + if ($dropdown.is('select[name^="destination_foreign"]')) { + $databaseDd = $dropdown.parent().parent().parent().find('select[name^="destination_foreign_db"]'); + $tableDd = $dropdown.parent().parent().parent().find('select[name^="destination_foreign_table"]'); + $columnDd = $dropdown.parent().parent().parent().find('select[name^="destination_foreign_column"]'); + foreign = '_foreign'; + } else { // internal relations + $databaseDd = $dropdown.parent().find('select[name^="destination_db"]'); + $tableDd = $dropdown.parent().find('select[name^="destination_table"]'); + $columnDd = $dropdown.parent().find('select[name^="destination_column"]'); + } + + // if the changed dropdown is a database selector + if ($dropdown.is('select[name^="destination' + foreign + '_db"]')) { + foreignDb = $dropdown.val(); + // if no database is selected empty table and column dropdowns + if (foreignDb === '') { + setDropdownValues($tableDd, []); + setDropdownValues($columnDd, []); + return; + } + } else { // if a table selector + foreignDb = $databaseDd.val(); + foreignTable = $dropdown.val(); + // if no table is selected empty the column dropdown + if (foreignTable === '') { + setDropdownValues($columnDd, []); + return; + } + } + var $msgbox = PMA_ajaxShowMessage(); + var $form = $dropdown.parents('form'); + var argsep = PMA_commonParams.get('arg_separator'); + var params = 'getDropdownValues=true' + argsep + 'ajax_request=true' + + argsep + 'db=' + $form.find('input[name="db"]').val() + + argsep + 'table=' + $form.find('input[name="table"]').val() + + argsep + 'foreign=' + (foreign !== '') + + argsep + 'foreignDb=' + encodeURIComponent(foreignDb) + + (foreignTable !== null ? + argsep + 'foreignTable=' + encodeURIComponent(foreignTable) : '' + ); + var $server = $form.find('input[name="server"]'); + if ($server.length > 0) { + params += argsep + 'server=' + $form.find('input[name="server"]').val(); + } + $.ajax({ + type: 'POST', + url: 'tbl_relation.php', + data: params, + dataType: 'json', + success: function (data) { + PMA_ajaxRemoveMessage($msgbox); + if (typeof data !== 'undefined' && data.success) { + // if the changed dropdown is a database selector + if (foreignTable === null) { + // set values for table and column dropdowns + setDropdownValues($tableDd, data.tables); + setDropdownValues($columnDd, []); + } else { // if a table selector + // set values for the column dropdown + var primary = null; + if (typeof data.primary !== 'undefined' + && 1 === data.primary.length + ) { + primary = data.primary[0]; + } + setDropdownValues($columnDd.first(), data.columns, primary); + setDropdownValues($columnDd.slice(1), data.columns); + } + } else { + PMA_ajaxShowMessage(data.error, false); + } + } + }); +} + +/** + * Unbind all event handlers before tearing down a page + */ +AJAX.registerTeardown('tbl_relation.js', function () { + $('body').off('change', + 'select[name^="destination_db"], ' + + 'select[name^="destination_table"], ' + + 'select[name^="destination_foreign_db"], ' + + 'select[name^="destination_foreign_table"]' + ); + $('body').off('click', 'a.add_foreign_key_field'); + $('body').off('click', 'a.add_foreign_key'); + $('a.drop_foreign_key_anchor.ajax').off('click'); +}); + +AJAX.registerOnload('tbl_relation.js', function () { + /** + * Ajax event handler to fetch table/column dropdown values. + */ + $('body').on('change', + 'select[name^="destination_db"], ' + + 'select[name^="destination_table"], ' + + 'select[name^="destination_foreign_db"], ' + + 'select[name^="destination_foreign_table"]', + function () { + getDropdownValues($(this)); + } + ); + + /** + * Ajax event handler to add a column to a foreign key constraint. + */ + $('body').on('click', 'a.add_foreign_key_field', function (event) { + event.preventDefault(); + event.stopPropagation(); + + // Add field. + $(this) + .prev('span') + .clone(true, true) + .insertBefore($(this)) + .find('select') + .val(''); + + // Add foreign field. + var $source_elem = $('select[name^="destination_foreign_column[' + + $(this).attr('data-index') + ']"]:last').parent(); + $source_elem + .clone(true, true) + .insertAfter($source_elem) + .find('select') + .val(''); + }); + + /** + * Ajax event handler to add a foreign key constraint. + */ + $('body').on('click', 'a.add_foreign_key', function (event) { + event.preventDefault(); + event.stopPropagation(); + + var $prev_row = $(this).closest('tr').prev('tr'); + var $newRow = $prev_row.clone(true, true); + + // Update serial number. + var curr_index = $newRow + .find('a.add_foreign_key_field') + .attr('data-index'); + var new_index = parseInt(curr_index) + 1; + $newRow.find('a.add_foreign_key_field').attr('data-index', new_index); + + // Update form parameter names. + $newRow.find('select[name^="foreign_key_fields_name"]:not(:first), ' + + 'select[name^="destination_foreign_column"]:not(:first)' + ).each(function () { + $(this).parent().remove(); + }); + $newRow.find('input, select').each(function () { + $(this).attr('name', + $(this).attr('name').replace(/\d/, new_index) + ); + }); + $newRow.find('input[type="text"]').each(function () { + $(this).val(''); + }); + // Finally add the row. + $newRow.insertAfter($prev_row); + }); + + /** + * Ajax Event handler for 'Drop Foreign key' + */ + $('a.drop_foreign_key_anchor.ajax').on('click', function (event) { + event.preventDefault(); + var $anchor = $(this); + + // Object containing reference to the current field's row + var $curr_row = $anchor.parents('tr'); + + var drop_query = escapeHtml( + $curr_row.children('td') + .children('.drop_foreign_key_msg') + .val() + ); + + var question = PMA_sprintf(PMA_messages.strDoYouReally, drop_query); + + $anchor.PMA_confirm(question, $anchor.attr('href'), function (url) { + var $msg = PMA_ajaxShowMessage(PMA_messages.strDroppingForeignKey, false); + var params = getJSConfirmCommonParam(this, $anchor.getPostData()); + $.post(url, params, function (data) { + if (data.success === true) { + PMA_ajaxRemoveMessage($msg); + PMA_commonActions.refreshMain(false, function () { + // Do nothing + }); + } else { + PMA_ajaxShowMessage(PMA_messages.strErrorProcessingRequest + ' : ' + data.error, false); + } + }); // end $.post() + }); // end $.PMA_confirm() + }); // end Drop Foreign key + + var windowwidth = $(window).width(); + $('.jsresponsive').css('max-width', (windowwidth - 35) + 'px'); +}); diff --git a/php/apps/phpmyadmin49/html/js/tbl_select.js b/php/apps/phpmyadmin49/html/js/tbl_select.js new file mode 100644 index 00000000..1e44db69 --- /dev/null +++ b/php/apps/phpmyadmin49/html/js/tbl_select.js @@ -0,0 +1,413 @@ +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * @fileoverview JavaScript functions used on tbl_select.php + * + * @requires jQuery + * @requires js/functions.js + */ + +/** + * Ajax event handlers for this page + * + * Actions ajaxified here: + * Table search + */ + +/** + * Checks if given data-type is numeric or date. + * + * @param string data_type Column data-type + * + * @return bool|string + */ +function PMA_checkIfDataTypeNumericOrDate (data_type) { + // To test for numeric data-types. + var numeric_re = new RegExp( + 'TINYINT|SMALLINT|MEDIUMINT|INT|BIGINT|DECIMAL|FLOAT|DOUBLE|REAL', + 'i' + ); + + // To test for date data-types. + var date_re = new RegExp( + 'DATETIME|DATE|TIMESTAMP|TIME|YEAR', + 'i' + ); + + // Return matched data-type + if (numeric_re.test(data_type)) { + return numeric_re.exec(data_type)[0]; + } + + if (date_re.test(data_type)) { + return date_re.exec(data_type)[0]; + } + + return false; +} + +/** + * Unbind all event handlers before tearing down a page + */ +AJAX.registerTeardown('tbl_select.js', function () { + $('#togglesearchformlink').off('click'); + $(document).off('submit', '#tbl_search_form.ajax'); + $('select.geom_func').off('change'); + $(document).off('click', 'span.open_search_gis_editor'); + $('body').off('change', 'select[name*="criteriaColumnOperators"]'); // Fix for bug #13778, changed 'click' to 'change' +}); + +AJAX.registerOnload('tbl_select.js', function () { + /** + * Prepare a div containing a link, otherwise it's incorrectly displayed + * after a couple of clicks + */ + $('
      ') + .insertAfter('#tbl_search_form') + // don't show it until we have results on-screen + .hide(); + + $('#togglesearchformlink') + .html(PMA_messages.strShowSearchCriteria) + .on('click', function () { + var $link = $(this); + $('#tbl_search_form').slideToggle(); + if ($link.text() === PMA_messages.strHideSearchCriteria) { + $link.text(PMA_messages.strShowSearchCriteria); + } else { + $link.text(PMA_messages.strHideSearchCriteria); + } + // avoid default click action + return false; + }); + + var tableRows = $('#fieldset_table_qbe select'); + $.each(tableRows, function (index, item) { + $(item).on('change', function () { + changeValueFieldType(this, index); + }); + }); + + /** + * Ajax event handler for Table search + */ + $(document).on('submit', '#tbl_search_form.ajax', function (event) { + var unaryFunctions = [ + 'IS NULL', + 'IS NOT NULL', + '= \'\'', + '!= \'\'' + ]; + + var geomUnaryFunctions = [ + 'IsEmpty', + 'IsSimple', + 'IsRing', + 'IsClosed', + ]; + + // jQuery object to reuse + var $search_form = $(this); + event.preventDefault(); + + // empty previous search results while we are waiting for new results + $('#sqlqueryresultsouter').empty(); + var $msgbox = PMA_ajaxShowMessage(PMA_messages.strSearching, false); + + PMA_prepareForAjaxRequest($search_form); + + var values = {}; + $search_form.find(':input').each(function () { + var $input = $(this); + if ($input.attr('type') === 'checkbox' || $input.attr('type') === 'radio') { + if ($input.is(':checked')) { + values[this.name] = $input.val(); + } + } else { + values[this.name] = $input.val(); + } + }); + var columnCount = $('select[name="columnsToDisplay[]"] option').length; + // Submit values only for the columns that have unary column operator or a search criteria + for (var a = 0; a < columnCount; a++) { + if ($.inArray(values['criteriaColumnOperators[' + a + ']'], unaryFunctions) >= 0) { + continue; + } + + if (values['geom_func[' + a + ']'] && + $.isArray(values['geom_func[' + a + ']'], geomUnaryFunctions) >= 0) { + continue; + } + + if (values['criteriaValues[' + a + ']'] === '' || values['criteriaValues[' + a + ']'] === null) { + delete values['criteriaValues[' + a + ']']; + delete values['criteriaColumnOperators[' + a + ']']; + delete values['criteriaColumnNames[' + a + ']']; + delete values['criteriaColumnTypes[' + a + ']']; + delete values['criteriaColumnCollations[' + a + ']']; + } + } + // If all columns are selected, use a single parameter to indicate that + if (values['columnsToDisplay[]'] !== null) { + if (values['columnsToDisplay[]'].length === columnCount) { + delete values['columnsToDisplay[]']; + values.displayAllColumns = true; + } + } else { + values.displayAllColumns = true; + } + + $.post($search_form.attr('action'), values, function (data) { + PMA_ajaxRemoveMessage($msgbox); + if (typeof data !== 'undefined' && data.success === true) { + if (typeof data.sql_query !== 'undefined') { // zero rows + $('#sqlqueryresultsouter').html(data.sql_query); + } else { // results found + $('#sqlqueryresultsouter').html(data.message); + $('.sqlqueryresults').trigger('makegrid').trigger('stickycolumns'); + } + $('#tbl_search_form') + // workaround for bug #3168569 - Issue on toggling the "Hide search criteria" in chrome. + .slideToggle() + .hide(); + $('#togglesearchformlink') + // always start with the Show message + .text(PMA_messages.strShowSearchCriteria); + $('#togglesearchformdiv') + // now it's time to show the div containing the link + .show(); + // needed for the display options slider in the results + PMA_init_slider(); + $('html, body').animate({ scrollTop: 0 }, 'fast'); + } else { + $('#sqlqueryresultsouter').html(data.error); + } + PMA_highlightSQL($('#sqlqueryresultsouter')); + }); // end $.post() + }); + + // Following section is related to the 'function based search' for geometry data types. + // Initialy hide all the open_gis_editor spans + $('span.open_search_gis_editor').hide(); + + $('select.geom_func').bind('change', function () { + var $geomFuncSelector = $(this); + + var binaryFunctions = [ + 'Contains', + 'Crosses', + 'Disjoint', + 'Equals', + 'Intersects', + 'Overlaps', + 'Touches', + 'Within', + 'MBRContains', + 'MBRDisjoint', + 'MBREquals', + 'MBRIntersects', + 'MBROverlaps', + 'MBRTouches', + 'MBRWithin', + 'ST_Contains', + 'ST_Crosses', + 'ST_Disjoint', + 'ST_Equals', + 'ST_Intersects', + 'ST_Overlaps', + 'ST_Touches', + 'ST_Within' + ]; + + var tempArray = [ + 'Envelope', + 'EndPoint', + 'StartPoint', + 'ExteriorRing', + 'Centroid', + 'PointOnSurface' + ]; + var outputGeomFunctions = binaryFunctions.concat(tempArray); + + // If the chosen function takes two geometry objects as parameters + var $operator = $geomFuncSelector.parents('tr').find('td:nth-child(5)').find('select'); + if ($.inArray($geomFuncSelector.val(), binaryFunctions) >= 0) { + $operator.prop('readonly', true); + } else { + $operator.prop('readonly', false); + } + + // if the chosen function's output is a geometry, enable GIS editor + var $editorSpan = $geomFuncSelector.parents('tr').find('span.open_search_gis_editor'); + if ($.inArray($geomFuncSelector.val(), outputGeomFunctions) >= 0) { + $editorSpan.show(); + } else { + $editorSpan.hide(); + } + }); + + $(document).on('click', 'span.open_search_gis_editor', function (event) { + event.preventDefault(); + + var $span = $(this); + // Current value + var value = $span.parent('td').children('input[type=\'text\']').val(); + // Field name + var field = 'Parameter'; + // Column type + var geom_func = $span.parents('tr').find('.geom_func').val(); + var type; + if (geom_func === 'Envelope') { + type = 'polygon'; + } else if (geom_func === 'ExteriorRing') { + type = 'linestring'; + } else { + type = 'point'; + } + // Names of input field and null checkbox + var input_name = $span.parent('td').children('input[type=\'text\']').attr('name'); + // Token + + openGISEditor(); + if (!gisEditorLoaded) { + loadJSAndGISEditor(value, field, type, input_name); + } else { + loadGISEditor(value, field, type, input_name); + } + }); + + /** + * Ajax event handler for Range-Search. + */ + $('body').on('change', 'select[name*="criteriaColumnOperators"]', function () { // Fix for bug #13778, changed 'click' to 'change' + $source_select = $(this); + // Get the column name. + var column_name = $(this) + .closest('tr') + .find('th:first') + .text(); + + // Get the data-type of column excluding size. + var data_type = $(this) + .closest('tr') + .find('td[data-type]') + .attr('data-type'); + data_type = PMA_checkIfDataTypeNumericOrDate(data_type); + + // Get the operator. + var operator = $(this).val(); + + if ((operator === 'BETWEEN' || operator === 'NOT BETWEEN') + && data_type + ) { + var $msgbox = PMA_ajaxShowMessage(); + $.ajax({ + url: 'tbl_select.php', + type: 'POST', + data: { + server: PMA_commonParams.get('server'), + ajax_request: 1, + db: $('input[name="db"]').val(), + table: $('input[name="table"]').val(), + column: column_name, + range_search: 1 + }, + success: function (response) { + PMA_ajaxRemoveMessage($msgbox); + if (response.success) { + // Get the column min value. + var min = response.column_data.min + ? '(' + PMA_messages.strColumnMin + + ' ' + response.column_data.min + ')' + : ''; + // Get the column max value. + var max = response.column_data.max + ? '(' + PMA_messages.strColumnMax + + ' ' + response.column_data.max + ')' + : ''; + var button_options = {}; + button_options[PMA_messages.strGo] = function () { + var min_value = $('#min_value').val(); + var max_value = $('#max_value').val(); + var final_value = ''; + if (min_value.length && max_value.length) { + final_value = min_value + ', ' + + max_value; + } + var $target_field = $source_select.closest('tr') + .find('[name*="criteriaValues"]'); + + // If target field is a select list. + if ($target_field.is('select')) { + $target_field.val(final_value); + var $options = $target_field.find('option'); + var $closest_min = null; + var $closest_max = null; + // Find closest min and max value. + $options.each(function () { + if ( + $closest_min === null + || Math.abs($(this).val() - min_value) < Math.abs($closest_min.val() - min_value) + ) { + $closest_min = $(this); + } + + if ( + $closest_max === null + || Math.abs($(this).val() - max_value) < Math.abs($closest_max.val() - max_value) + ) { + $closest_max = $(this); + } + }); + + $closest_min.attr('selected', 'selected'); + $closest_max.attr('selected', 'selected'); + } else { + $target_field.val(final_value); + } + $(this).dialog('close'); + }; + button_options[PMA_messages.strCancel] = function () { + $(this).dialog('close'); + }; + + // Display dialog box. + $('
      ').append( + '
      ' + + '' + operator + '' + + '' + + '' + '
      ' + + '' + min + '' + '
      ' + + '' + + '' + '
      ' + + '' + max + '' + + '
      ' + ).dialog({ + minWidth: 500, + maxHeight: 400, + modal: true, + buttons: button_options, + title: PMA_messages.strRangeSearch, + open: function () { + // Add datepicker wherever required. + PMA_addDatepicker($('#min_value'), data_type); + PMA_addDatepicker($('#max_value'), data_type); + }, + close: function () { + $(this).remove(); + } + }); + } else { + PMA_ajaxShowMessage(response.error); + } + }, + error: function (response) { + PMA_ajaxShowMessage(PMA_messages.strErrorProcessingRequest); + } + }); + } + }); + var windowwidth = $(window).width(); + $('.jsresponsive').css('max-width', (windowwidth - 69) + 'px'); +}); diff --git a/php/apps/phpmyadmin49/html/js/tbl_structure.js b/php/apps/phpmyadmin49/html/js/tbl_structure.js new file mode 100644 index 00000000..3f59c245 --- /dev/null +++ b/php/apps/phpmyadmin49/html/js/tbl_structure.js @@ -0,0 +1,500 @@ +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * @fileoverview functions used on the table structure page + * @name Table Structure + * + * @requires jQuery + * @requires jQueryUI + * @required js/functions.js + */ + +/** + * AJAX scripts for tbl_structure.php + * + * Actions ajaxified here: + * Drop Column + * Add Primary Key + * Drop Primary Key/Index + * + */ + +/** + * Reload fields table + */ +function reloadFieldForm () { + $.post($('#fieldsForm').attr('action'), $('#fieldsForm').serialize() + PMA_commonParams.get('arg_separator') + 'ajax_request=true', function (form_data) { + var $temp_div = $('
      ').append(form_data.message); + $('#fieldsForm').replaceWith($temp_div.find('#fieldsForm')); + $('#addColumns').replaceWith($temp_div.find('#addColumns')); + $('#move_columns_dialog').find('ul').replaceWith($temp_div.find('#move_columns_dialog ul')); + $('#moveColumns').removeClass('move-active'); + }); + $('#page_content').show(); +} + +function checkFirst () { + if ($('select[name=after_field] option:selected').data('pos') === 'first') { + $('input[name=field_where]').val('first'); + } else { + $('input[name=field_where]').val('after'); + } +} +/** + * Unbind all event handlers before tearing down a page + */ +AJAX.registerTeardown('tbl_structure.js', function () { + $(document).off('click', 'a.drop_column_anchor.ajax'); + $(document).off('click', 'a.add_key.ajax'); + $(document).off('click', '#move_columns_anchor'); + $(document).off('click', '#printView'); + $(document).off('submit', '.append_fields_form.ajax'); + $('body').off('click', '#fieldsForm.ajax button[name="submit_mult"], #fieldsForm.ajax input[name="submit_mult"]'); + $(document).off('click', 'a[name^=partition_action].ajax'); + $(document).off('click', '#remove_partitioning.ajax'); +}); + +AJAX.registerOnload('tbl_structure.js', function () { + // Re-initialize variables. + primary_indexes = []; + indexes = []; + fulltext_indexes = []; + spatial_indexes = []; + + /** + *Ajax action for submitting the "Column Change" and "Add Column" form + */ + $('.append_fields_form.ajax').off(); + $(document).on('submit', '.append_fields_form.ajax', function (event) { + event.preventDefault(); + /** + * @var the_form object referring to the export form + */ + var $form = $(this); + var field_cnt = $form.find('input[name=orig_num_fields]').val(); + + + function submitForm () { + $msg = PMA_ajaxShowMessage(PMA_messages.strProcessingRequest); + $.post($form.attr('action'), $form.serialize() + PMA_commonParams.get('arg_separator') + 'do_save_data=1', function (data) { + if ($('.sqlqueryresults').length !== 0) { + $('.sqlqueryresults').remove(); + } else if ($('.error:not(.tab)').length !== 0) { + $('.error:not(.tab)').remove(); + } + if (typeof data.success !== 'undefined' && data.success === true) { + $('#page_content') + .empty() + .append(data.message) + .show(); + PMA_highlightSQL($('#page_content')); + $('.result_query .notice').remove(); + reloadFieldForm(); + $form.remove(); + PMA_ajaxRemoveMessage($msg); + PMA_init_slider(); + PMA_reloadNavigation(); + } else { + PMA_ajaxShowMessage(data.error, false); + } + }); // end $.post() + } + + function checkIfConfirmRequired ($form, $field_cnt) { + var i = 0; + var id; + var elm; + var val; + var name_orig; + var elm_orig; + var val_orig; + var checkRequired = false; + for (i = 0; i < field_cnt; i++) { + id = '#field_' + i + '_5'; + elm = $(id); + val = elm.val(); + + name_orig = 'input[name=field_collation_orig\\[' + i + '\\]]'; + elm_orig = $form.find(name_orig); + val_orig = elm_orig.val(); + + if (val && val_orig && val !== val_orig) { + checkRequired = true; + break; + } + } + return checkRequired; + } + + /* + * First validate the form; if there is a problem, avoid submitting it + * + * checkTableEditForm() needs a pure element and not a jQuery object, + * this is why we pass $form[0] as a parameter (the jQuery object + * is actually an array of DOM elements) + */ + if (checkTableEditForm($form[0], field_cnt)) { + // OK, form passed validation step + + PMA_prepareForAjaxRequest($form); + if (PMA_checkReservedWordColumns($form)) { + // User wants to submit the form + + // If Collation is changed, Warn and Confirm + if (checkIfConfirmRequired($form, field_cnt)) { + var question = sprintf( + PMA_messages.strChangeColumnCollation, 'https://wiki.phpmyadmin.net/pma/Garbled_data' + ); + $form.PMA_confirm(question, $form.attr('action'), function (url) { + submitForm(); + }); + } else { + submitForm(); + } + } + } + }); // end change table button "do_save_data" + + /** + * Attach Event Handler for 'Drop Column' + */ + $(document).on('click', 'a.drop_column_anchor.ajax', function (event) { + event.preventDefault(); + /** + * @var curr_table_name String containing the name of the current table + */ + var curr_table_name = $(this).closest('form').find('input[name=table]').val(); + /** + * @var curr_row Object reference to the currently selected row (i.e. field in the table) + */ + var $curr_row = $(this).parents('tr'); + /** + * @var curr_column_name String containing name of the field referred to by {@link curr_row} + */ + var curr_column_name = $curr_row.children('th').children('label').text().trim(); + curr_column_name = escapeHtml(curr_column_name); + /** + * @var $after_field_item Corresponding entry in the 'After' field. + */ + var $after_field_item = $('select[name=\'after_field\'] option[value=\'' + curr_column_name + '\']'); + /** + * @var question String containing the question to be asked for confirmation + */ + var question = PMA_sprintf(PMA_messages.strDoYouReally, 'ALTER TABLE `' + escapeHtml(curr_table_name) + '` DROP `' + escapeHtml(curr_column_name) + '`;'); + var $this_anchor = $(this); + $this_anchor.PMA_confirm(question, $this_anchor.attr('href'), function (url) { + var $msg = PMA_ajaxShowMessage(PMA_messages.strDroppingColumn, false); + var params = getJSConfirmCommonParam(this, $this_anchor.getPostData()); + params += PMA_commonParams.get('arg_separator') + 'ajax_page_request=1'; + $.post(url, params, function (data) { + if (typeof data !== 'undefined' && data.success === true) { + PMA_ajaxRemoveMessage($msg); + if ($('.result_query').length) { + $('.result_query').remove(); + } + if (data.sql_query) { + $('
      ') + .html(data.sql_query) + .prependTo('#structure_content'); + PMA_highlightSQL($('#page_content')); + } + // Adjust the row numbers + for (var $row = $curr_row.next(); $row.length > 0; $row = $row.next()) { + var new_val = parseInt($row.find('td:nth-child(2)').text(), 10) - 1; + $row.find('td:nth-child(2)').text(new_val); + } + $after_field_item.remove(); + $curr_row.hide('medium').remove(); + + // Remove the dropped column from select menu for 'after field' + $('select[name=after_field]').find( + '[value="' + curr_column_name + '"]' + ).remove(); + + // by default select the (new) last option to add new column + // (in case last column is dropped) + $('select[name=after_field] option:last').attr('selected','selected'); + + // refresh table stats + if (data.tableStat) { + $('#tablestatistics').html(data.tableStat); + } + // refresh the list of indexes (comes from sql.php) + $('.index_info').replaceWith(data.indexes_list); + PMA_reloadNavigation(); + } else { + PMA_ajaxShowMessage(PMA_messages.strErrorProcessingRequest + ' : ' + data.error, false); + } + }); // end $.post() + }); // end $.PMA_confirm() + }); // end of Drop Column Anchor action + + /** + * Attach Event Handler for 'Print' link + */ + $(document).on('click', '#printView', function (event) { + event.preventDefault(); + + // Take to preview mode + printPreview(); + }); // end of Print View action + + /** + * Ajax Event handler for adding keys + */ + $(document).on('click', 'a.add_key.ajax', function (event) { + event.preventDefault(); + + var $this = $(this); + var curr_table_name = $this.closest('form').find('input[name=table]').val(); + var curr_column_name = $this.parents('tr').children('th').children('label').text().trim(); + + var add_clause = ''; + if ($this.is('.add_primary_key_anchor')) { + add_clause = 'ADD PRIMARY KEY'; + } else if ($this.is('.add_index_anchor')) { + add_clause = 'ADD INDEX'; + } else if ($this.is('.add_unique_anchor')) { + add_clause = 'ADD UNIQUE'; + } else if ($this.is('.add_spatial_anchor')) { + add_clause = 'ADD SPATIAL'; + } else if ($this.is('.add_fulltext_anchor')) { + add_clause = 'ADD FULLTEXT'; + } + var question = PMA_sprintf(PMA_messages.strDoYouReally, 'ALTER TABLE `' + + escapeHtml(curr_table_name) + '` ' + add_clause + '(`' + escapeHtml(curr_column_name) + '`);'); + + var $this_anchor = $(this); + + $this_anchor.PMA_confirm(question, $this_anchor.attr('href'), function (url) { + PMA_ajaxShowMessage(); + AJAX.source = $this; + + var params = getJSConfirmCommonParam(this, $this_anchor.getPostData()); + params += PMA_commonParams.get('arg_separator') + 'ajax_page_request=1'; + $.post(url, params, AJAX.responseHandler); + }); // end $.PMA_confirm() + }); // end Add key + + /** + * Inline move columns + **/ + $(document).on('click', '#move_columns_anchor', function (e) { + e.preventDefault(); + + if ($(this).hasClass('move-active')) { + return; + } + + /** + * @var button_options Object that stores the options passed to jQueryUI + * dialog + */ + var button_options = {}; + + button_options[PMA_messages.strGo] = function (event) { + event.preventDefault(); + var $msgbox = PMA_ajaxShowMessage(); + var $this = $(this); + var $form = $this.find('form'); + var serialized = $form.serialize(); + + // check if any columns were moved at all + if (serialized === $form.data('serialized-unmoved')) { + PMA_ajaxRemoveMessage($msgbox); + $this.dialog('close'); + return; + } + + $.post($form.prop('action'), serialized + PMA_commonParams.get('arg_separator') + 'ajax_request=true', function (data) { + if (data.success === false) { + PMA_ajaxRemoveMessage($msgbox); + $this + .clone() + .html(data.error) + .dialog({ + title: $(this).prop('title'), + height: 230, + width: 900, + modal: true, + buttons: button_options_error + }); // end dialog options + } else { + // sort the fields table + var $fields_table = $('table#tablestructure tbody'); + // remove all existing rows and remember them + var $rows = $fields_table.find('tr').remove(); + // loop through the correct order + for (var i in data.columns) { + var the_column = data.columns[i]; + var $the_row = $rows + .find('input:checkbox[value=\'' + the_column + '\']') + .closest('tr'); + // append the row for this column to the table + $fields_table.append($the_row); + } + var $firstrow = $fields_table.find('tr').eq(0); + // Adjust the row numbers and colors + for (var $row = $firstrow; $row.length > 0; $row = $row.next()) { + $row + .find('td:nth-child(2)') + .text($row.index() + 1) + .end() + .removeClass('odd even') + .addClass($row.index() % 2 === 0 ? 'odd' : 'even'); + } + PMA_ajaxShowMessage(data.message); + $this.dialog('close'); + } + }); + }; + button_options[PMA_messages.strCancel] = function () { + $(this).dialog('close'); + }; + + var button_options_error = {}; + button_options_error[PMA_messages.strOK] = function () { + $(this).dialog('close').remove(); + }; + + var columns = []; + + $('#tablestructure').find('tbody tr').each(function () { + var col_name = $(this).find('input:checkbox').eq(0).val(); + var hidden_input = $('') + .prop({ + name: 'move_columns[]', + type: 'hidden' + }) + .val(col_name); + columns[columns.length] = $('
    • ') + .addClass('placeholderDrag') + .text(col_name) + .append(hidden_input); + }); + + var col_list = $('#move_columns_dialog').find('ul') + .find('li').remove().end(); + for (var i in columns) { + col_list.append(columns[i]); + } + col_list.sortable({ + axis: 'y', + containment: $('#move_columns_dialog').find('div'), + tolerance: 'pointer' + }).disableSelection(); + var $form = $('#move_columns_dialog').find('form'); + $form.data('serialized-unmoved', $form.serialize()); + + $('#move_columns_dialog').dialog({ + modal: true, + buttons: button_options, + open: function () { + if ($('#move_columns_dialog').parents('.ui-dialog').height() > $(window).height()) { + $('#move_columns_dialog').dialog('option', 'height', $(window).height()); + } + }, + beforeClose: function () { + $('#move_columns_anchor').removeClass('move-active'); + } + }); + }); + + /** + * Handles multi submits in table structure page such as change, browse, drop, primary etc. + */ + $('body').on('click', '#fieldsForm.ajax button[name="submit_mult"], #fieldsForm.ajax input[name="submit_mult"]', function (e) { + e.preventDefault(); + var $button = $(this); + var $form = $button.parents('form'); + var argsep = PMA_commonParams.get('arg_separator'); + var submitData = $form.serialize() + argsep + 'ajax_request=true' + argsep + 'ajax_page_request=true' + argsep + 'submit_mult=' + $button.val(); + PMA_ajaxShowMessage(); + AJAX.source = $form; + $.post($form.attr('action'), submitData, AJAX.responseHandler); + }); + + /** + * Handles clicks on Action links in partition table + */ + $(document).on('click', 'a[name^=partition_action].ajax', function (e) { + e.preventDefault(); + var $link = $(this); + + function submitPartitionAction (url) { + var params = 'ajax_request=true&ajax_page_request=true&' + $link.getPostData(); + PMA_ajaxShowMessage(); + AJAX.source = $link; + $.post(url, params, AJAX.responseHandler); + } + + if ($link.is('#partition_action_DROP')) { + var question = PMA_messages.strDropPartitionWarning; + $link.PMA_confirm(question, $link.attr('href'), function (url) { + submitPartitionAction(url); + }); + } else if ($link.is('#partition_action_TRUNCATE')) { + var question = PMA_messages.strTruncatePartitionWarning; + $link.PMA_confirm(question, $link.attr('href'), function (url) { + submitPartitionAction(url); + }); + } else { + submitPartitionAction($link.attr('href')); + } + }); + + /** + * Handles remove partitioning + */ + $(document).on('click', '#remove_partitioning.ajax', function (e) { + e.preventDefault(); + var $link = $(this); + var question = PMA_messages.strRemovePartitioningWarning; + $link.PMA_confirm(question, $link.attr('href'), function (url) { + var params = getJSConfirmCommonParam({ + 'ajax_request' : true, + 'ajax_page_request' : true + }, $link.getPostData()); + PMA_ajaxShowMessage(); + AJAX.source = $link; + $.post(url, params, AJAX.responseHandler); + }); + }); + + $(document).on('change', 'select[name=after_field]', function () { + checkFirst(); + }); +}); + +/** Handler for "More" dropdown in structure table rows */ +AJAX.registerOnload('tbl_structure.js', function () { + var windowwidth = $(window).width(); + if (windowwidth > 768) { + if (! $('#fieldsForm').hasClass('HideStructureActions')) { + $('.table-structure-actions').width(function () { + var width = 5; + $(this).find('li').each(function () { + width += $(this).outerWidth(true); + }); + return width; + }); + } + } + + $('.jsresponsive').css('max-width', (windowwidth - 35) + 'px'); + var tableRows = $('.central_columns'); + $.each(tableRows, function (index, item) { + if ($(item).hasClass('add_button')) { + $(item).click(function () { + $('input:checkbox').prop('checked', false); + $('#checkbox_row_' + (index + 1)).prop('checked', true); + $('button[value=add_to_central_columns]').click(); + }); + } else { + $(item).click(function () { + $('input:checkbox').prop('checked', false); + $('#checkbox_row_' + (index + 1)).prop('checked', true); + $('button[value=remove_from_central_columns]').click(); + }); + } + }); +}); diff --git a/php/apps/phpmyadmin49/html/js/tbl_tracking.js b/php/apps/phpmyadmin49/html/js/tbl_tracking.js new file mode 100644 index 00000000..cd089573 --- /dev/null +++ b/php/apps/phpmyadmin49/html/js/tbl_tracking.js @@ -0,0 +1,106 @@ +/** + * Unbind all event handlers before tearing down the page + */ +AJAX.registerTeardown('tbl_tracking.js', function () { + $('body').off('click', '#versionsForm.ajax button[name="submit_mult"], #versionsForm.ajax input[name="submit_mult"]'); + $('body').off('click', 'a.delete_version_anchor.ajax'); + $('body').off('click', 'a.delete_entry_anchor.ajax'); +}); + +/** + * Bind event handlers + */ +AJAX.registerOnload('tbl_tracking.js', function () { + $('#versions tr:first th').append($('
      ')); + $('#versions').tablesorter({ + sortList: [[1, 0]], + headers: { + 0: { sorter: false }, + 1: { sorter: 'integer' }, + 5: { sorter: false }, + 6: { sorter: false } + } + }); + + if ($('#ddl_versions tbody tr').length > 0) { + $('#ddl_versions tr:first th').append($('
      ')); + $('#ddl_versions').tablesorter({ + sortList: [[0, 0]], + headers: { + 0: { sorter: 'integer' }, + 3: { sorter: false }, + 4: { sorter: false } + } + }); + } + + if ($('#dml_versions tbody tr').length > 0) { + $('#dml_versions tr:first th').append($('
      ')); + $('#dml_versions').tablesorter({ + sortList: [[0, 0]], + headers: { + 0: { sorter: 'integer' }, + 3: { sorter: false }, + 4: { sorter: false } + } + }); + } + + /** + * Handles multi submit for tracking versions + */ + $('body').on('click', '#versionsForm.ajax button[name="submit_mult"], #versionsForm.ajax input[name="submit_mult"]', function (e) { + e.preventDefault(); + var $button = $(this); + var $form = $button.parent('form'); + var argsep = PMA_commonParams.get('arg_separator'); + var submitData = $form.serialize() + argsep + 'ajax_request=true' + argsep + 'ajax_page_request=true' + argsep + 'submit_mult=' + $button.val(); + + if ($button.val() === 'delete_version') { + var question = PMA_messages.strDeleteTrackingVersionMultiple; + $button.PMA_confirm(question, $form.attr('action'), function (url) { + PMA_ajaxShowMessage(); + AJAX.source = $form; + $.post(url, submitData, AJAX.responseHandler); + }); + } else { + PMA_ajaxShowMessage(); + AJAX.source = $form; + $.post($form.attr('action'), submitData, AJAX.responseHandler); + } + }); + + /** + * Ajax Event handler for 'Delete version' + */ + $('body').on('click', 'a.delete_version_anchor.ajax', function (e) { + e.preventDefault(); + var $anchor = $(this); + var question = PMA_messages.strDeleteTrackingVersion; + $anchor.PMA_confirm(question, $anchor.attr('href'), function (url) { + PMA_ajaxShowMessage(); + AJAX.source = $anchor; + var argSep = PMA_commonParams.get('arg_separator'); + var params = getJSConfirmCommonParam(this, $anchor.getPostData()); + params += argSep + 'ajax_page_request=1'; + $.post(url, params, AJAX.responseHandler); + }); + }); + + /** + * Ajax Event handler for 'Delete tracking report entry' + */ + $('body').on('click', 'a.delete_entry_anchor.ajax', function (e) { + e.preventDefault(); + var $anchor = $(this); + var question = PMA_messages.strDeletingTrackingEntry; + $anchor.PMA_confirm(question, $anchor.attr('href'), function (url) { + PMA_ajaxShowMessage(); + AJAX.source = $anchor; + var argSep = PMA_commonParams.get('arg_separator'); + var params = getJSConfirmCommonParam(this, $anchor.getPostData()); + params += argSep + 'ajax_page_request=1'; + $.post(url, params, AJAX.responseHandler); + }); + }); +}); diff --git a/php/apps/phpmyadmin49/html/js/tbl_zoom_plot_jqplot.js b/php/apps/phpmyadmin49/html/js/tbl_zoom_plot_jqplot.js new file mode 100644 index 00000000..897caed6 --- /dev/null +++ b/php/apps/phpmyadmin49/html/js/tbl_zoom_plot_jqplot.js @@ -0,0 +1,628 @@ +// TODO: change the axis +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + ** @fileoverview JavaScript functions used on tbl_select.php + ** + ** @requires jQuery + ** @requires js/functions.js + **/ + + +/** + ** Display Help/Info + **/ +function displayHelp () { + $('
      ') + .append(PMA_messages.strDisplayHelp) + .appendTo('#page_content') + .dialog({ + width: 450, + height: 'auto', + title: PMA_messages.strHelpTitle + }); + return false; +} + +/** + ** Extend the array object for max function + ** @param array + **/ +Array.max = function (array) { + return Math.max.apply(Math, array); +}; + +/** + ** Extend the array object for min function + ** @param array + **/ +Array.min = function (array) { + return Math.min.apply(Math, array); +}; + +/** + ** Checks if a string contains only numeric value + ** @param n: String (to be checked) + **/ +function isNumeric (n) { + return !isNaN(parseFloat(n)) && isFinite(n); +} + +/** + ** Checks if an object is empty + ** @param n: Object (to be checked) + **/ +function isEmpty (obj) { + var name; + for (name in obj) { + return false; + } + return true; +} + +/** + ** Converts a date/time into timestamp + ** @param val String Date + ** @param type Sring Field type(datetime/timestamp/time/date) + **/ +function getTimeStamp (val, type) { + if (type.toString().search(/datetime/i) !== -1 || + type.toString().search(/timestamp/i) !== -1 + ) { + return $.datepicker.parseDateTime('yy-mm-dd', 'HH:mm:ss', val); + } else if (type.toString().search(/time/i) !== -1) { + return $.datepicker.parseDateTime('yy-mm-dd', 'HH:mm:ss', '1970-01-01 ' + val); + } else if (type.toString().search(/date/i) !== -1) { + return $.datepicker.parseDate('yy-mm-dd', val); + } +} + +/** + ** Classifies the field type into numeric,timeseries or text + ** @param field: field type (as in database structure) + **/ +function getType (field) { + if (field.toString().search(/int/i) !== -1 || + field.toString().search(/decimal/i) !== -1 || + field.toString().search(/year/i) !== -1 + ) { + return 'numeric'; + } else if (field.toString().search(/time/i) !== -1 || + field.toString().search(/date/i) !== -1 + ) { + return 'time'; + } else { + return 'text'; + } +} + +/** + ** Scrolls the view to the display section + **/ +function scrollToChart () { + var x = $('#dataDisplay').offset().top - 100; // 100 provides buffer in viewport + $('html,body').animate({ scrollTop: x }, 500); +} + +/** + * Unbind all event handlers before tearing down a page + */ +AJAX.registerTeardown('tbl_zoom_plot_jqplot.js', function () { + $('#tableid_0').off('change'); + $('#tableid_1').off('change'); + $('#tableid_2').off('change'); + $('#tableid_3').off('change'); + $('#inputFormSubmitId').off('click'); + $('#togglesearchformlink').off('click'); + $(document).off('keydown', '#dataDisplay :input'); + $('button.button-reset').off('click'); + $('div#resizer').off('resizestop'); + $('div#querychart').off('jqplotDataClick'); +}); + +AJAX.registerOnload('tbl_zoom_plot_jqplot.js', function () { + var cursorMode = ($('input[name=\'mode\']:checked').val() === 'edit') ? 'crosshair' : 'pointer'; + var currentChart = null; + var searchedDataKey = null; + var xLabel = $('#tableid_0').val(); + var yLabel = $('#tableid_1').val(); + // will be updated via Ajax + var xType = $('#types_0').val(); + var yType = $('#types_1').val(); + var dataLabel = $('#dataLabel').val(); + var lastX; + var lastY; + var zoomRatio = 1; + + + // Get query result + var searchedData; + try { + searchedData = JSON.parse($('#querydata').html()); + } catch (err) { + searchedData = null; + } + + /** + ** Input form submit on field change + **/ + + // first column choice corresponds to the X axis + $('#tableid_0').change(function () { + // AJAX request for field type, collation, operators, and value field + $.post('tbl_zoom_select.php', { + 'ajax_request' : true, + 'change_tbl_info' : true, + 'server' : PMA_commonParams.get('server'), + 'db' : PMA_commonParams.get('db'), + 'table' : PMA_commonParams.get('table'), + 'field' : $('#tableid_0').val(), + 'it' : 0 + }, function (data) { + $('#tableFieldsId').find('tr:eq(1) td:eq(0)').html(data.field_type); + $('#tableFieldsId').find('tr:eq(1) td:eq(1)').html(data.field_collation); + $('#tableFieldsId').find('tr:eq(1) td:eq(2)').html(data.field_operators); + $('#tableFieldsId').find('tr:eq(1) td:eq(3)').html(data.field_value); + xLabel = $('#tableid_0').val(); + $('#types_0').val(data.field_type); + xType = data.field_type; + $('#collations_0').val(data.field_collations); + addDateTimePicker(); + }); + }); + + // second column choice corresponds to the Y axis + $('#tableid_1').change(function () { + // AJAX request for field type, collation, operators, and value field + $.post('tbl_zoom_select.php', { + 'ajax_request' : true, + 'change_tbl_info' : true, + 'server' : PMA_commonParams.get('server'), + 'db' : PMA_commonParams.get('db'), + 'table' : PMA_commonParams.get('table'), + 'field' : $('#tableid_1').val(), + 'it' : 1 + }, function (data) { + $('#tableFieldsId').find('tr:eq(2) td:eq(0)').html(data.field_type); + $('#tableFieldsId').find('tr:eq(2) td:eq(1)').html(data.field_collation); + $('#tableFieldsId').find('tr:eq(2) td:eq(2)').html(data.field_operators); + $('#tableFieldsId').find('tr:eq(2) td:eq(3)').html(data.field_value); + yLabel = $('#tableid_1').val(); + $('#types_1').val(data.field_type); + yType = data.field_type; + $('#collations_1').val(data.field_collations); + addDateTimePicker(); + }); + }); + + $('#tableid_2').change(function () { + // AJAX request for field type, collation, operators, and value field + $.post('tbl_zoom_select.php', { + 'ajax_request' : true, + 'change_tbl_info' : true, + 'server' : PMA_commonParams.get('server'), + 'db' : PMA_commonParams.get('db'), + 'table' : PMA_commonParams.get('table'), + 'field' : $('#tableid_2').val(), + 'it' : 2 + }, function (data) { + $('#tableFieldsId').find('tr:eq(4) td:eq(0)').html(data.field_type); + $('#tableFieldsId').find('tr:eq(4) td:eq(1)').html(data.field_collation); + $('#tableFieldsId').find('tr:eq(4) td:eq(2)').html(data.field_operators); + $('#tableFieldsId').find('tr:eq(4) td:eq(3)').html(data.field_value); + $('#types_2').val(data.field_type); + $('#collations_2').val(data.field_collations); + addDateTimePicker(); + }); + }); + + $('#tableid_3').change(function () { + // AJAX request for field type, collation, operators, and value field + $.post('tbl_zoom_select.php', { + 'ajax_request' : true, + 'change_tbl_info' : true, + 'server' : PMA_commonParams.get('server'), + 'db' : PMA_commonParams.get('db'), + 'table' : PMA_commonParams.get('table'), + 'field' : $('#tableid_3').val(), + 'it' : 3 + }, function (data) { + $('#tableFieldsId').find('tr:eq(5) td:eq(0)').html(data.field_type); + $('#tableFieldsId').find('tr:eq(5) td:eq(1)').html(data.field_collation); + $('#tableFieldsId').find('tr:eq(5) td:eq(2)').html(data.field_operators); + $('#tableFieldsId').find('tr:eq(5) td:eq(3)').html(data.field_value); + $('#types_3').val(data.field_type); + $('#collations_3').val(data.field_collations); + addDateTimePicker(); + }); + }); + + /** + * Input form validation + **/ + $('#inputFormSubmitId').click(function () { + if ($('#tableid_0').get(0).selectedIndex === 0 || $('#tableid_1').get(0).selectedIndex === 0) { + PMA_ajaxShowMessage(PMA_messages.strInputNull); + } else if (xLabel === yLabel) { + PMA_ajaxShowMessage(PMA_messages.strSameInputs); + } + }); + + /** + ** Prepare a div containing a link, otherwise it's incorrectly displayed + ** after a couple of clicks + **/ + $('
      ') + .insertAfter('#zoom_search_form') + // don't show it until we have results on-screen + .hide(); + + $('#togglesearchformlink') + .html(PMA_messages.strShowSearchCriteria) + .bind('click', function () { + var $link = $(this); + $('#zoom_search_form').slideToggle(); + if ($link.text() === PMA_messages.strHideSearchCriteria) { + $link.text(PMA_messages.strShowSearchCriteria); + } else { + $link.text(PMA_messages.strHideSearchCriteria); + } + // avoid default click action + return false; + }); + + /** + ** Set dialog properties for the data display form + **/ + var buttonOptions = {}; + /* + * Handle saving of a row in the editor + */ + buttonOptions[PMA_messages.strSave] = function () { + // Find changed values by comparing form values with selectedRow Object + var newValues = {};// Stores the values changed from original + var sqlTypes = {}; + var it = 0; + var xChange = false; + var yChange = false; + var key; + var tempGetVal = function () { + return $(this).val(); + }; + for (key in selectedRow) { + var oldVal = selectedRow[key]; + var newVal = ($('#edit_fields_null_id_' + it).prop('checked')) ? null : $('#edit_fieldID_' + it).val(); + if (newVal instanceof Array) { // when the column is of type SET + newVal = $('#edit_fieldID_' + it).map(tempGetVal).get().join(','); + } + if (oldVal !== newVal) { + selectedRow[key] = newVal; + newValues[key] = newVal; + if (key === xLabel) { + xChange = true; + searchedData[searchedDataKey][xLabel] = newVal; + } else if (key === yLabel) { + yChange = true; + searchedData[searchedDataKey][yLabel] = newVal; + } + } + var $input = $('#edit_fieldID_' + it); + if ($input.hasClass('bit')) { + sqlTypes[key] = 'bit'; + } else { + sqlTypes[key] = null; + } + it++; + } // End data update + + // Update the chart series and replot + if (xChange || yChange) { + // Logic similar to plot generation, replot only if xAxis changes or yAxis changes. + // Code includes a lot of checks so as to replot only when necessary + if (xChange) { + xCord[searchedDataKey] = selectedRow[xLabel]; + // [searchedDataKey][0] contains the x value + if (xType === 'numeric') { + series[0][searchedDataKey][0] = selectedRow[xLabel]; + } else if (xType === 'time') { + series[0][searchedDataKey][0] = + getTimeStamp(selectedRow[xLabel], $('#types_0').val()); + } else { + series[0][searchedDataKey][0] = ''; + // TODO: text values + } + currentChart.series[0].data = series[0]; + // TODO: axis changing + currentChart.replot(); + } + if (yChange) { + yCord[searchedDataKey] = selectedRow[yLabel]; + // [searchedDataKey][1] contains the y value + if (yType === 'numeric') { + series[0][searchedDataKey][1] = selectedRow[yLabel]; + } else if (yType === 'time') { + series[0][searchedDataKey][1] = + getTimeStamp(selectedRow[yLabel], $('#types_1').val()); + } else { + series[0][searchedDataKey][1] = ''; + // TODO: text values + } + currentChart.series[0].data = series[0]; + // TODO: axis changing + currentChart.replot(); + } + } // End plot update + + // Generate SQL query for update + if (!isEmpty(newValues)) { + var sql_query = 'UPDATE `' + PMA_commonParams.get('table') + '` SET '; + for (key in newValues) { + sql_query += '`' + key + '`='; + var value = newValues[key]; + + // null + if (value === null) { + sql_query += 'NULL, '; + + // empty + } else if ($.trim(value) === '') { + sql_query += '\'\', '; + + // other + } else { + // type explicitly identified + if (sqlTypes[key] !== null) { + if (sqlTypes[key] === 'bit') { + sql_query += 'b\'' + value + '\', '; + } + // type not explicitly identified + } else { + if (!isNumeric(value)) { + sql_query += '\'' + value + '\', '; + } else { + sql_query += value + ', '; + } + } + } + } + // remove two extraneous characters ', ' + sql_query = sql_query.substring(0, sql_query.length - 2); + sql_query += ' WHERE ' + PMA_urldecode(searchedData[searchedDataKey].where_clause); + + // Post SQL query to sql.php + $.post('sql.php', { + 'server' : PMA_commonParams.get('server'), + 'db' : PMA_commonParams.get('db'), + 'ajax_request' : true, + 'sql_query' : sql_query, + 'inline_edit' : false + }, function (data) { + if (typeof data !== 'undefined' && data.success === true) { + $('#sqlqueryresultsouter').html(data.sql_query); + PMA_highlightSQL($('#sqlqueryresultsouter')); + } else { + PMA_ajaxShowMessage(data.error, false); + } + }); // End $.post + }// End database update + $('#dataDisplay').dialog('close'); + }; + buttonOptions[PMA_messages.strCancel] = function () { + $(this).dialog('close'); + }; + $('#dataDisplay').dialog({ + autoOpen: false, + title: PMA_messages.strDataPointContent, + modal: true, + buttons: buttonOptions, + width: $('#dataDisplay').width() + 80, + open: function () { + $(this).find('input[type=checkbox]').css('margin', '0.5em'); + } + }); + /** + * Attach Ajax event handlers for input fields + * in the dialog. Used to submit the Ajax + * request when the ENTER key is pressed. + */ + $(document).on('keydown', '#dataDisplay :input', function (e) { + if (e.which === 13) { // 13 is the ENTER key + e.preventDefault(); + if (typeof buttonOptions[PMA_messages.strSave] === 'function') { + buttonOptions[PMA_messages.strSave].call(); + } + } + }); + + + /* + * Generate plot using jqplot + */ + + if (searchedData !== null) { + $('#zoom_search_form') + .slideToggle() + .hide(); + $('#togglesearchformlink') + .text(PMA_messages.strShowSearchCriteria); + $('#togglesearchformdiv').show(); + var selectedRow; + var colorCodes = ['#FF0000', '#00FFFF', '#0000FF', '#0000A0', '#FF0080', '#800080', '#FFFF00', '#00FF00', '#FF00FF']; + var series = []; + var xCord = []; + var yCord = []; + var tempX; + var tempY; + var it = 0; + var xMax; // xAxis extreme max + var xMin; // xAxis extreme min + var yMax; // yAxis extreme max + var yMin; // yAxis extreme min + var xVal; + var yVal; + var format; + + var options = { + series: [ + // for a scatter plot + { showLine: false } + ], + grid: { + drawBorder: false, + shadow: false, + background: 'rgba(0,0,0,0)' + }, + axes: { + xaxis: { + label: $('#tableid_0').val(), + labelRenderer: $.jqplot.CanvasAxisLabelRenderer + }, + yaxis: { + label: $('#tableid_1').val(), + labelRenderer: $.jqplot.CanvasAxisLabelRenderer + } + }, + highlighter: { + show: true, + tooltipAxes: 'y', + yvalues: 2, + // hide the first y value + formatString: '%s%s' + }, + cursor: { + show: true, + zoom: true, + showTooltip: false + } + }; + + // If data label is not set, do not show tooltips + if (dataLabel === '') { + options.highlighter.show = false; + } + + // Classify types as either numeric,time,text + xType = getType(xType); + yType = getType(yType); + + // could have multiple series but we'll have just one + series[0] = []; + + if (xType === 'time') { + var originalXType = $('#types_0').val(); + if (originalXType === 'date') { + format = '%Y-%m-%d'; + } + // TODO: does not seem to work + // else if (originalXType === 'time') { + // format = '%H:%M'; + // } else { + // format = '%Y-%m-%d %H:%M'; + // } + $.extend(options.axes.xaxis, { + renderer: $.jqplot.DateAxisRenderer, + tickOptions: { + formatString: format + } + }); + } + if (yType === 'time') { + var originalYType = $('#types_1').val(); + if (originalYType === 'date') { + format = '%Y-%m-%d'; + } + $.extend(options.axes.yaxis, { + renderer: $.jqplot.DateAxisRenderer, + tickOptions: { + formatString: format + } + }); + } + + $.each(searchedData, function (key, value) { + if (xType === 'numeric') { + xVal = parseFloat(value[xLabel]); + } + if (xType === 'time') { + xVal = getTimeStamp(value[xLabel], originalXType); + } + if (yType === 'numeric') { + yVal = parseFloat(value[yLabel]); + } + if (yType === 'time') { + yVal = getTimeStamp(value[yLabel], originalYType); + } + series[0].push([ + xVal, + yVal, + // extra Y values + value[dataLabel], // for highlighter + // (may set an undefined value) + value.where_clause, // for click on point + key // key from searchedData + ]); + }); + + // under IE 8, the initial display is mangled; after a manual + // resizing, it's ok + // under IE 9, everything is fine + currentChart = $.jqplot('querychart', series, options); + currentChart.resetZoom(); + + $('button.button-reset').click(function (event) { + event.preventDefault(); + currentChart.resetZoom(); + }); + + $('div#resizer').resizable(); + $('div#resizer').bind('resizestop', function (event, ui) { + // make room so that the handle will still appear + $('div#querychart').height($('div#resizer').height() * 0.96); + $('div#querychart').width($('div#resizer').width() * 0.96); + currentChart.replot({ resetAxes: true }); + }); + + $('div#querychart').bind('jqplotDataClick', + function (event, seriesIndex, pointIndex, data) { + searchedDataKey = data[4]; // key from searchedData (global) + var field_id = 0; + var post_params = { + 'ajax_request' : true, + 'get_data_row' : true, + 'server' : PMA_commonParams.get('server'), + 'db' : PMA_commonParams.get('db'), + 'table' : PMA_commonParams.get('table'), + 'where_clause' : data[3] + }; + + $.post('tbl_zoom_select.php', post_params, function (data) { + // Row is contained in data.row_info, + // now fill the displayResultForm with row values + var key; + for (key in data.row_info) { + var $field = $('#edit_fieldID_' + field_id); + var $field_null = $('#edit_fields_null_id_' + field_id); + if (data.row_info[key] === null) { + $field_null.prop('checked', true); + $field.val(''); + } else { + $field_null.prop('checked', false); + if ($field.attr('multiple')) { // when the column is of type SET + $field.val(data.row_info[key].split(',')); + } else { + $field.val(data.row_info[key]); + } + } + field_id++; + } + selectedRow = data.row_info; + }); + + $('#dataDisplay').dialog('open'); + } + ); + } + + $('#help_dialog').click(function () { + displayHelp(); + }); +}); diff --git a/php/apps/phpmyadmin49/html/js/transformations/image_upload.js b/php/apps/phpmyadmin49/html/js/transformations/image_upload.js new file mode 100644 index 00000000..6a08d444 --- /dev/null +++ b/php/apps/phpmyadmin49/html/js/transformations/image_upload.js @@ -0,0 +1,28 @@ +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * Image upload transformations plugin js + * + * @package PhpMyAdmin + */ + +AJAX.registerOnload('transformations/image_upload.js', function () { + // Change thumbnail when image file is selected + // through file upload dialog + $('input.image-upload').on('change', function (event) { + if (this.files && this.files[0]) { + var reader = new FileReader(); + var $input = $(this); + reader.onload = function (e) { + $input.prevAll('img').attr('src', e.target.result); + }; + reader.readAsDataURL(this.files[0]); + } + }); +}); + +/** + * Unbind all event handlers before tearing down a page + */ +AJAX.registerTeardown('transformations/image_upload.js', function () { + $('input.image-upload').off('change'); +}); diff --git a/php/apps/phpmyadmin49/html/js/transformations/json.js b/php/apps/phpmyadmin49/html/js/transformations/json.js new file mode 100644 index 00000000..81ddaf22 --- /dev/null +++ b/php/apps/phpmyadmin49/html/js/transformations/json.js @@ -0,0 +1,18 @@ +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * JSON syntax highlighting transformation plugin + */ +AJAX.registerOnload('transformations/json.js', function () { + var $elm = $('#page_content').find('code.json'); + $elm.each(function () { + var $json = $(this); + var $pre = $json.find('pre'); + /* We only care about visible elements to avoid double processing */ + if ($pre.is(':visible')) { + var $highlight = $('
      '); + $json.append($highlight); + CodeMirror.runMode($json.text(), 'application/json', $highlight[0]); + $pre.hide(); + } + }); +}); diff --git a/php/apps/phpmyadmin49/html/js/transformations/json_editor.js b/php/apps/phpmyadmin49/html/js/transformations/json_editor.js new file mode 100644 index 00000000..affae4be --- /dev/null +++ b/php/apps/phpmyadmin49/html/js/transformations/json_editor.js @@ -0,0 +1,17 @@ +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * JSON syntax highlighting transformation plugin + * + * @package PhpMyAdmin + */ +AJAX.registerOnload('transformations/json_editor.js', function () { + $('textarea.transform_json_editor').each(function () { + CodeMirror.fromTextArea(this, { + lineNumbers: true, + matchBrackets: true, + indentUnit: 4, + mode: 'application/json', + lineWrapping: true + }); + }); +}); diff --git a/php/apps/phpmyadmin49/html/js/transformations/sql_editor.js b/php/apps/phpmyadmin49/html/js/transformations/sql_editor.js new file mode 100644 index 00000000..4149b976 --- /dev/null +++ b/php/apps/phpmyadmin49/html/js/transformations/sql_editor.js @@ -0,0 +1,11 @@ +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * SQL syntax highlighting transformation plugin js + * + * @package PhpMyAdmin + */ +AJAX.registerOnload('transformations/sql_editor.js', function () { + $('textarea.transform_sql_editor').each(function () { + PMA_getSQLEditor($(this), {}, 'both'); + }); +}); diff --git a/php/apps/phpmyadmin49/html/js/transformations/xml.js b/php/apps/phpmyadmin49/html/js/transformations/xml.js new file mode 100644 index 00000000..3fdf152e --- /dev/null +++ b/php/apps/phpmyadmin49/html/js/transformations/xml.js @@ -0,0 +1,18 @@ +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * XML syntax highlighting transformation plugin + */ +AJAX.registerOnload('transformations/xml.js', function () { + var $elm = $('#page_content').find('code.xml'); + $elm.each(function () { + var $json = $(this); + var $pre = $json.find('pre'); + /* We only care about visible elements to avoid double processing */ + if ($pre.is(':visible')) { + var $highlight = $('
      '); + $json.append($highlight); + CodeMirror.runMode($json.text(), 'application/xml', $highlight[0]); + $pre.hide(); + } + }); +}); diff --git a/php/apps/phpmyadmin49/html/js/transformations/xml_editor.js b/php/apps/phpmyadmin49/html/js/transformations/xml_editor.js new file mode 100644 index 00000000..7d2533d1 --- /dev/null +++ b/php/apps/phpmyadmin49/html/js/transformations/xml_editor.js @@ -0,0 +1,16 @@ +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * XML editor plugin + * + * @package PhpMyAdmin + */ +AJAX.registerOnload('transformations/xml_editor.js', function () { + $('textarea.transform_xml_editor').each(function () { + CodeMirror.fromTextArea(this, { + lineNumbers: true, + indentUnit: 4, + mode: 'application/xml', + lineWrapping: true + }); + }); +}); diff --git a/php/apps/phpmyadmin49/html/js/u2f.js b/php/apps/phpmyadmin49/html/js/u2f.js new file mode 100644 index 00000000..47f3e8d7 --- /dev/null +++ b/php/apps/phpmyadmin49/html/js/u2f.js @@ -0,0 +1,59 @@ +/** global: AJAX */ +/** global: PMA_messages */ +/** global: u2f */ +AJAX.registerOnload('u2f.js', function () { + var $inputReg = $('#u2f_registration_response'); + if ($inputReg.length > 0) { + var $formReg = $inputReg.parents('form'); + $formReg.find('input[type=submit]').hide(); + setTimeout(function () { + // A magic JS function that talks to the USB device. This function will keep polling for the USB device until it finds one. + var request = JSON.parse($inputReg.attr('data-request')); + u2f.register(request.appId, [request], JSON.parse($inputReg.attr('data-signatures')), function (data) { + // Handle returning error data + if (data.errorCode && data.errorCode !== 0) { + if (data.errorCode === 5) { + PMA_ajaxShowMessage(PMA_messages.strU2FTimeout, false); + } else { + PMA_ajaxShowMessage( + PMA_sprintf(PMA_messages.strU2FError, data.errorCode), false + ); + } + return; + } + + // Fill and submit form. + $inputReg.val(JSON.stringify(data)); + $formReg.submit(); + }); + }, 1000); + } + var $inputAuth = $('#u2f_authentication_response'); + if ($inputAuth.length > 0) { + var $formAuth = $inputAuth.parents('form'); + $formAuth.find('input[type=submit]').hide(); + setTimeout(function () { + // Magic JavaScript talking to your HID + // appid, challenge, authenticateRequests + var request = JSON.parse($inputAuth.attr('data-request')); + var handles = [request[0].keyHandle]; + u2f.sign(request[0].appId, request[0].challenge, request, function (data) { + // Handle returning error data + if (data.errorCode && data.errorCode !== 0) { + if (data.errorCode === 5) { + PMA_ajaxShowMessage(PMA_messages.strU2FTimeout, false); + } else { + PMA_ajaxShowMessage( + PMA_sprintf(PMA_messages.strU2FError, data.errorCode), false + ); + } + return; + } + + // Fill and submit form. + $inputAuth.val(JSON.stringify(data)); + $formAuth.submit(); + }); + }, 1000); + } +}); diff --git a/php/apps/phpmyadmin49/html/js/whitelist.php b/php/apps/phpmyadmin49/html/js/whitelist.php new file mode 100644 index 00000000..10ba2675 --- /dev/null +++ b/php/apps/phpmyadmin49/html/js/whitelist.php @@ -0,0 +1,46 @@ +start(); +if (!defined('TESTSUITE')) { + register_shutdown_function( + function () { + echo OutputBuffering::getInstance()->getContents(); + } + ); +} + +echo "var PMA_gotoWhitelist = new Array();\n"; +$i = 0; +foreach (Core::$goto_whitelist as $one_whitelist) { + echo 'PMA_gotoWhitelist[' , $i , ']="' , $one_whitelist , '";' , "\n"; + $i++; +} diff --git a/php/apps/phpmyadmin49/html/libraries/advisory_rules.txt b/php/apps/phpmyadmin49/html/libraries/advisory_rules.txt new file mode 100644 index 00000000..46446c0b --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/advisory_rules.txt @@ -0,0 +1,452 @@ +# phpMyAdmin Advisory rules file +# +# Use only UNIX style newlines +# +# This file is being parsed by Advisor.php, which should handle syntax +# errors correctly. However, PHP Warnings and the like are being consumed by +# the phpMyAdmin error handler, so those won't show up E.g.: Justification line +# is empty because you used an unescape percent sign, sprintf() returns an +# empty string and no warning/error is shown +# +# Rule Syntax: +# 'rule' identifier[the name of the rule] eexpr [an optional precondition] +# expr [variable or value calculation used for the test] +# expr [test, if evaluted to 'true' it fires the rule. Use 'value' to insert the calculated value (without quotes)] +# string [the issue (what is the problem?)] +# string [the recommendation (how do i fix it?)] +# formatted-string '|' comma-seperated-expr [the justification (result of the calculated value / why did this rule fire?)] + +# comma-seperated-expr: expr(,expr)* +# eexpr: [expr] - expr enclosed in [] +# expr: a php code literal with extras: +# - variable names are replaced with their respective values +# - fired('name of rule') is replaced with true/false when given rule has +# been fired. Note however that this is a very simple rules engine. +# Rules are only checked in sequential order as they are written down +# here. If given rule has not been checked yet, fired() will always +# evaluate to false +# - 'value' is replaced with the calculated value. If it is a string, it +# will be put within single quotes +# - other than that you may use any php function, initialized variable or +# constant +# +# identifier: A string enclosed in single quotes +# string: A quoteless string, may contain HTML. Variable names enclosed in +# curly braces are replaced with links to directly edit this variable. +# e.g. {tmp_table_size} +# formatted-string: You may use classic php sprintf() string formatting here, +# the arguments must be appended after a trailing pipe (|) as +# mentioned in above syntax percent signs (%) are +# automatically escaped (%%) in the following cases: When +# followed by a space, dot or comma and at the end of the +# line) +# +# Comments start with # +# + +# Queries + +rule 'Uptime below one day' + Uptime + value < 86400 + Uptime is less than 1 day, performance tuning may not be accurate. + To have more accurate averages it is recommended to let the server run for longer than a day before running this analyzer + The uptime is only %s | ADVISOR_timespanFormat(Uptime) + +rule 'Questions below 1,000' + Questions + value < 1000 + Fewer than 1,000 questions have been run against this server. The recommendations may not be accurate. + Let the server run for a longer time until it has executed a greater amount of queries. + Current amount of Questions: %s | Questions + +rule 'Percentage of slow queries' [Questions > 0] + Slow_queries / Questions * 100 + value >= 5 + There is a lot of slow queries compared to the overall amount of Queries. + You might want to increase {long_query_time} or optimize the queries listed in the slow query log + The slow query rate should be below 5%, your value is %s%. | round(value,2) + +rule 'Slow query rate' [Questions > 0] + (Slow_queries / Questions * 100) / Uptime + value * 60 * 60 > 1 + There is a high percentage of slow queries compared to the server uptime. + You might want to increase {long_query_time} or optimize the queries listed in the slow query log + You have a slow query rate of %s per hour, you should have less than 1% per hour. | ADVISOR_bytime(value,2) + +rule 'Long query time' + long_query_time + value >= 10 + {long_query_time} is set to 10 seconds or more, thus only slow queries that take above 10 seconds are logged. + It is suggested to set {long_query_time} to a lower value, depending on your environment. Usually a value of 1-5 seconds is suggested. + long_query_time is currently set to %ds. | value + +rule 'Slow query logging' [PMA_MYSQL_INT_VERSION < 50600] + log_slow_queries + value == 'OFF' + The slow query log is disabled. + Enable slow query logging by setting {log_slow_queries} to 'ON'. This will help troubleshooting badly performing queries. + log_slow_queries is set to 'OFF' + +rule 'Slow query logging' [PMA_MYSQL_INT_VERSION >= 50600] + slow_query_log + value == 'OFF' + The slow query log is disabled. + Enable slow query logging by setting {slow_query_log} to 'ON'. This will help troubleshooting badly performing queries. + slow_query_log is set to 'OFF' + +# +# versions +rule 'Release Series' + version + substr(value,0,2) <= '5.' && substr(value,2,1) < 1 + The MySQL server version less than 5.1. + You should upgrade, as MySQL 5.1 has improved performance, and MySQL 5.5 even more so. + Current version: %s | value + +rule 'Minor Version' [! fired('Release Series')] + version + substr(value,0,2) <= '5.' && substr(value,2,1) <= 1 && substr(value,4,2) < 30 + Version less than 5.1.30 (the first GA release of 5.1). + You should upgrade, as recent versions of MySQL 5.1 have improved performance and MySQL 5.5 even more so. + Current version: %s | value + +rule 'Minor Version' [! fired('Release Series')] + version + substr(value,0,1) == 5 && substr(value,2,1) == 5 && substr(value,4,2) < 8 + Version less than 5.5.8 (the first GA release of 5.5). + You should upgrade, to a stable version of MySQL 5.5. + Current version: %s | value + +rule 'Distribution' + version_comment + preg_match('/source/i',value) + Version is compiled from source, not a MySQL official binary. + If you did not compile from source, you may be using a package modified by a distribution. The MySQL manual only is accurate for official MySQL binaries, not any package distributions (such as RedHat, Debian/Ubuntu etc). + 'source' found in version_comment + +rule 'Distribution' + version_comment + preg_match('/percona/i',value) + The MySQL manual only is accurate for official MySQL binaries. + Percona documentation is at https://www.percona.com/software/documentation/ + 'percona' found in version_comment + +rule 'MySQL Architecture' + system_memory + value > 3072*1024 && !preg_match('/64/',version_compile_machine) && !preg_match('/64/',version_compile_os) + MySQL is not compiled as a 64-bit package. + Your memory capacity is above 3 GiB (assuming the Server is on localhost), so MySQL might not be able to access all of your memory. You might want to consider installing the 64-bit version of MySQL. + Available memory on this host: %s | ADVISOR_formatByteDown(value*1024, 2, 2) + +# +# Query cache + +# Lame: 'ON' == 0 is true, so you need to compare 'ON' == '0' +rule 'Query cache disabled' + query_cache_size + value == 0 || query_cache_type == 'OFF' || query_cache_type == '0' + The query cache is not enabled. + The query cache is known to greatly improve performance if configured correctly. Enable it by setting {query_cache_size} to a 2 digit MiB value and setting {query_cache_type} to 'ON'. Note: If you are using memcached, ignore this recommendation. + query_cache_size is set to 0 or query_cache_type is set to 'OFF' + +rule 'Query caching method' [!fired('Query cache disabled')] + Questions / Uptime + value > 100 + Suboptimal caching method. + You are using the MySQL Query cache with a fairly high traffic database. It might be worth considering to use memcached instead of the MySQL Query cache, especially if you have multiple slaves. + The query cache is enabled and the server receives %d queries per second. This rule fires if there is more than 100 queries per second. | round(value,1) + +rule 'Query cache efficiency (%)' [Com_select + Qcache_hits > 0 && !fired('Query cache disabled')] + Qcache_hits / (Com_select + Qcache_hits) * 100 + value < 20 + Query cache not running efficiently, it has a low hit rate. + Consider increasing {query_cache_limit}. + The current query cache hit rate of %s% is below 20% | round(value,1) + +rule 'Query Cache usage' [!fired('Query cache disabled')] + 100 - Qcache_free_memory / query_cache_size * 100 + value < 80 + Less than 80% of the query cache is being utilized. + This might be caused by {query_cache_limit} being too low. Flushing the query cache might help as well. + The current ratio of free query cache memory to total query cache size is %s%. It should be above 80% | round(value,1) + +rule 'Query cache fragmentation' [!fired('Query cache disabled')] + Qcache_free_blocks / (Qcache_total_blocks / 2) * 100 + value > 20 + The query cache is considerably fragmented. + Severe fragmentation is likely to (further) increase Qcache_lowmem_prunes. This might be caused by many Query cache low memory prunes due to {query_cache_size} being too small. For a immediate but short lived fix you can flush the query cache (might lock the query cache for a long time). Carefully adjusting {query_cache_min_res_unit} to a lower value might help too, e.g. you can set it to the average size of your queries in the cache using this formula: (query_cache_size - qcache_free_memory) / qcache_queries_in_cache + The cache is currently fragmented by %s% , with 100% fragmentation meaning that the query cache is an alternating pattern of free and used blocks. This value should be below 20%. | round(value,1) + +rule 'Query cache low memory prunes' [Qcache_inserts > 0 && !fired('Query cache disabled')] + Qcache_lowmem_prunes / Qcache_inserts * 100 + value > 0.1 + Cached queries are removed due to low query cache memory from the query cache. + You might want to increase {query_cache_size}, however keep in mind that the overhead of maintaining the cache is likely to increase with its size, so do this in small increments and monitor the results. + The ratio of removed queries to inserted queries is %s%. The lower this value is, the better (This rules firing limit: 0.1%) | round(value,1) + +rule 'Query cache max size' [!fired('Query cache disabled')] + query_cache_size + value > 1024 * 1024 * 128 + The query cache size is above 128 MiB. Big query caches may cause significant overhead that is required to maintain the cache. + Depending on your environment, it might be performance increasing to reduce this value. + Current query cache size: %s | ADVISOR_formatByteDown(value, 2, 2) + +rule 'Query cache min result size' [!fired('Query cache disabled')] + query_cache_limit + value == 1024*1024 + The max size of the result set in the query cache is the default of 1 MiB. + Changing {query_cache_limit} (usually by increasing) may increase efficiency. This variable determines the maximum size a query result may have to be inserted into the query cache. If there are many query results above 1 MiB that are well cacheable (many reads, little writes) then increasing {query_cache_limit} will increase efficiency. Whereas in the case of many query results being above 1 MiB that are not very well cacheable (often invalidated due to table updates) increasing {query_cache_limit} might reduce efficiency. + query_cache_limit is set to 1 MiB + +# +# Sorts +rule 'Percentage of sorts that cause temporary tables' [Sort_scan + Sort_range > 0] + Sort_merge_passes / (Sort_scan + Sort_range) * 100 + value > 10 + Too many sorts are causing temporary tables. + Consider increasing {sort_buffer_size} and/or {read_rnd_buffer_size}, depending on your system memory limits. + %s% of all sorts cause temporary tables, this value should be lower than 10%. | round(value,1) + +rule 'Rate of sorts that cause temporary tables' + Sort_merge_passes / Uptime + value * 60 * 60 > 1 + Too many sorts are causing temporary tables. + Consider increasing {sort_buffer_size} and/or {read_rnd_buffer_size}, depending on your system memory limits. + Temporary tables average: %s, this value should be less than 1 per hour. | ADVISOR_bytime(value,2) + +rule 'Sort rows' + Sort_rows / Uptime + value * 60 >= 1 + There are lots of rows being sorted. + While there is nothing wrong with a high amount of row sorting, you might want to make sure that the queries which require a lot of sorting use indexed columns in the ORDER BY clause, as this will result in much faster sorting. + Sorted rows average: %s | ADVISOR_bytime(value,2) + +# Joins, scans +rule 'Rate of joins without indexes' + (Select_range_check + Select_scan + Select_full_join) / Uptime + value * 60 * 60 > 1 + There are too many joins without indexes. + This means that joins are doing full table scans. Adding indexes for the columns being used in the join conditions will greatly speed up table joins. + Table joins average: %s, this value should be less than 1 per hour | ADVISOR_bytime(value,2) + +rule 'Rate of reading first index entry' + Handler_read_first / Uptime + value * 60 * 60 > 1 + The rate of reading the first index entry is high. + This usually indicates frequent full index scans. Full index scans are faster than table scans but require lots of CPU cycles in big tables, if those tables that have or had high volumes of UPDATEs and DELETEs, running 'OPTIMIZE TABLE' might reduce the amount of and/or speed up full index scans. Other than that full index scans can only be reduced by rewriting queries. + Index scans average: %s, this value should be less than 1 per hour | ADVISOR_bytime(value,2) + +rule 'Rate of reading fixed position' + Handler_read_rnd / Uptime + value * 60 * 60 > 1 + The rate of reading data from a fixed position is high. + This indicates that many queries need to sort results and/or do a full table scan, including join queries that do not use indexes. Add indexes where applicable. + Rate of reading fixed position average: %s, this value should be less than 1 per hour | ADVISOR_bytime(value,2) + +rule 'Rate of reading next table row' + Handler_read_rnd_next / Uptime + value * 60 * 60 > 1 + The rate of reading the next table row is high. + This indicates that many queries are doing full table scans. Add indexes where applicable. + Rate of reading next table row: %s, this value should be less than 1 per hour | ADVISOR_bytime(value,2) + +# temp tables +rule 'Different tmp_table_size and max_heap_table_size' + tmp_table_size - max_heap_table_size + value !=0 + {tmp_table_size} and {max_heap_table_size} are not the same. + If you have deliberately changed one of either: The server uses the lower value of either to determine the maximum size of in-memory tables. So if you wish to increase the in-memory table limit you will have to increase the other value as well. + Current values are tmp_table_size: %s, max_heap_table_size: %s | ADVISOR_formatByteDown(tmp_table_size, 2, 2), ADVISOR_formatByteDown(max_heap_table_size, 2, 2) + +rule 'Percentage of temp tables on disk' [Created_tmp_tables + Created_tmp_disk_tables > 0] + Created_tmp_disk_tables / (Created_tmp_tables + Created_tmp_disk_tables) * 100 + value > 25 + Many temporary tables are being written to disk instead of being kept in memory. + Increasing {max_heap_table_size} and {tmp_table_size} might help. However some temporary tables are always being written to disk, independent of the value of these variables. To eliminate these you will have to rewrite your queries to avoid those conditions (Within a temporary table: Presence of a BLOB or TEXT column or presence of a column bigger than 512 bytes) as mentioned in the beginning of an Article by the Pythian Group + %s% of all temporary tables are being written to disk, this value should be below 25% | round(value,1) + +rule 'Temp disk rate' [!fired('Percentage of temp tables on disk')] + Created_tmp_disk_tables / Uptime + value * 60 * 60 > 1 + Many temporary tables are being written to disk instead of being kept in memory. + Increasing {max_heap_table_size} and {tmp_table_size} might help. However some temporary tables are always being written to disk, independent of the value of these variables. To eliminate these you will have to rewrite your queries to avoid those conditions (Within a temporary table: Presence of a BLOB or TEXT column or presence of a column bigger than 512 bytes) as mentioned in the MySQL Documentation + Rate of temporary tables being written to disk: %s, this value should be less than 1 per hour | ADVISOR_bytime(value,2) + +# +# MyISAM index cache +rule 'MyISAM key buffer size' + key_buffer_size + value == 0 + Key buffer is not initialized. No MyISAM indexes will be cached. + Set {key_buffer_size} depending on the size of your MyISAM indexes. 64M is a good start. + key_buffer_size is 0 + +rule 'Max % MyISAM key buffer ever used' [key_buffer_size > 0] + Key_blocks_used * key_cache_block_size / key_buffer_size * 100 + value < 95 + MyISAM key buffer (index cache) % used is low. + You may need to decrease the size of {key_buffer_size}, re-examine your tables to see if indexes have been removed, or examine queries and expectations about what indexes are being used. + max % MyISAM key buffer ever used: %s%, this value should be above 95% | round(value,1) + +# Don't fire if above rule fired - we don't need the same advice twice +rule 'Percentage of MyISAM key buffer used' [key_buffer_size > 0 && !fired('Max % MyISAM key buffer ever used')] + ( 1 - Key_blocks_unused * key_cache_block_size / key_buffer_size) * 100 + value < 95 + MyISAM key buffer (index cache) % used is low. + You may need to decrease the size of {key_buffer_size}, re-examine your tables to see if indexes have been removed, or examine queries and expectations about what indexes are being used. + % MyISAM key buffer used: %s%, this value should be above 95% | round(value,1) + +rule 'Percentage of index reads from memory' [Key_read_requests > 0] + 100 - (Key_reads / Key_read_requests * 100) + value < 95 + The % of indexes that use the MyISAM key buffer is low. + You may need to increase {key_buffer_size}. + Index reads from memory: %s%, this value should be above 95% | round(value,1) + +# +# other caches +rule 'Rate of table open' + Opened_tables / Uptime + value*60*60 > 10 + The rate of opening tables is high. + Opening tables requires disk I/O which is costly. Increasing {table_open_cache} might avoid this. + Opened table rate: %s, this value should be less than 10 per hour | ADVISOR_bytime(value,2) + +rule 'Percentage of used open files limit' + Open_files / open_files_limit * 100 + value > 85 + The number of open files is approaching the max number of open files. You may get a "Too many open files" error. + Consider increasing {open_files_limit}, and check the error log when restarting after changing {open_files_limit}. + The number of opened files is at %s% of the limit. It should be below 85% | round(value,1) + +rule 'Rate of open files' + Open_files / Uptime + value * 60 * 60 > 5 + The rate of opening files is high. + Consider increasing {open_files_limit}, and check the error log when restarting after changing {open_files_limit}. + Opened files rate: %s, this value should be less than 5 per hour | ADVISOR_bytime(value,2) + +rule 'Immediate table locks %' [Table_locks_waited + Table_locks_immediate > 0] + Table_locks_immediate / (Table_locks_waited + Table_locks_immediate) * 100 + value < 95 + Too many table locks were not granted immediately. + Optimize queries and/or use InnoDB to reduce lock wait. + Immediate table locks: %s%, this value should be above 95% | round(value,1) + +rule 'Table lock wait rate' + Table_locks_waited / Uptime + value * 60 * 60 > 1 + Too many table locks were not granted immediately. + Optimize queries and/or use InnoDB to reduce lock wait. + Table lock wait rate: %s, this value should be less than 1 per hour | ADVISOR_bytime(value,2) + +rule 'Thread cache' + thread_cache_size + value < 1 + Thread cache is disabled, resulting in more overhead from new connections to MySQL. + Enable the thread cache by setting {thread_cache_size} > 0. + The thread cache is set to 0 + +rule 'Thread cache hit rate %' [thread_cache_size > 0] + 100 - Threads_created / Connections + value < 80 + Thread cache is not efficient. + Increase {thread_cache_size}. + Thread cache hitrate: %s%, this value should be above 80% | round(value,1) + +rule 'Threads that are slow to launch' [slow_launch_time > 0] + Slow_launch_threads + value > 0 + There are too many threads that are slow to launch. + This generally happens in case of general system overload as it is pretty simple operations. You might want to monitor your system load carefully. + %s thread(s) took longer than %s seconds to start, it should be 0 | value, slow_launch_time + +rule 'Slow launch time' + slow_launch_time + value > 2 + Slow_launch_time is above 2s. + Set {slow_launch_time} to 1s or 2s to correctly count threads that are slow to launch. + slow_launch_time is set to %s | value + +# +#Connections +rule 'Percentage of used connections' + Max_used_connections / max_connections * 100 + value > 80 + The maximum amount of used connections is getting close to the value of {max_connections}. + Increase {max_connections}, or decrease {wait_timeout} so that connections that do not close database handlers properly get killed sooner. Make sure the code closes database handlers properly. + Max_used_connections is at %s% of max_connections, it should be below 80% | round(value,1) + +rule 'Percentage of aborted connections' + Aborted_connects / Connections * 100 + value > 1 + Too many connections are aborted. + Connections are usually aborted when they cannot be authorized. This article might help you track down the source. + %s% of all connections are aborted. This value should be below 1% | round(value,1) + +rule 'Rate of aborted connections' + Aborted_connects / Uptime + value * 60 * 60 > 1 + Too many connections are aborted. + Connections are usually aborted when they cannot be authorized. This article might help you track down the source. + Aborted connections rate is at %s, this value should be less than 1 per hour | ADVISOR_bytime(value,2) + +rule 'Percentage of aborted clients' + Aborted_clients / Connections * 100 + value > 2 + Too many clients are aborted. + Clients are usually aborted when they did not close their connection to MySQL properly. This can be due to network issues or code not closing a database handler properly. Check your network and code. + %s% of all clients are aborted. This value should be below 2% | round(value,1) + +rule 'Rate of aborted clients' + Aborted_clients / Uptime + value * 60 * 60 > 1 + Too many clients are aborted. + Clients are usually aborted when they did not close their connection to MySQL properly. This can be due to network issues or code not closing a database handler properly. Check your network and code. + Aborted client rate is at %s, this value should be less than 1 per hour | ADVISOR_bytime(value,2) + +# +# InnoDB +rule 'Is InnoDB disabled?' [PMA_MYSQL_INT_VERSION < 50600] + have_innodb + value != "YES" + You do not have InnoDB enabled. + InnoDB is usually the better choice for table engines. + have_innodb is set to 'value' + +rule 'InnoDB log size' [innodb_buffer_pool_size > 0] + innodb_log_file_size / innodb_buffer_pool_size * 100 + value < 20 && innodb_log_file_size / (1024 * 1024) < 256 + The InnoDB log file size is not an appropriate size, in relation to the InnoDB buffer pool. + Especially on a system with a lot of writes to InnoDB tables you should set {innodb_log_file_size} to 25% of {innodb_buffer_pool_size}. However the bigger this value, the longer the recovery time will be when database crashes, so this value should not be set much higher than 256 MiB. Please note however that you cannot simply change the value of this variable. You need to shutdown the server, remove the InnoDB log files, set the new value in my.cnf, start the server, then check the error logs if everything went fine. See also this blog entry + Your InnoDB log size is at %s% in relation to the InnoDB buffer pool size, it should not be below 20% | round(value,1) + +rule 'Max InnoDB log size' [innodb_buffer_pool_size > 0 && innodb_log_file_size / innodb_buffer_pool_size * 100 < 30] + innodb_log_file_size / (1024 * 1024) + value > 256 + The InnoDB log file size is inadequately large. + It is usually sufficient to set {innodb_log_file_size} to 25% of the size of {innodb_buffer_pool_size}. A very big {innodb_log_file_size} slows down the recovery time after a database crash considerably. See also this Article. You need to shutdown the server, remove the InnoDB log files, set the new value in my.cnf, start the server, then check the error logs if everything went fine. See also this blog entry + Your absolute InnoDB log size is %s MiB | round(value,1) + +rule 'InnoDB buffer pool size' [system_memory > 0] + innodb_buffer_pool_size / system_memory * 100 + value < 60 + Your InnoDB buffer pool is fairly small. + The InnoDB buffer pool has a profound impact on performance for InnoDB tables. Assign all your remaining memory to this buffer. For database servers that use solely InnoDB as storage engine and have no other services (e.g. a web server) running, you may set this as high as 80% of your available memory. If that is not the case, you need to carefully assess the memory consumption of your other services and non-InnoDB-Tables and set this variable accordingly. If it is set too high, your system will start swapping, which decreases performance significantly. See also this article + You are currently using %s% of your memory for the InnoDB buffer pool. This rule fires if you are assigning less than 60%, however this might be perfectly adequate for your system if you don't have much InnoDB tables or other services running on the same machine. | value + +# +# other +rule 'MyISAM concurrent inserts' + concurrent_insert + value === 0 || value === 'NEVER' + Enable {concurrent_insert} by setting it to 1 + Setting {concurrent_insert} to 1 reduces contention between readers and writers for a given table. See also MySQL Documentation + concurrent_insert is set to 0 + +# INSERT DELAYED USAGE +#Delayed_errors 0 +#Delayed_insert_threads 0 +#Delayed_writes 0 +#Not_flushed_delayed_rows diff --git a/php/apps/phpmyadmin49/html/libraries/certs/12d55845.0 b/php/apps/phpmyadmin49/html/libraries/certs/12d55845.0 new file mode 100644 index 00000000..b2e43c93 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/certs/12d55845.0 @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDSjCCAjKgAwIBAgIQRK+wgNajJ7qJMDmGLvhAazANBgkqhkiG9w0BAQUFADA/ +MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT +DkRTVCBSb290IENBIFgzMB4XDTAwMDkzMDIxMTIxOVoXDTIxMDkzMDE0MDExNVow +PzEkMCIGA1UEChMbRGlnaXRhbCBTaWduYXR1cmUgVHJ1c3QgQ28uMRcwFQYDVQQD +Ew5EU1QgUm9vdCBDQSBYMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB +AN+v6ZdQCINXtMxiZfaQguzH0yxrMMpb7NnDfcdAwRgUi+DoM3ZJKuM/IUmTrE4O +rz5Iy2Xu/NMhD2XSKtkyj4zl93ewEnu1lcCJo6m67XMuegwGMoOifooUMM0RoOEq +OLl5CjH9UL2AZd+3UWODyOKIYepLYYHsUmu5ouJLGiifSKOeDNoJjj4XLh7dIN9b +xiqKqy69cK3FCxolkHRyxXtqqzTWMIn/5WgTe1QLyNau7Fqckh49ZLOMxt+/yUFw +7BZy1SbsOFU5Q9D8/RhcQPGX69Wam40dutolucbY38EVAjqr2m7xPi71XAicPNaD +aeQQmxkqtilX4+U9m5/wAl0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNV +HQ8BAf8EBAMCAQYwHQYDVR0OBBYEFMSnsaR7LHH62+FLkHX/xBVghYkQMA0GCSqG +SIb3DQEBBQUAA4IBAQCjGiybFwBcqR7uKGY3Or+Dxz9LwwmglSBd49lZRNI+DT69 +ikugdB/OEIKcdBodfpga3csTS7MgROSR6cz8faXbauX+5v3gTt23ADq1cEmv8uXr +AvHRAosZy5Q6XkjEGB5YGV8eAlrwDPGxrancWYaLbumR9YbK+rlmM6pZW87ipxZz +R8srzJmwN0jP41ZL9c8PDHIyh8bwRLtTcm1D9SZImlJnt1ir/md2cXjbDaJWFBM5 +JDGFoqgCWjBH4d1QB7wCCZAA62RjYJsWvIjJEubSfZGL+T0yjWW06XyxV3bqxbYo +Ob8VZRzI9neWagqNdwvYkQsEjgfbKbYK7p2CNTUQ +-----END CERTIFICATE----- diff --git a/php/apps/phpmyadmin49/html/libraries/certs/2e5ac55d.0 b/php/apps/phpmyadmin49/html/libraries/certs/2e5ac55d.0 new file mode 100644 index 00000000..b2e43c93 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/certs/2e5ac55d.0 @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDSjCCAjKgAwIBAgIQRK+wgNajJ7qJMDmGLvhAazANBgkqhkiG9w0BAQUFADA/ +MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT +DkRTVCBSb290IENBIFgzMB4XDTAwMDkzMDIxMTIxOVoXDTIxMDkzMDE0MDExNVow +PzEkMCIGA1UEChMbRGlnaXRhbCBTaWduYXR1cmUgVHJ1c3QgQ28uMRcwFQYDVQQD +Ew5EU1QgUm9vdCBDQSBYMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB +AN+v6ZdQCINXtMxiZfaQguzH0yxrMMpb7NnDfcdAwRgUi+DoM3ZJKuM/IUmTrE4O +rz5Iy2Xu/NMhD2XSKtkyj4zl93ewEnu1lcCJo6m67XMuegwGMoOifooUMM0RoOEq +OLl5CjH9UL2AZd+3UWODyOKIYepLYYHsUmu5ouJLGiifSKOeDNoJjj4XLh7dIN9b +xiqKqy69cK3FCxolkHRyxXtqqzTWMIn/5WgTe1QLyNau7Fqckh49ZLOMxt+/yUFw +7BZy1SbsOFU5Q9D8/RhcQPGX69Wam40dutolucbY38EVAjqr2m7xPi71XAicPNaD +aeQQmxkqtilX4+U9m5/wAl0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNV +HQ8BAf8EBAMCAQYwHQYDVR0OBBYEFMSnsaR7LHH62+FLkHX/xBVghYkQMA0GCSqG +SIb3DQEBBQUAA4IBAQCjGiybFwBcqR7uKGY3Or+Dxz9LwwmglSBd49lZRNI+DT69 +ikugdB/OEIKcdBodfpga3csTS7MgROSR6cz8faXbauX+5v3gTt23ADq1cEmv8uXr +AvHRAosZy5Q6XkjEGB5YGV8eAlrwDPGxrancWYaLbumR9YbK+rlmM6pZW87ipxZz +R8srzJmwN0jP41ZL9c8PDHIyh8bwRLtTcm1D9SZImlJnt1ir/md2cXjbDaJWFBM5 +JDGFoqgCWjBH4d1QB7wCCZAA62RjYJsWvIjJEubSfZGL+T0yjWW06XyxV3bqxbYo +Ob8VZRzI9neWagqNdwvYkQsEjgfbKbYK7p2CNTUQ +-----END CERTIFICATE----- diff --git a/php/apps/phpmyadmin49/html/libraries/certs/4042bcee.0 b/php/apps/phpmyadmin49/html/libraries/certs/4042bcee.0 new file mode 100644 index 00000000..9548dc1b --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/certs/4042bcee.0 @@ -0,0 +1,31 @@ +-----BEGIN CERTIFICATE----- +MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw +TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh +cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4 +WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu +ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY +MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc +h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+ +0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U +A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW +T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH +B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC +B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv +KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn +OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn +jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw +qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI +rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV +HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq +hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL +ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ +3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK +NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5 +ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur +TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC +jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc +oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq +4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA +mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d +emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc= +-----END CERTIFICATE----- diff --git a/php/apps/phpmyadmin49/html/libraries/certs/6187b673.0 b/php/apps/phpmyadmin49/html/libraries/certs/6187b673.0 new file mode 100644 index 00000000..9548dc1b --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/certs/6187b673.0 @@ -0,0 +1,31 @@ +-----BEGIN CERTIFICATE----- +MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw +TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh +cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4 +WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu +ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY +MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc +h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+ +0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U +A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW +T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH +B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC +B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv +KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn +OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn +jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw +qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI +rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV +HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq +hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL +ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ +3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK +NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5 +ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur +TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC +jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc +oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq +4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA +mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d +emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc= +-----END CERTIFICATE----- diff --git a/php/apps/phpmyadmin49/html/libraries/certs/README.rst b/php/apps/phpmyadmin49/html/libraries/certs/README.rst new file mode 100644 index 00000000..bc48f6ce --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/certs/README.rst @@ -0,0 +1,16 @@ +phpMyAdmin SSL certificates +=========================== + +This directory contains copy of root certificates used to sign phpmyadmin.net +and reports.phpmyadmin.net websites. It is used to allow operation on systems +where the certificates are missing or wrongly configured (happens on Windows +with wrongly compiled CURL). + +Currently included SSL certificates: + +* ISRG Root X1 +* DST Root CA X3 + +See https://letsencrypt.org/certificates/ for more info on them. + +In case of update, the filenames can be generated using c_rehash tool. diff --git a/php/apps/phpmyadmin49/html/libraries/certs/cacert.pem b/php/apps/phpmyadmin49/html/libraries/certs/cacert.pem new file mode 100644 index 00000000..5f2265fc --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/certs/cacert.pem @@ -0,0 +1,51 @@ +-----BEGIN CERTIFICATE----- +MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw +TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh +cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4 +WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu +ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY +MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc +h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+ +0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U +A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW +T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH +B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC +B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv +KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn +OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn +jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw +qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI +rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV +HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq +hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL +ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ +3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK +NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5 +ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur +TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC +jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc +oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq +4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA +mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d +emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIDSjCCAjKgAwIBAgIQRK+wgNajJ7qJMDmGLvhAazANBgkqhkiG9w0BAQUFADA/ +MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT +DkRTVCBSb290IENBIFgzMB4XDTAwMDkzMDIxMTIxOVoXDTIxMDkzMDE0MDExNVow +PzEkMCIGA1UEChMbRGlnaXRhbCBTaWduYXR1cmUgVHJ1c3QgQ28uMRcwFQYDVQQD +Ew5EU1QgUm9vdCBDQSBYMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB +AN+v6ZdQCINXtMxiZfaQguzH0yxrMMpb7NnDfcdAwRgUi+DoM3ZJKuM/IUmTrE4O +rz5Iy2Xu/NMhD2XSKtkyj4zl93ewEnu1lcCJo6m67XMuegwGMoOifooUMM0RoOEq +OLl5CjH9UL2AZd+3UWODyOKIYepLYYHsUmu5ouJLGiifSKOeDNoJjj4XLh7dIN9b +xiqKqy69cK3FCxolkHRyxXtqqzTWMIn/5WgTe1QLyNau7Fqckh49ZLOMxt+/yUFw +7BZy1SbsOFU5Q9D8/RhcQPGX69Wam40dutolucbY38EVAjqr2m7xPi71XAicPNaD +aeQQmxkqtilX4+U9m5/wAl0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNV +HQ8BAf8EBAMCAQYwHQYDVR0OBBYEFMSnsaR7LHH62+FLkHX/xBVghYkQMA0GCSqG +SIb3DQEBBQUAA4IBAQCjGiybFwBcqR7uKGY3Or+Dxz9LwwmglSBd49lZRNI+DT69 +ikugdB/OEIKcdBodfpga3csTS7MgROSR6cz8faXbauX+5v3gTt23ADq1cEmv8uXr +AvHRAosZy5Q6XkjEGB5YGV8eAlrwDPGxrancWYaLbumR9YbK+rlmM6pZW87ipxZz +R8srzJmwN0jP41ZL9c8PDHIyh8bwRLtTcm1D9SZImlJnt1ir/md2cXjbDaJWFBM5 +JDGFoqgCWjBH4d1QB7wCCZAA62RjYJsWvIjJEubSfZGL+T0yjWW06XyxV3bqxbYo +Ob8VZRzI9neWagqNdwvYkQsEjgfbKbYK7p2CNTUQ +-----END CERTIFICATE----- diff --git a/php/apps/phpmyadmin49/html/libraries/check_user_privileges.inc.php b/php/apps/phpmyadmin49/html/libraries/check_user_privileges.inc.php new file mode 100644 index 00000000..168a9920 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/check_user_privileges.inc.php @@ -0,0 +1,29 @@ +getCurrentUserAndHost(); +if ($username === '') { // MySQL is started with --skip-grant-tables + $GLOBALS['is_create_db_priv'] = true; + $GLOBALS['is_reload_priv'] = true; + $GLOBALS['db_to_create'] = ''; + $GLOBALS['dbs_where_create_table_allowed'] = array('*'); + $GLOBALS['dbs_to_test'] = false; + $GLOBALS['db_priv'] = true; + $GLOBALS['col_priv'] = true; + $GLOBALS['table_priv'] = true; + $GLOBALS['proc_priv'] = true; +} else { + $checkUserPrivileges->analyseShowGrant(); +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Advisor.php b/php/apps/phpmyadmin49/html/libraries/classes/Advisor.php new file mode 100644 index 00000000..b9e0e640 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Advisor.php @@ -0,0 +1,648 @@ +dbi = $dbi; + $this->expression = $expression; + /* + * Register functions for ExpressionLanguage, we intentionally + * do not implement support for compile as we do not use it. + */ + $this->expression->register( + 'round', + function (){}, + function ($arguments, $num) { + return round($num); + } + ); + $this->expression->register( + 'substr', + function (){}, + function ($arguments, $string, $start, $length) { + return substr($string, $start, $length); + } + ); + $this->expression->register( + 'preg_match', + function (){}, + function ($arguments, $pattern , $subject) { + return preg_match($pattern, $subject); + } + ); + $this->expression->register( + 'ADVISOR_bytime', + function (){}, + function ($arguments, $num, $precision) { + return self::byTime($num, $precision); + } + ); + $this->expression->register( + 'ADVISOR_timespanFormat', + function (){}, + function ($arguments, $seconds) { + return self::timespanFormat($seconds); + } + ); + $this->expression->register( + 'ADVISOR_formatByteDown', + function (){}, + function ($arguments, $value, $limes = 6, $comma = 0) { + return self::formatByteDown($value, $limes, $comma); + } + ); + $this->expression->register( + 'fired', + function (){}, + function ($arguments, $value) { + if (!isset($this->runResult['fired'])) { + return 0; + } + + // Did matching rule fire? + foreach ($this->runResult['fired'] as $rule) { + if ($rule['id'] == $value) { + return '1'; + } + } + + return '0'; + } + ); + /* Some global variables for advisor */ + $this->globals = array( + 'PMA_MYSQL_INT_VERSION' => $this->dbi->getVersion(), + ); + + } + + /** + * Get variables + * + * @return mixed + */ + public function getVariables() + { + return $this->variables; + } + + /** + * Set variables + * + * @param array $variables Variables + * + * @return Advisor + */ + public function setVariables(array $variables) + { + $this->variables = $variables; + + return $this; + } + + /** + * Set a variable and its value + * + * @param string|int $variable Variable to set + * @param mixed $value Value to set + * + * @return $this + */ + public function setVariable($variable, $value) + { + $this->variables[$variable] = $value; + + return $this; + } + + /** + * Get parseResult + * + * @return mixed + */ + public function getParseResult() + { + return $this->parseResult; + } + + /** + * Set parseResult + * + * @param array $parseResult Parse result + * + * @return Advisor + */ + public function setParseResult(array $parseResult) + { + $this->parseResult = $parseResult; + + return $this; + } + + /** + * Get runResult + * + * @return mixed + */ + public function getRunResult() + { + return $this->runResult; + } + + /** + * Set runResult + * + * @param array $runResult Run result + * + * @return Advisor + */ + public function setRunResult(array $runResult) + { + $this->runResult = $runResult; + + return $this; + } + + /** + * Parses and executes advisor rules + * + * @return array with run and parse results + */ + public function run() + { + // HowTo: A simple Advisory system in 3 easy steps. + + // Step 1: Get some variables to evaluate on + $this->setVariables( + array_merge( + $this->dbi->fetchResult('SHOW GLOBAL STATUS', 0, 1), + $this->dbi->fetchResult('SHOW GLOBAL VARIABLES', 0, 1) + ) + ); + + // Add total memory to variables as well + $sysinfo = SysInfo::get(); + $memory = $sysinfo->memory(); + $this->variables['system_memory'] + = isset($memory['MemTotal']) ? $memory['MemTotal'] : 0; + + // Step 2: Read and parse the list of rules + $this->setParseResult(static::parseRulesFile()); + // Step 3: Feed the variables to the rules and let them fire. Sets + // $runResult + $this->runRules(); + + return array( + 'parse' => array('errors' => $this->parseResult['errors']), + 'run' => $this->runResult + ); + } + + /** + * Stores current error in run results. + * + * @param string $description description of an error. + * @param Exception $exception exception raised + * + * @return void + */ + public function storeError($description, $exception) + { + $this->runResult['errors'][] = $description + . ' ' + . sprintf( + __('Error when evaluating: %s'), + $exception->getMessage() + ); + } + + /** + * Executes advisor rules + * + * @return boolean + */ + public function runRules() + { + $this->setRunResult( + array( + 'fired' => array(), + 'notfired' => array(), + 'unchecked' => array(), + 'errors' => array(), + ) + ); + + foreach ($this->parseResult['rules'] as $rule) { + $this->variables['value'] = 0; + $precond = true; + + if (isset($rule['precondition'])) { + try { + $precond = $this->ruleExprEvaluate($rule['precondition']); + } catch (Exception $e) { + $this->storeError( + sprintf( + __('Failed evaluating precondition for rule \'%s\'.'), + $rule['name'] + ), + $e + ); + continue; + } + } + + if (! $precond) { + $this->addRule('unchecked', $rule); + } else { + try { + $value = $this->ruleExprEvaluate($rule['formula']); + } catch (Exception $e) { + $this->storeError( + sprintf( + __('Failed calculating value for rule \'%s\'.'), + $rule['name'] + ), + $e + ); + continue; + } + + $this->variables['value'] = $value; + + try { + if ($this->ruleExprEvaluate($rule['test'])) { + $this->addRule('fired', $rule); + } else { + $this->addRule('notfired', $rule); + } + } catch (Exception $e) { + $this->storeError( + sprintf( + __('Failed running test for rule \'%s\'.'), + $rule['name'] + ), + $e + ); + } + } + } + + return true; + } + + /** + * Escapes percent string to be used in format string. + * + * @param string $str string to escape + * + * @return string + */ + public static function escapePercent($str) + { + return preg_replace('/%( |,|\.|$|\(|\)|<|>)/', '%%\1', $str); + } + + /** + * Wrapper function for translating. + * + * @param string $str the string + * @param string $param the parameters + * + * @return string + */ + public function translate($str, $param = null) + { + $string = _gettext(self::escapePercent($str)); + if (! is_null($param)) { + $params = $this->ruleExprEvaluate('[' . $param . ']'); + } else { + $params = array(); + } + return vsprintf($string, $params); + } + + /** + * Splits justification to text and formula. + * + * @param array $rule the rule + * + * @return string[] + */ + public static function splitJustification(array $rule) + { + $jst = preg_split('/\s*\|\s*/', $rule['justification'], 2); + if (count($jst) > 1) { + return array($jst[0], $jst[1]); + } + return array($rule['justification']); + } + + /** + * Adds a rule to the result list + * + * @param string $type type of rule + * @param array $rule rule itself + * + * @return void + */ + public function addRule($type, array $rule) + { + switch ($type) { + case 'notfired': + case 'fired': + $jst = self::splitJustification($rule); + if (count($jst) > 1) { + try { + /* Translate */ + $str = $this->translate($jst[0], $jst[1]); + } catch (Exception $e) { + $this->storeError( + sprintf( + __('Failed formatting string for rule \'%s\'.'), + $rule['name'] + ), + $e + ); + return; + } + + $rule['justification'] = $str; + } else { + $rule['justification'] = $this->translate($rule['justification']); + } + $rule['id'] = $rule['name']; + $rule['name'] = $this->translate($rule['name']); + $rule['issue'] = $this->translate($rule['issue']); + + // Replaces {server_variable} with 'server_variable' + // linking to server_variables.php + $rule['recommendation'] = preg_replace_callback( + '/\{([a-z_0-9]+)\}/Ui', + array($this, 'replaceVariable'), + $this->translate($rule['recommendation']) + ); + + // Replaces external Links with Core::linkURL() generated links + $rule['recommendation'] = preg_replace_callback( + '#href=("|\')(https?://[^\1]+)\1#i', + array($this, 'replaceLinkURL'), + $rule['recommendation'] + ); + break; + } + + $this->runResult[$type][] = $rule; + } + + /** + * Callback for wrapping links with Core::linkURL + * + * @param array $matches List of matched elements form preg_replace_callback + * + * @return string Replacement value + */ + private function replaceLinkURL(array $matches) + { + return 'href="' . Core::linkURL($matches[2]) . '" target="_blank" rel="noopener noreferrer"'; + } + + /** + * Callback for wrapping variable edit links + * + * @param array $matches List of matched elements form preg_replace_callback + * + * @return string Replacement value + */ + private function replaceVariable(array $matches) + { + return '' . htmlspecialchars($matches[1]) . ''; + } + + /** + * Runs a code expression, replacing variable names with their respective + * values + * + * @param string $expr expression to evaluate + * + * @return integer result of evaluated expression + * + * @throws Exception + */ + public function ruleExprEvaluate($expr) + { + // Actually evaluate the code + // This can throw exception + $value = $this->expression->evaluate( + $expr, + array_merge($this->variables, $this->globals) + ); + + return $value; + } + + /** + * Reads the rule file into an array, throwing errors messages on syntax + * errors. + * + * @return array with parsed data + */ + public static function parseRulesFile() + { + $filename = 'libraries/advisory_rules.txt'; + $file = file($filename, FILE_IGNORE_NEW_LINES); + + $errors = array(); + $rules = array(); + $lines = array(); + + if ($file === false) { + $errors[] = sprintf( + __('Error in reading file: The file \'%s\' does not exist or is not readable!'), + $filename + ); + return array('rules' => $rules, 'lines' => $lines, 'errors' => $errors); + } + + $ruleSyntax = array( + 'name', 'formula', 'test', 'issue', 'recommendation', 'justification' + ); + $numRules = count($ruleSyntax); + $numLines = count($file); + $ruleNo = -1; + $ruleLine = -1; + + for ($i = 0; $i < $numLines; $i++) { + $line = $file[$i]; + if ($line == "" || $line[0] == '#') { + continue; + } + + // Reading new rule + if (substr($line, 0, 4) == 'rule') { + if ($ruleLine > 0) { + $errors[] = sprintf( + __( + 'Invalid rule declaration on line %1$s, expected line ' + . '%2$s of previous rule.' + ), + $i + 1, + $ruleSyntax[$ruleLine++] + ); + continue; + } + if (preg_match("/rule\s'(.*)'( \[(.*)\])?$/", $line, $match)) { + $ruleLine = 1; + $ruleNo++; + $rules[$ruleNo] = array('name' => $match[1]); + $lines[$ruleNo] = array('name' => $i + 1); + if (isset($match[3])) { + $rules[$ruleNo]['precondition'] = $match[3]; + $lines[$ruleNo]['precondition'] = $i + 1; + } + } else { + $errors[] = sprintf( + __('Invalid rule declaration on line %s.'), + $i + 1 + ); + } + continue; + } else { + if ($ruleLine == -1) { + $errors[] = sprintf( + __('Unexpected characters on line %s.'), + $i + 1 + ); + } + } + + // Reading rule lines + if ($ruleLine > 0) { + if (!isset($line[0])) { + continue; // Empty lines are ok + } + // Non tabbed lines are not + if ($line[0] != "\t") { + $errors[] = sprintf( + __( + 'Unexpected character on line %1$s. Expected tab, but ' + . 'found "%2$s".' + ), + $i + 1, + $line[0] + ); + continue; + } + $rules[$ruleNo][$ruleSyntax[$ruleLine]] = chop( + mb_substr($line, 1) + ); + $lines[$ruleNo][$ruleSyntax[$ruleLine]] = $i + 1; + ++$ruleLine; + } + + // Rule complete + if ($ruleLine == $numRules) { + $ruleLine = -1; + } + } + + return array('rules' => $rules, 'lines' => $lines, 'errors' => $errors); + } + + /** + * Formats interval like 10 per hour + * + * @param integer $num number to format + * @param integer $precision required precision + * + * @return string formatted string + */ + public static function byTime($num, $precision) + { + if ($num >= 1) { // per second + $per = __('per second'); + } elseif ($num * 60 >= 1) { // per minute + $num = $num * 60; + $per = __('per minute'); + } elseif ($num * 60 * 60 >= 1 ) { // per hour + $num = $num * 60 * 60; + $per = __('per hour'); + } else { + $num = $num * 60 * 60 * 24; + $per = __('per day'); + } + + $num = round($num, $precision); + + if ($num == 0) { + $num = '<' . pow(10, -$precision); + } + + return "$num $per"; + } + + /** + * Wrapper for PhpMyAdmin\Util::timespanFormat + * + * This function is used when evaluating advisory_rules.txt + * + * @param int $seconds the timespan + * + * @return string the formatted value + */ + public static function timespanFormat($seconds) + { + return Util::timespanFormat($seconds); + } + + /** + * Wrapper around PhpMyAdmin\Util::formatByteDown + * + * This function is used when evaluating advisory_rules.txt + * + * @param double $value the value to format + * @param int $limes the sensitiveness + * @param int $comma the number of decimals to retain + * + * @return string the formatted value with unit + */ + public static function formatByteDown($value, $limes = 6, $comma = 0) + { + return implode(' ', Util::formatByteDown($value, $limes, $comma)); + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Bookmark.php b/php/apps/phpmyadmin49/html/libraries/classes/Bookmark.php new file mode 100644 index 00000000..9543f3e2 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Bookmark.php @@ -0,0 +1,376 @@ +dbi = $dbi; + $this->user = $user; + } + + /** + * Returns the ID of the bookmark + * + * @return int + */ + public function getId() + { + return $this->_id; + } + + /** + * Returns the database of the bookmark + * + * @return string + */ + public function getDatabase() + { + return $this->_database; + } + + /** + * Returns the user whom the bookmark belongs to + * + * @return string + */ + public function getUser() + { + return $this->_user; + } + + /** + * Returns the label of the bookmark + * + * @return string + */ + public function getLabel() + { + return $this->_label; + } + + /** + * Returns the query + * + * @return string + */ + public function getQuery() + { + return $this->_query; + } + + /** + * Adds a bookmark + * + * @return boolean whether the INSERT succeeds or not + * + * @access public + */ + public function save() + { + $cfgBookmark = self::getParams($this->user); + if (empty($cfgBookmark)) { + return false; + } + + $query = "INSERT INTO " . Util::backquote($cfgBookmark['db']) + . "." . Util::backquote($cfgBookmark['table']) + . " (id, dbase, user, query, label) VALUES (NULL, " + . "'" . $this->dbi->escapeString($this->_database) . "', " + . "'" . $this->dbi->escapeString($this->_user) . "', " + . "'" . $this->dbi->escapeString($this->_query) . "', " + . "'" . $this->dbi->escapeString($this->_label) . "')"; + return $this->dbi->query($query, DatabaseInterface::CONNECT_CONTROL); + } + + /** + * Deletes a bookmark + * + * @return bool true if successful + * + * @access public + */ + public function delete() + { + $cfgBookmark = self::getParams($this->user); + if (empty($cfgBookmark)) { + return false; + } + + $query = "DELETE FROM " . Util::backquote($cfgBookmark['db']) + . "." . Util::backquote($cfgBookmark['table']) + . " WHERE id = " . $this->_id; + return $this->dbi->tryQuery($query, DatabaseInterface::CONNECT_CONTROL); + } + + /** + * Returns the number of variables in a bookmark + * + * @return number number of variables + */ + public function getVariableCount() + { + $matches = array(); + preg_match_all("/\[VARIABLE[0-9]*\]/", $this->_query, $matches, PREG_SET_ORDER); + return count($matches); + } + + /** + * Replace the placeholders in the bookmark query with variables + * + * @param array $variables array of variables + * + * @return string query with variables applied + */ + public function applyVariables(array $variables) + { + // remove comments that encloses a variable placeholder + $query = preg_replace( + '|/\*(.*\[VARIABLE[0-9]*\].*)\*/|imsU', + '${1}', + $this->_query + ); + // replace variable placeholders with values + $number_of_variables = $this->getVariableCount(); + for ($i = 1; $i <= $number_of_variables; $i++) { + $var = ''; + if (! empty($variables[$i])) { + $var = $this->dbi->escapeString($variables[$i]); + } + $query = str_replace('[VARIABLE' . $i . ']', $var, $query); + // backward compatibility + if ($i == 1) { + $query = str_replace('[VARIABLE]', $var, $query); + } + } + return $query; + } + + /** + * Defines the bookmark parameters for the current user + * + * @return array the bookmark parameters for the current user + * @access public + */ + public static function getParams($user) + { + static $cfgBookmark = null; + + if (null !== $cfgBookmark) { + return $cfgBookmark; + } + + $relation = new Relation(); + $cfgRelation = $relation->getRelationsParam(); + if ($cfgRelation['bookmarkwork']) { + $cfgBookmark = array( + 'user' => $user, + 'db' => $cfgRelation['db'], + 'table' => $cfgRelation['bookmark'], + ); + } else { + $cfgBookmark = false; + } + + return $cfgBookmark; + } + + /** + * Creates a Bookmark object from the parameters + * + * @param array $bkm_fields the properties of the bookmark to add; here, + * $bkm_fields['bkm_sql_query'] is urlencoded + * @param boolean $all_users whether to make the bookmark available + * for all users + * + * @return Bookmark|false + */ + public static function createBookmark( + DatabaseInterface $dbi, + $user, + array $bkm_fields, + $all_users = false + ) { + if (!(isset($bkm_fields['bkm_sql_query']) + && strlen($bkm_fields['bkm_sql_query']) > 0 + && isset($bkm_fields['bkm_label']) + && strlen($bkm_fields['bkm_label']) > 0) + ) { + return false; + } + + $bookmark = new Bookmark($dbi, $user); + $bookmark->_database = $bkm_fields['bkm_database']; + $bookmark->_label = $bkm_fields['bkm_label']; + $bookmark->_query = $bkm_fields['bkm_sql_query']; + $bookmark->_user = $all_users ? '' : $bkm_fields['bkm_user']; + + return $bookmark; + } + + /** + * Gets the list of bookmarks defined for the current database + * + * @param string|bool $db the current database name or false + * + * @return Bookmark[] the bookmarks list + * + * @access public + */ + public static function getList(DatabaseInterface $dbi, $user, $db = false) + { + $cfgBookmark = self::getParams($user); + if (empty($cfgBookmark)) { + return array(); + } + + $query = "SELECT * FROM " . Util::backquote($cfgBookmark['db']) + . "." . Util::backquote($cfgBookmark['table']) + . " WHERE ( `user` = ''" + . " OR `user` = '" . $dbi->escapeString($cfgBookmark['user']) . "' )"; + if ($db !== false) { + $query .= " AND dbase = '" . $dbi->escapeString($db) . "'"; + } + $query .= " ORDER BY label ASC"; + + $result = $dbi->fetchResult( + $query, + null, + null, + DatabaseInterface::CONNECT_CONTROL, + DatabaseInterface::QUERY_STORE + ); + + if (! empty($result)) { + $bookmarks = array(); + foreach ($result as $row) { + $bookmark = new Bookmark($dbi, $user); + $bookmark->_id = $row['id']; + $bookmark->_database = $row['dbase']; + $bookmark->_user = $row['user']; + $bookmark->_label = $row['label']; + $bookmark->_query = $row['query']; + $bookmarks[] = $bookmark; + } + + return $bookmarks; + } + + return array(); + } + + /** + * Retrieve a specific bookmark + * + * @param string $db the current database name + * @param mixed $id an identifier of the bookmark to get + * @param string $id_field which field to look up the identifier + * @param boolean $action_bookmark_all true: get all bookmarks regardless + * of the owning user + * @param boolean $exact_user_match whether to ignore bookmarks with no user + * + * @return Bookmark the bookmark + * + * @access public + * + */ + public static function get( + DatabaseInterface $dbi, + $user, + $db, + $id, + $id_field = 'id', + $action_bookmark_all = false, + $exact_user_match = false + ) { + $cfgBookmark = self::getParams($user); + if (empty($cfgBookmark)) { + return null; + } + + $query = "SELECT * FROM " . Util::backquote($cfgBookmark['db']) + . "." . Util::backquote($cfgBookmark['table']) + . " WHERE dbase = '" . $dbi->escapeString($db) . "'"; + if (! $action_bookmark_all) { + $query .= " AND (user = '" + . $dbi->escapeString($cfgBookmark['user']) . "'"; + if (! $exact_user_match) { + $query .= " OR user = ''"; + } + $query .= ")"; + } + $query .= " AND " . Util::backquote($id_field) + . " = '" . $dbi->escapeString($id) . "' LIMIT 1"; + + $result = $dbi->fetchSingleRow($query, 'ASSOC', DatabaseInterface::CONNECT_CONTROL); + if (! empty($result)) { + $bookmark = new Bookmark($dbi, $user); + $bookmark->_id = $result['id']; + $bookmark->_database = $result['dbase']; + $bookmark->_user = $result['user']; + $bookmark->_label = $result['label']; + $bookmark->_query = $result['query']; + return $bookmark; + } + + return null; + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/BrowseForeigners.php b/php/apps/phpmyadmin49/html/libraries/classes/BrowseForeigners.php new file mode 100644 index 00000000..8d799740 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/BrowseForeigners.php @@ -0,0 +1,346 @@ +limitChars = (int) $limitChars; + $this->maxRows = (int) $maxRows; + $this->repeatCells = (int) $repeatCells; + $this->showAll = (bool) $showAll; + $this->themeImage = $themeImage; + } + + /** + * Function to get html for one relational key + * + * @param integer $horizontal_count the current horizontal count + * @param string $header table header + * @param array $keys all the keys + * @param integer $indexByKeyname index by keyname + * @param array $descriptions descriptions + * @param integer $indexByDescription index by description + * @param string $current_value current value on the edit form + * + * @return string $html the generated html + */ + private function getHtmlForOneKey( + $horizontal_count, + $header, + array $keys, + $indexByKeyname, + array $descriptions, + $indexByDescription, + $current_value + ) { + $horizontal_count++; + $output = ''; + + // whether the key name corresponds to the selected value in the form + $rightKeynameIsSelected = false; + $leftKeynameIsSelected = false; + + if ($this->repeatCells > 0 && $horizontal_count > $this->repeatCells) { + $output .= $header; + $horizontal_count = 0; + } + + // key names and descriptions for the left section, + // sorted by key names + $leftKeyname = $keys[$indexByKeyname]; + list( + $leftDescription, + $leftDescriptionTitle + ) = $this->getDescriptionAndTitle($descriptions[$indexByKeyname]); + + // key names and descriptions for the right section, + // sorted by descriptions + $rightKeyname = $keys[$indexByDescription]; + list( + $rightDescription, + $rightDescriptionTitle + ) = $this->getDescriptionAndTitle($descriptions[$indexByDescription]); + + $indexByDescription++; + + if (! empty($current_value)) { + $rightKeynameIsSelected = $rightKeyname == $current_value; + $leftKeynameIsSelected = $leftKeyname == $current_value; + } + + $output .= ''; + + $output .= Template::get('table/browse_foreigners/column_element')->render([ + 'keyname' => $leftKeyname, + 'description' => $leftDescription, + 'title' => $leftDescriptionTitle, + 'is_selected' => $leftKeynameIsSelected, + 'nowrap' => true, + ]); + $output .= Template::get('table/browse_foreigners/column_element')->render([ + 'keyname' => $leftKeyname, + 'description' => $leftDescription, + 'title' => $leftDescriptionTitle, + 'is_selected' => $leftKeynameIsSelected, + 'nowrap' => false, + ]); + + $output .= '' + . ''; + + $output .= Template::get('table/browse_foreigners/column_element')->render([ + 'keyname' => $rightKeyname, + 'description' => $rightDescription, + 'title' => $rightDescriptionTitle, + 'is_selected' => $rightKeynameIsSelected, + 'nowrap' => false, + ]); + $output .= Template::get('table/browse_foreigners/column_element')->render([ + 'keyname' => $rightKeyname, + 'description' => $rightDescription, + 'title' => $rightDescriptionTitle, + 'is_selected' => $rightKeynameIsSelected, + 'nowrap' => true, + ]); + + $output .= ''; + + return array($output, $horizontal_count, $indexByDescription); + } + + /** + * Function to get html for relational field selection + * + * @param string $db current database + * @param string $table current table + * @param string $field field + * @param array $foreignData foreign column data + * @param string $fieldkey field key + * @param string $current_value current columns's value + * + * @return string + */ + public function getHtmlForRelationalFieldSelection( + $db, + $table, + $field, + array $foreignData, + $fieldkey, + $current_value + ) { + $gotopage = $this->getHtmlForGotoPage($foreignData); + $foreignShowAll = Template::get('table/browse_foreigners/show_all')->render([ + 'foreign_data' => $foreignData, + 'show_all' => $this->showAll, + 'max_rows' => $this->maxRows, + ]); + + $output = '
      ' + . '
      ' + . Url::getHiddenInputs($db, $table) + . '' + . ''; + + if (isset($_POST['rownumber'])) { + $output .= ''; + } + $filter_value = (isset($_POST['foreign_filter']) + ? htmlspecialchars($_POST['foreign_filter']) + : ''); + $output .= '' + . '' + . '' + . '' + . '' + . '' . $gotopage . '' + . '' . $foreignShowAll . '' + . '
      ' + . '
      '; + + $output .= ''; + + if (!is_array($foreignData['disp_row'])) { + $output .= '' + . '
      '; + + return $output; + } + + $header = ' + ' . __('Keyname') . ' + ' . __('Description') . ' + + ' . __('Description') . ' + ' . __('Keyname') . ' + '; + + $output .= '' . $header . '' . "\n" + . '' . $header . '' . "\n" + . '' . "\n"; + + $descriptions = array(); + $keys = array(); + foreach ($foreignData['disp_row'] as $relrow) { + if ($foreignData['foreign_display'] != false) { + $descriptions[] = $relrow[$foreignData['foreign_display']]; + } else { + $descriptions[] = ''; + } + + $keys[] = $relrow[$foreignData['foreign_field']]; + } + + asort($keys); + + $horizontal_count = 0; + $indexByDescription = 0; + + foreach ($keys as $indexByKeyname => $value) { + list( + $html, + $horizontal_count, + $indexByDescription + ) = $this->getHtmlForOneKey( + $horizontal_count, + $header, + $keys, + $indexByKeyname, + $descriptions, + $indexByDescription, + $current_value + ); + $output .= $html; + } + + $output .= '' + . ''; + + return $output; + } + + /** + * Get the description (possibly truncated) and the title + * + * @param string $description the key name's description + * + * @return array the new description and title + */ + private function getDescriptionAndTitle($description) + { + if (mb_strlen($description) <= $this->limitChars) { + $description = htmlspecialchars( + $description + ); + $descriptionTitle = ''; + } else { + $descriptionTitle = htmlspecialchars( + $description + ); + $description = htmlspecialchars( + mb_substr( + $description, 0, $this->limitChars + ) + . '...' + ); + } + return array($description, $descriptionTitle); + } + + /** + * Function to get html for the goto page option + * + * @param array|null $foreignData foreign data + * + * @return string + */ + private function getHtmlForGotoPage($foreignData) + { + $gotopage = ''; + isset($_POST['pos']) ? $pos = $_POST['pos'] : $pos = 0; + if (!is_array($foreignData['disp_row'])) { + return $gotopage; + } + + $pageNow = @floor($pos / $this->maxRows) + 1; + $nbTotalPage = @ceil($foreignData['the_total'] / $this->maxRows); + + if ($foreignData['the_total'] > $this->maxRows) { + $gotopage = Util::pageselector( + 'pos', + $this->maxRows, + $pageNow, + $nbTotalPage, + 200, + 5, + 5, + 20, + 10, + __('Page number:') + ); + } + + return $gotopage; + } + + /** + * Function to get foreign limit + * + * @param string $foreignShowAll foreign navigation + * + * @return string + */ + public function getForeignLimit($foreignShowAll) + { + if (isset($foreignShowAll) && $foreignShowAll == __('Show all')) { + return null; + } + isset($_POST['pos']) ? $pos = $_POST['pos'] : $pos = 0; + return 'LIMIT ' . $pos . ', ' . $this->maxRows . ' '; + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/CentralColumns.php b/php/apps/phpmyadmin49/html/libraries/classes/CentralColumns.php new file mode 100644 index 00000000..7379cee4 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/CentralColumns.php @@ -0,0 +1,1453 @@ +dbi = $dbi; + + $this->user = $GLOBALS['cfg']['Server']['user']; + $this->maxRows = (int) $GLOBALS['cfg']['MaxRows']; + $this->charEditing = $GLOBALS['cfg']['CharEditing']; + $this->disableIs = (bool) $GLOBALS['cfg']['Server']['DisableIS']; + + $this->relation = new Relation(); + } + + /** + * Defines the central_columns parameters for the current user + * + * @return array the central_columns parameters for the current user + * @access public + */ + public function getParams() + { + static $cfgCentralColumns = null; + + if (null !== $cfgCentralColumns) { + return $cfgCentralColumns; + } + + $cfgRelation = $this->relation->getRelationsParam(); + + if ($cfgRelation['centralcolumnswork']) { + $cfgCentralColumns = array( + 'user' => $this->user, + 'db' => $cfgRelation['db'], + 'table' => $cfgRelation['central_columns'], + ); + } else { + $cfgCentralColumns = false; + } + + return $cfgCentralColumns; + } + + /** + * get $num columns of given database from central columns list + * starting at offset $from + * + * @param string $db selected database + * @param int $from starting offset of first result + * @param int $num maximum number of results to return + * + * @return array list of $num columns present in central columns list + * starting at offset $from for the given database + */ + public function getColumnsList($db, $from = 0, $num = 25) + { + $cfgCentralColumns = $this->getParams(); + if (empty($cfgCentralColumns)) { + return array(); + } + $pmadb = $cfgCentralColumns['db']; + $this->dbi->selectDb($pmadb, DatabaseInterface::CONNECT_CONTROL); + $central_list_table = $cfgCentralColumns['table']; + //get current values of $db from central column list + if ($num == 0) { + $query = 'SELECT * FROM ' . Util::backquote($central_list_table) . ' ' + . 'WHERE db_name = \'' . $this->dbi->escapeString($db) . '\';'; + } else { + $query = 'SELECT * FROM ' . Util::backquote($central_list_table) . ' ' + . 'WHERE db_name = \'' . $this->dbi->escapeString($db) . '\' ' + . 'LIMIT ' . $from . ', ' . $num . ';'; + } + $has_list = (array) $this->dbi->fetchResult( + $query, null, null, DatabaseInterface::CONNECT_CONTROL + ); + $this->handleColumnExtra($has_list); + return $has_list; + } + + /** + * Get the number of columns present in central list for given db + * + * @param string $db current database + * + * @return int number of columns in central list of columns for $db + */ + public function getCount($db) + { + $cfgCentralColumns = $this->getParams(); + if (empty($cfgCentralColumns)) { + return 0; + } + $pmadb = $cfgCentralColumns['db']; + $this->dbi->selectDb($pmadb, DatabaseInterface::CONNECT_CONTROL); + $central_list_table = $cfgCentralColumns['table']; + $query = 'SELECT count(db_name) FROM ' . + Util::backquote($central_list_table) . ' ' + . 'WHERE db_name = \'' . $this->dbi->escapeString($db) . '\';'; + $res = $this->dbi->fetchResult( + $query, null, null, DatabaseInterface::CONNECT_CONTROL + ); + if (isset($res[0])) { + return $res[0]; + } + + return 0; + } + + /** + * return the existing columns in central list among the given list of columns + * + * @param string $db the selected database + * @param string $cols comma separated list of given columns + * @param boolean $allFields set if need all the fields of existing columns, + * otherwise only column_name is returned + * + * @return array list of columns in central columns among given set of columns + */ + private function findExistingColNames( + $db, + $cols, + $allFields = false + ) { + $cfgCentralColumns = $this->getParams(); + if (empty($cfgCentralColumns)) { + return array(); + } + $pmadb = $cfgCentralColumns['db']; + $this->dbi->selectDb($pmadb, DatabaseInterface::CONNECT_CONTROL); + $central_list_table = $cfgCentralColumns['table']; + if ($allFields) { + $query = 'SELECT * FROM ' . Util::backquote($central_list_table) . ' ' + . 'WHERE db_name = \'' . $this->dbi->escapeString($db) . '\' AND col_name IN (' . $cols . ');'; + $has_list = (array) $this->dbi->fetchResult( + $query, null, null, DatabaseInterface::CONNECT_CONTROL + ); + $this->handleColumnExtra($has_list); + } else { + $query = 'SELECT col_name FROM ' + . Util::backquote($central_list_table) . ' ' + . 'WHERE db_name = \'' . $this->dbi->escapeString($db) . '\' AND col_name IN (' . $cols . ');'; + $has_list = (array) $this->dbi->fetchResult( + $query, null, null, DatabaseInterface::CONNECT_CONTROL + ); + } + + return $has_list; + } + + /** + * return error message to be displayed if central columns + * configuration storage is not completely configured + * + * @return Message + */ + private function configErrorMessage() + { + return Message::error( + __( + 'The configuration storage is not ready for the central list' + . ' of columns feature.' + ) + ); + } + + /** + * build the insert query for central columns list given PMA storage + * db, central_columns table, column name and corresponding definition to be added + * + * @param string $column column to add into central list + * @param array $def list of attributes of the column being added + * @param string $db PMA configuration storage database name + * @param string $central_list_table central columns configuration storage table name + * + * @return string query string to insert the given column + * with definition into central list + */ + private function getInsertQuery( + $column, + array $def, + $db, + $central_list_table + ) { + $type = ""; + $length = 0; + $attribute = ""; + if (isset($def['Type'])) { + $extracted_columnspec = Util::extractColumnSpec($def['Type']); + $attribute = trim($extracted_columnspec[ 'attribute']); + $type = $extracted_columnspec['type']; + $length = $extracted_columnspec['spec_in_brackets']; + } + if (isset($def['Attribute'])) { + $attribute = $def['Attribute']; + }; + $collation = isset($def['Collation'])?$def['Collation']:""; + $isNull = ($def['Null'] == "NO")?0:1; + $extra = isset($def['Extra'])?$def['Extra']:""; + $default = isset($def['Default'])?$def['Default']:""; + $insQuery = 'INSERT INTO ' + . Util::backquote($central_list_table) . ' ' + . 'VALUES ( \'' . $this->dbi->escapeString($db) . '\' ,' + . '\'' . $this->dbi->escapeString($column) . '\',\'' + . $this->dbi->escapeString($type) . '\',' + . '\'' . $this->dbi->escapeString($length) . '\',\'' + . $this->dbi->escapeString($collation) . '\',' + . '\'' . $this->dbi->escapeString($isNull) . '\',' + . '\'' . implode(',', array($extra, $attribute)) + . '\',\'' . $this->dbi->escapeString($default) . '\');'; + return $insQuery; + } + + /** + * If $isTable is true then unique columns from given tables as $field_select + * are added to central list otherwise the $field_select is considered as + * list of columns and these columns are added to central list if not already added + * + * @param array $field_select if $isTable is true selected tables list + * otherwise selected columns list + * @param bool $isTable if passed array is of tables or columns + * @param string $table if $isTable is false, then table name to + * which columns belong + * + * @return true|PhpMyAdmin\Message + */ + public function syncUniqueColumns( + array $field_select, + $isTable = true, + $table = null + ) { + $cfgCentralColumns = $this->getParams(); + if (empty($cfgCentralColumns)) { + return $this->configErrorMessage(); + } + $db = $_POST['db']; + $pmadb = $cfgCentralColumns['db']; + $central_list_table = $cfgCentralColumns['table']; + $this->dbi->selectDb($db); + $existingCols = array(); + $cols = ""; + $insQuery = array(); + $fields = array(); + $message = true; + if ($isTable) { + foreach ($field_select as $table) { + $fields[$table] = (array) $this->dbi->getColumns( + $db, $table, null, true + ); + foreach ($fields[$table] as $field => $def) { + $cols .= "'" . $this->dbi->escapeString($field) . "',"; + } + } + + $has_list = $this->findExistingColNames($db, trim($cols, ',')); + foreach ($field_select as $table) { + foreach ($fields[$table] as $field => $def) { + if (!in_array($field, $has_list)) { + $has_list[] = $field; + $insQuery[] = $this->getInsertQuery( + $field, $def, $db, $central_list_table + ); + } else { + $existingCols[] = "'" . $field . "'"; + } + } + } + } else { + if ($table === null) { + $table = $_POST['table']; + } + foreach ($field_select as $column) { + $cols .= "'" . $this->dbi->escapeString($column) . "',"; + } + $has_list = $this->findExistingColNames($db, trim($cols, ',')); + foreach ($field_select as $column) { + if (!in_array($column, $has_list)) { + $has_list[] = $column; + $field = (array) $this->dbi->getColumns( + $db, $table, $column, + true + ); + $insQuery[] = $this->getInsertQuery( + $column, $field, $db, $central_list_table + ); + } else { + $existingCols[] = "'" . $column . "'"; + } + } + } + if (! empty($existingCols)) { + $existingCols = implode(",", array_unique($existingCols)); + $message = Message::notice( + sprintf( + __( + 'Could not add %1$s as they already exist in central list!' + ), htmlspecialchars($existingCols) + ) + ); + $message->addMessage( + Message::notice( + "Please remove them first " + . "from central list if you want to update above columns" + ) + ); + } + $this->dbi->selectDb($pmadb, DatabaseInterface::CONNECT_CONTROL); + if (! empty($insQuery)) { + foreach ($insQuery as $query) { + if (!$this->dbi->tryQuery($query, DatabaseInterface::CONNECT_CONTROL)) { + $message = Message::error(__('Could not add columns!')); + $message->addMessage( + Message::rawError( + $this->dbi->getError(DatabaseInterface::CONNECT_CONTROL) + ) + ); + break; + } + } + } + return $message; + } + + /** + * if $isTable is true it removes all columns of given tables as $field_select from + * central columns list otherwise $field_select is columns list and it removes + * given columns if present in central list + * + * @param array $field_select if $isTable selected list of tables otherwise + * selected list of columns to remove from central list + * @param bool $isTable if passed array is of tables or columns + * + * @return true|PhpMyAdmin\Message + */ + public function deleteColumnsFromList( + array $field_select, + $isTable = true + ) { + $cfgCentralColumns = $this->getParams(); + if (empty($cfgCentralColumns)) { + return $this->configErrorMessage(); + } + $db = $_POST['db']; + $pmadb = $cfgCentralColumns['db']; + $central_list_table = $cfgCentralColumns['table']; + $this->dbi->selectDb($db); + $message = true; + $colNotExist = array(); + $fields = array(); + if ($isTable) { + $cols = ''; + foreach ($field_select as $table) { + $fields[$table] = (array) $this->dbi->getColumnNames( + $db, $table + ); + foreach ($fields[$table] as $col_select) { + $cols .= '\'' . $this->dbi->escapeString($col_select) . '\','; + } + } + $cols = trim($cols, ','); + $has_list = $this->findExistingColNames($db, $cols); + foreach ($field_select as $table) { + foreach ($fields[$table] as $column) { + if (!in_array($column, $has_list)) { + $colNotExist[] = "'" . $column . "'"; + } + } + } + + } else { + $cols = ''; + foreach ($field_select as $col_select) { + $cols .= '\'' . $this->dbi->escapeString($col_select) . '\','; + } + $cols = trim($cols, ','); + $has_list = $this->findExistingColNames($db, $cols); + foreach ($field_select as $column) { + if (!in_array($column, $has_list)) { + $colNotExist[] = "'" . $column . "'"; + } + } + } + if (!empty($colNotExist)) { + $colNotExist = implode(",", array_unique($colNotExist)); + $message = Message::notice( + sprintf( + __( + 'Couldn\'t remove Column(s) %1$s ' + . 'as they don\'t exist in central columns list!' + ), htmlspecialchars($colNotExist) + ) + ); + } + $this->dbi->selectDb($pmadb, DatabaseInterface::CONNECT_CONTROL); + + $query = 'DELETE FROM ' . Util::backquote($central_list_table) . ' ' + . 'WHERE db_name = \'' . $this->dbi->escapeString($db) . '\' AND col_name IN (' . $cols . ');'; + + if (!$this->dbi->tryQuery($query, DatabaseInterface::CONNECT_CONTROL)) { + $message = Message::error(__('Could not remove columns!')); + $message->addHtml('
      ' . htmlspecialchars($cols) . '
      '); + $message->addMessage( + Message::rawError( + $this->dbi->getError(DatabaseInterface::CONNECT_CONTROL) + ) + ); + } + return $message; + } + + /** + * Make the columns of given tables consistent with central list of columns. + * Updates only those columns which are not being referenced. + * + * @param string $db current database + * @param array $selected_tables list of selected tables. + * + * @return true|PhpMyAdmin\Message + */ + public function makeConsistentWithList( + $db, + array $selected_tables + ) { + $message = true; + foreach ($selected_tables as $table) { + $query = 'ALTER TABLE ' . Util::backquote($table); + $has_list = $this->getFromTable($db, $table, true); + $this->dbi->selectDb($db); + foreach ($has_list as $column) { + $column_status = $this->relation->checkChildForeignReferences( + $db, $table, $column['col_name'] + ); + //column definition can only be changed if + //it is not referenced by another column + if ($column_status['isEditable']) { + $query .= ' MODIFY ' . Util::backquote($column['col_name']) . ' ' + . $this->dbi->escapeString($column['col_type']); + if ($column['col_length']) { + $query .= '(' . $column['col_length'] . ')'; + } + + $query .= ' ' . $column['col_attribute']; + if ($column['col_isNull']) { + $query .= ' NULL'; + } else { + $query .= ' NOT NULL'; + } + + $query .= ' ' . $column['col_extra']; + if ($column['col_default']) { + if ($column['col_default'] != 'CURRENT_TIMESTAMP' + || $column['col_default'] != 'current_timestamp()') { + $query .= ' DEFAULT \'' . $this->dbi->escapeString( + $column['col_default'] + ) . '\''; + } else { + $query .= ' DEFAULT ' . $this->dbi->escapeString( + $column['col_default'] + ); + } + } + $query .= ','; + } + } + $query = trim($query, " ,") . ";"; + if (!$this->dbi->tryQuery($query)) { + if ($message === true) { + $message = Message::error( + $this->dbi->getError() + ); + } else { + $message->addText( + $this->dbi->getError(), + '
      ' + ); + } + } + } + return $message; + } + + /** + * return the columns present in central list of columns for a given + * table of a given database + * + * @param string $db given database + * @param string $table given table + * @param boolean $allFields set if need all the fields of existing columns, + * otherwise only column_name is returned + * + * @return array columns present in central list from given table of given db. + */ + public function getFromTable( + $db, + $table, + $allFields = false + ) { + $cfgCentralColumns = $this->getParams(); + if (empty($cfgCentralColumns)) { + return array(); + } + $this->dbi->selectDb($db); + $fields = (array) $this->dbi->getColumnNames( + $db, $table + ); + $cols = ''; + foreach ($fields as $col_select) { + $cols .= '\'' . $this->dbi->escapeString($col_select) . '\','; + } + $cols = trim($cols, ','); + $has_list = $this->findExistingColNames($db, $cols, $allFields); + if (! empty($has_list)) { + return (array)$has_list; + } + + return array(); + } + + /** + * update a column in central columns list if a edit is requested + * + * @param string $db current database + * @param string $orig_col_name original column name before edit + * @param string $col_name new column name + * @param string $col_type new column type + * @param string $col_attribute new column attribute + * @param string $col_length new column length + * @param int $col_isNull value 1 if new column isNull is true, 0 otherwise + * @param string $collation new column collation + * @param string $col_extra new column extra property + * @param string $col_default new column default value + * + * @return true|PhpMyAdmin\Message + */ + public function updateOneColumn( + $db, + $orig_col_name, + $col_name, + $col_type, + $col_attribute, + $col_length, + $col_isNull, + $collation, + $col_extra, + $col_default + ) { + $cfgCentralColumns = $this->getParams(); + if (empty($cfgCentralColumns)) { + return $this->configErrorMessage(); + } + $centralTable = $cfgCentralColumns['table']; + $this->dbi->selectDb($cfgCentralColumns['db'], DatabaseInterface::CONNECT_CONTROL); + if ($orig_col_name == "") { + $def = array(); + $def['Type'] = $col_type; + if ($col_length) { + $def['Type'] .= '(' . $col_length . ')'; + } + $def['Collation'] = $collation; + $def['Null'] = $col_isNull?__('YES'):__('NO'); + $def['Extra'] = $col_extra; + $def['Attribute'] = $col_attribute; + $def['Default'] = $col_default; + $query = $this->getInsertQuery($col_name, $def, $db, $centralTable); + } else { + $query = 'UPDATE ' . Util::backquote($centralTable) + . ' SET col_type = \'' . $this->dbi->escapeString($col_type) . '\'' + . ', col_name = \'' . $this->dbi->escapeString($col_name) . '\'' + . ', col_length = \'' . $this->dbi->escapeString($col_length) . '\'' + . ', col_isNull = ' . $col_isNull + . ', col_collation = \'' . $this->dbi->escapeString($collation) . '\'' + . ', col_extra = \'' + . implode(',', array($col_extra, $col_attribute)) . '\'' + . ', col_default = \'' . $this->dbi->escapeString($col_default) . '\'' + . ' WHERE db_name = \'' . $this->dbi->escapeString($db) . '\' ' + . 'AND col_name = \'' . $this->dbi->escapeString($orig_col_name) + . '\''; + } + if (!$this->dbi->tryQuery($query, DatabaseInterface::CONNECT_CONTROL)) { + return Message::error( + $this->dbi->getError(DatabaseInterface::CONNECT_CONTROL) + ); + } + return true; + } + + /** + * Update Multiple column in central columns list if a chnage is requested + * + * @return true|PhpMyAdmin\Message + */ + public function updateMultipleColumn() + { + $db = $_POST['db']; + $col_name = $_POST['field_name']; + $orig_col_name = $_POST['orig_col_name']; + $col_default = $_POST['field_default_type']; + $col_length = $_POST['field_length']; + $col_attribute = $_POST['field_attribute']; + $col_type = $_POST['field_type']; + $collation = $_POST['field_collation']; + $col_isNull = array(); + $col_extra = array(); + $num_central_fields = count($orig_col_name); + for ($i = 0; $i < $num_central_fields ; $i++) { + $col_isNull[$i] = isset($_POST['field_null'][$i]) ? 1 : 0; + $col_extra[$i] = isset($_POST['col_extra'][$i]) + ? $_POST['col_extra'][$i] : ''; + + if ($col_default[$i] == 'NONE') { + $col_default[$i] = ""; + } elseif ($col_default[$i] == 'USER_DEFINED') { + $col_default[$i] = $_POST['field_default_value'][$i]; + } + + $message = $this->updateOneColumn( + $db, $orig_col_name[$i], $col_name[$i], $col_type[$i], + $col_attribute[$i], $col_length[$i], $col_isNull[$i], $collation[$i], + $col_extra[$i], $col_default[$i] + ); + if (!is_bool($message)) { + return $message; + } + } + return true; + } + + /** + * get the html for table navigation in Central columns page + * + * @param int $total_rows total number of rows in complete result set + * @param int $pos offset of first result with complete result set + * @param string $db current database + * + * @return string html for table navigation in Central columns page + */ + public function getHtmlForTableNavigation($total_rows, $pos, $db) + { + $pageNow = ($pos / $this->maxRows) + 1; + $nbTotalPage = ceil($total_rows / $this->maxRows); + $page_selector = ($nbTotalPage > 1)?(Util::pageselector( + 'pos', $this->maxRows, $pageNow, $nbTotalPage + )):''; + return Template::get('database/central_columns/table_navigation')->render(array( + "pos" => $pos, + "max_rows" => $this->maxRows, + "db" => $db, + "total_rows" => $total_rows, + "nb_total_page" => $nbTotalPage, + "page_selector" => $page_selector, + )); + } + + /** + * function generate and return the table header for central columns page + * + * @param string $class styling class of 'th' elements + * @param string $title title of the 'th' elements + * @param integer $actionCount number of actions + * + * @return string html for table header in central columns view/edit page + */ + public function getTableHeader($class = '', $title = '', $actionCount = 0) + { + $action = ''; + if ($actionCount > 0) { + $action .= '' + . __('Action') . ''; + } + $tableheader = ''; + $tableheader .= '' + . '' + . '' + . $action + . '' + . __('Name') . '
      ' + . '' + . __('Type') . '
      ' + . '' + . __('Length/Values') . '
      ' + . '' + . __('Default') . '
      ' + . '' . __('Collation') . '
      ' + . '' + . __('Attribute') . '
      ' + . '' + . __('Null') . '
      ' + . '' + . __('A_I') . '
      ' + . ''; + $tableheader .= ''; + return $tableheader; + } + + /** + * Function generate and return the table header for + * multiple edit central columns page + * + * @param array $headers headers list + * + * @return string html for table header in central columns multi edit page + */ + private function getEditTableHeader(array $headers) + { + return Template::get( + 'database/central_columns/edit_table_header' + )->render([ + 'headers' => $headers, + ]); + } + + /** + * build the dropdown select html for tables of given database + * + * @param string $db current database + * + * @return string html dropdown for selecting table + */ + private function getHtmlForTableDropdown($db) + { + $this->dbi->selectDb($db); + $tables = $this->dbi->getTables($db); + $selectHtml = ''; + return $selectHtml; + } + + /** + * build dropdown select html to select column in selected table, + * include only columns which are not already in central list + * + * @param string $db current database to which selected table belongs + * @param string $selected_tbl selected table + * + * @return string html to select column + */ + public function getHtmlForColumnDropdown($db, $selected_tbl) + { + $existing_cols = $this->getFromTable($db, $selected_tbl); + $this->dbi->selectDb($db); + $columns = (array) $this->dbi->getColumnNames( + $db, $selected_tbl + ); + $selectColHtml = ""; + foreach ($columns as $column) { + if (!in_array($column, $existing_cols)) { + $selectColHtml .= ''; + } + } + return $selectColHtml; + } + + /** + * HTML to display the form that let user to add a column on Central columns page + * + * @param int $total_rows total number of rows in complete result set + * @param int $pos offset of first result with complete result set + * @param string $db current database + * + * @return string html to add a column in the central list + */ + public function getHtmlForAddColumn( + $total_rows, + $pos, + $db + ) { + $icon = Util::getIcon( + 'centralColumns_add', + __('Add column') + ); + $table_drop_down = $this->getHtmlForTableDropdown($db); + return Template::get('database/central_columns/add_column')->render(array( + 'icon' => $icon, + 'pos' => $pos, + 'db' => $db, + 'total_rows' => $total_rows, + 'table_drop_down' => $table_drop_down, + )); + } + + /** + * build html for a row in central columns table + * + * @param array $row array contains complete information of a particular row of central list table + * @param int $row_num position the row in the table + * @param string $db current database + * + * @return string html of a particular row in the central columns table. + */ + public function getHtmlForTableRow(array $row, $row_num, $db) + { + $tableHtml = '' + . Url::getHiddenInputs( + $db + ) + . '' + . '' + . '' + . '' + . '' + . '' . Util::getIcon('b_edit', __('Edit')) . '' + . '' + . '' . Util::getIcon('b_drop', __('Delete')) . '' + . '' + . '' + . ''; + + $tableHtml .= + '' + . '' . htmlspecialchars($row['col_name']) . '' + . '' + . Template::get('columns_definitions/column_name')->render(array( + 'column_number' => $row_num, + 'ci' => 0, + 'ci_offset' => 0, + 'column_meta' => array( + 'Field'=>$row['col_name'] + ), + 'cfg_relation' => array( + 'centralcolumnswork' => false + ), + 'max_rows' => $this->maxRows, + )) + . ''; + $tableHtml .= + '' + . htmlspecialchars($row['col_type']) . '' + . Template::get('columns_definitions/column_type') + ->render( + array( + 'column_number' => $row_num, + 'ci' => 1, + 'ci_offset' => 0, + 'type_upper' => mb_strtoupper($row['col_type']), + 'column_meta' => array() + ) + ) + . ''; + $tableHtml .= + '' + . '' . ($row['col_length']?htmlspecialchars($row['col_length']):"") + . '' + . Template::get('columns_definitions/column_length')->render( + array( + 'column_number' => $row_num, + 'ci' => 2, + 'ci_offset' => 0, + 'length_values_input_size' => 8, + 'length_to_display' => $row['col_length'] + ) + ) + . ''; + + $meta = array(); + if (!isset($row['col_default']) || $row['col_default'] == '') { + $meta['DefaultType'] = 'NONE'; + } else { + if ($row['col_default'] == 'CURRENT_TIMESTAMP' + || $row['col_default'] == 'current_timestamp()' + ) { + $meta['DefaultType'] = 'CURRENT_TIMESTAMP'; + } elseif ($row['col_default'] == 'NULL') { + $meta['DefaultType'] = $row['col_default']; + } else { + $meta['DefaultType'] = 'USER_DEFINED'; + $meta['DefaultValue'] = $row['col_default']; + } + } + $tableHtml .= + '' . (isset($row['col_default']) + ? htmlspecialchars($row['col_default']) : 'None') + . '' + . Template::get('columns_definitions/column_default') + ->render( + array( + 'column_number' => $row_num, + 'ci' => 3, + 'ci_offset' => 0, + 'type_upper' => mb_strtoupper($row['col_type']), + 'column_meta' => $meta, + 'char_editing' => $this->charEditing, + ) + ) + . ''; + + $tableHtml .= + '' + . '' . htmlspecialchars($row['col_collation']) . '' + . Charsets::getCollationDropdownBox( + $this->dbi, + $this->disableIs, + 'field_collation[' . $row_num . ']', + 'field_' . $row_num . '_4', $row['col_collation'], false + ) + . ''; + $tableHtml .= + '' + . '' . + ($row['col_attribute'] + ? htmlspecialchars($row['col_attribute']) : "" ) + . '' + . Template::get('columns_definitions/column_attribute') + ->render( + array( + 'column_number' => $row_num, + 'ci' => 5, + 'ci_offset' => 0, + 'extracted_columnspec' => array(), + 'column_meta' => $row['col_attribute'], + 'submit_attribute' => false, + 'attribute_types' => $this->dbi->types->getAttributes(), + ) + ) + . ''; + $tableHtml .= + '' + . '' . ($row['col_isNull'] ? __('Yes') : __('No')) + . '' + . Template::get('columns_definitions/column_null') + ->render( + array( + 'column_number' => $row_num, + 'ci' => 6, + 'ci_offset' => 0, + 'column_meta' => array( + 'Null' => $row['col_isNull'] + ) + ) + ) + . ''; + + $tableHtml .= + '' + . htmlspecialchars($row['col_extra']) . '' + . Template::get('columns_definitions/column_extra')->render( + array( + 'column_number' => $row_num, + 'ci' => 7, + 'ci_offset' => 0, + 'column_meta' => array('Extra'=>$row['col_extra']) + ) + ) + . ''; + + $tableHtml .= ''; + + return $tableHtml; + } + + /** + * build html for editing a row in central columns table + * + * @param array $row array contains complete information of a + * particular row of central list table + * @param int $row_num position the row in the table + * + * @return string html of a particular row in the central columns table. + */ + private function getHtmlForEditTableRow(array $row, $row_num) + { + $tableHtml = '' + . '' + . '' + . Template::get('columns_definitions/column_name')->render(array( + 'column_number' => $row_num, + 'ci' => 0, + 'ci_offset' => 0, + 'column_meta' => array( + 'Field' => $row['col_name'] + ), + 'cfg_relation' => array( + 'centralcolumnswork' => false + ), + 'max_rows' => $this->maxRows, + )) + . ''; + $tableHtml .= + '' + . Template::get('columns_definitions/column_type') + ->render( + array( + 'column_number' => $row_num, + 'ci' => 1, + 'ci_offset' => 0, + 'type_upper' => mb_strtoupper($row['col_type']), + 'column_meta' => array() + ) + ) + . ''; + $tableHtml .= + '' + . Template::get('columns_definitions/column_length')->render( + array( + 'column_number' => $row_num, + 'ci' => 2, + 'ci_offset' => 0, + 'length_values_input_size' => 8, + 'length_to_display' => $row['col_length'] + ) + ) + . ''; + $meta = array(); + if (!isset($row['col_default']) || $row['col_default'] == '') { + $meta['DefaultType'] = 'NONE'; + } else { + if ($row['col_default'] == 'CURRENT_TIMESTAMP' + || $row['col_default'] == 'current_timestamp()' + ) { + $meta['DefaultType'] = 'CURRENT_TIMESTAMP'; + } elseif ($row['col_default'] == 'NULL') { + $meta['DefaultType'] = $row['col_default']; + } else { + $meta['DefaultType'] = 'USER_DEFINED'; + $meta['DefaultValue'] = $row['col_default']; + } + } + $tableHtml .= + '' + . Template::get('columns_definitions/column_default') + ->render( + array( + 'column_number' => $row_num, + 'ci' => 3, + 'ci_offset' => 0, + 'type_upper' => mb_strtoupper($row['col_default']), + 'column_meta' => $meta, + 'char_editing' => $this->charEditing, + ) + ) + . ''; + $tableHtml .= + '' + . Charsets::getCollationDropdownBox( + $this->dbi, + $this->disableIs, + 'field_collation[' . $row_num . ']', + 'field_' . $row_num . '_4', $row['col_collation'], false + ) + . ''; + $tableHtml .= + '' + . Template::get('columns_definitions/column_attribute') + ->render( + array( + 'column_number' => $row_num, + 'ci' => 5, + 'ci_offset' => 0, + 'extracted_columnspec' => array( + 'attribute' => $row['col_attribute'] + ), + 'column_meta' => array(), + 'submit_attribute' => false, + 'attribute_types' => $this->dbi->types->getAttributes(), + ) + ) + . ''; + $tableHtml .= + '' + . Template::get('columns_definitions/column_null') + ->render( + array( + 'column_number' => $row_num, + 'ci' => 6, + 'ci_offset' => 0, + 'column_meta' => array( + 'Null' => $row['col_isNull'] + ) + ) + ) + . ''; + + $tableHtml .= + '' + . Template::get('columns_definitions/column_extra')->render( + array( + 'column_number' => $row_num, + 'ci' => 7, + 'ci_offset' => 0, + 'column_meta' => array('Extra' => $row['col_extra']) + ) + ) + . ''; + $tableHtml .= ''; + return $tableHtml; + } + + /** + * get the list of columns in given database excluding + * the columns present in current table + * + * @param string $db selected database + * @param string $table current table name + * + * @return string encoded list of columns present in central list for the given + * database + */ + public function getListRaw($db, $table) + { + $cfgCentralColumns = $this->getParams(); + if (empty($cfgCentralColumns)) { + return json_encode(array()); + } + $centralTable = $cfgCentralColumns['table']; + if (empty($table) || $table == '') { + $query = 'SELECT * FROM ' . Util::backquote($centralTable) . ' ' + . 'WHERE db_name = \'' . $this->dbi->escapeString($db) . '\';'; + } else { + $this->dbi->selectDb($db); + $columns = (array) $this->dbi->getColumnNames( + $db, $table + ); + $cols = ''; + foreach ($columns as $col_select) { + $cols .= '\'' . $this->dbi->escapeString($col_select) . '\','; + } + $cols = trim($cols, ','); + $query = 'SELECT * FROM ' . Util::backquote($centralTable) . ' ' + . 'WHERE db_name = \'' . $this->dbi->escapeString($db) . '\''; + if ($cols) { + $query .= ' AND col_name NOT IN (' . $cols . ')'; + } + $query .= ';'; + } + $this->dbi->selectDb($cfgCentralColumns['db'], DatabaseInterface::CONNECT_CONTROL); + $columns_list = (array)$this->dbi->fetchResult( + $query, null, null, DatabaseInterface::CONNECT_CONTROL + ); + $this->handleColumnExtra($columns_list); + return json_encode($columns_list); + } + + /** + * Get HTML for "check all" check box with "with selected" dropdown + * + * @param string $pmaThemeImage pma theme image url + * @param string $text_dir url for text directory + * + * @return string $html_output + */ + public function getTableFooter($pmaThemeImage, $text_dir) + { + $html_output = Template::get('select_all') + ->render( + array( + 'pma_theme_image' => $pmaThemeImage, + 'text_dir' => $text_dir, + 'form_name' => 'tableslistcontainer', + ) + ); + $html_output .= Util::getButtonOrImage( + 'edit_central_columns', 'mult_submit change_central_columns', + __('Edit'), 'b_edit', 'edit central columns' + ); + $html_output .= Util::getButtonOrImage( + 'delete_central_columns', 'mult_submit', + __('Delete'), 'b_drop', + 'remove_from_central_columns' + ); + return $html_output; + } + + /** + * function generate and return the table footer for + * multiple edit central columns page + * + * @return string html for table footer in central columns multi edit page + */ + private function getEditTableFooter() + { + $html_output = '
      ' + . '' + . '
      '; + return $html_output; + } + + /** + * Column `col_extra` is used to store both extra and attributes for a column. + * This method separates them. + * + * @param array &$columns_list columns list + * + * @return void + */ + private function handleColumnExtra(array &$columns_list) + { + foreach ($columns_list as &$row) { + $vals = explode(',', $row['col_extra']); + + if (in_array('BINARY', $vals)) { + $row['col_attribute'] = 'BINARY'; + } elseif (in_array('UNSIGNED', $vals)) { + $row['col_attribute'] = 'UNSIGNED'; + } elseif (in_array('UNSIGNED ZEROFILL', $vals)) { + $row['col_attribute'] = 'UNSIGNED ZEROFILL'; + } elseif (in_array('on update CURRENT_TIMESTAMP', $vals)) { + $row['col_attribute'] = 'on update CURRENT_TIMESTAMP'; + } else { + $row['col_attribute'] = ''; + } + + if (in_array('auto_increment', $vals)) { + $row['col_extra'] = 'auto_increment'; + } else { + $row['col_extra'] = ''; + } + } + } + + /** + * build html for adding a new user defined column to central list + * + * @param string $db current database + * @param integer $total_rows number of rows in central columns + * + * @return string html of the form to let user add a new user defined column to the + * list + */ + public function getHtmlForAddNewColumn($db, $total_rows) + { + $addNewColumn = '
      ' + . '+ ' . __('Add new column') . '' + . '
      ' + . Url::getHiddenInputs( + $db + ) + . '' + . '
      ' + . ''; + $addNewColumn .= $this->getTableHeader(); + $addNewColumn .= '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . ' ' + . ''; + $addNewColumn .= '
      ' + . Template::get('columns_definitions/column_name')->render(array( + 'column_number' => 0, + 'ci' => 0, + 'ci_offset' => 0, + 'column_meta' => array(), + 'cfg_relation' => array( + 'centralcolumnswork' => false + ), + 'max_rows' => $this->maxRows, + )) + . '' + . Template::get('columns_definitions/column_type') + ->render( + array( + 'column_number' => 0, + 'ci' => 1, + 'ci_offset' => 0, + 'type_upper' => '', + 'column_meta' => array() + ) + ) + . '' + . Template::get('columns_definitions/column_length')->render( + array( + 'column_number' => 0, + 'ci' => 2, + 'ci_offset' => 0, + 'length_values_input_size' => 8, + 'length_to_display' => '' + ) + ) + . '' + . Template::get('columns_definitions/column_default') + ->render( + array( + 'column_number' => 0, + 'ci' => 3, + 'ci_offset' => 0, + 'type_upper' => '', + 'column_meta' => array(), + 'char_editing' => $this->charEditing, + ) + ) + . '' + . Charsets::getCollationDropdownBox( + $this->dbi, + $this->disableIs, + 'field_collation[0]', + 'field_0_4', null, false + ) + . '' + . Template::get('columns_definitions/column_attribute') + ->render( + array( + 'column_number' => 0, + 'ci' => 5, + 'ci_offset' => 0, + 'extracted_columnspec' => array(), + 'column_meta' => array(), + 'submit_attribute' => false, + 'attribute_types' => $this->dbi->types->getAttributes(), + ) + ) + . '' + . Template::get('columns_definitions/column_null') + ->render( + array( + 'column_number' => 0, + 'ci' => 6, + 'ci_offset' => 0, + 'column_meta' => array() + ) + ) + . '' + . Template::get('columns_definitions/column_extra')->render( + array( + 'column_number' => 0, + 'ci' => 7, + 'ci_offset' => 0, + 'column_meta' => array() + ) + ) + . '' + . '
      '; + return $addNewColumn; + } + + /** + * Get HTML for editing page central columns + * + * @param array $selected_fld Array containing the selected fields + * @param string $selected_db String containing the name of database + * + * @return string HTML for complete editing page for central columns + */ + public function getHtmlForEditingPage(array $selected_fld, $selected_db) + { + $html = '
      '; + $header_cells = array( + __('Name'), __('Type'), __('Length/Values'), __('Default'), + __('Collation'), __('Attributes'), __('Null'), __('A_I') + ); + $html .= $this->getEditTableHeader($header_cells); + $selected_fld_safe = array(); + foreach ($selected_fld as $key) { + $selected_fld_safe[] = $this->dbi->escapeString($key); + } + $columns_list = implode("','", $selected_fld_safe); + $columns_list = "'" . $columns_list . "'"; + $list_detail_cols = $this->findExistingColNames($selected_db, $columns_list, true); + $row_num = 0; + foreach ($list_detail_cols as $row) { + $tableHtmlRow = $this->getHtmlForEditTableRow( + $row, + $row_num + ); + $html .= $tableHtmlRow; + $row_num++; + } + $html .= ''; + $html .= $this->getEditTableFooter(); + $html .= '
      '; + return $html; + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Charsets.php b/php/apps/phpmyadmin49/html/libraries/classes/Charsets.php new file mode 100644 index 00000000..cfb6cb83 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Charsets.php @@ -0,0 +1,700 @@ + 'big5', + 'cp-866' => 'cp866', + 'euc-jp' => 'ujis', + 'euc-kr' => 'euckr', + 'gb2312' => 'gb2312', + 'gbk' => 'gbk', + 'iso-8859-1' => 'latin1', + 'iso-8859-2' => 'latin2', + 'iso-8859-7' => 'greek', + 'iso-8859-8' => 'hebrew', + 'iso-8859-8-i' => 'hebrew', + 'iso-8859-9' => 'latin5', + 'iso-8859-13' => 'latin7', + 'iso-8859-15' => 'latin1', + 'koi8-r' => 'koi8r', + 'shift_jis' => 'sjis', + 'tis-620' => 'tis620', + 'utf-8' => 'utf8', + 'windows-1250' => 'cp1250', + 'windows-1251' => 'cp1251', + 'windows-1252' => 'latin1', + 'windows-1256' => 'cp1256', + 'windows-1257' => 'cp1257', + ); + + private static $_charsets = array(); + + /** + * The charset for the server + * + * @var string + */ + private static $_charset_server; + + private static $_charsets_descriptions = array(); + private static $_collations = array(); + private static $_default_collations = array(); + + /** + * Loads charset data from the MySQL server. + * + * @param DatabaseInterface $dbi DatabaseInterface instance + * @param boolean $disableIs Disable use of INFORMATION_SCHEMA + * + * @return void + */ + private static function loadCharsets(DatabaseInterface $dbi, $disableIs) + { + /* Data already loaded */ + if (count(self::$_charsets) > 0) { + return; + } + + if ($disableIs) { + $sql = 'SHOW CHARACTER SET'; + } else { + $sql = 'SELECT `CHARACTER_SET_NAME` AS `Charset`,' + . ' `DESCRIPTION` AS `Description`' + . ' FROM `information_schema`.`CHARACTER_SETS`'; + } + $res = $dbi->query($sql); + + self::$_charsets = array(); + while ($row = $dbi->fetchAssoc($res)) { + $name = $row['Charset']; + self::$_charsets[] = $name; + self::$_charsets_descriptions[$name] = $row['Description']; + } + $dbi->freeResult($res); + + sort(self::$_charsets, SORT_STRING); + } + + /** + * Loads collation data from the MySQL server. + * + * @param DatabaseInterface $dbi DatabaseInterface instance + * @param boolean $disableIs Disable use of INFORMATION_SCHEMA + * + * @return void + */ + private static function loadCollations(DatabaseInterface $dbi, $disableIs) + { + /* Data already loaded */ + if (count(self::$_collations) > 0) { + return; + } + + if ($disableIs) { + $sql = 'SHOW COLLATION'; + } else { + $sql = 'SELECT `CHARACTER_SET_NAME` AS `Charset`,' + . ' `COLLATION_NAME` AS `Collation`, `IS_DEFAULT` AS `Default`' + . ' FROM `information_schema`.`COLLATIONS`'; + } + + $res = $dbi->query($sql); + while ($row = $dbi->fetchAssoc($res)) { + $char_set_name = $row['Charset']; + $name = $row['Collation']; + self::$_collations[$char_set_name][] = $name; + if ($row['Default'] == 'Yes' || $row['Default'] == '1') { + self::$_default_collations[$char_set_name] = $name; + } + } + $dbi->freeResult($res); + + foreach (self::$_collations as $key => $value) { + sort(self::$_collations[$key], SORT_STRING); + } + } + + /** + * Get current MySQL server charset. + * + * @param DatabaseInterface $dbi DatabaseInterface instance + * + * @return string + */ + public static function getServerCharset(DatabaseInterface $dbi) + { + if (self::$_charset_server) { + return self::$_charset_server; + } else { + self::$_charset_server = $dbi->getVariable('character_set_server'); + return self::$_charset_server; + } + } + + /** + * Get MySQL charsets + * + * @param DatabaseInterface $dbi DatabaseInterface instance + * @param boolean $disableIs Disable use of INFORMATION_SCHEMA + * + * @return array + */ + public static function getMySQLCharsets(DatabaseInterface $dbi, $disableIs) + { + self::loadCharsets($dbi, $disableIs); + return self::$_charsets; + } + + /** + * Get MySQL charsets descriptions + * + * @param DatabaseInterface $dbi DatabaseInterface instance + * @param boolean $disableIs Disable use of INFORMATION_SCHEMA + * + * @return array + */ + public static function getMySQLCharsetsDescriptions(DatabaseInterface $dbi, $disableIs) + { + self::loadCharsets($dbi, $disableIs); + return self::$_charsets_descriptions; + } + + /** + * Get MySQL collations + * + * @param DatabaseInterface $dbi DatabaseInterface instance + * @param boolean $disableIs Disable use of INFORMATION_SCHEMA + * + * @return array + */ + public static function getMySQLCollations(DatabaseInterface $dbi, $disableIs) + { + self::loadCollations($dbi, $disableIs); + return self::$_collations; + } + + /** + * Get MySQL default collations + * + * @param DatabaseInterface $dbi DatabaseInterface instance + * @param boolean $disableIs Disable use of INFORMATION_SCHEMA + * + * @return array + */ + public static function getMySQLCollationsDefault(DatabaseInterface $dbi, $disableIs) + { + self::loadCollations($dbi, $disableIs); + return self::$_default_collations; + } + + /** + * Generate charset dropdown box + * + * @param DatabaseInterface $dbi DatabaseInterface instance + * @param boolean $disableIs Disable use of INFORMATION_SCHEMA + * @param string $name Element name + * @param string $id Element id + * @param null|string $default Default value + * @param bool $label Label + * @param bool $submitOnChange Submit on change + * + * @return string + */ + public static function getCharsetDropdownBox( + DatabaseInterface $dbi, + $disableIs, + $name = null, + $id = null, + $default = null, + $label = true, + $submitOnChange = false + ) { + self::loadCharsets($dbi, $disableIs); + if (empty($name)) { + $name = 'character_set'; + } + + $return_str = '' . "\n"; + + return $return_str; + } + + /** + * Generate collation dropdown box + * + * @param DatabaseInterface $dbi DatabaseInterface instance + * @param boolean $disableIs Disable use of INFORMATION_SCHEMA + * @param string $name Element name + * @param string $id Element id + * @param null|string $default Default value + * @param bool $label Label + * @param bool $submitOnChange Submit on change + * + * @return string + */ + public static function getCollationDropdownBox( + DatabaseInterface $dbi, + $disableIs, + $name = null, + $id = null, + $default = null, + $label = true, + $submitOnChange = false + ) { + self::loadCharsets($dbi, $disableIs); + self::loadCollations($dbi, $disableIs); + if (empty($name)) { + $name = 'collation'; + } + + $return_str = '' . "\n"; + + return $return_str; + } + + /** + * Returns description for given collation + * + * @param string $collation MySQL collation string + * + * @return string collation description + */ + public static function getCollationDescr($collation) + { + $parts = explode('_', $collation); + + $name = __('Unknown'); + $variant = null; + $suffixes = array(); + $unicode = false; + $unknown = false; + + $level = 0; + foreach ($parts as $part) { + if ($level == 0) { + /* Next will be language */ + $level = 1; + /* First should be charset */ + switch ($part) { + case 'binary': + $name = _pgettext('Collation', 'Binary'); + break; + // Unicode charsets + case 'utf8mb4': + $variant = 'UCA 4.0.0'; + // Fall through to other unicode + case 'ucs2': + case 'utf8': + case 'utf16': + case 'utf16le': + case 'utf16be': + case 'utf32': + $name = _pgettext('Collation', 'Unicode'); + $unicode = true; + break; + // West European charsets + case 'ascii': + case 'cp850': + case 'dec8': + case 'hp8': + case 'latin1': + case 'macroman': + $name = _pgettext('Collation', 'West European'); + break; + // Central European charsets + case 'cp1250': + case 'cp852': + case 'latin2': + case 'macce': + $name = _pgettext('Collation', 'Central European'); + break; + // Russian charsets + case 'cp866': + case 'koi8r': + $name = _pgettext('Collation', 'Russian'); + break; + // Chinese charsets + case 'gb2312': + case 'gbk': + $name = _pgettext('Collation', 'Simplified Chinese'); + break; + case 'big5': + $name = _pgettext('Collation', 'Traditional Chinese'); + break; + case 'gb18030': + $name = _pgettext('Collation', 'Chinese'); + $unicode = true; + break; + // Japanese charsets + case 'sjis': + case 'ujis': + case 'cp932': + case 'eucjpms': + $name = _pgettext('Collation', 'Japanese'); + break; + // Baltic charsets + case 'cp1257': + case 'latin7': + $name = _pgettext('Collation', 'Baltic'); + break; + // Other + case 'armscii8': + case 'armscii': + $name = _pgettext('Collation', 'Armenian'); + break; + case 'cp1251': + $name = _pgettext('Collation', 'Cyrillic'); + break; + case 'cp1256': + $name = _pgettext('Collation', 'Arabic'); + break; + case 'euckr': + $name = _pgettext('Collation', 'Korean'); + break; + case 'hebrew': + $name = _pgettext('Collation', 'Hebrew'); + break; + case 'geostd8': + $name = _pgettext('Collation', 'Georgian'); + break; + case 'greek': + $name = _pgettext('Collation', 'Greek'); + break; + case 'keybcs2': + $name = _pgettext('Collation', 'Czech-Slovak'); + break; + case 'koi8u': + $name = _pgettext('Collation', 'Ukrainian'); + break; + case 'latin5': + $name = _pgettext('Collation', 'Turkish'); + break; + case 'swe7': + $name = _pgettext('Collation', 'Swedish'); + break; + case 'tis620': + $name = _pgettext('Collation', 'Thai'); + break; + default: + $name = _pgettext('Collation', 'Unknown'); + $unknown = true; + break; + } + continue; + } + if ($level == 1) { + /* Next will be variant unless changed later */ + $level = 4; + /* Locale name or code */ + $found = true; + switch ($part) { + case 'general': + break; + case 'bulgarian': + case 'bg': + $name = _pgettext('Collation', 'Bulgarian'); + break; + case 'chinese': + case 'cn': + case 'zh': + if ($unicode) { + $name = _pgettext('Collation', 'Chinese'); + } + break; + case 'croatian': + case 'hr': + $name = _pgettext('Collation', 'Croatian'); + break; + case 'czech': + case 'cs': + $name = _pgettext('Collation', 'Czech'); + break; + case 'danish': + case 'da': + $name = _pgettext('Collation', 'Danish'); + break; + case 'english': + case 'en': + $name = _pgettext('Collation', 'English'); + break; + case 'esperanto': + case 'eo': + $name = _pgettext('Collation', 'Esperanto'); + break; + case 'estonian': + case 'et': + $name = _pgettext('Collation', 'Estonian'); + break; + case 'german1': + $name = _pgettext('Collation', 'German (dictionary order)'); + break; + case 'german2': + $name = _pgettext('Collation', 'German (phone book order)'); + break; + case 'german': + case 'de': + /* Name is set later */ + $level = 2; + break; + case 'hungarian': + case 'hu': + $name = _pgettext('Collation', 'Hungarian'); + break; + case 'icelandic': + case 'is': + $name = _pgettext('Collation', 'Icelandic'); + break; + case 'japanese': + case 'ja': + $name = _pgettext('Collation', 'Japanese'); + break; + case 'la': + $name = _pgettext('Collation', 'Classical Latin'); + break; + case 'latvian': + case 'lv': + $name = _pgettext('Collation', 'Latvian'); + break; + case 'lithuanian': + case 'lt': + $name = _pgettext('Collation', 'Lithuanian'); + break; + case 'korean': + case 'ko': + $name = _pgettext('Collation', 'Korean'); + break; + case 'myanmar': + case 'my': + $name = _pgettext('Collation', 'Burmese'); + break; + case 'persian': + $name = _pgettext('Collation', 'Persian'); + break; + case 'polish': + case 'pl': + $name = _pgettext('Collation', 'Polish'); + break; + case 'roman': + $name = _pgettext('Collation', 'West European'); + break; + case 'romanian': + case 'ro': + $name = _pgettext('Collation', 'Romanian'); + break; + case 'ru': + $name = _pgettext('Collation', 'Russian'); + break; + case 'si': + case 'sinhala': + $name = _pgettext('Collation', 'Sinhalese'); + break; + case 'slovak': + case 'sk': + $name = _pgettext('Collation', 'Slovak'); + break; + case 'slovenian': + case 'sl': + $name = _pgettext('Collation', 'Slovenian'); + break; + case 'spanish': + $name = _pgettext('Collation', 'Spanish (modern)'); + break; + case 'es': + /* Name is set later */ + $level = 3; + break; + case 'spanish2': + $name = _pgettext('Collation', 'Spanish (traditional)'); + break; + case 'swedish': + case 'sv': + $name = _pgettext('Collation', 'Swedish'); + break; + case 'thai': + case 'th': + $name = _pgettext('Collation', 'Thai'); + break; + case 'turkish': + case 'tr': + $name = _pgettext('Collation', 'Turkish'); + break; + case 'ukrainian': + case 'uk': + $name = _pgettext('Collation', 'Ukrainian'); + break; + case 'vietnamese': + case 'vi': + $name = _pgettext('Collation', 'Vietnamese'); + break; + case 'unicode': + if ($unknown) { + $name = _pgettext('Collation', 'Unicode'); + } + break; + default: + $found = false; + } + if ($found) { + continue; + } + // Not parsed token, fall to next level + } + if ($level == 2) { + /* Next will be variant */ + $level = 4; + /* Germal variant */ + if ($part == 'pb') { + $name = _pgettext('Collation', 'German (phone book order)'); + continue; + } + $name = _pgettext('Collation', 'German (dictionary order)'); + // Not parsed token, fall to next level + } + if ($level == 3) { + /* Next will be variant */ + $level = 4; + /* Spanish variant */ + if ($part == 'trad') { + $name = _pgettext('Collation', 'Spanish (traditional)'); + continue; + } + $name = _pgettext('Collation', 'Spanish (modern)'); + // Not parsed token, fall to next level + } + if ($level == 4) { + /* Next will be suffix */ + $level = 5; + /* Variant */ + $found = true; + switch ($part) { + case '0900': + $variant = 'UCA 9.0.0'; + break; + case '520': + $variant = 'UCA 5.2.0'; + break; + case 'mysql561': + $variant = 'MySQL 5.6.1'; + break; + case 'mysql500': + $variant = 'MySQL 5.0.0'; + break; + default: + $found = false; + } + if ($found) { + continue; + } + // Not parsed token, fall to next level + } + if ($level == 5) { + /* Suffixes */ + switch ($part) { + case 'ci': + $suffixes[] = _pgettext('Collation variant', 'case-insensitive'); + break; + case 'cs': + $suffixes[] = _pgettext('Collation variant', 'case-sensitive'); + break; + case 'ai': + $suffixes[] = _pgettext('Collation variant', 'accent-insensitive'); + break; + case 'as': + $suffixes[] = _pgettext('Collation variant', 'accent-sensitive'); + break; + case 'ks': + $suffixes[] = _pgettext('Collation variant', 'kana-sensitive'); + break; + case 'w2': + case 'l2': + $suffixes[] = _pgettext('Collation variant', 'multi-level'); + break; + case 'bin': + $suffixes[] = _pgettext('Collation variant', 'binary'); + break; + case 'nopad': + $suffixes[] = _pgettext('Collation variant', 'no-pad'); + break; + } + } + } + + $result = $name; + if (! is_null($variant)) { + $result .= ' (' . $variant . ')'; + } + if (count($suffixes) > 0) { + $result .= ', ' . implode(', ', $suffixes); + } + return $result; + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/CheckUserPrivileges.php b/php/apps/phpmyadmin49/html/libraries/classes/CheckUserPrivileges.php new file mode 100644 index 00000000..7b3770d3 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/CheckUserPrivileges.php @@ -0,0 +1,338 @@ +dbi = $dbi; + } + + /** + * Extracts details from a result row of a SHOW GRANT query + * + * @param string $row grant row + * + * @return array + */ + public function getItemsFromShowGrantsRow($row) + { + $db_name_offset = mb_strpos($row, ' ON ') + 4; + + $tblname_end_offset = mb_strpos($row, ' TO '); + $tblname_start_offset = false; + + if ($__tblname_start_offset = mb_strpos($row, '`.', $db_name_offset)) { + if ($__tblname_start_offset < $tblname_end_offset) { + $tblname_start_offset = $__tblname_start_offset + 1; + } + } + + if (!$tblname_start_offset) { + $tblname_start_offset = mb_strpos($row, '.', $db_name_offset); + } + + $show_grants_dbname = mb_substr( + $row, + $db_name_offset, + $tblname_start_offset - $db_name_offset + ); + + $show_grants_dbname = Util::unQuote($show_grants_dbname, '`'); + + $show_grants_str = mb_substr( + $row, + 6, + (mb_strpos($row, ' ON ') - 6) + ); + + $show_grants_tblname = mb_substr( + $row, + $tblname_start_offset + 1, + $tblname_end_offset - $tblname_start_offset - 1 + ); + $show_grants_tblname = Util::unQuote($show_grants_tblname, '`'); + + return array( + $show_grants_str, + $show_grants_dbname, + $show_grants_tblname + ); + } + + /** + * Check if user has required privileges for + * performing 'Adjust privileges' operations + * + * @param string $show_grants_str string containing grants for user + * @param string $show_grants_dbname name of db extracted from grant string + * @param string $show_grants_tblname name of table extracted from grant string + * + * @return void + */ + public function checkRequiredPrivilegesForAdjust( + $show_grants_str, + $show_grants_dbname, + $show_grants_tblname + ) { + // '... ALL PRIVILEGES ON *.* ...' OR '... ALL PRIVILEGES ON `mysql`.* ..' + // OR + // SELECT, INSERT, UPDATE, DELETE .... ON *.* OR `mysql`.* + if ($show_grants_str == 'ALL' + || $show_grants_str == 'ALL PRIVILEGES' + || (mb_strpos( + $show_grants_str, 'SELECT, INSERT, UPDATE, DELETE' + ) !== false) + ) { + if ($show_grants_dbname == '*' + && $show_grants_tblname == '*' + ) { + $GLOBALS['col_priv'] = true; + $GLOBALS['db_priv'] = true; + $GLOBALS['proc_priv'] = true; + $GLOBALS['table_priv'] = true; + + if ($show_grants_str == 'ALL PRIVILEGES' + || $show_grants_str == 'ALL' + ) { + $GLOBALS['is_reload_priv'] = true; + } + } + + // check for specific tables in `mysql` db + // Ex. '... ALL PRIVILEGES on `mysql`.`columns_priv` .. ' + if ($show_grants_dbname == 'mysql') { + switch ($show_grants_tblname) { + case "columns_priv": + $GLOBALS['col_priv'] = true; + break; + case "db": + $GLOBALS['db_priv'] = true; + break; + case "procs_priv": + $GLOBALS['proc_priv'] = true; + break; + case "tables_priv": + $GLOBALS['table_priv'] = true; + break; + case "*": + $GLOBALS['col_priv'] = true; + $GLOBALS['db_priv'] = true; + $GLOBALS['proc_priv'] = true; + $GLOBALS['table_priv'] = true; + break; + default: + } + } + } + } + + /** + * sets privilege information extracted from SHOW GRANTS result + * + * Detection for some CREATE privilege. + * + * Since MySQL 4.1.2, we can easily detect current user's grants using $userlink + * (no control user needed) and we don't have to try any other method for + * detection + * + * @todo fix to get really all privileges, not only explicitly defined for this user + * from MySQL manual: (https://dev.mysql.com/doc/refman/5.0/en/show-grants.html) + * SHOW GRANTS displays only the privileges granted explicitly to the named + * account. Other privileges might be available to the account, but they are not + * displayed. For example, if an anonymous account exists, the named account + * might be able to use its privileges, but SHOW GRANTS will not display them. + * + * @return void + */ + public function analyseShowGrant() + { + if (Util::cacheExists('is_create_db_priv')) { + $GLOBALS['is_create_db_priv'] = Util::cacheGet( + 'is_create_db_priv' + ); + $GLOBALS['is_reload_priv'] = Util::cacheGet( + 'is_reload_priv' + ); + $GLOBALS['db_to_create'] = Util::cacheGet( + 'db_to_create' + ); + $GLOBALS['dbs_where_create_table_allowed'] = Util::cacheGet( + 'dbs_where_create_table_allowed' + ); + $GLOBALS['dbs_to_test'] = Util::cacheGet( + 'dbs_to_test' + ); + + $GLOBALS['db_priv'] = Util::cacheGet( + 'db_priv' + ); + $GLOBALS['col_priv'] = Util::cacheGet( + 'col_priv' + ); + $GLOBALS['table_priv'] = Util::cacheGet( + 'table_priv' + ); + $GLOBALS['proc_priv'] = Util::cacheGet( + 'proc_priv' + ); + + return; + } + + // defaults + $GLOBALS['is_create_db_priv'] = false; + $GLOBALS['is_reload_priv'] = false; + $GLOBALS['db_to_create'] = ''; + $GLOBALS['dbs_where_create_table_allowed'] = array(); + $GLOBALS['dbs_to_test'] = $this->dbi->getSystemSchemas(); + $GLOBALS['proc_priv'] = false; + $GLOBALS['db_priv'] = false; + $GLOBALS['col_priv'] = false; + $GLOBALS['table_priv'] = false; + + $rs_usr = $this->dbi->tryQuery('SHOW GRANTS'); + + if (! $rs_usr) { + return; + } + + $re0 = '(^|(\\\\\\\\)+|[^\\\\])'; // non-escaped wildcards + $re1 = '(^|[^\\\\])(\\\)+'; // escaped wildcards + + while ($row = $this->dbi->fetchRow($rs_usr)) { + list( + $show_grants_str, + $show_grants_dbname, + $show_grants_tblname + ) = $this->getItemsFromShowGrantsRow($row[0]); + + if ($show_grants_dbname == '*') { + if ($show_grants_str != 'USAGE') { + $GLOBALS['dbs_to_test'] = false; + } + } elseif ($GLOBALS['dbs_to_test'] !== false) { + $GLOBALS['dbs_to_test'][] = $show_grants_dbname; + } + + if ( + mb_strpos($show_grants_str,'RELOAD') !== false + ) { + $GLOBALS['is_reload_priv'] = true; + } + + // check for the required privileges for adjust + $this->checkRequiredPrivilegesForAdjust( + $show_grants_str, + $show_grants_dbname, + $show_grants_tblname + ); + + /** + * @todo if we find CREATE VIEW but not CREATE, do not offer + * the create database dialog box + */ + if ($show_grants_str == 'ALL' + || $show_grants_str == 'ALL PRIVILEGES' + || $show_grants_str == 'CREATE' + || strpos($show_grants_str, 'CREATE,') !== false + ) { + if ($show_grants_dbname == '*') { + // a global CREATE privilege + $GLOBALS['is_create_db_priv'] = true; + $GLOBALS['is_reload_priv'] = true; + $GLOBALS['db_to_create'] = ''; + $GLOBALS['dbs_where_create_table_allowed'][] = '*'; + // @todo we should not break here, cause GRANT ALL *.* + // could be revoked by a later rule like GRANT SELECT ON db.* + break; + } else { + // this array may contain wildcards + $GLOBALS['dbs_where_create_table_allowed'][] = $show_grants_dbname; + + $dbname_to_test = Util::backquote($show_grants_dbname); + + if ($GLOBALS['is_create_db_priv']) { + // no need for any more tests if we already know this + continue; + } + + // does this db exist? + if ((preg_match('/' . $re0 . '%|_/', $show_grants_dbname) + && ! preg_match('/\\\\%|\\\\_/', $show_grants_dbname)) + || (! $this->dbi->tryQuery( + 'USE ' . preg_replace( + '/' . $re1 . '(%|_)/', '\\1\\3', $dbname_to_test + ) + ) + && mb_substr($this->dbi->getError(), 1, 4) != 1044) + ) { + /** + * Do not handle the underscore wildcard + * (this case must be rare anyway) + */ + $GLOBALS['db_to_create'] = preg_replace( + '/' . $re0 . '%/', '\\1', + $show_grants_dbname + ); + $GLOBALS['db_to_create'] = preg_replace( + '/' . $re1 . '(%|_)/', '\\1\\3', + $GLOBALS['db_to_create'] + ); + $GLOBALS['is_create_db_priv'] = true; + + /** + * @todo collect $GLOBALS['db_to_create'] into an array, + * to display a drop-down in the "Create database" dialog + */ + // we don't break, we want all possible databases + //break; + } // end if + } // end elseif + } // end if + + } // end while + + $this->dbi->freeResult($rs_usr); + + // must also cacheUnset() them in + // PhpMyAdmin\Plugins\Auth\AuthenticationCookie + Util::cacheSet('is_create_db_priv', $GLOBALS['is_create_db_priv']); + Util::cacheSet('is_reload_priv', $GLOBALS['is_reload_priv']); + Util::cacheSet('db_to_create', $GLOBALS['db_to_create']); + Util::cacheSet( + 'dbs_where_create_table_allowed', + $GLOBALS['dbs_where_create_table_allowed'] + ); + Util::cacheSet('dbs_to_test', $GLOBALS['dbs_to_test']); + + Util::cacheSet('proc_priv', $GLOBALS['proc_priv']); + Util::cacheSet('table_priv', $GLOBALS['table_priv']); + Util::cacheSet('col_priv', $GLOBALS['col_priv']); + Util::cacheSet('db_priv', $GLOBALS['db_priv']); + } // end function +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Config.php b/php/apps/phpmyadmin49/html/libraries/classes/Config.php new file mode 100644 index 00000000..1c1f3b6d --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Config.php @@ -0,0 +1,1844 @@ +settings = array('is_setup' => false); + + // functions need to refresh in case of config file changed goes in + // PhpMyAdmin\Config::load() + $this->load($source); + + // other settings, independent from config file, comes in + $this->checkSystem(); + + $this->base_settings = $this->settings; + + $this->userPreferences = new UserPreferences(); + } + + /** + * sets system and application settings + * + * @return void + */ + public function checkSystem() + { + $this->set('PMA_VERSION', '4.9.1'); + /* Major version */ + $this->set( + 'PMA_MAJOR_VERSION', + implode('.', array_slice(explode('.', $this->get('PMA_VERSION'), 3), 0, 2)) + ); + + $this->checkWebServerOs(); + $this->checkWebServer(); + $this->checkGd2(); + $this->checkClient(); + $this->checkUpload(); + $this->checkUploadSize(); + $this->checkOutputCompression(); + } + + /** + * whether to use gzip output compression or not + * + * @return void + */ + public function checkOutputCompression() + { + // If zlib output compression is set in the php configuration file, no + // output buffering should be run + if (ini_get('zlib.output_compression')) { + $this->set('OBGzip', false); + } + + // enable output-buffering (if set to 'auto') + if (strtolower($this->get('OBGzip')) == 'auto') { + $this->set('OBGzip', true); + } + } + + /** + * Sets the client platform based on user agent + * + * @param string $user_agent the user agent + * + * @return void + */ + private function _setClientPlatform($user_agent) + { + if (mb_strstr($user_agent, 'Win')) { + $this->set('PMA_USR_OS', 'Win'); + } elseif (mb_strstr($user_agent, 'Mac')) { + $this->set('PMA_USR_OS', 'Mac'); + } elseif (mb_strstr($user_agent, 'Linux')) { + $this->set('PMA_USR_OS', 'Linux'); + } elseif (mb_strstr($user_agent, 'Unix')) { + $this->set('PMA_USR_OS', 'Unix'); + } elseif (mb_strstr($user_agent, 'OS/2')) { + $this->set('PMA_USR_OS', 'OS/2'); + } else { + $this->set('PMA_USR_OS', 'Other'); + } + } + + /** + * Determines platform (OS), browser and version of the user + * Based on a phpBuilder article: + * + * @see http://www.phpbuilder.net/columns/tim20000821.php + * + * @return void + */ + public function checkClient() + { + if (Core::getenv('HTTP_USER_AGENT')) { + $HTTP_USER_AGENT = Core::getenv('HTTP_USER_AGENT'); + } else { + $HTTP_USER_AGENT = ''; + } + + // 1. Platform + $this->_setClientPlatform($HTTP_USER_AGENT); + + // 2. browser and version + // (must check everything else before Mozilla) + + $is_mozilla = preg_match( + '@Mozilla/([0-9]\.[0-9]{1,2})@', + $HTTP_USER_AGENT, + $mozilla_version + ); + + if (preg_match( + '@Opera(/| )([0-9]\.[0-9]{1,2})@', + $HTTP_USER_AGENT, + $log_version + )) { + $this->set('PMA_USR_BROWSER_VER', $log_version[2]); + $this->set('PMA_USR_BROWSER_AGENT', 'OPERA'); + } elseif (preg_match( + '@(MS)?IE ([0-9]{1,2}\.[0-9]{1,2})@', + $HTTP_USER_AGENT, + $log_version + )) { + $this->set('PMA_USR_BROWSER_VER', $log_version[2]); + $this->set('PMA_USR_BROWSER_AGENT', 'IE'); + } elseif (preg_match( + '@Trident/(7)\.0@', + $HTTP_USER_AGENT, + $log_version + )) { + $this->set('PMA_USR_BROWSER_VER', intval($log_version[1]) + 4); + $this->set('PMA_USR_BROWSER_AGENT', 'IE'); + } elseif (preg_match( + '@OmniWeb/([0-9]{1,3})@', + $HTTP_USER_AGENT, + $log_version + )) { + $this->set('PMA_USR_BROWSER_VER', $log_version[1]); + $this->set('PMA_USR_BROWSER_AGENT', 'OMNIWEB'); + // Konqueror 2.2.2 says Konqueror/2.2.2 + // Konqueror 3.0.3 says Konqueror/3 + } elseif (preg_match( + '@(Konqueror/)(.*)(;)@', + $HTTP_USER_AGENT, + $log_version + )) { + $this->set('PMA_USR_BROWSER_VER', $log_version[2]); + $this->set('PMA_USR_BROWSER_AGENT', 'KONQUEROR'); + // must check Chrome before Safari + } elseif ($is_mozilla + && preg_match('@Chrome/([0-9.]*)@', $HTTP_USER_AGENT, $log_version) + ) { + $this->set('PMA_USR_BROWSER_VER', $log_version[1]); + $this->set('PMA_USR_BROWSER_AGENT', 'CHROME'); + // newer Safari + } elseif ($is_mozilla + && preg_match('@Version/(.*) Safari@', $HTTP_USER_AGENT, $log_version) + ) { + $this->set( + 'PMA_USR_BROWSER_VER', $log_version[1] + ); + $this->set('PMA_USR_BROWSER_AGENT', 'SAFARI'); + // older Safari + } elseif ($is_mozilla + && preg_match('@Safari/([0-9]*)@', $HTTP_USER_AGENT, $log_version) + ) { + $this->set( + 'PMA_USR_BROWSER_VER', $mozilla_version[1] . '.' . $log_version[1] + ); + $this->set('PMA_USR_BROWSER_AGENT', 'SAFARI'); + // Firefox + } elseif (! mb_strstr($HTTP_USER_AGENT, 'compatible') + && preg_match('@Firefox/([\w.]+)@', $HTTP_USER_AGENT, $log_version) + ) { + $this->set( + 'PMA_USR_BROWSER_VER', $log_version[1] + ); + $this->set('PMA_USR_BROWSER_AGENT', 'FIREFOX'); + } elseif (preg_match('@rv:1\.9(.*)Gecko@', $HTTP_USER_AGENT)) { + $this->set('PMA_USR_BROWSER_VER', '1.9'); + $this->set('PMA_USR_BROWSER_AGENT', 'GECKO'); + } elseif ($is_mozilla) { + $this->set('PMA_USR_BROWSER_VER', $mozilla_version[1]); + $this->set('PMA_USR_BROWSER_AGENT', 'MOZILLA'); + } else { + $this->set('PMA_USR_BROWSER_VER', 0); + $this->set('PMA_USR_BROWSER_AGENT', 'OTHER'); + } + } + + /** + * Whether GD2 is present + * + * @return void + */ + public function checkGd2() + { + if ($this->get('GD2Available') == 'yes') { + $this->set('PMA_IS_GD2', 1); + return; + } + + if ($this->get('GD2Available') == 'no') { + $this->set('PMA_IS_GD2', 0); + return; + } + + if (!function_exists('imagecreatetruecolor')) { + $this->set('PMA_IS_GD2', 0); + return; + } + + if (function_exists('gd_info')) { + $gd_nfo = gd_info(); + if (mb_strstr($gd_nfo["GD Version"], '2.')) { + $this->set('PMA_IS_GD2', 1); + } else { + $this->set('PMA_IS_GD2', 0); + } + } else { + $this->set('PMA_IS_GD2', 0); + } + } + + /** + * Whether the Web server php is running on is IIS + * + * @return void + */ + public function checkWebServer() + { + // some versions return Microsoft-IIS, some Microsoft/IIS + // we could use a preg_match() but it's slower + if (Core::getenv('SERVER_SOFTWARE') + && stristr(Core::getenv('SERVER_SOFTWARE'), 'Microsoft') + && stristr(Core::getenv('SERVER_SOFTWARE'), 'IIS') + ) { + $this->set('PMA_IS_IIS', 1); + } else { + $this->set('PMA_IS_IIS', 0); + } + } + + /** + * Whether the os php is running on is windows or not + * + * @return void + */ + public function checkWebServerOs() + { + // Default to Unix or Equiv + $this->set('PMA_IS_WINDOWS', 0); + // If PHP_OS is defined then continue + if (defined('PHP_OS')) { + if (stristr(PHP_OS, 'win') && !stristr(PHP_OS, 'darwin')) { + // Is it some version of Windows + $this->set('PMA_IS_WINDOWS', 1); + } elseif (stristr(PHP_OS, 'OS/2')) { + // Is it OS/2 (No file permissions like Windows) + $this->set('PMA_IS_WINDOWS', 1); + } + } + } + + /** + * detects if Git revision + * @param string &$git_location (optional) verified git directory + * @return boolean + */ + public function isGitRevision(&$git_location = NULL) + { + // PMA config check + if (! $this->get('ShowGitRevision')) { + return false; + } + + // caching + if ( + isset($_SESSION['is_git_revision']) + && array_key_exists('git_location', $_SESSION) + ) { + // Define location using cached value + $git_location = $_SESSION['git_location']; + return $_SESSION['is_git_revision']; + } + + // find out if there is a .git folder + // or a .git file (--separate-git-dir) + $git = '.git'; + if (is_dir($git)) { + if (@is_file($git . '/config')) { + $git_location = $git; + } else { + $_SESSION['git_location'] = null; + $_SESSION['is_git_revision'] = false; + return false; + } + } elseif (is_file($git)) { + $contents = file_get_contents($git); + $gitmatch = array(); + // Matches expected format + if (! preg_match('/^gitdir: (.*)$/', + $contents, $gitmatch)) { + $_SESSION['git_location'] = null; + $_SESSION['is_git_revision'] = false; + return false; + } else { + if (@is_dir($gitmatch[1])) { + //Detected git external folder location + $git_location = $gitmatch[1]; + } else { + $_SESSION['git_location'] = null; + $_SESSION['is_git_revision'] = false; + return false; + } + } + } else { + $_SESSION['git_location'] = null; + $_SESSION['is_git_revision'] = false; + return false; + } + // Define session for caching + $_SESSION['git_location'] = $git_location; + $_SESSION['is_git_revision'] = true; + return true; + } + + /** + * detects Git revision, if running inside repo + * + * @return void + */ + public function checkGitRevision() + { + // find out if there is a .git folder + $git_folder = ''; + if (! $this->isGitRevision($git_folder)) { + $this->set('PMA_VERSION_GIT', 0); + return; + } + + if (! $ref_head = @file_get_contents($git_folder . '/HEAD')) { + $this->set('PMA_VERSION_GIT', 0); + return; + } + + if ($common_dir_contents = @file_get_contents($git_folder . '/commondir')) { + $git_folder = $git_folder . DIRECTORY_SEPARATOR . trim($common_dir_contents); + } + + $branch = false; + // are we on any branch? + if (strstr($ref_head, '/')) { + // remove ref: prefix + $ref_head = substr(trim($ref_head), 5); + if (substr($ref_head, 0, 11) === 'refs/heads/') { + $branch = substr($ref_head, 11); + } else { + $branch = basename($ref_head); + } + + $ref_file = $git_folder . '/' . $ref_head; + if (@file_exists($ref_file)) { + $hash = @file_get_contents($ref_file); + if (! $hash) { + $this->set('PMA_VERSION_GIT', 0); + return; + } + $hash = trim($hash); + } else { + // deal with packed refs + $packed_refs = @file_get_contents($git_folder . '/packed-refs'); + if (! $packed_refs) { + $this->set('PMA_VERSION_GIT', 0); + return; + } + // split file to lines + $ref_lines = explode("\n", $packed_refs); + foreach ($ref_lines as $line) { + // skip comments + if ($line[0] == '#') { + continue; + } + // parse line + $parts = explode(' ', $line); + // care only about named refs + if (count($parts) != 2) { + continue; + } + // have found our ref? + if ($parts[1] == $ref_head) { + $hash = $parts[0]; + break; + } + } + if (! isset($hash)) { + $this->set('PMA_VERSION_GIT', 0); + // Could not find ref + return; + } + } + } else { + $hash = trim($ref_head); + } + + $commit = false; + if (! preg_match('/^[0-9a-f]{40}$/i', $hash)) { + $commit = false; + } elseif (isset($_SESSION['PMA_VERSION_COMMITDATA_' . $hash])) { + $commit = $_SESSION['PMA_VERSION_COMMITDATA_' . $hash]; + } elseif (function_exists('gzuncompress')) { + $git_file_name = $git_folder . '/objects/' + . substr($hash, 0, 2) . '/' . substr($hash, 2); + if (@file_exists($git_file_name) ) { + if (! $commit = @file_get_contents($git_file_name)) { + $this->set('PMA_VERSION_GIT', 0); + return; + } + $commit = explode("\0", gzuncompress($commit), 2); + $commit = explode("\n", $commit[1]); + $_SESSION['PMA_VERSION_COMMITDATA_' . $hash] = $commit; + } else { + $pack_names = array(); + // work with packed data + $packs_file = $git_folder . '/objects/info/packs'; + if (@file_exists($packs_file) + && $packs = @file_get_contents($packs_file) + ) { + // File exists. Read it, parse the file to get the names of the + // packs. (to look for them in .git/object/pack directory later) + foreach (explode("\n", $packs) as $line) { + // skip blank lines + if (strlen(trim($line)) == 0) { + continue; + } + // skip non pack lines + if ($line[0] != 'P') { + continue; + } + // parse names + $pack_names[] = substr($line, 2); + } + } else { + // '.git/objects/info/packs' file can be missing + // (atlease in mysGit) + // File missing. May be we can look in the .git/object/pack + // directory for all the .pack files and use that list of + // files instead + $dirIterator = new DirectoryIterator( + $git_folder . '/objects/pack' + ); + foreach ($dirIterator as $file_info) { + $file_name = $file_info->getFilename(); + // if this is a .pack file + if ($file_info->isFile() && substr($file_name, -5) == '.pack' + ) { + $pack_names[] = $file_name; + } + } + } + $hash = strtolower($hash); + foreach ($pack_names as $pack_name) { + $index_name = str_replace('.pack', '.idx', $pack_name); + + // load index + $index_data = @file_get_contents( + $git_folder . '/objects/pack/' . $index_name + ); + if (! $index_data) { + continue; + } + // check format + if (substr($index_data, 0, 4) != "\377tOc") { + continue; + } + // check version + $version = unpack('N', substr($index_data, 4, 4)); + if ($version[1] != 2) { + continue; + } + // parse fanout table + $fanout = unpack( + "N*", + substr($index_data, 8, 256 * 4) + ); + + // find where we should search + $firstbyte = intval(substr($hash, 0, 2), 16); + // array is indexed from 1 and we need to get + // previous entry for start + if ($firstbyte == 0) { + $start = 0; + } else { + $start = $fanout[$firstbyte]; + } + $end = $fanout[$firstbyte + 1]; + + // stupid linear search for our sha + $found = false; + $offset = 8 + (256 * 4); + for ($position = $start; $position < $end; $position++) { + $sha = strtolower( + bin2hex( + substr($index_data, $offset + ($position * 20), 20) + ) + ); + if ($sha == $hash) { + $found = true; + break; + } + } + if (! $found) { + continue; + } + // read pack offset + $offset = 8 + (256 * 4) + (24 * $fanout[256]); + $pack_offset = unpack( + 'N', + substr($index_data, $offset + ($position * 4), 4) + ); + $pack_offset = $pack_offset[1]; + + // open pack file + $pack_file = fopen( + $git_folder . '/objects/pack/' . $pack_name, 'rb' + ); + if ($pack_file === false) { + continue; + } + // seek to start + fseek($pack_file, $pack_offset); + + // parse header + $header = ord(fread($pack_file, 1)); + $type = ($header >> 4) & 7; + $hasnext = ($header & 128) >> 7; + $size = $header & 0xf; + $offset = 4; + + while ($hasnext) { + $byte = ord(fread($pack_file, 1)); + $size |= ($byte & 0x7f) << $offset; + $hasnext = ($byte & 128) >> 7; + $offset += 7; + } + + // we care only about commit objects + if ($type != 1) { + continue; + } + + // read data + $commit = fread($pack_file, $size); + $commit = gzuncompress($commit); + $commit = explode("\n", $commit); + $_SESSION['PMA_VERSION_COMMITDATA_' . $hash] = $commit; + fclose($pack_file); + } + } + } + + $httpRequest = new HttpRequest(); + + // check if commit exists in Github + if ($commit !== false + && isset($_SESSION['PMA_VERSION_REMOTECOMMIT_' . $hash]) + ) { + $is_remote_commit = $_SESSION['PMA_VERSION_REMOTECOMMIT_' . $hash]; + } else { + $link = 'https://www.phpmyadmin.net/api/commit/' . $hash . '/'; + $is_found = $httpRequest->create($link, 'GET'); + switch($is_found) { + case false: + $is_remote_commit = false; + $_SESSION['PMA_VERSION_REMOTECOMMIT_' . $hash] = false; + break; + case null: + // no remote link for now, but don't cache this as Github is down + $is_remote_commit = false; + break; + default: + $is_remote_commit = true; + $_SESSION['PMA_VERSION_REMOTECOMMIT_' . $hash] = true; + if ($commit === false) { + // if no local commit data, try loading from Github + $commit_json = json_decode($is_found); + } + break; + } + } + + $is_remote_branch = false; + if ($is_remote_commit && $branch !== false) { + // check if branch exists in Github + if (isset($_SESSION['PMA_VERSION_REMOTEBRANCH_' . $hash])) { + $is_remote_branch = $_SESSION['PMA_VERSION_REMOTEBRANCH_' . $hash]; + } else { + $link = 'https://www.phpmyadmin.net/api/tree/' . $branch . '/'; + $is_found = $httpRequest->create($link, 'GET', true); + switch($is_found) { + case true: + $is_remote_branch = true; + $_SESSION['PMA_VERSION_REMOTEBRANCH_' . $hash] = true; + break; + case false: + $is_remote_branch = false; + $_SESSION['PMA_VERSION_REMOTEBRANCH_' . $hash] = false; + break; + case null: + // no remote link for now, but don't cache this as Github is down + $is_remote_branch = false; + break; + } + } + } + + if ($commit !== false) { + $author = array('name' => '', 'email' => '', 'date' => ''); + $committer = array('name' => '', 'email' => '', 'date' => ''); + + do { + $dataline = array_shift($commit); + $datalinearr = explode(' ', $dataline, 2); + $linetype = $datalinearr[0]; + if (in_array($linetype, array('author', 'committer'))) { + $user = $datalinearr[1]; + preg_match('/([^<]+)<([^>]+)> ([0-9]+)( [^ ]+)?/', $user, $user); + $user2 = array( + 'name' => trim($user[1]), + 'email' => trim($user[2]), + 'date' => date('Y-m-d H:i:s', $user[3])); + if (isset($user[4])) { + $user2['date'] .= $user[4]; + } + $$linetype = $user2; + } + } while ($dataline != ''); + $message = trim(implode(' ', $commit)); + + } elseif (isset($commit_json) && isset($commit_json->author) && isset($commit_json->committer) && isset($commit_json->message)) { + $author = array( + 'name' => $commit_json->author->name, + 'email' => $commit_json->author->email, + 'date' => $commit_json->author->date); + $committer = array( + 'name' => $commit_json->committer->name, + 'email' => $commit_json->committer->email, + 'date' => $commit_json->committer->date); + $message = trim($commit_json->message); + } else { + $this->set('PMA_VERSION_GIT', 0); + return; + } + + $this->set('PMA_VERSION_GIT', 1); + $this->set('PMA_VERSION_GIT_COMMITHASH', $hash); + $this->set('PMA_VERSION_GIT_BRANCH', $branch); + $this->set('PMA_VERSION_GIT_MESSAGE', $message); + $this->set('PMA_VERSION_GIT_AUTHOR', $author); + $this->set('PMA_VERSION_GIT_COMMITTER', $committer); + $this->set('PMA_VERSION_GIT_ISREMOTECOMMIT', $is_remote_commit); + $this->set('PMA_VERSION_GIT_ISREMOTEBRANCH', $is_remote_branch); + } + + /** + * loads default values from default source + * + * @return boolean success + */ + public function loadDefaults() + { + $cfg = array(); + if (! @file_exists($this->default_source)) { + $this->error_config_default_file = true; + return false; + } + $old_error_reporting = error_reporting(0); + ob_start(); + $GLOBALS['pma_config_loading'] = true; + $eval_result = include $this->default_source; + $GLOBALS['pma_config_loading'] = false; + ob_end_clean(); + error_reporting($old_error_reporting); + + if ($eval_result === false) { + $this->error_config_default_file = true; + return false; + } + + $this->default_source_mtime = filemtime($this->default_source); + + $this->default_server = $cfg['Servers'][1]; + unset($cfg['Servers']); + + $this->default = $cfg; + $this->settings = array_replace_recursive($this->settings, $cfg); + + $this->error_config_default_file = false; + + return true; + } + + /** + * loads configuration from $source, usually the config file + * should be called on object creation + * + * @param string $source config file + * + * @return bool + */ + public function load($source = null) + { + $this->loadDefaults(); + + if (null !== $source) { + $this->setSource($source); + } + + if (! $this->checkConfigSource()) { + return false; + } + + $cfg = array(); + + /** + * Parses the configuration file, we throw away any errors or + * output. + */ + $old_error_reporting = error_reporting(0); + ob_start(); + $GLOBALS['pma_config_loading'] = true; + $eval_result = include $this->getSource(); + $GLOBALS['pma_config_loading'] = false; + ob_end_clean(); + error_reporting($old_error_reporting); + + if ($eval_result === false) { + $this->error_config_file = true; + } else { + $this->error_config_file = false; + $this->source_mtime = filemtime($this->getSource()); + } + + /** + * Ignore keys with / as we do not use these + * + * These can be confusing for user configuration layer as it + * flatten array using / and thus don't see difference between + * $cfg['Export/method'] and $cfg['Export']['method'], while rest + * of thre code uses the setting only in latter form. + * + * This could be removed once we consistently handle both values + * in the functional code as well. + * + * It could use array_filter(...ARRAY_FILTER_USE_KEY), but it's not + * supported on PHP 5.5 and HHVM. + */ + $matched_keys = array_filter( + array_keys($cfg), + function ($key) {return strpos($key, '/') === false;} + ); + + $cfg = array_intersect_key($cfg, array_flip($matched_keys)); + + /** + * Backward compatibility code + */ + if (!empty($cfg['DefaultTabTable'])) { + $cfg['DefaultTabTable'] = str_replace( + '_properties', + '', + str_replace( + 'tbl_properties.php', + 'tbl_sql.php', + $cfg['DefaultTabTable'] + ) + ); + } + if (!empty($cfg['DefaultTabDatabase'])) { + $cfg['DefaultTabDatabase'] = str_replace( + '_details', + '', + str_replace( + 'db_details.php', + 'db_sql.php', + $cfg['DefaultTabDatabase'] + ) + ); + } + + $this->settings = array_replace_recursive($this->settings, $cfg); + + return true; + } + + /** + * Sets the connection collation + * + * @return void + */ + private function _setConnectionCollation() + { + $collation_connection = $this->get('DefaultConnectionCollation'); + if (! empty($collation_connection) + && $collation_connection != $GLOBALS['collation_connection'] + ) { + $GLOBALS['dbi']->setCollation($collation_connection); + } + } + + /** + * Loads user preferences and merges them with current config + * must be called after control connection has been established + * + * @return void + */ + public function loadUserPreferences() + { + // index.php should load these settings, so that phpmyadmin.css.php + // will have everything available in session cache + $server = isset($GLOBALS['server']) + ? $GLOBALS['server'] + : (!empty($GLOBALS['cfg']['ServerDefault']) + ? $GLOBALS['cfg']['ServerDefault'] + : 0); + $cache_key = 'server_' . $server; + if ($server > 0 && !defined('PMA_MINIMUM_COMMON')) { + $config_mtime = max($this->default_source_mtime, $this->source_mtime); + // cache user preferences, use database only when needed + if (! isset($_SESSION['cache'][$cache_key]['userprefs']) + || $_SESSION['cache'][$cache_key]['config_mtime'] < $config_mtime + ) { + $prefs = $this->userPreferences->load(); + $_SESSION['cache'][$cache_key]['userprefs'] + = $this->userPreferences->apply($prefs['config_data']); + $_SESSION['cache'][$cache_key]['userprefs_mtime'] = $prefs['mtime']; + $_SESSION['cache'][$cache_key]['userprefs_type'] = $prefs['type']; + $_SESSION['cache'][$cache_key]['config_mtime'] = $config_mtime; + } + } elseif ($server == 0 + || ! isset($_SESSION['cache'][$cache_key]['userprefs']) + ) { + $this->set('user_preferences', false); + return; + } + $config_data = $_SESSION['cache'][$cache_key]['userprefs']; + // type is 'db' or 'session' + $this->set( + 'user_preferences', + $_SESSION['cache'][$cache_key]['userprefs_type'] + ); + $this->set( + 'user_preferences_mtime', + $_SESSION['cache'][$cache_key]['userprefs_mtime'] + ); + + // load config array + $this->settings = array_replace_recursive($this->settings, $config_data); + $GLOBALS['cfg'] = array_replace_recursive($GLOBALS['cfg'], $config_data); + if (defined('PMA_MINIMUM_COMMON')) { + return; + } + + // settings below start really working on next page load, but + // changes are made only in index.php so everything is set when + // in frames + + // save theme + /** @var ThemeManager $tmanager */ + $tmanager = ThemeManager::getInstance(); + if ($tmanager->getThemeCookie() || isset($_REQUEST['set_theme'])) { + if ((! isset($config_data['ThemeDefault']) + && $tmanager->theme->getId() != 'original') + || isset($config_data['ThemeDefault']) + && $config_data['ThemeDefault'] != $tmanager->theme->getId() + ) { + // new theme was set in common.inc.php + $this->setUserValue( + null, + 'ThemeDefault', + $tmanager->theme->getId(), + 'original' + ); + } + } else { + // no cookie - read default from settings + if ($this->settings['ThemeDefault'] != $tmanager->theme->getId() + && $tmanager->checkTheme($this->settings['ThemeDefault']) + ) { + $tmanager->setActiveTheme($this->settings['ThemeDefault']); + $tmanager->setThemeCookie(); + } + } + + // save language + if (isset($_COOKIE['pma_lang']) || isset($_POST['lang'])) { + if ((! isset($config_data['lang']) + && $GLOBALS['lang'] != 'en') + || isset($config_data['lang']) + && $GLOBALS['lang'] != $config_data['lang'] + ) { + $this->setUserValue(null, 'lang', $GLOBALS['lang'], 'en'); + } + } else { + // read language from settings + if (isset($config_data['lang'])) { + $language = LanguageManager::getInstance()->getLanguage( + $config_data['lang'] + ); + if ($language !== false) { + $language->activate(); + $this->setCookie('pma_lang', $language->getCode()); + } + } + } + + // set connection collation + $this->_setConnectionCollation(); + } + + /** + * Sets config value which is stored in user preferences (if available) + * or in a cookie. + * + * If user preferences are not yet initialized, option is applied to + * global config and added to a update queue, which is processed + * by {@link loadUserPreferences()} + * + * @param string $cookie_name can be null + * @param string $cfg_path configuration path + * @param mixed $new_cfg_value new value + * @param mixed $default_value default value + * + * @return true|PhpMyAdmin\Message + */ + public function setUserValue($cookie_name, $cfg_path, $new_cfg_value, + $default_value = null + ) { + $result = true; + // use permanent user preferences if possible + $prefs_type = $this->get('user_preferences'); + if ($prefs_type) { + if ($default_value === null) { + $default_value = Core::arrayRead($cfg_path, $this->default); + } + $result = $this->userPreferences->persistOption($cfg_path, $new_cfg_value, $default_value); + } + if ($prefs_type != 'db' && $cookie_name) { + // fall back to cookies + if ($default_value === null) { + $default_value = Core::arrayRead($cfg_path, $this->settings); + } + $this->setCookie($cookie_name, $new_cfg_value, $default_value); + } + Core::arrayWrite($cfg_path, $GLOBALS['cfg'], $new_cfg_value); + Core::arrayWrite($cfg_path, $this->settings, $new_cfg_value); + return $result; + } + + /** + * Reads value stored by {@link setUserValue()} + * + * @param string $cookie_name cookie name + * @param mixed $cfg_value config value + * + * @return mixed + */ + public function getUserValue($cookie_name, $cfg_value) + { + $cookie_exists = isset($_COOKIE) && !empty($_COOKIE[$cookie_name]); + $prefs_type = $this->get('user_preferences'); + if ($prefs_type == 'db') { + // permanent user preferences value exists, remove cookie + if ($cookie_exists) { + $this->removeCookie($cookie_name); + } + } elseif ($cookie_exists) { + return $_COOKIE[$cookie_name]; + } + // return value from $cfg array + return $cfg_value; + } + + /** + * set source + * + * @param string $source source + * + * @return void + */ + public function setSource($source) + { + $this->source = trim($source); + } + + /** + * check config source + * + * @return boolean whether source is valid or not + */ + public function checkConfigSource() + { + if (! $this->getSource()) { + // no configuration file set at all + return false; + } + + if (! @file_exists($this->getSource())) { + $this->source_mtime = 0; + return false; + } + + if (! @is_readable($this->getSource())) { + // manually check if file is readable + // might be bug #3059806 Supporting running from CIFS/Samba shares + + $contents = false; + $handle = @fopen($this->getSource(), 'r'); + if ($handle !== false) { + $contents = @fread($handle, 1); // reading 1 byte is enough to test + fclose($handle); + } + if ($contents === false) { + $this->source_mtime = 0; + Core::fatalError( + sprintf( + function_exists('__') + ? __('Existing configuration file (%s) is not readable.') + : 'Existing configuration file (%s) is not readable.', + $this->getSource() + ) + ); + return false; + } + } + + return true; + } + + /** + * verifies the permissions on config file (if asked by configuration) + * (must be called after config.inc.php has been merged) + * + * @return void + */ + public function checkPermissions() + { + // Check for permissions (on platforms that support it): + if ($this->get('CheckConfigurationPermissions') && @file_exists($this->getSource())) { + $perms = @fileperms($this->getSource()); + if (!($perms === false) && ($perms & 2)) { + // This check is normally done after loading configuration + $this->checkWebServerOs(); + if ($this->get('PMA_IS_WINDOWS') == 0) { + $this->source_mtime = 0; + Core::fatalError( + __( + 'Wrong permissions on configuration file, ' + . 'should not be world writable!' + ) + ); + } + } + } + } + + /** + * Checks for errors + * (must be called after config.inc.php has been merged) + * + * @return void + */ + public function checkErrors() + { + if ($this->error_config_default_file) { + Core::fatalError( + sprintf( + __('Could not load default configuration from: %1$s'), + $this->default_source + ) + ); + } + + if ($this->error_config_file) { + $error = '[strong]' . __('Failed to read configuration file!') . '[/strong]' + . '[br][br]' + . __( + 'This usually means there is a syntax error in it, ' + . 'please check any errors shown below.' + ) + . '[br][br]' + . '[conferr]'; + trigger_error($error, E_USER_ERROR); + } + } + + /** + * returns specific config setting + * + * @param string $setting config setting + * + * @return mixed value + */ + public function get($setting) + { + if (isset($this->settings[$setting])) { + return $this->settings[$setting]; + } + return null; + } + + /** + * sets configuration variable + * + * @param string $setting configuration option + * @param mixed $value new value for configuration option + * + * @return void + */ + public function set($setting, $value) + { + if (! isset($this->settings[$setting]) + || $this->settings[$setting] !== $value + ) { + $this->settings[$setting] = $value; + $this->set_mtime = time(); + } + } + + /** + * returns source for current config + * + * @return string config source + */ + public function getSource() + { + return $this->source; + } + + /** + * returns a unique value to force a CSS reload if either the config + * or the theme changes + * + * @return int Summary of unix timestamps and fontsize, + * to be unique on theme parameters change + */ + public function getThemeUniqueValue() + { + if (null !== $this->get('FontSize')) { + $fontsize = intval($this->get('FontSize')); + } else { + $fontsize = 0; + } + return ( + $fontsize + + $this->source_mtime + + $this->default_source_mtime + + $this->get('user_preferences_mtime') + + $GLOBALS['PMA_Theme']->mtime_info + + $GLOBALS['PMA_Theme']->filesize_info); + } + + /** + * checks if upload is enabled + * + * @return void + */ + public function checkUpload() + { + if (!ini_get('file_uploads')) { + $this->set('enable_upload', false); + return; + } + + $this->set('enable_upload', true); + // if set "php_admin_value file_uploads Off" in httpd.conf + // ini_get() also returns the string "Off" in this case: + if ('off' == strtolower(ini_get('file_uploads'))) { + $this->set('enable_upload', false); + } + } + + /** + * Maximum upload size as limited by PHP + * Used with permission from Moodle (https://moodle.org/) by Martin Dougiamas + * + * this section generates $max_upload_size in bytes + * + * @return void + */ + public function checkUploadSize() + { + if (! $filesize = ini_get('upload_max_filesize')) { + $filesize = "5M"; + } + + if ($postsize = ini_get('post_max_size')) { + $this->set( + 'max_upload_size', + min(Core::getRealSize($filesize), Core::getRealSize($postsize)) + ); + } else { + $this->set('max_upload_size', Core::getRealSize($filesize)); + } + } + + /** + * Checks if protocol is https + * + * This function checks if the https protocol on the active connection. + * + * @return bool + */ + public function isHttps() + { + + if (null !== $this->get('is_https')) { + return $this->get('is_https'); + } + + $url = $this->get('PmaAbsoluteUri'); + + $is_https = false; + if (! empty($url) && parse_url($url, PHP_URL_SCHEME) === 'https') { + $is_https = true; + } elseif (strtolower(Core::getenv('HTTP_SCHEME')) == 'https') { + $is_https = true; + } elseif (strtolower(Core::getenv('HTTPS')) == 'on') { + $is_https = true; + } elseif (substr(strtolower(Core::getenv('REQUEST_URI')), 0, 6) == 'https:') { + $is_https = true; + } elseif (strtolower(Core::getenv('HTTP_HTTPS_FROM_LB')) == 'on') { + // A10 Networks load balancer + $is_https = true; + } elseif (strtolower(Core::getenv('HTTP_FRONT_END_HTTPS')) == 'on') { + $is_https = true; + } elseif (strtolower(Core::getenv('HTTP_X_FORWARDED_PROTO')) == 'https') { + $is_https = true; + } elseif (Core::getenv('SERVER_PORT') == 443) { + $is_https = true; + } + + $this->set('is_https', $is_https); + + return $is_https; + } + + /** + * Get phpMyAdmin root path + * + * @return string + */ + public function getRootPath() + { + static $cookie_path = null; + + if (null !== $cookie_path && !defined('TESTSUITE')) { + return $cookie_path; + } + + $url = $this->get('PmaAbsoluteUri'); + + if (! empty($url)) { + $path = parse_url($url, PHP_URL_PATH); + if (! empty($path)) { + if (substr($path, -1) != '/') { + return $path . '/'; + } + return $path; + } + } + + $parsed_url = parse_url($GLOBALS['PMA_PHP_SELF']); + + $parts = explode( + '/', + rtrim(str_replace('\\', '/', $parsed_url['path']), '/') + ); + + /* Remove filename */ + if (substr($parts[count($parts) - 1], -4) == '.php') { + $parts = array_slice($parts, 0, count($parts) - 1); + } + + /* Remove extra path from javascript calls */ + if (defined('PMA_PATH_TO_BASEDIR')) { + $parts = array_slice($parts, 0, count($parts) - 1); + } + + $parts[] = ''; + + return implode('/', $parts); + } + + /** + * enables backward compatibility + * + * @return void + */ + public function enableBc() + { + $GLOBALS['cfg'] = $this->settings; + $GLOBALS['default_server'] = $this->default_server; + unset($this->default_server); + $GLOBALS['is_upload'] = $this->get('enable_upload'); + $GLOBALS['max_upload_size'] = $this->get('max_upload_size'); + $GLOBALS['is_https'] = $this->get('is_https'); + + $defines = array( + 'PMA_VERSION', + 'PMA_MAJOR_VERSION', + 'PMA_THEME_VERSION', + 'PMA_THEME_GENERATION', + 'PMA_IS_WINDOWS', + 'PMA_IS_GD2', + 'PMA_USR_OS', + 'PMA_USR_BROWSER_VER', + 'PMA_USR_BROWSER_AGENT' + ); + + foreach ($defines as $define) { + if (! defined($define)) { + define($define, $this->get($define)); + } + } + } + + /** + * returns options for font size selection + * + * @param string $current_size current selected font size with unit + * + * @return array selectable font sizes + */ + protected static function getFontsizeOptions($current_size = '82%') + { + $unit = preg_replace('/[0-9.]*/', '', $current_size); + $value = preg_replace('/[^0-9.]*/', '', $current_size); + + $factors = array(); + $options = array(); + $options["$value"] = $value . $unit; + + if ($unit === '%') { + $factors[] = 1; + $factors[] = 5; + $factors[] = 10; + $options['100'] = '100%'; + } elseif ($unit === 'em') { + $factors[] = 0.05; + $factors[] = 0.2; + $factors[] = 1; + } elseif ($unit === 'pt') { + $factors[] = 0.5; + $factors[] = 2; + } elseif ($unit === 'px') { + $factors[] = 1; + $factors[] = 5; + $factors[] = 10; + } else { + //unknown font size unit + $factors[] = 0.05; + $factors[] = 0.2; + $factors[] = 1; + $factors[] = 5; + $factors[] = 10; + } + + foreach ($factors as $key => $factor) { + $option_inc = $value + $factor; + $option_dec = $value - $factor; + while (count($options) < 21) { + $options["$option_inc"] = $option_inc . $unit; + if ($option_dec > $factors[0]) { + $options["$option_dec"] = $option_dec . $unit; + } + $option_inc += $factor; + $option_dec -= $factor; + if (isset($factors[$key + 1]) + && $option_inc >= $value + $factors[$key + 1] + ) { + break; + } + } + } + ksort($options); + return $options; + } + + /** + * returns html selectbox for font sizes + * + * @return string html selectbox + */ + protected static function getFontsizeSelection() + { + $current_size = $GLOBALS['PMA_Config']->get('FontSize'); + // for the case when there is no config file (this is supported) + if (empty($current_size)) { + $current_size = '82%'; + } + $options = Config::getFontsizeOptions($current_size); + + $return = '' . "\n" + . ''; + + return $return; + } + + /** + * return complete font size selection form + * + * @return string html selectbox + */ + public static function getFontsizeForm() + { + return '
      ' . "\n" + . Url::getHiddenInputs() . "\n" + . Config::getFontsizeSelection() . "\n" + . '
      '; + } + + /** + * removes cookie + * + * @param string $cookie name of cookie to remove + * + * @return boolean result of setcookie() + */ + public function removeCookie($cookie) + { + if (defined('TESTSUITE')) { + if (isset($_COOKIE[$cookie])) { + unset($_COOKIE[$cookie]); + } + return true; + } + return setcookie( + $cookie, + '', + time() - 3600, + $this->getRootPath(), + '', + $this->isHttps() + ); + } + + /** + * sets cookie if value is different from current cookie value, + * or removes if value is equal to default + * + * @param string $cookie name of cookie to remove + * @param mixed $value new cookie value + * @param string $default default value + * @param int $validity validity of cookie in seconds (default is one month) + * @param bool $httponly whether cookie is only for HTTP (and not for scripts) + * + * @return boolean result of setcookie() + */ + public function setCookie($cookie, $value, $default = null, + $validity = null, $httponly = true + ) { + if (strlen($value) > 0 && null !== $default && $value === $default + ) { + // default value is used + if (isset($_COOKIE[$cookie])) { + // remove cookie + return $this->removeCookie($cookie); + } + return false; + } + + if (strlen($value) === 0 && isset($_COOKIE[$cookie])) { + // remove cookie, value is empty + return $this->removeCookie($cookie); + } + + if (! isset($_COOKIE[$cookie]) || $_COOKIE[$cookie] !== $value) { + // set cookie with new value + /* Calculate cookie validity */ + if ($validity === null) { + /* Valid for one month */ + $validity = time() + 2592000; + } elseif ($validity == 0) { + /* Valid for session */ + $validity = 0; + } else { + $validity = time() + $validity; + } + if (defined('TESTSUITE')) { + $_COOKIE[$cookie] = $value; + return true; + } + return setcookie( + $cookie, + $value, + $validity, + $this->getRootPath(), + '', + $this->isHttps(), + $httponly + ); + } + + // cookie has already $value as value + return true; + } + + + /** + * Error handler to catch fatal errors when loading configuration + * file + * + * + * PMA_Config_fatalErrorHandler + * @return void + */ + public static function fatalErrorHandler() + { + if (!isset($GLOBALS['pma_config_loading']) + || !$GLOBALS['pma_config_loading'] + ) { + return; + } + + $error = error_get_last(); + if ($error === null) { + return; + } + + Core::fatalError( + sprintf( + 'Failed to load phpMyAdmin configuration (%s:%s): %s', + Error::relPath($error['file']), + $error['line'], + $error['message'] + ) + ); + } + + /** + * Wrapper for footer/header rendering + * + * @param string $filename File to check and render + * @param string $id Div ID + * + * @return string + */ + private static function _renderCustom($filename, $id) + { + $retval = ''; + if (@file_exists($filename)) { + $retval .= '
      '; + ob_start(); + include $filename; + $retval .= ob_get_contents(); + ob_end_clean(); + $retval .= '
      '; + } + return $retval; + } + + /** + * Renders user configured footer + * + * @return string + */ + public static function renderFooter() + { + return self::_renderCustom(CUSTOM_FOOTER_FILE, 'pma_footer'); + } + + /** + * Renders user configured footer + * + * @return string + */ + public static function renderHeader() + { + return self::_renderCustom(CUSTOM_HEADER_FILE, 'pma_header'); + } + + /** + * Returns temporary dir path + * + * @param string $name Directory name + * + * @return string|null + */ + public function getTempDir($name) + { + static $temp_dir = array(); + + if (isset($temp_dir[$name]) && !defined('TESTSUITE')) { + return $temp_dir[$name]; + } + + $path = $this->get('TempDir'); + if (empty($path)) { + $path = null; + } else { + $path .= '/' . $name; + if (! @is_dir($path)) { + @mkdir($path, 0770, true); + } + if (! @is_dir($path) || ! @is_writable($path)) { + $path = null; + } + } + + $temp_dir[$name] = $path; + return $path; + } + + /** + * Returns temporary directory + * + * @return string + */ + public function getUploadTempDir() + { + // First try configured temp dir + // Fallback to PHP upload_tmp_dir + $dirs = array( + $this->getTempDir('upload'), + ini_get('upload_tmp_dir'), + sys_get_temp_dir(), + ); + + foreach ($dirs as $dir) { + if (! empty($dir) && @is_writable($dir)) { + return realpath($dir); + } + } + + return null; + } + + /** + * Selects server based on request parameters. + * + * @return integer + */ + public function selectServer() { + $server = 0; + $request = empty($_REQUEST['server']) ? 0 : $_REQUEST['server']; + + /** + * Lookup server by name + * (see FAQ 4.8) + */ + if (! is_numeric($request)) { + foreach ($this->settings['Servers'] as $i => $server) { + $verboseToLower = mb_strtolower($server['verbose']); + $serverToLower = mb_strtolower($request); + if ($server['host'] == $request + || $server['verbose'] == $request + || $verboseToLower == $serverToLower + || md5($verboseToLower) === $serverToLower + ) { + $request = $i; + break; + } + } + if (is_string($request)) { + $request = 0; + } + } + + /** + * If no server is selected, make sure that $this->settings['Server'] is empty (so + * that nothing will work), and skip server authentication. + * We do NOT exit here, but continue on without logging into any server. + * This way, the welcome page will still come up (with no server info) and + * present a choice of servers in the case that there are multiple servers + * and '$this->settings['ServerDefault'] = 0' is set. + */ + + if (is_numeric($request) && ! empty($request) && ! empty($this->settings['Servers'][$request])) { + $server = $request; + $this->settings['Server'] = $this->settings['Servers'][$server]; + } else { + if (!empty($this->settings['Servers'][$this->settings['ServerDefault']])) { + $server = $this->settings['ServerDefault']; + $this->settings['Server'] = $this->settings['Servers'][$server]; + } else { + $server = 0; + $this->settings['Server'] = array(); + } + } + + return $server; + } + + /** + * Checks whether Servers configuration is valid and possibly apply fixups. + * + * @return void + */ + public function checkServers() { + // Do we have some server? + if (! isset($this->settings['Servers']) || count($this->settings['Servers']) == 0) { + // No server => create one with defaults + $this->settings['Servers'] = array(1 => $this->default_server); + } else { + // We have server(s) => apply default configuration + $new_servers = array(); + + foreach ($this->settings['Servers'] as $server_index => $each_server) { + + // Detect wrong configuration + if (!is_int($server_index) || $server_index < 1) { + trigger_error( + sprintf(__('Invalid server index: %s'), $server_index), + E_USER_ERROR + ); + } + + $each_server = array_merge($this->default_server, $each_server); + + // Final solution to bug #582890 + // If we are using a socket connection + // and there is nothing in the verbose server name + // or the host field, then generate a name for the server + // in the form of "Server 2", localized of course! + if (empty($each_server['host']) && empty($each_server['verbose'])) { + $each_server['verbose'] = sprintf(__('Server %d'), $server_index); + } + + $new_servers[$server_index] = $each_server; + } + $this->settings['Servers'] = $new_servers; + } + } +} + +if (!defined('TESTSUITE')) { + register_shutdown_function(array('PhpMyAdmin\Config', 'fatalErrorHandler')); +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Config/ConfigFile.php b/php/apps/phpmyadmin49/html/libraries/classes/Config/ConfigFile.php new file mode 100644 index 00000000..5e4e712e --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Config/ConfigFile.php @@ -0,0 +1,531 @@ +_defaultCfg; + include './libraries/config.default.php'; + $cfg['fontsize'] = '82%'; + + // load additional config information + $cfg_db = &$this->_cfgDb; + include './libraries/config.values.php'; + + // apply default values overrides + if (count($cfg_db['_overrides'])) { + foreach ($cfg_db['_overrides'] as $path => $value) { + Core::arrayWrite($path, $cfg, $value); + } + } + + $this->_baseCfg = $base_config; + $this->_isInSetup = is_null($base_config); + $this->_id = 'ConfigFile' . $GLOBALS['server']; + if (!isset($_SESSION[$this->_id])) { + $_SESSION[$this->_id] = array(); + } + } + + /** + * Sets names of config options which will be placed in config file even if + * they are set to their default values (use only full paths) + * + * @param array $keys the names of the config options + * + * @return void + */ + public function setPersistKeys(array $keys) + { + // checking key presence is much faster than searching so move values + // to keys + $this->_persistKeys = array_flip($keys); + } + + /** + * Returns flipped array set by {@link setPersistKeys()} + * + * @return array + */ + public function getPersistKeysMap() + { + return $this->_persistKeys; + } + + /** + * By default ConfigFile allows setting of all configuration keys, use + * this method to set up a filter on {@link set()} method + * + * @param array|null $keys array of allowed keys or null to remove filter + * + * @return void + */ + public function setAllowedKeys($keys) + { + if ($keys === null) { + $this->_setFilter = null; + return; + } + // checking key presence is much faster than searching so move values + // to keys + $this->_setFilter = array_flip($keys); + } + + /** + * Sets path mapping for updating config in + * {@link updateWithGlobalConfig()} or reading + * by {@link getConfig()} or {@link getConfigArray()} + * + * @param array $mapping Contains the mapping of "Server/config options" + * to "Server/1/config options" + * + * @return void + */ + public function setCfgUpdateReadMapping(array $mapping) + { + $this->_cfgUpdateReadMapping = $mapping; + } + + /** + * Resets configuration data + * + * @return void + */ + public function resetConfigData() + { + $_SESSION[$this->_id] = array(); + } + + /** + * Sets configuration data (overrides old data) + * + * @param array $cfg Configuration options + * + * @return void + */ + public function setConfigData(array $cfg) + { + $_SESSION[$this->_id] = $cfg; + } + + /** + * Sets config value + * + * @param string $path Path + * @param mixed $value Value + * @param string $canonical_path Canonical path + * + * @return void + */ + public function set($path, $value, $canonical_path = null) + { + if ($canonical_path === null) { + $canonical_path = $this->getCanonicalPath($path); + } + // apply key whitelist + if ($this->_setFilter !== null + && ! isset($this->_setFilter[$canonical_path]) + ) { + return; + } + // if the path isn't protected it may be removed + if (isset($this->_persistKeys[$canonical_path])) { + Core::arrayWrite($path, $_SESSION[$this->_id], $value); + return; + } + + $default_value = $this->getDefault($canonical_path); + $remove_path = $value === $default_value; + if ($this->_isInSetup) { + // remove if it has a default value or is empty + $remove_path = $remove_path + || (empty($value) && empty($default_value)); + } else { + // get original config values not overwritten by user + // preferences to allow for overwriting options set in + // config.inc.php with default values + $instance_default_value = Core::arrayRead( + $canonical_path, + $this->_baseCfg + ); + // remove if it has a default value and base config (config.inc.php) + // uses default value + $remove_path = $remove_path + && ($instance_default_value === $default_value); + } + if ($remove_path) { + Core::arrayRemove($path, $_SESSION[$this->_id]); + return; + } + + Core::arrayWrite($path, $_SESSION[$this->_id], $value); + } + + /** + * Flattens multidimensional array, changes indices to paths + * (eg. 'key/subkey'). + * Used as array_walk() callback. + * + * @param mixed $value Value + * @param mixed $key Key + * @param mixed $prefix Prefix + * + * @return void + */ + private function _flattenArray($value, $key, $prefix) + { + // no recursion for numeric arrays + if (is_array($value) && !isset($value[0])) { + $prefix .= $key . '/'; + array_walk($value, array($this, '_flattenArray'), $prefix); + } else { + $this->_flattenArrayResult[$prefix . $key] = $value; + } + } + + /** + * Returns default config in a flattened array + * + * @return array + */ + public function getFlatDefaultConfig() + { + $this->_flattenArrayResult = array(); + array_walk($this->_defaultCfg, array($this, '_flattenArray'), ''); + $flat_cfg = $this->_flattenArrayResult; + $this->_flattenArrayResult = null; + return $flat_cfg; + } + + /** + * Updates config with values read from given array + * (config will contain differences to defaults from config.defaults.php). + * + * @param array $cfg Configuration + * + * @return void + */ + public function updateWithGlobalConfig(array $cfg) + { + // load config array and flatten it + $this->_flattenArrayResult = array(); + array_walk($cfg, array($this, '_flattenArray'), ''); + $flat_cfg = $this->_flattenArrayResult; + $this->_flattenArrayResult = null; + + // save values map for translating a few user preferences paths, + // should be complemented by code reading from generated config + // to perform inverse mapping + foreach ($flat_cfg as $path => $value) { + if (isset($this->_cfgUpdateReadMapping[$path])) { + $path = $this->_cfgUpdateReadMapping[$path]; + } + $this->set($path, $value, $path); + } + } + + /** + * Returns config value or $default if it's not set + * + * @param string $path Path of config file + * @param mixed $default Default values + * + * @return mixed + */ + public function get($path, $default = null) + { + return Core::arrayRead($path, $_SESSION[$this->_id], $default); + } + + /** + * Returns default config value or $default it it's not set ie. it doesn't + * exist in config.default.php ($cfg) and config.values.php + * ($_cfg_db['_overrides']) + * + * @param string $canonical_path Canonical path + * @param mixed $default Default value + * + * @return mixed + */ + public function getDefault($canonical_path, $default = null) + { + return Core::arrayRead($canonical_path, $this->_defaultCfg, $default); + } + + /** + * Returns config value, if it's not set uses the default one; returns + * $default if the path isn't set and doesn't contain a default value + * + * @param string $path Path + * @param mixed $default Default value + * + * @return mixed + */ + public function getValue($path, $default = null) + { + $v = Core::arrayRead($path, $_SESSION[$this->_id], null); + if ($v !== null) { + return $v; + } + $path = $this->getCanonicalPath($path); + return $this->getDefault($path, $default); + } + + /** + * Returns canonical path + * + * @param string $path Path + * + * @return string + */ + public function getCanonicalPath($path) + { + return preg_replace('#^Servers/([\d]+)/#', 'Servers/1/', $path); + } + + /** + * Returns config database entry for $path ($cfg_db in config_info.php) + * + * @param string $path path of the variable in config db + * @param mixed $default default value + * + * @return mixed + */ + public function getDbEntry($path, $default = null) + { + return Core::arrayRead($path, $this->_cfgDb, $default); + } + + /** + * Returns server count + * + * @return int + */ + public function getServerCount() + { + return isset($_SESSION[$this->_id]['Servers']) + ? count($_SESSION[$this->_id]['Servers']) + : 0; + } + + /** + * Returns server list + * + * @return array|null + */ + public function getServers() + { + return isset($_SESSION[$this->_id]['Servers']) + ? $_SESSION[$this->_id]['Servers'] + : null; + } + + /** + * Returns DSN of given server + * + * @param integer $server server index + * + * @return string + */ + public function getServerDSN($server) + { + if (!isset($_SESSION[$this->_id]['Servers'][$server])) { + return ''; + } + + $path = 'Servers/' . $server; + $dsn = 'mysqli://'; + if ($this->getValue("$path/auth_type") == 'config') { + $dsn .= $this->getValue("$path/user"); + if (! empty($this->getValue("$path/password"))) { + $dsn .= ':***'; + } + $dsn .= '@'; + } + if ($this->getValue("$path/host") != 'localhost') { + $dsn .= $this->getValue("$path/host"); + $port = $this->getValue("$path/port"); + if ($port) { + $dsn .= ':' . $port; + } + } else { + $dsn .= $this->getValue("$path/socket"); + } + return $dsn; + } + + /** + * Returns server name + * + * @param int $id server index + * + * @return string + */ + public function getServerName($id) + { + if (!isset($_SESSION[$this->_id]['Servers'][$id])) { + return ''; + } + $verbose = $this->get("Servers/$id/verbose"); + if (!empty($verbose)) { + return $verbose; + } + $host = $this->get("Servers/$id/host"); + return empty($host) ? 'localhost' : $host; + } + + /** + * Removes server + * + * @param int $server server index + * + * @return void + */ + public function removeServer($server) + { + if (!isset($_SESSION[$this->_id]['Servers'][$server])) { + return; + } + $last_server = $this->getServerCount(); + + for ($i = $server; $i < $last_server; $i++) { + $_SESSION[$this->_id]['Servers'][$i] + = $_SESSION[$this->_id]['Servers'][$i + 1]; + } + unset($_SESSION[$this->_id]['Servers'][$last_server]); + + if (isset($_SESSION[$this->_id]['ServerDefault']) + && $_SESSION[$this->_id]['ServerDefault'] == $last_server + ) { + unset($_SESSION[$this->_id]['ServerDefault']); + } + } + + /** + * Returns configuration array (full, multidimensional format) + * + * @return array + */ + public function getConfig() + { + $c = $_SESSION[$this->_id]; + foreach ($this->_cfgUpdateReadMapping as $map_to => $map_from) { + // if the key $c exists in $map_to + if (Core::arrayRead($map_to, $c) !== null) { + Core::arrayWrite($map_to, $c, Core::arrayRead($map_from, $c)); + Core::arrayRemove($map_from, $c); + } + } + return $c; + } + + /** + * Returns configuration array (flat format) + * + * @return array + */ + public function getConfigArray() + { + $this->_flattenArrayResult = array(); + array_walk($_SESSION[$this->_id], array($this, '_flattenArray'), ''); + $c = $this->_flattenArrayResult; + $this->_flattenArrayResult = null; + + $persistKeys = array_diff( + array_keys($this->_persistKeys), + array_keys($c) + ); + foreach ($persistKeys as $k) { + $c[$k] = $this->getDefault($this->getCanonicalPath($k)); + } + + foreach ($this->_cfgUpdateReadMapping as $map_to => $map_from) { + if (!isset($c[$map_from])) { + continue; + } + $c[$map_to] = $c[$map_from]; + unset($c[$map_from]); + } + return $c; + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Config/Descriptions.php b/php/apps/phpmyadmin49/html/libraries/classes/Config/Descriptions.php new file mode 100644 index 00000000..37fc5c22 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Config/Descriptions.php @@ -0,0 +1,1493 @@ +LOCK TABLES') + ); + case 'Export_asfile_name': + return __('Save as file'); + case 'Export_charset_name': + return __('Character set of the file'); + case 'Export_codegen_format_name': + return __('Format'); + case 'Export_compression_name': + return __('Compression'); + case 'Export_csv_columns_name': + return __('Put columns names in the first row'); + case 'Export_csv_enclosed_name': + return __('Columns enclosed with'); + case 'Export_csv_escaped_name': + return __('Columns escaped with'); + case 'Export_csv_null_name': + return __('Replace NULL with'); + case 'Export_csv_removeCRLF_name': + return __('Remove CRLF characters within columns'); + case 'Export_csv_separator_name': + return __('Columns terminated with'); + case 'Export_csv_terminated_name': + return __('Lines terminated with'); + case 'Export_excel_columns_name': + return __('Put columns names in the first row'); + case 'Export_excel_edition_name': + return __('Excel edition'); + case 'Export_excel_null_name': + return __('Replace NULL with'); + case 'Export_excel_removeCRLF_name': + return __('Remove CRLF characters within columns'); + case 'Export_file_template_database_name': + return __('Database name template'); + case 'Export_file_template_server_name': + return __('Server name template'); + case 'Export_file_template_table_name': + return __('Table name template'); + case 'Export_format_name': + return __('Format'); + case 'Export_htmlword_columns_name': + return __('Put columns names in the first row'); + case 'Export_htmlword_null_name': + return __('Replace NULL with'); + case 'Export_htmlword_structure_or_data_name': + return __('Dump table'); + case 'Export_latex_caption_name': + return __('Include table caption'); + case 'Export_latex_columns_name': + return __('Put columns names in the first row'); + case 'Export_latex_comments_name': + return __('Comments'); + case 'Export_latex_data_caption_name': + return __('Table caption'); + case 'Export_latex_data_continued_caption_name': + return __('Continued table caption'); + case 'Export_latex_data_label_name': + return __('Label key'); + case 'Export_latex_mime_name': + return __('MIME type'); + case 'Export_latex_null_name': + return __('Replace NULL with'); + case 'Export_latex_relation_name': + return __('Relationships'); + case 'Export_latex_structure_caption_name': + return __('Table caption'); + case 'Export_latex_structure_continued_caption_name': + return __('Continued table caption'); + case 'Export_latex_structure_label_name': + return __('Label key'); + case 'Export_latex_structure_or_data_name': + return __('Dump table'); + case 'Export_method_name': + return __('Export method'); + case 'Export_ods_columns_name': + return __('Put columns names in the first row'); + case 'Export_ods_null_name': + return __('Replace NULL with'); + case 'Export_odt_columns_name': + return __('Put columns names in the first row'); + case 'Export_odt_comments_name': + return __('Comments'); + case 'Export_odt_mime_name': + return __('MIME type'); + case 'Export_odt_null_name': + return __('Replace NULL with'); + case 'Export_odt_relation_name': + return __('Relationships'); + case 'Export_odt_structure_or_data_name': + return __('Dump table'); + case 'Export_onserver_name': + return __('Save on server'); + case 'Export_onserver_overwrite_name': + return __('Overwrite existing file(s)'); + case 'Export_as_separate_files_name': + return __('Export as separate files'); + case 'Export_quick_export_onserver_name': + return __('Save on server'); + case 'Export_quick_export_onserver_overwrite_name': + return __('Overwrite existing file(s)'); + case 'Export_remember_file_template_name': + return __('Remember file name template'); + case 'Export_sql_auto_increment_name': + return __('Add AUTO_INCREMENT value'); + case 'Export_sql_backquotes_name': + return __('Enclose table and column names with backquotes'); + case 'Export_sql_compatibility_name': + return __('SQL compatibility mode'); + case 'Export_sql_dates_name': + return __('Creation/Update/Check dates'); + case 'Export_sql_delayed_name': + return __('Use delayed inserts'); + case 'Export_sql_disable_fk_name': + return __('Disable foreign key checks'); + case 'Export_sql_views_as_tables_name': + return __('Export views as tables'); + case 'Export_sql_metadata_name': + return __('Export related metadata from phpMyAdmin configuration storage'); + case 'Export_sql_create_database_name': + return sprintf(__('Add %s'), 'CREATE DATABASE / USE'); + case 'Export_sql_drop_database_name': + return sprintf(__('Add %s'), 'DROP DATABASE'); + case 'Export_sql_drop_table_name': + return sprintf( + __('Add %s'), 'DROP TABLE / VIEW / PROCEDURE / FUNCTION / EVENT / TRIGGER' + ); + case 'Export_sql_create_table_name': + return sprintf(__('Add %s'), 'CREATE TABLE'); + case 'Export_sql_create_view_name': + return sprintf(__('Add %s'), 'CREATE VIEW'); + case 'Export_sql_create_trigger_name': + return sprintf(__('Add %s'), 'CREATE TRIGGER'); + case 'Export_sql_hex_for_binary_name': + return __('Use hexadecimal for BINARY & BLOB'); + case 'Export_sql_if_not_exists_name': + return __( + 'Add IF NOT EXISTS (less efficient as indexes will be generated during' + . ' table creation)' + ); + case 'Export_sql_ignore_name': + return __('Use ignore inserts'); + case 'Export_sql_include_comments_name': + return __('Comments'); + case 'Export_sql_insert_syntax_name': + return __('Syntax to use when inserting data'); + case 'Export_sql_max_query_size_name': + return __('Maximal length of created query'); + case 'Export_sql_mime_name': + return __('MIME type'); + case 'Export_sql_procedure_function_name': + return sprintf(__('Add %s'), 'CREATE PROCEDURE / FUNCTION / EVENT'); + case 'Export_sql_relation_name': + return __('Relationships'); + case 'Export_sql_structure_or_data_name': + return __('Dump table'); + case 'Export_sql_type_name': + return __('Export type'); + case 'Export_sql_use_transaction_name': + return __('Enclose export in a transaction'); + case 'Export_sql_utc_time_name': + return __('Export time in UTC'); + case 'Export_texytext_columns_name': + return __('Put columns names in the first row'); + case 'Export_texytext_null_name': + return __('Replace NULL with'); + case 'Export_texytext_structure_or_data_name': + return __('Dump table'); + case 'ForeignKeyDropdownOrder_desc': + return __( + 'Sort order for items in a foreign-key dropdown box; [kbd]content[/kbd] is ' + . 'the referenced data, [kbd]id[/kbd] is the key value.' + ); + case 'ForeignKeyDropdownOrder_name': + return __('Foreign key dropdown order'); + case 'ForeignKeyMaxLimit_desc': + return __('A dropdown will be used if fewer items are present.'); + case 'ForeignKeyMaxLimit_name': + return __('Foreign key limit'); + case 'DefaultForeignKeyChecks_desc': + return __('Default value for foreign key checks checkbox for some queries.'); + case 'DefaultForeignKeyChecks_name': + return __('Foreign key checks'); + case 'Form_Browse_name': + return __('Browse mode'); + case 'Form_Browse_desc': + return __('Customize browse mode.'); + case 'Form_CodeGen_name': + return 'CodeGen'; + case 'Form_CodeGen_desc': + return __('Customize default options.'); + case 'Form_Csv_name': + return __('CSV'); + case 'Form_Csv_desc': + return __('Customize default options.'); + case 'Form_Developer_name': + return __('Developer'); + case 'Form_Developer_desc': + return __('Settings for phpMyAdmin developers.'); + case 'Form_Edit_name': + return __('Edit mode'); + case 'Form_Edit_desc': + return __('Customize edit mode.'); + case 'Form_Export_defaults_name': + return __('Export defaults'); + case 'Form_Export_defaults_desc': + return __('Customize default export options.'); + case 'Form_General_name': + return __('General'); + case 'Form_General_desc': + return __('Set some commonly used options.'); + case 'Form_Import_defaults_name': + return __('Import defaults'); + case 'Form_Import_defaults_desc': + return __('Customize default common import options.'); + case 'Form_Import_export_name': + return __('Import / export'); + case 'Form_Import_export_desc': + return __('Set import and export directories and compression options.'); + case 'Form_Latex_name': + return __('LaTeX'); + case 'Form_Latex_desc': + return __('Customize default options.'); + case 'Form_Navi_databases_name': + return __('Databases'); + case 'Form_Navi_databases_desc': + return __('Databases display options.'); + case 'Form_Navi_panel_name': + return __('Navigation panel'); + case 'Form_Navi_panel_desc': + return __('Customize appearance of the navigation panel.'); + case 'Form_Navi_tree_name': + return __('Navigation tree'); + case 'Form_Navi_tree_desc': + return __('Customize the navigation tree.'); + case 'Form_Navi_servers_name': + return __('Servers'); + case 'Form_Navi_servers_desc': + return __('Servers display options.'); + case 'Form_Navi_tables_name': + return __('Tables'); + case 'Form_Navi_tables_desc': + return __('Tables display options.'); + case 'Form_Main_panel_name': + return __('Main panel'); + case 'Form_Microsoft_Office_name': + return __('Microsoft Office'); + case 'Form_Microsoft_Office_desc': + return __('Customize default options.'); + case 'Form_Open_Document_name': + return 'OpenDocument'; + case 'Form_Open_Document_desc': + return __('Customize default options.'); + case 'Form_Other_core_settings_name': + return __('Other core settings'); + case 'Form_Other_core_settings_desc': + return __('Settings that didn\'t fit anywhere else.'); + case 'Form_Page_titles_name': + return __('Page titles'); + case 'Form_Page_titles_desc': + return __( + 'Specify browser\'s title bar text. Refer to ' + . '[doc@faq6-27]documentation[/doc] for magic strings that can be used ' + . 'to get special values.' + ); + case 'Form_Security_name': + return __('Security'); + case 'Form_Security_desc': + return __( + 'Please note that phpMyAdmin is just a user interface and its features do not ' + . 'limit MySQL.' + ); + case 'Form_Server_name': + return __('Basic settings'); + case 'Form_Server_auth_name': + return __('Authentication'); + case 'Form_Server_auth_desc': + return __('Authentication settings.'); + case 'Form_Server_config_name': + return __('Server configuration'); + case 'Form_Server_config_desc': + return __( + 'Advanced server configuration, do not change these options unless you know ' + . 'what they are for.' + ); + case 'Form_Server_desc': + return __('Enter server connection parameters.'); + case 'Form_Server_pmadb_name': + return __('Configuration storage'); + case 'Form_Server_pmadb_desc': + return __( + 'Configure phpMyAdmin configuration storage to gain access to additional ' + . 'features, see [doc@linked-tables]phpMyAdmin configuration storage[/doc] in ' + . 'documentation.' + ); + case 'Form_Server_tracking_name': + return __('Changes tracking'); + case 'Form_Server_tracking_desc': + return __( + 'Tracking of changes made in database. Requires the phpMyAdmin configuration ' + . 'storage.' + ); + case 'Form_Sql_name': + return __('SQL'); + case 'Form_Sql_box_name': + return __('SQL Query box'); + case 'Form_Sql_box_desc': + return __('Customize links shown in SQL Query boxes.'); + case 'Form_Sql_desc': + return __('Customize default options.'); + case 'Form_Sql_queries_name': + return __('SQL queries'); + case 'Form_Sql_queries_desc': + return __('SQL queries settings.'); + case 'Form_Startup_name': + return __('Startup'); + case 'Form_Startup_desc': + return __('Customize startup page.'); + case 'Form_DbStructure_name': + return __('Database structure'); + case 'Form_DbStructure_desc': + return __('Choose which details to show in the database structure (list of tables).'); + case 'Form_TableStructure_name': + return __('Table structure'); + case 'Form_TableStructure_desc': + return __('Settings for the table structure (list of columns).'); + case 'Form_Tabs_name': + return __('Tabs'); + case 'Form_Tabs_desc': + return __('Choose how you want tabs to work.'); + case 'Form_DisplayRelationalSchema_name': + return __('Display relational schema'); + case 'Form_DisplayRelationalSchema_desc': + return ''; + case 'PDFDefaultPageSize_name': + return __('Paper size'); + case 'PDFDefaultPageSize_desc': + return ''; + case 'Form_Databases_name': + return __('Databases'); + case 'Form_Text_fields_name': + return __('Text fields'); + case 'Form_Text_fields_desc': + return __('Customize text input fields.'); + case 'Form_Texy_name': + return __('Texy! text'); + case 'Form_Texy_desc': + return __('Customize default options'); + case 'Form_Warnings_name': + return __('Warnings'); + case 'Form_Warnings_desc': + return __('Disable some of the warnings shown by phpMyAdmin.'); + case 'Form_Console_name': + return __('Console'); + case 'GZipDump_desc': + return __( + 'Enable gzip compression for import ' + . 'and export operations.' + ); + case 'GZipDump_name': + return __('GZip'); + case 'IconvExtraParams_name': + return __('Extra parameters for iconv'); + case 'IgnoreMultiSubmitErrors_desc': + return __( + 'If enabled, phpMyAdmin continues computing multiple-statement queries even if ' + . 'one of the queries failed.' + ); + case 'IgnoreMultiSubmitErrors_name': + return __('Ignore multiple statement errors'); + case 'Import_allow_interrupt_desc': + return __( + 'Allow interrupt of import in case script detects it is close to time limit. ' + . 'This might be a good way to import large files, however it can break ' + . 'transactions.' + ); + case 'Import_allow_interrupt_name': + return __('Partial import: allow interrupt'); + case 'Import_charset_name': + return __('Character set of the file'); + case 'Import_csv_col_names_name': + return __('Lines terminated with'); + case 'Import_csv_enclosed_name': + return __('Columns enclosed with'); + case 'Import_csv_escaped_name': + return __('Columns escaped with'); + case 'Import_csv_ignore_name': + return __('Do not abort on INSERT error'); + case 'Import_csv_replace_name': + return __('Add ON DUPLICATE KEY UPDATE'); + case 'Import_csv_replace_desc': + return __('Update data when duplicate keys found on import'); + case 'Import_csv_terminated_name': + return __('Columns terminated with'); + case 'Import_format_desc': + return __( + 'Default format; be aware that this list depends on location (database, table) ' + . 'and only SQL is always available.' + ); + case 'Import_format_name': + return __('Format of imported file'); + case 'Import_ldi_enclosed_name': + return __('Columns enclosed with'); + case 'Import_ldi_escaped_name': + return __('Columns escaped with'); + case 'Import_ldi_ignore_name': + return __('Do not abort on INSERT error'); + case 'Import_ldi_local_option_name': + return __('Use LOCAL keyword'); + case 'Import_ldi_replace_name': + return __('Add ON DUPLICATE KEY UPDATE'); + case 'Import_ldi_replace_desc': + return __('Update data when duplicate keys found on import'); + case 'Import_ldi_terminated_name': + return __('Columns terminated with'); + case 'Import_ods_col_names_name': + return __('Column names in first row'); + case 'Import_ods_empty_rows_name': + return __('Do not import empty rows'); + case 'Import_ods_recognize_currency_name': + return __('Import currencies ($5.00 to 5.00)'); + case 'Import_ods_recognize_percentages_name': + return __('Import percentages as proper decimals (12.00% to .12)'); + case 'Import_skip_queries_desc': + return __('Number of queries to skip from start.'); + case 'Import_skip_queries_name': + return __('Partial import: skip queries'); + case 'Import_sql_compatibility_name': + return __('SQL compatibility mode'); + case 'Import_sql_no_auto_value_on_zero_name': + return __('Do not use AUTO_INCREMENT for zero values'); + case 'Import_sql_read_as_multibytes_name': + return __('Read as multibytes'); + case 'InitialSlidersState_name': + return __('Initial state for sliders'); + case 'InsertRows_desc': + return __('How many rows can be inserted at one time.'); + case 'InsertRows_name': + return __('Number of inserted rows'); + case 'LimitChars_desc': + return __('Maximum number of characters shown in any non-numeric column on browse view.'); + case 'LimitChars_name': + return __('Limit column characters'); + case 'LoginCookieDeleteAll_desc': + return __( + 'If TRUE, logout deletes cookies for all servers; when set to FALSE, logout ' + . 'only occurs for the current server. Setting this to FALSE makes it easy to ' + . 'forget to log out from other servers when connected to multiple servers.' + ); + case 'LoginCookieDeleteAll_name': + return __('Delete all cookies on logout'); + case 'LoginCookieRecall_desc': + return __( + 'Define whether the previous login should be recalled or not in ' + . '[kbd]cookie[/kbd] authentication mode.' + ); + case 'LoginCookieRecall_name': + return __('Recall user name'); + case 'LoginCookieStore_desc': + return __( + 'Defines how long (in seconds) a login cookie should be stored in browser. ' + . 'The default of 0 means that it will be kept for the existing session only, ' + . 'and will be deleted as soon as you close the browser window. This is ' + . 'recommended for non-trusted environments.' + ); + case 'LoginCookieStore_name': + return __('Login cookie store'); + case 'LoginCookieValidity_desc': + return __('Define how long (in seconds) a login cookie is valid.'); + case 'LoginCookieValidity_name': + return __('Login cookie validity'); + case 'LongtextDoubleTextarea_desc': + return __('Double size of textarea for LONGTEXT columns.'); + case 'LongtextDoubleTextarea_name': + return __('Bigger textarea for LONGTEXT'); + case 'MaxCharactersInDisplayedSQL_desc': + return __('Maximum number of characters used when a SQL query is displayed.'); + case 'MaxCharactersInDisplayedSQL_name': + return __('Maximum displayed SQL length'); + case 'MaxDbList_cmt': + return __('Users cannot set a higher value'); + case 'MaxDbList_desc': + return __('Maximum number of databases displayed in database list.'); + case 'MaxDbList_name': + return __('Maximum databases'); + case 'FirstLevelNavigationItems_desc': + return __( + 'The number of items that can be displayed on each page on the first level' + . ' of the navigation tree.' + ); + case 'FirstLevelNavigationItems_name': + return __('Maximum items on first level'); + case 'MaxNavigationItems_desc': + return __('The number of items that can be displayed on each page of the navigation tree.'); + case 'MaxNavigationItems_name': + return __('Maximum items in branch'); + case 'MaxRows_desc': + return __( + 'Number of rows displayed when browsing a result set. If the result set ' + . 'contains more rows, "Previous" and "Next" links will be ' + . 'shown.' + ); + case 'MaxRows_name': + return __('Maximum number of rows to display'); + case 'MaxTableList_cmt': + return __('Users cannot set a higher value'); + case 'MaxTableList_desc': + return __('Maximum number of tables displayed in table list.'); + case 'MaxTableList_name': + return __('Maximum tables'); + case 'MemoryLimit_desc': + return __( + 'The number of bytes a script is allowed to allocate, eg. [kbd]32M[/kbd] ' + . '([kbd]-1[/kbd] for no limit and [kbd]0[/kbd] for no change).' + ); + case 'MemoryLimit_name': + return __('Memory limit'); + case 'ShowDatabasesNavigationAsTree_desc': + return __('In the navigation panel, replaces the database tree with a selector'); + case 'ShowDatabasesNavigationAsTree_name': + return __('Show databases navigation as tree'); + case 'NavigationWidth_name': + return __('Navigation panel width'); + case 'NavigationWidth_desc': + return __('Set to 0 to collapse navigation panel.'); + case 'NavigationLinkWithMainPanel_desc': + return __('Link with main panel by highlighting the current database or table.'); + case 'NavigationLinkWithMainPanel_name': + return __('Link with main panel'); + case 'NavigationDisplayLogo_desc': + return __('Show logo in navigation panel.'); + case 'NavigationDisplayLogo_name': + return __('Display logo'); + case 'NavigationLogoLink_desc': + return __('URL where logo in the navigation panel will point to.'); + case 'NavigationLogoLink_name': + return __('Logo link URL'); + case 'NavigationLogoLinkWindow_desc': + return __( + 'Open the linked page in the main window ([kbd]main[/kbd]) or in a new one ' + . '([kbd]new[/kbd]).' + ); + case 'NavigationLogoLinkWindow_name': + return __('Logo link target'); + case 'NavigationDisplayServers_desc': + return __('Display server choice at the top of the navigation panel.'); + case 'NavigationDisplayServers_name': + return __('Display servers selection'); + case 'NavigationTreeDefaultTabTable_name': + return __('Target for quick access icon'); + case 'NavigationTreeDefaultTabTable2_name': + return __('Target for second quick access icon'); + case 'NavigationTreeDisplayItemFilterMinimum_desc': + return __( + 'Defines the minimum number of items (tables, views, routines and events) to ' + . 'display a filter box.' + ); + case 'NavigationTreeDisplayItemFilterMinimum_name': + return __('Minimum number of items to display the filter box'); + case 'NavigationTreeDisplayDbFilterMinimum_name': + return __('Minimum number of databases to display the database filter box'); + case 'NavigationTreeEnableGrouping_desc': + return __( + 'Group items in the navigation tree (determined by the separator defined in ' . + 'the Databases and Tables tabs above).' + ); + case 'NavigationTreeEnableGrouping_name': + return __('Group items in the tree'); + case 'NavigationTreeDbSeparator_desc': + return __('String that separates databases into different tree levels.'); + case 'NavigationTreeDbSeparator_name': + return __('Database tree separator'); + case 'NavigationTreeTableSeparator_desc': + return __('String that separates tables into different tree levels.'); + case 'NavigationTreeTableSeparator_name': + return __('Table tree separator'); + case 'NavigationTreeTableLevel_name': + return __('Maximum table tree depth'); + case 'NavigationTreePointerEnable_desc': + return __('Highlight server under the mouse cursor.'); + case 'NavigationTreePointerEnable_name': + return __('Enable highlighting'); + case 'NavigationTreeEnableExpansion_desc': + return __('Whether to offer the possibility of tree expansion in the navigation panel.'); + case 'NavigationTreeEnableExpansion_name': + return __('Enable navigation tree expansion'); + case 'NavigationTreeShowTables_name': + return __('Show tables in tree'); + case 'NavigationTreeShowTables_desc': + return __('Whether to show tables under database in the navigation tree'); + case 'NavigationTreeShowViews_name': + return __('Show views in tree'); + case 'NavigationTreeShowViews_desc': + return __('Whether to show views under database in the navigation tree'); + case 'NavigationTreeShowFunctions_name': + return __('Show functions in tree'); + case 'NavigationTreeShowFunctions_desc': + return __('Whether to show functions under database in the navigation tree'); + case 'NavigationTreeShowProcedures_name': + return __('Show procedures in tree'); + case 'NavigationTreeShowProcedures_desc': + return __('Whether to show procedures under database in the navigation tree'); + case 'NavigationTreeShowEvents_name': + return __('Show events in tree'); + case 'NavigationTreeShowEvents_desc': + return __('Whether to show events under database in the navigation tree'); + case 'NumRecentTables_desc': + return __('Maximum number of recently used tables; set 0 to disable.'); + case 'NumFavoriteTables_desc': + return __('Maximum number of favorite tables; set 0 to disable.'); + case 'NumRecentTables_name': + return __('Recently used tables'); + case 'NumFavoriteTables_name': + return __('Favorite tables'); + case 'RowActionLinks_desc': + return __('These are Edit, Copy and Delete links.'); + case 'RowActionLinks_name': + return __('Where to show the table row links'); + case 'RowActionLinksWithoutUnique_desc': + return __('Whether to show row links even in the absence of a unique key.'); + case 'RowActionLinksWithoutUnique_name': + return __('Show row links anyway'); + case 'DisableShortcutKeys_name': + return __('Disable shortcut keys'); + case 'DisableShortcutKeys_desc': + return __('Disable shortcut keys'); + case 'NaturalOrder_desc': + return __('Use natural order for sorting table and database names.'); + case 'NaturalOrder_name': + return __('Natural order'); + case 'TableNavigationLinksMode_desc': + return __('Use only icons, only text or both.'); + case 'TableNavigationLinksMode_name': + return __('Table navigation bar'); + case 'OBGzip_desc': + return __('Use GZip output buffering for increased speed in HTTP transfers.'); + case 'OBGzip_name': + return __('GZip output buffering'); + case 'Order_desc': + return __( + '[kbd]SMART[/kbd] - i.e. descending order for columns of type TIME, DATE, ' + . 'DATETIME and TIMESTAMP, ascending order otherwise.' + ); + case 'Order_name': + return __('Default sorting order'); + case 'PersistentConnections_desc': + return __('Use persistent connections to MySQL databases.'); + case 'PersistentConnections_name': + return __('Persistent connections'); + case 'PmaNoRelation_DisableWarning_desc': + return __( + 'Disable the default warning that is displayed on the database details ' + . 'Structure page if any of the required tables for the phpMyAdmin ' + . 'configuration storage could not be found.' + ); + case 'PmaNoRelation_DisableWarning_name': + return __('Missing phpMyAdmin configuration storage tables'); + case 'ReservedWordDisableWarning_desc': + return __( + 'Disable the default warning that is displayed on the Structure page if column ' + . 'names in a table are reserved MySQL words.' + ); + case 'ReservedWordDisableWarning_name': + return __('MySQL reserved word warning'); + case 'TabsMode_desc': + return __('Use only icons, only text or both.'); + case 'TabsMode_name': + return __('How to display the menu tabs'); + case 'ActionLinksMode_desc': + return __('Use only icons, only text or both.'); + case 'ActionLinksMode_name': + return __('How to display various action links'); + case 'ProtectBinary_desc': + return __('Disallow BLOB and BINARY columns from editing.'); + case 'ProtectBinary_name': + return __('Protect binary columns'); + case 'QueryHistoryDB_desc': + return __( + 'Enable if you want DB-based query history (requires phpMyAdmin configuration ' + . 'storage). If disabled, this utilizes JS-routines to display query history ' + . '(lost by window close).' + ); + case 'QueryHistoryDB_name': + return __('Permanent query history'); + case 'QueryHistoryMax_cmt': + return __('Users cannot set a higher value'); + case 'QueryHistoryMax_desc': + return __('How many queries are kept in history.'); + case 'QueryHistoryMax_name': + return __('Query history length'); + case 'RecodingEngine_desc': + return __('Select which functions will be used for character set conversion.'); + case 'RecodingEngine_name': + return __('Recoding engine'); + case 'RememberSorting_desc': + return __('When browsing tables, the sorting of each table is remembered.'); + case 'RememberSorting_name': + return __('Remember table\'s sorting'); + case 'TablePrimaryKeyOrder_desc': + return __('Default sort order for tables with a primary key.'); + case 'TablePrimaryKeyOrder_name': + return __('Primary key default sort order'); + case 'RepeatCells_desc': + return __('Repeat the headers every X cells, [kbd]0[/kbd] deactivates this feature.'); + case 'RepeatCells_name': + return __('Repeat headers'); + case 'GridEditing_name': + return __('Grid editing: trigger action'); + case 'RelationalDisplay_name': + return __('Relational display'); + case 'RelationalDisplay_desc': + return __('For display Options'); + case 'SaveCellsAtOnce_name': + return __('Grid editing: save all edited cells at once'); + case 'SaveDir_desc': + return __('Directory where exports can be saved on server.'); + case 'SaveDir_name': + return __('Save directory'); + case 'Servers_AllowDeny_order_desc': + return __('Leave blank if not used.'); + case 'Servers_AllowDeny_order_name': + return __('Host authorization order'); + case 'Servers_AllowDeny_rules_desc': + return __('Leave blank for defaults.'); + case 'Servers_AllowDeny_rules_name': + return __('Host authorization rules'); + case 'Servers_AllowNoPassword_name': + return __('Allow logins without a password'); + case 'Servers_AllowRoot_name': + return __('Allow root login'); + case 'Servers_SessionTimeZone_name': + return __('Session timezone'); + case 'Servers_SessionTimeZone_desc': + return __( + 'Sets the effective timezone; possibly different than the one from your ' + . 'database server' + ); + case 'Servers_auth_http_realm_desc': + return __('HTTP Basic Auth Realm name to display when doing HTTP Auth.'); + case 'Servers_auth_http_realm_name': + return __('HTTP Realm'); + case 'Servers_auth_type_desc': + return __('Authentication method to use.'); + case 'Servers_auth_type_name': + return __('Authentication type'); + case 'Servers_bookmarktable_desc': + return __( + 'Leave blank for no [doc@bookmarks@]bookmark[/doc] ' + . 'support, suggested: [kbd]pma__bookmark[/kbd]' + ); + case 'Servers_bookmarktable_name': + return __('Bookmark table'); + case 'Servers_column_info_desc': + return __( + 'Leave blank for no column comments/mime types, suggested: ' + . '[kbd]pma__column_info[/kbd].' + ); + case 'Servers_column_info_name': + return __('Column information table'); + case 'Servers_compress_desc': + return __('Compress connection to MySQL server.'); + case 'Servers_compress_name': + return __('Compress connection'); + case 'Servers_controlpass_name': + return __('Control user password'); + case 'Servers_controluser_desc': + return __( + 'A special MySQL user configured with limited permissions, more information ' + . 'available on [doc@linked-tables]documentation[/doc].' + ); + case 'Servers_controluser_name': + return __('Control user'); + case 'Servers_controlhost_desc': + return __( + 'An alternate host to hold the configuration storage; leave blank to use the ' + . 'already defined host.' + ); + case 'Servers_controlhost_name': + return __('Control host'); + case 'Servers_controlport_desc': + return __( + 'An alternate port to connect to the host that holds the configuration storage; ' + . 'leave blank to use the default port, or the already defined port, if the ' + . 'controlhost equals host.' + ); + case 'Servers_controlport_name': + return __('Control port'); + case 'Servers_hide_db_desc': + return __('Hide databases matching regular expression (PCRE).'); + case 'Servers_DisableIS_desc': + return __( + 'More information on [a@https://github.com/phpmyadmin/phpmyadmin/issues/8970]phpMyAdmin ' + . 'issue tracker[/a] and [a@https://bugs.mysql.com/19588]MySQL Bugs[/a]' + ); + case 'Servers_DisableIS_name': + return __('Disable use of INFORMATION_SCHEMA'); + case 'Servers_hide_db_name': + return __('Hide databases'); + case 'Servers_history_desc': + return __( + 'Leave blank for no SQL query history support, suggested: ' + . '[kbd]pma__history[/kbd].' + ); + case 'Servers_history_name': + return __('SQL query history table'); + case 'Servers_host_desc': + return __('Hostname where MySQL server is running.'); + case 'Servers_host_name': + return __('Server hostname'); + case 'Servers_LogoutURL_name': + return __('Logout URL'); + case 'Servers_MaxTableUiprefs_desc': + return __( + 'Limits number of table preferences which are stored in database, the oldest ' + . 'records are automatically removed.' + ); + case 'Servers_MaxTableUiprefs_name': + return __('Maximal number of table preferences to store'); + case 'Servers_savedsearches_name': + return __('QBE saved searches table'); + case 'Servers_savedsearches_desc': + return __( + 'Leave blank for no QBE saved searches support, suggested: ' + . '[kbd]pma__savedsearches[/kbd].' + ); + case 'Servers_export_templates_name': + return __('Export templates table'); + case 'Servers_export_templates_desc': + return __( + 'Leave blank for no export template support, suggested: ' + . '[kbd]pma__export_templates[/kbd].' + ); + case 'Servers_central_columns_name': + return __('Central columns table'); + case 'Servers_central_columns_desc': + return __( + 'Leave blank for no central columns support, suggested: ' + . '[kbd]pma__central_columns[/kbd].' + ); + case 'Servers_only_db_desc': + return __( + 'You can use MySQL wildcard characters (% and _), escape them if you want to ' + . 'use their literal instances, i.e. use [kbd]\'my\_db\'[/kbd] and not ' + . '[kbd]\'my_db\'[/kbd].' + ); + case 'Servers_only_db_name': + return __('Show only listed databases'); + case 'Servers_password_desc': + return __('Leave empty if not using config auth.'); + case 'Servers_password_name': + return __('Password for config auth'); + case 'Servers_pdf_pages_desc': + return __('Leave blank for no PDF schema support, suggested: [kbd]pma__pdf_pages[/kbd].'); + case 'Servers_pdf_pages_name': + return __('PDF schema: pages table'); + case 'Servers_pmadb_desc': + return __( + 'Database used for relations, bookmarks, and PDF features. See ' + . '[doc@linked-tables]pmadb[/doc] for complete information. ' + . 'Leave blank for no support. Suggested: [kbd]phpmyadmin[/kbd].' + ); + case 'Servers_pmadb_name': + return __('Database name'); + case 'Servers_port_desc': + return __('Port on which MySQL server is listening, leave empty for default.'); + case 'Servers_port_name': + return __('Server port'); + case 'Servers_recent_desc': + return __( + 'Leave blank for no "persistent" recently used tables across sessions, ' + . 'suggested: [kbd]pma__recent[/kbd].' + ); + case 'Servers_recent_name': + return __('Recently used table'); + case 'Servers_favorite_desc': + return __( + 'Leave blank for no "persistent" favorite tables across sessions, ' + . 'suggested: [kbd]pma__favorite[/kbd].' + ); + case 'Servers_favorite_name': + return __('Favorites table'); + case 'Servers_relation_desc': + return __( + 'Leave blank for no ' + . '[doc@relations@]relation-links[/doc] support, ' + . 'suggested: [kbd]pma__relation[/kbd].' + ); + case 'Servers_relation_name': + return __('Relation table'); + case 'Servers_SignonSession_desc': + return __( + 'See [doc@authentication-modes]authentication ' + . 'types[/doc] for an example.' + ); + case 'Servers_SignonSession_name': + return __('Signon session name'); + case 'Servers_SignonURL_name': + return __('Signon URL'); + case 'Servers_socket_desc': + return __('Socket on which MySQL server is listening, leave empty for default.'); + case 'Servers_socket_name': + return __('Server socket'); + case 'Servers_ssl_desc': + return __('Enable SSL for connection to MySQL server.'); + case 'Servers_ssl_name': + return __('Use SSL'); + case 'Servers_table_coords_desc': + return __('Leave blank for no PDF schema support, suggested: [kbd]pma__table_coords[/kbd].'); + case 'Servers_table_coords_name': + return __('Designer and PDF schema: table coordinates'); + case 'Servers_table_info_desc': + return __( + 'Table to describe the display columns, leave blank for no support; ' + . 'suggested: [kbd]pma__table_info[/kbd].' + ); + case 'Servers_table_info_name': + return __('Display columns table'); + case 'Servers_table_uiprefs_desc': + return __( + 'Leave blank for no "persistent" tables\' UI preferences across sessions, ' + . 'suggested: [kbd]pma__table_uiprefs[/kbd].' + ); + case 'Servers_table_uiprefs_name': + return __('UI preferences table'); + case 'Servers_tracking_add_drop_database_desc': + return __( + 'Whether a DROP DATABASE IF EXISTS statement will be added as first line to ' + . 'the log when creating a database.' + ); + case 'Servers_tracking_add_drop_database_name': + return __('Add DROP DATABASE'); + case 'Servers_tracking_add_drop_table_desc': + return __( + 'Whether a DROP TABLE IF EXISTS statement will be added as first line to the ' + . 'log when creating a table.' + ); + case 'Servers_tracking_add_drop_table_name': + return __('Add DROP TABLE'); + case 'Servers_tracking_add_drop_view_desc': + return __( + 'Whether a DROP VIEW IF EXISTS statement will be added as first line to the ' + . 'log when creating a view.' + ); + case 'Servers_tracking_add_drop_view_name': + return __('Add DROP VIEW'); + case 'Servers_tracking_default_statements_desc': + return __('Defines the list of statements the auto-creation uses for new versions.'); + case 'Servers_tracking_default_statements_name': + return __('Statements to track'); + case 'Servers_tracking_desc': + return __( + 'Leave blank for no SQL query tracking support, suggested: ' + . '[kbd]pma__tracking[/kbd].' + ); + case 'Servers_tracking_name': + return __('SQL query tracking table'); + case 'Servers_tracking_version_auto_create_desc': + return __( + 'Whether the tracking mechanism creates versions for tables and views ' + . 'automatically.' + ); + case 'Servers_tracking_version_auto_create_name': + return __('Automatically create versions'); + case 'Servers_userconfig_desc': + return __( + 'Leave blank for no user preferences storage in database, suggested: ' + . '[kbd]pma__userconfig[/kbd].' + ); + case 'Servers_userconfig_name': + return __('User preferences storage table'); + case 'Servers_users_desc': + return __( + 'Both this table and the user groups table are required to enable the ' . + 'configurable menus feature; leaving either one of them blank will disable ' . + 'this feature, suggested: [kbd]pma__users[/kbd].' + ); + case 'Servers_users_name': + return __('Users table'); + case 'Servers_usergroups_desc': + return __( + 'Both this table and the users table are required to enable the configurable ' . + 'menus feature; leaving either one of them blank will disable this feature, ' . + 'suggested: [kbd]pma__usergroups[/kbd].' + ); + case 'Servers_usergroups_name': + return __('User groups table'); + case 'Servers_navigationhiding_desc': + return __( + 'Leave blank to disable the feature to hide and show navigation items, ' . + 'suggested: [kbd]pma__navigationhiding[/kbd].' + ); + case 'Servers_navigationhiding_name': + return __('Hidden navigation items table'); + case 'Servers_user_desc': + return __('Leave empty if not using config auth.'); + case 'Servers_user_name': + return __('User for config auth'); + case 'Servers_verbose_desc': + return __( + 'A user-friendly description of this server. Leave blank to display the ' . + 'hostname instead.' + ); + case 'Servers_verbose_name': + return __('Verbose name of this server'); + case 'ShowAll_desc': + return __('Whether a user should be displayed a "show all (rows)" button.'); + case 'ShowAll_name': + return __('Allow to display all the rows'); + case 'ShowChgPassword_desc': + return __( + 'Please note that enabling this has no effect with [kbd]config[/kbd] ' . + 'authentication mode because the password is hard coded in the configuration ' . + 'file; this does not limit the ability to execute the same command directly.' + ); + case 'ShowChgPassword_name': + return __('Show password change form'); + case 'ShowCreateDb_name': + return __('Show create database form'); + case 'ShowDbStructureComment_desc': + return __('Show or hide a column displaying the comments for all tables.'); + case 'ShowDbStructureComment_name': + return __('Show table comments'); + case 'ShowDbStructureCreation_desc': + return __('Show or hide a column displaying the Creation timestamp for all tables.'); + case 'ShowDbStructureCreation_name': + return __('Show creation timestamp'); + case 'ShowDbStructureLastUpdate_desc': + return __('Show or hide a column displaying the Last update timestamp for all tables.'); + case 'ShowDbStructureLastUpdate_name': + return __('Show last update timestamp'); + case 'ShowDbStructureLastCheck_desc': + return __('Show or hide a column displaying the Last check timestamp for all tables.'); + case 'ShowDbStructureLastCheck_name': + return __('Show last check timestamp'); + case 'ShowDbStructureCharset_desc': + return __('Show or hide a column displaying the charset for all tables.'); + case 'ShowDbStructureCharset_name': + return __('Show table charset'); + case 'ShowFieldTypesInDataEditView_desc': + return __( + 'Defines whether or not type fields should be initially displayed in ' . + 'edit/insert mode.' + ); + case 'ShowFieldTypesInDataEditView_name': + return __('Show field types'); + case 'ShowFunctionFields_desc': + return __('Display the function fields in edit/insert mode.'); + case 'ShowFunctionFields_name': + return __('Show function fields'); + case 'ShowHint_desc': + return __('Whether to show hint or not.'); + case 'ShowHint_name': + return __('Show hint'); + case 'ShowPhpInfo_desc': + return __( + 'Shows link to [a@https://php.net/manual/function.phpinfo.php]phpinfo()[/a] ' . + 'output.' + ); + case 'ShowPhpInfo_name': + return __('Show phpinfo() link'); + case 'ShowServerInfo_name': + return __('Show detailed MySQL server information'); + case 'ShowSQL_desc': + return __('Defines whether SQL queries generated by phpMyAdmin should be displayed.'); + case 'ShowSQL_name': + return __('Show SQL queries'); + case 'RetainQueryBox_desc': + return __('Defines whether the query box should stay on-screen after its submission.'); + case 'RetainQueryBox_name': + return __('Retain query box'); + case 'ShowStats_desc': + return __('Allow to display database and table statistics (eg. space usage).'); + case 'ShowStats_name': + return __('Show statistics'); + case 'SkipLockedTables_desc': + return __('Mark used tables and make it possible to show databases with locked tables.'); + case 'SkipLockedTables_name': + return __('Skip locked tables'); + case 'SQLQuery_Edit_name': + return __('Edit'); + case 'SQLQuery_Explain_name': + return __('Explain SQL'); + case 'SQLQuery_Refresh_name': + return __('Refresh'); + case 'SQLQuery_ShowAsPHP_name': + return __('Create PHP code'); + case 'SuhosinDisableWarning_desc': + return __( + 'Disable the default warning that is displayed on the main page if Suhosin is ' . + 'detected.' + ); + case 'SuhosinDisableWarning_name': + return __('Suhosin warning'); + case 'LoginCookieValidityDisableWarning_desc': + return __( + 'Disable the default warning that is displayed on the main page if the value ' . + 'of the PHP setting session.gc_maxlifetime is less than the value of ' . + '`LoginCookieValidity`.' + ); + case 'LoginCookieValidityDisableWarning_name': + return __('Login cookie validity warning'); + case 'TextareaCols_desc': + return __( + 'Textarea size (columns) in edit mode, this value will be emphasized for SQL ' . + 'query textareas (*2).' + ); + case 'TextareaCols_name': + return __('Textarea columns'); + case 'TextareaRows_desc': + return __( + 'Textarea size (rows) in edit mode, this value will be emphasized for SQL ' . + 'query textareas (*2).' + ); + case 'TextareaRows_name': + return __('Textarea rows'); + case 'TitleDatabase_desc': + return __('Title of browser window when a database is selected.'); + case 'TitleDatabase_name': + return __('Database'); + case 'TitleDefault_desc': + return __('Title of browser window when nothing is selected.'); + case 'TitleDefault_name': + return __('Default title'); + case 'TitleServer_desc': + return __('Title of browser window when a server is selected.'); + case 'TitleServer_name': + return __('Server'); + case 'TitleTable_desc': + return __('Title of browser window when a table is selected.'); + case 'TitleTable_name': + return __('Table'); + case 'TrustedProxies_desc': + return __( + 'Input proxies as [kbd]IP: trusted HTTP header[/kbd]. The following example ' . + 'specifies that phpMyAdmin should trust a HTTP_X_FORWARDED_FOR ' . + '(X-Forwarded-For) header coming from the proxy 1.2.3.4:[br][kbd]1.2.3.4: ' . + 'HTTP_X_FORWARDED_FOR[/kbd].' + ); + case 'TrustedProxies_name': + return __('List of trusted proxies for IP allow/deny'); + case 'UploadDir_desc': + return __('Directory on server where you can upload files for import.'); + case 'UploadDir_name': + return __('Upload directory'); + case 'UseDbSearch_desc': + return __('Allow for searching inside the entire database.'); + case 'UseDbSearch_name': + return __('Use database search'); + case 'UserprefsDeveloperTab_desc': + return __( + 'When disabled, users cannot set any of the options below, regardless of the ' . + 'checkbox on the right.' + ); + case 'UserprefsDeveloperTab_name': + return __('Enable the Developer tab in settings'); + case 'VersionCheck_desc': + return __('Enables check for latest version on main phpMyAdmin page.'); + case 'VersionCheck_name': + return __('Version check'); + case 'ProxyUrl_desc': + return __( + 'The url of the proxy to be used when retrieving the information about the ' . + 'latest version of phpMyAdmin or when submitting error reports. You need this ' . + 'if the server where phpMyAdmin is installed does not have direct access to ' . + 'the internet. The format is: "hostname:portnumber".' + ); + case 'ProxyUrl_name': + return __('Proxy url'); + case 'ProxyUser_desc': + return __( + 'The username for authenticating with the proxy. By default, no ' . + 'authentication is performed. If a username is supplied, Basic ' . + 'Authentication will be performed. No other types of authentication are ' . + 'currently supported.' + ); + case 'ProxyUser_name': + return __('Proxy username'); + case 'ProxyPass_desc': + return __('The password for authenticating with the proxy.'); + case 'ProxyPass_name': + return __('Proxy password'); + + case 'ZipDump_desc': + return __('Enable ZIP compression for import and export operations.'); + case 'ZipDump_name': + return __('ZIP'); + case 'CaptchaLoginPublicKey_desc': + return __('Enter your public key for your domain reCaptcha service.'); + case 'CaptchaLoginPublicKey_name': + return __('Public key for reCaptcha'); + case 'CaptchaLoginPrivateKey_desc': + return __('Enter your private key for your domain reCaptcha service.'); + case 'CaptchaLoginPrivateKey_name': + return __('Private key for reCaptcha'); + + case 'SendErrorReports_desc': + return __('Choose the default action when sending error reports.'); + case 'SendErrorReports_name': + return __('Send error reports'); + + case 'ConsoleEnterExecutes_desc': + return __( + 'Queries are executed by pressing Enter (instead of Ctrl+Enter). New lines ' . + 'will be inserted with Shift+Enter.' + ); + case 'ConsoleEnterExecutes_name': + return __('Enter executes queries in console'); + + case 'ZeroConf_desc': + return __( + 'Enable Zero Configuration mode which lets you setup phpMyAdmin ' + . 'configuration storage tables automatically.' + ); + case 'ZeroConf_name': + return __('Enable Zero Configuration mode'); + case 'Console_StartHistory_name': + return __('Show query history at start'); + case 'Console_AlwaysExpand_name': + return __('Always expand query messages'); + case 'Console_CurrentQuery_name': + return __('Show current browsing query'); + case 'Console_EnterExecutes_name': + return __('Execute queries on Enter and insert new line with Shift + Enter'); + case 'Console_DarkTheme_name': + return __('Switch to dark theme'); + case 'Console_Height_name': + return __('Console height'); + case 'Console_Mode_name': + return __('Console mode'); + case 'Console_GroupQueries_name': + return __('Group queries'); + case 'Console_Order_name': + return __('Order'); + case 'Console_OrderBy_name': + return __('Order by'); + case 'FontSize_name': + return __('Font size'); + case 'DefaultConnectionCollation_name': + return __('Server connection collation'); + } + return null; + } +} + diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Config/Form.php b/php/apps/phpmyadmin49/html/libraries/classes/Config/Form.php new file mode 100644 index 00000000..f7c670fa --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Config/Form.php @@ -0,0 +1,233 @@ +index = $index; + $this->_configFile = $cf; + $this->loadForm($form_name, $form); + } + + /** + * Returns type of given option + * + * @param string $option_name path or field name + * + * @return string|null one of: boolean, integer, double, string, select, array + */ + public function getOptionType($option_name) + { + $key = ltrim( + mb_substr( + $option_name, + mb_strrpos($option_name, '/') + ), + '/' + ); + return isset($this->_fieldsTypes[$key]) + ? $this->_fieldsTypes[$key] + : null; + } + + /** + * Returns allowed values for select fields + * + * @param string $option_path Option path + * + * @return array + */ + public function getOptionValueList($option_path) + { + $value = $this->_configFile->getDbEntry($option_path); + if ($value === null) { + trigger_error("$option_path - select options not defined", E_USER_ERROR); + return array(); + } + if (!is_array($value)) { + trigger_error("$option_path - not a static value list", E_USER_ERROR); + return array(); + } + // convert array('#', 'a', 'b') to array('a', 'b') + if (isset($value[0]) && $value[0] === '#') { + // remove first element ('#') + array_shift($value); + // $value has keys and value names, return it + return $value; + } + + // convert value list array('a', 'b') to array('a' => 'a', 'b' => 'b') + $has_string_keys = false; + $keys = array(); + for ($i = 0, $nb = count($value); $i < $nb; $i++) { + if (!isset($value[$i])) { + $has_string_keys = true; + break; + } + $keys[] = is_bool($value[$i]) ? (int)$value[$i] : $value[$i]; + } + if (! $has_string_keys) { + $value = array_combine($keys, $value); + } + + // $value has keys and value names, return it + return $value; + } + + /** + * array_walk callback function, reads path of form fields from + * array (see docs for \PhpMyAdmin\Config\Forms\BaseForm::getForms) + * + * @param mixed $value Value + * @param mixed $key Key + * @param mixed $prefix Prefix + * + * @return void + */ + private function _readFormPathsCallback($value, $key, $prefix) + { + static $group_counter = 0; + + if (is_array($value)) { + $prefix .= $key . '/'; + array_walk($value, array($this, '_readFormPathsCallback'), $prefix); + return; + } + + if (!is_int($key)) { + $this->default[$prefix . $key] = $value; + $value = $key; + } + // add unique id to group ends + if ($value == ':group:end') { + $value .= ':' . $group_counter++; + } + $this->fields[] = $prefix . $value; + } + + /** + * Reads form paths to {@link $fields} + * + * @param array $form Form + * + * @return void + */ + protected function readFormPaths(array $form) + { + // flatten form fields' paths and save them to $fields + $this->fields = array(); + array_walk($form, array($this, '_readFormPathsCallback'), ''); + + // $this->fields is an array of the form: [0..n] => 'field path' + // change numeric indexes to contain field names (last part of the path) + $paths = $this->fields; + $this->fields = array(); + foreach ($paths as $path) { + $key = ltrim( + mb_substr($path, mb_strrpos($path, '/')), + '/' + ); + $this->fields[$key] = $path; + } + // now $this->fields is an array of the form: 'field name' => 'field path' + } + + /** + * Reads fields' types to $this->_fieldsTypes + * + * @return void + */ + protected function readTypes() + { + $cf = $this->_configFile; + foreach ($this->fields as $name => $path) { + if (mb_strpos($name, ':group:') === 0) { + $this->_fieldsTypes[$name] = 'group'; + continue; + } + $v = $cf->getDbEntry($path); + if ($v !== null) { + $type = is_array($v) ? 'select' : $v; + } else { + $type = gettype($cf->getDefault($path)); + } + $this->_fieldsTypes[$name] = $type; + } + } + + /** + * Reads form settings and prepares class to work with given subset of + * config file + * + * @param string $form_name Form name + * @param array $form Form + * + * @return void + */ + public function loadForm($form_name, array $form) + { + $this->name = $form_name; + $this->readFormPaths($form); + $this->readTypes(); + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Config/FormDisplay.php b/php/apps/phpmyadmin49/html/libraries/classes/Config/FormDisplay.php new file mode 100644 index 00000000..5d9eedb4 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Config/FormDisplay.php @@ -0,0 +1,880 @@ +_jsLangStrings = array( + 'error_nan_p' => __('Not a positive number!'), + 'error_nan_nneg' => __('Not a non-negative number!'), + 'error_incorrect_port' => __('Not a valid port number!'), + 'error_invalid_value' => __('Incorrect value!'), + 'error_value_lte' => __('Value must be less than or equal to %s!')); + $this->_configFile = $cf; + // initialize validators + Validator::getValidators($this->_configFile); + } + + /** + * Returns {@link ConfigFile} associated with this instance + * + * @return ConfigFile + */ + public function getConfigFile() + { + return $this->_configFile; + } + + /** + * Registers form in form manager + * + * @param string $form_name Form name + * @param array $form Form data + * @param int $server_id 0 if new server, validation; >= 1 if editing a server + * + * @return void + */ + public function registerForm($form_name, array $form, $server_id = null) + { + $this->_forms[$form_name] = new Form( + $form_name, $form, $this->_configFile, $server_id + ); + $this->_isValidated = false; + foreach ($this->_forms[$form_name]->fields as $path) { + $work_path = $server_id === null + ? $path + : str_replace('Servers/1/', "Servers/$server_id/", $path); + $this->_systemPaths[$work_path] = $path; + $this->_translatedPaths[$work_path] = str_replace('/', '-', $work_path); + } + } + + /** + * Processes forms, returns true on successful save + * + * @param bool $allow_partial_save allows for partial form saving + * on failed validation + * @param bool $check_form_submit whether check for $_POST['submit_save'] + * + * @return boolean whether processing was successful + */ + public function process($allow_partial_save = true, $check_form_submit = true) + { + if ($check_form_submit && !isset($_POST['submit_save'])) { + return false; + } + + // save forms + if (count($this->_forms) > 0) { + return $this->save(array_keys($this->_forms), $allow_partial_save); + } + return false; + } + + /** + * Runs validation for all registered forms + * + * @return void + */ + private function _validate() + { + if ($this->_isValidated) { + return; + } + + $paths = array(); + $values = array(); + foreach ($this->_forms as $form) { + /* @var $form Form */ + $paths[] = $form->name; + // collect values and paths + foreach ($form->fields as $path) { + $work_path = array_search($path, $this->_systemPaths); + $values[$path] = $this->_configFile->getValue($work_path); + $paths[] = $path; + } + } + + // run validation + $errors = Validator::validate( + $this->_configFile, $paths, $values, false + ); + + // change error keys from canonical paths to work paths + if (is_array($errors) && count($errors) > 0) { + $this->_errors = array(); + foreach ($errors as $path => $error_list) { + $work_path = array_search($path, $this->_systemPaths); + // field error + if (! $work_path) { + // form error, fix path + $work_path = $path; + } + $this->_errors[$work_path] = $error_list; + } + } + $this->_isValidated = true; + } + + /** + * Outputs HTML for the forms under the menu tab + * + * @param bool $show_restore_default whether to show "restore default" + * button besides the input field + * @param array &$js_default stores JavaScript code + * to be displayed + * @param array &$js will be updated with javascript code + * @param bool $show_buttons whether show submit and reset button + * + * @return string $htmlOutput + */ + private function _displayForms( + $show_restore_default, array &$js_default, array &$js, $show_buttons + ) { + $htmlOutput = ''; + $validators = Validator::getValidators($this->_configFile); + + foreach ($this->_forms as $form) { + /* @var $form Form */ + $form_errors = isset($this->_errors[$form->name]) + ? $this->_errors[$form->name] : null; + $htmlOutput .= FormDisplayTemplate::displayFieldsetTop( + Descriptions::get("Form_{$form->name}"), + Descriptions::get("Form_{$form->name}", 'desc'), + $form_errors, + array('id' => $form->name) + ); + + foreach ($form->fields as $field => $path) { + $work_path = array_search($path, $this->_systemPaths); + $translated_path = $this->_translatedPaths[$work_path]; + // always true/false for user preferences display + // otherwise null + $userprefs_allow = isset($this->_userprefsKeys[$path]) + ? !isset($this->_userprefsDisallow[$path]) + : null; + // display input + $htmlOutput .= $this->_displayFieldInput( + $form, + $field, + $path, + $work_path, + $translated_path, + $show_restore_default, + $userprefs_allow, + $js_default + ); + // register JS validators for this field + if (isset($validators[$path])) { + FormDisplayTemplate::addJsValidate($translated_path, $validators[$path], $js); + } + } + $htmlOutput .= FormDisplayTemplate::displayFieldsetBottom($show_buttons); + } + return $htmlOutput; + } + + /** + * Outputs HTML for forms + * + * @param bool $tabbed_form if true, use a form with tabs + * @param bool $show_restore_default whether show "restore default" button + * besides the input field + * @param bool $show_buttons whether show submit and reset button + * @param string $form_action action attribute for the form + * @param array|null $hidden_fields array of form hidden fields (key: field + * name) + * + * @return string HTML for forms + */ + public function getDisplay( + $tabbed_form = false, + $show_restore_default = false, + $show_buttons = true, + $form_action = null, + $hidden_fields = null + ) { + static $js_lang_sent = false; + + $htmlOutput = ''; + + $js = array(); + $js_default = array(); + + $htmlOutput .= FormDisplayTemplate::displayFormTop($form_action, 'post', $hidden_fields); + + if ($tabbed_form) { + $tabs = array(); + foreach ($this->_forms as $form) { + $tabs[$form->name] = Descriptions::get("Form_$form->name"); + } + $htmlOutput .= FormDisplayTemplate::displayTabsTop($tabs); + } + + // validate only when we aren't displaying a "new server" form + $is_new_server = false; + foreach ($this->_forms as $form) { + /* @var $form Form */ + if ($form->index === 0) { + $is_new_server = true; + break; + } + } + if (! $is_new_server) { + $this->_validate(); + } + + // user preferences + $this->_loadUserprefsInfo(); + + // display forms + $htmlOutput .= $this->_displayForms( + $show_restore_default, $js_default, $js, $show_buttons + ); + + if ($tabbed_form) { + $htmlOutput .= FormDisplayTemplate::displayTabsBottom(); + } + $htmlOutput .= FormDisplayTemplate::displayFormBottom(); + + // if not already done, send strings used for validation to JavaScript + if (! $js_lang_sent) { + $js_lang_sent = true; + $js_lang = array(); + foreach ($this->_jsLangStrings as $strName => $strValue) { + $js_lang[] = "'$strName': '" . Sanitize::jsFormat($strValue, false) . '\''; + } + $js[] = "$.extend(PMA_messages, {\n\t" + . implode(",\n\t", $js_lang) . '})'; + } + + $js[] = "$.extend(defaultValues, {\n\t" + . implode(",\n\t", $js_default) . '})'; + $htmlOutput .= FormDisplayTemplate::displayJavascript($js); + + return $htmlOutput; + } + + /** + * Prepares data for input field display and outputs HTML code + * + * @param Form $form Form object + * @param string $field field name as it appears in $form + * @param string $system_path field path, eg. Servers/1/verbose + * @param string $work_path work path, eg. Servers/4/verbose + * @param string $translated_path work path changed so that it can be + * used as XHTML id + * @param bool $show_restore_default whether show "restore default" button + * besides the input field + * @param bool|null $userprefs_allow whether user preferences are enabled + * for this field (null - no support, + * true/false - enabled/disabled) + * @param array &$js_default array which stores JavaScript code + * to be displayed + * + * @return string HTML for input field + */ + private function _displayFieldInput( + Form $form, $field, $system_path, $work_path, + $translated_path, $show_restore_default, $userprefs_allow, array &$js_default + ) { + $name = Descriptions::get($system_path); + $description = Descriptions::get($system_path, 'desc'); + + $value = $this->_configFile->get($work_path); + $value_default = $this->_configFile->getDefault($system_path); + $value_is_default = false; + if ($value === null || $value === $value_default) { + $value = $value_default; + $value_is_default = true; + } + + $opts = array( + 'doc' => $this->getDocLink($system_path), + 'show_restore_default' => $show_restore_default, + 'userprefs_allow' => $userprefs_allow, + 'userprefs_comment' => Descriptions::get($system_path, 'cmt') + ); + if (isset($form->default[$system_path])) { + $opts['setvalue'] = $form->default[$system_path]; + } + + if (isset($this->_errors[$work_path])) { + $opts['errors'] = $this->_errors[$work_path]; + } + + $type = ''; + switch ($form->getOptionType($field)) { + case 'string': + $type = 'text'; + break; + case 'short_string': + $type = 'short_text'; + break; + case 'double': + case 'integer': + $type = 'number_text'; + break; + case 'boolean': + $type = 'checkbox'; + break; + case 'select': + $type = 'select'; + $opts['values'] = $form->getOptionValueList($form->fields[$field]); + break; + case 'array': + $type = 'list'; + $value = (array) $value; + $value_default = (array) $value_default; + break; + case 'group': + // :group:end is changed to :group:end:{unique id} in Form class + $htmlOutput = ''; + if (mb_substr($field, 7, 4) != 'end:') { + $htmlOutput .= FormDisplayTemplate::displayGroupHeader( + mb_substr($field, 7) + ); + } else { + FormDisplayTemplate::displayGroupFooter(); + } + return $htmlOutput; + case 'NULL': + trigger_error("Field $system_path has no type", E_USER_WARNING); + return null; + } + + // detect password fields + if ($type === 'text' + && (mb_substr($translated_path, -9) === '-password' + || mb_substr($translated_path, -4) === 'pass' + || mb_substr($translated_path, -4) === 'Pass') + ) { + $type = 'password'; + } + + // TrustedProxies requires changes before displaying + if ($system_path == 'TrustedProxies') { + foreach ($value as $ip => &$v) { + if (!preg_match('/^-\d+$/', $ip)) { + $v = $ip . ': ' . $v; + } + } + } + $this->_setComments($system_path, $opts); + + // send default value to form's JS + $js_line = '\'' . $translated_path . '\': '; + switch ($type) { + case 'text': + case 'short_text': + case 'number_text': + case 'password': + $js_line .= '\'' . Sanitize::escapeJsString($value_default) . '\''; + break; + case 'checkbox': + $js_line .= $value_default ? 'true' : 'false'; + break; + case 'select': + $value_default_js = is_bool($value_default) + ? (int) $value_default + : $value_default; + $js_line .= '[\'' . Sanitize::escapeJsString($value_default_js) . '\']'; + break; + case 'list': + $js_line .= '\'' . Sanitize::escapeJsString(implode("\n", $value_default)) + . '\''; + break; + } + $js_default[] = $js_line; + + return FormDisplayTemplate::displayInput( + $translated_path, $name, $type, $value, + $description, $value_is_default, $opts + ); + } + + /** + * Displays errors + * + * @return string HTML for errors + */ + public function displayErrors() + { + $this->_validate(); + if (count($this->_errors) == 0) { + return null; + } + + $htmlOutput = ''; + + foreach ($this->_errors as $system_path => $error_list) { + if (isset($this->_systemPaths[$system_path])) { + $name = Descriptions::get($this->_systemPaths[$system_path]); + } else { + $name = Descriptions::get('Form_' . $system_path); + } + $htmlOutput .= FormDisplayTemplate::displayErrors($name, $error_list); + } + + return $htmlOutput; + } + + /** + * Reverts erroneous fields to their default values + * + * @return void + */ + public function fixErrors() + { + $this->_validate(); + if (count($this->_errors) == 0) { + return; + } + + $cf = $this->_configFile; + foreach (array_keys($this->_errors) as $work_path) { + if (!isset($this->_systemPaths[$work_path])) { + continue; + } + $canonical_path = $this->_systemPaths[$work_path]; + $cf->set($work_path, $cf->getDefault($canonical_path)); + } + } + + /** + * Validates select field and casts $value to correct type + * + * @param string &$value Current value + * @param array $allowed List of allowed values + * + * @return bool + */ + private function _validateSelect(&$value, array $allowed) + { + $value_cmp = is_bool($value) + ? (int) $value + : $value; + foreach ($allowed as $vk => $v) { + // equality comparison only if both values are numeric or not numeric + // (allows to skip 0 == 'string' equalling to true) + // or identity (for string-string) + if (($vk == $value && !(is_numeric($value_cmp) xor is_numeric($vk))) + || $vk === $value + ) { + // keep boolean value as boolean + if (!is_bool($value)) { + settype($value, gettype($vk)); + } + return true; + } + } + return false; + } + + /** + * Validates and saves form data to session + * + * @param array|string $forms array of form names + * @param bool $allow_partial_save allows for partial form saving on + * failed validation + * + * @return boolean true on success (no errors and all saved) + */ + public function save($forms, $allow_partial_save = true) + { + $result = true; + $forms = (array) $forms; + + $values = array(); + $to_save = array(); + $is_setup_script = $GLOBALS['PMA_Config']->get('is_setup'); + if ($is_setup_script) { + $this->_loadUserprefsInfo(); + } + + $this->_errors = array(); + foreach ($forms as $form_name) { + /* @var $form Form */ + if (isset($this->_forms[$form_name])) { + $form = $this->_forms[$form_name]; + } else { + continue; + } + // get current server id + $change_index = $form->index === 0 + ? $this->_configFile->getServerCount() + 1 + : false; + // grab POST values + foreach ($form->fields as $field => $system_path) { + $work_path = array_search($system_path, $this->_systemPaths); + $key = $this->_translatedPaths[$work_path]; + $type = $form->getOptionType($field); + + // skip groups + if ($type == 'group') { + continue; + } + + // ensure the value is set + if (!isset($_POST[$key])) { + // checkboxes aren't set by browsers if they're off + if ($type == 'boolean') { + $_POST[$key] = false; + } else { + $this->_errors[$form->name][] = sprintf( + __('Missing data for %s'), + '' . Descriptions::get($system_path) . '' + ); + $result = false; + continue; + } + } + + // user preferences allow/disallow + if ($is_setup_script + && isset($this->_userprefsKeys[$system_path]) + ) { + if (isset($this->_userprefsDisallow[$system_path]) + && isset($_POST[$key . '-userprefs-allow']) + ) { + unset($this->_userprefsDisallow[$system_path]); + } elseif (!isset($_POST[$key . '-userprefs-allow'])) { + $this->_userprefsDisallow[$system_path] = true; + } + } + + // cast variables to correct type + switch ($type) { + case 'double': + $_POST[$key] = Util::requestString($_POST[$key]); + settype($_POST[$key], 'float'); + break; + case 'boolean': + case 'integer': + if ($_POST[$key] !== '') { + $_POST[$key] = Util::requestString($_POST[$key]); + settype($_POST[$key], $type); + } + break; + case 'select': + $successfully_validated = $this->_validateSelect( + $_POST[$key], + $form->getOptionValueList($system_path) + ); + if (! $successfully_validated) { + $this->_errors[$work_path][] = __('Incorrect value!'); + $result = false; + // "continue" for the $form->fields foreach-loop + continue 2; + } + break; + case 'string': + case 'short_string': + $_POST[$key] = Util::requestString($_POST[$key]); + break; + case 'array': + // eliminate empty values and ensure we have an array + $post_values = is_array($_POST[$key]) + ? $_POST[$key] + : explode("\n", $_POST[$key]); + $_POST[$key] = array(); + $this->_fillPostArrayParameters($post_values, $key); + break; + } + + // now we have value with proper type + $values[$system_path] = $_POST[$key]; + if ($change_index !== false) { + $work_path = str_replace( + "Servers/$form->index/", + "Servers/$change_index/", $work_path + ); + } + $to_save[$work_path] = $system_path; + } + } + + // save forms + if (!$allow_partial_save && !empty($this->_errors)) { + // don't look for non-critical errors + $this->_validate(); + return $result; + } + + foreach ($to_save as $work_path => $path) { + // TrustedProxies requires changes before saving + if ($path == 'TrustedProxies') { + $proxies = array(); + $i = 0; + foreach ($values[$path] as $value) { + $matches = array(); + $match = preg_match( + "/^(.+):(?:[ ]?)(\\w+)$/", $value, $matches + ); + if ($match) { + // correct 'IP: HTTP header' pair + $ip = trim($matches[1]); + $proxies[$ip] = trim($matches[2]); + } else { + // save also incorrect values + $proxies["-$i"] = $value; + $i++; + } + } + $values[$path] = $proxies; + } + $this->_configFile->set($work_path, $values[$path], $path); + } + if ($is_setup_script) { + $this->_configFile->set( + 'UserprefsDisallow', + array_keys($this->_userprefsDisallow) + ); + } + + // don't look for non-critical errors + $this->_validate(); + + return $result; + } + + /** + * Tells whether form validation failed + * + * @return boolean + */ + public function hasErrors() + { + return count($this->_errors) > 0; + } + + + /** + * Returns link to documentation + * + * @param string $path Path to documentation + * + * @return string + */ + public function getDocLink($path) + { + $test = mb_substr($path, 0, 6); + if ($test == 'Import' || $test == 'Export') { + return ''; + } + return Util::getDocuLink( + 'config', + 'cfg_' . $this->_getOptName($path) + ); + } + + /** + * Changes path so it can be used in URLs + * + * @param string $path Path + * + * @return string + */ + private function _getOptName($path) + { + return str_replace(array('Servers/1/', '/'), array('Servers/', '_'), $path); + } + + /** + * Fills out {@link userprefs_keys} and {@link userprefs_disallow} + * + * @return void + */ + private function _loadUserprefsInfo() + { + if ($this->_userprefsKeys !== null) { + return; + } + + $this->_userprefsKeys = array_flip(UserFormList::getFields()); + // read real config for user preferences display + $userprefs_disallow = $GLOBALS['PMA_Config']->get('is_setup') + ? $this->_configFile->get('UserprefsDisallow', array()) + : $GLOBALS['cfg']['UserprefsDisallow']; + $this->_userprefsDisallow = array_flip($userprefs_disallow); + } + + /** + * Sets field comments and warnings based on current environment + * + * @param string $system_path Path to settings + * @param array &$opts Chosen options + * + * @return void + */ + private function _setComments($system_path, array &$opts) + { + // RecodingEngine - mark unavailable types + if ($system_path == 'RecodingEngine') { + $comment = ''; + if (!function_exists('iconv')) { + $opts['values']['iconv'] .= ' (' . __('unavailable') . ')'; + $comment = sprintf( + __('"%s" requires %s extension'), 'iconv', 'iconv' + ); + } + if (!function_exists('recode_string')) { + $opts['values']['recode'] .= ' (' . __('unavailable') . ')'; + $comment .= ($comment ? ", " : '') . sprintf( + __('"%s" requires %s extension'), + 'recode', 'recode' + ); + } + /* mbstring is always there thanks to polyfill */ + $opts['comment'] = $comment; + $opts['comment_warning'] = true; + } + // ZipDump, GZipDump, BZipDump - check function availability + if ($system_path == 'ZipDump' + || $system_path == 'GZipDump' + || $system_path == 'BZipDump' + ) { + $comment = ''; + $funcs = array( + 'ZipDump' => array('zip_open', 'gzcompress'), + 'GZipDump' => array('gzopen', 'gzencode'), + 'BZipDump' => array('bzopen', 'bzcompress')); + if (!function_exists($funcs[$system_path][0])) { + $comment = sprintf( + __( + 'Compressed import will not work due to missing function %s.' + ), + $funcs[$system_path][0] + ); + } + if (!function_exists($funcs[$system_path][1])) { + $comment .= ($comment ? '; ' : '') . sprintf( + __( + 'Compressed export will not work due to missing function %s.' + ), + $funcs[$system_path][1] + ); + } + $opts['comment'] = $comment; + $opts['comment_warning'] = true; + } + if (! $GLOBALS['PMA_Config']->get('is_setup')) { + if (($system_path == 'MaxDbList' || $system_path == 'MaxTableList' + || $system_path == 'QueryHistoryMax') + ) { + $opts['comment'] = sprintf( + __('maximum %s'), $GLOBALS['cfg'][$system_path] + ); + } + } + } + + /** + * Copy items of an array to $_POST variable + * + * @param array $post_values List of parameters + * @param string $key Array key + * + * @return void + */ + private function _fillPostArrayParameters(array $post_values, $key) + { + foreach ($post_values as $v) { + $v = Util::requestString($v); + if ($v !== '') { + $_POST[$key][] = $v; + } + } + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Config/FormDisplayTemplate.php b/php/apps/phpmyadmin49/html/libraries/classes/Config/FormDisplayTemplate.php new file mode 100644 index 00000000..88dee0c7 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Config/FormDisplayTemplate.php @@ -0,0 +1,493 @@ +'; + $htmlOutput .= ''; + // we do validation on page refresh when browser remembers field values, + // add a field with known value which will be used for checks + if (! $has_check_page_refresh) { + $has_check_page_refresh = true; + $htmlOutput .= '' . "\n"; + } + $htmlOutput .= Url::getHiddenInputs('', '', 0, 'server') . "\n"; + $htmlOutput .= Url::getHiddenFields((array)$hidden_fields); + return $htmlOutput; + } + + /** + * Displays form tabs which are given by an array indexed by fieldset id + * ({@link self::displayFieldsetTop}), with values being tab titles. + * + * @param array $tabs tab names + * + * @return string + */ + public static function displayTabsTop(array $tabs) + { + $items = array(); + foreach ($tabs as $tab_id => $tab_name) { + $items[] = array( + 'content' => htmlspecialchars($tab_name), + 'url' => array( + 'href' => '#' . $tab_id, + ), + ); + } + + $htmlOutput = Template::get('list/unordered')->render( + array( + 'class' => 'tabs responsivetable', + 'items' => $items, + ) + ); + $htmlOutput .= '
      '; + $htmlOutput .= '
      '; + return $htmlOutput; + } + + /** + * Displays top part of a fieldset + * + * @param string $title title of fieldset + * @param string $description description shown on top of fieldset + * @param array|null $errors error messages to display + * @param array $attributes optional extra attributes of fieldset + * + * @return string + */ + public static function displayFieldsetTop( + $title = '', + $description = '', + $errors = null, + array $attributes = array() + ) { + global $_FormDisplayGroup; + + $_FormDisplayGroup = 0; + + $attributes = array_merge(array('class' => 'optbox'), $attributes); + + return Template::get('config/form_display/fieldset_top')->render([ + 'attributes' => $attributes, + 'title' => $title, + 'description' => $description, + 'errors' => $errors, + ]); + } + + /** + * Displays input field + * + * $opts keys: + * o doc - (string) documentation link + * o errors - error array + * o setvalue - (string) shows button allowing to set predefined value + * o show_restore_default - (boolean) whether show "restore default" button + * o userprefs_allow - whether user preferences are enabled for this field + * (null - no support, true/false - enabled/disabled) + * o userprefs_comment - (string) field comment + * o values - key - value pairs for '; + break; + case 'password': + $htmlOutput .= ''; + break; + case 'short_text': + // As seen in the reporting server (#15042) we sometimes receive + // an array here. No clue about its origin nor content, so let's avoid + // a notice on htmlspecialchars(). + if (! is_array($value)) { + $htmlOutput .= ''; + } + break; + case 'number_text': + $htmlOutput .= ''; + break; + case 'checkbox': + $htmlOutput .= ''; + break; + case 'select': + $htmlOutput .= ''; + break; + case 'list': + $htmlOutput .= ''; + break; + } + if (isset($opts['comment']) && $opts['comment']) { + $class = 'field-comment-mark'; + if (isset($opts['comment_warning']) && $opts['comment_warning']) { + $class .= ' field-comment-warning'; + } + $htmlOutput .= 'i'; + } + if ($is_setup_script + && isset($opts['userprefs_comment']) + && $opts['userprefs_comment'] + ) { + $htmlOutput .= '' + . $icons['tblops'] . ''; + } + if (isset($opts['setvalue']) && $opts['setvalue']) { + $htmlOutput .= '' . $icons['edit'] . ''; + } + if (isset($opts['show_restore_default']) && $opts['show_restore_default']) { + $htmlOutput .= '' . $icons['reload'] . ''; + } + // this must match with displayErrors() in scripts/config.js + if ($has_errors) { + $htmlOutput .= "\n
      "; + foreach ($opts['errors'] as $error) { + $htmlOutput .= '
      ' . htmlspecialchars($error) . '
      '; + } + $htmlOutput .= '
      '; + } + $htmlOutput .= ''; + if ($is_setup_script && isset($opts['userprefs_allow'])) { + $htmlOutput .= ''; + $htmlOutput .= 'get('is_setup') ? 3 : 2; + + return Template::get('config/form_display/group_header')->render([ + 'group' => $_FormDisplayGroup, + 'colspan' => $colspan, + 'header_text' => $headerText, + ]); + } + + /** + * Display group footer + * + * @return void + */ + public static function displayGroupFooter() + { + global $_FormDisplayGroup; + + $_FormDisplayGroup--; + } + + /** + * Displays bottom part of a fieldset + * + * @param bool $showButtons Whether show submit and reset button + * + * @return string + */ + public static function displayFieldsetBottom($showButtons = true) + { + return Template::get('config/form_display/fieldset_bottom')->render([ + 'show_buttons' => $showButtons, + 'is_setup' => $GLOBALS['PMA_Config']->get('is_setup'), + ]); + } + + /** + * Closes form tabs + * + * @return string + */ + public static function displayTabsBottom() + { + return Template::get('config/form_display/tabs_bottom')->render(); + } + + /** + * Displays bottom part of the form + * + * @return string + */ + public static function displayFormBottom() + { + return Template::get('config/form_display/form_bottom')->render(); + } + + /** + * Appends JS validation code to $js_array + * + * @param string $field_id ID of field to validate + * @param string|array $validators validators callback + * @param array &$js_array will be updated with javascript code + * + * @return void + */ + public static function addJsValidate($field_id, $validators, array &$js_array) + { + foreach ((array)$validators as $validator) { + $validator = (array)$validator; + $v_name = array_shift($validator); + $v_name = "PMA_" . $v_name; + $v_args = array(); + foreach ($validator as $arg) { + $v_args[] = Sanitize::escapeJsString($arg); + } + $v_args = $v_args ? ", ['" . implode("', '", $v_args) . "']" : ''; + $js_array[] = "validateField('$field_id', '$v_name', true$v_args)"; + } + } + + /** + * Displays JavaScript code + * + * @param array $js_array lines of javascript code + * + * @return string + */ + public static function displayJavascript(array $js_array) + { + if (empty($js_array)) { + return null; + } + + return Template::get('javascript/display')->render( + array('js_array' => $js_array,) + ); + } + + /** + * Displays error list + * + * @param string $name Name of item with errors + * @param array $errorList List of errors to show + * + * @return string HTML for errors + */ + public static function displayErrors($name, array $errorList) + { + return Template::get('config/form_display/errors')->render([ + 'name' => $name, + 'error_list' => $errorList, + ]); + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Config/Forms/BaseForm.php b/php/apps/phpmyadmin49/html/libraries/classes/Config/Forms/BaseForm.php new file mode 100644 index 00000000..c16fb8b3 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Config/Forms/BaseForm.php @@ -0,0 +1,85 @@ += 1 if editing a server + */ + public function __construct(ConfigFile $cf, $server_id = null) + { + parent::__construct($cf); + foreach (static::getForms() as $form_name => $form) { + $this->registerForm($form_name, $form, $server_id); + } + } + + /** + * List of available forms, each form is described as an array of fields to display. + * Fields MUST have their counterparts in the $cfg array. + * + * To define form field, use the notation below: + * $forms['Form group']['Form name'] = array('Option/path'); + * + * You can assign default values set by special button ("set value: ..."), eg.: + * 'Servers/1/pmadb' => 'phpmyadmin' + * + * To group options, use: + * ':group:' . __('group name') // just define a group + * or + * 'option' => ':group' // group starting from this option + * End group blocks with: + * ':group:end' + * + * @todo This should be abstract, but that does not work in PHP 5 + * + * @return array + */ + public static function getForms() + { + return array(); + } + + /** + * Returns list of fields used in the form. + * + * @return string[] + */ + public static function getFields() + { + $names = []; + foreach (static::getForms() as $form) { + foreach ($form as $k => $v) { + $names[] = is_int($k) ? $v : $k; + } + } + return $names; + } + + /** + * Returns name of the form + * + * @todo This should be abstract, but that does not work in PHP 5 + * + * @return string + */ + public static function getName() + { + return ''; + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Config/Forms/BaseFormList.php b/php/apps/phpmyadmin49/html/libraries/classes/Config/Forms/BaseFormList.php new file mode 100644 index 00000000..1065c3b3 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Config/Forms/BaseFormList.php @@ -0,0 +1,127 @@ +_forms = array(); + foreach (static::$all as $form) { + $class = static::get($form); + $this->_forms[] = new $class($cf); + } + } + + /** + * Processes forms, returns true on successful save + * + * @param bool $allow_partial_save allows for partial form saving + * on failed validation + * @param bool $check_form_submit whether check for $_POST['submit_save'] + * + * @return boolean whether processing was successful + */ + public function process($allow_partial_save = true, $check_form_submit = true) + { + $ret = true; + foreach ($this->_forms as $form) { + $ret = $ret && $form->process($allow_partial_save, $check_form_submit); + } + return $ret; + } + + /** + * Displays errors + * + * @return string HTML for errors + */ + public function displayErrors() + { + $ret = ''; + foreach ($this->_forms as $form) { + $ret .= $form->displayErrors(); + } + return $ret; + } + + /** + * Reverts erroneous fields to their default values + * + * @return void + */ + public function fixErrors() + { + foreach ($this->_forms as $form) { + $form->fixErrors(); + } + } + + /** + * Tells whether form validation failed + * + * @return boolean + */ + public function hasErrors() + { + $ret = false; + foreach ($this->_forms as $form) { + $ret = $ret || $form->hasErrors(); + } + return $ret; + } + + /** + * Returns list of fields used in the form. + * + * @return string[] + */ + public static function getFields() + { + $names = []; + foreach (static::$all as $form) { + $class = static::get($form); + $names = array_merge($names, $class::getFields()); + } + return $names; + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Config/Forms/Page/BrowseForm.php b/php/apps/phpmyadmin49/html/libraries/classes/Config/Forms/Page/BrowseForm.php new file mode 100644 index 00000000..e39a7fd2 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Config/Forms/Page/BrowseForm.php @@ -0,0 +1,21 @@ + MainForm::getForms()['Browse'] + ]; + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Config/Forms/Page/DbStructureForm.php b/php/apps/phpmyadmin49/html/libraries/classes/Config/Forms/Page/DbStructureForm.php new file mode 100644 index 00000000..03c9172a --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Config/Forms/Page/DbStructureForm.php @@ -0,0 +1,22 @@ + MainForm::getForms()['DbStructure'] + ]; + } +} + diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Config/Forms/Page/EditForm.php b/php/apps/phpmyadmin49/html/libraries/classes/Config/Forms/Page/EditForm.php new file mode 100644 index 00000000..b7b83b03 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Config/Forms/Page/EditForm.php @@ -0,0 +1,23 @@ + MainForm::getForms()['Edit'], + 'Text_fields' => FeaturesForm::getForms()['Text_fields'], + ]; + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Config/Forms/Page/ExportForm.php b/php/apps/phpmyadmin49/html/libraries/classes/Config/Forms/Page/ExportForm.php new file mode 100644 index 00000000..824be9c7 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Config/Forms/Page/ExportForm.php @@ -0,0 +1,12 @@ + MainForm::getForms()['TableStructure'] + ]; + } +} + diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Config/Forms/Setup/ConfigForm.php b/php/apps/phpmyadmin49/html/libraries/classes/Config/Forms/Setup/ConfigForm.php new file mode 100644 index 00000000..35dfd854 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Config/Forms/Setup/ConfigForm.php @@ -0,0 +1,23 @@ + array( + 'DefaultLang', + 'ServerDefault' + ), + ); + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Config/Forms/Setup/ExportForm.php b/php/apps/phpmyadmin49/html/libraries/classes/Config/Forms/Setup/ExportForm.php new file mode 100644 index 00000000..09e59a14 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Config/Forms/Setup/ExportForm.php @@ -0,0 +1,12 @@ + ':group', + 'IconvExtraParams', + ':group:end', + 'ZipDump', + 'GZipDump', + 'BZipDump', + 'CompressOnFly' + ); + $result['Security'] = array( + 'blowfish_secret', + 'CheckConfigurationPermissions', + 'TrustedProxies', + 'AllowUserDropDatabase', + 'AllowArbitraryServer', + 'ArbitraryServerRegexp', + 'LoginCookieRecall', + 'LoginCookieStore', + 'LoginCookieDeleteAll', + 'CaptchaLoginPublicKey', + 'CaptchaLoginPrivateKey' + ); + $result['Developer'] = array( + 'UserprefsDeveloperTab', + 'DBG/sql', + ); + $result['Other_core_settings'] = array( + 'OBGzip', + 'PersistentConnections', + 'ExecTimeLimit', + 'MemoryLimit', + 'UseDbSearch', + 'ProxyUrl', + 'ProxyUser', + 'ProxyPass', + 'AllowThirdPartyFraming', + 'ZeroConf', + ); + return $result; + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Config/Forms/Setup/ImportForm.php b/php/apps/phpmyadmin49/html/libraries/classes/Config/Forms/Setup/ImportForm.php new file mode 100644 index 00000000..cc36b1ba --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Config/Forms/Setup/ImportForm.php @@ -0,0 +1,12 @@ + array('Servers' => array(1 => array( + 'verbose', + 'host', + 'port', + 'socket', + 'ssl', + 'compress'))), + 'Server_auth' => array('Servers' => array(1 => array( + 'auth_type', + ':group:' . __('Config authentication'), + 'user', + 'password', + ':group:end', + ':group:' . __('HTTP authentication'), + 'auth_http_realm', + ':group:end', + ':group:' . __('Signon authentication'), + 'SignonSession', + 'SignonURL', + 'LogoutURL'))), + 'Server_config' => array('Servers' => array(1 => array( + 'only_db', + 'hide_db', + 'AllowRoot', + 'AllowNoPassword', + 'DisableIS', + 'AllowDeny/order', + 'AllowDeny/rules', + 'SessionTimeZone'))), + 'Server_pmadb' => array('Servers' => array(1 => array( + 'pmadb' => 'phpmyadmin', + 'controlhost', + 'controlport', + 'controluser', + 'controlpass', + 'bookmarktable' => 'pma__bookmark', + 'relation' => 'pma__relation', + 'userconfig' => 'pma__userconfig', + 'users' => 'pma__users', + 'usergroups' => 'pma__usergroups', + 'navigationhiding' => 'pma__navigationhiding', + 'table_info' => 'pma__table_info', + 'column_info' => 'pma__column_info', + 'history' => 'pma__history', + 'recent' => 'pma__recent', + 'favorite' => 'pma__favorite', + 'table_uiprefs' => 'pma__table_uiprefs', + 'tracking' => 'pma__tracking', + 'table_coords' => 'pma__table_coords', + 'pdf_pages' => 'pma__pdf_pages', + 'savedsearches' => 'pma__savedsearches', + 'central_columns' => 'pma__central_columns', + 'designer_settings' => 'pma__designer_settings', + 'export_templates' => 'pma__export_templates', + 'MaxTableUiprefs' => 100))), + 'Server_tracking' => array('Servers' => array(1 => array( + 'tracking_version_auto_create', + 'tracking_default_statements', + 'tracking_add_drop_view', + 'tracking_add_drop_table', + 'tracking_add_drop_database', + ))), + ); + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Config/Forms/Setup/SetupFormList.php b/php/apps/phpmyadmin49/html/libraries/classes/Config/Forms/Setup/SetupFormList.php new file mode 100644 index 00000000..900bf059 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Config/Forms/Setup/SetupFormList.php @@ -0,0 +1,25 @@ + array( + 'Export/method', + ':group:' . __('Quick'), + 'Export/quick_export_onserver', + 'Export/quick_export_onserver_overwrite', + ':group:end', + ':group:' . __('Custom'), + 'Export/format', + 'Export/compression', + 'Export/charset', + 'Export/lock_tables', + 'Export/as_separate_files', + 'Export/asfile' => ':group', + 'Export/onserver', + 'Export/onserver_overwrite', + ':group:end', + 'Export/file_template_table', + 'Export/file_template_database', + 'Export/file_template_server' + ), + 'Sql' => array( + 'Export/sql_include_comments' => ':group', + 'Export/sql_dates', + 'Export/sql_relation', + 'Export/sql_mime', + ':group:end', + 'Export/sql_use_transaction', + 'Export/sql_disable_fk', + 'Export/sql_views_as_tables', + 'Export/sql_metadata', + 'Export/sql_compatibility', + 'Export/sql_structure_or_data', + ':group:' . __('Structure'), + 'Export/sql_drop_database', + 'Export/sql_create_database', + 'Export/sql_drop_table', + 'Export/sql_create_table' => ':group', + 'Export/sql_if_not_exists', + 'Export/sql_auto_increment', + ':group:end', + 'Export/sql_create_view', + 'Export/sql_procedure_function', + 'Export/sql_create_trigger', + 'Export/sql_backquotes', + ':group:end', + ':group:' . __('Data'), + 'Export/sql_delayed', + 'Export/sql_ignore', + 'Export/sql_type', + 'Export/sql_insert_syntax', + 'Export/sql_max_query_size', + 'Export/sql_hex_for_binary', + 'Export/sql_utc_time' + ), + 'CodeGen' => array( + 'Export/codegen_format' + ), + 'Csv' => array( + ':group:' . __('CSV'), + 'Export/csv_separator', + 'Export/csv_enclosed', + 'Export/csv_escaped', + 'Export/csv_terminated', + 'Export/csv_null', + 'Export/csv_removeCRLF', + 'Export/csv_columns', + ':group:end', + ':group:' . __('CSV for MS Excel'), + 'Export/excel_null', + 'Export/excel_removeCRLF', + 'Export/excel_columns', + 'Export/excel_edition' + ), + 'Latex' => array( + 'Export/latex_caption', + 'Export/latex_structure_or_data', + ':group:' . __('Structure'), + 'Export/latex_structure_caption', + 'Export/latex_structure_continued_caption', + 'Export/latex_structure_label', + 'Export/latex_relation', + 'Export/latex_comments', + 'Export/latex_mime', + ':group:end', + ':group:' . __('Data'), + 'Export/latex_columns', + 'Export/latex_data_caption', + 'Export/latex_data_continued_caption', + 'Export/latex_data_label', + 'Export/latex_null' + ), + 'Microsoft_Office' => array( + ':group:' . __('Microsoft Word 2000'), + 'Export/htmlword_structure_or_data', + 'Export/htmlword_null', + 'Export/htmlword_columns'), + 'Open_Document' => array( + ':group:' . __('OpenDocument Spreadsheet'), + 'Export/ods_columns', + 'Export/ods_null', + ':group:end', + ':group:' . __('OpenDocument Text'), + 'Export/odt_structure_or_data', + ':group:' . __('Structure'), + 'Export/odt_relation', + 'Export/odt_comments', + 'Export/odt_mime', + ':group:end', + ':group:' . __('Data'), + 'Export/odt_columns', + 'Export/odt_null' + ), + 'Texy' => array( + 'Export/texytext_structure_or_data', + ':group:' . __('Data'), + 'Export/texytext_null', + 'Export/texytext_columns' + ), + ); + } + + public static function getName() + { + return __('Export'); + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Config/Forms/User/FeaturesForm.php b/php/apps/phpmyadmin49/html/libraries/classes/Config/Forms/User/FeaturesForm.php new file mode 100644 index 00000000..9a121e9e --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Config/Forms/User/FeaturesForm.php @@ -0,0 +1,84 @@ + array( + 'VersionCheck', + 'NaturalOrder', + 'InitialSlidersState', + 'SkipLockedTables', + 'DisableMultiTableMaintenance', + 'ShowHint', + 'SendErrorReports', + 'ConsoleEnterExecutes', + 'DisableShortcutKeys', + 'FontSize', + ), + 'Databases' => array( + 'Servers/1/only_db', // saves to Server/only_db + 'Servers/1/hide_db', // saves to Server/hide_db + 'MaxDbList', + 'MaxTableList', + 'DefaultConnectionCollation', + ), + 'Text_fields' => array( + 'CharEditing', + 'MinSizeForInputField', + 'MaxSizeForInputField', + 'CharTextareaCols', + 'CharTextareaRows', + 'TextareaCols', + 'TextareaRows', + 'LongtextDoubleTextarea' + ), + 'Page_titles' => array( + 'TitleDefault', + 'TitleTable', + 'TitleDatabase', + 'TitleServer' + ), + 'Warnings' => array( + 'PmaNoRelation_DisableWarning', + 'SuhosinDisableWarning', + 'LoginCookieValidityDisableWarning', + 'ReservedWordDisableWarning' + ), + 'Console' => array( + 'Console/Mode', + 'Console/StartHistory', + 'Console/AlwaysExpand', + 'Console/CurrentQuery', + 'Console/EnterExecutes', + 'Console/DarkTheme', + 'Console/Height', + 'Console/GroupQueries', + 'Console/OrderBy', + 'Console/Order', + ), + ); + // skip Developer form if no setting is available + if ($GLOBALS['cfg']['UserprefsDeveloperTab']) { + $result['Developer'] = array( + 'DBG/sql' + ); + } + return $result; + } + + public static function getName() + { + return __('Features'); + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Config/Forms/User/ImportForm.php b/php/apps/phpmyadmin49/html/libraries/classes/Config/Forms/User/ImportForm.php new file mode 100644 index 00000000..daf5c43b --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Config/Forms/User/ImportForm.php @@ -0,0 +1,60 @@ + array( + 'Import/format', + 'Import/charset', + 'Import/allow_interrupt', + 'Import/skip_queries' + ), + 'Sql' => array( + 'Import/sql_compatibility', + 'Import/sql_no_auto_value_on_zero', + 'Import/sql_read_as_multibytes' + ), + 'Csv' => array( + ':group:' . __('CSV'), + 'Import/csv_replace', + 'Import/csv_ignore', + 'Import/csv_terminated', + 'Import/csv_enclosed', + 'Import/csv_escaped', + 'Import/csv_col_names', + ':group:end', + ':group:' . __('CSV using LOAD DATA'), + 'Import/ldi_replace', + 'Import/ldi_ignore', + 'Import/ldi_terminated', + 'Import/ldi_enclosed', + 'Import/ldi_escaped', + 'Import/ldi_local_option' + ), + 'Open_Document' => array( + ':group:' . __('OpenDocument Spreadsheet'), + 'Import/ods_col_names', + 'Import/ods_empty_rows', + 'Import/ods_recognize_percentages', + 'Import/ods_recognize_currency' + ), + + ); + } + + public static function getName() + { + return __('Import'); + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Config/Forms/User/MainForm.php b/php/apps/phpmyadmin49/html/libraries/classes/Config/Forms/User/MainForm.php new file mode 100644 index 00000000..e3c54edd --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Config/Forms/User/MainForm.php @@ -0,0 +1,86 @@ + array( + 'ShowCreateDb', + 'ShowStats', + 'ShowServerInfo' + ), + 'DbStructure' => array( + 'ShowDbStructureCharset', + 'ShowDbStructureComment', + 'ShowDbStructureCreation', + 'ShowDbStructureLastUpdate', + 'ShowDbStructureLastCheck' + ), + 'TableStructure' => array( + 'HideStructureActions', + 'ShowColumnComments', + ':group:' . __('Default transformations'), + 'DefaultTransformations/Hex', + 'DefaultTransformations/Substring', + 'DefaultTransformations/Bool2Text', + 'DefaultTransformations/External', + 'DefaultTransformations/PreApPend', + 'DefaultTransformations/DateFormat', + 'DefaultTransformations/Inline', + 'DefaultTransformations/TextImageLink', + 'DefaultTransformations/TextLink', + ':group:end' + ), + 'Browse' => array( + 'TableNavigationLinksMode', + 'ActionLinksMode', + 'ShowAll', + 'MaxRows', + 'Order', + 'BrowsePointerEnable', + 'BrowseMarkerEnable', + 'GridEditing', + 'SaveCellsAtOnce', + 'RepeatCells', + 'LimitChars', + 'RowActionLinks', + 'RowActionLinksWithoutUnique', + 'TablePrimaryKeyOrder', + 'RememberSorting', + 'RelationalDisplay' + ), + 'Edit' => array( + 'ProtectBinary', + 'ShowFunctionFields', + 'ShowFieldTypesInDataEditView', + 'InsertRows', + 'ForeignKeyDropdownOrder', + 'ForeignKeyMaxLimit' + ), + 'Tabs' => array( + 'TabsMode', + 'DefaultTabServer', + 'DefaultTabDatabase', + 'DefaultTabTable' + ), + 'DisplayRelationalSchema' => array( + 'PDFDefaultPageSize' + ), + ); + } + + public static function getName() + { + return __('Main panel'); + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Config/Forms/User/NaviForm.php b/php/apps/phpmyadmin49/html/libraries/classes/Config/Forms/User/NaviForm.php new file mode 100644 index 00000000..1d9ff1ff --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Config/Forms/User/NaviForm.php @@ -0,0 +1,61 @@ + array( + 'ShowDatabasesNavigationAsTree', + 'NavigationLinkWithMainPanel', + 'NavigationDisplayLogo', + 'NavigationLogoLink', + 'NavigationLogoLinkWindow', + 'NavigationTreePointerEnable', + 'FirstLevelNavigationItems', + 'NavigationTreeDisplayItemFilterMinimum', + 'NumRecentTables', + 'NumFavoriteTables', + 'NavigationWidth', + ), + 'Navi_tree' => array( + 'MaxNavigationItems', + 'NavigationTreeEnableGrouping', + 'NavigationTreeEnableExpansion', + 'NavigationTreeShowTables', + 'NavigationTreeShowViews', + 'NavigationTreeShowFunctions', + 'NavigationTreeShowProcedures', + 'NavigationTreeShowEvents' + ), + 'Navi_servers' => array( + 'NavigationDisplayServers', + 'DisplayServersList', + ), + 'Navi_databases' => array( + 'NavigationTreeDisplayDbFilterMinimum', + 'NavigationTreeDbSeparator' + ), + 'Navi_tables' => array( + 'NavigationTreeDefaultTabTable', + 'NavigationTreeDefaultTabTable2', + 'NavigationTreeTableSeparator', + 'NavigationTreeTableLevel', + ), + ); + } + + public static function getName() + { + return __('Navigation panel'); + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Config/Forms/User/SqlForm.php b/php/apps/phpmyadmin49/html/libraries/classes/Config/Forms/User/SqlForm.php new file mode 100644 index 00000000..2058076a --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Config/Forms/User/SqlForm.php @@ -0,0 +1,42 @@ + array( + 'ShowSQL', + 'Confirm', + 'QueryHistoryMax', + 'IgnoreMultiSubmitErrors', + 'MaxCharactersInDisplayedSQL', + 'RetainQueryBox', + 'CodemirrorEnable', + 'LintEnable', + 'EnableAutocompleteForTablesAndColumns', + 'DefaultForeignKeyChecks', + ), + 'Sql_box' => array( + 'SQLQuery/Edit', + 'SQLQuery/Explain', + 'SQLQuery/ShowAsPHP', + 'SQLQuery/Refresh', + ), + ); + } + + public static function getName() + { + return __('SQL queries'); + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Config/Forms/User/UserFormList.php b/php/apps/phpmyadmin49/html/libraries/classes/Config/Forms/User/UserFormList.php new file mode 100644 index 00000000..f32cae32 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Config/Forms/User/UserFormList.php @@ -0,0 +1,23 @@ +userPreferences = new UserPreferences(); + + $form_class = PageFormList::get($formGroupName); + if (is_null($form_class)) { + return; + } + + if (isset($_REQUEST['printview']) && $_REQUEST['printview'] == '1') { + return; + } + + if (!empty($elemId)) { + $this->_elemId = $elemId; + } + $this->_groupName = $formGroupName; + + $cf = new ConfigFile($GLOBALS['PMA_Config']->base_settings); + $this->userPreferences->pageInit($cf); + + $form_display = new $form_class($cf); + + // Process form + $error = null; + if (isset($_POST['submit_save']) + && $_POST['submit_save'] == $formGroupName + ) { + $this->_processPageSettings($form_display, $cf, $error); + } + + // Display forms + $this->_HTML = $this->_getPageSettingsDisplay($form_display, $error); + } + + /** + * Process response to form + * + * @param FormDisplay &$form_display Form + * @param ConfigFile &$cf Configuration file + * @param Message|null &$error Error message + * + * @return void + */ + private function _processPageSettings(&$form_display, &$cf, &$error) + { + if ($form_display->process(false) && !$form_display->hasErrors()) { + // save settings + $result = $this->userPreferences->save($cf->getConfigArray()); + if ($result === true) { + // reload page + $response = Response::getInstance(); + Core::sendHeaderLocation( + $response->getFooter()->getSelfUrl('unencoded') + ); + exit(); + } else { + $error = $result; + } + } + } + + /** + * Store errors in _errorHTML + * + * @param FormDisplay &$form_display Form + * @param Message|null &$error Error message + * + * @return void + */ + private function _storeError(&$form_display, &$error) + { + $retval = ''; + if ($error) { + $retval .= $error->getDisplay(); + } + if ($form_display->hasErrors()) { + // form has errors + $retval .= '
      ' + . '' . __( + 'Cannot save settings, submitted configuration form contains ' + . 'errors!' + ) . '' + . $form_display->displayErrors() + . '
      '; + } + $this->_errorHTML = $retval; + } + + /** + * Display page-related settings + * + * @param FormDisplay &$form_display Form + * @param Message &$error Error message + * + * @return string + */ + private function _getPageSettingsDisplay(&$form_display, &$error) + { + $response = Response::getInstance(); + + $retval = ''; + + $this->_storeError($form_display, $error); + + $retval .= '
      '; + $retval .= '
      '; + $retval .= $form_display->getDisplay( + true, + true, + false, + $response->getFooter()->getSelfUrl(), + array( + 'submit_save' => $this->_groupName + ) + ); + $retval .= '
      '; + $retval .= '
      '; + + return $retval; + } + + /** + * Get HTML output + * + * @return string + */ + public function getHTML() + { + return $this->_HTML; + } + + /** + * Get error HTML output + * + * @return string + */ + public function getErrorHTML() + { + return $this->_errorHTML; + } + + /** + * Group to show for Page-related settings + * @param string $formGroupName The name of config form group to display + * @return PageSettings + */ + public static function showGroup($formGroupName) + { + $object = new PageSettings($formGroupName); + + $response = Response::getInstance(); + $response->addHTML($object->getErrorHTML()); + $response->addHTML($object->getHTML()); + + return $object; + } + + /** + * Get HTML for navigation settings + * @return string + */ + public static function getNaviSettings() + { + $object = new PageSettings('Navi', 'pma_navigation_settings'); + + $response = Response::getInstance(); + $response->addHTML($object->getErrorHTML()); + return $object->getHTML(); + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Config/ServerConfigChecks.php b/php/apps/phpmyadmin49/html/libraries/classes/Config/ServerConfigChecks.php new file mode 100644 index 00000000..485c3b42 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Config/ServerConfigChecks.php @@ -0,0 +1,560 @@ +cfg = $cfg; + } + + /** + * Perform config checks + * + * @return void + */ + public function performConfigChecks() + { + $blowfishSecret = $this->cfg->get('blowfish_secret'); + $blowfishSecretSet = false; + $cookieAuthUsed = false; + + list($cookieAuthUsed, $blowfishSecret, $blowfishSecretSet) + = $this->performConfigChecksServers( + $cookieAuthUsed, $blowfishSecret, $blowfishSecretSet + ); + + $this->performConfigChecksCookieAuthUsed( + $cookieAuthUsed, $blowfishSecretSet, + $blowfishSecret + ); + + // + // $cfg['AllowArbitraryServer'] + // should be disabled + // + if ($this->cfg->getValue('AllowArbitraryServer')) { + $sAllowArbitraryServerWarn = sprintf( + __( + 'This %soption%s should be disabled as it allows attackers to ' + . 'bruteforce login to any MySQL server. If you feel this is necessary, ' + . 'use %srestrict login to MySQL server%s or %strusted proxies list%s. ' + . 'However, IP-based protection with trusted proxies list may not be ' + . 'reliable if your IP belongs to an ISP where thousands of users, ' + . 'including you, are connected to.' + ), + '[a@' . Url::getCommon(array('page' => 'form', 'formset' => 'Features')) . '#tab_Security]', + '[/a]', + '[a@' . Url::getCommon(array('page' => 'form', 'formset' => 'Features')) . '#tab_Security]', + '[/a]', + '[a@' . Url::getCommon(array('page' => 'form', 'formset' => 'Features')) . '#tab_Security]', + '[/a]' + ); + SetupIndex::messagesSet( + 'notice', + 'AllowArbitraryServer', + Descriptions::get('AllowArbitraryServer'), + Sanitize::sanitize($sAllowArbitraryServerWarn) + ); + } + + $this->performConfigChecksLoginCookie(); + + $sDirectoryNotice = __( + 'This value should be double checked to ensure that this directory is ' + . 'neither world accessible nor readable or writable by other users on ' + . 'your server.' + ); + + // + // $cfg['SaveDir'] + // should not be world-accessible + // + if ($this->cfg->getValue('SaveDir') != '') { + SetupIndex::messagesSet( + 'notice', + 'SaveDir', + Descriptions::get('SaveDir'), + Sanitize::sanitize($sDirectoryNotice) + ); + } + + // + // $cfg['TempDir'] + // should not be world-accessible + // + if ($this->cfg->getValue('TempDir') != '') { + SetupIndex::messagesSet( + 'notice', + 'TempDir', + Descriptions::get('TempDir'), + Sanitize::sanitize($sDirectoryNotice) + ); + } + + $this->performConfigChecksZips(); + } + + /** + * Check config of servers + * + * @param boolean $cookieAuthUsed Cookie auth is used + * @param string $blowfishSecret Blowfish secret + * @param boolean $blowfishSecretSet Blowfish secret set + * + * @return array + */ + protected function performConfigChecksServers( + $cookieAuthUsed, $blowfishSecret, + $blowfishSecretSet + ) { + $serverCnt = $this->cfg->getServerCount(); + for ($i = 1; $i <= $serverCnt; $i++) { + $cookieAuthServer + = ($this->cfg->getValue("Servers/$i/auth_type") == 'cookie'); + $cookieAuthUsed |= $cookieAuthServer; + $serverName = $this->performConfigChecksServersGetServerName( + $this->cfg->getServerName($i), $i + ); + $serverName = htmlspecialchars($serverName); + + list($blowfishSecret, $blowfishSecretSet) + = $this->performConfigChecksServersSetBlowfishSecret( + $blowfishSecret, $cookieAuthServer, $blowfishSecretSet + ); + + // + // $cfg['Servers'][$i]['ssl'] + // should be enabled if possible + // + if (!$this->cfg->getValue("Servers/$i/ssl")) { + $title = Descriptions::get('Servers/1/ssl') . " ($serverName)"; + SetupIndex::messagesSet( + 'notice', + "Servers/$i/ssl", + $title, + __( + 'You should use SSL connections if your database server ' + . 'supports it.' + ) + ); + } + $sSecurityInfoMsg = Sanitize::sanitize(sprintf( + __( + 'If you feel this is necessary, use additional protection settings - ' + . '%1$shost authentication%2$s settings and %3$strusted proxies list%4%s. ' + . 'However, IP-based protection may not be reliable if your IP belongs ' + . 'to an ISP where thousands of users, including you, are connected to.' + ), + '[a@' . Url::getCommon(array('page' => 'servers', 'mode' => 'edit', 'id' => $i)) . '#tab_Server_config]', + '[/a]', + '[a@' . Url::getCommon(array('page' => 'form', 'formset' => 'Features')) . '#tab_Security]', + '[/a]' + )); + + // + // $cfg['Servers'][$i]['auth_type'] + // warn about full user credentials if 'auth_type' is 'config' + // + if ($this->cfg->getValue("Servers/$i/auth_type") == 'config' + && $this->cfg->getValue("Servers/$i/user") != '' + && $this->cfg->getValue("Servers/$i/password") != '' + ) { + $title = Descriptions::get('Servers/1/auth_type') + . " ($serverName)"; + SetupIndex::messagesSet( + 'notice', + "Servers/$i/auth_type", + $title, + Sanitize::sanitize(sprintf( + __( + 'You set the [kbd]config[/kbd] authentication type and included ' + . 'username and password for auto-login, which is not a desirable ' + . 'option for live hosts. Anyone who knows or guesses your phpMyAdmin ' + . 'URL can directly access your phpMyAdmin panel. Set %1$sauthentication ' + . 'type%2$s to [kbd]cookie[/kbd] or [kbd]http[/kbd].' + ), + '[a@' . Url::getCommon(array('page' => 'servers', 'mode' => 'edit', 'id' => $i)) . '#tab_Server]', + '[/a]' + )) + . ' ' . $sSecurityInfoMsg + ); + } + + // + // $cfg['Servers'][$i]['AllowRoot'] + // $cfg['Servers'][$i]['AllowNoPassword'] + // serious security flaw + // + if ($this->cfg->getValue("Servers/$i/AllowRoot") + && $this->cfg->getValue("Servers/$i/AllowNoPassword") + ) { + $title = Descriptions::get('Servers/1/AllowNoPassword') + . " ($serverName)"; + SetupIndex::messagesSet( + 'notice', + "Servers/$i/AllowNoPassword", + $title, + __('You allow for connecting to the server without a password.') + . ' ' . $sSecurityInfoMsg + ); + } + } + return array($cookieAuthUsed, $blowfishSecret, $blowfishSecretSet); + } + + /** + * Set blowfish secret + * + * @param string $blowfishSecret Blowfish secret + * @param boolean $cookieAuthServer Cookie auth is used + * @param boolean $blowfishSecretSet Blowfish secret set + * + * @return array + */ + protected function performConfigChecksServersSetBlowfishSecret( + $blowfishSecret, $cookieAuthServer, $blowfishSecretSet + ) { + if ($cookieAuthServer && $blowfishSecret === null) { + $blowfishSecretSet = true; + $this->cfg->set('blowfish_secret', Util::generateRandom(32)); + } + return array($blowfishSecret, $blowfishSecretSet); + } + + /** + * Define server name + * + * @param string $serverName Server name + * @param int $serverId Server id + * + * @return string Server name + */ + protected function performConfigChecksServersGetServerName( + $serverName, $serverId + ) { + if ($serverName == 'localhost') { + $serverName .= " [$serverId]"; + return $serverName; + } + return $serverName; + } + + /** + * Perform config checks for zip part. + * + * @return void + */ + protected function performConfigChecksZips() { + $this->performConfigChecksServerGZipdump(); + $this->performConfigChecksServerBZipdump(); + $this->performConfigChecksServersZipdump(); + } + + /** + * Perform config checks for zip part. + * + * @return void + */ + protected function performConfigChecksServersZipdump() { + // + // $cfg['ZipDump'] + // requires zip_open in import + // + if ($this->cfg->getValue('ZipDump') && !$this->functionExists('zip_open')) { + SetupIndex::messagesSet( + 'error', + 'ZipDump_import', + Descriptions::get('ZipDump'), + Sanitize::sanitize(sprintf( + __( + '%sZip decompression%s requires functions (%s) which are unavailable ' + . 'on this system.' + ), + '[a@' . Url::getCommon(array('page' => 'form', 'formset' => 'Features')) . '#tab_Import_export]', + '[/a]', + 'zip_open' + )) + ); + } + + // + // $cfg['ZipDump'] + // requires gzcompress in export + // + if ($this->cfg->getValue('ZipDump') && !$this->functionExists('gzcompress')) { + SetupIndex::messagesSet( + 'error', + 'ZipDump_export', + Descriptions::get('ZipDump'), + Sanitize::sanitize(sprintf( + __( + '%sZip compression%s requires functions (%s) which are unavailable on ' + . 'this system.' + ), + '[a@' . Url::getCommon(array('page' => 'form', 'formset' => 'Features')) . '#tab_Import_export]', + '[/a]', + 'gzcompress' + )) + ); + } + } + + /** + * Check config of servers + * + * @param boolean $cookieAuthUsed Cookie auth is used + * @param boolean $blowfishSecretSet Blowfish secret set + * @param string $blowfishSecret Blowfish secret + * + * @return array + */ + protected function performConfigChecksCookieAuthUsed( + $cookieAuthUsed, $blowfishSecretSet, + $blowfishSecret + ) { + // + // $cfg['blowfish_secret'] + // it's required for 'cookie' authentication + // + if ($cookieAuthUsed) { + if ($blowfishSecretSet) { + // 'cookie' auth used, blowfish_secret was generated + SetupIndex::messagesSet( + 'notice', + 'blowfish_secret_created', + Descriptions::get('blowfish_secret'), + Sanitize::sanitize(__( + 'You didn\'t have blowfish secret set and have enabled ' + . '[kbd]cookie[/kbd] authentication, so a key was automatically ' + . 'generated for you. It is used to encrypt cookies; you don\'t need to ' + . 'remember it.' + )) + ); + } else { + $blowfishWarnings = array(); + // check length + if (strlen($blowfishSecret) < 32) { + // too short key + $blowfishWarnings[] = __( + 'Key is too short, it should have at least 32 characters.' + ); + } + // check used characters + $hasDigits = (bool)preg_match('/\d/', $blowfishSecret); + $hasChars = (bool)preg_match('/\S/', $blowfishSecret); + $hasNonword = (bool)preg_match('/\W/', $blowfishSecret); + if (!$hasDigits || !$hasChars || !$hasNonword) { + $blowfishWarnings[] = Sanitize::sanitize( + __( + 'Key should contain letters, numbers [em]and[/em] ' + . 'special characters.' + ) + ); + } + if (!empty($blowfishWarnings)) { + SetupIndex::messagesSet( + 'error', + 'blowfish_warnings' . count($blowfishWarnings), + Descriptions::get('blowfish_secret'), + implode('
      ', $blowfishWarnings) + ); + } + } + } + } + + /** + * Check configuration for login cookie + * + * @return void + */ + protected function performConfigChecksLoginCookie() { + // + // $cfg['LoginCookieValidity'] + // value greater than session.gc_maxlifetime will cause + // random session invalidation after that time + $loginCookieValidity = $this->cfg->getValue('LoginCookieValidity'); + if ($loginCookieValidity > ini_get('session.gc_maxlifetime') + ) { + SetupIndex::messagesSet( + 'error', + 'LoginCookieValidity', + Descriptions::get('LoginCookieValidity'), + Sanitize::sanitize(sprintf( + __( + '%1$sLogin cookie validity%2$s greater than %3$ssession.gc_maxlifetime%4$s may ' + . 'cause random session invalidation (currently session.gc_maxlifetime ' + . 'is %5$d).' + ), + '[a@' . Url::getCommon(array('page' => 'form', 'formset' => 'Features')) . '#tab_Security]', + '[/a]', + '[a@' . Core::getPHPDocLink('session.configuration.php#ini.session.gc-maxlifetime') . ']', + '[/a]', + ini_get('session.gc_maxlifetime') + )) + ); + } + + // + // $cfg['LoginCookieValidity'] + // should be at most 1800 (30 min) + // + if ($loginCookieValidity > 1800) { + SetupIndex::messagesSet( + 'notice', + 'LoginCookieValidity', + Descriptions::get('LoginCookieValidity'), + Sanitize::sanitize(sprintf( + __( + '%sLogin cookie validity%s should be set to 1800 seconds (30 minutes) ' + . 'at most. Values larger than 1800 may pose a security risk such as ' + . 'impersonation.' + ), + '[a@' . Url::getCommon(array('page' => 'form', 'formset' => 'Features')) . '#tab_Security]', + '[/a]' + )) + ); + } + + // + // $cfg['LoginCookieValidity'] + // $cfg['LoginCookieStore'] + // LoginCookieValidity must be less or equal to LoginCookieStore + // + if (($this->cfg->getValue('LoginCookieStore') != 0) + && ($loginCookieValidity > $this->cfg->getValue('LoginCookieStore')) + ) { + SetupIndex::messagesSet( + 'error', + 'LoginCookieValidity', + Descriptions::get('LoginCookieValidity'), + Sanitize::sanitize(sprintf( + __( + 'If using [kbd]cookie[/kbd] authentication and %sLogin cookie store%s ' + . 'is not 0, %sLogin cookie validity%s must be set to a value less or ' + . 'equal to it.' + ), + '[a@' . Url::getCommon(array('page' => 'form', 'formset' => 'Features')) . '#tab_Security]', + '[/a]', + '[a@' . Url::getCommon(array('page' => 'form', 'formset' => 'Features')) . '#tab_Security]', + '[/a]' + )) + ); + } + } + + /** + * Check GZipDump configuration + * + * @return void + */ + protected function performConfigChecksServerBZipdump() + { + // + // $cfg['BZipDump'] + // requires bzip2 functions + // + if ($this->cfg->getValue('BZipDump') + && (!$this->functionExists('bzopen') || !$this->functionExists('bzcompress')) + ) { + $functions = $this->functionExists('bzopen') + ? '' : + 'bzopen'; + $functions .= $this->functionExists('bzcompress') + ? '' + : ($functions ? ', ' : '') . 'bzcompress'; + SetupIndex::messagesSet( + 'error', + 'BZipDump', + Descriptions::get('BZipDump'), + Sanitize::sanitize( + sprintf( + __( + '%1$sBzip2 compression and decompression%2$s requires functions (%3$s) which ' + . 'are unavailable on this system.' + ), + '[a@' . Url::getCommon(array('page' => 'form', 'formset' => 'Features')) . '#tab_Import_export]', + '[/a]', + $functions + ) + ) + ); + } + } + + /** + * Check GZipDump configuration + * + * @return void + */ + protected function performConfigChecksServerGZipdump() + { + // + // $cfg['GZipDump'] + // requires zlib functions + // + if ($this->cfg->getValue('GZipDump') + && (!$this->functionExists('gzopen') || !$this->functionExists('gzencode')) + ) { + SetupIndex::messagesSet( + 'error', + 'GZipDump', + Descriptions::get('GZipDump'), + Sanitize::sanitize(sprintf( + __( + '%1$sGZip compression and decompression%2$s requires functions (%3$s) which ' + . 'are unavailable on this system.' + ), + '[a@' . Url::getCommon(array('page' => 'form', 'formset' => 'Features')) . '#tab_Import_export]', + '[/a]', + 'gzencode' + )) + ); + } + } + + /** + * Wrapper around function_exists to allow mock in test + * + * @param string $name Function name + * + * @return boolean + */ + protected function functionExists($name) + { + return function_exists($name); + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Config/Validator.php b/php/apps/phpmyadmin49/html/libraries/classes/Config/Validator.php new file mode 100644 index 00000000..2d0bcf32 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Config/Validator.php @@ -0,0 +1,589 @@ +getDbEntry('_validators', array()); + if ($GLOBALS['PMA_Config']->get('is_setup')) { + return $validators; + } + + // not in setup script: load additional validators for user + // preferences we need original config values not overwritten + // by user preferences, creating a new PhpMyAdmin\Config instance is a + // better idea than hacking into its code + $uvs = $cf->getDbEntry('_userValidators', array()); + foreach ($uvs as $field => $uv_list) { + $uv_list = (array)$uv_list; + foreach ($uv_list as &$uv) { + if (!is_array($uv)) { + continue; + } + for ($i = 1, $nb = count($uv); $i < $nb; $i++) { + if (mb_substr($uv[$i], 0, 6) == 'value:') { + $uv[$i] = Core::arrayRead( + mb_substr($uv[$i], 6), + $GLOBALS['PMA_Config']->base_settings + ); + } + } + } + $validators[$field] = isset($validators[$field]) + ? array_merge((array)$validators[$field], $uv_list) + : $uv_list; + } + return $validators; + } + + /** + * Runs validation $validator_id on values $values and returns error list. + * + * Return values: + * o array, keys - field path or formset id, values - array of errors + * when $isPostSource is true values is an empty array to allow for error list + * cleanup in HTML document + * o false - when no validators match name(s) given by $validator_id + * + * @param ConfigFile $cf Config file instance + * @param string|array $validator_id ID of validator(s) to run + * @param array &$values Values to validate + * @param bool $isPostSource tells whether $values are directly from + * POST request + * + * @return bool|array + */ + public static function validate( + ConfigFile $cf, $validator_id, array &$values, $isPostSource + ) { + // find validators + $validator_id = (array) $validator_id; + $validators = static::getValidators($cf); + $vids = array(); + foreach ($validator_id as &$vid) { + $vid = $cf->getCanonicalPath($vid); + if (isset($validators[$vid])) { + $vids[] = $vid; + } + } + if (empty($vids)) { + return false; + } + + // create argument list with canonical paths and remember path mapping + $arguments = array(); + $key_map = array(); + foreach ($values as $k => $v) { + $k2 = $isPostSource ? str_replace('-', '/', $k) : $k; + $k2 = mb_strpos($k2, '/') + ? $cf->getCanonicalPath($k2) + : $k2; + $key_map[$k2] = $k; + $arguments[$k2] = $v; + } + + // validate + $result = array(); + foreach ($vids as $vid) { + // call appropriate validation functions + foreach ((array)$validators[$vid] as $validator) { + $vdef = (array) $validator; + $vname = array_shift($vdef); + $vname = 'PhpMyAdmin\Config\Validator::' . $vname; + $args = array_merge(array($vid, &$arguments), $vdef); + $r = call_user_func_array($vname, $args); + + // merge results + if (!is_array($r)) { + continue; + } + + foreach ($r as $key => $error_list) { + // skip empty values if $isPostSource is false + if (! $isPostSource && empty($error_list)) { + continue; + } + if (! isset($result[$key])) { + $result[$key] = array(); + } + $result[$key] = array_merge( + $result[$key], (array)$error_list + ); + } + } + } + + // restore original paths + $new_result = array(); + foreach ($result as $k => $v) { + $k2 = isset($key_map[$k]) ? $key_map[$k] : $k; + if (is_array($v)) { + $new_result[$k2] = array_map('htmlspecialchars', $v); + } else { + $new_result[$k2] = htmlspecialchars($v); + } + } + return empty($new_result) ? true : $new_result; + } + + /** + * Test database connection + * + * @param string $host host name + * @param string $port tcp port to use + * @param string $socket socket to use + * @param string $user username to use + * @param string $pass password to use + * @param string $error_key key to use in return array + * + * @return bool|array + */ + public static function testDBConnection( + $host, + $port, + $socket, + $user, + $pass = null, + $error_key = 'Server' + ) { + if ($GLOBALS['cfg']['DBG']['demo']) { + // Connection test disabled on the demo server! + return true; + } + + $error = null; + $host = Core::sanitizeMySQLHost($host); + + if (function_exists('error_clear_last')) { + /* PHP 7 only code */ + error_clear_last(); + } + + if (DatabaseInterface::checkDbExtension('mysqli')) { + $socket = empty($socket) ? null : $socket; + $port = empty($port) ? null : $port; + $extension = 'mysqli'; + } else { + $socket = empty($socket) ? null : ':' . ($socket[0] == '/' ? '' : '/') . $socket; + $port = empty($port) ? null : ':' . $port; + $extension = 'mysql'; + } + + if ($extension == 'mysql') { + $conn = @mysql_connect($host . $port . $socket, $user, $pass); + if (! $conn) { + $error = __('Could not connect to the database server!'); + } else { + mysql_close($conn); + } + } else { + $conn = @mysqli_connect($host, $user, $pass, null, $port, $socket); + if (! $conn) { + $error = __('Could not connect to the database server!'); + } else { + mysqli_close($conn); + } + } + if (! is_null($error)) { + $error .= ' - ' . error_get_last(); + } + return is_null($error) ? true : array($error_key => $error); + } + + /** + * Validate server config + * + * @param string $path path to config, not used + * keep this parameter since the method is invoked using + * reflection along with other similar methods + * @param array $values config values + * + * @return array + */ + public static function validateServer($path, array $values) + { + $result = array( + 'Server' => '', + 'Servers/1/user' => '', + 'Servers/1/SignonSession' => '', + 'Servers/1/SignonURL' => '' + ); + $error = false; + if (empty($values['Servers/1/auth_type'])) { + $values['Servers/1/auth_type'] = ''; + $result['Servers/1/auth_type'] = __('Invalid authentication type!'); + $error = true; + } + if ($values['Servers/1/auth_type'] == 'config' + && empty($values['Servers/1/user']) + ) { + $result['Servers/1/user'] = __( + 'Empty username while using [kbd]config[/kbd] authentication method!' + ); + $error = true; + } + if ($values['Servers/1/auth_type'] == 'signon' + && empty($values['Servers/1/SignonSession']) + ) { + $result['Servers/1/SignonSession'] = __( + 'Empty signon session name ' + . 'while using [kbd]signon[/kbd] authentication method!' + ); + $error = true; + } + if ($values['Servers/1/auth_type'] == 'signon' + && empty($values['Servers/1/SignonURL']) + ) { + $result['Servers/1/SignonURL'] = __( + 'Empty signon URL while using [kbd]signon[/kbd] authentication ' + . 'method!' + ); + $error = true; + } + + if (! $error && $values['Servers/1/auth_type'] == 'config') { + $password = ''; + if (! empty($values['Servers/1/password'])) { + $password = $values['Servers/1/password']; + } + $test = static::testDBConnection( + empty($values['Servers/1/host']) ? '' : $values['Servers/1/host'], + empty($values['Servers/1/port']) ? '' : $values['Servers/1/port'], + empty($values['Servers/1/socket']) ? '' : $values['Servers/1/socket'], + empty($values['Servers/1/user']) ? '' : $values['Servers/1/user'], + $password, + 'Server' + ); + + if ($test !== true) { + $result = array_merge($result, $test); + } + } + return $result; + } + + /** + * Validate pmadb config + * + * @param string $path path to config, not used + * keep this parameter since the method is invoked using + * reflection along with other similar methods + * @param array $values config values + * + * @return array + */ + public static function validatePMAStorage($path, array $values) + { + $result = array( + 'Server_pmadb' => '', + 'Servers/1/controluser' => '', + 'Servers/1/controlpass' => '' + ); + $error = false; + + if (empty($values['Servers/1/pmadb'])) { + return $result; + } + + $result = array(); + if (empty($values['Servers/1/controluser'])) { + $result['Servers/1/controluser'] = __( + 'Empty phpMyAdmin control user while using phpMyAdmin configuration ' + . 'storage!' + ); + $error = true; + } + if (empty($values['Servers/1/controlpass'])) { + $result['Servers/1/controlpass'] = __( + 'Empty phpMyAdmin control user password while using phpMyAdmin ' + . 'configuration storage!' + ); + $error = true; + } + if (! $error) { + $test = static::testDBConnection( + empty($values['Servers/1/host']) ? '' : $values['Servers/1/host'], + empty($values['Servers/1/port']) ? '' : $values['Servers/1/port'], + empty($values['Servers/1/socket']) ? '' : $values['Servers/1/socket'], + empty($values['Servers/1/controluser']) ? '' : $values['Servers/1/controluser'], + empty($values['Servers/1/controlpass']) ? '' : $values['Servers/1/controlpass'], + 'Server_pmadb' + ); + if ($test !== true) { + $result = array_merge($result, $test); + } + } + return $result; + } + + + /** + * Validates regular expression + * + * @param string $path path to config + * @param array $values config values + * + * @return array + */ + public static function validateRegex($path, array $values) + { + $result = array($path => ''); + + if (empty($values[$path])) { + return $result; + } + + if (function_exists('error_clear_last')) { + /* PHP 7 only code */ + error_clear_last(); + $last_error = null; + } else { + // As fallback we trigger another error to ensure + // that preg error will be different + @strpos(); + $last_error = error_get_last(); + } + + $matches = array(); + // in libraries/ListDatabase.php _checkHideDatabase(), + // a '/' is used as the delimiter for hide_db + @preg_match('/' . Util::requestString($values[$path]) . '/', '', $matches); + + $current_error = error_get_last(); + + if ($current_error !== $last_error) { + $error = preg_replace('/^preg_match\(\): /', '', $current_error['message']); + return array($path => $error); + } + + return $result; + } + + /** + * Validates TrustedProxies field + * + * @param string $path path to config + * @param array $values config values + * + * @return array + */ + public static function validateTrustedProxies($path, array $values) + { + $result = array($path => array()); + + if (empty($values[$path])) { + return $result; + } + + if (is_array($values[$path]) || is_object($values[$path])) { + // value already processed by FormDisplay::save + $lines = array(); + foreach ($values[$path] as $ip => $v) { + $v = Util::requestString($v); + $lines[] = preg_match('/^-\d+$/', $ip) + ? $v + : $ip . ': ' . $v; + } + } else { + // AJAX validation + $lines = explode("\n", $values[$path]); + } + foreach ($lines as $line) { + $line = trim($line); + $matches = array(); + // we catch anything that may (or may not) be an IP + if (!preg_match("/^(.+):(?:[ ]?)\\w+$/", $line, $matches)) { + $result[$path][] = __('Incorrect value:') . ' ' + . htmlspecialchars($line); + continue; + } + // now let's check whether we really have an IP address + if (filter_var($matches[1], FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) === false + && filter_var($matches[1], FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) === false + ) { + $ip = htmlspecialchars(trim($matches[1])); + $result[$path][] = sprintf(__('Incorrect IP address: %s'), $ip); + continue; + } + } + + return $result; + } + + /** + * Tests integer value + * + * @param string $path path to config + * @param array $values config values + * @param bool $allow_neg allow negative values + * @param bool $allow_zero allow zero + * @param int $max_value max allowed value + * @param string $error_string error message string + * + * @return string empty string if test is successful + */ + public static function validateNumber( + $path, + array $values, + $allow_neg, + $allow_zero, + $max_value, + $error_string + ) { + if (empty($values[$path])) { + return ''; + } + + $value = Util::requestString($values[$path]); + + if (intval($value) != $value + || (! $allow_neg && $value < 0) + || (! $allow_zero && $value == 0) + || $value > $max_value + ) { + return $error_string; + } + + return ''; + } + + /** + * Validates port number + * + * @param string $path path to config + * @param array $values config values + * + * @return array + */ + public static function validatePortNumber($path, array $values) + { + return array( + $path => static::validateNumber( + $path, + $values, + false, + false, + 65535, + __('Not a valid port number!') + ) + ); + } + + /** + * Validates positive number + * + * @param string $path path to config + * @param array $values config values + * + * @return array + */ + public static function validatePositiveNumber($path, array $values) + { + return array( + $path => static::validateNumber( + $path, + $values, + false, + false, + PHP_INT_MAX, + __('Not a positive number!') + ) + ); + } + + /** + * Validates non-negative number + * + * @param string $path path to config + * @param array $values config values + * + * @return array + */ + public static function validateNonNegativeNumber($path, array $values) + { + return array( + $path => static::validateNumber( + $path, + $values, + false, + true, + PHP_INT_MAX, + __('Not a non-negative number!') + ) + ); + } + + /** + * Validates value according to given regular expression + * Pattern and modifiers must be a valid for PCRE and JavaScript RegExp + * + * @param string $path path to config + * @param array $values config values + * @param string $regex regular expression to match + * + * @return array + */ + public static function validateByRegex($path, array $values, $regex) + { + if (!isset($values[$path])) { + return ''; + } + $result = preg_match($regex, Util::requestString($values[$path])); + return array($path => ($result ? '' : __('Incorrect value!'))); + } + + /** + * Validates upper bound for numeric inputs + * + * @param string $path path to config + * @param array $values config values + * @param int $max_value maximal allowed value + * + * @return array + */ + public static function validateUpperBound($path, array $values, $max_value) + { + $result = $values[$path] <= $max_value; + return array($path => ($result ? '' + : sprintf(__('Value must be less than or equal to %s!'), $max_value))); + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Console.php b/php/apps/phpmyadmin49/html/libraries/classes/Console.php new file mode 100644 index 00000000..565635b9 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Console.php @@ -0,0 +1,152 @@ +_isEnabled = true; + $this->relation = new Relation(); + } + + /** + * Set the ajax flag to indicate whether + * we are servicing an ajax request + * + * @param bool $isAjax Whether we are servicing an ajax request + * + * @return void + */ + public function setAjax($isAjax) + { + $this->_isAjax = (boolean) $isAjax; + } + + /** + * Disables the rendering of the footer + * + * @return void + */ + public function disable() + { + $this->_isEnabled = false; + } + + /** + * Renders the bookmark content + * + * @access public + * @return string + */ + public static function getBookmarkContent() + { + $cfgBookmark = Bookmark::getParams($GLOBALS['cfg']['Server']['user']); + if ($cfgBookmark) { + $bookmarks = Bookmark::getList( + $GLOBALS['dbi'], + $GLOBALS['cfg']['Server']['user'] + ); + $count_bookmarks = count($bookmarks); + if ($count_bookmarks > 0) { + $welcomeMessage = sprintf( + _ngettext( + 'Showing %1$d bookmark (both private and shared)', + 'Showing %1$d bookmarks (both private and shared)', + $count_bookmarks + ), + $count_bookmarks + ); + } else { + $welcomeMessage = __('No bookmarks'); + } + unset($count_bookmarks, $private_message, $shared_message); + return Template::get('console/bookmark_content') + ->render( + array( + 'welcome_message' => $welcomeMessage, + 'bookmarks' => $bookmarks, + ) + ); + } + return ''; + } + + /** + * Returns the list of JS scripts required by console + * + * @return array list of scripts + */ + public function getScripts() + { + return array('console.js'); + } + + /** + * Renders the console + * + * @access public + * @return string + */ + public function getDisplay() + { + if ((! $this->_isAjax) && $this->_isEnabled) { + $cfgBookmark = Bookmark::getParams( + $GLOBALS['cfg']['Server']['user'] + ); + + $image = Util::getImage('console', __('SQL Query Console')); + $_sql_history = $this->relation->getHistory( + $GLOBALS['cfg']['Server']['user'] + ); + $bookmarkContent = static::getBookmarkContent(); + + return Template::get('console/display')->render([ + 'cfg_bookmark' => $cfgBookmark, + 'image' => $image, + 'sql_history' => $_sql_history, + 'bookmark_content' => $bookmarkContent, + ]); + } + return ''; + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Controllers/Controller.php b/php/apps/phpmyadmin49/html/libraries/classes/Controllers/Controller.php new file mode 100644 index 00000000..24df284c --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Controllers/Controller.php @@ -0,0 +1,39 @@ +response = $response; + $this->dbi = $dbi; + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Controllers/Database/DatabaseStructureController.php b/php/apps/phpmyadmin49/html/libraries/classes/Controllers/Database/DatabaseStructureController.php new file mode 100644 index 00000000..81453754 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Controllers/Database/DatabaseStructureController.php @@ -0,0 +1,1115 @@ +relation = new Relation(); + } + + /** + * Retrieves databse information for further use + * + * @param string $sub_part Page part name + * + * @return void + */ + private function _getDbInfo($sub_part) + { + list( + $tables, + $num_tables, + $total_num_tables, + , + $is_show_stats, + $db_is_system_schema, + , + , + $pos + ) = Util::getDbInfo($this->db, $sub_part); + + $this->_tables = $tables; + $this->_num_tables = $num_tables; + $this->_pos = $pos; + $this->_db_is_system_schema = $db_is_system_schema; + $this->_total_num_tables = $total_num_tables; + $this->_is_show_stats = $is_show_stats; + } + + /** + * Index action + * + * @return void + */ + public function indexAction() + { + $response = Response::getInstance(); + + // Add/Remove favorite tables using Ajax request. + if ($response->isAjax() && !empty($_REQUEST['favorite_table'])) { + $this->addRemoveFavoriteTablesAction(); + return; + } + + // If there is an Ajax request for real row count of a table. + if ($response->isAjax() + && isset($_REQUEST['real_row_count']) + && $_REQUEST['real_row_count'] == true + ) { + $this->handleRealRowCountRequestAction(); + return; + } + + // Drops/deletes/etc. multiple tables if required + if ((! empty($_POST['submit_mult']) && isset($_POST['selected_tbl'])) + || isset($_POST['mult_btn']) + ) { + $this->multiSubmitAction(); + } + + $this->response->getHeader()->getScripts()->addFiles( + array( + 'db_structure.js', + 'tbl_change.js', + ) + ); + + // Gets the database structure + $this->_getDbInfo('_structure'); + + // Checks if there are any tables to be shown on current page. + // If there are no tables, the user is redirected to the last page + // having any. + if ($this->_total_num_tables > 0 && $this->_pos > $this->_total_num_tables) { + $uri = './db_structure.php' . Url::getCommonRaw(array( + 'db' => $this->db, + 'pos' => max(0, $this->_total_num_tables - $GLOBALS['cfg']['MaxTableList']), + 'reload' => 1 + )); + Core::sendHeaderLocation($uri); + } + + include_once 'libraries/replication.inc.php'; + + PageSettings::showGroup('DbStructure'); + + // 1. No tables + if ($this->_num_tables == 0) { + $this->response->addHTML( + Message::notice(__('No tables found in database.')) + ); + if (empty($this->_db_is_system_schema)) { + $this->response->addHTML(CreateTable::getHtml($this->db)); + } + return; + } + + // else + // 2. Shows table information + /** + * Displays the tables list + */ + $this->response->addHTML('
      '); + $_url_params = array( + 'pos' => $this->_pos, + 'db' => $this->db); + + // Add the sort options if they exists + if (isset($_REQUEST['sort'])) { + $_url_params['sort'] = $_REQUEST['sort']; + } + + if (isset($_REQUEST['sort_order'])) { + $_url_params['sort_order'] = $_REQUEST['sort_order']; + } + + $this->response->addHTML( + Util::getListNavigator( + $this->_total_num_tables, $this->_pos, $_url_params, + 'db_structure.php', 'frame_content', $GLOBALS['cfg']['MaxTableList'] + ) + ); + + $this->displayTableList(); + + // display again the table list navigator + $this->response->addHTML( + Util::getListNavigator( + $this->_total_num_tables, $this->_pos, $_url_params, + 'db_structure.php', 'frame_content', + $GLOBALS['cfg']['MaxTableList'] + ) + ); + + $this->response->addHTML('

      '); + + /** + * Work on the database + */ + /* DATABASE WORK */ + /* Printable view of a table */ + $this->response->addHTML( + Template::get('database/structure/print_view_data_dictionary_link') + ->render(array('url_query' => Url::getCommon( + array( + 'db' => $this->db, + 'goto' => 'db_structure.php', + ) + ))) + ); + + if (empty($this->_db_is_system_schema)) { + $this->response->addHTML(CreateTable::getHtml($this->db)); + } + } + + /** + * Add or remove favorite tables + * + * @return void + */ + public function addRemoveFavoriteTablesAction() + { + $fav_instance = RecentFavoriteTable::getInstance('favorite'); + if (isset($_REQUEST['favorite_tables'])) { + $favorite_tables = json_decode($_REQUEST['favorite_tables'], true); + } else { + $favorite_tables = array(); + } + // Required to keep each user's preferences separate. + $user = sha1($GLOBALS['cfg']['Server']['user']); + + // Request for Synchronization of favorite tables. + if (isset($_REQUEST['sync_favorite_tables'])) { + $cfgRelation = $this->relation->getRelationsParam(); + if ($cfgRelation['favoritework']) { + $this->synchronizeFavoriteTables($fav_instance, $user, $favorite_tables); + } + return; + } + $changes = true; + $titles = Util::buildActionTitles(); + $favorite_table = $_REQUEST['favorite_table']; + $already_favorite = $this->checkFavoriteTable($favorite_table); + + if (isset($_REQUEST['remove_favorite'])) { + if ($already_favorite) { + // If already in favorite list, remove it. + $fav_instance->remove($this->db, $favorite_table); + $already_favorite = false; // for favorite_anchor template + } + } elseif (isset($_REQUEST['add_favorite'])) { + if (!$already_favorite) { + $nbTables = count($fav_instance->getTables()); + if ($nbTables == $GLOBALS['cfg']['NumFavoriteTables']) { + $changes = false; + } else { + // Otherwise add to favorite list. + $fav_instance->add($this->db, $favorite_table); + $already_favorite = true; // for favorite_anchor template + } + } + } + + $favorite_tables[$user] = $fav_instance->getTables(); + $this->response->addJSON('changes', $changes); + if (!$changes) { + $this->response->addJSON( + 'message', + Template::get('components/error_message') + ->render( + array( + 'msg' => __("Favorite List is full!") + ) + ) + ); + return; + } + // Check if current table is already in favorite list. + $favParams = array('db' => $this->db, + 'ajax_request' => true, + 'favorite_table' => $favorite_table, + (($already_favorite ? 'remove' : 'add') . '_favorite') => true + ); + $this->response->addJSON( + array( + 'user' => $user, + 'favorite_tables' => json_encode($favorite_tables), + 'list' => $fav_instance->getHtmlList(), + 'anchor' => Template::get('database/structure/favorite_anchor') + ->render( + array( + 'table_name_hash' => md5($favorite_table), + 'db_table_name_hash' => md5($this->db . "." . $favorite_table), + 'fav_params' => $favParams, + 'already_favorite' => $already_favorite, + 'titles' => $titles, + ) + ) + ) + ); + } + + /** + * Handles request for real row count on database level view page. + * + * @return boolean true + */ + public function handleRealRowCountRequestAction() + { + $ajax_response = $this->response; + // If there is a request to update all table's row count. + if (!isset($_REQUEST['real_row_count_all'])) { + // Get the real row count for the table. + $real_row_count = $this->dbi + ->getTable($this->db, $_REQUEST['table']) + ->getRealRowCountTable(); + // Format the number. + $real_row_count = Util::formatNumber($real_row_count, 0); + $ajax_response->addJSON('real_row_count', $real_row_count); + return; + } + + // Array to store the results. + $real_row_count_all = array(); + // Iterate over each table and fetch real row count. + foreach ($this->_tables as $table) { + $row_count = $this->dbi + ->getTable($this->db, $table['TABLE_NAME']) + ->getRealRowCountTable(); + $real_row_count_all[] = array( + 'table' => $table['TABLE_NAME'], + 'row_count' => $row_count + ); + } + + $ajax_response->addJSON( + 'real_row_count_all', + json_encode($real_row_count_all) + ); + } + + /** + * Handles actions related to multiple tables + * + * @return void + */ + public function multiSubmitAction() + { + $action = 'db_structure.php'; + $err_url = 'db_structure.php' . Url::getCommon( + array('db' => $this->db) + ); + + // see bug #2794840; in this case, code path is: + // db_structure.php -> libraries/mult_submits.inc.php -> sql.php + // -> db_structure.php and if we got an error on the multi submit, + // we must display it here and not call again mult_submits.inc.php + if (! isset($_POST['error']) || false === $_POST['error']) { + include 'libraries/mult_submits.inc.php'; + } + if (empty($_POST['message'])) { + $_POST['message'] = Message::success(); + } + } + + /** + * Displays the list of tables + * + * @return void + */ + protected function displayTableList() + { + // filtering + $this->response->addHTML( + Template::get('filter')->render(['filter_value' => '']) + ); + + $i = $sum_entries = 0; + $overhead_check = false; + $create_time_all = ''; + $update_time_all = ''; + $check_time_all = ''; + $num_columns = $GLOBALS['cfg']['PropertiesNumColumns'] > 1 + ? ceil($this->_num_tables / $GLOBALS['cfg']['PropertiesNumColumns']) + 1 + : 0; + $row_count = 0; + $sum_size = 0; + $overhead_size = 0; + + $hidden_fields = array(); + $overall_approx_rows = false; + $structure_table_rows = []; + foreach ($this->_tables as $keyname => $current_table) { + // Get valid statistics whatever is the table type + + $drop_query = ''; + $drop_message = ''; + $overhead = ''; + $input_class = ['checkall']; + + $table_is_view = false; + // Sets parameters for links + $tbl_url_query = Url::getCommon( + array('db' => $this->db, 'table' => $current_table['TABLE_NAME']) + ); + // do not list the previous table's size info for a view + + list($current_table, $formatted_size, $unit, $formatted_overhead, + $overhead_unit, $overhead_size, $table_is_view, $sum_size) + = $this->getStuffForEngineTypeTable( + $current_table, $sum_size, $overhead_size + ); + + $curTable = $this->dbi + ->getTable($this->db, $current_table['TABLE_NAME']); + if (!$curTable->isMerge()) { + $sum_entries += $current_table['TABLE_ROWS']; + } + + if (isset($current_table['Collation'])) { + $collation = '' + . $current_table['Collation'] . ''; + } else { + $collation = '---'; + } + + if ($this->_is_show_stats) { + if ($formatted_overhead != '') { + $overhead = '' + . '' . $formatted_overhead . ' ' + . '' . $overhead_unit . '' + . '' . "\n"; + $overhead_check = true; + $input_class[] = 'tbl-overhead'; + } else { + $overhead = '-'; + } + } // end if + + if ($GLOBALS['cfg']['ShowDbStructureCharset']) { + if (isset($current_table['Collation'])) { + $charset = mb_substr($collation, 0, mb_strpos($collation, "_")); + } else { + $charset = ''; + } + } + + if ($GLOBALS['cfg']['ShowDbStructureCreation']) { + $create_time = isset($current_table['Create_time']) + ? $current_table['Create_time'] : ''; + if ($create_time + && (!$create_time_all + || $create_time < $create_time_all) + ) { + $create_time_all = $create_time; + } + } + + if ($GLOBALS['cfg']['ShowDbStructureLastUpdate']) { + $update_time = isset($current_table['Update_time']) + ? $current_table['Update_time'] : ''; + if ($update_time + && (!$update_time_all + || $update_time < $update_time_all) + ) { + $update_time_all = $update_time; + } + } + + if ($GLOBALS['cfg']['ShowDbStructureLastCheck']) { + $check_time = isset($current_table['Check_time']) + ? $current_table['Check_time'] : ''; + if ($check_time + && (!$check_time_all + || $check_time < $check_time_all) + ) { + $check_time_all = $check_time; + } + } + + $truename = (!empty($tooltip_truename) + && isset($tooltip_truename[$current_table['TABLE_NAME']])) + ? $tooltip_truename[$current_table['TABLE_NAME']] + : $current_table['TABLE_NAME']; + + $i++; + + $row_count++; + if ($table_is_view) { + $hidden_fields[] = ''; + } + + /* + * Always activate links for Browse, Search and Empty, even if + * the icons are greyed, because + * 1. for views, we don't know the number of rows at this point + * 2. for tables, another source could have populated them since the + * page was generated + * + * I could have used the PHP ternary conditional operator but I find + * the code easier to read without this operator. + */ + $may_have_rows = $current_table['TABLE_ROWS'] > 0 || $table_is_view; + $titles = Util::buildActionTitles(); + + $browse_table = Template::get('database/structure/browse_table') + ->render( + array( + 'tbl_url_query' => $tbl_url_query, + 'title' => $may_have_rows ? $titles['Browse'] + : $titles['NoBrowse'], + ) + ); + + $search_table = Template::get('database/structure/search_table') + ->render( + array( + 'tbl_url_query' => $tbl_url_query, + 'title' => $may_have_rows ? $titles['Search'] + : $titles['NoSearch'], + ) + ); + + $browse_table_label = Template::get( + 'database/structure/browse_table_label' + ) + ->render( + array( + 'tbl_url_query' => $tbl_url_query, + 'title' => htmlspecialchars( + $current_table['TABLE_COMMENT'] + ), + 'truename' => $truename, + ) + ); + + $empty_table = ''; + if (!$this->_db_is_system_schema) { + $empty_table = ''; + if (!$table_is_view) { + $empty_table = Template::get('database/structure/empty_table') + ->render( + array( + 'tbl_url_query' => $tbl_url_query, + 'sql_query' => urlencode( + 'TRUNCATE ' . Util::backquote( + $current_table['TABLE_NAME'] + ) + ), + 'message_to_show' => urlencode( + sprintf( + __('Table %s has been emptied.'), + htmlspecialchars( + $current_table['TABLE_NAME'] + ) + ) + ), + 'title' => $may_have_rows ? $titles['Empty'] + : $titles['NoEmpty'], + ) + ); + } + $drop_query = sprintf( + 'DROP %s %s', + ($table_is_view || $current_table['ENGINE'] == null) ? 'VIEW' + : 'TABLE', + Util::backquote( + $current_table['TABLE_NAME'] + ) + ); + $drop_message = sprintf( + (($table_is_view || $current_table['ENGINE'] == null) + ? __('View %s has been dropped.') + : __('Table %s has been dropped.')), + str_replace( + ' ', ' ', + htmlspecialchars($current_table['TABLE_NAME']) + ) + ); + } + + if ($num_columns > 0 + && $this->_num_tables > $num_columns + && ($row_count % $num_columns) == 0 + ) { + $row_count = 1; + + $this->response->addHTML( + Template::get('database/structure/table_header')->render([ + 'db' => $this->db, + 'db_is_system_schema' => $this->_db_is_system_schema, + 'replication' => $GLOBALS['replication_info']['slave']['status'], + 'properties_num_columns' => $GLOBALS['cfg']['PropertiesNumColumns'], + 'is_show_stats' => $GLOBALS['is_show_stats'], + 'show_charset' => $GLOBALS['cfg']['ShowDbStructureCharset'], + 'show_comment' => $GLOBALS['cfg']['ShowDbStructureComment'], + 'show_creation' => $GLOBALS['cfg']['ShowDbStructureCreation'], + 'show_last_update' => $GLOBALS['cfg']['ShowDbStructureLastUpdate'], + 'show_last_check' => $GLOBALS['cfg']['ShowDbStructureLastCheck'], + 'num_favorite_tables' => $GLOBALS['cfg']['NumFavoriteTables'], + 'structure_table_rows' => $structure_table_rows, + ]) + ); + $structure_table_rows = []; + } + + list($approx_rows, $show_superscript) = $this->isRowCountApproximated( + $current_table, $table_is_view + ); + + list($do, $ignored) = $this->getReplicationStatus($truename); + + $structure_table_rows[] = [ + 'db' => $this->db, + 'curr' => $i, + 'input_class' => implode(' ', $input_class), + 'table_is_view' => $table_is_view, + 'current_table' => $current_table, + 'browse_table_label' => $browse_table_label, + 'tracking_icon' => $this->getTrackingIcon($truename), + 'server_slave_status' => $GLOBALS['replication_info']['slave']['status'], + 'browse_table' => $browse_table, + 'tbl_url_query' => $tbl_url_query, + 'search_table' => $search_table, + 'db_is_system_schema' => $this->_db_is_system_schema, + 'titles' => $titles, + 'empty_table' => $empty_table, + 'drop_query' => $drop_query, + 'drop_message' => $drop_message, + 'collation' => $collation, + 'formatted_size' => $formatted_size, + 'unit' => $unit, + 'overhead' => $overhead, + 'create_time' => isset($create_time) + ? $create_time : '', + 'update_time' => isset($update_time) + ? $update_time : '', + 'check_time' => isset($check_time) + ? $check_time : '', + 'charset' => isset($charset) + ? $charset : '', + 'is_show_stats' => $this->_is_show_stats, + 'ignored' => $ignored, + 'do' => $do, + 'approx_rows' => $approx_rows, + 'show_superscript' => $show_superscript, + 'already_favorite' => $this->checkFavoriteTable( + $current_table['TABLE_NAME'] + ), + 'num_favorite_tables' => $GLOBALS['cfg']['NumFavoriteTables'], + 'properties_num_columns' => $GLOBALS['cfg']['PropertiesNumColumns'], + 'limit_chars' => $GLOBALS['cfg']['LimitChars'], + 'show_charset' => $GLOBALS['cfg']['ShowDbStructureCharset'], + 'show_comment' => $GLOBALS['cfg']['ShowDbStructureComment'], + 'show_creation' => $GLOBALS['cfg']['ShowDbStructureCreation'], + 'show_last_update' => $GLOBALS['cfg']['ShowDbStructureLastUpdate'], + 'show_last_check' => $GLOBALS['cfg']['ShowDbStructureLastCheck'], + ]; + + $overall_approx_rows = $overall_approx_rows || $approx_rows; + } // end foreach + + $db_collation = $this->dbi->getDbCollation($this->db); + $db_charset = mb_substr($db_collation, 0, mb_strpos($db_collation, "_")); + + // table form + $this->response->addHTML( + Template::get('database/structure/table_header')->render([ + 'db' => $this->db, + 'db_is_system_schema' => $this->_db_is_system_schema, + 'replication' => $GLOBALS['replication_info']['slave']['status'], + 'properties_num_columns' => $GLOBALS['cfg']['PropertiesNumColumns'], + 'is_show_stats' => $GLOBALS['is_show_stats'], + 'show_charset' => $GLOBALS['cfg']['ShowDbStructureCharset'], + 'show_comment' => $GLOBALS['cfg']['ShowDbStructureComment'], + 'show_creation' => $GLOBALS['cfg']['ShowDbStructureCreation'], + 'show_last_update' => $GLOBALS['cfg']['ShowDbStructureLastUpdate'], + 'show_last_check' => $GLOBALS['cfg']['ShowDbStructureLastCheck'], + 'num_favorite_tables' => $GLOBALS['cfg']['NumFavoriteTables'], + 'structure_table_rows' => $structure_table_rows, + 'body_for_table_summary' => [ + 'num_tables' => $this->_num_tables, + 'server_slave_status' => $GLOBALS['replication_info']['slave']['status'], + 'db_is_system_schema' => $this->_db_is_system_schema, + 'sum_entries' => $sum_entries, + 'db_collation' => $db_collation, + 'is_show_stats' => $this->_is_show_stats, + 'db_charset' => $db_charset, + 'sum_size' => $sum_size, + 'overhead_size' => $overhead_size, + 'create_time_all' => $create_time_all, + 'update_time_all' => $update_time_all, + 'check_time_all' => $check_time_all, + 'approx_rows' => $overall_approx_rows, + 'num_favorite_tables' => $GLOBALS['cfg']['NumFavoriteTables'], + 'db' => $GLOBALS['db'], + 'properties_num_columns' => $GLOBALS['cfg']['PropertiesNumColumns'], + 'dbi' => $GLOBALS['dbi'], + 'show_charset' => $GLOBALS['cfg']['ShowDbStructureCharset'], + 'show_comment' => $GLOBALS['cfg']['ShowDbStructureComment'], + 'show_creation' => $GLOBALS['cfg']['ShowDbStructureCreation'], + 'show_last_update' => $GLOBALS['cfg']['ShowDbStructureLastUpdate'], + 'show_last_check' => $GLOBALS['cfg']['ShowDbStructureLastCheck'], + ], + 'check_all_tables' => [ + 'pma_theme_image' => $GLOBALS['pmaThemeImage'], + 'text_dir' => $GLOBALS['text_dir'], + 'overhead_check' => $overhead_check, + 'db_is_system_schema' => $this->_db_is_system_schema, + 'hidden_fields' => $hidden_fields, + 'disable_multi_table' => $GLOBALS['cfg']['DisableMultiTableMaintenance'], + 'central_columns_work' => $GLOBALS['cfgRelation']['centralcolumnswork'], + ], + ]) + ); + } + + /** + * Returns the tracking icon if the table is tracked + * + * @param string $table table name + * + * @return string HTML for tracking icon + */ + protected function getTrackingIcon($table) + { + $tracking_icon = ''; + if (Tracker::isActive()) { + $is_tracked = Tracker::isTracked($this->db, $table); + if ($is_tracked + || Tracker::getVersion($this->db, $table) > 0 + ) { + $tracking_icon = Template::get( + 'database/structure/tracking_icon' + ) + ->render( + array( + 'db' => $this->db, + 'table' => $table, + 'is_tracked' => $is_tracked, + ) + ); + } + } + return $tracking_icon; + } + + /** + * Returns whether the row count is approximated + * + * @param array $current_table array containing details about the table + * @param boolean $table_is_view whether the table is a view + * + * @return array + */ + protected function isRowCountApproximated(array $current_table, $table_is_view) + { + $approx_rows = false; + $show_superscript = ''; + + // there is a null value in the ENGINE + // - when the table needs to be repaired, or + // - when it's a view + // so ensure that we'll display "in use" below for a table + // that needs to be repaired + if (isset($current_table['TABLE_ROWS']) + && ($current_table['ENGINE'] != null || $table_is_view) + ) { + // InnoDB/TokuDB table: we did not get an accurate row count + $approx_rows = !$table_is_view + && in_array($current_table['ENGINE'], array('InnoDB', 'TokuDB')) + && !$current_table['COUNTED']; + + if ($table_is_view + && $current_table['TABLE_ROWS'] >= $GLOBALS['cfg']['MaxExactCountViews'] + ) { + $approx_rows = true; + $show_superscript = Util::showHint( + Sanitize::sanitize( + sprintf( + __( + 'This view has at least this number of ' + . 'rows. Please refer to %sdocumentation%s.' + ), + '[doc@cfg_MaxExactCountViews]', '[/doc]' + ) + ) + ); + } + } + + return array($approx_rows, $show_superscript); + } + + /** + * Returns the replication status of the table. + * + * @param string $table table name + * + * @return array + */ + protected function getReplicationStatus($table) + { + $do = $ignored = false; + if ($GLOBALS['replication_info']['slave']['status']) { + + $nbServSlaveDoDb = count( + $GLOBALS['replication_info']['slave']['Do_DB'] + ); + $nbServSlaveIgnoreDb = count( + $GLOBALS['replication_info']['slave']['Ignore_DB'] + ); + $searchDoDBInTruename = array_search( + $table, $GLOBALS['replication_info']['slave']['Do_DB'] + ); + $searchDoDBInDB = array_search( + $this->db, $GLOBALS['replication_info']['slave']['Do_DB'] + ); + + $do = strlen($searchDoDBInTruename) > 0 + || strlen($searchDoDBInDB) > 0 + || ($nbServSlaveDoDb == 0 && $nbServSlaveIgnoreDb == 0) + || $this->hasTable( + $GLOBALS['replication_info']['slave']['Wild_Do_Table'], + $table + ); + + $searchDb = array_search( + $this->db, + $GLOBALS['replication_info']['slave']['Ignore_DB'] + ); + $searchTable = array_search( + $table, + $GLOBALS['replication_info']['slave']['Ignore_Table'] + ); + $ignored = strlen($searchTable) > 0 + || strlen($searchDb) > 0 + || $this->hasTable( + $GLOBALS['replication_info']['slave']['Wild_Ignore_Table'], + $table + ); + } + + return array($do, $ignored); + } + + /** + * Synchronize favorite tables + * + * + * @param RecentFavoriteTable $fav_instance Instance of this class + * @param string $user The user hash + * @param array $favorite_tables Existing favorites + * + * @return void + */ + protected function synchronizeFavoriteTables( + $fav_instance, + $user, + array $favorite_tables + ) { + $fav_instance_tables = $fav_instance->getTables(); + + if (empty($fav_instance_tables) + && isset($favorite_tables[$user]) + ) { + foreach ($favorite_tables[$user] as $key => $value) { + $fav_instance->add($value['db'], $value['table']); + } + } + $favorite_tables[$user] = $fav_instance->getTables(); + + $this->response->addJSON( + array( + 'favorite_tables' => json_encode($favorite_tables), + 'list' => $fav_instance->getHtmlList() + ) + ); + $server_id = $GLOBALS['server']; + // Set flag when localStorage and pmadb(if present) are in sync. + $_SESSION['tmpval']['favorites_synced'][$server_id] = true; + } + + /** + * Function to check if a table is already in favorite list. + * + * @param string $current_table current table + * + * @return true|false + */ + protected function checkFavoriteTable($current_table) + { + // ensure $_SESSION['tmpval']['favorite_tables'] is initialized + RecentFavoriteTable::getInstance('favorite'); + foreach ( + $_SESSION['tmpval']['favorite_tables'][$GLOBALS['server']] as $value + ) { + if ($value['db'] == $this->db && $value['table'] == $current_table) { + return true; + } + } + return false; + } + + /** + * Find table with truename + * + * @param array $db DB to look into + * @param string $truename Table name + * + * @return bool + */ + protected function hasTable(array $db, $truename) + { + foreach ($db as $db_table) { + if ($this->db == Replication::extractDbOrTable($db_table) + && preg_match( + "@^" . + preg_quote(mb_substr(Replication::extractDbOrTable($db_table, 'table'), 0, -1)) . "@", + $truename + ) + ) { + return true; + } + } + return false; + } + + /** + * Get the value set for ENGINE table, + * + * @param array $current_table current table + * @param integer $sum_size total table size + * @param integer $overhead_size overhead size + * + * @return array + * @internal param bool $table_is_view whether table is view or not + */ + protected function getStuffForEngineTypeTable( + array $current_table, $sum_size, $overhead_size + ) { + $formatted_size = '-'; + $unit = ''; + $formatted_overhead = ''; + $overhead_unit = ''; + $table_is_view = false; + + switch ( $current_table['ENGINE']) { + // MyISAM, ISAM or Heap table: Row count, data size and index size + // are accurate; data size is accurate for ARCHIVE + case 'MyISAM' : + case 'ISAM' : + case 'HEAP' : + case 'MEMORY' : + case 'ARCHIVE' : + case 'Aria' : + case 'Maria' : + list($current_table, $formatted_size, $unit, $formatted_overhead, + $overhead_unit, $overhead_size, $sum_size) + = $this->getValuesForAriaTable( + $current_table, $sum_size, $overhead_size, + $formatted_size, $unit, $formatted_overhead, $overhead_unit + ); + break; + case 'InnoDB' : + case 'PBMS' : + case 'TokuDB' : + // InnoDB table: Row count is not accurate but data and index sizes are. + // PBMS table in Drizzle: TABLE_ROWS is taken from table cache, + // so it may be unavailable + list($current_table, $formatted_size, $unit, $sum_size) + = $this->getValuesForInnodbTable( + $current_table, $sum_size + ); + break; + // Mysql 5.0.x (and lower) uses MRG_MyISAM + // and MySQL 5.1.x (and higher) uses MRG_MYISAM + // Both are aliases for MERGE + case 'MRG_MyISAM' : + case 'MRG_MYISAM' : + case 'MERGE' : + case 'BerkeleyDB' : + // Merge or BerkleyDB table: Only row count is accurate. + if ($this->_is_show_stats) { + $formatted_size = ' - '; + $unit = ''; + } + break; + // for a view, the ENGINE is sometimes reported as null, + // or on some servers it's reported as "SYSTEM VIEW" + case null : + case 'SYSTEM VIEW' : + // possibly a view, do nothing + break; + default : + // Unknown table type. + if ($this->_is_show_stats) { + $formatted_size = __('unknown'); + $unit = ''; + } + } // end switch + + if ($current_table['TABLE_TYPE'] == 'VIEW' + || $current_table['TABLE_TYPE'] == 'SYSTEM VIEW' + ) { + // countRecords() takes care of $cfg['MaxExactCountViews'] + $current_table['TABLE_ROWS'] = $this->dbi + ->getTable($this->db, $current_table['TABLE_NAME']) + ->countRecords(true); + $table_is_view = true; + } + + return array($current_table, $formatted_size, $unit, $formatted_overhead, + $overhead_unit, $overhead_size, $table_is_view, $sum_size + ); + } + + /** + * Get values for ARIA/MARIA tables + * + * @param array $current_table current table + * @param integer $sum_size sum size + * @param integer $overhead_size overhead size + * @param integer $formatted_size formatted size + * @param string $unit unit + * @param integer $formatted_overhead overhead formatted + * @param string $overhead_unit overhead unit + * + * @return array + */ + protected function getValuesForAriaTable( + array $current_table, $sum_size, $overhead_size, $formatted_size, $unit, + $formatted_overhead, $overhead_unit + ) { + if ($this->_db_is_system_schema) { + $current_table['Rows'] = $this->dbi + ->getTable($this->db, $current_table['Name']) + ->countRecords(); + } + + if ($this->_is_show_stats) { + $tblsize = $current_table['Data_length'] + + $current_table['Index_length']; + $sum_size += $tblsize; + list($formatted_size, $unit) = Util::formatByteDown( + $tblsize, 3, ($tblsize > 0) ? 1 : 0 + ); + if (isset($current_table['Data_free']) + && $current_table['Data_free'] > 0 + ) { + list($formatted_overhead, $overhead_unit) + = Util::formatByteDown( + $current_table['Data_free'], 3, + (($current_table['Data_free'] > 0) ? 1 : 0) + ); + $overhead_size += $current_table['Data_free']; + } + } + return array($current_table, $formatted_size, $unit, $formatted_overhead, + $overhead_unit, $overhead_size, $sum_size + ); + } + + /** + * Get values for InnoDB table + * + * @param array $current_table current table + * @param integer $sum_size sum size + * + * @return array + */ + protected function getValuesForInnodbTable( + array $current_table, $sum_size + ) { + $formatted_size = $unit = ''; + + if ((in_array($current_table['ENGINE'], array('InnoDB', 'TokuDB')) + && $current_table['TABLE_ROWS'] < $GLOBALS['cfg']['MaxExactCount']) + || !isset($current_table['TABLE_ROWS']) + ) { + $current_table['COUNTED'] = true; + $current_table['TABLE_ROWS'] = $this->dbi + ->getTable($this->db, $current_table['TABLE_NAME']) + ->countRecords(true); + } else { + $current_table['COUNTED'] = false; + } + + if ($this->_is_show_stats) { + $tblsize = $current_table['Data_length'] + + $current_table['Index_length']; + $sum_size += $tblsize; + list($formatted_size, $unit) = Util::formatByteDown( + $tblsize, 3, (($tblsize > 0) ? 1 : 0) + ); + } + + return array($current_table, $formatted_size, $unit, $sum_size); + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Controllers/DatabaseController.php b/php/apps/phpmyadmin49/html/libraries/classes/Controllers/DatabaseController.php new file mode 100644 index 00000000..af0ffc10 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Controllers/DatabaseController.php @@ -0,0 +1,30 @@ +db = $db; + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Controllers/Server/ServerBinlogController.php b/php/apps/phpmyadmin49/html/libraries/classes/Controllers/Server/ServerBinlogController.php new file mode 100644 index 00000000..57983ec3 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Controllers/Server/ServerBinlogController.php @@ -0,0 +1,263 @@ +binary_logs = $this->dbi->fetchResult( + 'SHOW MASTER LOGS', + 'Log_name', + null, + DatabaseInterface::CONNECT_USER, + DatabaseInterface::QUERY_STORE + ); + } + + /** + * Index action + * + * @return void + */ + public function indexAction() + { + /** + * Does the common work + */ + include_once 'libraries/server_common.inc.php'; + + $url_params = array(); + if (! isset($_POST['log']) + || ! array_key_exists($_POST['log'], $this->binary_logs) + ) { + $_POST['log'] = ''; + } else { + $url_params['log'] = $_POST['log']; + } + + if (!empty($_POST['dontlimitchars'])) { + $url_params['dontlimitchars'] = 1; + } + + $this->response->addHTML( + Template::get('server/sub_page_header')->render([ + 'type' => 'binlog', + ]) + ); + $this->response->addHTML($this->_getLogSelector($url_params)); + $this->response->addHTML($this->_getLogInfo($url_params)); + } + + /** + * Returns the html for log selector. + * + * @param array $url_params links parameters + * + * @return string + */ + private function _getLogSelector(array $url_params) + { + return Template::get('server/binlog/log_selector')->render( + array( + 'url_params' => $url_params, + 'binary_logs' => $this->binary_logs, + 'log' => $_POST['log'], + ) + ); + } + + /** + * Returns the html for binary log information. + * + * @param array $url_params links parameters + * + * @return string + */ + private function _getLogInfo(array $url_params) + { + /** + * Need to find the real end of rows? + */ + if (! isset($_POST['pos'])) { + $pos = 0; + } else { + /* We need this to be a integer */ + $pos = (int) $_POST['pos']; + } + + $sql_query = 'SHOW BINLOG EVENTS'; + if (! empty($_POST['log'])) { + $sql_query .= ' IN \'' . $_POST['log'] . '\''; + } + $sql_query .= ' LIMIT ' . $pos . ', ' . intval($GLOBALS['cfg']['MaxRows']); + + /** + * Sends the query + */ + $result = $this->dbi->query($sql_query); + + /** + * prepare some vars for displaying the result table + */ + // Gets the list of fields properties + if (isset($result) && $result) { + $num_rows = $this->dbi->numRows($result); + } else { + $num_rows = 0; + } + + if (empty($_POST['dontlimitchars'])) { + $dontlimitchars = false; + } else { + $dontlimitchars = true; + $url_params['dontlimitchars'] = 1; + } + + //html output + $html = Util::getMessage(Message::success(), $sql_query); + $html .= '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . ''; + + $html .= $this->_getAllLogItemInfo($result, $dontlimitchars); + + $html .= '' + . '
      '; + + $html .= $this->_getNavigationRow($url_params, $pos, $num_rows, $dontlimitchars); + + $html .= '
      ' . __('Log name') . '' . __('Position') . '' . __('Event type') . '' . __('Server ID') . '' . __('Original position') . '' . __('Information') . '
      '; + + return $html; + } + + /** + * Returns the html for Navigation Row. + * + * @param array $url_params Links parameters + * @param int $pos Position to display + * @param int $num_rows Number of results row + * @param bool $dontlimitchars Whether limit chars + * + * @return string + */ + private function _getNavigationRow(array $url_params, $pos, $num_rows, $dontlimitchars) + { + $html = ""; + // we do not know how much rows are in the binlog + // so we can just force 'NEXT' button + if ($pos > 0) { + $this_url_params = $url_params; + if ($pos > $GLOBALS['cfg']['MaxRows']) { + $this_url_params['pos'] = $pos - $GLOBALS['cfg']['MaxRows']; + } + + $html .= ''; + } else { + $html .= '>' . _pgettext('Previous page', 'Previous'); + } // end if... else... + $html .= ' < - '; + } + + $this_url_params = $url_params; + if ($pos > 0) { + $this_url_params['pos'] = $pos; + } + if ($dontlimitchars) { + unset($this_url_params['dontlimitchars']); + $tempTitle = __('Truncate Shown Queries'); + $tempImgMode = 'partial'; + } else { + $this_url_params['dontlimitchars'] = 1; + $tempTitle = __('Show Full Queries'); + $tempImgMode = 'full'; + } + $html .= '' + . '' . $tempTitle . ''; + + // we do not now how much rows are in the binlog + // so we can just force 'NEXT' button + if ($num_rows >= $GLOBALS['cfg']['MaxRows']) { + $this_url_params = $url_params; + $this_url_params['pos'] = $pos + $GLOBALS['cfg']['MaxRows']; + $html .= ' - '; + } else { + $html .= '>' . _pgettext('Next page', 'Next'); + } // end if... else... + $html .= ' > '; + } + + return $html; + } + + /** + * Returns the html for all binary log items. + * + * @param resource $result MySQL Query result + * @param bool $dontlimitchars Whether limit chars + * + * @return string + */ + private function _getAllLogItemInfo($result, $dontlimitchars) + { + $html = ""; + while ($value = $this->dbi->fetchAssoc($result)) { + $html .= Template::get('server/binlog/log_row')->render( + array( + 'value' => $value, + 'dontlimitchars' => $dontlimitchars, + ) + ); + } + return $html; + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Controllers/Server/ServerCollationsController.php b/php/apps/phpmyadmin49/html/libraries/classes/Controllers/Server/ServerCollationsController.php new file mode 100644 index 00000000..cead8ab1 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Controllers/Server/ServerCollationsController.php @@ -0,0 +1,76 @@ +response->addHTML( + Template::get('server/sub_page_header')->render([ + 'type' => 'collations', + ]) + ); + $this->response->addHTML( + $this->_getHtmlForCharsets( + Charsets::getMySQLCharsets($dbi, $disableIs), + Charsets::getMySQLCollations($dbi, $disableIs), + Charsets::getMySQLCharsetsDescriptions($dbi, $disableIs), + Charsets::getMySQLCollationsDefault($dbi, $disableIs) + ) + ); + } + + /** + * Returns the html for server Character Sets and Collations. + * + * @param array $mysqlCharsets Mysql Charsets list + * @param array $mysqlCollations Mysql Collations list + * @param array $mysqlCharsetsDesc Charsets descriptions + * @param array $mysqlDftCollations Default Collations list + * + * @return string + */ + function _getHtmlForCharsets(array $mysqlCharsets, array $mysqlCollations, + array $mysqlCharsetsDesc, array $mysqlDftCollations + ) { + return Template::get('server/collations/charsets')->render( + array( + 'mysql_charsets' => $mysqlCharsets, + 'mysql_collations' => $mysqlCollations, + 'mysql_charsets_desc' => $mysqlCharsetsDesc, + 'mysql_dft_collations' => $mysqlDftCollations, + ) + ); + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Controllers/Server/ServerDatabasesController.php b/php/apps/phpmyadmin49/html/libraries/classes/Controllers/Server/ServerDatabasesController.php new file mode 100644 index 00000000..d802d401 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Controllers/Server/ServerDatabasesController.php @@ -0,0 +1,470 @@ +isAjax() + && ($GLOBALS['dbi']->isSuperuser() || $GLOBALS['cfg']['AllowUserDropDatabase']) + ) { + $this->dropDatabasesAction(); + return; + } + + include_once 'libraries/replication.inc.php'; + + if (isset($_POST['new_db']) + && $response->isAjax() + ) { + $this->createDatabaseAction(); + return; + } + + include_once 'libraries/server_common.inc.php'; + + $header = $this->response->getHeader(); + $scripts = $header->getScripts(); + $scripts->addFile('server_databases.js'); + + $this->_setSortDetails(); + $this->_dbstats = ! empty($_POST['dbstats']); + $this->_pos = empty($_REQUEST['pos']) ? 0 : (int) $_REQUEST['pos']; + + /** + * Gets the databases list + */ + if ($GLOBALS['server'] > 0) { + $this->_databases = $this->dbi->getDatabasesFull( + null, $this->_dbstats, DatabaseInterface::CONNECT_USER, $this->_sort_by, + $this->_sort_order, $this->_pos, true + ); + $this->_database_count = count($GLOBALS['dblist']->databases); + } else { + $this->_database_count = 0; + } + + if ($this->_database_count > 0 && ! empty($this->_databases)) { + $databases = $this->_getHtmlForDatabases($replication_types); + } + + $this->response->addHTML(Template::get('server/databases/index')->render([ + 'show_create_db' => $GLOBALS['cfg']['ShowCreateDb'], + 'is_create_db_priv' => $GLOBALS['is_create_db_priv'], + 'dbstats' => $this->_dbstats, + 'db_to_create' => $GLOBALS['db_to_create'], + 'server_collation' => $GLOBALS['dbi']->getServerCollation(), + 'databases' => isset($databases) ? $databases : null, + 'dbi' => $GLOBALS['dbi'], + 'disable_is' => $GLOBALS['cfg']['Server']['DisableIS'], + ])); + } + + /** + * Handles creating a new database + * + * @return void + */ + public function createDatabaseAction() + { + // lower_case_table_names=1 `DB` becomes `db` + if ($GLOBALS['dbi']->getLowerCaseNames() === '1') { + $_POST['new_db'] = mb_strtolower( + $_POST['new_db'] + ); + } + /** + * Builds and executes the db creation sql query + */ + $sql_query = 'CREATE DATABASE ' . Util::backquote($_POST['new_db']); + if (! empty($_POST['db_collation'])) { + list($db_charset) = explode('_', $_POST['db_collation']); + $charsets = Charsets::getMySQLCharsets( + $GLOBALS['dbi'], + $GLOBALS['cfg']['Server']['DisableIS'] + ); + $collations = Charsets::getMySQLCollations( + $GLOBALS['dbi'], + $GLOBALS['cfg']['Server']['DisableIS'] + ); + if (in_array($db_charset, $charsets) + && in_array($_POST['db_collation'], $collations[$db_charset]) + ) { + $sql_query .= ' DEFAULT' + . Util::getCharsetQueryPart($_POST['db_collation']); + } + } + $sql_query .= ';'; + + $result = $GLOBALS['dbi']->tryQuery($sql_query); + + if (! $result) { + // avoid displaying the not-created db name in header or navi panel + $GLOBALS['db'] = ''; + + $message = Message::rawError($GLOBALS['dbi']->getError()); + $this->response->setRequestStatus(false); + $this->response->addJSON('message', $message); + } else { + $GLOBALS['db'] = $_POST['new_db']; + + $message = Message::success(__('Database %1$s has been created.')); + $message->addParam($_POST['new_db']); + $this->response->addJSON('message', $message); + $this->response->addJSON( + 'sql_query', Util::getMessage(null, $sql_query, 'success') + ); + + $this->response->addJSON( + 'url_query', + Util::getScriptNameForOption( + $GLOBALS['cfg']['DefaultTabDatabase'], 'database' + ) + . Url::getCommon(array('db' => $_POST['new_db'])) + ); + } + } + + /** + * Handles dropping multiple databases + * + * @return void + */ + public function dropDatabasesAction() + { + if (! isset($_POST['selected_dbs'])) { + $message = Message::error(__('No databases selected.')); + } else { + $action = 'server_databases.php'; + $err_url = $action . Url::getCommon(); + + $GLOBALS['submit_mult'] = 'drop_db'; + $GLOBALS['mult_btn'] = __('Yes'); + + include 'libraries/mult_submits.inc.php'; + + if (empty($message)) { // no error message + $number_of_databases = count($selected); + $message = Message::success( + _ngettext( + '%1$d database has been dropped successfully.', + '%1$d databases have been dropped successfully.', + $number_of_databases + ) + ); + $message->addParam($number_of_databases); + } + } + + if ($message instanceof Message) { + $this->response->setRequestStatus($message->isSuccess()); + $this->response->addJSON('message', $message); + } + } + + /** + * Extracts parameters $sort_order and $sort_by + * + * @return void + */ + private function _setSortDetails() + { + if (empty($_REQUEST['sort_by'])) { + $this->_sort_by = 'SCHEMA_NAME'; + } else { + $sort_by_whitelist = array( + 'SCHEMA_NAME', + 'DEFAULT_COLLATION_NAME', + 'SCHEMA_TABLES', + 'SCHEMA_TABLE_ROWS', + 'SCHEMA_DATA_LENGTH', + 'SCHEMA_INDEX_LENGTH', + 'SCHEMA_LENGTH', + 'SCHEMA_DATA_FREE' + ); + if (in_array($_REQUEST['sort_by'], $sort_by_whitelist)) { + $this->_sort_by = $_REQUEST['sort_by']; + } else { + $this->_sort_by = 'SCHEMA_NAME'; + } + } + + if (isset($_REQUEST['sort_order']) + && mb_strtolower($_REQUEST['sort_order']) == 'desc' + ) { + $this->_sort_order = 'desc'; + } else { + $this->_sort_order = 'asc'; + } + } + + /** + * Returns the html for Database List + * + * @param array $replication_types replication types + * + * @return string + */ + private function _getHtmlForDatabases(array $replication_types) + { + $first_database = reset($this->_databases); + // table col order + $column_order = $this->_getColumnOrder(); + + // calculate aggregate stats to display in footer + foreach ($this->_databases as $current) { + foreach ($column_order as $stat_name => $stat) { + if (array_key_exists($stat_name, $current) + && is_numeric($stat['footer']) + ) { + $column_order[$stat_name]['footer'] += $current[$stat_name]; + } + } + } + + $_url_params = array( + 'pos' => $this->_pos, + 'dbstats' => $this->_dbstats, + 'sort_by' => $this->_sort_by, + 'sort_order' => $this->_sort_order, + ); + + $html = Template::get('server/databases/databases_header')->render([ + 'database_count' => $this->_database_count, + 'pos' => $this->_pos, + 'url_params' => $_url_params, + 'max_db_list' => $GLOBALS['cfg']['MaxDbList'], + 'sort_by' => $this->_sort_by, + 'sort_order' => $this->_sort_order, + 'column_order' => $column_order, + 'first_database' => $first_database, + 'master_replication' => $GLOBALS['replication_info']['master']['status'], + 'slave_replication' => $GLOBALS['replication_info']['slave']['status'], + 'is_superuser' => $GLOBALS['dbi']->isSuperuser(), + 'allow_user_drop_database' => $GLOBALS['cfg']['AllowUserDropDatabase'], + ]); + + $html .= $this->_getHtmlForTableBody($column_order, $replication_types); + + $html .= Template::get('server/databases/databases_footer')->render([ + 'column_order' => $column_order, + 'first_database' => $first_database, + 'master_replication' => $GLOBALS['replication_info']['master']['status'], + 'slave_replication' => $GLOBALS['replication_info']['slave']['status'], + 'database_count' => $this->_database_count, + 'is_superuser' => $GLOBALS['dbi']->isSuperuser(), + 'allow_user_drop_database' => $GLOBALS['cfg']['AllowUserDropDatabase'], + 'pma_theme_image' => $GLOBALS['pmaThemeImage'], + 'text_dir' => $GLOBALS['text_dir'], + 'dbstats' => $this->_dbstats, + ]); + + return $html; + } + + /** + * Prepares the $column_order array + * + * @return array + */ + private function _getColumnOrder() + { + $column_order = array(); + $column_order['DEFAULT_COLLATION_NAME'] = array( + 'disp_name' => __('Collation'), + 'description_function' => array(Charsets::class, 'getCollationDescr'), + 'format' => 'string', + 'footer' => $this->dbi->getServerCollation(), + ); + $column_order['SCHEMA_TABLES'] = array( + 'disp_name' => __('Tables'), + 'format' => 'number', + 'footer' => 0, + ); + $column_order['SCHEMA_TABLE_ROWS'] = array( + 'disp_name' => __('Rows'), + 'format' => 'number', + 'footer' => 0, + ); + $column_order['SCHEMA_DATA_LENGTH'] = array( + 'disp_name' => __('Data'), + 'format' => 'byte', + 'footer' => 0, + ); + $column_order['SCHEMA_INDEX_LENGTH'] = array( + 'disp_name' => __('Indexes'), + 'format' => 'byte', + 'footer' => 0, + ); + $column_order['SCHEMA_LENGTH'] = array( + 'disp_name' => __('Total'), + 'format' => 'byte', + 'footer' => 0, + ); + $column_order['SCHEMA_DATA_FREE'] = array( + 'disp_name' => __('Overhead'), + 'format' => 'byte', + 'footer' => 0, + ); + + return $column_order; + } + + /** + * Returns the html for Database List + * + * @param array $column_order column order + * @param array $replication_types replication types + * + * @return string + */ + private function _getHtmlForTableBody(array $column_order, array $replication_types) + { + $html = '' . "\n"; + + foreach ($this->_databases as $current) { + $tr_class = ' db-row'; + if ($this->dbi->isSystemSchema($current['SCHEMA_NAME'], true)) { + $tr_class .= ' noclick'; + } + + $generated_html = $this->_buildHtmlForDb( + $current, + $column_order, + $replication_types, + $GLOBALS['replication_info'], + $tr_class + ); + $html .= $generated_html; + } // end foreach ($this->_databases as $key => $current) + $html .= ''; + + return $html; + } + + /** + * Builds the HTML for one database to display in the list + * of databases from server_databases.php + * + * @param array $current current database + * @param array $column_order column order + * @param array $replication_types replication types + * @param array $replication_info replication info + * @param string $tr_class HTMl class for the row + * + * @return array $column_order, $out + */ + function _buildHtmlForDb( + array $current, array $column_order, + array $replication_types, array $replication_info, $tr_class = '' + ) { + $master_replication = $slave_replication = ''; + foreach ($replication_types as $type) { + if ($replication_info[$type]['status']) { + $out = ''; + $key = array_search( + $current["SCHEMA_NAME"], + $replication_info[$type]['Ignore_DB'] + ); + if (strlen($key) > 0) { + $out = Util::getIcon( + 's_cancel', + __('Not replicated') + ); + } else { + $key = array_search( + $current["SCHEMA_NAME"], $replication_info[$type]['Do_DB'] + ); + + if (strlen($key) > 0 + || count($replication_info[$type]['Do_DB']) == 0 + ) { + // if ($key != null) did not work for index "0" + $out = Util::getIcon( + 's_success', + __('Replicated') + ); + } + } + + if ($type == 'master') { + $master_replication = $out; + } elseif ($type == 'slave') { + $slave_replication = $out; + } + } + } + + return Template::get('server/databases/table_row')->render([ + 'current' => $current, + 'tr_class' => $tr_class, + 'column_order' => $column_order, + 'master_replication_status' => $GLOBALS['replication_info']['master']['status'], + 'master_replication' => $master_replication, + 'slave_replication_status' => $GLOBALS['replication_info']['slave']['status'], + 'slave_replication' => $slave_replication, + 'is_superuser' => $GLOBALS['dbi']->isSuperuser(), + 'allow_user_drop_database' => $GLOBALS['cfg']['AllowUserDropDatabase'], + 'is_system_schema' => $GLOBALS['dbi']->isSystemSchema($current['SCHEMA_NAME'], true), + 'default_tab_database' => $GLOBALS['cfg']['DefaultTabDatabase'], + ]); + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Controllers/Server/ServerEnginesController.php b/php/apps/phpmyadmin49/html/libraries/classes/Controllers/Server/ServerEnginesController.php new file mode 100644 index 00000000..794060d7 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Controllers/Server/ServerEnginesController.php @@ -0,0 +1,100 @@ +response->addHTML( + Template::get('server/sub_page_header')->render([ + 'type' => 'engines', + ]) + ); + + /** + * Did the user request information about a certain storage engine? + */ + if (empty($_REQUEST['engine']) + || ! StorageEngine::isValid($_REQUEST['engine']) + ) { + $this->response->addHTML($this->_getHtmlForAllServerEngines()); + } else { + $engine = StorageEngine::getEngine($_REQUEST['engine']); + $this->response->addHTML($this->_getHtmlForServerEngine($engine)); + } + } + + /** + * Return HTML with all Storage Engine information + * + * @return string + */ + private function _getHtmlForAllServerEngines() + { + return Template::get('server/engines/engines')->render( + array('engines' => StorageEngine::getStorageEngines()) + ); + } + + /** + * Return HTML for a given Storage Engine + * + * @param StorageEngine $engine storage engine + * + * @return string + */ + private function _getHtmlForServerEngine(StorageEngine $engine) + { + $page = isset($_REQUEST['page']) ? $_REQUEST['page'] : ''; + $pageOutput = ! empty($page) ? $engine->getPage($page) : ''; + + /** + * Displays details about a given Storage Engine + */ + return Template::get('server/engines/engine')->render( + array( + 'title' => $engine->getTitle(), + 'help_page' => $engine->getMysqlHelpPage(), + 'comment' => $engine->getComment(), + 'info_pages' => $engine->getInfoPages(), + 'support' => $engine->getSupportInformationMessage(), + 'variables' => $engine->getHtmlVariables(), + 'page_output' => $pageOutput, + 'page' => $page, + 'engine' => $_REQUEST['engine'], + ) + ); + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Controllers/Server/ServerPluginsController.php b/php/apps/phpmyadmin49/html/libraries/classes/Controllers/Server/ServerPluginsController.php new file mode 100644 index 00000000..00941784 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Controllers/Server/ServerPluginsController.php @@ -0,0 +1,111 @@ +_setServerPlugins(); + } + + /** + * Index action + * + * @return void + */ + public function indexAction() + { + include 'libraries/server_common.inc.php'; + + $header = $this->response->getHeader(); + $scripts = $header->getScripts(); + $scripts->addFile('vendor/jquery/jquery.tablesorter.js'); + $scripts->addFile('server_plugins.js'); + + /** + * Displays the page + */ + $this->response->addHTML( + Template::get('server/sub_page_header')->render([ + 'type' => 'plugins', + ]) + ); + $this->response->addHTML($this->_getPluginsHtml()); + } + + /** + * Sets details about server plugins + * + * @return void + */ + private function _setServerPlugins() + { + $sql = "SELECT plugin_name, + plugin_type, + (plugin_status = 'ACTIVE') AS is_active, + plugin_type_version, + plugin_author, + plugin_description, + plugin_license + FROM information_schema.plugins + ORDER BY plugin_type, plugin_name"; + + $res = $this->dbi->query($sql); + $this->plugins = array(); + while ($row = $this->dbi->fetchAssoc($res)) { + $this->plugins[$row['plugin_type']][] = $row; + } + $this->dbi->freeResult($res); + ksort($this->plugins); + } + + /** + * Returns the html for plugin Tab. + * + * @return string + */ + private function _getPluginsHtml() + { + $html = '
      '; + $html .= Template::get('server/plugins/section_links') + ->render(array('plugins' => $this->plugins)); + + foreach ($this->plugins as $plugin_type => $plugin_list) { + $html .= Template::get('server/plugins/section') + ->render( + array( + 'plugin_type' => $plugin_type, + 'plugin_list' => $plugin_list, + ) + ); + } + $html .= '
      '; + return $html; + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Controllers/Server/ServerVariablesController.php b/php/apps/phpmyadmin49/html/libraries/classes/Controllers/Server/ServerVariablesController.php new file mode 100644 index 00000000..dca5e322 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Controllers/Server/ServerVariablesController.php @@ -0,0 +1,2837 @@ +variable_doc_links = $this->_getDocumentLinks(); + } + + /** + * Index action + * + * @return void + */ + public function indexAction() + { + $response = Response::getInstance(); + if ($response->isAjax() + && isset($_GET['type']) + && $_GET['type'] === 'getval' + ) { + $this->getValueAction(); + return; + } + + if ($response->isAjax() + && isset($_POST['type']) + && $_POST['type'] === 'setval' + ) { + $this->setValueAction(); + return; + } + + include 'libraries/server_common.inc.php'; + + $header = $this->response->getHeader(); + $scripts = $header->getScripts(); + $scripts->addFile('server_variables.js'); + + /** + * Displays the sub-page heading + */ + $this->response->addHTML( + Template::get('server/sub_page_header')->render([ + 'type' => 'variables', + 'link' => 'server_system_variables', + ]) + ); + + /** + * Sends the queries and buffers the results + */ + $serverVarsResult = $this->dbi->tryQuery('SHOW SESSION VARIABLES;'); + + if ($serverVarsResult !== false) { + + $serverVarsSession = array(); + while ($arr = $this->dbi->fetchRow($serverVarsResult)) { + $serverVarsSession[$arr[0]] = $arr[1]; + } + $this->dbi->freeResult($serverVarsResult); + + $serverVars = $this->dbi->fetchResult('SHOW GLOBAL VARIABLES;', 0, 1); + + /** + * Link templates + */ + $this->response->addHtml($this->_getHtmlForLinkTemplates()); + + /** + * Displays the page + */ + $this->response->addHtml( + $this->_getHtmlForServerVariables($serverVars, $serverVarsSession) + ); + } else { + /** + * Display the error message + */ + $this->response->addHTML( + Message::error( + sprintf( + __( + 'Not enough privilege to view server variables and ' + . 'settings. %s' + ), + Util::showMySQLDocu( + 'server-system-variables', + false, + 'sysvar_show_compatibility_56' + ) + ) + )->getDisplay() + ); + } + } + + /** + * Handle the AJAX request for a single variable value + * + * @return void + */ + public function getValueAction() + { + // Send with correct charset + header('Content-Type: text/html; charset=UTF-8'); + // Do not use double quotes inside the query to avoid a problem + // when server is running in ANSI_QUOTES sql_mode + $varValue = $this->dbi->fetchSingleRow( + 'SHOW GLOBAL VARIABLES WHERE Variable_name=\'' + . $GLOBALS['dbi']->escapeString($_GET['varName']) . '\';', + 'NUM' + ); + + if (isset($this->variable_doc_links[$_GET['varName']][3]) + && $this->variable_doc_links[$_GET['varName']][3] == 'byte' + ) { + $this->response->addJSON( + 'message', + implode( + ' ', Util::formatByteDown($varValue[1], 3, 3) + ) + ); + } else { + $this->response->addJSON( + 'message', + $varValue[1] + ); + } + } + + /** + * Handle the AJAX request for setting value for a single variable + * + * @return void + */ + public function setValueAction() + { + $value = $_POST['varValue']; + $matches = array(); + + if (isset($this->variable_doc_links[$_POST['varName']][3]) + && $this->variable_doc_links[$_POST['varName']][3] == 'byte' + && preg_match( + '/^\s*(\d+(\.\d+)?)\s*(mb|kb|mib|kib|gb|gib)\s*$/i', + $value, + $matches + ) + ) { + $exp = array( + 'kb' => 1, + 'kib' => 1, + 'mb' => 2, + 'mib' => 2, + 'gb' => 3, + 'gib' => 3 + ); + $value = floatval($matches[1]) * pow( + 1024, + $exp[mb_strtolower($matches[3])] + ); + } else { + $value = $GLOBALS['dbi']->escapeString($value); + } + + if (! is_numeric($value)) { + $value="'" . $value . "'"; + } + + if (! preg_match("/[^a-zA-Z0-9_]+/", $_POST['varName']) + && $this->dbi->query( + 'SET GLOBAL ' . $_POST['varName'] . ' = ' . $value + ) + ) { + // Some values are rounded down etc. + $varValue = $this->dbi->fetchSingleRow( + 'SHOW GLOBAL VARIABLES WHERE Variable_name="' + . $GLOBALS['dbi']->escapeString($_POST['varName']) + . '";', 'NUM' + ); + list($formattedValue, $isHtmlFormatted) = $this->_formatVariable( + $_POST['varName'], $varValue[1] + ); + + if ($isHtmlFormatted == false) { + $this->response->addJSON( + 'variable', + htmlspecialchars( + $formattedValue + ) + ); + } else { + $this->response->addJSON( + 'variable', + $formattedValue + ); + } + } else { + $this->response->setRequestStatus(false); + $this->response->addJSON( + 'error', + __('Setting variable failed') + ); + } + } + + /** + * Format Variable + * + * @param string $name variable name + * @param integer $value variable value + * + * @return array formatted string and bool if string is HTML formatted + */ + private function _formatVariable($name, $value) + { + $isHtmlFormatted = false; + $formattedValue = $value; + + if (is_numeric($value)) { + if (isset($this->variable_doc_links[$name][3]) + && $this->variable_doc_links[$name][3] == 'byte' + ) { + $isHtmlFormatted = true; + $formattedValue = '' + . htmlspecialchars( + implode(' ', Util::formatByteDown($value, 3, 3)) + ) + . ''; + } else { + $formattedValue = Util::formatNumber($value, 0); + } + } + + return array( + $formattedValue, + $isHtmlFormatted + ); + } + + /** + * Prints link templates + * + * @return string + */ + private function _getHtmlForLinkTemplates() + { + $url = 'server_variables.php' . Url::getCommon(); + return Template::get('server/variables/link_template') + ->render(array('url' => $url)); + } + + /** + * Prints Html for Server Variables + * + * @param array $serverVars global variables + * @param array $serverVarsSession session variables + * + * @return string + */ + private function _getHtmlForServerVariables(array $serverVars, array $serverVarsSession) + { + // filter + $filterValue = ! empty($_REQUEST['filter']) ? $_REQUEST['filter'] : ''; + $output = Template::get('filter') + ->render(array('filter_value' => $filterValue)); + + $output .= '
      '; + $output .= ''; + $output .= Template::get('server/variables/variable_table_head')->render(); + $output .= ''; + + $output .= $this->_getHtmlForServerVariablesItems( + $serverVars, $serverVarsSession + ); + + $output .= ''; + $output .= '
      '; + $output .= '
      '; + + return $output; + } + + + /** + * Prints Html for Server Variables Items + * + * @param array $serverVars global variables + * @param array $serverVarsSession session variables + * + * @return string + */ + private function _getHtmlForServerVariablesItems( + array $serverVars, array $serverVarsSession + ) { + // list of static (i.e. non-editable) system variables + $static_variables = $this->_getStaticSystemVariables(); + + $output = ''; + foreach ($serverVars as $name => $value) { + $has_session_value = isset($serverVarsSession[$name]) + && $serverVarsSession[$name] != $value; + $row_class = ($has_session_value ? ' diffSession' : ''); + $docLink = isset($this->variable_doc_links[$name]) + ? $this->variable_doc_links[$name] : null; + + list($formattedValue, $isHtmlFormatted) = $this->_formatVariable($name, $value); + + $output .= Template::get('server/variables/variable_row')->render(array( + 'row_class' => $row_class, + 'editable' => ! in_array( + strtolower($name), + $static_variables + ), + 'doc_link' => $docLink, + 'name' => $name, + 'value' => $formattedValue, + 'is_superuser' => $this->dbi->isSuperuser(), + 'is_html_formatted' => $isHtmlFormatted, + )); + + if ($has_session_value) { + list($formattedValue, $isHtmlFormatted)= $this->_formatVariable( + $name, $serverVarsSession[$name] + ); + $output .= Template::get('server/variables/session_variable_row') + ->render( + array( + 'row_class' => $row_class, + 'value' => $formattedValue, + 'is_html_formatted' => $isHtmlFormatted, + ) + ); + } + + } + + return $output; + } + + /** + * Returns Array of documentation links + * + * $variable_doc_links[string $name] = array( + * string $name, + * string $anchor, + * string $chapter, + * string $type, + * string $format); + * string $name: name of the system variable + * string $anchor: anchor to the documentation page + * string $chapter: chapter of "HTML, one page per chapter" documentation + * string $type: type of system variable + * string $format: if set to 'byte' it will format the variable + * with Util::formatByteDown() + * + * @return array + */ + private function _getDocumentLinks() + { + $variable_doc_links = array(); + $variable_doc_links['auto_increment_increment'] = array( + 'auto_increment_increment', + 'replication-options-master', + 'sysvar'); + $variable_doc_links['auto_increment_offset'] = array( + 'auto_increment_offset', + 'replication-options-master', + 'sysvar'); + $variable_doc_links['autocommit'] = array( + 'autocommit', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['automatic_sp_privileges'] = array( + 'automatic_sp_privileges', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['avoid_temporal_upgrade'] = array( + 'avoid_temporal_upgrade', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['back_log'] = array( + 'back_log', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['basedir'] = array( + 'basedir', + 'server-options', + 'option_mysqld'); + $variable_doc_links['big_tables'] = array( + 'big-tables', + 'server-options', + 'option_mysqld'); + $variable_doc_links['bind_address'] = array( + 'bind-address', + 'server-options', + 'option_mysqld'); + $variable_doc_links['binlog_cache_size'] = array( + 'binlog_cache_size', + 'replication-options-binary-log', + 'sysvar', + 'byte'); + $variable_doc_links['binlog_checksum'] = array( + 'binlog_checksum', + 'replication-options-binary-log', + 'sysvar'); + $variable_doc_links['binlog_direct_non_transactional_updates'] = array( + 'binlog_direct_non_transactional_updates', + 'replication-options-binary-log', + 'sysvar'); + $variable_doc_links['binlog_error_action'] = array( + 'binlog_error_action', + 'replication-options-binary-log', + 'sysvar'); + $variable_doc_links['binlog_format'] = array( + 'binlog-format', + 'server-options', + 'option_mysqld'); + $variable_doc_links['binlog_group_commit_sync_delay'] = array( + 'binlog_group_commit_sync_delay', + 'replication-options-binary-log', + 'sysvar'); + $variable_doc_links['binlog_group_commit_sync_no_delay_count'] = array( + 'binlog_group_commit_sync_no_delay_count', + 'replication-options-binary-log', + 'sysvar'); + $variable_doc_links['binlog_gtid_simple_recovery'] = array( + 'binlog_gtid_simple_recovery', + 'replication-options-gtids', + 'sysvar'); + $variable_doc_links['binlog_max_flush_queue_time'] = array( + 'binlog_max_flush_queue_time', + 'replication-options-binary-log', + 'sysvar'); + $variable_doc_links['binlog_order_commits'] = array( + 'binlog_order_commits', + 'replication-options-binary-log', + 'sysvar'); + $variable_doc_links['binlog_row_image'] = array( + 'binlog_row_image', + 'replication-options-binary-log', + 'sysvar'); + $variable_doc_links['binlog_rows_query_log_events'] = array( + 'binlog_rows_query_log_events', + 'replication-options-binary-log', + 'sysvar'); + $variable_doc_links['binlog_stmt_cache_size'] = array( + 'binlog_stmt_cache_size', + 'replication-options-binary-log', + 'sysvar', + 'byte'); + $variable_doc_links['block_encryption_mode'] = array( + 'block_encryption_mode', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['bulk_insert_buffer_size'] = array( + 'bulk_insert_buffer_size', + 'server-system-variables', + 'sysvar', + 'byte'); + $variable_doc_links['character_set_client'] = array( + 'character_set_client', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['character_set_connection'] = array( + 'character_set_connection', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['character_set_database'] = array( + 'character_set_database', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['character_set_filesystem'] = array( + 'character-set-filesystem', + 'server-options', + 'option_mysqld'); + $variable_doc_links['character_set_results'] = array( + 'character_set_results', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['character_set_server'] = array( + 'character-set-server', + 'server-options', + 'option_mysqld'); + $variable_doc_links['character_set_system'] = array( + 'character_set_system', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['character_sets_dir'] = array( + 'character-sets-dir', + 'server-options', + 'option_mysqld'); + $variable_doc_links['check_proxy_users'] = array( + 'check_proxy_users', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['collation_connection'] = array( + 'collation_connection', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['collation_database'] = array( + 'collation_database', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['collation_server'] = array( + 'collation-server', + 'server-options', + 'option_mysqld'); + $variable_doc_links['completion_type'] = array( + 'completion_type', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['concurrent_insert'] = array( + 'concurrent_insert', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['connect_timeout'] = array( + 'connect_timeout', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['core_file'] = array( + 'core_file', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['datadir'] = array( + 'datadir', + 'server-options', + 'option_mysqld'); + $variable_doc_links['date_format'] = array( + 'date_format', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['datetime_format'] = array( + 'datetime_format', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['debug'] = array( + 'debug', + 'server-options', + 'option_mysqld'); + $variable_doc_links['debug_sync'] = array( + 'debug_sync', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['default_authentication_plugin'] = array( + 'default_authentication_plugin', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['default_password_lifetime'] = array( + 'default_password_lifetime', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['default_storage_engine'] = array( + 'default-storage-engine', + 'server-options', + 'option_mysqld'); + $variable_doc_links['default_tmp_storage_engine'] = array( + 'default_tmp_storage_engine', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['default_week_format'] = array( + 'default_week_format', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['delay_key_write'] = array( + 'delay-key-write', + 'server-options', + 'option_mysqld'); + $variable_doc_links['delayed_insert_limit'] = array( + 'delayed_insert_limit', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['delayed_insert_timeout'] = array( + 'delayed_insert_timeout', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['delayed_queue_size'] = array( + 'delayed_queue_size', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['disabled_storage_engines'] = array( + 'disabled_storage_engines', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['disconnect_on_expired_password'] = array( + 'disconnect_on_expired_password', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['div_precision_increment'] = array( + 'div_precision_increment', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['end_markers_in_json'] = array( + 'end_markers_in_json', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['enforce_gtid_consistency'] = array( + 'enforce_gtid_consistency', + 'replication-options-gtids', + 'sysvar'); + $variable_doc_links['eq_range_index_dive_limit'] = array( + 'eq_range_index_dive_limit', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['engine_condition_pushdown'] = array( + 'engine-condition-pushdown', + 'server-options', + 'option_mysqld'); + $variable_doc_links['error_count'] = array( + 'error_count', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['event_scheduler'] = array( + 'event-scheduler', + 'server-options', + 'option_mysqld'); + $variable_doc_links['expire_logs_days'] = array( + 'expire_logs_days', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['explicit_defaults_for_timestamp'] = array( + 'explicit_defaults_for_timestamp', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['external_user'] = array( + 'external_user', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['flush'] = array( + 'flush', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['flush_time'] = array( + 'flush_time', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['foreign_key_checks'] = array( + 'foreign_key_checks', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['ft_boolean_syntax'] = array( + 'ft_boolean_syntax', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['ft_max_word_len'] = array( + 'ft_max_word_len', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['ft_min_word_len'] = array( + 'ft_min_word_len', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['ft_query_expansion_limit'] = array( + 'ft_query_expansion_limit', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['ft_stopword_file'] = array( + 'ft_stopword_file', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['general_log'] = array( + 'general-log', + 'server-options', + 'option_mysqld'); + $variable_doc_links['general_log_file'] = array( + 'general_log_file', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['group_concat_max_len'] = array( + 'group_concat_max_len', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['gtid_executed'] = array( + 'gtid_executed', + 'replication-options-gtids', + 'sysvar'); + $variable_doc_links['gtid_executed_compression_period'] = array( + 'gtid_executed_compression_period', + 'replication-options-gtids', + 'sysvar'); + $variable_doc_links['gtid_mode'] = array( + 'gtid_mode', + 'replication-options-gtids', + 'sysvar'); + $variable_doc_links['gtid_owned'] = array( + 'gtid_owned', + 'replication-options-gtids', + 'sysvar'); + $variable_doc_links['gtid_purged'] = array( + 'gtid_purged', + 'replication-options-gtids', + 'sysvar'); + $variable_doc_links['have_compress'] = array( + 'have_compress', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['have_crypt'] = array( + 'have_crypt', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['have_csv'] = array( + 'have_csv', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['have_dynamic_loading'] = array( + 'have_dynamic_loading', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['have_geometry'] = array( + 'have_geometry', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['have_innodb'] = array( + 'have_innodb', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['have_ndbcluster'] = array( + 'have_ndbcluster', + 'mysql-cluster-system-variables', + 'sysvar'); + $variable_doc_links['have_openssl'] = array( + 'have_openssl', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['have_partitioning'] = array( + 'have_partitioning', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['have_profiling'] = array( + 'have_profiling', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['have_query_cache'] = array( + 'have_query_cache', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['have_rtree_keys'] = array( + 'have_rtree_keys', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['have_ssl'] = array( + 'have_ssl', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['have_statement_timeout'] = array( + 'have_statement_timeout', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['have_symlink'] = array( + 'have_symlink', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['host_cache_size'] = array( + 'host_cache_size', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['hostname'] = array( + 'hostname', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['identity'] = array( + 'identity', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['ignore_builtin_innodb'] = array( + 'ignore-builtin-innodb', + 'innodb-parameters', + 'option_mysqld'); + $variable_doc_links['ignore_db_dirs'] = array( + 'ignore_db_dirs', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['init_connect'] = array( + 'init_connect', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['init_file'] = array( + 'init-file', + 'server-options', + 'option_mysqld'); + $variable_doc_links['init_slave'] = array( + 'init_slave', + 'replication-options-slave', + 'sysvar'); + $variable_doc_links['innodb_adaptive_flushing'] = array( + 'innodb_adaptive_flushing', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_adaptive_flushing_lwm'] = array( + 'innodb_adaptive_flushing_lwm', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_adaptive_hash_index'] = array( + 'innodb_adaptive_hash_index', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_adaptive_hash_index_parts'] = array( + 'innodb_adaptive_hash_index_parts', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_adaptive_max_sleep_delay'] = array( + 'innodb_adaptive_max_sleep_delay', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_additional_mem_pool_size'] = array( + 'innodb_additional_mem_pool_size', + 'innodb-parameters', + 'sysvar', + 'byte'); + $variable_doc_links['innodb_api_bk_commit_interval'] = array( + 'innodb_api_bk_commit_interval', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_api_disable_rowlock'] = array( + 'innodb_api_disable_rowlock', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_api_enable_binlog'] = array( + 'innodb_api_enable_binlog', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_api_enable_mdl'] = array( + 'innodb_api_enable_mdl', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_api_trx_level'] = array( + 'innodb_api_trx_level', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_autoextend_increment'] = array( + 'innodb_autoextend_increment', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_autoinc_lock_mode'] = array( + 'innodb_autoinc_lock_mode', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_buffer_pool_chunk_size'] = array( + 'innodb_buffer_pool_chunk_size', + 'innodb-parameters', + 'sysvar', + 'byte'); + $variable_doc_links['innodb_buffer_pool_dump_at_shutdown'] = array( + 'innodb_buffer_pool_dump_at_shutdown', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_buffer_pool_dump_now'] = array( + 'innodb_buffer_pool_dump_now', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_buffer_pool_dump_pct'] = array( + 'innodb_buffer_pool_dump_pct', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_buffer_pool_filename'] = array( + 'innodb_buffer_pool_filename', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_buffer_pool_instances'] = array( + 'innodb_buffer_pool_instances', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_buffer_pool_load_abort'] = array( + 'innodb_buffer_pool_load_abort', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_buffer_pool_load_at_startup'] = array( + 'innodb_buffer_pool_load_at_startup', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_buffer_pool_load_now'] = array( + 'innodb_buffer_pool_load_now', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_buffer_pool_size'] = array( + 'innodb_buffer_pool_size', + 'innodb-parameters', + 'sysvar', + 'byte'); + $variable_doc_links['innodb_change_buffer_max_size'] = array( + 'innodb_change_buffer_max_size', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_change_buffering'] = array( + 'innodb_change_buffering', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_checksum_algorithm'] = array( + 'innodb_checksum_algorithm', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_checksums'] = array( + 'innodb_checksums', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_cmp_per_index_enabled'] = array( + 'innodb_cmp_per_index_enabled', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_commit_concurrency'] = array( + 'innodb_commit_concurrency', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_compression_failure_threshold_pct'] = array( + 'innodb_compression_failure_threshold_pct', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_compression_level'] = array( + 'innodb_compression_level', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_compression_pad_pct_max'] = array( + 'innodb_compression_pad_pct_max', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_concurrency_tickets'] = array( + 'innodb_concurrency_tickets', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_data_file_path'] = array( + 'innodb_data_file_path', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_data_home_dir'] = array( + 'innodb_data_home_dir', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_disable_sort_file_cache'] = array( + 'innodb_disable_sort_file_cache', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_doublewrite'] = array( + 'innodb_doublewrite', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_fast_shutdown'] = array( + 'innodb_fast_shutdown', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_file_format'] = array( + 'innodb_file_format', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_file_format_check'] = array( + 'innodb_file_format_check', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_file_format_max'] = array( + 'innodb_file_format_max', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_file_per_table'] = array( + 'innodb_file_per_table', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_fill_factor'] = array( + 'innodb_fill_factor', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_flush_log_at_timeout'] = array( + 'innodb_flush_log_at_timeout', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_flush_log_at_trx_commit'] = array( + 'innodb_flush_log_at_trx_commit', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_flush_method'] = array( + 'innodb_flush_method', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_flush_neighbors'] = array( + 'innodb_flush_neighbors', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_flush_sync'] = array( + 'innodb_flush_sync', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_flushing_avg_loops'] = array( + 'innodb_flushing_avg_loops', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_force_load_corrupted'] = array( + 'innodb_force_load_corrupted', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_force_recovery'] = array( + 'innodb_force_recovery', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_ft_aux_table'] = array( + 'innodb_ft_aux_table', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_ft_cache_size'] = array( + 'innodb_ft_cache_size', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_ft_enable_diag_print'] = array( + 'innodb_ft_enable_diag_print', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_ft_enable_stopword'] = array( + 'innodb_ft_enable_stopword', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_ft_max_token_size'] = array( + 'innodb_ft_max_token_size', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_ft_min_token_size'] = array( + 'innodb_ft_min_token_size', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_ft_num_word_optimize'] = array( + 'innodb_ft_num_word_optimize', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_ft_result_cache_limit'] = array( + 'innodb_ft_result_cache_limit', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_ft_server_stopword_table'] = array( + 'innodb_ft_server_stopword_table', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_ft_sort_pll_degree'] = array( + 'innodb_ft_sort_pll_degree', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_ft_total_cache_size'] = array( + 'innodb_ft_total_cache_size', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_ft_user_stopword_table'] = array( + 'innodb_ft_user_stopword_table', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_io_capacity'] = array( + 'innodb_io_capacity', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_io_capacity_max'] = array( + 'innodb_io_capacity_max', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_large_prefix'] = array( + 'innodb_large_prefix', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_lock_wait_timeout'] = array( + 'innodb_lock_wait_timeout', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_locks_unsafe_for_binlog'] = array( + 'innodb_locks_unsafe_for_binlog', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_log_buffer_size'] = array( + 'innodb_log_buffer_size', + 'innodb-parameters', + 'sysvar', + 'byte'); + $variable_doc_links['innodb_log_checksum_algorithm'] = array( + 'innodb_log_checksum_algorithm', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_log_compressed_pages'] = array( + 'innodb_log_compressed_pages', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_log_file_size'] = array( + 'innodb_log_file_size', + 'innodb-parameters', + 'sysvar', + 'byte'); + $variable_doc_links['innodb_log_files_in_group'] = array( + 'innodb_log_files_in_group', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_log_group_home_dir'] = array( + 'innodb_log_group_home_dir', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_log_write_ahead_size'] = array( + 'innodb_log_write_ahead_size', + 'innodb-parameters', + 'sysvar', + 'byte'); + $variable_doc_links['innodb_lru_scan_depth'] = array( + 'innodb_lru_scan_depth', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_max_dirty_pages_pct'] = array( + 'innodb_max_dirty_pages_pct', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_max_dirty_pages_pct_lwm'] = array( + 'innodb_max_dirty_pages_pct_lwm', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_max_purge_lag'] = array( + 'innodb_max_purge_lag', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_max_purge_lag_delay'] = array( + 'innodb_max_purge_lag_delay', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_max_undo_log_size'] = array( + 'innodb_max_undo_log_size', + 'innodb-parameters', + 'sysvar', + 'byte'); + $variable_doc_links['innodb_mirrored_log_groups'] = array( + 'innodb_mirrored_log_groups', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_monitor_disable'] = array( + 'innodb_monitor_disable', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_monitor_enable'] = array( + 'innodb_monitor_enable', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_monitor_reset'] = array( + 'innodb_monitor_reset', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_monitor_reset_all'] = array( + 'innodb_monitor_reset_all', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_old_blocks_pct'] = array( + 'innodb_old_blocks_pct', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_old_blocks_time'] = array( + 'innodb_old_blocks_time', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_online_alter_log_max_size'] = array( + 'innodb_online_alter_log_max_size', + 'innodb-parameters', + 'sysvar', + 'byte'); + $variable_doc_links['innodb_open_files'] = array( + 'innodb_open_files', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_optimize_fulltext_only'] = array( + 'innodb_optimize_fulltext_only', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_page_cleaners'] = array( + 'innodb_page_cleaners', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_page_size'] = array( + 'innodb_page_size', + 'innodb-parameters', + 'sysvar', + 'byte'); + $variable_doc_links['innodb_print_all_deadlocks'] = array( + 'innodb_print_all_deadlocks', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_purge_batch_size'] = array( + 'innodb_purge_batch_size', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_purge_rseg_truncate_frequency'] = array( + 'innodb_purge_rseg_truncate_frequency', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_purge_threads'] = array( + 'innodb_purge_threads', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_random_read_ahead'] = array( + 'innodb_random_read_ahead', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_read_ahead_threshold'] = array( + 'innodb_read_ahead_threshold', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_read_io_threads'] = array( + 'innodb_read_io_threads', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_read_only'] = array( + 'innodb_read_only', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_replication_delay'] = array( + 'innodb_replication_delay', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_rollback_on_timeout'] = array( + 'innodb_rollback_on_timeout', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_rollback_segments'] = array( + 'innodb_rollback_segments', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_sort_buffer_size'] = array( + 'innodb_sort_buffer_size', + 'innodb-parameters', + 'sysvar', + 'byte'); + $variable_doc_links['innodb_spin_wait_delay'] = array( + 'innodb_spin_wait_delay', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_stats_auto_recalc'] = array( + 'innodb_stats_auto_recalc', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_stats_method'] = array( + 'innodb_stats_method', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_stats_on_metadata'] = array( + 'innodb_stats_on_metadata', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_stats_persistent'] = array( + 'innodb_stats_persistent', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_stats_persistent_sample_pages'] = array( + 'innodb_stats_persistent_sample_pages', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_stats_sample_pages'] = array( + 'innodb_stats_sample_pages', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_stats_transient_sample_pages'] = array( + 'innodb_stats_transient_sample_pages', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_status_output'] = array( + 'innodb_status_output', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_status_output_locks'] = array( + 'innodb_status_output_locks', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_strict_mode'] = array( + 'innodb_strict_mode', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_support_xa'] = array( + 'innodb_support_xa', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_sync_array_size'] = array( + 'innodb_sync_array_size', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_sync_spin_loops'] = array( + 'innodb_sync_spin_loops', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_table_locks'] = array( + 'innodb_table_locks', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_temp_data_file_path'] = array( + 'innodb_temp_data_file_path', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_thread_concurrency'] = array( + 'innodb_thread_concurrency', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_thread_sleep_delay'] = array( + 'innodb_thread_sleep_delay', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_undo_directory'] = array( + 'innodb_undo_directory', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_undo_log_truncate'] = array( + 'innodb_undo_log_truncate', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_undo_logs'] = array( + 'innodb_undo_logs', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_undo_tablespaces'] = array( + 'innodb_undo_tablespaces', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_use_native_aio'] = array( + 'innodb_use_native_aio', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_use_sys_malloc'] = array( + 'innodb_use_sys_malloc', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_version'] = array( + 'innodb_version', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_write_io_threads'] = array( + 'innodb_write_io_threads', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['insert_id'] = array( + 'insert_id', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['interactive_timeout'] = array( + 'interactive_timeout', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['internal_tmp_disk_storage_engine'] = array( + 'internal_tmp_disk_storage_engine', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['join_buffer_size'] = array( + 'join_buffer_size', + 'server-system-variables', + 'sysvar', + 'byte'); + $variable_doc_links['keep_files_on_create'] = array( + 'keep_files_on_create', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['key_buffer_size'] = array( + 'key_buffer_size', + 'server-system-variables', + 'sysvar', + 'byte'); + $variable_doc_links['key_cache_age_threshold'] = array( + 'key_cache_age_threshold', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['key_cache_block_size'] = array( + 'key_cache_block_size', + 'server-system-variables', + 'sysvar', + 'byte'); + $variable_doc_links['key_cache_division_limit'] = array( + 'key_cache_division_limit', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['language'] = array( + 'language', + 'server-options', + 'option_mysqld'); + $variable_doc_links['large_files_support'] = array( + 'large_files_support', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['large_page_size'] = array( + 'large_page_size', + 'server-system-variables', + 'sysvar', + 'byte'); + $variable_doc_links['large_pages'] = array( + 'large-pages', + 'server-options', + 'option_mysqld'); + $variable_doc_links['last_insert_id'] = array( + 'last_insert_id', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['lc_messages'] = array( + 'lc-messages', + 'server-options', + 'option_mysqld'); + $variable_doc_links['lc_messages_dir'] = array( + 'lc-messages-dir', + 'server-options', + 'option_mysqld'); + $variable_doc_links['lc_time_names'] = array( + 'lc_time_names', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['license'] = array( + 'license', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['local_infile'] = array( + 'local_infile', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['lock_wait_timeout'] = array( + 'lock_wait_timeout', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['locked_in_memory'] = array( + 'locked_in_memory', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['log_backward_compatible_user_definitions'] = array( + 'log_backward_compatible_user_definitions', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['log'] = array( + 'log', + 'server-options', + 'option_mysqld'); + $variable_doc_links['log_bin'] = array( + 'log_bin', + 'replication-options-binary-log', + 'sysvar'); + $variable_doc_links['log-bin'] = array( + 'log-bin', + 'replication-options-binary-log', + 'option_mysqld'); + $variable_doc_links['log_bin_basename'] = array( + 'log_bin_basename', + 'replication-options-binary-log', + 'sysvar'); + $variable_doc_links['log_bin_index'] = array( + 'log_bin_index', + 'replication-options-binary-log', + 'sysvar'); + $variable_doc_links['log_bin_trust_function_creators'] = array( + 'log-bin-trust-function-creators', + 'replication-options-binary-log', + 'option_mysqld'); + $variable_doc_links['log_bin_use_v1_row_events'] = array( + 'log_bin_use_v1_row_events', + 'replication-options-binary-log', + 'sysvar'); + $variable_doc_links['log_error'] = array( + 'log-error', + 'server-options', + 'option_mysqld'); + $variable_doc_links['log_error_verbosity'] = array( + 'log_error_verbosity', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['log_output'] = array( + 'log-output', + 'server-options', + 'option_mysqld'); + $variable_doc_links['log_queries_not_using_indexes'] = array( + 'log-queries-not-using-indexes', + 'server-options', + 'option_mysqld'); + $variable_doc_links['log_slave_updates'] = array( + 'log-slave-updates', + 'replication-options-slave', + 'option_mysqld'); + $variable_doc_links['log_slow_admin_statements'] = array( + 'log_slow_admin_statements', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['log_slow_slave_statements'] = array( + 'log_slow_slave_statements', + 'replication-options-slave', + 'sysvar'); + $variable_doc_links['log_syslog'] = array( + 'log_syslog', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['log_syslog_facility'] = array( + 'log_syslog_facility', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['log_syslog_include_pid'] = array( + 'log_syslog_include_pid', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['log_syslog_tag'] = array( + 'log_syslog_tag', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['log_throttle_queries_not_using_indexes'] = array( + 'log_throttle_queries_not_using_indexes', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['log_timestamps'] = array( + 'log_timestamps', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['log_slow_queries'] = array( + 'log-slow-queries', + 'server-options', + 'option_mysqld'); + $variable_doc_links['log_warnings'] = array( + 'log-warnings', + 'server-options', + 'option_mysqld'); + $variable_doc_links['long_query_time'] = array( + 'long_query_time', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['low_priority_updates'] = array( + 'low-priority-updates', + 'server-options', + 'option_mysqld'); + $variable_doc_links['lower_case_file_system'] = array( + 'lower_case_file_system', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['lower_case_table_names'] = array( + 'lower_case_table_names', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['master_info_repository'] = array( + 'master_info_repository', + 'replication-options-slave', + 'sysvar'); + $variable_doc_links['master_verify_checksum'] = array( + 'master_verify_checksum', + 'replication-options-binary-log', + 'sysvar'); + $variable_doc_links['master-bind'] = array( + '', + 'replication-options', + 0); + $variable_doc_links['max_allowed_packet'] = array( + 'max_allowed_packet', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['max_binlog_cache_size'] = array( + 'max_binlog_cache_size', + 'replication-options-binary-log', + 'sysvar', + 'byte'); + $variable_doc_links['max_binlog_size'] = array( + 'max_binlog_size', + 'replication-options-binary-log', + 'sysvar', + 'byte'); + $variable_doc_links['max_binlog_stmt_cache_size'] = array( + 'max_binlog_stmt_cache_size', + 'replication-options-binary-log', + 'sysvar', + 'byte'); + $variable_doc_links['max_connect_errors'] = array( + 'max_connect_errors', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['max_connections'] = array( + 'max_connections', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['max_delayed_threads'] = array( + 'max_delayed_threads', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['max_digest_length'] = array( + 'max_digest_length', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['max_error_count'] = array( + 'max_error_count', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['max_execution_time'] = array( + 'max_execution_time', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['max_heap_table_size'] = array( + 'max_heap_table_size', + 'server-system-variables', + 'sysvar', + 'byte'); + $variable_doc_links['max_insert_delayed_threads'] = array( + 'max_insert_delayed_threads', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['max_join_size'] = array( + 'max_join_size', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['max_length_for_sort_data'] = array( + 'max_length_for_sort_data', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['max_points_in_geometry'] = array( + 'max_points_in_geometry', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['max_long_data_size'] = array( + 'max_long_data_size', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['max_prepared_stmt_count'] = array( + 'max_prepared_stmt_count', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['max_relay_log_size'] = array( + 'max_relay_log_size', + 'server-system-variables', + 'sysvar', + 'byte'); + $variable_doc_links['max_seeks_for_key'] = array( + 'max_seeks_for_key', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['max_sort_length'] = array( + 'max_sort_length', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['max_sp_recursion_depth'] = array( + 'max_sp_recursion_depth', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['max_tmp_tables'] = array( + 'max_tmp_tables', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['max_user_connections'] = array( + 'max_user_connections', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['max_write_lock_count'] = array( + 'max_write_lock_count', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['metadata_locks_cache_size'] = array( + 'metadata_locks_cache_size', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['metadata_locks_hash_instances'] = array( + 'metadata_locks_hash_instances', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['memlock'] = array( + 'memlock', + 'server-options', + 'option_mysqld'); + $variable_doc_links['min_examined_row_limit'] = array( + 'min-examined-row-limit', + 'server-options', + 'option_mysqld'); + $variable_doc_links['multi_range_count'] = array( + 'multi_range_count', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['myisam_data_pointer_size'] = array( + 'myisam_data_pointer_size', + 'server-system-variables', + 'sysvar', + 'byte'); + $variable_doc_links['myisam_max_sort_file_size'] = array( + 'myisam_max_sort_file_size', + 'server-system-variables', + 'sysvar', + 'byte'); + $variable_doc_links['myisam_mmap_size'] = array( + 'myisam_mmap_size', + 'server-system-variables', + 'sysvar', + 'byte'); + $variable_doc_links['myisam_recover_options'] = array( + 'myisam_recover_options', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['myisam_repair_threads'] = array( + 'myisam_repair_threads', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['myisam_sort_buffer_size'] = array( + 'myisam_sort_buffer_size', + 'server-system-variables', + 'sysvar', + 'byte'); + $variable_doc_links['myisam_stats_method'] = array( + 'myisam_stats_method', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['myisam_use_mmap'] = array( + 'myisam_use_mmap', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['mysql_native_password_proxy_users'] = array( + 'mysql_native_password_proxy_users', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['named_pipe'] = array( + 'named_pipe', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['net_buffer_length'] = array( + 'net_buffer_length', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['net_read_timeout'] = array( + 'net_read_timeout', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['net_retry_count'] = array( + 'net_retry_count', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['net_write_timeout'] = array( + 'net_write_timeout', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['new'] = array( + 'new', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['ngram_token_size'] = array( + 'ngram_token_size', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['offline_mode'] = array( + 'offline_mode', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['old'] = array( + 'old', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['old_alter_table'] = array( + 'old-alter-table', + 'server-options', + 'option_mysqld'); + $variable_doc_links['old_passwords'] = array( + 'old-passwords', + 'server-options', + 'option_mysqld'); + $variable_doc_links['open_files_limit'] = array( + 'open-files-limit', + 'server-options', + 'option_mysqld'); + $variable_doc_links['optimizer_prune_level'] = array( + 'optimizer_prune_level', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['optimizer_search_depth'] = array( + 'optimizer_search_depth', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['optimizer_switch'] = array( + 'optimizer_switch', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['optimizer_trace'] = array( + 'optimizer_trace', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['optimizer_trace_features'] = array( + 'optimizer_trace_features', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['optimizer_trace_limit'] = array( + 'optimizer_trace_limit', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['optimizer_trace_max_mem_size'] = array( + 'optimizer_trace_max_mem_size', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['optimizer_trace_offset'] = array( + 'optimizer_trace_offset', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['partition'] = array( + 'partition', + 'server-options', + 'option_mysqld'); + $variable_doc_links['performance_schema'] = array( + 'performance_schema', + 'performance-schema-system-variables', + 'sysvar'); + $variable_doc_links['performance_schema_accounts_size'] = array( + 'performance_schema_accounts_size', + 'performance-schema-system-variables', + 'sysvar'); + $variable_doc_links['performance_schema_digests_size'] = array( + 'performance_schema_digests_size', + 'performance-schema-system-variables', + 'sysvar'); + $variable_doc_links['performance_schema_events_stages_history_long_size'] + = array( + 'performance_schema_events_stages_history_long_size', + 'performance-schema-system-variables', + 'sysvar', + ); + $variable_doc_links['performance_schema_events_stages_history_size'] = array( + 'performance_schema_events_stages_history_size', + 'performance-schema-system-variables', + 'sysvar'); + $variable_doc_links['performance_schema_events_statements_history_long_size'] + = array( + 'performance_schema_events_statements_history_long_size', + 'performance-schema-system-variables', + 'sysvar', + ); + $variable_doc_links['performance_schema_events_statements_history_size'] + = array( + 'performance_schema_events_statements_history_size', + 'performance-schema-system-variables', + 'sysvar', + ); + $variable_doc_links['performance_schema_events_transactions_history_long_size'] + = array( + 'performance_schema_events_transactions_history_long_size', + 'performance-schema-system-variables', + 'sysvar', + ); + $variable_doc_links['performance_schema_events_transactions_history_size'] + = array( + 'performance_schema_events_transactions_history_size', + 'performance-schema-system-variables', + 'sysvar', + ); + $variable_doc_links['performance_schema_events_waits_history_long_size'] + = array( + 'performance_schema_events_waits_history_long_size', + 'performance-schema-system-variables', + 'sysvar', + ); + $variable_doc_links['performance_schema_events_waits_history_size'] = array( + 'performance_schema_events_waits_history_size', + 'performance-schema-system-variables', + 'sysvar'); + $variable_doc_links['performance_schema_hosts_size'] = array( + 'performance_schema_hosts_size', + 'performance-schema-system-variables', + 'sysvar'); + $variable_doc_links['performance_schema_max_cond_classes'] = array( + 'performance_schema_max_cond_classes', + 'performance-schema-system-variables', + 'sysvar'); + $variable_doc_links['performance_schema_max_cond_instances'] = array( + 'performance_schema_max_cond_instances', + 'performance-schema-system-variables', + 'sysvar'); + $variable_doc_links['performance_schema_max_digest_length'] = array( + 'performance_schema_max_digest_length', + 'performance-schema-system-variables', + 'sysvar'); + $variable_doc_links['performance_schema_max_file_classes'] = array( + 'performance_schema_max_file_classes', + 'performance-schema-system-variables', + 'sysvar'); + $variable_doc_links['performance_schema_max_file_handles'] = array( + 'performance_schema_max_file_handles', + 'performance-schema-system-variables', + 'sysvar'); + $variable_doc_links['performance_schema_max_file_instances'] = array( + 'performance_schema_max_file_instances', + 'performance-schema-system-variables', + 'sysvar'); + $variable_doc_links['performance_schema_max_index_stat'] = array( + 'performance_schema_max_index_stat', + 'performance-schema-system-variables', + 'sysvar'); + $variable_doc_links['performance_schema_max_memory_classes'] = array( + 'performance_schema_max_memory_classes', + 'performance-schema-system-variables', + 'sysvar'); + $variable_doc_links['performance_schema_max_metadata_locks'] = array( + 'performance_schema_max_metadata_locks', + 'performance-schema-system-variables', + 'sysvar'); + $variable_doc_links['performance_schema_max_mutex_classes'] = array( + 'performance_schema_max_mutex_classes', + 'performance-schema-system-variables', + 'sysvar'); + $variable_doc_links['performance_schema_max_mutex_instances'] = array( + 'performance_schema_max_mutex_instances', + 'performance-schema-system-variables', + 'sysvar'); + $variable_doc_links['performance_schema_max_prepared_statements_instances'] = array( + 'performance_schema_max_prepared_statements_instances', + 'performance-schema-system-variables', + 'sysvar'); + $variable_doc_links['performance_schema_max_program_instances'] = array( + 'performance_schema_max_program_instances', + 'performance-schema-system-variables', + 'sysvar'); + $variable_doc_links['performance_schema_max_rwlock_classes'] = array( + 'performance_schema_max_rwlock_classes', + 'performance-schema-system-variables', + 'sysvar'); + $variable_doc_links['performance_schema_max_rwlock_instances'] = array( + 'performance_schema_max_rwlock_instances', + 'performance-schema-system-variables', + 'sysvar'); + $variable_doc_links['performance_schema_max_socket_classes'] = array( + 'performance_schema_max_socket_classes', + 'performance-schema-system-variables', + 'sysvar'); + $variable_doc_links['performance_schema_max_socket_instances'] = array( + 'performance_schema_max_socket_instances', + 'performance-schema-system-variables', + 'sysvar'); + $variable_doc_links['performance_schema_max_sql_text_length'] = array( + 'performance_schema_max_sql_text_length', + 'performance-schema-system-variables', + 'sysvar'); + $variable_doc_links['performance_schema_max_stage_classes'] = array( + 'performance_schema_max_stage_classes', + 'performance-schema-system-variables', + 'sysvar'); + $variable_doc_links['performance_schema_max_statement_classes'] = array( + 'performance_schema_max_statement_classes', + 'performance-schema-system-variables', + 'sysvar'); + $variable_doc_links['performance_schema_max_statement_stack'] = array( + 'performance_schema_max_statement_stack', + 'performance-schema-system-variables', + 'sysvar'); + $variable_doc_links['performance_schema_max_table_handles'] = array( + 'performance_schema_max_table_handles', + 'performance-schema-system-variables', + 'sysvar'); + $variable_doc_links['performance_schema_max_table_instances'] = array( + 'performance_schema_max_table_instances', + 'performance-schema-system-variables', + 'sysvar'); + $variable_doc_links['performance_schema_max_table_lock_stat'] = array( + 'performance_schema_max_table_lock_stat', + 'performance-schema-system-variables', + 'sysvar'); + $variable_doc_links['performance_schema_max_thread_classes'] = array( + 'performance_schema_max_thread_classes', + 'performance-schema-system-variables', + 'sysvar'); + $variable_doc_links['performance_schema_max_thread_instances'] = array( + 'performance_schema_max_thread_instances', + 'performance-schema-system-variables', + 'sysvar'); + $variable_doc_links['performance_schema_session_connect_attrs_size'] = array( + 'performance_schema_session_connect_attrs_size', + 'performance-schema-system-variables', + 'sysvar'); + $variable_doc_links['performance_schema_setup_actors_size'] = array( + 'performance_schema_setup_actors_size', + 'performance-schema-system-variables', + 'sysvar'); + $variable_doc_links['performance_schema_setup_objects_size'] = array( + 'performance_schema_setup_objects_size', + 'performance-schema-system-variables', + 'sysvar'); + $variable_doc_links['performance_schema_users_size'] = array( + 'performance_schema_users_size', + 'performance-schema-system-variables', + 'sysvar'); + $variable_doc_links['pid_file'] = array( + 'pid-file', + 'server-options', + 'option_mysqld'); + $variable_doc_links['plugin_dir'] = array( + 'plugin_dir', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['port'] = array( + 'port', + 'server-options', + 'option_mysqld'); + $variable_doc_links['preload_buffer_size'] = array( + 'preload_buffer_size', + 'server-system-variables', + 'sysvar', + 'byte'); + $variable_doc_links['profiling'] = array( + 'profiling', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['profiling_history_size'] = array( + 'profiling_history_size', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['protocol_version'] = array( + 'protocol_version', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['proxy_user'] = array( + 'proxy_user', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['pseudo_thread_id'] = array( + 'pseudo_thread_id', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['query_alloc_block_size'] = array( + 'query_alloc_block_size', + 'server-system-variables', + 'sysvar', + 'byte'); + $variable_doc_links['query_cache_limit'] = array( + 'query_cache_limit', + 'server-system-variables', + 'sysvar', + 'byte'); + $variable_doc_links['query_cache_min_res_unit'] = array( + 'query_cache_min_res_unit', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['query_cache_size'] = array( + 'query_cache_size', + 'server-system-variables', + 'sysvar', + 'byte'); + $variable_doc_links['query_cache_type'] = array( + 'query_cache_type', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['query_cache_wlock_invalidate'] = array( + 'query_cache_wlock_invalidate', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['query_prealloc_size'] = array( + 'query_prealloc_size', + 'server-system-variables', + 'sysvar', + 'byte'); + $variable_doc_links['rand_seed1'] = array( + 'rand_seed1', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['rand_seed2'] = array( + 'rand_seed2', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['range_alloc_block_size'] = array( + 'range_alloc_block_size', + 'server-system-variables', + 'sysvar', + 'byte'); + $variable_doc_links['rbr_exec_mode'] = array( + 'rbr_exec_mode', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['read_buffer_size'] = array( + 'read_buffer_size', + 'server-system-variables', + 'sysvar', + 'byte'); + $variable_doc_links['read_only'] = array( + 'read_only', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['read_rnd_buffer_size'] = array( + 'read_rnd_buffer_size', + 'server-system-variables', + 'sysvar', + 'byte'); + $variable_doc_links['relay_log'] = array( + 'relay_log', + 'replication-options-slave', + 'sysvar'); + $variable_doc_links['relay_log_basename'] = array( + 'relay_log_basename', + 'replication-options-slave', + 'sysvar'); + $variable_doc_links['relay-log-index'] = array( + 'relay-log-index', + 'replication-options-slave', + 'option_mysqld'); + $variable_doc_links['relay_log_index'] = array( + 'relay_log_index', + 'replication-options-slave', + 'sysvar'); + $variable_doc_links['relay_log_info_file'] = array( + 'relay_log_info_file', + 'replication-options-slave', + 'sysvar'); + $variable_doc_links['relay_log_info_repository'] = array( + 'relay_log_info_repository', + 'replication-options-slave', + 'sysvar'); + $variable_doc_links['relay_log_purge'] = array( + 'relay_log_purge', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['relay_log_recovery'] = array( + 'relay_log_recovery', + 'replication-options-slave', + 'sysvar'); + $variable_doc_links['relay_log_space_limit'] = array( + 'relay_log_space_limit', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['report_host'] = array( + 'report-host', + 'replication-options-slave', + 'option_mysqld'); + $variable_doc_links['report_password'] = array( + 'report-password', + 'replication-options-slave', + 'option_mysqld'); + $variable_doc_links['report_port'] = array( + 'report-port', + 'replication-options-slave', + 'option_mysqld'); + $variable_doc_links['report_user'] = array( + 'report-user', + 'replication-options-slave', + 'option_mysqld'); + $variable_doc_links['require_secure_transport'] = array( + 'require_secure_transport', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['rpl_stop_slave_timeout'] = array( + 'rpl_stop_slave_timeout', + 'replication-options-slave', + 'sysvar'); + $variable_doc_links['rpl_recovery_rank'] = array( + 'rpl_recovery_rank', + 'replication-options-slave', + 'option_mysqld'); + $variable_doc_links['rpl_semi_sync_master_enabled'] = array( + 'rpl_semi_sync_master_enabled', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['rpl_semi_sync_master_timeout'] = array( + 'rpl_semi_sync_master_timeout', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['rpl_semi_sync_master_trace_level'] = array( + 'rpl_semi_sync_master_trace_level', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['rpl_semi_sync_master_wait_no_slave'] = array( + 'rpl_semi_sync_master_wait_no_slave', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['rpl_semi_sync_slave_enabled'] = array( + 'rpl_semi_sync_slave_enabled', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['rpl_semi_sync_slave_trace_level'] = array( + 'rpl_semi_sync_slave_trace_level', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['safe_show_database'] = array( + 'safe-show-database', + 'server-options', + 'option_mysqld'); + $variable_doc_links['secure_auth'] = array( + 'secure-auth', + 'server-options', + 'option_mysqld'); + $variable_doc_links['secure_file_priv'] = array( + 'secure-file-priv', + 'server-options', + 'option_mysqld'); + $variable_doc_links['server_id'] = array( + 'server-id', + 'replication-options', + 'option_mysqld'); + $variable_doc_links['server_id_bits'] = array( + 'server_id_bits', + 'mysql-cluster-system-variables', + 'sysvar'); + $variable_doc_links['server_uuid'] = array( + 'server_uuid', + 'replication-options', + 'sysvar'); + $variable_doc_links['session_track_gtids'] = array( + 'session_track_gtids', + 'server_system_variables', + 'sysvar'); + $variable_doc_links['session_track_schema'] = array( + 'session_track_schema', + 'server_system_variables', + 'sysvar'); + $variable_doc_links['session_track_state_change'] = array( + 'session_track_state_change', + 'server_system_variables', + 'sysvar'); + $variable_doc_links['session_track_system_variables'] = array( + 'session_track_system_variables', + 'session_system_variables', + 'sysvar'); + $variable_doc_links['session_track_transaction_info'] = array( + 'session_track_transaction_info', + 'session_system_variables', + 'sysvar'); + $variable_doc_links['sha256_password_proxy_users'] = array( + 'sha256_password_proxy_users', + 'session_system_variables', + 'sysvar'); + $variable_doc_links['show_compatibility_56'] = array( + 'show_compatibility_56', + 'session_system_variables', + 'sysvar'); + $variable_doc_links['show_old_temporals'] = array( + 'show_old_temporals', + 'session_system_variables', + 'sysvar'); + $variable_doc_links['shared_memory'] = array( + 'shared_memory', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['shared_memory_base_name'] = array( + 'shared_memory_base_name', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['skip_external_locking'] = array( + 'skip-external-locking', + 'server-options', + 'option_mysqld'); + $variable_doc_links['skip_name_resolve'] = array( + 'skip-name-resolve', + 'server-options', + 'option_mysqld'); + $variable_doc_links['skip_networking'] = array( + 'skip-networking', + 'server-options', + 'option_mysqld'); + $variable_doc_links['skip_show_database'] = array( + 'skip-show-database', + 'server-options', + 'option_mysqld'); + $variable_doc_links['slave_allow_batching'] = array( + 'slave_allow_batching', + 'mysql-cluster-system-variables', + 'sysvar'); + $variable_doc_links['slave_checkpoint_group'] = array( + 'slave_checkpoint_group', + 'replication-options-slave', + 'sysvar'); + $variable_doc_links['slave_checkpoint_period'] = array( + 'slave_checkpoint_period', + 'replication-options-slave', + 'sysvar'); + $variable_doc_links['slave_compressed_protocol'] = array( + 'slave_compressed_protocol', + 'replication-options-slave', + 'sysvar'); + $variable_doc_links['slave_exec_mode'] = array( + 'slave_exec_mode', + 'replication-options-slave', + 'sysvar'); + $variable_doc_links['slave_load_tmpdir'] = array( + 'slave-load-tmpdir', + 'replication-options-slave', + 'option_mysqld'); + $variable_doc_links['slave_max_allowed_packet'] = array( + 'slave_max_allowed_packet', + 'replication-options-slave', + 'sysvar'); + $variable_doc_links['slave_net_timeout'] = array( + 'slave-net-timeout', + 'replication-options-slave', + 'option_mysqld'); + $variable_doc_links['slave_parallel_type'] = array( + 'slave_parallel_type', + 'replication-options-slave', + 'sysvar'); + $variable_doc_links['slave_parallel_workers'] = array( + 'slave_parallel_workers', + 'replication-options-slave', + 'sysvar'); + $variable_doc_links['slave_pending_jobs_size_max'] = array( + 'slave_pending_jobs_size_max', + 'replication-options-slave', + 'sysvar', + 'byte'); + $variable_doc_links['slave_preserve_commit_order'] = array( + 'slave_preserve_commit_order', + 'replication-options-slave', + 'sysvar'); + $variable_doc_links['slave_rows_search_algorithms'] = array( + 'slave_rows_search_algorithms', + 'replication-options-slave', + 'sysvar'); + $variable_doc_links['slave_skip_errors'] = array( + 'slave-skip-errors', + 'replication-options-slave', + 'option_mysqld'); + $variable_doc_links['slave_sql_verify_checksum'] = array( + 'slave_sql_verify_checksum', + 'replication-options-slave', + 'sysvar'); + $variable_doc_links['slave_transaction_retries'] = array( + 'slave_transaction_retries', + 'replication-options-slave', + 'sysvar'); + $variable_doc_links['slave_type_conversions'] = array( + 'slave_type_conversions', + 'replication-options-slave', + 'sysvar'); + $variable_doc_links['slow_launch_time'] = array( + 'slow_launch_time', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['slow_query_log'] = array( + 'slow-query-log', + 'server-options', + 'server-system-variables'); + $variable_doc_links['slow_query_log_file'] = array( + 'slow_query_log_file', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['socket'] = array( + 'socket', + 'server-options', + 'option_mysqld'); + $variable_doc_links['sort_buffer_size'] = array( + 'sort_buffer_size', + 'server-system-variables', + 'sysvar', + 'byte'); + $variable_doc_links['sql_auto_is_null'] = array( + 'sql_auto_is_null', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['sql_big_selects'] = array( + 'sql_big_selects', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['sql_big_tables'] = array( + 'big-tables', + 'server-options', + 'server-system-variables'); + $variable_doc_links['sql_buffer_result'] = array( + 'sql_buffer_result', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['sql_log_bin'] = array( + 'sql_log_bin', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['sql_log_off'] = array( + 'sql_log_off', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['sql_log_update'] = array( + 'sql_log_update', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['sql_low_priority_updates'] = array( + 'sql_low_priority_updates', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['sql_max_join_size'] = array( + 'sql_max_join_size', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['sql_mode'] = array( + 'sql-mode', + 'server-options', + 'option_mysqld'); + $variable_doc_links['sql_notes'] = array( + 'sql_notes', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['sql_quote_show_create'] = array( + 'sql_quote_show_create', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['sql_safe_updates'] = array( + 'sql_safe_updates', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['sql_select_limit'] = array( + 'sql_select_limit', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['sql_slave_skip_counter'] = array( + 'sql_slave_skip_counter', + 'replication-options-slave', + 'sysvar'); + $variable_doc_links['sql_warnings'] = array( + 'sql_warnings', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['ssl_ca'] = array( + 'ssl-ca', + 'ssl-options', + 'option_general'); + $variable_doc_links['ssl_capath'] = array( + 'ssl-capath', + 'ssl-options', + 'option_general'); + $variable_doc_links['ssl_cert'] = array( + 'ssl-cert', + 'ssl-options', + 'option_general'); + $variable_doc_links['ssl_cipher'] = array( + 'ssl-cipher', + 'ssl-options', + 'option_general'); + $variable_doc_links['ssl_crl'] = array( + 'ssl_crl', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['ssl_crlpath'] = array( + 'ssl_crlpath', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['ssl_key'] = array( + 'ssl-key', + 'ssl-options', + 'option_general'); + $variable_doc_links['storage_engine'] = array( + 'storage_engine', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['stored_program_cache'] = array( + 'stored_program_cache', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['super_read_only'] = array( + 'super_read_only', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['sync_binlog'] = array( + 'sync_binlog', + 'replication-options-binary-log', + 'sysvar'); + $variable_doc_links['sync_frm'] = array( + 'sync_frm', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['sync_master_info'] = array( + 'sync_master_info', + 'replication-options-slave', + 'sysvar'); + $variable_doc_links['sync_relay_log'] = array( + 'sync_relay_log', + 'replication-options-slave', + 'sysvar'); + $variable_doc_links['sync_relay_log_info'] = array( + 'sync_relay_log_info', + 'replication-options-slave', + 'sysvar'); + $variable_doc_links['system_time_zone'] = array( + 'system_time_zone', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['table_definition_cache'] = array( + 'table_definition_cache', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['table_lock_wait_timeout'] = array( + 'table_lock_wait_timeout', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['table_open_cache'] = array( + 'table_open_cache', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['table_open_cache_instances'] = array( + 'table_open_cache_instances', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['table_type'] = array( + 'table_type', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['thread_cache_size'] = array( + 'thread_cache_size', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['thread_concurrency'] = array( + 'thread_concurrency', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['thread_handling'] = array( + 'thread_handling', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['thread_stack'] = array( + 'thread_stack', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['time_format'] = array( + 'time_format', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['time_zone'] = array( + 'time_zone', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['timed_mutexes'] = array( + 'timed_mutexes', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['timestamp'] = array( + 'timestamp', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['tmp_table_size'] = array( + 'tmp_table_size', + 'server-system-variables', + 'sysvar', + 'byte'); + $variable_doc_links['tmpdir'] = array( + 'tmpdir', + 'server-options', + 'option_mysqld'); + $variable_doc_links['transaction_alloc_block_size'] = array( + 'transaction_alloc_block_size', + 'server-system-variables', + 'sysvar', + 'byte'); + $variable_doc_links['transaction_prealloc_size'] = array( + 'transaction_prealloc_size', + 'server-system-variables', + 'sysvar', + 'byte'); + $variable_doc_links['transaction_write_set_extraction'] = array( + 'transaction_write_set_extraction', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['tx_isolation'] = array( + 'tx_isolation', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['tx_read_only'] = array( + 'tx_read_only', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['unique_checks'] = array( + 'unique_checks', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['updatable_views_with_limit'] = array( + 'updatable_views_with_limit', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['version'] = array( + 'version', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['version_comment'] = array( + 'version_comment', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['version_compile_machine'] = array( + 'version_compile_machine', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['version_compile_os'] = array( + 'version_compile_os', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['wait_timeout'] = array( + 'wait_timeout', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['warning_count'] = array( + 'warning_count', + 'server-system-variables', + 'sysvar'); + return $variable_doc_links; + } + + /** + * Returns array of static(i.e. non-editable/ read-only) global system variables + * + * See https://dev.mysql.com/doc/refman/5.6/en/server-system-variables.html + * + * @return array + */ + private function _getStaticSystemVariables() + { + $static_variables = array( + 'audit_log_buffer_size', + 'audit_log_current_session', + 'audit_log_file', + 'audit_log_format', + 'audit_log_policy', + 'audit_log_strategy', + 'auto_generate_certs', + 'back_log', + 'basedir', + 'bind_address', + 'binlog_gtid_simple_recovery', + 'character_set_system', + 'character_sets_dir', + 'core_file', + 'daemon_memcached_enable_binlog', + 'daemon_memcached_engine_lib_name', + 'daemon_memcached_engine_lib_path', + 'daemon_memcached_option', + 'daemon_memcached_r_batch_size', + 'daemon_memcached_w_batch_size', + 'datadir', + 'date_format', + 'datetime_format', + 'default_authentication_plugin', + 'disabled_storage_engines', + 'explicit_defaults_for_timestamp', + 'ft_max_word_len', + 'ft_min_word_len', + 'ft_query_expansion_limit', + 'ft_stopword_file', + 'gtid_owned', + 'have_compress', + 'have_crypt', + 'have_dynamic_loading', + 'have_geometry', + 'have_openssl', + 'have_profiling', + 'have_query_cache', + 'have_rtree_keys', + 'have_ssl', + 'have_statement_timeout', + 'have_symlink', + 'hostname', + 'ignore_builtin_innodb', + 'ignore_db_dirs', + 'init_file', + 'innodb_adaptive_hash_index_parts', + 'innodb_additional_mem_pool_size', + 'innodb_api_disable_rowlock', + 'innodb_api_enable_binlog', + 'innodb_api_enable_mdl', + 'innodb_autoinc_lock_mode', + 'innodb_buffer_pool_chunk_size', + 'innodb_buffer_pool_instances', + 'innodb_buffer_pool_load_at_startup', + 'innodb_checksums', + 'innodb_data_file_path', + 'innodb_data_home_dir', + 'innodb_doublewrite', + 'innodb_file_format_check', + 'innodb_flush_method', + 'innodb_force_load_corrupted', + 'innodb_force_recovery', + 'innodb_ft_cache_size', + 'innodb_ft_max_token_size', + 'innodb_ft_min_token_size', + 'innodb_ft_sort_pll_degree', + 'innodb_ft_total_cache_size', + 'innodb_locks_unsafe_for_binlog', + 'innodb_log_buffer_size', + 'innodb_log_file_size', + 'innodb_log_files_in_group', + 'innodb_log_group_home_dir', + 'innodb_numa_interleave', + 'innodb_open_files', + 'innodb_page_cleaners', + 'innodb_page_size', + 'innodb_purge_threads', + 'innodb_read_io_threads', + 'innodb_read_only', + 'innodb_rollback_on_timeout', + 'innodb_sort_buffer_size', + 'innodb_sync_array_size', + 'innodb_sync_debug', + 'innodb_temp_data_file_path', + 'innodb_undo_directory', + 'innodb_undo_tablespaces', + 'innodb_use_native_aio', + 'innodb_use_sys_malloc', + 'innodb_version', + 'innodb_write_io_threads', + 'language', + 'large_files_support', + 'large_page_size', + 'large_pages', + 'lc_messages_dir', + 'license', + 'locked_in_memory', + 'log-bin', + 'log_bin', + 'log_bin_basename', + 'log_bin_index', + 'log_bin_use_v1_row_events', + 'log_bin_use_v1_row_events', + 'log_error', + 'log_slave_updates', + 'log_slave_updates', + 'lower_case_file_system', + 'lower_case_table_names', + 'max_digest_length', + 'mecab_rc_file', + 'metadata_locks_cache_size', + 'metadata_locks_hash_instances', + 'myisam_mmap_size', + 'myisam_recover_options', + 'named_pipe', + 'ndb-batch-size', + 'ndb-cluster-connection-pool', + 'ndb-cluster-connection-pool-nodeids', + 'ndb_log_apply_status', + 'ndb_log_apply_status', + 'ndb_log_orig', + 'ndb_log_orig', + 'ndb_log_transaction_id', + 'ndb_log_transaction_id', + 'ndb_optimized_node_selection', + 'Ndb_slave_max_replicated_epoch', + 'ndb_use_copying_alter_table', + 'ndb_version', + 'ndb_version_string', + 'ndb-wait-connected', + 'ndb-wait-setup', + 'ndbinfo_database', + 'ndbinfo_version', + 'ngram_token_size', + 'old', + 'open_files_limit', + 'performance_schema', + 'performance_schema_accounts_size', + 'performance_schema_digests_size', + 'performance_schema_events_stages_history_long_size', + 'performance_schema_events_stages_history_size', + 'performance_schema_events_statements_history_long_size', + 'performance_schema_events_statements_history_size', + 'performance_schema_events_transactions_history_long_size', + 'performance_schema_events_transactions_history_size', + 'performance_schema_events_waits_history_long_size', + 'performance_schema_events_waits_history_size', + 'performance_schema_hosts_size', + 'performance_schema_max_cond_classes', + 'performance_schema_max_cond_instances', + 'performance_schema_max_digest_length', + 'performance_schema_max_file_classes', + 'performance_schema_max_file_handles', + 'performance_schema_max_file_instances', + 'performance_schema_max_index_stat', + 'performance_schema_max_memory_classes', + 'performance_schema_max_metadata_locks', + 'performance_schema_max_mutex_classes', + 'performance_schema_max_mutex_instances', + 'performance_schema_max_prepared_statements_instances', + 'performance_schema_max_program_instances', + 'performance_schema_max_rwlock_classes', + 'performance_schema_max_rwlock_instances', + 'performance_schema_max_socket_classes', + 'performance_schema_max_socket_instances', + 'performance_schema_max_sql_text_length', + 'performance_schema_max_stage_classes', + 'performance_schema_max_statement_classes', + 'performance_schema_max_statement_stack', + 'performance_schema_max_table_handles', + 'performance_schema_max_table_instances', + 'performance_schema_max_table_lock_stat', + 'performance_schema_max_thread_classes', + 'performance_schema_max_thread_instances', + 'performance_schema_session_connect_attrs_size', + 'performance_schema_setup_actors_size', + 'performance_schema_setup_objects_size', + 'performance_schema_users_size', + 'pid_file', + 'plugin_dir', + 'port', + 'protocol_version', + 'relay_log', + 'relay_log_basename', + 'relay_log_index', + 'relay_log_index', + 'relay_log_info_file', + 'relay_log_recovery', + 'relay_log_space_limit', + 'report_host', + 'eport_password', + 'report_port', + 'report_user', + 'secure_file_priv', + 'server_id_bits', + 'server_id_bits', + 'server_uuid', + 'sha256_password_auto_generate_rsa_keys', + 'sha256_password_private_key_path', + 'sha256_password_public_key_path', + 'shared_memory', + 'shared_memory_base_name', + 'simplified_binlog_gtid_recovery', + 'skip_external_locking', + 'skip_name_resolve', + 'skip_networking', + 'skip_show_database', + 'slave_load_tmpdir', + 'slave_skip_errors', + 'slave_type_conversions', + 'socket', + 'ssl_ca', + 'ssl_capath', + 'ssl_cert', + 'ssl_cipher', + 'ssl_crl', + 'ssl_crlpath', + 'ssl_key', + 'system_time_zone', + 'table_open_cache_instances', + 'thread_concurrency', + 'thread_handling', + 'thread_stack', + 'time_format', + 'tls_version', + 'tmpdir', + 'validate_user_plugins', + 'version', + 'version_comment', + 'version_compile_machine', + 'version_compile_os', + 'version_tokens_session_number' + ); + + return $static_variables; + } + +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Controllers/Table/TableChartController.php b/php/apps/phpmyadmin49/html/libraries/classes/Controllers/Table/TableChartController.php new file mode 100644 index 00000000..82bb1c11 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Controllers/Table/TableChartController.php @@ -0,0 +1,246 @@ +sql_query = $sql_query; + $this->url_query = $url_query; + $this->cfg = $cfg; + } + + /** + * Execute the query and return the result + * + * @return void + */ + public function indexAction() + { + $response = Response::getInstance(); + if ($response->isAjax() + && isset($_REQUEST['pos']) + && isset($_REQUEST['session_max_rows']) + ) { + $this->ajaxAction(); + return; + } + + // Throw error if no sql query is set + if (!isset($this->sql_query) || $this->sql_query == '') { + $this->response->setRequestStatus(false); + $this->response->addHTML( + Message::error(__('No SQL query was set to fetch data.')) + ); + return; + } + + $this->response->getHeader()->getScripts()->addFiles( + array( + 'chart.js', + 'tbl_chart.js', + 'vendor/jqplot/jquery.jqplot.js', + 'vendor/jqplot/plugins/jqplot.barRenderer.js', + 'vendor/jqplot/plugins/jqplot.canvasAxisLabelRenderer.js', + 'vendor/jqplot/plugins/jqplot.canvasTextRenderer.js', + 'vendor/jqplot/plugins/jqplot.categoryAxisRenderer.js', + 'vendor/jqplot/plugins/jqplot.dateAxisRenderer.js', + 'vendor/jqplot/plugins/jqplot.pointLabels.js', + 'vendor/jqplot/plugins/jqplot.pieRenderer.js', + 'vendor/jqplot/plugins/jqplot.enhancedPieLegendRenderer.js', + 'vendor/jqplot/plugins/jqplot.highlighter.js' + ) + ); + + /** + * Extract values for common work + * @todo Extract common files + */ + $db = &$this->db; + $table = &$this->table; + $url_params = array(); + + /** + * Runs common work + */ + if (strlen($this->table) > 0) { + $url_params['goto'] = Util::getScriptNameForOption( + $this->cfg['DefaultTabTable'], 'table' + ); + $url_params['back'] = 'tbl_sql.php'; + include 'libraries/tbl_common.inc.php'; + $GLOBALS['dbi']->selectDb($GLOBALS['db']); + } elseif (strlen($this->db) > 0) { + $url_params['goto'] = Util::getScriptNameForOption( + $this->cfg['DefaultTabDatabase'], 'database' + ); + $url_params['back'] = 'sql.php'; + include 'libraries/db_common.inc.php'; + } else { + $url_params['goto'] = Util::getScriptNameForOption( + $this->cfg['DefaultTabServer'], 'server' + ); + $url_params['back'] = 'sql.php'; + include 'libraries/server_common.inc.php'; + } + + $data = array(); + + $result = $this->dbi->tryQuery($this->sql_query); + $fields_meta = $this->dbi->getFieldsMeta($result); + while ($row = $this->dbi->fetchAssoc($result)) { + $data[] = $row; + } + + $keys = array_keys($data[0]); + + $numeric_types = array('int', 'real'); + $numeric_column_count = 0; + foreach ($keys as $idx => $key) { + if (in_array($fields_meta[$idx]->type, $numeric_types)) { + $numeric_column_count++; + } + } + + if ($numeric_column_count == 0) { + $this->response->setRequestStatus(false); + $this->response->addJSON( + 'message', + __('No numeric columns present in the table to plot.') + ); + return; + } + + $url_params['db'] = $this->db; + $url_params['reload'] = 1; + + /** + * Displays the page + */ + $this->response->addHTML( + Template::get('table/chart/tbl_chart')->render( + array( + 'url_query' => $this->url_query, + 'url_params' => $url_params, + 'keys' => $keys, + 'fields_meta' => $fields_meta, + 'numeric_types' => $numeric_types, + 'numeric_column_count' => $numeric_column_count, + 'sql_query' => $this->sql_query + ) + ) + ); + } + + /** + * Handle ajax request + * + * @return void + */ + public function ajaxAction() + { + /** + * Extract values for common work + * @todo Extract common files + */ + $db = &$this->db; + $table = &$this->table; + + if (strlen($this->table) > 0 && strlen($this->db) > 0) { + include './libraries/tbl_common.inc.php'; + } + + $parser = new Parser($this->sql_query); + $statement = $parser->statements[0]; + if (empty($statement->limit)) { + $statement->limit = new Limit( + $_REQUEST['session_max_rows'], $_REQUEST['pos'] + ); + } else { + $start = $statement->limit->offset + $_REQUEST['pos']; + $rows = min( + $_REQUEST['session_max_rows'], + $statement->limit->rowCount - $_REQUEST['pos'] + ); + $statement->limit = new Limit($rows, $start); + } + $sql_with_limit = $statement->build(); + + $data = array(); + $result = $this->dbi->tryQuery($sql_with_limit); + while ($row = $this->dbi->fetchAssoc($result)) { + $data[] = $row; + } + + if (empty($data)) { + $this->response->setRequestStatus(false); + $this->response->addJSON('message', __('No data to display')); + return; + } + $sanitized_data = array(); + + foreach ($data as $data_row_number => $data_row) { + $tmp_row = array(); + foreach ($data_row as $data_column => $data_value) { + $tmp_row[htmlspecialchars($data_column)] = htmlspecialchars( + $data_value + ); + } + $sanitized_data[] = $tmp_row; + } + $this->response->setRequestStatus(true); + $this->response->addJSON('message', null); + $this->response->addJSON('chartData', json_encode($sanitized_data)); + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Controllers/Table/TableGisVisualizationController.php b/php/apps/phpmyadmin49/html/libraries/classes/Controllers/Table/TableGisVisualizationController.php new file mode 100644 index 00000000..b0d6d9bd --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Controllers/Table/TableGisVisualizationController.php @@ -0,0 +1,222 @@ +sql_query = $sql_query; + $this->url_params = $url_params; + $this->url_params['goto'] = $goto; + $this->url_params['back'] = $back; + $this->visualizationSettings = $visualizationSettings; + } + + /** + * Save to file + * + * @return void + */ + public function saveToFileAction() + { + $this->response->disable(); + $file_name = $this->visualizationSettings['spatialColumn']; + $save_format = $_GET['fileFormat']; + $this->visualization->toFile($file_name, $save_format); + } + + /** + * Index + * + * @return void + */ + public function indexAction() + { + // Throw error if no sql query is set + if (! isset($this->sql_query) || $this->sql_query == '') { + $this->response->setRequestStatus(false); + $this->response->addHTML( + Message::error(__('No SQL query was set to fetch data.')) + ); + return; + } + + // Execute the query and return the result + $result = $this->dbi->tryQuery($this->sql_query); + // Get the meta data of results + $meta = $this->dbi->getFieldsMeta($result); + + // Find the candidate fields for label column and spatial column + $labelCandidates = array(); + $spatialCandidates = array(); + foreach ($meta as $column_meta) { + if ($column_meta->type == 'geometry') { + $spatialCandidates[] = $column_meta->name; + } else { + $labelCandidates[] = $column_meta->name; + } + } + + // Get settings if any posted + if (Core::isValid($_POST['visualizationSettings'], 'array')) { + $this->visualizationSettings = $_POST['visualizationSettings']; + } + + // Check mysql version + $this->visualizationSettings['mysqlVersion'] = $this->dbi->getVersion(); + + if (!isset($this->visualizationSettings['labelColumn']) + && isset($labelCandidates[0]) + ) { + $this->visualizationSettings['labelColumn'] = ''; + } + + // If spatial column is not set, use first geometric column as spatial column + if (! isset($this->visualizationSettings['spatialColumn'])) { + $this->visualizationSettings['spatialColumn'] = $spatialCandidates[0]; + } + + // Convert geometric columns from bytes to text. + $pos = isset($_GET['pos']) ? $_GET['pos'] + : $_SESSION['tmpval']['pos']; + if (isset($_GET['session_max_rows'])) { + $rows = $_GET['session_max_rows']; + } else { + if ($_SESSION['tmpval']['max_rows'] != 'all') { + $rows = $_SESSION['tmpval']['max_rows']; + } else { + $rows = $GLOBALS['cfg']['MaxRows']; + } + } + $this->visualization = GisVisualization::get( + $this->sql_query, + $this->visualizationSettings, + $rows, + $pos + ); + + if (isset($_GET['saveToFile'])) { + $this->saveToFileAction(); + return; + } + + $this->response->getHeader()->getScripts()->addFiles( + array( + 'vendor/openlayers/OpenLayers.js', + 'vendor/jquery/jquery.svg.js', + 'tbl_gis_visualization.js', + ) + ); + + // If all the rows contain SRID, use OpenStreetMaps on the initial loading. + if (! isset($_POST['displayVisualization'])) { + if ($this->visualization->hasSrid()) { + $this->visualizationSettings['choice'] = 'useBaseLayer'; + } else { + unset($this->visualizationSettings['choice']); + } + } + + $this->visualization->setUserSpecifiedSettings($this->visualizationSettings); + if ($this->visualizationSettings != null) { + foreach ($this->visualization->getSettings() as $setting => $val) { + if (! isset($this->visualizationSettings[$setting])) { + $this->visualizationSettings[$setting] = $val; + } + } + } + + /** + * Displays the page + */ + $this->url_params['sql_query'] = $this->sql_query; + $downloadUrl = 'tbl_gis_visualization.php' . Url::getCommon( + array_merge( + $this->url_params, + array( + 'sql_signature' => Core::signSqlQuery($this->sql_query), + 'saveToFile' => true, + 'session_max_rows' => $rows, + 'pos' => $pos + ) + ) + ); + $html = Template::get('table/gis_visualization/gis_visualization')->render( + array( + 'url_params' => $this->url_params, + 'download_url' => $downloadUrl, + 'label_candidates' => $labelCandidates, + 'spatial_candidates' => $spatialCandidates, + 'visualization_settings' => $this->visualizationSettings, + 'sql_query' => $this->sql_query, + 'visualization' => $this->visualization->toImage('svg'), + 'draw_ol' => $this->visualization->asOl(), + 'pma_theme_image' => $GLOBALS['pmaThemeImage'] + ) + ); + + $this->response->addHTML($html); + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Controllers/Table/TableIndexesController.php b/php/apps/phpmyadmin49/html/libraries/classes/Controllers/Table/TableIndexesController.php new file mode 100644 index 00000000..080d292a --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Controllers/Table/TableIndexesController.php @@ -0,0 +1,177 @@ +index = $index; + } + + /** + * Index + * + * @return void + */ + public function indexAction() + { + if (isset($_POST['do_save_data'])) { + $this->doSaveDataAction(); + return; + } // end builds the new index + + $this->displayFormAction(); + } + + /** + * Display the form to edit/create an index + * + * @return void + */ + public function displayFormAction() + { + $GLOBALS['dbi']->selectDb($GLOBALS['db']); + $add_fields = 0; + if (isset($_POST['index']) && is_array($_POST['index'])) { + // coming already from form + if (isset($_POST['index']['columns']['names'])) { + $add_fields = count($_POST['index']['columns']['names']) + - $this->index->getColumnCount(); + } + if (isset($_POST['add_fields'])) { + $add_fields += $_POST['added_fields']; + } + } elseif (isset($_POST['create_index'])) { + $add_fields = $_POST['added_fields']; + } // end preparing form values + + // Get fields and stores their name/type + if (isset($_POST['create_edit_table'])) { + $fields = json_decode($_POST['columns'], true); + $index_params = array( + 'Non_unique' => ($_POST['index']['Index_choice'] == 'UNIQUE') + ? '0' : '1', + ); + $this->index->set($index_params); + $add_fields = count($fields); + } else { + $fields = $this->dbi->getTable($this->db, $this->table) + ->getNameAndTypeOfTheColumns(); + } + + $form_params = array( + 'db' => $this->db, + 'table' => $this->table, + ); + + if (isset($_POST['create_index'])) { + $form_params['create_index'] = 1; + } elseif (isset($_POST['old_index'])) { + $form_params['old_index'] = $_POST['old_index']; + } elseif (isset($_POST['index'])) { + $form_params['old_index'] = $_POST['index']; + } + + $this->response->getHeader()->getScripts()->addFile('indexes.js'); + + $this->response->addHTML( + Template::get('table/index_form')->render( + array( + 'fields' => $fields, + 'index' => $this->index, + 'form_params' => $form_params, + 'add_fields' => $add_fields, + 'create_edit_table' => isset($_POST['create_edit_table']) + ) + ) + ); + } + + /** + * Process the data from the edit/create index form, + * run the query to build the new index + * and moves back to "tbl_sql.php" + * + * @return void + */ + public function doSaveDataAction() + { + $error = false; + + $sql_query = $this->dbi->getTable($this->db, $this->table) + ->getSqlQueryForIndexCreateOrEdit($this->index, $error); + + // If there is a request for SQL previewing. + if (isset($_POST['preview_sql'])) { + + $this->response->addJSON( + 'sql_data', + Template::get('preview_sql') + ->render( + array( + 'query_data' => $sql_query + ) + ) + ); + } elseif (!$error) { + + $this->dbi->query($sql_query); + $response = Response::getInstance(); + if ($response->isAjax()) { + $message = Message::success( + __('Table %1$s has been altered successfully.') + ); + $message->addParam($this->table); + $this->response->addJSON( + 'message', Util::getMessage($message, $sql_query, 'success') + ); + $this->response->addJSON( + 'index_table', + Index::getHtmlForIndexes( + $this->table, $this->db + ) + ); + } else { + include 'tbl_structure.php'; + } + } else { + $this->response->setRequestStatus(false); + $this->response->addJSON('message', $error); + } + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Controllers/Table/TableRelationController.php b/php/apps/phpmyadmin49/html/libraries/classes/Controllers/Table/TableRelationController.php new file mode 100644 index 00000000..f351f85a --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Controllers/Table/TableRelationController.php @@ -0,0 +1,390 @@ +options_array = $options_array; + $this->cfgRelation = $cfgRelation; + $this->tbl_storage_engine = $tbl_storage_engine; + $this->existrel = $existrel; + $this->existrel_foreign = $existrel_foreign; + $this->upd_query = $upd_query; + $this->relation = new Relation(); + } + + /** + * Index + * + * @return void + */ + public function indexAction() + { + // Send table of column names to populate corresponding dropdowns depending + // on the current selection + if (isset($_POST['getDropdownValues']) + && $_POST['getDropdownValues'] === 'true' + ) { + // if both db and table are selected + if (isset($_POST['foreignTable'])) { + $this->getDropdownValueForTableAction(); + } else { // if only the db is selected + $this->getDropdownValueForDbAction(); + } + return; + } + + $this->response->getHeader()->getScripts()->addFiles( + array( + 'tbl_relation.js', + 'indexes.js' + ) + ); + + // Set the database + $this->dbi->selectDb($this->db); + + // updates for Internal relations + if (isset($_POST['destination_db']) && $this->cfgRelation['relwork']) { + $this->updateForInternalRelationAction(); + } + + // updates for foreign keys + $this->updateForForeignKeysAction(); + + // Updates for display field + if ($this->cfgRelation['displaywork'] && isset($_POST['display_field'])) { + $this->updateForDisplayField(); + } + + // If we did an update, refresh our data + if (isset($_POST['destination_db']) && $this->cfgRelation['relwork']) { + $this->existrel = $this->relation->getForeigners( + $this->db, $this->table, '', 'internal' + ); + } + if (isset($_POST['destination_foreign_db']) + && Util::isForeignKeySupported($this->tbl_storage_engine) + ) { + $this->existrel_foreign = $this->relation->getForeigners( + $this->db, $this->table, '', 'foreign' + ); + } + + // display secondary level tabs if necessary + $engine = $this->dbi->getTable($this->db, $this->table)->getStorageEngine(); + + $this->response->addHTML( + Template::get('table/secondary_tabs')->render( + array( + 'url_params' => array( + 'db' => $GLOBALS['db'], + 'table' => $GLOBALS['table'] + ), + 'is_foreign_key_supported' => Util::isForeignKeySupported($engine), + 'cfg_relation' => $this->relation->getRelationsParam(), + ) + ) + ); + $this->response->addHTML('
      '); + + /** + * Dialog + */ + // Now find out the columns of our $table + // need to use DatabaseInterface::QUERY_STORE with $this->dbi->numRows() + // in mysqli + $columns = $this->dbi->getColumns($this->db, $this->table); + + $column_array = array(); + $column_array[''] = ''; + foreach ($columns as $column) { + if (strtoupper($this->tbl_storage_engine) == 'INNODB' + || ! empty($column['Key']) + ) { + $column_array[$column['Field']] = $column['Field']; + } + } + if ($GLOBALS['cfg']['NaturalOrder']) { + uksort($column_array, 'strnatcasecmp'); + } + + // common form + $this->response->addHTML( + Template::get('table/relation/common_form')->render([ + 'db' => $this->db, + 'table' => $this->table, + 'cfg_relation' => $this->cfgRelation, + 'tbl_storage_engine' => $this->tbl_storage_engine, + 'existrel' => isset($this->existrel) ? $this->existrel : array(), + 'existrel_foreign' => isset($this->existrel_foreign) + ? $this->existrel_foreign['foreign_keys_data'] : array(), + 'options_array' => $this->options_array, + 'column_array' => $column_array, + 'save_row' => array_values($columns), + 'url_params' => $GLOBALS['url_params'], + 'databases' => $GLOBALS['dblist']->databases, + 'dbi' => $GLOBALS['dbi'], + ]) + ); + + if (Util::isForeignKeySupported($this->tbl_storage_engine)) { + $this->response->addHTML(Index::getHtmlForDisplayIndexes()); + } + $this->response->addHTML('
      '); + } + + /** + * Update for display field + * + * @return void + */ + public function updateForDisplayField() + { + if ($this->upd_query->updateDisplayField( + $_POST['display_field'], $this->cfgRelation + ) + ) { + $this->response->addHTML( + Util::getMessage( + __('Display column was successfully updated.'), + '', 'success' + ) + ); + } + } + + /** + * Update for FK + * + * @return void + */ + public function updateForForeignKeysAction() + { + $multi_edit_columns_name = isset($_POST['foreign_key_fields_name']) + ? $_POST['foreign_key_fields_name'] + : null; + $preview_sql_data = ''; + $seen_error = false; + + // (for now, one index name only; we keep the definitions if the + // foreign db is not the same) + if (isset($_POST['destination_foreign_db']) + && isset($_POST['destination_foreign_table']) + && isset($_POST['destination_foreign_column'])) { + list($html, $preview_sql_data, $display_query, $seen_error) + = $this->upd_query->updateForeignKeys( + $_POST['destination_foreign_db'], + $multi_edit_columns_name, $_POST['destination_foreign_table'], + $_POST['destination_foreign_column'], $this->options_array, + $this->table, + isset($this->existrel_foreign) + ? $this->existrel_foreign['foreign_keys_data'] + : null + ); + $this->response->addHTML($html); + } + + // If there is a request for SQL previewing. + if (isset($_POST['preview_sql'])) { + Core::previewSQL($preview_sql_data); + } + + if (!empty($display_query) && !$seen_error) { + $GLOBALS['display_query'] = $display_query; + $this->response->addHTML( + Util::getMessage( + __('Your SQL query has been executed successfully.'), + null, 'success' + ) + ); + } + } + + /** + * Update for internal relation + * + * @return void + */ + public function updateForInternalRelationAction() + { + $multi_edit_columns_name = isset($_POST['fields_name']) + ? $_POST['fields_name'] + : null; + + if ($this->upd_query->updateInternalRelations( + $multi_edit_columns_name, + $_POST['destination_db'], + $_POST['destination_table'], + $_POST['destination_column'], + $this->cfgRelation, + isset($this->existrel) ? $this->existrel : null + ) + ) { + $this->response->addHTML( + Util::getMessage( + __('Internal relationships were successfully updated.'), + '', 'success' + ) + ); + } + } + + /** + * Send table columns for foreign table dropdown + * + * @return void + * + */ + public function getDropdownValueForTableAction() + { + $foreignTable = $_POST['foreignTable']; + $table_obj = $this->dbi->getTable($_POST['foreignDb'], $foreignTable); + // Since views do not have keys defined on them provide the full list of + // columns + if ($table_obj->isView()) { + $columnList = $table_obj->getColumns(false, false); + } else { + $columnList = $table_obj->getIndexedColumns(false, false); + } + $columns = array(); + foreach ($columnList as $column) { + $columns[] = htmlspecialchars($column); + } + if ($GLOBALS['cfg']['NaturalOrder']) { + usort($columns, 'strnatcasecmp'); + } + $this->response->addJSON('columns', $columns); + + // @todo should be: $server->db($db)->table($table)->primary() + $primary = Index::getPrimary($foreignTable, $_POST['foreignDb']); + if (false === $primary) { + return; + } + + $this->response->addJSON('primary', array_keys($primary->getColumns())); + } + + /** + * Send database selection values for dropdown + * + * @return void + * + */ + public function getDropdownValueForDbAction() + { + $tables = array(); + $foreign = isset($_POST['foreign']) && $_POST['foreign'] === 'true'; + + if ($foreign) { + $query = 'SHOW TABLE STATUS FROM ' + . Util::backquote($_POST['foreignDb']); + $tables_rs = $this->dbi->query( + $query, + DatabaseInterface::CONNECT_USER, + DatabaseInterface::QUERY_STORE + ); + + while ($row = $this->dbi->fetchArray($tables_rs)) { + if (isset($row['Engine']) + && mb_strtoupper($row['Engine']) == $this->tbl_storage_engine + ) { + $tables[] = htmlspecialchars($row['Name']); + } + } + } else { + $query = 'SHOW TABLES FROM ' + . Util::backquote($_POST['foreignDb']); + $tables_rs = $this->dbi->query( + $query, + DatabaseInterface::CONNECT_USER, + DatabaseInterface::QUERY_STORE + ); + while ($row = $this->dbi->fetchArray($tables_rs)) { + $tables[] = htmlspecialchars($row[0]); + } + } + if ($GLOBALS['cfg']['NaturalOrder']) { + usort($tables, 'strnatcasecmp'); + } + $this->response->addJSON('tables', $tables); + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Controllers/Table/TableSearchController.php b/php/apps/phpmyadmin49/html/libraries/classes/Controllers/Table/TableSearchController.php new file mode 100644 index 00000000..90f945f0 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Controllers/Table/TableSearchController.php @@ -0,0 +1,1166 @@ +url_query = $url_query; + $this->_searchType = $searchType; + $this->_columnNames = array(); + $this->_columnNullFlags = array(); + $this->_columnTypes = array(); + $this->_columnCollations = array(); + $this->_geomColumnFlag = false; + $this->_foreigners = array(); + $this->relation = new Relation(); + // Loads table's information + $this->_loadTableInfo(); + $this->_connectionCharSet = $this->dbi->fetchValue( + "SELECT @@character_set_connection" + ); + } + + /** + * Gets all the columns of a table along with their types, collations + * and whether null or not. + * + * @return void + */ + private function _loadTableInfo() + { + // Gets the list and number of columns + $columns = $this->dbi->getColumns( + $this->db, $this->table, null, true + ); + // Get details about the geometry functions + $geom_types = Util::getGISDatatypes(); + + foreach ($columns as $row) { + // set column name + $this->_columnNames[] = $row['Field']; + + $type = $row['Type']; + // check whether table contains geometric columns + if (in_array($type, $geom_types)) { + $this->_geomColumnFlag = true; + } + // reformat mysql query output + if (strncasecmp($type, 'set', 3) == 0 + || strncasecmp($type, 'enum', 4) == 0 + ) { + $type = str_replace(',', ', ', $type); + } else { + // strip the "BINARY" attribute, except if we find "BINARY(" because + // this would be a BINARY or VARBINARY column type + if (! preg_match('@BINARY[\(]@i', $type)) { + $type = preg_replace('@BINARY@i', '', $type); + } + $type = preg_replace('@ZEROFILL@i', '', $type); + $type = preg_replace('@UNSIGNED@i', '', $type); + $type = mb_strtolower($type); + } + if (empty($type)) { + $type = ' '; + } + $this->_columnTypes[] = $type; + $this->_columnNullFlags[] = $row['Null']; + $this->_columnCollations[] + = ! empty($row['Collation']) && $row['Collation'] != 'NULL' + ? $row['Collation'] + : ''; + } // end for + + // Retrieve foreign keys + $this->_foreigners = $this->relation->getForeigners($this->db, $this->table); + } + + /** + * Index action + * + * @return void + */ + public function indexAction() + { + switch ($this->_searchType) { + case 'replace': + if (isset($_POST['find'])) { + $this->findAction(); + + return; + } + $this->response + ->getHeader() + ->getScripts() + ->addFile('tbl_find_replace.js'); + + if (isset($_POST['replace'])) { + $this->replaceAction(); + } + + // Displays the find and replace form + $this->displaySelectionFormAction(); + break; + + case 'normal': + $this->response->getHeader() + ->getScripts() + ->addFiles( + array( + 'makegrid.js', + 'sql.js', + 'tbl_select.js', + 'tbl_change.js', + 'vendor/jquery/jquery.uitablefilter.js', + 'gis_data_editor.js', + ) + ); + + if (isset($_POST['range_search'])) { + $this->rangeSearchAction(); + + return; + } + + /** + * No selection criteria received -> display the selection form + */ + if (!isset($_POST['columnsToDisplay']) + && !isset($_POST['displayAllColumns']) + ) { + $this->displaySelectionFormAction(); + } else { + $this->doSelectionAction(); + } + break; + + case 'zoom': + $this->response->getHeader() + ->getScripts() + ->addFiles( + array( + 'makegrid.js', + 'sql.js', + 'vendor/jqplot/jquery.jqplot.js', + 'vendor/jqplot/plugins/jqplot.canvasTextRenderer.js', + 'vendor/jqplot/plugins/jqplot.canvasAxisLabelRenderer.js', + 'vendor/jqplot/plugins/jqplot.dateAxisRenderer.js', + 'vendor/jqplot/plugins/jqplot.highlighter.js', + 'vendor/jqplot/plugins/jqplot.cursor.js', + 'tbl_zoom_plot_jqplot.js', + 'tbl_change.js', + ) + ); + + /** + * Handle AJAX request for data row on point select + * + * @var boolean Object containing parameters for the POST request + */ + if (isset($_POST['get_data_row']) + && $_POST['get_data_row'] == true + ) { + $this->getDataRowAction(); + + return; + } + /** + * Handle AJAX request for changing field information + * (value,collation,operators,field values) in input form + * + * @var boolean Object containing parameters for the POST request + */ + if (isset($_POST['change_tbl_info']) + && $_POST['change_tbl_info'] == true + ) { + $this->changeTableInfoAction(); + + return; + } + + //Set default datalabel if not selected + if (!isset($_POST['zoom_submit']) || $_POST['dataLabel'] == '') { + $dataLabel = $this->relation->getDisplayField($this->db, $this->table); + } else { + $dataLabel = $_POST['dataLabel']; + } + + // Displays the zoom search form + $this->displaySelectionFormAction($dataLabel); + + /* + * Handle the input criteria and generate the query result + * Form for displaying query results + */ + if (isset($_POST['zoom_submit']) + && $_POST['criteriaColumnNames'][0] != 'pma_null' + && $_POST['criteriaColumnNames'][1] != 'pma_null' + && $_POST['criteriaColumnNames'][0] != $_POST['criteriaColumnNames'][1] + ) { + if (! isset($goto)) { + $goto = Util::getScriptNameForOption( + $GLOBALS['cfg']['DefaultTabTable'], 'table' + ); + } + $this->zoomSubmitAction($dataLabel, $goto); + } + break; + } + } + + /** + * Zoom submit action + * + * @param string $dataLabel Data label + * @param string $goto Goto + * + * @return void + */ + public function zoomSubmitAction($dataLabel, $goto) + { + //Query generation part + $sql_query = $this->_buildSqlQuery(); + $sql_query .= ' LIMIT ' . $_POST['maxPlotLimit']; + + //Query execution part + $result = $this->dbi->query( + $sql_query . ";", + DatabaseInterface::CONNECT_USER, + DatabaseInterface::QUERY_STORE + ); + $fields_meta = $this->dbi->getFieldsMeta($result); + $data = array(); + while ($row = $this->dbi->fetchAssoc($result)) { + //Need a row with indexes as 0,1,2 for the getUniqueCondition + // hence using a temporary array + $tmpRow = array(); + foreach ($row as $val) { + $tmpRow[] = $val; + } + //Get unique condition on each row (will be needed for row update) + $uniqueCondition = Util::getUniqueCondition( + $result, // handle + count($this->_columnNames), // fields_cnt + $fields_meta, // fields_meta + $tmpRow, // row + true, // force_unique + false, // restrict_to_table + null // analyzed_sql_results + ); + //Append it to row array as where_clause + $row['where_clause'] = $uniqueCondition[0]; + + $tmpData = array( + $_POST['criteriaColumnNames'][0] => + $row[$_POST['criteriaColumnNames'][0]], + $_POST['criteriaColumnNames'][1] => + $row[$_POST['criteriaColumnNames'][1]], + 'where_clause' => $uniqueCondition[0] + ); + $tmpData[$dataLabel] = ($dataLabel) ? $row[$dataLabel] : ''; + $data[] = $tmpData; + } + unset($tmpData); + + //Displays form for point data and scatter plot + $titles = array( + 'Browse' => Util::getIcon( + 'b_browse', + __('Browse foreign values') + ) + ); + $this->response->addHTML( + Template::get('table/search/zoom_result_form')->render([ + 'db' => $this->db, + 'table' => $this->table, + 'column_names' => $this->_columnNames, + 'foreigners' => $this->_foreigners, + 'column_null_flags' => $this->_columnNullFlags, + 'column_types' => $this->_columnTypes, + 'titles' => $titles, + 'goto' => $goto, + 'data' => $data, + 'data_json' => json_encode($data), + 'zoom_submit' => isset($_POST['zoom_submit']), + 'foreign_max_limit' => $GLOBALS['cfg']['ForeignKeyMaxLimit'], + ]) + ); + } + + /** + * Change table info action + * + * @return void + */ + public function changeTableInfoAction() + { + $field = $_POST['field']; + if ($field == 'pma_null') { + $this->response->addJSON('field_type', ''); + $this->response->addJSON('field_collation', ''); + $this->response->addJSON('field_operators', ''); + $this->response->addJSON('field_value', ''); + return; + } + $key = array_search($field, $this->_columnNames); + $search_index + = ((isset($_POST['it']) && is_numeric($_POST['it'])) + ? intval($_POST['it']) : 0); + + $properties = $this->getColumnProperties($search_index, $key); + $this->response->addJSON( + 'field_type', htmlspecialchars($properties['type']) + ); + $this->response->addJSON('field_collation', $properties['collation']); + $this->response->addJSON('field_operators', $properties['func']); + $this->response->addJSON('field_value', $properties['value']); + } + + /** + * Get data row action + * + * @return void + */ + public function getDataRowAction() + { + $extra_data = array(); + $row_info_query = 'SELECT * FROM `' . $_POST['db'] . '`.`' + . $_POST['table'] . '` WHERE ' . $_POST['where_clause']; + $result = $this->dbi->query( + $row_info_query . ";", + DatabaseInterface::CONNECT_USER, + DatabaseInterface::QUERY_STORE + ); + $fields_meta = $this->dbi->getFieldsMeta($result); + while ($row = $this->dbi->fetchAssoc($result)) { + // for bit fields we need to convert them to printable form + $i = 0; + foreach ($row as $col => $val) { + if ($fields_meta[$i]->type == 'bit') { + $row[$col] = Util::printableBitValue( + $val, $fields_meta[$i]->length + ); + } + $i++; + } + $extra_data['row_info'] = $row; + } + $this->response->addJSON($extra_data); + } + + /** + * Do selection action + * + * @return void + */ + public function doSelectionAction() + { + /** + * Selection criteria have been submitted -> do the work + */ + $sql_query = $this->_buildSqlQuery(); + + /** + * Add this to ensure following procedures included running correctly. + */ + $db = $this->db; + + $sql = new Sql(); + $sql->executeQueryAndSendQueryResponse( + null, // analyzed_sql_results + false, // is_gotofile + $this->db, // db + $this->table, // table + null, // find_real_end + null, // sql_query_for_bookmark + null, // extra_data + null, // message_to_show + null, // message + null, // sql_data + $GLOBALS['goto'], // goto + $GLOBALS['pmaThemeImage'], // pmaThemeImage + null, // disp_query + null, // disp_message + null, // query_type + $sql_query, // sql_query + null, // selectedTables + null // complete_query + ); + } + + /** + * Display selection form action + * + * @param string $dataLabel Data label + * + * @return void + */ + public function displaySelectionFormAction($dataLabel = null) + { + $this->url_query .= '&goto=tbl_select.php&back=tbl_select.php'; + if (! isset($goto)) { + $goto = Util::getScriptNameForOption( + $GLOBALS['cfg']['DefaultTabTable'], 'table' + ); + } + // Displays the table search form + $this->response->addHTML( + Template::get('secondary_tabs') + ->render( + array( + 'url_params' => array( + 'db' => $this->db, + 'table' => $this->table, + ), + 'sub_tabs' => $this->_getSubTabs(), + ) + ) + ); + $this->response->addHTML( + Template::get('table/search/selection_form')->render(array( + 'search_type' => $this->_searchType, + 'db' => $this->db, + 'table' => $this->table, + 'goto' => $goto, + 'self' => $this, + 'geom_column_flag' => $this->_geomColumnFlag, + 'column_names' => $this->_columnNames, + 'column_types' => $this->_columnTypes, + 'column_collations' => $this->_columnCollations, + 'data_label' => $dataLabel, + 'criteria_column_names' => isset($_POST['criteriaColumnNames']) ? $_POST['criteriaColumnNames'] : null, + 'criteria_column_types' => isset($_POST['criteriaColumnTypes']) ? $_POST['criteriaColumnTypes'] : null, + 'sql_types' => $GLOBALS['dbi']->types, + 'max_rows' => intval($GLOBALS['cfg']['MaxRows']), + 'max_plot_limit' => ((! empty($_POST['maxPlotLimit'])) + ? intval($_POST['maxPlotLimit']) + : intval($GLOBALS['cfg']['maxRowPlotLimit'])), + )) + ); + } + + /** + * Range search action + * + * @return void + */ + public function rangeSearchAction() + { + $min_max = $this->getColumnMinMax($_POST['column']); + $this->response->addJSON('column_data', $min_max); + } + + /** + * Find action + * + * @return void + */ + public function findAction() + { + $useRegex = array_key_exists('useRegex', $_POST) + && $_POST['useRegex'] == 'on'; + + $preview = $this->getReplacePreview( + $_POST['columnIndex'], + $_POST['find'], + $_POST['replaceWith'], + $useRegex, + $this->_connectionCharSet + ); + $this->response->addJSON('preview', $preview); + } + + /** + * Replace action + * + * @return void + */ + public function replaceAction() + { + $this->replace( + $_POST['columnIndex'], + $_POST['findString'], + $_POST['replaceWith'], + $_POST['useRegex'], + $this->_connectionCharSet + ); + $this->response->addHTML( + Util::getMessage( + __('Your SQL query has been executed successfully.'), + null, 'success' + ) + ); + } + + /** + * Returns HTML for previewing strings found and their replacements + * + * @param int $columnIndex index of the column + * @param string $find string to find in the column + * @param string $replaceWith string to replace with + * @param boolean $useRegex to use Regex replace or not + * @param string $charSet character set of the connection + * + * @return string HTML for previewing strings found and their replacements + */ + function getReplacePreview( + $columnIndex, $find, $replaceWith, $useRegex, $charSet + ) { + $column = $this->_columnNames[$columnIndex]; + if ($useRegex) { + $result = $this->_getRegexReplaceRows( + $columnIndex, $find, $replaceWith, $charSet + ); + } else { + $sql_query = "SELECT " + . Util::backquote($column) . "," + . " REPLACE(" + . Util::backquote($column) . ", '" . $find . "', '" + . $replaceWith + . "')," + . " COUNT(*)" + . " FROM " . Util::backquote($this->db) + . "." . Util::backquote($this->table) + . " WHERE " . Util::backquote($column) + . " LIKE '%" . $find . "%' COLLATE " . $charSet . "_bin"; // here we + // change the collation of the 2nd operand to a case sensitive + // binary collation to make sure that the comparison + // is case sensitive + $sql_query .= " GROUP BY " . Util::backquote($column) + . " ORDER BY " . Util::backquote($column) . " ASC"; + + $result = $this->dbi->fetchResult($sql_query, 0); + } + + return Template::get('table/search/replace_preview')->render( + array( + 'db' => $this->db, + 'table' => $this->table, + 'column_index' => $columnIndex, + 'find' => $find, + 'replace_with' => $replaceWith, + 'use_regex' => $useRegex, + 'result' => $result + ) + ); + } + + /** + * Finds and returns Regex pattern and their replacements + * + * @param int $columnIndex index of the column + * @param string $find string to find in the column + * @param string $replaceWith string to replace with + * @param string $charSet character set of the connection + * + * @return array Array containing original values, replaced values and count + */ + private function _getRegexReplaceRows( + $columnIndex, $find, $replaceWith, $charSet + ) { + $column = $this->_columnNames[$columnIndex]; + $sql_query = "SELECT " + . Util::backquote($column) . "," + . " 1," // to add an extra column that will have replaced value + . " COUNT(*)" + . " FROM " . Util::backquote($this->db) + . "." . Util::backquote($this->table) + . " WHERE " . Util::backquote($column) + . " RLIKE '" . $GLOBALS['dbi']->escapeString($find) . "' COLLATE " + . $charSet . "_bin"; // here we + // change the collation of the 2nd operand to a case sensitive + // binary collation to make sure that the comparison is case sensitive + $sql_query .= " GROUP BY " . Util::backquote($column) + . " ORDER BY " . Util::backquote($column) . " ASC"; + + $result = $this->dbi->fetchResult($sql_query, 0); + + if (is_array($result)) { + /* Iterate over possible delimiters to get one */ + $delimiters = array('/', '@', '#', '~', '!', '$', '%', '^', '&', '_'); + $found = false; + for ($i = 0, $l = count($delimiters); $i < $l; $i++) { + if (strpos($find, $delimiters[$i]) === false) { + $found = true; + break; + } + } + if (! $found) { + return false; + } + $find = $delimiters[$i] . $find . $delimiters[$i]; + foreach ($result as $index=>$row) { + $result[$index][1] = preg_replace( + $find, + $replaceWith, + $row[0] + ); + } + } + return $result; + } + + /** + * Replaces a given string in a column with a give replacement + * + * @param int $columnIndex index of the column + * @param string $find string to find in the column + * @param string $replaceWith string to replace with + * @param boolean $useRegex to use Regex replace or not + * @param string $charSet character set of the connection + * + * @return void + */ + public function replace($columnIndex, $find, $replaceWith, $useRegex, + $charSet + ) { + $column = $this->_columnNames[$columnIndex]; + if ($useRegex) { + $toReplace = $this->_getRegexReplaceRows( + $columnIndex, $find, $replaceWith, $charSet + ); + $sql_query = "UPDATE " . Util::backquote($this->table) + . " SET " . Util::backquote($column) . " = CASE"; + if (is_array($toReplace)) { + foreach ($toReplace as $row) { + $sql_query .= "\n WHEN " . Util::backquote($column) + . " = '" . $GLOBALS['dbi']->escapeString($row[0]) + . "' THEN '" . $GLOBALS['dbi']->escapeString($row[1]) . "'"; + } + } + $sql_query .= " END" + . " WHERE " . Util::backquote($column) + . " RLIKE '" . $GLOBALS['dbi']->escapeString($find) . "' COLLATE " + . $charSet . "_bin"; // here we + // change the collation of the 2nd operand to a case sensitive + // binary collation to make sure that the comparison + // is case sensitive + } else { + $sql_query = "UPDATE " . Util::backquote($this->table) + . " SET " . Util::backquote($column) . " =" + . " REPLACE(" + . Util::backquote($column) . ", '" . $find . "', '" + . $replaceWith + . "')" + . " WHERE " . Util::backquote($column) + . " LIKE '%" . $find . "%' COLLATE " . $charSet . "_bin"; // here we + // change the collation of the 2nd operand to a case sensitive + // binary collation to make sure that the comparison + // is case sensitive + } + $this->dbi->query( + $sql_query, + DatabaseInterface::CONNECT_USER, + DatabaseInterface::QUERY_STORE + ); + $GLOBALS['sql_query'] = $sql_query; + } + + /** + * Finds minimum and maximum value of a given column. + * + * @param string $column Column name + * + * @return array + */ + public function getColumnMinMax($column) + { + $sql_query = 'SELECT MIN(' . Util::backquote($column) . ') AS `min`, ' + . 'MAX(' . Util::backquote($column) . ') AS `max` ' + . 'FROM ' . Util::backquote($this->db) . '.' + . Util::backquote($this->table); + + $result = $this->dbi->fetchSingleRow($sql_query); + + return $result; + } + + /** + * Returns an array with necessary configurations to create + * sub-tabs in the table_select page. + * + * @return array Array containing configuration (icon, text, link, id, args) + * of sub-tabs + */ + private function _getSubTabs() + { + $subtabs = array(); + $subtabs['search']['icon'] = 'b_search'; + $subtabs['search']['text'] = __('Table search'); + $subtabs['search']['link'] = 'tbl_select.php'; + $subtabs['search']['id'] = 'tbl_search_id'; + $subtabs['search']['args']['pos'] = 0; + + $subtabs['zoom']['icon'] = 'b_select'; + $subtabs['zoom']['link'] = 'tbl_zoom_select.php'; + $subtabs['zoom']['text'] = __('Zoom search'); + $subtabs['zoom']['id'] = 'zoom_search_id'; + + $subtabs['replace']['icon'] = 'b_find_replace'; + $subtabs['replace']['link'] = 'tbl_find_replace.php'; + $subtabs['replace']['text'] = __('Find and replace'); + $subtabs['replace']['id'] = 'find_replace_id'; + + return $subtabs; + } + + /** + * Builds the sql search query from the post parameters + * + * @return string the generated SQL query + */ + private function _buildSqlQuery() + { + $sql_query = 'SELECT '; + + // If only distinct values are needed + $is_distinct = (isset($_POST['distinct'])) ? 'true' : 'false'; + if ($is_distinct == 'true') { + $sql_query .= 'DISTINCT '; + } + + // if all column names were selected to display, we do a 'SELECT *' + // (more efficient and this helps prevent a problem in IE + // if one of the rows is edited and we come back to the Select results) + if (isset($_POST['zoom_submit']) || ! empty($_POST['displayAllColumns'])) { + $sql_query .= '* '; + } else { + $sql_query .= implode( + ', ', + Util::backquote($_POST['columnsToDisplay']) + ); + } // end if + + $sql_query .= ' FROM ' + . Util::backquote($_POST['table']); + $whereClause = $this->_generateWhereClause(); + $sql_query .= $whereClause; + + // if the search results are to be ordered + if (isset($_POST['orderByColumn']) && $_POST['orderByColumn'] != '--nil--') { + $sql_query .= ' ORDER BY ' + . Util::backquote($_POST['orderByColumn']) + . ' ' . $_POST['order']; + } // end if + return $sql_query; + } + + /** + * Provides a column's type, collation, operators list, and criteria value + * to display in table search form + * + * @param integer $search_index Row number in table search form + * @param integer $column_index Column index in ColumnNames array + * + * @return array Array containing column's properties + */ + public function getColumnProperties($search_index, $column_index) + { + $selected_operator = (isset($_POST['criteriaColumnOperators'][$search_index]) + ? $_POST['criteriaColumnOperators'][$search_index] : ''); + $entered_value = (isset($_POST['criteriaValues']) + ? $_POST['criteriaValues'] : ''); + $titles = array( + 'Browse' => Util::getIcon( + 'b_browse', __('Browse foreign values') + ) + ); + //Gets column's type and collation + $type = $this->_columnTypes[$column_index]; + $collation = $this->_columnCollations[$column_index]; + //Gets column's comparison operators depending on column type + $typeOperators = $GLOBALS['dbi']->types->getTypeOperatorsHtml( + preg_replace('@\(.*@s', '', $this->_columnTypes[$column_index]), + $this->_columnNullFlags[$column_index], $selected_operator + ); + $func = Template::get('table/search/column_comparison_operators')->render( + array( + 'search_index' => $search_index, + 'type_operators' => $typeOperators + ) + ); + //Gets link to browse foreign data(if any) and criteria inputbox + $foreignData = $this->relation->getForeignData( + $this->_foreigners, $this->_columnNames[$column_index], false, '', '' + ); + $value = Template::get('table/search/input_box')->render( + array( + 'str' => '', + 'column_type' => (string) $type, + 'column_id' => 'fieldID_', + 'in_zoom_search_edit' => false, + 'foreigners' => $this->_foreigners, + 'column_name' => $this->_columnNames[$column_index], + 'column_name_hash' => md5($this->_columnNames[$column_index]), + 'foreign_data' => $foreignData, + 'table' => $this->table, + 'column_index' => $search_index, + 'foreign_max_limit' => $GLOBALS['cfg']['ForeignKeyMaxLimit'], + 'criteria_values' => $entered_value, + 'db' => $this->db, + 'titles' => $titles, + 'in_fbs' => true + ) + ); + return array( + 'type' => $type, + 'collation' => $collation, + 'func' => $func, + 'value' => $value + ); + } + + /** + * Generates the where clause for the SQL search query to be executed + * + * @return string the generated where clause + */ + private function _generateWhereClause() + { + if (isset($_POST['customWhereClause']) + && trim($_POST['customWhereClause']) != '' + ) { + return ' WHERE ' . $_POST['customWhereClause']; + } + + // If there are no search criteria set or no unary criteria operators, + // return + if (! isset($_POST['criteriaValues']) + && ! isset($_POST['criteriaColumnOperators']) + && ! isset($_POST['geom_func']) + ) { + return ''; + } + + // else continue to form the where clause from column criteria values + $fullWhereClause = array(); + foreach ($_POST['criteriaColumnOperators'] as $column_index => $operator) { + $unaryFlag = $GLOBALS['dbi']->types->isUnaryOperator($operator); + $tmp_geom_func = isset($_POST['geom_func'][$column_index]) + ? $_POST['geom_func'][$column_index] : null; + + $whereClause = $this->_getWhereClause( + $_POST['criteriaValues'][$column_index], + $_POST['criteriaColumnNames'][$column_index], + $_POST['criteriaColumnTypes'][$column_index], + $operator, + $unaryFlag, + $tmp_geom_func + ); + + if ($whereClause) { + $fullWhereClause[] = $whereClause; + } + } // end foreach + + if (!empty($fullWhereClause)) { + return ' WHERE ' . implode(' AND ', $fullWhereClause); + } + return ''; + } + + /** + * Return the where clause in case column's type is ENUM. + * + * @param mixed $criteriaValues Search criteria input + * @param string $func_type Search function/operator + * + * @return string part of where clause. + */ + private function _getEnumWhereClause($criteriaValues, $func_type) + { + if (! is_array($criteriaValues)) { + $criteriaValues = explode(',', $criteriaValues); + } + $enum_selected_count = count($criteriaValues); + if ($func_type == '=' && $enum_selected_count > 1) { + $func_type = 'IN'; + $parens_open = '('; + $parens_close = ')'; + + } elseif ($func_type == '!=' && $enum_selected_count > 1) { + $func_type = 'NOT IN'; + $parens_open = '('; + $parens_close = ')'; + + } else { + $parens_open = ''; + $parens_close = ''; + } + $enum_where = '\'' + . $GLOBALS['dbi']->escapeString($criteriaValues[0]) . '\''; + for ($e = 1; $e < $enum_selected_count; $e++) { + $enum_where .= ', \'' + . $GLOBALS['dbi']->escapeString($criteriaValues[$e]) . '\''; + } + + return ' ' . $func_type . ' ' . $parens_open + . $enum_where . $parens_close; + } + + /** + * Return the where clause for a geometrical column. + * + * @param mixed $criteriaValues Search criteria input + * @param string $names Name of the column on which search is submitted + * @param string $func_type Search function/operator + * @param string $types Type of the field + * @param bool $geom_func Whether geometry functions should be applied + * + * @return string part of where clause. + */ + private function _getGeomWhereClause($criteriaValues, $names, + $func_type, $types, $geom_func = null + ) { + $geom_unary_functions = array( + 'IsEmpty' => 1, + 'IsSimple' => 1, + 'IsRing' => 1, + 'IsClosed' => 1, + ); + $where = ''; + + // Get details about the geometry functions + $geom_funcs = Util::getGISFunctions($types, true, false); + + // If the function takes multiple parameters + if(strpos($func_type, "IS NULL") !== false || strpos($func_type, "IS NOT NULL") !== false) { + $where = Util::backquote($names) . " " . $func_type; + return $where; + } elseif ($geom_funcs[$geom_func]['params'] > 1) { + // create gis data from the criteria input + $gis_data = Util::createGISData($criteriaValues, $this->dbi->getVersion()); + $where = $geom_func . '(' . Util::backquote($names) + . ', ' . $gis_data . ')'; + return $where; + } + + // New output type is the output type of the function being applied + $type = $geom_funcs[$geom_func]['type']; + $geom_function_applied = $geom_func + . '(' . Util::backquote($names) . ')'; + + // If the where clause is something like 'IsEmpty(`spatial_col_name`)' + if (isset($geom_unary_functions[$geom_func]) + && trim($criteriaValues) == '' + ) { + $where = $geom_function_applied; + + } elseif (in_array($type, Util::getGISDatatypes()) + && ! empty($criteriaValues) + ) { + // create gis data from the criteria input + $gis_data = Util::createGISData($criteriaValues, $this->dbi->getVersion()); + $where = $geom_function_applied . " " . $func_type . " " . $gis_data; + + } elseif (strlen($criteriaValues) > 0) { + $where = $geom_function_applied . " " + . $func_type . " '" . $criteriaValues . "'"; + } + return $where; + } + + /** + * Return the where clause for query generation based on the inputs provided. + * + * @param mixed $criteriaValues Search criteria input + * @param string $names Name of the column on which search is submitted + * @param string $types Type of the field + * @param string $func_type Search function/operator + * @param bool $unaryFlag Whether operator unary or not + * @param bool $geom_func Whether geometry functions should be applied + * + * @return string generated where clause. + */ + private function _getWhereClause($criteriaValues, $names, $types, + $func_type, $unaryFlag, $geom_func = null + ) { + // If geometry function is set + if (! empty($geom_func)) { + return $this->_getGeomWhereClause( + $criteriaValues, $names, $func_type, $types, $geom_func + ); + } + + $backquoted_name = Util::backquote($names); + $where = ''; + if ($unaryFlag) { + $where = $backquoted_name . ' ' . $func_type; + } elseif (strncasecmp($types, 'enum', 4) == 0 && (! empty($criteriaValues) || $criteriaValues[0] === '0')) { + $where = $backquoted_name; + $where .= $this->_getEnumWhereClause($criteriaValues, $func_type); + + } elseif ($criteriaValues != '') { + // For these types we quote the value. Even if it's another type + // (like INT), for a LIKE we always quote the value. MySQL converts + // strings to numbers and numbers to strings as necessary + // during the comparison + if (preg_match('@char|binary|blob|text|set|date|time|year@i', $types) + || mb_strpos(' ' . $func_type, 'LIKE') + ) { + $quot = '\''; + } else { + $quot = ''; + } + + // LIKE %...% + if ($func_type == 'LIKE %...%') { + $func_type = 'LIKE'; + $criteriaValues = '%' . $criteriaValues . '%'; + } + if ($func_type == 'REGEXP ^...$') { + $func_type = 'REGEXP'; + $criteriaValues = '^' . $criteriaValues . '$'; + } + + if ('IN (...)' != $func_type + && 'NOT IN (...)' != $func_type + && 'BETWEEN' != $func_type + && 'NOT BETWEEN' != $func_type + ) { + return $backquoted_name . ' ' . $func_type . ' ' . $quot + . $GLOBALS['dbi']->escapeString($criteriaValues) . $quot; + } + $func_type = str_replace(' (...)', '', $func_type); + + //Don't explode if this is already an array + //(Case for (NOT) IN/BETWEEN.) + if (is_array($criteriaValues)) { + $values = $criteriaValues; + } else { + $values = explode(',', $criteriaValues); + } + // quote values one by one + $emptyKey = false; + foreach ($values as $key => &$value) { + if ('' === $value) { + $emptyKey = $key; + $value = 'NULL'; + continue; + } + $value = $quot . $GLOBALS['dbi']->escapeString(trim($value)) + . $quot; + } + + if ('BETWEEN' == $func_type || 'NOT BETWEEN' == $func_type) { + $where = $backquoted_name . ' ' . $func_type . ' ' + . (isset($values[0]) ? $values[0] : '') + . ' AND ' . (isset($values[1]) ? $values[1] : ''); + } else { //[NOT] IN + if (false !== $emptyKey) { + unset($values[$emptyKey]); + } + $wheres = array(); + if (!empty($values)) { + $wheres[] = $backquoted_name . ' ' . $func_type + . ' (' . implode(',', $values) . ')'; + } + if (false !== $emptyKey) { + $wheres[] = $backquoted_name . ' IS NULL'; + } + $where = implode(' OR ', $wheres); + if (1 < count($wheres)) { + $where = '(' . $where . ')'; + } + } + } // end if + + return $where; + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Controllers/Table/TableStructureController.php b/php/apps/phpmyadmin49/html/libraries/classes/Controllers/Table/TableStructureController.php new file mode 100644 index 00000000..06b26dcd --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Controllers/Table/TableStructureController.php @@ -0,0 +1,1505 @@ +_db_is_system_schema = $db_is_system_schema; + $this->_url_query = Url::getCommonRaw(array('db' => $db, 'table' => $table)); + $this->_tbl_is_view = $tbl_is_view; + $this->_tbl_storage_engine = $tbl_storage_engine; + $this->_table_info_num_rows = $table_info_num_rows; + $this->_tbl_collation = $tbl_collation; + $this->_showtable = $showtable; + $this->table_obj = $this->dbi->getTable($this->db, $this->table); + + $this->createAddField = new CreateAddField($dbi); + $this->relation = new Relation(); + } + + /** + * Index action + * + * @return void + */ + public function indexAction() + { + PageSettings::showGroup('TableStructure'); + + /** + * Function implementations for this script + */ + include_once 'libraries/check_user_privileges.inc.php'; + + $this->response->getHeader()->getScripts()->addFiles( + array( + 'tbl_structure.js', + 'indexes.js' + ) + ); + + /** + * Handle column moving + */ + if (isset($_POST['move_columns']) + && is_array($_POST['move_columns']) + && $this->response->isAjax() + ) { + $this->moveColumns(); + return; + } + + /** + * handle MySQL reserved words columns check + */ + if (isset($_POST['reserved_word_check'])) { + if ($GLOBALS['cfg']['ReservedWordDisableWarning'] === false) { + $columns_names = $_POST['field_name']; + $reserved_keywords_names = array(); + foreach ($columns_names as $column) { + if (Context::isKeyword(trim($column), true)) { + $reserved_keywords_names[] = trim($column); + } + } + if (Context::isKeyword(trim($this->table), true)) { + $reserved_keywords_names[] = trim($this->table); + } + if (count($reserved_keywords_names) == 0) { + $this->response->setRequestStatus(false); + } + $this->response->addJSON( + 'message', sprintf( + _ngettext( + 'The name \'%s\' is a MySQL reserved keyword.', + 'The names \'%s\' are MySQL reserved keywords.', + count($reserved_keywords_names) + ), + implode(',', $reserved_keywords_names) + ) + ); + } else { + $this->response->setRequestStatus(false); + } + return; + } + /** + * A click on Change has been made for one column + */ + if (isset($_GET['change_column'])) { + $this->displayHtmlForColumnChange(null, 'tbl_structure.php'); + return; + } + + /** + * Adding or editing partitioning of the table + */ + if (isset($_POST['edit_partitioning']) + && ! isset($_POST['save_partitioning']) + ) { + $this->displayHtmlForPartitionChange(); + return; + } + + /** + * handle multiple field commands if required + * + * submit_mult_*_x comes from IE if is used + */ + $submit_mult = $this->getMultipleFieldCommandType(); + + if (! empty($submit_mult)) { + if (isset($_POST['selected_fld'])) { + if ($submit_mult == 'browse') { + // browsing the table displaying only selected columns + $this->displayTableBrowseForSelectedColumns( + $GLOBALS['goto'], $GLOBALS['pmaThemeImage'] + ); + } else { + // handle multiple field commands + // handle confirmation of deleting multiple columns + $action = 'tbl_structure.php'; + $GLOBALS['selected'] = $_POST['selected_fld']; + list( + $what_ret, $query_type_ret, $is_unset_submit_mult, + $mult_btn_ret, $centralColsError + ) + = $this->getDataForSubmitMult( + $submit_mult, $_POST['selected_fld'], $action + ); + //update the existing variables + // todo: refactor mult_submits.inc.php such as + // below globals are not needed anymore + if (isset($what_ret)) { + $GLOBALS['what'] = $what_ret; + global $what; + } + if (isset($query_type_ret)) { + $GLOBALS['query_type'] = $query_type_ret; + global $query_type; + } + if ($is_unset_submit_mult) { + unset($submit_mult); + } + if (isset($mult_btn_ret)) { + $GLOBALS['mult_btn'] = $mult_btn_ret; + global $mult_btn; + } + include 'libraries/mult_submits.inc.php'; + /** + * if $submit_mult == 'change', execution will have stopped + * at this point + */ + if (empty($message)) { + $message = Message::success(); + } + $this->response->addHTML( + Util::getMessage($message, $sql_query) + ); + } + } else { + $this->response->setRequestStatus(false); + $this->response->addJSON('message', __('No column selected.')); + } + } + + // display secondary level tabs if necessary + $engine = $this->table_obj->getStorageEngine(); + $this->response->addHTML( + Template::get('table/secondary_tabs')->render( + array( + 'url_params' => array( + 'db' => $this->db, + 'table' => $this->table + ), + 'is_foreign_key_supported' => Util::isForeignKeySupported($engine), + 'cfg_relation' => $this->relation->getRelationsParam(), + ) + ) + ); + $this->response->addHTML('
      '); + + /** + * Modifications have been submitted -> updates the table + */ + if (isset($_POST['do_save_data'])) { + $regenerate = $this->updateColumns(); + if ($regenerate) { + // This happens when updating failed + // @todo: do something appropriate + } else { + // continue to show the table's structure + unset($_POST['selected']); + } + } + + /** + * Modifications to the partitioning have been submitted -> updates the table + */ + if (isset($_POST['save_partitioning'])) { + $this->updatePartitioning(); + } + + /** + * Adding indexes + */ + if (isset($_POST['add_key']) + || isset($_POST['partition_maintenance']) + ) { + //todo: set some variables for sql.php include, to be eliminated + //after refactoring sql.php + $db = $this->db; + $table = $this->table; + $sql_query = $GLOBALS['sql_query']; + $cfg = $GLOBALS['cfg']; + $pmaThemeImage = $GLOBALS['pmaThemeImage']; + include 'sql.php'; + $GLOBALS['reload'] = true; + } + + /** + * Gets the relation settings + */ + $cfgRelation = $this->relation->getRelationsParam(); + + /** + * Runs common work + */ + // set db, table references, for require_once that follows + // got to be eliminated in long run + $db = &$this->db; + $table = &$this->table; + $url_params = array(); + include_once 'libraries/tbl_common.inc.php'; + $this->_db_is_system_schema = $db_is_system_schema; + $this->_url_query = Url::getCommonRaw(array( + 'db' => $db, + 'table' => $table, + 'goto' => 'tbl_structure.php', + 'back' => 'tbl_structure.php', + )); + /* The url_params array is initialized in above include */ + $url_params['goto'] = 'tbl_structure.php'; + $url_params['back'] = 'tbl_structure.php'; + + // 2. Gets table keys and retains them + // @todo should be: $server->db($db)->table($table)->primary() + $primary = Index::getPrimary($this->table, $this->db); + $columns_with_index = $this->dbi + ->getTable($this->db, $this->table) + ->getColumnsWithIndex( + Index::UNIQUE | Index::INDEX | Index::SPATIAL + | Index::FULLTEXT + ); + $columns_with_unique_index = $this->dbi + ->getTable($this->db, $this->table) + ->getColumnsWithIndex(Index::UNIQUE); + + // 3. Get fields + $fields = (array)$this->dbi->getColumns( + $this->db, $this->table, null, true + ); + + foreach ($fields as $key => $row) { + if ('text' === substr($row['Type'], -4)) { + $fields[$key]['Default'] = stripcslashes(substr($row['Default'], 1, -1)); + } + } + + //display table structure + $this->response->addHTML( + $this->displayStructure( + $cfgRelation, $columns_with_unique_index, $url_params, + $primary, $fields, $columns_with_index + ) + ); + + $this->response->addHTML('
      '); + } + + /** + * Moves columns in the table's structure based on $_REQUEST + * + * @return void + */ + protected function moveColumns() + { + $this->dbi->selectDb($this->db); + + /* + * load the definitions for all columns + */ + $columns = $this->dbi->getColumnsFull($this->db, $this->table); + $column_names = array_keys($columns); + $changes = array(); + + // move columns from first to last + for ($i = 0, $l = count($_POST['move_columns']); $i < $l; $i++) { + $column = $_POST['move_columns'][$i]; + // is this column already correctly placed? + if ($column_names[$i] == $column) { + continue; + } + + // it is not, let's move it to index $i + $data = $columns[$column]; + $extracted_columnspec = Util::extractColumnSpec($data['Type']); + if (isset($data['Extra']) + && $data['Extra'] == 'on update CURRENT_TIMESTAMP' + ) { + $extracted_columnspec['attribute'] = $data['Extra']; + unset($data['Extra']); + } + $current_timestamp = ($data['Type'] == 'timestamp' + || $data['Type'] == 'datetime') + && ($data['Default'] == 'CURRENT_TIMESTAMP' + || $data['Default'] == 'current_timestamp()'); + + if ($data['Null'] === 'YES' && $data['Default'] === null) { + $default_type = 'NULL'; + } elseif ($current_timestamp) { + $default_type = 'CURRENT_TIMESTAMP'; + } elseif ($data['Default'] === null) { + $default_type = 'NONE'; + } else { + $default_type = 'USER_DEFINED'; + } + + $virtual = array( + 'VIRTUAL', 'PERSISTENT', 'VIRTUAL GENERATED', 'STORED GENERATED' + ); + $data['Virtuality'] = ''; + $data['Expression'] = ''; + if (isset($data['Extra']) && in_array($data['Extra'], $virtual)) { + $data['Virtuality'] = str_replace(' GENERATED', '', $data['Extra']); + $expressions = $this->table_obj->getColumnGenerationExpression($column); + $data['Expression'] = $expressions[$column]; + } + + $changes[] = 'CHANGE ' . Table::generateAlter( + $column, + $column, + mb_strtoupper($extracted_columnspec['type']), + $extracted_columnspec['spec_in_brackets'], + $extracted_columnspec['attribute'], + isset($data['Collation']) ? $data['Collation'] : '', + $data['Null'] === 'YES' ? 'NULL' : 'NOT NULL', + $default_type, + $current_timestamp ? '' : $data['Default'], + isset($data['Extra']) && $data['Extra'] !== '' ? $data['Extra'] + : false, + isset($data['COLUMN_COMMENT']) && $data['COLUMN_COMMENT'] !== '' + ? $data['COLUMN_COMMENT'] : false, + $data['Virtuality'], + $data['Expression'], + $i === 0 ? '-first' : $column_names[$i - 1] + ); + // update current column_names array, first delete old position + for ($j = 0, $ll = count($column_names); $j < $ll; $j++) { + if ($column_names[$j] == $column) { + unset($column_names[$j]); + } + } + // insert moved column + array_splice($column_names, $i, 0, $column); + } + if (empty($changes)) { // should never happen + $this->response->setRequestStatus(false); + return; + } + // move columns + $this->dbi->tryQuery( + sprintf( + 'ALTER TABLE %s %s', + Util::backquote($this->table), + implode(', ', $changes) + ) + ); + $tmp_error = $this->dbi->getError(); + if ($tmp_error) { + $this->response->setRequestStatus(false); + $this->response->addJSON('message', Message::error($tmp_error)); + } else { + $message = Message::success( + __('The columns have been moved successfully.') + ); + $this->response->addJSON('message', $message); + $this->response->addJSON('columns', $column_names); + } + } + + /** + * Displays HTML for changing one or more columns + * + * @param array $selected the selected columns + * @param string $action target script to call + * + * @return boolean $regenerate true if error occurred + * + */ + protected function displayHtmlForColumnChange($selected, $action) + { + // $selected comes from mult_submits.inc.php + if (empty($selected)) { + $selected[] = $_REQUEST['field']; + $selected_cnt = 1; + } else { // from a multiple submit + $selected_cnt = count($selected); + } + + /** + * @todo optimize in case of multiple fields to modify + */ + $fields_meta = array(); + for ($i = 0; $i < $selected_cnt; $i++) { + $value = $this->dbi->getColumns( + $this->db, $this->table, $this->dbi->escapeString($selected[$i]), true + ); + if (count($value) == 0) { + $message = Message::error( + __('Failed to get description of column %s!') + ); + $message->addParam($selected[$i]); + $this->response->addHTML($message); + + } else { + $fields_meta[] = $value; + } + } + $num_fields = count($fields_meta); + // set these globals because tbl_columns_definition_form.inc.php + // verifies them + // @todo: refactor tbl_columns_definition_form.inc.php so that it uses + // protected function params + $GLOBALS['action'] = $action; + $GLOBALS['num_fields'] = $num_fields; + + /** + * Form for changing properties. + */ + include_once 'libraries/check_user_privileges.inc.php'; + include 'libraries/tbl_columns_definition_form.inc.php'; + } + + /** + * Displays HTML for partition change + * + * @return string HTML for partition change + */ + protected function displayHtmlForPartitionChange() + { + $partitionDetails = null; + if (! isset($_POST['partition_by'])) { + $partitionDetails = $this->_extractPartitionDetails(); + } + + include 'libraries/tbl_partition_definition.inc.php'; + $this->response->addHTML( + Template::get('table/structure/partition_definition_form') + ->render( + array( + 'db' => $this->db, + 'table' => $this->table, + 'partition_details' => $partitionDetails, + ) + ) + ); + } + + /** + * Extracts partition details from CREATE TABLE statement + * + * @return array[] array of partition details + */ + private function _extractPartitionDetails() + { + $createTable = (new Table($this->table, $this->db))->showCreate(); + if (! $createTable) { + return null; + } + + $parser = new Parser($createTable); + /** + * @var $stmt PhpMyAdmin\SqlParser\Statements\CreateStatement + */ + $stmt = $parser->statements[0]; + + $partitionDetails = array(); + + $partitionDetails['partition_by'] = ''; + $partitionDetails['partition_expr'] = ''; + $partitionDetails['partition_count'] = ''; + + if (! empty($stmt->partitionBy)) { + $openPos = strpos($stmt->partitionBy, "("); + $closePos = strrpos($stmt->partitionBy, ")"); + + $partitionDetails['partition_by'] + = trim(substr($stmt->partitionBy, 0, $openPos)); + $partitionDetails['partition_expr'] + = trim(substr($stmt->partitionBy, $openPos + 1, $closePos - ($openPos + 1))); + if (isset($stmt->partitionsNum)) { + $count = $stmt->partitionsNum; + } else { + $count = count($stmt->partitions); + } + $partitionDetails['partition_count'] = $count; + } + + $partitionDetails['subpartition_by'] = ''; + $partitionDetails['subpartition_expr'] = ''; + $partitionDetails['subpartition_count'] = ''; + + if (! empty($stmt->subpartitionBy)) { + $openPos = strpos($stmt->subpartitionBy, "("); + $closePos = strrpos($stmt->subpartitionBy, ")"); + + $partitionDetails['subpartition_by'] + = trim(substr($stmt->subpartitionBy, 0, $openPos)); + $partitionDetails['subpartition_expr'] + = trim(substr($stmt->subpartitionBy, $openPos + 1, $closePos - ($openPos + 1))); + if (isset($stmt->subpartitionsNum)) { + $count = $stmt->subpartitionsNum; + } else { + $count = count($stmt->partitions[0]->subpartitions); + } + $partitionDetails['subpartition_count'] = $count; + } + + // Only LIST and RANGE type parameters allow subpartitioning + $partitionDetails['can_have_subpartitions'] + = $partitionDetails['partition_count'] > 1 + && ($partitionDetails['partition_by'] == 'RANGE' + || $partitionDetails['partition_by'] == 'RANGE COLUMNS' + || $partitionDetails['partition_by'] == 'LIST' + || $partitionDetails['partition_by'] == 'LIST COLUMNS'); + + // Values are specified only for LIST and RANGE type partitions + $partitionDetails['value_enabled'] = isset($partitionDetails['partition_by']) + && ($partitionDetails['partition_by'] == 'RANGE' + || $partitionDetails['partition_by'] == 'RANGE COLUMNS' + || $partitionDetails['partition_by'] == 'LIST' + || $partitionDetails['partition_by'] == 'LIST COLUMNS'); + + $partitionDetails['partitions'] = array(); + + for ($i = 0; $i < intval($partitionDetails['partition_count']); $i++) { + + if (! isset($stmt->partitions[$i])) { + $partitionDetails['partitions'][$i] = array( + 'name' => 'p' . $i, + 'value_type' => '', + 'value' => '', + 'engine' => '', + 'comment' => '', + 'data_directory' => '', + 'index_directory' => '', + 'max_rows' => '', + 'min_rows' => '', + 'tablespace' => '', + 'node_group' => '', + ); + } else { + $p = $stmt->partitions[$i]; + $type = $p->type; + $expr = trim($p->expr, '()'); + if ($expr == 'MAXVALUE') { + $type .= ' MAXVALUE'; + $expr = ''; + } + $partitionDetails['partitions'][$i] = array( + 'name' => $p->name, + 'value_type' => $type, + 'value' => $expr, + 'engine' => $p->options->has('ENGINE', true), + 'comment' => trim($p->options->has('COMMENT', true), "'"), + 'data_directory' => trim($p->options->has('DATA DIRECTORY', true), "'"), + 'index_directory' => trim($p->options->has('INDEX_DIRECTORY', true), "'"), + 'max_rows' => $p->options->has('MAX_ROWS', true), + 'min_rows' => $p->options->has('MIN_ROWS', true), + 'tablespace' => $p->options->has('TABLESPACE', true), + 'node_group' => $p->options->has('NODEGROUP', true), + ); + } + + $partition =& $partitionDetails['partitions'][$i]; + $partition['prefix'] = 'partitions[' . $i . ']'; + + if ($partitionDetails['subpartition_count'] > 1) { + $partition['subpartition_count'] = $partitionDetails['subpartition_count']; + $partition['subpartitions'] = array(); + + for ($j = 0; $j < intval($partitionDetails['subpartition_count']); $j++) { + if (! isset($stmt->partitions[$i]->subpartitions[$j])) { + $partition['subpartitions'][$j] = array( + 'name' => $partition['name'] . '_s' . $j, + 'engine' => '', + 'comment' => '', + 'data_directory' => '', + 'index_directory' => '', + 'max_rows' => '', + 'min_rows' => '', + 'tablespace' => '', + 'node_group' => '', + ); + } else { + $sp = $stmt->partitions[$i]->subpartitions[$j]; + $partition['subpartitions'][$j] = array( + 'name' => $sp->name, + 'engine' => $sp->options->has('ENGINE', true), + 'comment' => trim($sp->options->has('COMMENT', true), "'"), + 'data_directory' => trim($sp->options->has('DATA DIRECTORY', true), "'"), + 'index_directory' => trim($sp->options->has('INDEX_DIRECTORY', true), "'"), + 'max_rows' => $sp->options->has('MAX_ROWS', true), + 'min_rows' => $sp->options->has('MIN_ROWS', true), + 'tablespace' => $sp->options->has('TABLESPACE', true), + 'node_group' => $sp->options->has('NODEGROUP', true), + ); + } + + $subpartition =& $partition['subpartitions'][$j]; + $subpartition['prefix'] = 'partitions[' . $i . ']' + . '[subpartitions][' . $j . ']'; + } + } + } + + return $partitionDetails; + } + + /** + * Update the table's partitioning based on $_REQUEST + * + * @return void + */ + protected function updatePartitioning() + { + $sql_query = "ALTER TABLE " . Util::backquote($this->table) . " " + . $this->createAddField->getPartitionsDefinition(); + + // Execute alter query + $result = $this->dbi->tryQuery($sql_query); + + if ($result !== false) { + $message = Message::success( + __('Table %1$s has been altered successfully.') + ); + $message->addParam($this->table); + $this->response->addHTML( + Util::getMessage($message, $sql_query, 'success') + ); + } else { + $this->response->setRequestStatus(false); + $this->response->addJSON( + 'message', + Message::rawError( + __('Query error') . ':
      ' . $this->dbi->getError() + ) + ); + } + } + + /** + * Function to get the type of command for multiple field handling + * + * @return string + */ + protected function getMultipleFieldCommandType() + { + $types = array( + 'change', 'drop', 'primary', + 'index', 'unique', 'spatial', + 'fulltext', 'browse' + ); + + foreach ($types as $type) { + if (isset($_POST['submit_mult_' . $type . '_x'])) { + return $type; + } + } + + if (isset($_POST['submit_mult'])) { + return $_POST['submit_mult']; + } elseif (isset($_POST['mult_btn']) + && $_POST['mult_btn'] == __('Yes') + ) { + if (isset($_POST['selected'])) { + $_POST['selected_fld'] = $_POST['selected']; + } + return 'row_delete'; + } + + return null; + } + + /** + * Function to display table browse for selected columns + * + * @param string $goto goto page url + * @param string $pmaThemeImage URI of the pma theme image + * + * @return void + */ + protected function displayTableBrowseForSelectedColumns($goto, $pmaThemeImage) + { + $GLOBALS['active_page'] = 'sql.php'; + $fields = array(); + foreach ($_POST['selected_fld'] as $sval) { + $fields[] = Util::backquote($sval); + } + $sql_query = sprintf( + 'SELECT %s FROM %s.%s', + implode(', ', $fields), + Util::backquote($this->db), + Util::backquote($this->table) + ); + + // Parse and analyze the query + $db = &$this->db; + list( + $analyzed_sql_results, + $db, + ) = ParseAnalyze::sqlQuery($sql_query, $db); + // @todo: possibly refactor + extract($analyzed_sql_results); + + $sql = new Sql(); + $this->response->addHTML( + $sql->executeQueryAndGetQueryResponse( + isset($analyzed_sql_results) ? $analyzed_sql_results : '', + false, // is_gotofile + $this->db, // db + $this->table, // table + null, // find_real_end + null, // sql_query_for_bookmark + null, // extra_data + null, // message_to_show + null, // message + null, // sql_data + $goto, // goto + $pmaThemeImage, // pmaThemeImage + null, // disp_query + null, // disp_message + null, // query_type + $sql_query, // sql_query + null, // selectedTables + null // complete_query + ) + ); + } + + /** + * Update the table's structure based on $_REQUEST + * + * @return boolean $regenerate true if error occurred + * + */ + protected function updateColumns() + { + $err_url = 'tbl_structure.php' . Url::getCommon( + array( + 'db' => $this->db, 'table' => $this->table + ) + ); + $regenerate = false; + $field_cnt = count($_POST['field_name']); + $changes = array(); + $adjust_privileges = array(); + + for ($i = 0; $i < $field_cnt; $i++) { + if (!$this->columnNeedsAlterTable($i)) { + continue; + } + + $changes[] = 'CHANGE ' . Table::generateAlter( + Util::getValueByKey($_POST, "field_orig.${i}", ''), + $_POST['field_name'][$i], + $_POST['field_type'][$i], + $_POST['field_length'][$i], + $_POST['field_attribute'][$i], + Util::getValueByKey($_POST, "field_collation.${i}", ''), + Util::getValueByKey($_POST, "field_null.${i}", 'NO'), + $_POST['field_default_type'][$i], + $_POST['field_default_value'][$i], + Util::getValueByKey($_POST, "field_extra.${i}", false), + Util::getValueByKey($_POST, "field_comments.${i}", ''), + Util::getValueByKey($_POST, "field_virtuality.${i}", ''), + Util::getValueByKey($_POST, "field_expression.${i}", ''), + Util::getValueByKey($_POST, "field_move_to.${i}", '') + ); + + // find the remembered sort expression + $sorted_col = $this->table_obj->getUiProp( + Table::PROP_SORTED_COLUMN + ); + // if the old column name is part of the remembered sort expression + if (mb_strpos( + $sorted_col, + Util::backquote($_POST['field_orig'][$i]) + ) !== false) { + // delete the whole remembered sort expression + $this->table_obj->removeUiProp(Table::PROP_SORTED_COLUMN); + } + + if (isset($_POST['field_adjust_privileges'][$i]) + && ! empty($_POST['field_adjust_privileges'][$i]) + && $_POST['field_orig'][$i] != $_POST['field_name'][$i] + ) { + $adjust_privileges[$_POST['field_orig'][$i]] + = $_POST['field_name'][$i]; + } + } // end for + + if (count($changes) > 0 || isset($_POST['preview_sql'])) { + // Builds the primary keys statements and updates the table + $key_query = ''; + /** + * this is a little bit more complex + * + * @todo if someone selects A_I when altering a column we need to check: + * - no other column with A_I + * - the column has an index, if not create one + * + */ + + // To allow replication, we first select the db to use + // and then run queries on this db. + if (!$this->dbi->selectDb($this->db)) { + Util::mysqlDie( + $this->dbi->getError(), + 'USE ' . Util::backquote($this->db) . ';', + false, + $err_url + ); + } + $sql_query = 'ALTER TABLE ' . Util::backquote($this->table) . ' '; + $sql_query .= implode(', ', $changes) . $key_query; + $sql_query .= ';'; + + // If there is a request for SQL previewing. + if (isset($_POST['preview_sql'])) { + Core::previewSQL(count($changes) > 0 ? $sql_query : ''); + } + + $columns_with_index = $this->dbi + ->getTable($this->db, $this->table) + ->getColumnsWithIndex( + Index::PRIMARY | Index::UNIQUE | Index::INDEX + | Index::SPATIAL | Index::FULLTEXT + ); + + $changedToBlob = array(); + // While changing the Column Collation + // First change to BLOB + for ($i = 0; $i < $field_cnt; $i++ ) { + if (isset($_POST['field_collation'][$i]) + && isset($_POST['field_collation_orig'][$i]) + && $_POST['field_collation'][$i] !== $_POST['field_collation_orig'][$i] + && ! in_array($_POST['field_orig'][$i], $columns_with_index) + ) { + $secondary_query = 'ALTER TABLE ' . Util::backquote( + $this->table + ) + . ' CHANGE ' . Util::backquote( + $_POST['field_orig'][$i] + ) + . ' ' . Util::backquote($_POST['field_orig'][$i]) + . ' BLOB'; + + if (isset($_POST['field_virtuality'][$i]) + && isset($_POST['field_expression'][$i])) { + if ($_POST['field_virtuality'][$i]) { + $secondary_query .= ' AS (' . $_POST['field_expression'][$i] . ') ' + . $_POST['field_virtuality'][$i]; + } + } + + $secondary_query .= ';'; + + $this->dbi->query($secondary_query); + $changedToBlob[$i] = true; + } else { + $changedToBlob[$i] = false; + } + } + + // Then make the requested changes + $result = $this->dbi->tryQuery($sql_query); + + if ($result !== false) { + $changed_privileges = $this->adjustColumnPrivileges( + $adjust_privileges + ); + + if ($changed_privileges) { + $message = Message::success( + __( + 'Table %1$s has been altered successfully. Privileges ' . + 'have been adjusted.' + ) + ); + } else { + $message = Message::success( + __('Table %1$s has been altered successfully.') + ); + } + $message->addParam($this->table); + + $this->response->addHTML( + Util::getMessage($message, $sql_query, 'success') + ); + } else { + // An error happened while inserting/updating a table definition + + // Save the Original Error + $orig_error = $this->dbi->getError(); + $changes_revert = array(); + + // Change back to Original Collation and data type + for ($i = 0; $i < $field_cnt; $i++) { + if ($changedToBlob[$i]) { + $changes_revert[] = 'CHANGE ' . Table::generateAlter( + Util::getValueByKey($_POST, "field_orig.${i}", ''), + $_POST['field_name'][$i], + $_POST['field_type_orig'][$i], + $_POST['field_length_orig'][$i], + $_POST['field_attribute_orig'][$i], + Util::getValueByKey($_POST, "field_collation_orig.${i}", ''), + Util::getValueByKey($_POST, "field_null_orig.${i}", 'NO'), + $_POST['field_default_type_orig'][$i], + $_POST['field_default_value_orig'][$i], + Util::getValueByKey($_POST, "field_extra_orig.${i}", false), + Util::getValueByKey($_POST, "field_comments_orig.${i}", ''), + Util::getValueByKey($_POST, "field_virtuality_orig.${i}", ''), + Util::getValueByKey($_POST, "field_expression_orig.${i}", ''), + Util::getValueByKey($_POST, "field_move_to_orig.${i}", '') + ); + } + } + + $revert_query = 'ALTER TABLE ' . Util::backquote($this->table) + . ' '; + $revert_query .= implode(', ', $changes_revert) . ''; + $revert_query .= ';'; + + // Column reverted back to original + $this->dbi->query($revert_query); + + $this->response->setRequestStatus(false); + $this->response->addJSON( + 'message', + Message::rawError( + __('Query error') . ':
      ' . $orig_error + ) + ); + $regenerate = true; + } + } + + // update field names in relation + if (isset($_POST['field_orig']) && is_array($_POST['field_orig'])) { + foreach ($_POST['field_orig'] as $fieldindex => $fieldcontent) { + if ($_POST['field_name'][$fieldindex] != $fieldcontent) { + $this->relation->renameField( + $this->db, $this->table, $fieldcontent, + $_POST['field_name'][$fieldindex] + ); + } + } + } + + // update mime types + if (isset($_POST['field_mimetype']) + && is_array($_POST['field_mimetype']) + && $GLOBALS['cfg']['BrowseMIME'] + ) { + foreach ($_POST['field_mimetype'] as $fieldindex => $mimetype) { + if (isset($_POST['field_name'][$fieldindex]) + && strlen($_POST['field_name'][$fieldindex]) > 0 + ) { + Transformations::setMIME( + $this->db, $this->table, + $_POST['field_name'][$fieldindex], + $mimetype, + $_POST['field_transformation'][$fieldindex], + $_POST['field_transformation_options'][$fieldindex], + $_POST['field_input_transformation'][$fieldindex], + $_POST['field_input_transformation_options'][$fieldindex] + ); + } + } + } + return $regenerate; + } + + /** + * Adjusts the Privileges for all the columns whose names have changed + * + * @param array $adjust_privileges assoc array of old col names mapped to new + * cols + * + * @return boolean $changed boolean whether at least one column privileges + * adjusted + */ + protected function adjustColumnPrivileges(array $adjust_privileges) + { + $changed = false; + + if (Util::getValueByKey($GLOBALS, 'col_priv', false) + && Util::getValueByKey($GLOBALS, 'is_reload_priv', false) + ) { + $this->dbi->selectDb('mysql'); + + // For Column specific privileges + foreach ($adjust_privileges as $oldCol => $newCol) { + + $this->dbi->query( + sprintf( + 'UPDATE %s SET Column_name = "%s" + WHERE Db = "%s" + AND Table_name = "%s" + AND Column_name = "%s";', + Util::backquote('columns_priv'), + $newCol, $this->db, $this->table, $oldCol + ) + ); + + // i.e. if atleast one column privileges adjusted + $changed = true; + } + + if ($changed) { + // Finally FLUSH the new privileges + $this->dbi->query("FLUSH PRIVILEGES;"); + } + } + + return $changed; + } + + /** + * Verifies if some elements of a column have changed + * + * @param integer $i column index in the request + * + * @return boolean $alterTableNeeded true if we need to generate ALTER TABLE + * + */ + protected function columnNeedsAlterTable($i) + { + // these two fields are checkboxes so might not be part of the + // request; therefore we define them to avoid notices below + if (! isset($_POST['field_null'][$i])) { + $_POST['field_null'][$i] = 'NO'; + } + if (! isset($_POST['field_extra'][$i])) { + $_POST['field_extra'][$i] = ''; + } + + // field_name does not follow the convention (corresponds to field_orig) + if ($_POST['field_name'][$i] != $_POST['field_orig'][$i]) { + return true; + } + + $fields = array( + 'field_attribute', 'field_collation', 'field_comments', + 'field_default_value', 'field_default_type', 'field_extra', + 'field_length', 'field_null', 'field_type' + ); + foreach ($fields as $field) { + if ($_POST[$field][$i] != $_POST[$field . '_orig'][$i]) { + return true; + } + } + return !empty($_POST['field_move_to'][$i]); + } + + /** + * Displays the table structure ('show table' works correct since 3.23.03) + * + * @param array $cfgRelation current relation parameters + * @param array $columns_with_unique_index Columns with unique index + * @param mixed $url_params Contains an associative + * array with url params + * @param Index|false $primary_index primary index or false if + * no one exists + * @param array $fields Fields + * @param array $columns_with_index Columns with index + * + * @return string + */ + protected function displayStructure( + array $cfgRelation, array $columns_with_unique_index, $url_params, + $primary_index, array $fields, array $columns_with_index + ) { + // prepare comments + $comments_map = array(); + $mime_map = array(); + + if ($GLOBALS['cfg']['ShowPropertyComments']) { + $comments_map = $this->relation->getComments($this->db, $this->table); + if ($cfgRelation['mimework'] && $GLOBALS['cfg']['BrowseMIME']) { + $mime_map = Transformations::getMIME($this->db, $this->table, true); + } + } + $centralColumns = new CentralColumns($GLOBALS['dbi']); + $central_list = $centralColumns->getFromTable( + $this->db, + $this->table + ); + $columns_list = array(); + + $titles = array( + 'Change' => Util::getIcon('b_edit', __('Change')), + 'Drop' => Util::getIcon('b_drop', __('Drop')), + 'NoDrop' => Util::getIcon('b_drop', __('Drop')), + 'Primary' => Util::getIcon('b_primary', __('Primary')), + 'Index' => Util::getIcon('b_index', __('Index')), + 'Unique' => Util::getIcon('b_unique', __('Unique')), + 'Spatial' => Util::getIcon('b_spatial', __('Spatial')), + 'IdxFulltext' => Util::getIcon('b_ftext', __('Fulltext')), + 'NoPrimary' => Util::getIcon('bd_primary', __('Primary')), + 'NoIndex' => Util::getIcon('bd_index', __('Index')), + 'NoUnique' => Util::getIcon('bd_unique', __('Unique')), + 'NoSpatial' => Util::getIcon('bd_spatial', __('Spatial')), + 'NoIdxFulltext' => Util::getIcon('bd_ftext', __('Fulltext')), + 'DistinctValues' => Util::getIcon('b_browse', __('Distinct values')), + ); + + $edit_view_url = ''; + if ($this->_tbl_is_view && ! $this->_db_is_system_schema) { + $edit_view_url = Url::getCommon( + array('db' => $this->db, 'table' => $this->table) + ); + } + + /** + * Displays Space usage and row statistics + */ + // BEGIN - Calc Table Space + // Get valid statistics whatever is the table type + if ($GLOBALS['cfg']['ShowStats']) { + //get table stats in HTML format + $tablestats = $this->getTableStats(); + //returning the response in JSON format to be used by Ajax + $this->response->addJSON('tableStat', $tablestats); + } + // END - Calc Table Space + + $hideStructureActions = false; + if ($GLOBALS['cfg']['HideStructureActions'] === true) { + $hideStructureActions = true; + } + + return Template::get('table/structure/display_structure')->render( + array( + 'hide_structure_actions' => $hideStructureActions, + 'db' => $this->db, + 'table' => $this->table, + 'db_is_system_schema' => $this->_db_is_system_schema, + 'tbl_is_view' => $this->_tbl_is_view, + 'mime_map' => $mime_map, + 'url_query' => $this->_url_query, + 'titles' => $titles, + 'tbl_storage_engine' => $this->_tbl_storage_engine, + 'primary' => $primary_index, + 'columns_with_unique_index' => $columns_with_unique_index, + 'edit_view_url' => $edit_view_url, + 'columns_list' => $columns_list, + 'table_stats' => isset($tablestats) ? $tablestats : null, + 'fields' => $fields, + 'columns_with_index' => $columns_with_index, + 'central_list' => $central_list, + 'comments_map' => $comments_map, + 'browse_mime' => $GLOBALS['cfg']['BrowseMIME'], + 'show_column_comments' => $GLOBALS['cfg']['ShowColumnComments'], + 'show_stats' => $GLOBALS['cfg']['ShowStats'], + 'relation_commwork' => $GLOBALS['cfgRelation']['commwork'], + 'relation_mimework' => $GLOBALS['cfgRelation']['mimework'], + 'central_columns_work' => $GLOBALS['cfgRelation']['centralcolumnswork'], + 'mysql_int_version' => $GLOBALS['dbi']->getVersion(), + 'is_mariadb' => $GLOBALS['dbi']->isMariaDB(), + 'pma_theme_image' => $GLOBALS['pmaThemeImage'], + 'text_dir' => $GLOBALS['text_dir'], + 'is_active' => Tracker::isActive(), + 'have_partitioning' => Partition::havePartitioning(), + 'partition_names' => Partition::getPartitionNames($this->db, $this->table), + ) + ); + } + + /** + * Get HTML snippet for display table statistics + * + * @return string $html_output + */ + protected function getTableStats() + { + if (empty($this->_showtable)) { + $this->_showtable = $this->dbi->getTable( + $this->db, $this->table + )->getStatusInfo(null, true); + } + + if (empty($this->_showtable['Data_length'])) { + $this->_showtable['Data_length'] = 0; + } + if (empty($this->_showtable['Index_length'])) { + $this->_showtable['Index_length'] = 0; + } + + $is_innodb = (isset($this->_showtable['Type']) + && $this->_showtable['Type'] == 'InnoDB'); + + $mergetable = $this->table_obj->isMerge(); + + // this is to display for example 261.2 MiB instead of 268k KiB + $max_digits = 3; + $decimals = 1; + list($data_size, $data_unit) = Util::formatByteDown( + $this->_showtable['Data_length'], $max_digits, $decimals + ); + if ($mergetable == false) { + list($index_size, $index_unit) = Util::formatByteDown( + $this->_showtable['Index_length'], $max_digits, $decimals + ); + } + // InnoDB returns a huge value in Data_free, do not use it + if (! $is_innodb && isset($this->_showtable['Data_free']) + && $this->_showtable['Data_free'] > 0 + ) { + list($free_size, $free_unit) = Util::formatByteDown( + $this->_showtable['Data_free'], $max_digits, $decimals + ); + list($effect_size, $effect_unit) = Util::formatByteDown( + $this->_showtable['Data_length'] + + $this->_showtable['Index_length'] + - $this->_showtable['Data_free'], + $max_digits, $decimals + ); + } else { + list($effect_size, $effect_unit) = Util::formatByteDown( + $this->_showtable['Data_length'] + + $this->_showtable['Index_length'], + $max_digits, $decimals + ); + } + list($tot_size, $tot_unit) = Util::formatByteDown( + $this->_showtable['Data_length'] + $this->_showtable['Index_length'], + $max_digits, $decimals + ); + if ($this->_table_info_num_rows > 0) { + list($avg_size, $avg_unit) = Util::formatByteDown( + ($this->_showtable['Data_length'] + + $this->_showtable['Index_length']) + / $this->_showtable['Rows'], + 6, + 1 + ); + } else { + $avg_size = $avg_unit = ''; + } + + return Template::get('table/structure/display_table_stats')->render( + array( + 'showtable' => $this->_showtable, + 'table_info_num_rows' => $this->_table_info_num_rows, + 'tbl_is_view' => $this->_tbl_is_view, + 'db_is_system_schema' => $this->_db_is_system_schema, + 'tbl_storage_engine' => $this->_tbl_storage_engine, + 'url_query' => $this->_url_query, + 'tbl_collation' => $this->_tbl_collation, + 'is_innodb' => $is_innodb, + 'mergetable' => $mergetable, + 'avg_size' => isset($avg_size) ? $avg_size : null, + 'avg_unit' => isset($avg_unit) ? $avg_unit : null, + 'data_size' => $data_size, + 'data_unit' => $data_unit, + 'index_size' => isset($index_size) ? $index_size : null, + 'index_unit' => isset($index_unit) ? $index_unit : null, + 'free_size' => isset($free_size) ? $free_size : null, + 'free_unit' => isset($free_unit) ? $free_unit : null, + 'effect_size' => $effect_size, + 'effect_unit' => $effect_unit, + 'tot_size' => $tot_size, + 'tot_unit' => $tot_unit, + 'table' => $GLOBALS['table'] + ) + ); + } + + /** + * Gets table primary key + * + * @return string + */ + protected function getKeyForTablePrimary() + { + $this->dbi->selectDb($this->db); + $result = $this->dbi->query( + 'SHOW KEYS FROM ' . Util::backquote($this->table) . ';' + ); + $primary = ''; + while ($row = $this->dbi->fetchAssoc($result)) { + // Backups the list of primary keys + if ($row['Key_name'] == 'PRIMARY') { + $primary .= $row['Column_name'] . ', '; + } + } // end while + $this->dbi->freeResult($result); + + return $primary; + } + + /** + * Get List of information for Submit Mult + * + * @param string $submit_mult mult_submit type + * @param array $selected the selected columns + * @param string $action action type + * + * @return array + */ + protected function getDataForSubmitMult($submit_mult, $selected, $action) + { + $centralColumns = new CentralColumns($GLOBALS['dbi']); + $what = null; + $query_type = null; + $is_unset_submit_mult = false; + $mult_btn = null; + $centralColsError = null; + switch ($submit_mult) { + case 'drop': + $what = 'drop_fld'; + break; + case 'primary': + // Gets table primary key + $primary = $this->getKeyForTablePrimary(); + if (empty($primary)) { + // no primary key, so we can safely create new + $is_unset_submit_mult = true; + $query_type = 'primary_fld'; + $mult_btn = __('Yes'); + } else { + // primary key exists, so lets as user + $what = 'primary_fld'; + } + break; + case 'index': + $is_unset_submit_mult = true; + $query_type = 'index_fld'; + $mult_btn = __('Yes'); + break; + case 'unique': + $is_unset_submit_mult = true; + $query_type = 'unique_fld'; + $mult_btn = __('Yes'); + break; + case 'spatial': + $is_unset_submit_mult = true; + $query_type = 'spatial_fld'; + $mult_btn = __('Yes'); + break; + case 'ftext': + $is_unset_submit_mult = true; + $query_type = 'fulltext_fld'; + $mult_btn = __('Yes'); + break; + case 'add_to_central_columns': + $centralColsError = $centralColumns->syncUniqueColumns( + $selected, + false + ); + break; + case 'remove_from_central_columns': + $centralColsError = $centralColumns->deleteColumnsFromList( + $selected, + false + ); + break; + case 'change': + $this->displayHtmlForColumnChange($selected, $action); + // execution stops here but PhpMyAdmin\Response correctly finishes + // the rendering + exit; + case 'browse': + // this should already be handled by tbl_structure.php + } + + return array( + $what, $query_type, $is_unset_submit_mult, $mult_btn, + $centralColsError + ); + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Controllers/TableController.php b/php/apps/phpmyadmin49/html/libraries/classes/Controllers/TableController.php new file mode 100644 index 00000000..60651ab8 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Controllers/TableController.php @@ -0,0 +1,40 @@ +db = $db; + $this->table = $table; + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Core.php b/php/apps/phpmyadmin49/html/libraries/classes/Core.php new file mode 100644 index 00000000..0d5a7ed6 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Core.php @@ -0,0 +1,1320 @@ + + * // $_REQUEST['db'] not set + * echo Core::ifSetOr($_REQUEST['db'], ''); // '' + * // $_POST['sql_query'] not set + * echo Core::ifSetOr($_POST['sql_query']); // null + * // $cfg['EnableFoo'] not set + * echo Core::ifSetOr($cfg['EnableFoo'], false, 'boolean'); // false + * echo Core::ifSetOr($cfg['EnableFoo']); // null + * // $cfg['EnableFoo'] set to 1 + * echo Core::ifSetOr($cfg['EnableFoo'], false, 'boolean'); // false + * echo Core::ifSetOr($cfg['EnableFoo'], false, 'similar'); // 1 + * echo Core::ifSetOr($cfg['EnableFoo'], false); // 1 + * // $cfg['EnableFoo'] set to true + * echo Core::ifSetOr($cfg['EnableFoo'], false, 'boolean'); // true + * + * + * @param mixed &$var param to check + * @param mixed $default default value + * @param mixed $type var type or array of values to check against $var + * + * @return mixed $var or $default + * + * @see self::isValid() + */ + public static function ifSetOr(&$var, $default = null, $type = 'similar') + { + if (! self::isValid($var, $type, $default)) { + return $default; + } + + return $var; + } + + /** + * checks given $var against $type or $compare + * + * $type can be: + * - false : no type checking + * - 'scalar' : whether type of $var is integer, float, string or boolean + * - 'numeric' : whether type of $var is any number representation + * - 'length' : whether type of $var is scalar with a string length > 0 + * - 'similar' : whether type of $var is similar to type of $compare + * - 'equal' : whether type of $var is identical to type of $compare + * - 'identical' : whether $var is identical to $compare, not only the type! + * - or any other valid PHP variable type + * + * + * // $_REQUEST['doit'] = true; + * Core::isValid($_REQUEST['doit'], 'identical', 'true'); // false + * // $_REQUEST['doit'] = 'true'; + * Core::isValid($_REQUEST['doit'], 'identical', 'true'); // true + * + * + * NOTE: call-by-reference is used to not get NOTICE on undefined vars, + * but the var is not altered inside this function, also after checking a var + * this var exists nut is not set, example: + * + * // $var is not set + * isset($var); // false + * functionCallByReference($var); // false + * isset($var); // true + * functionCallByReference($var); // true + * + * + * to avoid this we set this var to null if not isset + * + * @param mixed &$var variable to check + * @param mixed $type var type or array of valid values to check against $var + * @param mixed $compare var to compare with $var + * + * @return boolean whether valid or not + * + * @todo add some more var types like hex, bin, ...? + * @see https://secure.php.net/gettype + */ + public static function isValid(&$var, $type = 'length', $compare = null) + { + if (! isset($var)) { + // var is not even set + return false; + } + + if ($type === false) { + // no vartype requested + return true; + } + + if (is_array($type)) { + return in_array($var, $type); + } + + // allow some aliases of var types + $type = strtolower($type); + switch ($type) { + case 'identic' : + $type = 'identical'; + break; + case 'len' : + $type = 'length'; + break; + case 'bool' : + $type = 'boolean'; + break; + case 'float' : + $type = 'double'; + break; + case 'int' : + $type = 'integer'; + break; + case 'null' : + $type = 'NULL'; + break; + } + + if ($type === 'identical') { + return $var === $compare; + } + + // whether we should check against given $compare + if ($type === 'similar') { + switch (gettype($compare)) { + case 'string': + case 'boolean': + $type = 'scalar'; + break; + case 'integer': + case 'double': + $type = 'numeric'; + break; + default: + $type = gettype($compare); + } + } elseif ($type === 'equal') { + $type = gettype($compare); + } + + // do the check + if ($type === 'length' || $type === 'scalar') { + $is_scalar = is_scalar($var); + if ($is_scalar && $type === 'length') { + return strlen($var) > 0; + } + return $is_scalar; + } + + if ($type === 'numeric') { + return is_numeric($var); + } + + return gettype($var) === $type; + } + + /** + * Removes insecure parts in a path; used before include() or + * require() when a part of the path comes from an insecure source + * like a cookie or form. + * + * @param string $path The path to check + * + * @return string The secured path + * + * @access public + */ + public static function securePath($path) + { + // change .. to . + $path = preg_replace('@\.\.*@', '.', $path); + + return $path; + } // end function + + /** + * displays the given error message on phpMyAdmin error page in foreign language, + * ends script execution and closes session + * + * loads language file if not loaded already + * + * @param string $error_message the error message or named error message + * @param string|array $message_args arguments applied to $error_message + * + * @return void + */ + public static function fatalError($error_message, $message_args = null) { + /* Use format string if applicable */ + if (is_string($message_args)) { + $error_message = sprintf($error_message, $message_args); + } elseif (is_array($message_args)) { + $error_message = vsprintf($error_message, $message_args); + } + + /* + * Avoid using Response class as config does not have to be loaded yet + * (this can happen on early fatal error) + */ + if (isset($GLOBALS['dbi']) && !is_null($GLOBALS['dbi']) && isset($GLOBALS['PMA_Config']) && $GLOBALS['PMA_Config']->get('is_setup') === false && Response::getInstance()->isAjax()) { + $response = Response::getInstance(); + $response->setRequestStatus(false); + $response->addJSON('message', Message::error($error_message)); + } elseif (! empty($_REQUEST['ajax_request'])) { + // Generate JSON manually + self::headerJSON(); + echo json_encode( + array( + 'success' => false, + 'message' => Message::error($error_message)->getDisplay(), + ) + ); + } else { + $error_message = strtr($error_message, array('
      ' => '[br]')); + $error_header = __('Error'); + $lang = isset($GLOBALS['lang']) ? $GLOBALS['lang'] : 'en'; + $dir = isset($GLOBALS['text_dir']) ? $GLOBALS['text_dir'] : 'ltr'; + + // Displays the error message + include './libraries/error.inc.php'; + } + if (! defined('TESTSUITE')) { + exit; + } + } + + /** + * Returns a link to the PHP documentation + * + * @param string $target anchor in documentation + * + * @return string the URL + * + * @access public + */ + public static function getPHPDocLink($target) + { + /* List of PHP documentation translations */ + $php_doc_languages = array( + 'pt_BR', 'zh', 'fr', 'de', 'it', 'ja', 'pl', 'ro', 'ru', 'fa', 'es', 'tr' + ); + + $lang = 'en'; + if (in_array($GLOBALS['lang'], $php_doc_languages)) { + $lang = $GLOBALS['lang']; + } + + return self::linkURL('https://secure.php.net/manual/' . $lang . '/' . $target); + } + + /** + * Warn or fail on missing extension. + * + * @param string $extension Extension name + * @param bool $fatal Whether the error is fatal. + * @param string $extra Extra string to append to message. + * + * @return void + */ + public static function warnMissingExtension($extension, $fatal = false, $extra = '') + { + /* Gettext does not have to be loaded yet here */ + if (function_exists('__')) { + $message = __( + 'The %s extension is missing. Please check your PHP configuration.' + ); + } else { + $message + = 'The %s extension is missing. Please check your PHP configuration.'; + } + $doclink = self::getPHPDocLink('book.' . $extension . '.php'); + $message = sprintf( + $message, + '[a@' . $doclink . '@Documentation][em]' . $extension . '[/em][/a]' + ); + if ($extra != '') { + $message .= ' ' . $extra; + } + if ($fatal) { + self::fatalError($message); + return; + } + + $GLOBALS['error_handler']->addError( + $message, + E_USER_WARNING, + '', + '', + false + ); + } + + /** + * returns count of tables in given db + * + * @param string $db database to count tables for + * + * @return integer count of tables in $db + */ + public static function getTableCount($db) + { + $tables = $GLOBALS['dbi']->tryQuery( + 'SHOW TABLES FROM ' . Util::backquote($db) . ';', + DatabaseInterface::CONNECT_USER, + DatabaseInterface::QUERY_STORE + ); + if ($tables) { + $num_tables = $GLOBALS['dbi']->numRows($tables); + $GLOBALS['dbi']->freeResult($tables); + } else { + $num_tables = 0; + } + + return $num_tables; + } + + /** + * Converts numbers like 10M into bytes + * Used with permission from Moodle (https://moodle.org) by Martin Dougiamas + * (renamed with PMA prefix to avoid double definition when embedded + * in Moodle) + * + * @param string|int $size size (Default = 0) + * + * @return integer $size + */ + public static function getRealSize($size = 0) + { + if (! $size) { + return 0; + } + + $binaryprefixes = array( + 'T' => 1099511627776, + 't' => 1099511627776, + 'G' => 1073741824, + 'g' => 1073741824, + 'M' => 1048576, + 'm' => 1048576, + 'K' => 1024, + 'k' => 1024, + ); + + if (preg_match('/^([0-9]+)([KMGT])/i', $size, $matches)) { + return $matches[1] * $binaryprefixes[$matches[2]]; + } + + return (int) $size; + } // end getRealSize() + + /** + * boolean phpMyAdmin.Core::checkPageValidity(string &$page, array $whitelist) + * + * checks given $page against given $whitelist and returns true if valid + * it optionally ignores query parameters in $page (script.php?ignored) + * + * @param string &$page page to check + * @param array $whitelist whitelist to check page against + * @param boolean $include whether the page is going to be included + * + * @return boolean whether $page is valid or not (in $whitelist or not) + */ + public static function checkPageValidity(&$page, array $whitelist = [], $include = false) + { + if (empty($whitelist)) { + $whitelist = self::$goto_whitelist; + } + if (! isset($page) || !is_string($page)) { + return false; + } + + if (in_array($page, $whitelist)) { + return true; + } + if ($include) { + return false; + } + + $_page = mb_substr( + $page, + 0, + mb_strpos($page . '?', '?') + ); + if (in_array($_page, $whitelist)) { + return true; + } + + $_page = urldecode($page); + $_page = mb_substr( + $_page, + 0, + mb_strpos($_page . '?', '?') + ); + if (in_array($_page, $whitelist)) { + return true; + } + + return false; + } + + /** + * tries to find the value for the given environment variable name + * + * searches in $_SERVER, $_ENV then tries getenv() and apache_getenv() + * in this order + * + * @param string $var_name variable name + * + * @return string value of $var or empty string + */ + public static function getenv($var_name) + { + if (isset($_SERVER[$var_name])) { + return $_SERVER[$var_name]; + } + + if (isset($_ENV[$var_name])) { + return $_ENV[$var_name]; + } + + if (getenv($var_name)) { + return getenv($var_name); + } + + if (function_exists('apache_getenv') + && apache_getenv($var_name, true) + ) { + return apache_getenv($var_name, true); + } + + return ''; + } + + /** + * Send HTTP header, taking IIS limits into account (600 seems ok) + * + * @param string $uri the header to send + * @param bool $use_refresh whether to use Refresh: header when running on IIS + * + * @return void + */ + public static function sendHeaderLocation($uri, $use_refresh = false) + { + if ($GLOBALS['PMA_Config']->get('PMA_IS_IIS') && mb_strlen($uri) > 600) { + Response::getInstance()->disable(); + + echo Template::get('header_location') + ->render(array('uri' => $uri)); + + return; + } + + /* + * Avoid relative path redirect problems in case user entered URL + * like /phpmyadmin/index.php/ which some web servers happily accept. + */ + if ($uri[0] == '.') { + $uri = $GLOBALS['PMA_Config']->getRootPath() . substr($uri, 2); + } + + $response = Response::getInstance(); + + session_write_close(); + if ($response->headersSent()) { + trigger_error( + 'Core::sendHeaderLocation called when headers are already sent!', + E_USER_ERROR + ); + } + // bug #1523784: IE6 does not like 'Refresh: 0', it + // results in a blank page + // but we need it when coming from the cookie login panel) + if ($GLOBALS['PMA_Config']->get('PMA_IS_IIS') && $use_refresh) { + $response->header('Refresh: 0; ' . $uri); + } else { + $response->header('Location: ' . $uri); + } + } + + /** + * Outputs application/json headers. This includes no caching. + * + * @return void + */ + public static function headerJSON() + { + if (defined('TESTSUITE')) { + return; + } + // No caching + self::noCacheHeader(); + // MIME type + header('Content-Type: application/json; charset=UTF-8'); + // Disable content sniffing in browser + // This is needed in case we include HTML in JSON, browser might assume it's + // html to display + header('X-Content-Type-Options: nosniff'); + } + + /** + * Outputs headers to prevent caching in browser (and on the way). + * + * @return void + */ + public static function noCacheHeader() + { + if (defined('TESTSUITE')) { + return; + } + // rfc2616 - Section 14.21 + header('Expires: ' . gmdate(DATE_RFC1123)); + // HTTP/1.1 + header( + 'Cache-Control: no-store, no-cache, must-revalidate,' + . ' pre-check=0, post-check=0, max-age=0' + ); + + header('Pragma: no-cache'); // HTTP/1.0 + // test case: exporting a database into a .gz file with Safari + // would produce files not having the current time + // (added this header for Safari but should not harm other browsers) + header('Last-Modified: ' . gmdate(DATE_RFC1123)); + } + + + /** + * Sends header indicating file download. + * + * @param string $filename Filename to include in headers if empty, + * none Content-Disposition header will be sent. + * @param string $mimetype MIME type to include in headers. + * @param int $length Length of content (optional) + * @param bool $no_cache Whether to include no-caching headers. + * + * @return void + */ + public static function downloadHeader($filename, $mimetype, $length = 0, $no_cache = true) + { + if ($no_cache) { + self::noCacheHeader(); + } + /* Replace all possibly dangerous chars in filename */ + $filename = Sanitize::sanitizeFilename($filename); + if (!empty($filename)) { + header('Content-Description: File Transfer'); + header('Content-Disposition: attachment; filename="' . $filename . '"'); + } + header('Content-Type: ' . $mimetype); + // inform the server that compression has been done, + // to avoid a double compression (for example with Apache + mod_deflate) + $notChromeOrLessThan43 = PMA_USR_BROWSER_AGENT != 'CHROME' // see bug #4942 + || (PMA_USR_BROWSER_AGENT == 'CHROME' && PMA_USR_BROWSER_VER < 43); + if (strpos($mimetype, 'gzip') !== false && $notChromeOrLessThan43) { + header('Content-Encoding: gzip'); + } + header('Content-Transfer-Encoding: binary'); + if ($length > 0) { + header('Content-Length: ' . $length); + } + } + + /** + * Returns value of an element in $array given by $path. + * $path is a string describing position of an element in an associative array, + * eg. Servers/1/host refers to $array[Servers][1][host] + * + * @param string $path path in the array + * @param array $array the array + * @param mixed $default default value + * + * @return mixed array element or $default + */ + public static function arrayRead($path, array $array, $default = null) + { + $keys = explode('/', $path); + $value =& $array; + foreach ($keys as $key) { + if (! isset($value[$key])) { + return $default; + } + $value =& $value[$key]; + } + return $value; + } + + /** + * Stores value in an array + * + * @param string $path path in the array + * @param array &$array the array + * @param mixed $value value to store + * + * @return void + */ + public static function arrayWrite($path, array &$array, $value) + { + $keys = explode('/', $path); + $last_key = array_pop($keys); + $a =& $array; + foreach ($keys as $key) { + if (! isset($a[$key])) { + $a[$key] = array(); + } + $a =& $a[$key]; + } + $a[$last_key] = $value; + } + + /** + * Removes value from an array + * + * @param string $path path in the array + * @param array &$array the array + * + * @return void + */ + public static function arrayRemove($path, array &$array) + { + $keys = explode('/', $path); + $keys_last = array_pop($keys); + $path = array(); + $depth = 0; + + $path[0] =& $array; + $found = true; + // go as deep as required or possible + foreach ($keys as $key) { + if (! isset($path[$depth][$key])) { + $found = false; + break; + } + $depth++; + $path[$depth] =& $path[$depth - 1][$key]; + } + // if element found, remove it + if ($found) { + unset($path[$depth][$keys_last]); + $depth--; + } + + // remove empty nested arrays + for (; $depth >= 0; $depth--) { + if (! isset($path[$depth+1]) || count($path[$depth+1]) == 0) { + unset($path[$depth][$keys[$depth]]); + } else { + break; + } + } + } + + /** + * Returns link to (possibly) external site using defined redirector. + * + * @param string $url URL where to go. + * + * @return string URL for a link. + */ + public static function linkURL($url) + { + if (!preg_match('#^https?://#', $url)) { + return $url; + } + + $params = array(); + $params['url'] = $url; + + $url = Url::getCommon($params); + //strip off token and such sensitive information. Just keep url. + $arr = parse_url($url); + parse_str($arr["query"], $vars); + $query = http_build_query(array("url" => $vars["url"])); + + if (!is_null($GLOBALS['PMA_Config']) && $GLOBALS['PMA_Config']->get('is_setup')) { + $url = '../url.php?' . $query; + } else { + $url = './url.php?' . $query; + } + + return $url; + } + + /** + * Checks whether domain of URL is whitelisted domain or not. + * Use only for URLs of external sites. + * + * @param string $url URL of external site. + * + * @return boolean True: if domain of $url is allowed domain, + * False: otherwise. + */ + public static function isAllowedDomain($url) + { + $arr = parse_url($url); + // We need host to be set + if (! isset($arr['host']) || strlen($arr['host']) == 0) { + return false; + } + // We do not want these to be present + $blocked = array('user', 'pass', 'port'); + foreach ($blocked as $part) { + if (isset($arr[$part]) && strlen($arr[$part]) != 0) { + return false; + } + } + $domain = $arr["host"]; + $domainWhiteList = array( + /* Include current domain */ + $_SERVER['SERVER_NAME'], + /* phpMyAdmin domains */ + 'wiki.phpmyadmin.net', + 'www.phpmyadmin.net', + 'phpmyadmin.net', + 'demo.phpmyadmin.net', + 'docs.phpmyadmin.net', + /* mysql.com domains */ + 'dev.mysql.com','bugs.mysql.com', + /* mariadb domains */ + 'mariadb.org', 'mariadb.com', + /* php.net domains */ + 'php.net', + 'secure.php.net', + /* Github domains*/ + 'github.com','www.github.com', + /* Percona domains */ + 'www.percona.com', + /* Following are doubtful ones. */ + 'mysqldatabaseadministration.blogspot.com', + ); + + return in_array($domain, $domainWhiteList); + } + + /** + * Replace some html-unfriendly stuff + * + * @param string $buffer String to process + * + * @return string Escaped and cleaned up text suitable for html + */ + public static function mimeDefaultFunction($buffer) + { + $buffer = htmlspecialchars($buffer); + $buffer = str_replace(' ', '  ', $buffer); + $buffer = preg_replace("@((\015\012)|(\015)|(\012))@", '
      ' . "\n", $buffer); + + return $buffer; + } + + /** + * Displays SQL query before executing. + * + * @param array|string $query_data Array containing queries or query itself + * + * @return void + */ + public static function previewSQL($query_data) + { + $retval = '
      '; + if (empty($query_data)) { + $retval .= __('No change'); + } elseif (is_array($query_data)) { + foreach ($query_data as $query) { + $retval .= Util::formatSql($query); + } + } else { + $retval .= Util::formatSql($query_data); + } + $retval .= '
      '; + $response = Response::getInstance(); + $response->addJSON('sql_data', $retval); + exit; + } + + /** + * recursively check if variable is empty + * + * @param mixed $value the variable + * + * @return bool true if empty + */ + public static function emptyRecursive($value) + { + $empty = true; + if (is_array($value)) { + array_walk_recursive( + $value, + function ($item) use (&$empty) { + $empty = $empty && empty($item); + } + ); + } else { + $empty = empty($value); + } + return $empty; + } + + /** + * Creates some globals from $_POST variables matching a pattern + * + * @param array $post_patterns The patterns to search for + * + * @return void + */ + public static function setPostAsGlobal(array $post_patterns) + { + foreach (array_keys($_POST) as $post_key) { + foreach ($post_patterns as $one_post_pattern) { + if (preg_match($one_post_pattern, $post_key)) { + $GLOBALS[$post_key] = $_POST[$post_key]; + } + } + } + } + + /** + * Creates some globals from $_REQUEST + * + * @param string $param db|table + * + * @return void + */ + public static function setGlobalDbOrTable($param) + { + $GLOBALS[$param] = ''; + if (self::isValid($_REQUEST[$param])) { + // can we strip tags from this? + // only \ and / is not allowed in db names for MySQL + $GLOBALS[$param] = $_REQUEST[$param]; + $GLOBALS['url_params'][$param] = $GLOBALS[$param]; + } + } + + /** + * PATH_INFO could be compromised if set, so remove it from PHP_SELF + * and provide a clean PHP_SELF here + * + * @return void + */ + public static function cleanupPathInfo() + { + global $PMA_PHP_SELF; + + $PMA_PHP_SELF = self::getenv('PHP_SELF'); + if (empty($PMA_PHP_SELF)) { + $PMA_PHP_SELF = urldecode(self::getenv('REQUEST_URI')); + } + $_PATH_INFO = self::getenv('PATH_INFO'); + if (! empty($_PATH_INFO) && ! empty($PMA_PHP_SELF)) { + $question_pos = mb_strpos($PMA_PHP_SELF, '?'); + if ($question_pos != false) { + $PMA_PHP_SELF = mb_substr($PMA_PHP_SELF, 0, $question_pos); + } + $path_info_pos = mb_strrpos($PMA_PHP_SELF, $_PATH_INFO); + if ($path_info_pos !== false) { + $path_info_part = mb_substr($PMA_PHP_SELF, $path_info_pos, mb_strlen($_PATH_INFO)); + if ($path_info_part == $_PATH_INFO) { + $PMA_PHP_SELF = mb_substr($PMA_PHP_SELF, 0, $path_info_pos); + } + } + } + + $path = []; + foreach(explode('/', $PMA_PHP_SELF) as $part) { + // ignore parts that have no value + if (empty($part) || $part === '.') continue; + + if ($part !== '..') { + // cool, we found a new part + array_push($path, $part); + } elseif (count($path) > 0) { + // going back up? sure + array_pop($path); + } + // Here we intentionall ignore case where we go too up + // as there is nothing sane to do + } + + $PMA_PHP_SELF = htmlspecialchars('/' . join('/', $path)); + } + + /** + * Checks that required PHP extensions are there. + * @return void + */ + public static function checkExtensions() + { + /** + * Warning about mbstring. + */ + if (! function_exists('mb_detect_encoding')) { + self::warnMissingExtension('mbstring'); + } + + /** + * We really need this one! + */ + if (! function_exists('preg_replace')) { + self::warnMissingExtension('pcre', true); + } + + /** + * JSON is required in several places. + */ + if (! function_exists('json_encode')) { + self::warnMissingExtension('json', true); + } + + /** + * ctype is required for Twig. + */ + if (! function_exists('ctype_alpha')) { + self::warnMissingExtension('ctype', true); + } + + /** + * hash is required for cookie authentication. + */ + if (! function_exists('hash_hmac')) { + self::warnMissingExtension('hash', true); + } + } + + /** + * Gets the "true" IP address of the current user + * + * @return string the ip of the user + * + * @access private + */ + public static function getIp() + { + /* Get the address of user */ + if (empty($_SERVER['REMOTE_ADDR'])) { + /* We do not know remote IP */ + return false; + } + + $direct_ip = $_SERVER['REMOTE_ADDR']; + + /* Do we trust this IP as a proxy? If yes we will use it's header. */ + if (!isset($GLOBALS['cfg']['TrustedProxies'][$direct_ip])) { + /* Return true IP */ + return $direct_ip; + } + + /** + * Parse header in form: + * X-Forwarded-For: client, proxy1, proxy2 + */ + // Get header content + $value = self::getenv($GLOBALS['cfg']['TrustedProxies'][$direct_ip]); + // Grab first element what is client adddress + $value = explode(',', $value)[0]; + // checks that the header contains only one IP address, + $is_ip = filter_var($value, FILTER_VALIDATE_IP); + + if ($is_ip !== false) { + // True IP behind a proxy + return $value; + } + + // We could not parse header + return false; + } // end of the 'getIp()' function + + /** + * Sanitizes MySQL hostname + * + * * strips p: prefix(es) + * + * @param string $name User given hostname + * + * @return string + */ + public static function sanitizeMySQLHost($name) + { + while (strtolower(substr($name, 0, 2)) == 'p:') { + $name = substr($name, 2); + } + + return $name; + } + + /** + * Sanitizes MySQL username + * + * * strips part behind null byte + * + * @param string $name User given username + * + * @return string + */ + public static function sanitizeMySQLUser($name) + { + $position = strpos($name, chr(0)); + if ($position !== false) { + return substr($name, 0, $position); + } + return $name; + } + + /** + * Safe unserializer wrapper + * + * It does not unserialize data containing objects + * + * @param string $data Data to unserialize + * + * @return mixed + */ + public static function safeUnserialize($data) + { + if (! is_string($data)) { + return null; + } + + /* validate serialized data */ + $length = strlen($data); + $depth = 0; + for ($i = 0; $i < $length; $i++) { + $value = $data[$i]; + + switch ($value) + { + case '}': + /* end of array */ + if ($depth <= 0) { + return null; + } + $depth--; + break; + case 's': + /* string */ + // parse sting length + $strlen = intval(substr($data, $i + 2)); + // string start + $i = strpos($data, ':', $i + 2); + if ($i === false) { + return null; + } + // skip string, quotes and ; + $i += 2 + $strlen + 1; + if ($data[$i] != ';') { + return null; + } + break; + + case 'b': + case 'i': + case 'd': + /* bool, integer or double */ + // skip value to sepearator + $i = strpos($data, ';', $i); + if ($i === false) { + return null; + } + break; + case 'a': + /* array */ + // find array start + $i = strpos($data, '{', $i); + if ($i === false) { + return null; + } + // remember nesting + $depth++; + break; + case 'N': + /* null */ + // skip to end + $i = strpos($data, ';', $i); + if ($i === false) { + return null; + } + break; + default: + /* any other elements are not wanted */ + return null; + } + } + + // check unterminated arrays + if ($depth > 0) { + return null; + } + + return unserialize($data); + } + + /** + * Applies changes to PHP configuration. + * + * @return void + */ + public static function configure() + { + /** + * Set utf-8 encoding for PHP + */ + ini_set('default_charset', 'utf-8'); + mb_internal_encoding('utf-8'); + + /** + * Set precision to sane value, with higher values + * things behave slightly unexpectedly, for example + * round(1.2, 2) returns 1.199999999999999956. + */ + ini_set('precision', 14); + + /** + * check timezone setting + * this could produce an E_WARNING - but only once, + * if not done here it will produce E_WARNING on every date/time function + */ + date_default_timezone_set(@date_default_timezone_get()); + } + + /** + * Check whether PHP configuration matches our needs. + * + * @return void + */ + public static function checkConfiguration() + { + /** + * As we try to handle charsets by ourself, mbstring overloads just + * break it, see bug 1063821. + * + * We specifically use empty here as we are looking for anything else than + * empty value or 0. + */ + if (extension_loaded('mbstring') && !empty(ini_get('mbstring.func_overload'))) { + self::fatalError( + __( + 'You have enabled mbstring.func_overload in your PHP ' + . 'configuration. This option is incompatible with phpMyAdmin ' + . 'and might cause some data to be corrupted!' + ) + ); + } + + /** + * The ini_set and ini_get functions can be disabled using + * disable_functions but we're relying quite a lot of them. + */ + if (! function_exists('ini_get') || ! function_exists('ini_set')) { + self::fatalError( + __( + 'You have disabled ini_get and/or ini_set in php.ini. ' + . 'This option is incompatible with phpMyAdmin!' + ) + ); + } + } + + /** + * prints list item for main page + * + * @param string $name displayed text + * @param string $listId id, used for css styles + * @param string $url make item as link with $url as target + * @param string $mysql_help_page display a link to MySQL's manual + * @param string $target special target for $url + * @param string $a_id id for the anchor, + * used for jQuery to hook in functions + * @param string $class class for the li element + * @param string $a_class class for the anchor element + * + * @return void + */ + public static function printListItem($name, $listId = null, $url = null, + $mysql_help_page = null, $target = null, $a_id = null, $class = null, + $a_class = null + ) { + echo Template::get('list/item') + ->render( + array( + 'content' => $name, + 'id' => $listId, + 'class' => $class, + 'url' => array( + 'href' => $url, + 'target' => $target, + 'id' => $a_id, + 'class' => $a_class, + ), + 'mysql_help_page' => $mysql_help_page, + ) + ); + } + + /** + * Checks request and fails with fatal error if something problematic is found + * + * @return void + */ + public static function checkRequest() + { + if (isset($_REQUEST['GLOBALS']) || isset($_FILES['GLOBALS'])) { + self::fatalError(__("GLOBALS overwrite attempt")); + } + + /** + * protect against possible exploits - there is no need to have so much variables + */ + if (count($_REQUEST) > 1000) { + self::fatalError(__('possible exploit')); + } + } + + /** + * Sign the sql query using hmac using the session token + * + * @param string $sqlQuery The sql query + * @return string + */ + public static function signSqlQuery($sqlQuery) + { + /** @var array $cfg */ + global $cfg; + return hash_hmac('sha256', $sqlQuery, $_SESSION[' HMAC_secret '] . $cfg['blowfish_secret']); + } + + /** + * Check that the sql query has a valid hmac signature + * + * @param string $sqlQuery The sql query + * @param string $signature The Signature to check + * @return bool + */ + public static function checkSqlQuerySignature($sqlQuery, $signature) + { + /** @var array $cfg */ + global $cfg; + $hmac = hash_hmac('sha256', $sqlQuery, $_SESSION[' HMAC_secret '] . $cfg['blowfish_secret']); + return hash_equals($hmac, $signature); + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/CreateAddField.php b/php/apps/phpmyadmin49/html/libraries/classes/CreateAddField.php new file mode 100644 index 00000000..718b34d9 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/CreateAddField.php @@ -0,0 +1,556 @@ +dbi = $dbi; + } + + /** + * Transforms the radio button field_key into 4 arrays + * + * @return array An array of arrays which represents column keys for each index type + */ + private function getIndexedColumns() + { + $fieldCount = count($_POST['field_name']); + $fieldPrimary = json_decode($_POST['primary_indexes'], true); + $fieldIndex = json_decode($_POST['indexes'], true); + $fieldUnique = json_decode($_POST['unique_indexes'], true); + $fieldFullText = json_decode($_POST['fulltext_indexes'], true); + $fieldSpatial = json_decode($_POST['spatial_indexes'], true); + + return [ + $fieldCount, + $fieldPrimary, + $fieldIndex, + $fieldUnique, + $fieldFullText, + $fieldSpatial, + ]; + } + + /** + * Initiate the column creation statement according to the table creation or + * add columns to a existing table + * + * @param int $fieldCount number of columns + * @param boolean $isCreateTable true if requirement is to get the statement + * for table creation + * + * @return array $definitions An array of initial sql statements + * according to the request + */ + private function buildColumnCreationStatement( + $fieldCount, + $isCreateTable = true + ) { + $definitions = []; + $previousField = -1; + for ($i = 0; $i < $fieldCount; ++$i) { + // '0' is also empty for php :-( + if (strlen($_POST['field_name'][$i]) === 0) { + continue; + } + + $definition = $this->getStatementPrefix($isCreateTable) . + Table::generateFieldSpec( + trim($_POST['field_name'][$i]), + $_POST['field_type'][$i], + $_POST['field_length'][$i], + $_POST['field_attribute'][$i], + isset($_POST['field_collation'][$i]) + ? $_POST['field_collation'][$i] + : '', + isset($_POST['field_null'][$i]) + ? $_POST['field_null'][$i] + : 'NOT NULL', + $_POST['field_default_type'][$i], + $_POST['field_default_value'][$i], + isset($_POST['field_extra'][$i]) + ? $_POST['field_extra'][$i] + : false, + isset($_POST['field_comments'][$i]) + ? $_POST['field_comments'][$i] + : '', + isset($_POST['field_virtuality'][$i]) + ? $_POST['field_virtuality'][$i] + : '', + isset($_POST['field_expression'][$i]) + ? $_POST['field_expression'][$i] + : '' + ); + + $definition .= $this->setColumnCreationStatementSuffix($i, $previousField, $isCreateTable); + $previousField = $i; + $definitions[] = $definition; + } // end for + + return $definitions; + } + + /** + * Set column creation suffix according to requested position of the new column + * + * @param int $currentFieldNumber current column number + * @param int $previousField previous field for ALTER statement + * @param boolean $isCreateTable true if requirement is to get the statement + * for table creation + * + * @return string $sqlSuffix suffix + */ + private function setColumnCreationStatementSuffix( + $currentFieldNumber, + $previousField, + $isCreateTable = true + ) { + // no suffix is needed if request is a table creation + $sqlSuffix = ' '; + if ($isCreateTable) { + return $sqlSuffix; + } + + if ((string) $_POST['field_where'] === 'last') { + return $sqlSuffix; + } + + // Only the first field can be added somewhere other than at the end + if ($previousField == -1) { + if ((string) $_POST['field_where'] === 'first') { + $sqlSuffix .= ' FIRST'; + } else if (! empty($_POST['after_field'])) { + $sqlSuffix .= ' AFTER ' + . Util::backquote($_POST['after_field']); + } + } else { + $sqlSuffix .= ' AFTER ' + . Util::backquote( + $_POST['field_name'][$previousField] + ); + } + + return $sqlSuffix; + } + + /** + * Create relevant index statements + * + * @param array $index an array of index columns + * @param string $indexChoice index choice that which represents + * the index type of $indexed_fields + * @param boolean $isCreateTable true if requirement is to get the statement + * for table creation + * + * @return array an array of sql statements for indexes + */ + private function buildIndexStatements( + array $index, + $indexChoice, + $isCreateTable = true + ) { + $statement = []; + if (!count($index)) { + return $statement; + } + + $sqlQuery = $this->getStatementPrefix($isCreateTable) + . ' ' . $indexChoice; + + if (! empty($index['Key_name']) && $index['Key_name'] != 'PRIMARY') { + $sqlQuery .= ' ' . Util::backquote($index['Key_name']); + } + + $indexFields = []; + foreach ($index['columns'] as $key => $column) { + $indexFields[$key] = Util::backquote( + $_POST['field_name'][$column['col_index']] + ); + if ($column['size']) { + $indexFields[$key] .= '(' . $column['size'] . ')'; + } + } + + $sqlQuery .= ' (' . implode(', ', $indexFields) . ')'; + + $keyBlockSizes = $index['Key_block_size']; + if (! empty($keyBlockSizes)) { + $sqlQuery .= " KEY_BLOCK_SIZE = " + . $this->dbi->escapeString($keyBlockSizes); + } + + // specifying index type is allowed only for primary, unique and index only + $type = $index['Index_type']; + if ($index['Index_choice'] != 'SPATIAL' + && $index['Index_choice'] != 'FULLTEXT' + && in_array($type, Index::getIndexTypes()) + ) { + $sqlQuery .= ' USING ' . $type; + } + + $parser = $index['Parser']; + if ($index['Index_choice'] == 'FULLTEXT' && ! empty($parser)) { + $sqlQuery .= " WITH PARSER " . $this->dbi->escapeString($parser); + } + + $comment = $index['Index_comment']; + if (! empty($comment)) { + $sqlQuery .= " COMMENT '" . $this->dbi->escapeString($comment) + . "'"; + } + + $statement[] = $sqlQuery; + + return $statement; + } + + /** + * Statement prefix for the buildColumnCreationStatement() + * + * @param boolean $isCreateTable true if requirement is to get the statement + * for table creation + * + * @return string $sqlPrefix prefix + */ + private function getStatementPrefix($isCreateTable = true) + { + $sqlPrefix = " "; + if (! $isCreateTable) { + $sqlPrefix = ' ADD '; + } + return $sqlPrefix; + } + + /** + * Merge index definitions for one type of index + * + * @param array $definitions the index definitions to merge to + * @param boolean $isCreateTable true if requirement is to get the statement + * for table creation + * @param array $indexedColumns the columns for one type of index + * @param string $indexKeyword the index keyword to use in the definition + * + * @return array $index_definitions + */ + private function mergeIndexStatements( + array $definitions, + $isCreateTable, + array $indexedColumns, + $indexKeyword + ) { + foreach ($indexedColumns as $index) { + $statements = $this->buildIndexStatements( + $index, + " " . $indexKeyword . " ", + $isCreateTable + ); + $definitions = array_merge($definitions, $statements); + } + return $definitions; + } + + /** + * Returns sql statement according to the column and index specifications as + * requested + * + * @param boolean $isCreateTable true if requirement is to get the statement + * for table creation + * + * @return string sql statement + */ + private function getColumnCreationStatements($isCreateTable = true) + { + $sqlStatement = ""; + list( + $fieldCount, + $fieldPrimary, + $fieldIndex, + $fieldUnique, + $fieldFullText, + $fieldSpatial + ) = $this->getIndexedColumns(); + $definitions = $this->buildColumnCreationStatement( + $fieldCount, + $isCreateTable + ); + + // Builds the PRIMARY KEY statements + $primaryKeyStatements = $this->buildIndexStatements( + isset($fieldPrimary[0]) ? $fieldPrimary[0] : [], + " PRIMARY KEY ", + $isCreateTable + ); + $definitions = array_merge($definitions, $primaryKeyStatements); + + // Builds the INDEX statements + $definitions = $this->mergeIndexStatements( + $definitions, + $isCreateTable, + $fieldIndex, + "INDEX" + ); + + // Builds the UNIQUE statements + $definitions = $this->mergeIndexStatements( + $definitions, + $isCreateTable, + $fieldUnique, + "UNIQUE" + ); + + // Builds the FULLTEXT statements + $definitions = $this->mergeIndexStatements( + $definitions, + $isCreateTable, + $fieldFullText, + "FULLTEXT" + ); + + // Builds the SPATIAL statements + $definitions = $this->mergeIndexStatements( + $definitions, + $isCreateTable, + $fieldSpatial, + "SPATIAL" + ); + + if (count($definitions)) { + $sqlStatement = implode(', ', $definitions); + } + $sqlStatement = preg_replace('@, $@', '', $sqlStatement); + + return $sqlStatement; + } + + /** + * Returns the partitioning clause + * + * @return string partitioning clause + */ + public function getPartitionsDefinition() + { + $sqlQuery = ""; + if (! empty($_POST['partition_by']) + && ! empty($_POST['partition_expr']) + && ! empty($_POST['partition_count']) + && $_POST['partition_count'] > 1 + ) { + $sqlQuery .= " PARTITION BY " . $_POST['partition_by'] + . " (" . $_POST['partition_expr'] . ")" + . " PARTITIONS " . $_POST['partition_count']; + } + + if (! empty($_POST['subpartition_by']) + && ! empty($_POST['subpartition_expr']) + && ! empty($_POST['subpartition_count']) + && $_POST['subpartition_count'] > 1 + ) { + $sqlQuery .= " SUBPARTITION BY " . $_POST['subpartition_by'] + . " (" . $_POST['subpartition_expr'] . ")" + . " SUBPARTITIONS " . $_POST['subpartition_count']; + } + + if (! empty($_POST['partitions'])) { + $i = 0; + $partitions = []; + foreach ($_POST['partitions'] as $partition) { + $partitions[] = $this->getPartitionDefinition($partition); + $i++; + } + $sqlQuery .= " (" . implode(", ", $partitions) . ")"; + } + + return $sqlQuery; + } + + /** + * Returns the definition of a partition/subpartition + * + * @param array $partition array of partition/subpartition detiails + * @param boolean $isSubPartition whether a subpartition + * + * @return string partition/subpartition definition + */ + private function getPartitionDefinition(array $partition, $isSubPartition = false) + { + $sqlQuery = " " . ($isSubPartition ? "SUB" : "") . "PARTITION "; + $sqlQuery .= $partition['name']; + + if (! empty($partition['value_type'])) { + $sqlQuery .= " VALUES " . $partition['value_type']; + + if ($partition['value_type'] != 'LESS THAN MAXVALUE') { + $sqlQuery .= " (" . $partition['value'] . ")"; + } + } + + if (! empty($partition['engine'])) { + $sqlQuery .= " ENGINE = " . $partition['engine']; + } + if (! empty($partition['comment'])) { + $sqlQuery .= " COMMENT = '" . $partition['comment'] . "'"; + } + if (! empty($partition['data_directory'])) { + $sqlQuery .= " DATA DIRECTORY = '" . $partition['data_directory'] . "'"; + } + if (! empty($partition['index_directory'])) { + $sqlQuery .= " INDEX_DIRECTORY = '" . $partition['index_directory'] . "'"; + } + if (! empty($partition['max_rows'])) { + $sqlQuery .= " MAX_ROWS = " . $partition['max_rows']; + } + if (! empty($partition['min_rows'])) { + $sqlQuery .= " MIN_ROWS = " . $partition['min_rows']; + } + if (! empty($partition['tablespace'])) { + $sqlQuery .= " TABLESPACE = " . $partition['tablespace']; + } + if (! empty($partition['node_group'])) { + $sqlQuery .= " NODEGROUP = " . $partition['node_group']; + } + + if (! empty($partition['subpartitions'])) { + $j = 0; + $subpartitions = []; + foreach ($partition['subpartitions'] as $subpartition) { + $subpartitions[] = $this->getPartitionDefinition( + $subpartition, + true + ); + $j++; + } + $sqlQuery .= " (" . implode(", ", $subpartitions) . ")"; + } + + return $sqlQuery; + } + + /** + * Function to get table creation sql query + * + * @param string $db database name + * @param string $table table name + * + * @return string + */ + public function getTableCreationQuery($db, $table) + { + // get column addition statements + $sqlStatement = $this->getColumnCreationStatements(true); + + // Builds the 'create table' statement + $sqlQuery = 'CREATE TABLE ' . Util::backquote($db) . '.' + . Util::backquote(trim($table)) . ' (' . $sqlStatement . ')'; + + // Adds table type, character set, comments and partition definition + if (!empty($_POST['tbl_storage_engine']) + && ($_POST['tbl_storage_engine'] != 'Default') + ) { + $sqlQuery .= ' ENGINE = ' . $_POST['tbl_storage_engine']; + } + if (!empty($_POST['tbl_collation'])) { + $sqlQuery .= Util::getCharsetQueryPart($_POST['tbl_collation']); + } + if (! empty($_POST['connection']) + && ! empty($_POST['tbl_storage_engine']) + && $_POST['tbl_storage_engine'] == 'FEDERATED' + ) { + $sqlQuery .= " CONNECTION = '" + . $this->dbi->escapeString($_POST['connection']) . "'"; + } + if (!empty($_POST['comment'])) { + $sqlQuery .= ' COMMENT = \'' + . $this->dbi->escapeString($_POST['comment']) . '\''; + } + $sqlQuery .= $this->getPartitionsDefinition(); + $sqlQuery .= ';'; + + return $sqlQuery; + } + + /** + * Function to get the number of fields for the table creation form + * + * @return int + */ + public function getNumberOfFieldsFromRequest() + { + // Limit to 4096 fields (MySQL maximal value) + $mysqlLimit = 4096; + + if (isset($_POST['submit_num_fields'])) { // adding new fields + $numberOfFields = intval($_POST['orig_num_fields']) + intval($_POST['added_fields']); + } elseif (isset($_POST['orig_num_fields'])) { // retaining existing fields + $numberOfFields = intval($_POST['orig_num_fields']); + } elseif (isset($_POST['num_fields']) + && intval($_POST['num_fields']) > 0 + ) { // new table with specified number of fields + $numberOfFields = intval($_POST['num_fields']); + } else { // new table with unspecified number of fields + $numberOfFields = 4; + } + + return min($numberOfFields, $mysqlLimit); + } + + /** + * Function to execute the column creation statement + * + * @param string $db current database + * @param string $table current table + * @param string $errorUrl error page url + * + * @return array + */ + public function tryColumnCreationQuery($db, $table, $errorUrl) + { + // get column addition statements + $sqlStatement = $this->getColumnCreationStatements(false); + + // To allow replication, we first select the db to use and then run queries + // on this db. + if (!($this->dbi->selectDb($db))) { + Util::mysqlDie( + $this->dbi->getError(), + 'USE ' . Util::backquote($db), + false, + $errorUrl + ); + } + $sqlQuery = 'ALTER TABLE ' . + Util::backquote($table) . ' ' . $sqlStatement . ';'; + // If there is a request for SQL previewing. + if (isset($_POST['preview_sql'])) { + Core::previewSQL($sqlQuery); + } + return [$this->dbi->tryQuery($sqlQuery), $sqlQuery]; + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Database/DatabaseList.php b/php/apps/phpmyadmin49/html/libraries/classes/Database/DatabaseList.php new file mode 100644 index 00000000..cc383eec --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Database/DatabaseList.php @@ -0,0 +1,58 @@ +getDatabaseList(); + } + + return null; + } + + /** + * Accessor to PMA::$databases + * + * @return ListDatabase + */ + public function getDatabaseList() + { + if (null === $this->databases) { + $this->databases = new ListDatabase(); + } + + return $this->databases; + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Database/Designer.php b/php/apps/phpmyadmin49/html/libraries/classes/Database/Designer.php new file mode 100644 index 00000000..95e07019 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Database/Designer.php @@ -0,0 +1,422 @@ +relation = new Relation(); + } + + /** + * Function to get html for displaying the page edit/delete form + * + * @param string $db database name + * @param string $operation 'edit' or 'delete' depending on the operation + * + * @return string html content + */ + public function getHtmlForEditOrDeletePages($db, $operation) + { + $cfgRelation = $this->relation->getRelationsParam(); + return Template::get('database/designer/edit_delete_pages')->render([ + 'db' => $db, + 'operation' => $operation, + 'pdfwork' => $cfgRelation['pdfwork'], + 'pages' => $this->getPageIdsAndNames($db), + ]); + } + + /** + * Function to get html for displaying the page save as form + * + * @param string $db database name + * + * @return string html content + */ + public function getHtmlForPageSaveAs($db) + { + $cfgRelation = $this->relation->getRelationsParam(); + return Template::get('database/designer/page_save_as')->render([ + 'db' => $db, + 'pdfwork' => $cfgRelation['pdfwork'], + 'pages' => $this->getPageIdsAndNames($db), + ]); + } + + /** + * Retrieve IDs and names of schema pages + * + * @param string $db database name + * + * @return array array of schema page id and names + */ + private function getPageIdsAndNames($db) + { + $cfgRelation = $this->relation->getRelationsParam(); + $page_query = "SELECT `page_nr`, `page_descr` FROM " + . Util::backquote($cfgRelation['db']) . "." + . Util::backquote($cfgRelation['pdf_pages']) + . " WHERE db_name = '" . $GLOBALS['dbi']->escapeString($db) . "'" + . " ORDER BY `page_descr`"; + $page_rs = $this->relation->queryAsControlUser( + $page_query, + false, + DatabaseInterface::QUERY_STORE + ); + + $result = []; + while ($curr_page = $GLOBALS['dbi']->fetchAssoc($page_rs)) { + $result[intval($curr_page['page_nr'])] = $curr_page['page_descr']; + } + return $result; + } + + /** + * Function to get html for displaying the schema export + * + * @param string $db database name + * @param int $page the page to be exported + * + * @return string + */ + public function getHtmlForSchemaExport($db, $page) + { + /* Scan for schema plugins */ + /* @var $export_list SchemaPlugin[] */ + $export_list = Plugins::getPlugins( + "schema", + 'libraries/classes/Plugins/Schema/', + null + ); + + /* Fail if we didn't find any schema plugin */ + if (empty($export_list)) { + return Message::error( + __('Could not load schema plugins, please check your installation!') + )->getDisplay(); + } + + return Template::get('database/designer/schema_export') + ->render( + [ + 'db' => $db, + 'page' => $page, + 'export_list' => $export_list + ] + ); + } + + /** + * Returns HTML for including some variable to be accessed by JavaScript + * + * @param array $script_tables array on foreign key support for each table + * @param array $script_contr initialization data array + * @param array $script_display_field display fields of each table + * @param int $display_page page number of the selected page + * + * @return string html + */ + public function getHtmlForJsFields( + array $script_tables, + array $script_contr, + array $script_display_field, + $display_page + ) { + $cfgRelation = $this->relation->getRelationsParam(); + $designerConfig = new \stdClass(); + $designerConfig->db = $_GET['db']; + $designerConfig->scriptTables = $script_tables; + $designerConfig->scriptContr = $script_contr; + $designerConfig->server = $GLOBALS['server']; + $designerConfig->scriptDisplayField = $script_display_field; + $designerConfig->displayPage = (int) $display_page; + $designerConfig->tablesEnabled = $cfgRelation['pdfwork']; + return Template::get('database/designer/js_fields')->render([ + 'designer_config' => json_encode($designerConfig) + ]); + } + + /** + * Returns HTML for the menu bar of the designer page + * + * @param boolean $visualBuilder whether this is visual query builder + * @param string $selectedPage name of the selected page + * @param array $paramsArray array with class name for various buttons + * on side menu + * + * @return string html + */ + public function getPageMenu($visualBuilder, $selectedPage, array $paramsArray) + { + return Template::get('database/designer/side_menu')->render([ + 'visual_builder' => $visualBuilder, + 'selected_page' => $selectedPage, + 'params_array' => $paramsArray, + 'theme' => $GLOBALS['PMA_Theme'], + ]); + } + + /** + * Returns array of stored values of Designer Settings + * + * @return array stored values + */ + private function getSideMenuParamsArray() + { + $params = []; + + $cfgRelation = $this->relation->getRelationsParam(); + + if ($GLOBALS['cfgRelation']['designersettingswork']) { + $query = 'SELECT `settings_data` FROM ' + . Util::backquote($cfgRelation['db']) . '.' + . Util::backquote($cfgRelation['designer_settings']) + . ' WHERE ' . Util::backquote('username') . ' = "' + . $GLOBALS['dbi']->escapeString($GLOBALS['cfg']['Server']['user']) + . '";'; + + $result = $GLOBALS['dbi']->fetchSingleRow($query); + + $params = json_decode($result['settings_data'], true); + } + + return $params; + } + + /** + * Returns class names for various buttons on Designer Side Menu + * + * @return array class names of various buttons + */ + public function returnClassNamesFromMenuButtons() + { + $classes_array = []; + $params_array = $this->getSideMenuParamsArray(); + + if (isset($params_array['angular_direct']) + && $params_array['angular_direct'] == 'angular' + ) { + $classes_array['angular_direct'] = 'M_butt_Selected_down'; + } else { + $classes_array['angular_direct'] = 'M_butt'; + } + + if (isset($params_array['snap_to_grid']) + && $params_array['snap_to_grid'] == 'on' + ) { + $classes_array['snap_to_grid'] = 'M_butt_Selected_down'; + } else { + $classes_array['snap_to_grid'] = 'M_butt'; + } + + if (isset($params_array['pin_text']) + && $params_array['pin_text'] == 'true' + ) { + $classes_array['pin_text'] = 'M_butt_Selected_down'; + } else { + $classes_array['pin_text'] = 'M_butt'; + } + + if (isset($params_array['relation_lines']) + && $params_array['relation_lines'] == 'false' + ) { + $classes_array['relation_lines'] = 'M_butt_Selected_down'; + } else { + $classes_array['relation_lines'] = 'M_butt'; + } + + if (isset($params_array['small_big_all']) + && $params_array['small_big_all'] == 'v' + ) { + $classes_array['small_big_all'] = 'M_butt_Selected_down'; + } else { + $classes_array['small_big_all'] = 'M_butt'; + } + + if (isset($params_array['side_menu']) + && $params_array['side_menu'] == 'true' + ) { + $classes_array['side_menu'] = 'M_butt_Selected_down'; + } else { + $classes_array['side_menu'] = 'M_butt'; + } + + return $classes_array; + } + + /** + * Returns HTML for the canvas element + * + * @return string html + */ + public function getHtmlCanvas() + { + return Template::get('database/designer/canvas')->render(); + } + + /** + * Return HTML for the table list + * + * @return string html + */ + public function getHtmlTableList() + { + return Template::get('database/designer/table_list')->render([ + 'theme' => $GLOBALS['PMA_Theme'], + ]); + } + + /** + * Get HTML to display tables on designer page + * + * @param string $db The database name from the request + * @param array $designerTables The designer tables + * @param array $tab_pos tables positions + * @param int $display_page page number of the selected page + * @param array $tab_column table column info + * @param array $tables_all_keys all indices + * @param array $tables_pk_or_unique_keys unique or primary indices + * + * @return string html + */ + public function getDatabaseTables( + $db, + array $designerTables, + array $tab_pos, + $display_page, + array $tab_column, + array $tables_all_keys, + array $tables_pk_or_unique_keys + ) { + return Template::get('database/designer/database_tables')->render([ + 'db' => $GLOBALS['db'], + 'get_db' => $db, + 'has_query' => isset($_REQUEST['query']), + 'tab_pos' => $tab_pos, + 'display_page' => $display_page, + 'tab_column' => $tab_column, + 'tables_all_keys' => $tables_all_keys, + 'tables_pk_or_unique_keys' => $tables_pk_or_unique_keys, + 'tables' => $designerTables, + 'theme' => $GLOBALS['PMA_Theme'], + ]); + } + + /** + * Returns HTML for the new relations panel. + * + * @return string html + */ + public function getNewRelationPanel() + { + return Template::get('database/designer/new_relation_panel') + ->render(); + } + + /** + * Returns HTML for the relations delete panel + * + * @return string html + */ + public function getDeleteRelationPanel() + { + return Template::get('database/designer/delete_relation_panel') + ->render(); + } + + /** + * Returns HTML for the options panel + * + * @return string html + */ + public function getOptionsPanel() + { + return Template::get('database/designer/options_panel')->render(); + } + + /** + * Get HTML for the 'rename to' panel + * + * @return string html + */ + public function getRenameToPanel() + { + return Template::get('database/designer/rename_to_panel') + ->render(); + } + + /** + * Returns HTML for the 'having' panel + * + * @return string html + */ + public function getHavingQueryPanel() + { + return Template::get('database/designer/having_query_panel') + ->render(); + } + + /** + * Returns HTML for the 'aggregate' panel + * + * @return string html + */ + public function getAggregateQueryPanel() + { + return Template::get('database/designer/aggregate_query_panel') + ->render(); + } + + /** + * Returns HTML for the 'where' panel + * + * @return string html + */ + public function getWhereQueryPanel() + { + return Template::get('database/designer/where_query_panel') + ->render(); + } + + /** + * Returns HTML for the query details panel + * + * @param string $db Database name + * + * @return string html + */ + public function getQueryDetails($db) + { + return Template::get('database/designer/query_details')->render([ + 'db' => $db, + ]); + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Database/Designer/Common.php b/php/apps/phpmyadmin49/html/libraries/classes/Database/Designer/Common.php new file mode 100644 index 00000000..2fec226b --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Database/Designer/Common.php @@ -0,0 +1,784 @@ +relation = new Relation(); + } + + /** + * Retrieves table info and returns it + * + * @param string $db (optional) Filter only a DB ($table is required if you use $db) + * @param string $table (optional) Filter only a table ($db is now required) + * @return DesignerTable[] with table info + */ + public function getTablesInfo($db = null, $table = null) + { + $designerTables = array(); + $db = ($db === null) ? $GLOBALS['db'] : $db; + // seems to be needed later + $GLOBALS['dbi']->selectDb($db); + if ($db === null && $table === null) { + $tables = $GLOBALS['dbi']->getTablesFull($db); + } else { + $tables = $GLOBALS['dbi']->getTablesFull($db, $table); + } + + + foreach ($tables as $one_table) { + $DF = $this->relation->getDisplayField($db, $one_table['TABLE_NAME']); + $DF = is_string($DF) ? $DF : ''; + $DF = ($DF !== '') ? $DF : null; + $designerTables[] = new DesignerTable( + $db, + $one_table['TABLE_NAME'], + $one_table['ENGINE'], + $DF + ); + } + + return $designerTables; + } + + /** + * Retrieves table column info + * + * @param DesignerTable[] $designerTables The designer tables + * @return array table column nfo + */ + public function getColumnsInfo($designerTables) + { + //$GLOBALS['dbi']->selectDb($GLOBALS['db']); + $tabColumn = array(); + + foreach($designerTables as $designerTable) { + $fieldsRs = $GLOBALS['dbi']->query( + $GLOBALS['dbi']->getColumnsSql( + $designerTable->getDatabaseName(), + $designerTable->getTableName(), + null, + true + ), + DatabaseInterface::CONNECT_USER, + DatabaseInterface::QUERY_STORE + ); + $j = 0; + while ($row = $GLOBALS['dbi']->fetchAssoc($fieldsRs)) { + if (! isset($tabColumn[$designerTable->getDbTableString()])) { + $tabColumn[$designerTable->getDbTableString()] = []; + } + $tabColumn[$designerTable->getDbTableString()]['COLUMN_ID'][$j] = $j; + $tabColumn[$designerTable->getDbTableString()]['COLUMN_NAME'][$j] = $row['Field']; + $tabColumn[$designerTable->getDbTableString()]['TYPE'][$j] = $row['Type']; + $tabColumn[$designerTable->getDbTableString()]['NULLABLE'][$j] = $row['Null']; + $j++; + } + } + + return $tabColumn; + } + + /** + * Returns JavaScript code for initializing vars + * + * @param DesignerTable[] $designerTables The designer tables + * @return string JavaScript code + */ + public function getScriptContr($designerTables) + { + $GLOBALS['dbi']->selectDb($GLOBALS['db']); + $con = array(); + $con["C_NAME"] = array(); + $i = 0; + $alltab_rs = $GLOBALS['dbi']->query( + 'SHOW TABLES FROM ' . Util::backquote($GLOBALS['db']), + DatabaseInterface::CONNECT_USER, + DatabaseInterface::QUERY_STORE + ); + while ($val = @$GLOBALS['dbi']->fetchRow($alltab_rs)) { + $row = $this->relation->getForeigners($GLOBALS['db'], $val[0], '', 'internal'); + + if ($row !== false) { + foreach ($row as $field => $value) { + $con['C_NAME'][$i] = ''; + $con['DTN'][$i] = rawurlencode($GLOBALS['db'] . "." . $val[0]); + $con['DCN'][$i] = rawurlencode($field); + $con['STN'][$i] = rawurlencode( + $value['foreign_db'] . "." . $value['foreign_table'] + ); + $con['SCN'][$i] = rawurlencode($value['foreign_field']); + $i++; + } + } + $row = $this->relation->getForeigners($GLOBALS['db'], $val[0], '', 'foreign'); + + // We do not have access to the foreign keys if he user has partial access to the columns + if ($row !== false && isset($row['foreign_keys_data'])) { + foreach ($row['foreign_keys_data'] as $one_key) { + foreach ($one_key['index_list'] as $index => $one_field) { + $con['C_NAME'][$i] = rawurlencode($one_key['constraint']); + $con['DTN'][$i] = rawurlencode($GLOBALS['db'] . "." . $val[0]); + $con['DCN'][$i] = rawurlencode($one_field); + $con['STN'][$i] = rawurlencode( + (isset($one_key['ref_db_name']) ? + $one_key['ref_db_name'] : $GLOBALS['db']) + . "." . $one_key['ref_table_name'] + ); + $con['SCN'][$i] = rawurlencode($one_key['ref_index_list'][$index]); + $i++; + } + } + } + } + + $tableDbNames = []; + foreach($designerTables as $designerTable) { + $tableDbNames[] = $designerTable->getDbTableString(); + } + + $ti = 0; + $retval = array(); + for ($i = 0, $cnt = count($con["C_NAME"]); $i < $cnt; $i++) { + $c_name_i = $con['C_NAME'][$i]; + $dtn_i = $con['DTN'][$i]; + $retval[$ti] = array(); + $retval[$ti][$c_name_i] = array(); + if (in_array($dtn_i, $tableDbNames) && in_array($con['STN'][$i], $tableDbNames)) { + $retval[$ti][$c_name_i][$dtn_i] = array(); + $retval[$ti][$c_name_i][$dtn_i][$con['DCN'][$i]] = array( + 0 => $con['STN'][$i], + 1 => $con['SCN'][$i] + ); + } + $ti++; + } + return $retval; + } + + /** + * Returns UNIQUE and PRIMARY indices + * + * @param DesignerTable[] $designerTables The designer tables + * @return array unique or primary indices + */ + public function getPkOrUniqueKeys($designerTables) + { + return $this->getAllKeys($designerTables, true); + } + + /** + * Returns all indices + * + * @param DesignerTable[] $designerTables The designer tables + * @param bool $unique_only whether to include only unique ones + * + * @return array indices + */ + public function getAllKeys($designerTables, $unique_only = false) + { + $keys = array(); + + foreach ($designerTables as $designerTable) { + $schema = $designerTable->getDatabaseName(); + // for now, take into account only the first index segment + foreach (Index::getFromTable($designerTable->getTableName(), $schema) as $index) { + if ($unique_only && ! $index->isUnique()) { + continue; + } + $columns = $index->getColumns(); + foreach ($columns as $column_name => $dummy) { + $keys[$schema . '.' . $designerTable->getTableName() . '.' . $column_name] = 1; + } + } + } + return $keys; + } + + /** + * Return j_tab and h_tab arrays + * + * @param DesignerTable[] $designerTables The designer tables + * @return array + */ + public function getScriptTabs($designerTables) + { + $retval = array( + 'j_tabs' => array(), + 'h_tabs' => array() + ); + + foreach($designerTables as $designerTable) { + $key = rawurlencode($designerTable->getDbTableString()); + $retval['j_tabs'][$key] = $designerTable->supportsForeignkeys() ? 1 : 0; + $retval['h_tabs'][$key] = 1; + } + + return $retval; + } + + /** + * Returns table positions of a given pdf page + * + * @param int $pg pdf page id + * + * @return array of table positions + */ + public function getTablePositions($pg) + { + $cfgRelation = $this->relation->getRelationsParam(); + if (! $cfgRelation['pdfwork']) { + return array(); + } + + $query = " + SELECT CONCAT_WS('.', `db_name`, `table_name`) AS `name`, + `db_name` as `dbName`, `table_name` as `tableName`, + `x` AS `X`, + `y` AS `Y`, + 1 AS `V`, + 1 AS `H` + FROM " . Util::backquote($cfgRelation['db']) + . "." . Util::backquote($cfgRelation['table_coords']) . " + WHERE pdf_page_number = " . intval($pg); + + $tab_pos = $GLOBALS['dbi']->fetchResult( + $query, + 'name', + null, + DatabaseInterface::CONNECT_CONTROL, + DatabaseInterface::QUERY_STORE + ); + return $tab_pos; + } + + /** + * Returns page name of a given pdf page + * + * @param int $pg pdf page id + * + * @return string|null table name + */ + public function getPageName($pg) + { + $cfgRelation = $this->relation->getRelationsParam(); + if (! $cfgRelation['pdfwork']) { + return null; + } + + $query = "SELECT `page_descr`" + . " FROM " . Util::backquote($cfgRelation['db']) + . "." . Util::backquote($cfgRelation['pdf_pages']) + . " WHERE " . Util::backquote('page_nr') . " = " . intval($pg); + $page_name = $GLOBALS['dbi']->fetchResult( + $query, + null, + null, + DatabaseInterface::CONNECT_CONTROL, + DatabaseInterface::QUERY_STORE + ); + return ( is_array($page_name) && isset($page_name[0]) ) ? $page_name[0] : null; + } + + /** + * Deletes a given pdf page and its corresponding coordinates + * + * @param int $pg page id + * + * @return boolean success/failure + */ + public function deletePage($pg) + { + $cfgRelation = $this->relation->getRelationsParam(); + if (! $cfgRelation['pdfwork']) { + return false; + } + + $query = "DELETE FROM " . Util::backquote($cfgRelation['db']) + . "." . Util::backquote($cfgRelation['table_coords']) + . " WHERE " . Util::backquote('pdf_page_number') . " = " . intval($pg); + $success = $this->relation->queryAsControlUser( + $query, true, DatabaseInterface::QUERY_STORE + ); + + if ($success) { + $query = "DELETE FROM " . Util::backquote($cfgRelation['db']) + . "." . Util::backquote($cfgRelation['pdf_pages']) + . " WHERE " . Util::backquote('page_nr') . " = " . intval($pg); + $success = $this->relation->queryAsControlUser( + $query, true, DatabaseInterface::QUERY_STORE + ); + } + + return (boolean) $success; + } + + /** + * Returns the id of the default pdf page of the database. + * Default page is the one which has the same name as the database. + * + * @param string $db database + * + * @return int id of the default pdf page for the database + */ + public function getDefaultPage($db) + { + $cfgRelation = $this->relation->getRelationsParam(); + if (! $cfgRelation['pdfwork']) { + return -1; + } + + $query = "SELECT `page_nr`" + . " FROM " . Util::backquote($cfgRelation['db']) + . "." . Util::backquote($cfgRelation['pdf_pages']) + . " WHERE `db_name` = '" . $GLOBALS['dbi']->escapeString($db) . "'" + . " AND `page_descr` = '" . $GLOBALS['dbi']->escapeString($db) . "'"; + + $default_page_no = $GLOBALS['dbi']->fetchResult( + $query, + null, + null, + DatabaseInterface::CONNECT_CONTROL, + DatabaseInterface::QUERY_STORE + ); + + if (is_array($default_page_no) && isset($default_page_no[0])) { + return intval($default_page_no[0]); + } + return -1; + } + + /** + * Get the id of the page to load. If a default page exists it will be returned. + * If no such exists, returns the id of the first page of the database. + * + * @param string $db database + * + * @return int id of the page to load + */ + public function getLoadingPage($db) + { + $cfgRelation = $this->relation->getRelationsParam(); + if (! $cfgRelation['pdfwork']) { + return -1; + } + + $page_no = -1; + + $default_page_no = $this->getDefaultPage($db); + if ($default_page_no != -1) { + $page_no = $default_page_no; + } else { + $query = "SELECT MIN(`page_nr`)" + . " FROM " . Util::backquote($cfgRelation['db']) + . "." . Util::backquote($cfgRelation['pdf_pages']) + . " WHERE `db_name` = '" . $GLOBALS['dbi']->escapeString($db) . "'"; + + $min_page_no = $GLOBALS['dbi']->fetchResult( + $query, + null, + null, + DatabaseInterface::CONNECT_CONTROL, + DatabaseInterface::QUERY_STORE + ); + if (is_array($min_page_no) && isset($min_page_no[0])) { + $page_no = $min_page_no[0]; + } + } + return intval($page_no); + } + + /** + * Creates a new page and returns its auto-incrementing id + * + * @param string $pageName name of the page + * @param string $db name of the database + * + * @return int|null + */ + public function createNewPage($pageName, $db) + { + $cfgRelation = $this->relation->getRelationsParam(); + if ($cfgRelation['pdfwork']) { + $pageNumber = $this->relation->createPage( + $pageName, + $cfgRelation, + $db + ); + return $pageNumber; + } + return null; + } + + /** + * Saves positions of table(s) of a given pdf page + * + * @param int $pg pdf page id + * + * @return boolean success/failure + */ + public function saveTablePositions($pg) + { + $pageId = $GLOBALS['dbi']->escapeString($pg); + + $db = $GLOBALS['dbi']->escapeString($_POST['db']); + + $cfgRelation = $this->relation->getRelationsParam(); + if (! $cfgRelation['pdfwork']) { + return false; + } + + $query = "DELETE FROM " + . Util::backquote($GLOBALS['cfgRelation']['db']) + . "." . Util::backquote( + $GLOBALS['cfgRelation']['table_coords'] + ) + . " WHERE `pdf_page_number` = '" . $pageId . "'"; + + $res = $this->relation->queryAsControlUser( + $query, + true, + DatabaseInterface::QUERY_STORE + ); + + if (!$res) { + return (boolean)$res; + } + + foreach ($_POST['t_h'] as $key => $value) { + $DB = $_POST['t_db'][$key]; + $TAB = $_POST['t_tbl'][$key]; + if (!$value) { + continue; + } + + $query = "INSERT INTO " + . Util::backquote($GLOBALS['cfgRelation']['db']) . "." + . Util::backquote($GLOBALS['cfgRelation']['table_coords']) + . " (`db_name`, `table_name`, `pdf_page_number`, `x`, `y`)" + . " VALUES (" + . "'" . $GLOBALS['dbi']->escapeString($DB) . "', " + . "'" . $GLOBALS['dbi']->escapeString($TAB) . "', " + . "'" . $pageId . "', " + . "'" . $GLOBALS['dbi']->escapeString($_POST['t_x'][$key]) . "', " + . "'" . $GLOBALS['dbi']->escapeString($_POST['t_y'][$key]) . "')"; + + $res = $this->relation->queryAsControlUser( + $query, true, DatabaseInterface::QUERY_STORE + ); + } + + return (boolean) $res; + } + + /** + * Saves the display field for a table. + * + * @param string $db database name + * @param string $table table name + * @param string $field display field name + * + * @return boolean + */ + public function saveDisplayField($db, $table, $field) + { + $cfgRelation = $this->relation->getRelationsParam(); + if (!$cfgRelation['displaywork']) { + return false; + } + + $upd_query = new Table($table, $db, $GLOBALS['dbi']); + $upd_query->updateDisplayField($field, $cfgRelation); + + return true; + } + + /** + * Adds a new foreign relation + * + * @param string $db database name + * @param string $T1 foreign table + * @param string $F1 foreign field + * @param string $T2 master table + * @param string $F2 master field + * @param string $on_delete on delete action + * @param string $on_update on update action + * @param string $DB1 database + * @param string $DB2 database + * + * @return array array of success/failure and message + */ + public function addNewRelation($db, $T1, $F1, $T2, $F2, $on_delete, $on_update, $DB1, $DB2) + { + $tables = $GLOBALS['dbi']->getTablesFull($DB1, $T1); + $type_T1 = mb_strtoupper($tables[$T1]['ENGINE']); + $tables = $GLOBALS['dbi']->getTablesFull($DB2, $T2); + $type_T2 = mb_strtoupper($tables[$T2]['ENGINE']); + + // native foreign key + if (Util::isForeignKeySupported($type_T1) + && Util::isForeignKeySupported($type_T2) + && $type_T1 == $type_T2 + ) { + // relation exists? + $existrel_foreign = $this->relation->getForeigners($DB2, $T2, '', 'foreign'); + $foreigner = $this->relation->searchColumnInForeigners($existrel_foreign, $F2); + if ($foreigner + && isset($foreigner['constraint']) + ) { + return array(false, __('Error: relationship already exists.')); + } + // note: in InnoDB, the index does not requires to be on a PRIMARY + // or UNIQUE key + // improve: check all other requirements for InnoDB relations + $result = $GLOBALS['dbi']->query( + 'SHOW INDEX FROM ' . Util::backquote($DB1) + . '.' . Util::backquote($T1) . ';' + ); + + // will be use to emphasis prim. keys in the table view + $index_array1 = array(); + while ($row = $GLOBALS['dbi']->fetchAssoc($result)) { + $index_array1[$row['Column_name']] = 1; + } + $GLOBALS['dbi']->freeResult($result); + + $result = $GLOBALS['dbi']->query( + 'SHOW INDEX FROM ' . Util::backquote($DB2) + . '.' . Util::backquote($T2) . ';' + ); + // will be used to emphasis prim. keys in the table view + $index_array2 = array(); + while ($row = $GLOBALS['dbi']->fetchAssoc($result)) { + $index_array2[$row['Column_name']] = 1; + } + $GLOBALS['dbi']->freeResult($result); + + if (! empty($index_array1[$F1]) && ! empty($index_array2[$F2])) { + $upd_query = 'ALTER TABLE ' . Util::backquote($DB2) + . '.' . Util::backquote($T2) + . ' ADD FOREIGN KEY (' + . Util::backquote($F2) . ')' + . ' REFERENCES ' + . Util::backquote($DB1) . '.' + . Util::backquote($T1) . '(' + . Util::backquote($F1) . ')'; + + if ($on_delete != 'nix') { + $upd_query .= ' ON DELETE ' . $on_delete; + } + if ($on_update != 'nix') { + $upd_query .= ' ON UPDATE ' . $on_update; + } + $upd_query .= ';'; + if ($GLOBALS['dbi']->tryQuery($upd_query)) { + return array(true, __('FOREIGN KEY relationship has been added.')); + } + + $error = $GLOBALS['dbi']->getError(); + return array( + false, + __('Error: FOREIGN KEY relationship could not be added!') + . "
      " . $error + ); + } + + return array(false, __('Error: Missing index on column(s).')); + } + + // internal (pmadb) relation + if ($GLOBALS['cfgRelation']['relwork'] == false) { + return array(false, __('Error: Relational features are disabled!')); + } + + // no need to recheck if the keys are primary or unique at this point, + // this was checked on the interface part + + $q = "INSERT INTO " + . Util::backquote($GLOBALS['cfgRelation']['db']) + . "." + . Util::backquote($GLOBALS['cfgRelation']['relation']) + . "(master_db, master_table, master_field, " + . "foreign_db, foreign_table, foreign_field)" + . " values(" + . "'" . $GLOBALS['dbi']->escapeString($DB2) . "', " + . "'" . $GLOBALS['dbi']->escapeString($T2) . "', " + . "'" . $GLOBALS['dbi']->escapeString($F2) . "', " + . "'" . $GLOBALS['dbi']->escapeString($DB1) . "', " + . "'" . $GLOBALS['dbi']->escapeString($T1) . "', " + . "'" . $GLOBALS['dbi']->escapeString($F1) . "')"; + + if ($this->relation->queryAsControlUser($q, false, DatabaseInterface::QUERY_STORE) + ) { + return array(true, __('Internal relationship has been added.')); + } + + $error = $GLOBALS['dbi']->getError(DatabaseInterface::CONNECT_CONTROL); + return array( + false, + __('Error: Internal relationship could not be added!') + . "
      " . $error + ); + } + + /** + * Removes a foreign relation + * + * @param string $T1 foreign db.table + * @param string $F1 foreign field + * @param string $T2 master db.table + * @param string $F2 master field + * + * @return array array of success/failure and message + */ + public function removeRelation($T1, $F1, $T2, $F2) + { + list($DB1, $T1) = explode(".", $T1); + list($DB2, $T2) = explode(".", $T2); + + $tables = $GLOBALS['dbi']->getTablesFull($DB1, $T1); + $type_T1 = mb_strtoupper($tables[$T1]['ENGINE']); + $tables = $GLOBALS['dbi']->getTablesFull($DB2, $T2); + $type_T2 = mb_strtoupper($tables[$T2]['ENGINE']); + + if (Util::isForeignKeySupported($type_T1) + && Util::isForeignKeySupported($type_T2) + && $type_T1 == $type_T2 + ) { + // InnoDB + $existrel_foreign = $this->relation->getForeigners($DB2, $T2, '', 'foreign'); + $foreigner = $this->relation->searchColumnInForeigners($existrel_foreign, $F2); + + if (isset($foreigner['constraint'])) { + $upd_query = 'ALTER TABLE ' . Util::backquote($DB2) + . '.' . Util::backquote($T2) . ' DROP FOREIGN KEY ' + . Util::backquote($foreigner['constraint']) . ';'; + if ($GLOBALS['dbi']->query($upd_query)) { + return array(true, __('FOREIGN KEY relationship has been removed.')); + } + + $error = $GLOBALS['dbi']->getError(); + return array( + false, + __('Error: FOREIGN KEY relationship could not be removed!') + . "
      " . $error + ); + } + } + + // internal relations + $delete_query = "DELETE FROM " + . Util::backquote($GLOBALS['cfgRelation']['db']) . "." + . $GLOBALS['cfgRelation']['relation'] . " WHERE " + . "master_db = '" . $GLOBALS['dbi']->escapeString($DB2) . "'" + . " AND master_table = '" . $GLOBALS['dbi']->escapeString($T2) . "'" + . " AND master_field = '" . $GLOBALS['dbi']->escapeString($F2) . "'" + . " AND foreign_db = '" . $GLOBALS['dbi']->escapeString($DB1) . "'" + . " AND foreign_table = '" . $GLOBALS['dbi']->escapeString($T1) . "'" + . " AND foreign_field = '" . $GLOBALS['dbi']->escapeString($F1) . "'"; + + $result = $this->relation->queryAsControlUser( + $delete_query, + false, + DatabaseInterface::QUERY_STORE + ); + + if (!$result) { + $error = $GLOBALS['dbi']->getError(DatabaseInterface::CONNECT_CONTROL); + return array( + false, + __('Error: Internal relationship could not be removed!') . "
      " . $error + ); + } + + return array(true, __('Internal relationship has been removed.')); + } + + /** + * Save value for a designer setting + * + * @param string $index setting + * @param string $value value + * + * @return bool whether the operation succeeded + */ + public function saveSetting($index, $value) + { + $cfgRelation = $this->relation->getRelationsParam(); + $success = true; + if ($GLOBALS['cfgRelation']['designersettingswork']) { + + $cfgDesigner = array( + 'user' => $GLOBALS['cfg']['Server']['user'], + 'db' => $cfgRelation['db'], + 'table' => $cfgRelation['designer_settings'] + ); + + $orig_data_query = "SELECT settings_data" + . " FROM " . Util::backquote($cfgDesigner['db']) + . "." . Util::backquote($cfgDesigner['table']) + . " WHERE username = '" + . $GLOBALS['dbi']->escapeString($cfgDesigner['user']) . "';"; + + $orig_data = $GLOBALS['dbi']->fetchSingleRow( + $orig_data_query, 'ASSOC', DatabaseInterface::CONNECT_CONTROL + ); + + if (! empty($orig_data)) { + $orig_data = json_decode($orig_data['settings_data'], true); + $orig_data[$index] = $value; + $orig_data = json_encode($orig_data); + + $save_query = "UPDATE " + . Util::backquote($cfgDesigner['db']) + . "." . Util::backquote($cfgDesigner['table']) + . " SET settings_data = '" . $orig_data . "'" + . " WHERE username = '" + . $GLOBALS['dbi']->escapeString($cfgDesigner['user']) . "';"; + + $success = $this->relation->queryAsControlUser($save_query); + } else { + $save_data = array($index => $value); + + $query = "INSERT INTO " + . Util::backquote($cfgDesigner['db']) + . "." . Util::backquote($cfgDesigner['table']) + . " (username, settings_data)" + . " VALUES('" . $GLOBALS['dbi']->escapeString($cfgDesigner['user']) + . "', '" . json_encode($save_data) . "');"; + + $success = $this->relation->queryAsControlUser($query); + } + } + + return (bool) $success; + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Database/Designer/DesignerTable.php b/php/apps/phpmyadmin49/html/libraries/classes/Database/Designer/DesignerTable.php new file mode 100644 index 00000000..bfdd48f0 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Database/Designer/DesignerTable.php @@ -0,0 +1,88 @@ +databaseName = $databaseName; + $this->tableName = $tableName; + $this->tableEngine = $tableEngine; + $this->displayField = $displayField; + } + + /** + * The table engine supports or not foreign keys + * + * @return bool + */ + public function supportsForeignkeys() { + return Util::isForeignKeySupported($this->tableEngine); + } + + /** + * Get the database name + * + * @return string + */ + public function getDatabaseName() { + return $this->databaseName; + } + + /** + * Get the table name + * + * @return string + */ + public function getTableName() { + return $this->tableName; + } + + /** + * Get the table engine + * + * @return string + */ + public function getTableEngine() { + return $this->tableEngine; + } + + /** + * Get the db and table speparated with a dot + * + * @return string + */ + public function getDbTableString() { + return $this->databaseName . '.' . $this->tableName; + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Database/MultiTableQuery.php b/php/apps/phpmyadmin49/html/libraries/classes/Database/MultiTableQuery.php new file mode 100644 index 00000000..a1b1f745 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Database/MultiTableQuery.php @@ -0,0 +1,135 @@ +dbi = $dbi; + $this->db = $dbName; + $this->defaultNoOfColumns = $defaultNoOfColumns; + + $this->tables = $this->dbi->getTables($this->db); + } + + /** + * Get Multi-Table query page HTML + * + * @return string Multi-Table query page HTML + */ + public function getFormHtml() + { + $tables = []; + foreach($this->tables as $table) { + $tables[$table]['hash'] = md5($table); + $tables[$table]['columns'] = array_keys( + $this->dbi->getColumns($this->db, $table) + ); + } + return Template::get('database/multi_table_query/form')->render([ + 'db' => $this->db, + 'tables' => $tables, + 'default_no_of_columns' => $this->defaultNoOfColumns, + ]); + } + + /** + * Displays multi-table query results + * + * @param string $sqlQuery The query to parse + * @param string $db The current database + * @param string $pmaThemeImage Uri of the PMA theme image + * + * @return void + */ + public static function displayResults($sqlQuery, $db, $pmaThemeImage) + { + list( + $analyzedSqlResults, + $db, + $tableFromSql + ) = ParseAnalyze::sqlQuery($sqlQuery, $db); + + extract($analyzedSqlResults); + $goto = 'db_multi_table_query.php'; + $sql = new Sql(); + $sql->executeQueryAndSendQueryResponse( + null, // analyzed_sql_results + false, // is_gotofile + $db, // db + null, // table + null, // find_real_end + null, // sql_query_for_bookmark - see below + null, // extra_data + null, // message_to_show + null, // message + null, // sql_data + $goto, // goto + $pmaThemeImage, // pmaThemeImage + null, // disp_query + null, // disp_message + null, // query_type + $sqlQuery, // sql_query + null, // selectedTables + null // complete_query + ); + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Database/Qbe.php b/php/apps/phpmyadmin49/html/libraries/classes/Database/Qbe.php new file mode 100644 index 00000000..9f7bd2a9 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Database/Qbe.php @@ -0,0 +1,1961 @@ +_db = $dbname; + $this->_savedSearchList = $savedSearchList; + $this->_currentSearch = $currentSearch; + $this->_loadCriterias(); + // Sets criteria parameters + $this->_setSearchParams(); + $this->_setCriteriaTablesAndColumns(); + $this->relation = new Relation(); + } + + /** + * Initialize criterias + * + * @return static + */ + private function _loadCriterias() + { + if (null === $this->_currentSearch + || null === $this->_currentSearch->getCriterias() + ) { + return $this; + } + + $criterias = $this->_currentSearch->getCriterias(); + $_POST = $criterias + $_POST; + + return $this; + } + + /** + * Getter for current search + * + * @return SavedSearches + */ + private function _getCurrentSearch() + { + return $this->_currentSearch; + } + + /** + * Sets search parameters + * + * @return void + */ + private function _setSearchParams() + { + $criteriaColumnCount = $this->_initializeCriteriasCount(); + + $this->_criteriaColumnInsert = Core::ifSetOr( + $_POST['criteriaColumnInsert'], + null, + 'array' + ); + $this->_criteriaColumnDelete = Core::ifSetOr( + $_POST['criteriaColumnDelete'], + null, + 'array' + ); + + $this->_prev_criteria = isset($_POST['prev_criteria']) + ? $_POST['prev_criteria'] + : array(); + $this->_criteria = isset($_POST['criteria']) + ? $_POST['criteria'] + : array_fill(0, $criteriaColumnCount, ''); + + $this->_criteriaRowInsert = isset($_POST['criteriaRowInsert']) + ? $_POST['criteriaRowInsert'] + : array_fill(0, $criteriaColumnCount, ''); + $this->_criteriaRowDelete = isset($_POST['criteriaRowDelete']) + ? $_POST['criteriaRowDelete'] + : array_fill(0, $criteriaColumnCount, ''); + $this->_criteriaAndOrRow = isset($_POST['criteriaAndOrRow']) + ? $_POST['criteriaAndOrRow'] + : array_fill(0, $criteriaColumnCount, ''); + $this->_criteriaAndOrColumn = isset($_POST['criteriaAndOrColumn']) + ? $_POST['criteriaAndOrColumn'] + : array_fill(0, $criteriaColumnCount, ''); + // sets minimum width + $this->_form_column_width = 12; + $this->_formColumns = array(); + $this->_formSorts = array(); + $this->_formShows = array(); + $this->_formCriterions = array(); + $this->_formAndOrRows = array(); + $this->_formAndOrCols = array(); + } + + /** + * Sets criteria tables and columns + * + * @return void + */ + private function _setCriteriaTablesAndColumns() + { + // The tables list sent by a previously submitted form + if (Core::isValid($_POST['TableList'], 'array')) { + foreach ($_POST['TableList'] as $each_table) { + $this->_criteriaTables[$each_table] = ' selected="selected"'; + } + } // end if + $all_tables = $GLOBALS['dbi']->query( + 'SHOW TABLES FROM ' . Util::backquote($this->_db) . ';', + DatabaseInterface::CONNECT_USER, + DatabaseInterface::QUERY_STORE + ); + $all_tables_count = $GLOBALS['dbi']->numRows($all_tables); + if (0 == $all_tables_count) { + Message::error(__('No tables found in database.'))->display(); + exit; + } + // The tables list gets from MySQL + while (list($table) = $GLOBALS['dbi']->fetchRow($all_tables)) { + $columns = $GLOBALS['dbi']->getColumns($this->_db, $table); + + if (empty($this->_criteriaTables[$table]) + && ! empty($_POST['TableList']) + ) { + $this->_criteriaTables[$table] = ''; + } else { + $this->_criteriaTables[$table] = ' selected="selected"'; + } // end if + + // The fields list per selected tables + if ($this->_criteriaTables[$table] == ' selected="selected"') { + $each_table = Util::backquote($table); + $this->_columnNames[] = $each_table . '.*'; + foreach ($columns as $each_column) { + $each_column = $each_table . '.' + . Util::backquote($each_column['Field']); + $this->_columnNames[] = $each_column; + // increase the width if necessary + $this->_form_column_width = max( + mb_strlen($each_column), + $this->_form_column_width + ); + } // end foreach + } // end if + } // end while + $GLOBALS['dbi']->freeResult($all_tables); + + // sets the largest width found + $this->_realwidth = $this->_form_column_width . 'ex'; + } + /** + * Provides select options list containing column names + * + * @param integer $column_number Column Number (0,1,2) or more + * @param string $selected Selected criteria column name + * + * @return string HTML for select options + */ + private function _showColumnSelectCell($column_number, $selected = '') + { + return Template::get('database/qbe/column_select_cell')->render([ + 'column_number' => $column_number, + 'column_names' => $this->_columnNames, + 'selected' => $selected, + ]); + } + + /** + * Provides select options list containing sort options (ASC/DESC) + * + * @param integer $columnNumber Column Number (0,1,2) or more + * @param string $selected Selected criteria 'ASC' or 'DESC' + * + * @return string HTML for select options + */ + private function _getSortSelectCell( + $columnNumber, + $selected = '' + ) { + return Template::get('database/qbe/sort_select_cell')->render([ + 'real_width' => $this->_realwidth, + 'column_number' => $columnNumber, + 'selected' => $selected, + ]); + } + + /** + * Provides select options list containing sort order + * + * @param integer $columnNumber Column Number (0,1,2) or more + * @param integer $sortOrder Sort order + * + * @return string HTML for select options + */ + private function _getSortOrderSelectCell($columnNumber, $sortOrder) + { + $totalColumnCount = $this->_getNewColumnCount(); + return Template::get('database/qbe/sort_order_select_cell')->render([ + 'total_column_count' => $totalColumnCount, + 'column_number' => $columnNumber, + 'sort_order' => $sortOrder, + ]); + } + + /** + * Returns the new column count after adding and removing columns as instructed + * + * @return int new column count + */ + private function _getNewColumnCount() + { + $totalColumnCount = $this->_criteria_column_count; + if (! empty($this->_criteriaColumnInsert)) { + $totalColumnCount += count($this->_criteriaColumnInsert); + } + if (! empty($this->_criteriaColumnDelete)) { + $totalColumnCount -= count($this->_criteriaColumnDelete); + } + return $totalColumnCount; + } + + /** + * Provides search form's row containing column select options + * + * @return string HTML for search table's row + */ + private function _getColumnNamesRow() + { + $html_output = ''; + $html_output .= '' . __('Column:') . ''; + $new_column_count = 0; + for ( + $column_index = 0; + $column_index < $this->_criteria_column_count; + $column_index++ + ) { + if (isset($this->_criteriaColumnInsert[$column_index]) + && $this->_criteriaColumnInsert[$column_index] == 'on' + ) { + $html_output .= $this->_showColumnSelectCell( + $new_column_count + ); + $new_column_count++; + } + if (! empty($this->_criteriaColumnDelete) + && isset($this->_criteriaColumnDelete[$column_index]) + && $this->_criteriaColumnDelete[$column_index] == 'on' + ) { + continue; + } + $selected = ''; + if (isset($_POST['criteriaColumn'][$column_index])) { + $selected = $_POST['criteriaColumn'][$column_index]; + $this->_formColumns[$new_column_count] + = $_POST['criteriaColumn'][$column_index]; + } + $html_output .= $this->_showColumnSelectCell( + $new_column_count, + $selected + ); + $new_column_count++; + } // end for + $this->_new_column_count = $new_column_count; + $html_output .= ''; + return $html_output; + } + + /** + * Provides search form's row containing column aliases + * + * @return string HTML for search table's row + */ + private function _getColumnAliasRow() + { + $html_output = ''; + $html_output .= '' . __('Alias:') . ''; + $new_column_count = 0; + + for ( + $colInd = 0; + $colInd < $this->_criteria_column_count; + $colInd++ + ) { + if (! empty($this->_criteriaColumnInsert) + && isset($this->_criteriaColumnInsert[$colInd]) + && $this->_criteriaColumnInsert[$colInd] == 'on' + ) { + $html_output .= ''; + $html_output .= ''; + $html_output .= ''; + $new_column_count++; + } // end if + + if (! empty($this->_criteriaColumnDelete) + && isset($this->_criteriaColumnDelete[$colInd]) + && $this->_criteriaColumnDelete[$colInd] == 'on' + ) { + continue; + } + + $tmp_alias = ''; + if (! empty($_POST['criteriaAlias'][$colInd])) { + $tmp_alias + = $this->_formAliases[$new_column_count] + = $_POST['criteriaAlias'][$colInd]; + }// end if + + $html_output .= ''; + $html_output .= ''; + $html_output .= ''; + $new_column_count++; + } // end for + $html_output .= ''; + return $html_output; + } + + /** + * Provides search form's row containing sort(ASC/DESC) select options + * + * @return string HTML for search table's row + */ + private function _getSortRow() + { + $html_output = ''; + $html_output .= '' . __('Sort:') . ''; + $new_column_count = 0; + + for ( + $colInd = 0; + $colInd < $this->_criteria_column_count; + $colInd++ + ) { + if (! empty($this->_criteriaColumnInsert) + && isset($this->_criteriaColumnInsert[$colInd]) + && $this->_criteriaColumnInsert[$colInd] == 'on' + ) { + $html_output .= $this->_getSortSelectCell($new_column_count); + $new_column_count++; + } // end if + + if (! empty($this->_criteriaColumnDelete) + && isset($this->_criteriaColumnDelete[$colInd]) + && $this->_criteriaColumnDelete[$colInd] == 'on' + ) { + continue; + } + // If they have chosen all fields using the * selector, + // then sorting is not available, Fix for Bug #570698 + if (isset($_POST['criteriaSort'][$colInd]) + && isset($_POST['criteriaColumn'][$colInd]) + && mb_substr($_POST['criteriaColumn'][$colInd], -2) == '.*' + ) { + $_POST['criteriaSort'][$colInd] = ''; + } //end if + + $selected = ''; + if (isset($_POST['criteriaSort'][$colInd])) { + $this->_formSorts[$new_column_count] + = $_POST['criteriaSort'][$colInd]; + + if ($_POST['criteriaSort'][$colInd] == 'ASC') { + $selected = 'ASC'; + } elseif ($_POST['criteriaSort'][$colInd] == 'DESC') { + $selected = 'DESC'; + } + } else { + $this->_formSorts[$new_column_count] = ''; + } + + $html_output .= $this->_getSortSelectCell( + $new_column_count, $selected + ); + $new_column_count++; + } // end for + $html_output .= ''; + return $html_output; + } + + /** + * Provides search form's row containing sort order + * + * @return string HTML for search table's row + */ + private function _getSortOrder() + { + $html_output = ''; + $html_output .= '' . __('Sort order:') . ''; + $new_column_count = 0; + + for ( + $colInd = 0; + $colInd < $this->_criteria_column_count; + $colInd++ + ) { + if (! empty($this->_criteriaColumnInsert) + && isset($this->_criteriaColumnInsert[$colInd]) + && $this->_criteriaColumnInsert[$colInd] == 'on' + ) { + $html_output .= $this->_getSortOrderSelectCell( + $new_column_count, null + ); + $new_column_count++; + } // end if + + if (! empty($this->_criteriaColumnDelete) + && isset($this->_criteriaColumnDelete[$colInd]) + && $this->_criteriaColumnDelete[$colInd] == 'on' + ) { + continue; + } + + $sortOrder = null; + if (! empty($_POST['criteriaSortOrder'][$colInd])) { + $sortOrder + = $this->_formSortOrders[$new_column_count] + = $_POST['criteriaSortOrder'][$colInd]; + } + + $html_output .= $this->_getSortOrderSelectCell( + $new_column_count, $sortOrder + ); + $new_column_count++; + } // end for + $html_output .= ''; + return $html_output; + } + + /** + * Provides search form's row containing SHOW checkboxes + * + * @return string HTML for search table's row + */ + private function _getShowRow() + { + $html_output = ''; + $html_output .= '' . __('Show:') . ''; + $new_column_count = 0; + for ( + $column_index = 0; + $column_index < $this->_criteria_column_count; + $column_index++ + ) { + if (! empty($this->_criteriaColumnInsert) + && isset($this->_criteriaColumnInsert[$column_index]) + && $this->_criteriaColumnInsert[$column_index] == 'on' + ) { + $html_output .= ''; + $html_output .= ''; + $html_output .= ''; + $new_column_count++; + } // end if + if (! empty($this->_criteriaColumnDelete) + && isset($this->_criteriaColumnDelete[$column_index]) + && $this->_criteriaColumnDelete[$column_index] == 'on' + ) { + continue; + } + if (isset($_POST['criteriaShow'][$column_index])) { + $checked_options = ' checked="checked"'; + $this->_formShows[$new_column_count] + = $_POST['criteriaShow'][$column_index]; + } else { + $checked_options = ''; + } + $html_output .= ''; + $html_output .= ''; + $html_output .= ''; + $new_column_count++; + } // end for + $html_output .= ''; + return $html_output; + } + + /** + * Provides search form's row containing criteria Inputboxes + * + * @return string HTML for search table's row + */ + private function _getCriteriaInputboxRow() + { + $html_output = ''; + $html_output .= '' . __('Criteria:') . ''; + $new_column_count = 0; + for ( + $column_index = 0; + $column_index < $this->_criteria_column_count; + $column_index++ + ) { + if (! empty($this->_criteriaColumnInsert) + && isset($this->_criteriaColumnInsert[$column_index]) + && $this->_criteriaColumnInsert[$column_index] == 'on' + ) { + $html_output .= ''; + $html_output .= ''; + $html_output .= ''; + $new_column_count++; + } // end if + if (! empty($this->_criteriaColumnDelete) + && isset($this->_criteriaColumnDelete[$column_index]) + && $this->_criteriaColumnDelete[$column_index] == 'on' + ) { + continue; + } + if (isset($this->_criteria[$column_index])) { + $tmp_criteria = $this->_criteria[$column_index]; + } + if ((empty($this->_prev_criteria) + || ! isset($this->_prev_criteria[$column_index])) + || $this->_prev_criteria[$column_index] != htmlspecialchars($tmp_criteria) + ) { + $this->_formCriterions[$new_column_count] = $tmp_criteria; + } else { + $this->_formCriterions[$new_column_count] + = $this->_prev_criteria[$column_index]; + } + $html_output .= ''; + $html_output .= ''; + $html_output .= ''; + $html_output .= ''; + $new_column_count++; + } // end for + $html_output .= ''; + return $html_output; + } + + /** + * Provides footer options for adding/deleting row/columns + * + * @param string $type Whether row or column + * + * @return string HTML for footer options + */ + private function _getFootersOptions($type) + { + return Template::get('database/qbe/footer_options')->render([ + 'type' => $type, + ]); + } + + /** + * Provides search form table's footer options + * + * @return string HTML for table footer + */ + private function _getTableFooters() + { + $html_output = '
      '; + $html_output .= $this->_getFootersOptions("row"); + $html_output .= $this->_getFootersOptions("column"); + $html_output .= '
      '; + $html_output .= ''; + $html_output .= '
      '; + $html_output .= '
      '; + return $html_output; + } + + /** + * Provides a select list of database tables + * + * @return string HTML for table select list + */ + private function _getTablesList() + { + $html_output = '
      '; + $html_output .= '
      '; + $html_output .= '' . __('Use Tables') . ''; + // Build the options list for each table name + $options = ''; + $numTableListOptions = 0; + foreach ($this->_criteriaTables as $key => $val) { + $options .= ''; + $numTableListOptions++; + } + $html_output .= ''; + $html_output .= '
      '; + $html_output .= '
      '; + $html_output .= ''; + $html_output .= '
      '; + $html_output .= '
      '; + return $html_output; + } + + /** + * Provides And/Or modification cell along with Insert/Delete options + * (For modifying search form's table columns) + * + * @param integer $column_number Column Number (0,1,2) or more + * @param array|null $selected Selected criteria column name + * @param bool $last_column Whether this is the last column + * + * @return string HTML for modification cell + */ + private function _getAndOrColCell( + $column_number, $selected = null, $last_column = false + ) { + $html_output = ''; + if (! $last_column) { + $html_output .= '' . __('Or:') . ''; + $html_output .= ''; + $html_output .= '  ' . __('And:') . ''; + $html_output .= ''; + } + $html_output .= '
      ' . __('Ins'); + $html_output .= ''; + $html_output .= '  ' . __('Del'); + $html_output .= ''; + $html_output .= ''; + return $html_output; + } + + /** + * Provides search form's row containing column modifications options + * (For modifying search form's table columns) + * + * @return string HTML for search table's row + */ + private function _getModifyColumnsRow() + { + $html_output = ''; + $html_output .= '' . __('Modify:') . ''; + $new_column_count = 0; + for ( + $column_index = 0; + $column_index < $this->_criteria_column_count; + $column_index++ + ) { + if (! empty($this->_criteriaColumnInsert) + && isset($this->_criteriaColumnInsert[$column_index]) + && $this->_criteriaColumnInsert[$column_index] == 'on' + ) { + $html_output .= $this->_getAndOrColCell($new_column_count); + $new_column_count++; + } // end if + + if (! empty($this->_criteriaColumnDelete) + && isset($this->_criteriaColumnDelete[$column_index]) + && $this->_criteriaColumnDelete[$column_index] == 'on' + ) { + continue; + } + + if (isset($this->_criteriaAndOrColumn[$column_index])) { + $this->_formAndOrCols[$new_column_count] + = $this->_criteriaAndOrColumn[$column_index]; + } + $checked_options = array(); + if (isset($this->_criteriaAndOrColumn[$column_index]) + && $this->_criteriaAndOrColumn[$column_index] == 'or' + ) { + $checked_options['or'] = ' checked="checked"'; + $checked_options['and'] = ''; + } else { + $checked_options['and'] = ' checked="checked"'; + $checked_options['or'] = ''; + } + $html_output .= $this->_getAndOrColCell( + $new_column_count, + $checked_options, + ($column_index + 1 == $this->_criteria_column_count) + ); + $new_column_count++; + } // end for + $html_output .= ''; + return $html_output; + } + + /** + * Provides Insert/Delete options for criteria inputbox + * with AND/OR relationship modification options + * + * @param integer $row_index Number of criteria row + * @param array $checked_options If checked + * + * @return string HTML + */ + private function _getInsDelAndOrCell($row_index, array $checked_options) + { + $html_output = ''; + $html_output .= ''; + $html_output .= ''; + $html_output .= ''; + $html_output .= ''; + $html_output .= ''; + $html_output .= ''; + $html_output .= ''; + $html_output .= ''; + $html_output .= ''; + $html_output .= ''; + $html_output .= ''; + $html_output .= ''; + $html_output .= '
      '; + $html_output .= '' . __('Ins:') . ''; + $html_output .= ''; + $html_output .= ''; + $html_output .= '' . __('And:') . ''; + $html_output .= ''; + $html_output .= ''; + $html_output .= '
      '; + $html_output .= '' . __('Del:') . ''; + $html_output .= ''; + $html_output .= ''; + $html_output .= '' . __('Or:') . ''; + $html_output .= ''; + $html_output .= ''; + $html_output .= '
      '; + $html_output .= ''; + return $html_output; + } + + /** + * Provides rows for criteria inputbox Insert/Delete options + * with AND/OR relationship modification options + * + * @param integer $new_row_index New row index if rows are added/deleted + * + * @return string HTML table rows + */ + private function _getInputboxRow($new_row_index) + { + $html_output = ''; + $new_column_count = 0; + for ( + $column_index = 0; + $column_index < $this->_criteria_column_count; + $column_index++ + ) { + if (!empty($this->_criteriaColumnInsert) + && isset($this->_criteriaColumnInsert[$column_index]) + && $this->_criteriaColumnInsert[$column_index] == 'on' + ) { + $orFieldName = 'Or' . $new_row_index . '[' . $new_column_count . ']'; + $html_output .= ''; + $html_output .= ''; + $html_output .= ''; + $new_column_count++; + } // end if + if (!empty($this->_criteriaColumnDelete) + && isset($this->_criteriaColumnDelete[$column_index]) + && $this->_criteriaColumnDelete[$column_index] == 'on' + ) { + continue; + } + $or = 'Or' . $new_row_index; + if (! empty($_POST[$or]) && isset($_POST[$or][$column_index])) { + $tmp_or = $_POST[$or][$column_index]; + } else { + $tmp_or = ''; + } + $html_output .= ''; + $html_output .= ''; + $html_output .= ''; + if (!empty(${$or}) && isset(${$or}[$column_index])) { + $GLOBALS[${'cur' . $or}][$new_column_count] + = ${$or}[$column_index]; + } + $new_column_count++; + } // end for + return $html_output; + } + + /** + * Provides rows for criteria inputbox Insert/Delete options + * with AND/OR relationship modification options + * + * @return string HTML table rows + */ + private function _getInsDelAndOrCriteriaRows() + { + $html_output = ''; + $new_row_count = 0; + $checked_options = array(); + for ( + $row_index = 0; + $row_index <= $this->_criteria_row_count; + $row_index++ + ) { + if (isset($this->_criteriaRowInsert[$row_index]) + && $this->_criteriaRowInsert[$row_index] == 'on' + ) { + $checked_options['or'] = ' checked="checked"'; + $checked_options['and'] = ''; + $html_output .= ''; + $html_output .= $this->_getInsDelAndOrCell( + $new_row_count, $checked_options + ); + $html_output .= $this->_getInputboxRow( + $new_row_count + ); + $new_row_count++; + $html_output .= ''; + } // end if + if (isset($this->_criteriaRowDelete[$row_index]) + && $this->_criteriaRowDelete[$row_index] == 'on' + ) { + continue; + } + if (isset($this->_criteriaAndOrRow[$row_index])) { + $this->_formAndOrRows[$new_row_count] + = $this->_criteriaAndOrRow[$row_index]; + } + if (isset($this->_criteriaAndOrRow[$row_index]) + && $this->_criteriaAndOrRow[$row_index] == 'and' + ) { + $checked_options['and'] = ' checked="checked"'; + $checked_options['or'] = ''; + } else { + $checked_options['or'] = ' checked="checked"'; + $checked_options['and'] = ''; + } + $html_output .= ''; + $html_output .= $this->_getInsDelAndOrCell( + $new_row_count, $checked_options + ); + $html_output .= $this->_getInputboxRow( + $new_row_count + ); + $new_row_count++; + $html_output .= ''; + } // end for + $this->_new_row_count = $new_row_count; + return $html_output; + } + + /** + * Provides SELECT clause for building SQL query + * + * @return string Select clause + */ + private function _getSelectClause() + { + $select_clause = ''; + $select_clauses = array(); + for ( + $column_index = 0; + $column_index < $this->_criteria_column_count; + $column_index++ + ) { + if (! empty($this->_formColumns[$column_index]) + && isset($this->_formShows[$column_index]) + && $this->_formShows[$column_index] == 'on' + ) { + $select = $this->_formColumns[$column_index]; + if (! empty($this->_formAliases[$column_index])) { + $select .= " AS " + . Util::backquote($this->_formAliases[$column_index]); + } + $select_clauses[] = $select; + } + } // end for + if (!empty($select_clauses)) { + $select_clause = 'SELECT ' + . htmlspecialchars(implode(", ", $select_clauses)) . "\n"; + } + return $select_clause; + } + + /** + * Provides WHERE clause for building SQL query + * + * @return string Where clause + */ + private function _getWhereClause() + { + $where_clause = ''; + $criteria_cnt = 0; + for ( + $column_index = 0; + $column_index < $this->_criteria_column_count; + $column_index++ + ) { + if (! empty($this->_formColumns[$column_index]) + && ! empty($this->_formCriterions[$column_index]) + && $column_index + && isset($last_where) + && isset($this->_formAndOrCols) + ) { + $where_clause .= ' ' + . mb_strtoupper($this->_formAndOrCols[$last_where]) + . ' '; + } + if (! empty($this->_formColumns[$column_index]) + && ! empty($this->_formCriterions[$column_index]) + ) { + $where_clause .= '(' . $this->_formColumns[$column_index] . ' ' + . $this->_formCriterions[$column_index] . ')'; + $last_where = $column_index; + $criteria_cnt++; + } + } // end for + if ($criteria_cnt > 1) { + $where_clause = '(' . $where_clause . ')'; + } + // OR rows ${'cur' . $or}[$column_index] + if (! isset($this->_formAndOrRows)) { + $this->_formAndOrRows = array(); + } + for ( + $row_index = 0; + $row_index <= $this->_criteria_row_count; + $row_index++ + ) { + $criteria_cnt = 0; + $qry_orwhere = ''; + $last_orwhere = ''; + for ( + $column_index = 0; + $column_index < $this->_criteria_column_count; + $column_index++ + ) { + if (! empty($this->_formColumns[$column_index]) + && ! empty($_POST['Or' . $row_index][$column_index]) + && $column_index + ) { + $qry_orwhere .= ' ' + . mb_strtoupper( + $this->_formAndOrCols[$last_orwhere] + ) + . ' '; + } + if (! empty($this->_formColumns[$column_index]) + && ! empty($_POST['Or' . $row_index][$column_index]) + ) { + $qry_orwhere .= '(' . $this->_formColumns[$column_index] + . ' ' + . $_POST['Or' . $row_index][$column_index] + . ')'; + $last_orwhere = $column_index; + $criteria_cnt++; + } + } // end for + if ($criteria_cnt > 1) { + $qry_orwhere = '(' . $qry_orwhere . ')'; + } + if (! empty($qry_orwhere)) { + $where_clause .= "\n" + . mb_strtoupper( + isset($this->_formAndOrRows[$row_index]) + ? $this->_formAndOrRows[$row_index] . ' ' + : '' + ) + . $qry_orwhere; + } // end if + } // end for + + if (! empty($where_clause) && $where_clause != '()') { + $where_clause = 'WHERE ' . htmlspecialchars($where_clause) . "\n"; + } // end if + return $where_clause; + } + + /** + * Provides ORDER BY clause for building SQL query + * + * @return string Order By clause + */ + private function _getOrderByClause() + { + $orderby_clause = ''; + $orderby_clauses = array(); + + // Create copy of instance variables + $columns = $this->_formColumns; + $sort = $this->_formSorts; + $sortOrder = $this->_formSortOrders; + if (!empty($sortOrder) + && count($sortOrder) == count($sort) + && count($sortOrder) == count($columns) + ) { + // Sort all three arrays based on sort order + array_multisort($sortOrder, $sort, $columns); + } + + for ( + $column_index = 0; + $column_index < $this->_criteria_column_count; + $column_index++ + ) { + // if all columns are chosen with * selector, + // then sorting isn't available + // Fix for Bug #570698 + if (empty($columns[$column_index]) + && empty($sort[$column_index]) + ) { + continue; + } + + if (mb_substr($columns[$column_index], -2) == '.*') { + continue; + } + + if (! empty($sort[$column_index])) { + $orderby_clauses[] = $columns[$column_index] . ' ' + . $sort[$column_index]; + } + } // end for + if (!empty($orderby_clauses)) { + $orderby_clause = 'ORDER BY ' + . htmlspecialchars(implode(", ", $orderby_clauses)) . "\n"; + } + return $orderby_clause; + } + + /** + * Provides UNIQUE columns and INDEX columns present in criteria tables + * + * @param array $search_tables Tables involved in the search + * @param array $search_columns Columns involved in the search + * @param array $where_clause_columns Columns having criteria where clause + * + * @return array having UNIQUE and INDEX columns + */ + private function _getIndexes(array $search_tables, array $search_columns, + array $where_clause_columns + ) { + $unique_columns = array(); + $index_columns = array(); + + foreach ($search_tables as $table) { + $indexes = $GLOBALS['dbi']->getTableIndexes($this->_db, $table); + foreach ($indexes as $index) { + $column = $table . '.' . $index['Column_name']; + if (isset($search_columns[$column])) { + if ($index['Non_unique'] == 0) { + if (isset($where_clause_columns[$column])) { + $unique_columns[$column] = 'Y'; + } else { + $unique_columns[$column] = 'N'; + } + } else { + if (isset($where_clause_columns[$column])) { + $index_columns[$column] = 'Y'; + } else { + $index_columns[$column] = 'N'; + } + } + } + } // end while (each index of a table) + } // end while (each table) + + return array( + 'unique' => $unique_columns, + 'index' => $index_columns + ); + } + + /** + * Provides UNIQUE columns and INDEX columns present in criteria tables + * + * @param array $search_tables Tables involved in the search + * @param array $search_columns Columns involved in the search + * @param array $where_clause_columns Columns having criteria where clause + * + * @return array having UNIQUE and INDEX columns + */ + private function _getLeftJoinColumnCandidates(array $search_tables, array $search_columns, + array $where_clause_columns + ) { + $GLOBALS['dbi']->selectDb($this->_db); + + // Get unique columns and index columns + $indexes = $this->_getIndexes( + $search_tables, $search_columns, $where_clause_columns + ); + $unique_columns = $indexes['unique']; + $index_columns = $indexes['index']; + + list($candidate_columns, $needsort) + = $this->_getLeftJoinColumnCandidatesBest( + $search_tables, + $where_clause_columns, + $unique_columns, + $index_columns + ); + + // If we came up with $unique_columns (very good) or $index_columns (still + // good) as $candidate_columns we want to check if we have any 'Y' there + // (that would mean that they were also found in the whereclauses + // which would be great). if yes, we take only those + if ($needsort != 1) { + return $candidate_columns; + } + + $very_good = array(); + $still_good = array(); + foreach ($candidate_columns as $column => $is_where) { + $table = explode('.', $column); + $table = $table[0]; + if ($is_where == 'Y') { + $very_good[$column] = $table; + } else { + $still_good[$column] = $table; + } + } + if (count($very_good) > 0) { + $candidate_columns = $very_good; + // Candidates restricted in index+where + } else { + $candidate_columns = $still_good; + // None of the candidates where in a where-clause + } + + return $candidate_columns; + } + + /** + * Provides the main table to form the LEFT JOIN clause + * + * @param array $search_tables Tables involved in the search + * @param array $search_columns Columns involved in the search + * @param array $where_clause_columns Columns having criteria where clause + * @param array $where_clause_tables Tables having criteria where clause + * + * @return string table name + */ + private function _getMasterTable(array $search_tables, array $search_columns, + array $where_clause_columns, array $where_clause_tables + ) { + if (count($where_clause_tables) == 1) { + // If there is exactly one column that has a decent where-clause + // we will just use this + $master = key($where_clause_tables); + return $master; + } + + // Now let's find out which of the tables has an index + // (When the control user is the same as the normal user + // because he is using one of his databases as pmadb, + // the last db selected is not always the one where we need to work) + $candidate_columns = $this->_getLeftJoinColumnCandidates( + $search_tables, $search_columns, $where_clause_columns + ); + + // Generally, we need to display all the rows of foreign (referenced) + // table, whether they have any matching row in child table or not. + // So we select candidate tables which are foreign tables. + $foreign_tables = array(); + foreach ($candidate_columns as $one_table) { + $foreigners = $this->relation->getForeigners($this->_db, $one_table); + foreach ($foreigners as $key => $foreigner) { + if ($key != 'foreign_keys_data') { + if (in_array($foreigner['foreign_table'], $candidate_columns)) { + $foreign_tables[$foreigner['foreign_table']] + = $foreigner['foreign_table']; + } + continue; + } + foreach ($foreigner as $one_key) { + if (in_array($one_key['ref_table_name'], $candidate_columns)) { + $foreign_tables[$one_key['ref_table_name']] + = $one_key['ref_table_name']; + } + } + } + } + if (count($foreign_tables)) { + $candidate_columns = $foreign_tables; + } + + // If our array of candidates has more than one member we'll just + // find the smallest table. + // Of course the actual query would be faster if we check for + // the Criteria which gives the smallest result set in its table, + // but it would take too much time to check this + if (!(count($candidate_columns) > 1)) { + // Only one single candidate + return reset($candidate_columns); + } + + // Of course we only want to check each table once + $checked_tables = $candidate_columns; + $tsize = array(); + $maxsize = -1; + $result = ''; + foreach ($candidate_columns as $table) { + if ($checked_tables[$table] != 1) { + $_table = new Table($table, $this->_db); + $tsize[$table] = $_table->countRecords(); + $checked_tables[$table] = 1; + } + if ($tsize[$table] > $maxsize) { + $maxsize = $tsize[$table]; + $result = $table; + } + } + // Return largest table + return $result; + } + + /** + * Provides columns and tables that have valid where clause criteria + * + * @return array + */ + private function _getWhereClauseTablesAndColumns() + { + $where_clause_columns = array(); + $where_clause_tables = array(); + + // Now we need all tables that we have in the where clause + for ( + $column_index = 0, $nb = count($this->_criteria); + $column_index < $nb; + $column_index++ + ) { + $current_table = explode('.', $_POST['criteriaColumn'][$column_index]); + if (empty($current_table[0]) || empty($current_table[1])) { + continue; + } // end if + $table = str_replace('`', '', $current_table[0]); + $column = str_replace('`', '', $current_table[1]); + $column = $table . '.' . $column; + // Now we know that our array has the same numbers as $criteria + // we can check which of our columns has a where clause + if (! empty($this->_criteria[$column_index])) { + if (mb_substr($this->_criteria[$column_index], 0, 1) == '=' + || stristr($this->_criteria[$column_index], 'is') + ) { + $where_clause_columns[$column] = $column; + $where_clause_tables[$table] = $table; + } + } // end if + } // end for + return array( + 'where_clause_tables' => $where_clause_tables, + 'where_clause_columns' => $where_clause_columns + ); + } + + /** + * Provides FROM clause for building SQL query + * + * @param array $formColumns List of selected columns in the form + * + * @return string FROM clause + */ + private function _getFromClause(array $formColumns) + { + $from_clause = ''; + if (empty($formColumns)) { + return $from_clause; + } + + // Initialize some variables + $search_tables = $search_columns = array(); + + // We only start this if we have fields, otherwise it would be dumb + foreach ($formColumns as $value) { + $parts = explode('.', $value); + if (! empty($parts[0]) && ! empty($parts[1])) { + $table = str_replace('`', '', $parts[0]); + $search_tables[$table] = $table; + $search_columns[] = $table . '.' . str_replace( + '`', '', $parts[1] + ); + } + } // end while + + // Create LEFT JOINS out of Relations + $from_clause = $this->_getJoinForFromClause( + $search_tables, $search_columns + ); + + // In case relations are not defined, just generate the FROM clause + // from the list of tables, however we don't generate any JOIN + if (empty($from_clause)) { + // Create cartesian product + $from_clause = implode( + ", ", array_map(array('PhpMyAdmin\Util', 'backquote'), $search_tables) + ); + } + + return $from_clause; + } + + /** + * Formulates the WHERE clause by JOINing tables + * + * @param array $searchTables Tables involved in the search + * @param array $searchColumns Columns involved in the search + * + * @return string table name + */ + private function _getJoinForFromClause(array $searchTables, array $searchColumns) + { + // $relations[master_table][foreign_table] => clause + $relations = array(); + + // Fill $relations with inter table relationship data + foreach ($searchTables as $oneTable) { + $this->_loadRelationsForTable($relations, $oneTable); + } + + // Get tables and columns with valid where clauses + $validWhereClauses = $this->_getWhereClauseTablesAndColumns(); + $whereClauseTables = $validWhereClauses['where_clause_tables']; + $whereClauseColumns = $validWhereClauses['where_clause_columns']; + + // Get master table + $master = $this->_getMasterTable( + $searchTables, $searchColumns, + $whereClauseColumns, $whereClauseTables + ); + + // Will include master tables and all tables that can be combined into + // a cluster by their relation + $finalized = array(); + if (strlen($master) > 0) { + // Add master tables + $finalized[$master] = ''; + } + // Fill the $finalized array with JOIN clauses for each table + $this->_fillJoinClauses($finalized, $relations, $searchTables); + + // JOIN clause + $join = ''; + + // Tables that can not be combined with the table cluster + // which includes master table + $unfinalized = array_diff($searchTables, array_keys($finalized)); + if (count($unfinalized) > 0) { + + // We need to look for intermediary tables to JOIN unfinalized tables + // Heuristic to chose intermediary tables is to look for tables + // having relationships with unfinalized tables + foreach ($unfinalized as $oneTable) { + + $references = $this->relation->getChildReferences($this->_db, $oneTable); + foreach ($references as $column => $columnReferences) { + foreach ($columnReferences as $reference) { + + // Only from this schema + if ($reference['table_schema'] != $this->_db) { + continue; + } + + $table = $reference['table_name']; + + $this->_loadRelationsForTable($relations, $table); + + // Make copies + $tempFinalized = $finalized; + $tempSearchTables = $searchTables; + $tempSearchTables[] = $table; + + // Try joining with the added table + $this->_fillJoinClauses( + $tempFinalized, $relations, $tempSearchTables + ); + + $tempUnfinalized = array_diff( + $tempSearchTables, array_keys($tempFinalized) + ); + // Take greedy approach. + // If the unfinalized count drops we keep the new table + // and switch temporary varibles with the original ones + if (count($tempUnfinalized) < count($unfinalized)) { + $finalized = $tempFinalized; + $searchTables = $tempSearchTables; + } + + // We are done if no unfinalized tables anymore + if (count($tempUnfinalized) == 0) { + break 3; + } + } + } + } + + $unfinalized = array_diff($searchTables, array_keys($finalized)); + // If there are still unfinalized tables + if (count($unfinalized) > 0) { + // Add these tables as cartesian product before joined tables + $join .= implode( + ', ', array_map(array('PhpMyAdmin\Util', 'backquote'), $unfinalized) + ); + } + } + + $first = true; + // Add joined tables + foreach ($finalized as $table => $clause) { + if ($first) { + if (! empty($join)) { + $join .= ", "; + } + $join .= Util::backquote($table); + $first = false; + } else { + $join .= "\n LEFT JOIN " . Util::backquote( + $table + ) . " ON " . $clause; + } + } + + return $join; + } + + /** + * Loads relations for a given table into the $relations array + * + * @param array &$relations array of relations + * @param string $oneTable the table + * + * @return void + */ + private function _loadRelationsForTable(array &$relations, $oneTable) + { + $relations[$oneTable] = array(); + + $foreigners = $this->relation->getForeigners($GLOBALS['db'], $oneTable); + foreach ($foreigners as $field => $foreigner) { + // Foreign keys data + if ($field == 'foreign_keys_data') { + foreach ($foreigner as $oneKey) { + $clauses = array(); + // There may be multiple column relations + foreach ($oneKey['index_list'] as $index => $oneField) { + $clauses[] + = Util::backquote($oneTable) . "." + . Util::backquote($oneField) . " = " + . Util::backquote($oneKey['ref_table_name']) . "." + . Util::backquote($oneKey['ref_index_list'][$index]); + } + // Combine multiple column relations with AND + $relations[$oneTable][$oneKey['ref_table_name']] + = implode(" AND ", $clauses); + } + } else { // Internal relations + $relations[$oneTable][$foreigner['foreign_table']] + = Util::backquote($oneTable) . "." + . Util::backquote($field) . " = " + . Util::backquote($foreigner['foreign_table']) . "." + . Util::backquote($foreigner['foreign_field']); + } + } + } + + /** + * Fills the $finalized arrays with JOIN clauses for each of the tables + * + * @param array &$finalized JOIN clauses for each table + * @param array $relations Relations among tables + * @param array $searchTables Tables involved in the search + * + * @return void + */ + private function _fillJoinClauses(array &$finalized, array $relations, array $searchTables) + { + while (true) { + $added = false; + foreach ($searchTables as $masterTable) { + $foreignData = $relations[$masterTable]; + foreach ($foreignData as $foreignTable => $clause) { + if (! isset($finalized[$masterTable]) + && isset($finalized[$foreignTable]) + ) { + $finalized[$masterTable] = $clause; + $added = true; + } elseif (! isset($finalized[$foreignTable]) + && isset($finalized[$masterTable]) + && in_array($foreignTable, $searchTables) + ) { + $finalized[$foreignTable] = $clause; + $added = true; + } + if ($added) { + // We are done if all tables are in $finalized + if (count($finalized) == count($searchTables)) { + return; + } + } + } + } + // If no new tables were added during this iteration, break; + if (! $added) { + return; + } + } + } + + /** + * Provides the generated SQL query + * + * @param array $formColumns List of selected columns in the form + * + * @return string SQL query + */ + private function _getSQLQuery(array $formColumns) + { + $sql_query = ''; + // get SELECT clause + $sql_query .= $this->_getSelectClause(); + // get FROM clause + $from_clause = $this->_getFromClause($formColumns); + if (! empty($from_clause)) { + $sql_query .= 'FROM ' . htmlspecialchars($from_clause) . "\n"; + } + // get WHERE clause + $sql_query .= $this->_getWhereClause(); + // get ORDER BY clause + $sql_query .= $this->_getOrderByClause(); + return $sql_query; + } + + /** + * Provides the generated QBE form + * + * @return string QBE form + */ + public function getSelectionForm() + { + $html_output = '
      '; + $html_output .= '
      '; + $html_output .= '
      '; + + if ($GLOBALS['cfgRelation']['savedsearcheswork']) { + $html_output .= $this->_getSavedSearchesField(); + } + + $html_output .= '
      '; + $html_output .= ''; + // Get table's elements + $html_output .= $this->_getColumnNamesRow(); + $html_output .= $this->_getColumnAliasRow(); + $html_output .= $this->_getShowRow(); + $html_output .= $this->_getSortRow(); + $html_output .= $this->_getSortOrder(); + $html_output .= $this->_getCriteriaInputboxRow(); + $html_output .= $this->_getInsDelAndOrCriteriaRows(); + $html_output .= $this->_getModifyColumnsRow(); + $html_output .= '
      '; + $this->_new_row_count--; + $url_params = array(); + $url_params['db'] = $this->_db; + $url_params['criteriaColumnCount'] = $this->_new_column_count; + $url_params['rows'] = $this->_new_row_count; + $html_output .= Url::getHiddenInputs($url_params); + $html_output .= '
      '; + $html_output .= '
      '; + $html_output .= '
      '; + // get footers + $html_output .= $this->_getTableFooters(); + // get tables select list + $html_output .= $this->_getTablesList(); + $html_output .= '
      '; + $html_output .= '
      '; + $html_output .= Url::getHiddenInputs(array('db' => $this->_db)); + // get SQL query + $html_output .= '
      '; + $html_output .= '
      '; + $html_output .= '' + . sprintf( + __('SQL query on database %s:'), + Util::getDbLink($this->_db) + ); + $html_output .= ''; + $text_dir = 'ltr'; + $html_output .= ''; + $html_output .= '
      '; + // displays form's footers + $html_output .= '
      '; + $html_output .= ''; + $html_output .= ''; + $html_output .= '
      '; + $html_output .= '
      '; + $html_output .= '
      '; + return $html_output; + } + + /** + * Get fields to display + * + * @return string + */ + private function _getSavedSearchesField() + { + $html_output = __('Saved bookmarked search:'); + $html_output .= ' '; + $html_output .= ''; + $html_output .= ''; + $html_output .= ''; + if (null !== $currentSearchId) { + $html_output .= ''; + $html_output .= ''; + } + + return $html_output; + } + + /** + * Initialize _criteria_column_count + * + * @return int Previous number of columns + */ + private function _initializeCriteriasCount() + { + // sets column count + $criteriaColumnCount = Core::ifSetOr( + $_POST['criteriaColumnCount'], + 3, + 'numeric' + ); + $criteriaColumnAdd = Core::ifSetOr( + $_POST['criteriaColumnAdd'], + 0, + 'numeric' + ); + $this->_criteria_column_count = max( + $criteriaColumnCount + $criteriaColumnAdd, + 0 + ); + + // sets row count + $rows = Core::ifSetOr($_POST['rows'], 0, 'numeric'); + $criteriaRowAdd = Core::ifSetOr($_POST['criteriaRowAdd'], 0, 'numeric'); + $this->_criteria_row_count = min( + 100, + max($rows + $criteriaRowAdd, 0) + ); + + return $criteriaColumnCount; + } + + /** + * Get best + * + * @param array $search_tables Tables involved in the search + * @param array $where_clause_columns Columns with where clause + * @param array $unique_columns Unique columns + * @param array $index_columns Indexed columns + * + * @return array + */ + private function _getLeftJoinColumnCandidatesBest( + array $search_tables, array $where_clause_columns, array $unique_columns, array $index_columns + ) { + // now we want to find the best. + if (isset($unique_columns) && count($unique_columns) > 0) { + $candidate_columns = $unique_columns; + $needsort = 1; + return array($candidate_columns, $needsort); + } elseif (isset($index_columns) && count($index_columns) > 0) { + $candidate_columns = $index_columns; + $needsort = 1; + return array($candidate_columns, $needsort); + } elseif (isset($where_clause_columns) && count($where_clause_columns) > 0) { + $candidate_columns = $where_clause_columns; + $needsort = 0; + return array($candidate_columns, $needsort); + } + + $candidate_columns = $search_tables; + $needsort = 0; + return array($candidate_columns, $needsort); + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Database/Search.php b/php/apps/phpmyadmin49/html/libraries/classes/Database/Search.php new file mode 100644 index 00000000..f5923f9e --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Database/Search.php @@ -0,0 +1,339 @@ +db = $db; + $this->searchTypes = array( + '1' => __('at least one of the words'), + '2' => __('all of the words'), + '3' => __('the exact phrase as substring'), + '4' => __('the exact phrase as whole field'), + '5' => __('as regular expression'), + ); + // Sets criteria parameters + $this->setSearchParams(); + } + + /** + * Sets search parameters + * + * @return void + */ + private function setSearchParams() + { + $this->tablesNamesOnly = $GLOBALS['dbi']->getTables($this->db); + + if (empty($_POST['criteriaSearchType']) + || ! is_string($_POST['criteriaSearchType']) + || ! array_key_exists( + $_POST['criteriaSearchType'], + $this->searchTypes + ) + ) { + $this->criteriaSearchType = 1; + unset($_POST['submit_search']); + } else { + $this->criteriaSearchType = (int) $_POST['criteriaSearchType']; + $this->searchTypeDescription + = $this->searchTypes[$_POST['criteriaSearchType']]; + } + + if (empty($_POST['criteriaSearchString']) + || ! is_string($_POST['criteriaSearchString']) + ) { + $this->criteriaSearchString = ''; + unset($_POST['submit_search']); + } else { + $this->criteriaSearchString = $_POST['criteriaSearchString']; + } + + $this->criteriaTables = array(); + if (empty($_POST['criteriaTables']) + || ! is_array($_POST['criteriaTables']) + ) { + unset($_POST['submit_search']); + } else { + $this->criteriaTables = array_intersect( + $_POST['criteriaTables'], $this->tablesNamesOnly + ); + } + + if (empty($_POST['criteriaColumnName']) + || ! is_string($_POST['criteriaColumnName']) + ) { + unset($this->criteriaColumnName); + } else { + $this->criteriaColumnName = $GLOBALS['dbi']->escapeString( + $_POST['criteriaColumnName'] + ); + } + } + + /** + * Builds the SQL search query + * + * @param string $table The table name + * + * @return array 3 SQL queries (for count, display and delete results) + * + * @todo can we make use of fulltextsearch IN BOOLEAN MODE for this? + * PMA_backquote + * DatabaseInterface::freeResult + * DatabaseInterface::fetchAssoc + * $GLOBALS['db'] + * explode + * count + * strlen + */ + private function getSearchSqls($table) + { + // Statement types + $sqlstr_select = 'SELECT'; + $sqlstr_delete = 'DELETE'; + // Table to use + $sqlstr_from = ' FROM ' + . Util::backquote($GLOBALS['db']) . '.' + . Util::backquote($table); + // Gets where clause for the query + $where_clause = $this->getWhereClause($table); + // Builds complete queries + $sql = array(); + $sql['select_columns'] = $sqlstr_select . ' * ' . $sqlstr_from + . $where_clause; + // here, I think we need to still use the COUNT clause, even for + // VIEWs, anyway we have a WHERE clause that should limit results + $sql['select_count'] = $sqlstr_select . ' COUNT(*) AS `count`' + . $sqlstr_from . $where_clause; + $sql['delete'] = $sqlstr_delete . $sqlstr_from . $where_clause; + + return $sql; + } + + /** + * Provides where clause for building SQL query + * + * @param string $table The table name + * + * @return string The generated where clause + */ + private function getWhereClause($table) + { + // Columns to select + $allColumns = $GLOBALS['dbi']->getColumns($GLOBALS['db'], $table); + $likeClauses = array(); + // Based on search type, decide like/regex & '%'/'' + $like_or_regex = (($this->criteriaSearchType == 5) ? 'REGEXP' : 'LIKE'); + $automatic_wildcard = (($this->criteriaSearchType < 4) ? '%' : ''); + // For "as regular expression" (search option 5), LIKE won't be used + // Usage example: If user is searching for a literal $ in a regexp search, + // he should enter \$ as the value. + $criteriaSearchStringEscaped = $GLOBALS['dbi']->escapeString( + $this->criteriaSearchString + ); + // Extract search words or pattern + $search_words = (($this->criteriaSearchType > 2) + ? array($criteriaSearchStringEscaped) + : explode(' ', $criteriaSearchStringEscaped)); + + foreach ($search_words as $search_word) { + // Eliminates empty values + if (strlen($search_word) === 0) { + continue; + } + $likeClausesPerColumn = array(); + // for each column in the table + foreach ($allColumns as $column) { + if (! isset($this->criteriaColumnName) + || strlen($this->criteriaColumnName) === 0 + || $column['Field'] == $this->criteriaColumnName + ) { + $column = 'CONVERT(' . Util::backquote($column['Field']) + . ' USING utf8)'; + $likeClausesPerColumn[] = $column . ' ' . $like_or_regex . ' ' + . "'" + . $automatic_wildcard . $search_word . $automatic_wildcard + . "'"; + } + } // end for + if (count($likeClausesPerColumn) > 0) { + $likeClauses[] = implode(' OR ', $likeClausesPerColumn); + } + } // end for + // Use 'OR' if 'at least one word' is to be searched, else use 'AND' + $implode_str = ($this->criteriaSearchType == 1 ? ' OR ' : ' AND '); + if (empty($likeClauses)) { + // this could happen when the "inside column" does not exist + // in any selected tables + $where_clause = ' WHERE FALSE'; + } else { + $where_clause = ' WHERE (' + . implode(') ' . $implode_str . ' (', $likeClauses) + . ')'; + } + return $where_clause; + } + + /** + * Displays database search results + * + * @return string HTML for search results + */ + public function getSearchResults() + { + $resultTotal = 0; + $rows = []; + // For each table selected as search criteria + foreach ($this->criteriaTables as $eachTable) { + // Gets the SQL statements + $newSearchSqls = $this->getSearchSqls($eachTable); + // Executes the "COUNT" statement + $resultCount = intval($GLOBALS['dbi']->fetchValue( + $newSearchSqls['select_count'] + )); + $resultTotal += $resultCount; + // Gets the result row's HTML for a table + $rows[] = [ + 'table' => htmlspecialchars($eachTable), + 'new_search_sqls' => $newSearchSqls, + 'result_count' => $resultCount, + ]; + } + + return Template::get('database/search/results')->render([ + 'db' => $this->db, + 'rows' => $rows, + 'result_total' => $resultTotal, + 'criteria_tables' => $this->criteriaTables, + 'criteria_search_string' => htmlspecialchars($this->criteriaSearchString), + 'search_type_description' => $this->searchTypeDescription, + ]); + } + + /** + * Provides the main search form's html + * + * @return string HTML for selection form + */ + public function getSelectionForm() + { + $choices = array( + '1' => $this->searchTypes[1] . ' ' + . Util::showHint( + __('Words are separated by a space character (" ").') + ), + '2' => $this->searchTypes[2] . ' ' + . Util::showHint( + __('Words are separated by a space character (" ").') + ), + '3' => $this->searchTypes[3], + '4' => $this->searchTypes[4], + '5' => $this->searchTypes[5] . ' ' . Util::showMySQLDocu('Regexp') + ); + return Template::get('database/search/selection_form')->render([ + 'db' => $this->db, + 'choices' => $choices, + 'criteria_search_string' => $this->criteriaSearchString, + 'criteria_search_type' => $this->criteriaSearchType, + 'criteria_tables' => $this->criteriaTables, + 'tables_names_only' => $this->tablesNamesOnly, + 'criteria_column_name' => isset($this->criteriaColumnName) + ? $this->criteriaColumnName : null, + ]); + } + + /** + * Provides div tags for browsing search results and sql query form. + * + * @return string div tags + */ + public function getResultDivs() + { + return Template::get('database/search/result_divs')->render(); + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/DatabaseInterface.php b/php/apps/phpmyadmin49/html/libraries/classes/DatabaseInterface.php new file mode 100644 index 00000000..138b7982 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/DatabaseInterface.php @@ -0,0 +1,3095 @@ +_extension = $ext; + $this->_links = array(); + if (defined('TESTSUITE')) { + $this->_links[DatabaseInterface::CONNECT_USER] = 1; + $this->_links[DatabaseInterface::CONNECT_CONTROL] = 2; + } + $this->_table_cache = array(); + $this->_current_user = array(); + $this->types = new Types($this); + $this->relation = new Relation(); + } + + /** + * Checks whether database extension is loaded + * + * @param string $extension mysql extension to check + * + * @return bool + */ + public static function checkDbExtension($extension = 'mysql') + { + return function_exists($extension . '_connect'); + } + + /** + * runs a query + * + * @param string $query SQL query to execute + * @param mixed $link optional database link to use + * @param int $options optional query options + * @param bool $cache_affected_rows whether to cache affected rows + * + * @return mixed + */ + public function query($query, $link = DatabaseInterface::CONNECT_USER, $options = 0, + $cache_affected_rows = true + ) { + $res = $this->tryQuery($query, $link, $options, $cache_affected_rows) + or Util::mysqlDie($this->getError($link), $query); + return $res; + } + + /** + * Get a cached value from table cache. + * + * @param array $contentPath Array of the name of the target value + * @param mixed $default Return value on cache miss + * + * @return mixed cached value or default + */ + public function getCachedTableContent(array $contentPath, $default = null) + { + return Util::getValueByKey($this->_table_cache, $contentPath, $default); + } + + /** + * Set an item in table cache using dot notation. + * + * @param array $contentPath Array with the target path + * @param mixed $value Target value + * + * @return void + */ + public function cacheTableContent(array $contentPath, $value) + { + $loc = &$this->_table_cache; + + if (!isset($contentPath)) { + $loc = $value; + return; + } + + while (count($contentPath) > 1) { + $key = array_shift($contentPath); + + // If the key doesn't exist at this depth, we will just create an empty + // array to hold the next value, allowing us to create the arrays to hold + // final values at the correct depth. Then we'll keep digging into the + // array. + if (!isset($loc[$key]) || !is_array($loc[$key])) { + $loc[$key] = array(); + } + $loc = &$loc[$key]; + } + + $loc[array_shift($contentPath)] = $value; + } + + /** + * Clear the table cache. + * + * @return void + */ + public function clearTableCache() + { + $this->_table_cache = array(); + } + + /** + * Caches table data so Table does not require to issue + * SHOW TABLE STATUS again + * + * @param array $tables information for tables of some databases + * @param string $table table name + * + * @return void + */ + private function _cacheTableData(array $tables, $table) + { + // Note: I don't see why we would need array_merge_recursive() here, + // as it creates double entries for the same table (for example a double + // entry for Comment when changing the storage engine in Operations) + // Note 2: Instead of array_merge(), simply use the + operator because + // array_merge() renumbers numeric keys starting with 0, therefore + // we would lose a db name that consists only of numbers + + foreach ($tables as $one_database => $its_tables) { + if (isset($this->_table_cache[$one_database])) { + // the + operator does not do the intended effect + // when the cache for one table already exists + if ($table + && isset($this->_table_cache[$one_database][$table]) + ) { + unset($this->_table_cache[$one_database][$table]); + } + $this->_table_cache[$one_database] + = $this->_table_cache[$one_database] + $tables[$one_database]; + } else { + $this->_table_cache[$one_database] = $tables[$one_database]; + } + } + } + + /** + * Stores query data into session data for debugging purposes + * + * @param string $query Query text + * @param integer $link link type + * @param object|boolean $result Query result + * @param integer $time Time to execute query + * + * @return void + */ + private function _dbgQuery($query, $link, $result, $time) + { + $dbgInfo = array(); + $error_message = $this->getError($link); + if ($result == false && is_string($error_message)) { + $dbgInfo['error'] + = '' + . htmlspecialchars($error_message) . ''; + } + $dbgInfo['query'] = htmlspecialchars($query); + $dbgInfo['time'] = $time; + // Get and slightly format backtrace, this is used + // in the javascript console. + // Strip call to _dbgQuery + $dbgInfo['trace'] = Error::processBacktrace( + array_slice(debug_backtrace(), 1) + ); + $dbgInfo['hash'] = md5($query); + + $_SESSION['debug']['queries'][] = $dbgInfo; + } + + /** + * runs a query and returns the result + * + * @param string $query query to run + * @param integer $link link type + * @param integer $options query options + * @param bool $cache_affected_rows whether to cache affected row + * + * @return mixed + */ + public function tryQuery($query, $link = DatabaseInterface::CONNECT_USER, $options = 0, + $cache_affected_rows = true + ) { + $debug = $GLOBALS['cfg']['DBG']['sql']; + if (! isset($this->_links[$link])) { + return false; + } + + if ($debug) { + $time = microtime(true); + } + + $result = $this->_extension->realQuery($query, $this->_links[$link], $options); + + if ($cache_affected_rows) { + $GLOBALS['cached_affected_rows'] = $this->affectedRows($link, false); + } + + if ($debug) { + $time = microtime(true) - $time; + $this->_dbgQuery($query, $link, $result, $time); + if ($GLOBALS['cfg']['DBG']['sqllog']) { + $warningsCount = ''; + if ($options & DatabaseInterface::QUERY_STORE == DatabaseInterface::QUERY_STORE) { + if (isset($this->_links[$link]->warning_count)) { + $warningsCount = $this->_links[$link]->warning_count; + } + } + + openlog('phpMyAdmin', LOG_NDELAY | LOG_PID, LOG_USER); + + syslog( + LOG_INFO, + 'SQL[' . basename($_SERVER['SCRIPT_NAME']) . ']: ' + . sprintf('%0.3f', $time) . '(W:' . $warningsCount . ') > ' . $query + ); + closelog(); + } + } + + if ($result !== false && Tracker::isActive()) { + Tracker::handleQuery($query); + } + + return $result; + } + + /** + * Run multi query statement and return results + * + * @param string $multi_query multi query statement to execute + * @param mysqli $link mysqli object + * + * @return mysqli_result collection | boolean(false) + */ + public function tryMultiQuery($multi_query = '', $link = DatabaseInterface::CONNECT_USER) + { + if (! isset($this->_links[$link])) { + return false; + } + return $this->_extension->realMultiQuery($this->_links[$link], $multi_query); + } + + /** + * returns array with table names for given db + * + * @param string $database name of database + * @param mixed $link mysql link resource|object + * + * @return array tables names + */ + public function getTables($database, $link = DatabaseInterface::CONNECT_USER) + { + $tables = $this->fetchResult( + 'SHOW TABLES FROM ' . Util::backquote($database) . ';', + null, + 0, + $link, + self::QUERY_STORE + ); + if ($GLOBALS['cfg']['NaturalOrder']) { + usort($tables, 'strnatcasecmp'); + } + return $tables; + } + + + /** + * returns + * + * @param string $database name of database + * @param array $tables list of tables to search for for relations + * @param int $link mysql link resource|object + * + * @return array array of found foreign keys + */ + public function getForeignKeyConstrains($database, array $tables, $link = DatabaseInterface::CONNECT_USER) + { + $tablesListForQuery = ''; + foreach($tables as $table){ + $tablesListForQuery .= "'" . $this->escapeString($table) . "',"; + } + $tablesListForQuery = rtrim($tablesListForQuery, ','); + + $foreignKeyConstrains = $this->fetchResult( + "SELECT" + . " TABLE_NAME," + . " COLUMN_NAME," + . " REFERENCED_TABLE_NAME," + . " REFERENCED_COLUMN_NAME" + . " FROM information_schema.key_column_usage" + . " WHERE referenced_table_name IS NOT NULL" + . " AND TABLE_SCHEMA = '" . $this->escapeString($database) . "'" + . " AND TABLE_NAME IN (" . $tablesListForQuery . ")" + . " AND REFERENCED_TABLE_NAME IN (" . $tablesListForQuery . ");", + null, + null, + $link, + self::QUERY_STORE + ); + return $foreignKeyConstrains; + } + + /** + * returns a segment of the SQL WHERE clause regarding table name and type + * + * @param array|string $table table(s) + * @param boolean $tbl_is_group $table is a table group + * @param string $table_type whether table or view + * + * @return string a segment of the WHERE clause + */ + private function _getTableCondition($table, $tbl_is_group, $table_type) + { + // get table information from information_schema + if ($table) { + if (is_array($table)) { + $sql_where_table = 'AND t.`TABLE_NAME` ' + . Util::getCollateForIS() . ' IN (\'' + . implode( + '\', \'', + array_map( + array($this, 'escapeString'), + $table + ) + ) + . '\')'; + } elseif (true === $tbl_is_group) { + $sql_where_table = 'AND t.`TABLE_NAME` LIKE \'' + . Util::escapeMysqlWildcards( + $GLOBALS['dbi']->escapeString($table) + ) + . '%\''; + } else { + $sql_where_table = 'AND t.`TABLE_NAME` ' + . Util::getCollateForIS() . ' = \'' + . $GLOBALS['dbi']->escapeString($table) . '\''; + } + } else { + $sql_where_table = ''; + } + + if ($table_type) { + if ($table_type == 'view') { + $sql_where_table .= " AND t.`TABLE_TYPE` NOT IN ('BASE TABLE', 'SYSTEM VERSIONED')"; + } elseif ($table_type == 'table') { + $sql_where_table .= " AND t.`TABLE_TYPE` IN ('BASE TABLE', 'SYSTEM VERSIONED')"; + } + } + return $sql_where_table; + } + + /** + * returns the beginning of the SQL statement to fetch the list of tables + * + * @param string[] $this_databases databases to list + * @param string $sql_where_table additional condition + * + * @return string the SQL statement + */ + private function _getSqlForTablesFull($this_databases, $sql_where_table) + { + $sql = ' + SELECT *, + `TABLE_SCHEMA` AS `Db`, + `TABLE_NAME` AS `Name`, + `TABLE_TYPE` AS `TABLE_TYPE`, + `ENGINE` AS `Engine`, + `ENGINE` AS `Type`, + `VERSION` AS `Version`, + `ROW_FORMAT` AS `Row_format`, + `TABLE_ROWS` AS `Rows`, + `AVG_ROW_LENGTH` AS `Avg_row_length`, + `DATA_LENGTH` AS `Data_length`, + `MAX_DATA_LENGTH` AS `Max_data_length`, + `INDEX_LENGTH` AS `Index_length`, + `DATA_FREE` AS `Data_free`, + `AUTO_INCREMENT` AS `Auto_increment`, + `CREATE_TIME` AS `Create_time`, + `UPDATE_TIME` AS `Update_time`, + `CHECK_TIME` AS `Check_time`, + `TABLE_COLLATION` AS `Collation`, + `CHECKSUM` AS `Checksum`, + `CREATE_OPTIONS` AS `Create_options`, + `TABLE_COMMENT` AS `Comment` + FROM `information_schema`.`TABLES` t + WHERE `TABLE_SCHEMA` ' . Util::getCollateForIS() . ' + IN (\'' . implode("', '", $this_databases) . '\') + ' . $sql_where_table; + + return $sql; + } + + /** + * returns array of all tables in given db or dbs + * this function expects unquoted names: + * RIGHT: my_database + * WRONG: `my_database` + * WRONG: my\_database + * if $tbl_is_group is true, $table is used as filter for table names + * + * + * $GLOBALS['dbi']->getTablesFull('my_database'); + * $GLOBALS['dbi']->getTablesFull('my_database', 'my_table')); + * $GLOBALS['dbi']->getTablesFull('my_database', 'my_tables_', true)); + * + * + * @param string $database database + * @param string|array $table table name(s) + * @param boolean $tbl_is_group $table is a table group + * @param integer $limit_offset zero-based offset for the count + * @param boolean|integer $limit_count number of tables to return + * @param string $sort_by table attribute to sort by + * @param string $sort_order direction to sort (ASC or DESC) + * @param string $table_type whether table or view + * @param integer $link link type + * + * @todo move into Table + * + * @return array list of tables in given db(s) + */ + public function getTablesFull($database, $table = '', + $tbl_is_group = false, $limit_offset = 0, + $limit_count = false, $sort_by = 'Name', $sort_order = 'ASC', + $table_type = null, $link = DatabaseInterface::CONNECT_USER + ) { + if (true === $limit_count) { + $limit_count = $GLOBALS['cfg']['MaxTableList']; + } + // prepare and check parameters + if (! is_array($database)) { + $databases = array($database); + } else { + $databases = $database; + } + + $tables = array(); + + if (! $GLOBALS['cfg']['Server']['DisableIS']) { + $sql_where_table = $this->_getTableCondition( + $table, $tbl_is_group, $table_type + ); + + // for PMA bc: + // `SCHEMA_FIELD_NAME` AS `SHOW_TABLE_STATUS_FIELD_NAME` + // + // on non-Windows servers, + // added BINARY in the WHERE clause to force a case sensitive + // comparison (if we are looking for the db Aa we don't want + // to find the db aa) + $this_databases = array_map( + array($this, 'escapeString'), + $databases + ); + + $sql = $this->_getSqlForTablesFull($this_databases, $sql_where_table); + + // Sort the tables + $sql .= " ORDER BY $sort_by $sort_order"; + + if ($limit_count) { + $sql .= ' LIMIT ' . $limit_count . ' OFFSET ' . $limit_offset; + } + + $tables = $this->fetchResult( + $sql, array('TABLE_SCHEMA', 'TABLE_NAME'), null, $link + ); + + if ($sort_by == 'Name' && $GLOBALS['cfg']['NaturalOrder']) { + // here, the array's first key is by schema name + foreach ($tables as $one_database_name => $one_database_tables) { + uksort($one_database_tables, 'strnatcasecmp'); + + if ($sort_order == 'DESC') { + $one_database_tables = array_reverse($one_database_tables); + } + $tables[$one_database_name] = $one_database_tables; + } + } elseif ($sort_by == 'Data_length') { + // Size = Data_length + Index_length + foreach ($tables as $one_database_name => $one_database_tables) { + uasort( + $one_database_tables, + function ($a, $b) { + $aLength = $a['Data_length'] + $a['Index_length']; + $bLength = $b['Data_length'] + $b['Index_length']; + return $aLength == $bLength + ? 0 + : ($aLength < $bLength ? -1 : 1); + } + ); + + if ($sort_order == 'DESC') { + $one_database_tables = array_reverse($one_database_tables); + } + $tables[$one_database_name] = $one_database_tables; + } + } + } // end (get information from table schema) + + // If permissions are wrong on even one database directory, + // information_schema does not return any table info for any database + // this is why we fall back to SHOW TABLE STATUS even for MySQL >= 50002 + if (empty($tables)) { + foreach ($databases as $each_database) { + if ($table || (true === $tbl_is_group) || ! empty($table_type)) { + $sql = 'SHOW TABLE STATUS FROM ' + . Util::backquote($each_database) + . ' WHERE'; + $needAnd = false; + if ($table || (true === $tbl_is_group)) { + if (is_array($table)) { + $sql .= ' `Name` IN (\'' + . implode( + '\', \'', + array_map( + array($this, 'escapeString'), + $table, + $link + ) + ) . '\')'; + } else { + $sql .= " `Name` LIKE '" + . Util::escapeMysqlWildcards( + $this->escapeString($table, $link) + ) + . "%'"; + } + $needAnd = true; + } + if (! empty($table_type)) { + if ($needAnd) { + $sql .= " AND"; + } + if ($table_type == 'view') { + $sql .= " `Comment` = 'VIEW'"; + } elseif ($table_type == 'table') { + $sql .= " `Comment` != 'VIEW'"; + } + } + } else { + $sql = 'SHOW TABLE STATUS FROM ' + . Util::backquote($each_database); + } + + $each_tables = $this->fetchResult($sql, 'Name', null, $link); + + // Sort naturally if the config allows it and we're sorting + // the Name column. + if ($sort_by == 'Name' && $GLOBALS['cfg']['NaturalOrder']) { + uksort($each_tables, 'strnatcasecmp'); + + if ($sort_order == 'DESC') { + $each_tables = array_reverse($each_tables); + } + } else { + // Prepare to sort by creating array of the selected sort + // value to pass to array_multisort + + // Size = Data_length + Index_length + if ($sort_by == 'Data_length') { + foreach ($each_tables as $table_name => $table_data) { + ${$sort_by}[$table_name] = strtolower( + $table_data['Data_length'] + + $table_data['Index_length'] + ); + } + } else { + foreach ($each_tables as $table_name => $table_data) { + ${$sort_by}[$table_name] + = strtolower($table_data[$sort_by]); + } + } + + if (! empty($$sort_by)) { + if ($sort_order == 'DESC') { + array_multisort($$sort_by, SORT_DESC, $each_tables); + } else { + array_multisort($$sort_by, SORT_ASC, $each_tables); + } + } + + // cleanup the temporary sort array + unset($$sort_by); + } + + if ($limit_count) { + $each_tables = array_slice( + $each_tables, $limit_offset, $limit_count + ); + } + + foreach ($each_tables as $table_name => $each_table) { + if (! isset($each_tables[$table_name]['Type']) + && isset($each_tables[$table_name]['Engine']) + ) { + // pma BC, same parts of PMA still uses 'Type' + $each_tables[$table_name]['Type'] + =& $each_tables[$table_name]['Engine']; + } elseif (! isset($each_tables[$table_name]['Engine']) + && isset($each_tables[$table_name]['Type']) + ) { + // old MySQL reports Type, newer MySQL reports Engine + $each_tables[$table_name]['Engine'] + =& $each_tables[$table_name]['Type']; + } + + // Compatibility with INFORMATION_SCHEMA output + $each_tables[$table_name]['TABLE_SCHEMA'] + = $each_database; + $each_tables[$table_name]['TABLE_NAME'] + =& $each_tables[$table_name]['Name']; + $each_tables[$table_name]['ENGINE'] + =& $each_tables[$table_name]['Engine']; + $each_tables[$table_name]['VERSION'] + =& $each_tables[$table_name]['Version']; + $each_tables[$table_name]['ROW_FORMAT'] + =& $each_tables[$table_name]['Row_format']; + $each_tables[$table_name]['TABLE_ROWS'] + =& $each_tables[$table_name]['Rows']; + $each_tables[$table_name]['AVG_ROW_LENGTH'] + =& $each_tables[$table_name]['Avg_row_length']; + $each_tables[$table_name]['DATA_LENGTH'] + =& $each_tables[$table_name]['Data_length']; + $each_tables[$table_name]['MAX_DATA_LENGTH'] + =& $each_tables[$table_name]['Max_data_length']; + $each_tables[$table_name]['INDEX_LENGTH'] + =& $each_tables[$table_name]['Index_length']; + $each_tables[$table_name]['DATA_FREE'] + =& $each_tables[$table_name]['Data_free']; + $each_tables[$table_name]['AUTO_INCREMENT'] + =& $each_tables[$table_name]['Auto_increment']; + $each_tables[$table_name]['CREATE_TIME'] + =& $each_tables[$table_name]['Create_time']; + $each_tables[$table_name]['UPDATE_TIME'] + =& $each_tables[$table_name]['Update_time']; + $each_tables[$table_name]['CHECK_TIME'] + =& $each_tables[$table_name]['Check_time']; + $each_tables[$table_name]['TABLE_COLLATION'] + =& $each_tables[$table_name]['Collation']; + $each_tables[$table_name]['CHECKSUM'] + =& $each_tables[$table_name]['Checksum']; + $each_tables[$table_name]['CREATE_OPTIONS'] + =& $each_tables[$table_name]['Create_options']; + $each_tables[$table_name]['TABLE_COMMENT'] + =& $each_tables[$table_name]['Comment']; + + if (strtoupper($each_tables[$table_name]['Comment']) === 'VIEW' + && $each_tables[$table_name]['Engine'] == null + ) { + $each_tables[$table_name]['TABLE_TYPE'] = 'VIEW'; + } elseif ($each_database == 'information_schema') { + $each_tables[$table_name]['TABLE_TYPE'] = 'SYSTEM VIEW'; + } else { + /** + * @todo difference between 'TEMPORARY' and 'BASE TABLE' + * but how to detect? + */ + $each_tables[$table_name]['TABLE_TYPE'] = 'BASE TABLE'; + } + } + + $tables[$each_database] = $each_tables; + } + } + + // cache table data + // so Table does not require to issue SHOW TABLE STATUS again + $this->_cacheTableData($tables, $table); + + if (is_array($database)) { + return $tables; + } + + if (isset($tables[$database])) { + return $tables[$database]; + } + + if (isset($tables[mb_strtolower($database)])) { + // on windows with lower_case_table_names = 1 + // MySQL returns + // with SHOW DATABASES or information_schema.SCHEMATA: `Test` + // but information_schema.TABLES gives `test` + // see https://github.com/phpmyadmin/phpmyadmin/issues/8402 + return $tables[mb_strtolower($database)]; + } + + return $tables; + } + + /** + * Get VIEWs in a particular database + * + * @param string $db Database name to look in + * + * @return array $views Set of VIEWs inside the database + */ + public function getVirtualTables($db) + { + + $tables_full = $this->getTablesFull($db); + $views = array(); + + foreach ($tables_full as $table=>$tmp) { + + $_table = $this->getTable($db, $table); + if ($_table->isView()) { + $views[] = $table; + } + + } + + return $views; + + } + + + /** + * returns array with databases containing extended infos about them + * + * @param string $database database + * @param boolean $force_stats retrieve stats also for MySQL < 5 + * @param integer $link link type + * @param string $sort_by column to order by + * @param string $sort_order ASC or DESC + * @param integer $limit_offset starting offset for LIMIT + * @param bool|int $limit_count row count for LIMIT or true + * for $GLOBALS['cfg']['MaxDbList'] + * + * @todo move into ListDatabase? + * + * @return array $databases + */ + public function getDatabasesFull($database = null, $force_stats = false, + $link = DatabaseInterface::CONNECT_USER, $sort_by = 'SCHEMA_NAME', $sort_order = 'ASC', + $limit_offset = 0, $limit_count = false + ) { + $sort_order = strtoupper($sort_order); + + if (true === $limit_count) { + $limit_count = $GLOBALS['cfg']['MaxDbList']; + } + + $apply_limit_and_order_manual = true; + + if (! $GLOBALS['cfg']['Server']['DisableIS']) { + /** + * if $GLOBALS['cfg']['NaturalOrder'] is enabled, we cannot use LIMIT + * cause MySQL does not support natural ordering, + * we have to do it afterward + */ + $limit = ''; + if (! $GLOBALS['cfg']['NaturalOrder']) { + if ($limit_count) { + $limit = ' LIMIT ' . $limit_count . ' OFFSET ' . $limit_offset; + } + + $apply_limit_and_order_manual = false; + } + + // get table information from information_schema + if (! empty($database)) { + $sql_where_schema = 'WHERE `SCHEMA_NAME` LIKE \'' + . $this->escapeString($database, $link) . '\''; + } else { + $sql_where_schema = ''; + } + + $sql = 'SELECT *, + CAST(BIN_NAME AS CHAR CHARACTER SET utf8) AS SCHEMA_NAME + FROM ('; + $sql .= 'SELECT + BINARY s.SCHEMA_NAME AS BIN_NAME, + s.DEFAULT_COLLATION_NAME'; + if ($force_stats) { + $sql .= ', + COUNT(t.TABLE_SCHEMA) AS SCHEMA_TABLES, + SUM(t.TABLE_ROWS) AS SCHEMA_TABLE_ROWS, + SUM(t.DATA_LENGTH) AS SCHEMA_DATA_LENGTH, + SUM(t.MAX_DATA_LENGTH) AS SCHEMA_MAX_DATA_LENGTH, + SUM(t.INDEX_LENGTH) AS SCHEMA_INDEX_LENGTH, + SUM(t.DATA_LENGTH + t.INDEX_LENGTH) + AS SCHEMA_LENGTH, + SUM(IF(t.ENGINE <> \'InnoDB\', t.DATA_FREE, 0)) + AS SCHEMA_DATA_FREE'; + } + $sql .= ' + FROM `information_schema`.SCHEMATA s '; + if ($force_stats) { + $sql .= ' + LEFT JOIN `information_schema`.TABLES t + ON BINARY t.TABLE_SCHEMA = BINARY s.SCHEMA_NAME'; + } + $sql .= $sql_where_schema . ' + GROUP BY BINARY s.SCHEMA_NAME, s.DEFAULT_COLLATION_NAME + ORDER BY '; + if ($sort_by == 'SCHEMA_NAME' + || $sort_by == 'DEFAULT_COLLATION_NAME' + ) { + $sql .= 'BINARY '; + } + $sql .= Util::backquote($sort_by) + . ' ' . $sort_order + . $limit; + $sql .= ') a'; + + $databases = $this->fetchResult($sql, 'SCHEMA_NAME', null, $link); + + $mysql_error = $this->getError($link); + if (! count($databases) && $GLOBALS['errno']) { + Util::mysqlDie($mysql_error, $sql); + } + + // display only databases also in official database list + // f.e. to apply hide_db and only_db + $drops = array_diff( + array_keys($databases), (array) $GLOBALS['dblist']->databases + ); + foreach ($drops as $drop) { + unset($databases[$drop]); + } + } else { + $databases = array(); + foreach ($GLOBALS['dblist']->databases as $database_name) { + // Compatibility with INFORMATION_SCHEMA output + $databases[$database_name]['SCHEMA_NAME'] = $database_name; + + $databases[$database_name]['DEFAULT_COLLATION_NAME'] + = $this->getDbCollation($database_name); + + if (!$force_stats) { + continue; + } + + // get additional info about tables + $databases[$database_name]['SCHEMA_TABLES'] = 0; + $databases[$database_name]['SCHEMA_TABLE_ROWS'] = 0; + $databases[$database_name]['SCHEMA_DATA_LENGTH'] = 0; + $databases[$database_name]['SCHEMA_MAX_DATA_LENGTH'] = 0; + $databases[$database_name]['SCHEMA_INDEX_LENGTH'] = 0; + $databases[$database_name]['SCHEMA_LENGTH'] = 0; + $databases[$database_name]['SCHEMA_DATA_FREE'] = 0; + + $res = $this->query( + 'SHOW TABLE STATUS FROM ' + . Util::backquote($database_name) . ';' + ); + + if ($res === false) { + unset($res); + continue; + } + + while ($row = $this->fetchAssoc($res)) { + $databases[$database_name]['SCHEMA_TABLES']++; + $databases[$database_name]['SCHEMA_TABLE_ROWS'] + += $row['Rows']; + $databases[$database_name]['SCHEMA_DATA_LENGTH'] + += $row['Data_length']; + $databases[$database_name]['SCHEMA_MAX_DATA_LENGTH'] + += $row['Max_data_length']; + $databases[$database_name]['SCHEMA_INDEX_LENGTH'] + += $row['Index_length']; + + // for InnoDB, this does not contain the number of + // overhead bytes but the total free space + if ('InnoDB' != $row['Engine']) { + $databases[$database_name]['SCHEMA_DATA_FREE'] + += $row['Data_free']; + } + $databases[$database_name]['SCHEMA_LENGTH'] + += $row['Data_length'] + $row['Index_length']; + } + $this->freeResult($res); + unset($res); + } + } + + /** + * apply limit and order manually now + * (caused by older MySQL < 5 or $GLOBALS['cfg']['NaturalOrder']) + */ + if ($apply_limit_and_order_manual) { + $GLOBALS['callback_sort_order'] = $sort_order; + $GLOBALS['callback_sort_by'] = $sort_by; + usort( + $databases, + array(self::class, '_usortComparisonCallback') + ); + unset($GLOBALS['callback_sort_order'], $GLOBALS['callback_sort_by']); + + /** + * now apply limit + */ + if ($limit_count) { + $databases = array_slice($databases, $limit_offset, $limit_count); + } + } + + return $databases; + } + + /** + * usort comparison callback + * + * @param string $a first argument to sort + * @param string $b second argument to sort + * + * @return integer a value representing whether $a should be before $b in the + * sorted array or not + * + * @access private + */ + private static function _usortComparisonCallback($a, $b) + { + if ($GLOBALS['cfg']['NaturalOrder']) { + $sorter = 'strnatcasecmp'; + } else { + $sorter = 'strcasecmp'; + } + /* No sorting when key is not present */ + if (! isset($a[$GLOBALS['callback_sort_by']]) + || ! isset($b[$GLOBALS['callback_sort_by']]) + ) { + return 0; + } + // produces f.e.: + // return -1 * strnatcasecmp($a["SCHEMA_TABLES"], $b["SCHEMA_TABLES"]) + return ($GLOBALS['callback_sort_order'] == 'ASC' ? 1 : -1) * $sorter( + $a[$GLOBALS['callback_sort_by']], $b[$GLOBALS['callback_sort_by']] + ); + } // end of the '_usortComparisonCallback()' method + + /** + * returns detailed array with all columns for sql + * + * @param string $sql_query target SQL query to get columns + * @param array $view_columns alias for columns + * + * @return array + */ + public function getColumnMapFromSql($sql_query, array $view_columns = array()) + { + $result = $this->tryQuery($sql_query); + + if ($result === false) { + return array(); + } + + $meta = $this->getFieldsMeta( + $result + ); + + $nbFields = count($meta); + if ($nbFields <= 0) { + return array(); + } + + $column_map = array(); + $nbColumns = count($view_columns); + + for ($i=0; $i < $nbFields; $i++) { + + $map = array(); + $map['table_name'] = $meta[$i]->table; + $map['refering_column'] = $meta[$i]->name; + + if ($nbColumns > 1) { + $map['real_column'] = $view_columns[$i]; + } + + $column_map[] = $map; + } + + return $column_map; + } + + /** + * returns detailed array with all columns for given table in database, + * or all tables/databases + * + * @param string $database name of database + * @param string $table name of table to retrieve columns from + * @param string $column name of specific column + * @param mixed $link mysql link resource + * + * @return array + */ + public function getColumnsFull($database = null, $table = null, + $column = null, $link = DatabaseInterface::CONNECT_USER + ) { + if (! $GLOBALS['cfg']['Server']['DisableIS']) { + $sql_wheres = array(); + $array_keys = array(); + + // get columns information from information_schema + if (null !== $database) { + $sql_wheres[] = '`TABLE_SCHEMA` = \'' + . $this->escapeString($database, $link) . '\' '; + } else { + $array_keys[] = 'TABLE_SCHEMA'; + } + if (null !== $table) { + $sql_wheres[] = '`TABLE_NAME` = \'' + . $this->escapeString($table, $link) . '\' '; + } else { + $array_keys[] = 'TABLE_NAME'; + } + if (null !== $column) { + $sql_wheres[] = '`COLUMN_NAME` = \'' + . $this->escapeString($column, $link) . '\' '; + } else { + $array_keys[] = 'COLUMN_NAME'; + } + + // for PMA bc: + // `[SCHEMA_FIELD_NAME]` AS `[SHOW_FULL_COLUMNS_FIELD_NAME]` + $sql = ' + SELECT *, + `COLUMN_NAME` AS `Field`, + `COLUMN_TYPE` AS `Type`, + `COLLATION_NAME` AS `Collation`, + `IS_NULLABLE` AS `Null`, + `COLUMN_KEY` AS `Key`, + `COLUMN_DEFAULT` AS `Default`, + `EXTRA` AS `Extra`, + `PRIVILEGES` AS `Privileges`, + `COLUMN_COMMENT` AS `Comment` + FROM `information_schema`.`COLUMNS`'; + + if (count($sql_wheres)) { + $sql .= "\n" . ' WHERE ' . implode(' AND ', $sql_wheres); + } + return $this->fetchResult($sql, $array_keys, null, $link); + } + + $columns = array(); + if (null === $database) { + foreach ($GLOBALS['dblist']->databases as $database) { + $columns[$database] = $this->getColumnsFull( + $database, null, null, $link + ); + } + return $columns; + } elseif (null === $table) { + $tables = $this->getTables($database); + foreach ($tables as $table) { + $columns[$table] = $this->getColumnsFull( + $database, $table, null, $link + ); + } + return $columns; + } + $sql = 'SHOW FULL COLUMNS FROM ' + . Util::backquote($database) . '.' . Util::backquote($table); + if (null !== $column) { + $sql .= " LIKE '" . $this->escapeString($column, $link) . "'"; + } + + $columns = $this->fetchResult($sql, 'Field', null, $link); + $ordinal_position = 1; + foreach ($columns as $column_name => $each_column) { + + // Compatibility with INFORMATION_SCHEMA output + $columns[$column_name]['COLUMN_NAME'] + =& $columns[$column_name]['Field']; + $columns[$column_name]['COLUMN_TYPE'] + =& $columns[$column_name]['Type']; + $columns[$column_name]['COLLATION_NAME'] + =& $columns[$column_name]['Collation']; + $columns[$column_name]['IS_NULLABLE'] + =& $columns[$column_name]['Null']; + $columns[$column_name]['COLUMN_KEY'] + =& $columns[$column_name]['Key']; + $columns[$column_name]['COLUMN_DEFAULT'] + =& $columns[$column_name]['Default']; + $columns[$column_name]['EXTRA'] + =& $columns[$column_name]['Extra']; + $columns[$column_name]['PRIVILEGES'] + =& $columns[$column_name]['Privileges']; + $columns[$column_name]['COLUMN_COMMENT'] + =& $columns[$column_name]['Comment']; + + $columns[$column_name]['TABLE_CATALOG'] = null; + $columns[$column_name]['TABLE_SCHEMA'] = $database; + $columns[$column_name]['TABLE_NAME'] = $table; + $columns[$column_name]['ORDINAL_POSITION'] = $ordinal_position; + $columns[$column_name]['DATA_TYPE'] + = substr( + $columns[$column_name]['COLUMN_TYPE'], + 0, + strpos($columns[$column_name]['COLUMN_TYPE'], '(') + ); + /** + * @todo guess CHARACTER_MAXIMUM_LENGTH from COLUMN_TYPE + */ + $columns[$column_name]['CHARACTER_MAXIMUM_LENGTH'] = null; + /** + * @todo guess CHARACTER_OCTET_LENGTH from CHARACTER_MAXIMUM_LENGTH + */ + $columns[$column_name]['CHARACTER_OCTET_LENGTH'] = null; + $columns[$column_name]['NUMERIC_PRECISION'] = null; + $columns[$column_name]['NUMERIC_SCALE'] = null; + $columns[$column_name]['CHARACTER_SET_NAME'] + = substr( + $columns[$column_name]['COLLATION_NAME'], + 0, + strpos($columns[$column_name]['COLLATION_NAME'], '_') + ); + + $ordinal_position++; + } + + if (null !== $column) { + return reset($columns); + } + + return $columns; + } + + /** + * Returns SQL query for fetching columns for a table + * + * The 'Key' column is not calculated properly, use $GLOBALS['dbi']->getColumns() + * to get correct values. + * + * @param string $database name of database + * @param string $table name of table to retrieve columns from + * @param string $column name of column, null to show all columns + * @param boolean $full whether to return full info or only column names + * + * @see getColumns() + * + * @return string + */ + public function getColumnsSql($database, $table, $column = null, $full = false) + { + $sql = 'SHOW ' . ($full ? 'FULL' : '') . ' COLUMNS FROM ' + . Util::backquote($database) . '.' . Util::backquote($table) + . (($column !== null) ? "LIKE '" + . $GLOBALS['dbi']->escapeString($column) . "'" : ''); + + return $sql; + } + + /** + * Returns descriptions of columns in given table (all or given by $column) + * + * @param string $database name of database + * @param string $table name of table to retrieve columns from + * @param string $column name of column, null to show all columns + * @param boolean $full whether to return full info or only column names + * @param integer $link link type + * + * @return array array indexed by column names or, + * if $column is given, flat array description + */ + public function getColumns($database, $table, $column = null, $full = false, + $link = DatabaseInterface::CONNECT_USER + ) { + $sql = $this->getColumnsSql($database, $table, $column, $full); + $fields = $this->fetchResult($sql, 'Field', null, $link); + if (! is_array($fields) || count($fields) == 0) { + return array(); + } + // Check if column is a part of multiple-column index and set its 'Key'. + $indexes = Index::getFromTable($table, $database); + foreach ($fields as $field => $field_data) { + if (!empty($field_data['Key'])) { + continue; + } + + foreach ($indexes as $index) { + /** @var Index $index */ + if (!$index->hasColumn($field)) { + continue; + } + + $index_columns = $index->getColumns(); + if ($index_columns[$field]->getSeqInIndex() > 1) { + if ($index->isUnique()) { + $fields[$field]['Key'] = 'UNI'; + } else { + $fields[$field]['Key'] = 'MUL'; + } + } + } + } + + return ($column != null) ? array_shift($fields) : $fields; + } + + /** + * Returns all column names in given table + * + * @param string $database name of database + * @param string $table name of table to retrieve columns from + * @param mixed $link mysql link resource + * + * @return null|array + */ + public function getColumnNames($database, $table, $link = DatabaseInterface::CONNECT_USER) + { + $sql = $this->getColumnsSql($database, $table); + // We only need the 'Field' column which contains the table's column names + $fields = array_keys($this->fetchResult($sql, 'Field', null, $link)); + + if (! is_array($fields) || count($fields) == 0) { + return null; + } + return $fields; + } + + /** + * Returns SQL for fetching information on table indexes (SHOW INDEXES) + * + * @param string $database name of database + * @param string $table name of the table whose indexes are to be retrieved + * @param string $where additional conditions for WHERE + * + * @return string SQL for getting indexes + */ + public function getTableIndexesSql($database, $table, $where = null) + { + $sql = 'SHOW INDEXES FROM ' . Util::backquote($database) . '.' + . Util::backquote($table); + if ($where) { + $sql .= ' WHERE (' . $where . ')'; + } + return $sql; + } + + /** + * Returns indexes of a table + * + * @param string $database name of database + * @param string $table name of the table whose indexes are to be retrieved + * @param mixed $link mysql link resource + * + * @return array $indexes + */ + public function getTableIndexes($database, $table, $link = DatabaseInterface::CONNECT_USER) + { + $sql = $this->getTableIndexesSql($database, $table); + $indexes = $this->fetchResult($sql, null, null, $link); + + if (! is_array($indexes) || count($indexes) < 1) { + return array(); + } + return $indexes; + } + + /** + * returns value of given mysql server variable + * + * @param string $var mysql server variable name + * @param int $type DatabaseInterface::GETVAR_SESSION | + * DatabaseInterface::GETVAR_GLOBAL + * @param mixed $link mysql link resource|object + * + * @return mixed value for mysql server variable + */ + public function getVariable( + $var, $type = self::GETVAR_SESSION, $link = DatabaseInterface::CONNECT_USER + ) { + switch ($type) { + case self::GETVAR_SESSION: + $modifier = ' SESSION'; + break; + case self::GETVAR_GLOBAL: + $modifier = ' GLOBAL'; + break; + default: + $modifier = ''; + } + return $this->fetchValue( + 'SHOW' . $modifier . ' VARIABLES LIKE \'' . $var . '\';', 0, 1, $link + ); + } + + /** + * Sets new value for a variable if it is different from the current value + * + * @param string $var variable name + * @param string $value value to set + * @param mixed $link mysql link resource|object + * + * @return bool whether query was a successful + */ + public function setVariable($var, $value, $link = DatabaseInterface::CONNECT_USER) + { + $current_value = $this->getVariable( + $var, self::GETVAR_SESSION, $link + ); + if ($current_value == $value) { + return true; + } + + return $this->query("SET " . $var . " = " . $value . ';', $link); + } + + /** + * Convert version string to integer. + * + * @param string $version MySQL server version + * + * @return int + */ + public static function versionToInt($version) + { + $match = explode('.', $version); + return (int) sprintf('%d%02d%02d', $match[0], $match[1], intval($match[2])); + } + + /** + * Function called just after a connection to the MySQL database server has + * been established. It sets the connection collation, and determines the + * version of MySQL which is running. + * + * @return void + */ + public function postConnect() + { + $version = $this->fetchSingleRow( + 'SELECT @@version, @@version_comment', + 'ASSOC', + DatabaseInterface::CONNECT_USER + ); + + if ($version) { + $this->_version_int = self::versionToInt($version['@@version']); + $this->_version_str = $version['@@version']; + $this->_version_comment = $version['@@version_comment']; + if (stripos($version['@@version'], 'mariadb') !== false) { + $this->_is_mariadb = true; + } + if (stripos($version['@@version_comment'], 'percona') !== false) { + $this->_is_percona = true; + } + } + + if ($this->_version_int > 50503) { + $default_charset = 'utf8mb4'; + $default_collation = 'utf8mb4_general_ci'; + } else { + $default_charset = 'utf8'; + $default_collation = 'utf8_general_ci'; + } + $GLOBALS['collation_connection'] = $default_collation; + $GLOBALS['charset_connection'] = $default_charset; + $this->query( + "SET NAMES '$default_charset' COLLATE '$default_collation';", + DatabaseInterface::CONNECT_USER, + self::QUERY_STORE + ); + + /* Locale for messages */ + $locale = LanguageManager::getInstance()->getCurrentLanguage()->getMySQLLocale(); + if (! empty($locale)) { + $this->query( + "SET lc_messages = '" . $locale . "';", + DatabaseInterface::CONNECT_USER, + self::QUERY_STORE + ); + } + + // Set timezone for the session, if required. + if ($GLOBALS['cfg']['Server']['SessionTimeZone'] != '') { + $sql_query_tz = 'SET ' . Util::backquote('time_zone') . ' = ' + . '\'' + . $this->escapeString($GLOBALS['cfg']['Server']['SessionTimeZone']) + . '\''; + + if (! $this->tryQuery($sql_query_tz)) { + $error_message_tz = sprintf( + __( + 'Unable to use timezone "%1$s" for server %2$d. ' + . 'Please check your configuration setting for ' + . '[em]$cfg[\'Servers\'][%3$d][\'SessionTimeZone\'][/em]. ' + . 'phpMyAdmin is currently using the default time zone ' + . 'of the database server.' + ), + $GLOBALS['cfg']['Server']['SessionTimeZone'], + $GLOBALS['server'], + $GLOBALS['server'] + ); + + trigger_error($error_message_tz, E_USER_WARNING); + } + } + + /* Loads closest context to this version. */ + \PhpMyAdmin\SqlParser\Context::loadClosest( + ($this->_is_mariadb ? 'MariaDb' : 'MySql') . $this->_version_int + ); + + /** + * the DatabaseList class as a stub for the ListDatabase class + */ + $GLOBALS['dblist'] = new DatabaseList(); + } + + /** + * Sets collation connection for user link + * + * @param string $collation collation to set + */ + public function setCollation($collation) + { + $charset = $GLOBALS['charset_connection']; + /* Automatically adjust collation if not supported by server */ + if ($charset == 'utf8' && strncmp('utf8mb4_', $collation, 8) == 0) { + $collation = 'utf8_' . substr($collation, 8); + } + $result = $this->tryQuery( + "SET collation_connection = '" + . $this->escapeString($collation, DatabaseInterface::CONNECT_USER) + . "';", + DatabaseInterface::CONNECT_USER, + self::QUERY_STORE + ); + if ($result === false) { + trigger_error( + __('Failed to set configured collation connection!'), + E_USER_WARNING + ); + } else { + $GLOBALS['collation_connection'] = $collation; + } + } + + /** + * Function called just after a connection to the MySQL database server has + * been established. It sets the connection collation, and determines the + * version of MySQL which is running. + * + * @param integer $link link type + * + * @return void + */ + public function postConnectControl() + { + // If Zero configuration mode enabled, check PMA tables in current db. + if ($GLOBALS['cfg']['ZeroConf'] == true) { + /** + * the DatabaseList class as a stub for the ListDatabase class + */ + $GLOBALS['dblist'] = new DatabaseList(); + + if (strlen($GLOBALS['db'])) { + $cfgRelation = $this->relation->getRelationsParam(); + if (empty($cfgRelation['db'])) { + $this->relation->fixPmaTables($GLOBALS['db'], false); + } + } + $cfgRelation = $this->relation->getRelationsParam(); + if (empty($cfgRelation['db'])) { + if ($GLOBALS['dblist']->databases->exists('phpmyadmin')) { + $this->relation->fixPmaTables('phpmyadmin', false); + } + } + } + } + + /** + * returns a single value from the given result or query, + * if the query or the result has more than one row or field + * the first field of the first row is returned + * + * + * $sql = 'SELECT `name` FROM `user` WHERE `id` = 123'; + * $user_name = $GLOBALS['dbi']->fetchValue($sql); + * // produces + * // $user_name = 'John Doe' + * + * + * @param string $query The query to execute + * @param integer $row_number row to fetch the value from, + * starting at 0, with 0 being default + * @param integer|string $field field to fetch the value from, + * starting at 0, with 0 being default + * @param integer $link link type + * + * @return mixed value of first field in first row from result + * or false if not found + */ + public function fetchValue($query, $row_number = 0, $field = 0, $link = DatabaseInterface::CONNECT_USER) + { + $value = false; + + $result = $this->tryQuery( + $query, + $link, + self::QUERY_STORE, + false + ); + if ($result === false) { + return false; + } + + // return false if result is empty or false + // or requested row is larger than rows in result + if ($this->numRows($result) < ($row_number + 1)) { + return $value; + } + + // if $field is an integer use non associative mysql fetch function + if (is_int($field)) { + $fetch_function = 'fetchRow'; + } else { + $fetch_function = 'fetchAssoc'; + } + + // get requested row + for ($i = 0; $i <= $row_number; $i++) { + $row = $this->$fetch_function($result); + } + $this->freeResult($result); + + // return requested field + if (isset($row[$field])) { + $value = $row[$field]; + } + + return $value; + } + + /** + * returns only the first row from the result + * + * + * $sql = 'SELECT * FROM `user` WHERE `id` = 123'; + * $user = $GLOBALS['dbi']->fetchSingleRow($sql); + * // produces + * // $user = array('id' => 123, 'name' => 'John Doe') + * + * + * @param string $query The query to execute + * @param string $type NUM|ASSOC|BOTH returned array should either numeric + * associative or both + * @param integer $link link type + * + * @return array|boolean first row from result + * or false if result is empty + */ + public function fetchSingleRow($query, $type = 'ASSOC', $link = DatabaseInterface::CONNECT_USER) + { + $result = $this->tryQuery( + $query, + $link, + self::QUERY_STORE, + false + ); + if ($result === false) { + return false; + } + + // return false if result is empty or false + if (! $this->numRows($result)) { + return false; + } + + switch ($type) { + case 'NUM' : + $fetch_function = 'fetchRow'; + break; + case 'ASSOC' : + $fetch_function = 'fetchAssoc'; + break; + case 'BOTH' : + default : + $fetch_function = 'fetchArray'; + break; + } + + $row = $this->$fetch_function($result); + $this->freeResult($result); + return $row; + } + + /** + * Returns row or element of a row + * + * @param array $row Row to process + * @param string|null $value Which column to return + * + * @return mixed + */ + private function _fetchValue(array $row, $value) + { + if (is_null($value)) { + return $row; + } + + return $row[$value]; + } + + /** + * returns all rows in the resultset in one array + * + * + * $sql = 'SELECT * FROM `user`'; + * $users = $GLOBALS['dbi']->fetchResult($sql); + * // produces + * // $users[] = array('id' => 123, 'name' => 'John Doe') + * + * $sql = 'SELECT `id`, `name` FROM `user`'; + * $users = $GLOBALS['dbi']->fetchResult($sql, 'id'); + * // produces + * // $users['123'] = array('id' => 123, 'name' => 'John Doe') + * + * $sql = 'SELECT `id`, `name` FROM `user`'; + * $users = $GLOBALS['dbi']->fetchResult($sql, 0); + * // produces + * // $users['123'] = array(0 => 123, 1 => 'John Doe') + * + * $sql = 'SELECT `id`, `name` FROM `user`'; + * $users = $GLOBALS['dbi']->fetchResult($sql, 'id', 'name'); + * // or + * $users = $GLOBALS['dbi']->fetchResult($sql, 0, 1); + * // produces + * // $users['123'] = 'John Doe' + * + * $sql = 'SELECT `name` FROM `user`'; + * $users = $GLOBALS['dbi']->fetchResult($sql); + * // produces + * // $users[] = 'John Doe' + * + * $sql = 'SELECT `group`, `name` FROM `user`' + * $users = $GLOBALS['dbi']->fetchResult($sql, array('group', null), 'name'); + * // produces + * // $users['admin'][] = 'John Doe' + * + * $sql = 'SELECT `group`, `name` FROM `user`' + * $users = $GLOBALS['dbi']->fetchResult($sql, array('group', 'name'), 'id'); + * // produces + * // $users['admin']['John Doe'] = '123' + * + * + * @param string $query query to execute + * @param string|integer|array $key field-name or offset + * used as key for array + * or array of those + * @param string|integer $value value-name or offset + * used as value for array + * @param integer $link link type + * @param integer $options query options + * + * @return array resultrows or values indexed by $key + */ + public function fetchResult($query, $key = null, $value = null, + $link = DatabaseInterface::CONNECT_USER, $options = 0 + ) { + $resultrows = array(); + + $result = $this->tryQuery($query, $link, $options, false); + + // return empty array if result is empty or false + if ($result === false) { + return $resultrows; + } + + $fetch_function = 'fetchAssoc'; + + // no nested array if only one field is in result + if (null === $key && 1 === $this->numFields($result)) { + $value = 0; + $fetch_function = 'fetchRow'; + } + + // if $key is an integer use non associative mysql fetch function + if (is_int($key)) { + $fetch_function = 'fetchRow'; + } + + if (null === $key) { + while ($row = $this->$fetch_function($result)) { + $resultrows[] = $this->_fetchValue($row, $value); + } + } else { + if (is_array($key)) { + while ($row = $this->$fetch_function($result)) { + $result_target =& $resultrows; + foreach ($key as $key_index) { + if (null === $key_index) { + $result_target =& $result_target[]; + continue; + } + + if (! isset($result_target[$row[$key_index]])) { + $result_target[$row[$key_index]] = array(); + } + $result_target =& $result_target[$row[$key_index]]; + } + $result_target = $this->_fetchValue($row, $value); + } + } else { + while ($row = $this->$fetch_function($result)) { + $resultrows[$row[$key]] = $this->_fetchValue($row, $value); + } + } + } + + $this->freeResult($result); + return $resultrows; + } + + /** + * Get supported SQL compatibility modes + * + * @return array supported SQL compatibility modes + */ + public function getCompatibilities() + { + $compats = array('NONE'); + $compats[] = 'ANSI'; + $compats[] = 'DB2'; + $compats[] = 'MAXDB'; + $compats[] = 'MYSQL323'; + $compats[] = 'MYSQL40'; + $compats[] = 'MSSQL'; + $compats[] = 'ORACLE'; + // removed; in MySQL 5.0.33, this produces exports that + // can't be read by POSTGRESQL (see our bug #1596328) + //$compats[] = 'POSTGRESQL'; + $compats[] = 'TRADITIONAL'; + + return $compats; + } + + /** + * returns warnings for last query + * + * @param integer $link link type + * + * @return array warnings + */ + public function getWarnings($link = DatabaseInterface::CONNECT_USER) + { + return $this->fetchResult('SHOW WARNINGS', null, null, $link); + } + + /** + * returns an array of PROCEDURE or FUNCTION names for a db + * + * @param string $db db name + * @param string $which PROCEDURE | FUNCTION + * @param integer $link link type + * + * @return array the procedure names or function names + */ + public function getProceduresOrFunctions($db, $which, $link = DatabaseInterface::CONNECT_USER) + { + $shows = $this->fetchResult( + 'SHOW ' . $which . ' STATUS;', null, null, $link + ); + $result = array(); + foreach ($shows as $one_show) { + if ($one_show['Db'] == $db && $one_show['Type'] == $which) { + $result[] = $one_show['Name']; + } + } + return($result); + } + + /** + * returns the definition of a specific PROCEDURE, FUNCTION, EVENT or VIEW + * + * @param string $db db name + * @param string $which PROCEDURE | FUNCTION | EVENT | VIEW + * @param string $name the procedure|function|event|view name + * @param integer $link link type + * + * @return string the definition + */ + public function getDefinition($db, $which, $name, $link = DatabaseInterface::CONNECT_USER) + { + $returned_field = array( + 'PROCEDURE' => 'Create Procedure', + 'FUNCTION' => 'Create Function', + 'EVENT' => 'Create Event', + 'VIEW' => 'Create View' + ); + $query = 'SHOW CREATE ' . $which . ' ' + . Util::backquote($db) . '.' + . Util::backquote($name); + return($this->fetchValue($query, 0, $returned_field[$which], $link)); + } + + /** + * returns details about the PROCEDUREs or FUNCTIONs for a specific database + * or details about a specific routine + * + * @param string $db db name + * @param string $which PROCEDURE | FUNCTION or null for both + * @param string $name name of the routine (to fetch a specific routine) + * + * @return array information about ROCEDUREs or FUNCTIONs + */ + public function getRoutines($db, $which = null, $name = '') + { + $routines = array(); + if (! $GLOBALS['cfg']['Server']['DisableIS']) { + $query = "SELECT" + . " `ROUTINE_SCHEMA` AS `Db`," + . " `SPECIFIC_NAME` AS `Name`," + . " `ROUTINE_TYPE` AS `Type`," + . " `DEFINER` AS `Definer`," + . " `LAST_ALTERED` AS `Modified`," + . " `CREATED` AS `Created`," + . " `SECURITY_TYPE` AS `Security_type`," + . " `ROUTINE_COMMENT` AS `Comment`," + . " `CHARACTER_SET_CLIENT` AS `character_set_client`," + . " `COLLATION_CONNECTION` AS `collation_connection`," + . " `DATABASE_COLLATION` AS `Database Collation`," + . " `DTD_IDENTIFIER`" + . " FROM `information_schema`.`ROUTINES`" + . " WHERE `ROUTINE_SCHEMA` " . Util::getCollateForIS() + . " = '" . $GLOBALS['dbi']->escapeString($db) . "'"; + if (Core::isValid($which, array('FUNCTION','PROCEDURE'))) { + $query .= " AND `ROUTINE_TYPE` = '" . $which . "'"; + } + if (! empty($name)) { + $query .= " AND `SPECIFIC_NAME`" + . " = '" . $GLOBALS['dbi']->escapeString($name) . "'"; + } + $result = $this->fetchResult($query); + if (!empty($result)) { + $routines = $result; + } + } else { + if ($which == 'FUNCTION' || $which == null) { + $query = "SHOW FUNCTION STATUS" + . " WHERE `Db` = '" . $GLOBALS['dbi']->escapeString($db) . "'"; + if (! empty($name)) { + $query .= " AND `Name` = '" + . $GLOBALS['dbi']->escapeString($name) . "'"; + } + $result = $this->fetchResult($query); + if (!empty($result)) { + $routines = array_merge($routines, $result); + } + } + if ($which == 'PROCEDURE' || $which == null) { + $query = "SHOW PROCEDURE STATUS" + . " WHERE `Db` = '" . $GLOBALS['dbi']->escapeString($db) . "'"; + if (! empty($name)) { + $query .= " AND `Name` = '" + . $GLOBALS['dbi']->escapeString($name) . "'"; + } + $result = $this->fetchResult($query); + if (!empty($result)) { + $routines = array_merge($routines, $result); + } + } + } + + $ret = array(); + foreach ($routines as $routine) { + $one_result = array(); + $one_result['db'] = $routine['Db']; + $one_result['name'] = $routine['Name']; + $one_result['type'] = $routine['Type']; + $one_result['definer'] = $routine['Definer']; + $one_result['returns'] = isset($routine['DTD_IDENTIFIER']) + ? $routine['DTD_IDENTIFIER'] : ""; + $ret[] = $one_result; + } + + // Sort results by name + $name = array(); + foreach ($ret as $value) { + $name[] = $value['name']; + } + array_multisort($name, SORT_ASC, $ret); + + return($ret); + } + + /** + * returns details about the EVENTs for a specific database + * + * @param string $db db name + * @param string $name event name + * + * @return array information about EVENTs + */ + public function getEvents($db, $name = '') + { + if (! $GLOBALS['cfg']['Server']['DisableIS']) { + $query = "SELECT" + . " `EVENT_SCHEMA` AS `Db`," + . " `EVENT_NAME` AS `Name`," + . " `DEFINER` AS `Definer`," + . " `TIME_ZONE` AS `Time zone`," + . " `EVENT_TYPE` AS `Type`," + . " `EXECUTE_AT` AS `Execute at`," + . " `INTERVAL_VALUE` AS `Interval value`," + . " `INTERVAL_FIELD` AS `Interval field`," + . " `STARTS` AS `Starts`," + . " `ENDS` AS `Ends`," + . " `STATUS` AS `Status`," + . " `ORIGINATOR` AS `Originator`," + . " `CHARACTER_SET_CLIENT` AS `character_set_client`," + . " `COLLATION_CONNECTION` AS `collation_connection`, " + . "`DATABASE_COLLATION` AS `Database Collation`" + . " FROM `information_schema`.`EVENTS`" + . " WHERE `EVENT_SCHEMA` " . Util::getCollateForIS() + . " = '" . $GLOBALS['dbi']->escapeString($db) . "'"; + if (! empty($name)) { + $query .= " AND `EVENT_NAME`" + . " = '" . $GLOBALS['dbi']->escapeString($name) . "'"; + } + } else { + $query = "SHOW EVENTS FROM " . Util::backquote($db); + if (! empty($name)) { + $query .= " AND `Name` = '" + . $GLOBALS['dbi']->escapeString($name) . "'"; + } + } + + $result = array(); + if ($events = $this->fetchResult($query)) { + foreach ($events as $event) { + $one_result = array(); + $one_result['name'] = $event['Name']; + $one_result['type'] = $event['Type']; + $one_result['status'] = $event['Status']; + $result[] = $one_result; + } + } + + // Sort results by name + $name = array(); + foreach ($result as $value) { + $name[] = $value['name']; + } + array_multisort($name, SORT_ASC, $result); + + return $result; + } + + /** + * returns details about the TRIGGERs for a specific table or database + * + * @param string $db db name + * @param string $table table name + * @param string $delimiter the delimiter to use (may be empty) + * + * @return array information about triggers (may be empty) + */ + public function getTriggers($db, $table = '', $delimiter = '//') + { + $result = array(); + if (! $GLOBALS['cfg']['Server']['DisableIS']) { + $query = 'SELECT TRIGGER_SCHEMA, TRIGGER_NAME, EVENT_MANIPULATION' + . ', EVENT_OBJECT_TABLE, ACTION_TIMING, ACTION_STATEMENT' + . ', EVENT_OBJECT_SCHEMA, EVENT_OBJECT_TABLE, DEFINER' + . ' FROM information_schema.TRIGGERS' + . ' WHERE EVENT_OBJECT_SCHEMA ' . Util::getCollateForIS() . '=' + . ' \'' . $GLOBALS['dbi']->escapeString($db) . '\''; + + if (! empty($table)) { + $query .= " AND EVENT_OBJECT_TABLE " . Util::getCollateForIS() + . " = '" . $GLOBALS['dbi']->escapeString($table) . "';"; + } + } else { + $query = "SHOW TRIGGERS FROM " . Util::backquote($db); + if (! empty($table)) { + $query .= " LIKE '" . $GLOBALS['dbi']->escapeString($table) . "';"; + } + } + + if ($triggers = $this->fetchResult($query)) { + foreach ($triggers as $trigger) { + if ($GLOBALS['cfg']['Server']['DisableIS']) { + $trigger['TRIGGER_NAME'] = $trigger['Trigger']; + $trigger['ACTION_TIMING'] = $trigger['Timing']; + $trigger['EVENT_MANIPULATION'] = $trigger['Event']; + $trigger['EVENT_OBJECT_TABLE'] = $trigger['Table']; + $trigger['ACTION_STATEMENT'] = $trigger['Statement']; + $trigger['DEFINER'] = $trigger['Definer']; + } + $one_result = array(); + $one_result['name'] = $trigger['TRIGGER_NAME']; + $one_result['table'] = $trigger['EVENT_OBJECT_TABLE']; + $one_result['action_timing'] = $trigger['ACTION_TIMING']; + $one_result['event_manipulation'] = $trigger['EVENT_MANIPULATION']; + $one_result['definition'] = $trigger['ACTION_STATEMENT']; + $one_result['definer'] = $trigger['DEFINER']; + + // do not prepend the schema name; this way, importing the + // definition into another schema will work + $one_result['full_trigger_name'] = Util::backquote( + $trigger['TRIGGER_NAME'] + ); + $one_result['drop'] = 'DROP TRIGGER IF EXISTS ' + . $one_result['full_trigger_name']; + $one_result['create'] = 'CREATE TRIGGER ' + . $one_result['full_trigger_name'] . ' ' + . $trigger['ACTION_TIMING'] . ' ' + . $trigger['EVENT_MANIPULATION'] + . ' ON ' . Util::backquote($trigger['EVENT_OBJECT_TABLE']) + . "\n" . ' FOR EACH ROW ' + . $trigger['ACTION_STATEMENT'] . "\n" . $delimiter . "\n"; + + $result[] = $one_result; + } + } + + // Sort results by name + $name = array(); + foreach ($result as $value) { + $name[] = $value['name']; + } + array_multisort($name, SORT_ASC, $result); + + return($result); + } + + /** + * Formats database error message in a friendly way. + * This is needed because some errors messages cannot + * be obtained by mysql_error(). + * + * @param int $error_number Error code + * @param string $error_message Error message as returned by server + * + * @return string HML text with error details + */ + public static function formatError($error_number, $error_message) + { + $error_message = htmlspecialchars($error_message); + + $error = '#' . ((string) $error_number); + $separator = ' — '; + + if ($error_number == 2002) { + $error .= ' - ' . $error_message; + $error .= $separator; + $error .= __( + 'The server is not responding (or the local server\'s socket' + . ' is not correctly configured).' + ); + } elseif ($error_number == 2003) { + $error .= ' - ' . $error_message; + $error .= $separator . __('The server is not responding.'); + } elseif ($error_number == 1698 ) { + $error .= ' - ' . $error_message; + $error .= $separator . ''; + $error .= __('Logout and try as another user.') . ''; + } elseif ($error_number == 1005) { + if (strpos($error_message, 'errno: 13') !== false) { + $error .= ' - ' . $error_message; + $error .= $separator + . __( + 'Please check privileges of directory containing database.' + ); + } else { + /* InnoDB constraints, see + * https://dev.mysql.com/doc/refman/5.0/en/ + * innodb-foreign-key-constraints.html + */ + $error .= ' - ' . $error_message . + ' (' . __('Details…') . ')'; + } + } else { + $error .= ' - ' . $error_message; + } + + return $error; + } + + /** + * gets the current user with host + * + * @return string the current user i.e. user@host + */ + public function getCurrentUser() + { + if (Util::cacheExists('mysql_cur_user')) { + return Util::cacheGet('mysql_cur_user'); + } + $user = $this->fetchValue('SELECT CURRENT_USER();'); + if ($user !== false) { + Util::cacheSet('mysql_cur_user', $user); + return $user; + } + return '@'; + } + + /** + * Checks if current user is superuser + * + * @return bool Whether user is a superuser + */ + public function isSuperuser() + { + return self::isUserType('super'); + } + + /** + * Checks if current user has global create user/grant privilege + * or is a superuser (i.e. SELECT on mysql.users) + * while caching the result in session. + * + * @param string $type type of user to check for + * i.e. 'create', 'grant', 'super' + * + * @return bool Whether user is a given type of user + */ + public function isUserType($type) + { + if (Util::cacheExists('is_' . $type . 'user')) { + return Util::cacheGet('is_' . $type . 'user'); + } + + // when connection failed we don't have a $userlink + if (! isset($this->_links[DatabaseInterface::CONNECT_USER])) { + return false; + } + + // checking if user is logged in + if ($type === 'logged') { + return true; + } + + if (! $GLOBALS['cfg']['Server']['DisableIS'] || $type === 'super') { + // Prepare query for each user type check + $query = ''; + if ($type === 'super') { + $query = 'SELECT 1 FROM mysql.user LIMIT 1'; + } elseif ($type === 'create') { + list($user, $host) = $this->getCurrentUserAndHost(); + $query = "SELECT 1 FROM `INFORMATION_SCHEMA`.`USER_PRIVILEGES` " + . "WHERE `PRIVILEGE_TYPE` = 'CREATE USER' AND " + . "'''" . $user . "''@''" . $host . "''' LIKE `GRANTEE` LIMIT 1"; + } elseif ($type === 'grant') { + list($user, $host) = $this->getCurrentUserAndHost(); + $query = "SELECT 1 FROM (" + . "SELECT `GRANTEE`, `IS_GRANTABLE` FROM " + . "`INFORMATION_SCHEMA`.`COLUMN_PRIVILEGES` UNION " + . "SELECT `GRANTEE`, `IS_GRANTABLE` FROM " + . "`INFORMATION_SCHEMA`.`TABLE_PRIVILEGES` UNION " + . "SELECT `GRANTEE`, `IS_GRANTABLE` FROM " + . "`INFORMATION_SCHEMA`.`SCHEMA_PRIVILEGES` UNION " + . "SELECT `GRANTEE`, `IS_GRANTABLE` FROM " + . "`INFORMATION_SCHEMA`.`USER_PRIVILEGES`) t " + . "WHERE `IS_GRANTABLE` = 'YES' AND " + . "'''" . $user . "''@''" . $host . "''' LIKE `GRANTEE` LIMIT 1"; + } + + $is = false; + $result = $this->tryQuery( + $query, + self::CONNECT_USER, + self::QUERY_STORE + ); + if ($result) { + $is = (bool) $this->numRows($result); + } + $this->freeResult($result); + } else { + $is = false; + $grants = $this->fetchResult( + "SHOW GRANTS FOR CURRENT_USER();", + null, + null, + self::CONNECT_USER, + self::QUERY_STORE + ); + if ($grants) { + foreach ($grants as $grant) { + if ($type === 'create') { + if (strpos($grant, "ALL PRIVILEGES ON *.*") !== false + || strpos($grant, "CREATE USER") !== false + ) { + $is = true; + break; + } + } elseif ($type === 'grant') { + if (strpos($grant, "WITH GRANT OPTION") !== false) { + $is = true; + break; + } + } + } + } + } + + Util::cacheSet('is_' . $type . 'user', $is); + return $is; + } + + /** + * Get the current user and host + * + * @return array array of username and hostname + */ + public function getCurrentUserAndHost() + { + if (count($this->_current_user) == 0) { + $user = $this->getCurrentUser(); + $this->_current_user = explode("@", $user); + } + return $this->_current_user; + } + + /** + * Returns value for lower_case_table_names variable + * + * @return string + */ + public function getLowerCaseNames() + { + if (is_null($this->_lower_case_table_names)) { + $this->_lower_case_table_names = $this->fetchValue( + "SELECT @@lower_case_table_names" + ); + } + return $this->_lower_case_table_names; + } + + /** + * Get the list of system schemas + * + * @return array list of system schemas + */ + public function getSystemSchemas() + { + $schemas = array( + 'information_schema', 'performance_schema', 'mysql', 'sys' + ); + $systemSchemas = array(); + foreach ($schemas as $schema) { + if ($this->isSystemSchema($schema, true)) { + $systemSchemas[] = $schema; + } + } + return $systemSchemas; + } + + /** + * Checks whether given schema is a system schema + * + * @param string $schema_name Name of schema (database) to test + * @param bool $testForMysqlSchema Whether 'mysql' schema should + * be treated the same as IS and DD + * + * @return bool + */ + public function isSystemSchema($schema_name, $testForMysqlSchema = false) + { + $schema_name = strtolower($schema_name); + return $schema_name == 'information_schema' + || $schema_name == 'performance_schema' + || ($schema_name == 'mysql' && $testForMysqlSchema) + || $schema_name == 'sys'; + } + + /** + * Return connection parameters for the database server + * + * @param integer $mode Connection mode on of CONNECT_USER, CONNECT_CONTROL + * or CONNECT_AUXILIARY. + * @param array|null $server Server information like host/port/socket/persistent + * + * @return array user, host and server settings array + */ + public function getConnectionParams($mode, $server = null) + { + global $cfg; + + $user = null; + $password = null; + + if ($mode == DatabaseInterface::CONNECT_USER) { + $user = $cfg['Server']['user']; + $password = $cfg['Server']['password']; + $server = $cfg['Server']; + } elseif ($mode == DatabaseInterface::CONNECT_CONTROL) { + $user = $cfg['Server']['controluser']; + $password = $cfg['Server']['controlpass']; + + $server = array(); + + if (! empty($cfg['Server']['controlhost'])) { + $server['host'] = $cfg['Server']['controlhost']; + } else { + $server['host'] = $cfg['Server']['host']; + } + // Share the settings if the host is same + if ($server['host'] == $cfg['Server']['host']) { + $shared = array( + 'port', 'socket', 'compress', + 'ssl', 'ssl_key', 'ssl_cert', 'ssl_ca', + 'ssl_ca_path', 'ssl_ciphers', 'ssl_verify', + ); + foreach ($shared as $item) { + if (isset($cfg['Server'][$item])) { + $server[$item] = $cfg['Server'][$item]; + } + } + } + // Set configured port + if (! empty($cfg['Server']['controlport'])) { + $server['port'] = $cfg['Server']['controlport']; + } + // Set any configuration with control_ prefix + foreach ($cfg['Server'] as $key => $val) { + if (substr($key, 0, 8) === 'control_') { + $server[substr($key, 8)] = $val; + } + } + } else { + if (is_null($server)) { + return array(null, null, null); + } + if (isset($server['user'])) { + $user = $server['user']; + } + if (isset($server['password'])) { + $password = $server['password']; + } + } + + // Perform sanity checks on some variables + if (empty($server['port'])) { + $server['port'] = 0; + } else { + $server['port'] = intval($server['port']); + } + if (empty($server['socket'])) { + $server['socket'] = null; + } + if (empty($server['host'])) { + $server['host'] = 'localhost'; + } + if (!isset($server['ssl'])) { + $server['ssl'] = false; + } + if (!isset($server['compress'])) { + $server['compress'] = false; + } + + return array($user, $password, $server); + } + + /** + * connects to the database server + * + * @param integer $mode Connection mode on of CONNECT_USER, CONNECT_CONTROL + * or CONNECT_AUXILIARY. + * @param array|null $server Server information like host/port/socket/persistent + * @param integer $target How to store connection link, defaults to $mode + * + * @return mixed false on error or a connection object on success + */ + public function connect($mode, $server = null, $target = null) + { + list($user, $password, $server) = $this->getConnectionParams($mode, $server); + + if (is_null($target)) { + $target = $mode; + } + + if (is_null($user) || is_null($password)) { + trigger_error( + __('Missing connection parameters!'), + E_USER_WARNING + ); + return false; + } + + // Do not show location and backtrace for connection errors + $GLOBALS['error_handler']->setHideLocation(true); + $result = $this->_extension->connect( + $user, $password, $server + ); + $GLOBALS['error_handler']->setHideLocation(false); + + if ($result) { + $this->_links[$target] = $result; + /* Run post connect for user connections */ + if ($target == DatabaseInterface::CONNECT_USER) { + $this->postConnect(); + } elseif ($target == DatabaseInterface::CONNECT_CONTROL) { + $this->postConnectControl(); + } + return $result; + } + + if ($mode == DatabaseInterface::CONNECT_CONTROL) { + trigger_error( + __( + 'Connection for controluser as defined in your ' + . 'configuration failed.' + ), + E_USER_WARNING + ); + return false; + } elseif ($mode == DatabaseInterface::CONNECT_AUXILIARY) { + // Do not go back to main login if connection failed + // (currently used only in unit testing) + return false; + } + + return $result; + } + + /** + * selects given database + * + * @param string $dbname database name to select + * @param integer $link link type + * + * @return boolean + */ + public function selectDb($dbname, $link = DatabaseInterface::CONNECT_USER) + { + if (! isset($this->_links[$link])) { + return false; + } + return $this->_extension->selectDb($dbname, $this->_links[$link]); + } + + /** + * returns array of rows with associative and numeric keys from $result + * + * @param object $result result set identifier + * + * @return array + */ + public function fetchArray($result) + { + return $this->_extension->fetchArray($result); + } + + /** + * returns array of rows with associative keys from $result + * + * @param object $result result set identifier + * + * @return array + */ + public function fetchAssoc($result) + { + return $this->_extension->fetchAssoc($result); + } + + /** + * returns array of rows with numeric keys from $result + * + * @param object $result result set identifier + * + * @return array + */ + public function fetchRow($result) + { + return $this->_extension->fetchRow($result); + } + + /** + * Adjusts the result pointer to an arbitrary row in the result + * + * @param object $result database result + * @param integer $offset offset to seek + * + * @return bool true on success, false on failure + */ + public function dataSeek($result, $offset) + { + return $this->_extension->dataSeek($result, $offset); + } + + /** + * Frees memory associated with the result + * + * @param object $result database result + * + * @return void + */ + public function freeResult($result) + { + $this->_extension->freeResult($result); + } + + /** + * Check if there are any more query results from a multi query + * + * @param integer $link link type + * + * @return bool true or false + */ + public function moreResults($link = DatabaseInterface::CONNECT_USER) + { + if (! isset($this->_links[$link])) { + return false; + } + return $this->_extension->moreResults($this->_links[$link]); + } + + /** + * Prepare next result from multi_query + * + * @param integer $link link type + * + * @return bool true or false + */ + public function nextResult($link = DatabaseInterface::CONNECT_USER) + { + if (! isset($this->_links[$link])) { + return false; + } + return $this->_extension->nextResult($this->_links[$link]); + } + + /** + * Store the result returned from multi query + * + * @param integer $link link type + * + * @return mixed false when empty results / result set when not empty + */ + public function storeResult($link = DatabaseInterface::CONNECT_USER) + { + if (! isset($this->_links[$link])) { + return false; + } + return $this->_extension->storeResult($this->_links[$link]); + } + + /** + * Returns a string representing the type of connection used + * + * @param integer $link link type + * + * @return string type of connection used + */ + public function getHostInfo($link = DatabaseInterface::CONNECT_USER) + { + if (! isset($this->_links[$link])) { + return false; + } + return $this->_extension->getHostInfo($this->_links[$link]); + } + + /** + * Returns the version of the MySQL protocol used + * + * @param integer $link link type + * + * @return integer version of the MySQL protocol used + */ + public function getProtoInfo($link = DatabaseInterface::CONNECT_USER) + { + if (! isset($this->_links[$link])) { + return false; + } + return $this->_extension->getProtoInfo($this->_links[$link]); + } + + /** + * returns a string that represents the client library version + * + * @return string MySQL client library version + */ + public function getClientInfo() + { + return $this->_extension->getClientInfo(); + } + + /** + * returns last error message or false if no errors occurred + * + * @param integer $link link type + * + * @return string|bool $error or false + */ + public function getError($link = DatabaseInterface::CONNECT_USER) + { + if (! isset($this->_links[$link])) { + return false; + } + return $this->_extension->getError($this->_links[$link]); + } + + /** + * returns the number of rows returned by last query + * + * @param object $result result set identifier + * + * @return string|int + */ + public function numRows($result) + { + return $this->_extension->numRows($result); + } + + /** + * returns last inserted auto_increment id for given $link + * or $GLOBALS['userlink'] + * + * @param integer $link link type + * + * @return int|boolean + */ + public function insertId($link = DatabaseInterface::CONNECT_USER) + { + // If the primary key is BIGINT we get an incorrect result + // (sometimes negative, sometimes positive) + // and in the present function we don't know if the PK is BIGINT + // so better play safe and use LAST_INSERT_ID() + // + // When no controluser is defined, using mysqli_insert_id($link) + // does not always return the last insert id due to a mixup with + // the tracking mechanism, but this works: + return $this->fetchValue('SELECT LAST_INSERT_ID();', 0, 0, $link); + } + + /** + * returns the number of rows affected by last query + * + * @param integer $link link type + * @param bool $get_from_cache whether to retrieve from cache + * + * @return int|boolean + */ + public function affectedRows($link = DatabaseInterface::CONNECT_USER, $get_from_cache = true) + { + if (! isset($this->_links[$link])) { + return false; + } + + if ($get_from_cache) { + return $GLOBALS['cached_affected_rows']; + } + + return $this->_extension->affectedRows($this->_links[$link]); + } + + /** + * returns metainfo for fields in $result + * + * @param object $result result set identifier + * + * @return array meta info for fields in $result + */ + public function getFieldsMeta($result) + { + $result = $this->_extension->getFieldsMeta($result); + + if ($this->getLowerCaseNames() === '2') { + /** + * Fixup orgtable for lower_case_table_names = 2 + * + * In this setup MySQL server reports table name lower case + * but we still need to operate on original case to properly + * match existing strings + */ + foreach ($result as $value) { + if (strlen($value->orgtable) !== 0 && + mb_strtolower($value->orgtable) === mb_strtolower($value->table)) { + $value->orgtable = $value->table; + } + } + } + + return $result; + } + + /** + * return number of fields in given $result + * + * @param object $result result set identifier + * + * @return int field count + */ + public function numFields($result) + { + return $this->_extension->numFields($result); + } + + /** + * returns the length of the given field $i in $result + * + * @param object $result result set identifier + * @param int $i field + * + * @return int length of field + */ + public function fieldLen($result, $i) + { + return $this->_extension->fieldLen($result, $i); + } + + /** + * returns name of $i. field in $result + * + * @param object $result result set identifier + * @param int $i field + * + * @return string name of $i. field in $result + */ + public function fieldName($result, $i) + { + return $this->_extension->fieldName($result, $i); + } + + /** + * returns concatenated string of human readable field flags + * + * @param object $result result set identifier + * @param int $i field + * + * @return string field flags + */ + public function fieldFlags($result, $i) + { + return $this->_extension->fieldFlags($result, $i); + } + + /** + * returns properly escaped string for use in MySQL queries + * + * @param string $str string to be escaped + * @param mixed $link optional database link to use + * + * @return string a MySQL escaped string + */ + public function escapeString($str, $link = DatabaseInterface::CONNECT_USER) + { + if ($this->_extension === null || !isset($this->_links[$link])) { + return $str; + } + + return $this->_extension->escapeString($this->_links[$link], $str); + } + + /** + * Checks if this database server is running on Amazon RDS. + * + * @return boolean + */ + public function isAmazonRds() + { + if (Util::cacheExists('is_amazon_rds')) { + return Util::cacheGet('is_amazon_rds'); + } + $sql = 'SELECT @@basedir'; + $result = $this->fetchValue($sql); + $rds = (substr($result, 0, 10) == '/rdsdbbin/'); + Util::cacheSet('is_amazon_rds', $rds); + + return $rds; + } + + /** + * Gets SQL for killing a process. + * + * @param int $process Process ID + * + * @return string + */ + public function getKillQuery($process) + { + if ($this->isAmazonRds()) { + return 'CALL mysql.rds_kill(' . $process . ');'; + } + + return 'KILL ' . $process . ';'; + } + + /** + * Get the phpmyadmin database manager + * + * @return SystemDatabase + */ + public function getSystemDatabase() + { + return new SystemDatabase($this); + } + + /** + * Get a table with database name and table name + * + * @param string $db_name DB name + * @param string $table_name Table name + * + * @return Table + */ + public function getTable($db_name, $table_name) + { + return new Table($table_name, $db_name, $this); + } + + /** + * returns collation of given db + * + * @param string $db name of db + * + * @return string collation of $db + */ + public function getDbCollation($db) + { + if ($this->isSystemSchema($db)) { + // We don't have to check the collation of the virtual + // information_schema database: We know it! + return 'utf8_general_ci'; + } + + if (! $GLOBALS['cfg']['Server']['DisableIS']) { + // this is slow with thousands of databases + $sql = 'SELECT DEFAULT_COLLATION_NAME FROM information_schema.SCHEMATA' + . ' WHERE SCHEMA_NAME = \'' . $this->escapeString($db) + . '\' LIMIT 1'; + return $this->fetchValue($sql); + } + + $this->selectDb($db); + $return = $this->fetchValue('SELECT @@collation_database'); + if ($db !== $GLOBALS['db']) { + $this->selectDb($GLOBALS['db']); + } + return $return; + } + + /** + * returns default server collation from show variables + * + * @return string $server_collation + */ + function getServerCollation() + { + return $this->fetchValue('SELECT @@collation_server'); + } + + /** + * Server version as number + * + * @return integer + */ + public function getVersion() + { + return $this->_version_int; + } + + /** + * Server version + * + * @return string + */ + public function getVersionString() + { + return $this->_version_str; + } + + /** + * Server version comment + * + * @return string + */ + public function getVersionComment() + { + return $this->_version_comment; + } + + /** + * Whether connection is MariaDB + * + * @return boolean + */ + public function isMariaDB() + { + return $this->_is_mariadb; + } + + /** + * Whether connection is Percona + * + * @return boolean + */ + public function isPercona() + { + return $this->_is_percona; + } + + /** + * Load correct database driver + * + * @return void + */ + public static function load() + { + if (defined('TESTSUITE')) { + /** + * For testsuite we use dummy driver which can fake some queries. + */ + $extension = new DbiDummy(); + } else { + + /** + * First check for the mysqli extension, as it's the one recommended + * for the MySQL server's version that we support + * (if PHP 7+, it's the only one supported) + */ + $extension = 'mysqli'; + if (! self::checkDbExtension($extension)) { + + $docurl = Util::getDocuLink('faq', 'faqmysql'); + $doclink = sprintf( + __('See %sour documentation%s for more information.'), + '[a@' . $docurl . '@documentation]', + '[/a]' + ); + + if (PHP_VERSION_ID < 70000) { + $extension = 'mysql'; + if (! self::checkDbExtension($extension)) { + // warn about both extensions missing and exit + Core::warnMissingExtension( + 'mysqli', + true, + $doclink + ); + } elseif (empty($_SESSION['mysqlwarning'])) { + trigger_error( + __( + 'You are using the mysql extension which is deprecated in ' + . 'phpMyAdmin. Please consider installing the mysqli ' + . 'extension.' + ) . ' ' . $doclink, + E_USER_WARNING + ); + // tell the user just once per session + $_SESSION['mysqlwarning'] = true; + } + } else { + // mysql extension is not part of PHP 7+, so warn and exit + Core::warnMissingExtension( + 'mysqli', + true, + $doclink + ); + } + } + + /** + * Including The DBI Plugin + */ + switch($extension) { + case 'mysql' : + $extension = new DbiMysql(); + break; + case 'mysqli' : + $extension = new DbiMysqli(); + break; + } + } + $GLOBALS['dbi'] = new DatabaseInterface($extension); + + $container = Container::getDefaultContainer(); + $container->set('PMA_DatabaseInterface', $GLOBALS['dbi']); + $container->alias('dbi', 'PMA_DatabaseInterface'); + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Dbi/DbiDummy.php b/php/apps/phpmyadmin49/html/libraries/classes/Dbi/DbiDummy.php new file mode 100644 index 00000000..35cd9a0e --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Dbi/DbiDummy.php @@ -0,0 +1,454 @@ +_queries); $i < $nb; $i++) { + if ($this->_queries[$i]['query'] != $query) { + continue; + } + + $this->_queries[$i]['pos'] = 0; + if (!is_array($this->_queries[$i]['result'])) { + return false; + } + + return $i; + } + for ($i = 0, $nb = count($GLOBALS['dummy_queries']); $i < $nb; $i++) { + if ($GLOBALS['dummy_queries'][$i]['query'] != $query) { + continue; + } + + $GLOBALS['dummy_queries'][$i]['pos'] = 0; + if (!is_array($GLOBALS['dummy_queries'][$i]['result'])) { + return false; + } + + return $i + self::OFFSET_GLOBAL; + } + echo "Not supported query: $query\n"; + + return false; + } + + /** + * Run the multi query and output the results + * + * @param resource $link connection object + * @param string $query multi query statement to execute + * + * @return array|bool + */ + public function realMultiQuery($link, $query) + { + return false; + } + + /** + * returns result data from $result + * + * @param object $result MySQL result + * + * @return array + */ + public function fetchAny($result) + { + $query_data = &$this->getQueryData($result); + if ($query_data['pos'] >= count($query_data['result'])) { + return false; + } + $ret = $query_data['result'][$query_data['pos']]; + $query_data['pos'] += 1; + + return $ret; + } + + /** + * returns array of rows with associative and numeric keys from $result + * + * @param object $result result MySQL result + * + * @return array + */ + public function fetchArray($result) + { + $query_data = &$this->getQueryData($result); + $data = $this->fetchAny($result); + if (!is_array($data) + || !isset($query_data['columns']) + ) { + return $data; + } + + foreach ($data as $key => $val) { + $data[$query_data['columns'][$key]] = $val; + } + + return $data; + } + + /** + * returns array of rows with associative keys from $result + * + * @param object $result MySQL result + * + * @return array + */ + public function fetchAssoc($result) + { + $data = $this->fetchAny($result); + $query_data = &$this->getQueryData($result); + if (!is_array($data) || !isset($query_data['columns'])) { + return $data; + } + + $ret = array(); + foreach ($data as $key => $val) { + $ret[$query_data['columns'][$key]] = $val; + } + + return $ret; + } + + /** + * returns array of rows with numeric keys from $result + * + * @param object $result MySQL result + * + * @return array + */ + public function fetchRow($result) + { + $data = $this->fetchAny($result); + + return $data; + } + + /** + * Adjusts the result pointer to an arbitrary row in the result + * + * @param object $result database result + * @param integer $offset offset to seek + * + * @return bool true on success, false on failure + */ + public function dataSeek($result, $offset) + { + $query_data = &$this->getQueryData($result); + if ($offset > count($query_data['result'])) { + return false; + } + $query_data['pos'] = $offset; + + return true; + } + + /** + * Frees memory associated with the result + * + * @param object $result database result + * + * @return void + */ + public function freeResult($result) + { + return; + } + + /** + * Check if there are any more query results from a multi query + * + * @param resource $link the connection object + * + * @return bool false + */ + public function moreResults($link) + { + return false; + } + + /** + * Prepare next result from multi_query + * + * @param resource $link the connection object + * + * @return boolean false + */ + public function nextResult($link) + { + return false; + } + + /** + * Store the result returned from multi query + * + * @param resource $link the connection object + * + * @return mixed false when empty results / result set when not empty + */ + public function storeResult($link) + { + return false; + } + + /** + * Returns a string representing the type of connection used + * + * @param resource $link mysql link + * + * @return string type of connection used + */ + public function getHostInfo($link) + { + return ''; + } + + /** + * Returns the version of the MySQL protocol used + * + * @param resource $link mysql link + * + * @return integer version of the MySQL protocol used + */ + public function getProtoInfo($link) + { + return -1; + } + + /** + * returns a string that represents the client library version + * + * @return string MySQL client library version + */ + public function getClientInfo() + { + return ''; + } + + /** + * returns last error message or false if no errors occurred + * + * @param resource $link connection link + * + * @return string|bool $error or false + */ + public function getError($link) + { + return false; + } + + /** + * returns the number of rows returned by last query + * + * @param object $result MySQL result + * + * @return string|int + */ + public function numRows($result) + { + if (is_bool($result)) { + return 0; + } + + $query_data = &$this->getQueryData($result); + + return count($query_data['result']); + } + + /** + * returns the number of rows affected by last query + * + * @param resource $link the mysql object + * @param bool $get_from_cache whether to retrieve from cache + * + * @return string|int + */ + public function affectedRows($link = null, $get_from_cache = true) + { + return 0; + } + + /** + * returns metainfo for fields in $result + * + * @param object $result result set identifier + * + * @return array meta info for fields in $result + */ + public function getFieldsMeta($result) + { + return array(); + } + + /** + * return number of fields in given $result + * + * @param object $result MySQL result + * + * @return int field count + */ + public function numFields($result) + { + $query_data = &$this->getQueryData($result); + if (!isset($query_data['columns'])) { + return 0; + } + + return count($query_data['columns']); + } + + /** + * returns the length of the given field $i in $result + * + * @param object $result result set identifier + * @param int $i field + * + * @return int length of field + */ + public function fieldLen($result, $i) + { + return -1; + } + + /** + * returns name of $i. field in $result + * + * @param object $result result set identifier + * @param int $i field + * + * @return string name of $i. field in $result + */ + public function fieldName($result, $i) + { + return ''; + } + + /** + * returns concatenated string of human readable field flags + * + * @param object $result result set identifier + * @param int $i field + * + * @return string field flags + */ + public function fieldFlags($result, $i) + { + return ''; + } + + /** + * returns properly escaped string for use in MySQL queries + * + * @param mixed $link database link + * @param string $str string to be escaped + * + * @return string a MySQL escaped string + */ + public function escapeString($link, $str) + { + return $str; + } + + /** + * Adds query result for testing + * + * @param string $query SQL + * @param array $result Expected result + * + * @return void + */ + public function setResult($query, $result) + { + $this->_queries[] = array( + 'query' => $query, + 'result' => $result, + ); + } + + /** + * Return query data for ID + * + * @param object $result result set identifier + * + * @return array + */ + private function &getQueryData($result) + { + if ($result >= self::OFFSET_GLOBAL) { + return $GLOBALS['dummy_queries'][$result - self::OFFSET_GLOBAL]; + } else { + return $this->_queries[$result]; + } + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Dbi/DbiExtension.php b/php/apps/phpmyadmin49/html/libraries/classes/Dbi/DbiExtension.php new file mode 100644 index 00000000..b1b9ffae --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Dbi/DbiExtension.php @@ -0,0 +1,242 @@ +_realConnect($server_socket, $user, $password, null); + } else { + $link = $this->_realConnect( + $server['host'] . $server_port . $server_socket, + $user, $password, null + ); + } + return $link; + } + + /** + * selects given database + * + * @param string $dbname name of db to select + * @param resource|null $link mysql link resource + * + * @return bool + */ + public function selectDb($dbname, $link) + { + return mysql_select_db($dbname, $link); + } + + /** + * runs a query and returns the result + * + * @param string $query query to run + * @param resource|null $link mysql link resource + * @param int $options query options + * + * @return mixed + */ + public function realQuery($query, $link, $options) + { + if ($options == ($options | DatabaseInterface::QUERY_STORE)) { + return mysql_query($query, $link); + } elseif ($options == ($options | DatabaseInterface::QUERY_UNBUFFERED)) { + return mysql_unbuffered_query($query, $link); + } + + return mysql_query($query, $link); + } + + /** + * returns array of rows with associative and numeric keys from $result + * + * @param resource $result result MySQL result + * + * @return array + */ + public function fetchArray($result) + { + return mysql_fetch_array($result, MYSQL_BOTH); + } + + /** + * returns array of rows with associative keys from $result + * + * @param resource $result MySQL result + * + * @return array + */ + public function fetchAssoc($result) + { + return mysql_fetch_array($result, MYSQL_ASSOC); + } + + /** + * returns array of rows with numeric keys from $result + * + * @param resource $result MySQL result + * + * @return array + */ + public function fetchRow($result) + { + return mysql_fetch_array($result, MYSQL_NUM); + } + + /** + * Adjusts the result pointer to an arbitrary row in the result + * + * @param resource $result database result + * @param integer $offset offset to seek + * + * @return bool true on success, false on failure + */ + public function dataSeek($result, $offset) + { + return mysql_data_seek($result, $offset); + } + + /** + * Frees memory associated with the result + * + * @param resource $result database result + * + * @return void + */ + public function freeResult($result) + { + if (is_resource($result) && get_resource_type($result) === 'mysql result') { + mysql_free_result($result); + } + } + + /** + * Check if there are any more query results from a multi query + * + * @param resource $link the connection object + * + * @return bool false + */ + public function moreResults($link) + { + // N.B.: PHP's 'mysql' extension does not support + // multi_queries so this function will always + // return false. Use the 'mysqli' extension, if + // you need support for multi_queries. + return false; + } + + /** + * Prepare next result from multi_query + * + * @param resource $link the connection object + * + * @return boolean false + */ + public function nextResult($link) + { + // N.B.: PHP's 'mysql' extension does not support + // multi_queries so this function will always + // return false. Use the 'mysqli' extension, if + // you need support for multi_queries. + return false; + } + + /** + * Returns a string representing the type of connection used + * + * @param resource|null $link mysql link + * + * @return string type of connection used + */ + public function getHostInfo($link) + { + return mysql_get_host_info($link); + } + + /** + * Returns the version of the MySQL protocol used + * + * @param resource|null $link mysql link + * + * @return int version of the MySQL protocol used + */ + public function getProtoInfo($link) + { + return mysql_get_proto_info($link); + } + + /** + * returns a string that represents the client library version + * + * @return string MySQL client library version + */ + public function getClientInfo() + { + return mysql_get_client_info(); + } + + /** + * returns last error message or false if no errors occurred + * + * @param resource|null $link mysql link + * + * @return string|bool $error or false + */ + public function getError($link) + { + $GLOBALS['errno'] = 0; + + if (null !== $link && false !== $link) { + $error_number = mysql_errno($link); + $error_message = mysql_error($link); + } else { + $error_number = mysql_errno(); + $error_message = mysql_error(); + } + if (0 == $error_number) { + return false; + } + + // keep the error number for further check after + // the call to getError() + $GLOBALS['errno'] = $error_number; + + return $GLOBALS['dbi']->formatError($error_number, $error_message); + } + + /** + * returns the number of rows returned by last query + * + * @param resource $result MySQL result + * + * @return string|int + */ + public function numRows($result) + { + if (is_bool($result)) { + return 0; + } + + return mysql_num_rows($result); + } + + /** + * returns the number of rows affected by last query + * + * @param resource|null $link the mysql object + * + * @return int + */ + public function affectedRows($link) + { + return mysql_affected_rows($link); + } + + /** + * returns metainfo for fields in $result + * + * @param resource $result MySQL result + * + * @return array meta info for fields in $result + * + * @todo add missing keys like in mysqli_query (decimals) + */ + public function getFieldsMeta($result) + { + $fields = array(); + $num_fields = mysql_num_fields($result); + for ($i = 0; $i < $num_fields; $i++) { + $field = mysql_fetch_field($result, $i); + $field->flags = mysql_field_flags($result, $i); + $field->orgtable = mysql_field_table($result, $i); + $field->orgname = mysql_field_name($result, $i); + $fields[] = $field; + } + return $fields; + } + + /** + * return number of fields in given $result + * + * @param resource $result MySQL result + * + * @return int field count + */ + public function numFields($result) + { + return mysql_num_fields($result); + } + + /** + * returns the length of the given field $i in $result + * + * @param resource $result MySQL result + * @param int $i field + * + * @return int length of field + */ + public function fieldLen($result, $i) + { + return mysql_field_len($result, $i); + } + + /** + * returns name of $i. field in $result + * + * @param resource $result MySQL result + * @param int $i field + * + * @return string name of $i. field in $result + */ + public function fieldName($result, $i) + { + return mysql_field_name($result, $i); + } + + /** + * returns concatenated string of human readable field flags + * + * @param resource $result MySQL result + * @param int $i field + * + * @return string field flags + */ + public function fieldFlags($result, $i) + { + return mysql_field_flags($result, $i); + } + + /** + * Store the result returned from multi query + * + * @param resource $result MySQL result + * + * @return false + */ + public function storeResult($result) + { + return false; + } + + /** + * returns properly escaped string for use in MySQL queries + * + * @param mixed $link database link + * @param string $str string to be escaped + * + * @return string a MySQL escaped string + */ + public function escapeString($link, $str) + { + return mysql_real_escape_string($str, $link); + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Dbi/DbiMysqli.php b/php/apps/phpmyadmin49/html/libraries/classes/Dbi/DbiMysqli.php new file mode 100644 index 00000000..24c5bfda --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Dbi/DbiMysqli.php @@ -0,0 +1,596 @@ + 'num', + MYSQLI_PART_KEY_FLAG => 'part_key', + MYSQLI_SET_FLAG => 'set', + MYSQLI_TIMESTAMP_FLAG => 'timestamp', + MYSQLI_AUTO_INCREMENT_FLAG => 'auto_increment', + MYSQLI_ENUM_FLAG => 'enum', + MYSQLI_ZEROFILL_FLAG => 'zerofill', + MYSQLI_UNSIGNED_FLAG => 'unsigned', + MYSQLI_BLOB_FLAG => 'blob', + MYSQLI_MULTIPLE_KEY_FLAG => 'multiple_key', + MYSQLI_UNIQUE_KEY_FLAG => 'unique_key', + MYSQLI_PRI_KEY_FLAG => 'primary_key', + MYSQLI_NOT_NULL_FLAG => 'not_null', + ); + + /** + * connects to the database server + * + * @param string $user mysql user name + * @param string $password mysql user password + * @param array $server host/port/socket/persistent + * + * @return mixed false on error or a mysqli object on success + */ + public function connect( + $user, $password, array $server + ) { + if ($server) { + $server['host'] = (empty($server['host'])) + ? 'localhost' + : $server['host']; + } + + // NULL enables connection to the default socket + + $link = mysqli_init(); + + $client_flags = 0; + + /* Optionally compress connection */ + if ($server['compress'] && defined('MYSQLI_CLIENT_COMPRESS')) { + $client_flags |= MYSQLI_CLIENT_COMPRESS; + } + + /* Optionally enable SSL */ + if ($server['ssl']) { + $client_flags |= MYSQLI_CLIENT_SSL; + if (! empty($server['ssl_key']) || + ! empty($server['ssl_cert']) || + ! empty($server['ssl_ca']) || + ! empty($server['ssl_ca_path']) || + ! empty($server['ssl_ciphers']) + ) { + mysqli_ssl_set( + $link, + $server['ssl_key'], + $server['ssl_cert'], + $server['ssl_ca'], + $server['ssl_ca_path'], + $server['ssl_ciphers'] + ); + } + /* + * disables SSL certificate validation on mysqlnd for MySQL 5.6 or later + * @link https://bugs.php.net/bug.php?id=68344 + * @link https://github.com/phpmyadmin/phpmyadmin/pull/11838 + */ + if (! $server['ssl_verify']) { + mysqli_options( + $link, + MYSQLI_OPT_SSL_VERIFY_SERVER_CERT, + $server['ssl_verify'] + ); + $client_flags |= MYSQLI_CLIENT_SSL_DONT_VERIFY_SERVER_CERT; + } + } + + if ($GLOBALS['cfg']['PersistentConnections']) { + $host = 'p:' . $server['host']; + } else { + $host = $server['host']; + } + + $return_value = mysqli_real_connect( + $link, + $host, + $user, + $password, + '', + $server['port'], + $server['socket'], + $client_flags + ); + + if ($return_value === false || is_null($return_value)) { + /* + * Switch to SSL if server asked us to do so, unfortunately + * there are more ways MySQL server can tell this: + * + * - MySQL 8.0 and newer should return error 3159 + * - #2001 - SSL Connection is required. Please specify SSL options and retry. + * - #9002 - SSL connection is required. Please specify SSL options and retry. + */ + $error_number = mysqli_connect_errno(); + $error_message = mysqli_connect_error(); + if (! $server['ssl'] && ($error_number == 3159 || + (($error_number == 2001 || $error_number == 9002) && stripos($error_message, 'SSL Connection is required') !== false)) + ) { + trigger_error( + __('SSL connection enforced by server, automatically enabling it.'), + E_USER_WARNING + ); + $server['ssl'] = true; + return self::connect($user, $password, $server); + } + return false; + } + + if (defined('PMA_ENABLE_LDI')) { + mysqli_options($link, MYSQLI_OPT_LOCAL_INFILE, true); + } else { + mysqli_options($link, MYSQLI_OPT_LOCAL_INFILE, false); + } + + return $link; + } + + /** + * selects given database + * + * @param string $dbname database name to select + * @param mysqli $link the mysqli object + * + * @return boolean + */ + public function selectDb($dbname, $link) + { + return mysqli_select_db($link, $dbname); + } + + /** + * runs a query and returns the result + * + * @param string $query query to execute + * @param mysqli $link mysqli object + * @param int $options query options + * + * @return mysqli_result|bool + */ + public function realQuery($query, $link, $options) + { + if ($options == ($options | DatabaseInterface::QUERY_STORE)) { + $method = MYSQLI_STORE_RESULT; + } elseif ($options == ($options | DatabaseInterface::QUERY_UNBUFFERED)) { + $method = MYSQLI_USE_RESULT; + } else { + $method = 0; + } + + return mysqli_query($link, $query, $method); + } + + /** + * Run the multi query and output the results + * + * @param mysqli $link mysqli object + * @param string $query multi query statement to execute + * + * @return mysqli_result collection | boolean(false) + */ + public function realMultiQuery($link, $query) + { + return mysqli_multi_query($link, $query); + } + + /** + * returns array of rows with associative and numeric keys from $result + * + * @param mysqli_result $result result set identifier + * + * @return array + */ + public function fetchArray($result) + { + return mysqli_fetch_array($result, MYSQLI_BOTH); + } + + /** + * returns array of rows with associative keys from $result + * + * @param mysqli_result $result result set identifier + * + * @return array + */ + public function fetchAssoc($result) + { + return mysqli_fetch_array($result, MYSQLI_ASSOC); + } + + /** + * returns array of rows with numeric keys from $result + * + * @param mysqli_result $result result set identifier + * + * @return array + */ + public function fetchRow($result) + { + return mysqli_fetch_array($result, MYSQLI_NUM); + } + + /** + * Adjusts the result pointer to an arbitrary row in the result + * + * @param mysqli_result $result database result + * @param integer $offset offset to seek + * + * @return bool true on success, false on failure + */ + public function dataSeek($result, $offset) + { + return mysqli_data_seek($result, $offset); + } + + /** + * Frees memory associated with the result + * + * @param mysqli_result $result database result + * + * @return void + */ + public function freeResult($result) + { + if ($result instanceof mysqli_result) { + mysqli_free_result($result); + } + } + + /** + * Check if there are any more query results from a multi query + * + * @param mysqli $link the mysqli object + * + * @return bool true or false + */ + public function moreResults($link) + { + return mysqli_more_results($link); + } + + /** + * Prepare next result from multi_query + * + * @param mysqli $link the mysqli object + * + * @return bool true or false + */ + public function nextResult($link) + { + return mysqli_next_result($link); + } + + /** + * Store the result returned from multi query + * + * @param mysqli $link the mysqli object + * + * @return mixed false when empty results / result set when not empty + */ + public function storeResult($link) + { + return mysqli_store_result($link); + } + + /** + * Returns a string representing the type of connection used + * + * @param resource $link mysql link + * + * @return string type of connection used + */ + public function getHostInfo($link) + { + return mysqli_get_host_info($link); + } + + /** + * Returns the version of the MySQL protocol used + * + * @param resource $link mysql link + * + * @return integer version of the MySQL protocol used + */ + public function getProtoInfo($link) + { + return mysqli_get_proto_info($link); + } + + /** + * returns a string that represents the client library version + * + * @return string MySQL client library version + */ + public function getClientInfo() + { + return mysqli_get_client_info(); + } + + /** + * returns last error message or false if no errors occurred + * + * @param resource $link mysql link + * + * @return string|bool $error or false + */ + public function getError($link) + { + $GLOBALS['errno'] = 0; + + if (null !== $link && false !== $link) { + $error_number = mysqli_errno($link); + $error_message = mysqli_error($link); + } else { + $error_number = mysqli_connect_errno(); + $error_message = mysqli_connect_error(); + } + if (0 == $error_number) { + return false; + } + + // keep the error number for further check after + // the call to getError() + $GLOBALS['errno'] = $error_number; + + return $GLOBALS['dbi']->formatError($error_number, $error_message); + } + + /** + * returns the number of rows returned by last query + * + * @param mysqli_result $result result set identifier + * + * @return string|int + */ + public function numRows($result) + { + // see the note for tryQuery(); + if (is_bool($result)) { + return 0; + } + + return @mysqli_num_rows($result); + } + + /** + * returns the number of rows affected by last query + * + * @param mysqli $link the mysqli object + * + * @return int + */ + public function affectedRows($link) + { + return mysqli_affected_rows($link); + } + + /** + * returns metainfo for fields in $result + * + * @param mysqli_result $result result set identifier + * + * @return array meta info for fields in $result + */ + public function getFieldsMeta($result) + { + // Build an associative array for a type look up + $typeAr = array(); + $typeAr[MYSQLI_TYPE_DECIMAL] = 'real'; + $typeAr[MYSQLI_TYPE_NEWDECIMAL] = 'real'; + $typeAr[MYSQLI_TYPE_BIT] = 'int'; + $typeAr[MYSQLI_TYPE_TINY] = 'int'; + $typeAr[MYSQLI_TYPE_SHORT] = 'int'; + $typeAr[MYSQLI_TYPE_LONG] = 'int'; + $typeAr[MYSQLI_TYPE_FLOAT] = 'real'; + $typeAr[MYSQLI_TYPE_DOUBLE] = 'real'; + $typeAr[MYSQLI_TYPE_NULL] = 'null'; + $typeAr[MYSQLI_TYPE_TIMESTAMP] = 'timestamp'; + $typeAr[MYSQLI_TYPE_LONGLONG] = 'int'; + $typeAr[MYSQLI_TYPE_INT24] = 'int'; + $typeAr[MYSQLI_TYPE_DATE] = 'date'; + $typeAr[MYSQLI_TYPE_TIME] = 'time'; + $typeAr[MYSQLI_TYPE_DATETIME] = 'datetime'; + $typeAr[MYSQLI_TYPE_YEAR] = 'year'; + $typeAr[MYSQLI_TYPE_NEWDATE] = 'date'; + $typeAr[MYSQLI_TYPE_ENUM] = 'unknown'; + $typeAr[MYSQLI_TYPE_SET] = 'unknown'; + $typeAr[MYSQLI_TYPE_TINY_BLOB] = 'blob'; + $typeAr[MYSQLI_TYPE_MEDIUM_BLOB] = 'blob'; + $typeAr[MYSQLI_TYPE_LONG_BLOB] = 'blob'; + $typeAr[MYSQLI_TYPE_BLOB] = 'blob'; + $typeAr[MYSQLI_TYPE_VAR_STRING] = 'string'; + $typeAr[MYSQLI_TYPE_STRING] = 'string'; + // MySQL returns MYSQLI_TYPE_STRING for CHAR + // and MYSQLI_TYPE_CHAR === MYSQLI_TYPE_TINY + // so this would override TINYINT and mark all TINYINT as string + // see https://github.com/phpmyadmin/phpmyadmin/issues/8569 + //$typeAr[MYSQLI_TYPE_CHAR] = 'string'; + $typeAr[MYSQLI_TYPE_GEOMETRY] = 'geometry'; + $typeAr[MYSQLI_TYPE_BIT] = 'bit'; + $typeAr[MYSQLI_TYPE_JSON] = 'json'; + + $fields = mysqli_fetch_fields($result); + + // this happens sometimes (seen under MySQL 4.0.25) + if (!is_array($fields)) { + return false; + } + + foreach ($fields as $k => $field) { + $fields[$k]->_type = $field->type; + $fields[$k]->type = $typeAr[$field->type]; + $fields[$k]->_flags = $field->flags; + $fields[$k]->flags = $this->fieldFlags($result, $k); + + // Enhance the field objects for mysql-extension compatibility + //$flags = explode(' ', $fields[$k]->flags); + //array_unshift($flags, 'dummy'); + $fields[$k]->multiple_key + = (int) (bool) ($fields[$k]->_flags & MYSQLI_MULTIPLE_KEY_FLAG); + $fields[$k]->primary_key + = (int) (bool) ($fields[$k]->_flags & MYSQLI_PRI_KEY_FLAG); + $fields[$k]->unique_key + = (int) (bool) ($fields[$k]->_flags & MYSQLI_UNIQUE_KEY_FLAG); + $fields[$k]->not_null + = (int) (bool) ($fields[$k]->_flags & MYSQLI_NOT_NULL_FLAG); + $fields[$k]->unsigned + = (int) (bool) ($fields[$k]->_flags & MYSQLI_UNSIGNED_FLAG); + $fields[$k]->zerofill + = (int) (bool) ($fields[$k]->_flags & MYSQLI_ZEROFILL_FLAG); + $fields[$k]->numeric + = (int) (bool) ($fields[$k]->_flags & MYSQLI_NUM_FLAG); + $fields[$k]->blob + = (int) (bool) ($fields[$k]->_flags & MYSQLI_BLOB_FLAG); + } + return $fields; + } + + /** + * return number of fields in given $result + * + * @param mysqli_result $result result set identifier + * + * @return int field count + */ + public function numFields($result) + { + return mysqli_num_fields($result); + } + + /** + * returns the length of the given field $i in $result + * + * @param mysqli_result $result result set identifier + * @param int $i field + * + * @return int length of field + */ + public function fieldLen($result, $i) + { + if ($i >= $this->numFields($result)) { + return false; + } + return mysqli_fetch_field_direct($result, $i)->length; + } + + /** + * returns name of $i. field in $result + * + * @param mysqli_result $result result set identifier + * @param int $i field + * + * @return string name of $i. field in $result + */ + public function fieldName($result, $i) + { + if ($i >= $this->numFields($result)) { + return false; + } + return mysqli_fetch_field_direct($result, $i)->name; + } + + /** + * returns concatenated string of human readable field flags + * + * @param mysqli_result $result result set identifier + * @param int $i field + * + * @return string field flags + */ + public function fieldFlags($result, $i) + { + if ($i >= $this->numFields($result)) { + return false; + } + $f = mysqli_fetch_field_direct($result, $i); + $type = $f->type; + $charsetnr = $f->charsetnr; + $f = $f->flags; + $flags = array(); + foreach (self::$pma_mysqli_flag_names as $flag => $name) { + if ($f & $flag) { + $flags[] = $name; + } + } + // See https://dev.mysql.com/doc/refman/6.0/en/c-api-datatypes.html: + // to determine if a string is binary, we should not use MYSQLI_BINARY_FLAG + // but instead the charsetnr member of the MYSQL_FIELD + // structure. Watch out: some types like DATE returns 63 in charsetnr + // so we have to check also the type. + // Unfortunately there is no equivalent in the mysql extension. + if (($type == MYSQLI_TYPE_TINY_BLOB || $type == MYSQLI_TYPE_BLOB + || $type == MYSQLI_TYPE_MEDIUM_BLOB || $type == MYSQLI_TYPE_LONG_BLOB + || $type == MYSQLI_TYPE_VAR_STRING || $type == MYSQLI_TYPE_STRING) + && 63 == $charsetnr + ) { + $flags[] = 'binary'; + } + return implode(' ', $flags); + } + + /** + * returns properly escaped string for use in MySQL queries + * + * @param mixed $link database link + * @param string $str string to be escaped + * + * @return string a MySQL escaped string + */ + public function escapeString($link, $str) + { + return mysqli_real_escape_string($link, $str); + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Di/AliasItem.php b/php/apps/phpmyadmin49/html/libraries/classes/Di/AliasItem.php new file mode 100644 index 00000000..3f461dd2 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Di/AliasItem.php @@ -0,0 +1,46 @@ +container = $container; + $this->target = $target; + } + + /** + * Get the target item + * + * @param array $params Parameters + * @return mixed + */ + public function get(array $params = array()) + { + return $this->container->get($this->target, $params); + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Di/Container.php b/php/apps/phpmyadmin49/html/libraries/classes/Di/Container.php new file mode 100644 index 00000000..06fab997 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Di/Container.php @@ -0,0 +1,189 @@ +content = $base->content; + } else { + $this->alias('container', 'Container'); + } + $this->set('Container', $this); + } + + /** + * Get an object with given name and parameters + * + * @param string $name Name + * @param array $params Parameters + * + * @throws NotFoundException No entry was found for **this** identifier. + * @throws ContainerException Error while retrieving the entry. + * + * @return mixed + */ + public function get($name, array $params = array()) + { + if (!$this->has($name)) { + throw new NotFoundException("No entry was found for $name identifier."); + } + + if (isset($this->content[$name])) { + return $this->content[$name]->get($params); + } elseif (isset($GLOBALS[$name])) { + return $GLOBALS[$name]; + } else { + throw new ContainerException("Error while retrieving the entry."); + } + } + + /** + * Returns true if the container can return an entry for the given identifier. + * Returns false otherwise. + * + * `has($name)` returning true does not mean that `get($name)` will not throw an exception. + * It does however mean that `get($name)` will not throw a `NotFoundException`. + * + * @param string $name Identifier of the entry to look for. + * + * @return bool + */ + public function has($name) + { + return isset($this->content[$name]) || isset($GLOBALS[$name]); + } + + /** + * Remove an object from container + * + * @param string $name Name + * + * @return void + */ + public function remove($name) + { + unset($this->content[$name]); + } + + /** + * Rename an object in container + * + * @param string $name Name + * @param string $newName New name + * + * @return void + */ + public function rename($name, $newName) + { + $this->content[$newName] = $this->content[$name]; + $this->remove($name); + } + + /** + * Set values in the container + * + * @param string|array $name Name + * @param mixed $value Value + * + * @return void + */ + public function set($name, $value = null) + { + if (is_array($name)) { + foreach ($name as $key => $val) { + $this->set($key, $val); + } + return; + } + $this->content[$name] = new ValueItem($value); + } + + /** + * Register a service in the container + * + * @param string $name Name + * @param mixed $service Service + * + * @return void + */ + public function service($name, $service = null) + { + if (!isset($service)) { + $service = $name; + } + $this->content[$name] = new ServiceItem($this, $service); + } + + /** + * Register a factory in the container + * + * @param string $name Name + * @param mixed $factory Factory + * + * @return void + */ + public function factory($name, $factory = null) + { + if (!isset($factory)) { + $factory = $name; + } + $this->content[$name] = new FactoryItem($this, $factory); + } + + /** + * Register an alias in the container + * + * @param string $name Name + * @param string $target Target + * + * @return void + */ + public function alias($name, $target) + { + // The target may be not defined yet + $this->content[$name] = new AliasItem($this, $target); + } + + /** + * Get the global default container + * + * @return Container + */ + public static function getDefaultContainer() + { + if (!isset(static::$defaultContainer)) { + static::$defaultContainer = new Container(); + } + return static::$defaultContainer; + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Di/ContainerException.php b/php/apps/phpmyadmin49/html/libraries/classes/Di/ContainerException.php new file mode 100644 index 00000000..a5a2da0e --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Di/ContainerException.php @@ -0,0 +1,21 @@ +invoke($params); + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Di/Item.php b/php/apps/phpmyadmin49/html/libraries/classes/Di/Item.php new file mode 100644 index 00000000..9ddd6a8f --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Di/Item.php @@ -0,0 +1,25 @@ +_container = $container; + $this->_reflector = self::_resolveReflector($definition); + } + + /** + * Invoke the reflector with given parameters + * + * @param array $params Parameters + * @return mixed + */ + protected function invoke(array $params = array()) + { + $args = array(); + $reflector = $this->_reflector; + if ($reflector instanceof \ReflectionClass) { + $constructor = $reflector->getConstructor(); + if (isset($constructor)) { + $args = $this->_resolveArgs( + $constructor->getParameters(), + $params + ); + } + return $reflector->newInstanceArgs($args); + } + /** @var \ReflectionFunctionAbstract $reflector */ + $args = $this->_resolveArgs( + $reflector->getParameters(), + $params + ); + if ($reflector instanceof \ReflectionMethod) { + /** @var \ReflectionMethod $reflector */ + return $reflector->invokeArgs(null, $args); + } + /** @var \ReflectionFunction $reflector */ + return $reflector->invokeArgs($args); + } + + /** + * Getting required arguments with given parameters + * + * @param \ReflectionParameter[] $required Arguments + * @param array $params Parameters + * +*@return array + */ + private function _resolveArgs($required, array $params = array()) + { + $args = array(); + foreach ($required as $param) { + $name = $param->getName(); + $type = $param->getClass(); + if (isset($type)) { + $type = $type->getName(); + } + if (isset($params[$name])) { + $args[] = $params[$name]; + } elseif (is_string($type) && isset($params[$type])) { + $args[] = $params[$type]; + } else { + try { + $content = $this->_container->get($name); + if (isset($content)) { + $args[] = $content; + } elseif (is_string($type)) { + $args[] = $this->_container->get($type); + } else { + $args[] = null; + } + } catch (NotFoundException $e) { + $args[] = null; + } + } + } + return $args; + } + + /** + * Resolve the reflection + * + * @param mixed $definition Definition + * + * @return \Reflector + */ + private static function _resolveReflector($definition) + { + if (function_exists($definition)) { + return new \ReflectionFunction($definition); + } + if (is_string($definition)) { + $definition = explode('::', $definition); + } + if (!isset($definition[1])) { + return new \ReflectionClass($definition[0]); + } + return new \ReflectionMethod($definition[0], $definition[1]); + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Di/ServiceItem.php b/php/apps/phpmyadmin49/html/libraries/classes/Di/ServiceItem.php new file mode 100644 index 00000000..25a4576f --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Di/ServiceItem.php @@ -0,0 +1,34 @@ +instance)) { + $this->instance = $this->invoke(); + } + return $this->instance; + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Di/ValueItem.php b/php/apps/phpmyadmin49/html/libraries/classes/Di/ValueItem.php new file mode 100644 index 00000000..2997a374 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Di/ValueItem.php @@ -0,0 +1,41 @@ +value = $value; + } + + /** + * Get the value + * + * @param array $params Parameters + * @return mixed + */ + public function get(array $params = array()) + { + return $this->value; + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Display/ChangePassword.php b/php/apps/phpmyadmin49/html/libraries/classes/Display/ChangePassword.php new file mode 100644 index 00000000..dec25234 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Display/ChangePassword.php @@ -0,0 +1,165 @@ +'; + + $html .= Url::getHiddenInputs(); + + if (strpos($GLOBALS['PMA_PHP_SELF'], 'server_privileges') !== false) { + $html .= '' + . ''; + } + $html .= '
      ' + . '' . __('Change password') . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . ''; + + $serverType = Util::getServerType(); + $serverVersion = $GLOBALS['dbi']->getVersion(); + $orig_auth_plugin = Privileges::getCurrentAuthenticationPlugin( + 'change', + $username, + $hostname + ); + + if (($serverType == 'MySQL' + && $serverVersion >= 50507) + || ($serverType == 'MariaDB' + && $serverVersion >= 50200) + ) { + // Provide this option only for 5.7.6+ + // OR for privileged users in 5.5.7+ + if (($serverType == 'MySQL' + && $serverVersion >= 50706) + || ($GLOBALS['dbi']->isSuperuser() && $mode == 'edit_other') + ) { + $auth_plugin_dropdown = Privileges::getHtmlForAuthPluginsDropdown( + $orig_auth_plugin, 'change_pw', 'new' + ); + + $html .= '' + . '' + . '' + . '
      ' + . '' + . '' + . '
      ' + . '' + . '' + . '' + . __('Enter:') . '     ' + . '' + . 'Strength: ' + . ' ' + . 'Good' + . '
      ' . __('Re-type:') . ' ' + . '' + . '
      ' . __('Password Hashing:') . ''; + $html .= $auth_plugin_dropdown; + $html .= '
      '; + + $html .= '' + . Message::notice( + __( + 'This method requires using an \'SSL connection\' ' + . 'or an \'unencrypted connection that encrypts the ' + . 'password using RSA\'; while connecting to the server.' + ) + . Util::showMySQLDocu( + 'sha256-authentication-plugin' + ) + ) + ->getDisplay() + . '
      '; + } else { + $html .= '' + . ''; + } + } else { + $auth_plugin_dropdown = Privileges::getHtmlForAuthPluginsDropdown( + $orig_auth_plugin, 'change_pw', 'old' + ); + + $html .= '' + . '' . __('Password Hashing:') . ''; + $html .= $auth_plugin_dropdown . '' + . '' + . ''; + } + + $html .= '
    • ' + . '' + . ''; + return $html; + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Display/CreateTable.php b/php/apps/phpmyadmin49/html/libraries/classes/Display/CreateTable.php new file mode 100644 index 00000000..5114d629 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Display/CreateTable.php @@ -0,0 +1,53 @@ += 4.1.0, we should be able to detect if user has a CREATE + * privilege by looking at SHOW GRANTS output; + * for < 4.1.0, it could be more difficult because the logic tries to + * detect the current host and it might be expressed in many ways; also + * on a shared server, the user might be unable to define a controluser + * that has the proper rights to the "mysql" db; + * so we give up and assume that user has the right to create a table + * + * Note: in this case we could even skip the following "foreach" logic + * + * Addendum, 2006-01-19: ok, I give up. We got some reports about servers + * where the hostname field in mysql.user is not the same as the one + * in mysql.db for a user. In this case, SHOW GRANTS does not return + * the db-specific privileges. And probably, those users are on a shared + * server, so can't set up a control user with rights to the "mysql" db. + * We cannot reliably detect the db-specific privileges, so no more + * warnings about the lack of privileges for CREATE TABLE. Tested + * on MySQL 5.0.18. + * + * @package PhpMyAdmin + */ +namespace PhpMyAdmin\Display; + +use PhpMyAdmin\Template; + +require_once './libraries/check_user_privileges.inc.php'; + +/** + * PhpMyAdmin\Display\CreateTable class + * + * @package PhpMyAdmin + */ +class CreateTable +{ + /** + * Returns the html for create table. + * + * @param string $db database name + * + * @return string + */ + public static function getHtml($db) + { + return Template::get('database/create_table')->render( + array('db' => $db) + ); + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Display/Export.php b/php/apps/phpmyadmin49/html/libraries/classes/Display/Export.php new file mode 100644 index 00000000..53b6109f --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Display/Export.php @@ -0,0 +1,806 @@ +relation = new Relation(); + } + + /** + * Outputs appropriate checked statement for checkbox. + * + * @param string $str option name + * + * @return boolean + */ + private function checkboxCheck($str) + { + return isset($GLOBALS['cfg']['Export'][$str]) + && $GLOBALS['cfg']['Export'][$str]; + } + + /** + * Prints Html For Export Selection Options + * + * @param string $tmpSelect Tmp selected method of export + * + * @return string + */ + public function getHtmlForSelectOptions($tmpSelect = '') + { + // Check if the selected databases are defined in $_POST + // (from clicking Back button on export.php) + if (isset($_POST['db_select'])) { + $_POST['db_select'] = urldecode($_POST['db_select']); + $_POST['db_select'] = explode(",", $_POST['db_select']); + } + + $databases = []; + foreach ($GLOBALS['dblist']->databases as $currentDb) { + if ($GLOBALS['dbi']->isSystemSchema($currentDb, true)) { + continue; + } + $isSelected = false; + if (isset($_POST['db_select'])) { + if (in_array($currentDb, $_POST['db_select'])) { + $isSelected = true; + } + } elseif (!empty($tmpSelect)) { + if (mb_strpos( + ' ' . $tmpSelect, + '|' . $currentDb . '|' + )) { + $isSelected = true; + } + } else { + $isSelected = true; + } + $databases[] = [ + 'name' => $currentDb, + 'is_selected' => $isSelected, + ]; + } + + return Template::get('display/export/select_options')->render([ + 'databases' => $databases, + ]); + } + + /** + * Prints Html For Export Hidden Input + * + * @param string $exportType Selected Export Type + * @param string $db Selected DB + * @param string $table Selected Table + * @param string $singleTable Single Table + * @param string $sqlQuery SQL Query + * + * @return string + */ + public function getHtmlForHiddenInputs( + $exportType, + $db, + $table, + $singleTable, + $sqlQuery + ) { + global $cfg; + + // If the export method was not set, the default is quick + if (isset($_POST['export_method'])) { + $cfg['Export']['method'] = $_POST['export_method']; + } elseif (! isset($cfg['Export']['method'])) { + $cfg['Export']['method'] = 'quick'; + } + + if (empty($sqlQuery) && isset($_POST['sql_query'])) { + $sqlQuery = $_POST['sql_query']; + } + + return Template::get('display/export/hidden_inputs')->render([ + 'db' => $db, + 'table' => $table, + 'export_type' => $exportType, + 'export_method' => $cfg['Export']['method'], + 'single_table' => $singleTable, + 'sql_query' => $sqlQuery, + 'template_id' => isset($_POST['template_id']) ? $_POST['template_id'] : '', + ]); + } + + /** + * Returns HTML for the options in template dropdown + * + * @param string $exportType export type - server, database, or table + * + * @return string HTML for the options in teplate dropdown + */ + private function getOptionsForTemplates($exportType) + { + // Get the relation settings + $cfgRelation = $this->relation->getRelationsParam(); + + $query = "SELECT `id`, `template_name` FROM " + . Util::backquote($cfgRelation['db']) . '.' + . Util::backquote($cfgRelation['export_templates']) + . " WHERE `username` = " + . "'" . $GLOBALS['dbi']->escapeString($GLOBALS['cfg']['Server']['user']) + . "' AND `export_type` = '" . $GLOBALS['dbi']->escapeString($exportType) . "'" + . " ORDER BY `template_name`;"; + + $result = $this->relation->queryAsControlUser($query); + + $templates = []; + if ($result !== false) { + while ($row = $GLOBALS['dbi']->fetchAssoc($result, DatabaseInterface::CONNECT_CONTROL)) { + $templates[] = [ + 'name' => $row['template_name'], + 'id' => $row['id'], + ]; + } + } + + return Template::get('display/export/template_options')->render([ + 'templates' => $templates, + 'selected_template' => !empty($_POST['template_id']) ? $_POST['template_id'] : null, + ]); + } + + /** + * Prints Html For Export Options Method + * + * @return string + */ + private function getHtmlForOptionsMethod() + { + global $cfg; + if (isset($_POST['quick_or_custom'])) { + $exportMethod = $_POST['quick_or_custom']; + } else { + $exportMethod = $cfg['Export']['method']; + } + + return Template::get('display/export/method')->render([ + 'export_method' => $exportMethod, + ]); + } + + /** + * Prints Html For Export Options Selection + * + * @param string $exportType Selected Export Type + * @param string $multiValues Export Options + * + * @return string + */ + private function getHtmlForOptionsSelection($exportType, $multiValues) + { + return Template::get('display/export/selection')->render([ + 'export_type' => $exportType, + 'multi_values' => $multiValues, + ]); + } + + /** + * Prints Html For Export Options Format dropdown + * + * @param ExportPlugin[] $exportList Export List + * + * @return string + */ + private function getHtmlForOptionsFormatDropdown($exportList) + { + $dropdown = Plugins::getChoice('Export', 'what', $exportList, 'format'); + return Template::get('display/export/format_dropdown')->render([ + 'dropdown' => $dropdown, + ]); + } + + /** + * Prints Html For Export Options Format-specific options + * + * @param ExportPlugin[] $exportList Export List + * + * @return string + */ + private function getHtmlForOptionsFormat($exportList) + { + global $cfg; + $options = Plugins::getOptions('Export', $exportList); + + return Template::get('display/export/options_format')->render([ + 'options' => $options, + 'can_convert_kanji' => Encoding::canConvertKanji(), + 'exec_time_limit' => $cfg['ExecTimeLimit'], + ]); + } + + /** + * Prints Html For Export Options Rows + * + * @param string $db Selected DB + * @param string $table Selected Table + * @param string $unlimNumRows Num of Rows + * + * @return string + */ + private function getHtmlForOptionsRows($db, $table, $unlimNumRows) + { + $tableObject = new Table($table, $db); + $numberOfRows = $tableObject->countRecords(); + + return Template::get('display/export/options_rows')->render([ + 'allrows' => isset($_POST['allrows']) ? $_POST['allrows'] : null, + 'limit_to' => isset($_POST['limit_to']) ? $_POST['limit_to'] : null, + 'limit_from' => isset($_POST['limit_from']) ? $_POST['limit_from'] : null, + 'unlim_num_rows' => $unlimNumRows, + 'number_of_rows' => $numberOfRows, + ]); + } + + /** + * Prints Html For Export Options Quick Export + * + * @return string + */ + private function getHtmlForOptionsQuickExport() + { + global $cfg; + $saveDir = Util::userDir($cfg['SaveDir']); + $exportIsChecked = $this->checkboxCheck( + 'quick_export_onserver' + ); + $exportOverwriteIsChecked = $this->checkboxCheck( + 'quick_export_onserver_overwrite' + ); + + return Template::get('display/export/options_quick_export')->render([ + 'save_dir' => $saveDir, + 'export_is_checked' => $exportIsChecked, + 'export_overwrite_is_checked' => $exportOverwriteIsChecked, + ]); + } + + /** + * Prints Html For Export Options Save Dir + * + * @return string + */ + private function getHtmlForOptionsOutputSaveDir() + { + global $cfg; + $saveDir = Util::userDir($cfg['SaveDir']); + $exportIsChecked = $this->checkboxCheck( + 'onserver' + ); + $exportOverwriteIsChecked = $this->checkboxCheck( + 'onserver_overwrite' + ); + + return Template::get('display/export/options_output_save_dir')->render([ + 'save_dir' => $saveDir, + 'export_is_checked' => $exportIsChecked, + 'export_overwrite_is_checked' => $exportOverwriteIsChecked, + ]); + } + + + /** + * Prints Html For Export Options + * + * @param string $exportType Selected Export Type + * + * @return string + */ + private function getHtmlForOptionsOutputFormat($exportType) + { + $trans = new Message; + $trans->addText(__('@SERVER@ will become the server name')); + if ($exportType == 'database' || $exportType == 'table') { + $trans->addText(__(', @DATABASE@ will become the database name')); + if ($exportType == 'table') { + $trans->addText(__(', @TABLE@ will become the table name')); + } + } + + $msg = new Message( + __( + 'This value is interpreted using %1$sstrftime%2$s, ' + . 'so you can use time formatting strings. ' + . 'Additionally the following transformations will happen: %3$s. ' + . 'Other text will be kept as is. See the %4$sFAQ%5$s for details.' + ) + ); + $msg->addParamHtml( + '' + ); + $msg->addParamHtml(''); + $msg->addParam($trans); + $docUrl = Util::getDocuLink('faq', 'faq6-27'); + $msg->addParamHtml( + '' + ); + $msg->addParamHtml(''); + + if (isset($_POST['filename_template'])) { + $filenameTemplate = $_POST['filename_template']; + } else { + if ($exportType == 'database') { + $filenameTemplate = $GLOBALS['PMA_Config']->getUserValue( + 'pma_db_filename_template', + $GLOBALS['cfg']['Export']['file_template_database'] + ); + } elseif ($exportType == 'table') { + $filenameTemplate = $GLOBALS['PMA_Config']->getUserValue( + 'pma_table_filename_template', + $GLOBALS['cfg']['Export']['file_template_table'] + ); + } else { + $filenameTemplate = $GLOBALS['PMA_Config']->getUserValue( + 'pma_server_filename_template', + $GLOBALS['cfg']['Export']['file_template_server'] + ); + } + } + + return Template::get('display/export/options_output_format')->render([ + 'message' => $msg->getMessage(), + 'filename_template' => $filenameTemplate, + 'is_checked' => $this->checkboxCheck('remember_file_template'), + ]); + } + + /** + * Prints Html For Export Options Charset + * + * @return string + */ + private function getHtmlForOptionsOutputCharset() + { + global $cfg; + + return Template::get('display/export/options_output_charset')->render([ + 'encodings' => Encoding::listEncodings(), + 'export_charset' => $cfg['Export']['charset'], + ]); + } + + /** + * Prints Html For Export Options Compression + * + * @return string + */ + private function getHtmlForOptionsOutputCompression() + { + global $cfg; + if (isset($_POST['compression'])) { + $selectedCompression = $_POST['compression']; + } elseif (isset($cfg['Export']['compression'])) { + $selectedCompression = $cfg['Export']['compression']; + } else { + $selectedCompression = 'none'; + } + + // Since separate files export works with ZIP only + if (isset($cfg['Export']['as_separate_files']) + && $cfg['Export']['as_separate_files'] + ) { + $selectedCompression = 'zip'; + } + + // zip and gzip encode features + $isZip = ($cfg['ZipDump'] && function_exists('gzcompress')); + $isGzip = ($cfg['GZipDump'] && function_exists('gzencode')); + + return Template::get('display/export/options_output_compression')->render([ + 'is_zip' => $isZip, + 'is_gzip' => $isGzip, + 'selected_compression' => $selectedCompression, + ]); + } + + /** + * Prints Html For Export Options Radio + * + * @return string + */ + private function getHtmlForOptionsOutputRadio() + { + return Template::get('display/export/options_output_radio')->render([ + 'has_repopulate' => isset($_POST['repopulate']), + 'export_asfile' => $GLOBALS['cfg']['Export']['asfile'], + ]); + } + + /** + * Prints Html For Export Options Checkbox - Separate files + * + * @param string $exportType Selected Export Type + * + * @return string + */ + private function getHtmlForOptionsOutputSeparateFiles($exportType) + { + $isChecked = $this->checkboxCheck('as_separate_files'); + + return Template::get('display/export/options_output_separate_files')->render([ + 'is_checked' => $isChecked, + 'export_type' => $exportType, + ]); + } + + /** + * Prints Html For Export Options + * + * @param string $exportType Selected Export Type + * + * @return string + */ + private function getHtmlForOptionsOutput($exportType) + { + global $cfg; + + $hasAliases = isset($_SESSION['tmpval']['aliases']) + && !Core::emptyRecursive($_SESSION['tmpval']['aliases']); + unset($_SESSION['tmpval']['aliases']); + + $isCheckedLockTables = $this->checkboxCheck('lock_tables'); + $isCheckedAsfile = $this->checkboxCheck('asfile'); + + $optionsOutputSaveDir = ''; + if (isset($cfg['SaveDir']) && !empty($cfg['SaveDir'])) { + $optionsOutputSaveDir = $this->getHtmlForOptionsOutputSaveDir(); + } + $optionsOutputFormat = $this->getHtmlForOptionsOutputFormat($exportType); + $optionsOutputCharset = ''; + if (Encoding::isSupported()) { + $optionsOutputCharset = $this->getHtmlForOptionsOutputCharset(); + } + $optionsOutputCompression = $this->getHtmlForOptionsOutputCompression(); + $optionsOutputSeparateFiles = ''; + if ($exportType == 'server' || $exportType == 'database') { + $optionsOutputSeparateFiles = $this->getHtmlForOptionsOutputSeparateFiles( + $exportType + ); + } + $optionsOutputRadio = $this->getHtmlForOptionsOutputRadio(); + + return Template::get('display/export/options_output')->render([ + 'has_aliases' => $hasAliases, + 'export_type' => $exportType, + 'is_checked_lock_tables' => $isCheckedLockTables, + 'is_checked_asfile' => $isCheckedAsfile, + 'repopulate' => isset($_POST['repopulate']), + 'lock_tables' => isset($_POST['lock_tables']), + 'save_dir' => isset($cfg['SaveDir']) ? $cfg['SaveDir'] : null, + 'is_encoding_supported' => Encoding::isSupported(), + 'options_output_save_dir' => $optionsOutputSaveDir, + 'options_output_format' => $optionsOutputFormat, + 'options_output_charset' => $optionsOutputCharset, + 'options_output_compression' => $optionsOutputCompression, + 'options_output_separate_files' => $optionsOutputSeparateFiles, + 'options_output_radio' => $optionsOutputRadio, + ]); + } + + /** + * Prints Html For Export Options + * + * @param string $exportType Selected Export Type + * @param string $db Selected DB + * @param string $table Selected Table + * @param string $multiValues Export selection + * @param string $numTables number of tables + * @param ExportPlugin[] $exportList Export List + * @param string $unlimNumRows Number of Rows + * + * @return string + */ + public function getHtmlForOptions( + $exportType, + $db, + $table, + $multiValues, + $numTables, + $exportList, + $unlimNumRows + ) { + global $cfg; + $html = $this->getHtmlForOptionsMethod(); + $html .= $this->getHtmlForOptionsFormatDropdown($exportList); + $html .= $this->getHtmlForOptionsSelection($exportType, $multiValues); + + $tableObject = new Table($table, $db); + if (strlen($table) > 0 && empty($numTables) && ! $tableObject->isMerge()) { + $html .= $this->getHtmlForOptionsRows($db, $table, $unlimNumRows); + } + + if (isset($cfg['SaveDir']) && !empty($cfg['SaveDir'])) { + $html .= $this->getHtmlForOptionsQuickExport(); + } + + $html .= $this->getHtmlForAliasModalDialog(); + $html .= $this->getHtmlForOptionsOutput($exportType); + $html .= $this->getHtmlForOptionsFormat($exportList); + return $html; + } + + /** + * Generate Html For currently defined aliases + * + * @return string + */ + private function getHtmlForCurrentAlias() + { + $result = ''; + + $template = Template::get('export/alias_item'); + if (isset($_SESSION['tmpval']['aliases'])) { + foreach ($_SESSION['tmpval']['aliases'] as $db => $dbData) { + if (isset($dbData['alias'])) { + $result .= $template->render(array( + 'type' => _pgettext('Alias', 'Database'), + 'name' => $db, + 'field' => 'aliases[' . $db . '][alias]', + 'value' => $dbData['alias'], + )); + } + if (! isset($dbData['tables'])) { + continue; + } + foreach ($dbData['tables'] as $table => $tableData) { + if (isset($tableData['alias'])) { + $result .= $template->render(array( + 'type' => _pgettext('Alias', 'Table'), + 'name' => $db . '.' . $table, + 'field' => 'aliases[' . $db . '][tables][' . $table . '][alias]', + 'value' => $tableData['alias'], + )); + } + if (! isset($tableData['columns'])) { + continue; + } + foreach ($tableData['columns'] as $column => $columnName) { + $result .= $template->render(array( + 'type' => _pgettext('Alias', 'Column'), + 'name' => $db . '.' . $table . '.'. $column, + 'field' => 'aliases[' . $db . '][tables][' . $table . '][colums][' . $column . ']', + 'value' => $columnName, + )); + } + } + } + } + + // Empty row for javascript manipulations + $result .= '' . $template->render(array( + 'type' => '', 'name' => '', 'field' => 'aliases_new', 'value' => '' + )) . ''; + + return $result . '
      ' + . __('Defined aliases') + . '
      '; + } + + /** + * Generate Html For Alias Modal Dialog + * + * @return string + */ + public function getHtmlForAliasModalDialog() + { + $title = __('Rename exported databases/tables/columns'); + + $html = '
      '; + $html .= $this->getHtmlForCurrentAlias(); + $html .= Template::get('export/alias_add')->render(); + + $html .= '
      '; + return $html; + } + + /** + * Gets HTML to display export dialogs + * + * @param string $exportType export type: server|database|table + * @param string $db selected DB + * @param string $table selected table + * @param string $sqlQuery SQL query + * @param int $numTables number of tables + * @param int $unlimNumRows unlimited number of rows + * @param string $multiValues selector options + * + * @return string $html + */ + public function getDisplay( + $exportType, + $db, + $table, + $sqlQuery, + $numTables, + $unlimNumRows, + $multiValues + ) { + $cfgRelation = $this->relation->getRelationsParam(); + + if (isset($_POST['single_table'])) { + $GLOBALS['single_table'] = $_POST['single_table']; + } + + // Export a single table + if (isset($_GET['single_table'])) { + $GLOBALS['single_table'] = $_GET['single_table']; + } + + /* Scan for plugins */ + /* @var $exportList ExportPlugin[] */ + $exportList = Plugins::getPlugins( + "export", + 'libraries/classes/Plugins/Export/', + array( + 'export_type' => $exportType, + 'single_table' => isset($GLOBALS['single_table']) + ) + ); + + /* Fail if we didn't find any plugin */ + if (empty($exportList)) { + Message::error( + __('Could not load export plugins, please check your installation!') + )->display(); + exit; + } + + $html = Template::get('display/export/option_header')->render([ + 'export_type' => $exportType, + 'db' => $db, + 'table' => $table, + ]); + + if ($cfgRelation['exporttemplateswork']) { + $html .= Template::get('display/export/template_loading')->render([ + 'options' => $this->getOptionsForTemplates($exportType), + ]); + } + + $html .= '
      '; + + //output Hidden Inputs + $singleTableStr = isset($GLOBALS['single_table']) ? $GLOBALS['single_table'] + : ''; + $html .= $this->getHtmlForHiddenInputs( + $exportType, + $db, + $table, + $singleTableStr, + $sqlQuery + ); + + //output Export Options + $html .= $this->getHtmlForOptions( + $exportType, + $db, + $table, + $multiValues, + $numTables, + $exportList, + $unlimNumRows + ); + + $html .= '
      '; + return $html; + } + + /** + * Handles export template actions + * + * @param array $cfgRelation Relation configuration + * + * @return void + */ + public function handleTemplateActions(array $cfgRelation) + { + if (isset($_POST['templateId'])) { + $id = $GLOBALS['dbi']->escapeString($_POST['templateId']); + } else { + $id = ''; + } + + $templateTable = Util::backquote($cfgRelation['db']) . '.' + . Util::backquote($cfgRelation['export_templates']); + $user = $GLOBALS['dbi']->escapeString($GLOBALS['cfg']['Server']['user']); + + switch ($_POST['templateAction']) { + case 'create': + $query = "INSERT INTO " . $templateTable . "(" + . " `username`, `export_type`," + . " `template_name`, `template_data`" + . ") VALUES (" + . "'" . $user . "', " + . "'" . $GLOBALS['dbi']->escapeString($_POST['exportType']) + . "', '" . $GLOBALS['dbi']->escapeString($_POST['templateName']) + . "', '" . $GLOBALS['dbi']->escapeString($_POST['templateData']) + . "');"; + break; + case 'load': + $query = "SELECT `template_data` FROM " . $templateTable + . " WHERE `id` = " . $id . " AND `username` = '" . $user . "'"; + break; + case 'update': + $query = "UPDATE " . $templateTable . " SET `template_data` = " + . "'" . $GLOBALS['dbi']->escapeString($_POST['templateData']) . "'" + . " WHERE `id` = " . $id . " AND `username` = '" . $user . "'"; + break; + case 'delete': + $query = "DELETE FROM " . $templateTable + . " WHERE `id` = " . $id . " AND `username` = '" . $user . "'"; + break; + default: + $query = ''; + break; + } + + $result = $this->relation->queryAsControlUser($query, false); + + $response = Response::getInstance(); + if (! $result) { + $error = $GLOBALS['dbi']->getError(DatabaseInterface::CONNECT_CONTROL); + $response->setRequestStatus(false); + $response->addJSON('message', $error); + exit; + } + + $response->setRequestStatus(true); + if ('create' == $_POST['templateAction']) { + $response->addJSON( + 'data', + $this->getOptionsForTemplates($_POST['exportType']) + ); + } elseif ('load' == $_POST['templateAction']) { + $data = null; + while ($row = $GLOBALS['dbi']->fetchAssoc( + $result, DatabaseInterface::CONNECT_CONTROL + )) { + $data = $row['template_data']; + } + $response->addJSON('data', $data); + } + $GLOBALS['dbi']->freeResult($result); + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Display/GitRevision.php b/php/apps/phpmyadmin49/html/libraries/classes/Display/GitRevision.php new file mode 100644 index 00000000..8b350e8c --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Display/GitRevision.php @@ -0,0 +1,98 @@ +checkGitRevision(); + + if (! $GLOBALS['PMA_Config']->get('PMA_VERSION_GIT')) { + $response = Response::getInstance(); + $response->setRequestStatus(false); + return; + } + + // if using a remote commit fast-forwarded, link to GitHub + $commit_hash = substr( + $GLOBALS['PMA_Config']->get('PMA_VERSION_GIT_COMMITHASH'), + 0, + 7 + ); + $commit_hash = '' . $commit_hash . ''; + if ($GLOBALS['PMA_Config']->get('PMA_VERSION_GIT_ISREMOTECOMMIT')) { + $commit_hash = '' . $commit_hash . ''; + } + + $branch = $GLOBALS['PMA_Config']->get('PMA_VERSION_GIT_BRANCH'); + if ($GLOBALS['PMA_Config']->get('PMA_VERSION_GIT_ISREMOTEBRANCH')) { + $branch = '' . $branch . ''; + } + if ($branch !== false) { + $branch = sprintf(__('%1$s from %2$s branch'), $commit_hash, $branch); + } else { + $branch = $commit_hash . ' (' . __('no branch') . ')'; + } + + $committer = $GLOBALS['PMA_Config']->get('PMA_VERSION_GIT_COMMITTER'); + $author = $GLOBALS['PMA_Config']->get('PMA_VERSION_GIT_AUTHOR'); + Core::printListItem( + __('Git revision:') . ' ' + . $branch . ',
      ' + . sprintf( + __('committed on %1$s by %2$s'), + Util::localisedDate(strtotime($committer['date'])), + '' + . htmlspecialchars($committer['name']) . '' + ) + . ($author != $committer + ? ',
      ' + . sprintf( + __('authored on %1$s by %2$s'), + Util::localisedDate(strtotime($author['date'])), + '' + . htmlspecialchars($author['name']) . '' + ) + : ''), + 'li_pma_version_git', null, null, null + ); + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Display/Import.php b/php/apps/phpmyadmin49/html/libraries/classes/Display/Import.php new file mode 100644 index 00000000..f3cbb6b6 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Display/Import.php @@ -0,0 +1,111 @@ +display(); + exit; + } + + if (Core::isValid($_REQUEST['offset'], 'numeric')) { + $offset = intval($_REQUEST['offset']); + } + if (isset($_REQUEST['timeout_passed'])) { + $timeoutPassed = $_REQUEST['timeout_passed']; + } + + $localImportFile = ''; + if (isset($_REQUEST['local_import_file'])) { + $localImportFile = $_REQUEST['local_import_file']; + } + + // zip, gzip and bzip2 encode features + $compressions = array(); + if ($cfg['GZipDump'] && function_exists('gzopen')) { + $compressions[] = 'gzip'; + } + if ($cfg['BZipDump'] && function_exists('bzopen')) { + $compressions[] = 'bzip2'; + } + if ($cfg['ZipDump'] && function_exists('zip_open')) { + $compressions[] = 'zip'; + } + + return Template::get('display/import/import')->render([ + 'upload_id' => $uploadId, + 'handler' => $_SESSION[$SESSION_KEY]["handler"], + 'id_key' => $_SESSION[$SESSION_KEY]['handler']::getIdKey(), + 'pma_theme_image' => $GLOBALS['pmaThemeImage'], + 'import_type' => $importType, + 'db' => $db, + 'table' => $table, + 'max_upload_size' => $maxUploadSize, + 'import_list' => $importList, + 'local_import_file' => $localImportFile, + 'is_upload' => $GLOBALS['is_upload'], + 'upload_dir' => isset($cfg['UploadDir']) ? $cfg['UploadDir'] : null, + 'timeout_passed_global' => isset($GLOBALS['timeout_passed']) ? $GLOBALS['timeout_passed'] : null, + 'compressions' => $compressions, + 'is_encoding_supported' => Encoding::isSupported(), + 'encodings' => Encoding::listEncodings(), + 'import_charset' => isset($cfg['Import']['charset']) ? $cfg['Import']['charset'] : null, + 'dbi' => $GLOBALS['dbi'], + 'disable_is' => $cfg['Server']['DisableIS'], + 'timeout_passed' => isset($timeoutPassed) ? $timeoutPassed : null, + 'offset' => isset($offset) ? $offset : null, + 'can_convert_kanji' => Encoding::canConvertKanji(), + ]); + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Display/ImportAjax.php b/php/apps/phpmyadmin49/html/libraries/classes/Display/ImportAjax.php new file mode 100644 index 00000000..abb5e714 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Display/ImportAjax.php @@ -0,0 +1,134 @@ + null, + + /** string Table name */ + 'table' => null, + + /** string the URL to go back in case of errors */ + 'goto' => null, + + /** string the SQL query */ + 'sql_query' => null, + + /** + * integer the total number of rows returned by the SQL query without any + * appended "LIMIT" clause programmatically + */ + 'unlim_num_rows' => null, + + /** array meta information about fields */ + 'fields_meta' => null, + + /** boolean */ + 'is_count' => null, + + /** integer */ + 'is_export' => null, + + /** boolean */ + 'is_func' => null, + + /** integer */ + 'is_analyse' => null, + + /** integer the total number of rows returned by the SQL query */ + 'num_rows' => null, + + /** integer the total number of fields returned by the SQL query */ + 'fields_cnt' => null, + + /** double time taken for execute the SQL query */ + 'querytime' => null, + + /** string path for theme images directory */ + 'pma_theme_image' => null, + + /** string */ + 'text_dir' => null, + + /** boolean */ + 'is_maint' => null, + + /** boolean */ + 'is_explain' => null, + + /** boolean */ + 'is_show' => null, + + /** boolean */ + 'is_browse_distinct' => null, + + /** array table definitions */ + 'showtable' => null, + + /** string */ + 'printview' => null, + + /** string URL query */ + 'url_query' => null, + + /** array column names to highlight */ + 'highlight_columns' => null, + + /** array holding various display information */ + 'display_params' => null, + + /** array mime types information of fields */ + 'mime_map' => null, + + /** boolean */ + 'editable' => null, + + /** random unique ID to distinguish result set */ + 'unique_id' => null, + + /** where clauses for each row, each table in the row */ + 'whereClauseMap' => array(), + ); + + /** + * This variable contains the column transformation information + * for some of the system databases. + * One element of this array represent all relevant columns in all tables in + * one specific database + */ + public $transformation_info; + + /** + * @var Relation $relation + */ + private $relation; + + /** + * Get any property of this class + * + * @param string $property name of the property + * + * @return mixed|void if property exist, value of the relevant property + */ + public function __get($property) + { + if (array_key_exists($property, $this->_property_array)) { + return $this->_property_array[$property]; + } + } + + /** + * Set values for any property of this class + * + * @param string $property name of the property + * @param mixed $value value to set + * + * @return void + */ + public function __set($property, $value) + { + if (array_key_exists($property, $this->_property_array)) { + $this->_property_array[$property] = $value; + } + } + + /** + * Constructor for PhpMyAdmin\Display\Results class + * + * @param string $db the database name + * @param string $table the table name + * @param string $goto the URL to go back in case of errors + * @param string $sql_query the SQL query + * + * @access public + */ + public function __construct($db, $table, $goto, $sql_query) + { + $this->relation = new Relation(); + + $this->_setDefaultTransformations(); + + $this->__set('db', $db); + $this->__set('table', $table); + $this->__set('goto', $goto); + $this->__set('sql_query', $sql_query); + $this->__set('unique_id', rand()); + } + + /** + * Sets default transformations for some columns + * + * @return void + */ + private function _setDefaultTransformations() + { + $json_highlighting_data = array( + 'libraries/classes/Plugins/Transformations/Output/Text_Plain_Json.php', + 'PhpMyAdmin\Plugins\Transformations\Output\Text_Plain_Json', + 'Text_Plain' + ); + $sql_highlighting_data = array( + 'libraries/classes/Plugins/Transformations/Output/Text_Plain_Sql.php', + 'PhpMyAdmin\Plugins\Transformations\Output\Text_Plain_Sql', + 'Text_Plain' + ); + $blob_sql_highlighting_data = array( + 'libraries/classes/Plugins/Transformations/Output/Text_Octetstream_Sql.php', + 'PhpMyAdmin\Plugins\Transformations\Output\Text_Octetstream_Sql', + 'Text_Octetstream' + ); + $link_data = array( + 'libraries/classes/Plugins/Transformations/Text_Plain_Link.php', + 'PhpMyAdmin\Plugins\Transformations\Text_Plain_Link', + 'Text_Plain' + ); + $this->transformation_info = array( + 'information_schema' => array( + 'events' => array( + 'event_definition' => $sql_highlighting_data + ), + 'processlist' => array( + 'info' => $sql_highlighting_data + ), + 'routines' => array( + 'routine_definition' => $sql_highlighting_data + ), + 'triggers' => array( + 'action_statement' => $sql_highlighting_data + ), + 'views' => array( + 'view_definition' => $sql_highlighting_data + ) + ), + 'mysql' => array( + 'event' => array( + 'body' => $blob_sql_highlighting_data, + 'body_utf8' => $blob_sql_highlighting_data + ), + 'general_log' => array( + 'argument' => $sql_highlighting_data + ), + 'help_category' => array( + 'url' => $link_data + ), + 'help_topic' => array( + 'example' => $sql_highlighting_data, + 'url' => $link_data + ), + 'proc' => array( + 'param_list' => $blob_sql_highlighting_data, + 'returns' => $blob_sql_highlighting_data, + 'body' => $blob_sql_highlighting_data, + 'body_utf8' => $blob_sql_highlighting_data + ), + 'slow_log' => array( + 'sql_text' => $sql_highlighting_data + ) + ) + ); + + $cfgRelation = $this->relation->getRelationsParam(); + if ($cfgRelation['db']) { + $this->transformation_info[$cfgRelation['db']] = array(); + $relDb = &$this->transformation_info[$cfgRelation['db']]; + if (! empty($cfgRelation['history'])) { + $relDb[$cfgRelation['history']] = array( + 'sqlquery' => $sql_highlighting_data + ); + } + if (! empty($cfgRelation['bookmark'])) { + $relDb[$cfgRelation['bookmark']] = array( + 'query' => $sql_highlighting_data + ); + } + if (! empty($cfgRelation['tracking'])) { + $relDb[$cfgRelation['tracking']] = array( + 'schema_sql' => $sql_highlighting_data, + 'data_sql' => $sql_highlighting_data + ); + } + if (! empty($cfgRelation['favorite'])) { + $relDb[$cfgRelation['favorite']] = array( + 'tables' => $json_highlighting_data + ); + } + if (! empty($cfgRelation['recent'])) { + $relDb[$cfgRelation['recent']] = array( + 'tables' => $json_highlighting_data + ); + } + if (! empty($cfgRelation['savedsearches'])) { + $relDb[$cfgRelation['savedsearches']] = array( + 'search_data' => $json_highlighting_data + ); + } + if (! empty($cfgRelation['designer_settings'])) { + $relDb[$cfgRelation['designer_settings']] = array( + 'settings_data' => $json_highlighting_data + ); + } + if (! empty($cfgRelation['table_uiprefs'])) { + $relDb[$cfgRelation['table_uiprefs']] = array( + 'prefs' => $json_highlighting_data + ); + } + if (! empty($cfgRelation['userconfig'])) { + $relDb[$cfgRelation['userconfig']] = array( + 'config_data' => $json_highlighting_data + ); + } + if (! empty($cfgRelation['export_templates'])) { + $relDb[$cfgRelation['export_templates']] = array( + 'template_data' => $json_highlighting_data + ); + } + } + } + + /** + * Set properties which were not initialized at the constructor + * + * @param integer $unlim_num_rows the total number of rows returned by + * the SQL query without any appended + * "LIMIT" clause programmatically + * @param array $fields_meta meta information about fields + * @param boolean $is_count statement is SELECT COUNT + * @param integer $is_export statement contains INTO OUTFILE + * @param boolean $is_func statement contains a function like SUM() + * @param integer $is_analyse statement contains PROCEDURE ANALYSE + * @param integer $num_rows total no. of rows returned by SQL query + * @param integer $fields_cnt total no.of fields returned by SQL query + * @param double $querytime time taken for execute the SQL query + * @param string $pmaThemeImage path for theme images directory + * @param string $text_dir text direction + * @param boolean $is_maint statement contains a maintenance command + * @param boolean $is_explain statement contains EXPLAIN + * @param boolean $is_show statement contains SHOW + * @param array $showtable table definitions + * @param string $printview print view was requested + * @param string $url_query URL query + * @param boolean $editable whether the results set is editable + * @param boolean $is_browse_dist whether browsing distinct values + * + * @return void + * + * @see sql.php + */ + public function setProperties( + $unlim_num_rows, $fields_meta, $is_count, $is_export, $is_func, + $is_analyse, $num_rows, $fields_cnt, $querytime, $pmaThemeImage, $text_dir, + $is_maint, $is_explain, $is_show, $showtable, $printview, $url_query, + $editable, $is_browse_dist + ) { + + $this->__set('unlim_num_rows', $unlim_num_rows); + $this->__set('fields_meta', $fields_meta); + $this->__set('is_count', $is_count); + $this->__set('is_export', $is_export); + $this->__set('is_func', $is_func); + $this->__set('is_analyse', $is_analyse); + $this->__set('num_rows', $num_rows); + $this->__set('fields_cnt', $fields_cnt); + $this->__set('querytime', $querytime); + $this->__set('pma_theme_image', $pmaThemeImage); + $this->__set('text_dir', $text_dir); + $this->__set('is_maint', $is_maint); + $this->__set('is_explain', $is_explain); + $this->__set('is_show', $is_show); + $this->__set('showtable', $showtable); + $this->__set('printview', $printview); + $this->__set('url_query', $url_query); + $this->__set('editable', $editable); + $this->__set('is_browse_distinct', $is_browse_dist); + + } // end of the 'setProperties()' function + + + /** + * Defines the parts to display for a print view + * + * @param array $displayParts the parts to display + * + * @return array $displayParts the modified display parts + * + * @access private + * + */ + private function _setDisplayPartsForPrintView(array $displayParts) + { + // set all elements to false! + $displayParts['edit_lnk'] = self::NO_EDIT_OR_DELETE; // no edit link + $displayParts['del_lnk'] = self::NO_EDIT_OR_DELETE; // no delete link + $displayParts['sort_lnk'] = (string) '0'; + $displayParts['nav_bar'] = (string) '0'; + $displayParts['bkm_form'] = (string) '0'; + $displayParts['text_btn'] = (string) '0'; + $displayParts['pview_lnk'] = (string) '0'; + + return $displayParts; + } + + /** + * Defines the parts to display for a SHOW statement + * + * @param array $displayParts the parts to display + * + * @return array $displayParts the modified display parts + * + * @access private + * + */ + private function _setDisplayPartsForShow(array $displayParts) + { + preg_match( + '@^SHOW[[:space:]]+(VARIABLES|(FULL[[:space:]]+)?' + . 'PROCESSLIST|STATUS|TABLE|GRANTS|CREATE|LOGS|DATABASES|FIELDS' + . ')@i', + $this->__get('sql_query'), $which + ); + + $bIsProcessList = isset($which[1]); + if ($bIsProcessList) { + $str = ' ' . strtoupper($which[1]); + $bIsProcessList = $bIsProcessList + && strpos($str, 'PROCESSLIST') > 0; + } + + if ($bIsProcessList) { + // no edit link + $displayParts['edit_lnk'] = self::NO_EDIT_OR_DELETE; + // "kill process" type edit link + $displayParts['del_lnk'] = self::KILL_PROCESS; + } else { + // Default case -> no links + // no edit link + $displayParts['edit_lnk'] = self::NO_EDIT_OR_DELETE; + // no delete link + $displayParts['del_lnk'] = self::NO_EDIT_OR_DELETE; + } + // Other settings + $displayParts['sort_lnk'] = (string) '0'; + $displayParts['nav_bar'] = (string) '0'; + $displayParts['bkm_form'] = (string) '1'; + $displayParts['text_btn'] = (string) '1'; + $displayParts['pview_lnk'] = (string) '1'; + + return $displayParts; + } + + /** + * Defines the parts to display for statements not related to data + * + * @param array $displayParts the parts to display + * + * @return array $displayParts the modified display parts + * + * @access private + * + */ + private function _setDisplayPartsForNonData(array $displayParts) + { + // Statement is a "SELECT COUNT", a + // "CHECK/ANALYZE/REPAIR/OPTIMIZE/CHECKSUM", an "EXPLAIN" one or + // contains a "PROC ANALYSE" part + $displayParts['edit_lnk'] = self::NO_EDIT_OR_DELETE; // no edit link + $displayParts['del_lnk'] = self::NO_EDIT_OR_DELETE; // no delete link + $displayParts['sort_lnk'] = (string) '0'; + $displayParts['nav_bar'] = (string) '0'; + $displayParts['bkm_form'] = (string) '1'; + + if ($this->__get('is_maint')) { + $displayParts['text_btn'] = (string) '1'; + } else { + $displayParts['text_btn'] = (string) '0'; + } + $displayParts['pview_lnk'] = (string) '1'; + + return $displayParts; + } + + /** + * Defines the parts to display for other statements (probably SELECT) + * + * @param array $displayParts the parts to display + * + * @return array $displayParts the modified display parts + * + * @access private + * + */ + private function _setDisplayPartsForSelect(array $displayParts) + { + // Other statements (ie "SELECT" ones) -> updates + // $displayParts['edit_lnk'], $displayParts['del_lnk'] and + // $displayParts['text_btn'] (keeps other default values) + + $fields_meta = $this->__get('fields_meta'); + $prev_table = ''; + $displayParts['text_btn'] = (string) '1'; + $number_of_columns = $this->__get('fields_cnt'); + + for ($i = 0; $i < $number_of_columns; $i++) { + + $is_link = ($displayParts['edit_lnk'] != self::NO_EDIT_OR_DELETE) + || ($displayParts['del_lnk'] != self::NO_EDIT_OR_DELETE) + || ($displayParts['sort_lnk'] != '0'); + + // Displays edit/delete/sort/insert links? + if ($is_link + && $prev_table != '' + && $fields_meta[$i]->table != '' + && $fields_meta[$i]->table != $prev_table + ) { + // don't display links + $displayParts['edit_lnk'] = self::NO_EDIT_OR_DELETE; + $displayParts['del_lnk'] = self::NO_EDIT_OR_DELETE; + /** + * @todo May be problematic with same field names + * in two joined table. + */ + // $displayParts['sort_lnk'] = (string) '0'; + if ($displayParts['text_btn'] == '1') { + break; + } + } // end if + + // Always display print view link + $displayParts['pview_lnk'] = (string) '1'; + if ($fields_meta[$i]->table != '') { + $prev_table = $fields_meta[$i]->table; + } + } // end for + + if ($prev_table == '') { // no table for any of the columns + // don't display links + $displayParts['edit_lnk'] = self::NO_EDIT_OR_DELETE; + $displayParts['del_lnk'] = self::NO_EDIT_OR_DELETE; + } + + return $displayParts; + } + + /** + * Defines the parts to display for the results of a SQL query + * and the total number of rows + * + * @param array $displayParts the parts to display (see a few + * lines above for explanations) + * + * @return array the first element is an array with explicit indexes + * for all the display elements + * the second element is the total number of rows returned + * by the SQL query without any programmatically appended + * LIMIT clause (just a copy of $unlim_num_rows if it exists, + * else computed inside this function) + * + * + * @access private + * + * @see getTable() + */ + private function _setDisplayPartsAndTotal(array $displayParts) + { + $the_total = 0; + + // 1. Following variables are needed for use in isset/empty or + // use with array indexes or safe use in foreach + $db = $this->__get('db'); + $table = $this->__get('table'); + $unlim_num_rows = $this->__get('unlim_num_rows'); + $num_rows = $this->__get('num_rows'); + $printview = $this->__get('printview'); + + // 2. Updates the display parts + if ($printview == '1') { + $displayParts = $this->_setDisplayPartsForPrintView($displayParts); + + } elseif ($this->__get('is_count') || $this->__get('is_analyse') + || $this->__get('is_maint') || $this->__get('is_explain') + ) { + $displayParts = $this->_setDisplayPartsForNonData($displayParts); + + } elseif ($this->__get('is_show')) { + $displayParts = $this->_setDisplayPartsForShow($displayParts); + + } else { + $displayParts = $this->_setDisplayPartsForSelect($displayParts); + } // end if..elseif...else + + // 3. Gets the total number of rows if it is unknown + if (isset($unlim_num_rows) && $unlim_num_rows != '') { + $the_total = $unlim_num_rows; + } elseif ((($displayParts['nav_bar'] == '1') + || ($displayParts['sort_lnk'] == '1')) + && (strlen($db) > 0 && strlen($table) > 0) + ) { + $the_total = $GLOBALS['dbi']->getTable($db, $table)->countRecords(); + } + + // if for COUNT query, number of rows returned more than 1 + // (may be being used GROUP BY) + if ($this->__get('is_count') && isset($num_rows) && $num_rows > 1) { + $displayParts['nav_bar'] = (string) '1'; + $displayParts['sort_lnk'] = (string) '1'; + } + // 4. If navigation bar or sorting fields names URLs should be + // displayed but there is only one row, change these settings to + // false + if ($displayParts['nav_bar'] == '1' || $displayParts['sort_lnk'] == '1') { + + // - Do not display sort links if less than 2 rows. + // - For a VIEW we (probably) did not count the number of rows + // so don't test this number here, it would remove the possibility + // of sorting VIEW results. + $_table = new Table($table, $db); + if (isset($unlim_num_rows) + && ($unlim_num_rows < 2) + && ! $_table->isView() + ) { + $displayParts['sort_lnk'] = (string) '0'; + } + } // end if (3) + + return array($displayParts, $the_total); + + } // end of the 'setDisplayPartsAndTotal()' function + + + /** + * Return true if we are executing a query in the form of + * "SELECT * FROM ..." + * + * @param array $analyzed_sql_results analyzed sql results + * + * @return boolean + * + * @access private + * + * @see _getTableHeaders(), _getColumnParams() + */ + private function _isSelect(array $analyzed_sql_results) + { + return ! ($this->__get('is_count') + || $this->__get('is_export') + || $this->__get('is_func') + || $this->__get('is_analyse')) + && !empty($analyzed_sql_results['select_from']) + && !empty($analyzed_sql_results['statement']->from) + && (count($analyzed_sql_results['statement']->from) == 1) + && !empty($analyzed_sql_results['statement']->from[0]->table); + } + + + /** + * Get a navigation button + * + * @param string $caption iconic caption for button + * @param string $title text for button + * @param integer $pos position for next query + * @param string $html_sql_query query ready for display + * @param boolean $back whether 'begin' or 'previous' + * @param string $onsubmit optional onsubmit clause + * @param string $input_for_real_end optional hidden field for special treatment + * @param string $onclick optional onclick clause + * + * @return string html content + * + * @access private + * + * @see _getMoveBackwardButtonsForTableNavigation(), + * _getMoveForwardButtonsForTableNavigation() + */ + private function _getTableNavigationButton( + $caption, + $title, + $pos, + $html_sql_query, + $back, + $onsubmit = '', + $input_for_real_end = '', + $onclick = '' + ) { + $caption_output = ''; + if ($back) { + if (Util::showIcons('TableNavigationLinksMode')) { + $caption_output .= $caption; + } + if (Util::showText('TableNavigationLinksMode')) { + $caption_output .= ' ' . $title; + } + } else { + if (Util::showText('TableNavigationLinksMode')) { + $caption_output .= $title; + } + if (Util::showIcons('TableNavigationLinksMode')) { + $caption_output .= ' ' . $caption; + } + } + + return Template::get('display/results/table_navigation_button')->render([ + 'db' => $this->__get('db'), + 'table' => $this->__get('table'), + 'sql_query' => $html_sql_query, + 'pos' => $pos, + 'is_browse_distinct' => $this->__get('is_browse_distinct'), + 'goto' => $this->__get('goto'), + 'input_for_real_end' => $input_for_real_end, + 'caption_output' => $caption_output, + 'title' => $title, + 'onsubmit' => $onsubmit, + 'onclick' => $onclick, + ]); + } + + /** + * Possibly return a page selector for table navigation + * + * @param string $table_navigation_html the current navigation HTML + * + * @return array ($table_navigation_html, $nbTotalPage) + * + * @access private + * + */ + private function _getHtmlPageSelector($table_navigation_html) + { + $pageNow = @floor( + $_SESSION['tmpval']['pos'] + / $_SESSION['tmpval']['max_rows'] + ) + 1; + + $nbTotalPage = @ceil( + $this->__get('unlim_num_rows') + / $_SESSION['tmpval']['max_rows'] + ); + + if ($nbTotalPage > 1) { + $table_navigation_html .= ''; + $_url_params = array( + 'db' => $this->__get('db'), + 'table' => $this->__get('table'), + 'sql_query' => $this->__get('sql_query'), + 'goto' => $this->__get('goto'), + 'is_browse_distinct' => $this->__get('is_browse_distinct'), + ); + + //
      to keep the form alignment of button < and << + // and also to know what to execute when the selector changes + $table_navigation_html .= ''; + $table_navigation_html .= Url::getHiddenInputs($_url_params); + + $table_navigation_html .= Util::pageselector( + 'pos', + $_SESSION['tmpval']['max_rows'], + $pageNow, $nbTotalPage, 200, 5, 5, 20, 10 + ); + + $table_navigation_html .= '
      ' + . ''; + } + return array($table_navigation_html, $nbTotalPage); + } + + /** + * Get a navigation bar to browse among the results of a SQL query + * + * @param integer $pos_next the offset for the "next" page + * @param integer $pos_prev the offset for the "previous" page + * @param boolean $is_innodb whether its InnoDB or not + * @param string $sort_by_key_html the sort by key dialog + * + * @return string html content + * + * @access private + * + * @see _getTable() + */ + private function _getTableNavigation( + $pos_next, $pos_prev, $is_innodb, $sort_by_key_html + ) { + + $table_navigation_html = ''; + + // here, using htmlentities() would cause problems if the query + // contains accented characters + $html_sql_query = htmlspecialchars($this->__get('sql_query')); + + // Navigation bar + $table_navigation_html + .= '' + . '' + . ''; + + // Move to the beginning or to the previous page + if ($_SESSION['tmpval']['pos'] + && ($_SESSION['tmpval']['max_rows'] != self::ALL_ROWS) + ) { + + $table_navigation_html + .= $this->_getMoveBackwardButtonsForTableNavigation( + $html_sql_query, $pos_prev + ); + + } // end move back + + $nbTotalPage = 1; + //page redirection + // (unless we are showing all records) + if ($_SESSION['tmpval']['max_rows'] != self::ALL_ROWS) { + list( + $table_navigation_html, + $nbTotalPage + ) = $this->_getHtmlPageSelector($table_navigation_html); + } + + $showing_all = false; + if ($_SESSION['tmpval']['max_rows'] == self::ALL_ROWS) { + $showing_all = true; + } + + // Move to the next page or to the last one + if ($this->__get('unlim_num_rows') === false // view with unknown number of rows + || ($_SESSION['tmpval']['max_rows'] != self::ALL_ROWS + && $_SESSION['tmpval']['pos'] + $_SESSION['tmpval']['max_rows'] < $this->__get('unlim_num_rows') + && $this->__get('num_rows') >= $_SESSION['tmpval']['max_rows']) + ) { + + $table_navigation_html + .= $this->_getMoveForwardButtonsForTableNavigation( + $html_sql_query, $pos_next, $is_innodb + ); + + } // end move toward + + // show separator if pagination happen + if ($nbTotalPage > 1) { + $table_navigation_html + .= ''; + } + + // Display the "Show all" button if allowed + if ($GLOBALS['cfg']['ShowAll'] || ($this->__get('unlim_num_rows') <= 500) ) { + + $table_navigation_html .= $this->_getShowAllCheckboxForTableNavigation( + $showing_all, $html_sql_query + ); + + $table_navigation_html + .= ''; + + } // end show all + + $table_navigation_html .= '' + . ''; + + // if displaying a VIEW, $unlim_num_rows could be zero because + // of $cfg['MaxExactCountViews']; in this case, avoid passing + // the 5th parameter to checkFormElementInRange() + // (this means we can't validate the upper limit + $table_navigation_html .= '' + . '' + . ''; + + $table_navigation_html .= ''; + + $table_navigation_html .= '' + . '' + . ''; + + return $table_navigation_html; + + } // end of the '_getTableNavigation()' function + + + /** + * Prepare move backward buttons - previous and first + * + * @param string $html_sql_query the sql encoded by html special characters + * @param integer $pos_prev the offset for the "previous" page + * + * @return string html content + * + * @access private + * + * @see _getTableNavigation() + */ + private function _getMoveBackwardButtonsForTableNavigation( + $html_sql_query, $pos_prev + ) { + return $this->_getTableNavigationButton( + '<<', _pgettext('First page', 'Begin'), 0, $html_sql_query, true + ) + . $this->_getTableNavigationButton( + '<', _pgettext('Previous page', 'Previous'), $pos_prev, + $html_sql_query, true + ); + } // end of the '_getMoveBackwardButtonsForTableNavigation()' function + + + /** + * Prepare Show All checkbox for table navigation + * + * @param bool $showing_all whether all rows are shown currently + * @param string $html_sql_query the sql encoded by html special characters + * + * @return string html content + * + * @access private + * + * @see _getTableNavigation() + */ + private function _getShowAllCheckboxForTableNavigation( + $showing_all, $html_sql_query + ) { + return Template::get('display/results/show_all_checkbox')->render([ + 'db' => $this->__get('db'), + 'table' => $this->__get('table'), + 'is_browse_distinct' => $this->__get('is_browse_distinct'), + 'goto' => $this->__get('goto'), + 'unique_id' => $this->__get('unique_id'), + 'html_sql_query' => $html_sql_query, + 'showing_all' => $showing_all, + 'max_rows' => intval($GLOBALS['cfg']['MaxRows']), + ]); + } // end of the '_getShowAllButtonForTableNavigation()' function + + + /** + * Prepare move forward buttons - next and last + * + * @param string $html_sql_query the sql encoded by htmlspecialchars() + * @param integer $pos_next the offset for the "next" page + * @param boolean $is_innodb whether it's InnoDB or not + * + * @return string $buttons_html html content + * + * @access private + * + * @see _getTableNavigation() + */ + private function _getMoveForwardButtonsForTableNavigation( + $html_sql_query, $pos_next, $is_innodb + ) { + + // display the Next button + $buttons_html = $this->_getTableNavigationButton( + '>', + _pgettext('Next page', 'Next'), + $pos_next, + $html_sql_query, + false + ); + + // prepare some options for the End button + if ($is_innodb + && $this->__get('unlim_num_rows') > $GLOBALS['cfg']['MaxExactCount'] + ) { + $input_for_real_end = ''; + // no backquote around this message + $onclick = ''; + } else { + $input_for_real_end = $onclick = ''; + } + + $maxRows = $_SESSION['tmpval']['max_rows']; + $onsubmit = 'onsubmit="return ' + . (($_SESSION['tmpval']['pos'] + + $maxRows + < $this->__get('unlim_num_rows') + && $this->__get('num_rows') >= $maxRows) + ? 'true' + : 'false') . '"'; + + // display the End button + $buttons_html .= $this->_getTableNavigationButton( + '>>', + _pgettext('Last page', 'End'), + @((ceil( + $this->__get('unlim_num_rows') + / $_SESSION['tmpval']['max_rows'] + )- 1) * $maxRows), + $html_sql_query, false, $onsubmit, $input_for_real_end, $onclick + ); + + return $buttons_html; + + } // end of the '_getMoveForwardButtonsForTableNavigation()' function + + + /** + * Prepare fields for table navigation + * Number of rows + * + * @param string $sqlQuery the sql encoded by htmlspecialchars() + * + * @return string html content + * + * @access private + * + * @see _getTableNavigation() + */ + private function _getAdditionalFieldsForTableNavigation($sqlQuery) + { + $numberOfRowsPlaceholder = null; + if ($_SESSION['tmpval']['max_rows'] == self::ALL_ROWS) { + $numberOfRowsPlaceholder = __('All'); + } + + $numberOfRowsChoices = array( + '25' => 25, + '50' => 50, + '100' => 100, + '250' => 250, + '500' => 500 + ); + + return Template::get('display/results/additional_fields')->render([ + 'goto' => $this->__get('goto'), + 'is_browse_distinct' => $this->__get('is_browse_distinct'), + 'sql_query' => $sqlQuery, + 'number_of_rows_choices' => $numberOfRowsChoices, + 'number_of_rows_placeholder' => $numberOfRowsPlaceholder, + 'pos' => $_SESSION['tmpval']['pos'], + 'max_rows' => $_SESSION['tmpval']['max_rows'], + ]); + } + + /** + * Get the headers of the results table, for all of the columns + * + * @param array $displayParts which elements to display + * @param array $analyzed_sql_results analyzed sql results + * @param array $sort_expression sort expression + * @param array $sort_expression_nodirection sort expression + * without direction + * @param array $sort_direction sort direction + * @param boolean $is_limited_display with limited operations + * or not + * @param string $unsorted_sql_query query without the sort part + * + * @return string html content + * + * @access private + * + * @see getTableHeaders() + */ + private function _getTableHeadersForColumns( + array $displayParts, array $analyzed_sql_results, array $sort_expression, + array $sort_expression_nodirection, array $sort_direction, $is_limited_display, + $unsorted_sql_query + ) { + $html = ''; + + // required to generate sort links that will remember whether the + // "Show all" button has been clicked + $sql_md5 = md5($this->__get('sql_query')); + $session_max_rows = $is_limited_display + ? 0 + : $_SESSION['tmpval']['query'][$sql_md5]['max_rows']; + + // Following variable are needed for use in isset/empty or + // use with array indexes/safe use in the for loop + $highlight_columns = $this->__get('highlight_columns'); + $fields_meta = $this->__get('fields_meta'); + + // Prepare Display column comments if enabled + // ($GLOBALS['cfg']['ShowBrowseComments']). + $comments_map = $this->_getTableCommentsArray($analyzed_sql_results); + + list($col_order, $col_visib) = $this->_getColumnParams( + $analyzed_sql_results + ); + + // optimize: avoid calling a method on each iteration + $number_of_columns = $this->__get('fields_cnt'); + + for ($j = 0; $j < $number_of_columns; $j++) { + // PHP 7.4 fix for accessing array offset on bool + $col_visib_current = is_array($col_visib) ? $col_visib[$j] : null; + + // assign $i with the appropriate column order + $i = $col_order ? $col_order[$j] : $j; + + // See if this column should get highlight because it's used in the + // where-query. + $name = $fields_meta[$i]->name; + $condition_field = (isset($highlight_columns[$name]) + || isset($highlight_columns[Util::backquote($name)])) + ? true + : false; + + // Prepare comment-HTML-wrappers for each row, if defined/enabled. + $comments = $this->_getCommentForRow($comments_map, $fields_meta[$i]); + $display_params = $this->__get('display_params'); + + if (($displayParts['sort_lnk'] == '1') && ! $is_limited_display) { + + list($order_link, $sorted_header_html) + = $this->_getOrderLinkAndSortedHeaderHtml( + $fields_meta[$i], $sort_expression, + $sort_expression_nodirection, $i, $unsorted_sql_query, + $session_max_rows, $comments, + $sort_direction, $col_visib, + $col_visib_current + ); + + $html .= $sorted_header_html; + + $display_params['desc'][] = ' ' . "\n" . $order_link . $comments . ' ' . "\n"; + } else { + // Results can't be sorted + $html + .= $this->_getDraggableClassForNonSortableColumns( + $col_visib, $col_visib_current, $condition_field, + $fields_meta[$i], $comments + ); + + $display_params['desc'][] = ' ' . ' ' + . htmlspecialchars($fields_meta[$i]->name) + . $comments . ' '; + } // end else + + $this->__set('display_params', $display_params); + + } // end for + return $html; + } + + /** + * Get the headers of the results table + * + * @param array &$displayParts which elements to display + * @param array $analyzed_sql_results analyzed sql results + * @param string $unsorted_sql_query the unsorted sql query + * @param array $sort_expression sort expression + * @param array|string $sort_expression_nodirection sort expression + * without direction + * @param array $sort_direction sort direction + * @param boolean $is_limited_display with limited operations + * or not + * + * @return string html content + * + * @access private + * + * @see getTable() + */ + private function _getTableHeaders( + array &$displayParts, array $analyzed_sql_results, $unsorted_sql_query, + array $sort_expression = array(), $sort_expression_nodirection = '', + array $sort_direction = array(), $is_limited_display = false + ) { + + $table_headers_html = ''; + // Needed for use in isset/empty or + // use with array indexes/safe use in foreach + $printview = $this->__get('printview'); + $display_params = $this->__get('display_params'); + + // Output data needed for grid editing + $table_headers_html .= '' + . '
      ' + . Url::getHiddenInputs( + $this->__get('db'), $this->__get('table') + ) + . '
      '; + + // Output data needed for column reordering and show/hide column + $table_headers_html .= $this->_getDataForResettingColumnOrder($analyzed_sql_results); + + $display_params['emptypre'] = 0; + $display_params['emptyafter'] = 0; + $display_params['textbtn'] = ''; + $full_or_partial_text_link = null; + + $this->__set('display_params', $display_params); + + // Display options (if we are not in print view) + if (! (isset($printview) && ($printview == '1')) && ! $is_limited_display) { + + $table_headers_html .= $this->_getOptionsBlock(); + + // prepare full/partial text button or link + $full_or_partial_text_link = $this->_getFullOrPartialTextButtonOrLink(); + } + + // Start of form for multi-rows edit/delete/export + $table_headers_html .= $this->_getFormForMultiRowOperations( + $displayParts['del_lnk'] + ); + + // 1. Set $colspan and generate html with full/partial + // text button or link + list($colspan, $button_html) + = $this->_getFieldVisibilityParams( + $displayParts, $full_or_partial_text_link + ); + + $table_headers_html .= $button_html; + + // 2. Displays the fields' name + // 2.0 If sorting links should be used, checks if the query is a "JOIN" + // statement (see 2.1.3) + + // See if we have to highlight any header fields of a WHERE query. + // Uses SQL-Parser results. + $this->_setHighlightedColumnGlobalField($analyzed_sql_results); + + // Get the headers for all of the columns + $table_headers_html .= $this->_getTableHeadersForColumns( + $displayParts, $analyzed_sql_results, $sort_expression, + $sort_expression_nodirection, $sort_direction, + $is_limited_display, $unsorted_sql_query + ); + + // Display column at rightside - checkboxes or empty column + if (! $printview) { + $table_headers_html .= $this->_getColumnAtRightSide( + $displayParts, $full_or_partial_text_link, $colspan + ); + } + $table_headers_html .= '' . ''; + + return $table_headers_html; + + } // end of the '_getTableHeaders()' function + + + /** + * Prepare unsorted sql query and sort by key drop down + * + * @param array $analyzed_sql_results analyzed sql results + * @param array|null $sort_expression sort expression + * + * @return array two element array - $unsorted_sql_query, $drop_down_html + * + * @access private + * + * @see _getTableHeaders() + */ + private function _getUnsortedSqlAndSortByKeyDropDown( + array $analyzed_sql_results, array $sort_expression + ) { + $drop_down_html = ''; + + $unsorted_sql_query = Query::replaceClause( + $analyzed_sql_results['statement'], + $analyzed_sql_results['parser']->list, + 'ORDER BY', + '' + ); + + // Data is sorted by indexes only if it there is only one table. + if ($this->_isSelect($analyzed_sql_results)) { + // grab indexes data: + $indexes = Index::getFromTable( + $this->__get('table'), + $this->__get('db') + ); + + // do we have any index? + if (! empty($indexes)) { + $drop_down_html = $this->_getSortByKeyDropDown( + $indexes, $sort_expression, + $unsorted_sql_query + ); + } + } + + return array($unsorted_sql_query, $drop_down_html); + + } // end of the '_getUnsortedSqlAndSortByKeyDropDown()' function + + /** + * Prepare sort by key dropdown - html code segment + * + * @param Index[] $indexes the indexes of the table for sort criteria + * @param array|null $sort_expression the sort expression + * @param string $unsorted_sql_query the unsorted sql query + * + * @return string $drop_down_html html content + * + * @access private + * + * @see _getTableHeaders() + */ + private function _getSortByKeyDropDown( + $indexes, array $sort_expression, $unsorted_sql_query + ) { + + $drop_down_html = ''; + + $drop_down_html .= '
      ' . "\n" + . Url::getHiddenInputs( + $this->__get('db'), $this->__get('table') + ) + . Url::getHiddenFields(array('sort_by_key' => '1')) + . __('Sort by key') + . ': ' . "\n" + . '
      ' . "\n"; + + return $drop_down_html; + + } // end of the '_getSortByKeyDropDown()' function + + + /** + * Set column span, row span and prepare html with full/partial + * text button or link + * + * @param array &$displayParts which elements to display + * @param string $full_or_partial_text_link full/partial link or text button + * + * @return array 2 element array - $colspan, $button_html + * + * @access private + * + * @see _getTableHeaders() + */ + private function _getFieldVisibilityParams( + array &$displayParts, $full_or_partial_text_link + ) { + + $button_html = ''; + $display_params = $this->__get('display_params'); + + // 1. Displays the full/partial text button (part 1)... + $button_html .= '' . "\n"; + + $colspan = (($displayParts['edit_lnk'] != self::NO_EDIT_OR_DELETE) + && ($displayParts['del_lnk'] != self::NO_EDIT_OR_DELETE)) + ? ' colspan="4"' + : ''; + + // ... before the result table + if ((($displayParts['edit_lnk'] == self::NO_EDIT_OR_DELETE) + && ($displayParts['del_lnk'] == self::NO_EDIT_OR_DELETE)) + && ($displayParts['text_btn'] == '1') + ) { + + $display_params['emptypre'] + = (($displayParts['edit_lnk'] != self::NO_EDIT_OR_DELETE) + && ($displayParts['del_lnk'] != self::NO_EDIT_OR_DELETE)) ? 4 : 0; + + } elseif ((($GLOBALS['cfg']['RowActionLinks'] == self::POSITION_LEFT) + || ($GLOBALS['cfg']['RowActionLinks'] == self::POSITION_BOTH)) + && ($displayParts['text_btn'] == '1') + ) { + // ... at the left column of the result table header if possible + // and required + + $display_params['emptypre'] + = (($displayParts['edit_lnk'] != self::NO_EDIT_OR_DELETE) + && ($displayParts['del_lnk'] != self::NO_EDIT_OR_DELETE)) ? 4 : 0; + + $button_html .= '' . $full_or_partial_text_link . ''; + + } elseif ((($GLOBALS['cfg']['RowActionLinks'] == self::POSITION_LEFT) + || ($GLOBALS['cfg']['RowActionLinks'] == self::POSITION_BOTH)) + && (($displayParts['edit_lnk'] != self::NO_EDIT_OR_DELETE) + || ($displayParts['del_lnk'] != self::NO_EDIT_OR_DELETE)) + ) { + // ... elseif no button, displays empty(ies) col(s) if required + + $display_params['emptypre'] + = (($displayParts['edit_lnk'] != self::NO_EDIT_OR_DELETE) + && ($displayParts['del_lnk'] != self::NO_EDIT_OR_DELETE)) ? 4 : 0; + + $button_html .= ''; + + } elseif (($GLOBALS['cfg']['RowActionLinks'] == self::POSITION_NONE)) { + // ... elseif display an empty column if the actions links are + // disabled to match the rest of the table + $button_html .= ''; + } + + $this->__set('display_params', $display_params); + + return array($colspan, $button_html); + + } // end of the '_getFieldVisibilityParams()' function + + + /** + * Get table comments as array + * + * @param array $analyzed_sql_results analyzed sql results + * + * @return array $comments_map table comments + * + * @access private + * + * @see _getTableHeaders() + */ + private function _getTableCommentsArray(array $analyzed_sql_results) + { + if ((!$GLOBALS['cfg']['ShowBrowseComments']) + || (empty($analyzed_sql_results['statement']->from)) + ) { + return array(); + } + + $ret = array(); + foreach ($analyzed_sql_results['statement']->from as $field) { + if (empty($field->table)) { + continue; + } + $ret[$field->table] = $this->relation->getComments( + empty($field->database) ? $this->__get('db') : $field->database, + $field->table + ); + } + + return $ret; + + } // end of the '_getTableCommentsArray()' function + + + /** + * Set global array for store highlighted header fields + * + * @param array $analyzed_sql_results analyzed sql results + * + * @return void + * + * @access private + * + * @see _getTableHeaders() + */ + private function _setHighlightedColumnGlobalField(array $analyzed_sql_results) + { + $highlight_columns = array(); + + if (!empty($analyzed_sql_results['statement']->where)) { + foreach ($analyzed_sql_results['statement']->where as $expr) { + foreach ($expr->identifiers as $identifier) { + $highlight_columns[$identifier] = 'true'; + } + } + } + + $this->__set('highlight_columns', $highlight_columns); + + } // end of the '_setHighlightedColumnGlobalField()' function + + + /** + * Prepare data for column restoring and show/hide + * + * @param array $analyzed_sql_results analyzed sql results + * + * @return string $data_html html content + * + * @access private + * + * @see _getTableHeaders() + */ + private function _getDataForResettingColumnOrder(array $analyzed_sql_results) + { + if (! $this->_isSelect($analyzed_sql_results)) { + return ''; + } + + // generate the column order, if it is set + list($col_order, $col_visib) = $this->_getColumnParams( + $analyzed_sql_results + ); + + $data_html = ''; + + if ($col_order) { + $data_html .= ''; + } + + if ($col_visib) { + $data_html .= ''; + } + + // generate table create time + $table = new Table($this->__get('table'), $this->__get('db')); + if (! $table->isView()) { + $data_html .= ''; + } + + return $data_html; + + } // end of the '_getDataForResettingColumnOrder()' function + + + /** + * Prepare option fields block + * + * @return string html content + * + * @access private + * + * @see _getTableHeaders() + */ + private function _getOptionsBlock() + { + if(isset($_SESSION['tmpval']['possible_as_geometry']) && $_SESSION['tmpval']['possible_as_geometry'] == false) { + if($_SESSION['tmpval']['geoOption'] == self::GEOMETRY_DISP_GEOM) { + $_SESSION['tmpval']['geoOption'] = self::GEOMETRY_DISP_WKT; + } + } + return Template::get('display/results/options_block')->render([ + 'unique_id' => $this->__get('unique_id'), + 'geo_option' => $_SESSION['tmpval']['geoOption'], + 'hide_transformation' => $_SESSION['tmpval']['hide_transformation'], + 'display_blob' => $_SESSION['tmpval']['display_blob'], + 'display_binary' => $_SESSION['tmpval']['display_binary'], + 'relational_display' => $_SESSION['tmpval']['relational_display'], + 'displaywork' => $GLOBALS['cfgRelation']['displaywork'], + 'relwork' => $GLOBALS['cfgRelation']['relwork'], + 'possible_as_geometry' => $_SESSION['tmpval']['possible_as_geometry'], + 'pftext' => $_SESSION['tmpval']['pftext'], + 'db' => $this->__get('db'), + 'table' => $this->__get('table'), + 'sql_query' => $this->__get('sql_query'), + 'goto' => $this->__get('goto'), + ]); + } + + /** + * Get full/partial text button or link + * + * @return string html content + * + * @access private + * + * @see _getTableHeaders() + */ + private function _getFullOrPartialTextButtonOrLink() + { + + $url_params_full_text = array( + 'db' => $this->__get('db'), + 'table' => $this->__get('table'), + 'sql_query' => $this->__get('sql_query'), + 'goto' => $this->__get('goto'), + 'full_text_button' => 1 + ); + + if ($_SESSION['tmpval']['pftext'] == self::DISPLAY_FULL_TEXT) { + // currently in fulltext mode so show the opposite link + $tmp_image_file = $this->__get('pma_theme_image') . 's_partialtext.png'; + $tmp_txt = __('Partial texts'); + $url_params_full_text['pftext'] = self::DISPLAY_PARTIAL_TEXT; + } else { + $tmp_image_file = $this->__get('pma_theme_image') . 's_fulltext.png'; + $tmp_txt = __('Full texts'); + $url_params_full_text['pftext'] = self::DISPLAY_FULL_TEXT; + } + + $tmp_image = ''
+                     . $tmp_txt . ''; + $tmp_url = 'sql.php' . Url::getCommon($url_params_full_text); + + return Util::linkOrButton($tmp_url, $tmp_image); + + } // end of the '_getFullOrPartialTextButtonOrLink()' function + + + /** + * Prepare html form for multi row operations + * + * @param string $deleteLink the delete link of current row + * + * @return string $form_html html content + * + * @access private + * + * @see _getTableHeaders() + */ + private function _getFormForMultiRowOperations($deleteLink) + { + return Template::get('display/results/multi_row_operations_form')->render([ + 'delete_link' => $deleteLink, + 'delete_row' => self::DELETE_ROW, + 'kill_process' => self::KILL_PROCESS, + 'unique_id' => $this->__get('unique_id'), + 'db' => $this->__get('db'), + 'table' => $this->__get('table'), + ]); + } + + /** + * Get comment for row + * + * @param array $commentsMap comments array + * @param array $fieldsMeta set of field properties + * + * @return string html content + * + * @access private + * + * @see _getTableHeaders() + */ + private function _getCommentForRow(array $commentsMap, $fieldsMeta) + { + return Template::get('display/results/comment_for_row')->render([ + 'comments_map' => $commentsMap, + 'fields_meta' => $fieldsMeta, + 'limit_chars' => $GLOBALS['cfg']['LimitChars'], + ]); + } + + /** + * Prepare parameters and html for sorted table header fields + * + * @param array $fields_meta set of field properties + * @param array $sort_expression sort expression + * @param array $sort_expression_nodirection sort expression without direction + * @param integer $column_index the index of the column + * @param string $unsorted_sql_query the unsorted sql query + * @param integer $session_max_rows maximum rows resulted by sql + * @param string $comments comment for row + * @param array $sort_direction sort direction + * @param boolean $col_visib column is visible(false) + * array column isn't visible(string array) + * @param string $col_visib_j element of $col_visib array + * + * @return array 2 element array - $order_link, $sorted_header_html + * + * @access private + * + * @see _getTableHeaders() + */ + private function _getOrderLinkAndSortedHeaderHtml( + $fields_meta, array $sort_expression, array $sort_expression_nodirection, + $column_index, $unsorted_sql_query, $session_max_rows, + $comments, array $sort_direction, $col_visib, $col_visib_j + ) { + + $sorted_header_html = ''; + + // Checks if the table name is required; it's the case + // for a query with a "JOIN" statement and if the column + // isn't aliased, or in queries like + // SELECT `1`.`master_field` , `2`.`master_field` + // FROM `PMA_relation` AS `1` , `PMA_relation` AS `2` + + $sort_tbl = (isset($fields_meta->table) + && strlen($fields_meta->table) > 0 + && $fields_meta->orgname == $fields_meta->name) + ? Util::backquote( + $fields_meta->table + ) . '.' + : ''; + + $name_to_use_in_sort = $fields_meta->name; + + // Generates the orderby clause part of the query which is part + // of URL + list($single_sort_order, $multi_sort_order, $order_img) + = $this->_getSingleAndMultiSortUrls( + $sort_expression, $sort_expression_nodirection, $sort_tbl, + $name_to_use_in_sort, $sort_direction, $fields_meta, $column_index + ); + + if (preg_match( + '@(.*)([[:space:]](LIMIT (.*)|PROCEDURE (.*)|FOR UPDATE|' + . 'LOCK IN SHARE MODE))@is', + $unsorted_sql_query, $regs3 + )) { + $single_sorted_sql_query = $regs3[1] . $single_sort_order . $regs3[2]; + $multi_sorted_sql_query = $regs3[1] . $multi_sort_order . $regs3[2]; + } else { + $single_sorted_sql_query = $unsorted_sql_query . $single_sort_order; + $multi_sorted_sql_query = $unsorted_sql_query . $multi_sort_order; + } + + $_single_url_params = array( + 'db' => $this->__get('db'), + 'table' => $this->__get('table'), + 'sql_query' => $single_sorted_sql_query, + 'session_max_rows' => $session_max_rows, + 'is_browse_distinct' => $this->__get('is_browse_distinct'), + ); + + $_multi_url_params = array( + 'db' => $this->__get('db'), + 'table' => $this->__get('table'), + 'sql_query' => $multi_sorted_sql_query, + 'session_max_rows' => $session_max_rows, + 'is_browse_distinct' => $this->__get('is_browse_distinct'), + ); + $single_order_url = 'sql.php' . Url::getCommon($_single_url_params); + $multi_order_url = 'sql.php' . Url::getCommon($_multi_url_params); + + // Displays the sorting URL + // enable sort order swapping for image + $order_link = $this->_getSortOrderLink( + $order_img, $fields_meta, $single_order_url, $multi_order_url + ); + + $sorted_header_html .= $this->_getDraggableClassForSortableColumns( + $col_visib, $col_visib_j, + $fields_meta, $order_link, $comments + ); + + return array($order_link, $sorted_header_html); + + } // end of the '_getOrderLinkAndSortedHeaderHtml()' function + + /** + * Prepare parameters and html for sorted table header fields + * + * @param array $sort_expression sort expression + * @param array $sort_expression_nodirection sort expression without direction + * @param string $sort_tbl The name of the table to which + * the current column belongs to + * @param string $name_to_use_in_sort The current column under + * consideration + * @param array $sort_direction sort direction + * @param array $fields_meta set of field properties + * @param integer $column_index The index number to current column + * + * @return array 3 element array - $single_sort_order, $sort_order, $order_img + * + * @access private + * + * @see _getOrderLinkAndSortedHeaderHtml() + */ + private function _getSingleAndMultiSortUrls( + array $sort_expression, array $sort_expression_nodirection, $sort_tbl, + $name_to_use_in_sort, array $sort_direction, $fields_meta, $column_index + ) { + $sort_order = ""; + // Check if the current column is in the order by clause + $is_in_sort = $this->_isInSorted( + $sort_expression, $sort_expression_nodirection, + $sort_tbl, $name_to_use_in_sort + ); + $current_name = $name_to_use_in_sort; + if ($sort_expression_nodirection[0] == '' || !$is_in_sort) { + $special_index = $sort_expression_nodirection[0] == '' + ? 0 + : count($sort_expression_nodirection); + $sort_expression_nodirection[$special_index] + = Util::backquote( + $current_name + ); + $sort_direction[$special_index] = (preg_match( + '@time|date@i', + $fields_meta->type + )) ? self::DESCENDING_SORT_DIR : self::ASCENDING_SORT_DIR; + + } + + $sort_expression_nodirection = array_filter($sort_expression_nodirection); + $single_sort_order = null; + foreach ($sort_expression_nodirection as $index=>$expression) { + // check if this is the first clause, + // if it is then we have to add "order by" + $is_first_clause = ($index == 0); + $name_to_use_in_sort = $expression; + $sort_tbl_new = $sort_tbl; + // Test to detect if the column name is a standard name + // Standard name has the table name prefixed to the column name + if (mb_strpos($name_to_use_in_sort, '.') !== false) { + $matches = explode('.', $name_to_use_in_sort); + // Matches[0] has the table name + // Matches[1] has the column name + $name_to_use_in_sort = $matches[1]; + $sort_tbl_new = $matches[0]; + } + + // $name_to_use_in_sort might contain a space due to + // formatting of function expressions like "COUNT(name )" + // so we remove the space in this situation + $name_to_use_in_sort = str_replace(' )', ')', $name_to_use_in_sort); + $name_to_use_in_sort = str_replace('``', '`', $name_to_use_in_sort); + $name_to_use_in_sort = trim($name_to_use_in_sort, '`'); + + // If this the first column name in the order by clause add + // order by clause to the column name + $query_head = $is_first_clause ? "\nORDER BY " : ""; + // Again a check to see if the given column is a aggregate column + if (mb_strpos($name_to_use_in_sort, '(') !== false) { + $sort_order .= $query_head . $name_to_use_in_sort . ' ' ; + } else { + if (strlen($sort_tbl_new) > 0) { + $sort_tbl_new .= "."; + } + $sort_order .= $query_head . $sort_tbl_new + . Util::backquote( + $name_to_use_in_sort + ) . ' ' ; + } + + // For a special case where the code generates two dots between + // column name and table name. + $sort_order = preg_replace("/\.\./", ".", $sort_order); + // Incase this is the current column save $single_sort_order + if ($current_name == $name_to_use_in_sort) { + if (mb_strpos($current_name, '(') !== false) { + $single_sort_order = "\n" . 'ORDER BY ' . Util::backquote($current_name) . ' '; + } else { + $single_sort_order = "\n" . 'ORDER BY ' . $sort_tbl + . Util::backquote( + $current_name + ) . ' '; + } + if ($is_in_sort) { + list($single_sort_order, $order_img) + = $this->_getSortingUrlParams( + $sort_direction, $single_sort_order, $index + ); + } else { + $single_sort_order .= strtoupper($sort_direction[$index]); + } + } + if ($current_name == $name_to_use_in_sort && $is_in_sort) { + // We need to generate the arrow button and related html + list($sort_order, $order_img) = $this->_getSortingUrlParams( + $sort_direction, $sort_order, $index + ); + $order_img .= " " . ($index + 1) . ""; + } else { + $sort_order .= strtoupper($sort_direction[$index]); + } + // Separate columns by a comma + $sort_order .= ", "; + + unset($name_to_use_in_sort); + } + // remove the comma from the last column name in the newly + // constructed clause + $sort_order = mb_substr( + $sort_order, + 0, + mb_strlen($sort_order) - 2 + ); + if (empty($order_img)) { + $order_img = ''; + } + return array($single_sort_order, $sort_order, $order_img); + } + + /** + * Check whether the column is sorted + * + * @param array $sort_expression sort expression + * @param array $sort_expression_nodirection sort expression without direction + * @param string $sort_tbl the table name + * @param string $name_to_use_in_sort the sorting column name + * + * @return boolean $is_in_sort the column sorted or not + * + * @access private + * + * @see _getTableHeaders() + */ + private function _isInSorted( + array $sort_expression, array $sort_expression_nodirection, $sort_tbl, + $name_to_use_in_sort + ) { + + $index_in_expression = 0; + + foreach ($sort_expression_nodirection as $index => $clause) { + if (mb_strpos($clause, '.') !== false) { + $fragments = explode('.', $clause); + $clause2 = $fragments[0] . "." . str_replace('`', '', $fragments[1]); + } else { + $clause2 = $sort_tbl . str_replace('`', '', $clause); + } + if ($clause2 === $sort_tbl . $name_to_use_in_sort) { + $index_in_expression = $index; + break; + } + } + if (empty($sort_expression[$index_in_expression])) { + $is_in_sort = false; + } else { + // Field name may be preceded by a space, or any number + // of characters followed by a dot (tablename.fieldname) + // so do a direct comparison for the sort expression; + // this avoids problems with queries like + // "SELECT id, count(id)..." and clicking to sort + // on id or on count(id). + // Another query to test this: + // SELECT p.*, FROM_UNIXTIME(p.temps) FROM mytable AS p + // (and try clicking on each column's header twice) + $noSortTable = empty($sort_tbl) || mb_strpos( + $sort_expression_nodirection[$index_in_expression], $sort_tbl + ) === false; + $noOpenParenthesis = mb_strpos( + $sort_expression_nodirection[$index_in_expression], '(' + ) === false; + if (! empty($sort_tbl) && $noSortTable && $noOpenParenthesis) { + $new_sort_expression_nodirection = $sort_tbl + . $sort_expression_nodirection[$index_in_expression]; + } else { + $new_sort_expression_nodirection + = $sort_expression_nodirection[$index_in_expression]; + } + + //Back quotes are removed in next comparison, so remove them from value + //to compare. + $name_to_use_in_sort = str_replace('`', '', $name_to_use_in_sort); + + $is_in_sort = false; + $sort_name = str_replace('`', '', $sort_tbl) . $name_to_use_in_sort; + + if ($sort_name == str_replace('`', '', $new_sort_expression_nodirection) + || $sort_name == str_replace('`', '', $sort_expression_nodirection[$index_in_expression]) + ) { + $is_in_sort = true; + } + } + + return $is_in_sort; + + } // end of the '_isInSorted()' function + + + /** + * Get sort url parameters - sort order and order image + * + * @param array $sort_direction the sort direction + * @param string $sort_order the sorting order + * @param integer $index the index of sort direction array. + * + * @return array 2 element array - $sort_order, $order_img + * + * @access private + * + * @see _getSingleAndMultiSortUrls() + */ + private function _getSortingUrlParams(array $sort_direction, $sort_order, $index) + { + if (strtoupper(trim($sort_direction[$index])) == self::DESCENDING_SORT_DIR) { + $sort_order .= ' ASC'; + $order_img = ' ' . Util::getImage( + 's_desc', __('Descending'), + array('class' => "soimg", 'title' => '') + ); + $order_img .= ' ' . Util::getImage( + 's_asc', __('Ascending'), + array('class' => "soimg hide", 'title' => '') + ); + } else { + $sort_order .= ' DESC'; + $order_img = ' ' . Util::getImage( + 's_asc', __('Ascending'), + array('class' => "soimg", 'title' => '') + ); + $order_img .= ' ' . Util::getImage( + 's_desc', __('Descending'), + array('class' => "soimg hide", 'title' => '') + ); + } + return array($sort_order, $order_img); + } // end of the '_getSortingUrlParams()' function + + + /** + * Get sort order link + * + * @param string $order_img the sort order image + * @param array $fields_meta set of field properties + * @param string $order_url the url for sort + * @param string $multi_order_url the url for sort + * + * @return string the sort order link + * + * @access private + * + * @see _getTableHeaders() + */ + private function _getSortOrderLink( + $order_img, $fields_meta, $order_url, $multi_order_url + ) { + $order_link_params = array( + 'class' => 'sortlink' + ); + + $order_link_content = htmlspecialchars($fields_meta->name); + $inner_link_content = $order_link_content . $order_img + . ''; + + return Util::linkOrButton( + $order_url, $inner_link_content, $order_link_params + ); + + } // end of the '_getSortOrderLink()' function + + /** + * Check if the column contains numeric data. If yes, then set the + * column header's alignment right + * + * @param array $fields_meta set of field properties + * @param array &$th_class array containing classes + * + * @return void + * + * @see _getDraggableClassForSortableColumns() + */ + private function _getClassForNumericColumnType($fields_meta, array &$th_class) + { + if (preg_match( + '@int|decimal|float|double|real|bit|boolean|serial@i', + $fields_meta->type + )) { + $th_class[] = 'right'; + } + } + + /** + * Prepare columns to draggable effect for sortable columns + * + * @param boolean $col_visib the column is visible (false) + * array the column is not visible (string array) + * @param string $col_visib_j element of $col_visib array + * @param array $fields_meta set of field properties + * @param string $order_link the order link + * @param string $comments the comment for the column + * + * @return string $draggable_html html content + * + * @access private + * + * @see _getTableHeaders() + */ + private function _getDraggableClassForSortableColumns( + $col_visib, $col_visib_j, $fields_meta, + $order_link, $comments + ) { + + $draggable_html = '_getClassForNumericColumnType($fields_meta, $th_class); + if ($col_visib && !$col_visib_j) { + $th_class[] = 'hide'; + } + + $th_class[] = 'column_heading'; + if ($GLOBALS['cfg']['BrowsePointerEnable'] == true) { + $th_class[] = 'pointer'; + } + + if ($GLOBALS['cfg']['BrowseMarkerEnable'] == true) { + $th_class[] = 'marker'; + } + + $draggable_html .= ' class="' . implode(' ', $th_class) . '"'; + + $draggable_html .= ' data-column="' . htmlspecialchars($fields_meta->name) + . '">' . $order_link . $comments . ''; + + return $draggable_html; + + } // end of the '_getDraggableClassForSortableColumns()' function + + + /** + * Prepare columns to draggable effect for non sortable columns + * + * @param boolean $col_visib the column is visible (false) + * array the column is not visible (string array) + * @param string $col_visib_j element of $col_visib array + * @param boolean $condition_field whether to add CSS class condition + * @param array $fields_meta set of field properties + * @param string $comments the comment for the column + * + * @return string $draggable_html html content + * + * @access private + * + * @see _getTableHeaders() + */ + private function _getDraggableClassForNonSortableColumns( + $col_visib, $col_visib_j, $condition_field, + $fields_meta, $comments + ) { + + $draggable_html = '_getClassForNumericColumnType($fields_meta, $th_class); + if ($col_visib && !$col_visib_j) { + $th_class[] = 'hide'; + } + + if ($condition_field) { + $th_class[] = 'condition'; + } + + $draggable_html .= ' class="' . implode(' ', $th_class) . '"'; + + $draggable_html .= ' data-column="' + . htmlspecialchars($fields_meta->name) . '">'; + + $draggable_html .= htmlspecialchars($fields_meta->name); + + $draggable_html .= "\n" . $comments . ''; + + return $draggable_html; + + } // end of the '_getDraggableClassForNonSortableColumns()' function + + + /** + * Prepare column to show at right side - check boxes or empty column + * + * @param array &$displayParts which elements to display + * @param string $full_or_partial_text_link full/partial link or text button + * @param string $colspan column span of table header + * + * @return string html content + * + * @access private + * + * @see _getTableHeaders() + */ + private function _getColumnAtRightSide( + array &$displayParts, $full_or_partial_text_link, $colspan + ) { + + $right_column_html = ''; + $display_params = $this->__get('display_params'); + + // Displays the needed checkboxes at the right + // column of the result table header if possible and required... + if ((($GLOBALS['cfg']['RowActionLinks'] == self::POSITION_RIGHT) + || ($GLOBALS['cfg']['RowActionLinks'] == self::POSITION_BOTH)) + && (($displayParts['edit_lnk'] != self::NO_EDIT_OR_DELETE) + || ($displayParts['del_lnk'] != self::NO_EDIT_OR_DELETE)) + && ($displayParts['text_btn'] == '1') + ) { + + $display_params['emptyafter'] + = (($displayParts['edit_lnk'] != self::NO_EDIT_OR_DELETE) + && ($displayParts['del_lnk'] != self::NO_EDIT_OR_DELETE)) ? 4 : 1; + + $right_column_html .= "\n" + . '' + . $full_or_partial_text_link + . ''; + + } elseif ((($GLOBALS['cfg']['RowActionLinks'] == self::POSITION_LEFT) + || ($GLOBALS['cfg']['RowActionLinks'] == self::POSITION_BOTH)) + && (($displayParts['edit_lnk'] == self::NO_EDIT_OR_DELETE) + && ($displayParts['del_lnk'] == self::NO_EDIT_OR_DELETE)) + && (! isset($GLOBALS['is_header_sent']) || ! $GLOBALS['is_header_sent']) + ) { + // ... elseif no button, displays empty columns if required + // (unless coming from Browse mode print view) + + $display_params['emptyafter'] + = (($displayParts['edit_lnk'] != self::NO_EDIT_OR_DELETE) + && ($displayParts['del_lnk'] != self::NO_EDIT_OR_DELETE)) ? 4 : 1; + + $right_column_html .= "\n" . ''; + } + + $this->__set('display_params', $display_params); + + return $right_column_html; + + } // end of the '_getColumnAtRightSide()' function + + + /** + * Prepares the display for a value + * + * @param string $class class of table cell + * @param bool $conditionField whether to add CSS class condition + * @param string $value value to display + * + * @return string the td + * + * @access private + * + * @see _getDataCellForGeometryColumns(), + * _getDataCellForNonNumericColumns() + */ + private function _buildValueDisplay($class, $conditionField, $value) + { + return Template::get('display/results/value_display')->render([ + 'class' => $class, + 'condition_field' => $conditionField, + 'value' => $value, + ]); + } + + /** + * Prepares the display for a null value + * + * @param string $class class of table cell + * @param bool $conditionField whether to add CSS class condition + * @param object $meta the meta-information about this field + * @param string $align cell alignment + * + * @return string the td + * + * @access private + * + * @see _getDataCellForNumericColumns(), + * _getDataCellForGeometryColumns(), + * _getDataCellForNonNumericColumns() + */ + private function _buildNullDisplay($class, $conditionField, $meta, $align = '') + { + $classes = $this->_addClass($class, $conditionField, $meta, ''); + + return Template::get('display/results/null_display')->render([ + 'align' => $align, + 'meta' => $meta, + 'classes' => $classes, + ]); + } + + /** + * Prepares the display for an empty value + * + * @param string $class class of table cell + * @param bool $conditionField whether to add CSS class condition + * @param object $meta the meta-information about this field + * @param string $align cell alignment + * + * @return string the td + * + * @access private + * + * @see _getDataCellForNumericColumns(), + * _getDataCellForGeometryColumns(), + * _getDataCellForNonNumericColumns() + */ + private function _buildEmptyDisplay($class, $conditionField, $meta, $align = '') + { + $classes = $this->_addClass($class, $conditionField, $meta, 'nowrap'); + + return Template::get('display/results/empty_display')->render([ + 'align' => $align, + 'classes' => $classes, + ]); + } + + /** + * Adds the relevant classes. + * + * @param string $class class of table cell + * @param bool $condition_field whether to add CSS class + * condition + * @param object $meta the meta-information about the + * field + * @param string $nowrap avoid wrapping + * @param bool $is_field_truncated is field truncated (display ...) + * @param object|string $transformation_plugin transformation plugin. + * Can also be the default function: + * Core::mimeDefaultFunction + * @param string $default_function default transformation function + * + * @return string the list of classes + * + * @access private + * + * @see _buildNullDisplay(), _getRowData() + */ + private function _addClass( + $class, $condition_field, $meta, $nowrap, $is_field_truncated = false, + $transformation_plugin = '', $default_function = '' + ) { + $classes = array( + $class, + $nowrap, + ); + + if (isset($meta->mimetype)) { + $classes[] = preg_replace('/\//', '_', $meta->mimetype); + } + + if ($condition_field) { + $classes[] = 'condition'; + } + + if ($is_field_truncated) { + $classes[] = 'truncated'; + } + + $mime_map = $this->__get('mime_map'); + $orgFullColName = $this->__get('db') . '.' . $meta->orgtable + . '.' . $meta->orgname; + if ($transformation_plugin != $default_function + || !empty($mime_map[$orgFullColName]['input_transformation']) + ) { + $classes[] = 'transformed'; + } + + // Define classes to be added to this data field based on the type of data + $matches = array( + 'enum' => 'enum', + 'set' => 'set', + 'binary' => 'hex', + ); + + foreach ($matches as $key => $value) { + if (mb_strpos($meta->flags, $key) !== false) { + $classes[] = $value; + } + } + + if (mb_strpos($meta->type, 'bit') !== false) { + $classes[] = 'bit'; + } + + return implode(' ', $classes); + } // end of the '_addClass()' function + + /** + * Prepare the body of the results table + * + * @param integer &$dt_result the link id associated to the query + * which results have to be displayed which + * results have to be displayed + * @param array &$displayParts which elements to display + * @param array $map the list of relations + * @param array $analyzed_sql_results analyzed sql results + * @param boolean $is_limited_display with limited operations or not + * + * @return string $table_body_html html content + * + * @global array $row current row data + * + * @access private + * + * @see getTable() + */ + private function _getTableBody( + &$dt_result, array &$displayParts, array $map, array $analyzed_sql_results, + $is_limited_display = false + ) { + + global $row; // mostly because of browser transformations, + // to make the row-data accessible in a plugin + + $table_body_html = ''; + + // query without conditions to shorten URLs when needed, 200 is just + // guess, it should depend on remaining URL length + $url_sql_query = $this->_getUrlSqlQuery($analyzed_sql_results); + + $display_params = $this->__get('display_params'); + + if (! is_array($map)) { + $map = array(); + } + + $row_no = 0; + $display_params['edit'] = array(); + $display_params['copy'] = array(); + $display_params['delete'] = array(); + $display_params['data'] = array(); + $display_params['row_delete'] = array(); + $this->__set('display_params', $display_params); + + // name of the class added to all grid editable elements; + // if we don't have all the columns of a unique key in the result set, + // do not permit grid editing + if ($is_limited_display || ! $this->__get('editable')) { + $grid_edit_class = ''; + } else { + switch ($GLOBALS['cfg']['GridEditing']) { + case 'double-click': + // trying to reduce generated HTML by using shorter + // classes like click1 and click2 + $grid_edit_class = 'grid_edit click2'; + break; + case 'click': + $grid_edit_class = 'grid_edit click1'; + break; + default: // 'disabled' + $grid_edit_class = ''; + break; + } + } + + // prepare to get the column order, if available + list($col_order, $col_visib) = $this->_getColumnParams( + $analyzed_sql_results + ); + + // Correction University of Virginia 19991216 in the while below + // Previous code assumed that all tables have keys, specifically that + // the phpMyAdmin GUI should support row delete/edit only for such + // tables. + // Although always using keys is arguably the prescribed way of + // defining a relational table, it is not required. This will in + // particular be violated by the novice. + // We want to encourage phpMyAdmin usage by such novices. So the code + // below has been changed to conditionally work as before when the + // table being displayed has one or more keys; but to display + // delete/edit options correctly for tables without keys. + + $whereClauseMap = $this->__get('whereClauseMap'); + while ($row = $GLOBALS['dbi']->fetchRow($dt_result)) { + + // add repeating headers + if ((($row_no != 0) && ($_SESSION['tmpval']['repeat_cells'] != 0)) + && !($row_no % $_SESSION['tmpval']['repeat_cells']) + ) { + $table_body_html .= $this->_getRepeatingHeaders( + $display_params + ); + } + + $tr_class = array(); + if ($GLOBALS['cfg']['BrowsePointerEnable'] != true) { + $tr_class[] = 'nopointer'; + } + if ($GLOBALS['cfg']['BrowseMarkerEnable'] != true) { + $tr_class[] = 'nomarker'; + } + + // pointer code part + $classes = (empty($tr_class) ? ' ' : 'class="' . implode(' ', $tr_class) . '"'); + $table_body_html .= ''; + + // 1. Prepares the row + + // In print view these variable needs to be initialized + $del_url = $del_str = $edit_anchor_class + = $edit_str = $js_conf = $copy_url = $copy_str = $edit_url = null; + + // 1.2 Defines the URLs for the modify/delete link(s) + + if (($displayParts['edit_lnk'] != self::NO_EDIT_OR_DELETE) + || ($displayParts['del_lnk'] != self::NO_EDIT_OR_DELETE) + ) { + + // Results from a "SELECT" statement -> builds the + // WHERE clause to use in links (a unique key if possible) + /** + * @todo $where_clause could be empty, for example a table + * with only one field and it's a BLOB; in this case, + * avoid to display the delete and edit links + */ + list($where_clause, $clause_is_unique, $condition_array) + = Util::getUniqueCondition( + $dt_result, // handle + $this->__get('fields_cnt'), // fields_cnt + $this->__get('fields_meta'), // fields_meta + $row, // row + false, // force_unique + $this->__get('table'), // restrict_to_table + $analyzed_sql_results // analyzed_sql_results + ); + $whereClauseMap[$row_no][$this->__get('table')] = $where_clause; + $this->__set('whereClauseMap', $whereClauseMap); + + $where_clause_html = htmlspecialchars($where_clause); + + // 1.2.1 Modify link(s) - update row case + if ($displayParts['edit_lnk'] == self::UPDATE_ROW) { + + list($edit_url, $copy_url, $edit_str, $copy_str, + $edit_anchor_class) + = $this->_getModifiedLinks( + $where_clause, + $clause_is_unique, $url_sql_query + ); + + } // end if (1.2.1) + + // 1.2.2 Delete/Kill link(s) + list($del_url, $del_str, $js_conf) + = $this->_getDeleteAndKillLinks( + $where_clause, $clause_is_unique, + $url_sql_query, $displayParts['del_lnk'], + $row + ); + + // 1.3 Displays the links at left if required + if (($GLOBALS['cfg']['RowActionLinks'] == self::POSITION_LEFT) + || ($GLOBALS['cfg']['RowActionLinks'] == self::POSITION_BOTH) + ) { + + $table_body_html .= $this->_getPlacedLinks( + self::POSITION_LEFT, $del_url, $displayParts, $row_no, + $where_clause, $where_clause_html, $condition_array, + $edit_url, $copy_url, $edit_anchor_class, + $edit_str, $copy_str, $del_str, $js_conf + ); + + } elseif ($GLOBALS['cfg']['RowActionLinks'] == self::POSITION_NONE) { + + $table_body_html .= $this->_getPlacedLinks( + self::POSITION_NONE, $del_url, $displayParts, $row_no, + $where_clause, $where_clause_html, $condition_array, + $edit_url, $copy_url, $edit_anchor_class, + $edit_str, $copy_str, $del_str, $js_conf + ); + + } // end if (1.3) + } // end if (1) + + // 2. Displays the rows' values + if (is_null($this->__get('mime_map'))) { + $this->_setMimeMap(); + } + $table_body_html .= $this->_getRowValues( + $dt_result, + $row, + $row_no, + $col_order, + $map, + $grid_edit_class, + $col_visib, + $url_sql_query, + $analyzed_sql_results + ); + + // 3. Displays the modify/delete links on the right if required + if (($displayParts['edit_lnk'] != self::NO_EDIT_OR_DELETE) + || ($displayParts['del_lnk'] != self::NO_EDIT_OR_DELETE) + ) { + if (($GLOBALS['cfg']['RowActionLinks'] == self::POSITION_RIGHT) + || ($GLOBALS['cfg']['RowActionLinks'] == self::POSITION_BOTH) + ) { + + $table_body_html .= $this->_getPlacedLinks( + self::POSITION_RIGHT, $del_url, $displayParts, $row_no, + $where_clause, $where_clause_html, $condition_array, + $edit_url, $copy_url, $edit_anchor_class, + $edit_str, $copy_str, $del_str, $js_conf + ); + + } + } // end if (3) + + $table_body_html .= ''; + $table_body_html .= "\n"; + $row_no++; + + } // end while + + return $table_body_html; + + } // end of the '_getTableBody()' function + + /** + * Sets the MIME details of the columns in the results set + * + * @return void + */ + private function _setMimeMap() + { + $fields_meta = $this->__get('fields_meta'); + $mimeMap = array(); + $added = array(); + + for ($currentColumn = 0; + $currentColumn < $this->__get('fields_cnt'); + ++$currentColumn) { + + $meta = $fields_meta[$currentColumn]; + $orgFullTableName = $this->__get('db') . '.' . $meta->orgtable; + + if ($GLOBALS['cfgRelation']['commwork'] + && $GLOBALS['cfgRelation']['mimework'] + && $GLOBALS['cfg']['BrowseMIME'] + && ! $_SESSION['tmpval']['hide_transformation'] + && empty($added[$orgFullTableName]) + ) { + $mimeMap = array_merge( + $mimeMap, + Transformations::getMIME($this->__get('db'), $meta->orgtable, false, true) + ); + $added[$orgFullTableName] = true; + } + } + + // special browser transformation for some SHOW statements + if ($this->__get('is_show') + && ! $_SESSION['tmpval']['hide_transformation'] + ) { + preg_match( + '@^SHOW[[:space:]]+(VARIABLES|(FULL[[:space:]]+)?' + . 'PROCESSLIST|STATUS|TABLE|GRANTS|CREATE|LOGS|DATABASES|FIELDS' + . ')@i', + $this->__get('sql_query'), $which + ); + + if (isset($which[1])) { + $str = ' ' . strtoupper($which[1]); + $isShowProcessList = strpos($str, 'PROCESSLIST') > 0; + if ($isShowProcessList) { + $mimeMap['..Info'] = array( + 'mimetype' => 'Text_Plain', + 'transformation' => 'output/Text_Plain_Sql.php', + ); + } + + $isShowCreateTable = preg_match( + '@CREATE[[:space:]]+TABLE@i', $this->__get('sql_query') + ); + if ($isShowCreateTable) { + $mimeMap['..Create Table'] = array( + 'mimetype' => 'Text_Plain', + 'transformation' => 'output/Text_Plain_Sql.php', + ); + } + } + } + + $this->__set('mime_map', $mimeMap); + } + + /** + * Get the values for one data row + * + * @param integer &$dt_result the link id associated to + * the query which results + * have to be displayed which + * results have to be + * displayed + * @param array $row current row data + * @param integer $row_no the index of current row + * @param array|boolean $col_order the column order false when + * a property not found false + * when a property not found + * @param array $map the list of relations + * @param string $grid_edit_class the class for all editable + * columns + * @param boolean|array|string $col_visib column is visible(false); + * column isn't visible(string + * array) + * @param string $url_sql_query the analyzed sql query + * @param array $analyzed_sql_results analyzed sql results + * + * @return string $row_values_html html content + * + * @access private + * + * @see _getTableBody() + */ + private function _getRowValues( + &$dt_result, array $row, $row_no, $col_order, array $map, + $grid_edit_class, $col_visib, + $url_sql_query, array $analyzed_sql_results + ) { + $row_values_html = ''; + + // Following variable are needed for use in isset/empty or + // use with array indexes/safe use in foreach + $sql_query = $this->__get('sql_query'); + $fields_meta = $this->__get('fields_meta'); + $highlight_columns = $this->__get('highlight_columns'); + $mime_map = $this->__get('mime_map'); + + $row_info = $this->_getRowInfoForSpecialLinks($row, $col_order); + + $whereClauseMap = $this->__get('whereClauseMap'); + + $columnCount = $this->__get('fields_cnt'); + for ($currentColumn = 0; + $currentColumn < $columnCount; + ++$currentColumn) { + + // assign $i with appropriate column order + $i = $col_order ? $col_order[$currentColumn] : $currentColumn; + + $meta = $fields_meta[$i]; + $orgFullColName + = $this->__get('db') . '.' . $meta->orgtable . '.' . $meta->orgname; + + $not_null_class = $meta->not_null ? 'not_null' : ''; + $relation_class = isset($map[$meta->name]) ? 'relation' : ''; + $hide_class = ($col_visib && ! $col_visib[$currentColumn]) + ? 'hide' + : ''; + $grid_edit = $meta->orgtable != '' ? $grid_edit_class : ''; + + // handle datetime-related class, for grid editing + $field_type_class + = $this->_getClassForDateTimeRelatedFields($meta->type); + + $is_field_truncated = false; + // combine all the classes applicable to this column's value + $class = $this->_getClassesForColumn( + $grid_edit, $not_null_class, $relation_class, + $hide_class, $field_type_class + ); + + // See if this column should get highlight because it's used in the + // where-query. + $condition_field = (isset($highlight_columns) + && (isset($highlight_columns[$meta->name]) + || isset($highlight_columns[Util::backquote($meta->name)]))) + ? true + : false; + + // Wrap MIME-transformations. [MIME] + $default_function = [Core::class, 'mimeDefaultFunction']; // default_function + $transformation_plugin = $default_function; + $transform_options = array(); + + if ($GLOBALS['cfgRelation']['mimework'] + && $GLOBALS['cfg']['BrowseMIME'] + ) { + + if (isset($mime_map[$orgFullColName]['mimetype']) + && !empty($mime_map[$orgFullColName]['transformation']) + ) { + + $file = $mime_map[$orgFullColName]['transformation']; + $include_file = 'libraries/classes/Plugins/Transformations/' . $file; + + if (@file_exists($include_file)) { + + $class_name = Transformations::getClassName($include_file); + if (class_exists($class_name)) { + // todo add $plugin_manager + $plugin_manager = null; + $transformation_plugin = new $class_name( + $plugin_manager + ); + + $transform_options = Transformations::getOptions( + isset( + $mime_map[$orgFullColName] + ['transformation_options'] + ) + ? $mime_map[$orgFullColName] + ['transformation_options'] + : '' + ); + + $meta->mimetype = str_replace( + '_', '/', + $mime_map[$orgFullColName]['mimetype'] + ); + } + + } // end if file_exists + } // end if transformation is set + } // end if mime/transformation works. + + // Check whether the field needs to display with syntax highlighting + + $dbLower = mb_strtolower($this->__get('db')); + $tblLower = mb_strtolower($meta->orgtable); + $nameLower = mb_strtolower($meta->orgname); + if (! empty($this->transformation_info[$dbLower][$tblLower][$nameLower]) + && (trim($row[$i]) != '') + && ! $_SESSION['tmpval']['hide_transformation'] + ) { + include_once $this->transformation_info + [$dbLower][$tblLower][$nameLower][0]; + $transformation_plugin = new $this->transformation_info + [$dbLower][$tblLower][$nameLower][1](null); + + $transform_options = Transformations::getOptions( + isset($mime_map[$orgFullColName]['transformation_options']) + ? $mime_map[$orgFullColName]['transformation_options'] + : '' + ); + + $meta->mimetype = str_replace( + '_', '/', + $this->transformation_info[$dbLower] + [mb_strtolower($meta->orgtable)] + [mb_strtolower($meta->orgname)][2] + ); + + } + + // Check for the predefined fields need to show as link in schemas + include_once 'libraries/special_schema_links.inc.php'; + + if (isset($GLOBALS['special_schema_links']) + && (! empty($GLOBALS['special_schema_links'][$dbLower][$tblLower][$nameLower])) + ) { + + $linking_url = $this->_getSpecialLinkUrl( + $row[$i], $row_info, mb_strtolower($meta->orgname) + ); + $transformation_plugin = new Text_Plain_Link(); + + $transform_options = array( + 0 => $linking_url, + 2 => true + ); + + $meta->mimetype = str_replace( + '_', '/', + 'Text/Plain' + ); + + } + + /* + * The result set can have columns from more than one table, + * this is why we have to check for the unique conditions + * related to this table; however getUniqueCondition() is + * costly and does not need to be called if we already know + * the conditions for the current table. + */ + if (! isset($whereClauseMap[$row_no][$meta->orgtable])) { + $unique_conditions = Util::getUniqueCondition( + $dt_result, // handle + $this->__get('fields_cnt'), // fields_cnt + $this->__get('fields_meta'), // fields_meta + $row, // row + false, // force_unique + $meta->orgtable, // restrict_to_table + $analyzed_sql_results // analyzed_sql_results + ); + $whereClauseMap[$row_no][$meta->orgtable] = $unique_conditions[0]; + } + + $_url_params = array( + 'db' => $this->__get('db'), + 'table' => $meta->orgtable, + 'where_clause' => $whereClauseMap[$row_no][$meta->orgtable], + 'transform_key' => $meta->orgname + ); + + if (! empty($sql_query)) { + $_url_params['sql_query'] = $url_sql_query; + } + + $transform_options['wrapper_link'] = Url::getCommon($_url_params); + + $display_params = $this->__get('display_params'); + + // in some situations (issue 11406), numeric returns 1 + // even for a string type + // for decimal numeric is returning 1 + // have to improve logic + if (($meta->numeric == 1 && $meta->type != 'string') || $meta->type == 'real') { + // n u m e r i c + + $display_params['data'][$row_no][$i] + = $this->_getDataCellForNumericColumns( + $row[$i], + $class, + $condition_field, + $meta, + $map, + $is_field_truncated, + $analyzed_sql_results, + $transformation_plugin, + $default_function, + $transform_options + ); + + } elseif ($meta->type == self::GEOMETRY_FIELD) { + // g e o m e t r y + + // Remove 'grid_edit' from $class as we do not allow to + // inline-edit geometry data. + $class = str_replace('grid_edit', '', $class); + + $display_params['data'][$row_no][$i] + = $this->_getDataCellForGeometryColumns( + $row[$i], + $class, + $meta, + $map, + $_url_params, + $condition_field, + $transformation_plugin, + $default_function, + $transform_options, + $analyzed_sql_results + ); + + } else { + // n o t n u m e r i c + + $display_params['data'][$row_no][$i] + = $this->_getDataCellForNonNumericColumns( + $row[$i], + $class, + $meta, + $map, + $_url_params, + $condition_field, + $transformation_plugin, + $default_function, + $transform_options, + $is_field_truncated, + $analyzed_sql_results, + $dt_result, + $i + ); + + } + + // output stored cell + $row_values_html .= $display_params['data'][$row_no][$i]; + + if (isset($display_params['rowdata'][$i][$row_no])) { + $display_params['rowdata'][$i][$row_no] + .= $display_params['data'][$row_no][$i]; + } else { + $display_params['rowdata'][$i][$row_no] + = $display_params['data'][$row_no][$i]; + } + + $this->__set('display_params', $display_params); + + } // end for + + return $row_values_html; + + } // end of the '_getRowValues()' function + + /** + * Get link for display special schema links + * + * @param string $column_value column value + * @param array $row_info information about row + * @param string $field_name column name + * + * @return string generated link + */ + private function _getSpecialLinkUrl($column_value, array $row_info, $field_name) + { + + $linking_url_params = array(); + $link_relations = $GLOBALS['special_schema_links'] + [mb_strtolower($this->__get('db'))] + [mb_strtolower($this->__get('table'))] + [$field_name]; + + if (! is_array($link_relations['link_param'])) { + $linking_url_params[$link_relations['link_param']] = $column_value; + } else { + // Consider only the case of creating link for column field + // sql query that needs to be passed as url param + $sql = 'SELECT `' . $column_value . '` FROM `' + . $row_info[$link_relations['link_param'][1]] . '`.`' + . $row_info[$link_relations['link_param'][2]] . '`'; + $linking_url_params[$link_relations['link_param'][0]] = $sql; + } + + $divider = strpos($link_relations['default_page'], '?') ? '&' : '?'; + if (empty($link_relations['link_dependancy_params'])) { + return $link_relations['default_page'] + . Url::getCommonRaw($linking_url_params, $divider); + } + + foreach ($link_relations['link_dependancy_params'] as $new_param) { + + // If param_info is an array, set the key and value + // from that array + if (is_array($new_param['param_info'])) { + $linking_url_params[$new_param['param_info'][0]] + = $new_param['param_info'][1]; + continue; + } + + $linking_url_params[$new_param['param_info']] + = $row_info[mb_strtolower($new_param['column_name'])]; + + // Special case 1 - when executing routines, according + // to the type of the routine, url param changes + if (empty($row_info['routine_type'])) { + continue; + } + } + + return $link_relations['default_page'] + . Url::getCommonRaw($linking_url_params, $divider); + } + + + /** + * Prepare row information for display special links + * + * @param array $row current row data + * @param array|boolean $col_order the column order + * + * @return array $row_info associative array with column nama -> value + */ + private function _getRowInfoForSpecialLinks(array $row, $col_order) + { + + $row_info = array(); + $fields_meta = $this->__get('fields_meta'); + + for ($n = 0; $n < $this->__get('fields_cnt'); ++$n) { + $m = $col_order ? $col_order[$n] : $n; + $row_info[mb_strtolower($fields_meta[$m]->orgname)] + = $row[$m]; + } + + return $row_info; + + } + + /** + * Get url sql query without conditions to shorten URLs + * + * @param array $analyzed_sql_results analyzed sql results + * + * @return string $url_sql analyzed sql query + * + * @access private + * + * @see _getTableBody() + */ + private function _getUrlSqlQuery(array $analyzed_sql_results) + { + if (($analyzed_sql_results['querytype'] != 'SELECT') + || (mb_strlen($this->__get('sql_query')) < 200) + ) { + return $this->__get('sql_query'); + } + + $query = 'SELECT ' . Query::getClause( + $analyzed_sql_results['statement'], + $analyzed_sql_results['parser']->list, + 'SELECT' + ); + + $from_clause = Query::getClause( + $analyzed_sql_results['statement'], + $analyzed_sql_results['parser']->list, + 'FROM' + ); + + if (!empty($from_clause)) { + $query .= ' FROM ' . $from_clause; + } + + return $query; + + } // end of the '_getUrlSqlQuery()' function + + + /** + * Get column order and column visibility + * + * @param array $analyzed_sql_results analyzed sql results + * + * @return array 2 element array - $col_order, $col_visib + * + * @access private + * + * @see _getTableBody() + */ + private function _getColumnParams(array $analyzed_sql_results) + { + if ($this->_isSelect($analyzed_sql_results)) { + $pmatable = new Table($this->__get('table'), $this->__get('db')); + $col_order = $pmatable->getUiProp(Table::PROP_COLUMN_ORDER); + /* Validate the value */ + if ($col_order !== false) { + $fields_cnt = $this->__get('fields_cnt'); + foreach ($col_order as $value) { + if ($value >= $fields_cnt) { + $pmatable->removeUiProp(Table::PROP_COLUMN_ORDER); + $fields_cnt = false; + } + } + } + $col_visib = $pmatable->getUiProp(Table::PROP_COLUMN_VISIB); + } else { + $col_order = false; + $col_visib = false; + } + + return array($col_order, $col_visib); + } // end of the '_getColumnParams()' function + + + /** + * Get HTML for repeating headers + * + * @param array $display_params holds various display info + * + * @return string $header_html html content + * + * @access private + * + * @see _getTableBody() + */ + private function _getRepeatingHeaders( + array $display_params + ) { + $header_html = '' . "\n"; + + if ($display_params['emptypre'] > 0) { + + $header_html .= ' ' + . "\n" . '  ' . "\n"; + + } elseif ($GLOBALS['cfg']['RowActionLinks'] == self::POSITION_NONE) { + $header_html .= ' ' . "\n"; + } + + foreach ($display_params['desc'] as $val) { + $header_html .= $val; + } + + if ($display_params['emptyafter'] > 0) { + $header_html + .= ' ' + . "\n" . '  ' . "\n"; + } + $header_html .= '' . "\n"; + + return $header_html; + + } // end of the '_getRepeatingHeaders()' function + + + /** + * Get modified links + * + * @param string $where_clause the where clause of the sql + * @param boolean $clause_is_unique the unique condition of clause + * @param string $url_sql_query the analyzed sql query + * + * @return array 5 element array - $edit_url, $copy_url, + * $edit_str, $copy_str, $edit_anchor_class + * + * @access private + * + * @see _getTableBody() + */ + private function _getModifiedLinks( + $where_clause, $clause_is_unique, $url_sql_query + ) { + + $_url_params = array( + 'db' => $this->__get('db'), + 'table' => $this->__get('table'), + 'where_clause' => $where_clause, + 'clause_is_unique' => $clause_is_unique, + 'sql_query' => $url_sql_query, + 'goto' => 'sql.php', + ); + + $edit_url = 'tbl_change.php' + . Url::getCommon( + $_url_params + array('default_action' => 'update') + ); + + $copy_url = 'tbl_change.php' + . Url::getCommon( + $_url_params + array('default_action' => 'insert') + ); + + $edit_str = $this->_getActionLinkContent( + 'b_edit', __('Edit') + ); + $copy_str = $this->_getActionLinkContent( + 'b_insrow', __('Copy') + ); + + // Class definitions required for grid editing jQuery scripts + $edit_anchor_class = "edit_row_anchor"; + if ($clause_is_unique == 0) { + $edit_anchor_class .= ' nonunique'; + } + + return array($edit_url, $copy_url, $edit_str, $copy_str, $edit_anchor_class); + + } // end of the '_getModifiedLinks()' function + + + /** + * Get delete and kill links + * + * @param string $where_clause the where clause of the sql + * @param boolean $clause_is_unique the unique condition of clause + * @param string $url_sql_query the analyzed sql query + * @param string $del_lnk the delete link of current row + * @param array $row the current row + * + * @return array 3 element array + * $del_url, $del_str, $js_conf + * + * @access private + * + * @see _getTableBody() + */ + private function _getDeleteAndKillLinks( + $where_clause, $clause_is_unique, $url_sql_query, $del_lnk, array $row + ) { + + $goto = $this->__get('goto'); + + if ($del_lnk == self::DELETE_ROW) { // delete row case + + $_url_params = array( + 'db' => $this->__get('db'), + 'table' => $this->__get('table'), + 'sql_query' => $url_sql_query, + 'message_to_show' => __('The row has been deleted.'), + 'goto' => (empty($goto) ? 'tbl_sql.php' : $goto), + ); + + $lnk_goto = 'sql.php' . Url::getCommonRaw($_url_params); + + $del_query = 'DELETE FROM ' + . Util::backquote($this->__get('table')) + . ' WHERE ' . $where_clause . + ($clause_is_unique ? '' : ' LIMIT 1'); + + $_url_params = array( + 'db' => $this->__get('db'), + 'table' => $this->__get('table'), + 'sql_query' => $del_query, + 'message_to_show' => __('The row has been deleted.'), + 'goto' => $lnk_goto, + ); + $del_url = 'sql.php' . Url::getCommon($_url_params); + + $js_conf = 'DELETE FROM ' . Sanitize::jsFormat($this->__get('table')) + . ' WHERE ' . Sanitize::jsFormat($where_clause, false) + . ($clause_is_unique ? '' : ' LIMIT 1'); + + $del_str = $this->_getActionLinkContent('b_drop', __('Delete')); + + } elseif ($del_lnk == self::KILL_PROCESS) { // kill process case + + $_url_params = array( + 'db' => $this->__get('db'), + 'table' => $this->__get('table'), + 'sql_query' => $url_sql_query, + 'goto' => 'index.php', + ); + + $lnk_goto = 'sql.php' . Url::getCommonRaw($_url_params); + + $kill = $GLOBALS['dbi']->getKillQuery($row[0]); + + $_url_params = array( + 'db' => 'mysql', + 'sql_query' => $kill, + 'goto' => $lnk_goto, + ); + + $del_url = 'sql.php' . Url::getCommon($_url_params); + $js_conf = $kill; + $del_str = Util::getIcon( + 'b_drop', __('Kill') + ); + } else { + $del_url = $del_str = $js_conf = null; + } + + return array($del_url, $del_str, $js_conf); + + } // end of the '_getDeleteAndKillLinks()' function + + + /** + * Get content inside the table row action links (Edit/Copy/Delete) + * + * @param string $icon The name of the file to get + * @param string $display_text The text displaying after the image icon + * + * @return string + * + * @access private + * + * @see _getModifiedLinks(), _getDeleteAndKillLinks() + */ + private function _getActionLinkContent($icon, $display_text) + { + + $linkContent = ''; + + if (isset($GLOBALS['cfg']['RowActionType']) + && $GLOBALS['cfg']['RowActionType'] == self::ACTION_LINK_CONTENT_ICONS + ) { + + $linkContent .= '' + . Util::getImage( + $icon, $display_text + ) + . ''; + + } elseif (isset($GLOBALS['cfg']['RowActionType']) + && $GLOBALS['cfg']['RowActionType'] == self::ACTION_LINK_CONTENT_TEXT + ) { + + $linkContent .= '' . $display_text . ''; + + } else { + + $linkContent .= Util::getIcon( + $icon, $display_text + ); + + } + + return $linkContent; + + } + + + /** + * Prepare placed links + * + * @param string $dir the direction of links should place + * @param string $del_url the url for delete row + * @param array $displayParts which elements to display + * @param integer $row_no the index of current row + * @param string $where_clause the where clause of the sql + * @param string $where_clause_html the html encoded where clause + * @param array $condition_array array of keys (primary, unique, condition) + * @param string $edit_url the url for edit row + * @param string $copy_url the url for copy row + * @param string $edit_anchor_class the class for html element for edit + * @param string $edit_str the label for edit row + * @param string $copy_str the label for copy row + * @param string $del_str the label for delete row + * @param string $js_conf text for the JS confirmation + * + * @return string html content + * + * @access private + * + * @see _getTableBody() + */ + private function _getPlacedLinks( + $dir, $del_url, array $displayParts, $row_no, $where_clause, $where_clause_html, + array $condition_array, $edit_url, $copy_url, + $edit_anchor_class, $edit_str, $copy_str, $del_str, $js_conf + ) { + + if (! isset($js_conf)) { + $js_conf = ''; + } + + return $this->_getCheckboxAndLinks( + $dir, $del_url, $displayParts, + $row_no, $where_clause, $where_clause_html, $condition_array, + $edit_url, $copy_url, $edit_anchor_class, + $edit_str, $copy_str, $del_str, $js_conf + ); + + } // end of the '_getPlacedLinks()' function + + + /** + * Get the combined classes for a column + * + * @param string $grid_edit_class the class for all editable columns + * @param string $not_null_class the class for not null columns + * @param string $relation_class the class for relations in a column + * @param string $hide_class the class for visibility of a column + * @param string $field_type_class the class related to type of the field + * + * @return string $class the combined classes + * + * @access private + * + * @see _getTableBody() + */ + private function _getClassesForColumn( + $grid_edit_class, $not_null_class, $relation_class, + $hide_class, $field_type_class + ) { + $class = 'data ' . $grid_edit_class . ' ' . $not_null_class . ' ' + . $relation_class . ' ' . $hide_class . ' ' . $field_type_class; + + return $class; + + } // end of the '_getClassesForColumn()' function + + + /** + * Get class for datetime related fields + * + * @param string $type the type of the column field + * + * @return string $field_type_class the class for the column + * + * @access private + * + * @see _getTableBody() + */ + private function _getClassForDateTimeRelatedFields($type) + { + if ((substr($type, 0, 9) == self::TIMESTAMP_FIELD) + || ($type == self::DATETIME_FIELD) + ) { + $field_type_class = 'datetimefield'; + } elseif ($type == self::DATE_FIELD) { + $field_type_class = 'datefield'; + } elseif ($type == self::TIME_FIELD) { + $field_type_class = 'timefield'; + } elseif ($type == self::STRING_FIELD) { + $field_type_class = 'text'; + } else { + $field_type_class = ''; + } + return $field_type_class; + } // end of the '_getClassForDateTimeRelatedFields()' function + + + /** + * Prepare data cell for numeric type fields + * + * @param string $column the column's value + * @param string $class the html class for column + * @param boolean $condition_field the column should highlighted + * or not + * @param object $meta the meta-information about this + * field + * @param array $map the list of relations + * @param boolean $is_field_truncated the condition for blob data + * replacements + * @param array $analyzed_sql_results the analyzed query + * @param object|string $transformation_plugin the name of transformation plugin + * @param string $default_function the default transformation + * function + * @param array $transform_options the transformation parameters + * + * @return string $cell the prepared cell, html content + * + * @access private + * + * @see _getTableBody() + */ + private function _getDataCellForNumericColumns( + $column, $class, $condition_field, $meta, array $map, $is_field_truncated, + array $analyzed_sql_results, $transformation_plugin, $default_function, + array $transform_options + ) { + + if (! isset($column) || is_null($column)) { + + $cell = $this->_buildNullDisplay( + 'right ' . $class, $condition_field, $meta, '' + ); + + } elseif ($column != '') { + + $nowrap = ' nowrap'; + $where_comparison = ' = ' . $column; + + $cell = $this->_getRowData( + 'right ' . $class, $condition_field, + $analyzed_sql_results, $meta, $map, $column, $column, + $transformation_plugin, $default_function, $nowrap, + $where_comparison, $transform_options, + $is_field_truncated, '' + ); + } else { + + $cell = $this->_buildEmptyDisplay( + 'right ' . $class, $condition_field, $meta, '' + ); + } + + return $cell; + + } // end of the '_getDataCellForNumericColumns()' function + + + /** + * Get data cell for geometry type fields + * + * @param string $column the relevant column in data row + * @param string $class the html class for column + * @param object $meta the meta-information about + * this field + * @param array $map the list of relations + * @param array $_url_params the parameters for generate url + * @param boolean $condition_field the column should highlighted + * or not + * @param object|string $transformation_plugin the name of transformation + * function + * @param string $default_function the default transformation + * function + * @param string $transform_options the transformation parameters + * @param array $analyzed_sql_results the analyzed query + * + * @return string $cell the prepared data cell, html content + * + * @access private + * + * @see _getTableBody() + */ + private function _getDataCellForGeometryColumns( + $column, $class, $meta, array $map, array $_url_params, $condition_field, + $transformation_plugin, $default_function, $transform_options, + array $analyzed_sql_results + ) { + if (! isset($column) || is_null($column)) { + $cell = $this->_buildNullDisplay($class, $condition_field, $meta); + return $cell; + } + + if ($column == '') { + $cell = $this->_buildEmptyDisplay($class, $condition_field, $meta); + return $cell; + } + + // Display as [GEOMETRY - (size)] + if ($_SESSION['tmpval']['geoOption'] == self::GEOMETRY_DISP_GEOM) { + $geometry_text = $this->_handleNonPrintableContents( + strtoupper(self::GEOMETRY_FIELD), $column, $transformation_plugin, + $transform_options, $default_function, $meta, $_url_params + ); + + $cell = $this->_buildValueDisplay( + $class, $condition_field, $geometry_text + ); + return $cell; + } + + if ($_SESSION['tmpval']['geoOption'] == self::GEOMETRY_DISP_WKT) { + // Prepare in Well Known Text(WKT) format. + $where_comparison = ' = ' . $column; + + // Convert to WKT format + $wktval = Util::asWKT($column); + list( + $is_field_truncated, + $displayedColumn, + // skip 3rd param + ) = $this->_getPartialText($wktval); + + $cell = $this->_getRowData( + $class, $condition_field, $analyzed_sql_results, $meta, $map, + $wktval, $displayedColumn, $transformation_plugin, + $default_function, '', + $where_comparison, $transform_options, + $is_field_truncated, '' + ); + return $cell; + } + + // Prepare in Well Known Binary (WKB) format. + + if ($_SESSION['tmpval']['display_binary']) { + $where_comparison = ' = ' . $column; + + $wkbval = substr(bin2hex($column), 8); + list( + $is_field_truncated, + $displayedColumn, + // skip 3rd param + ) = $this->_getPartialText($wkbval); + + $cell = $this->_getRowData( + $class, $condition_field, + $analyzed_sql_results, $meta, $map, $wkbval, $displayedColumn, + $transformation_plugin, $default_function, '', + $where_comparison, $transform_options, + $is_field_truncated, '' + ); + return $cell; + } + + $wkbval = $this->_handleNonPrintableContents( + self::BINARY_FIELD, $column, $transformation_plugin, + $transform_options, $default_function, $meta, + $_url_params + ); + + $cell = $this->_buildValueDisplay( + $class, $condition_field, $wkbval + ); + + return $cell; + + } // end of the '_getDataCellForGeometryColumns()' function + + + /** + * Get data cell for non numeric type fields + * + * @param string $column the relevant column in data row + * @param string $class the html class for column + * @param object $meta the meta-information about + * the field + * @param array $map the list of relations + * @param array $_url_params the parameters for generate + * url + * @param boolean $condition_field the column should highlighted + * or not + * @param object|string $transformation_plugin the name of transformation + * function + * @param string $default_function the default transformation + * function + * @param string $transform_options the transformation parameters + * @param boolean $is_field_truncated is data truncated due to + * LimitChars + * @param array $analyzed_sql_results the analyzed query + * @param integer &$dt_result the link id associated to + * the query which results + * have to be displayed + * @param integer $col_index the column index + * + * @return string $cell the prepared data cell, html content + * + * @access private + * + * @see _getTableBody() + */ + private function _getDataCellForNonNumericColumns( + $column, $class, $meta, array $map, array $_url_params, $condition_field, + $transformation_plugin, $default_function, $transform_options, + $is_field_truncated, array $analyzed_sql_results, &$dt_result, $col_index + ) { + $original_length = 0; + + $is_analyse = $this->__get('is_analyse'); + $field_flags = $GLOBALS['dbi']->fieldFlags($dt_result, $col_index); + + $bIsText = gettype($transformation_plugin) === 'object' + && strpos($transformation_plugin->getMIMEtype(), 'Text') + === false; + + // disable inline grid editing + // if binary fields are protected + // or transformation plugin is of non text type + // such as image + if ((stristr($field_flags, self::BINARY_FIELD) + && ($GLOBALS['cfg']['ProtectBinary'] === 'all' + || ($GLOBALS['cfg']['ProtectBinary'] === 'noblob' + && !stristr($meta->type, self::BLOB_FIELD)) + || ($GLOBALS['cfg']['ProtectBinary'] === 'blob' + && stristr($meta->type, self::BLOB_FIELD)))) + || $bIsText + ) { + $class = str_replace('grid_edit', '', $class); + } + + if (! isset($column) || is_null($column)) { + $cell = $this->_buildNullDisplay($class, $condition_field, $meta); + return $cell; + } + + if ($column == '') { + $cell = $this->_buildEmptyDisplay($class, $condition_field, $meta); + return $cell; + } + + // Cut all fields to $GLOBALS['cfg']['LimitChars'] + // (unless it's a link-type transformation or binary) + $displayedColumn = $column; + if (!(gettype($transformation_plugin) === "object" + && strpos($transformation_plugin->getName(), 'Link') !== false) + && !stristr($field_flags, self::BINARY_FIELD) + ) { + list( + $is_field_truncated, + $column, + $original_length + ) = $this->_getPartialText($column); + } + + $formatted = false; + if (isset($meta->_type) && $meta->_type === MYSQLI_TYPE_BIT) { + + $displayedColumn = Util::printableBitValue( + $displayedColumn, $meta->length + ); + + // some results of PROCEDURE ANALYSE() are reported as + // being BINARY but they are quite readable, + // so don't treat them as BINARY + } elseif (stristr($field_flags, self::BINARY_FIELD) + && !(isset($is_analyse) && $is_analyse) + ) { + // we show the BINARY or BLOB message and field's size + // (or maybe use a transformation) + $binary_or_blob = self::BLOB_FIELD; + if ($meta->type === self::STRING_FIELD) { + $binary_or_blob = self::BINARY_FIELD; + } + $displayedColumn = $this->_handleNonPrintableContents( + $binary_or_blob, $displayedColumn, $transformation_plugin, + $transform_options, $default_function, + $meta, $_url_params, $is_field_truncated + ); + $class = $this->_addClass( + $class, $condition_field, $meta, '', + $is_field_truncated, $transformation_plugin, $default_function + ); + $result = strip_tags($column); + // disable inline grid editing + // if binary or blob data is not shown + if (stristr($result, $binary_or_blob)) { + $class = str_replace('grid_edit', '', $class); + } + $formatted = true; + } + + if ($formatted) { + $cell = $this->_buildValueDisplay( + $class, $condition_field, $displayedColumn + ); + return $cell; + } + + // transform functions may enable no-wrapping: + $function_nowrap = 'applyTransformationNoWrap'; + + $bool_nowrap = (($default_function != $transformation_plugin) + && function_exists($transformation_plugin->$function_nowrap())) + ? $transformation_plugin->$function_nowrap($transform_options) + : false; + + // do not wrap if date field type + $nowrap = (preg_match('@DATE|TIME@i', $meta->type) + || $bool_nowrap) ? ' nowrap' : ''; + + $where_comparison = ' = \'' + . $GLOBALS['dbi']->escapeString($column) + . '\''; + + $cell = $this->_getRowData( + $class, $condition_field, + $analyzed_sql_results, $meta, $map, $column, $displayedColumn, + $transformation_plugin, $default_function, $nowrap, + $where_comparison, $transform_options, + $is_field_truncated, $original_length + ); + + return $cell; + + } // end of the '_getDataCellForNonNumericColumns()' function + + /** + * Checks the posted options for viewing query results + * and sets appropriate values in the session. + * + * @todo make maximum remembered queries configurable + * @todo move/split into SQL class!? + * @todo currently this is called twice unnecessary + * @todo ignore LIMIT and ORDER in query!? + * + * @return void + * + * @access public + * + * @see sql.php file + */ + public function setConfigParamsForDisplayTable() + { + + $sql_md5 = md5($this->__get('sql_query')); + $query = array(); + if (isset($_SESSION['tmpval']['query'][$sql_md5])) { + $query = $_SESSION['tmpval']['query'][$sql_md5]; + } + + $query['sql'] = $this->__get('sql_query'); + + if (empty($query['repeat_cells'])) { + $query['repeat_cells'] = $GLOBALS['cfg']['RepeatCells']; + } + + // as this is a form value, the type is always string so we cannot + // use Core::isValid($_POST['session_max_rows'], 'integer') + if (Core::isValid($_POST['session_max_rows'], 'numeric')) { + $query['max_rows'] = (int)$_POST['session_max_rows']; + unset($_POST['session_max_rows']); + } elseif ($_POST['session_max_rows'] == self::ALL_ROWS) { + $query['max_rows'] = self::ALL_ROWS; + unset($_POST['session_max_rows']); + } elseif (empty($query['max_rows'])) { + $query['max_rows'] = intval($GLOBALS['cfg']['MaxRows']); + } + + if (Core::isValid($_REQUEST['pos'], 'numeric')) { + $query['pos'] = $_REQUEST['pos']; + unset($_REQUEST['pos']); + } elseif (empty($query['pos'])) { + $query['pos'] = 0; + } + + if (Core::isValid( + $_REQUEST['pftext'], + array( + self::DISPLAY_PARTIAL_TEXT, self::DISPLAY_FULL_TEXT + ) + ) + ) { + $query['pftext'] = $_REQUEST['pftext']; + unset($_REQUEST['pftext']); + } elseif (empty($query['pftext'])) { + $query['pftext'] = self::DISPLAY_PARTIAL_TEXT; + } + + if (Core::isValid( + $_REQUEST['relational_display'], + array( + self::RELATIONAL_KEY, self::RELATIONAL_DISPLAY_COLUMN + ) + ) + ) { + $query['relational_display'] = $_REQUEST['relational_display']; + unset($_REQUEST['relational_display']); + } elseif (empty($query['relational_display'])) { + // The current session value has priority over a + // change via Settings; this change will be apparent + // starting from the next session + $query['relational_display'] = $GLOBALS['cfg']['RelationalDisplay']; + } + + if (Core::isValid( + $_REQUEST['geoOption'], + array( + self::GEOMETRY_DISP_WKT, self::GEOMETRY_DISP_WKB, + self::GEOMETRY_DISP_GEOM + ) + ) + ) { + $query['geoOption'] = $_REQUEST['geoOption']; + unset($_REQUEST['geoOption']); + } elseif (empty($query['geoOption'])) { + $query['geoOption'] = self::GEOMETRY_DISP_GEOM; + } + + if (isset($_REQUEST['display_binary'])) { + $query['display_binary'] = true; + unset($_REQUEST['display_binary']); + } elseif (isset($_REQUEST['display_options_form'])) { + // we know that the checkbox was unchecked + unset($query['display_binary']); + } elseif (isset($_REQUEST['full_text_button'])) { + // do nothing to keep the value that is there in the session + } else { + // selected by default because some operations like OPTIMIZE TABLE + // and all queries involving functions return "binary" contents, + // according to low-level field flags + $query['display_binary'] = true; + } + + if (isset($_REQUEST['display_blob'])) { + $query['display_blob'] = true; + unset($_REQUEST['display_blob']); + } elseif (isset($_REQUEST['display_options_form'])) { + // we know that the checkbox was unchecked + unset($query['display_blob']); + } + + if (isset($_REQUEST['hide_transformation'])) { + $query['hide_transformation'] = true; + unset($_REQUEST['hide_transformation']); + } elseif (isset($_REQUEST['display_options_form'])) { + // we know that the checkbox was unchecked + unset($query['hide_transformation']); + } + + // move current query to the last position, to be removed last + // so only least executed query will be removed if maximum remembered + // queries limit is reached + unset($_SESSION['tmpval']['query'][$sql_md5]); + $_SESSION['tmpval']['query'][$sql_md5] = $query; + + // do not exceed a maximum number of queries to remember + if (count($_SESSION['tmpval']['query']) > 10) { + array_shift($_SESSION['tmpval']['query']); + //echo 'deleting one element ...'; + } + + // populate query configuration + $_SESSION['tmpval']['pftext'] + = $query['pftext']; + $_SESSION['tmpval']['relational_display'] + = $query['relational_display']; + $_SESSION['tmpval']['geoOption'] + = $query['geoOption']; + $_SESSION['tmpval']['display_binary'] = isset( + $query['display_binary'] + ); + $_SESSION['tmpval']['display_blob'] = isset( + $query['display_blob'] + ); + $_SESSION['tmpval']['hide_transformation'] = isset( + $query['hide_transformation'] + ); + $_SESSION['tmpval']['pos'] + = $query['pos']; + $_SESSION['tmpval']['max_rows'] + = $query['max_rows']; + $_SESSION['tmpval']['repeat_cells'] + = $query['repeat_cells']; + } + + /** + * Prepare a table of results returned by a SQL query. + * + * @param integer &$dt_result the link id associated to the query + * which results have to be displayed + * @param array &$displayParts the parts to display + * @param array $analyzed_sql_results analyzed sql results + * @param boolean $is_limited_display With limited operations or not + * + * @return string $table_html Generated HTML content for resulted table + * + * @access public + * + * @see sql.php file + */ + public function getTable( + &$dt_result, array &$displayParts, array $analyzed_sql_results, + $is_limited_display = false + ) { + + /** + * The statement this table is built for. + * @var \PhpMyAdmin\SqlParser\Statements\SelectStatement + */ + if (isset($analyzed_sql_results['statement'])) { + $statement = $analyzed_sql_results['statement']; + } else { + $statement = null; + } + + $table_html = ''; + // Following variable are needed for use in isset/empty or + // use with array indexes/safe use in foreach + $fields_meta = $this->__get('fields_meta'); + $showtable = $this->__get('showtable'); + $printview = $this->__get('printview'); + + /** + * @todo move this to a central place + * @todo for other future table types + */ + $is_innodb = (isset($showtable['Type']) + && $showtable['Type'] == self::TABLE_TYPE_INNO_DB); + + $sql = new Sql(); + if ($is_innodb && $sql->isJustBrowsing($analyzed_sql_results, true)) { + // "j u s t b r o w s i n g" + $pre_count = '~'; + $after_count = Util::showHint( + Sanitize::sanitize( + __('May be approximate. See [doc@faq3-11]FAQ 3.11[/doc].') + ) + ); + } else { + $pre_count = ''; + $after_count = ''; + } + + // 1. ----- Prepares the work ----- + + // 1.1 Gets the information about which functionalities should be + // displayed + + list( + $displayParts, + $total + ) = $this->_setDisplayPartsAndTotal($displayParts); + + // 1.2 Defines offsets for the next and previous pages + if ($displayParts['nav_bar'] == '1') { + list($pos_next, $pos_prev) = $this->_getOffsets(); + } // end if + + // 1.3 Extract sorting expressions. + // we need $sort_expression and $sort_expression_nodirection + // even if there are many table references + $sort_expression = array(); + $sort_expression_nodirection = array(); + $sort_direction = array(); + + if (!is_null($statement) && !empty($statement->order)) { + foreach ($statement->order as $o) { + $sort_expression[] = $o->expr->expr . ' ' . $o->type; + $sort_expression_nodirection[] = $o->expr->expr; + $sort_direction[] = $o->type; + } + } else { + $sort_expression[] = ''; + $sort_expression_nodirection[] = ''; + $sort_direction[] = ''; + } + + $number_of_columns = count($sort_expression_nodirection); + + // 1.4 Prepares display of first and last value of the sorted column + $sorted_column_message = ''; + for ( $i = 0; $i < $number_of_columns; $i++ ) { + $sorted_column_message .= $this->_getSortedColumnMessage( + $dt_result, $sort_expression_nodirection[$i] + ); + } + + // 2. ----- Prepare to display the top of the page ----- + + // 2.1 Prepares a messages with position information + if (($displayParts['nav_bar'] == '1') && isset($pos_next)) { + + $message = $this->_setMessageInformation( + $sorted_column_message, + $analyzed_sql_results, + $total, + $pos_next, + $pre_count, + $after_count + ); + + $table_html .= Util::getMessage( + $message, $this->__get('sql_query'), 'success' + ); + + } elseif ((!isset($printview) || ($printview != '1')) && !$is_limited_display) { + + $table_html .= Util::getMessage( + __('Your SQL query has been executed successfully.'), + $this->__get('sql_query'), 'success' + ); + } + + // 2.3 Prepare the navigation bars + if (strlen($this->__get('table')) === 0) { + + if ($analyzed_sql_results['querytype'] == 'SELECT') { + // table does not always contain a real table name, + // for example in MySQL 5.0.x, the query SHOW STATUS + // returns STATUS as a table name + $this->__set('table', $fields_meta[0]->table); + } else { + $this->__set('table', ''); + } + + } + + // can the result be sorted? + if ($displayParts['sort_lnk'] == '1' && ! is_null($analyzed_sql_results['statement'])) { + + // At this point, $sort_expression is an array + list($unsorted_sql_query, $sort_by_key_html) + = $this->_getUnsortedSqlAndSortByKeyDropDown( + $analyzed_sql_results, $sort_expression + ); + + } else { + $sort_by_key_html = $unsorted_sql_query = ''; + } + + if (($displayParts['nav_bar'] == '1') && !is_null($statement) && (empty($statement->limit))) { + $table_html .= $this->_getPlacedTableNavigations( + $pos_next, $pos_prev, self::PLACE_TOP_DIRECTION_DROPDOWN, + $is_innodb, $sort_by_key_html + ); + } + + // 2b ----- Get field references from Database ----- + // (see the 'relation' configuration variable) + + // initialize map + $map = array(); + + $target = array(); + if (!is_null($statement) && !empty($statement->from)) { + foreach ($statement->from as $field) { + if (!empty($field->table)) { + $target[] = $field->table; + } + } + } + + if (strlen($this->__get('table')) > 0) { + // This method set the values for $map array + $this->_setParamForLinkForeignKeyRelatedTables($map); + + // Coming from 'Distinct values' action of structure page + // We manipulate relations mechanism to show a link to related rows. + if ($this->__get('is_browse_distinct')) { + $map[$fields_meta[1]->name] = array( + $this->__get('table'), + $fields_meta[1]->name, + '', + $this->__get('db') + ); + } + } // end if + // end 2b + + // 3. ----- Prepare the results table ----- + if ($is_limited_display) { + $table_html .= "
      "; + } + + $table_html .= $this->_getTableHeaders( + $displayParts, + $analyzed_sql_results, + $unsorted_sql_query, + $sort_expression, + $sort_expression_nodirection, + $sort_direction, + $is_limited_display + ); + + $table_html .= '' . "\n"; + + $table_html .= $this->_getTableBody( + $dt_result, + $displayParts, + $map, + $analyzed_sql_results, + $is_limited_display + ); + + $this->__set('display_params', null); + + $table_html .= '' . "\n" . '
      '; + + // 4. ----- Prepares the link for multi-fields edit and delete + + if ($displayParts['del_lnk'] == self::DELETE_ROW + && $displayParts['del_lnk'] != self::KILL_PROCESS + ) { + + $table_html .= $this->_getMultiRowOperationLinks( + $dt_result, + $analyzed_sql_results, + $displayParts['del_lnk'] + ); + + } + + // 5. ----- Get the navigation bar at the bottom if required ----- + if (($displayParts['nav_bar'] == '1') && !is_null($statement) && empty($statement->limit)) { + $table_html .= $this->_getPlacedTableNavigations( + $pos_next, $pos_prev, self::PLACE_BOTTOM_DIRECTION_DROPDOWN, + $is_innodb, $sort_by_key_html + ); + } elseif (! isset($printview) || ($printview != '1')) { + $table_html .= "\n" . '

      ' . "\n"; + } + + // 6. ----- Prepare "Query results operations" + if ((! isset($printview) || ($printview != '1')) && ! $is_limited_display) { + $table_html .= $this->_getResultsOperations( + $displayParts, $analyzed_sql_results + ); + } + + return $table_html; + + } // end of the 'getTable()' function + + + /** + * Get offsets for next page and previous page + * + * @return array array with two elements - $pos_next, $pos_prev + * + * @access private + * + * @see getTable() + */ + private function _getOffsets() + { + + if ($_SESSION['tmpval']['max_rows'] == self::ALL_ROWS) { + $pos_next = 0; + $pos_prev = 0; + } else { + + $pos_next = $_SESSION['tmpval']['pos'] + + $_SESSION['tmpval']['max_rows']; + + $pos_prev = $_SESSION['tmpval']['pos'] + - $_SESSION['tmpval']['max_rows']; + + if ($pos_prev < 0) { + $pos_prev = 0; + } + } + + return array($pos_next, $pos_prev); + + } // end of the '_getOffsets()' function + + + /** + * Prepare sorted column message + * + * @param integer &$dt_result the link id associated to the + * query which results have to + * be displayed + * @param string $sort_expression_nodirection sort expression without direction + * + * @return string html content + * null if not found sorted column + * + * @access private + * + * @see getTable() + */ + private function _getSortedColumnMessage( + &$dt_result, $sort_expression_nodirection + ) { + + $fields_meta = $this->__get('fields_meta'); // To use array indexes + + if (empty($sort_expression_nodirection)) { + return null; + } + + if (mb_strpos($sort_expression_nodirection, '.') === false) { + $sort_table = $this->__get('table'); + $sort_column = $sort_expression_nodirection; + } else { + list($sort_table, $sort_column) + = explode('.', $sort_expression_nodirection); + } + + $sort_table = Util::unQuote($sort_table); + $sort_column = Util::unQuote($sort_column); + + // find the sorted column index in row result + // (this might be a multi-table query) + $sorted_column_index = false; + + foreach ($fields_meta as $key => $meta) { + if (($meta->table == $sort_table) && ($meta->name == $sort_column)) { + $sorted_column_index = $key; + break; + } + } + + if ($sorted_column_index === false) { + return null; + } + + // fetch first row of the result set + $row = $GLOBALS['dbi']->fetchRow($dt_result); + + // initializing default arguments + $default_function = [Core::class, 'mimeDefaultFunction']; + $transformation_plugin = $default_function; + $transform_options = array(); + + // check for non printable sorted row data + $meta = $fields_meta[$sorted_column_index]; + + if (stristr($meta->type, self::BLOB_FIELD) + || ($meta->type == self::GEOMETRY_FIELD) + ) { + + $column_for_first_row = $this->_handleNonPrintableContents( + $meta->type, $row[$sorted_column_index], + $transformation_plugin, $transform_options, + $default_function, $meta + ); + + } else { + $column_for_first_row = $row[$sorted_column_index]; + } + + $column_for_first_row = mb_strtoupper( + mb_substr( + $column_for_first_row, 0, $GLOBALS['cfg']['LimitChars'] + ) . '...' + ); + + // fetch last row of the result set + $GLOBALS['dbi']->dataSeek($dt_result, $this->__get('num_rows') - 1); + $row = $GLOBALS['dbi']->fetchRow($dt_result); + + // check for non printable sorted row data + $meta = $fields_meta[$sorted_column_index]; + if (stristr($meta->type, self::BLOB_FIELD) + || ($meta->type == self::GEOMETRY_FIELD) + ) { + + $column_for_last_row = $this->_handleNonPrintableContents( + $meta->type, $row[$sorted_column_index], + $transformation_plugin, $transform_options, + $default_function, $meta + ); + + } else { + $column_for_last_row = $row[$sorted_column_index]; + } + + $column_for_last_row = mb_strtoupper( + mb_substr( + $column_for_last_row, 0, $GLOBALS['cfg']['LimitChars'] + ) . '...' + ); + + // reset to first row for the loop in _getTableBody() + $GLOBALS['dbi']->dataSeek($dt_result, 0); + + // we could also use here $sort_expression_nodirection + return ' [' . htmlspecialchars($sort_column) + . ': ' . htmlspecialchars($column_for_first_row) . ' - ' + . htmlspecialchars($column_for_last_row) . ']'; + } // end of the '_getSortedColumnMessage()' function + + + /** + * Set the content that needs to be shown in message + * + * @param string $sorted_column_message the message for sorted column + * @param array $analyzed_sql_results the analyzed query + * @param integer $total the total number of rows returned by + * the SQL query without any + * programmatically appended LIMIT clause + * @param integer $pos_next the offset for next page + * @param string $pre_count the string renders before row count + * @param string $after_count the string renders after row count + * + * @return Message $message an object of Message + * + * @access private + * + * @see getTable() + */ + private function _setMessageInformation( + $sorted_column_message, array $analyzed_sql_results, $total, + $pos_next, $pre_count, $after_count + ) { + + $unlim_num_rows = $this->__get('unlim_num_rows'); // To use in isset() + + if (!empty($analyzed_sql_results['statement']->limit)) { + + $first_shown_rec = $analyzed_sql_results['statement']->limit->offset; + $row_count = $analyzed_sql_results['statement']->limit->rowCount; + + if ($row_count < $total) { + $last_shown_rec = $first_shown_rec + $row_count - 1; + } else { + $last_shown_rec = $first_shown_rec + $total - 1; + } + + } elseif (($_SESSION['tmpval']['max_rows'] == self::ALL_ROWS) + || ($pos_next > $total) + ) { + + $first_shown_rec = $_SESSION['tmpval']['pos']; + $last_shown_rec = $total - 1; + + } else { + + $first_shown_rec = $_SESSION['tmpval']['pos']; + $last_shown_rec = $pos_next - 1; + + } + + $table = new Table($this->__get('table'), $this->__get('db')); + if ($table->isView() + && ($total == $GLOBALS['cfg']['MaxExactCountViews']) + ) { + + $message = Message::notice( + __( + 'This view has at least this number of rows. ' + . 'Please refer to %sdocumentation%s.' + ) + ); + + $message->addParam('[doc@cfg_MaxExactCount]'); + $message->addParam('[/doc]'); + $message_view_warning = Util::showHint($message); + + } else { + $message_view_warning = false; + } + + $message = Message::success(__('Showing rows %1s - %2s')); + $message->addParam($first_shown_rec); + + if ($message_view_warning !== false) { + $message->addParamHtml('... ' . $message_view_warning); + } else { + $message->addParam($last_shown_rec); + } + + $message->addText('('); + + if ($message_view_warning === false) { + + if (isset($unlim_num_rows) && ($unlim_num_rows != $total)) { + $message_total = Message::notice( + $pre_count . __('%1$d total, %2$d in query') + ); + $message_total->addParam($total); + $message_total->addParam($unlim_num_rows); + } else { + $message_total = Message::notice($pre_count . __('%d total')); + $message_total->addParam($total); + } + + if (!empty($after_count)) { + $message_total->addHtml($after_count); + } + $message->addMessage($message_total, ''); + + $message->addText(', ', ''); + } + + $message_qt = Message::notice(__('Query took %01.4f seconds.') . ')'); + $message_qt->addParam($this->__get('querytime')); + + $message->addMessage($message_qt, ''); + if (! is_null($sorted_column_message)) { + $message->addHtml($sorted_column_message, ''); + } + + return $message; + } // end of the '_setMessageInformation()' function + + /** + * Set the value of $map array for linking foreign key related tables + * + * @param array &$map the list of relations + * + * @return void + * + * @access private + * + * @see getTable() + */ + private function _setParamForLinkForeignKeyRelatedTables(array &$map) + { + // To be able to later display a link to the related table, + // we verify both types of relations: either those that are + // native foreign keys or those defined in the phpMyAdmin + // configuration storage. If no PMA storage, we won't be able + // to use the "column to display" notion (for example show + // the name related to a numeric id). + $exist_rel = $this->relation->getForeigners( + $this->__get('db'), $this->__get('table'), '', self::POSITION_BOTH + ); + + if (! empty($exist_rel)) { + + foreach ($exist_rel as $master_field => $rel) { + if ($master_field != 'foreign_keys_data') { + $display_field = $this->relation->getDisplayField( + $rel['foreign_db'], $rel['foreign_table'] + ); + $map[$master_field] = array( + $rel['foreign_table'], + $rel['foreign_field'], + $display_field, + $rel['foreign_db'] + ); + } else { + foreach ($rel as $key => $one_key) { + foreach ($one_key['index_list'] as $index => $one_field) { + $display_field = $this->relation->getDisplayField( + isset($one_key['ref_db_name']) + ? $one_key['ref_db_name'] + : $GLOBALS['db'], + $one_key['ref_table_name'] + ); + + $map[$one_field] = array( + $one_key['ref_table_name'], + $one_key['ref_index_list'][$index], + $display_field, + isset($one_key['ref_db_name']) + ? $one_key['ref_db_name'] + : $GLOBALS['db'] + ); + } + } + } + } // end while + } // end if + + } // end of the '_setParamForLinkForeignKeyRelatedTables()' function + + + /** + * Prepare multi field edit/delete links + * + * @param integer &$dt_result the link id associated to the query which + * results have to be displayed + * @param array $analyzed_sql_results analyzed sql results + * @param string $del_link the display element - 'del_link' + * + * @return string $links_html html content + * + * @access private + * + * @see getTable() + */ + private function _getMultiRowOperationLinks( + &$dt_result, array $analyzed_sql_results, $del_link + ) { + + $links_html = '\n"; + + $links_html .= '' + . "\n"; + + if (! empty($url_query)) { + $links_html .= '' . "\n"; + } + + // fetch last row of the result set + $GLOBALS['dbi']->dataSeek($dt_result, $this->__get('num_rows') - 1); + $row = $GLOBALS['dbi']->fetchRow($dt_result); + + // @see DbiMysqi::fetchRow & DatabaseInterface::fetchRow + if (! is_array($row)) { + $row = array(); + } + + // $clause_is_unique is needed by getTable() to generate the proper param + // in the multi-edit and multi-delete form + list($where_clause, $clause_is_unique, $condition_array) + = Util::getUniqueCondition( + $dt_result, // handle + $this->__get('fields_cnt'), // fields_cnt + $this->__get('fields_meta'), // fields_meta + $row, // row + false, // force_unique + false, // restrict_to_table + $analyzed_sql_results // analyzed_sql_results + ); + unset($where_clause, $condition_array); + + // reset to first row for the loop in _getTableBody() + $GLOBALS['dbi']->dataSeek($dt_result, 0); + + $links_html .= '' . "\n"; + + $links_html .= '' . "\n"; + + return $links_html; + + } // end of the '_getMultiRowOperationLinks()' function + + + /** + * Prepare table navigation bar at the top or bottom + * + * @param integer $pos_next the offset for the "next" page + * @param integer $pos_prev the offset for the "previous" page + * @param string $place the place to show navigation + * @param boolean $is_innodb whether its InnoDB or not + * @param string $sort_by_key_html the sort by key dialog + * + * @return string html content of navigation bar + * + * @access private + * + * @see _getTable() + */ + private function _getPlacedTableNavigations( + $pos_next, $pos_prev, $place, $is_innodb, $sort_by_key_html + ) { + + $navigation_html = ''; + + if ($place == self::PLACE_BOTTOM_DIRECTION_DROPDOWN) { + $navigation_html .= '
      ' . "\n"; + } + + $navigation_html .= $this->_getTableNavigation( + $pos_next, $pos_prev, $is_innodb, $sort_by_key_html + ); + + if ($place == self::PLACE_TOP_DIRECTION_DROPDOWN) { + $navigation_html .= "\n"; + } + + return $navigation_html; + + } // end of the '_getPlacedTableNavigations()' function + + /** + * Generates HTML to display the Create view in span tag + * + * @param array $analyzed_sql_results analyzed sql results + * @param string $url_query String with URL Parameters + * + * @return string + * + * @access private + * + * @see _getResultsOperations() + */ + private function _getLinkForCreateView(array $analyzed_sql_results, $url_query) + { + $results_operations_html = ''; + if (empty($analyzed_sql_results['procedure'])) { + + $results_operations_html .= '' + . Util::linkOrButton( + 'view_create.php' . $url_query, + Util::getIcon( + 'b_view_add', __('Create view'), true + ), + array('class' => 'create_view ajax') + ) + . '' . "\n"; + } + return $results_operations_html; + + } + + /** + * Calls the _getResultsOperations with $only_view as true + * + * @param array $analyzed_sql_results analyzed sql results + * + * @return string + * + * @access public + * + */ + public function getCreateViewQueryResultOp(array $analyzed_sql_results) + { + + $results_operations_html = ''; + //calling to _getResultOperations with a fake $displayParts + //and setting only_view parameter to be true to generate just view + $results_operations_html .= $this->_getResultsOperations( + array(), + $analyzed_sql_results, + true + ); + return $results_operations_html; + } + + /** + * Get copy to clipboard links for results operations + * + * @return string $html + * + * @access private + */ + private function _getCopytoclipboardLinks() + { + $html = Util::linkOrButton( + '#', + Util::getIcon( + 'b_insrow', __('Copy to clipboard'), true + ), + array('id' => 'copyToClipBoard') + ); + + return $html; + } + + /** + * Get printview links for results operations + * + * @return string $html + * + * @access private + */ + private function _getPrintviewLinks() + { + $html = Util::linkOrButton( + '#', + Util::getIcon( + 'b_print', __('Print'), true + ), + array('id' => 'printView'), + 'print_view' + ); + + return $html; + } + + /** + * Get operations that are available on results. + * + * @param array $displayParts the parts to display + * @param array $analyzed_sql_results analyzed sql results + * @param boolean $only_view Whether to show only view + * + * @return string $results_operations_html html content + * + * @access private + * + * @see getTable() + */ + private function _getResultsOperations( + array $displayParts, array $analyzed_sql_results, $only_view = false + ) { + global $printview; + + $results_operations_html = ''; + $fields_meta = $this->__get('fields_meta'); // To safe use in foreach + $header_shown = false; + $header = '
      '; + } + return $results_operations_html; + } + + // Displays "printable view" link if required + if ($displayParts['pview_lnk'] == '1') { + $results_operations_html .= $this->_getPrintviewLinks(); + $results_operations_html .= $this->_getCopytoclipboardLinks(); + } // end displays "printable view" + + // Export link + // (the url_query has extra parameters that won't be used to export) + // (the single_table parameter is used in Export::getDisplay() + // to hide the SQL and the structure export dialogs) + // If the parser found a PROCEDURE clause + // (most probably PROCEDURE ANALYSE()) it makes no sense to + // display the Export link). + if (($analyzed_sql_results['querytype'] == self::QUERY_TYPE_SELECT) + && ! isset($printview) + && empty($analyzed_sql_results['procedure']) + ) { + + if (count($analyzed_sql_results['select_tables']) == 1) { + $_url_params['single_table'] = 'true'; + } + + if (! $header_shown) { + $results_operations_html .= $header; + $header_shown = true; + } + + $_url_params['unlim_num_rows'] = $this->__get('unlim_num_rows'); + + /** + * At this point we don't know the table name; this can happen + * for example with a query like + * SELECT bike_code FROM (SELECT bike_code FROM bikes) tmp + * As a workaround we set in the table parameter the name of the + * first table of this database, so that tbl_export.php and + * the script it calls do not fail + */ + if (empty($_url_params['table']) && ! empty($_url_params['db'])) { + $_url_params['table'] = $GLOBALS['dbi']->fetchValue("SHOW TABLES"); + /* No result (probably no database selected) */ + if ($_url_params['table'] === false) { + unset($_url_params['table']); + } + } + + $results_operations_html .= Util::linkOrButton( + 'tbl_export.php' . Url::getCommon($_url_params), + Util::getIcon( + 'b_tblexport', __('Export'), true + ) + ) + . "\n"; + + // prepare chart + $results_operations_html .= Util::linkOrButton( + 'tbl_chart.php' . Url::getCommon($_url_params), + Util::getIcon( + 'b_chart', __('Display chart'), true + ) + ) + . "\n"; + + // prepare GIS chart + $geometry_found = false; + // If at least one geometry field is found + foreach ($fields_meta as $meta) { + if ($meta->type == self::GEOMETRY_FIELD) { + $geometry_found = true; + break; + } + } + + if ($geometry_found) { + $results_operations_html + .= Util::linkOrButton( + 'tbl_gis_visualization.php' + . Url::getCommon($_url_params), + Util::getIcon( + 'b_globe', + __('Visualize GIS data'), + true + ) + ) + . "\n"; + } + } + + // CREATE VIEW + /** + * + * @todo detect privileges to create a view + * (but see 2006-01-19 note in PhpMyAdmin\Display\CreateTable, + * I think we cannot detect db-specific privileges reliably) + * Note: we don't display a Create view link if we found a PROCEDURE clause + */ + if (!$header_shown) { + $results_operations_html .= $header; + $header_shown = true; + } + + $results_operations_html .= $this->_getLinkForCreateView( + $analyzed_sql_results, $url_query + ); + + if ($header_shown) { + $results_operations_html .= '
      '; + } + + return $results_operations_html; + + } // end of the '_getResultsOperations()' function + + + /** + * Verifies what to do with non-printable contents (binary or BLOB) + * in Browse mode. + * + * @param string $category BLOB|BINARY|GEOMETRY + * @param string $content the binary content + * @param mixed $transformation_plugin transformation plugin. + * Can also be the default function: + * Core::mimeDefaultFunction + * @param string $transform_options transformation parameters + * @param string $default_function default transformation function + * @param object $meta the meta-information about the field + * @param array $url_params parameters that should go to the + * download link + * @param boolean &$is_truncated the result is truncated or not + * + * @return mixed string or float + * + * @access private + * + * @see _getDataCellForGeometryColumns(), + * _getDataCellForNonNumericColumns(), + * _getSortedColumnMessage() + */ + private function _handleNonPrintableContents( + $category, $content, $transformation_plugin, $transform_options, + $default_function, $meta, array $url_params = array(), &$is_truncated = null + ) { + + $is_truncated = false; + $result = '[' . $category; + + if (isset($content)) { + + $size = strlen($content); + $display_size = Util::formatByteDown($size, 3, 1); + $result .= ' - ' . $display_size[0] . ' ' . $display_size[1]; + + } else { + + $result .= ' - NULL'; + $size = 0; + + } + + $result .= ']'; + + // if we want to use a text transformation on a BLOB column + if (gettype($transformation_plugin) === "object") { + $posMimeOctetstream = strpos( + $transformation_plugin->getMIMESubtype(), + 'Octetstream' + ); + $posMimeText = strpos($transformation_plugin->getMIMEtype(), 'Text'); + if ($posMimeOctetstream + || $posMimeText !== false + ) { + // Applying Transformations on hex string of binary data + // seems more appropriate + $result = pack("H*", bin2hex($content)); + } + } + + if ($size <= 0) { + return($result); + } + + if ($default_function != $transformation_plugin) { + $result = $transformation_plugin->applyTransformation( + $result, + $transform_options, + $meta + ); + return($result); + } + + $result = $default_function($result, array(), $meta); + if (($_SESSION['tmpval']['display_binary'] + && $meta->type === self::STRING_FIELD) + || ($_SESSION['tmpval']['display_blob'] + && stristr($meta->type, self::BLOB_FIELD)) + ) { + // in this case, restart from the original $content + if (mb_check_encoding($content, 'utf-8') + && !preg_match('/[\x00-\x08\x0B\x0C\x0E-\x1F\x80-\x9F]/u', $content) + ) { + // show as text if it's valid utf-8 + $result = htmlspecialchars($content); + } else { + $result = '0x' . bin2hex($content); + } + list( + $is_truncated, + $result, + // skip 3rd param + ) = $this->_getPartialText($result); + } + + /* Create link to download */ + + // in PHP < 5.5, empty() only checks variables + $tmpdb = $this->__get('db'); + if (count($url_params) > 0 + && (!empty($tmpdb) && !empty($meta->orgtable)) + ) { + $result = '
      ' + . $result . ''; + } + + return($result); + + } // end of the '_handleNonPrintableContents()' function + + + /** + * Retrieves the associated foreign key info for a data cell + * + * @param array $map the list of relations + * @param object $meta the meta-information about the field + * @param string $where_comparison data for the where clause + * + * @return string formatted data + * + * @access private + * + */ + private function _getFromForeign(array $map, $meta, $where_comparison) + { + $dispsql = 'SELECT ' + . Util::backquote($map[$meta->name][2]) + . ' FROM ' + . Util::backquote($map[$meta->name][3]) + . '.' + . Util::backquote($map[$meta->name][0]) + . ' WHERE ' + . Util::backquote($map[$meta->name][1]) + . $where_comparison; + + $dispresult = $GLOBALS['dbi']->tryQuery( + $dispsql, + DatabaseInterface::CONNECT_USER, + DatabaseInterface::QUERY_STORE + ); + + if ($dispresult && $GLOBALS['dbi']->numRows($dispresult) > 0) { + list($dispval) = $GLOBALS['dbi']->fetchRow($dispresult, 0); + } else { + $dispval = __('Link not found!'); + } + + $GLOBALS['dbi']->freeResult($dispresult); + + return $dispval; + } + + /** + * Prepares the displayable content of a data cell in Browse mode, + * taking into account foreign key description field and transformations + * + * @param string $class css classes for the td element + * @param bool $condition_field whether the column is a part of + * the where clause + * @param array $analyzed_sql_results the analyzed query + * @param object $meta the meta-information about the + * field + * @param array $map the list of relations + * @param string $data data + * @param string $displayedData data that will be displayed (maybe be chunked) + * @param object|string $transformation_plugin transformation plugin. + * Can also be the default function: + * Core::mimeDefaultFunction + * @param string $default_function default function + * @param string $nowrap 'nowrap' if the content should + * not be wrapped + * @param string $where_comparison data for the where clause + * @param array $transform_options options for transformation + * @param bool $is_field_truncated whether the field is truncated + * @param string $original_length of a truncated column, or '' + * + * @return string formatted data + * + * @access private + * + * @see _getDataCellForNumericColumns(), _getDataCellForGeometryColumns(), + * _getDataCellForNonNumericColumns(), + * + */ + private function _getRowData( + $class, $condition_field, array $analyzed_sql_results, $meta, + array $map, $data, $displayedData, + $transformation_plugin, $default_function, $nowrap, $where_comparison, + array $transform_options, $is_field_truncated, $original_length='' + ) { + $relational_display = $_SESSION['tmpval']['relational_display']; + $printview = $this->__get('printview'); + $decimals = isset($meta->decimals) ? $meta->decimals : '-1'; + $result = '_addClass( + $class, $condition_field, $meta, $nowrap, + $is_field_truncated, $transformation_plugin, $default_function + ) + . '">'; + + if (!empty($analyzed_sql_results['statement']->expr)) { + foreach ($analyzed_sql_results['statement']->expr as $expr) { + if ((empty($expr->alias)) || (empty($expr->column))) { + continue; + } + if (strcasecmp($meta->name, $expr->alias) == 0) { + $meta->name = $expr->column; + } + } + } + + if (isset($map[$meta->name])) { + + // Field to display from the foreign table? + if (isset($map[$meta->name][2]) + && strlen($map[$meta->name][2]) > 0 + ) { + $dispval = $this->_getFromForeign( + $map, $meta, $where_comparison + ); + } else { + $dispval = ''; + } // end if... else... + + if (isset($printview) && ($printview == '1')) { + + $result .= ($transformation_plugin != $default_function + ? $transformation_plugin->applyTransformation( + $data, + $transform_options, + $meta + ) + : $default_function($data) + ) + . ' [->' . $dispval . ']'; + + } else { + + if ($relational_display == self::RELATIONAL_KEY) { + + // user chose "relational key" in the display options, so + // the title contains the display field + $title = (! empty($dispval)) + ? htmlspecialchars($dispval) + : ''; + + } else { + $title = htmlspecialchars($data); + } + + $sqlQuery = 'SELECT * FROM ' + . Util::backquote($map[$meta->name][3]) . '.' + . Util::backquote($map[$meta->name][0]) + . ' WHERE ' + . Util::backquote($map[$meta->name][1]) + . $where_comparison; + + $_url_params = array( + 'db' => $map[$meta->name][3], + 'table' => $map[$meta->name][0], + 'pos' => '0', + 'sql_signature' => Core::signSqlQuery($sqlQuery), + 'sql_query' => $sqlQuery, + ); + + if ($transformation_plugin != $default_function) { + // always apply a transformation on the real data, + // not on the display field + $displayedData = $transformation_plugin->applyTransformation( + $data, + $transform_options, + $meta + ); + } else { + + if ($relational_display == self::RELATIONAL_DISPLAY_COLUMN + && ! empty($map[$meta->name][2]) + ) { + // user chose "relational display field" in the + // display options, so show display field in the cell + $displayedData = $default_function($dispval); + } else { + // otherwise display data in the cell + $displayedData = $default_function($displayedData); + } + + } + + $tag_params = array('title' => $title); + if (strpos($class, 'grid_edit') !== false) { + $tag_params['class'] = 'ajax'; + } + $result .= Util::linkOrButton( + 'sql.php' . Url::getCommon($_url_params), + $displayedData, $tag_params + ); + } + + } else { + $result .= ($transformation_plugin != $default_function + ? $transformation_plugin->applyTransformation( + $data, + $transform_options, + $meta + ) + : $default_function($data) + ); + } + + $result .= '' . "\n"; + + return $result; + + } // end of the '_getRowData()' function + + + /** + * Prepares a checkbox for multi-row submits + * + * @param string $del_url delete url + * @param array $displayParts array with explicit indexes for all + * the display elements + * @param string $row_no the row number + * @param string $where_clause_html url encoded where clause + * @param array $condition_array array of conditions in the where clause + * @param string $id_suffix suffix for the id + * @param string $class css classes for the td element + * + * @return string the generated HTML + * + * @access private + * + * @see _getTableBody(), _getCheckboxAndLinks() + */ + private function _getCheckboxForMultiRowSubmissions( + $del_url, array $displayParts, $row_no, $where_clause_html, array $condition_array, + $id_suffix, $class + ) { + + $ret = ''; + + if (! empty($del_url) && $displayParts['del_lnk'] != self::KILL_PROCESS) { + + $ret .= '' + . '' + . ' '; + } + + return $ret; + + } // end of the '_getCheckboxForMultiRowSubmissions()' function + + + /** + * Prepares an Edit link + * + * @param string $edit_url edit url + * @param string $class css classes for td element + * @param string $edit_str text for the edit link + * @param string $where_clause where clause + * @param string $where_clause_html url encoded where clause + * + * @return string the generated HTML + * + * @access private + * + * @see _getTableBody(), _getCheckboxAndLinks() + */ + private function _getEditLink( + $edit_url, $class, $edit_str, $where_clause, $where_clause_html + ) { + + $ret = ''; + if (! empty($edit_url)) { + + $ret .= '' + . Util::linkOrButton($edit_url, $edit_str); + /* + * Where clause for selecting this row uniquely is provided as + * a hidden input. Used by jQuery scripts for handling grid editing + */ + if (! empty($where_clause)) { + $ret .= ''; + } + $ret .= ''; + } + + return $ret; + + } // end of the '_getEditLink()' function + + + /** + * Prepares an Copy link + * + * @param string $copy_url copy url + * @param string $copy_str text for the copy link + * @param string $where_clause where clause + * @param string $where_clause_html url encoded where clause + * @param string $class css classes for the td element + * + * @return string the generated HTML + * + * @access private + * + * @see _getTableBody(), _getCheckboxAndLinks() + */ + private function _getCopyLink( + $copy_url, $copy_str, $where_clause, $where_clause_html, $class + ) { + + $ret = ''; + if (! empty($copy_url)) { + + $ret .= '' + . Util::linkOrButton($copy_url, $copy_str); + + /* + * Where clause for selecting this row uniquely is provided as + * a hidden input. Used by jQuery scripts for handling grid editing + */ + if (! empty($where_clause)) { + $ret .= ''; + } + $ret .= ''; + } + + return $ret; + + } // end of the '_getCopyLink()' function + + + /** + * Prepares a Delete link + * + * @param string $del_url delete url + * @param string $del_str text for the delete link + * @param string $js_conf text for the JS confirmation + * @param string $class css classes for the td element + * + * @return string the generated HTML + * + * @access private + * + * @see _getTableBody(), _getCheckboxAndLinks() + */ + private function _getDeleteLink($del_url, $del_str, $js_conf, $class) + { + + $ret = ''; + if (empty($del_url)) { + return $ret; + } + + $ret .= '' + . Util::linkOrButton( + $del_url, + $del_str, + array('class' => 'delete_row requireConfirm' . $ajax) + ) + . '
      ' . $js_conf . '
      ' + . ''; + + return $ret; + + } // end of the '_getDeleteLink()' function + + + /** + * Prepare checkbox and links at some position (left or right) + * (only called for horizontal mode) + * + * @param string $position the position of the checkbox and links + * @param string $del_url delete url + * @param array $displayParts array with explicit indexes for all the + * display elements + * @param string $row_no row number + * @param string $where_clause where clause + * @param string $where_clause_html url encoded where clause + * @param array $condition_array array of conditions in the where clause + * @param string $edit_url edit url + * @param string $copy_url copy url + * @param string $class css classes for the td elements + * @param string $edit_str text for the edit link + * @param string $copy_str text for the copy link + * @param string $del_str text for the delete link + * @param string $js_conf text for the JS confirmation + * + * @return string the generated HTML + * + * @access private + * + * @see _getPlacedLinks() + */ + private function _getCheckboxAndLinks( + $position, $del_url, array $displayParts, $row_no, $where_clause, + $where_clause_html, array $condition_array, + $edit_url, $copy_url, $class, $edit_str, $copy_str, $del_str, $js_conf + ) { + + $ret = ''; + + if ($position == self::POSITION_LEFT) { + + $ret .= $this->_getCheckboxForMultiRowSubmissions( + $del_url, $displayParts, $row_no, $where_clause_html, + $condition_array, '_left', '' + ); + + $ret .= $this->_getEditLink( + $edit_url, $class, $edit_str, $where_clause, $where_clause_html + ); + + $ret .= $this->_getCopyLink( + $copy_url, $copy_str, $where_clause, $where_clause_html, '' + ); + + $ret .= $this->_getDeleteLink($del_url, $del_str, $js_conf, ''); + + } elseif ($position == self::POSITION_RIGHT) { + + $ret .= $this->_getDeleteLink($del_url, $del_str, $js_conf, ''); + + $ret .= $this->_getCopyLink( + $copy_url, $copy_str, $where_clause, $where_clause_html, '' + ); + + $ret .= $this->_getEditLink( + $edit_url, $class, $edit_str, $where_clause, $where_clause_html + ); + + $ret .= $this->_getCheckboxForMultiRowSubmissions( + $del_url, $displayParts, $row_no, $where_clause_html, + $condition_array, '_right', '' + ); + + } else { // $position == self::POSITION_NONE + + $ret .= $this->_getCheckboxForMultiRowSubmissions( + $del_url, $displayParts, $row_no, $where_clause_html, + $condition_array, '_left', '' + ); + } + + return $ret; + + } // end of the '_getCheckboxAndLinks()' function + + /** + * Truncates given string based on LimitChars configuration + * and Session pftext variable + * (string is truncated only if necessary) + * + * @param string $str string to be truncated + * + * @return mixed + * + * @access private + * + * @see _handleNonPrintableContents(), _getDataCellForGeometryColumns(), + * _getDataCellForNonNumericColumns + */ + private function _getPartialText($str) + { + $original_length = mb_strlen($str); + if ($original_length > $GLOBALS['cfg']['LimitChars'] + && $_SESSION['tmpval']['pftext'] === self::DISPLAY_PARTIAL_TEXT + ) { + $str = mb_substr( + $str, 0, $GLOBALS['cfg']['LimitChars'] + ) . '...'; + $truncated = true; + } else { + $truncated = false; + } + + return array($truncated, $str, $original_length); + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Encoding.php b/php/apps/phpmyadmin49/html/libraries/classes/Encoding.php new file mode 100644 index 00000000..f17e3117 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Encoding.php @@ -0,0 +1,334 @@ + array('iconv', self::ENGINE_ICONV, 'iconv'), + 'recode' => array('recode_string', self::ENGINE_RECODE, 'recode'), + 'mb' => array('mb_convert_encoding', self::ENGINE_MB, 'mbstring'), + 'none' => array('isset', self::ENGINE_NONE, ''), + ); + + /** + * Order of automatic detection of engines + * + * @var array + */ + private static $_engineorder = array( + 'iconv', 'mb', 'recode', + ); + + /** + * Kanji encodings list + * + * @var string + */ + private static $_kanji_encodings = 'ASCII,SJIS,EUC-JP,JIS'; + + /** + * Initializes encoding engine detecting available backends. + * + * @return void + */ + public static function initEngine() + { + $engine = 'auto'; + if (isset($GLOBALS['cfg']['RecodingEngine'])) { + $engine = $GLOBALS['cfg']['RecodingEngine']; + } + + /* Use user configuration */ + if (isset(self::$_enginemap[$engine])) { + if (function_exists(self::$_enginemap[$engine][0])) { + self::$_engine = self::$_enginemap[$engine][1]; + return; + } else { + Core::warnMissingExtension(self::$_enginemap[$engine][2]); + } + } + + /* Autodetection */ + foreach (self::$_engineorder as $engine) { + if (function_exists(self::$_enginemap[$engine][0])) { + self::$_engine = self::$_enginemap[$engine][1]; + return; + } + } + + /* Fallback to none conversion */ + self::$_engine = self::ENGINE_NONE; + } + + /** + * Setter for engine. Use with caution, mostly useful for testing. + * + * @param int $engine Engine enconding + * + * @return void + */ + public static function setEngine($engine) + { + self::$_engine = $engine; + } + + /** + * Checks whether there is any charset conversion supported + * + * @return bool + */ + public static function isSupported() + { + if (is_null(self::$_engine)) { + self::initEngine(); + } + return self::$_engine != self::ENGINE_NONE; + } + + /** + * Converts encoding of text according to parameters with detected + * conversion function. + * + * @param string $src_charset source charset + * @param string $dest_charset target charset + * @param string $what what to convert + * + * @return string converted text + * + * @access public + */ + public static function convertString($src_charset, $dest_charset, $what) + { + if ($src_charset == $dest_charset) { + return $what; + } + if (is_null(self::$_engine)) { + self::initEngine(); + } + switch (self::$_engine) { + case self::ENGINE_RECODE: + return recode_string( + $src_charset . '..' . $dest_charset, + $what + ); + case self::ENGINE_ICONV: + return iconv( + $src_charset, + $dest_charset . + (isset($GLOBALS['cfg']['IconvExtraParams']) ? $GLOBALS['cfg']['IconvExtraParams'] : ''), + $what + ); + case self::ENGINE_MB: + return mb_convert_encoding( + $what, + $dest_charset, + $src_charset + ); + default: + return $what; + } + } + + /** + * Detects whether Kanji encoding is available + * + * @return bool + */ + public static function canConvertKanji() + { + return $GLOBALS['lang'] == 'ja'; + } + + /** + * Setter for Kanji encodings. Use with caution, mostly useful for testing. + * + * @return string + */ + public static function getKanjiEncodings() + { + return self::$_kanji_encodings; + } + + /** + * Setter for Kanji encodings. Use with caution, mostly useful for testing. + * + * @param string $value Kanji encodings list + * + * @return void + */ + public static function setKanjiEncodings($value) + { + self::$_kanji_encodings = $value; + } + + /** + * Reverses SJIS & EUC-JP position in the encoding codes list + * + * @return void + */ + public static function kanjiChangeOrder() + { + $parts = explode(',', self::$_kanji_encodings); + if ($parts[1] == 'EUC-JP') { + self::$_kanji_encodings = 'ASCII,SJIS,EUC-JP,JIS'; + } else { + self::$_kanji_encodings = 'ASCII,EUC-JP,SJIS,JIS'; + } + } + + /** + * Kanji string encoding convert + * + * @param string $str the string to convert + * @param string $enc the destination encoding code + * @param string $kana set 'kana' convert to JIS-X208-kana + * + * @return string the converted string + */ + public static function kanjiStrConv($str, $enc, $kana) + { + if ($enc == '' && $kana == '') { + return $str; + } + + $string_encoding = mb_detect_encoding($str, self::$_kanji_encodings); + if ($string_encoding === false) { + $string_encoding = 'utf-8'; + } + + if ($kana == 'kana') { + $dist = mb_convert_kana($str, 'KV', $string_encoding); + $str = $dist; + } + if ($string_encoding != $enc && $enc != '') { + $dist = mb_convert_encoding($str, $enc, $string_encoding); + } else { + $dist = $str; + } + return $dist; + } + + + /** + * Kanji file encoding convert + * + * @param string $file the name of the file to convert + * @param string $enc the destination encoding code + * @param string $kana set 'kana' convert to JIS-X208-kana + * + * @return string the name of the converted file + */ + public static function kanjiFileConv($file, $enc, $kana) + { + if ($enc == '' && $kana == '') { + return $file; + } + $tmpfname = tempnam($GLOBALS['PMA_Config']->getUploadTempDir(), $enc); + $fpd = fopen($tmpfname, 'wb'); + $fps = fopen($file, 'r'); + self::kanjiChangeOrder(); + while (!feof($fps)) { + $line = fgets($fps, 4096); + $dist = self::kanjiStrConv($line, $enc, $kana); + fputs($fpd, $dist); + } // end while + self::kanjiChangeOrder(); + fclose($fps); + fclose($fpd); + unlink($file); + + return $tmpfname; + } + + /** + * Defines radio form fields to switch between encoding modes + * + * @return string xhtml code for the radio controls + */ + public static function kanjiEncodingForm() + { + return Template::get('encoding/kanji_encoding_form')->render(); + } + + /** + * Lists available encodings. + * + * @return array + */ + public static function listEncodings() + { + if (is_null(self::$_engine)) { + self::initEngine(); + } + /* Most engines do not support listing */ + if (self::$_engine != self::ENGINE_MB) { + return $GLOBALS['cfg']['AvailableCharsets']; + } + + return array_intersect( + array_map('strtolower', mb_list_encodings()), + $GLOBALS['cfg']['AvailableCharsets'] + ); + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Engines/Bdb.php b/php/apps/phpmyadmin49/html/libraries/classes/Engines/Bdb.php new file mode 100644 index 00000000..cde9480c --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Engines/Bdb.php @@ -0,0 +1,75 @@ + array( + 'title' => __('Version information'), + ), + 'bdb_cache_size' => array( + 'type' => PMA_ENGINE_DETAILS_TYPE_SIZE, + ), + 'bdb_home' => array(), + 'bdb_log_buffer_size' => array( + 'type' => PMA_ENGINE_DETAILS_TYPE_SIZE, + ), + 'bdb_logdir' => array(), + 'bdb_max_lock' => array( + 'type' => PMA_ENGINE_DETAILS_TYPE_NUMERIC, + ), + 'bdb_shared_data' => array(), + 'bdb_tmpdir' => array(), + 'bdb_data_direct' => array(), + 'bdb_lock_detect' => array(), + 'bdb_log_direct' => array(), + 'bdb_no_recover' => array(), + 'bdb_no_sync' => array(), + 'skip_sync_bdb_logs' => array(), + 'sync_bdb_logs' => array(), + ); + } + + /** + * Returns the pattern to be used in the query for SQL variables + * related to this storage engine + * + * @return string LIKE pattern + */ + public function getVariablesLikePattern() + { + return '%bdb%'; + } + + /** + * returns string with filename for the MySQL helppage + * about this storage engine + * + * @return string mysql helppage filename + */ + public function getMysqlHelpPage() + { + return 'bdb'; + } +} + diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Engines/Berkeleydb.php b/php/apps/phpmyadmin49/html/libraries/classes/Engines/Berkeleydb.php new file mode 100644 index 00000000..ad4f4605 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Engines/Berkeleydb.php @@ -0,0 +1,18 @@ + array( + 'title' => __('Data home directory'), + 'desc' => __( + 'The common part of the directory path for all InnoDB data ' + . 'files.' + ), + ), + 'innodb_data_file_path' => array( + 'title' => __('Data files'), + ), + 'innodb_autoextend_increment' => array( + 'title' => __('Autoextend increment'), + 'desc' => __( + 'The increment size for extending the size of an autoextending ' + . 'tablespace when it becomes full.' + ), + 'type' => PMA_ENGINE_DETAILS_TYPE_NUMERIC, + ), + 'innodb_buffer_pool_size' => array( + 'title' => __('Buffer pool size'), + 'desc' => __( + 'The size of the memory buffer InnoDB uses to cache data and ' + . 'indexes of its tables.' + ), + 'type' => PMA_ENGINE_DETAILS_TYPE_SIZE, + ), + 'innodb_additional_mem_pool_size' => array( + 'title' => 'innodb_additional_mem_pool_size', + 'type' => PMA_ENGINE_DETAILS_TYPE_SIZE, + ), + 'innodb_buffer_pool_awe_mem_mb' => array( + 'type' => PMA_ENGINE_DETAILS_TYPE_SIZE, + ), + 'innodb_checksums' => array(), + 'innodb_commit_concurrency' => array(), + 'innodb_concurrency_tickets' => array( + 'type' => PMA_ENGINE_DETAILS_TYPE_NUMERIC, + ), + 'innodb_doublewrite' => array(), + 'innodb_fast_shutdown' => array(), + 'innodb_file_io_threads' => array( + 'type' => PMA_ENGINE_DETAILS_TYPE_NUMERIC, + ), + 'innodb_file_per_table' => array(), + 'innodb_flush_log_at_trx_commit' => array(), + 'innodb_flush_method' => array(), + 'innodb_force_recovery' => array(), + 'innodb_lock_wait_timeout' => array( + 'type' => PMA_ENGINE_DETAILS_TYPE_NUMERIC, + ), + 'innodb_locks_unsafe_for_binlog' => array(), + 'innodb_log_arch_dir' => array(), + 'innodb_log_archive' => array(), + 'innodb_log_buffer_size' => array( + 'type' => PMA_ENGINE_DETAILS_TYPE_SIZE, + ), + 'innodb_log_file_size' => array( + 'type' => PMA_ENGINE_DETAILS_TYPE_SIZE, + ), + 'innodb_log_files_in_group' => array( + 'type' => PMA_ENGINE_DETAILS_TYPE_NUMERIC, + ), + 'innodb_log_group_home_dir' => array(), + 'innodb_max_dirty_pages_pct' => array( + 'type' => PMA_ENGINE_DETAILS_TYPE_NUMERIC, + ), + 'innodb_max_purge_lag' => array(), + 'innodb_mirrored_log_groups' => array( + 'type' => PMA_ENGINE_DETAILS_TYPE_NUMERIC, + ), + 'innodb_open_files' => array( + 'type' => PMA_ENGINE_DETAILS_TYPE_NUMERIC, + ), + 'innodb_support_xa' => array(), + 'innodb_sync_spin_loops' => array( + 'type' => PMA_ENGINE_DETAILS_TYPE_NUMERIC, + ), + 'innodb_table_locks' => array( + 'type' => PMA_ENGINE_DETAILS_TYPE_BOOLEAN, + ), + 'innodb_thread_concurrency' => array( + 'type' => PMA_ENGINE_DETAILS_TYPE_NUMERIC, + ), + 'innodb_thread_sleep_delay' => array( + 'type' => PMA_ENGINE_DETAILS_TYPE_NUMERIC, + ), + ); + } + + /** + * Returns the pattern to be used in the query for SQL variables + * related to InnoDb storage engine + * + * @return string SQL query LIKE pattern + */ + public function getVariablesLikePattern() + { + return 'innodb\\_%'; + } + + /** + * Get information pages + * + * @return array detail pages + */ + public function getInfoPages() + { + if ($this->support < PMA_ENGINE_SUPPORT_YES) { + return array(); + } + $pages = array(); + $pages['Bufferpool'] = __('Buffer Pool'); + $pages['Status'] = __('InnoDB Status'); + + return $pages; + } + + /** + * returns html tables with stats over inno db buffer pool + * + * @return string html table with stats + */ + public function getPageBufferpool() + { + // The following query is only possible because we know + // that we are on MySQL 5 here (checked above)! + // side note: I love MySQL 5 for this. :-) + $sql + = ' + SHOW STATUS + WHERE Variable_name LIKE \'Innodb\\_buffer\\_pool\\_%\' + OR Variable_name = \'Innodb_page_size\';'; + $status = $GLOBALS['dbi']->fetchResult($sql, 0, 1); + + $output = '' . "\n" + . ' ' . "\n" + . ' ' . "\n" + . ' ' . "\n" + . ' ' . "\n" + . ' ' . "\n" + . ' ' . "\n" + . ' ' . "\n" + . ' ' . "\n" + . ' ' . "\n" + . ' ' . "\n" + . ' ' . "\n" + . ' ' . "\n" + . ' ' . "\n" + . ' ' . "\n" + . ' ' . "\n" + . ' ' . "\n" + . ' ' . "\n" + . ' ' . "\n" + . ' ' . "\n" + . ' ' . "\n" + . ' ' . "\n" + . ' ' . "\n" + . ' ' . "\n" + . ' ' . "\n" + . ' ' . "\n" + . ' ' . "\n" + . ' '; + + // not present at least since MySQL 5.1.40 + if (isset($status['Innodb_buffer_pool_pages_latched'])) { + $output .= ' ' + . ' ' + . ' ' + . ' '; + } + + $output .= ' ' . "\n" + . '
      ' . "\n" + . ' ' . __('Buffer Pool Usage') . "\n" + . '
      ' . "\n" + . ' ' . __('Total') . "\n" + . ' : ' + . Util::formatNumber( + $status['Innodb_buffer_pool_pages_total'], + 0 + ) + . ' ' . __('pages') + . ' / ' + . join( + ' ', + Util::formatByteDown( + $status['Innodb_buffer_pool_pages_total'] + * $status['Innodb_page_size'] + ) + ) . "\n" + . '
      ' . __('Free pages') . '' + . Util::formatNumber( + $status['Innodb_buffer_pool_pages_free'], + 0 + ) + . '
      ' . __('Dirty pages') . '' + . Util::formatNumber( + $status['Innodb_buffer_pool_pages_dirty'], + 0 + ) + . '
      ' . __('Pages containing data') . '' + . Util::formatNumber( + $status['Innodb_buffer_pool_pages_data'], + 0 + ) . "\n" + . '
      ' . __('Pages to be flushed') . '' + . Util::formatNumber( + $status['Innodb_buffer_pool_pages_flushed'], + 0 + ) . "\n" + . '
      ' . __('Busy pages') . '' + . Util::formatNumber( + $status['Innodb_buffer_pool_pages_misc'], + 0 + ) . "\n" + . '
      ' . __('Latched pages') . '' + . Util::formatNumber( + $status['Innodb_buffer_pool_pages_latched'], + 0 + ) + . '
      ' . "\n\n" + . '' . "\n" + . ' ' . "\n" + . ' ' . "\n" + . ' ' . "\n" + . ' ' . "\n" + . ' ' . "\n" + . ' ' . "\n" + . ' ' . "\n" + . ' ' . "\n" + . ' ' . "\n" + . ' ' . "\n" + . ' ' . "\n" + . ' ' . "\n" + . ' ' . "\n" + . ' ' . "\n" + . ' ' . "\n" + . ' ' . "\n" + . ' ' . "\n" + . ' ' . "\n" + . ' ' . "\n" + . ' ' . "\n" + . ' ' . "\n" + . ' ' . "\n" + . ' ' . "\n" + . ' ' . "\n" + . ' ' . "\n" + . ' ' . "\n" + . ' ' . "\n" + . '
      ' . "\n" + . ' ' . __('Buffer Pool Activity') . "\n" + . '
      ' . __('Read requests') . '' + . Util::formatNumber( + $status['Innodb_buffer_pool_read_requests'], + 0 + ) . "\n" + . '
      ' . __('Write requests') . '' + . Util::formatNumber( + $status['Innodb_buffer_pool_write_requests'], + 0 + ) . "\n" + . '
      ' . __('Read misses') . '' + . Util::formatNumber( + $status['Innodb_buffer_pool_reads'], + 0 + ) . "\n" + . '
      ' . __('Write waits') . '' + . Util::formatNumber( + $status['Innodb_buffer_pool_wait_free'], + 0 + ) . "\n" + . '
      ' . __('Read misses in %') . '' + . ($status['Innodb_buffer_pool_read_requests'] == 0 + ? '---' + : htmlspecialchars( + Util::formatNumber( + $status['Innodb_buffer_pool_reads'] * 100 + / $status['Innodb_buffer_pool_read_requests'], + 3, + 2 + ) + ) . ' %') . "\n" + . '
      ' . __('Write waits in %') . '' + . ($status['Innodb_buffer_pool_write_requests'] == 0 + ? '---' + : htmlspecialchars( + Util::formatNumber( + $status['Innodb_buffer_pool_wait_free'] * 100 + / $status['Innodb_buffer_pool_write_requests'], + 3, + 2 + ) + ) . ' %') . "\n" + . '
      ' . "\n"; + + return $output; + } + + /** + * returns InnoDB status + * + * @return string result of SHOW ENGINE INNODB STATUS inside pre tags + */ + public function getPageStatus() + { + return '
      ' . "\n"
      +        . htmlspecialchars(
      +            $GLOBALS['dbi']->fetchValue('SHOW ENGINE INNODB STATUS;', 0, 'Status')
      +        ) . "\n"
      +        . '
      ' . "\n"; + } + + /** + * returns string with filename for the MySQL helppage + * about this storage engine + * + * @return string mysql helppage filename + */ + public function getMysqlHelpPage() + { + return 'innodb-storage-engine'; + } + + /** + * Gets the InnoDB plugin version number + * + * @return string the version number, or empty if not running as a plugin + */ + public function getInnodbPluginVersion() + { + return $GLOBALS['dbi']->fetchValue('SELECT @@innodb_version;'); + } + + /** + * Gets the InnoDB file format + * + * (do not confuse this with phpMyAdmin's storage engine plugins!) + * + * @return string the InnoDB file format + */ + public function getInnodbFileFormat() + { + return $GLOBALS['dbi']->fetchValue( + "SHOW GLOBAL VARIABLES LIKE 'innodb_file_format';", + 0, + 1 + ); + } + + /** + * Verifies if this server supports the innodb_file_per_table feature + * + * (do not confuse this with phpMyAdmin's storage engine plugins!) + * + * @return boolean whether this feature is supported or not + */ + public function supportsFilePerTable() + { + return ( + $GLOBALS['dbi']->fetchValue( + "SHOW GLOBAL VARIABLES LIKE 'innodb_file_per_table';", + 0, + 1 + ) == 'ON' + ); + } +} + diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Engines/Memory.php b/php/apps/phpmyadmin49/html/libraries/classes/Engines/Memory.php new file mode 100644 index 00000000..b2a8b9fc --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Engines/Memory.php @@ -0,0 +1,33 @@ + array( + 'type' => PMA_ENGINE_DETAILS_TYPE_SIZE, + ), + ); + } +} + diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Engines/Merge.php b/php/apps/phpmyadmin49/html/libraries/classes/Engines/Merge.php new file mode 100644 index 00000000..e610c8b6 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Engines/Merge.php @@ -0,0 +1,20 @@ + array( + 'title' => __('Data pointer size'), + 'desc' => __( + 'The default pointer size in bytes, to be used by CREATE TABLE ' + . 'for MyISAM tables when no MAX_ROWS option is specified.' + ), + 'type' => PMA_ENGINE_DETAILS_TYPE_SIZE, + ), + 'myisam_recover_options' => array( + 'title' => __('Automatic recovery mode'), + 'desc' => __( + 'The mode for automatic recovery of crashed MyISAM tables, as ' + . 'set via the --myisam-recover server startup option.' + ), + ), + 'myisam_max_sort_file_size' => array( + 'title' => __('Maximum size for temporary sort files'), + 'desc' => __( + 'The maximum size of the temporary file MySQL is allowed to use ' + . 'while re-creating a MyISAM index (during REPAIR TABLE, ALTER ' + . 'TABLE, or LOAD DATA INFILE).' + ), + 'type' => PMA_ENGINE_DETAILS_TYPE_SIZE, + ), + 'myisam_max_extra_sort_file_size' => array( + 'title' => __('Maximum size for temporary files on index creation'), + 'desc' => __( + 'If the temporary file used for fast MyISAM index creation ' + . 'would be larger than using the key cache by the amount ' + . 'specified here, prefer the key cache method.' + ), + 'type' => PMA_ENGINE_DETAILS_TYPE_SIZE, + ), + 'myisam_repair_threads' => array( + 'title' => __('Repair threads'), + 'desc' => __( + 'If this value is greater than 1, MyISAM table indexes are ' + . 'created in parallel (each index in its own thread) during ' + . 'the repair by sorting process.' + ), + 'type' => PMA_ENGINE_DETAILS_TYPE_NUMERIC, + ), + 'myisam_sort_buffer_size' => array( + 'title' => __('Sort buffer size'), + 'desc' => __( + 'The buffer that is allocated when sorting MyISAM indexes ' + . 'during a REPAIR TABLE or when creating indexes with CREATE ' + . 'INDEX or ALTER TABLE.' + ), + 'type' => PMA_ENGINE_DETAILS_TYPE_SIZE, + ), + 'myisam_stats_method' => array(), + 'delay_key_write' => array(), + 'bulk_insert_buffer_size' => array( + 'type' => PMA_ENGINE_DETAILS_TYPE_SIZE, + ), + 'skip_external_locking' => array(), + ); + } +} + diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Engines/Ndbcluster.php b/php/apps/phpmyadmin49/html/libraries/classes/Engines/Ndbcluster.php new file mode 100644 index 00000000..49e87ae2 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Engines/Ndbcluster.php @@ -0,0 +1,53 @@ + array(), + ); + } + + /** + * Returns the pattern to be used in the query for SQL variables + * related to NDBCLUSTER storage engine + * + * @return string SQL query LIKE pattern + */ + public function getVariablesLikePattern() + { + return 'ndb\\_%'; + } + + /** + * Returns string with filename for the MySQL help page + * about this storage engine + * + * @return string mysql helppage filename + */ + public function getMysqlHelpPage() + { + return 'ndbcluster'; + } +} + diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Engines/Pbxt.php b/php/apps/phpmyadmin49/html/libraries/classes/Engines/Pbxt.php new file mode 100644 index 00000000..9be40e7d --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Engines/Pbxt.php @@ -0,0 +1,193 @@ + array( + 'title' => __('Index cache size'), + 'desc' => __( + 'This is the amount of memory allocated to the' + . ' index cache. Default value is 32MB. The memory' + . ' allocated here is used only for caching index pages.' + ), + 'type' => PMA_ENGINE_DETAILS_TYPE_SIZE, + ), + 'pbxt_record_cache_size' => array( + 'title' => __('Record cache size'), + 'desc' => __( + 'This is the amount of memory allocated to the' + . ' record cache used to cache table data. The default' + . ' value is 32MB. This memory is used to cache changes to' + . ' the handle data (.xtd) and row pointer (.xtr) files.' + ), + 'type' => PMA_ENGINE_DETAILS_TYPE_SIZE, + ), + 'pbxt_log_cache_size' => array( + 'title' => __('Log cache size'), + 'desc' => __( + 'The amount of memory allocated to the' + . ' transaction log cache used to cache on transaction log' + . ' data. The default is 16MB.' + ), + 'type' => PMA_ENGINE_DETAILS_TYPE_SIZE, + ), + 'pbxt_log_file_threshold' => array( + 'title' => __('Log file threshold'), + 'desc' => __( + 'The size of a transaction log before rollover,' + . ' and a new log is created. The default value is 16MB.' + ), + 'type' => PMA_ENGINE_DETAILS_TYPE_SIZE, + ), + 'pbxt_transaction_buffer_size' => array( + 'title' => __('Transaction buffer size'), + 'desc' => __( + 'The size of the global transaction log buffer' + . ' (the engine allocates 2 buffers of this size).' + . ' The default is 1MB.' + ), + 'type' => PMA_ENGINE_DETAILS_TYPE_SIZE, + ), + 'pbxt_checkpoint_frequency' => array( + 'title' => __('Checkpoint frequency'), + 'desc' => __( + 'The amount of data written to the transaction' + . ' log before a checkpoint is performed.' + . ' The default value is 24MB.' + ), + 'type' => PMA_ENGINE_DETAILS_TYPE_SIZE, + ), + 'pbxt_data_log_threshold' => array( + 'title' => __('Data log threshold'), + 'desc' => __( + 'The maximum size of a data log file. The default' + . ' value is 64MB. PBXT can create a maximum of 32000 data' + . ' logs, which are used by all tables. So the value of' + . ' this variable can be increased to increase the total' + . ' amount of data that can be stored in the database.' + ), + 'type' => PMA_ENGINE_DETAILS_TYPE_SIZE, + ), + 'pbxt_garbage_threshold' => array( + 'title' => __('Garbage threshold'), + 'desc' => __( + 'The percentage of garbage in a data log file' + . ' before it is compacted. This is a value between 1 and' + . ' 99. The default is 50.' + ), + 'type' => PMA_ENGINE_DETAILS_TYPE_NUMERIC, + ), + 'pbxt_log_buffer_size' => array( + 'title' => __('Log buffer size'), + 'desc' => __( + 'The size of the buffer used when writing a data' + . ' log. The default is 256MB. The engine allocates one' + . ' buffer per thread, but only if the thread is required' + . ' to write a data log.' + ), + 'type' => PMA_ENGINE_DETAILS_TYPE_SIZE, + ), + 'pbxt_data_file_grow_size' => array( + 'title' => __('Data file grow size'), + 'desc' => __('The grow size of the handle data (.xtd) files.'), + 'type' => PMA_ENGINE_DETAILS_TYPE_SIZE, + ), + 'pbxt_row_file_grow_size' => array( + 'title' => __('Row file grow size'), + 'desc' => __('The grow size of the row pointer (.xtr) files.'), + 'type' => PMA_ENGINE_DETAILS_TYPE_SIZE, + ), + 'pbxt_log_file_count' => array( + 'title' => __('Log file count'), + 'desc' => __( + 'This is the number of transaction log files' + . ' (pbxt/system/xlog*.xt) the system will maintain. If the' + . ' number of logs exceeds this value then old logs will be' + . ' deleted, otherwise they are renamed and given the next' + . ' highest number.' + ), + 'type' => PMA_ENGINE_DETAILS_TYPE_NUMERIC, + ), + ); + } + + /** + * returns the pbxt engine specific handling for + * PMA_ENGINE_DETAILS_TYPE_SIZE variables. + * + * @param string $formatted_size the size expression (for example 8MB) + * + * @return string the formatted value and its unit + */ + public function resolveTypeSize($formatted_size) + { + if (preg_match('/^[0-9]+[a-zA-Z]+$/', $formatted_size)) { + $value = Util::extractValueFromFormattedSize( + $formatted_size + ); + } else { + $value = $formatted_size; + } + + return Util::formatByteDown($value); + } + + //-------------------- + /** + * Get information about pages + * + * @return array Information about pages + */ + public function getInfoPages() + { + $pages = array(); + $pages['Documentation'] = __('Documentation'); + + return $pages; + } + + //-------------------- + /** + * Get content of documentation page + * + * @return string + */ + public function getPageDocumentation() + { + $output = '

      ' . sprintf( + __( + 'Documentation and further information about PBXT' + . ' can be found on the %sPrimeBase XT Home Page%s.' + ), + '', + '' + ) + . '

      ' . "\n"; + + return $output; + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Engines/PerformanceSchema.php b/php/apps/phpmyadmin49/html/libraries/classes/Engines/PerformanceSchema.php new file mode 100644 index 00000000..9c627048 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Engines/PerformanceSchema.php @@ -0,0 +1,29 @@ + 'Internal error', + E_ERROR => 'Error', + E_WARNING => 'Warning', + E_PARSE => 'Parsing Error', + E_NOTICE => 'Notice', + E_CORE_ERROR => 'Core Error', + E_CORE_WARNING => 'Core Warning', + E_COMPILE_ERROR => 'Compile Error', + E_COMPILE_WARNING => 'Compile Warning', + E_USER_ERROR => 'User Error', + E_USER_WARNING => 'User Warning', + E_USER_NOTICE => 'User Notice', + E_STRICT => 'Runtime Notice', + E_DEPRECATED => 'Deprecation Notice', + E_RECOVERABLE_ERROR => 'Catchable Fatal Error', + ); + + /** + * Error levels + * + * @var array + */ + static public $errorlevel = array ( + 0 => 'error', + E_ERROR => 'error', + E_WARNING => 'error', + E_PARSE => 'error', + E_NOTICE => 'notice', + E_CORE_ERROR => 'error', + E_CORE_WARNING => 'error', + E_COMPILE_ERROR => 'error', + E_COMPILE_WARNING => 'error', + E_USER_ERROR => 'error', + E_USER_WARNING => 'error', + E_USER_NOTICE => 'notice', + E_STRICT => 'notice', + E_DEPRECATED => 'notice', + E_RECOVERABLE_ERROR => 'error', + ); + + /** + * The file in which the error occurred + * + * @var string + */ + protected $file = ''; + + /** + * The line in which the error occurred + * + * @var integer + */ + protected $line = 0; + + /** + * Holds the backtrace for this error + * + * @var array + */ + protected $backtrace = array(); + + /** + * Hide location of errors + */ + protected $hide_location = false; + + /** + * Constructor + * + * @param integer $errno error number + * @param string $errstr error message + * @param string $errfile file + * @param integer $errline line + */ + public function __construct($errno, $errstr, $errfile, $errline) + { + $this->setNumber($errno); + $this->setMessage($errstr, false); + $this->setFile($errfile); + $this->setLine($errline); + + // This function can be disabled in php.ini + if (function_exists('debug_backtrace')) { + $backtrace = @debug_backtrace(); + // remove last three calls: + // debug_backtrace(), handleError() and addError() + $backtrace = array_slice($backtrace, 3); + } else { + $backtrace = array(); + } + + $this->setBacktrace($backtrace); + } + + /** + * Process backtrace to avoid path disclossures, objects and so on + * + * @param array $backtrace backtrace + * + * @return array + */ + public static function processBacktrace(array $backtrace) + { + $result = array(); + + $members = array('line', 'function', 'class', 'type'); + + foreach ($backtrace as $idx => $step) { + /* Create new backtrace entry */ + $result[$idx] = array(); + + /* Make path relative */ + if (isset($step['file'])) { + $result[$idx]['file'] = self::relPath($step['file']); + } + + /* Store members we want */ + foreach ($members as $name) { + if (isset($step[$name])) { + $result[$idx][$name] = $step[$name]; + } + } + + /* Store simplified args */ + if (isset($step['args'])) { + foreach ($step['args'] as $key => $arg) { + $result[$idx]['args'][$key] = self::getArg($arg, $step['function']); + } + } + } + + return $result; + } + + /** + * Toggles location hiding + * + * @param boolean $hide Whether to hide + * + * @return void + */ + public function setHideLocation($hide) + { + $this->hide_location = $hide; + } + + /** + * sets PhpMyAdmin\Error::$_backtrace + * + * We don't store full arguments to avoid wakeup or memory problems. + * + * @param array $backtrace backtrace + * + * @return void + */ + public function setBacktrace(array $backtrace) + { + $this->backtrace = self::processBacktrace($backtrace); + } + + /** + * sets PhpMyAdmin\Error::$_line + * + * @param integer $line the line + * + * @return void + */ + public function setLine($line) + { + $this->line = $line; + } + + /** + * sets PhpMyAdmin\Error::$_file + * + * @param string $file the file + * + * @return void + */ + public function setFile($file) + { + $this->file = self::relPath($file); + } + + + /** + * returns unique PhpMyAdmin\Error::$hash, if not exists it will be created + * + * @return string PhpMyAdmin\Error::$hash + */ + public function getHash() + { + try { + $backtrace = serialize($this->getBacktrace()); + } catch(Exception $e) { + $backtrace = ''; + } + if ($this->hash === null) { + $this->hash = md5( + $this->getNumber() . + $this->getMessage() . + $this->getFile() . + $this->getLine() . + $backtrace + ); + } + + return $this->hash; + } + + /** + * returns PhpMyAdmin\Error::$_backtrace for first $count frames + * pass $count = -1 to get full backtrace. + * The same can be done by not passing $count at all. + * + * @param integer $count Number of stack frames. + * + * @return array PhpMyAdmin\Error::$_backtrace + */ + public function getBacktrace($count = -1) + { + if ($count != -1) { + return array_slice($this->backtrace, 0, $count); + } + return $this->backtrace; + } + + /** + * returns PhpMyAdmin\Error::$file + * + * @return string PhpMyAdmin\Error::$file + */ + public function getFile() + { + return $this->file; + } + + /** + * returns PhpMyAdmin\Error::$line + * + * @return integer PhpMyAdmin\Error::$line + */ + public function getLine() + { + return $this->line; + } + + /** + * returns type of error + * + * @return string type of error + */ + public function getType() + { + return self::$errortype[$this->getNumber()]; + } + + /** + * returns level of error + * + * @return string level of error + */ + public function getLevel() + { + return self::$errorlevel[$this->getNumber()]; + } + + /** + * returns title prepared for HTML Title-Tag + * + * @return string HTML escaped and truncated title + */ + public function getHtmlTitle() + { + return htmlspecialchars( + mb_substr($this->getTitle(), 0, 100) + ); + } + + /** + * returns title for error + * + * @return string + */ + public function getTitle() + { + return $this->getType() . ': ' . $this->getMessage(); + } + + /** + * Get HTML backtrace + * + * @return string + */ + public function getBacktraceDisplay() + { + return self::formatBacktrace( + $this->getBacktrace(), + "
      \n", + "
      \n" + ); + } + + /** + * return formatted backtrace field + * + * @param array $backtrace Backtrace data + * @param string $separator Arguments separator to use + * @param string $lines Lines separator to use + * + * @return string formatted backtrace + */ + public static function formatBacktrace(array $backtrace, $separator, $lines) + { + $retval = ''; + + foreach ($backtrace as $step) { + if (isset($step['file']) && isset($step['line'])) { + $retval .= self::relPath($step['file']) + . '#' . $step['line'] . ': '; + } + if (isset($step['class'])) { + $retval .= $step['class'] . $step['type']; + } + $retval .= self::getFunctionCall($step, $separator); + $retval .= $lines; + } + + return $retval; + } + + /** + * Formats function call in a backtrace + * + * @param array $step backtrace step + * @param string $separator Arguments separator to use + * + * @return string + */ + public static function getFunctionCall(array $step, $separator) + { + $retval = $step['function'] . '('; + if (isset($step['args'])) { + if (count($step['args']) > 1) { + $retval .= $separator; + foreach ($step['args'] as $arg) { + $retval .= "\t"; + $retval .= $arg; + $retval .= ',' . $separator; + } + } elseif (count($step['args']) > 0) { + foreach ($step['args'] as $arg) { + $retval .= $arg; + } + } + } + $retval .= ')'; + return $retval; + } + + /** + * Get a single function argument + * + * if $function is one of include/require + * the $arg is converted to a relative path + * + * @param string $arg argument to process + * @param string $function function name + * + * @return string + */ + public static function getArg($arg, $function) + { + $retval = ''; + $include_functions = array( + 'include', + 'include_once', + 'require', + 'require_once', + ); + $connect_functions = array( + 'mysql_connect', + 'mysql_pconnect', + 'mysqli_connect', + 'mysqli_real_connect', + 'connect', + '_realConnect' + ); + + if (in_array($function, $include_functions)) { + $retval .= self::relPath($arg); + } elseif (in_array($function, $connect_functions) + && getType($arg) === 'string' + ) { + $retval .= getType($arg) . ' ********'; + } elseif (is_scalar($arg)) { + $retval .= getType($arg) . ' ' + . htmlspecialchars(var_export($arg, true)); + } elseif (is_object($arg)) { + $retval .= ''; + } else { + $retval .= getType($arg); + } + + return $retval; + } + + /** + * Gets the error as string of HTML + * + * @return string + */ + public function getDisplay() + { + $this->isDisplayed(true); + $retval = '
      '; + if (! $this->isUserError()) { + $retval .= '' . $this->getType() . ''; + $retval .= ' in ' . $this->getFile() . '#' . $this->getLine(); + $retval .= "
      \n"; + } + $retval .= $this->getMessage(); + if (! $this->isUserError()) { + $retval .= "
      \n"; + $retval .= "
      \n"; + $retval .= "Backtrace
      \n"; + $retval .= "
      \n"; + $retval .= $this->getBacktraceDisplay(); + } + $retval .= '
      '; + + return $retval; + } + + /** + * whether this error is a user error + * + * @return boolean + */ + public function isUserError() + { + return $this->hide_location || + ($this->getNumber() & (E_USER_WARNING | E_USER_ERROR | E_USER_NOTICE)); + } + + /** + * return short relative path to phpMyAdmin basedir + * + * prevent path disclosure in error message, + * and make users feel safe to submit error reports + * + * @param string $path path to be shorten + * + * @return string shortened path + */ + public static function relPath($path) + { + $dest = @realpath($path); + + /* Probably affected by open_basedir */ + if ($dest === false) { + return basename($path); + } + + $Ahere = explode( + DIRECTORY_SEPARATOR, + realpath(__DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . '..') + ); + $Adest = explode(DIRECTORY_SEPARATOR, $dest); + + $result = '.'; + // && count ($Adest)>0 && count($Ahere)>0 ) + while (implode(DIRECTORY_SEPARATOR, $Adest) != implode(DIRECTORY_SEPARATOR, $Ahere)) { + if (count($Ahere) > count($Adest)) { + array_pop($Ahere); + $result .= DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . '..'; + } else { + array_pop($Adest); + } + } + $path = $result . str_replace(implode(DIRECTORY_SEPARATOR, $Adest), '', $dest); + return str_replace( + DIRECTORY_SEPARATOR . PATH_SEPARATOR, + DIRECTORY_SEPARATOR, + $path + ); + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/ErrorHandler.php b/php/apps/phpmyadmin49/html/libraries/classes/ErrorHandler.php new file mode 100644 index 00000000..f99c3d27 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/ErrorHandler.php @@ -0,0 +1,585 @@ +error_reporting = error_reporting(); + } + + /** + * Destructor + * + * stores errors in session + * + */ + public function __destruct() + { + if (isset($_SESSION)) { + if (! isset($_SESSION['errors'])) { + $_SESSION['errors'] = array(); + } + + // remember only not displayed errors + foreach ($this->errors as $key => $error) { + /** + * We don't want to store all errors here as it would + * explode user session. + */ + if (count($_SESSION['errors']) >= 10) { + $error = new Error( + 0, + __('Too many error messages, some are not displayed.'), + __FILE__, + __LINE__ + ); + $_SESSION['errors'][$error->getHash()] = $error; + break; + } elseif (($error instanceof Error) + && ! $error->isDisplayed() + ) { + $_SESSION['errors'][$key] = $error; + } + } + } + } + + /** + * Toggles location hiding + * + * @param boolean $hide Whether to hide + * + * @return void + */ + public function setHideLocation($hide) + { + $this->hide_location = $hide; + } + + /** + * returns array with all errors + * + * @param bool $check Whether to check for session errors + * + * @return Error[] + */ + public function getErrors($check=true) + { + if ($check) { + $this->checkSavedErrors(); + } + return $this->errors; + } + + /** + * returns the errors occurred in the current run only. + * Does not include the errors saved in the SESSION + * + * @return Error[] + */ + public function getCurrentErrors() + { + return $this->errors; + } + + /** + * Pops recent errors from the storage + * + * @param int $count Old error count + * + * @return Error[] + */ + public function sliceErrors($count) + { + $errors = $this->getErrors(false); + $this->errors = array_splice($errors, 0, $count); + return array_splice($errors, $count); + } + + /** + * Error handler - called when errors are triggered/occurred + * + * This calls the addError() function, escaping the error string + * Ignores the errors wherever Error Control Operator (@) is used. + * + * @param integer $errno error number + * @param string $errstr error string + * @param string $errfile error file + * @param integer $errline error line + * + * @return void + */ + public function handleError($errno, $errstr, $errfile, $errline) + { + /** + * Check if Error Control Operator (@) was used, but still show + * user errors even in this case. + */ + if (error_reporting() == 0 && + $this->error_reporting != 0 && + ($errno & (E_USER_WARNING | E_USER_ERROR | E_USER_NOTICE)) == 0 + ) { + return; + } + $this->addError($errstr, $errno, $errfile, $errline, true); + } + + /** + * Add an error; can also be called directly (with or without escaping) + * + * The following error types cannot be handled with a user defined function: + * E_ERROR, E_PARSE, E_CORE_ERROR, E_CORE_WARNING, E_COMPILE_ERROR, + * E_COMPILE_WARNING, + * and most of E_STRICT raised in the file where set_error_handler() is called. + * + * Do not use the context parameter as we want to avoid storing the + * complete $GLOBALS inside $_SESSION['errors'] + * + * @param string $errstr error string + * @param integer $errno error number + * @param string $errfile error file + * @param integer $errline error line + * @param boolean $escape whether to escape the error string + * + * @return void + */ + public function addError($errstr, $errno, $errfile, $errline, $escape = true) + { + if ($escape) { + $errstr = htmlspecialchars($errstr); + } + // create error object + $error = new Error( + $errno, + $errstr, + $errfile, + $errline + ); + $error->setHideLocation($this->hide_location); + + // do not repeat errors + $this->errors[$error->getHash()] = $error; + + switch ($error->getNumber()) { + case E_STRICT: + case E_DEPRECATED: + case E_NOTICE: + case E_WARNING: + case E_CORE_WARNING: + case E_COMPILE_WARNING: + case E_RECOVERABLE_ERROR: + /* Avoid rendering BB code in PHP errors */ + $error->setBBCode(false); + break; + case E_USER_NOTICE: + case E_USER_WARNING: + case E_USER_ERROR: + // just collect the error + // display is called from outside + break; + case E_ERROR: + case E_PARSE: + case E_CORE_ERROR: + case E_COMPILE_ERROR: + default: + // FATAL error, display it and exit + $this->dispFatalError($error); + exit; + } + } + + /** + * trigger a custom error + * + * @param string $errorInfo error message + * @param integer $errorNumber error number + * + * @return void + */ + public function triggerError($errorInfo, $errorNumber = null) + { + // we could also extract file and line from backtrace + // and call handleError() directly + trigger_error($errorInfo, $errorNumber); + } + + /** + * display fatal error and exit + * + * @param Error $error the error + * + * @return void + */ + protected function dispFatalError($error) + { + if (! headers_sent()) { + $this->dispPageStart($error); + } + $error->display(); + $this->dispPageEnd(); + exit; + } + + /** + * Displays user errors not displayed + * + * @return void + */ + public function dispUserErrors() + { + echo $this->getDispUserErrors(); + } + + /** + * Renders user errors not displayed + * + * @return string + */ + public function getDispUserErrors() + { + $retval = ''; + foreach ($this->getErrors() as $error) { + if ($error->isUserError() && ! $error->isDisplayed()) { + $retval .= $error->getDisplay(); + } + } + return $retval; + } + + /** + * display HTML header + * + * @param Error $error the error + * + * @return void + */ + protected function dispPageStart($error = null) + { + Response::getInstance()->disable(); + echo ''; + if ($error) { + echo $error->getTitle(); + } else { + echo 'phpMyAdmin error reporting page'; + } + echo ''; + } + + /** + * display HTML footer + * + * @return void + */ + protected function dispPageEnd() + { + echo ''; + } + + /** + * renders errors not displayed + * + * @return string + */ + public function getDispErrors() + { + $retval = ''; + // display errors if SendErrorReports is set to 'ask'. + if ($GLOBALS['cfg']['SendErrorReports'] != 'never') { + foreach ($this->getErrors() as $error) { + if (! $error->isDisplayed()) { + $retval .= $error->getDisplay(); + } + } + } else { + $retval .= $this->getDispUserErrors(); + } + // if preference is not 'never' and + // there are 'actual' errors to be reported + if ($GLOBALS['cfg']['SendErrorReports'] != 'never' + && $this->countErrors() != $this->countUserErrors() + ) { + // add report button. + $retval .= '
      'php', + 'send_error_report' => '1', + 'server' => $GLOBALS['server'], + )); + $retval .= '' + . '' + . ''; + + if ($GLOBALS['cfg']['SendErrorReports'] == 'ask') { + // add ignore buttons + $retval .= ''; + } + $retval .= ''; + $retval .= '
      '; + } + return $retval; + } + + /** + * displays errors not displayed + * + * @return void + */ + public function dispErrors() + { + echo $this->getDispErrors(); + } + + /** + * look in session for saved errors + * + * @return void + */ + protected function checkSavedErrors() + { + if (isset($_SESSION['errors'])) { + + // restore saved errors + foreach ($_SESSION['errors'] as $hash => $error) { + if ($error instanceof Error && ! isset($this->errors[$hash])) { + $this->errors[$hash] = $error; + } + } + + // delete stored errors + $_SESSION['errors'] = array(); + unset($_SESSION['errors']); + } + } + + /** + * return count of errors + * + * @param bool $check Whether to check for session errors + * + * @return integer number of errors occurred + */ + public function countErrors($check=true) + { + return count($this->getErrors($check)); + } + + /** + * return count of user errors + * + * @return integer number of user errors occurred + */ + public function countUserErrors() + { + $count = 0; + if ($this->countErrors()) { + foreach ($this->getErrors() as $error) { + if ($error->isUserError()) { + $count++; + } + } + } + + return $count; + } + + /** + * whether use errors occurred or not + * + * @return boolean + */ + public function hasUserErrors() + { + return (bool) $this->countUserErrors(); + } + + /** + * whether errors occurred or not + * + * @return boolean + */ + public function hasErrors() + { + return (bool) $this->countErrors(); + } + + /** + * number of errors to be displayed + * + * @return integer number of errors to be displayed + */ + public function countDisplayErrors() + { + if ($GLOBALS['cfg']['SendErrorReports'] != 'never') { + return $this->countErrors(); + } + + return $this->countUserErrors(); + } + + /** + * whether there are errors to display or not + * + * @return boolean + */ + public function hasDisplayErrors() + { + return (bool) $this->countDisplayErrors(); + } + + /** + * Deletes previously stored errors in SESSION. + * Saves current errors in session as previous errors. + * Required to save current errors in case 'ask' + * + * @return void + */ + public function savePreviousErrors() + { + unset($_SESSION['prev_errors']); + $_SESSION['prev_errors'] = $GLOBALS['error_handler']->getCurrentErrors(); + } + + /** + * Function to check if there are any errors to be prompted. + * Needed because user warnings raised are + * also collected by global error handler. + * This distinguishes between the actual errors + * and user errors raised to warn user. + * + *@return boolean true if there are errors to be "prompted", false otherwise + */ + public function hasErrorsForPrompt() + { + return ( + $GLOBALS['cfg']['SendErrorReports'] != 'never' + && $this->countErrors() != $this->countUserErrors() + ); + } + + /** + * Function to report all the collected php errors. + * Must be called at the end of each script + * by the $GLOBALS['error_handler'] only. + * + * @return void + */ + public function reportErrors() + { + // if there're no actual errors, + if (!$this->hasErrors() + || $this->countErrors() == $this->countUserErrors() + ) { + // then simply return. + return; + } + // Delete all the prev_errors in session & store new prev_errors in session + $this->savePreviousErrors(); + $response = Response::getInstance(); + $jsCode = ''; + if ($GLOBALS['cfg']['SendErrorReports'] == 'always') { + if ($response->isAjax()) { + // set flag for automatic report submission. + $response->addJSON('_sendErrorAlways', '1'); + } else { + // send the error reports asynchronously & without asking user + $jsCode .= '$("#pma_report_errors_form").submit();' + . 'PMA_ajaxShowMessage( + PMA_messages["phpErrorsBeingSubmitted"], false + );'; + // js code to appropriate focusing, + $jsCode .= '$("html, body").animate({ + scrollTop:$(document).height() + }, "slow");'; + } + } elseif ($GLOBALS['cfg']['SendErrorReports'] == 'ask') { + //ask user whether to submit errors or not. + if (!$response->isAjax()) { + // js code to show appropriate msgs, event binding & focusing. + $jsCode = 'PMA_ajaxShowMessage(PMA_messages["phpErrorsFound"]);' + . '$("#pma_ignore_errors_popup").bind("click", function() { + PMA_ignorePhpErrors() + });' + . '$("#pma_ignore_all_errors_popup").bind("click", + function() { + PMA_ignorePhpErrors(false) + });' + . '$("#pma_ignore_errors_bottom").bind("click", function(e) { + e.preventDefault(); + PMA_ignorePhpErrors() + });' + . '$("#pma_ignore_all_errors_bottom").bind("click", + function(e) { + e.preventDefault(); + PMA_ignorePhpErrors(false) + });' + . '$("html, body").animate({ + scrollTop:$(document).height() + }, "slow");'; + } + } + // The errors are already sent from the response. + // Just focus on errors division upon load event. + $response->getFooter()->getScripts()->addCode($jsCode); + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/ErrorReport.php b/php/apps/phpmyadmin49/html/libraries/classes/ErrorReport.php new file mode 100644 index 00000000..d11462a6 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/ErrorReport.php @@ -0,0 +1,271 @@ +httpRequest = $httpRequest; + $this->submissionUrl = 'https://reports.phpmyadmin.net/incidents/create'; + $this->relation = new Relation(); + } + + /** + * Returns the pretty printed error report data collected from the + * current configuration or from the request parameters sent by the + * error reporting js code. + * + * @return string the report + */ + private function getPrettyData() + { + $report = $this->getData(); + + return json_encode($report, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES); + } + + /** + * Returns the error report data collected from the current configuration or + * from the request parameters sent by the error reporting js code. + * + * @param string $exceptionType whether exception is 'js' or 'php' + * + * @return array error report if success, Empty Array otherwise + */ + public function getData($exceptionType = 'js') + { + $relParams = $this->relation->getRelationsParam(); + // common params for both, php & js exceptions + $report = [ + "pma_version" => PMA_VERSION, + "browser_name" => PMA_USR_BROWSER_AGENT, + "browser_version" => PMA_USR_BROWSER_VER, + "user_os" => PMA_USR_OS, + "server_software" => $_SERVER['SERVER_SOFTWARE'], + "user_agent_string" => $_SERVER['HTTP_USER_AGENT'], + "locale" => $_COOKIE['pma_lang'], + "configuration_storage" => + is_null($relParams['db']) ? "disabled" : "enabled", + "php_version" => phpversion() + ]; + + if ($exceptionType == 'js') { + if (empty($_POST['exception'])) { + return []; + } + $exception = $_POST['exception']; + $exception["stack"] = $this->translateStacktrace($exception["stack"]); + + if (isset($exception["url"])) { + list($uri, $scriptName) = $this->sanitizeUrl($exception["url"]); + $exception["uri"] = $uri; + $report["script_name"] = $scriptName; + unset($exception["url"]); + } else if (isset($_POST["url"])) { + list($uri, $scriptName) = $this->sanitizeUrl($_POST["url"]); + $exception["uri"] = $uri; + $report["script_name"] = $scriptName; + unset($_POST["url"]); + } else { + $report["script_name"] = null; + } + + $report["exception_type"] = 'js'; + $report["exception"] = $exception; + if (isset($_POST['microhistory'])) { + $report["microhistory"] = $_POST['microhistory']; + } + + if (! empty($_POST['description'])) { + $report['steps'] = $_POST['description']; + } + } elseif ($exceptionType == 'php') { + $errors = []; + // create php error report + $i = 0; + if (!isset($_SESSION['prev_errors']) + || $_SESSION['prev_errors'] == '' + ) { + return []; + } + foreach ($_SESSION['prev_errors'] as $errorObj) { + /* @var $errorObj PhpMyAdmin\Error */ + if ($errorObj->getLine() + && $errorObj->getType() + && $errorObj->getNumber() != E_USER_WARNING + ) { + $errors[$i++] = [ + "lineNum" => $errorObj->getLine(), + "file" => $errorObj->getFile(), + "type" => $errorObj->getType(), + "msg" => $errorObj->getOnlyMessage(), + "stackTrace" => $errorObj->getBacktrace(5), + "stackhash" => $errorObj->getHash() + ]; + } + } + + // if there were no 'actual' errors to be submitted. + if ($i==0) { + return []; // then return empty array + } + $report["exception_type"] = 'php'; + $report["errors"] = $errors; + } else { + return []; + } + + return $report; + } + + /** + * Sanitize a url to remove the identifiable host name and extract the + * current script name from the url fragment + * + * It returns two things in an array. The first is the uri without the + * hostname and identifying query params. The second is the name of the + * php script in the url + * + * @param string $url the url to sanitize + * + * @return array the uri and script name + */ + private function sanitizeUrl($url) + { + $components = parse_url($url); + if (isset($components["fragment"]) + && preg_match("", $components["fragment"], $matches) + ) { + $uri = str_replace($matches[0], "", $components["fragment"]); + $url = "https://example.com/" . $uri; + $components = parse_url($url); + } + + // get script name + preg_match("<([a-zA-Z\-_\d\.]*\.php|js\/[a-zA-Z\-_\d\/\.]*\.js)$>", $components["path"], $matches); + if (count($matches) < 2) { + $scriptName = 'index.php'; + } else { + $scriptName = $matches[1]; + } + + // remove deployment specific details to make uri more generic + if (isset($components["query"])) { + parse_str($components["query"], $queryArray); + unset($queryArray["db"]); + unset($queryArray["table"]); + unset($queryArray["token"]); + unset($queryArray["server"]); + $query = http_build_query($queryArray); + } else { + $query = ''; + } + + $uri = $scriptName . "?" . $query; + return [$uri, $scriptName]; + } + + /** + * Sends report data to the error reporting server + * + * @param array $report the report info to be sent + * + * @return string|null|bool the reply of the server + */ + public function send(array $report) + { + $response = $this->httpRequest->create( + $this->submissionUrl, + "POST", + false, + json_encode($report), + "Content-Type: application/json" + ); + return $response; + } + + /** + * Translates the cumulative line numbers in the stack trace as well as sanitize + * urls and trim long lines in the context + * + * @param array $stack the stack trace + * + * @return array $stack the modified stack trace + */ + private function translateStacktrace(array $stack) + { + foreach ($stack as &$level) { + foreach ($level["context"] as &$line) { + if (mb_strlen($line) > 80) { + $line = mb_substr($line, 0, 75) . "//..."; + } + } + list($uri, $scriptName) = $this->sanitizeUrl($level["url"]); + $level["uri"] = $uri; + $level["scriptname"] = $scriptName; + unset($level["url"]); + } + unset($level); + return $stack; + } + + /** + * Generates the error report form to collect user description and preview the + * report before being sent + * + * @return string the form + */ + public function getForm() + { + $datas = [ + 'report_data' => $this->getPrettyData(), + 'hidden_inputs' => Url::getHiddenInputs(), + 'hidden_fields' => null, + ]; + + $reportData = $this->getData(); + if (!empty($reportData)) { + $datas['hidden_fields'] = Url::getHiddenFields($reportData); + } + + return Template::get('error/report_form')->render($datas); + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Export.php b/php/apps/phpmyadmin49/html/libraries/classes/Export.php new file mode 100644 index 00000000..e4d028b2 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Export.php @@ -0,0 +1,1101 @@ += 43; // see bug #4942 + + if (function_exists('gzencode') + && ((! ini_get('zlib.output_compression') + && ! self::isGzHandlerEnabled()) + || $GLOBALS['save_on_server'] + || $chromeAndGreaterThan43) + ) { + return true; + } + + return false; + } + + /** + * Output handler for all exports, if needed buffering, it stores data into + * $dump_buffer, otherwise it prints them out. + * + * @param string $line the insert statement + * + * @return bool Whether output succeeded + */ + public static function outputHandler($line) + { + global $time_start, $dump_buffer, $dump_buffer_len, $save_filename; + + // Kanji encoding convert feature + if ($GLOBALS['output_kanji_conversion']) { + $line = Encoding::kanjiStrConv( + $line, + $GLOBALS['knjenc'], + isset($GLOBALS['xkana']) ? $GLOBALS['xkana'] : '' + ); + } + + // If we have to buffer data, we will perform everything at once at the end + if ($GLOBALS['buffer_needed']) { + + $dump_buffer .= $line; + if ($GLOBALS['onfly_compression']) { + + $dump_buffer_len += strlen($line); + + if ($dump_buffer_len > $GLOBALS['memory_limit']) { + if ($GLOBALS['output_charset_conversion']) { + $dump_buffer = Encoding::convertString( + 'utf-8', + $GLOBALS['charset'], + $dump_buffer + ); + } + if ($GLOBALS['compression'] == 'gzip' + && self::gzencodeNeeded() + ) { + // as a gzipped file + // without the optional parameter level because it bugs + $dump_buffer = gzencode($dump_buffer); + } + if ($GLOBALS['save_on_server']) { + $write_result = @fwrite($GLOBALS['file_handle'], $dump_buffer); + // Here, use strlen rather than mb_strlen to get the length + // in bytes to compare against the number of bytes written. + if ($write_result != strlen($dump_buffer)) { + $GLOBALS['message'] = Message::error( + __('Insufficient space to save the file %s.') + ); + $GLOBALS['message']->addParam($save_filename); + return false; + } + } else { + echo $dump_buffer; + } + $dump_buffer = ''; + $dump_buffer_len = 0; + } + } else { + $time_now = time(); + if ($time_start >= $time_now + 30) { + $time_start = $time_now; + header('X-pmaPing: Pong'); + } // end if + } + } else { + if ($GLOBALS['asfile']) { + if ($GLOBALS['output_charset_conversion']) { + $line = Encoding::convertString( + 'utf-8', + $GLOBALS['charset'], + $line + ); + } + if ($GLOBALS['save_on_server'] && mb_strlen($line) > 0) { + $write_result = @fwrite($GLOBALS['file_handle'], $line); + // Here, use strlen rather than mb_strlen to get the length + // in bytes to compare against the number of bytes written. + if (! $write_result + || $write_result != strlen($line) + ) { + $GLOBALS['message'] = Message::error( + __('Insufficient space to save the file %s.') + ); + $GLOBALS['message']->addParam($save_filename); + return false; + } + $time_now = time(); + if ($time_start >= $time_now + 30) { + $time_start = $time_now; + header('X-pmaPing: Pong'); + } // end if + } else { + // We export as file - output normally + echo $line; + } + } else { + // We export as html - replace special chars + echo htmlspecialchars($line); + } + } + return true; + } // end of the 'self::outputHandler()' function + + /** + * Returns HTML containing the footer for a displayed export + * + * @param string $back_button the link for going Back + * @param string $refreshButton the link for refreshing page + * + * @return string $html the HTML output + */ + public static function getHtmlForDisplayedExportFooter($back_button, $refreshButton) + { + /** + * Close the html tags and add the footers for on-screen export + */ + $html = '' + . ' ' + . '
      ' + // bottom back button + . $back_button + . $refreshButton + . '
      ' + . '' . "\n"; + return $html; + } + + /** + * Computes the memory limit for export + * + * @return int $memory_limit the memory limit + */ + public static function getMemoryLimit() + { + $memory_limit = trim(ini_get('memory_limit')); + $memory_limit_num = (int)substr($memory_limit, 0, -1); + $lowerLastChar = strtolower(substr($memory_limit, -1)); + // 2 MB as default + if (empty($memory_limit) || '-1' == $memory_limit) { + $memory_limit = 2 * 1024 * 1024; + } elseif ($lowerLastChar == 'm') { + $memory_limit = $memory_limit_num * 1024 * 1024; + } elseif ($lowerLastChar == 'k') { + $memory_limit = $memory_limit_num * 1024; + } elseif ($lowerLastChar == 'g') { + $memory_limit = $memory_limit_num * 1024 * 1024 * 1024; + } else { + $memory_limit = (int)$memory_limit; + } + + // Some of memory is needed for other things and as threshold. + // During export I had allocated (see memory_get_usage function) + // approx 1.2MB so this comes from that. + if ($memory_limit > 1500000) { + $memory_limit -= 1500000; + } + + // Some memory is needed for compression, assume 1/3 + $memory_limit /= 8; + return $memory_limit; + } + + /** + * Return the filename and MIME type for export file + * + * @param string $export_type type of export + * @param string $remember_template whether to remember template + * @param ExportPlugin $export_plugin the export plugin + * @param string $compression compression asked + * @param string $filename_template the filename template + * + * @return string[] the filename template and mime type + */ + public static function getFilenameAndMimetype( + $export_type, $remember_template, $export_plugin, $compression, + $filename_template + ) { + if ($export_type == 'server') { + if (! empty($remember_template)) { + $GLOBALS['PMA_Config']->setUserValue( + 'pma_server_filename_template', + 'Export/file_template_server', + $filename_template + ); + } + } elseif ($export_type == 'database') { + if (! empty($remember_template)) { + $GLOBALS['PMA_Config']->setUserValue( + 'pma_db_filename_template', + 'Export/file_template_database', + $filename_template + ); + } + } else { + if (! empty($remember_template)) { + $GLOBALS['PMA_Config']->setUserValue( + 'pma_table_filename_template', + 'Export/file_template_table', + $filename_template + ); + } + } + $filename = Util::expandUserString($filename_template); + // remove dots in filename (coming from either the template or already + // part of the filename) to avoid a remote code execution vulnerability + $filename = Sanitize::sanitizeFilename($filename, $replaceDots = true); + + // Grab basic dump extension and mime type + // Check if the user already added extension; + // get the substring where the extension would be if it was included + $extension_start_pos = mb_strlen($filename) - mb_strlen( + $export_plugin->getProperties()->getExtension() + ) - 1; + $user_extension = mb_substr( + $filename, $extension_start_pos, mb_strlen($filename) + ); + $required_extension = "." . $export_plugin->getProperties()->getExtension(); + if (mb_strtolower($user_extension) != $required_extension) { + $filename .= $required_extension; + } + $mime_type = $export_plugin->getProperties()->getMimeType(); + + // If dump is going to be compressed, set correct mime_type and add + // compression to extension + if ($compression == 'gzip') { + $filename .= '.gz'; + $mime_type = 'application/x-gzip'; + } elseif ($compression == 'zip') { + $filename .= '.zip'; + $mime_type = 'application/zip'; + } + return array($filename, $mime_type); + } + + /** + * Open the export file + * + * @param string $filename the export filename + * @param boolean $quick_export whether it's a quick export or not + * + * @return array the full save filename, possible message and the file handle + */ + public static function openFile($filename, $quick_export) + { + $file_handle = null; + $message = ''; + $doNotSaveItOver = true; + + if(isset($_POST['quick_export_onserver_overwrite'])) { + $doNotSaveItOver = $_POST['quick_export_onserver_overwrite'] != 'saveitover'; + } + + $save_filename = Util::userDir($GLOBALS['cfg']['SaveDir']) + . preg_replace('@[/\\\\]@', '_', $filename); + + if (@file_exists($save_filename) + && ((! $quick_export && empty($_POST['onserver_overwrite'])) + || ($quick_export + && $doNotSaveItOver)) + ) { + $message = Message::error( + __( + 'File %s already exists on server, ' + . 'change filename or check overwrite option.' + ) + ); + $message->addParam($save_filename); + } elseif (@is_file($save_filename) && ! @is_writable($save_filename)) { + $message = Message::error( + __( + 'The web server does not have permission ' + . 'to save the file %s.' + ) + ); + $message->addParam($save_filename); + } elseif (! $file_handle = @fopen($save_filename, 'w')) { + $message = Message::error( + __( + 'The web server does not have permission ' + . 'to save the file %s.' + ) + ); + $message->addParam($save_filename); + } + return array($save_filename, $message, $file_handle); + } + + /** + * Close the export file + * + * @param resource $file_handle the export file handle + * @param string $dump_buffer the current dump buffer + * @param string $save_filename the export filename + * + * @return Message $message a message object (or empty string) + */ + public static function closeFile($file_handle, $dump_buffer, $save_filename) + { + $write_result = @fwrite($file_handle, $dump_buffer); + fclose($file_handle); + // Here, use strlen rather than mb_strlen to get the length + // in bytes to compare against the number of bytes written. + if (strlen($dump_buffer) > 0 + && (! $write_result || $write_result != strlen($dump_buffer)) + ) { + $message = new Message( + __('Insufficient space to save the file %s.'), + Message::ERROR, + array($save_filename) + ); + } else { + $message = new Message( + __('Dump has been saved to file %s.'), + Message::SUCCESS, + array($save_filename) + ); + } + return $message; + } + + /** + * Compress the export buffer + * + * @param array|string $dump_buffer the current dump buffer + * @param string $compression the compression mode + * @param string $filename the filename + * + * @return object $message a message object (or empty string) + */ + public static function compress($dump_buffer, $compression, $filename) + { + if ($compression == 'zip' && function_exists('gzcompress')) { + $zipExtension = new ZipExtension(); + $filename = substr($filename, 0, -4); // remove extension (.zip) + $dump_buffer = $zipExtension->createFile($dump_buffer, $filename); + } elseif ($compression == 'gzip' && self::gzencodeNeeded()) { + // without the optional parameter level because it bugs + $dump_buffer = gzencode($dump_buffer); + } + return $dump_buffer; + } + + /** + * Saves the dump_buffer for a particular table in an array + * Used in separate files export + * + * @param string $object_name the name of current object to be stored + * @param boolean $append optional boolean to append to an existing index or not + * + * @return void + */ + public static function saveObjectInBuffer($object_name, $append = false) + { + + global $dump_buffer_objects, $dump_buffer, $dump_buffer_len; + + if (! empty($dump_buffer)) { + if ($append && isset($dump_buffer_objects[$object_name])) { + $dump_buffer_objects[$object_name] .= $dump_buffer; + } else { + $dump_buffer_objects[$object_name] = $dump_buffer; + } + } + + // Re - initialize + $dump_buffer = ''; + $dump_buffer_len = 0; + + } + + /** + * Returns HTML containing the header for a displayed export + * + * @param string $export_type the export type + * @param string $db the database name + * @param string $table the table name + * + * @return string[] the generated HTML and back button + */ + public static function getHtmlForDisplayedExportHeader($export_type, $db, $table) + { + $html = '
      '; + + /** + * Displays a back button with all the $_POST data in the URL + * (store in a variable to also display after the textarea) + */ + $back_button = '

      [ ' . __('Back') . ' ]

      '; + $html .= '
      '; + $html .= $back_button; + $refreshButton = '
      '; + $refreshButton .= '[ '. __('Refresh') .' ]'; + foreach($_POST as $name => $value) { + if (is_array($value)) { + foreach($value as $val) { + $refreshButton .= ''; + } + } + else { + $refreshButton .= ''; + } + } + $refreshButton .= '
      '; + $html .= $refreshButton + . '
      ' + . '
      ' + . ''; + + return $html_output; + } + + /** + * Get HTML for enum type + * + * @param array $column description of column in given table + * @param string $backup_field hidden input field + * @param string $column_name_appendix the name attribute + * @param array $extracted_columnspec associative array containing type, + * spec_in_brackets and possibly + * enum_set_values (another array) + * @param string $onChangeClause onchange clause for fields + * @param integer $tabindex tab index + * @param integer $tabindex_for_value offset for the values tabindex + * @param integer $idindex id index + * @param mixed $data data to edit + * @param boolean $readOnly is column read only or not + * + * @return string an html snippet + */ + private function getPmaTypeEnum( + array $column, + $backup_field, + $column_name_appendix, + array $extracted_columnspec, + $onChangeClause, + $tabindex, + $tabindex_for_value, + $idindex, + $data, + $readOnly + ) { + $html_output = ''; + if (! isset($column['values'])) { + $column['values'] = $this->getColumnEnumValues( + $column, + $extracted_columnspec + ); + } + $column_enum_values = $column['values']; + $html_output .= ''; + $html_output .= "\n" . ' ' . $backup_field . "\n"; + if (mb_strlen($column['Type']) > 20) { + $html_output .= $this->getDropDownDependingOnLength( + $column, + $column_name_appendix, + $onChangeClause, + $tabindex, + $tabindex_for_value, + $idindex, + $data, + $column_enum_values, + $readOnly + ); + } else { + $html_output .= $this->getRadioButtonDependingOnLength( + $column_name_appendix, + $onChangeClause, + $tabindex, + $column, + $tabindex_for_value, + $idindex, + $data, + $column_enum_values, + $readOnly + ); + } + return $html_output; + } + + /** + * Get column values + * + * @param array $column description of column in given table + * @param array $extracted_columnspec associative array containing type, + * spec_in_brackets and possibly enum_set_values + * (another array) + * + * @return array column values as an associative array + */ + private function getColumnEnumValues(array $column, array $extracted_columnspec) + { + $column['values'] = array(); + foreach ($extracted_columnspec['enum_set_values'] as $val) { + $column['values'][] = array( + 'plain' => $val, + 'html' => htmlspecialchars($val), + ); + } + return $column['values']; + } + + /** + * Get HTML drop down for more than 20 string length + * + * @param array $column description of column in given table + * @param string $column_name_appendix the name attribute + * @param string $onChangeClause onchange clause for fields + * @param integer $tabindex tab index + * @param integer $tabindex_for_value offset for the values tabindex + * @param integer $idindex id index + * @param string $data data to edit + * @param array $column_enum_values $column['values'] + * @param boolean $readOnly is column read only or not + * + * @return string an html snippet + */ + private function getDropDownDependingOnLength( + array $column, + $column_name_appendix, + $onChangeClause, + $tabindex, + $tabindex_for_value, + $idindex, + $data, + array $column_enum_values, + $readOnly + ) { + $html_output = ''; + + //Add hidden input, as disabled '; + } + return $html_output; + } + + /** + * Get HTML radio button for less than 20 string length + * + * @param string $column_name_appendix the name attribute + * @param string $onChangeClause onchange clause for fields + * @param integer $tabindex tab index + * @param array $column description of column in given table + * @param integer $tabindex_for_value offset for the values tabindex + * @param integer $idindex id index + * @param string $data data to edit + * @param array $column_enum_values $column['values'] + * @param boolean $readOnly is column read only or not + * + * @return string an html snippet + */ + private function getRadioButtonDependingOnLength( + $column_name_appendix, + $onChangeClause, + $tabindex, + array $column, + $tabindex_for_value, + $idindex, + $data, + array $column_enum_values, + $readOnly + ) { + $j = 0; + $html_output = ''; + foreach ($column_enum_values as $enum_value) { + $html_output .= ' ' + . ''; + $html_output .= '' . "\n"; + $j++; + } + return $html_output; + } + + /** + * Get the HTML for 'set' pma type + * + * @param array $column description of column in given table + * @param array $extracted_columnspec associative array containing type, + * spec_in_brackets and possibly + * enum_set_values (another array) + * @param string $backup_field hidden input field + * @param string $column_name_appendix the name attribute + * @param string $onChangeClause onchange clause for fields + * @param integer $tabindex tab index + * @param integer $tabindex_for_value offset for the values tabindex + * @param integer $idindex id index + * @param string $data description of the column field + * @param boolean $readOnly is column read only or not + * + * @return string an html snippet + */ + private function getPmaTypeSet( + array $column, + array $extracted_columnspec, + $backup_field, + $column_name_appendix, + $onChangeClause, + $tabindex, + $tabindex_for_value, + $idindex, + $data, + $readOnly + ) { + list($column_set_values, $select_size) = $this->getColumnSetValueAndSelectSize( + $column, + $extracted_columnspec + ); + $vset = array_flip(explode(',', $data)); + $html_output = $backup_field . "\n"; + $html_output .= ''; + $html_output .= ''; + + //Add hidden input, as disabled '; + } + return $html_output; + } + + /** + * Retrieve column 'set' value and select size + * + * @param array $column description of column in given table + * @param array $extracted_columnspec associative array containing type, + * spec_in_brackets and possibly enum_set_values + * (another array) + * + * @return array $column['values'], $column['select_size'] + */ + private function getColumnSetValueAndSelectSize( + array $column, + array $extracted_columnspec + ) { + if (! isset($column['values'])) { + $column['values'] = array(); + foreach ($extracted_columnspec['enum_set_values'] as $val) { + $column['values'][] = array( + 'plain' => $val, + 'html' => htmlspecialchars($val), + ); + } + $column['select_size'] = min(4, count($column['values'])); + } + return array($column['values'], $column['select_size']); + } + + /** + * Get HTML for binary and blob column + * + * @param array $column description of column in given table + * @param string $data data to edit + * @param string $special_chars special characters + * @param integer $biggest_max_file_size biggest max file size for uploading + * @param string $backup_field hidden input field + * @param string $column_name_appendix the name attribute + * @param string $onChangeClause onchange clause for fields + * @param integer $tabindex tab index + * @param integer $tabindex_for_value offset for the values tabindex + * @param integer $idindex id index + * @param string $text_dir text direction + * @param string $special_chars_encoded replaced char if the string starts + * with a \r\n pair (0x0d0a) add an extra \n + * @param string $vkey [multi_edit]['row_id'] + * @param boolean $is_upload is upload or not + * @param boolean $readOnly is column read only or not + * + * @return string an html snippet + */ + private function getBinaryAndBlobColumn( + array $column, + $data, + $special_chars, + $biggest_max_file_size, + $backup_field, + $column_name_appendix, + $onChangeClause, + $tabindex, + $tabindex_for_value, + $idindex, + $text_dir, + $special_chars_encoded, + $vkey, + $is_upload, + $readOnly + ) { + $html_output = ''; + // Add field type : Protected or Hexadecimal + $fields_type_html = ''; + // Default value : hex + $fields_type_val = 'hex'; + if (($GLOBALS['cfg']['ProtectBinary'] === 'blob' && $column['is_blob']) + || ($GLOBALS['cfg']['ProtectBinary'] === 'all') + || ($GLOBALS['cfg']['ProtectBinary'] === 'noblob' && !$column['is_blob']) + ) { + $html_output .= __('Binary - do not edit'); + if (isset($data)) { + $data_size = Util::formatByteDown( + mb_strlen(stripslashes($data)), + 3, + 1 + ); + $html_output .= ' (' . $data_size[0] . ' ' . $data_size[1] . ')'; + unset($data_size); + } + $fields_type_val = 'protected'; + $html_output .= ''; + } elseif ($column['is_blob'] + || ($column['len'] > $GLOBALS['cfg']['LimitChars']) + ) { + $html_output .= "\n" . $this->getTextarea( + $column, + $backup_field, + $column_name_appendix, + $onChangeClause, + $tabindex, + $tabindex_for_value, + $idindex, + $text_dir, + $special_chars_encoded, + 'HEX', + $readOnly + ); + } else { + // field size should be at least 4 and max $GLOBALS['cfg']['LimitChars'] + $fieldsize = min(max($column['len'], 4), $GLOBALS['cfg']['LimitChars']); + $html_output .= "\n" . $backup_field . "\n" . $this->getHtmlInput( + $column, + $column_name_appendix, + $special_chars, + $fieldsize, + $onChangeClause, + $tabindex, + $tabindex_for_value, + $idindex, + 'HEX', + $readOnly + ); + } + $html_output .= sprintf($fields_type_html, $fields_type_val); + + if ($is_upload && $column['is_blob'] && !$readOnly) { + // We don't want to prevent users from using + // browser's default drag-drop feature on some page(s), + // so we add noDragDrop class to the input + $html_output .= '
      ' + . ' '; + list($html_out,) = $this->getMaxUploadSize( + $column, + $biggest_max_file_size + ); + $html_output .= $html_out; + } + + if (!empty($GLOBALS['cfg']['UploadDir']) && !$readOnly) { + $html_output .= $this->getSelectOptionForUpload($vkey, $column); + } + + return $html_output; + } + + /** + * Get HTML input type + * + * @param array $column description of column in given table + * @param string $column_name_appendix the name attribute + * @param string $special_chars special characters + * @param integer $fieldsize html field size + * @param string $onChangeClause onchange clause for fields + * @param integer $tabindex tab index + * @param integer $tabindex_for_value offset for the values tabindex + * @param integer $idindex id index + * @param string $data_type the html5 data-* attribute type + * @param boolean $readOnly is column read only or not + * + * @return string an html snippet + */ + private function getHtmlInput( + array $column, + $column_name_appendix, + $special_chars, + $fieldsize, + $onChangeClause, + $tabindex, + $tabindex_for_value, + $idindex, + $data_type, + $readOnly + ) { + $input_type = 'text'; + // do not use the 'date' or 'time' types here; they have no effect on some + // browsers and create side effects (see bug #4218) + + $the_class = 'textfield'; + // verify True_Type which does not contain the parentheses and length + if ($readOnly) { + //NOOP. Disable date/timepicker + } elseif ($column['True_Type'] === 'date') { + $the_class .= ' datefield'; + } elseif ($column['True_Type'] === 'time') { + $the_class .= ' timefield'; + } elseif ($column['True_Type'] === 'datetime' + || $column['True_Type'] === 'timestamp' + ) { + $the_class .= ' datetimefield'; + } + $input_min_max = false; + if (in_array($column['True_Type'], $this->dbi->types->getIntegerTypes())) { + $extracted_columnspec = Util::extractColumnSpec( + $column['Type'] + ); + $is_unsigned = $extracted_columnspec['unsigned']; + $min_max_values = $this->dbi->types->getIntegerRange( + $column['True_Type'], + ! $is_unsigned + ); + $input_min_max = 'min="' . $min_max_values[0] . '" ' + . 'max="' . $min_max_values[1] . '"'; + $data_type = 'INT'; + } + return ''; + } + + /** + * Get HTML select option for upload + * + * @param string $vkey [multi_edit]['row_id'] + * @param array $column description of column in given table + * + * @return string|void an html snippet + */ + private function getSelectOptionForUpload($vkey, array $column) + { + $files = FileListing::getFileSelectOptions( + Util::userDir($GLOBALS['cfg']['UploadDir']) + ); + + if ($files === false) { + return '' . __('Error') . '
      ' . "\n" + . __('The directory you set for upload work cannot be reached.') . "\n"; + } elseif (!empty($files)) { + return "
      \n" + . '' . __('Or') . '' . ' ' + . __('web server upload directory:') . '
      ' . "\n" + . '' . "\n"; + } + + return null; + } + + /** + * Retrieve the maximum upload file size + * + * @param array $column description of column in given table + * @param integer $biggest_max_file_size biggest max file size for uploading + * + * @return array an html snippet and $biggest_max_file_size + */ + private function getMaxUploadSize(array $column, $biggest_max_file_size) + { + // find maximum upload size, based on field type + /** + * @todo with functions this is not so easy, as you can basically + * process any data with function like MD5 + */ + global $max_upload_size; + $max_field_sizes = array( + 'tinyblob' => '256', + 'blob' => '65536', + 'mediumblob' => '16777216', + 'longblob' => '4294967296' // yeah, really + ); + + $this_field_max_size = $max_upload_size; // from PHP max + if ($this_field_max_size > $max_field_sizes[$column['pma_type']]) { + $this_field_max_size = $max_field_sizes[$column['pma_type']]; + } + $html_output + = Util::getFormattedMaximumUploadSize( + $this_field_max_size + ) . "\n"; + // do not generate here the MAX_FILE_SIZE, because we should + // put only one in the form to accommodate the biggest field + if ($this_field_max_size > $biggest_max_file_size) { + $biggest_max_file_size = $this_field_max_size; + } + return array($html_output, $biggest_max_file_size); + } + + /** + * Get HTML for the Value column of other datatypes + * (here, "column" is used in the sense of HTML column in HTML table) + * + * @param array $column description of column in given table + * @param string $default_char_editing default char editing mode which is stored + * in the config.inc.php script + * @param string $backup_field hidden input field + * @param string $column_name_appendix the name attribute + * @param string $onChangeClause onchange clause for fields + * @param integer $tabindex tab index + * @param string $special_chars special characters + * @param integer $tabindex_for_value offset for the values tabindex + * @param integer $idindex id index + * @param string $text_dir text direction + * @param string $special_chars_encoded replaced char if the string starts + * with a \r\n pair (0x0d0a) add an extra \n + * @param string $data data to edit + * @param array $extracted_columnspec associative array containing type, + * spec_in_brackets and possibly + * enum_set_values (another array) + * @param boolean $readOnly is column read only or not + * + * @return string an html snippet + */ + private function getValueColumnForOtherDatatypes( + array $column, + $default_char_editing, + $backup_field, + $column_name_appendix, + $onChangeClause, + $tabindex, + $special_chars, + $tabindex_for_value, + $idindex, + $text_dir, + $special_chars_encoded, + $data, + array $extracted_columnspec, + $readOnly + ) { + // HTML5 data-* attribute data-type + $data_type = $this->dbi->types->getTypeClass($column['True_Type']); + $fieldsize = $this->getColumnSize($column, $extracted_columnspec); + $html_output = $backup_field . "\n"; + if ($column['is_char'] + && ($GLOBALS['cfg']['CharEditing'] == 'textarea' + || mb_strpos($data, "\n") !== false) + ) { + $html_output .= "\n"; + $GLOBALS['cfg']['CharEditing'] = $default_char_editing; + $html_output .= $this->getTextarea( + $column, + $backup_field, + $column_name_appendix, + $onChangeClause, + $tabindex, + $tabindex_for_value, + $idindex, + $text_dir, + $special_chars_encoded, + $data_type, + $readOnly + ); + } else { + $html_output .= $this->getHtmlInput( + $column, + $column_name_appendix, + $special_chars, + $fieldsize, + $onChangeClause, + $tabindex, + $tabindex_for_value, + $idindex, + $data_type, + $readOnly + ); + + if (preg_match('/(VIRTUAL|PERSISTENT|GENERATED)/', $column['Extra']) && $column['Extra'] !== 'DEFAULT_GENERATED') { + $html_output .= ''; + } + if ($column['Extra'] == 'auto_increment') { + $html_output .= ''; + } + if (substr($column['pma_type'], 0, 9) == 'timestamp') { + $html_output .= ''; + } + if (substr($column['pma_type'], 0, 8) == 'datetime') { + $html_output .= ''; + } + if ($column['True_Type'] == 'bit') { + $html_output .= ''; + } + if ($column['pma_type'] == 'date' + || $column['pma_type'] == 'datetime' + || substr($column['pma_type'], 0, 9) == 'timestamp' + ) { + // the _3 suffix points to the date field + // the _2 suffix points to the corresponding NULL checkbox + // in dateFormat, 'yy' means the year with 4 digits + } + } + return $html_output; + } + + /** + * Get the field size + * + * @param array $column description of column in given table + * @param array $extracted_columnspec associative array containing type, + * spec_in_brackets and possibly enum_set_values + * (another array) + * + * @return integer field size + */ + private function getColumnSize(array $column, array $extracted_columnspec) + { + if ($column['is_char']) { + $fieldsize = $extracted_columnspec['spec_in_brackets']; + if ($fieldsize > $GLOBALS['cfg']['MaxSizeForInputField']) { + /** + * This case happens for CHAR or VARCHAR columns which have + * a size larger than the maximum size for input field. + */ + $GLOBALS['cfg']['CharEditing'] = 'textarea'; + } + } else { + /** + * This case happens for example for INT or DATE columns; + * in these situations, the value returned in $column['len'] + * seems appropriate. + */ + $fieldsize = $column['len']; + } + return min( + max($fieldsize, $GLOBALS['cfg']['MinSizeForInputField']), + $GLOBALS['cfg']['MaxSizeForInputField'] + ); + } + + /** + * Get HTML for gis data types + * + * @return string an html snippet + */ + private function getHtmlForGisDataTypes() + { + $edit_str = Util::getIcon('b_edit', __('Edit/Insert')); + return '' + . Util::linkOrButton( + '#', + $edit_str, + array(), + '_blank' + ) + . ''; + } + + /** + * get html for continue insertion form + * + * @param string $table name of the table + * @param string $db name of the database + * @param array $where_clause_array array of where clauses + * @param string $err_url error url + * + * @return string an html snippet + */ + public function getContinueInsertionForm( + $table, + $db, + array $where_clause_array, + $err_url + ) { + return Template::get('table/insert/continue_insertion_form')->render([ + 'db' => $db, + 'table' => $table, + 'where_clause_array' => $where_clause_array, + 'err_url' => $err_url, + 'goto' => $GLOBALS['goto'], + 'sql_query' => isset($_POST['sql_query']) ? $_POST['sql_query'] : null, + 'has_where_clause' => isset($_POST['where_clause']), + 'insert_rows_default' => $GLOBALS['cfg']['InsertRows'], + ]); + } + + /** + * Get action panel + * + * @param array|null $where_clause where clause + * @param string $after_insert insert mode, e.g. new_insert, same_insert + * @param integer $tabindex tab index + * @param integer $tabindex_for_value offset for the values tabindex + * @param boolean $found_unique_key boolean variable for unique key + * + * @return string an html snippet + */ + public function getActionsPanel( + $where_clause, + $after_insert, + $tabindex, + $tabindex_for_value, + $found_unique_key + ) { + $html_output = '
      ' + . '' + . '' + . '' + . '' + . '' + . ''; + $html_output .='' + . $this->getSubmitAndResetButtonForActionsPanel($tabindex, $tabindex_for_value) + . '' + . '
      ' + . $this->getSubmitTypeDropDown($where_clause, $tabindex, $tabindex_for_value) + . "\n"; + + $html_output .= '' + . '   ' + . __('and then') . '   ' + . '' + . $this->getAfterInsertDropDown( + $where_clause, + $after_insert, + $found_unique_key + ) + . '
      ' + . '
      '; + return $html_output; + } + + /** + * Get a HTML drop down for submit types + * + * @param array|null $where_clause where clause + * @param integer $tabindex tab index + * @param integer $tabindex_for_value offset for the values tabindex + * + * @return string an html snippet + */ + private function getSubmitTypeDropDown( + $where_clause, + $tabindex, + $tabindex_for_value + ) { + $html_output = ''; + return $html_output; + } + + /** + * Get HTML drop down for after insert + * + * @param array|null $where_clause where clause + * @param string $after_insert insert mode, e.g. new_insert, same_insert + * @param boolean $found_unique_key boolean variable for unique key + * + * @return string an html snippet + */ + private function getAfterInsertDropDown($where_clause, $after_insert, $found_unique_key) + { + $html_output = ''; + return $html_output; + } + + /** + * get Submit button and Reset button for action panel + * + * @param integer $tabindex tab index + * @param integer $tabindex_for_value offset for the values tabindex + * + * @return string an html snippet + */ + private function getSubmitAndResetButtonForActionsPanel($tabindex, $tabindex_for_value) + { + return '' + . Util::showHint( + __( + 'Use TAB key to move from value to value,' + . ' or CTRL+arrows to move anywhere.' + ) + ) + . '' + . '' + . '' + . '' + . '' + . ''; + } + + /** + * Get table head and table foot for insert row table + * + * @param array $url_params url parameters + * + * @return string an html snippet + */ + private function getHeadAndFootOfInsertRowTable(array $url_params) + { + $html_output = '
      ' + . '' + . '' + . '' + . ''; + + if ($GLOBALS['cfg']['ShowFieldTypesInDataEditView']) { + $html_output .= $this->showTypeOrFunction('type', $url_params, true); + } + if ($GLOBALS['cfg']['ShowFunctionFields']) { + $html_output .= $this->showTypeOrFunction('function', $url_params, true); + } + + $html_output .= '' + . '' + . '' + . '' + . ' ' + . '' + . '' + . '' + . ''; + return $html_output; + } + + /** + * Prepares the field value and retrieve special chars, backup field and data array + * + * @param array $current_row a row of the table + * @param array $column description of column in given table + * @param array $extracted_columnspec associative array containing type, + * spec_in_brackets and possibly + * enum_set_values (another array) + * @param boolean $real_null_value whether column value null or not null + * @param array $gis_data_types list of GIS data types + * @param string $column_name_appendix string to append to column name in input + * @param bool $as_is use the data as is, used in repopulating + * + * @return array $real_null_value, $data, $special_chars, $backup_field, + * $special_chars_encoded + */ + private function getSpecialCharsAndBackupFieldForExistingRow( + array $current_row, + array $column, + array $extracted_columnspec, + $real_null_value, + array $gis_data_types, + $column_name_appendix, + $as_is + ) { + $special_chars_encoded = ''; + $data = null; + // (we are editing) + if (!isset($current_row[$column['Field']])) { + $real_null_value = true; + $current_row[$column['Field']] = ''; + $special_chars = ''; + $data = $current_row[$column['Field']]; + } elseif ($column['True_Type'] == 'bit') { + $special_chars = $as_is + ? $current_row[$column['Field']] + : Util::printableBitValue( + $current_row[$column['Field']], + $extracted_columnspec['spec_in_brackets'] + ); + } elseif ((substr($column['True_Type'], 0, 9) == 'timestamp' + || $column['True_Type'] == 'datetime' + || $column['True_Type'] == 'time') + && (mb_strpos($current_row[$column['Field']], ".") !== false) + ) { + $current_row[$column['Field']] = $as_is + ? $current_row[$column['Field']] + : Util::addMicroseconds( + $current_row[$column['Field']] + ); + $special_chars = htmlspecialchars($current_row[$column['Field']]); + } elseif (in_array($column['True_Type'], $gis_data_types)) { + // Convert gis data to Well Know Text format + $current_row[$column['Field']] = $as_is + ? $current_row[$column['Field']] + : Util::asWKT( + $current_row[$column['Field']], + true + ); + $special_chars = htmlspecialchars($current_row[$column['Field']]); + } else { + // special binary "characters" + if ($column['is_binary'] + || ($column['is_blob'] && $GLOBALS['cfg']['ProtectBinary'] !== 'all') + ) { + $current_row[$column['Field']] = $as_is + ? $current_row[$column['Field']] + : bin2hex( + $current_row[$column['Field']] + ); + } // end if + $special_chars = htmlspecialchars($current_row[$column['Field']]); + + //We need to duplicate the first \n or otherwise we will lose + //the first newline entered in a VARCHAR or TEXT column + $special_chars_encoded + = Util::duplicateFirstNewline($special_chars); + + $data = $current_row[$column['Field']]; + } // end if... else... + + //when copying row, it is useful to empty auto-increment column + // to prevent duplicate key error + if (isset($_POST['default_action']) + && $_POST['default_action'] === 'insert' + ) { + if ($column['Key'] === 'PRI' + && mb_strpos($column['Extra'], 'auto_increment') !== false + ) { + $data = $special_chars_encoded = $special_chars = null; + } + } + // If a timestamp field value is not included in an update + // statement MySQL auto-update it to the current timestamp; + // however, things have changed since MySQL 4.1, so + // it's better to set a fields_prev in this situation + $backup_field = ''; + + return array( + $real_null_value, + $special_chars_encoded, + $special_chars, + $data, + $backup_field + ); + } + + /** + * display default values + * + * @param array $column description of column in given table + * @param boolean $real_null_value whether column value null or not null + * + * @return array $real_null_value, $data, $special_chars, + * $backup_field, $special_chars_encoded + */ + private function getSpecialCharsAndBackupFieldForInsertingMode( + array $column, + $real_null_value + ) { + if (! isset($column['Default'])) { + $column['Default'] = ''; + $real_null_value = true; + $data = ''; + } else { + $data = $column['Default']; + } + + $trueType = $column['True_Type']; + + if ($trueType == 'bit') { + $special_chars = Util::convertBitDefaultValue( + $column['Default'] + ); + } elseif (substr($trueType, 0, 9) == 'timestamp' + || $trueType == 'datetime' + || $trueType == 'time' + ) { + $special_chars = Util::addMicroseconds($column['Default']); + } elseif ($trueType == 'binary' || $trueType == 'varbinary') { + $special_chars = bin2hex($column['Default']); + } elseif ('text' === substr($trueType, -4)) { + $textDefault = substr($column['Default'], 1, -1); + $special_chars = stripcslashes($textDefault !== false ? $textDefault : $column['Default']); + } else { + $special_chars = htmlspecialchars($column['Default']); + } + $backup_field = ''; + $special_chars_encoded = Util::duplicateFirstNewline( + $special_chars + ); + return array( + $real_null_value, $data, $special_chars, + $backup_field, $special_chars_encoded + ); + } + + /** + * Prepares the update/insert of a row + * + * @return array $loop_array, $using_key, $is_insert, $is_insertignore + */ + public function getParamsForUpdateOrInsert() + { + if (isset($_POST['where_clause'])) { + // we were editing something => use the WHERE clause + $loop_array = is_array($_POST['where_clause']) + ? $_POST['where_clause'] + : array($_POST['where_clause']); + $using_key = true; + $is_insert = isset($_POST['submit_type']) + && ($_POST['submit_type'] == 'insert' + || $_POST['submit_type'] == 'showinsert' + || $_POST['submit_type'] == 'insertignore'); + } else { + // new row => use indexes + $loop_array = array(); + if (! empty($_POST['fields'])) { + foreach ($_POST['fields']['multi_edit'] as $key => $dummy) { + $loop_array[] = $key; + } + } + $using_key = false; + $is_insert = true; + } + $is_insertignore = isset($_POST['submit_type']) + && $_POST['submit_type'] == 'insertignore'; + return array($loop_array, $using_key, $is_insert, $is_insertignore); + } + + /** + * Check wether insert row mode and if so include tbl_changen script and set + * global variables. + * + * @return void + */ + public function isInsertRow() + { + if (isset($_POST['insert_rows']) + && is_numeric($_POST['insert_rows']) + && $_POST['insert_rows'] != $GLOBALS['cfg']['InsertRows'] + ) { + $GLOBALS['cfg']['InsertRows'] = $_POST['insert_rows']; + $response = Response::getInstance(); + $header = $response->getHeader(); + $scripts = $header->getScripts(); + $scripts->addFile('vendor/jquery/additional-methods.js'); + $scripts->addFile('tbl_change.js'); + if (!defined('TESTSUITE')) { + include 'tbl_change.php'; + exit; + } + } + } + + /** + * set $_SESSION for edit_next + * + * @param string $one_where_clause one where clause from where clauses array + * + * @return void + */ + public function setSessionForEditNext($one_where_clause) + { + $local_query = 'SELECT * FROM ' . Util::backquote($GLOBALS['db']) + . '.' . Util::backquote($GLOBALS['table']) . ' WHERE ' + . str_replace('` =', '` >', $one_where_clause) . ' LIMIT 1;'; + + $res = $this->dbi->query($local_query); + $row = $this->dbi->fetchRow($res); + $meta = $this->dbi->getFieldsMeta($res); + // must find a unique condition based on unique key, + // not a combination of all fields + list($unique_condition, $clause_is_unique) + = Util::getUniqueCondition( + $res, // handle + count($meta), // fields_cnt + $meta, // fields_meta + $row, // row + true, // force_unique + false, // restrict_to_table + null // analyzed_sql_results + ); + if (! empty($unique_condition)) { + $_SESSION['edit_next'] = $unique_condition; + } + unset($unique_condition, $clause_is_unique); + } + + /** + * set $goto_include variable for different cases and retrieve like, + * if $GLOBALS['goto'] empty, if $goto_include previously not defined + * and new_insert, same_insert, edit_next + * + * @param string $goto_include store some script for include, otherwise it is + * boolean false + * + * @return string $goto_include + */ + public function getGotoInclude($goto_include) + { + $valid_options = array('new_insert', 'same_insert', 'edit_next'); + if (isset($_POST['after_insert']) + && in_array($_POST['after_insert'], $valid_options) + ) { + $goto_include = 'tbl_change.php'; + } elseif (! empty($GLOBALS['goto'])) { + if (! preg_match('@^[a-z_]+\.php$@', $GLOBALS['goto'])) { + // this should NOT happen + //$GLOBALS['goto'] = false; + $goto_include = false; + } else { + $goto_include = $GLOBALS['goto']; + } + if ($GLOBALS['goto'] == 'db_sql.php' && strlen($GLOBALS['table']) > 0) { + $GLOBALS['table'] = ''; + } + } + if (! $goto_include) { + if (strlen($GLOBALS['table']) === 0) { + $goto_include = 'db_sql.php'; + } else { + $goto_include = 'tbl_sql.php'; + } + } + return $goto_include; + } + + /** + * Defines the url to return in case of failure of the query + * + * @param array $url_params url parameters + * + * @return string error url for query failure + */ + public function getErrorUrl(array $url_params) + { + if (isset($_POST['err_url'])) { + return $_POST['err_url']; + } + + return 'tbl_change.php' . Url::getCommon($url_params); + } + + /** + * Builds the sql query + * + * @param boolean $is_insertignore $_POST['submit_type'] == 'insertignore' + * @param array $query_fields column names array + * @param array $value_sets array of query values + * + * @return array of query + */ + public function buildSqlQuery($is_insertignore, array $query_fields, array $value_sets) + { + if ($is_insertignore) { + $insert_command = 'INSERT IGNORE '; + } else { + $insert_command = 'INSERT '; + } + $query = array( + $insert_command . 'INTO ' + . Util::backquote($GLOBALS['table']) + . ' (' . implode(', ', $query_fields) . ') VALUES (' + . implode('), (', $value_sets) . ')' + ); + unset($insert_command, $query_fields); + return $query; + } + + /** + * Executes the sql query and get the result, then move back to the calling page + * + * @param array $url_params url parameters array + * @param array $query built query from buildSqlQuery() + * + * @return array $url_params, $total_affected_rows, $last_messages + * $warning_messages, $error_messages, $return_to_sql_query + */ + public function executeSqlQuery(array $url_params, array $query) + { + $return_to_sql_query = ''; + if (! empty($GLOBALS['sql_query'])) { + $url_params['sql_query'] = $GLOBALS['sql_query']; + $return_to_sql_query = $GLOBALS['sql_query']; + } + $GLOBALS['sql_query'] = implode('; ', $query) . ';'; + // to ensure that the query is displayed in case of + // "insert as new row" and then "insert another new row" + $GLOBALS['display_query'] = $GLOBALS['sql_query']; + + $total_affected_rows = 0; + $last_messages = array(); + $warning_messages = array(); + $error_messages = array(); + + foreach ($query as $single_query) { + if ($_POST['submit_type'] == 'showinsert') { + $last_messages[] = Message::notice(__('Showing SQL query')); + continue; + } + if ($GLOBALS['cfg']['IgnoreMultiSubmitErrors']) { + $result = $this->dbi->tryQuery($single_query); + } else { + $result = $this->dbi->query($single_query); + } + if (! $result) { + $error_messages[] = $this->dbi->getError(); + } else { + // The next line contains a real assignment, it's not a typo + if ($tmp = @$this->dbi->affectedRows()) { + $total_affected_rows += $tmp; + } + unset($tmp); + + $insert_id = $this->dbi->insertId(); + if ($insert_id != 0) { + // insert_id is id of FIRST record inserted in one insert, so if we + // inserted multiple rows, we had to increment this + + if ($total_affected_rows > 0) { + $insert_id = $insert_id + $total_affected_rows - 1; + } + $last_message = Message::notice(__('Inserted row id: %1$d')); + $last_message->addParam($insert_id); + $last_messages[] = $last_message; + } + $this->dbi->freeResult($result); + } + $warning_messages = $this->getWarningMessages(); + } + return array( + $url_params, + $total_affected_rows, + $last_messages, + $warning_messages, + $error_messages, + $return_to_sql_query + ); + } + + /** + * get the warning messages array + * + * @return array $warning_essages + */ + private function getWarningMessages() + { + $warning_essages = array(); + foreach ($this->dbi->getWarnings() as $warning) { + $warning_essages[] = Message::sanitize( + $warning['Level'] . ': #' . $warning['Code'] . ' ' . $warning['Message'] + ); + } + return $warning_essages; + } + + /** + * Column to display from the foreign table? + * + * @param string $where_comparison string that contain relation field value + * @param array $map all Relations to foreign tables for a given + * table or optionally a given column in a table + * @param string $relation_field relation field + * + * @return string $dispval display value from the foreign table + */ + public function getDisplayValueForForeignTableColumn( + $where_comparison, + array $map, + $relation_field + ) { + $foreigner = $this->relation->searchColumnInForeigners($map, $relation_field); + $display_field = $this->relation->getDisplayField( + $foreigner['foreign_db'], + $foreigner['foreign_table'] + ); + // Field to display from the foreign table? + if (isset($display_field) && strlen($display_field) > 0) { + $dispsql = 'SELECT ' . Util::backquote($display_field) + . ' FROM ' . Util::backquote($foreigner['foreign_db']) + . '.' . Util::backquote($foreigner['foreign_table']) + . ' WHERE ' . Util::backquote($foreigner['foreign_field']) + . $where_comparison; + $dispresult = $this->dbi->tryQuery( + $dispsql, + DatabaseInterface::CONNECT_USER, + DatabaseInterface::QUERY_STORE + ); + if ($dispresult && $this->dbi->numRows($dispresult) > 0) { + list($dispval) = $this->dbi->fetchRow($dispresult, 0); + } else { + $dispval = ''; + } + if ($dispresult) { + $this->dbi->freeResult($dispresult); + } + return $dispval; + } + return ''; + } + + /** + * Display option in the cell according to user choices + * + * @param array $map all Relations to foreign tables for a given + * table or optionally a given column in a table + * @param string $relation_field relation field + * @param string $where_comparison string that contain relation field value + * @param string $dispval display value from the foreign table + * @param string $relation_field_value relation field value + * + * @return string $output HTML tag + */ + public function getLinkForRelationalDisplayField( + array $map, + $relation_field, + $where_comparison, + $dispval, + $relation_field_value + ) { + $foreigner = $this->relation->searchColumnInForeigners($map, $relation_field); + if ('K' == $_SESSION['tmpval']['relational_display']) { + // user chose "relational key" in the display options, so + // the title contains the display field + $title = (! empty($dispval)) + ? ' title="' . htmlspecialchars($dispval) . '"' + : ''; + } else { + $title = ' title="' . htmlspecialchars($relation_field_value) . '"'; + } + $_url_params = array( + 'db' => $foreigner['foreign_db'], + 'table' => $foreigner['foreign_table'], + 'pos' => '0', + 'sql_query' => 'SELECT * FROM ' + . Util::backquote($foreigner['foreign_db']) + . '.' . Util::backquote($foreigner['foreign_table']) + . ' WHERE ' . Util::backquote($foreigner['foreign_field']) + . $where_comparison + ); + $output = ''; + + if ('D' == $_SESSION['tmpval']['relational_display']) { + // user chose "relational display field" in the + // display options, so show display field in the cell + $output .= (!empty($dispval)) ? htmlspecialchars($dispval) : ''; + } else { + // otherwise display data in the cell + $output .= htmlspecialchars($relation_field_value); + } + $output .= ''; + return $output; + } + + /** + * Transform edited values + * + * @param string $db db name + * @param string $table table name + * @param array $transformation mimetypes for all columns of a table + * [field_name][field_key] + * @param array &$edited_values transform columns list and new values + * @param string $file file containing the transformation plugin + * @param string $column_name column name + * @param array $extra_data extra data array + * @param string $type the type of transformation + * + * @return array $extra_data + */ + public function transformEditedValues( + $db, + $table, + array $transformation, + array &$edited_values, + $file, + $column_name, + array $extra_data, + $type + ) { + $include_file = 'libraries/classes/Plugins/Transformations/' . $file; + if (is_file($include_file)) { + $_url_params = array( + 'db' => $db, + 'table' => $table, + 'where_clause' => $_POST['where_clause'], + 'transform_key' => $column_name + ); + $transform_options = Transformations::getOptions( + isset($transformation[$type . '_options']) + ? $transformation[$type . '_options'] + : '' + ); + $transform_options['wrapper_link'] = Url::getCommon($_url_params); + $class_name = Transformations::getClassName($include_file); + if (class_exists($class_name)) { + /** @var TransformationsPlugin $transformation_plugin */ + $transformation_plugin = new $class_name(); + + foreach ($edited_values as $cell_index => $curr_cell_edited_values) { + if (isset($curr_cell_edited_values[$column_name])) { + $edited_values[$cell_index][$column_name] + = $extra_data['transformations'][$cell_index] + = $transformation_plugin->applyTransformation( + $curr_cell_edited_values[$column_name], + $transform_options, + '' + ); + } + } // end of loop for each transformation cell + } + } + return $extra_data; + } + + /** + * Get current value in multi edit mode + * + * @param array $multi_edit_funcs multiple edit functions array + * @param array $multi_edit_salt multiple edit array with encryption salt + * @param array $gis_from_text_functions array that contains gis from text functions + * @param string $current_value current value in the column + * @param array $gis_from_wkb_functions initially $val is $multi_edit_columns[$key] + * @param array $func_optional_param array('RAND','UNIX_TIMESTAMP') + * @param array $func_no_param array of set of string + * @param string $key an md5 of the column name + * + * @return array $cur_value + */ + public function getCurrentValueAsAnArrayForMultipleEdit( + $multi_edit_funcs, + $multi_edit_salt, + $gis_from_text_functions, + $current_value, + $gis_from_wkb_functions, + $func_optional_param, + $func_no_param, + $key + ) { + if (empty($multi_edit_funcs[$key])) { + return $current_value; + } elseif ('UUID' === $multi_edit_funcs[$key]) { + /* This way user will know what UUID new row has */ + $uuid = $this->dbi->fetchValue('SELECT UUID()'); + return "'" . $uuid . "'"; + } elseif ((in_array($multi_edit_funcs[$key], $gis_from_text_functions) + && substr($current_value, 0, 3) == "'''") + || in_array($multi_edit_funcs[$key], $gis_from_wkb_functions) + ) { + // Remove enclosing apostrophes + $current_value = mb_substr($current_value, 1, -1); + // Remove escaping apostrophes + $current_value = str_replace("''", "'", $current_value); + return $multi_edit_funcs[$key] . '(' . $current_value . ')'; + } elseif (! in_array($multi_edit_funcs[$key], $func_no_param) + || ($current_value != "''" + && in_array($multi_edit_funcs[$key], $func_optional_param)) + ) { + if ((isset($multi_edit_salt[$key]) + && ($multi_edit_funcs[$key] == "AES_ENCRYPT" + || $multi_edit_funcs[$key] == "AES_DECRYPT")) + || (! empty($multi_edit_salt[$key]) + && ($multi_edit_funcs[$key] == "DES_ENCRYPT" + || $multi_edit_funcs[$key] == "DES_DECRYPT" + || $multi_edit_funcs[$key] == "ENCRYPT")) + ) { + return $multi_edit_funcs[$key] . '(' . $current_value . ",'" + . $this->dbi->escapeString($multi_edit_salt[$key]) . "')"; + } + + return $multi_edit_funcs[$key] . '(' . $current_value . ')'; + } + + return $multi_edit_funcs[$key] . '()'; + } + + /** + * Get query values array and query fields array for insert and update in multi edit + * + * @param array $multi_edit_columns_name multiple edit columns name array + * @param array $multi_edit_columns_null multiple edit columns null array + * @param string $current_value current value in the column in loop + * @param array $multi_edit_columns_prev multiple edit previous columns array + * @param array $multi_edit_funcs multiple edit functions array + * @param boolean $is_insert boolean value whether insert or not + * @param array $query_values SET part of the sql query + * @param array $query_fields array of query fields + * @param string $current_value_as_an_array current value in the column + * as an array + * @param array $value_sets array of valu sets + * @param string $key an md5 of the column name + * @param array $multi_edit_columns_null_prev array of multiple edit columns + * null previous + * + * @return array ($query_values, $query_fields) + */ + public function getQueryValuesForInsertAndUpdateInMultipleEdit( + $multi_edit_columns_name, + $multi_edit_columns_null, + $current_value, + $multi_edit_columns_prev, + $multi_edit_funcs, + $is_insert, + $query_values, + $query_fields, + $current_value_as_an_array, + $value_sets, + $key, + $multi_edit_columns_null_prev + ) { + // i n s e r t + if ($is_insert) { + // no need to add column into the valuelist + if (strlen($current_value_as_an_array) > 0) { + $query_values[] = $current_value_as_an_array; + // first inserted row so prepare the list of fields + if (empty($value_sets)) { + $query_fields[] = Util::backquote( + $multi_edit_columns_name[$key] + ); + } + } + } elseif (! empty($multi_edit_columns_null_prev[$key]) + && ! isset($multi_edit_columns_null[$key]) + ) { + // u p d a t e + + // field had the null checkbox before the update + // field no longer has the null checkbox + $query_values[] + = Util::backquote($multi_edit_columns_name[$key]) + . ' = ' . $current_value_as_an_array; + } elseif (empty($multi_edit_funcs[$key]) + && isset($multi_edit_columns_prev[$key]) + && (("'" . $this->dbi->escapeString($multi_edit_columns_prev[$key]) . "'" === $current_value) + || ('0x' . $multi_edit_columns_prev[$key] === $current_value)) + ) { + // No change for this column and no MySQL function is used -> next column + } elseif (! empty($current_value)) { + // avoid setting a field to NULL when it's already NULL + // (field had the null checkbox before the update + // field still has the null checkbox) + if (empty($multi_edit_columns_null_prev[$key]) + || empty($multi_edit_columns_null[$key]) + ) { + $query_values[] + = Util::backquote($multi_edit_columns_name[$key]) + . ' = ' . $current_value_as_an_array; + } + } + return array($query_values, $query_fields); + } + + /** + * Get the current column value in the form for different data types + * + * @param string|false $possibly_uploaded_val uploaded file content + * @param string $key an md5 of the column name + * @param array $multi_edit_columns_type array of multi edit column types + * @param string $current_value current column value in the form + * @param array $multi_edit_auto_increment multi edit auto increment + * @param integer $rownumber index of where clause array + * @param array $multi_edit_columns_name multi edit column names array + * @param array $multi_edit_columns_null multi edit columns null array + * @param array $multi_edit_columns_null_prev multi edit columns previous null + * @param boolean $is_insert whether insert or not + * @param boolean $using_key whether editing or new row + * @param string $where_clause where clause + * @param string $table table name + * @param array $multi_edit_funcs multiple edit functions array + * + * @return string $current_value current column value in the form + */ + public function getCurrentValueForDifferentTypes( + $possibly_uploaded_val, + $key, + $multi_edit_columns_type, + $current_value, + $multi_edit_auto_increment, + $rownumber, + $multi_edit_columns_name, + $multi_edit_columns_null, + $multi_edit_columns_null_prev, + $is_insert, + $using_key, + $where_clause, + $table, + $multi_edit_funcs + ) { + // Fetch the current values of a row to use in case we have a protected field + if ($is_insert + && $using_key && isset($multi_edit_columns_type) + && is_array($multi_edit_columns_type) && !empty($where_clause) + ) { + $protected_row = $this->dbi->fetchSingleRow( + 'SELECT * FROM ' . Util::backquote($table) + . ' WHERE ' . $where_clause . ';' + ); + } + + if (false !== $possibly_uploaded_val) { + $current_value = $possibly_uploaded_val; + } elseif (! empty($multi_edit_funcs[$key])) { + $current_value = "'" . $this->dbi->escapeString($current_value) + . "'"; + } else { + // c o l u m n v a l u e i n t h e f o r m + if (isset($multi_edit_columns_type[$key])) { + $type = $multi_edit_columns_type[$key]; + } else { + $type = ''; + } + + if ($type != 'protected' && $type != 'set' && strlen($current_value) === 0) { + // best way to avoid problems in strict mode + // (works also in non-strict mode) + if (isset($multi_edit_auto_increment) + && isset($multi_edit_auto_increment[$key]) + ) { + $current_value = 'NULL'; + } else { + $current_value = "''"; + } + } elseif ($type == 'set') { + if (! empty($_POST['fields']['multi_edit'][$rownumber][$key])) { + $current_value = implode( + ',', + $_POST['fields']['multi_edit'][$rownumber][$key] + ); + $current_value = "'" + . $this->dbi->escapeString($current_value) . "'"; + } else { + $current_value = "''"; + } + } elseif ($type == 'protected') { + // here we are in protected mode (asked in the config) + // so tbl_change has put this special value in the + // columns array, so we do not change the column value + // but we can still handle column upload + + // when in UPDATE mode, do not alter field's contents. When in INSERT + // mode, insert empty field because no values were submitted. + // If protected blobs where set, insert original fields content. + if (! empty($protected_row[$multi_edit_columns_name[$key]])) { + $current_value = '0x' + . bin2hex($protected_row[$multi_edit_columns_name[$key]]); + } else { + $current_value = ''; + } + } elseif ($type === 'hex') { + if (substr($current_value, 0, 2) != '0x') { + $current_value = '0x' . $current_value; + } + } elseif ($type == 'bit') { + $current_value = preg_replace('/[^01]/', '0', $current_value); + $current_value = "b'" . $this->dbi->escapeString($current_value) + . "'"; + } elseif (! ($type == 'datetime' || $type == 'timestamp') + || ($current_value != 'CURRENT_TIMESTAMP' + && $current_value != 'current_timestamp()') + ) { + $current_value = "'" . $this->dbi->escapeString($current_value) + . "'"; + } + + // Was the Null checkbox checked for this field? + // (if there is a value, we ignore the Null checkbox: this could + // be possible if Javascript is disabled in the browser) + if (! empty($multi_edit_columns_null[$key]) + && ($current_value == "''" || $current_value == '') + ) { + $current_value = 'NULL'; + } + + // The Null checkbox was unchecked for this field + if (empty($current_value) + && ! empty($multi_edit_columns_null_prev[$key]) + && ! isset($multi_edit_columns_null[$key]) + ) { + $current_value = "''"; + } + } // end else (column value in the form) + return $current_value; + } + + /** + * Check whether inline edited value can be truncated or not, + * and add additional parameters for extra_data array if needed + * + * @param string $db Database name + * @param string $table Table name + * @param string $column_name Column name + * @param array &$extra_data Extra data for ajax response + * + * @return void + */ + public function verifyWhetherValueCanBeTruncatedAndAppendExtraData( + $db, + $table, + $column_name, + array &$extra_data + ) { + $extra_data['isNeedToRecheck'] = false; + + $sql_for_real_value = 'SELECT ' . Util::backquote($table) . '.' + . Util::backquote($column_name) + . ' FROM ' . Util::backquote($db) . '.' + . Util::backquote($table) + . ' WHERE ' . $_POST['where_clause'][0]; + + $result = $this->dbi->tryQuery($sql_for_real_value); + $fields_meta = $this->dbi->getFieldsMeta($result); + $meta = $fields_meta[0]; + if ($row = $this->dbi->fetchRow($result)) { + $new_value = $row[0]; + if ((substr($meta->type, 0, 9) == 'timestamp') + || ($meta->type == 'datetime') + || ($meta->type == 'time') + ) { + $new_value = Util::addMicroseconds($new_value); + } elseif (mb_strpos($meta->flags, 'binary') !== false) { + $new_value = '0x' . bin2hex($new_value); + } + $extra_data['isNeedToRecheck'] = true; + $extra_data['truncatableFieldValue'] = $new_value; + } + $this->dbi->freeResult($result); + } + + /** + * Function to get the columns of a table + * + * @param string $db current db + * @param string $table current table + * + * @return array + */ + public function getTableColumns($db, $table) + { + $this->dbi->selectDb($db); + return array_values($this->dbi->getColumns($db, $table, null, true)); + } + + /** + * Function to determine Insert/Edit rows + * + * @param string $where_clause where clause + * @param string $db current database + * @param string $table current table + * + * @return mixed + */ + public function determineInsertOrEdit($where_clause, $db, $table) + { + if (isset($_POST['where_clause'])) { + $where_clause = $_POST['where_clause']; + } + if (isset($_SESSION['edit_next'])) { + $where_clause = $_SESSION['edit_next']; + unset($_SESSION['edit_next']); + $after_insert = 'edit_next'; + } + if (isset($_POST['ShowFunctionFields'])) { + $GLOBALS['cfg']['ShowFunctionFields'] = $_POST['ShowFunctionFields']; + } + if (isset($_POST['ShowFieldTypesInDataEditView'])) { + $GLOBALS['cfg']['ShowFieldTypesInDataEditView'] + = $_POST['ShowFieldTypesInDataEditView']; + } + if (isset($_POST['after_insert'])) { + $after_insert = $_POST['after_insert']; + } + + if (isset($where_clause)) { + // we are editing + $insert_mode = false; + $where_clause_array = $this->getWhereClauseArray($where_clause); + list($where_clauses, $result, $rows, $found_unique_key) + = $this->analyzeWhereClauses( + $where_clause_array, + $table, + $db + ); + } else { + // we are inserting + $insert_mode = true; + $where_clause = null; + list($result, $rows) = $this->loadFirstRow($table, $db); + $where_clauses = null; + $where_clause_array = array(); + $found_unique_key = false; + } + + // Copying a row - fetched data will be inserted as a new row, + // therefore the where clause is needless. + if (isset($_POST['default_action']) + && $_POST['default_action'] === 'insert' + ) { + $where_clause = $where_clauses = null; + } + + return array( + $insert_mode, $where_clause, $where_clause_array, $where_clauses, + $result, $rows, $found_unique_key, + isset($after_insert) ? $after_insert : null + ); + } + + /** + * Function to get comments for the table columns + * + * @param string $db current database + * @param string $table current table + * + * @return array $comments_map comments for columns + */ + public function getCommentsMap($db, $table) + { + $comments_map = array(); + + if ($GLOBALS['cfg']['ShowPropertyComments']) { + $comments_map = $this->relation->getComments($db, $table); + } + + return $comments_map; + } + + /** + * Function to get URL parameters + * + * @param string $db current database + * @param string $table current table + * + * @return array $url_params url parameters + */ + public function getUrlParameters($db, $table) + { + /** + * @todo check if we could replace by "db_|tbl_" - please clarify!? + */ + $url_params = array( + 'db' => $db, + 'sql_query' => $_POST['sql_query'] + ); + + if (preg_match('@^tbl_@', $GLOBALS['goto'])) { + $url_params['table'] = $table; + } + + return $url_params; + } + + /** + * Function to get html for the gis editor div + * + * @return string + */ + public function getHtmlForGisEditor() + { + return '
      ' + . '' + . '
      '; + } + + /** + * Function to get html for the ignore option in insert mode + * + * @param int $row_id row id + * @param bool $checked ignore option is checked or not + * + * @return string + */ + public function getHtmlForIgnoreOption($row_id, $checked = true) + { + return '' + . '
      ' . "\n"; + } + + /** + * Function to get html for the function option + * + * @param array $column column + * @param string $column_name_appendix column name appendix + * + * @return String + */ + private function getHtmlForFunctionOption(array $column, $column_name_appendix) + { + return '' + . ''; + } + + /** + * Function to get html for the column type + * + * @param array $column column + * + * @return string + */ + private function getHtmlForInsertEditColumnType(array $column) + { + return ''; + } + + /** + * Function to get html for the insert edit form header + * + * @param bool $has_blob_field whether has blob field + * @param bool $is_upload whether is upload + * + * @return string + */ + public function getHtmlForInsertEditFormHeader($has_blob_field, $is_upload) + { + $html_output ='analyzeTableColumnsArray( + $column, + $comments_map, + $timestamp_seen + ); + } + $as_is = false; + if (!empty($repopulate) && !empty($current_row)) { + $current_row[$column['Field']] = $repopulate[$column['Field_md5']]; + $as_is = true; + } + + $extracted_columnspec + = Util::extractColumnSpec($column['Type']); + + if (-1 === $column['len']) { + $column['len'] = $this->dbi->fieldLen( + $current_result, + $column_number + ); + // length is unknown for geometry fields, + // make enough space to edit very simple WKTs + if (-1 === $column['len']) { + $column['len'] = 30; + } + } + //Call validation when the form submitted... + $onChangeClause = $chg_evt_handler + . "=\"return verificationsAfterFieldChange('" + . Sanitize::escapeJsString($column['Field_md5']) . "', '" + . Sanitize::escapeJsString($jsvkey) . "','" . $column['pma_type'] . "')\""; + + // Use an MD5 as an array index to avoid having special characters + // in the name attribute (see bug #1746964 ) + $column_name_appendix = $vkey . '[' . $column['Field_md5'] . ']'; + + if ($column['Type'] === 'datetime' + && ! isset($column['Default']) + && ! is_null($column['Default']) + && $insert_mode + ) { + $column['Default'] = date('Y-m-d H:i:s', time()); + } + + $html_output = $this->getHtmlForFunctionOption( + $column, + $column_name_appendix + ); + + if ($GLOBALS['cfg']['ShowFieldTypesInDataEditView']) { + $html_output .= $this->getHtmlForInsertEditColumnType($column); + } //End if + + // Get a list of GIS data types. + $gis_data_types = Util::getGISDatatypes(); + + // Prepares the field value + $real_null_value = false; + $special_chars_encoded = ''; + if (!empty($current_row)) { + // (we are editing) + list( + $real_null_value, $special_chars_encoded, $special_chars, + $data, $backup_field + ) + = $this->getSpecialCharsAndBackupFieldForExistingRow( + $current_row, + $column, + $extracted_columnspec, + $real_null_value, + $gis_data_types, + $column_name_appendix, + $as_is + ); + } else { + // (we are inserting) + // display default values + $tmp = $column; + if (isset($repopulate[$column['Field_md5']])) { + $tmp['Default'] = $repopulate[$column['Field_md5']]; + } + list($real_null_value, $data, $special_chars, $backup_field, + $special_chars_encoded + ) + = $this->getSpecialCharsAndBackupFieldForInsertingMode( + $tmp, + $real_null_value + ); + unset($tmp); + } + + $idindex = ($o_rows * $columns_cnt) + $column_number + 1; + $tabindex = $idindex; + + // Get a list of data types that are not yet supported. + $no_support_types = Util::unsupportedDatatypes(); + + // The function column + // ------------------- + $foreignData = $this->relation->getForeignData( + $foreigners, + $column['Field'], + false, + '', + '' + ); + if ($GLOBALS['cfg']['ShowFunctionFields']) { + $html_output .= $this->getFunctionColumn( + $column, + $is_upload, + $column_name_appendix, + $onChangeClause, + $no_support_types, + $tabindex_for_function, + $tabindex, + $idindex, + $insert_mode, + $readOnly, + $foreignData + ); + } + + // The null column + // --------------- + $html_output .= $this->getNullColumn( + $column, + $column_name_appendix, + $real_null_value, + $tabindex, + $tabindex_for_null, + $idindex, + $vkey, + $foreigners, + $foreignData, + $readOnly + ); + + // The value column (depends on type) + // ---------------- + // See bug #1667887 for the reason why we don't use the maxlength + // HTML attribute + + //add data attributes "no of decimals" and "data type" + $no_decimals = 0; + $type = current(explode("(", $column['pma_type'])); + if (preg_match('/\(([^()]+)\)/', $column['pma_type'], $match)) { + $match[0] = trim($match[0], '()'); + $no_decimals = $match[0]; + } + $html_output .= '' . "\n"; + // Will be used by js/tbl_change.js to set the default value + // for the "Continue insertion" feature + $html_output .= '' + . $special_chars . ''; + + // Check input transformation of column + $transformed_html = ''; + if (!empty($column_mime['input_transformation'])) { + $file = $column_mime['input_transformation']; + $include_file = 'libraries/classes/Plugins/Transformations/' . $file; + if (is_file($include_file)) { + $class_name = Transformations::getClassName($include_file); + if (class_exists($class_name)) { + $transformation_plugin = new $class_name(); + $transformation_options = Transformations::getOptions( + $column_mime['input_transformation_options'] + ); + $_url_params = array( + 'db' => $db, + 'table' => $table, + 'transform_key' => $column['Field'], + 'where_clause' => $where_clause + ); + $transformation_options['wrapper_link'] + = Url::getCommon($_url_params); + $current_value = ''; + if (isset($current_row[$column['Field']])) { + $current_value = $current_row[$column['Field']]; + } + if (method_exists($transformation_plugin, 'getInputHtml')) { + $transformed_html = $transformation_plugin->getInputHtml( + $column, + $row_id, + $column_name_appendix, + $transformation_options, + $current_value, + $text_dir, + $tabindex, + $tabindex_for_value, + $idindex + ); + } + if (method_exists($transformation_plugin, 'getScripts')) { + $GLOBALS['plugin_scripts'] = array_merge( + $GLOBALS['plugin_scripts'], + $transformation_plugin->getScripts() + ); + } + } + } + } + if (!empty($transformed_html)) { + $html_output .= $transformed_html; + } else { + $html_output .= $this->getValueColumn( + $column, + $backup_field, + $column_name_appendix, + $onChangeClause, + $tabindex, + $tabindex_for_value, + $idindex, + $data, + $special_chars, + $foreignData, + array($table, $db), + $row_id, + $titles, + $text_dir, + $special_chars_encoded, + $vkey, + $is_upload, + $biggest_max_file_size, + $default_char_editing, + $no_support_types, + $gis_data_types, + $extracted_columnspec, + $readOnly + ); + } + return $html_output; + } + + /** + * Function to get html for each insert/edit row + * + * @param array $url_params url parameters + * @param array $table_columns table columns + * @param array $comments_map comments map + * @param bool $timestamp_seen whether timestamp seen + * @param array $current_result current result + * @param string $chg_evt_handler javascript change event handler + * @param string $jsvkey javascript validation key + * @param string $vkey validation key + * @param bool $insert_mode whether insert mode + * @param array $current_row current row + * @param int &$o_rows row offset + * @param int &$tabindex tab index + * @param int $columns_cnt columns count + * @param bool $is_upload whether upload + * @param int $tabindex_for_function tab index offset for function + * @param array $foreigners foreigners + * @param int $tabindex_for_null tab index offset for null + * @param int $tabindex_for_value tab index offset for value + * @param string $table table + * @param string $db database + * @param int $row_id row id + * @param array $titles titles + * @param int $biggest_max_file_size biggest max file size + * @param string $text_dir text direction + * @param array $repopulate the data to be repopulated + * @param array $where_clause_array the array of where clauses + * + * @return string + */ + public function getHtmlForInsertEditRow( + array $url_params, + array $table_columns, + array $comments_map, + $timestamp_seen, + $current_result, + $chg_evt_handler, + $jsvkey, + $vkey, + $insert_mode, + array $current_row, + &$o_rows, + &$tabindex, + $columns_cnt, + $is_upload, + $tabindex_for_function, + array $foreigners, + $tabindex_for_null, + $tabindex_for_value, + $table, + $db, + $row_id, + array $titles, + $biggest_max_file_size, + $text_dir, + array $repopulate, + array $where_clause_array + ) { + $html_output = $this->getHeadAndFootOfInsertRowTable($url_params) + . ''; + + //store the default value for CharEditing + $default_char_editing = $GLOBALS['cfg']['CharEditing']; + $mime_map = Transformations::getMIME($db, $table); + $where_clause = ''; + if (isset($where_clause_array[$row_id])) { + $where_clause = $where_clause_array[$row_id]; + } + for ($column_number = 0; $column_number < $columns_cnt; $column_number++) { + $table_column = $table_columns[$column_number]; + $column_mime = array(); + if (isset($mime_map[$table_column['Field']])) { + $column_mime = $mime_map[$table_column['Field']]; + } + + $virtual = [ + 'VIRTUAL', + 'PERSISTENT', + 'VIRTUAL GENERATED', + 'STORED GENERATED', + ]; + if (! in_array($table_column['Extra'], $virtual)) { + $html_output .= $this->getHtmlForInsertEditFormColumn( + $table_columns, + $column_number, + $comments_map, + $timestamp_seen, + $current_result, + $chg_evt_handler, + $jsvkey, + $vkey, + $insert_mode, + $current_row, + $o_rows, + $tabindex, + $columns_cnt, + $is_upload, + $tabindex_for_function, + $foreigners, + $tabindex_for_null, + $tabindex_for_value, + $table, + $db, + $row_id, + $titles, + $biggest_max_file_size, + $default_char_editing, + $text_dir, + $repopulate, + $column_mime, + $where_clause + ); + } + } // end for + $o_rows++; + $html_output .= ' ' + . '
      ' . __('Column') . '' . __('Null') . '' . __('Value') . '
      ' + . '' + . '
      ' + . $column['Field_title'] + . '' + . '' + . '' . $column['pma_type'] . '' + . '

      ' + . '
      '; + + return $html_output; + } + + /** + * Returns whether the user has necessary insert/update privileges for the column + * + * @param array $table_column array of column details + * @param bool $insert_mode whether on insert mode + * + * @return boolean whether user has necessary privileges + */ + private function userHasColumnPrivileges(array $table_column, $insert_mode) + { + $privileges = $table_column['Privileges']; + return ($insert_mode && strstr($privileges, 'insert') !== false) + || (! $insert_mode && strstr($privileges, 'update') !== false); + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/IpAllowDeny.php b/php/apps/phpmyadmin49/html/libraries/classes/IpAllowDeny.php new file mode 100644 index 00000000..dd3ea382 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/IpAllowDeny.php @@ -0,0 +1,306 @@ + -1 + || mb_strpos($ipToTest, ':') > -1 + ) { + // assume IPv6 + $result = self::ipv6MaskTest($testRange, $ipToTest); + } else { + $result = self::ipv4MaskTest($testRange, $ipToTest); + } + + return $result; + } // end of the "self::ipMaskTest()" function + + /** + * Based on IP Pattern Matcher + * Originally by J.Adams + * Found on + * Modified for phpMyAdmin + * + * Matches: + * xxx.xxx.xxx.xxx (exact) + * xxx.xxx.xxx.[yyy-zzz] (range) + * xxx.xxx.xxx.xxx/nn (CIDR) + * + * Does not match: + * xxx.xxx.xxx.xx[yyy-zzz] (range, partial octets not supported) + * + * @param string $testRange string of IP range to match + * @param string $ipToTest string of IP to test against range + * + * @return boolean whether the IP mask matches + * + * @access public + */ + public static function ipv4MaskTest($testRange, $ipToTest) + { + $result = true; + $match = preg_match( + '|([0-9]+)\.([0-9]+)\.([0-9]+)\.([0-9]+)/([0-9]+)|', + $testRange, + $regs + ); + if ($match) { + // performs a mask match + $ipl = ip2long($ipToTest); + $rangel = ip2long( + $regs[1] . '.' . $regs[2] . '.' . $regs[3] . '.' . $regs[4] + ); + + $maskl = 0; + + for ($i = 0; $i < 31; $i++) { + if ($i < $regs[5] - 1) { + $maskl = $maskl + pow(2, (30 - $i)); + } // end if + } // end for + + return ($maskl & $rangel) == ($maskl & $ipl); + } + + // range based + $maskocts = explode('.', $testRange); + $ipocts = explode('.', $ipToTest); + + // perform a range match + for ($i = 0; $i < 4; $i++) { + if (preg_match('|\[([0-9]+)\-([0-9]+)\]|', $maskocts[$i], $regs)) { + if (($ipocts[$i] > $regs[2]) || ($ipocts[$i] < $regs[1])) { + $result = false; + } // end if + } else { + if ($maskocts[$i] <> $ipocts[$i]) { + $result = false; + } // end if + } // end if/else + } //end for + + return $result; + } // end of the "self::ipv4MaskTest()" function + + /** + * IPv6 matcher + * CIDR section taken from https://stackoverflow.com/a/10086404 + * Modified for phpMyAdmin + * + * Matches: + * xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx + * (exact) + * xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:[yyyy-zzzz] + * (range, only at end of IP - no subnets) + * xxxx:xxxx:xxxx:xxxx/nn + * (CIDR) + * + * Does not match: + * xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xx[yyy-zzz] + * (range, partial octets not supported) + * + * @param string $test_range string of IP range to match + * @param string $ip_to_test string of IP to test against range + * + * @return boolean whether the IP mask matches + * + * @access public + */ + public static function ipv6MaskTest($test_range, $ip_to_test) + { + $result = true; + + // convert to lowercase for easier comparison + $test_range = mb_strtolower($test_range); + $ip_to_test = mb_strtolower($ip_to_test); + + $is_cidr = mb_strpos($test_range, '/') > -1; + $is_range = mb_strpos($test_range, '[') > -1; + $is_single = ! $is_cidr && ! $is_range; + + $ip_hex = bin2hex(inet_pton($ip_to_test)); + + if ($is_single) { + $range_hex = bin2hex(inet_pton($test_range)); + $result = hash_equals($ip_hex, $range_hex); + return $result; + } + + if ($is_range) { + // what range do we operate on? + $range_match = array(); + $match = preg_match( + '/\[([0-9a-f]+)\-([0-9a-f]+)\]/', $test_range, $range_match + ); + if ($match) { + $range_start = $range_match[1]; + $range_end = $range_match[2]; + + // get the first and last allowed IPs + $first_ip = str_replace($range_match[0], $range_start, $test_range); + $first_hex = bin2hex(inet_pton($first_ip)); + $last_ip = str_replace($range_match[0], $range_end, $test_range); + $last_hex = bin2hex(inet_pton($last_ip)); + + // check if the IP to test is within the range + $result = ($ip_hex >= $first_hex && $ip_hex <= $last_hex); + } + return $result; + } + + if ($is_cidr) { + // Split in address and prefix length + list($first_ip, $subnet) = explode('/', $test_range); + + // Parse the address into a binary string + $first_bin = inet_pton($first_ip); + $first_hex = bin2hex($first_bin); + + $flexbits = 128 - $subnet; + + // Build the hexadecimal string of the last address + $last_hex = $first_hex; + + $pos = 31; + while ($flexbits > 0) { + // Get the character at this position + $orig = mb_substr($last_hex, $pos, 1); + + // Convert it to an integer + $origval = hexdec($orig); + + // OR it with (2^flexbits)-1, with flexbits limited to 4 at a time + $newval = $origval | (pow(2, min(4, $flexbits)) - 1); + + // Convert it back to a hexadecimal character + $new = dechex($newval); + + // And put that character back in the string + $last_hex = substr_replace($last_hex, $new, $pos, 1); + + // We processed one nibble, move to previous position + $flexbits -= 4; + --$pos; + } + + // check if the IP to test is within the range + $result = ($ip_hex >= $first_hex && $ip_hex <= $last_hex); + } + + return $result; + } // end of the "self::ipv6MaskTest()" function + + /** + * Runs through IP Allow/Deny rules the use of it below for more information + * + * @param string $type 'allow' | 'deny' type of rule to match + * + * @return bool Whether rule has matched + * + * @access public + * + * @see Core::getIp() + */ + public static function allowDeny($type) + { + global $cfg; + + // Grabs true IP of the user and returns if it can't be found + $remote_ip = Core::getIp(); + if (empty($remote_ip)) { + return false; + } + + // copy username + $username = $cfg['Server']['user']; + + // copy rule database + if (isset($cfg['Server']['AllowDeny']['rules'])) { + $rules = $cfg['Server']['AllowDeny']['rules']; + if (! is_array($rules)) { + $rules = array(); + } + } else { + $rules = array(); + } + + // lookup table for some name shortcuts + $shortcuts = array( + 'all' => '0.0.0.0/0', + 'localhost' => '127.0.0.1/8' + ); + + // Provide some useful shortcuts if server gives us address: + if (Core::getenv('SERVER_ADDR')) { + $shortcuts['localnetA'] = Core::getenv('SERVER_ADDR') . '/8'; + $shortcuts['localnetB'] = Core::getenv('SERVER_ADDR') . '/16'; + $shortcuts['localnetC'] = Core::getenv('SERVER_ADDR') . '/24'; + } + + foreach ($rules as $rule) { + // extract rule data + $rule_data = explode(' ', $rule); + + // check for rule type + if ($rule_data[0] != $type) { + continue; + } + + // check for username + if (($rule_data[1] != '%') //wildcarded first + && (! hash_equals($rule_data[1], $username)) + ) { + continue; + } + + // check if the config file has the full string with an extra + // 'from' in it and if it does, just discard it + if ($rule_data[2] == 'from') { + $rule_data[2] = $rule_data[3]; + } + + // Handle shortcuts with above array + if (isset($shortcuts[$rule_data[2]])) { + $rule_data[2] = $shortcuts[$rule_data[2]]; + } + + // Add code for host lookups here + // Excluded for the moment + + // Do the actual matching now + if (self::ipMaskTest($rule_data[2], $remote_ip)) { + return true; + } + } // end while + + return false; + } // end of the "self::allowDeny()" function +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Language.php b/php/apps/phpmyadmin49/html/libraries/classes/Language.php new file mode 100644 index 00000000..36e65f75 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Language.php @@ -0,0 +1,202 @@ +code = $code; + $this->name = $name; + $this->native = $native; + if (strpos($regex, '[-_]') === false) { + $regex = str_replace('|', '([-_][[:alpha:]]{2,3})?|', $regex); + } + $this->regex = $regex; + $this->mysql = $mysql; + } + + /** + * Returns native name for language + * + * @return string + */ + public function getNativeName() + { + return $this->native; + } + + /** + * Returns English name for language + * + * @return string + */ + public function getEnglishName() + { + return $this->name; + } + + /** + * Returns verbose name for language + * + * @return string + */ + public function getName() + { + if (! empty($this->native)) { + return $this->native . ' - ' . $this->name; + } + + return $this->name; + } + + /** + * Returns language code + * + * @return string + */ + public function getCode() + { + return $this->code; + } + + /** + * Returns MySQL locale code, can be empty + * + * @return string + */ + public function getMySQLLocale() + { + return $this->mysql; + } + + /** + * Compare function used for sorting + * + * @param Language $other Other object to compare + * + * @return int same as strcmp + */ + public function cmp($other) + { + return strcmp($this->name, $other->name); + } + + /** + * Checks whether language is currently active. + * + * @return bool + */ + public function isActive() + { + return $GLOBALS['lang'] == $this->code; + } + + /** + * Checks whether language matches HTTP header Accept-Language. + * + * @param string $header Header content + * + * @return bool + */ + public function matchesAcceptLanguage($header) + { + $pattern = '/^(' + . addcslashes($this->regex, '/') + . ')(;q=[0-9]\\.[0-9])?$/i'; + return preg_match($pattern, $header); + } + + /** + * Checks whether language matches HTTP header User-Agent + * + * @param string $header Header content + * + * @return bool + */ + public function matchesUserAgent($header) + { + $pattern = '/(\(|\[|;[[:space:]])(' + . addcslashes($this->regex, '/') + . ')(;|\]|\))/i'; + return preg_match($pattern, $header); + } + + /** + * Checks whether language is RTL + * + * @return bool + */ + public function isRTL() + { + return in_array($this->code, array('ar', 'fa', 'he', 'ur')); + } + + /** + * Activates given translation + * + * @return bool + */ + public function activate() + { + $GLOBALS['lang'] = $this->code; + + // Set locale + _setlocale(0, $this->code); + _bindtextdomain('phpmyadmin', LOCALE_PATH); + _textdomain('phpmyadmin'); + // Set PHP locale as well + if (function_exists('setlocale')) { + setlocale(0, $this->code); + } + + /* Text direction for language */ + if ($this->isRTL()) { + $GLOBALS['text_dir'] = 'rtl'; + } else { + $GLOBALS['text_dir'] = 'ltr'; + } + + /* TCPDF */ + $GLOBALS['l'] = array(); + + /* TCPDF settings */ + $GLOBALS['l']['a_meta_charset'] = 'UTF-8'; + $GLOBALS['l']['a_meta_dir'] = $GLOBALS['text_dir']; + $GLOBALS['l']['a_meta_language'] = $this->code; + + /* TCPDF translations */ + $GLOBALS['l']['w_page'] = __('Page number:'); + + /* Show possible warnings from langauge selection */ + LanguageManager::getInstance()->showWarnings(); + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/LanguageManager.php b/php/apps/phpmyadmin49/html/libraries/classes/LanguageManager.php new file mode 100644 index 00000000..6e117df9 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/LanguageManager.php @@ -0,0 +1,966 @@ + array( + 'af', + 'Afrikaans', + '', + 'af|afrikaans', + '', + ), + 'ar' => array( + 'ar', + 'Arabic', + 'العربية', + 'ar|arabic', + 'ar_AE', + ), + 'az' => array( + 'az', + 'Azerbaijani', + 'Azərbaycanca', + 'az|azerbaijani', + '', + ), + 'bn' => array( + 'bn', + 'Bangla', + 'বাংলা', + 'bn|bangla', + '', + ), + 'be' => array( + 'be', + 'Belarusian', + 'Беларуская', + 'be|belarusian', + 'be_BY', + ), + 'be@latin' => array( + 'be@latin', + 'Belarusian (latin)', + 'Biełaruskaja', + 'be[-_]lat|be@latin|belarusian latin', + '', + ), + 'bg' => array( + 'bg', + 'Bulgarian', + 'Български', + 'bg|bulgarian', + 'bg_BG', + ), + 'bs' => array( + 'bs', + 'Bosnian', + 'Bosanski', + 'bs|bosnian', + '', + ), + 'br' => array( + 'br', + 'Breton', + 'Brezhoneg', + 'br|breton', + '', + ), + 'brx' => array( + 'brx', + 'Bodo', + 'बड़ो', + 'brx|bodo', + '', + ), + 'ca' => array( + 'ca', + 'Catalan', + 'Català', + 'ca|catalan', + 'ca_ES', + ), + 'ckb' => array( + 'ckb', + 'Sorani', + 'سۆرانی', + 'ckb|sorani', + '', + ), + 'cs' => array( + 'cs', + 'Czech', + 'Čeština', + 'cs|czech', + 'cs_CZ', + ), + 'cy' => array( + 'cy', + 'Welsh', + 'Cymraeg', + 'cy|welsh', + '', + ), + 'da' => array( + 'da', + 'Danish', + 'Dansk', + 'da|danish', + 'da_DK', + ), + 'de' => array( + 'de', + 'German', + 'Deutsch', + 'de|german', + 'de_DE', + ), + 'el' => array( + 'el', + 'Greek', + 'Ελληνικά', + 'el|greek', + '', + ), + 'en' => array( + 'en', + 'English', + '', + 'en|english', + 'en_US', + ), + 'en_gb' => array( + 'en_GB', + 'English (United Kingdom)', + '', + 'en[_-]gb|english (United Kingdom)', + 'en_GB', + ), + 'eo' => array( + 'eo', + 'Esperanto', + 'Esperanto', + 'eo|esperanto', + '', + ), + 'es' => array( + 'es', + 'Spanish', + 'Español', + 'es|spanish', + 'es_ES', + ), + 'et' => array( + 'et', + 'Estonian', + 'Eesti', + 'et|estonian', + 'et_EE', + ), + 'eu' => array( + 'eu', + 'Basque', + 'Euskara', + 'eu|basque', + 'eu_ES', + ), + 'fa' => array( + 'fa', + 'Persian', + 'فارسی', + 'fa|persian', + '', + ), + 'fi' => array( + 'fi', + 'Finnish', + 'Suomi', + 'fi|finnish', + 'fi_FI', + ), + 'fil' => array( + 'fil', + 'Filipino', + 'Pilipino', + 'fil|filipino', + '', + ), + 'fr' => array( + 'fr', + 'French', + 'Français', + 'fr|french', + 'fr_FR', + ), + 'fy' => array( + 'fy', + 'Frisian', + 'Frysk', + 'fy|frisian', + '', + ), + 'gl' => array( + 'gl', + 'Galician', + 'Galego', + 'gl|galician', + 'gl_ES', + ), + 'gu' => array( + 'gu', + 'Gujarati', + 'ગુજરાતી', + 'gu|gujarati', + 'gu_IN', + ), + 'he' => array( + 'he', + 'Hebrew', + 'עברית', + 'he|hebrew', + 'he_IL', + ), + 'hi' => array( + 'hi', + 'Hindi', + 'हिन्दी', + 'hi|hindi', + 'hi_IN', + ), + 'hr' => array( + 'hr', + 'Croatian', + 'Hrvatski', + 'hr|croatian', + 'hr_HR', + ), + 'hu' => array( + 'hu', + 'Hungarian', + 'Magyar', + 'hu|hungarian', + 'hu_HU', + ), + 'hy' => array( + 'hy', + 'Armenian', + 'Հայերէն', + 'hy|armenian', + '', + ), + 'ia' => array( + 'ia', + 'Interlingua', + '', + 'ia|interlingua', + '', + ), + 'id' => array( + 'id', + 'Indonesian', + 'Bahasa Indonesia', + 'id|indonesian', + 'id_ID', + ), + 'ig' => array( + 'ig', + 'Igbo', + 'Asụsụ Igbo', + 'ig|igbo', + '', + ), + 'it' => array( + 'it', + 'Italian', + 'Italiano', + 'it|italian', + 'it_IT', + ), + 'ja' => array( + 'ja', + 'Japanese', + '日本語', + 'ja|japanese', + 'ja_JP', + ), + 'ko' => array( + 'ko', + 'Korean', + '한국어', + 'ko|korean', + 'ko_KR', + ), + 'ka' => array( + 'ka', + 'Georgian', + 'ქართული', + 'ka|georgian', + '', + ), + 'kab' => array( + 'kab', + 'Kabylian', + 'Taqbaylit', + 'kab|kabylian', + '', + ), + 'kk' => array( + 'kk', + 'Kazakh', + 'Қазақ', + 'kk|kazakh', + '', + ), + 'km' => array( + 'km', + 'Khmer', + 'ខ្មែរ', + 'km|khmer', + '', + ), + 'kn' => array( + 'kn', + 'Kannada', + 'ಕನ್ನಡ', + 'kn|kannada', + '', + ), + 'ksh' => array( + 'ksh', + 'Colognian', + 'Kölsch', + 'ksh|colognian', + '', + ), + 'ku' => array( + 'ku', + 'Kurdish', + 'کوردی', + 'ku|kurdish', + '', + ), + 'ky' => array( + 'ky', + 'Kyrgyz', + 'Кыргызча', + 'ky|kyrgyz', + '', + ), + 'li' => array( + 'li', + 'Limburgish', + 'Lèmbörgs', + 'li|limburgish', + '', + ), + 'lt' => array( + 'lt', + 'Lithuanian', + 'Lietuvių', + 'lt|lithuanian', + 'lt_LT', + ), + 'lv' => array( + 'lv', + 'Latvian', + 'Latviešu', + 'lv|latvian', + 'lv_LV', + ), + 'mk' => array( + 'mk', + 'Macedonian', + 'Macedonian', + 'mk|macedonian', + 'mk_MK', + ), + 'ml' => array( + 'ml', + 'Malayalam', + 'Malayalam', + 'ml|malayalam', + '', + ), + 'mn' => array( + 'mn', + 'Mongolian', + 'Монгол', + 'mn|mongolian', + 'mn_MN', + ), + 'ms' => array( + 'ms', + 'Malay', + 'Bahasa Melayu', + 'ms|malay', + 'ms_MY', + ), + 'my' => array( + 'my', + 'Burmese', + 'မြန်မာ', + 'my|burmese', + '', + ), + 'ne' => array( + 'ne', + 'Nepali', + 'नेपाली', + 'ne|nepali', + '', + ), + 'nb' => array( + 'nb', + 'Norwegian', + 'Norsk', + 'nb|norwegian', + 'nb_NO', + ), + 'nn' => array( + 'nn', + 'Norwegian Nynorsk', + 'Nynorsk', + 'nn|nynorsk', + 'nn_NO', + ), + 'nl' => array( + 'nl', + 'Dutch', + 'Nederlands', + 'nl|dutch', + 'nl_NL', + ), + 'pa' => array( + 'pa', + 'Punjabi', + 'ਪੰਜਾਬੀ', + 'pa|punjabi', + '', + ), + 'pl' => array( + 'pl', + 'Polish', + 'Polski', + 'pl|polish', + 'pl_PL', + ), + 'pt_br' => array( + 'pt_BR', + 'Brazilian Portuguese', + 'Português', + 'pt[-_]br|brazilian portuguese', + 'pt_BR', + ), + 'pt' => array( + 'pt', + 'Portuguese', + 'Português', + 'pt|portuguese', + 'pt_PT', + ), + 'ro' => array( + 'ro', + 'Romanian', + 'Română', + 'ro|romanian', + 'ro_RO', + ), + 'ru' => array( + 'ru', + 'Russian', + 'Русский', + 'ru|russian', + 'ru_RU', + ), + 'si' => array( + 'si', + 'Sinhala', + 'සිංහල', + 'si|sinhala', + '', + ), + 'sk' => array( + 'sk', + 'Slovak', + 'Slovenčina', + 'sk|slovak', + 'sk_SK', + ), + 'sl' => array( + 'sl', + 'Slovenian', + 'Slovenščina', + 'sl|slovenian', + 'sl_SI', + ), + 'sq' => array( + 'sq', + 'Albanian', + 'Shqip', + 'sq|albanian', + 'sq_AL', + ), + 'sr@latin' => array( + 'sr@latin', + 'Serbian (latin)', + 'Srpski', + 'sr[-_]lat|sr@latin|serbian latin', + 'sr_YU', + ), + 'sr' => array( + 'sr', + 'Serbian', + 'Српски', + 'sr|serbian', + 'sr_YU', + ), + 'sv' => array( + 'sv', + 'Swedish', + 'Svenska', + 'sv|swedish', + 'sv_SE', + ), + 'ta' => array( + 'ta', + 'Tamil', + 'தமிழ்', + 'ta|tamil', + 'ta_IN', + ), + 'te' => array( + 'te', + 'Telugu', + 'తెలుగు', + 'te|telugu', + 'te_IN', + ), + 'th' => array( + 'th', + 'Thai', + 'ภาษาไทย', + 'th|thai', + 'th_TH', + ), + 'tk' => array( + 'tk', + 'Turkmen', + 'Türkmençe', + 'tk|turkmen', + '', + ), + 'tr' => array( + 'tr', + 'Turkish', + 'Türkçe', + 'tr|turkish', + 'tr_TR', + ), + 'tt' => array( + 'tt', + 'Tatarish', + 'Tatarça', + 'tt|tatarish', + '', + ), + 'ug' => array( + 'ug', + 'Uyghur', + 'ئۇيغۇرچە', + 'ug|uyghur', + '', + ), + 'uk' => array( + 'uk', + 'Ukrainian', + 'Українська', + 'uk|ukrainian', + 'uk_UA', + ), + 'ur' => array( + 'ur', + 'Urdu', + 'اُردوُ', + 'ur|urdu', + 'ur_PK', + ), + 'uz@latin' => array( + 'uz@latin', + 'Uzbek (latin)', + 'O‘zbekcha', + 'uz[-_]lat|uz@latin|uzbek-latin', + '', + ), + 'uz' => array( + 'uz', + 'Uzbek (cyrillic)', + 'Ўзбекча', + 'uz[-_]cyr|uz@cyrillic|uzbek-cyrillic', + '', + ), + 'vi' => array( + 'vi', + 'Vietnamese', + 'Tiếng Việt', + 'vi|vietnamese', + 'vi_VN', + ), + 'vls' => array( + 'vls', + 'Flemish', + 'West-Vlams', + 'vls|flemish', + '', + ), + 'zh_tw' => array( + 'zh_TW', + 'Chinese traditional', + '中文', + 'zh[-_](tw|hk)|chinese traditional', + 'zh_TW', + ), + // only TW and HK use traditional Chinese while others (CN, SG, MY) + // use simplified Chinese + 'zh_cn' => array( + 'zh_CN', + 'Chinese simplified', + '中文', + 'zh(?![-_](tw|hk))([-_][[:alpha:]]{2,3})?|chinese simplified', + 'zh_CN', + ), + ); + + private $_available_locales; + private $_available_languages; + private $_lang_failed_cfg; + private $_lang_failed_cookie; + private $_lang_failed_request; + private static $instance; + + /** + * Returns LanguageManager singleton + * + * @return LanguageManager + */ + public static function getInstance() + { + if (self::$instance === null) { + self::$instance = new LanguageManager; + } + return self::$instance; + } + + /** + * Returns list of available locales + * + * @return array + */ + public function listLocaleDir() + { + $result = array('en'); + + /* Check for existing directory */ + if (!is_dir(LOCALE_PATH)) { + return $result; + } + + /* Open the directory */ + $handle = @opendir(LOCALE_PATH); + /* This can happen if the kit is English-only */ + if ($handle === false) { + return $result; + } + + /* Process all files */ + while (false !== ($file = readdir($handle))) { + $path = LOCALE_PATH + . '/' . $file + . '/LC_MESSAGES/phpmyadmin.mo'; + if ($file != "." + && $file != ".." + && @file_exists($path) + ) { + $result[] = $file; + } + } + /* Close the handle */ + closedir($handle); + + return $result; + } + + /** + * Returns (cached) list of all available locales + * + * @return array of strings + */ + public function availableLocales() + { + if (! $this->_available_locales) { + + if (! isset($GLOBALS['PMA_Config']) || empty($GLOBALS['PMA_Config']->get('FilterLanguages'))) { + $this->_available_locales = $this->listLocaleDir(); + } else { + $this->_available_locales = preg_grep( + '@' . $GLOBALS['PMA_Config']->get('FilterLanguages') . '@', + $this->listLocaleDir() + ); + } + } + return $this->_available_locales; + } + + /** + * Checks whether there are some languages available + * + * @return boolean + */ + public function hasChoice() + { + return count($this->availableLanguages()) > 1; + } + + /** + * Returns (cached) list of all available languages + * + * @return array of Language objects + */ + public function availableLanguages() + { + if (! $this->_available_languages) { + $this->_available_languages = array(); + + foreach($this->availableLocales() as $lang) { + $lang = strtolower($lang); + if (isset($this::$_language_data[$lang])) { + $data = $this::$_language_data[$lang]; + $this->_available_languages[$lang] = new Language( + $data[0], + $data[1], + $data[2], + $data[3], + $data[4] + ); + } else { + $this->_available_languages[$lang] = new Language( + $lang, + ucfirst($lang), + ucfirst($lang), + $lang, + '' + ); + } + } + } + return $this->_available_languages; + } + + /** + * Returns (cached) list of all available languages sorted + * by name + * + * @return array of Language objects + */ + public function sortedLanguages() + { + $this->availableLanguages(); + uasort($this->_available_languages, function($a, $b) + { + return $a->cmp($b); + } + ); + return $this->_available_languages; + } + + /** + * Return Language object for given code + * + * @param string $code Language code + * + * @return object|false Language object or false on failure + */ + public function getLanguage($code) + { + $code = strtolower($code); + $langs = $this->availableLanguages(); + if (isset($langs[$code])) { + return $langs[$code]; + } + return false; + } + + /** + * Return currently active Language object + * + * @return object Language object + */ + public function getCurrentLanguage() + { + return $this->_available_languages[strtolower($GLOBALS['lang'])]; + } + + /** + * Activates language based on configuration, user preferences or + * browser + * + * @return Language + */ + public function selectLanguage() + { + // check forced language + if (! empty($GLOBALS['PMA_Config']->get('Lang'))) { + $lang = $this->getLanguage($GLOBALS['PMA_Config']->get('Lang')); + if ($lang !== false) { + return $lang; + } + $this->_lang_failed_cfg = true; + } + + // Don't use REQUEST in following code as it might be confused by cookies + // with same name. Check user requested language (POST) + if (! empty($_POST['lang'])) { + $lang = $this->getLanguage($_POST['lang']); + if ($lang !== false) { + return $lang; + } + $this->_lang_failed_request = true; + } + + // check user requested language (GET) + if (! empty($_GET['lang'])) { + $lang = $this->getLanguage($_GET['lang']); + if ($lang !== false) { + return $lang; + } + $this->_lang_failed_request = true; + } + + // check previous set language + if (! empty($_COOKIE['pma_lang'])) { + $lang = $this->getLanguage($_COOKIE['pma_lang']); + if ($lang !== false) { + return $lang; + } + $this->_lang_failed_cookie = true; + } + + $langs = $this->availableLanguages(); + + // try to find out user's language by checking its HTTP_ACCEPT_LANGUAGE variable; + $accepted_languages = Core::getenv('HTTP_ACCEPT_LANGUAGE'); + if ($accepted_languages) { + foreach (explode(',', $accepted_languages) as $header) { + foreach ($langs as $language) { + if ($language->matchesAcceptLanguage($header)) { + return $language; + } + } + } + } + + // try to find out user's language by checking its HTTP_USER_AGENT variable + $user_agent = Core::getenv('HTTP_USER_AGENT'); + if (! empty($user_agent)) { + foreach ($langs as $language) { + if ($language->matchesUserAgent($user_agent)) { + return $language; + } + } + } + + // Didn't catch any valid lang : we use the default settings + if (isset($langs[$GLOBALS['PMA_Config']->get('DefaultLang')])) { + return $langs[$GLOBALS['PMA_Config']->get('DefaultLang')]; + } + + // Fallback to English + return $langs['en']; + } + + /** + * Displays warnings about invalid languages. This needs to be postponed + * to show messages at time when language is initialized. + * + * @return void + */ + public function showWarnings() + { + // now, that we have loaded the language strings we can send the errors + if ($this->_lang_failed_cfg + || $this->_lang_failed_cookie + || $this->_lang_failed_request + ) { + trigger_error( + __('Ignoring unsupported language code.'), + E_USER_ERROR + ); + } + } + + + /** + * Returns HTML code for the language selector + * + * @param boolean $use_fieldset whether to use fieldset for selection + * @param boolean $show_doc whether to show documentation links + * + * @return string + * + * @access public + */ + public function getSelectorDisplay($use_fieldset = false, $show_doc = true) + { + $_form_params = array( + 'db' => $GLOBALS['db'], + 'table' => $GLOBALS['table'], + ); + + // For non-English, display "Language" with emphasis because it's + // not a proper word in the current language; we show it to help + // people recognize the dialog + $language_title = __('Language') + . (__('Language') != 'Language' ? ' - Language' : ''); + if ($show_doc) { + $language_title .= Util::showDocu('faq', 'faq7-2'); + } + + $available_languages = $this->sortedLanguages(); + + return Template::get('select_lang')->render( + array( + 'language_title' => $language_title, + 'use_fieldset' => $use_fieldset, + 'available_languages' => $available_languages, + '_form_params' => $_form_params, + ) + ); + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Linter.php b/php/apps/phpmyadmin49/html/libraries/classes/Linter.php new file mode 100644 index 00000000..53dd207b --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Linter.php @@ -0,0 +1,179 @@ +length() : strlen($str); + + $lines = array(0); + for ($i = 0; $i < $len; ++$i) { + if ($str[$i] === "\n") { + $lines[] = $i + 1; + } + } + return $lines; + } + + /** + * Computes the number of the line and column given an absolute position. + * + * @param array $lines The starting position of each line. + * @param int $pos The absolute position + * + * @return array + */ + public static function findLineNumberAndColumn(array $lines, $pos) + { + $line = 0; + foreach ($lines as $lineNo => $lineStart) { + if ($lineStart > $pos) { + break; + } + $line = $lineNo; + } + return array($line, $pos - $lines[$line]); + } + + /** + * Runs the linting process. + * + * @param string $query The query to be checked. + * + * @return array + */ + public static function lint($query) + { + // Disabling lint for huge queries to save some resources. + if (mb_strlen($query) > 10000) { + return array( + array( + 'message' => __( + 'Linting is disabled for this query because it exceeds the ' + . 'maximum length.' + ), + 'fromLine' => 0, + 'fromColumn' => 0, + 'toLine' => 0, + 'toColumn' => 0, + 'severity' => 'warning', + ) + ); + } + + /** + * Lexer used for tokenizing the query. + * + * @var Lexer + */ + $lexer = new Lexer($query); + + /** + * Parsed used for analysing the query. + * + * @var Parser + */ + $parser = new Parser($lexer->list); + + /** + * Array containing all errors. + * + * @var array + */ + $errors = ParserError::get(array($lexer, $parser)); + + /** + * The response containing of all errors. + * + * @var array + */ + $response = array(); + + /** + * The starting position for each line. + * + * CodeMirror requires relative position to line, but the parser stores + * only the absolute position of the character in string. + * + * @var array + */ + $lines = static::getLines($query); + + // Building the response. + foreach ($errors as $idx => $error) { + + // Starting position of the string that caused the error. + list($fromLine, $fromColumn) = static::findLineNumberAndColumn( + $lines, $error[3] + ); + + // Ending position of the string that caused the error. + list($toLine, $toColumn) = static::findLineNumberAndColumn( + $lines, $error[3] + mb_strlen($error[2]) + ); + + // Building the response. + $response[] = array( + 'message' => sprintf( + __('%1$s (near %2$s)'), + htmlspecialchars($error[0]), htmlspecialchars($error[2]) + ), + 'fromLine' => $fromLine, + 'fromColumn' => $fromColumn, + 'toLine' => $toLine, + 'toColumn' => $toColumn, + 'severity' => 'error', + ); + } + + // Sending back the answer. + return $response; + } + +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/ListAbstract.php b/php/apps/phpmyadmin49/html/libraries/classes/ListAbstract.php new file mode 100644 index 00000000..9e28490e --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/ListAbstract.php @@ -0,0 +1,121 @@ +item_empty; + } + + /** + * checks if the given db names exists in the current list, if there is + * missing at least one item it returns false otherwise true + * + * @return boolean true if all items exists, otherwise false + */ + public function exists() + { + $this_elements = $this->getArrayCopy(); + foreach (func_get_args() as $result) { + if (! in_array($result, $this_elements)) { + return false; + } + } + + return true; + } + + /** + * returns HTML '; + return $html; + } + + /** + * Gets HTML for replace_prefix_tbl or copy_tbl_change_prefix + * + * @param string $action action type + * @param array $urlParams URL params + * + * @return string + */ + public function getHtmlForReplacePrefixTable($action, array $urlParams) + { + $html = '
      '; + $html .= Url::getHiddenInputs($urlParams); + $html .= '
      '; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= '
      ' . __('From') . ''; + $html .= ''; + $html .= '
      ' . __('To') . ''; + $html .= ''; + $html .= '
      '; + $html .= '
      '; + $html .= ''; + $html .= '
      '; + + return $html; + } + + /** + * Gets HTML for add_prefix_tbl + * + * @param string $action action type + * @param array $urlParams URL params + * + * @return string + */ + public function getHtmlForAddPrefixTable($action, array $urlParams) + { + $html = '
      '; + $html .= Url::getHiddenInputs($urlParams); + $html .= '
      '; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= '
      ' . __('Add prefix') . ''; + $html .= ''; + $html .= '
      '; + $html .= '
      '; + $html .= ''; + $html .= '
      '; + + return $html; + } + + /** + * Gets HTML for other mult_submits actions + * + * @param string $what mult_submit type + * @param string $action action type + * @param array $urlParams URL params + * @param string $fullQuery full sql query string + * + * @return string + */ + public function getHtmlForOtherActions($what, $action, array $urlParams, $fullQuery) + { + $html = '
      '; + $html .= Url::getHiddenInputs($urlParams); + $html .= '
      '; + $html .= ''; + if ($what == 'drop_db') { + $html .= __('You are about to DESTROY a complete database!') . ' '; + } + $html .= __('Do you really want to execute the following query?'); + $html .= ''; + $html .= '' . $fullQuery . ''; + $html .= '
      '; + $html .= '
      '; + // Display option to disable foreign key checks while dropping tables + if ($what === 'drop_tbl' || $what === 'empty_tbl' || $what === 'row_delete') { + $html .= '
      '; + $html .= Util::getFKCheckbox(); + $html .= '
      '; + } + $html .= ''; + $html .= ''; + $html .= '
      '; + $html .= '
      '; + + return $html; + } + + /** + * Get query string from Selected + * + * @param string $what mult_submit type + * @param string $table table name + * @param array $selected the selected columns + * @param array $views table views + * + * @return array + */ + public function getQueryFromSelected($what, $table, array $selected, array $views) + { + $reload = false; + $fullQueryViews = null; + $fullQuery = ''; + + if ($what == 'drop_tbl') { + $fullQueryViews = ''; + } + + $selectedCount = count($selected); + $i = 0; + foreach ($selected as $selectedValue) { + switch ($what) { + case 'row_delete': + $fullQuery .= 'DELETE FROM ' + . Util::backquote(htmlspecialchars($table)) + // Do not append a "LIMIT 1" clause here + // (it's not binlog friendly). + // We don't need the clause because the calling panel permits + // this feature only when there is a unique index. + . ' WHERE ' . htmlspecialchars($selectedValue) + . ';
      '; + break; + case 'drop_db': + $fullQuery .= 'DROP DATABASE ' + . Util::backquote(htmlspecialchars($selectedValue)) + . ';
      '; + $reload = true; + break; + + case 'drop_tbl': + $current = $selectedValue; + if (!empty($views) && in_array($current, $views)) { + $fullQueryViews .= (empty($fullQueryViews) ? 'DROP VIEW ' : ', ') + . Util::backquote(htmlspecialchars($current)); + } else { + $fullQuery .= (empty($fullQuery) ? 'DROP TABLE ' : ', ') + . Util::backquote(htmlspecialchars($current)); + } + break; + + case 'empty_tbl': + $fullQuery .= 'TRUNCATE '; + $fullQuery .= Util::backquote(htmlspecialchars($selectedValue)) + . ';
      '; + break; + + case 'primary_fld': + if ($fullQuery == '') { + $fullQuery .= 'ALTER TABLE ' + . Util::backquote(htmlspecialchars($table)) + . '
        DROP PRIMARY KEY,' + . '
         ADD PRIMARY KEY(' + . '
           ' + . Util::backquote(htmlspecialchars($selectedValue)) + . ','; + } else { + $fullQuery .= '
           ' + . Util::backquote(htmlspecialchars($selectedValue)) + . ','; + } + if ($i == $selectedCount - 1) { + $fullQuery = preg_replace('@,$@', ');
      ', $fullQuery); + } + break; + + case 'drop_fld': + if ($fullQuery == '') { + $fullQuery .= 'ALTER TABLE ' + . Util::backquote(htmlspecialchars($table)); + } + $fullQuery .= '
        DROP ' + . Util::backquote(htmlspecialchars($selectedValue)) + . ','; + if ($i == $selectedCount - 1) { + $fullQuery = preg_replace('@,$@', ';
      ', $fullQuery); + } + break; + } // end switch + $i++; + } + + if ($what == 'drop_tbl') { + if (!empty($fullQuery)) { + $fullQuery .= ';
      ' . "\n"; + } + if (!empty($fullQueryViews)) { + $fullQuery .= $fullQueryViews . ';
      ' . "\n"; + } + unset($fullQueryViews); + } + + $fullQueryViews = isset($fullQueryViews) ? $fullQueryViews : null; + + return [$fullQuery, $reload, $fullQueryViews]; + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Navigation/Navigation.php b/php/apps/phpmyadmin49/html/libraries/classes/Navigation/Navigation.php new file mode 100644 index 00000000..486bd2f7 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Navigation/Navigation.php @@ -0,0 +1,249 @@ +relation = new Relation(); + } + + /** + * Renders the navigation tree, or part of it + * + * @return string The navigation tree + */ + public function getDisplay() + { + /* Init */ + $retval = ''; + $response = Response::getInstance(); + if (! $response->isAjax()) { + $header = new NavigationHeader(); + $retval = $header->getDisplay(); + } + $tree = new NavigationTree(); + if (! $response->isAjax() + || ! empty($_POST['full']) + || ! empty($_POST['reload']) + ) { + if ($GLOBALS['cfg']['ShowDatabasesNavigationAsTree']) { + // provide database tree in navigation + $navRender = $tree->renderState(); + } else { + // provide legacy pre-4.0 navigation + $navRender = $tree->renderDbSelect(); + } + } else { + $navRender = $tree->renderPath(); + } + if (! $navRender) { + $retval .= Message::error( + __('An error has occurred while loading the navigation display') + )->getDisplay(); + } else { + $retval .= $navRender; + } + + if (! $response->isAjax()) { + // closes the tags that were opened by the navigation header + $retval .= '
      '; // pma_navigation_tree + $retval .= '
      '; + if (!defined('PMA_DISABLE_NAVI_SETTINGS')) { + $retval .= PageSettings::getNaviSettings(); + } + $retval .= '
      '; //pma_navi_settings_container + $retval .= '
      '; // pma_navigation_content + $retval .= $this->_getDropHandler(); + $retval .= '
      '; // pma_navigation + } + + return $retval; + } + + /** + * Add an item of navigation tree to the hidden items list in PMA database. + * + * @param string $itemName name of the navigation tree item + * @param string $itemType type of the navigation tree item + * @param string $dbName database name + * @param string $tableName table name if applicable + * + * @return void + */ + public function hideNavigationItem( + $itemName, $itemType, $dbName, $tableName = null + ) { + $navTable = Util::backquote($GLOBALS['cfgRelation']['db']) + . "." . Util::backquote($GLOBALS['cfgRelation']['navigationhiding']); + $sqlQuery = "INSERT INTO " . $navTable + . "(`username`, `item_name`, `item_type`, `db_name`, `table_name`)" + . " VALUES (" + . "'" . $GLOBALS['dbi']->escapeString($GLOBALS['cfg']['Server']['user']) . "'," + . "'" . $GLOBALS['dbi']->escapeString($itemName) . "'," + . "'" . $GLOBALS['dbi']->escapeString($itemType) . "'," + . "'" . $GLOBALS['dbi']->escapeString($dbName) . "'," + . "'" . (! empty($tableName)? $GLOBALS['dbi']->escapeString($tableName) : "" ) + . "')"; + $this->relation->queryAsControlUser($sqlQuery, false); + } + + /** + * Inserts Drag and Drop Import handler + * + * @return string html code for drop handler + */ + private function _getDropHandler() + { + $retval = ''; + $retval .= '
      ' + . __('Drop files here') + . '
      '; + $retval .= '
      '; + $retval .= '

      SQL upload ( '; + $retval .= '0 '; + $retval .= ') x'; + $retval .= '-

      '; + $retval .= '
      '; + $retval .= '
      '; + return $retval; + } + + /** + * Remove a hidden item of navigation tree from the + * list of hidden items in PMA database. + * + * @param string $itemName name of the navigation tree item + * @param string $itemType type of the navigation tree item + * @param string $dbName database name + * @param string $tableName table name if applicable + * + * @return void + */ + public function unhideNavigationItem( + $itemName, $itemType, $dbName, $tableName = null + ) { + $navTable = Util::backquote($GLOBALS['cfgRelation']['db']) + . "." . Util::backquote($GLOBALS['cfgRelation']['navigationhiding']); + $sqlQuery = "DELETE FROM " . $navTable + . " WHERE" + . " `username`='" + . $GLOBALS['dbi']->escapeString($GLOBALS['cfg']['Server']['user']) . "'" + . " AND `item_name`='" . $GLOBALS['dbi']->escapeString($itemName) . "'" + . " AND `item_type`='" . $GLOBALS['dbi']->escapeString($itemType) . "'" + . " AND `db_name`='" . $GLOBALS['dbi']->escapeString($dbName) . "'" + . (! empty($tableName) + ? " AND `table_name`='" . $GLOBALS['dbi']->escapeString($tableName) . "'" + : "" + ); + $this->relation->queryAsControlUser($sqlQuery, false); + } + + /** + * Returns HTML for the dialog to show hidden navigation items. + * + * @param string $dbName database name + * @param string $itemType type of the items to include + * @param string $tableName table name + * + * @return string HTML for the dialog to show hidden navigation items + */ + public function getItemUnhideDialog($dbName, $itemType = null, $tableName = null) + { + $html = '
      '; + $html .= '
      '; + $html .= Url::getHiddenInputs($dbName, $tableName); + + $navTable = Util::backquote($GLOBALS['cfgRelation']['db']) + . "." . Util::backquote($GLOBALS['cfgRelation']['navigationhiding']); + $sqlQuery = "SELECT `item_name`, `item_type` FROM " . $navTable + . " WHERE `username`='" + . $GLOBALS['dbi']->escapeString($GLOBALS['cfg']['Server']['user']) . "'" + . " AND `db_name`='" . $GLOBALS['dbi']->escapeString($dbName) . "'" + . " AND `table_name`='" + . (! empty($tableName) ? $GLOBALS['dbi']->escapeString($tableName) : '') . "'"; + $result = $this->relation->queryAsControlUser($sqlQuery, false); + + $hidden = array(); + if ($result) { + while ($row = $GLOBALS['dbi']->fetchArray($result)) { + $type = $row['item_type']; + if (! isset($hidden[$type])) { + $hidden[$type] = array(); + } + $hidden[$type][] = $row['item_name']; + } + } + $GLOBALS['dbi']->freeResult($result); + + $typeMap = array( + 'group' => __('Groups:'), + 'event' => __('Events:'), + 'function' => __('Functions:'), + 'procedure' => __('Procedures:'), + 'table' => __('Tables:'), + 'view' => __('Views:'), + ); + if (empty($tableName)) { + $first = true; + foreach ($typeMap as $t => $lable) { + if ((empty($itemType) || $itemType == $t) + && isset($hidden[$t]) + ) { + $html .= (! $first ? '
      ' : '') + . '' . $lable . ''; + $html .= ''; + foreach ($hidden[$t] as $hiddenItem) { + $params = array( + 'unhideNavItem' => true, + 'itemType' => $t, + 'itemName' => $hiddenItem, + 'dbName' => $dbName + ); + + $html .= ''; + $html .= ''; + $html .= ''; + } + $html .= '
      ' . htmlspecialchars($hiddenItem) . '' + . Util::getIcon('show', __('Show')) + . '
      '; + $first = false; + } + } + } + + $html .= '
      '; + $html .= '
      '; + return $html; + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Navigation/NavigationHeader.php b/php/apps/phpmyadmin49/html/libraries/classes/Navigation/NavigationHeader.php new file mode 100644 index 00000000..b0355250 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Navigation/NavigationHeader.php @@ -0,0 +1,259 @@ + true, + ) + ); + $class = ' class="list_container'; + if ($GLOBALS['cfg']['NavigationLinkWithMainPanel']) { + $class .= ' synced'; + } + if ($GLOBALS['cfg']['NavigationTreePointerEnable']) { + $class .= ' highlight'; + } + $class .= '"'; + $buffer = '
      '; + $buffer .= '
      '; + $buffer .= '
      '; + $buffer .= '
      '; + $buffer .= '
      '; + $buffer .= sprintf( + '', + $link_url + ); + $buffer .= $this->_logo(); + $buffer .= $this->_links(); + $buffer .= $this->_serverChoice(); + $buffer .= Util::getImage( + 'ajax_clock_small', + __('Loading…'), + array( + 'style' => 'visibility: hidden; display:none', + 'class' => 'throbber', + ) + ); + $buffer .= '
      '; // pma_navigation_header + $buffer .= '
      '; + + return $buffer; + } + + /** + * Create the code for displaying the phpMyAdmin + * logo based on configuration settings + * + * @return string HTML code for the logo + */ + private function _logo() + { + $logo = 'phpMyAdmin'; + if (isset($GLOBALS['pmaThemeImage'])) { + $imgTag = ''; + if (@file_exists($GLOBALS['pmaThemeImage'] . 'logo_left.png')) { + $logo = sprintf($imgTag, $GLOBALS['pmaThemeImage'], 'logo_left.png'); + } elseif (@file_exists($GLOBALS['pmaThemeImage'] . 'pma_logo2.png')) { + $logo = sprintf($imgTag, $GLOBALS['pmaThemeImage'], 'pma_logo2.png'); + } + } + + // display Logo, depending on $GLOBALS['cfg']['NavigationDisplayLogo'] + if (!$GLOBALS['cfg']['NavigationDisplayLogo']) { + return Template::get('navigation/logo')->render([ + 'display_logo' => false, + 'use_logo_link' => false, + 'logo_link' => null, + 'link_attribs' => null, + 'logo' => $logo, + ]); + } + + if (!$GLOBALS['cfg']['NavigationLogoLink']) { + return Template::get('navigation/logo')->render([ + 'display_logo' => true, + 'use_logo_link' => false, + 'logo_link' => null, + 'link_attribs' => null, + 'logo' => $logo, + ]); + } + + $useLogoLink = true; + $linkAttriks = null; + $logoLink = trim( + htmlspecialchars($GLOBALS['cfg']['NavigationLogoLink']) + ); + // prevent XSS, see PMASA-2013-9 + // if link has protocol, allow only http and https + if (! Sanitize::checkLink($logoLink, true)) { + $logoLink = 'index.php'; + } + switch ($GLOBALS['cfg']['NavigationLogoLinkWindow']) { + case 'new': + $linkAttriks = 'target="_blank" rel="noopener noreferrer"'; + break; + case 'main': + // do not add our parameters for an external link + $host = parse_url( + $GLOBALS['cfg']['NavigationLogoLink'], + PHP_URL_HOST + ); + if (empty($host)) { + $hasStartChar = strpos($logoLink, '?'); + $logoLink .= Url::getCommon( + array(), + is_bool($hasStartChar) ? '?' : Url::getArgSeparator() + ); + } else { + $linkAttriks = 'target="_blank" rel="noopener noreferrer"'; + } + } + + return Template::get('navigation/logo')->render([ + 'display_logo' => true, + 'use_logo_link' => $useLogoLink, + 'logo_link' => $logoLink, + 'link_attribs' => $linkAttriks, + 'logo' => $logo, + ]); + } + + /** + * Creates the code for displaying the links + * at the top of the navigation panel + * + * @return string HTML code for the links + */ + private function _links() + { + // always iconic + $showIcon = true; + $showText = false; + + $retval = ''; + $retval .= ''; + $retval .= ''; + + return $retval; + } + + /** + * Displays the MySQL servers choice form + * + * @return string HTML code for the MySQL servers choice + */ + private function _serverChoice() + { + $retval = ''; + if ($GLOBALS['cfg']['NavigationDisplayServers'] + && count($GLOBALS['cfg']['Servers']) > 1 + ) { + $retval .= ''; + $retval .= '
      '; + $retval .= Select::render(true, true); + $retval .= '
      '; + $retval .= ''; + } + + return $retval; + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Navigation/NavigationTree.php b/php/apps/phpmyadmin49/html/libraries/classes/Navigation/NavigationTree.php new file mode 100644 index 00000000..0ff9726d --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Navigation/NavigationTree.php @@ -0,0 +1,1560 @@ +_pos = (int) $_POST['pos']; + } elseif (isset($_GET['pos'])) { + $this->_pos = (int) $_GET['pos']; + } + if (!isset($this->_pos)) { + $this->_pos = $this->_getNavigationDbPos(); + } + // Get the active node + if (isset($_REQUEST['aPath'])) { + $this->_aPath[0] = $this->_parsePath($_REQUEST['aPath']); + $this->_pos2_name[0] = $_REQUEST['pos2_name']; + $this->_pos2_value[0] = $_REQUEST['pos2_value']; + if (isset($_REQUEST['pos3_name'])) { + $this->_pos3_name[0] = $_REQUEST['pos3_name']; + $this->_pos3_value[0] = $_REQUEST['pos3_value']; + } + } else { + if (isset($_POST['n0_aPath'])) { + $count = 0; + while (isset($_POST['n' . $count . '_aPath'])) { + $this->_aPath[$count] = $this->_parsePath( + $_POST['n' . $count . '_aPath'] + ); + $index = 'n' . $count . '_pos2_'; + $this->_pos2_name[$count] = $_POST[$index . 'name']; + $this->_pos2_value[$count] = $_POST[$index . 'value']; + $index = 'n' . $count . '_pos3_'; + if (isset($_POST[$index])) { + $this->_pos3_name[$count] = $_POST[$index . 'name']; + $this->_pos3_value[$count] = $_POST[$index . 'value']; + } + $count++; + } + } + } + if (isset($_REQUEST['vPath'])) { + $this->_vPath[0] = $this->_parsePath($_REQUEST['vPath']); + } else { + if (isset($_POST['n0_vPath'])) { + $count = 0; + while (isset($_POST['n' . $count . '_vPath'])) { + $this->_vPath[$count] = $this->_parsePath( + $_POST['n' . $count . '_vPath'] + ); + $count++; + } + } + } + if (isset($_REQUEST['searchClause'])) { + $this->_searchClause = $_REQUEST['searchClause']; + } + if (isset($_REQUEST['searchClause2'])) { + $this->_searchClause2 = $_REQUEST['searchClause2']; + } + // Initialise the tree by creating a root node + $node = NodeFactory::getInstance('NodeDatabaseContainer', 'root'); + $this->_tree = $node; + if ($GLOBALS['cfg']['NavigationTreeEnableGrouping'] + && $GLOBALS['cfg']['ShowDatabasesNavigationAsTree'] + ) { + $this->_tree->separator = $GLOBALS['cfg']['NavigationTreeDbSeparator']; + $this->_tree->separator_depth = 10000; + } + } + + /** + * Returns the database position for the page selector + * + * @return int + */ + private function _getNavigationDbPos() + { + $retval = 0; + + if (strlen($GLOBALS['db']) == 0) { + return $retval; + } + + /* + * @todo describe a scenario where this code is executed + */ + if (!$GLOBALS['cfg']['Server']['DisableIS']) { + $dbSeparator = $GLOBALS['dbi']->escapeString( + $GLOBALS['cfg']['NavigationTreeDbSeparator'] + ); + $query = "SELECT (COUNT(DB_first_level) DIV %d) * %d "; + $query .= "from ( "; + $query .= " SELECT distinct SUBSTRING_INDEX(SCHEMA_NAME, "; + $query .= " '%s', 1) "; + $query .= " DB_first_level "; + $query .= " FROM INFORMATION_SCHEMA.SCHEMATA "; + $query .= " WHERE `SCHEMA_NAME` < '%s' "; + $query .= ") t "; + + $retval = $GLOBALS['dbi']->fetchValue( + sprintf( + $query, + (int)$GLOBALS['cfg']['FirstLevelNavigationItems'], + (int)$GLOBALS['cfg']['FirstLevelNavigationItems'], + $dbSeparator, + $GLOBALS['dbi']->escapeString($GLOBALS['db']) + ) + ); + + return $retval; + } + + $prefixMap = array(); + if ($GLOBALS['dbs_to_test'] === false) { + $handle = $GLOBALS['dbi']->tryQuery("SHOW DATABASES"); + if ($handle !== false) { + while ($arr = $GLOBALS['dbi']->fetchArray($handle)) { + if (strcasecmp($arr[0], $GLOBALS['db']) >= 0) { + break; + } + + $prefix = strstr( + $arr[0], + $GLOBALS['cfg']['NavigationTreeDbSeparator'], + true + ); + if ($prefix === false) { + $prefix = $arr[0]; + } + $prefixMap[$prefix] = 1; + } + } + } else { + $databases = array(); + foreach ($GLOBALS['dbs_to_test'] as $db) { + $query = "SHOW DATABASES LIKE '" . $db . "'"; + $handle = $GLOBALS['dbi']->tryQuery($query); + if ($handle === false) { + continue; + } + while ($arr = $GLOBALS['dbi']->fetchArray($handle)) { + $databases[] = $arr[0]; + } + } + sort($databases); + foreach ($databases as $database) { + if (strcasecmp($database, $GLOBALS['db']) >= 0) { + break; + } + + $prefix = strstr( + $database, + $GLOBALS['cfg']['NavigationTreeDbSeparator'], + true + ); + if ($prefix === false) { + $prefix = $database; + } + $prefixMap[$prefix] = 1; + } + } + + $navItems = (int)$GLOBALS['cfg']['FirstLevelNavigationItems']; + $retval = floor((count($prefixMap) / $navItems)) * $navItems; + + return $retval; + } + + /** + * Converts an encoded path to a node in string format to an array + * + * @param string $string The path to parse + * + * @return array + */ + private function _parsePath($string) + { + $path = explode('.', $string); + foreach ($path as $key => $value) { + $path[$key] = base64_decode($value); + } + + return $path; + } + + /** + * Generates the tree structure so that it can be rendered later + * + * @return Node|false The active node or false in case of failure + */ + private function _buildPath() + { + $retval = $this->_tree; + + // Add all databases unconditionally + $data = $this->_tree->getData( + 'databases', + $this->_pos, + $this->_searchClause + ); + $hiddenCounts = $this->_tree->getNavigationHidingData(); + foreach ($data as $db) { + $node = NodeFactory::getInstance('NodeDatabase', $db); + if (isset($hiddenCounts[$db])) { + $node->setHiddenCount($hiddenCounts[$db]); + } + $this->_tree->addChild($node); + } + + // Whether build other parts of the tree depends + // on whether we have any paths in $this->_aPath + foreach ($this->_aPath as $key => $path) { + $retval = $this->_buildPathPart( + $path, + $this->_pos2_name[$key], + $this->_pos2_value[$key], + isset($this->_pos3_name[$key]) ? $this->_pos3_name[$key] : '', + isset($this->_pos3_value[$key]) ? $this->_pos3_value[$key] : '' + ); + } + + return $retval; + } + + /** + * Builds a branch of the tree + * + * @param array $path A paths pointing to the branch + * of the tree that needs to be built + * @param string $type2 The type of item being paginated on + * the second level of the tree + * @param int $pos2 The position for the pagination of + * the branch at the second level of the tree + * @param string $type3 The type of item being paginated on + * the third level of the tree + * @param int $pos3 The position for the pagination of + * the branch at the third level of the tree + * + * @return Node|false The active node or false in case of failure + */ + private function _buildPathPart(array $path, $type2, $pos2, $type3, $pos3) + { + if (empty($pos2)) { + $pos2 = 0; + } + if (empty($pos3)) { + $pos3 = 0; + } + + $retval = true; + if (count($path) <= 1) { + return $retval; + } + + array_shift($path); // remove 'root' + /* @var $db NodeDatabase */ + $db = $this->_tree->getChild($path[0]); + $retval = $db; + + if ($db === false) { + return false; + } + + $containers = $this->_addDbContainers($db, $type2, $pos2); + + array_shift($path); // remove db + + if ((count($path) <= 0 || !array_key_exists($path[0], $containers)) + && count($containers) != 1 + ) { + return $retval; + } + + if (count($containers) == 1) { + $container = array_shift($containers); + } else { + $container = $db->getChild($path[0], true); + if ($container === false) { + return false; + } + } + $retval = $container; + + if (count($container->children) <= 1) { + $dbData = $db->getData( + $container->real_name, + $pos2, + $this->_searchClause2 + ); + foreach ($dbData as $item) { + switch ($container->real_name) { + case 'events': + $node = NodeFactory::getInstance( + 'NodeEvent', + $item + ); + break; + case 'functions': + $node = NodeFactory::getInstance( + 'NodeFunction', + $item + ); + break; + case 'procedures': + $node = NodeFactory::getInstance( + 'NodeProcedure', + $item + ); + break; + case 'tables': + $node = NodeFactory::getInstance( + 'NodeTable', + $item + ); + break; + case 'views': + $node = NodeFactory::getInstance( + 'NodeView', + $item + ); + break; + default: + break; + } + if (isset($node)) { + if ($type2 == $container->real_name) { + $node->pos2 = $pos2; + } + $container->addChild($node); + } + } + } + if (count($path) > 1 && $path[0] != 'tables') { + $retval = false; + + return $retval; + } + + array_shift($path); // remove container + if (count($path) <= 0) { + return $retval; + } + + /* @var $table NodeTable */ + $table = $container->getChild($path[0], true); + if ($table === false) { + if (!$db->getPresence('tables', $path[0])) { + return false; + } + + $node = NodeFactory::getInstance( + 'NodeTable', + $path[0] + ); + if ($type2 == $container->real_name) { + $node->pos2 = $pos2; + } + $container->addChild($node); + $table = $container->getChild($path[0], true); + } + $retval = $table; + $containers = $this->_addTableContainers( + $table, + $pos2, + $type3, + $pos3 + ); + array_shift($path); // remove table + if (count($path) <= 0 + || !array_key_exists($path[0], $containers) + ) { + return $retval; + } + + $container = $table->getChild($path[0], true); + $retval = $container; + $tableData = $table->getData( + $container->real_name, + $pos3 + ); + foreach ($tableData as $item) { + switch ($container->real_name) { + case 'indexes': + $node = NodeFactory::getInstance( + 'NodeIndex', + $item + ); + break; + case 'columns': + $node = NodeFactory::getInstance( + 'NodeColumn', + $item + ); + break; + case 'triggers': + $node = NodeFactory::getInstance( + 'NodeTrigger', + $item + ); + break; + default: + break; + } + if (isset($node)) { + $node->pos2 = $container->parent->pos2; + if ($type3 == $container->real_name) { + $node->pos3 = $pos3; + } + $container->addChild($node); + } + } + + return $retval; + } + + /** + * Adds containers to a node that is a table + * + * References to existing children are returned + * if this function is called twice on the same node + * + * @param NodeTable $table The table node, new containers will be + * attached to this node + * @param int $pos2 The position for the pagination of + * the branch at the second level of the tree + * @param string $type3 The type of item being paginated on + * the third level of the tree + * @param int $pos3 The position for the pagination of + * the branch at the third level of the tree + * + * @return array An array of new nodes + */ + private function _addTableContainers($table, $pos2, $type3, $pos3) + { + $retval = array(); + if ($table->hasChildren(true) == 0) { + if ($table->getPresence('columns')) { + $retval['columns'] = NodeFactory::getInstance( + 'NodeColumnContainer' + ); + } + if ($table->getPresence('indexes')) { + $retval['indexes'] = NodeFactory::getInstance( + 'NodeIndexContainer' + ); + } + if ($table->getPresence('triggers')) { + $retval['triggers'] = NodeFactory::getInstance( + 'NodeTriggerContainer' + ); + } + // Add all new Nodes to the tree + foreach ($retval as $node) { + $node->pos2 = $pos2; + if ($type3 == $node->real_name) { + $node->pos3 = $pos3; + } + $table->addChild($node); + } + } else { + foreach ($table->children as $node) { + if ($type3 == $node->real_name) { + $node->pos3 = $pos3; + } + $retval[$node->real_name] = $node; + } + } + + return $retval; + } + + /** + * Adds containers to a node that is a database + * + * References to existing children are returned + * if this function is called twice on the same node + * + * @param NodeDatabase $db The database node, new containers will be + * attached to this node + * @param string $type The type of item being paginated on + * the second level of the tree + * @param int $pos2 The position for the pagination of + * the branch at the second level of the tree + * + * @return array An array of new nodes + */ + private function _addDbContainers($db, $type, $pos2) + { + // Get items to hide + $hidden = $db->getHiddenItems('group'); + if (!$GLOBALS['cfg']['NavigationTreeShowTables'] + && !in_array('tables', $hidden) + ) { + $hidden[] = 'tables'; + } + if (!$GLOBALS['cfg']['NavigationTreeShowViews'] + && !in_array('views', $hidden) + ) { + $hidden[] = 'views'; + } + if (!$GLOBALS['cfg']['NavigationTreeShowFunctions'] + && !in_array('functions', $hidden) + ) { + $hidden[] = 'functions'; + } + if (!$GLOBALS['cfg']['NavigationTreeShowProcedures'] + && !in_array('procedures', $hidden) + ) { + $hidden[] = 'procedures'; + } + if (!$GLOBALS['cfg']['NavigationTreeShowEvents'] + && !in_array('events', $hidden) + ) { + $hidden[] = 'events'; + } + + $retval = array(); + if ($db->hasChildren(true) == 0) { + if (!in_array('tables', $hidden) && $db->getPresence('tables')) { + $retval['tables'] = NodeFactory::getInstance( + 'NodeTableContainer' + ); + } + if (!in_array('views', $hidden) && $db->getPresence('views')) { + $retval['views'] = NodeFactory::getInstance( + 'NodeViewContainer' + ); + } + if (!in_array('functions', $hidden) && $db->getPresence('functions')) { + $retval['functions'] = NodeFactory::getInstance( + 'NodeFunctionContainer' + ); + } + if (!in_array('procedures', $hidden) && $db->getPresence('procedures')) { + $retval['procedures'] = NodeFactory::getInstance( + 'NodeProcedureContainer' + ); + } + if (!in_array('events', $hidden) && $db->getPresence('events')) { + $retval['events'] = NodeFactory::getInstance( + 'NodeEventContainer' + ); + } + // Add all new Nodes to the tree + foreach ($retval as $node) { + if ($type == $node->real_name) { + $node->pos2 = $pos2; + } + $db->addChild($node); + } + } else { + foreach ($db->children as $node) { + if ($type == $node->real_name) { + $node->pos2 = $pos2; + } + $retval[$node->real_name] = $node; + } + } + + return $retval; + } + + /** + * Recursively groups tree nodes given a separator + * + * @param mixed $node The node to group or null + * to group the whole tree. If + * passed as an argument, $node + * must be of type CONTAINER + * + * @return void + */ + public function groupTree($node = null) + { + if (!isset($node)) { + $node = $this->_tree; + } + $this->groupNode($node); + foreach ($node->children as $child) { + $this->groupTree($child); + } + } + + /** + * Recursively groups tree nodes given a separator + * + * @param Node $node The node to group + * + * @return void + */ + public function groupNode($node) + { + if ($node->type != Node::CONTAINER + || !$GLOBALS['cfg']['NavigationTreeEnableExpansion'] + ) { + return; + } + + $separators = array(); + if (is_array($node->separator)) { + $separators = $node->separator; + } else { + if (strlen($node->separator)) { + $separators[] = $node->separator; + } + } + $prefixes = array(); + if ($node->separator_depth > 0) { + foreach ($node->children as $child) { + $prefix_pos = false; + foreach ($separators as $separator) { + $sep_pos = mb_strpos($child->name, $separator); + if ($sep_pos != false + && $sep_pos != mb_strlen($child->name) + && $sep_pos != 0 + && ($prefix_pos == false || $sep_pos < $prefix_pos) + ) { + $prefix_pos = $sep_pos; + } + } + if ($prefix_pos !== false) { + $prefix = mb_substr($child->name, 0, $prefix_pos); + if (!isset($prefixes[$prefix])) { + $prefixes[$prefix] = 1; + } else { + $prefixes[$prefix]++; + } + } + //Bug #4375: Check if prefix is the name of a DB, to create a group. + foreach ($node->children as $otherChild) { + if (array_key_exists($otherChild->name, $prefixes)) { + $prefixes[$otherChild->name]++; + } + } + } + //Check if prefix is the name of a DB, to create a group. + foreach ($node->children as $child) { + if (array_key_exists($child->name, $prefixes)) { + $prefixes[$child->name]++; + } + } + } + // It is not a group if it has only one item + foreach ($prefixes as $key => $value) { + if ($value == 1) { + unset($prefixes[$key]); + } + } + // rfe #1634 Don't group if there's only one group and no other items + if (count($prefixes) == 1) { + $keys = array_keys($prefixes); + $key = $keys[0]; + if ($prefixes[$key] == count($node->children) - 1) { + unset($prefixes[$key]); + } + } + if (count($prefixes)) { + /** @var Node[] $groups */ + $groups = array(); + foreach ($prefixes as $key => $value) { + + // warn about large groups + if ($value > 500 && !$this->_largeGroupWarning) { + trigger_error( + __( + 'There are large item groups in navigation panel which ' + . 'may affect the performance. Consider disabling item ' + . 'grouping in the navigation panel.' + ), + E_USER_WARNING + ); + $this->_largeGroupWarning = true; + } + + $groups[$key] = new Node( + htmlspecialchars($key), + Node::CONTAINER, + true + ); + $groups[$key]->separator = $node->separator; + $groups[$key]->separator_depth = $node->separator_depth - 1; + $groups[$key]->icon = Util::getImage( + 'b_group' + ); + $groups[$key]->pos2 = $node->pos2; + $groups[$key]->pos3 = $node->pos3; + if ($node instanceof NodeTableContainer + || $node instanceof NodeViewContainer + ) { + $tblGroup = '&tbl_group=' . urlencode($key); + $groups[$key]->links = array( + 'text' => $node->links['text'] . $tblGroup, + 'icon' => $node->links['icon'] . $tblGroup, + ); + } + $node->addChild($groups[$key]); + foreach ($separators as $separator) { + $separatorLength = strlen($separator); + // FIXME: this could be more efficient + foreach ($node->children as $child) { + $keySeparatorLength = mb_strlen($key) + $separatorLength; + $name_substring = mb_substr( + $child->name, + 0, + $keySeparatorLength + ); + if (($name_substring != $key . $separator + && $child->name != $key) + || $child->type != Node::OBJECT + ) { + continue; + } + $class = get_class($child); + $className = substr($class, strrpos($class, '\\') + 1); + unset($class); + $new_child = NodeFactory::getInstance( + $className, + mb_substr( + $child->name, + $keySeparatorLength + ) + ); + + if ($new_child instanceof NodeDatabase + && $child->getHiddenCount() > 0 + ) { + $new_child->setHiddenCount($child->getHiddenCount()); + } + + $new_child->real_name = $child->real_name; + $new_child->icon = $child->icon; + $new_child->links = $child->links; + $new_child->pos2 = $child->pos2; + $new_child->pos3 = $child->pos3; + $groups[$key]->addChild($new_child); + foreach ($child->children as $elm) { + $new_child->addChild($elm); + } + $node->removeChild($child->name); + } + } + } + foreach ($prefixes as $key => $value) { + $this->groupNode($groups[$key]); + $groups[$key]->classes = "navGroup"; + } + } + } + + /** + * Renders a state of the tree, used in light mode when + * either JavaScript and/or Ajax are disabled + * + * @return string HTML code for the navigation tree + */ + public function renderState() + { + $this->_buildPath(); + $retval = $this->_quickWarp(); + $retval .= '
      '; + $retval .= '
        '; + $retval .= $this->_fastFilterHtml($this->_tree); + if ($GLOBALS['cfg']['NavigationTreeEnableExpansion'] + ) { + $retval .= $this->_controls(); + } + $retval .= '
      '; + $retval .= $this->_getPageSelector($this->_tree); + $this->groupTree(); + $retval .= "
        "; + $children = $this->_tree->children; + usort( + $children, + array('PhpMyAdmin\\Navigation\\NavigationTree', 'sortNode') + ); + $this->_setVisibility(); + for ($i = 0, $nbChildren = count($children); $i < $nbChildren; $i++) { + if ($i == 0) { + $retval .= $this->_renderNode($children[0], true, 'first'); + } else { + if ($i + 1 != $nbChildren) { + $retval .= $this->_renderNode($children[$i], true); + } else { + $retval .= $this->_renderNode($children[$i], true, 'last'); + } + } + } + $retval .= "
      "; + + return $retval; + } + + /** + * Renders a part of the tree, used for Ajax + * requests in light mode + * + * @return string HTML code for the navigation tree + */ + public function renderPath() + { + $node = $this->_buildPath(); + if ($node === false) { + $retval = false; + } else { + $this->groupTree(); + $retval = "
      "; + if (!empty($this->_searchClause) || !empty($this->_searchClause2)) { + $retval .= "
        "; + } else { + $retval .= "
          "; + } + $listContent = $this->_fastFilterHtml($node); + $listContent .= $this->_getPageSelector($node); + $children = $node->children; + usort( + $children, + array('PhpMyAdmin\\Navigation\\NavigationTree', 'sortNode') + ); + for ($i = 0, $nbChildren = count($children); $i < $nbChildren; $i++) { + if ($i + 1 != $nbChildren) { + $listContent .= $this->_renderNode($children[$i], true); + } else { + $listContent .= $this->_renderNode($children[$i], true, 'last'); + } + } + $retval .= $listContent; + $retval .= "
        "; + if (!$GLOBALS['cfg']['ShowDatabasesNavigationAsTree']) { + $retval .= ""; + $parents = $node->parents(true); + $retval .= urlencode($parents[0]->real_name); + $retval .= ""; + if (empty($listContent)) { + $retval .= "
        "; + $retval .= __('No tables found in database.'); + $retval .= "
        "; + } + } + $retval .= "
      "; + } + + if (!empty($this->_searchClause) || !empty($this->_searchClause2)) { + $results = 0; + if (!empty($this->_searchClause2)) { + if (is_object($node->realParent())) { + $results = $node->realParent() + ->getPresence( + $node->real_name, + $this->_searchClause2 + ); + } + } else { + $results = $this->_tree->getPresence( + 'databases', + $this->_searchClause + ); + } + $results = sprintf( + _ngettext( + '%s result found', + '%s results found', + $results + ), + $results + ); + Response::getInstance() + ->addJSON( + 'results', + $results + ); + } + + return $retval; + } + + /** + * Renders the parameters that are required on the client + * side to know which page(s) we will be requesting data from + * + * @param Node $node The node to create the pagination parameters for + * + * @return string + */ + private function _getPaginationParamsHtml($node) + { + $retval = ''; + $paths = $node->getPaths(); + if (isset($paths['aPath_clean'][2])) { + $retval .= ""; + $retval .= $paths['aPath_clean'][2]; + $retval .= ""; + $retval .= ""; + $retval .= htmlspecialchars($node->pos2); + $retval .= ""; + } + if (isset($paths['aPath_clean'][4])) { + $retval .= ""; + $retval .= $paths['aPath_clean'][4]; + $retval .= ""; + $retval .= ""; + $retval .= htmlspecialchars($node->pos3); + $retval .= ""; + } + + return $retval; + } + + /** + * Finds whether given tree matches this tree. + * + * @param array $tree Tree to check + * @param array $paths Paths to check + * + * @return boolean + */ + private function _findTreeMatch(array $tree, array $paths) + { + $match = false; + foreach ($tree as $path) { + $match = true; + foreach ($paths as $key => $part) { + if (!isset($path[$key]) || $part != $path[$key]) { + $match = false; + break; + } + } + if ($match) { + break; + } + } + + return $match; + } + + /** + * Renders a single node or a branch of the tree + * + * @param Node $node The node to render + * @param bool $recursive Bool: Whether to render a single node or a branch + * @param string $class An additional class for the list item + * + * @return string HTML code for the tree node or branch + */ + private function _renderNode($node, $recursive, $class = '') + { + $retval = ''; + $paths = $node->getPaths(); + if ($node->hasSiblings() + || $node->realParent() === false + ) { + $response = Response::getInstance(); + if ($node->type == Node::CONTAINER + && count($node->children) == 0 + && ! $response->isAjax() + ) { + return ''; + } + $retval .= '
    • '; + $sterile = array( + 'events', + 'triggers', + 'functions', + 'procedures', + 'views', + 'columns', + 'indexes', + ); + $parentName = ''; + $parents = $node->parents(false, true); + if (count($parents)) { + $parentName = $parents[0]->real_name; + } + // if node name itself is in sterile, then allow + if ($node->is_group + || (!in_array($parentName, $sterile) && !$node->isNew) + || (in_array($node->real_name, $sterile)) + ) { + $retval .= "
      "; + $iClass = ''; + if ($class == 'first') { + $iClass = " class='first'"; + } + $retval .= ""; + if (strpos($class, 'last') === false) { + $retval .= ""; + } + + $match = $this->_findTreeMatch( + $this->_vPath, + $paths['vPath_clean'] + ); + + $retval .= '_pos; + $retval .= ""; + $retval .= $this->_getPaginationParamsHtml($node); + if ($GLOBALS['cfg']['ShowDatabasesNavigationAsTree'] + || $parentName != 'root' + ) { + $retval .= $node->getIcon($match); + } + + $retval .= ""; + $retval .= "
      "; + } else { + $retval .= "
      "; + $iClass = ''; + if ($class == 'first') { + $iClass = " class='first'"; + } + $retval .= ""; + $retval .= $this->_getPaginationParamsHtml($node); + $retval .= "
      "; + } + + $linkClass = ''; + $haveAjax = array( + 'functions', + 'procedures', + 'events', + 'triggers', + 'indexes', + ); + $parent = $node->parents(false, true); + $isNewView = $parent[0]->real_name == 'views' && $node->isNew === true; + if ($parent[0]->type == Node::CONTAINER + && (in_array($parent[0]->real_name, $haveAjax) || $isNewView) + ) { + $linkClass = ' ajax'; + } + + if ($node->type == Node::CONTAINER) { + $retval .= ""; + } + + $divClass = ''; + + if (isset($node->links['icon']) && !empty($node->links['icon'])) { + $iconLinks = $node->links['icon']; + $icons = $node->icon; + if (!is_array($iconLinks)) { + $iconLinks = array($iconLinks); + $icons = array($icons); + } + + if (count($icons) > 1) { + $divClass = 'double'; + } + } + + $retval .= "
      "; + + if (isset($node->links['icon']) && !empty($node->links['icon'])) { + $args = array(); + foreach ($node->parents(true) as $parent) { + $args[] = urlencode($parent->real_name); + } + + foreach ($icons as $key => $icon) { + $link = vsprintf($iconLinks[$key], $args); + if ($linkClass != '') { + $retval .= ""; + $retval .= "{$icon}"; + } else { + $retval .= "{$icon}"; + } + } + } else { + $retval .= "{$node->icon}"; + } + $retval .= "
      "; + + if (isset($node->links['text'])) { + $args = array(); + foreach ($node->parents(true) as $parent) {; + $args[] = urlencode($parent->real_name); + } + $link = vsprintf($node->links['text'], $args); + $title = isset($node->links['title']) ? $node->links['title'] : ''; + if ($node->type == Node::CONTAINER) { + $retval .= " "; + $retval .= htmlspecialchars($node->name); + $retval .= ""; + } else { + $retval .= "real_name); + $retval .= ""; + } + } else { + $retval .= " {$node->name}"; + } + $retval .= $node->getHtmlForControlButtons(); + if ($node->type == Node::CONTAINER) { + $retval .= "
      "; + } + $retval .= '
      '; + $wrap = true; + } else { + $node->visible = true; + $wrap = false; + $retval .= $this->_getPaginationParamsHtml($node); + } + + if ($recursive) { + $hide = ''; + if (!$node->visible) { + $hide = " style='display: none;'"; + } + $children = $node->children; + usort( + $children, + array('PhpMyAdmin\\Navigation\\NavigationTree', 'sortNode') + ); + $buffer = ''; + $extra_class = ''; + for ($i = 0, $nbChildren = count($children); $i < $nbChildren; $i++) { + if ($i + 1 == $nbChildren) { + $extra_class = ' last'; + } + $buffer .= $this->_renderNode( + $children[$i], + true, + $children[$i]->classes . $extra_class + ); + } + if (!empty($buffer)) { + if ($wrap) { + $retval .= "
        "; + } + $retval .= $this->_fastFilterHtml($node); + $retval .= $this->_getPageSelector($node); + $retval .= $buffer; + if ($wrap) { + $retval .= "
    "; + } + } + } + if ($node->hasSiblings()) { + $retval .= ""; + } + + return $retval; + } + + /** + * Renders a database select box like the pre-4.0 navigation panel + * + * @return string HTML code + */ + public function renderDbSelect() + { + $this->_buildPath(); + $retval = $this->_quickWarp(); + $this->_tree->is_group = false; + $retval .= '
    '; + // Provide for pagination in database select + $retval .= Util::getListNavigator( + $this->_tree->getPresence('databases', ''), + $this->_pos, + array('server' => $GLOBALS['server']), + 'navigation.php', + 'frame_navigation', + $GLOBALS['cfg']['FirstLevelNavigationItems'], + 'pos', + array('dbselector') + ); + $children = $this->_tree->children; + $url_params = array( + 'server' => $GLOBALS['server'], + ); + $retval .= '
    '; + $retval .= '
    '; + $retval .= Url::getHiddenFields($url_params); + $retval .= '
    '; + $retval .= '
    '; + $retval .= '
      '; + $children = $this->_tree->children; + usort( + $children, + array('PhpMyAdmin\\Navigation\\NavigationTree', 'sortNode') + ); + $this->_setVisibility(); + for ($i = 0, $nbChildren = count($children); $i < $nbChildren; $i++) { + if ($i == 0) { + $retval .= $this->_renderNode($children[0], true, 'first'); + } else { + if ($i + 1 != $nbChildren) { + $retval .= $this->_renderNode($children[$i], true); + } else { + $retval .= $this->_renderNode($children[$i], true, 'last'); + } + } + } + $retval .= '
    '; + + return $retval; + } + + /** + * Makes some nodes visible based on the which node is active + * + * @return void + */ + private function _setVisibility() + { + foreach ($this->_vPath as $path) { + $node = $this->_tree; + foreach ($path as $value) { + $child = $node->getChild($value); + if ($child !== false) { + $child->visible = true; + $node = $child; + } + } + } + } + + /** + * Generates the HTML code for displaying the fast filter for tables + * + * @param Node $node The node for which to generate the fast filter html + * + * @return string LI element used for the fast filter + */ + private function _fastFilterHtml($node) + { + $retval = ''; + $filter_db_min + = (int)$GLOBALS['cfg']['NavigationTreeDisplayDbFilterMinimum']; + $filter_item_min + = (int)$GLOBALS['cfg']['NavigationTreeDisplayItemFilterMinimum']; + if ($node === $this->_tree + && $this->_tree->getPresence() >= $filter_db_min + ) { + $url_params = array( + 'pos' => 0, + ); + $retval .= '
  • '; + $retval .= '
    '; + $retval .= Url::getHiddenInputs($url_params); + $retval .= 'X'; + $retval .= "
    "; + $retval .= "
  • "; + + return $retval; + } + + if (($node->type == Node::CONTAINER + && ($node->real_name == 'tables' + || $node->real_name == 'views' + || $node->real_name == 'functions' + || $node->real_name == 'procedures' + || $node->real_name == 'events')) + && method_exists($node->realParent(), 'getPresence') + && $node->realParent()->getPresence($node->real_name) >= $filter_item_min + ) { + $paths = $node->getPaths(); + $url_params = array( + 'pos' => $this->_pos, + 'aPath' => $paths['aPath'], + 'vPath' => $paths['vPath'], + 'pos2_name' => $node->real_name, + 'pos2_value' => 0, + ); + $retval .= "
  • "; + $retval .= "
    "; + $retval .= Url::getHiddenFields($url_params); + $retval .= ""; + $retval .= "X"; + $retval .= "
    "; + $retval .= "
  • "; + } + + return $retval; + } + + /** + * Creates the code for displaying the controls + * at the top of the navigation tree + * + * @return string HTML code for the controls + */ + private function _controls() + { + // always iconic + $showIcon = true; + $showText = false; + + $retval = ''; + $retval .= ''; + $retval .= ''; + + return $retval; + } + + /** + * Generates the HTML code for displaying the list pagination + * + * @param Node $node The node for whose children the page + * selector will be created + * + * @return string + */ + private function _getPageSelector($node) + { + $retval = ''; + if ($node === $this->_tree) { + $retval .= Util::getListNavigator( + $this->_tree->getPresence('databases', $this->_searchClause), + $this->_pos, + array('server' => $GLOBALS['server']), + 'navigation.php', + 'frame_navigation', + $GLOBALS['cfg']['FirstLevelNavigationItems'], + 'pos', + array('dbselector') + ); + } else { + if ($node->type == Node::CONTAINER && !$node->is_group) { + $paths = $node->getPaths(); + + $level = isset($paths['aPath_clean'][4]) ? 3 : 2; + $_url_params = array( + 'aPath' => $paths['aPath'], + 'vPath' => $paths['vPath'], + 'pos' => $this->_pos, + 'server' => $GLOBALS['server'], + 'pos2_name' => $paths['aPath_clean'][2], + ); + if ($level == 3) { + $pos = $node->pos3; + $_url_params['pos2_value'] = $node->pos2; + $_url_params['pos3_name'] = $paths['aPath_clean'][4]; + } else { + $pos = $node->pos2; + } + $num = $node->realParent() + ->getPresence( + $node->real_name, + $this->_searchClause2 + ); + $retval .= Util::getListNavigator( + $num, + $pos, + $_url_params, + 'navigation.php', + 'frame_navigation', + $GLOBALS['cfg']['MaxNavigationItems'], + 'pos' . $level . '_value' + ); + } + } + + return $retval; + } + + /** + * Called by usort() for sorting the nodes in a container + * + * @param Node $a The first element used in the comparison + * @param Node $b The second element used in the comparison + * + * @return int See strnatcmp() and strcmp() + */ + static public function sortNode($a, $b) + { + if ($a->isNew) { + return -1; + } + + if ($b->isNew) { + return 1; + } + + if ($GLOBALS['cfg']['NaturalOrder']) { + return strnatcasecmp($a->name, $b->name); + } + + return strcasecmp($a->name, $b->name); + } + + /** + * Display quick warp links, contain Recents and Favorites + * + * @return string HTML code + */ + private function _quickWarp() + { + $retval = '
    '; + if ($GLOBALS['cfg']['NumRecentTables'] > 0) { + $retval .= RecentFavoriteTable::getInstance('recent') + ->getHtml(); + } + if ($GLOBALS['cfg']['NumFavoriteTables'] > 0) { + $retval .= RecentFavoriteTable::getInstance('favorite') + ->getHtml(); + } + $retval .= '
    '; + $retval .= '
    '; + + return $retval; + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Navigation/NodeFactory.php b/php/apps/phpmyadmin49/html/libraries/classes/Navigation/NodeFactory.php new file mode 100644 index 00000000..5c0f4dc9 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Navigation/NodeFactory.php @@ -0,0 +1,91 @@ +name = $name; + $this->real_name = $name; + } + if ($type === Node::CONTAINER) { + $this->type = Node::CONTAINER; + } + $this->is_group = (bool)$is_group; + $this->relation = new Relation(); + } + + /** + * Adds a child node to this node + * + * @param Node $child A child node + * + * @return void + */ + public function addChild($child) + { + $this->children[] = $child; + $child->parent = $this; + } + + /** + * Returns a child node given it's name + * + * @param string $name The name of requested child + * @param bool $real_name Whether to use the "real_name" + * instead of "name" in comparisons + * + * @return false|Node The requested child node or false, + * if the requested node cannot be found + */ + public function getChild($name, $real_name = false) + { + if ($real_name) { + foreach ($this->children as $child) { + if ($child->real_name == $name) { + return $child; + } + } + } else { + foreach ($this->children as $child) { + if ($child->name == $name) { + return $child; + } + } + } + + return false; + } + + /** + * Removes a child node from this node + * + * @param string $name The name of child to be removed + * + * @return void + */ + public function removeChild($name) + { + foreach ($this->children as $key => $child) { + if ($child->name == $name) { + unset($this->children[$key]); + break; + } + } + } + + /** + * Retrieves the parents for a node + * + * @param bool $self Whether to include the Node itself in the results + * @param bool $containers Whether to include nodes of type CONTAINER + * @param bool $groups Whether to include nodes which have $group == true + * + * @return array An array of parent Nodes + */ + public function parents($self = false, $containers = false, $groups = false) + { + $parents = array(); + if ($self + && ($this->type != Node::CONTAINER || $containers) + && (!$this->is_group || $groups) + ) { + $parents[] = $this; + } + $parent = $this->parent; + while (isset($parent)) { + if (($parent->type != Node::CONTAINER || $containers) + && (!$parent->is_group || $groups) + ) { + $parents[] = $parent; + } + $parent = $parent->parent; + } + + return $parents; + } + + /** + * Returns the actual parent of a node. If used twice on an index or columns + * node, it will return the table and database nodes. The names of the returned + * nodes can be used in SQL queries, etc... + * + * @return Node|false + */ + public function realParent() + { + $retval = $this->parents(); + if (count($retval) <= 0) { + return false; + } + + return $retval[0]; + } + + /** + * This function checks if the node has children nodes associated with it + * + * @param bool $count_empty_containers Whether to count empty child + * containers as valid children + * + * @return bool Whether the node has child nodes + */ + public function hasChildren($count_empty_containers = true) + { + $retval = false; + if ($count_empty_containers) { + if (count($this->children)) { + $retval = true; + } + } else { + foreach ($this->children as $child) { + if ($child->type == Node::OBJECT || $child->hasChildren(false)) { + $retval = true; + break; + } + } + } + + return $retval; + } + + /** + * Returns true if the node has some siblings (other nodes on the same tree + * level, in the same branch), false otherwise. + * The only exception is for nodes on + * the third level of the tree (columns and indexes), for which the function + * always returns true. This is because we want to render the containers + * for these nodes + * + * @return bool + */ + public function hasSiblings() + { + $retval = false; + $paths = $this->getPaths(); + if (count($paths['aPath_clean']) > 3) { + $retval = true; + + return $retval; + } + + foreach ($this->parent->children as $child) { + if ($child !== $this + && ($child->type == Node::OBJECT || $child->hasChildren(false)) + ) { + $retval = true; + break; + } + } + + return $retval; + } + + /** + * Returns the number of child nodes that a node has associated with it + * + * @return int The number of children nodes + */ + public function numChildren() + { + $retval = 0; + foreach ($this->children as $child) { + if ($child->type == Node::OBJECT) { + $retval++; + } else { + $retval += $child->numChildren(); + } + } + + return $retval; + } + + /** + * Returns the actual path and the virtual paths for a node + * both as clean arrays and base64 encoded strings + * + * @return array + */ + public function getPaths() + { + $aPath = array(); + $aPath_clean = array(); + foreach ($this->parents(true, true, false) as $parent) { + $aPath[] = base64_encode($parent->real_name); + $aPath_clean[] = $parent->real_name; + } + $aPath = implode('.', array_reverse($aPath)); + $aPath_clean = array_reverse($aPath_clean); + + $vPath = array(); + $vPath_clean = array(); + foreach ($this->parents(true, true, true) as $parent) { + $vPath[] = base64_encode($parent->name); + $vPath_clean[] = $parent->name; + } + $vPath = implode('.', array_reverse($vPath)); + $vPath_clean = array_reverse($vPath_clean); + + return array( + 'aPath' => $aPath, + 'aPath_clean' => $aPath_clean, + 'vPath' => $vPath, + 'vPath_clean' => $vPath_clean, + ); + } + + /** + * Returns the names of children of type $type present inside this container + * This method is overridden by the PhpMyAdmin\Navigation\Nodes\NodeDatabase and PhpMyAdmin\Navigation\Nodes\NodeTable classes + * + * @param string $type The type of item we are looking for + * ('tables', 'views', etc) + * @param int $pos The offset of the list within the results + * @param string $searchClause A string used to filter the results of the query + * + * @return array + */ + public function getData($type, $pos, $searchClause = '') + { + $maxItems = $GLOBALS['cfg']['FirstLevelNavigationItems']; + if (!$GLOBALS['cfg']['NavigationTreeEnableGrouping'] + || !$GLOBALS['cfg']['ShowDatabasesNavigationAsTree'] + ) { + if (isset($GLOBALS['cfg']['Server']['DisableIS']) + && !$GLOBALS['cfg']['Server']['DisableIS'] + ) { + $query = "SELECT `SCHEMA_NAME` "; + $query .= "FROM `INFORMATION_SCHEMA`.`SCHEMATA` "; + $query .= $this->_getWhereClause('SCHEMA_NAME', $searchClause); + $query .= "ORDER BY `SCHEMA_NAME` "; + $query .= "LIMIT $pos, $maxItems"; + $retval = $GLOBALS['dbi']->fetchResult($query); + + return $retval; + } + + if ($GLOBALS['dbs_to_test'] === false) { + $retval = array(); + $query = "SHOW DATABASES "; + $query .= $this->_getWhereClause('Database', $searchClause); + $handle = $GLOBALS['dbi']->tryQuery($query); + if ($handle === false) { + return $retval; + } + + $count = 0; + if (!$GLOBALS['dbi']->dataSeek($handle, $pos)) { + return $retval; + } + + while ($arr = $GLOBALS['dbi']->fetchArray($handle)) { + if ($count < $maxItems) { + $retval[] = $arr[0]; + $count++; + } else { + break; + } + } + + return $retval; + } + + $retval = array(); + $count = 0; + foreach ($this->_getDatabasesToSearch($searchClause) as $db) { + $query = "SHOW DATABASES LIKE '" . $db . "'"; + $handle = $GLOBALS['dbi']->tryQuery($query); + if ($handle === false) { + continue; + } + + while ($arr = $GLOBALS['dbi']->fetchArray($handle)) { + if ($this->_isHideDb($arr[0])) { + continue; + } + if (in_array($arr[0], $retval)) { + continue; + } + + if ($pos <= 0 && $count < $maxItems) { + $retval[] = $arr[0]; + $count++; + } + $pos--; + } + } + sort($retval); + + return $retval; + } + + $dbSeparator = $GLOBALS['cfg']['NavigationTreeDbSeparator']; + if (isset($GLOBALS['cfg']['Server']['DisableIS']) + && !$GLOBALS['cfg']['Server']['DisableIS'] + ) { + $query = "SELECT `SCHEMA_NAME` "; + $query .= "FROM `INFORMATION_SCHEMA`.`SCHEMATA`, "; + $query .= "("; + $query .= "SELECT DB_first_level "; + $query .= "FROM ( "; + $query .= "SELECT DISTINCT SUBSTRING_INDEX(SCHEMA_NAME, "; + $query .= "'" . $GLOBALS['dbi']->escapeString($dbSeparator) . "', 1) "; + $query .= "DB_first_level "; + $query .= "FROM INFORMATION_SCHEMA.SCHEMATA "; + $query .= $this->_getWhereClause('SCHEMA_NAME', $searchClause); + $query .= ") t "; + $query .= "ORDER BY DB_first_level ASC "; + $query .= "LIMIT $pos, $maxItems"; + $query .= ") t2 "; + $query .= $this->_getWhereClause('SCHEMA_NAME', $searchClause); + $query .= "AND 1 = LOCATE(CONCAT(DB_first_level, "; + $query .= "'" . $GLOBALS['dbi']->escapeString($dbSeparator) . "'), "; + $query .= "CONCAT(SCHEMA_NAME, "; + $query .= "'" . $GLOBALS['dbi']->escapeString($dbSeparator) . "')) "; + $query .= "ORDER BY SCHEMA_NAME ASC"; + $retval = $GLOBALS['dbi']->fetchResult($query); + + return $retval; + } + + if ($GLOBALS['dbs_to_test'] === false) { + $query = "SHOW DATABASES "; + $query .= $this->_getWhereClause('Database', $searchClause); + $handle = $GLOBALS['dbi']->tryQuery($query); + $prefixes = array(); + if ($handle !== false) { + $prefixMap = array(); + $total = $pos + $maxItems; + while ($arr = $GLOBALS['dbi']->fetchArray($handle)) { + $prefix = strstr($arr[0], $dbSeparator, true); + if ($prefix === false) { + $prefix = $arr[0]; + } + $prefixMap[$prefix] = 1; + if (sizeof($prefixMap) == $total) { + break; + } + } + $prefixes = array_slice(array_keys($prefixMap), $pos); + } + + $query = "SHOW DATABASES "; + $query .= $this->_getWhereClause('Database', $searchClause); + $query .= "AND ("; + $subClauses = array(); + foreach ($prefixes as $prefix) { + $subClauses[] = " LOCATE('" + . $GLOBALS['dbi']->escapeString($prefix) . $dbSeparator + . "', " + . "CONCAT(`Database`, '" . $dbSeparator . "')) = 1 "; + } + $query .= implode("OR", $subClauses) . ")"; + $retval = $GLOBALS['dbi']->fetchResult($query); + + return $retval; + } + + $retval = array(); + $prefixMap = array(); + $total = $pos + $maxItems; + foreach ($this->_getDatabasesToSearch($searchClause) as $db) { + $query = "SHOW DATABASES LIKE '" . $db . "'"; + $handle = $GLOBALS['dbi']->tryQuery($query); + if ($handle === false) { + continue; + } + + while ($arr = $GLOBALS['dbi']->fetchArray($handle)) { + if ($this->_isHideDb($arr[0])) { + continue; + } + $prefix = strstr($arr[0], $dbSeparator, true); + if ($prefix === false) { + $prefix = $arr[0]; + } + $prefixMap[$prefix] = 1; + if (sizeof($prefixMap) == $total) { + break 2; + } + } + } + $prefixes = array_slice(array_keys($prefixMap), $pos); + + foreach ($this->_getDatabasesToSearch($searchClause) as $db) { + $query = "SHOW DATABASES LIKE '" . $db . "'"; + $handle = $GLOBALS['dbi']->tryQuery($query); + if ($handle === false) { + continue; + } + + while ($arr = $GLOBALS['dbi']->fetchArray($handle)) { + if ($this->_isHideDb($arr[0])) { + continue; + } + if (in_array($arr[0], $retval)) { + continue; + } + + foreach ($prefixes as $prefix) { + $starts_with = strpos( + $arr[0] . $dbSeparator, + $prefix . $dbSeparator + ) === 0; + if ($starts_with) { + $retval[] = $arr[0]; + break; + } + } + } + } + sort($retval); + + return $retval; + } + + /** + * Returns the number of children of type $type present inside this container + * This method is overridden by the PhpMyAdmin\Navigation\Nodes\NodeDatabase and PhpMyAdmin\Navigation\Nodes\NodeTable classes + * + * @param string $type The type of item we are looking for + * ('tables', 'views', etc) + * @param string $searchClause A string used to filter the results of the query + * + * @return int + */ + public function getPresence($type = '', $searchClause = '') + { + if (!$GLOBALS['cfg']['NavigationTreeEnableGrouping'] + || !$GLOBALS['cfg']['ShowDatabasesNavigationAsTree'] + ) { + if (isset($GLOBALS['cfg']['Server']['DisableIS']) + && !$GLOBALS['cfg']['Server']['DisableIS'] + ) { + $query = "SELECT COUNT(*) "; + $query .= "FROM INFORMATION_SCHEMA.SCHEMATA "; + $query .= $this->_getWhereClause('SCHEMA_NAME', $searchClause); + $retval = (int)$GLOBALS['dbi']->fetchValue($query); + + return $retval; + } + + if ($GLOBALS['dbs_to_test'] === false) { + $query = "SHOW DATABASES "; + $query .= $this->_getWhereClause('Database', $searchClause); + $retval = $GLOBALS['dbi']->numRows( + $GLOBALS['dbi']->tryQuery($query) + ); + + return $retval; + } + + $retval = 0; + foreach ($this->_getDatabasesToSearch($searchClause) as $db) { + $query = "SHOW DATABASES LIKE '" . $db . "'"; + $retval += $GLOBALS['dbi']->numRows( + $GLOBALS['dbi']->tryQuery($query) + ); + } + + return $retval; + } + + $dbSeparator = $GLOBALS['cfg']['NavigationTreeDbSeparator']; + if (!$GLOBALS['cfg']['Server']['DisableIS']) { + $query = "SELECT COUNT(*) "; + $query .= "FROM ( "; + $query .= "SELECT DISTINCT SUBSTRING_INDEX(SCHEMA_NAME, "; + $query .= "'$dbSeparator', 1) "; + $query .= "DB_first_level "; + $query .= "FROM INFORMATION_SCHEMA.SCHEMATA "; + $query .= $this->_getWhereClause('SCHEMA_NAME', $searchClause); + $query .= ") t "; + $retval = (int)$GLOBALS['dbi']->fetchValue($query); + + return $retval; + } + + if ($GLOBALS['dbs_to_test'] !== false) { + $prefixMap = array(); + foreach ($this->_getDatabasesToSearch($searchClause) as $db) { + $query = "SHOW DATABASES LIKE '" . $db . "'"; + $handle = $GLOBALS['dbi']->tryQuery($query); + if ($handle === false) { + continue; + } + + while ($arr = $GLOBALS['dbi']->fetchArray($handle)) { + if ($this->_isHideDb($arr[0])) { + continue; + } + $prefix = strstr($arr[0], $dbSeparator, true); + if ($prefix === false) { + $prefix = $arr[0]; + } + $prefixMap[$prefix] = 1; + } + } + $retval = count($prefixMap); + + return $retval; + } + + $prefixMap = array(); + $query = "SHOW DATABASES "; + $query .= $this->_getWhereClause('Database', $searchClause); + $handle = $GLOBALS['dbi']->tryQuery($query); + if ($handle !== false) { + while ($arr = $GLOBALS['dbi']->fetchArray($handle)) { + $prefix = strstr($arr[0], $dbSeparator, true); + if ($prefix === false) { + $prefix = $arr[0]; + } + $prefixMap[$prefix] = 1; + } + } + $retval = count($prefixMap); + + return $retval; + } + + /** + * Detemines whether a given database should be hidden according to 'hide_db' + * + * @param string $db database name + * + * @return boolean whether to hide + */ + private function _isHideDb($db) + { + return !empty($GLOBALS['cfg']['Server']['hide_db']) + && preg_match('/' . $GLOBALS['cfg']['Server']['hide_db'] . '/', $db); + } + + /** + * Get the list of databases for 'SHOW DATABASES LIKE' queries. + * If a search clause is set it gets the highest priority while only_db gets + * the next priority. In case both are empty list of databases determined by + * GRANTs are used + * + * @param string $searchClause search clause + * + * @return array array of databases + */ + private function _getDatabasesToSearch($searchClause) + { + if (!empty($searchClause)) { + $databases = array( + "%" . $GLOBALS['dbi']->escapeString($searchClause) . "%", + ); + } elseif (!empty($GLOBALS['cfg']['Server']['only_db'])) { + $databases = $GLOBALS['cfg']['Server']['only_db']; + } elseif (!empty($GLOBALS['dbs_to_test'])) { + $databases = $GLOBALS['dbs_to_test']; + } + sort($databases); + + return $databases; + } + + /** + * Returns the WHERE clause depending on the $searchClause parameter + * and the hide_db directive + * + * @param string $columnName Column name of the column having database names + * @param string $searchClause A string used to filter the results of the query + * + * @return string + */ + private function _getWhereClause($columnName, $searchClause = '') + { + $whereClause = "WHERE TRUE "; + if (!empty($searchClause)) { + $whereClause .= "AND " . Util::backquote($columnName) + . " LIKE '%"; + $whereClause .= $GLOBALS['dbi']->escapeString($searchClause); + $whereClause .= "%' "; + } + + if (!empty($GLOBALS['cfg']['Server']['hide_db'])) { + $whereClause .= "AND " . Util::backquote($columnName) + . " NOT REGEXP '" + . $GLOBALS['dbi']->escapeString($GLOBALS['cfg']['Server']['hide_db']) + . "' "; + } + + if (!empty($GLOBALS['cfg']['Server']['only_db'])) { + if (is_string($GLOBALS['cfg']['Server']['only_db'])) { + $GLOBALS['cfg']['Server']['only_db'] = array( + $GLOBALS['cfg']['Server']['only_db'], + ); + } + $whereClause .= "AND ("; + $subClauses = array(); + foreach ($GLOBALS['cfg']['Server']['only_db'] as $each_only_db) { + $subClauses[] = " " . Util::backquote($columnName) + . " LIKE '" + . $GLOBALS['dbi']->escapeString($each_only_db) . "' "; + } + $whereClause .= implode("OR", $subClauses) . ") "; + } + + return $whereClause; + } + + /** + * Returns HTML for control buttons displayed infront of a node + * + * @return String HTML for control buttons + */ + public function getHtmlForControlButtons() + { + return ''; + } + + /** + * Returns CSS classes for a node + * + * @param boolean $match Whether the node matched loaded tree + * + * @return String with html classes. + */ + public function getCssClasses($match) + { + if (!$GLOBALS['cfg']['NavigationTreeEnableExpansion'] + ) { + return ''; + } + + $result = array('expander'); + + if ($this->is_group || $match) { + $result[] = 'loaded'; + } + if ($this->type == Node::CONTAINER) { + $result[] = 'container'; + } + + return implode(' ', $result); + } + + /** + * Returns icon for the node + * + * @param boolean $match Whether the node matched loaded tree + * + * @return String with image name + */ + public function getIcon($match) + { + if (!$GLOBALS['cfg']['NavigationTreeEnableExpansion'] + ) { + return ''; + } elseif ($match) { + $this->visible = true; + + return Util::getImage('b_minus'); + } + + return Util::getImage('b_plus', __('Expand/Collapse')); + } + + /** + * Gets the count of hidden elements for each database + * + * @return array array containing the count of hidden elements for each database + */ + public function getNavigationHidingData() + { + $cfgRelation = $this->relation->getRelationsParam(); + if ($cfgRelation['navwork']) { + $navTable = Util::backquote($cfgRelation['db']) + . "." . Util::backquote( + $cfgRelation['navigationhiding'] + ); + $sqlQuery = "SELECT `db_name`, COUNT(*) AS `count` FROM " . $navTable + . " WHERE `username`='" + . $GLOBALS['dbi']->escapeString( + $GLOBALS['cfg']['Server']['user'] + ) . "'" + . " GROUP BY `db_name`"; + $counts = $GLOBALS['dbi']->fetchResult( + $sqlQuery, + 'db_name', + 'count', + DatabaseInterface::CONNECT_CONTROL + ); + + return $counts; + } + + return null; + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Navigation/Nodes/NodeColumn.php b/php/apps/phpmyadmin49/html/libraries/classes/Navigation/Nodes/NodeColumn.php new file mode 100644 index 00000000..876cb221 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Navigation/Nodes/NodeColumn.php @@ -0,0 +1,41 @@ +icon = Util::getImage('pause', __('Column')); + $this->links = array( + 'text' => 'tbl_structure.php?server=' . $GLOBALS['server'] + . '&db=%3$s&table=%2$s&field=%1$s' + . '&change_column=1', + 'icon' => 'tbl_structure.php?server=' . $GLOBALS['server'] + . '&db=%3$s&table=%2$s&field=%1$s' + . '&change_column=1', + 'title' => __('Structure'), + ); + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Navigation/Nodes/NodeColumnContainer.php b/php/apps/phpmyadmin49/html/libraries/classes/Navigation/Nodes/NodeColumnContainer.php new file mode 100644 index 00000000..eaa7c9c0 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Navigation/Nodes/NodeColumnContainer.php @@ -0,0 +1,53 @@ +icon = Util::getImage('pause', __('Columns')); + $this->links = array( + 'text' => 'tbl_structure.php?server=' . $GLOBALS['server'] + . '&db=%2$s&table=%1$s', + 'icon' => 'tbl_structure.php?server=' . $GLOBALS['server'] + . '&db=%2$s&table=%1$s', + ); + $this->real_name = 'columns'; + + $new_label = _pgettext('Create new column', 'New'); + $new = NodeFactory::getInstance( + 'Node', + $new_label + ); + $new->isNew = true; + $new->icon = Util::getImage('b_column_add', $new_label); + $new->links = array( + 'text' => 'tbl_addfield.php?server=' . $GLOBALS['server'] + . '&db=%3$s&table=%2$s' + . '&field_where=last&after_field=', + 'icon' => 'tbl_addfield.php?server=' . $GLOBALS['server'] + . '&db=%3$s&table=%2$s' + . '&field_where=last&after_field=', + ); + $new->classes = 'new_column italics'; + $this->addChild($new); + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Navigation/Nodes/NodeDatabase.php b/php/apps/phpmyadmin49/html/libraries/classes/Navigation/Nodes/NodeDatabase.php new file mode 100644 index 00000000..246000c4 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Navigation/Nodes/NodeDatabase.php @@ -0,0 +1,715 @@ +icon = Util::getImage( + 's_db', + __('Database operations') + ); + + $script_name = Util::getScriptNameForOption( + $GLOBALS['cfg']['DefaultTabDatabase'], + 'database' + ); + $this->links = array( + 'text' => $script_name + . '?server=' . $GLOBALS['server'] + . '&db=%1$s', + 'icon' => 'db_operations.php?server=' . $GLOBALS['server'] + . '&db=%1$s&', + 'title' => __('Structure'), + ); + $this->classes = 'database'; + } + + /** + * Returns the number of children of type $type present inside this container + * This method is overridden by the PhpMyAdmin\Navigation\Nodes\NodeDatabase + * and PhpMyAdmin\Navigation\Nodes\NodeTable classes + * + * @param string $type The type of item we are looking for + * ('tables', 'views', etc) + * @param string $searchClause A string used to filter the results of + * the query + * @param boolean $singleItem Whether to get presence of a single known + * item or false in none + * + * @return int + */ + public function getPresence($type = '', $searchClause = '', $singleItem = false) + { + $retval = 0; + switch ($type) { + case 'tables': + $retval = $this->_getTableCount($searchClause, $singleItem); + break; + case 'views': + $retval = $this->_getViewCount($searchClause, $singleItem); + break; + case 'procedures': + $retval = $this->_getProcedureCount($searchClause, $singleItem); + break; + case 'functions': + $retval = $this->_getFunctionCount($searchClause, $singleItem); + break; + case 'events': + $retval = $this->_getEventCount($searchClause, $singleItem); + break; + default: + break; + } + + return $retval; + } + + /** + * Returns the number of tables or views present inside this database + * + * @param string $which tables|views + * @param string $searchClause A string used to filter the results of + * the query + * @param boolean $singleItem Whether to get presence of a single known + * item or false in none + * + * @return int + */ + private function _getTableOrViewCount($which, $searchClause, $singleItem) + { + $db = $this->real_name; + if ($which == 'tables') { + $condition = 'IN'; + } else { + $condition = 'NOT IN'; + } + + if (! $GLOBALS['cfg']['Server']['DisableIS']) { + $db = $GLOBALS['dbi']->escapeString($db); + $query = "SELECT COUNT(*) "; + $query .= "FROM `INFORMATION_SCHEMA`.`TABLES` "; + $query .= "WHERE `TABLE_SCHEMA`='$db' "; + $query .= "AND `TABLE_TYPE`" . $condition . "('BASE TABLE', 'SYSTEM VERSIONED') "; + if (! empty($searchClause)) { + $query .= "AND " . $this->_getWhereClauseForSearch( + $searchClause, + $singleItem, + 'TABLE_NAME' + ); + } + $retval = (int)$GLOBALS['dbi']->fetchValue($query); + } else { + $query = "SHOW FULL TABLES FROM "; + $query .= Util::backquote($db); + $query .= " WHERE `Table_type`" . $condition . "('BASE TABLE', 'SYSTEM VERSIONED') "; + if (!empty($searchClause)) { + $query .= "AND " . $this->_getWhereClauseForSearch( + $searchClause, + $singleItem, + 'Tables_in_' . $db + ); + } + $retval = $GLOBALS['dbi']->numRows( + $GLOBALS['dbi']->tryQuery($query) + ); + } + + return $retval; + } + + /** + * Returns the number of tables present inside this database + * + * @param string $searchClause A string used to filter the results of + * the query + * @param boolean $singleItem Whether to get presence of a single known + * item or false in none + * + * @return int + */ + private function _getTableCount($searchClause, $singleItem) + { + return $this->_getTableOrViewCount( + 'tables', + $searchClause, + $singleItem + ); + } + + /** + * Returns the number of views present inside this database + * + * @param string $searchClause A string used to filter the results of + * the query + * @param boolean $singleItem Whether to get presence of a single known + * item or false in none + * + * @return int + */ + private function _getViewCount($searchClause, $singleItem) + { + return $this->_getTableOrViewCount( + 'views', + $searchClause, + $singleItem + ); + } + + /** + * Returns the number of procedures present inside this database + * + * @param string $searchClause A string used to filter the results of + * the query + * @param boolean $singleItem Whether to get presence of a single known + * item or false in none + * + * @return int + */ + private function _getProcedureCount($searchClause, $singleItem) + { + $db = $this->real_name; + if (!$GLOBALS['cfg']['Server']['DisableIS']) { + $db = $GLOBALS['dbi']->escapeString($db); + $query = "SELECT COUNT(*) "; + $query .= "FROM `INFORMATION_SCHEMA`.`ROUTINES` "; + $query .= "WHERE `ROUTINE_SCHEMA` " + . Util::getCollateForIS() . "='$db'"; + $query .= "AND `ROUTINE_TYPE`='PROCEDURE' "; + if (!empty($searchClause)) { + $query .= "AND " . $this->_getWhereClauseForSearch( + $searchClause, + $singleItem, + 'ROUTINE_NAME' + ); + } + $retval = (int)$GLOBALS['dbi']->fetchValue($query); + } else { + $db = $GLOBALS['dbi']->escapeString($db); + $query = "SHOW PROCEDURE STATUS WHERE `Db`='$db' "; + if (!empty($searchClause)) { + $query .= "AND " . $this->_getWhereClauseForSearch( + $searchClause, + $singleItem, + 'Name' + ); + } + $retval = $GLOBALS['dbi']->numRows( + $GLOBALS['dbi']->tryQuery($query) + ); + } + + return $retval; + } + + /** + * Returns the number of functions present inside this database + * + * @param string $searchClause A string used to filter the results of + * the query + * @param boolean $singleItem Whether to get presence of a single known + * item or false in none + * + * @return int + */ + private function _getFunctionCount($searchClause, $singleItem) + { + $db = $this->real_name; + if (!$GLOBALS['cfg']['Server']['DisableIS']) { + $db = $GLOBALS['dbi']->escapeString($db); + $query = "SELECT COUNT(*) "; + $query .= "FROM `INFORMATION_SCHEMA`.`ROUTINES` "; + $query .= "WHERE `ROUTINE_SCHEMA` " + . Util::getCollateForIS() . "='$db' "; + $query .= "AND `ROUTINE_TYPE`='FUNCTION' "; + if (!empty($searchClause)) { + $query .= "AND " . $this->_getWhereClauseForSearch( + $searchClause, + $singleItem, + 'ROUTINE_NAME' + ); + } + $retval = (int)$GLOBALS['dbi']->fetchValue($query); + } else { + $db = $GLOBALS['dbi']->escapeString($db); + $query = "SHOW FUNCTION STATUS WHERE `Db`='$db' "; + if (!empty($searchClause)) { + $query .= "AND " . $this->_getWhereClauseForSearch( + $searchClause, + $singleItem, + 'Name' + ); + } + $retval = $GLOBALS['dbi']->numRows( + $GLOBALS['dbi']->tryQuery($query) + ); + } + + return $retval; + } + + /** + * Returns the number of events present inside this database + * + * @param string $searchClause A string used to filter the results of + * the query + * @param boolean $singleItem Whether to get presence of a single known + * item or false in none + * + * @return int + */ + private function _getEventCount($searchClause, $singleItem) + { + $db = $this->real_name; + if (!$GLOBALS['cfg']['Server']['DisableIS']) { + $db = $GLOBALS['dbi']->escapeString($db); + $query = "SELECT COUNT(*) "; + $query .= "FROM `INFORMATION_SCHEMA`.`EVENTS` "; + $query .= "WHERE `EVENT_SCHEMA` " + . Util::getCollateForIS() . "='$db' "; + if (!empty($searchClause)) { + $query .= "AND " . $this->_getWhereClauseForSearch( + $searchClause, + $singleItem, + 'EVENT_NAME' + ); + } + $retval = (int)$GLOBALS['dbi']->fetchValue($query); + } else { + $db = Util::backquote($db); + $query = "SHOW EVENTS FROM $db "; + if (!empty($searchClause)) { + $query .= "WHERE " . $this->_getWhereClauseForSearch( + $searchClause, + $singleItem, + 'Name' + ); + } + $retval = $GLOBALS['dbi']->numRows( + $GLOBALS['dbi']->tryQuery($query) + ); + } + + return $retval; + } + + /** + * Returns the WHERE clause for searching inside a database + * + * @param string $searchClause A string used to filter the results of the query + * @param boolean $singleItem Whether to get presence of a single known item + * @param string $columnName Name of the column in the result set to match + * + * @return string WHERE clause for searching + */ + private function _getWhereClauseForSearch( + $searchClause, + $singleItem, + $columnName + ) { + $query = ''; + if ($singleItem) { + $query .= Util::backquote($columnName) . " = "; + $query .= "'" . $GLOBALS['dbi']->escapeString($searchClause) . "'"; + } else { + $query .= Util::backquote($columnName) . " LIKE "; + $query .= "'%" . $GLOBALS['dbi']->escapeString($searchClause) + . "%'"; + } + + return $query; + } + + /** + * Returns the names of children of type $type present inside this container + * This method is overridden by the PhpMyAdmin\Navigation\Nodes\NodeDatabase + * and PhpMyAdmin\Navigation\Nodes\NodeTable classes + * + * @param string $type The type of item we are looking for + * ('tables', 'views', etc) + * @param int $pos The offset of the list within the results + * @param string $searchClause A string used to filter the results of the query + * + * @return array + */ + public function getData($type, $pos, $searchClause = '') + { + $retval = array(); + switch ($type) { + case 'tables': + $retval = $this->_getTables($pos, $searchClause); + break; + case 'views': + $retval = $this->_getViews($pos, $searchClause); + break; + case 'procedures': + $retval = $this->_getProcedures($pos, $searchClause); + break; + case 'functions': + $retval = $this->_getFunctions($pos, $searchClause); + break; + case 'events': + $retval = $this->_getEvents($pos, $searchClause); + break; + default: + break; + } + + // Remove hidden items so that they are not displayed in navigation tree + $cfgRelation = $this->relation->getRelationsParam(); + if ($cfgRelation['navwork']) { + $hiddenItems = $this->getHiddenItems(substr($type, 0, -1)); + foreach ($retval as $key => $item) { + if (in_array($item, $hiddenItems)) { + unset($retval[$key]); + } + } + } + + return $retval; + } + + /** + * Return list of hidden items of given type + * + * @param string $type The type of items we are looking for + * ('table', 'function', 'group', etc.) + * + * @return array Array containing hidden items of given type + */ + public function getHiddenItems($type) + { + $db = $this->real_name; + $cfgRelation = $this->relation->getRelationsParam(); + if (empty($cfgRelation['navigationhiding'])) { + return array(); + } + $navTable = Util::backquote($cfgRelation['db']) + . "." . Util::backquote($cfgRelation['navigationhiding']); + $sqlQuery = "SELECT `item_name` FROM " . $navTable + . " WHERE `username`='" . $cfgRelation['user'] . "'" + . " AND `item_type`='" . $type + . "'" . " AND `db_name`='" . $GLOBALS['dbi']->escapeString($db) + . "'"; + $result = $this->relation->queryAsControlUser($sqlQuery, false); + $hiddenItems = array(); + if ($result) { + while ($row = $GLOBALS['dbi']->fetchArray($result)) { + $hiddenItems[] = $row[0]; + } + } + $GLOBALS['dbi']->freeResult($result); + + return $hiddenItems; + } + + /** + * Returns the list of tables or views inside this database + * + * @param string $which tables|views + * @param int $pos The offset of the list within the results + * @param string $searchClause A string used to filter the results of the query + * + * @return array + */ + private function _getTablesOrViews($which, $pos, $searchClause) + { + if ($which == 'tables') { + $condition = 'IN'; + } else { + $condition = 'NOT IN'; + } + $maxItems = $GLOBALS['cfg']['MaxNavigationItems']; + $retval = array(); + $db = $this->real_name; + if (! $GLOBALS['cfg']['Server']['DisableIS']) { + $escdDb = $GLOBALS['dbi']->escapeString($db); + $query = "SELECT `TABLE_NAME` AS `name` "; + $query .= "FROM `INFORMATION_SCHEMA`.`TABLES` "; + $query .= "WHERE `TABLE_SCHEMA`='$escdDb' "; + $query .= "AND `TABLE_TYPE`" . $condition . "('BASE TABLE', 'SYSTEM VERSIONED') "; + if (! empty($searchClause)) { + $query .= "AND `TABLE_NAME` LIKE '%"; + $query .= $GLOBALS['dbi']->escapeString($searchClause); + $query .= "%'"; + } + $query .= "ORDER BY `TABLE_NAME` ASC "; + $query .= "LIMIT " . intval($pos) . ", $maxItems"; + $retval = $GLOBALS['dbi']->fetchResult($query); + } else { + $query = " SHOW FULL TABLES FROM "; + $query .= Util::backquote($db); + $query .= " WHERE `Table_type`" . $condition . "('BASE TABLE', 'SYSTEM VERSIONED') "; + if (!empty($searchClause)) { + $query .= "AND " . Util::backquote( + "Tables_in_" . $db + ); + $query .= " LIKE '%" . $GLOBALS['dbi']->escapeString( + $searchClause + ); + $query .= "%'"; + } + $handle = $GLOBALS['dbi']->tryQuery($query); + if ($handle !== false) { + $count = 0; + if ($GLOBALS['dbi']->dataSeek($handle, $pos)) { + while ($arr = $GLOBALS['dbi']->fetchArray($handle)) { + if ($count < $maxItems) { + $retval[] = $arr[0]; + $count++; + } else { + break; + } + } + } + } + } + + return $retval; + } + + /** + * Returns the list of tables inside this database + * + * @param int $pos The offset of the list within the results + * @param string $searchClause A string used to filter the results of the query + * + * @return array + */ + private function _getTables($pos, $searchClause) + { + return $this->_getTablesOrViews('tables', $pos, $searchClause); + } + + /** + * Returns the list of views inside this database + * + * @param int $pos The offset of the list within the results + * @param string $searchClause A string used to filter the results of the query + * + * @return array + */ + private function _getViews($pos, $searchClause) + { + return $this->_getTablesOrViews('views', $pos, $searchClause); + } + + /** + * Returns the list of procedures or functions inside this database + * + * @param string $routineType PROCEDURE|FUNCTION + * @param int $pos The offset of the list within the results + * @param string $searchClause A string used to filter the results of the query + * + * @return array + */ + private function _getRoutines($routineType, $pos, $searchClause) + { + $maxItems = $GLOBALS['cfg']['MaxNavigationItems']; + $retval = array(); + $db = $this->real_name; + if (!$GLOBALS['cfg']['Server']['DisableIS']) { + $escdDb = $GLOBALS['dbi']->escapeString($db); + $query = "SELECT `ROUTINE_NAME` AS `name` "; + $query .= "FROM `INFORMATION_SCHEMA`.`ROUTINES` "; + $query .= "WHERE `ROUTINE_SCHEMA` " + . Util::getCollateForIS() . "='$escdDb'"; + $query .= "AND `ROUTINE_TYPE`='" . $routineType . "' "; + if (!empty($searchClause)) { + $query .= "AND `ROUTINE_NAME` LIKE '%"; + $query .= $GLOBALS['dbi']->escapeString($searchClause); + $query .= "%'"; + } + $query .= "ORDER BY `ROUTINE_NAME` ASC "; + $query .= "LIMIT " . intval($pos) . ", $maxItems"; + $retval = $GLOBALS['dbi']->fetchResult($query); + } else { + $escdDb = $GLOBALS['dbi']->escapeString($db); + $query = "SHOW " . $routineType . " STATUS WHERE `Db`='$escdDb' "; + if (!empty($searchClause)) { + $query .= "AND `Name` LIKE '%"; + $query .= $GLOBALS['dbi']->escapeString($searchClause); + $query .= "%'"; + } + $handle = $GLOBALS['dbi']->tryQuery($query); + if ($handle !== false) { + $count = 0; + if ($GLOBALS['dbi']->dataSeek($handle, $pos)) { + while ($arr = $GLOBALS['dbi']->fetchArray($handle)) { + if ($count < $maxItems) { + $retval[] = $arr['Name']; + $count++; + } else { + break; + } + } + } + } + } + + return $retval; + } + + /** + * Returns the list of procedures inside this database + * + * @param int $pos The offset of the list within the results + * @param string $searchClause A string used to filter the results of the query + * + * @return array + */ + private function _getProcedures($pos, $searchClause) + { + return $this->_getRoutines('PROCEDURE', $pos, $searchClause); + } + + /** + * Returns the list of functions inside this database + * + * @param int $pos The offset of the list within the results + * @param string $searchClause A string used to filter the results of the query + * + * @return array + */ + private function _getFunctions($pos, $searchClause) + { + return $this->_getRoutines('FUNCTION', $pos, $searchClause); + } + + /** + * Returns the list of events inside this database + * + * @param int $pos The offset of the list within the results + * @param string $searchClause A string used to filter the results of the query + * + * @return array + */ + private function _getEvents($pos, $searchClause) + { + $maxItems = $GLOBALS['cfg']['MaxNavigationItems']; + $retval = array(); + $db = $this->real_name; + if (!$GLOBALS['cfg']['Server']['DisableIS']) { + $escdDb = $GLOBALS['dbi']->escapeString($db); + $query = "SELECT `EVENT_NAME` AS `name` "; + $query .= "FROM `INFORMATION_SCHEMA`.`EVENTS` "; + $query .= "WHERE `EVENT_SCHEMA` " + . Util::getCollateForIS() . "='$escdDb' "; + if (!empty($searchClause)) { + $query .= "AND `EVENT_NAME` LIKE '%"; + $query .= $GLOBALS['dbi']->escapeString($searchClause); + $query .= "%'"; + } + $query .= "ORDER BY `EVENT_NAME` ASC "; + $query .= "LIMIT " . intval($pos) . ", $maxItems"; + $retval = $GLOBALS['dbi']->fetchResult($query); + } else { + $escdDb = Util::backquote($db); + $query = "SHOW EVENTS FROM $escdDb "; + if (!empty($searchClause)) { + $query .= "WHERE `Name` LIKE '%"; + $query .= $GLOBALS['dbi']->escapeString($searchClause); + $query .= "%'"; + } + $handle = $GLOBALS['dbi']->tryQuery($query); + if ($handle !== false) { + $count = 0; + if ($GLOBALS['dbi']->dataSeek($handle, $pos)) { + while ($arr = $GLOBALS['dbi']->fetchArray($handle)) { + if ($count < $maxItems) { + $retval[] = $arr['Name']; + $count++; + } else { + break; + } + } + } + } + } + + return $retval; + } + + /** + * Returns HTML for control buttons displayed infront of a node + * + * @return String HTML for control buttons + */ + public function getHtmlForControlButtons() + { + $ret = ''; + $cfgRelation = $this->relation->getRelationsParam(); + if ($cfgRelation['navwork']) { + if ($this->hiddenCount > 0) { + $params = array( + 'showUnhideDialog' => true, + 'dbName' => $this->real_name, + ); + $ret = '' + . '' + . Util::getImage( + 'show', + __('Show hidden items') + ) + . ''; + } + } + + return $ret; + } + + /** + * Sets the number of hidden items in this database + * + * @param int $count hidden item count + * + * @return void + */ + public function setHiddenCount($count) + { + $this->hiddenCount = $count; + } + + /** + * Returns the number of hidden items in this database + * + * @return int hidden item count + */ + public function getHiddenCount() + { + return $this->hiddenCount; + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Navigation/Nodes/NodeDatabaseChild.php b/php/apps/phpmyadmin49/html/libraries/classes/Navigation/Nodes/NodeDatabaseChild.php new file mode 100644 index 00000000..ecae8fab --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Navigation/Nodes/NodeDatabaseChild.php @@ -0,0 +1,60 @@ +relation->getRelationsParam(); + if ($cfgRelation['navwork']) { + $db = $this->realParent()->real_name; + $item = $this->real_name; + + $params = array( + 'hideNavItem' => true, + 'itemType' => $this->getItemType(), + 'itemName' => $item, + 'dbName' => $db + ); + + $ret = '' + . '' + . Util::getImage('hide', __('Hide')) + . ''; + } + + return $ret; + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Navigation/Nodes/NodeDatabaseChildContainer.php b/php/apps/phpmyadmin49/html/libraries/classes/Navigation/Nodes/NodeDatabaseChildContainer.php new file mode 100644 index 00000000..e11ff697 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Navigation/Nodes/NodeDatabaseChildContainer.php @@ -0,0 +1,43 @@ +separator = $GLOBALS['cfg']['NavigationTreeTableSeparator']; + $this->separator_depth = (int)( + $GLOBALS['cfg']['NavigationTreeTableLevel'] + ); + } + } + + /** + * Returns the type of the item represented by the node. + * + * @return string type of the item + */ + protected function getItemType() + { + return 'group'; + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Navigation/Nodes/NodeDatabaseContainer.php b/php/apps/phpmyadmin49/html/libraries/classes/Navigation/Nodes/NodeDatabaseContainer.php new file mode 100644 index 00000000..33a9913d --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Navigation/Nodes/NodeDatabaseContainer.php @@ -0,0 +1,48 @@ +isNew = true; + $new->icon = Util::getImage('b_newdb', ''); + $new->links = array( + 'text' => 'server_databases.php?server=' . $GLOBALS['server'], + 'icon' => 'server_databases.php?server=' . $GLOBALS['server'], + ); + $new->classes = 'new_database italics'; + $this->addChild($new); + } + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Navigation/Nodes/NodeEvent.php b/php/apps/phpmyadmin49/html/libraries/classes/Navigation/Nodes/NodeEvent.php new file mode 100644 index 00000000..0737fcce --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Navigation/Nodes/NodeEvent.php @@ -0,0 +1,49 @@ +icon = Util::getImage('b_events'); + $this->links = array( + 'text' => 'db_events.php?server=' . $GLOBALS['server'] + . '&db=%2$s&item_name=%1$s&edit_item=1', + 'icon' => 'db_events.php?server=' . $GLOBALS['server'] + . '&db=%2$s&item_name=%1$s&export_item=1', + ); + $this->classes = 'event'; + } + + /** + * Returns the type of the item represented by the node. + * + * @return string type of the item + */ + protected function getItemType() + { + return 'event'; + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Navigation/Nodes/NodeEventContainer.php b/php/apps/phpmyadmin49/html/libraries/classes/Navigation/Nodes/NodeEventContainer.php new file mode 100644 index 00000000..0231c94d --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Navigation/Nodes/NodeEventContainer.php @@ -0,0 +1,50 @@ +icon = Util::getImage('b_events', ''); + $this->links = array( + 'text' => 'db_events.php?server=' . $GLOBALS['server'] + . '&db=%1$s', + 'icon' => 'db_events.php?server=' . $GLOBALS['server'] + . '&db=%1$s', + ); + $this->real_name = 'events'; + + $new = NodeFactory::getInstance( + 'Node', + _pgettext('Create new event', 'New') + ); + $new->isNew = true; + $new->icon = Util::getImage('b_event_add', ''); + $new->links = array( + 'text' => 'db_events.php?server=' . $GLOBALS['server'] + . '&db=%2$s&add_item=1', + 'icon' => 'db_events.php?server=' . $GLOBALS['server'] + . '&db=%2$s&add_item=1', + ); + $new->classes = 'new_event italics'; + $this->addChild($new); + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Navigation/Nodes/NodeFunction.php b/php/apps/phpmyadmin49/html/libraries/classes/Navigation/Nodes/NodeFunction.php new file mode 100644 index 00000000..2f5cd381 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Navigation/Nodes/NodeFunction.php @@ -0,0 +1,51 @@ +icon = Util::getImage('b_routines', __('Function')); + $this->links = array( + 'text' => 'db_routines.php?server=' . $GLOBALS['server'] + . '&db=%2$s&item_name=%1$s&item_type=FUNCTION' + . '&edit_item=1', + 'icon' => 'db_routines.php?server=' . $GLOBALS['server'] + . '&db=%2$s&item_name=%1$s&item_type=FUNCTION' + . '&execute_dialog=1', + ); + $this->classes = 'function'; + } + + /** + * Returns the type of the item represented by the node. + * + * @return string type of the item + */ + protected function getItemType() + { + return 'function'; + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Navigation/Nodes/NodeFunctionContainer.php b/php/apps/phpmyadmin49/html/libraries/classes/Navigation/Nodes/NodeFunctionContainer.php new file mode 100644 index 00000000..bf092edc --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Navigation/Nodes/NodeFunctionContainer.php @@ -0,0 +1,51 @@ +icon = Util::getImage('b_routines', __('Functions')); + $this->links = array( + 'text' => 'db_routines.php?server=' . $GLOBALS['server'] + . '&db=%1$s&type=FUNCTION', + 'icon' => 'db_routines.php?server=' . $GLOBALS['server'] + . '&db=%1$s&type=FUNCTION', + ); + $this->real_name = 'functions'; + + $new_label = _pgettext('Create new function', 'New'); + $new = NodeFactory::getInstance( + 'Node', + $new_label + ); + $new->isNew = true; + $new->icon = Util::getImage('b_routine_add', $new_label); + $new->links = array( + 'text' => 'db_routines.php?server=' . $GLOBALS['server'] + . '&db=%2$s&add_item=1&item_type=FUNCTION', + 'icon' => 'db_routines.php?server=' . $GLOBALS['server'] + . '&db=%2$s&add_item=1&item_type=FUNCTION', + ); + $new->classes = 'new_function italics'; + $this->addChild($new); + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Navigation/Nodes/NodeIndex.php b/php/apps/phpmyadmin49/html/libraries/classes/Navigation/Nodes/NodeIndex.php new file mode 100644 index 00000000..332cc2ce --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Navigation/Nodes/NodeIndex.php @@ -0,0 +1,39 @@ +icon = Util::getImage('b_index', __('Index')); + $this->links = array( + 'text' => 'tbl_indexes.php?server=' . $GLOBALS['server'] + . '&db=%3$s&table=%2$s&index=%1$s', + 'icon' => 'tbl_indexes.php?server=' . $GLOBALS['server'] + . '&db=%3$s&table=%2$s&index=%1$s', + ); + $this->classes = 'index'; + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Navigation/Nodes/NodeIndexContainer.php b/php/apps/phpmyadmin49/html/libraries/classes/Navigation/Nodes/NodeIndexContainer.php new file mode 100644 index 00000000..39ba9ee7 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Navigation/Nodes/NodeIndexContainer.php @@ -0,0 +1,53 @@ +icon = Util::getImage('b_index', __('Indexes')); + $this->links = array( + 'text' => 'tbl_structure.php?server=' . $GLOBALS['server'] + . '&db=%2$s&table=%1$s', + 'icon' => 'tbl_structure.php?server=' . $GLOBALS['server'] + . '&db=%2$s&table=%1$s', + ); + $this->real_name = 'indexes'; + + $new_label = _pgettext('Create new index', 'New'); + $new = NodeFactory::getInstance( + 'Node', + $new_label + ); + $new->isNew = true; + $new->icon = Util::getImage('b_index_add', $new_label); + $new->links = array( + 'text' => 'tbl_indexes.php?server=' . $GLOBALS['server'] + . '&create_index=1&added_fields=2' + . '&db=%3$s&table=%2$s', + 'icon' => 'tbl_indexes.php?server=' . $GLOBALS['server'] + . '&create_index=1&added_fields=2' + . '&db=%3$s&table=%2$s', + ); + $new->classes = 'new_index italics'; + $this->addChild($new); + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Navigation/Nodes/NodeProcedure.php b/php/apps/phpmyadmin49/html/libraries/classes/Navigation/Nodes/NodeProcedure.php new file mode 100644 index 00000000..e9a74eb0 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Navigation/Nodes/NodeProcedure.php @@ -0,0 +1,51 @@ +icon = Util::getImage('b_routines', __('Procedure')); + $this->links = array( + 'text' => 'db_routines.php?server=' . $GLOBALS['server'] + . '&db=%2$s&item_name=%1$s&item_type=PROCEDURE' + . '&edit_item=1', + 'icon' => 'db_routines.php?server=' . $GLOBALS['server'] + . '&db=%2$s&item_name=%1$s&item_type=PROCEDURE' + . '&execute_dialog=1', + ); + $this->classes = 'procedure'; + } + + /** + * Returns the type of the item represented by the node. + * + * @return string type of the item + */ + protected function getItemType() + { + return 'procedure'; + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Navigation/Nodes/NodeProcedureContainer.php b/php/apps/phpmyadmin49/html/libraries/classes/Navigation/Nodes/NodeProcedureContainer.php new file mode 100644 index 00000000..e4eb0fa8 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Navigation/Nodes/NodeProcedureContainer.php @@ -0,0 +1,51 @@ +icon = Util::getImage('b_routines', __('Procedures')); + $this->links = array( + 'text' => 'db_routines.php?server=' . $GLOBALS['server'] + . '&db=%1$s&type=PROCEDURE', + 'icon' => 'db_routines.php?server=' . $GLOBALS['server'] + . '&db=%1$s&type=PROCEDURE', + ); + $this->real_name = 'procedures'; + + $new_label = _pgettext('Create new procedure', 'New'); + $new = NodeFactory::getInstance( + 'Node', + $new_label + ); + $new->isNew = true; + $new->icon = Util::getImage('b_routine_add', $new_label); + $new->links = array( + 'text' => 'db_routines.php?server=' . $GLOBALS['server'] + . '&db=%2$s&add_item=1', + 'icon' => 'db_routines.php?server=' . $GLOBALS['server'] + . '&db=%2$s&add_item=1', + ); + $new->classes = 'new_procedure italics'; + $this->addChild($new); + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Navigation/Nodes/NodeTable.php b/php/apps/phpmyadmin49/html/libraries/classes/Navigation/Nodes/NodeTable.php new file mode 100644 index 00000000..cafe3065 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Navigation/Nodes/NodeTable.php @@ -0,0 +1,305 @@ +icon = array(); + $this->_addIcon( + Util::getScriptNameForOption( + $GLOBALS['cfg']['NavigationTreeDefaultTabTable'], + 'table' + ) + ); + $this->_addIcon( + Util::getScriptNameForOption( + $GLOBALS['cfg']['NavigationTreeDefaultTabTable2'], + 'table' + ) + ); + $title = Util::getTitleForTarget( + $GLOBALS['cfg']['DefaultTabTable'] + ); + $this->title = $title; + + $script_name = Util::getScriptNameForOption( + $GLOBALS['cfg']['DefaultTabTable'], + 'table' + ); + $this->links = array( + 'text' => $script_name + . '?server=' . $GLOBALS['server'] + . '&db=%2$s&table=%1$s' + . '&pos=0', + 'icon' => array( + Util::getScriptNameForOption( + $GLOBALS['cfg']['NavigationTreeDefaultTabTable'], + 'table' + ) + . '?server=' . $GLOBALS['server'] + . '&db=%2$s&table=%1$s', + Util::getScriptNameForOption( + $GLOBALS['cfg']['NavigationTreeDefaultTabTable2'], + 'table' + ) + . '?server=' . $GLOBALS['server'] + . '&db=%2$s&table=%1$s', + ), + 'title' => $this->title, + ); + $this->classes = 'table'; + } + + /** + * Returns the number of children of type $type present inside this container + * This method is overridden by the PhpMyAdmin\Navigation\Nodes\NodeDatabase + * and PhpMyAdmin\Navigation\Nodes\NodeTable classes + * + * @param string $type The type of item we are looking for + * ('columns' or 'indexes') + * @param string $searchClause A string used to filter the results of the query + * + * @return int + */ + public function getPresence($type = '', $searchClause = '') + { + $retval = 0; + $db = $this->realParent()->real_name; + $table = $this->real_name; + switch ($type) { + case 'columns': + if (!$GLOBALS['cfg']['Server']['DisableIS']) { + $db = $GLOBALS['dbi']->escapeString($db); + $table = $GLOBALS['dbi']->escapeString($table); + $query = "SELECT COUNT(*) "; + $query .= "FROM `INFORMATION_SCHEMA`.`COLUMNS` "; + $query .= "WHERE `TABLE_NAME`='$table' "; + $query .= "AND `TABLE_SCHEMA`='$db'"; + $retval = (int)$GLOBALS['dbi']->fetchValue($query); + } else { + $db = Util::backquote($db); + $table = Util::backquote($table); + $query = "SHOW COLUMNS FROM $table FROM $db"; + $retval = (int)$GLOBALS['dbi']->numRows( + $GLOBALS['dbi']->tryQuery($query) + ); + } + break; + case 'indexes': + $db = Util::backquote($db); + $table = Util::backquote($table); + $query = "SHOW INDEXES FROM $table FROM $db"; + $retval = (int)$GLOBALS['dbi']->numRows( + $GLOBALS['dbi']->tryQuery($query) + ); + break; + case 'triggers': + if (!$GLOBALS['cfg']['Server']['DisableIS']) { + $db = $GLOBALS['dbi']->escapeString($db); + $table = $GLOBALS['dbi']->escapeString($table); + $query = "SELECT COUNT(*) "; + $query .= "FROM `INFORMATION_SCHEMA`.`TRIGGERS` "; + $query .= "WHERE `EVENT_OBJECT_SCHEMA` " + . Util::getCollateForIS() . "='$db' "; + $query .= "AND `EVENT_OBJECT_TABLE` " + . Util::getCollateForIS() . "='$table'"; + $retval = (int)$GLOBALS['dbi']->fetchValue($query); + } else { + $db = Util::backquote($db); + $table = $GLOBALS['dbi']->escapeString($table); + $query = "SHOW TRIGGERS FROM $db WHERE `Table` = '$table'"; + $retval = (int)$GLOBALS['dbi']->numRows( + $GLOBALS['dbi']->tryQuery($query) + ); + } + break; + default: + break; + } + + return $retval; + } + + /** + * Returns the names of children of type $type present inside this container + * This method is overridden by the PhpMyAdmin\Navigation\Nodes\NodeDatabase + * and PhpMyAdmin\Navigation\Nodes\NodeTable classes + * + * @param string $type The type of item we are looking for + * ('tables', 'views', etc) + * @param int $pos The offset of the list within the results + * @param string $searchClause A string used to filter the results of the query + * + * @return array + */ + public function getData($type, $pos, $searchClause = '') + { + $maxItems = $GLOBALS['cfg']['MaxNavigationItems']; + $retval = array(); + $db = $this->realParent()->real_name; + $table = $this->real_name; + switch ($type) { + case 'columns': + if (!$GLOBALS['cfg']['Server']['DisableIS']) { + $db = $GLOBALS['dbi']->escapeString($db); + $table = $GLOBALS['dbi']->escapeString($table); + $query = "SELECT `COLUMN_NAME` AS `name` "; + $query .= "FROM `INFORMATION_SCHEMA`.`COLUMNS` "; + $query .= "WHERE `TABLE_NAME`='$table' "; + $query .= "AND `TABLE_SCHEMA`='$db' "; + $query .= "ORDER BY `COLUMN_NAME` ASC "; + $query .= "LIMIT " . intval($pos) . ", $maxItems"; + $retval = $GLOBALS['dbi']->fetchResult($query); + break; + } + + $db = Util::backquote($db); + $table = Util::backquote($table); + $query = "SHOW COLUMNS FROM $table FROM $db"; + $handle = $GLOBALS['dbi']->tryQuery($query); + if ($handle === false) { + break; + } + + $count = 0; + if ($GLOBALS['dbi']->dataSeek($handle, $pos)) { + while ($arr = $GLOBALS['dbi']->fetchArray($handle)) { + if ($count < $maxItems) { + $retval[] = $arr['Field']; + $count++; + } else { + break; + } + } + } + break; + case 'indexes': + $db = Util::backquote($db); + $table = Util::backquote($table); + $query = "SHOW INDEXES FROM $table FROM $db"; + $handle = $GLOBALS['dbi']->tryQuery($query); + if ($handle === false) { + break; + } + + $count = 0; + while ($arr = $GLOBALS['dbi']->fetchArray($handle)) { + if (in_array($arr['Key_name'], $retval)) { + continue; + } + if ($pos <= 0 && $count < $maxItems) { + $retval[] = $arr['Key_name']; + $count++; + } + $pos--; + } + break; + case 'triggers': + if (!$GLOBALS['cfg']['Server']['DisableIS']) { + $db = $GLOBALS['dbi']->escapeString($db); + $table = $GLOBALS['dbi']->escapeString($table); + $query = "SELECT `TRIGGER_NAME` AS `name` "; + $query .= "FROM `INFORMATION_SCHEMA`.`TRIGGERS` "; + $query .= "WHERE `EVENT_OBJECT_SCHEMA` " + . Util::getCollateForIS() . "='$db' "; + $query .= "AND `EVENT_OBJECT_TABLE` " + . Util::getCollateForIS() . "='$table' "; + $query .= "ORDER BY `TRIGGER_NAME` ASC "; + $query .= "LIMIT " . intval($pos) . ", $maxItems"; + $retval = $GLOBALS['dbi']->fetchResult($query); + break; + } + + $db = Util::backquote($db); + $table = $GLOBALS['dbi']->escapeString($table); + $query = "SHOW TRIGGERS FROM $db WHERE `Table` = '$table'"; + $handle = $GLOBALS['dbi']->tryQuery($query); + if ($handle === false) { + break; + } + + $count = 0; + if ($GLOBALS['dbi']->dataSeek($handle, $pos)) { + while ($arr = $GLOBALS['dbi']->fetchArray($handle)) { + if ($count < $maxItems) { + $retval[] = $arr['Trigger']; + $count++; + } else { + break; + } + } + } + break; + default: + break; + } + + return $retval; + } + + /** + * Returns the type of the item represented by the node. + * + * @return string type of the item + */ + protected function getItemType() + { + return 'table'; + } + + /** + * Add an icon to navigation tree + * + * @param string $page Page name to redirect + * + * @return void + */ + private function _addIcon($page) + { + if (empty($page)) { + return; + } + + switch ($page) { + case 'tbl_structure.php': + $this->icon[] = Util::getImage('b_props', __('Structure')); + break; + case 'tbl_select.php': + $this->icon[] = Util::getImage('b_search', __('Search')); + break; + case 'tbl_change.php': + $this->icon[] = Util::getImage('b_insrow', __('Insert')); + break; + case 'tbl_sql.php': + $this->icon[] = Util::getImage('b_sql', __('SQL')); + break; + case 'sql.php': + $this->icon[] = Util::getImage('b_browse', __('Browse')); + break; + } + } +} + diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Navigation/Nodes/NodeTableContainer.php b/php/apps/phpmyadmin49/html/libraries/classes/Navigation/Nodes/NodeTableContainer.php new file mode 100644 index 00000000..e0a30cf7 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Navigation/Nodes/NodeTableContainer.php @@ -0,0 +1,52 @@ +icon = Util::getImage('b_browse', __('Tables')); + $this->links = array( + 'text' => 'db_structure.php?server=' . $GLOBALS['server'] + . '&db=%1$s&tbl_type=table', + 'icon' => 'db_structure.php?server=' . $GLOBALS['server'] + . '&db=%1$s&tbl_type=table', + ); + $this->real_name = 'tables'; + $this->classes = 'tableContainer subContainer'; + + $new_label = _pgettext('Create new table', 'New'); + $new = NodeFactory::getInstance( + 'Node', + $new_label + ); + $new->isNew = true; + $new->icon = Util::getImage('b_table_add', $new_label); + $new->links = array( + 'text' => 'tbl_create.php?server=' . $GLOBALS['server'] + . '&db=%2$s', + 'icon' => 'tbl_create.php?server=' . $GLOBALS['server'] + . '&db=%2$s', + ); + $new->classes = 'new_table italics'; + $this->addChild($new); + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Navigation/Nodes/NodeTrigger.php b/php/apps/phpmyadmin49/html/libraries/classes/Navigation/Nodes/NodeTrigger.php new file mode 100644 index 00000000..af88444f --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Navigation/Nodes/NodeTrigger.php @@ -0,0 +1,39 @@ +icon = Util::getImage('b_triggers'); + $this->links = array( + 'text' => 'db_triggers.php?server=' . $GLOBALS['server'] + . '&db=%3$s&item_name=%1$s&edit_item=1', + 'icon' => 'db_triggers.php?server=' . $GLOBALS['server'] + . '&db=%3$s&item_name=%1$s&export_item=1', + ); + $this->classes = 'trigger'; + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Navigation/Nodes/NodeTriggerContainer.php b/php/apps/phpmyadmin49/html/libraries/classes/Navigation/Nodes/NodeTriggerContainer.php new file mode 100644 index 00000000..77cfc731 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Navigation/Nodes/NodeTriggerContainer.php @@ -0,0 +1,50 @@ +icon = Util::getImage('b_triggers'); + $this->links = array( + 'text' => 'db_triggers.php?server=' . $GLOBALS['server'] + . '&db=%2$s&table=%1$s', + 'icon' => 'db_triggers.php?server=' . $GLOBALS['server'] + . '&db=%2$s&table=%1$s', + ); + $this->real_name = 'triggers'; + + $new = NodeFactory::getInstance( + 'Node', + _pgettext('Create new trigger', 'New') + ); + $new->isNew = true; + $new->icon = Util::getImage('b_trigger_add', ''); + $new->links = array( + 'text' => 'db_triggers.php?server=' . $GLOBALS['server'] + . '&db=%3$s&add_item=1', + 'icon' => 'db_triggers.php?server=' . $GLOBALS['server'] + . '&db=%3$s&add_item=1', + ); + $new->classes = 'new_trigger italics'; + $this->addChild($new); + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Navigation/Nodes/NodeView.php b/php/apps/phpmyadmin49/html/libraries/classes/Navigation/Nodes/NodeView.php new file mode 100644 index 00000000..e7dd5185 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Navigation/Nodes/NodeView.php @@ -0,0 +1,49 @@ +icon = Util::getImage('b_props', __('View')); + $this->links = array( + 'text' => 'sql.php?server=' . $GLOBALS['server'] + . '&db=%2$s&table=%1$s&pos=0', + 'icon' => 'tbl_structure.php?server=' . $GLOBALS['server'] + . '&db=%2$s&table=%1$s', + ); + $this->classes = 'view'; + } + + /** + * Returns the type of the item represented by the node. + * + * @return string type of the item + */ + protected function getItemType() + { + return 'view'; + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Navigation/Nodes/NodeViewContainer.php b/php/apps/phpmyadmin49/html/libraries/classes/Navigation/Nodes/NodeViewContainer.php new file mode 100644 index 00000000..dfb97c49 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Navigation/Nodes/NodeViewContainer.php @@ -0,0 +1,52 @@ +icon = Util::getImage('b_views', __('Views')); + $this->links = array( + 'text' => 'db_structure.php?server=' . $GLOBALS['server'] + . '&db=%1$s&tbl_type=view', + 'icon' => 'db_structure.php?server=' . $GLOBALS['server'] + . '&db=%1$s&tbl_type=view', + ); + $this->classes = 'viewContainer subContainer'; + $this->real_name = 'views'; + + $new_label = _pgettext('Create new view', 'New'); + $new = NodeFactory::getInstance( + 'Node', + $new_label + ); + $new->isNew = true; + $new->icon = Util::getImage('b_view_add', $new_label); + $new->links = array( + 'text' => 'view_create.php?server=' . $GLOBALS['server'] + . '&db=%2$s', + 'icon' => 'view_create.php?server=' . $GLOBALS['server'] + . '&db=%2$s', + ); + $new->classes = 'new_view italics'; + $this->addChild($new); + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Normalization.php b/php/apps/phpmyadmin49/html/libraries/classes/Normalization.php new file mode 100644 index 00000000..76d541d6 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Normalization.php @@ -0,0 +1,1071 @@ +dbi = $dbi; + $this->relation = new Relation(); + } + + /** + * build the html for columns of $colTypeCategory category + * in form of given $listType in a table + * + * @param string $db current database + * @param string $table current table + * @param string $colTypeCategory supported all|Numeric|String|Spatial + * |Date and time using the _pgettext() format + * @param string $listType type of list to build, supported dropdown|checkbox + * + * @return string HTML for list of columns in form of given list types + */ + public function getHtmlForColumnsList( + $db, + $table, + $colTypeCategory = 'all', + $listType = 'dropdown' + ) { + $columnTypeList = []; + if ($colTypeCategory != 'all') { + $types = $this->dbi->types->getColumns(); + $columnTypeList = $types[$colTypeCategory]; + } + $this->dbi->selectDb($db); + $columns = $this->dbi->getColumns( + $db, + $table, + null, + true + ); + $type = ""; + $selectColHtml = ""; + foreach ($columns as $column => $def) { + if (isset($def['Type'])) { + $extractedColumnSpec = Util::extractColumnSpec($def['Type']); + $type = $extractedColumnSpec['type']; + } + if (empty($columnTypeList) + || in_array(mb_strtoupper($type), $columnTypeList) + ) { + if ($listType == 'checkbox') { + $selectColHtml .= '' + . htmlspecialchars($column) . ' [ ' + . htmlspecialchars($def['Type']) . ' ]
    '; + } else { + $selectColHtml .= ''; + } + } + } + return $selectColHtml; + } + + /** + * get the html of the form to add the new column to given table + * + * @param integer $numFields number of columns to add + * @param string $db current database + * @param string $table current table + * @param array $columnMeta array containing default values for the fields + * + * @return string HTML + */ + public function getHtmlForCreateNewColumn( + $numFields, + $db, + $table, + array $columnMeta = [] + ) { + $cfgRelation = $this->relation->getRelationsParam(); + $contentCells = []; + $availableMime = []; + $mimeMap = []; + if ($cfgRelation['mimework'] && $GLOBALS['cfg']['BrowseMIME']) { + $mimeMap = Transformations::getMIME($db, $table); + $availableMime = Transformations::getAvailableMIMEtypes(); + } + $commentsMap = $this->relation->getComments($db, $table); + for ($columnNumber = 0; $columnNumber < $numFields; $columnNumber++) { + $contentCells[$columnNumber] = [ + 'column_number' => $columnNumber, + 'column_meta' => $columnMeta, + 'type_upper' => '', + 'length_values_input_size' => 8, + 'length' => '', + 'extracted_columnspec' => [], + 'submit_attribute' => null, + 'comments_map' => $commentsMap, + 'fields_meta' => null, + 'is_backup' => true, + 'move_columns' => [], + 'cfg_relation' => $cfgRelation, + 'available_mime' => isset($availableMime) ? $availableMime : [], + 'mime_map' => $mimeMap + ]; + } + + return Template::get( + 'columns_definitions/table_fields_definitions' + )->render([ + 'is_backup' => true, + 'fields_meta' => null, + 'mimework' => $cfgRelation['mimework'], + 'content_cells' => $contentCells, + 'change_column' => $_POST['change_column'], + 'is_virtual_columns_supported' => Util::isVirtualColumnsSupported(), + 'browse_mime' => $GLOBALS['cfg']['BrowseMIME'], + 'server_type' => Util::getServerType(), + 'max_rows' => intval($GLOBALS['cfg']['MaxRows']), + 'char_editing' => $GLOBALS['cfg']['CharEditing'], + 'attribute_types' => $this->dbi->types->getAttributes(), + 'privs_available' => $GLOBALS['col_priv'] && $GLOBALS['is_reload_priv'], + 'max_length' => $this->dbi->getVersion() >= 50503 ? 1024 : 255, + 'dbi' => $this->dbi, + 'disable_is' => $GLOBALS['cfg']['Server']['DisableIS'], + ]); + } + + /** + * build the html for step 1.1 of normalization + * + * @param string $db current database + * @param string $table current table + * @param string $normalizedTo up to which step normalization will go, + * possible values 1nf|2nf|3nf + * + * @return string HTML for step 1.1 + */ + public function getHtmlFor1NFStep1($db, $table, $normalizedTo) + { + $step = 1; + $stepTxt = __('Make all columns atomic'); + $html = "

    " + . __('First step of normalization (1NF)') . "

    "; + $html .= "
    " . + "
    " . + "" . __('Step 1.') . $step . " " . $stepTxt . "" . + "

    " . __( + 'Do you have any column which can be split into more than' + . ' one column? ' + . 'For example: address can be split into street, city, country and zip.' + ) + . "
    ( " + . __( + 'Show me the central list of columns that are not already in this table' + ) . " )

    " + . "

    " . __( + 'Select a column which can be split into more ' + . 'than one (on select of \'no such column\', it\'ll move to next step).' + ) + . "

    " + . "
    " + . "" + . "" . __('split into ') + . "" + . "
    " + . "
    " + . "
    " + . "
    " + . "
    "; + return $html; + } + + /** + * build the html contents of various html elements in step 1.2 + * + * @param string $db current database + * @param string $table current table + * + * @return string HTML contents for step 1.2 + */ + public function getHtmlContentsFor1NFStep2($db, $table) + { + $step = 2; + $stepTxt = __('Have a primary key'); + $primary = Index::getPrimary($table, $db); + $hasPrimaryKey = "0"; + $legendText = __('Step 1.') . $step . " " . $stepTxt; + $extra = ''; + if ($primary) { + $headText = __("Primary key already exists."); + $subText = __("Taking you to next step…"); + $hasPrimaryKey = "1"; + } else { + $headText = __( + "There is no primary key; please add one.
    " + . "Hint: A primary key is a column " + . "(or combination of columns) that uniquely identify all rows." + ); + $subText = '' + . Util::getIcon( + 'b_index_add', + __( + 'Add a primary key on existing column(s)' + ) + ) + . ''; + $extra = __( + "If it's not possible to make existing " + . "column combinations as primary key" + ) . "
    " + . '' + . __('+ Add a new primary key column') . ''; + } + $res = [ + 'legendText' => $legendText, + 'headText' => $headText, + 'subText' => $subText, + 'hasPrimaryKey' => $hasPrimaryKey, + 'extra' => $extra + ]; + return $res; + } + + /** + * build the html contents of various html elements in step 1.4 + * + * @param string $db current database + * @param string $table current table + * + * @return string HTML contents for step 1.4 + */ + public function getHtmlContentsFor1NFStep4($db, $table) + { + $step = 4; + $stepTxt = __('Remove redundant columns'); + $legendText = __('Step 1.') . $step . " " . $stepTxt; + $headText = __( + "Do you have a group of columns which on combining gives an existing" + . " column? For example, if you have first_name, last_name and" + . " full_name then combining first_name and last_name gives full_name" + . " which is redundant." + ); + $subText = __( + "Check the columns which are redundant and click on remove. " + . "If no redundant column, click on 'No redundant column'" + ); + $extra = $this->getHtmlForColumnsList($db, $table, 'all', "checkbox") . "
    " + . '' + . ''; + $res = [ + 'legendText' => $legendText, + 'headText' => $headText, + 'subText' => $subText, + 'extra' => $extra + ]; + return $res; + } + + /** + * build the html contents of various html elements in step 1.3 + * + * @param string $db current database + * @param string $table current table + * + * @return string HTML contents for step 1.3 + */ + public function getHtmlContentsFor1NFStep3($db, $table) + { + $step = 3; + $stepTxt = __('Move repeating groups'); + $legendText = __('Step 1.') . $step . " " . $stepTxt; + $headText = __( + "Do you have a group of two or more columns that are closely " + . "related and are all repeating the same attribute? For example, " + . "a table that holds data on books might have columns such as book_id, " + . "author1, author2, author3 and so on which form a " + . "repeating group. In this case a new table (book_id, author) should " + . "be created." + ); + $subText = __( + "Check the columns which form a repeating group. " + . "If no such group, click on 'No repeating group'" + ); + $extra = $this->getHtmlForColumnsList($db, $table, 'all', "checkbox") . "
    " + . '' + . ''; + $primary = Index::getPrimary($table, $db); + $primarycols = $primary->getColumns(); + $pk = []; + foreach ($primarycols as $col) { + $pk[] = $col->getName(); + } + $res = [ + 'legendText' => $legendText, + 'headText' => $headText, + 'subText' => $subText, + 'extra' => $extra, + 'primary_key' => json_encode($pk) + ]; + return $res; + } + + /** + * build html contents for 2NF step 2.1 + * + * @param string $db current database + * @param string $table current table + * + * @return string HTML contents for 2NF step 2.1 + */ + public function getHtmlFor2NFstep1($db, $table) + { + $legendText = __('Step 2.') . "1 " . __('Find partial dependencies'); + $primary = Index::getPrimary($table, $db); + $primarycols = $primary->getColumns(); + $pk = []; + $subText = ''; + $selectPkForm = ""; + $extra = ""; + foreach ($primarycols as $col) { + $pk[] = $col->getName(); + $selectPkForm .= '' + . htmlspecialchars($col->getName()); + } + $key = implode(', ', $pk); + if (count($primarycols) > 1) { + $this->dbi->selectDb($db); + $columns = (array) $this->dbi->getColumnNames( + $db, + $table + ); + if (count($pk) == count($columns)) { + $headText = sprintf( + __( + 'No partial dependencies possible as ' + . 'no non-primary column exists since primary key ( %1$s ) ' + . 'is composed of all the columns in the table.' + ), + htmlspecialchars($key) + ) . '
    '; + $extra = '

    ' . __('Table is already in second normal form.') + . '

    '; + } else { + $headText = sprintf( + __( + 'The primary key ( %1$s ) consists of more than one column ' + . 'so we need to find the partial dependencies.' + ), + htmlspecialchars($key) + ) . '
    ' . __( + 'Please answer the following question(s) ' + . 'carefully to obtain a correct normalization.' + ) + . '
    ' . __( + '+ Show me the possible partial dependencies ' + . 'based on data in the table' + ) . ''; + $subText = __( + 'For each column below, ' + . 'please select the minimal set of columns among given set ' + . 'whose values combined together are sufficient' + . ' to determine the value of the column.' + ); + $cnt = 0; + foreach ($columns as $column) { + if (!in_array($column, $pk)) { + $cnt++; + $extra .= "" . sprintf( + __('\'%1$s\' depends on:'), + htmlspecialchars($column) + ) . "
    "; + $extra .= '
    ' + . $selectPkForm . '


    '; + } + } + } + } else { + $headText = sprintf( + __( + 'No partial dependencies possible as the primary key' + . ' ( %1$s ) has just one column.' + ), + htmlspecialchars($key) + ) . '
    '; + $extra = '

    ' . __('Table is already in second normal form.') . '

    '; + } + $res = [ + 'legendText' => $legendText, + 'headText' => $headText, + 'subText' => $subText, + 'extra' => $extra, + 'primary_key' => $key + ]; + return $res; + } + + /** + * build the html for showing the tables to have in order to put current table in 2NF + * + * @param array $partialDependencies array containing all the dependencies + * @param string $table current table + * + * @return string HTML + */ + public function getHtmlForNewTables2NF(array $partialDependencies, $table) + { + $html = '

    ' . sprintf( + __( + 'In order to put the ' + . 'original table \'%1$s\' into Second normal form we need ' + . 'to create the following tables:' + ), + htmlspecialchars($table) + ) . '

    '; + $tableName = $table; + $i = 1; + foreach ($partialDependencies as $key => $dependents) { + $html .= '

    ' + . '( ' . htmlspecialchars($key) . '' + . (count($dependents)>0?', ':'') + . htmlspecialchars(implode(', ', $dependents)) . ' )'; + $i++; + $tableName = 'table' . $i; + } + return $html; + } + + /** + * create/alter the tables needed for 2NF + * + * @param array $partialDependencies array containing all the partial dependencies + * @param object $tablesName name of new tables + * @param string $table current table + * @param string $db current database + * + * @return array + */ + public function createNewTablesFor2NF(array $partialDependencies, $tablesName, $table, $db) + { + $dropCols = false; + $nonPKCols = []; + $queries = []; + $error = false; + $headText = '

    ' . sprintf( + __('The second step of normalization is complete for table \'%1$s\'.'), + htmlspecialchars($table) + ) . '

    '; + if (count((array)$partialDependencies) == 1) { + return [ + 'legendText'=>__('End of step'), 'headText'=>$headText, + 'queryError'=>$error + ]; + } + $message = ''; + $this->dbi->selectDb($db); + foreach ($partialDependencies as $key => $dependents) { + if ($tablesName->$key != $table) { + $backquotedKey = implode(', ', Util::backquote(explode(', ', $key))); + $queries[] = 'CREATE TABLE ' . Util::backquote($tablesName->$key) + . ' SELECT DISTINCT ' . $backquotedKey + . (count($dependents)>0?', ':'') + . implode(',', Util::backquote($dependents)) + . ' FROM ' . Util::backquote($table) . ';'; + $queries[] = 'ALTER TABLE ' . Util::backquote($tablesName->$key) + . ' ADD PRIMARY KEY(' . $backquotedKey . ');'; + $nonPKCols = array_merge($nonPKCols, $dependents); + } else { + $dropCols = true; + } + } + + if ($dropCols) { + $query = 'ALTER TABLE ' . Util::backquote($table); + foreach ($nonPKCols as $col) { + $query .= ' DROP ' . Util::backquote($col) . ','; + } + $query = trim($query, ', '); + $query .= ';'; + $queries[] = $query; + } else { + $queries[] = 'DROP TABLE ' . Util::backquote($table); + } + foreach ($queries as $query) { + if (!$this->dbi->tryQuery($query)) { + $message = Message::error(__('Error in processing!')); + $message->addMessage( + Message::rawError( + $this->dbi->getError() + ), + '

    ' + ); + $error = true; + break; + } + } + return [ + 'legendText' => __('End of step'), + 'headText' => $headText, + 'queryError' => $error, + 'extra' => $message + ]; + } + + /** + * build the html for showing the new tables to have in order + * to put given tables in 3NF + * + * @param object $dependencies containing all the dependencies + * @param array $tables tables formed after 2NF and need to convert to 3NF + * @param string $db current database + * + * @return array containing html and the list of new tables + */ + public function getHtmlForNewTables3NF($dependencies, array $tables, $db) + { + $html = ""; + $i = 1; + $newTables = []; + foreach ($tables as $table => $arrDependson) { + if (count(array_unique($arrDependson)) == 1) { + continue; + } + $primary = Index::getPrimary($table, $db); + $primarycols = $primary->getColumns(); + $pk = []; + foreach ($primarycols as $col) { + $pk[] = $col->getName(); + } + $html .= '

    ' . sprintf( + __( + 'In order to put the ' + . 'original table \'%1$s\' into Third normal form we need ' + . 'to create the following tables:' + ), + htmlspecialchars($table) + ) . '

    '; + $tableName = $table; + $columnList = []; + foreach ($arrDependson as $key) { + $dependents = $dependencies->$key; + if ($key == $table) { + $key = implode(', ', $pk); + } + $tmpTableCols =array_merge(explode(', ', $key), $dependents); + sort($tmpTableCols); + if (!in_array($tmpTableCols, $columnList)) { + $columnList[] = $tmpTableCols; + $html .= '

    ' + . '( ' . htmlspecialchars($key) . '' + . (count($dependents)>0?', ':'') + . htmlspecialchars(implode(', ', $dependents)) . ' )'; + $newTables[$table][$tableName] = [ + "pk"=>$key, "nonpk"=>implode(', ', $dependents) + ]; + $i++; + $tableName = 'table' . $i; + } + } + } + return ['html' => $html, 'newTables' => $newTables, 'success' => true]; + } + + /** + * create new tables or alter existing to get 3NF + * + * @param array $newTables list of new tables to be created + * @param string $db current database + * + * @return array + */ + public function createNewTablesFor3NF(array $newTables, $db) + { + $queries = []; + $dropCols = false; + $error = false; + $headText = '

    ' . + __('The third step of normalization is complete.') + . '

    '; + if (count((array)$newTables) == 0) { + return [ + 'legendText'=>__('End of step'), 'headText'=>$headText, + 'queryError'=>$error + ]; + } + $message = ''; + $this->dbi->selectDb($db); + foreach ($newTables as $originalTable => $tablesList) { + foreach ($tablesList as $table => $cols) { + if ($table != $originalTable) { + $quotedPk = implode( + ', ', + Util::backquote(explode(', ', $cols->pk)) + ); + $quotedNonpk = implode( + ', ', + Util::backquote(explode(', ', $cols->nonpk)) + ); + $queries[] = 'CREATE TABLE ' . Util::backquote($table) + . ' SELECT DISTINCT ' . $quotedPk + . ', ' . $quotedNonpk + . ' FROM ' . Util::backquote($originalTable) . ';'; + $queries[] = 'ALTER TABLE ' . Util::backquote($table) + . ' ADD PRIMARY KEY(' . $quotedPk . ');'; + } else { + $dropCols = $cols; + } + } + if ($dropCols) { + $columns = (array) $this->dbi->getColumnNames( + $db, + $originalTable + ); + $colPresent = array_merge( + explode(', ', $dropCols->pk), + explode(', ', $dropCols->nonpk) + ); + $query = 'ALTER TABLE ' . Util::backquote($originalTable); + foreach ($columns as $col) { + if (!in_array($col, $colPresent)) { + $query .= ' DROP ' . Util::backquote($col) . ','; + } + } + $query = trim($query, ', '); + $query .= ';'; + $queries[] = $query; + } else { + $queries[] = 'DROP TABLE ' . Util::backquote($originalTable); + } + $dropCols = false; + } + foreach ($queries as $query) { + if (!$this->dbi->tryQuery($query)) { + $message = Message::error(__('Error in processing!')); + $message->addMessage( + Message::rawError( + $this->dbi->getError() + ), + '

    ' + ); + $error = true; + break; + } + } + return [ + 'legendText' => __('End of step'), + 'headText' => $headText, + 'queryError' => $error, + 'extra' => $message + ]; + } + + /** + * move the repeating group of columns to a new table + * + * @param string $repeatingColumns comma separated list of repeating group columns + * @param string $primaryColumns comma separated list of column in primary key + * of $table + * @param string $newTable name of the new table to be created + * @param string $newColumn name of the new column in the new table + * @param string $table current table + * @param string $db current database + * + * @return array + */ + public function moveRepeatingGroup( + $repeatingColumns, + $primaryColumns, + $newTable, + $newColumn, + $table, + $db + ) { + $repeatingColumnsArr = (array)Util::backquote( + explode(', ', $repeatingColumns) + ); + $primaryColumns = implode( + ',', + Util::backquote(explode(',', $primaryColumns)) + ); + $query1 = 'CREATE TABLE ' . Util::backquote($newTable); + $query2 = 'ALTER TABLE ' . Util::backquote($table); + $message = Message::success( + sprintf( + __('Selected repeating group has been moved to the table \'%s\''), + htmlspecialchars($table) + ) + ); + $first = true; + $error = false; + foreach ($repeatingColumnsArr as $repeatingColumn) { + if (!$first) { + $query1 .= ' UNION '; + } + $first = false; + $query1 .= ' SELECT ' . $primaryColumns . ',' . $repeatingColumn + . ' as ' . Util::backquote($newColumn) + . ' FROM ' . Util::backquote($table); + $query2 .= ' DROP ' . $repeatingColumn . ','; + } + $query2 = trim($query2, ','); + $queries = [$query1, $query2]; + $this->dbi->selectDb($db); + foreach ($queries as $query) { + if (!$this->dbi->tryQuery($query)) { + $message = Message::error(__('Error in processing!')); + $message->addMessage( + Message::rawError( + $this->dbi->getError() + ), + '

    ' + ); + $error = true; + break; + } + } + return [ + 'queryError' => $error, 'message' => $message + ]; + } + + /** + * build html for 3NF step 1 to find the transitive dependencies + * + * @param string $db current database + * @param array $tables tables formed after 2NF and need to process for 3NF + * + * @return string + */ + public function getHtmlFor3NFstep1($db, array $tables) + { + $legendText = __('Step 3.') . "1 " . __('Find transitive dependencies'); + $extra = ""; + $headText = __( + 'Please answer the following question(s) ' + . 'carefully to obtain a correct normalization.' + ); + $subText = __( + 'For each column below, ' + . 'please select the minimal set of columns among given set ' + . 'whose values combined together are sufficient' + . ' to determine the value of the column.
    ' + . 'Note: A column may have no transitive dependency, ' + . 'in that case you don\'t have to select any.' + ); + $cnt = 0; + foreach ($tables as $table) { + $primary = Index::getPrimary($table, $db); + $primarycols = $primary->getColumns(); + $selectTdForm = ""; + $pk = []; + foreach ($primarycols as $col) { + $pk[] = $col->getName(); + } + $this->dbi->selectDb($db); + $columns = (array) $this->dbi->getColumnNames( + $db, + $table + ); + if (count($columns) - count($pk) <= 1) { + continue; + } + foreach ($columns as $column) { + if (!in_array($column, $pk)) { + $selectTdForm .= '' + . '' . htmlspecialchars($column) . ''; + } + } + foreach ($columns as $column) { + if (!in_array($column, $pk)) { + $cnt++; + $extra .= "" . sprintf( + __('\'%1$s\' depends on:'), + htmlspecialchars($column) + ) + . "
    "; + $extra .= '
    ' + . $selectTdForm + . '


    '; + } + } + } + if ($extra == "") { + $headText = __( + "No Transitive dependencies possible as the table " + . "doesn't have any non primary key columns" + ); + $subText = ""; + $extra = "

    " . __("Table is already in Third normal form!") . "

    "; + } + $res = [ + 'legendText' => $legendText, + 'headText' => $headText, + 'subText' => $subText, + 'extra' => $extra + ]; + return $res; + } + + /** + * get html for options to normalize table + * + * @return string HTML + */ + public function getHtmlForNormalizeTable() + { + $htmlOutput = '
    ' + . Url::getHiddenInputs($GLOBALS['db'], $GLOBALS['table']) + . ''; + $htmlOutput .= '
    '; + $htmlOutput .= '' + . __('Improve table structure (Normalization):') . ''; + $htmlOutput .= '

    ' . __('Select up to what step you want to normalize') + . '

    '; + $choices = [ + '1nf' => __('First step of normalization (1NF)'), + '2nf' => __('Second step of normalization (1NF+2NF)'), + '3nf' => __('Third step of normalization (1NF+2NF+3NF)')]; + + $htmlOutput .= Util::getRadioFields( + 'normalizeTo', + $choices, + '1nf', + true + ); + $htmlOutput .= '
    ' + . "" . __( + 'Hint: Please follow the procedure carefully in order ' + . 'to obtain correct normalization' + ) . "" + . '' + . '
    ' + . '
    ' + . '
    '; + + return $htmlOutput; + } + + /** + * find all the possible partial dependencies based on data in the table. + * + * @param string $table current table + * @param string $db current database + * + * @return string HTML containing the list of all the possible partial dependencies + */ + public function findPartialDependencies($table, $db) + { + $dependencyList = []; + $this->dbi->selectDb($db); + $columns = (array) $this->dbi->getColumnNames( + $db, + $table + ); + $columns = (array)Util::backquote($columns); + $totalRowsRes = $this->dbi->fetchResult( + 'SELECT COUNT(*) FROM (SELECT * FROM ' + . Util::backquote($table) . ' LIMIT 500) as dt;' + ); + $totalRows = $totalRowsRes[0]; + $primary = Index::getPrimary($table, $db); + $primarycols = $primary->getColumns(); + $pk = []; + foreach ($primarycols as $col) { + $pk[] = Util::backquote($col->getName()); + } + $partialKeys = $this->getAllCombinationPartialKeys($pk); + $distinctValCount = $this->findDistinctValuesCount( + array_unique( + array_merge($columns, $partialKeys) + ), + $table + ); + foreach ($columns as $column) { + if (!in_array($column, $pk)) { + foreach ($partialKeys as $partialKey) { + if ($partialKey + && $this->checkPartialDependency( + $partialKey, + $column, + $table, + $distinctValCount[$partialKey], + $distinctValCount[$column], + $totalRows + ) + ) { + $dependencyList[$partialKey][] = $column; + } + } + } + } + + $html = __( + 'This list is based on a subset of the table\'s data ' + . 'and is not necessarily accurate. ' + ) + . '
    '; + foreach ($dependencyList as $dependon => $colList) { + $html .= '' + . '' + . '' + . htmlspecialchars(str_replace('`', '', $dependon)) . ' -> ' + . '' + . htmlspecialchars(str_replace('`', '', implode(', ', $colList))) + . '' + . ''; + } + if (empty($dependencyList)) { + $html .= '

    ' + . __('No partial dependencies found!') . '

    '; + } + $html .= '
    '; + return $html; + } + + /** + * check whether a particular column is dependent on given subset of primary key + * + * @param string $partialKey the partial key, subset of primary key, + * each column in key supposed to be backquoted + * @param string $column backquoted column on whose dependency being checked + * @param string $table current table + * @param integer $pkCnt distinct value count for given partial key + * @param integer $colCnt distinct value count for given column + * @param integer $totalRows total distinct rows count of the table + * + * @return boolean TRUE if $column is dependent on $partialKey, False otherwise + */ + private function checkPartialDependency( + $partialKey, + $column, + $table, + $pkCnt, + $colCnt, + $totalRows + ) { + $query = 'SELECT ' + . 'COUNT(DISTINCT ' . $partialKey . ',' . $column . ') as pkColCnt ' + . 'FROM (SELECT * FROM ' . Util::backquote($table) + . ' LIMIT 500) as dt' . ';'; + $res = $this->dbi->fetchResult($query, null, null); + $pkColCnt = $res[0]; + if ($pkCnt && $pkCnt == $colCnt && $colCnt == $pkColCnt) { + return true; + } + if ($totalRows && $totalRows == $pkCnt) { + return true; + } + return false; + } + + /** + * function to get distinct values count of all the column in the array $columns + * + * @param array $columns array of backquoted columns whose distinct values + * need to be counted. + * @param string $table table to which these columns belong + * + * @return array associative array containing the count + */ + private function findDistinctValuesCount(array $columns, $table) + { + $result = []; + $query = 'SELECT '; + foreach ($columns as $column) { + if ($column) { //each column is already backquoted + $query .= 'COUNT(DISTINCT ' . $column . ') as \'' + . $column . '_cnt\', '; + } + } + $query = trim($query, ', '); + $query .= ' FROM (SELECT * FROM ' . Util::backquote($table) + . ' LIMIT 500) as dt' . ';'; + $res = $this->dbi->fetchResult($query, null, null); + foreach ($columns as $column) { + if ($column) { + $result[$column] = $res[0][$column . '_cnt']; + } + } + return $result; + } + + /** + * find all the possible partial keys + * + * @param array $primaryKey array containing all the column present in primary key + * + * @return array containing all the possible partial keys(subset of primary key) + */ + private function getAllCombinationPartialKeys(array $primaryKey) + { + $results = ['']; + foreach ($primaryKey as $element) { + foreach ($results as $combination) { + array_push( + $results, + trim($element . ',' . $combination, ',') + ); + } + } + array_pop($results); //remove key which consist of all primary key columns + return $results; + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/OpenDocument.php b/php/apps/phpmyadmin49/html/libraries/classes/OpenDocument.php new file mode 100644 index 00000000..77104683 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/OpenDocument.php @@ -0,0 +1,177 @@ +' + . '' + . '' + . 'phpMyAdmin ' . PMA_VERSION . '' + . 'phpMyAdmin ' . PMA_VERSION + . '' + . '' . strftime('%Y-%m-%dT%H:%M:%S') + . '' + . '' + . '', + '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '', + '' + . '' + . '' + . '' + . '' + . '' + . '' + ); + + $name = array( + 'mimetype', + 'content.xml', + 'meta.xml', + 'styles.xml', + 'META-INF/manifest.xml' + ); + + $zipExtension = new ZipExtension(); + return $zipExtension->createFile($data, $name); + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Operations.php b/php/apps/phpmyadmin49/html/libraries/classes/Operations.php new file mode 100644 index 00000000..1cbd8e6a --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Operations.php @@ -0,0 +1,2158 @@ +relation = new Relation(); + } + + /** + * Get HTML output for database comment + * + * @param string $db database name + * + * @return string $html_output + */ + public function getHtmlForDatabaseComment($db) + { + $html_output = '
    ' + . '
    ' + . Url::getHiddenInputs($db) + . '
    ' + . ''; + if (Util::showIcons('ActionLinksMode')) { + $html_output .= Util::getImage('b_comment') . ' '; + } + $html_output .= __('Database comment'); + $html_output .= ''; + $html_output .= '' + . '
    '; + $html_output .= '
    ' + . '' + . '
    ' + . '
    ' + . '
    '; + + return $html_output; + } + + /** + * Get HTML output for rename database + * + * @param string $db database name + * @param string $db_collation dataset collation + * + * @return string $html_output + */ + public function getHtmlForRenameDatabase($db, $db_collation) + { + $html_output = '
    ' + . '
    '; + if (isset($db_collation)) { + $html_output .= '' . "\n"; + } + $html_output .= '' + . '' + . Url::getHiddenInputs($db) + . '
    ' + . ''; + + if (Util::showIcons('ActionLinksMode')) { + $html_output .= Util::getImage('b_edit') . ' '; + } + $html_output .= __('Rename database to') + . ''; + + $html_output .= ''; + $html_output .= '
    '; + + if ($GLOBALS['db_priv'] && $GLOBALS['table_priv'] + && $GLOBALS['col_priv'] && $GLOBALS['proc_priv'] + && $GLOBALS['is_reload_priv'] + ) { + $html_output .= ''; + } else { + $html_output .= ''; + } + + $html_output .= '
    '; + + $html_output .= '' + . '
    ' + . '
    ' + . '' + . '
    ' + . '
    ' + . '
    '; + + return $html_output; + } + + /** + * Get HTML for database drop link + * + * @param string $db database name + * + * @return string $html_output + */ + public function getHtmlForDropDatabaseLink($db) + { + $this_sql_query = 'DROP DATABASE ' . Util::backquote($db); + $this_url_params = array( + 'sql_query' => $this_sql_query, + 'back' => 'db_operations.php', + 'goto' => 'index.php', + 'reload' => '1', + 'purge' => '1', + 'message_to_show' => sprintf( + __('Database %s has been dropped.'), + htmlspecialchars(Util::backquote($db)) + ), + 'db' => null, + ); + + $html_output = '
    ' + . '
    '; + $html_output .= ''; + if (Util::showIcons('ActionLinksMode')) { + $html_output .= Util::getImage('b_deltbl') . ' '; + } + $html_output .= __('Remove database') + . ''; + $html_output .= '
      '; + $html_output .= $this->getDeleteDataOrTablelink( + $this_url_params, + 'DROP_DATABASE', + __('Drop the database (DROP)'), + 'drop_db_anchor' + ); + $html_output .= '
    ' + . '
    '; + + return $html_output; + } + + /** + * Get HTML snippet for copy database + * + * @param string $db database name + * @param string $db_collation dataset collation + * + * @return string $html_output + */ + public function getHtmlForCopyDatabase($db, $db_collation) + { + $drop_clause = 'DROP TABLE / DROP VIEW'; + $choices = array( + 'structure' => __('Structure only'), + 'data' => __('Structure and data'), + 'dataonly' => __('Data only') + ); + + $pma_switch_to_new = isset($_SESSION['pma_switch_to_new']) && $_SESSION['pma_switch_to_new']; + + $html_output = '
    '; + $html_output .= '
    '; + + if (isset($db_collation)) { + $html_output .= '' . "\n"; + } + $html_output .= '' . "\n" + . Url::getHiddenInputs($db); + $html_output .= '
    ' + . ''; + + if (Util::showIcons('ActionLinksMode')) { + $html_output .= Util::getImage('b_edit') . ' '; + } + $html_output .= __('Copy database to') + . '' + . '
    ' + . Util::getRadioFields( + 'what', $choices, 'data', true + ); + $html_output .= '
    '; + $html_output .= ''; + $html_output .= '
    '; + $html_output .= ''; + $html_output .= '
    '; + $html_output .= ''; + $html_output .= '
    '; + $html_output .= ''; + $html_output .= '
    '; + $html_output .= '
    '; + + if ($GLOBALS['db_priv'] && $GLOBALS['table_priv'] + && $GLOBALS['col_priv'] && $GLOBALS['proc_priv'] + && $GLOBALS['is_reload_priv'] + ) { + $html_output .= ''; + } else { + $html_output .= ''; + } + $html_output .= '
    '; + + $html_output .= ''; + $html_output .= '' + . '
    '; + $html_output .= '
    ' + . '' + . '
    ' + . '
    ' + . '
    '; + + return $html_output; + } + + /** + * Get HTML snippet for change database charset + * + * @param string $db database name + * @param string $db_collation dataset collation + * + * @return string $html_output + */ + public function getHtmlForChangeDatabaseCharset($db, $db_collation) + { + $html_output = '
    ' + . '
    '; + if (Util::showIcons('ActionLinksMode')) { + $html_output .= Util::getImage('s_asci') . ' '; + } + $html_output .= '' . "\n" + . '' . "\n" + . Charsets::getCollationDropdownBox( + $GLOBALS['dbi'], + $GLOBALS['cfg']['Server']['DisableIS'], + 'db_collation', + 'select_db_collation', + isset($db_collation) ? $db_collation : '', + false + ) + . '
    ' + . '' + . '' + . '
    ' + . '' + . '' + . '' + . '
    ' + . '' . "\n" + . '
    ' . "\n" + . '
    ' . "\n"; + + return $html_output; + } + + /** + * Run the Procedure definitions and function definitions + * + * to avoid selecting alternatively the current and new db + * we would need to modify the CREATE definitions to qualify + * the db name + * + * @param string $db database name + * + * @return void + */ + public function runProcedureAndFunctionDefinitions($db) + { + $procedure_names = $GLOBALS['dbi']->getProceduresOrFunctions($db, 'PROCEDURE'); + if ($procedure_names) { + foreach ($procedure_names as $procedure_name) { + $GLOBALS['dbi']->selectDb($db); + $tmp_query = $GLOBALS['dbi']->getDefinition( + $db, 'PROCEDURE', $procedure_name + ); + if ($tmp_query !== false) { + // collect for later display + $GLOBALS['sql_query'] .= "\n" . $tmp_query; + $GLOBALS['dbi']->selectDb($_POST['newname']); + $GLOBALS['dbi']->query($tmp_query); + } + } + } + + $function_names = $GLOBALS['dbi']->getProceduresOrFunctions($db, 'FUNCTION'); + if ($function_names) { + foreach ($function_names as $function_name) { + $GLOBALS['dbi']->selectDb($db); + $tmp_query = $GLOBALS['dbi']->getDefinition( + $db, 'FUNCTION', $function_name + ); + if ($tmp_query !== false) { + // collect for later display + $GLOBALS['sql_query'] .= "\n" . $tmp_query; + $GLOBALS['dbi']->selectDb($_POST['newname']); + $GLOBALS['dbi']->query($tmp_query); + } + } + } + } + + /** + * Create database before copy + * + * @return void + */ + public function createDbBeforeCopy() + { + $local_query = 'CREATE DATABASE IF NOT EXISTS ' + . Util::backquote($_POST['newname']); + if (isset($_POST['db_collation'])) { + $local_query .= ' DEFAULT' + . Util::getCharsetQueryPart($_POST['db_collation']); + } + $local_query .= ';'; + $GLOBALS['sql_query'] .= $local_query; + + // save the original db name because Tracker.php which + // may be called under $GLOBALS['dbi']->query() changes $GLOBALS['db'] + // for some statements, one of which being CREATE DATABASE + $original_db = $GLOBALS['db']; + $GLOBALS['dbi']->query($local_query); + $GLOBALS['db'] = $original_db; + + // Set the SQL mode to NO_AUTO_VALUE_ON_ZERO to prevent MySQL from creating + // export statements it cannot import + $sql_set_mode = "SET SQL_MODE='NO_AUTO_VALUE_ON_ZERO'"; + $GLOBALS['dbi']->query($sql_set_mode); + + // rebuild the database list because Table::moveCopy + // checks in this list if the target db exists + $GLOBALS['dblist']->databases->build(); + } + + /** + * Get views as an array and create SQL view stand-in + * + * @param array $tables_full array of all tables in given db or dbs + * @param ExportSql $export_sql_plugin export plugin instance + * @param string $db database name + * + * @return array $views + */ + public function getViewsAndCreateSqlViewStandIn( + array $tables_full, $export_sql_plugin, $db + ) { + $views = array(); + foreach ($tables_full as $each_table => $tmp) { + // to be able to rename a db containing views, + // first all the views are collected and a stand-in is created + // the real views are created after the tables + if ($GLOBALS['dbi']->getTable($db, $each_table)->isView()) { + + // If view exists, and 'add drop view' is selected: Drop it! + if ($_POST['what'] != 'nocopy' + && isset($_POST['drop_if_exists']) + && $_POST['drop_if_exists'] == 'true' + ) { + $drop_query = 'DROP VIEW IF EXISTS ' + . Util::backquote($_POST['newname']) . '.' + . Util::backquote($each_table); + $GLOBALS['dbi']->query($drop_query); + + $GLOBALS['sql_query'] .= "\n" . $drop_query . ';'; + } + + $views[] = $each_table; + // Create stand-in definition to resolve view dependencies + $sql_view_standin = $export_sql_plugin->getTableDefStandIn( + $db, $each_table, "\n" + ); + $GLOBALS['dbi']->selectDb($_POST['newname']); + $GLOBALS['dbi']->query($sql_view_standin); + $GLOBALS['sql_query'] .= "\n" . $sql_view_standin; + } + } + return $views; + } + + /** + * Get sql query for copy/rename table and boolean for whether copy/rename or not + * + * @param array $tables_full array of all tables in given db or dbs + * @param boolean $move whether database name is empty or not + * @param string $db database name + * + * @return array SQL queries for the constraints + */ + public function copyTables(array $tables_full, $move, $db) + { + $sqlContraints = array(); + foreach ($tables_full as $each_table => $tmp) { + // skip the views; we have created stand-in definitions + if ($GLOBALS['dbi']->getTable($db, $each_table)->isView()) { + continue; + } + + // value of $what for this table only + $this_what = $_POST['what']; + + // do not copy the data from a Merge table + // note: on the calling FORM, 'data' means 'structure and data' + if ($GLOBALS['dbi']->getTable($db, $each_table)->isMerge()) { + if ($this_what == 'data') { + $this_what = 'structure'; + } + if ($this_what == 'dataonly') { + $this_what = 'nocopy'; + } + } + + if ($this_what != 'nocopy') { + // keep the triggers from the original db+table + // (third param is empty because delimiters are only intended + // for importing via the mysql client or our Import feature) + $triggers = $GLOBALS['dbi']->getTriggers($db, $each_table, ''); + + if (! Table::moveCopy( + $db, $each_table, $_POST['newname'], $each_table, + (isset($this_what) ? $this_what : 'data'), + $move, 'db_copy' + )) { + $GLOBALS['_error'] = true; + break; + } + // apply the triggers to the destination db+table + if ($triggers) { + $GLOBALS['dbi']->selectDb($_POST['newname']); + foreach ($triggers as $trigger) { + $GLOBALS['dbi']->query($trigger['create']); + $GLOBALS['sql_query'] .= "\n" . $trigger['create'] . ';'; + } + } + + // this does not apply to a rename operation + if (isset($_POST['add_constraints']) + && ! empty($GLOBALS['sql_constraints_query']) + ) { + $sqlContraints[] = $GLOBALS['sql_constraints_query']; + unset($GLOBALS['sql_constraints_query']); + } + } + } + return $sqlContraints; + } + + /** + * Run the EVENT definition for selected database + * + * to avoid selecting alternatively the current and new db + * we would need to modify the CREATE definitions to qualify + * the db name + * + * @param string $db database name + * + * @return void + */ + public function runEventDefinitionsForDb($db) + { + $event_names = $GLOBALS['dbi']->fetchResult( + 'SELECT EVENT_NAME FROM information_schema.EVENTS WHERE EVENT_SCHEMA= \'' + . $GLOBALS['dbi']->escapeString($db) . '\';' + ); + if ($event_names) { + foreach ($event_names as $event_name) { + $GLOBALS['dbi']->selectDb($db); + $tmp_query = $GLOBALS['dbi']->getDefinition($db, 'EVENT', $event_name); + // collect for later display + $GLOBALS['sql_query'] .= "\n" . $tmp_query; + $GLOBALS['dbi']->selectDb($_POST['newname']); + $GLOBALS['dbi']->query($tmp_query); + } + } + } + + /** + * Handle the views, return the boolean value whether table rename/copy or not + * + * @param array $views views as an array + * @param boolean $move whether database name is empty or not + * @param string $db database name + * + * @return void + */ + public function handleTheViews(array $views, $move, $db) + { + // temporarily force to add DROP IF EXIST to CREATE VIEW query, + // to remove stand-in VIEW that was created earlier + // ( $_POST['drop_if_exists'] is used in moveCopy() ) + if (isset($_POST['drop_if_exists'])) { + $temp_drop_if_exists = $_POST['drop_if_exists']; + } + + $_POST['drop_if_exists'] = 'true'; + foreach ($views as $view) { + $copying_succeeded = Table::moveCopy( + $db, $view, $_POST['newname'], $view, 'structure', $move, 'db_copy' + ); + if (! $copying_succeeded) { + $GLOBALS['_error'] = true; + break; + } + } + unset($_POST['drop_if_exists']); + + if (isset($temp_drop_if_exists)) { + // restore previous value + $_POST['drop_if_exists'] = $temp_drop_if_exists; + } + } + + /** + * Adjust the privileges after Renaming the db + * + * @param string $oldDb Database name before renaming + * @param string $newname New Database name requested + * + * @return void + */ + public function adjustPrivilegesMoveDb($oldDb, $newname) + { + if ($GLOBALS['db_priv'] && $GLOBALS['table_priv'] + && $GLOBALS['col_priv'] && $GLOBALS['proc_priv'] + && $GLOBALS['is_reload_priv'] + ) { + $GLOBALS['dbi']->selectDb('mysql'); + $newname = str_replace("_", "\_", $newname); + $oldDb = str_replace("_", "\_", $oldDb); + + // For Db specific privileges + $query_db_specific = 'UPDATE ' . Util::backquote('db') + . 'SET Db = \'' . $GLOBALS['dbi']->escapeString($newname) + . '\' where Db = \'' . $GLOBALS['dbi']->escapeString($oldDb) . '\';'; + $GLOBALS['dbi']->query($query_db_specific); + + // For table specific privileges + $query_table_specific = 'UPDATE ' . Util::backquote('tables_priv') + . 'SET Db = \'' . $GLOBALS['dbi']->escapeString($newname) + . '\' where Db = \'' . $GLOBALS['dbi']->escapeString($oldDb) . '\';'; + $GLOBALS['dbi']->query($query_table_specific); + + // For column specific privileges + $query_col_specific = 'UPDATE ' . Util::backquote('columns_priv') + . 'SET Db = \'' . $GLOBALS['dbi']->escapeString($newname) + . '\' where Db = \'' . $GLOBALS['dbi']->escapeString($oldDb) . '\';'; + $GLOBALS['dbi']->query($query_col_specific); + + // For procedures specific privileges + $query_proc_specific = 'UPDATE ' . Util::backquote('procs_priv') + . 'SET Db = \'' . $GLOBALS['dbi']->escapeString($newname) + . '\' where Db = \'' . $GLOBALS['dbi']->escapeString($oldDb) . '\';'; + $GLOBALS['dbi']->query($query_proc_specific); + + // Finally FLUSH the new privileges + $flush_query = "FLUSH PRIVILEGES;"; + $GLOBALS['dbi']->query($flush_query); + } + } + + /** + * Adjust the privileges after Copying the db + * + * @param string $oldDb Database name before copying + * @param string $newname New Database name requested + * + * @return void + */ + public function adjustPrivilegesCopyDb($oldDb, $newname) + { + if ($GLOBALS['db_priv'] && $GLOBALS['table_priv'] + && $GLOBALS['col_priv'] && $GLOBALS['proc_priv'] + && $GLOBALS['is_reload_priv'] + ) { + $GLOBALS['dbi']->selectDb('mysql'); + $newname = str_replace("_", "\_", $newname); + $oldDb = str_replace("_", "\_", $oldDb); + + $query_db_specific_old = 'SELECT * FROM ' + . Util::backquote('db') . ' WHERE ' + . 'Db = "' . $oldDb . '";'; + + $old_privs_db = $GLOBALS['dbi']->fetchResult($query_db_specific_old, 0); + + foreach ($old_privs_db as $old_priv) { + $newDb_db_privs_query = 'INSERT INTO ' . Util::backquote('db') + . ' VALUES("' . $old_priv[0] . '", "' . $newname . '", "' + . $old_priv[2] . '", "' . $old_priv[3] . '", "' . $old_priv[4] + . '", "' . $old_priv[5] . '", "' . $old_priv[6] . '", "' + . $old_priv[7] . '", "' . $old_priv[8] . '", "' . $old_priv[9] + . '", "' . $old_priv[10] . '", "' . $old_priv[11] . '", "' + . $old_priv[12] . '", "' . $old_priv[13] . '", "' . $old_priv[14] + . '", "' . $old_priv[15] . '", "' . $old_priv[16] . '", "' + . $old_priv[17] . '", "' . $old_priv[18] . '", "' . $old_priv[19] + . '", "' . $old_priv[20] . '", "' . $old_priv[21] . '");'; + + $GLOBALS['dbi']->query($newDb_db_privs_query); + } + + // For Table Specific privileges + $query_table_specific_old = 'SELECT * FROM ' + . Util::backquote('tables_priv') . ' WHERE ' + . 'Db = "' . $oldDb . '";'; + + $old_privs_table = $GLOBALS['dbi']->fetchResult( + $query_table_specific_old, + 0 + ); + + foreach ($old_privs_table as $old_priv) { + $newDb_table_privs_query = 'INSERT INTO ' . Util::backquote( + 'tables_priv' + ) . ' VALUES("' . $old_priv[0] . '", "' . $newname . '", "' + . $old_priv[2] . '", "' . $old_priv[3] . '", "' . $old_priv[4] + . '", "' . $old_priv[5] . '", "' . $old_priv[6] . '", "' + . $old_priv[7] . '");'; + + $GLOBALS['dbi']->query($newDb_table_privs_query); + } + + // For Column Specific privileges + $query_col_specific_old = 'SELECT * FROM ' + . Util::backquote('columns_priv') . ' WHERE ' + . 'Db = "' . $oldDb . '";'; + + $old_privs_col = $GLOBALS['dbi']->fetchResult( + $query_col_specific_old, + 0 + ); + + foreach ($old_privs_col as $old_priv) { + $newDb_col_privs_query = 'INSERT INTO ' . Util::backquote( + 'columns_priv' + ) . ' VALUES("' . $old_priv[0] . '", "' . $newname . '", "' + . $old_priv[2] . '", "' . $old_priv[3] . '", "' . $old_priv[4] + . '", "' . $old_priv[5] . '", "' . $old_priv[6] . '");'; + + $GLOBALS['dbi']->query($newDb_col_privs_query); + } + + // For Procedure Specific privileges + $query_proc_specific_old = 'SELECT * FROM ' + . Util::backquote('procs_priv') . ' WHERE ' + . 'Db = "' . $oldDb . '";'; + + $old_privs_proc = $GLOBALS['dbi']->fetchResult( + $query_proc_specific_old, + 0 + ); + + foreach ($old_privs_proc as $old_priv) { + $newDb_proc_privs_query = 'INSERT INTO ' . Util::backquote( + 'procs_priv' + ) . ' VALUES("' . $old_priv[0] . '", "' . $newname . '", "' + . $old_priv[2] . '", "' . $old_priv[3] . '", "' . $old_priv[4] + . '", "' . $old_priv[5] . '", "' . $old_priv[6] . '", "' + . $old_priv[7] . '");'; + + $GLOBALS['dbi']->query($newDb_proc_privs_query); + } + + // Finally FLUSH the new privileges + $flush_query = "FLUSH PRIVILEGES;"; + $GLOBALS['dbi']->query($flush_query); + } + } + + /** + * Create all accumulated constraints + * + * @param array $sqlConstratints array of sql constraints for the database + * + * @return void + */ + public function createAllAccumulatedConstraints(array $sqlConstratints) + { + $GLOBALS['dbi']->selectDb($_POST['newname']); + foreach ($sqlConstratints as $one_query) { + $GLOBALS['dbi']->query($one_query); + // and prepare to display them + $GLOBALS['sql_query'] .= "\n" . $one_query; + } + } + + /** + * Duplicate the bookmarks for the db (done once for each db) + * + * @param boolean $_error whether table rename/copy or not + * @param string $db database name + * + * @return void + */ + public function duplicateBookmarks($_error, $db) + { + if (! $_error && $db != $_POST['newname']) { + $get_fields = array('user', 'label', 'query'); + $where_fields = array('dbase' => $db); + $new_fields = array('dbase' => $_POST['newname']); + Table::duplicateInfo( + 'bookmarkwork', 'bookmark', $get_fields, + $where_fields, $new_fields + ); + } + } + + /** + * Get the HTML snippet for order the table + * + * @param array $columns columns array + * + * @return string $html_out + */ + public function getHtmlForOrderTheTable(array $columns) + { + $html_output = '
    '; + $html_output .= '
    '; + $html_output .= Url::getHiddenInputs( + $GLOBALS['db'], $GLOBALS['table'] + ); + $html_output .= '
    ' + . '' . __('Alter table order by') . '' + . ' ' . __('(singly)') . ' ' + . '
    ' + . '' + . '' + . '' + . '' + . '
    ' + . '
    ' + . '' + . '' + . '
    ' + . '
    ' + . '
    '; + + return $html_output; + } + + /** + * Get the HTML snippet for move table + * + * @return string $html_output + */ + public function getHtmlForMoveTable() + { + $html_output = '
    '; + $html_output .= '
    ' + . Url::getHiddenInputs($GLOBALS['db'], $GLOBALS['table']); + + $html_output .= '' + . '' + . '
    '; + + $html_output .= '' . __('Move table to (database.table)') + . ''; + + if (count($GLOBALS['dblist']->databases) > $GLOBALS['cfg']['MaxDbList']) { + $html_output .= ''; + } else { + $html_output .= ''; + } + $html_output .= ' . '; + $html_output .= '
    '; + + // starting with MySQL 5.0.24, SHOW CREATE TABLE includes the AUTO_INCREMENT + // next value but users can decide if they want it or not for the operation + + $html_output .= '' + . '
    '; + + if ($GLOBALS['table_priv'] && $GLOBALS['col_priv'] + && $GLOBALS['is_reload_priv'] + ) { + $html_output .= ''; + } else { + $html_output .= ''; + } + $html_output .= '
    '; + + $html_output .= '
    ' + . '' + . '
    ' + . '
    ' + . '
    '; + + return $html_output; + } + + /** + * Get the HTML div for Table option + * + * @param Table $pma_table Table object + * @param string $comment Comment + * @param array $tbl_collation table collation + * @param string $tbl_storage_engine table storage engine + * @param string $pack_keys pack keys + * @param string $auto_increment value of auto increment + * @param string $delay_key_write delay key write + * @param string $transactional value of transactional + * @param string $page_checksum value of page checksum + * @param string $checksum the checksum + * + * @return string $html_output + */ + public function getTableOptionDiv($pma_table, $comment, $tbl_collation, $tbl_storage_engine, + $pack_keys, $auto_increment, $delay_key_write, + $transactional, $page_checksum, $checksum + ) { + $html_output = '
    '; + $html_output .= '
    getTableOptionFieldset( + $pma_table, $comment, $tbl_collation, + $tbl_storage_engine, $pack_keys, + $delay_key_write, $auto_increment, $transactional, $page_checksum, + $checksum + ); + + $html_output .= '
    ' + . '' + . '' + . '
    ' + . '
    ' + . '
    '; + + return $html_output; + } + + /** + * Get HTML for the rename table part of table options + * + * @return string $html_output + */ + private function getHtmlForRenameTable() + { + $html_output = '' . __('Rename table to') . '' + . '' + . '' + . '' + . ''; + + if ($GLOBALS['table_priv'] && $GLOBALS['col_priv'] + && $GLOBALS['is_reload_priv'] + ) { + $html_output .= ''; + } else { + $html_output .= ''; + } + $html_output .= ''; + + $html_output .= ''; + return $html_output; + } + + /** + * Get HTML for the table comments part of table options + * + * @param string $current_value of the table comments + * + * @return string $html_output + */ + private function getHtmlForTableComments($current_value) + { + $commentLength = $GLOBALS['dbi']->getVersion() >= 50503 ? 2048 : 60; + $html_output = '' . __('Table comments') . '' + . '' + . '' + . '' + . ''; + + return $html_output; + } + + /** + * Get HTML for the PACK KEYS part of table options + * + * @param string $current_value of the pack keys option + * + * @return string $html_output + */ + private function getHtmlForPackKeys($current_value) + { + $html_output = '' + . '' + . '' + . '' + . ''; + + if ($pma_table->isEngine(array('MYISAM', 'ARIA', 'ISAM'))) { + $html_output .= $this->getHtmlForPackKeys($pack_keys); + } // end if (MYISAM|ISAM) + + if ($pma_table->isEngine(array('MYISAM', 'ARIA'))) { + $html_output .= $this->getHtmlForTableRow( + 'new_checksum', + 'CHECKSUM', + $checksum + ); + + $html_output .= $this->getHtmlForTableRow( + 'new_delay_key_write', + 'DELAY_KEY_WRITE', + $delay_key_write + ); + } // end if (MYISAM) + + if ($pma_table->isEngine('ARIA')) { + $html_output .= $this->getHtmlForTableRow( + 'new_transactional', + 'TRANSACTIONAL', + $transactional + ); + + $html_output .= $this->getHtmlForTableRow( + 'new_page_checksum', + 'PAGE_CHECKSUM', + $page_checksum + ); + } // end if (ARIA) + + if (strlen($auto_increment) > 0 + && $pma_table->isEngine(array('MYISAM', 'ARIA', 'INNODB', 'PBXT', 'ROCKSDB')) + ) { + $html_output .= '' + . '' + . '' + . ' '; + } // end if (MYISAM|INNODB) + + $possible_row_formats = $this->getPossibleRowFormat(); + + // for MYISAM there is also COMPRESSED but it can be set only by the + // myisampack utility, so don't offer here the choice because if we + // try it inside an ALTER TABLE, MySQL (at least in 5.1.23-maria) + // does not return a warning + // (if the table was compressed, it can be seen on the Structure page) + + if (isset($possible_row_formats[$tbl_storage_engine])) { + $current_row_format + = mb_strtoupper($GLOBALS['showtable']['Row_format']); + $html_output .= '' + . '' + . ''; + $html_output .= Util::getDropdown( + 'new_row_format', $possible_row_formats[$tbl_storage_engine], + $current_row_format, 'new_row_format' + ); + $html_output .= ''; + } + $html_output .= '' + . ''; + + return $html_output; + } + + /** + * Get the common HTML table row (tr) for new_checksum, new_delay_key_write, + * new_transactional and new_page_checksum + * + * @param string $attribute class, name and id attribute + * @param string $label label value + * @param string $val checksum, delay_key_write, transactional, page_checksum + * + * @return string $html_output + */ + private function getHtmlForTableRow($attribute, $label, $val) + { + return '' + . '' + . '' + . '' + . '' + . '' + . '' + . ''; + } + + /** + * Get array of possible row formats + * + * @return array $possible_row_formats + */ + private function getPossibleRowFormat() + { + // the outer array is for engines, the inner array contains the dropdown + // option values as keys then the dropdown option labels + + $possible_row_formats = array( + 'ARCHIVE' => array( + 'COMPRESSED' => 'COMPRESSED', + ), + 'ARIA' => array( + 'FIXED' => 'FIXED', + 'DYNAMIC' => 'DYNAMIC', + 'PAGE' => 'PAGE' + ), + 'MARIA' => array( + 'FIXED' => 'FIXED', + 'DYNAMIC' => 'DYNAMIC', + 'PAGE' => 'PAGE' + ), + 'MYISAM' => array( + 'FIXED' => 'FIXED', + 'DYNAMIC' => 'DYNAMIC' + ), + 'PBXT' => array( + 'FIXED' => 'FIXED', + 'DYNAMIC' => 'DYNAMIC' + ), + 'INNODB' => array( + 'COMPACT' => 'COMPACT', + 'REDUNDANT' => 'REDUNDANT' + ) + ); + + /** @var Innodb $innodbEnginePlugin */ + $innodbEnginePlugin = StorageEngine::getEngine('Innodb'); + $innodbPluginVersion = $innodbEnginePlugin->getInnodbPluginVersion(); + if (!empty($innodbPluginVersion)) { + $innodb_file_format = $innodbEnginePlugin->getInnodbFileFormat(); + } else { + $innodb_file_format = ''; + } + /** + * Newer MySQL/MariaDB always return empty a.k.a '' on $innodb_file_format otherwise + * old versions of MySQL/MariaDB must be returning something or not empty. + * This patch is to support newer MySQL/MariaDB while also for backward compatibilities. + */ + if (( ('Barracuda' == $innodb_file_format) || ($innodb_file_format == '') ) + && $innodbEnginePlugin->supportsFilePerTable() + ) { + $possible_row_formats['INNODB']['DYNAMIC'] = 'DYNAMIC'; + $possible_row_formats['INNODB']['COMPRESSED'] = 'COMPRESSED'; + } + + return $possible_row_formats; + } + + /** + * Get HTML div for copy table + * + * @return string $html_output + */ + public function getHtmlForCopytable() + { + $html_output = '
    '; + $html_output .= '
    ' + . Url::getHiddenInputs($GLOBALS['db'], $GLOBALS['table']) + . ''; + + $html_output .= '
    '; + $html_output .= '' + . __('Copy table to (database.table)') . ''; + + if (count($GLOBALS['dblist']->databases) > $GLOBALS['cfg']['MaxDbList']) { + $html_output .= ''; + } else { + $html_output .= ''; + } + $html_output .= ' . '; + $html_output .= '
    '; + + $choices = array( + 'structure' => __('Structure only'), + 'data' => __('Structure and data'), + 'dataonly' => __('Data only') + ); + + $html_output .= Util::getRadioFields( + 'what', $choices, 'data', true + ); + $html_output .= '
    '; + + $html_output .= '' + . '
    ' + . '' + . '
    '; + + // display "Add constraints" choice only if there are + // foreign keys + if ($this->relation->getForeigners($GLOBALS['db'], $GLOBALS['table'], '', 'foreign')) { + $html_output .= ''; + $html_output .= '
    '; + } // endif + + $html_output .= '
    '; + + if ($GLOBALS['table_priv'] && $GLOBALS['col_priv'] + && $GLOBALS['is_reload_priv'] + ) { + $html_output .= ''; + } else { + $html_output .= ''; + } + $html_output .= '
    '; + + $pma_switch_to_new = isset($_SESSION['pma_switch_to_new']) && $_SESSION['pma_switch_to_new']; + + $html_output .= ''; + $html_output .= '' + . '
    '; + + $html_output .= '
    ' + . '' + . '
    ' + . '
    ' + . '
    '; + + return $html_output; + } + + /** + * Get HTML snippet for table maintenance + * + * @param Table $pma_table Table object + * @param array $url_params array of URL parameters + * + * @return string $html_output + */ + public function getHtmlForTableMaintenance($pma_table, array $url_params) + { + $html_output = '
    '; + $html_output .= '
    ' + . '' . __('Table maintenance') . ''; + $html_output .= '
      '; + + // Note: BERKELEY (BDB) is no longer supported, starting with MySQL 5.1 + $html_output .= $this->getListofMaintainActionLink($pma_table, $url_params); + + $html_output .= '
    ' + . '
    ' + . '
    '; + + return $html_output; + } + + /** + * Get HTML 'li' having a link of maintain action + * + * @param Table $pma_table Table object + * @param array $url_params Array of URL parameters + * + * @return string $html_output + */ + private function getListofMaintainActionLink($pma_table, array $url_params) + { + $html_output = ''; + + // analyze table + if ($pma_table->isEngine(array('MYISAM', 'ARIA', 'INNODB', 'BERKELEYDB', 'TOKUDB'))) { + $params = array( + 'sql_query' => 'ANALYZE TABLE ' + . Util::backquote($GLOBALS['table']), + 'table_maintenance' => 'Go', + ); + $html_output .= $this->getMaintainActionlink( + __('Analyze table'), + $params, + $url_params, + 'ANALYZE_TABLE' + ); + } + + // check table + if ($pma_table->isEngine(array('MYISAM', 'ARIA', 'INNODB', 'TOKUDB'))) { + $params = array( + 'sql_query' => 'CHECK TABLE ' + . Util::backquote($GLOBALS['table']), + 'table_maintenance' => 'Go', + ); + $html_output .= $this->getMaintainActionlink( + __('Check table'), + $params, + $url_params, + 'CHECK_TABLE' + ); + } + + // checksum table + $params = array( + 'sql_query' => 'CHECKSUM TABLE ' + . Util::backquote($GLOBALS['table']), + 'table_maintenance' => 'Go', + ); + $html_output .= $this->getMaintainActionlink( + __('Checksum table'), + $params, + $url_params, + 'CHECKSUM_TABLE' + ); + + // defragment table + if ($pma_table->isEngine(array('INNODB'))) { + $params = array( + 'sql_query' => 'ALTER TABLE ' + . Util::backquote($GLOBALS['table']) + . ' ENGINE = InnoDB;' + ); + $html_output .= $this->getMaintainActionlink( + __('Defragment table'), + $params, + $url_params, + 'InnoDB_File_Defragmenting' + ); + } + + // flush table + $params = array( + 'sql_query' => 'FLUSH TABLE ' + . Util::backquote($GLOBALS['table']), + 'message_to_show' => sprintf( + __('Table %s has been flushed.'), + htmlspecialchars($GLOBALS['table']) + ), + 'reload' => 1, + ); + $html_output .= $this->getMaintainActionlink( + __('Flush the table (FLUSH)'), + $params, + $url_params, + 'FLUSH' + ); + + // optimize table + if ($pma_table->isEngine(array('MYISAM', 'ARIA', 'INNODB', 'BERKELEYDB', 'TOKUDB'))) { + $params = array( + 'sql_query' => 'OPTIMIZE TABLE ' + . Util::backquote($GLOBALS['table']), + 'table_maintenance' => 'Go', + ); + $html_output .= $this->getMaintainActionlink( + __('Optimize table'), + $params, + $url_params, + 'OPTIMIZE_TABLE' + ); + } + + // repair table + if ($pma_table->isEngine(array('MYISAM', 'ARIA'))) { + $params = array( + 'sql_query' => 'REPAIR TABLE ' + . Util::backquote($GLOBALS['table']), + 'table_maintenance' => 'Go', + ); + $html_output .= $this->getMaintainActionlink( + __('Repair table'), + $params, + $url_params, + 'REPAIR_TABLE' + ); + } + + return $html_output; + } + + /** + * Get maintain action HTML link + * + * @param string $action_message action message + * @param array $params url parameters array + * @param array $url_params additional url parameters + * @param string $link contains name of page/anchor that is being linked + * + * @return string $html_output + */ + private function getMaintainActionlink($action_message, array $params, array $url_params, $link) + { + return '
  • ' + . Util::linkOrButton( + 'sql.php' . Url::getCommon(array_merge($url_params, $params)), + $action_message, + ['class' => 'maintain_action ajax'] + ) + . Util::showMySQLDocu($link) + . '
  • '; + } + + /** + * Get HTML for Delete data or table (truncate table, drop table) + * + * @param array $truncate_table_url_params url parameter array for truncate table + * @param array $dropTableUrlParams url parameter array for drop table + * + * @return string $html_output + */ + public function getHtmlForDeleteDataOrTable( + array $truncate_table_url_params, + array $dropTableUrlParams + ) { + $html_output = '
    ' + . '
    ' + . '' . __('Delete data or table') . ''; + + $html_output .= '
      '; + + if (! empty($truncate_table_url_params)) { + $html_output .= $this->getDeleteDataOrTablelink( + $truncate_table_url_params, + 'TRUNCATE_TABLE', + __('Empty the table (TRUNCATE)'), + 'truncate_tbl_anchor' + ); + } + if (!empty($dropTableUrlParams)) { + $html_output .= $this->getDeleteDataOrTablelink( + $dropTableUrlParams, + 'DROP_TABLE', + __('Delete the table (DROP)'), + 'drop_tbl_anchor' + ); + } + $html_output .= '
    '; + + return $html_output; + } + + /** + * Get the HTML link for Truncate table, Drop table and Drop db + * + * @param array $url_params url parameter array for delete data or table + * @param string $syntax TRUNCATE_TABLE or DROP_TABLE or DROP_DATABASE + * @param string $link link to be shown + * @param string $htmlId id of the link + * + * @return string html output + */ + public function getDeleteDataOrTablelink(array $url_params, $syntax, $link, $htmlId) + { + return '
  • ' . Util::linkOrButton( + 'sql.php' . Url::getCommon($url_params), + $link, + array('id' => $htmlId, 'class' => 'ajax') + ) + . Util::showMySQLDocu($syntax) + . '
  • '; + } + + /** + * Get HTML snippet for partition maintenance + * + * @param array $partition_names array of partition names for a specific db/table + * @param array $url_params url parameters + * + * @return string $html_output + */ + public function getHtmlForPartitionMaintenance(array $partition_names, array $url_params) + { + $choices = array( + 'ANALYZE' => __('Analyze'), + 'CHECK' => __('Check'), + 'OPTIMIZE' => __('Optimize'), + 'REBUILD' => __('Rebuild'), + 'REPAIR' => __('Repair'), + 'TRUNCATE' => __('Truncate') + ); + + $partition_method = Partition::getPartitionMethod( + $GLOBALS['db'], $GLOBALS['table'] + ); + // add COALESCE or DROP option to choices array depeding on Partition method + if ($partition_method == 'RANGE' + || $partition_method == 'RANGE COLUMNS' + || $partition_method == 'LIST' + || $partition_method == 'LIST COLUMNS' + ) { + $choices['DROP'] = __('Drop'); + } else { + $choices['COALESCE'] = __('Coalesce'); + } + + $html_output = '
    ' + . '
    ' + . Url::getHiddenInputs($GLOBALS['db'], $GLOBALS['table']) + . '
    ' + . '' + . __('Partition maintenance') + . Util::showMySQLDocu('partitioning_maintenance') + . ''; + + $html_select = '' . "\n"; + $html_output .= sprintf(__('Partition %s'), $html_select); + + $html_output .= '
    '; + $html_output .= Util::getRadioFields( + 'partition_operation', $choices, 'ANALYZE', false, true, 'floatleft' + ); + $this_url_params = array_merge( + $url_params, + array( + 'sql_query' => 'ALTER TABLE ' + . Util::backquote($GLOBALS['table']) + . ' REMOVE PARTITIONING;' + ) + ); + $html_output .= '

    '; + + $html_output .= '' + . __('Remove partitioning') . ''; + + $html_output .= '
    ' + . '
    ' + . '' + . '' + . '
    ' + . '
    ' + . '
    '; + + return $html_output; + } + + /** + * Get the HTML for Referential Integrity check + * + * @param array $foreign all Relations to foreign tables for a given table + * or optionally a given column in a table + * @param array $url_params array of url parameters + * + * @return string $html_output + */ + public function getHtmlForReferentialIntegrityCheck(array $foreign, array $url_params) + { + $html_output = '
    ' + . '
    ' + . '' . __('Check referential integrity:') . ''; + + $html_output .= '
      '; + + foreach ($foreign as $master => $arr) { + $join_query = 'SELECT ' + . Util::backquote($GLOBALS['table']) . '.*' + . ' FROM ' . Util::backquote($GLOBALS['table']) + . ' LEFT JOIN ' + . Util::backquote($arr['foreign_db']) + . '.' + . Util::backquote($arr['foreign_table']); + if ($arr['foreign_table'] == $GLOBALS['table']) { + $foreign_table = $GLOBALS['table'] . '1'; + $join_query .= ' AS ' . Util::backquote($foreign_table); + } else { + $foreign_table = $arr['foreign_table']; + } + $join_query .= ' ON ' + . Util::backquote($GLOBALS['table']) . '.' + . Util::backquote($master) + . ' = ' + . Util::backquote($arr['foreign_db']) + . '.' + . Util::backquote($foreign_table) . '.' + . Util::backquote($arr['foreign_field']) + . ' WHERE ' + . Util::backquote($arr['foreign_db']) + . '.' + . Util::backquote($foreign_table) . '.' + . Util::backquote($arr['foreign_field']) + . ' IS NULL AND ' + . Util::backquote($GLOBALS['table']) . '.' + . Util::backquote($master) + . ' IS NOT NULL'; + $this_url_params = array_merge( + $url_params, + array('sql_query' => $join_query) + ); + + $html_output .= '
    • ' + . '' + . $master . ' -> ' . $arr['foreign_db'] . '.' + . $arr['foreign_table'] . '.' . $arr['foreign_field'] + . '
    • ' . "\n"; + } // foreach $foreign + $html_output .= '
    '; + + return $html_output; + } + + /** + * Reorder table based on request params + * + * @return array SQL query and result + */ + public function getQueryAndResultForReorderingTable() + { + $sql_query = 'ALTER TABLE ' + . Util::backquote($GLOBALS['table']) + . ' ORDER BY ' + . Util::backquote(urldecode($_POST['order_field'])); + if (isset($_POST['order_order']) + && $_POST['order_order'] === 'desc' + ) { + $sql_query .= ' DESC'; + } else { + $sql_query .= ' ASC'; + } + $sql_query .= ';'; + $result = $GLOBALS['dbi']->query($sql_query); + + return array($sql_query, $result); + } + + /** + * Get table alters array + * + * @param Table $pma_table The Table object + * @param string $pack_keys pack keys + * @param string $checksum value of checksum + * @param string $page_checksum value of page checksum + * @param string $delay_key_write delay key write + * @param string $row_format row format + * @param string $newTblStorageEngine table storage engine + * @param string $transactional value of transactional + * @param string $tbl_collation collation of the table + * + * @return array $table_alters + */ + public function getTableAltersArray($pma_table, $pack_keys, + $checksum, $page_checksum, $delay_key_write, + $row_format, $newTblStorageEngine, $transactional, $tbl_collation + ) { + global $auto_increment; + + $table_alters = array(); + + if (isset($_POST['comment']) + && urldecode($_POST['prev_comment']) !== $_POST['comment'] + ) { + $table_alters[] = 'COMMENT = \'' + . $GLOBALS['dbi']->escapeString($_POST['comment']) . '\''; + } + + if (! empty($newTblStorageEngine) + && mb_strtolower($newTblStorageEngine) !== mb_strtolower($GLOBALS['tbl_storage_engine']) + ) { + $table_alters[] = 'ENGINE = ' . $newTblStorageEngine; + } + if (! empty($_POST['tbl_collation']) + && $_POST['tbl_collation'] !== $tbl_collation + ) { + $table_alters[] = 'DEFAULT ' + . Util::getCharsetQueryPart($_POST['tbl_collation']); + } + + if ($pma_table->isEngine(array('MYISAM', 'ARIA', 'ISAM')) + && isset($_POST['new_pack_keys']) + && $_POST['new_pack_keys'] != (string)$pack_keys + ) { + $table_alters[] = 'pack_keys = ' . $_POST['new_pack_keys']; + } + + $_POST['new_checksum'] = empty($_POST['new_checksum']) ? '0' : '1'; + if ($pma_table->isEngine(array('MYISAM', 'ARIA')) + && $_POST['new_checksum'] !== $checksum + ) { + $table_alters[] = 'checksum = ' . $_POST['new_checksum']; + } + + $_POST['new_transactional'] + = empty($_POST['new_transactional']) ? '0' : '1'; + if ($pma_table->isEngine('ARIA') + && $_POST['new_transactional'] !== $transactional + ) { + $table_alters[] = 'TRANSACTIONAL = ' . $_POST['new_transactional']; + } + + $_POST['new_page_checksum'] + = empty($_POST['new_page_checksum']) ? '0' : '1'; + if ($pma_table->isEngine('ARIA') + && $_POST['new_page_checksum'] !== $page_checksum + ) { + $table_alters[] = 'PAGE_CHECKSUM = ' . $_POST['new_page_checksum']; + } + + $_POST['new_delay_key_write'] + = empty($_POST['new_delay_key_write']) ? '0' : '1'; + if ($pma_table->isEngine(array('MYISAM', 'ARIA')) + && $_POST['new_delay_key_write'] !== $delay_key_write + ) { + $table_alters[] = 'delay_key_write = ' . $_POST['new_delay_key_write']; + } + + if ($pma_table->isEngine(array('MYISAM', 'ARIA', 'INNODB', 'PBXT', 'ROCKSDB')) + && ! empty($_POST['new_auto_increment']) + && (! isset($auto_increment) + || $_POST['new_auto_increment'] !== $auto_increment) + ) { + $table_alters[] = 'auto_increment = ' + . $GLOBALS['dbi']->escapeString($_POST['new_auto_increment']); + } + + if (! empty($_POST['new_row_format'])) { + $newRowFormat = $_POST['new_row_format']; + $newRowFormatLower = mb_strtolower($newRowFormat); + if ($pma_table->isEngine(array('MYISAM', 'ARIA', 'INNODB', 'PBXT')) + && (strlen($row_format) === 0 + || $newRowFormatLower !== mb_strtolower($row_format)) + ) { + $table_alters[] = 'ROW_FORMAT = ' + . $GLOBALS['dbi']->escapeString($newRowFormat); + } + } + + return $table_alters; + } + + /** + * Get warning messages array + * + * @return array $warning_messages + */ + public function getWarningMessagesArray() + { + $warning_messages = array(); + foreach ($GLOBALS['dbi']->getWarnings() as $warning) { + // In MariaDB 5.1.44, when altering a table from Maria to MyISAM + // and if TRANSACTIONAL was set, the system reports an error; + // I discussed with a Maria developer and he agrees that this + // should not be reported with a Level of Error, so here + // I just ignore it. But there are other 1478 messages + // that it's better to show. + if (! (isset($_POST['new_tbl_storage_engine']) + && $_POST['new_tbl_storage_engine'] == 'MyISAM' + && $warning['Code'] == '1478' + && $warning['Level'] == 'Error') + ) { + $warning_messages[] = $warning['Level'] . ': #' . $warning['Code'] + . ' ' . $warning['Message']; + } + } + return $warning_messages; + } + + /** + * Get SQL query and result after ran this SQL query for a partition operation + * has been requested by the user + * + * @return array $sql_query, $result + */ + public function getQueryAndResultForPartition() + { + $sql_query = 'ALTER TABLE ' + . Util::backquote($GLOBALS['table']) . ' ' + . $_POST['partition_operation'] + . ' PARTITION '; + + if ($_POST['partition_operation'] == 'COALESCE') { + $sql_query .= count($_POST['partition_name']); + } else { + $sql_query .= implode(', ', $_POST['partition_name']) . ';'; + } + + $result = $GLOBALS['dbi']->query($sql_query); + + return array($sql_query, $result); + } + + /** + * Adjust the privileges after renaming/moving a table + * + * @param string $oldDb Database name before table renaming/moving table + * @param string $oldTable Table name before table renaming/moving table + * @param string $newDb Database name after table renaming/ moving table + * @param string $newTable Table name after table renaming/moving table + * + * @return void + */ + public function adjustPrivilegesRenameOrMoveTable($oldDb, $oldTable, $newDb, $newTable) + { + if ($GLOBALS['table_priv'] && $GLOBALS['col_priv'] + && $GLOBALS['is_reload_priv'] + ) { + $GLOBALS['dbi']->selectDb('mysql'); + + // For table specific privileges + $query_table_specific = 'UPDATE ' . Util::backquote('tables_priv') + . 'SET Db = \'' . $GLOBALS['dbi']->escapeString($newDb) . '\', Table_name = \'' . $GLOBALS['dbi']->escapeString($newTable) + . '\' where Db = \'' . $GLOBALS['dbi']->escapeString($oldDb) . '\' AND Table_name = \'' . $GLOBALS['dbi']->escapeString($oldTable) + . '\';'; + $GLOBALS['dbi']->query($query_table_specific); + + // For column specific privileges + $query_col_specific = 'UPDATE ' . Util::backquote('columns_priv') + . 'SET Db = \'' . $GLOBALS['dbi']->escapeString($newDb) . '\', Table_name = \'' . $GLOBALS['dbi']->escapeString($newTable) + . '\' where Db = \'' . $GLOBALS['dbi']->escapeString($oldDb) . '\' AND Table_name = \'' . $GLOBALS['dbi']->escapeString($oldTable) + . '\';'; + $GLOBALS['dbi']->query($query_col_specific); + + // Finally FLUSH the new privileges + $flush_query = "FLUSH PRIVILEGES;"; + $GLOBALS['dbi']->query($flush_query); + } + } + + /** + * Adjust the privileges after copying a table + * + * @param string $oldDb Database name before table copying + * @param string $oldTable Table name before table copying + * @param string $newDb Database name after table copying + * @param string $newTable Table name after table copying + * + * @return void + */ + public function adjustPrivilegesCopyTable($oldDb, $oldTable, $newDb, $newTable) + { + if ($GLOBALS['table_priv'] && $GLOBALS['col_priv'] + && $GLOBALS['is_reload_priv'] + ) { + $GLOBALS['dbi']->selectDb('mysql'); + + // For Table Specific privileges + $query_table_specific_old = 'SELECT * FROM ' + . Util::backquote('tables_priv') . ' where ' + . 'Db = "' . $oldDb . '" AND Table_name = "' . $oldTable . '";'; + + $old_privs_table = $GLOBALS['dbi']->fetchResult( + $query_table_specific_old, + 0 + ); + + foreach ($old_privs_table as $old_priv) { + $newDb_table_privs_query = 'INSERT INTO ' + . Util::backquote('tables_priv') . ' VALUES("' + . $old_priv[0] . '", "' . $newDb . '", "' . $old_priv[2] . '", "' + . $newTable . '", "' . $old_priv[4] . '", "' . $old_priv[5] + . '", "' . $old_priv[6] . '", "' . $old_priv[7] . '");'; + + $GLOBALS['dbi']->query($newDb_table_privs_query); + } + + // For Column Specific privileges + $query_col_specific_old = 'SELECT * FROM ' + . Util::backquote('columns_priv') . ' WHERE ' + . 'Db = "' . $oldDb . '" AND Table_name = "' . $oldTable . '";'; + + $old_privs_col = $GLOBALS['dbi']->fetchResult( + $query_col_specific_old, + 0 + ); + + foreach ($old_privs_col as $old_priv) { + $newDb_col_privs_query = 'INSERT INTO ' + . Util::backquote('columns_priv') . ' VALUES("' + . $old_priv[0] . '", "' . $newDb . '", "' . $old_priv[2] . '", "' + . $newTable . '", "' . $old_priv[4] . '", "' . $old_priv[5] + . '", "' . $old_priv[6] . '");'; + + $GLOBALS['dbi']->query($newDb_col_privs_query); + } + + // Finally FLUSH the new privileges + $flush_query = "FLUSH PRIVILEGES;"; + $GLOBALS['dbi']->query($flush_query); + } + } + + /** + * Change all collations and character sets of all columns in table + * + * @param string $db Database name + * @param string $table Table name + * @param string $tbl_collation Collation Name + * + * @return void + */ + public function changeAllColumnsCollation($db, $table, $tbl_collation) + { + $GLOBALS['dbi']->selectDb($db); + + $change_all_collations_query = 'ALTER TABLE ' + . Util::backquote($table) + . ' CONVERT TO'; + + list($charset) = explode('_', $tbl_collation); + + $change_all_collations_query .= ' CHARACTER SET ' . $charset + . ($charset == $tbl_collation ? '' : ' COLLATE ' . $tbl_collation); + + $GLOBALS['dbi']->query($change_all_collations_query); + } + + /** + * Move or copy a table + * + * @param string $db current database name + * @param string $table current table name + * + * @return void + */ + public function moveOrCopyTable($db, $table) + { + /** + * Selects the database to work with + */ + $GLOBALS['dbi']->selectDb($db); + + /** + * $_POST['target_db'] could be empty in case we came from an input field + * (when there are many databases, no drop-down) + */ + if (empty($_POST['target_db'])) { + $_POST['target_db'] = $db; + } + + /** + * A target table name has been sent to this script -> do the work + */ + if (Core::isValid($_POST['new_name'])) { + if ($db == $_POST['target_db'] && $table == $_POST['new_name']) { + if (isset($_POST['submit_move'])) { + $message = Message::error(__('Can\'t move table to same one!')); + } else { + $message = Message::error(__('Can\'t copy table to same one!')); + } + } else { + Table::moveCopy( + $db, $table, $_POST['target_db'], $_POST['new_name'], + $_POST['what'], isset($_POST['submit_move']), 'one_table' + ); + + if (isset($_POST['adjust_privileges']) + && ! empty($_POST['adjust_privileges']) + ) { + if (isset($_POST['submit_move'])) { + $this->adjustPrivilegesRenameOrMoveTable( + $db, $table, $_POST['target_db'], $_POST['new_name'] + ); + } else { + $this->adjustPrivilegesCopyTable( + $db, $table, $_POST['target_db'], $_POST['new_name'] + ); + } + + if (isset($_POST['submit_move'])) { + $message = Message::success( + __( + 'Table %s has been moved to %s. Privileges have been ' + . 'adjusted.' + ) + ); + } else { + $message = Message::success( + __( + 'Table %s has been copied to %s. Privileges have been ' + . 'adjusted.' + ) + ); + } + + } else { + if (isset($_POST['submit_move'])) { + $message = Message::success( + __('Table %s has been moved to %s.') + ); + } else { + $message = Message::success( + __('Table %s has been copied to %s.') + ); + } + } + + $old = Util::backquote($db) . '.' + . Util::backquote($table); + $message->addParam($old); + + $new_name = $_POST['new_name']; + if ($GLOBALS['dbi']->getLowerCaseNames() === '1') { + $new_name = strtolower($new_name); + } + + $GLOBALS['table'] = $new_name; + + $new = Util::backquote($_POST['target_db']) . '.' + . Util::backquote($new_name); + $message->addParam($new); + + /* Check: Work on new table or on old table? */ + if (isset($_POST['submit_move']) + || Core::isValid($_POST['switch_to_new']) + ) { + } + } + } else { + /** + * No new name for the table! + */ + $message = Message::error(__('The table name is empty!')); + } + + $response = Response::getInstance(); + if ($response->isAjax()) { + $response->addJSON('message', $message); + if ($message->isSuccess()) { + $response->addJSON('db', $GLOBALS['db']); + } else { + $response->setRequestStatus(false); + } + exit; + } + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/OutputBuffering.php b/php/apps/phpmyadmin49/html/libraries/classes/OutputBuffering.php new file mode 100644 index 00000000..a66b045f --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/OutputBuffering.php @@ -0,0 +1,139 @@ +_mode = $this->_getMode(); + $this->_on = false; + } + + /** + * This function could be used eventually to support more modes. + * + * @return integer the output buffer mode + */ + private function _getMode() + { + $mode = 0; + if ($GLOBALS['cfg']['OBGzip'] && function_exists('ob_start')) { + if (ini_get('output_handler') == 'ob_gzhandler') { + // If a user sets the output_handler in php.ini to ob_gzhandler, then + // any right frame file in phpMyAdmin will not be handled properly by + // the browser. My fix was to check the ini file within the + // PMA_outBufferModeGet() function. + $mode = 0; + } elseif (function_exists('ob_get_level') && ob_get_level() > 0) { + // happens when php.ini's output_buffering is not Off + ob_end_clean(); + $mode = 1; + } else { + $mode = 1; + } + } + // Zero (0) is no mode or in other words output buffering is OFF. + // Follow 2^0, 2^1, 2^2, 2^3 type values for the modes. + // Useful if we ever decide to combine modes. Then a bitmask field of + // the sum of all modes will be the natural choice. + return $mode; + } + + /** + * Returns the singleton OutputBuffering object + * + * @return OutputBuffering object + */ + public static function getInstance() + { + if (empty(self::$_instance)) { + self::$_instance = new OutputBuffering(); + } + return self::$_instance; + } + + /** + * This function will need to run at the top of all pages if output + * output buffering is turned on. It also needs to be passed $mode from + * the PMA_outBufferModeGet() function or it will be useless. + * + * @return void + */ + public function start() + { + if (! $this->_on) { + if ($this->_mode && function_exists('ob_gzhandler')) { + ob_start('ob_gzhandler'); + } + ob_start(); + if (! defined('TESTSUITE')) { + header('X-ob_mode: ' . $this->_mode); + } + register_shutdown_function( + array(OutputBuffering::class, 'stop') + ); + $this->_on = true; + } + } + + /** + * This function will need to run at the bottom of all pages if output + * buffering is turned on. It also needs to be passed $mode from the + * PMA_outBufferModeGet() function or it will be useless. + * + * @return void + */ + public static function stop() + { + $buffer = OutputBuffering::getInstance(); + if ($buffer->_on) { + $buffer->_on = false; + $buffer->_content = ob_get_contents(); + if (ob_get_length() > 0) { + ob_end_clean(); + } + } + } + + /** + * Gets buffer content + * + * @return string buffer content + */ + public function getContents() + { + return $this->_content; + } + + /** + * Flushes output buffer + * + * @return void + */ + public function flush() + { + if (ob_get_status() && $this->_mode) { + ob_flush(); + } else { + flush(); + } + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/ParseAnalyze.php b/php/apps/phpmyadmin49/html/libraries/classes/ParseAnalyze.php new file mode 100644 index 00000000..c30af3e2 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/ParseAnalyze.php @@ -0,0 +1,78 @@ + 1) { + + /** + * @todo if there are more than one table name in the Select: + * - do not extract the first table name + * - do not show a table name in the page header + * - do not display the sub-pages links) + */ + $table = ''; + } else { + $table = $analyzed_sql_results['select_tables'][0][0]; + if (!empty($analyzed_sql_results['select_tables'][0][1])) { + $db = $analyzed_sql_results['select_tables'][0][1]; + } + } + // There is no point checking if a reload is required if we already decided + // to reload. Also, no reload is required for AJAX requests. + $response = Response::getInstance(); + if (empty($reload) && ! $response->isAjax()) { + // NOTE: Database names are case-insensitive. + $reload = strcasecmp($db, $prev_db) != 0; + } + + // Updating the array. + $analyzed_sql_results['reload'] = $reload; + } + + return array($analyzed_sql_results, $db, $table); + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Partition.php b/php/apps/phpmyadmin49/html/libraries/classes/Partition.php new file mode 100644 index 00000000..3d1e1a65 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Partition.php @@ -0,0 +1,268 @@ +name = $row['PARTITION_NAME']; + $this->ordinal = $row['PARTITION_ORDINAL_POSITION']; + $this->method = $row['PARTITION_METHOD']; + $this->expression = $row['PARTITION_EXPRESSION']; + $this->description = $row['PARTITION_DESCRIPTION']; + // no sub partitions, load all data to this object + if (empty($row['SUBPARTITION_NAME'])) { + $this->loadCommonData($row); + } + } + + /** + * Returns the partiotion description + * + * @return string partition description + */ + public function getDescription() + { + return $this->description; + } + + /** + * Add a sub partition + * + * @param SubPartition $partition Sub partition + * + * @return void + */ + public function addSubPartition(SubPartition $partition) + { + $this->subPartitions[] = $partition; + } + + /** + * Whether there are sub partitions + * + * @return boolean + */ + public function hasSubPartitions() + { + return ! empty($this->subPartitions); + } + + /** + * Returns the number of data rows + * + * @return integer number of rows + */ + public function getRows() + { + if (empty($this->subPartitions)) { + return $this->rows; + } + + $rows = 0; + foreach ($this->subPartitions as $subPartition) { + $rows += $subPartition->rows; + } + return $rows; + } + + /** + * Returns the total data length + * + * @return integer data length + */ + public function getDataLength() + { + if (empty($this->subPartitions)) { + return $this->dataLength; + } + + $dataLength = 0; + foreach ($this->subPartitions as $subPartition) { + $dataLength += $subPartition->dataLength; + } + return $dataLength; + } + + /** + * Returns the tatal index length + * + * @return integer index length + */ + public function getIndexLength() + { + if (empty($this->subPartitions)) { + return $this->indexLength; + } + + $indexLength = 0; + foreach ($this->subPartitions as $subPartition) { + $indexLength += $subPartition->indexLength; + } + return $indexLength; + } + + /** + * Returns the list of sub partitions + * + * @return SubPartition[] + */ + public function getSubPartitions() + { + return $this->subPartitions; + } + + /** + * Returns array of partitions for a specific db/table + * + * @param string $db database name + * @param string $table table name + * + * @access public + * @return Partition[] + */ + static public function getPartitions($db, $table) + { + if (Partition::havePartitioning()) { + $result = $GLOBALS['dbi']->fetchResult( + "SELECT * FROM `information_schema`.`PARTITIONS`" + . " WHERE `TABLE_SCHEMA` = '" . $GLOBALS['dbi']->escapeString($db) + . "' AND `TABLE_NAME` = '" . $GLOBALS['dbi']->escapeString($table) . "'" + ); + if ($result) { + $partitionMap = array(); + foreach ($result as $row) { + if (isset($partitionMap[$row['PARTITION_NAME']])) { + $partition = $partitionMap[$row['PARTITION_NAME']]; + } else { + $partition = new Partition($row); + $partitionMap[$row['PARTITION_NAME']] = $partition; + } + + if (! empty($row['SUBPARTITION_NAME'])) { + $parentPartition = $partition; + $partition = new SubPartition($row); + $parentPartition->addSubPartition($partition); + } + } + return array_values($partitionMap); + } + return array(); + } + + return array(); + } + + /** + * returns array of partition names for a specific db/table + * + * @param string $db database name + * @param string $table table name + * + * @access public + * @return array of partition names + */ + static public function getPartitionNames($db, $table) + { + if (Partition::havePartitioning()) { + return $GLOBALS['dbi']->fetchResult( + "SELECT DISTINCT `PARTITION_NAME` FROM `information_schema`.`PARTITIONS`" + . " WHERE `TABLE_SCHEMA` = '" . $GLOBALS['dbi']->escapeString($db) + . "' AND `TABLE_NAME` = '" . $GLOBALS['dbi']->escapeString($table) . "'" + ); + } + + return array(); + } + + /** + * returns the partition method used by the table. + * + * @param string $db database name + * @param string $table table name + * + * @return string partition method + */ + static public function getPartitionMethod($db, $table) + { + if (Partition::havePartitioning()) { + $partition_method = $GLOBALS['dbi']->fetchResult( + "SELECT `PARTITION_METHOD` FROM `information_schema`.`PARTITIONS`" + . " WHERE `TABLE_SCHEMA` = '" . $GLOBALS['dbi']->escapeString($db) . "'" + . " AND `TABLE_NAME` = '" . $GLOBALS['dbi']->escapeString($table) . "'" + . " LIMIT 1" + ); + if (! empty($partition_method)) { + return $partition_method[0]; + } + } + return null; + } + + /** + * checks if MySQL server supports partitioning + * + * @static + * @staticvar boolean $have_partitioning + * @staticvar boolean $already_checked + * @access public + * @return boolean + */ + static public function havePartitioning() + { + static $have_partitioning = false; + static $already_checked = false; + + if (! $already_checked) { + if ($GLOBALS['dbi']->getVersion() < 50600) { + if ($GLOBALS['dbi']->fetchValue( + "SELECT @@have_partitioning;" + )) { + $have_partitioning = true; + } + } else if ($GLOBALS['dbi']->getVersion() >= 80000) { + $have_partitioning = true; + } else { + // see https://dev.mysql.com/doc/refman/5.6/en/partitioning.html + $plugins = $GLOBALS['dbi']->fetchResult("SHOW PLUGINS"); + foreach ($plugins as $value) { + if ($value['Name'] == 'partition') { + $have_partitioning = true; + break; + } + } + } + $already_checked = true; + } + return $have_partitioning; + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Pdf.php b/php/apps/phpmyadmin49/html/libraries/classes/Pdf.php new file mode 100644 index 00000000..8ab3f1bf --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Pdf.php @@ -0,0 +1,154 @@ +SetAuthor('phpMyAdmin ' . PMA_VERSION); + $this->AddFont('DejaVuSans', '', 'dejavusans.php'); + $this->AddFont('DejaVuSans', 'B', 'dejavusansb.php'); + $this->SetFont(Pdf::PMA_PDF_FONT, '', 14); + $this->setFooterFont(array(Pdf::PMA_PDF_FONT, '', 14)); + } + + /** + * This function must be named "Footer" to work with the TCPDF library + * + * @return void + */ + // @codingStandardsIgnoreLine + public function Footer() + { + // Check if footer for this page already exists + if (!isset($this->footerset[$this->page])) { + $this->SetY(-15); + $this->SetFont(Pdf::PMA_PDF_FONT, '', 14); + $this->Cell( + 0, 6, + __('Page number:') . ' ' + . $this->getAliasNumPage() . '/' . $this->getAliasNbPages(), + 'T', 0, 'C' + ); + $this->Cell(0, 6, Util::localisedDate(), 0, 1, 'R'); + $this->SetY(20); + + // set footerset + $this->footerset[$this->page] = 1; + } + } + + /** + * Function to set alias which will be expanded on page rendering. + * + * @param string $name name of the alias + * @param string $value value of the alias + * + * @return void + */ + public function setAlias($name, $value) + { + $name = TCPDF_FONTS::UTF8ToUTF16BE( + $name, false, true, $this->CurrentFont + ); + $this->Alias[$name] = TCPDF_FONTS::UTF8ToUTF16BE( + $value, false, true, $this->CurrentFont + ); + } + + /** + * Improved with alias expanding. + * + * @return void + */ + public function _putpages() + { + if (count($this->Alias) > 0) { + $nbPages = count($this->pages); + for ($n = 1; $n <= $nbPages; $n++) { + $this->pages[$n] = strtr($this->pages[$n], $this->Alias); + } + } + parent::_putpages(); + } + + /** + * Displays an error message + * + * @param string $error_message the error message + * + * @return void + */ + // @codingStandardsIgnoreLine + public function Error($error_message = '') + { + Message::error( + __('Error while creating PDF:') . ' ' . $error_message + )->display(); + exit; + } + + /** + * Sends file as a download to user. + * + * @param string $filename file name + * + * @return void + */ + public function download($filename) + { + $pdfData = $this->getPDFData(); + Response::getInstance()->disable(); + Core::downloadHeader( + $filename, + 'application/pdf', + strlen($pdfData) + ); + echo $pdfData; + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Plugins.php b/php/apps/phpmyadmin49/html/libraries/classes/Plugins.php new file mode 100644 index 00000000..b125bcea --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Plugins.php @@ -0,0 +1,584 @@ +getProperties()) { + $plugin_list[] = $plugin; + } + } + } + } + + usort($plugin_list, function($cmp_name_1, $cmp_name_2) { + return strcasecmp( + $cmp_name_1->getProperties()->getText(), + $cmp_name_2->getProperties()->getText() + ); + }); + return $plugin_list; + } + + /** + * Returns locale string for $name or $name if no locale is found + * + * @param string $name for local string + * + * @return string locale string for $name + */ + public static function getString($name) + { + return isset($GLOBALS[$name]) ? $GLOBALS[$name] : $name; + } + + /** + * Returns html input tag option 'checked' if plugin $opt + * should be set by config or request + * + * @param string $section name of config section in + * $GLOBALS['cfg'][$section] for plugin + * @param string $opt name of option + * + * @return string html input tag option 'checked' + */ + public static function checkboxCheck($section, $opt) + { + // If the form is being repopulated using $_GET data, that is priority + if (isset($_GET[$opt]) + || ! isset($_GET['repopulate']) + && ((! empty($GLOBALS['timeout_passed']) && isset($_REQUEST[$opt])) + || ! empty($GLOBALS['cfg'][$section][$opt])) + ) { + return ' checked="checked"'; + } + return ''; + } + + /** + * Returns default value for option $opt + * + * @param string $section name of config section in + * $GLOBALS['cfg'][$section] for plugin + * @param string $opt name of option + * + * @return string default value for option $opt + */ + public static function getDefault($section, $opt) + { + if (isset($_GET[$opt])) { + // If the form is being repopulated using $_GET data, that is priority + return htmlspecialchars($_GET[$opt]); + } + + if (isset($GLOBALS['timeout_passed']) + && $GLOBALS['timeout_passed'] + && isset($_REQUEST[$opt]) + ) { + return htmlspecialchars($_REQUEST[$opt]); + } + + if (!isset($GLOBALS['cfg'][$section][$opt])) { + return ''; + } + + $matches = array(); + /* Possibly replace localised texts */ + if (!preg_match_all( + '/(str[A-Z][A-Za-z0-9]*)/', + $GLOBALS['cfg'][$section][$opt], + $matches + )) { + return htmlspecialchars($GLOBALS['cfg'][$section][$opt]); + } + + $val = $GLOBALS['cfg'][$section][$opt]; + foreach ($matches[0] as $match) { + if (isset($GLOBALS[$match])) { + $val = str_replace($match, $GLOBALS[$match], $val); + } + } + return htmlspecialchars($val); + } + + /** + * Returns html select form element for plugin choice + * and hidden fields denoting whether each plugin must be exported as a file + * + * @param string $section name of config section in + * $GLOBALS['cfg'][$section] for plugin + * @param string $name name of select element + * @param array &$list array with plugin instances + * @param string $cfgname name of config value, if none same as $name + * + * @return string html select tag + */ + public static function getChoice($section, $name, array $list, $cfgname = null) + { + if (! isset($cfgname)) { + $cfgname = $name; + } + $ret = '' . "\n"; + } + $ret .= '' . "\n" . $hidden; + + return $ret; + } + + /** + * Returns single option in a list element + * + * @param string $section name of config section in $GLOBALS['cfg'][$section] for plugin + * @param string $plugin_name unique plugin name + * @param array|\PhpMyAdmin\Properties\PropertyItem &$propertyGroup options property main group instance + * @param boolean $is_subgroup if this group is a subgroup + * + * @return string table row with option + */ + public static function getOneOption( + $section, + $plugin_name, + &$propertyGroup, + $is_subgroup = false + ) { + $ret = "\n"; + + if (! $is_subgroup) { + // for subgroup headers + if (mb_strpos(get_class($propertyGroup), "PropertyItem")) { + $properties = array($propertyGroup); + } else { + // for main groups + $ret .= '
    '; + + if (method_exists($propertyGroup, 'getText')) { + $text = $propertyGroup->getText(); + } + + if ($text != null) { + $ret .= '

    ' . self::getString($text) . '

    '; + } + $ret .= '
      '; + } + } + + if (! isset($properties)) { + $not_subgroup_header = true; + if (method_exists($propertyGroup, 'getProperties')) { + $properties = $propertyGroup->getProperties(); + } + } + + if (isset($properties)) { + /** @var OptionsPropertySubgroup $propertyItem */ + foreach ($properties as $propertyItem) { + $property_class = get_class($propertyItem); + // if the property is a subgroup, we deal with it recursively + if (mb_strpos($property_class, "Subgroup")) { + // for subgroups + // each subgroup can have a header, which may also be a form element + /** @var OptionsPropertyItem $subgroup_header */ + $subgroup_header = $propertyItem->getSubgroupHeader(); + if (isset($subgroup_header)) { + $ret .= self::getOneOption( + $section, + $plugin_name, + $subgroup_header + ); + } + + $ret .= '
    • getName() . '">'; + } else { + $ret .= '>'; + } + + $ret .= self::getOneOption( + $section, + $plugin_name, + $propertyItem, + true + ); + continue; + } + + // single property item + $ret .= self::getHtmlForProperty( + $section, $plugin_name, $propertyItem + ); + } + } + + if ($is_subgroup) { + // end subgroup + $ret .= '
    '; + } else { + // end main group + if (! empty($not_subgroup_header)) { + $ret .= '
    '; + } + } + + if (method_exists($propertyGroup, "getDoc")) { + $doc = $propertyGroup->getDoc(); + if ($doc != null) { + if (count($doc) == 3) { + $ret .= PhpMyAdmin\Util::showMySQLDocu( + $doc[1], + false, + $doc[2] + ); + } elseif (count($doc) == 1) { + $ret .= PhpMyAdmin\Util::showDocu('faq', $doc[0]); + } else { + $ret .= PhpMyAdmin\Util::showMySQLDocu( + $doc[1] + ); + } + } + } + + // Close the list element after $doc link is displayed + if (isset($property_class)) { + if ($property_class == 'PhpMyAdmin\Properties\Options\Items\BoolPropertyItem' + || $property_class == 'PhpMyAdmin\Properties\Options\Items\MessageOnlyPropertyItem' + || $property_class == 'PhpMyAdmin\Properties\Options\Items\SelectPropertyItem' + || $property_class == 'PhpMyAdmin\Properties\Options\Items\TextPropertyItem' + ) { + $ret .= ''; + } + } + $ret .= "\n"; + return $ret; + } + + /** + * Get HTML for properties items + * + * @param string $section name of config section in + * $GLOBALS['cfg'][$section] for plugin + * @param string $plugin_name unique plugin name + * @param OptionsPropertyItem $propertyItem Property item + * + * @return string + */ + public static function getHtmlForProperty( + $section, $plugin_name, $propertyItem + ) { + $ret = null; + $property_class = get_class($propertyItem); + switch ($property_class) { + case 'PhpMyAdmin\Properties\Options\Items\BoolPropertyItem': + $ret .= '
  • ' . "\n"; + $ret .= 'getName() + ); + + if ($propertyItem->getForce() != null) { + // Same code is also few lines lower, update both if needed + $ret .= ' onclick="if (!this.checked && ' + . '(!document.getElementById(\'checkbox_' . $plugin_name + . '_' . $propertyItem->getForce() . '\') ' + . '|| !document.getElementById(\'checkbox_' + . $plugin_name . '_' . $propertyItem->getForce() + . '\').checked)) ' + . 'return false; else return true;"'; + } + $ret .= ' />'; + $ret .= ''; + break; + case 'PhpMyAdmin\Properties\Options\Items\DocPropertyItem': + echo 'PhpMyAdmin\Properties\Options\Items\DocPropertyItem'; + break; + case 'PhpMyAdmin\Properties\Options\Items\HiddenPropertyItem': + $ret .= '
  • '; + break; + case 'PhpMyAdmin\Properties\Options\Items\MessageOnlyPropertyItem': + $ret .= '
  • ' . "\n"; + $ret .= '

    ' . self::getString($propertyItem->getText()) . '

    '; + break; + case 'PhpMyAdmin\Properties\Options\Items\RadioPropertyItem': + $default = self::getDefault( + $section, + $plugin_name . '_' . $propertyItem->getName() + ); + foreach ($propertyItem->getValues() as $key => $val) { + $ret .= '
  • ' + . self::getString($val) . '
  • '; + } + break; + case 'PhpMyAdmin\Properties\Options\Items\SelectPropertyItem': + $ret .= '
  • ' . "\n"; + $ret .= ''; + $ret .= ''; + break; + case 'PhpMyAdmin\Properties\Options\Items\TextPropertyItem': + case 'PhpMyAdmin\Properties\Options\Items\NumberPropertyItem': + $ret .= '
  • ' . "\n"; + $ret .= ''; + $ret .= 'getSize() != null + ? ' size="' . $propertyItem->getSize() . '"' + : '') + . ($propertyItem->getLen() != null + ? ' maxlength="' . $propertyItem->getLen() . '"' + : '') + . ' />'; + break; + default: + break; + } + return $ret; + } + + /** + * Returns html div with editable options for plugin + * + * @param string $section name of config section in $GLOBALS['cfg'][$section] + * @param array &$list array with plugin instances + * + * @return string html fieldset with plugin options + */ + public static function getOptions($section, array $list) + { + $ret = ''; + // Options for plugins that support them + foreach ($list as $plugin) { + $properties = $plugin->getProperties(); + if ($properties != null) { + $text = $properties->getText(); + $options = $properties->getOptions(); + } + + $elem = explode('\\', get_class($plugin)); + $plugin_name = array_pop($elem); + unset($elem); + $plugin_name = mb_strtolower( + mb_substr( + $plugin_name, + mb_strlen($section) + ) + ); + + $ret .= '
    '; + $ret .= '

    ' . self::getString($text) . '

    '; + + $no_options = true; + if (! is_null($options) && count($options) > 0) { + foreach ($options->getProperties() + as $propertyMainGroup + ) { + // check for hidden properties + $no_options = true; + foreach ($propertyMainGroup->getProperties() as $propertyItem) { + if (strcmp('PhpMyAdmin\Properties\Options\Items\HiddenPropertyItem', get_class($propertyItem))) { + $no_options = false; + break; + } + } + + $ret .= self::getOneOption( + $section, + $plugin_name, + $propertyMainGroup + ); + } + } + + if ($no_options) { + $ret .= '

    ' . __('This format has no options') . '

    '; + } + $ret .= '
    '; + } + return $ret; + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Auth/AuthenticationConfig.php b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Auth/AuthenticationConfig.php new file mode 100644 index 00000000..d9d7e075 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Auth/AuthenticationConfig.php @@ -0,0 +1,170 @@ +isAjax()) { + $response->setRequestStatus(false); + // reload_flag removes the token parameter from the URL and reloads + $response->addJSON('reload_flag', '1'); + if (defined('TESTSUITE')) { + return true; + } else { + exit; + } + } + + return true; + } + + /** + * Gets authentication credentials + * + * @return boolean always true + */ + public function readCredentials() + { + if ($GLOBALS['token_provided'] && $GLOBALS['token_mismatch']) { + return false; + } + + $this->user = $GLOBALS['cfg']['Server']['user']; + $this->password = $GLOBALS['cfg']['Server']['password']; + + return true; + } + + /** + * User is not allowed to login to MySQL -> authentication failed + * + * @param string $failure String describing why authentication has failed + * + * @return void + */ + public function showFailure($failure) + { + parent::showFailure($failure); + $conn_error = $GLOBALS['dbi']->getError(); + if (!$conn_error) { + $conn_error = __('Cannot connect: invalid settings.'); + } + + /* HTML header */ + $response = Response::getInstance(); + $response->getFooter() + ->setMinimal(); + $header = $response->getHeader(); + $header->setBodyId('loginform'); + $header->setTitle(__('Access denied!')); + $header->disableMenuAndConsole(); + echo '

    +
    +

    '; + echo sprintf(__('Welcome to %s'), ' phpMyAdmin '); + echo '

    +
    +
    + + + + + + + ' , "\n"; + if (count($GLOBALS['cfg']['Servers']) > 1) { + // offer a chance to login to other servers if the current one failed + echo '' , "\n"; + echo ' ' , "\n"; + echo '' , "\n"; + } + echo '
    '; + if (isset($GLOBALS['allowDeny_forbidden']) + && $GLOBALS['allowDeny_forbidden'] + ) { + trigger_error(__('Access denied!'), E_USER_NOTICE); + } else { + // Check whether user has configured something + if ($GLOBALS['PMA_Config']->source_mtime == 0) { + echo '

    ' , sprintf( + __( + 'You probably did not create a configuration file.' + . ' You might want to use the %1$ssetup script%2$s to' + . ' create one.' + ), + '', + '' + ) , '

    ' , "\n"; + } elseif (!isset($GLOBALS['errno']) + || (isset($GLOBALS['errno']) && $GLOBALS['errno'] != 2002) + && $GLOBALS['errno'] != 2003 + ) { + // if we display the "Server not responding" error, do not confuse + // users by telling them they have a settings problem + // (note: it's true that they could have a badly typed host name, + // but anyway the current message tells that the server + // rejected the connection, which is not really what happened) + // 2002 is the error given by mysqli + // 2003 is the error given by mysql + trigger_error( + __( + 'phpMyAdmin tried to connect to the MySQL server, and the' + . ' server rejected the connection. You should check the' + . ' host, username and password in your configuration and' + . ' make sure that they correspond to the information given' + . ' by the administrator of the MySQL server.' + ), + E_USER_WARNING + ); + } + echo Util::mysqlDie( + $conn_error, + '', + true, + '', + false + ); + } + $GLOBALS['error_handler']->dispUserErrors(); + echo '
    ' , "\n"; + echo '' + , __('Retry to connect') + , '' , "\n"; + echo '
    ' , "\n"; + echo Select::render(true, true); + echo '
    ' , "\n"; + if (!defined('TESTSUITE')) { + exit; + } + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Auth/AuthenticationCookie.php b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Auth/AuthenticationCookie.php new file mode 100644 index 00000000..a681f60d --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Auth/AuthenticationCookie.php @@ -0,0 +1,889 @@ +_use_openssl = ! class_exists('phpseclib\Crypt\Random'); + } + + /** + * Forces (not)using of openSSL + * + * @param boolean $use The flag + * + * @return void + */ + public function setUseOpenSSL($use) + { + $this->_use_openssl = $use; + } + + /** + * Displays authentication form + * + * this function MUST exit/quit the application + * + * @global string $conn_error the last connection error + * + * @return boolean|void + */ + public function showLoginForm() + { + global $conn_error; + + $response = Response::getInstance(); + if ($response->loginPage()) { + if (defined('TESTSUITE')) { + return true; + } else { + exit; + } + } + + // No recall if blowfish secret is not configured as it would produce + // garbage + if ($GLOBALS['cfg']['LoginCookieRecall'] + && ! empty($GLOBALS['cfg']['blowfish_secret']) + ) { + $default_user = $this->user; + $default_server = $GLOBALS['pma_auth_server']; + $autocomplete = ''; + } else { + $default_user = ''; + $default_server = ''; + // skip the IE autocomplete feature. + $autocomplete = ' autocomplete="off"'; + } + + echo Template::get('login/header')->render(['theme' => $GLOBALS['PMA_Theme']]); + + if ($GLOBALS['cfg']['DBG']['demo']) { + echo '
    '; + echo '' , __('phpMyAdmin Demo Server') , ''; + printf( + __( + 'You are using the demo server. You can do anything here, but ' + . 'please do not change root, debian-sys-maint and pma users. ' + . 'More information is available at %s.' + ), + 'demo.phpmyadmin.net' + ); + echo '
    '; + } + + // Show error message + if (! empty($conn_error)) { + Message::rawError($conn_error)->display(); + } elseif (isset($_GET['session_expired']) + && intval($_GET['session_expired']) == 1 + ) { + Message::rawError( + __('Your session has expired. Please log in again.') + )->display(); + } + + // Displays the languages form + $language_manager = LanguageManager::getInstance(); + if (empty($GLOBALS['cfg']['Lang']) && $language_manager->hasChoice()) { + echo "
    "; + // use fieldset, don't show doc link + echo $language_manager->getSelectorDisplay(true, false); + echo '
    '; + } + echo ' +
    + + '; + + if ($GLOBALS['error_handler']->hasDisplayErrors()) { + echo '
    '; + $GLOBALS['error_handler']->dispErrors(); + echo '
    '; + } + echo Template::get('login/footer')->render(); + echo Config::renderFooter(); + if (! defined('TESTSUITE')) { + exit; + } else { + return true; + } + } + + /** + * Gets authentication credentials + * + * this function DOES NOT check authentication - it just checks/provides + * authentication credentials required to connect to the MySQL server + * usually with $GLOBALS['dbi']->connect() + * + * it returns false if something is missing - which usually leads to + * showLoginForm() which displays login form + * + * it returns true if all seems ok which usually leads to auth_set_user() + * + * it directly switches to showFailure() if user inactivity timeout is reached + * + * @return boolean whether we get authentication settings or not + */ + public function readCredentials() + { + global $conn_error; + + // Initialization + /** + * @global $GLOBALS['pma_auth_server'] the user provided server to + * connect to + */ + $GLOBALS['pma_auth_server'] = ''; + + $this->user = $this->password = ''; + $GLOBALS['from_cookie'] = false; + + if (isset($_POST['pma_username']) && strlen($_POST['pma_username']) > 0) { + + // Verify Captcha if it is required. + if (! empty($GLOBALS['cfg']['CaptchaLoginPrivateKey']) + && ! empty($GLOBALS['cfg']['CaptchaLoginPublicKey']) + ) { + if (! empty($_POST["g-recaptcha-response"])) { + if (function_exists('curl_init')) { + $reCaptcha = new ReCaptcha\ReCaptcha( + $GLOBALS['cfg']['CaptchaLoginPrivateKey'], + new ReCaptcha\RequestMethod\CurlPost() + ); + } elseif (ini_get('allow_url_fopen')) { + $reCaptcha = new ReCaptcha\ReCaptcha( + $GLOBALS['cfg']['CaptchaLoginPrivateKey'], + new ReCaptcha\RequestMethod\Post() + ); + } else { + $reCaptcha = new ReCaptcha\ReCaptcha( + $GLOBALS['cfg']['CaptchaLoginPrivateKey'], + new ReCaptcha\RequestMethod\SocketPost() + ); + } + + // verify captcha status. + $resp = $reCaptcha->verify( + $_POST["g-recaptcha-response"], + Core::getIp() + ); + + // Check if the captcha entered is valid, if not stop the login. + if ($resp == null || ! $resp->isSuccess()) { + $codes = $resp->getErrorCodes(); + + if (in_array('invalid-json', $codes)) { + $conn_error = __('Failed to connect to the reCAPTCHA service!'); + } else { + $conn_error = __('Entered captcha is wrong, try again!'); + } + return false; + } + } else { + $conn_error = __('Missing reCAPTCHA verification, maybe it has been blocked by adblock?'); + return false; + } + } + + // The user just logged in + $this->user = Core::sanitizeMySQLUser($_POST['pma_username']); + $this->password = isset($_POST['pma_password']) ? $_POST['pma_password'] : ''; + if ($GLOBALS['cfg']['AllowArbitraryServer'] + && isset($_REQUEST['pma_servername']) + ) { + if ($GLOBALS['cfg']['ArbitraryServerRegexp']) { + $parts = explode(' ', $_REQUEST['pma_servername']); + if (count($parts) == 2) { + $tmp_host = $parts[0]; + } else { + $tmp_host = $_REQUEST['pma_servername']; + } + + $match = preg_match( + $GLOBALS['cfg']['ArbitraryServerRegexp'], $tmp_host + ); + if (! $match) { + $conn_error = __( + 'You are not allowed to log in to this MySQL server!' + ); + return false; + } + } + $GLOBALS['pma_auth_server'] = Core::sanitizeMySQLHost($_REQUEST['pma_servername']); + } + /* Secure current session on login to avoid session fixation */ + Session::secure(); + return true; + } + + // At the end, try to set the $this->user + // and $this->password variables from cookies + + // check cookies + if (empty($_COOKIE['pmaUser-' . $GLOBALS['server']])) { + return false; + } + + $value = $this->cookieDecrypt( + $_COOKIE['pmaUser-' . $GLOBALS['server']], + $this->_getEncryptionSecret() + ); + + if ($value === false) { + return false; + } + + $this->user = $value; + // user was never logged in since session start + if (empty($_SESSION['browser_access_time'])) { + return false; + } + + // User inactive too long + $last_access_time = time() - $GLOBALS['cfg']['LoginCookieValidity']; + foreach ($_SESSION['browser_access_time'] as $key => $value) { + if ($value < $last_access_time) { + unset($_SESSION['browser_access_time'][$key]); + } + } + // All sessions expired + if (empty($_SESSION['browser_access_time'])) { + Util::cacheUnset('is_create_db_priv'); + Util::cacheUnset('is_reload_priv'); + Util::cacheUnset('db_to_create'); + Util::cacheUnset('dbs_where_create_table_allowed'); + Util::cacheUnset('dbs_to_test'); + Util::cacheUnset('db_priv'); + Util::cacheUnset('col_priv'); + Util::cacheUnset('table_priv'); + Util::cacheUnset('proc_priv'); + + $this->showFailure('no-activity'); + if (! defined('TESTSUITE')) { + exit; + } else { + return false; + } + } + + // check password cookie + if (empty($_COOKIE['pmaAuth-' . $GLOBALS['server']])) { + return false; + } + $value = $this->cookieDecrypt( + $_COOKIE['pmaAuth-' . $GLOBALS['server']], + $this->_getSessionEncryptionSecret() + ); + if ($value === false) { + return false; + } + + $auth_data = json_decode($value, true); + + if (! is_array($auth_data) || ! isset($auth_data['password'])) { + return false; + } + $this->password = $auth_data['password']; + if ($GLOBALS['cfg']['AllowArbitraryServer'] && ! empty($auth_data['server'])) { + $GLOBALS['pma_auth_server'] = $auth_data['server']; + } + + $GLOBALS['from_cookie'] = true; + + return true; + } + + /** + * Set the user and password after last checkings if required + * + * @return boolean always true + */ + public function storeCredentials() + { + global $cfg; + + if ($GLOBALS['cfg']['AllowArbitraryServer'] + && ! empty($GLOBALS['pma_auth_server']) + ) { + /* Allow to specify 'host port' */ + $parts = explode(' ', $GLOBALS['pma_auth_server']); + if (count($parts) == 2) { + $tmp_host = $parts[0]; + $tmp_port = $parts[1]; + } else { + $tmp_host = $GLOBALS['pma_auth_server']; + $tmp_port = ''; + } + if ($cfg['Server']['host'] != $GLOBALS['pma_auth_server']) { + $cfg['Server']['host'] = $tmp_host; + if (! empty($tmp_port)) { + $cfg['Server']['port'] = $tmp_port; + } + } + unset($tmp_host, $tmp_port, $parts); + } + + return parent::storeCredentials(); + } + + /** + * Stores user credentials after successful login. + * + * @return void|bool + */ + public function rememberCredentials() + { + // Name and password cookies need to be refreshed each time + // Duration = one month for username + $this->storeUsernameCookie($this->user); + + // Duration = as configured + // Do not store password cookie on password change as we will + // set the cookie again after password has been changed + if (! isset($_POST['change_pw'])) { + $this->storePasswordCookie($this->password); + } + + // Set server cookies if required (once per session) and, in this case, + // force reload to ensure the client accepts cookies + if (! $GLOBALS['from_cookie']) { + // URL where to go: + $redirect_url = './index.php'; + + // any parameters to pass? + $url_params = array(); + if (strlen($GLOBALS['db']) > 0) { + $url_params['db'] = $GLOBALS['db']; + } + if (strlen($GLOBALS['table']) > 0) { + $url_params['table'] = $GLOBALS['table']; + } + // any target to pass? + if (! empty($GLOBALS['target']) + && $GLOBALS['target'] != 'index.php' + ) { + $url_params['target'] = $GLOBALS['target']; + } + + /** + * Clear user cache. + */ + Util::clearUserCache(); + + Response::getInstance() + ->disable(); + + Core::sendHeaderLocation( + $redirect_url . Url::getCommonRaw($url_params), + true + ); + if (! defined('TESTSUITE')) { + exit; + } else { + return false; + } + } // end if + + return true; + } + + /** + * Stores username in a cookie. + * + * @param string $username User name + * + * @return void + */ + public function storeUsernameCookie($username) + { + // Name and password cookies need to be refreshed each time + // Duration = one month for username + $GLOBALS['PMA_Config']->setCookie( + 'pmaUser-' . $GLOBALS['server'], + $this->cookieEncrypt( + $username, + $this->_getEncryptionSecret() + ) + ); + } + + /** + * Stores password in a cookie. + * + * @param string $password Password + * + * @return void + */ + public function storePasswordCookie($password) + { + $payload = array('password' => $password); + if ($GLOBALS['cfg']['AllowArbitraryServer'] && ! empty($GLOBALS['pma_auth_server'])) { + $payload['server'] = $GLOBALS['pma_auth_server']; + } + // Duration = as configured + $GLOBALS['PMA_Config']->setCookie( + 'pmaAuth-' . $GLOBALS['server'], + $this->cookieEncrypt( + json_encode($payload), + $this->_getSessionEncryptionSecret() + ), + null, + $GLOBALS['cfg']['LoginCookieStore'] + ); + } + + /** + * User is not allowed to login to MySQL -> authentication failed + * + * prepares error message and switches to showLoginForm() which display the error + * and the login form + * + * this function MUST exit/quit the application, + * currently done by call to showLoginForm() + * + * @param string $failure String describing why authentication has failed + * + * @return void + */ + public function showFailure($failure) + { + global $conn_error; + + parent::showFailure($failure); + + // Deletes password cookie and displays the login form + $GLOBALS['PMA_Config']->removeCookie('pmaAuth-' . $GLOBALS['server']); + + $conn_error = $this->getErrorMessage($failure); + + $response = Response::getInstance(); + + // needed for PHP-CGI (not need for FastCGI or mod-php) + $response->header('Cache-Control: no-store, no-cache, must-revalidate'); + $response->header('Pragma: no-cache'); + + $this->showLoginForm(); + } + + /** + * Returns blowfish secret or generates one if needed. + * + * @return string + */ + private function _getEncryptionSecret() + { + if (empty($GLOBALS['cfg']['blowfish_secret'])) { + return $this->_getSessionEncryptionSecret(); + } + + return $GLOBALS['cfg']['blowfish_secret']; + } + + /** + * Returns blowfish secret or generates one if needed. + * + * @return string + */ + private function _getSessionEncryptionSecret() + { + if (empty($_SESSION['encryption_key'])) { + if ($this->_use_openssl) { + $_SESSION['encryption_key'] = openssl_random_pseudo_bytes(32); + } else { + $_SESSION['encryption_key'] = Crypt\Random::string(32); + } + } + return $_SESSION['encryption_key']; + } + + /** + * Concatenates secret in order to make it 16 bytes log + * + * This doesn't add any security, just ensures the secret + * is long enough by copying it. + * + * @param string $secret Original secret + * + * @return string + */ + public function enlargeSecret($secret) + { + while (strlen($secret) < 16) { + $secret .= $secret; + } + return substr($secret, 0, 16); + } + + /** + * Derives MAC secret from encryption secret. + * + * @param string $secret the secret + * + * @return string the MAC secret + */ + public function getMACSecret($secret) + { + // Grab first part, up to 16 chars + // The MAC and AES secrets can overlap if original secret is short + $length = strlen($secret); + if ($length > 16) { + return substr($secret, 0, 16); + } + return $this->enlargeSecret( + $length == 1 ? $secret : substr($secret, 0, -1) + ); + } + + /** + * Derives AES secret from encryption secret. + * + * @param string $secret the secret + * + * @return string the AES secret + */ + public function getAESSecret($secret) + { + // Grab second part, up to 16 chars + // The MAC and AES secrets can overlap if original secret is short + $length = strlen($secret); + if ($length > 16) { + return substr($secret, -16); + } + return $this->enlargeSecret( + $length == 1 ? $secret : substr($secret, 1) + ); + } + + /** + * Cleans any SSL errors + * + * This can happen from corrupted cookies, by invalid encryption + * parameters used in older phpMyAdmin versions or by wrong openSSL + * configuration. + * + * In neither case the error is useful to user, but we need to clear + * the error buffer as otherwise the errors would pop up later, for + * example during MySQL SSL setup. + * + * @return void + */ + public function cleanSSLErrors() + { + if (function_exists('openssl_error_string')) { + while (($ssl_err = openssl_error_string()) !== false) { + } + } + } + + /** + * Encryption using openssl's AES or phpseclib's AES + * (phpseclib uses mcrypt when it is available) + * + * @param string $data original data + * @param string $secret the secret + * + * @return string the encrypted result + */ + public function cookieEncrypt($data, $secret) + { + $mac_secret = $this->getMACSecret($secret); + $aes_secret = $this->getAESSecret($secret); + $iv = $this->createIV(); + if ($this->_use_openssl) { + $result = openssl_encrypt( + $data, + 'AES-128-CBC', + $aes_secret, + 0, + $iv + ); + } else { + $cipher = new Crypt\AES(Crypt\Base::MODE_CBC); + $cipher->setIV($iv); + $cipher->setKey($aes_secret); + $result = base64_encode($cipher->encrypt($data)); + } + $this->cleanSSLErrors(); + $iv = base64_encode($iv); + return json_encode( + array( + 'iv' => $iv, + 'mac' => hash_hmac('sha1', $iv . $result, $mac_secret), + 'payload' => $result, + ) + ); + } + + /** + * Decryption using openssl's AES or phpseclib's AES + * (phpseclib uses mcrypt when it is available) + * + * @param string $encdata encrypted data + * @param string $secret the secret + * + * @return string|false original data, false on error + */ + public function cookieDecrypt($encdata, $secret) + { + $data = json_decode($encdata, true); + + if (! is_array($data) || ! isset($data['mac']) || ! isset($data['iv']) || ! isset($data['payload']) + || ! is_string($data['mac']) || ! is_string($data['iv']) || ! is_string($data['payload']) + ) { + return false; + } + + $mac_secret = $this->getMACSecret($secret); + $aes_secret = $this->getAESSecret($secret); + $newmac = hash_hmac('sha1', $data['iv'] . $data['payload'], $mac_secret); + + if (! hash_equals($data['mac'], $newmac)) { + return false; + } + + if ($this->_use_openssl) { + $result = openssl_decrypt( + $data['payload'], + 'AES-128-CBC', + $aes_secret, + 0, + base64_decode($data['iv']) + ); + } else { + $cipher = new Crypt\AES(Crypt\Base::MODE_CBC); + $cipher->setIV(base64_decode($data['iv'])); + $cipher->setKey($aes_secret); + $result = $cipher->decrypt(base64_decode($data['payload'])); + } + $this->cleanSSLErrors(); + return $result; + } + + /** + * Returns size of IV for encryption. + * + * @return int + */ + public function getIVSize() + { + if ($this->_use_openssl) { + return openssl_cipher_iv_length('AES-128-CBC'); + } + $cipher = new Crypt\AES(Crypt\Base::MODE_CBC); + return $cipher->block_size; + } + + /** + * Initialization + * Store the initialization vector because it will be needed for + * further decryption. I don't think necessary to have one iv + * per server so I don't put the server number in the cookie name. + * + * @return string + */ + public function createIV() + { + /* Testsuite shortcut only to allow predictable IV */ + if (! is_null($this->_cookie_iv)) { + return $this->_cookie_iv; + } + if ($this->_use_openssl) { + return openssl_random_pseudo_bytes( + $this->getIVSize() + ); + } + + return Crypt\Random::string( + $this->getIVSize() + ); + } + + /** + * Sets encryption IV to use + * + * This is for testing only! + * + * @param string $vector The IV + * + * @return void + */ + public function setIV($vector) + { + $this->_cookie_iv = $vector; + } + + /** + * Callback when user changes password. + * + * @param string $password New password to set + * + * @return void + */ + public function handlePasswordChange($password) + { + $this->storePasswordCookie($password); + } + + /** + * Perform logout + * + * @return void + */ + public function logOut() + { + // -> delete password cookie(s) + if ($GLOBALS['cfg']['LoginCookieDeleteAll']) { + foreach ($GLOBALS['cfg']['Servers'] as $key => $val) { + $GLOBALS['PMA_Config']->removeCookie('pmaAuth-' . $key); + if (isset($_COOKIE['pmaAuth-' . $key])) { + unset($_COOKIE['pmaAuth-' . $key]); + } + } + } else { + $GLOBALS['PMA_Config']->removeCookie( + 'pmaAuth-' . $GLOBALS['server'] + ); + if (isset($_COOKIE['pmaAuth-' . $GLOBALS['server']])) { + unset($_COOKIE['pmaAuth-' . $GLOBALS['server']]); + } + } + parent::logOut(); + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Auth/AuthenticationHttp.php b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Auth/AuthenticationHttp.php new file mode 100644 index 00000000..3b8f0d89 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Auth/AuthenticationHttp.php @@ -0,0 +1,214 @@ +isAjax()) { + $response->setRequestStatus(false); + // reload_flag removes the token parameter from the URL and reloads + $response->addJSON('reload_flag', '1'); + if (defined('TESTSUITE')) { + return true; + } else { + exit; + } + } + + return $this->authForm(); + } + + /** + * Displays authentication form + * + * @return boolean + */ + public function authForm() + { + if (empty($GLOBALS['cfg']['Server']['auth_http_realm'])) { + if (empty($GLOBALS['cfg']['Server']['verbose'])) { + $server_message = $GLOBALS['cfg']['Server']['host']; + } else { + $server_message = $GLOBALS['cfg']['Server']['verbose']; + } + $realm_message = 'phpMyAdmin ' . $server_message; + } else { + $realm_message = $GLOBALS['cfg']['Server']['auth_http_realm']; + } + + $response = Response::getInstance(); + + // remove non US-ASCII to respect RFC2616 + $realm_message = preg_replace('/[^\x20-\x7e]/i', '', $realm_message); + $response->header('WWW-Authenticate: Basic realm="' . $realm_message . '"'); + $response->setHttpResponseCode(401); + + /* HTML header */ + $footer = $response->getFooter(); + $footer->setMinimal(); + $header = $response->getHeader(); + $header->setTitle(__('Access denied!')); + $header->disableMenuAndConsole(); + $header->setBodyId('loginform'); + + $response->addHTML('

    '); + $response->addHTML(sprintf(__('Welcome to %s'), ' phpMyAdmin')); + $response->addHTML('

    '); + $response->addHTML('

    '); + $response->addHTML( + Message::error( + __('Wrong username/password. Access denied.') + ) + ); + $response->addHTML('

    '); + + $response->addHTML(Config::renderFooter()); + + if (!defined('TESTSUITE')) { + exit; + } else { + return false; + } + } + + /** + * Gets authentication credentials + * + * @return boolean whether we get authentication settings or not + */ + public function readCredentials() + { + // Grabs the $PHP_AUTH_USER variable + if (isset($GLOBALS['PHP_AUTH_USER'])) { + $this->user = $GLOBALS['PHP_AUTH_USER']; + } + if (empty($this->user)) { + if (Core::getenv('PHP_AUTH_USER')) { + $this->user = Core::getenv('PHP_AUTH_USER'); + } elseif (Core::getenv('REMOTE_USER')) { + // CGI, might be encoded, see below + $this->user = Core::getenv('REMOTE_USER'); + } elseif (Core::getenv('REDIRECT_REMOTE_USER')) { + // CGI, might be encoded, see below + $this->user = Core::getenv('REDIRECT_REMOTE_USER'); + } elseif (Core::getenv('AUTH_USER')) { + // WebSite Professional + $this->user = Core::getenv('AUTH_USER'); + } elseif (Core::getenv('HTTP_AUTHORIZATION')) { + // IIS, might be encoded, see below + $this->user = Core::getenv('HTTP_AUTHORIZATION'); + } elseif (Core::getenv('Authorization')) { + // FastCGI, might be encoded, see below + $this->user = Core::getenv('Authorization'); + } + } + // Grabs the $PHP_AUTH_PW variable + if (isset($GLOBALS['PHP_AUTH_PW'])) { + $this->password = $GLOBALS['PHP_AUTH_PW']; + } + if (empty($this->password)) { + if (Core::getenv('PHP_AUTH_PW')) { + $this->password = Core::getenv('PHP_AUTH_PW'); + } elseif (Core::getenv('REMOTE_PASSWORD')) { + // Apache/CGI + $this->password = Core::getenv('REMOTE_PASSWORD'); + } elseif (Core::getenv('AUTH_PASSWORD')) { + // WebSite Professional + $this->password = Core::getenv('AUTH_PASSWORD'); + } + } + // Sanitize empty password login + if (is_null($this->password)) { + $this->password = ''; + } + + // Avoid showing the password in phpinfo()'s output + unset($GLOBALS['PHP_AUTH_PW']); + unset($_SERVER['PHP_AUTH_PW']); + + // Decode possibly encoded information (used by IIS/CGI/FastCGI) + // (do not use explode() because a user might have a colon in his password + if (strcmp(substr($this->user, 0, 6), 'Basic ') == 0) { + $usr_pass = base64_decode(substr($this->user, 6)); + if (!empty($usr_pass)) { + $colon = strpos($usr_pass, ':'); + if ($colon) { + $this->user = substr($usr_pass, 0, $colon); + $this->password = substr($usr_pass, $colon + 1); + } + unset($colon); + } + unset($usr_pass); + } + + // sanitize username + $this->user = Core::sanitizeMySQLUser($this->user); + + // User logged out -> ensure the new username is not the same + $old_usr = isset($_REQUEST['old_usr']) ? $_REQUEST['old_usr'] : ''; + if (! empty($old_usr) + && (isset($this->user) && hash_equals($old_usr, $this->user)) + ) { + $this->user = ''; + } + + // Returns whether we get authentication settings or not + return !empty($this->user); + } + + /** + * User is not allowed to login to MySQL -> authentication failed + * + * @param string $failure String describing why authentication has failed + * + * @return void + */ + public function showFailure($failure) + { + parent::showFailure($failure); + $error = $GLOBALS['dbi']->getError(); + if ($error && $GLOBALS['errno'] != 1045) { + Core::fatalError($error); + } else { + $this->authForm(); + } + } + + /** + * Returns URL for login form. + * + * @return string + */ + public function getLoginFormURL() + { + return './index.php?old_usr=' . $this->user; + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Auth/AuthenticationSignon.php b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Auth/AuthenticationSignon.php new file mode 100644 index 00000000..a6d1c7bf --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Auth/AuthenticationSignon.php @@ -0,0 +1,280 @@ +=')) { + session_set_cookie_params($sessionCookieParams); + } + + session_set_cookie_params( + $sessionCookieParams['lifetime'], + $sessionCookieParams['path'], + $sessionCookieParams['domain'], + $sessionCookieParams['secure'], + $sessionCookieParams['httponly'] + ); + } + + /** + * Gets authentication credentials + * + * @return boolean whether we get authentication settings or not + */ + public function readCredentials() + { + /* Check if we're using same signon server */ + $signon_url = $GLOBALS['cfg']['Server']['SignonURL']; + if (isset($_SESSION['LAST_SIGNON_URL']) + && $_SESSION['LAST_SIGNON_URL'] != $signon_url + ) { + return false; + } + + /* Script name */ + $script_name = $GLOBALS['cfg']['Server']['SignonScript']; + + /* Session name */ + $session_name = $GLOBALS['cfg']['Server']['SignonSession']; + + /* Login URL */ + $signon_url = $GLOBALS['cfg']['Server']['SignonURL']; + + /* Current host */ + $single_signon_host = $GLOBALS['cfg']['Server']['host']; + + /* Current port */ + $single_signon_port = $GLOBALS['cfg']['Server']['port']; + + /* No configuration updates */ + $single_signon_cfgupdate = array(); + + /* Handle script based auth */ + if (!empty($script_name)) { + if (!@file_exists($script_name)) { + Core::fatalError( + __('Can not find signon authentication script:') + . ' ' . $script_name + ); + } + include $script_name; + + list ($this->user, $this->password) + = get_login_credentials($GLOBALS['cfg']['Server']['user']); + } elseif (isset($_COOKIE[$session_name])) { /* Does session exist? */ + /* End current session */ + $old_session = session_name(); + $old_id = session_id(); + $oldCookieParams = session_get_cookie_params(); + if (!defined('TESTSUITE')) { + session_write_close(); + } + /* Load single signon session */ + if (!defined('TESTSUITE')) { + $this->setCookieParams(); + session_name($session_name); + session_id($_COOKIE[$session_name]); + session_start(); + } + + /* Clear error message */ + unset($_SESSION['PMA_single_signon_error_message']); + + /* Grab credentials if they exist */ + if (isset($_SESSION['PMA_single_signon_user'])) { + $this->user = $_SESSION['PMA_single_signon_user']; + } + if (isset($_SESSION['PMA_single_signon_password'])) { + $this->password = $_SESSION['PMA_single_signon_password']; + } + if (isset($_SESSION['PMA_single_signon_host'])) { + $single_signon_host = $_SESSION['PMA_single_signon_host']; + } + + if (isset($_SESSION['PMA_single_signon_port'])) { + $single_signon_port = $_SESSION['PMA_single_signon_port']; + } + + if (isset($_SESSION['PMA_single_signon_cfgupdate'])) { + $single_signon_cfgupdate = $_SESSION['PMA_single_signon_cfgupdate']; + } + + /* Also get token as it is needed to access subpages */ + if (isset($_SESSION['PMA_single_signon_token'])) { + /* No need to care about token on logout */ + $pma_token = $_SESSION['PMA_single_signon_token']; + } + + /* End single signon session */ + if (!defined('TESTSUITE')) { + session_write_close(); + } + + /* Restart phpMyAdmin session */ + if (!defined('TESTSUITE')) { + $this->setCookieParams($oldCookieParams); + session_name($old_session); + if (!empty($old_id)) { + session_id($old_id); + } + session_start(); + } + + /* Set the single signon host */ + $GLOBALS['cfg']['Server']['host'] = $single_signon_host; + + /* Set the single signon port */ + $GLOBALS['cfg']['Server']['port'] = $single_signon_port; + + /* Configuration update */ + $GLOBALS['cfg']['Server'] = array_merge( + $GLOBALS['cfg']['Server'], + $single_signon_cfgupdate + ); + + /* Restore our token */ + if (!empty($pma_token)) { + $_SESSION[' PMA_token '] = $pma_token; + $_SESSION[' HMAC_secret '] = Util::generateRandom(16); + } + + /** + * Clear user cache. + */ + Util::clearUserCache(); + } + + // Returns whether we get authentication settings or not + if (empty($this->user)) { + unset($_SESSION['LAST_SIGNON_URL']); + + return false; + } + + $_SESSION['LAST_SIGNON_URL'] = $GLOBALS['cfg']['Server']['SignonURL']; + + return true; + } + + /** + * User is not allowed to login to MySQL -> authentication failed + * + * @param string $failure String describing why authentication has failed + * + * @return void + */ + public function showFailure($failure) + { + parent::showFailure($failure); + + /* Session name */ + $session_name = $GLOBALS['cfg']['Server']['SignonSession']; + + /* Does session exist? */ + if (isset($_COOKIE[$session_name])) { + if (!defined('TESTSUITE')) { + /* End current session */ + session_write_close(); + + /* Load single signon session */ + $this->setCookieParams(); + session_name($session_name); + session_id($_COOKIE[$session_name]); + session_start(); + } + + /* Set error message */ + $_SESSION['PMA_single_signon_error_message'] = $this->getErrorMessage($failure); + } + $this->showLoginForm(); + } + + /** + * Returns URL for login form. + * + * @return string + */ + public function getLoginFormURL() + { + return $GLOBALS['cfg']['Server']['SignonURL']; + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Plugins/AuthenticationPlugin.php b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/AuthenticationPlugin.php new file mode 100644 index 00000000..b174df01 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/AuthenticationPlugin.php @@ -0,0 +1,347 @@ +setSessionAccessTime(); + + $cfg['Server']['user'] = $this->user; + $cfg['Server']['password'] = $this->password; + + return true; + } + + /** + * Stores user credentials after successful login. + * + * @return void + */ + public function rememberCredentials() + { + } + + /** + * User is not allowed to login to MySQL -> authentication failed + * + * @param string $failure String describing why authentication has failed + * + * @return void + */ + public function showFailure($failure) + { + Logging::logUser($this->user, $failure); + } + + /** + * Perform logout + * + * @return void + */ + public function logOut() + { + /* Obtain redirect URL (before doing logout) */ + if (! empty($GLOBALS['cfg']['Server']['LogoutURL'])) { + $redirect_url = $GLOBALS['cfg']['Server']['LogoutURL']; + } else { + $redirect_url = $this->getLoginFormURL(); + } + + /* Clear credentials */ + $this->user = ''; + $this->password = ''; + + /* + * Get a logged-in server count in case of LoginCookieDeleteAll is disabled. + */ + $server = 0; + if ($GLOBALS['cfg']['LoginCookieDeleteAll'] === false + && $GLOBALS['cfg']['Server']['auth_type'] == 'cookie' + ) { + foreach ($GLOBALS['cfg']['Servers'] as $key => $val) { + if (isset($_COOKIE['pmaAuth-' . $key])) { + $server = $key; + } + } + } + + if ($server === 0) { + /* delete user's choices that were stored in session */ + if (! defined('TESTSUITE')) { + session_unset(); + session_destroy(); + } + + /* Redirect to login form (or configured URL) */ + Core::sendHeaderLocation($redirect_url); + } else { + /* Redirect to other autenticated server */ + $_SESSION['partial_logout'] = true; + Core::sendHeaderLocation( + './index.php' . Url::getCommonRaw(array('server' => $server)) + ); + } + } + + /** + * Returns URL for login form. + * + * @return string + */ + public function getLoginFormURL() + { + return './index.php'; + } + + /** + * Returns error message for failed authentication. + * + * @param string $failure String describing why authentication has failed + * + * @return string + */ + public function getErrorMessage($failure) + { + if ($failure == 'empty-denied') { + return __( + 'Login without a password is forbidden by configuration' + . ' (see AllowNoPassword)' + ); + } elseif ($failure == 'root-denied' || $failure == 'allow-denied') { + return __('Access denied!'); + } elseif ($failure == 'no-activity') { + return sprintf( + __('No activity within %s seconds; please log in again.'), + intval($GLOBALS['cfg']['LoginCookieValidity']) + ); + } + + $dbi_error = $GLOBALS['dbi']->getError(); + if (!empty($dbi_error)) { + return htmlspecialchars($dbi_error); + } elseif (isset($GLOBALS['errno'])) { + return '#' . $GLOBALS['errno'] . ' ' + . __('Cannot log in to the MySQL server'); + } + + return __('Cannot log in to the MySQL server'); + } + + /** + * Callback when user changes password. + * + * @param string $password New password to set + * + * @return void + */ + public function handlePasswordChange($password) + { + } + + /** + * Store session access time in session. + * + * Tries to workaround PHP 5 session garbage collection which + * looks at the session file's last modified time + * + * @return void + */ + public function setSessionAccessTime() + { + if (isset($_REQUEST['guid'])) { + $guid = (string)$_REQUEST['guid']; + } else { + $guid = 'default'; + } + if (isset($_REQUEST['access_time'])) { + // Ensure access_time is in range <0, LoginCookieValidity + 1> + // to avoid excessive extension of validity. + // + // Negative values can cause session expiry extension + // Too big values can cause overflow and lead to same + $time = time() - min(max(0, intval($_REQUEST['access_time'])), $GLOBALS['cfg']['LoginCookieValidity'] + 1); + } else { + $time = time(); + } + $_SESSION['browser_access_time'][$guid] = $time; + } + + /** + * High level authentication interface + * + * Gets the credentials or shows login form if necessary + * + * @return void + */ + public function authenticate() + { + $success = $this->readCredentials(); + + /* Show login form (this exits) */ + if (! $success) { + /* Force generating of new session */ + Session::secure(); + $this->showLoginForm(); + } + + /* Store credentials (eg. in cookies) */ + $this->storeCredentials(); + /* Check allow/deny rules */ + $this->checkRules(); + } + + /** + * Check configuration defined restrictions for authentication + * + * @return void + */ + public function checkRules() + { + global $cfg; + + // Check IP-based Allow/Deny rules as soon as possible to reject the + // user based on mod_access in Apache + if (isset($cfg['Server']['AllowDeny']) + && isset($cfg['Server']['AllowDeny']['order']) + ) { + $allowDeny_forbidden = false; // default + if ($cfg['Server']['AllowDeny']['order'] == 'allow,deny') { + $allowDeny_forbidden = true; + if (IpAllowDeny::allowDeny('allow')) { + $allowDeny_forbidden = false; + } + if (IpAllowDeny::allowDeny('deny')) { + $allowDeny_forbidden = true; + } + } elseif ($cfg['Server']['AllowDeny']['order'] == 'deny,allow') { + if (IpAllowDeny::allowDeny('deny')) { + $allowDeny_forbidden = true; + } + if (IpAllowDeny::allowDeny('allow')) { + $allowDeny_forbidden = false; + } + } elseif ($cfg['Server']['AllowDeny']['order'] == 'explicit') { + if (IpAllowDeny::allowDeny('allow') && ! IpAllowDeny::allowDeny('deny')) { + $allowDeny_forbidden = false; + } else { + $allowDeny_forbidden = true; + } + } // end if ... elseif ... elseif + + // Ejects the user if banished + if ($allowDeny_forbidden) { + $this->showFailure('allow-denied'); + } + } // end if + + // is root allowed? + if (! $cfg['Server']['AllowRoot'] && $cfg['Server']['user'] == 'root') { + $this->showFailure('root-denied'); + } + + // is a login without password allowed? + if (! $cfg['Server']['AllowNoPassword'] + && $cfg['Server']['password'] === '' + ) { + $this->showFailure('empty-denied'); + } + } + + /** + * Checks whether two factor authentication is active + * for given user and performs it. + * + * @return void + */ + public function checkTwoFactor() + { + $twofactor = new TwoFactor($this->user); + + /* Do we need to show the form? */ + if ($twofactor->check()) { + return; + } + + $response = Response::getInstance(); + if ($response->loginPage()) { + if (defined('TESTSUITE')) { + return true; + } else { + exit; + } + } + echo Template::get('login/header')->render(['theme' => $GLOBALS['PMA_Theme']]); + Message::rawNotice( + __('You have enabled two factor authentication, please confirm your login.') + )->display(); + echo Template::get('login/twofactor')->render([ + 'form' => $twofactor->render(), + 'show_submit' => $twofactor->showSubmit, + ]); + echo Template::get('login/footer')->render(); + echo Config::renderFooter(); + if (! defined('TESTSUITE')) { + exit; + } + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Export/ExportCodegen.php b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Export/ExportCodegen.php new file mode 100644 index 00000000..b2a2763f --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Export/ExportCodegen.php @@ -0,0 +1,444 @@ +initSpecificVariables(); + $this->setProperties(); + } + + /** + * Initialize the local variables that are used for export CodeGen + * + * @return void + */ + protected function initSpecificVariables() + { + $this->_setCgFormats( + array( + "NHibernate C# DO", + "NHibernate XML", + ) + ); + + $this->_setCgHandlers( + array( + "_handleNHibernateCSBody", + "_handleNHibernateXMLBody", + ) + ); + } + + /** + * Sets the export CodeGen properties + * + * @return void + */ + protected function setProperties() + { + $exportPluginProperties = new ExportPluginProperties(); + $exportPluginProperties->setText('CodeGen'); + $exportPluginProperties->setExtension('cs'); + $exportPluginProperties->setMimeType('text/cs'); + $exportPluginProperties->setOptionsText(__('Options')); + + // create the root group that will be the options field for + // $exportPluginProperties + // this will be shown as "Format specific options" + $exportSpecificOptions = new OptionsPropertyRootGroup( + "Format Specific Options" + ); + + // general options main group + $generalOptions = new OptionsPropertyMainGroup("general_opts"); + // create primary items and add them to the group + $leaf = new HiddenPropertyItem("structure_or_data"); + $generalOptions->addProperty($leaf); + $leaf = new SelectPropertyItem( + "format", + __('Format:') + ); + $leaf->setValues($this->_getCgFormats()); + $generalOptions->addProperty($leaf); + // add the main group to the root group + $exportSpecificOptions->addProperty($generalOptions); + + // set the options for the export plugin property item + $exportPluginProperties->setOptions($exportSpecificOptions); + $this->properties = $exportPluginProperties; + } + + /** + * Outputs export header + * + * @return bool Whether it succeeded + */ + public function exportHeader() + { + return true; + } + + /** + * Outputs export footer + * + * @return bool Whether it succeeded + */ + public function exportFooter() + { + return true; + } + + /** + * Outputs database header + * + * @param string $db Database name + * @param string $db_alias Aliases of db + * + * @return bool Whether it succeeded + */ + public function exportDBHeader($db, $db_alias = '') + { + return true; + } + + /** + * Outputs database footer + * + * @param string $db Database name + * + * @return bool Whether it succeeded + */ + public function exportDBFooter($db) + { + return true; + } + + /** + * Outputs CREATE DATABASE statement + * + * @param string $db Database name + * @param string $export_type 'server', 'database', 'table' + * @param string $db_alias Aliases of db + * + * @return bool Whether it succeeded + */ + public function exportDBCreate($db, $export_type, $db_alias = '') + { + return true; + } + + /** + * Outputs the content of a table in NHibernate format + * + * @param string $db database name + * @param string $table table name + * @param string $crlf the end of line sequence + * @param string $error_url the url to go back in case of error + * @param string $sql_query SQL query for obtaining data + * @param array $aliases Aliases of db/table/columns + * + * @return bool Whether it succeeded + */ + public function exportData( + $db, + $table, + $crlf, + $error_url, + $sql_query, + array $aliases = array() + ) { + $CG_FORMATS = $this->_getCgFormats(); + $CG_HANDLERS = $this->_getCgHandlers(); + + $format = $GLOBALS['codegen_format']; + if (isset($CG_FORMATS[$format])) { + $method = $CG_HANDLERS[$format]; + + return Export::outputHandler( + $this->$method($db, $table, $crlf, $aliases) + ); + } + + return Export::outputHandler(sprintf("%s is not supported.", $format)); + } + + /** + * Used to make identifiers (from table or database names) + * + * @param string $str name to be converted + * @param bool $ucfirst whether to make the first character uppercase + * + * @return string identifier + */ + public static function cgMakeIdentifier($str, $ucfirst = true) + { + // remove unsafe characters + $str = preg_replace('/[^\p{L}\p{Nl}_]/u', '', $str); + // make sure first character is a letter or _ + if (!preg_match('/^\pL/u', $str)) { + $str = '_' . $str; + } + if ($ucfirst) { + $str = ucfirst($str); + } + + return $str; + } + + /** + * C# Handler + * + * @param string $db database name + * @param string $table table name + * @param string $crlf line separator + * @param array $aliases Aliases of db/table/columns + * + * @return string containing C# code lines, separated by "\n" + */ + private function _handleNHibernateCSBody($db, $table, $crlf, array $aliases = array()) + { + $db_alias = $db; + $table_alias = $table; + $this->initAlias($aliases, $db_alias, $table_alias); + $lines = array(); + + $result = $GLOBALS['dbi']->query( + sprintf( + 'DESC %s.%s', + Util::backquote($db), + Util::backquote($table) + ) + ); + if ($result) { + /** @var TableProperty[] $tableProperties */ + $tableProperties = array(); + while ($row = $GLOBALS['dbi']->fetchRow($result)) { + $col_as = $this->getAlias($aliases, $row[0], 'col', $db, $table); + if (!empty($col_as)) { + $row[0] = $col_as; + } + $tableProperties[] = new TableProperty($row); + } + $GLOBALS['dbi']->freeResult($result); + $lines[] = 'using System;'; + $lines[] = 'using System.Collections;'; + $lines[] = 'using System.Collections.Generic;'; + $lines[] = 'using System.Text;'; + $lines[] = 'namespace ' . ExportCodegen::cgMakeIdentifier($db_alias); + $lines[] = '{'; + $lines[] = ' #region ' + . ExportCodegen::cgMakeIdentifier($table_alias); + $lines[] = ' public class ' + . ExportCodegen::cgMakeIdentifier($table_alias); + $lines[] = ' {'; + $lines[] = ' #region Member Variables'; + foreach ($tableProperties as $tableProperty) { + $lines[] = $tableProperty->formatCs( + ' protected #dotNetPrimitiveType# _#name#;' + ); + } + $lines[] = ' #endregion'; + $lines[] = ' #region Constructors'; + $lines[] = ' public ' + . ExportCodegen::cgMakeIdentifier($table_alias) . '() { }'; + $temp = array(); + foreach ($tableProperties as $tableProperty) { + if (!$tableProperty->isPK()) { + $temp[] = $tableProperty->formatCs( + '#dotNetPrimitiveType# #name#' + ); + } + } + $lines[] = ' public ' + . ExportCodegen::cgMakeIdentifier($table_alias) + . '(' + . implode(', ', $temp) + . ')'; + $lines[] = ' {'; + foreach ($tableProperties as $tableProperty) { + if (!$tableProperty->isPK()) { + $lines[] = $tableProperty->formatCs( + ' this._#name#=#name#;' + ); + } + } + $lines[] = ' }'; + $lines[] = ' #endregion'; + $lines[] = ' #region Public Properties'; + foreach ($tableProperties as $tableProperty) { + $lines[] = $tableProperty->formatCs( + ' public virtual #dotNetPrimitiveType# #ucfirstName#' + . "\n" + . ' {' . "\n" + . ' get {return _#name#;}' . "\n" + . ' set {_#name#=value;}' . "\n" + . ' }' + ); + } + $lines[] = ' #endregion'; + $lines[] = ' }'; + $lines[] = ' #endregion'; + $lines[] = '}'; + } + + return implode($crlf, $lines); + } + + /** + * XML Handler + * + * @param string $db database name + * @param string $table table name + * @param string $crlf line separator + * @param array $aliases Aliases of db/table/columns + * + * @return string containing XML code lines, separated by "\n" + */ + private function _handleNHibernateXMLBody( + $db, + $table, + $crlf, + array $aliases = array() + ) { + $db_alias = $db; + $table_alias = $table; + $this->initAlias($aliases, $db_alias, $table_alias); + $lines = array(); + $lines[] = ''; + $lines[] = ''; + $lines[] = ' '; + $result = $GLOBALS['dbi']->query( + sprintf( + "DESC %s.%s", + Util::backquote($db), + Util::backquote($table) + ) + ); + if ($result) { + while ($row = $GLOBALS['dbi']->fetchRow($result)) { + $col_as = $this->getAlias($aliases, $row[0], 'col', $db, $table); + if (!empty($col_as)) { + $row[0] = $col_as; + } + $tableProperty = new TableProperty($row); + if ($tableProperty->isPK()) { + $lines[] = $tableProperty->formatXml( + ' ' . "\n" + . ' ' . "\n" + . ' ' . "\n" + . ' ' + ); + } else { + $lines[] = $tableProperty->formatXml( + ' ' . "\n" + . ' ' . "\n" + . ' ' + ); + } + } + $GLOBALS['dbi']->freeResult($result); + } + $lines[] = ' '; + $lines[] = ''; + + return implode($crlf, $lines); + } + + + /* ~~~~~~~~~~~~~~~~~~~~ Getters and Setters ~~~~~~~~~~~~~~~~~~~~ */ + + /** + * Getter for CodeGen formats + * + * @return array + */ + private function _getCgFormats() + { + return $this->_cgFormats; + } + + /** + * Setter for CodeGen formats + * + * @param array $CG_FORMATS contains CodeGen Formats + * + * @return void + */ + private function _setCgFormats(array $CG_FORMATS) + { + $this->_cgFormats = $CG_FORMATS; + } + + /** + * Getter for CodeGen handlers + * + * @return array + */ + private function _getCgHandlers() + { + return $this->_cgHandlers; + } + + /** + * Setter for CodeGen handlers + * + * @param array $CG_HANDLERS contains CodeGen handler methods + * + * @return void + */ + private function _setCgHandlers(array $CG_HANDLERS) + { + $this->_cgHandlers = $CG_HANDLERS; + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Export/ExportCsv.php b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Export/ExportCsv.php new file mode 100644 index 00000000..13f2c87b --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Export/ExportCsv.php @@ -0,0 +1,332 @@ +setProperties(); + } + + /** + * Sets the export CSV properties + * + * @return void + */ + protected function setProperties() + { + $exportPluginProperties = new ExportPluginProperties(); + $exportPluginProperties->setText('CSV'); + $exportPluginProperties->setExtension('csv'); + $exportPluginProperties->setMimeType('text/comma-separated-values'); + $exportPluginProperties->setOptionsText(__('Options')); + + // create the root group that will be the options field for + // $exportPluginProperties + // this will be shown as "Format specific options" + $exportSpecificOptions = new OptionsPropertyRootGroup( + "Format Specific Options" + ); + + // general options main group + $generalOptions = new OptionsPropertyMainGroup("general_opts"); + // create leaf items and add them to the group + $leaf = new TextPropertyItem( + "separator", + __('Columns separated with:') + ); + $generalOptions->addProperty($leaf); + $leaf = new TextPropertyItem( + "enclosed", + __('Columns enclosed with:') + ); + $generalOptions->addProperty($leaf); + $leaf = new TextPropertyItem( + "escaped", + __('Columns escaped with:') + ); + $generalOptions->addProperty($leaf); + $leaf = new TextPropertyItem( + "terminated", + __('Lines terminated with:') + ); + $generalOptions->addProperty($leaf); + $leaf = new TextPropertyItem( + 'null', + __('Replace NULL with:') + ); + $generalOptions->addProperty($leaf); + $leaf = new BoolPropertyItem( + 'removeCRLF', + __('Remove carriage return/line feed characters within columns') + ); + $generalOptions->addProperty($leaf); + $leaf = new BoolPropertyItem( + 'columns', + __('Put columns names in the first row') + ); + $generalOptions->addProperty($leaf); + $leaf = new HiddenPropertyItem( + 'structure_or_data' + ); + $generalOptions->addProperty($leaf); + // add the main group to the root group + $exportSpecificOptions->addProperty($generalOptions); + + // set the options for the export plugin property item + $exportPluginProperties->setOptions($exportSpecificOptions); + $this->properties = $exportPluginProperties; + } + + /** + * Outputs export header + * + * @return bool Whether it succeeded + */ + public function exportHeader() + { + global $what, $csv_terminated, $csv_separator, $csv_enclosed, $csv_escaped; + + // Here we just prepare some values for export + if ($what == 'excel') { + $csv_terminated = "\015\012"; + switch ($GLOBALS['excel_edition']) { + case 'win': + // as tested on Windows with Excel 2002 and Excel 2007 + $csv_separator = ';'; + break; + case 'mac_excel2003': + $csv_separator = ';'; + break; + case 'mac_excel2008': + $csv_separator = ','; + break; + } + $csv_enclosed = '"'; + $csv_escaped = '"'; + if (isset($GLOBALS['excel_columns'])) { + $GLOBALS['csv_columns'] = 'yes'; + } + } else { + if (empty($csv_terminated) + || mb_strtolower($csv_terminated) == 'auto' + ) { + $csv_terminated = $GLOBALS['crlf']; + } else { + $csv_terminated = str_replace('\\r', "\015", $csv_terminated); + $csv_terminated = str_replace('\\n', "\012", $csv_terminated); + $csv_terminated = str_replace('\\t', "\011", $csv_terminated); + } // end if + $csv_separator = str_replace('\\t', "\011", $csv_separator); + } + + return true; + } + + /** + * Outputs export footer + * + * @return bool Whether it succeeded + */ + public function exportFooter() + { + return true; + } + + /** + * Outputs database header + * + * @param string $db Database name + * @param string $db_alias Alias of db + * + * @return bool Whether it succeeded + */ + public function exportDBHeader($db, $db_alias = '') + { + return true; + } + + /** + * Outputs database footer + * + * @param string $db Database name + * + * @return bool Whether it succeeded + */ + public function exportDBFooter($db) + { + return true; + } + + /** + * Outputs CREATE DATABASE statement + * + * @param string $db Database name + * @param string $export_type 'server', 'database', 'table' + * @param string $db_alias Aliases of db + * + * @return bool Whether it succeeded + */ + public function exportDBCreate($db, $export_type, $db_alias = '') + { + return true; + } + + /** + * Outputs the content of a table in CSV format + * + * @param string $db database name + * @param string $table table name + * @param string $crlf the end of line sequence + * @param string $error_url the url to go back in case of error + * @param string $sql_query SQL query for obtaining data + * @param array $aliases Aliases of db/table/columns + * + * @return bool Whether it succeeded + */ + public function exportData( + $db, + $table, + $crlf, + $error_url, + $sql_query, + array $aliases = array() + ) { + global $what, $csv_terminated, $csv_separator, $csv_enclosed, $csv_escaped; + + $db_alias = $db; + $table_alias = $table; + $this->initAlias($aliases, $db_alias, $table_alias); + + // Gets the data from the database + $result = $GLOBALS['dbi']->query( + $sql_query, + DatabaseInterface::CONNECT_USER, + DatabaseInterface::QUERY_UNBUFFERED + ); + $fields_cnt = $GLOBALS['dbi']->numFields($result); + + // If required, get fields name at the first line + if (isset($GLOBALS['csv_columns'])) { + $schema_insert = ''; + for ($i = 0; $i < $fields_cnt; $i++) { + $col_as = $GLOBALS['dbi']->fieldName($result, $i); + if (!empty($aliases[$db]['tables'][$table]['columns'][$col_as])) { + $col_as = $aliases[$db]['tables'][$table]['columns'][$col_as]; + } + $col_as = stripslashes($col_as); + if ($csv_enclosed == '') { + $schema_insert .= $col_as; + } else { + $schema_insert .= $csv_enclosed + . str_replace( + $csv_enclosed, + $csv_escaped . $csv_enclosed, + $col_as + ) + . $csv_enclosed; + } + $schema_insert .= $csv_separator; + } // end for + $schema_insert = trim(mb_substr($schema_insert, 0, -1)); + if (!Export::outputHandler($schema_insert . $csv_terminated)) { + return false; + } + } // end if + + // Format the data + while ($row = $GLOBALS['dbi']->fetchRow($result)) { + $schema_insert = ''; + for ($j = 0; $j < $fields_cnt; $j++) { + if (!isset($row[$j]) || is_null($row[$j])) { + $schema_insert .= $GLOBALS[$what . '_null']; + } elseif ($row[$j] == '0' || $row[$j] != '') { + // always enclose fields + if ($what == 'excel') { + $row[$j] = preg_replace("/\015(\012)?/", "\012", $row[$j]); + } + // remove CRLF characters within field + if (isset($GLOBALS[$what . '_removeCRLF']) + && $GLOBALS[$what . '_removeCRLF'] + ) { + $row[$j] = str_replace( + "\n", + "", + str_replace( + "\r", + "", + $row[$j] + ) + ); + } + if ($csv_enclosed == '') { + $schema_insert .= $row[$j]; + } else { + // also double the escape string if found in the data + if ($csv_escaped != $csv_enclosed) { + $schema_insert .= $csv_enclosed + . str_replace( + $csv_enclosed, + $csv_escaped . $csv_enclosed, + str_replace( + $csv_escaped, + $csv_escaped . $csv_escaped, + $row[$j] + ) + ) + . $csv_enclosed; + } else { + // avoid a problem when escape string equals enclose + $schema_insert .= $csv_enclosed + . str_replace( + $csv_enclosed, + $csv_escaped . $csv_enclosed, + $row[$j] + ) + . $csv_enclosed; + } + } + } else { + $schema_insert .= ''; + } + if ($j < $fields_cnt - 1) { + $schema_insert .= $csv_separator; + } + } // end for + + if (!Export::outputHandler($schema_insert . $csv_terminated)) { + return false; + } + } // end while + $GLOBALS['dbi']->freeResult($result); + + return true; + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Export/ExportExcel.php b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Export/ExportExcel.php new file mode 100644 index 00000000..fa4622ad --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Export/ExportExcel.php @@ -0,0 +1,88 @@ +setText('CSV for MS Excel'); + $exportPluginProperties->setExtension('csv'); + $exportPluginProperties->setMimeType('text/comma-separated-values'); + $exportPluginProperties->setOptionsText(__('Options')); + + // create the root group that will be the options field for + // $exportPluginProperties + // this will be shown as "Format specific options" + $exportSpecificOptions = new OptionsPropertyRootGroup( + "Format Specific Options" + ); + + // general options main group + $generalOptions = new OptionsPropertyMainGroup("general_opts"); + // create primary items and add them to the group + $leaf = new TextPropertyItem( + 'null', + __('Replace NULL with:') + ); + $generalOptions->addProperty($leaf); + $leaf = new BoolPropertyItem( + 'removeCRLF', + __('Remove carriage return/line feed characters within columns') + ); + $generalOptions->addProperty($leaf); + $leaf = new BoolPropertyItem( + 'columns', + __('Put columns names in the first row') + ); + $generalOptions->addProperty($leaf); + $leaf = new SelectPropertyItem( + 'edition', + __('Excel edition:') + ); + $leaf->setValues( + array( + 'win' => 'Windows', + 'mac_excel2003' => 'Excel 2003 / Macintosh', + 'mac_excel2008' => 'Excel 2008 / Macintosh', + ) + ); + $generalOptions->addProperty($leaf); + $leaf = new HiddenPropertyItem( + 'structure_or_data' + ); + $generalOptions->addProperty($leaf); + // add the main group to the root group + $exportSpecificOptions->addProperty($generalOptions); + + // set the options for the export plugin property item + $exportPluginProperties->setOptions($exportSpecificOptions); + $this->properties = $exportPluginProperties; + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Export/ExportHtmlword.php b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Export/ExportHtmlword.php new file mode 100644 index 00000000..5aa72935 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Export/ExportHtmlword.php @@ -0,0 +1,666 @@ +setProperties(); + } + + /** + * Sets the export HTML-Word properties + * + * @return void + */ + protected function setProperties() + { + $exportPluginProperties = new ExportPluginProperties(); + $exportPluginProperties->setText('Microsoft Word 2000'); + $exportPluginProperties->setExtension('doc'); + $exportPluginProperties->setMimeType('application/vnd.ms-word'); + $exportPluginProperties->setForceFile(true); + $exportPluginProperties->setOptionsText(__('Options')); + + // create the root group that will be the options field for + // $exportPluginProperties + // this will be shown as "Format specific options" + $exportSpecificOptions = new OptionsPropertyRootGroup( + "Format Specific Options" + ); + + // what to dump (structure/data/both) + $dumpWhat = new OptionsPropertyMainGroup( + "dump_what", __('Dump table') + ); + // create primary items and add them to the group + $leaf = new RadioPropertyItem("structure_or_data"); + $leaf->setValues( + array( + 'structure' => __('structure'), + 'data' => __('data'), + 'structure_and_data' => __('structure and data'), + ) + ); + $dumpWhat->addProperty($leaf); + // add the main group to the root group + $exportSpecificOptions->addProperty($dumpWhat); + + // data options main group + $dataOptions = new OptionsPropertyMainGroup( + "dump_what", __('Data dump options') + ); + $dataOptions->setForce('structure'); + // create primary items and add them to the group + $leaf = new TextPropertyItem( + "null", + __('Replace NULL with:') + ); + $dataOptions->addProperty($leaf); + $leaf = new BoolPropertyItem( + "columns", + __('Put columns names in the first row') + ); + $dataOptions->addProperty($leaf); + // add the main group to the root group + $exportSpecificOptions->addProperty($dataOptions); + + // set the options for the export plugin property item + $exportPluginProperties->setOptions($exportSpecificOptions); + $this->properties = $exportPluginProperties; + } + + /** + * Outputs export header + * + * @return bool Whether it succeeded + */ + public function exportHeader() + { + global $charset; + + return Export::outputHandler( + ' + + + + + + + ' + ); + } + + /** + * Outputs export footer + * + * @return bool Whether it succeeded + */ + public function exportFooter() + { + return Export::outputHandler(''); + } + + /** + * Outputs database header + * + * @param string $db Database name + * @param string $db_alias Aliases of db + * + * @return bool Whether it succeeded + */ + public function exportDBHeader($db, $db_alias = '') + { + if (empty($db_alias)) { + $db_alias = $db; + } + + return Export::outputHandler( + '

    ' . __('Database') . ' ' . htmlspecialchars($db_alias) . '

    ' + ); + } + + /** + * Outputs database footer + * + * @param string $db Database name + * + * @return bool Whether it succeeded + */ + public function exportDBFooter($db) + { + return true; + } + + /** + * Outputs CREATE DATABASE statement + * + * @param string $db Database name + * @param string $export_type 'server', 'database', 'table' + * @param string $db_alias Aliases of db + * + * @return bool Whether it succeeded + */ + public function exportDBCreate($db, $export_type, $db_alias = '') + { + return true; + } + + /** + * Outputs the content of a table in HTML-Word format + * + * @param string $db database name + * @param string $table table name + * @param string $crlf the end of line sequence + * @param string $error_url the url to go back in case of error + * @param string $sql_query SQL query for obtaining data + * @param array $aliases Aliases of db/table/columns + * + * @return bool Whether it succeeded + */ + public function exportData( + $db, + $table, + $crlf, + $error_url, + $sql_query, + array $aliases = array() + ) { + global $what; + + $db_alias = $db; + $table_alias = $table; + $this->initAlias($aliases, $db_alias, $table_alias); + + if (!Export::outputHandler( + '

    ' + . __('Dumping data for table') . ' ' . htmlspecialchars($table_alias) + . '

    ' + ) + ) { + return false; + } + if (!Export::outputHandler( + '' + ) + ) { + return false; + } + + // Gets the data from the database + $result = $GLOBALS['dbi']->query( + $sql_query, + DatabaseInterface::CONNECT_USER, + DatabaseInterface::QUERY_UNBUFFERED + ); + $fields_cnt = $GLOBALS['dbi']->numFields($result); + + // If required, get fields name at the first line + if (isset($GLOBALS['htmlword_columns'])) { + $schema_insert = ''; + for ($i = 0; $i < $fields_cnt; $i++) { + $col_as = $GLOBALS['dbi']->fieldName($result, $i); + if (!empty($aliases[$db]['tables'][$table]['columns'][$col_as])) { + $col_as = $aliases[$db]['tables'][$table]['columns'][$col_as]; + } + $col_as = stripslashes($col_as); + $schema_insert .= ''; + } // end for + $schema_insert .= ''; + if (!Export::outputHandler($schema_insert)) { + return false; + } + } // end if + + // Format the data + while ($row = $GLOBALS['dbi']->fetchRow($result)) { + $schema_insert = ''; + for ($j = 0; $j < $fields_cnt; $j++) { + if (!isset($row[$j]) || is_null($row[$j])) { + $value = $GLOBALS[$what . '_null']; + } elseif ($row[$j] == '0' || $row[$j] != '') { + $value = $row[$j]; + } else { + $value = ''; + } + $schema_insert .= ''; + } // end for + $schema_insert .= ''; + if (!Export::outputHandler($schema_insert)) { + return false; + } + } // end while + $GLOBALS['dbi']->freeResult($result); + + return Export::outputHandler('
    '); + } + + /** + * Returns a stand-in CREATE definition to resolve view dependencies + * + * @param string $db the database name + * @param string $view the view name + * @param string $crlf the end of line sequence + * @param array $aliases Aliases of db/table/columns + * + * @return string resulting definition + */ + public function getTableDefStandIn($db, $view, $crlf, $aliases = array()) + { + $schema_insert = '' + . '' + . '' + . '' + . '' + . '' + . ''; + + /** + * Get the unique keys in the view + */ + $unique_keys = array(); + $keys = $GLOBALS['dbi']->getTableIndexes($db, $view); + foreach ($keys as $key) { + if ($key['Non_unique'] == 0) { + $unique_keys[] = $key['Column_name']; + } + } + + $columns = $GLOBALS['dbi']->getColumns($db, $view); + foreach ($columns as $column) { + $col_as = $column['Field']; + if (!empty($aliases[$db]['tables'][$view]['columns'][$col_as])) { + $col_as = $aliases[$db]['tables'][$view]['columns'][$col_as]; + } + $schema_insert .= $this->formatOneColumnDefinition( + $column, + $unique_keys, + $col_as + ); + $schema_insert .= ''; + } + + $schema_insert .= '
    '; + + return $schema_insert; + } + + /** + * Returns $table's CREATE definition + * + * @param string $db the database name + * @param string $table the table name + * @param bool $do_relation whether to include relation comments + * @param bool $do_comments whether to include the pmadb-style column + * comments as comments in the structure; + * this is deprecated but the parameter is + * left here because export.php calls + * PMA_exportStructure() also for other + * export types which use this parameter + * @param bool $do_mime whether to include mime comments + * at the end + * @param bool $view whether we're handling a view + * @param array $aliases Aliases of db/table/columns + * + * @return string resulting schema + */ + public function getTableDef( + $db, + $table, + $do_relation, + $do_comments, + $do_mime, + $view = false, + array $aliases = array() + ) { + // set $cfgRelation here, because there is a chance that it's modified + // since the class initialization + global $cfgRelation; + + $schema_insert = ''; + + /** + * Gets fields properties + */ + $GLOBALS['dbi']->selectDb($db); + + // Check if we can use Relations + list($res_rel, $have_rel) = $this->relation->getRelationsAndStatus( + $do_relation && !empty($cfgRelation['relation']), + $db, + $table + ); + + /** + * Displays the table structure + */ + $schema_insert .= ''; + + $schema_insert .= ''; + $schema_insert .= ''; + $schema_insert .= ''; + $schema_insert .= ''; + $schema_insert .= ''; + if ($do_relation && $have_rel) { + $schema_insert .= ''; + } + if ($do_comments) { + $schema_insert .= ''; + $comments = $this->relation->getComments($db, $table); + } + if ($do_mime && $cfgRelation['mimework']) { + $schema_insert .= ''; + $mime_map = Transformations::getMIME($db, $table, true); + } + $schema_insert .= ''; + + $columns = $GLOBALS['dbi']->getColumns($db, $table); + /** + * Get the unique keys in the table + */ + $unique_keys = array(); + $keys = $GLOBALS['dbi']->getTableIndexes($db, $table); + foreach ($keys as $key) { + if ($key['Non_unique'] == 0) { + $unique_keys[] = $key['Column_name']; + } + } + foreach ($columns as $column) { + $col_as = $column['Field']; + if (!empty($aliases[$db]['tables'][$table]['columns'][$col_as])) { + $col_as = $aliases[$db]['tables'][$table]['columns'][$col_as]; + } + $schema_insert .= $this->formatOneColumnDefinition( + $column, + $unique_keys, + $col_as + ); + $field_name = $column['Field']; + if ($do_relation && $have_rel) { + $schema_insert .= ''; + } + if ($do_comments && $cfgRelation['commwork']) { + $schema_insert .= ''; + } + if ($do_mime && $cfgRelation['mimework']) { + $schema_insert .= ''; + } + + $schema_insert .= ''; + } // end foreach + + $schema_insert .= '
    ' + . htmlspecialchars( + $this->getRelationString( + $res_rel, + $field_name, + $db, + $aliases + ) + ) + . '' + . (isset($comments[$field_name]) + ? htmlspecialchars($comments[$field_name]) + : '') . '' + . (isset($mime_map[$field_name]) ? + htmlspecialchars( + str_replace('_', '/', $mime_map[$field_name]['mimetype']) + ) + : '') . '
    '; + + return $schema_insert; + } + + /** + * Outputs triggers + * + * @param string $db database name + * @param string $table table name + * + * @return string Formatted triggers list + */ + protected function getTriggers($db, $table) + { + $dump = ''; + $dump .= ''; + $dump .= ''; + $dump .= ''; + $dump .= ''; + $dump .= ''; + $dump .= ''; + + $triggers = $GLOBALS['dbi']->getTriggers($db, $table); + + foreach ($triggers as $trigger) { + $dump .= ''; + $dump .= '' + . '' + . '' + . '' + . ''; + } + + $dump .= '
    '; + + return $dump; + } + + /** + * Outputs table's structure + * + * @param string $db database name + * @param string $table table name + * @param string $crlf the end of line sequence + * @param string $error_url the url to go back in case of error + * @param string $export_mode 'create_table', 'triggers', 'create_view', + * 'stand_in' + * @param string $export_type 'server', 'database', 'table' + * @param bool $do_relation whether to include relation comments + * @param bool $do_comments whether to include the pmadb-style column + * comments as comments in the structure; + * this is deprecated but the parameter is + * left here because export.php calls + * PMA_exportStructure() also for other + * export types which use this parameter + * @param bool $do_mime whether to include mime comments + * @param bool $dates whether to include creation/update/check dates + * @param array $aliases Aliases of db/table/columns + * + * @return bool Whether it succeeded + */ + public function exportStructure( + $db, + $table, + $crlf, + $error_url, + $export_mode, + $export_type, + $do_relation = false, + $do_comments = false, + $do_mime = false, + $dates = false, + array $aliases = array() + ) { + $db_alias = $db; + $table_alias = $table; + $this->initAlias($aliases, $db_alias, $table_alias); + + $dump = ''; + + switch ($export_mode) { + case 'create_table': + $dump .= '

    ' + . __('Table structure for table') . ' ' + . htmlspecialchars($table_alias) + . '

    '; + $dump .= $this->getTableDef( + $db, + $table, + $do_relation, + $do_comments, + $do_mime, + false, + $aliases + ); + break; + case 'triggers': + $dump = ''; + $triggers = $GLOBALS['dbi']->getTriggers($db, $table); + if ($triggers) { + $dump .= '

    ' + . __('Triggers') . ' ' . htmlspecialchars($table_alias) + . '

    '; + $dump .= $this->getTriggers($db, $table); + } + break; + case 'create_view': + $dump .= '

    ' + . __('Structure for view') . ' ' . htmlspecialchars($table_alias) + . '

    '; + $dump .= $this->getTableDef( + $db, + $table, + $do_relation, + $do_comments, + $do_mime, + true, + $aliases + ); + break; + case 'stand_in': + $dump .= '

    ' + . __('Stand-in structure for view') . ' ' + . htmlspecialchars($table_alias) + . '

    '; + // export a stand-in definition to resolve view dependencies + $dump .= $this->getTableDefStandIn($db, $table, $crlf, $aliases); + } // end switch + + return Export::outputHandler($dump); + } + + /** + * Formats the definition for one column + * + * @param array $column info about this column + * @param array $unique_keys unique keys of the table + * @param string $col_alias Column Alias + * + * @return string Formatted column definition + */ + protected function formatOneColumnDefinition( + array $column, + array $unique_keys, + $col_alias = '' + ) { + if (empty($col_alias)) { + $col_alias = $column['Field']; + } + $definition = ''; + + $extracted_columnspec = Util::extractColumnSpec($column['Type']); + + $type = htmlspecialchars($extracted_columnspec['print_type']); + if (empty($type)) { + $type = ' '; + } + + if (!isset($column['Default'])) { + if ($column['Null'] != 'NO') { + $column['Default'] = 'NULL'; + } + } + + $fmt_pre = ''; + $fmt_post = ''; + if (in_array($column['Field'], $unique_keys)) { + $fmt_pre = '' . $fmt_pre; + $fmt_post = $fmt_post . ''; + } + if ($column['Key'] == 'PRI') { + $fmt_pre = '' . $fmt_pre; + $fmt_post = $fmt_post . ''; + } + $definition .= '' . $fmt_pre + . htmlspecialchars($col_alias) . $fmt_post . ''; + $definition .= '' . htmlspecialchars($type) . ''; + $definition .= '' + . (($column['Null'] == '' || $column['Null'] == 'NO') + ? __('No') + : __('Yes')) + . ''; + $definition .= '' + . htmlspecialchars(isset($column['Default']) ? $column['Default'] : '') + . ''; + + return $definition; + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Export/ExportJson.php b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Export/ExportJson.php new file mode 100644 index 00000000..27a2dc32 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Export/ExportJson.php @@ -0,0 +1,293 @@ +setProperties(); + } + + /** + * Encodes the data into JSON + * + * @param mixed $data Data to encode + * + * @return string + */ + public function encode($data) + { + $options = 0; + if (isset($GLOBALS['json_pretty_print']) + && $GLOBALS['json_pretty_print'] + ) { + $options |= JSON_PRETTY_PRINT; + } + if (isset($GLOBALS['json_unicode']) + && $GLOBALS['json_unicode'] + ) { + $options |= JSON_UNESCAPED_UNICODE; + } + return json_encode($data, $options); + } + + /** + * Sets the export JSON properties + * + * @return void + */ + protected function setProperties() + { + $exportPluginProperties = new ExportPluginProperties(); + $exportPluginProperties->setText('JSON'); + $exportPluginProperties->setExtension('json'); + $exportPluginProperties->setMimeType('text/plain'); + $exportPluginProperties->setOptionsText(__('Options')); + + // create the root group that will be the options field for + // $exportPluginProperties + // this will be shown as "Format specific options" + $exportSpecificOptions = new OptionsPropertyRootGroup( + "Format Specific Options" + ); + + // general options main group + $generalOptions = new OptionsPropertyMainGroup("general_opts"); + // create primary items and add them to the group + $leaf = new HiddenPropertyItem("structure_or_data"); + $generalOptions->addProperty($leaf); + + $leaf = new BoolPropertyItem( + 'pretty_print', + __('Output pretty-printed JSON (Use human-readable formatting)') + ); + $generalOptions->addProperty($leaf); + + $leaf = new BoolPropertyItem( + 'unicode', + __('Output unicode characters unescaped') + ); + $generalOptions->addProperty($leaf); + + // add the main group to the root group + $exportSpecificOptions->addProperty($generalOptions); + + // set the options for the export plugin property item + $exportPluginProperties->setOptions($exportSpecificOptions); + $this->properties = $exportPluginProperties; + } + + /** + * Outputs export header + * + * @return bool Whether it succeeded + */ + public function exportHeader() + { + global $crlf; + + $meta = array( + 'type' => 'header', + 'version' => PMA_VERSION, + 'comment' => 'Export to JSON plugin for PHPMyAdmin', + ); + + return Export::outputHandler( + '[' . $crlf . $this->encode($meta) . ',' . $crlf + ); + } + + /** + * Outputs export footer + * + * @return bool Whether it succeeded + */ + public function exportFooter() + { + global $crlf; + + return Export::outputHandler(']' . $crlf); + } + + /** + * Outputs database header + * + * @param string $db Database name + * @param string $db_alias Aliases of db + * + * @return bool Whether it succeeded + */ + public function exportDBHeader($db, $db_alias = '') + { + global $crlf; + + if (empty($db_alias)) { + $db_alias = $db; + } + + $meta = array( + 'type' => 'database', + 'name' => $db_alias + ); + + return Export::outputHandler( + $this->encode($meta) . ',' . $crlf + ); + } + + /** + * Outputs database footer + * + * @param string $db Database name + * + * @return bool Whether it succeeded + */ + public function exportDBFooter($db) + { + return true; + } + + /** + * Outputs CREATE DATABASE statement + * + * @param string $db Database name + * @param string $export_type 'server', 'database', 'table' + * @param string $db_alias Aliases of db + * + * @return bool Whether it succeeded + */ + public function exportDBCreate($db, $export_type, $db_alias = '') + { + return true; + } + + /** + * Outputs the content of a table in JSON format + * + * @param string $db database name + * @param string $table table name + * @param string $crlf the end of line sequence + * @param string $error_url the url to go back in case of error + * @param string $sql_query SQL query for obtaining data + * @param array $aliases Aliases of db/table/columns + * + * @return bool Whether it succeeded + */ + public function exportData( + $db, + $table, + $crlf, + $error_url, + $sql_query, + array $aliases = array() + ) { + $db_alias = $db; + $table_alias = $table; + $this->initAlias($aliases, $db_alias, $table_alias); + + if (! $this->first) { + if (!Export::outputHandler(',')) { + return false; + } + } else { + $this->first = false; + } + + $buffer = $this->encode( + array( + 'type' => 'table', + 'name' => $table_alias, + 'database' => $db_alias, + 'data' => "@@DATA@@" + ) + ); + list($header, $footer) = explode('"@@DATA@@"', $buffer); + + if (!Export::outputHandler($header . $crlf . '[' . $crlf)) { + return false; + } + + $result = $GLOBALS['dbi']->query( + $sql_query, + DatabaseInterface::CONNECT_USER, + DatabaseInterface::QUERY_UNBUFFERED + ); + $columns_cnt = $GLOBALS['dbi']->numFields($result); + $fields_meta = $GLOBALS['dbi']->getFieldsMeta($result); + + $columns = array(); + for ($i = 0; $i < $columns_cnt; $i++) { + $col_as = $GLOBALS['dbi']->fieldName($result, $i); + if (!empty($aliases[$db]['tables'][$table]['columns'][$col_as])) { + $col_as = $aliases[$db]['tables'][$table]['columns'][$col_as]; + } + $columns[$i] = stripslashes($col_as); + } + + $record_cnt = 0; + while ($record = $GLOBALS['dbi']->fetchRow($result)) { + + $record_cnt++; + + // Output table name as comment if this is the first record of the table + if ($record_cnt > 1) { + if (!Export::outputHandler(',' . $crlf)) { + return false; + } + } + + $data = array(); + + for ($i = 0; $i < $columns_cnt; $i++) { + if ($fields_meta[$i]->type === 'geometry') { + // export GIS types as hex + $record[$i] = '0x' . bin2hex($record[$i]); + } + $data[$columns[$i]] = $record[$i]; + } + + $encodedData = $this->encode($data); + if (! $encodedData) { + return false; + } + if (! Export::outputHandler($encodedData)) { + return false; + } + } + + if (!Export::outputHandler($crlf . ']' . $crlf . $footer . $crlf)) { + return false; + } + + $GLOBALS['dbi']->freeResult($result); + + return true; + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Export/ExportLatex.php b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Export/ExportLatex.php new file mode 100644 index 00000000..f9cebe18 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Export/ExportLatex.php @@ -0,0 +1,678 @@ +initSpecificVariables(); + $this->setProperties(); + } + + /** + * Initialize the local variables that are used for export Latex + * + * @return void + */ + protected function initSpecificVariables() + { + /* Messages used in default captions */ + $GLOBALS['strLatexContent'] = __('Content of table @TABLE@'); + $GLOBALS['strLatexContinued'] = __('(continued)'); + $GLOBALS['strLatexStructure'] = __('Structure of table @TABLE@'); + } + + /** + * Sets the export Latex properties + * + * @return void + */ + protected function setProperties() + { + global $plugin_param; + $hide_structure = false; + if ($plugin_param['export_type'] == 'table' + && !$plugin_param['single_table'] + ) { + $hide_structure = true; + } + + $exportPluginProperties = new ExportPluginProperties(); + $exportPluginProperties->setText('LaTeX'); + $exportPluginProperties->setExtension('tex'); + $exportPluginProperties->setMimeType('application/x-tex'); + $exportPluginProperties->setOptionsText(__('Options')); + + // create the root group that will be the options field for + // $exportPluginProperties + // this will be shown as "Format specific options" + $exportSpecificOptions = new OptionsPropertyRootGroup( + "Format Specific Options" + ); + + // general options main group + $generalOptions = new OptionsPropertyMainGroup("general_opts"); + // create primary items and add them to the group + $leaf = new BoolPropertyItem( + "caption", + __('Include table caption') + ); + $generalOptions->addProperty($leaf); + // add the main group to the root group + $exportSpecificOptions->addProperty($generalOptions); + + // what to dump (structure/data/both) main group + $dumpWhat = new OptionsPropertyMainGroup( + "dump_what", __('Dump table') + ); + // create primary items and add them to the group + $leaf = new RadioPropertyItem("structure_or_data"); + $leaf->setValues( + array( + 'structure' => __('structure'), + 'data' => __('data'), + 'structure_and_data' => __('structure and data'), + ) + ); + $dumpWhat->addProperty($leaf); + // add the main group to the root group + $exportSpecificOptions->addProperty($dumpWhat); + + // structure options main group + if (!$hide_structure) { + $structureOptions = new OptionsPropertyMainGroup( + "structure", __('Object creation options') + ); + $structureOptions->setForce('data'); + // create primary items and add them to the group + $leaf = new TextPropertyItem( + "structure_caption", + __('Table caption:') + ); + $leaf->setDoc('faq6-27'); + $structureOptions->addProperty($leaf); + $leaf = new TextPropertyItem( + "structure_continued_caption", + __('Table caption (continued):') + ); + $leaf->setDoc('faq6-27'); + $structureOptions->addProperty($leaf); + $leaf = new TextPropertyItem( + "structure_label", + __('Label key:') + ); + $leaf->setDoc('faq6-27'); + $structureOptions->addProperty($leaf); + if (!empty($GLOBALS['cfgRelation']['relation'])) { + $leaf = new BoolPropertyItem( + "relation", + __('Display foreign key relationships') + ); + $structureOptions->addProperty($leaf); + } + $leaf = new BoolPropertyItem( + "comments", + __('Display comments') + ); + $structureOptions->addProperty($leaf); + if (!empty($GLOBALS['cfgRelation']['mimework'])) { + $leaf = new BoolPropertyItem( + "mime", + __('Display MIME types') + ); + $structureOptions->addProperty($leaf); + } + // add the main group to the root group + $exportSpecificOptions->addProperty($structureOptions); + } + + // data options main group + $dataOptions = new OptionsPropertyMainGroup( + "data", __('Data dump options') + ); + $dataOptions->setForce('structure'); + // create primary items and add them to the group + $leaf = new BoolPropertyItem( + "columns", + __('Put columns names in the first row:') + ); + $dataOptions->addProperty($leaf); + $leaf = new TextPropertyItem( + "data_caption", + __('Table caption:') + ); + $leaf->setDoc('faq6-27'); + $dataOptions->addProperty($leaf); + $leaf = new TextPropertyItem( + "data_continued_caption", + __('Table caption (continued):') + ); + $leaf->setDoc('faq6-27'); + $dataOptions->addProperty($leaf); + $leaf = new TextPropertyItem( + "data_label", + __('Label key:') + ); + $leaf->setDoc('faq6-27'); + $dataOptions->addProperty($leaf); + $leaf = new TextPropertyItem( + 'null', + __('Replace NULL with:') + ); + $dataOptions->addProperty($leaf); + // add the main group to the root group + $exportSpecificOptions->addProperty($dataOptions); + + // set the options for the export plugin property item + $exportPluginProperties->setOptions($exportSpecificOptions); + $this->properties = $exportPluginProperties; + } + + /** + * Outputs export header + * + * @return bool Whether it succeeded + */ + public function exportHeader() + { + global $crlf; + global $cfg; + + $head = '% phpMyAdmin LaTeX Dump' . $crlf + . '% version ' . PMA_VERSION . $crlf + . '% https://www.phpmyadmin.net/' . $crlf + . '%' . $crlf + . '% ' . __('Host:') . ' ' . $cfg['Server']['host']; + if (!empty($cfg['Server']['port'])) { + $head .= ':' . $cfg['Server']['port']; + } + $head .= $crlf + . '% ' . __('Generation Time:') . ' ' + . Util::localisedDate() . $crlf + . '% ' . __('Server version:') . ' ' . $GLOBALS['dbi']->getVersionString() . $crlf + . '% ' . __('PHP Version:') . ' ' . phpversion() . $crlf; + + return Export::outputHandler($head); + } + + /** + * Outputs export footer + * + * @return bool Whether it succeeded + */ + public function exportFooter() + { + return true; + } + + /** + * Outputs database header + * + * @param string $db Database name + * @param string $db_alias Aliases of db + * + * @return bool Whether it succeeded + */ + public function exportDBHeader($db, $db_alias = '') + { + if (empty($db_alias)) { + $db_alias = $db; + } + global $crlf; + $head = '% ' . $crlf + . '% ' . __('Database:') . ' ' . '\'' . $db_alias . '\'' . $crlf + . '% ' . $crlf; + + return Export::outputHandler($head); + } + + /** + * Outputs database footer + * + * @param string $db Database name + * + * @return bool Whether it succeeded + */ + public function exportDBFooter($db) + { + return true; + } + + /** + * Outputs CREATE DATABASE statement + * + * @param string $db Database name + * @param string $export_type 'server', 'database', 'table' + * @param string $db_alias Aliases of db + * + * @return bool Whether it succeeded + */ + public function exportDBCreate($db, $export_type, $db_alias = '') + { + return true; + } + + /** + * Outputs the content of a table in JSON format + * + * @param string $db database name + * @param string $table table name + * @param string $crlf the end of line sequence + * @param string $error_url the url to go back in case of error + * @param string $sql_query SQL query for obtaining data + * @param array $aliases Aliases of db/table/columns + * + * @return bool Whether it succeeded + */ + public function exportData( + $db, + $table, + $crlf, + $error_url, + $sql_query, + array $aliases = array() + ) { + $db_alias = $db; + $table_alias = $table; + $this->initAlias($aliases, $db_alias, $table_alias); + + $result = $GLOBALS['dbi']->tryQuery( + $sql_query, + DatabaseInterface::CONNECT_USER, + DatabaseInterface::QUERY_UNBUFFERED + ); + + $columns_cnt = $GLOBALS['dbi']->numFields($result); + $columns = array(); + $columns_alias = array(); + for ($i = 0; $i < $columns_cnt; $i++) { + $columns[$i] = $col_as = $GLOBALS['dbi']->fieldName($result, $i); + if (!empty($aliases[$db]['tables'][$table]['columns'][$col_as])) { + $col_as = $aliases[$db]['tables'][$table]['columns'][$col_as]; + } + $columns_alias[$i] = $col_as; + } + + $buffer = $crlf . '%' . $crlf . '% ' . __('Data:') . ' ' . $table_alias + . $crlf . '%' . $crlf . ' \\begin{longtable}{|'; + + for ($index = 0; $index < $columns_cnt; $index++) { + $buffer .= 'l|'; + } + $buffer .= '} ' . $crlf; + + $buffer .= ' \\hline \\endhead \\hline \\endfoot \\hline ' . $crlf; + if (isset($GLOBALS['latex_caption'])) { + $buffer .= ' \\caption{' + . Util::expandUserString( + $GLOBALS['latex_data_caption'], + array( + 'texEscape', + get_class($this), + ), + array('table' => $table_alias, 'database' => $db_alias) + ) + . '} \\label{' + . Util::expandUserString( + $GLOBALS['latex_data_label'], + null, + array('table' => $table_alias, 'database' => $db_alias) + ) + . '} \\\\'; + } + if (!Export::outputHandler($buffer)) { + return false; + } + + // show column names + if (isset($GLOBALS['latex_columns'])) { + $buffer = '\\hline '; + for ($i = 0; $i < $columns_cnt; $i++) { + $buffer .= '\\multicolumn{1}{|c|}{\\textbf{' + . self::texEscape(stripslashes($columns_alias[$i])) . '}} & '; + } + + $buffer = mb_substr($buffer, 0, -2) . '\\\\ \\hline \hline '; + if (!Export::outputHandler($buffer . ' \\endfirsthead ' . $crlf)) { + return false; + } + if (isset($GLOBALS['latex_caption'])) { + if (!Export::outputHandler( + '\\caption{' + . Util::expandUserString( + $GLOBALS['latex_data_continued_caption'], + array( + 'texEscape', + get_class($this), + ), + array('table' => $table_alias, 'database' => $db_alias) + ) + . '} \\\\ ' + ) + ) { + return false; + } + } + if (!Export::outputHandler($buffer . '\\endhead \\endfoot' . $crlf)) { + return false; + } + } else { + if (!Export::outputHandler('\\\\ \hline')) { + return false; + } + } + + // print the whole table + while ($record = $GLOBALS['dbi']->fetchAssoc($result)) { + $buffer = ''; + // print each row + for ($i = 0; $i < $columns_cnt; $i++) { + if ((!function_exists('is_null') + || !is_null($record[$columns[$i]])) + && isset($record[$columns[$i]]) + ) { + $column_value = self::texEscape( + stripslashes($record[$columns[$i]]) + ); + } else { + $column_value = $GLOBALS['latex_null']; + } + + // last column ... no need for & character + if ($i == ($columns_cnt - 1)) { + $buffer .= $column_value; + } else { + $buffer .= $column_value . " & "; + } + } + $buffer .= ' \\\\ \\hline ' . $crlf; + if (!Export::outputHandler($buffer)) { + return false; + } + } + + $buffer = ' \\end{longtable}' . $crlf; + if (!Export::outputHandler($buffer)) { + return false; + } + + $GLOBALS['dbi']->freeResult($result); + + return true; + } // end getTableLaTeX + + /** + * Outputs table's structure + * + * @param string $db database name + * @param string $table table name + * @param string $crlf the end of line sequence + * @param string $error_url the url to go back in case of error + * @param string $export_mode 'create_table', 'triggers', 'create_view', + * 'stand_in' + * @param string $export_type 'server', 'database', 'table' + * @param bool $do_relation whether to include relation comments + * @param bool $do_comments whether to include the pmadb-style column + * comments as comments in the structure; + * this is deprecated but the parameter is + * left here because export.php calls + * exportStructure() also for other + * export types which use this parameter + * @param bool $do_mime whether to include mime comments + * @param bool $dates whether to include creation/update/check dates + * @param array $aliases Aliases of db/table/columns + * + * @return bool Whether it succeeded + */ + public function exportStructure( + $db, + $table, + $crlf, + $error_url, + $export_mode, + $export_type, + $do_relation = false, + $do_comments = false, + $do_mime = false, + $dates = false, + array $aliases = array() + ) { + $db_alias = $db; + $table_alias = $table; + $this->initAlias($aliases, $db_alias, $table_alias); + + global $cfgRelation; + + /* We do not export triggers */ + if ($export_mode == 'triggers') { + return true; + } + + /** + * Get the unique keys in the table + */ + $unique_keys = array(); + $keys = $GLOBALS['dbi']->getTableIndexes($db, $table); + foreach ($keys as $key) { + if ($key['Non_unique'] == 0) { + $unique_keys[] = $key['Column_name']; + } + } + + /** + * Gets fields properties + */ + $GLOBALS['dbi']->selectDb($db); + + // Check if we can use Relations + list($res_rel, $have_rel) = $this->relation->getRelationsAndStatus( + $do_relation && !empty($cfgRelation['relation']), + $db, + $table + ); + /** + * Displays the table structure + */ + $buffer = $crlf . '%' . $crlf . '% ' . __('Structure:') . ' ' + . $table_alias . $crlf . '%' . $crlf . ' \\begin{longtable}{'; + if (!Export::outputHandler($buffer)) { + return false; + } + + $alignment = '|l|c|c|c|'; + if ($do_relation && $have_rel) { + $alignment .= 'l|'; + } + if ($do_comments) { + $alignment .= 'l|'; + } + if ($do_mime && $cfgRelation['mimework']) { + $alignment .= 'l|'; + } + $buffer = $alignment . '} ' . $crlf; + + $header = ' \\hline '; + $header .= '\\multicolumn{1}{|c|}{\\textbf{' . __('Column') + . '}} & \\multicolumn{1}{|c|}{\\textbf{' . __('Type') + . '}} & \\multicolumn{1}{|c|}{\\textbf{' . __('Null') + . '}} & \\multicolumn{1}{|c|}{\\textbf{' . __('Default') . '}}'; + if ($do_relation && $have_rel) { + $header .= ' & \\multicolumn{1}{|c|}{\\textbf{' . __('Links to') . '}}'; + } + if ($do_comments) { + $header .= ' & \\multicolumn{1}{|c|}{\\textbf{' . __('Comments') . '}}'; + $comments = $this->relation->getComments($db, $table); + } + if ($do_mime && $cfgRelation['mimework']) { + $header .= ' & \\multicolumn{1}{|c|}{\\textbf{MIME}}'; + $mime_map = Transformations::getMIME($db, $table, true); + } + + // Table caption for first page and label + if (isset($GLOBALS['latex_caption'])) { + $buffer .= ' \\caption{' + . Util::expandUserString( + $GLOBALS['latex_structure_caption'], + array( + 'texEscape', + get_class($this), + ), + array('table' => $table_alias, 'database' => $db_alias) + ) + . '} \\label{' + . Util::expandUserString( + $GLOBALS['latex_structure_label'], + null, + array('table' => $table_alias, 'database' => $db_alias) + ) + . '} \\\\' . $crlf; + } + $buffer .= $header . ' \\\\ \\hline \\hline' . $crlf + . '\\endfirsthead' . $crlf; + // Table caption on next pages + if (isset($GLOBALS['latex_caption'])) { + $buffer .= ' \\caption{' + . Util::expandUserString( + $GLOBALS['latex_structure_continued_caption'], + array( + 'texEscape', + get_class($this), + ), + array('table' => $table_alias, 'database' => $db_alias) + ) + . '} \\\\ ' . $crlf; + } + $buffer .= $header . ' \\\\ \\hline \\hline \\endhead \\endfoot ' . $crlf; + + if (!Export::outputHandler($buffer)) { + return false; + } + + $fields = $GLOBALS['dbi']->getColumns($db, $table); + foreach ($fields as $row) { + $extracted_columnspec = Util::extractColumnSpec($row['Type']); + $type = $extracted_columnspec['print_type']; + if (empty($type)) { + $type = ' '; + } + + if (!isset($row['Default'])) { + if ($row['Null'] != 'NO') { + $row['Default'] = 'NULL'; + } + } + + $field_name = $col_as = $row['Field']; + if (!empty($aliases[$db]['tables'][$table]['columns'][$col_as])) { + $col_as = $aliases[$db]['tables'][$table]['columns'][$col_as]; + } + + $local_buffer = $col_as . "\000" . $type . "\000" + . (($row['Null'] == '' || $row['Null'] == 'NO') + ? __('No') : __('Yes')) + . "\000" . (isset($row['Default']) ? $row['Default'] : ''); + + if ($do_relation && $have_rel) { + $local_buffer .= "\000"; + $local_buffer .= $this->getRelationString( + $res_rel, + $field_name, + $db, + $aliases + ); + } + if ($do_comments && $cfgRelation['commwork']) { + $local_buffer .= "\000"; + if (isset($comments[$field_name])) { + $local_buffer .= $comments[$field_name]; + } + } + if ($do_mime && $cfgRelation['mimework']) { + $local_buffer .= "\000"; + if (isset($mime_map[$field_name])) { + $local_buffer .= str_replace( + '_', + '/', + $mime_map[$field_name]['mimetype'] + ); + } + } + $local_buffer = self::texEscape($local_buffer); + if ($row['Key'] == 'PRI') { + $pos = mb_strpos($local_buffer, "\000"); + $local_buffer = '\\textit{' + . + mb_substr($local_buffer, 0, $pos) + . '}' . + mb_substr($local_buffer, $pos); + } + if (in_array($field_name, $unique_keys)) { + $pos = mb_strpos($local_buffer, "\000"); + $local_buffer = '\\textbf{' + . + mb_substr($local_buffer, 0, $pos) + . '}' . + mb_substr($local_buffer, $pos); + } + $buffer = str_replace("\000", ' & ', $local_buffer); + $buffer .= ' \\\\ \\hline ' . $crlf; + + if (!Export::outputHandler($buffer)) { + return false; + } + } // end while + + $buffer = ' \\end{longtable}' . $crlf; + + return Export::outputHandler($buffer); + } // end of the 'exportStructure' method + + /** + * Escapes some special characters for use in TeX/LaTeX + * + * @param string $string the string to convert + * + * @return string the converted string with escape codes + */ + public static function texEscape($string) + { + $escape = array('$', '%', '{', '}', '&', '#', '_', '^'); + $cnt_escape = count($escape); + for ($k = 0; $k < $cnt_escape; $k++) { + $string = str_replace($escape[$k], '\\' . $escape[$k], $string); + } + + return $string; + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Export/ExportMediawiki.php b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Export/ExportMediawiki.php new file mode 100644 index 00000000..93210d51 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Export/ExportMediawiki.php @@ -0,0 +1,381 @@ +setProperties(); + } + + /** + * Sets the export MediaWiki properties + * + * @return void + */ + protected function setProperties() + { + $exportPluginProperties = new ExportPluginProperties(); + $exportPluginProperties->setText('MediaWiki Table'); + $exportPluginProperties->setExtension('mediawiki'); + $exportPluginProperties->setMimeType('text/plain'); + $exportPluginProperties->setOptionsText(__('Options')); + + // create the root group that will be the options field for + // $exportPluginProperties + // this will be shown as "Format specific options" + $exportSpecificOptions = new OptionsPropertyRootGroup( + "Format Specific Options" + ); + + // general options main group + $generalOptions = new OptionsPropertyMainGroup( + "general_opts", __('Dump table') + ); + + // what to dump (structure/data/both) + $subgroup = new OptionsPropertySubgroup( + "dump_table", __("Dump table") + ); + $leaf = new RadioPropertyItem('structure_or_data'); + $leaf->setValues( + array( + 'structure' => __('structure'), + 'data' => __('data'), + 'structure_and_data' => __('structure and data'), + ) + ); + $subgroup->setSubgroupHeader($leaf); + $generalOptions->addProperty($subgroup); + + // export table name + $leaf = new BoolPropertyItem( + "caption", + __('Export table names') + ); + $generalOptions->addProperty($leaf); + + // export table headers + $leaf = new BoolPropertyItem( + "headers", + __('Export table headers') + ); + $generalOptions->addProperty($leaf); + //add the main group to the root group + $exportSpecificOptions->addProperty($generalOptions); + + // set the options for the export plugin property item + $exportPluginProperties->setOptions($exportSpecificOptions); + $this->properties = $exportPluginProperties; + } + + /** + * Outputs export header + * + * @return bool Whether it succeeded + */ + public function exportHeader() + { + return true; + } + + /** + * Outputs export footer + * + * @return bool Whether it succeeded + */ + public function exportFooter() + { + return true; + } + + /** + * Outputs database header + * + * @param string $db Database name + * @param string $db_alias Alias of db + * + * @return bool Whether it succeeded + */ + public function exportDBHeader($db, $db_alias = '') + { + return true; + } + + /** + * Outputs database footer + * + * @param string $db Database name + * + * @return bool Whether it succeeded + */ + public function exportDBFooter($db) + { + return true; + } + + /** + * Outputs CREATE DATABASE statement + * + * @param string $db Database name + * @param string $export_type 'server', 'database', 'table' + * @param string $db_alias Aliases of db + * + * @return bool Whether it succeeded + */ + public function exportDBCreate($db, $export_type, $db_alias = '') + { + return true; + } + + /** + * Outputs table's structure + * + * @param string $db database name + * @param string $table table name + * @param string $crlf the end of line sequence + * @param string $error_url the url to go back in case of error + * @param string $export_mode 'create_table','triggers','create_view', + * 'stand_in' + * @param string $export_type 'server', 'database', 'table' + * @param bool $do_relation whether to include relation comments + * @param bool $do_comments whether to include the pmadb-style column + * comments as comments in the structure; this is + * deprecated but the parameter is left here + * because export.php calls exportStructure() + * also for other export types which use this + * parameter + * @param bool $do_mime whether to include mime comments + * @param bool $dates whether to include creation/update/check dates + * @param array $aliases Aliases of db/table/columns + * + * @return bool Whether it succeeded + */ + public function exportStructure( + $db, + $table, + $crlf, + $error_url, + $export_mode, + $export_type, + $do_relation = false, + $do_comments = false, + $do_mime = false, + $dates = false, + array $aliases = array() + ) { + $db_alias = $db; + $table_alias = $table; + $this->initAlias($aliases, $db_alias, $table_alias); + + $output = ''; + switch ($export_mode) { + case 'create_table': + $columns = $GLOBALS['dbi']->getColumns($db, $table); + $columns = array_values($columns); + $row_cnt = count($columns); + + // Print structure comment + $output = $this->_exportComment( + "Table structure for " + . Util::backquote($table_alias) + ); + + // Begin the table construction + $output .= "{| class=\"wikitable\" style=\"text-align:center;\"" + . $this->_exportCRLF(); + + // Add the table name + if (isset($GLOBALS['mediawiki_caption'])) { + $output .= "|+'''" . $table_alias . "'''" . $this->_exportCRLF(); + } + + // Add the table headers + if (isset($GLOBALS['mediawiki_headers'])) { + $output .= "|- style=\"background:#ffdead;\"" . $this->_exportCRLF(); + $output .= "! style=\"background:#ffffff\" | " + . $this->_exportCRLF(); + for ($i = 0; $i < $row_cnt; ++$i) { + $col_as = $columns[$i]['Field']; + if (!empty($aliases[$db]['tables'][$table]['columns'][$col_as]) + ) { + $col_as + = $aliases[$db]['tables'][$table]['columns'][$col_as]; + } + $output .= " | " . $col_as . $this->_exportCRLF(); + } + } + + // Add the table structure + $output .= "|-" . $this->_exportCRLF(); + $output .= "! Type" . $this->_exportCRLF(); + for ($i = 0; $i < $row_cnt; ++$i) { + $output .= " | " . $columns[$i]['Type'] . $this->_exportCRLF(); + } + + $output .= "|-" . $this->_exportCRLF(); + $output .= "! Null" . $this->_exportCRLF(); + for ($i = 0; $i < $row_cnt; ++$i) { + $output .= " | " . $columns[$i]['Null'] . $this->_exportCRLF(); + } + + $output .= "|-" . $this->_exportCRLF(); + $output .= "! Default" . $this->_exportCRLF(); + for ($i = 0; $i < $row_cnt; ++$i) { + $output .= " | " . $columns[$i]['Default'] . $this->_exportCRLF(); + } + + $output .= "|-" . $this->_exportCRLF(); + $output .= "! Extra" . $this->_exportCRLF(); + for ($i = 0; $i < $row_cnt; ++$i) { + $output .= " | " . $columns[$i]['Extra'] . $this->_exportCRLF(); + } + + $output .= "|}" . str_repeat($this->_exportCRLF(), 2); + break; + } // end switch + + return Export::outputHandler($output); + } + + /** + * Outputs the content of a table in MediaWiki format + * + * @param string $db database name + * @param string $table table name + * @param string $crlf the end of line sequence + * @param string $error_url the url to go back in case of error + * @param string $sql_query SQL query for obtaining data + * @param array $aliases Aliases of db/table/columns + * + * @return bool Whether it succeeded + */ + public function exportData( + $db, + $table, + $crlf, + $error_url, + $sql_query, + array $aliases = array() + ) { + $db_alias = $db; + $table_alias = $table; + $this->initAlias($aliases, $db_alias, $table_alias); + + // Print data comment + $output = $this->_exportComment( + "Table data for " . Util::backquote($table_alias) + ); + + // Begin the table construction + // Use the "wikitable" class for style + // Use the "sortable" class for allowing tables to be sorted by column + $output .= "{| class=\"wikitable sortable\" style=\"text-align:center;\"" + . $this->_exportCRLF(); + + // Add the table name + if (isset($GLOBALS['mediawiki_caption'])) { + $output .= "|+'''" . $table_alias . "'''" . $this->_exportCRLF(); + } + + // Add the table headers + if (isset($GLOBALS['mediawiki_headers'])) { + // Get column names + $column_names = $GLOBALS['dbi']->getColumnNames($db, $table); + + // Add column names as table headers + if (!is_null($column_names)) { + // Use '|-' for separating rows + $output .= "|-" . $this->_exportCRLF(); + + // Use '!' for separating table headers + foreach ($column_names as $column) { + if (!empty($aliases[$db]['tables'][$table]['columns'][$column]) + ) { + $column + = $aliases[$db]['tables'][$table]['columns'][$column]; + } + $output .= " ! " . $column . "" . $this->_exportCRLF(); + } + } + } + + // Get the table data from the database + $result = $GLOBALS['dbi']->query( + $sql_query, + DatabaseInterface::CONNECT_USER, + DatabaseInterface::QUERY_UNBUFFERED + ); + $fields_cnt = $GLOBALS['dbi']->numFields($result); + + while ($row = $GLOBALS['dbi']->fetchRow($result)) { + $output .= "|-" . $this->_exportCRLF(); + + // Use '|' for separating table columns + for ($i = 0; $i < $fields_cnt; ++$i) { + $output .= " | " . $row[$i] . "" . $this->_exportCRLF(); + } + } + + // End table construction + $output .= "|}" . str_repeat($this->_exportCRLF(), 2); + + return Export::outputHandler($output); + } + + /** + * Outputs comments containing info about the exported tables + * + * @param string $text Text of comment + * + * @return string The formatted comment + */ + private function _exportComment($text = '') + { + // see https://www.mediawiki.org/wiki/Help:Formatting + $comment = $this->_exportCRLF(); + $comment .= '' . str_repeat($this->_exportCRLF(), 2); + + return $comment; + } + + /** + * Outputs CRLF + * + * @return string CRLF + */ + private function _exportCRLF() + { + // The CRLF expected by the mediawiki format is "\n" + return "\n"; + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Export/ExportOds.php b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Export/ExportOds.php new file mode 100644 index 00000000..aa6c1648 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Export/ExportOds.php @@ -0,0 +1,342 @@ +setProperties(); + } + + /** + * Sets the export ODS properties + * + * @return void + */ + protected function setProperties() + { + $exportPluginProperties = new ExportPluginProperties(); + $exportPluginProperties->setText('OpenDocument Spreadsheet'); + $exportPluginProperties->setExtension('ods'); + $exportPluginProperties->setMimeType( + 'application/vnd.oasis.opendocument.spreadsheet' + ); + $exportPluginProperties->setForceFile(true); + $exportPluginProperties->setOptionsText(__('Options')); + + // create the root group that will be the options field for + // $exportPluginProperties + // this will be shown as "Format specific options" + $exportSpecificOptions = new OptionsPropertyRootGroup( + "Format Specific Options" + ); + + // general options main group + $generalOptions = new OptionsPropertyMainGroup("general_opts"); + // create primary items and add them to the group + $leaf = new TextPropertyItem( + "null", + __('Replace NULL with:') + ); + $generalOptions->addProperty($leaf); + $leaf = new BoolPropertyItem( + "columns", + __('Put columns names in the first row') + ); + $generalOptions->addProperty($leaf); + $leaf = new HiddenPropertyItem("structure_or_data"); + $generalOptions->addProperty($leaf); + // add the main group to the root group + $exportSpecificOptions->addProperty($generalOptions); + + // set the options for the export plugin property item + $exportPluginProperties->setOptions($exportSpecificOptions); + $this->properties = $exportPluginProperties; + } + + /** + * Outputs export header + * + * @return bool Whether it succeeded + */ + public function exportHeader() + { + $GLOBALS['ods_buffer'] .= '' + . '' + . '' + . '' + . '' + . '/' + . '' + . '/' + . '' + . '' + . '' + . '' + . ':' + . '' + . ':' + . '' + . ' ' + . '' + . '' + . '' + . '' + . '/' + . '' + . '/' + . '' + . ' ' + . '' + . ':' + . '' + . ' ' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . ''; + + return true; + } + + /** + * Outputs export footer + * + * @return bool Whether it succeeded + */ + public function exportFooter() + { + $GLOBALS['ods_buffer'] .= '' + . '' + . ''; + + return Export::outputHandler( + OpenDocument::create( + 'application/vnd.oasis.opendocument.spreadsheet', + $GLOBALS['ods_buffer'] + ) + ); + } + + /** + * Outputs database header + * + * @param string $db Database name + * @param string $db_alias Aliases of db + * + * @return bool Whether it succeeded + */ + public function exportDBHeader($db, $db_alias = '') + { + return true; + } + + /** + * Outputs database footer + * + * @param string $db Database name + * + * @return bool Whether it succeeded + */ + public function exportDBFooter($db) + { + return true; + } + + /** + * Outputs CREATE DATABASE statement + * + * @param string $db Database name + * @param string $export_type 'server', 'database', 'table' + * @param string $db_alias Aliases of db + * + * @return bool Whether it succeeded + */ + public function exportDBCreate($db, $export_type, $db_alias = '') + { + return true; + } + + /** + * Outputs the content of a table in NHibernate format + * + * @param string $db database name + * @param string $table table name + * @param string $crlf the end of line sequence + * @param string $error_url the url to go back in case of error + * @param string $sql_query SQL query for obtaining data + * @param array $aliases Aliases of db/table/columns + * + * @return bool Whether it succeeded + */ + public function exportData( + $db, + $table, + $crlf, + $error_url, + $sql_query, + array $aliases = array() + ) { + global $what; + + $db_alias = $db; + $table_alias = $table; + $this->initAlias($aliases, $db_alias, $table_alias); + // Gets the data from the database + $result = $GLOBALS['dbi']->query( + $sql_query, + DatabaseInterface::CONNECT_USER, + DatabaseInterface::QUERY_UNBUFFERED + ); + $fields_cnt = $GLOBALS['dbi']->numFields($result); + $fields_meta = $GLOBALS['dbi']->getFieldsMeta($result); + $field_flags = array(); + for ($j = 0; $j < $fields_cnt; $j++) { + $field_flags[$j] = $GLOBALS['dbi']->fieldFlags($result, $j); + } + + $GLOBALS['ods_buffer'] + .= ''; + + // If required, get fields name at the first line + if (isset($GLOBALS[$what . '_columns'])) { + $GLOBALS['ods_buffer'] .= ''; + for ($i = 0; $i < $fields_cnt; $i++) { + $col_as = $GLOBALS['dbi']->fieldName($result, $i); + if (!empty($aliases[$db]['tables'][$table]['columns'][$col_as])) { + $col_as = $aliases[$db]['tables'][$table]['columns'][$col_as]; + } + $GLOBALS['ods_buffer'] + .= '' + . '' + . htmlspecialchars( + stripslashes($col_as) + ) + . '' + . ''; + } // end for + $GLOBALS['ods_buffer'] .= ''; + } // end if + + // Format the data + while ($row = $GLOBALS['dbi']->fetchRow($result)) { + $GLOBALS['ods_buffer'] .= ''; + for ($j = 0; $j < $fields_cnt; $j++) { + if ($fields_meta[$j]->type === 'geometry') { + // export GIS types as hex + $row[$j] = '0x' . bin2hex($row[$j]); + } + if (!isset($row[$j]) || is_null($row[$j])) { + $GLOBALS['ods_buffer'] + .= '' + . '' + . htmlspecialchars($GLOBALS[$what . '_null']) + . '' + . ''; + } elseif (stristr($field_flags[$j], 'BINARY') + && $fields_meta[$j]->blob + ) { + // ignore BLOB + $GLOBALS['ods_buffer'] + .= '' + . '' + . ''; + } elseif ($fields_meta[$j]->type == "date") { + $GLOBALS['ods_buffer'] + .= '' + . '' + . htmlspecialchars($row[$j]) + . '' + . ''; + } elseif ($fields_meta[$j]->type == "time") { + $GLOBALS['ods_buffer'] + .= '' + . '' + . htmlspecialchars($row[$j]) + . '' + . ''; + } elseif ($fields_meta[$j]->type == "datetime") { + $GLOBALS['ods_buffer'] + .= '' + . '' + . htmlspecialchars($row[$j]) + . '' + . ''; + } elseif (($fields_meta[$j]->numeric + && $fields_meta[$j]->type != 'timestamp' + && !$fields_meta[$j]->blob) + || $fields_meta[$j]->type == 'real' + ) { + $GLOBALS['ods_buffer'] + .= '' + . '' + . htmlspecialchars($row[$j]) + . '' + . ''; + } else { + $GLOBALS['ods_buffer'] + .= '' + . '' + . htmlspecialchars($row[$j]) + . '' + . ''; + } + } // end for + $GLOBALS['ods_buffer'] .= ''; + } // end while + $GLOBALS['dbi']->freeResult($result); + + $GLOBALS['ods_buffer'] .= ''; + + return true; + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Export/ExportOdt.php b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Export/ExportOdt.php new file mode 100644 index 00000000..9ea59e39 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Export/ExportOdt.php @@ -0,0 +1,809 @@ +setProperties(); + } + + /** + * Sets the export ODT properties + * + * @return void + */ + protected function setProperties() + { + global $plugin_param; + $hide_structure = false; + if ($plugin_param['export_type'] == 'table' + && !$plugin_param['single_table'] + ) { + $hide_structure = true; + } + + $exportPluginProperties = new ExportPluginProperties(); + $exportPluginProperties->setText('OpenDocument Text'); + $exportPluginProperties->setExtension('odt'); + $exportPluginProperties->setMimeType( + 'application/vnd.oasis.opendocument.text' + ); + $exportPluginProperties->setForceFile(true); + $exportPluginProperties->setOptionsText(__('Options')); + + // create the root group that will be the options field for + // $exportPluginProperties + // this will be shown as "Format specific options" + $exportSpecificOptions = new OptionsPropertyRootGroup( + "Format Specific Options" + ); + + // what to dump (structure/data/both) main group + $dumpWhat = new OptionsPropertyMainGroup( + "general_opts", __('Dump table') + ); + // create primary items and add them to the group + $leaf = new RadioPropertyItem("structure_or_data"); + $leaf->setValues( + array( + 'structure' => __('structure'), + 'data' => __('data'), + 'structure_and_data' => __('structure and data'), + ) + ); + $dumpWhat->addProperty($leaf); + // add the main group to the root group + $exportSpecificOptions->addProperty($dumpWhat); + + // structure options main group + if (!$hide_structure) { + $structureOptions = new OptionsPropertyMainGroup( + "structure", __('Object creation options') + ); + $structureOptions->setForce('data'); + // create primary items and add them to the group + if (!empty($GLOBALS['cfgRelation']['relation'])) { + $leaf = new BoolPropertyItem( + "relation", + __('Display foreign key relationships') + ); + $structureOptions->addProperty($leaf); + } + $leaf = new BoolPropertyItem( + "comments", + __('Display comments') + ); + $structureOptions->addProperty($leaf); + if (!empty($GLOBALS['cfgRelation']['mimework'])) { + $leaf = new BoolPropertyItem( + "mime", + __('Display MIME types') + ); + $structureOptions->addProperty($leaf); + } + // add the main group to the root group + $exportSpecificOptions->addProperty($structureOptions); + } + + // data options main group + $dataOptions = new OptionsPropertyMainGroup( + "data", __('Data dump options') + ); + $dataOptions->setForce('structure'); + // create primary items and add them to the group + $leaf = new BoolPropertyItem( + "columns", + __('Put columns names in the first row') + ); + $dataOptions->addProperty($leaf); + $leaf = new TextPropertyItem( + 'null', + __('Replace NULL with:') + ); + $dataOptions->addProperty($leaf); + // add the main group to the root group + $exportSpecificOptions->addProperty($dataOptions); + + // set the options for the export plugin property item + $exportPluginProperties->setOptions($exportSpecificOptions); + $this->properties = $exportPluginProperties; + } + + /** + * Outputs export header + * + * @return bool Whether it succeeded + */ + public function exportHeader() + { + $GLOBALS['odt_buffer'] .= '' + . '' + . '' + . ''; + + return true; + } + + /** + * Outputs export footer + * + * @return bool Whether it succeeded + */ + public function exportFooter() + { + $GLOBALS['odt_buffer'] .= '' + . '' + . ''; + if (!Export::outputHandler( + OpenDocument::create( + 'application/vnd.oasis.opendocument.text', + $GLOBALS['odt_buffer'] + ) + ) + ) { + return false; + } + + return true; + } + + /** + * Outputs database header + * + * @param string $db Database name + * @param string $db_alias Aliases of db + * + * @return bool Whether it succeeded + */ + public function exportDBHeader($db, $db_alias = '') + { + if (empty($db_alias)) { + $db_alias = $db; + } + $GLOBALS['odt_buffer'] + .= '' + . __('Database') . ' ' . htmlspecialchars($db_alias) + . ''; + + return true; + } + + /** + * Outputs database footer + * + * @param string $db Database name + * + * @return bool Whether it succeeded + */ + public function exportDBFooter($db) + { + return true; + } + + /** + * Outputs CREATE DATABASE statement + * + * @param string $db Database name + * @param string $export_type 'server', 'database', 'table' + * @param string $db_alias Aliases of db + * + * @return bool Whether it succeeded + */ + public function exportDBCreate($db, $export_type, $db_alias = '') + { + return true; + } + + /** + * Outputs the content of a table in NHibernate format + * + * @param string $db database name + * @param string $table table name + * @param string $crlf the end of line sequence + * @param string $error_url the url to go back in case of error + * @param string $sql_query SQL query for obtaining data + * @param array $aliases Aliases of db/table/columns + * + * @return bool Whether it succeeded + */ + public function exportData( + $db, + $table, + $crlf, + $error_url, + $sql_query, + array $aliases = array() + ) { + global $what; + + $db_alias = $db; + $table_alias = $table; + $this->initAlias($aliases, $db_alias, $table_alias); + // Gets the data from the database + $result = $GLOBALS['dbi']->query( + $sql_query, + DatabaseInterface::CONNECT_USER, + DatabaseInterface::QUERY_UNBUFFERED + ); + $fields_cnt = $GLOBALS['dbi']->numFields($result); + $fields_meta = $GLOBALS['dbi']->getFieldsMeta($result); + $field_flags = array(); + for ($j = 0; $j < $fields_cnt; $j++) { + $field_flags[$j] = $GLOBALS['dbi']->fieldFlags($result, $j); + } + + $GLOBALS['odt_buffer'] + .= '' + . __('Dumping data for table') . ' ' . htmlspecialchars($table_alias) + . '' + . '' + . ''; + + // If required, get fields name at the first line + if (isset($GLOBALS[$what . '_columns'])) { + $GLOBALS['odt_buffer'] .= ''; + for ($i = 0; $i < $fields_cnt; $i++) { + $col_as = $GLOBALS['dbi']->fieldName($result, $i); + if (!empty($aliases[$db]['tables'][$table]['columns'][$col_as])) { + $col_as = $aliases[$db]['tables'][$table]['columns'][$col_as]; + } + $GLOBALS['odt_buffer'] + .= '' + . '' + . htmlspecialchars( + stripslashes($col_as) + ) + . '' + . ''; + } // end for + $GLOBALS['odt_buffer'] .= ''; + } // end if + + // Format the data + while ($row = $GLOBALS['dbi']->fetchRow($result)) { + $GLOBALS['odt_buffer'] .= ''; + for ($j = 0; $j < $fields_cnt; $j++) { + if ($fields_meta[$j]->type === 'geometry') { + // export GIS types as hex + $row[$j] = '0x' . bin2hex($row[$j]); + } + if (!isset($row[$j]) || is_null($row[$j])) { + $GLOBALS['odt_buffer'] + .= '' + . '' + . htmlspecialchars($GLOBALS[$what . '_null']) + . '' + . ''; + } elseif (stristr($field_flags[$j], 'BINARY') + && $fields_meta[$j]->blob + ) { + // ignore BLOB + $GLOBALS['odt_buffer'] + .= '' + . '' + . ''; + } elseif ($fields_meta[$j]->numeric + && $fields_meta[$j]->type != 'timestamp' + && !$fields_meta[$j]->blob + ) { + $GLOBALS['odt_buffer'] + .= '' + . '' + . htmlspecialchars($row[$j]) + . '' + . ''; + } else { + $GLOBALS['odt_buffer'] + .= '' + . '' + . htmlspecialchars($row[$j]) + . '' + . ''; + } + } // end for + $GLOBALS['odt_buffer'] .= ''; + } // end while + $GLOBALS['dbi']->freeResult($result); + + $GLOBALS['odt_buffer'] .= ''; + + return true; + } + + /** + * Returns a stand-in CREATE definition to resolve view dependencies + * + * @param string $db the database name + * @param string $view the view name + * @param string $crlf the end of line sequence + * @param array $aliases Aliases of db/table/columns + * + * @return string resulting definition + */ + public function getTableDefStandIn($db, $view, $crlf, $aliases = array()) + { + $db_alias = $db; + $view_alias = $view; + $this->initAlias($aliases, $db_alias, $view_alias); + /** + * Gets fields properties + */ + $GLOBALS['dbi']->selectDb($db); + + /** + * Displays the table structure + */ + $GLOBALS['odt_buffer'] + .= ''; + $columns_cnt = 4; + $GLOBALS['odt_buffer'] + .= ''; + /* Header */ + $GLOBALS['odt_buffer'] .= '' + . '' + . '' . __('Column') . '' + . '' + . '' + . '' . __('Type') . '' + . '' + . '' + . '' . __('Null') . '' + . '' + . '' + . '' . __('Default') . '' + . '' + . ''; + + $columns = $GLOBALS['dbi']->getColumns($db, $view); + foreach ($columns as $column) { + $col_as = $column['Field']; + if (!empty($aliases[$db]['tables'][$view]['columns'][$col_as])) { + $col_as = $aliases[$db]['tables'][$view]['columns'][$col_as]; + } + $GLOBALS['odt_buffer'] .= $this->formatOneColumnDefinition( + $column, + $col_as + ); + $GLOBALS['odt_buffer'] .= ''; + } // end foreach + + $GLOBALS['odt_buffer'] .= ''; + + return true; + } + + /** + * Returns $table's CREATE definition + * + * @param string $db the database name + * @param string $table the table name + * @param string $crlf the end of line sequence + * @param string $error_url the url to go back in case of error + * @param bool $do_relation whether to include relation comments + * @param bool $do_comments whether to include the pmadb-style column + * comments as comments in the structure; + * this is deprecated but the parameter is + * left here because export.php calls + * PMA_exportStructure() also for other + * @param bool $do_mime whether to include mime comments + * @param bool $show_dates whether to include creation/update/check dates + * @param bool $add_semicolon whether to add semicolon and end-of-line at + * the end + * @param bool $view whether we're handling a view + * @param array $aliases Aliases of db/table/columns + * + * @return bool true + */ + public function getTableDef( + $db, + $table, + $crlf, + $error_url, + $do_relation, + $do_comments, + $do_mime, + $show_dates = false, + $add_semicolon = true, + $view = false, + array $aliases = array() + ) { + global $cfgRelation; + + $db_alias = $db; + $table_alias = $table; + $this->initAlias($aliases, $db_alias, $table_alias); + /** + * Gets fields properties + */ + $GLOBALS['dbi']->selectDb($db); + + // Check if we can use Relations + list($res_rel, $have_rel) = $this->relation->getRelationsAndStatus( + $do_relation && !empty($cfgRelation['relation']), + $db, + $table + ); + /** + * Displays the table structure + */ + $GLOBALS['odt_buffer'] .= ''; + $columns_cnt = 4; + if ($do_relation && $have_rel) { + $columns_cnt++; + } + if ($do_comments) { + $columns_cnt++; + } + if ($do_mime && $cfgRelation['mimework']) { + $columns_cnt++; + } + $GLOBALS['odt_buffer'] .= ''; + /* Header */ + $GLOBALS['odt_buffer'] .= '' + . '' + . '' . __('Column') . '' + . '' + . '' + . '' . __('Type') . '' + . '' + . '' + . '' . __('Null') . '' + . '' + . '' + . '' . __('Default') . '' + . ''; + if ($do_relation && $have_rel) { + $GLOBALS['odt_buffer'] .= '' + . '' . __('Links to') . '' + . ''; + } + if ($do_comments) { + $GLOBALS['odt_buffer'] .= '' + . '' . __('Comments') . '' + . ''; + $comments = $this->relation->getComments($db, $table); + } + if ($do_mime && $cfgRelation['mimework']) { + $GLOBALS['odt_buffer'] .= '' + . '' . __('MIME type') . '' + . ''; + $mime_map = Transformations::getMIME($db, $table, true); + } + $GLOBALS['odt_buffer'] .= ''; + + $columns = $GLOBALS['dbi']->getColumns($db, $table); + foreach ($columns as $column) { + $col_as = $field_name = $column['Field']; + if (!empty($aliases[$db]['tables'][$table]['columns'][$col_as])) { + $col_as = $aliases[$db]['tables'][$table]['columns'][$col_as]; + } + $GLOBALS['odt_buffer'] .= $this->formatOneColumnDefinition( + $column, + $col_as + ); + if ($do_relation && $have_rel) { + $foreigner = $this->relation->searchColumnInForeigners($res_rel, $field_name); + if ($foreigner) { + $rtable = $foreigner['foreign_table']; + $rfield = $foreigner['foreign_field']; + if (!empty($aliases[$db]['tables'][$rtable]['columns'][$rfield]) + ) { + $rfield + = $aliases[$db]['tables'][$rtable]['columns'][$rfield]; + } + if (!empty($aliases[$db]['tables'][$rtable]['alias'])) { + $rtable = $aliases[$db]['tables'][$rtable]['alias']; + } + $relation = htmlspecialchars($rtable . ' (' . $rfield . ')'); + $GLOBALS['odt_buffer'] + .= '' + . '' + . htmlspecialchars($relation) + . '' + . ''; + } + } + if ($do_comments) { + if (isset($comments[$field_name])) { + $GLOBALS['odt_buffer'] + .= '' + . '' + . htmlspecialchars($comments[$field_name]) + . '' + . ''; + } else { + $GLOBALS['odt_buffer'] + .= '' + . '' + . ''; + } + } + if ($do_mime && $cfgRelation['mimework']) { + if (isset($mime_map[$field_name])) { + $GLOBALS['odt_buffer'] + .= '' + . '' + . htmlspecialchars( + str_replace('_', '/', $mime_map[$field_name]['mimetype']) + ) + . '' + . ''; + } else { + $GLOBALS['odt_buffer'] + .= '' + . '' + . ''; + } + } + $GLOBALS['odt_buffer'] .= ''; + } // end foreach + + $GLOBALS['odt_buffer'] .= ''; + + return true; + } // end of the '$this->getTableDef()' function + + /** + * Outputs triggers + * + * @param string $db database name + * @param string $table table name + * @param array $aliases Aliases of db/table/columns + * + * @return bool true + */ + protected function getTriggers($db, $table, array $aliases = array()) + { + $db_alias = $db; + $table_alias = $table; + $this->initAlias($aliases, $db_alias, $table_alias); + $GLOBALS['odt_buffer'] .= '' + . '' + . '' + . '' + . '' . __('Name') . '' + . '' + . '' + . '' . __('Time') . '' + . '' + . '' + . '' . __('Event') . '' + . '' + . '' + . '' . __('Definition') . '' + . '' + . ''; + + $triggers = $GLOBALS['dbi']->getTriggers($db, $table); + + foreach ($triggers as $trigger) { + $GLOBALS['odt_buffer'] .= ''; + $GLOBALS['odt_buffer'] .= '' + . '' + . htmlspecialchars($trigger['name']) + . '' + . ''; + $GLOBALS['odt_buffer'] .= '' + . '' + . htmlspecialchars($trigger['action_timing']) + . '' + . ''; + $GLOBALS['odt_buffer'] .= '' + . '' + . htmlspecialchars($trigger['event_manipulation']) + . '' + . ''; + $GLOBALS['odt_buffer'] .= '' + . '' + . htmlspecialchars($trigger['definition']) + . '' + . ''; + $GLOBALS['odt_buffer'] .= ''; + } + + $GLOBALS['odt_buffer'] .= ''; + + return true; + } + + /** + * Outputs table's structure + * + * @param string $db database name + * @param string $table table name + * @param string $crlf the end of line sequence + * @param string $error_url the url to go back in case of error + * @param string $export_mode 'create_table', 'triggers', 'create_view', + * 'stand_in' + * @param string $export_type 'server', 'database', 'table' + * @param bool $do_relation whether to include relation comments + * @param bool $do_comments whether to include the pmadb-style column + * comments as comments in the structure; + * this is deprecated but the parameter is + * left here because export.php calls + * PMA_exportStructure() also for other + * @param bool $do_mime whether to include mime comments + * @param bool $dates whether to include creation/update/check dates + * @param array $aliases Aliases of db/table/columns + * + * @return bool Whether it succeeded + */ + public function exportStructure( + $db, + $table, + $crlf, + $error_url, + $export_mode, + $export_type, + $do_relation = false, + $do_comments = false, + $do_mime = false, + $dates = false, + array $aliases = array() + ) { + $db_alias = $db; + $table_alias = $table; + $this->initAlias($aliases, $db_alias, $table_alias); + switch ($export_mode) { + case 'create_table': + $GLOBALS['odt_buffer'] + .= '' + . __('Table structure for table') . ' ' . + htmlspecialchars($table_alias) + . ''; + $this->getTableDef( + $db, + $table, + $crlf, + $error_url, + $do_relation, + $do_comments, + $do_mime, + $dates, + true, + false, + $aliases + ); + break; + case 'triggers': + $triggers = $GLOBALS['dbi']->getTriggers($db, $table, $aliases); + if ($triggers) { + $GLOBALS['odt_buffer'] + .= '' + . __('Triggers') . ' ' + . htmlspecialchars($table_alias) + . ''; + $this->getTriggers($db, $table); + } + break; + case 'create_view': + $GLOBALS['odt_buffer'] + .= '' + . __('Structure for view') . ' ' + . htmlspecialchars($table_alias) + . ''; + $this->getTableDef( + $db, + $table, + $crlf, + $error_url, + $do_relation, + $do_comments, + $do_mime, + $dates, + true, + true, + $aliases + ); + break; + case 'stand_in': + $GLOBALS['odt_buffer'] + .= '' + . __('Stand-in structure for view') . ' ' + . htmlspecialchars($table_alias) + . ''; + // export a stand-in definition to resolve view dependencies + $this->getTableDefStandIn($db, $table, $crlf, $aliases); + } // end switch + + return true; + } // end of the '$this->exportStructure' function + + /** + * Formats the definition for one column + * + * @param array $column info about this column + * @param string $col_as column alias + * + * @return string Formatted column definition + */ + protected function formatOneColumnDefinition($column, $col_as = '') + { + if (empty($col_as)) { + $col_as = $column['Field']; + } + $definition = ''; + $definition .= '' + . '' . htmlspecialchars($col_as) . '' + . ''; + + $extracted_columnspec + = Util::extractColumnSpec($column['Type']); + $type = htmlspecialchars($extracted_columnspec['print_type']); + if (empty($type)) { + $type = ' '; + } + + $definition .= '' + . '' . htmlspecialchars($type) . '' + . ''; + if (!isset($column['Default'])) { + if ($column['Null'] != 'NO') { + $column['Default'] = 'NULL'; + } else { + $column['Default'] = ''; + } + } + $definition .= '' + . '' + . (($column['Null'] == '' || $column['Null'] == 'NO') + ? __('No') + : __('Yes')) + . '' + . ''; + $definition .= '' + . '' . htmlspecialchars($column['Default']) . '' + . ''; + + return $definition; + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Export/ExportPdf.php b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Export/ExportPdf.php new file mode 100644 index 00000000..ff08fbc9 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Export/ExportPdf.php @@ -0,0 +1,385 @@ +initSpecificVariables(); + + $this->setProperties(); + } + + /** + * Initialize the local variables that are used for export PDF + * + * @return void + */ + protected function initSpecificVariables() + { + if (!empty($_POST['pdf_report_title'])) { + $this->_setPdfReportTitle($_POST['pdf_report_title']); + } + $this->_setPdf(new Pdf('L', 'pt', 'A3')); + } + + /** + * Sets the export PDF properties + * + * @return void + */ + protected function setProperties() + { + $exportPluginProperties = new ExportPluginProperties(); + $exportPluginProperties->setText('PDF'); + $exportPluginProperties->setExtension('pdf'); + $exportPluginProperties->setMimeType('application/pdf'); + $exportPluginProperties->setForceFile(true); + $exportPluginProperties->setOptionsText(__('Options')); + + // create the root group that will be the options field for + // $exportPluginProperties + // this will be shown as "Format specific options" + $exportSpecificOptions = new OptionsPropertyRootGroup( + "Format Specific Options" + ); + + // general options main group + $generalOptions = new OptionsPropertyMainGroup("general_opts"); + // create primary items and add them to the group + $leaf = new TextPropertyItem( + "report_title", + __('Report title:') + ); + $generalOptions->addProperty($leaf); + // add the group to the root group + $exportSpecificOptions->addProperty($generalOptions); + + // what to dump (structure/data/both) main group + $dumpWhat = new OptionsPropertyMainGroup( + "dump_what", __('Dump table') + ); + $leaf = new RadioPropertyItem("structure_or_data"); + $leaf->setValues( + array( + 'structure' => __('structure'), + 'data' => __('data'), + 'structure_and_data' => __('structure and data'), + ) + ); + $dumpWhat->addProperty($leaf); + // add the group to the root group + $exportSpecificOptions->addProperty($dumpWhat); + + // set the options for the export plugin property item + $exportPluginProperties->setOptions($exportSpecificOptions); + $this->properties = $exportPluginProperties; + } + + /** + * Outputs export header + * + * @return bool Whether it succeeded + */ + public function exportHeader() + { + $pdf_report_title = $this->_getPdfReportTitle(); + $pdf = $this->_getPdf(); + $pdf->Open(); + + $attr = array('titleFontSize' => 18, 'titleText' => $pdf_report_title); + $pdf->setAttributes($attr); + $pdf->setTopMargin(30); + + return true; + } + + /** + * Outputs export footer + * + * @return bool Whether it succeeded + */ + public function exportFooter() + { + $pdf = $this->_getPdf(); + + // instead of $pdf->Output(): + return Export::outputHandler($pdf->getPDFData()); + } + + /** + * Outputs database header + * + * @param string $db Database name + * @param string $db_alias Aliases of db + * + * @return bool Whether it succeeded + */ + public function exportDBHeader($db, $db_alias = '') + { + return true; + } + + /** + * Outputs database footer + * + * @param string $db Database name + * + * @return bool Whether it succeeded + */ + public function exportDBFooter($db) + { + return true; + } + + /** + * Outputs CREATE DATABASE statement + * + * @param string $db Database name + * @param string $export_type 'server', 'database', 'table' + * @param string $db_alias Aliases of db + * + * @return bool Whether it succeeded + */ + public function exportDBCreate($db, $export_type, $db_alias = '') + { + return true; + } + + /** + * Outputs the content of a table in NHibernate format + * + * @param string $db database name + * @param string $table table name + * @param string $crlf the end of line sequence + * @param string $error_url the url to go back in case of error + * @param string $sql_query SQL query for obtaining data + * @param array $aliases Aliases of db/table/columns + * + * @return bool Whether it succeeded + */ + public function exportData( + $db, + $table, + $crlf, + $error_url, + $sql_query, + array $aliases = array() + ) { + $db_alias = $db; + $table_alias = $table; + $this->initAlias($aliases, $db_alias, $table_alias); + $pdf = $this->_getPdf(); + $attr = array( + 'currentDb' => $db, + 'currentTable' => $table, + 'dbAlias' => $db_alias, + 'tableAlias' => $table_alias, + 'aliases' => $aliases, + ); + $pdf->setAttributes($attr); + $pdf->purpose = __('Dumping data'); + $pdf->mysqlReport($sql_query); + + return true; + } // end of the 'PMA_exportData()' function + + /** + * Outputs table structure + * + * @param string $db database name + * @param string $table table name + * @param string $crlf the end of line sequence + * @param string $error_url the url to go back in case of error + * @param string $export_mode 'create_table', 'triggers', 'create_view', + * 'stand_in' + * @param string $export_type 'server', 'database', 'table' + * @param bool $do_relation whether to include relation comments + * @param bool $do_comments whether to include the pmadb-style column + * comments as comments in the structure; + * this is deprecated but the parameter is + * left here because export.php calls + * PMA_exportStructure() also for other + * export types which use this parameter + * @param bool $do_mime whether to include mime comments + * @param bool $dates whether to include creation/update/check dates + * @param array $aliases aliases for db/table/columns + * + * @return bool Whether it succeeded + */ + public function exportStructure( + $db, + $table, + $crlf, + $error_url, + $export_mode, + $export_type, + $do_relation = false, + $do_comments = false, + $do_mime = false, + $dates = false, + array $aliases = array() + ) { + $db_alias = $db; + $table_alias = $table; + $this->initAlias($aliases, $db_alias, $table_alias); + $pdf = $this->_getPdf(); + // getting purpose to show at top + switch ($export_mode) { + case 'create_table': + $purpose = __('Table structure'); + break; + case 'triggers': + $purpose = __('Triggers'); + break; + case 'create_view': + $purpose = __('View structure'); + break; + case 'stand_in': + $purpose = __('Stand in'); + } // end switch + + $attr = array( + 'currentDb' => $db, + 'currentTable' => $table, + 'dbAlias' => $db_alias, + 'tableAlias' => $table_alias, + 'aliases' => $aliases, + 'purpose' => $purpose, + ); + $pdf->setAttributes($attr); + /** + * comment display set true as presently in pdf + * format, no option is present to take user input. + */ + $do_comments = true; + switch ($export_mode) { + case 'create_table': + $pdf->getTableDef( + $db, + $table, + $do_relation, + $do_comments, + $do_mime, + false, + $aliases + ); + break; + case 'triggers': + $pdf->getTriggers($db, $table); + break; + case 'create_view': + $pdf->getTableDef( + $db, + $table, + $do_relation, + $do_comments, + $do_mime, + false, + $aliases + ); + break; + case 'stand_in': + /* export a stand-in definition to resolve view dependencies + * Yet to develop this function + * $pdf->getTableDefStandIn($db, $table, $crlf); + */ + } // end switch + + return true; + } + + + /* ~~~~~~~~~~~~~~~~~~~~ Getters and Setters ~~~~~~~~~~~~~~~~~~~~ */ + + /** + * Gets the PhpMyAdmin\Plugins\Export\Helpers\Pdf instance + * + * @return Pdf + */ + private function _getPdf() + { + return $this->_pdf; + } + + /** + * Instantiates the PhpMyAdmin\Plugins\Export\Helpers\Pdf class + * + * @param Pdf $pdf The instance + * + * @return void + */ + private function _setPdf($pdf) + { + $this->_pdf = $pdf; + } + + /** + * Gets the PDF report title + * + * @return string + */ + private function _getPdfReportTitle() + { + return $this->_pdfReportTitle; + } + + /** + * Sets the PDF report title + * + * @param string $pdfReportTitle PDF report title + * + * @return void + */ + private function _setPdfReportTitle($pdfReportTitle) + { + $this->_pdfReportTitle = $pdfReportTitle; + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Export/ExportPhparray.php b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Export/ExportPhparray.php new file mode 100644 index 00000000..051ee3bf --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Export/ExportPhparray.php @@ -0,0 +1,257 @@ +setProperties(); + } + + /** + * Sets the export PHP Array properties + * + * @return void + */ + protected function setProperties() + { + $exportPluginProperties = new ExportPluginProperties(); + $exportPluginProperties->setText('PHP array'); + $exportPluginProperties->setExtension('php'); + $exportPluginProperties->setMimeType('text/plain'); + $exportPluginProperties->setOptionsText(__('Options')); + + // create the root group that will be the options field for + // $exportPluginProperties + // this will be shown as "Format specific options" + $exportSpecificOptions = new OptionsPropertyRootGroup( + "Format Specific Options" + ); + + // general options main group + $generalOptions = new OptionsPropertyMainGroup("general_opts"); + // create primary items and add them to the group + $leaf = new HiddenPropertyItem("structure_or_data"); + $generalOptions->addProperty($leaf); + // add the main group to the root group + $exportSpecificOptions->addProperty($generalOptions); + + // set the options for the export plugin property item + $exportPluginProperties->setOptions($exportSpecificOptions); + $this->properties = $exportPluginProperties; + } + + /** + * Removes end of comment from a string + * + * @param string $string String to replace + * + * @return string + */ + public function commentString($string) + { + return strtr($string, '*/', '-'); + } + + + /** + * Outputs export header + * + * @return bool Whether it succeeded + */ + public function exportHeader() + { + Export::outputHandler( + 'commentString(Util::backquote($db_alias)) + . $GLOBALS['crlf'] . ' */' . $GLOBALS['crlf'] + ); + + return true; + } + + /** + * Outputs database footer + * + * @param string $db Database name + * + * @return bool Whether it succeeded + */ + public function exportDBFooter($db) + { + return true; + } + + /** + * Outputs CREATE DATABASE statement + * + * @param string $db Database name + * @param string $export_type 'server', 'database', 'table' + * @param string $db_alias Aliases of db + * + * @return bool Whether it succeeded + */ + public function exportDBCreate($db, $export_type, $db_alias = '') + { + return true; + } + + /** + * Outputs the content of a table in PHP array format + * + * @param string $db database name + * @param string $table table name + * @param string $crlf the end of line sequence + * @param string $error_url the url to go back in case of error + * @param string $sql_query SQL query for obtaining data + * @param array $aliases Aliases of db/table/columns + * + * @return bool Whether it succeeded + */ + public function exportData( + $db, + $table, + $crlf, + $error_url, + $sql_query, + array $aliases = array() + ) { + $db_alias = $db; + $table_alias = $table; + $this->initAlias($aliases, $db_alias, $table_alias); + + $result = $GLOBALS['dbi']->query( + $sql_query, + DatabaseInterface::CONNECT_USER, + DatabaseInterface::QUERY_UNBUFFERED + ); + + $columns_cnt = $GLOBALS['dbi']->numFields($result); + $columns = array(); + for ($i = 0; $i < $columns_cnt; $i++) { + $col_as = $GLOBALS['dbi']->fieldName($result, $i); + if (!empty($aliases[$db]['tables'][$table]['columns'][$col_as])) { + $col_as = $aliases[$db]['tables'][$table]['columns'][$col_as]; + } + $columns[$i] = stripslashes($col_as); + } + + // fix variable names (based on + // https://secure.php.net/manual/language.variables.basics.php) + if (!preg_match( + '/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$/', + $table_alias + ) + ) { + // fix invalid characters in variable names by replacing them with + // underscores + $tablefixed = preg_replace( + '/[^a-zA-Z0-9_\x7f-\xff]/', + '_', + $table_alias + ); + + // variable name must not start with a number or dash... + if (preg_match('/^[a-zA-Z_\x7f-\xff]/', $tablefixed) === 0) { + $tablefixed = '_' . $tablefixed; + } + } else { + $tablefixed = $table; + } + + $buffer = ''; + $record_cnt = 0; + // Output table name as comment + $buffer .= $crlf . '/* ' + . $this->commentString(Util::backquote($db_alias)) . '.' + . $this->commentString(Util::backquote($table_alias)) . ' */' . $crlf; + $buffer .= '$' . $tablefixed . ' = array('; + + while ($record = $GLOBALS['dbi']->fetchRow($result)) { + $record_cnt++; + + if ($record_cnt == 1) { + $buffer .= $crlf . ' array('; + } else { + $buffer .= ',' . $crlf . ' array('; + } + + for ($i = 0; $i < $columns_cnt; $i++) { + $buffer .= var_export($columns[$i], true) + . " => " . var_export($record[$i], true) + . (($i + 1 >= $columns_cnt) ? '' : ','); + } + + $buffer .= ')'; + } + + $buffer .= $crlf . ');' . $crlf; + if (!Export::outputHandler($buffer)) { + return false; + } + + $GLOBALS['dbi']->freeResult($result); + + return true; + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Export/ExportSql.php b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Export/ExportSql.php new file mode 100644 index 00000000..d1eac7b8 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Export/ExportSql.php @@ -0,0 +1,2878 @@ +setProperties(); + + // Avoids undefined variables, use NULL so isset() returns false + if (!isset($GLOBALS['sql_backquotes'])) { + $GLOBALS['sql_backquotes'] = null; + } + } + + /** + * Sets the export SQL properties + * + * @return void + */ + protected function setProperties() + { + global $plugin_param; + + $hide_sql = false; + $hide_structure = false; + if ($plugin_param['export_type'] == 'table' + && !$plugin_param['single_table'] + ) { + $hide_structure = true; + $hide_sql = true; + } + + if (!$hide_sql) { + $exportPluginProperties = new ExportPluginProperties(); + $exportPluginProperties->setText('SQL'); + $exportPluginProperties->setExtension('sql'); + $exportPluginProperties->setMimeType('text/x-sql'); + $exportPluginProperties->setOptionsText(__('Options')); + + // create the root group that will be the options field for + // $exportPluginProperties + // this will be shown as "Format specific options" + $exportSpecificOptions = new OptionsPropertyRootGroup( + "Format Specific Options" + ); + + // general options main group + $generalOptions = new OptionsPropertyMainGroup("general_opts"); + + // comments + $subgroup = new OptionsPropertySubgroup("include_comments"); + $leaf = new BoolPropertyItem( + 'include_comments', + __( + 'Display comments (includes info such as export' + . ' timestamp, PHP version, and server version)' + ) + ); + $subgroup->setSubgroupHeader($leaf); + + $leaf = new TextPropertyItem( + 'header_comment', + __('Additional custom header comment (\n splits lines):') + ); + $subgroup->addProperty($leaf); + $leaf = new BoolPropertyItem( + 'dates', + __( + 'Include a timestamp of when databases were created, last' + . ' updated, and last checked' + ) + ); + $subgroup->addProperty($leaf); + if (!empty($GLOBALS['cfgRelation']['relation'])) { + $leaf = new BoolPropertyItem( + 'relation', + __('Display foreign key relationships') + ); + $subgroup->addProperty($leaf); + } + if (!empty($GLOBALS['cfgRelation']['mimework'])) { + $leaf = new BoolPropertyItem( + 'mime', + __('Display MIME types') + ); + $subgroup->addProperty($leaf); + } + $generalOptions->addProperty($subgroup); + + // enclose in a transaction + $leaf = new BoolPropertyItem( + "use_transaction", + __('Enclose export in a transaction') + ); + $leaf->setDoc( + array( + 'programs', + 'mysqldump', + 'option_mysqldump_single-transaction', + ) + ); + $generalOptions->addProperty($leaf); + + // disable foreign key checks + $leaf = new BoolPropertyItem( + "disable_fk", + __('Disable foreign key checks') + ); + $leaf->setDoc( + array( + 'manual_MySQL_Database_Administration', + 'server-system-variables', + 'sysvar_foreign_key_checks', + ) + ); + $generalOptions->addProperty($leaf); + + // export views as tables + $leaf = new BoolPropertyItem( + "views_as_tables", + __('Export views as tables') + ); + $generalOptions->addProperty($leaf); + + // export metadata + $leaf = new BoolPropertyItem( + "metadata", + __('Export metadata') + ); + $generalOptions->addProperty($leaf); + + // compatibility maximization + $compats = $GLOBALS['dbi']->getCompatibilities(); + if (count($compats) > 0) { + $values = array(); + foreach ($compats as $val) { + $values[$val] = $val; + } + + $leaf = new SelectPropertyItem( + "compatibility", + __( + 'Database system or older MySQL server to maximize output' + . ' compatibility with:' + ) + ); + $leaf->setValues($values); + $leaf->setDoc( + array( + 'manual_MySQL_Database_Administration', + 'Server_SQL_mode', + ) + ); + $generalOptions->addProperty($leaf); + + unset($values); + } + + // what to dump (structure/data/both) + $subgroup = new OptionsPropertySubgroup( + "dump_table", __("Dump table") + ); + $leaf = new RadioPropertyItem('structure_or_data'); + $leaf->setValues( + array( + 'structure' => __('structure'), + 'data' => __('data'), + 'structure_and_data' => __('structure and data'), + ) + ); + $subgroup->setSubgroupHeader($leaf); + $generalOptions->addProperty($subgroup); + + // add the main group to the root group + $exportSpecificOptions->addProperty($generalOptions); + + // structure options main group + if (!$hide_structure) { + $structureOptions = new OptionsPropertyMainGroup( + "structure", __('Object creation options') + ); + $structureOptions->setForce('data'); + + // begin SQL Statements + $subgroup = new OptionsPropertySubgroup(); + $leaf = new MessageOnlyPropertyItem( + 'add_statements', + __('Add statements:') + ); + $subgroup->setSubgroupHeader($leaf); + + // server export options + if ($plugin_param['export_type'] == 'server') { + $leaf = new BoolPropertyItem( + "drop_database", + sprintf(__('Add %s statement'), 'DROP DATABASE IF EXISTS') + ); + $subgroup->addProperty($leaf); + } + + if ($plugin_param['export_type'] == 'database') { + $create_clause = 'CREATE DATABASE / USE'; + $leaf = new BoolPropertyItem( + 'create_database', + sprintf(__('Add %s statement'), $create_clause) + ); + $subgroup->addProperty($leaf); + } + + if ($plugin_param['export_type'] == 'table') { + $drop_clause = $GLOBALS['dbi']->getTable( + $GLOBALS['db'], + $GLOBALS['table'] + )->isView() + ? 'DROP VIEW' + : 'DROP TABLE'; + } else { + $drop_clause = 'DROP TABLE / VIEW / PROCEDURE' + . ' / FUNCTION / EVENT'; + } + + $drop_clause .= ' / TRIGGER'; + + $leaf = new BoolPropertyItem( + 'drop_table', + sprintf(__('Add %s statement'), $drop_clause) + ); + $subgroup->addProperty($leaf); + + $subgroup_create_table = new OptionsPropertySubgroup(); + + // Add table structure option + $leaf = new BoolPropertyItem( + 'create_table', + sprintf(__('Add %s statement'), 'CREATE TABLE') + ); + $subgroup_create_table->setSubgroupHeader($leaf); + + $leaf = new BoolPropertyItem( + 'if_not_exists', + 'IF NOT EXISTS ' . __( + '(less efficient as indexes will be generated during table ' + . 'creation)' + ) + ); + $subgroup_create_table->addProperty($leaf); + + $leaf = new BoolPropertyItem( + 'auto_increment', + sprintf(__('%s value'), 'AUTO_INCREMENT') + ); + $subgroup_create_table->addProperty($leaf); + + $subgroup->addProperty($subgroup_create_table); + + // Add view option + $leaf = new BoolPropertyItem( + 'create_view', + sprintf(__('Add %s statement'), 'CREATE VIEW') + ); + $subgroup->addProperty($leaf); + + $leaf = new BoolPropertyItem( + 'procedure_function', + sprintf( + __('Add %s statement'), + 'CREATE PROCEDURE / FUNCTION / EVENT' + ) + ); + $subgroup->addProperty($leaf); + + // Add triggers option + $leaf = new BoolPropertyItem( + 'create_trigger', + sprintf(__('Add %s statement'), 'CREATE TRIGGER') + ); + $subgroup->addProperty($leaf); + + $structureOptions->addProperty($subgroup); + + $leaf = new BoolPropertyItem( + "backquotes", + __( + 'Enclose table and column names with backquotes ' + . '(Protects column and table names formed with' + . ' special characters or keywords)' + ) + ); + + $structureOptions->addProperty($leaf); + + // add the main group to the root group + $exportSpecificOptions->addProperty($structureOptions); + } + + // begin Data options + $dataOptions = new OptionsPropertyMainGroup( + "data", __('Data creation options') + ); + $dataOptions->setForce('structure'); + $leaf = new BoolPropertyItem( + "truncate", + __('Truncate table before insert') + ); + $dataOptions->addProperty($leaf); + + // begin SQL Statements + $subgroup = new OptionsPropertySubgroup(); + $leaf = new MessageOnlyPropertyItem( + __('Instead of INSERT statements, use:') + ); + $subgroup->setSubgroupHeader($leaf); + + $leaf = new BoolPropertyItem( + "delayed", + __('INSERT DELAYED statements') + ); + $leaf->setDoc( + array( + 'manual_MySQL_Database_Administration', + 'insert_delayed' + ) + ); + $subgroup->addProperty($leaf); + + $leaf = new BoolPropertyItem( + "ignore", + __('INSERT IGNORE statements') + ); + $leaf->setDoc( + array( + 'manual_MySQL_Database_Administration', + 'insert', + ) + ); + $subgroup->addProperty($leaf); + $dataOptions->addProperty($subgroup); + + // Function to use when dumping dat + $leaf = new SelectPropertyItem( + "type", + __('Function to use when dumping data:') + ); + $leaf->setValues( + array( + 'INSERT' => 'INSERT', + 'UPDATE' => 'UPDATE', + 'REPLACE' => 'REPLACE', + ) + ); + $dataOptions->addProperty($leaf); + + /* Syntax to use when inserting data */ + $subgroup = new OptionsPropertySubgroup(); + $leaf = new MessageOnlyPropertyItem( + null, + __('Syntax to use when inserting data:') + ); + $subgroup->setSubgroupHeader($leaf); + $leaf = new RadioPropertyItem( + "insert_syntax", + __('INSERT IGNORE statements') + ); + $leaf->setValues( + array( + 'complete' => __( + 'include column names in every INSERT statement' + . '
          Example: INSERT INTO' + . ' tbl_name (col_A,col_B,col_C) VALUES (1,2,3)' + ), + 'extended' => __( + 'insert multiple rows in every INSERT statement' + . '
          Example: INSERT INTO' + . ' tbl_name VALUES (1,2,3), (4,5,6), (7,8,9)' + ), + 'both' => __( + 'both of the above
          Example:' + . ' INSERT INTO tbl_name (col_A,col_B,col_C) VALUES' + . ' (1,2,3), (4,5,6), (7,8,9)' + ), + 'none' => __( + 'neither of the above
          Example:' + . ' INSERT INTO tbl_name VALUES (1,2,3)' + ), + ) + ); + $subgroup->addProperty($leaf); + $dataOptions->addProperty($subgroup); + + // Max length of query + $leaf = new NumberPropertyItem( + "max_query_size", + __('Maximal length of created query') + ); + $dataOptions->addProperty($leaf); + + // Dump binary columns in hexadecimal + $leaf = new BoolPropertyItem( + "hex_for_binary", + __( + 'Dump binary columns in hexadecimal notation' + . ' (for example, "abc" becomes 0x616263)' + ) + ); + $dataOptions->addProperty($leaf); + + // Dump time in UTC + $leaf = new BoolPropertyItem( + "utc_time", + __( + 'Dump TIMESTAMP columns in UTC (enables TIMESTAMP columns' + . ' to be dumped and reloaded between servers in different' + . ' time zones)' + ) + ); + $dataOptions->addProperty($leaf); + + // add the main group to the root group + $exportSpecificOptions->addProperty($dataOptions); + + // set the options for the export plugin property item + $exportPluginProperties->setOptions($exportSpecificOptions); + $this->properties = $exportPluginProperties; + } + } + + /** + * Generates SQL for routines export + * + * @param string $db Database + * @param array $aliases Aliases of db/table/columns + * @param string $type Type of exported routine + * @param string $name Verbose name of exported routine + * @param array $routines List of routines to export + * @param string $delimiter Delimiter to use in SQL + * + * @return string SQL query + */ + protected function _exportRoutineSQL( + $db, array $aliases, $type, $name, array $routines, $delimiter + ) { + global $crlf; + + $text = $this->_exportComment() + . $this->_exportComment($name) + . $this->_exportComment(); + + $used_alias = false; + $proc_query = ''; + + foreach ($routines as $routine) { + if (!empty($GLOBALS['sql_drop_table'])) { + $proc_query .= 'DROP ' . $type . ' IF EXISTS ' + . Util::backquote($routine) + . $delimiter . $crlf; + } + $create_query = $this->replaceWithAliases( + $GLOBALS['dbi']->getDefinition($db, $type, $routine), + $aliases, + $db, + '', + $flag + ); + // One warning per database + if ($flag) { + $used_alias = true; + } + $proc_query .= $create_query . $delimiter . $crlf . $crlf; + } + if ($used_alias) { + $text .= $this->_exportComment( + __('It appears your database uses routines;') + ) + . $this->_exportComment( + __('alias export may not work reliably in all cases.') + ) + . $this->_exportComment(); + } + $text .= $proc_query; + + return $text; + } + + /** + * Exports routines (procedures and functions) + * + * @param string $db Database + * @param array $aliases Aliases of db/table/columns + * + * @return bool Whether it succeeded + */ + public function exportRoutines($db, array $aliases = array()) + { + global $crlf; + + $db_alias = $db; + $this->initAlias($aliases, $db_alias); + + $text = ''; + $delimiter = '$$'; + + $procedure_names = $GLOBALS['dbi'] + ->getProceduresOrFunctions($db, 'PROCEDURE'); + $function_names = $GLOBALS['dbi']->getProceduresOrFunctions($db, 'FUNCTION'); + + if ($procedure_names || $function_names) { + $text .= $crlf + . 'DELIMITER ' . $delimiter . $crlf; + + if ($procedure_names) { + $text .= $this->_exportRoutineSQL( + $db, + $aliases, + 'PROCEDURE', + __('Procedures'), + $procedure_names, + $delimiter + ); + } + + if ($function_names) { + $text .= $this->_exportRoutineSQL( + $db, + $aliases, + 'FUNCTION', + __('Functions'), + $function_names, + $delimiter + ); + } + + $text .= 'DELIMITER ;' . $crlf; + } + + if (!empty($text)) { + return Export::outputHandler($text); + } + + return false; + } + + /** + * Possibly outputs comment + * + * @param string $text Text of comment + * + * @return string The formatted comment + */ + private function _exportComment($text = '') + { + if (isset($GLOBALS['sql_include_comments']) + && $GLOBALS['sql_include_comments'] + ) { + // see https://dev.mysql.com/doc/refman/5.0/en/ansi-diff-comments.html + if (empty($text)) { + return '--' . $GLOBALS['crlf']; + } + + $lines = preg_split("/\\r\\n|\\r|\\n/", $text); + $result = array(); + foreach ($lines as $line) { + $result[] = '-- ' . $line . $GLOBALS['crlf']; + } + return implode('', $result); + } + + return ''; + } + + /** + * Possibly outputs CRLF + * + * @return string $crlf or nothing + */ + private function _possibleCRLF() + { + if (isset($GLOBALS['sql_include_comments']) + && $GLOBALS['sql_include_comments'] + ) { + return $GLOBALS['crlf']; + } + + return ''; + } + + /** + * Outputs export footer + * + * @return bool Whether it succeeded + */ + public function exportFooter() + { + global $crlf; + + $foot = ''; + + if (isset($GLOBALS['sql_disable_fk'])) { + $foot .= 'SET FOREIGN_KEY_CHECKS=1;' . $crlf; + } + + if (isset($GLOBALS['sql_use_transaction'])) { + $foot .= 'COMMIT;' . $crlf; + } + + // restore connection settings + if ($this->_sent_charset) { + $foot .= $crlf + . '/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;' + . $crlf + . '/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;' + . $crlf + . '/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;' + . $crlf; + $this->_sent_charset = false; + } + + /* Restore timezone */ + if (isset($GLOBALS['sql_utc_time']) && $GLOBALS['sql_utc_time']) { + $GLOBALS['dbi']->query('SET time_zone = "' . $GLOBALS['old_tz'] . '"'); + } + + return Export::outputHandler($foot); + } + + /** + * Outputs export header. It is the first method to be called, so all + * the required variables are initialized here. + * + * @return bool Whether it succeeded + */ + public function exportHeader() + { + global $crlf, $cfg; + + if (isset($GLOBALS['sql_compatibility'])) { + $tmp_compat = $GLOBALS['sql_compatibility']; + if ($tmp_compat == 'NONE') { + $tmp_compat = ''; + } + $GLOBALS['dbi']->tryQuery('SET SQL_MODE="' . $tmp_compat . '"'); + unset($tmp_compat); + } + $head = $this->_exportComment('phpMyAdmin SQL Dump') + . $this->_exportComment('version ' . PMA_VERSION) + . $this->_exportComment('https://www.phpmyadmin.net/') + . $this->_exportComment(); + $host_string = __('Host:') . ' ' . $cfg['Server']['host']; + if (!empty($cfg['Server']['port'])) { + $host_string .= ':' . $cfg['Server']['port']; + } + $head .= $this->_exportComment($host_string); + $head .= $this->_exportComment( + __('Generation Time:') . ' ' + . Util::localisedDate() + ) + . $this->_exportComment( + __('Server version:') . ' ' . $GLOBALS['dbi']->getVersionString() + ) + . $this->_exportComment(__('PHP Version:') . ' ' . phpversion()) + . $this->_possibleCRLF(); + + if (isset($GLOBALS['sql_header_comment']) + && !empty($GLOBALS['sql_header_comment']) + ) { + // '\n' is not a newline (like "\n" would be), it's the characters + // backslash and n, as explained on the export interface + $lines = explode('\n', $GLOBALS['sql_header_comment']); + $head .= $this->_exportComment(); + foreach ($lines as $one_line) { + $head .= $this->_exportComment($one_line); + } + $head .= $this->_exportComment(); + } + + if (isset($GLOBALS['sql_disable_fk'])) { + $head .= 'SET FOREIGN_KEY_CHECKS=0;' . $crlf; + } + + // We want exported AUTO_INCREMENT columns to have still same value, + // do this only for recent MySQL exports + if ((! isset($GLOBALS['sql_compatibility']) + || $GLOBALS['sql_compatibility'] == 'NONE') + ) { + $head .= 'SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";' . $crlf; + } + + if (isset($GLOBALS['sql_use_transaction'])) { + $head .= 'SET AUTOCOMMIT = 0;' . $crlf + . 'START TRANSACTION;' . $crlf; + } + + /* Change timezone if we should export timestamps in UTC */ + if (isset($GLOBALS['sql_utc_time']) && $GLOBALS['sql_utc_time']) { + $head .= 'SET time_zone = "+00:00";' . $crlf; + $GLOBALS['old_tz'] = $GLOBALS['dbi'] + ->fetchValue('SELECT @@session.time_zone'); + $GLOBALS['dbi']->query('SET time_zone = "+00:00"'); + } + + $head .= $this->_possibleCRLF(); + + if (! empty($GLOBALS['asfile'])) { + // we are saving as file, therefore we provide charset information + // so that a utility like the mysql client can interpret + // the file correctly + if (isset($GLOBALS['charset']) + && isset(Charsets::$mysql_charset_map[$GLOBALS['charset']]) + ) { + // we got a charset from the export dialog + $set_names = Charsets::$mysql_charset_map[$GLOBALS['charset']]; + } else { + // by default we use the connection charset + $set_names = Charsets::$mysql_charset_map['utf-8']; + } + if ($set_names == 'utf8' && $GLOBALS['dbi']->getVersion() > 50503) { + $set_names = 'utf8mb4'; + } + $head .= $crlf + . '/*!40101 SET @OLD_CHARACTER_SET_CLIENT=' + . '@@CHARACTER_SET_CLIENT */;' . $crlf + . '/*!40101 SET @OLD_CHARACTER_SET_RESULTS=' + . '@@CHARACTER_SET_RESULTS */;' . $crlf + . '/*!40101 SET @OLD_COLLATION_CONNECTION=' + . '@@COLLATION_CONNECTION */;' . $crlf + . '/*!40101 SET NAMES ' . $set_names . ' */;' . $crlf . $crlf; + $this->_sent_charset = true; + } + + return Export::outputHandler($head); + } + + /** + * Outputs CREATE DATABASE statement + * + * @param string $db Database name + * @param string $export_type 'server', 'database', 'table' + * @param string $db_alias Aliases of db + * + * @return bool Whether it succeeded + */ + public function exportDBCreate($db, $export_type, $db_alias = '') + { + global $crlf; + + if (empty($db_alias)) { + $db_alias = $db; + } + if (isset($GLOBALS['sql_compatibility'])) { + $compat = $GLOBALS['sql_compatibility']; + } else { + $compat = 'NONE'; + } + if (isset($GLOBALS['sql_drop_database'])) { + if (!Export::outputHandler( + 'DROP DATABASE IF EXISTS ' + . Util::backquoteCompat( + $db_alias, + $compat, + isset($GLOBALS['sql_backquotes']) + ) + . ';' . $crlf + ) + ) { + return false; + } + } + if ($export_type == 'database' && !isset($GLOBALS['sql_create_database'])) { + return true; + } + + $create_query = 'CREATE DATABASE IF NOT EXISTS ' + . Util::backquoteCompat( + $db_alias, + $compat, + isset($GLOBALS['sql_backquotes']) + ); + $collation = $GLOBALS['dbi']->getDbCollation($db); + if (mb_strpos($collation, '_')) { + $create_query .= ' DEFAULT CHARACTER SET ' + . mb_substr( + $collation, + 0, + mb_strpos($collation, '_') + ) + . ' COLLATE ' . $collation; + } else { + $create_query .= ' DEFAULT CHARACTER SET ' . $collation; + } + $create_query .= ';' . $crlf; + if (!Export::outputHandler($create_query)) { + return false; + } + + return $this->_exportUseStatement($db_alias, $compat); + } + + /** + * Outputs USE statement + * + * @param string $db db to use + * @param string $compat sql compatibility + * + * @return bool Whether it succeeded + */ + private function _exportUseStatement($db, $compat) + { + global $crlf; + + if (isset($GLOBALS['sql_compatibility']) + && $GLOBALS['sql_compatibility'] == 'NONE' + ) { + $result = Export::outputHandler( + 'USE ' + . Util::backquoteCompat( + $db, + $compat, + isset($GLOBALS['sql_backquotes']) + ) + . ';' . $crlf + ); + } else { + $result = Export::outputHandler('USE ' . $db . ';' . $crlf); + } + + return $result; + } + + /** + * Outputs database header + * + * @param string $db Database name + * @param string $db_alias Alias of db + * + * @return bool Whether it succeeded + */ + public function exportDBHeader($db, $db_alias = '') + { + if (empty($db_alias)) { + $db_alias = $db; + } + if (isset($GLOBALS['sql_compatibility'])) { + $compat = $GLOBALS['sql_compatibility']; + } else { + $compat = 'NONE'; + } + $head = $this->_exportComment() + . $this->_exportComment( + __('Database:') . ' ' + . Util::backquoteCompat( + $db_alias, + $compat, + isset($GLOBALS['sql_backquotes']) + ) + ) + . $this->_exportComment(); + + return Export::outputHandler($head); + } + + /** + * Outputs database footer + * + * @param string $db Database name + * + * @return bool Whether it succeeded + */ + public function exportDBFooter($db) + { + global $crlf; + + $result = true; + + //add indexes to the sql dump file + if (isset($GLOBALS['sql_indexes'])) { + $result = Export::outputHandler($GLOBALS['sql_indexes']); + unset($GLOBALS['sql_indexes']); + } + //add auto increments to the sql dump file + if (isset($GLOBALS['sql_auto_increments'])) { + $result = Export::outputHandler($GLOBALS['sql_auto_increments']); + unset($GLOBALS['sql_auto_increments']); + } + //add constraints to the sql dump file + if (isset($GLOBALS['sql_constraints'])) { + $result = Export::outputHandler($GLOBALS['sql_constraints']); + unset($GLOBALS['sql_constraints']); + } + + return $result; + } + + /** + * Exports events + * + * @param string $db Database + * + * @return bool Whether it succeeded + */ + public function exportEvents($db) + { + global $crlf; + + $text = ''; + $delimiter = '$$'; + + $event_names = $GLOBALS['dbi']->fetchResult( + "SELECT EVENT_NAME FROM information_schema.EVENTS WHERE" + . " EVENT_SCHEMA= '" . $GLOBALS['dbi']->escapeString($db) + . "';" + ); + + if ($event_names) { + $text .= $crlf + . "DELIMITER " . $delimiter . $crlf; + + $text .= $this->_exportComment() + . $this->_exportComment(__('Events')) + . $this->_exportComment(); + + foreach ($event_names as $event_name) { + if (!empty($GLOBALS['sql_drop_table'])) { + $text .= "DROP EVENT " + . Util::backquote($event_name) + . $delimiter . $crlf; + } + $text .= $GLOBALS['dbi']->getDefinition($db, 'EVENT', $event_name) + . $delimiter . $crlf . $crlf; + } + + $text .= "DELIMITER ;" . $crlf; + } + + if (!empty($text)) { + return Export::outputHandler($text); + } + + return false; + } + + /** + * Exports metadata from Configuration Storage + * + * @param string $db database being exported + * @param string|array $tables table(s) being exported + * @param array $metadataTypes types of metadata to export + * + * @return bool Whether it succeeded + */ + public function exportMetadata( + $db, + $tables, + array $metadataTypes + ) { + $cfgRelation = $this->relation->getRelationsParam(); + if (!isset($cfgRelation['db'])) { + return true; + } + + $comment = $this->_possibleCRLF() + . $this->_possibleCRLF() + . $this->_exportComment() + . $this->_exportComment(__('Metadata')) + . $this->_exportComment(); + if (!Export::outputHandler($comment)) { + return false; + } + + if (!$this->_exportUseStatement( + $cfgRelation['db'], + $GLOBALS['sql_compatibility'] + ) + ) { + return false; + } + + $r = true; + if (is_array($tables)) { + // export metadata for each table + foreach ($tables as $table) { + $r &= $this->_exportMetadata($db, $table, $metadataTypes); + } + // export metadata for the database + $r &= $this->_exportMetadata($db, null, $metadataTypes); + } else { + // export metadata for single table + $r &= $this->_exportMetadata($db, $tables, $metadataTypes); + } + + return $r; + } + + /** + * Exports metadata from Configuration Storage + * + * @param string $db database being exported + * @param string $table table being exported + * @param array $metadataTypes types of metadata to export + * + * @return bool Whether it succeeded + */ + private function _exportMetadata( + $db, + $table, + array $metadataTypes + ) { + $cfgRelation = $this->relation->getRelationsParam(); + + if (isset($table)) { + $types = array( + 'column_info' => 'db_name', + 'table_uiprefs' => 'db_name', + 'tracking' => 'db_name', + ); + } else { + $types = array( + 'bookmark' => 'dbase', + 'relation' => 'master_db', + 'pdf_pages' => 'db_name', + 'savedsearches' => 'db_name', + 'central_columns' => 'db_name', + ); + } + + $aliases = array(); + + $comment = $this->_possibleCRLF() + . $this->_exportComment(); + + if (isset($table)) { + $comment .= $this->_exportComment( + sprintf( + __('Metadata for table %s'), + $table + ) + ); + } else { + $comment .= $this->_exportComment( + sprintf( + __('Metadata for database %s'), + $db + ) + ); + } + + $comment .= $this->_exportComment(); + + if (!Export::outputHandler($comment)) { + return false; + } + + foreach ($types as $type => $dbNameColumn) { + if (in_array($type, $metadataTypes) && isset($cfgRelation[$type])) { + + // special case, designer pages and their coordinates + if ($type == 'pdf_pages') { + + $sql_query = "SELECT `page_nr`, `page_descr` FROM " + . Util::backquote($cfgRelation['db']) + . "." . Util::backquote($cfgRelation[$type]) + . " WHERE " . Util::backquote($dbNameColumn) + . " = '" . $GLOBALS['dbi']->escapeString($db) . "'"; + + $result = $GLOBALS['dbi']->fetchResult( + $sql_query, + 'page_nr', + 'page_descr' + ); + + foreach ($result as $page => $name) { + // insert row for pdf_page + $sql_query_row = "SELECT `db_name`, `page_descr` FROM " + . Util::backquote($cfgRelation['db']) + . "." . Util::backquote( + $cfgRelation[$type] + ) + . " WHERE " . Util::backquote( + $dbNameColumn + ) + . " = '" . $GLOBALS['dbi']->escapeString($db) . "'" + . " AND `page_nr` = '" . intval($page) . "'"; + + if (!$this->exportData( + $cfgRelation['db'], + $cfgRelation[$type], + $GLOBALS['crlf'], + '', + $sql_query_row, + $aliases + ) + ) { + return false; + } + + $lastPage = $GLOBALS['crlf'] + . "SET @LAST_PAGE = LAST_INSERT_ID();" + . $GLOBALS['crlf']; + if (!Export::outputHandler($lastPage)) { + return false; + } + + $sql_query_coords = "SELECT `db_name`, `table_name`, " + . "'@LAST_PAGE' AS `pdf_page_number`, `x`, `y` FROM " + . Util::backquote($cfgRelation['db']) + . "." . Util::backquote( + $cfgRelation['table_coords'] + ) + . " WHERE `pdf_page_number` = '" . $page . "'"; + + $GLOBALS['exporting_metadata'] = true; + if (!$this->exportData( + $cfgRelation['db'], + $cfgRelation['table_coords'], + $GLOBALS['crlf'], + '', + $sql_query_coords, + $aliases + ) + ) { + $GLOBALS['exporting_metadata'] = false; + + return false; + } + $GLOBALS['exporting_metadata'] = false; + } + continue; + } + + // remove auto_incrementing id field for some tables + if ($type == 'bookmark') { + $sql_query = "SELECT `dbase`, `user`, `label`, `query` FROM "; + } elseif ($type == 'column_info') { + $sql_query = "SELECT `db_name`, `table_name`, `column_name`," + . " `comment`, `mimetype`, `transformation`," + . " `transformation_options`, `input_transformation`," + . " `input_transformation_options` FROM"; + } elseif ($type == 'savedsearches') { + $sql_query = "SELECT `username`, `db_name`, `search_name`," + . " `search_data` FROM"; + } else { + $sql_query = "SELECT * FROM "; + } + $sql_query .= Util::backquote($cfgRelation['db']) + . '.' . Util::backquote($cfgRelation[$type]) + . " WHERE " . Util::backquote($dbNameColumn) + . " = '" . $GLOBALS['dbi']->escapeString($db) . "'"; + if (isset($table)) { + $sql_query .= " AND `table_name` = '" + . $GLOBALS['dbi']->escapeString($table) . "'"; + } + + if (!$this->exportData( + $cfgRelation['db'], + $cfgRelation[$type], + $GLOBALS['crlf'], + '', + $sql_query, + $aliases + ) + ) { + return false; + } + } + } + + return true; + } + + /** + * Returns a stand-in CREATE definition to resolve view dependencies + * + * @param string $db the database name + * @param string $view the view name + * @param string $crlf the end of line sequence + * @param array $aliases Aliases of db/table/columns + * + * @return string resulting definition + */ + public function getTableDefStandIn($db, $view, $crlf, $aliases = array()) + { + $db_alias = $db; + $view_alias = $view; + $this->initAlias($aliases, $db_alias, $view_alias); + $create_query = ''; + if (!empty($GLOBALS['sql_drop_table'])) { + $create_query .= 'DROP VIEW IF EXISTS ' + . Util::backquote($view_alias) + . ';' . $crlf; + } + + $create_query .= 'CREATE TABLE '; + + if (isset($GLOBALS['sql_if_not_exists']) + && $GLOBALS['sql_if_not_exists'] + ) { + $create_query .= 'IF NOT EXISTS '; + } + $create_query .= Util::backquote($view_alias) . ' (' . $crlf; + $tmp = array(); + $columns = $GLOBALS['dbi']->getColumnsFull($db, $view); + foreach ($columns as $column_name => $definition) { + $col_alias = $column_name; + if (!empty($aliases[$db]['tables'][$view]['columns'][$col_alias])) { + $col_alias = $aliases[$db]['tables'][$view]['columns'][$col_alias]; + } + $tmp[] = Util::backquote($col_alias) . ' ' . + $definition['Type'] . $crlf; + } + $create_query .= implode(',', $tmp) . ');' . $crlf; + + return ($create_query); + } + + /** + * Returns CREATE definition that matches $view's structure + * + * @param string $db the database name + * @param string $view the view name + * @param string $crlf the end of line sequence + * @param bool $add_semicolon whether to add semicolon and end-of-line at + * the end + * @param array $aliases Aliases of db/table/columns + * + * @return string resulting schema + */ + private function _getTableDefForView( + $db, + $view, + $crlf, + $add_semicolon = true, + array $aliases = array() + ) { + $db_alias = $db; + $view_alias = $view; + $this->initAlias($aliases, $db_alias, $view_alias); + $create_query = "CREATE TABLE"; + if (isset($GLOBALS['sql_if_not_exists'])) { + $create_query .= " IF NOT EXISTS "; + } + $create_query .= Util::backquote($view_alias) . "(" . $crlf; + + $columns = $GLOBALS['dbi']->getColumns($db, $view, null, true); + + $firstCol = true; + foreach ($columns as $column) { + $col_alias = $column['Field']; + if (!empty($aliases[$db]['tables'][$view]['columns'][$col_alias])) { + $col_alias = $aliases[$db]['tables'][$view]['columns'][$col_alias]; + } + $extracted_columnspec = Util::extractColumnSpec( + $column['Type'] + ); + + if (!$firstCol) { + $create_query .= "," . $crlf; + } + $create_query .= " " . Util::backquote($col_alias); + $create_query .= " " . $column['Type']; + if ($extracted_columnspec['can_contain_collation'] + && !empty($column['Collation']) + ) { + $create_query .= " COLLATE " . $column['Collation']; + } + if ($column['Null'] == 'NO') { + $create_query .= " NOT NULL"; + } + if (isset($column['Default'])) { + $create_query .= " DEFAULT '" + . $GLOBALS['dbi']->escapeString($column['Default']) . "'"; + } else { + if ($column['Null'] == 'YES') { + $create_query .= " DEFAULT NULL"; + } + } + if (!empty($column['Comment'])) { + $create_query .= " COMMENT '" + . $GLOBALS['dbi']->escapeString($column['Comment']) . "'"; + } + $firstCol = false; + } + $create_query .= $crlf . ")" . ($add_semicolon ? ';' : '') . $crlf; + + if (isset($GLOBALS['sql_compatibility'])) { + $compat = $GLOBALS['sql_compatibility']; + } else { + $compat = 'NONE'; + } + if ($compat == 'MSSQL') { + $create_query = $this->_makeCreateTableMSSQLCompatible( + $create_query + ); + } + + return $create_query; + } + + /** + * Returns $table's CREATE definition + * + * @param string $db the database name + * @param string $table the table name + * @param string $crlf the end of line sequence + * @param string $error_url the url to go back in case + * of error + * @param bool $show_dates whether to include creation/ + * update/check dates + * @param bool $add_semicolon whether to add semicolon and + * end-of-line at the end + * @param bool $view whether we're handling a view + * @param bool $update_indexes_increments whether we need to update + * two global variables + * @param array $aliases Aliases of db/table/columns + * + * @return string resulting schema + */ + public function getTableDef( + $db, + $table, + $crlf, + $error_url, + $show_dates = false, + $add_semicolon = true, + $view = false, + $update_indexes_increments = true, + array $aliases = array() + ) { + global $sql_drop_table, $sql_backquotes, $sql_constraints, + $sql_constraints_query, $sql_indexes, $sql_indexes_query, + $sql_auto_increments, $sql_drop_foreign_keys; + + $db_alias = $db; + $table_alias = $table; + $this->initAlias($aliases, $db_alias, $table_alias); + + $schema_create = ''; + $auto_increment = ''; + $new_crlf = $crlf; + + if (isset($GLOBALS['sql_compatibility'])) { + $compat = $GLOBALS['sql_compatibility']; + } else { + $compat = 'NONE'; + } + + // need to use PhpMyAdmin\DatabaseInterface::QUERY_STORE + // with $GLOBALS['dbi']->numRows() in mysqli + $result = $GLOBALS['dbi']->tryQuery( + 'SHOW TABLE STATUS FROM ' . Util::backquote($db) + . ' WHERE Name = \'' . $GLOBALS['dbi']->escapeString($table) . '\'', + DatabaseInterface::CONNECT_USER, + DatabaseInterface::QUERY_STORE + ); + if ($result != false) { + if ($GLOBALS['dbi']->numRows($result) > 0) { + $tmpres = $GLOBALS['dbi']->fetchAssoc($result); + + // Here we optionally add the AUTO_INCREMENT next value, + // but starting with MySQL 5.0.24, the clause is already included + // in SHOW CREATE TABLE so we'll remove it below + if (isset($GLOBALS['sql_auto_increment']) + && !empty($tmpres['Auto_increment']) + ) { + $auto_increment .= ' AUTO_INCREMENT=' + . $tmpres['Auto_increment'] . ' '; + } + + if ($show_dates + && isset($tmpres['Create_time']) + && !empty($tmpres['Create_time']) + ) { + $schema_create .= $this->_exportComment( + __('Creation:') . ' ' + . Util::localisedDate( + strtotime($tmpres['Create_time']) + ) + ); + $new_crlf = $this->_exportComment() . $crlf; + } + + if ($show_dates + && isset($tmpres['Update_time']) + && !empty($tmpres['Update_time']) + ) { + $schema_create .= $this->_exportComment( + __('Last update:') . ' ' + . Util::localisedDate( + strtotime($tmpres['Update_time']) + ) + ); + $new_crlf = $this->_exportComment() . $crlf; + } + + if ($show_dates + && isset($tmpres['Check_time']) + && !empty($tmpres['Check_time']) + ) { + $schema_create .= $this->_exportComment( + __('Last check:') . ' ' + . Util::localisedDate( + strtotime($tmpres['Check_time']) + ) + ); + $new_crlf = $this->_exportComment() . $crlf; + } + } + $GLOBALS['dbi']->freeResult($result); + } + + $schema_create .= $new_crlf; + + // no need to generate a DROP VIEW here, it was done earlier + if (!empty($sql_drop_table) + && !$GLOBALS['dbi']->getTable($db, $table)->isView() + ) { + $schema_create .= 'DROP TABLE IF EXISTS ' + . Util::backquote($table_alias, $sql_backquotes) . ';' + . $crlf; + } + + // Complete table dump, + // Whether to quote table and column names or not + if ($sql_backquotes) { + $GLOBALS['dbi']->query('SET SQL_QUOTE_SHOW_CREATE = 1'); + } else { + $GLOBALS['dbi']->query('SET SQL_QUOTE_SHOW_CREATE = 0'); + } + + // I don't see the reason why this unbuffered query could cause problems, + // because SHOW CREATE TABLE returns only one row, and we free the + // results below. Nonetheless, we got 2 user reports about this + // (see bug 1562533) so I removed the unbuffered mode. + // $result = $GLOBALS['dbi']->query('SHOW CREATE TABLE ' . backquote($db) + // . '.' . backquote($table), null, DatabaseInterface::QUERY_UNBUFFERED); + // + // Note: SHOW CREATE TABLE, at least in MySQL 5.1.23, does not + // produce a displayable result for the default value of a BIT + // column, nor does the mysqldump command. See MySQL bug 35796 + $GLOBALS['dbi']->tryQuery('USE ' . Util::backquote($db)); + $result = $GLOBALS['dbi']->tryQuery( + 'SHOW CREATE TABLE ' . Util::backquote($db) . '.' + . Util::backquote($table) + ); + // an error can happen, for example the table is crashed + $tmp_error = $GLOBALS['dbi']->getError(); + if ($tmp_error) { + $message = sprintf(__('Error reading structure for table %s:'), "$db.$table"); + $message .= ' ' . $tmp_error; + if (! defined('TESTSUITE')) { + trigger_error($message, E_USER_ERROR); + } + return $this->_exportComment($message); + } + + // Old mode is stored so it can be restored once exporting is done. + $old_mode = Context::$MODE; + + $warning = ''; + if ($result != false && ($row = $GLOBALS['dbi']->fetchRow($result))) { + $create_query = $row[1]; + unset($row); + + // Convert end of line chars to one that we want (note that MySQL + // doesn't return query it will accept in all cases) + if (mb_strpos($create_query, "(\r\n ")) { + $create_query = str_replace("\r\n", $crlf, $create_query); + } elseif (mb_strpos($create_query, "(\n ")) { + $create_query = str_replace("\n", $crlf, $create_query); + } elseif (mb_strpos($create_query, "(\r ")) { + $create_query = str_replace("\r", $crlf, $create_query); + } + + /* + * Drop database name from VIEW creation. + * + * This is a bit tricky, but we need to issue SHOW CREATE TABLE with + * database name, but we don't want name to show up in CREATE VIEW + * statement. + */ + if ($view) { + $create_query = preg_replace( + '/' . preg_quote(Util::backquote($db), '/') . '\./', + '', + $create_query + ); + } + + // Substitute aliases in `CREATE` query. + $create_query = $this->replaceWithAliases( + $create_query, + $aliases, + $db, + $table, + $flag + ); + + // One warning per view. + if ($flag && $view) { + $warning = $this->_exportComment() + . $this->_exportComment( + __('It appears your database uses views;') + ) + . $this->_exportComment( + __('alias export may not work reliably in all cases.') + ) + . $this->_exportComment(); + } + + // Adding IF NOT EXISTS, if required. + if (isset($GLOBALS['sql_if_not_exists'])) { + $create_query = preg_replace( + '/^CREATE TABLE/', + 'CREATE TABLE IF NOT EXISTS', + $create_query + ); + } + + // Making the query MSSQL compatible. + if ($compat == 'MSSQL') { + $create_query = $this->_makeCreateTableMSSQLCompatible( + $create_query + ); + } + + // Views have no constraints, indexes, etc. They do not require any + // analysis. + if (!$view) { + + if (empty($sql_backquotes)) { + // Option "Enclose table and column names with backquotes" + // was checked. + Context::$MODE |= Context::SQL_MODE_NO_ENCLOSING_QUOTES; + } + + // Using appropriate quotes. + if (($compat === 'MSSQL') || ($sql_backquotes === '"')) { + Context::$MODE |= Context::SQL_MODE_ANSI_QUOTES; + } + } + + /** + * Parser used for analysis. + * + * @var Parser + */ + $parser = new Parser($create_query); + + /** + * `CREATE TABLE` statement. + * + * @var SelectStatement + */ + $statement = $parser->statements[0]; + + if (!empty($statement->entityOptions)) { + $engine = $statement->entityOptions->has('ENGINE'); + } else { + $engine = ''; + } + + /* Avoid operation on ARCHIVE tables as those can not be altered */ + if (!empty($statement->fields) && (empty($engine) || strtoupper($engine) != 'ARCHIVE')) { + + /** + * Fragments containining definition of each constraint. + * + * @var array + */ + $constraints = array(); + + /** + * Fragments containining definition of each index. + * + * @var array + */ + $indexes = array(); + + /** + * Fragments containining definition of each FULLTEXT index. + * + * @var array + */ + $indexes_fulltext = array(); + + /** + * Fragments containining definition of each foreign key that will + * be dropped. + * + * @var array + */ + $dropped = array(); + + /** + * Fragment containining definition of the `AUTO_INCREMENT`. + * + * @var array + */ + $auto_increment = array(); + + // Scanning each field of the `CREATE` statement to fill the arrays + // above. + // If the field is used in any of the arrays above, it is removed + // from the original definition. + // Also, AUTO_INCREMENT attribute is removed. + /** @var CreateDefinition $field */ + foreach ($statement->fields as $key => $field) { + + if ($field->isConstraint) { + // Creating the parts that add constraints. + $constraints[] = $field::build($field); + unset($statement->fields[$key]); + } elseif (!empty($field->key)) { + // Creating the parts that add indexes (must not be + // constraints). + if ($field->key->type === 'FULLTEXT KEY') { + $indexes_fulltext[] = $field->build($field); + unset($statement->fields[$key]); + } else { + if (empty($GLOBALS['sql_if_not_exists'])) { + $indexes[] = str_replace( + 'COMMENT=\'', 'COMMENT \'', $field::build($field) + ); + unset($statement->fields[$key]); + } + } + } + + // Creating the parts that drop foreign keys. + if (!empty($field->key)) { + if ($field->key->type === 'FOREIGN KEY') { + $dropped[] = 'FOREIGN KEY ' . Context::escape( + $field->name + ); + unset($statement->fields[$key]); + } + } + + // Dropping AUTO_INCREMENT. + if (!empty($field->options)) { + if ($field->options->has('AUTO_INCREMENT') + && empty($GLOBALS['sql_if_not_exists']) + ) { + + $auto_increment[] = $field::build($field); + $field->options->remove('AUTO_INCREMENT'); + } + } + } + + /** + * The header of the `ALTER` statement (`ALTER TABLE tbl`). + * + * @var string + */ + $alter_header = 'ALTER TABLE ' . + Util::backquoteCompat( + $table_alias, + $compat, + $sql_backquotes + ); + + /** + * The footer of the `ALTER` statement (usually ';') + * + * @var string + */ + $alter_footer = ';' . $crlf; + + // Generating constraints-related query. + if (!empty($constraints)) { + $sql_constraints_query = $alter_header . $crlf . ' ADD ' + . implode(',' . $crlf . ' ADD ', $constraints) + . $alter_footer; + + $sql_constraints = $this->generateComment( + $crlf, + $sql_constraints, + __('Constraints for dumped tables'), + __('Constraints for table'), + $table_alias, + $compat + ) . $sql_constraints_query; + } + + // Generating indexes-related query. + $sql_indexes_query = ''; + + if (!empty($indexes)) { + $sql_indexes_query .= $alter_header . $crlf . ' ADD ' + . implode(',' . $crlf . ' ADD ', $indexes) + . $alter_footer; + } + + if (!empty($indexes_fulltext)) { + // InnoDB supports one FULLTEXT index creation at a time. + // So FULLTEXT indexes are created one-by-one after other + // indexes where created. + $sql_indexes_query .= $alter_header . + ' ADD ' . implode( + $alter_footer . $alter_header . ' ADD ', + $indexes_fulltext + ) . $alter_footer; + } + + if ((!empty($indexes)) || (!empty($indexes_fulltext))) { + $sql_indexes = $this->generateComment( + $crlf, + $sql_indexes, + __('Indexes for dumped tables'), + __('Indexes for table'), + $table_alias, + $compat + ) . $sql_indexes_query; + } + + // Generating drop foreign keys-related query. + if (!empty($dropped)) { + $sql_drop_foreign_keys = $alter_header . $crlf . ' DROP ' + . implode(',' . $crlf . ' DROP ', $dropped) + . $alter_footer; + } + + // Generating auto-increment-related query. + if ((! empty($auto_increment)) && ($update_indexes_increments)) { + $sql_auto_increments_query = $alter_header . $crlf . ' MODIFY ' + . implode(',' . $crlf . ' MODIFY ', $auto_increment); + if (isset($GLOBALS['sql_auto_increment']) + && ($statement->entityOptions->has('AUTO_INCREMENT') !== false) + ) { + if (!isset($GLOBALS['table_data']) + || (isset($GLOBALS['table_data']) + && in_array($table, $GLOBALS['table_data'])) + ) { + $sql_auto_increments_query .= ', AUTO_INCREMENT=' + . $statement->entityOptions->has('AUTO_INCREMENT'); + } + } + $sql_auto_increments_query .= ';' . $crlf; + + $sql_auto_increments = $this->generateComment( + $crlf, + $sql_auto_increments, + __('AUTO_INCREMENT for dumped tables'), + __('AUTO_INCREMENT for table'), + $table_alias, + $compat + ) . $sql_auto_increments_query; + } + + // Removing the `AUTO_INCREMENT` attribute from the `CREATE TABLE` + // too. + if (!empty($statement->entityOptions) + && (empty($GLOBALS['sql_if_not_exists']) + || empty($GLOBALS['sql_auto_increment'])) + ) { + $statement->entityOptions->remove('AUTO_INCREMENT'); + } + + // Rebuilding the query. + $create_query = $statement->build(); + } + + $schema_create .= $create_query; + } + + $GLOBALS['dbi']->freeResult($result); + + // Restoring old mode. + Context::$MODE = $old_mode; + + return $warning . $schema_create . ($add_semicolon ? ';' . $crlf : ''); + } // end of the 'getTableDef()' function + + /** + * Returns $table's comments, relations etc. + * + * @param string $db database name + * @param string $table table name + * @param string $crlf end of line sequence + * @param bool $do_relation whether to include relation comments + * @param bool $do_mime whether to include mime comments + * @param array $aliases Aliases of db/table/columns + * + * @return string resulting comments + */ + private function _getTableComments( + $db, + $table, + $crlf, + $do_relation = false, + $do_mime = false, + array $aliases = array() + ) { + global $cfgRelation, $sql_backquotes; + + $db_alias = $db; + $table_alias = $table; + $this->initAlias($aliases, $db_alias, $table_alias); + + $schema_create = ''; + + // Check if we can use Relations + list($res_rel, $have_rel) = $this->relation->getRelationsAndStatus( + $do_relation && !empty($cfgRelation['relation']), + $db, + $table + ); + + if ($do_mime && $cfgRelation['mimework']) { + if (!($mime_map = Transformations::getMIME($db, $table, true))) { + unset($mime_map); + } + } + + if (isset($mime_map) && count($mime_map) > 0) { + $schema_create .= $this->_possibleCRLF() + . $this->_exportComment() + . $this->_exportComment( + __('MIME TYPES FOR TABLE') . ' ' + . Util::backquote($table, $sql_backquotes) . ':' + ); + foreach ($mime_map as $mime_field => $mime) { + $schema_create .= $this->_exportComment( + ' ' + . Util::backquote($mime_field, $sql_backquotes) + ) + . $this->_exportComment( + ' ' + . Util::backquote( + $mime['mimetype'], + $sql_backquotes + ) + ); + } + $schema_create .= $this->_exportComment(); + } + + if ($have_rel) { + $schema_create .= $this->_possibleCRLF() + . $this->_exportComment() + . $this->_exportComment( + __('RELATIONSHIPS FOR TABLE') . ' ' + . Util::backquote($table_alias, $sql_backquotes) + . ':' + ); + + foreach ($res_rel as $rel_field => $rel) { + if ($rel_field != 'foreign_keys_data') { + $rel_field_alias = !empty( + $aliases[$db]['tables'][$table]['columns'][$rel_field] + ) ? $aliases[$db]['tables'][$table]['columns'][$rel_field] + : $rel_field; + $schema_create .= $this->_exportComment( + ' ' + . Util::backquote( + $rel_field_alias, + $sql_backquotes + ) + ) + . $this->_exportComment( + ' ' + . Util::backquote( + $rel['foreign_table'], + $sql_backquotes + ) + . ' -> ' + . Util::backquote( + $rel['foreign_field'], + $sql_backquotes + ) + ); + } else { + foreach ($rel as $one_key) { + foreach ($one_key['index_list'] as $index => $field) { + $rel_field_alias = !empty( + $aliases[$db]['tables'][$table]['columns'][$field] + ) ? $aliases[$db]['tables'][$table]['columns'][$field] + : $field; + $schema_create .= $this->_exportComment( + ' ' + . Util::backquote( + $rel_field_alias, + $sql_backquotes + ) + ) + . $this->_exportComment( + ' ' + . Util::backquote( + $one_key['ref_table_name'], + $sql_backquotes + ) + . ' -> ' + . Util::backquote( + $one_key['ref_index_list'][$index], + $sql_backquotes + ) + ); + } + } + } + } + $schema_create .= $this->_exportComment(); + } + + return $schema_create; + } // end of the '_getTableComments()' function + + /** + * Outputs table's structure + * + * @param string $db database name + * @param string $table table name + * @param string $crlf the end of line sequence + * @param string $error_url the url to go back in case of error + * @param string $export_mode 'create_table','triggers','create_view', + * 'stand_in' + * @param string $export_type 'server', 'database', 'table' + * @param bool $relation whether to include relation comments + * @param bool $comments whether to include the pmadb-style column + * comments as comments in the structure; this is + * deprecated but the parameter is left here + * because export.php calls exportStructure() + * also for other export types which use this + * parameter + * @param bool $mime whether to include mime comments + * @param bool $dates whether to include creation/update/check dates + * @param array $aliases Aliases of db/table/columns + * + * @return bool Whether it succeeded + */ + public function exportStructure( + $db, + $table, + $crlf, + $error_url, + $export_mode, + $export_type, + $relation = false, + $comments = false, + $mime = false, + $dates = false, + array $aliases = array() + ) { + $db_alias = $db; + $table_alias = $table; + $this->initAlias($aliases, $db_alias, $table_alias); + if (isset($GLOBALS['sql_compatibility'])) { + $compat = $GLOBALS['sql_compatibility']; + } else { + $compat = 'NONE'; + } + + $formatted_table_name = Util::backquoteCompat( + $table_alias, + $compat, + isset($GLOBALS['sql_backquotes']) + ); + $dump = $this->_possibleCRLF() + . $this->_exportComment(str_repeat('-', 56)) + . $this->_possibleCRLF() + . $this->_exportComment(); + + switch ($export_mode) { + case 'create_table': + $dump .= $this->_exportComment( + __('Table structure for table') . ' ' . $formatted_table_name + ); + $dump .= $this->_exportComment(); + $dump .= $this->getTableDef( + $db, + $table, + $crlf, + $error_url, + $dates, + true, + false, + true, + $aliases + ); + $dump .= $this->_getTableComments( + $db, + $table, + $crlf, + $relation, + $mime, + $aliases + ); + break; + case 'triggers': + $dump = ''; + $delimiter = '$$'; + $triggers = $GLOBALS['dbi']->getTriggers($db, $table, $delimiter); + if ($triggers) { + $dump .= $this->_possibleCRLF() + . $this->_exportComment() + . $this->_exportComment( + __('Triggers') . ' ' . $formatted_table_name + ) + . $this->_exportComment(); + $used_alias = false; + $trigger_query = ''; + foreach ($triggers as $trigger) { + if (!empty($GLOBALS['sql_drop_table'])) { + $trigger_query .= $trigger['drop'] . ';' . $crlf; + } + + $trigger_query .= 'DELIMITER ' . $delimiter . $crlf; + $trigger_query .= $this->replaceWithAliases( + $trigger['create'], + $aliases, + $db, + $table, + $flag + ); + if ($flag) { + $used_alias = true; + } + $trigger_query .= 'DELIMITER ;' . $crlf; + } + // One warning per table. + if ($used_alias) { + $dump .= $this->_exportComment( + __('It appears your table uses triggers;') + ) + . $this->_exportComment( + __('alias export may not work reliably in all cases.') + ) + . $this->_exportComment(); + } + $dump .= $trigger_query; + } + break; + case 'create_view': + if (empty($GLOBALS['sql_views_as_tables'])) { + $dump .= $this->_exportComment( + __('Structure for view') + . ' ' + . $formatted_table_name + ) + . $this->_exportComment(); + // delete the stand-in table previously created (if any) + if ($export_type != 'table') { + $dump .= 'DROP TABLE IF EXISTS ' + . Util::backquote($table_alias) . ';' . $crlf; + } + $dump .= $this->getTableDef( + $db, + $table, + $crlf, + $error_url, + $dates, + true, + true, + true, + $aliases + ); + } else { + $dump .= $this->_exportComment( + sprintf( + __('Structure for view %s exported as a table'), + $formatted_table_name + ) + ) + . $this->_exportComment(); + // delete the stand-in table previously created (if any) + if ($export_type != 'table') { + $dump .= 'DROP TABLE IF EXISTS ' + . Util::backquote($table_alias) . ';' . $crlf; + } + $dump .= $this->_getTableDefForView( + $db, + $table, + $crlf, + true, + $aliases + ); + } + break; + case 'stand_in': + $dump .= $this->_exportComment( + __('Stand-in structure for view') . ' ' . $formatted_table_name + ) + . $this->_exportComment( + __('(See below for the actual view)') + ) + . $this->_exportComment(); + // export a stand-in definition to resolve view dependencies + $dump .= $this->getTableDefStandIn($db, $table, $crlf, $aliases); + } // end switch + + // this one is built by getTableDef() to use in table copy/move + // but not in the case of export + unset($GLOBALS['sql_constraints_query']); + + return Export::outputHandler($dump); + } + + /** + * Outputs the content of a table in SQL format + * + * @param string $db database name + * @param string $table table name + * @param string $crlf the end of line sequence + * @param string $error_url the url to go back in case of error + * @param string $sql_query SQL query for obtaining data + * @param array $aliases Aliases of db/table/columns + * + * @return bool Whether it succeeded + */ + public function exportData( + $db, + $table, + $crlf, + $error_url, + $sql_query, + array $aliases = array() + ) { + global $current_row, $sql_backquotes; + + // Do not export data for merge tables + if ($GLOBALS['dbi']->getTable($db, $table)->isMerge()) { + return true; + } + + $db_alias = $db; + $table_alias = $table; + $this->initAlias($aliases, $db_alias, $table_alias); + + if (isset($GLOBALS['sql_compatibility'])) { + $compat = $GLOBALS['sql_compatibility']; + } else { + $compat = 'NONE'; + } + + $formatted_table_name = Util::backquoteCompat( + $table_alias, + $compat, + $sql_backquotes + ); + + // Do not export data for a VIEW, unless asked to export the view as a table + // (For a VIEW, this is called only when exporting a single VIEW) + if ($GLOBALS['dbi']->getTable($db, $table)->isView() + && empty($GLOBALS['sql_views_as_tables']) + ) { + $head = $this->_possibleCRLF() + . $this->_exportComment() + . $this->_exportComment('VIEW ' . ' ' . $formatted_table_name) + . $this->_exportComment(__('Data:') . ' ' . __('None')) + . $this->_exportComment() + . $this->_possibleCRLF(); + + return Export::outputHandler($head); + } + + $result = $GLOBALS['dbi']->tryQuery( + $sql_query, + DatabaseInterface::CONNECT_USER, + DatabaseInterface::QUERY_UNBUFFERED + ); + // a possible error: the table has crashed + $tmp_error = $GLOBALS['dbi']->getError(); + if ($tmp_error) { + $message = sprintf(__('Error reading data for table %s:'), "$db.$table"); + $message .= ' ' . $tmp_error; + if (! defined('TESTSUITE')) { + trigger_error($message, E_USER_ERROR); + } + return Export::outputHandler( + $this->_exportComment($message) + ); + } + + if ($result == false) { + $GLOBALS['dbi']->freeResult($result); + + return true; + } + + $fields_cnt = $GLOBALS['dbi']->numFields($result); + + // Get field information + $fields_meta = $GLOBALS['dbi']->getFieldsMeta($result); + $field_flags = array(); + for ($j = 0; $j < $fields_cnt; $j++) { + $field_flags[$j] = $GLOBALS['dbi']->fieldFlags($result, $j); + } + + $field_set = array(); + for ($j = 0; $j < $fields_cnt; $j++) { + $col_as = $fields_meta[$j]->name; + if (!empty($aliases[$db]['tables'][$table]['columns'][$col_as])) { + $col_as = $aliases[$db]['tables'][$table]['columns'][$col_as]; + } + $field_set[$j] = Util::backquoteCompat( + $col_as, + $compat, + $sql_backquotes + ); + } + + if (isset($GLOBALS['sql_type']) + && $GLOBALS['sql_type'] == 'UPDATE' + ) { + // update + $schema_insert = 'UPDATE '; + if (isset($GLOBALS['sql_ignore'])) { + $schema_insert .= 'IGNORE '; + } + // avoid EOL blank + $schema_insert .= Util::backquoteCompat( + $table_alias, + $compat, + $sql_backquotes + ) . ' SET'; + } else { + // insert or replace + if (isset($GLOBALS['sql_type']) + && $GLOBALS['sql_type'] == 'REPLACE' + ) { + $sql_command = 'REPLACE'; + } else { + $sql_command = 'INSERT'; + } + + // delayed inserts? + if (isset($GLOBALS['sql_delayed'])) { + $insert_delayed = ' DELAYED'; + } else { + $insert_delayed = ''; + } + + // insert ignore? + if (isset($GLOBALS['sql_type']) + && $GLOBALS['sql_type'] == 'INSERT' + && isset($GLOBALS['sql_ignore']) + ) { + $insert_delayed .= ' IGNORE'; + } + //truncate table before insert + if (isset($GLOBALS['sql_truncate']) + && $GLOBALS['sql_truncate'] + && $sql_command == 'INSERT' + ) { + $truncate = 'TRUNCATE TABLE ' + . Util::backquoteCompat( + $table_alias, + $compat, + $sql_backquotes + ) . ";"; + $truncatehead = $this->_possibleCRLF() + . $this->_exportComment() + . $this->_exportComment( + __('Truncate table before insert') . ' ' + . $formatted_table_name + ) + . $this->_exportComment() + . $crlf; + Export::outputHandler($truncatehead); + Export::outputHandler($truncate); + } + + // scheme for inserting fields + if ($GLOBALS['sql_insert_syntax'] == 'complete' + || $GLOBALS['sql_insert_syntax'] == 'both' + ) { + $fields = implode(', ', $field_set); + $schema_insert = $sql_command . $insert_delayed . ' INTO ' + . Util::backquoteCompat( + $table_alias, + $compat, + $sql_backquotes + ) + // avoid EOL blank + . ' (' . $fields . ') VALUES'; + } else { + $schema_insert = $sql_command . $insert_delayed . ' INTO ' + . Util::backquoteCompat( + $table_alias, + $compat, + $sql_backquotes + ) + . ' VALUES'; + } + } + + //\x08\\x09, not required + $current_row = 0; + $query_size = 0; + if (($GLOBALS['sql_insert_syntax'] == 'extended' + || $GLOBALS['sql_insert_syntax'] == 'both') + && (!isset($GLOBALS['sql_type']) + || $GLOBALS['sql_type'] != 'UPDATE') + ) { + $separator = ','; + $schema_insert .= $crlf; + } else { + $separator = ';'; + } + + while ($row = $GLOBALS['dbi']->fetchRow($result)) { + if ($current_row == 0) { + $head = $this->_possibleCRLF() + . $this->_exportComment() + . $this->_exportComment( + __('Dumping data for table') . ' ' + . $formatted_table_name + ) + . $this->_exportComment() + . $crlf; + if (!Export::outputHandler($head)) { + return false; + } + } + // We need to SET IDENTITY_INSERT ON for MSSQL + if (isset($GLOBALS['sql_compatibility']) + && $GLOBALS['sql_compatibility'] == 'MSSQL' + && $current_row == 0 + ) { + if (!Export::outputHandler( + 'SET IDENTITY_INSERT ' + . Util::backquoteCompat( + $table_alias, + $compat, + $sql_backquotes + ) + . ' ON ;' . $crlf + ) + ) { + return false; + } + } + $current_row++; + $values = array(); + for ($j = 0; $j < $fields_cnt; $j++) { + // NULL + if (!isset($row[$j]) || is_null($row[$j])) { + $values[] = 'NULL'; + } elseif ($fields_meta[$j]->numeric + && $fields_meta[$j]->type != 'timestamp' + && !$fields_meta[$j]->blob + ) { + // a number + // timestamp is numeric on some MySQL 4.1, BLOBs are + // sometimes numeric + $values[] = $row[$j]; + } elseif (stristr($field_flags[$j], 'BINARY') !== false + && isset($GLOBALS['sql_hex_for_binary']) + ) { + // a true BLOB + // - mysqldump only generates hex data when the --hex-blob + // option is used, for fields having the binary attribute + // no hex is generated + // - a TEXT field returns type blob but a real blob + // returns also the 'binary' flag + + // empty blobs need to be different, but '0' is also empty + // :-( + if (empty($row[$j]) && $row[$j] != '0') { + $values[] = '\'\''; + } else { + $values[] = '0x' . bin2hex($row[$j]); + } + } elseif ($fields_meta[$j]->type == 'bit') { + // detection of 'bit' works only on mysqli extension + $values[] = "b'" . $GLOBALS['dbi']->escapeString( + Util::printableBitValue( + $row[$j], + $fields_meta[$j]->length + ) + ) + . "'"; + } elseif ($fields_meta[$j]->type === 'geometry') { + // export GIS types as hex + $values[] = '0x' . bin2hex($row[$j]); + } elseif (!empty($GLOBALS['exporting_metadata']) + && $row[$j] == '@LAST_PAGE' + ) { + $values[] = '@LAST_PAGE'; + } else { + // something else -> treat as a string + $values[] = '\'' + . $GLOBALS['dbi']->escapeString($row[$j]) + . '\''; + } // end if + } // end for + + // should we make update? + if (isset($GLOBALS['sql_type']) + && $GLOBALS['sql_type'] == 'UPDATE' + ) { + + $insert_line = $schema_insert; + for ($i = 0; $i < $fields_cnt; $i++) { + if (0 == $i) { + $insert_line .= ' '; + } + if ($i > 0) { + // avoid EOL blank + $insert_line .= ','; + } + $insert_line .= $field_set[$i] . ' = ' . $values[$i]; + } + + list($tmp_unique_condition, $tmp_clause_is_unique) + = Util::getUniqueCondition( + $result, // handle + $fields_cnt, // fields_cnt + $fields_meta, // fields_meta + $row, // row + false, // force_unique + false, // restrict_to_table + null // analyzed_sql_results + ); + $insert_line .= ' WHERE ' . $tmp_unique_condition; + unset($tmp_unique_condition, $tmp_clause_is_unique); + } else { + + // Extended inserts case + if ($GLOBALS['sql_insert_syntax'] == 'extended' + || $GLOBALS['sql_insert_syntax'] == 'both' + ) { + if ($current_row == 1) { + $insert_line = $schema_insert . '(' + . implode(', ', $values) . ')'; + } else { + $insert_line = '(' . implode(', ', $values) . ')'; + $insertLineSize = mb_strlen($insert_line); + $sql_max_size = $GLOBALS['sql_max_query_size']; + if (isset($sql_max_size) + && $sql_max_size > 0 + && $query_size + $insertLineSize > $sql_max_size + ) { + if (!Export::outputHandler(';' . $crlf)) { + return false; + } + $query_size = 0; + $current_row = 1; + $insert_line = $schema_insert . $insert_line; + } + } + $query_size += mb_strlen($insert_line); + // Other inserts case + } else { + $insert_line = $schema_insert + . '(' . implode(', ', $values) . ')'; + } + } + unset($values); + + if (!Export::outputHandler( + ($current_row == 1 ? '' : $separator . $crlf) + . $insert_line + ) + ) { + return false; + } + } // end while + + if ($current_row > 0) { + if (!Export::outputHandler(';' . $crlf)) { + return false; + } + } + + // We need to SET IDENTITY_INSERT OFF for MSSQL + if (isset($GLOBALS['sql_compatibility']) + && $GLOBALS['sql_compatibility'] == 'MSSQL' + && $current_row > 0 + ) { + $outputSucceeded = Export::outputHandler( + $crlf . 'SET IDENTITY_INSERT ' + . Util::backquoteCompat( + $table_alias, + $compat, + $sql_backquotes + ) + . ' OFF;' . $crlf + ); + if (!$outputSucceeded) { + return false; + } + } + + $GLOBALS['dbi']->freeResult($result); + + return true; + } // end of the 'exportData()' function + + /** + * Make a create table statement compatible with MSSQL + * + * @param string $create_query MySQL create table statement + * + * @return string MSSQL compatible create table statement + */ + private function _makeCreateTableMSSQLCompatible($create_query) + { + // In MSSQL + // 1. No 'IF NOT EXISTS' in CREATE TABLE + // 2. DATE field doesn't exists, we will use DATETIME instead + // 3. UNSIGNED attribute doesn't exist + // 4. No length on INT, TINYINT, SMALLINT, BIGINT and no precision on + // FLOAT fields + // 5. No KEY and INDEX inside CREATE TABLE + // 6. DOUBLE field doesn't exists, we will use FLOAT instead + + $create_query = preg_replace( + "/^CREATE TABLE IF NOT EXISTS/", + 'CREATE TABLE', + $create_query + ); + // first we need to replace all lines ended with '" DATE ...,\n' + // last preg_replace preserve us from situation with date text + // inside DEFAULT field value + $create_query = preg_replace( + "/\" date DEFAULT NULL(,)?\n/", + '" datetime DEFAULT NULL$1' . "\n", + $create_query + ); + $create_query = preg_replace( + "/\" date NOT NULL(,)?\n/", + '" datetime NOT NULL$1' . "\n", + $create_query + ); + $create_query = preg_replace( + '/" date NOT NULL DEFAULT \'([^\'])/', + '" datetime NOT NULL DEFAULT \'$1', + $create_query + ); + + // next we need to replace all lines ended with ') UNSIGNED ...,' + // last preg_replace preserve us from situation with unsigned text + // inside DEFAULT field value + $create_query = preg_replace( + "/\) unsigned NOT NULL(,)?\n/", + ') NOT NULL$1' . "\n", + $create_query + ); + $create_query = preg_replace( + "/\) unsigned DEFAULT NULL(,)?\n/", + ') DEFAULT NULL$1' . "\n", + $create_query + ); + $create_query = preg_replace( + '/\) unsigned NOT NULL DEFAULT \'([^\'])/', + ') NOT NULL DEFAULT \'$1', + $create_query + ); + + // we need to replace all lines ended with + // '" INT|TINYINT([0-9]{1,}) ...,' last preg_replace preserve us + // from situation with int([0-9]{1,}) text inside DEFAULT field + // value + $create_query = preg_replace( + '/" (int|tinyint|smallint|bigint)\([0-9]+\) DEFAULT NULL(,)?\n/', + '" $1 DEFAULT NULL$2' . "\n", + $create_query + ); + $create_query = preg_replace( + '/" (int|tinyint|smallint|bigint)\([0-9]+\) NOT NULL(,)?\n/', + '" $1 NOT NULL$2' . "\n", + $create_query + ); + $create_query = preg_replace( + '/" (int|tinyint|smallint|bigint)\([0-9]+\) NOT NULL DEFAULT \'([^\'])/', + '" $1 NOT NULL DEFAULT \'$2', + $create_query + ); + + // we need to replace all lines ended with + // '" FLOAT|DOUBLE([0-9,]{1,}) ...,' + // last preg_replace preserve us from situation with + // float([0-9,]{1,}) text inside DEFAULT field value + $create_query = preg_replace( + '/" (float|double)(\([0-9]+,[0-9,]+\))? DEFAULT NULL(,)?\n/', + '" float DEFAULT NULL$3' . "\n", + $create_query + ); + $create_query = preg_replace( + '/" (float|double)(\([0-9,]+,[0-9,]+\))? NOT NULL(,)?\n/', + '" float NOT NULL$3' . "\n", + $create_query + ); + $create_query = preg_replace( + '/" (float|double)(\([0-9,]+,[0-9,]+\))? NOT NULL DEFAULT \'([^\'])/', + '" float NOT NULL DEFAULT \'$3', + $create_query + ); + + // @todo remove indexes from CREATE TABLE + + return $create_query; + } + + /** + * replaces db/table/column names with their aliases + * + * @param string $sql_query SQL query in which aliases are to be substituted + * @param array $aliases Alias information for db/table/column + * @param string $db the database name + * @param string $table the tablename + * @param string &$flag the flag denoting whether any replacement was done + * + * @return string query replaced with aliases + */ + public function replaceWithAliases( + $sql_query, + array $aliases, + $db, + $table = '', + &$flag = null + ) { + $flag = false; + + /** + * The parser of this query. + * + * @var Parser $parser + */ + $parser = new Parser($sql_query); + + if (empty($parser->statements[0])) { + return $sql_query; + } + + /** + * The statement that represents the query. + * + * @var \PhpMyAdmin\SqlParser\Statements\CreateStatement $statement + */ + $statement = $parser->statements[0]; + + /** + * Old database name. + * + * @var string $old_database + */ + $old_database = $db; + + // Replacing aliases in `CREATE TABLE` statement. + if ($statement->options->has('TABLE')) { + + // Extracting the name of the old database and table from the + // statement to make sure the parameters are corect. + if (!empty($statement->name->database)) { + $old_database = $statement->name->database; + } + + /** + * Old table name. + * + * @var string $old_table + */ + $old_table = $statement->name->table; + + // Finding the aliased database name. + // The database might be empty so we have to add a few checks. + $new_database = null; + if (!empty($statement->name->database)) { + $new_database = $statement->name->database; + if (!empty($aliases[$old_database]['alias'])) { + $new_database = $aliases[$old_database]['alias']; + } + } + + // Finding the aliases table name. + $new_table = $old_table; + if (!empty($aliases[$old_database]['tables'][$old_table]['alias'])) { + $new_table = $aliases[$old_database]['tables'][$old_table]['alias']; + } + + // Replacing new values. + if (($statement->name->database !== $new_database) + || ($statement->name->table !== $new_table) + ) { + $statement->name->database = $new_database; + $statement->name->table = $new_table; + $statement->name->expr = null; // Force rebuild. + $flag = true; + } + + foreach ($statement->fields as $field) { + + // Column name. + if (!empty($field->type)) { + if (!empty($aliases[$old_database]['tables'][$old_table]['columns'][$field->name])) { + $field->name = $aliases[$old_database]['tables'] + [$old_table]['columns'][$field->name]; + $flag = true; + } + } + + // Key's columns. + if (!empty($field->key)) { + foreach ($field->key->columns as $key => $column) { + if (!empty($aliases[$old_database]['tables'][$old_table]['columns'][$column['name']])) { + $field->key->columns[$key]['name'] = $aliases[$old_database] + ['tables'][$old_table]['columns'][$column['name']]; + $flag = true; + } + } + } + + // References. + if (!empty($field->references)) { + $ref_table = $field->references->table->table; + // Replacing table. + if (!empty($aliases[$old_database]['tables'][$ref_table]['alias'])) { + $field->references->table->table + = $aliases[$old_database]['tables'][$ref_table]['alias']; + $field->references->table->expr = null; + $flag = true; + } + // Replacing column names. + foreach ($field->references->columns as $key => $column) { + if (!empty($aliases[$old_database]['tables'][$ref_table]['columns'][$column])) { + $field->references->columns[$key] + = $aliases[$old_database]['tables'][$ref_table]['columns'][$column]; + $flag = true; + } + } + } + } + } elseif ($statement->options->has('TRIGGER')) { + + // Extracting the name of the old database and table from the + // statement to make sure the parameters are corect. + if (!empty($statement->table->database)) { + $old_database = $statement->table->database; + } + + /** + * Old table name. + * + * @var string $old_table + */ + $old_table = $statement->table->table; + + if (!empty($aliases[$old_database]['tables'][$old_table]['alias'])) { + $statement->table->table + = $aliases[$old_database]['tables'][$old_table]['alias']; + $statement->table->expr = null; // Force rebuild. + $flag = true; + } + } + + if (($statement->options->has('TRIGGER')) + || ($statement->options->has('PROCEDURE')) + || ($statement->options->has('FUNCTION')) + || ($statement->options->has('VIEW')) + ) { + + // Repalcing the body. + for ($i = 0, $count = count($statement->body); $i < $count; ++$i) { + + /** + * Token parsed at this moment. + * + * @var Token $token + */ + $token = $statement->body[$i]; + + // Replacing only symbols (that are not variables) and unknown + // identifiers. + if ((($token->type === Token::TYPE_SYMBOL) + && (!($token->flags & Token::FLAG_SYMBOL_VARIABLE))) + || ((($token->type === Token::TYPE_KEYWORD) + && (!($token->flags & Token::FLAG_KEYWORD_RESERVED))) + || ($token->type === Token::TYPE_NONE)) + ) { + $alias = $this->getAlias($aliases, $token->value); + if (!empty($alias)) { + // Replacing the token. + $token->token = Context::escape($alias); + $flag = true; + } + } + } + } + + return $statement->build(); + } + + /** + * Generate comment + * + * @param string $crlf Carriage return character + * @param string $sql_statement SQL statement + * @param string $comment1 Comment for dumped table + * @param string $comment2 Comment for current table + * @param string $table_alias Table alias + * @param string $compat Compatibility mode + * + * @return string + */ + protected function generateComment( + $crlf, + $sql_statement, + $comment1, + $comment2, + $table_alias, + $compat + ) { + if (!isset($sql_statement)) { + if (isset($GLOBALS['no_constraints_comments'])) { + $sql_statement = ''; + } else { + $sql_statement = $crlf + . $this->_exportComment() + . $this->_exportComment($comment1) + . $this->_exportComment(); + } + } + + // comments for current table + if (!isset($GLOBALS['no_constraints_comments'])) { + $sql_statement .= $crlf + . $this->_exportComment() + . $this->_exportComment( + $comment2 . ' ' . Util::backquoteCompat( + $table_alias, + $compat, + isset($GLOBALS['sql_backquotes']) + ) + ) + . $this->_exportComment(); + } + + return $sql_statement; + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Export/ExportTexytext.php b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Export/ExportTexytext.php new file mode 100644 index 00000000..3100f3aa --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Export/ExportTexytext.php @@ -0,0 +1,620 @@ +setProperties(); + } + + /** + * Sets the export Texy! text properties + * + * @return void + */ + protected function setProperties() + { + $exportPluginProperties = new ExportPluginProperties(); + $exportPluginProperties->setText('Texy! text'); + $exportPluginProperties->setExtension('txt'); + $exportPluginProperties->setMimeType('text/plain'); + $exportPluginProperties->setOptionsText(__('Options')); + + // create the root group that will be the options field for + // $exportPluginProperties + // this will be shown as "Format specific options" + $exportSpecificOptions = new OptionsPropertyRootGroup( + "Format Specific Options" + ); + + // what to dump (structure/data/both) main group + $dumpWhat = new OptionsPropertyMainGroup( + "general_opts", __('Dump table') + ); + // create primary items and add them to the group + $leaf = new RadioPropertyItem("structure_or_data"); + $leaf->setValues( + array( + 'structure' => __('structure'), + 'data' => __('data'), + 'structure_and_data' => __('structure and data'), + ) + ); + $dumpWhat->addProperty($leaf); + // add the main group to the root group + $exportSpecificOptions->addProperty($dumpWhat); + + // data options main group + $dataOptions = new OptionsPropertyMainGroup( + "data", __('Data dump options') + ); + $dataOptions->setForce('structure'); + // create primary items and add them to the group + $leaf = new BoolPropertyItem( + "columns", + __('Put columns names in the first row') + ); + $dataOptions->addProperty($leaf); + $leaf = new TextPropertyItem( + 'null', + __('Replace NULL with:') + ); + $dataOptions->addProperty($leaf); + // add the main group to the root group + $exportSpecificOptions->addProperty($dataOptions); + + // set the options for the export plugin property item + $exportPluginProperties->setOptions($exportSpecificOptions); + $this->properties = $exportPluginProperties; + } + + /** + * Outputs export header + * + * @return bool Whether it succeeded + */ + public function exportHeader() + { + return true; + } + + /** + * Outputs export footer + * + * @return bool Whether it succeeded + */ + public function exportFooter() + { + return true; + } + + /** + * Outputs database header + * + * @param string $db Database name + * @param string $db_alias Alias of db + * + * @return bool Whether it succeeded + */ + public function exportDBHeader($db, $db_alias = '') + { + if (empty($db_alias)) { + $db_alias = $db; + } + + return Export::outputHandler( + '===' . __('Database') . ' ' . $db_alias . "\n\n" + ); + } + + /** + * Outputs database footer + * + * @param string $db Database name + * + * @return bool Whether it succeeded + */ + public function exportDBFooter($db) + { + return true; + } + + /** + * Outputs CREATE DATABASE statement + * + * @param string $db Database name + * @param string $export_type 'server', 'database', 'table' + * @param string $db_alias Aliases of db + * + * @return bool Whether it succeeded + */ + public function exportDBCreate($db, $export_type, $db_alias = '') + { + return true; + } + + /** + * Outputs the content of a table in NHibernate format + * + * @param string $db database name + * @param string $table table name + * @param string $crlf the end of line sequence + * @param string $error_url the url to go back in case of error + * @param string $sql_query SQL query for obtaining data + * @param array $aliases Aliases of db/table/columns + * + * @return bool Whether it succeeded + */ + public function exportData( + $db, + $table, + $crlf, + $error_url, + $sql_query, + array $aliases = array() + ) { + global $what; + + $db_alias = $db; + $table_alias = $table; + $this->initAlias($aliases, $db_alias, $table_alias); + + if (!Export::outputHandler( + '== ' . __('Dumping data for table') . ' ' . $table_alias . "\n\n" + ) + ) { + return false; + } + + // Gets the data from the database + $result = $GLOBALS['dbi']->query( + $sql_query, + DatabaseInterface::CONNECT_USER, + DatabaseInterface::QUERY_UNBUFFERED + ); + $fields_cnt = $GLOBALS['dbi']->numFields($result); + + // If required, get fields name at the first line + if (isset($GLOBALS[$what . '_columns'])) { + $text_output = "|------\n"; + for ($i = 0; $i < $fields_cnt; $i++) { + $col_as = $GLOBALS['dbi']->fieldName($result, $i); + if (!empty($aliases[$db]['tables'][$table]['columns'][$col_as])) { + $col_as = $aliases[$db]['tables'][$table]['columns'][$col_as]; + } + $text_output .= '|' + . htmlspecialchars(stripslashes($col_as)); + } // end for + $text_output .= "\n|------\n"; + if (!Export::outputHandler($text_output)) { + return false; + } + } // end if + + // Format the data + while ($row = $GLOBALS['dbi']->fetchRow($result)) { + $text_output = ''; + for ($j = 0; $j < $fields_cnt; $j++) { + if (!isset($row[$j]) || is_null($row[$j])) { + $value = $GLOBALS[$what . '_null']; + } elseif ($row[$j] == '0' || $row[$j] != '') { + $value = $row[$j]; + } else { + $value = ' '; + } + $text_output .= '|' + . str_replace( + '|', + '|', + htmlspecialchars($value) + ); + } // end for + $text_output .= "\n"; + if (!Export::outputHandler($text_output)) { + return false; + } + } // end while + $GLOBALS['dbi']->freeResult($result); + + return true; + } + + /** + * Returns a stand-in CREATE definition to resolve view dependencies + * + * @param string $db the database name + * @param string $view the view name + * @param string $crlf the end of line sequence + * @param array $aliases Aliases of db/table/columns + * + * @return string resulting definition + */ + public function getTableDefStandIn($db, $view, $crlf, $aliases = array()) + { + $text_output = ''; + + /** + * Get the unique keys in the table + */ + $unique_keys = array(); + $keys = $GLOBALS['dbi']->getTableIndexes($db, $view); + foreach ($keys as $key) { + if ($key['Non_unique'] == 0) { + $unique_keys[] = $key['Column_name']; + } + } + + /** + * Gets fields properties + */ + $GLOBALS['dbi']->selectDb($db); + + /** + * Displays the table structure + */ + + $text_output .= "|------\n" + . '|' . __('Column') + . '|' . __('Type') + . '|' . __('Null') + . '|' . __('Default') + . "\n|------\n"; + + $columns = $GLOBALS['dbi']->getColumns($db, $view); + foreach ($columns as $column) { + $col_as = $column['Field']; + if (!empty($aliases[$db]['tables'][$view]['columns'][$col_as])) { + $col_as = $aliases[$db]['tables'][$view]['columns'][$col_as]; + } + $text_output .= $this->formatOneColumnDefinition( + $column, + $unique_keys, + $col_as + ); + $text_output .= "\n"; + } // end foreach + + return $text_output; + } + + /** + * Returns $table's CREATE definition + * + * @param string $db the database name + * @param string $table the table name + * @param string $crlf the end of line sequence + * @param string $error_url the url to go back in case of error + * @param bool $do_relation whether to include relation comments + * @param bool $do_comments whether to include the pmadb-style column + * comments as comments in the structure; + * this is deprecated but the parameter is + * left here because export.php calls + * $this->exportStructure() also for other + * export types which use this parameter + * @param bool $do_mime whether to include mime comments + * @param bool $show_dates whether to include creation/update/check dates + * @param bool $add_semicolon whether to add semicolon and end-of-line + * at the end + * @param bool $view whether we're handling a view + * @param array $aliases Aliases of db/table/columns + * + * @return string resulting schema + */ + public function getTableDef( + $db, + $table, + $crlf, + $error_url, + $do_relation, + $do_comments, + $do_mime, + $show_dates = false, + $add_semicolon = true, + $view = false, + array $aliases = array() + ) { + global $cfgRelation; + + $text_output = ''; + + /** + * Get the unique keys in the table + */ + $unique_keys = array(); + $keys = $GLOBALS['dbi']->getTableIndexes($db, $table); + foreach ($keys as $key) { + if ($key['Non_unique'] == 0) { + $unique_keys[] = $key['Column_name']; + } + } + + /** + * Gets fields properties + */ + $GLOBALS['dbi']->selectDb($db); + + // Check if we can use Relations + list($res_rel, $have_rel) = $this->relation->getRelationsAndStatus( + $do_relation && !empty($cfgRelation['relation']), + $db, + $table + ); + + /** + * Displays the table structure + */ + + $text_output .= "|------\n"; + $text_output .= '|' . __('Column'); + $text_output .= '|' . __('Type'); + $text_output .= '|' . __('Null'); + $text_output .= '|' . __('Default'); + if ($do_relation && $have_rel) { + $text_output .= '|' . __('Links to'); + } + if ($do_comments) { + $text_output .= '|' . __('Comments'); + $comments = $this->relation->getComments($db, $table); + } + if ($do_mime && $cfgRelation['mimework']) { + $text_output .= '|' . htmlspecialchars('MIME'); + $mime_map = Transformations::getMIME($db, $table, true); + } + $text_output .= "\n|------\n"; + + $columns = $GLOBALS['dbi']->getColumns($db, $table); + foreach ($columns as $column) { + $col_as = $column['Field']; + if (!empty($aliases[$db]['tables'][$table]['columns'][$col_as])) { + $col_as = $aliases[$db]['tables'][$table]['columns'][$col_as]; + } + $text_output .= $this->formatOneColumnDefinition( + $column, + $unique_keys, + $col_as + ); + $field_name = $column['Field']; + if ($do_relation && $have_rel) { + $text_output .= '|' . htmlspecialchars( + $this->getRelationString( + $res_rel, + $field_name, + $db, + $aliases + ) + ); + } + if ($do_comments && $cfgRelation['commwork']) { + $text_output .= '|' + . (isset($comments[$field_name]) + ? htmlspecialchars($comments[$field_name]) + : ''); + } + if ($do_mime && $cfgRelation['mimework']) { + $text_output .= '|' + . (isset($mime_map[$field_name]) + ? htmlspecialchars( + str_replace('_', '/', $mime_map[$field_name]['mimetype']) + ) + : ''); + } + + $text_output .= "\n"; + } // end foreach + + return $text_output; + } // end of the '$this->getTableDef()' function + + /** + * Outputs triggers + * + * @param string $db database name + * @param string $table table name + * + * @return string Formatted triggers list + */ + public function getTriggers($db, $table) + { + $dump = "|------\n"; + $dump .= '|' . __('Name'); + $dump .= '|' . __('Time'); + $dump .= '|' . __('Event'); + $dump .= '|' . __('Definition'); + $dump .= "\n|------\n"; + + $triggers = $GLOBALS['dbi']->getTriggers($db, $table); + + foreach ($triggers as $trigger) { + $dump .= '|' . $trigger['name']; + $dump .= '|' . $trigger['action_timing']; + $dump .= '|' . $trigger['event_manipulation']; + $dump .= '|' . + str_replace( + '|', + '|', + htmlspecialchars($trigger['definition']) + ); + $dump .= "\n"; + } + + return $dump; + } + + /** + * Outputs table's structure + * + * @param string $db database name + * @param string $table table name + * @param string $crlf the end of line sequence + * @param string $error_url the url to go back in case of error + * @param string $export_mode 'create_table', 'triggers', 'create_view', + * 'stand_in' + * @param string $export_type 'server', 'database', 'table' + * @param bool $do_relation whether to include relation comments + * @param bool $do_comments whether to include the pmadb-style column + * comments as comments in the structure; + * this is deprecated but the parameter is + * left here because export.php calls + * $this->exportStructure() also for other + * export types which use this parameter + * @param bool $do_mime whether to include mime comments + * @param bool $dates whether to include creation/update/check dates + * @param array $aliases Aliases of db/table/columns + * + * @return bool Whether it succeeded + */ + public function exportStructure( + $db, + $table, + $crlf, + $error_url, + $export_mode, + $export_type, + $do_relation = false, + $do_comments = false, + $do_mime = false, + $dates = false, + array $aliases = array() + ) { + $db_alias = $db; + $table_alias = $table; + $this->initAlias($aliases, $db_alias, $table_alias); + $dump = ''; + + switch ($export_mode) { + case 'create_table': + $dump .= '== ' . __('Table structure for table') . ' ' + . $table_alias . "\n\n"; + $dump .= $this->getTableDef( + $db, + $table, + $crlf, + $error_url, + $do_relation, + $do_comments, + $do_mime, + $dates, + true, + false, + $aliases + ); + break; + case 'triggers': + $dump = ''; + $triggers = $GLOBALS['dbi']->getTriggers($db, $table); + if ($triggers) { + $dump .= '== ' . __('Triggers') . ' ' . $table_alias . "\n\n"; + $dump .= $this->getTriggers($db, $table); + } + break; + case 'create_view': + $dump .= '== ' . __('Structure for view') . ' ' . $table_alias . "\n\n"; + $dump .= $this->getTableDef( + $db, + $table, + $crlf, + $error_url, + $do_relation, + $do_comments, + $do_mime, + $dates, + true, + true, + $aliases + ); + break; + case 'stand_in': + $dump .= '== ' . __('Stand-in structure for view') + . ' ' . $table . "\n\n"; + // export a stand-in definition to resolve view dependencies + $dump .= $this->getTableDefStandIn($db, $table, $crlf, $aliases); + } // end switch + + return Export::outputHandler($dump); + } + + /** + * Formats the definition for one column + * + * @param array $column info about this column + * @param array $unique_keys unique keys for this table + * @param string $col_alias Column Alias + * + * @return string Formatted column definition + */ + public function formatOneColumnDefinition( + $column, + $unique_keys, + $col_alias = '' + ) { + if (empty($col_alias)) { + $col_alias = $column['Field']; + } + $extracted_columnspec + = Util::extractColumnSpec($column['Type']); + $type = $extracted_columnspec['print_type']; + if (empty($type)) { + $type = ' '; + } + + if (!isset($column['Default'])) { + if ($column['Null'] != 'NO') { + $column['Default'] = 'NULL'; + } + } + + $fmt_pre = ''; + $fmt_post = ''; + if (in_array($column['Field'], $unique_keys)) { + $fmt_pre = '**' . $fmt_pre; + $fmt_post = $fmt_post . '**'; + } + if ($column['Key'] == 'PRI') { + $fmt_pre = '//' . $fmt_pre; + $fmt_post = $fmt_post . '//'; + } + $definition = '|' + . $fmt_pre . htmlspecialchars($col_alias) . $fmt_post; + $definition .= '|' . htmlspecialchars($type); + $definition .= '|' + . (($column['Null'] == '' || $column['Null'] == 'NO') + ? __('No') : __('Yes')); + $definition .= '|' + . htmlspecialchars( + isset($column['Default']) ? $column['Default'] : '' + ); + + return $definition; + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Export/ExportXml.php b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Export/ExportXml.php new file mode 100644 index 00000000..b6ab2d9c --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Export/ExportXml.php @@ -0,0 +1,584 @@ +setProperties(); + } + + /** + * Initialize the local variables that are used for export XML + * + * @return void + */ + protected function initSpecificVariables() + { + global $table, $tables; + $this->_setTable($table); + if (is_array($tables)) { + $this->_setTables($tables); + } + } + + /** + * Sets the export XML properties + * + * @return void + */ + protected function setProperties() + { + // create the export plugin property item + $exportPluginProperties = new ExportPluginProperties(); + $exportPluginProperties->setText('XML'); + $exportPluginProperties->setExtension('xml'); + $exportPluginProperties->setMimeType('text/xml'); + $exportPluginProperties->setOptionsText(__('Options')); + + // create the root group that will be the options field for + // $exportPluginProperties + // this will be shown as "Format specific options" + $exportSpecificOptions = new OptionsPropertyRootGroup( + "Format Specific Options" + ); + + // general options main group + $generalOptions = new OptionsPropertyMainGroup("general_opts"); + // create primary items and add them to the group + $leaf = new HiddenPropertyItem("structure_or_data"); + $generalOptions->addProperty($leaf); + // add the main group to the root group + $exportSpecificOptions->addProperty($generalOptions); + + // export structure main group + $structure = new OptionsPropertyMainGroup( + "structure", __('Object creation options (all are recommended)') + ); + + // create primary items and add them to the group + $leaf = new BoolPropertyItem( + "export_events", + __('Events') + ); + $structure->addProperty($leaf); + $leaf = new BoolPropertyItem( + "export_functions", + __('Functions') + ); + $structure->addProperty($leaf); + $leaf = new BoolPropertyItem( + "export_procedures", + __('Procedures') + ); + $structure->addProperty($leaf); + $leaf = new BoolPropertyItem( + "export_tables", + __('Tables') + ); + $structure->addProperty($leaf); + $leaf = new BoolPropertyItem( + "export_triggers", + __('Triggers') + ); + $structure->addProperty($leaf); + $leaf = new BoolPropertyItem( + "export_views", + __('Views') + ); + $structure->addProperty($leaf); + $exportSpecificOptions->addProperty($structure); + + // data main group + $data = new OptionsPropertyMainGroup( + "data", __('Data dump options') + ); + // create primary items and add them to the group + $leaf = new BoolPropertyItem( + "export_contents", + __('Export contents') + ); + $data->addProperty($leaf); + $exportSpecificOptions->addProperty($data); + + // set the options for the export plugin property item + $exportPluginProperties->setOptions($exportSpecificOptions); + $this->properties = $exportPluginProperties; + } + + /** + * Generates output for SQL defintions of routines + * + * @param string $db Database name + * @param string $type Item type to be used in XML output + * @param string $dbitype Item type used in DBI qieries + * + * @return string XML with definitions + */ + private function _exportRoutines($db, $type, $dbitype) + { + // Export routines + $routines = $GLOBALS['dbi']->getProceduresOrFunctions( + $db, + $dbitype + ); + return $this->_exportDefinitions($db, $type, $dbitype, $routines); + } + + /** + * Generates output for SQL defintions + * + * @param string $db Database name + * @param string $type Item type to be used in XML output + * @param string $dbitype Item type used in DBI qieries + * @param array $names Names of items to export + * + * @return string XML with definitions + */ + private function _exportDefinitions($db, $type, $dbitype, array $names) + { + global $crlf; + + $head = ''; + + if ($names) { + foreach ($names as $name) { + $head .= ' ' . $crlf; + + // Do some formatting + $sql = $GLOBALS['dbi']->getDefinition($db, $dbitype, $name); + $sql = htmlspecialchars(rtrim($sql)); + $sql = str_replace("\n", "\n ", $sql); + + $head .= " " . $sql . $crlf; + $head .= ' ' . $crlf; + } + } + + return $head; + } + + /** + * Outputs export header. It is the first method to be called, so all + * the required variables are initialized here. + * + * @return bool Whether it succeeded + */ + public function exportHeader() + { + $this->initSpecificVariables(); + global $crlf, $cfg, $db; + $table = $this->_getTable(); + $tables = $this->_getTables(); + + $export_struct = isset($GLOBALS['xml_export_functions']) + || isset($GLOBALS['xml_export_procedures']) + || isset($GLOBALS['xml_export_tables']) + || isset($GLOBALS['xml_export_triggers']) + || isset($GLOBALS['xml_export_views']); + $export_data = isset($GLOBALS['xml_export_contents']) ? true : false; + + if ($GLOBALS['output_charset_conversion']) { + $charset = $GLOBALS['charset']; + } else { + $charset = 'utf-8'; + } + + $head = '' . $crlf + . '' . $crlf . $crlf; + + $head .= '' . $crlf; + + if ($export_struct) { + $result = $GLOBALS['dbi']->fetchResult( + 'SELECT `DEFAULT_CHARACTER_SET_NAME`, `DEFAULT_COLLATION_NAME`' + . ' FROM `information_schema`.`SCHEMATA` WHERE `SCHEMA_NAME`' + . ' = \'' . $GLOBALS['dbi']->escapeString($db) . '\' LIMIT 1' + ); + $db_collation = $result[0]['DEFAULT_COLLATION_NAME']; + $db_charset = $result[0]['DEFAULT_CHARACTER_SET_NAME']; + + $head .= ' ' . $crlf; + $head .= ' ' . $crlf; + $head .= ' ' . $crlf; + + if (is_null($tables)) { + $tables = array(); + } + + if (count($tables) === 0) { + $tables[] = $table; + } + + foreach ($tables as $table) { + // Export tables and views + $result = $GLOBALS['dbi']->fetchResult( + 'SHOW CREATE TABLE ' . Util::backquote($db) . '.' + . Util::backquote($table), + 0 + ); + $tbl = $result[$table][1]; + + $is_view = $GLOBALS['dbi']->getTable($db, $table) + ->isView(); + + if ($is_view) { + $type = 'view'; + } else { + $type = 'table'; + } + + if ($is_view && !isset($GLOBALS['xml_export_views'])) { + continue; + } + + if (!$is_view && !isset($GLOBALS['xml_export_tables'])) { + continue; + } + + $head .= ' ' + . $crlf; + + $tbl = " " . htmlspecialchars($tbl); + $tbl = str_replace("\n", "\n ", $tbl); + + $head .= $tbl . ';' . $crlf; + $head .= ' ' . $crlf; + + if (isset($GLOBALS['xml_export_triggers']) + && $GLOBALS['xml_export_triggers'] + ) { + // Export triggers + $triggers = $GLOBALS['dbi']->getTriggers($db, $table); + if ($triggers) { + foreach ($triggers as $trigger) { + $code = $trigger['create']; + $head .= ' ' . $crlf; + + // Do some formatting + $code = mb_substr(rtrim($code), 0, -3); + $code = " " . htmlspecialchars($code); + $code = str_replace("\n", "\n ", $code); + + $head .= $code . $crlf; + $head .= ' ' . $crlf; + } + + unset($trigger); + unset($triggers); + } + } + } + + if (isset($GLOBALS['xml_export_functions']) + && $GLOBALS['xml_export_functions'] + ) { + $head .= $this->_exportRoutines($db, 'function', 'FUNCTION'); + } + + if (isset($GLOBALS['xml_export_procedures']) + && $GLOBALS['xml_export_procedures'] + ) { + $head .= $this->_exportRoutines($db, 'procedure', 'PROCEDURE'); + } + + if (isset($GLOBALS['xml_export_events']) + && $GLOBALS['xml_export_events'] + ) { + // Export events + $events = $GLOBALS['dbi']->fetchResult( + "SELECT EVENT_NAME FROM information_schema.EVENTS " + . "WHERE EVENT_SCHEMA='" . $GLOBALS['dbi']->escapeString($db) + . "'" + ); + $head .= $this->_exportDefinitions( + $db, 'event', 'EVENT', $events + ); + } + + unset($result); + + $head .= ' ' . $crlf; + $head .= ' ' . $crlf; + + if ($export_data) { + $head .= $crlf; + } + } + + return Export::outputHandler($head); + } + + /** + * Outputs export footer + * + * @return bool Whether it succeeded + */ + public function exportFooter() + { + $foot = ''; + + return Export::outputHandler($foot); + } + + /** + * Outputs database header + * + * @param string $db Database name + * @param string $db_alias Aliases of db + * + * @return bool Whether it succeeded + */ + public function exportDBHeader($db, $db_alias = '') + { + global $crlf; + + if (empty($db_alias)) { + $db_alias = $db; + } + if (isset($GLOBALS['xml_export_contents']) + && $GLOBALS['xml_export_contents'] + ) { + $head = ' ' . $crlf . ' ' . $crlf; + + return Export::outputHandler($head); + } + + return true; + } + + /** + * Outputs database footer + * + * @param string $db Database name + * + * @return bool Whether it succeeded + */ + public function exportDBFooter($db) + { + global $crlf; + + if (isset($GLOBALS['xml_export_contents']) + && $GLOBALS['xml_export_contents'] + ) { + return Export::outputHandler(' ' . $crlf); + } + + return true; + } + + /** + * Outputs CREATE DATABASE statement + * + * @param string $db Database name + * @param string $export_type 'server', 'database', 'table' + * @param string $db_alias Aliases of db + * + * @return bool Whether it succeeded + */ + public function exportDBCreate($db, $export_type, $db_alias = '') + { + return true; + } + + /** + * Outputs the content of a table in XML format + * + * @param string $db database name + * @param string $table table name + * @param string $crlf the end of line sequence + * @param string $error_url the url to go back in case of error + * @param string $sql_query SQL query for obtaining data + * @param array $aliases Aliases of db/table/columns + * + * @return bool Whether it succeeded + */ + public function exportData( + $db, + $table, + $crlf, + $error_url, + $sql_query, + array $aliases = array() + ) { + // Do not export data for merge tables + if ($GLOBALS['dbi']->getTable($db, $table)->isMerge()) { + return true; + } + + $db_alias = $db; + $table_alias = $table; + $this->initAlias($aliases, $db_alias, $table_alias); + if (isset($GLOBALS['xml_export_contents']) + && $GLOBALS['xml_export_contents'] + ) { + $result = $GLOBALS['dbi']->query( + $sql_query, + DatabaseInterface::CONNECT_USER, + DatabaseInterface::QUERY_UNBUFFERED + ); + + $columns_cnt = $GLOBALS['dbi']->numFields($result); + $columns = array(); + for ($i = 0; $i < $columns_cnt; $i++) { + $columns[$i] = stripslashes($GLOBALS['dbi']->fieldName($result, $i)); + } + unset($i); + + $buffer = ' ' . $crlf; + if (!Export::outputHandler($buffer)) { + return false; + } + + while ($record = $GLOBALS['dbi']->fetchRow($result)) { + $buffer = ' ' . $crlf; + for ($i = 0; $i < $columns_cnt; $i++) { + $col_as = $columns[$i]; + if (!empty($aliases[$db]['tables'][$table]['columns'][$col_as]) + ) { + $col_as + = $aliases[$db]['tables'][$table]['columns'][$col_as]; + } + // If a cell is NULL, still export it to preserve + // the XML structure + if (!isset($record[$i]) || is_null($record[$i])) { + $record[$i] = 'NULL'; + } + $buffer .= ' ' + . htmlspecialchars((string)$record[$i]) + . '' . $crlf; + } + $buffer .= '
    ' . $crlf; + + if (!Export::outputHandler($buffer)) { + return false; + } + } + $GLOBALS['dbi']->freeResult($result); + } + + return true; + } + + + /* ~~~~~~~~~~~~~~~~~~~~ Getters and Setters ~~~~~~~~~~~~~~~~~~~~ */ + + /** + * Gets the table name + * + * @return string + */ + private function _getTable() + { + return $this->_table; + } + + /** + * Sets the table name + * + * @param string $table table name + * + * @return void + */ + private function _setTable($table) + { + $this->_table = $table; + } + + /** + * Gets the table names + * + * @return array + */ + private function _getTables() + { + return $this->_tables; + } + + /** + * Sets the table names + * + * @param array $tables table names + * + * @return void + */ + private function _setTables(array $tables) + { + $this->_tables = $tables; + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Export/ExportYaml.php b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Export/ExportYaml.php new file mode 100644 index 00000000..0e49fc65 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Export/ExportYaml.php @@ -0,0 +1,218 @@ +setProperties(); + } + + /** + * Sets the export YAML properties + * + * @return void + */ + protected function setProperties() + { + $exportPluginProperties = new ExportPluginProperties(); + $exportPluginProperties->setText('YAML'); + $exportPluginProperties->setExtension('yml'); + $exportPluginProperties->setMimeType('text/yaml'); + $exportPluginProperties->setForceFile(true); + $exportPluginProperties->setOptionsText(__('Options')); + + // create the root group that will be the options field for + // $exportPluginProperties + // this will be shown as "Format specific options" + $exportSpecificOptions = new OptionsPropertyRootGroup( + "Format Specific Options" + ); + + // general options main group + $generalOptions = new OptionsPropertyMainGroup("general_opts"); + // create primary items and add them to the group + $leaf = new HiddenPropertyItem("structure_or_data"); + $generalOptions->addProperty($leaf); + // add the main group to the root group + $exportSpecificOptions->addProperty($generalOptions); + + // set the options for the export plugin property item + $exportPluginProperties->setOptions($exportSpecificOptions); + $this->properties = $exportPluginProperties; + } + + /** + * Outputs export header + * + * @return bool Whether it succeeded + */ + public function exportHeader() + { + Export::outputHandler( + '%YAML 1.1' . $GLOBALS['crlf'] . '---' . $GLOBALS['crlf'] + ); + + return true; + } + + /** + * Outputs export footer + * + * @return bool Whether it succeeded + */ + public function exportFooter() + { + Export::outputHandler('...' . $GLOBALS['crlf']); + + return true; + } + + /** + * Outputs database header + * + * @param string $db Database name + * @param string $db_alias Aliases of db + * + * @return bool Whether it succeeded + */ + public function exportDBHeader($db, $db_alias = '') + { + return true; + } + + /** + * Outputs database footer + * + * @param string $db Database name + * + * @return bool Whether it succeeded + */ + public function exportDBFooter($db) + { + return true; + } + + /** + * Outputs CREATE DATABASE statement + * + * @param string $db Database name + * @param string $export_type 'server', 'database', 'table' + * @param string $db_alias Aliases of db + * + * @return bool Whether it succeeded + */ + public function exportDBCreate($db, $export_type, $db_alias = '') + { + return true; + } + + /** + * Outputs the content of a table in JSON format + * + * @param string $db database name + * @param string $table table name + * @param string $crlf the end of line sequence + * @param string $error_url the url to go back in case of error + * @param string $sql_query SQL query for obtaining data + * @param array $aliases Aliases of db/table/columns + * + * @return bool Whether it succeeded + */ + public function exportData( + $db, + $table, + $crlf, + $error_url, + $sql_query, + array $aliases = array() + ) { + $db_alias = $db; + $table_alias = $table; + $this->initAlias($aliases, $db_alias, $table_alias); + $result = $GLOBALS['dbi']->query( + $sql_query, + DatabaseInterface::CONNECT_USER, + DatabaseInterface::QUERY_UNBUFFERED + ); + + $columns_cnt = $GLOBALS['dbi']->numFields($result); + $columns = array(); + for ($i = 0; $i < $columns_cnt; $i++) { + $col_as = $GLOBALS['dbi']->fieldName($result, $i); + if (!empty($aliases[$db]['tables'][$table]['columns'][$col_as])) { + $col_as = $aliases[$db]['tables'][$table]['columns'][$col_as]; + } + $columns[$i] = stripslashes($col_as); + } + + $buffer = ''; + $record_cnt = 0; + while ($record = $GLOBALS['dbi']->fetchRow($result)) { + $record_cnt++; + + // Output table name as comment if this is the first record of the table + if ($record_cnt == 1) { + $buffer = '# ' . $db_alias . '.' . $table_alias . $crlf; + $buffer .= '-' . $crlf; + } else { + $buffer = '-' . $crlf; + } + + for ($i = 0; $i < $columns_cnt; $i++) { + if (!isset($record[$i])) { + continue; + } + + if (is_null($record[$i])) { + $buffer .= ' ' . $columns[$i] . ': null' . $crlf; + continue; + } + + if (is_numeric($record[$i])) { + $buffer .= ' ' . $columns[$i] . ': ' . $record[$i] . $crlf; + continue; + } + + $record[$i] = str_replace( + array('\\', '"', "\n", "\r"), + array('\\\\', '\"', '\n', '\r'), + $record[$i] + ); + $buffer .= ' ' . $columns[$i] . ': "' . $record[$i] . '"' . $crlf; + } + + if (!Export::outputHandler($buffer)) { + return false; + } + } + $GLOBALS['dbi']->freeResult($result); + + return true; + } // end getTableYAML +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Export/Helpers/Pdf.php b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Export/Helpers/Pdf.php new file mode 100644 index 00000000..4677327d --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Export/Helpers/Pdf.php @@ -0,0 +1,851 @@ +relation = new Relation(); + } + + /** + * Add page if needed. + * + * @param float|int $h cell height. Default value: 0 + * @param mixed $y starting y position, leave empty for current + * position + * @param boolean $addpage if true add a page, otherwise only return + * the true/false state + * + * @return boolean true in case of page break, false otherwise. + */ + public function checkPageBreak($h = 0, $y = '', $addpage = true) + { + if (TCPDF_STATIC::empty_string($y)) { + $y = $this->y; + } + $current_page = $this->page; + if ((($y + $h) > $this->PageBreakTrigger) + && (!$this->InFooter) + && ($this->AcceptPageBreak()) + ) { + if ($addpage) { + //Automatic page break + $x = $this->x; + $this->AddPage($this->CurOrientation); + $this->y = $this->dataY; + $oldpage = $this->page - 1; + + $this_page_orm = $this->pagedim[$this->page]['orm']; + $old_page_orm = $this->pagedim[$oldpage]['orm']; + $this_page_olm = $this->pagedim[$this->page]['olm']; + $old_page_olm = $this->pagedim[$oldpage]['olm']; + if ($this->rtl) { + if ($this_page_orm != $old_page_orm) { + $this->x = $x - ($this_page_orm - $old_page_orm); + } else { + $this->x = $x; + } + } else { + if ($this_page_olm != $old_page_olm) { + $this->x = $x + ($this_page_olm - $old_page_olm); + } else { + $this->x = $x; + } + } + } + + return true; + } + + // account for columns mode + return $current_page != $this->page; + } + + /** + * This method is used to render the page header. + * + * @return void + */ + // @codingStandardsIgnoreLine + public function Header() + { + global $maxY; + // We don't want automatic page breaks while generating header + // as this can lead to infinite recursion as auto generated page + // will want header as well causing another page break + // FIXME: Better approach might be to try to compact the content + $this->SetAutoPageBreak(false); + // Check if header for this page already exists + if (!isset($this->headerset[$this->page])) { + $fullwidth = 0; + foreach ($this->tablewidths as $width) { + $fullwidth += $width; + } + $this->SetY(($this->tMargin) - ($this->FontSizePt / $this->k) * 5); + $this->cellFontSize = $this->FontSizePt; + $this->SetFont( + PdfLib::PMA_PDF_FONT, + '', + ($this->titleFontSize + ? $this->titleFontSize + : $this->FontSizePt) + ); + $this->Cell(0, $this->FontSizePt, $this->titleText, 0, 1, 'C'); + $this->SetFont(PdfLib::PMA_PDF_FONT, '', $this->cellFontSize); + $this->SetY(($this->tMargin) - ($this->FontSizePt / $this->k) * 2.5); + $this->Cell( + 0, + $this->FontSizePt, + __('Database:') . ' ' . $this->dbAlias . ', ' + . __('Table:') . ' ' . $this->tableAlias . ', ' + . __('Purpose:') . ' ' . $this->purpose, + 0, + 1, + 'L' + ); + $l = ($this->lMargin); + foreach ($this->colTitles as $col => $txt) { + $this->SetXY($l, ($this->tMargin)); + $this->MultiCell( + $this->tablewidths[$col], + $this->FontSizePt, + $txt + ); + $l += $this->tablewidths[$col]; + $maxY = ($maxY < $this->getY()) ? $this->getY() : $maxY; + } + $this->SetXY($this->lMargin, $this->tMargin); + $this->setFillColor(200, 200, 200); + $l = ($this->lMargin); + foreach ($this->colTitles as $col => $txt) { + $this->SetXY($l, $this->tMargin); + $this->cell( + $this->tablewidths[$col], + $maxY - ($this->tMargin), + '', + 1, + 0, + 'L', + 1 + ); + $this->SetXY($l, $this->tMargin); + $this->MultiCell( + $this->tablewidths[$col], + $this->FontSizePt, + $txt, + 0, + 'C' + ); + $l += $this->tablewidths[$col]; + } + $this->setFillColor(255, 255, 255); + // set headerset + $this->headerset[$this->page] = 1; + } + + $this->dataY = $maxY; + $this->SetAutoPageBreak(true); + } + + /** + * Generate table + * + * @param int $lineheight Height of line + * + * @return void + */ + public function morepagestable($lineheight = 8) + { + // some things to set and 'remember' + $l = $this->lMargin; + $startheight = $h = $this->dataY; + $startpage = $currpage = $this->page; + + // calculate the whole width + $fullwidth = 0; + foreach ($this->tablewidths as $width) { + $fullwidth += $width; + } + + // Now let's start to write the table + $row = 0; + $tmpheight = array(); + $maxpage = $this->page; + + while ($data = $GLOBALS['dbi']->fetchRow($this->results)) { + $this->page = $currpage; + // write the horizontal borders + $this->Line($l, $h, $fullwidth + $l, $h); + // write the content and remember the height of the highest col + foreach ($data as $col => $txt) { + $this->page = $currpage; + $this->SetXY($l, $h); + if ($this->tablewidths[$col] > 0) { + $this->MultiCell( + $this->tablewidths[$col], + $lineheight, + $txt, + 0, + $this->colAlign[$col] + ); + $l += $this->tablewidths[$col]; + } + + if (!isset($tmpheight[$row . '-' . $this->page])) { + $tmpheight[$row . '-' . $this->page] = 0; + } + if ($tmpheight[$row . '-' . $this->page] < $this->GetY()) { + $tmpheight[$row . '-' . $this->page] = $this->GetY(); + } + if ($this->page > $maxpage) { + $maxpage = $this->page; + } + unset($data[$col]); + } + + // get the height we were in the last used page + $h = $tmpheight[$row . '-' . $maxpage]; + // set the "pointer" to the left margin + $l = $this->lMargin; + // set the $currpage to the last page + $currpage = $maxpage; + unset($data[$row]); + $row++; + } + // draw the borders + // we start adding a horizontal line on the last page + $this->page = $maxpage; + $this->Line($l, $h, $fullwidth + $l, $h); + // now we start at the top of the document and walk down + for ($i = $startpage; $i <= $maxpage; $i++) { + $this->page = $i; + $l = $this->lMargin; + $t = ($i == $startpage) ? $startheight : $this->tMargin; + $lh = ($i == $maxpage) ? $h : $this->h - $this->bMargin; + $this->Line($l, $t, $l, $lh); + foreach ($this->tablewidths as $width) { + $l += $width; + $this->Line($l, $t, $l, $lh); + } + } + // set it to the last page, if not it'll cause some problems + $this->page = $maxpage; + } + + /** + * Sets a set of attributes. + * + * @param array $attr array containing the attributes + * + * @return void + */ + public function setAttributes(array $attr = array()) + { + foreach ($attr as $key => $val) { + $this->$key = $val; + } + } + + /** + * Defines the top margin. + * The method can be called before creating the first page. + * + * @param float $topMargin the margin + * + * @return void + */ + public function setTopMargin($topMargin) + { + $this->tMargin = $topMargin; + } + + /** + * Prints triggers + * + * @param string $db database name + * @param string $table table name + * + * @return void + */ + public function getTriggers($db, $table) + { + $i = 0; + $triggers = $GLOBALS['dbi']->getTriggers($db, $table); + foreach ($triggers as $trigger) { + $i++; + break; + } + if ($i == 0) { + return; //prevents printing blank trigger list for any table + } + + unset($this->tablewidths); + unset($this->colTitles); + unset($this->titleWidth); + unset($this->colFits); + unset($this->display_column); + unset($this->colAlign); + + /** + * Making table heading + * Keeping column width constant + */ + $this->colTitles[0] = __('Name'); + $this->tablewidths[0] = 90; + $this->colTitles[1] = __('Time'); + $this->tablewidths[1] = 80; + $this->colTitles[2] = __('Event'); + $this->tablewidths[2] = 40; + $this->colTitles[3] = __('Definition'); + $this->tablewidths[3] = 240; + + for ($columns_cnt = 0; $columns_cnt < 4; $columns_cnt++) { + $this->colAlign[$columns_cnt] = 'L'; + $this->display_column[$columns_cnt] = true; + } + + // Starting to fill table with required info + + $this->setY($this->tMargin); + $this->AddPage(); + $this->SetFont(PdfLib::PMA_PDF_FONT, '', 9); + + $l = $this->lMargin; + $startheight = $h = $this->dataY; + $startpage = $currpage = $this->page; + + // calculate the whole width + $fullwidth = 0; + foreach ($this->tablewidths as $width) { + $fullwidth += $width; + } + + $row = 0; + $tmpheight = array(); + $maxpage = $this->page; + $data = array(); + + $triggers = $GLOBALS['dbi']->getTriggers($db, $table); + + foreach ($triggers as $trigger) { + $data[] = $trigger['name']; + $data[] = $trigger['action_timing']; + $data[] = $trigger['event_manipulation']; + $data[] = $trigger['definition']; + $this->page = $currpage; + // write the horizontal borders + $this->Line($l, $h, $fullwidth + $l, $h); + // write the content and remember the height of the highest col + foreach ($data as $col => $txt) { + $this->page = $currpage; + $this->SetXY($l, $h); + if ($this->tablewidths[$col] > 0) { + $this->MultiCell( + $this->tablewidths[$col], + $this->FontSizePt, + $txt, + 0, + $this->colAlign[$col] + ); + $l += $this->tablewidths[$col]; + } + + if (!isset($tmpheight[$row . '-' . $this->page])) { + $tmpheight[$row . '-' . $this->page] = 0; + } + if ($tmpheight[$row . '-' . $this->page] < $this->GetY()) { + $tmpheight[$row . '-' . $this->page] = $this->GetY(); + } + if ($this->page > $maxpage) { + $maxpage = $this->page; + } + } + // get the height we were in the last used page + $h = $tmpheight[$row . '-' . $maxpage]; + // set the "pointer" to the left margin + $l = $this->lMargin; + // set the $currpage to the last page + $currpage = $maxpage; + unset($data); + $row++; + } + // draw the borders + // we start adding a horizontal line on the last page + $this->page = $maxpage; + $this->Line($l, $h, $fullwidth + $l, $h); + // now we start at the top of the document and walk down + for ($i = $startpage; $i <= $maxpage; $i++) { + $this->page = $i; + $l = $this->lMargin; + $t = ($i == $startpage) ? $startheight : $this->tMargin; + $lh = ($i == $maxpage) ? $h : $this->h - $this->bMargin; + $this->Line($l, $t, $l, $lh); + foreach ($this->tablewidths as $width) { + $l += $width; + $this->Line($l, $t, $l, $lh); + } + } + // set it to the last page, if not it'll cause some problems + $this->page = $maxpage; + } + + /** + * Print $table's CREATE definition + * + * @param string $db the database name + * @param string $table the table name + * @param bool $do_relation whether to include relation comments + * @param bool $do_comments whether to include the pmadb-style column + * comments as comments in the structure; + * this is deprecated but the parameter is + * left here because export.php calls + * PMA_exportStructure() also for other + * export types which use this parameter + * @param bool $do_mime whether to include mime comments + * @param bool $view whether we're handling a view + * @param array $aliases aliases of db/table/columns + * + * @return void + */ + public function getTableDef( + $db, + $table, + $do_relation, + $do_comments, + $do_mime, + $view = false, + array $aliases = array() + ) { + // set $cfgRelation here, because there is a chance that it's modified + // since the class initialization + global $cfgRelation; + + unset($this->tablewidths); + unset($this->colTitles); + unset($this->titleWidth); + unset($this->colFits); + unset($this->display_column); + unset($this->colAlign); + + /** + * Gets fields properties + */ + $GLOBALS['dbi']->selectDb($db); + + /** + * All these three checks do_relation, do_comment and do_mime is + * not required. As presently all are set true by default. + * But when, methods to take user input will be developed, + * it will be of use + */ + // Check if we can use Relations + if ($do_relation) { + // Find which tables are related with the current one and write it in + // an array + $res_rel = $this->relation->getForeigners($db, $table); + $have_rel = !empty($res_rel); + } else { + $have_rel = false; + } // end if + + //column count and table heading + + $this->colTitles[0] = __('Column'); + $this->tablewidths[0] = 90; + $this->colTitles[1] = __('Type'); + $this->tablewidths[1] = 80; + $this->colTitles[2] = __('Null'); + $this->tablewidths[2] = 40; + $this->colTitles[3] = __('Default'); + $this->tablewidths[3] = 120; + + for ($columns_cnt = 0; $columns_cnt < 4; $columns_cnt++) { + $this->colAlign[$columns_cnt] = 'L'; + $this->display_column[$columns_cnt] = true; + } + + if ($do_relation && $have_rel) { + $this->colTitles[$columns_cnt] = __('Links to'); + $this->display_column[$columns_cnt] = true; + $this->colAlign[$columns_cnt] = 'L'; + $this->tablewidths[$columns_cnt] = 120; + $columns_cnt++; + } + if ($do_comments /*&& $cfgRelation['commwork']*/) { + $this->colTitles[$columns_cnt] = __('Comments'); + $this->display_column[$columns_cnt] = true; + $this->colAlign[$columns_cnt] = 'L'; + $this->tablewidths[$columns_cnt] = 120; + $columns_cnt++; + } + if ($do_mime && $cfgRelation['mimework']) { + $this->colTitles[$columns_cnt] = __('MIME'); + $this->display_column[$columns_cnt] = true; + $this->colAlign[$columns_cnt] = 'L'; + $this->tablewidths[$columns_cnt] = 120; + $columns_cnt++; + } + + // Starting to fill table with required info + + $this->setY($this->tMargin); + $this->AddPage(); + $this->SetFont(PdfLib::PMA_PDF_FONT, '', 9); + + // Now let's start to write the table structure + + if ($do_comments) { + $comments = $this->relation->getComments($db, $table); + } + if ($do_mime && $cfgRelation['mimework']) { + $mime_map = Transformations::getMIME($db, $table, true); + } + + $columns = $GLOBALS['dbi']->getColumns($db, $table); + /** + * Get the unique keys in the table. + * Presently, this information is not used. We will have to find out + * way of displaying it. + */ + $unique_keys = array(); + $keys = $GLOBALS['dbi']->getTableIndexes($db, $table); + foreach ($keys as $key) { + if ($key['Non_unique'] == 0) { + $unique_keys[] = $key['Column_name']; + } + } + + // some things to set and 'remember' + $l = $this->lMargin; + $startheight = $h = $this->dataY; + $startpage = $currpage = $this->page; + // calculate the whole width + $fullwidth = 0; + foreach ($this->tablewidths as $width) { + $fullwidth += $width; + } + + $row = 0; + $tmpheight = array(); + $maxpage = $this->page; + $data = array(); + + // fun begin + foreach ($columns as $column) { + $extracted_columnspec + = Util::extractColumnSpec($column['Type']); + + $type = $extracted_columnspec['print_type']; + if (empty($type)) { + $type = ' '; + } + + if (!isset($column['Default'])) { + if ($column['Null'] != 'NO') { + $column['Default'] = 'NULL'; + } + } + $data [] = $column['Field']; + $data [] = $type; + $data [] = ($column['Null'] == '' || $column['Null'] == 'NO') + ? 'No' + : 'Yes'; + $data [] = isset($column['Default']) ? $column['Default'] : ''; + + $field_name = $column['Field']; + + if ($do_relation && $have_rel) { + $data [] = isset($res_rel[$field_name]) + ? $res_rel[$field_name]['foreign_table'] + . ' (' . $res_rel[$field_name]['foreign_field'] + . ')' + : ''; + } + if ($do_comments) { + $data [] = isset($comments[$field_name]) + ? $comments[$field_name] + : ''; + } + if ($do_mime) { + $data [] = isset($mime_map[$field_name]) + ? $mime_map[$field_name]['mimetype'] + : ''; + } + + $this->page = $currpage; + // write the horizontal borders + $this->Line($l, $h, $fullwidth + $l, $h); + // write the content and remember the height of the highest col + foreach ($data as $col => $txt) { + $this->page = $currpage; + $this->SetXY($l, $h); + if ($this->tablewidths[$col] > 0) { + $this->MultiCell( + $this->tablewidths[$col], + $this->FontSizePt, + $txt, + 0, + $this->colAlign[$col] + ); + $l += $this->tablewidths[$col]; + } + + if (!isset($tmpheight[$row . '-' . $this->page])) { + $tmpheight[$row . '-' . $this->page] = 0; + } + if ($tmpheight[$row . '-' . $this->page] < $this->GetY()) { + $tmpheight[$row . '-' . $this->page] = $this->GetY(); + } + if ($this->page > $maxpage) { + $maxpage = $this->page; + } + } + + // get the height we were in the last used page + $h = $tmpheight[$row . '-' . $maxpage]; + // set the "pointer" to the left margin + $l = $this->lMargin; + // set the $currpage to the last page + $currpage = $maxpage; + unset($data); + $row++; + } + // draw the borders + // we start adding a horizontal line on the last page + $this->page = $maxpage; + $this->Line($l, $h, $fullwidth + $l, $h); + // now we start at the top of the document and walk down + for ($i = $startpage; $i <= $maxpage; $i++) { + $this->page = $i; + $l = $this->lMargin; + $t = ($i == $startpage) ? $startheight : $this->tMargin; + $lh = ($i == $maxpage) ? $h : $this->h - $this->bMargin; + $this->Line($l, $t, $l, $lh); + foreach ($this->tablewidths as $width) { + $l += $width; + $this->Line($l, $t, $l, $lh); + } + } + // set it to the last page, if not it'll cause some problems + $this->page = $maxpage; + } + + /** + * MySQL report + * + * @param string $query Query to execute + * + * @return void + */ + public function mysqlReport($query) + { + unset($this->tablewidths); + unset($this->colTitles); + unset($this->titleWidth); + unset($this->colFits); + unset($this->display_column); + unset($this->colAlign); + + /** + * Pass 1 for column widths + */ + $this->results = $GLOBALS['dbi']->query( + $query, + DatabaseInterface::CONNECT_USER, + DatabaseInterface::QUERY_UNBUFFERED + ); + $this->numFields = $GLOBALS['dbi']->numFields($this->results); + $this->fields = $GLOBALS['dbi']->getFieldsMeta($this->results); + + // sColWidth = starting col width (an average size width) + $availableWidth = $this->w - $this->lMargin - $this->rMargin; + $this->sColWidth = $availableWidth / $this->numFields; + $totalTitleWidth = 0; + + // loop through results header and set initial + // col widths/ titles/ alignment + // if a col title is less than the starting col width, + // reduce that column size + $colFits = array(); + $titleWidth = array(); + for ($i = 0; $i < $this->numFields; $i++) { + $col_as = $this->fields[$i]->name; + $db = $this->currentDb; + $table = $this->currentTable; + if (!empty($this->aliases[$db]['tables'][$table]['columns'][$col_as])) { + $col_as = $this->aliases[$db]['tables'][$table]['columns'][$col_as]; + } + $stringWidth = $this->getstringwidth($col_as) + 6; + // save the real title's width + $titleWidth[$i] = $stringWidth; + $totalTitleWidth += $stringWidth; + + // set any column titles less than the start width to + // the column title width + if ($stringWidth < $this->sColWidth) { + $colFits[$i] = $stringWidth; + } + $this->colTitles[$i] = $col_as; + $this->display_column[$i] = true; + + switch ($this->fields[$i]->type) { + case 'int': + $this->colAlign[$i] = 'R'; + break; + case 'blob': + case 'tinyblob': + case 'mediumblob': + case 'longblob': + /** + * @todo do not deactivate completely the display + * but show the field's name and [BLOB] + */ + if (stristr($this->fields[$i]->flags, 'BINARY')) { + $this->display_column[$i] = false; + unset($this->colTitles[$i]); + } + $this->colAlign[$i] = 'L'; + break; + default: + $this->colAlign[$i] = 'L'; + } + } + + // title width verification + if ($totalTitleWidth > $availableWidth) { + $adjustingMode = true; + } else { + $adjustingMode = false; + // we have enough space for all the titles at their + // original width so use the true title's width + foreach ($titleWidth as $key => $val) { + $colFits[$key] = $val; + } + } + + // loop through the data; any column whose contents + // is greater than the column size is resized + /** + * @todo force here a LIMIT to avoid reading all rows + */ + while ($row = $GLOBALS['dbi']->fetchRow($this->results)) { + foreach ($colFits as $key => $val) { + $stringWidth = $this->getstringwidth($row[$key]) + 6; + if ($adjustingMode && ($stringWidth > $this->sColWidth)) { + // any column whose data's width is bigger than + // the start width is now discarded + unset($colFits[$key]); + } else { + // if data's width is bigger than the current column width, + // enlarge the column (but avoid enlarging it if the + // data's width is very big) + if ($stringWidth > $val + && $stringWidth < ($this->sColWidth * 3) + ) { + $colFits[$key] = $stringWidth; + } + } + } + } + + $totAlreadyFitted = 0; + foreach ($colFits as $key => $val) { + // set fitted columns to smallest size + $this->tablewidths[$key] = $val; + // to work out how much (if any) space has been freed up + $totAlreadyFitted += $val; + } + + if ($adjustingMode) { + $surplus = (sizeof($colFits) * $this->sColWidth) - $totAlreadyFitted; + $surplusToAdd = $surplus / ($this->numFields - sizeof($colFits)); + } else { + $surplusToAdd = 0; + } + + for ($i = 0; $i < $this->numFields; $i++) { + if (!in_array($i, array_keys($colFits))) { + $this->tablewidths[$i] = $this->sColWidth + $surplusToAdd; + } + if ($this->display_column[$i] == false) { + $this->tablewidths[$i] = 0; + } + } + + ksort($this->tablewidths); + + $GLOBALS['dbi']->freeResult($this->results); + + // Pass 2 + + $this->results = $GLOBALS['dbi']->query( + $query, + DatabaseInterface::CONNECT_USER, + DatabaseInterface::QUERY_UNBUFFERED + ); + $this->setY($this->tMargin); + $this->AddPage(); + $this->SetFont(PdfLib::PMA_PDF_FONT, '', 9); + $this->morepagestable($this->FontSizePt); + $GLOBALS['dbi']->freeResult($this->results); + } // end of mysqlReport function +} // end of Pdf class diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Export/Helpers/TableProperty.php b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Export/Helpers/TableProperty.php new file mode 100644 index 00000000..de8bff20 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Export/Helpers/TableProperty.php @@ -0,0 +1,285 @@ +name = trim($row[0]); + $this->type = trim($row[1]); + $this->nullable = trim($row[2]); + $this->key = trim($row[3]); + $this->defaultValue = trim($row[4]); + $this->ext = trim($row[5]); + } + + /** + * Gets the pure type + * + * @return string type + */ + public function getPureType() + { + $pos = mb_strpos($this->type, "("); + if ($pos > 0) { + return mb_substr($this->type, 0, $pos); + } + return $this->type; + } + + /** + * Tells whether the key is null or not + * + * @return bool true if the key is not null, false otherwise + */ + public function isNotNull() + { + return $this->nullable == "NO" ? "true" : "false"; + } + + /** + * Tells whether the key is unique or not + * + * @return bool true if the key is unique, false otherwise + */ + public function isUnique() + { + return $this->key == "PRI" || $this->key == "UNI" ? "true" : "false"; + } + + /** + * Gets the .NET primitive type + * + * @return string type + */ + public function getDotNetPrimitiveType() + { + if (mb_strpos($this->type, "int") === 0) { + return "int"; + } + if (mb_strpos($this->type, "longtext") === 0) { + return "string"; + } + if (mb_strpos($this->type, "long") === 0) { + return "long"; + } + if (mb_strpos($this->type, "char") === 0) { + return "string"; + } + if (mb_strpos($this->type, "varchar") === 0) { + return "string"; + } + if (mb_strpos($this->type, "text") === 0) { + return "string"; + } + if (mb_strpos($this->type, "tinyint") === 0) { + return "bool"; + } + if (mb_strpos($this->type, "datetime") === 0) { + return "DateTime"; + } + return "unknown"; + } + + /** + * Gets the .NET object type + * + * @return string type + */ + public function getDotNetObjectType() + { + if (mb_strpos($this->type, "int") === 0) { + return "Int32"; + } + if (mb_strpos($this->type, "longtext") === 0) { + return "String"; + } + if (mb_strpos($this->type, "long") === 0) { + return "Long"; + } + if (mb_strpos($this->type, "char") === 0) { + return "String"; + } + if (mb_strpos($this->type, "varchar") === 0) { + return "String"; + } + if (mb_strpos($this->type, "text") === 0) { + return "String"; + } + if (mb_strpos($this->type, "tinyint") === 0) { + return "Boolean"; + } + if (mb_strpos($this->type, "datetime") === 0) { + return "DateTime"; + } + return "Unknown"; + } + + /** + * Gets the index name + * + * @return string containing the name of the index + */ + public function getIndexName() + { + if (strlen($this->key) > 0) { + return "index=\"" + . htmlspecialchars($this->name, ENT_COMPAT, 'UTF-8') + . "\""; + } + return ""; + } + + /** + * Tells whether the key is primary or not + * + * @return bool true if the key is primary, false otherwise + */ + public function isPK() + { + return $this->key == "PRI"; + } + + /** + * Formats a string for C# + * + * @param string $text string to be formatted + * + * @return string formatted text + */ + public function formatCs($text) + { + $text = str_replace( + "#name#", + ExportCodegen::cgMakeIdentifier($this->name, false), + $text + ); + return $this->format($text); + } + + /** + * Formats a string for XML + * + * @param string $text string to be formatted + * + * @return string formatted text + */ + public function formatXml($text) + { + $text = str_replace( + "#name#", + htmlspecialchars($this->name, ENT_COMPAT, 'UTF-8'), + $text + ); + $text = str_replace( + "#indexName#", + $this->getIndexName(), + $text + ); + return $this->format($text); + } + + /** + * Formats a string + * + * @param string $text string to be formatted + * + * @return string formatted text + */ + public function format($text) + { + $text = str_replace( + "#ucfirstName#", + ExportCodegen::cgMakeIdentifier($this->name), + $text + ); + $text = str_replace( + "#dotNetPrimitiveType#", + $this->getDotNetPrimitiveType(), + $text + ); + $text = str_replace( + "#dotNetObjectType#", + $this->getDotNetObjectType(), + $text + ); + $text = str_replace( + "#type#", + $this->getPureType(), + $text + ); + $text = str_replace( + "#notNull#", + $this->isNotNull(), + $text + ); + $text = str_replace( + "#unique#", + $this->isUnique(), + $text + ); + return $text; + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Export/README b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Export/README new file mode 100644 index 00000000..a05dc8a4 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Export/README @@ -0,0 +1,257 @@ +This directory holds export plugins for phpMyAdmin. Any new plugin should +basically follow the structure presented here. Official plugins need to +have str* messages with their definition in language files, but if you build +some plugins for your use, you can directly use texts in plugin. + +setProperties(); + } + + // optional - declare global variables and use getters later + /** + * Initialize the local variables that are used specific for export SQL + * + * @global type $global_variable_name + * [..] + * + * @return void + */ + protected function initSpecificVariables() + { + global $global_variable_name; + $this->_setGlobalVariableName($global_variable_name); + } + + /** + * Sets the export plugin properties. + * Called in the constructor. + * + * @return void + */ + protected function setProperties() + { + $exportPluginProperties = new PhpMyAdmin\Properties\Plugins\ExportPluginProperties(); + $exportPluginProperties->setText('[name]'); // the name of your plug-in + $exportPluginProperties->setExtension('[ext]'); // extension this plug-in can handle + $exportPluginProperties->setOptionsText(__('Options')); + + // create the root group that will be the options field for + // $exportPluginProperties + // this will be shown as "Format specific options" + $exportSpecificOptions = new PhpMyAdmin\Properties\Options\Groups\OptionsPropertyRootGroup( + "Format Specific Options" + ); + + // general options main group + $generalOptions = new PhpMyAdmin\Properties\Options\Groups\OptionsPropertyMainGroup( + "general_opts" + ); + + // optional : + // create primary items and add them to the group + // type - one of the classes listed in libraries/properties/options/items/ + // name - form element name + // text - description in GUI + // size - size of text element + // len - maximal size of input + // values - possible values of the item + $leaf = new PhpMyAdmin\Properties\Options\Items\RadioPropertyItem( + "structure_or_data" + ); + $leaf->setValues( + array( + 'structure' => __('structure'), + 'data' => __('data'), + 'structure_and_data' => __('structure and data') + ) + ); + $generalOptions->addProperty($leaf); + + // add the main group to the root group + $exportSpecificOptions->addProperty($generalOptions); + + // set the options for the export plugin property item + $exportPluginProperties->setOptions($exportSpecificOptions); + $this->properties = $exportPluginProperties; + } + + /** + * Outputs export header + * + * @return bool Whether it succeeded + */ + public function exportHeader () + { + // implementation + return true; + } + + /** + * Outputs export footer + * + * @return bool Whether it succeeded + */ + public function exportFooter () + { + // implementation + return true; + } + + /** + * Outputs database header + * + * @param string $db Database name + * @param string $db_alias Aliases of db + * + * @return bool Whether it succeeded + */ + public function exportDBHeader ($db, $db_alias = '') + { + // implementation + return true; + } + + /** + * Outputs database footer + * + * @param string $db Database name + * + * @return bool Whether it succeeded + */ + public function exportDBFooter ($db) + { + // implementation + return true; + } + + /** + * Outputs CREATE DATABASE statement + * + * @param string $db Database name + * @param string $db_alias Aliases of db + * + * @return bool Whether it succeeded + */ + public function exportDBCreate($db, $db_alias = '') + { + // implementation + return true; + } + + /** + * Outputs the content of a table in [Name] format + * + * @param string $db database name + * @param string $table table name + * @param string $crlf the end of line sequence + * @param string $error_url the url to go back in case of error + * @param string $sql_query SQL query for obtaining data + * @param array $aliases Aliases of db/table/columns + * + * @return bool Whether it succeeded + */ + public function exportData( + $db, $table, $crlf, $error_url, $sql_query, $aliases = array() + ) { + // implementation; + return true; + } + + // optional - implement other methods defined in PhpMyAdmin\Plugins\ExportPlugin.class.php: + // - exportRoutines() + // - exportStructure() + // - getTableDefStandIn() + // - getTriggers() + + // optional - implement other private methods in order to avoid + // having huge methods or avoid duplicate code. Make use of them + // as well as of the getters and setters declared both here + // and in the PhpMyAdmin\Plugins\ExportPlugin class + + + // optional: + /* ~~~~~~~~~~~~~~~~~~~~ Getters and Setters ~~~~~~~~~~~~~~~~~~~~ */ + + + /** + * Getter description + * + * @return type + */ + private function _getMyOptionalVariable() + { + return $this->_myOptionalVariable; + } + + /** + * Setter description + * + * @param type $my_optional_variable description + * + * @return void + */ + private function _setMyOptionalVariable($my_optional_variable) + { + $this->_myOptionalVariable = $my_optional_variable; + } + + /** + * Getter description + * + * @return type + */ + private function _getGlobalVariableName() + { + return $this->_globalVariableName; + } + + /** + * Setter description + * + * @param type $global_variable_name description + * + * @return void + */ + private function _setGlobalVariableName($global_variable_name) + { + $this->_globalVariableName = $global_variable_name; + } +} +?> diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Plugins/ExportPlugin.php b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/ExportPlugin.php new file mode 100644 index 00000000..56710ad7 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/ExportPlugin.php @@ -0,0 +1,378 @@ +relation = new Relation(); + } + + /** + * Outputs export header + * + * @return bool Whether it succeeded + */ + abstract public function exportHeader(); + + /** + * Outputs export footer + * + * @return bool Whether it succeeded + */ + abstract public function exportFooter(); + + /** + * Outputs database header + * + * @param string $db Database name + * @param string $db_alias Aliases of db + * + * @return bool Whether it succeeded + */ + abstract public function exportDBHeader($db, $db_alias = ''); + + /** + * Outputs database footer + * + * @param string $db Database name + * + * @return bool Whether it succeeded + */ + abstract public function exportDBFooter($db); + + /** + * Outputs CREATE DATABASE statement + * + * @param string $db Database name + * @param string $export_type 'server', 'database', 'table' + * @param string $db_alias Aliases of db + * + * @return bool Whether it succeeded + */ + abstract public function exportDBCreate($db, $export_type, $db_alias = ''); + + /** + * Outputs the content of a table + * + * @param string $db database name + * @param string $table table name + * @param string $crlf the end of line sequence + * @param string $error_url the url to go back in case of error + * @param string $sql_query SQL query for obtaining data + * @param array $aliases Aliases of db/table/columns + * + * @return bool Whether it succeeded + */ + abstract public function exportData( + $db, + $table, + $crlf, + $error_url, + $sql_query, + array $aliases = array() + ); + + /** + * The following methods are used in export.php or in db_operations.php, + * but they are not implemented by all export plugins + */ + + /** + * Exports routines (procedures and functions) + * + * @param string $db Database + * @param array $aliases Aliases of db/table/columns + * + * @return bool Whether it succeeded + */ + public function exportRoutines($db, array $aliases = array()) + { + ; + } + + /** + * Exports events + * + * @param string $db Database + * + * @return bool Whether it succeeded + */ + public function exportEvents($db) + { + ; + } + + /** + * Outputs table's structure + * + * @param string $db database name + * @param string $table table name + * @param string $crlf the end of line sequence + * @param string $error_url the url to go back in case of error + * @param string $export_mode 'create_table','triggers','create_view', + * 'stand_in' + * @param string $export_type 'server', 'database', 'table' + * @param bool $relation whether to include relation comments + * @param bool $comments whether to include the pmadb-style column comments + * as comments in the structure; this is deprecated + * but the parameter is left here because export.php + * calls exportStructure() also for other export + * types which use this parameter + * @param bool $mime whether to include mime comments + * @param bool $dates whether to include creation/update/check dates + * @param array $aliases Aliases of db/table/columns + * + * @return bool Whether it succeeded + */ + public function exportStructure( + $db, + $table, + $crlf, + $error_url, + $export_mode, + $export_type, + $relation = false, + $comments = false, + $mime = false, + $dates = false, + array $aliases = array() + ) { + ; + } + + /** + * Exports metadata from Configuration Storage + * + * @param string $db database being exported + * @param string|array $tables table(s) being exported + * @param array $metadataTypes types of metadata to export + * + * @return bool Whether it succeeded + */ + public function exportMetadata( + $db, + $tables, + array $metadataTypes + ) { + ; + } + + /** + * Returns a stand-in CREATE definition to resolve view dependencies + * + * @param string $db the database name + * @param string $view the view name + * @param string $crlf the end of line sequence + * @param array $aliases Aliases of db/table/columns + * + * @return string resulting definition + */ + public function getTableDefStandIn($db, $view, $crlf, $aliases = array()) + { + ; + } + + /** + * Outputs triggers + * + * @param string $db database name + * @param string $table table name + * + * @return string Formatted triggers list + */ + protected function getTriggers($db, $table) + { + ; + } + + /** + * Initialize the specific variables for each export plugin + * + * @return void + */ + protected function initSpecificVariables() + { + ; + } + + /* ~~~~~~~~~~~~~~~~~~~~ Getters and Setters ~~~~~~~~~~~~~~~~~~~~ */ + + /** + * Gets the export specific format plugin properties + * + * @return ExportPluginProperties + */ + public function getProperties() + { + return $this->properties; + } + + /** + * Sets the export plugins properties and is implemented by each export + * plugin + * + * @return void + */ + abstract protected function setProperties(); + + /** + * The following methods are implemented here so that they + * can be used by all export plugin without overriding it. + * Note: If you are creating a export plugin then don't include + * below methods unless you want to override them. + */ + + /** + * Initialize aliases + * + * @param array $aliases Alias information for db/table/column + * @param string &$db the database + * @param string &$table the table + * + * @return void + */ + public function initAlias($aliases, &$db, &$table = null) + { + if (!empty($aliases[$db]['tables'][$table]['alias'])) { + $table = $aliases[$db]['tables'][$table]['alias']; + } + if (!empty($aliases[$db]['alias'])) { + $db = $aliases[$db]['alias']; + } + } + + /** + * Search for alias of a identifier. + * + * @param array $aliases Alias information for db/table/column + * @param string $id the identifier to be searched + * @param string $type db/tbl/col or any combination of them + * representing what to be searched + * @param string $db the database in which search is to be done + * @param string $tbl the table in which search is to be done + * + * @return string alias of the identifier if found or '' + */ + public function getAlias(array $aliases, $id, $type = 'dbtblcol', $db = '', $tbl = '') + { + if (!empty($db) && isset($aliases[$db])) { + $aliases = array( + $db => $aliases[$db], + ); + } + // search each database + foreach ($aliases as $db_key => $db) { + // check if id is database and has alias + if (stristr($type, 'db') !== false + && $db_key === $id + && !empty($db['alias']) + ) { + return $db['alias']; + } + if (empty($db['tables'])) { + continue; + } + if (!empty($tbl) && isset($db['tables'][$tbl])) { + $db['tables'] = array( + $tbl => $db['tables'][$tbl], + ); + } + // search each of its tables + foreach ($db['tables'] as $table_key => $table) { + // check if id is table and has alias + if (stristr($type, 'tbl') !== false + && $table_key === $id + && !empty($table['alias']) + ) { + return $table['alias']; + } + if (empty($table['columns'])) { + continue; + } + // search each of its columns + foreach ($table['columns'] as $col_key => $col) { + // check if id is column + if (stristr($type, 'col') !== false + && $col_key === $id + && !empty($col) + ) { + return $col; + } + } + } + } + + return ''; + } + + /** + * Gives the relation string and + * also substitutes with alias if required + * in this format: + * [Foreign Table] ([Foreign Field]) + * + * @param array $res_rel the foreigners array + * @param string $field_name the field name + * @param string $db the field name + * @param array $aliases Alias information for db/table/column + * + * @return string the Relation string + */ + public function getRelationString( + array $res_rel, + $field_name, + $db, + array $aliases = array() + ) { + $relation = ''; + $foreigner = $this->relation->searchColumnInForeigners($res_rel, $field_name); + if ($foreigner) { + $ftable = $foreigner['foreign_table']; + $ffield = $foreigner['foreign_field']; + if (!empty($aliases[$db]['tables'][$ftable]['columns'][$ffield])) { + $ffield = $aliases[$db]['tables'][$ftable]['columns'][$ffield]; + } + if (!empty($aliases[$db]['tables'][$ftable]['alias'])) { + $ftable = $aliases[$db]['tables'][$ftable]['alias']; + } + $relation = $ftable . ' (' . $ffield . ')'; + } + + return $relation; + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Plugins/IOTransformationsPlugin.php b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/IOTransformationsPlugin.php new file mode 100644 index 00000000..084923dd --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/IOTransformationsPlugin.php @@ -0,0 +1,96 @@ +error; + } + + /** + * Returns the success status + * + * @return bool + */ + public function isSuccess() + { + return $this->success; + } + + /** + * Resets the object properties + * + * @return void + */ + public function reset() + { + $this->success = true; + $this->error = ''; + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Import/AbstractImportCsv.php b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Import/AbstractImportCsv.php new file mode 100644 index 00000000..25cdcbc9 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Import/AbstractImportCsv.php @@ -0,0 +1,92 @@ +setOptionsText(__('Options')); + + // create the root group that will be the options field for + // $importPluginProperties + // this will be shown as "Format specific options" + $importSpecificOptions = new OptionsPropertyRootGroup( + "Format Specific Options" + ); + + // general options main group + $generalOptions = new OptionsPropertyMainGroup("general_opts"); + + // create common items and add them to the group + $leaf = new BoolPropertyItem( + "replace", + __( + 'Update data when duplicate keys found on import (add ON DUPLICATE ' + . 'KEY UPDATE)' + ) + ); + $generalOptions->addProperty($leaf); + $leaf = new TextPropertyItem( + "terminated", + __('Columns separated with:') + ); + $leaf->setSize(2); + $generalOptions->addProperty($leaf); + $leaf = new TextPropertyItem( + "enclosed", + __('Columns enclosed with:') + ); + $leaf->setSize(2); + $leaf->setLen(2); + $generalOptions->addProperty($leaf); + $leaf = new TextPropertyItem( + "escaped", + __('Columns escaped with:') + ); + $leaf->setSize(2); + $leaf->setLen(2); + $generalOptions->addProperty($leaf); + $leaf = new TextPropertyItem( + "new_line", + __('Lines terminated with:') + ); + $leaf->setSize(2); + $generalOptions->addProperty($leaf); + + // add the main group to the root group + $importSpecificOptions->addProperty($generalOptions); + + // set the options for the import plugin property item + $importPluginProperties->setOptions($importSpecificOptions); + $this->properties = $importPluginProperties; + + return $generalOptions; + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Import/ImportCsv.php b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Import/ImportCsv.php new file mode 100644 index 00000000..b68fcea0 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Import/ImportCsv.php @@ -0,0 +1,718 @@ +setProperties(); + } + + /** + * Sets the import plugin properties. + * Called in the constructor. + * + * @return void + */ + protected function setProperties() + { + $this->_setAnalyze(false); + + if ($GLOBALS['plugin_param'] !== 'table') { + $this->_setAnalyze(true); + } + + $generalOptions = parent::setProperties(); + $this->properties->setText('CSV'); + $this->properties->setExtension('csv'); + + if ($GLOBALS['plugin_param'] !== 'table') { + $leaf = new BoolPropertyItem( + "col_names", + __( + 'The first line of the file contains the table column names' + . ' (if this is unchecked, the first line will become part' + . ' of the data)' + ) + ); + $generalOptions->addProperty($leaf); + } else { + $hint = new Message( + __( + 'If the data in each row of the file is not' + . ' in the same order as in the database, list the corresponding' + . ' column names here. Column names must be separated by commas' + . ' and not enclosed in quotations.' + ) + ); + $leaf = new TextPropertyItem( + "columns", + __('Column names: ') . Util::showHint($hint) + ); + $generalOptions->addProperty($leaf); + } + + $leaf = new BoolPropertyItem( + "ignore", + __('Do not abort on INSERT error') + ); + $generalOptions->addProperty($leaf); + } + + /** + * Handles the whole import logic + * + * @param array &$sql_data 2-element array with sql data + * + * @return void + */ + public function doImport(array &$sql_data = array()) + { + global $db, $table, $csv_terminated, $csv_enclosed, $csv_escaped, + $csv_new_line, $csv_columns, $err_url; + // $csv_replace and $csv_ignore should have been here, + // but we use directly from $_POST + global $error, $timeout_passed, $finished, $message; + + $replacements = array( + '\\n' => "\n", + '\\t' => "\t", + '\\r' => "\r", + ); + $csv_terminated = strtr($csv_terminated, $replacements); + $csv_enclosed = strtr($csv_enclosed, $replacements); + $csv_escaped = strtr($csv_escaped, $replacements); + $csv_new_line = strtr($csv_new_line, $replacements); + + $param_error = false; + if (strlen($csv_terminated) === 0) { + $message = Message::error( + __('Invalid parameter for CSV import: %s') + ); + $message->addParam(__('Columns terminated with')); + $error = true; + $param_error = true; + // The default dialog of MS Excel when generating a CSV produces a + // semi-colon-separated file with no chance of specifying the + // enclosing character. Thus, users who want to import this file + // tend to remove the enclosing character on the Import dialog. + // I could not find a test case where having no enclosing characters + // confuses this script. + // But the parser won't work correctly with strings so we allow just + // one character. + } elseif (mb_strlen($csv_enclosed) > 1) { + $message = Message::error( + __('Invalid parameter for CSV import: %s') + ); + $message->addParam(__('Columns enclosed with')); + $error = true; + $param_error = true; + // I could not find a test case where having no escaping characters + // confuses this script. + // But the parser won't work correctly with strings so we allow just + // one character. + } elseif (mb_strlen($csv_escaped) > 1) { + $message = Message::error( + __('Invalid parameter for CSV import: %s') + ); + $message->addParam(__('Columns escaped with')); + $error = true; + $param_error = true; + } elseif (mb_strlen($csv_new_line) != 1 + && $csv_new_line != 'auto' + ) { + $message = Message::error( + __('Invalid parameter for CSV import: %s') + ); + $message->addParam(__('Lines terminated with')); + $error = true; + $param_error = true; + } + + // If there is an error in the parameters entered, + // indicate that immediately. + if ($param_error) { + Util::mysqlDie( + $message->getMessage(), + '', + false, + $err_url + ); + } + + $buffer = ''; + $required_fields = 0; + + if (!$this->_getAnalyze()) { + $sql_template = 'INSERT'; + if (isset($_POST['csv_ignore'])) { + $sql_template .= ' IGNORE'; + } + $sql_template .= ' INTO ' . Util::backquote($table); + + $tmp_fields = $GLOBALS['dbi']->getColumns($db, $table); + + if (empty($csv_columns)) { + $fields = $tmp_fields; + } else { + $sql_template .= ' ('; + $fields = array(); + $tmp = preg_split('/,( ?)/', $csv_columns); + foreach ($tmp as $key => $val) { + if (count($fields) > 0) { + $sql_template .= ', '; + } + /* Trim also `, if user already included backquoted fields */ + $val = trim($val, " \t\r\n\0\x0B`"); + $found = false; + foreach ($tmp_fields as $field) { + if ($field['Field'] == $val) { + $found = true; + break; + } + } + if (!$found) { + $message = Message::error( + __( + 'Invalid column (%s) specified! Ensure that columns' + . ' names are spelled correctly, separated by commas' + . ', and not enclosed in quotes.' + ) + ); + $message->addParam($val); + $error = true; + break; + } + $fields[] = $field; + $sql_template .= Util::backquote($val); + } + $sql_template .= ') '; + } + + $required_fields = count($fields); + + $sql_template .= ' VALUES ('; + } + + // Defaults for parser + $i = 0; + $len = 0; + $lastlen = null; + $line = 1; + $lasti = -1; + $values = array(); + $csv_finish = false; + + $tempRow = array(); + $rows = array(); + $col_names = array(); + $tables = array(); + + $col_count = 0; + $max_cols = 0; + $csv_terminated_len = mb_strlen($csv_terminated); + while (!($finished && $i >= $len) && !$error && !$timeout_passed) { + $data = Import::getNextChunk(); + if ($data === false) { + // subtract data we didn't handle yet and stop processing + $GLOBALS['offset'] -= strlen($buffer); + break; + } elseif ($data === true) { + // Handle rest of buffer + } else { + // Append new data to buffer + $buffer .= $data; + unset($data); + + // Force a trailing new line at EOF to prevent parsing problems + if ($finished && $buffer) { + $finalch = mb_substr($buffer, -1); + if ($csv_new_line == 'auto' + && $finalch != "\r" + && $finalch != "\n" + ) { + $buffer .= "\n"; + } elseif ($csv_new_line != 'auto' + && $finalch != $csv_new_line + ) { + $buffer .= $csv_new_line; + } + } + + // Do not parse string when we're not at the end + // and don't have new line inside + if (($csv_new_line == 'auto' + && mb_strpos($buffer, "\r") === false + && mb_strpos($buffer, "\n") === false) + || ($csv_new_line != 'auto' + && mb_strpos($buffer, $csv_new_line) === false) + ) { + continue; + } + } + + // Current length of our buffer + $len = mb_strlen($buffer); + // Currently parsed char + + $ch = mb_substr($buffer, $i, 1); + if ($csv_terminated_len > 1 && $ch == $csv_terminated[0]) { + $ch = $this->readCsvTerminatedString( + $buffer, + $ch, + $i, + $csv_terminated_len + ); + $i += $csv_terminated_len - 1; + } + while ($i < $len) { + // Deadlock protection + if ($lasti == $i && $lastlen == $len) { + $message = Message::error( + __('Invalid format of CSV input on line %d.') + ); + $message->addParam($line); + $error = true; + break; + } + $lasti = $i; + $lastlen = $len; + + // This can happen with auto EOL and \r at the end of buffer + if (!$csv_finish) { + // Grab empty field + if ($ch == $csv_terminated) { + if ($i == $len - 1) { + break; + } + $values[] = ''; + $i++; + $ch = mb_substr($buffer, $i, 1); + if ($csv_terminated_len > 1 && $ch == $csv_terminated[0]) { + $ch = $this->readCsvTerminatedString( + $buffer, + $ch, + $i, + $csv_terminated_len + ); + $i += $csv_terminated_len - 1; + } + continue; + } + + // Grab one field + $fallbacki = $i; + if ($ch == $csv_enclosed) { + if ($i == $len - 1) { + break; + } + $need_end = true; + $i++; + $ch = mb_substr($buffer, $i, 1); + if ($csv_terminated_len > 1 && $ch == $csv_terminated[0]) { + $ch = $this->readCsvTerminatedString( + $buffer, + $ch, + $i, + $csv_terminated_len + ); + $i += $csv_terminated_len - 1; + } + } else { + $need_end = false; + } + $fail = false; + $value = ''; + while (($need_end + && ($ch != $csv_enclosed + || $csv_enclosed == $csv_escaped)) + || (!$need_end + && !($ch == $csv_terminated + || $ch == $csv_new_line + || ($csv_new_line == 'auto' + && ($ch == "\r" || $ch == "\n")))) + ) { + if ($ch == $csv_escaped) { + if ($i == $len - 1) { + $fail = true; + break; + } + $i++; + $ch = mb_substr($buffer, $i, 1); + if ($csv_terminated_len > 1 + && $ch == $csv_terminated[0] + ) { + $ch = $this->readCsvTerminatedString( + $buffer, + $ch, + $i, + $csv_terminated_len + ); + $i += $csv_terminated_len - 1; + } + if ($csv_enclosed == $csv_escaped + && ($ch == $csv_terminated + || $ch == $csv_new_line + || ($csv_new_line == 'auto' + && ($ch == "\r" || $ch == "\n"))) + ) { + break; + } + } + $value .= $ch; + if ($i == $len - 1) { + if (!$finished) { + $fail = true; + } + break; + } + $i++; + $ch = mb_substr($buffer, $i, 1); + if ($csv_terminated_len > 1 && $ch == $csv_terminated[0]) { + $ch = $this->readCsvTerminatedString( + $buffer, + $ch, + $i, + $csv_terminated_len + ); + $i += $csv_terminated_len - 1; + } + } + + // unquoted NULL string + if (false === $need_end && $value === 'NULL') { + $value = null; + } + + if ($fail) { + $i = $fallbacki; + $ch = mb_substr($buffer, $i, 1); + if ($csv_terminated_len > 1 && $ch == $csv_terminated[0]) { + $i += $csv_terminated_len - 1; + } + break; + } + // Need to strip trailing enclosing char? + if ($need_end && $ch == $csv_enclosed) { + if ($finished && $i == $len - 1) { + $ch = null; + } elseif ($i == $len - 1) { + $i = $fallbacki; + $ch = mb_substr($buffer, $i, 1); + if ($csv_terminated_len > 1 + && $ch == $csv_terminated[0] + ) { + $i += $csv_terminated_len - 1; + } + break; + } else { + $i++; + $ch = mb_substr($buffer, $i, 1); + if ($csv_terminated_len > 1 + && $ch == $csv_terminated[0] + ) { + $ch = $this->readCsvTerminatedString( + $buffer, + $ch, + $i, + $csv_terminated_len + ); + $i += $csv_terminated_len - 1; + } + } + } + // Are we at the end? + if ($ch == $csv_new_line + || ($csv_new_line == 'auto' && ($ch == "\r" || $ch == "\n")) + || ($finished && $i == $len - 1) + ) { + $csv_finish = true; + } + // Go to next char + if ($ch == $csv_terminated) { + if ($i == $len - 1) { + $i = $fallbacki; + $ch = mb_substr($buffer, $i, 1); + if ($csv_terminated_len > 1 + && $ch == $csv_terminated[0] + ) { + $i += $csv_terminated_len - 1; + } + break; + } + $i++; + $ch = mb_substr($buffer, $i, 1); + if ($csv_terminated_len > 1 + && $ch == $csv_terminated[0] + ) { + $ch = $this->readCsvTerminatedString( + $buffer, + $ch, + $i, + $csv_terminated_len + ); + $i += $csv_terminated_len - 1; + } + } + // If everything went okay, store value + $values[] = $value; + } + + // End of line + if ($csv_finish + || $ch == $csv_new_line + || ($csv_new_line == 'auto' && ($ch == "\r" || $ch == "\n")) + ) { + if ($csv_new_line == 'auto' && $ch == "\r") { // Handle "\r\n" + if ($i >= ($len - 2) && !$finished) { + break; // We need more data to decide new line + } + if (mb_substr($buffer, $i + 1, 1) == "\n") { + $i++; + } + } + // We didn't parse value till the end of line, so there was + // empty one + if (!$csv_finish) { + $values[] = ''; + } + + if ($this->_getAnalyze()) { + foreach ($values as $val) { + $tempRow[] = $val; + ++$col_count; + } + + if ($col_count > $max_cols) { + $max_cols = $col_count; + } + $col_count = 0; + + $rows[] = $tempRow; + $tempRow = array(); + } else { + // Do we have correct count of values? + if (count($values) != $required_fields) { + + // Hack for excel + if ($values[count($values) - 1] == ';') { + unset($values[count($values) - 1]); + } else { + $message = Message::error( + __( + 'Invalid column count in CSV input' + . ' on line %d.' + ) + ); + $message->addParam($line); + $error = true; + break; + } + } + + $first = true; + $sql = $sql_template; + foreach ($values as $key => $val) { + if (!$first) { + $sql .= ', '; + } + if ($val === null) { + $sql .= 'NULL'; + } else { + $sql .= '\'' + . $GLOBALS['dbi']->escapeString($val) + . '\''; + } + + $first = false; + } + $sql .= ')'; + if (isset($_POST['csv_replace'])) { + $sql .= " ON DUPLICATE KEY UPDATE "; + foreach ($fields as $field) { + $fieldName = Util::backquote( + $field['Field'] + ); + $sql .= $fieldName . " = VALUES(" . $fieldName + . "), "; + } + $sql = rtrim($sql, ', '); + } + + /** + * @todo maybe we could add original line to verbose + * SQL in comment + */ + Import::runQuery($sql, $sql, $sql_data); + } + + $line++; + $csv_finish = false; + $values = array(); + $buffer = mb_substr($buffer, $i + 1); + $len = mb_strlen($buffer); + $i = 0; + $lasti = -1; + $ch = mb_substr($buffer, 0, 1); + } + } // End of parser loop + } // End of import loop + + if ($this->_getAnalyze()) { + /* Fill out all rows */ + $num_rows = count($rows); + for ($i = 0; $i < $num_rows; ++$i) { + for ($j = count($rows[$i]); $j < $max_cols; ++$j) { + $rows[$i][] = 'NULL'; + } + } + + if (isset($_REQUEST['csv_col_names'])) { + $col_names = array_splice($rows, 0, 1); + $col_names = $col_names[0]; + // MySQL column names can't end with a space character. + foreach ($col_names as $key => $col_name) { + $col_names[$key] = rtrim($col_name); + } + } + + if ((isset($col_names) && count($col_names) != $max_cols) + || !isset($col_names) + ) { + // Fill out column names + for ($i = 0; $i < $max_cols; ++$i) { + $col_names[] = 'COL ' . ($i + 1); + } + } + + if (mb_strlen($db)) { + $result = $GLOBALS['dbi']->fetchResult('SHOW TABLES'); + $tbl_name = 'TABLE ' . (count($result) + 1); + } else { + $tbl_name = 'TBL_NAME'; + } + + $tables[] = array($tbl_name, $col_names, $rows); + + /* Obtain the best-fit MySQL types for each column */ + $analyses = array(); + $analyses[] = Import::analyzeTable($tables[0]); + + /** + * string $db_name (no backquotes) + * + * array $table = array(table_name, array() column_names, array()() rows) + * array $tables = array of "$table"s + * + * array $analysis = array(array() column_types, array() column_sizes) + * array $analyses = array of "$analysis"s + * + * array $create = array of SQL strings + * + * array $options = an associative array of options + */ + + /* Set database name to the currently selected one, if applicable */ + list($db_name, $options) = $this->getDbnameAndOptions($db, 'CSV_DB'); + + /* Non-applicable parameters */ + $create = null; + + /* Created and execute necessary SQL statements from data */ + Import::buildSql($db_name, $tables, $analyses, $create, $options, $sql_data); + + unset($tables); + unset($analyses); + } + + // Commit any possible data in buffers + Import::runQuery('', '', $sql_data); + + if (count($values) != 0 && !$error) { + $message = Message::error( + __('Invalid format of CSV input on line %d.') + ); + $message->addParam($line); + $error = true; + } + } + + /** + * Read the expected column_separated_with String of length + * $csv_terminated_len from the $buffer + * into variable $ch and return the read string $ch + * + * @param string $buffer The original string buffer read from + * csv file + * @param string $ch Partially read "column Separated with" + * string, also used to return after + * reading length equal $csv_terminated_len + * @param int $i Current read counter of buffer string + * @param int $csv_terminated_len The length of "column separated with" + * String + * + * @return string + */ + public function readCsvTerminatedString($buffer, $ch, $i, $csv_terminated_len) + { + for ($j = 0; $j < $csv_terminated_len - 1; $j++) { + $i++; + $ch .= mb_substr($buffer, $i, 1); + } + + return $ch; + } + /* ~~~~~~~~~~~~~~~~~~~~ Getters and Setters ~~~~~~~~~~~~~~~~~~~~ */ + + /** + * Returns true if the table should be analyzed, false otherwise + * + * @return bool + */ + private function _getAnalyze() + { + return $this->_analyze; + } + + /** + * Sets to true if the table should be analyzed, false otherwise + * + * @param bool $analyze status + * + * @return void + */ + private function _setAnalyze($analyze) + { + $this->_analyze = $analyze; + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Import/ImportLdi.php b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Import/ImportLdi.php new file mode 100644 index 00000000..e41e6f02 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Import/ImportLdi.php @@ -0,0 +1,177 @@ +setProperties(); + } + + /** + * Sets the import plugin properties. + * Called in the constructor. + * + * @return void + */ + protected function setProperties() + { + if ($GLOBALS['cfg']['Import']['ldi_local_option'] == 'auto') { + $GLOBALS['cfg']['Import']['ldi_local_option'] = false; + + $result = $GLOBALS['dbi']->tryQuery( + 'SELECT @@local_infile;' + ); + if ($result != false && $GLOBALS['dbi']->numRows($result) > 0) { + $tmp = $GLOBALS['dbi']->fetchRow($result); + if ($tmp[0] == 'ON') { + $GLOBALS['cfg']['Import']['ldi_local_option'] = true; + } + } + $GLOBALS['dbi']->freeResult($result); + unset($result); + } + + $generalOptions = parent::setProperties(); + $this->properties->setText('CSV using LOAD DATA'); + $this->properties->setExtension('ldi'); + + $leaf = new TextPropertyItem( + "columns", + __('Column names: ') + ); + $generalOptions->addProperty($leaf); + + $leaf = new BoolPropertyItem( + "ignore", + __('Do not abort on INSERT error') + ); + $generalOptions->addProperty($leaf); + + $leaf = new BoolPropertyItem( + "local_option", + __('Use LOCAL keyword') + ); + $generalOptions->addProperty($leaf); + } + + /** + * Handles the whole import logic + * + * @param array &$sql_data 2-element array with sql data + * + * @return void + */ + public function doImport(array &$sql_data = array()) + { + global $finished, $import_file, $charset_conversion, $table; + global $ldi_local_option, $ldi_replace, $ldi_ignore, $ldi_terminated, + $ldi_enclosed, $ldi_escaped, $ldi_new_line, $skip_queries, $ldi_columns; + + $compression = $GLOBALS['import_handle']->getCompression(); + + if ($import_file == 'none' + || $compression != 'none' + || $charset_conversion + ) { + // We handle only some kind of data! + $GLOBALS['message'] = Message::error( + __('This plugin does not support compressed imports!') + ); + $GLOBALS['error'] = true; + + return; + } + + $sql = 'LOAD DATA'; + if (isset($ldi_local_option)) { + $sql .= ' LOCAL'; + } + $sql .= ' INFILE \'' . $GLOBALS['dbi']->escapeString($import_file) + . '\''; + if (isset($ldi_replace)) { + $sql .= ' REPLACE'; + } elseif (isset($ldi_ignore)) { + $sql .= ' IGNORE'; + } + $sql .= ' INTO TABLE ' . Util::backquote($table); + + if (strlen($ldi_terminated) > 0) { + $sql .= ' FIELDS TERMINATED BY \'' . $ldi_terminated . '\''; + } + if (strlen($ldi_enclosed) > 0) { + $sql .= ' ENCLOSED BY \'' + . $GLOBALS['dbi']->escapeString($ldi_enclosed) . '\''; + } + if (strlen($ldi_escaped) > 0) { + $sql .= ' ESCAPED BY \'' + . $GLOBALS['dbi']->escapeString($ldi_escaped) . '\''; + } + if (strlen($ldi_new_line) > 0) { + if ($ldi_new_line == 'auto') { + $ldi_new_line + = (PHP_EOL == "\n") + ? '\n' + : '\r\n'; + } + $sql .= ' LINES TERMINATED BY \'' . $ldi_new_line . '\''; + } + if ($skip_queries > 0) { + $sql .= ' IGNORE ' . $skip_queries . ' LINES'; + $skip_queries = 0; + } + if (strlen($ldi_columns) > 0) { + $sql .= ' ('; + $tmp = preg_split('/,( ?)/', $ldi_columns); + $cnt_tmp = count($tmp); + for ($i = 0; $i < $cnt_tmp; $i++) { + if ($i > 0) { + $sql .= ', '; + } + /* Trim also `, if user already included backquoted fields */ + $sql .= Util::backquote( + trim($tmp[$i], " \t\r\n\0\x0B`") + ); + } // end for + $sql .= ')'; + } + + Import::runQuery($sql, $sql, $sql_data); + Import::runQuery('', '', $sql_data); + $finished = true; + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Import/ImportMediawiki.php b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Import/ImportMediawiki.php new file mode 100644 index 00000000..852110e4 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Import/ImportMediawiki.php @@ -0,0 +1,599 @@ +setProperties(); + } + + /** + * Sets the import plugin properties. + * Called in the constructor. + * + * @return void + */ + protected function setProperties() + { + $this->_setAnalyze(false); + if ($GLOBALS['plugin_param'] !== 'table') { + $this->_setAnalyze(true); + } + + $importPluginProperties = new ImportPluginProperties(); + $importPluginProperties->setText(__('MediaWiki Table')); + $importPluginProperties->setExtension('txt'); + $importPluginProperties->setMimeType('text/plain'); + $importPluginProperties->setOptions(array()); + $importPluginProperties->setOptionsText(__('Options')); + + $this->properties = $importPluginProperties; + } + + /** + * Handles the whole import logic + * + * @param array &$sql_data 2-element array with sql data + * + * @return void + */ + public function doImport(array &$sql_data = array()) + { + global $error, $timeout_passed, $finished; + + // Defaults for parser + + // The buffer that will be used to store chunks read from the imported file + $buffer = ''; + + // Used as storage for the last part of the current chunk data + // Will be appended to the first line of the next chunk, if there is one + $last_chunk_line = ''; + + // Remembers whether the current buffer line is part of a comment + $inside_comment = false; + // Remembers whether the current buffer line is part of a data comment + $inside_data_comment = false; + // Remembers whether the current buffer line is part of a structure comment + $inside_structure_comment = false; + + // MediaWiki only accepts "\n" as row terminator + $mediawiki_new_line = "\n"; + + // Initialize the name of the current table + $cur_table_name = ""; + + while (!$finished && !$error && !$timeout_passed) { + $data = Import::getNextChunk(); + + if ($data === false) { + // Subtract data we didn't handle yet and stop processing + $GLOBALS['offset'] -= mb_strlen($buffer); + break; + } elseif ($data === true) { + // Handle rest of buffer + } else { + // Append new data to buffer + $buffer = $data; + unset($data); + // Don't parse string if we're not at the end + // and don't have a new line inside + if (mb_strpos($buffer, $mediawiki_new_line) === false) { + continue; + } + } + + // Because of reading chunk by chunk, the first line from the buffer + // contains only a portion of an actual line from the imported file. + // Therefore, we have to append it to the last line from the previous + // chunk. If we are at the first chunk, $last_chunk_line should be empty. + $buffer = $last_chunk_line . $buffer; + + // Process the buffer line by line + $buffer_lines = explode($mediawiki_new_line, $buffer); + + $full_buffer_lines_count = count($buffer_lines); + // If the reading is not finalised, the final line of the current chunk + // will not be complete + if (! $finished) { + $last_chunk_line = $buffer_lines[--$full_buffer_lines_count]; + } + + for ($line_nr = 0; $line_nr < $full_buffer_lines_count; ++$line_nr) { + $cur_buffer_line = trim($buffer_lines[$line_nr]); + + // If the line is empty, go to the next one + if ($cur_buffer_line === '') { + continue; + } + + $first_character = $cur_buffer_line[0]; + $matches = array(); + + // Check beginning of comment + if (!strcmp(mb_substr($cur_buffer_line, 0, 4), "") + ) { + // Only data comments are closed. The structure comments + // will be closed when a data comment begins (in order to + // skip structure tables) + if ($inside_data_comment) { + $inside_data_comment = false; + } + + // End comments that are not related to table structure + if (!$inside_structure_comment) { + $inside_comment = false; + } + } else { + // Check table name + $match_table_name = array(); + if (preg_match( + "/^Table data for `(.*)`$/", + $cur_buffer_line, + $match_table_name + ) + ) { + $cur_table_name = $match_table_name[1]; + $inside_data_comment = true; + + $inside_structure_comment + = $this->_mngInsideStructComm( + $inside_structure_comment + ); + } elseif (preg_match( + "/^Table structure for `(.*)`$/", + $cur_buffer_line, + $match_table_name + ) + ) { + // The structure comments will be ignored + $inside_structure_comment = true; + } + } + continue; + } elseif (preg_match('/^\{\|(.*)$/', $cur_buffer_line, $matches)) { + // Check start of table + + // This will store all the column info on all rows from + // the current table read from the buffer + $cur_temp_table = array(); + + // Will be used as storage for the current row in the buffer + // Once all its columns are read, it will be added to + // $cur_temp_table and then it will be emptied + $cur_temp_line = array(); + + // Helps us differentiate the header columns + // from the normal columns + $in_table_header = false; + // End processing because the current line does not + // contain any column information + } elseif (mb_substr($cur_buffer_line, 0, 2) === '|-' + || mb_substr($cur_buffer_line, 0, 2) === '|+' + || mb_substr($cur_buffer_line, 0, 2) === '|}' + ) { + // Check begin row or end table + + // Add current line to the values storage + if (!empty($cur_temp_line)) { + // If the current line contains header cells + // ( marked with '!' ), + // it will be marked as table header + if ($in_table_header) { + // Set the header columns + $cur_temp_table_headers = $cur_temp_line; + } else { + // Normal line, add it to the table + $cur_temp_table [] = $cur_temp_line; + } + } + + // Empty the temporary buffer + $cur_temp_line = array(); + + // No more processing required at the end of the table + if (mb_substr($cur_buffer_line, 0, 2) === '|}') { + $current_table = array( + $cur_table_name, + $cur_temp_table_headers, + $cur_temp_table, + ); + + // Import the current table data into the database + $this->_importDataOneTable($current_table, $sql_data); + + // Reset table name + $cur_table_name = ""; + } + // What's after the row tag is now only attributes + + } elseif (($first_character === '|') || ($first_character === '!')) { + // Check cell elements + + // Header cells + if ($first_character === '!') { + // Mark as table header, but treat as normal row + $cur_buffer_line = str_replace('!!', '||', $cur_buffer_line); + // Will be used to set $cur_temp_line as table header + $in_table_header = true; + } else { + $in_table_header = false; + } + + // Loop through each table cell + $cells = $this->_explodeMarkup($cur_buffer_line); + foreach ($cells as $cell) { + $cell = $this->_getCellData($cell); + + // Delete the beginning of the column, if there is one + $cell = trim($cell); + $col_start_chars = array("|", "!"); + foreach ($col_start_chars as $col_start_char) { + $cell = $this->_getCellContent($cell, $col_start_char); + } + + // Add the cell to the row + $cur_temp_line [] = $cell; + } // foreach $cells + } else { + // If it's none of the above, then the current line has a bad + // format + $message = Message::error( + __('Invalid format of mediawiki input on line:
    %s.') + ); + $message->addParam($cur_buffer_line); + $error = true; + } + } // End treating full buffer lines + } // while - finished parsing buffer + } + + /** + * Imports data from a single table + * + * @param array $table containing all table info: + * + * $table[0] - string containing table name + * $table[1] - array[] of table headers + * $table[2] - array[][] of table content rows + * + * + * @param array &$sql_data 2-element array with sql data + * + * @global bool $analyze whether to scan for column types + * + * @return void + */ + private function _importDataOneTable(array $table, array &$sql_data) + { + $analyze = $this->_getAnalyze(); + if ($analyze) { + // Set the table name + $this->_setTableName($table[0]); + + // Set generic names for table headers if they don't exist + $this->_setTableHeaders($table[1], $table[2][0]); + + // Create the tables array to be used in Import::buildSql() + $tables = array(); + $tables [] = array($table[0], $table[1], $table[2]); + + // Obtain the best-fit MySQL types for each column + $analyses = array(); + $analyses [] = Import::analyzeTable($tables[0]); + + $this->_executeImportTables($tables, $analyses, $sql_data); + } + + // Commit any possible data in buffers + Import::runQuery('', '', $sql_data); + } + + /** + * Sets the table name + * + * @param string &$table_name reference to the name of the table + * + * @return void + */ + private function _setTableName(&$table_name) + { + if (empty($table_name)) { + $result = $GLOBALS['dbi']->fetchResult('SHOW TABLES'); + // todo check if the name below already exists + $table_name = 'TABLE ' . (count($result) + 1); + } + } + + /** + * Set generic names for table headers, if they don't exist + * + * @param array &$table_headers reference to the array containing the headers + * of a table + * @param array $table_row array containing the first content row + * + * @return void + */ + private function _setTableHeaders(array &$table_headers, array $table_row) + { + if (empty($table_headers)) { + // The first table row should contain the number of columns + // If they are not set, generic names will be given (COL 1, COL 2, etc) + $num_cols = count($table_row); + for ($i = 0; $i < $num_cols; ++$i) { + $table_headers [$i] = 'COL ' . ($i + 1); + } + } + } + + /** + * Sets the database name and additional options and calls Import::buildSql() + * Used in PMA_importDataAllTables() and $this->_importDataOneTable() + * + * @param array &$tables structure: + * array( + * array(table_name, array() column_names, array()() + * rows) + * ) + * @param array &$analyses structure: + * $analyses = array( + * array(array() column_types, array() column_sizes) + * ) + * @param array &$sql_data 2-element array with sql data + * + * @global string $db name of the database to import in + * + * @return void + */ + private function _executeImportTables(array &$tables, array &$analyses, array &$sql_data) + { + global $db; + + // $db_name : The currently selected database name, if applicable + // No backquotes + // $options : An associative array of options + list($db_name, $options) = $this->getDbnameAndOptions($db, 'mediawiki_DB'); + + // Array of SQL strings + // Non-applicable parameters + $create = null; + + // Create and execute necessary SQL statements from data + Import::buildSql($db_name, $tables, $analyses, $create, $options, $sql_data); + + unset($tables); + unset($analyses); + } + + /** + * Replaces all instances of the '||' separator between delimiters + * in a given string + * + * @param string $replace the string to be replaced with + * @param string $subject the text to be replaced + * + * @return string with replacements + */ + private function _delimiterReplace($replace, $subject) + { + // String that will be returned + $cleaned = ""; + // Possible states of current character + $inside_tag = false; + $inside_attribute = false; + // Attributes can be declared with either " or ' + $start_attribute_character = false; + + // The full separator is "||"; + // This remembers if the previous character was '|' + $partial_separator = false; + + // Parse text char by char + for ($i = 0; $i < strlen($subject); $i++) { + $cur_char = $subject[$i]; + // Check for separators + if ($cur_char == '|') { + // If we're not inside a tag, then this is part of a real separator, + // so we append it to the current segment + if (!$inside_attribute) { + $cleaned .= $cur_char; + if ($partial_separator) { + $inside_tag = false; + $inside_attribute = false; + } + } elseif ($partial_separator) { + // If we are inside a tag, we replace the current char with + // the placeholder and append that to the current segment + $cleaned .= $replace; + } + + // If the previous character was also '|', then this ends a + // full separator. If not, this may be the beginning of one + $partial_separator = !$partial_separator; + } else { + // If we're inside a tag attribute and the current character is + // not '|', but the previous one was, it means that the single '|' + // was not appended, so we append it now + if ($partial_separator && $inside_attribute) { + $cleaned .= "|"; + } + // If the char is different from "|", no separator can be formed + $partial_separator = false; + + // any other character should be appended to the current segment + $cleaned .= $cur_char; + + if ($cur_char == '<' && !$inside_attribute) { + // start of a tag + $inside_tag = true; + } elseif ($cur_char == '>' && !$inside_attribute) { + // end of a tag + $inside_tag = false; + } elseif (($cur_char == '"' || $cur_char == "'") && $inside_tag) { + // start or end of an attribute + if (!$inside_attribute) { + $inside_attribute = true; + // remember the attribute`s declaration character (" or ') + $start_attribute_character = $cur_char; + } else { + if ($cur_char == $start_attribute_character) { + $inside_attribute = false; + // unset attribute declaration character + $start_attribute_character = false; + } + } + } + } + } // end for each character in $subject + + return $cleaned; + } + + /** + * Separates a string into items, similarly to explode + * Uses the '||' separator (which is standard in the mediawiki format) + * and ignores any instances of it inside markup tags + * Used in parsing buffer lines containing data cells + * + * @param string $text text to be split + * + * @return array + */ + private function _explodeMarkup($text) + { + $separator = "||"; + $placeholder = "\x00"; + + // Remove placeholder instances + $text = str_replace($placeholder, '', $text); + + // Replace instances of the separator inside HTML-like + // tags with the placeholder + $cleaned = $this->_delimiterReplace($placeholder, $text); + // Explode, then put the replaced separators back in + $items = explode($separator, $cleaned); + foreach ($items as $i => $str) { + $items[$i] = str_replace($placeholder, $separator, $str); + } + + return $items; + } + + + /* ~~~~~~~~~~~~~~~~~~~~ Getters and Setters ~~~~~~~~~~~~~~~~~~~~ */ + + /** + * Returns true if the table should be analyzed, false otherwise + * + * @return bool + */ + private function _getAnalyze() + { + return $this->_analyze; + } + + /** + * Sets to true if the table should be analyzed, false otherwise + * + * @param bool $analyze status + * + * @return void + */ + private function _setAnalyze($analyze) + { + $this->_analyze = $analyze; + } + + /** + * Get cell + * + * @param string $cell Cell + * + * @return mixed + */ + private function _getCellData($cell) + { + // A cell could contain both parameters and data + $cell_data = explode('|', $cell, 2); + + // A '|' inside an invalid link should not + // be mistaken as delimiting cell parameters + if (mb_strpos($cell_data[0], '[[') === false) { + return $cell; + } + + if (count($cell_data) == 1) { + return $cell_data[0]; + } + + return $cell_data[1]; + } + + /** + * Manage $inside_structure_comment + * + * @param boolean $inside_structure_comment Value to test + * + * @return bool + */ + private function _mngInsideStructComm($inside_structure_comment) + { + // End ignoring structure rows + if ($inside_structure_comment) { + $inside_structure_comment = false; + } + + return $inside_structure_comment; + } + + /** + * Get cell content + * + * @param string $cell Cell + * @param string $col_start_char Start char + * + * @return string + */ + private function _getCellContent($cell, $col_start_char) + { + if (mb_strpos($cell, $col_start_char) === 0) { + $cell = trim(mb_substr($cell, 1)); + } + + return $cell; + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Import/ImportOds.php b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Import/ImportOds.php new file mode 100644 index 00000000..7100fadc --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Import/ImportOds.php @@ -0,0 +1,422 @@ +setProperties(); + } + + /** + * Sets the import plugin properties. + * Called in the constructor. + * + * @return void + */ + protected function setProperties() + { + $importPluginProperties = new ImportPluginProperties(); + $importPluginProperties->setText('OpenDocument Spreadsheet'); + $importPluginProperties->setExtension('ods'); + $importPluginProperties->setOptionsText(__('Options')); + + // create the root group that will be the options field for + // $importPluginProperties + // this will be shown as "Format specific options" + $importSpecificOptions = new OptionsPropertyRootGroup( + "Format Specific Options" + ); + + // general options main group + $generalOptions = new OptionsPropertyMainGroup("general_opts"); + // create primary items and add them to the group + $leaf = new BoolPropertyItem( + "col_names", + __( + 'The first line of the file contains the table column names' + . ' (if this is unchecked, the first line will become part' + . ' of the data)' + ) + ); + $generalOptions->addProperty($leaf); + $leaf = new BoolPropertyItem( + "empty_rows", + __('Do not import empty rows') + ); + $generalOptions->addProperty($leaf); + $leaf = new BoolPropertyItem( + "recognize_percentages", + __( + 'Import percentages as proper decimals (ex. 12.00% to .12)' + ) + ); + $generalOptions->addProperty($leaf); + $leaf = new BoolPropertyItem( + "recognize_currency", + __('Import currencies (ex. $5.00 to 5.00)') + ); + $generalOptions->addProperty($leaf); + + // add the main group to the root group + $importSpecificOptions->addProperty($generalOptions); + + // set the options for the import plugin property item + $importPluginProperties->setOptions($importSpecificOptions); + $this->properties = $importPluginProperties; + } + + /** + * Handles the whole import logic + * + * @param array &$sql_data 2-element array with sql data + * + * @return void + */ + public function doImport(array &$sql_data = array()) + { + global $db, $error, $timeout_passed, $finished; + + $i = 0; + $len = 0; + $buffer = ""; + + /** + * Read in the file via Import::getNextChunk so that + * it can process compressed files + */ + while (!($finished && $i >= $len) && !$error && !$timeout_passed) { + $data = Import::getNextChunk(); + if ($data === false) { + /* subtract data we didn't handle yet and stop processing */ + $GLOBALS['offset'] -= strlen($buffer); + break; + } elseif ($data === true) { + /* Handle rest of buffer */ + } else { + /* Append new data to buffer */ + $buffer .= $data; + unset($data); + } + } + + unset($data); + + /** + * Disable loading of external XML entities. + */ + libxml_disable_entity_loader(); + + /** + * Load the XML string + * + * The option LIBXML_COMPACT is specified because it can + * result in increased performance without the need to + * alter the code in any way. It's basically a freebee. + */ + $xml = @simplexml_load_string($buffer, "SimpleXMLElement", LIBXML_COMPACT); + + unset($buffer); + + if ($xml === false) { + $sheets = array(); + $GLOBALS['message'] = Message::error( + __( + 'The XML file specified was either malformed or incomplete.' + . ' Please correct the issue and try again.' + ) + ); + $GLOBALS['error'] = true; + } else { + /** @var SimpleXMLElement $root */ + $root = $xml->children('office', true)->{'body'}->{'spreadsheet'}; + if (empty($root)) { + $sheets = array(); + $GLOBALS['message'] = Message::error( + __('Could not parse OpenDocument Spreadsheet!') + ); + $GLOBALS['error'] = true; + } else { + $sheets = $root->children('table', true); + } + } + + $tables = array(); + + $max_cols = 0; + + $col_count = 0; + $col_names = array(); + + $tempRow = array(); + $tempRows = array(); + $rows = array(); + + /* Iterate over tables */ + /** @var SimpleXMLElement $sheet */ + foreach ($sheets as $sheet) { + $col_names_in_first_row = isset($_REQUEST['ods_col_names']); + + /* Iterate over rows */ + /** @var SimpleXMLElement $row */ + foreach ($sheet as $row) { + $type = $row->getName(); + if (strcmp('table-row', $type)) { + continue; + } + /* Iterate over columns */ + $cellCount = count($row); + $a = 0; + /** @var SimpleXMLElement $cell */ + foreach ($row as $cell) { + $a++; + $text = $cell->children('text', true); + $cell_attrs = $cell->attributes('office', true); + + if (count($text) != 0) { + $attr = $cell->attributes('table', true); + $num_repeat = (int)$attr['number-columns-repeated']; + $num_iterations = $num_repeat ? $num_repeat : 1; + + for ($k = 0; $k < $num_iterations; $k++) { + $value = $this->getValue($cell_attrs, $text); + if (!$col_names_in_first_row) { + $tempRow[] = $value; + } else { + // MySQL column names can't end with a space + // character. + $col_names[] = rtrim($value); + } + + ++$col_count; + } + continue; + } + + // skip empty repeats in the last row + if ($a == $cellCount) { + continue; + } + + $attr = $cell->attributes('table', true); + $num_null = (int)$attr['number-columns-repeated']; + + if ($num_null) { + if (!$col_names_in_first_row) { + for ($i = 0; $i < $num_null; ++$i) { + $tempRow[] = 'NULL'; + ++$col_count; + } + } else { + for ($i = 0; $i < $num_null; ++$i) { + $col_names[] = Import::getColumnAlphaName( + $col_count + 1 + ); + ++$col_count; + } + } + } else { + if (!$col_names_in_first_row) { + $tempRow[] = 'NULL'; + } else { + $col_names[] = Import::getColumnAlphaName( + $col_count + 1 + ); + } + + ++$col_count; + } + } //Endforeach + + /* Find the widest row */ + if ($col_count > $max_cols) { + $max_cols = $col_count; + } + + /* Don't include a row that is full of NULL values */ + if (!$col_names_in_first_row) { + if ($_REQUEST['ods_empty_rows']) { + foreach ($tempRow as $cell) { + if (strcmp('NULL', $cell)) { + $tempRows[] = $tempRow; + break; + } + } + } else { + $tempRows[] = $tempRow; + } + } + + $col_count = 0; + $col_names_in_first_row = false; + $tempRow = array(); + } + + /* Skip over empty sheets */ + if (count($tempRows) == 0 || count($tempRows[0]) == 0) { + $col_names = array(); + $tempRow = array(); + $tempRows = array(); + continue; + } + + /** + * Fill out each row as necessary to make + * every one exactly as wide as the widest + * row. This included column names. + */ + + /* Fill out column names */ + for ($i = count($col_names); $i < $max_cols; ++$i) { + $col_names[] = Import::getColumnAlphaName($i + 1); + } + + /* Fill out all rows */ + $num_rows = count($tempRows); + for ($i = 0; $i < $num_rows; ++$i) { + for ($j = count($tempRows[$i]); $j < $max_cols; ++$j) { + $tempRows[$i][] = 'NULL'; + } + } + + /* Store the table name so we know where to place the row set */ + $tbl_attr = $sheet->attributes('table', true); + $tables[] = array((string)$tbl_attr['name']); + + /* Store the current sheet in the accumulator */ + $rows[] = array((string)$tbl_attr['name'], $col_names, $tempRows); + $tempRows = array(); + $col_names = array(); + $max_cols = 0; + } + + unset($tempRow); + unset($tempRows); + unset($col_names); + unset($sheets); + unset($xml); + + /** + * Bring accumulated rows into the corresponding table + */ + $num_tables = count($tables); + for ($i = 0; $i < $num_tables; ++$i) { + $num_rows = count($rows); + for ($j = 0; $j < $num_rows; ++$j) { + if (strcmp($tables[$i][Import::TBL_NAME], $rows[$j][Import::TBL_NAME])) { + continue; + } + + if (!isset($tables[$i][Import::COL_NAMES])) { + $tables[$i][] = $rows[$j][Import::COL_NAMES]; + } + + $tables[$i][Import::ROWS] = $rows[$j][Import::ROWS]; + } + } + + /* No longer needed */ + unset($rows); + + /* Obtain the best-fit MySQL types for each column */ + $analyses = array(); + + $len = count($tables); + for ($i = 0; $i < $len; ++$i) { + $analyses[] = Import::analyzeTable($tables[$i]); + } + + /** + * string $db_name (no backquotes) + * + * array $table = array(table_name, array() column_names, array()() rows) + * array $tables = array of "$table"s + * + * array $analysis = array(array() column_types, array() column_sizes) + * array $analyses = array of "$analysis"s + * + * array $create = array of SQL strings + * + * array $options = an associative array of options + */ + + /* Set database name to the currently selected one, if applicable */ + list($db_name, $options) = $this->getDbnameAndOptions($db, 'ODS_DB'); + + /* Non-applicable parameters */ + $create = null; + + /* Created and execute necessary SQL statements from data */ + Import::buildSql($db_name, $tables, $analyses, $create, $options, $sql_data); + + unset($tables); + unset($analyses); + + /* Commit any possible data in buffers */ + Import::runQuery('', '', $sql_data); + } + + /** + * Get value + * + * @param array $cell_attrs Cell attributes + * @param array $text Texts + * + * @return float|string + */ + protected function getValue($cell_attrs, $text) + { + if ($_REQUEST['ods_recognize_percentages'] + && !strcmp( + 'percentage', + $cell_attrs['value-type'] + ) + ) { + $value = (double)$cell_attrs['value']; + + return $value; + } elseif ($_REQUEST['ods_recognize_currency'] + && !strcmp('currency', $cell_attrs['value-type']) + ) { + $value = (double)$cell_attrs['value']; + + return $value; + } + + /* We need to concatenate all paragraphs */ + $values = array(); + foreach ($text as $paragraph) { + $values[] = (string)$paragraph; + } + $value = implode("\n", $values); + + return $value; + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Import/ImportShp.php b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Import/ImportShp.php new file mode 100644 index 00000000..c5d8b52c --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Import/ImportShp.php @@ -0,0 +1,318 @@ +setProperties(); + if (extension_loaded('zip')) { + $this->zipExtension = new ZipExtension(); + } + } + + /** + * Sets the import plugin properties. + * Called in the constructor. + * + * @return void + */ + protected function setProperties() + { + $importPluginProperties = new ImportPluginProperties(); + $importPluginProperties->setText(__('ESRI Shape File')); + $importPluginProperties->setExtension('shp'); + $importPluginProperties->setOptions(array()); + $importPluginProperties->setOptionsText(__('Options')); + + $this->properties = $importPluginProperties; + } + + /** + * Handles the whole import logic + * + * @param array &$sql_data 2-element array with sql data + * + * @return void + */ + public function doImport(array &$sql_data = array()) + { + global $db, $error, $finished, + $import_file, $local_import_file, $message; + + $GLOBALS['finished'] = false; + + $compression = $GLOBALS['import_handle']->getCompression(); + + $shp = new ShapeFileImport(1); + // If the zip archive has more than one file, + // get the correct content to the buffer from .shp file. + if ($compression == 'application/zip' + && $this->zipExtension->getNumberOfFiles($import_file) > 1 + ) { + if ($GLOBALS['import_handle']->openZip('/^.*\.shp$/i') === false) { + $message = Message::error( + __('There was an error importing the ESRI shape file: "%s".') + ); + $message->addParam($GLOBALS['import_handle']->getError()); + + return; + } + } + + $temp_dbf_file = false; + // We need dbase extension to handle .dbf file + if (extension_loaded('dbase')) { + $temp = $GLOBALS['PMA_Config']->getTempDir('shp'); + // If we can extract the zip archive to 'TempDir' + // and use the files in it for import + if ($compression == 'application/zip' && ! is_null($temp)) { + $dbf_file_name = $this->zipExtension->findFile( + $import_file, + '/^.*\.dbf$/i' + ); + // If the corresponding .dbf file is in the zip archive + if ($dbf_file_name) { + // Extract the .dbf file and point to it. + $extracted = $this->zipExtension->extract( + $import_file, + $dbf_file_name + ); + if ($extracted !== false) { + $dbf_file_path = $temp . (PMA_IS_WINDOWS ? '\\' : '/') + . Sanitize::sanitizeFilename($dbf_file_name, true); + $handle = fopen($dbf_file_path, 'wb'); + if ($handle !== false) { + fwrite($handle, $extracted); + fclose($handle); + $temp_dbf_file = true; + // Replace the .dbf with .*, as required + // by the bsShapeFiles library. + $file_name = substr( + $dbf_file_path, 0, strlen($dbf_file_path) - 4 + ) . '.*'; + $shp->FileName = $file_name; + } + } + } + } elseif (!empty($local_import_file) + && !empty($GLOBALS['cfg']['UploadDir']) + && $compression == 'none' + ) { + // If file is in UploadDir, use .dbf file in the same UploadDir + // to load extra data. + // Replace the .shp with .*, + // so the bsShapeFiles library correctly locates .dbf file. + $file_name = mb_substr( + $import_file, + 0, + mb_strlen($import_file) - 4 + ) . '.*'; + $shp->FileName = $file_name; + } + } + + // Delete the .dbf file extracted to 'TempDir' + if ($temp_dbf_file + && isset($dbf_file_path) + && @file_exists($dbf_file_path) + ) { + unlink($dbf_file_path); + } + + // Load data + $shp->loadFromFile(''); + if ($shp->lastError != "") { + $error = true; + $message = Message::error( + __('There was an error importing the ESRI shape file: "%s".') + ); + $message->addParam($shp->lastError); + + return; + } + + switch ($shp->shapeType) { + // ESRI Null Shape + case 0: + break; + // ESRI Point + case 1: + $gis_type = 'point'; + break; + // ESRI PolyLine + case 3: + $gis_type = 'multilinestring'; + break; + // ESRI Polygon + case 5: + $gis_type = 'multipolygon'; + break; + // ESRI MultiPoint + case 8: + $gis_type = 'multipoint'; + break; + default: + $error = true; + $message = Message::error( + __('MySQL Spatial Extension does not support ESRI type "%s".') + ); + $message->addParam($shp->getShapeName()); + return; + } + + if (isset($gis_type)) { + /** @var GisMultiLineString|\PhpMyAdmin\Gis\GisMultiPoint|\PhpMyAdmin\Gis\GisPoint|GisPolygon $gis_obj */ + $gis_obj = GisFactory::factory($gis_type); + } else { + $gis_obj = null; + } + + $num_rows = count($shp->records); + // If .dbf file is loaded, the number of extra data columns + $num_data_cols = isset($shp->DBFHeader) ? count($shp->DBFHeader) : 0; + + $rows = array(); + $col_names = array(); + if ($num_rows != 0) { + foreach ($shp->records as $record) { + $tempRow = array(); + if ($gis_obj == null) { + $tempRow[] = null; + } else { + $tempRow[] = "GeomFromText('" + . $gis_obj->getShape($record->SHPData) . "')"; + } + + if (isset($shp->DBFHeader)) { + foreach ($shp->DBFHeader as $c) { + $cell = trim($record->DBFData[$c[0]]); + + if (!strcmp($cell, '')) { + $cell = 'NULL'; + } + + $tempRow[] = $cell; + } + } + $rows[] = $tempRow; + } + } + + if (count($rows) == 0) { + $error = true; + $message = Message::error( + __('The imported file does not contain any data!') + ); + + return; + } + + // Column names for spatial column and the rest of the columns, + // if they are available + $col_names[] = 'SPATIAL'; + for ($n = 0; $n < $num_data_cols; $n++) { + $col_names[] = $shp->DBFHeader[$n][0]; + } + + // Set table name based on the number of tables + if (strlen($db) > 0) { + $result = $GLOBALS['dbi']->fetchResult('SHOW TABLES'); + $table_name = 'TABLE ' . (count($result) + 1); + } else { + $table_name = 'TBL_NAME'; + } + $tables = array(array($table_name, $col_names, $rows)); + + // Use data from shape file to chose best-fit MySQL types for each column + $analyses = array(); + $analyses[] = Import::analyzeTable($tables[0]); + + $table_no = 0; + $spatial_col = 0; + $analyses[$table_no][Import::TYPES][$spatial_col] = Import::GEOMETRY; + $analyses[$table_no][Import::FORMATTEDSQL][$spatial_col] = true; + + // Set database name to the currently selected one, if applicable + if (strlen($db) > 0) { + $db_name = $db; + $options = array('create_db' => false); + } else { + $db_name = 'SHP_DB'; + $options = null; + } + + // Created and execute necessary SQL statements from data + $null_param = null; + Import::buildSql($db_name, $tables, $analyses, $null_param, $options, $sql_data); + + unset($tables); + unset($analyses); + + $finished = true; + $error = false; + + // Commit any possible data in buffers + Import::runQuery('', '', $sql_data); + } + + /** + * Returns specified number of bytes from the buffer. + * Buffer automatically fetches next chunk of data when the buffer + * falls short. + * Sets $eof when $GLOBALS['finished'] is set and the buffer falls short. + * + * @param int $length number of bytes + * + * @return string + */ + public static function readFromBuffer($length) + { + global $buffer, $eof; + + if (strlen($buffer) < $length) { + if ($GLOBALS['finished']) { + $eof = true; + } else { + $buffer .= Import::getNextChunk(); + } + } + $result = substr($buffer, 0, $length); + $buffer = substr($buffer, $length); + + return $result; + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Import/ImportSql.php b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Import/ImportSql.php new file mode 100644 index 00000000..240b8ba3 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Import/ImportSql.php @@ -0,0 +1,198 @@ +setProperties(); + } + + /** + * Sets the import plugin properties. + * Called in the constructor. + * + * @return void + */ + protected function setProperties() + { + $importPluginProperties = new ImportPluginProperties(); + $importPluginProperties->setText('SQL'); + $importPluginProperties->setExtension('sql'); + $importPluginProperties->setOptionsText(__('Options')); + + $compats = $GLOBALS['dbi']->getCompatibilities(); + if (count($compats) > 0) { + $values = array(); + foreach ($compats as $val) { + $values[$val] = $val; + } + + // create the root group that will be the options field for + // $importPluginProperties + // this will be shown as "Format specific options" + $importSpecificOptions = new OptionsPropertyRootGroup( + "Format Specific Options" + ); + + // general options main group + $generalOptions = new OptionsPropertyMainGroup("general_opts"); + // create primary items and add them to the group + $leaf = new SelectPropertyItem( + "compatibility", + __('SQL compatibility mode:') + ); + $leaf->setValues($values); + $leaf->setDoc( + array( + 'manual_MySQL_Database_Administration', + 'Server_SQL_mode', + ) + ); + $generalOptions->addProperty($leaf); + $leaf = new BoolPropertyItem( + "no_auto_value_on_zero", + __('Do not use AUTO_INCREMENT for zero values') + ); + $leaf->setDoc( + array( + 'manual_MySQL_Database_Administration', + 'Server_SQL_mode', + 'sqlmode_no_auto_value_on_zero', + ) + ); + $generalOptions->addProperty($leaf); + + // add the main group to the root group + $importSpecificOptions->addProperty($generalOptions); + // set the options for the import plugin property item + $importPluginProperties->setOptions($importSpecificOptions); + } + + $this->properties = $importPluginProperties; + } + + /** + * Handles the whole import logic + * + * @param array &$sql_data 2-element array with sql data + * + * @return void + */ + public function doImport(array &$sql_data = array()) + { + global $error, $timeout_passed; + + // Handle compatibility options. + $this->_setSQLMode($GLOBALS['dbi'], $_REQUEST); + + $bq = new BufferedQuery(); + if (isset($_POST['sql_delimiter'])) { + $bq->setDelimiter($_POST['sql_delimiter']); + } + + /** + * Will be set in Import::getNextChunk(). + * + * @global bool $GLOBALS ['finished'] + */ + $GLOBALS['finished'] = false; + + while ((!$error) && (!$timeout_passed)) { + + // Getting the first statement, the remaining data and the last + // delimiter. + $statement = $bq->extract(); + + // If there is no full statement, we are looking for more data. + if (empty($statement)) { + + // Importing new data. + $newData = Import::getNextChunk(); + + // Subtract data we didn't handle yet and stop processing. + if ($newData === false) { + $GLOBALS['offset'] -= mb_strlen($bq->query); + break; + } + + // Checking if the input buffer has finished. + if ($newData === true) { + $GLOBALS['finished'] = true; + break; + } + + // Convert CR (but not CRLF) to LF otherwise all queries may + // not get executed on some platforms. + $bq->query .= preg_replace("/\r($|[^\n])/", "\n$1", $newData); + + continue; + } + + // Executing the query. + Import::runQuery($statement, $statement, $sql_data); + } + + // Extracting remaining statements. + while ((!$error) && (!$timeout_passed) && (!empty($bq->query))) { + $statement = $bq->extract(true); + if (!empty($statement)) { + Import::runQuery($statement, $statement, $sql_data); + } + } + + // Finishing. + Import::runQuery('', '', $sql_data); + } + + /** + * Handle compatibility options + * + * @param PhpMyAdmin\DatabaseInterface $dbi Database interface + * @param array $request Request array + * + * @return void + */ + private function _setSQLMode($dbi, array $request) + { + $sql_modes = array(); + if (isset($request['sql_compatibility']) + && 'NONE' != $request['sql_compatibility'] + ) { + $sql_modes[] = $request['sql_compatibility']; + } + if (isset($request['sql_no_auto_value_on_zero'])) { + $sql_modes[] = 'NO_AUTO_VALUE_ON_ZERO'; + } + if (count($sql_modes) > 0) { + $dbi->tryQuery( + 'SET SQL_MODE="' . implode(',', $sql_modes) . '"' + ); + } + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Import/ImportXml.php b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Import/ImportXml.php new file mode 100644 index 00000000..6de2ace1 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Import/ImportXml.php @@ -0,0 +1,371 @@ +setProperties(); + } + + /** + * Sets the import plugin properties. + * Called in the constructor. + * + * @return void + */ + protected function setProperties() + { + $importPluginProperties = new ImportPluginProperties(); + $importPluginProperties->setText(__('XML')); + $importPluginProperties->setExtension('xml'); + $importPluginProperties->setMimeType('text/xml'); + $importPluginProperties->setOptions(array()); + $importPluginProperties->setOptionsText(__('Options')); + + $this->properties = $importPluginProperties; + } + + /** + * Handles the whole import logic + * + * @param array &$sql_data 2-element array with sql data + * + * @return void + */ + public function doImport(array &$sql_data = array()) + { + global $error, $timeout_passed, $finished, $db; + + $i = 0; + $len = 0; + $buffer = ""; + + /** + * Read in the file via Import::getNextChunk so that + * it can process compressed files + */ + while (!($finished && $i >= $len) && !$error && !$timeout_passed) { + $data = Import::getNextChunk(); + if ($data === false) { + /* subtract data we didn't handle yet and stop processing */ + $GLOBALS['offset'] -= strlen($buffer); + break; + } elseif ($data === true) { + /* Handle rest of buffer */ + } else { + /* Append new data to buffer */ + $buffer .= $data; + unset($data); + } + } + + unset($data); + + /** + * Disable loading of external XML entities. + */ + libxml_disable_entity_loader(); + + /** + * Load the XML string + * + * The option LIBXML_COMPACT is specified because it can + * result in increased performance without the need to + * alter the code in any way. It's basically a freebee. + */ + $xml = @simplexml_load_string($buffer, "SimpleXMLElement", LIBXML_COMPACT); + + unset($buffer); + + /** + * The XML was malformed + */ + if ($xml === false) { + Message::error( + __( + 'The XML file specified was either malformed or incomplete.' + . ' Please correct the issue and try again.' + ) + ) + ->display(); + unset($xml); + $GLOBALS['finished'] = false; + + return; + } + + /** + * Table accumulator + */ + $tables = array(); + /** + * Row accumulator + */ + $rows = array(); + + /** + * Temp arrays + */ + $tempRow = array(); + $tempCells = array(); + + /** + * CREATE code included (by default: no) + */ + $struct_present = false; + + /** + * Analyze the data in each table + */ + $namespaces = $xml->getNameSpaces(true); + + /** + * Get the database name, collation and charset + */ + $db_attr = $xml->children(isset($namespaces['pma']) ? $namespaces['pma'] : null) + ->{'structure_schemas'}->{'database'}; + + if ($db_attr instanceof SimpleXMLElement) { + $db_attr = $db_attr->attributes(); + $db_name = (string)$db_attr['name']; + $collation = (string)$db_attr['collation']; + $charset = (string)$db_attr['charset']; + } else { + /** + * If the structure section is not present + * get the database name from the data section + */ + $db_attr = $xml->children() + ->attributes(); + $db_name = (string)$db_attr['name']; + $collation = null; + $charset = null; + } + + /** + * The XML was malformed + */ + if ($db_name === null) { + Message::error( + __( + 'The XML file specified was either malformed or incomplete.' + . ' Please correct the issue and try again.' + ) + ) + ->display(); + unset($xml); + $GLOBALS['finished'] = false; + + return; + } + + /** + * Retrieve the structure information + */ + if (isset($namespaces['pma'])) { + /** + * Get structures for all tables + * + * @var SimpleXMLElement $struct + */ + $struct = $xml->children($namespaces['pma']); + + $create = array(); + + /** @var SimpleXMLElement $val1 */ + foreach ($struct as $val1) { + /** @var SimpleXMLElement $val2 */ + foreach ($val1 as $val2) { + // Need to select the correct database for the creation of + // tables, views, triggers, etc. + /** + * @todo Generating a USE here blocks importing of a table + * into another database. + */ + $attrs = $val2->attributes(); + $create[] = "USE " + . Util::backquote( + $attrs["name"] + ); + + foreach ($val2 as $val3) { + /** + * Remove the extra cosmetic spacing + */ + $val3 = str_replace(" ", "", (string)$val3); + $create[] = $val3; + } + } + } + + $struct_present = true; + } + + /** + * Move down the XML tree to the actual data + */ + $xml = $xml->children() + ->children(); + + $data_present = false; + + /** + * Only attempt to analyze/collect data if there is data present + */ + if ($xml && @count($xml->children())) { + $data_present = true; + + /** + * Process all database content + */ + foreach ($xml as $v1) { + $tbl_attr = $v1->attributes(); + + $isInTables = false; + $num_tables = count($tables); + for ($i = 0; $i < $num_tables; ++$i) { + if (!strcmp($tables[$i][Import::TBL_NAME], (string)$tbl_attr['name'])) { + $isInTables = true; + break; + } + } + + if (!$isInTables) { + $tables[] = array((string)$tbl_attr['name']); + } + + foreach ($v1 as $v2) { + $row_attr = $v2->attributes(); + if (!array_search((string)$row_attr['name'], $tempRow)) { + $tempRow[] = (string)$row_attr['name']; + } + $tempCells[] = (string)$v2; + } + + $rows[] = array((string)$tbl_attr['name'], $tempRow, $tempCells); + + $tempRow = array(); + $tempCells = array(); + } + + unset($tempRow); + unset($tempCells); + unset($xml); + + /** + * Bring accumulated rows into the corresponding table + */ + $num_tables = count($tables); + for ($i = 0; $i < $num_tables; ++$i) { + $num_rows = count($rows); + for ($j = 0; $j < $num_rows; ++$j) { + if (!strcmp($tables[$i][Import::TBL_NAME], $rows[$j][Import::TBL_NAME])) { + if (!isset($tables[$i][Import::COL_NAMES])) { + $tables[$i][] = $rows[$j][Import::COL_NAMES]; + } + + $tables[$i][Import::ROWS][] = $rows[$j][Import::ROWS]; + } + } + } + + unset($rows); + + if (!$struct_present) { + $analyses = array(); + + $len = count($tables); + for ($i = 0; $i < $len; ++$i) { + $analyses[] = Import::analyzeTable($tables[$i]); + } + } + } + + unset($xml); + unset($tempCells); + unset($rows); + + /** + * Only build SQL from data if there is data present + */ + if ($data_present) { + /** + * Set values to NULL if they were not present + * to maintain Import::buildSql() call integrity + */ + if (!isset($analyses)) { + $analyses = null; + if (!$struct_present) { + $create = null; + } + } + } + + /** + * string $db_name (no backquotes) + * + * array $table = array(table_name, array() column_names, array()() rows) + * array $tables = array of "$table"s + * + * array $analysis = array(array() column_types, array() column_sizes) + * array $analyses = array of "$analysis"s + * + * array $create = array of SQL strings + * + * array $options = an associative array of options + */ + + /* Set database name to the currently selected one, if applicable */ + if (strlen($db)) { + /* Override the database name in the XML file, if one is selected */ + $db_name = $db; + $options = array('create_db' => false); + } else { + if ($db_name === null) { + $db_name = 'XML_DB'; + } + + /* Set database collation/charset */ + $options = array( + 'db_collation' => $collation, + 'db_charset' => $charset, + ); + } + + /* Created and execute necessary SQL statements from data */ + Import::buildSql($db_name, $tables, $analyses, $create, $options, $sql_data); + + unset($analyses); + unset($tables); + unset($create); + + /* Commit any possible data in buffers */ + Import::runQuery('', '', $sql_data); + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Import/README b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Import/README new file mode 100644 index 00000000..20b856ee --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Import/README @@ -0,0 +1,153 @@ +This directory holds import plugins for phpMyAdmin. Any new plugin should +basically follow the structure presented here. The messages must use our +gettext mechanism, see https://wiki.phpmyadmin.net/pma/Gettext_for_developers. + +setProperties(); + } + + /** + * Sets the import plugin properties. + * Called in the constructor. + * + * @return void + */ + protected function setProperties() + { + $importPluginProperties = new PhpMyAdmin\Properties\Plugins\ImportPluginProperties(); + $importPluginProperties->setText('[name]'); // the name of your plug-in + $importPluginProperties->setExtension('[ext]'); // extension this plug-in can handle + $importPluginProperties->setOptionsText(__('Options')); + + // create the root group that will be the options field for + // $importPluginProperties + // this will be shown as "Format specific options" + $importSpecificOptions = new + PhpMyAdmin\Properties\Options\Groups\OptionsPropertyRootGroup( + "Format Specific Options" + ); + + // general options main group + $generalOptions = new PhpMyAdmin\Properties\Options\Groups\OptionsPropertyMainGroup( + "general_opts" + ); + + // optional : + // create primary items and add them to the group + // type - one of the classes listed in libraries/properties/options/items/ + // name - form element name + // text - description in GUI + // size - size of text element + // len - maximal size of input + // values - possible values of the item + $leaf = new PhpMyAdmin\Properties\Options\Items\RadioPropertyItem( + "structure_or_data" + ); + $leaf->setValues( + array( + 'structure' => __('structure'), + 'data' => __('data'), + 'structure_and_data' => __('structure and data') + ) + ); + $generalOptions->addProperty($leaf); + + // add the main group to the root group + $importSpecificOptions->addProperty($generalOptions); + + // set the options for the import plugin property item + $importPluginProperties->setOptions($importSpecificOptions); + $this->properties = $importPluginProperties; + } + + /** + * Handles the whole import logic + * + * @param array &$sql_data 2-element array with sql data + * + * @return void + */ + public function doImport(&$sql_data = array()) + { + // get globals (others are optional) + global $error, $timeout_passed, $finished; + + $buffer = ''; + while (! ($finished && $i >= $len) && ! $error && ! $timeout_passed) { + $data = Import::getNextChunk(); + if ($data === false) { + // subtract data we didn't handle yet and stop processing + $GLOBALS['offset'] -= strlen($buffer); + break; + } elseif ($data === true) { + // Handle rest of buffer + } else { + // Append new data to buffer + $buffer .= $data; + } + // PARSE $buffer here, post sql queries using: + Import::runQuery($sql, $verbose_sql_with_comments, $sql_data); + } // End of import loop + // Commit any possible data in buffers + Import::runQuery('', '', $sql_data); + } + + + // optional: + /* ~~~~~~~~~~~~~~~~~~~~ Getters and Setters ~~~~~~~~~~~~~~~~~~~~ */ + + + /** + * Getter description + * + * @return type + */ + private function _getMyOptionalVariable() + { + return $this->_myOptionalVariable; + } + + /** + * Setter description + * + * @param type $my_optional_variable description + * + * @return void + */ + private function _setMyOptionalVariable($my_optional_variable) + { + $this->_myOptionalVariable = $my_optional_variable; + } +} +?> diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Import/ShapeFileImport.php b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Import/ShapeFileImport.php new file mode 100644 index 00000000..eadb52e1 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Import/ShapeFileImport.php @@ -0,0 +1,44 @@ + $id, + 'finished' => false, + 'percent' => 0, + 'total' => 0, + 'complete' => 0, + 'plugin' => UploadApc::getIdKey(), + ); + } + $ret = $_SESSION[$SESSION_KEY][$id]; + + if (!ImportAjax::apcCheck() || $ret['finished']) { + return $ret; + } + $status = apc_fetch('upload_' . $id); + + if ($status) { + $ret['finished'] = (bool)$status['done']; + $ret['total'] = $status['total']; + $ret['complete'] = $status['current']; + + if ($ret['total'] > 0) { + $ret['percent'] = $ret['complete'] / $ret['total'] * 100; + } + + if ($ret['percent'] == 100) { + $ret['finished'] = (bool)true; + } + + $_SESSION[$SESSION_KEY][$id] = $ret; + } + + return $ret; + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Import/Upload/UploadNoplugin.php b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Import/Upload/UploadNoplugin.php new file mode 100644 index 00000000..b2539fe9 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Import/Upload/UploadNoplugin.php @@ -0,0 +1,60 @@ + $id, + 'finished' => false, + 'percent' => 0, + 'total' => 0, + 'complete' => 0, + 'plugin' => UploadNoplugin::getIdKey(), + ); + } + $ret = $_SESSION[$SESSION_KEY][$id]; + + return $ret; + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Import/Upload/UploadProgress.php b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Import/Upload/UploadProgress.php new file mode 100644 index 00000000..bc68c73d --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Import/Upload/UploadProgress.php @@ -0,0 +1,92 @@ + $id, + 'finished' => false, + 'percent' => 0, + 'total' => 0, + 'complete' => 0, + 'plugin' => UploadProgress::getIdKey(), + ); + } + $ret = $_SESSION[$SESSION_KEY][$id]; + + if (!ImportAjax::progressCheck() || $ret['finished']) { + return $ret; + } + + $status = uploadprogress_get_info($id); + + if ($status) { + if ($status['bytes_uploaded'] == $status['bytes_total']) { + $ret['finished'] = true; + } else { + $ret['finished'] = false; + } + $ret['total'] = $status['bytes_total']; + $ret['complete'] = $status['bytes_uploaded']; + + if ($ret['total'] > 0) { + $ret['percent'] = $ret['complete'] / $ret['total'] * 100; + } + } else { + $ret = array( + 'id' => $id, + 'finished' => true, + 'percent' => 100, + 'total' => $ret['total'], + 'complete' => $ret['total'], + 'plugin' => UploadProgress::getIdKey(), + ); + } + + $_SESSION[$SESSION_KEY][$id] = $ret; + + return $ret; + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Import/Upload/UploadSession.php b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Import/Upload/UploadSession.php new file mode 100644 index 00000000..5c2ded03 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Import/Upload/UploadSession.php @@ -0,0 +1,93 @@ + $id, + 'finished' => false, + 'percent' => 0, + 'total' => 0, + 'complete' => 0, + 'plugin' => UploadSession::getIdKey(), + ); + } + $ret = $_SESSION[$SESSION_KEY][$id]; + + if (!ImportAjax::sessionCheck() || $ret['finished']) { + return $ret; + } + + $status = false; + $sessionkey = ini_get('session.upload_progress.prefix') . $id; + + if (isset($_SESSION[$sessionkey])) { + $status = $_SESSION[$sessionkey]; + } + + if ($status) { + $ret['finished'] = $status['done']; + $ret['total'] = $status['content_length']; + $ret['complete'] = $status['bytes_processed']; + + if ($ret['total'] > 0) { + $ret['percent'] = $ret['complete'] / $ret['total'] * 100; + } + } else { + $ret = array( + 'id' => $id, + 'finished' => true, + 'percent' => 100, + 'total' => $ret['total'], + 'complete' => $ret['total'], + 'plugin' => UploadSession::getIdKey(), + ); + } + + $_SESSION[$SESSION_KEY][$id] = $ret; + + return $ret; + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Plugins/ImportPlugin.php b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/ImportPlugin.php new file mode 100644 index 00000000..42ed2286 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/ImportPlugin.php @@ -0,0 +1,76 @@ +properties; + } + + /** + * Sets the export plugins properties and is implemented by each import + * plugin + * + * @return void + */ + abstract protected function setProperties(); + + /** + * Define DB name and options + * + * @param string $currentDb DB + * @param string $defaultDb Default DB name + * + * @return array DB name and options (an associative array of options) + */ + protected function getDbnameAndOptions($currentDb, $defaultDb) + { + if (strlen($currentDb) > 0) { + $db_name = $currentDb; + $options = array('create_db' => false); + } else { + $db_name = $defaultDb; + $options = null; + } + + return array($db_name, $options); + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Schema/Dia/Dia.php b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Schema/Dia/Dia.php new file mode 100644 index 00000000..b1fe5027 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Schema/Dia/Dia.php @@ -0,0 +1,188 @@ +openMemory(); + /* + * Set indenting using three spaces, + * so output is formatted + */ + $this->setIndent(true); + $this->setIndentString(' '); + /* + * Create the XML document + */ + $this->startDocument('1.0', 'UTF-8'); + } + + /** + * Starts Dia Document + * + * dia document starts by first initializing dia:diagram tag + * then dia:diagramdata contains all the attributes that needed + * to define the document, then finally a Layer starts which + * holds all the objects. + * + * @param string $paper the size of the paper/document + * @param float $topMargin top margin of the paper/document in cm + * @param float $bottomMargin bottom margin of the paper/document in cm + * @param float $leftMargin left margin of the paper/document in cm + * @param float $rightMargin right margin of the paper/document in cm + * @param string $orientation orientation of the document, portrait or landscape + * + * @return void + * + * @access public + * @see XMLWriter::startElement(),XMLWriter::writeAttribute(), + * XMLWriter::writeRaw() + */ + public function startDiaDoc( + $paper, + $topMargin, + $bottomMargin, + $leftMargin, + $rightMargin, + $orientation + ) { + if ($orientation == 'P') { + $isPortrait = 'true'; + } else { + $isPortrait = 'false'; + } + $this->startElement('dia:diagram'); + $this->writeAttribute('xmlns:dia', 'http://www.lysator.liu.se/~alla/dia/'); + $this->startElement('dia:diagramdata'); + $this->writeRaw( + ' + + + + + + + + + #' . $paper . '# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ' + ); + $this->endElement(); + $this->startElement('dia:layer'); + $this->writeAttribute('name', 'Background'); + $this->writeAttribute('visible', 'true'); + $this->writeAttribute('active', 'true'); + } + + /** + * Ends Dia Document + * + * @return void + * @access public + * @see XMLWriter::endElement(),XMLWriter::endDocument() + */ + public function endDiaDoc() + { + $this->endElement(); + $this->endDocument(); + } + + /** + * Output Dia Document for download + * + * @param string $fileName name of the dia document + * + * @return void + * @access public + * @see XMLWriter::flush() + */ + public function showOutput($fileName) + { + if (ob_get_clean()) { + ob_end_clean(); + } + $output = $this->flush(); + Response::getInstance()->disable(); + Core::downloadHeader( + $fileName, + 'application/x-dia-diagram', + strlen($output) + ); + print $output; + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Schema/Dia/DiaRelationSchema.php b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Schema/Dia/DiaRelationSchema.php new file mode 100644 index 00000000..8797961c --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Schema/Dia/DiaRelationSchema.php @@ -0,0 +1,228 @@ +setShowColor(isset($_REQUEST['dia_show_color'])); + $this->setShowKeys(isset($_REQUEST['dia_show_keys'])); + $this->setOrientation($_REQUEST['dia_orientation']); + $this->setPaper($_REQUEST['dia_paper']); + + $this->diagram->startDiaDoc( + $this->paper, + $this->_topMargin, + $this->_bottomMargin, + $this->_leftMargin, + $this->_rightMargin, + $this->orientation + ); + + $alltables = $this->getTablesFromRequest(); + + foreach ($alltables as $table) { + if (!isset($this->tables[$table])) { + $this->_tables[$table] = new TableStatsDia( + $this->diagram, $this->db, $table, $this->pageNumber, + $this->showKeys, $this->offline + ); + } + } + + $seen_a_relation = false; + foreach ($alltables as $one_table) { + $exist_rel = $this->relation->getForeigners($this->db, $one_table, '', 'both'); + if (!$exist_rel) { + continue; + } + + $seen_a_relation = true; + foreach ($exist_rel as $master_field => $rel) { + /* put the foreign table on the schema only if selected + * by the user + * (do not use array_search() because we would have to + * to do a === false and this is not PHP3 compatible) + */ + if ($master_field != 'foreign_keys_data') { + if (in_array($rel['foreign_table'], $alltables)) { + $this->_addRelation( + $one_table, + $master_field, + $rel['foreign_table'], + $rel['foreign_field'], + $this->showKeys + ); + } + continue; + } + + foreach ($rel as $one_key) { + if (!in_array($one_key['ref_table_name'], $alltables)) { + continue; + } + + foreach ($one_key['index_list'] as $index => $one_field) { + $this->_addRelation( + $one_table, + $one_field, + $one_key['ref_table_name'], + $one_key['ref_index_list'][$index], + $this->showKeys + ); + } + } + } + } + $this->_drawTables(); + + if ($seen_a_relation) { + $this->_drawRelations(); + } + $this->diagram->endDiaDoc(); + } + + /** + * Output Dia Document for download + * + * @return void + * @access public + */ + public function showOutput() + { + $this->diagram->showOutput($this->getFileName('.dia')); + } + + /** + * Defines relation objects + * + * @param string $masterTable The master table name + * @param string $masterField The relation field in the master table + * @param string $foreignTable The foreign table name + * @param string $foreignField The relation field in the foreign table + * @param bool $showKeys Whether to display ONLY keys or not + * + * @return void + * + * @access private + * @see TableStatsDia::__construct(),RelationStatsDia::__construct() + */ + private function _addRelation( + $masterTable, + $masterField, + $foreignTable, + $foreignField, + $showKeys + ) { + if (!isset($this->_tables[$masterTable])) { + $this->_tables[$masterTable] = new TableStatsDia( + $this->diagram, $this->db, $masterTable, $this->pageNumber, $showKeys + ); + } + if (!isset($this->_tables[$foreignTable])) { + $this->_tables[$foreignTable] = new TableStatsDia( + $this->diagram, + $this->db, + $foreignTable, + $this->pageNumber, + $showKeys + ); + } + $this->_relations[] = new RelationStatsDia( + $this->diagram, + $this->_tables[$masterTable], + $masterField, + $this->_tables[$foreignTable], + $foreignField + ); + } + + /** + * Draws relation references + * + * connects master table's master field to + * foreign table's foreign field using Dia object + * type Database - Reference + * + * @return void + * + * @access private + * @see RelationStatsDia::relationDraw() + */ + private function _drawRelations() + { + foreach ($this->_relations as $relation) { + $relation->relationDraw($this->showColor); + } + } + + /** + * Draws tables + * + * Tables are generated using Dia object type Database - Table + * primary fields are underlined and bold in tables + * + * @return void + * + * @access private + * @see TableStatsDia::tableDraw() + */ + private function _drawTables() + { + foreach ($this->_tables as $table) { + $table->tableDraw($this->showColor); + } + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Schema/Dia/RelationStatsDia.php b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Schema/Dia/RelationStatsDia.php new file mode 100644 index 00000000..d645f667 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Schema/Dia/RelationStatsDia.php @@ -0,0 +1,214 @@ +diagram = $diagram; + $src_pos = $this->_getXy($master_table, $master_field); + $dest_pos = $this->_getXy($foreign_table, $foreign_field); + $this->srcConnPointsLeft = $src_pos[0]; + $this->srcConnPointsRight = $src_pos[1]; + $this->destConnPointsLeft = $dest_pos[0]; + $this->destConnPointsRight = $dest_pos[1]; + $this->masterTablePos = $src_pos[2]; + $this->foreignTablePos = $dest_pos[2]; + $this->masterTableId = $master_table->tableId; + $this->foreignTableId = $foreign_table->tableId; + } + + /** + * Each Table object have connection points + * which is used to connect to other objects in Dia + * we detect the position of key in fields and + * then determines its left and right connection + * points. + * + * @param string $table The current table name + * @param string $column The relation column name + * + * @return array Table right,left connection points and key position + * + * @access private + */ + private function _getXy($table, $column) + { + $pos = array_search($column, $table->fields); + // left, right, position + $value = 12; + if ($pos != 0) { + return array($pos + $value + $pos, $pos + $value + $pos + 1, $pos); + } + return array($pos + $value , $pos + $value + 1, $pos); + } + + /** + * Draws relation references + * + * connects master table's master field to foreign table's + * foreign field using Dia object type Database - Reference + * Dia object is used to generate the XML of Dia Document. + * Database reference Object and their attributes are involved + * in the combination of displaying Database - reference on Dia Document. + * + * @param boolean $showColor Whether to use one color per relation or not + * if showColor is true then an array of $listOfColors + * will be used to choose the random colors for + * references lines. we can change/add more colors to + * this + * + * @return boolean|void + * + * @access public + * @see PDF + */ + public function relationDraw($showColor) + { + ++DiaRelationSchema::$objectId; + /* + * if source connection points and destination connection + * points are same then return it false and don't draw that + * relation + */ + if ($this->srcConnPointsRight == $this->destConnPointsRight) { + if ($this->srcConnPointsLeft == $this->destConnPointsLeft) { + return false; + } + } + + if ($showColor) { + $listOfColors = array( + 'FF0000', + '000099', + '00FF00', + ); + shuffle($listOfColors); + $this->referenceColor = '#' . $listOfColors[0] . ''; + } else { + $this->referenceColor = '#000000'; + } + + $this->diagram->writeRaw( + ' + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #1# + + + #n# + + + + + + + + + + + + ' + ); + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Schema/Dia/TableStatsDia.php b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Schema/Dia/TableStatsDia.php new file mode 100644 index 00000000..470f762c --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Schema/Dia/TableStatsDia.php @@ -0,0 +1,229 @@ +tableId = ++DiaRelationSchema::$objectId; + } + + /** + * Displays an error when the table cannot be found. + * + * @return void + */ + protected function showMissingTableError() + { + ExportRelationSchema::dieSchema( + $this->pageNumber, + "DIA", + sprintf(__('The %s table doesn\'t exist!'), $this->tableName) + ); + } + + /** + * Do draw the table + * + * Tables are generated using object type Database - Table + * primary fields are underlined in tables. Dia object + * is used to generate the XML of Dia Document. Database Table + * Object and their attributes are involved in the combination + * of displaying Database - Table on Dia Document. + * + * @param boolean $showColor Whether to show color for tables text or not + * if showColor is true then an array of $listOfColors + * will be used to choose the random colors for tables + * text we can change/add more colors to this array + * + * @return void + * + * @access public + * @see Dia + */ + public function tableDraw($showColor) + { + if ($showColor) { + $listOfColors = array( + 'FF0000', + '000099', + '00FF00' + ); + shuffle($listOfColors); + $this->tableColor = '#' . $listOfColors[0] . ''; + } else { + $this->tableColor = '#000000'; + } + + $factor = 0.1; + + $this->diagram->startElement('dia:object'); + $this->diagram->writeAttribute('type', 'Database - Table'); + $this->diagram->writeAttribute('version', '0'); + $this->diagram->writeAttribute('id', '' . $this->tableId . ''); + $this->diagram->writeRaw( + ' + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #' . $this->tableName . '# + + + ## + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ' + ); + + $this->diagram->startElement('dia:attribute'); + $this->diagram->writeAttribute('name', 'attributes'); + + foreach ($this->fields as $field) { + $this->diagram->writeRaw( + ' + + #' . $field . '# + + + ## + + + ## + ' + ); + unset($pm); + $pm = 'false'; + if (in_array($field, $this->primary)) { + $pm = 'true'; + } + if ($field == $this->displayfield) { + $pm = 'false'; + } + $this->diagram->writeRaw( + ' + + + + + + + + + ' + ); + } + $this->diagram->endElement(); + $this->diagram->endElement(); + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Schema/Eps/Eps.php b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Schema/Eps/Eps.php new file mode 100644 index 00000000..e71b1357 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Schema/Eps/Eps.php @@ -0,0 +1,278 @@ +stringCommands = ""; + $this->stringCommands .= "%!PS-Adobe-3.0 EPSF-3.0 \n"; + } + + /** + * Set document title + * + * @param string $value sets the title text + * + * @return void + */ + public function setTitle($value) + { + $this->stringCommands .= '%%Title: ' . $value . "\n"; + } + + /** + * Set document author + * + * @param string $value sets the author + * + * @return void + */ + public function setAuthor($value) + { + $this->stringCommands .= '%%Creator: ' . $value . "\n"; + } + + /** + * Set document creation date + * + * @param string $value sets the date + * + * @return void + */ + public function setDate($value) + { + $this->stringCommands .= '%%CreationDate: ' . $value . "\n"; + } + + /** + * Set document orientation + * + * @param string $orientation sets the orientation + * + * @return void + */ + public function setOrientation($orientation) + { + $this->stringCommands .= "%%PageOrder: Ascend \n"; + if ($orientation == "L") { + $orientation = "Landscape"; + $this->stringCommands .= '%%Orientation: ' . $orientation . "\n"; + } else { + $orientation = "Portrait"; + $this->stringCommands .= '%%Orientation: ' . $orientation . "\n"; + } + $this->stringCommands .= "%%EndComments \n"; + $this->stringCommands .= "%%Pages 1 \n"; + $this->stringCommands .= "%%BoundingBox: 72 150 144 170 \n"; + } + + /** + * Set the font and size + * + * font can be set whenever needed in EPS + * + * @param string $value sets the font name e.g Arial + * @param integer $size sets the size of the font e.g 10 + * + * @return void + */ + public function setFont($value, $size) + { + $this->font = $value; + $this->fontSize = $size; + $this->stringCommands .= "/" . $value . " findfont % Get the basic font\n"; + $this->stringCommands .= "" + . $size . " scalefont % Scale the font to $size points\n"; + $this->stringCommands + .= "setfont % Make it the current font\n"; + } + + /** + * Get the font + * + * @return string return the font name e.g Arial + */ + public function getFont() + { + return $this->font; + } + + /** + * Get the font Size + * + * @return string return the size of the font e.g 10 + */ + public function getFontSize() + { + return $this->fontSize; + } + + /** + * Draw the line + * + * drawing the lines from x,y source to x,y destination and set the + * width of the line. lines helps in showing relationships of tables + * + * @param integer $x_from The x_from attribute defines the start + * left position of the element + * @param integer $y_from The y_from attribute defines the start + * right position of the element + * @param integer $x_to The x_to attribute defines the end + * left position of the element + * @param integer $y_to The y_to attribute defines the end + * right position of the element + * @param integer $lineWidth Sets the width of the line e.g 2 + * + * @return void + */ + public function line( + $x_from = 0, + $y_from = 0, + $x_to = 0, + $y_to = 0, + $lineWidth = 0 + ) { + $this->stringCommands .= $lineWidth . " setlinewidth \n"; + $this->stringCommands .= $x_from . ' ' . $y_from . " moveto \n"; + $this->stringCommands .= $x_to . ' ' . $y_to . " lineto \n"; + $this->stringCommands .= "stroke \n"; + } + + /** + * Draw the rectangle + * + * drawing the rectangle from x,y source to x,y destination and set the + * width of the line. rectangles drawn around the text shown of fields + * + * @param integer $x_from The x_from attribute defines the start + * left position of the element + * @param integer $y_from The y_from attribute defines the start + * right position of the element + * @param integer $x_to The x_to attribute defines the end + * left position of the element + * @param integer $y_to The y_to attribute defines the end + * right position of the element + * @param integer $lineWidth Sets the width of the line e.g 2 + * + * @return void + */ + public function rect($x_from, $y_from, $x_to, $y_to, $lineWidth) + { + $this->stringCommands .= $lineWidth . " setlinewidth \n"; + $this->stringCommands .= "newpath \n"; + $this->stringCommands .= $x_from . " " . $y_from . " moveto \n"; + $this->stringCommands .= "0 " . $y_to . " rlineto \n"; + $this->stringCommands .= $x_to . " 0 rlineto \n"; + $this->stringCommands .= "0 -" . $y_to . " rlineto \n"; + $this->stringCommands .= "closepath \n"; + $this->stringCommands .= "stroke \n"; + } + + /** + * Set the current point + * + * The moveto operator takes two numbers off the stack and treats + * them as x and y coordinates to which to move. The coordinates + * specified become the current point. + * + * @param integer $x The x attribute defines the left position of the element + * @param integer $y The y attribute defines the right position of the element + * + * @return void + */ + public function moveTo($x, $y) + { + $this->stringCommands .= $x . ' ' . $y . " moveto \n"; + } + + /** + * Output/Display the text + * + * @param string $text The string to be displayed + * + * @return void + */ + public function show($text) + { + $this->stringCommands .= '(' . $text . ") show \n"; + } + + /** + * Output the text at specified co-ordinates + * + * @param string $text String to be displayed + * @param integer $x X attribute defines the left position of the element + * @param integer $y Y attribute defines the right position of the element + * + * @return void + */ + public function showXY($text, $x, $y) + { + $this->moveTo($x, $y); + $this->show($text); + } + + /** + * Ends EPS Document + * + * @return void + */ + public function endEpsDoc() + { + $this->stringCommands .= "showpage \n"; + } + + /** + * Output EPS Document for download + * + * @param string $fileName name of the eps document + * + * @return void + */ + public function showOutput($fileName) + { + // if(ob_get_clean()){ + //ob_end_clean(); + //} + $output = $this->stringCommands; + Response::getInstance() + ->disable(); + Core::downloadHeader( + $fileName, + 'image/x-eps', + strlen($output) + ); + print $output; + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Schema/Eps/EpsRelationSchema.php b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Schema/Eps/EpsRelationSchema.php new file mode 100644 index 00000000..6056b157 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Schema/Eps/EpsRelationSchema.php @@ -0,0 +1,224 @@ +setShowColor(isset($_REQUEST['eps_show_color'])); + $this->setShowKeys(isset($_REQUEST['eps_show_keys'])); + $this->setTableDimension(isset($_REQUEST['eps_show_table_dimension'])); + $this->setAllTablesSameWidth(isset($_REQUEST['eps_all_tables_same_width'])); + $this->setOrientation($_REQUEST['eps_orientation']); + + $this->diagram->setTitle( + sprintf( + __('Schema of the %s database - Page %s'), + $this->db, + $this->pageNumber + ) + ); + $this->diagram->setAuthor('phpMyAdmin ' . PMA_VERSION); + $this->diagram->setDate(date("j F Y, g:i a")); + $this->diagram->setOrientation($this->orientation); + $this->diagram->setFont('Verdana', '10'); + + $alltables = $this->getTablesFromRequest(); + + foreach ($alltables as $table) { + if (! isset($this->_tables[$table])) { + $this->_tables[$table] = new TableStatsEps( + $this->diagram, $this->db, + $table, $this->diagram->getFont(), + $this->diagram->getFontSize(), $this->pageNumber, + $this->_tablewidth, $this->showKeys, + $this->tableDimension, $this->offline + ); + } + + if ($this->sameWide) { + $this->_tables[$table]->width = $this->_tablewidth; + } + } + + $seen_a_relation = false; + foreach ($alltables as $one_table) { + $exist_rel = $this->relation->getForeigners($this->db, $one_table, '', 'both'); + if (!$exist_rel) { + continue; + } + + $seen_a_relation = true; + foreach ($exist_rel as $master_field => $rel) { + /* put the foreign table on the schema only if selected + * by the user + * (do not use array_search() because we would have to + * to do a === false and this is not PHP3 compatible) + */ + if ($master_field != 'foreign_keys_data') { + if (in_array($rel['foreign_table'], $alltables)) { + $this->_addRelation( + $one_table, $this->diagram->getFont(), $this->diagram->getFontSize(), + $master_field, $rel['foreign_table'], + $rel['foreign_field'], $this->tableDimension + ); + } + continue; + } + + foreach ($rel as $one_key) { + if (!in_array($one_key['ref_table_name'], $alltables)) { + continue; + } + + foreach ($one_key['index_list'] + as $index => $one_field + ) { + $this->_addRelation( + $one_table, $this->diagram->getFont(), + $this->diagram->getFontSize(), + $one_field, $one_key['ref_table_name'], + $one_key['ref_index_list'][$index], + $this->tableDimension + ); + } + } + } + } + if ($seen_a_relation) { + $this->_drawRelations(); + } + + $this->_drawTables(); + $this->diagram->endEpsDoc(); + } + + /** + * Output Eps Document for download + * + * @return void + */ + public function showOutput() + { + $this->diagram->showOutput($this->getFileName('.eps')); + } + + /** + * Defines relation objects + * + * @param string $masterTable The master table name + * @param string $font The font + * @param int $fontSize The font size + * @param string $masterField The relation field in the master table + * @param string $foreignTable The foreign table name + * @param string $foreignField The relation field in the foreign table + * @param boolean $tableDimension Whether to display table position or not + * + * @return void + * + * @see _setMinMax,Table_Stats_Eps::__construct(), + * PhpMyAdmin\Plugins\Schema\Eps\RelationStatsEps::__construct() + */ + private function _addRelation( + $masterTable, $font, $fontSize, $masterField, + $foreignTable, $foreignField, $tableDimension + ) { + if (! isset($this->_tables[$masterTable])) { + $this->_tables[$masterTable] = new TableStatsEps( + $this->diagram, $this->db, $masterTable, $font, $fontSize, + $this->pageNumber, $this->_tablewidth, false, $tableDimension + ); + } + if (! isset($this->_tables[$foreignTable])) { + $this->_tables[$foreignTable] = new TableStatsEps( + $this->diagram, $this->db, $foreignTable, $font, $fontSize, + $this->pageNumber, $this->_tablewidth, false, $tableDimension + ); + } + $this->_relations[] = new RelationStatsEps( + $this->diagram, + $this->_tables[$masterTable], + $masterField, + $this->_tables[$foreignTable], + $foreignField + ); + } + + /** + * Draws relation arrows and lines connects master table's master field to + * foreign table's foreign field + * + * @return void + * + * @see Relation_Stats_Eps::relationDraw() + */ + private function _drawRelations() + { + foreach ($this->_relations as $relation) { + $relation->relationDraw(); + } + } + + /** + * Draws tables + * + * @return void + * + * @see Table_Stats_Eps::Table_Stats_tableDraw() + */ + private function _drawTables() + { + foreach ($this->_tables as $table) { + $table->tableDraw($this->showColor); + } + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Schema/Eps/RelationStatsEps.php b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Schema/Eps/RelationStatsEps.php new file mode 100644 index 00000000..61b7f6c2 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Schema/Eps/RelationStatsEps.php @@ -0,0 +1,110 @@ +wTick = 10; + parent::__construct( + $diagram, $master_table, $master_field, $foreign_table, $foreign_field + ); + $this->ySrc += 10; + $this->yDest += 10; + } + + /** + * draws relation links and arrows + * shows foreign key relations + * + * @see PMA_EPS + * + * @return void + */ + public function relationDraw() + { + // draw a line like -- to foreign field + $this->diagram->line( + $this->xSrc, + $this->ySrc, + $this->xSrc + $this->srcDir * $this->wTick, + $this->ySrc, + 1 + ); + // draw a line like -- to master field + $this->diagram->line( + $this->xDest + $this->destDir * $this->wTick, + $this->yDest, + $this->xDest, + $this->yDest, + 1 + ); + // draw a line that connects to master field line and foreign field line + $this->diagram->line( + $this->xSrc + $this->srcDir * $this->wTick, + $this->ySrc, + $this->xDest + $this->destDir * $this->wTick, + $this->yDest, + 1 + ); + $root2 = 2 * sqrt(2); + $this->diagram->line( + $this->xSrc + $this->srcDir * $this->wTick * 0.75, + $this->ySrc, + $this->xSrc + $this->srcDir * (0.75 - 1 / $root2) * $this->wTick, + $this->ySrc + $this->wTick / $root2, + 1 + ); + $this->diagram->line( + $this->xSrc + $this->srcDir * $this->wTick * 0.75, + $this->ySrc, + $this->xSrc + $this->srcDir * (0.75 - 1 / $root2) * $this->wTick, + $this->ySrc - $this->wTick / $root2, + 1 + ); + $this->diagram->line( + $this->xDest + $this->destDir * $this->wTick / 2, + $this->yDest, + $this->xDest + $this->destDir * (0.5 + 1 / $root2) * $this->wTick, + $this->yDest + $this->wTick / $root2, + 1 + ); + $this->diagram->line( + $this->xDest + $this->destDir * $this->wTick / 2, + $this->yDest, + $this->xDest + $this->destDir * (0.5 + 1 / $root2) * $this->wTick, + $this->yDest - $this->wTick / $root2, + 1 + ); + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Schema/Eps/TableStatsEps.php b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Schema/Eps/TableStatsEps.php new file mode 100644 index 00000000..c1059dee --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Schema/Eps/TableStatsEps.php @@ -0,0 +1,182 @@ +_setHeightTable($fontSize); + // setWidth must me after setHeight, because title + // can include table height which changes table width + $this->_setWidthTable($font, $fontSize); + if ($same_wide_width < $this->width) { + $same_wide_width = $this->width; + } + } + + /** + * Displays an error when the table cannot be found. + * + * @return void + */ + protected function showMissingTableError() + { + ExportRelationSchema::dieSchema( + $this->pageNumber, + "EPS", + sprintf(__('The %s table doesn\'t exist!'), $this->tableName) + ); + } + + /** + * Sets the width of the table + * + * @param string $font The font name + * @param integer $fontSize The font size + * + * @return void + * + * @see PMA_EPS + */ + private function _setWidthTable($font, $fontSize) + { + foreach ($this->fields as $field) { + $this->width = max( + $this->width, + Font::getStringWidth($field, $font, $fontSize) + ); + } + $this->width += Font::getStringWidth( + ' ', + $font, + $fontSize + ); + /* + * it is unknown what value must be added, because + * table title is affected by the table width value + */ + while ($this->width + < Font::getStringWidth( + $this->getTitle(), + $font, + $fontSize + )) { + $this->width += 7; + } + } + + /** + * Sets the height of the table + * + * @param integer $fontSize The font size + * + * @return void + */ + private function _setHeightTable($fontSize) + { + $this->heightCell = $fontSize + 4; + $this->height = (count($this->fields) + 1) * $this->heightCell; + } + + /** + * Draw the table + * + * @param boolean $showColor Whether to display color + * + * @return void + * + * @see PMA_EPS,PMA_EPS::line,PMA_EPS::rect + */ + public function tableDraw($showColor) + { + //echo $this->tableName.'
    '; + $this->diagram->rect( + $this->x, + $this->y + 12, + $this->width, + $this->heightCell, + 1 + ); + $this->diagram->showXY($this->getTitle(), $this->x + 5, $this->y + 14); + foreach ($this->fields as $field) { + $this->currentCell += $this->heightCell; + $this->diagram->rect( + $this->x, + $this->y + 12 + $this->currentCell, + $this->width, + $this->heightCell, + 1 + ); + $this->diagram->showXY( + $field, + $this->x + 5, + $this->y + 14 + $this->currentCell + ); + } + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Schema/ExportRelationSchema.php b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Schema/ExportRelationSchema.php new file mode 100644 index 00000000..ca9ca561 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Schema/ExportRelationSchema.php @@ -0,0 +1,307 @@ +db = $db; + $this->diagram = $diagram; + $this->setPageNumber($_REQUEST['page_number']); + $this->setOffline(isset($_REQUEST['offline_export'])); + $this->relation = new Relation(); + } + + /** + * Set Page Number + * + * @param integer $value Page Number of the document to be created + * + * @return void + */ + public function setPageNumber($value) + { + $this->pageNumber = intval($value); + } + + /** + * Returns the schema page number + * + * @return integer schema page number + */ + public function getPageNumber() + { + return $this->pageNumber; + } + + /** + * Sets showColor + * + * @param boolean $value whether to show colors + * + * @return void + */ + public function setShowColor($value) + { + $this->showColor = $value; + } + + /** + * Returns whether to show colors + * + * @return boolean whether to show colors + */ + public function isShowColor() + { + return $this->showColor; + } + + /** + * Set Table Dimension + * + * @param boolean $value show table co-ordinates or not + * + * @return void + */ + public function setTableDimension($value) + { + $this->tableDimension = $value; + } + + /** + * Returns whether to show table dimensions + * + * @return boolean whether to show table dimensions + */ + public function isTableDimension() + { + return $this->tableDimension; + } + + /** + * Set same width of All Tables + * + * @param boolean $value set same width of all tables or not + * + * @return void + */ + public function setAllTablesSameWidth($value) + { + $this->sameWide = $value; + } + + /** + * Returns whether to use same width for all tables or not + * + * @return boolean whether to use same width for all tables or not + */ + public function isAllTableSameWidth() + { + return $this->sameWide; + } + + /** + * Set Show only keys + * + * @param boolean $value show only keys or not + * + * @return void + * + * @access public + */ + public function setShowKeys($value) + { + $this->showKeys = $value; + } + + /** + * Returns whether to show keys + * + * @return boolean whether to show keys + */ + public function isShowKeys() + { + return $this->showKeys; + } + + /** + * Set Orientation + * + * @param string $value Orientation will be portrait or landscape + * + * @return void + * + * @access public + */ + public function setOrientation($value) + { + $this->orientation = ($value == 'P') ? 'P' : 'L'; + } + + /** + * Returns orientation + * + * @return string orientation + */ + public function getOrientation() + { + return $this->orientation; + } + + /** + * Set type of paper + * + * @param string $value paper type can be A4 etc + * + * @return void + * + * @access public + */ + public function setPaper($value) + { + $this->paper = $value; + } + + /** + * Returns the paper size + * + * @return string paper size + */ + public function getPaper() + { + return $this->paper; + } + + /** + * Set whether the document is generated from client side DB + * + * @param boolean $value offline or not + * + * @return void + * + * @access public + */ + public function setOffline($value) + { + $this->offline = $value; + } + + /** + * Returns whether the client side database is used + * + * @return boolean + * + * @access public + */ + public function isOffline() + { + return $this->offline; + } + + /** + * Get the table names from the request + * + * @return array an array of table names + */ + protected function getTablesFromRequest() + { + $tables = []; + if (isset($_POST['t_tbl'])) { + foreach($_POST['t_tbl'] as $table) { + $tables[] = rawurldecode($table); + } + } + return $tables; + } + + /** + * Returns the file name + * + * @param String $extension file extension + * + * @return string file name + */ + protected function getFileName($extension) + { + $filename = $this->db . $extension; + // Get the name of this page to use as filename + if ($this->pageNumber != -1 && !$this->offline) { + $_name_sql = 'SELECT page_descr FROM ' + . Util::backquote($GLOBALS['cfgRelation']['db']) . '.' + . Util::backquote($GLOBALS['cfgRelation']['pdf_pages']) + . ' WHERE page_nr = ' . $this->pageNumber; + $_name_rs = $this->relation->queryAsControlUser($_name_sql); + $_name_row = $GLOBALS['dbi']->fetchRow($_name_rs); + $filename = $_name_row[0] . $extension; + } + + return $filename; + } + + /** + * Displays an error message + * + * @param integer $pageNumber ID of the chosen page + * @param string $type Schema Type + * @param string $error_message The error message + * + * @access public + * + * @return void + */ + public static function dieSchema($pageNumber, $type = '', $error_message = '') + { + echo "

    " , __("SCHEMA ERROR: ") , $type , "

    " , "\n"; + if (!empty($error_message)) { + $error_message = htmlspecialchars($error_message); + } + echo '

    ' , "\n"; + echo ' ' , $error_message , "\n"; + echo '

    ' , "\n"; + echo '' , __('Back') , ''; + echo "\n"; + exit; + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Schema/Pdf/Pdf.php b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Schema/Pdf/Pdf.php new file mode 100644 index 00000000..138c56c8 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Schema/Pdf/Pdf.php @@ -0,0 +1,403 @@ +_pageNumber = $pageNumber; + $this->_withDoc = $withDoc; + $this->_db = $db; + $this->relation = new Relation(); + } + + /** + * Sets the value for margins + * + * @param float $c_margin margin + * + * @return void + */ + public function setCMargin($c_margin) + { + $this->cMargin = $c_margin; + } + + /** + * Sets the scaling factor, defines minimum coordinates and margins + * + * @param float|int $scale The scaling factor + * @param float|int $xMin The minimum X coordinate + * @param float|int $yMin The minimum Y coordinate + * @param float|int $leftMargin The left margin + * @param float|int $topMargin The top margin + * + * @return void + */ + public function setScale($scale = 1, $xMin = 0, $yMin = 0, + $leftMargin = -1, $topMargin = -1 + ) { + $this->scale = $scale; + $this->_xMin = $xMin; + $this->_yMin = $yMin; + if ($this->leftMargin != -1) { + $this->leftMargin = $leftMargin; + } + if ($this->topMargin != -1) { + $this->topMargin = $topMargin; + } + } + + /** + * Outputs a scaled cell + * + * @param float|int $w The cell width + * @param float|int $h The cell height + * @param string $txt The text to output + * @param mixed $border Whether to add borders or not + * @param integer $ln Where to put the cursor once the output is done + * @param string $align Align mode + * @param integer $fill Whether to fill the cell with a color or not + * @param string $link Link + * + * @return void + * + * @see TCPDF::Cell() + */ + public function cellScale($w, $h = 0, $txt = '', $border = 0, $ln = 0, + $align = '', $fill = 0, $link = '' + ) { + $h = $h / $this->scale; + $w = $w / $this->scale; + $this->Cell($w, $h, $txt, $border, $ln, $align, $fill, $link); + } + + /** + * Draws a scaled line + * + * @param float $x1 The horizontal position of the starting point + * @param float $y1 The vertical position of the starting point + * @param float $x2 The horizontal position of the ending point + * @param float $y2 The vertical position of the ending point + * + * @return void + * + * @see TCPDF::Line() + */ + public function lineScale($x1, $y1, $x2, $y2) + { + $x1 = ($x1 - $this->_xMin) / $this->scale + $this->leftMargin; + $y1 = ($y1 - $this->_yMin) / $this->scale + $this->topMargin; + $x2 = ($x2 - $this->_xMin) / $this->scale + $this->leftMargin; + $y2 = ($y2 - $this->_yMin) / $this->scale + $this->topMargin; + $this->Line($x1, $y1, $x2, $y2); + } + + /** + * Sets x and y scaled positions + * + * @param float $x The x position + * @param float $y The y position + * + * @return void + * + * @see TCPDF::SetXY() + */ + public function setXyScale($x, $y) + { + $x = ($x - $this->_xMin) / $this->scale + $this->leftMargin; + $y = ($y - $this->_yMin) / $this->scale + $this->topMargin; + $this->SetXY($x, $y); + } + + /** + * Sets the X scaled positions + * + * @param float $x The x position + * + * @return void + * + * @see TCPDF::SetX() + */ + public function setXScale($x) + { + $x = ($x - $this->_xMin) / $this->scale + $this->leftMargin; + $this->SetX($x); + } + + /** + * Sets the scaled font size + * + * @param float $size The font size (in points) + * + * @return void + * + * @see TCPDF::SetFontSize() + */ + public function setFontSizeScale($size) + { + // Set font size in points + $size = $size / $this->scale; + $this->SetFontSize($size); + } + + /** + * Sets the scaled line width + * + * @param float $width The line width + * + * @return void + * + * @see TCPDF::SetLineWidth() + */ + public function setLineWidthScale($width) + { + $width = $width / $this->scale; + $this->SetLineWidth($width); + } + + /** + * This method is used to render the page header. + * + * @return void + * + * @see TCPDF::Header() + */ + // @codingStandardsIgnoreLine + public function Header() + { + // We only show this if we find something in the new pdf_pages table + + // This function must be named "Header" to work with the TCPDF library + if ($this->_withDoc) { + if ($this->_offline || $this->_pageNumber == -1) { + $pg_name = __("PDF export page"); + } else { + $test_query = 'SELECT * FROM ' + . Util::backquote($GLOBALS['cfgRelation']['db']) . '.' + . Util::backquote($GLOBALS['cfgRelation']['pdf_pages']) + . ' WHERE db_name = \'' . $GLOBALS['dbi']->escapeString($this->_db) + . '\' AND page_nr = \'' . $this->_pageNumber . '\''; + $test_rs = $this->relation->queryAsControlUser($test_query); + $pages = @$GLOBALS['dbi']->fetchAssoc($test_rs); + $pg_name = ucfirst($pages['page_descr']); + } + + $this->SetFont($this->_ff, 'B', 14); + $this->Cell(0, 6, $pg_name, 'B', 1, 'C'); + $this->SetFont($this->_ff, ''); + $this->Ln(); + } + } + + /** + * This function must be named "Footer" to work with the TCPDF library + * + * @return void + * + * @see PDF::Footer() + */ + // @codingStandardsIgnoreLine + public function Footer() + { + if ($this->_withDoc) { + parent::Footer(); + } + } + + /** + * Sets widths + * + * @param array $w array of widths + * + * @return void + */ + public function setWidths(array $w) + { + // column widths + $this->widths = $w; + } + + /** + * Generates table row. + * + * @param array $data Data for table + * @param array $links Links for table cells + * + * @return void + */ + public function row(array $data, array $links) + { + // line height + $nb = 0; + $data_cnt = count($data); + for ($i = 0;$i < $data_cnt;$i++) { + $nb = max($nb, $this->numLines($this->widths[$i], $data[$i])); + } + $il = $this->FontSize; + $h = ($il + 1) * $nb; + // page break if necessary + $this->CheckPageBreak($h); + // draw the cells + $data_cnt = count($data); + for ($i = 0;$i < $data_cnt;$i++) { + $w = $this->widths[$i]; + // save current position + $x = $this->GetX(); + $y = $this->GetY(); + // draw the border + $this->Rect($x, $y, $w, $h); + if (isset($links[$i])) { + $this->Link($x, $y, $w, $h, $links[$i]); + } + // print text + $this->MultiCell($w, $il + 1, $data[$i], 0, 'L'); + // go to right side + $this->SetXY($x + $w, $y); + } + // go to line + $this->Ln($h); + } + + /** + * Compute number of lines used by a multicell of width w + * + * @param int $w width + * @param string $txt text + * + * @return int + */ + public function numLines($w, $txt) + { + $cw = &$this->CurrentFont['cw']; + if ($w == 0) { + $w = $this->w - $this->rMargin - $this->x; + } + $wmax = ($w-2 * $this->cMargin) * 1000 / $this->FontSize; + $s = str_replace("\r", '', $txt); + $nb = strlen($s); + if ($nb > 0 && $s[$nb-1] == "\n") { + $nb--; + } + $sep = -1; + $i = 0; + $j = 0; + $l = 0; + $nl = 1; + while ($i < $nb) { + $c = $s[$i]; + if ($c == "\n") { + $i++; + $sep = -1; + $j = $i; + $l = 0; + $nl++; + continue; + } + if ($c == ' ') { + $sep = $i; + } + $l += isset($cw[mb_ord($c)])?$cw[mb_ord($c)]:0 ; + if ($l > $wmax) { + if ($sep == -1) { + if ($i == $j) { + $i++; + } + } else { + $i = $sep + 1; + } + $sep = -1; + $j = $i; + $l = 0; + $nl++; + } else { + $i++; + } + } + return $nl; + } + + /** + * Set whether the document is generated from client side DB + * + * @param string $value whether offline + * + * @return void + * + * @access private + */ + public function setOffline($value) + { + $this->_offline = $value; + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Schema/Pdf/PdfRelationSchema.php b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Schema/Pdf/PdfRelationSchema.php new file mode 100644 index 00000000..aa4fc721 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Schema/Pdf/PdfRelationSchema.php @@ -0,0 +1,732 @@ +setShowGrid(isset($_REQUEST['pdf_show_grid'])); + $this->setShowColor(isset($_REQUEST['pdf_show_color'])); + $this->setShowKeys(isset($_REQUEST['pdf_show_keys'])); + $this->setTableDimension(isset($_REQUEST['pdf_show_table_dimension'])); + $this->setAllTablesSameWidth(isset($_REQUEST['pdf_all_tables_same_width'])); + $this->setWithDataDictionary(isset($_REQUEST['pdf_with_doc'])); + $this->setTableOrder($_REQUEST['pdf_table_order']); + $this->setOrientation($_REQUEST['pdf_orientation']); + $this->setPaper($_REQUEST['pdf_paper']); + + // Initializes a new document + parent::__construct( + $db, + new Pdf( + $this->orientation, 'mm', $this->paper, + $this->pageNumber, $this->_withDoc, $db + ) + ); + $this->diagram->SetTitle( + sprintf( + __('Schema of the %s database'), + $this->db + ) + ); + $this->diagram->setCMargin(0); + $this->diagram->Open(); + $this->diagram->SetAutoPageBreak('auto'); + $this->diagram->setOffline($this->offline); + + $alltables = $this->getTablesFromRequest(); + if ($this->getTableOrder() == 'name_asc') { + sort($alltables); + } elseif ($this->getTableOrder() == 'name_desc') { + rsort($alltables); + } + + if ($this->_withDoc) { + $this->diagram->SetAutoPageBreak('auto', 15); + $this->diagram->setCMargin(1); + $this->dataDictionaryDoc($alltables); + $this->diagram->SetAutoPageBreak('auto'); + $this->diagram->setCMargin(0); + } + + $this->diagram->Addpage(); + + if ($this->_withDoc) { + $this->diagram->SetLink($this->diagram->PMA_links['RT']['-'], -1); + $this->diagram->Bookmark(__('Relational schema')); + $this->diagram->setAlias('{00}', $this->diagram->PageNo()); + $this->_topMargin = 28; + $this->_bottomMargin = 28; + } + + /* snip */ + foreach ($alltables as $table) { + if (! isset($this->_tables[$table])) { + $this->_tables[$table] = new TableStatsPdf( + $this->diagram, + $this->db, + $table, + null, + $this->pageNumber, + $this->_tablewidth, + $this->showKeys, + $this->tableDimension, + $this->offline + ); + } + if ($this->sameWide) { + $this->_tables[$table]->width = $this->_tablewidth; + } + $this->_setMinMax($this->_tables[$table]); + } + + // Defines the scale factor + $innerWidth = $this->diagram->getPageWidth() - $this->_rightMargin + - $this->_leftMargin; + $innerHeight = $this->diagram->getPageHeight() - $this->_topMargin + - $this->_bottomMargin; + $this->_scale = ceil( + max( + ($this->_xMax - $this->_xMin) / $innerWidth, + ($this->_yMax - $this->_yMin) / $innerHeight + ) * 100 + ) / 100; + + $this->diagram->setScale( + $this->_scale, + $this->_xMin, + $this->_yMin, + $this->_leftMargin, + $this->_topMargin + ); + // Builds and save the PDF document + $this->diagram->setLineWidthScale(0.1); + + if ($this->_showGrid) { + $this->diagram->SetFontSize(10); + $this->_strokeGrid(); + } + $this->diagram->setFontSizeScale(14); + // previous logic was checking master tables and foreign tables + // but I think that looping on every table of the pdf page as a master + // and finding its foreigns is OK (then we can support innodb) + $seen_a_relation = false; + foreach ($alltables as $one_table) { + $exist_rel = $this->relation->getForeigners($this->db, $one_table, '', 'both'); + if (!$exist_rel) { + continue; + } + + $seen_a_relation = true; + foreach ($exist_rel as $master_field => $rel) { + // put the foreign table on the schema only if selected + // by the user + // (do not use array_search() because we would have to + // to do a === false and this is not PHP3 compatible) + if ($master_field != 'foreign_keys_data') { + if (in_array($rel['foreign_table'], $alltables)) { + $this->_addRelation( + $one_table, + $master_field, + $rel['foreign_table'], + $rel['foreign_field'] + ); + } + continue; + } + + foreach ($rel as $one_key) { + if (!in_array($one_key['ref_table_name'], $alltables)) { + continue; + } + + foreach ($one_key['index_list'] + as $index => $one_field + ) { + $this->_addRelation( + $one_table, + $one_field, + $one_key['ref_table_name'], + $one_key['ref_index_list'][$index] + ); + } + } + } // end while + } // end while + + if ($seen_a_relation) { + $this->_drawRelations(); + } + $this->_drawTables(); + } + + /** + * Set Show Grid + * + * @param boolean $value show grid of the document or not + * + * @return void + */ + public function setShowGrid($value) + { + $this->_showGrid = $value; + } + + /** + * Returns whether to show grid + * + * @return boolean whether to show grid + */ + public function isShowGrid() + { + return $this->_showGrid; + } + + /** + * Set Data Dictionary + * + * @param boolean $value show selected database data dictionary or not + * + * @return void + */ + public function setWithDataDictionary($value) + { + $this->_withDoc = $value; + } + + /** + * Return whether to show selected database data dictionary or not + * + * @return boolean whether to show selected database data dictionary or not + */ + public function isWithDataDictionary() + { + return $this->_withDoc; + } + + /** + * Sets the order of the table in data dictionary + * + * @param string $value table order + * + * @return void + */ + public function setTableOrder($value) + { + $this->_tableOrder = $value; + } + + /** + * Returns the order of the table in data dictionary + * + * @return string table order + */ + public function getTableOrder() + { + return $this->_tableOrder; + } + + /** + * Output Pdf Document for download + * + * @return void + */ + public function showOutput() + { + $this->diagram->download($this->getFileName('.pdf')); + } + + /** + * Sets X and Y minimum and maximum for a table cell + * + * @param TableStatsPdf $table The table name of which sets XY co-ordinates + * + * @return void + */ + private function _setMinMax($table) + { + $this->_xMax = max($this->_xMax, $table->x + $table->width); + $this->_yMax = max($this->_yMax, $table->y + $table->height); + $this->_xMin = min($this->_xMin, $table->x); + $this->_yMin = min($this->_yMin, $table->y); + } + + /** + * Defines relation objects + * + * @param string $masterTable The master table name + * @param string $masterField The relation field in the master table + * @param string $foreignTable The foreign table name + * @param string $foreignField The relation field in the foreign table + * + * @return void + * + * @see _setMinMax + */ + private function _addRelation($masterTable, $masterField, $foreignTable, + $foreignField + ) { + if (! isset($this->_tables[$masterTable])) { + $this->_tables[$masterTable] = new TableStatsPdf( + $this->diagram, + $this->db, + $masterTable, + null, + $this->pageNumber, + $this->_tablewidth, + $this->showKeys, + $this->tableDimension + ); + $this->_setMinMax($this->_tables[$masterTable]); + } + if (! isset($this->_tables[$foreignTable])) { + $this->_tables[$foreignTable] = new TableStatsPdf( + $this->diagram, + $this->db, + $foreignTable, + null, + $this->pageNumber, + $this->_tablewidth, + $this->showKeys, + $this->tableDimension + ); + $this->_setMinMax($this->_tables[$foreignTable]); + } + $this->relations[] = new RelationStatsPdf( + $this->diagram, + $this->_tables[$masterTable], + $masterField, + $this->_tables[$foreignTable], + $foreignField + ); + } + + /** + * Draws the grid + * + * @return void + * + * @see PMA_Schema_PDF + */ + private function _strokeGrid() + { + $gridSize = 10; + $labelHeight = 4; + $labelWidth = 5; + if ($this->_withDoc) { + $topSpace = 6; + $bottomSpace = 15; + } else { + $topSpace = 0; + $bottomSpace = 0; + } + + $this->diagram->SetMargins(0, 0); + $this->diagram->SetDrawColor(200, 200, 200); + // Draws horizontal lines + $innerHeight = $this->diagram->getPageHeight() - $topSpace - $bottomSpace; + for ($l = 0, + $size = intval($innerHeight / $gridSize); + $l <= $size; + $l++ + ) { + $this->diagram->line( + 0, $l * $gridSize + $topSpace, + $this->diagram->getPageWidth(), $l * $gridSize + $topSpace + ); + // Avoid duplicates + if ($l > 0 + && $l <= intval(($innerHeight - $labelHeight) / $gridSize) + ) { + $this->diagram->SetXY(0, $l * $gridSize + $topSpace); + $label = (string) sprintf( + '%.0f', + ($l * $gridSize + $topSpace - $this->_topMargin) + * $this->_scale + $this->_yMin + ); + $this->diagram->Cell($labelWidth, $labelHeight, ' ' . $label); + } // end if + } // end for + // Draws vertical lines + for ( + $j = 0, $size = intval($this->diagram->getPageWidth() / $gridSize); + $j <= $size; + $j++ + ) { + $this->diagram->line( + $j * $gridSize, + $topSpace, + $j * $gridSize, + $this->diagram->getPageHeight() - $bottomSpace + ); + $this->diagram->SetXY($j * $gridSize, $topSpace); + $label = (string) sprintf( + '%.0f', + ($j * $gridSize - $this->_leftMargin) * $this->_scale + $this->_xMin + ); + $this->diagram->Cell($labelWidth, $labelHeight, $label); + } + } + + /** + * Draws relation arrows + * + * @return void + * + * @see Relation_Stats_Pdf::relationdraw() + */ + private function _drawRelations() + { + $i = 0; + foreach ($this->relations as $relation) { + $relation->relationDraw($this->showColor, $i); + $i++; + } + } + + /** + * Draws tables + * + * @return void + * + * @see Table_Stats_Pdf::tableDraw() + */ + private function _drawTables() + { + foreach ($this->_tables as $table) { + $table->tableDraw(null, $this->_withDoc, $this->showColor); + } + } + + /** + * Generates data dictionary pages. + * + * @param array $alltables Tables to document. + * + * @return void + */ + public function dataDictionaryDoc(array $alltables) + { + // TOC + $this->diagram->addpage($this->orientation); + $this->diagram->Cell(0, 9, __('Table of contents'), 1, 0, 'C'); + $this->diagram->Ln(15); + $i = 1; + foreach ($alltables as $table) { + $this->diagram->PMA_links['doc'][$table]['-'] + = $this->diagram->AddLink(); + $this->diagram->SetX(10); + // $this->diagram->Ln(1); + $this->diagram->Cell( + 0, 6, __('Page number:') . ' {' . sprintf("%02d", $i) . '}', 0, 0, + 'R', 0, $this->diagram->PMA_links['doc'][$table]['-'] + ); + $this->diagram->SetX(10); + $this->diagram->Cell( + 0, 6, $i . ' ' . $table, 0, 1, + 'L', 0, $this->diagram->PMA_links['doc'][$table]['-'] + ); + // $this->diagram->Ln(1); + $fields = $GLOBALS['dbi']->getColumns($this->db, $table); + foreach ($fields as $row) { + $this->diagram->SetX(20); + $field_name = $row['Field']; + $this->diagram->PMA_links['doc'][$table][$field_name] + = $this->diagram->AddLink(); + //$this->diagram->Cell( + // 0, 6, $field_name, 0, 1, + // 'L', 0, $this->diagram->PMA_links['doc'][$table][$field_name] + //); + } + $i++; + } + $this->diagram->PMA_links['RT']['-'] = $this->diagram->AddLink(); + $this->diagram->SetX(10); + $this->diagram->Cell( + 0, 6, __('Page number:') . ' {00}', 0, 0, + 'R', 0, $this->diagram->PMA_links['RT']['-'] + ); + $this->diagram->SetX(10); + $this->diagram->Cell( + 0, 6, $i . ' ' . __('Relational schema'), 0, 1, + 'L', 0, $this->diagram->PMA_links['RT']['-'] + ); + $z = 0; + foreach ($alltables as $table) { + $z++; + $this->diagram->SetAutoPageBreak(true, 15); + $this->diagram->addpage($this->orientation); + $this->diagram->Bookmark($table); + $this->diagram->setAlias( + '{' . sprintf("%02d", $z) . '}', $this->diagram->PageNo() + ); + $this->diagram->PMA_links['RT'][$table]['-'] + = $this->diagram->AddLink(); + $this->diagram->SetLink( + $this->diagram->PMA_links['doc'][$table]['-'], -1 + ); + $this->diagram->SetFont($this->_ff, 'B', 18); + $this->diagram->Cell( + 0, 8, $z . ' ' . $table, 1, 1, + 'C', 0, $this->diagram->PMA_links['RT'][$table]['-'] + ); + $this->diagram->SetFont($this->_ff, '', 8); + $this->diagram->ln(); + + $cfgRelation = $this->relation->getRelationsParam(); + $comments = $this->relation->getComments($this->db, $table); + if ($cfgRelation['mimework']) { + $mime_map = Transformations::getMIME($this->db, $table, true); + } + + /** + * Gets table information + */ + $showtable = $GLOBALS['dbi']->getTable($this->db, $table) + ->getStatusInfo(); + $show_comment = isset($showtable['Comment']) + ? $showtable['Comment'] + : ''; + $create_time = isset($showtable['Create_time']) + ? Util::localisedDate( + strtotime($showtable['Create_time']) + ) + : ''; + $update_time = isset($showtable['Update_time']) + ? Util::localisedDate( + strtotime($showtable['Update_time']) + ) + : ''; + $check_time = isset($showtable['Check_time']) + ? Util::localisedDate( + strtotime($showtable['Check_time']) + ) + : ''; + + /** + * Gets fields properties + */ + $columns = $GLOBALS['dbi']->getColumns($this->db, $table); + + // Find which tables are related with the current one and write it in + // an array + $res_rel = $this->relation->getForeigners($this->db, $table); + + /** + * Displays the comments of the table if MySQL >= 3.23 + */ + + $break = false; + if (! empty($show_comment)) { + $this->diagram->Cell( + 0, 3, __('Table comments:') . ' ' . $show_comment, 0, 1 + ); + $break = true; + } + + if (! empty($create_time)) { + $this->diagram->Cell( + 0, 3, __('Creation:') . ' ' . $create_time, 0, 1 + ); + $break = true; + } + + if (! empty($update_time)) { + $this->diagram->Cell( + 0, 3, __('Last update:') . ' ' . $update_time, 0, 1 + ); + $break = true; + } + + if (! empty($check_time)) { + $this->diagram->Cell( + 0, 3, __('Last check:') . ' ' . $check_time, 0, 1 + ); + $break = true; + } + + if ($break == true) { + $this->diagram->Cell(0, 3, '', 0, 1); + $this->diagram->Ln(); + } + + $this->diagram->SetFont($this->_ff, 'B'); + if (isset($this->orientation) && $this->orientation == 'L') { + $this->diagram->Cell(25, 8, __('Column'), 1, 0, 'C'); + $this->diagram->Cell(20, 8, __('Type'), 1, 0, 'C'); + $this->diagram->Cell(20, 8, __('Attributes'), 1, 0, 'C'); + $this->diagram->Cell(10, 8, __('Null'), 1, 0, 'C'); + $this->diagram->Cell(20, 8, __('Default'), 1, 0, 'C'); + $this->diagram->Cell(25, 8, __('Extra'), 1, 0, 'C'); + $this->diagram->Cell(45, 8, __('Links to'), 1, 0, 'C'); + + if ($this->paper == 'A4') { + $comments_width = 67; + } else { + // this is really intended for 'letter' + /** + * @todo find optimal width for all formats + */ + $comments_width = 50; + } + $this->diagram->Cell($comments_width, 8, __('Comments'), 1, 0, 'C'); + $this->diagram->Cell(45, 8, 'MIME', 1, 1, 'C'); + $this->diagram->setWidths( + array(25, 20, 20, 10, 20, 25, 45, $comments_width, 45) + ); + } else { + $this->diagram->Cell(20, 8, __('Column'), 1, 0, 'C'); + $this->diagram->Cell(20, 8, __('Type'), 1, 0, 'C'); + $this->diagram->Cell(20, 8, __('Attributes'), 1, 0, 'C'); + $this->diagram->Cell(10, 8, __('Null'), 1, 0, 'C'); + $this->diagram->Cell(15, 8, __('Default'), 1, 0, 'C'); + $this->diagram->Cell(15, 8, __('Extra'), 1, 0, 'C'); + $this->diagram->Cell(30, 8, __('Links to'), 1, 0, 'C'); + $this->diagram->Cell(30, 8, __('Comments'), 1, 0, 'C'); + $this->diagram->Cell(30, 8, 'MIME', 1, 1, 'C'); + $this->diagram->setWidths(array(20, 20, 20, 10, 15, 15, 30, 30, 30)); + } + $this->diagram->SetFont($this->_ff, ''); + + foreach ($columns as $row) { + $extracted_columnspec + = Util::extractColumnSpec($row['Type']); + $type = $extracted_columnspec['print_type']; + $attribute = $extracted_columnspec['attribute']; + if (! isset($row['Default'])) { + if ($row['Null'] != '' && $row['Null'] != 'NO') { + $row['Default'] = 'NULL'; + } + } + $field_name = $row['Field']; + // $this->diagram->Ln(); + $this->diagram->PMA_links['RT'][$table][$field_name] + = $this->diagram->AddLink(); + $this->diagram->Bookmark($field_name, 1, -1); + $this->diagram->SetLink( + $this->diagram->PMA_links['doc'][$table][$field_name], -1 + ); + $foreigner = $this->relation->searchColumnInForeigners($res_rel, $field_name); + + $linksTo = ''; + if ($foreigner) { + $linksTo = '-> '; + if ($foreigner['foreign_db'] != $this->db) { + $linksTo .= $foreigner['foreign_db'] . '.'; + } + $linksTo .= $foreigner['foreign_table'] + . '.' . $foreigner['foreign_field']; + + if (isset($foreigner['on_update'])) { // not set for internal + $linksTo .= "\n" . 'ON UPDATE ' . $foreigner['on_update']; + $linksTo .= "\n" . 'ON DELETE ' . $foreigner['on_delete']; + } + } + + $this->diagram_row = array( + $field_name, + $type, + $attribute, + (($row['Null'] == '' || $row['Null'] == 'NO') + ? __('No') + : __('Yes')), + (isset($row['Default']) ? $row['Default'] : ''), + $row['Extra'], + $linksTo, + (isset($comments[$field_name]) + ? $comments[$field_name] + : ''), + (isset($mime_map) && isset($mime_map[$field_name]) + ? str_replace('_', '/', $mime_map[$field_name]['mimetype']) + : '') + ); + $links = array(); + $links[0] = $this->diagram->PMA_links['RT'][$table][$field_name]; + if ($foreigner + && isset($this->diagram->PMA_links['doc'][$foreigner['foreign_table']][$foreigner['foreign_field']]) + ) { + $links[6] = $this->diagram->PMA_links['doc'] + [$foreigner['foreign_table']][$foreigner['foreign_field']]; + } else { + unset($links[6]); + } + $this->diagram->row($this->diagram_row, $links); + } // end foreach + $this->diagram->SetFont($this->_ff, '', 14); + } //end each + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Schema/Pdf/RelationStatsPdf.php b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Schema/Pdf/RelationStatsPdf.php new file mode 100644 index 00000000..66a46b55 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Schema/Pdf/RelationStatsPdf.php @@ -0,0 +1,130 @@ +wTick = 5; + parent::__construct( + $diagram, $master_table, $master_field, $foreign_table, $foreign_field + ); + } + + /** + * draws relation links and arrows shows foreign key relations + * + * @param boolean $showColor Whether to use one color per relation or not + * @param integer $i The id of the link to draw + * + * @access public + * + * @return void + * + * @see Pdf + */ + public function relationDraw($showColor, $i) + { + if ($showColor) { + $d = $i % 6; + $j = ($i - $d) / 6; + $j = $j % 4; + $j++; + $case = array( + array(1, 0, 0), + array(0, 1, 0), + array(0, 0, 1), + array(1, 1, 0), + array(1, 0, 1), + array(0, 1, 1) + ); + list ($a, $b, $c) = $case[$d]; + $e = (1 - ($j - 1) / 6); + $this->diagram->SetDrawColor($a * 255 * $e, $b * 255 * $e, $c * 255 * $e); + } else { + $this->diagram->SetDrawColor(0); + } + $this->diagram->setLineWidthScale(0.2); + $this->diagram->lineScale( + $this->xSrc, + $this->ySrc, + $this->xSrc + $this->srcDir * $this->wTick, + $this->ySrc + ); + $this->diagram->lineScale( + $this->xDest + $this->destDir * $this->wTick, + $this->yDest, + $this->xDest, + $this->yDest + ); + $this->diagram->setLineWidthScale(0.1); + $this->diagram->lineScale( + $this->xSrc + $this->srcDir * $this->wTick, + $this->ySrc, + $this->xDest + $this->destDir * $this->wTick, + $this->yDest + ); + /* + * Draws arrows -> + */ + $root2 = 2 * sqrt(2); + $this->diagram->lineScale( + $this->xSrc + $this->srcDir * $this->wTick * 0.75, + $this->ySrc, + $this->xSrc + $this->srcDir * (0.75 - 1 / $root2) * $this->wTick, + $this->ySrc + $this->wTick / $root2 + ); + $this->diagram->lineScale( + $this->xSrc + $this->srcDir * $this->wTick * 0.75, + $this->ySrc, + $this->xSrc + $this->srcDir * (0.75 - 1 / $root2) * $this->wTick, + $this->ySrc - $this->wTick / $root2 + ); + + $this->diagram->lineScale( + $this->xDest + $this->destDir * $this->wTick / 2, + $this->yDest, + $this->xDest + $this->destDir * (0.5 + 1 / $root2) * $this->wTick, + $this->yDest + $this->wTick / $root2 + ); + $this->diagram->lineScale( + $this->xDest + $this->destDir * $this->wTick / 2, + $this->yDest, + $this->xDest + $this->destDir * (0.5 + 1 / $root2) * $this->wTick, + $this->yDest - $this->wTick / $root2 + ); + $this->diagram->SetDrawColor(0); + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Schema/Pdf/TableStatsPdf.php b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Schema/Pdf/TableStatsPdf.php new file mode 100644 index 00000000..9f930ca5 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Schema/Pdf/TableStatsPdf.php @@ -0,0 +1,231 @@ +heightCell = 6; + $this->_setHeight(); + /* + * setWidth must me after setHeight, because title + * can include table height which changes table width + */ + $this->_setWidth($fontSize); + if ($sameWideWidth < $this->width) { + $sameWideWidth = $this->width; + } + } + + /** + * Displays an error when the table cannot be found. + * + * @return void + */ + protected function showMissingTableError() + { + ExportRelationSchema::dieSchema( + $this->pageNumber, + "PDF", + sprintf(__('The %s table doesn\'t exist!'), $this->tableName) + ); + } + + /** + * Returns title of the current table, + * title can have the dimensions of the table + * + * @return string + */ + protected function getTitle() + { + $ret = ''; + if ($this->tableDimension) { + $ret = sprintf('%.0fx%0.f', $this->width, $this->height); + } + + return $ret . ' ' . $this->tableName; + } + + /** + * Sets the width of the table + * + * @param integer $fontSize The font size + * + * @access private + * + * @return void + * + * @see PMA_Schema_PDF + */ + private function _setWidth($fontSize) + { + foreach ($this->fields as $field) { + $this->width = max($this->width, $this->diagram->GetStringWidth($field)); + } + $this->width += $this->diagram->GetStringWidth(' '); + $this->diagram->SetFont($this->_ff, 'B', $fontSize); + /* + * it is unknown what value must be added, because + * table title is affected by the table width value + */ + while ($this->width < $this->diagram->GetStringWidth($this->getTitle())) { + $this->width += 5; + } + $this->diagram->SetFont($this->_ff, '', $fontSize); + } + + /** + * Sets the height of the table + * + * @return void + * + * @access private + */ + private function _setHeight() + { + $this->height = (count($this->fields) + 1) * $this->heightCell; + } + + /** + * Do draw the table + * + * @param integer $fontSize The font size + * @param boolean $withDoc Whether to include links to documentation + * @param boolean|integer $setColor Whether to display color + * + * @access public + * + * @return void + * + * @see PMA_Schema_PDF + */ + public function tableDraw($fontSize, $withDoc, $setColor = 0) + { + $this->diagram->setXyScale($this->x, $this->y); + $this->diagram->SetFont($this->_ff, 'B', $fontSize); + if ($setColor) { + $this->diagram->SetTextColor(200); + $this->diagram->SetFillColor(0, 0, 128); + } + if ($withDoc) { + $this->diagram->SetLink( + $this->diagram->PMA_links['RT'][$this->tableName]['-'], + -1 + ); + } else { + $this->diagram->PMA_links['doc'][$this->tableName]['-'] = ''; + } + + $this->diagram->cellScale( + $this->width, + $this->heightCell, + $this->getTitle(), + 1, + 1, + 'C', + $setColor, + $this->diagram->PMA_links['doc'][$this->tableName]['-'] + ); + $this->diagram->setXScale($this->x); + $this->diagram->SetFont($this->_ff, '', $fontSize); + $this->diagram->SetTextColor(0); + $this->diagram->SetFillColor(255); + + foreach ($this->fields as $field) { + if ($setColor) { + if (in_array($field, $this->primary)) { + $this->diagram->SetFillColor(215, 121, 123); + } + if ($field == $this->displayfield) { + $this->diagram->SetFillColor(142, 159, 224); + } + } + if ($withDoc) { + $this->diagram->SetLink( + $this->diagram->PMA_links['RT'][$this->tableName][$field], + -1 + ); + } else { + $this->diagram->PMA_links['doc'][$this->tableName][$field] = ''; + } + + $this->diagram->cellScale( + $this->width, + $this->heightCell, + ' ' . $field, + 1, + 1, + 'L', + $setColor, + $this->diagram->PMA_links['doc'][$this->tableName][$field] + ); + $this->diagram->setXScale($this->x); + $this->diagram->SetFillColor(255); + } + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Schema/RelationStats.php b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Schema/RelationStats.php new file mode 100644 index 00000000..8fe9f30e --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Schema/RelationStats.php @@ -0,0 +1,114 @@ +diagram = $diagram; + + $src_pos = $this->_getXy($master_table, $master_field); + $dest_pos = $this->_getXy($foreign_table, $foreign_field); + /* + * [0] is x-left + * [1] is x-right + * [2] is y + */ + $src_left = $src_pos[0] - $this->wTick; + $src_right = $src_pos[1] + $this->wTick; + $dest_left = $dest_pos[0] - $this->wTick; + $dest_right = $dest_pos[1] + $this->wTick; + + $d1 = abs($src_left - $dest_left); + $d2 = abs($src_right - $dest_left); + $d3 = abs($src_left - $dest_right); + $d4 = abs($src_right - $dest_right); + $d = min($d1, $d2, $d3, $d4); + + if ($d == $d1) { + $this->xSrc = $src_pos[0]; + $this->srcDir = -1; + $this->xDest = $dest_pos[0]; + $this->destDir = -1; + } elseif ($d == $d2) { + $this->xSrc = $src_pos[1]; + $this->srcDir = 1; + $this->xDest = $dest_pos[0]; + $this->destDir = -1; + } elseif ($d == $d3) { + $this->xSrc = $src_pos[0]; + $this->srcDir = -1; + $this->xDest = $dest_pos[1]; + $this->destDir = 1; + } else { + $this->xSrc = $src_pos[1]; + $this->srcDir = 1; + $this->xDest = $dest_pos[1]; + $this->destDir = 1; + } + $this->ySrc = $src_pos[2]; + $this->yDest = $dest_pos[2]; + } + + /** + * Gets arrows coordinates + * + * @param string $table The current table name + * @param string $column The relation column name + * + * @return array Arrows coordinates + * + * @access private + */ + private function _getXy($table, $column) + { + $pos = array_search($column, $table->fields); + + // x_left, x_right, y + return array( + $table->x, + $table->x + $table->width, + $table->y + ($pos + 1.5) * $table->heightCell, + ); + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Schema/SchemaDia.php b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Schema/SchemaDia.php new file mode 100644 index 00000000..02dd75af --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Schema/SchemaDia.php @@ -0,0 +1,97 @@ +setProperties(); + } + + /** + * Sets the schema export Dia properties + * + * @return void + */ + protected function setProperties() + { + $schemaPluginProperties = new SchemaPluginProperties(); + $schemaPluginProperties->setText('Dia'); + $schemaPluginProperties->setExtension('dia'); + $schemaPluginProperties->setMimeType('application/dia'); + + // create the root group that will be the options field for + // $schemaPluginProperties + // this will be shown as "Format specific options" + $exportSpecificOptions = new OptionsPropertyRootGroup( + "Format Specific Options" + ); + + // specific options main group + $specificOptions = new OptionsPropertyMainGroup("general_opts"); + // add options common to all plugins + $this->addCommonOptions($specificOptions); + + $leaf = new SelectPropertyItem( + "orientation", + __('Orientation') + ); + $leaf->setValues( + array( + 'L' => __('Landscape'), + 'P' => __('Portrait'), + ) + ); + $specificOptions->addProperty($leaf); + + $leaf = new SelectPropertyItem( + "paper", + __('Paper size') + ); + $leaf->setValues($this->getPaperSizeArray()); + $specificOptions->addProperty($leaf); + + // add the main group to the root group + $exportSpecificOptions->addProperty($specificOptions); + + // set the options for the schema export plugin property item + $schemaPluginProperties->setOptions($exportSpecificOptions); + $this->properties = $schemaPluginProperties; + } + + /** + * Exports the schema into DIA format. + * + * @param string $db database name + * + * @return bool Whether it succeeded + */ + public function exportSchema($db) + { + $export = new DiaRelationSchema($db); + $export->showOutput(); + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Schema/SchemaEps.php b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Schema/SchemaEps.php new file mode 100644 index 00000000..1df342f0 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Schema/SchemaEps.php @@ -0,0 +1,98 @@ +setProperties(); + } + + /** + * Sets the schema export EPS properties + * + * @return void + */ + protected function setProperties() + { + $schemaPluginProperties = new SchemaPluginProperties(); + $schemaPluginProperties->setText('EPS'); + $schemaPluginProperties->setExtension('eps'); + $schemaPluginProperties->setMimeType('application/eps'); + + // create the root group that will be the options field for + // $schemaPluginProperties + // this will be shown as "Format specific options" + $exportSpecificOptions = new OptionsPropertyRootGroup( + "Format Specific Options" + ); + + // specific options main group + $specificOptions = new OptionsPropertyMainGroup("general_opts"); + // add options common to all plugins + $this->addCommonOptions($specificOptions); + + // create leaf items and add them to the group + $leaf = new BoolPropertyItem( + 'all_tables_same_width', + __('Same width for all tables') + ); + $specificOptions->addProperty($leaf); + + $leaf = new SelectPropertyItem( + "orientation", + __('Orientation') + ); + $leaf->setValues( + array( + 'L' => __('Landscape'), + 'P' => __('Portrait'), + ) + ); + $specificOptions->addProperty($leaf); + + // add the main group to the root group + $exportSpecificOptions->addProperty($specificOptions); + + // set the options for the schema export plugin property item + $schemaPluginProperties->setOptions($exportSpecificOptions); + $this->properties = $schemaPluginProperties; + } + + /** + * Exports the schema into EPS format. + * + * @param string $db database name + * + * @return bool Whether it succeeded + */ + public function exportSchema($db) + { + $export = new EpsRelationSchema($db); + $export->showOutput(); + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Schema/SchemaPdf.php b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Schema/SchemaPdf.php new file mode 100644 index 00000000..0c12fb45 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Schema/SchemaPdf.php @@ -0,0 +1,130 @@ +setProperties(); + } + + /** + * Sets the schema export PDF properties + * + * @return void + */ + protected function setProperties() + { + $schemaPluginProperties = new SchemaPluginProperties(); + $schemaPluginProperties->setText('PDF'); + $schemaPluginProperties->setExtension('pdf'); + $schemaPluginProperties->setMimeType('application/pdf'); + + // create the root group that will be the options field for + // $schemaPluginProperties + // this will be shown as "Format specific options" + $exportSpecificOptions = new OptionsPropertyRootGroup( + "Format Specific Options" + ); + + // specific options main group + $specificOptions = new OptionsPropertyMainGroup("general_opts"); + // add options common to all plugins + $this->addCommonOptions($specificOptions); + + // create leaf items and add them to the group + $leaf = new BoolPropertyItem( + 'all_tables_same_width', + __('Same width for all tables') + ); + $specificOptions->addProperty($leaf); + + $leaf = new SelectPropertyItem( + "orientation", + __('Orientation') + ); + $leaf->setValues( + array( + 'L' => __('Landscape'), + 'P' => __('Portrait'), + ) + ); + $specificOptions->addProperty($leaf); + + $leaf = new SelectPropertyItem( + "paper", + __('Paper size') + ); + $leaf->setValues($this->getPaperSizeArray()); + $specificOptions->addProperty($leaf); + + $leaf = new BoolPropertyItem( + 'show_grid', + __('Show grid') + ); + $specificOptions->addProperty($leaf); + + $leaf = new BoolPropertyItem( + 'with_doc', + __('Data dictionary') + ); + $specificOptions->addProperty($leaf); + + $leaf = new SelectPropertyItem( + "table_order", + __('Order of the tables') + ); + $leaf->setValues( + array( + '' => __('None'), + 'name_asc' => __('Name (Ascending)'), + 'name_desc' => __('Name (Descending)'), + ) + ); + $specificOptions->addProperty($leaf); + + // add the main group to the root group + $exportSpecificOptions->addProperty($specificOptions); + + // set the options for the schema export plugin property item + $schemaPluginProperties->setOptions($exportSpecificOptions); + $this->properties = $schemaPluginProperties; + } + + /** + * Exports the schema into PDF format. + * + * @param string $db database name + * + * @return bool Whether it succeeded + */ + public function exportSchema($db) + { + $export = new PdfRelationSchema($db); + $export->showOutput(); + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Schema/SchemaSvg.php b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Schema/SchemaSvg.php new file mode 100644 index 00000000..fce81042 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Schema/SchemaSvg.php @@ -0,0 +1,85 @@ +setProperties(); + } + + /** + * Sets the schema export SVG properties + * + * @return void + */ + protected function setProperties() + { + $schemaPluginProperties = new SchemaPluginProperties(); + $schemaPluginProperties->setText('SVG'); + $schemaPluginProperties->setExtension('svg'); + $schemaPluginProperties->setMimeType('application/svg'); + + // create the root group that will be the options field for + // $schemaPluginProperties + // this will be shown as "Format specific options" + $exportSpecificOptions = new OptionsPropertyRootGroup( + "Format Specific Options" + ); + + // specific options main group + $specificOptions = new OptionsPropertyMainGroup("general_opts"); + // add options common to all plugins + $this->addCommonOptions($specificOptions); + + // create leaf items and add them to the group + $leaf = new BoolPropertyItem( + 'all_tables_same_width', + __('Same width for all tables') + ); + $specificOptions->addProperty($leaf); + + // add the main group to the root group + $exportSpecificOptions->addProperty($specificOptions); + + // set the options for the schema export plugin property item + $schemaPluginProperties->setOptions($exportSpecificOptions); + $this->properties = $schemaPluginProperties; + } + + /** + * Exports the schema into SVG format. + * + * @param string $db database name + * + * @return bool Whether it succeeded + */ + public function exportSchema($db) + { + $export = new SvgRelationSchema($db); + $export->showOutput(); + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Schema/Svg/RelationStatsSvg.php b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Schema/Svg/RelationStatsSvg.php new file mode 100644 index 00000000..b0f44d4e --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Schema/Svg/RelationStatsSvg.php @@ -0,0 +1,138 @@ +wTick = 10; + parent::__construct( + $diagram, + $master_table, + $master_field, + $foreign_table, + $foreign_field + ); + } + + /** + * draws relation links and arrows shows foreign key relations + * + * @param boolean $showColor Whether to use one color per relation or not + * + * @return void + * @access public + * + * @see PMA_SVG + */ + public function relationDraw($showColor) + { + if ($showColor) { + $listOfColors = array( + '#c00', + '#bbb', + '#333', + '#cb0', + '#0b0', + '#0bf', + '#b0b', + ); + shuffle($listOfColors); + $color = $listOfColors[0]; + } else { + $color = '#333'; + } + + $this->diagram->printElementLine( + 'line', + $this->xSrc, + $this->ySrc, + $this->xSrc + $this->srcDir * $this->wTick, + $this->ySrc, + 'stroke:' . $color . ';stroke-width:1;' + ); + $this->diagram->printElementLine( + 'line', + $this->xDest + $this->destDir * $this->wTick, + $this->yDest, + $this->xDest, + $this->yDest, + 'stroke:' . $color . ';stroke-width:1;' + ); + $this->diagram->printElementLine( + 'line', + $this->xSrc + $this->srcDir * $this->wTick, + $this->ySrc, + $this->xDest + $this->destDir * $this->wTick, + $this->yDest, + 'stroke:' . $color . ';stroke-width:1;' + ); + $root2 = 2 * sqrt(2); + $this->diagram->printElementLine( + 'line', + $this->xSrc + $this->srcDir * $this->wTick * 0.75, + $this->ySrc, + $this->xSrc + $this->srcDir * (0.75 - 1 / $root2) * $this->wTick, + $this->ySrc + $this->wTick / $root2, + 'stroke:' . $color . ';stroke-width:2;' + ); + $this->diagram->printElementLine( + 'line', + $this->xSrc + $this->srcDir * $this->wTick * 0.75, + $this->ySrc, + $this->xSrc + $this->srcDir * (0.75 - 1 / $root2) * $this->wTick, + $this->ySrc - $this->wTick / $root2, + 'stroke:' . $color . ';stroke-width:2;' + ); + $this->diagram->printElementLine( + 'line', + $this->xDest + $this->destDir * $this->wTick / 2, + $this->yDest, + $this->xDest + $this->destDir * (0.5 + 1 / $root2) * $this->wTick, + $this->yDest + $this->wTick / $root2, + 'stroke:' . $color . ';stroke-width:2;' + ); + $this->diagram->printElementLine( + 'line', + $this->xDest + $this->destDir * $this->wTick / 2, + $this->yDest, + $this->xDest + $this->destDir * (0.5 + 1 / $root2) * $this->wTick, + $this->yDest - $this->wTick / $root2, + 'stroke:' . $color . ';stroke-width:2;' + ); + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Schema/Svg/Svg.php b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Schema/Svg/Svg.php new file mode 100644 index 00000000..624bf106 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Schema/Svg/Svg.php @@ -0,0 +1,279 @@ +openMemory(); + /* + * Set indenting using three spaces, + * so output is formatted + */ + + $this->setIndent(true); + $this->setIndentString(' '); + /* + * Create the XML document + */ + + $this->startDocument('1.0', 'UTF-8'); + $this->startDtd( + 'svg', + '-//W3C//DTD SVG 1.1//EN', + 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd' + ); + $this->endDtd(); + } + + /** + * Set document title + * + * @param string $value sets the title text + * + * @return void + */ + public function setTitle($value) + { + $this->title = $value; + } + + /** + * Set document author + * + * @param string $value sets the author + * + * @return void + */ + public function setAuthor($value) + { + $this->author = $value; + } + + /** + * Set document font + * + * @param string $value sets the font e.g Arial, Sans-serif etc + * + * @return void + */ + public function setFont($value) + { + $this->font = $value; + } + + /** + * Get document font + * + * @return string returns the font name + */ + public function getFont() + { + return $this->font; + } + + /** + * Set document font size + * + * @param integer $value sets the font size in pixels + * + * @return void + */ + public function setFontSize($value) + { + $this->fontSize = $value; + } + + /** + * Get document font size + * + * @return integer returns the font size + */ + public function getFontSize() + { + return $this->fontSize; + } + + /** + * Starts RelationStatsSvg Document + * + * svg document starts by first initializing svg tag + * which contains all the attributes and namespace that needed + * to define the svg document + * + * @param integer $width total width of the RelationStatsSvg document + * @param integer $height total height of the RelationStatsSvg document + * @param integer $x min-x of the view box + * @param integer $y min-y of the view box + * + * @return void + * + * @see XMLWriter::startElement(),XMLWriter::writeAttribute() + */ + public function startSvgDoc($width, $height, $x = 0, $y = 0) + { + $this->startElement('svg'); + + if (!is_int($width)) { + $width = intval($width); + } + + if (!is_int($height)) { + $height = intval($height); + } + + if ($x != 0 || $y != 0) { + $this->writeAttribute('viewBox', "$x $y $width $height"); + } + $this->writeAttribute('width', ($width - $x) . 'px'); + $this->writeAttribute('height', ($height - $y) . 'px'); + $this->writeAttribute('xmlns', 'http://www.w3.org/2000/svg'); + $this->writeAttribute('version', '1.1'); + } + + /** + * Ends RelationStatsSvg Document + * + * @return void + * @see XMLWriter::endElement(),XMLWriter::endDocument() + */ + public function endSvgDoc() + { + $this->endElement(); + $this->endDocument(); + } + + /** + * output RelationStatsSvg Document + * + * svg document prompted to the user for download + * RelationStatsSvg document saved in .svg extension and can be + * easily changeable by using any svg IDE + * + * @param string $fileName file name + * + * @return void + * @see XMLWriter::startElement(),XMLWriter::writeAttribute() + */ + public function showOutput($fileName) + { + //ob_get_clean(); + $output = $this->flush(); + Response::getInstance()->disable(); + Core::downloadHeader( + $fileName, + 'image/svg+xml', + strlen($output) + ); + print $output; + } + + /** + * Draws RelationStatsSvg elements + * + * SVG has some predefined shape elements like rectangle & text + * and other elements who have x,y co-ordinates are drawn. + * specify their width and height and can give styles too. + * + * @param string $name RelationStatsSvg element name + * @param int $x The x attr defines the left position of the element + * (e.g. x="0" places the element 0 pixels from the + * left of the browser window) + * @param integer $y The y attribute defines the top position of the + * element (e.g. y="0" places the element 0 pixels + * from the top of the browser window) + * @param int|string $width The width attribute defines the width the element + * @param int|string $height The height attribute defines the height the element + * @param string $text The text attribute defines the text the element + * @param string $styles The style attribute defines the style the element + * styles can be defined like CSS styles + * + * @return void + * + * @see XMLWriter::startElement(), XMLWriter::writeAttribute(), + * XMLWriter::text(), XMLWriter::endElement() + */ + public function printElement( + $name, + $x, + $y, + $width = '', + $height = '', + $text = '', + $styles = '' + ) { + $this->startElement($name); + $this->writeAttribute('width', $width); + $this->writeAttribute('height', $height); + $this->writeAttribute('x', $x); + $this->writeAttribute('y', $y); + $this->writeAttribute('style', $styles); + if (isset($text)) { + $this->writeAttribute('font-family', $this->font); + $this->writeAttribute('font-size', $this->fontSize . 'px'); + $this->text($text); + } + $this->endElement(); + } + + /** + * Draws RelationStatsSvg Line element + * + * RelationStatsSvg line element is drawn for connecting the tables. + * arrows are also drawn by specify its start and ending + * co-ordinates + * + * @param string $name RelationStatsSvg element name i.e line + * @param integer $x1 Defines the start of the line on the x-axis + * @param integer $y1 Defines the start of the line on the y-axis + * @param integer $x2 Defines the end of the line on the x-axis + * @param integer $y2 Defines the end of the line on the y-axis + * @param string $styles The style attribute defines the style the element + * styles can be defined like CSS styles + * + * @return void + * + * @see XMLWriter::startElement(), XMLWriter::writeAttribute(), + * XMLWriter::endElement() + */ + public function printElementLine($name, $x1, $y1, $x2, $y2, $styles) + { + $this->startElement($name); + $this->writeAttribute('x1', $x1); + $this->writeAttribute('y1', $y1); + $this->writeAttribute('x2', $x2); + $this->writeAttribute('y2', $y2); + $this->writeAttribute('style', $styles); + $this->endElement(); + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Schema/Svg/SvgRelationSchema.php b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Schema/Svg/SvgRelationSchema.php new file mode 100644 index 00000000..846b8fa1 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Schema/Svg/SvgRelationSchema.php @@ -0,0 +1,268 @@ +setShowColor(isset($_REQUEST['svg_show_color'])); + $this->setShowKeys(isset($_REQUEST['svg_show_keys'])); + $this->setTableDimension(isset($_REQUEST['svg_show_table_dimension'])); + $this->setAllTablesSameWidth(isset($_REQUEST['svg_all_tables_same_width'])); + + $this->diagram->setTitle( + sprintf( + __('Schema of the %s database - Page %s'), + $this->db, + $this->pageNumber + ) + ); + $this->diagram->SetAuthor('phpMyAdmin ' . PMA_VERSION); + $this->diagram->setFont('Arial'); + $this->diagram->setFontSize(16); + + $alltables = $this->getTablesFromRequest(); + + foreach ($alltables as $table) { + if (!isset($this->_tables[$table])) { + $this->_tables[$table] = new TableStatsSvg( + $this->diagram, $this->db, + $table, $this->diagram->getFont(), + $this->diagram->getFontSize(), $this->pageNumber, + $this->_tablewidth, $this->showKeys, $this->tableDimension, + $this->offline + ); + } + + if ($this->sameWide) { + $this->_tables[$table]->width = &$this->_tablewidth; + } + $this->_setMinMax($this->_tables[$table]); + } + + $border = 15; + $this->diagram->startSvgDoc( + $this->_xMax + $border, + $this->_yMax + $border, + $this->_xMin - $border, + $this->_yMin - $border + ); + + $seen_a_relation = false; + foreach ($alltables as $one_table) { + $exist_rel = $this->relation->getForeigners($this->db, $one_table, '', 'both'); + if (!$exist_rel) { + continue; + } + + $seen_a_relation = true; + foreach ($exist_rel as $master_field => $rel) { + /* put the foreign table on the schema only if selected + * by the user + * (do not use array_search() because we would have to + * to do a === false and this is not PHP3 compatible) + */ + if ($master_field != 'foreign_keys_data') { + if (in_array($rel['foreign_table'], $alltables)) { + $this->_addRelation( + $one_table, + $this->diagram->getFont(), + $this->diagram->getFontSize(), + $master_field, + $rel['foreign_table'], + $rel['foreign_field'], + $this->tableDimension + ); + } + continue; + } + + foreach ($rel as $one_key) { + if (!in_array($one_key['ref_table_name'], $alltables)) { + continue; + } + + foreach ( + $one_key['index_list'] + as $index => $one_field + ) { + $this->_addRelation( + $one_table, + $this->diagram->getFont(), + $this->diagram->getFontSize(), + $one_field, + $one_key['ref_table_name'], + $one_key['ref_index_list'][$index], + $this->tableDimension + ); + } + } + } + } + if ($seen_a_relation) { + $this->_drawRelations(); + } + + $this->_drawTables(); + $this->diagram->endSvgDoc(); + } + + /** + * Output RelationStatsSvg Document for download + * + * @return void + */ + public function showOutput() + { + $this->diagram->showOutput($this->getFileName('.svg')); + } + + /** + * Sets X and Y minimum and maximum for a table cell + * + * @param string $table The table name + * + * @return void + */ + private function _setMinMax($table) + { + $this->_xMax = max($this->_xMax, $table->x + $table->width); + $this->_yMax = max($this->_yMax, $table->y + $table->height); + $this->_xMin = min($this->_xMin, $table->x); + $this->_yMin = min($this->_yMin, $table->y); + } + + /** + * Defines relation objects + * + * @param string $masterTable The master table name + * @param string $font The font face + * @param int $fontSize Font size + * @param string $masterField The relation field in the master table + * @param string $foreignTable The foreign table name + * @param string $foreignField The relation field in the foreign table + * @param boolean $tableDimension Whether to display table position or not + * + * @return void + * + * @see _setMinMax,Table_Stats_Svg::__construct(), + * PhpMyAdmin\Plugins\Schema\Svg\RelationStatsSvg::__construct() + */ + private function _addRelation( + $masterTable, + $font, + $fontSize, + $masterField, + $foreignTable, + $foreignField, + $tableDimension + ) { + if (!isset($this->_tables[$masterTable])) { + $this->_tables[$masterTable] = new TableStatsSvg( + $this->diagram, $this->db, + $masterTable, $font, $fontSize, $this->pageNumber, + $this->_tablewidth, false, $tableDimension + ); + $this->_setMinMax($this->_tables[$masterTable]); + } + if (!isset($this->_tables[$foreignTable])) { + $this->_tables[$foreignTable] = new TableStatsSvg( + $this->diagram, $this->db, + $foreignTable, $font, $fontSize, $this->pageNumber, + $this->_tablewidth, false, $tableDimension + ); + $this->_setMinMax($this->_tables[$foreignTable]); + } + $this->_relations[] = new RelationStatsSvg( + $this->diagram, + $this->_tables[$masterTable], + $masterField, + $this->_tables[$foreignTable], + $foreignField + ); + } + + /** + * Draws relation arrows and lines + * connects master table's master field to + * foreign table's foreign field + * + * @return void + * + * @see Relation_Stats_Svg::relationDraw() + */ + private function _drawRelations() + { + foreach ($this->_relations as $relation) { + $relation->relationDraw($this->showColor); + } + } + + /** + * Draws tables + * + * @return void + * + * @see Table_Stats_Svg::Table_Stats_tableDraw() + */ + private function _drawTables() + { + foreach ($this->_tables as $table) { + $table->tableDraw($this->showColor); + } + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Schema/Svg/TableStatsSvg.php b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Schema/Svg/TableStatsSvg.php new file mode 100644 index 00000000..29cbc9a9 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Schema/Svg/TableStatsSvg.php @@ -0,0 +1,202 @@ +_setHeightTable($fontSize); + // setWidth must me after setHeight, because title + // can include table height which changes table width + $this->_setWidthTable($font, $fontSize); + if ($same_wide_width < $this->width) { + $same_wide_width = $this->width; + } + } + + /** + * Displays an error when the table cannot be found. + * + * @return void + */ + protected function showMissingTableError() + { + ExportRelationSchema::dieSchema( + $this->pageNumber, + "SVG", + sprintf(__('The %s table doesn\'t exist!'), $this->tableName) + ); + } + + /** + * Sets the width of the table + * + * @param string $font The font size + * @param integer $fontSize The font size + * + * @return void + * @access private + * + * @see PMA_SVG + */ + private function _setWidthTable($font, $fontSize) + { + foreach ($this->fields as $field) { + $this->width = max( + $this->width, + Font::getStringWidth($field, $font, $fontSize) + ); + } + $this->width += Font::getStringWidth(' ', $font, $fontSize); + + /* + * it is unknown what value must be added, because + * table title is affected by the table width value + */ + while ($this->width + < Font::getStringWidth($this->getTitle(), $font, $fontSize) + ) { + $this->width += 7; + } + } + + /** + * Sets the height of the table + * + * @param integer $fontSize font size + * + * @return void + */ + private function _setHeightTable($fontSize) + { + $this->heightCell = $fontSize + 4; + $this->height = (count($this->fields) + 1) * $this->heightCell; + } + + /** + * draw the table + * + * @param boolean $showColor Whether to display color + * + * @access public + * @return void + * + * @see PMA_SVG,PMA_SVG::printElement + */ + public function tableDraw($showColor) + { + $this->diagram->printElement( + 'rect', + $this->x, + $this->y, + $this->width, + $this->heightCell, + null, + 'fill:#007;stroke:black;' + ); + $this->diagram->printElement( + 'text', + $this->x + 5, + $this->y + 14, + $this->width, + $this->heightCell, + $this->getTitle(), + 'fill:#fff;' + ); + foreach ($this->fields as $field) { + $this->currentCell += $this->heightCell; + $fillColor = 'none'; + if ($showColor) { + if (in_array($field, $this->primary)) { + $fillColor = '#aea'; + } + if ($field == $this->displayfield) { + $fillColor = 'none'; + } + } + $this->diagram->printElement( + 'rect', + $this->x, + $this->y + $this->currentCell, + $this->width, + $this->heightCell, + null, + 'fill:' . $fillColor . ';stroke:black;' + ); + $this->diagram->printElement( + 'text', + $this->x + 5, + $this->y + 14 + $this->currentCell, + $this->width, + $this->heightCell, + $field, + 'fill:black;' + ); + } + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Schema/TableStats.php b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Schema/TableStats.php new file mode 100644 index 00000000..420618de --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Schema/TableStats.php @@ -0,0 +1,191 @@ +diagram = $diagram; + $this->db = $db; + $this->pageNumber = $pageNumber; + $this->tableName = $tableName; + + $this->showKeys = $showKeys; + $this->tableDimension = $tableDimension; + + $this->offline = $offline; + + $this->relation = new Relation(); + + // checks whether the table exists + // and loads fields + $this->validateTableAndLoadFields(); + // load table coordinates + $this->loadCoordinates(); + // loads display field + $this->loadDisplayField(); + // loads primary keys + $this->loadPrimaryKey(); + } + + /** + * Validate whether the table exists. + * + * @return void + */ + protected function validateTableAndLoadFields() + { + $sql = 'DESCRIBE ' . Util::backquote($this->tableName); + $result = $GLOBALS['dbi']->tryQuery( + $sql, + DatabaseInterface::CONNECT_USER, + DatabaseInterface::QUERY_STORE + ); + if (! $result || ! $GLOBALS['dbi']->numRows($result)) { + $this->showMissingTableError(); + } + + if ($this->showKeys) { + $indexes = Index::getFromTable($this->tableName, $this->db); + $all_columns = array(); + foreach ($indexes as $index) { + $all_columns = array_merge( + $all_columns, + array_flip(array_keys($index->getColumns())) + ); + } + $this->fields = array_keys($all_columns); + } else { + while ($row = $GLOBALS['dbi']->fetchRow($result)) { + $this->fields[] = $row[0]; + } + } + } + + /** + * Displays an error when the table cannot be found. + * + * @return void + * @abstract + */ + protected abstract function showMissingTableError(); + + /** + * Loads coordinates of a table + * + * @return void + */ + protected function loadCoordinates() + { + if (isset($_POST['t_h'])) { + foreach ($_POST['t_h'] as $key => $value) { + $db = rawurldecode($_POST['t_db'][$key]); + $tbl = rawurldecode($_POST['t_tbl'][$key]); + if ($this->db . '.' . $this->tableName === $db . '.' . $tbl) { + $this->x = (double) $_POST['t_x'][$key]; + $this->y = (double) $_POST['t_y'][$key]; + break; + } + } + } + } + + /** + * Loads the table's display field + * + * @return void + */ + protected function loadDisplayField() + { + $this->displayfield = $this->relation->getDisplayField($this->db, $this->tableName); + } + + /** + * Loads the PRIMARY key. + * + * @return void + */ + protected function loadPrimaryKey() + { + $result = $GLOBALS['dbi']->query( + 'SHOW INDEX FROM ' . Util::backquote($this->tableName) . ';', + DatabaseInterface::CONNECT_USER, + DatabaseInterface::QUERY_STORE + ); + if ($GLOBALS['dbi']->numRows($result) > 0) { + while ($row = $GLOBALS['dbi']->fetchAssoc($result)) { + if ($row['Key_name'] == 'PRIMARY') { + $this->primary[] = $row['Column_name']; + } + } + } + } + + /** + * Returns title of the current table, + * title can have the dimensions/co-ordinates of the table + * + * @return string title of the current table + */ + protected function getTitle() + { + return ($this->tableDimension + ? sprintf('%.0fx%0.f', $this->width, $this->heightCell) + : '' + ) + . ' ' . $this->tableName; + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Plugins/SchemaPlugin.php b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/SchemaPlugin.php new file mode 100644 index 00000000..7be1959e --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/SchemaPlugin.php @@ -0,0 +1,89 @@ +properties; + } + + /** + * Sets the export plugins properties and is implemented by + * each schema export plugin + * + * @return void + */ + protected abstract function setProperties(); + + /** + * Exports the schema into the specified format. + * + * @param string $db database name + * + * @return bool Whether it succeeded + */ + public abstract function exportSchema($db); + + /** + * Adds export options common to all plugins. + * + * @param \PhpMyAdmin\Properties\Options\Groups\OptionsPropertyMainGroup $propertyGroup property group + * + * @return void + */ + protected function addCommonOptions(OptionsPropertyMainGroup $propertyGroup) + { + $leaf = new BoolPropertyItem('show_color', __('Show color')); + $propertyGroup->addProperty($leaf); + $leaf = new BoolPropertyItem('show_keys', __('Only show keys')); + $propertyGroup->addProperty($leaf); + } + + /** + * Returns the array of paper sizes + * + * @return array array of paper sizes + */ + protected function getPaperSizeArray() + { + $ret = array(); + foreach ($GLOBALS['cfg']['PDFPageSizes'] as $val) { + $ret[$val] = $val; + } + + return $ret; + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Transformations/Abs/Bool2TextTransformationsPlugin.php b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Transformations/Abs/Bool2TextTransformationsPlugin.php new file mode 100644 index 00000000..49a1c099 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Transformations/Abs/Bool2TextTransformationsPlugin.php @@ -0,0 +1,66 @@ +getOptions($options, $cfg['DefaultTransformations']['Bool2Text']); + + if ($buffer == '0') { + return $options[1]; // return false label + } + + return $options[0]; // or true one if nonzero + } + + /* ~~~~~~~~~~~~~~~~~~~~ Getters and Setters ~~~~~~~~~~~~~~~~~~~~ */ + + /** + * Gets the transformation name of the specific plugin + * + * @return string + */ + public static function getName() + { + return "Bool2Text"; + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Transformations/Abs/CodeMirrorEditorTransformationPlugin.php b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Transformations/Abs/CodeMirrorEditorTransformationPlugin.php new file mode 100644 index 00000000..129da8ea --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Transformations/Abs/CodeMirrorEditorTransformationPlugin.php @@ -0,0 +1,72 @@ +'; + } + $class = 'transform_' . strtolower(static::getName()) . '_editor'; + $html .= ''; + + return $html; + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Transformations/Abs/DateFormatTransformationsPlugin.php b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Transformations/Abs/DateFormatTransformationsPlugin.php new file mode 100644 index 00000000..595872cc --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Transformations/Abs/DateFormatTransformationsPlugin.php @@ -0,0 +1,161 @@ +getOptions($options, $cfg['DefaultTransformations']['DateFormat']); + + // further operations on $buffer using the $options[] array. + $options[2] = mb_strtolower($options[2]); + + if (empty($options[1])) { + if ($options[2] == 'local') { + $options[1] = __('%B %d, %Y at %I:%M %p'); + } else { + $options[1] = 'Y-m-d H:i:s'; + } + } + + $timestamp = -1; + + // INT columns will be treated as UNIX timestamps + // and need to be detected before the verification for + // MySQL TIMESTAMP + if ($meta->type == 'int') { + $timestamp = $buffer; + + // Detect TIMESTAMP(6 | 8 | 10 | 12 | 14) + // TIMESTAMP (2 | 4) not supported here. + // (Note: prior to MySQL 4.1, TIMESTAMP has a display size + // for example TIMESTAMP(8) means YYYYMMDD) + } else { + if (preg_match('/^(\d{2}){3,7}$/', $buffer)) { + + if (mb_strlen($buffer) == 14 || mb_strlen($buffer) == 8) { + $offset = 4; + } else { + $offset = 2; + } + + $aDate = array(); + $aDate['year'] = (int) + mb_substr($buffer, 0, $offset); + $aDate['month'] = (int) + mb_substr($buffer, $offset, 2); + $aDate['day'] = (int) + mb_substr($buffer, $offset + 2, 2); + $aDate['hour'] = (int) + mb_substr($buffer, $offset + 4, 2); + $aDate['minute'] = (int) + mb_substr($buffer, $offset + 6, 2); + $aDate['second'] = (int) + mb_substr($buffer, $offset + 8, 2); + + if (checkdate($aDate['month'], $aDate['day'], $aDate['year'])) { + $timestamp = mktime( + $aDate['hour'], + $aDate['minute'], + $aDate['second'], + $aDate['month'], + $aDate['day'], + $aDate['year'] + ); + } + // If all fails, assume one of the dozens of valid strtime() syntaxes + // (https://www.gnu.org/manual/tar-1.12/html_chapter/tar_7.html) + } else { + if (preg_match('/^[0-9]\d{1,9}$/', $buffer)) { + $timestamp = (int)$buffer; + } else { + $timestamp = strtotime($buffer); + } + } + } + + // If all above failed, maybe it's a Unix timestamp already? + if ($timestamp < 0 && preg_match('/^[1-9]\d{1,9}$/', $buffer)) { + $timestamp = $buffer; + } + + // Reformat a valid timestamp + if ($timestamp >= 0) { + $timestamp -= $options[0] * 60 * 60; + $source = $buffer; + if ($options[2] == 'local') { + $text = Util::localisedDate( + $timestamp, + $options[1] + ); + } elseif ($options[2] == 'utc') { + $text = gmdate($options[1], $timestamp); + } else { + $text = 'INVALID DATE TYPE'; + } + return '' . htmlspecialchars($text) . ''; + } + + return htmlspecialchars($buffer); + } + + /* ~~~~~~~~~~~~~~~~~~~~ Getters and Setters ~~~~~~~~~~~~~~~~~~~~ */ + + /** + * Gets the transformation name of the specific plugin + * + * @return string + */ + public static function getName() + { + return "Date Format"; + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Transformations/Abs/DownloadTransformationsPlugin.php b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Transformations/Abs/DownloadTransformationsPlugin.php new file mode 100644 index 00000000..d205b9f0 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Transformations/Abs/DownloadTransformationsPlugin.php @@ -0,0 +1,90 @@ + $val) { + if ($val->name == $options[1]) { + $pos = $key; + break; + } + } + if (isset($pos)) { + $cn = $row[$pos]; + } + } + if (empty($cn)) { + $cn = 'binary_file.dat'; + } + } + + return sprintf( + '%s', + $options['wrapper_link'], + htmlspecialchars(urlencode($cn)), + htmlspecialchars($cn), + htmlspecialchars($cn) + ); + } + + + /* ~~~~~~~~~~~~~~~~~~~~ Getters and Setters ~~~~~~~~~~~~~~~~~~~~ */ + + /** + * Gets the transformation name of the specific plugin + * + * @return string + */ + public static function getName() + { + return "Download"; + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Transformations/Abs/ExternalTransformationsPlugin.php b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Transformations/Abs/ExternalTransformationsPlugin.php new file mode 100644 index 00000000..d3286aa5 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Transformations/Abs/ExternalTransformationsPlugin.php @@ -0,0 +1,151 @@ +getOptions( + $options, + $cfg['DefaultTransformations']['External'] + ); + + if (isset($allowed_programs[$options[0]])) { + $program = $allowed_programs[$options[0]]; + } else { + $program = $allowed_programs[0]; + } + + // needs PHP >= 4.3.0 + $newstring = ''; + $descriptorspec = array( + 0 => array("pipe", "r"), + 1 => array("pipe", "w"), + ); + $process = proc_open($program . ' ' . $options[1], $descriptorspec, $pipes); + if (is_resource($process)) { + fwrite($pipes[0], $buffer); + fclose($pipes[0]); + + while (!feof($pipes[1])) { + $newstring .= fgets($pipes[1], 1024); + } + fclose($pipes[1]); + // we don't currently use the return value + proc_close($process); + } + + if ($options[2] == 1 || $options[2] == '2') { + $retstring = htmlspecialchars($newstring); + } else { + $retstring = $newstring; + } + + return $retstring; + } + + + /* ~~~~~~~~~~~~~~~~~~~~ Getters and Setters ~~~~~~~~~~~~~~~~~~~~ */ + + /** + * Gets the transformation name of the specific plugin + * + * @return string + */ + public static function getName() + { + return "External"; + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Transformations/Abs/FormattedTransformationsPlugin.php b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Transformations/Abs/FormattedTransformationsPlugin.php new file mode 100644 index 00000000..c97f1268 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Transformations/Abs/FormattedTransformationsPlugin.php @@ -0,0 +1,62 @@ +'; + } + + + /* ~~~~~~~~~~~~~~~~~~~~ Getters and Setters ~~~~~~~~~~~~~~~~~~~~ */ + + /** + * Gets the transformation name of the specific plugin + * + * @return string + */ + public static function getName() + { + return "Formatted"; + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Transformations/Abs/HexTransformationsPlugin.php b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Transformations/Abs/HexTransformationsPlugin.php new file mode 100644 index 00000000..627de1f8 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Transformations/Abs/HexTransformationsPlugin.php @@ -0,0 +1,68 @@ +getOptions($options, $cfg['DefaultTransformations']['Hex']); + $options[0] = intval($options[0]); + + if ($options[0] < 1) { + return bin2hex($buffer); + } else { + return chunk_split(bin2hex($buffer), $options[0], ' '); + } + } + + /* ~~~~~~~~~~~~~~~~~~~~ Getters and Setters ~~~~~~~~~~~~~~~~~~~~ */ + + /** + * Gets the transformation name of the specific plugin + * + * @return string + */ + public static function getName() + { + return "Hex"; + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Transformations/Abs/ImageLinkTransformationsPlugin.php b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Transformations/Abs/ImageLinkTransformationsPlugin.php new file mode 100644 index 00000000..6dca346a --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Transformations/Abs/ImageLinkTransformationsPlugin.php @@ -0,0 +1,64 @@ +[BLOB]'; + } + + /* ~~~~~~~~~~~~~~~~~~~~ Getters and Setters ~~~~~~~~~~~~~~~~~~~~ */ + + /** + * Gets the transformation name of the specific plugin + * + * @return string + */ + public static function getName() + { + return "ImageLink"; + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Transformations/Abs/ImageUploadTransformationsPlugin.php b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Transformations/Abs/ImageUploadTransformationsPlugin.php new file mode 100644 index 00000000..3909149d --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Transformations/Abs/ImageUploadTransformationsPlugin.php @@ -0,0 +1,118 @@ +'; + $html .= ''; + $src = 'transformation_wrapper.php' . $options['wrapper_link']; + } + $html .= ''
+            . __('Image preview here') . ''; + $html .= '
    '; + + return $html; + } + + /** + * Returns the array of scripts (filename) required for plugin + * initialization and handling + * + * @return array javascripts to be included + */ + public function getScripts() + { + return array( + 'transformations/image_upload.js', + ); + } + + /* ~~~~~~~~~~~~~~~~~~~~ Getters and Setters ~~~~~~~~~~~~~~~~~~~~ */ + + /** + * Gets the transformation name of the specific plugin + * + * @return string + */ + public static function getName() + { + return "Image upload"; + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Transformations/Abs/InlineTransformationsPlugin.php b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Transformations/Abs/InlineTransformationsPlugin.php new file mode 100644 index 00000000..a56fd80d --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Transformations/Abs/InlineTransformationsPlugin.php @@ -0,0 +1,79 @@ +getOptions($options, $cfg['DefaultTransformations']['Inline']); + + if (PMA_IS_GD2) { + return '[' . htmlspecialchars($buffer) . ']'; + } else { + return '[' . htmlspecialchars($buffer) . ']'; + } + } + + + + /* ~~~~~~~~~~~~~~~~~~~~ Getters and Setters ~~~~~~~~~~~~~~~~~~~~ */ + + /** + * Gets the transformation name of the specific plugin + * + * @return string + */ + public static function getName() + { + return "Inline"; + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Transformations/Abs/LongToIPv4TransformationsPlugin.php b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Transformations/Abs/LongToIPv4TransformationsPlugin.php new file mode 100644 index 00000000..8498d1b7 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Transformations/Abs/LongToIPv4TransformationsPlugin.php @@ -0,0 +1,63 @@ + 4294967295) { + return htmlspecialchars($buffer); + } + + return long2ip($buffer); + } + + + /* ~~~~~~~~~~~~~~~~~~~~ Getters and Setters ~~~~~~~~~~~~~~~~~~~~ */ + + /** + * Gets the transformation name of the specific plugin + * + * @return string + */ + public static function getName() + { + return "Long To IPv4"; + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Transformations/Abs/PreApPendTransformationsPlugin.php b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Transformations/Abs/PreApPendTransformationsPlugin.php new file mode 100644 index 00000000..401e34ba --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Transformations/Abs/PreApPendTransformationsPlugin.php @@ -0,0 +1,65 @@ +getOptions($options, $cfg['DefaultTransformations']['PreApPend']); + + //just prepend and/or append the options to the original text + return htmlspecialchars($options[0]) . htmlspecialchars($buffer) + . htmlspecialchars($options[1]); + } + + /* ~~~~~~~~~~~~~~~~~~~~ Getters and Setters ~~~~~~~~~~~~~~~~~~~~ */ + + /** + * Gets the transformation name of the specific plugin + * + * @return string + */ + public static function getName() + { + return "PreApPend"; + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Transformations/Abs/RegexValidationTransformationsPlugin.php b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Transformations/Abs/RegexValidationTransformationsPlugin.php new file mode 100644 index 00000000..158cad71 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Transformations/Abs/RegexValidationTransformationsPlugin.php @@ -0,0 +1,71 @@ +reset(); + if (!empty($options[0]) && !preg_match($options[0], $buffer)) { + $this->success = false; + $this->error = sprintf( + __('Validation failed for the input string %s.'), + htmlspecialchars($buffer) + ); + } + + return $buffer; + } + + /* ~~~~~~~~~~~~~~~~~~~~ Getters and Setters ~~~~~~~~~~~~~~~~~~~~ */ + + /** + * Gets the transformation name of the specific plugin + * + * @return string + */ + public static function getName() + { + return "Regex Validation"; + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Transformations/Abs/SQLTransformationsPlugin.php b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Transformations/Abs/SQLTransformationsPlugin.php new file mode 100644 index 00000000..bd1ad742 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Transformations/Abs/SQLTransformationsPlugin.php @@ -0,0 +1,62 @@ +getOptions($options, $cfg['DefaultTransformations']['Substring']); + + if ($options[1] != 'all') { + $newtext = mb_substr( + $buffer, + $options[0], + $options[1] + ); + } else { + $newtext = mb_substr($buffer, $options[0]); + } + + $length = mb_strlen($newtext); + $baselength = mb_strlen($buffer); + if ($length != $baselength) { + if ($options[0] != 0) { + $newtext = $options[2] . $newtext; + } + + if (($length + $options[0]) != $baselength) { + $newtext .= $options[2]; + } + } + + return htmlspecialchars($newtext); + } + + + /* ~~~~~~~~~~~~~~~~~~~~ Getters and Setters ~~~~~~~~~~~~~~~~~~~~ */ + + /** + * Gets the transformation name of the specific plugin + * + * @return string + */ + public static function getName() + { + return "Substring"; + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Transformations/Abs/TextFileUploadTransformationsPlugin.php b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Transformations/Abs/TextFileUploadTransformationsPlugin.php new file mode 100644 index 00000000..ff90bf9f --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Transformations/Abs/TextFileUploadTransformationsPlugin.php @@ -0,0 +1,100 @@ +'; + $html .= ''; + } + $html .= ''; + + return $html; + } + + /* ~~~~~~~~~~~~~~~~~~~~ Getters and Setters ~~~~~~~~~~~~~~~~~~~~ */ + + /** + * Gets the transformation name of the specific plugin + * + * @return string + */ + public static function getName() + { + return "Text file upload"; + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Transformations/Abs/TextImageLinkTransformationsPlugin.php b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Transformations/Abs/TextImageLinkTransformationsPlugin.php new file mode 100644 index 00000000..1229f84a --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Transformations/Abs/TextImageLinkTransformationsPlugin.php @@ -0,0 +1,76 @@ +getOptions($options, $cfg['DefaultTransformations']['TextImageLink']); + $url = $options[0] . $buffer; + /* Do not allow javascript links */ + if (! Sanitize::checkLink($url, true, true)) { + return htmlspecialchars($url); + } + return '' + . htmlspecialchars($buffer) . ''; + } + + + /* ~~~~~~~~~~~~~~~~~~~~ Getters and Setters ~~~~~~~~~~~~~~~~~~~~ */ + + /** + * Gets the transformation name of the specific plugin + * + * @return string + */ + public static function getName() + { + return "Image Link"; + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Transformations/Abs/TextLinkTransformationsPlugin.php b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Transformations/Abs/TextLinkTransformationsPlugin.php new file mode 100644 index 00000000..29a4d74c --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Transformations/Abs/TextLinkTransformationsPlugin.php @@ -0,0 +1,78 @@ +getOptions($options, $cfg['DefaultTransformations']['TextLink']); + $url = (isset($options[0]) ? $options[0] : '') . ((isset($options[2]) && $options[2]) ? '' : $buffer); + /* Do not allow javascript links */ + if (! Sanitize::checkLink($url, true, true)) { + return htmlspecialchars($url); + } + return '' + . htmlspecialchars(isset($options[1]) ? $options[1] : $buffer) + . ''; + } + + + /* ~~~~~~~~~~~~~~~~~~~~ Getters and Setters ~~~~~~~~~~~~~~~~~~~~ */ + + /** + * Gets the transformation name of the specific plugin + * + * @return string + */ + public static function getName() + { + return "TextLink"; + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Transformations/Input/Image_JPEG_Upload.php b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Transformations/Input/Image_JPEG_Upload.php new file mode 100644 index 00000000..2478658e --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Transformations/Input/Image_JPEG_Upload.php @@ -0,0 +1,42 @@ +'; + } + $class = 'transform_IPToBin'; + $html .= ''; + + return $html; + } + + /* ~~~~~~~~~~~~~~~~~~~~ Getters and Setters ~~~~~~~~~~~~~~~~~~~~ */ + + /** + * Gets the transformation name of the plugin + * + * @return string + */ + public static function getName() + { + return "IPv4/IPv6 To Binary"; + } + + /** + * Gets the plugin`s MIME type + * + * @return string + */ + public static function getMIMEType() + { + return "Text"; + } + + /** + * Gets the plugin`s MIME subtype + * + * @return string + */ + public static function getMIMESubtype() + { + return "Plain"; + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Transformations/Input/Text_Plain_JsonEditor.php b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Transformations/Input/Text_Plain_JsonEditor.php new file mode 100644 index 00000000..75ae8a5e --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Transformations/Input/Text_Plain_JsonEditor.php @@ -0,0 +1,83 @@ +getHeader() + ->getScripts(); + $scripts->addFile('vendor/codemirror/lib/codemirror.js'); + $scripts->addFile('vendor/codemirror/mode/javascript/javascript.js'); + $scripts->addFile('vendor/codemirror/addon/runmode/runmode.js'); + $scripts->addFile('transformations/json.js'); + } + } + + /** + * Gets the transformation description of the specific plugin + * + * @return string + */ + public static function getInfo() + { + return __( + 'Formats text as JSON with syntax highlighting.' + ); + } + + /** + * Does the actual work of each specific transformations plugin. + * + * @param string $buffer text to be transformed + * @param array $options transformation options + * @param string $meta meta information + * + * @return string + */ + public function applyTransformation($buffer, array $options = array(), $meta = '') + { + return '
    ' . "\n"
    +        . htmlspecialchars($buffer) . "\n"
    +        . '
    '; + } + + /* ~~~~~~~~~~~~~~~~~~~~ Getters and Setters ~~~~~~~~~~~~~~~~~~~~ */ + + /** + * Gets the plugin`s MIME type + * + * @return string + */ + public static function getMIMEType() + { + return "Text"; + } + + /** + * Gets the plugin`s MIME subtype + * + * @return string + */ + public static function getMIMESubtype() + { + return "Plain"; + } + + /** + * Gets the transformation name of the specific plugin + * + * @return string + */ + public static function getName() + { + return "JSON"; + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Transformations/Output/Text_Plain_Sql.php b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Transformations/Output/Text_Plain_Sql.php new file mode 100644 index 00000000..ebd9c5ec --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Transformations/Output/Text_Plain_Sql.php @@ -0,0 +1,58 @@ +getHeader() + ->getScripts(); + $scripts->addFile('vendor/codemirror/lib/codemirror.js'); + $scripts->addFile('vendor/codemirror/mode/sql/sql.js'); + $scripts->addFile('vendor/codemirror/addon/runmode/runmode.js'); + $scripts->addFile('functions.js'); + } + } + + /** + * Gets the plugin`s MIME type + * + * @return string + */ + public static function getMIMEType() + { + return "Text"; + } + + /** + * Gets the plugin`s MIME subtype + * + * @return string + */ + public static function getMIMESubtype() + { + return "Plain"; + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Transformations/Output/Text_Plain_Xml.php b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Transformations/Output/Text_Plain_Xml.php new file mode 100644 index 00000000..44bf6c0d --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Transformations/Output/Text_Plain_Xml.php @@ -0,0 +1,98 @@ +getHeader() + ->getScripts(); + $scripts->addFile('vendor/codemirror/lib/codemirror.js'); + $scripts->addFile('vendor/codemirror/mode/xml/xml.js'); + $scripts->addFile('vendor/codemirror/addon/runmode/runmode.js'); + $scripts->addFile('transformations/xml.js'); + } + } + + /** + * Gets the transformation description of the specific plugin + * + * @return string + */ + public static function getInfo() + { + return __( + 'Formats text as XML with syntax highlighting.' + ); + } + + /** + * Does the actual work of each specific transformations plugin. + * + * @param string $buffer text to be transformed + * @param array $options transformation options + * @param string $meta meta information + * + * @return string + */ + public function applyTransformation($buffer, array $options = array(), $meta = '') + { + return '
    ' . "\n"
    +        . htmlspecialchars($buffer) . "\n"
    +        . '
    '; + } + + /* ~~~~~~~~~~~~~~~~~~~~ Getters and Setters ~~~~~~~~~~~~~~~~~~~~ */ + + /** + * Gets the plugin`s MIME type + * + * @return string + */ + public static function getMIMEType() + { + return "Text"; + } + + /** + * Gets the plugin`s MIME subtype + * + * @return string + */ + public static function getMIMESubtype() + { + return "Plain"; + } + + /** + * Gets the transformation name of the specific plugin + * + * @return string + */ + public static function getName() + { + return "XML"; + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Transformations/README b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Transformations/README new file mode 100644 index 00000000..7d7a1255 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Transformations/README @@ -0,0 +1,4 @@ +TRANSFORMATION USAGE (Garvin Hicking, ) +==================== + +See the documentation for complete instructions on how to use transformation plugins. diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Transformations/TEMPLATE b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Transformations/TEMPLATE new file mode 100644 index 00000000..0f9c48d3 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Transformations/TEMPLATE @@ -0,0 +1,47 @@ + diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Transformations/TEMPLATE_ABSTRACT b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Transformations/TEMPLATE_ABSTRACT new file mode 100644 index 00000000..54ef4901 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Transformations/TEMPLATE_ABSTRACT @@ -0,0 +1,74 @@ +mimetype contains the original MimeType of the field (i.e. 'text/plain', 'image/jpeg' etc.) + + return $buffer; + } + + /* ~~~~~~~~~~~~~~~~~~~~ Getters and Setters ~~~~~~~~~~~~~~~~~~~~ */ + + + /** + * Gets the TransformationName of the specific plugin + * + * @return string + */ + public static function getName() + { + return "[TransformationName]"; + } +} +?> diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Transformations/Text_Plain_Link.php b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Transformations/Text_Plain_Link.php new file mode 100644 index 00000000..90c4bd3b --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/Transformations/Text_Plain_Link.php @@ -0,0 +1,41 @@ + $value) { + if (isset($options[$key]) && $options[$key] !== '') { + $result[$key] = $options[$key]; + } else { + $result[$key] = $value; + } + } + + return $result; + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Plugins/TwoFactor/Application.php b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/TwoFactor/Application.php new file mode 100644 index 00000000..c739b980 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/TwoFactor/Application.php @@ -0,0 +1,158 @@ +_google2fa = new Google2FA(); + $this->_google2fa->setWindow(8); + if (!isset($this->_twofactor->config['settings']['secret'])) { + $this->_twofactor->config['settings']['secret'] = ''; + } + } + + /** + * Get any property of this class + * + * @param string $property name of the property + * + * @return mixed|void if property exist, value of the relevant property + */ + public function __get($property) + { + switch ($property) { + case 'google2fa': + return $this->_google2fa; + } + } + + /** + * Checks authentication, returns true on success + * + * @return boolean + */ + public function check() + { + $this->_provided = false; + if (!isset($_POST['2fa_code'])) { + return false; + } + $this->_provided = true; + return $this->_google2fa->verifyKey( + $this->_twofactor->config['settings']['secret'], $_POST['2fa_code'] + ); + } + + /** + * Renders user interface to enter two-factor authentication + * + * @return string HTML code + */ + public function render() + { + return Template::get('login/twofactor/application')->render(); + } + + /** + * Renders user interface to configure two-factor authentication + * + * @return string HTML code + */ + public function setup() + { + $secret = $this->_twofactor->config['settings']['secret']; + $renderArray = ['secret' => $secret]; + if (extension_loaded('gd')) { + $inlineUrl = $this->_google2fa->getQRCodeInline( + 'phpMyAdmin (' . $this->getAppId(false) . ')', + $this->_twofactor->user, + $secret + ); + $renderArray['image'] = $inlineUrl; + } else { + $inlineUrl = $this->_google2fa->getQRCodeUrl( + 'phpMyAdmin (' . $this->getAppId(false) . ')', + $this->_twofactor->user, + $secret + ); + trigger_error( + __( + 'The gd PHP extension was not found.' + . ' The QRcode can not be displayed without the gd PHP extension.' + ), + E_USER_WARNING + ); + $renderArray['url'] = $inlineUrl; + } + return Template::get('login/twofactor/application_configure')->render($renderArray); + } + + /** + * Performs backend configuration + * + * @return boolean + */ + public function configure() + { + if (! isset($_SESSION['2fa_application_key'])) { + $_SESSION['2fa_application_key'] = $this->_google2fa->generateSecretKey(); + } + $this->_twofactor->config['settings']['secret'] = $_SESSION['2fa_application_key']; + + $result = $this->check(); + if ($result) { + unset($_SESSION['2fa_application_key']); + } + return $result; + } + + /** + * Get user visible name + * + * @return string + */ + public static function getName() + { + return __('Authentication Application (2FA)'); + } + + /** + * Get user visible description + * + * @return string + */ + public static function getDescription() + { + return __('Provides authentication using HOTP and TOTP applications such as FreeOTP, Google Authenticator or Authy.'); + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Plugins/TwoFactor/Invalid.php b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/TwoFactor/Invalid.php new file mode 100644 index 00000000..b4fbfd5a --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/TwoFactor/Invalid.php @@ -0,0 +1,65 @@ +render(); + } + + /** + * Get user visible name + * + * @return string + */ + public static function getName() + { + return 'Invalid two-factor authentication'; + } + + /** + * Get user visible description + * + * @return string + */ + public static function getDescription() + { + return 'Error fallback only!'; + } +} + diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Plugins/TwoFactor/Key.php b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/TwoFactor/Key.php new file mode 100644 index 00000000..3ce4b236 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/TwoFactor/Key.php @@ -0,0 +1,198 @@ +_twofactor->config['settings']['registrations'])) { + $this->_twofactor->config['settings']['registrations'] = []; + } + } + + /** + * Returns array of U2F registration objects + * + * @return array + */ + public function getRegistrations() + { + $result = []; + foreach ($this->_twofactor->config['settings']['registrations'] as $index => $data) { + $reg = new \StdClass; + $reg->keyHandle = $data['keyHandle']; + $reg->publicKey = $data['publicKey']; + $reg->certificate = $data['certificate']; + $reg->counter = $data['counter']; + $reg->index = $index; + $result[] = $reg; + } + return $result; + } + + /** + * Checks authentication, returns true on success + * + * @return boolean + */ + public function check() + { + $this->_provided = false; + if (!isset($_POST['u2f_authentication_response']) || !isset($_SESSION['authenticationRequest'])) { + return false; + } + $this->_provided = true; + try { + $response = json_decode($_POST['u2f_authentication_response']); + if (is_null($response)) { + return false; + } + $authentication = U2FServer::authenticate( + $_SESSION['authenticationRequest'], + $this->getRegistrations(), + $response + ); + $this->_twofactor->config['settings']['registrations'][$authentication->index]['counter'] = $authentication->counter; + $this->_twofactor->save(); + return true; + } catch (U2FException $e) { + $this->_message = $e->getMessage(); + return false; + } + } + + /** + * Loads needed javascripts into the page + * + * @return void + */ + public function loadScripts() + { + $response = Response::getInstance(); + $scripts = $response->getHeader()->getScripts(); + $scripts->addFile('vendor/u2f-api-polyfill.js'); + $scripts->addFile('u2f.js'); + } + + /** + * Renders user interface to enter two-factor authentication + * + * @return string HTML code + */ + public function render() + { + $request = U2FServer::makeAuthentication( + $this->getRegistrations(), + $this->getAppId(true) + ); + $_SESSION['authenticationRequest'] = $request; + $this->loadScripts(); + return Template::get('login/twofactor/key')->render([ + 'request' => json_encode($request), + 'is_https' => $GLOBALS['PMA_Config']->isHttps(), + ]); + } + + /** + * Renders user interface to configure two-factor authentication + * + * @return string HTML code + */ + public function setup() + { + $registrationData = U2FServer::makeRegistration( + $this->getAppId(true), + $this->getRegistrations() + ); + $_SESSION['registrationRequest'] = $registrationData['request']; + + $this->loadScripts(); + return Template::get('login/twofactor/key_configure')->render([ + 'request' => json_encode($registrationData['request']), + 'signatures' => json_encode($registrationData['signatures']), + 'is_https' => $GLOBALS['PMA_Config']->isHttps(), + ]); + } + + /** + * Performs backend configuration + * + * @return boolean + */ + public function configure() + { + $this->_provided = false; + if (! isset($_POST['u2f_registration_response']) || ! isset($_SESSION['registrationRequest'])) { + return false; + } + $this->_provided = true; + try { + $response = json_decode($_POST['u2f_registration_response']); + if (is_null($response)) { + return false; + } + $registration = U2FServer::register( + $_SESSION['registrationRequest'], $response + ); + $this->_twofactor->config['settings']['registrations'][] = [ + 'keyHandle' => $registration->getKeyHandle(), + 'publicKey' => $registration->getPublicKey(), + 'certificate' => $registration->getCertificate(), + 'counter' => $registration->getCounter(), + ]; + return true; + } catch (U2FException $e) { + $this->_message = $e->getMessage(); + return false; + } + } + + /** + * Get user visible name + * + * @return string + */ + public static function getName() + { + return __('Hardware Security Key (FIDO U2F)'); + } + + /** + * Get user visible description + * + * @return string + */ + public static function getDescription() + { + return __('Provides authentication using hardware security tokens supporting FIDO U2F.'); + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Plugins/TwoFactor/Simple.php b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/TwoFactor/Simple.php new file mode 100644 index 00000000..70d3a614 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/TwoFactor/Simple.php @@ -0,0 +1,64 @@ +render(); + } + + /** + * Get user visible name + * + * @return string + */ + public static function getName() + { + return __('Simple two-factor authentication'); + } + + /** + * Get user visible description + * + * @return string + */ + public static function getDescription() + { + return __('For testing purposes only!'); + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Plugins/TwoFactorPlugin.php b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/TwoFactorPlugin.php new file mode 100644 index 00000000..8e91b786 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/TwoFactorPlugin.php @@ -0,0 +1,170 @@ +_twofactor = $twofactor; + $this->_provided = false; + $this->_message = ''; + } + + /** + * Returns authentication error message + * + * @return string + */ + public function getError() + { + if ($this->_provided) { + if (!empty($this->_message)) { + return Message::rawError( + sprintf(__('Two-factor authentication failed: %s'), $this->_message) + )->getDisplay(); + } + return Message::rawError( + __('Two-factor authentication failed.') + )->getDisplay(); + } + return ''; + } + + /** + * Checks authentication, returns true on success + * + * @return boolean + */ + public function check() + { + return true; + } + + /** + * Renders user interface to enter two-factor authentication + * + * @return string HTML code + */ + public function render() + { + return ''; + } + + /** + * Renders user interface to configure two-factor authentication + * + * @return string HTML code + */ + public function setup() + { + return ''; + } + + /** + * Performs backend configuration + * + * @return boolean + */ + public function configure() + { + return true; + } + + /** + * Get user visible name + * + * @return string + */ + public static function getName() + { + return __('No Two-Factor'); + } + + /** + * Get user visible description + * + * @return string + */ + public static function getDescription() + { + return __('Login using password only.'); + } + + /** + * Return an applicaiton ID + * + * Either hostname or hostname with scheme. + * + * @param boolean $return_url Whether to generate URL + * + * @return string + */ + public function getAppId($return_url) + { + global $PMA_Config; + + $url = $PMA_Config->get('PmaAbsoluteUri'); + $parsed = []; + if (!empty($url)) { + $parsed = parse_url($url); + } + if (empty($parsed['scheme'])) { + $parsed['scheme'] = $PMA_Config->isHttps() ? 'https' : 'http'; + } + if (empty($parsed['host'])) { + $parsed['host'] = Core::getenv('HTTP_HOST'); + } + if ($return_url) { + return $parsed['scheme'] . '://' . $parsed['host'] . (!empty($parsed['port']) ? ':' . $parsed['port'] : ''); + } else { + return $parsed['host']; + } + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Plugins/UploadInterface.php b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/UploadInterface.php new file mode 100644 index 00000000..28bf9d0a --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Plugins/UploadInterface.php @@ -0,0 +1,33 @@ +upload plugins + * + * @package PhpMyAdmin + */ +namespace PhpMyAdmin\Plugins; + +/** + * Provides a common interface that will have to implemented by all of the + * import->upload plugins. + * + * @package PhpMyAdmin + */ +interface UploadInterface +{ + /** + * Gets the specific upload ID Key + * + * @return string ID Key + */ + public static function getIdKey(); + + /** + * Returns upload status. + * + * @param string $id upload id + * + * @return array|null + */ + public static function getUploadStatus($id); +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Properties/Options/Groups/OptionsPropertyMainGroup.php b/php/apps/phpmyadmin49/html/libraries/classes/Properties/Options/Groups/OptionsPropertyMainGroup.php new file mode 100644 index 00000000..d976da5a --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Properties/Options/Groups/OptionsPropertyMainGroup.php @@ -0,0 +1,33 @@ +_subgroupHeader; + } + + /** + * Sets the subgroup header + * + * @param \PhpMyAdmin\Properties\PropertyItem $subgroupHeader subgroup header + * + * @return void + */ + public function setSubgroupHeader($subgroupHeader) + { + $this->_subgroupHeader = $subgroupHeader; + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Properties/Options/Items/BoolPropertyItem.php b/php/apps/phpmyadmin49/html/libraries/classes/Properties/Options/Items/BoolPropertyItem.php new file mode 100644 index 00000000..2a45cf0b --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Properties/Options/Items/BoolPropertyItem.php @@ -0,0 +1,33 @@ +getProperties() == null + && in_array($property, $this->getProperties(), true) + ) { + return; + } + $this->_properties [] = $property; + } + + /** + * Removes a property from the group of properties + * + * @param OptionsPropertyItem $property the property instance to be removed + * from the group + * + * @return void + */ + public function removeProperty($property) + { + $this->_properties = array_diff( + $this->getProperties(), + array($property) + ); + } + + + /* ~~~~~~~~~~~~~~~~~~~~ Getters and Setters ~~~~~~~~~~~~~~~~~~~~ */ + + /** + * Gets the instance of the class + * + * @return array + */ + public function getGroup() + { + return $this; + } + + /** + * Gets the group of properties + * + * @return array + */ + public function getProperties() + { + return $this->_properties; + } + + /** + * Gets the number of properties + * + * @return int + */ + public function getNrOfProperties() + { + if (is_null($this->_properties)) { + return 0; + } + return count($this->_properties); + } + + /** + * Countable interface implementation. + * + * @return int + */ + public function count() { + return $this->getNrOfProperties(); + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Properties/Options/OptionsPropertyItem.php b/php/apps/phpmyadmin49/html/libraries/classes/Properties/Options/OptionsPropertyItem.php new file mode 100644 index 00000000..2e4d6ac0 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Properties/Options/OptionsPropertyItem.php @@ -0,0 +1,134 @@ +_name = $name; + } + if ($text) { + $this->_text = $text; + } + } + + /* ~~~~~~~~~~~~~~~~~~~~ Getters and Setters ~~~~~~~~~~~~~~~~~~~~ */ + + /** + * Gets the name + * + * @return string + */ + public function getName() + { + return $this->_name; + } + + /** + * Sets the name + * + * @param string $name name + * + * @return void + */ + public function setName($name) + { + $this->_name = $name; + } + + /** + * Gets the text + * + * @return string + */ + public function getText() + { + return $this->_text; + } + + /** + * Sets the text + * + * @param string $text text + * + * @return void + */ + public function setText($text) + { + $this->_text = $text; + } + + /** + * Gets the force parameter + * + * @return string + */ + public function getForce() + { + return $this->_force; + } + + /** + * Sets the force parameter + * + * @param string $force force parameter + * + * @return void + */ + public function setForce($force) + { + $this->_force = $force; + } + + /** + * Returns the property type ( either "options", or "plugin" ). + * + * @return string + */ + public function getPropertyType() + { + return "options"; + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Properties/Options/OptionsPropertyOneItem.php b/php/apps/phpmyadmin49/html/libraries/classes/Properties/Options/OptionsPropertyOneItem.php new file mode 100644 index 00000000..d44a7fe6 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Properties/Options/OptionsPropertyOneItem.php @@ -0,0 +1,159 @@ +_force_one; + } + + /** + * Sets the force parameter + * + * @param bool $force force parameter + * + * @return void + */ + public function setForce($force) + { + $this->_force_one = $force; + } + + /** + * Gets the values + * + * @return string + */ + public function getValues() + { + return $this->_values; + } + + /** + * Sets the values + * + * @param array $values values + * + * @return void + */ + public function setValues(array $values) + { + $this->_values = $values; + } + + /** + * Gets MySQL documentation pointer + * + * @return array + */ + public function getDoc() + { + return $this->_doc; + } + + /** + * Sets the doc + * + * @param string $doc MySQL documentation pointer + * + * @return void + */ + public function setDoc($doc) + { + $this->_doc = $doc; + } + + /** + * Gets the length + * + * @return int + */ + public function getLen() + { + return $this->_len; + } + + /** + * Sets the length + * + * @param int $len length + * + * @return void + */ + public function setLen($len) + { + $this->_len = $len; + } + + /** + * Gets the size + * + * @return int + */ + public function getSize() + { + return $this->_size; + } + + /** + * Sets the size + * + * @param int $size size + * + * @return void + */ + public function setSize($size) + { + $this->_size = $size; + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Properties/Plugins/ExportPluginProperties.php b/php/apps/phpmyadmin49/html/libraries/classes/Properties/Plugins/ExportPluginProperties.php new file mode 100644 index 00000000..a5a19da9 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Properties/Plugins/ExportPluginProperties.php @@ -0,0 +1,62 @@ +_forceFile; + } + + /** + * Sets the force file parameter + * + * @param bool $forceFile the force file parameter + * + * @return void + */ + public function setForceFile($forceFile) + { + $this->_forceFile = $forceFile; + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Properties/Plugins/ImportPluginProperties.php b/php/apps/phpmyadmin49/html/libraries/classes/Properties/Plugins/ImportPluginProperties.php new file mode 100644 index 00000000..bbcdd59e --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Properties/Plugins/ImportPluginProperties.php @@ -0,0 +1,31 @@ +_text; + } + + /** + * Sets the text + * + * @param string $text text + * + * @return void + */ + public function setText($text) + { + $this->_text = $text; + } + + /** + * Gets the extension + * + * @return string + */ + public function getExtension() + { + return $this->_extension; + } + + /** + * Sets the extension + * + * @param string $extension extension + * + * @return void + */ + public function setExtension($extension) + { + $this->_extension = $extension; + } + + /** + * Gets the options + * + * @return OptionsPropertyRootGroup + */ + public function getOptions() + { + return $this->_options; + } + + /** + * Sets the options + * + * @param OptionsPropertyRootGroup $options options + * + * @return void + */ + public function setOptions($options) + { + $this->_options = $options; + } + + /** + * Gets the options text + * + * @return string + */ + public function getOptionsText() + { + return $this->_optionsText; + } + + /** + * Sets the options text + * + * @param string $optionsText optionsText + * + * @return void + */ + public function setOptionsText($optionsText) + { + $this->_optionsText = $optionsText; + } + + /** + * Gets the MIME type + * + * @return string + */ + public function getMimeType() + { + return $this->_mimeType; + } + + /** + * Sets the MIME type + * + * @param string $mimeType MIME type + * + * @return void + */ + public function setMimeType($mimeType) + { + $this->_mimeType = $mimeType; + } + + /** + * Returns the property type ( either "options", or "plugin" ). + * + * @return string + */ + public function getPropertyType() + { + return "plugin"; + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Properties/Plugins/SchemaPluginProperties.php b/php/apps/phpmyadmin49/html/libraries/classes/Properties/Plugins/SchemaPluginProperties.php new file mode 100644 index 00000000..e3635337 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Properties/Plugins/SchemaPluginProperties.php @@ -0,0 +1,44 @@ +relation = new Relation(); + $this->_tableType = $type; + $server_id = $GLOBALS['server']; + if (! isset($_SESSION['tmpval'][$this->_tableType . '_tables'][$server_id]) + ) { + $_SESSION['tmpval'][$this->_tableType . '_tables'][$server_id] + = $this->_getPmaTable() ? $this->getFromDb() : array(); + } + $this->_tables + =& $_SESSION['tmpval'][$this->_tableType . '_tables'][$server_id]; + } + + /** + * Returns class instance. + * + * @param string $type the table type + * + * @return RecentFavoriteTable + */ + public static function getInstance($type) + { + if (! array_key_exists($type, self::$_instances)) { + self::$_instances[$type] = new RecentFavoriteTable($type); + } + return self::$_instances[$type]; + } + + /** + * Returns the recent/favorite tables array + * + * @return array + */ + public function getTables() + { + return $this->_tables; + } + + /** + * Returns recently used tables or favorite from phpMyAdmin database. + * + * @return array + */ + public function getFromDb() + { + // Read from phpMyAdmin database, if recent tables is not in session + $sql_query + = " SELECT `tables` FROM " . $this->_getPmaTable() . + " WHERE `username` = '" . $GLOBALS['dbi']->escapeString($GLOBALS['cfg']['Server']['user']) . "'"; + + $return = array(); + $result = $this->relation->queryAsControlUser($sql_query, false); + if ($result) { + $row = $GLOBALS['dbi']->fetchArray($result); + if (isset($row[0])) { + $return = json_decode($row[0], true); + } + } + return $return; + } + + /** + * Save recent/favorite tables into phpMyAdmin database. + * + * @return true|Message + */ + public function saveToDb() + { + $username = $GLOBALS['cfg']['Server']['user']; + $sql_query + = " REPLACE INTO " . $this->_getPmaTable() . " (`username`, `tables`)" . + " VALUES ('" . $GLOBALS['dbi']->escapeString($username) . "', '" + . $GLOBALS['dbi']->escapeString( + json_encode($this->_tables) + ) . "')"; + + $success = $GLOBALS['dbi']->tryQuery($sql_query, DatabaseInterface::CONNECT_CONTROL); + + if (! $success) { + $error_msg = ''; + switch ($this->_tableType) { + case 'recent': + $error_msg = __('Could not save recent table!'); + break; + + case 'favorite': + $error_msg = __('Could not save favorite table!'); + break; + } + $message = Message::error($error_msg); + $message->addMessage( + Message::rawError( + $GLOBALS['dbi']->getError(DatabaseInterface::CONNECT_CONTROL) + ), + '

    ' + ); + return $message; + } + return true; + } + + /** + * Trim recent.favorite table according to the + * NumRecentTables/NumFavoriteTables configuration. + * + * @return boolean True if trimming occurred + */ + public function trim() + { + $max = max( + $GLOBALS['cfg']['Num' . ucfirst($this->_tableType) . 'Tables'], 0 + ); + $trimming_occurred = count($this->_tables) > $max; + while (count($this->_tables) > $max) { + array_pop($this->_tables); + } + return $trimming_occurred; + } + + /** + * Return HTML ul. + * + * @return string + */ + public function getHtmlList() + { + $html = ''; + if (count($this->_tables)) { + if ($this->_tableType == 'recent') { + foreach ($this->_tables as $table) { + $html .= '
  • '; + } + } else { + foreach ($this->_tables as $table) { + $html .= ''; + } + } + } else { + $html .= ''; + } + return $html; + } + + /** + * Return HTML. + * + * @return string + */ + public function getHtml() + { + $html = '
    '; + if ($this->_tableType == 'recent') { + $html .= '' + . __('Recent') . '
      '; + } else { + $html .= '' + . __('Favorites') . '
        '; + } + $html .= $this->getHtmlList(); + $html .= '
    '; + return $html; + } + + /** + * Add recently used or favorite tables. + * + * @param string $db database name where the table is located + * @param string $table table name + * + * @return true|Message True if success, Message if not + */ + public function add($db, $table) + { + // If table does not exist, do not add._getPmaTable() + if (! $GLOBALS['dbi']->getColumns($db, $table)) { + return true; + } + + $table_arr = array(); + $table_arr['db'] = $db; + $table_arr['table'] = $table; + + // add only if this is new table + if (! isset($this->_tables[0]) || $this->_tables[0] != $table_arr) { + array_unshift($this->_tables, $table_arr); + $this->_tables = array_merge(array_unique($this->_tables, SORT_REGULAR)); + $this->trim(); + if ($this->_getPmaTable()) { + return $this->saveToDb(); + } + } + return true; + } + + /** + * Removes recent/favorite tables that don't exist. + * + * @param string $db database + * @param string $table table + * + * @return boolean|Message True if invalid and removed, False if not invalid, + * Message if error while removing + */ + public function removeIfInvalid($db, $table) + { + foreach ($this->_tables as $tbl) { + if ($tbl['db'] == $db && $tbl['table'] == $table) { + // TODO Figure out a better way to find the existence of a table + if (! $GLOBALS['dbi']->getColumns($tbl['db'], $tbl['table'])) { + return $this->remove($tbl['db'], $tbl['table']); + } + } + } + return false; + } + + /** + * Remove favorite tables. + * + * @param string $db database name where the table is located + * @param string $table table name + * + * @return true|Message True if success, Message if not + */ + public function remove($db, $table) + { + $table_arr = array(); + $table_arr['db'] = $db; + $table_arr['table'] = $table; + foreach ($this->_tables as $key => $value) { + if ($value['db'] == $db && $value['table'] == $table) { + unset($this->_tables[$key]); + } + } + if ($this->_getPmaTable()) { + return $this->saveToDb(); + } + return true; + } + + /** + * Generate Html for sync Favorite tables anchor. (from localStorage to pmadb) + * + * @return string + */ + public function getHtmlSyncFavoriteTables() + { + $retval = ''; + $server_id = $GLOBALS['server']; + if ($server_id == 0) { + return ''; + } + $cfgRelation = $this->relation->getRelationsParam(); + // Not to show this once list is synchronized. + if ($cfgRelation['favoritework'] && ! isset($_SESSION['tmpval']['favorites_synced'][$server_id])) { + $params = array('ajax_request' => true, 'favorite_table' => true, + 'sync_favorite_tables' => true); + $url = 'db_structure.php' . Url::getCommon($params); + $retval = ''; + } + return $retval; + } + + /** + * Generate Html to update recent tables. + * + * @return string html + */ + public static function getHtmlUpdateRecentTables() + { + $params = array('ajax_request' => true, 'recent_table' => true); + $url = 'index.php' . Url::getCommon($params); + $retval = ''; + return $retval; + } + + /** + * Reutrn the name of the configuration storage table + * + * @return string pma table name + */ + private function _getPmaTable() + { + $cfgRelation = $this->relation->getRelationsParam(); + if (! empty($cfgRelation['db']) + && ! empty($cfgRelation[$this->_tableType]) + ) { + return Util::backquote($cfgRelation['db']) . "." + . Util::backquote($cfgRelation[$this->_tableType]); + } + return null; + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Relation.php b/php/apps/phpmyadmin49/html/libraries/classes/Relation.php new file mode 100644 index 00000000..75ff20a8 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Relation.php @@ -0,0 +1,2117 @@ +query( + $sql, + DatabaseInterface::CONNECT_CONTROL, + $options, + $cache_affected_rows + ); + } else { + $result = @$GLOBALS['dbi']->tryQuery( + $sql, + DatabaseInterface::CONNECT_CONTROL, + $options, + $cache_affected_rows + ); + } // end if... else... + + if ($result) { + return $result; + } + + return false; + } + + /** + * Returns current relation parameters + * + * @return array $cfgRelation + */ + public function getRelationsParam() + { + if (empty($_SESSION['relation'][$GLOBALS['server']]) + || (empty($_SESSION['relation'][$GLOBALS['server']]['PMA_VERSION'])) + || $_SESSION['relation'][$GLOBALS['server']]['PMA_VERSION'] != PMA_VERSION + ) { + $_SESSION['relation'][$GLOBALS['server']] = $this->checkRelationsParam(); + } + + // just for BC but needs to be before getRelationsParamDiagnostic() + // which uses it + $GLOBALS['cfgRelation'] = $_SESSION['relation'][$GLOBALS['server']]; + + return $_SESSION['relation'][$GLOBALS['server']]; + } + + /** + * prints out diagnostic info for pma relation feature + * + * @param array $cfgRelation Relation configuration + * + * @return string + */ + public function getRelationsParamDiagnostic(array $cfgRelation) + { + $retval = '
    '; + + $messages = array(); + $messages['error'] = '' + . __('not OK') + . ''; + + $messages['ok'] = '' + . _pgettext('Correctly working', 'OK') + . ''; + + $messages['enabled'] = '' . __('Enabled') . ''; + $messages['disabled'] = '' . __('Disabled') . ''; + + if (strlen($cfgRelation['db']) == 0) { + $retval .= __('Configuration of pmadb…') . ' ' + . $messages['error'] + . Util::showDocu('setup', 'linked-tables') + . '
    ' . "\n" + . __('General relation features') + . ' ' . __('Disabled') + . '' . "\n"; + if ($GLOBALS['cfg']['ZeroConf']) { + if (strlen($GLOBALS['db']) == 0) { + $retval .= $this->getHtmlFixPmaTables(true, true); + } else { + $retval .= $this->getHtmlFixPmaTables(true); + } + } + } else { + $retval .= '' . "\n"; + + if (! $cfgRelation['allworks'] + && $GLOBALS['cfg']['ZeroConf'] + // Avoid showing a "Create missing tables" link if it's a + // problem of missing definition + && $this->arePmadbTablesDefined() + ) { + $retval .= $this->getHtmlFixPmaTables(false); + $retval .= '
    '; + } + + $retval .= $this->getDiagMessageForParameter( + 'pmadb', + $cfgRelation['db'], + $messages, + 'pmadb' + ); + $retval .= $this->getDiagMessageForParameter( + 'relation', + isset($cfgRelation['relation']), + $messages, + 'relation' + ); + $retval .= $this->getDiagMessageForFeature( + __('General relation features'), + 'relwork', + $messages + ); + $retval .= $this->getDiagMessageForParameter( + 'table_info', + isset($cfgRelation['table_info']), + $messages, + 'table_info' + ); + $retval .= $this->getDiagMessageForFeature( + __('Display Features'), + 'displaywork', + $messages + ); + $retval .= $this->getDiagMessageForParameter( + 'table_coords', + isset($cfgRelation['table_coords']), + $messages, + 'table_coords' + ); + $retval .= $this->getDiagMessageForParameter( + 'pdf_pages', + isset($cfgRelation['pdf_pages']), + $messages, + 'pdf_pages' + ); + $retval .= $this->getDiagMessageForFeature( + __('Designer and creation of PDFs'), + 'pdfwork', + $messages + ); + $retval .= $this->getDiagMessageForParameter( + 'column_info', + isset($cfgRelation['column_info']), + $messages, + 'column_info' + ); + $retval .= $this->getDiagMessageForFeature( + __('Displaying Column Comments'), + 'commwork', + $messages, + false + ); + $retval .= $this->getDiagMessageForFeature( + __('Browser transformation'), + 'mimework', + $messages + ); + if ($cfgRelation['commwork'] && ! $cfgRelation['mimework']) { + $retval .= ''; + } + $retval .= $this->getDiagMessageForParameter( + 'bookmarktable', + isset($cfgRelation['bookmark']), + $messages, + 'bookmark' + ); + $retval .= $this->getDiagMessageForFeature( + __('Bookmarked SQL query'), + 'bookmarkwork', + $messages + ); + $retval .= $this->getDiagMessageForParameter( + 'history', + isset($cfgRelation['history']), + $messages, + 'history' + ); + $retval .= $this->getDiagMessageForFeature( + __('SQL history'), + 'historywork', + $messages + ); + $retval .= $this->getDiagMessageForParameter( + 'recent', + isset($cfgRelation['recent']), + $messages, + 'recent' + ); + $retval .= $this->getDiagMessageForFeature( + __('Persistent recently used tables'), + 'recentwork', + $messages + ); + $retval .= $this->getDiagMessageForParameter( + 'favorite', + isset($cfgRelation['favorite']), + $messages, + 'favorite' + ); + $retval .= $this->getDiagMessageForFeature( + __('Persistent favorite tables'), + 'favoritework', + $messages + ); + $retval .= $this->getDiagMessageForParameter( + 'table_uiprefs', + isset($cfgRelation['table_uiprefs']), + $messages, + 'table_uiprefs' + ); + $retval .= $this->getDiagMessageForFeature( + __('Persistent tables\' UI preferences'), + 'uiprefswork', + $messages + ); + $retval .= $this->getDiagMessageForParameter( + 'tracking', + isset($cfgRelation['tracking']), + $messages, + 'tracking' + ); + $retval .= $this->getDiagMessageForFeature( + __('Tracking'), + 'trackingwork', + $messages + ); + $retval .= $this->getDiagMessageForParameter( + 'userconfig', + isset($cfgRelation['userconfig']), + $messages, + 'userconfig' + ); + $retval .= $this->getDiagMessageForFeature( + __('User preferences'), + 'userconfigwork', + $messages + ); + $retval .= $this->getDiagMessageForParameter( + 'users', + isset($cfgRelation['users']), + $messages, + 'users' + ); + $retval .= $this->getDiagMessageForParameter( + 'usergroups', + isset($cfgRelation['usergroups']), + $messages, + 'usergroups' + ); + $retval .= $this->getDiagMessageForFeature( + __('Configurable menus'), + 'menuswork', + $messages + ); + $retval .= $this->getDiagMessageForParameter( + 'navigationhiding', + isset($cfgRelation['navigationhiding']), + $messages, + 'navigationhiding' + ); + $retval .= $this->getDiagMessageForFeature( + __('Hide/show navigation items'), + 'navwork', + $messages + ); + $retval .= $this->getDiagMessageForParameter( + 'savedsearches', + isset($cfgRelation['savedsearches']), + $messages, + 'savedsearches' + ); + $retval .= $this->getDiagMessageForFeature( + __('Saving Query-By-Example searches'), + 'savedsearcheswork', + $messages + ); + $retval .= $this->getDiagMessageForParameter( + 'central_columns', + isset($cfgRelation['central_columns']), + $messages, + 'central_columns' + ); + $retval .= $this->getDiagMessageForFeature( + __('Managing Central list of columns'), + 'centralcolumnswork', + $messages + ); + $retval .= $this->getDiagMessageForParameter( + 'designer_settings', + isset($cfgRelation['designer_settings']), + $messages, + 'designer_settings' + ); + $retval .= $this->getDiagMessageForFeature( + __('Remembering Designer Settings'), + 'designersettingswork', + $messages + ); + $retval .= $this->getDiagMessageForParameter( + 'export_templates', + isset($cfgRelation['export_templates']), + $messages, + 'export_templates' + ); + $retval .= $this->getDiagMessageForFeature( + __('Saving export templates'), + 'exporttemplateswork', + $messages + ); + $retval .= '
    '; + $retval .= __( + 'Please see the documentation on how to' + . ' update your column_info table.' + ); + $retval .= Util::showDocu( + 'config', + 'cfg_Servers_column_info' + ); + $retval .= '
    ' . "\n"; + + if (! $cfgRelation['allworks']) { + + $retval .= '

    ' . __('Quick steps to set up advanced features:') + . '

    '; + + $items = array(); + $items[] = sprintf( + __( + 'Create the needed tables with the ' + . '%screate_tables.sql.' + ), + htmlspecialchars(SQL_DIR) + ) . ' ' . Util::showDocu('setup', 'linked-tables'); + $items[] = __('Create a pma user and give access to these tables.') . ' ' + . Util::showDocu('config', 'cfg_Servers_controluser'); + $items[] = __( + 'Enable advanced features in configuration file ' + . '(config.inc.php), for example by ' + . 'starting from config.sample.inc.php.' + ) . ' ' . Util::showDocu('setup', 'quick-install'); + $items[] = __( + 'Re-login to phpMyAdmin to load the updated configuration file.' + ); + + $retval .= Template::get('list/unordered')->render( + array('items' => $items,) + ); + } + } + + return $retval; + } + + /** + * prints out one diagnostic message for a feature + * + * @param string $feature_name feature name in a message string + * @param string $relation_parameter the $GLOBALS['cfgRelation'] parameter to check + * @param array $messages utility messages + * @param boolean $skip_line whether to skip a line after the message + * + * @return string + */ + public function getDiagMessageForFeature($feature_name, + $relation_parameter, array $messages, $skip_line = true + ) { + $retval = ' ' . $feature_name . ': '; + if (isset($GLOBALS['cfgRelation'][$relation_parameter]) + && $GLOBALS['cfgRelation'][$relation_parameter] + ) { + $retval .= $messages['enabled']; + } else { + $retval .= $messages['disabled']; + } + $retval .= ''; + if ($skip_line) { + $retval .= ' '; + } + return $retval; + } + + /** + * prints out one diagnostic message for a configuration parameter + * + * @param string $parameter config parameter name to display + * @param boolean $relationParameterSet whether this parameter is set + * @param array $messages utility messages + * @param string $docAnchor anchor in documentation + * + * @return string + */ + public function getDiagMessageForParameter($parameter, + $relationParameterSet, array $messages, $docAnchor + ) { + $retval = ''; + $retval .= '$cfg[\'Servers\'][$i][\'' . $parameter . '\'] ... '; + $retval .= ''; + if ($relationParameterSet) { + $retval .= $messages['ok']; + } else { + $retval .= sprintf( + $messages['error'], + Util::getDocuLink('config', 'cfg_Servers_' . $docAnchor) + ); + } + $retval .= '' . "\n"; + return $retval; + } + + /** + * Defines the relation parameters for the current user + * just a copy of the functions used for relations ;-) + * but added some stuff to check what will work + * + * @access protected + * @return array the relation parameters for the current user + */ + public function checkRelationsParam() + { + $cfgRelation = array(); + $cfgRelation['PMA_VERSION'] = PMA_VERSION; + + $workToTable = array( + 'relwork' => 'relation', + 'displaywork' => array('relation', 'table_info'), + 'bookmarkwork' => 'bookmarktable', + 'pdfwork' => array('table_coords', 'pdf_pages'), + 'commwork' => 'column_info', + 'mimework' => 'column_info', + 'historywork' => 'history', + 'recentwork' => 'recent', + 'favoritework' => 'favorite', + 'uiprefswork' => 'table_uiprefs', + 'trackingwork' => 'tracking', + 'userconfigwork' => 'userconfig', + 'menuswork' => array('users', 'usergroups'), + 'navwork' => 'navigationhiding', + 'savedsearcheswork' => 'savedsearches', + 'centralcolumnswork' => 'central_columns', + 'designersettingswork' => 'designer_settings', + 'exporttemplateswork' => 'export_templates', + ); + + foreach ($workToTable as $work => $table) { + $cfgRelation[$work] = false; + } + $cfgRelation['allworks'] = false; + $cfgRelation['user'] = null; + $cfgRelation['db'] = null; + + if ($GLOBALS['server'] == 0 + || empty($GLOBALS['cfg']['Server']['pmadb']) + || ! $GLOBALS['dbi']->selectDb( + $GLOBALS['cfg']['Server']['pmadb'], DatabaseInterface::CONNECT_CONTROL + ) + ) { + // No server selected -> no bookmark table + // we return the array with the falses in it, + // to avoid some 'Uninitialized string offset' errors later + $GLOBALS['cfg']['Server']['pmadb'] = false; + return $cfgRelation; + } + + $cfgRelation['user'] = $GLOBALS['cfg']['Server']['user']; + $cfgRelation['db'] = $GLOBALS['cfg']['Server']['pmadb']; + + // Now I just check if all tables that i need are present so I can for + // example enable relations but not pdf... + // I was thinking of checking if they have all required columns but I + // fear it might be too slow + + $tab_query = 'SHOW TABLES FROM ' + . Util::backquote( + $GLOBALS['cfg']['Server']['pmadb'] + ); + $tab_rs = $this->queryAsControlUser( + $tab_query, false, DatabaseInterface::QUERY_STORE + ); + + if (! $tab_rs) { + // query failed ... ? + //$GLOBALS['cfg']['Server']['pmadb'] = false; + return $cfgRelation; + } + + while ($curr_table = @$GLOBALS['dbi']->fetchRow($tab_rs)) { + if ($curr_table[0] == $GLOBALS['cfg']['Server']['bookmarktable']) { + $cfgRelation['bookmark'] = $curr_table[0]; + } elseif ($curr_table[0] == $GLOBALS['cfg']['Server']['relation']) { + $cfgRelation['relation'] = $curr_table[0]; + } elseif ($curr_table[0] == $GLOBALS['cfg']['Server']['table_info']) { + $cfgRelation['table_info'] = $curr_table[0]; + } elseif ($curr_table[0] == $GLOBALS['cfg']['Server']['table_coords']) { + $cfgRelation['table_coords'] = $curr_table[0]; + } elseif ($curr_table[0] == $GLOBALS['cfg']['Server']['column_info']) { + $cfgRelation['column_info'] = $curr_table[0]; + } elseif ($curr_table[0] == $GLOBALS['cfg']['Server']['pdf_pages']) { + $cfgRelation['pdf_pages'] = $curr_table[0]; + } elseif ($curr_table[0] == $GLOBALS['cfg']['Server']['history']) { + $cfgRelation['history'] = $curr_table[0]; + } elseif ($curr_table[0] == $GLOBALS['cfg']['Server']['recent']) { + $cfgRelation['recent'] = $curr_table[0]; + } elseif ($curr_table[0] == $GLOBALS['cfg']['Server']['favorite']) { + $cfgRelation['favorite'] = $curr_table[0]; + } elseif ($curr_table[0] == $GLOBALS['cfg']['Server']['table_uiprefs']) { + $cfgRelation['table_uiprefs'] = $curr_table[0]; + } elseif ($curr_table[0] == $GLOBALS['cfg']['Server']['tracking']) { + $cfgRelation['tracking'] = $curr_table[0]; + } elseif ($curr_table[0] == $GLOBALS['cfg']['Server']['userconfig']) { + $cfgRelation['userconfig'] = $curr_table[0]; + } elseif ($curr_table[0] == $GLOBALS['cfg']['Server']['users']) { + $cfgRelation['users'] = $curr_table[0]; + } elseif ($curr_table[0] == $GLOBALS['cfg']['Server']['usergroups']) { + $cfgRelation['usergroups'] = $curr_table[0]; + } elseif ($curr_table[0] == $GLOBALS['cfg']['Server']['navigationhiding']) { + $cfgRelation['navigationhiding'] = $curr_table[0]; + } elseif ($curr_table[0] == $GLOBALS['cfg']['Server']['savedsearches']) { + $cfgRelation['savedsearches'] = $curr_table[0]; + } elseif ($curr_table[0] == $GLOBALS['cfg']['Server']['central_columns']) { + $cfgRelation['central_columns'] = $curr_table[0]; + } elseif ($curr_table[0] == $GLOBALS['cfg']['Server']['designer_settings']) { + $cfgRelation['designer_settings'] = $curr_table[0]; + } elseif ($curr_table[0] == $GLOBALS['cfg']['Server']['export_templates']) { + $cfgRelation['export_templates'] = $curr_table[0]; + } + } // end while + $GLOBALS['dbi']->freeResult($tab_rs); + + if (isset($cfgRelation['relation'])) { + $cfgRelation['relwork'] = true; + } + + if (isset($cfgRelation['relation']) && isset($cfgRelation['table_info'])) { + $cfgRelation['displaywork'] = true; + } + + if (isset($cfgRelation['table_coords']) && isset($cfgRelation['pdf_pages'])) { + $cfgRelation['pdfwork'] = true; + } + + if (isset($cfgRelation['column_info'])) { + $cfgRelation['commwork'] = true; + // phpMyAdmin 4.3+ + // Check for input transformations upgrade. + $cfgRelation['mimework'] = $this->tryUpgradeTransformations(); + } + + if (isset($cfgRelation['history'])) { + $cfgRelation['historywork'] = true; + } + + if (isset($cfgRelation['recent'])) { + $cfgRelation['recentwork'] = true; + } + + if (isset($cfgRelation['favorite'])) { + $cfgRelation['favoritework'] = true; + } + + if (isset($cfgRelation['table_uiprefs'])) { + $cfgRelation['uiprefswork'] = true; + } + + if (isset($cfgRelation['tracking'])) { + $cfgRelation['trackingwork'] = true; + } + + if (isset($cfgRelation['userconfig'])) { + $cfgRelation['userconfigwork'] = true; + } + + if (isset($cfgRelation['bookmark'])) { + $cfgRelation['bookmarkwork'] = true; + } + + if (isset($cfgRelation['users']) && isset($cfgRelation['usergroups'])) { + $cfgRelation['menuswork'] = true; + } + + if (isset($cfgRelation['navigationhiding'])) { + $cfgRelation['navwork'] = true; + } + + if (isset($cfgRelation['savedsearches'])) { + $cfgRelation['savedsearcheswork'] = true; + } + + if (isset($cfgRelation['central_columns'])) { + $cfgRelation['centralcolumnswork'] = true; + } + + if (isset($cfgRelation['designer_settings'])) { + $cfgRelation['designersettingswork'] = true; + } + + if (isset($cfgRelation['export_templates'])) { + $cfgRelation['exporttemplateswork'] = true; + } + + $allWorks = true; + foreach ($workToTable as $work => $table) { + if (! $cfgRelation[$work]) { + if (is_string($table)) { + if (isset($GLOBALS['cfg']['Server'][$table]) + && $GLOBALS['cfg']['Server'][$table] !== false + ) { + $allWorks = false; + break; + } + } elseif (is_array($table)) { + $oneNull = false; + foreach ($table as $t) { + if (isset($GLOBALS['cfg']['Server'][$t]) + && $GLOBALS['cfg']['Server'][$t] === false + ) { + $oneNull = true; + break; + } + } + if (! $oneNull) { + $allWorks = false; + break; + } + } + } + } + $cfgRelation['allworks'] = $allWorks; + + return $cfgRelation; + } + + /** + * Check whether column_info table input transformation + * upgrade is required and try to upgrade silently + * + * @return bool false if upgrade failed + * + * @access public + */ + public function tryUpgradeTransformations() + { + // From 4.3, new input oriented transformation feature was introduced. + // Check whether column_info table has input transformation columns + $new_cols = array( + "input_transformation", + "input_transformation_options" + ); + $query = 'SHOW COLUMNS FROM ' + . Util::backquote($GLOBALS['cfg']['Server']['pmadb']) + . '.' . Util::backquote( + $GLOBALS['cfg']['Server']['column_info'] + ) + . ' WHERE Field IN (\'' . implode('\', \'', $new_cols) . '\')'; + $result = $this->queryAsControlUser( + $query, false, DatabaseInterface::QUERY_STORE + ); + if ($result) { + $rows = $GLOBALS['dbi']->numRows($result); + $GLOBALS['dbi']->freeResult($result); + // input transformations are present + // no need to upgrade + if ($rows === 2) { + return true; + // try silent upgrade without disturbing the user + } + + // read upgrade query file + $query = @file_get_contents(SQL_DIR . 'upgrade_column_info_4_3_0+.sql'); + // replace database name from query to with set in config.inc.php + $query = str_replace( + '`phpmyadmin`', + Util::backquote($GLOBALS['cfg']['Server']['pmadb']), + $query + ); + // replace pma__column_info table name from query + // to with set in config.inc.php + $query = str_replace( + '`pma__column_info`', + Util::backquote( + $GLOBALS['cfg']['Server']['column_info'] + ), + $query + ); + $GLOBALS['dbi']->tryMultiQuery($query, DatabaseInterface::CONNECT_CONTROL); + // skips result sets of query as we are not interested in it + while ($GLOBALS['dbi']->moreResults(DatabaseInterface::CONNECT_CONTROL) + && $GLOBALS['dbi']->nextResult(DatabaseInterface::CONNECT_CONTROL) + ) { + } + $error = $GLOBALS['dbi']->getError(DatabaseInterface::CONNECT_CONTROL); + // return true if no error exists otherwise false + return empty($error); + } + // some failure, either in upgrading or something else + // make some noise, time to wake up user. + return false; + } + + /** + * Gets all Relations to foreign tables for a given table or + * optionally a given column in a table + * + * @param string $db the name of the db to check for + * @param string $table the name of the table to check for + * @param string $column the name of the column to check for + * @param string $source the source for foreign key information + * + * @return array db,table,column + * + * @access public + */ + public function getForeigners($db, $table, $column = '', $source = 'both') + { + $cfgRelation = $this->getRelationsParam(); + $foreign = array(); + + if ($cfgRelation['relwork'] && ($source == 'both' || $source == 'internal')) { + $rel_query = ' + SELECT `master_field`, + `foreign_db`, + `foreign_table`, + `foreign_field` + FROM ' . Util::backquote($cfgRelation['db']) + . '.' . Util::backquote($cfgRelation['relation']) . ' + WHERE `master_db` = \'' . $GLOBALS['dbi']->escapeString($db) . '\' + AND `master_table` = \'' . $GLOBALS['dbi']->escapeString($table) + . '\' '; + if (strlen($column) > 0) { + $rel_query .= ' AND `master_field` = ' + . '\'' . $GLOBALS['dbi']->escapeString($column) . '\''; + } + $foreign = $GLOBALS['dbi']->fetchResult( + $rel_query, 'master_field', null, DatabaseInterface::CONNECT_CONTROL + ); + } + + if (($source == 'both' || $source == 'foreign') && strlen($table) > 0) { + $tableObj = new Table($table, $db); + $show_create_table = $tableObj->showCreate(); + if ($show_create_table) { + $parser = new Parser($show_create_table); + /** + * @var \PhpMyAdmin\SqlParser\Statements\CreateStatement $stmt + */ + $stmt = $parser->statements[0]; + $foreign['foreign_keys_data'] = TableUtils::getForeignKeys( + $stmt + ); + } + } + + /** + * Emulating relations for some information_schema tables + */ + $isInformationSchema = mb_strtolower($db) == 'information_schema'; + $isMysql = mb_strtolower($db) == 'mysql'; + if (($isInformationSchema || $isMysql) + && ($source == 'internal' || $source == 'both') + ) { + if ($isInformationSchema) { + $relations_key = 'information_schema_relations'; + include_once './libraries/information_schema_relations.inc.php'; + } else { + $relations_key = 'mysql_relations'; + include_once './libraries/mysql_relations.inc.php'; + } + if (isset($GLOBALS[$relations_key][$table])) { + foreach ($GLOBALS[$relations_key][$table] as $field => $relations) { + if ((strlen($column) === 0 || $column == $field) + && (! isset($foreign[$field]) + || strlen($foreign[$field]) === 0) + ) { + $foreign[$field] = $relations; + } + } + } + } + + return $foreign; + } + + /** + * Gets the display field of a table + * + * @param string $db the name of the db to check for + * @param string $table the name of the table to check for + * + * @return string field name + * + * @access public + */ + public function getDisplayField($db, $table) + { + $cfgRelation = $this->getRelationsParam(); + + /** + * Try to fetch the display field from DB. + */ + if ($cfgRelation['displaywork']) { + $disp_query = ' + SELECT `display_field` + FROM ' . Util::backquote($cfgRelation['db']) + . '.' . Util::backquote($cfgRelation['table_info']) . ' + WHERE `db_name` = \'' . $GLOBALS['dbi']->escapeString($db) . '\' + AND `table_name` = \'' . $GLOBALS['dbi']->escapeString($table) + . '\''; + + $row = $GLOBALS['dbi']->fetchSingleRow( + $disp_query, 'ASSOC', DatabaseInterface::CONNECT_CONTROL + ); + if (isset($row['display_field'])) { + return $row['display_field']; + } + } + + /** + * Emulating the display field for some information_schema tables. + */ + if ($db == 'information_schema') { + switch ($table) { + case 'CHARACTER_SETS': + return 'DESCRIPTION'; + case 'TABLES': + return 'TABLE_COMMENT'; + } + } + + /** + * Pick first char field + */ + $columns = $GLOBALS['dbi']->getColumnsFull($db, $table); + if ($columns) { + foreach ($columns as $column) { + if ($GLOBALS['dbi']->types->getTypeClass($column['DATA_TYPE']) == 'CHAR') { + return $column['COLUMN_NAME']; + } + } + } + return false; + } + + /** + * Gets the comments for all columns of a table or the db itself + * + * @param string $db the name of the db to check for + * @param string $table the name of the table to check for + * + * @return array [column_name] = comment + * + * @access public + */ + public function getComments($db, $table = '') + { + $comments = array(); + + if ($table != '') { + // MySQL native column comments + $columns = $GLOBALS['dbi']->getColumns($db, $table, null, true); + if ($columns) { + foreach ($columns as $column) { + if (! empty($column['Comment'])) { + $comments[$column['Field']] = $column['Comment']; + } + } + } + } else { + $comments[] = $this->getDbComment($db); + } + + return $comments; + } + + /** + * Gets the comment for a db + * + * @param string $db the name of the db to check for + * + * @return string comment + * + * @access public + */ + public function getDbComment($db) + { + $cfgRelation = $this->getRelationsParam(); + $comment = ''; + + if ($cfgRelation['commwork']) { + // pmadb internal db comment + $com_qry = " + SELECT `comment` + FROM " . Util::backquote($cfgRelation['db']) + . "." . Util::backquote($cfgRelation['column_info']) + . " + WHERE db_name = '" . $GLOBALS['dbi']->escapeString($db) . "' + AND table_name = '' + AND column_name = '(db_comment)'"; + $com_rs = $this->queryAsControlUser( + $com_qry, true, DatabaseInterface::QUERY_STORE + ); + + if ($com_rs && $GLOBALS['dbi']->numRows($com_rs) > 0) { + $row = $GLOBALS['dbi']->fetchAssoc($com_rs); + $comment = $row['comment']; + } + $GLOBALS['dbi']->freeResult($com_rs); + } + + return $comment; + } + + /** + * Gets the comment for a db + * + * @access public + * + * @return string comment + */ + public function getDbComments() + { + $cfgRelation = $this->getRelationsParam(); + $comments = array(); + + if ($cfgRelation['commwork']) { + // pmadb internal db comment + $com_qry = " + SELECT `db_name`, `comment` + FROM " . Util::backquote($cfgRelation['db']) + . "." . Util::backquote($cfgRelation['column_info']) + . " + WHERE `column_name` = '(db_comment)'"; + $com_rs = $this->queryAsControlUser( + $com_qry, true, DatabaseInterface::QUERY_STORE + ); + + if ($com_rs && $GLOBALS['dbi']->numRows($com_rs) > 0) { + while ($row = $GLOBALS['dbi']->fetchAssoc($com_rs)) { + $comments[$row['db_name']] = $row['comment']; + } + } + $GLOBALS['dbi']->freeResult($com_rs); + } + + return $comments; + } + + /** + * Set a database comment to a certain value. + * + * @param string $db the name of the db + * @param string $comment the value of the column + * + * @return boolean true, if comment-query was made. + * + * @access public + */ + public function setDbComment($db, $comment = '') + { + $cfgRelation = $this->getRelationsParam(); + + if (! $cfgRelation['commwork']) { + return false; + } + + if (strlen($comment) > 0) { + $upd_query = 'INSERT INTO ' + . Util::backquote($cfgRelation['db']) . '.' + . Util::backquote($cfgRelation['column_info']) + . ' (`db_name`, `table_name`, `column_name`, `comment`)' + . ' VALUES (\'' + . $GLOBALS['dbi']->escapeString($db) + . "', '', '(db_comment)', '" + . $GLOBALS['dbi']->escapeString($comment) + . "') " + . ' ON DUPLICATE KEY UPDATE ' + . "`comment` = '" . $GLOBALS['dbi']->escapeString($comment) . "'"; + } else { + $upd_query = 'DELETE FROM ' + . Util::backquote($cfgRelation['db']) . '.' + . Util::backquote($cfgRelation['column_info']) + . ' WHERE `db_name` = \'' . $GLOBALS['dbi']->escapeString($db) + . '\' + AND `table_name` = \'\' + AND `column_name` = \'(db_comment)\''; + } + + if (isset($upd_query)) { + return $this->queryAsControlUser($upd_query); + } + + return false; + } + + /** + * Set a SQL history entry + * + * @param string $db the name of the db + * @param string $table the name of the table + * @param string $username the username + * @param string $sqlquery the sql query + * + * @return void + * + * @access public + */ + public function setHistory($db, $table, $username, $sqlquery) + { + $maxCharactersInDisplayedSQL = $GLOBALS['cfg']['MaxCharactersInDisplayedSQL']; + // Prevent to run this automatically on Footer class destroying in testsuite + if (defined('TESTSUITE') + || mb_strlen($sqlquery) > $maxCharactersInDisplayedSQL + ) { + return; + } + + $cfgRelation = $this->getRelationsParam(); + + if (! isset($_SESSION['sql_history'])) { + $_SESSION['sql_history'] = array(); + } + + $_SESSION['sql_history'][] = array( + 'db' => $db, + 'table' => $table, + 'sqlquery' => $sqlquery, + ); + + if (count($_SESSION['sql_history']) > $GLOBALS['cfg']['QueryHistoryMax']) { + // history should not exceed a maximum count + array_shift($_SESSION['sql_history']); + } + + if (! $cfgRelation['historywork'] || ! $GLOBALS['cfg']['QueryHistoryDB']) { + return; + } + + $this->queryAsControlUser( + 'INSERT INTO ' + . Util::backquote($cfgRelation['db']) . '.' + . Util::backquote($cfgRelation['history']) . ' + (`username`, + `db`, + `table`, + `timevalue`, + `sqlquery`) + VALUES + (\'' . $GLOBALS['dbi']->escapeString($username) . '\', + \'' . $GLOBALS['dbi']->escapeString($db) . '\', + \'' . $GLOBALS['dbi']->escapeString($table) . '\', + NOW(), + \'' . $GLOBALS['dbi']->escapeString($sqlquery) . '\')' + ); + + $this->purgeHistory($username); + } + + /** + * Gets a SQL history entry + * + * @param string $username the username + * + * @return array list of history items + * + * @access public + */ + public function getHistory($username) + { + $cfgRelation = $this->getRelationsParam(); + + if (! $cfgRelation['historywork']) { + return false; + } + + /** + * if db-based history is disabled but there exists a session-based + * history, use it + */ + if (! $GLOBALS['cfg']['QueryHistoryDB']) { + if (isset($_SESSION['sql_history'])) { + return array_reverse($_SESSION['sql_history']); + } + return false; + } + + $hist_query = ' + SELECT `db`, + `table`, + `sqlquery`, + `timevalue` + FROM ' . Util::backquote($cfgRelation['db']) + . '.' . Util::backquote($cfgRelation['history']) . ' + WHERE `username` = \'' . $GLOBALS['dbi']->escapeString($username) . '\' + ORDER BY `id` DESC'; + + return $GLOBALS['dbi']->fetchResult( + $hist_query, null, null, DatabaseInterface::CONNECT_CONTROL + ); + } + + /** + * purges SQL history + * + * deletes entries that exceeds $cfg['QueryHistoryMax'], oldest first, for the + * given user + * + * @param string $username the username + * + * @return void + * + * @access public + */ + public function purgeHistory($username) + { + $cfgRelation = $this->getRelationsParam(); + if (! $GLOBALS['cfg']['QueryHistoryDB'] || ! $cfgRelation['historywork']) { + return; + } + + if (! $cfgRelation['historywork']) { + return; + } + + $search_query = ' + SELECT `timevalue` + FROM ' . Util::backquote($cfgRelation['db']) + . '.' . Util::backquote($cfgRelation['history']) . ' + WHERE `username` = \'' . $GLOBALS['dbi']->escapeString($username) . '\' + ORDER BY `timevalue` DESC + LIMIT ' . $GLOBALS['cfg']['QueryHistoryMax'] . ', 1'; + + if ($max_time = $GLOBALS['dbi']->fetchValue( + $search_query, 0, 0, DatabaseInterface::CONNECT_CONTROL + )) { + $this->queryAsControlUser( + 'DELETE FROM ' + . Util::backquote($cfgRelation['db']) . '.' + . Util::backquote($cfgRelation['history']) . ' + WHERE `username` = \'' . $GLOBALS['dbi']->escapeString($username) + . '\' + AND `timevalue` <= \'' . $max_time . '\'' + ); + } + } + + /** + * Prepares the dropdown for one mode + * + * @param array $foreign the keys and values for foreigns + * @param string $data the current data of the dropdown + * @param string $mode the needed mode + * + * @return array the '; + } elseif ($mode == 'id-content') { + $reloptions[] = $reloption . '>' + . htmlspecialchars($key) . ' - ' . $value . ''; + } elseif ($mode == 'id-only') { + $reloptions[] = $reloption . '>' + . htmlspecialchars($key) . ''; + } + } // end foreach + + return $reloptions; + } + + /** + * Outputs dropdown with values of foreign fields + * + * @param array $disp_row array of the displayed row + * @param string $foreign_field the foreign field + * @param string $foreign_display the foreign field to display + * @param string $data the current data of the dropdown (field in row) + * @param int $max maximum number of items in the dropdown + * + * @return string the '; + $top_count = count($top); + if ($max == -1 || $top_count < $max) { + $ret .= implode('', $top); + if ($foreign_display && $top_count > 0) { + // this empty option is to visually mark the beginning of the + // second series of values (bottom) + $ret .= ''; + } + } + if ($foreign_display) { + $ret .= implode('', $bottom); + } + + return $ret; + } + + /** + * Gets foreign keys in preparation for a drop-down selector + * + * @param array|boolean $foreigners array of the foreign keys + * @param string $field the foreign field name + * @param bool $override_total whether to override the total + * @param string $foreign_filter a possible filter + * @param string $foreign_limit a possible LIMIT clause + * @param bool $get_total optional, whether to get total num of rows + * in $foreignData['the_total;] + * (has an effect of performance) + * + * @return array data about the foreign keys + * + * @access public + */ + public function getForeignData( + $foreigners, $field, $override_total, + $foreign_filter, $foreign_limit, $get_total=false + ) { + // we always show the foreign field in the drop-down; if a display + // field is defined, we show it besides the foreign field + $foreign_link = false; + do { + if (! $foreigners) { + break; + } + $foreigner = $this->searchColumnInForeigners($foreigners, $field); + if ($foreigner != false) { + $foreign_db = $foreigner['foreign_db']; + $foreign_table = $foreigner['foreign_table']; + $foreign_field = $foreigner['foreign_field']; + } else { + break; + } + + // Count number of rows in the foreign table. Currently we do + // not use a drop-down if more than ForeignKeyMaxLimit rows in the + // foreign table, + // for speed reasons and because we need a better interface for this. + // + // We could also do the SELECT anyway, with a LIMIT, and ensure that + // the current value of the field is one of the choices. + + // Check if table has more rows than specified by + // $GLOBALS['cfg']['ForeignKeyMaxLimit'] + $moreThanLimit = $GLOBALS['dbi']->getTable($foreign_db, $foreign_table) + ->checkIfMinRecordsExist($GLOBALS['cfg']['ForeignKeyMaxLimit']); + + if ($override_total == true + || !$moreThanLimit + ) { + // foreign_display can be false if no display field defined: + $foreign_display = $this->getDisplayField($foreign_db, $foreign_table); + + $f_query_main = 'SELECT ' . Util::backquote($foreign_field) + . ( + ($foreign_display == false) + ? '' + : ', ' . Util::backquote($foreign_display) + ); + $f_query_from = ' FROM ' . Util::backquote($foreign_db) + . '.' . Util::backquote($foreign_table); + $f_query_filter = empty($foreign_filter) ? '' : ' WHERE ' + . Util::backquote($foreign_field) + . ' LIKE "%' . $GLOBALS['dbi']->escapeString($foreign_filter) . '%"' + . ( + ($foreign_display == false) + ? '' + : ' OR ' . Util::backquote($foreign_display) + . ' LIKE "%' . $GLOBALS['dbi']->escapeString($foreign_filter) + . '%"' + ); + $f_query_order = ($foreign_display == false) ? '' :' ORDER BY ' + . Util::backquote($foreign_table) . '.' + . Util::backquote($foreign_display); + + $f_query_limit = ! empty($foreign_limit) ? ($foreign_limit) : ''; + + if (!empty($foreign_filter)) { + $the_total = $GLOBALS['dbi']->fetchValue( + 'SELECT COUNT(*)' . $f_query_from . $f_query_filter + ); + if ($the_total === false) { + $the_total = 0; + } + } + + $disp = $GLOBALS['dbi']->tryQuery( + $f_query_main . $f_query_from . $f_query_filter + . $f_query_order . $f_query_limit + ); + if ($disp && $GLOBALS['dbi']->numRows($disp) > 0) { + // If a resultset has been created, pre-cache it in the $disp_row + // array. This helps us from not needing to use mysql_data_seek by + // accessing a pre-cached PHP array. Usually those resultsets are + // not that big, so a performance hit should not be expected. + $disp_row = array(); + while ($single_disp_row = @$GLOBALS['dbi']->fetchAssoc($disp)) { + $disp_row[] = $single_disp_row; + } + @$GLOBALS['dbi']->freeResult($disp); + } else { + // Either no data in the foreign table or + // user does not have select permission to foreign table/field + // Show an input field with a 'Browse foreign values' link + $disp_row = null; + $foreign_link = true; + } + } else { + $disp_row = null; + $foreign_link = true; + } + } while (false); + + if ($get_total) { + $the_total = $GLOBALS['dbi']->getTable($foreign_db, $foreign_table) + ->countRecords(true); + } + + $foreignData = array(); + $foreignData['foreign_link'] = $foreign_link; + $foreignData['the_total'] = isset($the_total) ? $the_total : null; + $foreignData['foreign_display'] = ( + isset($foreign_display) ? $foreign_display : null + ); + $foreignData['disp_row'] = isset($disp_row) ? $disp_row : null; + $foreignData['foreign_field'] = isset($foreign_field) ? $foreign_field : null; + + return $foreignData; + } + + /** + * Rename a field in relation tables + * + * usually called after a column in a table was renamed + * + * @param string $db database name + * @param string $table table name + * @param string $field old field name + * @param string $new_name new field name + * + * @return void + */ + public function renameField($db, $table, $field, $new_name) + { + $cfgRelation = $this->getRelationsParam(); + + if ($cfgRelation['displaywork']) { + $table_query = 'UPDATE ' + . Util::backquote($cfgRelation['db']) . '.' + . Util::backquote($cfgRelation['table_info']) + . ' SET display_field = \'' . $GLOBALS['dbi']->escapeString( + $new_name + ) . '\'' + . ' WHERE db_name = \'' . $GLOBALS['dbi']->escapeString($db) + . '\'' + . ' AND table_name = \'' . $GLOBALS['dbi']->escapeString($table) + . '\'' + . ' AND display_field = \'' . $GLOBALS['dbi']->escapeString($field) + . '\''; + $this->queryAsControlUser($table_query); + } + + if ($cfgRelation['relwork']) { + $table_query = 'UPDATE ' + . Util::backquote($cfgRelation['db']) . '.' + . Util::backquote($cfgRelation['relation']) + . ' SET master_field = \'' . $GLOBALS['dbi']->escapeString( + $new_name + ) . '\'' + . ' WHERE master_db = \'' . $GLOBALS['dbi']->escapeString($db) + . '\'' + . ' AND master_table = \'' . $GLOBALS['dbi']->escapeString($table) + . '\'' + . ' AND master_field = \'' . $GLOBALS['dbi']->escapeString($field) + . '\''; + $this->queryAsControlUser($table_query); + + $table_query = 'UPDATE ' + . Util::backquote($cfgRelation['db']) . '.' + . Util::backquote($cfgRelation['relation']) + . ' SET foreign_field = \'' . $GLOBALS['dbi']->escapeString( + $new_name + ) . '\'' + . ' WHERE foreign_db = \'' . $GLOBALS['dbi']->escapeString($db) + . '\'' + . ' AND foreign_table = \'' . $GLOBALS['dbi']->escapeString($table) + . '\'' + . ' AND foreign_field = \'' . $GLOBALS['dbi']->escapeString($field) + . '\''; + $this->queryAsControlUser($table_query); + } + } + + + /** + * Performs SQL query used for renaming table. + * + * @param string $table Relation table to use + * @param string $source_db Source database name + * @param string $target_db Target database name + * @param string $source_table Source table name + * @param string $target_table Target table name + * @param string $db_field Name of database field + * @param string $table_field Name of table field + * + * @return void + */ + public function renameSingleTable($table, + $source_db, $target_db, + $source_table, $target_table, + $db_field, $table_field + ) { + $query = 'UPDATE ' + . Util::backquote($GLOBALS['cfgRelation']['db']) . '.' + . Util::backquote($GLOBALS['cfgRelation'][$table]) + . ' SET ' + . $db_field . ' = \'' . $GLOBALS['dbi']->escapeString($target_db) + . '\', ' + . $table_field . ' = \'' . $GLOBALS['dbi']->escapeString($target_table) + . '\'' + . ' WHERE ' + . $db_field . ' = \'' . $GLOBALS['dbi']->escapeString($source_db) . '\'' + . ' AND ' + . $table_field . ' = \'' . $GLOBALS['dbi']->escapeString($source_table) + . '\''; + $this->queryAsControlUser($query); + } + + + /** + * Rename a table in relation tables + * + * usually called after table has been moved + * + * @param string $source_db Source database name + * @param string $target_db Target database name + * @param string $source_table Source table name + * @param string $target_table Target table name + * + * @return void + */ + public function renameTable($source_db, $target_db, $source_table, $target_table) + { + // Move old entries from PMA-DBs to new table + if ($GLOBALS['cfgRelation']['commwork']) { + $this->renameSingleTable( + 'column_info', + $source_db, $target_db, + $source_table, $target_table, + 'db_name', 'table_name' + ); + } + + // updating bookmarks is not possible since only a single table is + // moved, and not the whole DB. + + if ($GLOBALS['cfgRelation']['displaywork']) { + $this->renameSingleTable( + 'table_info', + $source_db, $target_db, + $source_table, $target_table, + 'db_name', 'table_name' + ); + } + + if ($GLOBALS['cfgRelation']['relwork']) { + $this->renameSingleTable( + 'relation', + $source_db, $target_db, + $source_table, $target_table, + 'foreign_db', 'foreign_table' + ); + + $this->renameSingleTable( + 'relation', + $source_db, $target_db, + $source_table, $target_table, + 'master_db', 'master_table' + ); + } + + if ($GLOBALS['cfgRelation']['pdfwork']) { + if ($source_db == $target_db) { + // rename within the database can be handled + $this->renameSingleTable( + 'table_coords', + $source_db, $target_db, + $source_table, $target_table, + 'db_name', 'table_name' + ); + } else { + // if the table is moved out of the database we can no loger keep the + // record for table coordinate + $remove_query = "DELETE FROM " + . Util::backquote($GLOBALS['cfgRelation']['db']) . "." + . Util::backquote($GLOBALS['cfgRelation']['table_coords']) + . " WHERE db_name = '" . $GLOBALS['dbi']->escapeString($source_db) . "'" + . " AND table_name = '" . $GLOBALS['dbi']->escapeString($source_table) + . "'"; + $this->queryAsControlUser($remove_query); + } + } + + if ($GLOBALS['cfgRelation']['uiprefswork']) { + $this->renameSingleTable( + 'table_uiprefs', + $source_db, $target_db, + $source_table, $target_table, + 'db_name', 'table_name' + ); + } + + if ($GLOBALS['cfgRelation']['navwork']) { + // update hidden items inside table + $this->renameSingleTable( + 'navigationhiding', + $source_db, $target_db, + $source_table, $target_table, + 'db_name', 'table_name' + ); + + // update data for hidden table + $query = "UPDATE " + . Util::backquote($GLOBALS['cfgRelation']['db']) . "." + . Util::backquote( + $GLOBALS['cfgRelation']['navigationhiding'] + ) + . " SET db_name = '" . $GLOBALS['dbi']->escapeString($target_db) + . "'," + . " item_name = '" . $GLOBALS['dbi']->escapeString($target_table) + . "'" + . " WHERE db_name = '" . $GLOBALS['dbi']->escapeString($source_db) + . "'" + . " AND item_name = '" . $GLOBALS['dbi']->escapeString($source_table) + . "'" + . " AND item_type = 'table'"; + $this->queryAsControlUser($query); + } + } + + /** + * Create a PDF page + * + * @param string $newpage name of the new PDF page + * @param array $cfgRelation Relation configuration + * @param string $db database name + * + * @return int $pdf_page_number + */ + public function createPage($newpage, array $cfgRelation, $db) + { + if (! isset($newpage) || $newpage == '') { + $newpage = __('no description'); + } + $ins_query = 'INSERT INTO ' + . Util::backquote($GLOBALS['cfgRelation']['db']) . '.' + . Util::backquote($cfgRelation['pdf_pages']) + . ' (db_name, page_descr)' + . ' VALUES (\'' + . $GLOBALS['dbi']->escapeString($db) . '\', \'' + . $GLOBALS['dbi']->escapeString($newpage) . '\')'; + $this->queryAsControlUser($ins_query, false); + + return $GLOBALS['dbi']->insertId(DatabaseInterface::CONNECT_CONTROL); + } + + /** + * Get child table references for a table column. + * This works only if 'DisableIS' is false. An empty array is returned otherwise. + * + * @param string $db name of master table db. + * @param string $table name of master table. + * @param string $column name of master table column. + * + * @return array $child_references + */ + public function getChildReferences($db, $table, $column = '') + { + $child_references = array(); + if (! $GLOBALS['cfg']['Server']['DisableIS']) { + $rel_query = "SELECT `column_name`, `table_name`," + . " `table_schema`, `referenced_column_name`" + . " FROM `information_schema`.`key_column_usage`" + . " WHERE `referenced_table_name` = '" + . $GLOBALS['dbi']->escapeString($table) . "'" + . " AND `referenced_table_schema` = '" + . $GLOBALS['dbi']->escapeString($db) . "'"; + if ($column) { + $rel_query .= " AND `referenced_column_name` = '" + . $GLOBALS['dbi']->escapeString($column) . "'"; + } + + $child_references = $GLOBALS['dbi']->fetchResult( + $rel_query, array('referenced_column_name', null) + ); + } + return $child_references; + } + + /** + * Check child table references and foreign key for a table column. + * + * @param string $db name of master table db. + * @param string $table name of master table. + * @param string $column name of master table column. + * @param array|null $foreigners_full foreiners array for the whole table. + * @param array|null $child_references_full child references for the whole table. + * + * @return array $column_status telling about references if foreign key. + */ + public function checkChildForeignReferences( + $db, $table, $column, $foreigners_full = null, $child_references_full = null + ) { + $column_status = array(); + $column_status['isEditable'] = false; + $column_status['isReferenced'] = false; + $column_status['isForeignKey'] = false; + $column_status['references'] = array(); + + $foreigners = array(); + if ($foreigners_full !== null) { + if (isset($foreigners_full[$column])) { + $foreigners[$column] = $foreigners_full[$column]; + } + if (isset($foreigners_full['foreign_keys_data'])) { + $foreigners['foreign_keys_data'] = $foreigners_full['foreign_keys_data']; + } + } else { + $foreigners = $this->getForeigners($db, $table, $column, 'foreign'); + } + $foreigner = $this->searchColumnInForeigners($foreigners, $column); + + $child_references = array(); + if ($child_references_full !== null) { + if (isset($child_references_full[$column])) { + $child_references = $child_references_full[$column]; + } + } else { + $child_references = $this->getChildReferences($db, $table, $column); + } + + if (sizeof($child_references, 0) > 0 + || $foreigner + ) { + if (sizeof($child_references, 0) > 0) { + $column_status['isReferenced'] = true; + foreach ($child_references as $columns) { + array_push( + $column_status['references'], + Util::backquote($columns['table_schema']) + . '.' . Util::backquote($columns['table_name']) + ); + } + } + + if ($foreigner) { + $column_status['isForeignKey'] = true; + } + } else { + $column_status['isEditable'] = true; + } + + return $column_status; + } + + /** + * Search a table column in foreign data. + * + * @param array $foreigners Table Foreign data + * @param string $column Column name + * + * @return bool|array + */ + public function searchColumnInForeigners(array $foreigners, $column) + { + if (isset($foreigners[$column])) { + return $foreigners[$column]; + } + + $foreigner = array(); + foreach ($foreigners['foreign_keys_data'] as $one_key) { + $column_index = array_search($column, $one_key['index_list']); + if ($column_index !== false) { + $foreigner['foreign_field'] + = $one_key['ref_index_list'][$column_index]; + $foreigner['foreign_db'] = isset($one_key['ref_db_name']) + ? $one_key['ref_db_name'] + : $GLOBALS['db']; + $foreigner['foreign_table'] = $one_key['ref_table_name']; + $foreigner['constraint'] = $one_key['constraint']; + $foreigner['on_update'] = isset($one_key['on_update']) + ? $one_key['on_update'] + : 'RESTRICT'; + $foreigner['on_delete'] = isset($one_key['on_delete']) + ? $one_key['on_delete'] + : 'RESTRICT'; + + return $foreigner; + } + } + + return false; + } + + /** + * Returns default PMA table names and their create queries. + * + * @return array table name, create query + */ + public function getDefaultPmaTableNames() + { + $pma_tables = array(); + $create_tables_file = file_get_contents( + SQL_DIR . 'create_tables.sql' + ); + + $queries = explode(';', $create_tables_file); + + foreach ($queries as $query) { + if (preg_match( + '/CREATE TABLE IF NOT EXISTS `(.*)` \(/', + $query, + $table + ) + ) { + $pma_tables[$table[1]] = $query . ';'; + } + } + + return $pma_tables; + } + + /** + * Create a table named phpmyadmin to be used as configuration storage + * + * @return bool + */ + public function createPmaDatabase() + { + $GLOBALS['dbi']->tryQuery("CREATE DATABASE IF NOT EXISTS `phpmyadmin`"); + if ($error = $GLOBALS['dbi']->getError()) { + if ($GLOBALS['errno'] == 1044) { + $GLOBALS['message'] = __( + 'You do not have necessary privileges to create a database named' + . ' \'phpmyadmin\'. You may go to \'Operations\' tab of any' + . ' database to set up the phpMyAdmin configuration storage there.' + ); + } else { + $GLOBALS['message'] = $error; + } + return false; + } + return true; + } + + /** + * Creates PMA tables in the given db, updates if already exists. + * + * @param string $db database + * @param boolean $create whether to create tables if they don't exist. + * + * @return void + */ + public function fixPmaTables($db, $create = true) + { + $tablesToFeatures = array( + 'pma__bookmark' => 'bookmarktable', + 'pma__relation' => 'relation', + 'pma__table_info' => 'table_info', + 'pma__table_coords' => 'table_coords', + 'pma__pdf_pages' => 'pdf_pages', + 'pma__column_info' => 'column_info', + 'pma__history' => 'history', + 'pma__recent' => 'recent', + 'pma__favorite' => 'favorite', + 'pma__table_uiprefs' => 'table_uiprefs', + 'pma__tracking' => 'tracking', + 'pma__userconfig' => 'userconfig', + 'pma__users' => 'users', + 'pma__usergroups' => 'usergroups', + 'pma__navigationhiding' => 'navigationhiding', + 'pma__savedsearches' => 'savedsearches', + 'pma__central_columns' => 'central_columns', + 'pma__designer_settings' => 'designer_settings', + 'pma__export_templates' => 'export_templates', + ); + + $existingTables = $GLOBALS['dbi']->getTables($db, DatabaseInterface::CONNECT_CONTROL); + + $createQueries = null; + $foundOne = false; + foreach ($tablesToFeatures as $table => $feature) { + if (! in_array($table, $existingTables)) { + if ($create) { + if ($createQueries == null) { // first create + $createQueries = $this->getDefaultPmaTableNames(); + $GLOBALS['dbi']->selectDb($db); + } + $GLOBALS['dbi']->tryQuery($createQueries[$table]); + if ($error = $GLOBALS['dbi']->getError()) { + $GLOBALS['message'] = $error; + return; + } + $foundOne = true; + $GLOBALS['cfg']['Server'][$feature] = $table; + } + } else { + $foundOne = true; + $GLOBALS['cfg']['Server'][$feature] = $table; + } + } + + if (! $foundOne) { + return; + } + $GLOBALS['cfg']['Server']['pmadb'] = $db; + $_SESSION['relation'][$GLOBALS['server']] = $this->checkRelationsParam(); + + $cfgRelation = $this->getRelationsParam(); + if ($cfgRelation['recentwork'] || $cfgRelation['favoritework']) { + // Since configuration storage is updated, we need to + // re-initialize the favorite and recent tables stored in the + // session from the current configuration storage. + if ($cfgRelation['favoritework']) { + $fav_tables = RecentFavoriteTable::getInstance('favorite'); + $_SESSION['tmpval']['favorite_tables'][$GLOBALS['server']] + = $fav_tables->getFromDb(); + } + + if ($cfgRelation['recentwork']) { + $recent_tables = RecentFavoriteTable::getInstance('recent'); + $_SESSION['tmpval']['recent_tables'][$GLOBALS['server']] + = $recent_tables->getFromDb(); + } + + // Reload navi panel to update the recent/favorite lists. + $GLOBALS['reload'] = true; + } + } + + /** + * Get Html for PMA tables fixing anchor. + * + * @param boolean $allTables whether to create all tables + * @param boolean $createDb whether to create the pmadb also + * + * @return string Html + */ + public function getHtmlFixPmaTables($allTables, $createDb = false) + { + $retval = ''; + + $url_query = Url::getCommon(array('db' => $GLOBALS['db']), ''); + if ($allTables) { + if ($createDb) { + $url_query .= '&goto=db_operations.php&create_pmadb=1'; + $message = Message::notice( + __( + '%sCreate%s a database named \'phpmyadmin\' and setup ' + . 'the phpMyAdmin configuration storage there.' + ) + ); + } else { + $url_query .= '&goto=db_operations.php&fixall_pmadb=1'; + $message = Message::notice( + __( + '%sCreate%s the phpMyAdmin configuration storage in the ' + . 'current database.' + ) + ); + } + } else { + $url_query .= '&goto=db_operations.php&fix_pmadb=1'; + $message = Message::notice( + __('%sCreate%s missing phpMyAdmin configuration storage tables.') + ); + } + $message->addParamHtml(''); + $message->addParamHtml(''); + + $retval .= $message->getDisplay(); + + return $retval; + } + + /** + * Gets the relations info and status, depending on the condition + * + * @param boolean $condition whether to look for foreigners or not + * @param string $db database name + * @param string $table table name + * + * @return array ($res_rel, $have_rel) + */ + public function getRelationsAndStatus($condition, $db, $table) + { + if ($condition) { + // Find which tables are related with the current one and write it in + // an array + $res_rel = $this->getForeigners($db, $table); + + if (count($res_rel) > 0) { + $have_rel = true; + } else { + $have_rel = false; + } + } else { + $have_rel = false; + $res_rel = array(); + } // end if + return(array($res_rel, $have_rel)); + } + + /** + * Verifies if all the pmadb tables are defined + * + * @return boolean + */ + public function arePmadbTablesDefined() + { + if (empty($GLOBALS['cfg']['Server']['bookmarktable']) + || empty($GLOBALS['cfg']['Server']['relation']) + || empty($GLOBALS['cfg']['Server']['table_info']) + || empty($GLOBALS['cfg']['Server']['table_coords']) + || empty($GLOBALS['cfg']['Server']['column_info']) + || empty($GLOBALS['cfg']['Server']['pdf_pages']) + || empty($GLOBALS['cfg']['Server']['history']) + || empty($GLOBALS['cfg']['Server']['recent']) + || empty($GLOBALS['cfg']['Server']['favorite']) + || empty($GLOBALS['cfg']['Server']['table_uiprefs']) + || empty($GLOBALS['cfg']['Server']['tracking']) + || empty($GLOBALS['cfg']['Server']['userconfig']) + || empty($GLOBALS['cfg']['Server']['users']) + || empty($GLOBALS['cfg']['Server']['usergroups']) + || empty($GLOBALS['cfg']['Server']['navigationhiding']) + || empty($GLOBALS['cfg']['Server']['savedsearches']) + || empty($GLOBALS['cfg']['Server']['central_columns']) + || empty($GLOBALS['cfg']['Server']['designer_settings']) + || empty($GLOBALS['cfg']['Server']['export_templates']) + ) { + return false; + } + + return true; + } + + /** + * Get tables for foreign key constraint + * + * @param string $foreignDb Database name + * @param string $tblStorageEngine Table storage engine + * + * @return array Table names + */ + public function getTables($foreignDb, $tblStorageEngine) + { + $tables = array(); + $tablesRows = $GLOBALS['dbi']->query( + 'SHOW TABLE STATUS FROM ' . Util::backquote($foreignDb), + DatabaseInterface::CONNECT_USER, + DatabaseInterface::QUERY_STORE + ); + while ($row = $GLOBALS['dbi']->fetchRow($tablesRows)) { + if (isset($row[1]) && mb_strtoupper($row[1]) == $tblStorageEngine) { + $tables[] = $row[0]; + } + } + if ($GLOBALS['cfg']['NaturalOrder']) { + usort($tables, 'strnatcasecmp'); + } + return $tables; + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/RelationCleanup.php b/php/apps/phpmyadmin49/html/libraries/classes/RelationCleanup.php new file mode 100644 index 00000000..0f2d0225 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/RelationCleanup.php @@ -0,0 +1,371 @@ +getRelationsParam(); + + if ($cfgRelation['commwork']) { + $remove_query = 'DELETE FROM ' + . Util::backquote($cfgRelation['db']) + . '.' . Util::backquote($cfgRelation['column_info']) + . ' WHERE db_name = \'' . $GLOBALS['dbi']->escapeString($db) . '\'' + . ' AND table_name = \'' . $GLOBALS['dbi']->escapeString($table) + . '\'' + . ' AND column_name = \'' . $GLOBALS['dbi']->escapeString($column) + . '\''; + $relation->queryAsControlUser($remove_query); + } + + if ($cfgRelation['displaywork']) { + $remove_query = 'DELETE FROM ' + . Util::backquote($cfgRelation['db']) + . '.' . Util::backquote($cfgRelation['table_info']) + . ' WHERE db_name = \'' . $GLOBALS['dbi']->escapeString($db) . '\'' + . ' AND table_name = \'' . $GLOBALS['dbi']->escapeString($table) + . '\'' + . ' AND display_field = \'' . $GLOBALS['dbi']->escapeString($column) + . '\''; + $relation->queryAsControlUser($remove_query); + } + + if ($cfgRelation['relwork']) { + $remove_query = 'DELETE FROM ' + . Util::backquote($cfgRelation['db']) + . '.' . Util::backquote($cfgRelation['relation']) + . ' WHERE master_db = \'' . $GLOBALS['dbi']->escapeString($db) + . '\'' + . ' AND master_table = \'' . $GLOBALS['dbi']->escapeString($table) + . '\'' + . ' AND master_field = \'' . $GLOBALS['dbi']->escapeString($column) + . '\''; + $relation->queryAsControlUser($remove_query); + + $remove_query = 'DELETE FROM ' + . Util::backquote($cfgRelation['db']) + . '.' . Util::backquote($cfgRelation['relation']) + . ' WHERE foreign_db = \'' . $GLOBALS['dbi']->escapeString($db) + . '\'' + . ' AND foreign_table = \'' . $GLOBALS['dbi']->escapeString($table) + . '\'' + . ' AND foreign_field = \'' . $GLOBALS['dbi']->escapeString($column) + . '\''; + $relation->queryAsControlUser($remove_query); + } + } + + /** + * Cleanup table related relation stuff + * + * @param string $db database name + * @param string $table table name + * + * @return void + */ + public static function table($db, $table) + { + $relation = new Relation(); + $cfgRelation = $relation->getRelationsParam(); + + if ($cfgRelation['commwork']) { + $remove_query = 'DELETE FROM ' + . Util::backquote($cfgRelation['db']) + . '.' . Util::backquote($cfgRelation['column_info']) + . ' WHERE db_name = \'' . $GLOBALS['dbi']->escapeString($db) . '\'' + . ' AND table_name = \'' . $GLOBALS['dbi']->escapeString($table) + . '\''; + $relation->queryAsControlUser($remove_query); + } + + if ($cfgRelation['displaywork']) { + $remove_query = 'DELETE FROM ' + . Util::backquote($cfgRelation['db']) + . '.' . Util::backquote($cfgRelation['table_info']) + . ' WHERE db_name = \'' . $GLOBALS['dbi']->escapeString($db) . '\'' + . ' AND table_name = \'' . $GLOBALS['dbi']->escapeString($table) + . '\''; + $relation->queryAsControlUser($remove_query); + } + + if ($cfgRelation['pdfwork']) { + $remove_query = 'DELETE FROM ' + . Util::backquote($cfgRelation['db']) + . '.' . Util::backquote($cfgRelation['table_coords']) + . ' WHERE db_name = \'' . $GLOBALS['dbi']->escapeString($db) . '\'' + . ' AND table_name = \'' . $GLOBALS['dbi']->escapeString($table) + . '\''; + $relation->queryAsControlUser($remove_query); + } + + if ($cfgRelation['relwork']) { + $remove_query = 'DELETE FROM ' + . Util::backquote($cfgRelation['db']) + . '.' . Util::backquote($cfgRelation['relation']) + . ' WHERE master_db = \'' . $GLOBALS['dbi']->escapeString($db) + . '\'' + . ' AND master_table = \'' . $GLOBALS['dbi']->escapeString($table) + . '\''; + $relation->queryAsControlUser($remove_query); + + $remove_query = 'DELETE FROM ' + . Util::backquote($cfgRelation['db']) + . '.' . Util::backquote($cfgRelation['relation']) + . ' WHERE foreign_db = \'' . $GLOBALS['dbi']->escapeString($db) + . '\'' + . ' AND foreign_table = \'' . $GLOBALS['dbi']->escapeString($table) + . '\''; + $relation->queryAsControlUser($remove_query); + } + + if ($cfgRelation['uiprefswork']) { + $remove_query = 'DELETE FROM ' + . Util::backquote($cfgRelation['db']) + . '.' . Util::backquote($cfgRelation['table_uiprefs']) + . ' WHERE db_name = \'' . $GLOBALS['dbi']->escapeString($db) . '\'' + . ' AND table_name = \'' . $GLOBALS['dbi']->escapeString($table) + . '\''; + $relation->queryAsControlUser($remove_query); + } + + if ($cfgRelation['navwork']) { + $remove_query = 'DELETE FROM ' + . Util::backquote($cfgRelation['db']) + . '.' . Util::backquote($cfgRelation['navigationhiding']) + . ' WHERE db_name = \'' . $GLOBALS['dbi']->escapeString($db) . '\'' + . ' AND (table_name = \'' . $GLOBALS['dbi']->escapeString($table) + . '\'' + . ' OR (item_name = \'' . $GLOBALS['dbi']->escapeString($table) + . '\'' + . ' AND item_type = \'table\'))'; + $relation->queryAsControlUser($remove_query); + } + } + + /** + * Cleanup database related relation stuff + * + * @param string $db database name + * + * @return void + */ + public static function database($db) + { + $relation = new Relation(); + $cfgRelation = $relation->getRelationsParam(); + + if ($cfgRelation['commwork']) { + $remove_query = 'DELETE FROM ' + . Util::backquote($cfgRelation['db']) + . '.' . Util::backquote($cfgRelation['column_info']) + . ' WHERE db_name = \'' . $GLOBALS['dbi']->escapeString($db) . '\''; + $relation->queryAsControlUser($remove_query); + } + + if ($cfgRelation['bookmarkwork']) { + $remove_query = 'DELETE FROM ' + . Util::backquote($cfgRelation['db']) + . '.' . Util::backquote($cfgRelation['bookmark']) + . ' WHERE dbase = \'' . $GLOBALS['dbi']->escapeString($db) . '\''; + $relation->queryAsControlUser($remove_query); + } + + if ($cfgRelation['displaywork']) { + $remove_query = 'DELETE FROM ' + . Util::backquote($cfgRelation['db']) + . '.' . Util::backquote($cfgRelation['table_info']) + . ' WHERE db_name = \'' . $GLOBALS['dbi']->escapeString($db) . '\''; + $relation->queryAsControlUser($remove_query); + } + + if ($cfgRelation['pdfwork']) { + $remove_query = 'DELETE FROM ' + . Util::backquote($cfgRelation['db']) + . '.' . Util::backquote($cfgRelation['pdf_pages']) + . ' WHERE db_name = \'' . $GLOBALS['dbi']->escapeString($db) . '\''; + $relation->queryAsControlUser($remove_query); + + $remove_query = 'DELETE FROM ' + . Util::backquote($cfgRelation['db']) + . '.' . Util::backquote($cfgRelation['table_coords']) + . ' WHERE db_name = \'' . $GLOBALS['dbi']->escapeString($db) . '\''; + $relation->queryAsControlUser($remove_query); + } + + if ($cfgRelation['relwork']) { + $remove_query = 'DELETE FROM ' + . Util::backquote($cfgRelation['db']) + . '.' . Util::backquote($cfgRelation['relation']) + . ' WHERE master_db = \'' + . $GLOBALS['dbi']->escapeString($db) . '\''; + $relation->queryAsControlUser($remove_query); + + $remove_query = 'DELETE FROM ' + . Util::backquote($cfgRelation['db']) + . '.' . Util::backquote($cfgRelation['relation']) + . ' WHERE foreign_db = \'' . $GLOBALS['dbi']->escapeString($db) + . '\''; + $relation->queryAsControlUser($remove_query); + } + + if ($cfgRelation['uiprefswork']) { + $remove_query = 'DELETE FROM ' + . Util::backquote($cfgRelation['db']) + . '.' . Util::backquote($cfgRelation['table_uiprefs']) + . ' WHERE db_name = \'' . $GLOBALS['dbi']->escapeString($db) . '\''; + $relation->queryAsControlUser($remove_query); + } + + if ($cfgRelation['navwork']) { + $remove_query = 'DELETE FROM ' + . Util::backquote($cfgRelation['db']) + . '.' . Util::backquote($cfgRelation['navigationhiding']) + . ' WHERE db_name = \'' . $GLOBALS['dbi']->escapeString($db) . '\''; + $relation->queryAsControlUser($remove_query); + } + + if ($cfgRelation['savedsearcheswork']) { + $remove_query = 'DELETE FROM ' + . Util::backquote($cfgRelation['db']) + . '.' . Util::backquote($cfgRelation['savedsearches']) + . ' WHERE db_name = \'' . $GLOBALS['dbi']->escapeString($db) . '\''; + $relation->queryAsControlUser($remove_query); + } + + if ($cfgRelation['centralcolumnswork']) { + $remove_query = 'DELETE FROM ' + . Util::backquote($cfgRelation['db']) + . '.' . Util::backquote($cfgRelation['central_columns']) + . ' WHERE db_name = \'' . $GLOBALS['dbi']->escapeString($db) . '\''; + $relation->queryAsControlUser($remove_query); + } + } + + /** + * Cleanup user related relation stuff + * + * @param string $username username + * + * @return void + */ + public static function user($username) + { + $relation = new Relation(); + $cfgRelation = $relation->getRelationsParam(); + + if ($cfgRelation['bookmarkwork']) { + $remove_query = "DELETE FROM " + . Util::backquote($cfgRelation['db']) + . "." . Util::backquote($cfgRelation['bookmark']) + . " WHERE `user` = '" . $GLOBALS['dbi']->escapeString($username) + . "'"; + $relation->queryAsControlUser($remove_query); + } + + if ($cfgRelation['historywork']) { + $remove_query = "DELETE FROM " + . Util::backquote($cfgRelation['db']) + . "." . Util::backquote($cfgRelation['history']) + . " WHERE `username` = '" . $GLOBALS['dbi']->escapeString($username) + . "'"; + $relation->queryAsControlUser($remove_query); + } + + if ($cfgRelation['recentwork']) { + $remove_query = "DELETE FROM " + . Util::backquote($cfgRelation['db']) + . "." . Util::backquote($cfgRelation['recent']) + . " WHERE `username` = '" . $GLOBALS['dbi']->escapeString($username) + . "'"; + $relation->queryAsControlUser($remove_query); + } + + if ($cfgRelation['favoritework']) { + $remove_query = "DELETE FROM " + . Util::backquote($cfgRelation['db']) + . "." . Util::backquote($cfgRelation['favorite']) + . " WHERE `username` = '" . $GLOBALS['dbi']->escapeString($username) + . "'"; + $relation->queryAsControlUser($remove_query); + } + + if ($cfgRelation['uiprefswork']) { + $remove_query = "DELETE FROM " + . Util::backquote($cfgRelation['db']) + . "." . Util::backquote($cfgRelation['table_uiprefs']) + . " WHERE `username` = '" . $GLOBALS['dbi']->escapeString($username) + . "'"; + $relation->queryAsControlUser($remove_query); + } + + if ($cfgRelation['userconfigwork']) { + $remove_query = "DELETE FROM " + . Util::backquote($cfgRelation['db']) + . "." . Util::backquote($cfgRelation['userconfig']) + . " WHERE `username` = '" . $GLOBALS['dbi']->escapeString($username) + . "'"; + $relation->queryAsControlUser($remove_query); + } + + if ($cfgRelation['menuswork']) { + $remove_query = "DELETE FROM " + . Util::backquote($cfgRelation['db']) + . "." . Util::backquote($cfgRelation['users']) + . " WHERE `username` = '" . $GLOBALS['dbi']->escapeString($username) + . "'"; + $relation->queryAsControlUser($remove_query); + } + + if ($cfgRelation['navwork']) { + $remove_query = "DELETE FROM " + . Util::backquote($cfgRelation['db']) + . "." . Util::backquote($cfgRelation['navigationhiding']) + . " WHERE `username` = '" . $GLOBALS['dbi']->escapeString($username) + . "'"; + $relation->queryAsControlUser($remove_query); + } + + if ($cfgRelation['savedsearcheswork']) { + $remove_query = "DELETE FROM " + . Util::backquote($cfgRelation['db']) + . "." . Util::backquote($cfgRelation['savedsearches']) + . " WHERE `username` = '" . $GLOBALS['dbi']->escapeString($username) + . "'"; + $relation->queryAsControlUser($remove_query); + } + + if ($cfgRelation['designersettingswork']) { + $remove_query = "DELETE FROM " + . Util::backquote($cfgRelation['db']) + . "." . Util::backquote($cfgRelation['designer_settings']) + . " WHERE `username` = '" . $GLOBALS['dbi']->escapeString($username) + . "'"; + $relation->queryAsControlUser($remove_query); + } + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Replication.php b/php/apps/phpmyadmin49/html/libraries/classes/Replication.php new file mode 100644 index 00000000..d53fada5 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Replication.php @@ -0,0 +1,173 @@ +tryQuery($action . " SLAVE " . $control . ";", $link); + } + + /** + * Changes master for replication slave + * + * @param string $user replication user on master + * @param string $password password for the user + * @param string $host master's hostname or IP + * @param int $port port, where mysql is running + * @param array $pos position of mysql replication, + * array should contain fields File and Position + * @param bool $stop shall we stop slave? + * @param bool $start shall we start slave? + * @param mixed $link mysql link + * + * @return string output of CHANGE MASTER mysql command + */ + public static function slaveChangeMaster($user, $password, $host, $port, + array $pos, $stop = true, $start = true, $link = null + ) { + if ($stop) { + self::slaveControl("STOP", null, $link); + } + + $out = $GLOBALS['dbi']->tryQuery( + 'CHANGE MASTER TO ' . + 'MASTER_HOST=\'' . $host . '\',' . + 'MASTER_PORT=' . ($port * 1) . ',' . + 'MASTER_USER=\'' . $user . '\',' . + 'MASTER_PASSWORD=\'' . $password . '\',' . + 'MASTER_LOG_FILE=\'' . $pos["File"] . '\',' . + 'MASTER_LOG_POS=' . $pos["Position"] . ';', $link + ); + + if ($start) { + self::slaveControl("START", null, $link); + } + + return $out; + } + + /** + * This function provides connection to remote mysql server + * + * @param string $user mysql username + * @param string $password password for the user + * @param string $host mysql server's hostname or IP + * @param int $port mysql remote port + * @param string $socket path to unix socket + * + * @return mixed $link mysql link on success + */ + public static function connectToMaster( + $user, $password, $host = null, $port = null, $socket = null + ) { + $server = array(); + $server['user'] = $user; + $server['password'] = $password; + $server["host"] = Core::sanitizeMySQLHost($host); + $server["port"] = $port; + $server["socket"] = $socket; + + // 5th parameter set to true means that it's an auxiliary connection + // and we must not go back to login page if it fails + return $GLOBALS['dbi']->connect(DatabaseInterface::CONNECT_AUXILIARY, $server); + } + + /** + * Fetches position and file of current binary log on master + * + * @param mixed $link mysql link + * + * @return array an array containing File and Position in MySQL replication + * on master server, useful for self::slaveChangeMaster + */ + public static function slaveBinLogMaster($link = null) + { + $data = $GLOBALS['dbi']->fetchResult('SHOW MASTER STATUS', null, null, $link); + $output = array(); + + if (! empty($data)) { + $output["File"] = $data[0]["File"]; + $output["Position"] = $data[0]["Position"]; + } + return $output; + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/ReplicationGui.php b/php/apps/phpmyadmin49/html/libraries/classes/ReplicationGui.php new file mode 100644 index 00000000..38155fbf --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/ReplicationGui.php @@ -0,0 +1,1089 @@ +getDisplay(); + $_SESSION['replication']['sr_action_status'] = 'unknown'; + } elseif ($_SESSION['replication']['sr_action_status'] == 'success') { + $success_message = $_SESSION['replication']['sr_action_info']; + $html .= Message::success($success_message)->getDisplay(); + $_SESSION['replication']['sr_action_status'] = 'unknown'; + } + } + return $html; + } + + /** + * returns HTML for master replication + * + * @return String HTML code + */ + public static function getHtmlForMasterReplication() + { + $html = ''; + if (! isset($_POST['repl_clear_scr'])) { + $html .= '
    '; + $html .= '' . __('Master replication') . ''; + $html .= __('This server is configured as master in a replication process.'); + $html .= '
      '; + $html .= '
    • '; + $html .= __('Show master status') . ''; + $html .= self::getHtmlForReplicationStatusTable('master', true, false); + $html .= '
    • '; + + $html .= '
    • '; + $html .= __('Show connected slaves') . ''; + $html .= self::getHtmlForReplicationSlavesTable(true); + $html .= '
    • '; + + $_url_params = $GLOBALS['url_params']; + $_url_params['mr_adduser'] = true; + $_url_params['repl_clear_scr'] = true; + + $html .= '
    • '; + $html .= __('Add slave replication user') . '
    • '; + } + + // Display 'Add replication slave user' form + if (isset($_POST['mr_adduser'])) { + $html .= self::getHtmlForReplicationMasterAddSlaveUser(); + } elseif (! isset($_POST['repl_clear_scr'])) { + $html .= "
    "; + $html .= "
    "; + } + + return $html; + } + + /** + * returns HTML for master replication configuration + * + * @return String HTML code + */ + public static function getHtmlForMasterConfiguration() + { + $html = '
    '; + $html .= '' . __('Master configuration') . ''; + $html .= __( + 'This server is not configured as a master server in a ' + . 'replication process. You can choose from either replicating ' + . 'all databases and ignoring some of them (useful if you want to ' + . 'replicate a majority of the databases) or you can choose to ignore ' + . 'all databases by default and allow only certain databases to be ' + . 'replicated. Please select the mode:' + ) . '

    '; + + $html .= ''; + $html .= '

    '; + $html .= __('Please select databases:') . '
    '; + $html .= self::getHtmlForReplicationDbMultibox(); + $html .= '

    '; + $html .= __( + 'Now, add the following lines at the end of [mysqld] section' + . ' in your my.cnf and please restart the MySQL server afterwards.' + ) . '
    '; + $html .= '
    ';
    +        $html .= __(
    +            'Once you restarted MySQL server, please click on Go button. '
    +            . 'Afterwards, you should see a message informing you, that this server'
    +            . ' is configured as master.'
    +        );
    +        $html .= '
    '; + $html .= '
    '; + $html .= '
    '; + $html .= Url::getHiddenInputs('', ''); + $html .= ' '; + $html .= '
    '; + $html .= '
    '; + + return $html; + } + + /** + * returns HTML for slave replication configuration + * + * @param bool $server_slave_status Whether it is Master or Slave + * @param array $server_slave_replication Slave replication + * + * @return String HTML code + */ + public static function getHtmlForSlaveConfiguration( + $server_slave_status, array $server_slave_replication + ) { + $html = '
    '; + $html .= '' . __('Slave replication') . ''; + /** + * check for multi-master replication functionality + */ + $server_slave_multi_replication = $GLOBALS['dbi']->fetchResult( + 'SHOW ALL SLAVES STATUS' + ); + if ($server_slave_multi_replication) { + $html .= __('Master connection:'); + $html .= '
    '; + $html .= Url::getHiddenInputs($GLOBALS['url_params']); + $html .= ' '; + $html .= ' '; + $html .= '
    '; + $html .= '

    '; + } + if ($server_slave_status) { + $html .= '
    '; + + $_url_params = $GLOBALS['url_params']; + $_url_params['sr_take_action'] = true; + $_url_params['sr_slave_server_control'] = true; + + if ($server_slave_replication[0]['Slave_IO_Running'] == 'No') { + $_url_params['sr_slave_action'] = 'start'; + } else { + $_url_params['sr_slave_action'] = 'stop'; + } + + $_url_params['sr_slave_control_parm'] = 'IO_THREAD'; + $slave_control_io_link = Url::getCommon($_url_params, ''); + + if ($server_slave_replication[0]['Slave_SQL_Running'] == 'No') { + $_url_params['sr_slave_action'] = 'start'; + } else { + $_url_params['sr_slave_action'] = 'stop'; + } + + $_url_params['sr_slave_control_parm'] = 'SQL_THREAD'; + $slave_control_sql_link = Url::getCommon($_url_params, ''); + + if ($server_slave_replication[0]['Slave_IO_Running'] == 'No' + || $server_slave_replication[0]['Slave_SQL_Running'] == 'No' + ) { + $_url_params['sr_slave_action'] = 'start'; + } else { + $_url_params['sr_slave_action'] = 'stop'; + } + + $_url_params['sr_slave_control_parm'] = null; + $slave_control_full_link = Url::getCommon($_url_params, ''); + + $_url_params['sr_slave_action'] = 'reset'; + $slave_control_reset_link = Url::getCommon($_url_params, ''); + + $_url_params = $GLOBALS['url_params']; + $_url_params['sr_take_action'] = true; + $_url_params['sr_slave_skip_error'] = true; + $slave_skip_error_link = Url::getCommon($_url_params, ''); + + if ($server_slave_replication[0]['Slave_SQL_Running'] == 'No') { + $html .= Message::error( + __('Slave SQL Thread not running!') + )->getDisplay(); + } + if ($server_slave_replication[0]['Slave_IO_Running'] == 'No') { + $html .= Message::error( + __('Slave IO Thread not running!') + )->getDisplay(); + } + + $_url_params = $GLOBALS['url_params']; + $_url_params['sl_configure'] = true; + $_url_params['repl_clear_scr'] = true; + + $reconfiguremaster_link = Url::getCommon($_url_params, ''); + + $html .= __( + 'Server is configured as slave in a replication process. Would you ' . + 'like to:' + ); + $html .= '
    '; + $html .= ''; + $html .= '
    '; + + } elseif (! isset($_POST['sl_configure'])) { + $_url_params = $GLOBALS['url_params']; + $_url_params['sl_configure'] = true; + $_url_params['repl_clear_scr'] = true; + + $html .= sprintf( + __( + 'This server is not configured as slave in a replication process. ' + . 'Would you like to %sconfigure%s it?' + ), + '', + '' + ); + } + $html .= '
    '; + + return $html; + } + + /** + * returns HTML for Slave Error Management + * + * @param String $slave_skip_error_link error link + * + * @return String HTML code + */ + public static function getHtmlForSlaveErrorManagement($slave_skip_error_link) + { + $html = ''; + $html .= __('Error management:') . ''; + $html .= '
    '; + $html .= Message::error( + __('Skipping errors might lead into unsynchronized master and slave!') + )->getDisplay(); + $html .= '
      '; + $html .= '
    • '; + $html .= __('Skip current error') . '
    • '; + $html .= '
    • '; + $html .= '
      '; + $html .= Url::getHiddenInputs('', ''); + $html .= sprintf( + __('Skip next %s errors.'), + '' + ); + $html .= ' '; + $html .= ' '; + $html .= '
    • '; + $html .= '
    '; + $html .= '
    '; + return $html; + } + + /** + * returns HTML for not configure for a server replication + * + * @return String HTML code + */ + public static function getHtmlForNotServerReplication() + { + $_url_params = $GLOBALS['url_params']; + $_url_params['mr_configure'] = true; + + $html = '
    '; + $html .= '' . __('Master replication') . ''; + $html .= sprintf( + __( + 'This server is not configured as master in a replication process. ' + . 'Would you like to %sconfigure%s it?' + ), + '', + '' + ); + $html .= '
    '; + return $html; + } + + /** + * returns HTML code for selecting databases + * + * @return String HTML code + */ + public static function getHtmlForReplicationDbMultibox() + { + $multi_values = ''; + $multi_values .= '
    '; + $multi_values .= '' . __('Select all') . ''; + $multi_values .= ' / '; + $multi_values .= '' . __('Unselect all') . ''; + + return $multi_values; + } + + /** + * returns HTML for changing master + * + * @param String $submitname - submit button name + * + * @return String HTML code + */ + public static function getHtmlForReplicationChangeMaster($submitname) + { + $html = ''; + list($username_length, $hostname_length) + = self::getUsernameHostnameLength(); + + $html .= '
    '; + $html .= Url::getHiddenInputs('', ''); + $html .= '
    '; + $html .= ' ' . __('Slave configuration'); + $html .= ' - ' . __('Change or reconfigure master server') . ''; + $html .= __( + 'Make sure you have a unique server-id in your configuration file (my.cnf). ' + . 'If not, please add the following line into [mysqld] section:' + ); + $html .= '
    '; + $html .= '
    server-id=' . time() . '
    '; + + $html .= self::getHtmlForAddUserInputDiv( + array('text'=>__('User name:'), 'for'=>"text_username"), + array( + 'type'=>'text', + 'name'=>'username', + 'id'=>'text_username', + 'maxlength'=>$username_length, + 'title'=>__('User name'), + 'required'=>'required' + ) + ); + + $html .= self::getHtmlForAddUserInputDiv( + array('text'=>__('Password:'), 'for'=>"text_pma_pw"), + array( + 'type'=>'password', + 'name'=>'pma_pw', + 'id'=>'text_pma_pw', + 'title'=>__('Password'), + 'required'=>'required' + ) + ); + + $html .= self::getHtmlForAddUserInputDiv( + array('text'=>__('Host:'), 'for'=>"text_hostname"), + array( + 'type'=>'text', + 'name'=>'hostname', + 'id'=>'text_hostname', + 'maxlength'=>$hostname_length, + 'value'=>'', + 'required'=>'required' + ) + ); + + $html .= self::getHtmlForAddUserInputDiv( + array('text'=>__('Port:'), 'for'=>"text_port"), + array( + 'type'=>'number', + 'name'=>'text_port', + 'id'=>'text_port', + 'maxlength'=>6, + 'value'=>'3306', + 'required'=>'required' + ) + ); + + $html .= '
    '; + $html .= ' '; + $html .= '
    '; + + return $html; + } + + /** + * returns HTML code for Add user input div + * + * @param array $label_array label tag elements + * @param array $input_array input tag elements + * + * @return String HTML code + */ + public static function getHtmlForAddUserInputDiv(array $label_array, array $input_array) + { + $html = '
    '; + $html .= ' '; + + $html .= ' $value) { + $html .= ' ' . $key . '="' . $value . '" '; + } + $html .= ' />'; + $html .= '
    '; + return $html; + } + + /** + * This function returns html code for table with replication status. + * + * @param string $type either master or slave + * @param boolean $hidden if true, then default style is set to hidden, + * default value false + * @param boolean $title if true, then title is displayed, default true + * + * @return String HTML code + */ + public static function getHtmlForReplicationStatusTable($type, $hidden = false, $title = true) + { + global ${"{$type}_variables"}; + global ${"{$type}_variables_alerts"}; + global ${"{$type}_variables_oks"}; + global ${"server_{$type}_replication"}; + global ${"strReplicationStatus_{$type}"}; + + $html = ''; + + // TODO check the Masters server id? + // seems to default to '1' when queried via SHOW VARIABLES , + // but resulted in error on the master when slave connects + // [ERROR] Error reading packet from server: Misconfigured master + // - server id was not set ( server_errno=1236) + // [ERROR] Got fatal error 1236: 'Misconfigured master + // - server id was not set' from master when reading data from binary log + // + //$server_id = $GLOBALS['dbi']->fetchValue( + // "SHOW VARIABLES LIKE 'server_id'", 0, 1 + //); + + $html .= '
    '; + + if ($title) { + if ($type == 'master') { + $html .= '

    '; + $html .= __('Master status') . '

    '; + } else { + $html .= '

    '; + $html .= __('Slave status') . '

    '; + } + } else { + $html .= '
    '; + } + + $html .= ' '; + $html .= ' '; + $html .= ' '; + $html .= ' '; + $html .= ' '; + $html .= ' '; + $html .= ' '; + $html .= ' '; + + foreach (${"{$type}_variables"} as $variable) { + $html .= ' '; + $html .= ' '; + $html .= ' '; + $html .= ' '; + } + + $html .= ' '; + $html .= '
    ' . __('Variable') . '' . __('Value') . '
    '; + $html .= htmlspecialchars($variable); + $html .= ' '; + + // TODO change to regexp or something, to allow for negative match + if (isset(${"{$type}_variables_alerts"}[$variable]) + && ${"{$type}_variables_alerts"}[$variable] == ${"server_{$type}_replication"}[0][$variable] + ) { + $html .= ''; + + } elseif (isset(${"{$type}_variables_oks"}[$variable]) + && ${"{$type}_variables_oks"}[$variable] == ${"server_{$type}_replication"}[0][$variable] + ) { + $html .= ''; + } else { + $html .= ''; + } + // allow wrapping long table lists into multiple lines + static $variables_wrap = array( + 'Replicate_Do_DB', 'Replicate_Ignore_DB', + 'Replicate_Do_Table', 'Replicate_Ignore_Table', + 'Replicate_Wild_Do_Table', 'Replicate_Wild_Ignore_Table'); + if (in_array($variable, $variables_wrap)) { + $html .= htmlspecialchars(str_replace( + ',', + ', ', + ${"server_{$type}_replication"}[0][$variable] + )); + } else { + $html .= htmlspecialchars(${"server_{$type}_replication"}[0][$variable]); + } + $html .= ''; + + $html .= '
    '; + $html .= '
    '; + $html .= '
    '; + + return $html; + } + + /** + * returns html code for table with slave users connected to this master + * + * @param boolean $hidden - if true, then default style is set to hidden, + * - default value false + * + * @return string + */ + public static function getHtmlForReplicationSlavesTable($hidden = false) + { + $html = ''; + // Fetch data + $data = $GLOBALS['dbi']->fetchResult('SHOW SLAVE HOSTS', null, null); + + $html .= '
    '; + $html .= '
    '; + $html .= ' '; + $html .= ' '; + $html .= ' '; + $html .= ' '; + $html .= ' '; + $html .= ' '; + $html .= ' '; + $html .= ' '; + + foreach ($data as $slave) { + $html .= ' '; + $html .= ' '; + $html .= ' '; + $html .= ' '; + } + + $html .= ' '; + $html .= '
    ' . __('Server ID') . '' . __('Host') . '
    ' . $slave['Server_id'] . '' . $slave['Host'] . '
    '; + $html .= '
    '; + $html .= Message::notice( + __( + 'Only slaves started with the ' + . '--report-host=host_name option are visible in this list.' + ) + )->getDisplay(); + $html .= '
    '; + $html .= '
    '; + + return $html; + } + + /** + * get the correct username and hostname lengths for this MySQL server + * + * @return array username length, hostname length + */ + public static function getUsernameHostnameLength() + { + $fields_info = $GLOBALS['dbi']->getColumns('mysql', 'user'); + $username_length = 16; + $hostname_length = 41; + foreach ($fields_info as $val) { + if ($val['Field'] == 'User') { + strtok($val['Type'], '()'); + $v = strtok('()'); + if (is_int($v)) { + $username_length = $v; + } + } elseif ($val['Field'] == 'Host') { + strtok($val['Type'], '()'); + $v = strtok('()'); + if (is_int($v)) { + $hostname_length = $v; + } + } + } + return array($username_length, $hostname_length); + } + + /** + * returns html code to add a replication slave user to the master + * + * @return String HTML code + */ + public static function getHtmlForReplicationMasterAddSlaveUser() + { + $html = ''; + list($username_length, $hostname_length) + = self::getUsernameHostnameLength(); + + if (isset($_POST['username']) && strlen($_POST['username']) === 0) { + $GLOBALS['pred_username'] = 'any'; + } + $html .= '
    '; + $html .= '
    ' . __('Add slave replication user') . '' + . self::getHtmlForAddUserLoginForm($username_length) + . '
    ' + . '' + . '' + . ' ' + . '' + . '' + . ' ' + . '' + . '
    ' + . '' + . '' + . ' ' + . '' + . '' + . '
    '; + + return $html; + } + + /** + * returns HTML for TableInfoForm + * + * @param int $hostname_length Selected hostname length + * + * @return String HTML code + */ + public static function getHtmlForTableInfoForm($hostname_length) + { + $html = ' ' + . ' ' + . ' ' + . '
    ' + . '' + . Util::showHint( + __( + 'When Host table is used, this field is ignored ' + . 'and values stored in Host table are used instead.' + ) + ) + . '
    ' + . '
    ' + . '' + . '' + . ' ' + . '' + . '' + . '
    ' + . '
    ' + . '' + . ' ' + . '' + . '
    ' + . '
    ' + . '' + . '' + . ' ' + . '' + . '' + . '
    ' + . ''; + $html .= ''; + return $html; + } + + /** + * handle control requests + * + * @return NULL + */ + public static function handleControlRequest() + { + if (isset($_POST['sr_take_action'])) { + $refresh = false; + $result = false; + $messageSuccess = null; + $messageError = null; + + if (isset($_POST['slave_changemaster']) && ! $GLOBALS['cfg']['AllowArbitraryServer']) { + $_SESSION['replication']['sr_action_status'] = 'error'; + $_SESSION['replication']['sr_action_info'] = __('Connection to server is disabled, please enable $cfg[\'AllowArbitraryServer\'] in phpMyAdmin configuration.'); + } elseif (isset($_POST['slave_changemaster'])) { + $result = self::handleRequestForSlaveChangeMaster(); + } elseif (isset($_POST['sr_slave_server_control'])) { + $result = self::handleRequestForSlaveServerControl(); + $refresh = true; + + switch ($_POST['sr_slave_action']) { + case 'start': + $messageSuccess = __('Replication started successfully.'); + $messageError = __('Error starting replication.'); + break; + case 'stop': + $messageSuccess = __('Replication stopped successfully.'); + $messageError = __('Error stopping replication.'); + break; + case 'reset': + $messageSuccess = __('Replication resetting successfully.'); + $messageError = __('Error resetting replication.'); + break; + default: + $messageSuccess = __('Success.'); + $messageError = __('Error.'); + break; + } + } elseif (isset($_POST['sr_slave_skip_error'])) { + $result = self::handleRequestForSlaveSkipError(); + } + + if ($refresh) { + $response = Response::getInstance(); + if ($response->isAjax()) { + $response->setRequestStatus($result); + $response->addJSON( + 'message', + $result + ? Message::success($messageSuccess) + : Message::error($messageError) + ); + } else { + Core::sendHeaderLocation( + './server_replication.php' + . Url::getCommonRaw($GLOBALS['url_params']) + ); + } + } + unset($refresh); + } + } + + /** + * handle control requests for Slave Change Master + * + * @return boolean + */ + public static function handleRequestForSlaveChangeMaster() + { + $sr = array(); + $_SESSION['replication']['m_username'] = $sr['username'] + = $GLOBALS['dbi']->escapeString($_POST['username']); + $_SESSION['replication']['m_password'] = $sr['pma_pw'] + = $GLOBALS['dbi']->escapeString($_POST['pma_pw']); + $_SESSION['replication']['m_hostname'] = $sr['hostname'] + = $GLOBALS['dbi']->escapeString($_POST['hostname']); + $_SESSION['replication']['m_port'] = $sr['port'] + = $GLOBALS['dbi']->escapeString($_POST['text_port']); + $_SESSION['replication']['m_correct'] = ''; + $_SESSION['replication']['sr_action_status'] = 'error'; + $_SESSION['replication']['sr_action_info'] = __('Unknown error'); + + // Attempt to connect to the new master server + $link_to_master = Replication::connectToMaster( + $sr['username'], $sr['pma_pw'], $sr['hostname'], $sr['port'] + ); + + if (! $link_to_master) { + $_SESSION['replication']['sr_action_status'] = 'error'; + $_SESSION['replication']['sr_action_info'] = sprintf( + __('Unable to connect to master %s.'), + htmlspecialchars($sr['hostname']) + ); + } else { + // Read the current master position + $position = Replication::slaveBinLogMaster($link_to_master); + + if (empty($position)) { + $_SESSION['replication']['sr_action_status'] = 'error'; + $_SESSION['replication']['sr_action_info'] + = __( + 'Unable to read master log position. ' + . 'Possible privilege problem on master.' + ); + } else { + $_SESSION['replication']['m_correct'] = true; + + if (! Replication::slaveChangeMaster( + $sr['username'], + $sr['pma_pw'], + $sr['hostname'], + $sr['port'], + $position, + true, + false + ) + ) { + $_SESSION['replication']['sr_action_status'] = 'error'; + $_SESSION['replication']['sr_action_info'] + = __('Unable to change master!'); + } else { + $_SESSION['replication']['sr_action_status'] = 'success'; + $_SESSION['replication']['sr_action_info'] = sprintf( + __('Master server changed successfully to %s.'), + htmlspecialchars($sr['hostname']) + ); + } + } + } + + return $_SESSION['replication']['sr_action_status'] === 'success'; + } + + /** + * handle control requests for Slave Server Control + * + * @return boolean + */ + public static function handleRequestForSlaveServerControl() + { + if (empty($_POST['sr_slave_control_parm'])) { + $_POST['sr_slave_control_parm'] = null; + } + if ($_POST['sr_slave_action'] == 'reset') { + $qStop = Replication::slaveControl("STOP"); + $qReset = $GLOBALS['dbi']->tryQuery("RESET SLAVE;"); + $qStart = Replication::slaveControl("START"); + + $result = ($qStop !== false && $qStop !== -1 && + $qReset !== false && $qReset !== -1 && + $qStart !== false && $qStart !== -1); + } else { + $qControl = Replication::slaveControl( + $_POST['sr_slave_action'], + $_POST['sr_slave_control_parm'] + ); + + $result = ($qControl !== false && $qControl !== -1); + } + + return $result; + } + + /** + * handle control requests for Slave Skip Error + * + * @return boolean + */ + public static function handleRequestForSlaveSkipError() + { + $count = 1; + if (isset($_POST['sr_skip_errors_count'])) { + $count = $_POST['sr_skip_errors_count'] * 1; + } + + $qStop = Replication::slaveControl("STOP"); + $qSkip = $GLOBALS['dbi']->tryQuery( + "SET GLOBAL SQL_SLAVE_SKIP_COUNTER = " . $count . ";" + ); + $qStart = Replication::slaveControl("START"); + + $result = ($qStop !== false && $qStop !== -1 && + $qSkip !== false && $qSkip !== -1 && + $qStart !== false && $qStart !== -1); + + return $result; + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Response.php b/php/apps/phpmyadmin49/html/libraries/classes/Response.php new file mode 100644 index 00000000..4594e464 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Response.php @@ -0,0 +1,572 @@ +start(); + register_shutdown_function(array($this, 'response')); + } + $this->_header = new Header(); + $this->_HTML = ''; + $this->_JSON = array(); + $this->_footer = new Footer(); + + $this->_isSuccess = true; + $this->_isDisabled = false; + $this->setAjax(! empty($_REQUEST['ajax_request'])); + $this->_CWD = getcwd(); + } + + /** + * Set the ajax flag to indicate whether + * we are servicing an ajax request + * + * @param bool $isAjax Whether we are servicing an ajax request + * + * @return void + */ + public function setAjax($isAjax) + { + $this->_isAjax = (boolean) $isAjax; + $this->_header->setAjax($this->_isAjax); + $this->_footer->setAjax($this->_isAjax); + } + + /** + * Returns the singleton Response object + * + * @return Response object + */ + public static function getInstance() + { + if (empty(self::$_instance)) { + self::$_instance = new Response(); + } + return self::$_instance; + } + + /** + * Set the status of an ajax response, + * whether it is a success or an error + * + * @param bool $state Whether the request was successfully processed + * + * @return void + */ + public function setRequestStatus($state) + { + $this->_isSuccess = ($state == true); + } + + /** + * Returns true or false depending on whether + * we are servicing an ajax request + * + * @return bool + */ + public function isAjax() + { + return $this->_isAjax; + } + + /** + * Returns the path to the current working directory + * Necessary to work around a PHP bug where the CWD is + * reset after the initial script exits + * + * @return string + */ + public function getCWD() + { + return $this->_CWD; + } + + /** + * Disables the rendering of the header + * and the footer in responses + * + * @return void + */ + public function disable() + { + $this->_header->disable(); + $this->_footer->disable(); + $this->_isDisabled = true; + } + + /** + * Returns a PhpMyAdmin\Header object + * + * @return Header + */ + public function getHeader() + { + return $this->_header; + } + + /** + * Returns a PhpMyAdmin\Footer object + * + * @return Footer + */ + public function getFooter() + { + return $this->_footer; + } + + /** + * Add HTML code to the response + * + * @param string $content A string to be appended to + * the current output buffer + * + * @return void + */ + public function addHTML($content) + { + if (is_array($content)) { + foreach ($content as $msg) { + $this->addHTML($msg); + } + } elseif ($content instanceof Message) { + $this->_HTML .= $content->getDisplay(); + } else { + $this->_HTML .= $content; + } + } + + /** + * Add JSON code to the response + * + * @param mixed $json Either a key (string) or an + * array or key-value pairs + * @param mixed $value Null, if passing an array in $json otherwise + * it's a string value to the key + * + * @return void + */ + public function addJSON($json, $value = null) + { + if (is_array($json)) { + foreach ($json as $key => $value) { + $this->addJSON($key, $value); + } + } else { + if ($value instanceof Message) { + $this->_JSON[$json] = $value->getDisplay(); + } else { + $this->_JSON[$json] = $value; + } + } + + } + + /** + * Renders the HTML response text + * + * @return string + */ + private function _getDisplay() + { + // The header may contain nothing at all, + // if its content was already rendered + // and, in this case, the header will be + // in the content part of the request + $retval = $this->_header->getDisplay(); + $retval .= $this->_HTML; + $retval .= $this->_footer->getDisplay(); + return $retval; + } + + /** + * Sends an HTML response to the browser + * + * @return void + */ + private function _htmlResponse() + { + echo $this->_getDisplay(); + } + + /** + * Sends a JSON response to the browser + * + * @return void + */ + private function _ajaxResponse() + { + /* Avoid wrapping in case we're disabled */ + if ($this->_isDisabled) { + echo $this->_getDisplay(); + return; + } + + if (! isset($this->_JSON['message'])) { + $this->_JSON['message'] = $this->_getDisplay(); + } elseif ($this->_JSON['message'] instanceof Message) { + $this->_JSON['message'] = $this->_JSON['message']->getDisplay(); + } + + if ($this->_isSuccess) { + $this->_JSON['success'] = true; + } else { + $this->_JSON['success'] = false; + $this->_JSON['error'] = $this->_JSON['message']; + unset($this->_JSON['message']); + } + + if ($this->_isSuccess) { + $this->addJSON('_title', $this->getHeader()->getTitleTag()); + + if (isset($GLOBALS['dbi'])) { + $menuHash = $this->getHeader()->getMenu()->getHash(); + $this->addJSON('_menuHash', $menuHash); + $hashes = array(); + if (isset($_REQUEST['menuHashes'])) { + $hashes = explode('-', $_REQUEST['menuHashes']); + } + if (! in_array($menuHash, $hashes)) { + $this->addJSON( + '_menu', + $this->getHeader() + ->getMenu() + ->getDisplay() + ); + } + } + + $this->addJSON('_scripts', $this->getHeader()->getScripts()->getFiles()); + $this->addJSON('_selflink', $this->getFooter()->getSelfUrl()); + $this->addJSON('_displayMessage', $this->getHeader()->getMessage()); + + $debug = $this->_footer->getDebugMessage(); + if (empty($_REQUEST['no_debug']) + && strlen($debug) > 0 + ) { + $this->addJSON('_debug', $debug); + } + + $errors = $this->_footer->getErrorMessages(); + if (strlen($errors) > 0) { + $this->addJSON('_errors', $errors); + } + $promptPhpErrors = $GLOBALS['error_handler']->hasErrorsForPrompt(); + $this->addJSON('_promptPhpErrors', $promptPhpErrors); + + if (empty($GLOBALS['error_message'])) { + // set current db, table and sql query in the querywindow + // (this is for the bottom console) + $query = ''; + $maxChars = $GLOBALS['cfg']['MaxCharactersInDisplayedSQL']; + if (isset($GLOBALS['sql_query']) + && mb_strlen($GLOBALS['sql_query']) < $maxChars + ) { + $query = $GLOBALS['sql_query']; + } + $this->addJSON( + '_reloadQuerywindow', + array( + 'db' => Core::ifSetOr($GLOBALS['db'], ''), + 'table' => Core::ifSetOr($GLOBALS['table'], ''), + 'sql_query' => $query + ) + ); + if (! empty($GLOBALS['focus_querywindow'])) { + $this->addJSON('_focusQuerywindow', $query); + } + if (! empty($GLOBALS['reload'])) { + $this->addJSON('_reloadNavigation', 1); + } + $this->addJSON('_params', $this->getHeader()->getJsParams()); + } + } + + // Set the Content-Type header to JSON so that jQuery parses the + // response correctly. + Core::headerJSON(); + + $result = json_encode($this->_JSON); + if ($result === false) { + switch (json_last_error()) { + case JSON_ERROR_NONE: + $error = 'No errors'; + break; + case JSON_ERROR_DEPTH: + $error = 'Maximum stack depth exceeded'; + break; + case JSON_ERROR_STATE_MISMATCH: + $error = 'Underflow or the modes mismatch'; + break; + case JSON_ERROR_CTRL_CHAR: + $error = 'Unexpected control character found'; + break; + case JSON_ERROR_SYNTAX: + $error = 'Syntax error, malformed JSON'; + break; + case JSON_ERROR_UTF8: + $error = 'Malformed UTF-8 characters, possibly incorrectly encoded'; + break; + case JSON_ERROR_RECURSION: + $error = 'One or more recursive references in the value to be encoded'; + break; + case JSON_ERROR_INF_OR_NAN: + $error = 'One or more NAN or INF values in the value to be encoded'; + break; + case JSON_ERROR_UNSUPPORTED_TYPE: + $error = 'A value of a type that cannot be encoded was given'; + default: + $error = 'Unknown error'; + break; + } + echo json_encode( + array( + 'success' => false, + 'error' => 'JSON encoding failed: ' . $error, + ) + ); + } else { + echo $result; + } + } + + /** + * Sends an HTML response to the browser + * + * @return void + */ + public function response() + { + chdir($this->getCWD()); + $buffer = OutputBuffering::getInstance(); + if (empty($this->_HTML)) { + $this->_HTML = $buffer->getContents(); + } + if ($this->isAjax()) { + $this->_ajaxResponse(); + } else { + $this->_htmlResponse(); + } + $buffer->flush(); + exit; + } + + /** + * Wrapper around PHP's header() function. + * + * @param string $text header string + * + * @return void + */ + public function header($text) + { + header($text); + } + + /** + * Wrapper around PHP's headers_sent() function. + * + * @return bool + */ + public function headersSent() + { + return headers_sent(); + } + + /** + * Wrapper around PHP's http_response_code() function. + * + * @param int $response_code will set the response code. + * + * @return void + */ + public function httpResponseCode($response_code) + { + http_response_code($response_code); + } + + /** + * Sets http response code. + * + * @param int $response_code will set the response code. + * + * @return void + */ + public function setHttpResponseCode($response_code) + { + $this->httpResponseCode($response_code); + switch ($response_code) { + case 100: $httpStatusMsg = ' Continue'; break; + case 101: $httpStatusMsg = ' Switching Protocols'; break; + case 200: $httpStatusMsg = ' OK'; break; + case 201: $httpStatusMsg = ' Created'; break; + case 202: $httpStatusMsg = ' Accepted'; break; + case 203: $httpStatusMsg = ' Non-Authoritative Information'; break; + case 204: $httpStatusMsg = ' No Content'; break; + case 205: $httpStatusMsg = ' Reset Content'; break; + case 206: $httpStatusMsg = ' Partial Content'; break; + case 300: $httpStatusMsg = ' Multiple Choices'; break; + case 301: $httpStatusMsg = ' Moved Permanently'; break; + case 302: $httpStatusMsg = ' Moved Temporarily'; break; + case 303: $httpStatusMsg = ' See Other'; break; + case 304: $httpStatusMsg = ' Not Modified'; break; + case 305: $httpStatusMsg = ' Use Proxy'; break; + case 400: $httpStatusMsg = ' Bad Request'; break; + case 401: $httpStatusMsg = ' Unauthorized'; break; + case 402: $httpStatusMsg = ' Payment Required'; break; + case 403: $httpStatusMsg = ' Forbidden'; break; + case 404: $httpStatusMsg = ' Not Found'; break; + case 405: $httpStatusMsg = ' Method Not Allowed'; break; + case 406: $httpStatusMsg = ' Not Acceptable'; break; + case 407: $httpStatusMsg = ' Proxy Authentication Required'; break; + case 408: $httpStatusMsg = ' Request Time-out'; break; + case 409: $httpStatusMsg = ' Conflict'; break; + case 410: $httpStatusMsg = ' Gone'; break; + case 411: $httpStatusMsg = ' Length Required'; break; + case 412: $httpStatusMsg = ' Precondition Failed'; break; + case 413: $httpStatusMsg = ' Request Entity Too Large'; break; + case 414: $httpStatusMsg = ' Request-URI Too Large'; break; + case 415: $httpStatusMsg = ' Unsupported Media Type'; break; + case 500: $httpStatusMsg = ' Internal Server Error'; break; + case 501: $httpStatusMsg = ' Not Implemented'; break; + case 502: $httpStatusMsg = ' Bad Gateway'; break; + case 503: $httpStatusMsg = ' Service Unavailable'; break; + case 504: $httpStatusMsg = ' Gateway Time-out'; break; + case 505: $httpStatusMsg = ' HTTP Version not supported'; break; + default: $httpStatusMsg = ' Web server is down'; break; + } + if (php_sapi_name() !== 'cgi-fcgi') { + $this->header('status: ' . $response_code . $httpStatusMsg); + } + } + + /** + * Generate header for 303 + * + * @param string $location will set location to redirect. + * + * @return void + */ + public function generateHeader303($location) + { + $this->setHttpResponseCode(303); + $this->header('Location: '.$location); + if (!defined('TESTSUITE')) { + exit; + } + } + + /** + * Configures response for the login page + * + * @return bool Whether caller should exit + */ + public function loginPage() + { + /* Handle AJAX redirection */ + if ($this->isAjax()) { + $this->setRequestStatus(false); + // redirect_flag redirects to the login page + $this->addJSON('redirect_flag', '1'); + return true; + } + + $this->getFooter()->setMinimal(); + $header = $this->getHeader(); + $header->setBodyId('loginform'); + $header->setTitle('phpMyAdmin'); + $header->disableMenuAndConsole(); + $header->disableWarnings(); + return false; + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Rte/Events.php b/php/apps/phpmyadmin49/html/libraries/classes/Rte/Events.php new file mode 100644 index 00000000..0306b1d3 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Rte/Events.php @@ -0,0 +1,629 @@ + array('ENABLE', + 'DISABLE', + 'DISABLE ON SLAVE'), + 'display' => array('ENABLED', + 'DISABLED', + 'SLAVESIDE_DISABLED') + ); + $event_type = array('RECURRING', + 'ONE TIME'); + $event_interval = array('YEAR', + 'QUARTER', + 'MONTH', + 'DAY', + 'HOUR', + 'MINUTE', + 'WEEK', + 'SECOND', + 'YEAR_MONTH', + 'DAY_HOUR', + 'DAY_MINUTE', + 'DAY_SECOND', + 'HOUR_MINUTE', + 'HOUR_SECOND', + 'MINUTE_SECOND'); + } + + /** + * Main function for the events functionality + * + * @return void + */ + public static function main() + { + global $db; + + self::setGlobals(); + /** + * Process all requests + */ + self::handleEditor(); + Export::events(); + /** + * Display a list of available events + */ + $items = $GLOBALS['dbi']->getEvents($db); + echo RteList::get('event', $items); + /** + * Display a link for adding a new event, if + * the user has the privileges and a link to + * toggle the state of the event scheduler. + */ + echo Footer::events(); + } // end self::main() + + /** + * Handles editor requests for adding or editing an item + * + * @return void + */ + public static function handleEditor() + { + global $_REQUEST, $_POST, $errors, $db; + + if (! empty($_POST['editor_process_add']) + || ! empty($_POST['editor_process_edit']) + ) { + $sql_query = ''; + + $item_query = self::getQueryFromRequest(); + + if (! count($errors)) { // set by PhpMyAdmin\Rte\Routines::getQueryFromRequest() + // Execute the created query + if (! empty($_POST['editor_process_edit'])) { + // Backup the old trigger, in case something goes wrong + $create_item = $GLOBALS['dbi']->getDefinition( + $db, + 'EVENT', + $_POST['item_original_name'] + ); + $drop_item = "DROP EVENT " + . Util::backquote($_POST['item_original_name']) + . ";\n"; + $result = $GLOBALS['dbi']->tryQuery($drop_item); + if (! $result) { + $errors[] = sprintf( + __('The following query has failed: "%s"'), + htmlspecialchars($drop_item) + ) + . '
    ' + . __('MySQL said: ') . $GLOBALS['dbi']->getError(); + } else { + $result = $GLOBALS['dbi']->tryQuery($item_query); + if (! $result) { + $errors[] = sprintf( + __('The following query has failed: "%s"'), + htmlspecialchars($item_query) + ) + . '
    ' + . __('MySQL said: ') . $GLOBALS['dbi']->getError(); + // We dropped the old item, but were unable to create + // the new one. Try to restore the backup query + $result = $GLOBALS['dbi']->tryQuery($create_item); + $errors = General::checkResult( + $result, + __( + 'Sorry, we failed to restore the dropped event.' + ), + $create_item, + $errors + ); + } else { + $message = Message::success( + __('Event %1$s has been modified.') + ); + $message->addParam( + Util::backquote($_POST['item_name']) + ); + $sql_query = $drop_item . $item_query; + } + } + } else { + // 'Add a new item' mode + $result = $GLOBALS['dbi']->tryQuery($item_query); + if (! $result) { + $errors[] = sprintf( + __('The following query has failed: "%s"'), + htmlspecialchars($item_query) + ) + . '

    ' + . __('MySQL said: ') . $GLOBALS['dbi']->getError(); + } else { + $message = Message::success( + __('Event %1$s has been created.') + ); + $message->addParam( + Util::backquote($_POST['item_name']) + ); + $sql_query = $item_query; + } + } + } + + if (count($errors)) { + $message = Message::error( + '' + . __( + 'One or more errors have occurred while processing your request:' + ) + . '' + ); + $message->addHtml('
      '); + foreach ($errors as $string) { + $message->addHtml('
    • ' . $string . '
    • '); + } + $message->addHtml('
    '); + } + + $output = Util::getMessage($message, $sql_query); + $response = Response::getInstance(); + if ($response->isAjax()) { + if ($message->isSuccess()) { + $events = $GLOBALS['dbi']->getEvents($db, $_POST['item_name']); + $event = $events[0]; + $response->addJSON( + 'name', + htmlspecialchars( + mb_strtoupper($_POST['item_name']) + ) + ); + if (! empty($event)) { + $response->addJSON('new_row', RteList::getEventRow($event)); + } + $response->addJSON('insert', ! empty($event)); + $response->addJSON('message', $output); + } else { + $response->setRequestStatus(false); + $response->addJSON('message', $message); + } + exit; + } + } + /** + * Display a form used to add/edit a trigger, if necessary + */ + if (count($errors) + || (empty($_POST['editor_process_add']) + && empty($_POST['editor_process_edit']) + && (! empty($_REQUEST['add_item']) + || ! empty($_REQUEST['edit_item']) + || ! empty($_POST['item_changetype']))) + ) { // FIXME: this must be simpler than that + $operation = ''; + if (! empty($_POST['item_changetype'])) { + $operation = 'change'; + } + // Get the data for the form (if any) + if (! empty($_REQUEST['add_item'])) { + $title = Words::get('add'); + $item = self::getDataFromRequest(); + $mode = 'add'; + } elseif (! empty($_REQUEST['edit_item'])) { + $title = __("Edit event"); + if (! empty($_REQUEST['item_name']) + && empty($_POST['editor_process_edit']) + && empty($_POST['item_changetype']) + ) { + $item = self::getDataFromName($_REQUEST['item_name']); + if ($item !== false) { + $item['item_original_name'] = $item['item_name']; + } + } else { + $item = self::getDataFromRequest(); + } + $mode = 'edit'; + } + General::sendEditor('EVN', $mode, $item, $title, $db, $operation); + } + } // end self::handleEditor() + + /** + * This function will generate the values that are required to for the editor + * + * @return array Data necessary to create the editor. + */ + public static function getDataFromRequest() + { + $retval = array(); + $indices = array('item_name', + 'item_original_name', + 'item_status', + 'item_execute_at', + 'item_interval_value', + 'item_interval_field', + 'item_starts', + 'item_ends', + 'item_definition', + 'item_preserve', + 'item_comment', + 'item_definer'); + foreach ($indices as $index) { + $retval[$index] = isset($_POST[$index]) ? $_POST[$index] : ''; + } + $retval['item_type'] = 'ONE TIME'; + $retval['item_type_toggle'] = 'RECURRING'; + if (isset($_POST['item_type']) && $_POST['item_type'] == 'RECURRING') { + $retval['item_type'] = 'RECURRING'; + $retval['item_type_toggle'] = 'ONE TIME'; + } + return $retval; + } // end self::getDataFromRequest() + + /** + * This function will generate the values that are required to complete + * the "Edit event" form given the name of a event. + * + * @param string $name The name of the event. + * + * @return array Data necessary to create the editor. + */ + public static function getDataFromName($name) + { + global $db; + + $retval = array(); + $columns = "`EVENT_NAME`, `STATUS`, `EVENT_TYPE`, `EXECUTE_AT`, " + . "`INTERVAL_VALUE`, `INTERVAL_FIELD`, `STARTS`, `ENDS`, " + . "`EVENT_DEFINITION`, `ON_COMPLETION`, `DEFINER`, `EVENT_COMMENT`"; + $where = "EVENT_SCHEMA " . Util::getCollateForIS() . "=" + . "'" . $GLOBALS['dbi']->escapeString($db) . "' " + . "AND EVENT_NAME='" . $GLOBALS['dbi']->escapeString($name) . "'"; + $query = "SELECT $columns FROM `INFORMATION_SCHEMA`.`EVENTS` WHERE $where;"; + $item = $GLOBALS['dbi']->fetchSingleRow($query); + if (! $item) { + return false; + } + $retval['item_name'] = $item['EVENT_NAME']; + $retval['item_status'] = $item['STATUS']; + $retval['item_type'] = $item['EVENT_TYPE']; + if ($retval['item_type'] == 'RECURRING') { + $retval['item_type_toggle'] = 'ONE TIME'; + } else { + $retval['item_type_toggle'] = 'RECURRING'; + } + $retval['item_execute_at'] = $item['EXECUTE_AT']; + $retval['item_interval_value'] = $item['INTERVAL_VALUE']; + $retval['item_interval_field'] = $item['INTERVAL_FIELD']; + $retval['item_starts'] = $item['STARTS']; + $retval['item_ends'] = $item['ENDS']; + $retval['item_preserve'] = ''; + if ($item['ON_COMPLETION'] == 'PRESERVE') { + $retval['item_preserve'] = " checked='checked'"; + } + $retval['item_definition'] = $item['EVENT_DEFINITION']; + $retval['item_definer'] = $item['DEFINER']; + $retval['item_comment'] = $item['EVENT_COMMENT']; + + return $retval; + } // end self::getDataFromName() + + /** + * Displays a form used to add/edit an event + * + * @param string $mode If the editor will be used to edit an event + * or add a new one: 'edit' or 'add'. + * @param string $operation If the editor was previously invoked with + * JS turned off, this will hold the name of + * the current operation + * @param array $item Data for the event returned by + * self::getDataFromRequest() or + * self::getDataFromName() + * + * @return string HTML code for the editor. + */ + public static function getEditorForm($mode, $operation, array $item) + { + global $db, $table, $event_status, $event_type, $event_interval; + + $modeToUpper = mb_strtoupper($mode); + + $response = Response::getInstance(); + + // Escape special characters + $need_escape = array( + 'item_original_name', + 'item_name', + 'item_type', + 'item_execute_at', + 'item_interval_value', + 'item_starts', + 'item_ends', + 'item_definition', + 'item_definer', + 'item_comment' + ); + foreach ($need_escape as $index) { + $item[$index] = htmlentities($item[$index], ENT_QUOTES); + } + $original_data = ''; + if ($mode == 'edit') { + $original_data = "\n"; + } + // Handle some logic first + if ($operation == 'change') { + if ($item['item_type'] == 'RECURRING') { + $item['item_type'] = 'ONE TIME'; + $item['item_type_toggle'] = 'RECURRING'; + } else { + $item['item_type'] = 'RECURRING'; + $item['item_type_toggle'] = 'ONE TIME'; + } + } + if ($item['item_type'] == 'ONE TIME') { + $isrecurring_class = ' hide'; + $isonetime_class = ''; + } else { + $isrecurring_class = ''; + $isonetime_class = ' hide'; + } + // Create the output + $retval = ""; + $retval .= "\n\n"; + $retval .= "\n"; + $retval .= "\n"; + $retval .= $original_data; + $retval .= Url::getHiddenInputs($db, $table) . "\n"; + $retval .= "
    \n"; + $retval .= "" . __('Details') . "\n"; + $retval .= "\n"; + $retval .= "\n"; + $retval .= " \n"; + $retval .= " \n"; + $retval .= "\n"; + + $retval .= "\n"; + $retval .= " \n"; + $retval .= " \n"; + $retval .= "\n"; + $retval .= "\n"; + $retval .= " \n"; + $retval .= " \n"; + $retval .= "\n"; + $retval .= "\n"; + $retval .= " \n"; + $retval .= " \n"; + $retval .= " \n"; + $retval .= " \n"; + $retval .= " \n"; + $retval .= " \n"; + $retval .= " \n"; + $retval .= " \n"; + $retval .= " \n"; + $retval .= " \n"; + $retval .= " \n"; + $retval .= " \n"; + $retval .= " \n"; + + return $retval; + } // end self::getParameterRow() + + /** + * Displays a form used to add/edit a routine + * + * @param string $mode If the editor will be used to edit a routine + * or add a new one: 'edit' or 'add'. + * @param string $operation If the editor was previously invoked with + * JS turned off, this will hold the name of + * the current operation + * @param array $routine Data for the routine returned by + * self::getDataFromRequest() or + * self::getDataFromName() + * + * @return string HTML code for the editor. + */ + public static function getEditorForm($mode, $operation, array $routine) + { + global $db, $errors, $param_sqldataaccess, $param_opts_num; + + $response = Response::getInstance(); + + // Escape special characters + $need_escape = array( + 'item_original_name', + 'item_name', + 'item_returnlength', + 'item_definition', + 'item_definer', + 'item_comment' + ); + foreach ($need_escape as $key => $index) { + $routine[$index] = htmlentities($routine[$index], ENT_QUOTES, 'UTF-8'); + } + for ($i = 0; $i < $routine['item_num_params']; $i++) { + $routine['item_param_name'][$i] = htmlentities( + $routine['item_param_name'][$i], + ENT_QUOTES + ); + $routine['item_param_length'][$i] = htmlentities( + $routine['item_param_length'][$i], + ENT_QUOTES + ); + } + + // Handle some logic first + if ($operation == 'change') { + if ($routine['item_type'] == 'PROCEDURE') { + $routine['item_type'] = 'FUNCTION'; + $routine['item_type_toggle'] = 'PROCEDURE'; + } else { + $routine['item_type'] = 'PROCEDURE'; + $routine['item_type_toggle'] = 'FUNCTION'; + } + } elseif ($operation == 'add' + || ($routine['item_num_params'] == 0 && $mode == 'add' && ! $errors) + ) { + $routine['item_param_dir'][] = ''; + $routine['item_param_name'][] = ''; + $routine['item_param_type'][] = ''; + $routine['item_param_length'][] = ''; + $routine['item_param_opts_num'][] = ''; + $routine['item_param_opts_text'][] = ''; + $routine['item_num_params']++; + } elseif ($operation == 'remove') { + unset($routine['item_param_dir'][$routine['item_num_params'] - 1]); + unset($routine['item_param_name'][$routine['item_num_params'] - 1]); + unset($routine['item_param_type'][$routine['item_num_params'] - 1]); + unset($routine['item_param_length'][$routine['item_num_params'] - 1]); + unset($routine['item_param_opts_num'][$routine['item_num_params'] - 1]); + unset($routine['item_param_opts_text'][$routine['item_num_params'] - 1]); + $routine['item_num_params']--; + } + $disableRemoveParam = ''; + if (! $routine['item_num_params']) { + $disableRemoveParam = " color: gray;' disabled='disabled"; + } + $original_routine = ''; + if ($mode == 'edit') { + $original_routine = "\n" + . "\n"; + } + $isfunction_class = ''; + $isprocedure_class = ''; + $isfunction_select = ''; + $isprocedure_select = ''; + if ($routine['item_type'] == 'PROCEDURE') { + $isfunction_class = ' hide'; + $isprocedure_select = " selected='selected'"; + } else { + $isprocedure_class = ' hide'; + $isfunction_select = " selected='selected'"; + } + + // Create the output + $retval = ""; + $retval .= "\n\n"; + $retval .= "\n"; + $retval .= "\n"; + $retval .= $original_routine; + $retval .= Url::getHiddenInputs($db) . "\n"; + $retval .= "
    \n"; + $retval .= "" . __('Details') . "\n"; + $retval .= "
    " . __('Event name') . "\n"; + $retval .= " \n"; + $retval .= " \n"; + $retval .= "
    " . __('Event type') . "\n"; + if ($response->isAjax()) { + $retval .= " \n"; + } else { + $retval .= " \n"; + $retval .= " {$item['item_type']}\n"; + $retval .= " \n"; + $retval .= " \n"; + $retval .= " \n"; + $retval .= " \n"; + $retval .= " \n"; + $retval .= " $value) { + $selected = ""; + if (! empty($item['item_interval_field']) + && $item['item_interval_field'] == $value + ) { + $selected = " selected='selected'"; + } + $retval .= "$value"; + } + $retval .= " \n"; + $retval .= "
    " . _pgettext('Start of recurring event', 'Start'); + $retval .= " \n"; + $retval .= " \n"; + $retval .= " \n"; + $retval .= " \n"; + $retval .= "
    " . __('On completion preserve') . "\n"; + $retval .= " \n"; + $retval .= " isAjax()) { + $retval .= "\n"; + $retval .= "\n"; + } + $retval .= "\n\n"; + $retval .= "\n\n"; + + return $retval; + } // end self::getEditorForm() + + /** + * Composes the query necessary to create an event from an HTTP request. + * + * @return string The CREATE EVENT query. + */ + public static function getQueryFromRequest() + { + global $_REQUEST, $errors, $event_status, $event_type, $event_interval; + + $query = 'CREATE '; + if (! empty($_POST['item_definer'])) { + if (mb_strpos($_POST['item_definer'], '@') !== false + ) { + $arr = explode('@', $_POST['item_definer']); + $query .= 'DEFINER=' . Util::backquote($arr[0]); + $query .= '@' . Util::backquote($arr[1]) . ' '; + } else { + $errors[] = __('The definer must be in the "username@hostname" format!'); + } + } + $query .= 'EVENT '; + if (! empty($_POST['item_name'])) { + $query .= Util::backquote($_POST['item_name']) . ' '; + } else { + $errors[] = __('You must provide an event name!'); + } + $query .= 'ON SCHEDULE '; + if (! empty($_POST['item_type']) + && in_array($_POST['item_type'], $event_type) + ) { + if ($_POST['item_type'] == 'RECURRING') { + if (! empty($_POST['item_interval_value']) + && !empty($_POST['item_interval_field']) + && in_array($_POST['item_interval_field'], $event_interval) + ) { + $query .= 'EVERY ' . intval($_POST['item_interval_value']) . ' '; + $query .= $_POST['item_interval_field'] . ' '; + } else { + $errors[] + = __('You must provide a valid interval value for the event.'); + } + if (! empty($_POST['item_starts'])) { + $query .= "STARTS '" + . $GLOBALS['dbi']->escapeString($_POST['item_starts']) + . "' "; + } + if (! empty($_POST['item_ends'])) { + $query .= "ENDS '" + . $GLOBALS['dbi']->escapeString($_POST['item_ends']) + . "' "; + } + } else { + if (! empty($_POST['item_execute_at'])) { + $query .= "AT '" + . $GLOBALS['dbi']->escapeString($_POST['item_execute_at']) + . "' "; + } else { + $errors[] + = __('You must provide a valid execution time for the event.'); + } + } + } else { + $errors[] = __('You must provide a valid type for the event.'); + } + $query .= 'ON COMPLETION '; + if (empty($_POST['item_preserve'])) { + $query .= 'NOT '; + } + $query .= 'PRESERVE '; + if (! empty($_POST['item_status'])) { + foreach ($event_status['display'] as $key => $value) { + if ($value == $_POST['item_status']) { + $query .= $event_status['query'][$key] . ' '; + break; + } + } + } + if (! empty($_POST['item_comment'])) { + $query .= "COMMENT '" . $GLOBALS['dbi']->escapeString( + $_POST['item_comment'] + ) . "' "; + } + $query .= 'DO '; + if (! empty($_POST['item_definition'])) { + $query .= $_POST['item_definition']; + } else { + $errors[] = __('You must provide an event definition.'); + } + + return $query; + } // end self::getQueryFromRequest() +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Rte/Export.php b/php/apps/phpmyadmin49/html/libraries/classes/Rte/Export.php new file mode 100644 index 00000000..731b0505 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Rte/Export.php @@ -0,0 +1,145 @@ +isAjax()) { + $response->addJSON('message', $export_data); + $response->addJSON('title', $title); + exit; + } else { + $export_data = ''; + echo "
    \n" + , "$title\n" + , $export_data + , "
    \n"; + } + } else { + $_db = htmlspecialchars(Util::backquote($db)); + $message = __('Error in processing request:') . ' ' + . sprintf(Words::get('no_view'), $item_name, $_db); + $message = Message::error($message); + + if ($response->isAjax()) { + $response->setRequestStatus(false); + $response->addJSON('message', $message); + exit; + } else { + $message->display(); + } + } + } // end self::handle() + + /** + * If necessary, prepares event information and passes + * it to self::handle() for the actual export. + * + * @return void + */ + public static function events() + { + global $_GET, $db; + + if (! empty($_GET['export_item']) && ! empty($_GET['item_name'])) { + $item_name = $_GET['item_name']; + $export_data = $GLOBALS['dbi']->getDefinition($db, 'EVENT', $item_name); + if (! $export_data) { + $export_data = false; + } + self::handle($export_data); + } + } // end self::events() + + /** + * If necessary, prepares routine information and passes + * it to self::handle() for the actual export. + * + * @return void + */ + public static function routines() + { + global $_GET, $db; + + if (! empty($_GET['export_item']) + && ! empty($_GET['item_name']) + && ! empty($_GET['item_type']) + ) { + if ($_GET['item_type'] == 'FUNCTION' || $_GET['item_type'] == 'PROCEDURE') { + $rtn_definition + = $GLOBALS['dbi']->getDefinition( + $db, + $_GET['item_type'], + $_GET['item_name'] + ); + if (! $rtn_definition) { + $export_data = false; + } else { + $export_data = "DELIMITER $$\n" + . $rtn_definition + . "$$\nDELIMITER ;\n"; + } + + self::handle($export_data); + } + } + } // end self::routines() + + /** + * If necessary, prepares trigger information and passes + * it to self::handle() for the actual export. + * + * @return void + */ + public static function triggers() + { + global $_GET, $db, $table; + + if (! empty($_GET['export_item']) && ! empty($_GET['item_name'])) { + $item_name = $_GET['item_name']; + $triggers = $GLOBALS['dbi']->getTriggers($db, $table, ''); + $export_data = false; + foreach ($triggers as $trigger) { + if ($trigger['name'] === $item_name) { + $export_data = $trigger['create']; + break; + } + } + self::handle($export_data); + } + } // end self::triggers() +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Rte/Footer.php b/php/apps/phpmyadmin49/html/libraries/classes/Rte/Footer.php new file mode 100644 index 00000000..2fdb2583 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Rte/Footer.php @@ -0,0 +1,137 @@ +\n"; + $retval .= "
    \n"; + $retval .= "" . _pgettext('Create new procedure', 'New') . "\n"; + $retval .= " \n"; + $retval .= "
    \n"; + $retval .= "\n\n"; + + return $retval; + } // end self::getLinks() + + /** + * Creates a fieldset for adding a new routine, if the user has the privileges. + * + * @return string HTML code with containing the footer fieldset + */ + public static function routines() + { + return self::getLinks('CREATE_PROCEDURE', 'CREATE ROUTINE', 'ROUTINE'); + }// end self::routines() + + /** + * Creates a fieldset for adding a new trigger, if the user has the privileges. + * + * @return string HTML code with containing the footer fieldset + */ + public static function triggers() + { + return self::getLinks('CREATE_TRIGGER', 'TRIGGER', 'TRIGGER'); + } // end self::triggers() + + /** + * Creates a fieldset for adding a new event, if the user has the privileges. + * + * @return string HTML code with containing the footer fieldset + */ + public static function events() + { + global $db, $url_query; + + /** + * For events, we show the usual 'Add event' form and also + * a form for toggling the state of the event scheduler + */ + // Init options for the event scheduler toggle functionality + $es_state = $GLOBALS['dbi']->fetchValue( + "SHOW GLOBAL VARIABLES LIKE 'event_scheduler'", + 0, + 1 + ); + $es_state = mb_strtolower($es_state); + $options = array( + 0 => array( + 'label' => __('OFF'), + 'value' => "SET GLOBAL event_scheduler=\"OFF\"", + 'selected' => ($es_state != 'on') + ), + 1 => array( + 'label' => __('ON'), + 'value' => "SET GLOBAL event_scheduler=\"ON\"", + 'selected' => ($es_state == 'on') + ) + ); + // Generate output + $retval = "\n"; + $retval .= "
    \n"; + // show the usual footer + $retval .= self::getLinks('CREATE_EVENT', 'EVENT', 'EVENT'); + $retval .= "
    \n"; + $retval .= " \n"; + $retval .= " " . __('Event scheduler status') . "\n"; + $retval .= " \n"; + $retval .= "
    \n"; + // show the toggle button + $retval .= Util::toggleButton( + "sql.php$url_query&goto=db_events.php" . urlencode("?db=$db"), + 'sql_query', + $options, + 'PMA_slidingMessage(data.sql_query);' + ); + $retval .= "
    \n"; + $retval .= "
    \n"; + $retval .= "
    \n"; + $retval .= "
    "; + $retval .= "\n"; + + return $retval; + } // end self::events() +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Rte/General.php b/php/apps/phpmyadmin49/html/libraries/classes/Rte/General.php new file mode 100644 index 00000000..9b13380d --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Rte/General.php @@ -0,0 +1,100 @@ +' + . __('The backed up query was:') + . "\"" . htmlspecialchars($createStatement) . "\"" . '
    ' + . __('MySQL said: ') . $GLOBALS['dbi']->getError(); + + return $errors; + } + + /** + * Send TRI or EVN editor via ajax or by echoing. + * + * @param string $type TRI or EVN + * @param string $mode Editor mode 'add' or 'edit' + * @param array $item Data necessary to create the editor + * @param string $title Title of the editor + * @param string $db Database + * @param string $operation Operation 'change' or '' + * + * @return void + */ + public static function sendEditor($type, $mode, array $item, $title, $db, $operation = null) + { + $response = Response::getInstance(); + if ($item !== false) { + // Show form + if ($type == 'TRI') { + $editor = Triggers::getEditorForm($mode, $item); + } else { // EVN + $editor = Events::getEditorForm($mode, $operation, $item); + } + if ($response->isAjax()) { + $response->addJSON('message', $editor); + $response->addJSON('title', $title); + } else { + echo "\n\n

    $title

    \n\n$editor"; + unset($_POST); + } + exit; + } else { + $message = __('Error in processing request:') . ' '; + $message .= sprintf( + Words::get('not_found'), + htmlspecialchars(Util::backquote($_REQUEST['item_name'])), + htmlspecialchars(Util::backquote($db)) + ); + $message = Message::error($message); + if ($response->isAjax()) { + $response->setRequestStatus(false); + $response->addJSON('message', $message); + exit; + } else { + $message->display(); + } + } + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Rte/Routines.php b/php/apps/phpmyadmin49/html/libraries/classes/Rte/Routines.php new file mode 100644 index 00000000..adba6f1b --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Rte/Routines.php @@ -0,0 +1,1721 @@ +getRoutines($db, $type); + echo RteList::get('routine', $items); + /** + * Display the form for adding a new routine, if the user has the privileges. + */ + echo Footer::routines(); + /** + * Display a warning for users with PHP's old "mysql" extension. + */ + if (! DatabaseInterface::checkDbExtension('mysqli')) { + trigger_error( + __( + 'You are using PHP\'s deprecated \'mysql\' extension, ' + . 'which is not capable of handling multi queries. ' + . '[strong]The execution of some stored routines may fail![/strong] ' + . 'Please use the improved \'mysqli\' extension to ' + . 'avoid any problems.' + ), + E_USER_WARNING + ); + } + } // end self::main() + + /** + * Handles editor requests for adding or editing an item + * + * @return void + */ + public static function handleEditor() + { + global $_GET, $_POST, $_REQUEST, $GLOBALS, $db, $errors; + + $errors = self::handleRequestCreateOrEdit($errors, $db); + $response = Response::getInstance(); + + /** + * Display a form used to add/edit a routine, if necessary + */ + // FIXME: this must be simpler than that + if (count($errors) + || ( empty($_POST['editor_process_add']) + && empty($_POST['editor_process_edit']) + && (! empty($_REQUEST['add_item']) || ! empty($_REQUEST['edit_item']) + || ! empty($_POST['routine_addparameter']) + || ! empty($_POST['routine_removeparameter']) + || ! empty($_POST['routine_changetype']))) + ) { + // Handle requests to add/remove parameters and changing routine type + // This is necessary when JS is disabled + $operation = ''; + if (! empty($_POST['routine_addparameter'])) { + $operation = 'add'; + } elseif (! empty($_POST['routine_removeparameter'])) { + $operation = 'remove'; + } elseif (! empty($_POST['routine_changetype'])) { + $operation = 'change'; + } + // Get the data for the form (if any) + if (! empty($_REQUEST['add_item'])) { + $title = Words::get('add'); + $routine = self::getDataFromRequest(); + $mode = 'add'; + } elseif (! empty($_REQUEST['edit_item'])) { + $title = __("Edit routine"); + if (! $operation && ! empty($_GET['item_name']) + && empty($_POST['editor_process_edit']) + ) { + $routine = self::getDataFromName( + $_GET['item_name'], $_GET['item_type'] + ); + if ($routine !== false) { + $routine['item_original_name'] = $routine['item_name']; + $routine['item_original_type'] = $routine['item_type']; + } + } else { + $routine = self::getDataFromRequest(); + } + $mode = 'edit'; + } + if ($routine !== false) { + // Show form + $editor = self::getEditorForm($mode, $operation, $routine); + if ($response->isAjax()) { + $response->addJSON('message', $editor); + $response->addJSON('title', $title); + $response->addJSON('param_template', self::getParameterRow()); + $response->addJSON('type', $routine['item_type']); + } else { + echo "\n\n

    $title

    \n\n$editor"; + } + exit; + } else { + $message = __('Error in processing request:') . ' '; + $message .= sprintf( + Words::get('no_edit'), + htmlspecialchars( + Util::backquote($_REQUEST['item_name']) + ), + htmlspecialchars(Util::backquote($db)) + ); + + $message = Message::error($message); + if ($response->isAjax()) { + $response->setRequestStatus(false); + $response->addJSON('message', $message); + exit; + } else { + $message->display(); + } + } + } + } + + /** + * Handle request to create or edit a routine + * + * @param array $errors Errors + * @param string $db DB name + * + * @return array + */ + public static function handleRequestCreateOrEdit(array $errors, $db) + { + if (empty($_POST['editor_process_add']) + && empty($_POST['editor_process_edit']) + ) { + return $errors; + } + + $sql_query = ''; + $routine_query = self::getQueryFromRequest(); + if (!count($errors)) { // set by self::getQueryFromRequest() + // Execute the created query + if (!empty($_POST['editor_process_edit'])) { + $isProcOrFunc = in_array( + $_POST['item_original_type'], + array('PROCEDURE', 'FUNCTION') + ); + + if (!$isProcOrFunc) { + $errors[] = sprintf( + __('Invalid routine type: "%s"'), + htmlspecialchars($_POST['item_original_type']) + ); + } else { + // Backup the old routine, in case something goes wrong + $create_routine = $GLOBALS['dbi']->getDefinition( + $db, + $_POST['item_original_type'], + $_POST['item_original_name'] + ); + + $privilegesBackup = self::backupPrivileges(); + + $drop_routine = "DROP {$_POST['item_original_type']} " + . Util::backquote($_POST['item_original_name']) + . ";\n"; + $result = $GLOBALS['dbi']->tryQuery($drop_routine); + if (!$result) { + $errors[] = sprintf( + __('The following query has failed: "%s"'), + htmlspecialchars($drop_routine) + ) + . '
    ' + . __('MySQL said: ') . $GLOBALS['dbi']->getError(); + } else { + list($newErrors, $message) = self::create( + $routine_query, + $create_routine, + $privilegesBackup + ); + if (empty($newErrors)) { + $sql_query = $drop_routine . $routine_query; + } else { + $errors = array_merge($errors, $newErrors); + } + unset($newErrors); + if (null === $message) { + unset($message); + } + } + } + } else { + // 'Add a new routine' mode + $result = $GLOBALS['dbi']->tryQuery($routine_query); + if (!$result) { + $errors[] = sprintf( + __('The following query has failed: "%s"'), + htmlspecialchars($routine_query) + ) + . '

    ' + . __('MySQL said: ') . $GLOBALS['dbi']->getError(); + } else { + $message = Message::success( + __('Routine %1$s has been created.') + ); + $message->addParam( + Util::backquote($_POST['item_name']) + ); + $sql_query = $routine_query; + } + } + } + + if (count($errors)) { + $message = Message::error( + __( + 'One or more errors have occurred while' + . ' processing your request:' + ) + ); + $message->addHtml('
      '); + foreach ($errors as $string) { + $message->addHtml('
    • ' . $string . '
    • '); + } + $message->addHtml('
    '); + } + + $output = Util::getMessage($message, $sql_query); + $response = Response::getInstance(); + if (!$response->isAjax()) { + return $errors; + } + + if (!$message->isSuccess()) { + $response->setRequestStatus(false); + $response->addJSON('message', $output); + exit; + } + + $routines = $GLOBALS['dbi']->getRoutines( + $db, + $_POST['item_type'], + $_POST['item_name'] + ); + $routine = $routines[0]; + $response->addJSON( + 'name', + htmlspecialchars( + mb_strtoupper($_POST['item_name']) + ) + ); + $response->addJSON('new_row', RteList::getRoutineRow($routine)); + $response->addJSON('insert', !empty($routine)); + $response->addJSON('message', $output); + exit; + } + + /** + * Backup the privileges + * + * @return array + */ + public static function backupPrivileges() + { + if (! $GLOBALS['proc_priv'] || ! $GLOBALS['is_reload_priv']) { + return array(); + } + + // Backup the Old Privileges before dropping + // if $_POST['item_adjust_privileges'] set + if (! isset($_POST['item_adjust_privileges']) + || empty($_POST['item_adjust_privileges']) + ) { + return array(); + } + + $privilegesBackupQuery = 'SELECT * FROM ' . Util::backquote( + 'mysql' + ) + . '.' . Util::backquote('procs_priv') + . ' where Routine_name = "' . $_POST['item_original_name'] + . '" AND Routine_type = "' . $_POST['item_original_type'] + . '";'; + + $privilegesBackup = $GLOBALS['dbi']->fetchResult( + $privilegesBackupQuery, + 0 + ); + + return $privilegesBackup; + } + + /** + * Create the routine + * + * @param string $routine_query Query to create routine + * @param string $create_routine Query to restore routine + * @param array $privilegesBackup Privileges backup + * + * @return array + */ + public static function create( + $routine_query, + $create_routine, + array $privilegesBackup + ) { + $result = $GLOBALS['dbi']->tryQuery($routine_query); + if (!$result) { + $errors = array(); + $errors[] = sprintf( + __('The following query has failed: "%s"'), + htmlspecialchars($routine_query) + ) + . '
    ' + . __('MySQL said: ') . $GLOBALS['dbi']->getError(); + // We dropped the old routine, + // but were unable to create the new one + // Try to restore the backup query + $result = $GLOBALS['dbi']->tryQuery($create_routine); + $errors = General::checkResult( + $result, + __( + 'Sorry, we failed to restore' + . ' the dropped routine.' + ), + $create_routine, + $errors + ); + + return array($errors, null); + } + + // Default value + $resultAdjust = false; + + if ($GLOBALS['proc_priv'] + && $GLOBALS['is_reload_priv'] + ) { + // Insert all the previous privileges + // but with the new name and the new type + foreach ($privilegesBackup as $priv) { + $adjustProcPrivilege = 'INSERT INTO ' + . Util::backquote('mysql') . '.' + . Util::backquote('procs_priv') + . ' VALUES("' . $priv[0] . '", "' + . $priv[1] . '", "' . $priv[2] . '", "' + . $_POST['item_name'] . '", "' + . $_POST['item_type'] . '", "' + . $priv[5] . '", "' + . $priv[6] . '", "' + . $priv[7] . '");'; + $resultAdjust = $GLOBALS['dbi']->query( + $adjustProcPrivilege + ); + } + } + + $message = self::flushPrivileges($resultAdjust); + + return array(array(), $message); + } + + /** + * Flush privileges and get message + * + * @param bool $flushPrivileges Flush privileges + * + * @return Message + */ + public static function flushPrivileges($flushPrivileges) + { + if ($flushPrivileges) { + // Flush the Privileges + $flushPrivQuery = 'FLUSH PRIVILEGES;'; + $GLOBALS['dbi']->query($flushPrivQuery); + + $message = Message::success( + __( + 'Routine %1$s has been modified. Privileges have been adjusted.' + ) + ); + } else { + $message = Message::success( + __('Routine %1$s has been modified.') + ); + } + $message->addParam( + Util::backquote($_POST['item_name']) + ); + + return $message; + } // end self::handleEditor() + + /** + * This function will generate the values that are required to + * complete the editor form. It is especially necessary to handle + * the 'Add another parameter', 'Remove last parameter' and + * 'Change routine type' functionalities when JS is disabled. + * + * @return array Data necessary to create the routine editor. + */ + public static function getDataFromRequest() + { + global $_REQUEST, $param_directions, $param_sqldataaccess; + + $retval = array(); + $indices = array('item_name', + 'item_original_name', + 'item_returnlength', + 'item_returnopts_num', + 'item_returnopts_text', + 'item_definition', + 'item_comment', + 'item_definer'); + foreach ($indices as $index) { + $retval[$index] = isset($_POST[$index]) ? $_POST[$index] : ''; + } + + $retval['item_type'] = 'PROCEDURE'; + $retval['item_type_toggle'] = 'FUNCTION'; + if (isset($_REQUEST['item_type']) && $_REQUEST['item_type'] == 'FUNCTION') { + $retval['item_type'] = 'FUNCTION'; + $retval['item_type_toggle'] = 'PROCEDURE'; + } + $retval['item_original_type'] = 'PROCEDURE'; + if (isset($_POST['item_original_type']) + && $_POST['item_original_type'] == 'FUNCTION' + ) { + $retval['item_original_type'] = 'FUNCTION'; + } + $retval['item_num_params'] = 0; + $retval['item_param_dir'] = array(); + $retval['item_param_name'] = array(); + $retval['item_param_type'] = array(); + $retval['item_param_length'] = array(); + $retval['item_param_opts_num'] = array(); + $retval['item_param_opts_text'] = array(); + if (isset($_POST['item_param_name']) + && isset($_POST['item_param_type']) + && isset($_POST['item_param_length']) + && isset($_POST['item_param_opts_num']) + && isset($_POST['item_param_opts_text']) + && is_array($_POST['item_param_name']) + && is_array($_POST['item_param_type']) + && is_array($_POST['item_param_length']) + && is_array($_POST['item_param_opts_num']) + && is_array($_POST['item_param_opts_text']) + ) { + if ($_POST['item_type'] == 'PROCEDURE') { + $retval['item_param_dir'] = $_POST['item_param_dir']; + foreach ($retval['item_param_dir'] as $key => $value) { + if (! in_array($value, $param_directions, true)) { + $retval['item_param_dir'][$key] = ''; + } + } + } + $retval['item_param_name'] = $_POST['item_param_name']; + $retval['item_param_type'] = $_POST['item_param_type']; + foreach ($retval['item_param_type'] as $key => $value) { + if (! in_array($value, Util::getSupportedDatatypes(), true)) { + $retval['item_param_type'][$key] = ''; + } + } + $retval['item_param_length'] = $_POST['item_param_length']; + $retval['item_param_opts_num'] = $_POST['item_param_opts_num']; + $retval['item_param_opts_text'] = $_POST['item_param_opts_text']; + $retval['item_num_params'] = max( + count($retval['item_param_name']), + count($retval['item_param_type']), + count($retval['item_param_length']), + count($retval['item_param_opts_num']), + count($retval['item_param_opts_text']) + ); + } + $retval['item_returntype'] = ''; + if (isset($_POST['item_returntype']) + && in_array($_POST['item_returntype'], Util::getSupportedDatatypes()) + ) { + $retval['item_returntype'] = $_POST['item_returntype']; + } + + $retval['item_isdeterministic'] = ''; + if (isset($_POST['item_isdeterministic']) + && mb_strtolower($_POST['item_isdeterministic']) == 'on' + ) { + $retval['item_isdeterministic'] = " checked='checked'"; + } + $retval['item_securitytype_definer'] = ''; + $retval['item_securitytype_invoker'] = ''; + if (isset($_POST['item_securitytype'])) { + if ($_POST['item_securitytype'] === 'DEFINER') { + $retval['item_securitytype_definer'] = " selected='selected'"; + } elseif ($_POST['item_securitytype'] === 'INVOKER') { + $retval['item_securitytype_invoker'] = " selected='selected'"; + } + } + $retval['item_sqldataaccess'] = ''; + if (isset($_POST['item_sqldataaccess']) + && in_array($_POST['item_sqldataaccess'], $param_sqldataaccess, true) + ) { + $retval['item_sqldataaccess'] = $_POST['item_sqldataaccess']; + } + + return $retval; + } // end self::getDataFromRequest() + + /** + * This function will generate the values that are required to complete + * the "Edit routine" form given the name of a routine. + * + * @param string $name The name of the routine. + * @param string $type Type of routine (ROUTINE|PROCEDURE) + * @param bool $all Whether to return all data or just the info about parameters. + * + * @return array Data necessary to create the routine editor. + */ + public static function getDataFromName($name, $type, $all = true) + { + global $db; + + $retval = array(); + + // Build and execute the query + $fields = "SPECIFIC_NAME, ROUTINE_TYPE, DTD_IDENTIFIER, " + . "ROUTINE_DEFINITION, IS_DETERMINISTIC, SQL_DATA_ACCESS, " + . "ROUTINE_COMMENT, SECURITY_TYPE"; + $where = "ROUTINE_SCHEMA " . Util::getCollateForIS() . "=" + . "'" . $GLOBALS['dbi']->escapeString($db) . "' " + . "AND SPECIFIC_NAME='" . $GLOBALS['dbi']->escapeString($name) . "'" + . "AND ROUTINE_TYPE='" . $GLOBALS['dbi']->escapeString($type) . "'"; + $query = "SELECT $fields FROM INFORMATION_SCHEMA.ROUTINES WHERE $where;"; + + $routine = $GLOBALS['dbi']->fetchSingleRow($query, 'ASSOC'); + + if (! $routine) { + return false; + } + + // Get required data + $retval['item_name'] = $routine['SPECIFIC_NAME']; + $retval['item_type'] = $routine['ROUTINE_TYPE']; + + $definition + = $GLOBALS['dbi']->getDefinition( + $db, + $routine['ROUTINE_TYPE'], + $routine['SPECIFIC_NAME'] + ); + + if ($definition == null) { + return false; + } + + $parser = new Parser($definition); + + /** + * @var CreateStatement $stmt + */ + $stmt = $parser->statements[0]; + + $params = Routine::getParameters($stmt); + $retval['item_num_params'] = $params['num']; + $retval['item_param_dir'] = $params['dir']; + $retval['item_param_name'] = $params['name']; + $retval['item_param_type'] = $params['type']; + $retval['item_param_length'] = $params['length']; + $retval['item_param_length_arr'] = $params['length_arr']; + $retval['item_param_opts_num'] = $params['opts']; + $retval['item_param_opts_text'] = $params['opts']; + + // Get extra data + if (!$all) { + return $retval; + } + + if ($retval['item_type'] == 'FUNCTION') { + $retval['item_type_toggle'] = 'PROCEDURE'; + } else { + $retval['item_type_toggle'] = 'FUNCTION'; + } + $retval['item_returntype'] = ''; + $retval['item_returnlength'] = ''; + $retval['item_returnopts_num'] = ''; + $retval['item_returnopts_text'] = ''; + + if (! empty($routine['DTD_IDENTIFIER'])) { + $options = array(); + foreach ($stmt->return->options->options as $opt) { + $options[] = is_string($opt) ? $opt : $opt['value']; + } + + $retval['item_returntype'] = $stmt->return->name; + $retval['item_returnlength'] = implode(',', $stmt->return->parameters); + $retval['item_returnopts_num'] = implode(' ', $options); + $retval['item_returnopts_text'] = implode(' ', $options); + } + + $retval['item_definer'] = $stmt->options->has('DEFINER'); + $retval['item_definition'] = $routine['ROUTINE_DEFINITION']; + $retval['item_isdeterministic'] = ''; + if ($routine['IS_DETERMINISTIC'] == 'YES') { + $retval['item_isdeterministic'] = " checked='checked'"; + } + $retval['item_securitytype_definer'] = ''; + $retval['item_securitytype_invoker'] = ''; + if ($routine['SECURITY_TYPE'] == 'DEFINER') { + $retval['item_securitytype_definer'] = " selected='selected'"; + } elseif ($routine['SECURITY_TYPE'] == 'INVOKER') { + $retval['item_securitytype_invoker'] = " selected='selected'"; + } + $retval['item_sqldataaccess'] = $routine['SQL_DATA_ACCESS']; + $retval['item_comment'] = $routine['ROUTINE_COMMENT']; + + return $retval; + } // self::getDataFromName() + + /** + * Creates one row for the parameter table used in the routine editor. + * + * @param array $routine Data for the routine returned by + * self::getDataFromRequest() or + * self::getDataFromName() + * @param mixed $index Either a numeric index of the row being processed + * or NULL to create a template row for AJAX request + * @param string $class Class used to hide the direction column, if the + * row is for a stored function. + * + * @return string HTML code of one row of parameter table for the editor. + */ + public static function getParameterRow(array $routine = array(), $index = null, $class = '') + { + global $param_directions, $param_opts_num, $titles; + + if ($index === null) { + // template row for AJAX request + $i = 0; + $index = '%s'; + $drop_class = ''; + $routine = array( + 'item_param_dir' => array(0 => ''), + 'item_param_name' => array(0 => ''), + 'item_param_type' => array(0 => ''), + 'item_param_length' => array(0 => ''), + 'item_param_opts_num' => array(0 => ''), + 'item_param_opts_text' => array(0 => '') + ); + } elseif (! empty($routine)) { + // regular row for routine editor + $drop_class = ' hide'; + $i = $index; + } else { + // No input data. This shouldn't happen, + // but better be safe than sorry. + return ''; + } + + // Create the output + $retval = ""; + $retval .= "
    " + . "" + . "\n"; + $retval .= " \n"; + $retval .= " \n"; + $retval .= " \n"; + $retval .= " \n"; + $retval .= " ---\n"; + $retval .= Charsets::getCharsetDropdownBox( + $GLOBALS['dbi'], + $GLOBALS['cfg']['Server']['DisableIS'], + "item_param_opts_text[$index]", + null, + $routine['item_param_opts_text'][$i] + ); + $retval .= " ---\n"; + $retval .= " \n"; + $retval .= " \n"; + $retval .= " \n"; + $retval .= " {$titles['Drop']}\n"; + $retval .= " \n"; + $retval .= "
    \n"; + $retval .= "\n"; + $retval .= " \n"; + $retval .= " \n"; + $retval .= "\n"; + $retval .= "\n"; + $retval .= " \n"; + $retval .= " "; + $retval .= ""; + $retval .= ""; + $retval .= " "; + $retval .= " "; + $retval .= ""; + // parameter handling end + $retval .= ""; + $retval .= " "; + $retval .= " "; + $retval .= ""; + $retval .= ""; + $retval .= " "; + $retval .= " "; + $retval .= " "; + $retval .= ""; + $retval .= ""; + $retval .= " "; + $retval .= " "; + $retval .= ""; + $retval .= ""; + $retval .= " "; + $retval .= " "; + $retval .= ""; + $retval .= ""; + $retval .= " "; + $retval .= " "; + $retval .= ""; + if (isset($_REQUEST['edit_item']) + && ! empty($_REQUEST['edit_item']) + ) { + $retval .= ""; + $retval .= " "; + if ($GLOBALS['proc_priv'] + && $GLOBALS['is_reload_priv'] + ) { + $retval .= " "; + } else { + $retval .= " "; + } + $retval .= ""; + } + + $retval .= ""; + $retval .= " "; + $retval .= " "; + $retval .= ""; + $retval .= ""; + $retval .= " "; + $retval .= " "; + $retval .= ""; + $retval .= ""; + $retval .= " "; + $retval .= " "; + $retval .= ""; + $retval .= ""; + $retval .= " "; + $retval .= " "; + $retval .= ""; + $retval .= "
    " . __('Routine name') . "\n"; + $retval .= " \n"; + if ($response->isAjax()) { + $retval .= " \n"; + } else { + $retval .= "\n" + . "
    \n" + . $routine['item_type'] . "\n" + . "
    \n" + . "\n"; + } + $retval .= "
    " . __('Parameters') . "\n"; + // parameter handling start + $retval .= " \n"; + $retval .= " \n"; + $retval .= " \n"; + $retval .= " \n"; + $retval .= " \n"; + $retval .= " \n"; + $retval .= " \n"; + $retval .= " \n"; + $retval .= " \n"; + $retval .= " \n"; + $retval .= " "; + $retval .= " \n"; + $retval .= " \n"; + for ($i = 0; $i < $routine['item_num_params']; $i++) { // each parameter + $retval .= self::getParameterRow($routine, $i, $isprocedure_class); + } + $retval .= " \n"; + $retval .= "
    " + . __('Direction') . "" . __('Name') . "" . __('Type') . "" . __('Length/Values') . "" . __('Options') . " 
    "; + $retval .= "
     "; + $retval .= " "; + $retval .= " "; + $retval .= "
    " . __('Return type') . "
    " . __('Return length/values') . "---
    " . __('Return options') . "
    "; + $retval .= Charsets::getCharsetDropdownBox( + $GLOBALS['dbi'], + $GLOBALS['cfg']['Server']['DisableIS'], + "item_returnopts_text", + null, + $routine['item_returnopts_text'] + ); + $retval .= "
    "; + $retval .= "
    "; + $retval .= "
    ---
    "; + $retval .= "
    " . __('Definition') . "
    " . __('Is deterministic') . "
    " . __('Adjust privileges'); + $retval .= Util::showDocu('faq', 'faq6-39'); + $retval .= "
    " . __('Definer') . "
    " . __('Security type') . "
    " . __('SQL data access') . "
    " . __('Comment') . "
    "; + $retval .= "
    "; + if ($response->isAjax()) { + $retval .= ""; + $retval .= ""; + } + $retval .= ""; + $retval .= ""; + + return $retval; + } // end self::getEditorForm() + + /** + * Composes the query necessary to create a routine from an HTTP request. + * + * @return string The CREATE [ROUTINE | PROCEDURE] query. + */ + public static function getQueryFromRequest() + { + global $_REQUEST, $errors, $param_sqldataaccess, $param_directions, $dbi; + + $_POST['item_type'] = isset($_POST['item_type']) + ? $_POST['item_type'] : ''; + + $query = 'CREATE '; + if (! empty($_POST['item_definer'])) { + if (mb_strpos($_POST['item_definer'], '@') !== false) { + $arr = explode('@', $_POST['item_definer']); + + $do_backquote = true; + if (substr($arr[0], 0, 1) === "`" + && substr($arr[0], -1) === "`" + ) { + $do_backquote = false; + } + $query .= 'DEFINER=' . Util::backquote($arr[0], $do_backquote); + + $do_backquote = true; + if (substr($arr[1], 0, 1) === "`" + && substr($arr[1], -1) === "`" + ) { + $do_backquote = false; + } + $query .= '@' . Util::backquote($arr[1], $do_backquote) . ' '; + } else { + $errors[] = __('The definer must be in the "username@hostname" format!'); + } + } + if ($_POST['item_type'] == 'FUNCTION' + || $_POST['item_type'] == 'PROCEDURE' + ) { + $query .= $_POST['item_type'] . ' '; + } else { + $errors[] = sprintf( + __('Invalid routine type: "%s"'), + htmlspecialchars($_POST['item_type']) + ); + } + if (! empty($_POST['item_name'])) { + $query .= Util::backquote($_POST['item_name']); + } else { + $errors[] = __('You must provide a routine name!'); + } + $params = ''; + $warned_about_dir = false; + $warned_about_length = false; + + if (! empty($_POST['item_param_name']) + && ! empty($_POST['item_param_type']) + && ! empty($_POST['item_param_length']) + && is_array($_POST['item_param_name']) + && is_array($_POST['item_param_type']) + && is_array($_POST['item_param_length']) + ) { + $item_param_name = $_POST['item_param_name']; + $item_param_type = $_POST['item_param_type']; + $item_param_length = $_POST['item_param_length']; + + for ($i=0, $nb = count($item_param_name); $i < $nb; $i++) { + if (! empty($item_param_name[$i]) + && ! empty($item_param_type[$i]) + ) { + if ($_POST['item_type'] == 'PROCEDURE' + && ! empty($_POST['item_param_dir'][$i]) + && in_array($_POST['item_param_dir'][$i], $param_directions) + ) { + $params .= $_POST['item_param_dir'][$i] . " " + . Util::backquote($item_param_name[$i]) + . " " . $item_param_type[$i]; + } elseif ($_POST['item_type'] == 'FUNCTION') { + $params .= Util::backquote($item_param_name[$i]) + . " " . $item_param_type[$i]; + } elseif (! $warned_about_dir) { + $warned_about_dir = true; + $errors[] = sprintf( + __('Invalid direction "%s" given for parameter.'), + htmlspecialchars($_POST['item_param_dir'][$i]) + ); + } + if ($item_param_length[$i] != '' + && !preg_match( + '@^(DATE|TINYBLOB|TINYTEXT|BLOB|TEXT|' + . 'MEDIUMBLOB|MEDIUMTEXT|LONGBLOB|LONGTEXT|' + . 'SERIAL|BOOLEAN)$@i', + $item_param_type[$i] + ) + ) { + $params .= "(" . $item_param_length[$i] . ")"; + } elseif ($item_param_length[$i] == '' + && preg_match( + '@^(ENUM|SET|VARCHAR|VARBINARY)$@i', + $item_param_type[$i] + ) + ) { + if (! $warned_about_length) { + $warned_about_length = true; + $errors[] = __( + 'You must provide length/values for routine parameters' + . ' of type ENUM, SET, VARCHAR and VARBINARY.' + ); + } + } + if (! empty($_POST['item_param_opts_text'][$i])) { + if ($dbi->types->getTypeClass($item_param_type[$i]) == 'CHAR') { + if(! in_array($item_param_type[$i], array('VARBINARY', 'BINARY'))) { + $params .= ' CHARSET ' + . mb_strtolower( + $_POST['item_param_opts_text'][$i] + ); + } + } + } + if (! empty($_POST['item_param_opts_num'][$i])) { + if ($dbi->types->getTypeClass($item_param_type[$i]) == 'NUMBER') { + $params .= ' ' + . mb_strtoupper( + $_POST['item_param_opts_num'][$i] + ); + } + } + if ($i != (count($item_param_name) - 1)) { + $params .= ", "; + } + } else { + $errors[] = __( + 'You must provide a name and a type for each routine parameter.' + ); + break; + } + } + } + $query .= "(" . $params . ") "; + if ($_POST['item_type'] == 'FUNCTION') { + $item_returntype = isset($_POST['item_returntype']) + ? $_POST['item_returntype'] + : null; + + if (! empty($item_returntype) + && in_array( + $item_returntype, Util::getSupportedDatatypes() + ) + ) { + $query .= "RETURNS " . $item_returntype; + } else { + $errors[] = __('You must provide a valid return type for the routine.'); + } + if (! empty($_POST['item_returnlength']) + && !preg_match( + '@^(DATE|DATETIME|TIME|TINYBLOB|TINYTEXT|BLOB|TEXT|' + . 'MEDIUMBLOB|MEDIUMTEXT|LONGBLOB|LONGTEXT|SERIAL|BOOLEAN)$@i', + $item_returntype + ) + ) { + $query .= "(" . $_POST['item_returnlength'] . ")"; + } elseif (empty($_POST['item_returnlength']) + && preg_match( + '@^(ENUM|SET|VARCHAR|VARBINARY)$@i', $item_returntype + ) + ) { + if (! $warned_about_length) { + $errors[] = __( + 'You must provide length/values for routine parameters' + . ' of type ENUM, SET, VARCHAR and VARBINARY.' + ); + } + } + if (! empty($_POST['item_returnopts_text'])) { + if ($dbi->types->getTypeClass($item_returntype) == 'CHAR') { + $query .= ' CHARSET ' + . mb_strtolower($_POST['item_returnopts_text']); + } + } + if (! empty($_POST['item_returnopts_num'])) { + if ($dbi->types->getTypeClass($item_returntype) == 'NUMBER') { + $query .= ' ' + . mb_strtoupper($_POST['item_returnopts_num']); + } + } + $query .= ' '; + } + if (! empty($_POST['item_comment'])) { + $query .= "COMMENT '" . $GLOBALS['dbi']->escapeString($_POST['item_comment']) + . "' "; + } + if (isset($_POST['item_isdeterministic'])) { + $query .= 'DETERMINISTIC '; + } else { + $query .= 'NOT DETERMINISTIC '; + } + if (! empty($_POST['item_sqldataaccess']) + && in_array($_POST['item_sqldataaccess'], $param_sqldataaccess) + ) { + $query .= $_POST['item_sqldataaccess'] . ' '; + } + if (! empty($_POST['item_securitytype'])) { + if ($_POST['item_securitytype'] == 'DEFINER' + || $_POST['item_securitytype'] == 'INVOKER' + ) { + $query .= 'SQL SECURITY ' . $_POST['item_securitytype'] . ' '; + } + } + if (! empty($_POST['item_definition'])) { + $query .= $_POST['item_definition']; + } else { + $errors[] = __('You must provide a routine definition.'); + } + + return $query; + } // end self::getQueryFromRequest() + + /** + * Handles requests for executing a routine + * + * @return void + */ + public static function handleExecute() + { + global $_GET, $_POST, $_REQUEST, $GLOBALS, $db; + + $response = Response::getInstance(); + + /** + * Handle all user requests other than the default of listing routines + */ + if (! empty($_POST['execute_routine']) && ! empty($_POST['item_name'])) { + // Build the queries + $routine = self::getDataFromName( + $_POST['item_name'], $_POST['item_type'], false + ); + if ($routine === false) { + $message = __('Error in processing request:') . ' '; + $message .= sprintf( + Words::get('not_found'), + htmlspecialchars(Util::backquote($_POST['item_name'])), + htmlspecialchars(Util::backquote($db)) + ); + $message = Message::error($message); + if ($response->isAjax()) { + $response->setRequestStatus(false); + $response->addJSON('message', $message); + exit; + } else { + echo $message->getDisplay(); + unset($_POST); + } + } + + $queries = array(); + $end_query = array(); + $args = array(); + $all_functions = $GLOBALS['dbi']->types->getAllFunctions(); + for ($i = 0; $i < $routine['item_num_params']; $i++) { + if (isset($_POST['params'][$routine['item_param_name'][$i]])) { + $value = $_POST['params'][$routine['item_param_name'][$i]]; + if (is_array($value)) { // is SET type + $value = implode(',', $value); + } + $value = $GLOBALS['dbi']->escapeString($value); + if (! empty($_POST['funcs'][$routine['item_param_name'][$i]]) + && in_array( + $_POST['funcs'][$routine['item_param_name'][$i]], + $all_functions + ) + ) { + $queries[] = "SET @p$i=" + . $_POST['funcs'][$routine['item_param_name'][$i]] + . "('$value');\n"; + } else { + $queries[] = "SET @p$i='$value';\n"; + } + $args[] = "@p$i"; + } else { + $args[] = "@p$i"; + } + if ($routine['item_type'] == 'PROCEDURE') { + if ($routine['item_param_dir'][$i] == 'OUT' + || $routine['item_param_dir'][$i] == 'INOUT' + ) { + $end_query[] = "@p$i AS " + . Util::backquote($routine['item_param_name'][$i]); + } + } + } + if ($routine['item_type'] == 'PROCEDURE') { + $queries[] = "CALL " . Util::backquote($routine['item_name']) + . "(" . implode(', ', $args) . ");\n"; + if (count($end_query)) { + $queries[] = "SELECT " . implode(', ', $end_query) . ";\n"; + } + } else { + $queries[] = "SELECT " . Util::backquote($routine['item_name']) + . "(" . implode(', ', $args) . ") " + . "AS " . Util::backquote($routine['item_name']) + . ";\n"; + } + + // Get all the queries as one SQL statement + $multiple_query = implode("", $queries); + + $outcome = true; + $affected = 0; + + // Execute query + if (! $GLOBALS['dbi']->tryMultiQuery($multiple_query)) { + $outcome = false; + } + + // Generate output + if ($outcome) { + + // Pass the SQL queries through the "pretty printer" + $output = Util::formatSql(implode($queries, "\n")); + + // Display results + $output .= "
    "; + $output .= sprintf( + __('Execution results of routine %s'), + Util::backquote(htmlspecialchars($routine['item_name'])) + ); + $output .= ""; + + $nbResultsetToDisplay = 0; + + do { + + $result = $GLOBALS['dbi']->storeResult(); + $num_rows = $GLOBALS['dbi']->numRows($result); + + if (($result !== false) && ($num_rows > 0)) { + + $output .= ""; + foreach ($GLOBALS['dbi']->getFieldsMeta($result) as $field) { + $output .= ""; + } + $output .= ""; + + while ($row = $GLOBALS['dbi']->fetchAssoc($result)) { + $output .= "" . self::browseRow($row) . ""; + } + + $output .= "
    "; + $output .= htmlspecialchars($field->name); + $output .= "
    "; + $nbResultsetToDisplay++; + $affected = $num_rows; + + } + + if (! $GLOBALS['dbi']->moreResults()) { + break; + } + + $output .= "
    "; + + $GLOBALS['dbi']->freeResult($result); + + } while ($outcome = $GLOBALS['dbi']->nextResult()); + } + + if ($outcome) { + + $output .= "
    "; + + $message = __('Your SQL query has been executed successfully.'); + if ($routine['item_type'] == 'PROCEDURE') { + $message .= '
    '; + + // TODO : message need to be modified according to the + // output from the routine + $message .= sprintf( + _ngettext( + '%d row affected by the last statement inside the ' + . 'procedure.', + '%d rows affected by the last statement inside the ' + . 'procedure.', + $affected + ), + $affected + ); + } + $message = Message::success($message); + + if ($nbResultsetToDisplay == 0) { + $notice = __( + 'MySQL returned an empty result set (i.e. zero rows).' + ); + $output .= Message::notice($notice)->getDisplay(); + } + + } else { + $output = ''; + $message = Message::error( + sprintf( + __('The following query has failed: "%s"'), + htmlspecialchars($multiple_query) + ) + . '

    ' + . __('MySQL said: ') . $GLOBALS['dbi']->getError() + ); + } + + // Print/send output + if ($response->isAjax()) { + $response->setRequestStatus($message->isSuccess()); + $response->addJSON('message', $message->getDisplay() . $output); + $response->addJSON('dialog', false); + exit; + } else { + echo $message->getDisplay() , $output; + if ($message->isError()) { + // At least one query has failed, so shouldn't + // execute any more queries, so we quit. + exit; + } + unset($_POST); + // Now deliberately fall through to displaying the routines list + } + return; + } elseif (! empty($_GET['execute_dialog']) && ! empty($_GET['item_name'])) { + /** + * Display the execute form for a routine. + */ + $routine = self::getDataFromName( + $_GET['item_name'], $_GET['item_type'], true + ); + if ($routine !== false) { + $form = self::getExecuteForm($routine); + if ($response->isAjax()) { + $title = __("Execute routine") . " " . Util::backquote( + htmlentities($_GET['item_name'], ENT_QUOTES) + ); + $response->addJSON('message', $form); + $response->addJSON('title', $title); + $response->addJSON('dialog', true); + } else { + echo "\n\n

    " . __("Execute routine") . "

    \n\n"; + echo $form; + } + exit; + } elseif (($response->isAjax())) { + $message = __('Error in processing request:') . ' '; + $message .= sprintf( + Words::get('not_found'), + htmlspecialchars(Util::backquote($_GET['item_name'])), + htmlspecialchars(Util::backquote($db)) + ); + $message = Message::error($message); + + $response->setRequestStatus(false); + $response->addJSON('message', $message); + exit; + } + } + } + + /** + * Browse row array + * + * @param array $row Columns + * + * @return string + */ + private static function browseRow(array $row) + { + $output = null; + foreach ($row as $value) { + if ($value === null) { + $value = 'NULL'; + } else { + $value = htmlspecialchars($value); + } + $output .= "" . $value . ""; + } + return $output; + } + + /** + * Creates the HTML code that shows the routine execution dialog. + * + * @param array $routine Data for the routine returned by + * self::getDataFromName() + * + * @return string HTML code for the routine execution dialog. + */ + public static function getExecuteForm(array $routine) + { + global $db, $cfg; + + $response = Response::getInstance(); + + // Escape special characters + $routine['item_name'] = htmlentities($routine['item_name'], ENT_QUOTES); + for ($i = 0; $i < $routine['item_num_params']; $i++) { + $routine['item_param_name'][$i] = htmlentities( + $routine['item_param_name'][$i], + ENT_QUOTES + ); + } + + // Create the output + $retval = ""; + $retval .= "\n\n"; + $retval .= "
    isAjax()) { + $retval .= "{$routine['item_name']}\n"; + $retval .= "\n"; + $retval .= "\n"; + } else { + $retval .= "" . __('Routine parameters') . "\n"; + $retval .= "
    \n"; + $retval .= __('Routine parameters'); + $retval .= "
    \n"; + } + $retval .= "\n"; + $retval .= "\n"; + $retval .= "\n"; + if ($cfg['ShowFunctionFields']) { + $retval .= "\n"; + } + $retval .= "\n"; + $retval .= "\n"; + // Get a list of data types that are not yet supported. + $no_support_types = Util::unsupportedDatatypes(); + for ($i = 0; $i < $routine['item_num_params']; $i++) { // Each parameter + if ($routine['item_type'] == 'PROCEDURE' + && $routine['item_param_dir'][$i] == 'OUT' + ) { + continue; + } + $retval .= "\n\n"; + $retval .= "\n"; + $retval .= "\n"; + if ($cfg['ShowFunctionFields']) { + $retval .= "\n"; + } + // Append a class to date/time fields so that + // jQuery can attach a datepicker to them + $class = ''; + if ($routine['item_param_type'][$i] == 'DATETIME' + || $routine['item_param_type'][$i] == 'TIMESTAMP' + ) { + $class = 'datetimefield'; + } elseif ($routine['item_param_type'][$i] == 'DATE') { + $class = 'datefield'; + } + $retval .= "\n"; + $retval .= "\n"; + } + $retval .= "\n
    " . __('Name') . "" . __('Type') . "" . __('Function') . "" . __('Value') . "
    {$routine['item_param_name'][$i]}{$routine['item_param_type'][$i]}\n"; + if (stristr($routine['item_param_type'][$i], 'enum') + || stristr($routine['item_param_type'][$i], 'set') + || in_array( + mb_strtolower($routine['item_param_type'][$i]), + $no_support_types + ) + ) { + $retval .= "--\n"; + } else { + $field = array( + 'True_Type' => mb_strtolower( + $routine['item_param_type'][$i] + ), + 'Type' => '', + 'Key' => '', + 'Field' => '', + 'Default' => '', + 'first_timestamp' => false + ); + $retval .= ""; + } + $retval .= "\n"; + if (in_array($routine['item_param_type'][$i], array('ENUM', 'SET'))) { + if ($routine['item_param_type'][$i] == 'ENUM') { + $input_type = 'radio'; + } else { + $input_type = 'checkbox'; + } + foreach ($routine['item_param_length_arr'][$i] as $value) { + $value = htmlentities(Util::unquote($value), ENT_QUOTES); + $retval .= "" + . $value . "
    \n"; + } + } elseif (in_array( + mb_strtolower($routine['item_param_type'][$i]), + $no_support_types + )) { + $retval .= "\n"; + } else { + $retval .= "\n"; + } + $retval .= "
    \n"; + if (! $response->isAjax()) { + $retval .= "\n\n"; + $retval .= "
    \n"; + $retval .= " \n"; + $retval .= "
    \n"; + } else { + $retval .= ""; + $retval .= ""; + } + $retval .= "
    \n\n"; + $retval .= "\n\n"; + + return $retval; + } // end self::getExecuteForm() +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Rte/RteList.php b/php/apps/phpmyadmin49/html/libraries/classes/Rte/RteList.php new file mode 100644 index 00000000..fedf3278 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Rte/RteList.php @@ -0,0 +1,486 @@ +\n"; + $retval .= '
    '; + $retval .= Url::getHiddenInputs($GLOBALS['db'], $GLOBALS['table']); + $retval .= "
    \n"; + $retval .= " \n"; + $retval .= " " . Words::get('title') . "\n"; + $retval .= " " + . Util::showMySQLDocu(Words::get('docu')) . "\n"; + $retval .= " \n"; + $retval .= "
    \n"; + $retval .= " " . Words::get('nothing') . "\n"; + $retval .= "
    \n"; + $retval .= " \n"; + $retval .= " \n"; + $retval .= " \n"; + // th cells with a colspan need corresponding td cells, according to W3C + switch ($type) { + case 'routine': + $retval .= " \n"; + $retval .= " \n"; + $retval .= " \n"; + $retval .= " \n"; + $retval .= " \n"; + $retval .= " \n"; + $retval .= " \n"; // see comment above + for ($i = 0; $i < 7; $i++) { + $retval .= " \n"; + } + break; + case 'trigger': + $retval .= " \n"; + $retval .= " \n"; + if (empty($table)) { + $retval .= " \n"; + } + $retval .= " \n"; + $retval .= " \n"; + $retval .= " \n"; + $retval .= " \n"; + $retval .= " \n"; // see comment above + for ($i = 0; $i < (empty($table) ? 7 : 6); $i++) { + $retval .= " \n"; + } + break; + case 'event': + $retval .= " \n"; + $retval .= " \n"; + $retval .= " \n"; + $retval .= " \n"; + $retval .= " \n"; + $retval .= " \n"; + $retval .= " \n"; // see comment above + for ($i = 0; $i < 6; $i++) { + $retval .= " \n"; + } + break; + default: + break; + } + $retval .= " \n"; + $retval .= " \n"; + $count = 0; + $response = Response::getInstance(); + foreach ($items as $item) { + if ($response->isAjax() && empty($_REQUEST['ajax_page_request'])) { + $rowclass = 'ajaxInsert hide'; + } else { + $rowclass = ''; + } + // Get each row from the correct function + switch ($type) { + case 'routine': + $retval .= self::getRoutineRow($item, $rowclass); + break; + case 'trigger': + $retval .= self::getTriggerRow($item, $rowclass); + break; + case 'event': + $retval .= self::getEventRow($item, $rowclass); + break; + default: + break; + } + $count++; + } + $retval .= "
    " . __('Name') . "" . __('Action') . "" . __('Type') . "" . __('Returns') . "
    " . __('Name') . "" . __('Table') . "" . __('Action') . "" . __('Time') . "" . __('Event') . "
    " . __('Name') . "" . __('Status') . "" . __('Action') . "" . __('Type') . "
    \n"; + + if (count($items)) { + $retval .= '
    '; + $retval .= Template::get('select_all') + ->render( + array( + 'pma_theme_image' => $GLOBALS['pmaThemeImage'], + 'text_dir' => $GLOBALS['text_dir'], + 'form_name' => 'rteListForm', + ) + ); + $retval .= Util::getButtonOrImage( + 'submit_mult', 'mult_submit', + __('Export'), 'b_export', 'export' + ); + $retval .= Util::getButtonOrImage( + 'submit_mult', 'mult_submit', + __('Drop'), 'b_drop', 'drop' + ); + $retval .= '
    '; + } + + $retval .= "
    \n"; + $retval .= "
    \n"; + $retval .= "\n"; + + return $retval; + } // end self::get() + + /** + * Creates the contents for a row in the list of routines + * + * @param array $routine An array of routine data + * @param string $rowclass Additional class + * + * @return string HTML code of a row for the list of routines + */ + public static function getRoutineRow(array $routine, $rowclass = '') + { + global $url_query, $db, $titles; + + $sql_drop = sprintf( + 'DROP %s IF EXISTS %s', + $routine['type'], + Util::backquote($routine['name']) + ); + $type_link = "item_type={$routine['type']}"; + + $retval = " \n"; + $retval .= " \n"; + $retval .= ' '; + $retval .= " \n"; + $retval .= " \n"; + $retval .= " " + . htmlspecialchars($sql_drop) . "\n"; + $retval .= " \n"; + $retval .= " " + . htmlspecialchars($routine['name']) . "\n"; + $retval .= " \n"; + $retval .= " \n"; + $retval .= " \n"; + + // this is for our purpose to decide whether to + // show the edit link or not, so we need the DEFINER for the routine + $where = "ROUTINE_SCHEMA " . Util::getCollateForIS() . "=" + . "'" . $GLOBALS['dbi']->escapeString($db) . "' " + . "AND SPECIFIC_NAME='" . $GLOBALS['dbi']->escapeString($routine['name']) . "'" + . "AND ROUTINE_TYPE='" . $GLOBALS['dbi']->escapeString($routine['type']) . "'"; + $query = "SELECT `DEFINER` FROM INFORMATION_SCHEMA.ROUTINES WHERE $where;"; + $routine_definer = $GLOBALS['dbi']->fetchValue($query); + + $curr_user = $GLOBALS['dbi']->getCurrentUser(); + + // Since editing a procedure involved dropping and recreating, check also for + // CREATE ROUTINE privilege to avoid lost procedures. + if ((Util::currentUserHasPrivilege('CREATE ROUTINE', $db) + && $curr_user == $routine_definer) + || $GLOBALS['dbi']->isSuperuser() + ) { + $retval .= ' ' . $titles['Edit'] . "\n"; + } else { + $retval .= " {$titles['NoEdit']}\n"; + } + $retval .= " \n"; + $retval .= " \n"; + + // There is a problem with Util::currentUserHasPrivilege(): + // it does not detect all kinds of privileges, for example + // a direct privilege on a specific routine. So, at this point, + // we show the Execute link, hoping that the user has the correct rights. + // Also, information_schema might be hiding the ROUTINE_DEFINITION + // but a routine with no input parameters can be nonetheless executed. + + // Check if the routine has any input parameters. If it does, + // we will show a dialog to get values for these parameters, + // otherwise we can execute it directly. + + $definition = $GLOBALS['dbi']->getDefinition( + $db, $routine['type'], $routine['name'] + ); + if ($definition !== false) { + $parser = new Parser($definition); + + /** + * @var CreateStatement $stmt + */ + $stmt = $parser->statements[0]; + + $params = Routine::getParameters($stmt); + + if (Util::currentUserHasPrivilege('EXECUTE', $db)) { + $execute_action = 'execute_routine'; + for ($i = 0; $i < $params['num']; $i++) { + if ($routine['type'] == 'PROCEDURE' + && $params['dir'][$i] == 'OUT' + ) { + continue; + } + $execute_action = 'execute_dialog'; + break; + } + $query_part = $execute_action . '=1&item_name=' + . urlencode($routine['name']) . '&' . $type_link; + $retval .= ' ' . $titles['Execute'] . "\n"; + } else { + $retval .= " {$titles['NoExecute']}\n"; + } + } + + $retval .= " \n"; + $retval .= " \n"; + if ((Util::currentUserHasPrivilege('CREATE ROUTINE', $db) + && $curr_user == $routine_definer) + || $GLOBALS['dbi']->isSuperuser() + ) { + $retval .= ' ' . $titles['Export'] . "\n"; + } else { + $retval .= " {$titles['NoExport']}\n"; + } + $retval .= " \n"; + $retval .= " \n"; + $retval .= Util::linkOrButton( + 'sql.php' . $url_query . '&sql_query=' . urlencode($sql_drop) . '&goto=db_routines.php' . urlencode("?db={$db}"), + $titles['Drop'], + ['class' => 'ajax drop_anchor'] + ); + $retval .= " \n"; + $retval .= " \n"; + $retval .= " {$routine['type']}\n"; + $retval .= " \n"; + $retval .= " \n"; + $retval .= " " + . htmlspecialchars($routine['returns']) . "\n"; + $retval .= " \n"; + $retval .= " \n"; + + return $retval; + } // end self::getRoutineRow() + + /** + * Creates the contents for a row in the list of triggers + * + * @param array $trigger An array of routine data + * @param string $rowclass Additional class + * + * @return string HTML code of a cell for the list of triggers + */ + public static function getTriggerRow(array $trigger, $rowclass = '') + { + global $url_query, $db, $table, $titles; + + $retval = " \n"; + $retval .= " \n"; + $retval .= ' '; + $retval .= " \n"; + $retval .= " \n"; + $retval .= " " + . htmlspecialchars($trigger['drop']) . "\n"; + $retval .= " \n"; + $retval .= " " . htmlspecialchars($trigger['name']) . "\n"; + $retval .= " \n"; + $retval .= " \n"; + if (empty($table)) { + $retval .= " \n"; + $retval .= "" + . htmlspecialchars($trigger['table']) . ""; + $retval .= " \n"; + } + $retval .= " \n"; + if (Util::currentUserHasPrivilege('TRIGGER', $db, $table)) { + $retval .= ' ' . $titles['Edit'] . "\n"; + } else { + $retval .= " {$titles['NoEdit']}\n"; + } + $retval .= " \n"; + $retval .= " \n"; + $retval .= ' ' . $titles['Export'] . "\n"; + $retval .= " \n"; + $retval .= " \n"; + if (Util::currentUserHasPrivilege('TRIGGER', $db)) { + $retval .= Util::linkOrButton( + 'sql.php' . $url_query . '&sql_query=' . urlencode($trigger['drop']) . '&goto=db_triggers.php' . urlencode("?db={$db}"), + $titles['Drop'], + ['class' => 'ajax drop_anchor'] + ); + } else { + $retval .= " {$titles['NoDrop']}\n"; + } + $retval .= " \n"; + $retval .= " \n"; + $retval .= " {$trigger['action_timing']}\n"; + $retval .= " \n"; + $retval .= " \n"; + $retval .= " {$trigger['event_manipulation']}\n"; + $retval .= " \n"; + $retval .= " \n"; + + return $retval; + } // end self::getTriggerRow() + + /** + * Creates the contents for a row in the list of events + * + * @param array $event An array of routine data + * @param string $rowclass Additional class + * + * @return string HTML code of a cell for the list of events + */ + public static function getEventRow(array $event, $rowclass = '') + { + global $url_query, $db, $titles; + + $sql_drop = sprintf( + 'DROP EVENT IF EXISTS %s', + Util::backquote($event['name']) + ); + + $retval = " \n"; + $retval .= " \n"; + $retval .= ' '; + $retval .= " \n"; + $retval .= " \n"; + $retval .= " " + . htmlspecialchars($sql_drop) . "\n"; + $retval .= " \n"; + $retval .= " " + . htmlspecialchars($event['name']) . "\n"; + $retval .= " \n"; + $retval .= " \n"; + $retval .= " \n"; + $retval .= " {$event['status']}\n"; + $retval .= " \n"; + $retval .= " \n"; + if (Util::currentUserHasPrivilege('EVENT', $db)) { + $retval .= ' ' . $titles['Edit'] . "\n"; + } else { + $retval .= " {$titles['NoEdit']}\n"; + } + $retval .= " \n"; + $retval .= " \n"; + $retval .= ' ' . $titles['Export'] . "\n"; + $retval .= " \n"; + $retval .= " \n"; + if (Util::currentUserHasPrivilege('EVENT', $db)) { + $retval .= Util::linkOrButton( + 'sql.php' . $url_query . '&sql_query=' . urlencode($sql_drop) . '&goto=db_events.php' . urlencode("?db={$db}"), + $titles['Drop'], + ['class' => 'ajax drop_anchor'] + ); + } else { + $retval .= " {$titles['NoDrop']}\n"; + } + $retval .= " \n"; + $retval .= " \n"; + $retval .= " {$event['type']}\n"; + $retval .= " \n"; + $retval .= " \n"; + + return $retval; + } // end self::getEventRow() +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Rte/Triggers.php b/php/apps/phpmyadmin49/html/libraries/classes/Rte/Triggers.php new file mode 100644 index 00000000..c79d3eca --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Rte/Triggers.php @@ -0,0 +1,478 @@ +getTriggers($db, $table); + echo RteList::get('trigger', $items); + /** + * Display a link for adding a new trigger, + * if the user has the necessary privileges + */ + echo Footer::triggers(); + } // end self::main() + + /** + * Handles editor requests for adding or editing an item + * + * @return void + */ + public static function handleEditor() + { + global $_REQUEST, $_POST, $errors, $db, $table; + + if (! empty($_POST['editor_process_add']) + || ! empty($_POST['editor_process_edit']) + ) { + $sql_query = ''; + + $item_query = self::getQueryFromRequest(); + + if (! count($errors)) { // set by PhpMyAdmin\Rte\Routines::getQueryFromRequest() + // Execute the created query + if (! empty($_POST['editor_process_edit'])) { + // Backup the old trigger, in case something goes wrong + $trigger = self::getDataFromName($_POST['item_original_name']); + $create_item = $trigger['create']; + $drop_item = $trigger['drop'] . ';'; + $result = $GLOBALS['dbi']->tryQuery($drop_item); + if (! $result) { + $errors[] = sprintf( + __('The following query has failed: "%s"'), + htmlspecialchars($drop_item) + ) + . '
    ' + . __('MySQL said: ') . $GLOBALS['dbi']->getError(); + } else { + $result = $GLOBALS['dbi']->tryQuery($item_query); + if (! $result) { + $errors[] = sprintf( + __('The following query has failed: "%s"'), + htmlspecialchars($item_query) + ) + . '
    ' + . __('MySQL said: ') . $GLOBALS['dbi']->getError(); + // We dropped the old item, but were unable to create the + // new one. Try to restore the backup query. + $result = $GLOBALS['dbi']->tryQuery($create_item); + + $errors = General::checkResult( + $result, + __( + 'Sorry, we failed to restore the dropped trigger.' + ), + $create_item, + $errors + ); + } else { + $message = Message::success( + __('Trigger %1$s has been modified.') + ); + $message->addParam( + Util::backquote($_POST['item_name']) + ); + $sql_query = $drop_item . $item_query; + } + } + } else { + // 'Add a new item' mode + $result = $GLOBALS['dbi']->tryQuery($item_query); + if (! $result) { + $errors[] = sprintf( + __('The following query has failed: "%s"'), + htmlspecialchars($item_query) + ) + . '

    ' + . __('MySQL said: ') . $GLOBALS['dbi']->getError(); + } else { + $message = Message::success( + __('Trigger %1$s has been created.') + ); + $message->addParam( + Util::backquote($_POST['item_name']) + ); + $sql_query = $item_query; + } + } + } + + if (count($errors)) { + $message = Message::error( + '' + . __( + 'One or more errors have occurred while processing your request:' + ) + . '' + ); + $message->addHtml('
      '); + foreach ($errors as $string) { + $message->addHtml('
    • ' . $string . '
    • '); + } + $message->addHtml('
    '); + } + + $output = Util::getMessage($message, $sql_query); + $response = Response::getInstance(); + if ($response->isAjax()) { + if ($message->isSuccess()) { + $items = $GLOBALS['dbi']->getTriggers($db, $table, ''); + $trigger = false; + foreach ($items as $value) { + if ($value['name'] == $_POST['item_name']) { + $trigger = $value; + } + } + $insert = false; + if (empty($table) + || ($trigger !== false && $table == $trigger['table']) + ) { + $insert = true; + $response->addJSON('new_row', RteList::getTriggerRow($trigger)); + $response->addJSON( + 'name', + htmlspecialchars( + mb_strtoupper( + $_POST['item_name'] + ) + ) + ); + } + $response->addJSON('insert', $insert); + $response->addJSON('message', $output); + } else { + $response->addJSON('message', $message); + $response->setRequestStatus(false); + } + exit; + } + } + + /** + * Display a form used to add/edit a trigger, if necessary + */ + if (count($errors) + || (empty($_POST['editor_process_add']) + && empty($_POST['editor_process_edit']) + && (! empty($_REQUEST['add_item']) + || ! empty($_REQUEST['edit_item']))) // FIXME: this must be simpler than that + ) { + // Get the data for the form (if any) + if (! empty($_REQUEST['add_item'])) { + $title = Words::get('add'); + $item = self::getDataFromRequest(); + $mode = 'add'; + } elseif (! empty($_REQUEST['edit_item'])) { + $title = __("Edit trigger"); + if (! empty($_REQUEST['item_name']) + && empty($_POST['editor_process_edit']) + ) { + $item = self::getDataFromName($_REQUEST['item_name']); + if ($item !== false) { + $item['item_original_name'] = $item['item_name']; + } + } else { + $item = self::getDataFromRequest(); + } + $mode = 'edit'; + } + General::sendEditor('TRI', $mode, $item, $title, $db); + } + } // end self::handleEditor() + + /** + * This function will generate the values that are required to for the editor + * + * @return array Data necessary to create the editor. + */ + public static function getDataFromRequest() + { + $retval = array(); + $indices = array('item_name', + 'item_table', + 'item_original_name', + 'item_action_timing', + 'item_event_manipulation', + 'item_definition', + 'item_definer'); + foreach ($indices as $index) { + $retval[$index] = isset($_POST[$index]) ? $_POST[$index] : ''; + } + return $retval; + } // end self::getDataFromRequest() + + /** + * This function will generate the values that are required to complete + * the "Edit trigger" form given the name of a trigger. + * + * @param string $name The name of the trigger. + * + * @return array Data necessary to create the editor. + */ + public static function getDataFromName($name) + { + global $db, $table, $_REQUEST; + + $temp = array(); + $items = $GLOBALS['dbi']->getTriggers($db, $table, ''); + foreach ($items as $value) { + if ($value['name'] == $name) { + $temp = $value; + } + } + if (empty($temp)) { + return false; + } else { + $retval = array(); + $retval['create'] = $temp['create']; + $retval['drop'] = $temp['drop']; + $retval['item_name'] = $temp['name']; + $retval['item_table'] = $temp['table']; + $retval['item_action_timing'] = $temp['action_timing']; + $retval['item_event_manipulation'] = $temp['event_manipulation']; + $retval['item_definition'] = $temp['definition']; + $retval['item_definer'] = $temp['definer']; + return $retval; + } + } // end self::getDataFromName() + + /** + * Displays a form used to add/edit a trigger + * + * @param string $mode If the editor will be used to edit a trigger + * or add a new one: 'edit' or 'add'. + * @param array $item Data for the trigger returned by self::getDataFromRequest() + * or self::getDataFromName() + * + * @return string HTML code for the editor. + */ + public static function getEditorForm($mode, array $item) + { + global $db, $table, $event_manipulations, $action_timings; + + $modeToUpper = mb_strtoupper($mode); + $response = Response::getInstance(); + + // Escape special characters + $need_escape = array( + 'item_original_name', + 'item_name', + 'item_definition', + 'item_definer' + ); + foreach ($need_escape as $key => $index) { + $item[$index] = htmlentities($item[$index], ENT_QUOTES, 'UTF-8'); + } + $original_data = ''; + if ($mode == 'edit') { + $original_data = "\n"; + } + $query = "SELECT `TABLE_NAME` FROM `INFORMATION_SCHEMA`.`TABLES` "; + $query .= "WHERE `TABLE_SCHEMA`='" . $GLOBALS['dbi']->escapeString($db) . "' "; + $query .= "AND `TABLE_TYPE` IN ('BASE TABLE', 'SYSTEM VERSIONED')"; + $tables = $GLOBALS['dbi']->fetchResult($query); + + // Create the output + $retval = ""; + $retval .= "\n\n"; + $retval .= "
    \n"; + $retval .= "\n"; + $retval .= $original_data; + $retval .= Url::getHiddenInputs($db, $table) . "\n"; + $retval .= "
    \n"; + $retval .= "" . __('Details') . "\n"; + $retval .= "\n"; + $retval .= "\n"; + $retval .= " \n"; + $retval .= " \n"; + $retval .= "\n"; + $retval .= "\n"; + $retval .= " \n"; + $retval .= " \n"; + $retval .= "\n"; + $retval .= "\n"; + $retval .= " \n"; + $retval .= " \n"; + $retval .= "\n"; + $retval .= "\n"; + $retval .= " \n"; + $retval .= " \n"; + $retval .= "\n"; + $retval .= "\n"; + $retval .= " \n"; + $retval .= " '; + + $html_output .= ''; + + $html_output .= '' + . '' + . '' + . '' + . ''; + $current_user = $row['User']; + $current_host = $row['Host']; + $routine = $row['Routine_name']; + $html_output .= ''; + $html_output .= ''; + + $html_output .= ''; + + } + return $html_output; + } + + /** + * Get the HTML for user form and check the privileges for a particular database. + * + * @param string $db database name + * + * @return string $html_output + */ + public static function getHtmlForSpecificDbPrivileges($db) + { + $html_output = ''; + + if ($GLOBALS['dbi']->isSuperuser()) { + // check the privileges for a particular database. + $html_output = ''; + $html_output .= Url::getHiddenInputs($db); + $html_output .= '
    '; + $html_output .= '
    '; + $html_output .= '' . "\n" + . Util::getIcon('b_usrcheck') + . ' ' + . sprintf( + __('Users having access to "%s"'), + '' + . htmlspecialchars($db) + . '' + ) + . "\n" + . '' . "\n"; + + $html_output .= '
    '; + $html_output .= '
    " . __('Trigger name') . "\n"; + $retval .= " \n"; + $retval .= " \n"; + $retval .= "
    " . _pgettext('Trigger action time', 'Time') . "
    " . __('Event') . "
    " . __('Definition') . "
    " . __('Definer') . "isAjax()) { + $retval .= "\n"; + $retval .= "\n"; + } + $retval .= "\n\n"; + $retval .= "\n\n"; + + return $retval; + } // end self::getEditorForm() + + /** + * Composes the query necessary to create a trigger from an HTTP request. + * + * @return string The CREATE TRIGGER query. + */ + public static function getQueryFromRequest() + { + global $_REQUEST, $db, $errors, $action_timings, $event_manipulations; + + $query = 'CREATE '; + if (! empty($_POST['item_definer'])) { + if (mb_strpos($_POST['item_definer'], '@') !== false + ) { + $arr = explode('@', $_POST['item_definer']); + $query .= 'DEFINER=' . Util::backquote($arr[0]); + $query .= '@' . Util::backquote($arr[1]) . ' '; + } else { + $errors[] = __('The definer must be in the "username@hostname" format!'); + } + } + $query .= 'TRIGGER '; + if (! empty($_POST['item_name'])) { + $query .= Util::backquote($_POST['item_name']) . ' '; + } else { + $errors[] = __('You must provide a trigger name!'); + } + if (! empty($_POST['item_timing']) + && in_array($_POST['item_timing'], $action_timings) + ) { + $query .= $_POST['item_timing'] . ' '; + } else { + $errors[] = __('You must provide a valid timing for the trigger!'); + } + if (! empty($_POST['item_event']) + && in_array($_POST['item_event'], $event_manipulations) + ) { + $query .= $_POST['item_event'] . ' '; + } else { + $errors[] = __('You must provide a valid event for the trigger!'); + } + $query .= 'ON '; + if (! empty($_POST['item_table']) + && in_array($_POST['item_table'], $GLOBALS['dbi']->getTables($db)) + ) { + $query .= Util::backquote($_POST['item_table']); + } else { + $errors[] = __('You must provide a valid table name!'); + } + $query .= ' FOR EACH ROW '; + if (! empty($_POST['item_definition'])) { + $query .= $_POST['item_definition']; + } else { + $errors[] = __('You must provide a trigger definition.'); + } + + return $query; + } // end self::getQueryFromRequest() +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Rte/Words.php b/php/apps/phpmyadmin49/html/libraries/classes/Rte/Words.php new file mode 100644 index 00000000..ce3c305e --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Rte/Words.php @@ -0,0 +1,87 @@ + __('Add routine'), + 'docu' => 'STORED_ROUTINES', + 'export' => __('Export of routine %s'), + 'human' => __('routine'), + 'no_create' => __( + 'You do not have the necessary privileges to create a routine.' + ), + 'no_edit' => __( + 'No routine with name %1$s found in database %2$s. ' + . 'You might be lacking the necessary privileges to edit this routine.' + ), + 'no_view' => __( + 'No routine with name %1$s found in database %2$s. ' + . 'You might be lacking the necessary privileges to view/export this routine.' + ), + 'not_found' => __('No routine with name %1$s found in database %2$s.'), + 'nothing' => __('There are no routines to display.'), + 'title' => __('Routines'), + ); + break; + case 'TRI': + $words = array( + 'add' => __('Add trigger'), + 'docu' => 'TRIGGERS', + 'export' => __('Export of trigger %s'), + 'human' => __('trigger'), + 'no_create' => __( + 'You do not have the necessary privileges to create a trigger.' + ), + 'not_found' => __('No trigger with name %1$s found in database %2$s.'), + 'nothing' => __('There are no triggers to display.'), + 'title' => __('Triggers'), + ); + break; + case 'EVN': + $words = array( + 'add' => __('Add event'), + 'docu' => 'EVENTS', + 'export' => __('Export of event %s'), + 'human' => __('event'), + 'no_create' => __( + 'You do not have the necessary privileges to create an event.' + ), + 'not_found' => __('No event with name %1$s found in database %2$s.'), + 'nothing' => __('There are no events to display.'), + 'title' => __('Events'), + ); + break; + default: + $words = array(); + break; + } + + return isset($words[$index]) ? $words[$index] : ''; + } // end self::get() +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Sanitize.php b/php/apps/phpmyadmin49/html/libraries/classes/Sanitize.php new file mode 100644 index 00000000..e8ee32d5 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Sanitize.php @@ -0,0 +1,466 @@ +get('is_setup'); + // Adjust path to setup script location + if ($is_setup) { + foreach ($valid_starts as $key => $value) { + if (substr($value, 0, 2) === './') { + $valid_starts[$key] = '.' . $value; + } + } + } + if ($other) { + $valid_starts[] = 'mailto:'; + $valid_starts[] = 'ftp://'; + } + if ($http) { + $valid_starts[] = 'http://'; + } + if ($is_setup) { + $valid_starts[] = '?page=form&'; + $valid_starts[] = '?page=servers&'; + } + foreach ($valid_starts as $val) { + if (substr($url, 0, strlen($val)) == $val) { + return true; + } + } + return false; + } + + /** + * Callback function for replacing [a@link@target] links in bb code. + * + * @param array $found Array of preg matches + * + * @return string Replaced string + */ + public static function replaceBBLink(array $found) + { + /* Check for valid link */ + if (! self::checkLink($found[1])) { + return $found[0]; + } + /* a-z and _ allowed in target */ + if (! empty($found[3]) && preg_match('/[^a-z_]+/i', $found[3])) { + return $found[0]; + } + + /* Construct target */ + $target = ''; + if (! empty($found[3])) { + $target = ' target="' . $found[3] . '"'; + if ($found[3] == '_blank') { + $target .= ' rel="noopener noreferrer"'; + } + } + + /* Construct url */ + if (substr($found[1], 0, 4) == 'http') { + $url = Core::linkURL($found[1]); + } else { + $url = $found[1]; + } + + return ''; + } + + /** + * Callback function for replacing [doc@anchor] links in bb code. + * + * @param array $found Array of preg matches + * + * @return string Replaced string + */ + public static function replaceDocLink(array $found) + { + if (count($found) >= 4) { + $page = $found[1]; + $anchor = $found[3]; + } else { + $anchor = $found[1]; + if (strncmp('faq', $anchor, 3) == 0) { + $page = 'faq'; + } elseif (strncmp('cfg', $anchor, 3) == 0) { + $page = 'config'; + } else { + /* Guess */ + $page = 'setup'; + } + } + $link = Util::getDocuLink($page, $anchor); + return ''; + } + + /** + * Sanitizes $message, taking into account our special codes + * for formatting. + * + * If you want to include result in element attribute, you should escape it. + * + * Examples: + * + *

    + * + *
    bar + * + * @param string $message the message + * @param boolean $escape whether to escape html in result + * @param boolean $safe whether string is safe (can keep < and > chars) + * + * @return string the sanitized message + */ + public static function sanitize($message, $escape = false, $safe = false) + { + if (!$safe) { + $message = strtr($message, array('<' => '<', '>' => '>')); + } + + /* Interpret bb code */ + $replace_pairs = array( + '[em]' => '', + '[/em]' => '', + '[strong]' => '', + '[/strong]' => '', + '[code]' => '', + '[/code]' => '', + '[kbd]' => '', + '[/kbd]' => '', + '[br]' => '
    ', + '[/a]' => '', + '[/doc]' => '', + '[sup]' => '', + '[/sup]' => '', + // used in common.inc.php: + '[conferr]' => '', + // used in libraries/Util.php + '[dochelpicon]' => Util::getImage('b_help', __('Documentation')), + ); + + $message = strtr($message, $replace_pairs); + + /* Match links in bb code ([a@url@target], where @target is options) */ + $pattern = '/\[a@([^]"@]*)(@([^]"]*))?\]/'; + + /* Find and replace all links */ + $message = preg_replace_callback($pattern, function($match){ + return self::replaceBBLink($match); + }, $message); + + /* Replace documentation links */ + $message = preg_replace_callback( + '/\[doc@([a-zA-Z0-9_-]+)(@([a-zA-Z0-9_-]*))?\]/', + function($match){ + return self::replaceDocLink($match); + }, + $message + ); + + /* Possibly escape result */ + if ($escape) { + $message = htmlspecialchars($message); + } + + return $message; + } + + + /** + * Sanitize a filename by removing anything besides legit characters + * + * Intended usecase: + * When using a filename in a Content-Disposition header + * the value should not contain ; or " + * + * When exporting, avoiding generation of an unexpected double-extension file + * + * @param string $filename The filename + * @param boolean $replaceDots Whether to also replace dots + * + * @return string the sanitized filename + * + */ + public static function sanitizeFilename($filename, $replaceDots = false) + { + $pattern = '/[^A-Za-z0-9_'; + // if we don't have to replace dots + if (! $replaceDots) { + // then add the dot to the list of legit characters + $pattern .= '.'; + } + $pattern .= '-]/'; + $filename = preg_replace($pattern, '_', $filename); + return $filename; + } + + /** + * Format a string so it can be a string inside JavaScript code inside an + * eventhandler (onclick, onchange, on..., ). + * This function is used to displays a javascript confirmation box for + * "DROP/DELETE/ALTER" queries. + * + * @param string $a_string the string to format + * @param boolean $add_backquotes whether to add backquotes to the string or not + * + * @return string the formatted string + * + * @access public + */ + public static function jsFormat($a_string = '', $add_backquotes = true) + { + $a_string = htmlspecialchars($a_string); + $a_string = self::escapeJsString($a_string); + // Needed for inline javascript to prevent some browsers + // treating it as a anchor + $a_string = str_replace('#', '\\#', $a_string); + + return $add_backquotes + ? Util::backquote($a_string) + : $a_string; + } // end of the 'jsFormat' function + + /** + * escapes a string to be inserted as string a JavaScript block + * enclosed by + * this requires only to escape ' with \' and end of script block + * + * We also remove NUL byte as some browsers (namely MSIE) ignore it and + * inserting it anywhere inside '', + '\\' => '\\\\', + '\'' => '\\\'', + '"' => '\"', + "\n" => '\n', + "\r" => '\r' + ) + ) + ); + } + + /** + * Formats a value for javascript code. + * + * @param string $value String to be formatted. + * + * @return string formatted value. + */ + public static function formatJsVal($value) + { + if (is_bool($value)) { + if ($value) { + return 'true'; + } + + return 'false'; + } + + if (is_int($value)) { + return (int)$value; + } + + return '"' . self::escapeJsString($value) . '"'; + } + + /** + * Formats an javascript assignment with proper escaping of a value + * and support for assigning array of strings. + * + * @param string $key Name of value to set + * @param mixed $value Value to set, can be either string or array of strings + * @param bool $escape Whether to escape value or keep it as it is + * (for inclusion of js code) + * + * @return string Javascript code. + */ + public static function getJsValue($key, $value, $escape = true) + { + $result = $key . ' = '; + if (!$escape) { + $result .= $value; + } elseif (is_array($value)) { + $result .= '['; + foreach ($value as $val) { + $result .= self::formatJsVal($val) . ","; + } + $result .= "];\n"; + } else { + $result .= self::formatJsVal($value) . ";\n"; + } + return $result; + } + + /** + * Prints an javascript assignment with proper escaping of a value + * and support for assigning array of strings. + * + * @param string $key Name of value to set + * @param mixed $value Value to set, can be either string or array of strings + * + * @return void + */ + public static function printJsValue($key, $value) + { + echo self::getJsValue($key, $value); + } + + /** + * Formats javascript assignment for form validation api + * with proper escaping of a value. + * + * @param string $key Name of value to set + * @param string $value Value to set + * @param boolean $addOn Check if $.validator.format is required or not + * @param boolean $comma Check if comma is required + * + * @return string Javascript code. + */ + public static function getJsValueForFormValidation($key, $value, $addOn, $comma) + { + $result = $key . ': '; + if ($addOn) { + $result .= '$.validator.format('; + } + $result .= self::formatJsVal($value); + if ($addOn) { + $result .= ')'; + } + if ($comma) { + $result .= ', '; + } + return $result; + } + + /** + * Prints javascript assignment for form validation api + * with proper escaping of a value. + * + * @param string $key Name of value to set + * @param string $value Value to set + * @param boolean $addOn Check if $.validator.format is required or not + * @param boolean $comma Check if comma is required + * + * @return void + */ + public static function printJsValueForFormValidation($key, $value, $addOn=false, $comma=true) + { + echo self::getJsValueForFormValidation($key, $value, $addOn, $comma); + } + + /** + * Removes all variables from request except whitelisted ones. + * + * @param string &$whitelist list of variables to allow + * + * @return void + * @access public + */ + public static function removeRequestVars(&$whitelist) + { + // do not check only $_REQUEST because it could have been overwritten + // and use type casting because the variables could have become + // strings + if (! isset($_REQUEST)) { + $_REQUEST = array(); + } + if (! isset($_GET)) { + $_GET = array(); + } + if (! isset($_POST)) { + $_POST = array(); + } + if (! isset($_COOKIE)) { + $_COOKIE = array(); + } + $keys = array_keys( + array_merge((array)$_REQUEST, (array)$_GET, (array)$_POST, (array)$_COOKIE) + ); + + foreach ($keys as $key) { + if (! in_array($key, $whitelist)) { + unset($_REQUEST[$key], $_GET[$key], $_POST[$key]); + continue; + } + + // allowed stuff could be compromised so escape it + // we require it to be a string + if (isset($_REQUEST[$key]) && ! is_string($_REQUEST[$key])) { + unset($_REQUEST[$key]); + } + if (isset($_POST[$key]) && ! is_string($_POST[$key])) { + unset($_POST[$key]); + } + if (isset($_COOKIE[$key]) && ! is_string($_COOKIE[$key])) { + unset($_COOKIE[$key]); + } + if (isset($_GET[$key]) && ! is_string($_GET[$key])) { + unset($_GET[$key]); + } + } + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/SavedSearches.php b/php/apps/phpmyadmin49/html/libraries/classes/SavedSearches.php new file mode 100644 index 00000000..968abf4a --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/SavedSearches.php @@ -0,0 +1,468 @@ +setConfig($config); + $this->relation = new Relation(); + } + + /** + * Setter of id + * + * @param int|null $searchId Id of search + * + * @return static + */ + public function setId($searchId) + { + $searchId = (int)$searchId; + if (empty($searchId)) { + $searchId = null; + } + + $this->_id = $searchId; + return $this; + } + + /** + * Getter of id + * + * @return int|null + */ + public function getId() + { + return $this->_id; + } + + /** + * Setter of searchName + * + * @param string $searchName Saved search name + * + * @return static + */ + public function setSearchName($searchName) + { + $this->_searchName = $searchName; + return $this; + } + + /** + * Getter of searchName + * + * @return string + */ + public function getSearchName() + { + return $this->_searchName; + } + + /** + * Setter of config + * + * @param array $config Global configuration + * + * @return static + */ + public function setConfig(array $config) + { + $this->_config = $config; + return $this; + } + + /** + * Getter of config + * + * @return array + */ + public function getConfig() + { + return $this->_config; + } + + /** + * Setter for criterias + * + * @param array|string $criterias Criterias of saved searches + * @param bool $json Criterias are in JSON format + * + * @return static + */ + public function setCriterias($criterias, $json = false) + { + if (true === $json && is_string($criterias)) { + $this->_criterias = json_decode($criterias, true); + return $this; + } + + $aListFieldsToGet = array( + 'criteriaColumn', + 'criteriaSort', + 'criteriaShow', + 'criteria', + 'criteriaAndOrRow', + 'criteriaAndOrColumn', + 'rows', + 'TableList' + ); + + $data = array(); + + $data['criteriaColumnCount'] = count($criterias['criteriaColumn']); + + foreach ($aListFieldsToGet as $field) { + if (isset($criterias[$field])) { + $data[$field] = $criterias[$field]; + } + } + + /* Limit amount of rows */ + if (!isset($data['rows'])) { + $data['rows'] = 0; + } else { + $data['rows'] = min( + max(0, intval($data['rows'])), + 100 + ); + } + + for ($i = 0; $i <= $data['rows']; $i++) { + $data['Or' . $i] = $criterias['Or' . $i]; + } + + $this->_criterias = $data; + return $this; + } + + /** + * Getter for criterias + * + * @return array + */ + public function getCriterias() + { + return $this->_criterias; + } + + /** + * Setter for username + * + * @param string $username Username + * + * @return static + */ + public function setUsername($username) + { + $this->_username = $username; + return $this; + } + + /** + * Getter for username + * + * @return string + */ + public function getUsername() + { + return $this->_username; + } + + /** + * Setter for DB name + * + * @param string $dbname DB name + * + * @return static + */ + public function setDbname($dbname) + { + $this->_dbname = $dbname; + return $this; + } + + /** + * Getter for DB name + * + * @return string + */ + public function getDbname() + { + return $this->_dbname; + } + + /** + * Save the search + * + * @return boolean + */ + public function save() + { + if (null == $this->getSearchName()) { + $message = Message::error( + __('Please provide a name for this bookmarked search.') + ); + $response = Response::getInstance(); + $response->setRequestStatus($message->isSuccess()); + $response->addJSON('fieldWithError', 'searchName'); + $response->addJSON('message', $message); + exit; + } + + if (null == $this->getUsername() + || null == $this->getDbname() + || null == $this->getSearchName() + || null == $this->getCriterias() + ) { + $message = Message::error( + __('Missing information to save the bookmarked search.') + ); + $response = Response::getInstance(); + $response->setRequestStatus($message->isSuccess()); + $response->addJSON('message', $message); + exit; + } + + $savedSearchesTbl + = Util::backquote($this->_config['cfgRelation']['db']) . "." + . Util::backquote($this->_config['cfgRelation']['savedsearches']); + + //If it's an insert. + if (null === $this->getId()) { + $wheres = array( + "search_name = '" . $GLOBALS['dbi']->escapeString($this->getSearchName()) + . "'" + ); + $existingSearches = $this->getList($wheres); + + if (!empty($existingSearches)) { + $message = Message::error( + __('An entry with this name already exists.') + ); + $response = Response::getInstance(); + $response->setRequestStatus($message->isSuccess()); + $response->addJSON('fieldWithError', 'searchName'); + $response->addJSON('message', $message); + exit; + } + + $sqlQuery = "INSERT INTO " . $savedSearchesTbl + . "(`username`, `db_name`, `search_name`, `search_data`)" + . " VALUES (" + . "'" . $GLOBALS['dbi']->escapeString($this->getUsername()) . "'," + . "'" . $GLOBALS['dbi']->escapeString($this->getDbname()) . "'," + . "'" . $GLOBALS['dbi']->escapeString($this->getSearchName()) . "'," + . "'" . $GLOBALS['dbi']->escapeString(json_encode($this->getCriterias())) + . "')"; + + $result = (bool) $this->relation->queryAsControlUser($sqlQuery); + if (!$result) { + return false; + } + + $this->setId($GLOBALS['dbi']->insertId()); + + return true; + } + + //Else, it's an update. + $wheres = array( + "id != " . $this->getId(), + "search_name = '" . $GLOBALS['dbi']->escapeString($this->getSearchName()) . "'" + ); + $existingSearches = $this->getList($wheres); + + if (!empty($existingSearches)) { + $message = Message::error( + __('An entry with this name already exists.') + ); + $response = Response::getInstance(); + $response->setRequestStatus($message->isSuccess()); + $response->addJSON('fieldWithError', 'searchName'); + $response->addJSON('message', $message); + exit; + } + + $sqlQuery = "UPDATE " . $savedSearchesTbl + . "SET `search_name` = '" + . $GLOBALS['dbi']->escapeString($this->getSearchName()) . "', " + . "`search_data` = '" + . $GLOBALS['dbi']->escapeString(json_encode($this->getCriterias())) . "' " + . "WHERE id = " . $this->getId(); + return (bool) $this->relation->queryAsControlUser($sqlQuery); + } + + /** + * Delete the search + * + * @return boolean + */ + public function delete() + { + if (null == $this->getId()) { + $message = Message::error( + __('Missing information to delete the search.') + ); + $response = Response::getInstance(); + $response->setRequestStatus($message->isSuccess()); + $response->addJSON('fieldWithError', 'searchId'); + $response->addJSON('message', $message); + exit; + } + + $savedSearchesTbl + = Util::backquote($this->_config['cfgRelation']['db']) . "." + . Util::backquote($this->_config['cfgRelation']['savedsearches']); + + $sqlQuery = "DELETE FROM " . $savedSearchesTbl + . "WHERE id = '" . $GLOBALS['dbi']->escapeString($this->getId()) . "'"; + + return (bool) $this->relation->queryAsControlUser($sqlQuery); + } + + /** + * Load the current search from an id. + * + * @return bool Success + */ + public function load() + { + if (null == $this->getId()) { + $message = Message::error( + __('Missing information to load the search.') + ); + $response = Response::getInstance(); + $response->setRequestStatus($message->isSuccess()); + $response->addJSON('fieldWithError', 'searchId'); + $response->addJSON('message', $message); + exit; + } + + $savedSearchesTbl = Util::backquote($this->_config['cfgRelation']['db']) + . "." + . Util::backquote($this->_config['cfgRelation']['savedsearches']); + $sqlQuery = "SELECT id, search_name, search_data " + . "FROM " . $savedSearchesTbl . " " + . "WHERE id = '" . $GLOBALS['dbi']->escapeString($this->getId()) . "' "; + + $resList = $this->relation->queryAsControlUser($sqlQuery); + + if (false === ($oneResult = $GLOBALS['dbi']->fetchArray($resList))) { + $message = Message::error(__('Error while loading the search.')); + $response = Response::getInstance(); + $response->setRequestStatus($message->isSuccess()); + $response->addJSON('fieldWithError', 'searchId'); + $response->addJSON('message', $message); + exit; + } + + $this->setSearchName($oneResult['search_name']) + ->setCriterias($oneResult['search_data'], true); + + return true; + } + + /** + * Get the list of saved searches of a user on a DB + * + * @param string[] $wheres List of filters + * + * @return array List of saved searches or empty array on failure + */ + public function getList(array $wheres = array()) + { + if (null == $this->getUsername() + || null == $this->getDbname() + ) { + return array(); + } + + $savedSearchesTbl = Util::backquote($this->_config['cfgRelation']['db']) + . "." + . Util::backquote($this->_config['cfgRelation']['savedsearches']); + $sqlQuery = "SELECT id, search_name " + . "FROM " . $savedSearchesTbl . " " + . "WHERE " + . "username = '" . $GLOBALS['dbi']->escapeString($this->getUsername()) . "' " + . "AND db_name = '" . $GLOBALS['dbi']->escapeString($this->getDbname()) . "' "; + + foreach ($wheres as $where) { + $sqlQuery .= "AND " . $where . " "; + } + + $sqlQuery .= "order by search_name ASC "; + + $resList = $this->relation->queryAsControlUser($sqlQuery); + + $list = array(); + while ($oneResult = $GLOBALS['dbi']->fetchArray($resList)) { + $list[$oneResult['id']] = $oneResult['search_name']; + } + + return $list; + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Scripts.php b/php/apps/phpmyadmin49/html/libraries/classes/Scripts.php new file mode 100644 index 00000000..481ebff1 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Scripts.php @@ -0,0 +1,214 @@ + PMA_VERSION)); + $result .= "\n"; + } else { + $result .= '' . "\n"; + } + } + return $result; + } + + /** + * Generates new Scripts objects + * + */ + public function __construct() + { + $this->_files = array(); + $this->_code = ''; + + } + + /** + * Adds a new file to the list of scripts + * + * @param string $filename The name of the file to include + * @param array $params Additional parameters to pass to the file + * + * @return void + */ + public function addFile( + $filename, + array $params = array() + ) { + $hash = md5($filename); + if (!empty($this->_files[$hash])) { + return; + } + + $has_onload = $this->_eventBlacklist($filename); + $this->_files[$hash] = array( + 'has_onload' => $has_onload, + 'filename' => $filename, + 'params' => $params, + ); + } + + /** + * Add new files to the list of scripts + * + * @param array $filelist The array of file names + * + * @return void + */ + public function addFiles(array $filelist) + { + foreach ($filelist as $filename) { + $this->addFile($filename); + } + } + + /** + * Determines whether to fire up an onload event for a file + * + * @param string $filename The name of the file to be checked + * against the blacklist + * + * @return int 1 to fire up the event, 0 not to + */ + private function _eventBlacklist($filename) + { + if (strpos($filename, 'jquery') !== false + || strpos($filename, 'codemirror') !== false + || strpos($filename, 'messages.php') !== false + || strpos($filename, 'ajax.js') !== false + || strpos($filename, 'cross_framing_protection.js') !== false + ) { + return 0; + } + + return 1; + } + + /** + * Adds a new code snippet to the code to be executed + * + * @param string $code The JS code to be added + * + * @return void + */ + public function addCode($code) + { + $this->_code .= "$code\n"; + } + + /** + * Returns a list with filenames and a flag to indicate + * whether to register onload events for this file + * + * @return array + */ + public function getFiles() + { + $retval = array(); + foreach ($this->_files as $file) { + //If filename contains a "?", continue. + if (strpos($file['filename'], "?") !== false) { + continue; + } + $retval[] = array( + 'name' => $file['filename'], + 'fire' => $file['has_onload'] + ); + + } + return $retval; + } + + /** + * Renders all the JavaScript file inclusions, code and events + * + * @return string + */ + public function getDisplay() + { + $retval = ''; + + if (count($this->_files) > 0) { + $retval .= $this->_includeFiles( + $this->_files + ); + } + + $code = 'AJAX.scriptHandler'; + foreach ($this->_files as $file) { + $code .= sprintf( + '.add("%s",%d)', + Sanitize::escapeJsString($file['filename']), + $file['has_onload'] ? 1 : 0 + ); + } + $code .= ';'; + $this->addCode($code); + + $code = '$(function() {'; + foreach ($this->_files as $file) { + if ($file['has_onload']) { + $code .= 'AJAX.fireOnload("'; + $code .= Sanitize::escapeJsString($file['filename']); + $code .= '");'; + } + } + $code .= '});'; + $this->addCode($code); + + $retval .= ''; + + return $retval; + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Server/Privileges.php b/php/apps/phpmyadmin49/html/libraries/classes/Server/Privileges.php new file mode 100644 index 00000000..c731bdcc --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Server/Privileges.php @@ -0,0 +1,5438 @@ +isAjax()) { + $response->addJSON('message', $dialog); + exit; + } else { + $html .= $dialog; + } + } + + return $html; + } + + /** + * Escapes wildcard in a database+table specification + * before using it in a GRANT statement. + * + * Escaping a wildcard character in a GRANT is only accepted at the global + * or database level, not at table level; this is why I remove + * the escaping character. Internally, in mysql.tables_priv.Db there are + * no escaping (for example test_db) but in mysql.db you'll see test\_db + * for a db-specific privilege. + * + * @param string $dbname Database name + * @param string $tablename Table name + * + * @return string the escaped (if necessary) database.table + */ + public static function wildcardEscapeForGrant($dbname, $tablename) + { + if (strlen($dbname) === 0) { + $db_and_table = '*.*'; + } else { + if (strlen($tablename) > 0) { + $db_and_table = Util::backquote( + Util::unescapeMysqlWildcards($dbname) + ) + . '.' . Util::backquote($tablename); + } else { + $db_and_table = Util::backquote($dbname) . '.*'; + } + } + return $db_and_table; + } + + /** + * Generates a condition on the user name + * + * @param string $initial the user's initial + * + * @return string the generated condition + */ + public static function rangeOfUsers($initial = '') + { + // strtolower() is used because the User field + // might be BINARY, so LIKE would be case sensitive + if ($initial === null || $initial === '') { + return ''; + } + + $ret = " WHERE `User` LIKE '" + . $GLOBALS['dbi']->escapeString($initial) . "%'" + . " OR `User` LIKE '" + . $GLOBALS['dbi']->escapeString(mb_strtolower($initial)) + . "%'"; + return $ret; + } // end function + + /** + * Formats privilege name for a display + * + * @param array $privilege Privilege information + * @param boolean $html Whether to use HTML + * + * @return string + */ + public static function formatPrivilege(array $privilege, $html) + { + if ($html) { + return '' + . $privilege[1] . ''; + } + + return $privilege[1]; + } + + /** + * Parses privileges into an array, it modifies the array + * + * @param array &$row Results row from + * + * @return void + */ + public static function fillInTablePrivileges(array &$row) + { + $row1 = $GLOBALS['dbi']->fetchSingleRow( + 'SHOW COLUMNS FROM `mysql`.`tables_priv` LIKE \'Table_priv\';', + 'ASSOC' + ); + // note: in MySQL 5.0.3 we get "Create View', 'Show view'; + // the View for Create is spelled with uppercase V + // the view for Show is spelled with lowercase v + // and there is a space between the words + + $av_grants = explode( + '\',\'', + mb_substr( + $row1['Type'], + mb_strpos($row1['Type'], '(') + 2, + mb_strpos($row1['Type'], ')') + - mb_strpos($row1['Type'], '(') - 3 + ) + ); + + $users_grants = explode(',', $row['Table_priv']); + + foreach ($av_grants as $current_grant) { + $row[$current_grant . '_priv'] + = in_array($current_grant, $users_grants) ? 'Y' : 'N'; + } + unset($row['Table_priv']); + } + + + /** + * Extracts the privilege information of a priv table row + * + * @param array|null $row the row + * @param boolean $enableHTML add tag with tooltips + * @param boolean $tablePrivs whether row contains table privileges + * + * @global resource $user_link the database connection + * + * @return array + */ + public static function extractPrivInfo($row = null, $enableHTML = false, $tablePrivs = false) + { + if ($tablePrivs) { + $grants = self::getTableGrantsArray(); + } else { + $grants = self::getGrantsArray(); + } + + if (! is_null($row) && isset($row['Table_priv'])) { + self::fillInTablePrivileges($row); + } + + $privs = array(); + $allPrivileges = true; + foreach ($grants as $current_grant) { + if ((! is_null($row) && isset($row[$current_grant[0]])) + || (is_null($row) && isset($GLOBALS[$current_grant[0]])) + ) { + if ((! is_null($row) && $row[$current_grant[0]] == 'Y') + || (is_null($row) + && ($GLOBALS[$current_grant[0]] == 'Y' + || (is_array($GLOBALS[$current_grant[0]]) + && count($GLOBALS[$current_grant[0]]) == $_REQUEST['column_count'] + && empty($GLOBALS[$current_grant[0] . '_none'])))) + ) { + $privs[] = self::formatPrivilege($current_grant, $enableHTML); + } elseif (! empty($GLOBALS[$current_grant[0]]) + && is_array($GLOBALS[$current_grant[0]]) + && empty($GLOBALS[$current_grant[0] . '_none']) + ) { + // Required for proper escaping of ` (backtick) in a column name + $grant_cols = array_map( + function($val) { + return Util::backquote($val); + }, + $GLOBALS[$current_grant[0]] + ); + + $privs[] = self::formatPrivilege($current_grant, $enableHTML) + . ' (' . join(', ', $grant_cols) . ')'; + } else { + $allPrivileges = false; + } + } + } + if (empty($privs)) { + if ($enableHTML) { + $privs[] = 'USAGE'; + } else { + $privs[] = 'USAGE'; + } + } elseif ($allPrivileges + && (! isset($_POST['grant_count']) || count($privs) == $_POST['grant_count']) + ) { + if ($enableHTML) { + $privs = array('ALL PRIVILEGES' + ); + } else { + $privs = array('ALL PRIVILEGES'); + } + } + return $privs; + } // end of the 'self::extractPrivInfo()' function + + /** + * Returns an array of table grants and their descriptions + * + * @return array array of table grants + */ + public static function getTableGrantsArray() + { + return array( + array( + 'Delete', + 'DELETE', + $GLOBALS['strPrivDescDelete'] + ), + array( + 'Create', + 'CREATE', + $GLOBALS['strPrivDescCreateTbl'] + ), + array( + 'Drop', + 'DROP', + $GLOBALS['strPrivDescDropTbl'] + ), + array( + 'Index', + 'INDEX', + $GLOBALS['strPrivDescIndex'] + ), + array( + 'Alter', + 'ALTER', + $GLOBALS['strPrivDescAlter'] + ), + array( + 'Create View', + 'CREATE_VIEW', + $GLOBALS['strPrivDescCreateView'] + ), + array( + 'Show view', + 'SHOW_VIEW', + $GLOBALS['strPrivDescShowView'] + ), + array( + 'Trigger', + 'TRIGGER', + $GLOBALS['strPrivDescTrigger'] + ), + ); + } + + /** + * Get the grants array which contains all the privilege types + * and relevant grant messages + * + * @return array + */ + public static function getGrantsArray() + { + return array( + array( + 'Select_priv', + 'SELECT', + __('Allows reading data.') + ), + array( + 'Insert_priv', + 'INSERT', + __('Allows inserting and replacing data.') + ), + array( + 'Update_priv', + 'UPDATE', + __('Allows changing data.') + ), + array( + 'Delete_priv', + 'DELETE', + __('Allows deleting data.') + ), + array( + 'Create_priv', + 'CREATE', + __('Allows creating new databases and tables.') + ), + array( + 'Drop_priv', + 'DROP', + __('Allows dropping databases and tables.') + ), + array( + 'Reload_priv', + 'RELOAD', + __('Allows reloading server settings and flushing the server\'s caches.') + ), + array( + 'Shutdown_priv', + 'SHUTDOWN', + __('Allows shutting down the server.') + ), + array( + 'Process_priv', + 'PROCESS', + __('Allows viewing processes of all users.') + ), + array( + 'File_priv', + 'FILE', + __('Allows importing data from and exporting data into files.') + ), + array( + 'References_priv', + 'REFERENCES', + __('Has no effect in this MySQL version.') + ), + array( + 'Index_priv', + 'INDEX', + __('Allows creating and dropping indexes.') + ), + array( + 'Alter_priv', + 'ALTER', + __('Allows altering the structure of existing tables.') + ), + array( + 'Show_db_priv', + 'SHOW DATABASES', + __('Gives access to the complete list of databases.') + ), + array( + 'Super_priv', + 'SUPER', + __( + 'Allows connecting, even if maximum number of connections ' + . 'is reached; required for most administrative operations ' + . 'like setting global variables or killing threads of other users.' + ) + ), + array( + 'Create_tmp_table_priv', + 'CREATE TEMPORARY TABLES', + __('Allows creating temporary tables.') + ), + array( + 'Lock_tables_priv', + 'LOCK TABLES', + __('Allows locking tables for the current thread.') + ), + array( + 'Repl_slave_priv', + 'REPLICATION SLAVE', + __('Needed for the replication slaves.') + ), + array( + 'Repl_client_priv', + 'REPLICATION CLIENT', + __('Allows the user to ask where the slaves / masters are.') + ), + array( + 'Create_view_priv', + 'CREATE VIEW', + __('Allows creating new views.') + ), + array( + 'Event_priv', + 'EVENT', + __('Allows to set up events for the event scheduler.') + ), + array( + 'Trigger_priv', + 'TRIGGER', + __('Allows creating and dropping triggers.') + ), + // for table privs: + array( + 'Create View_priv', + 'CREATE VIEW', + __('Allows creating new views.') + ), + array( + 'Show_view_priv', + 'SHOW VIEW', + __('Allows performing SHOW CREATE VIEW queries.') + ), + // for table privs: + array( + 'Show view_priv', + 'SHOW VIEW', + __('Allows performing SHOW CREATE VIEW queries.') + ), + array( + 'Delete_history_priv', + 'DELETE HISTORY', + $GLOBALS['strPrivDescDeleteHistoricalRows'] + ), + array( + 'Delete versioning rows_priv', + 'DELETE HISTORY', + $GLOBALS['strPrivDescDeleteHistoricalRows'] + ), + array( + 'Create_routine_priv', + 'CREATE ROUTINE', + __('Allows creating stored routines.') + ), + array( + 'Alter_routine_priv', + 'ALTER ROUTINE', + __('Allows altering and dropping stored routines.') + ), + array( + 'Create_user_priv', + 'CREATE USER', + __('Allows creating, dropping and renaming user accounts.') + ), + array( + 'Execute_priv', + 'EXECUTE', + __('Allows executing stored routines.') + ), + ); + } + + /** + * Displays on which column(s) a table-specific privilege is granted + * + * @param array $columns columns array + * @param array $row first row from result or boolean false + * @param string $name_for_select privilege types - Select_priv, Insert_priv + * Update_priv, References_priv + * @param string $priv_for_header privilege for header + * @param string $name privilege name: insert, select, update, references + * @param string $name_for_dfn name for dfn + * @param string $name_for_current name for current + * + * @return string $html_output html snippet + */ + public static function getHtmlForColumnPrivileges(array $columns, array $row, $name_for_select, + $priv_for_header, $name, $name_for_dfn, $name_for_current + ) { + $data = array( + 'columns' => $columns, + 'row' => $row, + 'name_for_select' => $name_for_select, + 'priv_for_header' => $priv_for_header, + 'name' => $name, + 'name_for_dfn' => $name_for_dfn, + 'name_for_current' => $name_for_current + ); + + $html_output = Template::get('privileges/column_privileges') + ->render($data); + + return $html_output; + } // end function + + /** + * Get sql query for display privileges table + * + * @param string $db the database + * @param string $table the table + * @param string $username username for database connection + * @param string $hostname hostname for database connection + * + * @return string sql query + */ + public static function getSqlQueryForDisplayPrivTable($db, $table, $username, $hostname) + { + if ($db == '*') { + return "SELECT * FROM `mysql`.`user`" + . " WHERE `User` = '" . $GLOBALS['dbi']->escapeString($username) . "'" + . " AND `Host` = '" . $GLOBALS['dbi']->escapeString($hostname) . "';"; + } elseif ($table == '*') { + return "SELECT * FROM `mysql`.`db`" + . " WHERE `User` = '" . $GLOBALS['dbi']->escapeString($username) . "'" + . " AND `Host` = '" . $GLOBALS['dbi']->escapeString($hostname) . "'" + . " AND '" . $GLOBALS['dbi']->escapeString(Util::unescapeMysqlWildcards($db)) . "'" + . " LIKE `Db`;"; + } + return "SELECT `Table_priv`" + . " FROM `mysql`.`tables_priv`" + . " WHERE `User` = '" . $GLOBALS['dbi']->escapeString($username) . "'" + . " AND `Host` = '" . $GLOBALS['dbi']->escapeString($hostname) . "'" + . " AND `Db` = '" . $GLOBALS['dbi']->escapeString(Util::unescapeMysqlWildcards($db)) . "'" + . " AND `Table_name` = '" . $GLOBALS['dbi']->escapeString($table) . "';"; + } + + /** + * Displays a dropdown to select the user group + * with menu items configured to each of them. + * + * @param string $username username + * + * @return string html to select the user group + */ + public static function getHtmlToChooseUserGroup($username) + { + $relation = new Relation(); + $cfgRelation = $relation->getRelationsParam(); + $groupTable = Util::backquote($cfgRelation['db']) + . "." . Util::backquote($cfgRelation['usergroups']); + $userTable = Util::backquote($cfgRelation['db']) + . "." . Util::backquote($cfgRelation['users']); + + $userGroup = ''; + if (isset($GLOBALS['username'])) { + $sql_query = "SELECT `usergroup` FROM " . $userTable + . " WHERE `username` = '" . $GLOBALS['dbi']->escapeString($username) . "'"; + $userGroup = $GLOBALS['dbi']->fetchValue( + $sql_query, 0, 0, DatabaseInterface::CONNECT_CONTROL + ); + } + + $allUserGroups = array('' => ''); + $sql_query = "SELECT DISTINCT `usergroup` FROM " . $groupTable; + $result = $relation->queryAsControlUser($sql_query, false); + if ($result) { + while ($row = $GLOBALS['dbi']->fetchRow($result)) { + $allUserGroups[$row[0]] = $row[0]; + } + } + $GLOBALS['dbi']->freeResult($result); + + // render the template + $data = array( + 'all_user_groups' => $allUserGroups, + 'user_group' => $userGroup, + 'params' => array('username' => $username) + ); + $html_output = Template::get('privileges/choose_user_group') + ->render($data); + + return $html_output; + } + + /** + * Sets the user group from request values + * + * @param string $username username + * @param string $userGroup user group to set + * + * @return void + */ + public static function setUserGroup($username, $userGroup) + { + $relation = new Relation(); + $cfgRelation = $relation->getRelationsParam(); + if (empty($cfgRelation['db']) || empty($cfgRelation['users']) || empty($cfgRelation['usergroups'])) { + return; + } + + $userTable = Util::backquote($cfgRelation['db']) + . "." . Util::backquote($cfgRelation['users']); + + $sql_query = "SELECT `usergroup` FROM " . $userTable + . " WHERE `username` = '" . $GLOBALS['dbi']->escapeString($username) . "'"; + $oldUserGroup = $GLOBALS['dbi']->fetchValue( + $sql_query, 0, 0, DatabaseInterface::CONNECT_CONTROL + ); + + if ($oldUserGroup === false) { + $upd_query = "INSERT INTO " . $userTable . "(`username`, `usergroup`)" + . " VALUES ('" . $GLOBALS['dbi']->escapeString($username) . "', " + . "'" . $GLOBALS['dbi']->escapeString($userGroup) . "')"; + } else { + if (empty($userGroup)) { + $upd_query = "DELETE FROM " . $userTable + . " WHERE `username`='" . $GLOBALS['dbi']->escapeString($username) . "'"; + } elseif ($oldUserGroup != $userGroup) { + $upd_query = "UPDATE " . $userTable + . " SET `usergroup`='" . $GLOBALS['dbi']->escapeString($userGroup) . "'" + . " WHERE `username`='" . $GLOBALS['dbi']->escapeString($username) . "'"; + } + } + if (isset($upd_query)) { + $relation->queryAsControlUser($upd_query); + } + } + + /** + * Displays the privileges form table + * + * @param string $db the database + * @param string $table the table + * @param boolean $submit whether to display the submit button or not + * + * @global array $cfg the phpMyAdmin configuration + * @global resource $user_link the database connection + * + * @return string html snippet + */ + public static function getHtmlToDisplayPrivilegesTable($db = '*', + $table = '*', $submit = true + ) { + $html_output = ''; + $sql_query = ''; + + if ($db == '*') { + $table = '*'; + } + + if (isset($GLOBALS['username'])) { + $username = $GLOBALS['username']; + $hostname = $GLOBALS['hostname']; + $sql_query = self::getSqlQueryForDisplayPrivTable( + $db, $table, $username, $hostname + ); + $row = $GLOBALS['dbi']->fetchSingleRow($sql_query); + } + if (empty($row)) { + if ($table == '*' && $GLOBALS['dbi']->isSuperuser()) { + $row = array(); + if ($db == '*') { + $sql_query = 'SHOW COLUMNS FROM `mysql`.`user`;'; + } elseif ($table == '*') { + $sql_query = 'SHOW COLUMNS FROM `mysql`.`db`;'; + } + $res = $GLOBALS['dbi']->query($sql_query); + while ($row1 = $GLOBALS['dbi']->fetchRow($res)) { + if (mb_substr($row1[0], 0, 4) == 'max_') { + $row[$row1[0]] = 0; + } elseif (mb_substr($row1[0], 0, 5) == 'x509_' + || mb_substr($row1[0], 0, 4) == 'ssl_' + ) { + $row[$row1[0]] = ''; + } else { + $row[$row1[0]] = 'N'; + } + } + $GLOBALS['dbi']->freeResult($res); + } elseif ($table == '*') { + $row = array(); + } else { + $row = array('Table_priv' => ''); + } + } + if (isset($row['Table_priv'])) { + self::fillInTablePrivileges($row); + + // get columns + $res = $GLOBALS['dbi']->tryQuery( + 'SHOW COLUMNS FROM ' + . Util::backquote( + Util::unescapeMysqlWildcards($db) + ) + . '.' . Util::backquote($table) . ';' + ); + $columns = array(); + if ($res) { + while ($row1 = $GLOBALS['dbi']->fetchRow($res)) { + $columns[$row1[0]] = array( + 'Select' => false, + 'Insert' => false, + 'Update' => false, + 'References' => false + ); + } + $GLOBALS['dbi']->freeResult($res); + } + unset($res, $row1); + } + // table-specific privileges + if (! empty($columns)) { + $html_output .= self::getHtmlForTableSpecificPrivileges( + $username, $hostname, $db, $table, $columns, $row + ); + } else { + // global or db-specific + $html_output .= self::getHtmlForGlobalOrDbSpecificPrivs($db, $table, $row); + } + $html_output .= '' . "\n"; + if ($submit) { + $html_output .= '' . "\n"; + } + return $html_output; + } // end of the 'PMA_displayPrivTable()' function + + /** + * Get HTML for "Require" + * + * @param array $row privilege array + * + * @return string html snippet + */ + public static function getHtmlForRequires(array $row) + { + $specified = (isset($row['ssl_type']) && $row['ssl_type'] == 'SPECIFIED'); + $require_options = array( + array( + 'name' => 'ssl_type', + 'value' => 'NONE', + 'description' => __( + 'Does not require SSL-encrypted connections.' + ), + 'label' => 'REQUIRE NONE', + 'checked' => ((isset($row['ssl_type']) + && ($row['ssl_type'] == 'NONE' + || $row['ssl_type'] == '')) + ? 'checked="checked"' + : '' + ), + 'disabled' => false, + 'radio' => true + ), + array( + 'name' => 'ssl_type', + 'value' => 'ANY', + 'description' => __( + 'Requires SSL-encrypted connections.' + ), + 'label' => 'REQUIRE SSL', + 'checked' => (isset($row['ssl_type']) && ($row['ssl_type'] == 'ANY') + ? 'checked="checked"' + : '' + ), + 'disabled' => false, + 'radio' => true + ), + array( + 'name' => 'ssl_type', + 'value' => 'X509', + 'description' => __( + 'Requires a valid X509 certificate.' + ), + 'label' => 'REQUIRE X509', + 'checked' => (isset($row['ssl_type']) && ($row['ssl_type'] == 'X509') + ? 'checked="checked"' + : '' + ), + 'disabled' => false, + 'radio' => true + ), + array( + 'name' => 'ssl_type', + 'value' => 'SPECIFIED', + 'description' => '', + 'label' => 'SPECIFIED', + 'checked' => ($specified ? 'checked="checked"' : ''), + 'disabled' => false, + 'radio' => true + ), + array( + 'name' => 'ssl_cipher', + 'value' => (isset($row['ssl_cipher']) + ? htmlspecialchars($row['ssl_cipher']) : '' + ), + 'description' => __( + 'Requires that a specific cipher method be used for a connection.' + ), + 'label' => 'REQUIRE CIPHER', + 'checked' => '', + 'disabled' => ! $specified, + 'radio' => false + ), + array( + 'name' => 'x509_issuer', + 'value' => (isset($row['x509_issuer']) + ? htmlspecialchars($row['x509_issuer']) : '' + ), + 'description' => __( + 'Requires that a valid X509 certificate issued by this CA be presented.' + ), + 'label' => 'REQUIRE ISSUER', + 'checked' => '', + 'disabled' => ! $specified, + 'radio' => false + ), + array( + 'name' => 'x509_subject', + 'value' => (isset($row['x509_subject']) + ? htmlspecialchars($row['x509_subject']) : '' + ), + 'description' => __( + 'Requires that a valid X509 certificate with this subject be presented.' + ), + 'label' => 'REQUIRE SUBJECT', + 'checked' => '', + 'disabled' => ! $specified, + 'radio' => false + ), + ); + + $html_output = Template::get('privileges/require_options') + ->render(array('require_options' => $require_options)); + + return $html_output; + } + + /** + * Get HTML for "Resource limits" + * + * @param array $row first row from result or boolean false + * + * @return string html snippet + */ + public static function getHtmlForResourceLimits(array $row) + { + $limits = array( + array( + 'input_name' => 'max_questions', + 'name_main' => 'MAX QUERIES PER HOUR', + 'value' => (isset($row['max_questions']) ? $row['max_questions'] : '0'), + 'description' => __( + 'Limits the number of queries the user may send to the server per hour.' + ) + ), + array( + 'input_name' => 'max_updates', + 'name_main' => 'MAX UPDATES PER HOUR', + 'value' => (isset($row['max_updates']) ? $row['max_updates'] : '0'), + 'description' => __( + 'Limits the number of commands that change any table ' + . 'or database the user may execute per hour.' + ) + ), + array( + 'input_name' => 'max_connections', + 'name_main' => 'MAX CONNECTIONS PER HOUR', + 'value' => (isset($row['max_connections']) ? $row['max_connections'] : '0'), + 'description' => __( + 'Limits the number of new connections the user may open per hour.' + ) + ), + array( + 'input_name' => 'max_user_connections', + 'name_main' => 'MAX USER_CONNECTIONS', + 'value' => (isset($row['max_user_connections']) ? + $row['max_user_connections'] : '0'), + 'description' => __( + 'Limits the number of simultaneous connections ' + . 'the user may have.' + ) + ) + ); + + return Template::get('privileges/resource_limits') + ->render(array('limits' => $limits)); + } + + /** + * Get the HTML snippet for routine specific privileges + * + * @param string $username username for database connection + * @param string $hostname hostname for database connection + * @param string $db the database + * @param string $routine the routine + * @param string $url_dbname url encoded db name + * + * @return string $html_output + */ + public static function getHtmlForRoutineSpecificPrivileges( + $username, $hostname, $db, $routine, $url_dbname + ) { + $header = self::getHtmlHeaderForUserProperties( + false, $url_dbname, $db, $username, $hostname, + $routine, 'routine' + ); + + $sql = "SELECT `Proc_priv`" + . " FROM `mysql`.`procs_priv`" + . " WHERE `User` = '" . $GLOBALS['dbi']->escapeString($username) . "'" + . " AND `Host` = '" . $GLOBALS['dbi']->escapeString($hostname) . "'" + . " AND `Db` = '" + . $GLOBALS['dbi']->escapeString(Util::unescapeMysqlWildcards($db)) . "'" + . " AND `Routine_name` LIKE '" . $GLOBALS['dbi']->escapeString($routine) . "';"; + $res = $GLOBALS['dbi']->fetchValue($sql); + + $privs = self::parseProcPriv($res); + + $routineArray = array(self::getTriggerPrivilegeTable()); + $privTableNames = array(__('Routine')); + $privCheckboxes = self::getHtmlForGlobalPrivTableWithCheckboxes( + $routineArray, $privTableNames, $privs + ); + + $data = array( + 'username' => $username, + 'hostname' => $hostname, + 'database' => $db, + 'routine' => $routine, + 'grant_count' => count($privs), + 'priv_checkboxes' => $privCheckboxes, + 'header' => $header, + ); + $html_output = Template::get('privileges/edit_routine_privileges') + ->render($data); + + return $html_output; + } + + /** + * Get routine privilege table as an array + * + * @return privilege type array + */ + public static function getTriggerPrivilegeTable() + { + $routinePrivTable = array( + array( + 'Grant', + 'GRANT', + __( + 'Allows user to give to other users or remove from other users ' + . 'privileges that user possess on this routine.' + ) + ), + array( + 'Alter_routine', + 'ALTER ROUTINE', + __('Allows altering and dropping this routine.') + ), + array( + 'Execute', + 'EXECUTE', + __('Allows executing this routine.') + ) + ); + return $routinePrivTable; + } + + /** + * Get the HTML snippet for table specific privileges + * + * @param string $username username for database connection + * @param string $hostname hostname for database connection + * @param string $db the database + * @param string $table the table + * @param array $columns columns array + * @param array $row current privileges row + * + * @return string $html_output + */ + public static function getHtmlForTableSpecificPrivileges( + $username, $hostname, $db, $table, array $columns, array $row + ) { + $res = $GLOBALS['dbi']->query( + 'SELECT `Column_name`, `Column_priv`' + . ' FROM `mysql`.`columns_priv`' + . ' WHERE `User`' + . ' = \'' . $GLOBALS['dbi']->escapeString($username) . "'" + . ' AND `Host`' + . ' = \'' . $GLOBALS['dbi']->escapeString($hostname) . "'" + . ' AND `Db`' + . ' = \'' . $GLOBALS['dbi']->escapeString( + Util::unescapeMysqlWildcards($db) + ) . "'" + . ' AND `Table_name`' + . ' = \'' . $GLOBALS['dbi']->escapeString($table) . '\';' + ); + + while ($row1 = $GLOBALS['dbi']->fetchRow($res)) { + $row1[1] = explode(',', $row1[1]); + foreach ($row1[1] as $current) { + $columns[$row1[0]][$current] = true; + } + } + $GLOBALS['dbi']->freeResult($res); + unset($res, $row1, $current); + + $html_output = '' . "\n" + . '' . "\n" + . '
    ' . "\n" + . '' . __('Table-specific privileges') + . '' + . '

    ' + . __('Note: MySQL privilege names are expressed in English.') + . '

    '; + + // privs that are attached to a specific column + $html_output .= self::getHtmlForAttachedPrivilegesToTableSpecificColumn( + $columns, $row + ); + + // privs that are not attached to a specific column + $html_output .= '
    ' . "\n" + . self::getHtmlForNotAttachedPrivilegesToTableSpecificColumn($row) + . '
    ' . "\n"; + + // for Safari 2.0.2 + $html_output .= '
    ' . "\n"; + + return $html_output; + } + + /** + * Get HTML snippet for privileges that are attached to a specific column + * + * @param array $columns columns array + * @param array $row first row from result or boolean false + * + * @return string $html_output + */ + public static function getHtmlForAttachedPrivilegesToTableSpecificColumn(array $columns, array $row) + { + $html_output = self::getHtmlForColumnPrivileges( + $columns, $row, 'Select_priv', 'SELECT', + 'select', __('Allows reading data.'), 'Select' + ); + + $html_output .= self::getHtmlForColumnPrivileges( + $columns, $row, 'Insert_priv', 'INSERT', + 'insert', __('Allows inserting and replacing data.'), 'Insert' + ); + + $html_output .= self::getHtmlForColumnPrivileges( + $columns, $row, 'Update_priv', 'UPDATE', + 'update', __('Allows changing data.'), 'Update' + ); + + $html_output .= self::getHtmlForColumnPrivileges( + $columns, $row, 'References_priv', 'REFERENCES', 'references', + __('Has no effect in this MySQL version.'), 'References' + ); + return $html_output; + } + + /** + * Get HTML for privileges that are not attached to a specific column + * + * @param array $row first row from result or boolean false + * + * @return string $html_output + */ + public static function getHtmlForNotAttachedPrivilegesToTableSpecificColumn(array $row) + { + $html_output = ''; + + foreach ($row as $current_grant => $current_grant_value) { + $grant_type = substr($current_grant, 0, -5); + if (in_array($grant_type, array('Select', 'Insert', 'Update', 'References')) + ) { + continue; + } + // make a substitution to match the messages variables; + // also we must substitute the grant we get, because we can't generate + // a form variable containing blanks (those would get changed to + // an underscore when receiving the POST) + if ($current_grant == 'Create View_priv') { + $tmp_current_grant = 'CreateView_priv'; + $current_grant = 'Create_view_priv'; + } elseif ($current_grant == 'Show view_priv') { + $tmp_current_grant = 'ShowView_priv'; + $current_grant = 'Show_view_priv'; + } elseif ($current_grant == 'Delete versioning rows_priv') { + $tmp_current_grant = 'DeleteHistoricalRows_priv'; + $current_grant = 'Delete_history_priv'; + } else { + $tmp_current_grant = $current_grant; + } + + $html_output .= '
    ' . "\n" + . '' . "\n"; + + $privGlobalName1 = 'strPrivDesc' + . mb_substr( + $tmp_current_grant, + 0, + - 5 + ); + $html_output .= '' . "\n" + . '
    ' . "\n"; + } // end foreach () + return $html_output; + } + + /** + * Get HTML for global or database specific privileges + * + * @param string $db the database + * @param string $table the table + * @param array $row first row from result or boolean false + * + * @return string $html_output + */ + public static function getHtmlForGlobalOrDbSpecificPrivs($db, $table, array $row) + { + $privTable_names = array(0 => __('Data'), + 1 => __('Structure'), + 2 => __('Administration') + ); + $privTable = array(); + // d a t a + $privTable[0] = self::getDataPrivilegeTable($db); + + // s t r u c t u r e + $privTable[1] = self::getStructurePrivilegeTable($table, $row); + + // a d m i n i s t r a t i o n + $privTable[2] = self::getAdministrationPrivilegeTable($db); + + $html_output = ''; + if ($db == '*') { + $legend = __('Global privileges'); + $menu_label = __('Global'); + } elseif ($table == '*') { + $legend = __('Database-specific privileges'); + $menu_label = __('Database'); + } else { + $legend = __('Table-specific privileges'); + $menu_label = __('Table'); + } + $html_output .= '
    ' + . '' . $legend + . ' ' + . ' ' + . '' + . '

    ' + . __('Note: MySQL privilege names are expressed in English.') + . '

    '; + + // Output the Global privilege tables with checkboxes + $html_output .= self::getHtmlForGlobalPrivTableWithCheckboxes( + $privTable, $privTable_names, $row + ); + + // The "Resource limits" box is not displayed for db-specific privs + if ($db == '*') { + $html_output .= self::getHtmlForResourceLimits($row); + $html_output .= self::getHtmlForRequires($row); + } + // for Safari 2.0.2 + $html_output .= '
    '; + + return $html_output; + } + + /** + * Get data privilege table as an array + * + * @param string $db the database + * + * @return string data privilege table + */ + public static function getDataPrivilegeTable($db) + { + $data_privTable = array( + array('Select', 'SELECT', __('Allows reading data.')), + array('Insert', 'INSERT', __('Allows inserting and replacing data.')), + array('Update', 'UPDATE', __('Allows changing data.')), + array('Delete', 'DELETE', __('Allows deleting data.')) + ); + if ($db == '*') { + $data_privTable[] + = array('File', + 'FILE', + __('Allows importing data from and exporting data into files.') + ); + } + return $data_privTable; + } + + /** + * Get structure privilege table as an array + * + * @param string $table the table + * @param array $row first row from result or boolean false + * + * @return string structure privilege table + */ + public static function getStructurePrivilegeTable($table, array $row) + { + $structure_privTable = array( + array('Create', + 'CREATE', + ($table == '*' + ? __('Allows creating new databases and tables.') + : __('Allows creating new tables.') + ) + ), + array('Alter', + 'ALTER', + __('Allows altering the structure of existing tables.') + ), + array('Index', 'INDEX', __('Allows creating and dropping indexes.')), + array('Drop', + 'DROP', + ($table == '*' + ? __('Allows dropping databases and tables.') + : __('Allows dropping tables.') + ) + ), + array('Create_tmp_table', + 'CREATE TEMPORARY TABLES', + __('Allows creating temporary tables.') + ), + array('Show_view', + 'SHOW VIEW', + __('Allows performing SHOW CREATE VIEW queries.') + ), + array('Create_routine', + 'CREATE ROUTINE', + __('Allows creating stored routines.') + ), + array('Alter_routine', + 'ALTER ROUTINE', + __('Allows altering and dropping stored routines.') + ), + array('Execute', 'EXECUTE', __('Allows executing stored routines.')), + ); + // this one is for a db-specific priv: Create_view_priv + if (isset($row['Create_view_priv'])) { + $structure_privTable[] = array('Create_view', + 'CREATE VIEW', + __('Allows creating new views.') + ); + } + // this one is for a table-specific priv: Create View_priv + if (isset($row['Create View_priv'])) { + $structure_privTable[] = array('Create View', + 'CREATE VIEW', + __('Allows creating new views.') + ); + } + if (isset($row['Event_priv'])) { + // MySQL 5.1.6 + $structure_privTable[] = array('Event', + 'EVENT', + __('Allows to set up events for the event scheduler.') + ); + $structure_privTable[] = array('Trigger', + 'TRIGGER', + __('Allows creating and dropping triggers.') + ); + } + return $structure_privTable; + } + + /** + * Get administration privilege table as an array + * + * @param string $db the table + * + * @return string administration privilege table + */ + public static function getAdministrationPrivilegeTable($db) + { + if ($db == '*') { + $adminPrivTable = array( + array('Grant', + 'GRANT', + __( + 'Allows adding users and privileges ' + . 'without reloading the privilege tables.' + ) + ), + ); + $adminPrivTable[] = array('Super', + 'SUPER', + __( + 'Allows connecting, even if maximum number ' + . 'of connections is reached; required for ' + . 'most administrative operations like ' + . 'setting global variables or killing threads of other users.' + ) + ); + $adminPrivTable[] = array('Process', + 'PROCESS', + __('Allows viewing processes of all users.') + ); + $adminPrivTable[] = array('Reload', + 'RELOAD', + __('Allows reloading server settings and flushing the server\'s caches.') + ); + $adminPrivTable[] = array('Shutdown', + 'SHUTDOWN', + __('Allows shutting down the server.') + ); + $adminPrivTable[] = array('Show_db', + 'SHOW DATABASES', + __('Gives access to the complete list of databases.') + ); + } + else { + $adminPrivTable = array( + array('Grant', + 'GRANT', + __( + 'Allows user to give to other users or remove from other' + . ' users the privileges that user possess yourself.' + ) + ), + ); + } + $adminPrivTable[] = array('Lock_tables', + 'LOCK TABLES', + __('Allows locking tables for the current thread.') + ); + $adminPrivTable[] = array('References', + 'REFERENCES', + __('Has no effect in this MySQL version.') + ); + if ($db == '*') { + $adminPrivTable[] = array('Repl_client', + 'REPLICATION CLIENT', + __('Allows the user to ask where the slaves / masters are.') + ); + $adminPrivTable[] = array('Repl_slave', + 'REPLICATION SLAVE', + __('Needed for the replication slaves.') + ); + $adminPrivTable[] = array('Create_user', + 'CREATE USER', + __('Allows creating, dropping and renaming user accounts.') + ); + } + return $adminPrivTable; + } + + /** + * Get HTML snippet for global privileges table with check boxes + * + * @param array $privTable privileges table array + * @param array $privTableNames names of the privilege tables + * (Data, Structure, Administration) + * @param array $row first row from result or boolean false + * + * @return string $html_output + */ + public static function getHtmlForGlobalPrivTableWithCheckboxes( + array $privTable, array $privTableNames, array $row + ) { + return Template::get('privileges/global_priv_table')->render(array( + 'priv_table' => $privTable, + 'priv_table_names' => $privTableNames, + 'row' => $row, + )); + } + + /** + * Gets the currently active authentication plugins + * + * @param string $orig_auth_plugin Default Authentication plugin + * @param string $mode are we creating a new user or are we just + * changing one? + * (allowed values: 'new', 'edit', 'change_pw') + * @param string $versions Is MySQL version newer or older than 5.5.7 + * + * @return string $html_output + */ + public static function getHtmlForAuthPluginsDropdown( + $orig_auth_plugin, + $mode = 'new', + $versions = 'new' + ) { + $select_id = 'select_authentication_plugin' + . ($mode =='change_pw' ? '_cp' : ''); + + if ($versions == 'new') { + $active_auth_plugins = self::getActiveAuthPlugins(); + + if (isset($active_auth_plugins['mysql_old_password'])) { + unset($active_auth_plugins['mysql_old_password']); + } + } else { + $active_auth_plugins = array( + 'mysql_native_password' => __('Native MySQL authentication') + ); + } + + $html_output = Util::getDropdown( + 'authentication_plugin', + $active_auth_plugins, + $orig_auth_plugin, + $select_id + ); + + return $html_output; + } + + /** + * Gets the currently active authentication plugins + * + * @return array $result array of plugin names and descriptions + */ + public static function getActiveAuthPlugins() + { + $get_plugins_query = "SELECT `PLUGIN_NAME`, `PLUGIN_DESCRIPTION`" + . " FROM `information_schema`.`PLUGINS` " + . "WHERE `PLUGIN_TYPE` = 'AUTHENTICATION';"; + $resultset = $GLOBALS['dbi']->query($get_plugins_query); + + $result = array(); + + while ($row = $GLOBALS['dbi']->fetchAssoc($resultset)) { + // if description is known, enable its translation + if ('mysql_native_password' == $row['PLUGIN_NAME']) { + $row['PLUGIN_DESCRIPTION'] = __('Native MySQL authentication'); + } elseif ('sha256_password' == $row['PLUGIN_NAME']) { + $row['PLUGIN_DESCRIPTION'] = __('SHA256 password authentication'); + } + + $result[$row['PLUGIN_NAME']] = $row['PLUGIN_DESCRIPTION']; + } + + return $result; + } + + /** + * Displays the fields used by the "new user" form as well as the + * "change login information / copy user" form. + * + * @param string $mode are we creating a new user or are we just + * changing one? (allowed values: 'new', 'change') + * @param string $username User name + * @param string $hostname Host name + * + * @global array $cfg the phpMyAdmin configuration + * @global resource $user_link the database connection + * + * @return string $html_output a HTML snippet + */ + public static function getHtmlForLoginInformationFields( + $mode = 'new', + $username = null, + $hostname = null + ) { + list($username_length, $hostname_length) = self::getUsernameAndHostnameLength(); + + if (isset($GLOBALS['username']) && strlen($GLOBALS['username']) === 0) { + $GLOBALS['pred_username'] = 'any'; + } + $html_output = '
    ' . "\n" + . '' . __('Login Information') . '' . "\n" + . '
    ' . "\n" + . '' . "\n" + . '' . "\n"; + + $html_output .= '' . "\n" + . '' . "\n"; + + $html_output .= '' . "\n"; + + $html_output .= '
    ' + . Message::notice( + __( + 'An account already exists with the same username ' + . 'but possibly a different hostname.' + ) + )->getDisplay() + . '
    '; + $html_output .= '
    '; + + $html_output .= '
    ' . "\n" + . '' . "\n"; + + $html_output .= '' . "\n" + . ' ' . "\n" + . '' . "\n"; + + $html_output .= '' . "\n" + . Util::showHint( + __( + 'When Host table is used, this field is ignored ' + . 'and values stored in Host table are used instead.' + ) + ) + . '
    ' . "\n"; + + $html_output .= '
    ' . "\n" + . '' . "\n" + . '' . "\n" + . '' . "\n" + . '' . "\n" + . '' . "\n" + . 'Strength: ' + . ' ' + . '' . "\n" + . '
    ' . "\n"; + + $html_output .= '
    ' . "\n" + . '' . "\n" + . ' ' . "\n" + . '' . "\n" + . '
    ' . "\n" + . '
    ' + . ' ' . "\n"; + + $auth_plugin_dropdown = self::getHtmlForAuthPluginsDropdown( + $orig_auth_plugin, $mode, 'new' + ); + } else { + $html_output .= __('Password Hashing Method') + . ' ' . "\n"; + $auth_plugin_dropdown = self::getHtmlForAuthPluginsDropdown( + $orig_auth_plugin, $mode, 'old' + ); + } + $html_output .= $auth_plugin_dropdown; + + $html_output .= '' + . Message::notice( + __( + 'This method requires using an \'SSL connection\' ' + . 'or an \'unencrypted connection that encrypts the password ' + . 'using RSA\'; while connecting to the server.' + ) + . Util::showMySQLDocu('sha256-authentication-plugin') + ) + ->getDisplay() + . '
    '; + + $html_output .= '' . "\n" + // Generate password added here via jQuery + . '
    ' . "\n"; + + return $html_output; + } // end of the 'self::getHtmlForLoginInformationFields()' function + + /** + * Get username and hostname length + * + * @return array username length and hostname length + */ + public static function getUsernameAndHostnameLength() + { + /* Fallback values */ + $username_length = 16; + $hostname_length = 41; + + /* Try to get real lengths from the database */ + $fields_info = $GLOBALS['dbi']->fetchResult( + 'SELECT COLUMN_NAME, CHARACTER_MAXIMUM_LENGTH ' + . 'FROM information_schema.columns ' + . "WHERE table_schema = 'mysql' AND table_name = 'user' " + . "AND COLUMN_NAME IN ('User', 'Host')" + ); + foreach ($fields_info as $val) { + if ($val['COLUMN_NAME'] == 'User') { + $username_length = $val['CHARACTER_MAXIMUM_LENGTH']; + } elseif ($val['COLUMN_NAME'] == 'Host') { + $hostname_length = $val['CHARACTER_MAXIMUM_LENGTH']; + } + } + return array($username_length, $hostname_length); + } + + /** + * Get current authentication plugin in use - for a user or globally + * + * @param string $mode are we creating a new user or are we just + * changing one? (allowed values: 'new', 'change') + * @param string $username User name + * @param string $hostname Host name + * + * @return string authentication plugin in use + */ + public static function getCurrentAuthenticationPlugin( + $mode = 'new', + $username = null, + $hostname = null + ) { + /* Fallback (standard) value */ + $authentication_plugin = 'mysql_native_password'; + $serverVersion = $GLOBALS['dbi']->getVersion(); + + if (isset($username) && isset($hostname) + && $mode == 'change' + ) { + $row = $GLOBALS['dbi']->fetchSingleRow( + 'SELECT `plugin` FROM `mysql`.`user` WHERE ' + . '`User` = "' . $username . '" AND `Host` = "' . $hostname . '" LIMIT 1' + ); + // Table 'mysql'.'user' may not exist for some previous + // versions of MySQL - in that case consider fallback value + if (isset($row) && $row) { + $authentication_plugin = $row['plugin']; + } + } elseif ($mode == 'change') { + list($username, $hostname) = $GLOBALS['dbi']->getCurrentUserAndHost(); + + $row = $GLOBALS['dbi']->fetchSingleRow( + 'SELECT `plugin` FROM `mysql`.`user` WHERE ' + . '`User` = "' . $username . '" AND `Host` = "' . $hostname . '"' + ); + if (isset($row) && $row && ! empty($row['plugin'])) { + $authentication_plugin = $row['plugin']; + } + } elseif ($serverVersion >= 50702) { + $row = $GLOBALS['dbi']->fetchSingleRow( + 'SELECT @@default_authentication_plugin' + ); + $authentication_plugin = is_array($row) ? $row['@@default_authentication_plugin'] : null; + } + + return $authentication_plugin; + } + + /** + * Returns all the grants for a certain user on a certain host + * Used in the export privileges for all users section + * + * @param string $user User name + * @param string $host Host name + * + * @return string containing all the grants text + */ + public static function getGrants($user, $host) + { + $grants = $GLOBALS['dbi']->fetchResult( + "SHOW GRANTS FOR '" + . $GLOBALS['dbi']->escapeString($user) . "'@'" + . $GLOBALS['dbi']->escapeString($host) . "'" + ); + $response = ''; + foreach ($grants as $one_grant) { + $response .= $one_grant . ";\n\n"; + } + return $response; + } // end of the 'self::getGrants()' function + + /** + * Update password and get message for password updating + * + * @param string $err_url error url + * @param string $username username + * @param string $hostname hostname + * + * @return string $message success or error message after updating password + */ + public static function updatePassword($err_url, $username, $hostname) + { + // similar logic in user_password.php + $message = ''; + + if (empty($_POST['nopass']) + && isset($_POST['pma_pw']) + && isset($_POST['pma_pw2']) + ) { + if ($_POST['pma_pw'] != $_POST['pma_pw2']) { + $message = Message::error(__('The passwords aren\'t the same!')); + } elseif (empty($_POST['pma_pw']) || empty($_POST['pma_pw2'])) { + $message = Message::error(__('The password is empty!')); + } + } + + // here $nopass could be == 1 + if (empty($message)) { + $hashing_function = 'PASSWORD'; + $serverType = Util::getServerType(); + $serverVersion = $GLOBALS['dbi']->getVersion(); + $authentication_plugin + = (isset($_POST['authentication_plugin']) + ? $_POST['authentication_plugin'] + : self::getCurrentAuthenticationPlugin( + 'change', + $username, + $hostname + )); + + // Use 'ALTER USER ...' syntax for MySQL 5.7.6+ + if ($serverType == 'MySQL' + && $serverVersion >= 50706 + ) { + if ($authentication_plugin != 'mysql_old_password') { + $query_prefix = "ALTER USER '" + . $GLOBALS['dbi']->escapeString($username) + . "'@'" . $GLOBALS['dbi']->escapeString($hostname) . "'" + . " IDENTIFIED WITH " + . $authentication_plugin + . " BY '"; + } else { + $query_prefix = "ALTER USER '" + . $GLOBALS['dbi']->escapeString($username) + . "'@'" . $GLOBALS['dbi']->escapeString($hostname) . "'" + . " IDENTIFIED BY '"; + } + + // in $sql_query which will be displayed, hide the password + $sql_query = $query_prefix . "*'"; + + $local_query = $query_prefix + . $GLOBALS['dbi']->escapeString($_POST['pma_pw']) . "'"; + } elseif ($serverType == 'MariaDB' && $serverVersion >= 10000) { + // MariaDB uses "SET PASSWORD" syntax to change user password. + // On Galera cluster only DDL queries are replicated, since + // users are stored in MyISAM storage engine. + $query_prefix = "SET PASSWORD FOR '" + . $GLOBALS['dbi']->escapeString($username) + . "'@'" . $GLOBALS['dbi']->escapeString($hostname) . "'" + . " = PASSWORD ('"; + $sql_query = $local_query = $query_prefix + . $GLOBALS['dbi']->escapeString($_POST['pma_pw']) . "')"; + } elseif ($serverType == 'MariaDB' + && $serverVersion >= 50200 + && $GLOBALS['dbi']->isSuperuser() + ) { + // Use 'UPDATE `mysql`.`user` ...' Syntax for MariaDB 5.2+ + if ($authentication_plugin == 'mysql_native_password') { + // Set the hashing method used by PASSWORD() + // to be 'mysql_native_password' type + $GLOBALS['dbi']->tryQuery('SET old_passwords = 0;'); + + } elseif ($authentication_plugin == 'sha256_password') { + // Set the hashing method used by PASSWORD() + // to be 'sha256_password' type + $GLOBALS['dbi']->tryQuery('SET `old_passwords` = 2;'); + } + + $hashedPassword = self::getHashedPassword($_POST['pma_pw']); + + $sql_query = 'SET PASSWORD FOR \'' + . $GLOBALS['dbi']->escapeString($username) + . '\'@\'' . $GLOBALS['dbi']->escapeString($hostname) . '\' = ' + . (($_POST['pma_pw'] == '') + ? '\'\'' + : $hashing_function . '(\'' + . preg_replace('@.@s', '*', $_POST['pma_pw']) . '\')'); + + $local_query = "UPDATE `mysql`.`user` SET " + . " `authentication_string` = '" . $hashedPassword + . "', `Password` = '', " + . " `plugin` = '" . $authentication_plugin . "'" + . " WHERE `User` = '" . $username . "' AND Host = '" + . $hostname . "';"; + } else { + // USE 'SET PASSWORD ...' syntax for rest of the versions + // Backup the old value, to be reset later + $row = $GLOBALS['dbi']->fetchSingleRow( + 'SELECT @@old_passwords;' + ); + $orig_value = $row['@@old_passwords']; + $update_plugin_query = "UPDATE `mysql`.`user` SET" + . " `plugin` = '" . $authentication_plugin . "'" + . " WHERE `User` = '" . $username . "' AND Host = '" + . $hostname . "';"; + + // Update the plugin for the user + if (!($GLOBALS['dbi']->tryQuery($update_plugin_query))) { + Util::mysqlDie( + $GLOBALS['dbi']->getError(), + $update_plugin_query, + false, $err_url + ); + } + $GLOBALS['dbi']->tryQuery("FLUSH PRIVILEGES;"); + + if ($authentication_plugin == 'mysql_native_password') { + // Set the hashing method used by PASSWORD() + // to be 'mysql_native_password' type + $GLOBALS['dbi']->tryQuery('SET old_passwords = 0;'); + } elseif ($authentication_plugin == 'sha256_password') { + // Set the hashing method used by PASSWORD() + // to be 'sha256_password' type + $GLOBALS['dbi']->tryQuery('SET `old_passwords` = 2;'); + } + $sql_query = 'SET PASSWORD FOR \'' + . $GLOBALS['dbi']->escapeString($username) + . '\'@\'' . $GLOBALS['dbi']->escapeString($hostname) . '\' = ' + . (($_POST['pma_pw'] == '') + ? '\'\'' + : $hashing_function . '(\'' + . preg_replace('@.@s', '*', $_POST['pma_pw']) . '\')'); + + $local_query = 'SET PASSWORD FOR \'' + . $GLOBALS['dbi']->escapeString($username) + . '\'@\'' . $GLOBALS['dbi']->escapeString($hostname) . '\' = ' + . (($_POST['pma_pw'] == '') ? '\'\'' : $hashing_function + . '(\'' . $GLOBALS['dbi']->escapeString($_POST['pma_pw']) . '\')'); + } + + if (!($GLOBALS['dbi']->tryQuery($local_query))) { + Util::mysqlDie( + $GLOBALS['dbi']->getError(), $sql_query, false, $err_url + ); + } + // Flush privileges after successful password change + $GLOBALS['dbi']->tryQuery("FLUSH PRIVILEGES;"); + + $message = Message::success( + __('The password for %s was changed successfully.') + ); + $message->addParam('\'' . $username . '\'@\'' . $hostname . '\''); + if (isset($orig_value)) { + $GLOBALS['dbi']->tryQuery( + 'SET `old_passwords` = ' . $orig_value . ';' + ); + } + } + return $message; + } + + /** + * Revokes privileges and get message and SQL query for privileges revokes + * + * @param string $dbname database name + * @param string $tablename table name + * @param string $username username + * @param string $hostname host name + * @param string $itemType item type + * + * @return array ($message, $sql_query) + */ + public static function getMessageAndSqlQueryForPrivilegesRevoke($dbname, + $tablename, $username, $hostname, $itemType + ) { + $db_and_table = self::wildcardEscapeForGrant($dbname, $tablename); + + $sql_query0 = 'REVOKE ALL PRIVILEGES ON ' . $itemType . ' ' . $db_and_table + . ' FROM \'' + . $GLOBALS['dbi']->escapeString($username) . '\'@\'' + . $GLOBALS['dbi']->escapeString($hostname) . '\';'; + + $sql_query1 = 'REVOKE GRANT OPTION ON ' . $itemType . ' ' . $db_and_table + . ' FROM \'' . $GLOBALS['dbi']->escapeString($username) . '\'@\'' + . $GLOBALS['dbi']->escapeString($hostname) . '\';'; + + $GLOBALS['dbi']->query($sql_query0); + if (! $GLOBALS['dbi']->tryQuery($sql_query1)) { + // this one may fail, too... + $sql_query1 = ''; + } + $sql_query = $sql_query0 . ' ' . $sql_query1; + $message = Message::success( + __('You have revoked the privileges for %s.') + ); + $message->addParam('\'' . $username . '\'@\'' . $hostname . '\''); + + return array($message, $sql_query); + } + + /** + * Get REQUIRE cluase + * + * @return string REQUIRE clause + */ + public static function getRequireClause() + { + $arr = isset($_POST['ssl_type']) ? $_POST : $GLOBALS; + if (isset($arr['ssl_type']) && $arr['ssl_type'] == 'SPECIFIED') { + $require = array(); + if (! empty($arr['ssl_cipher'])) { + $require[] = "CIPHER '" + . $GLOBALS['dbi']->escapeString($arr['ssl_cipher']) . "'"; + } + if (! empty($arr['x509_issuer'])) { + $require[] = "ISSUER '" + . $GLOBALS['dbi']->escapeString($arr['x509_issuer']) . "'"; + } + if (! empty($arr['x509_subject'])) { + $require[] = "SUBJECT '" + . $GLOBALS['dbi']->escapeString($arr['x509_subject']) . "'"; + } + if (count($require)) { + $require_clause = " REQUIRE " . implode(" AND ", $require); + } else { + $require_clause = " REQUIRE NONE"; + } + } elseif (isset($arr['ssl_type']) && $arr['ssl_type'] == 'X509') { + $require_clause = " REQUIRE X509"; + } elseif (isset($arr['ssl_type']) && $arr['ssl_type'] == 'ANY') { + $require_clause = " REQUIRE SSL"; + } else { + $require_clause = " REQUIRE NONE"; + } + + return $require_clause; + } + + /** + * Get a WITH clause for 'update privileges' and 'add user' + * + * @return string $sql_query + */ + public static function getWithClauseForAddUserAndUpdatePrivs() + { + $sql_query = ''; + if (((isset($_POST['Grant_priv']) && $_POST['Grant_priv'] == 'Y') + || (isset($GLOBALS['Grant_priv']) && $GLOBALS['Grant_priv'] == 'Y')) + && ! ((Util::getServerType() == 'MySQL' || Util::getServerType() == 'Percona Server') + && $GLOBALS['dbi']->getVersion() >= 80011) + ) { + $sql_query .= ' GRANT OPTION'; + } + if (isset($_POST['max_questions']) || isset($GLOBALS['max_questions'])) { + $max_questions = isset($_POST['max_questions']) + ? (int)$_POST['max_questions'] : (int)$GLOBALS['max_questions']; + $max_questions = max(0, $max_questions); + $sql_query .= ' MAX_QUERIES_PER_HOUR ' . $max_questions; + } + if (isset($_POST['max_connections']) || isset($GLOBALS['max_connections'])) { + $max_connections = isset($_POST['max_connections']) + ? (int)$_POST['max_connections'] : (int)$GLOBALS['max_connections']; + $max_connections = max(0, $max_connections); + $sql_query .= ' MAX_CONNECTIONS_PER_HOUR ' . $max_connections; + } + if (isset($_POST['max_updates']) || isset($GLOBALS['max_updates'])) { + $max_updates = isset($_POST['max_updates']) + ? (int)$_POST['max_updates'] : (int)$GLOBALS['max_updates']; + $max_updates = max(0, $max_updates); + $sql_query .= ' MAX_UPDATES_PER_HOUR ' . $max_updates; + } + if (isset($_POST['max_user_connections']) + || isset($GLOBALS['max_user_connections']) + ) { + $max_user_connections = isset($_POST['max_user_connections']) + ? (int)$_POST['max_user_connections'] + : (int)$GLOBALS['max_user_connections']; + $max_user_connections = max(0, $max_user_connections); + $sql_query .= ' MAX_USER_CONNECTIONS ' . $max_user_connections; + } + return ((!empty($sql_query)) ? ' WITH' . $sql_query : ''); + } + + /** + * Get HTML for addUsersForm, This function call if isset($_GET['adduser']) + * + * @param string $dbname database name + * + * @return string HTML for addUserForm + */ + public static function getHtmlForAddUser($dbname) + { + $html_output = '

    ' . "\n" + . Util::getIcon('b_usradd') . __('Add user account') . "\n" + . '

    ' . "\n" + . '
    ' . "\n" + . Url::getHiddenInputs('', '') + . self::getHtmlForLoginInformationFields('new'); + + $html_output .= '
    ' . "\n" + . '' . __('Database for user account') . '' . "\n"; + + $html_output .= Template::get('checkbox') + ->render( + array( + 'html_field_name' => 'createdb-1', + 'label' => __('Create database with same name and grant all privileges.'), + 'checked' => false, + 'onclick' => false, + 'html_field_id' => 'createdb-1', + ) + ); + $html_output .= '
    ' . "\n"; + $html_output .= Template::get('checkbox') + ->render( + array( + 'html_field_name' => 'createdb-2', + 'label' => __('Grant all privileges on wildcard name (username\\_%).'), + 'checked' => false, + 'onclick' => false, + 'html_field_id' => 'createdb-2', + ) + ); + $html_output .= '
    ' . "\n"; + + if (! empty($dbname) ) { + $html_output .= Template::get('checkbox') + ->render( + array( + 'html_field_name' => 'createdb-3', + 'label' => sprintf(__('Grant all privileges on database %s.'), htmlspecialchars($dbname)), + 'checked' => true, + 'onclick' => false, + 'html_field_id' => 'createdb-3', + ) + ); + $html_output .= '' . "\n"; + $html_output .= '
    ' . "\n"; + } + + $html_output .= '
    ' . "\n"; + if ($GLOBALS['is_grantuser']) { + $html_output .= self::getHtmlToDisplayPrivilegesTable('*', '*', false); + } + $html_output .= '' . "\n" + . '
    ' . "\n"; + + return $html_output; + } + + /** + * Get the list of privileges and list of compared privileges as strings + * and return a array that contains both strings + * + * @return array $list_of_privileges, $list_of_compared_privileges + */ + public static function getListOfPrivilegesAndComparedPrivileges() + { + $list_of_privileges + = '`User`, ' + . '`Host`, ' + . '`Select_priv`, ' + . '`Insert_priv`, ' + . '`Update_priv`, ' + . '`Delete_priv`, ' + . '`Create_priv`, ' + . '`Drop_priv`, ' + . '`Grant_priv`, ' + . '`Index_priv`, ' + . '`Alter_priv`, ' + . '`References_priv`, ' + . '`Create_tmp_table_priv`, ' + . '`Lock_tables_priv`, ' + . '`Create_view_priv`, ' + . '`Show_view_priv`, ' + . '`Create_routine_priv`, ' + . '`Alter_routine_priv`, ' + . '`Execute_priv`'; + + $listOfComparedPrivs + = '`Select_priv` = \'N\'' + . ' AND `Insert_priv` = \'N\'' + . ' AND `Update_priv` = \'N\'' + . ' AND `Delete_priv` = \'N\'' + . ' AND `Create_priv` = \'N\'' + . ' AND `Drop_priv` = \'N\'' + . ' AND `Grant_priv` = \'N\'' + . ' AND `References_priv` = \'N\'' + . ' AND `Create_tmp_table_priv` = \'N\'' + . ' AND `Lock_tables_priv` = \'N\'' + . ' AND `Create_view_priv` = \'N\'' + . ' AND `Show_view_priv` = \'N\'' + . ' AND `Create_routine_priv` = \'N\'' + . ' AND `Alter_routine_priv` = \'N\'' + . ' AND `Execute_priv` = \'N\''; + + $list_of_privileges .= + ', `Event_priv`, ' + . '`Trigger_priv`'; + $listOfComparedPrivs .= + ' AND `Event_priv` = \'N\'' + . ' AND `Trigger_priv` = \'N\''; + return array($list_of_privileges, $listOfComparedPrivs); + } + + /** + * Get the HTML for routine based privileges + * + * @param string $db database name + * @param string $index_checkbox starting index for rows to be added + * + * @return string $html_output + */ + public static function getHtmlTableBodyForSpecificDbRoutinePrivs($db, $index_checkbox) + { + $sql_query = 'SELECT * FROM `mysql`.`procs_priv` WHERE Db = \'' . $GLOBALS['dbi']->escapeString($db) . '\';'; + $res = $GLOBALS['dbi']->query($sql_query); + $html_output = ''; + while ($row = $GLOBALS['dbi']->fetchAssoc($res)) { + + $html_output .= '
    ' . htmlspecialchars($row['User']) + . '' . htmlspecialchars($row['Host']) + . '' . 'routine' + . '' . '' . htmlspecialchars($row['Routine_name']) . '' + . '' . 'Yes' + . ''; + if ($GLOBALS['is_grantuser']) { + $specific_db = (isset($row['Db']) && $row['Db'] != '*') + ? $row['Db'] : ''; + $specific_table = (isset($row['Table_name']) + && $row['Table_name'] != '*') + ? $row['Table_name'] : ''; + $html_output .= self::getUserLink( + 'edit', + $current_user, + $current_host, + $specific_db, + $specific_table, + $routine + ); + } + $html_output .= ''; + $html_output .= self::getUserLink( + 'export', + $current_user, + $current_host, + $specific_db, + $specific_table, + $routine + ); + $html_output .= '
    '; + $html_output .= self::getHtmlForPrivsTableHead(); + $privMap = self::getPrivMap($db); + $html_output .= self::getHtmlTableBodyForSpecificDbOrTablePrivs($privMap, $db); + $html_output .= '
    '; + $html_output .= '
    '; + + $html_output .= '
    '; + $html_output .= Template::get('select_all') + ->render( + array( + 'pma_theme_image' => $GLOBALS['pmaThemeImage'], + 'text_dir' => $GLOBALS['text_dir'], + 'form_name' => "usersForm", + ) + ); + $html_output .= Util::getButtonOrImage( + 'submit_mult', 'mult_submit', + __('Export'), 'b_tblexport', 'export' + ); + + $html_output .= ''; + $html_output .= '
    '; + $html_output .= ''; + } else { + $html_output .= self::getHtmlForViewUsersError(); + } + + $response = Response::getInstance(); + if ($response->isAjax() == true + && empty($_REQUEST['ajax_page_request']) + ) { + $message = Message::success(__('User has been added.')); + $response->addJSON('message', $message); + $response->addJSON('user_form', $html_output); + exit; + } else { + // Offer to create a new user for the current database + $html_output .= self::getAddUserHtmlFieldset($db); + } + return $html_output; + } + + /** + * Get the HTML for user form and check the privileges for a particular table. + * + * @param string $db database name + * @param string $table table name + * + * @return string $html_output + */ + public static function getHtmlForSpecificTablePrivileges($db, $table) + { + $html_output = ''; + if ($GLOBALS['dbi']->isSuperuser()) { + // check the privileges for a particular table. + $html_output = '
    '; + $html_output .= Url::getHiddenInputs($db, $table); + $html_output .= '
    '; + $html_output .= '' + . Util::getIcon('b_usrcheck') + . sprintf( + __('Users having access to "%s"'), + '' + . htmlspecialchars($db) . '.' . htmlspecialchars($table) + . '' + ) + . ''; + + $html_output .= '
    '; + $html_output .= ''; + $html_output .= self::getHtmlForPrivsTableHead(); + $privMap = self::getPrivMap($db); + $sql_query = "SELECT `User`, `Host`, `Db`," + . " 't' AS `Type`, `Table_name`, `Table_priv`" + . " FROM `mysql`.`tables_priv`" + . " WHERE '" . $GLOBALS['dbi']->escapeString($db) . "' LIKE `Db`" + . " AND '" . $GLOBALS['dbi']->escapeString($table) . "' LIKE `Table_name`" + . " AND NOT (`Table_priv` = '' AND Column_priv = '')" + . " ORDER BY `User` ASC, `Host` ASC, `Db` ASC, `Table_priv` ASC;"; + $res = $GLOBALS['dbi']->query($sql_query); + self::mergePrivMapFromResult($privMap, $res); + $html_output .= self::getHtmlTableBodyForSpecificDbOrTablePrivs($privMap, $db); + $html_output .= '
    '; + + $html_output .= '
    '; + $html_output .= Template::get('select_all') + ->render( + array( + 'pma_theme_image' => $GLOBALS['pmaThemeImage'], + 'text_dir' => $GLOBALS['text_dir'], + 'form_name' => "usersForm", + ) + ); + $html_output .= Util::getButtonOrImage( + 'submit_mult', 'mult_submit', + __('Export'), 'b_tblexport', 'export' + ); + + $html_output .= '
    '; + $html_output .= '
    '; + } else { + $html_output .= self::getHtmlForViewUsersError(); + } + // Offer to create a new user for the current database + $html_output .= self::getAddUserHtmlFieldset($db, $table); + return $html_output; + } + + /** + * gets privilege map + * + * @param string $db the database + * + * @return array $privMap the privilege map + */ + public static function getPrivMap($db) + { + list($listOfPrivs, $listOfComparedPrivs) + = self::getListOfPrivilegesAndComparedPrivileges(); + $sql_query + = "(" + . " SELECT " . $listOfPrivs . ", '*' AS `Db`, 'g' AS `Type`" + . " FROM `mysql`.`user`" + . " WHERE NOT (" . $listOfComparedPrivs . ")" + . ")" + . " UNION " + . "(" + . " SELECT " . $listOfPrivs . ", `Db`, 'd' AS `Type`" + . " FROM `mysql`.`db`" + . " WHERE '" . $GLOBALS['dbi']->escapeString($db) . "' LIKE `Db`" + . " AND NOT (" . $listOfComparedPrivs . ")" + . ")" + . " ORDER BY `User` ASC, `Host` ASC, `Db` ASC;"; + $res = $GLOBALS['dbi']->query($sql_query); + $privMap = array(); + self::mergePrivMapFromResult($privMap, $res); + return $privMap; + } + + /** + * merge privilege map and rows from resultset + * + * @param array &$privMap the privilege map reference + * @param object $result the resultset of query + * + * @return void + */ + public static function mergePrivMapFromResult(array &$privMap, $result) + { + while ($row = $GLOBALS['dbi']->fetchAssoc($result)) { + $user = $row['User']; + $host = $row['Host']; + if (! isset($privMap[$user])) { + $privMap[$user] = array(); + } + if (! isset($privMap[$user][$host])) { + $privMap[$user][$host] = array(); + } + $privMap[$user][$host][] = $row; + } + } + + /** + * Get HTML snippet for privileges table head + * + * @return string $html_output + */ + public static function getHtmlForPrivsTableHead() + { + return '' + . '' + . '' + . '' . __('User name') . '' + . '' . __('Host name') . '' + . '' . __('Type') . '' + . '' . __('Privileges') . '' + . '' . __('Grant') . '' + . '' . __('Action') . '' + . '' + . ''; + } + + /** + * Get HTML error for View Users form + * For non superusers such as grant/create users + * + * @return string $html_output + */ + public static function getHtmlForViewUsersError() + { + return Message::error( + __('Not enough privilege to view users.') + )->getDisplay(); + } + + /** + * Get HTML snippet for table body of specific database or table privileges + * + * @param array $privMap privilege map + * @param string $db database + * + * @return string $html_output + */ + public static function getHtmlTableBodyForSpecificDbOrTablePrivs($privMap, $db) + { + $html_output = ''; + $index_checkbox = 0; + if (empty($privMap)) { + $html_output .= '' + . '' + . __('No user found.') + . '' + . '' + . ''; + return $html_output; + } + + foreach ($privMap as $current_user => $val) { + foreach ($val as $current_host => $current_privileges) { + $nbPrivileges = count($current_privileges); + $html_output .= ''; + + $value = htmlspecialchars($current_user . '&#27;' . $current_host); + $html_output .= ' 1) { + $html_output .= ' rowspan="' . $nbPrivileges . '"'; + } + $html_output .= '>'; + $html_output .= '' . "\n"; + + // user + $html_output .= ' 1) { + $html_output .= ' rowspan="' . $nbPrivileges . '"'; + } + $html_output .= '>'; + if (empty($current_user)) { + $html_output .= '' + . __('Any') . ''; + } else { + $html_output .= htmlspecialchars($current_user); + } + $html_output .= ''; + + // host + $html_output .= ' 1) { + $html_output .= ' rowspan="' . $nbPrivileges . '"'; + } + $html_output .= '>'; + $html_output .= htmlspecialchars($current_host); + $html_output .= ''; + + $html_output .= self::getHtmlListOfPrivs( + $db, $current_privileges, $current_user, + $current_host + ); + } + } + + //For fetching routine based privileges + $html_output .= self::getHtmlTableBodyForSpecificDbRoutinePrivs($db, $index_checkbox); + $html_output .= ''; + + return $html_output; + } + + /** + * Get HTML to display privileges + * + * @param string $db Database name + * @param array $current_privileges List of privileges + * @param string $current_user Current user + * @param string $current_host Current host + * + * @return string HTML to display privileges + */ + public static function getHtmlListOfPrivs( + $db, array $current_privileges, $current_user, + $current_host + ) { + $nbPrivileges = count($current_privileges); + $html_output = null; + for ($i = 0; $i < $nbPrivileges; $i++) { + $current = $current_privileges[$i]; + + // type + $html_output .= ''; + if ($current['Type'] == 'g') { + $html_output .= __('global'); + } elseif ($current['Type'] == 'd') { + if ($current['Db'] == Util::escapeMysqlWildcards($db)) { + $html_output .= __('database-specific'); + } else { + $html_output .= __('wildcard') . ': ' + . '' + . htmlspecialchars($current['Db']) + . ''; + } + } elseif ($current['Type'] == 't') { + $html_output .= __('table-specific'); + } + $html_output .= ''; + + // privileges + $html_output .= ''; + if (isset($current['Table_name'])) { + $privList = explode(',', $current['Table_priv']); + $privs = array(); + $grantsArr = self::getTableGrantsArray(); + foreach ($grantsArr as $grant) { + $privs[$grant[0]] = 'N'; + foreach ($privList as $priv) { + if ($grant[0] == $priv) { + $privs[$grant[0]] = 'Y'; + } + } + } + $html_output .= '' + . join( + ',', + self::extractPrivInfo($privs, true, true) + ) + . ''; + } else { + $html_output .= '' + . join( + ',', + self::extractPrivInfo($current, true, false) + ) + . ''; + } + $html_output .= ''; + + // grant + $html_output .= ''; + $containsGrant = false; + if (isset($current['Table_name'])) { + $privList = explode(',', $current['Table_priv']); + foreach ($privList as $priv) { + if ($priv == 'Grant') { + $containsGrant = true; + } + } + } else { + $containsGrant = $current['Grant_priv'] == 'Y'; + } + $html_output .= ($containsGrant ? __('Yes') : __('No')); + $html_output .= ''; + + // action + $html_output .= ''; + $specific_db = (isset($current['Db']) && $current['Db'] != '*') + ? $current['Db'] : ''; + $specific_table = (isset($current['Table_name']) + && $current['Table_name'] != '*') + ? $current['Table_name'] : ''; + if ($GLOBALS['is_grantuser']) { + $html_output .= self::getUserLink( + 'edit', + $current_user, + $current_host, + $specific_db, + $specific_table + ); + } + $html_output .= ''; + $html_output .= '' + . self::getUserLink( + 'export', + $current_user, + $current_host, + $specific_db, + $specific_table + ) + . ''; + + $html_output .= ''; + if (($i + 1) < $nbPrivileges) { + $html_output .= ''; + } + } + return $html_output; + } + + /** + * Returns edit, revoke or export link for a user. + * + * @param string $linktype The link type (edit | revoke | export) + * @param string $username User name + * @param string $hostname Host name + * @param string $dbname Database name + * @param string $tablename Table name + * @param string $routinename Routine name + * @param string $initial Initial value + * + * @return string HTML code with link + */ + public static function getUserLink( + $linktype, $username, $hostname, $dbname = '', + $tablename = '', $routinename = '', $initial = '' + ) { + $html = ' $username, + 'hostname' => $hostname + ); + switch($linktype) { + case 'edit': + $params['dbname'] = $dbname; + $params['tablename'] = $tablename; + $params['routinename'] = $routinename; + break; + case 'revoke': + $params['dbname'] = $dbname; + $params['tablename'] = $tablename; + $params['routinename'] = $routinename; + $params['revokeall'] = 1; + break; + case 'export': + $params['initial'] = $initial; + $params['export'] = 1; + break; + } + + $html .= ' href="server_privileges.php'; + if ($linktype == 'revoke') { + $html .= '" data-post="' . Url::getCommon($params, ''); + } else { + $html .= Url::getCommon($params); + } + $html .= '">'; + + switch($linktype) { + case 'edit': + $html .= Util::getIcon('b_usredit', __('Edit privileges')); + break; + case 'revoke': + $html .= Util::getIcon('b_usrdrop', __('Revoke')); + break; + case 'export': + $html .= Util::getIcon('b_tblexport', __('Export')); + break; + } + $html .= ''; + + return $html; + } + + /** + * Returns user group edit link + * + * @param string $username User name + * + * @return string HTML code with link + */ + public static function getUserGroupEditLink($username) + { + return '' + . Util::getIcon('b_usrlist', __('Edit user group')) + . ''; + } + + /** + * Returns number of defined user groups + * + * @return integer $user_group_count + */ + public static function getUserGroupCount() + { + $relation = new Relation(); + $cfgRelation = $relation->getRelationsParam(); + $user_group_table = Util::backquote($cfgRelation['db']) + . '.' . Util::backquote($cfgRelation['usergroups']); + $sql_query = 'SELECT COUNT(*) FROM ' . $user_group_table; + $user_group_count = $GLOBALS['dbi']->fetchValue( + $sql_query, 0, 0, DatabaseInterface::CONNECT_CONTROL + ); + + return $user_group_count; + } + + /** + * Returns name of user group that user is part of + * + * @param string $username User name + * + * @return mixed usergroup if found or null if not found + */ + public static function getUserGroupForUser($username) + { + $relation = new Relation(); + $cfgRelation = $relation->getRelationsParam(); + + if (empty($cfgRelation['db']) + || empty($cfgRelation['users']) + ) { + return null; + } + + $user_table = Util::backquote($cfgRelation['db']) + . '.' . Util::backquote($cfgRelation['users']); + $sql_query = 'SELECT `usergroup` FROM ' . $user_table + . ' WHERE `username` = \'' . $username . '\'' + . ' LIMIT 1'; + + $usergroup = $GLOBALS['dbi']->fetchValue( + $sql_query, 0, 0, DatabaseInterface::CONNECT_CONTROL + ); + + if ($usergroup === false) { + return null; + } + + return $usergroup; + } + + /** + * This function return the extra data array for the ajax behavior + * + * @param string $password password + * @param string $sql_query sql query + * @param string $hostname hostname + * @param string $username username + * + * @return array $extra_data + */ + public static function getExtraDataForAjaxBehavior( + $password, $sql_query, $hostname, $username + ) { + $relation = new Relation(); + if (isset($GLOBALS['dbname'])) { + //if (preg_match('/\\\\(?:_|%)/i', $dbname)) { + if (preg_match('/(? 0) { + $extra_data['sql_query'] = Util::getMessage(null, $sql_query); + } + + if (isset($_POST['change_copy'])) { + /** + * generate html on the fly for the new user that was just created. + */ + $new_user_string = '' . "\n" + . ' ' + . '' . "\n" + . '' . "\n" + . '' . htmlspecialchars($hostname) . '' . "\n"; + + $new_user_string .= ''; + + if (! empty($password) || isset($_POST['pma_pw'])) { + $new_user_string .= __('Yes'); + } else { + $new_user_string .= '' + . __('No') + . ''; + }; + + $new_user_string .= '' . "\n"; + $new_user_string .= '' + . '' . join(', ', self::extractPrivInfo(null, true)) . '' + . ''; //Fill in privileges here + + // if $cfg['Servers'][$i]['users'] and $cfg['Servers'][$i]['usergroups'] are + // enabled + $cfgRelation = $relation->getRelationsParam(); + if (!empty($cfgRelation['users']) && !empty($cfgRelation['usergroups'])) { + $new_user_string .= ''; + } + + $new_user_string .= ''; + if ((isset($_POST['Grant_priv']) && $_POST['Grant_priv'] == 'Y')) { + $new_user_string .= __('Yes'); + } else { + $new_user_string .= __('No'); + } + $new_user_string .=''; + + if ($GLOBALS['is_grantuser']) { + $new_user_string .= '' + . self::getUserLink('edit', $username, $hostname) + . '' . "\n"; + } + + if ($cfgRelation['menuswork'] && $user_group_count > 0) { + $new_user_string .= '' + . self::getUserGroupEditLink($username) + . '' . "\n"; + } + + $new_user_string .= '' + . self::getUserLink( + 'export', + $username, + $hostname, + '', + '', + '', + isset($_GET['initial']) ? $_GET['initial'] : '' + ) + . '' . "\n"; + + $new_user_string .= ''; + + $extra_data['new_user_string'] = $new_user_string; + + /** + * Generate the string for this alphabet's initial, to update the user + * pagination + */ + $new_user_initial = mb_strtoupper( + mb_substr($username, 0, 1) + ); + $newUserInitialString = '' + . $new_user_initial . ''; + $extra_data['new_user_initial'] = $new_user_initial; + $extra_data['new_user_initial_string'] = $newUserInitialString; + } + + if (isset($_POST['update_privs'])) { + $extra_data['db_specific_privs'] = false; + $extra_data['db_wildcard_privs'] = false; + if (isset($dbname_is_wildcard)) { + $extra_data['db_specific_privs'] = ! $dbname_is_wildcard; + $extra_data['db_wildcard_privs'] = $dbname_is_wildcard; + } + $new_privileges = join(', ', self::extractPrivInfo(null, true)); + + $extra_data['new_privileges'] = $new_privileges; + } + + if (isset($_GET['validate_username'])) { + $sql_query = "SELECT * FROM `mysql`.`user` WHERE `User` = '" + . $_GET['username'] . "';"; + $res = $GLOBALS['dbi']->query($sql_query); + $row = $GLOBALS['dbi']->fetchRow($res); + if (empty($row)) { + $extra_data['user_exists'] = false; + } else { + $extra_data['user_exists'] = true; + } + } + + return $extra_data; + } + + /** + * Get the HTML snippet for change user login information + * + * @param string $username username + * @param string $hostname host name + * + * @return string HTML snippet + */ + public static function getChangeLoginInformationHtmlForm($username, $hostname) + { + $choices = array( + '4' => __('… keep the old one.'), + '1' => __('… delete the old one from the user tables.'), + '2' => __( + '… revoke all active privileges from ' + . 'the old one and delete it afterwards.' + ), + '3' => __( + '… delete the old one from the user tables ' + . 'and reload the privileges afterwards.' + ) + ); + + $html_output = '' . "\n"; + + return $html_output; + } + + /** + * Provide a line with links to the relevant database and table + * + * @param string $url_dbname url database name that urlencode() string + * @param string $dbname database name + * @param string $tablename table name + * + * @return string HTML snippet + */ + public static function getLinkToDbAndTable($url_dbname, $dbname, $tablename) + { + $html_output = '[ ' . __('Database') + . ' ' + . htmlspecialchars(Util::unescapeMysqlWildcards($dbname)) . ': ' + . Util::getTitleForTarget( + $GLOBALS['cfg']['DefaultTabDatabase'] + ) + . " ]\n"; + + if (strlen($tablename) > 0) { + $html_output .= ' [ ' . __('Table') . ' ' . htmlspecialchars($tablename) . ': ' + . Util::getTitleForTarget( + $GLOBALS['cfg']['DefaultTabTable'] + ) + . " ]\n"; + } + return $html_output; + } + + /** + * no db name given, so we want all privs for the given user + * db name was given, so we want all user specific rights for this db + * So this function returns user rights as an array + * + * @param string $username username + * @param string $hostname host name + * @param string $type database or table + * @param string $dbname database name + * + * @return array $db_rights database rights + */ + public static function getUserSpecificRights($username, $hostname, $type, $dbname = '') + { + $user_host_condition = " WHERE `User`" + . " = '" . $GLOBALS['dbi']->escapeString($username) . "'" + . " AND `Host`" + . " = '" . $GLOBALS['dbi']->escapeString($hostname) . "'"; + + if ($type == 'database') { + $tables_to_search_for_users = array( + 'tables_priv', 'columns_priv', 'procs_priv' + ); + $dbOrTableName = 'Db'; + } elseif ($type == 'table') { + $user_host_condition .= " AND `Db` LIKE '" + . $GLOBALS['dbi']->escapeString($dbname) . "'"; + $tables_to_search_for_users = array('columns_priv',); + $dbOrTableName = 'Table_name'; + } else { // routine + $user_host_condition .= " AND `Db` LIKE '" + . $GLOBALS['dbi']->escapeString($dbname) . "'"; + $tables_to_search_for_users = array('procs_priv',); + $dbOrTableName = 'Routine_name'; + } + + // we also want privileges for this user not in table `db` but in other table + $tables = $GLOBALS['dbi']->fetchResult('SHOW TABLES FROM `mysql`;'); + + $db_rights_sqls = array(); + foreach ($tables_to_search_for_users as $table_search_in) { + if (in_array($table_search_in, $tables)) { + $db_rights_sqls[] = ' + SELECT DISTINCT `' . $dbOrTableName . '` + FROM `mysql`.' . Util::backquote($table_search_in) + . $user_host_condition; + } + } + + $user_defaults = array( + $dbOrTableName => '', + 'Grant_priv' => 'N', + 'privs' => array('USAGE'), + 'Column_priv' => true, + ); + + // for the rights + $db_rights = array(); + + $db_rights_sql = '(' . implode(') UNION (', $db_rights_sqls) . ')' + . ' ORDER BY `' . $dbOrTableName . '` ASC'; + + $db_rights_result = $GLOBALS['dbi']->query($db_rights_sql); + + while ($db_rights_row = $GLOBALS['dbi']->fetchAssoc($db_rights_result)) { + $db_rights_row = array_merge($user_defaults, $db_rights_row); + if ($type == 'database') { + // only Db names in the table `mysql`.`db` uses wildcards + // as we are in the db specific rights display we want + // all db names escaped, also from other sources + $db_rights_row['Db'] = Util::escapeMysqlWildcards( + $db_rights_row['Db'] + ); + } + $db_rights[$db_rights_row[$dbOrTableName]] = $db_rights_row; + } + + $GLOBALS['dbi']->freeResult($db_rights_result); + + if ($type == 'database') { + $sql_query = 'SELECT * FROM `mysql`.`db`' + . $user_host_condition . ' ORDER BY `Db` ASC'; + } elseif ($type == 'table') { + $sql_query = 'SELECT `Table_name`,' + . ' `Table_priv`,' + . ' IF(`Column_priv` = _latin1 \'\', 0, 1)' + . ' AS \'Column_priv\'' + . ' FROM `mysql`.`tables_priv`' + . $user_host_condition + . ' ORDER BY `Table_name` ASC;'; + } else { + $sql_query = "SELECT `Routine_name`, `Proc_priv`" + . " FROM `mysql`.`procs_priv`" + . $user_host_condition + . " ORDER BY `Routine_name`"; + + } + + $result = $GLOBALS['dbi']->query($sql_query); + + while ($row = $GLOBALS['dbi']->fetchAssoc($result)) { + if (isset($db_rights[$row[$dbOrTableName]])) { + $db_rights[$row[$dbOrTableName]] + = array_merge($db_rights[$row[$dbOrTableName]], $row); + } else { + $db_rights[$row[$dbOrTableName]] = $row; + } + if ($type == 'database') { + // there are db specific rights for this user + // so we can drop this db rights + $db_rights[$row['Db']]['can_delete'] = true; + } + } + $GLOBALS['dbi']->freeResult($result); + return $db_rights; + } + + /** + * Parses Proc_priv data + * + * @param string $privs Proc_priv + * + * @return array + */ + public static function parseProcPriv($privs) + { + $result = array( + 'Alter_routine_priv' => 'N', + 'Execute_priv' => 'N', + 'Grant_priv' => 'N', + ); + foreach (explode(',', $privs) as $priv) { + if ($priv == 'Alter Routine') { + $result['Alter_routine_priv'] = 'Y'; + } else { + $result[$priv . '_priv'] = 'Y'; + } + } + return $result; + } + + /** + * Get a HTML table for display user's tabel specific or database specific rights + * + * @param string $username username + * @param string $hostname host name + * @param string $type database, table or routine + * @param string $dbname database name + * + * @return array $html_output + */ + public static function getHtmlForAllTableSpecificRights( + $username, $hostname, $type, $dbname = '' + ) { + $uiData = array( + 'database' => array( + 'form_id' => 'database_specific_priv', + 'sub_menu_label' => __('Database'), + 'legend' => __('Database-specific privileges'), + 'type_label' => __('Database'), + ), + 'table' => array( + 'form_id' => 'table_specific_priv', + 'sub_menu_label' => __('Table'), + 'legend' => __('Table-specific privileges'), + 'type_label' => __('Table'), + ), + 'routine' => array( + 'form_id' => 'routine_specific_priv', + 'sub_menu_label' => __('Routine'), + 'legend' => __('Routine-specific privileges'), + 'type_label' => __('Routine'), + ), + ); + + /** + * no db name given, so we want all privs for the given user + * db name was given, so we want all user specific rights for this db + */ + $db_rights = self::getUserSpecificRights($username, $hostname, $type, $dbname); + ksort($db_rights); + + $foundRows = array(); + $privileges = array(); + foreach ($db_rights as $row) { + $onePrivilege = array(); + + $paramTableName = ''; + $paramRoutineName = ''; + + if ($type == 'database') { + $name = $row['Db']; + $onePrivilege['grant'] = $row['Grant_priv'] == 'Y'; + $onePrivilege['table_privs'] = ! empty($row['Table_priv']) + || ! empty($row['Column_priv']); + $onePrivilege['privileges'] = join(',', self::extractPrivInfo($row, true)); + + $paramDbName = $row['Db']; + + } elseif ($type == 'table') { + $name = $row['Table_name']; + $onePrivilege['grant'] = in_array( + 'Grant', + explode(',', $row['Table_priv']) + ); + $onePrivilege['column_privs'] = ! empty($row['Column_priv']); + $onePrivilege['privileges'] = join(',', self::extractPrivInfo($row, true)); + + $paramDbName = $dbname; + $paramTableName = $row['Table_name']; + + } else { // routine + $name = $row['Routine_name']; + $onePrivilege['grant'] = in_array( + 'Grant', + explode(',', $row['Proc_priv']) + ); + + $privs = self::parseProcPriv($row['Proc_priv']); + $onePrivilege['privileges'] = join( + ',', + self::extractPrivInfo($privs, true) + ); + + $paramDbName = $dbname; + $paramRoutineName = $row['Routine_name']; + } + + $foundRows[] = $name; + $onePrivilege['name'] = $name; + + $onePrivilege['edit_link'] = ''; + if ($GLOBALS['is_grantuser']) { + $onePrivilege['edit_link'] = self::getUserLink( + 'edit', + $username, + $hostname, + $paramDbName, + $paramTableName, + $paramRoutineName + ); + } + + $onePrivilege['revoke_link'] = ''; + if ($type != 'database' || ! empty($row['can_delete'])) { + $onePrivilege['revoke_link'] = self::getUserLink( + 'revoke', + $username, + $hostname, + $paramDbName, + $paramTableName, + $paramRoutineName + ); + } + + $privileges[] = $onePrivilege; + } + + $data = $uiData[$type]; + $data['privileges'] = $privileges; + $data['username'] = $username; + $data['hostname'] = $hostname; + $data['database'] = $dbname; + $data['type'] = $type; + + if ($type == 'database') { + + // we already have the list of databases from libraries/common.inc.php + // via $pma = new PMA; + $pred_db_array = $GLOBALS['dblist']->databases; + $databases_to_skip = array('information_schema', 'performance_schema'); + + $databases = array(); + if (! empty($pred_db_array)) { + foreach ($pred_db_array as $current_db) { + if (in_array($current_db, $databases_to_skip)) { + continue; + } + $current_db_escaped = Util::escapeMysqlWildcards($current_db); + // cannot use array_diff() once, outside of the loop, + // because the list of databases has special characters + // already escaped in $foundRows, + // contrary to the output of SHOW DATABASES + if (! in_array($current_db_escaped, $foundRows)) { + $databases[] = $current_db; + } + } + } + $data['databases'] = $databases; + + } elseif ($type == 'table') { + $result = @$GLOBALS['dbi']->tryQuery( + "SHOW TABLES FROM " . Util::backquote($dbname), + DatabaseInterface::CONNECT_USER, + DatabaseInterface::QUERY_STORE + ); + + $tables = array(); + if ($result) { + while ($row = $GLOBALS['dbi']->fetchRow($result)) { + if (! in_array($row[0], $foundRows)) { + $tables[] = $row[0]; + } + } + $GLOBALS['dbi']->freeResult($result); + } + $data['tables'] = $tables; + + } else { // routine + $routineData = $GLOBALS['dbi']->getRoutines($dbname); + + $routines = array(); + foreach ($routineData as $routine) { + if (! in_array($routine['name'], $foundRows)) { + $routines[] = $routine['name']; + } + } + $data['routines'] = $routines; + } + + $html_output = Template::get('privileges/privileges_summary') + ->render($data); + + return $html_output; + } + + /** + * Get HTML for display the users overview + * (if less than 50 users, display them immediately) + * + * @param array $result ran sql query + * @param array $db_rights user's database rights array + * @param string $pmaThemeImage a image source link + * @param string $text_dir text directory + * + * @return string HTML snippet + */ + public static function getUsersOverview($result, array $db_rights, $pmaThemeImage, $text_dir) + { + while ($row = $GLOBALS['dbi']->fetchAssoc($result)) { + $row['privs'] = self::extractPrivInfo($row, true); + $db_rights[$row['User']][$row['Host']] = $row; + } + $GLOBALS['dbi']->freeResult($result); + $user_group_count = 0; + if ($GLOBALS['cfgRelation']['menuswork']) { + $user_group_count = self::getUserGroupCount(); + } + + $html_output + = '
    ' . "\n" + . Url::getHiddenInputs('', '') + . '
    ' + . '' . "\n" + . '' . "\n" + . '' . "\n" + . '' . "\n" + . '' . "\n" + . '' . "\n" + . '' . "\n"; + if ($GLOBALS['cfgRelation']['menuswork']) { + $html_output .= '' . "\n"; + } + $html_output .= '' . "\n" + . '' . "\n" + . '' . "\n" + . '' . "\n"; + + $html_output .= '' . "\n"; + $html_output .= self::getHtmlTableBodyForUserRights($db_rights); + $html_output .= '' + . '
    ' . __('User name') . '' . __('Host name') . '' . __('Password') . '' . __('Global privileges') . ' ' + . Util::showHint( + __('Note: MySQL privilege names are expressed in English.') + ) + . '' . __('User group') . '' . __('Grant') . '' + . __('Action') . '
    ' . "\n"; + + $html_output .= '
    ' + . Template::get('select_all') + ->render( + array( + 'pma_theme_image' => $pmaThemeImage, + 'text_dir' => $text_dir, + 'form_name' => 'usersForm', + ) + ) . "\n"; + $html_output .= Util::getButtonOrImage( + 'submit_mult', 'mult_submit', + __('Export'), 'b_tblexport', 'export' + ); + $html_output .= ''; + $html_output .= '
    ' + . '
    '; + + // add/delete user fieldset + $html_output .= self::getFieldsetForAddDeleteUser(); + $html_output .= '
    ' . "\n"; + + return $html_output; + } + + /** + * Get table body for 'tableuserrights' table in userform + * + * @param array $db_rights user's database rights array + * + * @return string HTML snippet + */ + public static function getHtmlTableBodyForUserRights(array $db_rights) + { + $relation = new Relation(); + $cfgRelation = $relation->getRelationsParam(); + if ($cfgRelation['menuswork']) { + $users_table = Util::backquote($cfgRelation['db']) + . "." . Util::backquote($cfgRelation['users']); + $sql_query = 'SELECT * FROM ' . $users_table; + $result = $relation->queryAsControlUser($sql_query, false); + $group_assignment = array(); + if ($result) { + while ($row = $GLOBALS['dbi']->fetchAssoc($result)) { + $group_assignment[$row['username']] = $row['usergroup']; + } + } + $GLOBALS['dbi']->freeResult($result); + + $user_group_count = self::getUserGroupCount(); + } + + $index_checkbox = 0; + $html_output = ''; + foreach ($db_rights as $user) { + ksort($user); + foreach ($user as $host) { + $index_checkbox++; + $html_output .= '' + . "\n"; + $html_output .= '' + . '' . "\n"; + + $html_output .= '' . "\n" + . '' . htmlspecialchars($host['Host']) . '' . "\n"; + + $html_output .= ''; + + $password_column = 'Password'; + + $check_plugin_query = "SELECT * FROM `mysql`.`user` WHERE " + . "`User` = '" . $host['User'] . "' AND `Host` = '" + . $host['Host'] . "'"; + $res = $GLOBALS['dbi']->fetchSingleRow($check_plugin_query); + + if ((isset($res['authentication_string']) + && ! empty($res['authentication_string'])) + || (isset($res['Password']) + && ! empty($res['Password'])) + ) { + $host[$password_column] = 'Y'; + } else { + $host[$password_column] = 'N'; + } + + switch ($host[$password_column]) { + case 'Y': + $html_output .= __('Yes'); + break; + case 'N': + $html_output .= '' . __('No') + . ''; + break; + // this happens if this is a definition not coming from mysql.user + default: + $html_output .= '--'; // in future version, replace by "not present" + break; + } // end switch + + if (! isset($host['Select_priv'])) { + $html_output .= Util::showHint( + __('The selected user was not found in the privilege table.') + ); + } + + $html_output .= '' . "\n"; + + $html_output .= '' . "\n" + . '' . implode(',' . "\n" . ' ', $host['privs']) . "\n" + . '' . "\n"; + if ($cfgRelation['menuswork']) { + $html_output .= '' . "\n" + . (isset($group_assignment[$host['User']]) + ? htmlspecialchars($group_assignment[$host['User']]) + : '' + ) + . '' . "\n"; + } + $html_output .= '' + . ($host['Grant_priv'] == 'Y' ? __('Yes') : __('No')) + . '' . "\n"; + + if ($GLOBALS['is_grantuser']) { + $html_output .= '' + . self::getUserLink( + 'edit', + $host['User'], + $host['Host'] + ) + . ''; + } + if ($cfgRelation['menuswork'] && $user_group_count > 0) { + if (empty($host['User'])) { + $html_output .= ''; + } else { + $html_output .= '' + . self::getUserGroupEditLink($host['User']) + . ''; + } + } + $html_output .= '' + . self::getUserLink( + 'export', + $host['User'], + $host['Host'], + '', + '', + '', + isset($_GET['initial']) ? $_GET['initial'] : '' + ) + . ''; + $html_output .= ''; + } + } + return $html_output; + } + + /** + * Get HTML fieldset for Add/Delete user + * + * @return string HTML snippet + */ + public static function getFieldsetForAddDeleteUser() + { + $html_output = self::getAddUserHtmlFieldset(); + + $html_output .= Template::get('privileges/delete_user_fieldset') + ->render(array()); + + return $html_output; + } + + /** + * Get HTML for Displays the initials + * + * @param array $array_initials array for all initials, even non A-Z + * + * @return string HTML snippet + */ + public static function getHtmlForInitials(array $array_initials) + { + // initialize to false the letters A-Z + for ($letter_counter = 1; $letter_counter < 27; $letter_counter++) { + if (! isset($array_initials[mb_chr($letter_counter + 64)])) { + $array_initials[mb_chr($letter_counter + 64)] = false; + } + } + + $initials = $GLOBALS['dbi']->tryQuery( + 'SELECT DISTINCT UPPER(LEFT(`User`,1)) FROM `user`' + . ' ORDER BY UPPER(LEFT(`User`,1)) ASC', + DatabaseInterface::CONNECT_USER, + DatabaseInterface::QUERY_STORE + ); + if ($initials) { + while (list($tmp_initial) = $GLOBALS['dbi']->fetchRow($initials)) { + $array_initials[$tmp_initial] = true; + } + } + + // Display the initials, which can be any characters, not + // just letters. For letters A-Z, we add the non-used letters + // as greyed out. + + uksort($array_initials, "strnatcasecmp"); + + $html_output = Template::get('privileges/initials_row') + ->render( + array( + 'array_initials' => $array_initials, + 'initial' => isset($_GET['initial']) ? $_GET['initial'] : null, + ) + ); + + return $html_output; + } + + /** + * Get the database rights array for Display user overview + * + * @return array $db_rights database rights array + */ + public static function getDbRightsForUserOverview() + { + // we also want users not in table `user` but in other table + $tables = $GLOBALS['dbi']->fetchResult('SHOW TABLES FROM `mysql`;'); + + $tablesSearchForUsers = array( + 'user', 'db', 'tables_priv', 'columns_priv', 'procs_priv', + ); + + $db_rights_sqls = array(); + foreach ($tablesSearchForUsers as $table_search_in) { + if (in_array($table_search_in, $tables)) { + $db_rights_sqls[] = 'SELECT DISTINCT `User`, `Host` FROM `mysql`.`' + . $table_search_in . '` ' + . (isset($_GET['initial']) + ? self::rangeOfUsers($_GET['initial']) + : ''); + } + } + $user_defaults = array( + 'User' => '', + 'Host' => '%', + 'Password' => '?', + 'Grant_priv' => 'N', + 'privs' => array('USAGE'), + ); + + // for the rights + $db_rights = array(); + + $db_rights_sql = '(' . implode(') UNION (', $db_rights_sqls) . ')' + . ' ORDER BY `User` ASC, `Host` ASC'; + + $db_rights_result = $GLOBALS['dbi']->query($db_rights_sql); + + while ($db_rights_row = $GLOBALS['dbi']->fetchAssoc($db_rights_result)) { + $db_rights_row = array_merge($user_defaults, $db_rights_row); + $db_rights[$db_rights_row['User']][$db_rights_row['Host']] + = $db_rights_row; + } + $GLOBALS['dbi']->freeResult($db_rights_result); + ksort($db_rights); + + return $db_rights; + } + + /** + * Delete user and get message and sql query for delete user in privileges + * + * @param array $queries queries + * + * @return array Message + */ + public static function deleteUser(array $queries) + { + $sql_query = ''; + if (empty($queries)) { + $message = Message::error(__('No users selected for deleting!')); + } else { + if ($_POST['mode'] == 3) { + $queries[] = '# ' . __('Reloading the privileges') . ' …'; + $queries[] = 'FLUSH PRIVILEGES;'; + } + $drop_user_error = ''; + foreach ($queries as $sql_query) { + if ($sql_query[0] != '#') { + if (! $GLOBALS['dbi']->tryQuery($sql_query)) { + $drop_user_error .= $GLOBALS['dbi']->getError() . "\n"; + } + } + } + // tracking sets this, causing the deleted db to be shown in navi + unset($GLOBALS['db']); + + $sql_query = join("\n", $queries); + if (! empty($drop_user_error)) { + $message = Message::rawError($drop_user_error); + } else { + $message = Message::success( + __('The selected users have been deleted successfully.') + ); + } + } + return array($sql_query, $message); + } + + /** + * Update the privileges and return the success or error message + * + * @param string $username username + * @param string $hostname host name + * @param string $tablename table name + * @param string $dbname database name + * @param string $itemType item type + * + * @return Message success message or error message for update + */ + public static function updatePrivileges($username, $hostname, $tablename, $dbname, $itemType) + { + $db_and_table = self::wildcardEscapeForGrant($dbname, $tablename); + + $sql_query0 = 'REVOKE ALL PRIVILEGES ON ' . $itemType . ' ' . $db_and_table + . ' FROM \'' . $GLOBALS['dbi']->escapeString($username) + . '\'@\'' . $GLOBALS['dbi']->escapeString($hostname) . '\';'; + + if (! isset($_POST['Grant_priv']) || $_POST['Grant_priv'] != 'Y') { + $sql_query1 = 'REVOKE GRANT OPTION ON ' . $itemType . ' ' . $db_and_table + . ' FROM \'' . $GLOBALS['dbi']->escapeString($username) . '\'@\'' + . $GLOBALS['dbi']->escapeString($hostname) . '\';'; + } else { + $sql_query1 = ''; + } + + // Should not do a GRANT USAGE for a table-specific privilege, it + // causes problems later (cannot revoke it) + if (! (strlen($tablename) > 0 + && 'USAGE' == implode('', self::extractPrivInfo())) + ) { + $sql_query2 = 'GRANT ' . join(', ', self::extractPrivInfo()) + . ' ON ' . $itemType . ' ' . $db_and_table + . ' TO \'' . $GLOBALS['dbi']->escapeString($username) . '\'@\'' + . $GLOBALS['dbi']->escapeString($hostname) . '\''; + + if (strlen($dbname) === 0) { + // add REQUIRE clause + $sql_query2 .= self::getRequireClause(); + } + + if ((isset($_POST['Grant_priv']) && $_POST['Grant_priv'] == 'Y') + || (strlen($dbname) === 0 + && (isset($_POST['max_questions']) || isset($_POST['max_connections']) + || isset($_POST['max_updates']) + || isset($_POST['max_user_connections']))) + ) { + $sql_query2 .= self::getWithClauseForAddUserAndUpdatePrivs(); + } + $sql_query2 .= ';'; + } + if (! $GLOBALS['dbi']->tryQuery($sql_query0)) { + // This might fail when the executing user does not have + // ALL PRIVILEGES himself. + // See https://github.com/phpmyadmin/phpmyadmin/issues/9673 + $sql_query0 = ''; + } + if (! empty($sql_query1) && ! $GLOBALS['dbi']->tryQuery($sql_query1)) { + // this one may fail, too... + $sql_query1 = ''; + } + if (! empty($sql_query2)) { + $GLOBALS['dbi']->query($sql_query2); + } else { + $sql_query2 = ''; + } + $sql_query = $sql_query0 . ' ' . $sql_query1 . ' ' . $sql_query2; + $message = Message::success(__('You have updated the privileges for %s.')); + $message->addParam('\'' . $username . '\'@\'' . $hostname . '\''); + + return array($sql_query, $message); + } + + /** + * Get List of information: Changes / copies a user + * + * @return array + */ + public static function getDataForChangeOrCopyUser() + { + $queries = null; + $password = null; + + if (isset($_POST['change_copy'])) { + $user_host_condition = ' WHERE `User` = ' + . "'" . $GLOBALS['dbi']->escapeString($_POST['old_username']) . "'" + . ' AND `Host` = ' + . "'" . $GLOBALS['dbi']->escapeString($_POST['old_hostname']) . "';"; + $row = $GLOBALS['dbi']->fetchSingleRow( + 'SELECT * FROM `mysql`.`user` ' . $user_host_condition + ); + if (! $row) { + $response = Response::getInstance(); + $response->addHTML( + Message::notice(__('No user found.'))->getDisplay() + ); + unset($_POST['change_copy']); + } else { + extract($row, EXTR_OVERWRITE); + foreach ($row as $key => $value) { + $GLOBALS[$key] = $value; + } + $serverVersion = $GLOBALS['dbi']->getVersion(); + // Recent MySQL versions have the field "Password" in mysql.user, + // so the previous extract creates $Password but this script + // uses $password + if (! isset($password) && isset($Password)) { + $password = $Password; + } + if (Util::getServerType() == 'MySQL' + && $serverVersion >= 50606 + && $serverVersion < 50706 + && ((isset($authentication_string) + && empty($password)) + || (isset($plugin) + && $plugin == 'sha256_password')) + ) { + $password = $authentication_string; + } + + if (Util::getServerType() == 'MariaDB' + && $serverVersion >= 50500 + && isset($authentication_string) + && empty($password) + ) { + $password = $authentication_string; + } + + // Always use 'authentication_string' column + // for MySQL 5.7.6+ since it does not have + // the 'password' column at all + if (in_array(Util::getServerType(), array('MySQL', 'Percona Server')) + && $serverVersion >= 50706 + && isset($authentication_string) + ) { + $password = $authentication_string; + } + + $queries = array(); + } + } + + return array($queries, $password); + } + + /** + * Update Data for information: Deletes users + * + * @param array $queries queries array + * + * @return array + */ + public static function getDataForDeleteUsers($queries) + { + if (isset($_POST['change_copy'])) { + $selected_usr = array( + $_POST['old_username'] . '&#27;' . $_POST['old_hostname'] + ); + } else { + $selected_usr = $_POST['selected_usr']; + $queries = array(); + } + + // this happens, was seen in https://reports.phpmyadmin.net/reports/view/17146 + if (! is_array($selected_usr)) { + return array(); + } + + foreach ($selected_usr as $each_user) { + list($this_user, $this_host) = explode('&#27;', $each_user); + $queries[] = '# ' + . sprintf( + __('Deleting %s'), + '\'' . $this_user . '\'@\'' . $this_host . '\'' + ) + . ' ...'; + $queries[] = 'DROP USER \'' + . $GLOBALS['dbi']->escapeString($this_user) + . '\'@\'' . $GLOBALS['dbi']->escapeString($this_host) . '\';'; + RelationCleanup::user($this_user); + + if (isset($_POST['drop_users_db'])) { + $queries[] = 'DROP DATABASE IF EXISTS ' + . Util::backquote($this_user) . ';'; + $GLOBALS['reload'] = true; + } + } + return $queries; + } + + /** + * update Message For Reload + * + * @return array + */ + public static function updateMessageForReload() + { + $message = null; + if (isset($_GET['flush_privileges'])) { + $sql_query = 'FLUSH PRIVILEGES;'; + $GLOBALS['dbi']->query($sql_query); + $message = Message::success( + __('The privileges were reloaded successfully.') + ); + } + + if (isset($_GET['validate_username'])) { + $message = Message::success(); + } + + return $message; + } + + /** + * update Data For Queries from queries_for_display + * + * @param array $queries queries array + * @param array|null $queries_for_display queries array for display + * + * @return null + */ + public static function getDataForQueries(array $queries, $queries_for_display) + { + $tmp_count = 0; + foreach ($queries as $sql_query) { + if ($sql_query[0] != '#') { + $GLOBALS['dbi']->query($sql_query); + } + // when there is a query containing a hidden password, take it + // instead of the real query sent + if (isset($queries_for_display[$tmp_count])) { + $queries[$tmp_count] = $queries_for_display[$tmp_count]; + } + $tmp_count++; + } + + return $queries; + } + + /** + * update Data for information: Adds a user + * + * @param string $dbname db name + * @param string $username user name + * @param string $hostname host name + * @param string $password password + * @param bool $is_menuwork is_menuwork set? + * + * @return array + */ + public static function addUser( + $dbname, $username, $hostname, + $password, $is_menuwork + ) { + $_add_user_error = false; + $message = null; + $queries = null; + $queries_for_display = null; + $sql_query = null; + + if (!isset($_POST['adduser_submit']) && !isset($_POST['change_copy'])) { + return array( + $message, $queries, $queries_for_display, $sql_query, $_add_user_error + ); + } + + $sql_query = ''; + if ($_POST['pred_username'] == 'any') { + $username = ''; + } + switch ($_POST['pred_hostname']) { + case 'any': + $hostname = '%'; + break; + case 'localhost': + $hostname = 'localhost'; + break; + case 'hosttable': + $hostname = ''; + break; + case 'thishost': + $_user_name = $GLOBALS['dbi']->fetchValue('SELECT USER()'); + $hostname = mb_substr( + $_user_name, + (mb_strrpos($_user_name, '@') + 1) + ); + unset($_user_name); + break; + } + $sql = "SELECT '1' FROM `mysql`.`user`" + . " WHERE `User` = '" . $GLOBALS['dbi']->escapeString($username) . "'" + . " AND `Host` = '" . $GLOBALS['dbi']->escapeString($hostname) . "';"; + if ($GLOBALS['dbi']->fetchValue($sql) == 1) { + $message = Message::error(__('The user %s already exists!')); + $message->addParam('[em]\'' . $username . '\'@\'' . $hostname . '\'[/em]'); + $_GET['adduser'] = true; + $_add_user_error = true; + + return array( + $message, + $queries, + $queries_for_display, + $sql_query, + $_add_user_error + ); + } + + list( + $create_user_real, $create_user_show, $real_sql_query, $sql_query, + $password_set_real, $password_set_show, + $alter_real_sql_query, + $alter_sql_query + ) = self::getSqlQueriesForDisplayAndAddUser( + $username, $hostname, (isset($password) ? $password : '') + ); + + if (empty($_POST['change_copy'])) { + $_error = false; + + if (isset($create_user_real)) { + if (!$GLOBALS['dbi']->tryQuery($create_user_real)) { + $_error = true; + } + if (isset($password_set_real) && !empty($password_set_real) + && isset($_POST['authentication_plugin']) + ) { + self::setProperPasswordHashing( + $_POST['authentication_plugin'] + ); + if ($GLOBALS['dbi']->tryQuery($password_set_real)) { + $sql_query .= $password_set_show; + } + } + $sql_query = $create_user_show . $sql_query; + } + + list($sql_query, $message) = self::addUserAndCreateDatabase( + $_error, + $real_sql_query, + $sql_query, + $username, + $hostname, + isset($dbname) ? $dbname : null, + $alter_real_sql_query, + $alter_sql_query + ); + if (!empty($_POST['userGroup']) && $is_menuwork) { + self::setUserGroup($GLOBALS['username'], $_POST['userGroup']); + } + + return array( + $message, + $queries, + $queries_for_display, + $sql_query, + $_add_user_error + ); + } + + // Copy the user group while copying a user + $old_usergroup = + isset($_POST['old_usergroup']) ? $_POST['old_usergroup'] : null; + self::setUserGroup($_POST['username'], $old_usergroup); + + if (isset($create_user_real)) { + $queries[] = $create_user_real; + } + $queries[] = $real_sql_query; + + if (isset($password_set_real) && ! empty($password_set_real) + && isset($_POST['authentication_plugin']) + ) { + self::setProperPasswordHashing( + $_POST['authentication_plugin'] + ); + + $queries[] = $password_set_real; + } + // we put the query containing the hidden password in + // $queries_for_display, at the same position occupied + // by the real query in $queries + $tmp_count = count($queries); + if (isset($create_user_real)) { + $queries_for_display[$tmp_count - 2] = $create_user_show; + } + if (isset($password_set_real) && ! empty($password_set_real)) { + $queries_for_display[$tmp_count - 3] = $create_user_show; + $queries_for_display[$tmp_count - 2] = $sql_query; + $queries_for_display[$tmp_count - 1] = $password_set_show; + } else { + $queries_for_display[$tmp_count - 1] = $sql_query; + } + + return array( + $message, $queries, $queries_for_display, $sql_query, $_add_user_error + ); + } + + /** + * Sets proper value of `old_passwords` according to + * the authentication plugin selected + * + * @param string $auth_plugin authentication plugin selected + * + * @return void + */ + public static function setProperPasswordHashing($auth_plugin) + { + // Set the hashing method used by PASSWORD() + // to be of type depending upon $authentication_plugin + if ($auth_plugin == 'sha256_password') { + $GLOBALS['dbi']->tryQuery('SET `old_passwords` = 2;'); + } elseif ($auth_plugin == 'mysql_old_password') { + $GLOBALS['dbi']->tryQuery('SET `old_passwords` = 1;'); + } else { + $GLOBALS['dbi']->tryQuery('SET `old_passwords` = 0;'); + } + } + + /** + * Update DB information: DB, Table, isWildcard + * + * @return array + */ + public static function getDataForDBInfo() + { + $username = null; + $hostname = null; + $dbname = null; + $tablename = null; + $routinename = null; + $dbname_is_wildcard = null; + + if (isset($_REQUEST['username'])) { + $username = $_REQUEST['username']; + } + if (isset($_REQUEST['hostname'])) { + $hostname = $_REQUEST['hostname']; + } + /** + * Checks if a dropdown box has been used for selecting a database / table + */ + if (Core::isValid($_POST['pred_tablename'])) { + $tablename = $_POST['pred_tablename']; + } elseif (Core::isValid($_REQUEST['tablename'])) { + $tablename = $_REQUEST['tablename']; + } else { + unset($tablename); + } + + if (Core::isValid($_POST['pred_routinename'])) { + $routinename = $_POST['pred_routinename']; + } elseif (Core::isValid($_REQUEST['routinename'])) { + $routinename = $_REQUEST['routinename']; + } else { + unset($routinename); + } + + if (isset($_POST['pred_dbname'])) { + $is_valid_pred_dbname = true; + foreach ($_POST['pred_dbname'] as $key => $db_name) { + if (! Core::isValid($db_name)) { + $is_valid_pred_dbname = false; + break; + } + } + } + + if (isset($_REQUEST['dbname'])) { + $is_valid_dbname = true; + if (is_array($_REQUEST['dbname'])) { + foreach ($_REQUEST['dbname'] as $key => $db_name) { + if (! Core::isValid($db_name)) { + $is_valid_dbname = false; + break; + } + } + } else { + if (! Core::isValid($_REQUEST['dbname'])) { + $is_valid_dbname = false; + } + } + } + + if (isset($is_valid_pred_dbname) && $is_valid_pred_dbname) { + $dbname = $_POST['pred_dbname']; + // If dbname contains only one database. + if (count($dbname) == 1) { + $dbname = $dbname[0]; + } + } elseif (isset($is_valid_dbname) && $is_valid_dbname) { + $dbname = $_REQUEST['dbname']; + } else { + unset($dbname); + unset($tablename); + } + + if (isset($dbname)) { + if (is_array($dbname)) { + $db_and_table = $dbname; + foreach ($db_and_table as $key => $db_name) { + $db_and_table[$key] .= '.'; + } + } else { + $unescaped_db = Util::unescapeMysqlWildcards($dbname); + $db_and_table = Util::backquote($unescaped_db) . '.'; + } + if (isset($tablename)) { + $db_and_table .= Util::backquote($tablename); + } else { + if (is_array($db_and_table)) { + foreach ($db_and_table as $key => $db_name) { + $db_and_table[$key] .= '*'; + } + } else { + $db_and_table .= '*'; + } + } + } else { + $db_and_table = '*.*'; + } + + // check if given $dbname is a wildcard or not + if (isset($dbname)) { + //if (preg_match('/\\\\(?:_|%)/i', $dbname)) { + if (! is_array($dbname) && preg_match('/(?'; + + if (isset($_POST['selected_usr'])) { + // export privileges for selected users + $title = __('Privileges'); + + //For removing duplicate entries of users + $_POST['selected_usr'] = array_unique($_POST['selected_usr']); + + foreach ($_POST['selected_usr'] as $export_user) { + $export_username = mb_substr( + $export_user, 0, mb_strpos($export_user, '&') + ); + $export_hostname = mb_substr( + $export_user, mb_strrpos($export_user, ';') + 1 + ); + $export .= '# ' + . sprintf( + __('Privileges for %s'), + '`' . htmlspecialchars($export_username) + . '`@`' . htmlspecialchars($export_hostname) . '`' + ) + . "\n\n"; + $export .= self::getGrants($export_username, $export_hostname) . "\n"; + } + } else { + // export privileges for a single user + $title = __('User') . ' `' . htmlspecialchars($username) + . '`@`' . htmlspecialchars($hostname) . '`'; + $export .= self::getGrants($username, $hostname); + } + // remove trailing whitespace + $export = trim($export); + + $export .= ''; + + return array($title, $export); + } + + /** + * Get HTML for display Add userfieldset + * + * @param string $db the database + * @param string $table the table name + * + * @return string html output + */ + public static function getAddUserHtmlFieldset($db = '', $table = '') + { + if (!$GLOBALS['is_createuser']) { + return ''; + } + $rel_params = array(); + $url_params = array( + 'adduser' => 1 + ); + if (!empty($db)) { + $url_params['dbname'] + = $rel_params['checkprivsdb'] + = $db; + } + if (!empty($table)) { + $url_params['tablename'] + = $rel_params['checkprivstable'] + = $table; + } + + return Template::get('privileges/add_user_fieldset') + ->render( + array( + 'url_params' => $url_params, + 'rel_params' => $rel_params + ) + ); + } + + /** + * Get HTML header for display User's properties + * + * @param boolean $dbname_is_wildcard whether database name is wildcard or not + * @param string $url_dbname url database name that urlencode() string + * @param string $dbname database name + * @param string $username username + * @param string $hostname host name + * @param string $entity_name entity (table or routine) name + * @param string $entity_type optional, type of entity ('table' or 'routine') + * + * @return string $html_output + */ + public static function getHtmlHeaderForUserProperties( + $dbname_is_wildcard, $url_dbname, $dbname, + $username, $hostname, $entity_name, $entity_type='table' + ) { + $html_output = '

    ' . "\n" + . Util::getIcon('b_usredit') + . __('Edit privileges:') . ' ' + . __('User account'); + + if (! empty($dbname)) { + $html_output .= ' \'' . htmlspecialchars($username) + . '\'@\'' . htmlspecialchars($hostname) + . '\'' . "\n"; + + $html_output .= ' - '; + $html_output .= ($dbname_is_wildcard + || is_array($dbname) && count($dbname) > 1) + ? __('Databases') : __('Database'); + if (! empty($entity_name) && $entity_type === 'table') { + $html_output .= ' ' . htmlspecialchars($dbname) + . ''; + + $html_output .= ' - ' . __('Table') + . ' ' . htmlspecialchars($entity_name) . ''; + } elseif (! empty($entity_name)) { + $html_output .= ' ' . htmlspecialchars($dbname) + . ''; + + $html_output .= ' - ' . __('Routine') + . ' ' . htmlspecialchars($entity_name) . ''; + } else { + if (! is_array($dbname)) { + $dbname = array($dbname); + } + $html_output .= ' ' + . htmlspecialchars(implode(', ', $dbname)) + . ''; + } + + } else { + $html_output .= ' \'' . htmlspecialchars($username) + . '\'@\'' . htmlspecialchars($hostname) + . '\'' . "\n"; + + } + $html_output .= '

    ' . "\n"; + $cur_user = $GLOBALS['dbi']->getCurrentUser(); + $user = $username . '@' . $hostname; + // Add a short notice for the user + // to remind him that he is editing his own privileges + if ($user === $cur_user) { + $html_output .= Message::notice( + __( + 'Note: You are attempting to edit privileges of the ' + . 'user with which you are currently logged in.' + ) + )->getDisplay(); + } + return $html_output; + } + + /** + * Get HTML snippet for display user overview page + * + * @param string $pmaThemeImage a image source link + * @param string $text_dir text directory + * + * @return string $html_output + */ + public static function getHtmlForUserOverview($pmaThemeImage, $text_dir) + { + $html_output = '

    ' . "\n" + . Util::getIcon('b_usrlist') + . __('User accounts overview') . "\n" + . '

    ' . "\n"; + + $password_column = 'Password'; + $server_type = Util::getServerType(); + $serverVersion = $GLOBALS['dbi']->getVersion(); + if (($server_type == 'MySQL' || $server_type == 'Percona Server') + && $serverVersion >= 50706 + ) { + $password_column = 'authentication_string'; + } + // $sql_query is for the initial-filtered, + // $sql_query_all is for counting the total no. of users + + $sql_query = $sql_query_all = 'SELECT *,' . + " IF(`" . $password_column . "` = _latin1 '', 'N', 'Y') AS 'Password'" . + ' FROM `mysql`.`user`'; + + $sql_query .= (isset($_GET['initial']) + ? self::rangeOfUsers($_GET['initial']) + : ''); + + $sql_query .= ' ORDER BY `User` ASC, `Host` ASC;'; + $sql_query_all .= ' ;'; + + $res = $GLOBALS['dbi']->tryQuery( + $sql_query, + DatabaseInterface::CONNECT_USER, + DatabaseInterface::QUERY_STORE + ); + $res_all = $GLOBALS['dbi']->tryQuery( + $sql_query_all, + DatabaseInterface::CONNECT_USER, + DatabaseInterface::QUERY_STORE + ); + + if (! $res) { + // the query failed! This may have two reasons: + // - the user does not have enough privileges + // - the privilege tables use a structure of an earlier version. + // so let's try a more simple query + + $GLOBALS['dbi']->freeResult($res); + $GLOBALS['dbi']->freeResult($res_all); + $sql_query = 'SELECT * FROM `mysql`.`user`'; + $res = $GLOBALS['dbi']->tryQuery( + $sql_query, + DatabaseInterface::CONNECT_USER, + DatabaseInterface::QUERY_STORE + ); + + if (! $res) { + $html_output .= self::getHtmlForViewUsersError(); + $html_output .= self::getAddUserHtmlFieldset(); + } else { + // This message is hardcoded because I will replace it by + // a automatic repair feature soon. + $raw = 'Your privilege table structure seems to be older than' + . ' this MySQL version!
    ' + . 'Please run the mysql_upgrade command' + . ' that should be included in your MySQL server distribution' + . ' to solve this problem!'; + $html_output .= Message::rawError($raw)->getDisplay(); + } + $GLOBALS['dbi']->freeResult($res); + } else { + $db_rights = self::getDbRightsForUserOverview(); + // for all initials, even non A-Z + $array_initials = array(); + + foreach ($db_rights as $right) { + foreach ($right as $account) { + if (empty($account['User']) && $account['Host'] == 'localhost') { + $html_output .= Message::notice( + __( + 'A user account allowing any user from localhost to ' + . 'connect is present. This will prevent other users ' + . 'from connecting if the host part of their account ' + . 'allows a connection from any (%) host.' + ) + . Util::showMySQLDocu('problems-connecting') + )->getDisplay(); + break 2; + } + } + } + + /** + * Displays the initials + * Also not necessary if there is less than 20 privileges + */ + if ($GLOBALS['dbi']->numRows($res_all) > 20) { + $html_output .= self::getHtmlForInitials($array_initials); + } + + /** + * Display the user overview + * (if less than 50 users, display them immediately) + */ + if (isset($_GET['initial']) + || isset($_GET['showall']) + || $GLOBALS['dbi']->numRows($res) < 50 + ) { + $html_output .= self::getUsersOverview( + $res, $db_rights, $pmaThemeImage, $text_dir + ); + } else { + $html_output .= self::getAddUserHtmlFieldset(); + } // end if (display overview) + + $response = Response::getInstance(); + if (! $response->isAjax() + || ! empty($_REQUEST['ajax_page_request']) + ) { + if ($GLOBALS['is_reload_priv']) { + $flushnote = new Message( + __( + 'Note: phpMyAdmin gets the users’ privileges directly ' + . 'from MySQL’s privilege tables. The content of these ' + . 'tables may differ from the privileges the server uses, ' + . 'if they have been changed manually. In this case, ' + . 'you should %sreload the privileges%s before you continue.' + ), + Message::NOTICE + ); + $flushnote->addParamHtml( + '' + ); + $flushnote->addParamHtml(''); + } else { + $flushnote = new Message( + __( + 'Note: phpMyAdmin gets the users’ privileges directly ' + . 'from MySQL’s privilege tables. The content of these ' + . 'tables may differ from the privileges the server uses, ' + . 'if they have been changed manually. In this case, ' + . 'the privileges have to be reloaded but currently, you ' + . 'don\'t have the RELOAD privilege.' + ) + . Util::showMySQLDocu( + 'privileges-provided', + false, + 'priv_reload' + ), + Message::NOTICE + ); + } + $html_output .= $flushnote->getDisplay(); + } + } + + return $html_output; + } + + /** + * Get HTML snippet for display user properties + * + * @param boolean $dbname_is_wildcard whether database name is wildcard or not + * @param string $url_dbname url database name that urlencode() string + * @param string $username username + * @param string $hostname host name + * @param string $dbname database name + * @param string $tablename table name + * + * @return string $html_output + */ + public static function getHtmlForUserProperties($dbname_is_wildcard, $url_dbname, + $username, $hostname, $dbname, $tablename + ) { + $html_output = '
    '; + $html_output .= self::getHtmlHeaderForUserProperties( + $dbname_is_wildcard, $url_dbname, $dbname, $username, $hostname, + $tablename, 'table' + ); + + $sql = "SELECT '1' FROM `mysql`.`user`" + . " WHERE `User` = '" . $GLOBALS['dbi']->escapeString($username) . "'" + . " AND `Host` = '" . $GLOBALS['dbi']->escapeString($hostname) . "';"; + + $user_does_not_exists = (bool) ! $GLOBALS['dbi']->fetchValue($sql); + + if ($user_does_not_exists) { + $html_output .= Message::error( + __('The selected user was not found in the privilege table.') + )->getDisplay(); + $html_output .= self::getHtmlForLoginInformationFields(); + } + + $_params = array( + 'username' => $username, + 'hostname' => $hostname, + ); + if (! is_array($dbname) && strlen($dbname) > 0) { + $_params['dbname'] = $dbname; + if (strlen($tablename) > 0) { + $_params['tablename'] = $tablename; + } + } else { + $_params['dbname'] = $dbname; + } + + $html_output .= '' . "\n"; + + if (! is_array($dbname) && strlen($tablename) === 0 + && empty($dbname_is_wildcard) + ) { + // no table name was given, display all table specific rights + // but only if $dbname contains no wildcards + if (strlen($dbname) === 0) { + $html_output .= self::getHtmlForAllTableSpecificRights( + $username, $hostname, 'database' + ); + } else { + // unescape wildcards in dbname at table level + $unescaped_db = Util::unescapeMysqlWildcards($dbname); + + $html_output .= self::getHtmlForAllTableSpecificRights( + $username, $hostname, 'table', $unescaped_db + ); + $html_output .= self::getHtmlForAllTableSpecificRights( + $username, $hostname, 'routine', $unescaped_db + ); + } + } + + // Provide a line with links to the relevant database and table + if (! is_array($dbname) && strlen($dbname) > 0 && empty($dbname_is_wildcard)) { + $html_output .= self::getLinkToDbAndTable($url_dbname, $dbname, $tablename); + + } + + if (! is_array($dbname) && strlen($dbname) === 0 && ! $user_does_not_exists) { + //change login information + $html_output .= ChangePassword::getHtml( + 'edit_other', + $username, + $hostname + ); + $html_output .= self::getChangeLoginInformationHtmlForm($username, $hostname); + } + $html_output .= '
    '; + + return $html_output; + } + + /** + * Get queries for Table privileges to change or copy user + * + * @param string $user_host_condition user host condition to + * select relevant table privileges + * @param array $queries queries array + * @param string $username username + * @param string $hostname host name + * + * @return array $queries + */ + public static function getTablePrivsQueriesForChangeOrCopyUser($user_host_condition, + array $queries, $username, $hostname + ) { + $res = $GLOBALS['dbi']->query( + 'SELECT `Db`, `Table_name`, `Table_priv` FROM `mysql`.`tables_priv`' + . $user_host_condition, + DatabaseInterface::CONNECT_USER, + DatabaseInterface::QUERY_STORE + ); + while ($row = $GLOBALS['dbi']->fetchAssoc($res)) { + + $res2 = $GLOBALS['dbi']->query( + 'SELECT `Column_name`, `Column_priv`' + . ' FROM `mysql`.`columns_priv`' + . ' WHERE `User`' + . ' = \'' . $GLOBALS['dbi']->escapeString($_POST['old_username']) . "'" + . ' AND `Host`' + . ' = \'' . $GLOBALS['dbi']->escapeString($_POST['old_username']) . '\'' + . ' AND `Db`' + . ' = \'' . $GLOBALS['dbi']->escapeString($row['Db']) . "'" + . ' AND `Table_name`' + . ' = \'' . $GLOBALS['dbi']->escapeString($row['Table_name']) . "'" + . ';', + DatabaseInterface::CONNECT_USER, + DatabaseInterface::QUERY_STORE + ); + + $tmp_privs1 = self::extractPrivInfo($row); + $tmp_privs2 = array( + 'Select' => array(), + 'Insert' => array(), + 'Update' => array(), + 'References' => array() + ); + + while ($row2 = $GLOBALS['dbi']->fetchAssoc($res2)) { + $tmp_array = explode(',', $row2['Column_priv']); + if (in_array('Select', $tmp_array)) { + $tmp_privs2['Select'][] = $row2['Column_name']; + } + if (in_array('Insert', $tmp_array)) { + $tmp_privs2['Insert'][] = $row2['Column_name']; + } + if (in_array('Update', $tmp_array)) { + $tmp_privs2['Update'][] = $row2['Column_name']; + } + if (in_array('References', $tmp_array)) { + $tmp_privs2['References'][] = $row2['Column_name']; + } + } + if (count($tmp_privs2['Select']) > 0 && ! in_array('SELECT', $tmp_privs1)) { + $tmp_privs1[] = 'SELECT (`' . join('`, `', $tmp_privs2['Select']) . '`)'; + } + if (count($tmp_privs2['Insert']) > 0 && ! in_array('INSERT', $tmp_privs1)) { + $tmp_privs1[] = 'INSERT (`' . join('`, `', $tmp_privs2['Insert']) . '`)'; + } + if (count($tmp_privs2['Update']) > 0 && ! in_array('UPDATE', $tmp_privs1)) { + $tmp_privs1[] = 'UPDATE (`' . join('`, `', $tmp_privs2['Update']) . '`)'; + } + if (count($tmp_privs2['References']) > 0 + && ! in_array('REFERENCES', $tmp_privs1) + ) { + $tmp_privs1[] + = 'REFERENCES (`' . join('`, `', $tmp_privs2['References']) . '`)'; + } + + $queries[] = 'GRANT ' . join(', ', $tmp_privs1) + . ' ON ' . Util::backquote($row['Db']) . '.' + . Util::backquote($row['Table_name']) + . ' TO \'' . $GLOBALS['dbi']->escapeString($username) + . '\'@\'' . $GLOBALS['dbi']->escapeString($hostname) . '\'' + . (in_array('Grant', explode(',', $row['Table_priv'])) + ? ' WITH GRANT OPTION;' + : ';'); + } + return $queries; + } + + /** + * Get queries for database specific privileges for change or copy user + * + * @param array $queries queries array with string + * @param string $username username + * @param string $hostname host name + * + * @return array $queries + */ + public static function getDbSpecificPrivsQueriesForChangeOrCopyUser( + array $queries, $username, $hostname + ) { + $user_host_condition = ' WHERE `User`' + . ' = \'' . $GLOBALS['dbi']->escapeString($_POST['old_username']) . "'" + . ' AND `Host`' + . ' = \'' . $GLOBALS['dbi']->escapeString($_POST['old_hostname']) . '\';'; + + $res = $GLOBALS['dbi']->query( + 'SELECT * FROM `mysql`.`db`' . $user_host_condition + ); + + while ($row = $GLOBALS['dbi']->fetchAssoc($res)) { + $queries[] = 'GRANT ' . join(', ', self::extractPrivInfo($row)) + . ' ON ' . Util::backquote($row['Db']) . '.*' + . ' TO \'' . $GLOBALS['dbi']->escapeString($username) + . '\'@\'' . $GLOBALS['dbi']->escapeString($hostname) . '\'' + . ($row['Grant_priv'] == 'Y' ? ' WITH GRANT OPTION;' : ';'); + } + $GLOBALS['dbi']->freeResult($res); + + $queries = self::getTablePrivsQueriesForChangeOrCopyUser( + $user_host_condition, $queries, $username, $hostname + ); + + return $queries; + } + + /** + * Prepares queries for adding users and + * also create database and return query and message + * + * @param boolean $_error whether user create or not + * @param string $real_sql_query SQL query for add a user + * @param string $sql_query SQL query to be displayed + * @param string $username username + * @param string $hostname host name + * @param string $dbname database name + * @param string $alter_real_sql_query SQL query for ALTER USER + * @param string $alter_sql_query SQL query for ALTER USER to be displayed + * + * @return array $sql_query, $message + */ + public static function addUserAndCreateDatabase( + $_error, + $real_sql_query, + $sql_query, + $username, + $hostname, + $dbname, + $alter_real_sql_query, + $alter_sql_query + ) { + if ($_error || (!empty($real_sql_query) + && !$GLOBALS['dbi']->tryQuery($real_sql_query)) + ) { + $_POST['createdb-1'] = $_POST['createdb-2'] + = $_POST['createdb-3'] = null; + $message = Message::rawError($GLOBALS['dbi']->getError()); + } elseif ($alter_real_sql_query !== '' && !$GLOBALS['dbi']->tryQuery($alter_real_sql_query)) { + $_POST['createdb-1'] = $_POST['createdb-2'] + = $_POST['createdb-3'] = null; + $message = Message::rawError($GLOBALS['dbi']->getError()); + } else { + $sql_query .= $alter_sql_query; + $message = Message::success(__('You have added a new user.')); + } + + if (isset($_POST['createdb-1'])) { + // Create database with same name and grant all privileges + $q = 'CREATE DATABASE IF NOT EXISTS ' + . Util::backquote( + $GLOBALS['dbi']->escapeString($username) + ) . ';'; + $sql_query .= $q; + if (! $GLOBALS['dbi']->tryQuery($q)) { + $message = Message::rawError($GLOBALS['dbi']->getError()); + } + + /** + * Reload the navigation + */ + $GLOBALS['reload'] = true; + $GLOBALS['db'] = $username; + + $q = 'GRANT ALL PRIVILEGES ON ' + . Util::backquote( + Util::escapeMysqlWildcards( + $GLOBALS['dbi']->escapeString($username) + ) + ) . '.* TO \'' + . $GLOBALS['dbi']->escapeString($username) + . '\'@\'' . $GLOBALS['dbi']->escapeString($hostname) . '\';'; + $sql_query .= $q; + if (! $GLOBALS['dbi']->tryQuery($q)) { + $message = Message::rawError($GLOBALS['dbi']->getError()); + } + } + + if (isset($_POST['createdb-2'])) { + // Grant all privileges on wildcard name (username\_%) + $q = 'GRANT ALL PRIVILEGES ON ' + . Util::backquote( + Util::escapeMysqlWildcards( + $GLOBALS['dbi']->escapeString($username) + ) . '\_%' + ) . '.* TO \'' + . $GLOBALS['dbi']->escapeString($username) + . '\'@\'' . $GLOBALS['dbi']->escapeString($hostname) . '\';'; + $sql_query .= $q; + if (! $GLOBALS['dbi']->tryQuery($q)) { + $message = Message::rawError($GLOBALS['dbi']->getError()); + } + } + + if (isset($_POST['createdb-3'])) { + // Grant all privileges on the specified database to the new user + $q = 'GRANT ALL PRIVILEGES ON ' + . Util::backquote( + $GLOBALS['dbi']->escapeString($dbname) + ) . '.* TO \'' + . $GLOBALS['dbi']->escapeString($username) + . '\'@\'' . $GLOBALS['dbi']->escapeString($hostname) . '\';'; + $sql_query .= $q; + if (! $GLOBALS['dbi']->tryQuery($q)) { + $message = Message::rawError($GLOBALS['dbi']->getError()); + } + } + return array($sql_query, $message); + } + + /** + * Get the hashed string for password + * + * @param string $password password + * + * @return string $hashedPassword + */ + public static function getHashedPassword($password) + { + $password = $GLOBALS['dbi']->escapeString($password); + $result = $GLOBALS['dbi']->fetchSingleRow( + "SELECT PASSWORD('" . $password . "') AS `password`;" + ); + + $hashedPassword = $result['password']; + + return $hashedPassword; + } + + /** + * Check if MariaDB's 'simple_password_check' + * OR 'cracklib_password_check' is ACTIVE + * + * @return boolean if atleast one of the plugins is ACTIVE + */ + public static function checkIfMariaDBPwdCheckPluginActive() + { + $serverVersion = $GLOBALS['dbi']->getVersion(); + if (!(Util::getServerType() == 'MariaDB' && $serverVersion >= 100002)) { + return false; + } + + $result = $GLOBALS['dbi']->tryQuery( + 'SHOW PLUGINS SONAME LIKE \'%_password_check%\'' + ); + + /* Plugins are not working, for example directory does not exists */ + if ($result === false) { + return false; + } + + while ($row = $GLOBALS['dbi']->fetchAssoc($result)) { + if ($row['Status'] === 'ACTIVE') { + return true; + } + } + + return false; + } + + + /** + * Get SQL queries for Display and Add user + * + * @param string $username username + * @param string $hostname host name + * @param string $password password + * + * @return array ($create_user_real, $create_user_show, $real_sql_query, $sql_query + * $password_set_real, $password_set_show, $alter_real_sql_query, $alter_sql_query) + */ + public static function getSqlQueriesForDisplayAndAddUser($username, $hostname, $password) + { + $slashedUsername = $GLOBALS['dbi']->escapeString($username); + $slashedHostname = $GLOBALS['dbi']->escapeString($hostname); + $slashedPassword = $GLOBALS['dbi']->escapeString($password); + $serverType = Util::getServerType(); + $serverVersion = $GLOBALS['dbi']->getVersion(); + + $create_user_stmt = sprintf( + 'CREATE USER \'%s\'@\'%s\'', + $slashedUsername, + $slashedHostname + ); + $isMariaDBPwdPluginActive = self::checkIfMariaDBPwdCheckPluginActive(); + + // See https://github.com/phpmyadmin/phpmyadmin/pull/11560#issuecomment-147158219 + // for details regarding details of syntax usage for various versions + + // 'IDENTIFIED WITH auth_plugin' + // is supported by MySQL 5.5.7+ + if (($serverType == 'MySQL' || $serverType == 'Percona Server') + && $serverVersion >= 50507 + && isset($_POST['authentication_plugin']) + ) { + $create_user_stmt .= ' IDENTIFIED WITH ' + . $_POST['authentication_plugin']; + } + + // 'IDENTIFIED VIA auth_plugin' + // is supported by MariaDB 5.2+ + if ($serverType == 'MariaDB' + && $serverVersion >= 50200 + && isset($_POST['authentication_plugin']) + && ! $isMariaDBPwdPluginActive + ) { + $create_user_stmt .= ' IDENTIFIED VIA ' + . $_POST['authentication_plugin']; + } + + $create_user_real = $create_user_show = $create_user_stmt; + + $password_set_stmt = 'SET PASSWORD FOR \'%s\'@\'%s\' = \'%s\''; + $password_set_show = sprintf( + $password_set_stmt, + $slashedUsername, + $slashedHostname, + '***' + ); + + $sql_query_stmt = sprintf( + 'GRANT %s ON *.* TO \'%s\'@\'%s\'', + join(', ', self::extractPrivInfo()), + $slashedUsername, + $slashedHostname + ); + $real_sql_query = $sql_query = $sql_query_stmt; + + // Set the proper hashing method + if (isset($_POST['authentication_plugin'])) { + self::setProperPasswordHashing( + $_POST['authentication_plugin'] + ); + } + + // Use 'CREATE USER ... WITH ... AS ..' syntax for + // newer MySQL versions + // and 'CREATE USER ... VIA .. USING ..' syntax for + // newer MariaDB versions + if ((($serverType == 'MySQL' || $serverType == 'Percona Server') + && $serverVersion >= 50706) + || ($serverType == 'MariaDB' + && $serverVersion >= 50200) + ) { + $password_set_real = null; + + // Required for binding '%' with '%s' + $create_user_stmt = str_replace( + '%', '%%', $create_user_stmt + ); + + // MariaDB uses 'USING' whereas MySQL uses 'AS' + // but MariaDB with validation plugin needs cleartext password + if ($serverType == 'MariaDB' + && ! $isMariaDBPwdPluginActive + ) { + $create_user_stmt .= ' USING \'%s\''; + } elseif ($serverType == 'MariaDB') { + $create_user_stmt .= ' IDENTIFIED BY \'%s\''; + } elseif (($serverType == 'MySQL' || $serverType == 'Percona Server') && $serverVersion >= 80011) { + $create_user_stmt .= ' BY \'%s\''; + } else { + $create_user_stmt .= ' AS \'%s\''; + } + + if ($_POST['pred_password'] == 'keep') { + $create_user_real = sprintf( + $create_user_stmt, + $slashedPassword + ); + $create_user_show = sprintf( + $create_user_stmt, + '***' + ); + } elseif ($_POST['pred_password'] == 'none') { + $create_user_real = sprintf( + $create_user_stmt, + null + ); + $create_user_show = sprintf( + $create_user_stmt, + '***' + ); + } else { + if (! (($serverType == 'MariaDB' && $isMariaDBPwdPluginActive) + || ($serverType == 'MySQL' || $serverType == 'Percona Server') && $serverVersion >= 80011)) { + $hashedPassword = self::getHashedPassword($_POST['pma_pw']); + } else { + // MariaDB with validation plugin needs cleartext password + $hashedPassword = $_POST['pma_pw']; + } + $create_user_real = sprintf( + $create_user_stmt, + $hashedPassword + ); + $create_user_show = sprintf( + $create_user_stmt, + '***' + ); + } + } else { + // Use 'SET PASSWORD' syntax for pre-5.7.6 MySQL versions + // and pre-5.2.0 MariaDB versions + if ($_POST['pred_password'] == 'keep') { + $password_set_real = sprintf( + $password_set_stmt, + $slashedUsername, + $slashedHostname, + $slashedPassword + ); + } elseif ($_POST['pred_password'] == 'none') { + $password_set_real = sprintf( + $password_set_stmt, + $slashedUsername, + $slashedHostname, + null + ); + } else { + $hashedPassword = self::getHashedPassword($_POST['pma_pw']); + $password_set_real = sprintf( + $password_set_stmt, + $slashedUsername, + $slashedHostname, + $hashedPassword + ); + } + } + + $alter_real_sql_query = ''; + $alter_sql_query = ''; + if (($serverType == 'MySQL' || $serverType == 'Percona Server') && $serverVersion >= 80011) { + $sql_query_stmt = ''; + if ((isset($_POST['Grant_priv']) && $_POST['Grant_priv'] == 'Y') + || (isset($GLOBALS['Grant_priv']) && $GLOBALS['Grant_priv'] == 'Y') + ) { + $sql_query_stmt = ' WITH GRANT OPTION'; + } + $real_sql_query .= $sql_query_stmt; + $sql_query .= $sql_query_stmt; + + $alter_sql_query_stmt = sprintf( + 'ALTER USER \'%s\'@\'%s\'', + $slashedUsername, + $slashedHostname + ); + $alter_real_sql_query = $alter_sql_query_stmt; + $alter_sql_query = $alter_sql_query_stmt; + } + + // add REQUIRE clause + $require_clause = self::getRequireClause(); + $with_clause = self::getWithClauseForAddUserAndUpdatePrivs(); + + if (($serverType == 'MySQL' || $serverType == 'Percona Server') && $serverVersion >= 80011) { + $alter_real_sql_query .= $require_clause; + $alter_sql_query .= $require_clause; + $alter_real_sql_query .= $with_clause; + $alter_sql_query .= $with_clause; + } else { + $real_sql_query .= $require_clause; + $sql_query .= $require_clause; + $real_sql_query .= $with_clause; + $sql_query .= $with_clause; + } + + if (isset($create_user_real)) { + $create_user_real .= ';'; + $create_user_show .= ';'; + } + if ($alter_real_sql_query !== '') { + $alter_real_sql_query .= ';'; + $alter_sql_query .= ';'; + } + $real_sql_query .= ';'; + $sql_query .= ';'; + // No Global GRANT_OPTION privilege + if (!$GLOBALS['is_grantuser']) { + $real_sql_query = ''; + $sql_query = ''; + } + + // Use 'SET PASSWORD' for pre-5.7.6 MySQL versions + // and pre-5.2.0 MariaDB + if (($serverType == 'MySQL' + && $serverVersion >= 50706) + || ($serverType == 'MariaDB' + && $serverVersion >= 50200) + ) { + $password_set_real = null; + $password_set_show = null; + } else { + if ($password_set_real !== null) { + $password_set_real .= ";"; + } + $password_set_show .= ";"; + } + + return array( + $create_user_real, + $create_user_show, + $real_sql_query, + $sql_query, + $password_set_real, + $password_set_show, + $alter_real_sql_query, + $alter_sql_query + ); + } + + /** + * Returns the type ('PROCEDURE' or 'FUNCTION') of the routine + * + * @param string $dbname database + * @param string $routineName routine + * + * @return string type + */ + public static function getRoutineType($dbname, $routineName) + { + $routineData = $GLOBALS['dbi']->getRoutines($dbname); + + foreach ($routineData as $routine) { + if ($routine['name'] === $routineName) { + return $routine['type']; + } + } + return ''; + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Server/Select.php b/php/apps/phpmyadmin49/html/libraries/classes/Server/Select.php new file mode 100644 index 00000000..b5b434de --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Server/Select.php @@ -0,0 +1,125 @@ +'; + + if (! $omit_fieldset) { + $retval .= '
    '; + } + + $retval .= Url::getHiddenFields(array()); + $retval .= ' '; + + $retval .= ''; + if (! $omit_fieldset) { + $retval .= '
    '; + } + $retval .= ''; + } elseif ($list) { + $retval .= ''; + } + + return $retval; + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Server/Status.php b/php/apps/phpmyadmin49/html/libraries/classes/Server/Status.php new file mode 100644 index 00000000..d6705435 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Server/Status.php @@ -0,0 +1,335 @@ +fetchValue( + 'SELECT UNIX_TIMESTAMP() - ' . $serverStatusData->status['Uptime'] + ); + + $retval = '

    '; + $bytes_received = $serverStatusData->status['Bytes_received']; + $bytes_sent = $serverStatusData->status['Bytes_sent']; + $retval .= sprintf( + __('Network traffic since startup: %s'), + implode( + ' ', + Util::formatByteDown( + $bytes_received + $bytes_sent, + 3, + 1 + ) + ) + ); + $retval .= '

    '; + $retval .= '

    '; + $retval .= sprintf( + __('This MySQL server has been running for %1$s. It started up on %2$s.'), + Util::timespanFormat($serverStatusData->status['Uptime']), + Util::localisedDate($start_time) + ) . "\n"; + $retval .= '

    '; + + return $retval; + } + + /** + * Returns HTML to display replication information + * + * @return string HTML on replication + */ + public static function getHtmlForReplicationInfo() + { + $retval = '

    '; + if ($GLOBALS['replication_info']['master']['status'] + && $GLOBALS['replication_info']['slave']['status'] + ) { + $retval .= __( + 'This MySQL server works as master and ' + . 'slave in replication process.' + ); + } elseif ($GLOBALS['replication_info']['master']['status']) { + $retval .= __( + 'This MySQL server works as master ' + . 'in replication process.' + ); + } elseif ($GLOBALS['replication_info']['slave']['status']) { + $retval .= __( + 'This MySQL server works as slave ' + . 'in replication process.' + ); + } + $retval .= '

    '; + + /* + * if the server works as master or slave in replication process, + * display useful information + */ + $retval .= '
    '; + $retval .= '

    '; + $retval .= __('Replication status'); + $retval .= '

    '; + foreach ($GLOBALS['replication_types'] as $type) { + if (isset($GLOBALS['replication_info'][$type]['status']) + && $GLOBALS['replication_info'][$type]['status'] + ) { + $retval .= ReplicationGui::getHtmlForReplicationStatusTable($type); + } + } + + return $retval; + } + + /** + * Prints server state traffic information + * + * @param Data $serverStatusData Server status data + * + * @return string + */ + public static function getHtmlForServerStateTraffic(Data $serverStatusData) + { + $hour_factor = 3600 / $serverStatusData->status['Uptime']; + $retval = ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= '
    '; + $retval .= __('Traffic') . ' '; + $retval .= Util::showHint( + __( + 'On a busy server, the byte counters may overrun, so those statistics ' + . 'as reported by the MySQL server may be incorrect.' + ) + ); + $retval .= '#ø ' . __('per hour') . '
    ' . __('Received') . ''; + $retval .= implode( + ' ', + Util::formatByteDown( + $serverStatusData->status['Bytes_received'], 3, 1 + ) + ); + $retval .= ''; + $retval .= implode( + ' ', + Util::formatByteDown( + $serverStatusData->status['Bytes_received'] * $hour_factor, 3, 1 + ) + ); + $retval .= '
    ' . __('Sent') . ''; + $retval .= implode( + ' ', + Util::formatByteDown( + $serverStatusData->status['Bytes_sent'], 3, 1 + ) + ); + $retval .= ''; + $retval .= implode( + ' ', + Util::formatByteDown( + $serverStatusData->status['Bytes_sent'] * $hour_factor, 3, 1 + ) + ); + $retval .= '
    ' . __('Total') . ''; + $bytes_received = $serverStatusData->status['Bytes_received']; + $bytes_sent = $serverStatusData->status['Bytes_sent']; + $retval .= implode( + ' ', + Util::formatByteDown( + $bytes_received + $bytes_sent, 3, 1 + ) + ); + $retval .= ''; + $bytes_received = $serverStatusData->status['Bytes_received']; + $bytes_sent = $serverStatusData->status['Bytes_sent']; + $retval .= implode( + ' ', + Util::formatByteDown( + ($bytes_received + $bytes_sent) * $hour_factor, 3, 1 + ) + ); + $retval .= '
    '; + return $retval; + } + + /** + * Prints server state connections information + * + * @param Data $serverStatusData Server status data + * + * @return string + */ + public static function getHtmlForServerStateConnections(Data $serverStatusData) + { + $hour_factor = 3600 / $serverStatusData->status['Uptime']; + $retval = ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= '
    ' . __('Connections') . '#ø ' . __('per hour') . '%
    ' . __('Max. concurrent connections') . ''; + $retval .= Util::formatNumber( + $serverStatusData->status['Max_used_connections'], 0 + ); + $retval .= '--- ---
    ' . __('Failed attempts') . ''; + $retval .= Util::formatNumber( + $serverStatusData->status['Aborted_connects'], 4, 1, true + ); + $retval .= ''; + $retval .= Util::formatNumber( + $serverStatusData->status['Aborted_connects'] * $hour_factor, 4, 2, true + ); + $retval .= ''; + if ($serverStatusData->status['Connections'] > 0) { + $abortNum = $serverStatusData->status['Aborted_connects']; + $connectNum = $serverStatusData->status['Connections']; + + $retval .= Util::formatNumber( + $abortNum * 100 / $connectNum, + 0, 2, true + ); + $retval .= '%'; + } else { + $retval .= '--- '; + } + $retval .= '
    ' . __('Aborted') . ''; + $retval .= Util::formatNumber( + $serverStatusData->status['Aborted_clients'], 4, 1, true + ); + $retval .= ''; + $retval .= Util::formatNumber( + $serverStatusData->status['Aborted_clients'] * $hour_factor, 4, 2, true + ); + $retval .= ''; + if ($serverStatusData->status['Connections'] > 0) { + $abortNum = $serverStatusData->status['Aborted_clients']; + $connectNum = $serverStatusData->status['Connections']; + + $retval .= Util::formatNumber( + $abortNum * 100 / $connectNum, + 0, 2, true + ); + $retval .= '%'; + } else { + $retval .= '--- '; + } + $retval .= '
    ' . __('Total') . ''; + $retval .= Util::formatNumber( + $serverStatusData->status['Connections'], 4, 0 + ); + $retval .= ''; + $retval .= Util::formatNumber( + $serverStatusData->status['Connections'] * $hour_factor, 4, 2 + ); + $retval .= ''; + $retval .= Util::formatNumber(100, 0, 2); + $retval .= '%
    '; + + return $retval; + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Server/Status/Advisor.php b/php/apps/phpmyadmin49/html/libraries/classes/Server/Status/Advisor.php new file mode 100644 index 00000000..2356c710 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Server/Status/Advisor.php @@ -0,0 +1,73 @@ +'; + $output .= Util::getIcon('b_help', __('Instructions')); + $output .= ''; + $output .= '
    '; + $output .= '
    '; + $output .= '

    '; + $output .= __( + 'The Advisor system can provide recommendations ' + . 'on server variables by analyzing the server status variables.' + ); + $output .= '

    '; + $output .= '

    '; + $output .= __( + 'Do note however that this system provides recommendations ' + . 'based on simple calculations and by rule of thumb which may ' + . 'not necessarily apply to your system.' + ); + $output .= '

    '; + $output .= '

    '; + $output .= __( + 'Prior to changing any of the configuration, be sure to know ' + . 'what you are changing (by reading the documentation) and how ' + . 'to undo the change. Wrong tuning can have a very negative ' + . 'effect on performance.' + ); + $output .= '

    '; + $output .= '

    '; + $output .= __( + 'The best way to tune your system would be to change only one ' + . 'setting at a time, observe or benchmark your database, and undo ' + . 'the change if there was no clearly measurable improvement.' + ); + $output .= '

    '; + $output .= '
    '; + $output .= '
    '; + $advisor = new PmaAdvisor($GLOBALS['dbi'], new ExpressionLanguage()); + $output .= htmlspecialchars( + json_encode( + $advisor->run() + ) + ); + $output .= '
    '; + + return $output; + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Server/Status/Data.php b/php/apps/phpmyadmin49/html/libraries/classes/Server/Status/Data.php new file mode 100644 index 00000000..0192657d --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Server/Status/Data.php @@ -0,0 +1,504 @@ + section + // variable names match when they begin with the given string + + 'Com_' => 'com', + 'Innodb_' => 'innodb', + 'Ndb_' => 'ndb', + 'Handler_' => 'handler', + 'Qcache_' => 'qcache', + 'Threads_' => 'threads', + 'Slow_launch_threads' => 'threads', + + 'Binlog_cache_' => 'binlog_cache', + 'Created_tmp_' => 'created_tmp', + 'Key_' => 'key', + + 'Delayed_' => 'delayed', + 'Not_flushed_delayed_rows' => 'delayed', + + 'Flush_commands' => 'query', + 'Last_query_cost' => 'query', + 'Slow_queries' => 'query', + 'Queries' => 'query', + 'Prepared_stmt_count' => 'query', + + 'Select_' => 'select', + 'Sort_' => 'sort', + + 'Open_tables' => 'table', + 'Opened_tables' => 'table', + 'Open_table_definitions' => 'table', + 'Opened_table_definitions' => 'table', + 'Table_locks_' => 'table', + + 'Rpl_status' => 'repl', + 'Slave_' => 'repl', + + 'Tc_' => 'tc', + + 'Ssl_' => 'ssl', + + 'Open_files' => 'files', + 'Open_streams' => 'files', + 'Opened_files' => 'files', + ); + } + + /** + * Gets the sections for constructor + * + * @return array + */ + private function _getSections() + { + return array( + // section => section name (description) + 'com' => 'Com', + 'query' => __('SQL query'), + 'innodb' => 'InnoDB', + 'ndb' => 'NDB', + 'handler' => __('Handler'), + 'qcache' => __('Query cache'), + 'threads' => __('Threads'), + 'binlog_cache' => __('Binary log'), + 'created_tmp' => __('Temporary data'), + 'delayed' => __('Delayed inserts'), + 'key' => __('Key cache'), + 'select' => __('Joins'), + 'repl' => __('Replication'), + 'sort' => __('Sorting'), + 'table' => __('Tables'), + 'tc' => __('Transaction coordinator'), + 'files' => __('Files'), + 'ssl' => 'SSL', + 'other' => __('Other') + ); + } + + /** + * Gets the links for constructor + * + * @return array + */ + private function _getLinks() + { + $links = array(); + // variable or section name => (name => url) + + $links['table'][__('Flush (close) all tables')] = [ + 'url' => $this->selfUrl, + 'params' => Url::getCommon(['flush' => 'TABLES'], ''), + ]; + $links['table'][__('Show open tables')] = [ + 'url' => 'sql.php', + 'params' => Url::getCommon([ + 'sql_query' => 'SHOW OPEN TABLES', + 'goto' => $this->selfUrl, + ], ''), + ]; + + if ($GLOBALS['replication_info']['master']['status']) { + $links['repl'][__('Show slave hosts')] = [ + 'url' => 'sql.php', + 'params' => Url::getCommon([ + 'sql_query' => 'SHOW SLAVE HOSTS', + 'goto' => $this->selfUrl, + ], ''), + ]; + $links['repl'][__('Show master status')] = [ + 'url' => '#replication_master', + 'params' => '', + ]; + } + if ($GLOBALS['replication_info']['slave']['status']) { + $links['repl'][__('Show slave status')] = [ + 'url' => '#replication_slave', + 'params' => '', + ]; + } + + $links['repl']['doc'] = 'replication'; + + $links['qcache'][__('Flush query cache')] = [ + 'url' => $this->selfUrl, + 'params' => Url::getCommon(['flush' => 'QUERY CACHE'], ''), + ]; + $links['qcache']['doc'] = 'query_cache'; + + $links['threads']['doc'] = 'mysql_threads'; + + $links['key']['doc'] = 'myisam_key_cache'; + + $links['binlog_cache']['doc'] = 'binary_log'; + + $links['Slow_queries']['doc'] = 'slow_query_log'; + + $links['innodb'][__('Variables')] = [ + 'url' => 'server_engines.php', + 'params' => Url::getCommon(['engine' => 'InnoDB'], ''), + ]; + $links['innodb'][__('InnoDB Status')] = [ + 'url' => 'server_engines.php', + 'params' => Url::getCommon([ + 'engine' => 'InnoDB', + 'page' => 'Status', + ], ''), + ]; + $links['innodb']['doc'] = 'innodb'; + + return($links); + } + + /** + * Calculate some values + * + * @param array $server_status contains results of SHOW GLOBAL STATUS + * @param array $server_variables contains results of SHOW GLOBAL VARIABLES + * + * @return array $server_status + */ + private function _calculateValues(array $server_status, array $server_variables) + { + // Key_buffer_fraction + if (isset($server_status['Key_blocks_unused']) + && isset($server_variables['key_cache_block_size']) + && isset($server_variables['key_buffer_size']) + && $server_variables['key_buffer_size'] != 0 + ) { + $server_status['Key_buffer_fraction_%'] + = 100 + - $server_status['Key_blocks_unused'] + * $server_variables['key_cache_block_size'] + / $server_variables['key_buffer_size'] + * 100; + } elseif (isset($server_status['Key_blocks_used']) + && isset($server_variables['key_buffer_size']) + && $server_variables['key_buffer_size'] != 0 + ) { + $server_status['Key_buffer_fraction_%'] + = $server_status['Key_blocks_used'] + * 1024 + / $server_variables['key_buffer_size']; + } + + // Ratio for key read/write + if (isset($server_status['Key_writes']) + && isset($server_status['Key_write_requests']) + && $server_status['Key_write_requests'] > 0 + ) { + $key_writes = $server_status['Key_writes']; + $key_write_requests = $server_status['Key_write_requests']; + $server_status['Key_write_ratio_%'] + = 100 * $key_writes / $key_write_requests; + } + + if (isset($server_status['Key_reads']) + && isset($server_status['Key_read_requests']) + && $server_status['Key_read_requests'] > 0 + ) { + $key_reads = $server_status['Key_reads']; + $key_read_requests = $server_status['Key_read_requests']; + $server_status['Key_read_ratio_%'] + = 100 * $key_reads / $key_read_requests; + } + + // Threads_cache_hitrate + if (isset($server_status['Threads_created']) + && isset($server_status['Connections']) + && $server_status['Connections'] > 0 + ) { + + $server_status['Threads_cache_hitrate_%'] + = 100 - $server_status['Threads_created'] + / $server_status['Connections'] * 100; + } + return $server_status; + } + + /** + * Sort variables into arrays + * + * @param array $server_status contains results of SHOW GLOBAL STATUS + * @param array $allocations allocations for sections + * @param array $allocationMap map variables to their section + * @param array $sectionUsed is a section used? + * @param array $used_queries used queries + * + * @return array ($allocationMap, $sectionUsed, $used_queries) + */ + private function _sortVariables( + array $server_status, array $allocations, array $allocationMap, array $sectionUsed, + array $used_queries + ) { + foreach ($server_status as $name => $value) { + $section_found = false; + foreach ($allocations as $filter => $section) { + if (mb_strpos($name, $filter) !== false) { + $allocationMap[$name] = $section; + $sectionUsed[$section] = true; + $section_found = true; + if ($section == 'com' && $value > 0) { + $used_queries[$name] = $value; + } + break; // Only exits inner loop + } + } + if (! $section_found) { + $allocationMap[$name] = 'other'; + $sectionUsed['other'] = true; + } + } + return array($allocationMap, $sectionUsed, $used_queries); + } + + /** + * Constructor + */ + public function __construct() + { + $this->selfUrl = basename($GLOBALS['PMA_PHP_SELF']); + + // get status from server + $server_status_result = $GLOBALS['dbi']->tryQuery('SHOW GLOBAL STATUS'); + $server_status = array(); + if ($server_status_result === false) { + $this->dataLoaded = false; + } else { + $this->dataLoaded = true; + while ($arr = $GLOBALS['dbi']->fetchRow($server_status_result)) { + $server_status[$arr[0]] = $arr[1]; + } + $GLOBALS['dbi']->freeResult($server_status_result); + } + + // for some calculations we require also some server settings + $server_variables = $GLOBALS['dbi']->fetchResult( + 'SHOW GLOBAL VARIABLES', 0, 1 + ); + + // cleanup of some deprecated values + $server_status = self::cleanDeprecated($server_status); + + // calculate some values + $server_status = $this->_calculateValues( + $server_status, $server_variables + ); + + // split variables in sections + $allocations = $this->_getAllocations(); + + $sections = $this->_getSections(); + + // define some needful links/commands + $links = $this->_getLinks(); + + // Variable to contain all com_ variables (query statistics) + $used_queries = array(); + + // Variable to map variable names to their respective section name + // (used for js category filtering) + $allocationMap = array(); + + // Variable to mark used sections + $sectionUsed = array(); + + // sort vars into arrays + list( + $allocationMap, $sectionUsed, $used_queries + ) = $this->_sortVariables( + $server_status, $allocations, $allocationMap, $sectionUsed, + $used_queries + ); + + // admin commands are not queries (e.g. they include COM_PING, + // which is excluded from $server_status['Questions']) + unset($used_queries['Com_admin_commands']); + + // Set all class properties + $this->db_isLocal = false; + $serverHostToLower = mb_strtolower( + $GLOBALS['cfg']['Server']['host'] + ); + if ($serverHostToLower === 'localhost' + || $GLOBALS['cfg']['Server']['host'] === '127.0.0.1' + || $GLOBALS['cfg']['Server']['host'] === '::1' + ) { + $this->db_isLocal = true; + } + $this->status = $server_status; + $this->sections = $sections; + $this->variables = $server_variables; + $this->used_queries = $used_queries; + $this->allocationMap = $allocationMap; + $this->links = $links; + $this->sectionUsed = $sectionUsed; + } + + /** + * cleanup of some deprecated values + * + * @param array $server_status status array to process + * + * @return array + */ + public static function cleanDeprecated(array $server_status) + { + $deprecated = array( + 'Com_prepare_sql' => 'Com_stmt_prepare', + 'Com_execute_sql' => 'Com_stmt_execute', + 'Com_dealloc_sql' => 'Com_stmt_close', + ); + foreach ($deprecated as $old => $new) { + if (isset($server_status[$old]) && isset($server_status[$new])) { + unset($server_status[$old]); + } + } + return $server_status; + } + + /** + * Generates menu HTML + * + * @return string + */ + public function getMenuHtml() + { + $url_params = Url::getCommon(); + $items = array( + array( + 'name' => __('Server'), + 'url' => 'server_status.php' + ), + array( + 'name' => __('Processes'), + 'url' => 'server_status_processes.php' + ), + array( + 'name' => __('Query statistics'), + 'url' => 'server_status_queries.php' + ), + array( + 'name' => __('All status variables'), + 'url' => 'server_status_variables.php' + ), + array( + 'name' => __('Monitor'), + 'url' => 'server_status_monitor.php' + ), + array( + 'name' => __('Advisor'), + 'url' => 'server_status_advisor.php' + ) + ); + + $retval = '
      '; + foreach ($items as $item) { + $class = ''; + if ($item['url'] === $this->selfUrl) { + $class = ' class="tabactive"'; + } + $retval .= '
    • '; + $retval .= ''; + $retval .= $item['name']; + $retval .= ''; + $retval .= '
    • '; + } + $retval .= '
    '; + $retval .= '
    '; + + return $retval; + } + + /** + * Builds a '; + foreach ($refreshRates as $rate) { + $selected = ($rate == $defaultRate)?' selected="selected"':''; + $return .= ''; + } + $return .= ''; + return $return; + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Server/Status/Monitor.php b/php/apps/phpmyadmin49/html/libraries/classes/Server/Status/Monitor.php new file mode 100644 index 00000000..b040193b --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Server/Status/Monitor.php @@ -0,0 +1,829 @@ +'; + $retval .= '
    '; + $retval .= '
    '; + $retval .= '
    '; + + $retval .= ''; + + return $retval; + } + + /** + * Returns html for Analyse Dialog + * + * @return string + */ + public static function getHtmlForAnalyseDialog() + { + $retval = '
    '; + $retval .= '

    ' . __('Selected time range:'); + $retval .= ' - '; + $retval .= ''; + $retval .= '

    '; + $retval .= ''; + $retval .= ''; + $retval .= '
    '; + $retval .= ''; + $retval .= ''; + $retval .= '

    '; + $retval .= __( + 'Choose from which log you want the statistics to be generated from.' + ); + $retval .= '

    '; + $retval .= '

    '; + $retval .= __('Results are grouped by query text.'); + $retval .= '

    '; + $retval .= '
    '; + $retval .= '
    '; + $retval .= ''; + $retval .= '

    '; + $retval .= '
    '; + $retval .= '
    '; + + return $retval; + } + + /** + * Returns html for Instructions Dialog + * + * @return string + */ + public static function getHtmlForInstructionsDialog() + { + $retval = '
    '; + $retval .= __( + 'The phpMyAdmin Monitor can assist you in optimizing the server' + . ' configuration and track down time intensive queries. For the latter you' + . ' will need to set log_output to \'TABLE\' and have either the' + . ' slow_query_log or general_log enabled. Note however, that the' + . ' general_log produces a lot of data and increases server load' + . ' by up to 15%.' + ); + + $retval .= '

    '; + $retval .= ''; + $retval .= '
    '; + $retval .= '
    '; + $retval .= '

    '; + $retval .= ''; + $retval .= __('Using the monitor:'); + $retval .= '

    '; + $retval .= __( + 'Your browser will refresh all displayed charts in a regular interval.' + . ' You may add charts and change the refresh rate under \'Settings\',' + . ' or remove any chart using the cog icon on each respective chart.' + ); + $retval .= '

    '; + $retval .= __( + 'To display queries from the logs, select the relevant time span on any' + . ' chart by holding down the left mouse button and panning over the' + . ' chart. Once confirmed, this will load a table of grouped queries,' + . ' there you may click on any occurring SELECT statements to further' + . ' analyze them.' + ); + $retval .= '

    '; + $retval .= '

    '; + $retval .= Util::getImage('s_attention'); + $retval .= ''; + $retval .= __('Please note:'); + $retval .= '
    '; + $retval .= __( + 'Enabling the general_log may increase the server load by' + . ' 5-15%. Also be aware that generating statistics from the logs is a' + . ' load intensive task, so it is advisable to select only a small time' + . ' span and to disable the general_log and empty its table once' + . ' monitoring is not required any more.' + ); + $retval .= '

    '; + $retval .= '
    '; + $retval .= '
    '; + + return $retval; + } + + /** + * Returns html for addChartDialog + * + * @return string + */ + public static function getHtmlForAddChartDialog() + { + $retval = '
    '; + $retval .= '
    '; + $retval .= '

    '; + $retval .= ''; + $retval .= ''; + $retval .= '
    '; + $retval .= ''; + $retval .= '
    '; + $retval .= '
    '; + $retval .= '
    '; + $retval .= '
    '; + $retval .= ''; + $retval .= ''; + $retval .= '

    '; + $retval .= ''; + $retval .= '
    '; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= '(' . __('KiB') . ', '; + $retval .= '' . __('MiB') . ')'; + $retval .= '
    '; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= '

    '; + $retval .= '' . __('Add this series') . ''; + $retval .= ''; + $retval .= ' | ' . __('Clear series') . ''; + $retval .= ''; + $retval .= '

    '; + $retval .= __('Series in chart:'); + $retval .= '
    '; + $retval .= ''; + $retval .= '' . __('None') . ''; + $retval .= ''; + $retval .= '
    '; + $retval .= '
    '; + $retval .= '
    '; + + return $retval; + } + + /** + * Returns html with Tab Links + * + * @return string + */ + public static function getHtmlForTabLinks() + { + $retval = ''; + + return $retval; + } + + /** + * Returns html with Settings dialog + * + * @return string + */ + public static function getHtmlForSettingsDialog() + { + $retval = '
    '; + $retval .= ''; + $retval .= Util::getImage('b_chart') . __('Add chart'); + $retval .= ''; + $retval .= ''; + $retval .= Util::getImage('b_tblops') + . __('Enable charts dragging'); + $retval .= ''; + $retval .= '
    '; + $retval .= '
    '; + $retval .= __('Refresh rate') . '
    '; + $retval .= Data::getHtmlForRefreshList( + 'gridChartRefresh', + 5, + Array(2, 3, 4, 5, 10, 20, 40, 60, 120, 300, 600, 1200) + ); + $retval .= '
    '; + $retval .= '
    '; + $retval .= '
    '; + $retval .= __('Chart columns'); + $retval .= '
    '; + $retval .= ''; + $retval .= '
    '; + $retval .= '
    '; + $retval .= '' . __('Chart arrangement') . ' '; + $retval .= Util::showHint( + __( + 'The arrangement of the charts is stored to the browsers local storage. ' + . 'You may want to export it if you have a complicated set up.' + ) + ); + $retval .= '
    '; + $retval .= ''; + $retval .= __('Import'); + $retval .= ''; + $retval .= '  '; + $retval .= ''; + $retval .= __('Export'); + $retval .= ''; + $retval .= '  '; + $retval .= ''; + $retval .= __('Reset to default'); + $retval .= ''; + $retval .= '
    '; + $retval .= '
    '; + + return $retval; + } + + + /** + * Define some data and links needed on the client side + * + * @param Data $serverStatusData Server status data + * + * @return string + */ + public static function getHtmlForClientSideDataAndLinks(Data $serverStatusData) + { + /** + * Define some data needed on the client side + */ + $input = ''; + $form = '
    '; + $form .= sprintf($input, 'server_time', microtime(true) * 1000); + $form .= sprintf($input, 'server_os', SysInfo::getOs()); + $form .= sprintf($input, 'is_superuser', $GLOBALS['dbi']->isSuperuser()); + $form .= sprintf($input, 'server_db_isLocal', $serverStatusData->db_isLocal); + $form .= '
    '; + /** + * Define some links used on client side + */ + $links = '
    '; + $links .= Util::showMySQLDocu('general-thread-states'); + $links .= '
    '; + $links .= '
    '; + $links .= Util::showMySQLDocu('explain-output'); + $links .= '
    '; + + return $form . $links; + } + + /***************************Ajax request function***********************************/ + + /** + * Returns JSon for real-time charting data + * + * @return array + */ + public static function getJsonForChartingData() + { + $ret = json_decode($_POST['requiredData'], true); + $statusVars = array(); + $serverVars = array(); + $sysinfo = $cpuload = $memory = 0; + + /* Accumulate all required variables and data */ + list($serverVars, $statusVars, $ret) = self::getJsonForChartingDataGet( + $ret, $serverVars, $statusVars, $sysinfo, $cpuload, $memory + ); + + // Retrieve all required status variables + if (count($statusVars)) { + $statusVarValues = $GLOBALS['dbi']->fetchResult( + "SHOW GLOBAL STATUS WHERE Variable_name='" + . implode("' OR Variable_name='", $statusVars) . "'", + 0, + 1 + ); + } else { + $statusVarValues = array(); + } + + // Retrieve all required server variables + if (count($serverVars)) { + $serverVarValues = $GLOBALS['dbi']->fetchResult( + "SHOW GLOBAL VARIABLES WHERE Variable_name='" + . implode("' OR Variable_name='", $serverVars) . "'", + 0, + 1 + ); + } else { + $serverVarValues = array(); + } + + // ...and now assign them + $ret = self::getJsonForChartingDataSet($ret, $statusVarValues, $serverVarValues); + + $ret['x'] = microtime(true) * 1000; + return $ret; + } + + /** + * Assign the variables for real-time charting data + * + * @param array $ret Real-time charting data + * @param array $statusVarValues Status variable values + * @param array $serverVarValues Server variable values + * + * @return array + */ + public static function getJsonForChartingDataSet(array $ret, array $statusVarValues, array $serverVarValues) + { + foreach ($ret as $chart_id => $chartNodes) { + foreach ($chartNodes as $node_id => $nodeDataPoints) { + foreach ($nodeDataPoints as $point_id => $dataPoint) { + switch ($dataPoint['type']) { + case 'statusvar': + $ret[$chart_id][$node_id][$point_id]['value'] + = $statusVarValues[$dataPoint['name']]; + break; + case 'servervar': + $ret[$chart_id][$node_id][$point_id]['value'] + = $serverVarValues[$dataPoint['name']]; + break; + } + } + } + } + return $ret; + } + + /** + * Get called to get JSON for charting data + * + * @param array $ret Real-time charting data + * @param array $serverVars Server variable values + * @param array $statusVars Status variable values + * @param mixed $sysinfo System info + * @param mixed $cpuload CPU load + * @param mixed $memory Memory + * + * @return array + */ + public static function getJsonForChartingDataGet( + array $ret, array $serverVars, array $statusVars, $sysinfo, $cpuload, $memory + ) { + // For each chart + foreach ($ret as $chart_id => $chartNodes) { + // For each data series + foreach ($chartNodes as $node_id => $nodeDataPoints) { + // For each data point in the series (usually just 1) + foreach ($nodeDataPoints as $point_id => $dataPoint) { + list($serverVars, $statusVars, $ret[$chart_id][$node_id][$point_id]) + = self::getJsonForChartingDataSwitch( + $dataPoint['type'], $dataPoint['name'], $serverVars, + $statusVars, $ret[$chart_id][$node_id][$point_id], + $sysinfo, $cpuload, $memory + ); + } /* foreach */ + } /* foreach */ + } + return array($serverVars, $statusVars, $ret); + } + + /** + * Switch called to get JSON for charting data + * + * @param string $type Type + * @param string $pName Name + * @param array $serverVars Server variable values + * @param array $statusVars Status variable values + * @param array $ret Real-time charting data + * @param mixed $sysinfo System info + * @param mixed $cpuload CPU load + * @param mixed $memory Memory + * + * @return array + */ + public static function getJsonForChartingDataSwitch( + $type, $pName, array $serverVars, array $statusVars, array $ret, + $sysinfo, $cpuload, $memory + ) { + switch ($type) { + /* We only collect the status and server variables here to + * read them all in one query, + * and only afterwards assign them. + * Also do some white list filtering on the names + */ + case 'servervar': + if (!preg_match('/[^a-zA-Z_]+/', $pName)) { + $serverVars[] = $pName; + } + break; + + case 'statusvar': + if (!preg_match('/[^a-zA-Z_]+/', $pName)) { + $statusVars[] = $pName; + } + break; + + case 'proc': + $result = $GLOBALS['dbi']->query('SHOW PROCESSLIST'); + $ret['value'] = $GLOBALS['dbi']->numRows($result); + break; + + case 'cpu': + if (!$sysinfo) { + $sysinfo = SysInfo::get(); + } + if (!$cpuload) { + $cpuload = $sysinfo->loadavg(); + } + + if (SysInfo::getOs() == 'Linux') { + $ret['idle'] = $cpuload['idle']; + $ret['busy'] = $cpuload['busy']; + } else { + $ret['value'] = $cpuload['loadavg']; + } + + break; + + case 'memory': + if (!$sysinfo) { + $sysinfo = SysInfo::get(); + } + if (!$memory) { + $memory = $sysinfo->memory(); + } + + $ret['value'] = isset($memory[$pName]) ? $memory[$pName] : 0; + break; + } + + return array($serverVars, $statusVars, $ret); + } + + /** + * Returns JSon for log data with type: slow + * + * @param int $start Unix Time: Start time for query + * @param int $end Unix Time: End time for query + * + * @return array + */ + public static function getJsonForLogDataTypeSlow($start, $end) + { + $query = 'SELECT start_time, user_host, '; + $query .= 'Sec_to_Time(Sum(Time_to_Sec(query_time))) as query_time, '; + $query .= 'Sec_to_Time(Sum(Time_to_Sec(lock_time))) as lock_time, '; + $query .= 'SUM(rows_sent) AS rows_sent, '; + $query .= 'SUM(rows_examined) AS rows_examined, db, sql_text, '; + $query .= 'COUNT(sql_text) AS \'#\' '; + $query .= 'FROM `mysql`.`slow_log` '; + $query .= 'WHERE start_time > FROM_UNIXTIME(' . $start . ') '; + $query .= 'AND start_time < FROM_UNIXTIME(' . $end . ') GROUP BY sql_text'; + + $result = $GLOBALS['dbi']->tryQuery($query); + + $return = array('rows' => array(), 'sum' => array()); + + while ($row = $GLOBALS['dbi']->fetchAssoc($result)) { + $type = mb_strtolower( + mb_substr( + $row['sql_text'], + 0, + mb_strpos($row['sql_text'], ' ') + ) + ); + + switch($type) { + case 'insert': + case 'update': + //Cut off big inserts and updates, but append byte count instead + if (mb_strlen($row['sql_text']) > 220) { + $implode_sql_text = implode( + ' ', + Util::formatByteDown( + mb_strlen($row['sql_text']), 2, 2 + ) + ); + $row['sql_text'] = mb_substr($row['sql_text'], 0, 200) + . '... [' . $implode_sql_text . ']'; + } + break; + default: + break; + } + + if (! isset($return['sum'][$type])) { + $return['sum'][$type] = 0; + } + $return['sum'][$type] += $row['#']; + $return['rows'][] = $row; + } + + $return['sum']['TOTAL'] = array_sum($return['sum']); + $return['numRows'] = count($return['rows']); + + $GLOBALS['dbi']->freeResult($result); + return $return; + } + + /** + * Returns JSon for log data with type: general + * + * @param int $start Unix Time: Start time for query + * @param int $end Unix Time: End time for query + * + * @return array + */ + public static function getJsonForLogDataTypeGeneral($start, $end) + { + $limitTypes = ''; + if (isset($_POST['limitTypes']) && $_POST['limitTypes']) { + $limitTypes + = 'AND argument REGEXP \'^(INSERT|SELECT|UPDATE|DELETE)\' '; + } + + $query = 'SELECT TIME(event_time) as event_time, user_host, thread_id, '; + $query .= 'server_id, argument, count(argument) as \'#\' '; + $query .= 'FROM `mysql`.`general_log` '; + $query .= 'WHERE command_type=\'Query\' '; + $query .= 'AND event_time > FROM_UNIXTIME(' . $start . ') '; + $query .= 'AND event_time < FROM_UNIXTIME(' . $end . ') '; + $query .= $limitTypes . 'GROUP by argument'; // HAVING count > 1'; + + $result = $GLOBALS['dbi']->tryQuery($query); + + $return = array('rows' => array(), 'sum' => array()); + $insertTables = array(); + $insertTablesFirst = -1; + $i = 0; + $removeVars = isset($_POST['removeVariables']) + && $_POST['removeVariables']; + + while ($row = $GLOBALS['dbi']->fetchAssoc($result)) { + preg_match('/^(\w+)\s/', $row['argument'], $match); + $type = mb_strtolower($match[1]); + + if (! isset($return['sum'][$type])) { + $return['sum'][$type] = 0; + } + $return['sum'][$type] += $row['#']; + + switch($type) { + /** @noinspection PhpMissingBreakStatementInspection */ + case 'insert': + // Group inserts if selected + if ($removeVars + && preg_match( + '/^INSERT INTO (`|\'|"|)([^\s\\1]+)\\1/i', + $row['argument'], $matches + ) + ) { + $insertTables[$matches[2]]++; + if ($insertTables[$matches[2]] > 1) { + $return['rows'][$insertTablesFirst]['#'] + = $insertTables[$matches[2]]; + + // Add a ... to the end of this query to indicate that + // there's been other queries + $temp = $return['rows'][$insertTablesFirst]['argument']; + $return['rows'][$insertTablesFirst]['argument'] + .= self::getSuspensionPoints( + $temp[strlen($temp) - 1] + ); + + // Group this value, thus do not add to the result list + continue 2; + } else { + $insertTablesFirst = $i; + $insertTables[$matches[2]] += $row['#'] - 1; + } + } + // No break here + + case 'update': + // Cut off big inserts and updates, + // but append byte count therefor + if (mb_strlen($row['argument']) > 220) { + $row['argument'] = mb_substr($row['argument'], 0, 200) + . '... [' + . implode( + ' ', + Util::formatByteDown( + mb_strlen($row['argument']), + 2, + 2 + ) + ) + . ']'; + } + break; + + default: + break; + } + + $return['rows'][] = $row; + $i++; + } + + $return['sum']['TOTAL'] = array_sum($return['sum']); + $return['numRows'] = count($return['rows']); + + $GLOBALS['dbi']->freeResult($result); + + return $return; + } + + /** + * Return suspension points if needed + * + * @param string $lastChar Last char + * + * @return null|string Return suspension points if needed + */ + public static function getSuspensionPoints($lastChar) + { + if ($lastChar != '.') { + return '
    ...'; + } + + return null; + } + + /** + * Returns JSon for logging vars + * + * @return array + */ + public static function getJsonForLoggingVars() + { + if (isset($_POST['varName']) && isset($_POST['varValue'])) { + $value = $GLOBALS['dbi']->escapeString($_POST['varValue']); + if (! is_numeric($value)) { + $value="'" . $value . "'"; + } + + if (! preg_match("/[^a-zA-Z0-9_]+/", $_POST['varName'])) { + $GLOBALS['dbi']->query( + 'SET GLOBAL ' . $_POST['varName'] . ' = ' . $value + ); + } + + } + + $loggingVars = $GLOBALS['dbi']->fetchResult( + 'SHOW GLOBAL VARIABLES WHERE Variable_name IN' + . ' ("general_log","slow_query_log","long_query_time","log_output")', + 0, + 1 + ); + return $loggingVars; + } + + /** + * Returns JSon for query_analyzer + * + * @return array + */ + public static function getJsonForQueryAnalyzer() + { + $return = array(); + + if (strlen($_POST['database']) > 0) { + $GLOBALS['dbi']->selectDb($_POST['database']); + } + + if ($profiling = Util::profilingSupported()) { + $GLOBALS['dbi']->query('SET PROFILING=1;'); + } + + // Do not cache query + $query = preg_replace( + '/^(\s*SELECT)/i', + '\\1 SQL_NO_CACHE', + $_POST['query'] + ); + + $GLOBALS['dbi']->tryQuery($query); + $return['affectedRows'] = $GLOBALS['cached_affected_rows']; + + $result = $GLOBALS['dbi']->tryQuery('EXPLAIN ' . $query); + while ($row = $GLOBALS['dbi']->fetchAssoc($result)) { + $return['explain'][] = $row; + } + + // In case an error happened + $return['error'] = $GLOBALS['dbi']->getError(); + + $GLOBALS['dbi']->freeResult($result); + + if ($profiling) { + $return['profiling'] = array(); + $result = $GLOBALS['dbi']->tryQuery( + 'SELECT seq,state,duration FROM INFORMATION_SCHEMA.PROFILING' + . ' WHERE QUERY_ID=1 ORDER BY seq' + ); + while ($row = $GLOBALS['dbi']->fetchAssoc($result)) { + $return['profiling'][]= $row; + } + $GLOBALS['dbi']->freeResult($result); + } + return $return; + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Server/Status/Processes.php b/php/apps/phpmyadmin49/html/libraries/classes/Server/Status/Processes.php new file mode 100644 index 00000000..336b28b6 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Server/Status/Processes.php @@ -0,0 +1,305 @@ +getDisplay(); + $retval = $notice . ''; + return $retval; + } + + /** + * Prints Server Process list + * + * @return string + */ + public static function getHtmlForServerProcesslist() + { + $show_full_sql = ! empty($_POST['full']); + + // This array contains display name and real column name of each + // sortable column in the table + $sortable_columns = array( + array( + 'column_name' => __('ID'), + 'order_by_field' => 'Id' + ), + array( + 'column_name' => __('User'), + 'order_by_field' => 'User' + ), + array( + 'column_name' => __('Host'), + 'order_by_field' => 'Host' + ), + array( + 'column_name' => __('Database'), + 'order_by_field' => 'db' + ), + array( + 'column_name' => __('Command'), + 'order_by_field' => 'Command' + ), + array( + 'column_name' => __('Time'), + 'order_by_field' => 'Time' + ), + array( + 'column_name' => __('Status'), + 'order_by_field' => 'State' + ), + array( + 'column_name' => __('Progress'), + 'order_by_field' => 'Progress' + ), + array( + 'column_name' => __('SQL query'), + 'order_by_field' => 'Info' + ) + ); + $sortableColCount = count($sortable_columns); + + $sql_query = $show_full_sql + ? 'SHOW FULL PROCESSLIST' + : 'SHOW PROCESSLIST'; + if ((! empty($_POST['order_by_field']) + && ! empty($_POST['sort_order'])) + || (! empty($_POST['showExecuting'])) + ) { + $sql_query = 'SELECT * FROM `INFORMATION_SCHEMA`.`PROCESSLIST` '; + } + if (! empty($_POST['showExecuting'])) { + $sql_query .= ' WHERE state != "" '; + } + if (!empty($_POST['order_by_field']) && !empty($_POST['sort_order'])) { + $sql_query .= ' ORDER BY ' + . Util::backquote($_POST['order_by_field']) + . ' ' . $_POST['sort_order']; + } + + $result = $GLOBALS['dbi']->query($sql_query); + + $retval = '
    '; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + foreach ($sortable_columns as $column) { + + $is_sorted = ! empty($_POST['order_by_field']) + && ! empty($_POST['sort_order']) + && ($_POST['order_by_field'] == $column['order_by_field']); + + $column['sort_order'] = 'ASC'; + if ($is_sorted && $_POST['sort_order'] === 'ASC') { + $column['sort_order'] = 'DESC'; + } + if (isset($_POST['showExecuting'])) { + $column['showExecuting'] = 'on'; + } + + $retval .= ''; + } + + $retval .= ''; + $retval .= ''; + $retval .= ''; + + while ($process = $GLOBALS['dbi']->fetchAssoc($result)) { + $retval .= self::getHtmlForServerProcessItem( + $process, + $show_full_sql + ); + } + $retval .= ''; + $retval .= '
    ' . __('Processes') . ''; + $columnUrl = Url::getCommon($column); + $retval .= ''; + + $retval .= $column['column_name']; + + if ($is_sorted) { + $asc_display_style = 'inline'; + $desc_display_style = 'none'; + if ($_POST['sort_order'] === 'DESC') { + $desc_display_style = 'inline'; + $asc_display_style = 'none'; + } + $retval .= ''
+                    . __('Descending') . ''; + $retval .= ''
+                    . __('Ascending') . ''; + } + + $retval .= ''; + + if (0 === --$sortableColCount) { + $url_params = array(); + if ($show_full_sql) { + $url_params['full'] = ''; + } else { + $url_params['full'] = 1; + } + if (isset($_POST['showExecuting'])) { + $url_params['showExecuting'] = 'on'; + } + if (isset($_POST['order_by_field'])) { + $url_params['order_by_field'] = $_POST['order_by_field']; + } + if (isset($_POST['sort_order'])) { + $url_params['sort_order'] = $_POST['sort_order']; + } + $retval .= ''; + if ($show_full_sql) { + $retval .= Util::getImage('s_partialtext', + __('Truncate Shown Queries'), ['class' => 'icon_fulltext']); + } else { + $retval .= Util::getImage('s_fulltext', + __('Show Full Queries'), ['class' => 'icon_fulltext']); + } + $retval .= ''; + } + $retval .= '
    '; + $retval .= '
    '; + + return $retval; + } + + /** + * Returns the html for the list filter + * + * @return string + */ + public static function getHtmlForProcessListFilter() + { + $showExecuting = ''; + if (! empty($_POST['showExecuting'])) { + $showExecuting = ' checked="checked"'; + } + + $url_params = array( + 'ajax_request' => true, + 'full' => (isset($_POST['full']) ? $_POST['full'] : ''), + 'column_name' => (isset($_POST['column_name']) ? $_POST['column_name'] : ''), + 'order_by_field' + => (isset($_POST['order_by_field']) ? $_POST['order_by_field'] : ''), + 'sort_order' => (isset($_POST['sort_order']) ? $_POST['sort_order'] : ''), + ); + + $retval = ''; + $retval .= '
    '; + $retval .= '' . __('Filters') . ''; + $retval .= '
    '; + $retval .= Url::getHiddenInputs($url_params); + $retval .= ''; + $retval .= '
    '; + $retval .= ''; + $retval .= ''; + $retval .= '
    '; + $retval .= '
    '; + $retval .= '
    '; + + return $retval; + } + + /** + * Prints Every Item of Server Process + * + * @param array $process data of Every Item of Server Process + * @param bool $show_full_sql show full sql or not + * + * @return string + */ + public static function getHtmlForServerProcessItem(array $process, $show_full_sql) + { + // Array keys need to modify due to the way it has used + // to display column values + if ((! empty($_POST['order_by_field']) && ! empty($_POST['sort_order'])) + || (! empty($_POST['showExecuting'])) + ) { + foreach (array_keys($process) as $key) { + $new_key = ucfirst(mb_strtolower($key)); + if ($new_key !== $key) { + $process[$new_key] = $process[$key]; + unset($process[$key]); + } + } + } + + $retval = ''; + $retval .= '' + . __('Kill') . ''; + $retval .= '' . $process['Id'] . ''; + $retval .= '' . htmlspecialchars($process['User']) . ''; + $retval .= '' . htmlspecialchars($process['Host']) . ''; + $retval .= '' . ((! isset($process['db']) + || strlen($process['db']) === 0) + ? '' . __('None') . '' + : htmlspecialchars($process['db'])) . ''; + $retval .= '' . htmlspecialchars($process['Command']) . ''; + $retval .= '' . $process['Time'] . ''; + $processStatusStr = empty($process['State']) ? '---' : $process['State']; + $retval .= '' . $processStatusStr . ''; + $processProgress = empty($process['Progress']) ? '---' : $process['Progress']; + $retval .= '' . $processProgress . ''; + $retval .= ''; + + if (empty($process['Info'])) { + $retval .= '---'; + } else { + $retval .= Util::formatSql($process['Info'], ! $show_full_sql); + } + $retval .= ''; + $retval .= ''; + + return $retval; + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Server/Status/Queries.php b/php/apps/phpmyadmin49/html/libraries/classes/Server/Status/Queries.php new file mode 100644 index 00000000..948aca47 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Server/Status/Queries.php @@ -0,0 +1,156 @@ +status['Uptime']; + $used_queries = $serverStatusData->used_queries; + $total_queries = array_sum($used_queries); + + $retval .= '

    '; + /* l10n: Questions is the name of a MySQL Status variable */ + $retval .= sprintf( + __('Questions since startup: %s'), + Util::formatNumber($total_queries, 0) + ); + $retval .= ' '; + $retval .= Util::showMySQLDocu( + 'server-status-variables', + false, + 'statvar_Questions' + ); + $retval .= '
    '; + $retval .= ''; + $retval .= 'ø ' . __('per hour:') . ' '; + $retval .= Util::formatNumber($total_queries * $hour_factor, 0); + $retval .= '
    '; + $retval .= 'ø ' . __('per minute:') . ' '; + $retval .= Util::formatNumber( + $total_queries * 60 / $serverStatusData->status['Uptime'], + 0 + ); + $retval .= '
    '; + if ($total_queries / $serverStatusData->status['Uptime'] >= 1) { + $retval .= 'ø ' . __('per second:') . ' '; + $retval .= Util::formatNumber( + $total_queries / $serverStatusData->status['Uptime'], + 0 + ); + } + $retval .= '
    '; + $retval .= '

    '; + + $retval .= self::getHtmlForDetails($serverStatusData); + + return $retval; + } + + /** + * Returns the html content for the query details + * + * @param Data $serverStatusData Server status data + * + * @return string + */ + public static function getHtmlForDetails(Data $serverStatusData) + { + $hour_factor = 3600 / $serverStatusData->status['Uptime']; + $used_queries = $serverStatusData->used_queries; + $total_queries = array_sum($used_queries); + // reverse sort by value to show most used statements first + arsort($used_queries); + + //(- $serverStatusData->status['Connections']); + $perc_factor = 100 / $total_queries; + + $retval = ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + + $chart_json = array(); + $query_sum = array_sum($used_queries); + $other_sum = 0; + foreach ($used_queries as $name => $value) { + // For the percentage column, use Questions - Connections, because + // the number of connections is not an item of the Query types + // but is included in Questions. Then the total of the percentages is 100. + $name = str_replace(array('Com_', '_'), array('', ' '), $name); + // Group together values that make out less than 2% into "Other", but only + // if we have more than 6 fractions already + if ($value < $query_sum * 0.02 && count($chart_json)>6) { + $other_sum += $value; + } else { + $chart_json[$name] = $value; + } + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + } + $retval .= ''; + $retval .= '
    ' . __('Statements') . ''; + /* l10n: # = Amount of queries */ + $retval .= __('#'); + $retval .= 'ø ' . __('per hour') + . '%
    ' . htmlspecialchars($name) . ''; + $retval .= htmlspecialchars( + Util::formatNumber($value, 5, 0, true) + ); + $retval .= ''; + $retval .= htmlspecialchars( + Util::formatNumber($value * $hour_factor, 4, 1, true) + ); + $retval .= ''; + $retval .= htmlspecialchars( + Util::formatNumber($value * $perc_factor, 0, 2) + ); + $retval .= '
    '; + + $retval .= '
    '; + + return $retval; + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Server/Status/Variables.php b/php/apps/phpmyadmin49/html/libraries/classes/Server/Status/Variables.php new file mode 100644 index 00000000..2fcdea7c --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Server/Status/Variables.php @@ -0,0 +1,776 @@ +'; + $retval .= '' . __('Filters') . ''; + $retval .= '
    '; + $retval .= Url::getHiddenInputs(); + $retval .= ''; + $retval .= '
    '; + $retval .= ''; + $retval .= ''; + $retval .= '
    '; + $retval .= '
    '; + $retval .= ''; + $retval .= ''; + $retval .= '
    '; + $retval .= '
    '; + $retval .= ''; + $retval .= '
    '; + $retval .= '
    '; + $retval .= ''; + $retval .= ''; + $retval .= '
    '; + $retval .= '
    '; + $retval .= ''; + + return $retval; + } + + /** + * Prints the suggestion links + * + * @param Data $serverStatusData Server status data + * + * @return string + */ + public static function getHtmlForLinkSuggestions(Data $serverStatusData) + { + $retval = ''; + + return $retval; + } + + /** + * Returns a table with variables information + * + * @param Data $serverStatusData Server status data + * + * @return string + */ + public static function getHtmlForVariablesList(Data $serverStatusData) + { + $retval = ''; + $strShowStatus = self::getDescriptions(); + /** + * define some alerts + */ + // name => max value before alert + $alerts = array( + // lower is better + // variable => max value + 'Aborted_clients' => 0, + 'Aborted_connects' => 0, + + 'Binlog_cache_disk_use' => 0, + + 'Created_tmp_disk_tables' => 0, + + 'Handler_read_rnd' => 0, + 'Handler_read_rnd_next' => 0, + + 'Innodb_buffer_pool_pages_dirty' => 0, + 'Innodb_buffer_pool_reads' => 0, + 'Innodb_buffer_pool_wait_free' => 0, + 'Innodb_log_waits' => 0, + 'Innodb_row_lock_time_avg' => 10, // ms + 'Innodb_row_lock_time_max' => 50, // ms + 'Innodb_row_lock_waits' => 0, + + 'Slow_queries' => 0, + 'Delayed_errors' => 0, + 'Select_full_join' => 0, + 'Select_range_check' => 0, + 'Sort_merge_passes' => 0, + 'Opened_tables' => 0, + 'Table_locks_waited' => 0, + 'Qcache_lowmem_prunes' => 0, + + 'Qcache_free_blocks' => + isset($serverStatusData->status['Qcache_total_blocks']) + ? $serverStatusData->status['Qcache_total_blocks'] / 5 + : 0, + 'Slow_launch_threads' => 0, + + // depends on Key_read_requests + // normally lower then 1:0.01 + 'Key_reads' => isset($serverStatusData->status['Key_read_requests']) + ? (0.01 * $serverStatusData->status['Key_read_requests']) : 0, + // depends on Key_write_requests + // normally nearly 1:1 + 'Key_writes' => isset($serverStatusData->status['Key_write_requests']) + ? (0.9 * $serverStatusData->status['Key_write_requests']) : 0, + + 'Key_buffer_fraction' => 0.5, + + // alert if more than 95% of thread cache is in use + 'Threads_cached' => isset($serverStatusData->variables['thread_cache_size']) + ? 0.95 * $serverStatusData->variables['thread_cache_size'] : 0 + + // higher is better + // variable => min value + //'Handler read key' => '> ', + ); + + $retval .= self::getHtmlForRenderVariables( + $serverStatusData, + $alerts, + $strShowStatus + ); + + return $retval; + } + + /** + * Returns HTML for render variables list + * + * @param Data $serverStatusData Server status data + * @param array $alerts Alert Array + * @param array $strShowStatus Status Array + * + * @return string + */ + public static function getHtmlForRenderVariables(Data $serverStatusData, array $alerts, array $strShowStatus) + { + $retval = '
    '; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + + foreach ($serverStatusData->status as $name => $value) { + $retval .= ''; + + $retval .= ''; + + $retval .= ''; + $retval .= ''; + $retval .= ''; + } + $retval .= ''; + $retval .= '
    ' . __('Variable') . '' . __('Value') . '' . __('Description') . '
    '; + $retval .= htmlspecialchars(str_replace('_', ' ', $name)); + // Fields containing % are calculated, + // they can not be described in MySQL documentation + if (mb_strpos($name, '%') === false) { + $retval .= Util::showMySQLDocu( + 'server-status-variables', + false, + 'statvar_' . $name + ); + } + $retval .= ''; + if (isset($alerts[$name])) { + if ($value > $alerts[$name]) { + $retval .= ''; + } else { + $retval .= ''; + } + } + if (substr($name, -1) === '%') { + $retval .= htmlspecialchars( + Util::formatNumber($value, 0, 2) + ) . ' %'; + } elseif (strpos($name, 'Uptime') !== false) { + $retval .= htmlspecialchars( + Util::timespanFormat($value) + ); + } elseif (is_numeric($value) && $value > 1000) { + $retval .= '' + . htmlspecialchars(Util::formatNumber($value, 3, 1)) + . ''; + } elseif (is_numeric($value)) { + $retval .= htmlspecialchars( + Util::formatNumber($value, 3, 1) + ); + } else { + $retval .= htmlspecialchars($value); + } + if (isset($alerts[$name])) { + $retval .= ''; + } + $retval .= ''; + $retval .= ''; + if (isset($alerts[$name])) { + if ($value > $alerts[$name]) { + $retval .= ''; + } else { + $retval .= ''; + } + } + $retval .= htmlspecialchars($value); + if (isset($alerts[$name])) { + $retval .= ''; + } + $retval .= ''; + $retval .= ''; + + if (isset($strShowStatus[$name])) { + $retval .= $strShowStatus[$name]; + } + + if (isset($serverStatusData->links[$name])) { + foreach ($serverStatusData->links[$name] as $link_name => $link_url) { + if ('doc' == $link_name) { + $retval .= Util::showMySQLDocu($link_url); + } else { + $retval .= ' ' + . $link_name . ''; + } + } + unset($link_url, $link_name); + } + $retval .= '
    '; + $retval .= '
    '; + + return $retval; + } + + /** + * Returns a list of variable descriptions + * + * @return array + */ + public static function getDescriptions() + { + /** + * Messages are built using the message name + */ + return array( + 'Aborted_clients' => __( + 'The number of connections that were aborted because the client died' + . ' without closing the connection properly.' + ), + 'Aborted_connects' => __( + 'The number of failed attempts to connect to the MySQL server.' + ), + 'Binlog_cache_disk_use' => __( + 'The number of transactions that used the temporary binary log cache' + . ' but that exceeded the value of binlog_cache_size and used a' + . ' temporary file to store statements from the transaction.' + ), + 'Binlog_cache_use' => __( + 'The number of transactions that used the temporary binary log cache.' + ), + 'Connections' => __( + 'The number of connection attempts (successful or not)' + . ' to the MySQL server.' + ), + 'Created_tmp_disk_tables' => __( + 'The number of temporary tables on disk created automatically by' + . ' the server while executing statements. If' + . ' Created_tmp_disk_tables is big, you may want to increase the' + . ' tmp_table_size value to cause temporary tables to be' + . ' memory-based instead of disk-based.' + ), + 'Created_tmp_files' => __( + 'How many temporary files mysqld has created.' + ), + 'Created_tmp_tables' => __( + 'The number of in-memory temporary tables created automatically' + . ' by the server while executing statements.' + ), + 'Delayed_errors' => __( + 'The number of rows written with INSERT DELAYED for which some' + . ' error occurred (probably duplicate key).' + ), + 'Delayed_insert_threads' => __( + 'The number of INSERT DELAYED handler threads in use. Every' + . ' different table on which one uses INSERT DELAYED gets' + . ' its own thread.' + ), + 'Delayed_writes' => __( + 'The number of INSERT DELAYED rows written.' + ), + 'Flush_commands' => __( + 'The number of executed FLUSH statements.' + ), + 'Handler_commit' => __( + 'The number of internal COMMIT statements.' + ), + 'Handler_delete' => __( + 'The number of times a row was deleted from a table.' + ), + 'Handler_discover' => __( + 'The MySQL server can ask the NDB Cluster storage engine if it' + . ' knows about a table with a given name. This is called discovery.' + . ' Handler_discover indicates the number of time tables have been' + . ' discovered.' + ), + 'Handler_read_first' => __( + 'The number of times the first entry was read from an index. If this' + . ' is high, it suggests that the server is doing a lot of full' + . ' index scans; for example, SELECT col1 FROM foo, assuming that' + . ' col1 is indexed.' + ), + 'Handler_read_key' => __( + 'The number of requests to read a row based on a key. If this is' + . ' high, it is a good indication that your queries and tables' + . ' are properly indexed.' + ), + 'Handler_read_next' => __( + 'The number of requests to read the next row in key order. This is' + . ' incremented if you are querying an index column with a range' + . ' constraint or if you are doing an index scan.' + ), + 'Handler_read_prev' => __( + 'The number of requests to read the previous row in key order.' + . ' This read method is mainly used to optimize ORDER BY … DESC.' + ), + 'Handler_read_rnd' => __( + 'The number of requests to read a row based on a fixed position.' + . ' This is high if you are doing a lot of queries that require' + . ' sorting of the result. You probably have a lot of queries that' + . ' require MySQL to scan whole tables or you have joins that' + . ' don\'t use keys properly.' + ), + 'Handler_read_rnd_next' => __( + 'The number of requests to read the next row in the data file.' + . ' This is high if you are doing a lot of table scans. Generally' + . ' this suggests that your tables are not properly indexed or that' + . ' your queries are not written to take advantage of the indexes' + . ' you have.' + ), + 'Handler_rollback' => __( + 'The number of internal ROLLBACK statements.' + ), + 'Handler_update' => __( + 'The number of requests to update a row in a table.' + ), + 'Handler_write' => __( + 'The number of requests to insert a row in a table.' + ), + 'Innodb_buffer_pool_pages_data' => __( + 'The number of pages containing data (dirty or clean).' + ), + 'Innodb_buffer_pool_pages_dirty' => __( + 'The number of pages currently dirty.' + ), + 'Innodb_buffer_pool_pages_flushed' => __( + 'The number of buffer pool pages that have been requested' + . ' to be flushed.' + ), + 'Innodb_buffer_pool_pages_free' => __( + 'The number of free pages.' + ), + 'Innodb_buffer_pool_pages_latched' => __( + 'The number of latched pages in InnoDB buffer pool. These are pages' + . ' currently being read or written or that can\'t be flushed or' + . ' removed for some other reason.' + ), + 'Innodb_buffer_pool_pages_misc' => __( + 'The number of pages busy because they have been allocated for' + . ' administrative overhead such as row locks or the adaptive' + . ' hash index. This value can also be calculated as' + . ' Innodb_buffer_pool_pages_total - Innodb_buffer_pool_pages_free' + . ' - Innodb_buffer_pool_pages_data.' + ), + 'Innodb_buffer_pool_pages_total' => __( + 'Total size of buffer pool, in pages.' + ), + 'Innodb_buffer_pool_read_ahead_rnd' => __( + 'The number of "random" read-aheads InnoDB initiated. This happens' + . ' when a query is to scan a large portion of a table but in' + . ' random order.' + ), + 'Innodb_buffer_pool_read_ahead_seq' => __( + 'The number of sequential read-aheads InnoDB initiated. This' + . ' happens when InnoDB does a sequential full table scan.' + ), + 'Innodb_buffer_pool_read_requests' => __( + 'The number of logical read requests InnoDB has done.' + ), + 'Innodb_buffer_pool_reads' => __( + 'The number of logical reads that InnoDB could not satisfy' + . ' from buffer pool and had to do a single-page read.' + ), + 'Innodb_buffer_pool_wait_free' => __( + 'Normally, writes to the InnoDB buffer pool happen in the' + . ' background. However, if it\'s necessary to read or create a page' + . ' and no clean pages are available, it\'s necessary to wait for' + . ' pages to be flushed first. This counter counts instances of' + . ' these waits. If the buffer pool size was set properly, this' + . ' value should be small.' + ), + 'Innodb_buffer_pool_write_requests' => __( + 'The number writes done to the InnoDB buffer pool.' + ), + 'Innodb_data_fsyncs' => __( + 'The number of fsync() operations so far.' + ), + 'Innodb_data_pending_fsyncs' => __( + 'The current number of pending fsync() operations.' + ), + 'Innodb_data_pending_reads' => __( + 'The current number of pending reads.' + ), + 'Innodb_data_pending_writes' => __( + 'The current number of pending writes.' + ), + 'Innodb_data_read' => __( + 'The amount of data read so far, in bytes.' + ), + 'Innodb_data_reads' => __( + 'The total number of data reads.' + ), + 'Innodb_data_writes' => __( + 'The total number of data writes.' + ), + 'Innodb_data_written' => __( + 'The amount of data written so far, in bytes.' + ), + 'Innodb_dblwr_pages_written' => __( + 'The number of pages that have been written for' + . ' doublewrite operations.' + ), + 'Innodb_dblwr_writes' => __( + 'The number of doublewrite operations that have been performed.' + ), + 'Innodb_log_waits' => __( + 'The number of waits we had because log buffer was too small and' + . ' we had to wait for it to be flushed before continuing.' + ), + 'Innodb_log_write_requests' => __( + 'The number of log write requests.' + ), + 'Innodb_log_writes' => __( + 'The number of physical writes to the log file.' + ), + 'Innodb_os_log_fsyncs' => __( + 'The number of fsync() writes done to the log file.' + ), + 'Innodb_os_log_pending_fsyncs' => __( + 'The number of pending log file fsyncs.' + ), + 'Innodb_os_log_pending_writes' => __( + 'Pending log file writes.' + ), + 'Innodb_os_log_written' => __( + 'The number of bytes written to the log file.' + ), + 'Innodb_pages_created' => __( + 'The number of pages created.' + ), + 'Innodb_page_size' => __( + 'The compiled-in InnoDB page size (default 16KB). Many values are' + . ' counted in pages; the page size allows them to be easily' + . ' converted to bytes.' + ), + 'Innodb_pages_read' => __( + 'The number of pages read.' + ), + 'Innodb_pages_written' => __( + 'The number of pages written.' + ), + 'Innodb_row_lock_current_waits' => __( + 'The number of row locks currently being waited for.' + ), + 'Innodb_row_lock_time_avg' => __( + 'The average time to acquire a row lock, in milliseconds.' + ), + 'Innodb_row_lock_time' => __( + 'The total time spent in acquiring row locks, in milliseconds.' + ), + 'Innodb_row_lock_time_max' => __( + 'The maximum time to acquire a row lock, in milliseconds.' + ), + 'Innodb_row_lock_waits' => __( + 'The number of times a row lock had to be waited for.' + ), + 'Innodb_rows_deleted' => __( + 'The number of rows deleted from InnoDB tables.' + ), + 'Innodb_rows_inserted' => __( + 'The number of rows inserted in InnoDB tables.' + ), + 'Innodb_rows_read' => __( + 'The number of rows read from InnoDB tables.' + ), + 'Innodb_rows_updated' => __( + 'The number of rows updated in InnoDB tables.' + ), + 'Key_blocks_not_flushed' => __( + 'The number of key blocks in the key cache that have changed but' + . ' haven\'t yet been flushed to disk. It used to be known as' + . ' Not_flushed_key_blocks.' + ), + 'Key_blocks_unused' => __( + 'The number of unused blocks in the key cache. You can use this' + . ' value to determine how much of the key cache is in use.' + ), + 'Key_blocks_used' => __( + 'The number of used blocks in the key cache. This value is a' + . ' high-water mark that indicates the maximum number of blocks' + . ' that have ever been in use at one time.' + ), + 'Key_buffer_fraction_%' => __( + 'Percentage of used key cache (calculated value)' + ), + 'Key_read_requests' => __( + 'The number of requests to read a key block from the cache.' + ), + 'Key_reads' => __( + 'The number of physical reads of a key block from disk. If Key_reads' + . ' is big, then your key_buffer_size value is probably too small.' + . ' The cache miss rate can be calculated as' + . ' Key_reads/Key_read_requests.' + ), + 'Key_read_ratio_%' => __( + 'Key cache miss calculated as rate of physical reads compared' + . ' to read requests (calculated value)' + ), + 'Key_write_requests' => __( + 'The number of requests to write a key block to the cache.' + ), + 'Key_writes' => __( + 'The number of physical writes of a key block to disk.' + ), + 'Key_write_ratio_%' => __( + 'Percentage of physical writes compared' + . ' to write requests (calculated value)' + ), + 'Last_query_cost' => __( + 'The total cost of the last compiled query as computed by the query' + . ' optimizer. Useful for comparing the cost of different query' + . ' plans for the same query. The default value of 0 means that' + . ' no query has been compiled yet.' + ), + 'Max_used_connections' => __( + 'The maximum number of connections that have been in use' + . ' simultaneously since the server started.' + ), + 'Not_flushed_delayed_rows' => __( + 'The number of rows waiting to be written in INSERT DELAYED queues.' + ), + 'Opened_tables' => __( + 'The number of tables that have been opened. If opened tables is' + . ' big, your table cache value is probably too small.' + ), + 'Open_files' => __( + 'The number of files that are open.' + ), + 'Open_streams' => __( + 'The number of streams that are open (used mainly for logging).' + ), + 'Open_tables' => __( + 'The number of tables that are open.' + ), + 'Qcache_free_blocks' => __( + 'The number of free memory blocks in query cache. High numbers can' + . ' indicate fragmentation issues, which may be solved by issuing' + . ' a FLUSH QUERY CACHE statement.' + ), + 'Qcache_free_memory' => __( + 'The amount of free memory for query cache.' + ), + 'Qcache_hits' => __( + 'The number of cache hits.' + ), + 'Qcache_inserts' => __( + 'The number of queries added to the cache.' + ), + 'Qcache_lowmem_prunes' => __( + 'The number of queries that have been removed from the cache to' + . ' free up memory for caching new queries. This information can' + . ' help you tune the query cache size. The query cache uses a' + . ' least recently used (LRU) strategy to decide which queries' + . ' to remove from the cache.' + ), + 'Qcache_not_cached' => __( + 'The number of non-cached queries (not cachable, or not cached' + . ' due to the query_cache_type setting).' + ), + 'Qcache_queries_in_cache' => __( + 'The number of queries registered in the cache.' + ), + 'Qcache_total_blocks' => __( + 'The total number of blocks in the query cache.' + ), + 'Rpl_status' => __( + 'The status of failsafe replication (not yet implemented).' + ), + 'Select_full_join' => __( + 'The number of joins that do not use indexes. If this value is' + . ' not 0, you should carefully check the indexes of your tables.' + ), + 'Select_full_range_join' => __( + 'The number of joins that used a range search on a reference table.' + ), + 'Select_range_check' => __( + 'The number of joins without keys that check for key usage after' + . ' each row. (If this is not 0, you should carefully check the' + . ' indexes of your tables.)' + ), + 'Select_range' => __( + 'The number of joins that used ranges on the first table. (It\'s' + . ' normally not critical even if this is big.)' + ), + 'Select_scan' => __( + 'The number of joins that did a full scan of the first table.' + ), + 'Slave_open_temp_tables' => __( + 'The number of temporary tables currently' + . ' open by the slave SQL thread.' + ), + 'Slave_retried_transactions' => __( + 'Total (since startup) number of times the replication slave SQL' + . ' thread has retried transactions.' + ), + 'Slave_running' => __( + 'This is ON if this server is a slave that is connected to a master.' + ), + 'Slow_launch_threads' => __( + 'The number of threads that have taken more than slow_launch_time' + . ' seconds to create.' + ), + 'Slow_queries' => __( + 'The number of queries that have taken more than long_query_time' + . ' seconds.' + ), + 'Sort_merge_passes' => __( + 'The number of merge passes the sort algorithm has had to do.' + . ' If this value is large, you should consider increasing the' + . ' value of the sort_buffer_size system variable.' + ), + 'Sort_range' => __( + 'The number of sorts that were done with ranges.' + ), + 'Sort_rows' => __( + 'The number of sorted rows.' + ), + 'Sort_scan' => __( + 'The number of sorts that were done by scanning the table.' + ), + 'Table_locks_immediate' => __( + 'The number of times that a table lock was acquired immediately.' + ), + 'Table_locks_waited' => __( + 'The number of times that a table lock could not be acquired' + . ' immediately and a wait was needed. If this is high, and you have' + . ' performance problems, you should first optimize your queries,' + . ' and then either split your table or tables or use replication.' + ), + 'Threads_cached' => __( + 'The number of threads in the thread cache. The cache hit rate can' + . ' be calculated as Threads_created/Connections. If this value is' + . ' red you should raise your thread_cache_size.' + ), + 'Threads_connected' => __( + 'The number of currently open connections.' + ), + 'Threads_created' => __( + 'The number of threads created to handle connections. If' + . ' Threads_created is big, you may want to increase the' + . ' thread_cache_size value. (Normally this doesn\'t give a notable' + . ' performance improvement if you have a good thread' + . ' implementation.)' + ), + 'Threads_cache_hitrate_%' => __( + 'Thread cache hit rate (calculated value)' + ), + 'Threads_running' => __( + 'The number of threads that are not sleeping.' + ) + ); + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Server/UserGroups.php b/php/apps/phpmyadmin49/html/libraries/classes/Server/UserGroups.php new file mode 100644 index 00000000..337cd9a9 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Server/UserGroups.php @@ -0,0 +1,379 @@ +' + . sprintf(__('Users of \'%s\' user group'), htmlspecialchars($userGroup)) + . ''; + + $cfgRelation = $relation->getRelationsParam(); + $usersTable = Util::backquote($cfgRelation['db']) + . "." . Util::backquote($cfgRelation['users']); + $sql_query = "SELECT `username` FROM " . $usersTable + . " WHERE `usergroup`='" . $GLOBALS['dbi']->escapeString($userGroup) + . "'"; + $result = $relation->queryAsControlUser($sql_query, false); + if ($result) { + if ($GLOBALS['dbi']->numRows($result) == 0) { + $html_output .= '

    ' + . __('No users were found belonging to this user group.') + . '

    '; + } else { + $html_output .= '' + . '' + . ''; + $i = 0; + while ($row = $GLOBALS['dbi']->fetchRow($result)) { + $i++; + $html_output .= '' + . '' + . '' + . ''; + } + $html_output .= '' + . '
    #' . __('User') . '
    ' . $i . ' ' . htmlspecialchars($row[0]) . '
    '; + } + } + $GLOBALS['dbi']->freeResult($result); + return $html_output; + } + + /** + * Returns HTML for the 'user groups' table + * + * @return string HTML for the 'user groups' table + */ + public static function getHtmlForUserGroupsTable() + { + $relation = new Relation(); + $html_output = '

    ' . __('User groups') . '

    '; + $cfgRelation = $relation->getRelationsParam(); + $groupTable = Util::backquote($cfgRelation['db']) + . "." . Util::backquote($cfgRelation['usergroups']); + $sql_query = "SELECT * FROM " . $groupTable . " ORDER BY `usergroup` ASC"; + $result = $relation->queryAsControlUser($sql_query, false); + + if ($result && $GLOBALS['dbi']->numRows($result)) { + $html_output .= '
    '; + $html_output .= Url::getHiddenInputs(); + $html_output .= ''; + $html_output .= ''; + $html_output .= ''; + $html_output .= ''; + $html_output .= ''; + $html_output .= ''; + $html_output .= ''; + $html_output .= ''; + $html_output .= ''; + + $userGroups = array(); + while ($row = $GLOBALS['dbi']->fetchAssoc($result)) { + $groupName = $row['usergroup']; + if (! isset($userGroups[$groupName])) { + $userGroups[$groupName] = array(); + } + $userGroups[$groupName][$row['tab']] = $row['allowed']; + } + foreach ($userGroups as $groupName => $tabs) { + $html_output .= ''; + $html_output .= ''; + $html_output .= ''; + $html_output .= ''; + $html_output .= ''; + + $html_output .= ''; + + $html_output .= ''; + } + + $html_output .= ''; + $html_output .= '
    ' + . __('User group') . '' . __('Server level tabs') . '' . __('Database level tabs') . '' . __('Table level tabs') . '' . __('Action') . '
    ' . htmlspecialchars($groupName) . '' . self::getAllowedTabNames($tabs, 'server') . '' . self::getAllowedTabNames($tabs, 'db') . '' . self::getAllowedTabNames($tabs, 'table') . ''; + $html_output .= '' + . Util::getIcon('b_usrlist', __('View users')) + . ''; + $html_output .= '  '; + $html_output .= '' + . Util::getIcon('b_edit', __('Edit')) . ''; + $html_output .= '  '; + $html_output .= '' + . Util::getIcon('b_drop', __('Delete')) . ''; + $html_output .= '
    '; + $html_output .= '
    '; + } + $GLOBALS['dbi']->freeResult($result); + + $html_output .= '
    '; + $html_output .= '' + . Util::getIcon('b_usradd') + . __('Add user group') . ''; + $html_output .= '
    '; + + return $html_output; + } + + /** + * Returns the list of allowed menu tab names + * based on a data row from usergroup table. + * + * @param array $row row of usergroup table + * @param string $level 'server', 'db' or 'table' + * + * @return string comma separated list of allowed menu tab names + */ + public static function getAllowedTabNames(array $row, $level) + { + $tabNames = array(); + $tabs = Util::getMenuTabList($level); + foreach ($tabs as $tab => $tabName) { + if (! isset($row[$level . '_' . $tab]) + || $row[$level . '_' . $tab] == 'Y' + ) { + $tabNames[] = $tabName; + } + } + return implode(', ', $tabNames); + } + + /** + * Deletes a user group + * + * @param string $userGroup user group name + * + * @return void + */ + public static function delete($userGroup) + { + $relation = new Relation(); + $cfgRelation = $relation->getRelationsParam(); + $userTable = Util::backquote($cfgRelation['db']) + . "." . Util::backquote($cfgRelation['users']); + $groupTable = Util::backquote($cfgRelation['db']) + . "." . Util::backquote($cfgRelation['usergroups']); + $sql_query = "DELETE FROM " . $userTable + . " WHERE `usergroup`='" . $GLOBALS['dbi']->escapeString($userGroup) + . "'"; + $relation->queryAsControlUser($sql_query, true); + $sql_query = "DELETE FROM " . $groupTable + . " WHERE `usergroup`='" . $GLOBALS['dbi']->escapeString($userGroup) + . "'"; + $relation->queryAsControlUser($sql_query, true); + } + + /** + * Returns HTML for add/edit user group dialog + * + * @param string $userGroup name of the user group in case of editing + * + * @return string HTML for add/edit user group dialog + */ + public static function getHtmlToEditUserGroup($userGroup = null) + { + $relation = new Relation(); + $html_output = ''; + if ($userGroup == null) { + $html_output .= '

    ' . __('Add user group') . '

    '; + } else { + $html_output .= '

    ' + . sprintf(__('Edit user group: \'%s\''), htmlspecialchars($userGroup)) + . '

    '; + } + + $html_output .= '
    '; + $urlParams = array(); + if ($userGroup != null) { + $urlParams['userGroup'] = $userGroup; + $urlParams['editUserGroupSubmit'] = '1'; + } else { + $urlParams['addUserGroupSubmit'] = '1'; + } + $html_output .= Url::getHiddenInputs($urlParams); + + $html_output .= '
    '; + $html_output .= '' . __('User group menu assignments') + . '   ' + . '' + . '' + . ''; + + if ($userGroup == null) { + $html_output .= ''; + $html_output .= ''; + $html_output .= '
    '; + } + + $allowedTabs = array( + 'server' => array(), + 'db' => array(), + 'table' => array() + ); + if ($userGroup != null) { + $cfgRelation = $relation->getRelationsParam(); + $groupTable = Util::backquote($cfgRelation['db']) + . "." . Util::backquote($cfgRelation['usergroups']); + $sql_query = "SELECT * FROM " . $groupTable + . " WHERE `usergroup`='" . $GLOBALS['dbi']->escapeString($userGroup) + . "'"; + $result = $relation->queryAsControlUser($sql_query, false); + if ($result) { + while ($row = $GLOBALS['dbi']->fetchAssoc($result)) { + $key = $row['tab']; + $value = $row['allowed']; + if (substr($key, 0, 7) == 'server_' && $value == 'Y') { + $allowedTabs['server'][] = mb_substr($key, 7); + } elseif (substr($key, 0, 3) == 'db_' && $value == 'Y') { + $allowedTabs['db'][] = mb_substr($key, 3); + } elseif (substr($key, 0, 6) == 'table_' + && $value == 'Y' + ) { + $allowedTabs['table'][] = mb_substr($key, 6); + } + } + } + $GLOBALS['dbi']->freeResult($result); + } + + $html_output .= self::getTabList( + __('Server-level tabs'), 'server', $allowedTabs['server'] + ); + $html_output .= self::getTabList( + __('Database-level tabs'), 'db', $allowedTabs['db'] + ); + $html_output .= self::getTabList( + __('Table-level tabs'), 'table', $allowedTabs['table'] + ); + + $html_output .= '
    '; + + $html_output .= ''; + + return $html_output; + } + + /** + * Returns HTML for checkbox groups to choose + * tabs of 'server', 'db' or 'table' levels. + * + * @param string $title title of the checkbox group + * @param string $level 'server', 'db' or 'table' + * @param array $selected array of selected allowed tabs + * + * @return string HTML for checkbox groups + */ + public static function getTabList($title, $level, array $selected) + { + $tabs = Util::getMenuTabList($level); + $html_output = '
    '; + $html_output .= '' . $title . ''; + foreach ($tabs as $tab => $tabName) { + $html_output .= '
    '; + $html_output .= ''; + $html_output .= ''; + $html_output .= '
    '; + } + $html_output .= '
    '; + return $html_output; + } + + /** + * Add/update a user group with allowed menu tabs. + * + * @param string $userGroup user group name + * @param boolean $new whether this is a new user group + * + * @return void + */ + public static function edit($userGroup, $new = false) + { + $relation = new Relation(); + $tabs = Util::getMenuTabList(); + $cfgRelation = $relation->getRelationsParam(); + $groupTable = Util::backquote($cfgRelation['db']) + . "." . Util::backquote($cfgRelation['usergroups']); + + if (! $new) { + $sql_query = "DELETE FROM " . $groupTable + . " WHERE `usergroup`='" . $GLOBALS['dbi']->escapeString($userGroup) + . "';"; + $relation->queryAsControlUser($sql_query, true); + } + + $sql_query = "INSERT INTO " . $groupTable + . "(`usergroup`, `tab`, `allowed`)" + . " VALUES "; + $first = true; + foreach ($tabs as $tabGroupName => $tabGroup) { + foreach ($tabGroup as $tab => $tabName) { + if (! $first) { + $sql_query .= ", "; + } + $tabName = $tabGroupName . '_' . $tab; + $allowed = isset($_POST[$tabName]) && $_POST[$tabName] == 'Y'; + $sql_query .= "('" . $GLOBALS['dbi']->escapeString($userGroup) . "', '" . $tabName . "', '" + . ($allowed ? "Y" : "N") . "')"; + $first = false; + } + } + $sql_query .= ";"; + $relation->queryAsControlUser($sql_query, true); + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Server/Users.php b/php/apps/phpmyadmin49/html/libraries/classes/Server/Users.php new file mode 100644 index 00000000..bd505898 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Server/Users.php @@ -0,0 +1,62 @@ + __('User accounts overview'), + 'url' => 'server_privileges.php', + 'params' => Url::getCommon(array('viewing_mode' => 'server')), + ) + ); + + if ($GLOBALS['dbi']->isSuperuser()) { + $items[] = array( + 'name' => __('User groups'), + 'url' => 'server_user_groups.php', + 'params' => Url::getCommon(), + ); + } + + $retval = '
      '; + foreach ($items as $item) { + $class = ''; + if ($item['url'] === $selfUrl) { + $class = ' class="tabactive"'; + } + $retval .= '
    • '; + $retval .= ''; + $retval .= $item['name']; + $retval .= ''; + $retval .= '
    • '; + } + $retval .= '
    '; + $retval .= '
    '; + + return $retval; + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Session.php b/php/apps/phpmyadmin49/html/libraries/classes/Session.php new file mode 100644 index 00000000..7e4cf628 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Session.php @@ -0,0 +1,236 @@ +getMessage()) + ); + } + + /* + * Session initialization is done before selecting language, so we + * can not use translations here. + */ + Core::fatalError( + 'Error during session start; please check your PHP and/or ' + . 'webserver log file and configure your PHP ' + . 'installation properly. Also ensure that cookies are enabled ' + . 'in your browser.' + . '

    ' + . implode('

    ', $messages) + ); + } + + /** + * Set up session + * + * @param PhpMyAdmin\Config $config Configuration handler + * @param PhpMyAdmin\ErrorHandler $errorHandler Error handler + * @return void + */ + public static function setUp(Config $config, ErrorHandler $errorHandler) + { + // verify if PHP supports session, die if it does not + if (!function_exists('session_name')) { + Core::warnMissingExtension('session', true); + } elseif (! empty(ini_get('session.auto_start')) + && session_name() != 'phpMyAdmin' + && !empty(session_id())) { + // Do not delete the existing non empty session, it might be used by + // other applications; instead just close it. + if (empty($_SESSION)) { + // Ignore errors as this might have been destroyed in other + // request meanwhile + @session_destroy(); + } elseif (function_exists('session_abort')) { + // PHP 5.6 and newer + session_abort(); + } else { + session_write_close(); + } + } + + // session cookie settings + session_set_cookie_params( + 0, $config->getRootPath(), + '', $config->isHttps(), true + ); + + // cookies are safer (use ini_set() in case this function is disabled) + ini_set('session.use_cookies', 'true'); + + // optionally set session_save_path + $path = $config->get('SessionSavePath'); + if (!empty($path)) { + session_save_path($path); + // We can not do this unconditionally as this would break + // any more complex setup (eg. cluster), see + // https://github.com/phpmyadmin/phpmyadmin/issues/8346 + ini_set('session.save_handler', 'files'); + } + + // use cookies only + ini_set('session.use_only_cookies', '1'); + // strict session mode (do not accept random string as session ID) + ini_set('session.use_strict_mode', '1'); + // make the session cookie HttpOnly + ini_set('session.cookie_httponly', '1'); + // do not force transparent session ids + ini_set('session.use_trans_sid', '0'); + + // delete session/cookies when browser is closed + ini_set('session.cookie_lifetime', '0'); + + // warn but don't work with bug + ini_set('session.bug_compat_42', 'false'); + ini_set('session.bug_compat_warn', 'true'); + + // use more secure session ids + ini_set('session.hash_function', '1'); + + // some pages (e.g. stylesheet) may be cached on clients, but not in shared + // proxy servers + session_cache_limiter('private'); + + $session_name = 'phpMyAdmin'; + @session_name($session_name); + + // Restore correct sesion ID (it might have been reset by auto started session + if (isset($_COOKIE['phpMyAdmin'])) { + session_id($_COOKIE['phpMyAdmin']); + } + + // on first start of session we check for errors + // f.e. session dir cannot be accessed - session file not created + $orig_error_count = $errorHandler->countErrors(false); + + $session_result = session_start(); + + if ($session_result !== true + || $orig_error_count != $errorHandler->countErrors(false) + ) { + setcookie($session_name, '', 1); + $errors = $errorHandler->sliceErrors($orig_error_count); + self::sessionFailed($errors); + } + unset($orig_error_count, $session_result); + + /** + * Disable setting of session cookies for further session_start() calls. + */ + if(session_status() !== PHP_SESSION_ACTIVE) { + ini_set('session.use_cookies', 'true'); + } + + /** + * Token which is used for authenticating access queries. + * (we use "space PMA_token space" to prevent overwriting) + */ + if (empty($_SESSION[' PMA_token '])) { + self::generateToken(); + + /** + * Check for disk space on session storage by trying to write it. + * + * This seems to be most reliable approach to test if sessions are working, + * otherwise the check would fail with custom session backends. + */ + $orig_error_count = $errorHandler->countErrors(); + session_write_close(); + if ($errorHandler->countErrors() > $orig_error_count) { + $errors = $errorHandler->sliceErrors($orig_error_count); + self::sessionFailed($errors); + } + session_start(); + if (empty($_SESSION[' PMA_token '])) { + Core::fatalError( + 'Failed to store CSRF token in session! ' . + 'Probably sessions are not working properly.' + ); + } + } + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Sql.php b/php/apps/phpmyadmin49/html/libraries/classes/Sql.php new file mode 100644 index 00000000..58cceb74 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Sql.php @@ -0,0 +1,2326 @@ +relation = new Relation(); + } + + /** + * Parses and analyzes the given SQL query. + * + * @param string $sql_query SQL query + * @param string $db DB name + * + * @return mixed + */ + public function parseAndAnalyze($sql_query, $db = null) + { + if (is_null($db) && isset($GLOBALS['db']) && strlen($GLOBALS['db'])) { + $db = $GLOBALS['db']; + } + list($analyzed_sql_results,,) = ParseAnalyze::sqlQuery($sql_query, $db); + return $analyzed_sql_results; + } + + /** + * Handle remembered sorting order, only for single table query + * + * @param string $db database name + * @param string $table table name + * @param array &$analyzed_sql_results the analyzed query results + * @param string &$full_sql_query SQL query + * + * @return void + */ + private function handleSortOrder( + $db, $table, array &$analyzed_sql_results, &$full_sql_query + ) { + $pmatable = new Table($table, $db); + + if (empty($analyzed_sql_results['order'])) { + + // Retrieving the name of the column we should sort after. + $sortCol = $pmatable->getUiProp(Table::PROP_SORTED_COLUMN); + if (empty($sortCol)) { + return; + } + + // Remove the name of the table from the retrieved field name. + $sortCol = str_replace( + Util::backquote($table) . '.', + '', + $sortCol + ); + + // Create the new query. + $full_sql_query = Query::replaceClause( + $analyzed_sql_results['statement'], + $analyzed_sql_results['parser']->list, + 'ORDER BY ' . $sortCol + ); + + // TODO: Avoid reparsing the query. + $analyzed_sql_results = Query::getAll($full_sql_query); + } else { + // Store the remembered table into session. + $pmatable->setUiProp( + Table::PROP_SORTED_COLUMN, + Query::getClause( + $analyzed_sql_results['statement'], + $analyzed_sql_results['parser']->list, + 'ORDER BY' + ) + ); + } + } + + /** + * Append limit clause to SQL query + * + * @param array &$analyzed_sql_results the analyzed query results + * + * @return string limit clause appended SQL query + */ + private function getSqlWithLimitClause(array &$analyzed_sql_results) + { + return Query::replaceClause( + $analyzed_sql_results['statement'], + $analyzed_sql_results['parser']->list, + 'LIMIT ' . $_SESSION['tmpval']['pos'] . ', ' + . $_SESSION['tmpval']['max_rows'] + ); + } + + /** + * Verify whether the result set has columns from just one table + * + * @param array $fields_meta meta fields + * + * @return boolean whether the result set has columns from just one table + */ + private function resultSetHasJustOneTable(array $fields_meta) + { + $just_one_table = true; + $prev_table = ''; + foreach ($fields_meta as $one_field_meta) { + if ($one_field_meta->table != '' + && $prev_table != '' + && $one_field_meta->table != $prev_table + ) { + $just_one_table = false; + } + if ($one_field_meta->table != '') { + $prev_table = $one_field_meta->table; + } + } + return $just_one_table && $prev_table != ''; + } + + /** + * Verify whether the result set contains all the columns + * of at least one unique key + * + * @param string $db database name + * @param string $table table name + * @param array $fields_meta meta fields + * + * @return boolean whether the result set contains a unique key + */ + private function resultSetContainsUniqueKey($db, $table, array $fields_meta) + { + $columns = $GLOBALS['dbi']->getColumns($db, $table); + $resultSetColumnNames = array(); + foreach ($fields_meta as $oneMeta) { + $resultSetColumnNames[] = $oneMeta->name; + } + foreach (Index::getFromTable($table, $db) as $index) { + if ($index->isUnique()) { + $indexColumns = $index->getColumns(); + $numberFound = 0; + foreach ($indexColumns as $indexColumnName => $dummy) { + if (in_array($indexColumnName, $resultSetColumnNames)) { + $numberFound++; + } else if (!in_array($indexColumnName, $columns)) { + $numberFound++; + } else if (strpos($columns[$indexColumnName]['Extra'], 'INVISIBLE') !== false) { + $numberFound++; + } + } + if ($numberFound == count($indexColumns)) { + return true; + } + } + } + return false; + } + + /** + * Get the HTML for relational column dropdown + * During grid edit, if we have a relational field, returns the html for the + * dropdown + * + * @param string $db current database + * @param string $table current table + * @param string $column current column + * @param string $curr_value current selected value + * + * @return string $dropdown html for the dropdown + */ + private function getHtmlForRelationalColumnDropdown($db, $table, $column, $curr_value) + { + $foreigners = $this->relation->getForeigners($db, $table, $column); + + $foreignData = $this->relation->getForeignData($foreigners, $column, false, '', ''); + + if ($foreignData['disp_row'] == null) { + //Handle the case when number of values + //is more than $cfg['ForeignKeyMaxLimit'] + $_url_params = array( + 'db' => $db, + 'table' => $table, + 'field' => $column + ); + + $dropdown = '' + . htmlspecialchars($_POST['curr_value']) + . '' + . '' + . __('Browse foreign values') + . ''; + } else { + $dropdown = $this->relation->foreignDropdown( + $foreignData['disp_row'], + $foreignData['foreign_field'], + $foreignData['foreign_display'], + $curr_value, + $GLOBALS['cfg']['ForeignKeyMaxLimit'] + ); + $dropdown = ''; + } + + return $dropdown; + } + + /** + * Get the HTML for the profiling table and accompanying chart if profiling is set. + * Otherwise returns null + * + * @param string $url_query url query + * @param string $db current database + * @param array $profiling_results array containing the profiling info + * + * @return string $profiling_table html for the profiling table and chart + */ + private function getHtmlForProfilingChart($url_query, $db, $profiling_results) + { + if (! empty($profiling_results)) { + $url_query = isset($url_query) + ? $url_query + : Url::getCommon(array('db' => $db)); + + $profiling_table = ''; + $profiling_table .= '
    ' . __('Profiling') + . '' . "\n"; + $profiling_table .= '
    '; + $profiling_table .= '

    ' . __('Detailed profile') . '

    '; + $profiling_table .= '' . "\n"; + $profiling_table .= ' ' . "\n"; + $profiling_table .= ' ' . "\n"; + $profiling_table .= ' ' . "\n"; + $profiling_table .= ' ' . "\n"; + $profiling_table .= ' ' . "\n"; + list($detailed_table, $chart_json, $profiling_stats) + = $this->analyzeAndGetTableHtmlForProfilingResults($profiling_results); + $profiling_table .= $detailed_table; + $profiling_table .= '
    ' . __('Order') + . '
    ' . __('State') + . Util::showMySQLDocu('general-thread-states') + . '
    ' . __('Time') + . '
    ' . "\n"; + $profiling_table .= '
    '; + + $profiling_table .= '
    '; + $profiling_table .= '

    ' . __('Summary by state') . '

    '; + $profiling_table .= '' . "\n"; + $profiling_table .= ' ' . "\n"; + $profiling_table .= ' ' . "\n"; + $profiling_table .= ' ' . "\n"; + $profiling_table .= ' ' . "\n"; + $profiling_table .= ' ' . "\n"; + $profiling_table .= ' ' . "\n"; + $profiling_table .= ' ' . "\n"; + $profiling_table .= $this->getTableHtmlForProfilingSummaryByState( + $profiling_stats + ); + $profiling_table .= '
    ' . __('State') + . Util::showMySQLDocu('general-thread-states') + . '
    ' . __('Total Time') + . '
    ' . __('% Time') + . '
    ' . __('Calls') + . '
    ' . __('ø Time') + . '
    ' . "\n"; + + $profiling_table .= << + url_query = '$url_query'; + +EOT; + $profiling_table .= "
    "; + $profiling_table .= "
    "; + + //require_once 'libraries/chart.lib.php'; + $profiling_table .= '
    '; + $profiling_table .= json_encode($chart_json); + $profiling_table .= '
    '; + $profiling_table .= '
    '; + $profiling_table .= '
    '; + $profiling_table .= ''; + $profiling_table .= '
    ' . "\n"; + } else { + $profiling_table = null; + } + return $profiling_table; + } + + /** + * Function to get HTML for detailed profiling results table, profiling stats, and + * $chart_json for displaying the chart. + * + * @param array $profiling_results profiling results + * + * @return mixed + */ + private function analyzeAndGetTableHtmlForProfilingResults( + $profiling_results + ) { + $profiling_stats = array( + 'total_time' => 0, + 'states' => array(), + ); + $chart_json = Array(); + $i = 1; + $table = ''; + foreach ($profiling_results as $one_result) { + if (isset($profiling_stats['states'][ucwords($one_result['Status'])])) { + $states = $profiling_stats['states']; + $states[ucwords($one_result['Status'])]['total_time'] + += $one_result['Duration']; + $states[ucwords($one_result['Status'])]['calls']++; + } else { + $profiling_stats['states'][ucwords($one_result['Status'])] = array( + 'total_time' => $one_result['Duration'], + 'calls' => 1, + ); + } + $profiling_stats['total_time'] += $one_result['Duration']; + + $table .= ' ' . "\n"; + $table .= '' . $i++ . '' . "\n"; + $table .= '' . ucwords($one_result['Status']) + . '' . "\n"; + $table .= '' + . (Util::formatNumber($one_result['Duration'], 3, 1)) + . 's' + . $one_result['Duration'] . '' . "\n"; + if (isset($chart_json[ucwords($one_result['Status'])])) { + $chart_json[ucwords($one_result['Status'])] + += $one_result['Duration']; + } else { + $chart_json[ucwords($one_result['Status'])] + = $one_result['Duration']; + } + } + return array($table, $chart_json, $profiling_stats); + } + + /** + * Function to get HTML for summary by state table + * + * @param array $profiling_stats profiling stats + * + * @return string $table html for the table + */ + private function getTableHtmlForProfilingSummaryByState(array $profiling_stats) + { + $table = ''; + foreach ($profiling_stats['states'] as $name => $stats) { + $table .= ' ' . "\n"; + $table .= '' . $name . '' . "\n"; + $table .= '' + . Util::formatNumber($stats['total_time'], 3, 1) + . 's' + . $stats['total_time'] . '' . "\n"; + $table .= '' + . Util::formatNumber( + 100 * ($stats['total_time'] / $profiling_stats['total_time']), + 0, 2 + ) + . '%' . "\n"; + $table .= '' . $stats['calls'] . '' + . "\n"; + $table .= '' + . Util::formatNumber( + $stats['total_time'] / $stats['calls'], 3, 1 + ) + . 's' + . number_format($stats['total_time'] / $stats['calls'], 8, '.', '') + . '' . "\n"; + $table .= ' ' . "\n"; + } + return $table; + } + + /** + * Get the HTML for the enum column dropdown + * During grid edit, if we have a enum field, returns the html for the + * dropdown + * + * @param string $db current database + * @param string $table current table + * @param string $column current column + * @param string $curr_value currently selected value + * + * @return string $dropdown html for the dropdown + */ + private function getHtmlForEnumColumnDropdown($db, $table, $column, $curr_value) + { + $values = $this->getValuesForColumn($db, $table, $column); + $dropdown = ''; + $dropdown .= $this->getHtmlForOptionsList($values, array($curr_value)); + $dropdown = ''; + return $dropdown; + } + + /** + * Get value of a column for a specific row (marked by $where_clause) + * + * @param string $db current database + * @param string $table current table + * @param string $column current column + * @param string $where_clause where clause to select a particular row + * + * @return string with value + */ + private function getFullValuesForSetColumn($db, $table, $column, $where_clause) + { + $result = $GLOBALS['dbi']->fetchSingleRow( + "SELECT `$column` FROM `$db`.`$table` WHERE $where_clause" + ); + + return $result[$column]; + } + + /** + * Get the HTML for the set column dropdown + * During grid edit, if we have a set field, returns the html for the + * dropdown + * + * @param string $db current database + * @param string $table current table + * @param string $column current column + * @param string $curr_value currently selected value + * + * @return string $dropdown html for the set column + */ + private function getHtmlForSetColumn($db, $table, $column, $curr_value) + { + $values = $this->getValuesForColumn($db, $table, $column); + $dropdown = ''; + $full_values = + isset($_POST['get_full_values']) ? $_POST['get_full_values'] : false; + $where_clause = + isset($_POST['where_clause']) ? $_POST['where_clause'] : null; + + // If the $curr_value was truncated, we should + // fetch the correct full values from the table + if ($full_values && ! empty($where_clause)) { + $curr_value = $this->getFullValuesForSetColumn( + $db, $table, $column, $where_clause + ); + } + + //converts characters of $curr_value to HTML entities + $converted_curr_value = htmlentities( + $curr_value, ENT_COMPAT, "UTF-8" + ); + + $selected_values = explode(',', $converted_curr_value); + + $dropdown .= $this->getHtmlForOptionsList($values, $selected_values); + + $select_size = (sizeof($values) > 10) ? 10 : sizeof($values); + $dropdown = ''; + + return $dropdown; + } + + /** + * Get all the values for a enum column or set column in a table + * + * @param string $db current database + * @param string $table current table + * @param string $column current column + * + * @return array $values array containing the value list for the column + */ + private function getValuesForColumn($db, $table, $column) + { + $field_info_query = $GLOBALS['dbi']->getColumnsSql($db, $table, $column); + + $field_info_result = $GLOBALS['dbi']->fetchResult( + $field_info_query, + null, + null, + DatabaseInterface::CONNECT_USER, + DatabaseInterface::QUERY_STORE + ); + + $values = Util::parseEnumSetValues($field_info_result[0]['Type']); + + return $values; + } + + /** + * Get HTML for options list + * + * @param array $values set of values + * @param array $selected_values currently selected values + * + * @return string $options HTML for options list + */ + private function getHtmlForOptionsList(array $values, array $selected_values) + { + $options = ''; + foreach ($values as $value) { + $options .= '
    '; + + } else { + $html = null; + } + + return $html; + } + + /** + * Function to check whether to remember the sorting order or not + * + * @param array $analyzed_sql_results the analyzed query and other variables set + * after analyzing the query + * + * @return boolean + */ + private function isRememberSortingOrder(array $analyzed_sql_results) + { + return $GLOBALS['cfg']['RememberSorting'] + && ! ($analyzed_sql_results['is_count'] + || $analyzed_sql_results['is_export'] + || $analyzed_sql_results['is_func'] + || $analyzed_sql_results['is_analyse']) + && $analyzed_sql_results['select_from'] + && isset($analyzed_sql_results['select_expr']) + && isset($analyzed_sql_results['select_tables']) + && ((empty($analyzed_sql_results['select_expr'])) + || ((count($analyzed_sql_results['select_expr']) == 1) + && ($analyzed_sql_results['select_expr'][0] == '*'))) + && count($analyzed_sql_results['select_tables']) == 1; + } + + /** + * Function to check whether the LIMIT clause should be appended or not + * + * @param array $analyzed_sql_results the analyzed query and other variables set + * after analyzing the query + * + * @return boolean + */ + private function isAppendLimitClause(array $analyzed_sql_results) + { + // Assigning LIMIT clause to an syntactically-wrong query + // is not needed. Also we would want to show the true query + // and the true error message to the query executor + + return (isset($analyzed_sql_results['parser']) + && count($analyzed_sql_results['parser']->errors) === 0) + && ($_SESSION['tmpval']['max_rows'] != 'all') + && ! ($analyzed_sql_results['is_export'] + || $analyzed_sql_results['is_analyse']) + && ($analyzed_sql_results['select_from'] + || $analyzed_sql_results['is_subquery']) + && empty($analyzed_sql_results['limit']); + } + + /** + * Function to check whether this query is for just browsing + * + * @param array $analyzed_sql_results the analyzed query and other variables set + * after analyzing the query + * @param boolean $find_real_end whether the real end should be found + * + * @return boolean + */ + public function isJustBrowsing(array $analyzed_sql_results, $find_real_end) + { + return ! $analyzed_sql_results['is_group'] + && ! $analyzed_sql_results['is_func'] + && empty($analyzed_sql_results['union']) + && empty($analyzed_sql_results['distinct']) + && $analyzed_sql_results['select_from'] + && (count($analyzed_sql_results['select_tables']) === 1) + && (empty($analyzed_sql_results['statement']->where) + || (count($analyzed_sql_results['statement']->where) == 1 + && $analyzed_sql_results['statement']->where[0]->expr ==='1')) + && empty($analyzed_sql_results['group']) + && ! isset($find_real_end) + && ! $analyzed_sql_results['is_subquery'] + && ! $analyzed_sql_results['join'] + && empty($analyzed_sql_results['having']); + } + + /** + * Function to check whether the related transformation information should be deleted + * + * @param array $analyzed_sql_results the analyzed query and other variables set + * after analyzing the query + * + * @return boolean + */ + private function isDeleteTransformationInfo(array $analyzed_sql_results) + { + return !empty($analyzed_sql_results['querytype']) + && (($analyzed_sql_results['querytype'] == 'ALTER') + || ($analyzed_sql_results['querytype'] == 'DROP')); + } + + /** + * Function to check whether the user has rights to drop the database + * + * @param array $analyzed_sql_results the analyzed query and other variables set + * after analyzing the query + * @param boolean $allowUserDropDatabase whether the user is allowed to drop db + * @param boolean $is_superuser whether this user is a superuser + * + * @return boolean + */ + public function hasNoRightsToDropDatabase(array $analyzed_sql_results, + $allowUserDropDatabase, $is_superuser + ) { + return ! $allowUserDropDatabase + && isset($analyzed_sql_results['drop_database']) + && $analyzed_sql_results['drop_database'] + && ! $is_superuser; + } + + /** + * Function to set a column property + * + * @param Table $pmatable Table instance + * @param string $request_index col_order|col_visib + * + * @return boolean $retval + */ + private function setColumnProperty($pmatable, $request_index) + { + $property_value = array_map('intval', explode(',', $_POST[$request_index])); + switch($request_index) { + case 'col_order': + $property_to_set = Table::PROP_COLUMN_ORDER; + break; + case 'col_visib': + $property_to_set = Table::PROP_COLUMN_VISIB; + break; + default: + $property_to_set = ''; + } + $retval = $pmatable->setUiProp( + $property_to_set, + $property_value, + $_POST['table_create_time'] + ); + if (gettype($retval) != 'boolean') { + $response = Response::getInstance(); + $response->setRequestStatus(false); + $response->addJSON('message', $retval->getString()); + exit; + } + + return $retval; + } + + /** + * Function to check the request for setting the column order or visibility + * + * @param string $table the current table + * @param string $db the current database + * + * @return void + */ + public function setColumnOrderOrVisibility($table, $db) + { + $pmatable = new Table($table, $db); + $retval = false; + + // set column order + if (isset($_POST['col_order'])) { + $retval = $this->setColumnProperty($pmatable, 'col_order'); + } + + // set column visibility + if ($retval === true && isset($_POST['col_visib'])) { + $retval = $this->setColumnProperty($pmatable, 'col_visib'); + } + + $response = Response::getInstance(); + $response->setRequestStatus($retval == true); + exit; + } + + /** + * Function to add a bookmark + * + * @param string $goto goto page URL + * + * @return void + */ + public function addBookmark($goto) + { + $bookmark = Bookmark::createBookmark( + $GLOBALS['dbi'], + $GLOBALS['cfg']['Server']['user'], + $_POST['bkm_fields'], + (isset($_POST['bkm_all_users']) + && $_POST['bkm_all_users'] == 'true' ? true : false + ) + ); + $result = $bookmark->save(); + $response = Response::getInstance(); + if ($response->isAjax()) { + if ($result) { + $msg = Message::success(__('Bookmark %s has been created.')); + $msg->addParam($_POST['bkm_fields']['bkm_label']); + $response->addJSON('message', $msg); + } else { + $msg = Message::error(__('Bookmark not created!')); + $response->setRequestStatus(false); + $response->addJSON('message', $msg); + } + exit; + } else { + // go back to sql.php to redisplay query; do not use & in this case: + /** + * @todo In which scenario does this happen? + */ + Core::sendHeaderLocation( + './' . $goto + . '&label=' . $_POST['bkm_fields']['bkm_label'] + ); + } + } + + /** + * Function to find the real end of rows + * + * @param string $db the current database + * @param string $table the current table + * + * @return mixed the number of rows if "retain" param is true, otherwise true + */ + public function findRealEndOfRows($db, $table) + { + $unlim_num_rows = $GLOBALS['dbi']->getTable($db, $table)->countRecords(true); + $_SESSION['tmpval']['pos'] = $this->getStartPosToDisplayRow($unlim_num_rows); + + return $unlim_num_rows; + } + + /** + * Function to get values for the relational columns + * + * @param string $db the current database + * @param string $table the current table + * + * @return void + */ + public function getRelationalValues($db, $table) + { + $column = $_POST['column']; + if ($_SESSION['tmpval']['relational_display'] == 'D' + && isset($_POST['relation_key_or_display_column']) + && $_POST['relation_key_or_display_column'] + ) { + $curr_value = $_POST['relation_key_or_display_column']; + } else { + $curr_value = $_POST['curr_value']; + } + $dropdown = $this->getHtmlForRelationalColumnDropdown( + $db, $table, $column, $curr_value + ); + $response = Response::getInstance(); + $response->addJSON('dropdown', $dropdown); + exit; + } + + /** + * Function to get values for Enum or Set Columns + * + * @param string $db the current database + * @param string $table the current table + * @param string $columnType whether enum or set + * + * @return void + */ + public function getEnumOrSetValues($db, $table, $columnType) + { + $column = $_POST['column']; + $curr_value = $_POST['curr_value']; + $response = Response::getInstance(); + if ($columnType == "enum") { + $dropdown = $this->getHtmlForEnumColumnDropdown( + $db, $table, $column, $curr_value + ); + $response->addJSON('dropdown', $dropdown); + } else { + $select = $this->getHtmlForSetColumn( + $db, $table, $column, $curr_value + ); + $response->addJSON('select', $select); + } + exit; + } + + /** + * Function to get the default sql query for browsing page + * + * @param string $db the current database + * @param string $table the current table + * + * @return string $sql_query the default $sql_query for browse page + */ + public function getDefaultSqlQueryForBrowse($db, $table) + { + $bookmark = Bookmark::get( + $GLOBALS['dbi'], + $GLOBALS['cfg']['Server']['user'], + $db, + $table, + 'label', + false, + true + ); + + if (! empty($bookmark) && ! empty($bookmark->getQuery())) { + $GLOBALS['using_bookmark_message'] = Message::notice( + __('Using bookmark "%s" as default browse query.') + ); + $GLOBALS['using_bookmark_message']->addParam($table); + $GLOBALS['using_bookmark_message']->addHtml( + Util::showDocu('faq', 'faq6-22') + ); + $sql_query = $bookmark->getQuery(); + } else { + + $defaultOrderByClause = ''; + + if (isset($GLOBALS['cfg']['TablePrimaryKeyOrder']) + && ($GLOBALS['cfg']['TablePrimaryKeyOrder'] !== 'NONE') + ) { + + $primaryKey = null; + $primary = Index::getPrimary($table, $db); + + if ($primary !== false) { + + $primarycols = $primary->getColumns(); + + foreach ($primarycols as $col) { + $primaryKey = $col->getName(); + break; + } + + if ($primaryKey != null) { + $defaultOrderByClause = ' ORDER BY ' + . Util::backquote($table) . '.' + . Util::backquote($primaryKey) . ' ' + . $GLOBALS['cfg']['TablePrimaryKeyOrder']; + } + + } + + } + + $sql_query = 'SELECT * FROM ' . Util::backquote($table) + . $defaultOrderByClause; + + } + + return $sql_query; + } + + /** + * Responds an error when an error happens when executing the query + * + * @param boolean $is_gotofile whether goto file or not + * @param string $error error after executing the query + * @param string $full_sql_query full sql query + * + * @return void + */ + private function handleQueryExecuteError($is_gotofile, $error, $full_sql_query) + { + if ($is_gotofile) { + $message = Message::rawError($error); + $response = Response::getInstance(); + $response->setRequestStatus(false); + $response->addJSON('message', $message); + } else { + Util::mysqlDie($error, $full_sql_query, '', ''); + } + exit; + } + + /** + * Function to store the query as a bookmark + * + * @param string $db the current database + * @param string $bkm_user the bookmarking user + * @param string $sql_query_for_bookmark the query to be stored in bookmark + * @param string $bkm_label bookmark label + * @param boolean $bkm_replace whether to replace existing bookmarks + * + * @return void + */ + public function storeTheQueryAsBookmark($db, $bkm_user, $sql_query_for_bookmark, + $bkm_label, $bkm_replace + ) { + $bfields = array( + 'bkm_database' => $db, + 'bkm_user' => $bkm_user, + 'bkm_sql_query' => $sql_query_for_bookmark, + 'bkm_label' => $bkm_label, + ); + + // Should we replace bookmark? + if (isset($bkm_replace)) { + $bookmarks = Bookmark::getList( + $GLOBALS['dbi'], + $GLOBALS['cfg']['Server']['user'], + $db + ); + foreach ($bookmarks as $bookmark) { + if ($bookmark->getLabel() == $bkm_label) { + $bookmark->delete(); + } + } + } + + $bookmark = Bookmark::createBookmark( + $GLOBALS['dbi'], + $GLOBALS['cfg']['Server']['user'], + $bfields, + isset($_POST['bkm_all_users']) + ); + $bookmark->save(); + } + + /** + * Executes the SQL query and measures its execution time + * + * @param string $full_sql_query the full sql query + * + * @return array ($result, $querytime) + */ + private function executeQueryAndMeasureTime($full_sql_query) + { + // close session in case the query takes too long + session_write_close(); + + // Measure query time. + $querytime_before = array_sum(explode(' ', microtime())); + + $result = @$GLOBALS['dbi']->tryQuery( + $full_sql_query, DatabaseInterface::CONNECT_USER, DatabaseInterface::QUERY_STORE + ); + $querytime_after = array_sum(explode(' ', microtime())); + + // reopen session + session_start(); + + return array($result, $querytime_after - $querytime_before); + } + + /** + * Function to get the affected or changed number of rows after executing a query + * + * @param boolean $is_affected whether the query affected a table + * @param mixed $result results of executing the query + * + * @return int $num_rows number of rows affected or changed + */ + private function getNumberOfRowsAffectedOrChanged($is_affected, $result) + { + if (! $is_affected) { + $num_rows = ($result) ? @$GLOBALS['dbi']->numRows($result) : 0; + } else { + $num_rows = @$GLOBALS['dbi']->affectedRows(); + } + + return $num_rows; + } + + /** + * Checks if the current database has changed + * This could happen if the user sends a query like "USE `database`;" + * + * @param string $db the database in the query + * + * @return int $reload whether to reload the navigation(1) or not(0) + */ + private function hasCurrentDbChanged($db) + { + if (strlen($db) > 0) { + $current_db = $GLOBALS['dbi']->fetchValue('SELECT DATABASE()'); + // $current_db is false, except when a USE statement was sent + return ($current_db != false) && ($db !== $current_db); + } + + return false; + } + + /** + * If a table, database or column gets dropped, clean comments. + * + * @param string $db current database + * @param string $table current table + * @param string $column current column + * @param bool $purge whether purge set or not + * + * @return array $extra_data + */ + private function cleanupRelations($db, $table, $column, $purge) + { + if (! empty($purge) && strlen($db) > 0) { + if (strlen($table) > 0) { + if (isset($column) && strlen($column) > 0) { + RelationCleanup::column($db, $table, $column); + } else { + RelationCleanup::table($db, $table); + } + } else { + RelationCleanup::database($db); + } + } + } + + /** + * Function to count the total number of rows for the same 'SELECT' query without + * the 'LIMIT' clause that may have been programatically added + * + * @param int $num_rows number of rows affected/changed by the query + * @param bool $justBrowsing whether just browsing or not + * @param string $db the current database + * @param string $table the current table + * @param array $analyzed_sql_results the analyzed query and other variables set + * after analyzing the query + * + * @return int $unlim_num_rows unlimited number of rows + */ + private function countQueryResults( + $num_rows, $justBrowsing, $db, $table, array $analyzed_sql_results + ) { + + /* Shortcut for not analyzed/empty query */ + if (empty($analyzed_sql_results)) { + return 0; + } + + if (!$this->isAppendLimitClause($analyzed_sql_results)) { + // if we did not append a limit, set this to get a correct + // "Showing rows..." message + // $_SESSION['tmpval']['max_rows'] = 'all'; + $unlim_num_rows = $num_rows; + } elseif ($this->isAppendLimitClause($analyzed_sql_results) && $_SESSION['tmpval']['max_rows'] > $num_rows) { + // When user has not defined a limit in query and total rows in + // result are less than max_rows to display, there is no need + // to count total rows for that query again + $unlim_num_rows = $_SESSION['tmpval']['pos'] + $num_rows; + } elseif ($analyzed_sql_results['querytype'] == 'SELECT' + || $analyzed_sql_results['is_subquery'] + ) { + // c o u n t q u e r y + + // If we are "just browsing", there is only one table (and no join), + // and no WHERE clause (or just 'WHERE 1 '), + // we do a quick count (which uses MaxExactCount) because + // SQL_CALC_FOUND_ROWS is not quick on large InnoDB tables + + // However, do not count again if we did it previously + // due to $find_real_end == true + if ($justBrowsing) { + // Get row count (is approximate for InnoDB) + $unlim_num_rows = $GLOBALS['dbi']->getTable($db, $table)->countRecords(); + /** + * @todo Can we know at this point that this is InnoDB, + * (in this case there would be no need for getting + * an exact count)? + */ + if ($unlim_num_rows < $GLOBALS['cfg']['MaxExactCount']) { + // Get the exact count if approximate count + // is less than MaxExactCount + /** + * @todo In countRecords(), MaxExactCount is also verified, + * so can we avoid checking it twice? + */ + $unlim_num_rows = $GLOBALS['dbi']->getTable($db, $table) + ->countRecords(true); + } + + } else { + + // The SQL_CALC_FOUND_ROWS option of the SELECT statement is used. + + // For UNION statements, only a SQL_CALC_FOUND_ROWS is required + // after the first SELECT. + + $count_query = Query::replaceClause( + $analyzed_sql_results['statement'], + $analyzed_sql_results['parser']->list, + 'SELECT SQL_CALC_FOUND_ROWS', + null, + true + ); + + // Another LIMIT clause is added to avoid long delays. + // A complete result will be returned anyway, but the LIMIT would + // stop the query as soon as the result that is required has been + // computed. + + if (empty($analyzed_sql_results['union'])) { + $count_query .= ' LIMIT 1'; + } + + // Running the count query. + $GLOBALS['dbi']->tryQuery($count_query); + + $unlim_num_rows = $GLOBALS['dbi']->fetchValue('SELECT FOUND_ROWS()'); + } // end else "just browsing" + } else {// not $is_select + $unlim_num_rows = 0; + } + + return $unlim_num_rows; + } + + /** + * Function to handle all aspects relating to executing the query + * + * @param array $analyzed_sql_results analyzed sql results + * @param string $full_sql_query full sql query + * @param boolean $is_gotofile whether to go to a file + * @param string $db current database + * @param string $table current table + * @param boolean $find_real_end whether to find the real end + * @param string $sql_query_for_bookmark sql query to be stored as bookmark + * @param array $extra_data extra data + * + * @return mixed + */ + private function executeTheQuery(array $analyzed_sql_results, $full_sql_query, $is_gotofile, + $db, $table, $find_real_end, $sql_query_for_bookmark, $extra_data + ) { + $response = Response::getInstance(); + $response->getHeader()->getMenu()->setTable($table); + + // Only if we ask to see the php code + if (isset($GLOBALS['show_as_php'])) { + $result = null; + $num_rows = 0; + $unlim_num_rows = 0; + } else { // If we don't ask to see the php code + if (isset($_SESSION['profiling']) + && Util::profilingSupported() + ) { + $GLOBALS['dbi']->query('SET PROFILING=1;'); + } + + list( + $result, + $GLOBALS['querytime'] + ) = $this->executeQueryAndMeasureTime($full_sql_query); + + // Displays an error message if required and stop parsing the script + $error = $GLOBALS['dbi']->getError(); + if ($error && $GLOBALS['cfg']['IgnoreMultiSubmitErrors']) { + $extra_data['error'] = $error; + } elseif ($error) { + $this->handleQueryExecuteError($is_gotofile, $error, $full_sql_query); + } + + // If there are no errors and bookmarklabel was given, + // store the query as a bookmark + if (! empty($_POST['bkm_label']) && ! empty($sql_query_for_bookmark)) { + $cfgBookmark = Bookmark::getParams($GLOBALS['cfg']['Server']['user']); + $this->storeTheQueryAsBookmark( + $db, $cfgBookmark['user'], + $sql_query_for_bookmark, $_POST['bkm_label'], + isset($_POST['bkm_replace']) ? $_POST['bkm_replace'] : null + ); + } // end store bookmarks + + // Gets the number of rows affected/returned + // (This must be done immediately after the query because + // mysql_affected_rows() reports about the last query done) + $num_rows = $this->getNumberOfRowsAffectedOrChanged( + $analyzed_sql_results['is_affected'], $result + ); + + // Grabs the profiling results + if (isset($_SESSION['profiling']) + && Util::profilingSupported() + ) { + $profiling_results = $GLOBALS['dbi']->fetchResult('SHOW PROFILE;'); + } + + $justBrowsing = $this->isJustBrowsing( + $analyzed_sql_results, isset($find_real_end) ? $find_real_end : null + ); + + $unlim_num_rows = $this->countQueryResults( + $num_rows, $justBrowsing, $db, $table, $analyzed_sql_results + ); + + $this->cleanupRelations( + isset($db) ? $db : '', + isset($table) ? $table : '', + isset($_POST['dropped_column']) ? $_POST['dropped_column'] : null, + isset($_POST['purge']) ? $_POST['purge'] : null + ); + + if (isset($_POST['dropped_column']) + && strlen($db) > 0 + && strlen($table) > 0 + ) { + // to refresh the list of indexes (Ajax mode) + $extra_data['indexes_list'] = Index::getHtmlForIndexes( + $table, + $db + ); + } + } + + return array($result, $num_rows, $unlim_num_rows, + isset($profiling_results) ? $profiling_results : null, $extra_data + ); + } + /** + * Delete related transformation information + * + * @param string $db current database + * @param string $table current table + * @param array $analyzed_sql_results analyzed sql results + * + * @return void + */ + private function deleteTransformationInfo($db, $table, array $analyzed_sql_results) + { + if (! isset($analyzed_sql_results['statement'])) { + return; + } + $statement = $analyzed_sql_results['statement']; + if ($statement instanceof AlterStatement) { + if (!empty($statement->altered[0]) + && $statement->altered[0]->options->has('DROP') + ) { + if (!empty($statement->altered[0]->field->column)) { + Transformations::clear( + $db, + $table, + $statement->altered[0]->field->column + ); + } + } + } elseif ($statement instanceof DropStatement) { + Transformations::clear($db, $table); + } + } + + /** + * Function to get the message for the no rows returned case + * + * @param string $message_to_show message to show + * @param array $analyzed_sql_results analyzed sql results + * @param int $num_rows number of rows + * + * @return string $message + */ + private function getMessageForNoRowsReturned($message_to_show, + array $analyzed_sql_results, $num_rows + ) { + if ($analyzed_sql_results['querytype'] == 'DELETE"') { + $message = Message::getMessageForDeletedRows($num_rows); + } elseif ($analyzed_sql_results['is_insert']) { + if ($analyzed_sql_results['querytype'] == 'REPLACE') { + // For REPLACE we get DELETED + INSERTED row count, + // so we have to call it affected + $message = Message::getMessageForAffectedRows($num_rows); + } else { + $message = Message::getMessageForInsertedRows($num_rows); + } + $insert_id = $GLOBALS['dbi']->insertId(); + if ($insert_id != 0) { + // insert_id is id of FIRST record inserted in one insert, + // so if we inserted multiple rows, we had to increment this + $message->addText('[br]'); + // need to use a temporary because the Message class + // currently supports adding parameters only to the first + // message + $_inserted = Message::notice(__('Inserted row id: %1$d')); + $_inserted->addParam($insert_id + $num_rows - 1); + $message->addMessage($_inserted); + } + } elseif ($analyzed_sql_results['is_affected']) { + $message = Message::getMessageForAffectedRows($num_rows); + + // Ok, here is an explanation for the !$is_select. + // The form generated by PhpMyAdmin\SqlQueryForm + // and db_sql.php has many submit buttons + // on the same form, and some confusion arises from the + // fact that $message_to_show is sent for every case. + // The $message_to_show containing a success message and sent with + // the form should not have priority over errors + } elseif (! empty($message_to_show) + && $analyzed_sql_results['querytype'] != 'SELECT' + ) { + $message = Message::rawSuccess(htmlspecialchars($message_to_show)); + } elseif (! empty($GLOBALS['show_as_php'])) { + $message = Message::success(__('Showing as PHP code')); + } elseif (isset($GLOBALS['show_as_php'])) { + /* User disable showing as PHP, query is only displayed */ + $message = Message::notice(__('Showing SQL query')); + } else { + $message = Message::success( + __('MySQL returned an empty result set (i.e. zero rows).') + ); + } + + if (isset($GLOBALS['querytime'])) { + $_querytime = Message::notice( + '(' . __('Query took %01.4f seconds.') . ')' + ); + $_querytime->addParam($GLOBALS['querytime']); + $message->addMessage($_querytime); + } + + // In case of ROLLBACK, notify the user. + if (isset($_POST['rollback_query'])) { + $message->addText(__('[ROLLBACK occurred.]')); + } + + return $message; + } + + /** + * Function to respond back when the query returns zero rows + * This method is called + * 1-> When browsing an empty table + * 2-> When executing a query on a non empty table which returns zero results + * 3-> When executing a query on an empty table + * 4-> When executing an INSERT, UPDATE, DELETE query from the SQL tab + * 5-> When deleting a row from BROWSE tab + * 6-> When searching using the SEARCH tab which returns zero results + * 7-> When changing the structure of the table except change operation + * + * @param array $analyzed_sql_results analyzed sql results + * @param string $db current database + * @param string $table current table + * @param string $message_to_show message to show + * @param int $num_rows number of rows + * @param DisplayResults $displayResultsObject DisplayResult instance + * @param array $extra_data extra data + * @param string $pmaThemeImage uri of the theme image + * @param object $result executed query results + * @param string $sql_query sql query + * @param string $complete_query complete sql query + * + * @return string html + */ + private function getQueryResponseForNoResultsReturned(array $analyzed_sql_results, $db, + $table, $message_to_show, $num_rows, $displayResultsObject, $extra_data, + $pmaThemeImage, $result, $sql_query, $complete_query + ) { + if ($this->isDeleteTransformationInfo($analyzed_sql_results)) { + $this->deleteTransformationInfo($db, $table, $analyzed_sql_results); + } + + if (isset($extra_data['error'])) { + $message = Message::rawError($extra_data['error']); + } else { + $message = $this->getMessageForNoRowsReturned( + isset($message_to_show) ? $message_to_show : null, + $analyzed_sql_results, $num_rows + ); + } + + $html_output = ''; + $html_message = Util::getMessage( + $message, $GLOBALS['sql_query'], 'success' + ); + $html_output .= $html_message; + if (!isset($GLOBALS['show_as_php'])) { + + if (! empty($GLOBALS['reload'])) { + $extra_data['reload'] = 1; + $extra_data['db'] = $GLOBALS['db']; + } + + // For ajax requests add message and sql_query as JSON + if (empty($_REQUEST['ajax_page_request'])) { + $extra_data['message'] = $message; + if ($GLOBALS['cfg']['ShowSQL']) { + $extra_data['sql_query'] = $html_message; + } + } + + $response = Response::getInstance(); + $response->addJSON(isset($extra_data) ? $extra_data : array()); + + if (!empty($analyzed_sql_results['is_select']) && + !isset($extra_data['error'])) { + $url_query = isset($url_query) ? $url_query : null; + + $displayParts = array( + 'edit_lnk' => null, + 'del_lnk' => null, + 'sort_lnk' => '1', + 'nav_bar' => '0', + 'bkm_form' => '1', + 'text_btn' => '1', + 'pview_lnk' => '1' + ); + + $html_output .= $this->getHtmlForSqlQueryResultsTable( + $displayResultsObject, + $pmaThemeImage, $url_query, $displayParts, + false, 0, $num_rows, true, $result, + $analyzed_sql_results, true + ); + + $html_output .= $displayResultsObject->getCreateViewQueryResultOp( + $analyzed_sql_results + ); + + $cfgBookmark = Bookmark::getParams($GLOBALS['cfg']['Server']['user']); + if ($cfgBookmark) { + $html_output .= $this->getHtmlForBookmark( + $displayParts, + $cfgBookmark, + $sql_query, $db, $table, + isset($complete_query) ? $complete_query : $sql_query, + $cfgBookmark['user'] + ); + } + } + } + + return $html_output; + } + + /** + * Function to send response for ajax grid edit + * + * @param object $result result of the executed query + * + * @return void + */ + private function sendResponseForGridEdit($result) + { + $row = $GLOBALS['dbi']->fetchRow($result); + $field_flags = $GLOBALS['dbi']->fieldFlags($result, 0); + if (stristr($field_flags, DisplayResults::BINARY_FIELD)) { + $row[0] = bin2hex($row[0]); + } + $response = Response::getInstance(); + $response->addJSON('value', $row[0]); + exit; + } + + /** + * Function to get html for the sql query results div + * + * @param string $previous_update_query_html html for the previously executed query + * @param string $profiling_chart_html html for profiling + * @param Message $missing_unique_column_msg message for the missing unique column + * @param Message $bookmark_created_msg message for bookmark creation + * @param string $table_html html for the table for displaying sql + * results + * @param string $indexes_problems_html html for displaying errors in indexes + * @param string $bookmark_support_html html for displaying bookmark form + * + * @return string $html_output + */ + private function getHtmlForSqlQueryResults($previous_update_query_html, + $profiling_chart_html, $missing_unique_column_msg, $bookmark_created_msg, + $table_html, $indexes_problems_html, $bookmark_support_html + ) { + //begin the sqlqueryresults div here. container div + $html_output = '
    '; + $html_output .= isset($previous_update_query_html) + ? $previous_update_query_html : ''; + $html_output .= isset($profiling_chart_html) ? $profiling_chart_html : ''; + $html_output .= isset($missing_unique_column_msg) + ? $missing_unique_column_msg->getDisplay() : ''; + $html_output .= isset($bookmark_created_msg) + ? $bookmark_created_msg->getDisplay() : ''; + $html_output .= $table_html; + $html_output .= isset($indexes_problems_html) ? $indexes_problems_html : ''; + $html_output .= isset($bookmark_support_html) ? $bookmark_support_html : ''; + $html_output .= '
    '; // end sqlqueryresults div + + return $html_output; + } + + /** + * Returns a message for successful creation of a bookmark or null if a bookmark + * was not created + * + * @return Message $bookmark_created_msg + */ + private function getBookmarkCreatedMessage() + { + if (isset($_GET['label'])) { + $bookmark_created_msg = Message::success( + __('Bookmark %s has been created.') + ); + $bookmark_created_msg->addParam($_GET['label']); + } else { + $bookmark_created_msg = null; + } + + return $bookmark_created_msg; + } + + /** + * Function to get html for the sql query results table + * + * @param DisplayResults $displayResultsObject instance of DisplayResult + * @param string $pmaThemeImage theme image uri + * @param string $url_query url query + * @param array $displayParts the parts to display + * @param bool $editable whether the result table is + * editable or not + * @param int $unlim_num_rows unlimited number of rows + * @param int $num_rows number of rows + * @param bool $showtable whether to show table or not + * @param object $result result of the executed query + * @param array $analyzed_sql_results analyzed sql results + * @param bool $is_limited_display Show only limited operations or not + * + * @return string + */ + private function getHtmlForSqlQueryResultsTable($displayResultsObject, + $pmaThemeImage, $url_query, array $displayParts, + $editable, $unlim_num_rows, $num_rows, $showtable, $result, + array $analyzed_sql_results, $is_limited_display = false + ) { + $printview = isset($_POST['printview']) && $_POST['printview'] == '1' ? '1' : null; + $table_html = ''; + $browse_dist = ! empty($_POST['is_browse_distinct']); + + if ($analyzed_sql_results['is_procedure']) { + + do { + if (! isset($result)) { + $result = $GLOBALS['dbi']->storeResult(); + } + $num_rows = $GLOBALS['dbi']->numRows($result); + + if ($result !== false && $num_rows > 0) { + + $fields_meta = $GLOBALS['dbi']->getFieldsMeta($result); + if (! is_array($fields_meta)) { + $fields_cnt = 0; + } else { + $fields_cnt = count($fields_meta); + } + + $displayResultsObject->setProperties( + $num_rows, + $fields_meta, + $analyzed_sql_results['is_count'], + $analyzed_sql_results['is_export'], + $analyzed_sql_results['is_func'], + $analyzed_sql_results['is_analyse'], + $num_rows, + $fields_cnt, + $GLOBALS['querytime'], + $pmaThemeImage, + $GLOBALS['text_dir'], + $analyzed_sql_results['is_maint'], + $analyzed_sql_results['is_explain'], + $analyzed_sql_results['is_show'], + $showtable, + $printview, + $url_query, + $editable, + $browse_dist + ); + + $displayParts = array( + 'edit_lnk' => $displayResultsObject::NO_EDIT_OR_DELETE, + 'del_lnk' => $displayResultsObject::NO_EDIT_OR_DELETE, + 'sort_lnk' => '1', + 'nav_bar' => '1', + 'bkm_form' => '1', + 'text_btn' => '1', + 'pview_lnk' => '1' + ); + + $table_html .= $displayResultsObject->getTable( + $result, + $displayParts, + $analyzed_sql_results, + $is_limited_display + ); + } + + $GLOBALS['dbi']->freeResult($result); + unset($result); + + } while ($GLOBALS['dbi']->moreResults() && $GLOBALS['dbi']->nextResult()); + + } else { + $fields_meta = array(); + if (isset($result) && ! is_bool($result)) { + $fields_meta = $GLOBALS['dbi']->getFieldsMeta($result); + } + $fields_cnt = count($fields_meta); + $_SESSION['is_multi_query'] = false; + $displayResultsObject->setProperties( + $unlim_num_rows, + $fields_meta, + $analyzed_sql_results['is_count'], + $analyzed_sql_results['is_export'], + $analyzed_sql_results['is_func'], + $analyzed_sql_results['is_analyse'], + $num_rows, + $fields_cnt, $GLOBALS['querytime'], + $pmaThemeImage, $GLOBALS['text_dir'], + $analyzed_sql_results['is_maint'], + $analyzed_sql_results['is_explain'], + $analyzed_sql_results['is_show'], + $showtable, + $printview, + $url_query, + $editable, + $browse_dist + ); + + if (! is_bool($result)) { + $table_html .= $displayResultsObject->getTable( + $result, + $displayParts, + $analyzed_sql_results, + $is_limited_display + ); + } + $GLOBALS['dbi']->freeResult($result); + } + + return $table_html; + } + + /** + * Function to get html for the previous query if there is such. If not will return + * null + * + * @param string $disp_query display query + * @param bool $showSql whether to show sql + * @param array $sql_data sql data + * @param string $disp_message display message + * + * @return string $previous_update_query_html + */ + private function getHtmlForPreviousUpdateQuery($disp_query, $showSql, $sql_data, + $disp_message + ) { + // previous update query (from tbl_replace) + if (isset($disp_query) && ($showSql == true) && empty($sql_data)) { + $previous_update_query_html = Util::getMessage( + $disp_message, $disp_query, 'success' + ); + } else { + $previous_update_query_html = null; + } + + return $previous_update_query_html; + } + + /** + * To get the message if a column index is missing. If not will return null + * + * @param string $table current table + * @param string $db current database + * @param boolean $editable whether the results table can be editable or not + * @param boolean $has_unique whether there is a unique key + * + * @return Message $message + */ + private function getMessageIfMissingColumnIndex($table, $db, $editable, $has_unique) + { + if (!empty($table) && ($GLOBALS['dbi']->isSystemSchema($db) || !$editable)) { + $missing_unique_column_msg = Message::notice( + sprintf( + __( + 'Current selection does not contain a unique column.' + . ' Grid edit, checkbox, Edit, Copy and Delete features' + . ' are not available. %s' + ), + Util::showDocu( + 'config', + 'cfg_RowActionLinksWithoutUnique' + ) + ) + ); + } elseif (! empty($table) && ! $has_unique) { + $missing_unique_column_msg = Message::notice( + sprintf( + __( + 'Current selection does not contain a unique column.' + . ' Grid edit, Edit, Copy and Delete features may result in' + . ' undesired behavior. %s' + ), + Util::showDocu( + 'config', + 'cfg_RowActionLinksWithoutUnique' + ) + ) + ); + } else { + $missing_unique_column_msg = null; + } + + return $missing_unique_column_msg; + } + + /** + * Function to get html to display problems in indexes + * + * @param string $query_type query type + * @param array|null $selectedTables array of table names selected from the + * database structure page, for an action + * like check table, optimize table, + * analyze table or repair table + * @param string $db current database + * + * @return string + */ + private function getHtmlForIndexesProblems($query_type, $selectedTables, $db) + { + // BEGIN INDEX CHECK See if indexes should be checked. + if (isset($query_type) + && $query_type == 'check_tbl' + && isset($selectedTables) + && is_array($selectedTables) + ) { + $indexes_problems_html = ''; + foreach ($selectedTables as $tbl_name) { + $check = Index::findDuplicates($tbl_name, $db); + if (! empty($check)) { + $indexes_problems_html .= sprintf( + __('Problems with indexes of table `%s`'), $tbl_name + ); + $indexes_problems_html .= $check; + } + } + } else { + $indexes_problems_html = null; + } + + return $indexes_problems_html; + } + + /** + * Function to display results when the executed query returns non empty results + * + * @param object $result executed query results + * @param array $analyzed_sql_results analysed sql results + * @param string $db current database + * @param string $table current table + * @param string $message message to show + * @param array $sql_data sql data + * @param DisplayResults $displayResultsObject Instance of DisplayResults + * @param string $pmaThemeImage uri of the theme image + * @param int $unlim_num_rows unlimited number of rows + * @param int $num_rows number of rows + * @param string $disp_query display query + * @param string $disp_message display message + * @param array $profiling_results profiling results + * @param string $query_type query type + * @param array|null $selectedTables array of table names selected + * from the database structure page, for + * an action like check table, + * optimize table, analyze table or + * repair table + * @param string $sql_query sql query + * @param string $complete_query complete sql query + * + * @return string html + */ + private function getQueryResponseForResultsReturned($result, array $analyzed_sql_results, + $db, $table, $message, $sql_data, $displayResultsObject, $pmaThemeImage, + $unlim_num_rows, $num_rows, $disp_query, $disp_message, $profiling_results, + $query_type, $selectedTables, $sql_query, $complete_query + ) { + // If we are retrieving the full value of a truncated field or the original + // value of a transformed field, show it here + if (isset($_POST['grid_edit']) && $_POST['grid_edit'] == true) { + $this->sendResponseForGridEdit($result); + // script has exited at this point + } + + // Gets the list of fields properties + if (isset($result) && $result) { + $fields_meta = $GLOBALS['dbi']->getFieldsMeta($result); + } + + // Should be initialized these parameters before parsing + $showtable = isset($showtable) ? $showtable : null; + $url_query = isset($url_query) ? $url_query : null; + + $response = Response::getInstance(); + $header = $response->getHeader(); + $scripts = $header->getScripts(); + + $just_one_table = $this->resultSetHasJustOneTable($fields_meta); + + // hide edit and delete links: + // - for information_schema + // - if the result set does not contain all the columns of a unique key + // (unless this is an updatable view) + // - if the SELECT query contains a join or a subquery + + $updatableView = false; + + $statement = isset($analyzed_sql_results['statement']) ? $analyzed_sql_results['statement'] : null; + if ($statement instanceof SelectStatement) { + if (!empty($statement->expr)) { + if ($statement->expr[0]->expr === '*') { + $_table = new Table($table, $db); + $updatableView = $_table->isUpdatableView(); + } + } + + if ($analyzed_sql_results['join'] + || $analyzed_sql_results['is_subquery'] + || count($analyzed_sql_results['select_tables']) !== 1 + ) { + $just_one_table = false; + } + } + + $has_unique = $this->resultSetContainsUniqueKey( + $db, $table, $fields_meta + ); + + $editable = ($has_unique + || $GLOBALS['cfg']['RowActionLinksWithoutUnique'] + || $updatableView) + && $just_one_table; + + $_SESSION['tmpval']['possible_as_geometry'] = $editable; + + $displayParts = array( + 'edit_lnk' => $displayResultsObject::UPDATE_ROW, + 'del_lnk' => $displayResultsObject::DELETE_ROW, + 'sort_lnk' => '1', + 'nav_bar' => '1', + 'bkm_form' => '1', + 'text_btn' => '0', + 'pview_lnk' => '1' + ); + + if ($GLOBALS['dbi']->isSystemSchema($db) || !$editable) { + $displayParts = array( + 'edit_lnk' => $displayResultsObject::NO_EDIT_OR_DELETE, + 'del_lnk' => $displayResultsObject::NO_EDIT_OR_DELETE, + 'sort_lnk' => '1', + 'nav_bar' => '1', + 'bkm_form' => '1', + 'text_btn' => '1', + 'pview_lnk' => '1' + ); + + } + if (isset($_POST['printview']) && $_POST['printview'] == '1') { + $displayParts = array( + 'edit_lnk' => $displayResultsObject::NO_EDIT_OR_DELETE, + 'del_lnk' => $displayResultsObject::NO_EDIT_OR_DELETE, + 'sort_lnk' => '0', + 'nav_bar' => '0', + 'bkm_form' => '0', + 'text_btn' => '0', + 'pview_lnk' => '0' + ); + } + + if (isset($_POST['table_maintenance'])) { + $scripts->addFile('makegrid.js'); + $scripts->addFile('sql.js'); + $table_maintenance_html = ''; + if (isset($message)) { + $message = Message::success($message); + $table_maintenance_html = Util::getMessage( + $message, $GLOBALS['sql_query'], 'success' + ); + } + $table_maintenance_html .= $this->getHtmlForSqlQueryResultsTable( + $displayResultsObject, + $pmaThemeImage, $url_query, $displayParts, + false, $unlim_num_rows, $num_rows, $showtable, $result, + $analyzed_sql_results + ); + if (empty($sql_data) || ($sql_data['valid_queries'] = 1)) { + $response->addHTML($table_maintenance_html); + exit(); + } + } + + if (!isset($_POST['printview']) || $_POST['printview'] != '1') { + $scripts->addFile('makegrid.js'); + $scripts->addFile('sql.js'); + unset($GLOBALS['message']); + //we don't need to buffer the output in getMessage here. + //set a global variable and check against it in the function + $GLOBALS['buffer_message'] = false; + } + + $previous_update_query_html = $this->getHtmlForPreviousUpdateQuery( + isset($disp_query) ? $disp_query : null, + $GLOBALS['cfg']['ShowSQL'], isset($sql_data) ? $sql_data : null, + isset($disp_message) ? $disp_message : null + ); + + $profiling_chart_html = $this->getHtmlForProfilingChart( + $url_query, $db, isset($profiling_results) ? $profiling_results :array() + ); + + $missing_unique_column_msg = $this->getMessageIfMissingColumnIndex( + $table, $db, $editable, $has_unique + ); + + $bookmark_created_msg = $this->getBookmarkCreatedMessage(); + + $table_html = $this->getHtmlForSqlQueryResultsTable( + $displayResultsObject, + $pmaThemeImage, $url_query, $displayParts, + $editable, $unlim_num_rows, $num_rows, $showtable, $result, + $analyzed_sql_results + ); + + $indexes_problems_html = $this->getHtmlForIndexesProblems( + isset($query_type) ? $query_type : null, + isset($selectedTables) ? $selectedTables : null, $db + ); + + $cfgBookmark = Bookmark::getParams($GLOBALS['cfg']['Server']['user']); + if ($cfgBookmark) { + $bookmark_support_html = $this->getHtmlForBookmark( + $displayParts, + $cfgBookmark, + $sql_query, $db, $table, + isset($complete_query) ? $complete_query : $sql_query, + $cfgBookmark['user'] + ); + } else { + $bookmark_support_html = ''; + } + + $html_output = isset($table_maintenance_html) ? $table_maintenance_html : ''; + + $html_output .= $this->getHtmlForSqlQueryResults( + $previous_update_query_html, $profiling_chart_html, + $missing_unique_column_msg, $bookmark_created_msg, + $table_html, $indexes_problems_html, $bookmark_support_html + ); + + return $html_output; + } + + /** + * Function to execute the query and send the response + * + * @param array $analyzed_sql_results analysed sql results + * @param bool $is_gotofile whether goto file or not + * @param string $db current database + * @param string $table current table + * @param bool|null $find_real_end whether to find real end or not + * @param string $sql_query_for_bookmark the sql query to be stored as bookmark + * @param array|null $extra_data extra data + * @param string $message_to_show message to show + * @param string $message message + * @param array|null $sql_data sql data + * @param string $goto goto page url + * @param string $pmaThemeImage uri of the PMA theme image + * @param string $disp_query display query + * @param string $disp_message display message + * @param string $query_type query type + * @param string $sql_query sql query + * @param array|null $selectedTables array of table names selected from the + * database structure page, for an action + * like check table, optimize table, + * analyze table or repair table + * @param string $complete_query complete query + * + * @return void + */ + public function executeQueryAndSendQueryResponse($analyzed_sql_results, + $is_gotofile, $db, $table, $find_real_end, $sql_query_for_bookmark, + $extra_data, $message_to_show, $message, $sql_data, $goto, $pmaThemeImage, + $disp_query, $disp_message, $query_type, $sql_query, $selectedTables, + $complete_query + ) { + if ($analyzed_sql_results == null) { + // Parse and analyze the query + list( + $analyzed_sql_results, + $db, + $table_from_sql + ) = ParseAnalyze::sqlQuery($sql_query, $db); + // @todo: possibly refactor + extract($analyzed_sql_results); + + if ($table != $table_from_sql && !empty($table_from_sql)) { + $table = $table_from_sql; + } + } + + $html_output = $this->executeQueryAndGetQueryResponse( + $analyzed_sql_results, // analyzed_sql_results + $is_gotofile, // is_gotofile + $db, // db + $table, // table + $find_real_end, // find_real_end + $sql_query_for_bookmark, // sql_query_for_bookmark + $extra_data, // extra_data + $message_to_show, // message_to_show + $message, // message + $sql_data, // sql_data + $goto, // goto + $pmaThemeImage, // pmaThemeImage + $disp_query, // disp_query + $disp_message, // disp_message + $query_type, // query_type + $sql_query, // sql_query + $selectedTables, // selectedTables + $complete_query // complete_query + ); + + $response = Response::getInstance(); + $response->addHTML($html_output); + } + + /** + * Function to execute the query and send the response + * + * @param array $analyzed_sql_results analysed sql results + * @param bool $is_gotofile whether goto file or not + * @param string $db current database + * @param string $table current table + * @param bool|null $find_real_end whether to find real end or not + * @param string $sql_query_for_bookmark the sql query to be stored as bookmark + * @param array|null $extra_data extra data + * @param string $message_to_show message to show + * @param string $message message + * @param array|null $sql_data sql data + * @param string $goto goto page url + * @param string $pmaThemeImage uri of the PMA theme image + * @param string $disp_query display query + * @param string $disp_message display message + * @param string $query_type query type + * @param string $sql_query sql query + * @param array|null $selectedTables array of table names selected from the + * database structure page, for an action + * like check table, optimize table, + * analyze table or repair table + * @param string $complete_query complete query + * + * @return string html + */ + public function executeQueryAndGetQueryResponse(array $analyzed_sql_results, + $is_gotofile, $db, $table, $find_real_end, $sql_query_for_bookmark, + $extra_data, $message_to_show, $message, $sql_data, $goto, $pmaThemeImage, + $disp_query, $disp_message, $query_type, $sql_query, $selectedTables, + $complete_query + ) { + // Handle disable/enable foreign key checks + $default_fk_check = Util::handleDisableFKCheckInit(); + + // Handle remembered sorting order, only for single table query. + // Handling is not required when it's a union query + // (the parser never sets the 'union' key to 0). + // Handling is also not required if we came from the "Sort by key" + // drop-down. + if (! empty($analyzed_sql_results) + && $this->isRememberSortingOrder($analyzed_sql_results) + && empty($analyzed_sql_results['union']) + && ! isset($_POST['sort_by_key']) + ) { + if (! isset($_SESSION['sql_from_query_box'])) { + $this->handleSortOrder($db, $table, $analyzed_sql_results, $sql_query); + } else { + unset($_SESSION['sql_from_query_box']); + } + + } + + $displayResultsObject = new DisplayResults( + $GLOBALS['db'], $GLOBALS['table'], $goto, $sql_query + ); + $displayResultsObject->setConfigParamsForDisplayTable(); + + // assign default full_sql_query + $full_sql_query = $sql_query; + + // Do append a "LIMIT" clause? + if ($this->isAppendLimitClause($analyzed_sql_results)) { + $full_sql_query = $this->getSqlWithLimitClause($analyzed_sql_results); + } + + $GLOBALS['reload'] = $this->hasCurrentDbChanged($db); + $GLOBALS['dbi']->selectDb($db); + + // Execute the query + list($result, $num_rows, $unlim_num_rows, $profiling_results, $extra_data) + = $this->executeTheQuery( + $analyzed_sql_results, + $full_sql_query, + $is_gotofile, + $db, + $table, + isset($find_real_end) ? $find_real_end : null, + isset($sql_query_for_bookmark) ? $sql_query_for_bookmark : null, + isset($extra_data) ? $extra_data : null + ); + + if ($GLOBALS['dbi']->moreResults()) { + $GLOBALS['dbi']->nextResult(); + } + + $operations = new Operations(); + $warning_messages = $operations->getWarningMessagesArray(); + + // No rows returned -> move back to the calling page + if ((0 == $num_rows && 0 == $unlim_num_rows) + || $analyzed_sql_results['is_affected'] + ) { + $html_output = $this->getQueryResponseForNoResultsReturned( + $analyzed_sql_results, $db, $table, + isset($message_to_show) ? $message_to_show : null, + $num_rows, $displayResultsObject, $extra_data, + $pmaThemeImage, isset($result) ? $result : null, + $sql_query, isset($complete_query) ? $complete_query : null + ); + } else { + // At least one row is returned -> displays a table with results + $html_output = $this->getQueryResponseForResultsReturned( + isset($result) ? $result : null, + $analyzed_sql_results, + $db, + $table, + isset($message) ? $message : null, + isset($sql_data) ? $sql_data : null, + $displayResultsObject, + $pmaThemeImage, + $unlim_num_rows, + $num_rows, + isset($disp_query) ? $disp_query : null, + isset($disp_message) ? $disp_message : null, + $profiling_results, + isset($query_type) ? $query_type : null, + isset($selectedTables) ? $selectedTables : null, + $sql_query, + isset($complete_query) ? $complete_query : null + ); + } + + // Handle disable/enable foreign key checks + Util::handleDisableFKCheckCleanup($default_fk_check); + + foreach ($warning_messages as $warning) { + $message = Message::notice(Message::sanitize($warning)); + $html_output .= $message->getDisplay(); + } + + return $html_output; + } + + /** + * Function to define pos to display a row + * + * @param int $number_of_line Number of the line to display + * @param int $max_rows Number of rows by page + * + * @return int Start position to display the line + */ + private function getStartPosToDisplayRow($number_of_line, $max_rows = null) + { + if (null === $max_rows) { + $max_rows = $_SESSION['tmpval']['max_rows']; + } + + return @((ceil($number_of_line / $max_rows) - 1) * $max_rows); + } + + /** + * Function to calculate new pos if pos is higher than number of rows + * of displayed table + * + * @param string $db Database name + * @param string $table Table name + * @param int|null $pos Initial position + * + * @return int Number of pos to display last page + */ + public function calculatePosForLastPage($db, $table, $pos) + { + if (null === $pos) { + $pos = $_SESSION['tmpval']['pos']; + } + + $_table = new Table($table, $db); + $unlim_num_rows = $_table->countRecords(true); + //If position is higher than number of rows + if ($unlim_num_rows <= $pos && 0 != $pos) { + $pos = $this->getStartPosToDisplayRow($unlim_num_rows); + } + + return $pos; + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/SqlQueryForm.php b/php/apps/phpmyadmin49/html/libraries/classes/SqlQueryForm.php new file mode 100644 index 00000000..497d4794 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/SqlQueryForm.php @@ -0,0 +1,446 @@ +' . "\n"; + + $html .= '' + . "\n" . Url::getHiddenInputs($db, $table) . "\n" + . '' . "\n" + . '' . "\n" + . '' + . "\n" . '' . "\n"; + + // display querybox + if ($display_tab === 'full' || $display_tab === 'sql') { + $html .= self::getHtmlForInsert( + $query, $delimiter + ); + } + + // Bookmark Support + if ($display_tab === 'full') { + $cfgBookmark = Bookmark::getParams($GLOBALS['cfg']['Server']['user']); + if ($cfgBookmark) { + $html .= self::getHtmlForBookmark(); + } + } + + // Japanese encoding setting + if (Encoding::canConvertKanji()) { + $html .= Encoding::kanjiEncodingForm(); + } + + $html .= '' . "\n"; + // print an empty div, which will be later filled with + // the sql query results by ajax + $html .= '
    '; + + return $html; + } + + /** + * Get initial values for Sql Query Form Insert + * + * @param string $query query to display in the textarea + * + * @return array ($legend, $query, $columns_list) + * + * @usedby self::getHtmlForInsert() + */ + public static function init($query) + { + $columns_list = array(); + if (strlen($GLOBALS['db']) === 0) { + // prepare for server related + $legend = sprintf( + __('Run SQL query/queries on server “%s”'), + htmlspecialchars( + ! empty($GLOBALS['cfg']['Servers'][$GLOBALS['server']]['verbose']) + ? $GLOBALS['cfg']['Servers'][$GLOBALS['server']]['verbose'] + : $GLOBALS['cfg']['Servers'][$GLOBALS['server']]['host'] + ) + ); + } elseif (strlen($GLOBALS['table']) === 0) { + // prepare for db related + $db = $GLOBALS['db']; + // if you want navigation: + $tmp_db_link = ''; + $legend = sprintf(__('Run SQL query/queries on database %s'), $tmp_db_link); + if (empty($query)) { + $query = Util::expandUserString( + $GLOBALS['cfg']['DefaultQueryDatabase'], 'backquote' + ); + } + } else { + $db = $GLOBALS['db']; + $table = $GLOBALS['table']; + // Get the list and number of fields + // we do a try_query here, because we could be in the query window, + // trying to synchronize and the table has not yet been created + $columns_list = $GLOBALS['dbi']->getColumns( + $db, $GLOBALS['table'], null, true + ); + + $tmp_tbl_link = ''; + $tmp_tbl_link .= htmlspecialchars($db) + . '.' . htmlspecialchars($table) . ''; + $legend = sprintf(__('Run SQL query/queries on table %s'), $tmp_tbl_link); + if (empty($query)) { + $query = Util::expandUserString( + $GLOBALS['cfg']['DefaultQueryTable'], 'backquote' + ); + } + } + $legend .= ': ' . Util::showMySQLDocu('SELECT'); + + return array($legend, $query, $columns_list); + } + + /** + * return HTML for Sql Query Form Insert + * + * @param string $query query to display in the textarea + * @param string $delimiter default delimiter to use + * + * @return string + * + * @usedby self::getHtml() + */ + public static function getHtmlForInsert( + $query = '', $delimiter = ';' + ) { + // enable auto select text in textarea + if ($GLOBALS['cfg']['TextareaAutoSelect']) { + $auto_sel = ' onclick="selectContent(this, sql_box_locked, true);"'; + } else { + $auto_sel = ''; + } + + $locking = ''; + $height = $GLOBALS['cfg']['TextareaRows'] * 2; + + list($legend, $query, $columns_list) = self::init($query); + + if (! empty($columns_list)) { + $sqlquerycontainer_id = 'sqlquerycontainer'; + } else { + $sqlquerycontainer_id = 'sqlquerycontainerfull'; + } + + $html = '' + . '
    ' + . '
    '; + $html .= '' . $legend . ''; + $html .= '
    '; + $html .= '
    ' + . ''; + $html .= '
    '; + // Add buttons to generate query easily for + // select all, single select, insert, update and delete + if (! empty($columns_list)) { + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + } + $html .= ''; + if ($GLOBALS['cfg']['CodemirrorEnable']) { + $html .= ''; + } + $html .= ''; + + // parameter binding + $html .= '
    '; + $html .= ''; + $html .= ''; + $html .= Util::showDocu('faq', 'faq6-40'); + $html .= '
    '; + $html .= '
    '; + + $html .= '
    ' . "\n"; + + if (! empty($columns_list)) { + $html .= '
    ' + . '' + . '' + . '
    '; + if (Util::showIcons('ActionLinksMode')) { + $html .= ''; + } else { + $html .= ''; + } + $html .= '
    ' . "\n" + . '
    ' . "\n"; + } + + $html .= '
    ' . "\n"; + $html .= '
    ' . "\n"; + + $cfgBookmark = Bookmark::getParams($GLOBALS['cfg']['Server']['user']); + if ($cfgBookmark) { + $html .= '
    '; + $html .= '
    '; + $html .= ''; + $html .= ''; + $html .= '
    '; + $html .= '
    '; + $html .= ''; + $html .= ''; + $html .= '
    '; + $html .= '
    '; + $html .= ''; + $html .= ''; + $html .= '
    '; + $html .= '
    '; + } + + $html .= '
    ' . "\n"; + $html .= '
    ' . "\n" + . '
    ' . "\n"; + + $html .= '
    ' . "\n"; + $html .= '
    ' . "\n"; + $html .= '
    ' . "\n"; + + $html .= '
    '; + $html .= '' . "\n"; + $html .= ' ]'; + $html .= '
    '; + + $html .= '
    '; + $html .= '' + . ''; + $html .= '
    '; + + $html .= '
    '; + $html .= '' + . ''; + $html .= '
    '; + + $html .= '
    '; + $html .= '' + . ''; + $html .= '
    '; + + // Disable/Enable foreign key checks + $html .= '
    '; + $html .= Util::getFKCheckbox(); + $html .= '
    '; + + $html .= '' . "\n"; + $html .= '
    ' . "\n"; + $html .= '
    ' . "\n"; + + return $html; + } + + /** + * return HTML for sql Query Form Bookmark + * + * @return string|null + * + * @usedby self::getHtml() + */ + public static function getHtmlForBookmark() + { + $bookmark_list = Bookmark::getList( + $GLOBALS['dbi'], + $GLOBALS['cfg']['Server']['user'], + $GLOBALS['db'] + ); + if (empty($bookmark_list) || count($bookmark_list) < 1) { + return null; + } + + $html = '
    '; + $html .= ''; + $html .= __('Bookmarked SQL query') . '' . "\n"; + $html .= '
    '; + $html .= ' ' . "\n"; + $html .= '
    ' . "\n"; + $html .= '
    ' . "\n"; + $html .= '' + . '' . "\n"; + $html .= '' + . '' . "\n"; + $html .= '' + . '' . "\n"; + $html .= '
    ' . "\n"; + $html .= '
    ' . "\n"; + $html .= '
    ' . "\n"; + $html .= __('Variables'); + $html .= Util::showDocu('faq', 'faqbookmark'); + $html .= '
    '; + $html .= '
    ' . "\n"; + $html .= '
    ' . "\n"; + + $html .= '
    '; + $html .= ''; + $html .= '
    ' . "\n"; + $html .= '
    ' . "\n"; + + return $html; + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/StorageEngine.php b/php/apps/phpmyadmin49/html/libraries/classes/StorageEngine.php new file mode 100644 index 00000000..c0e8662f --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/StorageEngine.php @@ -0,0 +1,461 @@ +engine = $engine; + $this->title = $storage_engines[$engine]['Engine']; + $this->comment = (isset($storage_engines[$engine]['Comment']) + ? $storage_engines[$engine]['Comment'] + : ''); + switch ($storage_engines[$engine]['Support']) { + case 'DEFAULT': + $this->support = PMA_ENGINE_SUPPORT_DEFAULT; + break; + case 'YES': + $this->support = PMA_ENGINE_SUPPORT_YES; + break; + case 'DISABLED': + $this->support = PMA_ENGINE_SUPPORT_DISABLED; + break; + case 'NO': + default: + $this->support = PMA_ENGINE_SUPPORT_NO; + } + } + } + + /** + * Returns array of storage engines + * + * @static + * @staticvar array $storage_engines storage engines + * @access public + * @return string[] array of storage engines + */ + static public function getStorageEngines() + { + static $storage_engines = null; + + if (null == $storage_engines) { + $storage_engines + = $GLOBALS['dbi']->fetchResult('SHOW STORAGE ENGINES', 'Engine'); + if ($GLOBALS['dbi']->getVersion() >= 50708) { + $disabled = Util::cacheGet( + 'disabled_storage_engines', + function () { + return $GLOBALS['dbi']->fetchValue( + 'SELECT @@disabled_storage_engines' + ); + } + ); + foreach (explode(",", $disabled) as $engine) { + if (isset($storage_engines[$engine])) { + $storage_engines[$engine]['Support'] = 'DISABLED'; + } + } + } + } + + return $storage_engines; + } + + /** + * Returns HTML code for storage engine select box + * + * @param string $name The name of the select form element + * @param string $id The ID of the form field + * @param string $selected The selected engine + * @param boolean $offerUnavailableEngines Should unavailable storage + * engines be offered? + * @param boolean $addEmpty Whether to provide empty option + * + * @static + * @return string html selectbox + */ + static public function getHtmlSelect( + $name = 'engine', $id = null, + $selected = null, $offerUnavailableEngines = false, + $addEmpty = false + ) { + $selected = mb_strtolower($selected); + $output = '' . "\n"; + return $output; + } + + /** + * Loads the corresponding engine plugin, if available. + * + * @param string $engine The engine ID + * + * @return StorageEngine The engine plugin + * @static + */ + static public function getEngine($engine) + { + switch(mb_strtolower($engine)) { + case 'bdb': + return new Bdb($engine); + case 'berkeleydb': + return new Berkeleydb($engine); + case 'binlog': + return new Binlog($engine); + case 'innobase': + return new Innobase($engine); + case 'innodb': + return new Innodb($engine); + case 'memory': + return new Memory($engine); + case 'merge': + return new Merge($engine); + case 'mrg_myisam': + return new MrgMyisam($engine); + case 'myisam': + return new Myisam($engine); + case 'ndbcluster': + return new Ndbcluster($engine); + case 'pbxt': + return new Pbxt($engine); + case 'performance_schema': + return new PerformanceSchema($engine); + default: + return new StorageEngine($engine); + } + } + + /** + * Returns true if given engine name is supported/valid, otherwise false + * + * @param string $engine name of engine + * + * @static + * @return boolean whether $engine is valid or not + */ + static public function isValid($engine) + { + if ($engine == "PBMS") { + return true; + } + $storage_engines = self::getStorageEngines(); + return isset($storage_engines[$engine]); + } + + /** + * Returns as HTML table of the engine's server variables + * + * @return string The table that was generated based on the retrieved + * information + */ + public function getHtmlVariables() + { + $ret = ''; + + foreach ($this->getVariablesStatus() as $details) { + $ret .= '' . "\n" + . ' ' . "\n"; + if (! empty($details['desc'])) { + $ret .= ' ' + . Util::showHint($details['desc']) + . "\n"; + } + $ret .= ' ' . "\n" + . ' ' . htmlspecialchars($details['title']) . '' + . "\n" + . ' '; + switch ($details['type']) { + case PMA_ENGINE_DETAILS_TYPE_SIZE: + $parsed_size = $this->resolveTypeSize($details['value']); + $ret .= $parsed_size[0] . ' ' . $parsed_size[1]; + unset($parsed_size); + break; + case PMA_ENGINE_DETAILS_TYPE_NUMERIC: + $ret .= Util::formatNumber($details['value']) . ' '; + break; + default: + $ret .= htmlspecialchars($details['value']) . ' '; + } + $ret .= '' . "\n" + . '' . "\n"; + } + + if (! $ret) { + $ret = '

    ' . "\n" + . ' ' + . __( + 'There is no detailed status information available for this ' + . 'storage engine.' + ) + . "\n" + . '

    ' . "\n"; + } else { + $ret = '' . "\n" . $ret . '
    ' . "\n"; + } + + return $ret; + } + + /** + * Returns the engine specific handling for + * PMA_ENGINE_DETAILS_TYPE_SIZE type variables. + * + * This function should be overridden when + * PMA_ENGINE_DETAILS_TYPE_SIZE type needs to be + * handled differently for a particular engine. + * + * @param integer $value Value to format + * + * @return string the formatted value and its unit + */ + public function resolveTypeSize($value) + { + return Util::formatByteDown($value); + } + + /** + * Returns array with detailed info about engine specific server variables + * + * @return array array with detailed info about specific engine server variables + */ + public function getVariablesStatus() + { + $variables = $this->getVariables(); + $like = $this->getVariablesLikePattern(); + + if ($like) { + $like = " LIKE '" . $like . "' "; + } else { + $like = ''; + } + + $mysql_vars = array(); + + $sql_query = 'SHOW GLOBAL VARIABLES ' . $like . ';'; + $res = $GLOBALS['dbi']->query($sql_query); + while ($row = $GLOBALS['dbi']->fetchAssoc($res)) { + if (isset($variables[$row['Variable_name']])) { + $mysql_vars[$row['Variable_name']] + = $variables[$row['Variable_name']]; + } elseif (! $like + && mb_strpos(mb_strtolower($row['Variable_name']), mb_strtolower($this->engine)) !== 0 + ) { + continue; + } + $mysql_vars[$row['Variable_name']]['value'] = $row['Value']; + + if (empty($mysql_vars[$row['Variable_name']]['title'])) { + $mysql_vars[$row['Variable_name']]['title'] = $row['Variable_name']; + } + + if (! isset($mysql_vars[$row['Variable_name']]['type'])) { + $mysql_vars[$row['Variable_name']]['type'] + = PMA_ENGINE_DETAILS_TYPE_PLAINTEXT; + } + } + $GLOBALS['dbi']->freeResult($res); + + return $mysql_vars; + } + + /** + * Reveals the engine's title + * + * @return string The title + */ + public function getTitle() + { + return $this->title; + } + + /** + * Fetches the server's comment about this engine + * + * @return string The comment + */ + public function getComment() + { + return $this->comment; + } + + /** + * Information message on whether this storage engine is supported + * + * @return string The localized message. + */ + public function getSupportInformationMessage() + { + switch ($this->support) { + case PMA_ENGINE_SUPPORT_DEFAULT: + $message = __('%s is the default storage engine on this MySQL server.'); + break; + case PMA_ENGINE_SUPPORT_YES: + $message = __('%s is available on this MySQL server.'); + break; + case PMA_ENGINE_SUPPORT_DISABLED: + $message = __('%s has been disabled for this MySQL server.'); + break; + case PMA_ENGINE_SUPPORT_NO: + default: + $message = __( + 'This MySQL server does not support the %s storage engine.' + ); + } + return sprintf($message, htmlspecialchars($this->title)); + } + + /** + * Generates a list of MySQL variables that provide information about this + * engine. This function should be overridden when extending this class + * for a particular engine. + * + * @return array The list of variables. + */ + public function getVariables() + { + return array(); + } + + /** + * Returns string with filename for the MySQL helppage + * about this storage engine + * + * @return string MySQL help page filename + */ + public function getMysqlHelpPage() + { + return $this->engine . '-storage-engine'; + } + + /** + * Returns the pattern to be used in the query for SQL variables + * related to the storage engine + * + * @return string SQL query LIKE pattern + */ + public function getVariablesLikePattern() + { + return ''; + } + + /** + * Returns a list of available information pages with labels + * + * @return string[] The list + */ + public function getInfoPages() + { + return array(); + } + + /** + * Generates the requested information page + * + * @param string $id page id + * + * @return string html output + */ + public function getPage($id) + { + if (! array_key_exists($id, $this->getInfoPages())) { + return ''; + } + + $id = 'getPage' . $id; + + return $this->$id(); + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/SubPartition.php b/php/apps/phpmyadmin49/html/libraries/classes/SubPartition.php new file mode 100644 index 00000000..d3f6be1e --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/SubPartition.php @@ -0,0 +1,180 @@ +db = $row['TABLE_SCHEMA']; + $this->table = $row['TABLE_NAME']; + $this->loadData($row); + } + + /** + * Loads data from the fetched row from information_schema.PARTITIONS + * + * @param array $row fetched row + * + * @return void + */ + protected function loadData(array $row) + { + $this->name = $row['SUBPARTITION_NAME']; + $this->ordinal = $row['SUBPARTITION_ORDINAL_POSITION']; + $this->method = $row['SUBPARTITION_METHOD']; + $this->expression = $row['SUBPARTITION_EXPRESSION']; + $this->loadCommonData($row); + } + + /** + * Loads some data that is common to both partitions and sub partitions + * + * @param array $row fetched row + * + * @return void + */ + protected function loadCommonData(array $row) + { + $this->rows = $row['TABLE_ROWS']; + $this->dataLength = $row['DATA_LENGTH']; + $this->indexLength = $row['INDEX_LENGTH']; + $this->comment = $row['PARTITION_COMMENT']; + } + + /** + * Return the partition name + * + * @return string partition name + */ + public function getName() + { + return $this->name; + } + + /** + * Return the ordinal of the partition + * + * @return number the ordinal + */ + public function getOrdinal() + { + return $this->ordinal; + } + + /** + * Returns the partition method + * + * @return string partition method + */ + public function getMethod() + { + return $this->method; + } + + /** + * Returns the partition expression + * + * @return string partition expression + */ + public function getExpression() + { + return $this->expression; + } + + /** + * Returns the number of data rows + * + * @return integer number of rows + */ + public function getRows() + { + return $this->rows; + } + + /** + * Returns the data length + * + * @return integer data length + */ + public function getDataLength() + { + return $this->dataLength; + } + + /** + * Returns the index length + * + * @return integer index length + */ + public function getIndexLength() + { + return $this->indexLength; + } + + /** + * Returns the partition comment + * + * @return string partition comment + */ + public function getComment() + { + return $this->comment; + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/SysInfo.php b/php/apps/phpmyadmin49/html/libraries/classes/SysInfo.php new file mode 100644 index 00000000..f33d9e12 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/SysInfo.php @@ -0,0 +1,64 @@ +supported()) { + return $ret; + } + } + + return new SysInfoBase(); + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/SysInfoBase.php b/php/apps/phpmyadmin49/html/libraries/classes/SysInfoBase.php new file mode 100644 index 00000000..3052b1e4 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/SysInfoBase.php @@ -0,0 +1,48 @@ + 0); + } + + /** + * Gets information about memory usage + * + * @return array with memory usage data + */ + public function memory() + { + return array(); + } + + /** + * Checks whether class is supported in this environment + * + * @return true on success + */ + public function supported() + { + return true; + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/SysInfoLinux.php b/php/apps/phpmyadmin49/html/libraries/classes/SysInfoLinux.php new file mode 100644 index 00000000..3fc4f612 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/SysInfoLinux.php @@ -0,0 +1,94 @@ + $nums[1] + $nums[2] + $nums[3], + 'idle' => intval($nums[4]), + ); + } + + /** + * Checks whether class is supported in this environment + * + * @return true on success + */ + public function supported() + { + return @is_readable('/proc/meminfo') && @is_readable('/proc/stat'); + } + + /** + * Gets information about memory usage + * + * @return array with memory usage data + */ + function memory() + { + preg_match_all( + SysInfo::MEMORY_REGEXP, + file_get_contents('/proc/meminfo'), + $matches + ); + + $mem = array_combine($matches[1], $matches[2]); + + $defaults = array( + 'MemTotal' => 0, + 'MemFree' => 0, + 'Cached' => 0, + 'Buffers' => 0, + 'SwapTotal' => 0, + 'SwapFree' => 0, + 'SwapCached' => 0, + ); + + $mem = array_merge($defaults, $mem); + + foreach ($mem as $idx => $value) { + $mem[$idx] = intval($value); + } + + $mem['MemUsed'] = $mem['MemTotal'] + - $mem['MemFree'] - $mem['Cached'] - $mem['Buffers']; + + $mem['SwapUsed'] = $mem['SwapTotal'] + - $mem['SwapFree'] - $mem['SwapCached']; + + return $mem; + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/SysInfoSunOS.php b/php/apps/phpmyadmin49/html/libraries/classes/SysInfoSunOS.php new file mode 100644 index 00000000..99390e85 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/SysInfoSunOS.php @@ -0,0 +1,82 @@ +_kstat('unix:0:system_misc:avenrun_1min'); + + return array('loadavg' => $load1); + } + + /** + * Checks whether class is supported in this environment + * + * @return true on success + */ + public function supported() + { + return @is_readable('/proc/meminfo'); + } + + /** + * Gets information about memory usage + * + * @return array with memory usage data + */ + public function memory() + { + $pagesize = $this->_kstat('unix:0:seg_cache:slab_size'); + $mem = array(); + $mem['MemTotal'] + = $this->_kstat('unix:0:system_pages:pagestotal') * $pagesize; + $mem['MemUsed'] + = $this->_kstat('unix:0:system_pages:pageslocked') * $pagesize; + $mem['MemFree'] + = $this->_kstat('unix:0:system_pages:pagesfree') * $pagesize; + $mem['SwapTotal'] = $this->_kstat('unix:0:vminfo:swap_avail') / 1024; + $mem['SwapUsed'] = $this->_kstat('unix:0:vminfo:swap_alloc') / 1024; + $mem['SwapFree'] = $this->_kstat('unix:0:vminfo:swap_free') / 1024; + + return $mem; + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/SysInfoWINNT.php b/php/apps/phpmyadmin49/html/libraries/classes/SysInfoWINNT.php new file mode 100644 index 00000000..921d933d --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/SysInfoWINNT.php @@ -0,0 +1,131 @@ +_wmi = null; + } else { + // initialize the wmi object + $objLocator = new COM('WbemScripting.SWbemLocator'); + $this->_wmi = $objLocator->ConnectServer(); + } + } + + /** + * Gets load information + * + * @return array with load data + */ + function loadavg() + { + $loadavg = ""; + $sum = 0; + $buffer = $this->_getWMI('Win32_Processor', array('LoadPercentage')); + + foreach ($buffer as $load) { + $value = $load['LoadPercentage']; + $loadavg .= $value . ' '; + $sum += $value; + } + + return array('loadavg' => $sum / count($buffer)); + } + + /** + * Checks whether class is supported in this environment + * + * @return true on success + */ + public function supported() + { + return !is_null($this->_wmi); + } + + /** + * Reads data from WMI + * + * @param string $strClass Class to read + * @param array $strValue Values to read + * + * @return array with results + */ + private function _getWMI($strClass, array $strValue = array()) + { + $arrData = array(); + + $objWEBM = $this->_wmi->Get($strClass); + $arrProp = $objWEBM->Properties_; + $arrWEBMCol = $objWEBM->Instances_(); + foreach ($arrWEBMCol as $objItem) { + $arrInstance = array(); + foreach ($arrProp as $propItem) { + $name = $propItem->Name; + if (empty($strValue) || in_array($name, $strValue)) { + $value = $objItem->$name; + if (is_string($value)) { + $arrInstance[$name] = trim($value); + } else { + $arrInstance[$name] = $value; + } + } + } + $arrData[] = $arrInstance; + } + + return $arrData; + } + + /** + * Gets information about memory usage + * + * @return array with memory usage data + */ + function memory() + { + $buffer = $this->_getWMI( + "Win32_OperatingSystem", + array('TotalVisibleMemorySize', 'FreePhysicalMemory') + ); + $mem = Array(); + $mem['MemTotal'] = $buffer[0]['TotalVisibleMemorySize']; + $mem['MemFree'] = $buffer[0]['FreePhysicalMemory']; + $mem['MemUsed'] = $mem['MemTotal'] - $mem['MemFree']; + + $buffer = $this->_getWMI('Win32_PageFileUsage'); + + $mem['SwapTotal'] = 0; + $mem['SwapUsed'] = 0; + $mem['SwapPeak'] = 0; + + foreach ($buffer as $swapdevice) { + $mem['SwapTotal'] += $swapdevice['AllocatedBaseSize'] * 1024; + $mem['SwapUsed'] += $swapdevice['CurrentUsage'] * 1024; + $mem['SwapPeak'] += $swapdevice['PeakUsage'] * 1024; + } + + return $mem; + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/SystemDatabase.php b/php/apps/phpmyadmin49/html/libraries/classes/SystemDatabase.php new file mode 100644 index 00000000..2242beda --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/SystemDatabase.php @@ -0,0 +1,133 @@ +dbi = $dbi; + $this->relation = new Relation(); + } + + /** + * Get existing data on transformations applied for + * columns in a particular table + * + * @param string $db Database name looking for + * + * @return \mysqli_result Result of executed SQL query + */ + public function getExistingTransformationData($db) + { + $cfgRelation = $this->relation->getRelationsParam(); + + // Get the existing transformation details of the same database + // from pma__column_info table + $pma_transformation_sql = sprintf( + "SELECT * FROM %s.%s WHERE `db_name` = '%s'", + Util::backquote($cfgRelation['db']), + Util::backquote($cfgRelation['column_info']), + $GLOBALS['dbi']->escapeString($db) + ); + + return $this->dbi->tryQuery($pma_transformation_sql); + } + + /** + * Get SQL query for store new transformation details of a VIEW + * + * @param object $pma_transformation_data Result set of SQL execution + * @param array $column_map Details of VIEW columns + * @param string $view_name Name of the VIEW + * @param string $db Database name of the VIEW + * + * @return string $new_transformations_sql SQL query for new transformations + */ + function getNewTransformationDataSql( + $pma_transformation_data, array $column_map, $view_name, $db + ) { + $cfgRelation = $this->relation->getRelationsParam(); + + // Need to store new transformation details for VIEW + $new_transformations_sql = sprintf( + "INSERT INTO %s.%s (" + . "`db_name`, `table_name`, `column_name`, " + . "`comment`, `mimetype`, `transformation`, " + . "`transformation_options`) VALUES", + Util::backquote($cfgRelation['db']), + Util::backquote($cfgRelation['column_info']) + ); + + $column_count = 0; + $add_comma = false; + + while ($data_row = $this->dbi->fetchAssoc($pma_transformation_data)) { + + foreach ($column_map as $column) { + + if ($data_row['table_name'] != $column['table_name'] + || $data_row['column_name'] != $column['refering_column'] + ) { + continue; + } + + $new_transformations_sql .= sprintf( + "%s ('%s', '%s', '%s', '%s', '%s', '%s', '%s')", + $add_comma ? ', ' : '', + $db, + $view_name, + isset($column['real_column']) + ? $column['real_column'] + : $column['refering_column'], + $data_row['comment'], + $data_row['mimetype'], + $data_row['transformation'], + $GLOBALS['dbi']->escapeString( + $data_row['transformation_options'] + ) + ); + + $add_comma = true; + $column_count++; + break; + } + + if ($column_count == count($column_map)) { + break; + } + } + + return ($column_count > 0) ? $new_transformations_sql : ''; + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Table.php b/php/apps/phpmyadmin49/html/libraries/classes/Table.php new file mode 100644 index 00000000..b9f7233f --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Table.php @@ -0,0 +1,2648 @@ +_dbi = $dbi; + $this->_name = $table_name; + $this->_db_name = $db_name; + $this->relation = new Relation(); + } + + /** + * returns table name + * + * @see Table::getName() + * @return string table name + */ + public function __toString() + { + return $this->getName(); + } + + /** + * Table getter + * + * @param string $table_name table name + * @param string $db_name database name + * @param DatabaseInterface $dbi database interface for the table + * + * @return Table + */ + public static function get($table_name, $db_name, DatabaseInterface $dbi = null) + { + return new Table($table_name, $db_name, $dbi); + } + + /** + * return the last error + * + * @return string the last error + */ + public function getLastError() + { + return end($this->errors); + } + + /** + * return the last message + * + * @return string the last message + */ + public function getLastMessage() + { + return end($this->messages); + } + + /** + * returns table name + * + * @param boolean $backquoted whether to quote name with backticks `` + * + * @return string table name + */ + public function getName($backquoted = false) + { + if ($backquoted) { + return Util::backquote($this->_name); + } + return $this->_name; + } + + /** + * returns database name for this table + * + * @param boolean $backquoted whether to quote name with backticks `` + * + * @return string database name for this table + */ + public function getDbName($backquoted = false) + { + if ($backquoted) { + return Util::backquote($this->_db_name); + } + return $this->_db_name; + } + + /** + * returns full name for table, including database name + * + * @param boolean $backquoted whether to quote name with backticks `` + * + * @return string + */ + public function getFullName($backquoted = false) + { + return $this->getDbName($backquoted) . '.' + . $this->getName($backquoted); + } + + + /** + * Checks the storage engine used to create table + * + * @param array|string $engine Checks the table engine against an + * array of engine strings or a single string, should be uppercase + * + * @return bool True, if $engine matches the storage engine for the table, + * False otherwise. + */ + public function isEngine($engine) + { + $tbl_storage_engine = $this->getStorageEngine(); + + if (is_array($engine)){ + foreach($engine as $e){ + if($e == $tbl_storage_engine){ + return true; + } + } + return false; + }else{ + return $tbl_storage_engine == $engine; + } + } + + /** + * returns whether the table is actually a view + * + * @return boolean whether the given is a view + */ + public function isView() + { + $db = $this->_db_name; + $table = $this->_name; + if (empty($db) || empty($table)) { + return false; + } + + // use cached data or load information with SHOW command + if ($this->_dbi->getCachedTableContent(array($db, $table)) != null + || $GLOBALS['cfg']['Server']['DisableIS'] + ) { + $type = $this->getStatusInfo('TABLE_TYPE'); + return $type == 'VIEW' || $type == 'SYSTEM VIEW'; + } + + // information_schema tables are 'SYSTEM VIEW's + if ($db == 'information_schema') { + return true; + } + + // query information_schema + $result = $this->_dbi->fetchResult( + "SELECT TABLE_NAME + FROM information_schema.VIEWS + WHERE TABLE_SCHEMA = '" . $GLOBALS['dbi']->escapeString($db) . "' + AND TABLE_NAME = '" . $GLOBALS['dbi']->escapeString($table) . "'" + ); + return $result ? true : false; + } + + /** + * Returns whether the table is actually an updatable view + * + * @return boolean whether the given is an updatable view + */ + public function isUpdatableView() + { + if (empty($this->_db_name) || empty($this->_name)) { + return false; + } + + $result = $this->_dbi->fetchResult( + "SELECT TABLE_NAME + FROM information_schema.VIEWS + WHERE TABLE_SCHEMA = '" . $GLOBALS['dbi']->escapeString($this->_db_name) . "' + AND TABLE_NAME = '" . $GLOBALS['dbi']->escapeString($this->_name) . "' + AND IS_UPDATABLE = 'YES'" + ); + return $result ? true : false; + } + + /** + * Checks if this is a merge table + * + * If the ENGINE of the table is MERGE or MRG_MYISAM (alias), + * this is a merge table. + * + * @return boolean true if it is a merge table + */ + public function isMerge() + { + return $this->isEngine(array('MERGE', 'MRG_MYISAM')); + } + + /** + * Returns full table status info, or specific if $info provided + * this info is collected from information_schema + * + * @param string $info specific information to be fetched + * @param boolean $force_read read new rather than serving from cache + * @param boolean $disable_error if true, disables error message + * + * @todo DatabaseInterface::getTablesFull needs to be merged + * somehow into this class or at least better documented + * + * @return mixed + */ + public function getStatusInfo( + $info = null, + $force_read = false, + $disable_error = false + ) { + $db = $this->_db_name; + $table = $this->_name; + + if (! empty($_SESSION['is_multi_query'])) { + $disable_error = true; + } + + // sometimes there is only one entry (ExactRows) so + // we have to get the table's details + if ($this->_dbi->getCachedTableContent(array($db, $table)) == null + || $force_read + || count($this->_dbi->getCachedTableContent(array($db, $table))) == 1 + ) { + $this->_dbi->getTablesFull($db, $table); + } + + if ($this->_dbi->getCachedTableContent(array($db, $table)) == null) { + // happens when we enter the table creation dialog + // or when we really did not get any status info, for example + // when $table == 'TABLE_NAMES' after the user tried SHOW TABLES + return ''; + } + + if (null === $info) { + return $this->_dbi->getCachedTableContent(array($db, $table)); + } + + // array_key_exists allows for null values + if (!array_key_exists( + $info, $this->_dbi->getCachedTableContent(array($db, $table)) + ) + ) { + if (! $disable_error) { + trigger_error( + __('Unknown table status:') . ' ' . $info, + E_USER_WARNING + ); + } + return false; + } + + return $this->_dbi->getCachedTableContent(array($db, $table, $info)); + } + + /** + * Returns the Table storage Engine for current table. + * + * @return string Return storage engine info if it is set for + * the selected table else return blank. + */ + public function getStorageEngine() { + $table_storage_engine = $this->getStatusInfo('ENGINE', false, true); + if ($table_storage_engine === false) { + return ''; + } + return strtoupper($table_storage_engine); + } + + /** + * Returns the comments for current table. + * + * @return string Return comment info if it is set for the selected table or return blank. + */ + public function getComment() { + $table_comment = $this->getStatusInfo('TABLE_COMMENT', false, true); + if ($table_comment === false) { + return ''; + } + return $table_comment; + } + + /** + * Returns the collation for current table. + * + * @return string Return blank if collation is empty else return the collation info from table info. + */ + public function getCollation() { + $table_collation = $this->getStatusInfo('TABLE_COLLATION', false, true); + if ($table_collation === false) { + return ''; + } + return $table_collation; + } + + /** + * Returns the info about no of rows for current table. + * + * @return integer Return no of rows info if it is not null for the selected table or return 0. + */ + public function getNumRows() { + $table_num_row_info = $this->getStatusInfo('TABLE_ROWS', false, true); + if (false === $table_num_row_info) { + $table_num_row_info = $this->_dbi->getTable($this->_db_name, $showtable['Name']) + ->countRecords(true); + } + return $table_num_row_info ? $table_num_row_info : 0 ; + } + + /** + * Returns the Row format for current table. + * + * @return string Return table row format info if it is set for the selected table or return blank. + */ + public function getRowFormat() { + $table_row_format = $this->getStatusInfo('ROW_FORMAT', false, true); + if ($table_row_format === false) { + return ''; + } + return $table_row_format; + } + + /** + * Returns the auto increment option for current table. + * + * @return integer Return auto increment info if it is set for the selected table or return blank. + */ + public function getAutoIncrement() { + $table_auto_increment = $this->getStatusInfo('AUTO_INCREMENT', false, true); + return isset($table_auto_increment) ? $table_auto_increment : ''; + } + + /** + * Returns the array for CREATE statement for current table. + * @return array Return options array info if it is set for the selected table or return blank. + */ + public function getCreateOptions() { + $table_options = $this->getStatusInfo('CREATE_OPTIONS', false, true); + $create_options_tmp = empty($table_options) ? array() : explode(' ', $table_options); + $create_options = array(); + // export create options by its name as variables into global namespace + // f.e. pack_keys=1 becomes available as $pack_keys with value of '1' + // unset($pack_keys); + foreach ($create_options_tmp as $each_create_option) { + $each_create_option = explode('=', $each_create_option); + if (isset($each_create_option[1])) { + // ensure there is no ambiguity for PHP 5 and 7 + $create_options[$each_create_option[0]] = $each_create_option[1]; + } + } + // we need explicit DEFAULT value here (different from '0') + $create_options['pack_keys'] = (! isset($create_options['pack_keys']) || strlen($create_options['pack_keys']) == 0) + ? 'DEFAULT' + : $create_options['pack_keys']; + return $create_options; + } + + /** + * generates column specification for ALTER or CREATE TABLE syntax + * + * @param string $name name + * @param string $type type ('INT', 'VARCHAR', 'BIT', ...) + * @param string $length length ('2', '5,2', '', ...) + * @param string $attribute attribute + * @param string $collation collation + * @param bool|string $null with 'NULL' or 'NOT NULL' + * @param string $default_type whether default is CURRENT_TIMESTAMP, + * NULL, NONE, USER_DEFINED + * @param string $default_value default value for USER_DEFINED + * default type + * @param string $extra 'AUTO_INCREMENT' + * @param string $comment field comment + * @param string $virtuality virtuality of the column + * @param string $expression expression for the virtual column + * @param string $move_to new position for column + * + * @todo move into class PMA_Column + * @todo on the interface, some js to clear the default value when the + * default current_timestamp is checked + * + * @return string field specification + */ + static function generateFieldSpec($name, $type, $length = '', + $attribute = '', $collation = '', $null = false, + $default_type = 'USER_DEFINED', $default_value = '', $extra = '', + $comment = '', $virtuality = '', $expression = '', $move_to = '' + ) { + $is_timestamp = mb_strpos( + mb_strtoupper($type), + 'TIMESTAMP' + ) !== false; + + $query = Util::backquote($name) . ' ' . $type; + + // allow the possibility of a length for TIME, DATETIME and TIMESTAMP + // (will work on MySQL >= 5.6.4) + // + // MySQL permits a non-standard syntax for FLOAT and DOUBLE, + // see https://dev.mysql.com/doc/refman/5.5/en/floating-point-types.html + // + $pattern = '@^(DATE|TINYBLOB|TINYTEXT|BLOB|TEXT|' + . 'MEDIUMBLOB|MEDIUMTEXT|LONGBLOB|LONGTEXT|SERIAL|BOOLEAN|UUID)$@i'; + if (strlen($length) !== 0 && ! preg_match($pattern, $type)) { + // Note: The variable $length here can contain several other things + // besides length - ENUM/SET value or length of DECIMAL (eg. 12,3) + // so we can't just convert it to integer + $query .= '(' . $length . ')'; + } + if ($attribute != '') { + $query .= ' ' . $attribute; + + if ($is_timestamp + && preg_match('/TIMESTAMP/i', $attribute) + && strlen($length) !== 0 + && $length !== 0 + ) { + $query .= '(' . $length . ')'; + } + } + + // if column is virtual, check if server type is Mysql as only Mysql server + // supports extra column properties + $isVirtualColMysql = $virtuality && in_array(Util::getServerType(), array('MySQL', 'Percona Server')); + // if column is virtual, check if server type is MariaDB as MariaDB server + // supports no extra virtual column properties except CHARACTER SET for text column types + $isVirtualColMariaDB = $virtuality && Util::getServerType() === 'MariaDB'; + + $matches = preg_match( + '@^(TINYTEXT|TEXT|MEDIUMTEXT|LONGTEXT|VARCHAR|CHAR|ENUM|SET)$@i', + $type + ); + if (! empty($collation) && $collation != 'NULL' && $matches) { + $query .= Util::getCharsetQueryPart( + $isVirtualColMariaDB ? preg_replace('~_.+~s', '', $collation) : $collation, + true + ); + } + + if ($virtuality) { + $query .= ' AS (' . $expression . ') ' . $virtuality; + } + + if (! $virtuality || $isVirtualColMysql) { + if ($null !== false) { + if ($null == 'YES') { + $query .= ' NULL'; + } else { + $query .= ' NOT NULL'; + } + } + + if (! $virtuality) { + switch ($default_type) { + case 'USER_DEFINED' : + if ($is_timestamp && $default_value === '0') { + // a TIMESTAMP does not accept DEFAULT '0' + // but DEFAULT 0 works + $query .= ' DEFAULT 0'; + } elseif ($type == 'BIT') { + $query .= ' DEFAULT b\'' + . preg_replace('/[^01]/', '0', $default_value) + . '\''; + } elseif ($type == 'BOOLEAN') { + if (preg_match('/^1|T|TRUE|YES$/i', $default_value)) { + $query .= ' DEFAULT TRUE'; + } elseif (preg_match('/^0|F|FALSE|NO$/i', $default_value)) { + $query .= ' DEFAULT FALSE'; + } else { + // Invalid BOOLEAN value + $query .= ' DEFAULT \'' + . $GLOBALS['dbi']->escapeString($default_value) . '\''; + } + } elseif ($type == 'BINARY' || $type == 'VARBINARY') { + $query .= ' DEFAULT 0x' . $default_value; + } else { + $query .= ' DEFAULT \'' + . $GLOBALS['dbi']->escapeString($default_value) . '\''; + } + break; + /** @noinspection PhpMissingBreakStatementInspection */ + case 'NULL' : + // If user uncheck null checkbox and not change default value null, + // default value will be ignored. + if ($null !== false && $null !== 'YES') { + break; + } + // else fall-through intended, no break here + case 'CURRENT_TIMESTAMP' : + case 'current_timestamp()': + $query .= ' DEFAULT ' . $default_type; + + if (strlen($length) !== 0 + && $length !== 0 + && $is_timestamp + && $default_type !== 'NULL' // Not to be added in case of NULL + ) { + $query .= '(' . $length . ')'; + } + break; + case 'NONE' : + default : + break; + } + } + + if (!empty($extra)) { + if ($virtuality) { + $extra = trim(preg_replace('~^\s*AUTO_INCREMENT\s*~is', ' ', $extra)); + } + + $query .= ' ' . $extra; + } + } + + if (!empty($comment)) { + $query .= " COMMENT '" . $GLOBALS['dbi']->escapeString($comment) . "'"; + } + + // move column + if ($move_to == '-first') { // dash can't appear as part of column name + $query .= ' FIRST'; + } elseif ($move_to != '') { + $query .= ' AFTER ' . Util::backquote($move_to); + } + return $query; + } // end function + + /** + * Checks if the number of records in a table is at least equal to + * $min_records + * + * @param int $min_records Number of records to check for in a table + * + * @return bool True, if at least $min_records exist, False otherwise. + */ + public function checkIfMinRecordsExist($min_records = 0) + { + $check_query = 'SELECT '; + $fieldsToSelect = ''; + + $uniqueFields = $this->getUniqueColumns(true, false); + if (count($uniqueFields) > 0) { + $fieldsToSelect = implode(', ', $uniqueFields); + } else { + $indexedCols = $this->getIndexedColumns(true, false); + if (count($indexedCols) > 0) { + $fieldsToSelect = implode(', ', $indexedCols); + } else { + $fieldsToSelect = '*'; + } + } + + $check_query .= $fieldsToSelect + . ' FROM ' . $this->getFullName(true) + . ' LIMIT ' . $min_records; + + $res = $GLOBALS['dbi']->tryQuery( + $check_query + ); + + if ($res !== false) { + $num_records = $GLOBALS['dbi']->numRows($res); + if ($num_records >= $min_records) { + return true; + } + } + + return false; + } + + /** + * Counts and returns (or displays) the number of records in a table + * + * @param bool $force_exact whether to force an exact count + * + * @return mixed the number of records if "retain" param is true, + * otherwise true + */ + public function countRecords($force_exact = false) + { + $is_view = $this->isView(); + $db = $this->_db_name; + $table = $this->_name; + + if ($this->_dbi->getCachedTableContent(array($db, $table, 'ExactRows')) != null) { + $row_count = $this->_dbi->getCachedTableContent( + array($db, $table, 'ExactRows') + ); + return $row_count; + } + $row_count = false; + + if (! $force_exact) { + if (($this->_dbi->getCachedTableContent(array($db, $table, 'Rows')) == null) + && !$is_view + ) { + $tmp_tables = $this->_dbi->getTablesFull($db, $table); + if (isset($tmp_tables[$table])) { + $this->_dbi->cacheTableContent( + array($db, $table), + $tmp_tables[$table] + ); + } + } + if ($this->_dbi->getCachedTableContent(array($db, $table, 'Rows')) != null) { + $row_count = $this->_dbi->getCachedTableContent( + array($db, $table, 'Rows') + ); + } else { + $row_count = false; + } + } + // for a VIEW, $row_count is always false at this point + if (false !== $row_count + && $row_count >= $GLOBALS['cfg']['MaxExactCount'] + ) { + return $row_count; + } + + if (! $is_view) { + $row_count = $this->_dbi->fetchValue( + 'SELECT COUNT(*) FROM ' . Util::backquote($db) . '.' + . Util::backquote($table) + ); + } else { + // For complex views, even trying to get a partial record + // count could bring down a server, so we offer an + // alternative: setting MaxExactCountViews to 0 will bypass + // completely the record counting for views + + if ($GLOBALS['cfg']['MaxExactCountViews'] == 0) { + $row_count = false; + } else { + // Counting all rows of a VIEW could be too long, + // so use a LIMIT clause. + // Use try_query because it can fail (when a VIEW is + // based on a table that no longer exists) + $result = $this->_dbi->tryQuery( + 'SELECT 1 FROM ' . Util::backquote($db) . '.' + . Util::backquote($table) . ' LIMIT ' + . $GLOBALS['cfg']['MaxExactCountViews'], + DatabaseInterface::CONNECT_USER, + DatabaseInterface::QUERY_STORE + ); + if (!$this->_dbi->getError()) { + $row_count = $this->_dbi->numRows($result); + $this->_dbi->freeResult($result); + } + } + } + if ($row_count) { + $this->_dbi->cacheTableContent(array($db, $table, 'ExactRows'), $row_count); + } + + return $row_count; + } // end of the 'Table::countRecords()' function + + /** + * Generates column specification for ALTER syntax + * + * @param string $oldcol old column name + * @param string $newcol new column name + * @param string $type type ('INT', 'VARCHAR', 'BIT', ...) + * @param string $length length ('2', '5,2', '', ...) + * @param string $attribute attribute + * @param string $collation collation + * @param bool|string $null with 'NULL' or 'NOT NULL' + * @param string $default_type whether default is CURRENT_TIMESTAMP, + * NULL, NONE, USER_DEFINED + * @param string $default_value default value for USER_DEFINED default + * type + * @param string $extra 'AUTO_INCREMENT' + * @param string $comment field comment + * @param string $virtuality virtuality of the column + * @param string $expression expression for the virtual column + * @param string $move_to new position for column + * + * @see Table::generateFieldSpec() + * + * @return string field specification + */ + public static function generateAlter($oldcol, $newcol, $type, $length, + $attribute, $collation, $null, $default_type, $default_value, + $extra, $comment, $virtuality, $expression, $move_to + ) { + return Util::backquote($oldcol) . ' ' + . self::generateFieldSpec( + $newcol, $type, $length, $attribute, + $collation, $null, $default_type, $default_value, $extra, + $comment, $virtuality, $expression, $move_to + ); + } // end function + + /** + * Inserts existing entries in a PMA_* table by reading a value from an old + * entry + * + * @param string $work The array index, which Relation feature to + * check ('relwork', 'commwork', ...) + * @param string $pma_table The array index, which PMA-table to update + * ('bookmark', 'relation', ...) + * @param array $get_fields Which fields will be SELECT'ed from the old entry + * @param array $where_fields Which fields will be used for the WHERE query + * (array('FIELDNAME' => 'FIELDVALUE')) + * @param array $new_fields Which fields will be used as new VALUES. + * These are the important keys which differ + * from the old entry + * (array('FIELDNAME' => 'NEW FIELDVALUE')) + * + * @global relation variable + * + * @return int|boolean + */ + public static function duplicateInfo($work, $pma_table, array $get_fields, + array $where_fields, array $new_fields + ) { + $relation = new Relation(); + $last_id = -1; + + if (!isset($GLOBALS['cfgRelation']) || !$GLOBALS['cfgRelation'][$work]) { + return true; + } + + $select_parts = array(); + $row_fields = array(); + foreach ($get_fields as $get_field) { + $select_parts[] = Util::backquote($get_field); + $row_fields[$get_field] = 'cc'; + } + + $where_parts = array(); + foreach ($where_fields as $_where => $_value) { + $where_parts[] = Util::backquote($_where) . ' = \'' + . $GLOBALS['dbi']->escapeString($_value) . '\''; + } + + $new_parts = array(); + $new_value_parts = array(); + foreach ($new_fields as $_where => $_value) { + $new_parts[] = Util::backquote($_where); + $new_value_parts[] = $GLOBALS['dbi']->escapeString($_value); + } + + $table_copy_query = ' + SELECT ' . implode(', ', $select_parts) . ' + FROM ' . Util::backquote($GLOBALS['cfgRelation']['db']) . '.' + . Util::backquote($GLOBALS['cfgRelation'][$pma_table]) . ' + WHERE ' . implode(' AND ', $where_parts); + + // must use DatabaseInterface::QUERY_STORE here, since we execute + // another query inside the loop + $table_copy_rs = $relation->queryAsControlUser( + $table_copy_query, true, DatabaseInterface::QUERY_STORE + ); + + while ($table_copy_row = @$GLOBALS['dbi']->fetchAssoc($table_copy_rs)) { + $value_parts = array(); + foreach ($table_copy_row as $_key => $_val) { + if (isset($row_fields[$_key]) && $row_fields[$_key] == 'cc') { + $value_parts[] = $GLOBALS['dbi']->escapeString($_val); + } + } + + $new_table_query = 'INSERT IGNORE INTO ' + . Util::backquote($GLOBALS['cfgRelation']['db']) + . '.' . Util::backquote($GLOBALS['cfgRelation'][$pma_table]) + . ' (' . implode(', ', $select_parts) . ', ' + . implode(', ', $new_parts) . ') VALUES (\'' + . implode('\', \'', $value_parts) . '\', \'' + . implode('\', \'', $new_value_parts) . '\')'; + + $relation->queryAsControlUser($new_table_query); + $last_id = $GLOBALS['dbi']->insertId(); + } // end while + + $GLOBALS['dbi']->freeResult($table_copy_rs); + + return $last_id; + } // end of 'Table::duplicateInfo()' function + + /** + * Copies or renames table + * + * @param string $source_db source database + * @param string $source_table source table + * @param string $target_db target database + * @param string $target_table target table + * @param string $what what to be moved or copied (data, dataonly) + * @param bool $move whether to move + * @param string $mode mode + * + * @return bool true if success, false otherwise + */ + public static function moveCopy($source_db, $source_table, $target_db, + $target_table, $what, $move, $mode + ) { + global $err_url; + + $relation = new Relation(); + + // Try moving the tables directly, using native `RENAME` statement. + if ($move && $what == 'data') { + $tbl = new Table($source_table, $source_db); + if ($tbl->rename($target_table, $target_db)) { + $GLOBALS['message'] = $tbl->getLastMessage(); + return true; + } + } + + // Setting required export settings. + $GLOBALS['sql_backquotes'] = 1; + $GLOBALS['asfile'] = 1; + + // Ensuring the target database is valid. + if (! $GLOBALS['dblist']->databases->exists($source_db, $target_db)) { + if (! $GLOBALS['dblist']->databases->exists($source_db)) { + $GLOBALS['message'] = Message::rawError( + sprintf( + __('Source database `%s` was not found!'), + htmlspecialchars($source_db) + ) + ); + } + if (! $GLOBALS['dblist']->databases->exists($target_db)) { + $GLOBALS['message'] = Message::rawError( + sprintf( + __('Target database `%s` was not found!'), + htmlspecialchars($target_db) + ) + ); + } + return false; + } + + /** + * The full name of source table, quoted. + * @var string $source + */ + $source = Util::backquote($source_db) + . '.' . Util::backquote($source_table); + + // If the target database is not specified, the operation is taking + // place in the same database. + if (! isset($target_db) || strlen($target_db) === 0) { + $target_db = $source_db; + } + + // Selecting the database could avoid some problems with replicated + // databases, when moving table from replicated one to not replicated one. + $GLOBALS['dbi']->selectDb($target_db); + + /** + * The full name of target table, quoted. + * @var string $target + */ + $target = Util::backquote($target_db) + . '.' . Util::backquote($target_table); + + // No table is created when this is a data-only operation. + if ($what != 'dataonly') { + /** + * Instance used for exporting the current structure of the table. + * + * @var PhpMyAdmin\Plugins\Export\ExportSql + */ + $export_sql_plugin = Plugins::getPlugin( + "export", + "sql", + 'libraries/classes/Plugins/Export/', + array( + 'export_type' => 'table', + 'single_table' => false, + ) + ); + + $no_constraints_comments = true; + $GLOBALS['sql_constraints_query'] = ''; + // set the value of global sql_auto_increment variable + if (isset($_POST['sql_auto_increment'])) { + $GLOBALS['sql_auto_increment'] = $_POST['sql_auto_increment']; + } + + /** + * The old structure of the table.. + * @var string $sql_structure + */ + $sql_structure = $export_sql_plugin->getTableDef( + $source_db, $source_table, "\n", $err_url, false, false + ); + + unset($no_constraints_comments); + + // ----------------------------------------------------------------- + // Phase 0: Preparing structures used. + + /** + * The destination where the table is moved or copied to. + * @var Expression + */ + $destination = new Expression( + $target_db, $target_table, '' + ); + + // Find server's SQL mode so the builder can generate correct + // queries. + // One of the options that alters the behaviour is `ANSI_QUOTES`. + Context::setMode( + $GLOBALS['dbi']->fetchValue("SELECT @@sql_mode") + ); + + // ----------------------------------------------------------------- + // Phase 1: Dropping existent element of the same name (if exists + // and required). + + if (isset($_POST['drop_if_exists']) + && $_POST['drop_if_exists'] == 'true' + ) { + + /** + * Drop statement used for building the query. + * @var DropStatement $statement + */ + $statement = new DropStatement(); + + $tbl = new Table($target_db, $target_table); + + $statement->options = new OptionsArray( + array( + $tbl->isView() ? 'VIEW' : 'TABLE', + 'IF EXISTS', + ) + ); + + $statement->fields = array($destination); + + // Building the query. + $drop_query = $statement->build() . ';'; + + // Executing it. + $GLOBALS['dbi']->query($drop_query); + $GLOBALS['sql_query'] .= "\n" . $drop_query; + + // If an existing table gets deleted, maintain any entries for + // the PMA_* tables. + $maintain_relations = true; + } + + // ----------------------------------------------------------------- + // Phase 2: Generating the new query of this structure. + + /** + * The parser responsible for parsing the old queries. + * @var Parser $parser + */ + $parser = new Parser($sql_structure); + + if (!empty($parser->statements[0])) { + + /** + * The CREATE statement of this structure. + * @var \PhpMyAdmin\SqlParser\Statements\CreateStatement $statement + */ + $statement = $parser->statements[0]; + + // Changing the destination. + $statement->name = $destination; + + // Building back the query. + $sql_structure = $statement->build() . ';'; + + // Executing it. + $GLOBALS['dbi']->query($sql_structure); + $GLOBALS['sql_query'] .= "\n" . $sql_structure; + } + + // ----------------------------------------------------------------- + // Phase 3: Adding constraints. + // All constraint names are removed because they must be unique. + + if (($move || isset($GLOBALS['add_constraints'])) + && !empty($GLOBALS['sql_constraints_query']) + ) { + + $parser = new Parser($GLOBALS['sql_constraints_query']); + + /** + * The ALTER statement that generates the constraints. + * @var \PhpMyAdmin\SqlParser\Statements\AlterStatement $statement + */ + $statement = $parser->statements[0]; + + // Changing the altered table to the destination. + $statement->table = $destination; + + // Removing the name of the constraints. + foreach ($statement->altered as $idx => $altered) { + // All constraint names are removed because they must be unique. + if ($altered->options->has('CONSTRAINT')) { + $altered->field = null; + } + } + + // Building back the query. + $GLOBALS['sql_constraints_query'] = $statement->build() . ';'; + + // Executing it. + if ($mode == 'one_table') { + $GLOBALS['dbi']->query($GLOBALS['sql_constraints_query']); + } + $GLOBALS['sql_query'] .= "\n" . $GLOBALS['sql_constraints_query']; + if ($mode == 'one_table') { + unset($GLOBALS['sql_constraints_query']); + } + } + + // ----------------------------------------------------------------- + // Phase 4: Adding indexes. + // View phase 3. + + if (!empty($GLOBALS['sql_indexes'])) { + + $parser = new Parser($GLOBALS['sql_indexes']); + + $GLOBALS['sql_indexes'] = ''; + /** + * The ALTER statement that generates the indexes. + * @var \PhpMyAdmin\SqlParser\Statements\AlterStatement $statement + */ + foreach ($parser->statements as $statement) { + + // Changing the altered table to the destination. + $statement->table = $destination; + + // Removing the name of the constraints. + foreach ($statement->altered as $idx => $altered) { + // All constraint names are removed because they must be unique. + if ($altered->options->has('CONSTRAINT')) { + $altered->field = null; + } + } + + // Building back the query. + $sql_index = $statement->build() . ';'; + + // Executing it. + if ($mode == 'one_table' || $mode == 'db_copy') { + $GLOBALS['dbi']->query($sql_index); + } + + $GLOBALS['sql_indexes'] .= $sql_index; + } + + $GLOBALS['sql_query'] .= "\n" . $GLOBALS['sql_indexes']; + if ($mode == 'one_table' || $mode == 'db_copy') { + unset($GLOBALS['sql_indexes']); + } + } + + // ----------------------------------------------------------------- + // Phase 5: Adding AUTO_INCREMENT. + + if (! empty($GLOBALS['sql_auto_increments'])) { + if ($mode == 'one_table' || $mode == 'db_copy') { + + $parser = new Parser($GLOBALS['sql_auto_increments']); + + /** + * The ALTER statement that alters the AUTO_INCREMENT value. + * @var \PhpMyAdmin\SqlParser\Statements\AlterStatement $statement + */ + $statement = $parser->statements[0]; + + // Changing the altered table to the destination. + $statement->table = $destination; + + // Building back the query. + $GLOBALS['sql_auto_increments'] = $statement->build() . ';'; + + // Executing it. + $GLOBALS['dbi']->query($GLOBALS['sql_auto_increments']); + $GLOBALS['sql_query'] .= "\n" . $GLOBALS['sql_auto_increments']; + unset($GLOBALS['sql_auto_increments']); + } + } + } else { + $GLOBALS['sql_query'] = ''; + } + + $_table = new Table($target_table, $target_db); + // Copy the data unless this is a VIEW + if (($what == 'data' || $what == 'dataonly') + && ! $_table->isView() + ) { + $sql_set_mode = "SET SQL_MODE='NO_AUTO_VALUE_ON_ZERO'"; + $GLOBALS['dbi']->query($sql_set_mode); + $GLOBALS['sql_query'] .= "\n\n" . $sql_set_mode . ';'; + + $_old_table = new Table($source_table, $source_db); + $nonGeneratedCols = $_old_table->getNonGeneratedColumns(true); + if (count($nonGeneratedCols) > 0) { + $sql_insert_data = 'INSERT INTO ' . $target . '(' + . implode(', ', $nonGeneratedCols) + . ') SELECT ' . implode(', ', $nonGeneratedCols) + . ' FROM ' . $source; + + $GLOBALS['dbi']->query($sql_insert_data); + $GLOBALS['sql_query'] .= "\n\n" . $sql_insert_data . ';'; + } + } + + $relation->getRelationsParam(); + + // Drops old table if the user has requested to move it + if ($move) { + + // This could avoid some problems with replicated databases, when + // moving table from replicated one to not replicated one + $GLOBALS['dbi']->selectDb($source_db); + + $_source_table = new Table($source_table, $source_db); + if ($_source_table->isView()) { + $sql_drop_query = 'DROP VIEW'; + } else { + $sql_drop_query = 'DROP TABLE'; + } + $sql_drop_query .= ' ' . $source; + $GLOBALS['dbi']->query($sql_drop_query); + + // Renable table in configuration storage + $relation->renameTable( + $source_db, $target_db, + $source_table, $target_table + ); + + $GLOBALS['sql_query'] .= "\n\n" . $sql_drop_query . ';'; + // end if ($move) + return true; + } + + // we are copying + // Create new entries as duplicates from old PMA DBs + if ($what == 'dataonly' || isset($maintain_relations)) { + return true; + } + + if ($GLOBALS['cfgRelation']['commwork']) { + // Get all comments and MIME-Types for current table + $comments_copy_rs = $relation->queryAsControlUser( + 'SELECT column_name, comment' + . ($GLOBALS['cfgRelation']['mimework'] + ? ', mimetype, transformation, transformation_options' + : '') + . ' FROM ' + . Util::backquote($GLOBALS['cfgRelation']['db']) + . '.' + . Util::backquote($GLOBALS['cfgRelation']['column_info']) + . ' WHERE ' + . ' db_name = \'' + . $GLOBALS['dbi']->escapeString($source_db) . '\'' + . ' AND ' + . ' table_name = \'' + . $GLOBALS['dbi']->escapeString($source_table) . '\'' + ); + + // Write every comment as new copied entry. [MIME] + while ($comments_copy_row + = $GLOBALS['dbi']->fetchAssoc($comments_copy_rs)) { + $new_comment_query = 'REPLACE INTO ' + . Util::backquote($GLOBALS['cfgRelation']['db']) + . '.' . Util::backquote( + $GLOBALS['cfgRelation']['column_info'] + ) + . ' (db_name, table_name, column_name, comment' + . ($GLOBALS['cfgRelation']['mimework'] + ? ', mimetype, transformation, transformation_options' + : '') + . ') ' . ' VALUES(' . '\'' . $GLOBALS['dbi']->escapeString($target_db) + . '\',\'' . $GLOBALS['dbi']->escapeString($target_table) . '\',\'' + . $GLOBALS['dbi']->escapeString($comments_copy_row['column_name']) + . '\',\'' + . $GLOBALS['dbi']->escapeString($comments_copy_row['comment']) + . '\'' + . ($GLOBALS['cfgRelation']['mimework'] + ? ',\'' . $GLOBALS['dbi']->escapeString( + $comments_copy_row['mimetype'] + ) + . '\',' . '\'' . $GLOBALS['dbi']->escapeString( + $comments_copy_row['transformation'] + ) + . '\',' . '\'' . $GLOBALS['dbi']->escapeString( + $comments_copy_row['transformation_options'] + ) + . '\'' + : '') + . ')'; + $relation->queryAsControlUser($new_comment_query); + } // end while + $GLOBALS['dbi']->freeResult($comments_copy_rs); + unset($comments_copy_rs); + } + + // duplicating the bookmarks must not be done here, but + // just once per db + + $get_fields = array('display_field'); + $where_fields = array( + 'db_name' => $source_db, + 'table_name' => $source_table + ); + $new_fields = array( + 'db_name' => $target_db, + 'table_name' => $target_table + ); + self::duplicateInfo( + 'displaywork', + 'table_info', + $get_fields, + $where_fields, + $new_fields + ); + + /** + * @todo revise this code when we support cross-db relations + */ + $get_fields = array( + 'master_field', + 'foreign_table', + 'foreign_field' + ); + $where_fields = array( + 'master_db' => $source_db, + 'master_table' => $source_table + ); + $new_fields = array( + 'master_db' => $target_db, + 'foreign_db' => $target_db, + 'master_table' => $target_table + ); + self::duplicateInfo( + 'relwork', + 'relation', + $get_fields, + $where_fields, + $new_fields + ); + + $get_fields = array( + 'foreign_field', + 'master_table', + 'master_field' + ); + $where_fields = array( + 'foreign_db' => $source_db, + 'foreign_table' => $source_table + ); + $new_fields = array( + 'master_db' => $target_db, + 'foreign_db' => $target_db, + 'foreign_table' => $target_table + ); + self::duplicateInfo( + 'relwork', + 'relation', + $get_fields, + $where_fields, + $new_fields + ); + + /** + * @todo Can't get duplicating PDFs the right way. The + * page numbers always get screwed up independently from + * duplication because the numbers do not seem to be stored on a + * per-database basis. Would the author of pdf support please + * have a look at it? + * + $get_fields = array('page_descr'); + $where_fields = array('db_name' => $source_db); + $new_fields = array('db_name' => $target_db); + $last_id = self::duplicateInfo( + 'pdfwork', + 'pdf_pages', + $get_fields, + $where_fields, + $new_fields + ); + + if (isset($last_id) && $last_id >= 0) { + $get_fields = array('x', 'y'); + $where_fields = array( + 'db_name' => $source_db, + 'table_name' => $source_table + ); + $new_fields = array( + 'db_name' => $target_db, + 'table_name' => $target_table, + 'pdf_page_number' => $last_id + ); + self::duplicateInfo( + 'pdfwork', + 'table_coords', + $get_fields, + $where_fields, + $new_fields + ); + } + */ + + return true; + } + + /** + * checks if given name is a valid table name, + * currently if not empty, trailing spaces, '.', '/' and '\' + * + * @param string $table_name name to check + * @param boolean $is_backquoted whether this name is used inside backquotes or not + * + * @todo add check for valid chars in filename on current system/os + * @see https://dev.mysql.com/doc/refman/5.0/en/legal-names.html + * + * @return boolean whether the string is valid or not + */ + static function isValidName($table_name, $is_backquoted = false) + { + if ($table_name !== rtrim($table_name)) { + // trailing spaces not allowed even in backquotes + return false; + } + + if (strlen($table_name) === 0) { + // zero length + return false; + } + + if (! $is_backquoted && $table_name !== trim($table_name)) { + // spaces at the start or in between only allowed inside backquotes + return false; + } + + if (! $is_backquoted && preg_match('/^[a-zA-Z0-9_$]+$/', $table_name)) { + // only allow the above regex in unquoted identifiers + // see : https://dev.mysql.com/doc/refman/5.7/en/identifiers.html + return true; + } elseif ($is_backquoted) { + // If backquoted, all characters should be allowed (except w/ trailing spaces) + return true; + } + + // If not backquoted and doesn't follow the above regex + return false; + } + + /** + * renames table + * + * @param string $new_name new table name + * @param string $new_db new database name + * + * @return bool success + */ + public function rename($new_name, $new_db = null) + { + if ($GLOBALS['dbi']->getLowerCaseNames() === '1') { + $new_name = strtolower($new_name); + } + + if (null !== $new_db && $new_db !== $this->getDbName()) { + // Ensure the target is valid + if (! $GLOBALS['dblist']->databases->exists($new_db)) { + $this->errors[] = __('Invalid database:') . ' ' . $new_db; + return false; + } + } else { + $new_db = $this->getDbName(); + } + + $new_table = new Table($new_name, $new_db); + + if ($this->getFullName() === $new_table->getFullName()) { + return true; + } + + // Allow whitespaces (not trailing) in $new_name, + // since we are using $backquoted in getting the fullName of table + // below to be used in the query + if (! self::isValidName($new_name, true)) { + $this->errors[] = __('Invalid table name:') . ' ' + . $new_table->getFullName(); + return false; + } + + // If the table is moved to a different database drop its triggers first + $triggers = $this->_dbi->getTriggers( + $this->getDbName(), $this->getName(), '' + ); + $handle_triggers = $this->getDbName() != $new_db && $triggers; + if ($handle_triggers) { + foreach ($triggers as $trigger) { + $sql = 'DROP TRIGGER IF EXISTS ' + . Util::backquote($this->getDbName()) + . '.' . Util::backquote($trigger['name']) . ';'; + $this->_dbi->query($sql); + } + } + + /* + * tested also for a view, in MySQL 5.0.92, 5.1.55 and 5.5.13 + */ + $GLOBALS['sql_query'] = ' + RENAME TABLE ' . $this->getFullName(true) . ' + TO ' . $new_table->getFullName(true) . ';'; + // I don't think a specific error message for views is necessary + if (! $this->_dbi->query($GLOBALS['sql_query'])) { + // Restore triggers in the old database + if ($handle_triggers) { + $this->_dbi->selectDb($this->getDbName()); + foreach ($triggers as $trigger) { + $this->_dbi->query($trigger['create']); + } + } + $this->errors[] = sprintf( + __('Failed to rename table %1$s to %2$s!'), + $this->getFullName(), + $new_table->getFullName() + ); + return false; + } + + $old_name = $this->getName(); + $old_db = $this->getDbName(); + $this->_name = $new_name; + $this->_db_name = $new_db; + + // Renable table in configuration storage + $this->relation->renameTable( + $old_db, $new_db, + $old_name, $new_name + ); + + $this->messages[] = sprintf( + __('Table %1$s has been renamed to %2$s.'), + htmlspecialchars($old_name), + htmlspecialchars($new_name) + ); + return true; + } + + /** + * Get all unique columns + * + * returns an array with all columns with unique content, in fact these are + * all columns being single indexed in PRIMARY or UNIQUE + * + * e.g. + * - PRIMARY(id) // id + * - UNIQUE(name) // name + * - PRIMARY(fk_id1, fk_id2) // NONE + * - UNIQUE(x,y) // NONE + * + * @param bool $backquoted whether to quote name with backticks `` + * @param bool $fullName whether to include full name of the table as a prefix + * + * @return array + */ + public function getUniqueColumns($backquoted = true, $fullName = true) + { + $sql = $this->_dbi->getTableIndexesSql( + $this->getDbName(), + $this->getName(), + 'Non_unique = 0' + ); + $uniques = $this->_dbi->fetchResult( + $sql, + array('Key_name', null), + 'Column_name' + ); + + $return = array(); + foreach ($uniques as $index) { + if (count($index) > 1) { + continue; + } + if ($fullName) { + $possible_column = $this->getFullName($backquoted) . '.'; + } else { + $possible_column = ''; + } + if ($backquoted) { + $possible_column .= Util::backquote($index[0]); + } else { + $possible_column .= $index[0]; + } + // a column might have a primary and an unique index on it + if (! in_array($possible_column, $return)) { + $return[] = $possible_column; + } + } + + return $return; + } + + /** + * Formats lists of columns + * + * returns an array with all columns that make use of an index + * + * e.g. index(col1, col2) would return col1, col2 + * + * @param array $indexed column data + * @param bool $backquoted whether to quote name with backticks `` + * @param bool $fullName whether to include full name of the table as a prefix + * + * @return array + */ + private function _formatColumns(array $indexed, $backquoted, $fullName) + { + $return = array(); + foreach ($indexed as $column) { + $return[] = ($fullName ? $this->getFullName($backquoted) . '.' : '') + . ($backquoted ? Util::backquote($column) : $column); + } + + return $return; + } + + /** + * Get all indexed columns + * + * returns an array with all columns that make use of an index + * + * e.g. index(col1, col2) would return col1, col2 + * + * @param bool $backquoted whether to quote name with backticks `` + * @param bool $fullName whether to include full name of the table as a prefix + * + * @return array + */ + public function getIndexedColumns($backquoted = true, $fullName = true) + { + $sql = $this->_dbi->getTableIndexesSql( + $this->getDbName(), + $this->getName(), + '' + ); + $indexed = $this->_dbi->fetchResult($sql, 'Column_name', 'Column_name'); + + return $this->_formatColumns($indexed, $backquoted, $fullName); + } + + /** + * Get all columns + * + * returns an array with all columns + * + * @param bool $backquoted whether to quote name with backticks `` + * @param bool $fullName whether to include full name of the table as a prefix + * + * @return array + */ + public function getColumns($backquoted = true, $fullName = true) + { + $sql = 'SHOW COLUMNS FROM ' . $this->getFullName(true); + $indexed = $this->_dbi->fetchResult($sql, 'Field', 'Field'); + + return $this->_formatColumns($indexed, $backquoted, $fullName); + } + + /** + * Get meta info for fields in table + * + * @return mixed + */ + public function getColumnsMeta() + { + $move_columns_sql_query = sprintf( + 'SELECT * FROM %s.%s LIMIT 1', + Util::backquote($this->_db_name), + Util::backquote($this->_name) + ); + $move_columns_sql_result = $this->_dbi->tryQuery($move_columns_sql_query); + if ($move_columns_sql_result !== false) { + return $this->_dbi->getFieldsMeta($move_columns_sql_result); + } else { + // unsure how to reproduce but it was seen on the reporting server + return array(); + } + } + + /** + * Get non-generated columns in table + * + * @param bool $backquoted whether to quote name with backticks `` + * + * @return array + */ + public function getNonGeneratedColumns($backquoted = true) + { + $columns_meta_query = 'SHOW COLUMNS FROM ' . $this->getFullName(true); + $ret = array(); + + $columns_meta_query_result = $this->_dbi->fetchResult( + $columns_meta_query + ); + + if ($columns_meta_query_result + && $columns_meta_query_result !== false + ) { + foreach ($columns_meta_query_result as $column) { + $value = $column['Field']; + if ($backquoted === true) { + $value = Util::backquote($value); + } + + if (( + strpos($column['Extra'], 'GENERATED') === false + && strpos($column['Extra'], 'VIRTUAL') === false + ) || $column['Extra'] === 'DEFAULT_GENERATED') { + array_push($ret, $value); + } + } + } + + return $ret; + } + + /** + * Return UI preferences for this table from phpMyAdmin database. + * + * @return array + */ + protected function getUiPrefsFromDb() + { + $cfgRelation = $this->relation->getRelationsParam(); + $pma_table = Util::backquote($cfgRelation['db']) . "." + . Util::backquote($cfgRelation['table_uiprefs']); + + // Read from phpMyAdmin database + $sql_query = " SELECT `prefs` FROM " . $pma_table + . " WHERE `username` = '" . $GLOBALS['dbi']->escapeString($GLOBALS['cfg']['Server']['user']) . "'" + . " AND `db_name` = '" . $GLOBALS['dbi']->escapeString($this->_db_name) . "'" + . " AND `table_name` = '" . $GLOBALS['dbi']->escapeString($this->_name) . "'"; + + $row = $this->_dbi->fetchArray($this->relation->queryAsControlUser($sql_query)); + if (isset($row[0])) { + return json_decode($row[0], true); + } + + return array(); + } + + /** + * Save this table's UI preferences into phpMyAdmin database. + * + * @return true|Message + */ + protected function saveUiPrefsToDb() + { + $cfgRelation = $this->relation->getRelationsParam(); + $pma_table = Util::backquote($cfgRelation['db']) . "." + . Util::backquote($cfgRelation['table_uiprefs']); + + $secureDbName = $GLOBALS['dbi']->escapeString($this->_db_name); + + $username = $GLOBALS['cfg']['Server']['user']; + $sql_query = " REPLACE INTO " . $pma_table + . " (username, db_name, table_name, prefs) VALUES ('" + . $GLOBALS['dbi']->escapeString($username) . "', '" . $secureDbName + . "', '" . $GLOBALS['dbi']->escapeString($this->_name) . "', '" + . $GLOBALS['dbi']->escapeString(json_encode($this->uiprefs)) . "')"; + + $success = $this->_dbi->tryQuery($sql_query, DatabaseInterface::CONNECT_CONTROL); + + if (!$success) { + $message = Message::error( + __('Could not save table UI preferences!') + ); + $message->addMessage( + Message::rawError( + $this->_dbi->getError(DatabaseInterface::CONNECT_CONTROL) + ), + '

    ' + ); + return $message; + } + + // Remove some old rows in table_uiprefs if it exceeds the configured + // maximum rows + $sql_query = 'SELECT COUNT(*) FROM ' . $pma_table; + $rows_count = $this->_dbi->fetchValue($sql_query); + $max_rows = $GLOBALS['cfg']['Server']['MaxTableUiprefs']; + if ($rows_count > $max_rows) { + $num_rows_to_delete = $rows_count - $max_rows; + $sql_query + = ' DELETE FROM ' . $pma_table . + ' ORDER BY last_update ASC' . + ' LIMIT ' . $num_rows_to_delete; + $success = $this->_dbi->tryQuery( + $sql_query, DatabaseInterface::CONNECT_CONTROL + ); + + if (!$success) { + $message = Message::error( + sprintf( + __( + 'Failed to cleanup table UI preferences (see ' . + '$cfg[\'Servers\'][$i][\'MaxTableUiprefs\'] %s)' + ), + Util::showDocu('config', 'cfg_Servers_MaxTableUiprefs') + ) + ); + $message->addMessage( + Message::rawError( + $this->_dbi->getError(DatabaseInterface::CONNECT_CONTROL) + ), + '

    ' + ); + return $message; + } + } + + return true; + } + + /** + * Loads the UI preferences for this table. + * If pmadb and table_uiprefs is set, it will load the UI preferences from + * phpMyAdmin database. + * + * @return void + */ + protected function loadUiPrefs() + { + $cfgRelation = $this->relation->getRelationsParam(); + $server_id = $GLOBALS['server']; + + // set session variable if it's still undefined + if (!isset($_SESSION['tmpval']['table_uiprefs'][$server_id][$this->_db_name][$this->_name])) { + // check whether we can get from pmadb + $_SESSION['tmpval']['table_uiprefs'][$server_id][$this->_db_name] + [$this->_name] = $cfgRelation['uiprefswork'] + ? $this->getUiPrefsFromDb() + : array(); + } + $this->uiprefs =& $_SESSION['tmpval']['table_uiprefs'][$server_id] + [$this->_db_name][$this->_name]; + } + + /** + * Get a property from UI preferences. + * Return false if the property is not found. + * Available property: + * - PROP_SORTED_COLUMN + * - PROP_COLUMN_ORDER + * - PROP_COLUMN_VISIB + * + * @param string $property property + * + * @return mixed + */ + public function getUiProp($property) + { + if (! isset($this->uiprefs)) { + $this->loadUiPrefs(); + } + + // do checking based on property + if ($property == self::PROP_SORTED_COLUMN) { + if (!isset($this->uiprefs[$property])) { + return false; + } + + if (!isset($_POST['discard_remembered_sort'])) { + // check if the column name exists in this table + $tmp = explode(' ', $this->uiprefs[$property]); + $colname = $tmp[0]; + //remove backquoting from colname + $colname = str_replace('`', '', $colname); + //get the available column name without backquoting + $avail_columns = $this->getColumns(false); + + foreach ($avail_columns as $each_col) { + // check if $each_col ends with $colname + if (substr_compare( + $each_col, + $colname, + mb_strlen($each_col) - mb_strlen($colname) + ) === 0 + ) { + return $this->uiprefs[$property]; + } + } + } + // remove the property, since it no longer exists in database + $this->removeUiProp($property); + return false; + } + + if ($property == self::PROP_COLUMN_ORDER + || $property == self::PROP_COLUMN_VISIB + ) { + if ($this->isView() || !isset($this->uiprefs[$property])) { + return false; + } + + // check if the table has not been modified + if ($this->getStatusInfo('Create_time') == $this->uiprefs['CREATE_TIME'] + ) { + return array_map('intval', $this->uiprefs[$property]); + } + + // remove the property, since the table has been modified + $this->removeUiProp($property); + return false; + } + + // default behaviour for other property: + return isset($this->uiprefs[$property]) ? $this->uiprefs[$property] : false; + } + + /** + * Set a property from UI preferences. + * If pmadb and table_uiprefs is set, it will save the UI preferences to + * phpMyAdmin database. + * Available property: + * - PROP_SORTED_COLUMN + * - PROP_COLUMN_ORDER + * - PROP_COLUMN_VISIB + * + * @param string $property Property + * @param mixed $value Value for the property + * @param string $table_create_time Needed for PROP_COLUMN_ORDER + * and PROP_COLUMN_VISIB + * + * @return boolean|Message + */ + public function setUiProp($property, $value, $table_create_time = null) + { + if (! isset($this->uiprefs)) { + $this->loadUiPrefs(); + } + // we want to save the create time if the property is PROP_COLUMN_ORDER + if (! $this->isView() + && ($property == self::PROP_COLUMN_ORDER + || $property == self::PROP_COLUMN_VISIB) + ) { + $curr_create_time = $this->getStatusInfo('CREATE_TIME'); + if (isset($table_create_time) + && $table_create_time == $curr_create_time + ) { + $this->uiprefs['CREATE_TIME'] = $curr_create_time; + } else { + // there is no $table_create_time, or + // supplied $table_create_time is older than current create time, + // so don't save + return Message::error( + sprintf( + __( + 'Cannot save UI property "%s". The changes made will ' . + 'not be persistent after you refresh this page. ' . + 'Please check if the table structure has been changed.' + ), + $property + ) + ); + } + } + // save the value + $this->uiprefs[$property] = $value; + + // check if pmadb is set + $cfgRelation = $this->relation->getRelationsParam(); + if ($cfgRelation['uiprefswork']) { + return $this->saveUiprefsToDb(); + } + return true; + } + + /** + * Remove a property from UI preferences. + * + * @param string $property the property + * + * @return true|Message + */ + public function removeUiProp($property) + { + if (! isset($this->uiprefs)) { + $this->loadUiPrefs(); + } + if (isset($this->uiprefs[$property])) { + unset($this->uiprefs[$property]); + + // check if pmadb is set + $cfgRelation = $this->relation->getRelationsParam(); + if ($cfgRelation['uiprefswork']) { + return $this->saveUiprefsToDb(); + } + } + return true; + } + + /** + * Get all column names which are MySQL reserved words + * + * @return array + * @access public + */ + public function getReservedColumnNames() + { + $columns = $this->getColumns(false); + $return = array(); + foreach ($columns as $column) { + $temp = explode('.', $column); + $column_name = $temp[2]; + if (Context::isKeyword($column_name, true)) { + $return[] = $column_name; + } + } + return $return; + } + + /** + * Function to get the name and type of the columns of a table + * + * @return array + */ + public function getNameAndTypeOfTheColumns() + { + $columns = array(); + foreach ($this->_dbi->getColumnsFull( + $this->_db_name, $this->_name + ) as $row) { + if (preg_match('@^(set|enum)\((.+)\)$@i', $row['Type'], $tmp)) { + $tmp[2] = mb_substr( + preg_replace('@([^,])\'\'@', '\\1\\\'', ',' . $tmp[2]), 1 + ); + $columns[$row['Field']] = $tmp[1] . '(' + . str_replace(',', ', ', $tmp[2]) . ')'; + } else { + $columns[$row['Field']] = $row['Type']; + } + } + return $columns; + } + + /** + * Get index with index name + * + * @param string $index Index name + * + * @return Index + */ + public function getIndex($index) + { + return Index::singleton($this->_db_name, $this->_name, $index); + } + + /** + * Function to get the sql query for index creation or edit + * + * @param Index $index current index + * @param bool &$error whether error occurred or not + * + * @return string + */ + public function getSqlQueryForIndexCreateOrEdit($index, &$error) + { + // $sql_query is the one displayed in the query box + $sql_query = sprintf( + 'ALTER TABLE %s.%s', + Util::backquote($this->_db_name), + Util::backquote($this->_name) + ); + + // Drops the old index + if (! empty($_POST['old_index'])) { + if ($_POST['old_index'] == 'PRIMARY') { + $sql_query .= ' DROP PRIMARY KEY,'; + } else { + $sql_query .= sprintf( + ' DROP INDEX %s,', + Util::backquote($_POST['old_index']) + ); + } + } // end if + + // Builds the new one + switch ($index->getChoice()) { + case 'PRIMARY': + if ($index->getName() == '') { + $index->setName('PRIMARY'); + } elseif ($index->getName() != 'PRIMARY') { + $error = Message::error( + __('The name of the primary key must be "PRIMARY"!') + ); + } + $sql_query .= ' ADD PRIMARY KEY'; + break; + case 'FULLTEXT': + case 'UNIQUE': + case 'INDEX': + case 'SPATIAL': + if ($index->getName() == 'PRIMARY') { + $error = Message::error( + __('Can\'t rename index to PRIMARY!') + ); + } + $sql_query .= sprintf( + ' ADD %s ', + $index->getChoice() + ); + if ($index->getName()) { + $sql_query .= Util::backquote($index->getName()); + } + break; + } // end switch + + $index_fields = array(); + foreach ($index->getColumns() as $key => $column) { + $index_fields[$key] = Util::backquote($column->getName()); + if ($column->getSubPart()) { + $index_fields[$key] .= '(' . $column->getSubPart() . ')'; + } + } // end while + + if (empty($index_fields)) { + $error = Message::error(__('No index parts defined!')); + } else { + $sql_query .= ' (' . implode(', ', $index_fields) . ')'; + } + + $keyBlockSizes = $index->getKeyBlockSize(); + if (! empty($keyBlockSizes)) { + $sql_query .= sprintf( + ' KEY_BLOCK_SIZE = %s', + $GLOBALS['dbi']->escapeString($keyBlockSizes) + ); + } + + // specifying index type is allowed only for primary, unique and index only + // TokuDB is using Fractal Tree, Using Type is not useless + // Ref: https://mariadb.com/kb/en/mariadb/storage-engine-index-types/ + $type = $index->getType(); + if ($index->getChoice() != 'SPATIAL' + && $index->getChoice() != 'FULLTEXT' + && in_array($type, Index::getIndexTypes()) + && ! $this->isEngine(array('TOKUDB')) + ) { + $sql_query .= ' USING ' . $type; + } + + $parser = $index->getParser(); + if ($index->getChoice() == 'FULLTEXT' && ! empty($parser)) { + $sql_query .= ' WITH PARSER ' . $GLOBALS['dbi']->escapeString($parser); + } + + $comment = $index->getComment(); + if (! empty($comment)) { + $sql_query .= sprintf( + " COMMENT '%s'", + $GLOBALS['dbi']->escapeString($comment) + ); + } + + $sql_query .= ';'; + + return $sql_query; + } + + /** + * Function to handle update for display field + * + * @param string $display_field display field + * @param array $cfgRelation configuration relation + * + * @return boolean True on update succeed or False on failure + */ + public function updateDisplayField($display_field, array $cfgRelation) + { + $upd_query = false; + if ($display_field == '') { + $upd_query = 'DELETE FROM ' + . Util::backquote($GLOBALS['cfgRelation']['db']) + . '.' . Util::backquote($cfgRelation['table_info']) + . ' WHERE db_name = \'' + . $GLOBALS['dbi']->escapeString($this->_db_name) . '\'' + . ' AND table_name = \'' + . $GLOBALS['dbi']->escapeString($this->_name) . '\''; + } else { + $upd_query = 'REPLACE INTO ' + . Util::backquote($GLOBALS['cfgRelation']['db']) + . '.' . Util::backquote($cfgRelation['table_info']) + . '(db_name, table_name, display_field) VALUES(' + . '\'' . $GLOBALS['dbi']->escapeString($this->_db_name) . '\',' + . '\'' . $GLOBALS['dbi']->escapeString($this->_name) . '\',' + . '\'' . $GLOBALS['dbi']->escapeString($display_field) . '\')'; + } + + if ($upd_query) { + $this->_dbi->query( + $upd_query, + DatabaseInterface::CONNECT_CONTROL, + 0, + false + ); + return true; + } + return false; + } + + /** + * Function to get update query for updating internal relations + * + * @param array $multi_edit_columns_name multi edit column names + * @param array $destination_db destination tables + * @param array $destination_table destination tables + * @param array $destination_column destination columns + * @param array $cfgRelation configuration relation + * @param array|null $existrel db, table, column + * + * @return boolean + */ + public function updateInternalRelations(array $multi_edit_columns_name, + array $destination_db, array $destination_table, array $destination_column, + array $cfgRelation, $existrel + ) { + $updated = false; + foreach ($destination_db as $master_field_md5 => $foreign_db) { + $upd_query = null; + // Map the fieldname's md5 back to its real name + $master_field = $multi_edit_columns_name[$master_field_md5]; + $foreign_table = $destination_table[$master_field_md5]; + $foreign_field = $destination_column[$master_field_md5]; + if (! empty($foreign_db) + && ! empty($foreign_table) + && ! empty($foreign_field) + ) { + if (! isset($existrel[$master_field])) { + $upd_query = 'INSERT INTO ' + . Util::backquote($GLOBALS['cfgRelation']['db']) + . '.' . Util::backquote($cfgRelation['relation']) + . '(master_db, master_table, master_field, foreign_db,' + . ' foreign_table, foreign_field)' + . ' values(' + . '\'' . $GLOBALS['dbi']->escapeString($this->_db_name) . '\', ' + . '\'' . $GLOBALS['dbi']->escapeString($this->_name) . '\', ' + . '\'' . $GLOBALS['dbi']->escapeString($master_field) . '\', ' + . '\'' . $GLOBALS['dbi']->escapeString($foreign_db) . '\', ' + . '\'' . $GLOBALS['dbi']->escapeString($foreign_table) . '\',' + . '\'' . $GLOBALS['dbi']->escapeString($foreign_field) . '\')'; + + } elseif ($existrel[$master_field]['foreign_db'] != $foreign_db + || $existrel[$master_field]['foreign_table'] != $foreign_table + || $existrel[$master_field]['foreign_field'] != $foreign_field + ) { + $upd_query = 'UPDATE ' + . Util::backquote($GLOBALS['cfgRelation']['db']) + . '.' . Util::backquote($cfgRelation['relation']) + . ' SET foreign_db = \'' + . $GLOBALS['dbi']->escapeString($foreign_db) . '\', ' + . ' foreign_table = \'' + . $GLOBALS['dbi']->escapeString($foreign_table) . '\', ' + . ' foreign_field = \'' + . $GLOBALS['dbi']->escapeString($foreign_field) . '\' ' + . ' WHERE master_db = \'' + . $GLOBALS['dbi']->escapeString($this->_db_name) . '\'' + . ' AND master_table = \'' + . $GLOBALS['dbi']->escapeString($this->_name) . '\'' + . ' AND master_field = \'' + . $GLOBALS['dbi']->escapeString($master_field) . '\''; + } // end if... else.... + } elseif (isset($existrel[$master_field])) { + $upd_query = 'DELETE FROM ' + . Util::backquote($GLOBALS['cfgRelation']['db']) + . '.' . Util::backquote($cfgRelation['relation']) + . ' WHERE master_db = \'' + . $GLOBALS['dbi']->escapeString($this->_db_name) . '\'' + . ' AND master_table = \'' + . $GLOBALS['dbi']->escapeString($this->_name) . '\'' + . ' AND master_field = \'' + . $GLOBALS['dbi']->escapeString($master_field) . '\''; + } // end if... else.... + + if (isset($upd_query)) { + $this->_dbi->query( + $upd_query, + DatabaseInterface::CONNECT_CONTROL, + 0, + false + ); + $updated = true; + } + } + return $updated; + } + + /** + * Function to handle foreign key updates + * + * @param array $destination_foreign_db destination foreign database + * @param array $multi_edit_columns_name multi edit column names + * @param array $destination_foreign_table destination foreign table + * @param array $destination_foreign_column destination foreign column + * @param array $options_array options array + * @param string $table current table + * @param array $existrel_foreign db, table, column + * + * @return array + */ + public function updateForeignKeys(array $destination_foreign_db, + array $multi_edit_columns_name, array $destination_foreign_table, + array $destination_foreign_column, array $options_array, $table, array $existrel_foreign + ) { + $html_output = ''; + $preview_sql_data = ''; + $display_query = ''; + $seen_error = false; + + foreach ($destination_foreign_db as $master_field_md5 => $foreign_db) { + $create = false; + $drop = false; + + // Map the fieldname's md5 back to its real name + $master_field = $multi_edit_columns_name[$master_field_md5]; + + $foreign_table = $destination_foreign_table[$master_field_md5]; + $foreign_field = $destination_foreign_column[$master_field_md5]; + + if (isset($existrel_foreign[$master_field_md5]['ref_db_name'])) { + $ref_db_name = $existrel_foreign[$master_field_md5]['ref_db_name']; + } else { + $ref_db_name = $GLOBALS['db']; + } + + $empty_fields = false; + foreach ($master_field as $key => $one_field) { + if ((! empty($one_field) && empty($foreign_field[$key])) + || (empty($one_field) && ! empty($foreign_field[$key])) + ) { + $empty_fields = true; + } + + if (empty($one_field) && empty($foreign_field[$key])) { + unset($master_field[$key]); + unset($foreign_field[$key]); + } + } + + if (! empty($foreign_db) + && ! empty($foreign_table) + && ! $empty_fields + ) { + if (isset($existrel_foreign[$master_field_md5])) { + $constraint_name + = $existrel_foreign[$master_field_md5]['constraint']; + $on_delete = !empty( + $existrel_foreign[$master_field_md5]['on_delete'] + ) + ? $existrel_foreign[$master_field_md5]['on_delete'] + : 'RESTRICT'; + $on_update = ! empty( + $existrel_foreign[$master_field_md5]['on_update'] + ) + ? $existrel_foreign[$master_field_md5]['on_update'] + : 'RESTRICT'; + + if ($ref_db_name != $foreign_db + || $existrel_foreign[$master_field_md5]['ref_table_name'] != $foreign_table + || $existrel_foreign[$master_field_md5]['ref_index_list'] != $foreign_field + || $existrel_foreign[$master_field_md5]['index_list'] != $master_field + || $_POST['constraint_name'][$master_field_md5] != $constraint_name + || ($_POST['on_delete'][$master_field_md5] != $on_delete) + || ($_POST['on_update'][$master_field_md5] != $on_update) + ) { + // another foreign key is already defined for this field + // or an option has been changed for ON DELETE or ON UPDATE + $drop = true; + $create = true; + } // end if... else.... + } else { + // no key defined for this field(s) + $create = true; + } + } elseif (isset($existrel_foreign[$master_field_md5])) { + $drop = true; + } // end if... else.... + + $tmp_error_drop = false; + if ($drop) { + $drop_query = 'ALTER TABLE ' . Util::backquote($table) + . ' DROP FOREIGN KEY ' + . Util::backquote( + $existrel_foreign[$master_field_md5]['constraint'] + ) + . ';'; + + if (! isset($_POST['preview_sql'])) { + $display_query .= $drop_query . "\n"; + $this->_dbi->tryQuery($drop_query); + $tmp_error_drop = $this->_dbi->getError(); + + if (! empty($tmp_error_drop)) { + $seen_error = true; + $html_output .= Util::mysqlDie( + $tmp_error_drop, $drop_query, false, '', false + ); + continue; + } + } else { + $preview_sql_data .= $drop_query . "\n"; + } + } + $tmp_error_create = false; + if (!$create) { + continue; + } + + $create_query = $this->_getSQLToCreateForeignKey( + $table, $master_field, $foreign_db, $foreign_table, $foreign_field, + $_POST['constraint_name'][$master_field_md5], + $options_array[$_POST['on_delete'][$master_field_md5]], + $options_array[$_POST['on_update'][$master_field_md5]] + ); + + if (! isset($_POST['preview_sql'])) { + $display_query .= $create_query . "\n"; + $this->_dbi->tryQuery($create_query); + $tmp_error_create = $this->_dbi->getError(); + if (! empty($tmp_error_create)) { + $seen_error = true; + + if (substr($tmp_error_create, 1, 4) == '1005') { + $message = Message::error( + __( + 'Error creating foreign key on %1$s (check data ' . + 'types)' + ) + ); + $message->addParam(implode(', ', $master_field)); + $html_output .= $message->getDisplay(); + } else { + $html_output .= Util::mysqlDie( + $tmp_error_create, $create_query, false, '', false + ); + } + $html_output .= Util::showMySQLDocu( + 'InnoDB_foreign_key_constraints' + ) . "\n"; + } + } else { + $preview_sql_data .= $create_query . "\n"; + } + + // this is an alteration and the old constraint has been dropped + // without creation of a new one + if ($drop && $create && empty($tmp_error_drop) + && ! empty($tmp_error_create) + ) { + // a rollback may be better here + $sql_query_recreate = '# Restoring the dropped constraint...' . "\n"; + $sql_query_recreate .= $this->_getSQLToCreateForeignKey( + $table, + $master_field, + $existrel_foreign[$master_field_md5]['ref_db_name'], + $existrel_foreign[$master_field_md5]['ref_table_name'], + $existrel_foreign[$master_field_md5]['ref_index_list'], + $existrel_foreign[$master_field_md5]['constraint'], + $options_array[$existrel_foreign[$master_field_md5]['on_delete']], + $options_array[$existrel_foreign[$master_field_md5]['on_update']] + ); + if (! isset($_POST['preview_sql'])) { + $display_query .= $sql_query_recreate . "\n"; + $this->_dbi->tryQuery($sql_query_recreate); + } else { + $preview_sql_data .= $sql_query_recreate; + } + } + } // end foreach + + return array( + $html_output, + $preview_sql_data, + $display_query, + $seen_error + ); + } + + /** + * Returns the SQL query for foreign key constraint creation + * + * @param string $table table name + * @param array $field field names + * @param string $foreignDb foreign database name + * @param string $foreignTable foreign table name + * @param array $foreignField foreign field names + * @param string $name name of the constraint + * @param string $onDelete on delete action + * @param string $onUpdate on update action + * + * @return string SQL query for foreign key constraint creation + */ + private function _getSQLToCreateForeignKey( + $table, + array $field, + $foreignDb, + $foreignTable, + array $foreignField, + $name = null, + $onDelete = null, + $onUpdate = null + ) { + $sql_query = 'ALTER TABLE ' . Util::backquote($table) . ' ADD '; + // if user entered a constraint name + if (! empty($name)) { + $sql_query .= ' CONSTRAINT ' . Util::backquote($name); + } + + foreach ($field as $key => $one_field) { + $field[$key] = Util::backquote($one_field); + } + foreach ($foreignField as $key => $one_field) { + $foreignField[$key] = Util::backquote($one_field); + } + $sql_query .= ' FOREIGN KEY (' . implode(', ', $field) . ') REFERENCES ' + . ($this->_db_name != $foreignDb + ? Util::backquote($foreignDb) . '.' : '') + . Util::backquote($foreignTable) + . '(' . implode(', ', $foreignField) . ')'; + + if (! empty($onDelete)) { + $sql_query .= ' ON DELETE ' . $onDelete; + } + if (! empty($onUpdate)) { + $sql_query .= ' ON UPDATE ' . $onUpdate; + } + $sql_query .= ';'; + + return $sql_query; + } + + /** + * Returns the generation expression for virtual columns + * + * @param string $column name of the column + * + * @return array|boolean associative array of column name and their expressions + * or false on failure + */ + public function getColumnGenerationExpression($column = null) + { + $serverType = Util::getServerType(); + if ($serverType == 'MySQL' + && $GLOBALS['dbi']->getVersion() > 50705 + && ! $GLOBALS['cfg']['Server']['DisableIS'] + ) { + $sql + = "SELECT + `COLUMN_NAME` AS `Field`, + `GENERATION_EXPRESSION` AS `Expression` + FROM + `information_schema`.`COLUMNS` + WHERE + `TABLE_SCHEMA` = '" . $GLOBALS['dbi']->escapeString($this->_db_name) . "' + AND `TABLE_NAME` = '" . $GLOBALS['dbi']->escapeString($this->_name) . "'"; + if ($column != null) { + $sql .= " AND `COLUMN_NAME` = '" . $GLOBALS['dbi']->escapeString($column) + . "'"; + } + $columns = $this->_dbi->fetchResult($sql, 'Field', 'Expression'); + return $columns; + } + + $createTable = $this->showCreate(); + if (!$createTable) { + return false; + } + + $parser = new Parser($createTable); + /** + * @var \PhpMyAdmin\SqlParser\Statements\CreateStatement $stmt + */ + $stmt = $parser->statements[0]; + $fields = TableUtils::getFields($stmt); + if ($column != null) { + $expression = isset($fields[$column]['expr']) ? + substr($fields[$column]['expr'], 1, -1) : ''; + return array($column => $expression); + } + + $ret = array(); + foreach ($fields as $field => $options) { + if (isset($options['expr'])) { + $ret[$field] = substr($options['expr'], 1, -1); + } + } + return $ret; + } + + /** + * Returns the CREATE statement for this table + * + * @return mixed + */ + public function showCreate() + { + return $this->_dbi->fetchValue( + 'SHOW CREATE TABLE ' . Util::backquote($this->_db_name) . '.' + . Util::backquote($this->_name), + 0, 1 + ); + } + + /** + * Returns the real row count for a table + * + * @return number + */ + public function getRealRowCountTable() + { + // SQL query to get row count for a table. + $result = $this->_dbi->fetchSingleRow( + sprintf( + 'SELECT COUNT(*) AS %s FROM %s.%s', + Util::backquote('row_count'), + Util::backquote($this->_db_name), + Util::backquote($this->_name) + ) + ); + return $result['row_count']; + } + + /** + * Get columns with indexes + * + * @param int $types types bitmask + * + * @return array an array of columns + */ + public function getColumnsWithIndex($types) + { + $columns_with_index = array(); + foreach ( + Index::getFromTableByChoice( + $this->_name, + $this->_db_name, + $types + ) as $index + ) { + $columns = $index->getColumns(); + foreach ($columns as $column_name => $dummy) { + $columns_with_index[] = $column_name; + } + } + return $columns_with_index; + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Template.php b/php/apps/phpmyadmin49/html/libraries/classes/Template.php new file mode 100644 index 00000000..02c9f6cf --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Template.php @@ -0,0 +1,135 @@ +name = $name; + + if (is_null($this::$twig)) { + $loader = new FilesystemLoader(static::BASE_PATH); + $cache_dir = $GLOBALS['PMA_Config']->getTempDir('twig'); + /* Twig expects false when cache is not configured */ + if (is_null($cache_dir)) { + $cache_dir = false; + } + $twig = new Environment($loader, array( + 'auto_reload' => true, + 'cache' => $cache_dir, + 'debug' => false, + )); + $twig->addExtension(new CharsetsExtension()); + $twig->addExtension(new CoreExtension()); + $twig->addExtension(new I18nExtension()); + $twig->addExtension(new IndexExtension()); + $twig->addExtension(new MessageExtension()); + $twig->addExtension(new PartitionExtension()); + $twig->addExtension(new PhpFunctionsExtension()); + $twig->addExtension(new PluginsExtension()); + $twig->addExtension(new RelationExtension()); + $twig->addExtension(new SanitizeExtension()); + $twig->addExtension(new ServerPrivilegesExtension()); + $twig->addExtension(new StorageEngineExtension()); + $twig->addExtension(new TrackerExtension()); + $twig->addExtension(new TableExtension()); + $twig->addExtension(new TransformationsExtension()); + $twig->addExtension(new UrlExtension()); + $twig->addExtension(new UtilExtension()); + $this::$twig = $twig; + } + } + + /** + * Template getter + * + * @param string $name Template name + * + * @return Template + */ + public static function get($name) + { + return new Template($name); + } + + /** + * Render template + * + * @param array $data Variables to be provided to the template + * + * @return string + */ + public function render(array $data = array()) + { + try { + $template = $this::$twig->load($this->name . '.twig'); + } catch (\RuntimeException $e) { + /* Retry with disabled cache */ + $this::$twig->setCache(false); + $template = $this::$twig->load($this->name . '.twig'); + /* + * The trigger error is intentionally after second load + * to avoid triggering error when disabling cache does not + * solve it. + */ + trigger_error( + sprintf( + __('Error while working with template cache: %s'), + $e->getMessage() + ), + E_USER_WARNING + ); + } + return $template->render($data); + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Theme.php b/php/apps/phpmyadmin49/html/libraries/classes/Theme.php new file mode 100644 index 00000000..d9ef6528 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Theme.php @@ -0,0 +1,465 @@ +getPath() . '/theme.json'; + if (! @file_exists($infofile)) { + return false; + } + + if ($this->mtime_info === filemtime($infofile)) { + return true; + } + $content = @file_get_contents($infofile); + if ($content === false) { + return false; + } + $data = json_decode($content, true); + + // Did we get expected data? + if (! is_array($data)) { + return false; + } + // Check that all required data are there + $members = array('name', 'version', 'supports'); + foreach ($members as $member) { + if (! isset($data[$member])) { + return false; + } + } + + // Version check + if (! is_array($data['supports'])) { + return false; + } + if (! in_array(PMA_MAJOR_VERSION, $data['supports'])) { + return false; + } + + $this->mtime_info = filemtime($infofile); + $this->filesize_info = filesize($infofile); + + $this->setVersion($data['version']); + $this->setName($data['name']); + + return true; + } + + /** + * returns theme object loaded from given folder + * or false if theme is invalid + * + * @param string $folder path to theme + * + * @return Theme|false + * @static + * @access public + */ + static public function load($folder) + { + $theme = new Theme(); + + $theme->setPath($folder); + + if (! $theme->loadInfo()) { + return false; + } + + $theme->checkImgPath(); + + return $theme; + } + + /** + * checks image path for existence - if not found use img from fallback theme + * + * @access public + * @return bool + */ + public function checkImgPath() + { + // try current theme first + if (is_dir($this->getPath() . '/img/')) { + $this->setImgPath($this->getPath() . '/img/'); + return true; + } + + // try fallback theme + $fallback = './themes/' . ThemeManager::FALLBACK_THEME . '/img/'; + if (is_dir($fallback)) { + $this->setImgPath($fallback); + return true; + } + + // we failed + trigger_error( + sprintf( + __('No valid image path for theme %s found!'), + $this->getName() + ), + E_USER_ERROR + ); + return false; + } + + /** + * returns path to theme + * + * @access public + * @return string path to theme + */ + public function getPath() + { + return $this->path; + } + + /** + * returns layout file + * + * @access public + * @return string layout file + */ + public function getLayoutFile() + { + return $this->getPath() . '/layout.inc.php'; + } + + /** + * set path to theme + * + * @param string $path path to theme + * + * @return void + * @access public + */ + public function setPath($path) + { + $this->path = trim($path); + } + + /** + * sets version + * + * @param string $version version to set + * + * @return void + * @access public + */ + public function setVersion($version) + { + $this->version = trim($version); + } + + /** + * returns version + * + * @return string version + * @access public + */ + public function getVersion() + { + return $this->version; + } + + /** + * checks theme version against $version + * returns true if theme version is equal or higher to $version + * + * @param string $version version to compare to + * + * @return boolean true if theme version is equal or higher to $version + * @access public + */ + public function checkVersion($version) + { + return version_compare($this->getVersion(), $version, 'lt'); + } + + /** + * sets name + * + * @param string $name name to set + * + * @return void + * @access public + */ + public function setName($name) + { + $this->name = trim($name); + } + + /** + * returns name + * + * @access public + * @return string name + */ + public function getName() + { + return $this->name; + } + + /** + * sets id + * + * @param string $id new id + * + * @return void + * @access public + */ + public function setId($id) + { + $this->id = trim($id); + } + + /** + * returns id + * + * @return string id + * @access public + */ + public function getId() + { + return $this->id; + } + + /** + * Sets path to images for the theme + * + * @param string $path path to images for this theme + * + * @return void + * @access public + */ + public function setImgPath($path) + { + $this->img_path = $path; + } + + /** + * Returns the path to image for the theme. + * If filename is given, it possibly fallbacks to fallback + * theme for it if image does not exist. + * + * @param string $file file name for image + * @param string $fallback fallback image + * + * @access public + * @return string image path for this theme + */ + public function getImgPath($file = null, $fallback = null) + { + if (is_null($file)) { + return $this->img_path; + } + + if (is_readable($this->img_path . $file)) { + return $this->img_path . $file; + } + + if (! is_null($fallback)) { + return $this->getImgPath($fallback); + } + + return './themes/' . ThemeManager::FALLBACK_THEME . '/img/' . $file; + } + + /** + * load css (send to stdout, normally the browser) + * + * @return bool + * @access public + */ + public function loadCss() + { + $success = true; + + /* Variables to be used by the themes: */ + $theme = $this; + if ($GLOBALS['text_dir'] === 'ltr') { + $right = 'right'; + $left = 'left'; + } else { + $right = 'left'; + $left = 'right'; + } + + foreach ($this->_cssFiles as $file) { + $path = $this->getPath() . "/css/$file.css.php"; + $fallback = "./themes/" + . ThemeManager::FALLBACK_THEME . "/css/$file.css.php"; + + if (is_readable($path)) { + echo "\n/* FILE: " , $file , ".css.php */\n"; + include $path; + } elseif (is_readable($fallback)) { + echo "\n/* FILE: " , $file , ".css.php */\n"; + include $fallback; + } else { + $success = false; + } + } + return $success; + } + + /** + * Renders the preview for this theme + * + * @return string + * @access public + */ + public function getPrintPreview() + { + $url_params = ['set_theme' => $this->getId()]; + $screen = null; + $path = $this->getPath() . '/screen.png'; + if (@file_exists($path)) { + $screen = $path; + } + + return Template::get('theme_preview')->render([ + 'url_params' => $url_params, + 'name' => $this->getName(), + 'version' => $this->getVersion(), + 'id' => $this->getId(), + 'screen' => $screen, + ]); + } + + /** + * Gets currently configured font size. + * + * @return String with font size. + */ + function getFontSize() + { + $fs = $GLOBALS['PMA_Config']->get('FontSize'); + if (!is_null($fs)) { + return $fs; + } + return '82%'; + } + + /** + * Generates code for CSS gradient using various browser extensions. + * + * @param string $start_color Color of gradient start, hex value without # + * @param string $end_color Color of gradient end, hex value without # + * + * @return string CSS code. + */ + function getCssGradient($start_color, $end_color) + { + $result = array(); + // Opera 9.5+, IE 9 + $result[] = 'background-image: url(./themes/svg_gradient.php?from=' + . $start_color . '&to=' . $end_color . ');'; + $result[] = 'background-size: 100% 100%;'; + // Safari 4-5, Chrome 1-9 + $result[] = 'background: ' + . '-webkit-gradient(linear, left top, left bottom, from(#' + . $start_color . '), to(#' . $end_color . '));'; + // Safari 5.1, Chrome 10+ + $result[] = 'background: -webkit-linear-gradient(top, #' + . $start_color . ', #' . $end_color . ');'; + // Firefox 3.6+ + $result[] = 'background: -moz-linear-gradient(top, #' + . $start_color . ', #' . $end_color . ');'; + // IE 10 + $result[] = 'background: -ms-linear-gradient(top, #' + . $start_color . ', #' . $end_color . ');'; + // Opera 11.10 + $result[] = 'background: -o-linear-gradient(top, #' + . $start_color . ', #' . $end_color . ');'; + return implode("\n", $result); + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/ThemeManager.php b/php/apps/phpmyadmin49/html/libraries/classes/ThemeManager.php new file mode 100644 index 00000000..9d7ff2c2 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/ThemeManager.php @@ -0,0 +1,453 @@ +themes = array(); + $this->theme_default = self::FALLBACK_THEME; + $this->active_theme = ''; + + if (! $this->setThemesPath('./themes/')) { + return; + } + + $this->setThemePerServer($GLOBALS['cfg']['ThemePerServer']); + + $this->loadThemes(); + + $this->theme = new Theme; + + if (! $this->checkTheme($GLOBALS['cfg']['ThemeDefault'])) { + trigger_error( + sprintf( + __('Default theme %s not found!'), + htmlspecialchars($GLOBALS['cfg']['ThemeDefault']) + ), + E_USER_ERROR + ); + $GLOBALS['cfg']['ThemeDefault'] = false; + } + + $this->theme_default = $GLOBALS['cfg']['ThemeDefault']; + + // check if user have a theme cookie + $cookie_theme = $this->getThemeCookie(); + if (! $cookie_theme || ! $this->setActiveTheme($cookie_theme)) { + if ($GLOBALS['cfg']['ThemeDefault']) { + // otherwise use default theme + $this->setActiveTheme($this->theme_default); + } else { + // or fallback theme + $this->setActiveTheme(self::FALLBACK_THEME); + } + } + } + + /** + * Returns the singleton Response object + * + * @return Response object + */ + public static function getInstance() + { + if (empty(self::$_instance)) { + self::$_instance = new ThemeManager(); + } + return self::$_instance; + } + + /** + * sets path to folder containing the themes + * + * @param string $path path to themes folder + * + * @access public + * @return boolean success + */ + public function setThemesPath($path) + { + if (! $this->_checkThemeFolder($path)) { + return false; + } + + $this->_themes_path = trim($path); + return true; + } + + /** + * sets if there are different themes per server + * + * @param boolean $per_server Whether to enable per server flag + * + * @access public + * @return void + */ + public function setThemePerServer($per_server) + { + $this->per_server = (bool) $per_server; + } + + /** + * Sets active theme + * + * @param string $theme theme name + * + * @access public + * @return bool true on success + */ + public function setActiveTheme($theme = null) + { + if (! $this->checkTheme($theme)) { + trigger_error( + sprintf( + __('Theme %s not found!'), + htmlspecialchars($theme) + ), + E_USER_ERROR + ); + return false; + } + + $this->active_theme = $theme; + $this->theme = $this->themes[$theme]; + + // need to set later + //$this->setThemeCookie(); + + return true; + } + + /** + * Returns name for storing theme + * + * @return string cookie name + * @access public + */ + public function getThemeCookieName() + { + // Allow different theme per server + if (isset($GLOBALS['server']) && $this->per_server) { + return $this->cookie_name . '-' . $GLOBALS['server']; + } + + return $this->cookie_name; + } + + /** + * returns name of theme stored in the cookie + * + * @return string theme name from cookie + * @access public + */ + public function getThemeCookie() + { + $name = $this->getThemeCookieName(); + if (isset($_COOKIE[$name])) { + return $_COOKIE[$name]; + } + + return false; + } + + /** + * save theme in cookie + * + * @return bool true + * @access public + */ + public function setThemeCookie() + { + $GLOBALS['PMA_Config']->setCookie( + $this->getThemeCookieName(), + $this->theme->id, + $this->theme_default + ); + // force a change of a dummy session variable to avoid problems + // with the caching of phpmyadmin.css.php + $GLOBALS['PMA_Config']->set('theme-update', $this->theme->id); + return true; + } + + /** + * Checks whether folder is valid for storing themes + * + * @param string $folder Folder name to test + * + * @return boolean + * @access private + */ + private function _checkThemeFolder($folder) + { + if (! is_dir($folder)) { + trigger_error( + sprintf( + __('Theme path not found for theme %s!'), + htmlspecialchars($folder) + ), + E_USER_ERROR + ); + return false; + } + + return true; + } + + /** + * read all themes + * + * @return bool true + * @access public + */ + public function loadThemes() + { + $this->themes = array(); + + if (false === ($handleThemes = opendir($this->_themes_path))) { + trigger_error( + 'phpMyAdmin-ERROR: cannot open themes folder: ' + . $this->_themes_path, + E_USER_WARNING + ); + return false; + } + + // check for themes directory + while (false !== ($PMA_Theme = readdir($handleThemes))) { + // Skip non dirs, . and .. + if ($PMA_Theme == '.' + || $PMA_Theme == '..' + || ! @is_dir($this->_themes_path . $PMA_Theme) + ) { + continue; + } + if (array_key_exists($PMA_Theme, $this->themes)) { + continue; + } + $new_theme = Theme::load( + $this->_themes_path . $PMA_Theme + ); + if ($new_theme) { + $new_theme->setId($PMA_Theme); + $this->themes[$PMA_Theme] = $new_theme; + } + } // end get themes + closedir($handleThemes); + + ksort($this->themes); + return true; + } + + /** + * checks if given theme name is a known theme + * + * @param string $theme name fo theme to check for + * + * @return bool + * @access public + */ + public function checkTheme($theme) + { + return array_key_exists($theme, $this->themes); + } + + /** + * returns HTML selectbox, with or without form enclosed + * + * @param boolean $form whether enclosed by from tags or not + * + * @return string + * @access public + */ + public function getHtmlSelectBox($form = true) + { + $select_box = ''; + + if ($form) { + $select_box .= '
    '; + $select_box .= $theme_preview_href . __('Theme:') . '' . "\n"; + + $select_box .= ''; + + if ($form) { + $select_box .= '
    '; + } + + return $select_box; + } + + /** + * Renders the previews for all themes + * + * @return string + * @access public + */ + public function getPrintPreviews() + { + $retval = ''; + foreach ($this->themes as $each_theme) { + $retval .= $each_theme->getPrintPreview(); + } // end 'open themes' + return $retval; + } + + /** + * returns Theme object for fall back theme + * + * @return Theme fall back theme + * @access public + */ + public function getFallBackTheme() + { + if (isset($this->themes[self::FALLBACK_THEME])) { + return $this->themes[self::FALLBACK_THEME]; + } + + return false; + } + + /** + * prints css data + * + * @return bool + * @access public + */ + public function printCss() + { + if ($this->theme->loadCss()) { + return true; + } + + // if loading css for this theme failed, try default theme css + $fallback_theme = $this->getFallBackTheme(); + if ($fallback_theme && $fallback_theme->loadCss()) { + return true; + } + + return false; + } + + /** + * Theme initialization + * + * @return void + * @access public + */ + public static function initializeTheme() + { + $tmanager = self::getInstance(); + + /** + * the theme object + * + * @global Theme $GLOBALS['PMA_Theme'] + */ + $GLOBALS['PMA_Theme'] = $tmanager->theme; + + // BC + /** + * the theme path + * @global string $GLOBALS['pmaThemePath'] + */ + $GLOBALS['pmaThemePath'] = $GLOBALS['PMA_Theme']->getPath(); + /** + * the theme image path + * @global string $GLOBALS['pmaThemeImage'] + */ + $GLOBALS['pmaThemeImage'] = $GLOBALS['PMA_Theme']->getImgPath(); + + /** + * load layout file if exists + */ + if (@file_exists($GLOBALS['PMA_Theme']->getLayoutFile())) { + include $GLOBALS['PMA_Theme']->getLayoutFile(); + } + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Tracker.php b/php/apps/phpmyadmin49/html/libraries/classes/Tracker.php new file mode 100644 index 00000000..027fb1af --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Tracker.php @@ -0,0 +1,924 @@ +getRelationsParam(); + /* Restore original state */ + self::$enabled = true; + if (! $cfgRelation['trackingwork']) { + return false; + } + + $pma_table = self::_getTrackingTable(); + + return isset($pma_table); + } + + /** + * Parses the name of a table from a SQL statement substring. + * + * @param string $string part of SQL statement + * + * @static + * + * @return string the name of table + */ + static protected function getTableName($string) + { + if (mb_strstr($string, '.')) { + $temp = explode('.', $string); + $tablename = $temp[1]; + } else { + $tablename = $string; + } + + $str = explode("\n", $tablename); + $tablename = $str[0]; + + $tablename = str_replace(';', '', $tablename); + $tablename = str_replace('`', '', $tablename); + $tablename = trim($tablename); + + return $tablename; + } + + + /** + * Gets the tracking status of a table, is it active or deactive ? + * + * @param string $dbname name of database + * @param string $tablename name of table + * + * @static + * + * @return boolean true or false + */ + public static function isTracked($dbname, $tablename) + { + if (! self::$enabled) { + return false; + } + + if (isset(self::$_tracking_cache[$dbname][$tablename])) { + return self::$_tracking_cache[$dbname][$tablename]; + } + /* We need to avoid attempt to track any queries + * from Relation::getRelationsParam + */ + self::$enabled = false; + $relation = new Relation(); + $cfgRelation = $relation->getRelationsParam(); + /* Restore original state */ + self::$enabled = true; + if (! $cfgRelation['trackingwork']) { + return false; + } + + $sql_query = " SELECT tracking_active FROM " . self::_getTrackingTable() . + " WHERE db_name = '" . $GLOBALS['dbi']->escapeString($dbname) . "' " . + " AND table_name = '" . $GLOBALS['dbi']->escapeString($tablename) . "' " . + " ORDER BY version DESC LIMIT 1"; + + $result = $GLOBALS['dbi']->fetchValue($sql_query, 0, 0, DatabaseInterface::CONNECT_CONTROL) == 1; + + self::$_tracking_cache[$dbname][$tablename] = $result; + + return $result; + } + + /** + * Returns the comment line for the log. + * + * @return string Comment, contains date and username + */ + public static function getLogComment() + { + $date = Util::date('Y-m-d H:i:s'); + $user = preg_replace('/\s+/', ' ', $GLOBALS['cfg']['Server']['user']); + + return "# log " . $date . " " . $user . "\n"; + } + + /** + * Creates tracking version of a table / view + * (in other words: create a job to track future changes on the table). + * + * @param string $dbname name of database + * @param string $tablename name of table + * @param string $version version + * @param string $tracking_set set of tracking statements + * @param bool $is_view if table is a view + * + * @static + * + * @return int result of version insertion + */ + public static function createVersion($dbname, $tablename, $version, + $tracking_set = '', $is_view = false + ) { + global $sql_backquotes, $export_type; + + $relation = new Relation(); + + if ($tracking_set == '') { + $tracking_set + = $GLOBALS['cfg']['Server']['tracking_default_statements']; + } + + // get Export SQL instance + /* @var $export_sql_plugin PhpMyAdmin\Plugins\Export\ExportSql */ + $export_sql_plugin = Plugins::getPlugin( + "export", + "sql", + 'libraries/classes/Plugins/Export/', + array( + 'export_type' => $export_type, + 'single_table' => false, + ) + ); + + $sql_backquotes = true; + + $date = Util::date('Y-m-d H:i:s'); + + // Get data definition snapshot of table + + $columns = $GLOBALS['dbi']->getColumns($dbname, $tablename, null, true); + // int indices to reduce size + $columns = array_values($columns); + // remove Privileges to reduce size + for ($i = 0, $nb = count($columns); $i < $nb; $i++) { + unset($columns[$i]['Privileges']); + } + + $indexes = $GLOBALS['dbi']->getTableIndexes($dbname, $tablename); + + $snapshot = array('COLUMNS' => $columns, 'INDEXES' => $indexes); + $snapshot = serialize($snapshot); + + // Get DROP TABLE / DROP VIEW and CREATE TABLE SQL statements + $sql_backquotes = true; + + $create_sql = ""; + + if ($GLOBALS['cfg']['Server']['tracking_add_drop_table'] == true + && $is_view == false + ) { + $create_sql .= self::getLogComment() + . 'DROP TABLE IF EXISTS ' . Util::backquote($tablename) . ";\n"; + + } + + if ($GLOBALS['cfg']['Server']['tracking_add_drop_view'] == true + && $is_view == true + ) { + $create_sql .= self::getLogComment() + . 'DROP VIEW IF EXISTS ' . Util::backquote($tablename) . ";\n"; + } + + $create_sql .= self::getLogComment() . + $export_sql_plugin->getTableDef($dbname, $tablename, "\n", ""); + + // Save version + + $sql_query = "/*NOTRACK*/\n" . + "INSERT INTO " . self::_getTrackingTable() . " (" . + "db_name, " . + "table_name, " . + "version, " . + "date_created, " . + "date_updated, " . + "schema_snapshot, " . + "schema_sql, " . + "data_sql, " . + "tracking " . + ") " . + "values ( + '" . $GLOBALS['dbi']->escapeString($dbname) . "', + '" . $GLOBALS['dbi']->escapeString($tablename) . "', + '" . $GLOBALS['dbi']->escapeString($version) . "', + '" . $GLOBALS['dbi']->escapeString($date) . "', + '" . $GLOBALS['dbi']->escapeString($date) . "', + '" . $GLOBALS['dbi']->escapeString($snapshot) . "', + '" . $GLOBALS['dbi']->escapeString($create_sql) . "', + '" . $GLOBALS['dbi']->escapeString("\n") . "', + '" . $GLOBALS['dbi']->escapeString($tracking_set) + . "' )"; + + $result = $relation->queryAsControlUser($sql_query); + + if ($result) { + // Deactivate previous version + self::deactivateTracking($dbname, $tablename, ($version - 1)); + } + + return $result; + } + + + /** + * Removes all tracking data for a table or a version of a table + * + * @param string $dbname name of database + * @param string $tablename name of table + * @param string $version version + * + * @static + * + * @return int result of version insertion + */ + public static function deleteTracking($dbname, $tablename, $version = '') + { + $relation = new Relation(); + + $sql_query = "/*NOTRACK*/\n" + . "DELETE FROM " . self::_getTrackingTable() + . " WHERE `db_name` = '" + . $GLOBALS['dbi']->escapeString($dbname) . "'" + . " AND `table_name` = '" + . $GLOBALS['dbi']->escapeString($tablename) . "'"; + if ($version) { + $sql_query .= " AND `version` = '" + . $GLOBALS['dbi']->escapeString($version) . "'"; + } + $result = $relation->queryAsControlUser($sql_query); + + return $result; + } + + /** + * Creates tracking version of a database + * (in other words: create a job to track future changes on the database). + * + * @param string $dbname name of database + * @param string $version version + * @param string $query query + * @param string $tracking_set set of tracking statements + * + * @static + * + * @return int result of version insertion + */ + public static function createDatabaseVersion($dbname, $version, $query, + $tracking_set = 'CREATE DATABASE,ALTER DATABASE,DROP DATABASE' + ) { + $relation = new Relation(); + + $date = Util::date('Y-m-d H:i:s'); + + if ($tracking_set == '') { + $tracking_set + = $GLOBALS['cfg']['Server']['tracking_default_statements']; + } + + $create_sql = ""; + + if ($GLOBALS['cfg']['Server']['tracking_add_drop_database'] == true) { + $create_sql .= self::getLogComment() + . 'DROP DATABASE IF EXISTS ' . Util::backquote($dbname) . ";\n"; + } + + $create_sql .= self::getLogComment() . $query; + + // Save version + $sql_query = "/*NOTRACK*/\n" . + "INSERT INTO " . self::_getTrackingTable() . " (" . + "db_name, " . + "table_name, " . + "version, " . + "date_created, " . + "date_updated, " . + "schema_snapshot, " . + "schema_sql, " . + "data_sql, " . + "tracking " . + ") " . + "values ( + '" . $GLOBALS['dbi']->escapeString($dbname) . "', + '" . $GLOBALS['dbi']->escapeString('') . "', + '" . $GLOBALS['dbi']->escapeString($version) . "', + '" . $GLOBALS['dbi']->escapeString($date) . "', + '" . $GLOBALS['dbi']->escapeString($date) . "', + '" . $GLOBALS['dbi']->escapeString('') . "', + '" . $GLOBALS['dbi']->escapeString($create_sql) . "', + '" . $GLOBALS['dbi']->escapeString("\n") . "', + '" . $GLOBALS['dbi']->escapeString($tracking_set) + . "' )"; + + $result = $relation->queryAsControlUser($sql_query); + + return $result; + } + + + + /** + * Changes tracking of a table. + * + * @param string $dbname name of database + * @param string $tablename name of table + * @param string $version version + * @param integer $new_state the new state of tracking + * + * @static + * + * @return int result of SQL query + */ + static private function _changeTracking($dbname, $tablename, + $version, $new_state + ) { + $relation = new Relation(); + + $sql_query = " UPDATE " . self::_getTrackingTable() . + " SET `tracking_active` = '" . $new_state . "' " . + " WHERE `db_name` = '" . $GLOBALS['dbi']->escapeString($dbname) . "' " . + " AND `table_name` = '" . $GLOBALS['dbi']->escapeString($tablename) . "' " . + " AND `version` = '" . $GLOBALS['dbi']->escapeString($version) . "' "; + + $result = $relation->queryAsControlUser($sql_query); + + return $result; + } + + /** + * Changes tracking data of a table. + * + * @param string $dbname name of database + * @param string $tablename name of table + * @param string $version version + * @param string $type type of data(DDL || DML) + * @param string|array $new_data the new tracking data + * + * @static + * + * @return bool result of change + */ + public static function changeTrackingData($dbname, $tablename, + $version, $type, $new_data + ) { + $relation = new Relation(); + + if ($type == 'DDL') { + $save_to = 'schema_sql'; + } elseif ($type == 'DML') { + $save_to = 'data_sql'; + } else { + return false; + } + $date = Util::date('Y-m-d H:i:s'); + + $new_data_processed = ''; + if (is_array($new_data)) { + foreach ($new_data as $data) { + $new_data_processed .= '# log ' . $date . ' ' . $data['username'] + . $GLOBALS['dbi']->escapeString($data['statement']) . "\n"; + } + } else { + $new_data_processed = $new_data; + } + + $sql_query = " UPDATE " . self::_getTrackingTable() . + " SET `" . $save_to . "` = '" . $new_data_processed . "' " . + " WHERE `db_name` = '" . $GLOBALS['dbi']->escapeString($dbname) . "' " . + " AND `table_name` = '" . $GLOBALS['dbi']->escapeString($tablename) . "' " . + " AND `version` = '" . $GLOBALS['dbi']->escapeString($version) . "' "; + + $result = $relation->queryAsControlUser($sql_query); + + return (boolean) $result; + } + + /** + * Activates tracking of a table. + * + * @param string $dbname name of database + * @param string $tablename name of table + * @param string $version version + * + * @static + * + * @return int result of SQL query + */ + public static function activateTracking($dbname, $tablename, $version) + { + return self::_changeTracking($dbname, $tablename, $version, 1); + } + + + /** + * Deactivates tracking of a table. + * + * @param string $dbname name of database + * @param string $tablename name of table + * @param string $version version + * + * @static + * + * @return int result of SQL query + */ + public static function deactivateTracking($dbname, $tablename, $version) + { + return self::_changeTracking($dbname, $tablename, $version, 0); + } + + + /** + * Gets the newest version of a tracking job + * (in other words: gets the HEAD version). + * + * @param string $dbname name of database + * @param string $tablename name of table + * @param string $statement tracked statement + * + * @static + * + * @return int (-1 if no version exists | > 0 if a version exists) + */ + public static function getVersion($dbname, $tablename, $statement = null) + { + $relation = new Relation(); + + $sql_query = " SELECT MAX(version) FROM " . self::_getTrackingTable() . + " WHERE `db_name` = '" . $GLOBALS['dbi']->escapeString($dbname) . "' " . + " AND `table_name` = '" . $GLOBALS['dbi']->escapeString($tablename) . "' "; + + if ($statement != "") { + $sql_query .= " AND FIND_IN_SET('" + . $statement . "',tracking) > 0" ; + } + $row = $GLOBALS['dbi']->fetchArray($relation->queryAsControlUser($sql_query)); + return isset($row[0]) + ? $row[0] + : -1; + } + + + /** + * Gets the record of a tracking job. + * + * @param string $dbname name of database + * @param string $tablename name of table + * @param string $version version number + * + * @static + * + * @return mixed record DDM log, DDL log, structure snapshot, tracked + * statements. + */ + public static function getTrackedData($dbname, $tablename, $version) + { + $relation = new Relation(); + + $sql_query = " SELECT * FROM " . self::_getTrackingTable() . + " WHERE `db_name` = '" . $GLOBALS['dbi']->escapeString($dbname) . "' "; + if (! empty($tablename)) { + $sql_query .= " AND `table_name` = '" + . $GLOBALS['dbi']->escapeString($tablename) . "' "; + } + $sql_query .= " AND `version` = '" . $GLOBALS['dbi']->escapeString($version) + . "' " . " ORDER BY `version` DESC LIMIT 1"; + + $mixed = $GLOBALS['dbi']->fetchAssoc($relation->queryAsControlUser($sql_query)); + + // PHP 7.4 fix for accessing array offset on null + if (!is_array($mixed)) { + $mixed = array( + 'schema_sql' => null, + 'data_sql' => null, + 'tracking' => null, + 'schema_snapshot' => null, + ); + } + + // Parse log + $log_schema_entries = explode('# log ', $mixed['schema_sql']); + $log_data_entries = explode('# log ', $mixed['data_sql']); + + $ddl_date_from = $date = Util::date('Y-m-d H:i:s'); + + $ddlog = array(); + $first_iteration = true; + + // Iterate tracked data definition statements + // For each log entry we want to get date, username and statement + foreach ($log_schema_entries as $log_entry) { + if (trim($log_entry) != '') { + $date = mb_substr($log_entry, 0, 19); + $username = mb_substr( + $log_entry, 20, mb_strpos($log_entry, "\n") - 20 + ); + if ($first_iteration) { + $ddl_date_from = $date; + $first_iteration = false; + } + $statement = rtrim(mb_strstr($log_entry, "\n")); + + $ddlog[] = array( 'date' => $date, + 'username'=> $username, + 'statement' => $statement ); + } + } + + $date_from = $ddl_date_from; + $ddl_date_to = $date; + + $dml_date_from = $date_from; + + $dmlog = array(); + $first_iteration = true; + + // Iterate tracked data manipulation statements + // For each log entry we want to get date, username and statement + foreach ($log_data_entries as $log_entry) { + if (trim($log_entry) != '') { + $date = mb_substr($log_entry, 0, 19); + $username = mb_substr( + $log_entry, 20, mb_strpos($log_entry, "\n") - 20 + ); + if ($first_iteration) { + $dml_date_from = $date; + $first_iteration = false; + } + $statement = rtrim(mb_strstr($log_entry, "\n")); + + $dmlog[] = array( 'date' => $date, + 'username' => $username, + 'statement' => $statement ); + } + } + + $dml_date_to = $date; + + // Define begin and end of date range for both logs + $data = array(); + if (strtotime($ddl_date_from) <= strtotime($dml_date_from)) { + $data['date_from'] = $ddl_date_from; + } else { + $data['date_from'] = $dml_date_from; + } + if (strtotime($ddl_date_to) >= strtotime($dml_date_to)) { + $data['date_to'] = $ddl_date_to; + } else { + $data['date_to'] = $dml_date_to; + } + $data['ddlog'] = $ddlog; + $data['dmlog'] = $dmlog; + $data['tracking'] = $mixed['tracking']; + $data['schema_snapshot'] = $mixed['schema_snapshot']; + + return $data; + } + + + /** + * Parses a query. Gets + * - statement identifier (UPDATE, ALTER TABLE, ...) + * - type of statement, is it part of DDL or DML ? + * - tablename + * + * @param string $query query + * + * @static + * @todo: using PMA SQL Parser when possible + * @todo: support multi-table/view drops + * + * @return mixed Array containing identifier, type and tablename. + * + */ + public static function parseQuery($query) + { + // Usage of PMA_SQP does not work here + // + // require_once("libraries/sqlparser.lib.php"); + // $parsed_sql = PMA_SQP_parse($query); + // $sql_info = PMA_SQP_analyze($parsed_sql); + + $parser = new Parser($query); + + $tokens = $parser->list->tokens; + + // Parse USE statement, need it for SQL dump imports + if ($tokens[0]->value == 'USE') { + $GLOBALS['db'] = $tokens[2]->value; + } + + $result = array(); + + if (!empty($parser->statements)) { + $statement = $parser->statements[0]; + $options = isset($statement->options) ? $statement->options->options : null; + + /* + * DDL statements + */ + $result['type'] = 'DDL'; + + // Parse CREATE statement + if ($statement instanceof CreateStatement) { + if (empty($options) || !isset($options[6])) { + return $result; + } + + if ($options[6] == 'VIEW' || $options[6] == 'TABLE') { + $result['identifier'] = 'CREATE ' . $options[6]; + $result['tablename'] = $statement->name->table ; + } elseif ($options[6] == 'DATABASE') { + $result['identifier'] = 'CREATE DATABASE' ; + $result['tablename'] = '' ; + + // In case of CREATE DATABASE, table field of the CreateStatement is actually name of the database + $GLOBALS['db'] = $statement->name->table; + } elseif ($options[6] == 'INDEX' + || $options[6] == 'UNIQUE INDEX' + || $options[6] == 'FULLTEXT INDEX' + || $options[6] == 'SPATIAL INDEX' + ){ + $result['identifier'] = 'CREATE INDEX'; + + // In case of CREATE INDEX, we have to get the table name from body of the statement + $result['tablename'] = $statement->body[3]->value == '.' ? $statement->body[4]->value + : $statement->body[2]->value ; + } + } + + // Parse ALTER statement + elseif ($statement instanceof AlterStatement) { + if (empty($options) || !isset($options[3])) { + return $result; + } + + if ($options[3] == 'VIEW' || $options[3] == 'TABLE') { + $result['identifier'] = 'ALTER ' . $options[3] ; + $result['tablename'] = $statement->table->table ; + } elseif ($options[3] == 'DATABASE') { + $result['identifier'] = 'ALTER DATABASE' ; + $result['tablename'] = '' ; + + $GLOBALS['db'] = $statement->table->table ; + } + } + + // Parse DROP statement + elseif ($statement instanceof DropStatement) { + if (empty($options) || !isset($options[1])) { + return $result; + } + + if ($options[1] == 'VIEW' || $options[1] == 'TABLE') { + $result['identifier'] = 'DROP ' . $options[1] ; + $result['tablename'] = $statement->fields[0]->table; + } elseif ($options[1] == 'DATABASE') { + $result['identifier'] = 'DROP DATABASE' ; + $result['tablename'] = ''; + + $GLOBALS['db'] = $statement->fields[0]->table; + } elseif ($options[1] == 'INDEX') { + $result['identifier'] = 'DROP INDEX' ; + $result['tablename'] = $statement->table->table; + } + } + + // Prase RENAME statement + elseif ($statement instanceof RenameStatement) { + $result['identifier'] = 'RENAME TABLE'; + $result['tablename'] = $statement->renames[0]->old->table; + $result['tablename_after_rename'] = $statement->renames[0]->new->table; + } + + if (isset($result['identifier'])) { + return $result ; + } + + /* + * DML statements + */ + $result['type'] = 'DML'; + + // Parse UPDATE statement + if ($statement instanceof UpdateStatement) { + $result['identifier'] = 'UPDATE'; + $result['tablename'] = $statement->tables[0]->table; + } + + // Parse INSERT INTO statement + if ($statement instanceof InsertStatement) { + $result['identifier'] = 'INSERT'; + $result['tablename'] = $statement->into->dest->table; + } + + // Parse DELETE statement + if ($statement instanceof DeleteStatement) { + $result['identifier'] = 'DELETE'; + $result['tablename'] = $statement->from[0]->table; + } + + // Parse TRUNCATE statement + if ($statement instanceof TruncateStatement) { + $result['identifier'] = 'TRUNCATE' ; + $result['tablename'] = $statement->table->table; + } + } + + return $result; + } + + + /** + * Analyzes a given SQL statement and saves tracking data. + * + * @param string $query a SQL query + * + * @static + * + * @return void + */ + public static function handleQuery($query) + { + $relation = new Relation(); + + // If query is marked as untouchable, leave + if (mb_strstr($query, "/*NOTRACK*/")) { + return; + } + + if (! (substr($query, -1) == ';')) { + $query = $query . ";\n"; + } + // Get some information about query + $result = self::parseQuery($query); + + // Get database name + $dbname = trim(isset($GLOBALS['db']) ? $GLOBALS['db'] : '', '`'); + // $dbname can be empty, for example when coming from Synchronize + // and this is a query for the remote server + if (empty($dbname)) { + return; + } + + // If we found a valid statement + if (isset($result['identifier'])) { + if (! self::isTracked($dbname, $result['tablename'])) { + return; + } + + $version = self::getVersion( + $dbname, $result['tablename'], $result['identifier'] + ); + + // If version not exists and auto-creation is enabled + if ($GLOBALS['cfg']['Server']['tracking_version_auto_create'] == true + && $version == -1 + ) { + // Create the version + + switch ($result['identifier']) { + case 'CREATE TABLE': + self::createVersion($dbname, $result['tablename'], '1'); + break; + case 'CREATE VIEW': + self::createVersion( + $dbname, $result['tablename'], '1', '', true + ); + break; + case 'CREATE DATABASE': + self::createDatabaseVersion($dbname, '1', $query); + break; + } // end switch + } + + // If version exists + if ($version != -1) { + if ($result['type'] == 'DDL') { + $save_to = 'schema_sql'; + } elseif ($result['type'] == 'DML') { + $save_to = 'data_sql'; + } else { + $save_to = ''; + } + $date = Util::date('Y-m-d H:i:s'); + + // Cut off `dbname`. from query + $query = preg_replace( + '/`' . preg_quote($dbname, '/') . '`\s?\./', + '', + $query + ); + + // Add log information + $query = self::getLogComment() . $query ; + + // Mark it as untouchable + $sql_query = " /*NOTRACK*/\n" + . " UPDATE " . self::_getTrackingTable() + . " SET " . Util::backquote($save_to) + . " = CONCAT( " . Util::backquote($save_to) . ",'\n" + . $GLOBALS['dbi']->escapeString($query) . "') ," + . " `date_updated` = '" . $date . "' "; + + // If table was renamed we have to change + // the tablename attribute in pma_tracking too + if ($result['identifier'] == 'RENAME TABLE') { + $sql_query .= ', `table_name` = \'' + . $GLOBALS['dbi']->escapeString($result['tablename_after_rename']) + . '\' '; + } + + // Save the tracking information only for + // 1. the database + // 2. the table / view + // 3. the statements + // we want to track + $sql_query .= + " WHERE FIND_IN_SET('" . $result['identifier'] . "',tracking) > 0" . + " AND `db_name` = '" . $GLOBALS['dbi']->escapeString($dbname) . "' " . + " AND `table_name` = '" + . $GLOBALS['dbi']->escapeString($result['tablename']) . "' " . + " AND `version` = '" . $GLOBALS['dbi']->escapeString($version) . "' "; + + $relation->queryAsControlUser($sql_query); + } + } + } + + /** + * Returns the tracking table + * + * @return string tracking table + */ + private static function _getTrackingTable() + { + $relation = new Relation(); + $cfgRelation = $relation->getRelationsParam(); + return Util::backquote($cfgRelation['db']) + . '.' . Util::backquote($cfgRelation['tracking']); + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Tracking.php b/php/apps/phpmyadmin49/html/libraries/classes/Tracking.php new file mode 100644 index 00000000..82c8b25a --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Tracking.php @@ -0,0 +1,1274 @@ += $filter_ts_from + && $timestamp <= $filter_ts_to + && (in_array('*', $filter_users) || $filtered_user) + ) { + $tmp_entries[] = array( + 'id' => $id, + 'timestamp' => $timestamp, + 'username' => $entry['username'], + 'statement' => $entry['statement'] + ); + } + $id++; + } + return($tmp_entries); + } + + /** + * Function to get html for data definition and data manipulation statements + * + * @param string $urlQuery url query + * @param int $lastVersion last version + * @param string $db database + * @param array $selected selected tables + * @param string $type type of the table; table, view or both + * + * @return string HTML + */ + public static function getHtmlForDataDefinitionAndManipulationStatements( + $urlQuery, + $lastVersion, + $db, + array $selected, + $type = 'both' + ) { + return Template::get('table/tracking/create_version')->render([ + 'url_query' => $urlQuery, + 'last_version' => $lastVersion, + 'db' => $db, + 'selected' => $selected, + 'type' => $type, + 'default_statements' => $GLOBALS['cfg']['Server']['tracking_default_statements'], + ]); + } + + /** + * Function to get html for activate/deactivate tracking + * + * @param string $action activate|deactivate + * @param string $urlQuery url query + * @param int $lastVersion last version + * + * @return string HTML + */ + public static function getHtmlForActivateDeactivateTracking( + $action, + $urlQuery, + $lastVersion + ) { + return Template::get('table/tracking/activate_deactivate')->render([ + 'action' => $action, + 'url_query' => $urlQuery, + 'last_version' => $lastVersion, + 'db' => $GLOBALS['db'], + 'table' => $GLOBALS['table'], + ]); + } + + /** + * Function to get the list versions of the table + * + * @return array + */ + public static function getListOfVersionsOfTable() + { + $relation = new Relation(); + $cfgRelation = $relation->getRelationsParam(); + $sql_query = " SELECT * FROM " . + Util::backquote($cfgRelation['db']) . "." . + Util::backquote($cfgRelation['tracking']) . + " WHERE db_name = '" . $GLOBALS['dbi']->escapeString($GLOBALS['db']) . + "' " . + " AND table_name = '" . + $GLOBALS['dbi']->escapeString($GLOBALS['table']) . "' " . + " ORDER BY version DESC "; + + return $relation->queryAsControlUser($sql_query); + } + + /** + * Function to get html for displaying last version number + * + * @param array $sql_result sql result + * @param int $last_version last version + * @param array $url_params url parameters + * @param string $url_query url query + * @param string $pmaThemeImage path to theme's image folder + * @param string $text_dir text direction + * + * @return string + */ + public static function getHtmlForTableVersionDetails( + $sql_result, $last_version, array $url_params, + $url_query, $pmaThemeImage, $text_dir + ) { + $tracking_active = false; + + $html = '
    '; + $html .= Url::getHiddenInputs($GLOBALS['db'], $GLOBALS['table']); + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + + $GLOBALS['dbi']->dataSeek($sql_result, 0); + $delete = Util::getIcon('b_drop', __('Delete version')); + $report = Util::getIcon('b_report', __('Tracking report')); + $structure = Util::getIcon('b_props', __('Structure snapshot')); + + while ($version = $GLOBALS['dbi']->fetchArray($sql_result)) { + if ($version['version'] == $last_version) { + if ($version['tracking_active'] == 1) { + $tracking_active = true; + } else { + $tracking_active = false; + } + } + $checkbox_id = 'selected_versions_' . htmlspecialchars($version['version']); + + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + } + + $html .= ''; + $html .= '
    ' . __('Version') . '' . __('Created') . '' . __('Updated') . '' . __('Status') . '' . __('Action') . '' . __('Show') . '
    '; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= '' . htmlspecialchars($version['date_created']) . '' . htmlspecialchars($version['date_updated']) . '' . self::getVersionStatus($version) . '' . $delete . '' . $report . ''; + $html .= '  '; + $html .= '' . $structure . ''; + $html .= '
    '; + + $html .= Template::get('select_all') + ->render( + array( + 'pma_theme_image' => $pmaThemeImage, + 'text_dir' => $text_dir, + 'form_name' => 'versionsForm', + ) + ); + $html .= Util::getButtonOrImage( + 'submit_mult', 'mult_submit', + __('Delete version'), 'b_drop', 'delete_version' + ); + + $html .= '
    '; + + if ($tracking_active) { + $html .= self::getHtmlForActivateDeactivateTracking( + 'deactivate', $url_query, $last_version + ); + } else { + $html .= self::getHtmlForActivateDeactivateTracking( + 'activate', $url_query, $last_version + ); + } + + return $html; + } + + /** + * Function to get the last version number of a table + * + * @param array $sql_result sql result + * + * @return int + */ + public static function getTableLastVersionNumber($sql_result) + { + $maxversion = $GLOBALS['dbi']->fetchArray($sql_result); + return intval(is_array($maxversion) ? $maxversion['version'] : null); + } + + /** + * Function to get sql results for selectable tables + * + * @return array + */ + public static function getSqlResultForSelectableTables() + { + $relation = new Relation(); + $cfgRelation = $relation->getRelationsParam(); + + $sql_query = " SELECT DISTINCT db_name, table_name FROM " . + Util::backquote($cfgRelation['db']) . "." . + Util::backquote($cfgRelation['tracking']) . + " WHERE db_name = '" . $GLOBALS['dbi']->escapeString($GLOBALS['db']) . + "' " . + " ORDER BY db_name, table_name"; + + return $relation->queryAsControlUser($sql_query); + } + + /** + * Function to get html for selectable table rows + * + * @param array $selectableTablesSqlResult sql results for selectable rows + * @param string $urlQuery url query + * + * @return string + */ + public static function getHtmlForSelectableTables( + $selectableTablesSqlResult, + $urlQuery + ) { + $entries = []; + while ($entry = $GLOBALS['dbi']->fetchArray($selectableTablesSqlResult)) { + $entry['is_tracked'] = Tracker::isTracked( + $entry['db_name'], + $entry['table_name'] + ); + $entries[] = $entry; + } + + return Template::get('table/tracking/selectable_tables')->render([ + 'url_query' => $urlQuery, + 'db' => $GLOBALS['db'], + 'table' => $GLOBALS['table'], + 'entries' => $entries, + 'selected_table' => isset($_POST['table']) ? $_POST['table'] : null, + ]); + } + + /** + * Function to get html for tracking report and tracking report export + * + * @param string $url_query url query + * @param array $data data + * @param array $url_params url params + * @param boolean $selection_schema selection schema + * @param boolean $selection_data selection data + * @param boolean $selection_both selection both + * @param int $filter_ts_to filter time stamp from + * @param int $filter_ts_from filter time stamp tp + * @param array $filter_users filter users + * + * @return string + */ + public static function getHtmlForTrackingReport($url_query, array $data, array $url_params, + $selection_schema, $selection_data, $selection_both, $filter_ts_to, + $filter_ts_from, array $filter_users + ) { + $html = '

    ' . __('Tracking report') + . ' [' . __('Close') + . ']

    '; + + $html .= '' . __('Tracking statements') . ' ' + . htmlspecialchars($data['tracking']) . '
    '; + $html .= '
    '; + + list($str1, $str2, $str3, $str4, $str5) = self::getHtmlForElementsOfTrackingReport( + $selection_schema, $selection_data, $selection_both + ); + + // Prepare delete link content here + $drop_image_or_text = ''; + if (Util::showIcons('ActionLinksMode')) { + $drop_image_or_text .= Util::getImage( + 'b_drop', __('Delete tracking data row from report') + ); + } + if (Util::showText('ActionLinksMode')) { + $drop_image_or_text .= __('Delete'); + } + + /* + * First, list tracked data definition statements + */ + if (count($data['ddlog']) == 0 && count($data['dmlog']) == 0) { + $msg = Message::notice(__('No data')); + $msg->display(); + } + + $html .= self::getHtmlForTrackingReportExportForm1( + $data, $url_params, $selection_schema, $selection_data, $selection_both, + $filter_ts_to, $filter_ts_from, $filter_users, $str1, $str2, $str3, + $str4, $str5, $drop_image_or_text + ); + + $html .= self::getHtmlForTrackingReportExportForm2( + $url_params, $str1, $str2, $str3, $str4, $str5 + ); + + $html .= "



    \n"; + + return $html; + } + + /** + * Generate HTML element for report form + * + * @param boolean $selection_schema selection schema + * @param boolean $selection_data selection data + * @param boolean $selection_both selection both + * + * @return array + */ + public static function getHtmlForElementsOfTrackingReport( + $selection_schema, $selection_data, $selection_both + ) { + $str1 = ''; + $str2 = ''; + $str3 = ''; + $str4 = ''; + $str5 = '' + . ''; + return array($str1, $str2, $str3, $str4, $str5); + } + + /** + * Generate HTML for export form + * + * @param array $data data + * @param array $url_params url params + * @param boolean $selection_schema selection schema + * @param boolean $selection_data selection data + * @param boolean $selection_both selection both + * @param int $filter_ts_to filter time stamp from + * @param int $filter_ts_from filter time stamp tp + * @param array $filter_users filter users + * @param string $str1 HTML for logtype select + * @param string $str2 HTML for "from date" + * @param string $str3 HTML for "to date" + * @param string $str4 HTML for user + * @param string $str5 HTML for "list report" + * @param string $drop_image_or_text HTML for image or text + * + * @return string HTML for form + */ + public static function getHtmlForTrackingReportExportForm1( + array $data, array $url_params, $selection_schema, $selection_data, $selection_both, + $filter_ts_to, $filter_ts_from, array $filter_users, $str1, $str2, $str3, + $str4, $str5, $drop_image_or_text + ) { + $ddlog_count = 0; + + $html = '
    '; + $html .= Url::getHiddenInputs($url_params + [ + 'report' => 'true', + 'version' => $_POST['version'], + ]); + + $html .= sprintf( + __('Show %1$s with dates from %2$s to %3$s by user %4$s %5$s'), + $str1, $str2, $str3, $str4, $str5 + ); + + if ($selection_schema || $selection_both && count($data['ddlog']) > 0) { + list($temp, $ddlog_count) = self::getHtmlForDataDefinitionStatements( + $data, $filter_users, $filter_ts_from, $filter_ts_to, $url_params, + $drop_image_or_text + ); + $html .= $temp; + unset($temp); + } //endif + + /* + * Secondly, list tracked data manipulation statements + */ + if (($selection_data || $selection_both) && count($data['dmlog']) > 0) { + $html .= self::getHtmlForDataManipulationStatements( + $data, $filter_users, $filter_ts_from, $filter_ts_to, $url_params, + $ddlog_count, $drop_image_or_text + ); + } + $html .= '
    '; + return $html; + } + + /** + * Generate HTML for export form + * + * @param array $url_params Parameters + * @param string $str1 HTML for logtype select + * @param string $str2 HTML for "from date" + * @param string $str3 HTML for "to date" + * @param string $str4 HTML for user + * @param string $str5 HTML for "list report" + * + * @return string HTML for form + */ + public static function getHtmlForTrackingReportExportForm2( + array $url_params, $str1, $str2, $str3, $str4, $str5 + ) { + $html = '
    '; + $html .= Url::getHiddenInputs($url_params + [ + 'report' => 'true', + 'version' => $_POST['version'], + ]); + + $html .= sprintf( + __('Show %1$s with dates from %2$s to %3$s by user %4$s %5$s'), + $str1, $str2, $str3, $str4, $str5 + ); + $html .= '
    '; + + $html .= '
    '; + $html .= Url::getHiddenInputs($url_params + [ + 'report' => 'true', + 'version' => $_POST['version'], + 'logtype' => $_POST['logtype'], + 'date_from' => $_POST['date_from'], + 'date_to' => $_POST['date_to'], + 'users' => $_POST['users'], + 'report_export' => 'true', + ]); + + $str_export1 = ''; + + $str_export2 = ''; + + $html .= "
    " . sprintf(__('Export as %s'), $str_export1) + . $str_export2 . "
    "; + $html .= '
    '; + return $html; + } + + /** + * Function to get html for data manipulation statements + * + * @param array $data data + * @param array $filter_users filter users + * @param int $filter_ts_from filter time staml from + * @param int $filter_ts_to filter time stamp to + * @param array $url_params url parameters + * @param int $ddlog_count data definition log count + * @param string $drop_image_or_text drop image or text + * + * @return string + */ + public static function getHtmlForDataManipulationStatements(array $data, array $filter_users, + $filter_ts_from, $filter_ts_to, array $url_params, $ddlog_count, + $drop_image_or_text + ) { + // no need for the secondth returned parameter + list($html,) = self::getHtmlForDataStatements( + $data, $filter_users, $filter_ts_from, $filter_ts_to, $url_params, + $drop_image_or_text, 'dmlog', __('Data manipulation statement'), + $ddlog_count, 'dml_versions' + ); + + return $html; + } + + /** + * Function to get html for data definition statements in schema snapshot + * + * @param array $data data + * @param array $filter_users filter users + * @param int $filter_ts_from filter time stamp from + * @param int $filter_ts_to filter time stamp to + * @param array $url_params url parameters + * @param string $drop_image_or_text drop image or text + * + * @return array + */ + public static function getHtmlForDataDefinitionStatements(array $data, array $filter_users, + $filter_ts_from, $filter_ts_to, array $url_params, $drop_image_or_text + ) { + list($html, $line_number) = self::getHtmlForDataStatements( + $data, $filter_users, $filter_ts_from, $filter_ts_to, $url_params, + $drop_image_or_text, 'ddlog', __('Data definition statement'), + 1, 'ddl_versions' + ); + + return array($html, $line_number); + } + + /** + * Function to get html for data statements in schema snapshot + * + * @param array $data data + * @param array $filterUsers filter users + * @param int $filterTsFrom filter time stamp from + * @param int $filterTsTo filter time stamp to + * @param array $urlParams url parameters + * @param string $dropImageOrText drop image or text + * @param string $whichLog dmlog|ddlog + * @param string $headerMessage message for this section + * @param int $lineNumber line number + * @param string $tableId id for the table element + * + * @return array [$html, $lineNumber] + */ + private static function getHtmlForDataStatements( + array $data, + array $filterUsers, + $filterTsFrom, + $filterTsTo, + array $urlParams, + $dropImageOrText, + $whichLog, + $headerMessage, + $lineNumber, + $tableId + ) { + $offset = $lineNumber; + $entries = []; + foreach ($data[$whichLog] as $entry) { + $timestamp = strtotime($entry['date']); + if ($timestamp >= $filterTsFrom + && $timestamp <= $filterTsTo + && (in_array('*', $filterUsers) + || in_array($entry['username'], $filterUsers)) + ) { + $entry['formated_statement'] = Util::formatSql($entry['statement'], true); + $deleteParam = 'delete_' . $whichLog; + $entry['url_params'] = Url::getCommon($urlParams + [ + 'report' => 'true', + 'version' => $_POST['version'], + $deleteParam => ($lineNumber - $offset), + ], ''); + $entry['line_number'] = $lineNumber; + $entries[] = $entry; + } + $lineNumber++; + } + + $html = Template::get('table/tracking/report_table')->render([ + 'table_id' => $tableId, + 'header_message' => $headerMessage, + 'entries' => $entries, + 'drop_image_or_text' => $dropImageOrText, + ]); + + return [$html, $lineNumber]; + } + + /** + * Function to get html for schema snapshot + * + * @param string $url_query url query + * + * @return string + */ + public static function getHtmlForSchemaSnapshot($url_query) + { + $html = '

    ' . __('Structure snapshot') + . ' [' . __('Close') + . ']

    '; + $data = Tracker::getTrackedData( + $_POST['db'], $_POST['table'], $_POST['version'] + ); + + // Get first DROP TABLE/VIEW and CREATE TABLE/VIEW statements + $drop_create_statements = $data['ddlog'][0]['statement']; + + if (mb_strstr($data['ddlog'][0]['statement'], 'DROP TABLE') + || mb_strstr($data['ddlog'][0]['statement'], 'DROP VIEW') + ) { + $drop_create_statements .= $data['ddlog'][1]['statement']; + } + // Print SQL code + $html .= Util::getMessage( + sprintf( + __('Version %s snapshot (SQL code)'), + htmlspecialchars($_POST['version']) + ), + $drop_create_statements + ); + + // Unserialize snapshot + $temp = Core::safeUnserialize($data['schema_snapshot']); + if ($temp === null) { + $temp = array('COLUMNS' => array(), 'INDEXES' => array()); + } + $columns = $temp['COLUMNS']; + $indexes = $temp['INDEXES']; + $html .= self::getHtmlForColumns($columns); + + if (count($indexes) > 0) { + $html .= self::getHtmlForIndexes($indexes); + } // endif + $html .= '


    '; + + return $html; + } + + /** + * Function to get html for displaying columns in the schema snapshot + * + * @param array $columns columns + * + * @return string + */ + public static function getHtmlForColumns(array $columns) + { + return Template::get('table/tracking/structure_snapshot_columns')->render([ + 'columns' => $columns, + ]); + } + + /** + * Function to get html for the indexes in schema snapshot + * + * @param array $indexes indexes + * + * @return string + */ + public static function getHtmlForIndexes(array $indexes) + { + return Template::get('table/tracking/structure_snapshot_indexes')->render([ + 'indexes' => $indexes, + ]);; + } + + /** + * Function to handle the tracking report + * + * @param array &$data tracked data + * + * @return string HTML for the message + */ + public static function deleteTrackingReportRows(array &$data) + { + $html = ''; + if (isset($_POST['delete_ddlog'])) { + // Delete ddlog row data + $html .= self::deleteFromTrackingReportLog( + $data, + 'ddlog', + 'DDL', + __('Tracking data definition successfully deleted') + ); + } + + if (isset($_POST['delete_dmlog'])) { + // Delete dmlog row data + $html .= self::deleteFromTrackingReportLog( + $data, + 'dmlog', + 'DML', + __('Tracking data manipulation successfully deleted') + ); + } + return $html; + } + + /** + * Function to delete from a tracking report log + * + * @param array &$data tracked data + * @param string $which_log ddlog|dmlog + * @param string $type DDL|DML + * @param string $message success message + * + * @return string HTML for the message + */ + public static function deleteFromTrackingReportLog(array &$data, $which_log, $type, $message) + { + $html = ''; + $delete_id = $_POST['delete_' . $which_log]; + + // Only in case of valid id + if ($delete_id == (int)$delete_id) { + unset($data[$which_log][$delete_id]); + + $successfullyDeleted = Tracker::changeTrackingData( + $GLOBALS['db'], + $GLOBALS['table'], + $_POST['version'], + $type, + $data[$which_log] + ); + if ($successfullyDeleted) { + $msg = Message::success($message); + } else { + $msg = Message::rawError(__('Query error')); + } + $html .= $msg->getDisplay(); + } + return $html; + } + + /** + * Function to export as sql dump + * + * @param array $entries entries + * + * @return string HTML SQL query form + */ + public static function exportAsSqlDump(array $entries) + { + $html = ''; + $new_query = "# " + . __( + 'You can execute the dump by creating and using a temporary database. ' + . 'Please ensure that you have the privileges to do so.' + ) + . "\n" + . "# " . __('Comment out these two lines if you do not need them.') . "\n" + . "\n" + . "CREATE database IF NOT EXISTS pma_temp_db; \n" + . "USE pma_temp_db; \n" + . "\n"; + + foreach ($entries as $entry) { + $new_query .= $entry['statement']; + } + $msg = Message::success( + __('SQL statements exported. Please copy the dump or execute it.') + ); + $html .= $msg->getDisplay(); + + $db_temp = $GLOBALS['db']; + $table_temp = $GLOBALS['table']; + + $GLOBALS['db'] = $GLOBALS['table'] = ''; + + $html .= SqlQueryForm::getHtml($new_query, 'sql'); + + $GLOBALS['db'] = $db_temp; + $GLOBALS['table'] = $table_temp; + + return $html; + } + + /** + * Function to export as sql execution + * + * @param array $entries entries + * + * @return array + */ + public static function exportAsSqlExecution(array $entries) + { + $sql_result = array(); + foreach ($entries as $entry) { + $sql_result = $GLOBALS['dbi']->query("/*NOTRACK*/\n" . $entry['statement']); + } + + return $sql_result; + } + + /** + * Function to export as entries + * + * @param array $entries entries + * + * @return void + */ + public static function exportAsFileDownload(array $entries) + { + ini_set('url_rewriter.tags', ''); + + // Replace all multiple whitespaces by a single space + $table = htmlspecialchars(preg_replace('/\s+/', ' ', $_POST['table'])); + $dump = "# " . sprintf( + __('Tracking report for table `%s`'), $table + ) + . "\n" . "# " . date('Y-m-d H:i:s') . "\n"; + foreach ($entries as $entry) { + $dump .= $entry['statement']; + } + $filename = 'log_' . $table . '.sql'; + Response::getInstance()->disable(); + Core::downloadHeader( + $filename, + 'text/x-sql', + strlen($dump) + ); + echo $dump; + + exit(); + } + + /** + * Function to activate or deactivate tracking + * + * @param string $action activate|deactivate + * + * @return string HTML for the success message + */ + public static function changeTracking($action) + { + $html = ''; + if ($action == 'activate') { + $method = 'activateTracking'; + $message = __('Tracking for %1$s was activated at version %2$s.'); + } else { + $method = 'deactivateTracking'; + $message = __('Tracking for %1$s was deactivated at version %2$s.'); + } + $status = Tracker::$method( + $GLOBALS['db'], $GLOBALS['table'], $_POST['version'] + ); + if ($status) { + $msg = Message::success( + sprintf( + $message, + htmlspecialchars($GLOBALS['db'] . '.' . $GLOBALS['table']), + htmlspecialchars($_POST['version']) + ) + ); + $html .= $msg->getDisplay(); + } + + return $html; + } + + /** + * Function to get tracking set + * + * @return string + */ + public static function getTrackingSet() + { + $tracking_set = ''; + + // a key is absent from the request if it has been removed from + // tracking_default_statements in the config + if (isset($_POST['alter_table']) && $_POST['alter_table'] == true) { + $tracking_set .= 'ALTER TABLE,'; + } + if (isset($_POST['rename_table']) && $_POST['rename_table'] == true) { + $tracking_set .= 'RENAME TABLE,'; + } + if (isset($_POST['create_table']) && $_POST['create_table'] == true) { + $tracking_set .= 'CREATE TABLE,'; + } + if (isset($_POST['drop_table']) && $_POST['drop_table'] == true) { + $tracking_set .= 'DROP TABLE,'; + } + if (isset($_POST['alter_view']) && $_POST['alter_view'] == true) { + $tracking_set .= 'ALTER VIEW,'; + } + if (isset($_POST['create_view']) && $_POST['create_view'] == true) { + $tracking_set .= 'CREATE VIEW,'; + } + if (isset($_POST['drop_view']) && $_POST['drop_view'] == true) { + $tracking_set .= 'DROP VIEW,'; + } + if (isset($_POST['create_index']) && $_POST['create_index'] == true) { + $tracking_set .= 'CREATE INDEX,'; + } + if (isset($_POST['drop_index']) && $_POST['drop_index'] == true) { + $tracking_set .= 'DROP INDEX,'; + } + if (isset($_POST['insert']) && $_POST['insert'] == true) { + $tracking_set .= 'INSERT,'; + } + if (isset($_POST['update']) && $_POST['update'] == true) { + $tracking_set .= 'UPDATE,'; + } + if (isset($_POST['delete']) && $_POST['delete'] == true) { + $tracking_set .= 'DELETE,'; + } + if (isset($_POST['truncate']) && $_POST['truncate'] == true) { + $tracking_set .= 'TRUNCATE,'; + } + $tracking_set = rtrim($tracking_set, ','); + + return $tracking_set; + } + + /** + * Deletes a tracking version + * + * @param string $version tracking version + * + * @return string HTML of the success message + */ + public static function deleteTrackingVersion($version) + { + $html = ''; + $versionDeleted = Tracker::deleteTracking( + $GLOBALS['db'], + $GLOBALS['table'], + $version + ); + if ($versionDeleted) { + $msg = Message::success( + sprintf( + __('Version %1$s of %2$s was deleted.'), + htmlspecialchars($version), + htmlspecialchars($GLOBALS['db'] . '.' . $GLOBALS['table']) + ) + ); + $html .= $msg->getDisplay(); + } + + return $html; + } + + /** + * Function to create the tracking version + * + * @return string HTML of the success message + */ + public static function createTrackingVersion() + { + $html = ''; + $tracking_set = self::getTrackingSet(); + + $versionCreated = Tracker::createVersion( + $GLOBALS['db'], + $GLOBALS['table'], + $_POST['version'], + $tracking_set, + $GLOBALS['dbi']->getTable($GLOBALS['db'], $GLOBALS['table'])->isView() + ); + if ($versionCreated) { + $msg = Message::success( + sprintf( + __('Version %1$s was created, tracking for %2$s is active.'), + htmlspecialchars($_POST['version']), + htmlspecialchars($GLOBALS['db'] . '.' . $GLOBALS['table']) + ) + ); + $html .= $msg->getDisplay(); + } + + return $html; + } + + /** + * Create tracking version for multiple tables + * + * @param array $selected list of selected tables + * + * @return void + */ + public static function createTrackingForMultipleTables(array $selected) + { + $tracking_set = self::getTrackingSet(); + + foreach ($selected as $selected_table) { + Tracker::createVersion( + $GLOBALS['db'], + $selected_table, + $_POST['version'], + $tracking_set, + $GLOBALS['dbi']->getTable($GLOBALS['db'], $selected_table)->isView() + ); + } + } + + /** + * Function to get the entries + * + * @param array $data data + * @param int $filter_ts_from filter time stamp from + * @param int $filter_ts_to filter time stamp to + * @param array $filter_users filter users + * + * @return array + */ + public static function getEntries(array $data, $filter_ts_from, $filter_ts_to, array $filter_users) + { + $entries = array(); + // Filtering data definition statements + if ($_POST['logtype'] == 'schema' + || $_POST['logtype'] == 'schema_and_data' + ) { + $entries = array_merge( + $entries, + self::filterTracking( + $data['ddlog'], $filter_ts_from, $filter_ts_to, $filter_users + ) + ); + } + + // Filtering data manipulation statements + if ($_POST['logtype'] == 'data' + || $_POST['logtype'] == 'schema_and_data' + ) { + $entries = array_merge( + $entries, + self::filterTracking( + $data['dmlog'], $filter_ts_from, $filter_ts_to, $filter_users + ) + ); + } + + // Sort it + $ids = $timestamps = $usernames = $statements = array(); + foreach ($entries as $key => $row) { + $ids[$key] = $row['id']; + $timestamps[$key] = $row['timestamp']; + $usernames[$key] = $row['username']; + $statements[$key] = $row['statement']; + } + + array_multisort( + $timestamps, SORT_ASC, $ids, SORT_ASC, $usernames, + SORT_ASC, $statements, SORT_ASC, $entries + ); + + return $entries; + } + + /** + * Function to get version status + * + * @param array $version version info + * + * @return string $version_status The status message + */ + public static function getVersionStatus(array $version) + { + if ($version['tracking_active'] == 1) { + return __('active'); + } + + return __('not active'); + } + + /** + * Get HTML for untracked tables + * + * @param string $db current database + * @param array $untrackedTables untracked tables + * @param string $urlQuery url query string + * @param string $pmaThemeImage path to theme's image folder + * @param string $textDir text direction + * + * @return string HTML + */ + public static function getHtmlForUntrackedTables( + $db, + array $untrackedTables, + $urlQuery, + $pmaThemeImage, + $textDir + ) { + return Template::get('database/tracking/untracked_tables')->render([ + 'db' => $db, + 'untracked_tables' => $untrackedTables, + 'url_query' => $urlQuery, + 'pma_theme_image' => $pmaThemeImage, + 'text_dir' => $textDir, + ]); + } + + /** + * Helper function: Recursive function for getting table names from $table_list + * + * @param array $table_list Table list + * @param string $db Current database + * @param boolean $testing Testing + * + * @return array $untracked_tables + */ + public static function extractTableNames(array $table_list, $db, $testing = false) + { + $untracked_tables = array(); + $sep = $GLOBALS['cfg']['NavigationTreeTableSeparator']; + + foreach ($table_list as $key => $value) { + if (is_array($value) && array_key_exists(('is' . $sep . 'group'), $value) + && $value['is' . $sep . 'group'] + ) { + $untracked_tables = array_merge(self::extractTableNames($value, $db), $untracked_tables); //Recursion step + } + else { + if (is_array($value) && ($testing || Tracker::getVersion($db, $value['Name']) == -1)) { + $untracked_tables[] = $value['Name']; + } + } + } + return $untracked_tables; + } + + + /** + * Get untracked tables + * + * @param string $db current database + * + * @return array $untracked_tables + */ + public static function getUntrackedTables($db) + { + $table_list = Util::getTableList($db); + $untracked_tables = self::extractTableNames($table_list, $db); //Use helper function to get table list recursively. + return $untracked_tables; + } + + /** + * Get tracked tables + * + * @param string $db current database + * @param object $allTablesResult result set of tracked tables + * @param string $urlQuery url query string + * @param string $pmaThemeImage path to theme's image folder + * @param string $textDir text direction + * @param array $cfgRelation configuration storage info + * + * @return string HTML + */ + public static function getHtmlForTrackedTables( + $db, + $allTablesResult, + $urlQuery, + $pmaThemeImage, + $textDir, + array $cfgRelation + ) { + $relation = new Relation(); + $versions = []; + while ($oneResult = $GLOBALS['dbi']->fetchArray($allTablesResult)) { + list($tableName, $versionNumber) = $oneResult; + $tableQuery = ' SELECT * FROM ' . + Util::backquote($cfgRelation['db']) . '.' . + Util::backquote($cfgRelation['tracking']) . + ' WHERE `db_name` = \'' + . $GLOBALS['dbi']->escapeString($GLOBALS['db']) + . '\' AND `table_name` = \'' + . $GLOBALS['dbi']->escapeString($tableName) + . '\' AND `version` = \'' . $versionNumber . '\''; + + $tableResult = $relation->queryAsControlUser($tableQuery); + $versionData = $GLOBALS['dbi']->fetchArray($tableResult); + $versionData['status_button'] = self::getStatusButton( + $versionData, + $urlQuery + ); + $versions[] = $versionData; + } + return Template::get('database/tracking/tracked_tables')->render([ + 'db' => $db, + 'versions' => $versions, + 'text_dir' => $textDir, + 'pma_theme_image' => $pmaThemeImage, + ]); + } + + /** + * Get tracking status button + * + * @param array $versionData data about tracking versions + * @param string $urlQuery url query string + * + * @return string HTML + */ + private static function getStatusButton(array $versionData, $urlQuery) + { + $state = self::getVersionStatus($versionData); + $options = array( + 0 => array( + 'label' => __('not active'), + 'value' => 'deactivate_now', + 'selected' => ($state != 'active') + ), + 1 => array( + 'label' => __('active'), + 'value' => 'activate_now', + 'selected' => ($state == 'active') + ) + ); + $link = 'tbl_tracking.php' . $urlQuery . '&table=' + . htmlspecialchars($versionData['table_name']) + . '&version=' . $versionData['version']; + + return Util::toggleButton( + $link, + 'toggle_activation', + $options, + null + ); + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Transformations.php b/php/apps/phpmyadmin49/html/libraries/classes/Transformations.php new file mode 100644 index 00000000..9c527500 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Transformations.php @@ -0,0 +1,469 @@ + + * getOptions("'option ,, quoted',abd,'2,3',"); + * // array { + * // 'option ,, quoted', + * // 'abc', + * // '2,3', + * // '', + * // } + *
    + * + * @param string $option_string comma separated options + * + * @return array options + */ + public static function getOptions($option_string) + { + $result = array(); + + if (strlen($option_string) === 0 + || ! $transform_options = preg_split('/,/', $option_string) + ) { + return $result; + } + + while (($option = array_shift($transform_options)) !== null) { + $trimmed = trim($option); + if (strlen($trimmed) > 1 + && $trimmed[0] == "'" + && $trimmed[strlen($trimmed) - 1] == "'" + ) { + // '...' + $option = mb_substr($trimmed, 1, -1); + } elseif (isset($trimmed[0]) && $trimmed[0] == "'") { + // '..., + $trimmed = ltrim($option); + while (($option = array_shift($transform_options)) !== null) { + // ..., + $trimmed .= ',' . $option; + $rtrimmed = rtrim($trimmed); + if ($rtrimmed[strlen($rtrimmed) - 1] == "'") { + // ,...' + break; + } + } + $option = mb_substr($rtrimmed, 1, -1); + } + $result[] = stripslashes($option); + } + + return $result; + } + + /** + * Gets all available MIME-types + * + * @access public + * @staticvar array mimetypes + * @return array array[mimetype], array[transformation] + */ + public static function getAvailableMIMEtypes() + { + static $stack = null; + + if (null !== $stack) { + return $stack; + } + + $stack = array(); + $sub_dirs = array( + 'Input/' => 'input_', + 'Output/' => '', + '' => '' + ); + + foreach ($sub_dirs as $sd => $prefix) { + $handle = opendir('libraries/classes/Plugins/Transformations/' . $sd); + + if (! $handle) { + $stack[$prefix . 'transformation'] = array(); + $stack[$prefix . 'transformation_file'] = array(); + continue; + } + + $filestack = array(); + while ($file = readdir($handle)) { + // Ignore hidden files + if ($file[0] == '.') { + continue; + } + // Ignore old plugins (.class in filename) + if (strpos($file, '.class') !== false) { + continue; + } + $filestack[] = $file; + } + + closedir($handle); + sort($filestack); + + foreach ($filestack as $file) { + if (preg_match('|^[^.].*_.*_.*\.php$|', $file)) { + // File contains transformation functions. + $parts = explode('_', str_replace('.php', '', $file)); + $mimetype = $parts[0] . "/" . $parts[1]; + $stack['mimetype'][$mimetype] = $mimetype; + + $stack[$prefix . 'transformation'][] = $mimetype . ': ' . $parts[2]; + $stack[$prefix . 'transformation_file'][] = $sd . $file; + if ($sd === '') { + $stack['input_transformation'][] = $mimetype . ': ' . $parts[2]; + $stack['input_transformation_file'][] = $sd . $file; + } + + } elseif (preg_match('|^[^.].*\.php$|', $file)) { + // File is a plain mimetype, no functions. + $base = str_replace('.php', '', $file); + + if ($base != 'global') { + $mimetype = str_replace('_', '/', $base); + $stack['mimetype'][$mimetype] = $mimetype; + $stack['empty_mimetype'][$mimetype] = $mimetype; + } + } + } + } + return $stack; + } + + /** + * Returns the class name of the transformation + * + * @param string $filename transformation file name + * + * @return string the class name of transformation + */ + public static function getClassName($filename) + { + // get the transformation class name + $class_name = explode(".php", $filename); + $class_name = 'PhpMyAdmin\\' . str_replace('/', '\\', mb_substr($class_name[0], 18)); + + return $class_name; + } + + /** + * Returns the description of the transformation + * + * @param string $file transformation file + * + * @return string the description of the transformation + */ + public static function getDescription($file) + { + $include_file = 'libraries/classes/Plugins/Transformations/' . $file; + /* @var $class_name \PhpMyAdmin\Plugins\TransformationsInterface */ + $class_name = self::getClassName($include_file); + if (class_exists($class_name)) { + return $class_name::getInfo(); + } + return ''; + } + + /** + * Returns the name of the transformation + * + * @param string $file transformation file + * + * @return string the name of the transformation + */ + public static function getName($file) + { + $include_file = 'libraries/classes/Plugins/Transformations/' . $file; + /* @var $class_name \PhpMyAdmin\Plugins\TransformationsInterface */ + $class_name = self::getClassName($include_file); + if (class_exists($class_name)) { + return $class_name::getName(); + } + return ''; + } + + /** + * Fixups old MIME or transformation name to new one + * + * - applies some hardcoded fixups + * - adds spaces after _ and numbers + * - capitalizes words + * - removes back spaces + * + * @param string $value Value to fixup + * + * @return string + */ + static function fixupMIME($value) + { + $value = str_replace( + array("jpeg", "png"), array("JPEG", "PNG"), $value + ); + return str_replace( + ' ', + '', + ucwords( + preg_replace('/([0-9_]+)/', '$1 ', $value) + ) + ); + } + + /** + * Gets the mimetypes for all columns of a table + * + * @param string $db the name of the db to check for + * @param string $table the name of the table to check for + * @param boolean $strict whether to include only results having a mimetype set + * @param boolean $fullName whether to use full column names as the key + * + * @access public + * + * @return array [field_name][field_key] = field_value + */ + public static function getMIME($db, $table, $strict = false, $fullName = false) + { + $relation = new Relation(); + $cfgRelation = $relation->getRelationsParam(); + + if (! $cfgRelation['mimework']) { + return false; + } + + $com_qry = ''; + if ($fullName) { + $com_qry .= "SELECT CONCAT(" + . "`db_name`, '.', `table_name`, '.', `column_name`" + . ") AS column_name, "; + } else { + $com_qry = "SELECT `column_name`, "; + } + $com_qry .= '`mimetype`, + `transformation`, + `transformation_options`, + `input_transformation`, + `input_transformation_options` + FROM ' . Util::backquote($cfgRelation['db']) . '.' + . Util::backquote($cfgRelation['column_info']) . ' + WHERE `db_name` = \'' . $GLOBALS['dbi']->escapeString($db) . '\' + AND `table_name` = \'' . $GLOBALS['dbi']->escapeString($table) . '\' + AND ( `mimetype` != \'\'' . (!$strict ? ' + OR `transformation` != \'\' + OR `transformation_options` != \'\' + OR `input_transformation` != \'\' + OR `input_transformation_options` != \'\'' : '') . ')'; + $result = $GLOBALS['dbi']->fetchResult( + $com_qry, 'column_name', null, DatabaseInterface::CONNECT_CONTROL + ); + + foreach ($result as $column => $values) { + // convert mimetype to new format (f.e. Text_Plain, etc) + $delimiter_space = '- '; + $delimiter = "_"; + $values['mimetype'] = self::fixupMIME($values['mimetype']); + + // For transformation of form + // output/image_jpeg__inline.inc.php + // extract dir part. + $dir = explode('/', $values['transformation']); + $subdir = ''; + if (count($dir) === 2) { + $subdir = ucfirst($dir[0]) . '/'; + $values['transformation'] = $dir[1]; + } + + $values['transformation'] = self::fixupMIME($values['transformation']); + $values['transformation'] = $subdir . $values['transformation']; + $result[$column] = $values; + } + + return $result; + } // end of the 'getMIME()' function + + /** + * Set a single mimetype to a certain value. + * + * @param string $db the name of the db + * @param string $table the name of the table + * @param string $key the name of the column + * @param string $mimetype the mimetype of the column + * @param string $transformation the transformation of the column + * @param string $transformationOpts the transformation options of the column + * @param string $inputTransform the input transformation of the column + * @param string $inputTransformOpts the input transformation options of the column + * @param boolean $forcedelete force delete, will erase any existing + * comments for this column + * + * @access public + * + * @return boolean true, if comment-query was made. + */ + public static function setMIME($db, $table, $key, $mimetype, $transformation, + $transformationOpts, $inputTransform, $inputTransformOpts, $forcedelete = false + ) { + $relation = new Relation(); + $cfgRelation = $relation->getRelationsParam(); + + if (! $cfgRelation['mimework']) { + return false; + } + + // lowercase mimetype & transformation + $mimetype = mb_strtolower($mimetype); + $transformation = mb_strtolower($transformation); + + // Do we have any parameter to set? + $has_value = ( + strlen($mimetype) > 0 || + strlen($transformation) > 0 || + strlen($transformationOpts) > 0 || + strlen($inputTransform) > 0 || + strlen($inputTransformOpts) > 0 + ); + + $test_qry = ' + SELECT `mimetype`, + `comment` + FROM ' . Util::backquote($cfgRelation['db']) . '.' + . Util::backquote($cfgRelation['column_info']) . ' + WHERE `db_name` = \'' . $GLOBALS['dbi']->escapeString($db) . '\' + AND `table_name` = \'' . $GLOBALS['dbi']->escapeString($table) . '\' + AND `column_name` = \'' . $GLOBALS['dbi']->escapeString($key) . '\''; + + $test_rs = $relation->queryAsControlUser( + $test_qry, true, DatabaseInterface::QUERY_STORE + ); + + if ($test_rs && $GLOBALS['dbi']->numRows($test_rs) > 0) { + $row = @$GLOBALS['dbi']->fetchAssoc($test_rs); + $GLOBALS['dbi']->freeResult($test_rs); + + if (! $forcedelete && ($has_value || strlen($row['comment']) > 0)) { + $upd_query = 'UPDATE ' + . Util::backquote($cfgRelation['db']) . '.' + . Util::backquote($cfgRelation['column_info']) + . ' SET ' + . '`mimetype` = \'' + . $GLOBALS['dbi']->escapeString($mimetype) . '\', ' + . '`transformation` = \'' + . $GLOBALS['dbi']->escapeString($transformation) . '\', ' + . '`transformation_options` = \'' + . $GLOBALS['dbi']->escapeString($transformationOpts) . '\', ' + . '`input_transformation` = \'' + . $GLOBALS['dbi']->escapeString($inputTransform) . '\', ' + . '`input_transformation_options` = \'' + . $GLOBALS['dbi']->escapeString($inputTransformOpts) . '\''; + } else { + $upd_query = 'DELETE FROM ' + . Util::backquote($cfgRelation['db']) + . '.' . Util::backquote($cfgRelation['column_info']); + } + $upd_query .= ' + WHERE `db_name` = \'' . $GLOBALS['dbi']->escapeString($db) . '\' + AND `table_name` = \'' . $GLOBALS['dbi']->escapeString($table) + . '\' + AND `column_name` = \'' . $GLOBALS['dbi']->escapeString($key) + . '\''; + } elseif ($has_value) { + + $upd_query = 'INSERT INTO ' + . Util::backquote($cfgRelation['db']) + . '.' . Util::backquote($cfgRelation['column_info']) + . ' (db_name, table_name, column_name, mimetype, ' + . 'transformation, transformation_options, ' + . 'input_transformation, input_transformation_options) ' + . ' VALUES(' + . '\'' . $GLOBALS['dbi']->escapeString($db) . '\',' + . '\'' . $GLOBALS['dbi']->escapeString($table) . '\',' + . '\'' . $GLOBALS['dbi']->escapeString($key) . '\',' + . '\'' . $GLOBALS['dbi']->escapeString($mimetype) . '\',' + . '\'' . $GLOBALS['dbi']->escapeString($transformation) . '\',' + . '\'' . $GLOBALS['dbi']->escapeString($transformationOpts) . '\',' + . '\'' . $GLOBALS['dbi']->escapeString($inputTransform) . '\',' + . '\'' . $GLOBALS['dbi']->escapeString($inputTransformOpts) . '\')'; + } + + if (isset($upd_query)) { + return $relation->queryAsControlUser($upd_query); + } + + return false; + } // end of 'setMIME()' function + + + /** + * GLOBAL Plugin functions + */ + + /** + * Delete related transformation details + * after deleting database. table or column + * + * @param string $db Database name + * @param string $table Table name + * @param string $column Column name + * + * @return boolean State of the query execution + */ + public static function clear($db, $table = '', $column = '') + { + $relation = new Relation(); + $cfgRelation = $relation->getRelationsParam(); + + if (! isset($cfgRelation['column_info'])) { + return false; + } + + $delete_sql = 'DELETE FROM ' + . Util::backquote($cfgRelation['db']) . '.' + . Util::backquote($cfgRelation['column_info']) + . ' WHERE '; + + if (($column != '') && ($table != '')) { + + $delete_sql .= '`db_name` = \'' . $db . '\' AND ' + . '`table_name` = \'' . $table . '\' AND ' + . '`column_name` = \'' . $column . '\' '; + + } elseif ($table != '') { + + $delete_sql .= '`db_name` = \'' . $db . '\' AND ' + . '`table_name` = \'' . $table . '\' '; + + } else { + $delete_sql .= '`db_name` = \'' . $db . '\' '; + } + + return $GLOBALS['dbi']->tryQuery($delete_sql); + + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Twig/CharsetsExtension.php b/php/apps/phpmyadmin49/html/libraries/classes/Twig/CharsetsExtension.php new file mode 100644 index 00000000..0bdd2c75 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Twig/CharsetsExtension.php @@ -0,0 +1,44 @@ + array('html')) + ), + new TwigFunction( + 'Charsets_getCollationDropdownBox', + 'PhpMyAdmin\Charsets::getCollationDropdownBox', + array('is_safe' => array('html')) + ), + ); + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Twig/CoreExtension.php b/php/apps/phpmyadmin49/html/libraries/classes/Twig/CoreExtension.php new file mode 100644 index 00000000..138fdcdf --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Twig/CoreExtension.php @@ -0,0 +1,35 @@ + array('html')) + ), + ); + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Twig/I18n/NodeTrans.php b/php/apps/phpmyadmin49/html/libraries/classes/Twig/I18n/NodeTrans.php new file mode 100644 index 00000000..a24fe58a --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Twig/I18n/NodeTrans.php @@ -0,0 +1,169 @@ +node). + * The attributes are automatically made available as array items ($this['name']). + * + * @param Node $body Body of node trans + * @param Node $plural Node plural + * @param AbstractExpression $count Node count + * @param Node $context Node context + * @param Node $notes Node notes + * @param int $lineno The line number + * @param string $tag The tag name associated with the Node + */ + public function __construct( + Node $body, + Node $plural = null, + AbstractExpression $count = null, + Node $context = null, + Node $notes = null, + $lineno, + $tag = null + ) { + $nodes = array('body' => $body); + if (null !== $count) { + $nodes['count'] = $count; + } + if (null !== $plural) { + $nodes['plural'] = $plural; + } + if (null !== $context) { + $nodes['context'] = $context; + } + if (null !== $notes) { + $nodes['notes'] = $notes; + } + + Node::__construct($nodes, array(), $lineno, $tag); + } + + /** + * Compiles the node to PHP. + * + * @param Compiler $compiler Node compiler + * + * @return void + */ + public function compile(Compiler $compiler) + { + $compiler->addDebugInfo($this); + + list($msg, $vars) = $this->compileString($this->getNode('body')); + + if ($this->hasNode('plural')) { + list($msg1, $vars1) = $this->compileString($this->getNode('plural')); + + $vars = array_merge($vars, $vars1); + } + + $function = $this->getTransFunction( + $this->hasNode('plural'), + $this->hasNode('context') + ); + + if ($this->hasNode('notes')) { + $message = trim($this->getNode('notes')->getAttribute('data')); + + // line breaks are not allowed cause we want a single line comment + $message = str_replace(array("\n", "\r"), ' ', $message); + $compiler->write("// l10n: {$message}\n"); + } + + if ($vars) { + $compiler + ->write('echo strtr(' . $function . '(') + ->subcompile($msg) + ; + + if ($this->hasNode('plural')) { + $compiler + ->raw(', ') + ->subcompile($msg1) + ->raw(', abs(') + ->subcompile($this->hasNode('count') ? $this->getNode('count') : null) + ->raw(')') + ; + } + + $compiler->raw('), array('); + + foreach ($vars as $var) { + if ('count' === $var->getAttribute('name')) { + $compiler + ->string('%count%') + ->raw(' => abs(') + ->subcompile($this->hasNode('count') ? $this->getNode('count') : null) + ->raw('), ') + ; + } else { + $compiler + ->string('%' . $var->getAttribute('name') . '%') + ->raw(' => ') + ->subcompile($var) + ->raw(', ') + ; + } + } + + $compiler->raw("));\n"); + } else { + $compiler->write('echo ' . $function . '('); + + if ($this->hasNode('context')) { + $context = trim($this->getNode('context')->getAttribute('data')); + $compiler->write('"' . $context . '", '); + } + + $compiler->subcompile($msg); + + if ($this->hasNode('plural')) { + $compiler + ->raw(', ') + ->subcompile($msg1) + ->raw(', abs(') + ->subcompile($this->hasNode('count') ? $this->getNode('count') : null) + ->raw(')') + ; + } + + $compiler->raw(");\n"); + } + } + + /** + * @param bool $plural Return plural or singular function to use + * @param bool $hasMsgContext It has message context? + * + * @return string + */ + protected function getTransFunction($plural, $hasMsgContext = false) + { + if ($hasMsgContext) { + return $plural ? '_ngettext' : '_pgettext'; + } + + return $plural ? '_ngettext' : '_gettext'; + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Twig/I18n/TokenParserTrans.php b/php/apps/phpmyadmin49/html/libraries/classes/Twig/I18n/TokenParserTrans.php new file mode 100644 index 00000000..44743e99 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Twig/I18n/TokenParserTrans.php @@ -0,0 +1,81 @@ +getLine(); + $stream = $this->parser->getStream(); + $count = null; + $plural = null; + $notes = null; + $context = null; + + if (!$stream->test(Token::BLOCK_END_TYPE)) { + $body = $this->parser->getExpressionParser()->parseExpression(); + } else { + $stream->expect(Token::BLOCK_END_TYPE); + $body = $this->parser->subparse(array($this, 'decideForFork')); + $next = $stream->next()->getValue(); + + if ('plural' === $next) { + $count = $this->parser->getExpressionParser()->parseExpression(); + $stream->expect(Token::BLOCK_END_TYPE); + $plural = $this->parser->subparse(array($this, 'decideForFork')); + + if ('notes' === $stream->next()->getValue()) { + $stream->expect(Token::BLOCK_END_TYPE); + $notes = $this->parser->subparse(array($this, 'decideForEnd'), true); + } + } elseif ('context' === $next) { + $stream->expect(Token::BLOCK_END_TYPE); + $context = $this->parser->subparse(array($this, 'decideForEnd'), true); + } elseif ('notes' === $next) { + $stream->expect(Token::BLOCK_END_TYPE); + $notes = $this->parser->subparse(array($this, 'decideForEnd'), true); + } + } + + $stream->expect(Token::BLOCK_END_TYPE); + + $this->checkTransString($body, $lineno); + + return new NodeTrans($body, $plural, $count, $context, $notes, $lineno, $this->getTag()); + } + + /** + * Tests the current token for a type. + * + * @param Token $token Twig token to test + * + * @return bool + */ + public function decideForFork(Token $token) + { + return $token->test(array('plural', 'context', 'notes', 'endtrans')); + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Twig/I18nExtension.php b/php/apps/phpmyadmin49/html/libraries/classes/Twig/I18nExtension.php new file mode 100644 index 00000000..dc668304 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Twig/I18nExtension.php @@ -0,0 +1,42 @@ + array('html')) + ), + ); + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Twig/MessageExtension.php b/php/apps/phpmyadmin49/html/libraries/classes/Twig/MessageExtension.php new file mode 100644 index 00000000..c2d0796d --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Twig/MessageExtension.php @@ -0,0 +1,45 @@ +getDisplay(); + }, + array('is_safe' => array('html')) + ), + new TwigFunction( + 'Message_error', + function ($string) { + return Message::error($string)->getDisplay(); + }, + array('is_safe' => array('html')) + ), + ); + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Twig/PartitionExtension.php b/php/apps/phpmyadmin49/html/libraries/classes/Twig/PartitionExtension.php new file mode 100644 index 00000000..d4d49cd7 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Twig/PartitionExtension.php @@ -0,0 +1,35 @@ + array('html')) + ), + ); + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Twig/PhpFunctionsExtension.php b/php/apps/phpmyadmin49/html/libraries/classes/Twig/PhpFunctionsExtension.php new file mode 100644 index 00000000..4cf7fc26 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Twig/PhpFunctionsExtension.php @@ -0,0 +1,39 @@ + array('html')) + ), + new TwigFunction( + 'Plugins_getChoice', + 'PhpMyAdmin\Plugins::getChoice', + array('is_safe' => array('html')) + ), + new TwigFunction( + 'Plugins_getDefault', + 'PhpMyAdmin\Plugins::getDefault', + array('is_safe' => array('html')) + ), + new TwigFunction( + 'Plugins_getOptions', + 'PhpMyAdmin\Plugins::getOptions', + array('is_safe' => array('html')) + ), + ); + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Twig/RelationExtension.php b/php/apps/phpmyadmin49/html/libraries/classes/Twig/RelationExtension.php new file mode 100644 index 00000000..ce69ffd0 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Twig/RelationExtension.php @@ -0,0 +1,54 @@ + array('html')) + ), + new TwigFunction( + 'Relation_getDisplayField', + [$relation, 'getDisplayField'], + array('is_safe' => array('html')) + ), + new TwigFunction( + 'Relation_getForeignData', + [$relation, 'getForeignData'] + ), + new TwigFunction( + 'Relation_getTables', + [$relation, 'getTables'] + ), + new TwigFunction( + 'Relation_searchColumnInForeigners', + [$relation, 'searchColumnInForeigners'] + ), + ); + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Twig/SanitizeExtension.php b/php/apps/phpmyadmin49/html/libraries/classes/Twig/SanitizeExtension.php new file mode 100644 index 00000000..668e2aeb --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Twig/SanitizeExtension.php @@ -0,0 +1,45 @@ + array('html')) + ), + new TwigFunction( + 'Sanitize_jsFormat', + 'PhpMyAdmin\Sanitize::jsFormat', + array('is_safe' => array('html')) + ), + new TwigFunction( + 'Sanitize_sanitize', + 'PhpMyAdmin\Sanitize::sanitize', + array('is_safe' => array('html')) + ), + ); + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Twig/ServerPrivilegesExtension.php b/php/apps/phpmyadmin49/html/libraries/classes/Twig/ServerPrivilegesExtension.php new file mode 100644 index 00000000..df7d4b85 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Twig/ServerPrivilegesExtension.php @@ -0,0 +1,35 @@ + array('html')) + ), + ); + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Twig/StorageEngineExtension.php b/php/apps/phpmyadmin49/html/libraries/classes/Twig/StorageEngineExtension.php new file mode 100644 index 00000000..9ce01f95 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Twig/StorageEngineExtension.php @@ -0,0 +1,35 @@ + array('html')) + ), + ); + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Twig/TableExtension.php b/php/apps/phpmyadmin49/html/libraries/classes/Twig/TableExtension.php new file mode 100644 index 00000000..c736e40a --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Twig/TableExtension.php @@ -0,0 +1,34 @@ + array('html')) + ), + new TwigFunction( + 'Url_getHiddenFields', + 'PhpMyAdmin\Url::getHiddenFields', + array('is_safe' => array('html')) + ), + new TwigFunction( + 'Url_getCommon', + 'PhpMyAdmin\Url::getCommon', + array('is_safe' => array('html')) + ), + new TwigFunction( + 'Url_getCommonRaw', + 'PhpMyAdmin\Url::getCommonRaw', + array('is_safe' => array('html')) + ), + new TwigFunction( + 'Url_link', + 'PhpMyAdmin\Core::linkURL' + ), + ); + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Twig/UtilExtension.php b/php/apps/phpmyadmin49/html/libraries/classes/Twig/UtilExtension.php new file mode 100644 index 00000000..3a483d07 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Twig/UtilExtension.php @@ -0,0 +1,189 @@ + array('html')) + ), + new TwigFunction( + 'Util_convertBitDefaultValue', + 'PhpMyAdmin\Util::convertBitDefaultValue' + ), + new TwigFunction( + 'Util_escapeMysqlWildcards', + 'PhpMyAdmin\Util::escapeMysqlWildcards' + ), + new TwigFunction( + 'Util_extractColumnSpec', + 'PhpMyAdmin\Util::extractColumnSpec' + ), + new TwigFunction( + 'Util_formatByteDown', + 'PhpMyAdmin\Util::formatByteDown' + ), + new TwigFunction( + 'Util_formatNumber', + 'PhpMyAdmin\Util::formatNumber' + ), + new TwigFunction( + 'Util_formatSql', + 'PhpMyAdmin\Util::formatSql', + array('is_safe' => array('html')) + ), + new TwigFunction( + 'Util_getButtonOrImage', + 'PhpMyAdmin\Util::getButtonOrImage', + array('is_safe' => array('html')) + ), + new TwigFunction( + 'Util_getClassForType', + 'PhpMyAdmin\Util::getClassForType', + array('is_safe' => array('html')) + ), + new TwigFunction( + 'Util_getDivForSliderEffect', + 'PhpMyAdmin\Util::getDivForSliderEffect', + array('is_safe' => array('html')) + ), + new TwigFunction( + 'Util_getDocuLink', + 'PhpMyAdmin\Util::getDocuLink', + array('is_safe' => array('html')) + ), + new TwigFunction( + 'Util_getListNavigator', + 'PhpMyAdmin\Util::getListNavigator', + array('is_safe' => array('html')) + ), + new TwigFunction( + 'Util_showDocu', + 'PhpMyAdmin\Util::showDocu', + array('is_safe' => array('html')) + ), + new TwigFunction( + 'Util_getDropdown', + 'PhpMyAdmin\Util::getDropdown', + array('is_safe' => array('html')) + ), + new TwigFunction( + 'Util_getFKCheckbox', + 'PhpMyAdmin\Util::getFKCheckbox', + array('is_safe' => array('html')) + ), + new TwigFunction( + 'Util_getGISDatatypes', + 'PhpMyAdmin\Util::getGISDatatypes' + ), + new TwigFunction( + 'Util_getGISFunctions', + 'PhpMyAdmin\Util::getGISFunctions' + ), + new TwigFunction( + 'Util_getHtmlTab', + 'PhpMyAdmin\Util::getHtmlTab', + array('is_safe' => array('html')) + ), + new TwigFunction( + 'Util_getIcon', + 'PhpMyAdmin\Util::getIcon', + array('is_safe' => array('html')) + ), + new TwigFunction( + 'Util_getImage', + 'PhpMyAdmin\Util::getImage', + array('is_safe' => array('html')) + ), + new TwigFunction( + 'Util_getRadioFields', + 'PhpMyAdmin\Util::getRadioFields', + array('is_safe' => array('html')) + ), + new TwigFunction( + 'Util_getSelectUploadFileBlock', + 'PhpMyAdmin\Util::getSelectUploadFileBlock', + array('is_safe' => array('html')) + ), + new TwigFunction( + 'Util_getScriptNameForOption', + 'PhpMyAdmin\Util::getScriptNameForOption', + array('is_safe' => array('html')) + ), + new TwigFunction( + 'Util_getStartAndNumberOfRowsPanel', + 'PhpMyAdmin\Util::getStartAndNumberOfRowsPanel', + array('is_safe' => array('html')) + ), + new TwigFunction( + 'Util_getSupportedDatatypes', + 'PhpMyAdmin\Util::getSupportedDatatypes', + array('is_safe' => array('html')) + ), + new TwigFunction( + 'Util_isForeignKeySupported', + 'PhpMyAdmin\Util::isForeignKeySupported' + ), + new TwigFunction( + 'Util_linkOrButton', + 'PhpMyAdmin\Util::linkOrButton', + array('is_safe' => array('html')) + ), + new TwigFunction( + 'Util_localisedDate', + 'PhpMyAdmin\Util::localisedDate' + ), + new TwigFunction( + 'Util_showHint', + 'PhpMyAdmin\Util::showHint', + array('is_safe' => array('html')) + ), + new TwigFunction( + 'Util_showDocu', + 'PhpMyAdmin\Util::showDocu', + array('is_safe' => array('html')) + ), + new TwigFunction( + 'Util_showIcons', + 'PhpMyAdmin\Util::showIcons' + ), + new TwigFunction( + 'Util_showMySQLDocu', + 'PhpMyAdmin\Util::showMySQLDocu', + array('is_safe' => array('html')) + ), + new TwigFunction( + 'Util_sortableTableHeader', + 'PhpMyAdmin\Util::sortableTableHeader', + array('is_safe' => array('html')) + ), + ); + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/TwoFactor.php b/php/apps/phpmyadmin49/html/libraries/classes/TwoFactor.php new file mode 100644 index 00000000..9ac4ee20 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/TwoFactor.php @@ -0,0 +1,288 @@ +userPreferences = new UserPreferences(); + $this->user = $user; + $this->_available = $this->getAvailable(); + $this->config = $this->readConfig(); + $this->_writable = ($this->config['type'] == 'db'); + $this->_backend = $this->getBackend(); + } + + /** + * Reads the configuration + * + * @return array + */ + public function readConfig() + { + $result = []; + $config = $this->userPreferences->load(); + if (isset($config['config_data']['2fa'])) { + $result = $config['config_data']['2fa']; + } + $result['type'] = $config['type']; + if (! isset($result['backend'])) { + $result['backend'] = ''; + } + if (! isset($result['settings'])) { + $result['settings'] = []; + } + return $result; + } + + /** + * Get any property of this class + * + * @param string $property name of the property + * + * @return mixed|void if property exist, value of the relevant property + */ + public function __get($property) + { + switch ($property) { + case 'backend': + return $this->_backend; + case 'available': + return $this->_available; + case 'writable': + return $this->_writable; + case 'showSubmit': + $backend = $this->_backend; + return $backend::$showSubmit; + } + } + + /** + * Returns list of available backends + * + * @return array + */ + public function getAvailable() + { + $result = []; + if ($GLOBALS['cfg']['DBG']['simple2fa']) { + $result[] = 'simple'; + } + if (class_exists('PragmaRX\Google2FA\Google2FA') && class_exists('BaconQrCode\Renderer\Image\Png')) { + $result[] = 'application'; + } + if (class_exists('Samyoul\U2F\U2FServer\U2FServer')) { + $result[] = 'key'; + } + return $result; + } + + /** + * Returns list of missing dependencies + * + * @return array + */ + public function getMissingDeps() + { + $result = []; + if (!class_exists('PragmaRX\Google2FA\Google2FA')) { + $result[] = [ + 'class' => \PhpMyAdmin\Plugins\TwoFactor\Application::getName(), + 'dep' => 'pragmarx/google2fa', + ]; + } + if (!class_exists('BaconQrCode\Renderer\Image\Png')) { + $result[] = [ + 'class' => \PhpMyAdmin\Plugins\TwoFactor\Application::getName(), + 'dep' => 'bacon/bacon-qr-code', + ]; + } + if (!class_exists('Samyoul\U2F\U2FServer\U2FServer')) { + $result[] = [ + 'class' => \PhpMyAdmin\Plugins\TwoFactor\Key::getName(), + 'dep' => 'samyoul/u2f-php-server', + ]; + } + return $result; + } + + /** + * Returns class name for given name + * + * @param string $name Backend name + * + * @return string + */ + public function getBackendClass($name) + { + $result = 'PhpMyAdmin\\Plugins\\TwoFactorPlugin'; + if (in_array($name, $this->_available)) { + $result = 'PhpMyAdmin\\Plugins\\TwoFactor\\' . ucfirst($name); + } elseif (! empty($name)) { + $result = 'PhpMyAdmin\\Plugins\\TwoFactor\\Invalid'; + } + return $result; + } + + /** + * Returns backend for current user + * + * @return PhpMyAdmin\Plugins\TwoFactorPlugin + */ + public function getBackend() + { + $name = $this->getBackendClass($this->config['backend']); + return new $name($this); + } + + /** + * Checks authentication, returns true on success + * + * @param boolean $skip_session Skip session cache + * + * @return boolean + */ + public function check($skip_session = false) + { + if ($skip_session) { + return $this->_backend->check(); + } + if (empty($_SESSION['two_factor_check'])) { + $_SESSION['two_factor_check'] = $this->_backend->check(); + } + return $_SESSION['two_factor_check']; + } + + /** + * Renders user interface to enter two-factor authentication + * + * @return string HTML code + */ + public function render() + { + return $this->_backend->getError() . $this->_backend->render(); + } + + /** + * Renders user interface to configure two-factor authentication + * + * @return string HTML code + */ + public function setup() + { + return $this->_backend->getError() . $this->_backend->setup(); + } + + /** + * Saves current configuration. + * + * @return true|PhpMyAdmin\Message + */ + public function save() + { + return $this->userPreferences->persistOption('2fa', $this->config, null); + } + + /** + * Changes two-factor authentication settings + * + * The object might stay in partialy changed setup + * if configuration fails. + * + * @param string $name Backend name + * + * @return boolean + */ + public function configure($name) + { + $this->config = [ + 'backend' => $name + ]; + if ($name === '') { + $cls = $this->getBackendClass($name); + $this->config['settings'] = []; + $this->_backend = new $cls($this); + } else { + if (! in_array($name, $this->_available)) { + return false; + } + $cls = $this->getBackendClass($name); + $this->config['settings'] = []; + $this->_backend = new $cls($this); + if (! $this->_backend->configure()) { + return false; + } + } + $result = $this->save(); + if ($result !== true) { + $result->display(); + } + return true; + } + + /** + * Returns array with all available backends + * + * @return array + */ + public function getAllBackends() + { + $all = array_merge([''], $this->available); + $backends = []; + foreach ($all as $name) { + $cls = $this->getBackendClass($name); + $backends[] = [ + 'id' => $cls::$id, + 'name' => $cls::getName(), + 'description' => $cls::getDescription(), + ]; + } + return $backends; + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Types.php b/php/apps/phpmyadmin49/html/libraries/classes/Types.php new file mode 100644 index 00000000..d53bcc27 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Types.php @@ -0,0 +1,828 @@ +_dbi = $dbi; + } + + /** + * Returns list of unary operators. + * + * @return string[] + */ + public function getUnaryOperators() + { + return array( + 'IS NULL', + 'IS NOT NULL', + "= ''", + "!= ''", + ); + } + + /** + * Check whether operator is unary. + * + * @param string $op operator name + * + * @return boolean + */ + public function isUnaryOperator($op) + { + return in_array($op, $this->getUnaryOperators()); + } + + /** + * Returns list of operators checking for NULL. + * + * @return string[] + */ + public function getNullOperators() + { + return array( + 'IS NULL', + 'IS NOT NULL', + ); + } + + /** + * ENUM search operators + * + * @return string[] + */ + public function getEnumOperators() + { + return array( + '=', + '!=', + ); + } + + /** + * TEXT search operators + * + * @return string[] + */ + public function getTextOperators() + { + return array( + 'LIKE', + 'LIKE %...%', + 'NOT LIKE', + '=', + '!=', + 'REGEXP', + 'REGEXP ^...$', + 'NOT REGEXP', + "= ''", + "!= ''", + 'IN (...)', + 'NOT IN (...)', + 'BETWEEN', + 'NOT BETWEEN', + ); + } + + /** + * Number search operators + * + * @return string[] + */ + public function getNumberOperators() + { + return array( + '=', + '>', + '>=', + '<', + '<=', + '!=', + 'LIKE', + 'LIKE %...%', + 'NOT LIKE', + 'IN (...)', + 'NOT IN (...)', + 'BETWEEN', + 'NOT BETWEEN', + ); + } + + /** + * Returns operators for given type + * + * @param string $type Type of field + * @param boolean $null Whether field can be NULL + * + * @return string[] + */ + public function getTypeOperators($type, $null) + { + $ret = array(); + $class = $this->getTypeClass($type); + + if (strncasecmp($type, 'enum', 4) == 0) { + $ret = array_merge($ret, $this->getEnumOperators()); + } elseif ($class == 'CHAR') { + $ret = array_merge($ret, $this->getTextOperators()); + } else { + $ret = array_merge($ret, $this->getNumberOperators()); + } + + if ($null) { + $ret = array_merge($ret, $this->getNullOperators()); + } + + return $ret; + } + + /** + * Returns operators for given type as html options + * + * @param string $type Type of field + * @param boolean $null Whether field can be NULL + * @param string $selectedOperator Option to be selected + * + * @return string Generated Html + */ + public function getTypeOperatorsHtml($type, $null, $selectedOperator = null) + { + $html = ''; + + foreach ($this->getTypeOperators($type, $null) as $fc) { + if (isset($selectedOperator) && $selectedOperator == $fc) { + $selected = ' selected="selected"'; + } else { + $selected = ''; + } + $html .= ''; + } + + return $html; + } + + /** + * Returns the data type description. + * + * @param string $type The data type to get a description. + * + * @return string + * + */ + public function getTypeDescription($type) + { + $type = mb_strtoupper($type); + switch ($type) { + case 'TINYINT': + return __( + 'A 1-byte integer, signed range is -128 to 127, unsigned range is ' . + '0 to 255' + ); + case 'SMALLINT': + return __( + 'A 2-byte integer, signed range is -32,768 to 32,767, unsigned ' . + 'range is 0 to 65,535' + ); + case 'MEDIUMINT': + return __( + 'A 3-byte integer, signed range is -8,388,608 to 8,388,607, ' . + 'unsigned range is 0 to 16,777,215' + ); + case 'INT': + return __( + 'A 4-byte integer, signed range is ' . + '-2,147,483,648 to 2,147,483,647, unsigned range is 0 to ' . + '4,294,967,295' + ); + case 'BIGINT': + return __( + 'An 8-byte integer, signed range is -9,223,372,036,854,775,808 ' . + 'to 9,223,372,036,854,775,807, unsigned range is 0 to ' . + '18,446,744,073,709,551,615' + ); + case 'DECIMAL': + return __( + 'A fixed-point number (M, D) - the maximum number of digits (M) ' . + 'is 65 (default 10), the maximum number of decimals (D) is 30 ' . + '(default 0)' + ); + case 'FLOAT': + return __( + 'A small floating-point number, allowable values are ' . + '-3.402823466E+38 to -1.175494351E-38, 0, and 1.175494351E-38 to ' . + '3.402823466E+38' + ); + case 'DOUBLE': + return __( + 'A double-precision floating-point number, allowable values are ' . + '-1.7976931348623157E+308 to -2.2250738585072014E-308, 0, and ' . + '2.2250738585072014E-308 to 1.7976931348623157E+308' + ); + case 'REAL': + return __( + 'Synonym for DOUBLE (exception: in REAL_AS_FLOAT SQL mode it is ' . + 'a synonym for FLOAT)' + ); + case 'BIT': + return __( + 'A bit-field type (M), storing M of bits per value (default is 1, ' . + 'maximum is 64)' + ); + case 'BOOLEAN': + return __( + 'A synonym for TINYINT(1), a value of zero is considered false, ' . + 'nonzero values are considered true' + ); + case 'SERIAL': + return __('An alias for BIGINT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE'); + case 'DATE': + return sprintf( + __('A date, supported range is %1$s to %2$s'), '1000-01-01', + '9999-12-31' + ); + case 'DATETIME': + return sprintf( + __('A date and time combination, supported range is %1$s to %2$s'), + '1000-01-01 00:00:00', '9999-12-31 23:59:59' + ); + case 'TIMESTAMP': + return __( + 'A timestamp, range is 1970-01-01 00:00:01 UTC to 2038-01-09 ' . + '03:14:07 UTC, stored as the number of seconds since the epoch ' . + '(1970-01-01 00:00:00 UTC)' + ); + case 'TIME': + return sprintf( + __('A time, range is %1$s to %2$s'), '-838:59:59', '838:59:59' + ); + case 'YEAR': + return __( + "A year in four-digit (4, default) or two-digit (2) format, the " . + "allowable values are 70 (1970) to 69 (2069) or 1901 to 2155 and " . + "0000" + ); + case 'CHAR': + return __( + 'A fixed-length (0-255, default 1) string that is always ' . + 'right-padded with spaces to the specified length when stored' + ); + case 'VARCHAR': + return sprintf( + __( + 'A variable-length (%s) string, the effective maximum length ' . + 'is subject to the maximum row size' + ), '0-65,535' + ); + case 'TINYTEXT': + return __( + 'A TEXT column with a maximum length of 255 (2^8 - 1) characters, ' . + 'stored with a one-byte prefix indicating the length of the value ' . + 'in bytes' + ); + case 'TEXT': + return __( + 'A TEXT column with a maximum length of 65,535 (2^16 - 1) ' . + 'characters, stored with a two-byte prefix indicating the length ' . + 'of the value in bytes' + ); + case 'MEDIUMTEXT': + return __( + 'A TEXT column with a maximum length of 16,777,215 (2^24 - 1) ' . + 'characters, stored with a three-byte prefix indicating the ' . + 'length of the value in bytes' + ); + case 'LONGTEXT': + return __( + 'A TEXT column with a maximum length of 4,294,967,295 or 4GiB ' . + '(2^32 - 1) characters, stored with a four-byte prefix indicating ' . + 'the length of the value in bytes' + ); + case 'BINARY': + return __( + 'Similar to the CHAR type, but stores binary byte strings rather ' . + 'than non-binary character strings' + ); + case 'VARBINARY': + return __( + 'Similar to the VARCHAR type, but stores binary byte strings ' . + 'rather than non-binary character strings' + ); + case 'TINYBLOB': + return __( + 'A BLOB column with a maximum length of 255 (2^8 - 1) bytes, ' . + 'stored with a one-byte prefix indicating the length of the value' + ); + case 'MEDIUMBLOB': + return __( + 'A BLOB column with a maximum length of 16,777,215 (2^24 - 1) ' . + 'bytes, stored with a three-byte prefix indicating the length of ' . + 'the value' + ); + case 'BLOB': + return __( + 'A BLOB column with a maximum length of 65,535 (2^16 - 1) bytes, ' . + 'stored with a two-byte prefix indicating the length of the value' + ); + case 'LONGBLOB': + return __( + 'A BLOB column with a maximum length of 4,294,967,295 or 4GiB ' . + '(2^32 - 1) bytes, stored with a four-byte prefix indicating the ' . + 'length of the value' + ); + case 'ENUM': + return __( + "An enumeration, chosen from the list of up to 65,535 values or " . + "the special '' error value" + ); + case 'SET': + return __("A single value chosen from a set of up to 64 members"); + case 'GEOMETRY': + return __('A type that can store a geometry of any type'); + case 'POINT': + return __('A point in 2-dimensional space'); + case 'LINESTRING': + return __('A curve with linear interpolation between points'); + case 'POLYGON': + return __('A polygon'); + case 'MULTIPOINT': + return __('A collection of points'); + case 'MULTILINESTRING': + return __( + 'A collection of curves with linear interpolation between points' + ); + case 'MULTIPOLYGON': + return __('A collection of polygons'); + case 'GEOMETRYCOLLECTION': + return __('A collection of geometry objects of any type'); + case 'JSON': + return __( + 'Stores and enables efficient access to data in JSON' + . ' (JavaScript Object Notation) documents' + ); + } + return ''; + } + + /** + * Returns class of a type, used for functions available for type + * or default values. + * + * @param string $type The data type to get a class. + * + * @return string + * + */ + public function getTypeClass($type) + { + $type = mb_strtoupper($type); + switch ($type) { + case 'TINYINT': + case 'SMALLINT': + case 'MEDIUMINT': + case 'INT': + case 'BIGINT': + case 'DECIMAL': + case 'FLOAT': + case 'DOUBLE': + case 'REAL': + case 'BIT': + case 'BOOLEAN': + case 'SERIAL': + return 'NUMBER'; + + case 'DATE': + case 'DATETIME': + case 'TIMESTAMP': + case 'TIME': + case 'YEAR': + return 'DATE'; + + case 'CHAR': + case 'VARCHAR': + case 'TINYTEXT': + case 'TEXT': + case 'MEDIUMTEXT': + case 'LONGTEXT': + case 'BINARY': + case 'VARBINARY': + case 'TINYBLOB': + case 'MEDIUMBLOB': + case 'BLOB': + case 'LONGBLOB': + case 'ENUM': + case 'SET': + return 'CHAR'; + + case 'GEOMETRY': + case 'POINT': + case 'LINESTRING': + case 'POLYGON': + case 'MULTIPOINT': + case 'MULTILINESTRING': + case 'MULTIPOLYGON': + case 'GEOMETRYCOLLECTION': + return 'SPATIAL'; + + case 'JSON': + return 'JSON'; + } + + return ''; + } + + /** + * Returns array of functions available for a class. + * + * @param string $class The class to get function list. + * + * @return string[] + * + */ + public function getFunctionsClass($class) + { + $isMariaDB = $this->_dbi->isMariaDB(); + $serverVersion = $this->_dbi->getVersion(); + + switch ($class) { + case 'CHAR': + $ret = array( + 'AES_DECRYPT', + 'AES_ENCRYPT', + 'BIN', + 'CHAR', + 'COMPRESS', + 'CURRENT_USER', + 'DATABASE', + 'DAYNAME', + 'DES_DECRYPT', + 'DES_ENCRYPT', + 'ENCRYPT', + 'HEX', + 'INET6_NTOA', + 'INET_NTOA', + 'LOAD_FILE', + 'LOWER', + 'LTRIM', + 'MD5', + 'MONTHNAME', + 'OLD_PASSWORD', + 'PASSWORD', + 'QUOTE', + 'REVERSE', + 'RTRIM', + 'SHA1', + 'SOUNDEX', + 'SPACE', + 'TRIM', + 'UNCOMPRESS', + 'UNHEX', + 'UPPER', + 'USER', + 'UUID', + 'VERSION', + ); + + if (($isMariaDB && $serverVersion < 100012) + || $serverVersion < 50603 + ) { + $ret = array_diff($ret, array('INET6_NTOA')); + } + return $ret; + + case 'DATE': + return array( + 'CURRENT_DATE', + 'CURRENT_TIME', + 'DATE', + 'FROM_DAYS', + 'FROM_UNIXTIME', + 'LAST_DAY', + 'NOW', + 'SEC_TO_TIME', + 'SYSDATE', + 'TIME', + 'TIMESTAMP', + 'UTC_DATE', + 'UTC_TIME', + 'UTC_TIMESTAMP', + 'YEAR', + ); + + case 'NUMBER': + $ret = array( + 'ABS', + 'ACOS', + 'ASCII', + 'ASIN', + 'ATAN', + 'BIT_LENGTH', + 'BIT_COUNT', + 'CEILING', + 'CHAR_LENGTH', + 'CONNECTION_ID', + 'COS', + 'COT', + 'CRC32', + 'DAYOFMONTH', + 'DAYOFWEEK', + 'DAYOFYEAR', + 'DEGREES', + 'EXP', + 'FLOOR', + 'HOUR', + 'INET6_ATON', + 'INET_ATON', + 'LENGTH', + 'LN', + 'LOG', + 'LOG2', + 'LOG10', + 'MICROSECOND', + 'MINUTE', + 'MONTH', + 'OCT', + 'ORD', + 'PI', + 'QUARTER', + 'RADIANS', + 'RAND', + 'ROUND', + 'SECOND', + 'SIGN', + 'SIN', + 'SQRT', + 'TAN', + 'TO_DAYS', + 'TO_SECONDS', + 'TIME_TO_SEC', + 'UNCOMPRESSED_LENGTH', + 'UNIX_TIMESTAMP', + 'UUID_SHORT', + 'WEEK', + 'WEEKDAY', + 'WEEKOFYEAR', + 'YEARWEEK', + ); + if (($isMariaDB && $serverVersion < 100012) + || $serverVersion < 50603 + ) { + $ret = array_diff($ret, array('INET6_ATON')); + } + return $ret; + + case 'SPATIAL': + if ($serverVersion >= 50600) { + return array( + 'ST_GeomFromText', + 'ST_GeomFromWKB', + + 'ST_GeomCollFromText', + 'ST_LineFromText', + 'ST_MLineFromText', + 'ST_PointFromText', + 'ST_MPointFromText', + 'ST_PolyFromText', + 'ST_MPolyFromText', + + 'ST_GeomCollFromWKB', + 'ST_LineFromWKB', + 'ST_MLineFromWKB', + 'ST_PointFromWKB', + 'ST_MPointFromWKB', + 'ST_PolyFromWKB', + 'ST_MPolyFromWKB', + ); + } else { + return array( + 'GeomFromText', + 'GeomFromWKB', + + 'GeomCollFromText', + 'LineFromText', + 'MLineFromText', + 'PointFromText', + 'MPointFromText', + 'PolyFromText', + 'MPolyFromText', + + 'GeomCollFromWKB', + 'LineFromWKB', + 'MLineFromWKB', + 'PointFromWKB', + 'MPointFromWKB', + 'PolyFromWKB', + 'MPolyFromWKB', + ); + + } + } + return array(); + } + + /** + * Returns array of functions available for a type. + * + * @param string $type The data type to get function list. + * + * @return string[] + * + */ + public function getFunctions($type) + { + $class = $this->getTypeClass($type); + return $this->getFunctionsClass($class); + } + + /** + * Returns array of all functions available. + * + * @return string[] + * + */ + public function getAllFunctions() + { + $ret = array_merge( + $this->getFunctionsClass('CHAR'), + $this->getFunctionsClass('NUMBER'), + $this->getFunctionsClass('DATE'), + $this->getFunctionsClass('UUID') + ); + sort($ret); + return $ret; + } + + /** + * Returns array of all attributes available. + * + * @return string[] + * + */ + public function getAttributes() + { + return array( + '', + 'BINARY', + 'UNSIGNED', + 'UNSIGNED ZEROFILL', + 'on update CURRENT_TIMESTAMP', + ); + } + + /** + * Returns array of all column types available. + * + * VARCHAR, TINYINT, TEXT and DATE are listed first, based on + * estimated popularity. + * + * @return string[] + * + */ + public function getColumns() + { + $isMariaDB = $this->_dbi->isMariaDB(); + $serverVersion = $this->_dbi->getVersion(); + + // most used types + $ret = array( + 'INT', + 'VARCHAR', + 'TEXT', + 'DATE', + ); + // numeric + $ret[_pgettext('numeric types', 'Numeric')] = array( + 'TINYINT', + 'SMALLINT', + 'MEDIUMINT', + 'INT', + 'BIGINT', + '-', + 'DECIMAL', + 'FLOAT', + 'DOUBLE', + 'REAL', + '-', + 'BIT', + 'BOOLEAN', + 'SERIAL', + ); + + // Date/Time + $ret[_pgettext('date and time types', 'Date and time')] = array( + 'DATE', + 'DATETIME', + 'TIMESTAMP', + 'TIME', + 'YEAR', + ); + + // Text + $ret[_pgettext('string types', 'String')] = array( + 'CHAR', + 'VARCHAR', + '-', + 'TINYTEXT', + 'TEXT', + 'MEDIUMTEXT', + 'LONGTEXT', + '-', + 'BINARY', + 'VARBINARY', + '-', + 'TINYBLOB', + 'MEDIUMBLOB', + 'BLOB', + 'LONGBLOB', + '-', + 'ENUM', + 'SET', + ); + + $ret[_pgettext('spatial types', 'Spatial')] = array( + 'GEOMETRY', + 'POINT', + 'LINESTRING', + 'POLYGON', + 'MULTIPOINT', + 'MULTILINESTRING', + 'MULTIPOLYGON', + 'GEOMETRYCOLLECTION', + ); + + if (($isMariaDB && $serverVersion > 100207) + || (!$isMariaDB && $serverVersion >= 50708)) { + $ret['JSON'] = array( + 'JSON', + ); + } + + return $ret; + } + + /** + * Returns an array of integer types + * + * @return string[] integer types + */ + public function getIntegerTypes() + { + return array('tinyint', 'smallint', 'mediumint', 'int', 'bigint'); + } + + /** + * Returns the min and max values of a given integer type + * + * @param string $type integer type + * @param boolean $signed whether signed + * + * @return string[] min and max values + */ + public function getIntegerRange($type, $signed = true) + { + static $min_max_data = array( + 'unsigned' => array( + 'tinyint' => array('0', '255'), + 'smallint' => array('0', '65535'), + 'mediumint' => array('0', '16777215'), + 'int' => array('0', '4294967295'), + 'bigint' => array('0', '18446744073709551615') + ), + 'signed' => array( + 'tinyint' => array('-128', '127'), + 'smallint' => array('-32768', '32767'), + 'mediumint' => array('-8388608', '8388607'), + 'int' => array('-2147483648', '2147483647'), + 'bigint' => array('-9223372036854775808', '9223372036854775807') + ) + ); + $relevantArray = $signed + ? $min_max_data['signed'] + : $min_max_data['unsigned']; + return isset($relevantArray[$type]) ? $relevantArray[$type] : array('', ''); + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Url.php b/php/apps/phpmyadmin49/html/libraries/classes/Url.php new file mode 100644 index 00000000..6733d126 --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Url.php @@ -0,0 +1,267 @@ + 0) { + $params['db'] = $db; + } + if (strlen($table) > 0) { + $params['table'] = $table; + } + } + + if (! empty($GLOBALS['server']) + && $GLOBALS['server'] != $GLOBALS['cfg']['ServerDefault'] + ) { + $params['server'] = $GLOBALS['server']; + } + if (empty($_COOKIE['pma_lang']) && ! empty($GLOBALS['lang'])) { + $params['lang'] = $GLOBALS['lang']; + } + + if (! is_array($skip)) { + if (isset($params[$skip])) { + unset($params[$skip]); + } + } else { + foreach ($skip as $skipping) { + if (isset($params[$skipping])) { + unset($params[$skipping]); + } + } + } + + return Url::getHiddenFields($params); + } + + /** + * create hidden form fields from array with name => value + * + * + * $values = array( + * 'aaa' => aaa, + * 'bbb' => array( + * 'bbb_0', + * 'bbb_1', + * ), + * 'ccc' => array( + * 'a' => 'ccc_a', + * 'b' => 'ccc_b', + * ), + * ); + * echo Url::getHiddenFields($values); + * + * // produces: + * + * + * + * + * + * + * + * @param array $values hidden values + * @param string $pre prefix + * + * @return string form fields of type hidden + */ + public static function getHiddenFields(array $values, $pre = '') + { + $fields = ''; + + /* Always include token in plain forms */ + if ($pre === '') { + $values['token'] = $_SESSION[' PMA_token ']; + } + + foreach ($values as $name => $value) { + if (! empty($pre)) { + $name = $pre . '[' . $name . ']'; + } + + if (is_array($value)) { + $fields .= Url::getHiddenFields($value, $name); + } else { + // do not generate an ending "\n" because + // Url::getHiddenInputs() is sometimes called + // from a JS document.write() + $fields .= ''; + } + } + + return $fields; + } + + /** + * Generates text with URL parameters. + * + * + * $params['myparam'] = 'myvalue'; + * $params['db'] = 'mysql'; + * $params['table'] = 'rights'; + * // note the missing ? + * echo 'script.php' . Url::getCommon($params); + * // produces with cookies enabled: + * // script.php?myparam=myvalue&db=mysql&table=rights + * // with cookies disabled: + * // script.php?server=1&lang=en&myparam=myvalue&db=mysql + * // &table=rights + * + * // note the missing ? + * echo 'script.php' . Url::getCommon(); + * // produces with cookies enabled: + * // script.php + * // with cookies disabled: + * // script.php?server=1&lang=en + * + * + * @param mixed $params optional, Contains an associative array with url params + * @param string $divider optional character to use instead of '?' + * + * @return string string with URL parameters + * @access public + */ + public static function getCommon($params = array(), $divider = '?') + { + return htmlspecialchars( + Url::getCommonRaw($params, $divider) + ); + } + + /** + * Generates text with URL parameters. + * + * + * $params['myparam'] = 'myvalue'; + * $params['db'] = 'mysql'; + * $params['table'] = 'rights'; + * // note the missing ? + * echo 'script.php' . Url::getCommon($params); + * // produces with cookies enabled: + * // script.php?myparam=myvalue&db=mysql&table=rights + * // with cookies disabled: + * // script.php?server=1&lang=en&myparam=myvalue&db=mysql + * // &table=rights + * + * // note the missing ? + * echo 'script.php' . Url::getCommon(); + * // produces with cookies enabled: + * // script.php + * // with cookies disabled: + * // script.php?server=1&lang=en + * + * + * @param mixed $params optional, Contains an associative array with url params + * @param string $divider optional character to use instead of '?' + * + * @return string string with URL parameters + * @access public + */ + public static function getCommonRaw($params = array(), $divider = '?') + { + $separator = Url::getArgSeparator(); + + // avoid overwriting when creating navi panel links to servers + if (isset($GLOBALS['server']) + && $GLOBALS['server'] != $GLOBALS['cfg']['ServerDefault'] + && ! isset($params['server']) + && ! $GLOBALS['PMA_Config']->get('is_setup') + ) { + $params['server'] = $GLOBALS['server']; + } + + if (empty($_COOKIE['pma_lang']) && ! empty($GLOBALS['lang'])) { + $params['lang'] = $GLOBALS['lang']; + } + + $query = http_build_query($params, null, $separator); + + if ($divider != '?' || strlen($query) > 0) { + return $divider . $query; + } + + return ''; + } + + /** + * Returns url separator + * + * extracted from arg_separator.input as set in php.ini + * we do not use arg_separator.output to avoid problems with & and & + * + * @param string $encode whether to encode separator or not, + * currently 'none' or 'html' + * + * @return string character used for separating url parts usually ; or & + * @access public + */ + public static function getArgSeparator($encode = 'none') + { + static $separator = null; + static $html_separator = null; + + if (null === $separator) { + // use separators defined by php, but prefer ';' + // as recommended by W3C + // (see https://www.w3.org/TR/1999/REC-html401-19991224/appendix + // /notes.html#h-B.2.2) + $arg_separator = ini_get('arg_separator.input'); + if (mb_strpos($arg_separator, ';') !== false) { + $separator = ';'; + } elseif (strlen($arg_separator) > 0) { + $separator = $arg_separator[0]; + } else { + $separator = '&'; + } + $html_separator = htmlentities($separator); + } + + switch ($encode) { + case 'html': + return $html_separator; + case 'text' : + case 'none' : + default : + return $separator; + } + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/UserPassword.php b/php/apps/phpmyadmin49/html/libraries/classes/UserPassword.php new file mode 100644 index 00000000..585463fe --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/UserPassword.php @@ -0,0 +1,251 @@ +isAjax()) { + /** + * If in an Ajax request, we don't need to show the rest of the page + */ + if ($change_password_message['error']) { + $response->addJSON('message', $change_password_message['msg']); + $response->setRequestStatus(false); + } else { + $sql_query = Util::getMessage( + $change_password_message['msg'], + $sql_query, + 'success' + ); + $response->addJSON('message', $sql_query); + } + exit; + } + } + + /** + * Generate the message + * + * @return array error value and message + */ + public function setChangePasswordMsg() + { + $error = false; + $message = Message::success(__('The profile has been updated.')); + + if (($_POST['nopass'] != '1')) { + if (strlen($_POST['pma_pw']) === 0 || strlen($_POST['pma_pw2']) === 0) { + $message = Message::error(__('The password is empty!')); + $error = true; + } elseif ($_POST['pma_pw'] !== $_POST['pma_pw2']) { + $message = Message::error( + __('The passwords aren\'t the same!') + ); + $error = true; + } elseif (strlen($_POST['pma_pw']) > 256) { + $message = Message::error(__('Password is too long!')); + $error = true; + } + } + return array('error' => $error, 'msg' => $message); + } + + /** + * Change the password + * + * @param string $password New password + * @param string $message Message + * @param array $change_password_message Message to show + * + * @return void + */ + public function changePassword($password, $message, array $change_password_message) + { + global $auth_plugin; + + $hashing_function = $this->changePassHashingFunction(); + + list($username, $hostname) = $GLOBALS['dbi']->getCurrentUserAndHost(); + + $serverType = Util::getServerType(); + $serverVersion = $GLOBALS['dbi']->getVersion(); + + if (isset($_POST['authentication_plugin']) + && ! empty($_POST['authentication_plugin']) + ) { + $orig_auth_plugin = $_POST['authentication_plugin']; + } else { + $orig_auth_plugin = Privileges::getCurrentAuthenticationPlugin( + 'change', $username, $hostname + ); + } + + $sql_query = 'SET password = ' + . (($password == '') ? '\'\'' : $hashing_function . '(\'***\')'); + + if ($serverType == 'MySQL' + && $serverVersion >= 50706 + ) { + $sql_query = 'ALTER USER \'' . $username . '\'@\'' . $hostname + . '\' IDENTIFIED WITH ' . $orig_auth_plugin . ' BY ' + . (($password == '') ? '\'\'' : '\'***\''); + } elseif (($serverType == 'MySQL' + && $serverVersion >= 50507) + || ($serverType == 'MariaDB' + && $serverVersion >= 50200) + ) { + // For MySQL versions 5.5.7+ and MariaDB versions 5.2+, + // explicitly set value of `old_passwords` so that + // it does not give an error while using + // the PASSWORD() function + if ($orig_auth_plugin == 'sha256_password') { + $value = 2; + } else { + $value = 0; + } + $GLOBALS['dbi']->tryQuery('SET `old_passwords` = ' . $value . ';'); + } + + $this->changePassUrlParamsAndSubmitQuery( + $username, $hostname, $password, + $sql_query, $hashing_function, $orig_auth_plugin + ); + + $auth_plugin->handlePasswordChange($password); + $this->getChangePassMessage($change_password_message, $sql_query); + $this->changePassDisplayPage($message, $sql_query); + } + + /** + * Generate the hashing function + * + * @return string $hashing_function + */ + private function changePassHashingFunction() + { + if (Core::isValid( + $_POST['authentication_plugin'], 'identical', 'mysql_old_password' + )) { + $hashing_function = 'OLD_PASSWORD'; + } else { + $hashing_function = 'PASSWORD'; + } + return $hashing_function; + } + + /** + * Changes password for a user + * + * @param string $username Username + * @param string $hostname Hostname + * @param string $password Password + * @param string $sql_query SQL query + * @param string $hashing_function Hashing function + * @param string $orig_auth_plugin Original Authentication Plugin + * + * @return void + */ + private function changePassUrlParamsAndSubmitQuery( + $username, $hostname, $password, $sql_query, $hashing_function, $orig_auth_plugin + ) { + $err_url = 'user_password.php' . Url::getCommon(); + + $serverType = Util::getServerType(); + $serverVersion = $GLOBALS['dbi']->getVersion(); + + if ($serverType == 'MySQL' && $serverVersion >= 50706) { + $local_query = 'ALTER USER \'' . $username . '\'@\'' . $hostname . '\'' + . ' IDENTIFIED with ' . $orig_auth_plugin . ' BY ' + . (($password == '') + ? '\'\'' + : '\'' . $GLOBALS['dbi']->escapeString($password) . '\''); + } elseif ($serverType == 'MariaDB' + && $serverVersion >= 50200 + && $serverVersion < 100100 + && $orig_auth_plugin !== '' + ) { + if ($orig_auth_plugin == 'mysql_native_password') { + // Set the hashing method used by PASSWORD() + // to be 'mysql_native_password' type + $GLOBALS['dbi']->tryQuery('SET old_passwords = 0;'); + } elseif ($orig_auth_plugin == 'sha256_password') { + // Set the hashing method used by PASSWORD() + // to be 'sha256_password' type + $GLOBALS['dbi']->tryQuery('SET `old_passwords` = 2;'); + } + + $hashedPassword = Privileges::getHashedPassword($_POST['pma_pw']); + + $local_query = "UPDATE `mysql`.`user` SET" + . " `authentication_string` = '" . $hashedPassword + . "', `Password` = '', " + . " `plugin` = '" . $orig_auth_plugin . "'" + . " WHERE `User` = '" . $username . "' AND Host = '" + . $hostname . "';"; + } else { + $local_query = 'SET password = ' . (($password == '') + ? '\'\'' + : $hashing_function . '(\'' + . $GLOBALS['dbi']->escapeString($password) . '\')'); + } + if (! @$GLOBALS['dbi']->tryQuery($local_query)) { + Util::mysqlDie( + $GLOBALS['dbi']->getError(), + $sql_query, + false, + $err_url + ); + } + + // Flush privileges after successful password change + $GLOBALS['dbi']->tryQuery("FLUSH PRIVILEGES;"); + } + + /** + * Display the page + * + * @param string $message Message + * @param string $sql_query SQL query + * + * @return void + */ + private function changePassDisplayPage($message, $sql_query) + { + echo '

    ' , __('Change password') , '

    ' , "\n\n"; + echo Util::getMessage( + $message, $sql_query, 'success' + ); + echo '' , "\n" + , '' , __('Back') , ''; + exit; + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/UserPreferences.php b/php/apps/phpmyadmin49/html/libraries/classes/UserPreferences.php new file mode 100644 index 00000000..76723fcd --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/UserPreferences.php @@ -0,0 +1,274 @@ +relation = new Relation(); + } + + /** + * Common initialization for user preferences modification pages + * + * @param ConfigFile $cf Config file instance + * + * @return void + */ + public function pageInit(ConfigFile $cf) + { + $forms_all_keys = UserFormList::getFields(); + $cf->resetConfigData(); // start with a clean instance + $cf->setAllowedKeys($forms_all_keys); + $cf->setCfgUpdateReadMapping( + array( + 'Server/hide_db' => 'Servers/1/hide_db', + 'Server/only_db' => 'Servers/1/only_db' + ) + ); + $cf->updateWithGlobalConfig($GLOBALS['cfg']); + } + + /** + * Loads user preferences + * + * Returns an array: + * * config_data - path => value pairs + * * mtime - last modification time + * * type - 'db' (config read from pmadb) or 'session' (read from user session) + * + * @return array + */ + public function load() + { + $cfgRelation = $this->relation->getRelationsParam(); + if (! $cfgRelation['userconfigwork']) { + // no pmadb table, use session storage + if (! isset($_SESSION['userconfig'])) { + $_SESSION['userconfig'] = array( + 'db' => array(), + 'ts' => time()); + } + return array( + 'config_data' => $_SESSION['userconfig']['db'], + 'mtime' => $_SESSION['userconfig']['ts'], + 'type' => 'session'); + } + // load configuration from pmadb + $query_table = Util::backquote($cfgRelation['db']) . '.' + . Util::backquote($cfgRelation['userconfig']); + $query = 'SELECT `config_data`, UNIX_TIMESTAMP(`timevalue`) ts' + . ' FROM ' . $query_table + . ' WHERE `username` = \'' + . $GLOBALS['dbi']->escapeString($cfgRelation['user']) + . '\''; + $row = $GLOBALS['dbi']->fetchSingleRow($query, 'ASSOC', DatabaseInterface::CONNECT_CONTROL); + + return array( + 'config_data' => $row ? json_decode($row['config_data'], true) : array(), + 'mtime' => $row ? $row['ts'] : time(), + 'type' => 'db'); + } + + /** + * Saves user preferences + * + * @param array $config_array configuration array + * + * @return true|PhpMyAdmin\Message + */ + public function save(array $config_array) + { + $cfgRelation = $this->relation->getRelationsParam(); + $server = isset($GLOBALS['server']) + ? $GLOBALS['server'] + : $GLOBALS['cfg']['ServerDefault']; + $cache_key = 'server_' . $server; + if (! $cfgRelation['userconfigwork']) { + // no pmadb table, use session storage + $_SESSION['userconfig'] = array( + 'db' => $config_array, + 'ts' => time()); + if (isset($_SESSION['cache'][$cache_key]['userprefs'])) { + unset($_SESSION['cache'][$cache_key]['userprefs']); + } + return true; + } + + // save configuration to pmadb + $query_table = Util::backquote($cfgRelation['db']) . '.' + . Util::backquote($cfgRelation['userconfig']); + $query = 'SELECT `username` FROM ' . $query_table + . ' WHERE `username` = \'' + . $GLOBALS['dbi']->escapeString($cfgRelation['user']) + . '\''; + + $has_config = $GLOBALS['dbi']->fetchValue( + $query, 0, 0, DatabaseInterface::CONNECT_CONTROL + ); + $config_data = json_encode($config_array); + if ($has_config) { + $query = 'UPDATE ' . $query_table + . ' SET `timevalue` = NOW(), `config_data` = \'' + . $GLOBALS['dbi']->escapeString($config_data) + . '\'' + . ' WHERE `username` = \'' + . $GLOBALS['dbi']->escapeString($cfgRelation['user']) + . '\''; + } else { + $query = 'INSERT INTO ' . $query_table + . ' (`username`, `timevalue`,`config_data`) ' + . 'VALUES (\'' + . $GLOBALS['dbi']->escapeString($cfgRelation['user']) . '\', NOW(), ' + . '\'' . $GLOBALS['dbi']->escapeString($config_data) . '\')'; + } + if (isset($_SESSION['cache'][$cache_key]['userprefs'])) { + unset($_SESSION['cache'][$cache_key]['userprefs']); + } + if (!$GLOBALS['dbi']->tryQuery($query, DatabaseInterface::CONNECT_CONTROL)) { + $message = Message::error(__('Could not save configuration')); + $message->addMessage( + Message::rawError( + $GLOBALS['dbi']->getError(DatabaseInterface::CONNECT_CONTROL) + ), + '

    ' + ); + return $message; + } + return true; + } + + /** + * Returns a user preferences array filtered by $cfg['UserprefsDisallow'] + * (blacklist) and keys from user preferences form (whitelist) + * + * @param array $config_data path => value pairs + * + * @return array + */ + public function apply(array $config_data) + { + $cfg = array(); + $blacklist = array_flip($GLOBALS['cfg']['UserprefsDisallow']); + $whitelist = array_flip(UserFormList::getFields()); + // whitelist some additional fields which are custom handled + $whitelist['ThemeDefault'] = true; + $whitelist['lang'] = true; + $whitelist['Server/hide_db'] = true; + $whitelist['Server/only_db'] = true; + $whitelist['2fa'] = true; + foreach ($config_data as $path => $value) { + if (! isset($whitelist[$path]) || isset($blacklist[$path])) { + continue; + } + Core::arrayWrite($path, $cfg, $value); + } + return $cfg; + } + + /** + * Updates one user preferences option (loads and saves to database). + * + * No validation is done! + * + * @param string $path configuration + * @param mixed $value value + * @param mixed $default_value default value + * + * @return true|PhpMyAdmin\Message + */ + public function persistOption($path, $value, $default_value) + { + $prefs = $this->load(); + if ($value === $default_value) { + if (isset($prefs['config_data'][$path])) { + unset($prefs['config_data'][$path]); + } else { + return true; + } + } else { + $prefs['config_data'][$path] = $value; + } + return $this->save($prefs['config_data']); + } + + /** + * Redirects after saving new user preferences + * + * @param string $file_name Filename + * @param array|null $params URL parameters + * @param string $hash Hash value + * + * @return void + */ + public function redirect($file_name, + $params = null, $hash = null + ) { + // redirect + $url_params = array('saved' => 1); + if (is_array($params)) { + $url_params = array_merge($params, $url_params); + } + if ($hash) { + $hash = '#' . urlencode($hash); + } + Core::sendHeaderLocation('./' . $file_name + . Url::getCommonRaw($url_params) . $hash + ); + } + + /** + * Shows form which allows to quickly load + * settings stored in browser's local storage + * + * @return string + */ + public function autoloadGetHeader() + { + if (isset($_REQUEST['prefs_autoload']) + && $_REQUEST['prefs_autoload'] == 'hide' + ) { + $_SESSION['userprefs_autoload'] = true; + return ''; + } + + $script_name = basename(basename($GLOBALS['PMA_PHP_SELF'])); + $return_url = $script_name . '?' . http_build_query($_GET, '', '&'); + + return Template::get('prefs_autoload') + ->render( + array( + 'hidden_inputs' => Url::getHiddenInputs(), + 'return_url' => $return_url, + ) + ); + } +} diff --git a/php/apps/phpmyadmin49/html/libraries/classes/Util.php b/php/apps/phpmyadmin49/html/libraries/classes/Util.php new file mode 100644 index 00000000..65c889ab --- /dev/null +++ b/php/apps/phpmyadmin49/html/libraries/classes/Util.php @@ -0,0 +1,4744 @@ +'; + if ($include_icon) { + $button .= self::getImage($icon, $alternate); + } + if ($include_icon && $include_text) { + $button .= ' '; + } + if ($include_text) { + $button .= $alternate; + } + $button .= $menu_icon ? '' : ''; + + return $button; + } + + /** + * Returns an HTML IMG tag for a particular image from a theme + * + * The image name should match CSS class defined in icons.css.php + * + * @param string $image The name of the file to get + * @param string $alternate Used to set 'alt' and 'title' attributes + * of the image + * @param array $attributes An associative array of other attributes + * + * @return string an html IMG tag + */ + public static function getImage($image, $alternate = '', array $attributes = array()) + { + $alternate = htmlspecialchars($alternate); + + // Set $url accordingly + if (isset($GLOBALS['pmaThemeImage'])) { + $url = $GLOBALS['pmaThemeImage'] . $image; + } else { + $url = './themes/pmahomme/' . $image; + } + + if (isset($attributes['class'])) { + $attributes['class'] = "icon ic_$image " . $attributes['class']; + } else { + $attributes['class'] = "icon ic_$image"; + } + + // set all other attributes + $attr_str = ''; + foreach ($attributes as $key => $value) { + if (! in_array($key, array('alt', 'title'))) { + $attr_str .= " $key=\"$value\""; + } + } + + // override the alt attribute + if (isset($attributes['alt'])) { + $alt = $attributes['alt']; + } else { + $alt = $alternate; + } + + // override the title attribute + if (isset($attributes['title'])) { + $title = $attributes['title']; + } else { + $title = $alternate; + } + + // generate the IMG tag + $template = '%s'; + $retval = sprintf($template, $title, $alt, $attr_str); + + return $retval; + } + + /** + * Returns the formatted maximum size for an upload + * + * @param integer $max_upload_size the size + * + * @return string the message + * + * @access public + */ + public static function getFormattedMaximumUploadSize($max_upload_size) + { + // I have to reduce the second parameter (sensitiveness) from 6 to 4 + // to avoid weird results like 512 kKib + list($max_size, $max_unit) = self::formatByteDown($max_upload_size, 4); + return '(' . sprintf(__('Max: %s%s'), $max_size, $max_unit) . ')'; + } + + /** + * Generates a hidden field which should indicate to the browser + * the maximum size for upload + * + * @param integer $max_size the size + * + * @return string the INPUT field + * + * @access public + */ + public static function generateHiddenMaxFileSize($max_size) + { + return ''; + } + + /** + * Add slashes before "_" and "%" characters for using them in MySQL + * database, table and field names. + * Note: This function does not escape backslashes! + * + * @param string $name the string to escape + * + * @return string the escaped string + * + * @access public + */ + public static function escapeMysqlWildcards($name) + { + return strtr($name, array('_' => '\\_', '%' => '\\%')); + } // end of the 'escapeMysqlWildcards()' function + + /** + * removes slashes before "_" and "%" characters + * Note: This function does not unescape backslashes! + * + * @param string $name the string to escape + * + * @return string the escaped string + * + * @access public + */ + public static function unescapeMysqlWildcards($name) + { + return strtr($name, array('\\_' => '_', '\\%' => '%')); + } // end of the 'unescapeMysqlWildcards()' function + + /** + * removes quotes (',",`) from a quoted string + * + * checks if the string is quoted and removes this quotes + * + * @param string $quoted_string string to remove quotes from + * @param string $quote type of quote to remove + * + * @return string unqoted string + */ + public static function unQuote($quoted_string, $quote = null) + { + $quotes = array(); + + if ($quote === null) { + $quotes[] = '`'; + $quotes[] = '"'; + $quotes[] = "'"; + } else { + $quotes[] = $quote; + } + + foreach ($quotes as $quote) { + if (mb_substr($quoted_string, 0, 1) === $quote + && mb_substr($quoted_string, -1, 1) === $quote + ) { + $unquoted_string = mb_substr($quoted_string, 1, -1); + // replace escaped quotes + $unquoted_string = str_replace( + $quote . $quote, + $quote, + $unquoted_string + ); + return $unquoted_string; + } + } + + return $quoted_string; + } + + /** + * format sql strings + * + * @param string $sqlQuery raw SQL string + * @param boolean $truncate truncate the query if it is too long + * + * @return string the formatted sql + * + * @global array $cfg the configuration array + * + * @access public + * @todo move into PMA_Sql + */ + public static function formatSql($sqlQuery, $truncate = false) + { + global $cfg; + + if ($truncate + && mb_strlen($sqlQuery) > $cfg['MaxCharactersInDisplayedSQL'] + ) { + $sqlQuery = mb_substr( + $sqlQuery, + 0, + $cfg['MaxCharactersInDisplayedSQL'] + ) . '[...]'; + } + return '
    ' . "\n"
    +            . htmlspecialchars($sqlQuery) . "\n"
    +            . '
    '; + } // end of the "formatSql()" function + + /** + * Displays a link to the documentation as an icon + * + * @param string $link documentation link + * @param string $target optional link target + * @param boolean $bbcode optional flag indicating whether to output bbcode + * + * @return string the html link + * + * @access public + */ + public static function showDocLink($link, $target = 'documentation', $bbcode = false) + { + if($bbcode){ + return "[a@$link@$target][dochelpicon][/a]"; + } + + return '' + . self::getImage('b_help', __('Documentation')) + . ''; + } // end of the 'showDocLink()' function + + /** + * Get a URL link to the official MySQL documentation + * + * @param string $link contains name of page/anchor that is being linked + * @param string $anchor anchor to page part + * + * @return string the URL link + * + * @access public + */ + public static function getMySQLDocuURL($link, $anchor = '') + { + // Fixup for newly used names: + $link = str_replace('_', '-', mb_strtolower($link)); + + if (empty($link)) { + $link = 'index'; + } + $mysql = '5.5'; + $lang = 'en'; + if (isset($GLOBALS['dbi'])) { + $serverVersion = $GLOBALS['dbi']->getVersion(); + if ($serverVersion >= 50700) { + $mysql = '5.7'; + } elseif ($serverVersion >= 50600) { + $mysql = '5.6'; + } elseif ($serverVersion >= 50500) { + $mysql = '5.5'; + } + } + $url = 'https://dev.mysql.com/doc/refman/' + . $mysql . '/' . $lang . '/' . $link . '.html'; + if (! empty($anchor)) { + $url .= '#' . $anchor; + } + + return Core::linkURL($url); + } + + /** + * Displays a link to the official MySQL documentation + * + * @param string $link contains name of page/anchor that is being linked + * @param bool $big_icon whether to use big icon (like in left frame) + * @param string $anchor anchor to page part + * @param bool $just_open whether only the opening tag should be returned + * + * @return string the html link + * + * @access public + */ + public static function showMySQLDocu( + $link, + $big_icon = false, + $anchor = '', + $just_open = false + ) { + $url = self::getMySQLDocuURL($link, $anchor); + $open_link = ''; + if ($just_open) { + return $open_link; + } elseif ($big_icon) { + return $open_link + . self::getImage('b_sqlhelp', __('Documentation')) . ''; + } + + return self::showDocLink($url, 'mysql_doc'); + } // end of the 'showMySQLDocu()' function + + /** + * Returns link to documentation. + * + * @param string $page Page in documentation + * @param string $anchor Optional anchor in page + * + * @return string URL + */ + public static function getDocuLink($page, $anchor = '') + { + /* Construct base URL */ + $url = $page . '.html'; + if (!empty($anchor)) { + $url .= '#' . $anchor; + } + + /* Check if we have built local documentation, however + * provide consistent URL for testsuite + */ + if (! defined('TESTSUITE') && @file_exists('doc/html/index.html')) { + if ($GLOBALS['PMA_Config']->get('is_setup')) { + return '../doc/html/' . $url; + } + + return './doc/html/' . $url; + } + + return Core::linkURL('https://docs.phpmyadmin.net/en/latest/' . $url); + } + + /** + * Displays a link to the phpMyAdmin documentation + * + * @param string $page Page in documentation + * @param string $anchor Optional anchor in page + * @param boolean $bbcode Optional flag indicating whether to output bbcode + * + * @return string the html link + * + * @access public + */ + public static function showDocu($page, $anchor = '', $bbcode = false) + { + return self::showDocLink(self::getDocuLink($page, $anchor), 'documentation', $bbcode); + } // end of the 'showDocu()' function + + /** + * Displays a link to the PHP documentation + * + * @param string $target anchor in documentation + * + * @return string the html link + * + * @access public + */ + public static function showPHPDocu($target) + { + $url = Core::getPHPDocLink($target); + + return self::showDocLink($url); + } // end of the 'showPHPDocu()' function + + /** + * Returns HTML code for a tooltip + * + * @param string $message the message for the tooltip + * + * @return string + * + * @access public + */ + public static function showHint($message) + { + if ($GLOBALS['cfg']['ShowHint']) { + $classClause = ' class="pma_hint"'; + } else { + $classClause = ''; + } + return '' + . self::getImage('b_help') + . '' . $message . '' + . ''; + } + + /** + * Displays a MySQL error message in the main panel when $exit is true. + * Returns the error message otherwise. + * + * @param string|bool $server_msg Server's error message. + * @param string $sql_query The SQL query that failed. + * @param bool $is_modify_link Whether to show a "modify" link or not. + * @param string $back_url URL for the "back" link (full path is + * not required). + * @param bool $exit Whether execution should be stopped or + * the error message should be returned. + * + * @return string + * + * @global string $table The current table. + * @global string $db The current database. + * + * @access public + */ + public static function mysqlDie( + $server_msg = '', + $sql_query = '', + $is_modify_link = true, + $back_url = '', + $exit = true + ) { + global $table, $db; + + /** + * Error message to be built. + * @var string $error_msg + */ + $error_msg = ''; + + // Checking for any server errors. + if (empty($server_msg)) { + $server_msg = $GLOBALS['dbi']->getError(); + } + + // Finding the query that failed, if not specified. + if ((empty($sql_query) && (!empty($GLOBALS['sql_query'])))) { + $sql_query = $GLOBALS['sql_query']; + } + $sql_query = trim($sql_query); + + /** + * The lexer used for analysis. + * @var Lexer $lexer + */ + $lexer = new Lexer($sql_query); + + /** + * The parser used for analysis. + * @var Parser $parser + */ + $parser = new Parser($lexer->list); + + /** + * The errors found by the lexer and the parser. + * @var array $errors + */ + $errors = ParserError::get(array($lexer, $parser)); + + if (empty($sql_query)) { + $formatted_sql = ''; + } elseif (count($errors)) { + $formatted_sql = htmlspecialchars($sql_query); + } else { + $formatted_sql = self::formatSql($sql_query, true); + } + + $error_msg .= '

    ' . __('Error') . '

    '; + + // For security reasons, if the MySQL refuses the connection, the query + // is hidden so no details are revealed. + if ((!empty($sql_query)) && (!(mb_strstr($sql_query, 'connect')))) { + // Static analysis errors. + if (!empty($errors)) { + $error_msg .= '

    ' . __('Static analysis:') + . '

    '; + $error_msg .= '

    ' . sprintf( + __('%d errors were found during analysis.'), + count($errors) + ) . '

    '; + $error_msg .= '

      '; + $error_msg .= implode( + ParserError::format( + $errors, + '
    1. %2$s (near "%4$s" at position %5$d)
    2. ' + ) + ); + $error_msg .= '

    '; + } + + // Display the SQL query and link to MySQL documentation. + $error_msg .= '

    ' . __('SQL query:') . '' . "\n"; + $formattedSqlToLower = mb_strtolower($formatted_sql); + + // TODO: Show documentation for all statement types. + if (mb_strstr($formattedSqlToLower, 'select')) { + // please show me help to the error on select + $error_msg .= self::showMySQLDocu('SELECT'); + } + + if ($is_modify_link) { + $_url_params = array( + 'sql_query' => $sql_query, + 'show_query' => 1, + ); + if (strlen($table) > 0) { + $_url_params['db'] = $db; + $_url_params['table'] = $table; + $doedit_goto = ''; + } elseif (strlen($db) > 0) { + $_url_params['db'] = $db; + $doedit_goto = ''; + } else { + $doedit_goto = ''; + } + + $error_msg .= $doedit_goto + . self::getIcon('b_edit', __('Edit')) + . ''; + } + + $error_msg .= '

    ' . "\n" + . '

    ' . "\n" + . $formatted_sql . "\n" + . '

    ' . "\n"; + } + + // Display server's error. + if (!empty($server_msg)) { + $server_msg = preg_replace( + "@((\015\012)|(\015)|(\012)){3,}@", + "\n\n", + $server_msg + ); + + // Adds a link to MySQL documentation. + $error_msg .= '

    ' . "\n" + . ' ' . __('MySQL said: ') . '' + . self::showMySQLDocu('Error-messages-server') + . "\n" + . '

    ' . "\n"; + + // The error message will be displayed within a CODE segment. + // To preserve original formatting, but allow word-wrapping, + // a couple of replacements are done. + // All non-single blanks and TAB-characters are replaced with their + // HTML-counterpart + $server_msg = str_replace( + array(' ', "\t"), + array('  ', '    '), + $server_msg + ); + + // Replace line breaks + $server_msg = nl2br($server_msg); + + $error_msg .= '' . $server_msg . '
    '; + } + + $error_msg .= '
    '; + $_SESSION['Import_message']['message'] = $error_msg; + + if (!$exit) { + return $error_msg; + } + + /** + * If this is an AJAX request, there is no "Back" link and + * `Response()` is used to send the response. + */ + $response = Response::getInstance(); + if ($response->isAjax()) { + $response->setRequestStatus(false); + $response->addJSON('message', $error_msg); + exit; + } + + if (!empty($back_url)) { + if (mb_strstr($back_url, '?')) { + $back_url .= '&no_history=true'; + } else { + $back_url .= '?no_history=true'; + } + + $_SESSION['Import_message']['go_back_url'] = $back_url; + + $error_msg .= '
    ' + . '[ ' . __('Back') . ' ]' + . '
    ' . "\n\n"; + } + + exit($error_msg); + } + + /** + * Check the correct row count + * + * @param string $db the db name + * @param array $table the table infos + * + * @return int $rowCount the possibly modified row count + * + */ + private static function _checkRowCount($db, array $table) + { + $rowCount = 0; + + if ($table['Rows'] === null) { + // Do not check exact row count here, + // if row count is invalid possibly the table is defect + // and this would break the navigation panel; + // but we can check row count if this is a view or the + // information_schema database + // since Table::countRecords() returns a limited row count + // in this case. + + // set this because Table::countRecords() can use it + $tbl_is_view = $table['TABLE_TYPE'] == 'VIEW'; + + if ($tbl_is_view || $GLOBALS['dbi']->isSystemSchema($db)) { + $rowCount = $GLOBALS['dbi'] + ->getTable($db, $table['Name']) + ->countRecords(); + } + } + return $rowCount; + } + + /** + * returns array with tables of given db with extended information and grouped + * + * @param string $db name of db + * @param string $tables name of tables + * @param integer $limit_offset list offset + * @param int|bool $limit_count max tables to return + * + * @return array (recursive) grouped table list + */ + public static function getTableList( + $db, + $tables = null, + $limit_offset = 0, + $limit_count = false + ) { + $sep = $GLOBALS['cfg']['NavigationTreeTableSeparator']; + + if ($tables === null) { + $tables = $GLOBALS['dbi']->getTablesFull( + $db, + '', + false, + $limit_offset, + $limit_count + ); + if ($GLOBALS['cfg']['NaturalOrder']) { + uksort($tables, 'strnatcasecmp'); + } + } + + if (count($tables) < 1) { + return $tables; + } + + $default = array( + 'Name' => '', + 'Rows' => 0, + 'Comment' => '', + 'disp_name' => '', + ); + + $table_groups = array(); + + foreach ($tables as $table_name => $table) { + $table['Rows'] = self::_checkRowCount($db, $table); + + // in $group we save the reference to the place in $table_groups + // where to store the table info + if ($GLOBALS['cfg']['NavigationTreeEnableGrouping'] + && $sep && mb_strstr($table_name, $sep) + ) { + $parts = explode($sep, $table_name); + + $group =& $table_groups; + $i = 0; + $group_name_full = ''; + $parts_cnt = count($parts) - 1; + + while (($i < $parts_cnt) + && ($i < $GLOBALS['cfg']['NavigationTreeTableLevel']) + ) { + $group_name = $parts[$i] . $sep; + $group_name_full .= $group_name; + + if (! isset($group[$group_name])) { + $group[$group_name] = array(); + $group[$group_name]['is' . $sep . 'group'] = true; + $group[$group_name]['tab' . $sep . 'count'] = 1; + $group[$group_name]['tab' . $sep . 'group'] + = $group_name_full; + + } elseif (! isset($group[$group_name]['is' . $sep . 'group'])) { + $table = $group[$group_name]; + $group[$group_name] = array(); + $group[$group_name][$group_name] = $table; + $group[$group_name]['is' . $sep . 'group'] = true; + $group[$group_name]['tab' . $sep . 'count'] = 1; + $group[$group_name]['tab' . $sep . 'group'] + = $group_name_full; + + } else { + $group[$group_name]['tab' . $sep . 'count']++; + } + + $group =& $group[$group_name]; + $i++; + } + + } else { + if (! isset($table_groups[$table_name])) { + $table_groups[$table_name] = array(); + } + $group =& $table_groups; + } + + $table['disp_name'] = $table['Name']; + $group[$table_name] = array_merge($default, $table); + } + + return $table_groups; + } + + /* ----------------------- Set of misc functions ----------------------- */ + + /** + * Adds backquotes on both sides of a database, table or field name. + * and escapes backquotes inside the name with another backquote + * + * example: + * + * echo backquote('owner`s db'); // `owner``s db` + * + * + * + * @param mixed $a_name the database, table or field name to "backquote" + * or array of it + * @param boolean $do_it a flag to bypass this function (used by dump + * functions) + * + * @return mixed the "backquoted" database, table or field name + * + * @access public + */ + public static function backquote($a_name, $do_it = true) + { + if (is_array($a_name)) { + foreach ($a_name as &$data) { + $data = self::backquote($data, $do_it); + } + return $a_name; + } + + if (! $do_it) { + if (!(Context::isKeyword($a_name) & Token::FLAG_KEYWORD_RESERVED) + ) { + return $a_name; + } + } + + // '0' is also empty for php :-( + if (strlen($a_name) > 0 && $a_name !== '*') { + return '`' . str_replace('`', '``', $a_name) . '`'; + } + + return $a_name; + } // end of the 'backquote()' function + + /** + * Adds backquotes on both sides of a database, table or field name. + * in compatibility mode + * + * example: + * + * echo backquoteCompat('owner`s db'); // `owner``s db` + * + * + * + * @param mixed $a_name the database, table or field name to + * "backquote" or array of it + * @param string $compatibility string compatibility mode (used by dump + * functions) + * @param boolean $do_it a flag to bypass this function (used by dump + * functions) + * + * @return mixed the "backquoted" database, table or field name + * + * @access public + */ + public static function backquoteCompat( + $a_name, + $compatibility = 'MSSQL', + $do_it = true + ) { + if (is_array($a_name)) { + foreach ($a_name as &$data) { + $data = self::backquoteCompat($data, $compatibility, $do_it); + } + return $a_name; + } + + if (! $do_it) { + if (!Context::isKeyword($a_name)) { + return $a_name; + } + } + + // @todo add more compatibility cases (ORACLE for example) + switch ($compatibility) { + case 'MSSQL': + $quote = '"'; + break; + default: + $quote = "`"; + break; + } + + // '0' is also empty for php :-( + if (strlen($a_name) > 0 && $a_name !== '*') { + return $quote . $a_name . $quote; + } + + return $a_name; + } // end of the 'backquoteCompat()' function + + /** + * Prepare the message and the query + * usually the message is the result of the query executed + * + * @param Message|string $message the message to display + * @param string $sql_query the query to display + * @param string $type the type (level) of the message + * + * @return string + * + * @access public + */ + public static function getMessage( + $message, + $sql_query = null, + $type = 'notice' + ) { + global $cfg; + $retval = ''; + + if (null === $sql_query) { + if (! empty($GLOBALS['display_query'])) { + $sql_query = $GLOBALS['display_query']; + } elseif (! empty($GLOBALS['unparsed_sql'])) { + $sql_query = $GLOBALS['unparsed_sql']; + } elseif (! empty($GLOBALS['sql_query'])) { + $sql_query = $GLOBALS['sql_query']; + } else { + $sql_query = ''; + } + } + + $render_sql = $cfg['ShowSQL'] == true && ! empty($sql_query) && $sql_query !== ';'; + + if (isset($GLOBALS['using_bookmark_message'])) { + $retval .= $GLOBALS['using_bookmark_message']->getDisplay(); + unset($GLOBALS['using_bookmark_message']); + } + + if ($render_sql) { + $retval .= '
    ' . "\n"; + } + + if ($message instanceof Message) { + if (isset($GLOBALS['special_message'])) { + $message->addText($GLOBALS['special_message']); + unset($GLOBALS['special_message']); + } + $retval .= $message->getDisplay(); + } else { + $retval .= '
    '; + $retval .= Sanitize::sanitize($message); + if (isset($GLOBALS['special_message'])) { + $retval .= Sanitize::sanitize($GLOBALS['special_message']); + unset($GLOBALS['special_message']); + } + $retval .= '
    '; + } + + if ($render_sql) { + $query_too_big = false; + + $queryLength = mb_strlen($sql_query); + if ($queryLength > $cfg['MaxCharactersInDisplayedSQL']) { + // when the query is large (for example an INSERT of binary + // data), the parser chokes; so avoid parsing the query + $query_too_big = true; + $query_base = mb_substr( + $sql_query, + 0, + $cfg['MaxCharactersInDisplayedSQL'] + ) . '[...]'; + } else { + $query_base = $sql_query; + } + + // Html format the query to be displayed + // If we want to show some sql code it is easiest to create it here + /* SQL-Parser-Analyzer */ + + if (! empty($GLOBALS['show_as_php'])) { + $new_line = '\\n"
    ' . "\n" . '    . "'; + $query_base = htmlspecialchars(addslashes($query_base)); + $query_base = preg_replace( + '/((\015\012)|(\015)|(\012))/', + $new_line, + $query_base + ); + $query_base = '
    ' . "\n"
    +                    . '$sql = "' . $query_base . '";' . "\n"
    +                    . '
    '; + } elseif ($query_too_big) { + $query_base = '
    ' . "\n" .
    +                    htmlspecialchars($query_base) .
    +                    '
    '; + } else { + $query_base = self::formatSql($query_base); + } + + // Prepares links that may be displayed to edit/explain the query + // (don't go to default pages, we must go to the page + // where the query box is available) + + // Basic url query part + $url_params = array(); + if (! isset($GLOBALS['db'])) { + $GLOBALS['db'] = ''; + } + if (strlen($GLOBALS['db']) > 0) { + $url_params['db'] = $GLOBALS['db']; + if (strlen($GLOBALS['table']) > 0) { + $url_params['table'] = $GLOBALS['table']; + $edit_link = 'tbl_sql.php'; + } else { + $edit_link = 'db_sql.php'; + } + } else { + $edit_link = 'server_sql.php'; + } + + // Want to have the query explained + // but only explain a SELECT (that has not been explained) + /* SQL-Parser-Analyzer */ + $explain_link = ''; + $is_select = preg_match('@^SELECT[[:space:]]+@i', $sql_query); + if (! empty($cfg['SQLQuery']['Explain']) && ! $query_too_big) { + $explain_params = $url_params; + if ($is_select) { + $explain_params['sql_query'] = 'EXPLAIN ' . $sql_query; + $explain_link = ' [ ' + . self::linkOrButton( + 'import.php' . Url::getCommon($explain_params), + __('Explain SQL') + ) . ' ]'; + } elseif (preg_match( + '@^EXPLAIN[[:space:]]+SELECT[[:space:]]+@i', + $sql_query + )) { + $explain_params['sql_query'] + = mb_substr($sql_query, 8); + $explain_link = ' [ ' + . self::linkOrButton( + 'import.php' . Url::getCommon($explain_params), + __('Skip Explain SQL') + ) . ']'; + $url = 'https://mariadb.org/explain_analyzer/analyze/' + . '?client=phpMyAdmin&raw_explain=' + . urlencode(self::_generateRowQueryOutput($sql_query)); + $explain_link .= ' [' + . self::linkOrButton( + htmlspecialchars('url.php?url=' . urlencode($url)), + sprintf(__('Analyze Explain at %s'), 'mariadb.org'), + array(), + '_blank' + ) . ' ]'; + } + } //show explain + + $url_params['sql_query'] = $sql_query; + $url_params['show_query'] = 1; + + // even if the query is big and was truncated, offer the chance + // to edit it (unless it's enormous, see linkOrButton() ) + if (! empty($cfg['SQLQuery']['Edit']) + && empty($GLOBALS['show_as_php']) + ) { + $edit_link .= Url::getCommon($url_params); + $edit_link = ' [ ' + . self::linkOrButton($edit_link, __('Edit')) + . ' ]'; + } else { + $edit_link = ''; + } + + // Also we would like to get the SQL formed in some nice + // php-code + if (! empty($cfg['SQLQuery']['ShowAsPHP']) && ! $query_too_big) { + + if (! empty($GLOBALS['show_as_php'])) { + $php_link = ' [ ' + . self::linkOrButton( + 'import.php' . Url::getCommon($url_params), + __('Without PHP code') + ) + . ' ]'; + + $php_link .= ' [ ' + . self::linkOrButton( + 'import.php' . Url::getCommon($url_params), + __('Submit query') + ) + . ' ]'; + } else { + $php_params = $url_params; + $php_params['show_as_php'] = 1; + $php_link = ' [ ' + . self::linkOrButton( + 'import.php' . Url::getCommon($php_params), + __('Create PHP code') + ) + . ' ]'; + } + } else { + $php_link = ''; + } //show as php + + // Refresh query + if (! empty($cfg['SQLQuery']['Refresh']) + && ! isset($GLOBALS['show_as_php']) // 'Submit query' does the same + && preg_match('@^(SELECT|SHOW)[[:space:]]+@i', $sql_query) + ) { + $refresh_link = 'import.php' . Url::getCommon($url_params); + $refresh_link = ' [ ' + . self::linkOrButton($refresh_link, __('Refresh')) . ']'; + } else { + $refresh_link = ''; + } //refresh + + $retval .= '
    '; + $retval .= $query_base; + $retval .= '
    '; + + $retval .= ''; + + $retval .= '
    '; + } + + return $retval; + } // end of the 'getMessage()' function + + /** + * Execute an EXPLAIN query and formats results similar to MySQL command line + * utility. + * + * @param string $sqlQuery EXPLAIN query + * + * @return string query resuls + */ + private static function _generateRowQueryOutput($sqlQuery) + { + $ret = ''; + $result = $GLOBALS['dbi']->query($sqlQuery); + if ($result) { + $devider = '+'; + $columnNames = '|'; + $fieldsMeta = $GLOBALS['dbi']->getFieldsMeta($result); + foreach ($fieldsMeta as $meta) { + $devider .= '---+'; + $columnNames .= ' ' . $meta->name . ' |'; + } + $devider .= "\n"; + + $ret .= $devider . $columnNames . "\n" . $devider; + while ($row = $GLOBALS['dbi']->fetchRow($result)) { + $values = '|'; + foreach ($row as $value) { + if (is_null($value)) { + $value = 'NULL'; + } + $values .= ' ' . $value . ' |'; + } + $ret .= $values . "\n"; + } + $ret .= $devider; + } + return $ret; + } + + /** + * Verifies if current MySQL server supports profiling + * + * @access public + * + * @return boolean whether profiling is supported + */ + public static function profilingSupported() + { + if (!self::cacheExists('profiling_supported')) { + // 5.0.37 has profiling but for example, 5.1.20 does not + // (avoid a trip to the server for MySQL before 5.0.37) + // and do not set a constant as we might be switching servers + if ($GLOBALS['dbi']->fetchValue("SELECT @@have_profiling") + ) { + self::cacheSet('profiling_supported', true); + } else { + self::cacheSet('profiling_supported', false); + } + } + + return self::cacheGet('profiling_supported'); + } + + /** + * Formats $value to byte view + * + * @param double|int $value the value to format + * @param int $limes the sensitiveness + * @param int $comma the number of decimals to retain + * + * @return array the formatted value and its unit + * + * @access public + */ + public static function formatByteDown($value, $limes = 6, $comma = 0) + { + if ($value === null) { + return null; + } + + $byteUnits = array( + /* l10n: shortcuts for Byte */ + __('B'), + /* l10n: shortcuts for Kilobyte */ + __('KiB'), + /* l10n: shortcuts for Megabyte */ + __('MiB'), + /* l10n: shortcuts for Gigabyte */ + __('GiB'), + /* l10n: shortcuts for Terabyte */ + __('TiB'), + /* l10n: shortcuts for Petabyte */ + __('PiB'), + /* l10n: shortcuts for Exabyte */ + __('EiB') + ); + + $dh = pow(10, $comma); + $li = pow(10, $limes); + $unit = $byteUnits[0]; + + for ($d = 6, $ex = 15; $d >= 1; $d--, $ex-=3) { + $unitSize = $li * pow(10, $ex); + if (isset($byteUnits[$d]) && $value >= $unitSize) { + // use 1024.0 to avoid integer overflow on 64-bit machines + $value = round($value / (pow(1024, $d) / $dh)) /$dh; + $unit = $byteUnits[$d]; + break 1; + } // end if + } // end for + + if ($unit != $byteUnits[0]) { + // if the unit is not bytes (as represented in current language) + // reformat with max length of 5 + // 4th parameter=true means do not reformat if value < 1 + $return_value = self::formatNumber($value, 5, $comma, true); + } else { + // do not reformat, just handle the locale + $return_value = self::formatNumber($value, 0); + } + + return array(trim($return_value), $unit); + } // end of the 'formatByteDown' function + + + /** + * Formats $value to the given length and appends SI prefixes + * with a $length of 0 no truncation occurs, number is only formatted + * to the current locale + * + * examples: + * + * echo formatNumber(123456789, 6); // 123,457 k + * echo formatNumber(-123456789, 4, 2); // -123.46 M + * echo formatNumber(-0.003, 6); // -3 m + * echo formatNumber(0.003, 3, 3); // 0.003 + * echo formatNumber(0.00003, 3, 2); // 0.03 m + * echo formatNumber(0, 6); // 0 + * + * + * @param double $value the value to format + * @param integer $digits_left number of digits left of the comma + * @param integer $digits_right number of digits right of the comma + * @param boolean $only_down do not reformat numbers below 1 + * @param boolean $noTrailingZero removes trailing zeros right of the comma + * (default: true) + * + * @return string the formatted value and its unit + * + * @access public + */ + public static function formatNumber( + $value, + $digits_left = 3, + $digits_right = 0, + $only_down = false, + $noTrailingZero = true + ) { + if ($value == 0) { + return '0'; + } + + $originalValue = $value; + //number_format is not multibyte safe, str_replace is safe + if ($digits_left === 0) { + $value = number_format( + $value, + $digits_right, + /* l10n: Decimal separator */ + __('.'), + /* l10n: Thousands separator */ + __(',') + ); + if (($originalValue != 0) && (floatval($value) == 0)) { + $value = ' <' . (1 / pow(10, $digits_right)); + } + return $value; + } + + // this units needs no translation, ISO + $units = array( + -8 => 'y', + -7 => 'z', + -6 => 'a', + -5 => 'f', + -4 => 'p', + -3 => 'n', + -2 => 'µ', + -1 => 'm', + 0 => ' ', + 1 => 'k', + 2 => 'M', + 3 => 'G', + 4 => 'T', + 5 => 'P', + 6 => 'E', + 7 => 'Z', + 8 => 'Y' + ); + /* l10n: Decimal separator */ + $decimal_sep = __('.'); + /* l10n: Thousands separator */ + $thousands_sep = __(','); + + // check for negative value to retain sign + if ($value < 0) { + $sign = '-'; + $value = abs($value); + } else { + $sign = ''; + } + + $dh = pow(10, $digits_right); + + /* + * This gives us the right SI prefix already, + * but $digits_left parameter not incorporated + */ + $d = floor(log10($value) / 3); + /* + * Lowering the SI prefix by 1 gives us an additional 3 zeros + * So if we have 3,6,9,12.. free digits ($digits_left - $cur_digits) + * to use, then lower the SI prefix + */ + $cur_digits = floor(log10($value / pow(1000, $d))+1); + if ($digits_left > $cur_digits) { + $d -= floor(($digits_left - $cur_digits)/3); + } + + if ($d < 0 && $only_down) { + $d = 0; + } + + $value = round($value / (pow(1000, $d) / $dh)) /$dh; + $unit = $units[$d]; + + // number_format is not multibyte safe, str_replace is safe + $formattedValue = number_format( + $value, + $digits_right, + $decimal_sep, + $thousands_sep + ); + // If we don't want any zeros, remove them now + if ($noTrailingZero && strpos($formattedValue, $decimal_sep) !== false) { + $formattedValue = preg_replace('/' . preg_quote($decimal_sep) . '?0+$/', '', $formattedValue); + } + + if ($originalValue != 0 && floatval($value) == 0) { + return ' <' . number_format( + (1 / pow(10, $digits_right)), + $digits_right, + $decimal_sep, + $thousands_sep + ) + . ' ' . $unit; + } + + return $sign . $formattedValue . ' ' . $unit; + } // end of the 'formatNumber' function + + /** + * Returns the number of bytes when a formatted size is given + * + * @param string $formatted_size the size expression (for example 8MB) + * + * @return integer The numerical part of the expression (for example 8) + */ + public static function extractValueFromFormattedSize($formatted_size) + { + $return_value = -1; + + if (preg_match('/^[0-9]+GB$/', $formatted_size)) { + $return_value = mb_substr($formatted_size, 0, -2) + * pow(1024, 3); + } elseif (preg_match('/^[0-9]+MB$/', $formatted_size)) { + $return_value = mb_substr($formatted_size, 0, -2) + * pow(1024, 2); + } elseif (preg_match('/^[0-9]+K$/', $formatted_size)) { + $return_value = mb_substr($formatted_size, 0, -1) + * pow(1024, 1); + } + return $return_value; + }// end of the 'extractValueFromFormattedSize' function + + /** + * Writes localised date + * + * @param integer $timestamp the current timestamp + * @param string $format format + * + * @return string the formatted date + * + * @access public + */ + public static function localisedDate($timestamp = -1, $format = '') + { + $month = array( + /* l10n: Short month name */ + __('Jan'), + /* l10n: Short month name */ + __('Feb'), + /* l10n: Short month name */ + __('Mar'), + /* l10n: Short month name */ + __('Apr'), + /* l10n: Short month name */ + _pgettext('Short month name', 'May'), + /* l10n: Short month name */ + __('Jun'), + /* l10n: Short month name */ + __('Jul'), + /* l10n: Short month name */ + __('Aug'), + /* l10n: Short month name */ + __('Sep'), + /* l10n: Short month name */ + __('Oct'), + /* l10n: Short month name */ + __('Nov'), + /* l10n: Short month name */ + __('Dec')); + $day_of_week = array( + /* l10n: Short week day name */ + _pgettext('Short week day name', 'Sun'), + /* l10n: Short week day name */ + __('Mon'), + /* l10n: Short week day name */ + __('Tue'), + /* l10n: Short week day name */ + __('Wed'), + /* l10n: Short week day name */ + __('Thu'), + /* l10n: Short week day name */ + __('Fri'), + /* l10n: Short week day name */ + __('Sat')); + + if ($format == '') { + /* l10n: See https://secure.php.net/manual/en/function.strftime.php */ + $format = __('%B %d, %Y at %I:%M %p'); + } + + if ($timestamp == -1) { + $timestamp = time(); + } + + $date = preg_replace( + '@%[aA]@', + $day_of_week[(int)strftime('%w', $timestamp)], + $format + ); + $date = preg_replace( + '@%[bB]@', + $month[(int)strftime('%m', $timestamp)-1], + $date + ); + + /* Fill in AM/PM */ + $hours = (int)date('H', $timestamp); + if ($hours >= 12) { + $am_pm = _pgettext('AM/PM indication in time', 'PM'); + } else { + $am_pm = _pgettext('AM/PM indication in time', 'AM'); + } + $date = preg_replace('@%[pP]@', $am_pm, $date); + + $ret = strftime($date, $timestamp); + // Some OSes such as Win8.1 Traditional Chinese version did not produce UTF-8 + // output here. See https://github.com/phpmyadmin/phpmyadmin/issues/10598 + if (mb_detect_encoding($ret, 'UTF-8', true) != 'UTF-8') { + $ret = date('Y-m-d H:i:s', $timestamp); + } + + return $ret; + } // end of the 'localisedDate()' function + + /** + * returns a tab for tabbed navigation. + * If the variables $link and $args ar left empty, an inactive tab is created + * + * @param array $tab array with all options + * @param array $url_params tab specific URL parameters + * + * @return string html code for one tab, a link if valid otherwise a span + * + * @access public + */ + public static function getHtmlTab(array $tab, array $url_params = array()) + { + // default values + $defaults = array( + 'text' => '', + 'class' => '', + 'active' => null, + 'link' => '', + 'sep' => '?', + 'attr' => '', + 'args' => '', + 'warning' => '', + 'fragment' => '', + 'id' => '', + ); + + $tab = array_merge($defaults, $tab); + + // determine additional style-class + if (empty($tab['class'])) { + if (! empty($tab['active']) + || Core::isValid($GLOBALS['active_page'], 'identical', $tab['link']) + ) { + $tab['class'] = 'active'; + } elseif (is_null($tab['active']) && empty($GLOBALS['active_page']) + && (basename($GLOBALS['PMA_PHP_SELF']) == $tab['link']) + ) { + $tab['class'] = 'active'; + } + } + + // build the link + if (! empty($tab['link'])) { + // If there are any tab specific URL parameters, merge those with + // the general URL parameters + if (! empty($tab['args']) && is_array($tab['args'])) { + $url_params = array_merge($url_params, $tab['args']); + } + $tab['link'] = htmlentities($tab['link']) . Url::getCommon($url_params); + } + + if (! empty($tab['fragment'])) { + $tab['link'] .= $tab['fragment']; + } + + // display icon + if (isset($tab['icon'])) { + // avoid generating an alt tag, because it only illustrates + // the text that follows and if browser does not display + // images, the text is duplicated + $tab['text'] = self::getIcon( + $tab['icon'], + $tab['text'], + false, + true, + 'TabsMode' + ); + + } elseif (empty($tab['text'])) { + // check to not display an empty link-text + $tab['text'] = '?'; + trigger_error( + 'empty linktext in function ' . __FUNCTION__ . '()', + E_USER_NOTICE + ); + } + + //Set the id for the tab, if set in the params + $tabId = (empty($tab['id']) ? null : $tab['id']); + + $item = array(); + if (!empty($tab['link'])) { + $item = array( + 'content' => $tab['text'], + 'url' => array( + 'href' => empty($tab['link']) ? null : $tab['link'], + 'id' => $tabId, + 'class' => 'tab' . htmlentities($tab['class']), + ), + ); + } else { + $item['content'] = '' . $tab['text'] . ''; + } + + $item['class'] = $tab['class'] == 'active' ? 'active' : ''; + + return Template::get('list/item') + ->render($item); + } // end of the 'getHtmlTab()' function + + /** + * returns html-code for a tab navigation + * + * @param array $tabs one element per tab + * @param array $url_params additional URL parameters + * @param string $menu_id HTML id attribute for the menu container + * @param bool $resizable whether to add a "resizable" class + * + * @return string html-code for tab-navigation + */ + public static function getHtmlTabs( + array $tabs, + array $url_params, + $menu_id, + $resizable = false + ) { + $class = ''; + if ($resizable) { + $class = ' class="resizable-menu"'; + } + + $tab_navigation = '' . "\n"; + + return $tab_navigation; + } + + /** + * Displays a link, or a link with code to trigger POST request. + * + * POST is used in following cases: + * + * - URL is too long + * - URL components are over Suhosin limits + * - There is SQL query in the parameters + * + * @param string $url the URL + * @param string $message the link message + * @param mixed $tag_params string: js confirmation; array: additional tag + * params (f.e. style="") + * @param string $target target + * + * @return string the results to be echoed or saved in an array + */ + public static function linkOrButton( + $url, $message, $tag_params = array(), $target = '' + ) { + $url_length = strlen($url); + + if (! is_array($tag_params)) { + $tmp = $tag_params; + $tag_params = array(); + if (! empty($tmp)) { + $tag_params['onclick'] = 'return confirmLink(this, \'' + . Sanitize::escapeJsString($tmp) . '\')'; + } + unset($tmp); + } + if (! empty($target)) { + $tag_params['target'] = $target; + if ($target === '_blank' && strncmp($url, 'url.php?', 8) == 0) { + $tag_params['rel'] = 'noopener noreferrer'; + } + } + + // Suhosin: Check that each query parameter is not above maximum + $in_suhosin_limits = true; + if ($url_length <= $GLOBALS['cfg']['LinkLengthLimit']) { + $suhosin_get_MaxValueLength = ini_get('suhosin.get.max_value_length'); + if ($suhosin_get_MaxValueLength) { + $query_parts = self::splitURLQuery($url); + foreach ($query_parts as $query_pair) { + if (strpos($query_pair, '=') === false) { + continue; + } + + list(, $eachval) = explode('=', $query_pair); + if (strlen($eachval) > $suhosin_get_MaxValueLength + ) { + $in_suhosin_limits = false; + break; + } + } + } + } + + $tag_params_strings = array(); + if (($url_length > $GLOBALS['cfg']['LinkLengthLimit']) + || ! $in_suhosin_limits + // Has as sql_query without a signature + || ( strpos($url, 'sql_query=') !== false && strpos($url, 'sql_signature=') === false) + || strpos($url, 'view[as]=') !== false + ) { + $parts = explode('?', $url, 2); + /* + * The data-post indicates that client should do POST + * this is handled in js/ajax.js + */ + $tag_params_strings[] = 'data-post="' . (isset($parts[1]) ? $parts[1] : '') . '"'; + $url = $parts[0]; + if(array_key_exists('class', $tag_params) + && strpos($tag_params['class'], 'create_view') !== false + ) { + $url .= '?' . explode('&', $parts[1], 2)[0]; + } + + } + + foreach ($tag_params as $par_name => $par_value) { + $tag_params_strings[] = $par_name . '="' . htmlspecialchars($par_value) . '"'; + } + + // no whitespace within an else Safari will make it part of the link + return '' + . $message . ''; + } // end of the 'linkOrButton()' function + + /** + * Splits a URL string by parameter + * + * @param string $url the URL + * + * @return array the parameter/value pairs, for example [0] db=sakila + */ + public static function splitURLQuery($url) + { + // decode encoded url separators + $separator = Url::getArgSeparator(); + // on most places separator is still hard coded ... + if ($separator !== '&') { + // ... so always replace & with $separator + $url = str_replace(htmlentities('&'), $separator, $url); + $url = str_replace('&', $separator, $url); + } + + $url = str_replace(htmlentities($separator), $separator, $url); + // end decode + + $url_parts = parse_url($url); + + if (! empty($url_parts['query'])) { + return explode($separator, $url_parts['query']); + } + + return array(); + } + + /** + * Returns a given timespan value in a readable format. + * + * @param int $seconds the timespan + * + * @return string the formatted value + */ + public static function timespanFormat($seconds) + { + $days = floor($seconds / 86400); + if ($days > 0) { + $seconds -= $days * 86400; + } + + $hours = floor($seconds / 3600); + if ($days > 0 || $hours > 0) { + $seconds -= $hours * 3600; + } + + $minutes = floor($seconds / 60); + if ($days > 0 || $hours > 0 || $minutes > 0) { + $seconds -= $minutes * 60; + } + + return sprintf( + __('%s days, %s hours, %s minutes and %s seconds'), + (string)$days, + (string)$hours, + (string)$minutes, + (string)$seconds + ); + } + + /** + * Function added to avoid path disclosures. + * Called by each script that needs parameters, it displays + * an error message and, by default, stops the execution. + * + * @param string[] $params The names of the parameters needed by the calling + * script + * @param boolean $request Check parameters in request + * + * @return void + * + * @access public + */ + public static function checkParameters($params, $request=false) + { + $reported_script_name = basename($GLOBALS['PMA_PHP_SELF']); + $found_error = false; + $error_message = ''; + if ($request) { + $array = $_REQUEST; + } else { + $array = $GLOBALS; + } + + foreach ($params as $param) { + if (! isset($array[$param])) { + $error_message .= $reported_script_name + . ': ' . __('Missing parameter:') . ' ' + . $param + . self::showDocu('faq', 'faqmissingparameters',true) + . '[br]'; + $found_error = true; + } + } + if ($found_error) { + Core::fatalError($error_message); + } + } // end function + + /** + * Function to generate unique condition for specified row. + * + * @param resource $handle current query result + * @param integer $fields_cnt number of fields + * @param array $fields_meta meta information about fields + * @param array $row current row + * @param boolean $force_unique generate condition only on pk + * or unique + * @param string|boolean $restrict_to_table restrict the unique condition + * to this table or false if + * none + * @param array|null $analyzed_sql_results the analyzed query + * + * @access public + * + * @return array the calculated condition and whether condition is unique + */ + public static function getUniqueCondition( + $handle, $fields_cnt, array $fields_meta, array $row, $force_unique = false, + $restrict_to_table = false, $analyzed_sql_results = null + ) { + $primary_key = ''; + $unique_key = ''; + $nonprimary_condition = ''; + $preferred_condition = ''; + $primary_key_array = array(); + $unique_key_array = array(); + $nonprimary_condition_array = array(); + $condition_array = array(); + + for ($i = 0; $i < $fields_cnt; ++$i) { + + $con_val = ''; + $field_flags = $GLOBALS['dbi']->fieldFlags($handle, $i); + $meta = $fields_meta[$i]; + + // do not use a column alias in a condition + if (! isset($meta->orgname) || strlen($meta->orgname) === 0) { + $meta->orgname = $meta->name; + + if (!empty($analyzed_sql_results['statement']->expr)) { + foreach ($analyzed_sql_results['statement']->expr as $expr) { + if ((empty($expr->alias)) || (empty($expr->column))) { + continue; + } + if (strcasecmp($meta->name, $expr->alias) == 0) { + $meta->orgname = $expr->column; + break; + } + } + } + } + + // Do not use a table alias in a condition. + // Test case is: + // select * from galerie x WHERE + //(select count(*) from galerie y where y.datum=x.datum)>1 + // + // But orgtable is present only with mysqli extension so the + // fix is only for mysqli. + // Also, do not use the original table name if we are dealing with + // a view because this view might be updatable. + // (The isView() verification should not be costly in most cases + // because there is some caching in the function). + if (isset($meta->orgtable) + && ($meta->table != $meta->orgtable) + && ! $GLOBALS['dbi']->getTable($GLOBALS['db'], $meta->table)->isView() + ) { + $meta->table = $meta->orgtable; + } + + // If this field is not from the table which the unique clause needs + // to be restricted to. + if ($restrict_to_table && $restrict_to_table != $meta->table) { + continue; + } + + // to fix the bug where float fields (primary or not) + // can't be matched because of the imprecision of + // floating comparison, use CONCAT + // (also, the syntax "CONCAT(field) IS NULL" + // that we need on the next "if" will work) + if ($meta->type == 'real') { + $con_key = 'CONCAT(' . self::backquote($meta->table) . '.' + . self::backquote($meta->orgname) . ')'; + } else { + $con_key = self::backquote($meta->table) . '.' + . self::backquote($meta->orgname); + } // end if... else... + $condition = ' ' . $con_key . ' '; + + if (! isset($row[$i]) || is_null($row[$i])) { + $con_val = 'IS NULL'; + } else { + // timestamp is numeric on some MySQL 4.1 + // for real we use CONCAT above and it should compare to string + if ($meta->numeric + && ($meta->type != 'timestamp') + && ($meta->type != 'real') + ) { + $con_val = '= ' . $row[$i]; + } elseif ((($meta->type == 'blob') || ($meta->type == 'string')) + && stristr($field_flags, 'BINARY') + && ! empty($row[$i]) + ) { + // hexify only if this is a true not empty BLOB or a BINARY + + // do not waste memory building a too big condition + if (mb_strlen($row[$i]) < 1000) { + // use a CAST if possible, to avoid problems + // if the field contains wildcard characters % or _ + $con_val = '= CAST(0x' . bin2hex($row[$i]) . ' AS BINARY)'; + } elseif ($fields_cnt == 1) { + // when this blob is the only field present + // try settling with length comparison + $condition = ' CHAR_LENGTH(' . $con_key . ') '; + $con_val = ' = ' . mb_strlen($row[$i]); + } else { + // this blob won't be part of the final condition + $con_val = null; + } + } elseif (in_array($meta->type, self::getGISDatatypes()) + && ! empty($row[$i]) + ) { + // do not build a too big condition + if (mb_strlen($row[$i]) < 5000) { + $condition .= '=0x' . bin2hex($row[$i]) . ' AND'; + } else { + $condition = ''; + } + } elseif ($meta->type == 'bit') { + $con_val = "= b'" + . self::printableBitValue($row[$i], $meta->length) . "'"; + } else { + $con_val = '= \'' + . $GLOBALS['dbi']->escapeString($row[$i]) . '\''; + } + } + + if ($con_val != null) { + + $condition .= $con_val . ' AND'; + + if ($meta->primary_key > 0) { + $primary_key .= $condition; + $primary_key_array[$con_key] = $con_val; + } elseif ($meta->unique_key > 0) { + $unique_key .= $condition; + $unique_key_array[$con_key] = $con_val; + } + + $nonprimary_condition .= $condition; + $nonprimary_condition_array[$con_key] = $con_val; + } + } // end for + + // Correction University of Virginia 19991216: + // prefer primary or unique keys for condition, + // but use conjunction of all values if no primary key + $clause_is_unique = true; + + if ($primary_key) { + $preferred_condition = $primary_key; + $condition_array = $primary_key_array; + + } elseif ($unique_key) { + $preferred_condition = $unique_key; + $condition_array = $unique_key_array; + + } elseif (! $force_unique) { + $preferred_condition = $nonprimary_condition; + $condition_array = $nonprimary_condition_array; + $clause_is_unique = false; + } + + $where_clause = trim(preg_replace('|\s?AND$|', '', $preferred_condition)); + return(array($where_clause, $clause_is_unique, $condition_array)); + } // end function + + /** + * Generate the charset query part + * + * @param string $collation Collation + * @param boolean optional $override force 'CHARACTER SET' keyword + * + * @return string + */ + static function getCharsetQueryPart($collation, $override = false) + { + list($charset) = explode('_', $collation); + $keyword = ' CHARSET='; + + if ($override) { + $keyword = ' CHARACTER SET '; + } + return $keyword . $charset + . ($charset == $collation ? '' : ' COLLATE ' . $collation); + } + + /** + * Generate a button or image tag + * + * @param string $button_name name of button element + * @param string $button_class class of button or image element + * @param string $text text to display + * @param string $image image to display + * @param string $value value + * + * @return string html content + * + * @access public + */ + public static function getButtonOrImage( + $button_name, $button_class, $text, $image, $value = '' + ) { + if ($value == '') { + $value = $text; + } + if ($GLOBALS['cfg']['ActionLinksMode'] == 'text') { + return ' ' . "\n"; + } + return '' . "\n"; + } // end function + + /** + * Generate a pagination selector for browsing resultsets + * + * @param string $name The name for the request parameter + * @param int $rows Number of rows in the pagination set + * @param int $pageNow current page number + * @param int $nbTotalPage number of total pages + * @param int $showAll If the number of pages is lower than this + * variable, no pages will be omitted in pagination + * @param int $sliceStart How many rows at the beginning should always + * be shown? + * @param int $sliceEnd How many rows at the end should always be shown? + * @param int $percent Percentage of calculation page offsets to hop to a + * next page + * @param int $range Near the current page, how many pages should + * be considered "nearby" and displayed as well? + * @param string $prompt The prompt to display (sometimes empty) + * + * @return string + * + * @access public + */ + public static function pageselector( + $name, $rows, $pageNow = 1, $nbTotalPage = 1, $showAll = 200, + $sliceStart = 5, + $sliceEnd = 5, $percent = 20, $range = 10, $prompt = '' + ) { + $increment = floor($nbTotalPage / $percent); + $pageNowMinusRange = ($pageNow - $range); + $pageNowPlusRange = ($pageNow + $range); + + $gotopage = $prompt . ' '; + + return $gotopage; + } // end function + + + /** + * Calculate page number through position + * @param int $pos position of first item + * @param int $max_count number of items per page + * @return int $page_num + * @access public + */ + public static function getPageFromPosition($pos, $max_count) + { + return floor($pos / $max_count) + 1; + } + + /** + * Prepare navigation for a list + * + * @param int $count number of elements in the list + * @param int $pos current position in the list + * @param array $_url_params url parameters + * @param string $script script name for form target + * @param string $frame target frame + * @param int $max_count maximum number of elements to display from + * the list + * @param string $name the name for the request parameter + * @param string[] $classes additional classes for the container + * + * @return string $list_navigator_html the html content + * + * @access public + * + * @todo use $pos from $_url_params + */ + + public static function getListNavigator( + $count, $pos, array $_url_params, $script, $frame, $max_count, $name = 'pos', + $classes = array() + ) { + + // This is often coming from $cfg['MaxTableList'] and + // people sometimes set it to empty string + $max_count = intval($max_count); + if ($max_count <= 0) { + $max_count = 250; + } + + $class = $frame == 'frame_navigation' ? ' class="ajax"' : ''; + + $list_navigator_html = ''; + + if ($max_count < $count) { + + $classes[] = 'pageselector'; + $list_navigator_html .= '
    '; + + if ($frame != 'frame_navigation') { + $list_navigator_html .= __('Page number:'); + } + + // Move to the beginning or to the previous page + if ($pos > 0) { + $caption1 = ''; $caption2 = ''; + if (self::showIcons('TableNavigationLinksMode')) { + $caption1 .= '<< '; + $caption2 .= '< '; + } + if (self::showText('TableNavigationLinksMode')) { + $caption1 .= _pgettext('First page', 'Begin'); + $caption2 .= _pgettext('Previous page', 'Previous'); + } + $title1 = ' title="' . _pgettext('First page', 'Begin') . '"'; + $title2 = ' title="' . _pgettext('Previous page', 'Previous') . '"'; + + $_url_params[$name] = 0; + $list_navigator_html .= '' . $caption1 + . ''; + + $_url_params[$name] = $pos - $max_count; + $list_navigator_html .= ' ' + . $caption2 . ''; + } + + $list_navigator_html .= '
    '; + + $list_navigator_html .= Url::getHiddenInputs($_url_params); + $list_navigator_html .= self::pageselector( + $name, + $max_count, + self::getPageFromPosition($pos, $max_count), + ceil($count / $max_count) + ); + $list_navigator_html .= '
    '; + + if ($pos + $max_count < $count) { + $caption3 = ''; $caption4 = ''; + if (self::showText('TableNavigationLinksMode')) { + $caption3 .= _pgettext('Next page', 'Next'); + $caption4 .= _pgettext('Last page', 'End'); + } + if (self::showIcons('TableNavigationLinksMode')) { + $caption3 .= ' >'; + $caption4 .= ' >>'; + if (! self::showText('TableNavigationLinksMode')) { + + } + } + $title3 = ' title="' . _pgettext('Next page', 'Next') . '"'; + $title4 = ' title="' . _pgettext('Last page', 'End') . '"'; + + $_url_params[$name] = $pos + $max_count; + $list_navigator_html .= '' . $caption3 + . ''; + + $_url_params[$name] = floor($count / $max_count) * $max_count; + if ($_url_params[$name] == $count) { + $_url_params[$name] = $count - $max_count; + } + + $list_navigator_html .= ' ' + . $caption4 . ''; + } + $list_navigator_html .= '
    ' . "\n"; + } + + return $list_navigator_html; + } + + /** + * replaces %u in given path with current user name + * + * example: + * + * $user_dir = userDir('/var/pma_tmp/%u/'); // '/var/pma_tmp/root/' + * + * + * + * @param string $dir with wildcard for user + * + * @return string per user directory + */ + public static function userDir($dir) + { + // add trailing slash + if (mb_substr($dir, -1) != '/') { + $dir .= '/'; + } + + return str_replace('%u', Core::securePath($GLOBALS['cfg']['Server']['user']), $dir); + } + + /** + * returns html code for db link to default db page + * + * @param string $database database + * + * @return string html link to default db page + */ + public static function getDbLink($database = null) + { + if (strlen($database) === 0) { + if (strlen($GLOBALS['db']) === 0) { + return ''; + } + $database = $GLOBALS['db']; + } else { + $database = self::unescapeMysqlWildcards($database); + } + + return '' . htmlspecialchars($database) . ''; + } + + /** + * Prepare a lightbulb hint explaining a known external bug + * that affects a functionality + * + * @param string $functionality localized message explaining the func. + * @param string $component 'mysql' (eventually, 'php') + * @param string $minimum_version of this component + * @param string $bugref bug reference for this component + * + * @return String + */ + public static function getExternalBug( + $functionality, $component, $minimum_version, $bugref + ) { + $ext_but_html = ''; + if (($component == 'mysql') && ($GLOBALS['dbi']->getVersion() < $minimum_version)) { + $ext_but_html .= self::showHint( + sprintf( + __('The %s functionality is affected by a known bug, see %s'), + $functionality, + Core::linkURL('https://bugs.mysql.com/') . $bugref + ) + ); + } + return $ext_but_html; + } + + /** + * Generates a set of radio HTML fields + * + * @param string $html_field_name the radio HTML field + * @param array $choices the choices values and labels + * @param string $checked_choice the choice to check by default + * @param boolean $line_break whether to add HTML line break after a choice + * @param boolean $escape_label whether to use htmlspecialchars() on label + * @param string $class enclose each choice with a div of this class + * @param string $id_prefix prefix for the id attribute, name will be + * used if this is not supplied + * + * @return string set of html radio fiels + */ + public static function getRadioFields( + $html_field_name, array $choices, $checked_choice = '', + $line_break = true, $escape_label = true, $class = '', + $id_prefix = '' + ) { + $radio_html = ''; + + foreach ($choices as $choice_value => $choice_label) { + + if (! $id_prefix) { + $id_prefix = $html_field_name; + } + $html_field_id = $id_prefix . '_' . $choice_value; + + if ($choice_value == $checked_choice){ + $checked = 1; + } + else{ + $checked = 0; + } + $radio_html .= Template::get('radio_fields')->render([ + 'class' => $class, + 'html_field_name' => $html_field_name, + 'html_field_id' => $html_field_id, + 'choice_value' => $choice_value, + 'is_line_break' => $line_break, + 'choice_label' => $choice_label, + 'escape_label' => $escape_label, + 'checked' => $checked + ]); + } + + return $radio_html; + + } + + /** + * Generates and returns an HTML dropdown + * + * @param string $select_name name for the select element + * @param array $choices choices values + * @param string $active_choice the choice to select by default + * @param string $id id of the select element; can be different in + * case the dropdown is present more than once + * on the page + * @param string $class class for the select element + * @param string $placeholder Placeholder for dropdown if nothing else + * is selected + * + * @return string html content + * + * @todo support titles + */ + public static function getDropdown( + $select_name, array $choices, $active_choice, $id, $class = '', $placeholder = null + ) { + $resultOptions = []; + $selected = false; + + foreach ($choices as $one_choice_value => $one_choice_label) { + $resultOptions[$one_choice_value]['value'] = $one_choice_value; + $resultOptions[$one_choice_value]['selected'] = false; + + if ($one_choice_value == $active_choice) { + $resultOptions[$one_choice_value]['selected'] = true; + $selected = true; + } + $resultOptions[$one_choice_value]['label'] = $one_choice_label; + } + return Template::get('dropdown')->render([ + 'select_name' => $select_name, + 'id' => $id, + 'class' => $class, + 'placeholder' => $placeholder, + 'selected' => $selected, + 'result_options' => $resultOptions, + ]); + } + + /** + * Generates a slider effect (jQjuery) + * Takes care of generating the initial
    and the link + * controlling the slider; you have to generate the
    yourself + * after the sliding section. + * + * @param string $id the id of the
    on which to apply the effect + * @param string $message the message to show as a link + * @param string|null $overrideDefault override InitialSlidersState config + * + * @return string html div element + * + */ + public static function getDivForSliderEffect($id = '', $message = '', $overrideDefault = null) + { + return Template::get('div_for_slider_effect')->render([ + 'id' => $id, + 'initial_sliders_state' => ($overrideDefault != null) ? $overrideDefault : $GLOBALS['cfg']['InitialSlidersState'], + 'message' => $message, + ]); + } + + /** + * Creates an AJAX sliding toggle button + * (or and equivalent form when AJAX is disabled) + * + * @param string $action The URL for the request to be executed + * @param string $select_name The name for the dropdown box + * @param array $options An array of options (see PhpMyAdmin\Rte\Footer) + * @param string $callback A JS snippet to execute when the request is + * successfully processed + * + * @return string HTML code for the toggle button + */ + public static function toggleButton($action, $select_name, array $options, $callback) + { + // Do the logic first + $link = "$action&" . urlencode($select_name) . "="; + $link_on = $link . urlencode($options[1]['value']); + $link_off = $link . urlencode($options[0]['value']); + + if ($options[1]['selected'] == true) { + $state = 'on'; + } elseif ($options[0]['selected'] == true) { + $state = 'off'; + } else { + $state = 'on'; + } + + return Template::get('toggle_button')->render( + [ + 'pma_theme_image' => $GLOBALS['pmaThemeImage'], + 'text_dir' => $GLOBALS['text_dir'], + 'link_on' => $link_on, + 'link_off' => $link_off, + 'toggle_on' => $options[1]['label'], + 'toggle_off' => $options[0]['label'], + 'callback' => $callback, + 'state' => $state + ]); + } // end toggleButton() + + /** + * Clears cache content which needs to be refreshed on user change. + * + * @return void + */ + public static function clearUserCache() + { + self::cacheUnset('is_superuser'); + self::cacheUnset('is_createuser'); + self::cacheUnset('is_grantuser'); + } + + /** + * Calculates session cache key + * + * @return string + */ + public static function cacheKey() + { + if (isset($GLOBALS['cfg']['Server']['user'])) { + return 'server_' . $GLOBALS['server'] . '_' . $GLOBALS['cfg']['Server']['user']; + } + + return 'server_' . $GLOBALS['server']; + } + + /** + * Verifies if something is cached in the session + * + * @param string $var variable name + * + * @return boolean + */ + public static function cacheExists($var) + { + return isset($_SESSION['cache'][self::cacheKey()][$var]); + } + + /** + * Gets cached information from the session + * + * @param string $var variable name + * @param \Closure $callback callback to fetch the value + * + * @return mixed + */ + public static function cacheGet($var, $callback = null) + { + if (self::cacheExists($var)) { + return $_SESSION['cache'][self::cacheKey()][$var]; + } + + if ($callback) { + $val = $callback(); + self::cacheSet($var, $val); + return $val; + } + return null; + } + + /** + * Caches information in the session + * + * @param string $var variable name + * @param mixed $val value + * + * @return mixed + */ + public static function cacheSet($var, $val = null) + { + $_SESSION['cache'][self::cacheKey()][$var] = $val; + } + + /** + * Removes cached information from the session + * + * @param string $var variable name + * + * @return void + */ + public static function cacheUnset($var) + { + unset($_SESSION['cache'][self::cacheKey()][$var]); + } + + /** + * Converts a bit value to printable format; + * in MySQL a BIT field can be from 1 to 64 bits so we need this + * function because in PHP, decbin() supports only 32 bits + * on 32-bit servers + * + * @param integer $value coming from a BIT field + * @param integer $length length + * + * @return string the printable value + */ + public static function printableBitValue($value, $length) + { + // if running on a 64-bit server or the length is safe for decbin() + if (PHP_INT_SIZE == 8 || $length < 33) { + $printable = decbin($value); + } else { + // FIXME: does not work for the leftmost bit of a 64-bit value + $i = 0; + $printable = ''; + while ($value >= pow(2, $i)) { + ++$i; + } + if ($i != 0) { + --$i; + } + + while ($i >= 0) { + if ($value - pow(2, $i) < 0) { + $printable = '0' . $printable; + } else { + $printable = '1' . $printable; + $value = $value - pow(2, $i); + } + --$i; + } + $printable = strrev($printable); + } + $printable = str_pad($printable, $length, '0', STR_PAD_LEFT); + return $printable; + } + + /** + * Verifies whether the value contains a non-printable character + * + * @param string $value value + * + * @return integer + */ + public static function containsNonPrintableAscii($value) + { + return preg_match('@[^[:print:]]@', $value); + } + + /** + * Converts a BIT type default value + * for example, b'010' becomes 010 + * + * @param string $bit_default_value value + * + * @return string the converted value + */ + public static function convertBitDefaultValue($bit_default_value) + { + return rtrim(ltrim(htmlspecialchars_decode($bit_default_value, ENT_QUOTES), "b'"), "'"); + } + + /** + * Extracts the various parts from a column spec + * + * @param string $columnspec Column specification + * + * @return array associative array containing type, spec_in_brackets + * and possibly enum_set_values (another array) + */ + public static function extractColumnSpec($columnspec) + { + $first_bracket_pos = mb_strpos($columnspec, '('); + if ($first_bracket_pos) { + $spec_in_brackets = chop( + mb_substr( + $columnspec, + $first_bracket_pos + 1, + mb_strrpos($columnspec, ')') - $first_bracket_pos - 1 + ) + ); + // convert to lowercase just to be sure + $type = mb_strtolower( + chop(mb_substr($columnspec, 0, $first_bracket_pos)) + ); + } else { + // Split trailing attributes such as unsigned, + // binary, zerofill and get data type name + $type_parts = explode(' ', $columnspec); + $type = mb_strtolower($type_parts[0]); + $spec_in_brackets = ''; + } + + if ('enum' == $type || 'set' == $type) { + // Define our working vars + $enum_set_values = self::parseEnumSetValues($columnspec, false); + $printtype = $type + . '(' . str_replace("','", "', '", $spec_in_brackets) . ')'; + $binary = false; + $unsigned = false; + $zerofill = false; + } else { + $enum_set_values = array(); + + /* Create printable type name */ + $printtype = mb_strtolower($columnspec); + + // Strip the "BINARY" attribute, except if we find "BINARY(" because + // this would be a BINARY or VARBINARY column type; + // by the way, a BLOB should not show the BINARY attribute + // because this is not accepted in MySQL syntax. + if (preg_match('@binary@', $printtype) + && ! preg_match('@binary[\(]@', $printtype) + ) { + $printtype = preg_replace('@binary@', '', $printtype); + $binary = true; + } else { + $binary = false; + } + + $printtype = preg_replace( + '@zerofill@', '', $printtype, -1, $zerofill_cnt + ); + $zerofill = ($zerofill_cnt > 0); + $printtype = preg_replace( + '@unsigned@', '', $printtype, -1, $unsigned_cnt + ); + $unsigned = ($unsigned_cnt > 0); + $printtype = trim($printtype); + } + + $attribute = ' '; + if ($binary) { + $attribute = 'BINARY'; + } + if ($unsigned) { + $attribute = 'UNSIGNED'; + } + if ($zerofill) { + $attribute = 'UNSIGNED ZEROFILL'; + } + + $can_contain_collation = false; + if (! $binary + && preg_match( + "@^(char|varchar|text|tinytext|mediumtext|longtext|set|enum)@", $type + ) + ) { + $can_contain_collation = true; + } + + // for the case ENUM('–','“') + $displayed_type = htmlspecialchars($printtype); + if (mb_strlen($printtype) > $GLOBALS['cfg']['LimitChars']) { + $displayed_type = ''; + $displayed_type .= htmlspecialchars( + mb_substr( + $printtype, 0, $GLOBALS['cfg']['LimitChars'] + ) . '...' + ); + $displayed_type .= ''; + } + + return array( + 'type' => $type, + 'spec_in_brackets' => $spec_in_brackets, + 'enum_set_values' => $enum_set_values, + 'print_type' => $printtype, + 'binary' => $binary, + 'unsigned' => $unsigned, + 'zerofill' => $zerofill, + 'attribute' => $attribute, + 'can_contain_collation' => $can_contain_collation, + 'displayed_type' => $displayed_type + ); + } + + /** + * Verifies if this table's engine supports foreign keys + * + * @param string $engine engine + * + * @return boolean + */ + public static function isForeignKeySupported($engine) + { + $engine = strtoupper($engine); + if (($engine == 'INNODB') || ($engine == 'PBXT')) { + return true; + } elseif ($engine == 'NDBCLUSTER' || $engine == 'NDB') { + $ndbver = strtolower( + $GLOBALS['dbi']->fetchValue("SELECT @@ndb_version_string") + ); + if (substr($ndbver, 0, 4) == 'ndb-') { + $ndbver = substr($ndbver, 4); + } + return version_compare($ndbver, 7.3, '>='); + } + + return false; + } + + /** + * Is Foreign key check enabled? + * + * @return bool + */ + public static function isForeignKeyCheck() + { + if ($GLOBALS['cfg']['DefaultForeignKeyChecks'] === 'enable') { + return true; + } elseif ($GLOBALS['cfg']['DefaultForeignKeyChecks'] === 'disable') { + return false; + } + return ($GLOBALS['dbi']->getVariable('FOREIGN_KEY_CHECKS') == 'ON'); + } + + /** + * Get HTML for Foreign key check checkbox + * + * @return string HTML for checkbox + */ + public static function getFKCheckbox() + { + return Template::get('fk_checkbox')->render([ + 'checked' => self::isForeignKeyCheck(), + ]); + } + + /** + * Handle foreign key check request + * + * @return bool Default foreign key checks value + */ + public static function handleDisableFKCheckInit() + { + $default_fk_check_value + = $GLOBALS['dbi']->getVariable('FOREIGN_KEY_CHECKS') == 'ON'; + if (isset($_REQUEST['fk_checks'])) { + if (empty($_REQUEST['fk_checks'])) { + // Disable foreign key checks + $GLOBALS['dbi']->setVariable('FOREIGN_KEY_CHECKS', 'OFF'); + } else { + // Enable foreign key checks + $GLOBALS['dbi']->setVariable('FOREIGN_KEY_CHECKS', 'ON'); + } + } // else do nothing, go with default + return $default_fk_check_value; + } + + /** + * Cleanup changes done for foreign key check + * + * @param bool $default_fk_check_value original value for 'FOREIGN_KEY_CHECKS' + * + * @return void + */ + public static function handleDisableFKCheckCleanup($default_fk_check_value) + { + $GLOBALS['dbi']->setVariable( + 'FOREIGN_KEY_CHECKS', $default_fk_check_value ? 'ON' : 'OFF' + ); + } + + /** + * Converts GIS data to Well Known Text format + * + * @param string $data GIS data + * @param bool $includeSRID Add SRID to the WKT + * + * @return string GIS data in Well Know Text format + */ + public static function asWKT($data, $includeSRID = false) + { + // Convert to WKT format + $hex = bin2hex($data); + $spatialAsText = 'ASTEXT'; + $spatialSrid = 'SRID'; + if ($GLOBALS['dbi']->getVersion() >= 50600) { + $spatialAsText = 'ST_ASTEXT'; + $spatialSrid = 'ST_SRID'; + } + $wktsql = "SELECT $spatialAsText(x'" . $hex . "')"; + if ($includeSRID) { + $wktsql .= ", $spatialSrid(x'" . $hex . "')"; + } + + $wktresult = $GLOBALS['dbi']->tryQuery( + $wktsql + ); + $wktarr = $GLOBALS['dbi']->fetchRow($wktresult, 0); + $wktval = $wktarr[0]; + + if ($includeSRID) { + $srid = $wktarr[1]; + $wktval = "'" . $wktval . "'," . $srid; + } + @$GLOBALS['dbi']->freeResult($wktresult); + + return $wktval; + } + + /** + * If the string starts with a \r\n pair (0x0d0a) add an extra \n + * + * @param string $string string + * + * @return string with the chars replaced + */ + public static function duplicateFirstNewline($string) + { + $first_occurence = mb_strpos($string, "\r\n"); + if ($first_occurence === 0) { + $string = "\n" . $string; + } + return $string; + } + + /** + * Get the action word corresponding to a script name + * in order to display it as a title in navigation panel + * + * @param string $target a valid value for $cfg['NavigationTreeDefaultTabTable'], + * $cfg['NavigationTreeDefaultTabTable2'], + * $cfg['DefaultTabTable'] or $cfg['DefaultTabDatabase'] + * + * @return string Title for the $cfg value + */ + public static function getTitleForTarget($target) + { + $mapping = array( + 'structure' => __('Structure'), + 'sql' => __('SQL'), + 'search' =>__('Search'), + 'insert' =>__('Insert'), + 'browse' => __('Browse'), + 'operations' => __('Operations'), + + // For backward compatiblity + + // Values for $cfg['DefaultTabTable'] + 'tbl_structure.php' => __('Structure'), + 'tbl_sql.php' => __('SQL'), + 'tbl_select.php' =>__('Search'), + 'tbl_change.php' =>__('Insert'), + 'sql.php' => __('Browse'), + // Values for $cfg['DefaultTabDatabase'] + 'db_structure.php' => __('Structure'), + 'db_sql.php' => __('SQL'), + 'db_search.php' => __('Search'), + 'db_operations.php' => __('Operations'), + ); + return isset($mapping[$target]) ? $mapping[$target] : false; + } + + /** + * Get the script name corresponding to a plain English config word + * in order to append in links on navigation and main panel + * + * @param string $target a valid value for + * $cfg['NavigationTreeDefaultTabTable'], + * $cfg['NavigationTreeDefaultTabTable2'], + * $cfg['DefaultTabTable'], $cfg['DefaultTabDatabase'] or + * $cfg['DefaultTabServer'] + * @param string $location one out of 'server', 'table', 'database' + * + * @return string script name corresponding to the config word + */ + public static function getScriptNameForOption($target, $location) + { + if ($location == 'server') { + // Values for $cfg['DefaultTabServer'] + switch ($target) { + case 'welcome': + return 'index.php'; + case 'databases': + return 'server_databases.php'; + case 'status': + return 'server_status.php'; + case 'variables': + return 'server_variables.php'; + case 'privileges': + return 'server_privileges.php'; + } + } elseif ($location == 'database') { + // Values for $cfg['DefaultTabDatabase'] + switch ($target) { + case 'structure': + return 'db_structure.php'; + case 'sql': + return 'db_sql.php'; + case 'search': + return 'db_search.php'; + case 'operations': + return 'db_operations.php'; + } + } elseif ($location == 'table') { + // Values for $cfg['DefaultTabTable'], + // $cfg['NavigationTreeDefaultTabTable'] and + // $cfg['NavigationTreeDefaultTabTable2'] + switch ($target) { + case 'structure': + return 'tbl_structure.php'; + case 'sql': + return 'tbl_sql.php'; + case 'search': + return 'tbl_select.php'; + case 'insert': + return 'tbl_change.php'; + case 'browse': + return 'sql.php'; + } + } + + return $target; + } + + /** + * Formats user string, expanding @VARIABLES@, accepting strftime format + * string. + * + * @param string $string Text where to do expansion. + * @param array|string $escape Function to call for escaping variable values. + * Can also be an array of: + * - the escape method name + * - the class that contains the method + * - location of the class (for inclusion) + * @param array $updates Array with overrides for default parameters + * (obtained from GLOBALS). + * + * @return string + */ + public static function expandUserString( + $string, $escape = null, array $updates = array() + ) { + /* Content */ + $vars = array(); + $vars['http_host'] = Core::getenv('HTTP_HOST'); + $vars['server_name'] = $GLOBALS['cfg']['Server']['host']; + $vars['server_verbose'] = $GLOBALS['cfg']['Server']['verbose']; + + if (empty($GLOBALS['cfg']['Server']['verbose'])) { + $vars['server_verbose_or_name'] = $GLOBALS['cfg']['Server']['host']; + } else { + $vars['server_verbose_or_name'] = $GLOBALS['cfg']['Server']['verbose']; + } + + $vars['database'] = $GLOBALS['db']; + $vars['table'] = $GLOBALS['table']; + $vars['phpmyadmin_version'] = 'phpMyAdmin ' . PMA_VERSION; + + /* Update forced variables */ + foreach ($updates as $key => $val) { + $vars[$key] = $val; + } + + /* Replacement mapping */ + /* + * The __VAR__ ones are for backward compatibility, because user + * might still have it in cookies. + */ + $replace = array( + '@HTTP_HOST@' => $vars['http_host'], + '@SERVER@' => $vars['server_name'], + '__SERVER__' => $vars['server_name'], + '@VERBOSE@' => $vars['server_verbose'], + '@VSERVER@' => $vars['server_verbose_or_name'], + '@DATABASE@' => $vars['database'], + '__DB__' => $vars['database'], + '@TABLE@' => $vars['table'], + '__TABLE__' => $vars['table'], + '@PHPMYADMIN@' => $vars['phpmyadmin_version'], + ); + + /* Optional escaping */ + if (! is_null($escape)) { + if (is_array($escape)) { + $escape_class = new $escape[1]; + $escape_method = $escape[0]; + } + foreach ($replace as $key => $val) { + if (is_array($escape)) { + $replace[$key] = $escape_class->$escape_method($val); + } else { + $replace[$key] = ($escape == 'backquote') + ? self::$escape($val) + : $escape($val); + } + } + } + + /* Backward compatibility in 3.5.x */ + if (mb_strpos($string, '@FIELDS@') !== false) { + $string = strtr($string, array('@FIELDS@' => '@COLUMNS@')); + } + + /* Fetch columns list if required */ + if (mb_strpos($string, '@COLUMNS@') !== false) { + $columns_list = $GLOBALS['dbi']->getColumns( + $GLOBALS['db'], $GLOBALS['table'] + ); + + // sometimes the table no longer exists at this point + if (! is_null($columns_list)) { + $column_names = array(); + foreach ($columns_list as $column) { + if (! is_null($escape)) { + $column_names[] = self::$escape($column['Field']); + } else { + $column_names[] = $column['Field']; + } + } + $replace['@COLUMNS@'] = implode(',', $column_names); + } else { + $replace['@COLUMNS@'] = '*'; + } + } + + /* Do the replacement */ + return strtr(strftime($string), $replace); + } + + /** + * Prepare the form used to browse anywhere on the local server for a file to + * import + * + * @param string $max_upload_size maximum upload size + * + * @return String + */ + public static function getBrowseUploadFileBlock($max_upload_size) + { + $block_html = ''; + + if ($GLOBALS['is_upload'] && ! empty($GLOBALS['cfg']['UploadDir'])) { + $block_html .= '
    {% endif %} + {% endfor %} + +
    + +
    + +
    + {% spaceless %} + + {% endspaceless %} +
    + + +
    + + +
    + +
    {# Slider div #} +
    diff --git a/php/apps/phpmyadmin49/html/templates/database/qbe/column_select_cell.twig b/php/apps/phpmyadmin49/html/templates/database/qbe/column_select_cell.twig new file mode 100644 index 00000000..071249f8 --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/database/qbe/column_select_cell.twig @@ -0,0 +1,11 @@ + + + diff --git a/php/apps/phpmyadmin49/html/templates/database/qbe/footer_options.twig b/php/apps/phpmyadmin49/html/templates/database/qbe/footer_options.twig new file mode 100644 index 00000000..7f43f8de --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/database/qbe/footer_options.twig @@ -0,0 +1,16 @@ +
    + {% if type == 'row' %} + {% trans 'Add/Delete criteria rows' %}: + {% else %} + {% trans 'Add/Delete columns' %}: + {% endif %} + +
    diff --git a/php/apps/phpmyadmin49/html/templates/database/qbe/sort_order_select_cell.twig b/php/apps/phpmyadmin49/html/templates/database/qbe/sort_order_select_cell.twig new file mode 100644 index 00000000..7a63d772 --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/database/qbe/sort_order_select_cell.twig @@ -0,0 +1,10 @@ + + + diff --git a/php/apps/phpmyadmin49/html/templates/database/qbe/sort_select_cell.twig b/php/apps/phpmyadmin49/html/templates/database/qbe/sort_select_cell.twig new file mode 100644 index 00000000..4f0989e8 --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/database/qbe/sort_select_cell.twig @@ -0,0 +1,9 @@ + + + diff --git a/php/apps/phpmyadmin49/html/templates/database/search/result_divs.twig b/php/apps/phpmyadmin49/html/templates/database/search/result_divs.twig new file mode 100644 index 00000000..9f5f19ff --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/database/search/result_divs.twig @@ -0,0 +1,13 @@ +{# These two table-image and table-link elements display the table name in browse search results #} +
    + +
    +{# Div for browsing results #} +
    + {# This browse-results div is used to load the browse and delete results in the db search #} +
    +
    + {# This sqlqueryform div is used to load the delete form in the db search #} +
    +{# Toggle query box link #} + diff --git a/php/apps/phpmyadmin49/html/templates/database/search/results.twig b/php/apps/phpmyadmin49/html/templates/database/search/results.twig new file mode 100644 index 00000000..efe55296 --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/database/search/results.twig @@ -0,0 +1,62 @@ + + + {% for row in rows %} + + + {% if row.result_count > 0 %} + {% set url_params = { + 'db': db, + 'table': row.table, + 'goto': 'db_sql.php', + 'pos': 0, + 'is_js_confirmed': 0 + } %} + + + {% else %} + + + {% endif %} + + {% endfor %} +
    + {{ 'Search results for "%s" %s:'|format( + criteria_search_string, + search_type_description + )|raw }} +
    + {% set result_message %} + {% trans %} + %1$s match in %2$s + {% plural row.result_count %} + %1$s matches in %2$s + {% endtrans %} + {% endset %} + {{ result_message|format(row.result_count, row.table)|raw }} + + + {% trans 'Browse' %} + + + + {% trans 'Delete' %} + +
    + +{% if criteria_tables|length > 1 %} +

    + {% trans %} + Total: {{ count }} match + {% plural result_total %} + Total: {{ count }} matches + {% endtrans %} +

    +{% endif %} diff --git a/php/apps/phpmyadmin49/html/templates/database/search/selection_form.twig b/php/apps/phpmyadmin49/html/templates/database/search/selection_form.twig new file mode 100644 index 00000000..2a7ed8b4 --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/database/search/selection_form.twig @@ -0,0 +1,63 @@ + +
    + {{ Url_getHiddenInputs(db) }} +
    + {% trans 'Search in database' %} +

    + + +

    + +
    + {% trans 'Find:' %} + {# 4th parameter set to true to add line breaks #} + {# 5th parameter set to false to avoid htmlspecialchars() escaping + in the label since we have some HTML in some labels #} + {{ Util_getRadioFields( + 'criteriaSearchType', + choices, + criteria_search_type, + true, + false + ) }} +
    + +
    + {% trans 'Inside tables:' %} +

    + + {% trans 'Select all' %} + / + + {% trans 'Unselect all' %} + +

    + +
    + +

    + {# Inputbox for column name entry #} + + +

    +
    +
    + +
    +
    +
    + +
    diff --git a/php/apps/phpmyadmin49/html/templates/database/structure/body_for_table_summary.twig b/php/apps/phpmyadmin49/html/templates/database/structure/body_for_table_summary.twig new file mode 100644 index 00000000..a06dc9ea --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/database/structure/body_for_table_summary.twig @@ -0,0 +1,95 @@ + + + + + {% set num_tables_trans -%} + {% trans %}%s table{% plural num_tables %}%s tables{% endtrans %} + {%- endset %} + {{ num_tables_trans|format(Util_formatNumber(num_tables, 0)) }} + + {% if server_slave_status %} + {% trans 'Replication' %} + {% endif %} + {% set sum_colspan = db_is_system_schema ? 4 : 7 %} + {% if num_favorite_tables == 0 %} + {% set sum_colspan = sum_colspan - 1 %} + {% endif %} + {% trans 'Sum' %} + {% set row_count_sum = Util_formatNumber(sum_entries, 0) %} + {# If a table shows approximate rows count, display update-all-real-count anchor. #} + {% set row_sum_url = [] %} + {% if approx_rows is defined %} + {% set row_sum_url = { + 'ajax_request': true, + 'db': db, + 'real_row_count': 'true', + 'real_row_count_all': 'true' + } %} + {% endif %} + {% if approx_rows %} + {% set cell_text -%} + ~ + {{- row_count_sum -}} + + {%- endset %} + {% else %} + {% set cell_text = row_count_sum %} + {% endif %} + {{ cell_text }} + {% if not (properties_num_columns > 1) %} + {# MySQL <= 5.5.2 #} + {% set default_engine = dbi.fetchValue('SELECT @@storage_engine;') %} + {% if default_engine is empty %} + {# MySQL >= 5.5.3 #} + {% set default_engine = dbi.fetchValue('SELECT @@default_storage_engine;') %} + {% endif %} + + + {{ default_engine }} + + + + {% if db_collation is not empty %} + + {{ db_collation }} + + {% endif %} + + {% endif %} + + {% if is_show_stats %} + {% set sum = Util_formatByteDown(sum_size, 3, 1) %} + {% set sum_formatted = sum[0] %} + {% set sum_unit = sum[1] %} + {{ sum_formatted }} {{ sum_unit }} + + {% set overhead = Util_formatByteDown(overhead_size, 3, 1) %} + {% set overhead_formatted = overhead[0] %} + {% set overhead_unit = overhead[1] %} + {{ overhead_formatted }} {{ overhead_unit }} + {% endif %} + + {% if show_charset %} + {{ db_charset }} + {% endif %} + {% if show_comment %} + + {% endif %} + {% if show_creation %} + + {{ create_time_all ? Util_localisedDate(strtotime(create_time_all)) : '-' }} + + {% endif %} + {% if show_last_update %} + + {{ update_time_all ? Util_localisedDate(strtotime(update_time_all)) : '-' }} + + {% endif %} + {% if show_last_check %} + + {{ check_time_all ? Util_localisedDate(strtotime(check_time_all)) : '-' }} + + {% endif %} + + diff --git a/php/apps/phpmyadmin49/html/templates/database/structure/browse_table.twig b/php/apps/phpmyadmin49/html/templates/database/structure/browse_table.twig new file mode 100644 index 00000000..826a599e --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/database/structure/browse_table.twig @@ -0,0 +1,3 @@ + + {{ title|raw }} + diff --git a/php/apps/phpmyadmin49/html/templates/database/structure/browse_table_label.twig b/php/apps/phpmyadmin49/html/templates/database/structure/browse_table_label.twig new file mode 100644 index 00000000..5cb86179 --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/database/structure/browse_table_label.twig @@ -0,0 +1,3 @@ + + {{ truename }} + diff --git a/php/apps/phpmyadmin49/html/templates/database/structure/check_all_tables.twig b/php/apps/phpmyadmin49/html/templates/database/structure/check_all_tables.twig new file mode 100644 index 00000000..25323f91 --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/database/structure/check_all_tables.twig @@ -0,0 +1,40 @@ + diff --git a/php/apps/phpmyadmin49/html/templates/database/structure/empty_table.twig b/php/apps/phpmyadmin49/html/templates/database/structure/empty_table.twig new file mode 100644 index 00000000..753556b0 --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/database/structure/empty_table.twig @@ -0,0 +1,4 @@ + + {{ title|raw }} + diff --git a/php/apps/phpmyadmin49/html/templates/database/structure/favorite_anchor.twig b/php/apps/phpmyadmin49/html/templates/database/structure/favorite_anchor.twig new file mode 100644 index 00000000..34a97b39 --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/database/structure/favorite_anchor.twig @@ -0,0 +1,7 @@ + + {{ already_favorite ? titles['Favorite']|raw : titles['NoFavorite']|raw }} + diff --git a/php/apps/phpmyadmin49/html/templates/database/structure/print_view_data_dictionary_link.twig b/php/apps/phpmyadmin49/html/templates/database/structure/print_view_data_dictionary_link.twig new file mode 100644 index 00000000..81d52035 --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/database/structure/print_view_data_dictionary_link.twig @@ -0,0 +1,8 @@ + diff --git a/php/apps/phpmyadmin49/html/templates/database/structure/search_table.twig b/php/apps/phpmyadmin49/html/templates/database/structure/search_table.twig new file mode 100644 index 00000000..0bc2892a --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/database/structure/search_table.twig @@ -0,0 +1,3 @@ + + {{ title|raw }} + diff --git a/php/apps/phpmyadmin49/html/templates/database/structure/show_create.twig b/php/apps/phpmyadmin49/html/templates/database/structure/show_create.twig new file mode 100644 index 00000000..520e9744 --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/database/structure/show_create.twig @@ -0,0 +1,31 @@ +
    +

    {% trans 'Showing create queries' %}

    + {% set views = [] %} + {% set tables = [] %} + {% for object in db_objects %} + {% if dbi.getTable(db, object).isView() %} + {% set views = views|merge([object]) %} + {% else %} + {% set tables = tables|merge([object]) %} + {% endif %} + {% endfor %} + {% if tables is not empty %} + {% include 'database/structure/show_create_row.twig' with { + 'db': db, + 'title': 'Tables'|trans, + 'raw_title': 'Table', + 'db_objects': tables, + 'dbi': dbi + } only %} + {% endif %} + + {% if views is not empty %} + {% include 'database/structure/show_create_row.twig' with { + 'db': db, + 'title': 'Views'|trans, + 'raw_title': 'View', + 'db_objects': views, + 'dbi': dbi + } only %} + {% endif %} +
    diff --git a/php/apps/phpmyadmin49/html/templates/database/structure/show_create_row.twig b/php/apps/phpmyadmin49/html/templates/database/structure/show_create_row.twig new file mode 100644 index 00000000..b0ce9007 --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/database/structure/show_create_row.twig @@ -0,0 +1,19 @@ +
    + {{ title }} + + + + + + + + + {% for object in db_objects %} + + + + + {% endfor %} + +
    {{ raw_title }}{{ 'Create %s'|trans|format(raw_title) }}
    {{ Core_mimeDefaultFunction(object) }}{{ Core_mimeDefaultFunction(dbi.getTable(db, object).showCreate()) }}
    +
    diff --git a/php/apps/phpmyadmin49/html/templates/database/structure/structure_table_row.twig b/php/apps/phpmyadmin49/html/templates/database/structure/structure_table_row.twig new file mode 100644 index 00000000..0926eb70 --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/database/structure/structure_table_row.twig @@ -0,0 +1,225 @@ + + + + + + {{ browse_table_label|raw }} + {{ tracking_icon|raw }} + + {% if server_slave_status %} + + {{ ignored ? Util_getImage('s_cancel', 'Not replicated'|trans) }} + {{ do ? Util_getImage('s_success', 'Replicated'|trans) }} + + {% endif %} + + {# Favorite table anchor #} + {% if num_favorite_tables > 0 %} + + {# Check if current table is already in favorite list #} + {% set fav_params = { + 'db': db, + 'ajax_request': true, + 'favorite_table': current_table['TABLE_NAME'], + ((already_favorite ? 'remove' : 'add') ~ '_favorite'): true + } %} + {% include 'database/structure/favorite_anchor.twig' with { + 'table_name_hash': md5(current_table['TABLE_NAME']), + 'db_table_name_hash': md5(db ~ '.' ~ current_table['TABLE_NAME']), + 'fav_params': fav_params, + 'already_favorite': already_favorite, + 'titles': titles + } only %} + + {% endif %} + + + {{ browse_table|raw }} + + + + {{ titles['Structure']|raw }} + + + + {{ search_table|raw }} + + + {% if not db_is_system_schema %} + + {{ titles['Insert']|raw }} + + {{ empty_table|raw }} + + + {{ titles['Drop']|raw }} + + + {% endif %} + + {% if current_table['TABLE_ROWS'] is defined + and (current_table['ENGINE'] != null or table_is_view) %} + {# Get the row count #} + {% set row_count = Util_formatNumber(current_table['TABLE_ROWS'], 0) %} + + {# Content to be appended into 'tbl_rows' cell. + If row count is approximate, display it as an anchor to get real count. #} + + {% if approx_rows %} + + + ~{{ row_count }} + + + {% else %} + {{ row_count }} + {% endif %} + {{ show_superscript|raw }} + + + {% if not (properties_num_columns > 1) %} + + {% if current_table['ENGINE'] is not empty %} + {{ current_table['ENGINE'] }} + {% elseif table_is_view %} + {% trans 'View' %} + {% endif %} + + {% if collation|length > 0 %} + + {{ collation|raw }} + + {% endif %} + {% endif %} + + {% if is_show_stats %} + + + {{ formatted_size }} + {{ unit }} + + + + {{ overhead|raw }} + + {% endif %} + + {% if not (show_charset > 1) %} + {% if charset|length > 0 %} + + {{ charset|raw }} + + {% endif %} + {% endif %} + + {% if show_comment %} + {% set comment = current_table['Comment'] %} + + {% if comment|length > limit_chars %} + + {{ comment|slice(0, limit_chars) }} + ... + + {% else %} + {{ comment }} + {% endif %} + + {% endif %} + + {% if show_creation %} + + {{ create_time ? Util_localisedDate(strtotime(create_time)) : '-' }} + + {% endif %} + + {% if show_last_update %} + + {{ update_time ? Util_localisedDate(strtotime(update_time)) : '-'}} + + {% endif %} + + {% if show_last_check %} + + {{ check_time ? Util_localisedDate(strtotime(check_time)) : '-' }} + + {% endif %} + + {% elseif table_is_view %} + - + + {% trans 'View' %} + + --- + {% if is_show_stats %} + - + - + {% endif %} + {% if show_charset %} + + {% endif %} + {% if show_comment %} + + {% endif %} + {% if show_creation %} + - + {% endif %} + {% if show_last_update %} + - + {% endif %} + {% if show_last_check %} + - + {% endif %} + + {% else %} + {% set count = 0 %} + {% if properties_num_columns %} + {% set count = count + 2 %} + {% endif %} + {% if is_show_stats %} + {% set count = count + 2 %} + {% endif %} + {% if show_charset %} + {% set count = count + 1 %} + {% endif %} + {% if show_comment %} + {% set count = count + 1 %} + {% endif %} + {% if show_creation %} + {% set count = count + 1 %} + {% endif %} + {% if show_last_update %} + {% set count = count + 1 %} + {% endif %} + {% if show_last_check %} + {% set count = count + 1 %} + {% endif %} + + {% if db_is_system_schema %} + {% set action_colspan = 3 %} + {% else %} + {% set action_colspan = 6 %} + {% endif %} + {% if num_favorite_tables > 0 %} + {% set action_colspan = action_colspan + 1 %} + {% endif %} + + {% set colspan_for_structure = action_colspan + 3 %} + + {% trans 'in use' %} + + {% endif %} + diff --git a/php/apps/phpmyadmin49/html/templates/database/structure/table_header.twig b/php/apps/phpmyadmin49/html/templates/database/structure/table_header.twig new file mode 100644 index 00000000..19818a9e --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/database/structure/table_header.twig @@ -0,0 +1,80 @@ +
    +{{ Url_getHiddenInputs(db) }} +
    + + + + + + {% if replication %} + + {% endif %} + + {% if db_is_system_schema %} + {% set action_colspan = 3 %} + {% else %} + {% set action_colspan = 6 %} + {% endif %} + {% if num_favorite_tables > 0 %} + {% set action_colspan = action_colspan + 1 %} + {% endif %} + + {# larger values are more interesting so default sort order is DESC #} + + {% if not (properties_num_columns > 1) %} + + + {% endif %} + + {% if is_show_stats %} + {# larger values are more interesting so default sort order is DESC #} + + {# larger values are more interesting so default sort order is DESC #} + + {% endif %} + + {% if show_charset %} + + {% endif %} + + {% if show_comment %} + + {% endif %} + + {% if show_creation %} + {# newer values are more interesting so default sort order is DESC #} + + {% endif %} + + {% if show_last_update %} + {# newer values are more interesting so default sort order is DESC #} + + {% endif %} + + {% if show_last_check %} + {# newer values are more interesting so default sort order is DESC #} + + {% endif %} + + + + {% for structure_table_row in structure_table_rows %} + {% include 'database/structure/structure_table_row.twig' with structure_table_row only %} + {% endfor %} + + {% if body_for_table_summary %} + {% include 'database/structure/body_for_table_summary.twig' with body_for_table_summary only %} + {% endif %} +
    {{ Util_sortableTableHeader('Table'|trans, 'table') }}{% trans 'Replication' %} + {% trans 'Action' %} + + {{ Util_sortableTableHeader('Rows'|trans, 'records', 'DESC') }} + {{ Util_showHint(Sanitize_sanitize( + 'May be approximate. Click on the number to get the exact count. See [doc@faq3-11]FAQ 3.11[/doc].'|trans + )) }} + {{ Util_sortableTableHeader('Type'|trans, 'type') }}{{ Util_sortableTableHeader('Collation'|trans, 'collation') }}{{ Util_sortableTableHeader('Size'|trans, 'size', 'DESC') }}{{ Util_sortableTableHeader('Overhead'|trans, 'overhead', 'DESC') }}{{ Util_sortableTableHeader('Charset'|trans, 'charset') }}{{ Util_sortableTableHeader('Comment'|trans, 'comment') }}{{ Util_sortableTableHeader('Creation'|trans, 'creation', 'DESC') }}{{ Util_sortableTableHeader('Last update'|trans, 'last_update', 'DESC') }}{{ Util_sortableTableHeader('Last check'|trans, 'last_check', 'DESC') }}
    +
    +{% if check_all_tables %} + {% include 'database/structure/check_all_tables.twig' with check_all_tables only %} +{% endif %} +
    diff --git a/php/apps/phpmyadmin49/html/templates/database/structure/tracking_icon.twig b/php/apps/phpmyadmin49/html/templates/database/structure/tracking_icon.twig new file mode 100644 index 00000000..ca7e894b --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/database/structure/tracking_icon.twig @@ -0,0 +1,7 @@ + + {% if is_tracked -%} + {{ Util_getImage('eye', 'Tracking is active.'|trans) }} + {%- else -%} + {{ Util_getImage('eye_grey', 'Tracking is not active.'|trans) }} + {%- endif %} + diff --git a/php/apps/phpmyadmin49/html/templates/database/tracking/tracked_tables.twig b/php/apps/phpmyadmin49/html/templates/database/tracking/tracked_tables.twig new file mode 100644 index 00000000..9f7a755f --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/database/tracking/tracked_tables.twig @@ -0,0 +1,107 @@ +
    +

    {% trans 'Tracked tables' %}

    + +
    + {{ Url_getHiddenInputs(db) }} + + + + + + + + + + + + + + + {% for version in versions %} + + + + + + + + + + + {% endfor %} + +
    {% trans 'Table' %}{% trans 'Last version' %}{% trans 'Created' %}{% trans 'Updated' %}{% trans 'Status' %}{% trans 'Action' %}{% trans 'Show' %}
    + + + + + {{ version.version }} + + {{ version.date_created }} + + {{ version.date_updated }} + + {{ version.status_button|raw }} + + + {{ Util_getIcon('b_drop', 'Delete tracking'|trans) }} + + + + {{ Util_getIcon('b_versions', 'Versions'|trans) }} + + + {{ Util_getIcon('b_report', 'Tracking report'|trans) }} + + + {{ Util_getIcon('b_props', 'Structure snapshot'|trans) }} + +
    + {% include 'select_all.twig' with { + 'pma_theme_image': pma_theme_image, + 'text_dir': text_dir, + 'form_name': 'trackedForm' + } only %} + {{ Util_getButtonOrImage( + 'submit_mult', + 'mult_submit', + 'Delete tracking'|trans, + 'b_drop', + 'delete_tracking' + ) }} +
    +
    diff --git a/php/apps/phpmyadmin49/html/templates/database/tracking/untracked_tables.twig b/php/apps/phpmyadmin49/html/templates/database/tracking/untracked_tables.twig new file mode 100644 index 00000000..8077c763 --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/database/tracking/untracked_tables.twig @@ -0,0 +1,47 @@ +

    {% trans 'Untracked tables' %}

    +
    + {{ Url_getHiddenInputs(db) }} + + + + + + + + + + {% for table_name in untracked_tables if Tracker_getVersion(db, table_name) == -1 %} + + + + + + {% endfor %} + +
    {% trans 'Table' %}{% trans 'Action' %}
    + + + + + + {{ Util_getIcon('eye', 'Track table'|trans) }} + +
    + {% include 'select_all.twig' with { + 'pma_theme_image': pma_theme_image, + 'text_dir': text_dir, + 'form_name': 'untrackedForm' + } only %} + {{ Util_getButtonOrImage( + 'submit_mult', + 'mult_submit', + 'Track table'|trans, + 'eye', + 'track' + ) }} +
    diff --git a/php/apps/phpmyadmin49/html/templates/display/export/format_dropdown.twig b/php/apps/phpmyadmin49/html/templates/display/export/format_dropdown.twig new file mode 100644 index 00000000..adda19ba --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/display/export/format_dropdown.twig @@ -0,0 +1,4 @@ +
    +

    {% trans 'Format:' %}

    + {{ dropdown|raw }} +
    diff --git a/php/apps/phpmyadmin49/html/templates/display/export/hidden_inputs.twig b/php/apps/phpmyadmin49/html/templates/display/export/hidden_inputs.twig new file mode 100644 index 00000000..fa01a187 --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/display/export/hidden_inputs.twig @@ -0,0 +1,23 @@ +{% if export_type == 'server' %} + {{ Url_getHiddenInputs('', '', 1) }} +{% elseif export_type == 'database' %} + {{ Url_getHiddenInputs(db, '', 1) }} +{% else %} + {{ Url_getHiddenInputs(db, table, 1) }} +{% endif %} + +{# Just to keep this value for possible next display of this form after saving on server #} +{% if single_table is not empty %} + +{% endif %} + + + +{# The export method (quick, custom or custom-no-form) #} + + +{% if sql_query is not empty %} + +{% endif %} + + diff --git a/php/apps/phpmyadmin49/html/templates/display/export/method.twig b/php/apps/phpmyadmin49/html/templates/display/export/method.twig new file mode 100644 index 00000000..5521f574 --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/display/export/method.twig @@ -0,0 +1,22 @@ +{% if export_method != 'custom-no-form' %} +
    +

    {% trans 'Export method:' %}

    +
      +
    • + + +
    • + +
    • + + +
    • +
    +
    +{% endif %} diff --git a/php/apps/phpmyadmin49/html/templates/display/export/option_header.twig b/php/apps/phpmyadmin49/html/templates/display/export/option_header.twig new file mode 100644 index 00000000..074586a7 --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/display/export/option_header.twig @@ -0,0 +1,12 @@ + diff --git a/php/apps/phpmyadmin49/html/templates/display/export/options_format.twig b/php/apps/phpmyadmin49/html/templates/display/export/options_format.twig new file mode 100644 index 00000000..7cfbe13b --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/display/export/options_format.twig @@ -0,0 +1,24 @@ +
    +

    {% trans 'Format-specific options:' %}

    +

    + {% trans 'Scroll down to fill in the options for the selected format and ignore the options for other formats.' %} +

    + {{ options|raw }} +
    + +{% if can_convert_kanji %} + {# Japanese encoding setting #} +
    +

    {% trans 'Encoding Conversion:' %}

    + {% include 'encoding/kanji_encoding_form.twig' %} +
    +{% endif %} + +
    + 0 %} + onclick="check_time_out({{ exec_time_limit }})" + {%- endif %}> +
    diff --git a/php/apps/phpmyadmin49/html/templates/display/export/options_output.twig b/php/apps/phpmyadmin49/html/templates/display/export/options_output.twig new file mode 100644 index 00000000..60d721ca --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/display/export/options_output.twig @@ -0,0 +1,54 @@ +
    +

    {% trans 'Output:' %}

    +
      +
    • + + +
    • + + {% if export_type != 'server' %} +
    • + + +
    • + {% endif %} + +
    • + + +
        + {% if save_dir is not empty %} + {{ options_output_save_dir|raw }} + {% endif %} + + {{ options_output_format|raw }} + + {% if is_encoding_supported %} + {{ options_output_charset|raw }} + {% endif %} + + {{ options_output_compression|raw }} + + {% if export_type == 'server' or export_type == 'database' %} + {{ options_output_separate_files|raw }} + {% endif %} +
      +
    • + + {{ options_output_radio|raw }} +
    + + ' + )|raw }} +
    diff --git a/php/apps/phpmyadmin49/html/templates/display/export/options_output_charset.twig b/php/apps/phpmyadmin49/html/templates/display/export/options_output_charset.twig new file mode 100644 index 00000000..bd316bf9 --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/display/export/options_output_charset.twig @@ -0,0 +1,16 @@ +
  • + + +
  • diff --git a/php/apps/phpmyadmin49/html/templates/display/export/options_output_compression.twig b/php/apps/phpmyadmin49/html/templates/display/export/options_output_compression.twig new file mode 100644 index 00000000..19059811 --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/display/export/options_output_compression.twig @@ -0,0 +1,24 @@ +{% if is_zip or is_gzip %} +
  • + + +
  • +{% else %} + +{% endif %} diff --git a/php/apps/phpmyadmin49/html/templates/display/export/options_output_format.twig b/php/apps/phpmyadmin49/html/templates/display/export/options_output_format.twig new file mode 100644 index 00000000..0039d87c --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/display/export/options_output_format.twig @@ -0,0 +1,13 @@ +
  • + + + + +
  • diff --git a/php/apps/phpmyadmin49/html/templates/display/export/options_output_radio.twig b/php/apps/phpmyadmin49/html/templates/display/export/options_output_radio.twig new file mode 100644 index 00000000..7adf21ee --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/display/export/options_output_radio.twig @@ -0,0 +1,7 @@ +
  • + + +
  • diff --git a/php/apps/phpmyadmin49/html/templates/display/export/options_output_save_dir.twig b/php/apps/phpmyadmin49/html/templates/display/export/options_output_save_dir.twig new file mode 100644 index 00000000..b4526aa4 --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/display/export/options_output_save_dir.twig @@ -0,0 +1,15 @@ +
  • + + +
  • +
  • + + +
  • diff --git a/php/apps/phpmyadmin49/html/templates/display/export/options_output_separate_files.twig b/php/apps/phpmyadmin49/html/templates/display/export/options_output_separate_files.twig new file mode 100644 index 00000000..fec85c4d --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/display/export/options_output_separate_files.twig @@ -0,0 +1,12 @@ +
  • + + +
  • diff --git a/php/apps/phpmyadmin49/html/templates/display/export/options_quick_export.twig b/php/apps/phpmyadmin49/html/templates/display/export/options_quick_export.twig new file mode 100644 index 00000000..b3bd159f --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/display/export/options_quick_export.twig @@ -0,0 +1,20 @@ +
    +

    {% trans 'Output:' %}

    +
      +
    • + + +
    • +
    • + + +
    • +
    +
    diff --git a/php/apps/phpmyadmin49/html/templates/display/export/options_rows.twig b/php/apps/phpmyadmin49/html/templates/display/export/options_rows.twig new file mode 100644 index 00000000..5a7e39ed --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/display/export/options_rows.twig @@ -0,0 +1,35 @@ +
    +

    {% trans 'Rows:' %}

    +
      +
    • + + +
        +
      • + + +
      • +
      • + + +
      • +
      +
    • +
    • + + +
    • +
    +
    diff --git a/php/apps/phpmyadmin49/html/templates/display/export/select_options.twig b/php/apps/phpmyadmin49/html/templates/display/export/select_options.twig new file mode 100644 index 00000000..7b153a3d --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/display/export/select_options.twig @@ -0,0 +1,19 @@ +
    +

    + + {% trans 'Select all' %} + + / + + {% trans 'Unselect all' %} + +

    + + +
    diff --git a/php/apps/phpmyadmin49/html/templates/display/export/selection.twig b/php/apps/phpmyadmin49/html/templates/display/export/selection.twig new file mode 100644 index 00000000..6f691f5a --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/display/export/selection.twig @@ -0,0 +1,10 @@ +
    + {% if export_type == 'server' %} +

    {% trans 'Databases:' %}

    + {% elseif export_type == 'database' %} +

    {% trans 'Tables:' %}

    + {% endif %} + {% if multi_values is not empty %} + {{ multi_values|raw }} + {% endif %} +
    diff --git a/php/apps/phpmyadmin49/html/templates/display/export/template_loading.twig b/php/apps/phpmyadmin49/html/templates/display/export/template_loading.twig new file mode 100644 index 00000000..e1f57d83 --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/display/export/template_loading.twig @@ -0,0 +1,27 @@ +
    +

    {% trans 'Export templates:' %}

    + +
    +
    +

    {% trans 'New template:' %}

    + + +
    +
    + +
    +
    +

    {% trans 'Existing templates:' %}

    + + + + +
    +
    + +
    +
    diff --git a/php/apps/phpmyadmin49/html/templates/display/export/template_options.twig b/php/apps/phpmyadmin49/html/templates/display/export/template_options.twig new file mode 100644 index 00000000..ddcd4f5b --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/display/export/template_options.twig @@ -0,0 +1,7 @@ + + +{% for template in templates %} + +{% endfor %} diff --git a/php/apps/phpmyadmin49/html/templates/display/import/import.twig b/php/apps/phpmyadmin49/html/templates/display/import/import.twig new file mode 100644 index 00000000..331045a7 --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/display/import/import.twig @@ -0,0 +1,194 @@ + +
    +
    + ajax clock + + + +
    + + + {% if import_type == 'server' %} + {{ Url_getHiddenInputs('', '', 1) }} + {% elseif import_type == 'database' %} + {{ Url_getHiddenInputs(db, '', 1) }} + {% else %} + {{ Url_getHiddenInputs(db, table, 1) }} + {% endif %} + + + + +
    +

    {% trans 'File to import:' %}

    + + {# We don't have show anything about compression, when no supported #} + {% if compressions is not empty %} +
    +

    + {{ 'File may be compressed (%s) or uncompressed.'|trans|format(compressions|join(', ')) }} +
    + {% trans 'A compressed file\'s name must end in .[format].[compression]. Example: .sql.zip' %} +

    +
    + {% endif %} + +
    + {% if is_upload and upload_dir is not empty %} +
      +
    • + + {{ Util_getBrowseUploadFileBlock(max_upload_size) }} + {% trans 'You may also drag and drop a file on any page.' %} +
    • +
    • + + {{ Util_getSelectUploadFileBlock( + import_list, + upload_dir + ) }} +
    • +
    + {% elseif is_upload %} + {{ Util_getBrowseUploadFileBlock(max_upload_size) }} +

    {% trans 'You may also drag and drop a file on any page.' %}

    + {% elseif not is_upload %} + {{ Message_notice('File uploads are not allowed on this server.'|trans) }} + {% elseif upload_dir is not empty %} + {{ Util_getSelectUploadFileBlock( + import_list, + upload_dir + ) }} + {% endif %} +
    + +
    + {# Charset of file #} + + {% if is_encoding_supported %} + + {% else %} + {{ Charsets_getCharsetDropdownBox( + dbi, + disable_is, + 'charset_of_file', + 'charset_of_file', + 'utf8', + false + ) }} + {% endif %} +
    +
    + +
    +

    {% trans 'Partial import:' %}

    + + {% if timeout_passed is defined and timeout_passed %} +
    + + {{ 'Previous import timed out, after resubmitting will continue from position %d.'|trans|format(offset) }} +
    + {% endif %} + +
    + + +
    + + {% if not (timeout_passed is defined and timeout_passed) %} +
    + + +
    + {% else %} + {# If timeout has passed, + do not show the Skip dialog to avoid the risk of someone + entering a value here that would interfere with "skip" #} + + {% endif %} +
    + +
    +

    {% trans 'Other options:' %}

    +
    + {{ Util_getFKCheckbox() }} +
    +
    + +
    +

    {% trans 'Format:' %}

    + {{ Plugins_getChoice('Import', 'format', import_list) }} +
    +
    + +
    +

    {% trans 'Format-specific options:' %}

    +

    + {% trans 'Scroll down to fill in the options for the selected format and ignore the options for other formats.' %} +

    + {{ Plugins_getOptions('Import', import_list) }} +
    +
    + + {# Japanese encoding setting #} + {% if can_convert_kanji %} +
    +

    {% trans 'Encoding Conversion:' %}

    + {% include 'encoding/kanji_encoding_form.twig' %} +
    + {% endif %} + +
    + +
    +
    +
    diff --git a/php/apps/phpmyadmin49/html/templates/display/import/javascript.twig b/php/apps/phpmyadmin49/html/templates/display/import/javascript.twig new file mode 100644 index 00000000..01bd199f --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/display/import/javascript.twig @@ -0,0 +1,171 @@ +$( function() { + {# Add event when user click on "Go" button #} + $("#buttonGo").bind("click", function() { + {# Hide form #} + $("#upload_form_form").css("display", "none"); + + {% if handler != 'PhpMyAdmin\\Plugins\\Import\\Upload\\UploadNoplugin' %} + {# Some variable for javascript #} + {% set ajax_url = 'import_status.php?id=' ~ upload_id ~ '&' ~ Url_getCommonRaw({ + 'import_status': 1 + }) %} + {% set promot_str = Sanitize_jsFormat( + 'The file being uploaded is probably larger than the maximum allowed size or this is a known bug in webkit based (Safari, Google Chrome, Arora etc.) browsers.'|trans, + false + ) %} + {% set statustext_str = Sanitize_escapeJsString('%s of %s'|trans) %} + {% set second_str = Sanitize_jsFormat('%s/sec.'|trans, false) %} + {% set remaining_min = Sanitize_jsFormat('About %MIN min. %SEC sec. remaining.'|trans, false) %} + {% set remaining_second = Sanitize_jsFormat('About %SEC sec. remaining.'|trans, false) %} + {% set processed_str = Sanitize_jsFormat( + 'The file is being processed, please be patient.'|trans, + false + ) %} + {% set import_url = Url_getCommonRaw({'import_status': 1}) %} + + {% set upload_html %} + {% spaceless %} +
    +
    +
    +
    +
    +
    +
    +
    + ajax clock {{ Sanitize_jsFormat('Uploading your import file…'|trans, false) -}} +
    +
    +
    + {% endspaceless %} + {% endset %} + + {# Start output #} + var finished = false; + var percent = 0.0; + var total = 0; + var complete = 0; + var original_title = parent && parent.document ? parent.document.title : false; + var import_start; + + var perform_upload = function () { + new $.getJSON( + "{{ ajax_url|raw }}", + {}, + function(response) { + finished = response.finished; + percent = response.percent; + total = response.total; + complete = response.complete; + + if (total==0 && complete==0 && percent==0) { + $("#upload_form_status_info").html('ajax clock {{ promot_str|raw }}'); + $("#upload_form_status").css("display", "none"); + } else { + var now = new Date(); + now = Date.UTC( + now.getFullYear(), + now.getMonth(), + now.getDate(), + now.getHours(), + now.getMinutes(), + now.getSeconds()) + + now.getMilliseconds() - 1000; + var statustext = PMA_sprintf( + "{{ statustext_str|raw }}", + formatBytes( + complete, 1, PMA_messages.strDecimalSeparator + ), + formatBytes( + total, 1, PMA_messages.strDecimalSeparator + ) + ); + + if ($("#importmain").is(":visible")) { + {# Show progress UI #} + $("#importmain").hide(); + $("#import_form_status") + .html('{{ upload_html|raw }}') + .show(); + import_start = now; + } + else if (percent > 9 || complete > 2000000) { + {# Calculate estimated time #} + var used_time = now - import_start; + var seconds = parseInt(((total - complete) / complete) * used_time / 1000); + var speed = PMA_sprintf( + "{{ second_str|raw }}", + formatBytes(complete / used_time * 1000, 1, PMA_messages.strDecimalSeparator) + ); + + var minutes = parseInt(seconds / 60); + seconds %= 60; + var estimated_time; + if (minutes > 0) { + estimated_time = "{{ remaining_min|raw }}" + .replace("%MIN", minutes) + .replace("%SEC", seconds); + } + else { + estimated_time = "{{ remaining_second|raw }}" + .replace("%SEC", seconds); + } + + statustext += "
    " + speed + "

    " + estimated_time; + } + + var percent_str = Math.round(percent) + "%"; + $("#status").animate({width: percent_str}, 150); + $(".percentage").text(percent_str); + + {# Show percent in window title #} + if (original_title !== false) { + parent.document.title + = percent_str + " - " + original_title; + } + else { + document.title + = percent_str + " - " + original_title; + } + $("#statustext").html(statustext); + } + + if (finished == true) { + if (original_title !== false) { + parent.document.title = original_title; + } + else { + document.title = original_title; + } + $("#importmain").hide(); + {# Loads the message, either success or mysql error #} + $("#import_form_status") + .html('ajax clock {{ processed_str|raw }}') + .show(); + $("#import_form_status").load("import_status.php?message=true&{{ import_url|raw }}"); + PMA_reloadNavigation(); + + {# If finished #} + } + else { + setTimeout(perform_upload, 1000); + } + }); + }; + setTimeout(perform_upload, 1000); + {% else %} + {# No plugin available #} + {% set image_tag -%} + ajax clock + {{- Sanitize_jsFormat( + 'Please be patient, the file is being uploaded. Details about the upload are not available.'|trans, + false + ) -}} + {{- Util_showDocu('faq', 'faq2-9') -}} + {%- endset %} + $('#upload_form_status_info').html('{{ image_tag|raw }}'); + $("#upload_form_status").css("display", "none"); + {% endif %} + }); +}); diff --git a/php/apps/phpmyadmin49/html/templates/display/results/additional_fields.twig b/php/apps/phpmyadmin49/html/templates/display/results/additional_fields.twig new file mode 100644 index 00000000..745b3bbd --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/display/results/additional_fields.twig @@ -0,0 +1,14 @@ + + +{# Do not change the position when changing the number of rows #} + + +{% trans 'Number of rows:' %} +{{ Util_getDropdown( + 'session_max_rows', + number_of_rows_choices, + max_rows, + '', + 'autosubmit', + number_of_rows_placeholder +) }} diff --git a/php/apps/phpmyadmin49/html/templates/display/results/comment_for_row.twig b/php/apps/phpmyadmin49/html/templates/display/results/comment_for_row.twig new file mode 100644 index 00000000..5380b71d --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/display/results/comment_for_row.twig @@ -0,0 +1,10 @@ +{% if comments_map[fields_meta.table] is defined + and comments_map[fields_meta.table][fields_meta.name] is defined %} + + {% if comments_map[fields_meta.table][fields_meta.name]|length > limit_chars %} + {{ comments_map[fields_meta.table][fields_meta.name]|slice(0, limit_chars) }}… + {% else %} + {{ comments_map[fields_meta.table][fields_meta.name] }} + {% endif %} + +{% endif %} diff --git a/php/apps/phpmyadmin49/html/templates/display/results/empty_display.twig b/php/apps/phpmyadmin49/html/templates/display/results/empty_display.twig new file mode 100644 index 00000000..cd43ebc7 --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/display/results/empty_display.twig @@ -0,0 +1 @@ + diff --git a/php/apps/phpmyadmin49/html/templates/display/results/multi_row_operations_form.twig b/php/apps/phpmyadmin49/html/templates/display/results/multi_row_operations_form.twig new file mode 100644 index 00000000..bf87c7e0 --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/display/results/multi_row_operations_form.twig @@ -0,0 +1,12 @@ +{% if delete_link == delete_row or delete_link == kill_process %} +
    + {{ Url_getHiddenInputs(db, table, 1) }} + +{% endif %} + +
    + diff --git a/php/apps/phpmyadmin49/html/templates/display/results/null_display.twig b/php/apps/phpmyadmin49/html/templates/display/results/null_display.twig new file mode 100644 index 00000000..f2ea2e5f --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/display/results/null_display.twig @@ -0,0 +1,7 @@ + diff --git a/php/apps/phpmyadmin49/html/templates/display/results/options_block.twig b/php/apps/phpmyadmin49/html/templates/display/results/options_block.twig new file mode 100644 index 00000000..465f9e34 --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/display/results/options_block.twig @@ -0,0 +1,117 @@ + + {{ Url_getHiddenInputs({ + 'db': db, + 'table': table, + 'sql_query': sql_query, + 'goto': goto, + 'display_options_form': 1 + }) }} + + {{ Util_getDivForSliderEffect('', 'Options'|trans) }} +
    +
    + {# pftext means "partial or full texts" (done to reduce line lengths #} + {{ Util_getRadioFields( + 'pftext', + { + 'P': 'Partial texts'|trans, + 'F': 'Full texts'|trans + }, + pftext, + true, + true, + '', + 'pftext_' ~ unique_id + ) }} +
    + + {% if relwork and displaywork %} +
    + {{ Util_getRadioFields( + 'relational_display', + { + 'K': 'Relational key'|trans, + 'D': 'Display column for relationships'|trans + }, + relational_display, + true, + true, + '', + 'relational_display_' ~ unique_id + ) }} +
    + {% endif %} + +
    + {% include 'checkbox.twig' with { + 'html_field_name': 'display_binary', + 'label': 'Show binary contents'|trans, + 'checked': display_binary is not empty, + 'onclick': false, + 'html_field_id': 'display_binary_' ~ unique_id + } only %} + {% include 'checkbox.twig' with { + 'html_field_name': 'display_blob', + 'label': 'Show BLOB contents'|trans, + 'checked': display_blob is not empty, + 'onclick': false, + 'html_field_id': 'display_blob_' ~ unique_id + } only %} +
    + + {# I would have preferred to name this "display_transformation". + This is the only way I found to be able to keep this setting sticky + per SQL query, and at the same time have a default that displays + the transformations. #} +
    + {% include 'checkbox.twig' with { + 'html_field_name': 'hide_transformation', + 'label': 'Hide browser transformation'|trans, + 'checked': hide_transformation is not empty, + 'onclick': false, + 'html_field_id': 'hide_transformation_' ~ unique_id + } only %} +
    + + + {% if possible_as_geometry %} +
    + {{ Util_getRadioFields( + 'geoOption', + { + 'GEOM': 'Geometry'|trans, + 'WKT': 'Well Known Text'|trans, + 'WKB': 'Well Known Binary'|trans + }, + geo_option, + true, + true, + '', + 'geoOption_' ~ unique_id + ) }} +
    + {% else %} +
    + {{ possible_as_geometry }} + {{ Util_getRadioFields( + 'geoOption', + { + 'WKT': 'Well Known Text'|trans, + 'WKB': 'Well Known Binary'|trans + }, + geo_option, + true, + true, + '', + 'geoOption_' ~ unique_id + ) }} +
    + {% endif %} +
    +
    + +
    + +
    + {# slider effect div #} + diff --git a/php/apps/phpmyadmin49/html/templates/display/results/show_all_checkbox.twig b/php/apps/phpmyadmin49/html/templates/display/results/show_all_checkbox.twig new file mode 100644 index 00000000..b2674d38 --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/display/results/show_all_checkbox.twig @@ -0,0 +1,13 @@ + diff --git a/php/apps/phpmyadmin49/html/templates/display/results/table_navigation_button.twig b/php/apps/phpmyadmin49/html/templates/display/results/table_navigation_button.twig new file mode 100644 index 00000000..eb4fa42e --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/display/results/table_navigation_button.twig @@ -0,0 +1,12 @@ + diff --git a/php/apps/phpmyadmin49/html/templates/display/results/value_display.twig b/php/apps/phpmyadmin49/html/templates/display/results/value_display.twig new file mode 100644 index 00000000..35640b91 --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/display/results/value_display.twig @@ -0,0 +1,3 @@ + diff --git a/php/apps/phpmyadmin49/html/templates/div_for_slider_effect.twig b/php/apps/phpmyadmin49/html/templates/div_for_slider_effect.twig new file mode 100644 index 00000000..64539993 --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/div_for_slider_effect.twig @@ -0,0 +1,16 @@ +{% if initial_sliders_state == 'disabled' %} + +{% else %} + {# + Bad hack on the next line. document.write() conflicts with jQuery, + hence, opening the
    with PHP itself instead of JavaScript. + + @todo find a better solution that uses $.append(), the recommended + method maybe by using an additional param, the id of the div to + append to + #} +
    + NULL + +
    + {{ Url_getHiddenInputs(db, table) }} + + + + + + + +
    +
    +
    + {{ Url_getHiddenInputs(db, table) }} + + + + + {{ input_for_real_end|raw }} + +
    +
    + {{ value|raw }} +
    + + + + + + + + + + + + + + + + + + + + + + + +
    {% trans 'Define new aliases' %}
    + + + + + + + +
    + + + + + + + +
    + + + + + + + +
    diff --git a/php/apps/phpmyadmin49/html/templates/export/alias_item.twig b/php/apps/phpmyadmin49/html/templates/export/alias_item.twig new file mode 100644 index 00000000..b016861b --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/export/alias_item.twig @@ -0,0 +1,10 @@ + + {{ type }} + {{ name }} + + + + + + + diff --git a/php/apps/phpmyadmin49/html/templates/filter.twig b/php/apps/phpmyadmin49/html/templates/filter.twig new file mode 100644 index 00000000..572ec465 --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/filter.twig @@ -0,0 +1,8 @@ +
    + {% trans "Filters" %} +
    + + +
    +
    diff --git a/php/apps/phpmyadmin49/html/templates/fk_checkbox.twig b/php/apps/phpmyadmin49/html/templates/fk_checkbox.twig new file mode 100644 index 00000000..afcc6156 --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/fk_checkbox.twig @@ -0,0 +1,4 @@ + + + diff --git a/php/apps/phpmyadmin49/html/templates/header_location.twig b/php/apps/phpmyadmin49/html/templates/header_location.twig new file mode 100644 index 00000000..113dab3d --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/header_location.twig @@ -0,0 +1,22 @@ +{# Manage HTML redirection #} + + + - - - + + + + + + + + + + diff --git a/php/apps/phpmyadmin49/html/templates/javascript/display.twig b/php/apps/phpmyadmin49/html/templates/javascript/display.twig new file mode 100644 index 00000000..12027efc --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/javascript/display.twig @@ -0,0 +1,7 @@ + diff --git a/php/apps/phpmyadmin49/html/templates/list/item.twig b/php/apps/phpmyadmin49/html/templates/list/item.twig new file mode 100644 index 00000000..cfbc7729 --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/list/item.twig @@ -0,0 +1,19 @@ + + + {% if url is defined and url is iterable and url['href'] is not empty %} + + {% endif %} + {{ content|raw }} + {% if url is defined and url is iterable and url['href'] is not empty %} + + {% endif %} + {% if mysql_help_page is not empty %} + {{ Util_showMySQLDocu(mysql_help_page) }} + {% endif %} + diff --git a/php/apps/phpmyadmin49/html/templates/list/unordered.twig b/php/apps/phpmyadmin49/html/templates/list/unordered.twig new file mode 100644 index 00000000..11f114e1 --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/list/unordered.twig @@ -0,0 +1,14 @@ + + + {% if items is not empty %} + {% for item in items %} + {% if item is not iterable %} + {% set item = {'content': item} %} + {% endif %} + {% include 'list/item.twig' with item only %} + {% endfor %} + {% elseif content is not empty %} + {{ content|raw }} + {% endif %} + diff --git a/php/apps/phpmyadmin49/html/templates/login/footer.twig b/php/apps/phpmyadmin49/html/templates/login/footer.twig new file mode 100644 index 00000000..04f5b844 --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/login/footer.twig @@ -0,0 +1 @@ +
    diff --git a/php/apps/phpmyadmin49/html/templates/login/header.twig b/php/apps/phpmyadmin49/html/templates/login/header.twig new file mode 100644 index 00000000..e274ebc9 --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/login/header.twig @@ -0,0 +1,13 @@ +
    + +

    {{ 'Welcome to %s'|trans|format('phpMyAdmin')|raw }}

    + + + +
    +{{ Message_error("There is mismatch between HTTPS indicated on the server and client. This can lead to non working phpMyAdmin or a security risk. Please fix your server configuration to indicate HTTPS properly."|trans) }} +
    diff --git a/php/apps/phpmyadmin49/html/templates/login/twofactor.twig b/php/apps/phpmyadmin49/html/templates/login/twofactor.twig new file mode 100644 index 00000000..52ca9c88 --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/login/twofactor.twig @@ -0,0 +1,7 @@ +
    +{{ Url_getHiddenInputs() }} +{{ form|raw }} +{% if show_submit %} + +{% endif %} +
    diff --git a/php/apps/phpmyadmin49/html/templates/login/twofactor/application.twig b/php/apps/phpmyadmin49/html/templates/login/twofactor/application.twig new file mode 100644 index 00000000..8c0d7480 --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/login/twofactor/application.twig @@ -0,0 +1,4 @@ +

    + +

    +

    {% trans "Open the two-factor authentication app on your device to view your authentication code and verify your identity." %}

    diff --git a/php/apps/phpmyadmin49/html/templates/login/twofactor/application_configure.twig b/php/apps/phpmyadmin49/html/templates/login/twofactor/application_configure.twig new file mode 100644 index 00000000..97b069da --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/login/twofactor/application_configure.twig @@ -0,0 +1,22 @@ +{{ Url_getHiddenInputs() }} +{% if image is defined %} +

    + {% trans "Please scan following QR code into the two-factor authentication app on your device and enter authentication code it generates." %} +

    +

    + +

    +{% else %} +

    + {% trans "Please enter following secret/key into the two-factor authentication app on your device and enter authentication code it generates." %} +

    +

    + {% trans "OTP url:" %} {{ url }} +

    +{% endif %} +

    + {% trans "Secret/key:" %} {{ secret }} +

    +

    + +

    diff --git a/php/apps/phpmyadmin49/html/templates/login/twofactor/invalid.twig b/php/apps/phpmyadmin49/html/templates/login/twofactor/invalid.twig new file mode 100644 index 00000000..568dd94a --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/login/twofactor/invalid.twig @@ -0,0 +1,3 @@ +
    +{% trans "The configured two factor authentication is not available, please install missing dependencies." %} +
    diff --git a/php/apps/phpmyadmin49/html/templates/login/twofactor/key-https-warning.twig b/php/apps/phpmyadmin49/html/templates/login/twofactor/key-https-warning.twig new file mode 100644 index 00000000..d3f9c04c --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/login/twofactor/key-https-warning.twig @@ -0,0 +1,5 @@ +{% if not is_https %} +
    +{% trans "You are not using https to access phpMyAdmin, therefore FIDO U2F device will most likely refuse to authenticate you." %} +
    +{% endif %} diff --git a/php/apps/phpmyadmin49/html/templates/login/twofactor/key.twig b/php/apps/phpmyadmin49/html/templates/login/twofactor/key.twig new file mode 100644 index 00000000..3781aaf2 --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/login/twofactor/key.twig @@ -0,0 +1,5 @@ +{% include 'login/twofactor/key-https-warning.twig' %} +

    +{% trans "Please connect your FIDO U2F device into your computer's USB port. Then confirm login on the device." %} +

    + diff --git a/php/apps/phpmyadmin49/html/templates/login/twofactor/key_configure.twig b/php/apps/phpmyadmin49/html/templates/login/twofactor/key_configure.twig new file mode 100644 index 00000000..d89803da --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/login/twofactor/key_configure.twig @@ -0,0 +1,5 @@ +{% include 'login/twofactor/key-https-warning.twig' %} +

    +{% trans "Please connect your FIDO U2F device into your computer's USB port. Then confirm registration on the device." %} +

    + diff --git a/php/apps/phpmyadmin49/html/templates/login/twofactor/simple.twig b/php/apps/phpmyadmin49/html/templates/login/twofactor/simple.twig new file mode 100644 index 00000000..7fd98241 --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/login/twofactor/simple.twig @@ -0,0 +1 @@ + diff --git a/php/apps/phpmyadmin49/html/templates/navigation/logo.twig b/php/apps/phpmyadmin49/html/templates/navigation/logo.twig new file mode 100644 index 00000000..d4a01f7e --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/navigation/logo.twig @@ -0,0 +1,12 @@ +{% if display_logo %} + +{% endif %} diff --git a/php/apps/phpmyadmin49/html/templates/prefs_autoload.twig b/php/apps/phpmyadmin49/html/templates/prefs_autoload.twig new file mode 100644 index 00000000..efe44a9e --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/prefs_autoload.twig @@ -0,0 +1,15 @@ + diff --git a/php/apps/phpmyadmin49/html/templates/prefs_twofactor.twig b/php/apps/phpmyadmin49/html/templates/prefs_twofactor.twig new file mode 100644 index 00000000..e32ec21e --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/prefs_twofactor.twig @@ -0,0 +1,58 @@ +
    +

    +{% trans "Two-factor authentication status" %} +{{ Util_showDocu('two_factor') }} +

    +
    +{% if enabled %} +{% if num_backends == 0 %} +

    {% trans "Two-factor authentication is not available, please install optional dependencies to enable authentication backends." %}

    +

    {% trans "Following composer packages are missing:" %}

    +
      +{% for item in missing %} +
    • {{ item.dep }} ({{ item.class }})
    • +{% endfor %} +
    +{% else %} +{% if backend_id %} +

    {% trans "Two-factor authentication is available and configured for this account." %}

    +{% else %} +

    {% trans "Two-factor authentication is available, but not configured for this account." %}

    +{% endif %} +{% endif %} +{% else %} +

    {% trans "Two-factor authentication is not available, enable phpMyAdmin configuration storage to use it." %}

    +{% endif %} +
    +
    + +{% if backend_id %} +
    +

    {{ backend_name }}

    +
    +

    {% trans "You have enabled two factor authentication." %}

    +

    {{ backend_description }}

    +
    +{{ Url_getHiddenInputs() }} + +
    +
    +
    +{% elseif num_backends > 0 %} +
    +

    {% trans "Configure two-factor authentication" %}

    +
    +
    +{{ Url_getHiddenInputs() }} +{% for backend in backends %} + +{% endfor %} + +
    +
    +
    +{% endif %} diff --git a/php/apps/phpmyadmin49/html/templates/prefs_twofactor_configure.twig b/php/apps/phpmyadmin49/html/templates/prefs_twofactor_configure.twig new file mode 100644 index 00000000..ce597a35 --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/prefs_twofactor_configure.twig @@ -0,0 +1,13 @@ +
    +

    {% trans "Configure two-factor authentication" %}

    +
    +
    +{{ Url_getHiddenInputs() }} + +{{ form|raw }} + +
    +
    +
    + + diff --git a/php/apps/phpmyadmin49/html/templates/prefs_twofactor_confirm.twig b/php/apps/phpmyadmin49/html/templates/prefs_twofactor_confirm.twig new file mode 100644 index 00000000..7d785a7f --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/prefs_twofactor_confirm.twig @@ -0,0 +1,12 @@ +
    +

    {% trans "Confirm disabling two-factor authentication" %}

    +
    +
    +{{ Message_notice("By disabling two factor authentication you will be again able to login using password only."|trans) }} +{{ Url_getHiddenInputs() }} +{{ form|raw }} + + +
    +
    +
    diff --git a/php/apps/phpmyadmin49/html/templates/preview_sql.twig b/php/apps/phpmyadmin49/html/templates/preview_sql.twig new file mode 100644 index 00000000..9219a17a --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/preview_sql.twig @@ -0,0 +1,11 @@ +
    + {% if query_data is empty %} + {% trans 'No change' %} + {% elseif query_data is iterable %} + {% for query in query_data %} + {{ Util_formatSql(query) }} + {% endfor %} + {% else %} + {{ Util_formatSql(query_data) }} + {% endif %} +
    diff --git a/php/apps/phpmyadmin49/html/templates/privileges/add_privileges_database.twig b/php/apps/phpmyadmin49/html/templates/privileges/add_privileges_database.twig new file mode 100644 index 00000000..d48723fc --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/privileges/add_privileges_database.twig @@ -0,0 +1,14 @@ + + +{%- if databases is not empty %} + +{% endif -%} + + +{{ Util_showHint("Wildcards % and _ should be escaped with a \\ to use them literally."|trans) }} diff --git a/php/apps/phpmyadmin49/html/templates/privileges/add_privileges_routine.twig b/php/apps/phpmyadmin49/html/templates/privileges/add_privileges_routine.twig new file mode 100644 index 00000000..4e101291 --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/privileges/add_privileges_routine.twig @@ -0,0 +1,14 @@ + + + + +{%- if routines is not empty %} + +{% endif -%} + + diff --git a/php/apps/phpmyadmin49/html/templates/privileges/add_privileges_table.twig b/php/apps/phpmyadmin49/html/templates/privileges/add_privileges_table.twig new file mode 100644 index 00000000..bdcaeb7e --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/privileges/add_privileges_table.twig @@ -0,0 +1,14 @@ + + + + +{%- if tables is not empty %} + +{% endif -%} + + diff --git a/php/apps/phpmyadmin49/html/templates/privileges/add_user_fieldset.twig b/php/apps/phpmyadmin49/html/templates/privileges/add_user_fieldset.twig new file mode 100644 index 00000000..6483ec85 --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/privileges/add_user_fieldset.twig @@ -0,0 +1,8 @@ +
    + {% trans %}New{% context %}Create new user{% endtrans %} + + {{ Util_getIcon('b_usradd') }}{% trans 'Add user account' %} +
    diff --git a/php/apps/phpmyadmin49/html/templates/privileges/choose_user_group.twig b/php/apps/phpmyadmin49/html/templates/privileges/choose_user_group.twig new file mode 100644 index 00000000..64a2e223 --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/privileges/choose_user_group.twig @@ -0,0 +1,9 @@ +
    + {{ Url_getHiddenInputs(params) }} +
    + {% trans 'User group' %} + {% trans 'User group' %}: + {{ Util_getDropdown('userGroup', all_user_groups, user_group, 'userGroup_select') }} + +
    +
    diff --git a/php/apps/phpmyadmin49/html/templates/privileges/column_privileges.twig b/php/apps/phpmyadmin49/html/templates/privileges/column_privileges.twig new file mode 100644 index 00000000..50af9c2a --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/privileges/column_privileges.twig @@ -0,0 +1,24 @@ +
    + + + + + {% trans 'Or' %} + +
    diff --git a/php/apps/phpmyadmin49/html/templates/privileges/delete_user_fieldset.twig b/php/apps/phpmyadmin49/html/templates/privileges/delete_user_fieldset.twig new file mode 100644 index 00000000..d948afea --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/privileges/delete_user_fieldset.twig @@ -0,0 +1,17 @@ +
    + + {{ Util_getIcon('b_usrdrop') }}{% trans 'Remove selected user accounts' %} + + +

    ({% trans 'Revoke all active privileges from the users and delete them afterwards.' %})

    + + +
    + + diff --git a/php/apps/phpmyadmin49/html/templates/privileges/edit_routine_privileges.twig b/php/apps/phpmyadmin49/html/templates/privileges/edit_routine_privileges.twig new file mode 100644 index 00000000..be3c0b18 --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/privileges/edit_routine_privileges.twig @@ -0,0 +1,26 @@ +
    + {{ header|raw }} + +
    diff --git a/php/apps/phpmyadmin49/html/templates/privileges/global_priv_table.twig b/php/apps/phpmyadmin49/html/templates/privileges/global_priv_table.twig new file mode 100644 index 00000000..8a6d1155 --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/privileges/global_priv_table.twig @@ -0,0 +1,18 @@ +{% for key, table in priv_table %} +
    + + + + + {% for priv in table %} + {% set checked = row[priv[0] ~ '_priv'] is defined and row[priv[0] ~ '_priv'] == 'Y' ? ' checked="checked"' %} + {% set formatted_priv = ServerPrivileges_formatPrivilege(priv, true) %} + {% include 'privileges/global_priv_tbl_item.twig' with { + 'checked': checked, + 'formatted_priv': formatted_priv, + 'priv': priv + } only %} + {% endfor %} +
    +{% endfor %} diff --git a/php/apps/phpmyadmin49/html/templates/privileges/global_priv_tbl_item.twig b/php/apps/phpmyadmin49/html/templates/privileges/global_priv_tbl_item.twig new file mode 100644 index 00000000..5a5c3b6c --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/privileges/global_priv_tbl_item.twig @@ -0,0 +1,9 @@ +
    + + +
    diff --git a/php/apps/phpmyadmin49/html/templates/privileges/initials_row.twig b/php/apps/phpmyadmin49/html/templates/privileges/initials_row.twig new file mode 100644 index 00000000..a36e1421 --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/privileges/initials_row.twig @@ -0,0 +1,24 @@ + + + {% for tmp_initial, initial_was_found in array_initials if tmp_initial is not same as(null) %} + {% if initial_was_found %} + + {% else %} + + {% endif %} + {% endfor %} + + +
    + + {{- tmp_initial|raw -}} + + {{ tmp_initial|raw }} + + {% trans 'Show all' %} + +
    diff --git a/php/apps/phpmyadmin49/html/templates/privileges/privileges_summary.twig b/php/apps/phpmyadmin49/html/templates/privileges/privileges_summary.twig new file mode 100644 index 00000000..31f2173d --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/privileges/privileges_summary.twig @@ -0,0 +1,62 @@ + diff --git a/php/apps/phpmyadmin49/html/templates/privileges/privileges_summary_row.twig b/php/apps/phpmyadmin49/html/templates/privileges/privileges_summary_row.twig new file mode 100644 index 00000000..4470ca51 --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/privileges/privileges_summary_row.twig @@ -0,0 +1,14 @@ + + {{ name }} + {{ privileges|raw }} + {{ grant ? 'Yes'|trans : 'No'|trans }} + + {% if type == 'database' %} + {{ table_privs ? 'Yes'|trans : 'No'|trans }} + {% elseif type == 'table' %} + {{ column_privs ? 'Yes'|trans : 'No'|trans }} + {% endif %} + + {{ edit_link|raw }} + {{ revoke_link|raw }} + diff --git a/php/apps/phpmyadmin49/html/templates/privileges/require_options.twig b/php/apps/phpmyadmin49/html/templates/privileges/require_options.twig new file mode 100644 index 00000000..157f2723 --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/privileges/require_options.twig @@ -0,0 +1,14 @@ +
    + SSL +
    + {% for require_option in require_options %} + {% if require_option['name'] is same as('ssl_cipher') %} +
    + {% endif %} + {% include 'privileges/require_options_item.twig' with { + 'require_option': require_option + } only %} + {% endfor %} +
    {# END specified_div #} +
    {# END require_ssl_div #} +
    diff --git a/php/apps/phpmyadmin49/html/templates/privileges/require_options_item.twig b/php/apps/phpmyadmin49/html/templates/privileges/require_options_item.twig new file mode 100644 index 00000000..9270daae --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/privileges/require_options_item.twig @@ -0,0 +1,23 @@ +
    + {% if require_option['radio'] %} + + + {% else %} + + + {% endif %} +
    diff --git a/php/apps/phpmyadmin49/html/templates/privileges/resource_limit_item.twig b/php/apps/phpmyadmin49/html/templates/privileges/resource_limit_item.twig new file mode 100644 index 00000000..af0a3583 --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/privileges/resource_limit_item.twig @@ -0,0 +1,11 @@ +
    + + +
    diff --git a/php/apps/phpmyadmin49/html/templates/privileges/resource_limits.twig b/php/apps/phpmyadmin49/html/templates/privileges/resource_limits.twig new file mode 100644 index 00000000..8d61ac59 --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/privileges/resource_limits.twig @@ -0,0 +1,13 @@ +
    + {% trans 'Resource limits' %} +

    + + {% trans 'Note: Setting these options to 0 (zero) removes the limit.' %} + +

    + {% for limit in limits %} + {% include 'privileges/resource_limit_item.twig' with { + 'limit': limit + } only %} + {% endfor %} +
    diff --git a/php/apps/phpmyadmin49/html/templates/radio_fields.twig b/php/apps/phpmyadmin49/html/templates/radio_fields.twig new file mode 100644 index 00000000..46d3a3cb --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/radio_fields.twig @@ -0,0 +1,11 @@ +{% if class is not empty %} +
    +{% endif %} + + +{% if is_line_break %} +
    +{% endif %} +{% if class is not empty %} +
    +{% endif %} diff --git a/php/apps/phpmyadmin49/html/templates/secondary_tabs.twig b/php/apps/phpmyadmin49/html/templates/secondary_tabs.twig new file mode 100644 index 00000000..20c9c11a --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/secondary_tabs.twig @@ -0,0 +1,6 @@ +
      + {% for tab in sub_tabs %} + {{ Util_getHtmlTab(tab, url_params) }} + {% endfor %} +
    +
    diff --git a/php/apps/phpmyadmin49/html/templates/select_all.twig b/php/apps/phpmyadmin49/html/templates/select_all.twig new file mode 100644 index 00000000..b9c21021 --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/select_all.twig @@ -0,0 +1,6 @@ +{% trans 'With selected:' %} + + +{% trans 'With selected:' %} diff --git a/php/apps/phpmyadmin49/html/templates/select_lang.twig b/php/apps/phpmyadmin49/html/templates/select_lang.twig new file mode 100644 index 00000000..98b5863f --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/select_lang.twig @@ -0,0 +1,32 @@ +
    + {{ Url_getHiddenInputs(_form_params) }} + + {% if use_fieldset %} +
    + {{ language_title|raw }} + {% else %} + + + + {% endif %} + + + + {% if use_fieldset %} +
    + {% endif %} + +
    diff --git a/php/apps/phpmyadmin49/html/templates/server/binlog/log_row.twig b/php/apps/phpmyadmin49/html/templates/server/binlog/log_row.twig new file mode 100644 index 00000000..a5ffabd8 --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/server/binlog/log_row.twig @@ -0,0 +1,10 @@ + + {{ value['Log_name'] }} + {{ value['Pos'] }} + {{ value['Event_type'] }} + {{ value['Server_id'] }} + + {{- value['Orig_log_pos'] is defined ? value['Orig_log_pos'] : value['End_log_pos'] -}} + + {{ Util_formatSql(value['Info'], not dontlimitchars) }} + diff --git a/php/apps/phpmyadmin49/html/templates/server/binlog/log_selector.twig b/php/apps/phpmyadmin49/html/templates/server/binlog/log_selector.twig new file mode 100644 index 00000000..bbdc382b --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/server/binlog/log_selector.twig @@ -0,0 +1,29 @@ +
    + {{ Url_getHiddenInputs(url_params) }} +
    + + {% trans 'Select binary log to view' %} + + {% set full_size = 0 %} + + {{ binary_logs|length }} + {% trans 'Files' %}, + {% if full_size > 0 %} + {{ Util_formatByteDown(full_size)|join(' ') }} + {% endif %} +
    +
    + +
    +
    diff --git a/php/apps/phpmyadmin49/html/templates/server/collations/charsets.twig b/php/apps/phpmyadmin49/html/templates/server/collations/charsets.twig new file mode 100644 index 00000000..e23cf3c3 --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/server/collations/charsets.twig @@ -0,0 +1,26 @@ +
    + + + + + + + + {% for current_charset in mysql_charsets %} + + + + {% for current_collation in mysql_collations[current_charset] %} + + + + + {% endfor %} + {% endfor %} +
    {% trans 'Collation' %}{% trans 'Description' %}
    + {{ current_charset }} + {% if mysql_charsets_desc[current_charset] is not empty %} + ({{ mysql_charsets_desc[current_charset] }}) + {% endif %} +
    {{ current_collation }}{{ Charsets_getCollationDescr(current_collation) }}
    +
    diff --git a/php/apps/phpmyadmin49/html/templates/server/databases/create.twig b/php/apps/phpmyadmin49/html/templates/server/databases/create.twig new file mode 100644 index 00000000..fff3c2dc --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/server/databases/create.twig @@ -0,0 +1,50 @@ +
      +
    • + {% if is_create_db_priv %} +
      +

      + + {{ Util_showMySQLDocu('CREATE_DATABASE') }} +

      + + {{ Url_getHiddenInputs('', '', 5) }} + + {% if dbstats is not empty %} + + {% endif %} + + + {{ Charsets_getCollationDropdownBox( + dbi, + disable_is, + 'db_collation', + null, + server_collation, + true + ) }} + +
      + {% else %} + {# db creation no privileges message #} +

      + {{ Util_getImage('b_newdb') }} + {% trans 'Create database' %} + {{ Util_showMySQLDocu('CREATE_DATABASE') }} +

      + + + {{ Util_getImage( + 's_error', + '', + {'hspace': 2, 'border': 0, 'align': 'middle'} + ) }} + {% trans 'No Privileges' %} + + {% endif %} +
    • +
    diff --git a/php/apps/phpmyadmin49/html/templates/server/databases/databases_footer.twig b/php/apps/phpmyadmin49/html/templates/server/databases/databases_footer.twig new file mode 100644 index 00000000..31204823 --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/server/databases/databases_footer.twig @@ -0,0 +1,75 @@ + + + {% if is_superuser or allow_user_drop_database %} + + {% endif %} + + {% trans 'Total' %}: + {{- database_count -}} + + + {% for stat_name, stat in column_order if stat_name in first_database|keys %} + {% if stat['format'] is same as('byte') %} + {% set byte_format = Util_formatByteDown(stat['footer'], 3, 1) %} + {% set value = byte_format[0] %} + {% set unit = byte_format[1] %} + {% elseif stat['format'] is same as('number') %} + {% set value = Util_formatNumber(stat['footer'], 0) %} + {% else %} + {% set value = htmlentities(stat['footer'], 0) %} + {% endif %} + + + {% if stat['description_function'] is defined %} + + {{ value }} + + {% else %} + {{ value }} + {% endif %} + + {% if stat['format'] is same as('byte') %} + {{ unit }} + {% endif %} + {% endfor %} + {% if master_replication %} + + {% endif %} + {% if slave_replication %} + + {% endif %} + + + + +
    + +{# Footer buttons #} +{% if is_superuser or allow_user_drop_database %} + {% include 'select_all.twig' with { + 'pma_theme_image': pma_theme_image, + 'text_dir': text_dir, + 'form_name': 'dbStatsForm' + } only %} + + {{ Util_getButtonOrImage( + '', + 'mult_submit ajax', + 'Drop'|trans, + 'b_deltbl' + ) }} +{% endif %} + +{# Enable statistics #} +{% if dbstats is empty %} + {{ Message_notice('Note: Enabling the database statistics here might cause heavy traffic between the web server and the MySQL server.'|trans) }} + +{% endif %} + +
    diff --git a/php/apps/phpmyadmin49/html/templates/server/databases/databases_header.twig b/php/apps/phpmyadmin49/html/templates/server/databases/databases_header.twig new file mode 100644 index 00000000..996a8514 --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/server/databases/databases_header.twig @@ -0,0 +1,29 @@ +
    + {{ Util_getListNavigator( + database_count, + pos, + url_params, + 'server_databases.php', + 'frame_content', + max_db_list + ) }} +
    + {{ Url_getHiddenInputs(url_params) }} + {% set url_params = url_params|merge({ + 'sort_by': 'SCHEMA_NAME', + 'sort_order': sort_by == 'SCHEMA_NAME' and sort_order == 'asc' ? 'desc' : 'asc' + }) %} +
    + + {% include 'server/databases/table_header.twig' with { + 'url_params': url_params, + 'sort_by': sort_by, + 'sort_order': sort_order, + 'sort_order_text': sort_order == 'asc' ? 'Ascending'|trans : 'Descending'|trans, + 'column_order': column_order, + 'first_database': first_database, + 'master_replication': master_replication, + 'slave_replication': slave_replication, + 'is_superuser': is_superuser, + 'allow_user_drop_database': allow_user_drop_database + } only %} diff --git a/php/apps/phpmyadmin49/html/templates/server/databases/index.twig b/php/apps/phpmyadmin49/html/templates/server/databases/index.twig new file mode 100644 index 00000000..7d465e2b --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/server/databases/index.twig @@ -0,0 +1,25 @@ +{# Displays the sub-page heading #} +{% include 'server/sub_page_header.twig' with { + 'type': dbstats ? 'database_statistics' : 'databases' +} only %} + +{# Displays For Create database #} +{% if show_create_db %} + {% include 'server/databases/create.twig' with { + 'is_create_db_priv': is_create_db_priv, + 'dbstats': dbstats, + 'db_to_create': db_to_create, + 'server_collation': server_collation, + 'dbi': dbi, + 'disable_is': disable_is + } only %} +{% endif %} + +{% include 'filter.twig' with {'filter_value': ''} only %} + +{# Displays the page #} +{% if databases is not null %} + {{ databases|raw }} +{% else %} +

    {% trans 'No databases' %}

    +{% endif %} diff --git a/php/apps/phpmyadmin49/html/templates/server/databases/table_header.twig b/php/apps/phpmyadmin49/html/templates/server/databases/table_header.twig new file mode 100644 index 00000000..29455ab4 --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/server/databases/table_header.twig @@ -0,0 +1,39 @@ + + + {% if is_superuser or allow_user_drop_database %} + + {% endif %} + + {% for stat_name, stat in column_order if stat_name in first_database|keys %} + {% set url_params = url_params|merge({ + 'sort_by': stat_name, + 'sort_order': sort_by == stat_name and sort_order == 'desc' ? 'asc' : 'desc' + }) %} + + + + {{ stat['disp_name'] }} + {{ sort_by == stat_name ? Util_getImage( + 's_' ~ sort_order, + sort_order_text + ) }} + + + {% endfor %} + {% if master_replication %} + + {% endif %} + {% if slave_replication %} + + {% endif %} + + + diff --git a/php/apps/phpmyadmin49/html/templates/server/databases/table_row.twig b/php/apps/phpmyadmin49/html/templates/server/databases/table_row.twig new file mode 100644 index 00000000..03bdd57e --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/server/databases/table_row.twig @@ -0,0 +1,65 @@ + + {% if is_superuser or allow_user_drop_database %} + + {% endif %} + + {% for stat_name, stat in column_order if stat_name in current|keys %} + {% if stat['format'] is same as('byte') %} + {% set byte_format = Util_formatByteDown(current[stat_name], 3, 1) %} + {% set value = byte_format[0] %} + {% set unit = byte_format[1] %} + {% elseif stat['format'] is same as('number') %} + {% set value = Util_formatNumber(current[stat_name], 0) %} + {% else %} + {% set value = htmlentities(current[stat_name], 0) %} + {% endif %} + + + {% if stat['format'] is same as('byte') %} + + {% endif %} + {% endfor %} + + {% if master_replication_status %} + + {% endif %} + + {% if slave_replication_status %} + + {% endif %} + + + diff --git a/php/apps/phpmyadmin49/html/templates/server/engines/engine.twig b/php/apps/phpmyadmin49/html/templates/server/engines/engine.twig new file mode 100644 index 00000000..7744ed1c --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/server/engines/engine.twig @@ -0,0 +1,39 @@ +

    + {{ Util_getImage('b_engine') }} + {{ title }} + {{ Util_showMySQLDocu(help_page) }} +

    +

    {{ comment }}

    + +{% if info_pages is not empty and info_pages is iterable %} +

    + [ + {% if page is empty %} + {% trans 'Variables' %} + {% else %} + + {% trans 'Variables' %} + + {% endif %} + {% for current, label in info_pages %} + | + {% if page is defined and page == current %} + {{ label }} + {% else %} + + {{ label }} + + {% endif %} + {% endfor %} + ] +

    +{% endif %} + +{% if page_output is not empty %} + {{ page_output|raw }} +{% else %} +

    {{ support }}

    + {{ variables|raw }} +{% endif %} diff --git a/php/apps/phpmyadmin49/html/templates/server/engines/engines.twig b/php/apps/phpmyadmin49/html/templates/server/engines/engines.twig new file mode 100644 index 00000000..5a3d68c1 --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/server/engines/engines.twig @@ -0,0 +1,22 @@ +
    + + {% trans 'Database' %} + {{ sort_by == 'SCHEMA_NAME' ? Util_getImage( + 's_' ~ sort_order, + sort_order_text + ) }} + + {% trans 'Master replication' %}{% trans 'Slave replication' %}{% trans 'Action' %}
    + + + + {{ current['SCHEMA_NAME'] }} + + + {% if stat['description_function'] is defined %} + + {{ value }} + + {% else %} + {{ value }} + {% endif %} + {{ unit }} + {{ master_replication|raw }} + + {{ slave_replication|raw }} + + + {{ Util_getIcon('s_rights', 'Check privileges'|trans) }} + +
    + + + + + + + + {% for engine, details in engines %} + + + + + {% endfor %} + +
    {% trans 'Storage Engine' %}{% trans 'Description' %}
    + + {{ details['Engine'] }} + + {{ details['Comment'] }}
    diff --git a/php/apps/phpmyadmin49/html/templates/server/plugins/section.twig b/php/apps/phpmyadmin49/html/templates/server/plugins/section.twig new file mode 100644 index 00000000..bca286d4 --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/server/plugins/section.twig @@ -0,0 +1,35 @@ +
    + + + + + + + + + + + + + {% for plugin in plugin_list %} + + + + + + + + {% endfor %} + +
    + {{ plugin_type }} +
    {% trans 'Plugin' %}{% trans 'Description' %}{% trans 'Version' %}{% trans 'Author' %}{% trans 'License' %}
    + {{ plugin['plugin_name'] }} + {% if not plugin['is_active'] %} + + {% trans 'disabled' %} + + {% endif %} + {{ plugin['plugin_description'] }}{{ plugin['plugin_type_version'] }}{{ plugin['plugin_author'] }}{{ plugin['plugin_license'] }}
    +
    diff --git a/php/apps/phpmyadmin49/html/templates/server/plugins/section_links.twig b/php/apps/phpmyadmin49/html/templates/server/plugins/section_links.twig new file mode 100644 index 00000000..f6862330 --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/server/plugins/section_links.twig @@ -0,0 +1,7 @@ + diff --git a/php/apps/phpmyadmin49/html/templates/server/sub_page_header.twig b/php/apps/phpmyadmin49/html/templates/server/sub_page_header.twig new file mode 100644 index 00000000..eb85c712 --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/server/sub_page_header.twig @@ -0,0 +1,48 @@ +{# array contains Sub page icon and text #} +{% set header = { + 'variables': { + 'image': 's_vars', + 'text': 'Server variables and settings'|trans + }, + 'engines': { + 'image': 'b_engine', + 'text': 'Storage engines'|trans + }, + 'plugins': { + 'image': 'b_plugin', + 'text': 'Plugins'|trans + }, + 'binlog': { + 'image': 's_tbl', + 'text': 'Binary log'|trans + }, + 'collations': { + 'image': 's_asci', + 'text': 'Character sets and collations'|trans + }, + 'replication': { + 'image': 's_replication', + 'text': 'Replication'|trans + }, + 'database_statistics': { + 'image': 's_db', + 'text': 'Databases statistics'|trans + }, + 'databases': { + 'image': 's_db', + 'text': 'Databases'|trans + }, + 'privileges': { + 'image': 'b_usrlist', + 'text': 'Privileges'|trans + } +} %} +

    + {% if is_image|default(true) %} + {{ Util_getImage(header[type]['image']) }} + {% else %} + {{ Util_getIcon(header[type]['image']) }} + {% endif %} + {{ header[type]['text'] }} + {{ link is defined ? Util_showMySQLDocu(link) }} +

    diff --git a/php/apps/phpmyadmin49/html/templates/server/variables/link_template.twig b/php/apps/phpmyadmin49/html/templates/server/variables/link_template.twig new file mode 100644 index 00000000..4bcbfb15 --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/server/variables/link_template.twig @@ -0,0 +1,10 @@ + + {{ Util_getIcon('b_save', 'Save'|trans) }} + + + {{ Util_getIcon('b_close', 'Cancel'|trans) }} + +{{ Util_getImage('b_help', 'Documentation'|trans, { + 'class': 'hide', + 'id': 'docImage' +}) }} diff --git a/php/apps/phpmyadmin49/html/templates/server/variables/session_variable_row.twig b/php/apps/phpmyadmin49/html/templates/server/variables/session_variable_row.twig new file mode 100644 index 00000000..f6f286af --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/server/variables/session_variable_row.twig @@ -0,0 +1,5 @@ + + + ({% trans 'Session value' %}) +  {{ value }} + diff --git a/php/apps/phpmyadmin49/html/templates/server/variables/variable_row.twig b/php/apps/phpmyadmin49/html/templates/server/variables/variable_row.twig new file mode 100644 index 00000000..174ac61b --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/server/variables/variable_row.twig @@ -0,0 +1,29 @@ + + + {% if editable %} + {{ Util_getIcon('b_edit', 'Edit'|trans) }} + {% else %} + + {{ Util_getIcon('bd_edit', 'Edit'|trans) }} + + {% endif %} + + + {% if doc_link != null %} + + {{ Util_showMySQLDocu(doc_link[1], false, doc_link[2] ~ '_' ~ doc_link[0], true) }} + {{ name|e|replace({'_': ' '})|raw }} + + + {% else %} + {{ name|replace({'_': ' '}) }} + {% endif %} + + + {% if is_html_formatted == false %} + {{ value|e|replace({',': ',​'})|raw }} + {% else %} + {{ value|raw }} + {% endif %} + + diff --git a/php/apps/phpmyadmin49/html/templates/server/variables/variable_table_head.twig b/php/apps/phpmyadmin49/html/templates/server/variables/variable_table_head.twig new file mode 100644 index 00000000..9badd76a --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/server/variables/variable_table_head.twig @@ -0,0 +1,7 @@ + + + {% trans 'Action' %} + {% trans 'Variable' %} + {% trans 'Value' %} + + diff --git a/php/apps/phpmyadmin49/html/templates/start_and_number_of_rows_panel.twig b/php/apps/phpmyadmin49/html/templates/start_and_number_of_rows_panel.twig new file mode 100644 index 00000000..7d27f8f5 --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/start_and_number_of_rows_panel.twig @@ -0,0 +1,20 @@ +
    +
    + + 0 -%} + max="{{ unlim_num_rows - 1 }}" + {%- endif %} + value="{{ pos }}" /> + + + + + + +
    +
    diff --git a/php/apps/phpmyadmin49/html/templates/table/browse_foreigners/column_element.twig b/php/apps/phpmyadmin49/html/templates/table/browse_foreigners/column_element.twig new file mode 100644 index 00000000..f2ec2ba7 --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/table/browse_foreigners/column_element.twig @@ -0,0 +1,12 @@ + + {{ is_selected ? '' }} + + {% if nowrap %} + {{ keyname }} + {% else %} + {{ description }} + {% endif %} + + {{ is_selected ? '' }} + diff --git a/php/apps/phpmyadmin49/html/templates/table/browse_foreigners/show_all.twig b/php/apps/phpmyadmin49/html/templates/table/browse_foreigners/show_all.twig new file mode 100644 index 00000000..12a54419 --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/table/browse_foreigners/show_all.twig @@ -0,0 +1,5 @@ +{% if foreign_data.disp_row is iterable and + (show_all and foreign_data.the_total > max_rows) %} + +{% endif %} diff --git a/php/apps/phpmyadmin49/html/templates/table/chart/tbl_chart.twig b/php/apps/phpmyadmin49/html/templates/table/chart/tbl_chart.twig new file mode 100644 index 00000000..1458690b --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/table/chart/tbl_chart.twig @@ -0,0 +1,161 @@ + +{# Display Chart options #} +
    + + {{ Url_getHiddenInputs(url_params) }} +
    + + {% trans 'Display chart' %} + +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    + + +
    + + + + + + + + + + + + +

    + + + + +

    + + +
    + {% set xaxis = null %} +
    + + +
    + + + + +
    +
    + + +
    + + +
    +
    +
    +
    + + +
    + + + + +
    + {{ Util_getStartAndNumberOfRowsPanel(sql_query) }} +
    + +
    + +
    diff --git a/php/apps/phpmyadmin49/html/templates/table/gis_visualization/gis_visualization.twig b/php/apps/phpmyadmin49/html/templates/table/gis_visualization/gis_visualization.twig new file mode 100644 index 00000000..49a7ce17 --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/table/gis_visualization/gis_visualization.twig @@ -0,0 +1,80 @@ +
    +
    + {% trans 'Display GIS Visualization' %} +
    +
    + {{ Url_getHiddenInputs(url_params) }} + + + + + + + + + + + + {{ Util_getStartAndNumberOfRowsPanel(sql_query) }} +
    + +
    +
    + + {{ Util_getImage('b_saveimage', 'Save'|trans) }} + + +
    +
    +
    + +
    + +
    + {{ visualization|raw }} +
    +
    + + +
    +
    diff --git a/php/apps/phpmyadmin49/html/templates/table/index_form.twig b/php/apps/phpmyadmin49/html/templates/table/index_form.twig new file mode 100644 index 00000000..72f47741 --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/table/index_form.twig @@ -0,0 +1,219 @@ +
    + + {{ Url_getHiddenInputs(form_params) }} + +
    +
    +
    +
    + + + +
    + + +
    + +
    +
    + + + +
    + {{ index.generateIndexChoiceSelector(create_edit_table)|raw }} +
    + + {{ Util_getDivForSliderEffect('indexoptions', 'Advanced Options'|trans) }} + +
    +
    + + + +
    + + +
    + +
    + +
    + + + +
    + {{ index.generateIndexTypeSelector()|raw }} +
    + +
    +
    + + + +
    + + +
    + +
    +
    + + + +
    + + +
    +
    + + +
    + + + + + + + + + + {% set spatial_types = [ + 'geometry', + 'point', + 'linestring', + 'polygon', + 'multipoint', + 'multilinestring', + 'multipolygon', + 'geomtrycollection' + ] %} + + {% for column in index.getColumns() %} + + + + + + {% endfor %} + {% if add_fields > 0 %} + {% for i in range(1, add_fields) %} + + + + + + {% endfor %} + {% endif %} + +
    + {% trans 'Column' %} + + {% trans 'Size' %} +
    + + + + + +
    + + + + + +
    +
    + +
    +
    + +
    +
    +
    +
    + + +
    +
    diff --git a/php/apps/phpmyadmin49/html/templates/table/insert/continue_insertion_form.twig b/php/apps/phpmyadmin49/html/templates/table/insert/continue_insertion_form.twig new file mode 100644 index 00000000..c39a96f3 --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/table/insert/continue_insertion_form.twig @@ -0,0 +1,19 @@ +
    + {{ Url_getHiddenInputs(db, table) }} + + + + + {% if has_where_clause %} + {% for key_id, where_clause in where_clause_array %} + + {% endfor %} + {% endif %} + + {% set insert_rows %} + + {% endset %} + {{ 'Continue insertion with %s rows'|trans|format(insert_rows)|raw }} +
    diff --git a/php/apps/phpmyadmin49/html/templates/table/relation/common_form.twig b/php/apps/phpmyadmin49/html/templates/table/relation/common_form.twig new file mode 100644 index 00000000..8dd30698 --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/table/relation/common_form.twig @@ -0,0 +1,191 @@ +
    + {{ Url_getHiddenInputs(db, table) }} + {# InnoDB #} + {% if Util_isForeignKeySupported(tbl_storage_engine) %} +
    + {% trans 'Foreign key constraints' %} +
    + + + + + {% if tbl_storage_engine|upper == 'INNODB' %} + + {% else %} + + {% endif %} + + + + + + + + + + + {% set i = 0 %} + {% if existrel_foreign is not empty %} + {% for key, one_key in existrel_foreign %} + {# Foreign database dropdown #} + {% set foreign_db = one_key['ref_db_name'] is defined + and one_key['ref_db_name'] is not null + ? one_key['ref_db_name'] : db %} + {% set foreign_table = false %} + {% if foreign_db %} + {% set foreign_table = one_key['ref_table_name'] is defined + and one_key['ref_table_name'] is not null + ? one_key['ref_table_name'] : false %} + {% endif %} + {% set unique_columns = [] %} + {% if foreign_db and foreign_table %} + {% set table_obj = Table_get(foreign_table, foreign_db) %} + {% set unique_columns = table_obj.getUniqueColumns(false, false) %} + {% endif %} + {% include 'table/relation/foreign_key_row.twig' with { + 'i': i, + 'one_key': one_key, + 'column_array': column_array, + 'options_array': options_array, + 'tbl_storage_engine': tbl_storage_engine, + 'db': db, + 'table': table, + 'url_params': url_params, + 'databases': databases, + 'foreign_db': foreign_db, + 'foreign_table': foreign_table, + 'unique_columns': unique_columns + } only %} + {% set i = i + 1 %} + {% endfor %} + {% endif %} + {% include 'table/relation/foreign_key_row.twig' with { + 'i': i, + 'one_key': [], + 'column_array': column_array, + 'options_array': options_array, + 'tbl_storage_engine': tbl_storage_engine, + 'db': db, + 'table': table, + 'url_params': url_params, + 'databases': databases, + 'foreign_db': foreign_db, + 'foreign_table': foreign_table, + 'unique_columns': unique_columns + } only %} + {% set i = i + 1 %} + + +
    {% trans 'Actions' %}{% trans 'Constraint properties' %} + {% trans 'Column' %} + {{ Util_showHint('Creating a foreign key over a non-indexed column would automatically create an index on it. Alternatively, you can define an index below, before creating the foreign key.'|trans) }} + + {% trans 'Column' %} + {{ Util_showHint('Only columns with index will be displayed. You can define an index below.'|trans) }} + + {% trans 'Foreign key constraint' %} + ({{ tbl_storage_engine }}) +
    {% trans 'Database' %}{% trans 'Table' %}{% trans 'Column' %}
    + + {% trans '+ Add constraint' %} + +
    +
    +
    + {% endif %} + + {% if cfg_relation['relwork'] %} + {% if Util_isForeignKeySupported(tbl_storage_engine) %} + {{ Util_getDivForSliderEffect('ir_div', 'Internal relationships'|trans) }} + {% endif %} + +
    + + {% trans 'Internal relationships' %} + {{ Util_showDocu('config', 'cfg_Servers_relation') }} + + + + + + {% set saved_row_cnt = save_row|length - 1 %} + {% for i in 0..saved_row_cnt %} + {% set myfield = save_row[i]['Field'] %} + {# Use an md5 as array index to avoid having special characters + in the name attribute (see bug #1746964 ) #} + {% set myfield_md5 = md5(myfield) %} + + {% set foreign_table = false %} + {% set foreign_column = false %} + + {# Database dropdown #} + {% if existrel[myfield] is defined %} + {% set foreign_db = existrel[myfield]['foreign_db'] %} + {% else %} + {% set foreign_db = db %} + {% endif %} + + {# Table dropdown #} + {% set tables = [] %} + {% if foreign_db %} + {% if existrel[myfield] is defined %} + {% set foreign_table = existrel[myfield]['foreign_table'] %} + {% endif %} + {% set tables = dbi.getTables(foreign_db) %} + {% endif %} + + {# Column dropdown #} + {% set unique_columns = [] %} + {% if foreign_db and foreign_table %} + {% if existrel[myfield] is defined %} + {% set foreign_column = existrel[myfield]['foreign_field'] %} + {% endif %} + {% set table_obj = Table_get(foreign_table, foreign_db) %} + {% set unique_columns = table_obj.getUniqueColumns(false, false) %} + {% endif %} + + {% include 'table/relation/internal_relational_row.twig' with { + 'myfield': myfield, + 'myfield_md5': myfield_md5, + 'databases': databases, + 'tables': tables, + 'columns': unique_columns, + 'foreign_db': foreign_db, + 'foreign_table': foreign_table, + 'foreign_column': foreign_column + } only %} + {% endfor %} +
    {% trans 'Column' %}{% trans 'Internal relation' %} + {% if Util_isForeignKeySupported(tbl_storage_engine) %} + {{ Util_showHint('An internal relation is not necessary when a corresponding FOREIGN KEY relation exists.'|trans) }} + {% endif %} +
    +
    + {% if Util_isForeignKeySupported(tbl_storage_engine) %} +
    + {% endif %} + {% endif %} + + {% if cfg_relation['displaywork'] %} + {% set disp = Relation_getDisplayField(db, table) %} +
    + + +
    + {% endif %} + +
    + + +
    + diff --git a/php/apps/phpmyadmin49/html/templates/table/relation/dropdown_generate.twig b/php/apps/phpmyadmin49/html/templates/table/relation/dropdown_generate.twig new file mode 100644 index 00000000..ff179bc9 --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/table/relation/dropdown_generate.twig @@ -0,0 +1,9 @@ +{{ dropdown_question is not empty ? dropdown_question -}} + diff --git a/php/apps/phpmyadmin49/html/templates/table/relation/foreign_key_row.twig b/php/apps/phpmyadmin49/html/templates/table/relation/foreign_key_row.twig new file mode 100644 index 00000000..4e73bf11 --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/table/relation/foreign_key_row.twig @@ -0,0 +1,140 @@ + + {# Drop key anchor #} + + {% set js_msg = '' %} + {% set this_params = null %} + {% if one_key['constraint'] is defined %} + {% set drop_fk_query = 'ALTER TABLE ' ~ Util_backquote(db) ~ '.' ~ Util_backquote(table) + ~ ' DROP FOREIGN KEY ' + ~ Util_backquote(one_key['constraint']) ~ ';' + %} + {% set this_params = url_params %} + {% set this_params = { + 'goto': 'tbl_relation.php', + 'back': 'tbl_relation.php', + 'sql_query': drop_fk_query, + 'message_to_show': 'Foreign key constraint %s has been dropped'|trans|format( + one_key['constraint'] + ) + } %} + {% set js_msg = Sanitize_jsFormat( + 'ALTER TABLE ' ~ db ~ '.' ~ table + ~ ' DROP FOREIGN KEY ' + ~ one_key['constraint'] ~ ';' + ) %} + {% endif %} + {% if one_key['constraint'] is defined %} + + {% set drop_url = 'sql.php' ~ Url_getCommon(this_params) %} + {% set drop_str = Util_getIcon('b_drop', 'Drop'|trans) %} + {{ Util_linkOrButton(drop_url, drop_str, {'class': 'drop_foreign_key_anchor ajax'}) }} + {% endif %} + + + + + +
    + {# For ON DELETE and ON UPDATE, the default action + is RESTRICT as per MySQL doc; however, a SHOW CREATE TABLE + won't display the clause if it's set as RESTRICT. #} + {% set on_delete = one_key['on_delete'] is defined + ? one_key['on_delete'] : 'RESTRICT' %} + {% set on_update = one_key['on_update'] is defined + ? one_key['on_update'] : 'RESTRICT' %} + + {% include 'table/relation/dropdown_generate.twig' with { + 'dropdown_question': 'ON DELETE', + 'select_name': 'on_delete[' ~ i ~ ']', + 'choices': options_array, + 'selected_value': on_delete + } only %} + + + {% include 'table/relation/dropdown_generate.twig' with { + 'dropdown_question': 'ON UPDATE', + 'select_name': 'on_update[' ~ i ~ ']', + 'choices': options_array, + 'selected_value': on_update + } only %} + +
    + + + {% if one_key['index_list'] is defined %} + {% for key, column in one_key['index_list'] %} + + {% include 'table/relation/dropdown_generate.twig' with { + 'dropdown_question': '', + 'select_name': 'foreign_key_fields_name[' ~ i ~ '][]', + 'choices': column_array, + 'selected_value': column + } only %} + + {% endfor %} + {% else %} + + {% include 'table/relation/dropdown_generate.twig' with { + 'dropdown_question': '', + 'select_name': 'foreign_key_fields_name[' ~ i ~ '][]', + 'choices': column_array, + 'selected_value': '' + } only %} + + {% endif %} + + {% trans '+ Add column' %} + + + {% set tables = [] %} + {% if foreign_db %} + {% set tables = Relation_getTables(foreign_db, tbl_storage_engine) %} + {% endif %} + + + {% include 'table/relation/relational_dropdown.twig' with { + 'name': 'destination_foreign_db[' ~ i ~ ']', + 'title': 'Database'|trans, + 'values': databases, + 'foreign': foreign_db + } only %} + + + + + {% include 'table/relation/relational_dropdown.twig' with { + 'name': 'destination_foreign_table[' ~ i ~ ']', + 'title': 'Table'|trans, + 'values': tables, + 'foreign': foreign_table + } only %} + + + + {% if foreign_db and foreign_table %} + {% for foreign_column in one_key['ref_index_list'] %} + + {% include 'table/relation/relational_dropdown.twig' with { + 'name': 'destination_foreign_column[' ~ i ~ '][]', + 'title': 'Column'|trans, + 'values': unique_columns, + 'foreign': foreign_column + } only %} + + {% endfor %} + {% else %} + + {% include 'table/relation/relational_dropdown.twig' with { + 'name': 'destination_foreign_column[' ~ i ~ '][]', + 'title': 'Column'|trans, + 'values': [], + 'foreign': '' + } only %} + + {% endif %} + + diff --git a/php/apps/phpmyadmin49/html/templates/table/relation/internal_relational_row.twig b/php/apps/phpmyadmin49/html/templates/table/relation/internal_relational_row.twig new file mode 100644 index 00000000..f588f229 --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/table/relation/internal_relational_row.twig @@ -0,0 +1,30 @@ + + + {{ myfield }} + + + + + {% include 'table/relation/relational_dropdown.twig' with { + 'name': 'destination_db[' ~ myfield_md5 ~ ']', + 'title': 'Database'|trans, + 'values': databases, + 'foreign': foreign_db + } only %} + + {% include 'table/relation/relational_dropdown.twig' with { + 'name': 'destination_table[' ~ myfield_md5 ~ ']', + 'title': 'Table'|trans, + 'values': tables, + 'foreign': foreign_table + } only %} + + {% include 'table/relation/relational_dropdown.twig' with { + 'name': 'destination_column[' ~ myfield_md5 ~ ']', + 'title': 'Column'|trans, + 'values': columns, + 'foreign': foreign_column + } only %} + + diff --git a/php/apps/phpmyadmin49/html/templates/table/relation/relational_dropdown.twig b/php/apps/phpmyadmin49/html/templates/table/relation/relational_dropdown.twig new file mode 100644 index 00000000..9d5f3c97 --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/table/relation/relational_dropdown.twig @@ -0,0 +1,18 @@ + diff --git a/php/apps/phpmyadmin49/html/templates/table/search/column_comparison_operators.twig b/php/apps/phpmyadmin49/html/templates/table/search/column_comparison_operators.twig new file mode 100644 index 00000000..b72e530c --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/table/search/column_comparison_operators.twig @@ -0,0 +1,3 @@ + diff --git a/php/apps/phpmyadmin49/html/templates/table/search/fields_table.twig b/php/apps/phpmyadmin49/html/templates/table/search/fields_table.twig new file mode 100644 index 00000000..c2da41a6 --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/table/search/fields_table.twig @@ -0,0 +1,23 @@ + + {% include 'table/search/table_header.twig' with { + 'geom_column_flag': geom_column_flag + } only %} + + {% if search_type == 'zoom' %} + {% include 'table/search/rows_zoom.twig' with { + 'self': self, + 'column_names': column_names, + 'criteria_column_names': criteria_column_names, + 'criteria_column_types': criteria_column_types + } only %} + {% else %} + {% include 'table/search/rows_normal.twig' with { + 'self': self, + 'geom_column_flag': geom_column_flag, + 'column_names': column_names, + 'column_types': column_types, + 'column_collations': column_collations + } only %} + {% endif %} + +
    diff --git a/php/apps/phpmyadmin49/html/templates/table/search/form_tag.twig b/php/apps/phpmyadmin49/html/templates/table/search/form_tag.twig new file mode 100644 index 00000000..827c051f --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/table/search/form_tag.twig @@ -0,0 +1,4 @@ +
    + {{ Url_getHiddenInputs(db, table) }} + + diff --git a/php/apps/phpmyadmin49/html/templates/table/search/geom_func.twig b/php/apps/phpmyadmin49/html/templates/table/search/geom_func.twig new file mode 100644 index 00000000..3ac3000a --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/table/search/geom_func.twig @@ -0,0 +1,19 @@ +{# Displays 'Function' column if it is present #} + + {% set geom_types = Util_getGISDatatypes() %} + {% if column_types[column_index] in geom_types %} + + {% else %} +   + {% endif %} + diff --git a/php/apps/phpmyadmin49/html/templates/table/search/input_box.twig b/php/apps/phpmyadmin49/html/templates/table/search/input_box.twig new file mode 100644 index 00000000..d82c9a84 --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/table/search/input_box.twig @@ -0,0 +1,97 @@ +{# Get inputbox based on different column types (Foreign key, geometrical, enum) #} +{% if foreigners and Relation_searchColumnInForeigners(foreigners, column_name) %} + {% if foreign_data['disp_row'] is iterable %} + + {% elseif foreign_data['foreign_link'] == true %} + + + {{ titles['Browse']|replace({"'": "\\'"})|raw }} + + {% endif %} +{% elseif column_type in Util_getGISDatatypes() %} + + {% if in_fbs %} + {% set edit_url = 'gis_data_editor.php' ~ Url_getCommon() %} + {% set edit_str = Util_getIcon('b_edit', 'Edit/Insert'|trans) %} + + {{ Util_linkOrButton(edit_url, edit_str, [], '_blank') }} + + {% endif %} +{% elseif column_type starts with 'enum' + or (column_type starts with 'set' and in_zoom_search_edit) %} + {% set in_zoom_search_edit = false %} + {% set value = column_type|e|slice(5, -1)|replace({''': ''})|split(', ') %} + {% set cnt_value = value|length %} + {# + Enum in edit mode --> dropdown + Enum in search mode --> multiselect + Set in edit mode --> multiselect + Set in search mode --> input (skipped here, so the 'else' section would handle it) + #} + {% if (column_type starts with 'enum' and not in_zoom_search_edit) + or (column_type starts with 'set' and in_zoom_search_edit) %} + + {% endif %} + {# Add select options #} + + {% for i in 0..cnt_value - 1 %} + {% if criteria_values[column_index] is defined + and criteria_values[column_index] is iterable + and value[i] in criteria_values[column_index] %} + + {% else %} + + {% endif %} + {% endfor %} + +{% else %} + {% set the_class = 'textfield' %} + {% if column_type == 'date' %} + {% set the_class = the_class ~ ' datefield' %} + {% elseif column_type == 'datetime' or column_type starts with 'timestamp' %} + {% set the_class = the_class ~ ' datetimefield' %} + {% elseif column_type starts with 'bit' %} + {% set the_class = the_class ~ ' bit' %} + {% endif %} + +{% endif %} diff --git a/php/apps/phpmyadmin49/html/templates/table/search/options.twig b/php/apps/phpmyadmin49/html/templates/table/search/options.twig new file mode 100644 index 00000000..99ae9851 --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/table/search/options.twig @@ -0,0 +1,67 @@ +{{ Util_getDivForSliderEffect('searchoptions', 'Options'|trans) }} + +{# Displays columns select list for selecting distinct columns in the search #} +
    + + {% trans 'Select columns (at least one):' %} + + + + +
    + +{# Displays input box for custom 'Where' clause to be used in the search #} +
    + + {% trans 'Or' %} + {% trans 'Add search conditions (body of the "where" clause):' %} + + {{ Util_showMySQLDocu('Functions') }} + +
    + +{# Displays option of changing default number of rows displayed per page #} +
    + {% trans 'Number of rows per page' %} + +
    + +{# Displays option for ordering search results by a column value (Asc or Desc) #} +
    + {% trans 'Display order:' %} + + + {{ Util_getRadioFields( + 'order', + { + 'ASC': 'Ascending'|trans, + 'DESC': 'Descending'|trans + }, + 'ASC', + false, + true, + 'formelement' + ) }} + +
    +
    diff --git a/php/apps/phpmyadmin49/html/templates/table/search/options_zoom.twig b/php/apps/phpmyadmin49/html/templates/table/search/options_zoom.twig new file mode 100644 index 00000000..328eb4ae --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/table/search/options_zoom.twig @@ -0,0 +1,43 @@ + + {# Select options for data label #} + + + + + {# Inputbox for changing default maximum rows to plot #} + + + + +
    + + + +
    + + + +
    diff --git a/php/apps/phpmyadmin49/html/templates/table/search/replace_preview.twig b/php/apps/phpmyadmin49/html/templates/table/search/replace_preview.twig new file mode 100644 index 00000000..dab10f08 --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/table/search/replace_preview.twig @@ -0,0 +1,39 @@ + + {{ Url_getHiddenInputs(db, table) }} + + + + + + +
    + {% trans 'Find and replace - preview' %} + + + + + + + + + + {% if result is iterable %} + {% for row in result %} + + {# count #} + {# original #} + {# replaced #} + + {% endfor %} + {% endif %} + +
    {% trans 'Count' %}{% trans 'Original string' %}{% trans 'Replaced string' %}
    {{ row[2] }}{{ row[0] }}{{ row[1] }}
    +
    + +
    + +
    +
    diff --git a/php/apps/phpmyadmin49/html/templates/table/search/rows_normal.twig b/php/apps/phpmyadmin49/html/templates/table/search/rows_normal.twig new file mode 100644 index 00000000..37e3089c --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/table/search/rows_normal.twig @@ -0,0 +1,39 @@ +{% for column_index in 0..column_names|length - 1 %} + + {# If 'Function' column is present trying to change comment #} + {% if geom_column_flag %} + {% include 'table/search/geom_func.twig' with { + 'column_index': column_index, + 'column_types': column_types + } only %} + {% endif %} + {# Displays column's name, type, collation and value #} + + {{ column_names[column_index] }} + + {% set properties = self.getColumnProperties(column_index, column_index) %} + + {{ properties['type'] }} + + + {{ properties['collation'] }} + + + {{ properties['func']|raw }} + + {# here, the data-type attribute is needed for a date/time picker #} + + {{ properties['value']|raw }} + {# Displays hidden fields #} + + + + + +{% endfor %} diff --git a/php/apps/phpmyadmin49/html/templates/table/search/rows_zoom.twig b/php/apps/phpmyadmin49/html/templates/table/search/rows_zoom.twig new file mode 100644 index 00000000..79ef78f3 --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/table/search/rows_zoom.twig @@ -0,0 +1,74 @@ +{# Get already set search criteria (if any) #} +{% set type = [] %} +{% set collation = [] %} +{% set func = [] %} +{% set value = [] %} + +{% for i in 0..3 %} + {# After X-Axis and Y-Axis column rows, display additional criteria option #} + {% if i == 2 %} + + + {% trans 'Additional search criteria' %} + + + {% endif %} + + + + + {% if criteria_column_names is defined + and criteria_column_names[i] != 'pma_null' %} + {% set key = array_search(criteria_column_names[i], column_names) %} + {% set properties = self.getColumnProperties(i, key) %} + {% set type = type|merge({i: properties['type']}) %} + {% set collation = collation|merge({i: properties['collation']}) %} + {% set func = func|merge({i: properties['func']}) %} + {% set value = value|merge({i: properties['value']}) %} + {% endif %} + {# Column type #} + + {{ type[i] is defined ? type[i] }} + + {# Column Collation #} + + {{ collation[i] is defined ? collation[i] }} + + {# Select options for column operators #} + + {{ func[i] is defined ? func[i]|raw }} + + {# Inputbox for search criteria value #} + + + + {{ value[i] is defined ? value[i]|raw }} + {# Displays hidden fields #} + + + + +{% endfor %} diff --git a/php/apps/phpmyadmin49/html/templates/table/search/search_and_replace.twig b/php/apps/phpmyadmin49/html/templates/table/search/search_and_replace.twig new file mode 100644 index 00000000..530ea6df --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/table/search/search_and_replace.twig @@ -0,0 +1,25 @@ +{% trans 'Find:' %} + +{% trans 'Replace with:' %} + + +{% trans 'Column:' %} + + +{% include 'checkbox.twig' with { + 'html_field_id': 'useRegex', + 'html_field_name': 'useRegex', + 'label': 'Use regular expression'|trans, + 'checked': false, + 'onclick': false +} only %} diff --git a/php/apps/phpmyadmin49/html/templates/table/search/selection_form.twig b/php/apps/phpmyadmin49/html/templates/table/search/selection_form.twig new file mode 100644 index 00000000..64d99dc8 --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/table/search/selection_form.twig @@ -0,0 +1,102 @@ +{% if search_type == 'zoom' %} + {% include 'table/search/form_tag.twig' with { + 'script_name': 'tbl_zoom_select.php', + 'form_id': 'zoom_search_form', + 'db': db, + 'table': table, + 'goto': goto + } only %} + +{% elseif search_type == 'normal' %} + {% include 'table/search/form_tag.twig' with { + 'script_name': 'tbl_select.php', + 'form_id': 'tbl_search_form', + 'db': db, + 'table': table, + 'goto': goto + } only %} + +{% elseif search_type == 'replace' %} + {% include 'table/search/form_tag.twig' with { + 'script_name': 'tbl_find_replace.php', + 'form_id': 'find_replace_form', + 'db': db, + 'table': table, + 'goto': goto + } only %} +
    +
    + + {% trans 'Find and replace' %} + + {% include 'table/search/search_and_replace.twig' with { + 'column_names': column_names, + 'column_types': column_types, + 'sql_types': sql_types + } only %} +
    +
    +{% else %} + {% include 'table/search/form_tag.twig' with { + 'script_name': '', + 'form_id': '', + 'db': db, + 'table': table, + 'goto': goto + } only %} +{% endif %} + +{# Displays selection form's footer elements #} +
    + +
    + +
    diff --git a/php/apps/phpmyadmin49/html/templates/table/search/table_header.twig b/php/apps/phpmyadmin49/html/templates/table/search/table_header.twig new file mode 100644 index 00000000..4093f5bf --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/table/search/table_header.twig @@ -0,0 +1,12 @@ + + + {% if geom_column_flag %} + {% trans 'Function' %} + {% endif %} + {% trans 'Column' %} + {% trans 'Type' %} + {% trans 'Collation' %} + {% trans 'Operator' %} + {% trans 'Value' %} + + diff --git a/php/apps/phpmyadmin49/html/templates/table/search/zoom_result_form.twig b/php/apps/phpmyadmin49/html/templates/table/search/zoom_result_form.twig new file mode 100644 index 00000000..d76ae19a --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/table/search/zoom_result_form.twig @@ -0,0 +1,85 @@ +
    + {{ Url_getHiddenInputs(db, table) }} + + + +
    + {% trans 'Browse/Edit the points' %} + + {# JSON encode the data(query result) #} +
    + {% if zoom_submit and data is not empty %} +
    +
    + + {% trans 'How to use' %} + +
    +
    + {{ data_json }} +
    +
    + +
    + {% endif %} +
    + + {# Displays rows in point edit form #} +
    + + + + + + + + + + {% for column_index in 0..column_names|length - 1 %} + {% set field_popup = column_names[column_index] %} + {% set foreign_data = Relation_getForeignData( + foreigners, + field_popup, + false, + '', + '' + ) %} + + + {# Null checkbox if column can be null #} + + {# Column's Input box #} + + + {% endfor %} + +
    {% trans 'Column' %}{% trans 'Null' %}{% trans 'Value' %}
    {{ column_names[column_index] }} + {% if column_null_flags[column_index] == 'YES' %} + + {% endif %} + + {% include 'table/search/input_box.twig' with { + 'str': '', + 'column_type': column_types[column_index], + 'column_id': column_types[column_index] ? 'edit_fieldID_' : 'fieldID_', + 'in_zoom_search_edit': true, + 'foreigners': foreigners, + 'column_name': field_popup, + 'column_name_hash': md5(field_popup), + 'foreign_data': foreign_data, + 'table': table, + 'column_index': column_index, + 'foreign_max_limit': foreign_max_limit, + 'criteria_values': '', + 'db': db, + 'titles': titles, + 'in_fbs': false + } only %} +
    +
    + + diff --git a/php/apps/phpmyadmin49/html/templates/table/secondary_tabs.twig b/php/apps/phpmyadmin49/html/templates/table/secondary_tabs.twig new file mode 100644 index 00000000..a38b039e --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/table/secondary_tabs.twig @@ -0,0 +1,17 @@ +{% if cfg_relation['relwork'] or is_foreign_key_supported %} +
      + {{ Util_getHtmlTab({ + 'icon': 'b_props', + 'link': 'tbl_structure.php', + 'text': 'Table structure'|trans, + 'id': 'table_strucuture_id' + }, url_params) }} + {{ Util_getHtmlTab({ + 'icon': 'b_relations', + 'link': 'tbl_relation.php', + 'text': 'Relation view'|trans, + 'id': 'table_relation_id' + }, url_params) }} +
    +
    +{% endif %} diff --git a/php/apps/phpmyadmin49/html/templates/table/structure/action_row_in_structure_table.twig b/php/apps/phpmyadmin49/html/templates/table/structure/action_row_in_structure_table.twig new file mode 100644 index 00000000..45bf4a3f --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/table/structure/action_row_in_structure_table.twig @@ -0,0 +1,31 @@ +
  • +{% if type == 'text' + or type == 'blob' + or tbl_storage_engine == 'ARCHIVE' + or has_field %} + {{ titles['No' ~ action]|raw }} +{% else %} + + {{ titles[action]|raw }} + +{% endif %} +
  • diff --git a/php/apps/phpmyadmin49/html/templates/table/structure/actions_in_table_structure.twig b/php/apps/phpmyadmin49/html/templates/table/structure/actions_in_table_structure.twig new file mode 100644 index 00000000..6607f851 --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/table/structure/actions_in_table_structure.twig @@ -0,0 +1,140 @@ +
      + {% if hide_structure_actions %} + + {% endif %} +
    diff --git a/php/apps/phpmyadmin49/html/templates/table/structure/add_column.twig b/php/apps/phpmyadmin49/html/templates/table/structure/add_column.twig new file mode 100644 index 00000000..1d53cc96 --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/table/structure/add_column.twig @@ -0,0 +1,24 @@ +
    + {{ Url_getHiddenInputs(db, table) }} + {% if Util_showIcons('ActionLinksMode') %} + {{ Util_getImage('b_insrow', 'Add column'|trans) }}  + {% endif %} + {% set num_fields -%} + + {%- endset %} + {{ 'Add %s column(s)'|trans|format(num_fields)|raw }} +   + {# I tried displaying the drop-down inside the label but with Firefox the drop-down was blinking #} + + +
    diff --git a/php/apps/phpmyadmin49/html/templates/table/structure/check_all_table_column.twig b/php/apps/phpmyadmin49/html/templates/table/structure/check_all_table_column.twig new file mode 100644 index 00000000..57e6e112 --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/table/structure/check_all_table_column.twig @@ -0,0 +1,93 @@ + diff --git a/php/apps/phpmyadmin49/html/templates/table/structure/display_partitions.twig b/php/apps/phpmyadmin49/html/templates/table/structure/display_partitions.twig new file mode 100644 index 00000000..3bcbbe2f --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/table/structure/display_partitions.twig @@ -0,0 +1,145 @@ +
    +
    + + {% trans 'Partitions' %} + {{ Util_showMySQLDocu('partitioning') }} + + {% if partitions is empty %} + {{ Message_notice('No partitioning defined!'|trans) }} + {% else %} +

    + {% trans 'Partitioned by:' %} + {{ partition_method }}({{ partition_expression }}) +

    + {% if has_sub_partitions %} +

    + {% trans 'Sub partitioned by:' %} + {{ sub_partition_method }}({{ sub_partition_expression }}) +

    + {% endif %} + + + + + + {% if has_description %} + + {% endif %} + + + + + + + + + {% for partition in partitions %} + + {% if has_sub_partitions %} + + + {% else %} + + {% endif %} + + {% if has_description %} + + {% endif %} + + + + + {% for action, icon in action_icons %} + + {% endfor %} + + {% if has_sub_partitions %} + {% for sub_partition in partition.getSubPartitions() %} + + + + + {% if has_description %} + + {% endif %} + + + + + + + {% endfor %} + {% endif %} + + {% endfor %} + +
    #{% trans 'Partition' %}{% trans 'Expression' %}{% trans 'Rows' %}{% trans 'Data length' %}{% trans 'Index length' %}{% trans 'Comment' %} + {% trans 'Action' %} +
    {{ partition.getOrdinal() }}{{ partition.getOrdinal() }}{{ partition.getName() }} + + {{- partition.getExpression() -}} + {{- partition.getMethod() == 'LIST' ? ' IN (' : ' < ' -}} + {{- partition.getDescription() -}} + {{- partition.getMethod() == 'LIST' ? ')' -}} + + {{ partition.getRows() }} + {% set data_length = Util_formatByteDown( + partition.getDataLength(), + 3, + 1 + ) %} + {{ data_length[0] }} + {{ data_length[1] }} + + {% set index_length = Util_formatByteDown( + partition.getIndexLength(), + 3, + 1 + ) %} + {{ index_length[0] }} + {{ index_length[1] }} + {{ partition.getComment() }} + + {{ icon|raw }} + +
    {{ sub_partition.getOrdinal() }}{{ sub_partition.getName() }}{{ sub_partition.getRows() }} + {% set data_length = Util_formatByteDown( + sub_partition.getDataLength(), + 3, + 1 + ) %} + {{ data_length[0] }} + {{ data_length[1] }} + + {% set index_length = Util_formatByteDown( + sub_partition.getIndexLength(), + 3, + 1 + ) %} + {{ index_length[0] }} + {{ index_length[1] }} + {{ sub_partition.getComment() }}
    + {% endif %} +

    + +
    diff --git a/php/apps/phpmyadmin49/html/templates/table/structure/display_structure.twig b/php/apps/phpmyadmin49/html/templates/table/structure/display_structure.twig new file mode 100644 index 00000000..82a8205c --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/table/structure/display_structure.twig @@ -0,0 +1,228 @@ +
    + {{ Url_getHiddenInputs(db, table) }} + +
    + + {# Table header #} + {% include 'table/structure/table_structure_header.twig' with { + 'db_is_system_schema': db_is_system_schema, + 'tbl_is_view': tbl_is_view, + 'show_column_comments': show_column_comments + } only %} + + {# Table body #} + {% set rownum = 0 %} + {% set columns_list = [] %} + {% for row in fields %} + {% set rownum = rownum + 1 %} + {% set columns_list = columns_list|merge([row['Field']]) %} + {% set field_charset = row['Collation'] %} + + {% set extracted_columnspec = Util_extractColumnSpec(row['Type']) %} + {% set attribute = extracted_columnspec['attribute'] %} + {% if strpos(row['Extra'], 'on update CURRENT_TIMESTAMP') + is not same as(false) %} + {% set attribute = 'on update CURRENT_TIMESTAMP' %} + {% endif %} + + {% if row['Default'] is null %} + {% if row['Null'] == 'YES' %} + {% set row = row|merge({'Default': 'NULL'}) %} + {% endif %} + {% else %} + {% set row = row|merge({'Default': row['Default']|e}) %} + {% endif %} + + {% set field_name = row['Field']|e %} + {% set displayed_field_name = field_name %} + {# For column comments #} + {% set comments = '' %} + {# Underline commented fields and display a hover-title (CSS only) #} + + {% if comments_map[row['Field']] is defined %} + {% set displayed_field_name -%} + + {{- field_name|raw -}} + + {%- endset %} + {% set comments = comments_map[row['Field']] %} + {% endif %} + + {% if primary and primary.hasColumn(field_name) %} + {% set displayed_field_name = displayed_field_name ~ Util_getImage( + 'b_primary', 'Primary'|trans + ) %} + {% endif %} + {% if field_name in columns_with_index %} + {% set displayed_field_name = displayed_field_name ~ Util_getImage( + 'bd_primary', 'Index'|trans + ) %} + {% endif %} + + {% include 'table/structure/table_structure_row.twig' with { + 'row': row, + 'rownum': rownum, + 'displayed_field_name': preg_replace( + '/[\\x00-\\x1F]/', + '⁑', + displayed_field_name + ), + 'type_nowrap': Util_getClassForType(extracted_columnspec['type']), + 'extracted_columnspec': extracted_columnspec, + 'attribute': attribute, + 'tbl_is_view': tbl_is_view, + 'db_is_system_schema': db_is_system_schema, + 'url_query': url_query, + 'titles': titles, + 'table': table, + 'tbl_storage_engine': tbl_storage_engine, + 'field_charset': field_charset, + 'comments': comments, + 'show_column_comments': show_column_comments, + 'relation_commwork': relation_commwork, + 'relation_mimework': relation_mimework, + 'browse_mime': browse_mime + } only %} + {% if not tbl_is_view and not db_is_system_schema %} + {% include 'table/structure/actions_in_table_structure.twig' with { + 'row': row, + 'rownum': rownum, + 'extracted_columnspec': extracted_columnspec, + 'type': extracted_columnspec['print_type'] is not empty ? extracted_columnspec['print_type'], + 'tbl_storage_engine': tbl_storage_engine, + 'primary': primary, + 'field_name': field_name, + 'url_query': url_query, + 'titles': titles, + 'columns_with_unique_index': columns_with_unique_index, + 'is_in_central_columns': row['Field'] in central_list ? true : false, + 'central_columns_work': central_columns_work, + 'table': table, + 'hide_structure_actions': hide_structure_actions, + 'mysql_int_version': mysql_int_version + } only %} + {% endif %} + + {% endfor %} + +
    +
    + {% include 'table/structure/check_all_table_column.twig' with { + 'pma_theme_image': pma_theme_image, + 'text_dir': text_dir, + 'tbl_is_view': tbl_is_view, + 'db_is_system_schema': db_is_system_schema, + 'tbl_storage_engine': tbl_storage_engine, + 'central_columns_work': central_columns_work + } only %} +
    + +{% include 'table/structure/move_columns_dialog.twig' with { + 'db': db, + 'table': table +} only %} +{# Work on the table #} + +{% if not tbl_is_view and not db_is_system_schema %} + {% include 'table/structure/add_column.twig' with { + 'columns_list': columns_list, + 'db': db, + 'table': table + } only %} +{% endif %} + +{# Displays indexes #} +{% if not tbl_is_view and not db_is_system_schema + and 'ARCHIVE' != tbl_storage_engine %} + {{ Index_getHtmlForDisplayIndexes() }} +{% endif %} + +{# Display partition details #} +{% if have_partitioning %} + {# Detect partitioning #} + {% if partition_names is not empty and partition_names[0] is not null %} + {% set partitions = Partition_getPartitions(db, table) %} + {% set first_partition = partitions[0] %} + {% set range_or_list = first_partition.getMethod() == 'RANGE' + or first_partition.getMethod() == 'RANGE COLUMNS' + or first_partition.getMethod() == 'LIST' + or first_partition.getMethod() == 'LIST COLUMNS' %} + {% set sub_partitions = first_partition.getSubPartitions() %} + {% set has_sub_partitions = first_partition.hasSubPartitions() %} + {% if has_sub_partitions %} + {% set first_sub_partition = sub_partitions[0] %} + {% endif %} + + {% set action_icons = { + 'ANALYZE': Util_getIcon('b_search', 'Analyze'|trans), + 'CHECK': Util_getIcon('eye', 'Check'|trans), + 'OPTIMIZE': Util_getIcon('normalize', 'Optimize'|trans), + 'REBUILD': Util_getIcon('s_tbl', 'Rebuild'|trans), + 'REPAIR': Util_getIcon('b_tblops', 'Repair'|trans), + 'TRUNCATE': Util_getIcon('b_empty', 'Truncate'|trans), + } %} + {% if range_or_list %} + {% set action_icons = action_icons|merge({'DROP': Util_getIcon('b_drop', 'Drop'|trans)}) %} + {% endif %} + + {{ Util_getDivForSliderEffect('partitions', 'Partitions'|trans) }} + + {% set remove_sql = 'ALTER TABLE ' ~ Util_backquote(table) ~ ' REMOVE PARTITIONING' %} + {% set remove_url = 'sql.php' ~ url_query ~ '&sql_query=' ~ remove_sql|url_encode %} + + {% include 'table/structure/display_partitions.twig' with { + 'db': db, + 'table': table, + 'url_query': url_query, + 'partitions': partitions, + 'partition_method': first_partition.getMethod(), + 'partition_expression': first_partition.getExpression(), + 'has_description': first_partition.getDescription() is not empty, + 'has_sub_partitions': has_sub_partitions, + 'sub_partition_method': has_sub_partitions ? first_sub_partition.getMethod(), + 'sub_partition_expression': has_sub_partitions ? first_sub_partition.getExpression(), + 'action_icons': action_icons, + 'range_or_list': range_or_list, + 'remove_url': remove_url + } only %} + {% else %} + {% include 'table/structure/display_partitions.twig' with { + 'db': db, + 'table': table + } only %} + {% endif %} + {# For closing Slider effect div #} +
    +{% endif %} + +{# Displays Space usage and row statistics #} +{% if show_stats %} + {{ table_stats|raw }} +{% endif %} +
    diff --git a/php/apps/phpmyadmin49/html/templates/table/structure/display_table_stats.twig b/php/apps/phpmyadmin49/html/templates/table/structure/display_table_stats.twig new file mode 100644 index 00000000..14e6ab33 --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/table/structure/display_table_stats.twig @@ -0,0 +1,79 @@ +
    +
    + {% trans 'Information' %} + {% if showtable['TABLE_COMMENT'] %} +

    + {% trans 'Table comments:' %} + {{ showtable['TABLE_COMMENT'] }} +

    + {% endif %} + + + {% if not tbl_is_view and not db_is_system_schema %} + + + + + + + + + + {% if index_size is defined %} + + + + + + {% endif %} + + {% if free_size is defined %} + + + + + + + + + + + {% endif %} + + {% if tot_size is defined and mergetable == false %} + + + + + + {% endif %} + + {# Optimize link if overhead #} + {% if free_size is defined + and (tbl_storage_engine == 'MYISAM' + or tbl_storage_engine == 'ARIA' + or tbl_storage_engine == 'MARIA' + or tbl_storage_engine == 'BDB') %} + + + + {% endif %} + +
    {% trans 'Space usage' %}
    {% trans 'Data' %}{{ data_size }}{{ data_unit }}
    {% trans 'Index' %}{{ index_size }}{{ index_unit }}
    {% trans 'Overhead' %}{{ free_size }}{{ free_unit }}
    {% trans 'Effective' %}{{ effect_size }}{{ effect_unit }}
    {% trans 'Total' %}{{ tot_size }}{{ tot_unit }}
    + {% endif %} + + {% include 'table/structure/row_stats_table.twig' with { + 'showtable': showtable, + 'tbl_collation': tbl_collation, + 'is_innodb': is_innodb, + 'mergetable': mergetable, + 'avg_size': avg_size is defined ? avg_size : null, + 'avg_unit': avg_unit is defined ? avg_unit : null + } only %} +
    +
    diff --git a/php/apps/phpmyadmin49/html/templates/table/structure/move_columns_dialog.twig b/php/apps/phpmyadmin49/html/templates/table/structure/move_columns_dialog.twig new file mode 100644 index 00000000..7e7a8e26 --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/table/structure/move_columns_dialog.twig @@ -0,0 +1,9 @@ +
    +

    {% trans 'Move the columns by dragging them up and down.' %}

    +
    +
    + {{ Url_getHiddenInputs(db, table) }} +
      +
      +
      +
      diff --git a/php/apps/phpmyadmin49/html/templates/table/structure/optional_action_links.twig b/php/apps/phpmyadmin49/html/templates/table/structure/optional_action_links.twig new file mode 100644 index 00000000..262b612c --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/table/structure/optional_action_links.twig @@ -0,0 +1,34 @@ +{{ Util_getIcon('b_print', 'Print'|trans, true) }} +{% if not tbl_is_view and not db_is_system_schema %} + {# Only display propose table structure for MySQL < 8.0 #} + {% if mysql_int_version < 80000 or is_mariadb %} + + {{ Util_getIcon( + 'b_tblanalyse', + 'Propose table structure'|trans, + true + ) }} + + {{ Util_showMySQLDocu('procedure_analyse') }} + {% endif %} + {% if is_active %} + + {{ Util_getIcon('eye', 'Track table'|trans, true) }} + + {% endif %} + + {{ Util_getIcon('b_move', 'Move columns'|trans, true) }} + + + {{ Util_getIcon('normalize', 'Normalize'|trans, true) }} + +{% endif %} +{% if tbl_is_view and not db_is_system_schema %} + {% if is_active %} + + {{ Util_getIcon('eye', 'Track view'|trans, true) }} + + {% endif %} +{% endif %} diff --git a/php/apps/phpmyadmin49/html/templates/table/structure/partition_definition_form.twig b/php/apps/phpmyadmin49/html/templates/table/structure/partition_definition_form.twig new file mode 100644 index 00000000..186a6c71 --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/table/structure/partition_definition_form.twig @@ -0,0 +1,14 @@ +
      + {{ Url_getHiddenInputs(db, table) }} + + +
      + {% trans 'Edit partitioning' %} + {% include 'columns_definitions/partitions.twig' with { + 'partition_details': partition_details + } only %} +
      +
      + +
      +
      diff --git a/php/apps/phpmyadmin49/html/templates/table/structure/row_stats_table.twig b/php/apps/phpmyadmin49/html/templates/table/structure/row_stats_table.twig new file mode 100644 index 00000000..e176e904 --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/table/structure/row_stats_table.twig @@ -0,0 +1,95 @@ + + + + {% if showtable['Row_format'] is defined %} + + + {% if showtable['Row_format'] == 'Fixed' %} + + {% elseif showtable['Row_format'] == 'Dynamic' %} + + {% else %} + + {% endif %} + + {% endif %} + + {% if showtable['Create_options'] is not empty %} + + + {% if showtable['Create_options'] == 'partitioned' %} + + {% else %} + + {% endif %} + + {% endif %} + + {% if tbl_collation is not empty %} + + + + + {% endif %} + + {% if not is_innodb and showtable['Rows'] is defined %} + + + + + {% endif %} + + {% if not is_innodb + and showtable['Avg_row_length'] is defined + and showtable['Avg_row_length'] > 0 %} + + + {% set avg_row_length = Util_formatByteDown(showtable['Avg_row_length'], 6, 1) %} + + + {% endif %} + + {% if not is_innodb + and showtable['Data_length'] is defined + and showtable['Rows'] is defined + and showtable['Rows'] > 0 + and mergetable == false %} + + + + + {% endif %} + + {% if showtable['Auto_increment'] is defined %} + + + + + {% endif %} + + {% if showtable['Create_time'] is defined %} + + + + + {% endif %} + + {% if showtable['Update_time'] is defined %} + + + + + {% endif %} + + {% if showtable['Check_time'] is defined %} + + + + + {% endif %} + +
      {% trans 'Row statistics' %}
      {% trans 'Format' %}{% trans 'static' %}{% trans 'dynamic' %}{{ showtable['Row_format'] }}
      {% trans 'Options' %}{% trans 'partitioned' %}{{ showtable['Create_options'] }}
      {% trans 'Collation' %} + + {{ tbl_collation }} + +
      {% trans 'Rows' %}{{ Util_formatNumber(showtable['Rows'], 0) }}
      {% trans 'Row length' %}{{ avg_row_length[0] }} {{ avg_row_length[1] }}
      {% trans 'Row size' %}{{ avg_size }} {{ avg_unit }}
      {% trans 'Next autoindex' %}{{ Util_formatNumber(showtable['Auto_increment'], 0) }}
      {% trans 'Creation' %}{{ Util_localisedDate(showtable['Create_time']|date('U')) }}
      {% trans 'Last update' %}{{ Util_localisedDate(showtable['Update_time']|date('U')) }}
      {% trans 'Last check' %}{{ Util_localisedDate(showtable['Check_time']|date('U')) }}
      diff --git a/php/apps/phpmyadmin49/html/templates/table/structure/table_structure_header.twig b/php/apps/phpmyadmin49/html/templates/table/structure/table_structure_header.twig new file mode 100644 index 00000000..5af12980 --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/table/structure/table_structure_header.twig @@ -0,0 +1,21 @@ + + + + # + {% trans 'Name' %} + {% trans 'Type' %} + {% trans 'Collation' %} + {% trans 'Attributes' %} + {% trans 'Null' %} + {% trans 'Default' %} + {% if show_column_comments -%} + {% trans 'Comments' %} + {%- endif %} + {% trans 'Extra' %} + {# @see tbl_structure.js, function moreOptsMenuResize() #} + {% if not db_is_system_schema and not tbl_is_view %} + {% trans 'Action' %} + {% endif %} + + diff --git a/php/apps/phpmyadmin49/html/templates/table/structure/table_structure_row.twig b/php/apps/phpmyadmin49/html/templates/table/structure/table_structure_row.twig new file mode 100644 index 00000000..e6942d7c --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/table/structure/table_structure_row.twig @@ -0,0 +1,59 @@ + + + +{{ rownum }} + + + + + + {{ extracted_columnspec['displayed_type']|raw }} + {% if relation_commwork and relation_mimework and browse_mime + and mime_map[row['Field']]['mimetype'] is defined %} +
      MIME: {{ mime_map[row['Field']]['mimetype']|replace({'_': '/'})|lower }} + {% endif %} +
      + + +{% if field_charset is not empty %} + {{ field_charset }} +{% endif %} + +{{ attribute }} +{{ row['Null'] == 'YES' ? 'Yes'|trans : 'No'|trans }} + + {% if row['Default'] is not null %} + {% if extracted_columnspec['type'] == 'bit' %} + {{ Util_convertBitDefaultValue(row['Default']) }} + {% else %} + {{ row['Default']|raw }} + {% endif %} + {% else %} + {% trans %}None{% context %}None for default{% endtrans %} + {% endif %} + +{% if show_column_comments %} + + {{ comments }} + +{% endif %} +{{ row['Extra']|upper }} +{% if not tbl_is_view and not db_is_system_schema %} + + + {{ titles['Change']|raw }} + + + + + {{ titles['Drop']|raw }} + + +{% endif %} diff --git a/php/apps/phpmyadmin49/html/templates/table/tracking/activate_deactivate.twig b/php/apps/phpmyadmin49/html/templates/table/tracking/activate_deactivate.twig new file mode 100644 index 00000000..5817e420 --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/table/tracking/activate_deactivate.twig @@ -0,0 +1,27 @@ +
      +
      + {{ Url_getHiddenInputs(db, table) }} +
      + + {% if action == 'activate' %} + {% set legend = 'Activate tracking for %s'|trans %} + {% set value = 'activate_now' %} + {% set button = 'Activate now'|trans %} + {% elseif action == 'deactivate' %} + {% set legend = 'Deactivate tracking for %s'|trans %} + {% set value = 'deactivate_now' %} + {% set button = 'Deactivate now'|trans %} + {% else %} + {% set legend = '' %} + {% set value = '' %} + {% set button = '' %} + {% endif %} + + {{ legend|format(db ~ '.' ~ table) }} + + + + +
      +
      +
      diff --git a/php/apps/phpmyadmin49/html/templates/table/tracking/create_version.twig b/php/apps/phpmyadmin49/html/templates/table/tracking/create_version.twig new file mode 100644 index 00000000..4a655769 --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/table/tracking/create_version.twig @@ -0,0 +1,79 @@ +
      +
      + {{ Url_getHiddenInputs(db) }} + {% for selected_table in selected %} + + {% endfor %} + +
      + + {% if selected|length == 1 %} + {{ 'Create version %1$s of %2$s'|trans|format( + last_version + 1, + db ~ '.' ~ selected[0] + ) }} + {% else %} + {{ 'Create version %1$s'|trans|format(last_version + 1) }} + {% endif %} + + +

      {% trans 'Track these data definition statements:' %}

      + + {% if type == 'both' or type == 'table' %} + + ALTER TABLE
      + + RENAME TABLE
      + + CREATE TABLE
      + + DROP TABLE
      + {% endif %} + {% if type == 'both' %} +
      + {% endif %} + {% if type == 'both' or type == 'view' %} + + ALTER VIEW
      + + CREATE VIEW
      + + DROP VIEW
      + {% endif %} +
      + + + CREATE INDEX
      + + DROP INDEX
      + +

      {% trans 'Track these data manipulation statements:' %}

      + + INSERT
      + + UPDATE
      + + DELETE
      + + TRUNCATE
      +
      + +
      + + +
      +
      +
      diff --git a/php/apps/phpmyadmin49/html/templates/table/tracking/report_table.twig b/php/apps/phpmyadmin49/html/templates/table/tracking/report_table.twig new file mode 100644 index 00000000..fe005266 --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/table/tracking/report_table.twig @@ -0,0 +1,27 @@ + + + + + + + + + + + + {% for entry in entries %} + + + + + + + + {% endfor %} + +
      {% trans %}#{% context %}Number{% endtrans %}{% trans 'Date' %}{% trans 'Username' %}{{ header_message }}{% trans 'Action' %}
      {{ entry.line_number }}{{ entry.date }}{{ entry.username }}{{ entry.formated_statement|raw }} + + {{ drop_image_or_text|raw }} + +
      diff --git a/php/apps/phpmyadmin49/html/templates/table/tracking/selectable_tables.twig b/php/apps/phpmyadmin49/html/templates/table/tracking/selectable_tables.twig new file mode 100644 index 00000000..eca71bf1 --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/table/tracking/selectable_tables.twig @@ -0,0 +1,17 @@ +
      + {{ Url_getHiddenInputs(db, table) }} + + +
      diff --git a/php/apps/phpmyadmin49/html/templates/table/tracking/structure_snapshot_columns.twig b/php/apps/phpmyadmin49/html/templates/table/tracking/structure_snapshot_columns.twig new file mode 100644 index 00000000..c98dfbe3 --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/table/tracking/structure_snapshot_columns.twig @@ -0,0 +1,56 @@ +

      {% trans 'Structure' %}

      + + + + + + + + + + + + + + + {% set index = 1 %} + {% for field in columns %} + + + {% set index = index + 1 %} + + + + + + + + + {% endfor %} + +
      {% trans %}#{% context %}Number{% endtrans %}{% trans 'Column' %}{% trans 'Type' %}{% trans 'Collation' %}{% trans 'Null' %}{% trans 'Default' %}{% trans 'Extra' %}{% trans 'Comment' %}
      {{ index }} + + {{ field['Field'] }} + {% if field['Key'] == 'PRI' %} + {{ Util_getImage('b_primary', 'Primary'|trans) }} + {% elseif field['Key'] is not empty %} + {{ Util_getImage('bd_primary', 'Index'|trans) }} + {% endif %} + + {{ field['Type'] }}{{ field['Collation'] }}{{ field['Null'] == 'YES' ? 'Yes'|trans : 'No'|trans }} + {% if field['Default'] is defined %} + {% set extracted_columnspec = Util_extractColumnSpec(field['Type']) %} + {% if extracted_columnspec['type'] == 'bit' %} + {# here, $field['Default'] contains something like b'010' #} + {{ Util_convertBitDefaultValue(field['Default']) }} + {% else %} + {{ field['Default'] }} + {% endif %} + {% else %} + {% if field['Null'] == 'YES' %} + NULL + {% else %} + {% trans %}None{% context %}None for default{% endtrans %} + {% endif %} + {% endif %} + {{ field['Extra'] }}{{ field['Comment'] }}
      diff --git a/php/apps/phpmyadmin49/html/templates/table/tracking/structure_snapshot_indexes.twig b/php/apps/phpmyadmin49/html/templates/table/tracking/structure_snapshot_indexes.twig new file mode 100644 index 00000000..a919252f --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/table/tracking/structure_snapshot_indexes.twig @@ -0,0 +1,33 @@ +

      {% trans 'Indexes' %}

      + + + + + + + + + + + + + + + + {% for index in indexes %} + + + + + + + + + + + + {% endfor %} + +
      {% trans 'Keyname' %}{% trans 'Type' %}{% trans 'Unique' %}{% trans 'Packed' %}{% trans 'Column' %}{% trans 'Cardinality' %}{% trans 'Collation' %}{% trans 'Null' %}{% trans 'Comment' %}
      + {{ index['Key_name'] }} + {{ index['Index_type'] }}{{ index['Non_unique'] == 0 ? 'Yes'|trans : 'No'|trans }}{{ index['Packed'] != '' ? 'Yes'|trans : 'No'|trans }}{{ index['Column_name'] }}{{ index['Cardinality'] }}{{ index['Collation'] }}{{ index['Null'] }}{{ index['Comment'] }}
      diff --git a/php/apps/phpmyadmin49/html/templates/test/add_data.twig b/php/apps/phpmyadmin49/html/templates/test/add_data.twig new file mode 100644 index 00000000..d562612b --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/test/add_data.twig @@ -0,0 +1,2 @@ +{{ variable1 }} +{{ variable2 }} diff --git a/php/apps/phpmyadmin49/html/templates/test/echo.twig b/php/apps/phpmyadmin49/html/templates/test/echo.twig new file mode 100644 index 00000000..9c5fc602 --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/test/echo.twig @@ -0,0 +1 @@ +{{ variable -}} diff --git a/php/apps/phpmyadmin49/html/templates/test/gettext/gettext.twig b/php/apps/phpmyadmin49/html/templates/test/gettext/gettext.twig new file mode 100644 index 00000000..4fcf61b8 --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/test/gettext/gettext.twig @@ -0,0 +1 @@ +{% trans "Text" %} diff --git a/php/apps/phpmyadmin49/html/templates/test/gettext/notes.twig b/php/apps/phpmyadmin49/html/templates/test/gettext/notes.twig new file mode 100644 index 00000000..e5a38e4a --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/test/gettext/notes.twig @@ -0,0 +1,5 @@ +{% trans %} +Text +{% notes %} +Notes +{% endtrans %} diff --git a/php/apps/phpmyadmin49/html/templates/test/gettext/pgettext.twig b/php/apps/phpmyadmin49/html/templates/test/gettext/pgettext.twig new file mode 100644 index 00000000..53650277 --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/test/gettext/pgettext.twig @@ -0,0 +1,5 @@ +{% trans %} +Text +{% context %} +Text context +{% endtrans %} diff --git a/php/apps/phpmyadmin49/html/templates/test/gettext/plural.twig b/php/apps/phpmyadmin49/html/templates/test/gettext/plural.twig new file mode 100644 index 00000000..5bc44e9e --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/test/gettext/plural.twig @@ -0,0 +1,5 @@ +{% trans %} +One table +{% plural table_count %} +{{ count }} tables +{% endtrans %} diff --git a/php/apps/phpmyadmin49/html/templates/test/gettext/plural_notes.twig b/php/apps/phpmyadmin49/html/templates/test/gettext/plural_notes.twig new file mode 100644 index 00000000..0c6da182 --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/test/gettext/plural_notes.twig @@ -0,0 +1,7 @@ +{% trans %} +One table +{% plural table_count %} +{{ count }} tables +{% notes %} +Number of tables +{% endtrans %} diff --git a/php/apps/phpmyadmin49/html/templates/test/static.twig b/php/apps/phpmyadmin49/html/templates/test/static.twig new file mode 100644 index 00000000..703390d1 --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/test/static.twig @@ -0,0 +1 @@ +static content \ No newline at end of file diff --git a/php/apps/phpmyadmin49/html/templates/theme_preview.twig b/php/apps/phpmyadmin49/html/templates/theme_preview.twig new file mode 100644 index 00000000..9ec60eac --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/theme_preview.twig @@ -0,0 +1,16 @@ + diff --git a/php/apps/phpmyadmin49/html/templates/toggle_button.twig b/php/apps/phpmyadmin49/html/templates/toggle_button.twig new file mode 100644 index 00000000..2a7cc1ef --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/toggle_button.twig @@ -0,0 +1,24 @@ +
      +
      +
      + + + + + + + + + +
      + {{ link_on|raw }} +
      {{ toggle_on }}
      +
       
      + {{ link_off|raw }} +
      {{ toggle_off }}
      +
      + {{ callback }} + {{ text_dir }} +
      +
      +
      diff --git a/php/apps/phpmyadmin49/html/templates/view_create.twig b/php/apps/phpmyadmin49/html/templates/view_create.twig new file mode 100644 index 00000000..a89b7951 --- /dev/null +++ b/php/apps/phpmyadmin49/html/templates/view_create.twig @@ -0,0 +1,120 @@ + +
      +
      + {{ Url_getHiddenInputs(url_params) }} +
      + + {% if ajax_dialog %} + {% trans 'Details' %} + {% else %} + {% if view['operation'] == 'create' %} + {% trans 'Create view' %} + {% else %} + {% trans 'Edit view' %} + {% endif %} + {% endif %} + + + {% if view['operation'] == 'create' %} + + + + + {% endif %} + + + + + + + + + + + + + + + + + {% if view['operation'] == 'create' %} + + + + + {% else %} + + + + {% endif %} + + + + + + + + + + + + + + + + +
      + +
      + +
      {% trans 'Definer' %}
      SQL SECURITY + +
      {% trans 'VIEW name' %} + +
      + +
      {% trans 'Column names' %} + +
      AS +
      + + +
      WITH CHECK OPTION + +
      +
      + + + + + {% if ajax_dialog == false %} + + + {% endif %} + +
      +
      diff --git a/php/apps/phpmyadmin49/html/themes.php b/php/apps/phpmyadmin49/html/themes.php new file mode 100644 index 00000000..760015b1 --- /dev/null +++ b/php/apps/phpmyadmin49/html/themes.php @@ -0,0 +1,35 @@ +getFooter()->setMinimal(); +$header = $response->getHeader(); +$header->setBodyId('bodythemes'); +$header->setTitle('phpMyAdmin - ' . __('Theme')); +$header->disableMenuAndConsole(); + +$hash = '#pma_' . preg_replace('/([0-9]*)\.([0-9]*)\..*/', '\1_\2', PMA_VERSION); +$url = Core::linkURL('https://www.phpmyadmin.net/themes/') . $hash; +$output = '

      phpMyAdmin - ' . __('Theme') . '

      '; +$output .= '

      '; +$output .= ''; +$output .= __('Get more themes!'); +$output .= ''; +$output .= '

      '; +$output .= ThemeManager::getInstance()->getPrintPreviews(); + +$response->addHTML($output); diff --git a/php/apps/phpmyadmin49/html/themes/dot.gif b/php/apps/phpmyadmin49/html/themes/dot.gif new file mode 100644 index 00000000..35d42e80 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/dot.gif differ diff --git a/php/apps/phpmyadmin49/html/themes/original/css/common.css.php b/php/apps/phpmyadmin49/html/themes/original/css/common.css.php new file mode 100644 index 00000000..88f36e50 --- /dev/null +++ b/php/apps/phpmyadmin49/html/themes/original/css/common.css.php @@ -0,0 +1,3440 @@ + +/******************************************************************************/ + +/* general tags */ +html { + font-size: getFontSize(); ?> +} + +input, +select, +textarea { + font-size: 1em; +} + + +body { + + font-family: ; + + padding: 0; + margin: 0; + margin-: 240px; + color: ; + background: ; +} + +body#loginform { + margin: 0; +} + +#page_content { + margin: 0 .5em; +} + +.desktop50 { + width: 50%; +} + +.all100 { + width: 100%; +} + +.all85{ + width: 85%; +} + +.auth_config_tbl{ + margin: 0 auto; +} + + + textarea, tt, pre, code { + font-family: ; + } + +h1 { + font-size: 140%; + font-weight: bold; +} + +h2 { + font-size: 120%; + font-weight: bold; +} + +h3 { + font-weight: bold; +} + +a, a:link, +a:visited, +a:active, +button.mult_submit, +.checkall_box+label { + text-decoration: none; + color: #0000FF; + cursor: pointer; +} + +a:hover, +button.mult_submit:hover, +button.mult_submit:focus, +.checkall_box+label:hover { + text-decoration: underline; + color: #FF0000; +} + +dfn { + font-style: normal; +} + +dfn:hover { + font-style: normal; + cursor: help; +} + +th { + font-weight: bold; + color: ; + background: ; +} + +a img { + border: 0; +} + +hr { + color: ; + background-color: ; + border: 0; + height: 1px; +} + +form { + padding: 0; + margin: 0; + display: inline; +} + +textarea { + overflow: visible; +} + +fieldset { + margin-top: 1em; + border: solid 1px; + padding: 0.5em; + background: ; +} + +fieldset fieldset { + margin: 0.8em; +} + +fieldset legend { + font-weight: bold; + color: #444444; + background-color: ; +} + +.some-margin { + margin: .5em; + margin-top: 1em; +} + +/* buttons in some browsers (eg. Konqueror) are block elements, + this breaks design */ +button { + display: inline; +} + +table caption, +table th, +table td { + padding: 0.1em 0.5em 0.1em 0.5em; + margin: 0.1em; + vertical-align: top; +} + +img, +input, +select, +button { + vertical-align: middle; +} + +/******************************************************************************/ +/* classes */ +.clearfloat { + clear: both; +} + +.floatleft { + float: ; + margin-: 1em; +} + +.floatright { + float: ; +} + +table.nospacing { + border-spacing: 0; +} + +table.nopadding tr th, table.nopadding tr td { + padding: 0; +} + +th.left, td.left { + text-align: left; +} + +th.center, td.center { + text-align: center; +} + +th.right, td.right { + text-align: right; + padding-right: 1em; +} + +tr.vtop, th.vtop, td.vtop { + vertical-align: top; +} + +tr.vmiddle, th.vmiddle, td.vmiddle { + vertical-align: middle; +} + +tr.vbottom, th.vbottom, td.vbottom { + vertical-align: bottom; +} + +.paddingtop { + padding-top: 1em; +} + +div.tools { + border: 1px solid #000000; + padding: 0.2em; +} + +div.tools, +fieldset.tblFooters { + margin-top: 0; + margin-bottom: 0.5em; + /* avoid a thick line since this should be used under another fieldset */ + border-top: 0; + text-align: ; + float: none; + clear: both; +} + +div.null_div { + height: 20px; + text-align: center; + font-style:normal; + min-width:50px; +} + +fieldset .formelement { + float: ; + margin-: 0.5em; + /* IE */ + white-space: nowrap; +} + +/* revert for Gecko */ +fieldset div[class=formelement] { + white-space: normal; +} + +button.mult_submit { + border: none; + background-color: transparent; +} + +/* odd items 1,3,5,7,... */ +table tr:nth-child(odd), +#table_index tbody:nth-of-type(odd) tr, +#table_index tbody:nth-of-type(odd) th { + background: ; +} + +/* even items 2,4,6,8,... */ +table tr:nth-child(even), +#table_index tbody:nth-of-type(even) tr, +#table_index tbody:nth-of-type(even) th { + background: ; +} + +table tr th, +table tr { + text-align: ; +} + +/* marked table rows */ +td.marked:not(.nomarker), +table tr.marked:not(.nomarker) td, +table tr.marked:not(.nomarker) th, +table tr.marked:not(.nomarker) { + background: ; + color: ; +} + +/* hovered items */ +table tr:not(.nopointer):hover, +.hover:not(.nopointer) { + background: ; + color: ; +} + +/* hovered table rows */ +#table_index tbody:hover tr, +#table_index tbody:hover th, +table tr.hover:not(.nopointer) th { + background: ; + color: ; +} + +/** + * marks table rows/cells if the db field is in a where condition + */ +td.condition, +th.condition { + border: 1px solid ; +} + +/** + * cells with the value NULL + */ +td.null { + font-style: italic; + color: #7d7d7d; +} + +table .valueHeader { + text-align: ; + white-space: normal; +} +table .value { + text-align: ; + white-space: normal; +} +/* IE doesnt handles 'pre' right */ +table [class=value] { + white-space: normal; +} + + + + .value { + font-family: ; + } + +.attention { + color: red; + font-weight: bold; +} +.allfine { + color: green; +} + + +img.lightbulb { + cursor: pointer; +} + +.pdflayout { + overflow: hidden; + clip: inherit; + background-color: #FFFFFF; + display: none; + border: 1px solid #000000; + position: relative; +} + +.pdflayout_table { + background: #D3DCE3; + color: #000000; + overflow: hidden; + clip: inherit; + z-index: 2; + display: inline; + visibility: inherit; + cursor: move; + position: absolute; + font-size: 80%; + border: 1px dashed #000000; +} + +/* Doc links in SQL */ +.cm-sql-doc { + text-decoration: none; + border-bottom: 1px dotted #000; + color: inherit !important; +} + +/* leave some space between icons and text */ +.icon { + image-rendering: pixelated; + vertical-align: middle; + margin-: 0.3em; + background-repeat: no-repeat; + background-position: center; +} + +/* no extra space in table cells */ +td .icon { + margin: 0; +} + +.selectallarrow { + margin-: 0.3em; + margin-: 0.6em; +} + +/* message boxes: error, confirmation */ +#pma_errors, #pma_demo, #pma_footer { + position: relative; + padding: 0 0.5em; +} + +.success h1, +.notice h1, +div.error h1 { + border-bottom: 2px solid; + font-weight: bold; + text-align: ; + margin: 0 0 0.2em 0; +} + +div.success, +div.notice, +div.error { + margin: 0.3em 0 0 0; + border: 2px solid; + background-repeat: no-repeat; + clear: both; + + background-position: 10px 50%; + padding: 0.1em 0.1em 0.1em 42px; + + background-position: 99% 50%; + padding: 0.1em 40px 0.1em 0.1em; + +} +div.success img.icon, +div.notice img.icon, +div.error img.icon { + display: block; + float: ; + margin-: -22px; +} + +.success { + color: #000000; + background-color: #f0fff0; +} +h1.success, +div.success { + border-color: #00FF00; +} +.success h1 { + border-color: #00FF00; +} + +.notice { + color: #000000; + background-color: #FFFFDD; +} +h1.notice, +div.notice { + border-color: #FFD700; +} +.notice h1 { + border-color: #FFD700; +} + +.error { + background-color: #FFFFCC; + color: #ff0000; +} + +h1.error, +div.error { + border-color: #ff0000; +} +div.error h1 { + border-color: #ff0000; +} + +.confirmation { + background-color: #FFFFCC; +} +fieldset.confirmation { + border: 0.1em solid #FF0000; +} +fieldset.confirmation legend { + border-left: 0.1em solid #FF0000; + border-right: 0.1em solid #FF0000; + font-weight: bold; + background-image: url(getImgPath('s_really.png');?>); + background-repeat: no-repeat; + + background-position: 5px 50%; + padding: 0.2em 0.2em 0.2em 25px; + + background-position: 97% 50%; + padding: 0.2em 25px 0.2em 0.2em; + +} +/* end messageboxes */ + +.new_central_col{ + width: 100%; +} + +.tblcomment { + font-size: 70%; + font-weight: normal; + color: #000099; +} + +.tblHeaders { + font-weight: bold; + color: ; + background: ; +} + +div.tools, +.tblFooters { + font-weight: normal; + color: ; + background: ; +} + +.tblHeaders a:link, +.tblHeaders a:active, +.tblHeaders a:visited, +div.tools a:link, +div.tools a:visited, +div.tools a:active, +.tblFooters a:link, +.tblFooters a:active, +.tblFooters a:visited { + color: #0000FF; +} + +.tblHeaders a:hover, +div.tools a:hover, +.tblFooters a:hover { + color: #FF0000; +} + +/* forbidden, no privilegs */ +.noPrivileges { + color: #FF0000; + font-weight: bold; +} + +/* disabled text */ +.disabled, +.disabled a:link, +.disabled a:active, +.disabled a:visited { + color: #666666; +} + +.disabled a:hover { + color: #666666; + text-decoration: none; +} + +tr.disabled td, +td.disabled { + background-color: #cccccc; +} + +.nowrap { + white-space: nowrap; +} + +/** + * zoom search + */ +div#resizer { + width: 600px; + height: 400px; +} +div#querychart { + float: left; + width: 600px; +} + +/** + * login form + */ +body#loginform h1, +body#loginform a.logo { + display: block; + text-align: center; +} + +body#loginform { + margin-top: 1em; + text-align: center; +} + +body#loginform div.container { + text-align: ; + width: 30em; + margin: 0 auto; +} + +form.login label { + float: ; + width: 10em; + font-weight: bolder; +} + +.commented_column { + border-bottom: 1px dashed black; +} + +.column_attribute { + font-size: 70%; +} + +.cfg_dbg_demo{ + margin: 0.5em 1em 0.5em 1em; +} + +.central_columns_navigation{ + padding:1.5% 0em !important; +} + +.central_columns_add_column{ + display:inline-block; + margin-left:1%; + max-width:50% +} + +.message_errors_found{ + margin-top: 20px; +} + +.repl_gui_skip_err_cnt{ + width: 30px; +} + +.font_weight_bold{ + font-weight: bold; +} + +.color_gray{ + color: gray; +} + +.pma_sliding_message{ + display: inline-block; +} + +/******************************************************************************/ +/* specific elements */ + +/* topmenu */ +ul#topmenu, ul#topmenu2, ul.tabs { + font-weight: bold; + list-style-type: none; + margin: 0; + padding: 0; +} + +ul#topmenu2 { + margin: 0.25em 0.5em 0; + height: 2em; + clear: both; +} + +ul#topmenu li, ul#topmenu2 li { + float: ; + margin: 0; + padding: 0; + vertical-align: middle; +} + +#topmenu img, #topmenu2 img { + vertical-align: middle; + margin-: 0.1em; +} + +/* default tab styles */ +ul#topmenu a, ul#topmenu span { + display: block; + margin: 2px 2px 0; + padding: 2px 2px 0; + white-space: nowrap; +} + +ul#topmenu2 a { + display: block; + margin: 0.1em; + padding: 0.2em; + white-space: nowrap; +} + +span.caution { + color: #FF0000; +} +fieldset.caution a { + color: #FF0000; +} +fieldset.caution a:hover { + color: #ffffff; + background-color: #FF0000; +} + +#topmenu { + margin-top: 0.5em; + padding: 0.1em 0.3em 0.1em 0.3em; +} + +ul#topmenu ul { + -moz-box-shadow: 2px 2px 3px #666; + -webkit-box-shadow: 2px 2px 3px #666; + box-shadow: 2px 2px 3px #666; +} + +ul#topmenu > li { + border-bottom: 1pt solid black; +} + +/* default tab styles */ +ul#topmenu a, ul#topmenu span { + background-color: ; + border: 0 solid ; + border-width: 1pt 1pt 0 1pt; + -moz-border-radius: 0.4em 0.4em 0 0; + border-radius: 0.4em 0.4em 0 0; +} + +ul#topmenu ul a { + border-width: 1pt 0 0 0; + -moz-border-radius: 0; + border-radius: 0; +} + +ul#topmenu ul li:first-child a { + border-width: 0; +} + +/* enabled hover/active tabs */ +ul#topmenu > li > a:hover, +ul#topmenu > li > .tabactive { + margin: 0; + padding: 2px 4px; + text-decoration: none; +} + +ul#topmenu ul a:hover, +ul#topmenu ul .tabactive { + text-decoration: none; +} + +ul#topmenu a.tab:hover, +ul#topmenu .tabactive { + background-color: ; +} + +ul#topmenu2 a.tab:hover, +ul#topmenu2 a.tabactive { + background-color: ; + -moz-border-radius: 0.3em; + border-radius: 0.3em; + text-decoration: none; +} + +/* to be able to cancel the bottom border, use
    • */ +ul#topmenu > li.active { + border-bottom: 1pt solid ; +} +/* end topmenu */ + +/* zoom search */ +div#dataDisplay input, div#dataDisplay select { + margin: 0; + margin-: 0.5em; +} +div#dataDisplay th { + line-height: 2em; +} +table#tableFieldsId { + width: 100%; +} + +/* Calendar */ +table.calendar { + width: 100%; +} +table.calendar td { + text-align: center; +} +table.calendar td a { + display: block; +} + +table.calendar td a:hover { + background-color: #CCFFCC; +} + +table.calendar th { + background-color: #D3DCE3; +} + +table.calendar td.selected { + background-color: #FFCC99; +} + +img.calendar { + border: none; +} +form.clock { + text-align: center; +} +/* end Calendar */ + + +/* table stats */ +div#tablestatistics table { + float: ; + margin-top: 0.5em; + margin-bottom: 0.5em; + margin-: 0.5em; + min-width: 16em; +} +/* end table stats */ + + +/* server privileges */ +#tableuserrights td, +#tablespecificuserrights td, +#tabledatabases td { + vertical-align: middle; +} +/* end server privileges */ + + + +/* Heading */ +#topmenucontainer { + background: white; + padding-: 1em; + width: 100%; +} + +#serverinfo { + background: white; + font-weight: bold; + padding-bottom: 0.5em; + padding-: 2.2em; + width: 10000px; + overflow: hidden; +} + +#serverinfo .item { + white-space: nowrap; +} + +#page_nav_icons { + position: fixed; + top: 0; + : 0; + z-index: 99; + padding: .1em 0; +} + +#goto_pagetop, #lock_page_icon, #page_settings_icon { + padding: .3em; + background: white; +} + +#page_settings_icon { + cursor: pointer; + display: none; +} + +#page_settings_modal { + display: none; +} + +#pma_navigation_settings { + display: none; +} + +#span_table_comment { + font-weight: bold; + font-style: italic; + white-space: nowrap; + margin-left: 10px; + color: #D6D6D6; + text-shadow: none; +} + +#serverinfo img { + margin: 0 0.1em 0 0.2em; +} + + +#textSQLDUMP { + width: 95%; + height: 95%; + font-family: "Courier New", Courier, mono; + font-size: 110%; +} + +#TooltipContainer { + position: absolute; + z-index: 99; + width: 20em; + height: auto; + overflow: visible; + visibility: hidden; + background-color: #ffffcc; + color: #006600; + border: 0.1em solid #000000; + padding: 0.5em; +} + +/* user privileges */ +#fieldset_add_user_login div.item { + border-bottom: 1px solid silver; + padding-bottom: 0.3em; + margin-bottom: 0.3em; +} + +#fieldset_add_user_login label { + float: ; + display: block; + width: 15em; + max-width: 100%; + text-align: ; + padding-: 0.5em; +} + +#fieldset_add_user_login span.options #select_pred_username, +#fieldset_add_user_login span.options #select_pred_hostname, +#fieldset_add_user_login span.options #select_pred_password { + width: 100%; + max-width: 100%; +} + +#fieldset_add_user_login span.options { + float: ; + display: block; + width: 12em; + max-width: 100%; + padding-: 0.5em; +} + +#fieldset_add_user_login input { + width: 12em; + clear: ; + max-width: 100%; +} + +#fieldset_add_user_login span.options input { + width: auto; +} + +#fieldset_user_priv div.item { + float: ; + width: 9em; + max-width: 100%; +} + +#fieldset_user_priv div.item div.item { + float: none; +} + +#fieldset_user_priv div.item label { + white-space: nowrap; +} + +#fieldset_user_priv div.item select { + width: 100%; +} + +#fieldset_user_global_rights fieldset { + float: ; +} + +#fieldset_user_group_rights fieldset { + float: ; +} + +#fieldset_user_global_rights>legend input { + margin-: 2em; +} +/* end user privileges */ + + +/* serverstatus */ + +.linkElem:hover { + text-decoration: underline; + color: #235a81; + cursor: pointer; +} + +h3#serverstatusqueries span { + font-size:60%; + display:inline; +} + +.buttonlinks { + float: ; + white-space: nowrap; +} + +/* Also used for the variables page */ +fieldset#tableFilter { + padding: 0.1em 1em; +} + +div#serverStatusTabs { + margin-top:1em; +} + +caption a.top { + float: ; +} + +div#serverstatusquerieschart { + float: ; + width: 500px; + height: 350px; + margin-: 50px; +} + +div#serverstatus table#serverstatusqueriesdetails { + float: ; +} + +table#serverstatustraffic { + float: ; +} +table#serverstatusconnections { + float: ; + margin-: 30px; +} + +table#serverstatusvariables { + width: 100%; + margin-bottom: 1em; +} +table#serverstatusvariables .name { + width: 18em; + white-space:nowrap; +} +table#serverstatusvariables .value { + width: 6em; +} +table#serverstatusconnections { + float: ; + margin-: 30px; +} + +div#serverstatus table tbody td.descr a, +div#serverstatus table .tblFooters a { + white-space: nowrap; +} + +div.liveChart { + clear:both; + min-width:500px; + height:400px; + padding-bottom:80px; +} + +#addChartDialog input[type="text"] { + margin: 0; + padding:3px; +} + +div#chartVariableSettings { + border:1px solid #ddd; + background-color:#E6E6E6; + margin-left:10px; +} + +table#chartGrid td { + padding: 3px; + margin: 0; +} + +table#chartGrid div.monitorChart { + background: #EBEBEB; + overflow: hidden; + border: none; +} + +div.tabLinks { + margin-left: 0.3em; + float: ; + padding: 5px 0; +} + +div.tabLinks a, div.tabLinks label { + margin-right: 7px; +} + +div.tabLinks .icon { + margin: -0.2em 0.3em 0 0; +} + +.popupContent { + display: none; + position: absolute; + border: 1px solid #CCC; + margin:0; + padding:3px; + -moz-box-shadow: 1px 1px 6px #ddd; + -webkit-box-shadow: 2px 2px 3px #666; + box-shadow: 2px 2px 3px #666; + background-color:white; + z-index: 2; +} + +div#logTable { + padding-top: 10px; + clear: both; +} + +div#logTable table { + width:100%; +} + +.smallIndent { + padding-left: 7px; +} + +/* end serverstatus */ + +#sectionlinks { + margin-bottom: 15px; + padding: 10px; + border: 1px solid #CCC; +} +#sectionlinks a { + margin-: 7px; +} + +/* server variables */ +#serverVariables { + width: 100%; +} +#serverVariables .var-row > tr { + line-height: 2em; +} + +#serverVariables .var-header { + font-weight: bold; + color: ; + background: ; + text-align: ; +} +#serverVariables .var-row { + padding: 0.5em; + min-height: 18px; +} +#serverVariables .var-name { + font-weight: bold; +} +#serverVariables .var-name.session { + font-weight: normal; + font-style: italic; +} +#serverVariables .var-value { + text-align: ; + float: ; +} + +/* server variables editor */ +#serverVariables .editLink { + padding-: 1em; + font-family: sans-serif; +} +#serverVariables .serverVariableEditor { + width: 100%; + overflow: hidden; +} +#serverVariables .serverVariableEditor input { + width: 100%; + margin: 0 0.5em; + box-sizing: border-box; + -ms-box-sizing: border-box; + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + height: 2.2em; +} +#serverVariables .serverVariableEditor div { + display: block; + overflow: hidden; + padding-: 1em; +} +#serverVariables .serverVariableEditor a { + margin: 0 0.5em; + line-height: 2em; +} +/* end server variables */ + +/* profiling */ + +div#profilingchart { + width: 850px; + height: 370px; + float: left; +} + +#profilingchart .jqplot-highlighter-tooltip{ + top: auto !important; + left: 11px; + bottom:24px; +} +/* end profiling */ + +/* table charting */ +.chartOption { + float: ; + margin-: 40px; +} +/* end table charting */ + +/* querybox */ + +div#sqlquerycontainer { + float: ; + width: 69%; + /* height: 15em; */ +} + +div#tablefieldscontainer { + float: ; + width: 29%; + /* height: 15em; */ +} + +div#tablefieldscontainer select { + width: 100%; + /* height: 12em; */ +} + +textarea#sqlquery { + width: 100%; + /* height: 100%; */ +} +textarea#sql_query_edit { + height: 7em; + width: 95%; + display: block; +} +div#queryboxcontainer div#bookmarkoptions { + margin-top: .5em; +} +/* end querybox */ + +/* main page */ +#maincontainer { + background-image: url(getImgPath('logo_right.png');?>); + background-position: bottom; + background-repeat: no-repeat; +} + +#mysqlmaininformation, +#pmamaininformation { + float: ; + width: 49%; +} + +#maincontainer ul { + list-style-type: disc; + vertical-align: middle; +} + +#maincontainer li { + margin: 0.2em 0; +} + +#full_name_layer { + position: absolute; + padding: 2px; + margin-top: -3px; + z-index: 801; + + border: solid 1px #888; + background: #fff; + +} +/* end main page */ + +/* iconic view for ul items */ + +li.no_bullets { + list-style-type:none !important; + margin-: -25px !important; //align with other list items which have bullets +} + +/* end iconic view for ul items */ + +#body_browse_foreigners { + background: ; + margin: .5em .5em 0 .5em; +} + +#bodythemes { + width: 500px; + margin: auto; + text-align: center; +} + +#bodythemes img { + border: .1em solid #000; +} + +#bodythemes a:hover img { + border: .1em solid red; +} + +#fieldset_select_fields { + float: ; +} + +#selflink { + clear: both; + display: block; + margin-top: 1em; + margin-bottom: 1em; + width: 98%; + margin-left: 1%; + border-top: .1em solid silver; + text-align: ; +} + +#table_innodb_bufferpool_usage, +#table_innodb_bufferpool_activity { + float: ; +} + +#div_mysql_charset_collations table { + float: ; +} + +#div_mysql_charset_collations table th, +#div_mysql_charset_collations table td { + padding: 0.4em; +} + +#div_mysql_charset_collations table th#collationHeader { + width: 35%; +} + +#qbe_div_table_list { + float: ; +} + +#qbe_div_sql_query { + float: ; +} + +label.desc { + width: 30em; + float: ; +} + +label.desc sup { + position: absolute; +} + +code.php { + display: block; + padding-left: 0.3em; + margin-top: 0; + margin-bottom: 0; + max-height: 10em; + overflow: auto; + direction: ltr; +} + +code.sql, +div.sqlvalidate { + display: block; + padding: 0.3em; + margin-top: 0; + margin-bottom: 0; + max-height: 10em; + overflow: auto; + direction: ltr; +} + +.result_query div.sqlOuter, +div.sqlvalidate { + border: solid 1px; + border-top: 0; + border-bottom: 0; + background: ; +} + +.result_query div.sqlOuter { + text-align: ; +} + +#PMA_slidingMessage code.sql { + border: solid 1px; + border-top: 0; + background: ; +} + +#main_pane_left { + width: 60%; + min-width: 260px; + float: ; + padding-top: 1em; +} + +#main_pane_right { + overflow: hidden; + min-width: 160px; + padding-top: 1em; + padding-: 1em; +} + +.group { + border-: 0.3em solid ; + margin-bottom: 1em; +} + +.group h2 { + background: ; + padding: 0.1em 0.3em; + margin-top: 0; +} + +.group-cnt { + padding: 0 0 0 0.5em; + display: inline-block; + width: 98%; +} + +textarea#partitiondefinition { + height:3em; +} + + +/* for elements that should be revealed only via js */ +.hide { + display: none; +} + +#list_server { + list-style-type: none; + padding: 0; +} + +/** + * Progress bar styles + */ +div.upload_progress +{ + width: 400px; + margin: 3em auto; + text-align: center; +} + +div.upload_progress_bar_outer +{ + border: 1px solid #000; + width: 202px; + position: relative; + margin: 0 auto 1em; + color: ; +} + +div.upload_progress_bar_inner +{ + background-color: ; + width: 0; + height: 12px; + margin: 1px; + overflow: hidden; + color: ; + position: relative; +} + +div.upload_progress_bar_outer div.percentage +{ + position: absolute; + top: 0; + left: 0; + width: 202px; +} + +div.upload_progress_bar_inner div.percentage +{ + top: -1px; + left: -1px; +} + +div#statustext { + margin-top: .5em; +} + +table#serverconnection_src_remote, +table#serverconnection_trg_remote, +table#serverconnection_src_local, +table#serverconnection_trg_local { + float: left; +} +/** + * Validation error message styles + */ +input[type=text].invalid_value, +input[type=password].invalid_value, +input[type=number].invalid_value, +input[type=date].invalid_value, +select.invalid_value, +.invalid_value { + background: #FFCCCC; +} + +/** + * Ajax notification styling + */ + .ajax_notification { + top: 0; /** The notification needs to be shown on the top of the page */ + position: fixed; + margin-top: 0; + margin-right: auto; + margin-bottom: 0; + margin-left: auto; + padding: 3px 5px; /** Keep a little space on the sides of the text */ + width: 350px; + background-color: #FFD700; + z-index: 1100; /** If this is not kept at a high z-index, the jQueryUI modal dialogs (z-index:1000) might hide this */ + text-align: center; + display: block; + left: 0; + right: 0; + background-image: url(getImgPath('ajax_clock_small.gif');?>); + background-repeat: no-repeat; + background-position: 2%; + } + + #loading_parent { + /** Need this parent to properly center the notification division */ + position: relative; + width: 100%; + } +/** + * Export and Import styles + */ + +.export_table_list_container { + display: inline-block; + max-height: 20em; + overflow-y: scroll; +} + +.export_table_select th { + text-align: center; + vertical-align: middle; +} + +.export_table_select .all { + font-weight: bold; + border-bottom: 1px solid black; +} + +.export_structure, .export_data { + text-align: center; +} + +.export_table_name { + vertical-align: middle; +} + +.exportoptions h2 { + word-wrap: break-word; +} + +.exportoptions h3, .importoptions h3 { + border-bottom: 1px #999999 solid; + font-size: 110%; +} + +.exportoptions ul, .importoptions ul, .format_specific_options ul { + list-style-type: none; + margin-bottom: 15px; +} + +.exportoptions li, .importoptions li { + margin: 7px; +} +.exportoptions label, .importoptions label, .exportoptions p, .importoptions p { + margin: 5px; + float: none; +} + +#csv_options label.desc, #ldi_options label.desc, #latex_options label.desc, #output label.desc{ + float: left; + width: 15em; +} + +.exportoptions, .importoptions { + margin: 20px 30px 30px 10px +} + +.format_specific_options h3 { + margin: 10px 0 0 10px; + border: 0; +} + +.format_specific_options { + border: 1px solid #999; + margin: 7px 0; + padding: 3px; +} + +p.desc { + margin: 5px; +} + +/** + * Export styles only + */ +select#db_select, +select#table_select { + width: 400px; +} + +.export_sub_options { + margin: 20px 0 0 30px; +} + +.export_sub_options h4 { + border-bottom: 1px #999 solid; +} + +.export_sub_options li.subgroup { + display: inline-block; + margin-top: 0; +} + +.export_sub_options li { + margin-bottom: 0; +} + +#output_quick_export { + display: none; +} +/** + * Import styles only + */ + +.importoptions #import_notification { + margin: 10px 0; + font-style: italic; +} + +input#input_import_file { + margin: 5px; +} + +.formelementrow { + margin: 5px 0 5px 0; +} + +#filterText { + vertical-align: baseline; +} + +#popup_background { + display: none; + position: fixed; + _position: absolute; /* hack for IE6 */ + width: 100%; + height: 100%; + top: 0; + left: 0; + background: #000; + z-index: 1000; + overflow: hidden; +} + +/** + * Create table styles + */ +#create_table_form table.table-name td { + vertical-align: middle; +} + +/** + * Table structure styles + */ +#fieldsForm ul.table-structure-actions { + margin: 0; + padding: 0; + list-style: none; +} +#fieldsForm ul.table-structure-actions li { + float: ; + margin-: 0.5em; /* same as padding of "table td" */ +} +#fieldsForm ul.table-structure-actions .submenu li { + padding: 0.3em; + margin: 0.1em; +} +#structure-action-links a { + margin-: 1em; +} +#addColumns input[type="radio"] { + margin: 0; + margin-: 1em; +} +#addColumns input[type="submit"] { + margin-: 1em; +} + +/** + * Indexes + */ +#index_frm .index_info input[type="text"], +#index_frm .index_info select { + width: 100%; + box-sizing: border-box; + -ms-box-sizing: border-box; + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; +} + +#index_frm .slider { + width: 10em; + margin: .6em; + float: ; +} + +#index_frm .add_fields { + float: ; +} + +#index_frm .add_fields input { + margin-: 1em; +} + +#index_frm input { + margin: 0; +} + +#index_frm td { + vertical-align: middle; +} + +table#index_columns { + width: 100%; +} + +table#index_columns select { + width: 85%; + float: right; +} + +#move_columns_dialog div { + padding: 1em; +} + +#move_columns_dialog ul { + list-style: none; + margin: 0; + padding: 0; +} + +#move_columns_dialog li { + background: ; + border: 1px solid #aaa; + color: ; + font-weight: bold; + margin: .4em; + padding: .2em; + -webkit-border-radius: 2px; + -moz-border-radius: 2px; + border-radius: 2px; +} + +/* config forms */ +.config-form ul.tabs { + margin: 1.1em 0.2em 0; + padding: 0 0 0.3em 0; + list-style: none; + font-weight: bold; +} + +.config-form ul.tabs li { + float: ; +} + +.config-form ul.tabs li a { + display: block; + margin: 0.1em 0.2em 0; + padding: 0.1em 0.4em; + white-space: nowrap; + text-decoration: none; + border: 1px solid ; + border-bottom: none; +} + +.config-form ul.tabs li a:hover, +.config-form ul.tabs li a:active, +.config-form ul.tabs li a.active { + margin: 0; + padding: 0.1em 0.6em 0.2em; +} + +.config-form ul.tabs li.active a { + background-color: ; +} + +.config-form fieldset { + margin-top: 0; + padding: 0; + clear: both; + /*border-color: ;*/ +} + +.config-form legend { + display: none; +} + +.config-form fieldset p { + margin: 0; + padding: 0.5em; + background: ; +} + +.config-form fieldset .errors { /* form error list */ + margin: 0 -2px 1em -2px; + padding: .5em 1.5em; + background: #FBEAD9; + border: 0 #C83838 solid; + border-width: 1px 0; + list-style: none; + font-family: sans-serif; + font-size: small; +} + +.config-form fieldset .inline_errors { /* field error list */ + margin: .3em .3em .3em 0; + padding: 0; + list-style: none; + color: #9A0000; + font-size: small; +} + +.config-form fieldset th { + padding: .3em .3em .3em .5em; + text-align: left; + vertical-align: top; + width: 40%; + background: transparent; +} + +.config-form fieldset .doc, +.config-form fieldset .disabled-notice { + margin-left: 1em; +} + +.config-form fieldset .disabled-notice { + font-size: 80%; + text-transform: uppercase; + color: #E00; + cursor: help; +} + +.config-form fieldset td { + padding-top: .3em; + padding-bottom: .3em; + vertical-align: top; +} + +.config-form fieldset th small { + display: block; + font-weight: normal; + font-family: sans-serif; + font-size: x-small; + color: #444; +} + +.config-form fieldset th, +.config-form fieldset td { + border-top: 1px solid; +} + +fieldset .group-header th { + background: ; +} + +fieldset .group-header + tr th { + padding-top: .6em; +} + +fieldset .group-field-1 th, +fieldset .group-header-2 th { + padding-left: 1.5em; +} + +fieldset .group-field-2 th, +fieldset .group-header-3 th { + padding-left: 3em; +} + +fieldset .group-field-3 th { + padding-left: 4.5em; +} + +fieldset .disabled-field th, +fieldset .disabled-field th small, +fieldset .disabled-field td { + color: #666; + background-color: #ddd; +} + +.config-form .lastrow { + border-top: 1px #000 solid; +} + +.config-form .lastrow { + background: ; + padding: .5em; + text-align: center; +} + +.config-form .lastrow input { + font-weight: bold; +} + +/* form elements */ + +.config-form span.checkbox { + padding: 2px; + display: inline-block; +} + +.config-form .custom { /* customized field */ + background: #FFC; +} + +.config-form span.checkbox.custom { + padding: 1px; + border: 1px #EDEC90 solid; + background: #FFC; +} + +.config-form .field-error { + border-color: #A11 !important; +} + +.config-form input[type="text"], +.config-form input[type="password"], +.config-form input[type="number"], +.config-form select, +.config-form textarea { + border: 1px #A7A6AA solid; + height: auto; +} + +.config-form input[type="text"]:focus, +.config-form input[type="password"]:focus, +.config-form input[type="number"]:focus, +.config-form select:focus, +.config-form textarea:focus { + border: 1px #6676FF solid; + background: #F7FBFF; +} + +.config-form .field-comment-mark { + font-family: serif; + color: #007; + cursor: help; + padding: 0 0.2em; + font-weight: bold; + font-style: italic; +} + +.config-form .field-comment-warning { + color: #A00; +} + +/* error list */ +.config-form dd { + margin-left: .5em; +} + +.config-form dd:before { + content: "\25B8 "; +} + +.click-hide-message { + cursor: pointer; +} + +.prefsmanage_opts { + margin-: 2em; +} + +#prefs_autoload { + margin-bottom: .5em; + margin-left: .5em; +} + +#placeholder .button { + position: absolute; + cursor: pointer; +} + +#placeholder div.button { + font-size: smaller; + color: #999; + background-color: #eee; + padding: 2px; +} + +.wrapper { + float: ; + margin-bottom: 0.5em; +} +.toggleButton { + position: relative; + cursor: pointer; + font-size: .8em; + text-align: center; + line-height: 1.4em; + height: 1.55em; + overflow: hidden; + border-right: .1em solid #888; + border-left: .1em solid #888; +} +.toggleButton table, +.toggleButton td, +.toggleButton img { + padding: 0; + position: relative; +} +.toggleButton .container { + position: absolute; +} +.toggleButton .container td, +.toggleButton .container tr { + background-image: none; + background: none !important; +} +.toggleButton .toggleOn { + color: #fff; + padding: 0 1em; +} +.toggleButton .toggleOff { + padding: 0 1em; +} + +.doubleFieldset fieldset { + width: 48%; + float: ; + padding: 0; +} +.doubleFieldset fieldset.left { + margin-: 1%; +} +.doubleFieldset fieldset.right { + margin-: 1%; +} +.doubleFieldset legend { + margin-: 0.5em; +} +.doubleFieldset div.wrap { + padding: 0.5em; +} + +#table_name_col_no_outer { + margin-top: 30px; +} + +#table_name_col_no { + position: fixed; + top: 44px; + width: 100%; + background: ; +} + +#table_columns input[type="text"], +#table_columns input[type="password"], +#table_columns input[type="number"], +#table_columns input[type="date"], +#table_columns select { + width: 10em; + box-sizing: border-box; + -ms-box-sizing: border-box; + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; +} + +input#auto_increment_opt { + width: min-content; +} + +#placeholder { + position: relative; + border: 1px solid #aaa; + float: ; + overflow: hidden; + width: 450px; + height: 300px; +} + +#openlayersmap{ + width: 450px; + height: 300px; +} + +.placeholderDrag { + cursor: move; +} + +#placeholder .button { + position: absolute; +} + +#left_arrow { + left: 8px; + top: 26px; +} + +#right_arrow { + left: 26px; + top: 26px; +} + +#up_arrow { + left: 17px; + top: 8px; +} + +#down_arrow { + left: 17px; + top: 44px; +} + +#zoom_in { + left: 17px; + top: 67px; +} + +#zoom_world { + left: 17px; + top: 85px; +} + +#zoom_out { + left: 17px; + top: 103px; +} + +.colborder { + cursor: col-resize; + height: 100%; + margin-left: -5px; + position: absolute; + width: 5px; +} + +.colborder_active { + border-right: 2px solid #a44; +} + +.pma_table td { + position: static; +} + +.pma_table th.draggable span, +.sticky_columns th.draggable span, +.pma_table tbody td span { + display: block; + overflow: hidden; +} + +.pma_table tbody td span code span { + display: inline; +} + +.modal-copy input { + display: block; + width: 100%; + margin-top: 1.5em; + padding: .3em 0; +} + +.cRsz { + position: absolute; +} + +.draggable { + cursor: move; +} + +.cCpy { + background: #000; + color: #FFF; + font-weight: bold; + margin: 0.1em; + padding: 0.3em; + position: absolute; +} + +.cPointer { + background: url(getImgPath('col_pointer.png');?>); + height: 20px; + margin-left: -5px; /* must be minus half of its width */ + margin-top: -10px; + position: absolute; + width: 10px; +} + +.tooltip { + background: #333 !important; + opacity: .8 !important; + border: 1px solid #000 !important; + -moz-border-radius: .3em !important; + -webkit-border-radius: .3em !important; + border-radius: .3em !important; + text-shadow: -1px -1px #000 !important; + font-size: .8em !important; + font-weight: bold !important; + padding: 1px 3px !important; +} + +.tooltip * { + background: none !important; + color: #FFF !important; +} + + +.data_full_width { + width: 100%; +} + +.cDrop { + left: 0; + position: absolute; + top: 0; +} + +.coldrop { + background: url(getImgPath('col_drop.png');?>); + cursor: pointer; + height: 16px; + margin-left: 0.5em; + margin-top: 0.3em; + position: absolute; + width: 16px; +} + +.coldrop:hover, +.coldrop-hover { + background-color: #999; +} + +.cList { + background: #EEE; + border: solid 1px #999; + position: absolute; +} + +.cList .lDiv div { + padding: .2em .5em .2em .2em; +} + +.cList .lDiv div:hover { + background: #DDD; + cursor: pointer; +} + +.cList .lDiv div input { + cursor: pointer; +} + +.showAllColBtn { + border-bottom: solid 1px #999; + border-top: solid 1px #999; + cursor: pointer; + font-size: .9em; + font-weight: bold; + padding: .35em 1em; + text-align: center; +} + +.showAllColBtn:hover { + background: #DDD; +} + +.navigation { + background: #E5E5E5; + border: 1px solid black; + margin: 0.8em 0; +} + +.navigation td { + margin: 0; + padding: 0; + vertical-align: middle; + white-space: nowrap; +} + +.navigation_separator { + color: #555; + display: inline-block; + text-align: center; + width: 1.2em; + text-shadow: 1px 0 #FFF; +} + +.navigation input[type=submit] { + background: none; + border: 0; + margin: 0; + padding: 0.3em 0.5em; + min-width: 1.5em; + font-weight: bold; +} + +.navigation input[type=submit]:hover, .navigation input.edit_mode_active { + background: #333; + color: white; + cursor: pointer; +} + +.navigation select { + margin: 0 .8em; +} + +.cEdit { + margin: 0; + padding: 0; + position: absolute; +} + +.cEdit input[type=text], +.cEdit input[type=password], +.cEdit input[type=number] { + background: #FFF; + height: 100%; + margin: 0; + padding: 0; +} + +.cEdit .edit_area { + background: #FFF; + border: 1px solid #999; + min-width: 10em; + padding: .3em .5em; +} + +.cEdit .edit_area select, +.cEdit .edit_area textarea { + width: 97%; +} + +.cEdit .cell_edit_hint { + color: #555; + font-size: .8em; + margin: .3em .2em; +} + +.cEdit .edit_box { + overflow-x: hidden; + overflow-y: scroll; + padding: 0; +} + +.cEdit .edit_box_posting { + background: #FFF url(getImgPath('ajax_clock_small.gif');?>) no-repeat right center; + padding-right: 1.5em; +} + +.cEdit .edit_area_loading { + background: #FFF url(getImgPath('ajax_clock_small.gif');?>) no-repeat center; + height: 10em; +} + +.cEdit .goto_link { + background: #EEE; + color: #555; + padding: .2em .3em; +} + +.saving_edited_data { + background: url(getImgPath('ajax_clock_small.gif');?>) no-repeat left; + padding-left: 20px; +} + +/* css for timepicker */ +.ui-timepicker-div .ui-widget-header { margin-bottom: 8px; } +.ui-timepicker-div dl { text-align: ; } +.ui-timepicker-div dl dt { height: 25px; margin-bottom: -25px; } +.ui-timepicker-div dl dd { margin: 0 10px 10px 85px; } +.ui-timepicker-div td { font-size: 90%; } +.ui-tpicker-grid-label { background: none; border: none; margin: 0; padding: 0; } +.ui-timepicker-rtl { direction: rtl; } +.ui-timepicker-rtl dl { text-align: right; } +.ui-timepicker-rtl dl dd { margin: 0 65px 10px 10px; } + +input.btn { + color: #333; + background-color: #D0DCE0; +} + +body .ui-widget { + font-size: 1em; +} + +.ui-dialog fieldset legend a { + color: #0000FF; +} + +.ui-draggable { + z-index: 801; +} + +/* jqPlot */ + +/*rules for the plot target div. These will be cascaded down to all plot elements +according to css rules*/ +.jqplot-target { + position: relative; + color: #222222; + font-family: "Trebuchet MS", Arial, Helvetica, sans-serif; + font-size: 1em; +/* height: 300px; + width: 400px;*/ +} + +/*rules applied to all axes*/ +.jqplot-axis { + font-size: 0.75em; +} + +.jqplot-xaxis { + margin-top: 10px; +} + +.jqplot-x2axis { + margin-bottom: 10px; +} + +.jqplot-yaxis { + margin-right: 10px; +} + +.jqplot-y2axis, .jqplot-y3axis, .jqplot-y4axis, .jqplot-y5axis, .jqplot-y6axis, +.jqplot-y7axis, .jqplot-y8axis, .jqplot-y9axis, .jqplot-yMidAxis { + margin-left: 10px; + margin-right: 10px; +} + +/*rules applied to all axis tick divs*/ +.jqplot-axis-tick, .jqplot-xaxis-tick, .jqplot-yaxis-tick, .jqplot-x2axis-tick, +.jqplot-y2axis-tick, .jqplot-y3axis-tick, .jqplot-y4axis-tick, .jqplot-y5axis-tick, +.jqplot-y6axis-tick, .jqplot-y7axis-tick, .jqplot-y8axis-tick, .jqplot-y9axis-tick, +.jqplot-yMidAxis-tick { + position: absolute; + white-space: pre; +} + + +.jqplot-xaxis-tick { + top: 0; + /* initial position untill tick is drawn in proper place */ + left: 15px; + vertical-align: top; +} + +.jqplot-x2axis-tick { + bottom: 0; + /* initial position untill tick is drawn in proper place */ + left: 15px; + vertical-align: bottom; +} + +.jqplot-yaxis-tick { + right: 0; + /* initial position untill tick is drawn in proper place */ + top: 15px; + text-align: right; +} + +.jqplot-yaxis-tick.jqplot-breakTick { + right: -20px; + margin-right: 0; + padding:1px 5px 1px 5px; + z-index: 2; + font-size: 1.5em; +} + +.jqplot-y2axis-tick, .jqplot-y3axis-tick, .jqplot-y4axis-tick, .jqplot-y5axis-tick, +.jqplot-y6axis-tick, .jqplot-y7axis-tick, .jqplot-y8axis-tick, .jqplot-y9axis-tick { + left: 0; + /* initial position until tick is drawn in proper place */ + top: 15px; + text-align: left; +} + +.jqplot-yMidAxis-tick { + text-align: center; + white-space: nowrap; +} + +.jqplot-xaxis-label { + margin-top: 10px; + font-size: 11pt; + position: absolute; +} + +.jqplot-x2axis-label { + margin-bottom: 10px; + font-size: 11pt; + position: absolute; +} + +.jqplot-yaxis-label { + margin-right: 10px; +/* text-align: center;*/ + font-size: 11pt; + position: absolute; +} + +.jqplot-yMidAxis-label { + font-size: 11pt; + position: absolute; +} + +.jqplot-y2axis-label, .jqplot-y3axis-label, .jqplot-y4axis-label, +.jqplot-y5axis-label, .jqplot-y6axis-label, .jqplot-y7axis-label, +.jqplot-y8axis-label, .jqplot-y9axis-label { +/* text-align: center;*/ + font-size: 11pt; + margin-left: 10px; + position: absolute; +} + +.jqplot-meterGauge-tick { + font-size: 0.75em; + color: #999999; +} + +.jqplot-meterGauge-label { + font-size: 1em; + color: #999999; +} + +table.jqplot-table-legend { + margin-top: 12px; + margin-bottom: 12px; + margin-left: 12px; + margin-right: 12px; +} + +table.jqplot-table-legend, table.jqplot-cursor-legend { + background-color: rgba(255,255,255,0.6); + border: 1px solid #cccccc; + position: absolute; + font-size: 0.75em; +} + +td.jqplot-table-legend { + vertical-align:middle; +} + +/* +These rules could be used instead of assigning +element styles and relying on js object properties. +*/ + +/* +td.jqplot-table-legend-swatch { + padding-top: 0.5em; + text-align: center; +} + +tr.jqplot-table-legend:first td.jqplot-table-legend-swatch { + padding-top: 0px; +} +*/ + +td.jqplot-seriesToggle:hover, td.jqplot-seriesToggle:active { + cursor: pointer; +} + +.jqplot-table-legend .jqplot-series-hidden { + text-decoration: line-through; +} + +div.jqplot-table-legend-swatch-outline { + border: 1px solid #cccccc; + padding:1px; +} + +div.jqplot-table-legend-swatch { + width:0; + height:0; + border-top-width: 5px; + border-bottom-width: 5px; + border-left-width: 6px; + border-right-width: 6px; + border-top-style: solid; + border-bottom-style: solid; + border-left-style: solid; + border-right-style: solid; +} + +.jqplot-title { + top: 0; + left: 0; + padding-bottom: 0.5em; + font-size: 1.2em; +} + +table.jqplot-cursor-tooltip { + border: 1px solid #cccccc; + font-size: 0.75em; +} + + +.jqplot-cursor-tooltip { + border: 1px solid #cccccc; + font-size: 0.75em; + white-space: nowrap; + background: rgba(208,208,208,0.5); + padding: 1px; +} + +.jqplot-highlighter-tooltip, .jqplot-canvasOverlay-tooltip { + border: 1px solid #cccccc; + font-size: 0.75em; + white-space: nowrap; + background: rgba(208,208,208,0.5); + padding: 1px; +} + +.jqplot-point-label { + font-size: 0.75em; + z-index: 2; +} + +td.jqplot-cursor-legend-swatch { + vertical-align: middle; + text-align: center; +} + +div.jqplot-cursor-legend-swatch { + width: 1.2em; + height: 0.7em; +} + +.jqplot-error { +/* Styles added to the plot target container when there is an error go here.*/ + text-align: center; +} + +.jqplot-error-message { +/* Styling of the custom error message div goes here.*/ + position: relative; + top: 46%; + display: inline-block; +} + +div.jqplot-bubble-label { + font-size: 0.8em; +/* background: rgba(90%, 90%, 90%, 0.15);*/ + padding-left: 2px; + padding-right: 2px; + color: rgb(20%, 20%, 20%); +} + +div.jqplot-bubble-label.jqplot-bubble-label-highlight { + background: rgba(90%, 90%, 90%, 0.7); +} + +div.jqplot-noData-container { + text-align: center; + background-color: rgba(96%, 96%, 96%, 0.3); +} + +.relationalTable td { + vertical-align: top; +} + +.relationalTable select { + width: 125px; + margin-right: 5px; +} + +.report-data { + height:13em; + overflow:scroll; + width:570px; + border: solid 1px; + background: white; + padding: 2px; +} + +.report-description { + height:10em; + width:570px; +} + +div#page_content div#tableslistcontainer table.data { + border-top: 0.1px solid #EEEEEE; +} + +div#page_content div#tableslistcontainer, div#page_content div.notice, div#page_content div.result_query { + margin-top: 1em; +} + +table.show_create { + margin-top: 1em; +} + +table.show_create td { + border-right: 1px solid #bbb; +} + +#alias_modal table { + width: 100%; +} + +#alias_modal label { + font-weight: bold; +} + +.ui-dialog { + position: fixed; +} + +.small_font { + font-size: smaller; +} + +/* Console styles */ +#pma_console_container { + width: 100%; + position: fixed; + bottom: 0; + : 0; + z-index: 100; +} +#pma_console { + position: relative; + margin-: 240px; + z-index: 100; +} +#pma_console .templates { + display: none; +} +#pma_console .mid_text, +#pma_console .toolbar span { + vertical-align: middle; +} +#pma_console .toolbar { + position: relative; + background: #ccc; + border-top: solid 1px #aaa; + cursor: n-resize; +} +#pma_console .toolbar.collapsed:not(:hover) { + display: inline-block; + border-top--radius: 3px; + border-: solid 1px #aaa; +} +#pma_console .toolbar.collapsed { + cursor: default; +} +#pma_console .toolbar.collapsed>.button { + display: none; +} +#pma_console .message span.text, +#pma_console .message span.action, +#pma_console .toolbar .button, +#pma_console .toolbar .text, +#pma_console .switch_button { + padding: 0 3px; + display: inline-block; +} +#pma_console .message span.action, +#pma_console .toolbar .button, +#pma_console .switch_button { + cursor: pointer; +} +#pma_console .message span.action:hover, +#pma_console .toolbar .button:hover, +#pma_console .switch_button:hover, +#pma_console .toolbar .button.active { + background: #ddd; +} +#pma_console .toolbar .text { + font-weight: bold; +} +#pma_console .toolbar .button, +#pma_console .toolbar .text { + margin-: .4em; +} +#pma_console .toolbar .button, +#pma_console .toolbar .text { + float: ; +} +#pma_console .content { + overflow-x: hidden; + overflow-y: auto; + margin-bottom: -65px; + border-top: solid 1px #aaa; + background: #fff; + padding-top: .4em; +} +#pma_console .content.console_dark_theme { + background: #000; + color: #fff; +} +#pma_console .content.console_dark_theme .CodeMirror-wrap { + background: #000; + color: #fff; +} +#pma_console .content.console_dark_theme .action_content { + color: #000; +} +#pma_console .content.console_dark_theme .message { + border-color: #373B41; +} +#pma_console .content.console_dark_theme .CodeMirror-cursor { + border-color: #fff; +} +#pma_console .content.console_dark_theme .cm-keyword { + color: #de935f; +} +#pma_console .message, +#pma_console .query_input { + position: relative; + font-family: Monaco, Consolas, monospace; + cursor: text; + margin: 0 10px .2em 1.4em; +} +#pma_console .message { + border-bottom: solid 1px #ccc; + padding-bottom: .2em; +} +#pma_console .message.expanded>.action_content { + position: relative; +} +#pma_console .message:before, +#pma_console .query_input:before { + left: -0.7em; + position: absolute; + content: ">"; +} +#pma_console .query_input:before { + top: -2px; +} +#pma_console .query_input textarea { + width: 100%; + height: 4em; + resize: vertical; +} +#pma_console .message:hover:before { + color: #7cf; + font-weight: bold; +} +#pma_console .message.expanded:before { + content: "]"; +} +#pma_console .message.welcome:before { + display: none; +} +#pma_console .message.failed:before, +#pma_console .message.failed.expanded:before, +#pma_console .message.failed:hover:before { + content: "="; + color: #944; +} +#pma_console .message.pending:before { + opacity: .3; +} +#pma_console .message.collapsed>.query { + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; +} +#pma_console .message.expanded>.query { + display: block; + white-space: pre; + word-wrap: break-word; +} +#pma_console .message .text.targetdb, +#pma_console .message.collapsed .action.collapse, +#pma_console .message.expanded .action.expand, +#pma_console .message .action.requery, +#pma_console .message .action.profiling, +#pma_console .message .action.explain, +#pma_console .message .action.bookmark { + display: none; +} +#pma_console .message.select .action.profiling, +#pma_console .message.select .action.explain, +#pma_console .message.history .text.targetdb, +#pma_console .message.successed .text.targetdb, +#pma_console .message.history .action.requery, +#pma_console .message.history .action.bookmark, +#pma_console .message.bookmark .action.requery, +#pma_console .message.bookmark .action.bookmark, +#pma_console .message.successed .action.requery, +#pma_console .message.successed .action.bookmark { + display: inline-block; +} +#pma_console .message .action_content { + position: absolute; + bottom: 100%; + background: #ccc; + border: solid 1px #aaa; + border-top--radius: 3px; +} +html.ie8 #pma_console .message .action_content { + position: relative!important; +} +#pma_console .message.bookmark .text.targetdb, +#pma_console .message .text.query_time { + margin: 0; + display: inline-block; +} +#pma_console .message.failed .text.query_time, +#pma_console .message .text.failed { + display: none; +} +#pma_console .message.failed .text.failed { + display: inline-block; +} +#pma_console .message .text { + background: #fff; +} +#pma_console .message.collapsed>.action_content { + display: none; +} +#pma_console .message.collapsed:hover>.action_content { + display: block; +} +#pma_console .message .bookmark_label { + padding: 0 4px; + top: 0; + background: #369; + color: #fff; + border-radius: 3px; +} +#pma_console .message .bookmark_label.shared { + background: #396; +} +#pma_console .message.expanded .bookmark_label { + border-top-left-radius: 0; + border-top-right-radius: 0; +} +#pma_console .query_input { + position: relative; +} +#pma_console .mid_layer { + height: 100%; + width: 100%; + position: absolute; + top: 0; + /* For support IE8, this layer doesn't use filter:opacity or opacity, + js code will fade this layer opacity to 0.18(using animation) */ + background: #666; + display: none; + cursor: pointer; + z-index: 200; +} +#pma_console .card { + position: absolute; + width: 94%; + height: 100%; + min-height: 48px; + : 100%; + top: 0; + border-: solid 1px #999; + z-index: 300; + transition: 0.2s; + -ms-transition: 0.2s; + -webkit-transition: 0.2s; + -moz-transition: 0.2s; +} +#pma_console .card.show { + : 6%; + box-shadow: -2px 1px 4px -1px #999; +} + +html.ie7 #pma_console .query_input { + display: none; +} + +#pma_bookmarks .content.add_bookmark, +#pma_console_options .content { + padding: 4px 6px; +} +#pma_bookmarks .content.add_bookmark .options { + margin-: 1.4em; + padding-bottom: .4em; + margin-bottom: .4em; + border-bottom: solid 1px #ccc; +} +#pma_bookmarks .content.add_bookmark .options button { + margin: 0 7px; + vertical-align: bottom; +} +#pma_bookmarks .content.add_bookmark input[type=text] { + margin: 0; + padding: 2px 4px; +} +#pma_console .button.hide, +#pma_console .message span.text.hide { + display: none; +} +#debug_console.grouped .ungroup_queries, +#debug_console.ungrouped .group_queries { + display: inline-block; +} +#debug_console.ungrouped .ungroup_queries, +#debug_console.ungrouped .sort_count, +#debug_console.grouped .group_queries { + display: none; +} +#debug_console .count { + margin-right: 8px; +} +#debug_console .show_trace .trace, +#debug_console .show_args .args { + display: block; +} +#debug_console .hide_trace .trace, +#debug_console .hide_args .args, +#debug_console .show_trace .action.dbg_show_trace, +#debug_console .hide_trace .action.dbg_hide_trace, +#debug_console .traceStep.hide_args .action.dbg_hide_args, +#debug_console .traceStep.show_args .action.dbg_show_args { + display: none; +} + +#debug_console .traceStep:after, +#debug_console .trace.welcome:after, +#debug_console .debug>.welcome:after { + content: ""; + display: table; + clear: both; +} +#debug_console .debug_summary { + float: left; +} +#debug_console .trace.welcome .time { + float: right; +} +#debug_console .traceStep .file, +#debug_console .script_name { + float: right; +} +#debug_console .traceStep .args pre { + margin: 0; +} + +/* Code mirror console style*/ + +.cm-s-pma .CodeMirror-code pre, +.cm-s-pma .CodeMirror-code { + font-family: Monaco, Consolas, monospace; +} +.cm-s-pma .CodeMirror-measure>pre, +.cm-s-pma .CodeMirror-code>pre, +.cm-s-pma .CodeMirror-lines { + padding: 0; +} +.cm-s-pma.CodeMirror { + resize: none; + height: auto; + width: 100%; + min-height: initial; + max-height: initial; +} +.cm-s-pma .CodeMirror-scroll { + cursor: text; +} + +/* PMA drop-improt style */ + +.pma_drop_handler { + display: none; + position: fixed; + top: 0; + left: 0; + width: 100%; + background: rgba(0, 0, 0, 0.6); + height: 100%; + z-index: 999; + color: white; + font-size: 30pt; + text-align: center; + padding-top: 20%; +} + +.pma_sql_import_status { + display: none; + position: fixed; + bottom: 0; + right: 25px; + width: 400px; + border: 1px solid #999; + background: #f3f3f3; + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + border-radius: 4px; + -moz-box-shadow: 2px 2px 5px #ccc; + -webkit-box-shadow: 2px 2px 5px #ccc; + box-shadow: 2px 2px 5px #ccc; +} + +.pma_sql_import_status h2, +.pma_drop_result h2 { + background-color: #bbb; + padding: .1em .3em; + margin-top: 0; + margin-bottom: 0; + color: #fff; + font-size: 1.6em; + font-weight: normal; + text-shadow: 0 1px 0 #777; + -moz-box-shadow: 1px 1px 15px #999 inset; + -webkit-box-shadow: 1px 1px 15px #999 inset; + box-shadow: 1px 1px 15px #999 inset; +} + +.pma_sql_import_status div { + height: 270px; + overflow-y:auto; + overflow-x:hidden; + list-style-type: none; +} + +.pma_sql_import_status div li { + padding: 8px 10px; + border-bottom: 1px solid #bbb; + color: rgb(148, 14, 14); + background: white; +} + +.pma_sql_import_status div li .filesize { + float: right; +} + +.pma_sql_import_status h2 .minimize { + float: right; + margin-right: 5px; + padding: 0 10px; +} + +.pma_sql_import_status h2 .close { + float: right; + margin-right: 5px; + padding: 0 10px; + display: none; +} + +.pma_sql_import_status h2 .minimize:hover, +.pma_sql_import_status h2 .close:hover, +.pma_drop_result h2 .close:hover { + background: rgba(155, 149, 149, 0.78); + cursor: pointer; +} + +.pma_drop_file_status { + color: #235a81; +} + +.pma_drop_file_status span.underline:hover { + cursor: pointer; + text-decoration: underline; +} + +.pma_drop_result { + position: fixed; + top: 10%; + left: 20%; + width: 60%; + background: white; + min-height: 300px; + z-index: 800; + -webkit-box-shadow: 0 0 15px #999; + border-radius: 10px; + cursor: move; +} + +.pma_drop_result h2 .close { + float: right; + margin-right: 5px; + padding: 0 10px; +} + +#composite_index_list { + list-style-type: none; + list-style-position: inside; +} + +span.drag_icon { + display: inline-block; + background-image: url('getImgPath('s_sortable.png');?>'); + background-position: center center; + background-repeat: no-repeat; + width: 1em; + height: 3em; + cursor: move; +} + +.topmargin { + margin-top: 1em; +} + +/* styles for sortable tables created with tablesorter jquery plugin */ +th.header { + cursor: pointer; + color: #0000FF; +} + +th.header:hover { + text-decoration: underline; +} + +th.header .sorticon { + width: 16px; + height: 16px; + background-repeat: no-repeat; + background-position: right center; + display: inline-table; + vertical-align: middle; + float: right; +} + +th.headerSortUp .sorticon, th.headerSortDown:hover .sorticon { + background-image: url(getImgPath('s_desc.png');?>); +} + +th.headerSortDown .sorticon, th.headerSortUp:hover .sorticon { + background-image: url(getImgPath('s_asc.png');?>); +} +/* end of styles of sortable tables */ + +/* styles for jQuery-ui to support rtl languages */ +body .ui-dialog .ui-dialog-titlebar-close { + : .3em; + : initial; +} + +body .ui-dialog .ui-dialog-title { + float: ; +} + +body .ui-dialog .ui-dialog-buttonpane .ui-dialog-buttonset { + float: ; +} +/* end of styles for jQuery-ui to support rtl languages */ + +/* Override some jQuery-ui styling to have square corners */ +body .ui-corner-all, +body .ui-corner-top, +body .ui-corner-left, +body .ui-corner-tl { + border-top-left-radius: 0; +} +body .ui-corner-all, +body .ui-corner-top, +body .ui-corner-right, +body .ui-corner-tr { + border-top-right-radius: 0; +} +body .ui-corner-all, +body .ui-corner-bottom, +body .ui-corner-left, +body .ui-corner-bl { + border-bottom-left-radius: 0; +} +body .ui-corner-all, +body .ui-corner-bottom, +body .ui-corner-right, +body .ui-corner-br { + border-bottom-right-radius: 0; +} +/* Override jQuery-ui styling for ui-dialog */ +body .ui-dialog { + padding: 0; + border-color: #000000; +} +body .ui-dialog .ui-dialog-titlebar { + padding: .3em .5em; + border: none; + border-bottom: 1px solid #000000; +} +body .ui-dialog .ui-dialog-titlebar button { + border: 1px solid #999999; +} +body .ui-dialog .ui-dialog-content { + padding: .2em .4em; +} +body .ui-dialog .ui-dialog-buttonpane { + background: #D3DCE3; + border-top: 1px solid #000000; +} +body .ui-dialog .ui-dialog-buttonpane button { + margin: .1em 0 .1em .4em; + border: 1px solid #999999; + color: #000000; +} +body .ui-dialog .ui-button-text-only .ui-button-text { + padding: .2em .6em; +} + +.scrollindicator { + display: none; +} + +@media only screen and (max-width: 768px) { + /* For mobile phones: */ + #main_pane_left { + width: 100%; + } + + #main_pane_right { + padding-top: 0; + padding-: 1px; + padding-: 1px; + } + + ul#topmenu, + ul.tabs { + display: flex; + } + + .navigationbar { + display: inline-flex; + margin: 0 !important; + border-radius: 0 !important; + overflow: auto; + } + + .scrollindicator { + padding: 5px; + cursor: pointer; + display: inline; + } + + .responsivetable { + overflow-x: auto; + } + + body#loginform div.container { + width: 100%; + } + + .largescreenonly { + display: none; + } + + .width100, .desktop50 { + width: 100%; + } + + .width96 { + width: 96% !important; + } + + #page_nav_icons { + display: none; + } + + table#serverstatusconnections { + margin-left: 0; + } + + #table_name_col_no { + top: 62px + } + + .tdblock tr td { + display: block; + } + + #table_columns { + margin-top: 60px; + } + + #table_columns .tablesorter { + min-width: 100%; + } + + .doubleFieldset fieldset { + width: 98%; + } + + div#serverstatusquerieschart { + width: 100%; + height: 450px; + } + + .ui-dialog { + margin: 1%; + width: 95% !important; + } + + #serverinfo .item { + margin: 4px; + } +} + +/* templates/database/designer */ +/* side menu */ +#name-panel { + overflow:hidden; +} diff --git a/php/apps/phpmyadmin49/html/themes/original/css/navigation.css.php b/php/apps/phpmyadmin49/html/themes/original/css/navigation.css.php new file mode 100644 index 00000000..9666b02a --- /dev/null +++ b/php/apps/phpmyadmin49/html/themes/original/css/navigation.css.php @@ -0,0 +1,424 @@ + + +/******************************************************************************/ +/* Navigation */ + +#pma_navigation { + background: ; + color: ; + width: px; + overflow: hidden; + position: fixed; + top: 0; + : 0; + height: 100vh; + border-: 1px solid gray; + z-index: 800; +} + +#pma_navigation_content { + width: 100%; + height: 100%; + position: absolute; + top: 0; + : 0; + z-index: 0; + padding-bottom: 1em; +} + +#pma_navigation ul { + margin: 0; +} + +#pma_navigation form { + margin: 0; + padding: 0; + display: inline; +} + +#pma_navigation select#select_server, +#pma_navigation select#lightm_db { + width: 100%; +} + +/******************************************************************************/ +/* specific elements */ + +#pma_navigation div.pageselector { + text-align: center; + margin: 0 0 0; + margin-: 0.75em; + border-: 1px solid #666; +} + +#pma_navigation div#pmalogo { + + background-color: ; + padding: .3em; +} + +#pma_navigation div#recentTableList, +#pma_navigation div#FavoriteTableList { + text-align: center; + margin-bottom: 0.5em; +} + +#pma_navigation #recentTable, +#pma_navigation #FavoriteTable { + width: 200px; +} + +#pma_navigation #pmalogo, +#pma_navigation #serverChoice, +#pma_navigation #navipanellinks, +#pma_navigation #recentTableList, +#pma_navigation #FavoriteTableList, +#pma_navigation #databaseList, +#pma_navigation div.pageselector.dbselector { + text-align: center; + margin-bottom: 0.3em; + padding-bottom: 0.3em; + border: 0; +} + +#pma_navigation #recentTableList select, +#pma_navigation #FavoriteTableList select, +#pma_navigation #serverChoice select + { + width: 80%; +} + +#pma_navigation #recentTableList, +#pma_navigation #FavoriteTableList { + margin-bottom: 0; + padding-bottom: 0; +} + +#pma_navigation_content > img.throbber { + display: block; + margin: 0 auto; +} + +/* Navigation tree*/ +#pma_navigation_tree { + margin: 0; + margin-: 1em; + color: #444; + height: 74%; + position: relative; +} + +#pma_navigation_select_database { + text-align: left; + padding: 0 0 0; + border: 0; + margin: 0; +} + +#pma_navigation_db_select { + margin-top: 0.5em; + margin-: 0.75em; +} +#pma_navigation_db_select select { + background: url("./themes/pmahomme/img/select_bg.png") repeat scroll 0 0; + -webkit-border-radius: 2px; + border-radius: 2px; + border: 1px solid #bbb; + border-top: 1px solid #bbb; + color: #333; + padding: 4px 6px; + margin: 0 0 0; + width: 92%; + font-size: 1.11em; +} + +#pma_navigation_tree_content { + width: 100%; + overflow: hidden; + overflow-y: auto; + position: absolute; + height: 100%; +} +#pma_navigation_tree_content a.hover_show_full { + position: relative; + z-index: 100; + vertical-align: sub; +} +#pma_navigation_tree a { + color: ; +} +#pma_navigation_tree a:hover { + text-decoration: underline; +} +#pma_navigation_tree li.activePointer { + color: ; + background-color: ; +} +#pma_navigation_tree li.selected { + color: ; + background-color: ; +} +#pma_navigation_tree li .dbItemControls { + padding-left: 4px; +} +#pma_navigation_tree li .navItemControls { + display: none; + padding-left: 4px; +} +#pma_navigation_tree li.activePointer .navItemControls { + display: inline; + opacity: 0.5; +} +#pma_navigation_tree li.activePointer .navItemControls:hover { + display: inline; + opacity: 1.0; +} +#pma_navigation_tree ul { + clear: both; + padding: 0; + list-style-type: none; + margin: 0; +} +#pma_navigation_tree ul ul { + position: relative; +} +#pma_navigation_tree li { + white-space: nowrap; + clear: both; + min-height: 16px; +} +#pma_navigation_tree img { + margin: 0; +} +#pma_navigation_tree i { + display: block; +} +#pma_navigation_tree div.block { + position: relative; + width:1.5em; + height:1.5em; + min-width: 16px; + min-height: 16px; + float: ; +} +#pma_navigation_tree div.block.double { + width: 3em; +} +#pma_navigation_tree div.block i, +#pma_navigation_tree div.block b { + width: 1.5em; + height: 1.7em; + min-width: 16px; + min-height: 8px; + position: absolute; + bottom: 0.7em; + : 0.75em; + z-index: 0; +} +#pma_navigation_tree div.block i { + border-: 1px solid #666; + border-bottom: 1px solid #666; + position: relative; + z-index: 0; +} +#pma_navigation_tree div.block i.first { /* Removes top segment */ + border-: 0; +} +/* Bottom segment for the tree element connections */ +#pma_navigation_tree div.block b { + display: block; + height: 0.75em; + bottom: 0; + : 0.75em; + border-: 1px solid #666; +} +#pma_navigation_tree div.block a, +#pma_navigation_tree div.block u { + position: absolute; + : 50%; + top: 50%; + z-index: 10; +}#pma_navigation_tree div.block a + a { + : 100%; +} +#pma_navigation_tree div.block.double a, +#pma_navigation_tree div.block.double u { + : 25%; +} +#pma_navigation_tree div.block.double a + a { + : 70%; +} +#pma_navigation_tree div.block img { + position: relative; + top: -0.6em; + : 0; + margin-: -5px; +} +#pma_navigation_tree li.last > ul { + background: none; +} +#pma_navigation_tree li > a, #pma_navigation_tree li > i { + line-height: 1.5em; + height: 1.5em; + padding-: 0.3em; +} +#pma_navigation_tree .list_container { + border-: 1px solid #666; + margin-: 0.75em; + padding-: 0.75em; +} +#pma_navigation_tree .last > .list_container { + border-: 0 solid #666; +} + +/* Fast filter */ +li.fast_filter { + padding-: 0.75em; + margin-: 0.75em; + padding-: 35px; + border-: 1px solid #666; +} +li.fast_filter input { + padding-: 1.7em; + width: 100%; +} +li.fast_filter span { + position: relative; + : 1.5em; + padding: 0.2em; + cursor: pointer; + font-weight: bold; + color: #800; +} +/* IE10+ has its own reset X */ +html.ie li.fast_filter span { + display: none; +} +html.ie.ie9 li.fast_filter span, +html.ie.ie8 li.fast_filter span { + display: auto; +} +html.ie li.fast_filter input { + padding-: .2em; +} +html.ie.ie9 li.fast_filter input, +html.ie.ie8 li.fast_filter input { + padding-: 1.7em; +} +li.fast_filter.db_fast_filter { + border: 0; +} + +/* Resize handler */ +#pma_navigation_resizer { + width: 3px; + height: 100%; + background-color: #aaa; + cursor: col-resize; + position: fixed; + top: 0; + : 240px; + z-index: 801; +} +#pma_navigation_collapser { + width: 20px; + height: 22px; + line-height: 22px; + background: #eee; + color: #555; + font-weight: bold; + position: fixed; + top: 0; + : px; + text-align: center; + cursor: pointer; + z-index: 800; + text-shadow: 0 1px 0 #fff; + filter: dropshadow(color=#fff, offx=0, offy=1); + border: 1px solid #888; +} + +#navigation_controls_outer { + min-height: 21px !important; +} + +#navigation_controls_outer.activePointer { + background-color: transparent !important; +} + +#navigation_controls { + float: right; + padding-right: 23px; +} + +/* Quick warp links */ +.pma_quick_warp { + margin-top: 5px; + margin-: 2px; + position: relative; +} +.pma_quick_warp .drop_list { + float: ; + margin-: 3px; + padding: 2px 0; +} +.pma_quick_warp .drop_button{ + padding: 0 .3em; + border: 1px solid #ddd; + background: #f2f2f2; + cursor: pointer; +} +.pma_quick_warp .drop_list:hover .drop_button { + background: #fff; +} +.pma_quick_warp .drop_list ul { + position: absolute; + margin: 0; + padding: 0; + overflow: hidden; + overflow-y: auto; + list-style: none; + background: #fff; + border: 1px solid #ddd; + border-top--radius: 0; + border-bottom--radius: 0; + top: 100%; + : 3px; + : 0; + display: none; + z-index: 802; +} +.pma_quick_warp .drop_list:hover ul { + display: block; +} +.pma_quick_warp .drop_list li { + white-space: nowrap; +} +.pma_quick_warp .drop_list li img { + vertical-align: sub; +} +.pma_quick_warp .drop_list li:hover { + background: #f2f2f2; +} +.pma_quick_warp .drop_list a { + display: block; + padding: .1em .3em; +} +.pma_quick_warp .drop_list a.favorite_table_anchor { + clear: left; + float: left; + padding: .1em .3em 0; +} diff --git a/php/apps/phpmyadmin49/html/themes/original/css/printview.css b/php/apps/phpmyadmin49/html/themes/original/css/printview.css new file mode 100644 index 00000000..809cbdd0 --- /dev/null +++ b/php/apps/phpmyadmin49/html/themes/original/css/printview.css @@ -0,0 +1,169 @@ +@media print { + #back_button_print_view, #print_button_print_view { + display: none; + } +} + +/* For removing element from Print View */ +.print_ignore { + display: none; +} + +.nowrap { + white-space: nowrap; +} + +.hide { + display: none; +} + +/* Standard CSS */ +body, table, th, td { + color: #000; + background-color: #fff; + font-size: 12px; +} + +/* To remove link text decoration */ +a:link { + color:#000; + text-decoration:none +} + +/* To remove any image borders */ +img { + border: 0; +} + +/* Table specific */ +table, th, td { + border: .1em solid #000; + background-color: #fff; +} + +table { + border-collapse: collapse; + border-spacing: 0.2em; +} + +thead { + border-collapse: collapse; + border-spacing: 0.2em; + border: .1em solid #000; + font-weight: 900; +} + +th, td { + padding: 0.2em; +} + +thead th { + font-weight: bold; + background-color: #e5e5e5; + border: .1em solid #000; +} + +th.vtop, td.vtop { + vertical-align: top; +} + +th.vbottom, td.vbottom { + vertical-align: bottom; +} + +/* Common Elements not to be included */ +/* Hide Navigation and Top Menu bar */ +#pma_navigation, #floating_menubar { + display: none; +} +/* Hide console */ +#pma_console_container { + display: none; +} + +/* Hide Navigation items (like Goto Top) */ +#page_nav_icons { + display: none; +} + +/* Hide the Create Table form */ +#create_table_form_minimal { + display: none; +} + +/* Hide the Page Settings Modal box */ +#page_settings_modal { + display: none; +} + +/* Hide footer, Demo notice, errors div */ +#pma_footer, #pma_demo, #pma_errors { + display: none; +} + +/* Hide the #selflink div */ +#selflink { + display: none; +} + +/* Position the main content */ +#page_content { + position: absolute; + left: 0; + top: 0; + width: 95%; + float: none; +} + +/* Specific Class for overriding while Print */ +.print { + background-color: #000; +} + +/* For the Success message div */ +div.success { + background-color: #fff; +} + +.sqlOuter { + color: black; + background-color: #000; +} + +/* For hiding 'Open a New phpMyAdmin Window' button */ +.ic_window-new, .ic_s_cog { + display: none; +} + +.sticky_columns tr { + display: none; +} + +#structure-action-links, #addColumns { + display: none; +} + +/* Hide extra menu on tbl_structure.php */ +#topmenu2 { + display: none; +} + +.cDrop, .cEdit, .cList, .cCpy, .cPointer { + display: none; +} + +/* odd items 1,3,5,7,... */ +table tbody:first-of-type tr:nth-child(odd), +table tbody:first-of-type tr:nth-child(odd) th { + background: #fff; +} + +/* even items 2,4,6,8,... */ +table tbody:first-of-type tr:nth-child(even), +table tbody:first-of-type tr:nth-child(even) th { + background: #DFDFDF; +} + +.column_attribute { + font-size: 100%; +} diff --git a/php/apps/phpmyadmin49/html/themes/original/img/ajax_clock_small.gif b/php/apps/phpmyadmin49/html/themes/original/img/ajax_clock_small.gif new file mode 100644 index 00000000..bde4932c Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/ajax_clock_small.gif differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/arrow_ltr.png b/php/apps/phpmyadmin49/html/themes/original/img/arrow_ltr.png new file mode 100644 index 00000000..cd79ab42 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/arrow_ltr.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/arrow_rtl.png b/php/apps/phpmyadmin49/html/themes/original/img/arrow_rtl.png new file mode 100644 index 00000000..d035b9d9 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/arrow_rtl.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/b_bookmark.png b/php/apps/phpmyadmin49/html/themes/original/img/b_bookmark.png new file mode 100644 index 00000000..09b1b586 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/b_bookmark.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/b_browse.png b/php/apps/phpmyadmin49/html/themes/original/img/b_browse.png new file mode 100644 index 00000000..440b2e63 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/b_browse.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/b_calendar.png b/php/apps/phpmyadmin49/html/themes/original/img/b_calendar.png new file mode 100644 index 00000000..8dfe6287 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/b_calendar.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/b_chart.png b/php/apps/phpmyadmin49/html/themes/original/img/b_chart.png new file mode 100644 index 00000000..a8c43e65 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/b_chart.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/b_close.png b/php/apps/phpmyadmin49/html/themes/original/img/b_close.png new file mode 100644 index 00000000..8d8e98ec Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/b_close.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/b_column_add.png b/php/apps/phpmyadmin49/html/themes/original/img/b_column_add.png new file mode 100644 index 00000000..57e2a8ad Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/b_column_add.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/b_comment.png b/php/apps/phpmyadmin49/html/themes/original/img/b_comment.png new file mode 100644 index 00000000..fa2ee942 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/b_comment.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/b_dbstatistics.png b/php/apps/phpmyadmin49/html/themes/original/img/b_dbstatistics.png new file mode 100644 index 00000000..fca48f9a Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/b_dbstatistics.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/b_deltbl.png b/php/apps/phpmyadmin49/html/themes/original/img/b_deltbl.png new file mode 100644 index 00000000..2144bce4 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/b_deltbl.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/b_docs.png b/php/apps/phpmyadmin49/html/themes/original/img/b_docs.png new file mode 100644 index 00000000..bd47ebc1 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/b_docs.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/b_drop.png b/php/apps/phpmyadmin49/html/themes/original/img/b_drop.png new file mode 100644 index 00000000..42bea8d1 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/b_drop.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/b_edit.png b/php/apps/phpmyadmin49/html/themes/original/img/b_edit.png new file mode 100644 index 00000000..2abf0818 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/b_edit.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/b_empty.png b/php/apps/phpmyadmin49/html/themes/original/img/b_empty.png new file mode 100644 index 00000000..c5071858 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/b_empty.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/b_engine.png b/php/apps/phpmyadmin49/html/themes/original/img/b_engine.png new file mode 100644 index 00000000..12efc889 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/b_engine.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/b_event_add.png b/php/apps/phpmyadmin49/html/themes/original/img/b_event_add.png new file mode 100644 index 00000000..1f31c7cb Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/b_event_add.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/b_events.png b/php/apps/phpmyadmin49/html/themes/original/img/b_events.png new file mode 100644 index 00000000..07c60ff8 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/b_events.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/b_export.png b/php/apps/phpmyadmin49/html/themes/original/img/b_export.png new file mode 100644 index 00000000..9a43efdf Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/b_export.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/b_favorite.png b/php/apps/phpmyadmin49/html/themes/original/img/b_favorite.png new file mode 100644 index 00000000..d4f65788 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/b_favorite.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/b_find_replace.png b/php/apps/phpmyadmin49/html/themes/original/img/b_find_replace.png new file mode 100644 index 00000000..a0773af8 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/b_find_replace.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/b_ftext.png b/php/apps/phpmyadmin49/html/themes/original/img/b_ftext.png new file mode 100644 index 00000000..2ae04d77 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/b_ftext.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/b_globe.gif b/php/apps/phpmyadmin49/html/themes/original/img/b_globe.gif new file mode 100644 index 00000000..ef03dcf0 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/b_globe.gif differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/b_group.png b/php/apps/phpmyadmin49/html/themes/original/img/b_group.png new file mode 100644 index 00000000..4dd9d0b5 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/b_group.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/b_help.png b/php/apps/phpmyadmin49/html/themes/original/img/b_help.png new file mode 100644 index 00000000..eddf4d92 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/b_help.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/b_home.png b/php/apps/phpmyadmin49/html/themes/original/img/b_home.png new file mode 100644 index 00000000..5bfd8ad6 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/b_home.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/b_import.png b/php/apps/phpmyadmin49/html/themes/original/img/b_import.png new file mode 100644 index 00000000..ee313da9 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/b_import.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/b_index.png b/php/apps/phpmyadmin49/html/themes/original/img/b_index.png new file mode 100644 index 00000000..8a76003c Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/b_index.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/b_index_add.png b/php/apps/phpmyadmin49/html/themes/original/img/b_index_add.png new file mode 100644 index 00000000..78b1216a Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/b_index_add.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/b_inline_edit.png b/php/apps/phpmyadmin49/html/themes/original/img/b_inline_edit.png new file mode 100644 index 00000000..0d2dab05 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/b_inline_edit.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/b_insrow.png b/php/apps/phpmyadmin49/html/themes/original/img/b_insrow.png new file mode 100644 index 00000000..ce32a0a1 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/b_insrow.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/b_key.png b/php/apps/phpmyadmin49/html/themes/original/img/b_key.png new file mode 100644 index 00000000..9af08692 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/b_key.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/b_minus.png b/php/apps/phpmyadmin49/html/themes/original/img/b_minus.png new file mode 100644 index 00000000..668f0da1 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/b_minus.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/b_more.png b/php/apps/phpmyadmin49/html/themes/original/img/b_more.png new file mode 100644 index 00000000..9d84943e Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/b_more.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/b_move.png b/php/apps/phpmyadmin49/html/themes/original/img/b_move.png new file mode 100644 index 00000000..7ed238cf Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/b_move.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/b_newdb.png b/php/apps/phpmyadmin49/html/themes/original/img/b_newdb.png new file mode 100644 index 00000000..94166608 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/b_newdb.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/b_newtbl.png b/php/apps/phpmyadmin49/html/themes/original/img/b_newtbl.png new file mode 100644 index 00000000..f675656f Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/b_newtbl.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/b_nextpage.png b/php/apps/phpmyadmin49/html/themes/original/img/b_nextpage.png new file mode 100644 index 00000000..75a2c67c Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/b_nextpage.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/b_no_favorite.png b/php/apps/phpmyadmin49/html/themes/original/img/b_no_favorite.png new file mode 100644 index 00000000..0217dc3b Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/b_no_favorite.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/b_plugin.png b/php/apps/phpmyadmin49/html/themes/original/img/b_plugin.png new file mode 100644 index 00000000..205b9dab Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/b_plugin.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/b_plus.png b/php/apps/phpmyadmin49/html/themes/original/img/b_plus.png new file mode 100644 index 00000000..22bb1a9f Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/b_plus.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/b_primary.png b/php/apps/phpmyadmin49/html/themes/original/img/b_primary.png new file mode 100644 index 00000000..25a24ca2 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/b_primary.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/b_print.png b/php/apps/phpmyadmin49/html/themes/original/img/b_print.png new file mode 100644 index 00000000..892e51fa Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/b_print.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/b_props.png b/php/apps/phpmyadmin49/html/themes/original/img/b_props.png new file mode 100644 index 00000000..07ad49c6 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/b_props.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/b_relations.png b/php/apps/phpmyadmin49/html/themes/original/img/b_relations.png new file mode 100644 index 00000000..7c16044b Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/b_relations.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/b_report.png b/php/apps/phpmyadmin49/html/themes/original/img/b_report.png new file mode 100644 index 00000000..f5b57cdd Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/b_report.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/b_routine_add.png b/php/apps/phpmyadmin49/html/themes/original/img/b_routine_add.png new file mode 100644 index 00000000..78517c10 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/b_routine_add.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/b_routines.png b/php/apps/phpmyadmin49/html/themes/original/img/b_routines.png new file mode 100644 index 00000000..439899be Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/b_routines.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/b_save.png b/php/apps/phpmyadmin49/html/themes/original/img/b_save.png new file mode 100644 index 00000000..c35f004a Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/b_save.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/b_saveimage.png b/php/apps/phpmyadmin49/html/themes/original/img/b_saveimage.png new file mode 100644 index 00000000..0bc614a4 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/b_saveimage.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/b_sbrowse.png b/php/apps/phpmyadmin49/html/themes/original/img/b_sbrowse.png new file mode 100644 index 00000000..f69952c5 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/b_sbrowse.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/b_search.png b/php/apps/phpmyadmin49/html/themes/original/img/b_search.png new file mode 100644 index 00000000..6f34d0a5 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/b_search.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/b_select.png b/php/apps/phpmyadmin49/html/themes/original/img/b_select.png new file mode 100644 index 00000000..a83a0136 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/b_select.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/b_snewtbl.png b/php/apps/phpmyadmin49/html/themes/original/img/b_snewtbl.png new file mode 100644 index 00000000..f881dfd3 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/b_snewtbl.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/b_spatial.png b/php/apps/phpmyadmin49/html/themes/original/img/b_spatial.png new file mode 100644 index 00000000..6498e451 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/b_spatial.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/b_sql.png b/php/apps/phpmyadmin49/html/themes/original/img/b_sql.png new file mode 100644 index 00000000..218a64ce Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/b_sql.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/b_sqlhelp.png b/php/apps/phpmyadmin49/html/themes/original/img/b_sqlhelp.png new file mode 100644 index 00000000..3f4f59de Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/b_sqlhelp.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/b_table_add.png b/php/apps/phpmyadmin49/html/themes/original/img/b_table_add.png new file mode 100644 index 00000000..783e08bb Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/b_table_add.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/b_tblanalyse.png b/php/apps/phpmyadmin49/html/themes/original/img/b_tblanalyse.png new file mode 100644 index 00000000..6809a2d6 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/b_tblanalyse.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/b_tblexport.png b/php/apps/phpmyadmin49/html/themes/original/img/b_tblexport.png new file mode 100644 index 00000000..9886204b Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/b_tblexport.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/b_tblimport.png b/php/apps/phpmyadmin49/html/themes/original/img/b_tblimport.png new file mode 100644 index 00000000..8d0c3d9e Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/b_tblimport.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/b_tblops.png b/php/apps/phpmyadmin49/html/themes/original/img/b_tblops.png new file mode 100644 index 00000000..b71f7e0f Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/b_tblops.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/b_tbloptimize.png b/php/apps/phpmyadmin49/html/themes/original/img/b_tbloptimize.png new file mode 100644 index 00000000..0c8425ce Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/b_tbloptimize.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/b_tipp.png b/php/apps/phpmyadmin49/html/themes/original/img/b_tipp.png new file mode 100644 index 00000000..2cc774f7 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/b_tipp.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/b_trigger_add.png b/php/apps/phpmyadmin49/html/themes/original/img/b_trigger_add.png new file mode 100644 index 00000000..920a2a4f Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/b_trigger_add.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/b_triggers.png b/php/apps/phpmyadmin49/html/themes/original/img/b_triggers.png new file mode 100644 index 00000000..2ee1dc6f Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/b_triggers.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/b_undo.png b/php/apps/phpmyadmin49/html/themes/original/img/b_undo.png new file mode 100644 index 00000000..fef0fa4e Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/b_undo.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/b_unique.png b/php/apps/phpmyadmin49/html/themes/original/img/b_unique.png new file mode 100644 index 00000000..dc850775 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/b_unique.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/b_usradd.png b/php/apps/phpmyadmin49/html/themes/original/img/b_usradd.png new file mode 100644 index 00000000..5596b867 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/b_usradd.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/b_usrcheck.png b/php/apps/phpmyadmin49/html/themes/original/img/b_usrcheck.png new file mode 100644 index 00000000..dae06dab Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/b_usrcheck.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/b_usrdrop.png b/php/apps/phpmyadmin49/html/themes/original/img/b_usrdrop.png new file mode 100644 index 00000000..b407e7bf Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/b_usrdrop.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/b_usredit.png b/php/apps/phpmyadmin49/html/themes/original/img/b_usredit.png new file mode 100644 index 00000000..ed6c75a6 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/b_usredit.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/b_usrlist.png b/php/apps/phpmyadmin49/html/themes/original/img/b_usrlist.png new file mode 100644 index 00000000..0b43fa07 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/b_usrlist.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/b_versions.png b/php/apps/phpmyadmin49/html/themes/original/img/b_versions.png new file mode 100644 index 00000000..c253dc06 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/b_versions.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/b_view.png b/php/apps/phpmyadmin49/html/themes/original/img/b_view.png new file mode 100644 index 00000000..204b10d6 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/b_view.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/b_view_add.png b/php/apps/phpmyadmin49/html/themes/original/img/b_view_add.png new file mode 100644 index 00000000..2a6cfaf0 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/b_view_add.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/b_views.png b/php/apps/phpmyadmin49/html/themes/original/img/b_views.png new file mode 100644 index 00000000..a368d6a8 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/b_views.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/bd_browse.png b/php/apps/phpmyadmin49/html/themes/original/img/bd_browse.png new file mode 100644 index 00000000..f9547867 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/bd_browse.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/bd_deltbl.png b/php/apps/phpmyadmin49/html/themes/original/img/bd_deltbl.png new file mode 100644 index 00000000..b05b74bb Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/bd_deltbl.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/bd_drop.png b/php/apps/phpmyadmin49/html/themes/original/img/bd_drop.png new file mode 100644 index 00000000..4591b9da Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/bd_drop.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/bd_edit.png b/php/apps/phpmyadmin49/html/themes/original/img/bd_edit.png new file mode 100644 index 00000000..722afc17 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/bd_edit.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/bd_empty.png b/php/apps/phpmyadmin49/html/themes/original/img/bd_empty.png new file mode 100644 index 00000000..f1800d06 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/bd_empty.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/bd_export.png b/php/apps/phpmyadmin49/html/themes/original/img/bd_export.png new file mode 100644 index 00000000..bfcacf46 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/bd_export.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/bd_ftext.png b/php/apps/phpmyadmin49/html/themes/original/img/bd_ftext.png new file mode 100644 index 00000000..8e1a1153 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/bd_ftext.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/bd_index.png b/php/apps/phpmyadmin49/html/themes/original/img/bd_index.png new file mode 100644 index 00000000..b47a488b Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/bd_index.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/bd_insrow.png b/php/apps/phpmyadmin49/html/themes/original/img/bd_insrow.png new file mode 100644 index 00000000..b325ffa9 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/bd_insrow.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/bd_nextpage.png b/php/apps/phpmyadmin49/html/themes/original/img/bd_nextpage.png new file mode 100644 index 00000000..fd121254 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/bd_nextpage.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/bd_primary.png b/php/apps/phpmyadmin49/html/themes/original/img/bd_primary.png new file mode 100644 index 00000000..e1d9152a Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/bd_primary.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/bd_routine_add.png b/php/apps/phpmyadmin49/html/themes/original/img/bd_routine_add.png new file mode 100644 index 00000000..6f45cbd0 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/bd_routine_add.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/bd_sbrowse.png b/php/apps/phpmyadmin49/html/themes/original/img/bd_sbrowse.png new file mode 100644 index 00000000..c820422c Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/bd_sbrowse.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/bd_select.png b/php/apps/phpmyadmin49/html/themes/original/img/bd_select.png new file mode 100644 index 00000000..2cc94983 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/bd_select.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/bd_spatial.png b/php/apps/phpmyadmin49/html/themes/original/img/bd_spatial.png new file mode 100644 index 00000000..16f3b38c Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/bd_spatial.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/bd_unique.png b/php/apps/phpmyadmin49/html/themes/original/img/bd_unique.png new file mode 100644 index 00000000..7aa38dba Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/bd_unique.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/centralColumns.png b/php/apps/phpmyadmin49/html/themes/original/img/centralColumns.png new file mode 100644 index 00000000..4b94c9f7 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/centralColumns.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/centralColumns_add.png b/php/apps/phpmyadmin49/html/themes/original/img/centralColumns_add.png new file mode 100644 index 00000000..72825645 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/centralColumns_add.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/centralColumns_delete.png b/php/apps/phpmyadmin49/html/themes/original/img/centralColumns_delete.png new file mode 100644 index 00000000..98d6b05d Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/centralColumns_delete.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/cleardot.gif b/php/apps/phpmyadmin49/html/themes/original/img/cleardot.gif new file mode 100644 index 00000000..a9d7bea4 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/cleardot.gif differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/col_drop.png b/php/apps/phpmyadmin49/html/themes/original/img/col_drop.png new file mode 100644 index 00000000..9d84943e Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/col_drop.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/col_pointer.png b/php/apps/phpmyadmin49/html/themes/original/img/col_pointer.png new file mode 100644 index 00000000..041fbf2b Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/col_pointer.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/col_pointer_ver.png b/php/apps/phpmyadmin49/html/themes/original/img/col_pointer_ver.png new file mode 100644 index 00000000..79766f26 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/col_pointer_ver.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/console.png b/php/apps/phpmyadmin49/html/themes/original/img/console.png new file mode 100644 index 00000000..ad351da5 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/console.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/east-mini.png b/php/apps/phpmyadmin49/html/themes/original/img/east-mini.png new file mode 100644 index 00000000..6685939e Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/east-mini.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/error.ico b/php/apps/phpmyadmin49/html/themes/original/img/error.ico new file mode 100644 index 00000000..b5c06183 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/error.ico differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/eye.png b/php/apps/phpmyadmin49/html/themes/original/img/eye.png new file mode 100644 index 00000000..6c9972e0 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/eye.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/eye_grey.png b/php/apps/phpmyadmin49/html/themes/original/img/eye_grey.png new file mode 100644 index 00000000..0dc92a90 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/eye_grey.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/hide.png b/php/apps/phpmyadmin49/html/themes/original/img/hide.png new file mode 100644 index 00000000..2c914431 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/hide.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/lightbulb.png b/php/apps/phpmyadmin49/html/themes/original/img/lightbulb.png new file mode 100644 index 00000000..0cead20e Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/lightbulb.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/lightbulb_off.png b/php/apps/phpmyadmin49/html/themes/original/img/lightbulb_off.png new file mode 100644 index 00000000..dd3632d1 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/lightbulb_off.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/logo_left.png b/php/apps/phpmyadmin49/html/themes/original/img/logo_left.png new file mode 100644 index 00000000..004e7050 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/logo_left.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/logo_right.png b/php/apps/phpmyadmin49/html/themes/original/img/logo_right.png new file mode 100644 index 00000000..a4902891 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/logo_right.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/more.png b/php/apps/phpmyadmin49/html/themes/original/img/more.png new file mode 100644 index 00000000..ac803c44 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/more.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/new_data.png b/php/apps/phpmyadmin49/html/themes/original/img/new_data.png new file mode 100644 index 00000000..c173bc03 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/new_data.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/new_data_hovered.png b/php/apps/phpmyadmin49/html/themes/original/img/new_data_hovered.png new file mode 100644 index 00000000..73b09a63 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/new_data_hovered.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/new_data_selected.png b/php/apps/phpmyadmin49/html/themes/original/img/new_data_selected.png new file mode 100644 index 00000000..a75abe34 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/new_data_selected.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/new_data_selected_hovered.png b/php/apps/phpmyadmin49/html/themes/original/img/new_data_selected_hovered.png new file mode 100644 index 00000000..7091ae36 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/new_data_selected_hovered.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/new_struct.png b/php/apps/phpmyadmin49/html/themes/original/img/new_struct.png new file mode 100644 index 00000000..79fe646c Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/new_struct.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/new_struct_hovered.png b/php/apps/phpmyadmin49/html/themes/original/img/new_struct_hovered.png new file mode 100644 index 00000000..b29aaed3 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/new_struct_hovered.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/new_struct_selected.png b/php/apps/phpmyadmin49/html/themes/original/img/new_struct_selected.png new file mode 100644 index 00000000..bc61749a Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/new_struct_selected.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/new_struct_selected_hovered.png b/php/apps/phpmyadmin49/html/themes/original/img/new_struct_selected_hovered.png new file mode 100644 index 00000000..9a82bc42 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/new_struct_selected_hovered.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/normalize.png b/php/apps/phpmyadmin49/html/themes/original/img/normalize.png new file mode 100644 index 00000000..5f8e95f5 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/normalize.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/north-mini.png b/php/apps/phpmyadmin49/html/themes/original/img/north-mini.png new file mode 100644 index 00000000..c3b60620 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/north-mini.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/pause.png b/php/apps/phpmyadmin49/html/themes/original/img/pause.png new file mode 100644 index 00000000..0271217a Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/pause.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/play.png b/php/apps/phpmyadmin49/html/themes/original/img/play.png new file mode 100644 index 00000000..75a2c67c Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/play.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/s_asc.png b/php/apps/phpmyadmin49/html/themes/original/img/s_asc.png new file mode 100644 index 00000000..42ec9553 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/s_asc.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/s_asci.png b/php/apps/phpmyadmin49/html/themes/original/img/s_asci.png new file mode 100644 index 00000000..57267f43 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/s_asci.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/s_attention.png b/php/apps/phpmyadmin49/html/themes/original/img/s_attention.png new file mode 100644 index 00000000..5a536e81 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/s_attention.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/s_cancel.png b/php/apps/phpmyadmin49/html/themes/original/img/s_cancel.png new file mode 100644 index 00000000..f101a884 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/s_cancel.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/s_cog.png b/php/apps/phpmyadmin49/html/themes/original/img/s_cog.png new file mode 100644 index 00000000..5f40f737 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/s_cog.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/s_collapseall.png b/php/apps/phpmyadmin49/html/themes/original/img/s_collapseall.png new file mode 100644 index 00000000..3d56b7c5 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/s_collapseall.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/s_db.png b/php/apps/phpmyadmin49/html/themes/original/img/s_db.png new file mode 100644 index 00000000..2e35e05b Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/s_db.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/s_desc.png b/php/apps/phpmyadmin49/html/themes/original/img/s_desc.png new file mode 100644 index 00000000..cc8804c1 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/s_desc.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/s_error.png b/php/apps/phpmyadmin49/html/themes/original/img/s_error.png new file mode 100644 index 00000000..448225cf Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/s_error.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/s_fulltext.png b/php/apps/phpmyadmin49/html/themes/original/img/s_fulltext.png new file mode 100644 index 00000000..b810b0cc Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/s_fulltext.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/s_host.png b/php/apps/phpmyadmin49/html/themes/original/img/s_host.png new file mode 100644 index 00000000..4ff1ce85 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/s_host.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/s_info.png b/php/apps/phpmyadmin49/html/themes/original/img/s_info.png new file mode 100644 index 00000000..b697dddd Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/s_info.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/s_lang.png b/php/apps/phpmyadmin49/html/themes/original/img/s_lang.png new file mode 100644 index 00000000..abbb88a4 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/s_lang.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/s_link.png b/php/apps/phpmyadmin49/html/themes/original/img/s_link.png new file mode 100644 index 00000000..609970a3 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/s_link.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/s_lock.png b/php/apps/phpmyadmin49/html/themes/original/img/s_lock.png new file mode 100644 index 00000000..55258949 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/s_lock.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/s_loggoff.png b/php/apps/phpmyadmin49/html/themes/original/img/s_loggoff.png new file mode 100644 index 00000000..ac5ccde8 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/s_loggoff.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/s_notice.png b/php/apps/phpmyadmin49/html/themes/original/img/s_notice.png new file mode 100644 index 00000000..64d1722e Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/s_notice.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/s_okay.png b/php/apps/phpmyadmin49/html/themes/original/img/s_okay.png new file mode 100644 index 00000000..9bf92354 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/s_okay.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/s_partialtext.png b/php/apps/phpmyadmin49/html/themes/original/img/s_partialtext.png new file mode 100644 index 00000000..a8fbb343 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/s_partialtext.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/s_passwd.png b/php/apps/phpmyadmin49/html/themes/original/img/s_passwd.png new file mode 100644 index 00000000..c705bd55 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/s_passwd.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/s_really.png b/php/apps/phpmyadmin49/html/themes/original/img/s_really.png new file mode 100644 index 00000000..e48c9cd4 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/s_really.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/s_reload.png b/php/apps/phpmyadmin49/html/themes/original/img/s_reload.png new file mode 100644 index 00000000..cc0b9e55 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/s_reload.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/s_replication.png b/php/apps/phpmyadmin49/html/themes/original/img/s_replication.png new file mode 100644 index 00000000..c5c5a5c5 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/s_replication.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/s_rights.png b/php/apps/phpmyadmin49/html/themes/original/img/s_rights.png new file mode 100644 index 00000000..bae32e79 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/s_rights.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/s_sortable.png b/php/apps/phpmyadmin49/html/themes/original/img/s_sortable.png new file mode 100644 index 00000000..c64bd358 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/s_sortable.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/s_status.png b/php/apps/phpmyadmin49/html/themes/original/img/s_status.png new file mode 100644 index 00000000..d53c1ebe Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/s_status.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/s_success.png b/php/apps/phpmyadmin49/html/themes/original/img/s_success.png new file mode 100644 index 00000000..fd2cee9d Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/s_success.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/s_sync.png b/php/apps/phpmyadmin49/html/themes/original/img/s_sync.png new file mode 100644 index 00000000..5ced64c5 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/s_sync.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/s_tbl.png b/php/apps/phpmyadmin49/html/themes/original/img/s_tbl.png new file mode 100644 index 00000000..a3782a90 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/s_tbl.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/s_theme.png b/php/apps/phpmyadmin49/html/themes/original/img/s_theme.png new file mode 100644 index 00000000..156cd435 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/s_theme.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/s_top.png b/php/apps/phpmyadmin49/html/themes/original/img/s_top.png new file mode 100644 index 00000000..dd57623d Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/s_top.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/s_unlink.png b/php/apps/phpmyadmin49/html/themes/original/img/s_unlink.png new file mode 100644 index 00000000..551679e9 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/s_unlink.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/s_vars.png b/php/apps/phpmyadmin49/html/themes/original/img/s_vars.png new file mode 100644 index 00000000..b7ff721b Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/s_vars.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/s_views.png b/php/apps/phpmyadmin49/html/themes/original/img/s_views.png new file mode 100644 index 00000000..568d3c3e Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/s_views.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/show.png b/php/apps/phpmyadmin49/html/themes/original/img/show.png new file mode 100644 index 00000000..666653f6 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/show.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/south-mini.png b/php/apps/phpmyadmin49/html/themes/original/img/south-mini.png new file mode 100644 index 00000000..654673b3 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/south-mini.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/spacer.png b/php/apps/phpmyadmin49/html/themes/original/img/spacer.png new file mode 100644 index 00000000..240ca4f8 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/spacer.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/toggle-ltr.png b/php/apps/phpmyadmin49/html/themes/original/img/toggle-ltr.png new file mode 100644 index 00000000..2ea417f4 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/toggle-ltr.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/toggle-rtl.png b/php/apps/phpmyadmin49/html/themes/original/img/toggle-rtl.png new file mode 100644 index 00000000..399b06dc Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/toggle-rtl.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/vertical_line.png b/php/apps/phpmyadmin49/html/themes/original/img/vertical_line.png new file mode 100644 index 00000000..a88a7c7b Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/vertical_line.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/west-mini.png b/php/apps/phpmyadmin49/html/themes/original/img/west-mini.png new file mode 100644 index 00000000..61ad92e0 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/west-mini.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/window-new.png b/php/apps/phpmyadmin49/html/themes/original/img/window-new.png new file mode 100644 index 00000000..d18722fd Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/window-new.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/zoom-minus-mini.png b/php/apps/phpmyadmin49/html/themes/original/img/zoom-minus-mini.png new file mode 100644 index 00000000..1b8d84dc Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/zoom-minus-mini.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/zoom-plus-mini.png b/php/apps/phpmyadmin49/html/themes/original/img/zoom-plus-mini.png new file mode 100644 index 00000000..466cc7bc Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/zoom-plus-mini.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/img/zoom-world-mini.png b/php/apps/phpmyadmin49/html/themes/original/img/zoom-world-mini.png new file mode 100644 index 00000000..dcc60f15 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/img/zoom-world-mini.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/jquery/images/ui-bg_glass_55_fbf9ee_1x400.png b/php/apps/phpmyadmin49/html/themes/original/jquery/images/ui-bg_glass_55_fbf9ee_1x400.png new file mode 100644 index 00000000..534c590f Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/jquery/images/ui-bg_glass_55_fbf9ee_1x400.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/jquery/images/ui-bg_glass_65_ffffff_1x400.png b/php/apps/phpmyadmin49/html/themes/original/jquery/images/ui-bg_glass_65_ffffff_1x400.png new file mode 100644 index 00000000..3e56dbdc Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/jquery/images/ui-bg_glass_65_ffffff_1x400.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/jquery/images/ui-bg_glass_75_dadada_1x400.png b/php/apps/phpmyadmin49/html/themes/original/jquery/images/ui-bg_glass_75_dadada_1x400.png new file mode 100644 index 00000000..6b8b33a1 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/jquery/images/ui-bg_glass_75_dadada_1x400.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/jquery/images/ui-bg_glass_75_e6e6e6_1x400.png b/php/apps/phpmyadmin49/html/themes/original/jquery/images/ui-bg_glass_75_e6e6e6_1x400.png new file mode 100644 index 00000000..81e2065b Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/jquery/images/ui-bg_glass_75_e6e6e6_1x400.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/jquery/images/ui-bg_glass_95_fef1ec_1x400.png b/php/apps/phpmyadmin49/html/themes/original/jquery/images/ui-bg_glass_95_fef1ec_1x400.png new file mode 100644 index 00000000..7172755b Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/jquery/images/ui-bg_glass_95_fef1ec_1x400.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/jquery/images/ui-bg_highlight-soft_75_cccccc_1x100.png b/php/apps/phpmyadmin49/html/themes/original/jquery/images/ui-bg_highlight-soft_75_cccccc_1x100.png new file mode 100644 index 00000000..ae3ccae0 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/jquery/images/ui-bg_highlight-soft_75_cccccc_1x100.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/jquery/images/ui-icons_222222_256x240.png b/php/apps/phpmyadmin49/html/themes/original/jquery/images/ui-icons_222222_256x240.png new file mode 100644 index 00000000..3201d9a1 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/jquery/images/ui-icons_222222_256x240.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/jquery/images/ui-icons_2e83ff_256x240.png b/php/apps/phpmyadmin49/html/themes/original/jquery/images/ui-icons_2e83ff_256x240.png new file mode 100644 index 00000000..1d920d7d Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/jquery/images/ui-icons_2e83ff_256x240.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/jquery/images/ui-icons_454545_256x240.png b/php/apps/phpmyadmin49/html/themes/original/jquery/images/ui-icons_454545_256x240.png new file mode 100644 index 00000000..47da8e5d Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/jquery/images/ui-icons_454545_256x240.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/jquery/images/ui-icons_888888_256x240.png b/php/apps/phpmyadmin49/html/themes/original/jquery/images/ui-icons_888888_256x240.png new file mode 100644 index 00000000..95e0c3ef Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/jquery/images/ui-icons_888888_256x240.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/jquery/images/ui-icons_cd0a0a_256x240.png b/php/apps/phpmyadmin49/html/themes/original/jquery/images/ui-icons_cd0a0a_256x240.png new file mode 100644 index 00000000..0ea8a5a2 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/original/jquery/images/ui-icons_cd0a0a_256x240.png differ diff --git a/php/apps/phpmyadmin49/html/themes/original/jquery/jquery-ui.css b/php/apps/phpmyadmin49/html/themes/original/jquery/jquery-ui.css new file mode 100644 index 00000000..1ebcbc90 --- /dev/null +++ b/php/apps/phpmyadmin49/html/themes/original/jquery/jquery-ui.css @@ -0,0 +1,1312 @@ +/*! jQuery UI - v1.12.1 - 2016-12-21 +* http://jqueryui.com +* Includes: draggable.css, core.css, resizable.css, selectable.css, sortable.css, accordion.css, autocomplete.css, menu.css, button.css, controlgroup.css, checkboxradio.css, datepicker.css, dialog.css, progressbar.css, selectmenu.css, slider.css, spinner.css, tabs.css, tooltip.css, theme.css +* To view and modify this theme, visit http://jqueryui.com/themeroller/?scope=&folderName=smoothness&cornerRadiusShadow=8px&offsetLeftShadow=-8px&offsetTopShadow=-8px&thicknessShadow=8px&opacityShadow=30&bgImgOpacityShadow=0&bgTextureShadow=flat&bgColorShadow=aaaaaa&opacityOverlay=30&bgImgOpacityOverlay=0&bgTextureOverlay=flat&bgColorOverlay=aaaaaa&iconColorError=cd0a0a&fcError=cd0a0a&borderColorError=cd0a0a&bgImgOpacityError=95&bgTextureError=glass&bgColorError=fef1ec&iconColorHighlight=2e83ff&fcHighlight=363636&borderColorHighlight=fcefa1&bgImgOpacityHighlight=55&bgTextureHighlight=glass&bgColorHighlight=fbf9ee&iconColorActive=454545&fcActive=212121&borderColorActive=aaaaaa&bgImgOpacityActive=65&bgTextureActive=glass&bgColorActive=ffffff&iconColorHover=454545&fcHover=212121&borderColorHover=999999&bgImgOpacityHover=75&bgTextureHover=glass&bgColorHover=dadada&iconColorDefault=888888&fcDefault=555555&borderColorDefault=d3d3d3&bgImgOpacityDefault=75&bgTextureDefault=glass&bgColorDefault=e6e6e6&iconColorContent=222222&fcContent=222222&borderColorContent=aaaaaa&bgImgOpacityContent=75&bgTextureContent=flat&bgColorContent=ffffff&iconColorHeader=222222&fcHeader=222222&borderColorHeader=aaaaaa&bgImgOpacityHeader=75&bgTextureHeader=highlight_soft&bgColorHeader=cccccc&cornerRadius=4px&fsDefault=1.1em&fwDefault=normal&ffDefault=Verdana%2CArial%2Csans-serif +* Copyright jQuery Foundation and other contributors; Licensed MIT */ + +.ui-draggable-handle { + -ms-touch-action: none; + touch-action: none; +} +/* Layout helpers +----------------------------------*/ +.ui-helper-hidden { + display: none; +} +.ui-helper-hidden-accessible { + border: 0; + clip: rect(0 0 0 0); + height: 1px; + margin: -1px; + overflow: hidden; + padding: 0; + position: absolute; + width: 1px; +} +.ui-helper-reset { + margin: 0; + padding: 0; + border: 0; + outline: 0; + line-height: 1.3; + text-decoration: none; + font-size: 100%; + list-style: none; +} +.ui-helper-clearfix:before, +.ui-helper-clearfix:after { + content: ""; + display: table; + border-collapse: collapse; +} +.ui-helper-clearfix:after { + clear: both; +} +.ui-helper-zfix { + width: 100%; + height: 100%; + top: 0; + left: 0; + position: absolute; + opacity: 0; + filter:Alpha(Opacity=0); /* support: IE8 */ +} + +.ui-front { + z-index: 100; +} + + +/* Interaction Cues +----------------------------------*/ +.ui-state-disabled { + cursor: default !important; + pointer-events: none; +} + + +/* Icons +----------------------------------*/ +.ui-icon { + display: inline-block; + vertical-align: middle; + margin-top: -.25em; + position: relative; + text-indent: -99999px; + overflow: hidden; + background-repeat: no-repeat; +} + +.ui-widget-icon-block { + left: 50%; + margin-left: -8px; + display: block; +} + +/* Misc visuals +----------------------------------*/ + +/* Overlays */ +.ui-widget-overlay { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; +} +.ui-resizable { + position: relative; +} +.ui-resizable-handle { + position: absolute; + font-size: 0.1px; + display: block; + -ms-touch-action: none; + touch-action: none; +} +.ui-resizable-disabled .ui-resizable-handle, +.ui-resizable-autohide .ui-resizable-handle { + display: none; +} +.ui-resizable-n { + cursor: n-resize; + height: 7px; + width: 100%; + top: -5px; + left: 0; +} +.ui-resizable-s { + cursor: s-resize; + height: 7px; + width: 100%; + bottom: -5px; + left: 0; +} +.ui-resizable-e { + cursor: e-resize; + width: 7px; + right: -5px; + top: 0; + height: 100%; +} +.ui-resizable-w { + cursor: w-resize; + width: 7px; + left: -5px; + top: 0; + height: 100%; +} +.ui-resizable-se { + cursor: se-resize; + width: 12px; + height: 12px; + right: 1px; + bottom: 1px; +} +.ui-resizable-sw { + cursor: sw-resize; + width: 9px; + height: 9px; + left: -5px; + bottom: -5px; +} +.ui-resizable-nw { + cursor: nw-resize; + width: 9px; + height: 9px; + left: -5px; + top: -5px; +} +.ui-resizable-ne { + cursor: ne-resize; + width: 9px; + height: 9px; + right: -5px; + top: -5px; +} +.ui-selectable { + -ms-touch-action: none; + touch-action: none; +} +.ui-selectable-helper { + position: absolute; + z-index: 100; + border: 1px dotted black; +} +.ui-sortable-handle { + -ms-touch-action: none; + touch-action: none; +} +.ui-accordion .ui-accordion-header { + display: block; + cursor: pointer; + position: relative; + margin: 2px 0 0 0; + padding: .5em .5em .5em .7em; + font-size: 100%; +} +.ui-accordion .ui-accordion-content { + padding: 1em 2.2em; + border-top: 0; + overflow: auto; +} +.ui-autocomplete { + position: absolute; + top: 0; + left: 0; + cursor: default; +} +.ui-menu { + list-style: none; + padding: 0; + margin: 0; + display: block; + outline: 0; +} +.ui-menu .ui-menu { + position: absolute; +} +.ui-menu .ui-menu-item { + margin: 0; + cursor: pointer; + /* support: IE10, see #8844 */ + list-style-image: url("data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7"); +} +.ui-menu .ui-menu-item-wrapper { + position: relative; + padding: 3px 1em 3px .4em; +} +.ui-menu .ui-menu-divider { + margin: 5px 0; + height: 0; + font-size: 0; + line-height: 0; + border-width: 1px 0 0 0; +} +.ui-menu .ui-state-focus, +.ui-menu .ui-state-active { + margin: -1px; +} + +/* icon support */ +.ui-menu-icons { + position: relative; +} +.ui-menu-icons .ui-menu-item-wrapper { + padding-left: 2em; +} + +/* left-aligned */ +.ui-menu .ui-icon { + position: absolute; + top: 0; + bottom: 0; + left: .2em; + margin: auto 0; +} + +/* right-aligned */ +.ui-menu .ui-menu-icon { + left: auto; + right: 0; +} +.ui-button { + padding: .4em 1em; + display: inline-block; + position: relative; + line-height: normal; + margin-right: .1em; + cursor: pointer; + vertical-align: middle; + text-align: center; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + + /* Support: IE <= 11 */ + overflow: visible; +} + +.ui-button, +.ui-button:link, +.ui-button:visited, +.ui-button:hover, +.ui-button:active { + text-decoration: none; +} + +/* to make room for the icon, a width needs to be set here */ +.ui-button-icon-only { + width: 2em; + box-sizing: border-box; + text-indent: -9999px; + white-space: nowrap; +} + +/* no icon support for input elements */ +input.ui-button.ui-button-icon-only { + text-indent: 0; +} + +/* button icon element(s) */ +.ui-button-icon-only .ui-icon { + position: absolute; + top: 50%; + left: 50%; + margin-top: -8px; + margin-left: -8px; +} + +.ui-button.ui-icon-notext .ui-icon { + padding: 0; + width: 2.1em; + height: 2.1em; + text-indent: -9999px; + white-space: nowrap; + +} + +input.ui-button.ui-icon-notext .ui-icon { + width: auto; + height: auto; + text-indent: 0; + white-space: normal; + padding: .4em 1em; +} + +/* workarounds */ +/* Support: Firefox 5 - 40 */ +input.ui-button::-moz-focus-inner, +button.ui-button::-moz-focus-inner { + border: 0; + padding: 0; +} +.ui-controlgroup { + vertical-align: middle; + display: inline-block; +} +.ui-controlgroup > .ui-controlgroup-item { + float: left; + margin-left: 0; + margin-right: 0; +} +.ui-controlgroup > .ui-controlgroup-item:focus, +.ui-controlgroup > .ui-controlgroup-item.ui-visual-focus { + z-index: 9999; +} +.ui-controlgroup-vertical > .ui-controlgroup-item { + display: block; + float: none; + width: 100%; + margin-top: 0; + margin-bottom: 0; + text-align: left; +} +.ui-controlgroup-vertical .ui-controlgroup-item { + box-sizing: border-box; +} +.ui-controlgroup .ui-controlgroup-label { + padding: .4em 1em; +} +.ui-controlgroup .ui-controlgroup-label span { + font-size: 80%; +} +.ui-controlgroup-horizontal .ui-controlgroup-label + .ui-controlgroup-item { + border-left: none; +} +.ui-controlgroup-vertical .ui-controlgroup-label + .ui-controlgroup-item { + border-top: none; +} +.ui-controlgroup-horizontal .ui-controlgroup-label.ui-widget-content { + border-right: none; +} +.ui-controlgroup-vertical .ui-controlgroup-label.ui-widget-content { + border-bottom: none; +} + +/* Spinner specific style fixes */ +.ui-controlgroup-vertical .ui-spinner-input { + + /* Support: IE8 only, Android < 4.4 only */ + width: 75%; + width: calc( 100% - 2.4em ); +} +.ui-controlgroup-vertical .ui-spinner .ui-spinner-up { + border-top-style: solid; +} + +.ui-checkboxradio-label .ui-icon-background { + box-shadow: inset 1px 1px 1px #ccc; + border-radius: .12em; + border: none; +} +.ui-checkboxradio-radio-label .ui-icon-background { + width: 16px; + height: 16px; + border-radius: 1em; + overflow: visible; + border: none; +} +.ui-checkboxradio-radio-label.ui-checkboxradio-checked .ui-icon, +.ui-checkboxradio-radio-label.ui-checkboxradio-checked:hover .ui-icon { + background-image: none; + width: 8px; + height: 8px; + border-width: 4px; + border-style: solid; +} +.ui-checkboxradio-disabled { + pointer-events: none; +} +.ui-datepicker { + width: 17em; + padding: .2em .2em 0; + display: none; +} +.ui-datepicker .ui-datepicker-header { + position: relative; + padding: .2em 0; +} +.ui-datepicker .ui-datepicker-prev, +.ui-datepicker .ui-datepicker-next { + position: absolute; + top: 2px; + width: 1.8em; + height: 1.8em; +} +.ui-datepicker .ui-datepicker-prev-hover, +.ui-datepicker .ui-datepicker-next-hover { + top: 1px; +} +.ui-datepicker .ui-datepicker-prev { + left: 2px; +} +.ui-datepicker .ui-datepicker-next { + right: 2px; +} +.ui-datepicker .ui-datepicker-prev-hover { + left: 1px; +} +.ui-datepicker .ui-datepicker-next-hover { + right: 1px; +} +.ui-datepicker .ui-datepicker-prev span, +.ui-datepicker .ui-datepicker-next span { + display: block; + position: absolute; + left: 50%; + margin-left: -8px; + top: 50%; + margin-top: -8px; +} +.ui-datepicker .ui-datepicker-title { + margin: 0 2.3em; + line-height: 1.8em; + text-align: center; +} +.ui-datepicker .ui-datepicker-title select { + font-size: 1em; + margin: 1px 0; +} +.ui-datepicker select.ui-datepicker-month, +.ui-datepicker select.ui-datepicker-year { + width: 45%; +} +.ui-datepicker table { + width: 100%; + font-size: .9em; + border-collapse: collapse; + margin: 0 0 .4em; +} +.ui-datepicker th { + padding: .7em .3em; + text-align: center; + font-weight: bold; + border: 0; +} +.ui-datepicker td { + border: 0; + padding: 1px; +} +.ui-datepicker td span, +.ui-datepicker td a { + display: block; + padding: .2em; + text-align: right; + text-decoration: none; +} +.ui-datepicker .ui-datepicker-buttonpane { + background-image: none; + margin: .7em 0 0 0; + padding: 0 .2em; + border-left: 0; + border-right: 0; + border-bottom: 0; +} +.ui-datepicker .ui-datepicker-buttonpane button { + float: right; + margin: .5em .2em .4em; + cursor: pointer; + padding: .2em .6em .3em .6em; + width: auto; + overflow: visible; +} +.ui-datepicker .ui-datepicker-buttonpane button.ui-datepicker-current { + float: left; +} + +/* with multiple calendars */ +.ui-datepicker.ui-datepicker-multi { + width: auto; +} +.ui-datepicker-multi .ui-datepicker-group { + float: left; +} +.ui-datepicker-multi .ui-datepicker-group table { + width: 95%; + margin: 0 auto .4em; +} +.ui-datepicker-multi-2 .ui-datepicker-group { + width: 50%; +} +.ui-datepicker-multi-3 .ui-datepicker-group { + width: 33.3%; +} +.ui-datepicker-multi-4 .ui-datepicker-group { + width: 25%; +} +.ui-datepicker-multi .ui-datepicker-group-last .ui-datepicker-header, +.ui-datepicker-multi .ui-datepicker-group-middle .ui-datepicker-header { + border-left-width: 0; +} +.ui-datepicker-multi .ui-datepicker-buttonpane { + clear: left; +} +.ui-datepicker-row-break { + clear: both; + width: 100%; + font-size: 0; +} + +/* RTL support */ +.ui-datepicker-rtl { + direction: rtl; +} +.ui-datepicker-rtl .ui-datepicker-prev { + right: 2px; + left: auto; +} +.ui-datepicker-rtl .ui-datepicker-next { + left: 2px; + right: auto; +} +.ui-datepicker-rtl .ui-datepicker-prev:hover { + right: 1px; + left: auto; +} +.ui-datepicker-rtl .ui-datepicker-next:hover { + left: 1px; + right: auto; +} +.ui-datepicker-rtl .ui-datepicker-buttonpane { + clear: right; +} +.ui-datepicker-rtl .ui-datepicker-buttonpane button { + float: left; +} +.ui-datepicker-rtl .ui-datepicker-buttonpane button.ui-datepicker-current, +.ui-datepicker-rtl .ui-datepicker-group { + float: right; +} +.ui-datepicker-rtl .ui-datepicker-group-last .ui-datepicker-header, +.ui-datepicker-rtl .ui-datepicker-group-middle .ui-datepicker-header { + border-right-width: 0; + border-left-width: 1px; +} + +/* Icons */ +.ui-datepicker .ui-icon { + display: block; + text-indent: -99999px; + overflow: hidden; + background-repeat: no-repeat; + left: .5em; + top: .3em; +} +.ui-dialog { + position: absolute; + top: 0; + left: 0; + padding: .2em; + outline: 0; +} +.ui-dialog .ui-dialog-titlebar { + padding: .4em 1em; + position: relative; +} +.ui-dialog .ui-dialog-title { + float: left; + margin: .1em 0; + white-space: nowrap; + width: 90%; + overflow: hidden; + text-overflow: ellipsis; +} +.ui-dialog .ui-dialog-titlebar-close { + position: absolute; + right: .3em; + top: 50%; + width: 20px; + margin: -10px 0 0 0; + padding: 1px; + height: 20px; +} +.ui-dialog .ui-dialog-content { + position: relative; + border: 0; + padding: .5em 1em; + background: none; + overflow: auto; +} +.ui-dialog .ui-dialog-buttonpane { + text-align: left; + border-width: 1px 0 0 0; + background-image: none; + margin-top: .5em; + padding: .3em 1em .5em .4em; +} +.ui-dialog .ui-dialog-buttonpane .ui-dialog-buttonset { + float: right; +} +.ui-dialog .ui-dialog-buttonpane button { + margin: .5em .4em .5em 0; + cursor: pointer; +} +.ui-dialog .ui-resizable-n { + height: 2px; + top: 0; +} +.ui-dialog .ui-resizable-e { + width: 2px; + right: 0; +} +.ui-dialog .ui-resizable-s { + height: 2px; + bottom: 0; +} +.ui-dialog .ui-resizable-w { + width: 2px; + left: 0; +} +.ui-dialog .ui-resizable-se, +.ui-dialog .ui-resizable-sw, +.ui-dialog .ui-resizable-ne, +.ui-dialog .ui-resizable-nw { + width: 7px; + height: 7px; +} +.ui-dialog .ui-resizable-se { + right: 0; + bottom: 0; +} +.ui-dialog .ui-resizable-sw { + left: 0; + bottom: 0; +} +.ui-dialog .ui-resizable-ne { + right: 0; + top: 0; +} +.ui-dialog .ui-resizable-nw { + left: 0; + top: 0; +} +.ui-draggable .ui-dialog-titlebar { + cursor: move; +} +.ui-progressbar { + height: 2em; + text-align: left; + overflow: hidden; +} +.ui-progressbar .ui-progressbar-value { + margin: -1px; + height: 100%; +} +.ui-progressbar .ui-progressbar-overlay { + background: url("data:image/gif;base64,R0lGODlhKAAoAIABAAAAAP///yH/C05FVFNDQVBFMi4wAwEAAAAh+QQJAQABACwAAAAAKAAoAAACkYwNqXrdC52DS06a7MFZI+4FHBCKoDeWKXqymPqGqxvJrXZbMx7Ttc+w9XgU2FB3lOyQRWET2IFGiU9m1frDVpxZZc6bfHwv4c1YXP6k1Vdy292Fb6UkuvFtXpvWSzA+HycXJHUXiGYIiMg2R6W459gnWGfHNdjIqDWVqemH2ekpObkpOlppWUqZiqr6edqqWQAAIfkECQEAAQAsAAAAACgAKAAAApSMgZnGfaqcg1E2uuzDmmHUBR8Qil95hiPKqWn3aqtLsS18y7G1SzNeowWBENtQd+T1JktP05nzPTdJZlR6vUxNWWjV+vUWhWNkWFwxl9VpZRedYcflIOLafaa28XdsH/ynlcc1uPVDZxQIR0K25+cICCmoqCe5mGhZOfeYSUh5yJcJyrkZWWpaR8doJ2o4NYq62lAAACH5BAkBAAEALAAAAAAoACgAAAKVDI4Yy22ZnINRNqosw0Bv7i1gyHUkFj7oSaWlu3ovC8GxNso5fluz3qLVhBVeT/Lz7ZTHyxL5dDalQWPVOsQWtRnuwXaFTj9jVVh8pma9JjZ4zYSj5ZOyma7uuolffh+IR5aW97cHuBUXKGKXlKjn+DiHWMcYJah4N0lYCMlJOXipGRr5qdgoSTrqWSq6WFl2ypoaUAAAIfkECQEAAQAsAAAAACgAKAAAApaEb6HLgd/iO7FNWtcFWe+ufODGjRfoiJ2akShbueb0wtI50zm02pbvwfWEMWBQ1zKGlLIhskiEPm9R6vRXxV4ZzWT2yHOGpWMyorblKlNp8HmHEb/lCXjcW7bmtXP8Xt229OVWR1fod2eWqNfHuMjXCPkIGNileOiImVmCOEmoSfn3yXlJWmoHGhqp6ilYuWYpmTqKUgAAIfkECQEAAQAsAAAAACgAKAAAApiEH6kb58biQ3FNWtMFWW3eNVcojuFGfqnZqSebuS06w5V80/X02pKe8zFwP6EFWOT1lDFk8rGERh1TTNOocQ61Hm4Xm2VexUHpzjymViHrFbiELsefVrn6XKfnt2Q9G/+Xdie499XHd2g4h7ioOGhXGJboGAnXSBnoBwKYyfioubZJ2Hn0RuRZaflZOil56Zp6iioKSXpUAAAh+QQJAQABACwAAAAAKAAoAAACkoQRqRvnxuI7kU1a1UU5bd5tnSeOZXhmn5lWK3qNTWvRdQxP8qvaC+/yaYQzXO7BMvaUEmJRd3TsiMAgswmNYrSgZdYrTX6tSHGZO73ezuAw2uxuQ+BbeZfMxsexY35+/Qe4J1inV0g4x3WHuMhIl2jXOKT2Q+VU5fgoSUI52VfZyfkJGkha6jmY+aaYdirq+lQAACH5BAkBAAEALAAAAAAoACgAAAKWBIKpYe0L3YNKToqswUlvznigd4wiR4KhZrKt9Upqip61i9E3vMvxRdHlbEFiEXfk9YARYxOZZD6VQ2pUunBmtRXo1Lf8hMVVcNl8JafV38aM2/Fu5V16Bn63r6xt97j09+MXSFi4BniGFae3hzbH9+hYBzkpuUh5aZmHuanZOZgIuvbGiNeomCnaxxap2upaCZsq+1kAACH5BAkBAAEALAAAAAAoACgAAAKXjI8By5zf4kOxTVrXNVlv1X0d8IGZGKLnNpYtm8Lr9cqVeuOSvfOW79D9aDHizNhDJidFZhNydEahOaDH6nomtJjp1tutKoNWkvA6JqfRVLHU/QUfau9l2x7G54d1fl995xcIGAdXqMfBNadoYrhH+Mg2KBlpVpbluCiXmMnZ2Sh4GBqJ+ckIOqqJ6LmKSllZmsoq6wpQAAAh+QQJAQABACwAAAAAKAAoAAAClYx/oLvoxuJDkU1a1YUZbJ59nSd2ZXhWqbRa2/gF8Gu2DY3iqs7yrq+xBYEkYvFSM8aSSObE+ZgRl1BHFZNr7pRCavZ5BW2142hY3AN/zWtsmf12p9XxxFl2lpLn1rseztfXZjdIWIf2s5dItwjYKBgo9yg5pHgzJXTEeGlZuenpyPmpGQoKOWkYmSpaSnqKileI2FAAACH5BAkBAAEALAAAAAAoACgAAAKVjB+gu+jG4kORTVrVhRlsnn2dJ3ZleFaptFrb+CXmO9OozeL5VfP99HvAWhpiUdcwkpBH3825AwYdU8xTqlLGhtCosArKMpvfa1mMRae9VvWZfeB2XfPkeLmm18lUcBj+p5dnN8jXZ3YIGEhYuOUn45aoCDkp16hl5IjYJvjWKcnoGQpqyPlpOhr3aElaqrq56Bq7VAAAOw=="); + height: 100%; + filter: alpha(opacity=25); /* support: IE8 */ + opacity: 0.25; +} +.ui-progressbar-indeterminate .ui-progressbar-value { + background-image: none; +} +.ui-selectmenu-menu { + padding: 0; + margin: 0; + position: absolute; + top: 0; + left: 0; + display: none; +} +.ui-selectmenu-menu .ui-menu { + overflow: auto; + overflow-x: hidden; + padding-bottom: 1px; +} +.ui-selectmenu-menu .ui-menu .ui-selectmenu-optgroup { + font-size: 1em; + font-weight: bold; + line-height: 1.5; + padding: 2px 0.4em; + margin: 0.5em 0 0 0; + height: auto; + border: 0; +} +.ui-selectmenu-open { + display: block; +} +.ui-selectmenu-text { + display: block; + margin-right: 20px; + overflow: hidden; + text-overflow: ellipsis; +} +.ui-selectmenu-button.ui-button { + text-align: left; + white-space: nowrap; + width: 14em; +} +.ui-selectmenu-icon.ui-icon { + float: right; + margin-top: 0; +} +.ui-slider { + position: relative; + text-align: left; +} +.ui-slider .ui-slider-handle { + position: absolute; + z-index: 2; + width: 1.2em; + height: 1.2em; + cursor: default; + -ms-touch-action: none; + touch-action: none; +} +.ui-slider .ui-slider-range { + position: absolute; + z-index: 1; + font-size: .7em; + display: block; + border: 0; + background-position: 0 0; +} + +/* support: IE8 - See #6727 */ +.ui-slider.ui-state-disabled .ui-slider-handle, +.ui-slider.ui-state-disabled .ui-slider-range { + filter: inherit; +} + +.ui-slider-horizontal { + height: .8em; +} +.ui-slider-horizontal .ui-slider-handle { + top: -.3em; + margin-left: -.6em; +} +.ui-slider-horizontal .ui-slider-range { + top: 0; + height: 100%; +} +.ui-slider-horizontal .ui-slider-range-min { + left: 0; +} +.ui-slider-horizontal .ui-slider-range-max { + right: 0; +} + +.ui-slider-vertical { + width: .8em; + height: 100px; +} +.ui-slider-vertical .ui-slider-handle { + left: -.3em; + margin-left: 0; + margin-bottom: -.6em; +} +.ui-slider-vertical .ui-slider-range { + left: 0; + width: 100%; +} +.ui-slider-vertical .ui-slider-range-min { + bottom: 0; +} +.ui-slider-vertical .ui-slider-range-max { + top: 0; +} +.ui-spinner { + position: relative; + display: inline-block; + overflow: hidden; + padding: 0; + vertical-align: middle; +} +.ui-spinner-input { + border: none; + background: none; + color: inherit; + padding: .222em 0; + margin: .2em 0; + vertical-align: middle; + margin-left: .4em; + margin-right: 2em; +} +.ui-spinner-button { + width: 1.6em; + height: 50%; + font-size: .5em; + padding: 0; + margin: 0; + text-align: center; + position: absolute; + cursor: default; + display: block; + overflow: hidden; + right: 0; +} +/* more specificity required here to override default borders */ +.ui-spinner a.ui-spinner-button { + border-top-style: none; + border-bottom-style: none; + border-right-style: none; +} +.ui-spinner-up { + top: 0; +} +.ui-spinner-down { + bottom: 0; +} +.ui-tabs { + position: relative;/* position: relative prevents IE scroll bug (element with position: relative inside container with overflow: auto appear as "fixed") */ + padding: .2em; +} +.ui-tabs .ui-tabs-nav { + margin: 0; + padding: .2em .2em 0; +} +.ui-tabs .ui-tabs-nav li { + list-style: none; + float: left; + position: relative; + top: 0; + margin: 1px .2em 0 0; + border-bottom-width: 0; + padding: 0; + white-space: nowrap; +} +.ui-tabs .ui-tabs-nav .ui-tabs-anchor { + float: left; + padding: .5em 1em; + text-decoration: none; +} +.ui-tabs .ui-tabs-nav li.ui-tabs-active { + margin-bottom: -1px; + padding-bottom: 1px; +} +.ui-tabs .ui-tabs-nav li.ui-tabs-active .ui-tabs-anchor, +.ui-tabs .ui-tabs-nav li.ui-state-disabled .ui-tabs-anchor, +.ui-tabs .ui-tabs-nav li.ui-tabs-loading .ui-tabs-anchor { + cursor: text; +} +.ui-tabs-collapsible .ui-tabs-nav li.ui-tabs-active .ui-tabs-anchor { + cursor: pointer; +} +.ui-tabs .ui-tabs-panel { + display: block; + border-width: 0; + padding: 1em 1.4em; + background: none; +} +.ui-tooltip { + padding: 8px; + position: absolute; + z-index: 9999; + max-width: 300px; +} +body .ui-tooltip { + border-width: 2px; +} + +/* Component containers +----------------------------------*/ +.ui-widget { + font-family: Verdana,Arial,sans-serif; + font-size: 1.1em; +} +.ui-widget .ui-widget { + font-size: 1em; +} +.ui-widget input, +.ui-widget select, +.ui-widget textarea, +.ui-widget button { + font-family: Verdana,Arial,sans-serif; + font-size: 1em; +} +.ui-widget.ui-widget-content { + border: 1px solid #d3d3d3; +} +.ui-widget-content { + border: 1px solid #aaaaaa; + background: #ffffff; + color: #222222; +} +.ui-widget-content a { + color: #222222; +} +.ui-widget-header { + border: 1px solid #aaaaaa; + background: #cccccc url("images/ui-bg_highlight-soft_75_cccccc_1x100.png") 50% 50% repeat-x; + color: #222222; + font-weight: bold; +} +.ui-widget-header a { + color: #222222; +} + +/* Interaction states +----------------------------------*/ +.ui-state-default, +.ui-widget-content .ui-state-default, +.ui-widget-header .ui-state-default, +.ui-button, + +/* We use html here because we need a greater specificity to make sure disabled +works properly when clicked or hovered */ +html .ui-button.ui-state-disabled:hover, +html .ui-button.ui-state-disabled:active { + border: 1px solid #d3d3d3; + background: #e6e6e6 url("images/ui-bg_glass_75_e6e6e6_1x400.png") 50% 50% repeat-x; + font-weight: normal; + color: #555555; +} +.ui-state-default a, +.ui-state-default a:link, +.ui-state-default a:visited, +a.ui-button, +a:link.ui-button, +a:visited.ui-button, +.ui-button { + color: #555555; + text-decoration: none; +} +.ui-state-hover, +.ui-widget-content .ui-state-hover, +.ui-widget-header .ui-state-hover, +.ui-state-focus, +.ui-widget-content .ui-state-focus, +.ui-widget-header .ui-state-focus, +.ui-button:hover, +.ui-button:focus { + border: 1px solid #999999; + background: #dadada url("images/ui-bg_glass_75_dadada_1x400.png") 50% 50% repeat-x; + font-weight: normal; + color: #212121; +} +.ui-state-hover a, +.ui-state-hover a:hover, +.ui-state-hover a:link, +.ui-state-hover a:visited, +.ui-state-focus a, +.ui-state-focus a:hover, +.ui-state-focus a:link, +.ui-state-focus a:visited, +a.ui-button:hover, +a.ui-button:focus { + color: #212121; + text-decoration: none; +} + +.ui-visual-focus { + box-shadow: 0 0 3px 1px rgb(94, 158, 214); +} +.ui-state-active, +.ui-widget-content .ui-state-active, +.ui-widget-header .ui-state-active, +a.ui-button:active, +.ui-button:active, +.ui-button.ui-state-active:hover { + border: 1px solid #aaaaaa; + background: #ffffff url("images/ui-bg_glass_65_ffffff_1x400.png") 50% 50% repeat-x; + font-weight: normal; + color: #212121; +} +.ui-icon-background, +.ui-state-active .ui-icon-background { + border: #aaaaaa; + background-color: #212121; +} +.ui-state-active a, +.ui-state-active a:link, +.ui-state-active a:visited { + color: #212121; + text-decoration: none; +} + +/* Interaction Cues +----------------------------------*/ +.ui-state-highlight, +.ui-widget-content .ui-state-highlight, +.ui-widget-header .ui-state-highlight { + border: 1px solid #fcefa1; + background: #fbf9ee url("images/ui-bg_glass_55_fbf9ee_1x400.png") 50% 50% repeat-x; + color: #363636; +} +.ui-state-checked { + border: 1px solid #fcefa1; + background: #fbf9ee; +} +.ui-state-highlight a, +.ui-widget-content .ui-state-highlight a, +.ui-widget-header .ui-state-highlight a { + color: #363636; +} +.ui-state-error, +.ui-widget-content .ui-state-error, +.ui-widget-header .ui-state-error { + border: 1px solid #cd0a0a; + background: #fef1ec url("images/ui-bg_glass_95_fef1ec_1x400.png") 50% 50% repeat-x; + color: #cd0a0a; +} +.ui-state-error a, +.ui-widget-content .ui-state-error a, +.ui-widget-header .ui-state-error a { + color: #cd0a0a; +} +.ui-state-error-text, +.ui-widget-content .ui-state-error-text, +.ui-widget-header .ui-state-error-text { + color: #cd0a0a; +} +.ui-priority-primary, +.ui-widget-content .ui-priority-primary, +.ui-widget-header .ui-priority-primary { + font-weight: bold; +} +.ui-priority-secondary, +.ui-widget-content .ui-priority-secondary, +.ui-widget-header .ui-priority-secondary { + opacity: .7; + filter:Alpha(Opacity=70); /* support: IE8 */ + font-weight: normal; +} +.ui-state-disabled, +.ui-widget-content .ui-state-disabled, +.ui-widget-header .ui-state-disabled { + opacity: .35; + filter:Alpha(Opacity=35); /* support: IE8 */ + background-image: none; +} +.ui-state-disabled .ui-icon { + filter:Alpha(Opacity=35); /* support: IE8 - See #6059 */ +} + +/* Icons +----------------------------------*/ + +/* states and images */ +.ui-icon { + width: 16px; + height: 16px; +} +.ui-icon, +.ui-widget-content .ui-icon { + background-image: url("images/ui-icons_222222_256x240.png"); +} +.ui-widget-header .ui-icon { + background-image: url("images/ui-icons_222222_256x240.png"); +} +.ui-state-hover .ui-icon, +.ui-state-focus .ui-icon, +.ui-button:hover .ui-icon, +.ui-button:focus .ui-icon { + background-image: url("images/ui-icons_454545_256x240.png"); +} +.ui-state-active .ui-icon, +.ui-button:active .ui-icon { + background-image: url("images/ui-icons_454545_256x240.png"); +} +.ui-state-highlight .ui-icon, +.ui-button .ui-state-highlight.ui-icon { + background-image: url("images/ui-icons_2e83ff_256x240.png"); +} +.ui-state-error .ui-icon, +.ui-state-error-text .ui-icon { + background-image: url("images/ui-icons_cd0a0a_256x240.png"); +} +.ui-button .ui-icon { + background-image: url("images/ui-icons_888888_256x240.png"); +} + +/* positioning */ +.ui-icon-blank { background-position: 16px 16px; } +.ui-icon-caret-1-n { background-position: 0 0; } +.ui-icon-caret-1-ne { background-position: -16px 0; } +.ui-icon-caret-1-e { background-position: -32px 0; } +.ui-icon-caret-1-se { background-position: -48px 0; } +.ui-icon-caret-1-s { background-position: -65px 0; } +.ui-icon-caret-1-sw { background-position: -80px 0; } +.ui-icon-caret-1-w { background-position: -96px 0; } +.ui-icon-caret-1-nw { background-position: -112px 0; } +.ui-icon-caret-2-n-s { background-position: -128px 0; } +.ui-icon-caret-2-e-w { background-position: -144px 0; } +.ui-icon-triangle-1-n { background-position: 0 -16px; } +.ui-icon-triangle-1-ne { background-position: -16px -16px; } +.ui-icon-triangle-1-e { background-position: -32px -16px; } +.ui-icon-triangle-1-se { background-position: -48px -16px; } +.ui-icon-triangle-1-s { background-position: -65px -16px; } +.ui-icon-triangle-1-sw { background-position: -80px -16px; } +.ui-icon-triangle-1-w { background-position: -96px -16px; } +.ui-icon-triangle-1-nw { background-position: -112px -16px; } +.ui-icon-triangle-2-n-s { background-position: -128px -16px; } +.ui-icon-triangle-2-e-w { background-position: -144px -16px; } +.ui-icon-arrow-1-n { background-position: 0 -32px; } +.ui-icon-arrow-1-ne { background-position: -16px -32px; } +.ui-icon-arrow-1-e { background-position: -32px -32px; } +.ui-icon-arrow-1-se { background-position: -48px -32px; } +.ui-icon-arrow-1-s { background-position: -65px -32px; } +.ui-icon-arrow-1-sw { background-position: -80px -32px; } +.ui-icon-arrow-1-w { background-position: -96px -32px; } +.ui-icon-arrow-1-nw { background-position: -112px -32px; } +.ui-icon-arrow-2-n-s { background-position: -128px -32px; } +.ui-icon-arrow-2-ne-sw { background-position: -144px -32px; } +.ui-icon-arrow-2-e-w { background-position: -160px -32px; } +.ui-icon-arrow-2-se-nw { background-position: -176px -32px; } +.ui-icon-arrowstop-1-n { background-position: -192px -32px; } +.ui-icon-arrowstop-1-e { background-position: -208px -32px; } +.ui-icon-arrowstop-1-s { background-position: -224px -32px; } +.ui-icon-arrowstop-1-w { background-position: -240px -32px; } +.ui-icon-arrowthick-1-n { background-position: 1px -48px; } +.ui-icon-arrowthick-1-ne { background-position: -16px -48px; } +.ui-icon-arrowthick-1-e { background-position: -32px -48px; } +.ui-icon-arrowthick-1-se { background-position: -48px -48px; } +.ui-icon-arrowthick-1-s { background-position: -64px -48px; } +.ui-icon-arrowthick-1-sw { background-position: -80px -48px; } +.ui-icon-arrowthick-1-w { background-position: -96px -48px; } +.ui-icon-arrowthick-1-nw { background-position: -112px -48px; } +.ui-icon-arrowthick-2-n-s { background-position: -128px -48px; } +.ui-icon-arrowthick-2-ne-sw { background-position: -144px -48px; } +.ui-icon-arrowthick-2-e-w { background-position: -160px -48px; } +.ui-icon-arrowthick-2-se-nw { background-position: -176px -48px; } +.ui-icon-arrowthickstop-1-n { background-position: -192px -48px; } +.ui-icon-arrowthickstop-1-e { background-position: -208px -48px; } +.ui-icon-arrowthickstop-1-s { background-position: -224px -48px; } +.ui-icon-arrowthickstop-1-w { background-position: -240px -48px; } +.ui-icon-arrowreturnthick-1-w { background-position: 0 -64px; } +.ui-icon-arrowreturnthick-1-n { background-position: -16px -64px; } +.ui-icon-arrowreturnthick-1-e { background-position: -32px -64px; } +.ui-icon-arrowreturnthick-1-s { background-position: -48px -64px; } +.ui-icon-arrowreturn-1-w { background-position: -64px -64px; } +.ui-icon-arrowreturn-1-n { background-position: -80px -64px; } +.ui-icon-arrowreturn-1-e { background-position: -96px -64px; } +.ui-icon-arrowreturn-1-s { background-position: -112px -64px; } +.ui-icon-arrowrefresh-1-w { background-position: -128px -64px; } +.ui-icon-arrowrefresh-1-n { background-position: -144px -64px; } +.ui-icon-arrowrefresh-1-e { background-position: -160px -64px; } +.ui-icon-arrowrefresh-1-s { background-position: -176px -64px; } +.ui-icon-arrow-4 { background-position: 0 -80px; } +.ui-icon-arrow-4-diag { background-position: -16px -80px; } +.ui-icon-extlink { background-position: -32px -80px; } +.ui-icon-newwin { background-position: -48px -80px; } +.ui-icon-refresh { background-position: -64px -80px; } +.ui-icon-shuffle { background-position: -80px -80px; } +.ui-icon-transfer-e-w { background-position: -96px -80px; } +.ui-icon-transferthick-e-w { background-position: -112px -80px; } +.ui-icon-folder-collapsed { background-position: 0 -96px; } +.ui-icon-folder-open { background-position: -16px -96px; } +.ui-icon-document { background-position: -32px -96px; } +.ui-icon-document-b { background-position: -48px -96px; } +.ui-icon-note { background-position: -64px -96px; } +.ui-icon-mail-closed { background-position: -80px -96px; } +.ui-icon-mail-open { background-position: -96px -96px; } +.ui-icon-suitcase { background-position: -112px -96px; } +.ui-icon-comment { background-position: -128px -96px; } +.ui-icon-person { background-position: -144px -96px; } +.ui-icon-print { background-position: -160px -96px; } +.ui-icon-trash { background-position: -176px -96px; } +.ui-icon-locked { background-position: -192px -96px; } +.ui-icon-unlocked { background-position: -208px -96px; } +.ui-icon-bookmark { background-position: -224px -96px; } +.ui-icon-tag { background-position: -240px -96px; } +.ui-icon-home { background-position: 0 -112px; } +.ui-icon-flag { background-position: -16px -112px; } +.ui-icon-calendar { background-position: -32px -112px; } +.ui-icon-cart { background-position: -48px -112px; } +.ui-icon-pencil { background-position: -64px -112px; } +.ui-icon-clock { background-position: -80px -112px; } +.ui-icon-disk { background-position: -96px -112px; } +.ui-icon-calculator { background-position: -112px -112px; } +.ui-icon-zoomin { background-position: -128px -112px; } +.ui-icon-zoomout { background-position: -144px -112px; } +.ui-icon-search { background-position: -160px -112px; } +.ui-icon-wrench { background-position: -176px -112px; } +.ui-icon-gear { background-position: -192px -112px; } +.ui-icon-heart { background-position: -208px -112px; } +.ui-icon-star { background-position: -224px -112px; } +.ui-icon-link { background-position: -240px -112px; } +.ui-icon-cancel { background-position: 0 -128px; } +.ui-icon-plus { background-position: -16px -128px; } +.ui-icon-plusthick { background-position: -32px -128px; } +.ui-icon-minus { background-position: -48px -128px; } +.ui-icon-minusthick { background-position: -64px -128px; } +.ui-icon-close { background-position: -80px -128px; } +.ui-icon-closethick { background-position: -96px -128px; } +.ui-icon-key { background-position: -112px -128px; } +.ui-icon-lightbulb { background-position: -128px -128px; } +.ui-icon-scissors { background-position: -144px -128px; } +.ui-icon-clipboard { background-position: -160px -128px; } +.ui-icon-copy { background-position: -176px -128px; } +.ui-icon-contact { background-position: -192px -128px; } +.ui-icon-image { background-position: -208px -128px; } +.ui-icon-video { background-position: -224px -128px; } +.ui-icon-script { background-position: -240px -128px; } +.ui-icon-alert { background-position: 0 -144px; } +.ui-icon-info { background-position: -16px -144px; } +.ui-icon-notice { background-position: -32px -144px; } +.ui-icon-help { background-position: -48px -144px; } +.ui-icon-check { background-position: -64px -144px; } +.ui-icon-bullet { background-position: -80px -144px; } +.ui-icon-radio-on { background-position: -96px -144px; } +.ui-icon-radio-off { background-position: -112px -144px; } +.ui-icon-pin-w { background-position: -128px -144px; } +.ui-icon-pin-s { background-position: -144px -144px; } +.ui-icon-play { background-position: 0 -160px; } +.ui-icon-pause { background-position: -16px -160px; } +.ui-icon-seek-next { background-position: -32px -160px; } +.ui-icon-seek-prev { background-position: -48px -160px; } +.ui-icon-seek-end { background-position: -64px -160px; } +.ui-icon-seek-start { background-position: -80px -160px; } +/* ui-icon-seek-first is deprecated, use ui-icon-seek-start instead */ +.ui-icon-seek-first { background-position: -80px -160px; } +.ui-icon-stop { background-position: -96px -160px; } +.ui-icon-eject { background-position: -112px -160px; } +.ui-icon-volume-off { background-position: -128px -160px; } +.ui-icon-volume-on { background-position: -144px -160px; } +.ui-icon-power { background-position: 0 -176px; } +.ui-icon-signal-diag { background-position: -16px -176px; } +.ui-icon-signal { background-position: -32px -176px; } +.ui-icon-battery-0 { background-position: -48px -176px; } +.ui-icon-battery-1 { background-position: -64px -176px; } +.ui-icon-battery-2 { background-position: -80px -176px; } +.ui-icon-battery-3 { background-position: -96px -176px; } +.ui-icon-circle-plus { background-position: 0 -192px; } +.ui-icon-circle-minus { background-position: -16px -192px; } +.ui-icon-circle-close { background-position: -32px -192px; } +.ui-icon-circle-triangle-e { background-position: -48px -192px; } +.ui-icon-circle-triangle-s { background-position: -64px -192px; } +.ui-icon-circle-triangle-w { background-position: -80px -192px; } +.ui-icon-circle-triangle-n { background-position: -96px -192px; } +.ui-icon-circle-arrow-e { background-position: -112px -192px; } +.ui-icon-circle-arrow-s { background-position: -128px -192px; } +.ui-icon-circle-arrow-w { background-position: -144px -192px; } +.ui-icon-circle-arrow-n { background-position: -160px -192px; } +.ui-icon-circle-zoomin { background-position: -176px -192px; } +.ui-icon-circle-zoomout { background-position: -192px -192px; } +.ui-icon-circle-check { background-position: -208px -192px; } +.ui-icon-circlesmall-plus { background-position: 0 -208px; } +.ui-icon-circlesmall-minus { background-position: -16px -208px; } +.ui-icon-circlesmall-close { background-position: -32px -208px; } +.ui-icon-squaresmall-plus { background-position: -48px -208px; } +.ui-icon-squaresmall-minus { background-position: -64px -208px; } +.ui-icon-squaresmall-close { background-position: -80px -208px; } +.ui-icon-grip-dotted-vertical { background-position: 0 -224px; } +.ui-icon-grip-dotted-horizontal { background-position: -16px -224px; } +.ui-icon-grip-solid-vertical { background-position: -32px -224px; } +.ui-icon-grip-solid-horizontal { background-position: -48px -224px; } +.ui-icon-gripsmall-diagonal-se { background-position: -64px -224px; } +.ui-icon-grip-diagonal-se { background-position: -80px -224px; } + + +/* Misc visuals +----------------------------------*/ + +/* Corner radius */ +.ui-corner-all, +.ui-corner-top, +.ui-corner-left, +.ui-corner-tl { + border-top-left-radius: 4px; +} +.ui-corner-all, +.ui-corner-top, +.ui-corner-right, +.ui-corner-tr { + border-top-right-radius: 4px; +} +.ui-corner-all, +.ui-corner-bottom, +.ui-corner-left, +.ui-corner-bl { + border-bottom-left-radius: 4px; +} +.ui-corner-all, +.ui-corner-bottom, +.ui-corner-right, +.ui-corner-br { + border-bottom-right-radius: 4px; +} + +/* Overlays */ +.ui-widget-overlay { + background: #aaaaaa; + opacity: .3; + filter: Alpha(Opacity=30); /* support: IE8 */ +} +.ui-widget-shadow { + -webkit-box-shadow: -8px -8px 8px #aaaaaa; + box-shadow: -8px -8px 8px #aaaaaa; +} diff --git a/php/apps/phpmyadmin49/html/themes/original/layout.inc.php b/php/apps/phpmyadmin49/html/themes/original/layout.inc.php new file mode 100644 index 00000000..cfa1dc0f --- /dev/null +++ b/php/apps/phpmyadmin49/html/themes/original/layout.inc.php @@ -0,0 +1,76 @@ + + +.CodeMirror { + height: em; + direction: ltr; +} +#inline_editor_outer .CodeMirror { + height: em; +} +.insertRowTable .CodeMirror { + height: em; + width: em; + border: 1px solid #a9a9a9; +} +#pma_console .CodeMirror-gutters { + background-color: initial; + border: none; +} +span.cm-keyword, span.cm-statement-verb { + color: #909; +} +span.cm-variable { + color: black; +} +span.cm-comment { + color: #808000; +} +span.cm-mysql-string { + color: #008000; +} +span.cm-operator { + color: fuchsia; +} +span.cm-mysql-word { + color: black; +} +span.cm-builtin { + color: #f00; +} +span.cm-variable-2 { + color: #f90; +} +span.cm-variable-3 { + color: #00f; +} +span.cm-separator { + color: fuchsia; +} +span.cm-number { + color: teal; +} +.autocomplete-column-name { + display: inline-block; +} +.autocomplete-column-hint { + display: inline-block; + float: right; + color: #666; + margin-left: 1em; +} +.CodeMirror-hints { + z-index: 999; +} +.CodeMirror-lint-tooltip { + z-index: 200; + font-family: inherit; +} +.CodeMirror-lint-tooltip code { + font-family: monospace; + font-weight: bold; +} diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/css/common.css.php b/php/apps/phpmyadmin49/html/themes/pmahomme/css/common.css.php new file mode 100644 index 00000000..140d627e --- /dev/null +++ b/php/apps/phpmyadmin49/html/themes/pmahomme/css/common.css.php @@ -0,0 +1,3693 @@ + +/******************************************************************************/ +/* general tags */ +html { + font-size: getFontSize(); ?> +} + +input, +select, +textarea { + font-size: 1em; +} + + +body { + + font-family: ; + + padding: 0; + margin: 0; + margin-: 240px; + color: ; + background: ; +} + +body#loginform { + margin: 0; +} + +#page_content { + margin: 0 .5em; +} + +.desktop50 { + width: 50%; +} + +.all100 { + width: 100%; +} + +.all85{ + width: 85%; +} + +.auth_config_tbl{ + margin: 0 auto; +} + + + textarea, + tt, + pre, + code { + font-family: ; + } + + + +h1 { + font-size: 140%; + font-weight: bold; +} + +h2 { + font-size: 2em; + font-weight: normal; + text-shadow: 0 1px 0 #fff; + padding: 10px 0 10px; + padding-: 3px; + color: #777; +} + +/* Hiding icons in the page titles */ +h2 img { + display: none; +} + +h2 a img { + display: inline; +} + +.data, +.data_full_width { + margin: 0 0 12px; +} + +.data_full_width { + width: 100%; +} + +h3 { + font-weight: bold; +} + +a, +a:link, +a:visited, +a:active, +button.mult_submit, +.checkall_box+label { + text-decoration: none; + color: #235a81; + cursor: pointer; + outline: none; + +} + +a:hover, +button.mult_submit:hover, +button.mult_submit:focus, +.checkall_box+label:hover { + text-decoration: underline; + color: #235a81; +} + +#initials_table { + background: #f3f3f3; + border: 1px solid #aaa; + margin-bottom: 10px; + -moz-border-radius: 5px; + -webkit-border-radius: 5px; + border-radius: 5px; +} + +#initials_table td { + padding: 8px !important; +} + +#initials_table a { + border: 1px solid #aaa; + background: #fff; + padding: 4px 8px; + -moz-border-radius: 5px; + -webkit-border-radius: 5px; + border-radius: 5px; + getCssGradient('ffffff', 'e0e0e0'); ?> +} + +#initials_table a.active { + border: 1px solid #666; + box-shadow: 0 0 2px #999; + getCssGradient('bbbbbb', 'ffffff'); ?> +} + +dfn { + font-style: normal; +} + +dfn:hover { + font-style: normal; + cursor: help; +} + +th { + font-weight: bold; + color: ; + background: #f3f3f3; + getCssGradient('ffffff', 'cccccc'); ?> +} + +a img { + border: 0; +} + +hr { + color: ; + background-color: ; + border: 0; + height: 1px; +} + +form { + padding: 0; + margin: 0; + display: inline; +} + + +input, +select { + /* Fix outline in Chrome: */ + outline: none; +} + +input[type=text], +input[type=password], +input[type=number], +input[type=date] { + border-radius: 2px; + -moz-border-radius: 2px; + -webkit-border-radius: 2px; + + + background: white; + border: 1px solid #aaa; + color: #555; + padding: 4px; +} + +input[type=text], +input[type=password], +input[type=number], +input[type=date], +input[type=checkbox], +select { + margin: 6px; +} + +input[type=number] { + width: 50px; +} + +input#auto_increment_opt { + width: min-content; +} + +input[type=text], +input[type=password], +input[type=number], +input[type=date], +select { + transition: all 0.2s; + -ms-transition: all 0.2s; + -webkit-transition: all 0.2s; + -moz-transition: all 0.2s; +} + +input[type=text][disabled], +input[type=text][disabled]:hover, +input[type=password][disabled], +input[type=password][disabled]:hover, +input[type=number][disabled], +input[type=number][disabled]:hover, +input[type=date][disabled], +input[type=date][disabled]:hover, +select[disabled], +select[disabled]:hover { + background: #e8e8e8; + box-shadow: none; + -webkit-box-shadow: none; + -moz-box-shadow: none; +} + +input[type=text]:hover, +input[type=text]:focus, +input[type=password]:hover, +input[type=password]:focus, +input[type=number]:hover, +input[type=number]:focus, +input[type=date]:hover, +input[type=date]:focus, +select:focus { + border: 1px solid #7c7c7c; + background: #fff; +} + +input[type=text]:hover, +input[type=password]:hover, +input[type=number]:hover, +input[type=date]:hover { + box-shadow: 0 1px 3px #aaa; + -webkit-box-shadow: 0 1px 3px #aaa; + -moz-box-shadow: 0 1px 3px #aaa; +} + +input[type=submit], +input[type=button], +button[type=submit]:not(.mult_submit) { + font-weight: bold !important; +} + +input[type=submit], +input[type=button], +button[type=submit]:not(.mult_submit), +input[type=reset], +input[name=submit_reset], +input.button { + margin: 6px 14px; + border: 1px solid #aaa; + padding: 3px 7px; + color: #111; + text-decoration: none; + background: #ddd; + + border-radius: 12px; + -webkit-border-radius: 12px; + -moz-border-radius: 12px; + + text-shadow: 0 1px 0 #fff; + + getCssGradient('f8f8f8', 'd8d8d8'); ?> +} + +input[type=submit]:hover, +input[type=button]:hover, +button[type=submit]:not(.mult_submit):hover, +input[type=reset]:hover, +input[name=submit_reset]:hover, +input.button:hover { + position: relative; + getCssGradient('fff', 'ddd'); ?> + cursor: pointer; +} + +input[type=submit]:active, +input[type=button]:active, +button[type=submit]:not(.mult_submit):active, +input[type=reset]:active, +input[name=submit_reset]:active, +input.button:active { + position: relative; + getCssGradient('eee', 'ddd'); ?> + box-shadow: 0 1px 6px -2px #333 inset; + text-shadow: none; +} + +input[type=submit]:disabled, +input[type=button]:disabled, +button[type=submit]:not(.mult_submit):disabled, +input[type=reset]:disabled, +input[name=submit_reset]:disabled, +input.button:disabled { + background: #ccc; + color: #666; + text-shadow: none; +} + +textarea { + overflow: visible; + margin: 6px; +} + +textarea.char { + margin: 6px; +} + +fieldset, .preview_sql { + margin-top: 1em; + border-radius: 4px 4px 0 0; + -moz-border-radius: 4px 4px 0 0; + -webkit-border-radius: 4px 4px 0 0; + border: #aaa solid 1px; + padding: 0.5em; + background: #eee; + text-shadow: 1px 1px 2px #fff inset; + -moz-box-shadow: 1px 1px 2px #fff inset; + -webkit-box-shadow: 1px 1px 2px #fff inset; + box-shadow: 1px 1px 2px #fff inset; +} + +fieldset fieldset { + margin: .8em; + background: #fff; + border: 1px solid #aaa; + background: #E8E8E8; + +} + +fieldset legend { + font-weight: bold; + color: #444; + padding: 5px 10px; + border-radius: 2px; + -moz-border-radius: 2px; + -webkit-border-radius: 2px; + border: 1px solid #aaa; + background-color: #fff; + -moz-box-shadow: 3px 3px 15px #bbb; + -webkit-box-shadow: 3px 3px 15px #bbb; + box-shadow: 3px 3px 15px #bbb; + max-width: 100%; +} + +.some-margin { + margin: 1.5em; +} + +/* buttons in some browsers (eg. Konqueror) are block elements, this breaks design */ +button { + display: inline; +} + +table caption, +table th, +table td { + padding: .1em .3em; + margin: .1em; + vertical-align: middle; + text-shadow: 0 1px 0 #fff; +} + +/* 3.4 */ +.datatable{ + table-layout: fixed; +} + +table { + border-collapse: collapse; +} + +thead th { + border-right: 1px solid #fff; +} + +th { + text-align: left; +} + + +img, +button { + vertical-align: middle; +} + +input[type="checkbox"], +input[type="radio"] { + vertical-align: -11%; +} + + +select { + -moz-border-radius: 2px; + -webkit-border-radius: 2px; + border-radius: 2px; + + border: 1px solid #bbb; + color: #333; + padding: 3px; + background: white; + margin:6px; +} + +select[multiple] { + getCssGradient('ffffff', 'f2f2f2'); ?> +} + +/******************************************************************************/ +/* classes */ +.clearfloat { + clear: both; +} + +.floatleft { + float: ; + margin-: 1em; +} + +.floatright { + float: ; +} + +.center { + text-align: center; +} + +.displayblock { + display: block; +} + +table.nospacing { + border-spacing: 0; +} + +table.nopadding tr th, table.nopadding tr td { + padding: 0; +} + +th.left, td.left { + text-align: left; +} + +th.center, td.center { + text-align: center; +} + +th.right, td.right { + text-align: right; + padding-right: 1em; +} + +tr.vtop th, tr.vtop td, th.vtop, td.vtop { + vertical-align: top; +} + +tr.vmiddle th, tr.vmiddle td, th.vmiddle, td.vmiddle { + vertical-align: middle; +} + +tr.vbottom th, tr.vbottom td, th.vbottom, td.vbottom { + vertical-align: bottom; +} + +.paddingtop { + padding-top: 1em; +} + +.separator { + color: #fff; + text-shadow: 0 1px 0 #000; +} + +div.tools { + /* border: 1px solid #000; */ + padding: .2em; +} + +div.tools a { + color: #3a7ead !important; +} + +div.tools, +fieldset.tblFooters { + margin-top: 0; + margin-bottom: .5em; + /* avoid a thick line since this should be used under another fieldset */ + border-top: 0; + text-align: ; + float: none; + clear: both; + -webkit-border-radius: 0 0 4px 4px; + -moz-border-radius: 0 0 4px 4px; + border-radius: 0 0 4px 5px; +} + +div.null_div { + height: 20px; + text-align: center; + font-style: normal; + min-width: 50px; +} + +fieldset .formelement { + float: ; + margin-: .5em; + /* IE */ + white-space: nowrap; +} + +/* revert for Gecko */ +fieldset div[class=formelement] { + white-space: normal; +} + +button.mult_submit { + border: none; + background-color: transparent; +} + +/* odd items 1,3,5,7,... */ +table tbody:first-of-type tr:nth-child(odd), +table tbody:first-of-type tr:nth-child(odd) th, +#table_index tbody:nth-of-type(odd) tr, +#table_index tbody:nth-of-type(odd) th { + background: #fff; +} + +/* even items 2,4,6,8,... */ +table tbody:first-of-type tr:nth-child(even), +table tbody:first-of-type tr:nth-child(even) th, +#table_index tbody:nth-of-type(even) tr, +#table_index tbody:nth-of-type(even) th { + background: #DFDFDF; +} + +table tr th, +table tr { + text-align: ; +} + +/* marked table rows */ +td.marked:not(.nomarker), +table tr.marked:not(.nomarker) td, +table tbody:first-of-type tr.marked:not(.nomarker) th, +table tr.marked:not(.nomarker) { + getCssGradient('ced6df', 'b6c6d7'); ?> + color: ; +} + +/* hovered items */ +table tbody:first-of-type tr:not(.nopointer):hover, +table tbody:first-of-type tr:not(.nopointer):hover th, +.hover:not(.nopointer) { + getCssGradient('ced6df', 'b6c6d7'); ?> + color: ; +} + +/* hovered table rows */ +#table_index tbody:hover tr, +#table_index tbody:hover th, +table tr.hover:not(.nopointer) th { + getCssGradient('ced6df', 'b6c6d7'); ?> + color: ; +} + +/** + * marks table rows/cells if the db field is in a where condition + */ +.condition { + border-color: !important; +} + +th.condition { + border-width: 1px 1px 0 1px; + border-style: solid; +} + +td.condition { + border-width: 0 1px 0 1px; + border-style: solid; +} + +tr:last-child td.condition { + border-width: 0 1px 1px 1px; +} + + + /* for first th which must have right border set (ltr only) */ + .before-condition { + border-right: 1px solid ; + } + + +/** + * cells with the value NULL + */ +td.null { + font-style: italic; + color: #7d7d7d; +} + +table .valueHeader { + text-align: ; + white-space: normal; +} +table .value { + text-align: ; + white-space: normal; +} +/* IE doesnt handles 'pre' right */ +table [class=value] { + white-space: normal; +} + + + + .value { + font-family: ; + } + +.attention { + color: red; + font-weight: bold; +} +.allfine { + color: green; +} + + +img.lightbulb { + cursor: pointer; +} + +.pdflayout { + overflow: hidden; + clip: inherit; + background-color: #fff; + display: none; + border: 1px solid #000; + position: relative; +} + +.pdflayout_table { + background: #D3DCE3; + color: #000; + overflow: hidden; + clip: inherit; + z-index: 2; + display: inline; + visibility: inherit; + cursor: move; + position: absolute; + font-size: 80%; + border: 1px dashed #000; +} + +/* Doc links in SQL */ +.cm-sql-doc { + text-decoration: none; + border-bottom: 1px dotted #000; + color: inherit !important; +} + +/* no extra space in table cells */ +td .icon { + image-rendering: pixelated; + margin: 0; +} + +.selectallarrow { + margin-: .3em; + margin-: .6em; +} + +/* message boxes: error, confirmation */ +#pma_errors, #pma_demo, #pma_footer { + position: relative; + padding: 0 0.5em; +} + +.success h1, +.notice h1, +div.error h1 { + border-bottom: 2px solid; + font-weight: bold; + text-align: ; + margin: 0 0 .2em 0; +} + +div.success, +div.notice, +div.error { + margin: .5em 0 0.5em; + border: 1px solid; + background-repeat: no-repeat; + + background-position: 10px 50%; + padding: 10px 10px 10px 10px; + + background-position: 99% 50%; + padding: 10px 35px 10px 10px; + + + -moz-border-radius: 5px; + -webkit-border-radius: 5px; + border-radius: 5px; + + -moz-box-shadow: 0 1px 1px #fff inset; + -webkit-box-shadow: 0 1px 1px #fff inset; + box-shadow: 0 1px 1px #fff inset; +} + +.success a, +.notice a, +.error a { + text-decoration: underline; +} + +.success { + color: #000; + background-color: #ebf8a4; +} + +h1.success, +div.success { + border-color: #a2d246; +} +.success h1 { + border-color: #00FF00; +} + +.notice { + color: #000; + background-color: #e8eef1; +} + +h1.notice, +div.notice { + border-color: #3a6c7e; +} + +.notice h1 { + border-color: #ffb10a; +} + +.error { + border: 1px solid maroon !important; + color: #000; + background: pink; +} + +h1.error, +div.error { + border-color: #333; +} + +div.error h1 { + border-color: #ff0000; +} + +.confirmation { + color: #000; + background-color: pink; +} + +fieldset.confirmation { +} + +fieldset.confirmation legend { +} + +/* end messageboxes */ + +.new_central_col{ + width: 100%; +} + +.tblcomment { + font-size: 70%; + font-weight: normal; + color: #000099; +} + +.tblHeaders { + font-weight: bold; + color: ; + background: ; +} + +div.tools, +.tblFooters { + font-weight: normal; + color: ; + background: ; +} + +.tblHeaders a:link, +.tblHeaders a:active, +.tblHeaders a:visited, +div.tools a:link, +div.tools a:visited, +div.tools a:active, +.tblFooters a:link, +.tblFooters a:active, +.tblFooters a:visited { + color: #0000FF; +} + +.tblHeaders a:hover, +div.tools a:hover, +.tblFooters a:hover { + color: #FF0000; +} + +/* forbidden, no privileges */ +.noPrivileges { + color: #FF0000; + font-weight: bold; +} + +/* disabled text */ +.disabled, +.disabled a:link, +.disabled a:active, +.disabled a:visited { + color: #666; +} + +.disabled a:hover { + color: #666; + text-decoration: none; +} + +tr.disabled td, +td.disabled { + background-color: #f3f3f3; + color: #aaa; +} + +.nowrap { + white-space: nowrap; +} + +/** + * login form + */ +body#loginform h1, +body#loginform a.logo { + display: block; + text-align: center; +} + +body#loginform { + margin-top: 1em; + text-align: center; +} + +body#loginform div.container { + text-align: ; + width: 30em; + margin: 0 auto; +} + +form.login label { + width: 10em; + font-weight: bolder; + display: inline-block; + margin-: 2em; +} + +form.login input[type=text], +form.login input[type=password], +form.login select { + box-sizing: border-box; + width: 14em; +} + +.commented_column { + border-bottom: 1px dashed #000; +} + +.column_attribute { + font-size: 70%; +} + +.cfg_dbg_demo{ + margin: 0.5em 1em 0.5em 1em; +} + +.central_columns_navigation{ + padding:1.5% 0em !important; +} + +.central_columns_add_column{ + display:inline-block; + margin-left:1%; + max-width:50% +} + +.message_errors_found{ + margin-top: 20px; +} + +.repl_gui_skip_err_cnt{ + width: 30px; +} + +.font_weight_bold{ + font-weight: bold; +} + +.color_gray{ + color: gray; +} + +.pma_sliding_message{ + display: inline-block; +} + +li.last.database{ + margin-bottom: 15px +} +/******************************************************************************/ +/* specific elements */ + +/* topmenu */ +#topmenu a { + text-shadow: 0 1px 0 #fff; +} + +#topmenu .error { + background: #eee;border: 0 !important;color: #aaa; +} + +ul#topmenu, +ul#topmenu2, +ul.tabs { + font-weight: bold; + list-style-type: none; + margin: 0; + padding: 0; +} + +ul#topmenu2 { + margin: .25em .5em 0; + height: 2em; + clear: both; +} + +ul#topmenu li, +ul#topmenu2 li { + float: ; + margin: 0; + vertical-align: middle; +} + +#topmenu img, +#topmenu2 img { + margin-right: .5em; + vertical-align: -3px; +} + +.menucontainer { + getCssGradient('ffffff', 'dcdcdc'); ?> + border-top: 1px solid #aaa; +} + +.scrollindicator { + display: none; +} + +/* default tab styles */ +.tabactive { + background: #fff !important; +} + +ul#topmenu2 a { + display: block; + margin: 7px 6px 7px; + margin-: 0; + padding: 4px 10px; + white-space: nowrap; + border: 1px solid #ddd; + border-radius: 20px; + -moz-border-radius: 20px; + -webkit-border-radius: 20px; + background: #f2f2f2; + +} + +span.caution { + color: #FF0000; +} +fieldset.caution a { + color: #FF0000; +} +fieldset.caution a:hover { + color: #fff; + background-color: #FF0000; +} + +#topmenu { + margin-top: .5em; + padding: .1em .3em; +} + +ul#topmenu ul { + -moz-box-shadow: 1px 1px 6px #ddd; + -webkit-box-shadow: 2px 2px 3px #666; + box-shadow: 2px 2px 3px #666; +} + +ul#topmenu ul.only { + : 0; +} + +ul#topmenu > li { + border-right: 1px solid #fff; + border-left: 1px solid #ccc; + border-bottom: 1px solid #ccc; +} + +ul#topmenu > li:first-child { + border-left: 0; +} + +/* default tab styles */ +ul#topmenu a, +ul#topmenu span { + padding: .6em; +} + +ul#topmenu ul a { + border-width: 1pt 0 0 0; + -moz-border-radius: 0; + -webkit-border-radius: 0; + border-radius: 0; +} + +ul#topmenu ul li:first-child a { + border-width: 0; +} + +/* enabled hover/active tabs */ +ul#topmenu > li > a:hover, +ul#topmenu > li > .tabactive { + text-decoration: none; +} + +ul#topmenu ul a:hover, +ul#topmenu ul .tabactive { + text-decoration: none; +} + +ul#topmenu a.tab:hover, +ul#topmenu .tabactive { + /* background-color: ; */ +} + +ul#topmenu2 a.tab:hover, +ul#topmenu2 a.tabactive { + background-color: ; + border-radius: .3em; + -moz-border-radius: .3em; + -webkit-border-radius: .3em; + text-decoration: none; +} + +/* to be able to cancel the bottom border, use
    • */ +ul#topmenu > li.active { + /* border-bottom: 0pt solid ; */ + border-right: 0; + border-bottom-color: #fff; +} +/* end topmenu */ + +/* zoom search */ +div#dataDisplay input, +div#dataDisplay select { + margin: 0; + margin-: .5em; +} +div#dataDisplay th { + line-height: 2em; +} +table#tableFieldsId { + width: 100%; +} + +/* Calendar */ +table.calendar { + width: 100%; +} +table.calendar td { + text-align: center; +} +table.calendar td a { + display: block; +} + +table.calendar td a:hover { + background-color: #CCFFCC; +} + +table.calendar th { + background-color: #D3DCE3; +} + +table.calendar td.selected { + background-color: #FFCC99; +} + +img.calendar { + border: none; +} +form.clock { + text-align: center; +} +/* end Calendar */ + + +/* table stats */ +div#tablestatistics table { + float: ; + margin-bottom: .5em; + margin-: 1.5em; + margin-top: .5em; + min-width: 16em; +} + +/* end table stats */ + + +/* server privileges */ +#tableuserrights td, +#tablespecificuserrights td, +#tabledatabases td { + vertical-align: middle; +} +/* end server privileges */ + + +/* Heading */ +#topmenucontainer { + padding-: 1em; + width: 100%; +} + +#serverinfo { + background: #888; + padding: .3em .9em; + padding-: 2.2em; + text-shadow: 0 1px 0 #000; + max-width: 100%; + max-height: 16px; + overflow: hidden; +} + +#serverinfo .item { + white-space: nowrap; + color: #fff; +} + +#page_nav_icons { + position: fixed; + top: 0; + : 0; + z-index: 99; + padding: .25em 0; +} + +#goto_pagetop, #lock_page_icon, #page_settings_icon { + padding: .25em; + background: #888; +} + +#page_settings_icon { + cursor: pointer; + display: none; +} + +#page_settings_modal { + display: none; +} + +#pma_navigation_settings { + display: none; +} + +#span_table_comment { + font-weight: bold; + font-style: italic; + white-space: nowrap; + margin-left: 10px; + color: #D6D6D6; + text-shadow: none; +} + +#serverinfo img { + margin: 0 .1em 0; + margin-: .2em; +} + + +#textSQLDUMP { + width: 95%; + height: 95%; + font-family: Consolas, "Courier New", Courier, mono; + font-size: 110%; +} + +#TooltipContainer { + position: absolute; + z-index: 99; + width: 20em; + height: auto; + overflow: visible; + visibility: hidden; + background-color: #ffffcc; + color: #006600; + border: .1em solid #000; + padding: .5em; +} + +/* user privileges */ +#fieldset_add_user_login div.item { + border-bottom: 1px solid silver; + padding-bottom: .3em; + margin-bottom: .3em; +} + +#fieldset_add_user_login label { + float: ; + display: block; + width: 10em; + max-width: 100%; + text-align: ; + padding-: .5em; +} + +#fieldset_add_user_login span.options #select_pred_username, +#fieldset_add_user_login span.options #select_pred_hostname, +#fieldset_add_user_login span.options #select_pred_password { + width: 100%; + max-width: 100%; +} + +#fieldset_add_user_login span.options { + float: ; + display: block; + width: 12em; + max-width: 100%; + padding-: .5em; +} + +#fieldset_add_user_login input { + width: 12em; + clear: ; + max-width: 100%; +} + +#fieldset_add_user_login span.options input { + width: auto; +} + +#fieldset_user_priv div.item { + float: ; + width: 9em; + max-width: 100%; +} + +#fieldset_user_priv div.item div.item { + float: none; +} + +#fieldset_user_priv div.item label { + white-space: nowrap; +} + +#fieldset_user_priv div.item select { + width: 100%; +} + +#fieldset_user_global_rights fieldset { + float: ; +} + +#fieldset_user_group_rights fieldset { + float: ; +} + +#fieldset_user_global_rights>legend input { + margin-: 2em; +} +/* end user privileges */ + + +/* serverstatus */ + +.linkElem:hover { + text-decoration: underline; + color: #235a81; + cursor: pointer; +} + +h3#serverstatusqueries span { + font-size: 60%; + display: inline; +} + +.buttonlinks { + float: ; + white-space: nowrap; +} + +/* Also used for the variables page */ +fieldset#tableFilter { + padding: 0.1em 1em; +} + +div#serverStatusTabs { + margin-top: 1em; +} + +caption a.top { + float: ; +} + +div#serverstatusquerieschart { + float: ; + width: 500px; + height: 350px; + margin-: 50px; +} + +table#serverstatusqueriesdetails, +table#serverstatustraffic { + float: ; +} + +table#serverstatusqueriesdetails th { + min-width: 35px; +} + +table#serverstatusvariables { + width: 100%; + margin-bottom: 1em; +} +table#serverstatusvariables .name { + width: 18em; + white-space: nowrap; +} +table#serverstatusvariables .value { + width: 6em; +} +table#serverstatusconnections { + float: ; + margin-: 30px; +} + +div#serverstatus table tbody td.descr a, +div#serverstatus table .tblFooters a { + white-space: nowrap; +} + +div.liveChart { + clear: both; + min-width: 500px; + height: 400px; + padding-bottom: 80px; +} + +#addChartDialog input[type="text"] { + margin: 0; + padding: 3px; +} + +div#chartVariableSettings { + border: 1px solid #ddd; + background-color: #E6E6E6; + margin-left: 10px; +} + +table#chartGrid td { + padding: 3px; + margin: 0; +} + +table#chartGrid div.monitorChart { + background: #EBEBEB; + overflow: hidden; + border: none; +} + +div.tabLinks { + margin-left: 0.3em; + float: ; + padding: 5px 0; +} + +div.tabLinks a, div.tabLinks label { + margin-right: 7px; +} + +div.tabLinks .icon { + margin: -0.2em 0.3em 0 0; +} + +.popupContent { + display: none; + position: absolute; + border: 1px solid #CCC; + margin: 0; + padding: 3px; + -moz-box-shadow: 2px 2px 3px #666; + -webkit-box-shadow: 2px 2px 3px #666; + box-shadow: 2px 2px 3px #666; + background-color: #fff; + z-index: 2; +} + +div#logTable { + padding-top: 10px; + clear: both; +} + +div#logTable table { + width: 100%; +} + +div#queryAnalyzerDialog { + min-width: 700px; +} + +div#queryAnalyzerDialog div.CodeMirror-scroll { + height: auto; +} + +div#queryAnalyzerDialog div#queryProfiling { + height: 300px; +} + +div#queryAnalyzerDialog td.explain { + width: 250px; +} + +div#queryAnalyzerDialog table.queryNums { + display: none; + border: 0; + text-align: left; +} + +.smallIndent { + padding-: 7px; +} + +/* end serverstatus */ + +/* server variables */ +#serverVariables { + width: 100%; +} +#serverVariables .var-row > td { + line-height: 2em; +} +#serverVariables .var-header { + color: ; + background: #f3f3f3; + getCssGradient('ffffff', 'cccccc'); ?> + font-weight: bold; + text-align: ; +} +#serverVariables .var-row { + padding: 0.5em; + min-height: 18px; +} +#serverVariables .var-name { + font-weight: bold; +} +#serverVariables .var-name.session { + font-weight: normal; + font-style: italic; +} +#serverVariables .var-value { + float: ; + text-align: ; +} +#serverVariables .var-doc { + overflow:visible; + float: ; +} + +/* server variables editor */ +#serverVariables .editLink { + padding-: 1em; + font-family: sans-serif; +} +#serverVariables .serverVariableEditor { + width: 100%; + overflow: hidden; +} +#serverVariables .serverVariableEditor input { + width: 100%; + margin: 0 0.5em; + box-sizing: border-box; + -ms-box-sizing: border-box; + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + height: 2.2em; +} +#serverVariables .serverVariableEditor div { + display: block; + overflow: hidden; + padding-: 1em; +} +#serverVariables .serverVariableEditor a { + margin: 0 0.5em; + line-height: 2em; +} +/* end server variables */ + + +p.notice { + margin: 1.5em 0; + border: 1px solid #000; + background-repeat: no-repeat; + + background-position: 10px 50%; + padding: 10px 10px 10px 25px; + + background-position: 99% 50%; + padding: 25px 10px 10px 10px + + -moz-border-radius: 5px; + -webkit-border-radius: 5px; + border-radius: 5px; + -moz-box-shadow: 0 1px 2px #fff inset; + -webkit-box-shadow: 0 1px 2px #fff inset; + box-shadow: 0 1px 2px #fff inset; + background: #555; + color: #d4fb6a; +} + +p.notice a { + color: #fff; + text-decoration: underline; +} + +/* profiling */ + +div#profilingchart { + width: 850px; + height: 370px; + float: ; +} + +#profilingchart .jqplot-highlighter-tooltip{ + top: auto !important; + left: 11px; + bottom:24px; +} +/* end profiling */ + +/* table charting */ +#resizer { + border: 1px solid silver; +} +#inner-resizer { /* make room for the resize handle */ + padding: 10px; +} +.chartOption { + float: ; + margin-: 40px; +} +/* end table charting */ + +/* querybox */ + +#togglequerybox { + margin: 0 10px; +} + +#serverstatus h3 +{ + margin: 15px 0; + font-weight: normal; + color: #999; + font-size: 1.7em; +} +#sectionlinks { + margin-bottom: 15px; + padding: 16px; + background: #f3f3f3; + border: 1px solid #aaa; + border-radius: 5px; + -webkit-border-radius: 5px; + -moz-border-radius: 5px; + box-shadow: 0 1px 1px #fff inset; + -webkit-box-shadow: 0 1px 1px #fff inset; + -moz-box-shadow: 0 1px 1px #fff inset; +} +#sectionlinks a, +.buttonlinks a, +a.button { + font-weight: bold; + text-shadow: 0 1px 0 #fff; + line-height: 35px; + margin-: 7px; + border: 1px solid #aaa; + padding: 3px 7px; + color: #111 !important; + text-decoration: none; + background: #ddd; + white-space: nowrap; + border-radius: 20px; + -webkit-border-radius: 20px; + -moz-border-radius: 20px; + getCssGradient('f8f8f8', 'd8d8d8'); ?> +} +#sectionlinks a:hover, +.buttonlinks a:hover, +a.button:hover { + getCssGradient('ffffff', 'dddddd'); ?> +} + +div#sqlquerycontainer { + float: ; + width: 69%; + /* height: 15em; */ +} + +div#tablefieldscontainer { + float: ; + width: 29%; + margin-top: -20px; + /* height: 15em; */ +} + +div#tablefieldscontainer select { + width: 100%; + background: #fff; + /* height: 12em; */ +} + +textarea#sqlquery { + width: 100%; + /* height: 100%; */ + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + border-radius: 4px; + border: 1px solid #aaa; + padding: 5px; + font-family: inherit; +} +textarea#sql_query_edit { + height: 7em; + width: 95%; + display: block; +} +div#queryboxcontainer div#bookmarkoptions { + margin-top: .5em; +} +/* end querybox */ + +/* main page */ +#maincontainer { + /* background-image: url(getImgPath('logo_right.png');?>); */ + /* background-position: bottom; */ + /* background-repeat: no-repeat; */ +} + +#mysqlmaininformation, +#pmamaininformation { + float: ; + width: 49%; +} + +#maincontainer ul { + list-style-type: disc; + vertical-align: middle; +} + +#maincontainer li { + margin-bottom: .3em; +} + +#full_name_layer { + position: absolute; + padding: 2px; + margin-top: -3px; + z-index: 801; + + border-radius: 3px; + border: solid 1px #888; + background: #fff; + +} +/* end main page */ + + +/* iconic view for ul items */ + +li.no_bullets { + list-style-type:none !important; + margin-left: -25px !important; //align with other list items which have bullets +} + +/* end iconic view for ul items */ + +#body_browse_foreigners { + background: ; + margin: .5em .5em 0 .5em; +} + +#bodythemes { + width: 500px; + margin: auto; + text-align: center; +} + +#bodythemes img { + border: .1em solid #000; +} + +#bodythemes a:hover img { + border: .1em solid red; +} + +#fieldset_select_fields { + float: ; +} + +#selflink { + clear: both; + display: block; + margin-top: 1em; + margin-bottom: 1em; + width: 98%; + margin-: 1%; + border-top: .1em solid silver; + text-align: ; +} + +#table_innodb_bufferpool_usage, +#table_innodb_bufferpool_activity { + float: ; +} + +#div_mysql_charset_collations table { + float: ; +} + +#div_mysql_charset_collations table th, +#div_mysql_charset_collations table td { + padding: 0.4em; +} + +#div_mysql_charset_collations table th#collationHeader { + width: 35%; +} + +#qbe_div_table_list { + float: ; +} + +#qbe_div_sql_query { + float: ; +} + +label.desc { + width: 30em; + float: ; +} + +label.desc sup { + position: absolute; +} + +code.php { + display: block; + padding-left: 1em; + margin-top: 0; + margin-bottom: 0; + max-height: 10em; + overflow: auto; + direction: ltr; +} + +code.sql, +div.sqlvalidate { + display: block; + padding: 1em; + margin-top: 0; + margin-bottom: 0; + max-height: 10em; + overflow: auto; + direction: ltr; +} + +.result_query div.sqlOuter { + background: ; + text-align: ; +} + +.result_query .success, .result_query .error { + margin-bottom: 0; + border-bottom: none !important; + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; + padding-bottom: 5px; +} + +#PMA_slidingMessage code.sql, +div.sqlvalidate { + background: ; +} + +#main_pane_left { + width: 60%; + min-width: 260px; + float: ; + padding-top: 1em; +} + +#main_pane_right { + overflow: hidden; + min-width: 160px; + padding-top: 1em; + padding-: 1em; + padding-: .5em; +} + +.group { + + border: 1px solid #999; + background: #f3f3f3; + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + border-radius: 4px; + -moz-box-shadow: 2px 2px 5px #ccc; + -webkit-box-shadow: 2px 2px 5px #ccc; + box-shadow: 2px 2px 5px #ccc; + margin-bottom: 1em; + padding-bottom: 1em; +} + +.group h2 { + background-color: #bbb; + padding: .1em .3em; + margin-top: 0; + color: #fff; + font-size: 1.6em; + font-weight: normal; + text-shadow: 0 1px 0 #777; + -moz-box-shadow: 1px 1px 15px #999 inset; + -webkit-box-shadow: 1px 1px 15px #999 inset; + box-shadow: 1px 1px 15px #999 inset; +} + +.group-cnt { + padding: 0; + padding-: .5em; + display: inline-block; + width: 98%; +} + +textarea#partitiondefinition { + height: 3em; +} + + +/* for elements that should be revealed only via js */ +.hide { + display: none; +} + +#list_server { + list-style-type: none; + padding: 0; +} + +/** + * Progress bar styles + */ +div.upload_progress +{ + width: 400px; + margin: 3em auto; + text-align: center; +} + +div.upload_progress_bar_outer +{ + border: 1px solid #000; + width: 202px; + position: relative; + margin: 0 auto 1em; + color: ; +} + +div.upload_progress_bar_inner +{ + background-color: ; + width: 0; + height: 12px; + margin: 1px; + overflow: hidden; + color: ; + position: relative; +} + +div.upload_progress_bar_outer div.percentage +{ + position: absolute; + top: 0; + : 0; + width: 202px; +} + +div.upload_progress_bar_inner div.percentage +{ + top: -1px; + : -1px; +} + +div#statustext { + margin-top: .5em; +} + +table#serverconnection_src_remote, +table#serverconnection_trg_remote, +table#serverconnection_src_local, +table#serverconnection_trg_local { + float: ; +} +/** + * Validation error message styles + */ +input[type=text].invalid_value, +input[type=password].invalid_value, +input[type=number].invalid_value, +input[type=date].invalid_value, +select.invalid_value, +.invalid_value { + background: #FFCCCC; +} + +/** + * Ajax notification styling + */ + .ajax_notification { + top: 0; /** The notification needs to be shown on the top of the page */ + position: fixed; + margin-top: 0; + margin-right: auto; + margin-bottom: 0; + margin-: auto; + padding: 5px; /** Keep a little space on the sides of the text */ + width: 350px; + + z-index: 1100; /** If this is not kept at a high z-index, the jQueryUI modal dialogs (z-index: 1000) might hide this */ + text-align: center; + display: inline; + left: 0; + right: 0; + background-image: url(getImgPath('ajax_clock_small.gif');?>); + background-repeat: no-repeat; + background-position: 2%; + border: 1px solid #e2b709; + } + +/* additional styles */ +.ajax_notification { + margin-top: 200px; + background: #ffe57e; + border-radius: 5px; + -moz-border-radius: 5px; + -webkit-border-radius: 5px; + box-shadow: 0 5px 90px #888; + -moz-box-shadow: 0 5px 90px #888; + -webkit-box-shadow: 0 5px 90px #888; +} + +#loading_parent { + /** Need this parent to properly center the notification division */ + position: relative; + width: 100%; + } +/** + * Export and Import styles + */ + +.export_table_list_container { + display: inline-block; + max-height: 20em; + overflow-y: scroll; +} + +.export_table_select th { + text-align: center; + vertical-align: middle; +} + +.export_table_select .all { + font-weight: bold; + border-bottom: 1px solid black; +} + +.export_structure, .export_data { + text-align: center; +} + +.export_table_name { + vertical-align: middle; +} + +.exportoptions h2 { + word-wrap: break-word; +} + +.exportoptions h3, +.importoptions h3 { + border-bottom: 1px #999 solid; + font-size: 110%; +} + +.exportoptions ul, +.importoptions ul, +.format_specific_options ul { + list-style-type: none; + margin-bottom: 15px; +} + +.exportoptions li, +.importoptions li { + margin: 7px; +} +.exportoptions label, +.importoptions label, +.exportoptions p, +.importoptions p { + margin: 5px; + float: none; +} + +#csv_options label.desc, +#ldi_options label.desc, +#latex_options label.desc, +#output label.desc { + float: ; + width: 15em; +} + +.exportoptions, +.importoptions { + margin: 20px 30px 30px; + margin-: 10px; +} + +.exportoptions #buttonGo, +.importoptions #buttonGo { + font-weight: bold; + margin-: 14px; + border: 1px solid #aaa; + padding: 5px 12px; + color: #111; + text-decoration: none; + + border-radius: 12px; + -webkit-border-radius: 12px; + -moz-border-radius: 12px; + + text-shadow: 0 1px 0 #fff; + + getCssGradient('ffffff', 'cccccc'); ?> + cursor: pointer; +} + +.format_specific_options h3 { + margin: 10px 0 0; + margin-: 10px; + border: 0; +} + +.format_specific_options { + border: 1px solid #999; + margin: 7px 0; + padding: 3px; +} + +p.desc { + margin: 5px; +} + +/** + * Export styles only + */ +select#db_select, +select#table_select { + width: 400px; +} + +.export_sub_options { + margin: 20px 0 0; + margin-: 30px; +} + +.export_sub_options h4 { + border-bottom: 1px #999 solid; +} + +.export_sub_options li.subgroup { + display: inline-block; + margin-top: 0; +} + +.export_sub_options li { + margin-bottom: 0; +} +#export_refresh_form { + margin-left: 20px; +} +#export_back_button { + display: inline; +} +#output_quick_export { + display: none; +} +/** + * Import styles only + */ + +.importoptions #import_notification { + margin: 10px 0; + font-style: italic; +} + +input#input_import_file { + margin: 5px; +} + +.formelementrow { + margin: 5px 0 5px 0; +} + +#filterText { + vertical-align: baseline; +} + +#popup_background { + display: none; + position: fixed; + _position: absolute; /* hack for IE6 */ + width: 100%; + height: 100%; + top: 0; + : 0; + background: #000; + z-index: 1000; + overflow: hidden; +} + +/** + * Table structure styles + */ +#fieldsForm ul.table-structure-actions { + margin: 0; + padding: 0; + list-style: none; +} +#fieldsForm ul.table-structure-actions li { + float: ; + margin-: 0.3em; /* same as padding of "table td" */ +} +#fieldsForm ul.table-structure-actions .submenu li { + padding: 0; + margin: 0; +} +#fieldsForm ul.table-structure-actions .submenu li span { + padding: 0.3em; + margin: 0.1em; +} +#structure-action-links a { + margin-: 1em; +} +#addColumns input[type="radio"] { + margin: 3px 0 0; + margin-: 1em; +} +/** + * Indexes + */ +#index_frm .index_info input[type="text"], +#index_frm .index_info select { + width: 100%; + margin: 0; + box-sizing: border-box; + -ms-box-sizing: border-box; + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; +} + +#index_frm .index_info div { + padding: .2em 0; +} + +#index_frm .index_info .label { + float: ; + min-width: 12em; +} + +#index_frm .slider { + width: 10em; + margin: .6em; + float: ; +} + +#index_frm .add_fields { + float: ; +} + +#index_frm .add_fields input { + margin-: 1em; +} + +#index_frm input { + margin: 0; +} + +#index_frm td { + vertical-align: middle; +} + +table#index_columns { + width: 100%; +} + +table#index_columns select { + width: 85%; + float: ; +} + +#move_columns_dialog div { + padding: 1em; +} + +#move_columns_dialog ul { + list-style: none; + margin: 0; + padding: 0; +} + +#move_columns_dialog li { + background: ; + border: 1px solid #aaa; + color: ; + font-weight: bold; + margin: .4em; + padding: .2em; + -webkit-border-radius: 2px; + -moz-border-radius: 2px; + border-radius: 2px; +} + +/* config forms */ +.config-form ul.tabs { + margin: 1.1em .2em 0; + padding: 0 0 .3em 0; + list-style: none; + font-weight: bold; +} + +.config-form ul.tabs li { + float: ; + margin-bottom: -1px; +} + +.config-form ul.tabs li a { + display: block; + margin: .1em .2em 0; + white-space: nowrap; + text-decoration: none; + border: 1px solid ; + border-bottom: 1px solid #aaa; +} + +.config-form ul.tabs li a { + padding: 7px 10px; + -webkit-border-radius: 5px 5px 0 0; + -moz-border-radius: 5px 5px 0 0; + border-radius: 5px 5px 0 0; + background: #f2f2f2; + color: #555; + text-shadow: 0 1px 0 #fff; +} + +.config-form ul.tabs li a:hover, +.config-form ul.tabs li a:active { + background: #e5e5e5; +} + +.config-form ul.tabs li.active a { + background-color: #fff; + margin-top: 1px; + color: #000; + text-shadow: none; + border-color: #aaa; + border-bottom: 1px solid #fff; +} + +.config-form fieldset { + margin-top: 0; + padding: 0; + clear: both; + -webkit-border-radius: 0; + -moz-border-radius: 0; + border-radius: 0; +} + +.config-form legend { + display: none; +} + +.config-form fieldset p { + margin: 0; + padding: .5em; + background: #fff; + border-top: 0; +} + +.config-form fieldset .errors { /* form error list */ + margin: 0 -2px 1em; + padding: .5em 1.5em; + background: #FBEAD9; + border: 0 #C83838 solid; + border-width: 1px 0; + list-style: none; + font-family: sans-serif; + font-size: small; +} + +.config-form fieldset .inline_errors { /* field error list */ + margin: .3em .3em .3em; + margin-: 0; + padding: 0; + list-style: none; + color: #9A0000; + font-size: small; +} + +.config-form fieldset th { + padding: .3em .3em .3em; + padding-: .5em; + text-align: ; + vertical-align: top; + width: 40%; + background: transparent; + filter: none; +} + +.config-form fieldset .doc, +.config-form fieldset .disabled-notice { + margin-: 1em; +} + +.config-form fieldset .disabled-notice { + font-size: 80%; + text-transform: uppercase; + color: #E00; + cursor: help; +} + +.config-form fieldset td { + padding-top: .3em; + padding-bottom: .3em; + vertical-align: top; +} + +.config-form fieldset th small { + display: block; + font-weight: normal; + font-family: sans-serif; + font-size: x-small; + color: #444; +} + +.config-form fieldset th, +.config-form fieldset td { + border-top: 1px solid; + border-: none; +} + +fieldset .group-header th { + background: ; +} + +fieldset .group-header + tr th { + padding-top: .6em; +} + +fieldset .group-field-1 th, +fieldset .group-header-2 th { + padding-: 1.5em; +} + +fieldset .group-field-2 th, +fieldset .group-header-3 th { + padding-: 3em; +} + +fieldset .group-field-3 th { + padding-: 4.5em; +} + +fieldset .disabled-field th, +fieldset .disabled-field th small, +fieldset .disabled-field td { + color: #666; + background-color: #ddd; +} + +.config-form .lastrow { + border-top: 1px #000 solid; +} + +.config-form .lastrow { + background: ; + padding: .5em; + text-align: center; +} + +.config-form .lastrow input { + font-weight: bold; +} + +/* form elements */ + +.config-form span.checkbox { + padding: 2px; + display: inline-block; +} + +.config-form .custom { /* customized field */ + background: #FFC; +} + +.config-form span.checkbox.custom { + padding: 1px; + border: 1px #EDEC90 solid; + background: #FFC; +} + +.config-form .field-error { + border-color: #A11 !important; +} + +.config-form input[type="text"], +.config-form input[type="password"], +.config-form input[type="number"], +.config-form select, +.config-form textarea { + border: 1px #A7A6AA solid; + height: auto; +} + +.config-form input[type="text"]:focus, +.config-form input[type="password"]:focus, +.config-form input[type="number"]:focus, +.config-form select:focus, +.config-form textarea:focus { + border: 1px #6676FF solid; + background: #F7FBFF; +} + +.config-form .field-comment-mark { + font-family: serif; + color: #007; + cursor: help; + padding: 0 .2em; + font-weight: bold; + font-style: italic; +} + +.config-form .field-comment-warning { + color: #A00; +} + +/* error list */ +.config-form dd { + margin-: .5em; +} + +.config-form dd:before { + content: "\25B8 "; +} + +.click-hide-message { + cursor: pointer; +} + +.prefsmanage_opts { + margin-: 2em; +} + +#prefs_autoload { + margin-bottom: .5em; + margin-left: .5em; +} + +#placeholder .button { + position: absolute; + cursor: pointer; +} + +#placeholder div.button { + font-size: smaller; + color: #999; + background-color: #eee; + padding: 2px; +} + +.wrapper { + float: ; + margin-bottom: 1.5em; +} +.toggleButton { + position: relative; + cursor: pointer; + font-size: .8em; + text-align: center; + line-height: 1.4em; + height: 1.55em; + overflow: hidden; + border-right: .1em solid #888; + border-left: .1em solid #888; + -webkit-border-radius: .3em; + -moz-border-radius: .3em; + border-radius: .3em; +} +.toggleButton table, +.toggleButton td, +.toggleButton img { + padding: 0; + position: relative; +} +.toggleButton .container { + position: absolute; +} +.toggleButton .container td, +.toggleButton .container tr { + background-image: none; + background: none !important; +} +.toggleButton .toggleOn { + color: #fff; + padding: 0 1em; + text-shadow: 0 0 .2em #000; +} +.toggleButton .toggleOff { + padding: 0 1em; +} + +.doubleFieldset fieldset { + width: 48%; + float: ; + padding: 0; +} +.doubleFieldset fieldset.left { + margin-: 1%; +} +.doubleFieldset fieldset.right { + margin-: 1%; +} +.doubleFieldset legend { + margin-: 1.5em; +} +.doubleFieldset div.wrap { + padding: 1.5em; +} + +#table_name_col_no_outer { + margin-top: 45px; +} + +#table_name_col_no { + position: fixed; + top: 55px; + width: 100%; + background: #ffffff; +} + +#table_columns input[type="text"], +#table_columns input[type="password"], +#table_columns input[type="number"], +#table_columns select { + width: 10em; + box-sizing: border-box; + -ms-box-sizing: border-box; + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; +} + +#placeholder { + position: relative; + border: 1px solid #aaa; + float: ; + overflow: hidden; + width: 450px; + height: 300px; +} + +#openlayersmap{ + width: 450px; + height: 300px; +} + +.placeholderDrag { + cursor: move; +} + +#placeholder .button { + position: absolute; +} + +#left_arrow { + left: 8px; + top: 26px; +} + +#right_arrow { + left: 26px; + top: 26px; +} + +#up_arrow { + left: 17px; + top: 8px; +} + +#down_arrow { + left: 17px; + top: 44px; +} + +#zoom_in { + left: 17px; + top: 67px; +} + +#zoom_world { + left: 17px; + top: 85px; +} + +#zoom_out { + left: 17px; + top: 103px; +} + +.colborder { + cursor: col-resize; + height: 100%; + margin-: -6px; + position: absolute; + width: 5px; +} + +.colborder_active { + border-: 2px solid #a44; +} + +.pma_table td { + position: static; +} + +.pma_table th.draggable span, +.sticky_columns th.draggable span, +.pma_table tbody td span { + display: block; + overflow: hidden; +} + +.pma_table tbody td span code span { + display: inline; +} + +.pma_table th.draggable.right span { + margin-: 0px; +} + +.pma_table th.draggable span { + margin-: 10px; +} + +.modal-copy input { + display: block; + width: 100%; + margin-top: 1.5em; + padding: .3em 0; +} + +.cRsz { + position: absolute; +} + +.cCpy { + background: #333; + color: #FFF; + font-weight: bold; + margin: .1em; + padding: .3em; + position: absolute; + text-shadow: -1px -1px #000; + + -moz-box-shadow: 0 0 .7em #000; + -webkit-box-shadow: 0 0 .7em #000; + box-shadow: 0 0 .7em #000; + -moz-border-radius: .3em; + -webkit-border-radius: .3em; + border-radius: .3em; +} + +.cPointer { + background: url(getImgPath('col_pointer.png');?>); + height: 20px; + margin-: -5px; /* must be minus half of its width */ + margin-top: -10px; + position: absolute; + width: 10px; +} + +.tooltip { + background: #333 !important; + opacity: .8 !important; + border: 1px solid #000 !important; + -moz-border-radius: .3em !important; + -webkit-border-radius: .3em !important; + border-radius: .3em !important; + text-shadow: -1px -1px #000 !important; + font-size: .8em !important; + font-weight: bold !important; + padding: 1px 3px !important; +} + +.tooltip * { + background: none !important; + color: #FFF !important; +} + +.cDrop { + left: 0; + position: absolute; + top: 0; +} + +.coldrop { + background: url(getImgPath('col_drop.png');?>); + cursor: pointer; + height: 16px; + margin-: .3em; + margin-top: .3em; + position: absolute; + width: 16px; +} + +.coldrop:hover, +.coldrop-hover { + background-color: #999; +} + +.cList { + background: #EEE; + border: solid 1px #999; + position: absolute; + -moz-box-shadow: 0 .2em .5em #333; + -webkit-box-shadow: 0 .2em .5em #333; + box-shadow: 0 .2em .5em #333; +} + +.cList .lDiv div { + padding: .2em .5em .2em; + padding-: .2em; +} + +.cList .lDiv div:hover { + background: #DDD; + cursor: pointer; +} + +.cList .lDiv div input { + cursor: pointer; +} + +.showAllColBtn { + border-bottom: solid 1px #999; + border-top: solid 1px #999; + cursor: pointer; + font-size: .9em; + font-weight: bold; + padding: .35em 1em; + text-align: center; +} + +.showAllColBtn:hover { + background: #DDD; +} + +.turnOffSelect { + -moz-user-select: none; + -khtml-user-select: none; + -webkit-user-select: none; + user-select: none; +} + +.navigation { + margin: .8em 0; + + border-radius: 5px; + -webkit-border-radius: 5px; + -moz-border-radius: 5px; + + getCssGradient('eeeeee', 'cccccc'); ?> +} + +.navigation td { + margin: 0; + padding: 0; + vertical-align: middle; + white-space: nowrap; +} + +.navigation_separator { + color: #999; + display: inline-block; + font-size: 1.5em; + text-align: center; + height: 1.4em; + width: 1.2em; + text-shadow: 1px 0 #FFF; +} + +.navigation input[type=submit] { + background: none; + border: 0; + filter: none; + margin: 0; + padding: .8em .5em; + + border-radius: 0; + -webkit-border-radius: 0; + -moz-border-radius: 0; +} + +.navigation input[type=submit]:hover, +.navigation input.edit_mode_active { + color: #fff; + cursor: pointer; + text-shadow: none; + + getCssGradient('333333', '555555'); ?> +} + +.navigation select { + margin: 0 .8em; +} + +.cEdit { + margin: 0; + padding: 0; + position: absolute; +} + +.cEdit input[type=text] { + background: #FFF; + height: 100%; + margin: 0; + padding: 0; +} + +.cEdit .edit_area { + background: #FFF; + border: 1px solid #999; + min-width: 10em; + padding: .3em .5em; +} + +.cEdit .edit_area select, +.cEdit .edit_area textarea { + width: 97%; +} + +.cEdit .cell_edit_hint { + color: #555; + font-size: .8em; + margin: .3em .2em; +} + +.cEdit .edit_box { + overflow-x: hidden; + overflow-y: scroll; + padding: 0; + margin: 0; +} + +.cEdit .edit_box_posting { + background: #FFF url(getImgPath('ajax_clock_small.gif');?>) no-repeat right center; + padding-: 1.5em; +} + +.cEdit .edit_area_loading { + background: #FFF url(getImgPath('ajax_clock_small.gif');?>) no-repeat center; + height: 10em; +} + +.cEdit .goto_link { + background: #EEE; + color: #555; + padding: .2em .3em; +} + +.saving_edited_data { + background: url(getImgPath('ajax_clock_small.gif');?>) no-repeat left; + padding-: 20px; +} + +.relationalTable td { + vertical-align: top; +} + +.relationalTable select { + width: 125px; + margin-right: 5px; +} + +/* css for timepicker */ +.ui-timepicker-div .ui-widget-header { margin-bottom: 8px; } +.ui-timepicker-div dl { text-align: ; } +.ui-timepicker-div dl dt { height: 25px; margin-bottom: -25px; } +.ui-timepicker-div dl dd { margin: 0 10px 10px 85px; } +.ui-timepicker-div td { font-size: 90%; } +.ui-tpicker-grid-label { background: none; border: none; margin: 0; padding: 0; } +.ui-timepicker-rtl { direction: rtl; } +.ui-timepicker-rtl dl { text-align: right; } +.ui-timepicker-rtl dl dd { margin: 0 65px 10px 10px; } + +input.btn { + color: #333; + background-color: #D0DCE0; +} + +body .ui-widget { + font-size: 1em; +} + +.ui-dialog fieldset legend a { + color: #235A81; +} + +.ui-draggable { + z-index: 801; +} + +/* over-riding jqplot-yaxis class */ +.jqplot-yaxis { + left:0 !important; + min-width:25px; + width:auto; +} +.jqplot-axis { + overflow:hidden; +} + +.report-data { + height:13em; + overflow:scroll; + width:570px; + border: solid 1px; + background: white; + padding: 2px; +} + +.report-description { + height:10em; + width:570px; +} + +div#page_content div#tableslistcontainer table.data { + border-top: 0.1px solid #EEEEEE; +} + +div#page_content div#tableslistcontainer, div#page_content div.notice, div#page_content div.result_query { + margin-top: 1em; +} + +table.show_create { + margin-top: 1em; +} + +table.show_create td { + border-right: 1px solid #bbb; +} + +#alias_modal table { + width: 100%; +} + +#alias_modal label { + font-weight: bold; +} + +.ui-dialog { + position: fixed; +} + + +.small_font { + font-size: smaller; +} + +/* Console styles */ +#pma_console_container { + width: 100%; + position: fixed; + bottom: 0; + : 0; + z-index: 100; +} +#pma_console { + position: relative; + margin-: 240px; +} +#pma_console .templates { + display: none; +} +#pma_console .mid_text, +#pma_console .toolbar span { + vertical-align: middle; +} +#pma_console .toolbar { + position: relative; + background: #ccc; + border-top: solid 1px #aaa; + cursor: n-resize; +} +#pma_console .toolbar.collapsed:not(:hover) { + display: inline-block; + border-top--radius: 3px; + border-: solid 1px #aaa; +} +#pma_console .toolbar.collapsed { + cursor: default; +} +#pma_console .toolbar.collapsed>.button { + display: none; +} +#pma_console .message span.text, +#pma_console .message span.action, +#pma_console .toolbar .button, +#pma_console .toolbar .text, +#pma_console .switch_button { + padding: 0 3px; + display: inline-block; +} +#pma_console .message span.action, +#pma_console .toolbar .button, +#pma_console .switch_button { + cursor: pointer; +} +#pma_console .message span.action:hover, +#pma_console .toolbar .button:hover, +#pma_console .switch_button:hover, +#pma_console .toolbar .button.active { + background: #ddd; +} +#pma_console .toolbar .text { + font-weight: bold; +} +#pma_console .toolbar .button, +#pma_console .toolbar .text { + margin-: .4em; +} +#pma_console .toolbar .button, +#pma_console .toolbar .text { + float: ; +} +#pma_console .content { + overflow-x: hidden; + overflow-y: auto; + margin-bottom: -65px; + border-top: solid 1px #aaa; + background: #fff; + padding-top: .4em; +} +#pma_console .content.console_dark_theme { + background: #000; + color: #fff; +} +#pma_console .content.console_dark_theme .CodeMirror-wrap { + background: #000; + color: #fff; +} +#pma_console .content.console_dark_theme .action_content { + color: #000; +} +#pma_console .content.console_dark_theme .message { + border-color: #373B41; +} +#pma_console .content.console_dark_theme .CodeMirror-cursor { + border-color: #fff; +} +#pma_console .content.console_dark_theme .cm-keyword { + color: #de935f; +} +#pma_console .message, +#pma_console .query_input { + position: relative; + font-family: Monaco, Consolas, monospace; + cursor: text; + margin: 0 10px .2em 1.4em; +} +#pma_console .message { + border-bottom: solid 1px #ccc; + padding-bottom: .2em; +} +#pma_console .message.expanded>.action_content { + position: relative; +} +#pma_console .message:before, +#pma_console .query_input:before { + left: -0.7em; + position: absolute; + content: ">"; +} +#pma_console .query_input:before { + top: -2px; +} +#pma_console .query_input textarea { + width: 100%; + height: 4em; + resize: vertical; +} +#pma_console .message:hover:before { + color: #7cf; + font-weight: bold; +} +#pma_console .message.expanded:before { + content: "]"; +} +#pma_console .message.welcome:before { + display: none; +} +#pma_console .message.failed:before, +#pma_console .message.failed.expanded:before, +#pma_console .message.failed:hover:before { + content: "="; + color: #944; +} +#pma_console .message.pending:before { + opacity: .3; +} +#pma_console .message.collapsed>.query { + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; +} +#pma_console .message.expanded>.query { + display: block; + white-space: pre; + word-wrap: break-word; +} +#pma_console .message .text.targetdb, +#pma_console .message.collapsed .action.collapse, +#pma_console .message.expanded .action.expand, +#pma_console .message .action.requery, +#pma_console .message .action.profiling, +#pma_console .message .action.explain, +#pma_console .message .action.bookmark { + display: none; +} +#pma_console .message.select .action.profiling, +#pma_console .message.select .action.explain, +#pma_console .message.history .text.targetdb, +#pma_console .message.successed .text.targetdb, +#pma_console .message.history .action.requery, +#pma_console .message.history .action.bookmark, +#pma_console .message.bookmark .action.requery, +#pma_console .message.bookmark .action.bookmark, +#pma_console .message.successed .action.requery, +#pma_console .message.successed .action.bookmark { + display: inline-block; +} +#pma_console .message .action_content { + position: absolute; + bottom: 100%; + background: #ccc; + border: solid 1px #aaa; + border-top--radius: 3px; +} +html.ie8 #pma_console .message .action_content { + position: relative!important; +} +#pma_console .message.bookmark .text.targetdb, +#pma_console .message .text.query_time { + margin: 0; + display: inline-block; +} +#pma_console .message.failed .text.query_time, +#pma_console .message .text.failed { + display: none; +} +#pma_console .message.failed .text.failed { + display: inline-block; +} +#pma_console .message .text { + background: #fff; +} +#pma_console .message.collapsed>.action_content { + display: none; +} +#pma_console .message.collapsed:hover>.action_content { + display: block; +} +#pma_console .message .bookmark_label { + padding: 0 4px; + top: 0; + background: #369; + color: #fff; + border-radius: 3px; +} +#pma_console .message .bookmark_label.shared { + background: #396; +} +#pma_console .message.expanded .bookmark_label { + border-top-left-radius: 0; + border-top-right-radius: 0; +} +#pma_console .query_input { + position: relative; +} +#pma_console .mid_layer { + height: 100%; + width: 100%; + position: absolute; + top: 0; + /* For support IE8, this layer doesn't use filter:opacity or opacity, + js code will fade this layer opacity to 0.18(using animation) */ + background: #666; + display: none; + cursor: pointer; + z-index: 200; +} +#pma_console .card { + position: absolute; + width: 94%; + height: 100%; + min-height: 48px; + : 100%; + top: 0; + border-: solid 1px #999; + z-index: 300; + transition: 0.2s; + -ms-transition: 0.2s; + -webkit-transition: 0.2s; + -moz-transition: 0.2s; +} +#pma_console .card.show { + : 6%; + box-shadow: -2px 1px 4px -1px #999; +} + +html.ie7 #pma_console .query_input { + display: none; +} + +#pma_bookmarks .content.add_bookmark, +#pma_console_options .content { + padding: 4px 6px; +} +#pma_bookmarks .content.add_bookmark .options { + margin-: 1.4em; + padding-bottom: .4em; + margin-bottom: .4em; + border-bottom: solid 1px #ccc; +} +#pma_bookmarks .content.add_bookmark .options button { + margin: 0 7px; + vertical-align: bottom; +} +#pma_bookmarks .content.add_bookmark input[type=text] { + margin: 0; + padding: 2px 4px; +} +#pma_console .button.hide, +#pma_console .message span.text.hide { + display: none; +} +#debug_console.grouped .ungroup_queries, +#debug_console.ungrouped .group_queries { + display: inline-block; +} +#debug_console.ungrouped .ungroup_queries, +#debug_console.ungrouped .sort_count, +#debug_console.grouped .group_queries { + display: none; +} +#debug_console .count { + margin-right: 8px; +} +#debug_console .show_trace .trace, +#debug_console .show_args .args { + display: block; +} +#debug_console .hide_trace .trace, +#debug_console .hide_args .args, +#debug_console .show_trace .action.dbg_show_trace, +#debug_console .hide_trace .action.dbg_hide_trace, +#debug_console .traceStep.hide_args .action.dbg_hide_args, +#debug_console .traceStep.show_args .action.dbg_show_args { + display: none; +} + +#debug_console .traceStep:after, +#debug_console .trace.welcome:after, +#debug_console .debug>.welcome:after { + content: ""; + display: table; + clear: both; +} +#debug_console .debug_summary { + float: left; +} +#debug_console .trace.welcome .time { + float: right; +} +#debug_console .traceStep .file, +#debug_console .script_name { + float: right; +} +#debug_console .traceStep .args pre { + margin: 0; +} + +/* Code mirror console style*/ + +.cm-s-pma .CodeMirror-code pre, +.cm-s-pma .CodeMirror-code { + font-family: Monaco, Consolas, monospace; +} +.cm-s-pma .CodeMirror-measure>pre, +.cm-s-pma .CodeMirror-code>pre, +.cm-s-pma .CodeMirror-lines { + padding: 0; +} +.cm-s-pma.CodeMirror { + resize: none; + height: auto; + width: 100%; + min-height: initial; + max-height: initial; +} +.cm-s-pma .CodeMirror-scroll { + cursor: text; +} + +/* PMA drop-improt style */ + +.pma_drop_handler { + display: none; + position: fixed; + top: 0; + left: 0; + width: 100%; + background: rgba(0, 0, 0, 0.6); + height: 100%; + z-index: 999; + color: white; + font-size: 30pt; + text-align: center; + padding-top: 20%; +} + +.pma_sql_import_status { + display: none; + position: fixed; + bottom: 0; + right: 25px; + width: 400px; + border: 1px solid #999; + background: #f3f3f3; + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + border-radius: 4px; + -moz-box-shadow: 2px 2px 5px #ccc; + -webkit-box-shadow: 2px 2px 5px #ccc; + box-shadow: 2px 2px 5px #ccc; +} + +.pma_sql_import_status h2, +.pma_drop_result h2 { + background-color: #bbb; + padding: .1em .3em; + margin-top: 0; + margin-bottom: 0; + color: #fff; + font-size: 1.6em; + font-weight: normal; + text-shadow: 0 1px 0 #777; + -moz-box-shadow: 1px 1px 15px #999 inset; + -webkit-box-shadow: 1px 1px 15px #999 inset; + box-shadow: 1px 1px 15px #999 inset; +} + +.pma_sql_import_status div { + height: 270px; + overflow-y:auto; + overflow-x:hidden; + list-style-type: none; +} + +.pma_sql_import_status div li { + padding: 8px 10px; + border-bottom: 1px solid #bbb; + color: rgb(148, 14, 14); + background: white; +} + +.pma_sql_import_status div li .filesize { + float: right; +} + +.pma_sql_import_status h2 .minimize { + float: right; + margin-right: 5px; + padding: 0 10px; +} + +.pma_sql_import_status h2 .close { + float: right; + margin-right: 5px; + padding: 0 10px; + display: none; +} + +.pma_sql_import_status h2 .minimize:hover, +.pma_sql_import_status h2 .close:hover, +.pma_drop_result h2 .close:hover { + background: rgba(155, 149, 149, 0.78); + cursor: pointer; +} + +.pma_drop_file_status { + color: #235a81; +} + +.pma_drop_file_status span.underline:hover { + cursor: pointer; + text-decoration: underline; +} + +.pma_drop_result { + position: fixed; + top: 10%; + left: 20%; + width: 60%; + background: white; + min-height: 300px; + z-index: 800; + -webkit-box-shadow: 0 0 15px #999; + border-radius: 10px; + cursor: move; +} + +.pma_drop_result h2 .close { + float: right; + margin-right: 5px; + padding: 0 10px; +} + +.dependencies_box { + background-color: white; + border: 3px ridge black; +} + +#composite_index_list { + list-style-type: none; + list-style-position: inside; +} + +span.drag_icon { + display: inline-block; + background-image: url('getImgPath('s_sortable.png');?>'); + background-position: center center; + background-repeat: no-repeat; + width: 1em; + height: 3em; + cursor: move; +} + +.topmargin { + margin-top: 1em; +} + +meter[value="1"]::-webkit-meter-optimum-value { + background: linear-gradient(white 3%, #E32929 5%, transparent 10%, #E32929); +} +meter[value="2"]::-webkit-meter-optimum-value { + background: linear-gradient(white 3%, #FF6600 5%, transparent 10%, #FF6600); +} +meter[value="3"]::-webkit-meter-optimum-value { + background: linear-gradient(white 3%, #FFD700 5%, transparent 10%, #FFD700); +} + +/* styles for sortable tables created with tablesorter jquery plugin */ +th.header { + cursor: pointer; + color: #235a81; +} + +th.header:hover { + text-decoration: underline; +} + +th.header .sorticon { + width: 16px; + height: 16px; + background-repeat: no-repeat; + background-position: right center; + display: inline-table; + vertical-align: middle; + float: right; +} + +th.headerSortUp .sorticon, th.headerSortDown:hover .sorticon { + background-image: url(getImgPath('s_desc.png');?>); +} + +th.headerSortDown .sorticon, th.headerSortUp:hover .sorticon { + background-image: url(getImgPath('s_asc.png');?>); +} +/* end of styles of sortable tables */ + +/* styles for jQuery-ui to support rtl languages */ +body .ui-dialog .ui-dialog-titlebar-close { + : .3em; + : initial; +} + +body .ui-dialog .ui-dialog-title { + float: ; +} + +body .ui-dialog .ui-dialog-buttonpane .ui-dialog-buttonset { + float: ; +} +/* end of styles for jQuery-ui to support rtl languages */ + +@media only screen and (max-width: 768px) { + /* For mobile phones: */ + #main_pane_left { + width: 100%; + } + + #main_pane_right { + padding-top: 0; + padding-: 1px; + padding-: 1px; + } + + ul#topmenu, + ul.tabs { + display: flex; + } + + .navigationbar { + display: inline-flex; + margin: 0 !important; + border-radius: 0 !important; + overflow: auto; + } + + .scrollindicator { + padding: 5px; + cursor: pointer; + display: inline; + } + + .responsivetable { + overflow-x: auto; + } + + body#loginform div.container { + width: 100%; + } + + .largescreenonly { + display: none; + } + + .width100, .desktop50 { + width: 100%; + } + + .width96 { + width: 96% !important; + } + + #page_nav_icons { + display: none; + } + + table#serverstatusconnections { + margin-left: 0; + } + + #table_name_col_no { + top: 62px + } + + .tdblock tr td { + display: block; + } + + #table_columns { + margin-top: 60px; + } + + #table_columns .tablesorter { + min-width: 100%; + } + + .doubleFieldset fieldset { + width: 98%; + } + + div#serverstatusquerieschart { + width: 100%; + height: 450px; + } + + .ui-dialog { + margin: 1%; + width: 95% !important; + } +} +/* templates/database/designer */ +/* side menu */ +#name-panel { + overflow:hidden; +} diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/css/designer.css.php b/php/apps/phpmyadmin49/html/themes/pmahomme/css/designer.css.php new file mode 100644 index 00000000..d407ad14 --- /dev/null +++ b/php/apps/phpmyadmin49/html/themes/pmahomme/css/designer.css.php @@ -0,0 +1,515 @@ +getImgPath('designer/Header.png'); +$headerLinkedImg = $theme->getImgPath('designer/Header_Linked.png'); +$minusImg = $theme->getImgPath('designer/minus.png'); +$plusImg = $theme->getImgPath('designer/plus.png'); +$leftPanelButtonImg = $theme->getImgPath('designer/left_panel_butt.png'); +$topPanelImg = $theme->getImgPath('designer/top_panel.png'); +$smallTabImg = $theme->getImgPath('designer/small_tab.png'); +$frams1Img = $theme->getImgPath('designer/1.png'); +$frams2Img = $theme->getImgPath('designer/2.png'); +$frams3Img = $theme->getImgPath('designer/3.png'); +$frams4Img = $theme->getImgPath('designer/4.png'); +$frams5Img = $theme->getImgPath('designer/5.png'); +$frams6Img = $theme->getImgPath('designer/6.png'); +$frams7Img = $theme->getImgPath('designer/7.png'); +$frams8Img = $theme->getImgPath('designer/8.png'); +$resizeImg = $theme->getImgPath('designer/resize.png'); +?> + +/* Designer */ +.input_tab { + background-color: #A6C7E1; + color: #000; +} + +.content_fullscreen { + position: relative; + overflow: auto; +} + +#canvas_outer { + position: relative; + width: 100%; + display: block; +} + +#canvas { + background-color: #fff; + color: #000; +} + +canvas.designer { + display: inline-block; + overflow: hidden; + text-align: left; +} + +canvas.designer * { + behavior: url(#default#VML); +} + +.designer_tab { + background-color: #fff; + color: #000; + border-collapse: collapse; + border: 1px solid #aaa; + z-index: 1; + -moz-user-select: none; +} + +.designer_tab .header { + background-image: url(); + background-repeat: repeat-x; +} + +.tab_zag { + text-align: center; + cursor: move; + padding: 1px; + font-weight: bold; +} + +.tab_zag_2 { + background-image: url(); + background-repeat: repeat-x; + text-align: center; + cursor: move; + padding: 1px; + font-weight: bold; +} + +.tab_field { + background: #fff; + color: #000; + cursor: default; +} + +.tab_field:hover, .tab_field_3:hover { + background-color: #CCFFCC; + color: #000; + background-repeat: repeat-x; + cursor: default; +} + +.tab_field_3 { + background-color: #FFE6E6; /*#DDEEFF*/ + color: #000; + cursor: default; +} + +#designer_hint { + white-space: nowrap; + position: absolute; + background-color: #99FF99; + color: #000; + z-index: 3; + border: #00CC66 solid 1px; + display: none; +} + +.scroll_tab { + overflow: auto; + width: 100%; + height: 500px; +} + +.designer_Tabs { + cursor: default; + color: #0055bb; + white-space: nowrap; + text-decoration: none; + text-indent: 3px; + font-weight: bold; + margin-left: 2px; + text-align: ; + background-color: #fff; + background-image: url(); + border: #ccc solid 1px; +} + +.designer_Tabs:hover { + cursor: default; + color: #0055bb; + background: #FFEE99; + text-indent: 3px; + font-weight: bold; + white-space: nowrap; + text-decoration: none; + border: #9999FF solid 1px; + text-align: ; +} + +.owner { + font-weight: normal; + color: #888; +} + +.option_tab { + padding-left: 2px; + padding-right: 2px; + width: 5px; +} + +.select_all { + vertical-align: top; + padding-left: 2px; + padding-right: 2px; + cursor: default; + width: 1px; + color: #000; + background-image: url(); + background-repeat: repeat-x; +} + +.small_tab { + vertical-align: top; + background-color: #0064ea; + color: #fff; + background-image: url(); + cursor: default; + text-align: center; + font-weight: bold; + padding-left: 2px; + padding-right: 2px; + width: 1px; + text-decoration: none; +} + +.small_tab:hover { + vertical-align: top; + color: #fff; + background-color: #FF9966; + cursor: default; + padding-left: 2px; + padding-right: 2px; + text-align: center; + font-weight: bold; + width: 1px; + text-decoration: none; +} + +.small_tab_pref { + background-image: url(); + background-repeat: repeat-x; + text-align: center; + width: 1px; +} + +.small_tab_pref:hover { + vertical-align: top; + color: #fff; + background-color: #FF9966; + cursor: default; + text-align: center; + font-weight: bold; + width: 1px; + text-decoration: none; +} + +.butt { + border: #4477aa solid 1px; + font-weight: bold; + height: 19px; + width: 70px; + background-color: #fff; + color: #000; + vertical-align: baseline; +} + +.L_butt2_1 { + padding: 1px; + text-decoration: none; + vertical-align: middle; + cursor: default; +} + +.L_butt2_1:hover { + padding: 0; + border: #0099CC solid 1px; + background: #FFEE99; + color: #000; + text-decoration: none; + vertical-align: middle; + cursor: default; +} + +/* ---------------------------------------------------------------------------*/ +.bor { + width: 10px; + height: 10px; +} + +.frams1 { + background: url() no-repeat right bottom; +} + +.frams2 { + background: url() no-repeat left bottom; +} + +.frams3 { + background: url() no-repeat left top; +} + +.frams4 { + background: url() no-repeat right top; +} + +.frams5 { + background: url() repeat-x center bottom; +} + +.frams6 { + background: url() repeat-y left; +} + +.frams7 { + background: url() repeat-x top; +} + +.frams8 { + background: url() repeat-y right; +} + +#osn_tab { + position: absolute; + background-color: #fff; + color: #000; +} + +.designer_header { + background-color: #EAEEF0; + color: #000; + text-align: center; + font-weight: bold; + margin: 0; + padding: 0; + background-image: url(); + background-position: top; + background-repeat: repeat-x; + border-right: #999 solid 1px; + border-left: #999 solid 1px; + height: 28px; + z-index: 101; + width: 100%; + position: fixed; +} + +.designer_header a, .designer_header span{ + display: block; + float: ; + margin: 3px 1px 4px; + height: 20px; + border: 1px dotted #fff; +} + +.designer_header .M_bord { + display: block; + float: ; + margin: 4px; + height: 20px; + width: 2px; +} + +.designer_header a.first { + margin-right: 1em; +} + +.designer_header a.last { + margin-left: 1em; +} + +a.M_butt_Selected_down_IE, +a.M_butt_Selected_down { + border: 1px solid #C0C0BB; + background-color: #99FF99; + color: #000; +} + +a.M_butt_Selected_down_IE:hover, +a.M_butt_Selected_down:hover, +a.M_butt:hover { + border: 1px solid #0099CC; + background-color: #FFEE99; + color: #000; +} + +#layer_menu { + z-index: 98; + position: relative; + float: right; + background-color: #EAEEF0; + border: #999 solid 1px; +} + +#layer_menu.left { + float: left; +} + +#layer_upd_relation { + position: absolute; + : 637px; + top: 224px; + z-index: 100; +} + +#layer_new_relation { + position: absolute; + : 636px; + top: 85px; + z-index: 100; + width: 153px; +} + +#designer_optionse { + position: absolute; + : 636px; + top: 85px; + z-index: 100; + width: 153px; +} + +#layer_menu_sizer { + background-image: url(); + cursor: ew-resize; +} + +#layer_menu_sizer .icon { + margin: 0; +} + +.panel { + position: fixed; + top: 60px; + : 0; + width: 350px; + max-height: 500px; + display: none; + overflow: auto; + padding-top: 34px; + z-index: 102; +} + +a.trigger { + position: fixed; + text-decoration: none; + top: 60px; + : 0; + color: #fff; + padding: 10px 40px 10px 15px; + background: #333 url() 85% 55% no-repeat; + border: 1px solid #444; + display: block; + z-index: 102; +} + +a.trigger:hover { + color: #080808; + background: #fff696 url() 85% 55% no-repeat; + border: 1px solid #999; +} + +a.active.trigger { + background: #222 url() 85% 55% no-repeat; + z-index: 999; +} + +a.active.trigger:hover { + background: #fff696 url() 85% 55% no-repeat; +} + +.toggle_container .block { + background-color: #DBE4E8; + border-top: 1px solid #999; +} + +.history_table { + text-align: center; + cursor: pointer; + background-color: #DBE4E8; +} + +.history_table:hover { + background-color: #9999CC; +} + +#ab { + min-width: 300px; +} + +#ab .ui-accordion-content { + padding: 0; +} + +#box { + display: none; +} + +#foreignkeychk { + text-align: ; + position: absolute; + cursor: pointer; +} + +.side-menu { + float: left; + position: fixed; + width: auto; + height: auto; + background: #efefef; + border: 1px solid grey; + overflow: hidden; + z-index: 50; + padding: 2px; +} + +.side-menu.right { + float: right; + right: 0; +} + +.side-menu .hide { + display: none; +} + +.side-menu a { + display: block; + float: none; + overflow: hidden; +} + +.side-menu img, +.side-menu .text { + float: left; +} + +#name-panel { + border-bottom: 1px solid grey; + text-align: center; + background: #efefef; + width: 100%; + font-size: 1.2em; + padding: 10px; + font-weight: bold; +} + +#container-form { + width: 100%; + position: absolute; + left: 0; +} diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/css/enum_editor.css.php b/php/apps/phpmyadmin49/html/themes/pmahomme/css/enum_editor.css.php new file mode 100644 index 00000000..60429533 --- /dev/null +++ b/php/apps/phpmyadmin49/html/themes/pmahomme/css/enum_editor.css.php @@ -0,0 +1,80 @@ + + +/** + * ENUM/SET editor styles + */ +p.enum_notice { + margin: 5px 2px; + font-size: 80%; +} + +#enum_editor p { + margin-top: 0; + font-style: italic; +} + +#enum_editor .values, +#enum_editor .add { + width: 100%; +} + +#enum_editor .add td { + vertical-align: middle; + width: 50%; + padding: 0 0 0; + padding-: 1em; +} + +#enum_editor .values td.drop { + width: 1.8em; + cursor: pointer; + vertical-align: middle; +} + +#enum_editor .values input { + margin: .1em 0; + padding-: 2em; + width: 100%; +} + +#enum_editor .values img { + width: 1.8em; + vertical-align: middle; +} + +#enum_editor input.add_value { + margin: 0; + margin-: 0.4em; +} + +#enum_editor_output textarea { + width: 100%; + float: ; + margin: 1em 0 0 0; +} + +/** + * ENUM/SET editor integration for the routines editor + */ +.enum_hint { + position: relative; +} + +.enum_hint a { + position: absolute; + : 81%; + bottom: .35em; +} diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/css/gis.css.php b/php/apps/phpmyadmin49/html/themes/pmahomme/css/gis.css.php new file mode 100644 index 00000000..5f7979a7 --- /dev/null +++ b/php/apps/phpmyadmin49/html/themes/pmahomme/css/gis.css.php @@ -0,0 +1,52 @@ + + +/** + * GIS data editor styles + */ +a.close_gis_editor { + float: ; +} + +#gis_editor { + display: none; + position: fixed; + _position: absolute; /* hack for IE */ + z-index: 1001; + overflow-y: auto; + overflow-x: hidden; +} + +#gis_data { + min-height: 230px; +} + +#gis_data_textarea { + height: 6em; +} + +#gis_data_editor { + background: #D0DCE0; + padding: 15px; + min-height: 500px; +} + +#gis_data_editor .choice { + display: none; +} + +#gis_data_editor input[type="text"] { + width: 75px; +} diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/css/icons.css.php b/php/apps/phpmyadmin49/html/themes/pmahomme/css/icons.css.php new file mode 100644 index 00000000..352ca563 --- /dev/null +++ b/php/apps/phpmyadmin49/html/themes/pmahomme/css/icons.css.php @@ -0,0 +1,194 @@ +getImgPath('designer/left_panel_butt.png'); + +// unplanned execution path +if (! defined('PHPMYADMIN') && ! defined('TESTSUITE')) { + exit(); +} +?> +.icon { + margin: 0; + margin-left: .3em; + padding: 0 !important; + width: 16px; + height: 16px; +} +.icon_fulltext { + width: 50px; + height: 19px; +} +.ic_asc_order { background-image: url('getImgPath('asc_order.png'); ?>'); } +.ic_b_bookmark { background-image: url('getImgPath('b_bookmark.png'); ?>'); } +.ic_b_browse { background-image: url('getImgPath('b_browse.png'); ?>'); } +.ic_b_calendar { background-image: url('getImgPath('b_calendar.png'); ?>'); } +.ic_b_chart { background-image: url('getImgPath('b_chart.png'); ?>'); } +.ic_b_close { background-image: url('getImgPath('b_close.png'); ?>'); } +.ic_b_column_add { background-image: url('getImgPath('b_column_add.png'); ?>'); } +.ic_b_comment { background-image: url('getImgPath('b_comment.png'); ?>'); } +.ic_b_dbstatistics { background-image: url('getImgPath('b_dbstatistics.png'); ?>'); } +.ic_b_deltbl { background-image: url('getImgPath('b_deltbl.png'); ?>'); } +.ic_b_docs { background-image: url('getImgPath('b_docs.png'); ?>'); } +.ic_b_docsql { background-image: url('getImgPath('b_docsql.png'); ?>'); } +.ic_b_drop { background-image: url('getImgPath('b_drop.png'); ?>'); } +.ic_b_edit { background-image: url('getImgPath('b_edit.png'); ?>'); } +.ic_b_empty { background-image: url('getImgPath('b_empty.png'); ?>'); } +.ic_b_engine { background-image: url('getImgPath('b_engine.png'); ?>'); } +.ic_b_event_add { background-image: url('getImgPath('b_event_add.png'); ?>'); } +.ic_b_events { background-image: url('getImgPath('b_events.png'); ?>'); } +.ic_b_export { background-image: url('getImgPath('b_export.png'); ?>'); } +.ic_b_favorite { background-image: url('getImgPath('b_favorite.png'); ?>'); } +.ic_b_find_replace { background-image: url('getImgPath('b_find_replace.png'); ?>'); } +.ic_b_firstpage { background-image: url('getImgPath('b_firstpage.png'); ?>'); } +.ic_b_ftext { background-image: url('getImgPath('b_ftext.png'); ?>'); } +.ic_b_globe { background-image: url('getImgPath('b_globe.gif'); ?>'); } +.ic_b_group { background-image: url('getImgPath('b_group.png'); ?>'); } +.ic_b_help { background-image: url('getImgPath('b_help.png'); ?>'); } +.ic_b_home { background-image: url('getImgPath('b_home.png'); ?>'); } +.ic_b_import { background-image: url('getImgPath('b_import.png'); ?>'); } +.ic_b_index { background-image: url('getImgPath('b_index.png'); ?>'); } +.ic_b_index_add { background-image: url('getImgPath('b_index_add.png'); ?>'); } +.ic_b_info { background-image: url('getImgPath('b_info.png'); ?>'); width: 11px; height: 11px; } +.ic_b_inline_edit { background-image: url('getImgPath('b_inline_edit.png'); ?>'); } +.ic_b_insrow { background-image: url('getImgPath('b_insrow.png'); ?>'); } +.ic_b_lastpage { background-image: url('getImgPath('b_lastpage.png'); ?>'); } +.ic_b_minus { background-image: url('getImgPath('b_minus.png'); ?>'); } +.ic_b_more { background-image: url('getImgPath('b_more.png'); ?>'); } +.ic_b_move { background-image: url('getImgPath('b_move.png'); ?>'); } +.ic_b_newdb { background-image: url('getImgPath('b_newdb.png'); ?>'); } +.ic_b_newtbl { background-image: url('getImgPath('b_newtbl.png'); ?>'); } +.ic_b_nextpage { background-image: url('getImgPath('b_nextpage.png'); ?>'); } +.ic_b_no_favorite { background-image: url('getImgPath('b_no_favorite.png'); ?>'); } +.ic_b_pdfdoc { background-image: url('getImgPath('b_pdfdoc.png'); ?>'); } +.ic_b_plugin { background-image: url('getImgPath('b_plugin.png'); ?>'); } +.ic_b_plus { background-image: url('getImgPath('b_plus.png'); ?>'); } +.ic_b_prevpage { background-image: url('getImgPath('b_prevpage.png'); ?>'); } +.ic_b_primary { background-image: url('getImgPath('b_primary.png'); ?>'); } +.ic_b_print { background-image: url('getImgPath('b_print.png'); ?>'); } +.ic_b_props { background-image: url('getImgPath('b_props.png'); ?>'); } +.ic_b_relations { background-image: url('getImgPath('b_relations.png'); ?>'); } +.ic_b_report { background-image: url('getImgPath('b_report.png'); ?>'); } +.ic_b_routine_add { background-image: url('getImgPath('b_routine_add.png'); ?>'); } +.ic_b_routines { background-image: url('getImgPath('b_routines.png'); ?>'); } +.ic_b_save { background-image: url('getImgPath('b_save.png'); ?>'); } +.ic_b_saveimage { background-image: url('getImgPath('b_saveimage.png'); ?>'); } +.ic_b_sbrowse { background-image: url('getImgPath('b_sbrowse.png'); ?>'); } +.ic_b_sdb { background-image: url('getImgPath('b_sdb.png'); ?>'); width: 10px; height: 10px; } +.ic_b_search { background-image: url('getImgPath('b_search.png'); ?>'); } +.ic_b_select { background-image: url('getImgPath('b_select.png'); ?>'); } +.ic_b_snewtbl { background-image: url('getImgPath('b_snewtbl.png'); ?>'); } +.ic_b_spatial { background-image: url('getImgPath('b_spatial.png'); ?>'); } +.ic_b_sql { background-image: url('getImgPath('b_sql.png'); ?>'); } +.ic_b_sqldoc { background-image: url('getImgPath('b_sqldoc.png'); ?>'); } +.ic_b_sqlhelp { background-image: url('getImgPath('b_sqlhelp.png'); ?>'); } +.ic_b_table_add { background-image: url('getImgPath('b_table_add.png'); ?>'); } +.ic_b_tblanalyse { background-image: url('getImgPath('b_tblanalyse.png'); ?>'); } +.ic_b_tblexport { background-image: url('getImgPath('b_tblexport.png'); ?>'); } +.ic_b_tblimport { background-image: url('getImgPath('b_tblimport.png'); ?>'); } +.ic_b_tblops { background-image: url('getImgPath('b_tblops.png'); ?>'); } +.ic_b_tbloptimize { background-image: url('getImgPath('b_tbloptimize.png'); ?>'); } +.ic_b_tipp { background-image: url('getImgPath('b_tipp.png'); ?>'); } +.ic_b_trigger_add { background-image: url('getImgPath('b_trigger_add.png'); ?>'); } +.ic_b_triggers { background-image: url('getImgPath('b_triggers.png'); ?>'); } +.ic_b_undo { background-image: url('getImgPath('b_undo.png'); ?>'); } +.ic_b_unique { background-image: url('getImgPath('b_unique.png'); ?>'); } +.ic_b_usradd { background-image: url('getImgPath('b_usradd.png'); ?>'); } +.ic_b_usrcheck { background-image: url('getImgPath('b_usrcheck.png'); ?>'); } +.ic_b_usrdrop { background-image: url('getImgPath('b_usrdrop.png'); ?>'); } +.ic_b_usredit { background-image: url('getImgPath('b_usredit.png'); ?>'); } +.ic_b_usrlist { background-image: url('getImgPath('b_usrlist.png'); ?>'); } +.ic_b_versions { background-image: url('getImgPath('b_versions.png'); ?>'); } +.ic_b_view { background-image: url('getImgPath('b_view.png'); ?>'); } +.ic_b_view_add { background-image: url('getImgPath('b_view_add.png'); ?>'); } +.ic_b_views { background-image: url('getImgPath('b_views.png'); ?>'); } +.ic_b_left { background-image: url('getImgPath('b_left.png'); ?>'); } +.ic_b_right { background-image: url('getImgPath('b_right.png'); ?>'); } +.ic_bd_browse { background-image: url('getImgPath('bd_browse.png'); ?>'); } +.ic_bd_deltbl { background-image: url('getImgPath('bd_deltbl.png'); ?>'); } +.ic_bd_drop { background-image: url('getImgPath('bd_drop.png'); ?>'); } +.ic_bd_edit { background-image: url('getImgPath('bd_edit.png'); ?>'); } +.ic_bd_empty { background-image: url('getImgPath('bd_empty.png'); ?>'); } +.ic_bd_export { background-image: url('getImgPath('bd_export.png'); ?>'); } +.ic_bd_firstpage { background-image: url('getImgPath('bd_firstpage.png'); ?>'); } +.ic_bd_ftext { background-image: url('getImgPath('bd_ftext.png'); ?>'); } +.ic_bd_index { background-image: url('getImgPath('bd_index.png'); ?>'); } +.ic_bd_insrow { background-image: url('getImgPath('bd_insrow.png'); ?>'); } +.ic_bd_lastpage { background-image: url('getImgPath('bd_lastpage.png'); ?>'); } +.ic_bd_nextpage { background-image: url('getImgPath('bd_nextpage.png'); ?>'); } +.ic_bd_prevpage { background-image: url('getImgPath('bd_prevpage.png'); ?>'); } +.ic_bd_primary { background-image: url('getImgPath('bd_primary.png'); ?>'); } +.ic_bd_routine_add { background-image: url('getImgPath('bd_routine_add.png'); ?>'); } +.ic_bd_sbrowse { background-image: url('getImgPath('bd_sbrowse.png'); ?>'); } +.ic_bd_select { background-image: url('getImgPath('bd_select.png'); ?>'); } +.ic_bd_spatial { background-image: url('getImgPath('bd_spatial.png'); ?>'); } +.ic_bd_unique { background-image: url('getImgPath('bd_unique.png'); ?>'); } +.ic_centralColumns { background-image: url('getImgPath('centralColumns.png'); ?>'); } +.ic_centralColumns_add { background-image: url('getImgPath('centralColumns_add.png'); ?>'); } +.ic_centralColumns_delete { background-image: url('getImgPath('centralColumns_delete.png'); ?>'); } +.ic_col_drop { background-image: url('getImgPath('col_drop.png'); ?>'); } +.ic_console { background-image: url('getImgPath('console.png'); ?>'); } +.ic_database { background-image: url('getImgPath('database.png'); ?>'); } +.ic_eye { background-image: url('getImgPath('eye.png'); ?>'); } +.ic_eye_grey { background-image: url('getImgPath('eye_grey.png'); ?>'); } +.ic_hide { background-image: url('getImgPath('hide.png'); ?>'); } +.ic_item { background-image: url('getImgPath('item.png'); ?>'); width: 9px; height: 9px; } +.ic_lightbulb { background-image: url('getImgPath('lightbulb.png'); ?>'); } +.ic_lightbulb_off { background-image: url('getImgPath('lightbulb_off.png'); ?>'); } +.ic_more { background-image: url('getImgPath('more.png'); ?>'); width: 13px; } +.ic_new_data { background-image: url('getImgPath('new_data.png'); ?>'); } +.ic_new_data_hovered { background-image: url('getImgPath('new_data_hovered.png'); ?>'); } +.ic_new_data_selected { background-image: url('getImgPath('new_data_selected.png'); ?>'); } +.ic_new_data_selected_hovered { background-image: url('getImgPath('new_data_selected_hovered.png'); ?>'); } +.ic_new_struct { background-image: url('getImgPath('new_struct.png'); ?>'); } +.ic_new_struct_hovered { background-image: url('getImgPath('new_struct_hovered.png'); ?>'); } +.ic_new_struct_selected { background-image: url('getImgPath('new_struct_selected.png'); ?>'); } +.ic_new_struct_selected_hovered { background-image: url('getImgPath('new_struct_selected_hovered.png'); ?>'); } +.ic_normalize { background-image: url('getImgPath('normalize.png'); ?>'); } +.ic_pause { background-image: url('getImgPath('pause.png'); ?>'); } +.ic_php_sym { background-image: url('getImgPath('php_sym.png'); ?>'); } +.ic_play { background-image: url('getImgPath('play.png'); ?>'); } +.ic_s_asc { background-image: url('getImgPath('s_asc.png'); ?>'); } +.ic_s_asci { background-image: url('getImgPath('s_asci.png'); ?>'); } +.ic_s_attention { background-image: url('getImgPath('s_attention.png'); ?>'); } +.ic_s_cancel { background-image: url('getImgPath('s_cancel.png'); ?>'); } +.ic_s_cancel2 { background-image: url('getImgPath('s_cancel2.png'); ?>'); } +.ic_s_cog { background-image: url('getImgPath('s_cog.png'); ?>'); } +.ic_s_db { background-image: url('getImgPath('s_db.png'); ?>'); } +.ic_s_desc { background-image: url('getImgPath('s_desc.png'); ?>'); } +.ic_s_error { background-image: url('getImgPath('s_error.png'); ?>'); } +.ic_s_host { background-image: url('getImgPath('s_host.png'); ?>'); } +.ic_s_info { background-image: url('getImgPath('s_info.png'); ?>'); } +.ic_s_lang { background-image: url('getImgPath('s_lang.png'); ?>'); } +.ic_s_link { background-image: url('getImgPath('s_link.png'); ?>'); } +.ic_s_lock { background-image: url('getImgPath('s_lock.png'); ?>'); } +.ic_s_loggoff { background-image: url('getImgPath('s_loggoff.png'); ?>'); } +.ic_s_notice { background-image: url('getImgPath('s_notice.png'); ?>'); } +.ic_s_okay { background-image: url('getImgPath('s_okay.png'); ?>'); } +.ic_s_passwd { background-image: url('getImgPath('s_passwd.png'); ?>'); } +.ic_s_process { background-image: url('getImgPath('s_process.png'); ?>'); } +.ic_s_really { background-image: url('getImgPath('s_really.png'); ?>'); width: 11px; height: 11px; } +.ic_s_reload { background-image: url('getImgPath('s_reload.png'); ?>'); } +.ic_s_replication { background-image: url('getImgPath('s_replication.png'); ?>'); } +.ic_s_rights { background-image: url('getImgPath('s_rights.png'); ?>'); } +.ic_s_sortable { background-image: url('getImgPath('s_sortable.png'); ?>'); } +.ic_s_status { background-image: url('getImgPath('s_status.png'); ?>'); } +.ic_s_success { background-image: url('getImgPath('s_success.png'); ?>'); } +.ic_s_sync { background-image: url('getImgPath('s_sync.png'); ?>'); } +.ic_s_tbl { background-image: url('getImgPath('s_tbl.png'); ?>'); } +.ic_s_theme { background-image: url('getImgPath('s_theme.png'); ?>'); } +.ic_s_top { background-image: url('getImgPath('s_top.png'); ?>'); } +.ic_s_unlink { background-image: url('getImgPath('s_unlink.png'); ?>'); } +.ic_s_vars { background-image: url('getImgPath('s_vars.png'); ?>'); } +.ic_s_views { background-image: url('getImgPath('s_views.png'); ?>'); } +.ic_show { background-image: url('getImgPath('show.png'); ?>'); } +.ic_window-new { background-image: url('getImgPath('window-new.png'); ?>'); } +.ic_ajax_clock_small { background-image: url('getImgPath('ajax_clock_small.gif'); ?>'); } +.ic_s_partialtext { background-image: url('getImgPath('s_partialtext.png'); ?>'); } +.ic_s_fulltext { background-image: url('getImgPath('s_fulltext.png'); ?>'); } diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/css/jqplot.css.php b/php/apps/phpmyadmin49/html/themes/pmahomme/css/jqplot.css.php new file mode 100644 index 00000000..1c5c5ac9 --- /dev/null +++ b/php/apps/phpmyadmin49/html/themes/pmahomme/css/jqplot.css.php @@ -0,0 +1,273 @@ + + +/* jqPlot */ + +/*rules for the plot target div. These will be cascaded down to all plot elements according to css rules*/ +.jqplot-target { + position: relative; + color: #222222; + font-family: "Trebuchet MS", Arial, Helvetica, sans-serif; + font-size: 1em; +/* height: 300px; + width: 590px;*/ +} + +/*rules applied to all axes*/ +.jqplot-axis { + font-size: 0.75em; +} + +.jqplot-xaxis { + margin-top: 10px; +} + +.jqplot-x2axis { + margin-bottom: 10px; +} + +.jqplot-yaxis { + margin-: 10px; +} + +.jqplot-y2axis, .jqplot-y3axis, .jqplot-y4axis, .jqplot-y5axis, .jqplot-y6axis, .jqplot-y7axis, .jqplot-y8axis, .jqplot-y9axis, .jqplot-yMidAxis { + margin-left: 10px; + margin-right: 10px; +} + +/*rules applied to all axis tick divs*/ +.jqplot-axis-tick, .jqplot-xaxis-tick, .jqplot-yaxis-tick, .jqplot-x2axis-tick, .jqplot-y2axis-tick, .jqplot-y3axis-tick, .jqplot-y4axis-tick, .jqplot-y5axis-tick, .jqplot-y6axis-tick, .jqplot-y7axis-tick, .jqplot-y8axis-tick, .jqplot-y9axis-tick, .jqplot-yMidAxis-tick { + position: absolute; + white-space: pre; +} + + +.jqplot-xaxis-tick { + top: 0; + /* initial position untill tick is drawn in proper place */ + : 15px; + vertical-align: top; +} + +.jqplot-x2axis-tick { + bottom: 0; + /* initial position untill tick is drawn in proper place */ + : 15px; + vertical-align: bottom; +} + +.jqplot-yaxis-tick { + : 0px; + /* initial position untill tick is drawn in proper place */ + top: 15px; + text-align: ; +} + +.jqplot-yaxis-tick.jqplot-breakTick { + : -20px; + margin-: 0; + padding:1px 5px 1px; + z-index: 2; + font-size: 1.5em; +} + +.jqplot-y2axis-tick, .jqplot-y3axis-tick, .jqplot-y4axis-tick, .jqplot-y5axis-tick, .jqplot-y6axis-tick, .jqplot-y7axis-tick, .jqplot-y8axis-tick, .jqplot-y9axis-tick { + : 0px; + /* initial position untill tick is drawn in proper place */ + top: 15px; +/* padding-left: 10px;*/ +/* padding-right: 15px;*/ + text-align: ; +} + +.jqplot-yMidAxis-tick { + text-align: center; + white-space: nowrap; +} + +.jqplot-xaxis-label { + margin-top: 10px; + font-size: 11pt; + position: absolute; +} + +.jqplot-x2axis-label { + margin-bottom: 10px; + font-size: 11pt; + position: absolute; +} + +.jqplot-yaxis-label { + margin-right: 10px; +/* text-align: center;*/ + font-size: 11pt; + position: absolute; +} + +.jqplot-yMidAxis-label { + font-size: 11pt; + position: absolute; +} + +.jqplot-y2axis-label, .jqplot-y3axis-label, .jqplot-y4axis-label, .jqplot-y5axis-label, .jqplot-y6axis-label, .jqplot-y7axis-label, .jqplot-y8axis-label, .jqplot-y9axis-label { +/* text-align: center;*/ + font-size: 11pt; + margin-: 10px; + position: absolute; +} + +.jqplot-meterGauge-tick { + font-size: 0.75em; + color: #999999; +} + +.jqplot-meterGauge-label { + font-size: 1em; + color: #999999; +} + +table.jqplot-table-legend { + margin-top: 12px; + margin-bottom: 12px; + margin-left: 12px; + margin-right: 12px; +} + +table.jqplot-table-legend, table.jqplot-cursor-legend { + background-color: rgba(255,255,255,0.6); + border: 1px solid #cccccc; + position: absolute; + font-size: 0.75em; +} + +td.jqplot-table-legend { + vertical-align: middle; +} + +/* +These rules could be used instead of assigning +element styles and relying on js object properties. +*/ + +/* +td.jqplot-table-legend-swatch { + padding-top: 0.5em; + text-align: center; +} + +tr.jqplot-table-legend:first td.jqplot-table-legend-swatch { + padding-top: 0px; +} +*/ + +td.jqplot-seriesToggle:hover, td.jqplot-seriesToggle:active { + cursor: pointer; +} + +.jqplot-table-legend .jqplot-series-hidden { + text-decoration: line-through; +} + +div.jqplot-table-legend-swatch-outline { + border: 1px solid #cccccc; + padding: 1px; +} + +div.jqplot-table-legend-swatch { + width: 0; + height: 0; + border-top-width: 5px; + border-bottom-width: 5px; + border-left-width: 6px; + border-right-width: 6px; + border-top-style: solid; + border-bottom-style: solid; + border-left-style: solid; + border-right-style: solid; +} + +.jqplot-title { + top: 0; + : 0px; + padding-bottom: 0.5em; + font-size: 1.2em; +} + +table.jqplot-cursor-tooltip { + border: 1px solid #cccccc; + font-size: 0.75em; +} + + +.jqplot-cursor-tooltip { + border: 1px solid #cccccc; + font-size: 0.75em; + white-space: nowrap; + background: rgba(208,208,208,0.5); + padding: 1px; +} + +.jqplot-highlighter-tooltip, .jqplot-canvasOverlay-tooltip { + border: 1px solid #cccccc; + font-size: 0.75em; + white-space: nowrap; + background: rgba(208,208,208,0.5); + padding: 1px; +} + +.jqplot-point-label { + font-size: 0.75em; + z-index: 2; +} + +td.jqplot-cursor-legend-swatch { + vertical-align: middle; + text-align: center; +} + +div.jqplot-cursor-legend-swatch { + width: 1.2em; + height: 0.7em; +} + +.jqplot-error { +/* Styles added to the plot target container when there is an error go here.*/ + text-align: center; +} + +.jqplot-error-message { +/* Styling of the custom error message div goes here.*/ + position: relative; + top: 46%; + display: inline-block; +} + +div.jqplot-bubble-label { + font-size: 0.8em; +/* background: rgba(90%, 90%, 90%, 0.15);*/ + padding-left: 2px; + padding-right: 2px; + color: rgb(20%, 20%, 20%); +} + +div.jqplot-bubble-label.jqplot-bubble-label-highlight { + background: rgba(90%, 90%, 90%, 0.7); +} + +div.jqplot-noData-container { + text-align: center; + background-color: rgba(96%, 96%, 96%, 0.3); +} diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/css/navigation.css.php b/php/apps/phpmyadmin49/html/themes/pmahomme/css/navigation.css.php new file mode 100644 index 00000000..e93c0a12 --- /dev/null +++ b/php/apps/phpmyadmin49/html/themes/pmahomme/css/navigation.css.php @@ -0,0 +1,429 @@ + + +/******************************************************************************/ +/* Navigation */ + +#pma_navigation { + width: px; + position: fixed; + top: 0; + : 0; + height: 100vh; + background: url(./themes/pmahomme/img/left_nav_bg.png) repeat-y right 0 ; + color: ; + z-index: 800; +} + +#pma_navigation_header { + overflow: hidden; +} + +#pma_navigation_content { + width: 100%; + height: 100%; + position: absolute; + top: 0; + : 0; + z-index: 0; +} + +#pma_navigation ul { + margin: 0; +} + +#pma_navigation form { + margin: 0; + padding: 0; + display: inline; +} + +#pma_navigation select#select_server, +#pma_navigation select#lightm_db { + width: 100%; +} + +/******************************************************************************/ +/* specific elements */ + +#pma_navigation div.pageselector { + text-align: center; + margin: 0; + margin-: 0.75em; + border-: 1px solid #666; +} + +#pma_navigation div#pmalogo { + +} + +#pma_navigation #pmalogo, +#pma_navigation #serverChoice, +#pma_navigation #navipanellinks, +#pma_navigation #recentTableList, +#pma_navigation #favoriteTableList, +#pma_navigation #databaseList, +#pma_navigation div.pageselector.dbselector { + text-align: center; + padding: 5px 10px 0; + border: 0; +} + +#pma_navigation #recentTable, +#pma_navigation #favoriteTable { + width: 200px; +} + +#pma_navigation #favoriteTableList select, +#pma_navigation #serverChoice select + { + width: 80%; +} + +#pma_navigation_content > img.throbber { + display: none; + margin: .3em auto 0; +} + +/* Navigation tree*/ +#pma_navigation_tree { + margin: 0; + margin-: 5px; + overflow: hidden; + color: #444; + height: 74%; + position: relative; +} +#pma_navigation_select_database { + text-align: left; + padding: 0 0 0; + border: 0; + margin: 0; +} + +#pma_navigation_db_select { + margin-top: 0.5em; + margin-: 0.75em; +} +#pma_navigation_db_select select { + background: url("./themes/pmahomme/img/select_bg.png") repeat scroll 0 0; + -webkit-border-radius: 2px; + border-radius: 2px; + border: 1px solid #bbb; + border-top: 1px solid #bbb; + color: #333; + padding: 4px 6px; + margin: 0 0 0; + width: 92%; + font-size: 1.11em; +} + +#pma_navigation_tree_content { + width: 100%; + overflow: hidden; + overflow-y: auto; + position: absolute; + height: 100%; +} +#pma_navigation_tree_content a.hover_show_full { + position: relative; + z-index: 100; + vertical-align: sub; +} +#pma_navigation_tree a { + color: ; +} +#pma_navigation_tree a:hover { + text-decoration: underline; +} +#pma_navigation_tree li.activePointer { + color: ; + background-color: ; +} +#pma_navigation_tree li.selected { + color: ; + background-color: ; +} +#pma_navigation_tree li .dbItemControls { + padding-left: 4px; +} +#pma_navigation_tree li .navItemControls { + display: none; + padding-left: 4px; +} +#pma_navigation_tree li.activePointer .navItemControls { + display: inline; + opacity: 0.5; +} +#pma_navigation_tree li.activePointer .navItemControls:hover { + display: inline; + opacity: 1.0; +} +#pma_navigation_tree ul { + clear: both; + padding: 0; + list-style-type: none; + margin: 0; +} +#pma_navigation_tree ul ul { + position: relative; +} +#pma_navigation_tree li, +#pma_navigation_tree li.fast_filter { + white-space: nowrap; + clear: both; + min-height: 16px; +} +#pma_navigation_tree img { + margin: 0; +} +#pma_navigation_tree i { + display: block; +} +#pma_navigation_tree div.block { + position: relative; + width: 1.5em; + height: 1.5em; + min-width: 16px; + min-height: 16px; + float: ; +} +#pma_navigation_tree div.block.double { + width: 2.5em; +} +#pma_navigation_tree div.block i, +#pma_navigation_tree div.block b { + width: 1.5em; + height: 1.7em; + min-width: 16px; + min-height: 8px; + position: absolute; + bottom: 0.7em; + : 0.75em; + z-index: 0; +} +#pma_navigation_tree div.block i { /* Top and right segments for the tree element connections */ + display: block; + border-: 1px solid #666; + border-bottom: 1px solid #666; + position: relative; + z-index: 0; +} +#pma_navigation_tree div.block i.first { /* Removes top segment */ + border-: 0; +} +#pma_navigation_tree div.block b { /* Bottom segment for the tree element connections */ + display: block; + height: 0.75em; + bottom: 0; + : 0.75em; + border-: 1px solid #666; +} +#pma_navigation_tree div.block a, +#pma_navigation_tree div.block u { + position: absolute; + : 50%; + top: 50%; + z-index: 10; +} +#pma_navigation_tree div.block a + a { + : 100%; +} +#pma_navigation_tree div.block.double a, +#pma_navigation_tree div.block.double u { + : 33%; +} +#pma_navigation_tree div.block.double a + a { + : 85%; +} +#pma_navigation_tree div.block img { + position: relative; + top: -0.6em; + : 0; + margin-: -7px; +} +#pma_navigation_tree div.throbber img { + top: 2px; + : 2px; +} +#pma_navigation_tree li.last > ul { + background: none; +} +#pma_navigation_tree li > a, #pma_navigation_tree li > i { + line-height: 1.5em; + height: 1.5em; + padding-: 0.3em; +} +#pma_navigation_tree .list_container { + border-: 1px solid #666; + margin-: 0.75em; + padding-: 0.75em; +} +#pma_navigation_tree .last > .list_container { + border-: 0 solid #666; +} + +/* Fast filter */ +li.fast_filter { + padding-: 0.75em; + margin-: 0.75em; + padding-: 35px; + border-: 1px solid #666; + list-style: none; +} +li.fast_filter input { + margin: 3px 0 0 0; + font-size: 0.7em; + padding-top: 2px; + padding-bottom: 2px; + padding-: 4px; + padding-: 1.7em; + width: 100%; +} +li.fast_filter span { + position: relative; + : 1.5em; + padding: 0.2em; + cursor: pointer; + font-weight: bold; + color: #800; + font-size: 0.7em; +} +/* IE10+ has its own reset X */ +html.ie li.fast_filter span { + display: none; +} +html.ie.ie9 li.fast_filter span, +html.ie.ie8 li.fast_filter span { + display: auto; +} +html.ie li.fast_filter input { + padding-: .2em; +} +html.ie.ie9 li.fast_filter input, +html.ie.ie8 li.fast_filter input { + padding-: 1.7em; +} +li.fast_filter.db_fast_filter { + border: 0; + margin-left: 0; + margin-right: 10px; +} + +#navigation_controls_outer { + min-height: 21px !important; +} + +#navigation_controls_outer.activePointer { + background-color: transparent !important; +} + +#navigation_controls { + float: right; + padding-right: 23px; +} + +/* Resize handler */ +#pma_navigation_resizer { + width: 3px; + height: 100%; + background-color: #aaa; + cursor: col-resize; + position: fixed; + top: 0; + : 240px; + z-index: 801; +} +#pma_navigation_collapser { + width: 20px; + height: 22px; + line-height: 22px; + background: #eee; + color: #555; + font-weight: bold; + position: fixed; + top: 0; + : px; + text-align: center; + cursor: pointer; + z-index: 800; + text-shadow: 0 1px 0 #fff; + filter: dropshadow(color=#fff, offx=0, offy=1); + border: 1px solid #888; +} + +/* Quick warp links */ +.pma_quick_warp { + margin-top: 5px; + margin-: 2px; + position: relative; +} +.pma_quick_warp .drop_list { + float: ; + margin-: 3px; + padding: 2px 0; +} +.pma_quick_warp .drop_button { + padding: 0 .3em; + border: 1px solid #ddd; + border-radius: .3em; + background: #f2f2f2; + cursor: pointer; +} +.pma_quick_warp .drop_list:hover .drop_button { + background: #fff; +} +.pma_quick_warp .drop_list ul { + position: absolute; + margin: 0; + padding: 0; + overflow: hidden; + overflow-y: auto; + list-style: none; + background: #fff; + border: 1px solid #ddd; + border-radius: .3em; + border-top--radius: 0; + border-bottom--radius: 0; + box-shadow: 0 0 5px #ccc; + top: 100%; + : 3px; + : 0; + display: none; + z-index: 802; +} +.pma_quick_warp .drop_list:hover ul { + display: block; +} +.pma_quick_warp .drop_list li { + white-space: nowrap; + padding: 0; + border-radius: 0; +} +.pma_quick_warp .drop_list li img { + vertical-align: sub; +} +.pma_quick_warp .drop_list li:hover { + background: #f2f2f2; +} +.pma_quick_warp .drop_list a { + display: block; + padding: .2em .3em; +} +.pma_quick_warp .drop_list a.favorite_table_anchor { + clear: left; + float: left; + padding: .1em .3em 0; +} diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/css/printview.css b/php/apps/phpmyadmin49/html/themes/pmahomme/css/printview.css new file mode 100644 index 00000000..809cbdd0 --- /dev/null +++ b/php/apps/phpmyadmin49/html/themes/pmahomme/css/printview.css @@ -0,0 +1,169 @@ +@media print { + #back_button_print_view, #print_button_print_view { + display: none; + } +} + +/* For removing element from Print View */ +.print_ignore { + display: none; +} + +.nowrap { + white-space: nowrap; +} + +.hide { + display: none; +} + +/* Standard CSS */ +body, table, th, td { + color: #000; + background-color: #fff; + font-size: 12px; +} + +/* To remove link text decoration */ +a:link { + color:#000; + text-decoration:none +} + +/* To remove any image borders */ +img { + border: 0; +} + +/* Table specific */ +table, th, td { + border: .1em solid #000; + background-color: #fff; +} + +table { + border-collapse: collapse; + border-spacing: 0.2em; +} + +thead { + border-collapse: collapse; + border-spacing: 0.2em; + border: .1em solid #000; + font-weight: 900; +} + +th, td { + padding: 0.2em; +} + +thead th { + font-weight: bold; + background-color: #e5e5e5; + border: .1em solid #000; +} + +th.vtop, td.vtop { + vertical-align: top; +} + +th.vbottom, td.vbottom { + vertical-align: bottom; +} + +/* Common Elements not to be included */ +/* Hide Navigation and Top Menu bar */ +#pma_navigation, #floating_menubar { + display: none; +} +/* Hide console */ +#pma_console_container { + display: none; +} + +/* Hide Navigation items (like Goto Top) */ +#page_nav_icons { + display: none; +} + +/* Hide the Create Table form */ +#create_table_form_minimal { + display: none; +} + +/* Hide the Page Settings Modal box */ +#page_settings_modal { + display: none; +} + +/* Hide footer, Demo notice, errors div */ +#pma_footer, #pma_demo, #pma_errors { + display: none; +} + +/* Hide the #selflink div */ +#selflink { + display: none; +} + +/* Position the main content */ +#page_content { + position: absolute; + left: 0; + top: 0; + width: 95%; + float: none; +} + +/* Specific Class for overriding while Print */ +.print { + background-color: #000; +} + +/* For the Success message div */ +div.success { + background-color: #fff; +} + +.sqlOuter { + color: black; + background-color: #000; +} + +/* For hiding 'Open a New phpMyAdmin Window' button */ +.ic_window-new, .ic_s_cog { + display: none; +} + +.sticky_columns tr { + display: none; +} + +#structure-action-links, #addColumns { + display: none; +} + +/* Hide extra menu on tbl_structure.php */ +#topmenu2 { + display: none; +} + +.cDrop, .cEdit, .cList, .cCpy, .cPointer { + display: none; +} + +/* odd items 1,3,5,7,... */ +table tbody:first-of-type tr:nth-child(odd), +table tbody:first-of-type tr:nth-child(odd) th { + background: #fff; +} + +/* even items 2,4,6,8,... */ +table tbody:first-of-type tr:nth-child(even), +table tbody:first-of-type tr:nth-child(even) th { + background: #DFDFDF; +} + +.column_attribute { + font-size: 100%; +} diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/css/resizable-menu.css.php b/php/apps/phpmyadmin49/html/themes/pmahomme/css/resizable-menu.css.php new file mode 100644 index 00000000..6cebc896 --- /dev/null +++ b/php/apps/phpmyadmin49/html/themes/pmahomme/css/resizable-menu.css.php @@ -0,0 +1,57 @@ + +ul.resizable-menu a, +ul.resizable-menu span { + display: block; + margin: 0; + padding: 0; + white-space: nowrap; +} + +ul.resizable-menu .submenu { + display: none; + position: relative; +} + +ul.resizable-menu .shown { + display: inline-block; +} + +ul.resizable-menu ul { + margin: 0; + padding: 0; + position: absolute; + list-style-type: none; + display: none; + border: 1px #ddd solid; + z-index: 2; + : 0; +} + +ul.resizable-menu li:hover { + getCssGradient('ffffff', 'e5e5e5'); ?> +} + +ul.resizable-menu li:hover ul, +ul.resizable-menu .submenuhover ul { + display: block; + background: #fff; +} + +ul.resizable-menu ul li { + width: 100%; +} diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/css/rte.css.php b/php/apps/phpmyadmin49/html/themes/pmahomme/css/rte.css.php new file mode 100644 index 00000000..f70ffe27 --- /dev/null +++ b/php/apps/phpmyadmin49/html/themes/pmahomme/css/rte.css.php @@ -0,0 +1,50 @@ + + +.rte_table { + table-layout: fixed; +} + +.rte_table td { + vertical-align: middle; + padding: 0.2em; +} + +.rte_table tr td:nth-child(1) { + font-weight: bold; +} + +.rte_table input, +.rte_table select, +.rte_table textarea { + width: 100%; + margin: 0; + box-sizing: border-box; + -ms-box-sizing: border-box; + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; +} + +.rte_table input[type=button], +.rte_table input[type=checkbox], +.rte_table input[type=radio] { + width: auto; + margin-right: 6px; +} + +.rte_table .routine_params_table { + width: 100%; +} diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/ajax_clock_small.gif b/php/apps/phpmyadmin49/html/themes/pmahomme/img/ajax_clock_small.gif new file mode 100644 index 00000000..bde4932c Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/ajax_clock_small.gif differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/arrow_ltr.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/arrow_ltr.png new file mode 100644 index 00000000..cd79ab42 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/arrow_ltr.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/arrow_rtl.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/arrow_rtl.png new file mode 100644 index 00000000..d035b9d9 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/arrow_rtl.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/asc_order.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/asc_order.png new file mode 100644 index 00000000..51ce21b1 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/asc_order.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_bookmark.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_bookmark.png new file mode 100644 index 00000000..275cfa6c Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_bookmark.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_browse.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_browse.png new file mode 100644 index 00000000..8fd62b45 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_browse.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_calendar.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_calendar.png new file mode 100644 index 00000000..0eed98c3 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_calendar.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_chart.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_chart.png new file mode 100644 index 00000000..c97c2787 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_chart.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_close.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_close.png new file mode 100644 index 00000000..8d8e98ec Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_close.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_column_add.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_column_add.png new file mode 100644 index 00000000..70fa63ec Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_column_add.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_comment.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_comment.png new file mode 100644 index 00000000..e95e887d Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_comment.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_dbstatistics.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_dbstatistics.png new file mode 100644 index 00000000..c97c2787 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_dbstatistics.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_deltbl.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_deltbl.png new file mode 100644 index 00000000..de936288 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_deltbl.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_docs.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_docs.png new file mode 100644 index 00000000..54d12151 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_docs.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_docsql.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_docsql.png new file mode 100644 index 00000000..2f6f0561 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_docsql.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_drop.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_drop.png new file mode 100644 index 00000000..672e3586 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_drop.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_edit.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_edit.png new file mode 100644 index 00000000..003b5d77 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_edit.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_empty.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_empty.png new file mode 100644 index 00000000..91e22572 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_empty.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_engine.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_engine.png new file mode 100644 index 00000000..3a92d770 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_engine.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_event_add.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_event_add.png new file mode 100644 index 00000000..1f31c7cb Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_event_add.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_events.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_events.png new file mode 100644 index 00000000..07c60ff8 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_events.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_export.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_export.png new file mode 100644 index 00000000..ba8ecf53 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_export.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_favorite.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_favorite.png new file mode 100644 index 00000000..c4e1787e Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_favorite.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_find_replace.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_find_replace.png new file mode 100644 index 00000000..aff56f58 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_find_replace.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_firstpage.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_firstpage.png new file mode 100644 index 00000000..8e768efe Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_firstpage.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_ftext.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_ftext.png new file mode 100644 index 00000000..0d2cde23 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_ftext.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_globe.gif b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_globe.gif new file mode 100644 index 00000000..ef03dcf0 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_globe.gif differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_group.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_group.png new file mode 100644 index 00000000..4dd9d0b5 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_group.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_help.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_help.png new file mode 100644 index 00000000..54d12151 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_help.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_home.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_home.png new file mode 100644 index 00000000..d9fc35ef Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_home.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_import.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_import.png new file mode 100644 index 00000000..a3b80bd8 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_import.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_index.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_index.png new file mode 100644 index 00000000..a7a11a15 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_index.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_index_add.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_index_add.png new file mode 100644 index 00000000..2cef18ac Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_index_add.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_inline_edit.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_inline_edit.png new file mode 100644 index 00000000..c8fd6062 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_inline_edit.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_insrow.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_insrow.png new file mode 100644 index 00000000..ce32a0a1 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_insrow.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_key.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_key.png new file mode 100644 index 00000000..d1b15d9c Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_key.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_lastpage.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_lastpage.png new file mode 100644 index 00000000..1b4ac092 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_lastpage.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_left.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_left.png new file mode 100644 index 00000000..db427f96 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_left.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_minus.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_minus.png new file mode 100644 index 00000000..19ad78b6 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_minus.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_more.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_more.png new file mode 100644 index 00000000..9d84943e Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_more.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_move.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_move.png new file mode 100644 index 00000000..aeb7a55d Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_move.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_newdb.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_newdb.png new file mode 100644 index 00000000..25312edd Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_newdb.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_newtbl.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_newtbl.png new file mode 100644 index 00000000..f675656f Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_newtbl.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_nextpage.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_nextpage.png new file mode 100644 index 00000000..75a2c67c Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_nextpage.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_no_favorite.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_no_favorite.png new file mode 100644 index 00000000..925a17f4 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_no_favorite.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_pdfdoc.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_pdfdoc.png new file mode 100644 index 00000000..a8074cf4 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_pdfdoc.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_plugin.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_plugin.png new file mode 100644 index 00000000..205b9dab Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_plugin.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_plus.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_plus.png new file mode 100644 index 00000000..c26fb249 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_plus.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_prevpage.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_prevpage.png new file mode 100644 index 00000000..752478ae Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_prevpage.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_primary.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_primary.png new file mode 100644 index 00000000..7333d891 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_primary.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_print.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_print.png new file mode 100644 index 00000000..18a28a85 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_print.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_props.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_props.png new file mode 100644 index 00000000..58906696 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_props.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_relations.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_relations.png new file mode 100644 index 00000000..7c16044b Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_relations.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_report.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_report.png new file mode 100644 index 00000000..f5b57cdd Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_report.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_right.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_right.png new file mode 100644 index 00000000..462d1fee Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_right.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_routine_add.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_routine_add.png new file mode 100644 index 00000000..78517c10 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_routine_add.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_routines.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_routines.png new file mode 100644 index 00000000..439899be Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_routines.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_save.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_save.png new file mode 100644 index 00000000..e82bfed1 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_save.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_saveimage.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_saveimage.png new file mode 100644 index 00000000..0bc614a4 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_saveimage.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_sbrowse.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_sbrowse.png new file mode 100644 index 00000000..8fd62b45 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_sbrowse.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_sdb.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_sdb.png new file mode 100644 index 00000000..ceb79349 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_sdb.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_search.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_search.png new file mode 100644 index 00000000..1527a0a7 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_search.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_select.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_select.png new file mode 100644 index 00000000..821d30f1 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_select.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_snewtbl.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_snewtbl.png new file mode 100644 index 00000000..b2863b4e Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_snewtbl.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_spatial.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_spatial.png new file mode 100644 index 00000000..6498e451 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_spatial.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_sql.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_sql.png new file mode 100644 index 00000000..e52ff7fb Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_sql.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_sqldoc.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_sqldoc.png new file mode 100644 index 00000000..7762f499 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_sqldoc.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_sqlhelp.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_sqlhelp.png new file mode 100644 index 00000000..dbeacb64 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_sqlhelp.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_table_add.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_table_add.png new file mode 100644 index 00000000..06b6d57c Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_table_add.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_tblanalyse.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_tblanalyse.png new file mode 100644 index 00000000..6809a2d6 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_tblanalyse.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_tblexport.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_tblexport.png new file mode 100644 index 00000000..ba8ecf53 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_tblexport.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_tblimport.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_tblimport.png new file mode 100644 index 00000000..a3b80bd8 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_tblimport.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_tblops.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_tblops.png new file mode 100644 index 00000000..82b88dda Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_tblops.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_tbloptimize.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_tbloptimize.png new file mode 100644 index 00000000..0c8425ce Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_tbloptimize.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_tipp.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_tipp.png new file mode 100644 index 00000000..0cead20e Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_tipp.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_trigger_add.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_trigger_add.png new file mode 100644 index 00000000..920a2a4f Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_trigger_add.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_triggers.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_triggers.png new file mode 100644 index 00000000..2ee1dc6f Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_triggers.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_undo.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_undo.png new file mode 100644 index 00000000..fef0fa4e Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_undo.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_unique.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_unique.png new file mode 100644 index 00000000..94a520f0 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_unique.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_usradd.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_usradd.png new file mode 100644 index 00000000..eb96e6cd Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_usradd.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_usrcheck.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_usrcheck.png new file mode 100644 index 00000000..7fabcb2b Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_usrcheck.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_usrdrop.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_usrdrop.png new file mode 100644 index 00000000..2e0e7d63 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_usrdrop.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_usredit.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_usredit.png new file mode 100644 index 00000000..143a3976 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_usredit.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_usrlist.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_usrlist.png new file mode 100644 index 00000000..07a620ec Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_usrlist.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_versions.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_versions.png new file mode 100644 index 00000000..c253dc06 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_versions.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_view.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_view.png new file mode 100644 index 00000000..204b10d6 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_view.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_view_add.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_view_add.png new file mode 100644 index 00000000..ebded825 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_view_add.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_views.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_views.png new file mode 100644 index 00000000..65f64d77 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/b_views.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/bd_browse.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/bd_browse.png new file mode 100644 index 00000000..5a28d46e Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/bd_browse.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/bd_deltbl.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/bd_deltbl.png new file mode 100644 index 00000000..fb518fc7 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/bd_deltbl.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/bd_drop.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/bd_drop.png new file mode 100644 index 00000000..19ccf881 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/bd_drop.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/bd_edit.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/bd_edit.png new file mode 100644 index 00000000..b8844896 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/bd_edit.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/bd_empty.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/bd_empty.png new file mode 100644 index 00000000..e6a855ac Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/bd_empty.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/bd_export.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/bd_export.png new file mode 100644 index 00000000..1fbb6042 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/bd_export.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/bd_firstpage.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/bd_firstpage.png new file mode 100644 index 00000000..9abaad5f Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/bd_firstpage.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/bd_ftext.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/bd_ftext.png new file mode 100644 index 00000000..818fcd4d Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/bd_ftext.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/bd_index.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/bd_index.png new file mode 100644 index 00000000..fbade943 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/bd_index.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/bd_insrow.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/bd_insrow.png new file mode 100644 index 00000000..b325ffa9 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/bd_insrow.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/bd_lastpage.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/bd_lastpage.png new file mode 100644 index 00000000..48985174 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/bd_lastpage.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/bd_nextpage.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/bd_nextpage.png new file mode 100644 index 00000000..5373f9ab Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/bd_nextpage.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/bd_prevpage.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/bd_prevpage.png new file mode 100644 index 00000000..f1e98321 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/bd_prevpage.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/bd_primary.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/bd_primary.png new file mode 100644 index 00000000..d1b15d9c Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/bd_primary.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/bd_routine_add.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/bd_routine_add.png new file mode 100644 index 00000000..6f45cbd0 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/bd_routine_add.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/bd_sbrowse.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/bd_sbrowse.png new file mode 100644 index 00000000..5a28d46e Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/bd_sbrowse.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/bd_select.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/bd_select.png new file mode 100644 index 00000000..f9a23ef3 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/bd_select.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/bd_spatial.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/bd_spatial.png new file mode 100644 index 00000000..ea16fedf Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/bd_spatial.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/bd_unique.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/bd_unique.png new file mode 100644 index 00000000..ebacdf49 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/bd_unique.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/centralColumns.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/centralColumns.png new file mode 100644 index 00000000..4b94c9f7 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/centralColumns.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/centralColumns_add.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/centralColumns_add.png new file mode 100644 index 00000000..72825645 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/centralColumns_add.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/centralColumns_delete.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/centralColumns_delete.png new file mode 100644 index 00000000..98d6b05d Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/centralColumns_delete.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/col_drop.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/col_drop.png new file mode 100644 index 00000000..9d84943e Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/col_drop.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/col_pointer.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/col_pointer.png new file mode 100644 index 00000000..00d78c7c Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/col_pointer.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/col_pointer_ver.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/col_pointer_ver.png new file mode 100644 index 00000000..0b735526 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/col_pointer_ver.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/console.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/console.png new file mode 100644 index 00000000..f84ce2c1 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/console.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/database.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/database.png new file mode 100644 index 00000000..0d0e388f Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/database.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/1.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/1.png new file mode 100644 index 00000000..a9da1dc1 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/1.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/2.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/2.png new file mode 100644 index 00000000..c153fd1d Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/2.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/2leftarrow.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/2leftarrow.png new file mode 100644 index 00000000..77d5edd5 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/2leftarrow.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/2leftarrow_m.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/2leftarrow_m.png new file mode 100644 index 00000000..9ab18073 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/2leftarrow_m.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/2rightarrow.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/2rightarrow.png new file mode 100644 index 00000000..f59f2f3a Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/2rightarrow.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/2rightarrow_m.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/2rightarrow_m.png new file mode 100644 index 00000000..61af4d3a Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/2rightarrow_m.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/3.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/3.png new file mode 100644 index 00000000..cf1d45e1 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/3.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/4.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/4.png new file mode 100644 index 00000000..bdb1ad05 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/4.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/5.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/5.png new file mode 100644 index 00000000..1e35a89f Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/5.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/6.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/6.png new file mode 100644 index 00000000..c5da1cf9 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/6.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/7.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/7.png new file mode 100644 index 00000000..2b4f80b3 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/7.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/8.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/8.png new file mode 100644 index 00000000..292fdd09 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/8.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/FieldKey_small.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/FieldKey_small.png new file mode 100644 index 00000000..0b588949 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/FieldKey_small.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/Field_small.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/Field_small.png new file mode 100644 index 00000000..c396eb3b Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/Field_small.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/Field_small_char.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/Field_small_char.png new file mode 100644 index 00000000..e35c2a37 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/Field_small_char.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/Field_small_date.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/Field_small_date.png new file mode 100644 index 00000000..21df4e27 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/Field_small_date.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/Field_small_int.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/Field_small_int.png new file mode 100644 index 00000000..6d6fe88a Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/Field_small_int.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/Header.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/Header.png new file mode 100644 index 00000000..2d2090e9 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/Header.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/Header_Linked.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/Header_Linked.png new file mode 100644 index 00000000..afd3f045 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/Header_Linked.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/anchor.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/anchor.png new file mode 100644 index 00000000..576d4355 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/anchor.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/and_icon.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/and_icon.png new file mode 100644 index 00000000..2ecce01a Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/and_icon.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/ang_direct.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/ang_direct.png new file mode 100644 index 00000000..3ce29a6f Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/ang_direct.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/bord.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/bord.png new file mode 100644 index 00000000..0b3191de Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/bord.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/bottom.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/bottom.png new file mode 100644 index 00000000..19cbbcdc Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/bottom.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/def.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/def.png new file mode 100644 index 00000000..ea026854 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/def.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/display_field.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/display_field.png new file mode 100644 index 00000000..9e5e2dfe Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/display_field.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/downarrow1.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/downarrow1.png new file mode 100644 index 00000000..a2d09541 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/downarrow1.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/downarrow2.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/downarrow2.png new file mode 100644 index 00000000..60827b3d Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/downarrow2.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/downarrow2_m.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/downarrow2_m.png new file mode 100644 index 00000000..70f7f926 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/downarrow2_m.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/exec.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/exec.png new file mode 100644 index 00000000..06ce466a Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/exec.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/exec_small.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/exec_small.png new file mode 100644 index 00000000..357166fe Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/exec_small.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/exitFullscreen.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/exitFullscreen.png new file mode 100644 index 00000000..4397aede Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/exitFullscreen.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/export.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/export.png new file mode 100644 index 00000000..64b58e3e Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/export.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/favicon.ico b/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/favicon.ico new file mode 100644 index 00000000..29c2595b Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/favicon.ico differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/grid.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/grid.png new file mode 100644 index 00000000..f581a68d Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/grid.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/help.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/help.png new file mode 100644 index 00000000..facf1a1a Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/help.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/help_relation.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/help_relation.png new file mode 100644 index 00000000..321f0745 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/help_relation.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/left_panel_butt.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/left_panel_butt.png new file mode 100644 index 00000000..03317301 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/left_panel_butt.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/left_panel_tab.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/left_panel_tab.png new file mode 100644 index 00000000..c596f92d Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/left_panel_tab.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/minus.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/minus.png new file mode 100644 index 00000000..2feb9355 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/minus.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/or_icon.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/or_icon.png new file mode 100644 index 00000000..75be472b Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/or_icon.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/other_table.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/other_table.png new file mode 100644 index 00000000..96453969 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/other_table.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/page_add.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/page_add.png new file mode 100644 index 00000000..5d8a1d1a Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/page_add.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/page_delete.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/page_delete.png new file mode 100644 index 00000000..a512c62c Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/page_delete.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/page_edit.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/page_edit.png new file mode 100644 index 00000000..4f0ce4a0 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/page_edit.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/pdf.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/pdf.png new file mode 100644 index 00000000..616060a2 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/pdf.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/plus.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/plus.png new file mode 100644 index 00000000..ac3e6020 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/plus.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/query_builder.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/query_builder.png new file mode 100644 index 00000000..6e54e8e5 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/query_builder.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/relation.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/relation.png new file mode 100644 index 00000000..6c09c7f1 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/relation.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/reload.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/reload.png new file mode 100644 index 00000000..6b5cda5b Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/reload.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/resize.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/resize.png new file mode 100644 index 00000000..b3f4732f Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/resize.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/resizeright.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/resizeright.png new file mode 100644 index 00000000..e984170c Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/resizeright.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/rightarrow1.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/rightarrow1.png new file mode 100644 index 00000000..d2641d73 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/rightarrow1.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/rightarrow2.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/rightarrow2.png new file mode 100644 index 00000000..76f2fe08 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/rightarrow2.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/save.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/save.png new file mode 100644 index 00000000..6d5c0525 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/save.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/save_as.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/save_as.png new file mode 100644 index 00000000..8c4d1518 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/save_as.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/small_tab.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/small_tab.png new file mode 100644 index 00000000..fb84f54c Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/small_tab.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/table.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/table.png new file mode 100644 index 00000000..aa2b1c71 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/table.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/toggle_lines.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/toggle_lines.png new file mode 100644 index 00000000..46d28c09 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/toggle_lines.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/top_panel.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/top_panel.png new file mode 100644 index 00000000..8a5dab9c Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/top_panel.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/uparrow2_m.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/uparrow2_m.png new file mode 100644 index 00000000..5d1342d4 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/uparrow2_m.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/viewInFullscreen.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/viewInFullscreen.png new file mode 100644 index 00000000..a6c76917 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/designer/viewInFullscreen.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/east-mini.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/east-mini.png new file mode 100644 index 00000000..6685939e Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/east-mini.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/error.ico b/php/apps/phpmyadmin49/html/themes/pmahomme/img/error.ico new file mode 100644 index 00000000..8f4d509d Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/error.ico differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/eye.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/eye.png new file mode 100644 index 00000000..6c9972e0 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/eye.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/eye_grey.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/eye_grey.png new file mode 100644 index 00000000..0dc92a90 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/eye_grey.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/hide.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/hide.png new file mode 100644 index 00000000..2c914431 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/hide.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/item.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/item.png new file mode 100644 index 00000000..f2c33904 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/item.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/left_nav_bg.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/left_nav_bg.png new file mode 100644 index 00000000..42b6f382 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/left_nav_bg.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/lightbulb.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/lightbulb.png new file mode 100644 index 00000000..0cead20e Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/lightbulb.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/lightbulb_off.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/lightbulb_off.png new file mode 100644 index 00000000..dd3632d1 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/lightbulb_off.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/logo_left.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/logo_left.png new file mode 100644 index 00000000..004e7050 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/logo_left.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/logo_right.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/logo_right.png new file mode 100644 index 00000000..a4902891 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/logo_right.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/more.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/more.png new file mode 100644 index 00000000..ac803c44 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/more.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/new_data.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/new_data.png new file mode 100644 index 00000000..c173bc03 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/new_data.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/new_data_hovered.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/new_data_hovered.png new file mode 100644 index 00000000..73b09a63 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/new_data_hovered.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/new_data_selected.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/new_data_selected.png new file mode 100644 index 00000000..a75abe34 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/new_data_selected.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/new_data_selected_hovered.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/new_data_selected_hovered.png new file mode 100644 index 00000000..7091ae36 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/new_data_selected_hovered.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/new_struct.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/new_struct.png new file mode 100644 index 00000000..79fe646c Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/new_struct.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/new_struct_hovered.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/new_struct_hovered.png new file mode 100644 index 00000000..b29aaed3 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/new_struct_hovered.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/new_struct_selected.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/new_struct_selected.png new file mode 100644 index 00000000..bc61749a Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/new_struct_selected.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/new_struct_selected_hovered.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/new_struct_selected_hovered.png new file mode 100644 index 00000000..9a82bc42 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/new_struct_selected_hovered.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/normalize.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/normalize.png new file mode 100644 index 00000000..5475f342 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/normalize.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/north-mini.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/north-mini.png new file mode 100644 index 00000000..c3b60620 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/north-mini.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/pause.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/pause.png new file mode 100644 index 00000000..0271217a Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/pause.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/php_sym.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/php_sym.png new file mode 100644 index 00000000..20b8350f Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/php_sym.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/play.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/play.png new file mode 100644 index 00000000..75a2c67c Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/play.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/pma_logo2.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/pma_logo2.png new file mode 100644 index 00000000..30f22de6 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/pma_logo2.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/s_asc.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/s_asc.png new file mode 100644 index 00000000..51cf60dc Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/s_asc.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/s_asci.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/s_asci.png new file mode 100644 index 00000000..6a56ea19 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/s_asci.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/s_attention.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/s_attention.png new file mode 100644 index 00000000..5a536e81 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/s_attention.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/s_cancel.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/s_cancel.png new file mode 100644 index 00000000..65a63461 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/s_cancel.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/s_cancel2.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/s_cancel2.png new file mode 100644 index 00000000..dd5dccc7 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/s_cancel2.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/s_cog.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/s_cog.png new file mode 100644 index 00000000..a9cbc244 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/s_cog.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/s_collapseall.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/s_collapseall.png new file mode 100644 index 00000000..3d56b7c5 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/s_collapseall.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/s_db.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/s_db.png new file mode 100644 index 00000000..0d0e388f Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/s_db.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/s_desc.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/s_desc.png new file mode 100644 index 00000000..51ce21b1 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/s_desc.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/s_error.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/s_error.png new file mode 100644 index 00000000..71d13a10 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/s_error.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/s_fulltext.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/s_fulltext.png new file mode 100644 index 00000000..b810b0cc Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/s_fulltext.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/s_host.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/s_host.png new file mode 100644 index 00000000..20a9b45c Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/s_host.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/s_info.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/s_info.png new file mode 100644 index 00000000..f3d221fe Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/s_info.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/s_lang.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/s_lang.png new file mode 100644 index 00000000..5a1d7ab4 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/s_lang.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/s_link.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/s_link.png new file mode 100644 index 00000000..609970a3 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/s_link.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/s_lock.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/s_lock.png new file mode 100644 index 00000000..55258949 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/s_lock.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/s_loggoff.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/s_loggoff.png new file mode 100644 index 00000000..f2581cb0 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/s_loggoff.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/s_notice.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/s_notice.png new file mode 100644 index 00000000..5a536e81 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/s_notice.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/s_okay.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/s_okay.png new file mode 100644 index 00000000..9bf92354 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/s_okay.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/s_partialtext.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/s_partialtext.png new file mode 100644 index 00000000..a8fbb343 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/s_partialtext.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/s_passwd.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/s_passwd.png new file mode 100644 index 00000000..c705bd55 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/s_passwd.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/s_process.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/s_process.png new file mode 100644 index 00000000..a9cbc244 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/s_process.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/s_really.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/s_really.png new file mode 100644 index 00000000..e48c9cd4 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/s_really.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/s_reload.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/s_reload.png new file mode 100644 index 00000000..656519c5 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/s_reload.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/s_replication.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/s_replication.png new file mode 100644 index 00000000..c5c5a5c5 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/s_replication.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/s_rights.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/s_rights.png new file mode 100644 index 00000000..407a62fe Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/s_rights.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/s_sortable.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/s_sortable.png new file mode 100644 index 00000000..879c3b59 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/s_sortable.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/s_status.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/s_status.png new file mode 100644 index 00000000..aee657c0 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/s_status.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/s_success.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/s_success.png new file mode 100644 index 00000000..4c79cd9e Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/s_success.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/s_sync.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/s_sync.png new file mode 100644 index 00000000..5ced64c5 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/s_sync.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/s_tbl.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/s_tbl.png new file mode 100644 index 00000000..6632aa29 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/s_tbl.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/s_theme.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/s_theme.png new file mode 100644 index 00000000..a18b1033 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/s_theme.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/s_top.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/s_top.png new file mode 100644 index 00000000..dd57623d Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/s_top.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/s_unlink.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/s_unlink.png new file mode 100644 index 00000000..551679e9 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/s_unlink.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/s_vars.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/s_vars.png new file mode 100644 index 00000000..b62dcd23 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/s_vars.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/s_views.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/s_views.png new file mode 100644 index 00000000..65f64d77 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/s_views.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/select_bg.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/select_bg.png new file mode 100644 index 00000000..22c3ea6c Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/select_bg.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/show.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/show.png new file mode 100644 index 00000000..666653f6 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/show.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/south-mini.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/south-mini.png new file mode 100644 index 00000000..654673b3 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/south-mini.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/spacer.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/spacer.png new file mode 100644 index 00000000..240ca4f8 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/spacer.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/toggle-ltr.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/toggle-ltr.png new file mode 100644 index 00000000..2dd681f6 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/toggle-ltr.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/toggle-rtl.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/toggle-rtl.png new file mode 100644 index 00000000..a50f5bb3 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/toggle-rtl.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/vertical_line.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/vertical_line.png new file mode 100644 index 00000000..a88a7c7b Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/vertical_line.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/west-mini.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/west-mini.png new file mode 100644 index 00000000..61ad92e0 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/west-mini.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/window-new.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/window-new.png new file mode 100644 index 00000000..d18722fd Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/window-new.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/zoom-minus-mini.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/zoom-minus-mini.png new file mode 100644 index 00000000..1b8d84dc Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/zoom-minus-mini.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/zoom-plus-mini.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/zoom-plus-mini.png new file mode 100644 index 00000000..466cc7bc Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/zoom-plus-mini.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/img/zoom-world-mini.png b/php/apps/phpmyadmin49/html/themes/pmahomme/img/zoom-world-mini.png new file mode 100644 index 00000000..dcc60f15 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/img/zoom-world-mini.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/jquery/images/ui-bg_glass_55_fbf9ee_1x400.png b/php/apps/phpmyadmin49/html/themes/pmahomme/jquery/images/ui-bg_glass_55_fbf9ee_1x400.png new file mode 100644 index 00000000..534c590f Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/jquery/images/ui-bg_glass_55_fbf9ee_1x400.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/jquery/images/ui-bg_glass_65_ffffff_1x400.png b/php/apps/phpmyadmin49/html/themes/pmahomme/jquery/images/ui-bg_glass_65_ffffff_1x400.png new file mode 100644 index 00000000..3e56dbdc Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/jquery/images/ui-bg_glass_65_ffffff_1x400.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/jquery/images/ui-bg_glass_75_dadada_1x400.png b/php/apps/phpmyadmin49/html/themes/pmahomme/jquery/images/ui-bg_glass_75_dadada_1x400.png new file mode 100644 index 00000000..6b8b33a1 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/jquery/images/ui-bg_glass_75_dadada_1x400.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/jquery/images/ui-bg_glass_75_e6e6e6_1x400.png b/php/apps/phpmyadmin49/html/themes/pmahomme/jquery/images/ui-bg_glass_75_e6e6e6_1x400.png new file mode 100644 index 00000000..81e2065b Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/jquery/images/ui-bg_glass_75_e6e6e6_1x400.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/jquery/images/ui-bg_glass_95_fef1ec_1x400.png b/php/apps/phpmyadmin49/html/themes/pmahomme/jquery/images/ui-bg_glass_95_fef1ec_1x400.png new file mode 100644 index 00000000..7172755b Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/jquery/images/ui-bg_glass_95_fef1ec_1x400.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/jquery/images/ui-bg_highlight-soft_75_cccccc_1x100.png b/php/apps/phpmyadmin49/html/themes/pmahomme/jquery/images/ui-bg_highlight-soft_75_cccccc_1x100.png new file mode 100644 index 00000000..ae3ccae0 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/jquery/images/ui-bg_highlight-soft_75_cccccc_1x100.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/jquery/images/ui-icons_222222_256x240.png b/php/apps/phpmyadmin49/html/themes/pmahomme/jquery/images/ui-icons_222222_256x240.png new file mode 100644 index 00000000..3201d9a1 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/jquery/images/ui-icons_222222_256x240.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/jquery/images/ui-icons_2e83ff_256x240.png b/php/apps/phpmyadmin49/html/themes/pmahomme/jquery/images/ui-icons_2e83ff_256x240.png new file mode 100644 index 00000000..1d920d7d Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/jquery/images/ui-icons_2e83ff_256x240.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/jquery/images/ui-icons_454545_256x240.png b/php/apps/phpmyadmin49/html/themes/pmahomme/jquery/images/ui-icons_454545_256x240.png new file mode 100644 index 00000000..47da8e5d Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/jquery/images/ui-icons_454545_256x240.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/jquery/images/ui-icons_888888_256x240.png b/php/apps/phpmyadmin49/html/themes/pmahomme/jquery/images/ui-icons_888888_256x240.png new file mode 100644 index 00000000..95e0c3ef Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/jquery/images/ui-icons_888888_256x240.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/jquery/images/ui-icons_cd0a0a_256x240.png b/php/apps/phpmyadmin49/html/themes/pmahomme/jquery/images/ui-icons_cd0a0a_256x240.png new file mode 100644 index 00000000..0ea8a5a2 Binary files /dev/null and b/php/apps/phpmyadmin49/html/themes/pmahomme/jquery/images/ui-icons_cd0a0a_256x240.png differ diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/jquery/jquery-ui.css b/php/apps/phpmyadmin49/html/themes/pmahomme/jquery/jquery-ui.css new file mode 100644 index 00000000..1ebcbc90 --- /dev/null +++ b/php/apps/phpmyadmin49/html/themes/pmahomme/jquery/jquery-ui.css @@ -0,0 +1,1312 @@ +/*! jQuery UI - v1.12.1 - 2016-12-21 +* http://jqueryui.com +* Includes: draggable.css, core.css, resizable.css, selectable.css, sortable.css, accordion.css, autocomplete.css, menu.css, button.css, controlgroup.css, checkboxradio.css, datepicker.css, dialog.css, progressbar.css, selectmenu.css, slider.css, spinner.css, tabs.css, tooltip.css, theme.css +* To view and modify this theme, visit http://jqueryui.com/themeroller/?scope=&folderName=smoothness&cornerRadiusShadow=8px&offsetLeftShadow=-8px&offsetTopShadow=-8px&thicknessShadow=8px&opacityShadow=30&bgImgOpacityShadow=0&bgTextureShadow=flat&bgColorShadow=aaaaaa&opacityOverlay=30&bgImgOpacityOverlay=0&bgTextureOverlay=flat&bgColorOverlay=aaaaaa&iconColorError=cd0a0a&fcError=cd0a0a&borderColorError=cd0a0a&bgImgOpacityError=95&bgTextureError=glass&bgColorError=fef1ec&iconColorHighlight=2e83ff&fcHighlight=363636&borderColorHighlight=fcefa1&bgImgOpacityHighlight=55&bgTextureHighlight=glass&bgColorHighlight=fbf9ee&iconColorActive=454545&fcActive=212121&borderColorActive=aaaaaa&bgImgOpacityActive=65&bgTextureActive=glass&bgColorActive=ffffff&iconColorHover=454545&fcHover=212121&borderColorHover=999999&bgImgOpacityHover=75&bgTextureHover=glass&bgColorHover=dadada&iconColorDefault=888888&fcDefault=555555&borderColorDefault=d3d3d3&bgImgOpacityDefault=75&bgTextureDefault=glass&bgColorDefault=e6e6e6&iconColorContent=222222&fcContent=222222&borderColorContent=aaaaaa&bgImgOpacityContent=75&bgTextureContent=flat&bgColorContent=ffffff&iconColorHeader=222222&fcHeader=222222&borderColorHeader=aaaaaa&bgImgOpacityHeader=75&bgTextureHeader=highlight_soft&bgColorHeader=cccccc&cornerRadius=4px&fsDefault=1.1em&fwDefault=normal&ffDefault=Verdana%2CArial%2Csans-serif +* Copyright jQuery Foundation and other contributors; Licensed MIT */ + +.ui-draggable-handle { + -ms-touch-action: none; + touch-action: none; +} +/* Layout helpers +----------------------------------*/ +.ui-helper-hidden { + display: none; +} +.ui-helper-hidden-accessible { + border: 0; + clip: rect(0 0 0 0); + height: 1px; + margin: -1px; + overflow: hidden; + padding: 0; + position: absolute; + width: 1px; +} +.ui-helper-reset { + margin: 0; + padding: 0; + border: 0; + outline: 0; + line-height: 1.3; + text-decoration: none; + font-size: 100%; + list-style: none; +} +.ui-helper-clearfix:before, +.ui-helper-clearfix:after { + content: ""; + display: table; + border-collapse: collapse; +} +.ui-helper-clearfix:after { + clear: both; +} +.ui-helper-zfix { + width: 100%; + height: 100%; + top: 0; + left: 0; + position: absolute; + opacity: 0; + filter:Alpha(Opacity=0); /* support: IE8 */ +} + +.ui-front { + z-index: 100; +} + + +/* Interaction Cues +----------------------------------*/ +.ui-state-disabled { + cursor: default !important; + pointer-events: none; +} + + +/* Icons +----------------------------------*/ +.ui-icon { + display: inline-block; + vertical-align: middle; + margin-top: -.25em; + position: relative; + text-indent: -99999px; + overflow: hidden; + background-repeat: no-repeat; +} + +.ui-widget-icon-block { + left: 50%; + margin-left: -8px; + display: block; +} + +/* Misc visuals +----------------------------------*/ + +/* Overlays */ +.ui-widget-overlay { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; +} +.ui-resizable { + position: relative; +} +.ui-resizable-handle { + position: absolute; + font-size: 0.1px; + display: block; + -ms-touch-action: none; + touch-action: none; +} +.ui-resizable-disabled .ui-resizable-handle, +.ui-resizable-autohide .ui-resizable-handle { + display: none; +} +.ui-resizable-n { + cursor: n-resize; + height: 7px; + width: 100%; + top: -5px; + left: 0; +} +.ui-resizable-s { + cursor: s-resize; + height: 7px; + width: 100%; + bottom: -5px; + left: 0; +} +.ui-resizable-e { + cursor: e-resize; + width: 7px; + right: -5px; + top: 0; + height: 100%; +} +.ui-resizable-w { + cursor: w-resize; + width: 7px; + left: -5px; + top: 0; + height: 100%; +} +.ui-resizable-se { + cursor: se-resize; + width: 12px; + height: 12px; + right: 1px; + bottom: 1px; +} +.ui-resizable-sw { + cursor: sw-resize; + width: 9px; + height: 9px; + left: -5px; + bottom: -5px; +} +.ui-resizable-nw { + cursor: nw-resize; + width: 9px; + height: 9px; + left: -5px; + top: -5px; +} +.ui-resizable-ne { + cursor: ne-resize; + width: 9px; + height: 9px; + right: -5px; + top: -5px; +} +.ui-selectable { + -ms-touch-action: none; + touch-action: none; +} +.ui-selectable-helper { + position: absolute; + z-index: 100; + border: 1px dotted black; +} +.ui-sortable-handle { + -ms-touch-action: none; + touch-action: none; +} +.ui-accordion .ui-accordion-header { + display: block; + cursor: pointer; + position: relative; + margin: 2px 0 0 0; + padding: .5em .5em .5em .7em; + font-size: 100%; +} +.ui-accordion .ui-accordion-content { + padding: 1em 2.2em; + border-top: 0; + overflow: auto; +} +.ui-autocomplete { + position: absolute; + top: 0; + left: 0; + cursor: default; +} +.ui-menu { + list-style: none; + padding: 0; + margin: 0; + display: block; + outline: 0; +} +.ui-menu .ui-menu { + position: absolute; +} +.ui-menu .ui-menu-item { + margin: 0; + cursor: pointer; + /* support: IE10, see #8844 */ + list-style-image: url("data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7"); +} +.ui-menu .ui-menu-item-wrapper { + position: relative; + padding: 3px 1em 3px .4em; +} +.ui-menu .ui-menu-divider { + margin: 5px 0; + height: 0; + font-size: 0; + line-height: 0; + border-width: 1px 0 0 0; +} +.ui-menu .ui-state-focus, +.ui-menu .ui-state-active { + margin: -1px; +} + +/* icon support */ +.ui-menu-icons { + position: relative; +} +.ui-menu-icons .ui-menu-item-wrapper { + padding-left: 2em; +} + +/* left-aligned */ +.ui-menu .ui-icon { + position: absolute; + top: 0; + bottom: 0; + left: .2em; + margin: auto 0; +} + +/* right-aligned */ +.ui-menu .ui-menu-icon { + left: auto; + right: 0; +} +.ui-button { + padding: .4em 1em; + display: inline-block; + position: relative; + line-height: normal; + margin-right: .1em; + cursor: pointer; + vertical-align: middle; + text-align: center; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + + /* Support: IE <= 11 */ + overflow: visible; +} + +.ui-button, +.ui-button:link, +.ui-button:visited, +.ui-button:hover, +.ui-button:active { + text-decoration: none; +} + +/* to make room for the icon, a width needs to be set here */ +.ui-button-icon-only { + width: 2em; + box-sizing: border-box; + text-indent: -9999px; + white-space: nowrap; +} + +/* no icon support for input elements */ +input.ui-button.ui-button-icon-only { + text-indent: 0; +} + +/* button icon element(s) */ +.ui-button-icon-only .ui-icon { + position: absolute; + top: 50%; + left: 50%; + margin-top: -8px; + margin-left: -8px; +} + +.ui-button.ui-icon-notext .ui-icon { + padding: 0; + width: 2.1em; + height: 2.1em; + text-indent: -9999px; + white-space: nowrap; + +} + +input.ui-button.ui-icon-notext .ui-icon { + width: auto; + height: auto; + text-indent: 0; + white-space: normal; + padding: .4em 1em; +} + +/* workarounds */ +/* Support: Firefox 5 - 40 */ +input.ui-button::-moz-focus-inner, +button.ui-button::-moz-focus-inner { + border: 0; + padding: 0; +} +.ui-controlgroup { + vertical-align: middle; + display: inline-block; +} +.ui-controlgroup > .ui-controlgroup-item { + float: left; + margin-left: 0; + margin-right: 0; +} +.ui-controlgroup > .ui-controlgroup-item:focus, +.ui-controlgroup > .ui-controlgroup-item.ui-visual-focus { + z-index: 9999; +} +.ui-controlgroup-vertical > .ui-controlgroup-item { + display: block; + float: none; + width: 100%; + margin-top: 0; + margin-bottom: 0; + text-align: left; +} +.ui-controlgroup-vertical .ui-controlgroup-item { + box-sizing: border-box; +} +.ui-controlgroup .ui-controlgroup-label { + padding: .4em 1em; +} +.ui-controlgroup .ui-controlgroup-label span { + font-size: 80%; +} +.ui-controlgroup-horizontal .ui-controlgroup-label + .ui-controlgroup-item { + border-left: none; +} +.ui-controlgroup-vertical .ui-controlgroup-label + .ui-controlgroup-item { + border-top: none; +} +.ui-controlgroup-horizontal .ui-controlgroup-label.ui-widget-content { + border-right: none; +} +.ui-controlgroup-vertical .ui-controlgroup-label.ui-widget-content { + border-bottom: none; +} + +/* Spinner specific style fixes */ +.ui-controlgroup-vertical .ui-spinner-input { + + /* Support: IE8 only, Android < 4.4 only */ + width: 75%; + width: calc( 100% - 2.4em ); +} +.ui-controlgroup-vertical .ui-spinner .ui-spinner-up { + border-top-style: solid; +} + +.ui-checkboxradio-label .ui-icon-background { + box-shadow: inset 1px 1px 1px #ccc; + border-radius: .12em; + border: none; +} +.ui-checkboxradio-radio-label .ui-icon-background { + width: 16px; + height: 16px; + border-radius: 1em; + overflow: visible; + border: none; +} +.ui-checkboxradio-radio-label.ui-checkboxradio-checked .ui-icon, +.ui-checkboxradio-radio-label.ui-checkboxradio-checked:hover .ui-icon { + background-image: none; + width: 8px; + height: 8px; + border-width: 4px; + border-style: solid; +} +.ui-checkboxradio-disabled { + pointer-events: none; +} +.ui-datepicker { + width: 17em; + padding: .2em .2em 0; + display: none; +} +.ui-datepicker .ui-datepicker-header { + position: relative; + padding: .2em 0; +} +.ui-datepicker .ui-datepicker-prev, +.ui-datepicker .ui-datepicker-next { + position: absolute; + top: 2px; + width: 1.8em; + height: 1.8em; +} +.ui-datepicker .ui-datepicker-prev-hover, +.ui-datepicker .ui-datepicker-next-hover { + top: 1px; +} +.ui-datepicker .ui-datepicker-prev { + left: 2px; +} +.ui-datepicker .ui-datepicker-next { + right: 2px; +} +.ui-datepicker .ui-datepicker-prev-hover { + left: 1px; +} +.ui-datepicker .ui-datepicker-next-hover { + right: 1px; +} +.ui-datepicker .ui-datepicker-prev span, +.ui-datepicker .ui-datepicker-next span { + display: block; + position: absolute; + left: 50%; + margin-left: -8px; + top: 50%; + margin-top: -8px; +} +.ui-datepicker .ui-datepicker-title { + margin: 0 2.3em; + line-height: 1.8em; + text-align: center; +} +.ui-datepicker .ui-datepicker-title select { + font-size: 1em; + margin: 1px 0; +} +.ui-datepicker select.ui-datepicker-month, +.ui-datepicker select.ui-datepicker-year { + width: 45%; +} +.ui-datepicker table { + width: 100%; + font-size: .9em; + border-collapse: collapse; + margin: 0 0 .4em; +} +.ui-datepicker th { + padding: .7em .3em; + text-align: center; + font-weight: bold; + border: 0; +} +.ui-datepicker td { + border: 0; + padding: 1px; +} +.ui-datepicker td span, +.ui-datepicker td a { + display: block; + padding: .2em; + text-align: right; + text-decoration: none; +} +.ui-datepicker .ui-datepicker-buttonpane { + background-image: none; + margin: .7em 0 0 0; + padding: 0 .2em; + border-left: 0; + border-right: 0; + border-bottom: 0; +} +.ui-datepicker .ui-datepicker-buttonpane button { + float: right; + margin: .5em .2em .4em; + cursor: pointer; + padding: .2em .6em .3em .6em; + width: auto; + overflow: visible; +} +.ui-datepicker .ui-datepicker-buttonpane button.ui-datepicker-current { + float: left; +} + +/* with multiple calendars */ +.ui-datepicker.ui-datepicker-multi { + width: auto; +} +.ui-datepicker-multi .ui-datepicker-group { + float: left; +} +.ui-datepicker-multi .ui-datepicker-group table { + width: 95%; + margin: 0 auto .4em; +} +.ui-datepicker-multi-2 .ui-datepicker-group { + width: 50%; +} +.ui-datepicker-multi-3 .ui-datepicker-group { + width: 33.3%; +} +.ui-datepicker-multi-4 .ui-datepicker-group { + width: 25%; +} +.ui-datepicker-multi .ui-datepicker-group-last .ui-datepicker-header, +.ui-datepicker-multi .ui-datepicker-group-middle .ui-datepicker-header { + border-left-width: 0; +} +.ui-datepicker-multi .ui-datepicker-buttonpane { + clear: left; +} +.ui-datepicker-row-break { + clear: both; + width: 100%; + font-size: 0; +} + +/* RTL support */ +.ui-datepicker-rtl { + direction: rtl; +} +.ui-datepicker-rtl .ui-datepicker-prev { + right: 2px; + left: auto; +} +.ui-datepicker-rtl .ui-datepicker-next { + left: 2px; + right: auto; +} +.ui-datepicker-rtl .ui-datepicker-prev:hover { + right: 1px; + left: auto; +} +.ui-datepicker-rtl .ui-datepicker-next:hover { + left: 1px; + right: auto; +} +.ui-datepicker-rtl .ui-datepicker-buttonpane { + clear: right; +} +.ui-datepicker-rtl .ui-datepicker-buttonpane button { + float: left; +} +.ui-datepicker-rtl .ui-datepicker-buttonpane button.ui-datepicker-current, +.ui-datepicker-rtl .ui-datepicker-group { + float: right; +} +.ui-datepicker-rtl .ui-datepicker-group-last .ui-datepicker-header, +.ui-datepicker-rtl .ui-datepicker-group-middle .ui-datepicker-header { + border-right-width: 0; + border-left-width: 1px; +} + +/* Icons */ +.ui-datepicker .ui-icon { + display: block; + text-indent: -99999px; + overflow: hidden; + background-repeat: no-repeat; + left: .5em; + top: .3em; +} +.ui-dialog { + position: absolute; + top: 0; + left: 0; + padding: .2em; + outline: 0; +} +.ui-dialog .ui-dialog-titlebar { + padding: .4em 1em; + position: relative; +} +.ui-dialog .ui-dialog-title { + float: left; + margin: .1em 0; + white-space: nowrap; + width: 90%; + overflow: hidden; + text-overflow: ellipsis; +} +.ui-dialog .ui-dialog-titlebar-close { + position: absolute; + right: .3em; + top: 50%; + width: 20px; + margin: -10px 0 0 0; + padding: 1px; + height: 20px; +} +.ui-dialog .ui-dialog-content { + position: relative; + border: 0; + padding: .5em 1em; + background: none; + overflow: auto; +} +.ui-dialog .ui-dialog-buttonpane { + text-align: left; + border-width: 1px 0 0 0; + background-image: none; + margin-top: .5em; + padding: .3em 1em .5em .4em; +} +.ui-dialog .ui-dialog-buttonpane .ui-dialog-buttonset { + float: right; +} +.ui-dialog .ui-dialog-buttonpane button { + margin: .5em .4em .5em 0; + cursor: pointer; +} +.ui-dialog .ui-resizable-n { + height: 2px; + top: 0; +} +.ui-dialog .ui-resizable-e { + width: 2px; + right: 0; +} +.ui-dialog .ui-resizable-s { + height: 2px; + bottom: 0; +} +.ui-dialog .ui-resizable-w { + width: 2px; + left: 0; +} +.ui-dialog .ui-resizable-se, +.ui-dialog .ui-resizable-sw, +.ui-dialog .ui-resizable-ne, +.ui-dialog .ui-resizable-nw { + width: 7px; + height: 7px; +} +.ui-dialog .ui-resizable-se { + right: 0; + bottom: 0; +} +.ui-dialog .ui-resizable-sw { + left: 0; + bottom: 0; +} +.ui-dialog .ui-resizable-ne { + right: 0; + top: 0; +} +.ui-dialog .ui-resizable-nw { + left: 0; + top: 0; +} +.ui-draggable .ui-dialog-titlebar { + cursor: move; +} +.ui-progressbar { + height: 2em; + text-align: left; + overflow: hidden; +} +.ui-progressbar .ui-progressbar-value { + margin: -1px; + height: 100%; +} +.ui-progressbar .ui-progressbar-overlay { + background: url("data:image/gif;base64,R0lGODlhKAAoAIABAAAAAP///yH/C05FVFNDQVBFMi4wAwEAAAAh+QQJAQABACwAAAAAKAAoAAACkYwNqXrdC52DS06a7MFZI+4FHBCKoDeWKXqymPqGqxvJrXZbMx7Ttc+w9XgU2FB3lOyQRWET2IFGiU9m1frDVpxZZc6bfHwv4c1YXP6k1Vdy292Fb6UkuvFtXpvWSzA+HycXJHUXiGYIiMg2R6W459gnWGfHNdjIqDWVqemH2ekpObkpOlppWUqZiqr6edqqWQAAIfkECQEAAQAsAAAAACgAKAAAApSMgZnGfaqcg1E2uuzDmmHUBR8Qil95hiPKqWn3aqtLsS18y7G1SzNeowWBENtQd+T1JktP05nzPTdJZlR6vUxNWWjV+vUWhWNkWFwxl9VpZRedYcflIOLafaa28XdsH/ynlcc1uPVDZxQIR0K25+cICCmoqCe5mGhZOfeYSUh5yJcJyrkZWWpaR8doJ2o4NYq62lAAACH5BAkBAAEALAAAAAAoACgAAAKVDI4Yy22ZnINRNqosw0Bv7i1gyHUkFj7oSaWlu3ovC8GxNso5fluz3qLVhBVeT/Lz7ZTHyxL5dDalQWPVOsQWtRnuwXaFTj9jVVh8pma9JjZ4zYSj5ZOyma7uuolffh+IR5aW97cHuBUXKGKXlKjn+DiHWMcYJah4N0lYCMlJOXipGRr5qdgoSTrqWSq6WFl2ypoaUAAAIfkECQEAAQAsAAAAACgAKAAAApaEb6HLgd/iO7FNWtcFWe+ufODGjRfoiJ2akShbueb0wtI50zm02pbvwfWEMWBQ1zKGlLIhskiEPm9R6vRXxV4ZzWT2yHOGpWMyorblKlNp8HmHEb/lCXjcW7bmtXP8Xt229OVWR1fod2eWqNfHuMjXCPkIGNileOiImVmCOEmoSfn3yXlJWmoHGhqp6ilYuWYpmTqKUgAAIfkECQEAAQAsAAAAACgAKAAAApiEH6kb58biQ3FNWtMFWW3eNVcojuFGfqnZqSebuS06w5V80/X02pKe8zFwP6EFWOT1lDFk8rGERh1TTNOocQ61Hm4Xm2VexUHpzjymViHrFbiELsefVrn6XKfnt2Q9G/+Xdie499XHd2g4h7ioOGhXGJboGAnXSBnoBwKYyfioubZJ2Hn0RuRZaflZOil56Zp6iioKSXpUAAAh+QQJAQABACwAAAAAKAAoAAACkoQRqRvnxuI7kU1a1UU5bd5tnSeOZXhmn5lWK3qNTWvRdQxP8qvaC+/yaYQzXO7BMvaUEmJRd3TsiMAgswmNYrSgZdYrTX6tSHGZO73ezuAw2uxuQ+BbeZfMxsexY35+/Qe4J1inV0g4x3WHuMhIl2jXOKT2Q+VU5fgoSUI52VfZyfkJGkha6jmY+aaYdirq+lQAACH5BAkBAAEALAAAAAAoACgAAAKWBIKpYe0L3YNKToqswUlvznigd4wiR4KhZrKt9Upqip61i9E3vMvxRdHlbEFiEXfk9YARYxOZZD6VQ2pUunBmtRXo1Lf8hMVVcNl8JafV38aM2/Fu5V16Bn63r6xt97j09+MXSFi4BniGFae3hzbH9+hYBzkpuUh5aZmHuanZOZgIuvbGiNeomCnaxxap2upaCZsq+1kAACH5BAkBAAEALAAAAAAoACgAAAKXjI8By5zf4kOxTVrXNVlv1X0d8IGZGKLnNpYtm8Lr9cqVeuOSvfOW79D9aDHizNhDJidFZhNydEahOaDH6nomtJjp1tutKoNWkvA6JqfRVLHU/QUfau9l2x7G54d1fl995xcIGAdXqMfBNadoYrhH+Mg2KBlpVpbluCiXmMnZ2Sh4GBqJ+ckIOqqJ6LmKSllZmsoq6wpQAAAh+QQJAQABACwAAAAAKAAoAAAClYx/oLvoxuJDkU1a1YUZbJ59nSd2ZXhWqbRa2/gF8Gu2DY3iqs7yrq+xBYEkYvFSM8aSSObE+ZgRl1BHFZNr7pRCavZ5BW2142hY3AN/zWtsmf12p9XxxFl2lpLn1rseztfXZjdIWIf2s5dItwjYKBgo9yg5pHgzJXTEeGlZuenpyPmpGQoKOWkYmSpaSnqKileI2FAAACH5BAkBAAEALAAAAAAoACgAAAKVjB+gu+jG4kORTVrVhRlsnn2dJ3ZleFaptFrb+CXmO9OozeL5VfP99HvAWhpiUdcwkpBH3825AwYdU8xTqlLGhtCosArKMpvfa1mMRae9VvWZfeB2XfPkeLmm18lUcBj+p5dnN8jXZ3YIGEhYuOUn45aoCDkp16hl5IjYJvjWKcnoGQpqyPlpOhr3aElaqrq56Bq7VAAAOw=="); + height: 100%; + filter: alpha(opacity=25); /* support: IE8 */ + opacity: 0.25; +} +.ui-progressbar-indeterminate .ui-progressbar-value { + background-image: none; +} +.ui-selectmenu-menu { + padding: 0; + margin: 0; + position: absolute; + top: 0; + left: 0; + display: none; +} +.ui-selectmenu-menu .ui-menu { + overflow: auto; + overflow-x: hidden; + padding-bottom: 1px; +} +.ui-selectmenu-menu .ui-menu .ui-selectmenu-optgroup { + font-size: 1em; + font-weight: bold; + line-height: 1.5; + padding: 2px 0.4em; + margin: 0.5em 0 0 0; + height: auto; + border: 0; +} +.ui-selectmenu-open { + display: block; +} +.ui-selectmenu-text { + display: block; + margin-right: 20px; + overflow: hidden; + text-overflow: ellipsis; +} +.ui-selectmenu-button.ui-button { + text-align: left; + white-space: nowrap; + width: 14em; +} +.ui-selectmenu-icon.ui-icon { + float: right; + margin-top: 0; +} +.ui-slider { + position: relative; + text-align: left; +} +.ui-slider .ui-slider-handle { + position: absolute; + z-index: 2; + width: 1.2em; + height: 1.2em; + cursor: default; + -ms-touch-action: none; + touch-action: none; +} +.ui-slider .ui-slider-range { + position: absolute; + z-index: 1; + font-size: .7em; + display: block; + border: 0; + background-position: 0 0; +} + +/* support: IE8 - See #6727 */ +.ui-slider.ui-state-disabled .ui-slider-handle, +.ui-slider.ui-state-disabled .ui-slider-range { + filter: inherit; +} + +.ui-slider-horizontal { + height: .8em; +} +.ui-slider-horizontal .ui-slider-handle { + top: -.3em; + margin-left: -.6em; +} +.ui-slider-horizontal .ui-slider-range { + top: 0; + height: 100%; +} +.ui-slider-horizontal .ui-slider-range-min { + left: 0; +} +.ui-slider-horizontal .ui-slider-range-max { + right: 0; +} + +.ui-slider-vertical { + width: .8em; + height: 100px; +} +.ui-slider-vertical .ui-slider-handle { + left: -.3em; + margin-left: 0; + margin-bottom: -.6em; +} +.ui-slider-vertical .ui-slider-range { + left: 0; + width: 100%; +} +.ui-slider-vertical .ui-slider-range-min { + bottom: 0; +} +.ui-slider-vertical .ui-slider-range-max { + top: 0; +} +.ui-spinner { + position: relative; + display: inline-block; + overflow: hidden; + padding: 0; + vertical-align: middle; +} +.ui-spinner-input { + border: none; + background: none; + color: inherit; + padding: .222em 0; + margin: .2em 0; + vertical-align: middle; + margin-left: .4em; + margin-right: 2em; +} +.ui-spinner-button { + width: 1.6em; + height: 50%; + font-size: .5em; + padding: 0; + margin: 0; + text-align: center; + position: absolute; + cursor: default; + display: block; + overflow: hidden; + right: 0; +} +/* more specificity required here to override default borders */ +.ui-spinner a.ui-spinner-button { + border-top-style: none; + border-bottom-style: none; + border-right-style: none; +} +.ui-spinner-up { + top: 0; +} +.ui-spinner-down { + bottom: 0; +} +.ui-tabs { + position: relative;/* position: relative prevents IE scroll bug (element with position: relative inside container with overflow: auto appear as "fixed") */ + padding: .2em; +} +.ui-tabs .ui-tabs-nav { + margin: 0; + padding: .2em .2em 0; +} +.ui-tabs .ui-tabs-nav li { + list-style: none; + float: left; + position: relative; + top: 0; + margin: 1px .2em 0 0; + border-bottom-width: 0; + padding: 0; + white-space: nowrap; +} +.ui-tabs .ui-tabs-nav .ui-tabs-anchor { + float: left; + padding: .5em 1em; + text-decoration: none; +} +.ui-tabs .ui-tabs-nav li.ui-tabs-active { + margin-bottom: -1px; + padding-bottom: 1px; +} +.ui-tabs .ui-tabs-nav li.ui-tabs-active .ui-tabs-anchor, +.ui-tabs .ui-tabs-nav li.ui-state-disabled .ui-tabs-anchor, +.ui-tabs .ui-tabs-nav li.ui-tabs-loading .ui-tabs-anchor { + cursor: text; +} +.ui-tabs-collapsible .ui-tabs-nav li.ui-tabs-active .ui-tabs-anchor { + cursor: pointer; +} +.ui-tabs .ui-tabs-panel { + display: block; + border-width: 0; + padding: 1em 1.4em; + background: none; +} +.ui-tooltip { + padding: 8px; + position: absolute; + z-index: 9999; + max-width: 300px; +} +body .ui-tooltip { + border-width: 2px; +} + +/* Component containers +----------------------------------*/ +.ui-widget { + font-family: Verdana,Arial,sans-serif; + font-size: 1.1em; +} +.ui-widget .ui-widget { + font-size: 1em; +} +.ui-widget input, +.ui-widget select, +.ui-widget textarea, +.ui-widget button { + font-family: Verdana,Arial,sans-serif; + font-size: 1em; +} +.ui-widget.ui-widget-content { + border: 1px solid #d3d3d3; +} +.ui-widget-content { + border: 1px solid #aaaaaa; + background: #ffffff; + color: #222222; +} +.ui-widget-content a { + color: #222222; +} +.ui-widget-header { + border: 1px solid #aaaaaa; + background: #cccccc url("images/ui-bg_highlight-soft_75_cccccc_1x100.png") 50% 50% repeat-x; + color: #222222; + font-weight: bold; +} +.ui-widget-header a { + color: #222222; +} + +/* Interaction states +----------------------------------*/ +.ui-state-default, +.ui-widget-content .ui-state-default, +.ui-widget-header .ui-state-default, +.ui-button, + +/* We use html here because we need a greater specificity to make sure disabled +works properly when clicked or hovered */ +html .ui-button.ui-state-disabled:hover, +html .ui-button.ui-state-disabled:active { + border: 1px solid #d3d3d3; + background: #e6e6e6 url("images/ui-bg_glass_75_e6e6e6_1x400.png") 50% 50% repeat-x; + font-weight: normal; + color: #555555; +} +.ui-state-default a, +.ui-state-default a:link, +.ui-state-default a:visited, +a.ui-button, +a:link.ui-button, +a:visited.ui-button, +.ui-button { + color: #555555; + text-decoration: none; +} +.ui-state-hover, +.ui-widget-content .ui-state-hover, +.ui-widget-header .ui-state-hover, +.ui-state-focus, +.ui-widget-content .ui-state-focus, +.ui-widget-header .ui-state-focus, +.ui-button:hover, +.ui-button:focus { + border: 1px solid #999999; + background: #dadada url("images/ui-bg_glass_75_dadada_1x400.png") 50% 50% repeat-x; + font-weight: normal; + color: #212121; +} +.ui-state-hover a, +.ui-state-hover a:hover, +.ui-state-hover a:link, +.ui-state-hover a:visited, +.ui-state-focus a, +.ui-state-focus a:hover, +.ui-state-focus a:link, +.ui-state-focus a:visited, +a.ui-button:hover, +a.ui-button:focus { + color: #212121; + text-decoration: none; +} + +.ui-visual-focus { + box-shadow: 0 0 3px 1px rgb(94, 158, 214); +} +.ui-state-active, +.ui-widget-content .ui-state-active, +.ui-widget-header .ui-state-active, +a.ui-button:active, +.ui-button:active, +.ui-button.ui-state-active:hover { + border: 1px solid #aaaaaa; + background: #ffffff url("images/ui-bg_glass_65_ffffff_1x400.png") 50% 50% repeat-x; + font-weight: normal; + color: #212121; +} +.ui-icon-background, +.ui-state-active .ui-icon-background { + border: #aaaaaa; + background-color: #212121; +} +.ui-state-active a, +.ui-state-active a:link, +.ui-state-active a:visited { + color: #212121; + text-decoration: none; +} + +/* Interaction Cues +----------------------------------*/ +.ui-state-highlight, +.ui-widget-content .ui-state-highlight, +.ui-widget-header .ui-state-highlight { + border: 1px solid #fcefa1; + background: #fbf9ee url("images/ui-bg_glass_55_fbf9ee_1x400.png") 50% 50% repeat-x; + color: #363636; +} +.ui-state-checked { + border: 1px solid #fcefa1; + background: #fbf9ee; +} +.ui-state-highlight a, +.ui-widget-content .ui-state-highlight a, +.ui-widget-header .ui-state-highlight a { + color: #363636; +} +.ui-state-error, +.ui-widget-content .ui-state-error, +.ui-widget-header .ui-state-error { + border: 1px solid #cd0a0a; + background: #fef1ec url("images/ui-bg_glass_95_fef1ec_1x400.png") 50% 50% repeat-x; + color: #cd0a0a; +} +.ui-state-error a, +.ui-widget-content .ui-state-error a, +.ui-widget-header .ui-state-error a { + color: #cd0a0a; +} +.ui-state-error-text, +.ui-widget-content .ui-state-error-text, +.ui-widget-header .ui-state-error-text { + color: #cd0a0a; +} +.ui-priority-primary, +.ui-widget-content .ui-priority-primary, +.ui-widget-header .ui-priority-primary { + font-weight: bold; +} +.ui-priority-secondary, +.ui-widget-content .ui-priority-secondary, +.ui-widget-header .ui-priority-secondary { + opacity: .7; + filter:Alpha(Opacity=70); /* support: IE8 */ + font-weight: normal; +} +.ui-state-disabled, +.ui-widget-content .ui-state-disabled, +.ui-widget-header .ui-state-disabled { + opacity: .35; + filter:Alpha(Opacity=35); /* support: IE8 */ + background-image: none; +} +.ui-state-disabled .ui-icon { + filter:Alpha(Opacity=35); /* support: IE8 - See #6059 */ +} + +/* Icons +----------------------------------*/ + +/* states and images */ +.ui-icon { + width: 16px; + height: 16px; +} +.ui-icon, +.ui-widget-content .ui-icon { + background-image: url("images/ui-icons_222222_256x240.png"); +} +.ui-widget-header .ui-icon { + background-image: url("images/ui-icons_222222_256x240.png"); +} +.ui-state-hover .ui-icon, +.ui-state-focus .ui-icon, +.ui-button:hover .ui-icon, +.ui-button:focus .ui-icon { + background-image: url("images/ui-icons_454545_256x240.png"); +} +.ui-state-active .ui-icon, +.ui-button:active .ui-icon { + background-image: url("images/ui-icons_454545_256x240.png"); +} +.ui-state-highlight .ui-icon, +.ui-button .ui-state-highlight.ui-icon { + background-image: url("images/ui-icons_2e83ff_256x240.png"); +} +.ui-state-error .ui-icon, +.ui-state-error-text .ui-icon { + background-image: url("images/ui-icons_cd0a0a_256x240.png"); +} +.ui-button .ui-icon { + background-image: url("images/ui-icons_888888_256x240.png"); +} + +/* positioning */ +.ui-icon-blank { background-position: 16px 16px; } +.ui-icon-caret-1-n { background-position: 0 0; } +.ui-icon-caret-1-ne { background-position: -16px 0; } +.ui-icon-caret-1-e { background-position: -32px 0; } +.ui-icon-caret-1-se { background-position: -48px 0; } +.ui-icon-caret-1-s { background-position: -65px 0; } +.ui-icon-caret-1-sw { background-position: -80px 0; } +.ui-icon-caret-1-w { background-position: -96px 0; } +.ui-icon-caret-1-nw { background-position: -112px 0; } +.ui-icon-caret-2-n-s { background-position: -128px 0; } +.ui-icon-caret-2-e-w { background-position: -144px 0; } +.ui-icon-triangle-1-n { background-position: 0 -16px; } +.ui-icon-triangle-1-ne { background-position: -16px -16px; } +.ui-icon-triangle-1-e { background-position: -32px -16px; } +.ui-icon-triangle-1-se { background-position: -48px -16px; } +.ui-icon-triangle-1-s { background-position: -65px -16px; } +.ui-icon-triangle-1-sw { background-position: -80px -16px; } +.ui-icon-triangle-1-w { background-position: -96px -16px; } +.ui-icon-triangle-1-nw { background-position: -112px -16px; } +.ui-icon-triangle-2-n-s { background-position: -128px -16px; } +.ui-icon-triangle-2-e-w { background-position: -144px -16px; } +.ui-icon-arrow-1-n { background-position: 0 -32px; } +.ui-icon-arrow-1-ne { background-position: -16px -32px; } +.ui-icon-arrow-1-e { background-position: -32px -32px; } +.ui-icon-arrow-1-se { background-position: -48px -32px; } +.ui-icon-arrow-1-s { background-position: -65px -32px; } +.ui-icon-arrow-1-sw { background-position: -80px -32px; } +.ui-icon-arrow-1-w { background-position: -96px -32px; } +.ui-icon-arrow-1-nw { background-position: -112px -32px; } +.ui-icon-arrow-2-n-s { background-position: -128px -32px; } +.ui-icon-arrow-2-ne-sw { background-position: -144px -32px; } +.ui-icon-arrow-2-e-w { background-position: -160px -32px; } +.ui-icon-arrow-2-se-nw { background-position: -176px -32px; } +.ui-icon-arrowstop-1-n { background-position: -192px -32px; } +.ui-icon-arrowstop-1-e { background-position: -208px -32px; } +.ui-icon-arrowstop-1-s { background-position: -224px -32px; } +.ui-icon-arrowstop-1-w { background-position: -240px -32px; } +.ui-icon-arrowthick-1-n { background-position: 1px -48px; } +.ui-icon-arrowthick-1-ne { background-position: -16px -48px; } +.ui-icon-arrowthick-1-e { background-position: -32px -48px; } +.ui-icon-arrowthick-1-se { background-position: -48px -48px; } +.ui-icon-arrowthick-1-s { background-position: -64px -48px; } +.ui-icon-arrowthick-1-sw { background-position: -80px -48px; } +.ui-icon-arrowthick-1-w { background-position: -96px -48px; } +.ui-icon-arrowthick-1-nw { background-position: -112px -48px; } +.ui-icon-arrowthick-2-n-s { background-position: -128px -48px; } +.ui-icon-arrowthick-2-ne-sw { background-position: -144px -48px; } +.ui-icon-arrowthick-2-e-w { background-position: -160px -48px; } +.ui-icon-arrowthick-2-se-nw { background-position: -176px -48px; } +.ui-icon-arrowthickstop-1-n { background-position: -192px -48px; } +.ui-icon-arrowthickstop-1-e { background-position: -208px -48px; } +.ui-icon-arrowthickstop-1-s { background-position: -224px -48px; } +.ui-icon-arrowthickstop-1-w { background-position: -240px -48px; } +.ui-icon-arrowreturnthick-1-w { background-position: 0 -64px; } +.ui-icon-arrowreturnthick-1-n { background-position: -16px -64px; } +.ui-icon-arrowreturnthick-1-e { background-position: -32px -64px; } +.ui-icon-arrowreturnthick-1-s { background-position: -48px -64px; } +.ui-icon-arrowreturn-1-w { background-position: -64px -64px; } +.ui-icon-arrowreturn-1-n { background-position: -80px -64px; } +.ui-icon-arrowreturn-1-e { background-position: -96px -64px; } +.ui-icon-arrowreturn-1-s { background-position: -112px -64px; } +.ui-icon-arrowrefresh-1-w { background-position: -128px -64px; } +.ui-icon-arrowrefresh-1-n { background-position: -144px -64px; } +.ui-icon-arrowrefresh-1-e { background-position: -160px -64px; } +.ui-icon-arrowrefresh-1-s { background-position: -176px -64px; } +.ui-icon-arrow-4 { background-position: 0 -80px; } +.ui-icon-arrow-4-diag { background-position: -16px -80px; } +.ui-icon-extlink { background-position: -32px -80px; } +.ui-icon-newwin { background-position: -48px -80px; } +.ui-icon-refresh { background-position: -64px -80px; } +.ui-icon-shuffle { background-position: -80px -80px; } +.ui-icon-transfer-e-w { background-position: -96px -80px; } +.ui-icon-transferthick-e-w { background-position: -112px -80px; } +.ui-icon-folder-collapsed { background-position: 0 -96px; } +.ui-icon-folder-open { background-position: -16px -96px; } +.ui-icon-document { background-position: -32px -96px; } +.ui-icon-document-b { background-position: -48px -96px; } +.ui-icon-note { background-position: -64px -96px; } +.ui-icon-mail-closed { background-position: -80px -96px; } +.ui-icon-mail-open { background-position: -96px -96px; } +.ui-icon-suitcase { background-position: -112px -96px; } +.ui-icon-comment { background-position: -128px -96px; } +.ui-icon-person { background-position: -144px -96px; } +.ui-icon-print { background-position: -160px -96px; } +.ui-icon-trash { background-position: -176px -96px; } +.ui-icon-locked { background-position: -192px -96px; } +.ui-icon-unlocked { background-position: -208px -96px; } +.ui-icon-bookmark { background-position: -224px -96px; } +.ui-icon-tag { background-position: -240px -96px; } +.ui-icon-home { background-position: 0 -112px; } +.ui-icon-flag { background-position: -16px -112px; } +.ui-icon-calendar { background-position: -32px -112px; } +.ui-icon-cart { background-position: -48px -112px; } +.ui-icon-pencil { background-position: -64px -112px; } +.ui-icon-clock { background-position: -80px -112px; } +.ui-icon-disk { background-position: -96px -112px; } +.ui-icon-calculator { background-position: -112px -112px; } +.ui-icon-zoomin { background-position: -128px -112px; } +.ui-icon-zoomout { background-position: -144px -112px; } +.ui-icon-search { background-position: -160px -112px; } +.ui-icon-wrench { background-position: -176px -112px; } +.ui-icon-gear { background-position: -192px -112px; } +.ui-icon-heart { background-position: -208px -112px; } +.ui-icon-star { background-position: -224px -112px; } +.ui-icon-link { background-position: -240px -112px; } +.ui-icon-cancel { background-position: 0 -128px; } +.ui-icon-plus { background-position: -16px -128px; } +.ui-icon-plusthick { background-position: -32px -128px; } +.ui-icon-minus { background-position: -48px -128px; } +.ui-icon-minusthick { background-position: -64px -128px; } +.ui-icon-close { background-position: -80px -128px; } +.ui-icon-closethick { background-position: -96px -128px; } +.ui-icon-key { background-position: -112px -128px; } +.ui-icon-lightbulb { background-position: -128px -128px; } +.ui-icon-scissors { background-position: -144px -128px; } +.ui-icon-clipboard { background-position: -160px -128px; } +.ui-icon-copy { background-position: -176px -128px; } +.ui-icon-contact { background-position: -192px -128px; } +.ui-icon-image { background-position: -208px -128px; } +.ui-icon-video { background-position: -224px -128px; } +.ui-icon-script { background-position: -240px -128px; } +.ui-icon-alert { background-position: 0 -144px; } +.ui-icon-info { background-position: -16px -144px; } +.ui-icon-notice { background-position: -32px -144px; } +.ui-icon-help { background-position: -48px -144px; } +.ui-icon-check { background-position: -64px -144px; } +.ui-icon-bullet { background-position: -80px -144px; } +.ui-icon-radio-on { background-position: -96px -144px; } +.ui-icon-radio-off { background-position: -112px -144px; } +.ui-icon-pin-w { background-position: -128px -144px; } +.ui-icon-pin-s { background-position: -144px -144px; } +.ui-icon-play { background-position: 0 -160px; } +.ui-icon-pause { background-position: -16px -160px; } +.ui-icon-seek-next { background-position: -32px -160px; } +.ui-icon-seek-prev { background-position: -48px -160px; } +.ui-icon-seek-end { background-position: -64px -160px; } +.ui-icon-seek-start { background-position: -80px -160px; } +/* ui-icon-seek-first is deprecated, use ui-icon-seek-start instead */ +.ui-icon-seek-first { background-position: -80px -160px; } +.ui-icon-stop { background-position: -96px -160px; } +.ui-icon-eject { background-position: -112px -160px; } +.ui-icon-volume-off { background-position: -128px -160px; } +.ui-icon-volume-on { background-position: -144px -160px; } +.ui-icon-power { background-position: 0 -176px; } +.ui-icon-signal-diag { background-position: -16px -176px; } +.ui-icon-signal { background-position: -32px -176px; } +.ui-icon-battery-0 { background-position: -48px -176px; } +.ui-icon-battery-1 { background-position: -64px -176px; } +.ui-icon-battery-2 { background-position: -80px -176px; } +.ui-icon-battery-3 { background-position: -96px -176px; } +.ui-icon-circle-plus { background-position: 0 -192px; } +.ui-icon-circle-minus { background-position: -16px -192px; } +.ui-icon-circle-close { background-position: -32px -192px; } +.ui-icon-circle-triangle-e { background-position: -48px -192px; } +.ui-icon-circle-triangle-s { background-position: -64px -192px; } +.ui-icon-circle-triangle-w { background-position: -80px -192px; } +.ui-icon-circle-triangle-n { background-position: -96px -192px; } +.ui-icon-circle-arrow-e { background-position: -112px -192px; } +.ui-icon-circle-arrow-s { background-position: -128px -192px; } +.ui-icon-circle-arrow-w { background-position: -144px -192px; } +.ui-icon-circle-arrow-n { background-position: -160px -192px; } +.ui-icon-circle-zoomin { background-position: -176px -192px; } +.ui-icon-circle-zoomout { background-position: -192px -192px; } +.ui-icon-circle-check { background-position: -208px -192px; } +.ui-icon-circlesmall-plus { background-position: 0 -208px; } +.ui-icon-circlesmall-minus { background-position: -16px -208px; } +.ui-icon-circlesmall-close { background-position: -32px -208px; } +.ui-icon-squaresmall-plus { background-position: -48px -208px; } +.ui-icon-squaresmall-minus { background-position: -64px -208px; } +.ui-icon-squaresmall-close { background-position: -80px -208px; } +.ui-icon-grip-dotted-vertical { background-position: 0 -224px; } +.ui-icon-grip-dotted-horizontal { background-position: -16px -224px; } +.ui-icon-grip-solid-vertical { background-position: -32px -224px; } +.ui-icon-grip-solid-horizontal { background-position: -48px -224px; } +.ui-icon-gripsmall-diagonal-se { background-position: -64px -224px; } +.ui-icon-grip-diagonal-se { background-position: -80px -224px; } + + +/* Misc visuals +----------------------------------*/ + +/* Corner radius */ +.ui-corner-all, +.ui-corner-top, +.ui-corner-left, +.ui-corner-tl { + border-top-left-radius: 4px; +} +.ui-corner-all, +.ui-corner-top, +.ui-corner-right, +.ui-corner-tr { + border-top-right-radius: 4px; +} +.ui-corner-all, +.ui-corner-bottom, +.ui-corner-left, +.ui-corner-bl { + border-bottom-left-radius: 4px; +} +.ui-corner-all, +.ui-corner-bottom, +.ui-corner-right, +.ui-corner-br { + border-bottom-right-radius: 4px; +} + +/* Overlays */ +.ui-widget-overlay { + background: #aaaaaa; + opacity: .3; + filter: Alpha(Opacity=30); /* support: IE8 */ +} +.ui-widget-shadow { + -webkit-box-shadow: -8px -8px 8px #aaaaaa; + box-shadow: -8px -8px 8px #aaaaaa; +} diff --git a/php/apps/phpmyadmin49/html/themes/pmahomme/layout.inc.php b/php/apps/phpmyadmin49/html/themes/pmahomme/layout.inc.php new file mode 100644 index 00000000..6ac5ddce --- /dev/null +++ b/php/apps/phpmyadmin49/html/themes/pmahomme/layout.inc.php @@ -0,0 +1,77 @@ + array('regexp' => '/^[a-z0-9]+$/i')); + $color = filter_input(INPUT_GET, $get_name, FILTER_VALIDATE_REGEXP, $opts); + if (preg_match('/^[a-f0-9]{6}$/', $color)) { + return '#' . $color; + } + return $color ? $color : $default; +} + +$from = PMA_gradientGetColor('from', 'white'); +$to = PMA_gradientGetColor('to', 'blank'); + +echo ''; +?> + + + + + + + + + diff --git a/php/apps/phpmyadmin49/html/tmp/twig/06/06f8eb7e2cc9dcd88b24253ef85a069b47d801b3fffc06e7aea2068971e06515.php b/php/apps/phpmyadmin49/html/tmp/twig/06/06f8eb7e2cc9dcd88b24253ef85a069b47d801b3fffc06e7aea2068971e06515.php new file mode 100644 index 00000000..d9c7a7b9 --- /dev/null +++ b/php/apps/phpmyadmin49/html/tmp/twig/06/06f8eb7e2cc9dcd88b24253ef85a069b47d801b3fffc06e7aea2068971e06515.php @@ -0,0 +1,78 @@ +parent = false; + + $this->blocks = [ + ]; + } + + protected function doDisplay(array $context, array $blocks = []) + { + // line 1 + echo "env, ($context["align"] ?? null), "html", null, true); + echo " + data-decimals=\""; + // line 2 + (($this->getAttribute(($context["meta"] ?? null), "decimals", [], "any", true, true)) ? (print (twig_escape_filter($this->env, $this->getAttribute(($context["meta"] ?? null), "decimals", []), "html", null, true))) : (print ("-1"))); + echo "\" + data-type=\""; + // line 3 + echo twig_escape_filter($this->env, $this->getAttribute(($context["meta"] ?? null), "type", []), "html", null, true); + echo "\" + "; + // line 5 + echo " class=\""; + echo twig_escape_filter($this->env, ($context["classes"] ?? null), "html", null, true); + echo " null\"> + NULL + +"; + } + + public function getTemplateName() + { + return "display/results/null_display.twig"; + } + + public function isTraitable() + { + return false; + } + + public function getDebugInfo() + { + return array ( 43 => 5, 39 => 3, 35 => 2, 30 => 1,); + } + + /** @deprecated since 1.27 (to be removed in 2.0). Use getSourceContext() instead */ + public function getSource() + { + @trigger_error('The '.__METHOD__.' method is deprecated since version 1.27 and will be removed in 2.0. Use getSourceContext() instead.', E_USER_DEPRECATED); + + return $this->getSourceContext()->getCode(); + } + + public function getSourceContext() + { + return new Source("", "display/results/null_display.twig", "/var/www/html/templates/display/results/null_display.twig"); + } +} diff --git a/php/apps/phpmyadmin49/html/tmp/twig/07/07f658775362dd23f2700519ecf5cffce6ccdf4c81a61a6efe34a4718970990e.php b/php/apps/phpmyadmin49/html/tmp/twig/07/07f658775362dd23f2700519ecf5cffce6ccdf4c81a61a6efe34a4718970990e.php new file mode 100644 index 00000000..6f791897 --- /dev/null +++ b/php/apps/phpmyadmin49/html/tmp/twig/07/07f658775362dd23f2700519ecf5cffce6ccdf4c81a61a6efe34a4718970990e.php @@ -0,0 +1,231 @@ +parent = false; + + $this->blocks = [ + ]; + } + + protected function doDisplay(array $context, array $blocks = []) + { + // line 1 + echo "env, ($context["tr_class"] ?? null), "html", null, true); + echo "\""; + } + echo " data-filter-row=\""; + echo twig_escape_filter($this->env, twig_upper_filter($this->env, $this->getAttribute(($context["current"] ?? null), "SCHEMA_NAME", [], "array")), "html", null, true); + echo "\"> + "; + // line 2 + if ((($context["is_superuser"] ?? null) || ($context["allow_user_drop_database"] ?? null))) { + // line 3 + echo " + env, $this->getAttribute(($context["current"] ?? null), "SCHEMA_NAME", [], "array"), "html", null, true); + echo "\" value=\""; + // line 6 + echo twig_escape_filter($this->env, $this->getAttribute(($context["current"] ?? null), "SCHEMA_NAME", [], "array"), "html", null, true); + echo "\""; + // line 7 + if (($context["is_system_schema"] ?? null)) { + echo " disabled=\"disabled\""; + } + echo " /> + + "; + } + // line 10 + echo " + $this->getAttribute(($context["current"] ?? null), "SCHEMA_NAME", [], "array")]); + echo "\" title=\""; + // line 13 + echo twig_escape_filter($this->env, sprintf(_gettext("Jump to database '%s'"), twig_escape_filter($this->env, $this->getAttribute(($context["current"] ?? null), "SCHEMA_NAME", [], "array"))), "html", null, true); + echo "\"> + "; + // line 14 + echo twig_escape_filter($this->env, $this->getAttribute(($context["current"] ?? null), "SCHEMA_NAME", [], "array"), "html", null, true); + echo " + + + "; + // line 17 + $context['_parent'] = $context; + $context['_seq'] = twig_ensure_traversable(($context["column_order"] ?? null)); + foreach ($context['_seq'] as $context["stat_name"] => $context["stat"]) { + if (twig_in_filter($context["stat_name"], twig_get_array_keys_filter(($context["current"] ?? null)))) { + // line 18 + echo " "; + if (($this->getAttribute($context["stat"], "format", [], "array") === "byte")) { + // line 19 + echo " "; + $context["byte_format"] = PhpMyAdmin\Util::formatByteDown($this->getAttribute(($context["current"] ?? null), $context["stat_name"], [], "array"), 3, 1); + // line 20 + echo " "; + $context["value"] = $this->getAttribute(($context["byte_format"] ?? null), 0, [], "array"); + // line 21 + echo " "; + $context["unit"] = $this->getAttribute(($context["byte_format"] ?? null), 1, [], "array"); + // line 22 + echo " "; + } elseif (($this->getAttribute($context["stat"], "format", [], "array") === "number")) { + // line 23 + echo " "; + $context["value"] = PhpMyAdmin\Util::formatNumber($this->getAttribute(($context["current"] ?? null), $context["stat_name"], [], "array"), 0); + // line 24 + echo " "; + } else { + // line 25 + echo " "; + $context["value"] = htmlentities($this->getAttribute(($context["current"] ?? null), $context["stat_name"], [], "array"), 0); + // line 26 + echo " "; + } + // line 27 + echo " + + "; + // line 29 + if ($this->getAttribute($context["stat"], "description_function", [], "array", true, true)) { + // line 30 + echo " env, PhpMyAdmin\Charsets::getCollationDescr($this->getAttribute(($context["current"] ?? null), $context["stat_name"], [], "array")), "html", null, true); + echo "\"> + "; + // line 31 + echo twig_escape_filter($this->env, ($context["value"] ?? null), "html", null, true); + echo " + + "; + } else { + // line 34 + echo " "; + echo twig_escape_filter($this->env, ($context["value"] ?? null), "html", null, true); + echo " + "; + } + // line 36 + echo " + "; + // line 37 + if (($this->getAttribute($context["stat"], "format", [], "array") === "byte")) { + // line 38 + echo " "; + echo twig_escape_filter($this->env, ($context["unit"] ?? null), "html", null, true); + echo " + "; + } + // line 40 + echo " "; + } + } + $_parent = $context['_parent']; + unset($context['_seq'], $context['_iterated'], $context['stat_name'], $context['stat'], $context['_parent'], $context['loop']); + $context = array_intersect_key($context, $_parent) + $_parent; + // line 41 + echo " + "; + // line 42 + if (($context["master_replication_status"] ?? null)) { + // line 43 + echo " + "; + // line 44 + echo ($context["master_replication"] ?? null); + echo " + + "; + } + // line 47 + echo " + "; + // line 48 + if (($context["slave_replication_status"] ?? null)) { + // line 49 + echo " + "; + // line 50 + echo ($context["slave_replication"] ?? null); + echo " + + "; + } + // line 53 + echo " + + getAttribute(($context["current"] ?? null), "SCHEMA_NAME", [], "array")); + echo "\" href=\"server_privileges.php"; + // line 57 + echo PhpMyAdmin\Url::getCommon(["db" => $this->getAttribute( // line 58 +($context["current"] ?? null), "SCHEMA_NAME", [], "array"), "checkprivsdb" => $this->getAttribute( // line 59 +($context["current"] ?? null), "SCHEMA_NAME", [], "array")]); + // line 60 + echo "\" title=\""; + // line 61 + echo twig_escape_filter($this->env, sprintf(_gettext("Check privileges for database \"%s\"."), twig_escape_filter($this->env, $this->getAttribute(($context["current"] ?? null), "SCHEMA_NAME", [], "array"))), "html", null, true); + echo "\"> + "; + // line 62 + echo PhpMyAdmin\Util::getIcon("s_rights", _gettext("Check privileges")); + echo " + + + +"; + } + + public function getTemplateName() + { + return "server/databases/table_row.twig"; + } + + public function isTraitable() + { + return false; + } + + public function getDebugInfo() + { + return array ( 196 => 62, 192 => 61, 190 => 60, 188 => 59, 187 => 58, 186 => 57, 183 => 56, 179 => 53, 173 => 50, 170 => 49, 168 => 48, 165 => 47, 159 => 44, 156 => 43, 154 => 42, 151 => 41, 144 => 40, 138 => 38, 136 => 37, 133 => 36, 127 => 34, 121 => 31, 116 => 30, 114 => 29, 110 => 27, 107 => 26, 104 => 25, 101 => 24, 98 => 23, 95 => 22, 92 => 21, 89 => 20, 86 => 19, 83 => 18, 78 => 17, 72 => 14, 68 => 13, 65 => 12, 63 => 11, 60 => 10, 52 => 7, 49 => 6, 46 => 5, 43 => 3, 41 => 2, 30 => 1,); + } + + /** @deprecated since 1.27 (to be removed in 2.0). Use getSourceContext() instead */ + public function getSource() + { + @trigger_error('The '.__METHOD__.' method is deprecated since version 1.27 and will be removed in 2.0. Use getSourceContext() instead.', E_USER_DEPRECATED); + + return $this->getSourceContext()->getCode(); + } + + public function getSourceContext() + { + return new Source("", "server/databases/table_row.twig", "/var/www/html/templates/server/databases/table_row.twig"); + } +} diff --git a/php/apps/phpmyadmin49/html/tmp/twig/08/08f306b2de1428dd15e0a78c85514f84af0c3f743de6e7b130c8f64ae6320dd1.php b/php/apps/phpmyadmin49/html/tmp/twig/08/08f306b2de1428dd15e0a78c85514f84af0c3f743de6e7b130c8f64ae6320dd1.php new file mode 100644 index 00000000..96d11cc0 --- /dev/null +++ b/php/apps/phpmyadmin49/html/tmp/twig/08/08f306b2de1428dd15e0a78c85514f84af0c3f743de6e7b130c8f64ae6320dd1.php @@ -0,0 +1,76 @@ +parent = false; + + $this->blocks = [ + ]; + } + + protected function doDisplay(array $context, array $blocks = []) + { + // line 1 + echo " + + "; + // line 3 + echo _gettext("Action"); + echo " + "; + // line 4 + echo _gettext("Variable"); + echo " + "; + // line 5 + echo _gettext("Value"); + echo " + + +"; + } + + public function getTemplateName() + { + return "server/variables/variable_table_head.twig"; + } + + public function isTraitable() + { + return false; + } + + public function getDebugInfo() + { + return array ( 42 => 5, 38 => 4, 34 => 3, 30 => 1,); + } + + /** @deprecated since 1.27 (to be removed in 2.0). Use getSourceContext() instead */ + public function getSource() + { + @trigger_error('The '.__METHOD__.' method is deprecated since version 1.27 and will be removed in 2.0. Use getSourceContext() instead.', E_USER_DEPRECATED); + + return $this->getSourceContext()->getCode(); + } + + public function getSourceContext() + { + return new Source("", "server/variables/variable_table_head.twig", "/var/www/html/templates/server/variables/variable_table_head.twig"); + } +} diff --git a/php/apps/phpmyadmin49/html/tmp/twig/0c/0c7f3826af429cf4156c048fa88e56bfe4c9343fc3ae8e68d10deee45f2aa945.php b/php/apps/phpmyadmin49/html/tmp/twig/0c/0c7f3826af429cf4156c048fa88e56bfe4c9343fc3ae8e68d10deee45f2aa945.php new file mode 100644 index 00000000..d64a818b --- /dev/null +++ b/php/apps/phpmyadmin49/html/tmp/twig/0c/0c7f3826af429cf4156c048fa88e56bfe4c9343fc3ae8e68d10deee45f2aa945.php @@ -0,0 +1,94 @@ +parent = false; + + $this->blocks = [ + ]; + } + + protected function doDisplay(array $context, array $blocks = []) + { + // line 1 + echo "
      env, ($context["parent_div_classes"] ?? null), "html", null, true); + echo "\"> + "; + // line 2 + $context['_parent'] = $context; + $context['_seq'] = twig_ensure_traversable(($context["content_array"] ?? null)); + foreach ($context['_seq'] as $context["_key"] => $context["content"]) { + // line 3 + echo " "; + if ((isset($context["content"]) || array_key_exists("content", $context))) { + // line 4 + echo "
      env, $this->getAttribute($context["content"], 0, [], "array"), "html", null, true); + echo "\"> + "; + // line 5 + echo (($this->getAttribute($context["content"], "image", [], "array", true, true)) ? ($this->getAttribute($context["content"], "image", [], "array")) : ("")); + echo " + "; + // line 6 + echo twig_escape_filter($this->env, $this->getAttribute($context["content"], 1, [], "array"), "html", null, true); + echo " +
      + "; + } + // line 9 + echo " "; + } + $_parent = $context['_parent']; + unset($context['_seq'], $context['_iterated'], $context['_key'], $context['content'], $context['_parent'], $context['loop']); + $context = array_intersect_key($context, $_parent) + $_parent; + // line 10 + echo "
      +"; + } + + public function getTemplateName() + { + return "console/toolbar.twig"; + } + + public function isTraitable() + { + return false; + } + + public function getDebugInfo() + { + return array ( 63 => 10, 57 => 9, 51 => 6, 47 => 5, 42 => 4, 39 => 3, 35 => 2, 30 => 1,); + } + + /** @deprecated since 1.27 (to be removed in 2.0). Use getSourceContext() instead */ + public function getSource() + { + @trigger_error('The '.__METHOD__.' method is deprecated since version 1.27 and will be removed in 2.0. Use getSourceContext() instead.', E_USER_DEPRECATED); + + return $this->getSourceContext()->getCode(); + } + + public function getSourceContext() + { + return new Source("", "console/toolbar.twig", "/var/www/html/templates/console/toolbar.twig"); + } +} diff --git a/php/apps/phpmyadmin49/html/tmp/twig/11/1104ea9092621abee6010996d0b0f07f0f0be2360f543199609a215fc3c09079.php b/php/apps/phpmyadmin49/html/tmp/twig/11/1104ea9092621abee6010996d0b0f07f0f0be2360f543199609a215fc3c09079.php new file mode 100644 index 00000000..4256e0e1 --- /dev/null +++ b/php/apps/phpmyadmin49/html/tmp/twig/11/1104ea9092621abee6010996d0b0f07f0f0be2360f543199609a215fc3c09079.php @@ -0,0 +1,98 @@ +parent = false; + + $this->blocks = [ + ]; + } + + protected function doDisplay(array $context, array $blocks = []) + { + // line 1 + echo " +
      + "; + // line 3 + echo PhpMyAdmin\Url::getHiddenInputs(($context["db"] ?? null), ($context["table"] ?? null)); + echo " + + + env, ($context["is_browse_distinct"] ?? null), "html", null, true); + echo "\" /> + env, ($context["max_rows"] ?? null), "html", null, true)))); + echo "\" /> + env, ($context["goto"] ?? null), "html", null, true); + echo "\" /> + env, ($context["unique_id"] ?? null), "html", null, true); + echo "\" class=\"showAllRows\""; + // line 10 + echo ((($context["showing_all"] ?? null)) ? (" checked=\"checked\"") : ("")); + echo " value=\"all\" /> + +
      + +"; + } + + public function getTemplateName() + { + return "display/results/show_all_checkbox.twig"; + } + + public function isTraitable() + { + return false; + } + + public function getDebugInfo() + { + return array ( 62 => 11, 58 => 10, 55 => 9, 51 => 8, 47 => 7, 43 => 6, 38 => 4, 34 => 3, 30 => 1,); + } + + /** @deprecated since 1.27 (to be removed in 2.0). Use getSourceContext() instead */ + public function getSource() + { + @trigger_error('The '.__METHOD__.' method is deprecated since version 1.27 and will be removed in 2.0. Use getSourceContext() instead.', E_USER_DEPRECATED); + + return $this->getSourceContext()->getCode(); + } + + public function getSourceContext() + { + return new Source("", "display/results/show_all_checkbox.twig", "/var/www/html/templates/display/results/show_all_checkbox.twig"); + } +} diff --git a/php/apps/phpmyadmin49/html/tmp/twig/19/192548230bd30a8740679da996ffb40d8109a4e259c07209ae95f52f541053ce.php b/php/apps/phpmyadmin49/html/tmp/twig/19/192548230bd30a8740679da996ffb40d8109a4e259c07209ae95f52f541053ce.php new file mode 100644 index 00000000..73827370 --- /dev/null +++ b/php/apps/phpmyadmin49/html/tmp/twig/19/192548230bd30a8740679da996ffb40d8109a4e259c07209ae95f52f541053ce.php @@ -0,0 +1,221 @@ +parent = false; + + $this->blocks = [ + ]; + } + + protected function doDisplay(array $context, array $blocks = []) + { + // line 1 + echo " + + "; + // line 3 + if ((($context["is_superuser"] ?? null) || ($context["allow_user_drop_database"] ?? null))) { + // line 4 + echo " + "; + } + // line 6 + echo " + "; + // line 7 + echo _gettext("Total"); + echo ": "; + // line 8 + echo twig_escape_filter($this->env, ($context["database_count"] ?? null), "html", null, true); + // line 9 + echo " + + "; + // line 11 + $context['_parent'] = $context; + $context['_seq'] = twig_ensure_traversable(($context["column_order"] ?? null)); + foreach ($context['_seq'] as $context["stat_name"] => $context["stat"]) { + if (twig_in_filter($context["stat_name"], twig_get_array_keys_filter(($context["first_database"] ?? null)))) { + // line 12 + echo " "; + if (($this->getAttribute($context["stat"], "format", [], "array") === "byte")) { + // line 13 + echo " "; + $context["byte_format"] = PhpMyAdmin\Util::formatByteDown($this->getAttribute($context["stat"], "footer", [], "array"), 3, 1); + // line 14 + echo " "; + $context["value"] = $this->getAttribute(($context["byte_format"] ?? null), 0, [], "array"); + // line 15 + echo " "; + $context["unit"] = $this->getAttribute(($context["byte_format"] ?? null), 1, [], "array"); + // line 16 + echo " "; + } elseif (($this->getAttribute($context["stat"], "format", [], "array") === "number")) { + // line 17 + echo " "; + $context["value"] = PhpMyAdmin\Util::formatNumber($this->getAttribute($context["stat"], "footer", [], "array"), 0); + // line 18 + echo " "; + } else { + // line 19 + echo " "; + $context["value"] = htmlentities($this->getAttribute($context["stat"], "footer", [], "array"), 0); + // line 20 + echo " "; + } + // line 21 + echo " + + "; + // line 23 + if ($this->getAttribute($context["stat"], "description_function", [], "array", true, true)) { + // line 24 + echo " env, PhpMyAdmin\Charsets::getCollationDescr($this->getAttribute($context["stat"], "footer", [], "array")), "html", null, true); + echo "\"> + "; + // line 25 + echo twig_escape_filter($this->env, ($context["value"] ?? null), "html", null, true); + echo " + + "; + } else { + // line 28 + echo " "; + echo twig_escape_filter($this->env, ($context["value"] ?? null), "html", null, true); + echo " + "; + } + // line 30 + echo " + "; + // line 31 + if (($this->getAttribute($context["stat"], "format", [], "array") === "byte")) { + // line 32 + echo " "; + echo twig_escape_filter($this->env, ($context["unit"] ?? null), "html", null, true); + echo " + "; + } + // line 34 + echo " "; + } + } + $_parent = $context['_parent']; + unset($context['_seq'], $context['_iterated'], $context['stat_name'], $context['stat'], $context['_parent'], $context['loop']); + $context = array_intersect_key($context, $_parent) + $_parent; + // line 35 + echo " "; + if (($context["master_replication"] ?? null)) { + // line 36 + echo " + "; + } + // line 38 + echo " "; + if (($context["slave_replication"] ?? null)) { + // line 39 + echo " + "; + } + // line 41 + echo " + + + +
    • + +"; + // line 48 + if ((($context["is_superuser"] ?? null) || ($context["allow_user_drop_database"] ?? null))) { + // line 49 + echo " "; + $this->loadTemplate("select_all.twig", "server/databases/databases_footer.twig", 49)->display(twig_to_array(["pma_theme_image" => // line 50 +($context["pma_theme_image"] ?? null), "text_dir" => // line 51 +($context["text_dir"] ?? null), "form_name" => "dbStatsForm"])); + // line 54 + echo " + "; + // line 55 + echo PhpMyAdmin\Util::getButtonOrImage("", "mult_submit ajax", _gettext("Drop"), "b_deltbl"); + // line 60 + echo " +"; + } + // line 62 + echo " +"; + // line 64 + if (twig_test_empty(($context["dbstats"] ?? null))) { + // line 65 + echo " "; + echo call_user_func_array($this->env->getFunction('Message_notice')->getCallable(), [_gettext("Note: Enabling the database statistics here might cause heavy traffic between the web server and the MySQL server.")]); + echo " + +"; + } + // line 74 + echo " +
      +"; + } + + public function getTemplateName() + { + return "server/databases/databases_footer.twig"; + } + + public function isTraitable() + { + return false; + } + + public function getDebugInfo() + { + return array ( 189 => 74, 181 => 69, 175 => 68, 168 => 65, 166 => 64, 163 => 62, 159 => 60, 157 => 55, 154 => 54, 152 => 51, 151 => 50, 149 => 49, 147 => 48, 139 => 41, 135 => 39, 132 => 38, 128 => 36, 125 => 35, 118 => 34, 112 => 32, 110 => 31, 107 => 30, 101 => 28, 95 => 25, 90 => 24, 88 => 23, 84 => 21, 81 => 20, 78 => 19, 75 => 18, 72 => 17, 69 => 16, 66 => 15, 63 => 14, 60 => 13, 57 => 12, 52 => 11, 48 => 9, 46 => 8, 43 => 7, 40 => 6, 36 => 4, 34 => 3, 30 => 1,); + } + + /** @deprecated since 1.27 (to be removed in 2.0). Use getSourceContext() instead */ + public function getSource() + { + @trigger_error('The '.__METHOD__.' method is deprecated since version 1.27 and will be removed in 2.0. Use getSourceContext() instead.', E_USER_DEPRECATED); + + return $this->getSourceContext()->getCode(); + } + + public function getSourceContext() + { + return new Source("", "server/databases/databases_footer.twig", "/var/www/html/templates/server/databases/databases_footer.twig"); + } +} diff --git a/php/apps/phpmyadmin49/html/tmp/twig/26/263bef0a45caf61c28f1095f3d1b1d5122232406edc1900ea410e6bec70a5ab5.php b/php/apps/phpmyadmin49/html/tmp/twig/26/263bef0a45caf61c28f1095f3d1b1d5122232406edc1900ea410e6bec70a5ab5.php new file mode 100644 index 00000000..95a6749c --- /dev/null +++ b/php/apps/phpmyadmin49/html/tmp/twig/26/263bef0a45caf61c28f1095f3d1b1d5122232406edc1900ea410e6bec70a5ab5.php @@ -0,0 +1,132 @@ +parent = false; + + $this->blocks = [ + ]; + } + + protected function doDisplay(array $context, array $blocks = []) + { + // line 1 + echo "
      + "; + // line 2 + echo PhpMyAdmin\Url::getHiddenInputs(($context["_form_params"] ?? null)); + echo " + + "; + // line 4 + if (($context["use_fieldset"] ?? null)) { + // line 5 + echo "
      + "; + // line 6 + echo ($context["language_title"] ?? null); + echo " + "; + } else { + // line 8 + echo " + + + "; + } + // line 12 + echo " + + + "; + // line 28 + if (($context["use_fieldset"] ?? null)) { + // line 29 + echo "
      + "; + } + // line 31 + echo " +
      +"; + } + + public function getTemplateName() + { + return "select_lang.twig"; + } + + public function isTraitable() + { + return false; + } + + public function getDebugInfo() + { + return array ( 100 => 31, 96 => 29, 94 => 28, 89 => 25, 80 => 22, 77 => 21, 74 => 19, 72 => 18, 68 => 17, 66 => 16, 62 => 15, 57 => 12, 51 => 9, 48 => 8, 43 => 6, 40 => 5, 38 => 4, 33 => 2, 30 => 1,); + } + + /** @deprecated since 1.27 (to be removed in 2.0). Use getSourceContext() instead */ + public function getSource() + { + @trigger_error('The '.__METHOD__.' method is deprecated since version 1.27 and will be removed in 2.0. Use getSourceContext() instead.', E_USER_DEPRECATED); + + return $this->getSourceContext()->getCode(); + } + + public function getSourceContext() + { + return new Source("", "select_lang.twig", "/var/www/html/templates/select_lang.twig"); + } +} diff --git a/php/apps/phpmyadmin49/html/tmp/twig/2b/2b19c3283bb1a1d40c130ef2bc08b4641ec48406750f1fd40f3ff550d1a0a21b.php b/php/apps/phpmyadmin49/html/tmp/twig/2b/2b19c3283bb1a1d40c130ef2bc08b4641ec48406750f1fd40f3ff550d1a0a21b.php new file mode 100644 index 00000000..b8b42500 --- /dev/null +++ b/php/apps/phpmyadmin49/html/tmp/twig/2b/2b19c3283bb1a1d40c130ef2bc08b4641ec48406750f1fd40f3ff550d1a0a21b.php @@ -0,0 +1,114 @@ +parent = false; + + $this->blocks = [ + ]; + } + + protected function doDisplay(array $context, array $blocks = []) + { + // line 1 + echo " $context["value"]) { + // line 3 + echo " "; + echo twig_escape_filter($this->env, $context["key"], "html", null, true); + echo "=\""; + echo twig_escape_filter($this->env, $context["value"], "html", null, true); + echo "\""; + } + $_parent = $context['_parent']; + unset($context['_seq'], $context['_iterated'], $context['key'], $context['value'], $context['_parent'], $context['loop']); + $context = array_intersect_key($context, $_parent) + $_parent; + // line 4 + echo "> +"; + // line 5 + echo twig_escape_filter($this->env, ($context["title"] ?? null), "html", null, true); + echo " +"; + // line 6 + if ( !twig_test_empty(($context["description"] ?? null))) { + // line 7 + echo "

      "; + echo ($context["description"] ?? null); + echo "

      +"; + } + // line 10 + if ((twig_test_iterable(($context["errors"] ?? null)) && (twig_length_filter($this->env, ($context["errors"] ?? null)) > 0))) { + // line 11 + echo "
      + "; + // line 12 + $context['_parent'] = $context; + $context['_seq'] = twig_ensure_traversable(($context["errors"] ?? null)); + foreach ($context['_seq'] as $context["_key"] => $context["error"]) { + // line 13 + echo "
      "; + echo twig_escape_filter($this->env, $context["error"], "html", null, true); + echo "
      + "; + } + $_parent = $context['_parent']; + unset($context['_seq'], $context['_iterated'], $context['_key'], $context['error'], $context['_parent'], $context['loop']); + $context = array_intersect_key($context, $_parent) + $_parent; + // line 15 + echo "
      +"; + } + // line 17 + echo " +"; + } + + public function getTemplateName() + { + return "config/form_display/fieldset_top.twig"; + } + + public function isTraitable() + { + return false; + } + + public function getDebugInfo() + { + return array ( 83 => 17, 79 => 15, 70 => 13, 66 => 12, 63 => 11, 61 => 10, 55 => 7, 53 => 6, 49 => 5, 46 => 4, 36 => 3, 32 => 2, 30 => 1,); + } + + /** @deprecated since 1.27 (to be removed in 2.0). Use getSourceContext() instead */ + public function getSource() + { + @trigger_error('The '.__METHOD__.' method is deprecated since version 1.27 and will be removed in 2.0. Use getSourceContext() instead.', E_USER_DEPRECATED); + + return $this->getSourceContext()->getCode(); + } + + public function getSourceContext() + { + return new Source("", "config/form_display/fieldset_top.twig", "/var/www/html/templates/config/form_display/fieldset_top.twig"); + } +} diff --git a/php/apps/phpmyadmin49/html/tmp/twig/2c/2c12cc2b5f8448ad956d0f89197a641906a8421e4ecc3a012834786f1f8e13fc.php b/php/apps/phpmyadmin49/html/tmp/twig/2c/2c12cc2b5f8448ad956d0f89197a641906a8421e4ecc3a012834786f1f8e13fc.php new file mode 100644 index 00000000..c506395b --- /dev/null +++ b/php/apps/phpmyadmin49/html/tmp/twig/2c/2c12cc2b5f8448ad956d0f89197a641906a8421e4ecc3a012834786f1f8e13fc.php @@ -0,0 +1,87 @@ +parent = false; + + $this->blocks = [ + ]; + } + + protected function doDisplay(array $context, array $blocks = []) + { + // line 2 + $context["header"] = ["variables" => ["image" => "s_vars", "text" => _gettext("Server variables and settings")], "engines" => ["image" => "b_engine", "text" => _gettext("Storage engines")], "plugins" => ["image" => "b_plugin", "text" => _gettext("Plugins")], "binlog" => ["image" => "s_tbl", "text" => _gettext("Binary log")], "collations" => ["image" => "s_asci", "text" => _gettext("Character sets and collations")], "replication" => ["image" => "s_replication", "text" => _gettext("Replication")], "database_statistics" => ["image" => "s_db", "text" => _gettext("Databases statistics")], "databases" => ["image" => "s_db", "text" => _gettext("Databases")], "privileges" => ["image" => "b_usrlist", "text" => _gettext("Privileges")]]; + // line 40 + echo "

      + "; + // line 41 + if ((((isset($context["is_image"]) || array_key_exists("is_image", $context))) ? (_twig_default_filter(($context["is_image"] ?? null), true)) : (true))) { + // line 42 + echo " "; + echo PhpMyAdmin\Util::getImage($this->getAttribute($this->getAttribute(($context["header"] ?? null), ($context["type"] ?? null), [], "array"), "image", [], "array")); + echo " + "; + } else { + // line 44 + echo " "; + echo PhpMyAdmin\Util::getIcon($this->getAttribute($this->getAttribute(($context["header"] ?? null), ($context["type"] ?? null), [], "array"), "image", [], "array")); + echo " + "; + } + // line 46 + echo " "; + echo twig_escape_filter($this->env, $this->getAttribute($this->getAttribute(($context["header"] ?? null), ($context["type"] ?? null), [], "array"), "text", [], "array"), "html", null, true); + echo " + "; + // line 47 + echo (((isset($context["link"]) || array_key_exists("link", $context))) ? (PhpMyAdmin\Util::showMySQLDocu(($context["link"] ?? null))) : ("")); + echo " +

      +"; + } + + public function getTemplateName() + { + return "server/sub_page_header.twig"; + } + + public function isTraitable() + { + return false; + } + + public function getDebugInfo() + { + return array ( 54 => 47, 49 => 46, 43 => 44, 37 => 42, 35 => 41, 32 => 40, 30 => 2,); + } + + /** @deprecated since 1.27 (to be removed in 2.0). Use getSourceContext() instead */ + public function getSource() + { + @trigger_error('The '.__METHOD__.' method is deprecated since version 1.27 and will be removed in 2.0. Use getSourceContext() instead.', E_USER_DEPRECATED); + + return $this->getSourceContext()->getCode(); + } + + public function getSourceContext() + { + return new Source("", "server/sub_page_header.twig", "/var/www/html/templates/server/sub_page_header.twig"); + } +} diff --git a/php/apps/phpmyadmin49/html/tmp/twig/3e/3ebbc971646af72a6942ff9c486565ed541f1571c289a6d41025167e1f99397f.php b/php/apps/phpmyadmin49/html/tmp/twig/3e/3ebbc971646af72a6942ff9c486565ed541f1571c289a6d41025167e1f99397f.php new file mode 100644 index 00000000..20ec57a8 --- /dev/null +++ b/php/apps/phpmyadmin49/html/tmp/twig/3e/3ebbc971646af72a6942ff9c486565ed541f1571c289a6d41025167e1f99397f.php @@ -0,0 +1,111 @@ +parent = false; + + $this->blocks = [ + ]; + } + + protected function doDisplay(array $context, array $blocks = []) + { + // line 1 + echo "env, ($context["id"] ?? null), "html", null, true); + echo "\""; + } + // line 2 + if ( !twig_test_empty(($context["class"] ?? null))) { + echo " class=\""; + echo twig_escape_filter($this->env, ($context["class"] ?? null), "html", null, true); + echo "\""; + } + echo "> + + "; + // line 4 + if ( !twig_test_empty(($context["items"] ?? null))) { + // line 5 + echo " "; + $context['_parent'] = $context; + $context['_seq'] = twig_ensure_traversable(($context["items"] ?? null)); + foreach ($context['_seq'] as $context["_key"] => $context["item"]) { + // line 6 + echo " "; + if ( !twig_test_iterable($context["item"])) { + // line 7 + echo " "; + $context["item"] = ["content" => $context["item"]]; + // line 8 + echo " "; + } + // line 9 + echo " "; + $this->loadTemplate("list/item.twig", "list/unordered.twig", 9)->display(twig_to_array($context["item"])); + // line 10 + echo " "; + } + $_parent = $context['_parent']; + unset($context['_seq'], $context['_iterated'], $context['_key'], $context['item'], $context['_parent'], $context['loop']); + $context = array_intersect_key($context, $_parent) + $_parent; + // line 11 + echo " "; + } elseif ( !twig_test_empty(($context["content"] ?? null))) { + // line 12 + echo " "; + echo ($context["content"] ?? null); + echo " + "; + } + // line 14 + echo " +"; + } + + public function getTemplateName() + { + return "list/unordered.twig"; + } + + public function isTraitable() + { + return false; + } + + public function getDebugInfo() + { + return array ( 80 => 14, 74 => 12, 71 => 11, 65 => 10, 62 => 9, 59 => 8, 56 => 7, 53 => 6, 48 => 5, 46 => 4, 37 => 2, 30 => 1,); + } + + /** @deprecated since 1.27 (to be removed in 2.0). Use getSourceContext() instead */ + public function getSource() + { + @trigger_error('The '.__METHOD__.' method is deprecated since version 1.27 and will be removed in 2.0. Use getSourceContext() instead.', E_USER_DEPRECATED); + + return $this->getSourceContext()->getCode(); + } + + public function getSourceContext() + { + return new Source("", "list/unordered.twig", "/var/www/html/templates/list/unordered.twig"); + } +} diff --git a/php/apps/phpmyadmin49/html/tmp/twig/40/405ff29197f052f6f0add29e52ae0f2c59b0b4a4c5841ff267e7281f683e7c6b.php b/php/apps/phpmyadmin49/html/tmp/twig/40/405ff29197f052f6f0add29e52ae0f2c59b0b4a4c5841ff267e7281f683e7c6b.php new file mode 100644 index 00000000..3675aff8 --- /dev/null +++ b/php/apps/phpmyadmin49/html/tmp/twig/40/405ff29197f052f6f0add29e52ae0f2c59b0b4a4c5841ff267e7281f683e7c6b.php @@ -0,0 +1,174 @@ +parent = false; + + $this->blocks = [ + ]; + } + + protected function doDisplay(array $context, array $blocks = []) + { + // line 1 + echo "
      + "; + // line 2 + echo PhpMyAdmin\Url::getHiddenInputs(["db" => // line 3 +($context["db"] ?? null), "table" => // line 4 +($context["table"] ?? null), "sql_query" => // line 5 +($context["sql_query"] ?? null), "goto" => // line 6 +($context["goto"] ?? null), "display_options_form" => 1]); + // line 8 + echo " + + "; + // line 10 + echo PhpMyAdmin\Util::getDivForSliderEffect("", _gettext("Options")); + echo " +
      +
      + "; + // line 14 + echo " "; + echo PhpMyAdmin\Util::getRadioFields("pftext", ["P" => _gettext("Partial texts"), "F" => _gettext("Full texts")], // line 20 +($context["pftext"] ?? null), true, true, "", ("pftext_" . // line 24 +($context["unique_id"] ?? null))); + // line 25 + echo " +
      + + "; + // line 28 + if ((($context["relwork"] ?? null) && ($context["displaywork"] ?? null))) { + // line 29 + echo "
      + "; + // line 30 + echo PhpMyAdmin\Util::getRadioFields("relational_display", ["K" => _gettext("Relational key"), "D" => _gettext("Display column for relationships")], // line 36 +($context["relational_display"] ?? null), true, true, "", ("relational_display_" . // line 40 +($context["unique_id"] ?? null))); + // line 41 + echo " +
      + "; + } + // line 44 + echo " +
      + "; + // line 46 + $this->loadTemplate("checkbox.twig", "display/results/options_block.twig", 46)->display(twig_to_array(["html_field_name" => "display_binary", "label" => _gettext("Show binary contents"), "checked" => !twig_test_empty( // line 49 +($context["display_binary"] ?? null)), "onclick" => false, "html_field_id" => ("display_binary_" . // line 51 +($context["unique_id"] ?? null))])); + // line 53 + echo " "; + $this->loadTemplate("checkbox.twig", "display/results/options_block.twig", 53)->display(twig_to_array(["html_field_name" => "display_blob", "label" => _gettext("Show BLOB contents"), "checked" => !twig_test_empty( // line 56 +($context["display_blob"] ?? null)), "onclick" => false, "html_field_id" => ("display_blob_" . // line 58 +($context["unique_id"] ?? null))])); + // line 60 + echo "
      + + "; + // line 66 + echo "
      + "; + // line 67 + $this->loadTemplate("checkbox.twig", "display/results/options_block.twig", 67)->display(twig_to_array(["html_field_name" => "hide_transformation", "label" => _gettext("Hide browser transformation"), "checked" => !twig_test_empty( // line 70 +($context["hide_transformation"] ?? null)), "onclick" => false, "html_field_id" => ("hide_transformation_" . // line 72 +($context["unique_id"] ?? null))])); + // line 74 + echo "
      + + + "; + // line 77 + if (($context["possible_as_geometry"] ?? null)) { + // line 78 + echo "
      + "; + // line 79 + echo PhpMyAdmin\Util::getRadioFields("geoOption", ["GEOM" => _gettext("Geometry"), "WKT" => _gettext("Well Known Text"), "WKB" => _gettext("Well Known Binary")], // line 86 +($context["geo_option"] ?? null), true, true, "", ("geoOption_" . // line 90 +($context["unique_id"] ?? null))); + // line 91 + echo " +
      + "; + } else { + // line 94 + echo "
      + "; + // line 95 + echo twig_escape_filter($this->env, ($context["possible_as_geometry"] ?? null), "html", null, true); + echo " + "; + // line 96 + echo PhpMyAdmin\Util::getRadioFields("geoOption", ["WKT" => _gettext("Well Known Text"), "WKB" => _gettext("Well Known Binary")], // line 102 +($context["geo_option"] ?? null), true, true, "", ("geoOption_" . // line 106 +($context["unique_id"] ?? null))); + // line 107 + echo " +
      + "; + } + // line 110 + echo "
      +
      + +
      + +
      + "; + // line 117 + echo " +"; + } + + public function getTemplateName() + { + return "display/results/options_block.twig"; + } + + public function isTraitable() + { + return false; + } + + public function getDebugInfo() + { + return array ( 143 => 117, 138 => 114, 132 => 110, 127 => 107, 125 => 106, 124 => 102, 123 => 96, 119 => 95, 116 => 94, 111 => 91, 109 => 90, 108 => 86, 107 => 79, 104 => 78, 102 => 77, 97 => 74, 95 => 72, 94 => 70, 93 => 67, 90 => 66, 86 => 60, 84 => 58, 83 => 56, 81 => 53, 79 => 51, 78 => 49, 77 => 46, 73 => 44, 68 => 41, 66 => 40, 65 => 36, 64 => 30, 61 => 29, 59 => 28, 54 => 25, 52 => 24, 51 => 20, 49 => 14, 43 => 10, 39 => 8, 37 => 6, 36 => 5, 35 => 4, 34 => 3, 33 => 2, 30 => 1,); + } + + /** @deprecated since 1.27 (to be removed in 2.0). Use getSourceContext() instead */ + public function getSource() + { + @trigger_error('The '.__METHOD__.' method is deprecated since version 1.27 and will be removed in 2.0. Use getSourceContext() instead.', E_USER_DEPRECATED); + + return $this->getSourceContext()->getCode(); + } + + public function getSourceContext() + { + return new Source("", "display/results/options_block.twig", "/var/www/html/templates/display/results/options_block.twig"); + } +} diff --git a/php/apps/phpmyadmin49/html/tmp/twig/41/4178cd9070ac9a64ab3b0d79c97ca23f41ce66c6f663399f665d1da8bfb34b9f.php b/php/apps/phpmyadmin49/html/tmp/twig/41/4178cd9070ac9a64ab3b0d79c97ca23f41ce66c6f663399f665d1da8bfb34b9f.php new file mode 100644 index 00000000..393c9f21 --- /dev/null +++ b/php/apps/phpmyadmin49/html/tmp/twig/41/4178cd9070ac9a64ab3b0d79c97ca23f41ce66c6f663399f665d1da8bfb34b9f.php @@ -0,0 +1,89 @@ +parent = false; + + $this->blocks = [ + ]; + } + + protected function doDisplay(array $context, array $blocks = []) + { + // line 1 + if (($context["display_logo"] ?? null)) { + // line 2 + echo " +"; + } + } + + public function getTemplateName() + { + return "navigation/logo.twig"; + } + + public function isTraitable() + { + return false; + } + + public function getDebugInfo() + { + return array ( 57 => 11, 53 => 9, 51 => 8, 46 => 7, 41 => 5, 37 => 4, 35 => 3, 32 => 2, 30 => 1,); + } + + /** @deprecated since 1.27 (to be removed in 2.0). Use getSourceContext() instead */ + public function getSource() + { + @trigger_error('The '.__METHOD__.' method is deprecated since version 1.27 and will be removed in 2.0. Use getSourceContext() instead.', E_USER_DEPRECATED); + + return $this->getSourceContext()->getCode(); + } + + public function getSourceContext() + { + return new Source("", "navigation/logo.twig", "/var/www/html/templates/navigation/logo.twig"); + } +} diff --git a/php/apps/phpmyadmin49/html/tmp/twig/42/427a94c1df74a957631100318dbacaa728ee046ead261c9ce4923cff09513a1b.php b/php/apps/phpmyadmin49/html/tmp/twig/42/427a94c1df74a957631100318dbacaa728ee046ead261c9ce4923cff09513a1b.php new file mode 100644 index 00000000..bc7d5d60 --- /dev/null +++ b/php/apps/phpmyadmin49/html/tmp/twig/42/427a94c1df74a957631100318dbacaa728ee046ead261c9ce4923cff09513a1b.php @@ -0,0 +1,100 @@ +parent = false; + + $this->blocks = [ + ]; + } + + protected function doDisplay(array $context, array $blocks = []) + { + // line 1 + echo "
      env, ($context["parent_div_classes"] ?? null), "html", null, true); + echo "\"> + "; + // line 2 + $context['_parent'] = $context; + $context['_seq'] = twig_ensure_traversable(($context["content_array"] ?? null)); + foreach ($context['_seq'] as $context["_key"] => $context["content"]) { + // line 3 + echo " "; + if ((isset($context["content"]) || array_key_exists("content", $context))) { + // line 4 + echo " env, $this->getAttribute($context["content"], 0, [], "array"), "html", null, true); + echo "\"> + "; + // line 5 + echo twig_escape_filter($this->env, $this->getAttribute($context["content"], 1, [], "array"), "html", null, true); + echo " + "; + // line 6 + if ($this->getAttribute($context["content"], "extraSpan", [], "array", true, true)) { + // line 7 + echo " : "; + echo twig_escape_filter($this->env, $this->getAttribute($context["content"], "extraSpan", [], "array"), "html", null, true); + echo " + "; + } + // line 9 + echo " + "; + } + // line 11 + echo " "; + } + $_parent = $context['_parent']; + unset($context['_seq'], $context['_iterated'], $context['_key'], $context['content'], $context['_parent'], $context['loop']); + $context = array_intersect_key($context, $_parent) + $_parent; + // line 12 + echo "
      +"; + } + + public function getTemplateName() + { + return "console/query_action.twig"; + } + + public function isTraitable() + { + return false; + } + + public function getDebugInfo() + { + return array ( 69 => 12, 63 => 11, 59 => 9, 53 => 7, 51 => 6, 47 => 5, 42 => 4, 39 => 3, 35 => 2, 30 => 1,); + } + + /** @deprecated since 1.27 (to be removed in 2.0). Use getSourceContext() instead */ + public function getSource() + { + @trigger_error('The '.__METHOD__.' method is deprecated since version 1.27 and will be removed in 2.0. Use getSourceContext() instead.', E_USER_DEPRECATED); + + return $this->getSourceContext()->getCode(); + } + + public function getSourceContext() + { + return new Source("", "console/query_action.twig", "/var/www/html/templates/console/query_action.twig"); + } +} diff --git a/php/apps/phpmyadmin49/html/tmp/twig/43/4356086eca88968ff47a5fedfb91bb77e8b29455fcf2eb179dcaae3448deb4e9.php b/php/apps/phpmyadmin49/html/tmp/twig/43/4356086eca88968ff47a5fedfb91bb77e8b29455fcf2eb179dcaae3448deb4e9.php new file mode 100644 index 00000000..53a289be --- /dev/null +++ b/php/apps/phpmyadmin49/html/tmp/twig/43/4356086eca88968ff47a5fedfb91bb77e8b29455fcf2eb179dcaae3448deb4e9.php @@ -0,0 +1,95 @@ +parent = false; + + $this->blocks = [ + ]; + } + + protected function doDisplay(array $context, array $blocks = []) + { + // line 1 + if ( !twig_test_empty(($context["class"] ?? null))) { + // line 2 + echo "
      env, ($context["class"] ?? null), "html", null, true); + echo "\"> +"; + } + // line 4 + echo "env, ($context["html_field_name"] ?? null), "html", null, true); + echo "\" id=\""; + echo ($context["html_field_id"] ?? null); + echo "\" value=\""; + echo twig_escape_filter($this->env, ($context["choice_value"] ?? null), "html", null, true); + echo "\""; + echo ((($context["checked"] ?? null)) ? (" checked=\"checked\"") : ("")); + echo " /> + +"; + // line 6 + if (($context["is_line_break"] ?? null)) { + // line 7 + echo "
      +"; + } + // line 9 + if ( !twig_test_empty(($context["class"] ?? null))) { + // line 10 + echo "
      +"; + } + } + + public function getTemplateName() + { + return "radio_fields.twig"; + } + + public function isTraitable() + { + return false; + } + + public function getDebugInfo() + { + return array ( 63 => 10, 61 => 9, 57 => 7, 55 => 6, 49 => 5, 38 => 4, 32 => 2, 30 => 1,); + } + + /** @deprecated since 1.27 (to be removed in 2.0). Use getSourceContext() instead */ + public function getSource() + { + @trigger_error('The '.__METHOD__.' method is deprecated since version 1.27 and will be removed in 2.0. Use getSourceContext() instead.', E_USER_DEPRECATED); + + return $this->getSourceContext()->getCode(); + } + + public function getSourceContext() + { + return new Source("", "radio_fields.twig", "/var/www/html/templates/radio_fields.twig"); + } +} diff --git a/php/apps/phpmyadmin49/html/tmp/twig/4a/4a0032fdd7708c77327d12ce16d3d1ecc04d875b3e714d8fb91b2376b4d9d34b.php b/php/apps/phpmyadmin49/html/tmp/twig/4a/4a0032fdd7708c77327d12ce16d3d1ecc04d875b3e714d8fb91b2376b4d9d34b.php new file mode 100644 index 00000000..b37c65c9 --- /dev/null +++ b/php/apps/phpmyadmin49/html/tmp/twig/4a/4a0032fdd7708c77327d12ce16d3d1ecc04d875b3e714d8fb91b2376b4d9d34b.php @@ -0,0 +1,98 @@ +parent = false; + + $this->blocks = [ + ]; + } + + protected function doDisplay(array $context, array $blocks = []) + { + // line 2 + $this->loadTemplate("server/sub_page_header.twig", "server/databases/index.twig", 2)->display(twig_to_array(["type" => (( // line 3 +($context["dbstats"] ?? null)) ? ("database_statistics") : ("databases"))])); + // line 5 + echo " +"; + // line 7 + if (($context["show_create_db"] ?? null)) { + // line 8 + echo " "; + $this->loadTemplate("server/databases/create.twig", "server/databases/index.twig", 8)->display(twig_to_array(["is_create_db_priv" => // line 9 +($context["is_create_db_priv"] ?? null), "dbstats" => // line 10 +($context["dbstats"] ?? null), "db_to_create" => // line 11 +($context["db_to_create"] ?? null), "server_collation" => // line 12 +($context["server_collation"] ?? null), "dbi" => // line 13 +($context["dbi"] ?? null), "disable_is" => // line 14 +($context["disable_is"] ?? null)])); + } + // line 17 + echo " +"; + // line 18 + $this->loadTemplate("filter.twig", "server/databases/index.twig", 18)->display(twig_to_array(["filter_value" => ""])); + // line 19 + echo " +"; + // line 21 + if ( !(null === ($context["databases"] ?? null))) { + // line 22 + echo " "; + echo ($context["databases"] ?? null); + echo " +"; + } else { + // line 24 + echo "

      "; + echo _gettext("No databases"); + echo "

      +"; + } + } + + public function getTemplateName() + { + return "server/databases/index.twig"; + } + + public function isTraitable() + { + return false; + } + + public function getDebugInfo() + { + return array ( 64 => 24, 58 => 22, 56 => 21, 53 => 19, 51 => 18, 48 => 17, 45 => 14, 44 => 13, 43 => 12, 42 => 11, 41 => 10, 40 => 9, 38 => 8, 36 => 7, 33 => 5, 31 => 3, 30 => 2,); + } + + /** @deprecated since 1.27 (to be removed in 2.0). Use getSourceContext() instead */ + public function getSource() + { + @trigger_error('The '.__METHOD__.' method is deprecated since version 1.27 and will be removed in 2.0. Use getSourceContext() instead.', E_USER_DEPRECATED); + + return $this->getSourceContext()->getCode(); + } + + public function getSourceContext() + { + return new Source("", "server/databases/index.twig", "/var/www/html/templates/server/databases/index.twig"); + } +} diff --git a/php/apps/phpmyadmin49/html/tmp/twig/4f/4f6fc30198c168923bb8cadc16f4ee4ef1d9c18c3615406217cb66c3c2252b2c.php b/php/apps/phpmyadmin49/html/tmp/twig/4f/4f6fc30198c168923bb8cadc16f4ee4ef1d9c18c3615406217cb66c3c2252b2c.php new file mode 100644 index 00000000..9b97e9a9 --- /dev/null +++ b/php/apps/phpmyadmin49/html/tmp/twig/4f/4f6fc30198c168923bb8cadc16f4ee4ef1d9c18c3615406217cb66c3c2252b2c.php @@ -0,0 +1,88 @@ +parent = false; + + $this->blocks = [ + ]; + } + + protected function doDisplay(array $context, array $blocks = []) + { + // line 1 + echo "
      +env, PhpMyAdmin\Core::linkURL("https://www.phpmyadmin.net/"), "html", null, true); + echo "\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"logo\"> +env, $this->getAttribute(($context["theme"] ?? null), "getImgPath", [0 => "logo_right.png", 1 => "pma_logo.png"], "method"), "html", null, true); + echo "\" id=\"imLogo\" name=\"imLogo\" alt=\"phpMyAdmin\" border=\"0\" /> + +

      "; + // line 5 + echo sprintf(_gettext("Welcome to %s"), "phpMyAdmin"); + echo "

      + + + +
      +"; + // line 12 + echo call_user_func_array($this->env->getFunction('Message_error')->getCallable(), [_gettext("There is mismatch between HTTPS indicated on the server and client. This can lead to non working phpMyAdmin or a security risk. Please fix your server configuration to indicate HTTPS properly.")]); + echo " +
      +"; + } + + public function getTemplateName() + { + return "login/header.twig"; + } + + public function isTraitable() + { + return false; + } + + public function getDebugInfo() + { + return array ( 55 => 12, 48 => 8, 42 => 5, 37 => 3, 33 => 2, 30 => 1,); + } + + /** @deprecated since 1.27 (to be removed in 2.0). Use getSourceContext() instead */ + public function getSource() + { + @trigger_error('The '.__METHOD__.' method is deprecated since version 1.27 and will be removed in 2.0. Use getSourceContext() instead.', E_USER_DEPRECATED); + + return $this->getSourceContext()->getCode(); + } + + public function getSourceContext() + { + return new Source("", "login/header.twig", "/var/www/html/templates/login/header.twig"); + } +} diff --git a/php/apps/phpmyadmin49/html/tmp/twig/51/518632ba7de4d6508ab4444aae88c285256ff2841828b18c9b952469647462b9.php b/php/apps/phpmyadmin49/html/tmp/twig/51/518632ba7de4d6508ab4444aae88c285256ff2841828b18c9b952469647462b9.php new file mode 100644 index 00000000..d3d0ece2 --- /dev/null +++ b/php/apps/phpmyadmin49/html/tmp/twig/51/518632ba7de4d6508ab4444aae88c285256ff2841828b18c9b952469647462b9.php @@ -0,0 +1,90 @@ +parent = false; + + $this->blocks = [ + ]; + } + + protected function doDisplay(array $context, array $blocks = []) + { + // line 1 + $context["colspan"] = 2; + // line 2 + if (($context["is_setup"] ?? null)) { + // line 3 + echo " "; + $context["colspan"] = (($context["colspan"] ?? null) + 1); + } + // line 5 + if (($context["show_buttons"] ?? null)) { + // line 6 + echo "
      + + +"; + } + // line 13 + echo "
      env, ($context["colspan"] ?? null), "html", null, true); + echo "\" class=\"lastrow\"> + + +
      + +"; + } + + public function getTemplateName() + { + return "config/form_display/fieldset_bottom.twig"; + } + + public function isTraitable() + { + return false; + } + + public function getDebugInfo() + { + return array ( 58 => 13, 51 => 9, 47 => 8, 43 => 7, 40 => 6, 38 => 5, 34 => 3, 32 => 2, 30 => 1,); + } + + /** @deprecated since 1.27 (to be removed in 2.0). Use getSourceContext() instead */ + public function getSource() + { + @trigger_error('The '.__METHOD__.' method is deprecated since version 1.27 and will be removed in 2.0. Use getSourceContext() instead.', E_USER_DEPRECATED); + + return $this->getSourceContext()->getCode(); + } + + public function getSourceContext() + { + return new Source("", "config/form_display/fieldset_bottom.twig", "/var/www/html/templates/config/form_display/fieldset_bottom.twig"); + } +} diff --git a/php/apps/phpmyadmin49/html/tmp/twig/52/525fca17dcdf46d78569eca0bc231e68d32dd5264048d1d9beefa08fb4d1a0e8.php b/php/apps/phpmyadmin49/html/tmp/twig/52/525fca17dcdf46d78569eca0bc231e68d32dd5264048d1d9beefa08fb4d1a0e8.php new file mode 100644 index 00000000..30923443 --- /dev/null +++ b/php/apps/phpmyadmin49/html/tmp/twig/52/525fca17dcdf46d78569eca0bc231e68d32dd5264048d1d9beefa08fb4d1a0e8.php @@ -0,0 +1,79 @@ +parent = false; + + $this->blocks = [ + ]; + } + + protected function doDisplay(array $context, array $blocks = []) + { + // line 1 + echo " + "; + // line 2 + echo PhpMyAdmin\Util::getIcon("b_save", _gettext("Save")); + echo " + + + "; + // line 5 + echo PhpMyAdmin\Util::getIcon("b_close", _gettext("Cancel")); + echo " + +"; + // line 7 + echo PhpMyAdmin\Util::getImage("b_help", _gettext("Documentation"), ["class" => "hide", "id" => "docImage"]); + // line 10 + echo " +"; + } + + public function getTemplateName() + { + return "server/variables/link_template.twig"; + } + + public function isTraitable() + { + return false; + } + + public function getDebugInfo() + { + return array ( 48 => 10, 46 => 7, 41 => 5, 35 => 2, 30 => 1,); + } + + /** @deprecated since 1.27 (to be removed in 2.0). Use getSourceContext() instead */ + public function getSource() + { + @trigger_error('The '.__METHOD__.' method is deprecated since version 1.27 and will be removed in 2.0. Use getSourceContext() instead.', E_USER_DEPRECATED); + + return $this->getSourceContext()->getCode(); + } + + public function getSourceContext() + { + return new Source("", "server/variables/link_template.twig", "/var/www/html/templates/server/variables/link_template.twig"); + } +} diff --git a/php/apps/phpmyadmin49/html/tmp/twig/53/534b5714cf1a768097d1864db37f1951cc7fd2244070d0d2f079676f746521c6.php b/php/apps/phpmyadmin49/html/tmp/twig/53/534b5714cf1a768097d1864db37f1951cc7fd2244070d0d2f079676f746521c6.php new file mode 100644 index 00000000..ce5dad6d --- /dev/null +++ b/php/apps/phpmyadmin49/html/tmp/twig/53/534b5714cf1a768097d1864db37f1951cc7fd2244070d0d2f079676f746521c6.php @@ -0,0 +1,101 @@ +parent = false; + + $this->blocks = [ + ]; + } + + protected function doDisplay(array $context, array $blocks = []) + { + // line 1 + echo "
      +
      + + "; + // line 4 + if (PhpMyAdmin\Util::showIcons("ActionLinksMode")) { + // line 5 + echo PhpMyAdmin\Util::getImage("b_table_add"); + } + // line 7 + echo " "; + echo _gettext("Create table"); + // line 8 + echo " + "; + // line 9 + echo PhpMyAdmin\Url::getHiddenInputs(($context["db"] ?? null)); + echo " +
      + "; + // line 11 + echo _gettext("Name"); + echo ": + +
      +
      + "; + // line 15 + echo _gettext("Number of columns"); + echo ": + +
      +
      +
      +
      + +
      +
      +"; + } + + public function getTemplateName() + { + return "database/create_table.twig"; + } + + public function isTraitable() + { + return false; + } + + public function getDebugInfo() + { + return array ( 67 => 21, 58 => 15, 51 => 11, 46 => 9, 43 => 8, 40 => 7, 37 => 5, 35 => 4, 30 => 1,); + } + + /** @deprecated since 1.27 (to be removed in 2.0). Use getSourceContext() instead */ + public function getSource() + { + @trigger_error('The '.__METHOD__.' method is deprecated since version 1.27 and will be removed in 2.0. Use getSourceContext() instead.', E_USER_DEPRECATED); + + return $this->getSourceContext()->getCode(); + } + + public function getSourceContext() + { + return new Source("", "database/create_table.twig", "/var/www/html/templates/database/create_table.twig"); + } +} diff --git a/php/apps/phpmyadmin49/html/tmp/twig/61/61d73f03fcaf8023c1e9acca50180acb3feff8329a449db947a786bc44e5f32c.php b/php/apps/phpmyadmin49/html/tmp/twig/61/61d73f03fcaf8023c1e9acca50180acb3feff8329a449db947a786bc44e5f32c.php new file mode 100644 index 00000000..ca920dc0 --- /dev/null +++ b/php/apps/phpmyadmin49/html/tmp/twig/61/61d73f03fcaf8023c1e9acca50180acb3feff8329a449db947a786bc44e5f32c.php @@ -0,0 +1,73 @@ +parent = false; + + $this->blocks = [ + ]; + } + + protected function doDisplay(array $context, array $blocks = []) + { + // line 1 + echo "env, ($context["group"] ?? null), "html", null, true); + echo "\"> + env, ($context["colspan"] ?? null), "html", null, true); + echo "\"> + "; + // line 3 + echo twig_escape_filter($this->env, ($context["header_text"] ?? null), "html", null, true); + echo " + + +"; + } + + public function getTemplateName() + { + return "config/form_display/group_header.twig"; + } + + public function isTraitable() + { + return false; + } + + public function getDebugInfo() + { + return array ( 39 => 3, 35 => 2, 30 => 1,); + } + + /** @deprecated since 1.27 (to be removed in 2.0). Use getSourceContext() instead */ + public function getSource() + { + @trigger_error('The '.__METHOD__.' method is deprecated since version 1.27 and will be removed in 2.0. Use getSourceContext() instead.', E_USER_DEPRECATED); + + return $this->getSourceContext()->getCode(); + } + + public function getSourceContext() + { + return new Source("", "config/form_display/group_header.twig", "/var/www/html/templates/config/form_display/group_header.twig"); + } +} diff --git a/php/apps/phpmyadmin49/html/tmp/twig/63/636911557a5dfe3a9afd89271e569baecce360fbb02bf00be26fdff3d757a3cf.php b/php/apps/phpmyadmin49/html/tmp/twig/63/636911557a5dfe3a9afd89271e569baecce360fbb02bf00be26fdff3d757a3cf.php new file mode 100644 index 00000000..1128f7ca --- /dev/null +++ b/php/apps/phpmyadmin49/html/tmp/twig/63/636911557a5dfe3a9afd89271e569baecce360fbb02bf00be26fdff3d757a3cf.php @@ -0,0 +1,94 @@ +parent = false; + + $this->blocks = [ + ]; + } + + protected function doDisplay(array $context, array $blocks = []) + { + // line 1 + echo "
      + "; + // line 2 + echo PhpMyAdmin\Util::getListNavigator( // line 3 +($context["database_count"] ?? null), // line 4 +($context["pos"] ?? null), // line 5 +($context["url_params"] ?? null), "server_databases.php", "frame_content", // line 8 +($context["max_db_list"] ?? null)); + // line 9 + echo " +
      + "; + // line 11 + echo PhpMyAdmin\Url::getHiddenInputs(($context["url_params"] ?? null)); + echo " + "; + // line 12 + $context["url_params"] = twig_array_merge(($context["url_params"] ?? null), ["sort_by" => "SCHEMA_NAME", "sort_order" => (((( // line 14 +($context["sort_by"] ?? null) == "SCHEMA_NAME") && (($context["sort_order"] ?? null) == "asc"))) ? ("desc") : ("asc"))]); + // line 16 + echo "
      + + "; + // line 18 + $this->loadTemplate("server/databases/table_header.twig", "server/databases/databases_header.twig", 18)->display(twig_to_array(["url_params" => // line 19 +($context["url_params"] ?? null), "sort_by" => // line 20 +($context["sort_by"] ?? null), "sort_order" => // line 21 +($context["sort_order"] ?? null), "sort_order_text" => ((( // line 22 +($context["sort_order"] ?? null) == "asc")) ? (_gettext("Ascending")) : (_gettext("Descending"))), "column_order" => // line 23 +($context["column_order"] ?? null), "first_database" => // line 24 +($context["first_database"] ?? null), "master_replication" => // line 25 +($context["master_replication"] ?? null), "slave_replication" => // line 26 +($context["slave_replication"] ?? null), "is_superuser" => // line 27 +($context["is_superuser"] ?? null), "allow_user_drop_database" => // line 28 +($context["allow_user_drop_database"] ?? null)])); + } + + public function getTemplateName() + { + return "server/databases/databases_header.twig"; + } + + public function isTraitable() + { + return false; + } + + public function getDebugInfo() + { + return array ( 64 => 28, 63 => 27, 62 => 26, 61 => 25, 60 => 24, 59 => 23, 58 => 22, 57 => 21, 56 => 20, 55 => 19, 54 => 18, 50 => 16, 48 => 14, 47 => 12, 43 => 11, 39 => 9, 37 => 8, 36 => 5, 35 => 4, 34 => 3, 33 => 2, 30 => 1,); + } + + /** @deprecated since 1.27 (to be removed in 2.0). Use getSourceContext() instead */ + public function getSource() + { + @trigger_error('The '.__METHOD__.' method is deprecated since version 1.27 and will be removed in 2.0. Use getSourceContext() instead.', E_USER_DEPRECATED); + + return $this->getSourceContext()->getCode(); + } + + public function getSourceContext() + { + return new Source("", "server/databases/databases_header.twig", "/var/www/html/templates/server/databases/databases_header.twig"); + } +} diff --git a/php/apps/phpmyadmin49/html/tmp/twig/64/6470998d61dab2727603865fd9a0dc407df8eff317545eb3960a5a31e7dabc55.php b/php/apps/phpmyadmin49/html/tmp/twig/64/6470998d61dab2727603865fd9a0dc407df8eff317545eb3960a5a31e7dabc55.php new file mode 100644 index 00000000..d1d7d782 --- /dev/null +++ b/php/apps/phpmyadmin49/html/tmp/twig/64/6470998d61dab2727603865fd9a0dc407df8eff317545eb3960a5a31e7dabc55.php @@ -0,0 +1,155 @@ +parent = false; + + $this->blocks = [ + ]; + } + + protected function doDisplay(array $context, array $blocks = []) + { + // line 1 + echo "
        +
      • + "; + // line 3 + if (($context["is_create_db_priv"] ?? null)) { + // line 4 + echo " +

        + + "; + // line 10 + echo PhpMyAdmin\Util::showMySQLDocu("CREATE_DATABASE"); + echo " +

        + + "; + // line 13 + echo PhpMyAdmin\Url::getHiddenInputs("", "", 5); + echo " + + "; + // line 15 + if ( !twig_test_empty(($context["dbstats"] ?? null))) { + // line 16 + echo " + "; + } + // line 18 + echo " + env, ($context["db_to_create"] ?? null), "html", null, true); + echo "\" + maxlength=\"64\" class=\"textfield\" id=\"text_create_db\" required + placeholder=\""; + // line 21 + echo _gettext("Database name"); + echo "\" /> + "; + // line 22 + echo PhpMyAdmin\Charsets::getCollationDropdownBox( // line 23 +($context["dbi"] ?? null), // line 24 +($context["disable_is"] ?? null), "db_collation", null, // line 27 +($context["server_collation"] ?? null), true); + // line 29 + echo " + + + "; + } else { + // line 33 + echo " "; + // line 34 + echo "

        + "; + // line 35 + echo PhpMyAdmin\Util::getImage("b_newdb"); + echo " + "; + // line 36 + echo _gettext("Create database"); + // line 37 + echo " "; + echo PhpMyAdmin\Util::showMySQLDocu("CREATE_DATABASE"); + echo " +

        + + + "; + // line 41 + echo PhpMyAdmin\Util::getImage("s_error", "", ["hspace" => 2, "border" => 0, "align" => "middle"]); + // line 45 + echo " + "; + // line 46 + echo _gettext("No Privileges"); + // line 47 + echo " + "; + } + // line 49 + echo "
      • +
      +"; + } + + public function getTemplateName() + { + return "server/databases/create.twig"; + } + + public function isTraitable() + { + return false; + } + + public function getDebugInfo() + { + return array ( 123 => 49, 119 => 47, 117 => 46, 114 => 45, 112 => 41, 104 => 37, 102 => 36, 98 => 35, 95 => 34, 93 => 33, 87 => 30, 84 => 29, 82 => 27, 81 => 24, 80 => 23, 79 => 22, 75 => 21, 70 => 19, 67 => 18, 63 => 16, 61 => 15, 56 => 13, 50 => 10, 47 => 9, 45 => 8, 41 => 7, 36 => 4, 34 => 3, 30 => 1,); + } + + /** @deprecated since 1.27 (to be removed in 2.0). Use getSourceContext() instead */ + public function getSource() + { + @trigger_error('The '.__METHOD__.' method is deprecated since version 1.27 and will be removed in 2.0. Use getSourceContext() instead.', E_USER_DEPRECATED); + + return $this->getSourceContext()->getCode(); + } + + public function getSourceContext() + { + return new Source("", "server/databases/create.twig", "/var/www/html/templates/server/databases/create.twig"); + } +} diff --git a/php/apps/phpmyadmin49/html/tmp/twig/67/67df5b157f80ea76a0872677250d740b8389403d71dd909bce4e1fcf08c90342.php b/php/apps/phpmyadmin49/html/tmp/twig/67/67df5b157f80ea76a0872677250d740b8389403d71dd909bce4e1fcf08c90342.php new file mode 100644 index 00000000..a0dd5328 --- /dev/null +++ b/php/apps/phpmyadmin49/html/tmp/twig/67/67df5b157f80ea76a0872677250d740b8389403d71dd909bce4e1fcf08c90342.php @@ -0,0 +1,56 @@ +parent = false; + + $this->blocks = [ + ]; + } + + protected function doDisplay(array $context, array $blocks = []) + { + // line 1 + echo " +"; + } + + public function getTemplateName() + { + return "login/footer.twig"; + } + + public function getDebugInfo() + { + return array ( 30 => 1,); + } + + /** @deprecated since 1.27 (to be removed in 2.0). Use getSourceContext() instead */ + public function getSource() + { + @trigger_error('The '.__METHOD__.' method is deprecated since version 1.27 and will be removed in 2.0. Use getSourceContext() instead.', E_USER_DEPRECATED); + + return $this->getSourceContext()->getCode(); + } + + public function getSourceContext() + { + return new Source("", "login/footer.twig", "/var/www/html/templates/login/footer.twig"); + } +} diff --git a/php/apps/phpmyadmin49/html/tmp/twig/7c/7c273103d0603ee23d114722d65370a75b0c07e14b519b501ec183700aeec23f.php b/php/apps/phpmyadmin49/html/tmp/twig/7c/7c273103d0603ee23d114722d65370a75b0c07e14b519b501ec183700aeec23f.php new file mode 100644 index 00000000..ba0c2eff --- /dev/null +++ b/php/apps/phpmyadmin49/html/tmp/twig/7c/7c273103d0603ee23d114722d65370a75b0c07e14b519b501ec183700aeec23f.php @@ -0,0 +1,86 @@ +parent = false; + + $this->blocks = [ + ]; + } + + protected function doDisplay(array $context, array $blocks = []) + { + // line 1 + echo " +env, ($context["goto"] ?? null), "html", null, true); + echo "\" /> +"; + // line 4 + echo "env, ($context["pos"] ?? null), "html", null, true); + echo "\" /> +env, ($context["is_browse_distinct"] ?? null), "html", null, true); + echo "\" /> +"; + // line 6 + echo _gettext("Number of rows:"); + // line 7 + echo PhpMyAdmin\Util::getDropdown("session_max_rows", // line 9 +($context["number_of_rows_choices"] ?? null), // line 10 +($context["max_rows"] ?? null), "", "autosubmit", // line 13 +($context["number_of_rows_placeholder"] ?? null)); + // line 14 + echo " +"; + } + + public function getTemplateName() + { + return "display/results/additional_fields.twig"; + } + + public function isTraitable() + { + return false; + } + + public function getDebugInfo() + { + return array ( 55 => 14, 53 => 13, 52 => 10, 51 => 9, 50 => 7, 48 => 6, 44 => 5, 39 => 4, 35 => 2, 30 => 1,); + } + + /** @deprecated since 1.27 (to be removed in 2.0). Use getSourceContext() instead */ + public function getSource() + { + @trigger_error('The '.__METHOD__.' method is deprecated since version 1.27 and will be removed in 2.0. Use getSourceContext() instead.', E_USER_DEPRECATED); + + return $this->getSourceContext()->getCode(); + } + + public function getSourceContext() + { + return new Source("", "display/results/additional_fields.twig", "/var/www/html/templates/display/results/additional_fields.twig"); + } +} diff --git a/php/apps/phpmyadmin49/html/tmp/twig/81/814d0d7b18e26addd47cf8ee346f369d20c9672d804e440af9e7fdd587412ad0.php b/php/apps/phpmyadmin49/html/tmp/twig/81/814d0d7b18e26addd47cf8ee346f369d20c9672d804e440af9e7fdd587412ad0.php new file mode 100644 index 00000000..a1f77b29 --- /dev/null +++ b/php/apps/phpmyadmin49/html/tmp/twig/81/814d0d7b18e26addd47cf8ee346f369d20c9672d804e440af9e7fdd587412ad0.php @@ -0,0 +1,264 @@ +parent = false; + + $this->blocks = [ + ]; + } + + protected function doDisplay(array $context, array $blocks = []) + { + // line 1 + echo "
      +
      + "; + // line 4 + echo " "; + $this->loadTemplate("console/toolbar.twig", "console/display.twig", 4)->display(twig_to_array(["parent_div_classes" => "collapsed", "content_array" => [0 => [0 => "switch_button console_switch", 1 => _gettext("Console"), "image" => // line 7 +($context["image"] ?? null)], 1 => [0 => "button clear", 1 => _gettext("Clear")], 2 => [0 => "button history", 1 => _gettext("History")], 3 => [0 => "button options", 1 => _gettext("Options")], 4 => (( // line 11 +(isset($context["cfg_bookmark"]) || array_key_exists("cfg_bookmark", $context))) ? ([0 => "button bookmarks", 1 => _gettext("Bookmarks")]) : (null)), 5 => [0 => "button debug hide", 1 => _gettext("Debug SQL")]]])); + // line 15 + echo " "; + // line 16 + echo "
      +
      +
      + + "; + // line 20 + echo _gettext("Press Ctrl+Enter to execute query"); + // line 21 + echo " + + "; + // line 23 + echo _gettext("Press Enter to execute query"); + // line 24 + echo " +
      + "; + // line 26 + if ( !twig_test_empty(($context["sql_history"] ?? null))) { + // line 27 + echo " "; + $context['_parent'] = $context; + $context['_seq'] = twig_ensure_traversable(twig_reverse_filter($this->env, ($context["sql_history"] ?? null))); + foreach ($context['_seq'] as $context["_key"] => $context["record"]) { + // line 28 + echo "
      getAttribute($context["record"], "sqlquery", [], "array"))) ? (" select") : ("")); + echo "\" + targetdb=\""; + // line 30 + echo twig_escape_filter($this->env, $this->getAttribute($context["record"], "db", [], "array"), "html", null, true); + echo "\" targettable=\""; + echo twig_escape_filter($this->env, $this->getAttribute($context["record"], "table", [], "array"), "html", null, true); + echo "\"> + "; + // line 31 + $this->loadTemplate("console/query_action.twig", "console/display.twig", 31)->display(twig_to_array(["parent_div_classes" => "action_content", "content_array" => [0 => [0 => "action collapse", 1 => _gettext("Collapse")], 1 => [0 => "action expand", 1 => _gettext("Expand")], 2 => [0 => "action requery", 1 => _gettext("Requery")], 3 => [0 => "action edit", 1 => _gettext("Edit")], 4 => [0 => "action explain", 1 => _gettext("Explain")], 5 => [0 => "action profiling", 1 => _gettext("Profiling")], 6 => (( // line 40 +(isset($context["cfg_bookmark"]) || array_key_exists("cfg_bookmark", $context))) ? ([0 => "action bookmark", 1 => _gettext("Bookmark")]) : (null)), 7 => [0 => "text failed", 1 => _gettext("Query failed")], 8 => [0 => "text targetdb", 1 => _gettext("Database"), "extraSpan" => $this->getAttribute( // line 42 +$context["record"], "db", [], "array")], 9 => [0 => "text query_time", 1 => _gettext("Queried time"), "extraSpan" => (($this->getAttribute( // line 46 +$context["record"], "timevalue", [], "array", true, true)) ? ($this->getAttribute( // line 47 +$context["record"], "timevalue", [], "array")) : (_gettext("During current session")))]]])); + // line 51 + echo " "; + echo twig_escape_filter($this->env, $this->getAttribute($context["record"], "sqlquery", [], "array"), "html", null, true); + echo " +
      + "; + } + $_parent = $context['_parent']; + unset($context['_seq'], $context['_iterated'], $context['_key'], $context['record'], $context['_parent'], $context['loop']); + $context = array_intersect_key($context, $_parent) + $_parent; + // line 54 + echo " "; + } + // line 55 + echo "
      +
      + +
      +
      + "; + // line 61 + echo "
      + "; + // line 63 + echo "
      + "; + // line 64 + $this->loadTemplate("console/toolbar.twig", "console/display.twig", 64)->display(twig_to_array(["parent_div_classes" => "", "content_array" => [0 => [0 => "button order order_asc", 1 => _gettext("ascending")], 1 => [0 => "button order order_desc", 1 => _gettext("descending")], 2 => [0 => "text", 1 => _gettext("Order:")], 3 => [0 => "switch_button", 1 => _gettext("Debug SQL")], 4 => [0 => "button order_by sort_count", 1 => _gettext("Count")], 5 => [0 => "button order_by sort_exec", 1 => _gettext("Execution order")], 6 => [0 => "button order_by sort_time", 1 => _gettext("Time taken")], 7 => [0 => "text", 1 => _gettext("Order by:")], 8 => [0 => "button group_queries", 1 => _gettext("Group queries")], 9 => [0 => "button ungroup_queries", 1 => _gettext("Ungroup queries")]]])); + // line 79 + echo "
      +
      +
      +
      +
      + "; + // line 84 + $this->loadTemplate("console/query_action.twig", "console/display.twig", 84)->display(twig_to_array(["parent_div_classes" => "debug_query action_content", "content_array" => [0 => [0 => "action collapse", 1 => _gettext("Collapse")], 1 => [0 => "action expand", 1 => _gettext("Expand")], 2 => [0 => "action dbg_show_trace", 1 => _gettext("Show trace")], 3 => [0 => "action dbg_hide_trace", 1 => _gettext("Hide trace")], 4 => [0 => "text count hide", 1 => _gettext("Count")], 5 => [0 => "text time", 1 => _gettext("Time taken")]]])); + // line 95 + echo "
      +
      + "; + // line 97 + if (($context["cfg_bookmark"] ?? null)) { + // line 98 + echo "
      + "; + // line 99 + $this->loadTemplate("console/toolbar.twig", "console/display.twig", 99)->display(twig_to_array(["parent_div_classes" => "", "content_array" => [0 => [0 => "switch_button", 1 => _gettext("Bookmarks")], 1 => [0 => "button refresh", 1 => _gettext("Refresh")], 2 => [0 => "button add", 1 => _gettext("Add")]]])); + // line 107 + echo "
      + "; + // line 108 + echo ($context["bookmark_content"] ?? null); + echo " +
      +
      +
      + "; + // line 112 + $this->loadTemplate("console/toolbar.twig", "console/display.twig", 112)->display(twig_to_array(["parent_div_classes" => "", "content_array" => [0 => [0 => "switch_button", 1 => _gettext("Add bookmark")]]])); + // line 118 + echo "
      +
      + + + + +
      +
      + +
      +
      +
      +
      + "; + } + // line 138 + echo " "; + // line 139 + echo "
      + "; + // line 140 + $this->loadTemplate("console/toolbar.twig", "console/display.twig", 140)->display(twig_to_array(["parent_div_classes" => "", "content_array" => [0 => [0 => "switch_button", 1 => _gettext("Options")], 1 => [0 => "button default", 1 => _gettext("Set default")]]])); + // line 147 + echo "
      + +
      + +
      + +
      + +
      + +
      +
      +
      +
      + "; + // line 175 + echo " "; + $this->loadTemplate("console/query_action.twig", "console/display.twig", 175)->display(twig_to_array(["parent_div_classes" => "query_actions", "content_array" => [0 => [0 => "action collapse", 1 => _gettext("Collapse")], 1 => [0 => "action expand", 1 => _gettext("Expand")], 2 => [0 => "action requery", 1 => _gettext("Requery")], 3 => [0 => "action edit", 1 => _gettext("Edit")], 4 => [0 => "action explain", 1 => _gettext("Explain")], 5 => [0 => "action profiling", 1 => _gettext("Profiling")], 6 => (( // line 184 +(isset($context["cfg_bookmark"]) || array_key_exists("cfg_bookmark", $context))) ? ([0 => "action bookmark", 1 => _gettext("Bookmark")]) : (null)), 7 => [0 => "text failed", 1 => _gettext("Query failed")], 8 => [0 => "text targetdb", 1 => _gettext("Database"), "extraSpan" => ""], 9 => [0 => "text query_time", 1 => _gettext("Queried time"), "extraSpan" => ""]]])); + // line 190 + echo "
      +
      +
      +"; + } + + public function getTemplateName() + { + return "console/display.twig"; + } + + public function isTraitable() + { + return false; + } + + public function getDebugInfo() + { + return array ( 231 => 190, 229 => 184, 227 => 175, 220 => 169, 218 => 168, 213 => 165, 211 => 162, 205 => 158, 203 => 157, 198 => 154, 196 => 153, 191 => 150, 189 => 149, 185 => 147, 183 => 140, 180 => 139, 178 => 138, 166 => 129, 163 => 128, 161 => 127, 155 => 124, 149 => 121, 144 => 118, 142 => 112, 135 => 108, 132 => 107, 130 => 99, 127 => 98, 125 => 97, 121 => 95, 119 => 84, 112 => 79, 110 => 64, 107 => 63, 104 => 61, 97 => 55, 94 => 54, 84 => 51, 82 => 47, 81 => 46, 80 => 42, 79 => 40, 78 => 31, 72 => 30, 68 => 29, 66 => 28, 61 => 27, 59 => 26, 55 => 24, 53 => 23, 49 => 21, 47 => 20, 41 => 16, 39 => 15, 37 => 11, 36 => 7, 34 => 4, 30 => 1,); + } + + /** @deprecated since 1.27 (to be removed in 2.0). Use getSourceContext() instead */ + public function getSource() + { + @trigger_error('The '.__METHOD__.' method is deprecated since version 1.27 and will be removed in 2.0. Use getSourceContext() instead.', E_USER_DEPRECATED); + + return $this->getSourceContext()->getCode(); + } + + public function getSourceContext() + { + return new Source("", "console/display.twig", "/var/www/html/templates/console/display.twig"); + } +} diff --git a/php/apps/phpmyadmin49/html/tmp/twig/82/8240427e40301f23936ddf4e668b118f7c550611fc70554b26bf810d6d56270c.php b/php/apps/phpmyadmin49/html/tmp/twig/82/8240427e40301f23936ddf4e668b118f7c550611fc70554b26bf810d6d56270c.php new file mode 100644 index 00000000..d5df858c --- /dev/null +++ b/php/apps/phpmyadmin49/html/tmp/twig/82/8240427e40301f23936ddf4e668b118f7c550611fc70554b26bf810d6d56270c.php @@ -0,0 +1,75 @@ +parent = false; + + $this->blocks = [ + ]; + } + + protected function doDisplay(array $context, array $blocks = []) + { + // line 1 + echo "env, ($context["row_class"] ?? null), "html", null, true); + echo "\" data-filter-row=\""; + echo twig_escape_filter($this->env, twig_upper_filter($this->env, ($context["name"] ?? null)), "html", null, true); + echo "\"> + + + + +"; + } + + public function getTemplateName() + { + return "server/variables/session_variable_row.twig"; + } + + public function isTraitable() + { + return false; + } + + public function getDebugInfo() + { + return array ( 42 => 4, 38 => 3, 30 => 1,); + } + + /** @deprecated since 1.27 (to be removed in 2.0). Use getSourceContext() instead */ + public function getSource() + { + @trigger_error('The '.__METHOD__.' method is deprecated since version 1.27 and will be removed in 2.0. Use getSourceContext() instead.', E_USER_DEPRECATED); + + return $this->getSourceContext()->getCode(); + } + + public function getSourceContext() + { + return new Source("", "server/variables/session_variable_row.twig", "/var/www/html/templates/server/variables/session_variable_row.twig"); + } +} diff --git a/php/apps/phpmyadmin49/html/tmp/twig/93/93f7c22b16a66ae2662c3761f29afc78d78617f9ca5b13d1d3dc4dd26c6c03ec.php b/php/apps/phpmyadmin49/html/tmp/twig/93/93f7c22b16a66ae2662c3761f29afc78d78617f9ca5b13d1d3dc4dd26c6c03ec.php new file mode 100644 index 00000000..f6fa59ce --- /dev/null +++ b/php/apps/phpmyadmin49/html/tmp/twig/93/93f7c22b16a66ae2662c3761f29afc78d78617f9ca5b13d1d3dc4dd26c6c03ec.php @@ -0,0 +1,137 @@ +parent = false; + + $this->blocks = [ + ]; + } + + protected function doDisplay(array $context, array $blocks = []) + { + // line 1 + echo "env, ($context["id"] ?? null), "html", null, true); + echo "\""; + } + // line 2 + if ( !twig_test_empty(($context["class"] ?? null))) { + echo " class=\""; + echo twig_escape_filter($this->env, ($context["class"] ?? null), "html", null, true); + echo "\""; + } + echo "> + + "; + // line 4 + if ((((isset($context["url"]) || array_key_exists("url", $context)) && twig_test_iterable(($context["url"] ?? null))) && !twig_test_empty($this->getAttribute(($context["url"] ?? null), "href", [], "array")))) { + // line 5 + echo " getAttribute(($context["url"] ?? null), "href", [], "array"))) { + echo " href=\""; + echo $this->getAttribute(($context["url"] ?? null), "href", [], "array"); + echo "\""; + } + // line 6 + if ( !twig_test_empty($this->getAttribute(($context["url"] ?? null), "target", [], "array"))) { + echo " target=\""; + echo twig_escape_filter($this->env, $this->getAttribute(($context["url"] ?? null), "target", [], "array"), "html", null, true); + echo "\""; + } + // line 7 + if (( !twig_test_empty($this->getAttribute(($context["url"] ?? null), "target", [], "array")) && ($this->getAttribute(($context["url"] ?? null), "target", [], "array") == "_blank"))) { + echo " rel=\"noopener noreferrer\""; + } + // line 8 + if ( !twig_test_empty($this->getAttribute(($context["url"] ?? null), "id", [], "array"))) { + echo " id=\""; + echo twig_escape_filter($this->env, $this->getAttribute(($context["url"] ?? null), "id", [], "array"), "html", null, true); + echo "\""; + } + // line 9 + if ( !twig_test_empty($this->getAttribute(($context["url"] ?? null), "class", [], "array"))) { + echo " class=\""; + echo twig_escape_filter($this->env, $this->getAttribute(($context["url"] ?? null), "class", [], "array"), "html", null, true); + echo "\""; + } + // line 10 + if ( !twig_test_empty($this->getAttribute(($context["url"] ?? null), "title", [], "array"))) { + echo " title=\""; + echo twig_escape_filter($this->env, $this->getAttribute(($context["url"] ?? null), "title", [], "array"), "html", null, true); + echo "\""; + } + echo "> + "; + } + // line 12 + echo " "; + echo ($context["content"] ?? null); + echo " + "; + // line 13 + if ((((isset($context["url"]) || array_key_exists("url", $context)) && twig_test_iterable(($context["url"] ?? null))) && !twig_test_empty($this->getAttribute(($context["url"] ?? null), "href", [], "array")))) { + // line 14 + echo " + "; + } + // line 16 + echo " "; + if ( !twig_test_empty(($context["mysql_help_page"] ?? null))) { + // line 17 + echo " "; + echo PhpMyAdmin\Util::showMySQLDocu(($context["mysql_help_page"] ?? null)); + echo " + "; + } + // line 19 + echo " +"; + } + + public function getTemplateName() + { + return "list/item.twig"; + } + + public function isTraitable() + { + return false; + } + + public function getDebugInfo() + { + return array ( 106 => 19, 100 => 17, 97 => 16, 93 => 14, 91 => 13, 86 => 12, 77 => 10, 71 => 9, 65 => 8, 61 => 7, 55 => 6, 48 => 5, 46 => 4, 37 => 2, 30 => 1,); + } + + /** @deprecated since 1.27 (to be removed in 2.0). Use getSourceContext() instead */ + public function getSource() + { + @trigger_error('The '.__METHOD__.' method is deprecated since version 1.27 and will be removed in 2.0. Use getSourceContext() instead.', E_USER_DEPRECATED); + + return $this->getSourceContext()->getCode(); + } + + public function getSourceContext() + { + return new Source("", "list/item.twig", "/var/www/html/templates/list/item.twig"); + } +} diff --git a/php/apps/phpmyadmin49/html/tmp/twig/a2/a2f4bb1e800819e72287a8528e43dbc9ae70b14d125ee0e251092bded127b7fd.php b/php/apps/phpmyadmin49/html/tmp/twig/a2/a2f4bb1e800819e72287a8528e43dbc9ae70b14d125ee0e251092bded127b7fd.php new file mode 100644 index 00000000..10a05462 --- /dev/null +++ b/php/apps/phpmyadmin49/html/tmp/twig/a2/a2f4bb1e800819e72287a8528e43dbc9ae70b14d125ee0e251092bded127b7fd.php @@ -0,0 +1,95 @@ +parent = false; + + $this->blocks = [ + ]; + } + + protected function doDisplay(array $context, array $blocks = []) + { + // line 1 + if ((($context["initial_sliders_state"] ?? null) == "disabled")) { + // line 2 + echo " env, ($context["id"] ?? null), "html", null, true); + echo "\""; + } + echo "> +"; + } else { + // line 4 + echo " "; + // line 12 + echo " env, ($context["id"] ?? null), "html", null, true); + echo "\""; + } + // line 13 + echo " "; + if ((($context["initial_sliders_state"] ?? null) == "closed")) { + // line 14 + echo "style=\"display: none; overflow:auto;\""; + } + echo " class=\"pma_auto_slider\""; + // line 15 + if ((isset($context["message"]) || array_key_exists("message", $context))) { + echo " title=\""; + echo twig_escape_filter($this->env, ($context["message"] ?? null), "html", null, true); + echo "\""; + } + echo "> +"; + } + } + + public function getTemplateName() + { + return "div_for_slider_effect.twig"; + } + + public function isTraitable() + { + return false; + } + + public function getDebugInfo() + { + return array ( 58 => 15, 54 => 14, 51 => 13, 44 => 12, 42 => 4, 32 => 2, 30 => 1,); + } + + /** @deprecated since 1.27 (to be removed in 2.0). Use getSourceContext() instead */ + public function getSource() + { + @trigger_error('The '.__METHOD__.' method is deprecated since version 1.27 and will be removed in 2.0. Use getSourceContext() instead.', E_USER_DEPRECATED); + + return $this->getSourceContext()->getCode(); + } + + public function getSourceContext() + { + return new Source("", "div_for_slider_effect.twig", "/var/www/html/templates/div_for_slider_effect.twig"); + } +} diff --git a/php/apps/phpmyadmin49/html/tmp/twig/a2/a2f82a068744a90be01600f26765724d37559f6466e76fd22de332bdf01683a5.php b/php/apps/phpmyadmin49/html/tmp/twig/a2/a2f82a068744a90be01600f26765724d37559f6466e76fd22de332bdf01683a5.php new file mode 100644 index 00000000..773a435b --- /dev/null +++ b/php/apps/phpmyadmin49/html/tmp/twig/a2/a2f82a068744a90be01600f26765724d37559f6466e76fd22de332bdf01683a5.php @@ -0,0 +1,307 @@ +parent = false; + + $this->blocks = [ + ]; + } + + protected function doDisplay(array $context, array $blocks = []) + { + // line 1 + echo "\$( function() { + "; + // line 3 + echo " \$(\"#buttonGo\").bind(\"click\", function() { + "; + // line 5 + echo " \$(\"#upload_form_form\").css(\"display\", \"none\"); + + "; + // line 7 + if ((($context["handler"] ?? null) != "PhpMyAdmin\\Plugins\\Import\\Upload\\UploadNoplugin")) { + // line 8 + echo " "; + // line 9 + echo " "; + $context["ajax_url"] = ((("import_status.php?id=" . ($context["upload_id"] ?? null)) . "&") . PhpMyAdmin\Url::getCommonRaw(["import_status" => 1])); + // line 12 + echo " "; + $context["promot_str"] = PhpMyAdmin\Sanitize::jsFormat(_gettext("The file being uploaded is probably larger than the maximum allowed size or this is a known bug in webkit based (Safari, Google Chrome, Arora etc.) browsers."), false); + // line 16 + echo " "; + $context["statustext_str"] = PhpMyAdmin\Sanitize::escapeJsString(_gettext("%s of %s")); + // line 17 + echo " "; + $context["second_str"] = PhpMyAdmin\Sanitize::jsFormat(_gettext("%s/sec."), false); + // line 18 + echo " "; + $context["remaining_min"] = PhpMyAdmin\Sanitize::jsFormat(_gettext("About %MIN min. %SEC sec. remaining."), false); + // line 19 + echo " "; + $context["remaining_second"] = PhpMyAdmin\Sanitize::jsFormat(_gettext("About %SEC sec. remaining."), false); + // line 20 + echo " "; + $context["processed_str"] = PhpMyAdmin\Sanitize::jsFormat(_gettext("The file is being processed, please be patient."), false); + // line 24 + echo " "; + $context["import_url"] = PhpMyAdmin\Url::getCommonRaw(["import_status" => 1]); + // line 25 + echo " + "; + // line 26 + ob_start(function () { return ''; }); + // line 27 + echo " "; + ob_start(function () { return ''; }); + // line 28 + echo "
      +
      +
      +
      +
      +
      +
      +
      + env, ($context["pma_theme_image"] ?? null), "html", null, true); + echo "ajax_clock_small.gif\" width=\"16\" height=\"16\" alt=\"ajax clock\" /> "; + echo PhpMyAdmin\Sanitize::jsFormat(_gettext("Uploading your import file…"), false); + // line 37 + echo "
      +
      +
      + "; + echo trim(preg_replace('/>\s+<', ob_get_clean())); + // line 41 + echo " "; + $context["upload_html"] = ('' === $tmp = ob_get_clean()) ? '' : new Markup($tmp, $this->env->getCharset()); + // line 42 + echo " + "; + // line 44 + echo " var finished = false; + var percent = 0.0; + var total = 0; + var complete = 0; + var original_title = parent && parent.document ? parent.document.title : false; + var import_start; + + var perform_upload = function () { + new \$.getJSON( + \""; + // line 53 + echo ($context["ajax_url"] ?? null); + echo "\", + {}, + function(response) { + finished = response.finished; + percent = response.percent; + total = response.total; + complete = response.complete; + + if (total==0 && complete==0 && percent==0) { + \$(\"#upload_form_status_info\").html('env, ($context["pma_theme_image"] ?? null), "html", null, true); + echo "ajax_clock_small.gif\" width=\"16\" height=\"16\" alt=\"ajax clock\" /> "; + echo ($context["promot_str"] ?? null); + echo "'); + \$(\"#upload_form_status\").css(\"display\", \"none\"); + } else { + var now = new Date(); + now = Date.UTC( + now.getFullYear(), + now.getMonth(), + now.getDate(), + now.getHours(), + now.getMinutes(), + now.getSeconds()) + + now.getMilliseconds() - 1000; + var statustext = PMA_sprintf( + \""; + // line 75 + echo ($context["statustext_str"] ?? null); + echo "\", + formatBytes( + complete, 1, PMA_messages.strDecimalSeparator + ), + formatBytes( + total, 1, PMA_messages.strDecimalSeparator + ) + ); + + if (\$(\"#importmain\").is(\":visible\")) { + "; + // line 86 + echo " \$(\"#importmain\").hide(); + \$(\"#import_form_status\") + .html('"; + // line 88 + echo ($context["upload_html"] ?? null); + echo "') + .show(); + import_start = now; + } + else if (percent > 9 || complete > 2000000) { + "; + // line 94 + echo " var used_time = now - import_start; + var seconds = parseInt(((total - complete) / complete) * used_time / 1000); + var speed = PMA_sprintf( + \""; + // line 97 + echo ($context["second_str"] ?? null); + echo "\", + formatBytes(complete / used_time * 1000, 1, PMA_messages.strDecimalSeparator) + ); + + var minutes = parseInt(seconds / 60); + seconds %= 60; + var estimated_time; + if (minutes > 0) { + estimated_time = \""; + // line 105 + echo ($context["remaining_min"] ?? null); + echo "\" + .replace(\"%MIN\", minutes) + .replace(\"%SEC\", seconds); + } + else { + estimated_time = \""; + // line 110 + echo ($context["remaining_second"] ?? null); + echo "\" + .replace(\"%SEC\", seconds); + } + + statustext += \"
      \" + speed + \"

      \" + estimated_time; + } + + var percent_str = Math.round(percent) + \"%\"; + \$(\"#status\").animate({width: percent_str}, 150); + \$(\".percentage\").text(percent_str); + + "; + // line 122 + echo " if (original_title !== false) { + parent.document.title + = percent_str + \" - \" + original_title; + } + else { + document.title + = percent_str + \" - \" + original_title; + } + \$(\"#statustext\").html(statustext); + } + + if (finished == true) { + if (original_title !== false) { + parent.document.title = original_title; + } + else { + document.title = original_title; + } + \$(\"#importmain\").hide(); + "; + // line 142 + echo " \$(\"#import_form_status\") + .html('env, ($context["pma_theme_image"] ?? null), "html", null, true); + echo "ajax_clock_small.gif\" width=\"16\" height=\"16\" alt=\"ajax clock\" /> "; + echo ($context["processed_str"] ?? null); + echo "') + .show(); + \$(\"#import_form_status\").load(\"import_status.php?message=true&"; + // line 145 + echo ($context["import_url"] ?? null); + echo "\"); + PMA_reloadNavigation(); + + "; + // line 149 + echo " } + else { + setTimeout(perform_upload, 1000); + } + }); + }; + setTimeout(perform_upload, 1000); + "; + } else { + // line 157 + echo " "; + // line 158 + echo " "; + ob_start(function () { return ''; }); + // line 159 + echo "env, ($context["pma_theme_image"] ?? null), "html", null, true); + // line 160 + echo "ajax_clock_small.gif\" width=\"16\" height=\"16\" alt=\"ajax clock\" />"; + // line 161 + echo PhpMyAdmin\Sanitize::jsFormat(_gettext("Please be patient, the file is being uploaded. Details about the upload are not available."), false); + // line 165 + echo PhpMyAdmin\Util::showDocu("faq", "faq2-9"); + $context["image_tag"] = ('' === $tmp = ob_get_clean()) ? '' : new Markup($tmp, $this->env->getCharset()); + // line 167 + echo " \$('#upload_form_status_info').html('"; + echo ($context["image_tag"] ?? null); + echo "'); + \$(\"#upload_form_status\").css(\"display\", \"none\"); + "; + } + // line 170 + echo " }); +}); +"; + } + + public function getTemplateName() + { + return "display/import/javascript.twig"; + } + + public function isTraitable() + { + return false; + } + + public function getDebugInfo() + { + return array ( 275 => 170, 268 => 167, 265 => 165, 263 => 161, 261 => 160, 258 => 159, 255 => 158, 253 => 157, 243 => 149, 237 => 145, 230 => 143, 227 => 142, 206 => 122, 192 => 110, 184 => 105, 173 => 97, 168 => 94, 160 => 88, 156 => 86, 143 => 75, 125 => 62, 113 => 53, 102 => 44, 99 => 42, 96 => 41, 90 => 37, 86 => 36, 76 => 28, 73 => 27, 71 => 26, 68 => 25, 65 => 24, 62 => 20, 59 => 19, 56 => 18, 53 => 17, 50 => 16, 47 => 12, 44 => 9, 42 => 8, 40 => 7, 36 => 5, 33 => 3, 30 => 1,); + } + + /** @deprecated since 1.27 (to be removed in 2.0). Use getSourceContext() instead */ + public function getSource() + { + @trigger_error('The '.__METHOD__.' method is deprecated since version 1.27 and will be removed in 2.0. Use getSourceContext() instead.', E_USER_DEPRECATED); + + return $this->getSourceContext()->getCode(); + } + + public function getSourceContext() + { + return new Source("", "display/import/javascript.twig", "/var/www/html/templates/display/import/javascript.twig"); + } +} diff --git a/php/apps/phpmyadmin49/html/tmp/twig/a7/a756caf89bcb9018812c288ff323cdc0463accd3ff135b6e0c84078c314577e2.php b/php/apps/phpmyadmin49/html/tmp/twig/a7/a756caf89bcb9018812c288ff323cdc0463accd3ff135b6e0c84078c314577e2.php new file mode 100644 index 00000000..22a0fc08 --- /dev/null +++ b/php/apps/phpmyadmin49/html/tmp/twig/a7/a756caf89bcb9018812c288ff323cdc0463accd3ff135b6e0c84078c314577e2.php @@ -0,0 +1,77 @@ +parent = false; + + $this->blocks = [ + ]; + } + + protected function doDisplay(array $context, array $blocks = []) + { + // line 1 + echo "
      + "; + // line 2 + echo _gettext("Filters"); + echo " +
      + + env, ($context["filter_value"] ?? null), "html", null, true); + echo "\" /> +
      +
      +"; + } + + public function getTemplateName() + { + return "filter.twig"; + } + + public function isTraitable() + { + return false; + } + + public function getDebugInfo() + { + return array ( 43 => 6, 38 => 4, 33 => 2, 30 => 1,); + } + + /** @deprecated since 1.27 (to be removed in 2.0). Use getSourceContext() instead */ + public function getSource() + { + @trigger_error('The '.__METHOD__.' method is deprecated since version 1.27 and will be removed in 2.0. Use getSourceContext() instead.', E_USER_DEPRECATED); + + return $this->getSourceContext()->getCode(); + } + + public function getSourceContext() + { + return new Source("", "filter.twig", "/var/www/html/templates/filter.twig"); + } +} diff --git a/php/apps/phpmyadmin49/html/tmp/twig/ae/ae321d7f5a4dafcf8667fa9fc06ba972765e6afcb9dfdbaf2b57dbd11f4e7890.php b/php/apps/phpmyadmin49/html/tmp/twig/ae/ae321d7f5a4dafcf8667fa9fc06ba972765e6afcb9dfdbaf2b57dbd11f4e7890.php new file mode 100644 index 00000000..3b411698 --- /dev/null +++ b/php/apps/phpmyadmin49/html/tmp/twig/ae/ae321d7f5a4dafcf8667fa9fc06ba972765e6afcb9dfdbaf2b57dbd11f4e7890.php @@ -0,0 +1,140 @@ +parent = false; + + $this->blocks = [ + ]; + } + + protected function doDisplay(array $context, array $blocks = []) + { + // line 1 + echo "
      env, ($context["row_class"] ?? null), "html", null, true); + echo "\" data-filter-row=\""; + echo twig_escape_filter($this->env, twig_upper_filter($this->env, ($context["name"] ?? null)), "html", null, true); + echo "\"> + + + + +"; + } + + public function getTemplateName() + { + return "server/variables/variable_row.twig"; + } + + public function isTraitable() + { + return false; + } + + public function getDebugInfo() + { + return array ( 108 => 28, 102 => 26, 96 => 24, 94 => 23, 90 => 22, 87 => 21, 81 => 19, 74 => 15, 70 => 14, 65 => 13, 63 => 12, 59 => 10, 53 => 7, 48 => 6, 40 => 4, 38 => 3, 30 => 1,); + } + + /** @deprecated since 1.27 (to be removed in 2.0). Use getSourceContext() instead */ + public function getSource() + { + @trigger_error('The '.__METHOD__.' method is deprecated since version 1.27 and will be removed in 2.0. Use getSourceContext() instead.', E_USER_DEPRECATED); + + return $this->getSourceContext()->getCode(); + } + + public function getSourceContext() + { + return new Source("", "server/variables/variable_row.twig", "/var/www/html/templates/server/variables/variable_row.twig"); + } +} diff --git a/php/apps/phpmyadmin49/html/tmp/twig/af/af8cb837379b0b8ea52505cbebef6943de1eda11a6bb4545ebc38811a106795a.php b/php/apps/phpmyadmin49/html/tmp/twig/af/af8cb837379b0b8ea52505cbebef6943de1eda11a6bb4545ebc38811a106795a.php new file mode 100644 index 00000000..ef6ef4f4 --- /dev/null +++ b/php/apps/phpmyadmin49/html/tmp/twig/af/af8cb837379b0b8ea52505cbebef6943de1eda11a6bb4545ebc38811a106795a.php @@ -0,0 +1,451 @@ +parent = false; + + $this->blocks = [ + ]; + } + + protected function doDisplay(array $context, array $blocks = []) + { + // line 1 + echo " +
      +
      + env, ($context["pma_theme_image"] ?? null), "html", null, true); + echo "ajax_clock_small.gif\" width=\"16\" height=\"16\" alt=\"ajax clock\" class=\"hide\" /> + + + +
      + + env, ($context["id_key"] ?? null), "html", null, true); + echo "\" value=\""; + echo twig_escape_filter($this->env, ($context["upload_id"] ?? null), "html", null, true); + echo "\" /> + "; + // line 27 + if ((($context["import_type"] ?? null) == "server")) { + // line 28 + echo " "; + echo PhpMyAdmin\Url::getHiddenInputs("", "", 1); + echo " + "; + } elseif (( // line 29 +($context["import_type"] ?? null) == "database")) { + // line 30 + echo " "; + echo PhpMyAdmin\Url::getHiddenInputs(($context["db"] ?? null), "", 1); + echo " + "; + } else { + // line 32 + echo " "; + echo PhpMyAdmin\Url::getHiddenInputs(($context["db"] ?? null), ($context["table"] ?? null), 1); + echo " + "; + } + // line 34 + echo " env, ($context["import_type"] ?? null), "html", null, true); + echo "\" /> + +
      +

      + "; + // line 38 + echo PhpMyAdmin\Util::getImage("b_import", _gettext("Import")); + echo " + "; + // line 39 + if ((($context["import_type"] ?? null) == "server")) { + // line 40 + echo " "; + echo _gettext("Importing into the current server"); + // line 41 + echo " "; + } elseif ((($context["import_type"] ?? null) == "database")) { + // line 42 + echo " "; + echo twig_escape_filter($this->env, sprintf(_gettext("Importing into the database \"%s\""), ($context["db"] ?? null)), "html", null, true); + echo " + "; + } else { + // line 44 + echo " "; + echo twig_escape_filter($this->env, sprintf(_gettext("Importing into the table \"%s\""), ($context["table"] ?? null)), "html", null, true); + echo " + "; + } + // line 46 + echo "

      +
      + +
      +

      "; + // line 50 + echo _gettext("File to import:"); + echo "

      + + "; + // line 53 + echo " "; + if ( !twig_test_empty(($context["compressions"] ?? null))) { + // line 54 + echo "
      +

      + "; + // line 56 + echo twig_escape_filter($this->env, sprintf(_gettext("File may be compressed (%s) or uncompressed."), twig_join_filter(($context["compressions"] ?? null), ", ")), "html", null, true); + echo " +
      + "; + // line 58 + echo _gettext("A compressed file's name must end in .[format].[compression]. Example: .sql.zip"); + // line 59 + echo "

      +
      + "; + } + // line 62 + echo " +
      + "; + // line 64 + if ((($context["is_upload"] ?? null) && !twig_test_empty(($context["upload_dir"] ?? null)))) { + // line 65 + echo "
        +
      • + + "; + // line 68 + echo PhpMyAdmin\Util::getBrowseUploadFileBlock(($context["max_upload_size"] ?? null)); + echo " + "; + // line 69 + echo _gettext("You may also drag and drop a file on any page."); + // line 70 + echo "
      • +
      • + + "; + // line 76 + echo PhpMyAdmin\Util::getSelectUploadFileBlock( // line 77 +($context["import_list"] ?? null), // line 78 +($context["upload_dir"] ?? null)); + // line 79 + echo " +
      • +
      + "; + } elseif ( // line 82 +($context["is_upload"] ?? null)) { + // line 83 + echo " "; + echo PhpMyAdmin\Util::getBrowseUploadFileBlock(($context["max_upload_size"] ?? null)); + echo " +

      "; + // line 84 + echo _gettext("You may also drag and drop a file on any page."); + echo "

      + "; + } elseif ( ! // line 85 +($context["is_upload"] ?? null)) { + // line 86 + echo " "; + echo call_user_func_array($this->env->getFunction('Message_notice')->getCallable(), [_gettext("File uploads are not allowed on this server.")]); + echo " + "; + } elseif ( !twig_test_empty( // line 87 +($context["upload_dir"] ?? null))) { + // line 88 + echo " "; + echo PhpMyAdmin\Util::getSelectUploadFileBlock( // line 89 +($context["import_list"] ?? null), // line 90 +($context["upload_dir"] ?? null)); + // line 91 + echo " + "; + } + // line 93 + echo "
      + +
      + "; + // line 97 + echo " + "; + // line 98 + if (($context["is_encoding_supported"] ?? null)) { + // line 99 + echo " + "; + } else { + // line 111 + echo " "; + echo PhpMyAdmin\Charsets::getCharsetDropdownBox( // line 112 +($context["dbi"] ?? null), // line 113 +($context["disable_is"] ?? null), "charset_of_file", "charset_of_file", "utf8", false); + // line 118 + echo " + "; + } + // line 120 + echo "
      +
      + +
      +

      "; + // line 124 + echo _gettext("Partial import:"); + echo "

      + + "; + // line 126 + if (((isset($context["timeout_passed"]) || array_key_exists("timeout_passed", $context)) && ($context["timeout_passed"] ?? null))) { + // line 127 + echo "
      + env, ($context["offset"] ?? null), "html", null, true); + echo "\" /> + "; + // line 129 + echo twig_escape_filter($this->env, sprintf(_gettext("Previous import timed out, after resubmitting will continue from position %d."), ($context["offset"] ?? null)), "html", null, true); + echo " +
      + "; + } + // line 132 + echo " +
      + + +
      + + "; + // line 141 + if ( !((isset($context["timeout_passed"]) || array_key_exists("timeout_passed", $context)) && ($context["timeout_passed"] ?? null))) { + // line 142 + echo "
      + + +
      + "; + } else { + // line 151 + echo " "; + // line 154 + echo " + "; + } + // line 158 + echo "
      + +
      +

      "; + // line 161 + echo _gettext("Other options:"); + echo "

      +
      + "; + // line 163 + echo PhpMyAdmin\Util::getFKCheckbox(); + echo " +
      +
      + +
      +

      "; + // line 168 + echo _gettext("Format:"); + echo "

      + "; + // line 169 + echo PhpMyAdmin\Plugins::getChoice("Import", "format", ($context["import_list"] ?? null)); + echo " +
      +
      + +
      +

      "; + // line 174 + echo _gettext("Format-specific options:"); + echo "

      +

      + "; + // line 176 + echo _gettext("Scroll down to fill in the options for the selected format and ignore the options for other formats."); + // line 177 + echo "

      + "; + // line 178 + echo PhpMyAdmin\Plugins::getOptions("Import", ($context["import_list"] ?? null)); + echo " +
      +
      + + "; + // line 183 + echo " "; + if (($context["can_convert_kanji"] ?? null)) { + // line 184 + echo "
      +

      "; + // line 185 + echo _gettext("Encoding Conversion:"); + echo "

      + "; + // line 186 + $this->loadTemplate("encoding/kanji_encoding_form.twig", "display/import/import.twig", 186)->display($context); + // line 187 + echo "
      + "; + } + // line 189 + echo " +
      + +
      + +
      +"; + } + + public function getTemplateName() + { + return "display/import/import.twig"; + } + + public function isTraitable() + { + return false; + } + + public function getDebugInfo() + { + return array ( 416 => 191, 412 => 189, 408 => 187, 406 => 186, 402 => 185, 399 => 184, 396 => 183, 389 => 178, 386 => 177, 384 => 176, 379 => 174, 371 => 169, 367 => 168, 359 => 163, 354 => 161, 349 => 158, 345 => 156, 343 => 155, 341 => 154, 339 => 151, 334 => 148, 332 => 147, 329 => 145, 327 => 144, 323 => 142, 321 => 141, 316 => 138, 314 => 137, 309 => 135, 304 => 132, 298 => 129, 294 => 128, 291 => 127, 289 => 126, 284 => 124, 278 => 120, 274 => 118, 272 => 113, 271 => 112, 269 => 111, 265 => 109, 256 => 106, 253 => 105, 249 => 104, 247 => 103, 246 => 102, 241 => 101, 237 => 100, 234 => 99, 232 => 98, 227 => 97, 222 => 93, 218 => 91, 216 => 90, 215 => 89, 213 => 88, 211 => 87, 206 => 86, 204 => 85, 200 => 84, 195 => 83, 193 => 82, 188 => 79, 186 => 78, 185 => 77, 184 => 76, 181 => 75, 178 => 74, 176 => 73, 172 => 70, 170 => 69, 166 => 68, 161 => 65, 159 => 64, 155 => 62, 150 => 59, 148 => 58, 143 => 56, 139 => 54, 136 => 53, 131 => 50, 125 => 46, 119 => 44, 113 => 42, 110 => 41, 107 => 40, 105 => 39, 101 => 38, 93 => 34, 87 => 32, 81 => 30, 79 => 29, 74 => 28, 72 => 27, 66 => 26, 62 => 24, 59 => 23, 57 => 22, 47 => 13, 45 => 11, 44 => 10, 43 => 9, 42 => 8, 35 => 4, 30 => 1,); + } + + /** @deprecated since 1.27 (to be removed in 2.0). Use getSourceContext() instead */ + public function getSource() + { + @trigger_error('The '.__METHOD__.' method is deprecated since version 1.27 and will be removed in 2.0. Use getSourceContext() instead.', E_USER_DEPRECATED); + + return $this->getSourceContext()->getCode(); + } + + public function getSourceContext() + { + return new Source("", "display/import/import.twig", "/var/www/html/templates/display/import/import.twig"); + } +} diff --git a/php/apps/phpmyadmin49/html/tmp/twig/af/afd545260bfe71cd627eed351c20ae495a2a7f5c543af31c03cef5cf249436b1.php b/php/apps/phpmyadmin49/html/tmp/twig/af/afd545260bfe71cd627eed351c20ae495a2a7f5c543af31c03cef5cf249436b1.php new file mode 100644 index 00000000..ad05d8c6 --- /dev/null +++ b/php/apps/phpmyadmin49/html/tmp/twig/af/afd545260bfe71cd627eed351c20ae495a2a7f5c543af31c03cef5cf249436b1.php @@ -0,0 +1,130 @@ +parent = false; + + $this->blocks = [ + ]; + } + + protected function doDisplay(array $context, array $blocks = []) + { + // line 1 + echo "
      +
      ("; + // line 3 + echo _gettext("Session value"); + echo ") "; + // line 4 + echo twig_escape_filter($this->env, ($context["value"] ?? null), "html", null, true); + echo "
      + "; + // line 3 + if (($context["editable"] ?? null)) { + // line 4 + echo " env, ($context["name"] ?? null), "html", null, true); + echo "\" class=\"editLink\">"; + echo PhpMyAdmin\Util::getIcon("b_edit", _gettext("Edit")); + echo " + "; + } else { + // line 6 + echo " + "; + // line 7 + echo PhpMyAdmin\Util::getIcon("bd_edit", _gettext("Edit")); + echo " + + "; + } + // line 10 + echo " + "; + // line 12 + if ((($context["doc_link"] ?? null) != null)) { + // line 13 + echo " env, twig_replace_filter(($context["name"] ?? null), ["_" => " "]), "html", null, true); + echo "\"> + "; + // line 14 + echo PhpMyAdmin\Util::showMySQLDocu($this->getAttribute(($context["doc_link"] ?? null), 1, [], "array"), false, (($this->getAttribute(($context["doc_link"] ?? null), 2, [], "array") . "_") . $this->getAttribute(($context["doc_link"] ?? null), 0, [], "array")), true); + echo " + "; + // line 15 + echo twig_replace_filter(twig_escape_filter($this->env, ($context["name"] ?? null)), ["_" => " "]); + echo " + + + "; + } else { + // line 19 + echo " "; + echo twig_escape_filter($this->env, twig_replace_filter(($context["name"] ?? null), ["_" => " "]), "html", null, true); + echo " + "; + } + // line 21 + echo " + "; + // line 23 + if ((($context["is_html_formatted"] ?? null) == false)) { + // line 24 + echo " "; + echo twig_replace_filter(twig_escape_filter($this->env, ($context["value"] ?? null)), ["," => ",​"]); + echo " + "; + } else { + // line 26 + echo " "; + echo ($context["value"] ?? null); + echo " + "; + } + // line 28 + echo "
      + + + + + + + "; + // line 9 + $context['_parent'] = $context; + $context['_seq'] = twig_ensure_traversable(($context["mysql_charsets"] ?? null)); + foreach ($context['_seq'] as $context["_key"] => $context["current_charset"]) { + // line 10 + echo " + + + "; + // line 18 + $context['_parent'] = $context; + $context['_seq'] = twig_ensure_traversable($this->getAttribute(($context["mysql_collations"] ?? null), $context["current_charset"], [], "array")); + foreach ($context['_seq'] as $context["_key"] => $context["current_collation"]) { + // line 19 + echo " getAttribute(($context["mysql_dft_collations"] ?? null), $context["current_charset"], [], "array") == $context["current_collation"])) ? (" marked") : ("")); + echo "\"> + + + + "; + } + $_parent = $context['_parent']; + unset($context['_seq'], $context['_iterated'], $context['_key'], $context['current_collation'], $context['_parent'], $context['loop']); + $context = array_intersect_key($context, $_parent) + $_parent; + // line 24 + echo " "; + } + $_parent = $context['_parent']; + unset($context['_seq'], $context['_iterated'], $context['_key'], $context['current_charset'], $context['_parent'], $context['loop']); + $context = array_intersect_key($context, $_parent) + $_parent; + // line 25 + echo "
      "; + // line 5 + echo _gettext("Collation"); + echo ""; + // line 6 + echo _gettext("Description"); + echo "
      + "; + // line 12 + echo twig_escape_filter($this->env, $context["current_charset"], "html", null, true); + echo " + "; + // line 13 + if ( !twig_test_empty($this->getAttribute(($context["mysql_charsets_desc"] ?? null), $context["current_charset"], [], "array"))) { + // line 14 + echo " ("; + echo twig_escape_filter($this->env, $this->getAttribute(($context["mysql_charsets_desc"] ?? null), $context["current_charset"], [], "array"), "html", null, true); + echo ") + "; + } + // line 16 + echo "
      "; + // line 20 + echo twig_escape_filter($this->env, $context["current_collation"], "html", null, true); + echo ""; + // line 21 + echo twig_escape_filter($this->env, PhpMyAdmin\Charsets::getCollationDescr($context["current_collation"]), "html", null, true); + echo "
      +
      +"; + } + + public function getTemplateName() + { + return "server/collations/charsets.twig"; + } + + public function isTraitable() + { + return false; + } + + public function getDebugInfo() + { + return array ( 98 => 25, 92 => 24, 83 => 21, 79 => 20, 74 => 19, 70 => 18, 66 => 16, 60 => 14, 58 => 13, 54 => 12, 50 => 10, 46 => 9, 40 => 6, 36 => 5, 30 => 1,); + } + + /** @deprecated since 1.27 (to be removed in 2.0). Use getSourceContext() instead */ + public function getSource() + { + @trigger_error('The '.__METHOD__.' method is deprecated since version 1.27 and will be removed in 2.0. Use getSourceContext() instead.', E_USER_DEPRECATED); + + return $this->getSourceContext()->getCode(); + } + + public function getSourceContext() + { + return new Source("", "server/collations/charsets.twig", "/var/www/html/templates/server/collations/charsets.twig"); + } +} diff --git a/php/apps/phpmyadmin49/html/tmp/twig/b0/b06709daf82b8b87df25e8003d35775632cccdee9496a704f20271f0dcffc242.php b/php/apps/phpmyadmin49/html/tmp/twig/b0/b06709daf82b8b87df25e8003d35775632cccdee9496a704f20271f0dcffc242.php new file mode 100644 index 00000000..25f857d7 --- /dev/null +++ b/php/apps/phpmyadmin49/html/tmp/twig/b0/b06709daf82b8b87df25e8003d35775632cccdee9496a704f20271f0dcffc242.php @@ -0,0 +1,100 @@ +parent = false; + + $this->blocks = [ + ]; + } + + protected function doDisplay(array $context, array $blocks = []) + { + // line 1 + echo " +
      + "; + // line 3 + echo PhpMyAdmin\Url::getHiddenInputs(($context["db"] ?? null), ($context["table"] ?? null)); + echo " + + env, ($context["pos"] ?? null), "html", null, true); + echo "\" /> + env, ($context["is_browse_distinct"] ?? null), "html", null, true); + echo "\" /> + env, ($context["goto"] ?? null), "html", null, true); + echo "\" /> + "; + // line 8 + echo ($context["input_for_real_end"] ?? null); + echo " + env, ($context["title"] ?? null), "html", null, true); + echo "\""; + // line 10 + echo ($context["onclick"] ?? null); + echo " /> +
      + +"; + } + + public function getTemplateName() + { + return "display/results/table_navigation_button.twig"; + } + + public function isTraitable() + { + return false; + } + + public function getDebugInfo() + { + return array ( 66 => 10, 61 => 9, 57 => 8, 53 => 7, 49 => 6, 45 => 5, 41 => 4, 37 => 3, 33 => 2, 30 => 1,); + } + + /** @deprecated since 1.27 (to be removed in 2.0). Use getSourceContext() instead */ + public function getSource() + { + @trigger_error('The '.__METHOD__.' method is deprecated since version 1.27 and will be removed in 2.0. Use getSourceContext() instead.', E_USER_DEPRECATED); + + return $this->getSourceContext()->getCode(); + } + + public function getSourceContext() + { + return new Source("", "display/results/table_navigation_button.twig", "/var/www/html/templates/display/results/table_navigation_button.twig"); + } +} diff --git a/php/apps/phpmyadmin49/html/tmp/twig/b0/b0b0d27050f934d1aacc6ae53676440327143054c86035c3a8aa93b9ae04c05c.php b/php/apps/phpmyadmin49/html/tmp/twig/b0/b0b0d27050f934d1aacc6ae53676440327143054c86035c3a8aa93b9ae04c05c.php new file mode 100644 index 00000000..74561481 --- /dev/null +++ b/php/apps/phpmyadmin49/html/tmp/twig/b0/b0b0d27050f934d1aacc6ae53676440327143054c86035c3a8aa93b9ae04c05c.php @@ -0,0 +1,84 @@ +parent = false; + + $this->blocks = [ + ]; + } + + protected function doDisplay(array $context, array $blocks = []) + { + // line 1 + if (($this->getAttribute(($context["comments_map"] ?? null), $this->getAttribute(($context["fields_meta"] ?? null), "table", []), [], "array", true, true) && $this->getAttribute($this->getAttribute( // line 2 +($context["comments_map"] ?? null), $this->getAttribute(($context["fields_meta"] ?? null), "table", []), [], "array", false, true), $this->getAttribute(($context["fields_meta"] ?? null), "name", []), [], "array", true, true))) { + // line 3 + echo " env, $this->getAttribute($this->getAttribute(($context["comments_map"] ?? null), $this->getAttribute(($context["fields_meta"] ?? null), "table", []), [], "array"), $this->getAttribute(($context["fields_meta"] ?? null), "name", []), [], "array"), "html", null, true); + echo "\"> + "; + // line 4 + if ((twig_length_filter($this->env, $this->getAttribute($this->getAttribute(($context["comments_map"] ?? null), $this->getAttribute(($context["fields_meta"] ?? null), "table", []), [], "array"), $this->getAttribute(($context["fields_meta"] ?? null), "name", []), [], "array")) > ($context["limit_chars"] ?? null))) { + // line 5 + echo " "; + echo twig_escape_filter($this->env, twig_slice($this->env, $this->getAttribute($this->getAttribute(($context["comments_map"] ?? null), $this->getAttribute(($context["fields_meta"] ?? null), "table", []), [], "array"), $this->getAttribute(($context["fields_meta"] ?? null), "name", []), [], "array"), 0, ($context["limit_chars"] ?? null)), "html", null, true); + echo "… + "; + } else { + // line 7 + echo " "; + echo twig_escape_filter($this->env, $this->getAttribute($this->getAttribute(($context["comments_map"] ?? null), $this->getAttribute(($context["fields_meta"] ?? null), "table", []), [], "array"), $this->getAttribute(($context["fields_meta"] ?? null), "name", []), [], "array"), "html", null, true); + echo " + "; + } + // line 9 + echo " +"; + } + } + + public function getTemplateName() + { + return "display/results/comment_for_row.twig"; + } + + public function isTraitable() + { + return false; + } + + public function getDebugInfo() + { + return array ( 52 => 9, 46 => 7, 40 => 5, 38 => 4, 33 => 3, 31 => 2, 30 => 1,); + } + + /** @deprecated since 1.27 (to be removed in 2.0). Use getSourceContext() instead */ + public function getSource() + { + @trigger_error('The '.__METHOD__.' method is deprecated since version 1.27 and will be removed in 2.0. Use getSourceContext() instead.', E_USER_DEPRECATED); + + return $this->getSourceContext()->getCode(); + } + + public function getSourceContext() + { + return new Source("", "display/results/comment_for_row.twig", "/var/www/html/templates/display/results/comment_for_row.twig"); + } +} diff --git a/php/apps/phpmyadmin49/html/tmp/twig/b2/b2f70354940304f8f9191e2aa90f40509447a5d1e48636d45546f5d2b168d427.php b/php/apps/phpmyadmin49/html/tmp/twig/b2/b2f70354940304f8f9191e2aa90f40509447a5d1e48636d45546f5d2b168d427.php new file mode 100644 index 00000000..0a4fa678 --- /dev/null +++ b/php/apps/phpmyadmin49/html/tmp/twig/b2/b2f70354940304f8f9191e2aa90f40509447a5d1e48636d45546f5d2b168d427.php @@ -0,0 +1,82 @@ +parent = false; + + $this->blocks = [ + ]; + } + + protected function doDisplay(array $context, array $blocks = []) + { + // line 1 + echo "
      +"; + // line 2 + $context['_parent'] = $context; + $context['_seq'] = twig_ensure_traversable(twig_get_array_keys_filter(($context["plugins"] ?? null))); + foreach ($context['_seq'] as $context["_key"] => $context["plugin_type"]) { + // line 3 + echo " env, preg_replace("/[^a-z]/", "", twig_lower_filter($this->env, $context["plugin_type"])), "html", null, true); + echo "\"> + "; + // line 4 + echo twig_escape_filter($this->env, $context["plugin_type"], "html", null, true); + echo " + +"; + } + $_parent = $context['_parent']; + unset($context['_seq'], $context['_iterated'], $context['_key'], $context['plugin_type'], $context['_parent'], $context['loop']); + $context = array_intersect_key($context, $_parent) + $_parent; + // line 7 + echo "
      +"; + } + + public function getTemplateName() + { + return "server/plugins/section_links.twig"; + } + + public function isTraitable() + { + return false; + } + + public function getDebugInfo() + { + return array ( 51 => 7, 42 => 4, 37 => 3, 33 => 2, 30 => 1,); + } + + /** @deprecated since 1.27 (to be removed in 2.0). Use getSourceContext() instead */ + public function getSource() + { + @trigger_error('The '.__METHOD__.' method is deprecated since version 1.27 and will be removed in 2.0. Use getSourceContext() instead.', E_USER_DEPRECATED); + + return $this->getSourceContext()->getCode(); + } + + public function getSourceContext() + { + return new Source("", "server/plugins/section_links.twig", "/var/www/html/templates/server/plugins/section_links.twig"); + } +} diff --git a/php/apps/phpmyadmin49/html/tmp/twig/ba/ba6bf0167a11e06100e6d327a30501471ad08c20d6360890b7d46506cc360763.php b/php/apps/phpmyadmin49/html/tmp/twig/ba/ba6bf0167a11e06100e6d327a30501471ad08c20d6360890b7d46506cc360763.php new file mode 100644 index 00000000..b0afa123 --- /dev/null +++ b/php/apps/phpmyadmin49/html/tmp/twig/ba/ba6bf0167a11e06100e6d327a30501471ad08c20d6360890b7d46506cc360763.php @@ -0,0 +1,71 @@ +parent = false; + + $this->blocks = [ + ]; + } + + protected function doDisplay(array $context, array $blocks = []) + { + // line 1 + echo " +"; + } + + public function getTemplateName() + { + return "javascript/display.twig"; + } + + public function isTraitable() + { + return false; + } + + public function getDebugInfo() + { + return array ( 35 => 4, 30 => 1,); + } + + /** @deprecated since 1.27 (to be removed in 2.0). Use getSourceContext() instead */ + public function getSource() + { + @trigger_error('The '.__METHOD__.' method is deprecated since version 1.27 and will be removed in 2.0. Use getSourceContext() instead.', E_USER_DEPRECATED); + + return $this->getSourceContext()->getCode(); + } + + public function getSourceContext() + { + return new Source("", "javascript/display.twig", "/var/www/html/templates/javascript/display.twig"); + } +} diff --git a/php/apps/phpmyadmin49/html/tmp/twig/c1/c19f1ab0de77c7a5fd55afb4ea44941f9f51353e5cfb717e563d9d03509b4088.php b/php/apps/phpmyadmin49/html/tmp/twig/c1/c19f1ab0de77c7a5fd55afb4ea44941f9f51353e5cfb717e563d9d03509b4088.php new file mode 100644 index 00000000..17a16257 --- /dev/null +++ b/php/apps/phpmyadmin49/html/tmp/twig/c1/c19f1ab0de77c7a5fd55afb4ea44941f9f51353e5cfb717e563d9d03509b4088.php @@ -0,0 +1,69 @@ +parent = false; + + $this->blocks = [ + ]; + } + + protected function doDisplay(array $context, array $blocks = []) + { + // line 1 + echo " + + +"; + } + + public function getTemplateName() + { + return "fk_checkbox.twig"; + } + + public function isTraitable() + { + return false; + } + + public function getDebugInfo() + { + return array ( 37 => 4, 33 => 3, 30 => 1,); + } + + /** @deprecated since 1.27 (to be removed in 2.0). Use getSourceContext() instead */ + public function getSource() + { + @trigger_error('The '.__METHOD__.' method is deprecated since version 1.27 and will be removed in 2.0. Use getSourceContext() instead.', E_USER_DEPRECATED); + + return $this->getSourceContext()->getCode(); + } + + public function getSourceContext() + { + return new Source("", "fk_checkbox.twig", "/var/www/html/templates/fk_checkbox.twig"); + } +} diff --git a/php/apps/phpmyadmin49/html/tmp/twig/c4/c467c7276bddeeb84a8f9a23ae6a77d2c1e6bc3e078e1839783a32b2d8ad16b6.php b/php/apps/phpmyadmin49/html/tmp/twig/c4/c467c7276bddeeb84a8f9a23ae6a77d2c1e6bc3e078e1839783a32b2d8ad16b6.php new file mode 100644 index 00000000..769894b5 --- /dev/null +++ b/php/apps/phpmyadmin49/html/tmp/twig/c4/c467c7276bddeeb84a8f9a23ae6a77d2c1e6bc3e078e1839783a32b2d8ad16b6.php @@ -0,0 +1,109 @@ +parent = false; + + $this->blocks = [ + ]; + } + + protected function doDisplay(array $context, array $blocks = []) + { + // line 1 + echo " +"; + } + + public function getTemplateName() + { + return "dropdown.twig"; + } + + public function isTraitable() + { + return false; + } + + public function getDebugInfo() + { + return array ( 78 => 11, 68 => 9, 64 => 8, 60 => 7, 51 => 5, 49 => 4, 47 => 3, 39 => 2, 30 => 1,); + } + + /** @deprecated since 1.27 (to be removed in 2.0). Use getSourceContext() instead */ + public function getSource() + { + @trigger_error('The '.__METHOD__.' method is deprecated since version 1.27 and will be removed in 2.0. Use getSourceContext() instead.', E_USER_DEPRECATED); + + return $this->getSourceContext()->getCode(); + } + + public function getSourceContext() + { + return new Source("", "dropdown.twig", "/var/www/html/templates/dropdown.twig"); + } +} diff --git a/php/apps/phpmyadmin49/html/tmp/twig/c6/c6dfd21e7838c3914b1cabaebfefead7f389f5578abe471e19796d7c6fb6979f.php b/php/apps/phpmyadmin49/html/tmp/twig/c6/c6dfd21e7838c3914b1cabaebfefead7f389f5578abe471e19796d7c6fb6979f.php new file mode 100644 index 00000000..e7839fc2 --- /dev/null +++ b/php/apps/phpmyadmin49/html/tmp/twig/c6/c6dfd21e7838c3914b1cabaebfefead7f389f5578abe471e19796d7c6fb6979f.php @@ -0,0 +1,112 @@ +parent = false; + + $this->blocks = [ + ]; + } + + protected function doDisplay(array $context, array $blocks = []) + { + // line 1 + echo " + + + + + + + + "; + // line 9 + $context['_parent'] = $context; + $context['_seq'] = twig_ensure_traversable(($context["engines"] ?? null)); + foreach ($context['_seq'] as $context["engine"] => $context["details"]) { + // line 10 + echo " getAttribute($context["details"], "Support", [], "array") == "NO") || ($this->getAttribute($context["details"], "Support", [], "array") == "DISABLED"))) ? (" disabled") : ("")); + echo " + "; + // line 12 + echo ((($this->getAttribute($context["details"], "Support", [], "array") == "DEFAULT")) ? (" marked") : ("")); + echo "\"> + + + + "; + } + $_parent = $context['_parent']; + unset($context['_seq'], $context['_iterated'], $context['engine'], $context['details'], $context['_parent'], $context['loop']); + $context = array_intersect_key($context, $_parent) + $_parent; + // line 21 + echo " +
      "; + // line 4 + echo _gettext("Storage Engine"); + echo ""; + // line 5 + echo _gettext("Description"); + echo "
      + $context["engine"]]); + echo "\"> + "; + // line 15 + echo twig_escape_filter($this->env, $this->getAttribute($context["details"], "Engine", [], "array"), "html", null, true); + echo " + + "; + // line 18 + echo twig_escape_filter($this->env, $this->getAttribute($context["details"], "Comment", [], "array"), "html", null, true); + echo "
      +"; + } + + public function getTemplateName() + { + return "server/engines/engines.twig"; + } + + public function isTraitable() + { + return false; + } + + public function getDebugInfo() + { + return array ( 80 => 21, 71 => 18, 65 => 15, 61 => 14, 56 => 12, 52 => 11, 50 => 10, 46 => 9, 39 => 5, 35 => 4, 30 => 1,); + } + + /** @deprecated since 1.27 (to be removed in 2.0). Use getSourceContext() instead */ + public function getSource() + { + @trigger_error('The '.__METHOD__.' method is deprecated since version 1.27 and will be removed in 2.0. Use getSourceContext() instead.', E_USER_DEPRECATED); + + return $this->getSourceContext()->getCode(); + } + + public function getSourceContext() + { + return new Source("", "server/engines/engines.twig", "/var/www/html/templates/server/engines/engines.twig"); + } +} diff --git a/php/apps/phpmyadmin49/html/tmp/twig/c7/c7d28e18d1d27b0733a19983aac95bd74b7c5e476fdd80df16d77ae72849199c.php b/php/apps/phpmyadmin49/html/tmp/twig/c7/c7d28e18d1d27b0733a19983aac95bd74b7c5e476fdd80df16d77ae72849199c.php new file mode 100644 index 00000000..fd1e9905 --- /dev/null +++ b/php/apps/phpmyadmin49/html/tmp/twig/c7/c7d28e18d1d27b0733a19983aac95bd74b7c5e476fdd80df16d77ae72849199c.php @@ -0,0 +1,88 @@ +parent = false; + + $this->blocks = [ + ]; + } + + protected function doDisplay(array $context, array $blocks = []) + { + // line 1 + echo "env, ($context["html_field_name"] ?? null), "html", null, true); + echo "\""; + // line 2 + if ((isset($context["html_field_id"]) || array_key_exists("html_field_id", $context))) { + echo " id=\""; + echo twig_escape_filter($this->env, ($context["html_field_id"] ?? null), "html", null, true); + echo "\""; + } + // line 3 + if (((isset($context["checked"]) || array_key_exists("checked", $context)) && ($context["checked"] ?? null))) { + echo " checked=\"checked\""; + } + // line 4 + if (((isset($context["onclick"]) || array_key_exists("onclick", $context)) && ($context["onclick"] ?? null))) { + echo " class=\"autosubmit\""; + } + echo " />env, ($context["html_field_id"] ?? null), "html", null, true); + echo "\""; + } + // line 6 + echo ">"; + echo twig_escape_filter($this->env, ($context["label"] ?? null), "html", null, true); + echo " +"; + } + + public function getTemplateName() + { + return "checkbox.twig"; + } + + public function isTraitable() + { + return false; + } + + public function getDebugInfo() + { + return array ( 55 => 6, 49 => 5, 44 => 4, 40 => 3, 34 => 2, 30 => 1,); + } + + /** @deprecated since 1.27 (to be removed in 2.0). Use getSourceContext() instead */ + public function getSource() + { + @trigger_error('The '.__METHOD__.' method is deprecated since version 1.27 and will be removed in 2.0. Use getSourceContext() instead.', E_USER_DEPRECATED); + + return $this->getSourceContext()->getCode(); + } + + public function getSourceContext() + { + return new Source("", "checkbox.twig", "/var/www/html/templates/checkbox.twig"); + } +} diff --git a/php/apps/phpmyadmin49/html/tmp/twig/d3/d34f9c2d0b465f52ee2949083afec2c169b9bd1f81fd5cb0918190663b75d584.php b/php/apps/phpmyadmin49/html/tmp/twig/d3/d34f9c2d0b465f52ee2949083afec2c169b9bd1f81fd5cb0918190663b75d584.php new file mode 100644 index 00000000..7b9eed89 --- /dev/null +++ b/php/apps/phpmyadmin49/html/tmp/twig/d3/d34f9c2d0b465f52ee2949083afec2c169b9bd1f81fd5cb0918190663b75d584.php @@ -0,0 +1,151 @@ +parent = false; + + $this->blocks = [ + ]; + } + + protected function doDisplay(array $context, array $blocks = []) + { + // line 1 + echo " + + "; + // line 3 + if ((($context["is_superuser"] ?? null) || ($context["allow_user_drop_database"] ?? null))) { + // line 4 + echo " + "; + } + // line 6 + echo " + + "; + // line 8 + echo _gettext("Database"); + // line 9 + echo " "; + echo (((($context["sort_by"] ?? null) == "SCHEMA_NAME")) ? (PhpMyAdmin\Util::getImage(("s_" . // line 10 +($context["sort_order"] ?? null)), // line 11 +($context["sort_order_text"] ?? null))) : ("")); + // line 12 + echo " + + + "; + // line 15 + $context['_parent'] = $context; + $context['_seq'] = twig_ensure_traversable(($context["column_order"] ?? null)); + foreach ($context['_seq'] as $context["stat_name"] => $context["stat"]) { + if (twig_in_filter($context["stat_name"], twig_get_array_keys_filter(($context["first_database"] ?? null)))) { + // line 16 + echo " "; + $context["url_params"] = twig_array_merge(($context["url_params"] ?? null), ["sort_by" => // line 17 +$context["stat_name"], "sort_order" => (((( // line 18 +($context["sort_by"] ?? null) == $context["stat_name"]) && (($context["sort_order"] ?? null) == "desc"))) ? ("asc") : ("desc"))]); + // line 20 + echo " + getAttribute($context["stat"], "format", [], "array") === "byte")) ? (" colspan=\"2\"") : ("")); + echo "> + + "; + // line 23 + echo twig_escape_filter($this->env, $this->getAttribute($context["stat"], "disp_name", [], "array"), "html", null, true); + echo " + "; + // line 24 + echo (((($context["sort_by"] ?? null) == $context["stat_name"])) ? (PhpMyAdmin\Util::getImage(("s_" . // line 25 +($context["sort_order"] ?? null)), // line 26 +($context["sort_order_text"] ?? null))) : ("")); + // line 27 + echo " + + + "; + } + } + $_parent = $context['_parent']; + unset($context['_seq'], $context['_iterated'], $context['stat_name'], $context['stat'], $context['_parent'], $context['loop']); + $context = array_intersect_key($context, $_parent) + $_parent; + // line 31 + echo " "; + if (($context["master_replication"] ?? null)) { + // line 32 + echo " "; + echo _gettext("Master replication"); + echo " + "; + } + // line 34 + echo " "; + if (($context["slave_replication"] ?? null)) { + // line 35 + echo " "; + echo _gettext("Slave replication"); + echo " + "; + } + // line 37 + echo " "; + echo _gettext("Action"); + echo " + + +"; + } + + public function getTemplateName() + { + return "server/databases/table_header.twig"; + } + + public function isTraitable() + { + return false; + } + + public function getDebugInfo() + { + return array ( 116 => 37, 110 => 35, 107 => 34, 101 => 32, 98 => 31, 88 => 27, 86 => 26, 85 => 25, 84 => 24, 80 => 23, 76 => 22, 72 => 21, 69 => 20, 67 => 18, 66 => 17, 64 => 16, 59 => 15, 54 => 12, 52 => 11, 51 => 10, 49 => 9, 47 => 8, 43 => 7, 40 => 6, 36 => 4, 34 => 3, 30 => 1,); + } + + /** @deprecated since 1.27 (to be removed in 2.0). Use getSourceContext() instead */ + public function getSource() + { + @trigger_error('The '.__METHOD__.' method is deprecated since version 1.27 and will be removed in 2.0. Use getSourceContext() instead.', E_USER_DEPRECATED); + + return $this->getSourceContext()->getCode(); + } + + public function getSourceContext() + { + return new Source("", "server/databases/table_header.twig", "/var/www/html/templates/server/databases/table_header.twig"); + } +} diff --git a/php/apps/phpmyadmin49/html/tmp/twig/d4/d47642a9cb5f30a929af567715c4f75d0b8c2e8fcc505bc01b905176bc561a1a.php b/php/apps/phpmyadmin49/html/tmp/twig/d4/d47642a9cb5f30a929af567715c4f75d0b8c2e8fcc505bc01b905176bc561a1a.php new file mode 100644 index 00000000..c7c48f74 --- /dev/null +++ b/php/apps/phpmyadmin49/html/tmp/twig/d4/d47642a9cb5f30a929af567715c4f75d0b8c2e8fcc505bc01b905176bc561a1a.php @@ -0,0 +1,148 @@ +parent = false; + + $this->blocks = [ + ]; + } + + protected function doDisplay(array $context, array $blocks = []) + { + // line 1 + echo "
      + env, preg_replace("/[^a-z]/", "", twig_lower_filter($this->env, ($context["plugin_type"] ?? null))), "html", null, true); + echo "\"> + + + + + + + + + + + + "; + // line 17 + $context['_parent'] = $context; + $context['_seq'] = twig_ensure_traversable(($context["plugin_list"] ?? null)); + foreach ($context['_seq'] as $context["_key"] => $context["plugin"]) { + // line 18 + echo " + + + + + + + "; + } + $_parent = $context['_parent']; + unset($context['_seq'], $context['_iterated'], $context['_key'], $context['plugin'], $context['_parent'], $context['loop']); + $context = array_intersect_key($context, $_parent) + $_parent; + // line 33 + echo " +
      + "; + // line 5 + echo twig_escape_filter($this->env, ($context["plugin_type"] ?? null), "html", null, true); + echo " +
      "; + // line 9 + echo _gettext("Plugin"); + echo ""; + // line 10 + echo _gettext("Description"); + echo ""; + // line 11 + echo _gettext("Version"); + echo ""; + // line 12 + echo _gettext("Author"); + echo ""; + // line 13 + echo _gettext("License"); + echo "
      + "; + // line 20 + echo twig_escape_filter($this->env, $this->getAttribute($context["plugin"], "plugin_name", [], "array"), "html", null, true); + echo " + "; + // line 21 + if ( !$this->getAttribute($context["plugin"], "is_active", [], "array")) { + // line 22 + echo " + "; + // line 23 + echo _gettext("disabled"); + // line 24 + echo " + "; + } + // line 26 + echo " "; + // line 27 + echo twig_escape_filter($this->env, $this->getAttribute($context["plugin"], "plugin_description", [], "array"), "html", null, true); + echo ""; + // line 28 + echo twig_escape_filter($this->env, $this->getAttribute($context["plugin"], "plugin_type_version", [], "array"), "html", null, true); + echo ""; + // line 29 + echo twig_escape_filter($this->env, $this->getAttribute($context["plugin"], "plugin_author", [], "array"), "html", null, true); + echo ""; + // line 30 + echo twig_escape_filter($this->env, $this->getAttribute($context["plugin"], "plugin_license", [], "array"), "html", null, true); + echo "
      +
      +"; + } + + public function getTemplateName() + { + return "server/plugins/section.twig"; + } + + public function isTraitable() + { + return false; + } + + public function getDebugInfo() + { + return array ( 115 => 33, 106 => 30, 102 => 29, 98 => 28, 94 => 27, 91 => 26, 87 => 24, 85 => 23, 82 => 22, 80 => 21, 76 => 20, 72 => 18, 68 => 17, 61 => 13, 57 => 12, 53 => 11, 49 => 10, 45 => 9, 38 => 5, 33 => 3, 30 => 1,); + } + + /** @deprecated since 1.27 (to be removed in 2.0). Use getSourceContext() instead */ + public function getSource() + { + @trigger_error('The '.__METHOD__.' method is deprecated since version 1.27 and will be removed in 2.0. Use getSourceContext() instead.', E_USER_DEPRECATED); + + return $this->getSourceContext()->getCode(); + } + + public function getSourceContext() + { + return new Source("", "server/plugins/section.twig", "/var/www/html/templates/server/plugins/section.twig"); + } +} diff --git a/php/apps/phpmyadmin49/html/tmp/twig/f0/f085eb7c57d18f26913038cc91df088c6db00b1155c4b4905fbbcd1edb04b4bd.php b/php/apps/phpmyadmin49/html/tmp/twig/f0/f085eb7c57d18f26913038cc91df088c6db00b1155c4b4905fbbcd1edb04b4bd.php new file mode 100644 index 00000000..d4e96ad8 --- /dev/null +++ b/php/apps/phpmyadmin49/html/tmp/twig/f0/f085eb7c57d18f26913038cc91df088c6db00b1155c4b4905fbbcd1edb04b4bd.php @@ -0,0 +1,84 @@ +parent = false; + + $this->blocks = [ + ]; + } + + protected function doDisplay(array $context, array $blocks = []) + { + // line 1 + if (((($context["delete_link"] ?? null) == ($context["delete_row"] ?? null)) || (($context["delete_link"] ?? null) == ($context["kill_process"] ?? null)))) { + // line 2 + echo "
      env, ($context["unique_id"] ?? null), "html", null, true); + echo "\" + class=\"ajax\"> + "; + // line 7 + echo PhpMyAdmin\Url::getHiddenInputs(($context["db"] ?? null), ($context["table"] ?? null), 1); + echo " + +"; + } + // line 10 + echo " +
      + env, ($context["unique_id"] ?? null), "html", null, true); + echo "\"> +"; + } + + public function getTemplateName() + { + return "display/results/multi_row_operations_form.twig"; + } + + public function isTraitable() + { + return false; + } + + public function getDebugInfo() + { + return array ( 52 => 12, 48 => 10, 42 => 7, 37 => 5, 32 => 2, 30 => 1,); + } + + /** @deprecated since 1.27 (to be removed in 2.0). Use getSourceContext() instead */ + public function getSource() + { + @trigger_error('The '.__METHOD__.' method is deprecated since version 1.27 and will be removed in 2.0. Use getSourceContext() instead.', E_USER_DEPRECATED); + + return $this->getSourceContext()->getCode(); + } + + public function getSourceContext() + { + return new Source("", "display/results/multi_row_operations_form.twig", "/var/www/html/templates/display/results/multi_row_operations_form.twig"); + } +} diff --git a/php/apps/phpmyadmin49/html/tmp/twig/fb/fb356887a987a6d7869296dced47e0bcb76faf8b91b547790b3fb47e95498dcb.php b/php/apps/phpmyadmin49/html/tmp/twig/fb/fb356887a987a6d7869296dced47e0bcb76faf8b91b547790b3fb47e95498dcb.php new file mode 100644 index 00000000..6eeff5af --- /dev/null +++ b/php/apps/phpmyadmin49/html/tmp/twig/fb/fb356887a987a6d7869296dced47e0bcb76faf8b91b547790b3fb47e95498dcb.php @@ -0,0 +1,56 @@ +parent = false; + + $this->blocks = [ + ]; + } + + protected function doDisplay(array $context, array $blocks = []) + { + // line 1 + echo " +"; + } + + public function getTemplateName() + { + return "config/form_display/form_bottom.twig"; + } + + public function getDebugInfo() + { + return array ( 30 => 1,); + } + + /** @deprecated since 1.27 (to be removed in 2.0). Use getSourceContext() instead */ + public function getSource() + { + @trigger_error('The '.__METHOD__.' method is deprecated since version 1.27 and will be removed in 2.0. Use getSourceContext() instead.', E_USER_DEPRECATED); + + return $this->getSourceContext()->getCode(); + } + + public function getSourceContext() + { + return new Source("", "config/form_display/form_bottom.twig", "/var/www/html/templates/config/form_display/form_bottom.twig"); + } +} diff --git a/php/apps/phpmyadmin49/html/tmp/twig/fe/fe1307971c6668f448da0328b21fac3116f226351b0ace79f8d09c1e1c1f2057.php b/php/apps/phpmyadmin49/html/tmp/twig/fe/fe1307971c6668f448da0328b21fac3116f226351b0ace79f8d09c1e1c1f2057.php new file mode 100644 index 00000000..b8743575 --- /dev/null +++ b/php/apps/phpmyadmin49/html/tmp/twig/fe/fe1307971c6668f448da0328b21fac3116f226351b0ace79f8d09c1e1c1f2057.php @@ -0,0 +1,56 @@ +parent = false; + + $this->blocks = [ + ]; + } + + protected function doDisplay(array $context, array $blocks = []) + { + // line 1 + echo " +"; + } + + public function getTemplateName() + { + return "config/form_display/tabs_bottom.twig"; + } + + public function getDebugInfo() + { + return array ( 30 => 1,); + } + + /** @deprecated since 1.27 (to be removed in 2.0). Use getSourceContext() instead */ + public function getSource() + { + @trigger_error('The '.__METHOD__.' method is deprecated since version 1.27 and will be removed in 2.0. Use getSourceContext() instead.', E_USER_DEPRECATED); + + return $this->getSourceContext()->getCode(); + } + + public function getSourceContext() + { + return new Source("", "config/form_display/tabs_bottom.twig", "/var/www/html/templates/config/form_display/tabs_bottom.twig"); + } +} diff --git a/php/apps/phpmyadmin49/html/transformation_overview.php b/php/apps/phpmyadmin49/html/transformation_overview.php new file mode 100644 index 00000000..761e5db8 --- /dev/null +++ b/php/apps/phpmyadmin49/html/transformation_overview.php @@ -0,0 +1,73 @@ +getHeader(); +$header->disableMenuAndConsole(); + +$types = Transformations::getAvailableMIMEtypes(); +?> + +

      + $mimetype) { + + if (isset($types['empty_mimetype'][$mimetype])) { + echo '' , htmlspecialchars($mimetype) , '
      '; + } else { + echo htmlspecialchars($mimetype) , '
      '; + } + +} +$transformation_types = array( + 'transformation', 'input_transformation' +); +$label = array( + 'transformation' => __('Available browser display transformations'), + 'input_transformation' => __('Available input transformations') +); +$th = array( + 'transformation' => __('Browser display transformation'), + 'input_transformation' => __('Input transformation') +); +?> +
      + + +

      +
      + + + + + + + + $transform) { + $desc = Transformations::getDescription($types[$ttype . '_file'][$key]); + ?> + + + + + + +
      + getRelationsParam(); + +/** + * Ensures db and table are valid, else moves to the "parent" script + */ +require_once './libraries/db_table_exists.inc.php'; + + +/** + * Sets globals from $_REQUEST + */ +$request_params = array( + 'cn', + 'ct', + 'sql_query', + 'transform_key', + 'where_clause' +); +$size_params = array( + 'newHeight', + 'newWidth', +); +foreach ($request_params as $one_request_param) { + if (isset($_REQUEST[$one_request_param])) { + if (in_array($one_request_param, $size_params)) { + $GLOBALS[$one_request_param] = intval($_REQUEST[$one_request_param]); + if ($GLOBALS[$one_request_param] > 2000) { + $GLOBALS[$one_request_param] = 2000; + } + } else { + $GLOBALS[$one_request_param] = $_REQUEST[$one_request_param]; + } + } +} + + +/** + * Get the list of the fields of the current table + */ +$GLOBALS['dbi']->selectDb($db); +if (isset($where_clause)) { + $result = $GLOBALS['dbi']->query( + 'SELECT * FROM ' . PhpMyAdmin\Util::backquote($table) + . ' WHERE ' . $where_clause . ';', + PhpMyAdmin\DatabaseInterface::CONNECT_USER, + PhpMyAdmin\DatabaseInterface::QUERY_STORE + ); + $row = $GLOBALS['dbi']->fetchAssoc($result); +} else { + $result = $GLOBALS['dbi']->query( + 'SELECT * FROM ' . PhpMyAdmin\Util::backquote($table) . ' LIMIT 1;', + PhpMyAdmin\DatabaseInterface::CONNECT_USER, + PhpMyAdmin\DatabaseInterface::QUERY_STORE + ); + $row = $GLOBALS['dbi']->fetchAssoc($result); +} + +// No row returned +if (! $row) { + exit; +} // end if (no record returned) + +$default_ct = 'application/octet-stream'; + +if ($cfgRelation['commwork'] && $cfgRelation['mimework']) { + $mime_map = Transformations::getMIME($db, $table); + $mime_options = Transformations::getOptions( + isset($mime_map[$transform_key]['transformation_options']) + ? $mime_map[$transform_key]['transformation_options'] : '' + ); + + foreach ($mime_options as $key => $option) { + if (substr($option, 0, 10) == '; charset=') { + $mime_options['charset'] = $option; + } + } +} + +// Only output the http headers +$response = Response::getInstance(); +$response->getHeader()->sendHttpHeaders(); + +// [MIME] +if (isset($ct) && ! empty($ct)) { + $mime_type = $ct; +} else { + $mime_type = (!empty($mime_map[$transform_key]['mimetype']) + ? str_replace('_', '/', $mime_map[$transform_key]['mimetype']) + : $default_ct) + . (isset($mime_options['charset']) ? $mime_options['charset'] : ''); +} + +Core::downloadHeader($cn, $mime_type); + +if (! isset($_REQUEST['resize'])) { + if (stripos($mime_type, 'html') === false) { + echo $row[$transform_key]; + } else { + echo htmlspecialchars($row[$transform_key]); + } +} else { + // if image_*__inline.inc.php finds that we can resize, + // it sets the resize parameter to jpeg or png + + $srcImage = imagecreatefromstring($row[$transform_key]); + $srcWidth = ImageSX($srcImage); + $srcHeight = ImageSY($srcImage); + + // Check to see if the width > height or if width < height + // if so adjust accordingly to make sure the image + // stays smaller than the new width and new height + + $ratioWidth = $srcWidth/$_REQUEST['newWidth']; + $ratioHeight = $srcHeight/$_REQUEST['newHeight']; + + if ($ratioWidth < $ratioHeight) { + $destWidth = $srcWidth/$ratioHeight; + $destHeight = $_REQUEST['newHeight']; + } else { + $destWidth = $_REQUEST['newWidth']; + $destHeight = $srcHeight/$ratioWidth; + } + + if ($_REQUEST['resize']) { + $destImage = ImageCreateTrueColor($destWidth, $destHeight); + } + + // ImageCopyResized($destImage, $srcImage, 0, 0, 0, 0, + // $destWidth, $destHeight, $srcWidth, $srcHeight); + // better quality but slower: + ImageCopyResampled( + $destImage, $srcImage, 0, 0, 0, 0, $destWidth, + $destHeight, $srcWidth, $srcHeight + ); + + if ($_REQUEST['resize'] == 'jpeg') { + ImageJPEG($destImage, null, 75); + } + if ($_REQUEST['resize'] == 'png') { + ImagePNG($destImage); + } + ImageDestroy($srcImage); + ImageDestroy($destImage); +} diff --git a/php/apps/phpmyadmin49/html/url.php b/php/apps/phpmyadmin49/html/url.php new file mode 100644 index 00000000..edaab5ac --- /dev/null +++ b/php/apps/phpmyadmin49/html/url.php @@ -0,0 +1,43 @@ +getHeader()->sendHttpHeaders(); +$response->disable(); + +if (! Core::isValid($_GET['url']) + || ! preg_match('/^https:\/\/[^\n\r]*$/', $_GET['url']) + || ! Core::isAllowedDomain($_GET['url']) +) { + Core::sendHeaderLocation('./'); +} else { + // JavaScript redirection is necessary. Because if header() is used + // then web browser sometimes does not change the HTTP_REFERER + // field and so with old URL as Referer, token also goes to + // external site. + echo ""; + // Display redirecting msg on screen. + // Do not display the value of $_GET['url'] to avoid showing injected content + echo __('Taking you to the target site.'); +} +die(); diff --git a/php/apps/phpmyadmin49/html/user_password.php b/php/apps/phpmyadmin49/html/user_password.php new file mode 100644 index 00000000..10663e93 --- /dev/null +++ b/php/apps/phpmyadmin49/html/user_password.php @@ -0,0 +1,73 @@ +getHeader(); +$scripts = $header->getScripts(); +$scripts->addFile('server_privileges.js'); +$scripts->addFile('vendor/zxcvbn.js'); + +$userPassword = new UserPassword(); + +/** + * Displays an error message and exits if the user isn't allowed to use this + * script + */ +if (! $GLOBALS['cfg']['ShowChgPassword']) { + $GLOBALS['cfg']['ShowChgPassword'] = $GLOBALS['dbi']->selectDb('mysql'); +} +if ($cfg['Server']['auth_type'] == 'config' || ! $cfg['ShowChgPassword']) { + Message::error( + __('You don\'t have sufficient privileges to be here right now!') + )->display(); + exit; +} // end if + +/** + * If the "change password" form has been submitted, checks for valid values + * and submit the query or logout + */ +if (isset($_POST['nopass'])) { + if ($_POST['nopass'] == '1') { + $password = ''; + } else { + $password = $_POST['pma_pw']; + } + $change_password_message = $userPassword->setChangePasswordMsg(); + $msg = $change_password_message['msg']; + if (! $change_password_message['error']) { + $userPassword->changePassword($password, $msg, $change_password_message); + } else { + $userPassword->getChangePassMessage($change_password_message); + } +} + +/** + * If the "change password" form hasn't been submitted or the values submitted + * aren't valid -> displays the form + */ + +// Displays an error message if required +if (isset($msg)) { + $msg->display(); + unset($msg); +} + +echo ChangePassword::getHtml('change_pw', $username, $hostname); +exit; diff --git a/php/apps/phpmyadmin49/html/version_check.php b/php/apps/phpmyadmin49/html/version_check.php new file mode 100644 index 00000000..4805e573 --- /dev/null +++ b/php/apps/phpmyadmin49/html/version_check.php @@ -0,0 +1,44 @@ +disable(); + +// Always send the correct headers +Core::headerJSON(); + +$versionInformation = new VersionInformation(); +$versionDetails = $versionInformation->getLatestVersion(); + +if (empty($versionDetails)) { + echo json_encode(array()); +} else { + $latestCompatible = $versionInformation->getLatestCompatibleVersion( + $versionDetails->releases + ); + $version = ''; + $date = ''; + if ($latestCompatible != null) { + $version = $latestCompatible['version']; + $date = $latestCompatible['date']; + } + echo json_encode( + array( + 'version' => (! empty($version) ? $version : ''), + 'date' => (! empty($date) ? $date : ''), + ) + ); +} diff --git a/php/apps/phpmyadmin49/html/view_create.php b/php/apps/phpmyadmin49/html/view_create.php new file mode 100644 index 00000000..d9dfbc20 --- /dev/null +++ b/php/apps/phpmyadmin49/html/view_create.php @@ -0,0 +1,234 @@ +addJSON( + 'message', + $message + ); + $response->setRequestStatus(false); + exit; +} + +if (isset($_POST['createview']) || isset($_POST['alterview'])) { + /** + * Creates the view + */ + $sep = "\r\n"; + + if (isset($_POST['createview'])) { + $sql_query = 'CREATE'; + if (isset($_POST['view']['or_replace'])) { + $sql_query .= ' OR REPLACE'; + } + } else { + $sql_query = 'ALTER'; + } + + if (Core::isValid($_POST['view']['algorithm'], $view_algorithm_options)) { + $sql_query .= $sep . ' ALGORITHM = ' . $_POST['view']['algorithm']; + } + + if (! empty($_POST['view']['definer'])) { + if (strpos($_POST['view']['definer'], '@') === false) { + $sql_query .= $sep . 'DEFINER=' + . PhpMyAdmin\Util::backquote($_POST['view']['definer']); + } else { + $arr = explode('@', $_POST['view']['definer']); + $sql_query .= $sep . 'DEFINER=' . PhpMyAdmin\Util::backquote($arr[0]); + $sql_query .= '@' . PhpMyAdmin\Util::backquote($arr[1]) . ' '; + } + } + + if (isset($_POST['view']['sql_security'])) { + if (in_array($_POST['view']['sql_security'], $view_security_options)) { + $sql_query .= $sep . ' SQL SECURITY ' + . $_POST['view']['sql_security']; + } + } + + $sql_query .= $sep . ' VIEW ' + . PhpMyAdmin\Util::backquote($_POST['view']['name']); + + if (! empty($_POST['view']['column_names'])) { + $sql_query .= $sep . ' (' . $_POST['view']['column_names'] . ')'; + } + + $sql_query .= $sep . ' AS ' . $_POST['view']['as']; + + if (isset($_POST['view']['with'])) { + if (in_array($_POST['view']['with'], $view_with_options)) { + $sql_query .= $sep . ' WITH ' . $_POST['view']['with'] + . ' CHECK OPTION'; + } + } + + if (!$GLOBALS['dbi']->tryQuery($sql_query)) { + if (! isset($_POST['ajax_dialog'])) { + $message = PhpMyAdmin\Message::rawError($GLOBALS['dbi']->getError()); + return; + } + + $response->addJSON( + 'message', + PhpMyAdmin\Message::error( + "" . htmlspecialchars($sql_query) . "

      " + . $GLOBALS['dbi']->getError() + ) + ); + $response->setRequestStatus(false); + exit; + } + + // If different column names defined for VIEW + $view_columns = array(); + if (isset($_POST['view']['column_names'])) { + $view_columns = explode(',', $_POST['view']['column_names']); + } + + $column_map = $GLOBALS['dbi']->getColumnMapFromSql( + $_POST['view']['as'], $view_columns + ); + + $systemDb = $GLOBALS['dbi']->getSystemDatabase(); + $pma_transformation_data = $systemDb->getExistingTransformationData( + $GLOBALS['db'] + ); + + if ($pma_transformation_data !== false) { + + // SQL for store new transformation details of VIEW + $new_transformations_sql = $systemDb->getNewTransformationDataSql( + $pma_transformation_data, $column_map, + $_POST['view']['name'], $GLOBALS['db'] + ); + + // Store new transformations + if ($new_transformations_sql != '') { + $GLOBALS['dbi']->tryQuery($new_transformations_sql); + } + + } + unset($pma_transformation_data); + + if (! isset($_POST['ajax_dialog'])) { + $message = PhpMyAdmin\Message::success(); + include 'tbl_structure.php'; + } else { + $response->addJSON( + 'message', + PhpMyAdmin\Util::getMessage( + PhpMyAdmin\Message::success(), + $sql_query + ) + ); + $response->setRequestStatus(true); + } + + exit; +} + +$sql_query = ! empty($_POST['sql_query']) ? $_POST['sql_query'] : ''; + +// prefill values if not already filled from former submission +$view = array( + 'operation' => 'create', + 'or_replace' => '', + 'algorithm' => '', + 'definer' => '', + 'sql_security' => '', + 'name' => '', + 'column_names' => '', + 'as' => $sql_query, + 'with' => '', + 'algorithm' => '', +); + +// Used to prefill the fields when editing a view +if (isset($_GET['db']) && isset($_GET['table'])) { + $item = $GLOBALS['dbi']->fetchSingleRow( + sprintf( + "SELECT `VIEW_DEFINITION`, `CHECK_OPTION`, `DEFINER`, + `SECURITY_TYPE` + FROM `INFORMATION_SCHEMA`.`VIEWS` + WHERE TABLE_SCHEMA='%s' + AND TABLE_NAME='%s';", + $GLOBALS['dbi']->escapeString($_GET['db']), + $GLOBALS['dbi']->escapeString($_GET['table']) + ) + ); + $createView = $GLOBALS['dbi']->getTable($_GET['db'], $_GET['table']) + ->showCreate(); + + // CREATE ALGORITHM= DE... + $parts = explode(" ", substr($createView, 17)); + $item['ALGORITHM'] = $parts[0]; + + $view['operation'] = 'alter'; + $view['definer'] = $item['DEFINER']; + $view['sql_security'] = $item['SECURITY_TYPE']; + $view['name'] = $_GET['table']; + $view['as'] = $item['VIEW_DEFINITION']; + $view['with'] = $item['CHECK_OPTION']; + $view['algorithm'] = $item['ALGORITHM']; + +} + +if (Core::isValid($_POST['view'], 'array')) { + $view = array_merge($view, $_POST['view']); +} + +$url_params['db'] = $GLOBALS['db']; +$url_params['reload'] = 1; + +echo Template::get('view_create')->render([ + 'ajax_dialog' => isset($_POST['ajax_dialog']), + 'text_dir' => $text_dir, + 'url_params' => $url_params, + 'view' => $view, + 'view_algorithm_options' => $view_algorithm_options, + 'view_with_options' => $view_with_options, + 'view_security_options' => $view_security_options, +]); diff --git a/php/apps/phpmyadmin49/html/view_operations.php b/php/apps/phpmyadmin49/html/view_operations.php new file mode 100644 index 00000000..5fd9ab8f --- /dev/null +++ b/php/apps/phpmyadmin49/html/view_operations.php @@ -0,0 +1,149 @@ +getHeader(); +$scripts = $header->getScripts(); +$scripts->addFile('tbl_operations.js'); + +/** + * Runs common work + */ +require './libraries/tbl_common.inc.php'; +$url_query .= '&goto=view_operations.php&back=view_operations.php'; +$url_params['goto'] = $url_params['back'] = 'view_operations.php'; + +$operations = new Operations(); + +/** + * Updates if required + */ +$_message = new Message; +$_type = 'success'; +if (isset($_POST['submitoptions'])) { + + if (isset($_POST['new_name'])) { + if ($pma_table->rename($_POST['new_name'])) { + $_message->addText($pma_table->getLastMessage()); + $result = true; + $GLOBALS['table'] = $pma_table->getName(); + /* Force reread after rename */ + $pma_table->getStatusInfo(null, true); + $reload = true; + } else { + $_message->addText($pma_table->getLastError()); + $result = false; + } + } + + $warning_messages = $operations->getWarningMessagesArray(); +} + +if (isset($result)) { + // set to success by default, because result set could be empty + // (for example, a table rename) + if (empty($_message->getString())) { + if ($result) { + $_message->addText( + __('Your SQL query has been executed successfully.') + ); + } else { + $_message->addText(__('Error')); + } + // $result should exist, regardless of $_message + $_type = $result ? 'success' : 'error'; + } + if (! empty($warning_messages)) { + $_message->addMessagesString($warning_messages); + $_message->isError(true); + unset($warning_messages); + } + echo Util::getMessage( + $_message, $sql_query, $_type + ); +} +unset($_message, $_type); + +$url_params['goto'] = 'view_operations.php'; +$url_params['back'] = 'view_operations.php'; + +/** + * Displays the page + */ +?> + +
      +
      + + +
      + + + + + + + +
      +
      +
      +
      + + +
      +
      +
      + 'DROP VIEW ' . Util::backquote( + $GLOBALS['table'] + ), + 'goto' => 'tbl_structure.php', + 'reload' => '1', + 'purge' => '1', + 'message_to_show' => sprintf( + __('View %s has been dropped.'), + htmlspecialchars($GLOBALS['table']) + ), + 'table' => $GLOBALS['table'] + ) +); +echo '
      '; +echo '
      '; +echo '' , __('Delete data or table') , ''; + +echo '
        '; +echo $operations->getDeleteDataOrTablelink( + $drop_view_url_params, + 'DROP VIEW', + __('Delete the view (DROP)'), + 'drop_view_anchor' +); +echo '
      '; +echo '
      '; +echo '
      '; diff --git a/php/apps/phpmyadmin49/html/yarn.lock b/php/apps/phpmyadmin49/html/yarn.lock new file mode 100644 index 00000000..5890527e --- /dev/null +++ b/php/apps/phpmyadmin49/html/yarn.lock @@ -0,0 +1,856 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@babel/code-frame@^7.0.0": + version "7.5.5" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.5.5.tgz#bc0782f6d69f7b7d49531219699b988f669a8f9d" + integrity sha512-27d4lZoomVyo51VegxI20xZPuSHusqbQag/ztrBC7wegWoQ1nLREPVSKSW8byhTlzTKyNE4ifaTA6lCp7JjpFw== + dependencies: + "@babel/highlight" "^7.0.0" + +"@babel/highlight@^7.0.0": + version "7.5.0" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.5.0.tgz#56d11312bd9248fa619591d02472be6e8cb32540" + integrity sha512-7dV4eu9gBxoM0dAnj/BCFDW9LFU0zvTrkq0ugM7pnHEgguOEeOz1so2ZghEdzviYzQEED0r4EAgpsBChKy1TRQ== + dependencies: + chalk "^2.0.0" + esutils "^2.0.2" + js-tokens "^4.0.0" + +acorn-jsx@^5.0.2: + version "5.0.2" + resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.0.2.tgz#84b68ea44b373c4f8686023a551f61a21b7c4a4f" + integrity sha512-tiNTrP1MP0QrChmD2DdupCr6HWSFeKVw5d/dHTu4Y7rkAkRhU/Dt7dphAfIUyxtHpl/eBVip5uTNSpQJHylpAw== + +acorn@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.0.0.tgz#26b8d1cd9a9b700350b71c0905546f64d1284e7a" + integrity sha512-PaF/MduxijYYt7unVGRuds1vBC9bFxbNf+VWqhOClfdgy7RlVkQqt610ig1/yxTgsDIfW1cWDel5EBbOy3jdtQ== + +ajv@^6.10.0, ajv@^6.10.2: + version "6.10.2" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.10.2.tgz#d3cea04d6b017b2894ad69040fec8b623eb4bd52" + integrity sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw== + dependencies: + fast-deep-equal "^2.0.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" + +ansi-escapes@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.2.0.tgz#8780b98ff9dbf5638152d1f1fe5c1d7b4442976b" + integrity sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ== + +ansi-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998" + integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg= + +ansi-regex@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997" + integrity sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg== + +ansi-styles@^3.2.0, ansi-styles@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" + integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== + dependencies: + color-convert "^1.9.0" + +argparse@^1.0.7: + version "1.0.10" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" + integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== + dependencies: + sprintf-js "~1.0.2" + +astral-regex@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-1.0.0.tgz#6c8c3fb827dd43ee3918f27b82782ab7658a6fd9" + integrity sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg== + +balanced-match@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" + integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +callsites@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" + integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== + +chalk@^2.0.0, chalk@^2.1.0, chalk@^2.4.2: + version "2.4.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" + integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== + dependencies: + ansi-styles "^3.2.1" + escape-string-regexp "^1.0.5" + supports-color "^5.3.0" + +chardet@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e" + integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA== + +cli-cursor@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-2.1.0.tgz#b35dac376479facc3e94747d41d0d0f5238ffcb5" + integrity sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU= + dependencies: + restore-cursor "^2.0.0" + +cli-width@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.0.tgz#ff19ede8a9a5e579324147b0c11f0fbcbabed639" + integrity sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk= + +codemirror@5.48.4: + version "5.48.4" + resolved "https://registry.yarnpkg.com/codemirror/-/codemirror-5.48.4.tgz#4210fbe92be79a88f0eea348fab3ae78da85ce47" + integrity sha512-pUhZXDQ6qXSpWdwlgAwHEkd4imA0kf83hINmUEzJpmG80T/XLtDDEzZo8f6PQLuRCcUQhmzqqIo3ZPTRaWByRA== + +color-convert@^1.9.0: + version "1.9.3" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" + integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== + dependencies: + color-name "1.1.3" + +color-name@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" + integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= + +cross-spawn@^6.0.5: + version "6.0.5" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" + integrity sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ== + dependencies: + nice-try "^1.0.4" + path-key "^2.0.1" + semver "^5.5.0" + shebang-command "^1.2.0" + which "^1.2.9" + +debug@^4.0.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791" + integrity sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw== + dependencies: + ms "^2.1.1" + +deep-is@~0.1.3: + version "0.1.3" + resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" + integrity sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ= + +doctrine@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961" + integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w== + dependencies: + esutils "^2.0.2" + +emoji-regex@^7.0.1: + version "7.0.3" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156" + integrity sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA== + +escape-string-regexp@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= + +eslint-scope@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.0.0.tgz#e87c8887c73e8d1ec84f1ca591645c358bfc8fb9" + integrity sha512-oYrhJW7S0bxAFDvWqzvMPRm6pcgcnWc4QnofCAqRTRfQC0JcwenzGglTtsLyIuuWFfkqDG9vz67cnttSd53djw== + dependencies: + esrecurse "^4.1.0" + estraverse "^4.1.1" + +eslint-utils@^1.4.2: + version "1.4.2" + resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-1.4.2.tgz#166a5180ef6ab7eb462f162fd0e6f2463d7309ab" + integrity sha512-eAZS2sEUMlIeCjBeubdj45dmBHQwPHWyBcT1VSYB7o9x9WRRqKxyUoiXlRjyAwzN7YEzHJlYg0NmzDRWx6GP4Q== + dependencies: + eslint-visitor-keys "^1.0.0" + +eslint-visitor-keys@^1.0.0, eslint-visitor-keys@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.1.0.tgz#e2a82cea84ff246ad6fb57f9bde5b46621459ec2" + integrity sha512-8y9YjtM1JBJU/A9Kc+SbaOV4y29sSWckBwMHa+FGtVj5gN/sbnKDf6xJUl+8g7FAij9LVaP8C24DUiH/f/2Z9A== + +eslint@^6.2.2: + version "6.2.2" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-6.2.2.tgz#03298280e7750d81fcd31431f3d333e43d93f24f" + integrity sha512-mf0elOkxHbdyGX1IJEUsNBzCDdyoUgljF3rRlgfyYh0pwGnreLc0jjD6ZuleOibjmnUWZLY2eXwSooeOgGJ2jw== + dependencies: + "@babel/code-frame" "^7.0.0" + ajv "^6.10.0" + chalk "^2.1.0" + cross-spawn "^6.0.5" + debug "^4.0.1" + doctrine "^3.0.0" + eslint-scope "^5.0.0" + eslint-utils "^1.4.2" + eslint-visitor-keys "^1.1.0" + espree "^6.1.1" + esquery "^1.0.1" + esutils "^2.0.2" + file-entry-cache "^5.0.1" + functional-red-black-tree "^1.0.1" + glob-parent "^5.0.0" + globals "^11.7.0" + ignore "^4.0.6" + import-fresh "^3.0.0" + imurmurhash "^0.1.4" + inquirer "^6.4.1" + is-glob "^4.0.0" + js-yaml "^3.13.1" + json-stable-stringify-without-jsonify "^1.0.1" + levn "^0.3.0" + lodash "^4.17.14" + minimatch "^3.0.4" + mkdirp "^0.5.1" + natural-compare "^1.4.0" + optionator "^0.8.2" + progress "^2.0.0" + regexpp "^2.0.1" + semver "^6.1.2" + strip-ansi "^5.2.0" + strip-json-comments "^3.0.1" + table "^5.2.3" + text-table "^0.2.0" + v8-compile-cache "^2.0.3" + +espree@^6.1.1: + version "6.1.1" + resolved "https://registry.yarnpkg.com/espree/-/espree-6.1.1.tgz#7f80e5f7257fc47db450022d723e356daeb1e5de" + integrity sha512-EYbr8XZUhWbYCqQRW0duU5LxzL5bETN6AjKBGy1302qqzPaCH10QbRg3Wvco79Z8x9WbiE8HYB4e75xl6qUYvQ== + dependencies: + acorn "^7.0.0" + acorn-jsx "^5.0.2" + eslint-visitor-keys "^1.1.0" + +esprima@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" + integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== + +esquery@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.0.1.tgz#406c51658b1f5991a5f9b62b1dc25b00e3e5c708" + integrity sha512-SmiyZ5zIWH9VM+SRUReLS5Q8a7GxtRdxEBVZpm98rJM7Sb+A9DVCndXfkeFUd3byderg+EbDkfnevfCwynWaNA== + dependencies: + estraverse "^4.0.0" + +esrecurse@^4.1.0: + version "4.2.1" + resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.2.1.tgz#007a3b9fdbc2b3bb87e4879ea19c92fdbd3942cf" + integrity sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ== + dependencies: + estraverse "^4.1.0" + +estraverse@^4.0.0, estraverse@^4.1.0, estraverse@^4.1.1: + version "4.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" + integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== + +esutils@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" + integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== + +external-editor@^3.0.3: + version "3.1.0" + resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-3.1.0.tgz#cb03f740befae03ea4d283caed2741a83f335495" + integrity sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew== + dependencies: + chardet "^0.7.0" + iconv-lite "^0.4.24" + tmp "^0.0.33" + +fast-deep-equal@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz#7b05218ddf9667bf7f370bf7fdb2cb15fdd0aa49" + integrity sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk= + +fast-json-stable-stringify@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz#d5142c0caee6b1189f87d3a76111064f86c8bbf2" + integrity sha1-1RQsDK7msRifh9OnYREGT4bIu/I= + +fast-levenshtein@~2.0.4: + version "2.0.6" + resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" + integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= + +figures@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/figures/-/figures-2.0.0.tgz#3ab1a2d2a62c8bfb431a0c94cb797a2fce27c962" + integrity sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI= + dependencies: + escape-string-regexp "^1.0.5" + +file-entry-cache@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-5.0.1.tgz#ca0f6efa6dd3d561333fb14515065c2fafdf439c" + integrity sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g== + dependencies: + flat-cache "^2.0.1" + +flat-cache@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-2.0.1.tgz#5d296d6f04bda44a4630a301413bdbc2ec085ec0" + integrity sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA== + dependencies: + flatted "^2.0.0" + rimraf "2.6.3" + write "1.0.3" + +flatted@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-2.0.1.tgz#69e57caa8f0eacbc281d2e2cb458d46fdb449e08" + integrity sha512-a1hQMktqW9Nmqr5aktAux3JMNqaucxGcjtjWnZLHX7yyPCmlSV3M54nGYbqT8K+0GhF3NBgmJCc3ma+WOgX8Jg== + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= + +functional-red-black-tree@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" + integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc= + +glob-parent@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.0.0.tgz#1dc99f0f39b006d3e92c2c284068382f0c20e954" + integrity sha512-Z2RwiujPRGluePM6j699ktJYxmPpJKCfpGA13jz2hmFZC7gKetzrWvg5KN3+OsIFmydGyZ1AVwERCq1w/ZZwRg== + dependencies: + is-glob "^4.0.1" + +glob@^7.1.3: + version "7.1.4" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.4.tgz#aa608a2f6c577ad357e1ae5a5c26d9a8d1969255" + integrity sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + +globals@^11.7.0: + version "11.12.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" + integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== + +has-flag@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" + integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= + +iconv-lite@^0.4.24: + version "0.4.24" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" + integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== + dependencies: + safer-buffer ">= 2.1.2 < 3" + +ignore@^4.0.6: + version "4.0.6" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc" + integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg== + +import-fresh@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.1.0.tgz#6d33fa1dcef6df930fae003446f33415af905118" + integrity sha512-PpuksHKGt8rXfWEr9m9EHIpgyyaltBy8+eF6GJM0QCAxMgxCfucMF3mjecK2QsJr0amJW7gTqh5/wht0z2UhEQ== + dependencies: + parent-module "^1.0.0" + resolve-from "^4.0.0" + +imurmurhash@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" + integrity sha1-khi5srkoojixPcT7a21XbyMUU+o= + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +inquirer@^6.4.1: + version "6.5.2" + resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-6.5.2.tgz#ad50942375d036d327ff528c08bd5fab089928ca" + integrity sha512-cntlB5ghuB0iuO65Ovoi8ogLHiWGs/5yNrtUcKjFhSSiVeAIVpD7koaSU9RM8mpXw5YDi9RdYXGQMaOURB7ycQ== + dependencies: + ansi-escapes "^3.2.0" + chalk "^2.4.2" + cli-cursor "^2.1.0" + cli-width "^2.0.0" + external-editor "^3.0.3" + figures "^2.0.0" + lodash "^4.17.12" + mute-stream "0.0.7" + run-async "^2.2.0" + rxjs "^6.4.0" + string-width "^2.1.0" + strip-ansi "^5.1.0" + through "^2.3.6" + +is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= + +is-fullwidth-code-point@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" + integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8= + +is-glob@^4.0.0, is-glob@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc" + integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg== + dependencies: + is-extglob "^2.1.1" + +is-promise@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.1.0.tgz#79a2a9ece7f096e80f36d2b2f3bc16c1ff4bf3fa" + integrity sha1-eaKp7OfwlugPNtKy87wWwf9L8/o= + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= + +jquery-migrate@3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/jquery-migrate/-/jquery-migrate-3.1.0.tgz#8158de74cca39fb7fc2635db570151ae7ce3ddea" + integrity sha512-u/MtE1ST2pCr3rCyouJG2xMiw/k3OzLNeRKprjKTeHUezCGr0DyEgeXFdqFLmQfxfR5EsVu+mGo/sCcYdiYcIQ== + +jquery-mousewheel@3.1.13: + version "3.1.13" + resolved "https://registry.yarnpkg.com/jquery-mousewheel/-/jquery-mousewheel-3.1.13.tgz#06f0335f16e353a695e7206bf50503cb523a6ee5" + integrity sha1-BvAzXxbjU6aV5yBr9QUDy1I6buU= + +jquery-ui-timepicker-addon@1.6.3: + version "1.6.3" + resolved "https://registry.yarnpkg.com/jquery-ui-timepicker-addon/-/jquery-ui-timepicker-addon-1.6.3.tgz#8037c39b0b630282dd0b37dd8ad7fc5e1163377f" + integrity sha1-gDfDmwtjAoLdCzfditf8XhFjN38= + +jquery-ui@1.12.1: + version "1.12.1" + resolved "https://registry.yarnpkg.com/jquery-ui/-/jquery-ui-1.12.1.tgz#bcb4045c8dd0539c134bc1488cdd3e768a7a9e51" + integrity sha1-vLQEXI3QU5wTS8FIjN0+dop6nlE= + +jquery-validation@1.19.1: + version "1.19.1" + resolved "https://registry.yarnpkg.com/jquery-validation/-/jquery-validation-1.19.1.tgz#a85043467dc2b70d9fff05778646d150e747742f" + integrity sha512-QNnrZBqSltWUEJx+shOY5WtfrIb0gWmDjFfQP8rZKqMMSfpRSwEkSqhfHPvDfkObD8Hnv5KHSYI8yg73sVFdqA== + +jquery.event.drag@2.2.2: + version "2.2.2" + resolved "https://registry.yarnpkg.com/jquery.event.drag/-/jquery.event.drag-2.2.2.tgz#11bbbf83f4c6ef5f3b5065564663913c9f964be1" + integrity sha1-Ebu/g/TG7187UGVWRmORPJ+WS+E= + +jquery@3.4.1: + version "3.4.1" + resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.4.1.tgz#714f1f8d9dde4bdfa55764ba37ef214630d80ef2" + integrity sha512-36+AdBzCL+y6qjw5Tx7HgzeGCzC81MDDgaUP8ld2zhx58HdqXGoBd+tHdrBMiyjGQs0Hxs/MLZTu/eHNJJuWPw== + +js-cookie@2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/js-cookie/-/js-cookie-2.2.1.tgz#69e106dc5d5806894562902aa5baec3744e9b2b8" + integrity sha512-HvdH2LzI/EAZcUwA8+0nKNtWHqS+ZmijLA30RwZA0bo7ToCckjK5MkGhjED9KoRcXO6BaGI3I9UIzSA1FKFPOQ== + +js-tokens@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + +js-yaml@^3.13.1: + version "3.13.1" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.13.1.tgz#aff151b30bfdfa8e49e05da22e7415e9dfa37847" + integrity sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw== + dependencies: + argparse "^1.0.7" + esprima "^4.0.0" + +json-schema-traverse@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" + integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== + +json-stable-stringify-without-jsonify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" + integrity sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE= + +levn@^0.3.0, levn@~0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee" + integrity sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4= + dependencies: + prelude-ls "~1.1.2" + type-check "~0.3.2" + +lodash@^4.17.12, lodash@^4.17.14: + version "4.17.15" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548" + integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A== + +mimic-fn@^1.0.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.2.0.tgz#820c86a39334640e99516928bd03fca88057d022" + integrity sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ== + +minimatch@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" + integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== + dependencies: + brace-expansion "^1.1.7" + +minimist@0.0.8: + version "0.0.8" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" + integrity sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0= + +mkdirp@^0.5.1: + version "0.5.1" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" + integrity sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM= + dependencies: + minimist "0.0.8" + +ms@^2.1.1: + version "2.1.2" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== + +mute-stream@0.0.7: + version "0.0.7" + resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab" + integrity sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s= + +natural-compare@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" + integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc= + +nice-try@^1.0.4: + version "1.0.5" + resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" + integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== + +once@^1.3.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= + dependencies: + wrappy "1" + +onetime@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-2.0.1.tgz#067428230fd67443b2794b22bba528b6867962d4" + integrity sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ= + dependencies: + mimic-fn "^1.0.0" + +optionator@^0.8.2: + version "0.8.2" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.2.tgz#364c5e409d3f4d6301d6c0b4c05bba50180aeb64" + integrity sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q= + dependencies: + deep-is "~0.1.3" + fast-levenshtein "~2.0.4" + levn "~0.3.0" + prelude-ls "~1.1.2" + type-check "~0.3.2" + wordwrap "~1.0.0" + +os-tmpdir@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" + integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ= + +parent-module@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" + integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== + dependencies: + callsites "^3.0.0" + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= + +path-key@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" + integrity sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A= + +prelude-ls@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" + integrity sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ= + +progress@^2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" + integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== + +punycode@^2.1.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" + integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== + +regexpp@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-2.0.1.tgz#8d19d31cf632482b589049f8281f93dbcba4d07f" + integrity sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw== + +resolve-from@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" + integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== + +restore-cursor@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-2.0.0.tgz#9f7ee287f82fd326d4fd162923d62129eee0dfaf" + integrity sha1-n37ih/gv0ybU/RYpI9YhKe7g368= + dependencies: + onetime "^2.0.0" + signal-exit "^3.0.2" + +rimraf@2.6.3: + version "2.6.3" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab" + integrity sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA== + dependencies: + glob "^7.1.3" + +run-async@^2.2.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.3.0.tgz#0371ab4ae0bdd720d4166d7dfda64ff7a445a6c0" + integrity sha1-A3GrSuC91yDUFm19/aZP96RFpsA= + dependencies: + is-promise "^2.1.0" + +rxjs@^6.4.0: + version "6.5.2" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.5.2.tgz#2e35ce815cd46d84d02a209fb4e5921e051dbec7" + integrity sha512-HUb7j3kvb7p7eCUHE3FqjoDsC1xfZQ4AHFWfTKSpZ+sAhhz5X1WX0ZuUqWbzB2QhSLp3DoLUG+hMdEDKqWo2Zg== + dependencies: + tslib "^1.9.0" + +"safer-buffer@>= 2.1.2 < 3": + version "2.1.2" + resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" + integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== + +semver@^5.5.0: + version "5.7.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" + integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== + +semver@^6.1.2: + version "6.3.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" + integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== + +shebang-command@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" + integrity sha1-RKrGW2lbAzmJaMOfNj/uXer98eo= + dependencies: + shebang-regex "^1.0.0" + +shebang-regex@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" + integrity sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM= + +signal-exit@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d" + integrity sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0= + +slice-ansi@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-2.1.0.tgz#cacd7693461a637a5788d92a7dd4fba068e81636" + integrity sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ== + dependencies: + ansi-styles "^3.2.0" + astral-regex "^1.0.0" + is-fullwidth-code-point "^2.0.0" + +sprintf-js@~1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" + integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= + +string-width@^2.1.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" + integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw== + dependencies: + is-fullwidth-code-point "^2.0.0" + strip-ansi "^4.0.0" + +string-width@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-3.1.0.tgz#22767be21b62af1081574306f69ac51b62203961" + integrity sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w== + dependencies: + emoji-regex "^7.0.1" + is-fullwidth-code-point "^2.0.0" + strip-ansi "^5.1.0" + +strip-ansi@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f" + integrity sha1-qEeQIusaw2iocTibY1JixQXuNo8= + dependencies: + ansi-regex "^3.0.0" + +strip-ansi@^5.1.0, strip-ansi@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae" + integrity sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA== + dependencies: + ansi-regex "^4.1.0" + +strip-json-comments@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.0.1.tgz#85713975a91fb87bf1b305cca77395e40d2a64a7" + integrity sha512-VTyMAUfdm047mwKl+u79WIdrZxtFtn+nBxHeb844XBQ9uMNTuTHdx2hc5RiAJYqwTj3wc/xe5HLSdJSkJ+WfZw== + +supports-color@^5.3.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" + integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== + dependencies: + has-flag "^3.0.0" + +table@^5.2.3: + version "5.4.6" + resolved "https://registry.yarnpkg.com/table/-/table-5.4.6.tgz#1292d19500ce3f86053b05f0e8e7e4a3bb21079e" + integrity sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug== + dependencies: + ajv "^6.10.2" + lodash "^4.17.14" + slice-ansi "^2.1.0" + string-width "^3.0.0" + +text-table@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" + integrity sha1-f17oI66AUgfACvLfSoTsP8+lcLQ= + +through@^2.3.6: + version "2.3.8" + resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" + integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU= + +tmp@^0.0.33: + version "0.0.33" + resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" + integrity sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw== + dependencies: + os-tmpdir "~1.0.2" + +tracekit@0.4.5: + version "0.4.5" + resolved "https://registry.yarnpkg.com/tracekit/-/tracekit-0.4.5.tgz#55d40ea76ddf2bde137d9aaeb94933b5e94c5991" + integrity sha512-LAb1udnpvhpgcx6/gmv7s6RO5lBwQGgAT/1VW0egSNSMvH/3xU3xKLoJ3pc+nkJ5AMv9qgTBnCkrUzbrHmCLpg== + +tslib@^1.9.0: + version "1.10.0" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.10.0.tgz#c3c19f95973fb0a62973fb09d90d961ee43e5c8a" + integrity sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ== + +type-check@~0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72" + integrity sha1-WITKtRLPHTVeP7eE8wgEsrUg23I= + dependencies: + prelude-ls "~1.1.2" + +updated-jqplot@1.0.9: + version "1.0.9" + resolved "https://registry.yarnpkg.com/updated-jqplot/-/updated-jqplot-1.0.9.tgz#3daeda1a74ea991256749364dc9225af038280b7" + integrity sha1-Pa7aGnTqmRJWdJNk3JIlrwOCgLc= + +uri-js@^4.2.2: + version "4.2.2" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.2.2.tgz#94c540e1ff772956e2299507c010aea6c8838eb0" + integrity sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ== + dependencies: + punycode "^2.1.0" + +v8-compile-cache@^2.0.3: + version "2.1.0" + resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.1.0.tgz#e14de37b31a6d194f5690d67efc4e7f6fc6ab30e" + integrity sha512-usZBT3PW+LOjM25wbqIlZwPeJV+3OSz3M1k1Ws8snlW39dZyYL9lOGC5FgPVHfk0jKmjiDV8Z0mIbVQPiwFs7g== + +which@^1.2.9: + version "1.3.1" + resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" + integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== + dependencies: + isexe "^2.0.0" + +wordwrap@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" + integrity sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus= + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= + +write@1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/write/-/write-1.0.3.tgz#0800e14523b923a387e415123c865616aae0f5c3" + integrity sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig== + dependencies: + mkdirp "^0.5.1" + +zxcvbn@4.4.2: + version "4.4.2" + resolved "https://registry.yarnpkg.com/zxcvbn/-/zxcvbn-4.4.2.tgz#28ec17cf09743edcab056ddd8b1b06262cc73c30" + integrity sha1-KOwXzwl0PtyrBW3dixsGJizHPDA= diff --git a/php/apps/phpmyadmin49/import.php b/php/apps/phpmyadmin49/import.php new file mode 100644 index 00000000..7bd3b8f2 --- /dev/null +++ b/php/apps/phpmyadmin49/import.php @@ -0,0 +1,784 @@ +addJSON( + 'console_message_bookmark', PhpMyAdmin\Console::getBookmarkContent() + ); + exit; +} +// If it's a console bookmark add request +if (isset($_POST['console_bookmark_add'])) { + if (isset($_POST['label']) && isset($_POST['db']) + && isset($_POST['bookmark_query']) && isset($_POST['shared']) + ) { + $cfgBookmark = Bookmark::getParams($GLOBALS['cfg']['Server']['user']); + $bookmarkFields = array( + 'bkm_database' => $_POST['db'], + 'bkm_user' => $cfgBookmark['user'], + 'bkm_sql_query' => $_POST['bookmark_query'], + 'bkm_label' => $_POST['label'] + ); + $isShared = ($_POST['shared'] == 'true' ? true : false); + $bookmark = Bookmark::createBookmark( + $GLOBALS['dbi'], + $GLOBALS['cfg']['Server']['user'], + $bookmarkFields, + $isShared + ); + if ($bookmark !== false && $bookmark->save()) { + $response->addJSON('message', __('Succeeded')); + $response->addJSON('data', $bookmarkFields); + $response->addJSON('isShared', $isShared); + } else { + $response->addJSON('message', __('Failed')); + } + die(); + } else { + $response->addJSON('message', __('Incomplete params')); + die(); + } +} + +$format = ''; + +/** + * Sets globals from $_POST + */ +$post_params = array( + 'charset_of_file', + 'format', + 'import_type', + 'is_js_confirmed', + 'MAX_FILE_SIZE', + 'message_to_show', + 'noplugin', + 'skip_queries', + 'local_import_file' +); + +foreach ($post_params as $one_post_param) { + if (isset($_POST[$one_post_param])) { + $GLOBALS[$one_post_param] = $_POST[$one_post_param]; + } +} + +// reset import messages for ajax request +$_SESSION['Import_message']['message'] = null; +$_SESSION['Import_message']['go_back_url'] = null; +// default values +$GLOBALS['reload'] = false; + +// Use to identify current cycle is executing +// a multiquery statement or stored routine +if (!isset($_SESSION['is_multi_query'])) { + $_SESSION['is_multi_query'] = false; +} + +$ajax_reload = array(); +// Are we just executing plain query or sql file? +// (eg. non import, but query box/window run) +if (! empty($sql_query)) { + + // apply values for parameters + if (! empty($_POST['parameterized']) + && ! empty($_POST['parameters']) + && is_array($_POST['parameters']) + ) { + $parameters = $_POST['parameters']; + foreach ($parameters as $parameter => $replacement) { + $quoted = preg_quote($parameter, '/'); + // making sure that :param does not apply values to :param1 + $sql_query = preg_replace( + '/' . $quoted . '([^a-zA-Z0-9_])/', + $GLOBALS['dbi']->escapeString($replacement) . '${1}', + $sql_query + ); + // for parameters the appear at the end of the string + $sql_query = preg_replace( + '/' . $quoted . '$/', + $GLOBALS['dbi']->escapeString($replacement), + $sql_query + ); + } + } + + // run SQL query + $import_text = $sql_query; + $import_type = 'query'; + $format = 'sql'; + $_SESSION['sql_from_query_box'] = true; + + // If there is a request to ROLLBACK when finished. + if (isset($_POST['rollback_query'])) { + Import::handleRollbackRequest($import_text); + } + + // refresh navigation and main panels + if (preg_match('/^(DROP)\s+(VIEW|TABLE|DATABASE|SCHEMA)\s+/i', $sql_query)) { + $GLOBALS['reload'] = true; + $ajax_reload['reload'] = true; + } + + // refresh navigation panel only + if (preg_match( + '/^(CREATE|ALTER)\s+(VIEW|TABLE|DATABASE|SCHEMA)\s+/i', + $sql_query + )) { + $ajax_reload['reload'] = true; + } + + // do a dynamic reload if table is RENAMED + // (by sending the instruction to the AJAX response handler) + if (preg_match( + '/^RENAME\s+TABLE\s+(.*?)\s+TO\s+(.*?)($|;|\s)/i', + $sql_query, + $rename_table_names + )) { + $ajax_reload['reload'] = true; + $ajax_reload['table_name'] = PhpMyAdmin\Util::unQuote( + $rename_table_names[2] + ); + } + + $sql_query = ''; +} elseif (! empty($sql_file)) { + // run uploaded SQL file + $import_file = $sql_file; + $import_type = 'queryfile'; + $format = 'sql'; + unset($sql_file); +} elseif (! empty($_POST['id_bookmark'])) { + // run bookmark + $import_type = 'query'; + $format = 'sql'; +} + +// If we didn't get any parameters, either user called this directly, or +// upload limit has been reached, let's assume the second possibility. +if ($_POST == array() && $_GET == array()) { + $message = PhpMyAdmin\Message::error( + __( + 'You probably tried to upload a file that is too large. Please refer ' . + 'to %sdocumentation%s for a workaround for this limit.' + ) + ); + $message->addParam('[doc@faq1-16]'); + $message->addParam('[/doc]'); + + // so we can obtain the message + $_SESSION['Import_message']['message'] = $message->getDisplay(); + $_SESSION['Import_message']['go_back_url'] = $GLOBALS['goto']; + + $response->setRequestStatus(false); + $response->addJSON('message', $message); + + exit; // the footer is displayed automatically +} + +// Add console message id to response output +if (isset($_POST['console_message_id'])) { + $response->addJSON('console_message_id', $_POST['console_message_id']); +} + +/** + * Sets globals from $_POST patterns, for import plugins + * We only need to load the selected plugin + */ + +if (! in_array( + $format, + array( + 'csv', + 'ldi', + 'mediawiki', + 'ods', + 'shp', + 'sql', + 'xml' + ) +) +) { + // this should not happen for a normal user + // but only during an attack + Core::fatalError('Incorrect format parameter'); +} + +$post_patterns = array( + '/^force_file_/', + '/^' . $format . '_/' +); + +Core::setPostAsGlobal($post_patterns); + +// Check needed parameters +PhpMyAdmin\Util::checkParameters(array('import_type', 'format')); + +// We don't want anything special in format +$format = Core::securePath($format); + +if (strlen($table) > 0 && strlen($db) > 0) { + $urlparams = array('db' => $db, 'table' => $table); +} elseif (strlen($db) > 0) { + $urlparams = array('db' => $db); +} else { + $urlparams = array(); +} + +// Create error and goto url +if ($import_type == 'table') { + $goto = 'tbl_import.php'; +} elseif ($import_type == 'database') { + $goto = 'db_import.php'; +} elseif ($import_type == 'server') { + $goto = 'server_import.php'; +} else { + if (empty($goto) || !preg_match('@^(server|db|tbl)(_[a-z]*)*\.php$@i', $goto)) { + if (strlen($table) > 0 && strlen($db) > 0) { + $goto = 'tbl_structure.php'; + } elseif (strlen($db) > 0) { + $goto = 'db_structure.php'; + } else { + $goto = 'server_sql.php'; + } + } +} +$err_url = $goto . Url::getCommon($urlparams); +$_SESSION['Import_message']['go_back_url'] = $err_url; +// Avoid setting selflink to 'import.php' +// problem similar to bug 4276 +if (basename($_SERVER['SCRIPT_NAME']) === 'import.php') { + $_SERVER['SCRIPT_NAME'] = $goto; +} + + +if (strlen($db) > 0) { + $GLOBALS['dbi']->selectDb($db); +} + +Util::setTimeLimit(); +if (! empty($cfg['MemoryLimit'])) { + ini_set('memory_limit', $cfg['MemoryLimit']); +} + +$timestamp = time(); +if (isset($_POST['allow_interrupt'])) { + $maximum_time = ini_get('max_execution_time'); +} else { + $maximum_time = 0; +} + +// set default values +$timeout_passed = false; +$error = false; +$read_multiply = 1; +$finished = false; +$offset = 0; +$max_sql_len = 0; +$file_to_unlink = ''; +$sql_query = ''; +$sql_query_disabled = false; +$go_sql = false; +$executed_queries = 0; +$run_query = true; +$charset_conversion = false; +$reset_charset = false; +$bookmark_created = false; +$result = false; +$msg = 'Sorry an unexpected error happened!'; + +// Bookmark Support: get a query back from bookmark if required +if (! empty($_POST['id_bookmark'])) { + $id_bookmark = (int)$_POST['id_bookmark']; + switch ($_POST['action_bookmark']) { + case 0: // bookmarked query that have to be run + $bookmark = Bookmark::get( + $GLOBALS['dbi'], + $GLOBALS['cfg']['Server']['user'], + $db, + $id_bookmark, + 'id', + isset($_POST['action_bookmark_all']) + ); + + if (! empty($_POST['bookmark_variable'])) { + $import_text = $bookmark->applyVariables( + $_POST['bookmark_variable'] + ); + } else { + $import_text = $bookmark->getQuery(); + } + + // refresh navigation and main panels + if (preg_match( + '/^(DROP)\s+(VIEW|TABLE|DATABASE|SCHEMA)\s+/i', + $import_text + )) { + $GLOBALS['reload'] = true; + $ajax_reload['reload'] = true; + } + + // refresh navigation panel only + if (preg_match( + '/^(CREATE|ALTER)\s+(VIEW|TABLE|DATABASE|SCHEMA)\s+/i', + $import_text + ) + ) { + $ajax_reload['reload'] = true; + } + break; + case 1: // bookmarked query that have to be displayed + $bookmark = Bookmark::get( + $GLOBALS['dbi'], + $GLOBALS['cfg']['Server']['user'], + $db, + $id_bookmark + ); + $import_text = $bookmark->getQuery(); + if ($response->isAjax()) { + $message = PhpMyAdmin\Message::success(__('Showing bookmark')); + $response->setRequestStatus($message->isSuccess()); + $response->addJSON('message', $message); + $response->addJSON('sql_query', $import_text); + $response->addJSON('action_bookmark', $_POST['action_bookmark']); + exit; + } else { + $run_query = false; + } + break; + case 2: // bookmarked query that have to be deleted + $bookmark = Bookmark::get( + $GLOBALS['dbi'], + $GLOBALS['cfg']['Server']['user'], + $db, + $id_bookmark + ); + if (! empty($bookmark)) { + $bookmark->delete(); + if ($response->isAjax()) { + $message = PhpMyAdmin\Message::success( + __('The bookmark has been deleted.') + ); + $response->setRequestStatus($message->isSuccess()); + $response->addJSON('message', $message); + $response->addJSON('action_bookmark', $_POST['action_bookmark']); + $response->addJSON('id_bookmark', $id_bookmark); + exit; + } else { + $run_query = false; + $error = true; // this is kind of hack to skip processing the query + } + } + + break; + } +} // end bookmarks reading + +// Do no run query if we show PHP code +if (isset($GLOBALS['show_as_php'])) { + $run_query = false; + $go_sql = true; +} + +// We can not read all at once, otherwise we can run out of memory +$memory_limit = trim(ini_get('memory_limit')); +// 2 MB as default +if (empty($memory_limit)) { + $memory_limit = 2 * 1024 * 1024; +} +// In case no memory limit we work on 10MB chunks +if ($memory_limit == -1) { + $memory_limit = 10 * 1024 * 1024; +} + +// Calculate value of the limit +$memoryUnit = mb_strtolower(substr($memory_limit, -1)); +if ('m' == $memoryUnit) { + $memory_limit = (int)substr($memory_limit, 0, -1) * 1024 * 1024; +} elseif ('k' == $memoryUnit) { + $memory_limit = (int)substr($memory_limit, 0, -1) * 1024; +} elseif ('g' == $memoryUnit) { + $memory_limit = (int)substr($memory_limit, 0, -1) * 1024 * 1024 * 1024; +} else { + $memory_limit = (int)$memory_limit; +} + +// Just to be sure, there might be lot of memory needed for uncompression +$read_limit = $memory_limit / 8; + +// handle filenames +if (isset($_FILES['import_file'])) { + $import_file = $_FILES['import_file']['tmp_name']; +} +if (! empty($local_import_file) && ! empty($cfg['UploadDir'])) { + + // sanitize $local_import_file as it comes from a POST + $local_import_file = Core::securePath($local_import_file); + + $import_file = PhpMyAdmin\Util::userDir($cfg['UploadDir']) + . $local_import_file; + + /* + * Do not allow symlinks to avoid security issues + * (user can create symlink to file he can not access, + * but phpMyAdmin can). + */ + if (@is_link($import_file)) { + $import_file = 'none'; + } + +} elseif (empty($import_file) || ! is_uploaded_file($import_file)) { + $import_file = 'none'; +} + +// Do we have file to import? + +if ($import_file != 'none' && ! $error) { + /** + * Handle file compression + */ + $import_handle = new File($import_file); + $import_handle->checkUploadedFile(); + if ($import_handle->isError()) { + Import::stop($import_handle->getError()); + } + $import_handle->setDecompressContent(true); + $import_handle->open(); + if ($import_handle->isError()) { + Import::stop($import_handle->getError()); + } +} elseif (! $error) { + if (! isset($import_text) || empty($import_text)) { + $message = PhpMyAdmin\Message::error( + __( + 'No data was received to import. Either no file name was ' . + 'submitted, or the file size exceeded the maximum size permitted ' . + 'by your PHP configuration. See [doc@faq1-16]FAQ 1.16[/doc].' + ) + ); + Import::stop($message); + } +} + +// so we can obtain the message +//$_SESSION['Import_message'] = $message->getDisplay(); + +// Convert the file's charset if necessary +if (Encoding::isSupported() && isset($charset_of_file)) { + if ($charset_of_file != 'utf-8') { + $charset_conversion = true; + } +} elseif (isset($charset_of_file) && $charset_of_file != 'utf-8') { + $GLOBALS['dbi']->query('SET NAMES \'' . $charset_of_file . '\''); + // We can not show query in this case, it is in different charset + $sql_query_disabled = true; + $reset_charset = true; +} + +// Something to skip? (because timeout has passed) +if (! $error && isset($_POST['skip'])) { + $original_skip = $skip = intval($_POST['skip']); + while ($skip > 0 && ! $finished) { + Import::getNextChunk($skip < $read_limit ? $skip : $read_limit); + // Disable read progressivity, otherwise we eat all memory! + $read_multiply = 1; + $skip -= $read_limit; + } + unset($skip); +} + +// This array contain the data like numberof valid sql queries in the statement +// and complete valid sql statement (which affected for rows) +$sql_data = array('valid_sql' => array(), 'valid_queries' => 0); + +if (! $error) { + /* @var $import_plugin ImportPlugin */ + $import_plugin = Plugins::getPlugin( + "import", + $format, + 'libraries/classes/Plugins/Import/', + $import_type + ); + if ($import_plugin == null) { + $message = PhpMyAdmin\Message::error( + __('Could not load import plugins, please check your installation!') + ); + Import::stop($message); + } else { + // Do the real import + try { + $default_fk_check = PhpMyAdmin\Util::handleDisableFKCheckInit(); + $import_plugin->doImport($sql_data); + PhpMyAdmin\Util::handleDisableFKCheckCleanup($default_fk_check); + } catch (Exception $e) { + PhpMyAdmin\Util::handleDisableFKCheckCleanup($default_fk_check); + throw $e; + } + } +} + +if (isset($import_handle)) { + $import_handle->close(); +} + +// Cleanup temporary file +if ($file_to_unlink != '') { + unlink($file_to_unlink); +} + +// Reset charset back, if we did some changes +if ($reset_charset) { + $GLOBALS['dbi']->query('SET CHARACTER SET ' . $GLOBALS['charset_connection']); + $GLOBALS['dbi']->setCollation($collation_connection); +} + +// Show correct message +if (! empty($id_bookmark) && $_POST['action_bookmark'] == 2) { + $message = PhpMyAdmin\Message::success(__('The bookmark has been deleted.')); + $display_query = $import_text; + $error = false; // unset error marker, it was used just to skip processing +} elseif (! empty($id_bookmark) && $_POST['action_bookmark'] == 1) { + $message = PhpMyAdmin\Message::notice(__('Showing bookmark')); +} elseif ($bookmark_created) { + $special_message = '[br]' . sprintf( + __('Bookmark %s has been created.'), + htmlspecialchars($_POST['bkm_label']) + ); +} elseif ($finished && ! $error) { + // Do not display the query with message, we do it separately + $display_query = ';'; + if ($import_type != 'query') { + $message = PhpMyAdmin\Message::success( + '' + . _ngettext( + 'Import has been successfully finished, %d query executed.', + 'Import has been successfully finished, %d queries executed.', + $executed_queries + ) + . '' + ); + $message->addParam($executed_queries); + + if (! empty($import_notice)) { + $message->addHtml($import_notice); + } + if (! empty($local_import_file)) { + $message->addText('(' . $local_import_file . ')'); + } else { + $message->addText('(' . $_FILES['import_file']['name'] . ')'); + } + } +} + +// Did we hit timeout? Tell it user. +if ($timeout_passed) { + $urlparams['timeout_passed'] = '1'; + $urlparams['offset'] = $GLOBALS['offset']; + if (isset($local_import_file)) { + $urlparams['local_import_file'] = $local_import_file; + } + + $importUrl = $err_url = $goto . Url::getCommon($urlparams); + + $message = PhpMyAdmin\Message::error( + __( + 'Script timeout passed, if you want to finish import,' + . ' please %sresubmit the same file%s and import will resume.' + ) + ); + $message->addParamHtml(''); + $message->addParamHtml(''); + + if ($offset == 0 || (isset($original_skip) && $original_skip == $offset)) { + $message->addText( + __( + 'However on last run no data has been parsed,' + . ' this usually means phpMyAdmin won\'t be able to' + . ' finish this import unless you increase php time limits.' + ) + ); + } +} + +// if there is any message, copy it into $_SESSION as well, +// so we can obtain it by AJAX call +if (isset($message)) { + $_SESSION['Import_message']['message'] = $message->getDisplay(); +} +// Parse and analyze the query, for correct db and table name +// in case of a query typed in the query window +// (but if the query is too large, in case of an imported file, the parser +// can choke on it so avoid parsing) +$sqlLength = mb_strlen($sql_query); +if ($sqlLength <= $GLOBALS['cfg']['MaxCharactersInDisplayedSQL']) { + list( + $analyzed_sql_results, + $db, + $table_from_sql + ) = ParseAnalyze::sqlQuery($sql_query, $db); + // @todo: possibly refactor + extract($analyzed_sql_results); + + if ($table != $table_from_sql && !empty($table_from_sql)) { + $table = $table_from_sql; + } +} + +// There was an error? +if (isset($my_die)) { + foreach ($my_die as $key => $die) { + PhpMyAdmin\Util::mysqlDie( + $die['error'], $die['sql'], false, $err_url, $error + ); + } +} + +if ($go_sql) { + + if (! empty($sql_data) && ($sql_data['valid_queries'] > 1)) { + $_SESSION['is_multi_query'] = true; + $sql_queries = $sql_data['valid_sql']; + } else { + $sql_queries = array($sql_query); + } + + $html_output = ''; + + foreach ($sql_queries as $sql_query) { + + // parse sql query + list( + $analyzed_sql_results, + $db, + $table_from_sql + ) = ParseAnalyze::sqlQuery($sql_query, $db); + // @todo: possibly refactor + extract($analyzed_sql_results); + + // Check if User is allowed to issue a 'DROP DATABASE' Statement + if ($sql->hasNoRightsToDropDatabase( + $analyzed_sql_results, $cfg['AllowUserDropDatabase'], $GLOBALS['dbi']->isSuperuser() + )) { + PhpMyAdmin\Util::mysqlDie( + __('"DROP DATABASE" statements are disabled.'), + '', + false, + $_SESSION['Import_message']['go_back_url'] + ); + return; + } // end if + + if ($table != $table_from_sql && !empty($table_from_sql)) { + $table = $table_from_sql; + } + + $html_output .= $sql->executeQueryAndGetQueryResponse( + $analyzed_sql_results, // analyzed_sql_results + false, // is_gotofile + $db, // db + $table, // table + null, // find_real_end + null, // sql_query_for_bookmark - see below + null, // extra_data + null, // message_to_show + null, // message + null, // sql_data + $goto, // goto + $pmaThemeImage, // pmaThemeImage + null, // disp_query + null, // disp_message + null, // query_type + $sql_query, // sql_query + null, // selectedTables + null // complete_query + ); + } + + // sql_query_for_bookmark is not included in Sql::executeQueryAndGetQueryResponse + // since only one bookmark has to be added for all the queries submitted through + // the SQL tab + if (! empty($_POST['bkm_label']) && ! empty($import_text)) { + $cfgBookmark = Bookmark::getParams($GLOBALS['cfg']['Server']['user']); + $sql->storeTheQueryAsBookmark( + $db, $cfgBookmark['user'], + $_POST['sql_query'], $_POST['bkm_label'], + isset($_POST['bkm_replace']) ? $_POST['bkm_replace'] : null + ); + } + + $response->addJSON('ajax_reload', $ajax_reload); + $response->addHTML($html_output); + exit(); + +} elseif ($result) { + // Save a Bookmark with more than one queries (if Bookmark label given). + if (! empty($_POST['bkm_label']) && ! empty($import_text)) { + $cfgBookmark = Bookmark::getParams($GLOBALS['cfg']['Server']['user']); + $sql->storeTheQueryAsBookmark( + $db, $cfgBookmark['user'], + $_POST['sql_query'], $_POST['bkm_label'], + isset($_POST['bkm_replace']) ? $_POST['bkm_replace'] : null + ); + } + + $response->setRequestStatus(true); + $response->addJSON('message', PhpMyAdmin\Message::success($msg)); + $response->addJSON( + 'sql_query', + PhpMyAdmin\Util::getMessage($msg, $sql_query, 'success') + ); +} elseif ($result == false) { + $response->setRequestStatus(false); + $response->addJSON('message', PhpMyAdmin\Message::error($msg)); +} else { + $active_page = $goto; + include '' . $goto; +} + +// If there is request for ROLLBACK in the end. +if (isset($_POST['rollback_query'])) { + $GLOBALS['dbi']->query('ROLLBACK'); +} diff --git a/php/apps/phpmyadmin49/import_status.php b/php/apps/phpmyadmin49/import_status.php new file mode 100644 index 00000000..02e39d45 --- /dev/null +++ b/php/apps/phpmyadmin49/import_status.php @@ -0,0 +1,121 @@ + $value) { + // only copy session-prefixed data + if (substr($key, 0, strlen(UPLOAD_PREFIX)) + == UPLOAD_PREFIX) { + $sessionupload[$key] = $value; + } + } + // PMA will kill all variables, so let's use a constant + define('SESSIONUPLOAD', serialize($sessionupload)); + session_write_close(); + + session_name('phpMyAdmin'); + session_id($_COOKIE['phpMyAdmin']); +} + */ + +define('PMA_MINIMUM_COMMON', 1); + +require_once 'libraries/common.inc.php'; +list( + $SESSION_KEY, + $upload_id, + $plugins +) = ImportAjax::uploadProgressSetup(); + +/* +if (defined('SESSIONUPLOAD')) { + // write sessionupload back into the loaded PMA session + + $sessionupload = unserialize(SESSIONUPLOAD); + foreach ($sessionupload as $key => $value) { + $_SESSION[$key] = $value; + } + + // remove session upload data that are not set anymore + foreach ($_SESSION as $key => $value) { + if (substr($key, 0, strlen(UPLOAD_PREFIX)) + == UPLOAD_PREFIX + && ! isset($sessionupload[$key]) + ) { + unset($_SESSION[$key]); + } + } +} + */ + +// $_GET["message"] is used for asking for an import message +if (isset($_GET["message"]) && $_GET["message"]) { + + // AJAX requests can't be cached! + Core::noCacheHeader(); + + header('Content-type: text/html'); + + // wait 0.3 sec before we check for $_SESSION variable, + // which is set inside import.php + usleep(300000); + + $maximumTime = ini_get('max_execution_time'); + $timestamp = time(); + // wait until message is available + while ($_SESSION['Import_message']['message'] == null) { + // close session before sleeping + session_write_close(); + // sleep + usleep(250000); // 0.25 sec + // reopen session + session_start(); + + if ((time() - $timestamp) > $maximumTime) { + $_SESSION['Import_message']['message'] = PhpMyAdmin\Message::error( + __('Could not load the progress of the import.') + )->getDisplay(); + break; + } + } + + echo $_SESSION['Import_message']['message']; + echo '
      ' , "\n"; + echo ' [ ' , __('Back') , ' ]' , "\n"; + echo '
      ' , "\n"; + +} else { + ImportAjax::status($_GET["id"]); +} diff --git a/php/apps/phpmyadmin49/index.php b/php/apps/phpmyadmin49/index.php new file mode 100644 index 00000000..ef276476 --- /dev/null +++ b/php/apps/phpmyadmin49/index.php @@ -0,0 +1,691 @@ +setUserValue( + null, + 'FontSize', + $_POST['set_fontsize'], + '82%' + ); + header('Location: index.php' . Url::getCommonRaw()); + exit(); +} +// if user selected a theme +if (isset($_POST['set_theme'])) { + $tmanager = ThemeManager::getInstance(); + $tmanager->setActiveTheme($_POST['set_theme']); + $tmanager->setThemeCookie(); + + $userPreferences = new UserPreferences(); + $prefs = $userPreferences->load(); + $prefs["config_data"]["ThemeDefault"] = $_POST['set_theme']; + $userPreferences->save($prefs["config_data"]); + + header('Location: index.php' . Url::getCommonRaw()); + exit(); +} +// Change collation connection +if (isset($_POST['collation_connection'])) { + $GLOBALS['PMA_Config']->setUserValue( + null, + 'DefaultConnectionCollation', + $_POST['collation_connection'], + 'utf8mb4_unicode_ci' + ); + header('Location: index.php' . Url::getCommonRaw()); + exit(); +} + + +// See FAQ 1.34 +if (! empty($_REQUEST['db'])) { + $page = null; + if (! empty($_REQUEST['table'])) { + $page = Util::getScriptNameForOption( + $GLOBALS['cfg']['DefaultTabTable'], 'table' + ); + } else { + $page = Util::getScriptNameForOption( + $GLOBALS['cfg']['DefaultTabDatabase'], 'database' + ); + } + include $page; + exit; +} + +$response = Response::getInstance(); +/** + * Check if it is an ajax request to reload the recent tables list. + */ +if ($response->isAjax() && ! empty($_REQUEST['recent_table'])) { + $response->addJSON( + 'list', + RecentFavoriteTable::getInstance('recent')->getHtmlList() + ); + exit; +} + +if ($GLOBALS['PMA_Config']->isGitRevision()) { + // If ajax request to get revision + if (isset($_REQUEST['git_revision']) && $response->isAjax()) { + GitRevision::display(); + exit; + } + // Else show empty html + echo '
      '; +} + +// Handles some variables that may have been sent by the calling script +$GLOBALS['db'] = ''; +$GLOBALS['table'] = ''; +$show_query = '1'; + +// Any message to display? +if (! empty($message)) { + echo Util::getMessage($message); + unset($message); +} +if (isset($_SESSION['partial_logout'])) { + Message::success( + __('You were logged out from one server, to logout completely from phpMyAdmin, you need to logout from all servers.') + )->display(); + unset($_SESSION['partial_logout']); +} + +$common_url_query = Url::getCommon(); +$mysql_cur_user_and_host = ''; + +// when $server > 0, a server has been chosen so we can display +// all MySQL-related information +if ($server > 0) { + include 'libraries/server_common.inc.php'; + + // Use the verbose name of the server instead of the hostname + // if a value is set + $server_info = ''; + if (! empty($cfg['Server']['verbose'])) { + $server_info .= htmlspecialchars($cfg['Server']['verbose']); + if ($GLOBALS['cfg']['ShowServerInfo']) { + $server_info .= ' ('; + } + } + if ($GLOBALS['cfg']['ShowServerInfo'] || empty($cfg['Server']['verbose'])) { + $server_info .= $GLOBALS['dbi']->getHostInfo(); + } + if (! empty($cfg['Server']['verbose']) && $GLOBALS['cfg']['ShowServerInfo']) { + $server_info .= ')'; + } + $mysql_cur_user_and_host = $GLOBALS['dbi']->fetchValue('SELECT USER();'); + + // should we add the port info here? + $short_server_info = (!empty($GLOBALS['cfg']['Server']['verbose']) + ? $GLOBALS['cfg']['Server']['verbose'] + : $GLOBALS['cfg']['Server']['host']); +} + +echo '
      ' , "\n"; +// Anchor for favorite tables synchronization. +echo RecentFavoriteTable::getInstance('favorite')->getHtmlSyncFavoriteTables(); +echo '
      '; +if ($server > 0 || count($cfg['Servers']) > 1 +) { + if ($cfg['DBG']['demo']) { + echo '
      '; + echo '

      ' , __('phpMyAdmin Demo Server') , '

      '; + echo '

      '; + printf( + __( + 'You are using the demo server. You can do anything here, but ' + . 'please do not change root, debian-sys-maint and pma users. ' + . 'More information is available at %s.' + ), + 'demo.phpmyadmin.net' + ); + echo '

      '; + echo '
      '; + } + echo '
      '; + echo '

      ' , __('General settings') , '

      '; + echo '
        '; + + /** + * Displays the MySQL servers choice form + */ + if ($cfg['ServerDefault'] == 0 + || (! $cfg['NavigationDisplayServers'] + && (count($cfg['Servers']) > 1 + || ($server == 0 && count($cfg['Servers']) == 1))) + ) { + echo '
      • '; + echo Util::getImage('s_host') , " " + , Select::render(true, true); + echo '
      • '; + } + + /** + * Displays the mysql server related links + */ + if ($server > 0) { + include_once 'libraries/check_user_privileges.inc.php'; + + // Logout for advanced authentication + if ($cfg['Server']['auth_type'] != 'config') { + if ($cfg['ShowChgPassword']) { + $conditional_class = 'ajax'; + Core::printListItem( + Util::getImage('s_passwd') . " " . __( + 'Change password' + ), + 'li_change_password', + 'user_password.php' . $common_url_query, + null, + null, + 'change_password_anchor', + "no_bullets", + $conditional_class + ); + } + } // end if + echo '
      • '; + echo '
        ' , "\n" + . Url::getHiddenInputs(null, null, 4, 'collation_connection') + . ' ' . "\n" + + . Charsets::getCollationDropdownBox( + $GLOBALS['dbi'], + $GLOBALS['cfg']['Server']['DisableIS'], + 'collation_connection', + 'select_collation_connection', + $collation_connection, + true, + true + ) + . '
        ' . "\n" + . '
      • ' . "\n"; + } // end of if ($server > 0) + echo '
      '; + echo '
      '; +} + +echo '
      '; +echo '

      ' , __('Appearance settings') , '

      '; +echo '
        '; + +// Displays language selection combo +$language_manager = LanguageManager::getInstance(); +if (empty($cfg['Lang']) && $language_manager->hasChoice()) { + echo '
      • '; + + echo Util::getImage('s_lang') , " " + , $language_manager->getSelectorDisplay(); + echo '
      • '; +} + +// ThemeManager if available + +if ($GLOBALS['cfg']['ThemeManager']) { + echo '
      • '; + echo Util::getImage('s_theme') , " " + , ThemeManager::getInstance()->getHtmlSelectBox(); + echo '
      • '; +} +echo '
      • '; +echo Config::getFontsizeForm(); +echo '
      • '; + +echo '
      '; + +// User preferences + +if ($server > 0) { + echo '
        '; + Core::printListItem( + Util::getImage('b_tblops') . " " . __( + 'More settings' + ), + 'li_user_preferences', + 'prefs_manage.php' . $common_url_query, + null, + null, + null, + "no_bullets" + ); + echo '
      '; +} + +echo '
      '; + + +echo '
      '; +echo '
      '; + + +if ($server > 0 && $GLOBALS['cfg']['ShowServerInfo']) { + + echo '
      '; + echo '

      ' , __('Database server') , '

      '; + echo '
        ' , "\n"; + Core::printListItem( + __('Server:') . ' ' . $server_info, + 'li_server_info' + ); + Core::printListItem( + __('Server type:') . ' ' . Util::getServerType(), + 'li_server_type' + ); + Core::printListItem( + __('Server connection:') . ' ' . Util::getServerSSL(), + 'li_server_type' + ); + Core::printListItem( + __('Server version:') + . ' ' + . $GLOBALS['dbi']->getVersionString() . ' - ' . $GLOBALS['dbi']->getVersionComment(), + 'li_server_version' + ); + Core::printListItem( + __('Protocol version:') . ' ' . $GLOBALS['dbi']->getProtoInfo(), + 'li_mysql_proto' + ); + Core::printListItem( + __('User:') . ' ' . htmlspecialchars($mysql_cur_user_and_host), + 'li_user_info' + ); + + echo '
      • '; + echo ' ' , __('Server charset:') , ' ' + . ' '; + + $charset = Charsets::getServerCharset($GLOBALS['dbi']); + $charsets = Charsets::getMySQLCharsetsDescriptions( + $GLOBALS['dbi'], + $GLOBALS['cfg']['Server']['DisableIS'] + ); + + echo ' ' , $charsets[$charset], ' (' . $charset, ')'; + echo ' ' + . '
      • ' + . '
      ' + . '
      '; +} + +if ($GLOBALS['cfg']['ShowServerInfo'] || $GLOBALS['cfg']['ShowPhpInfo']) { + echo '
      '; + echo '

      ' , __('Web server') , '

      '; + echo '
        '; + if ($GLOBALS['cfg']['ShowServerInfo']) { + Core::printListItem($_SERVER['SERVER_SOFTWARE'], 'li_web_server_software'); + + if ($server > 0) { + $client_version_str = $GLOBALS['dbi']->getClientInfo(); + if (preg_match('#\d+\.\d+\.\d+#', $client_version_str)) { + $client_version_str = 'libmysql - ' . $client_version_str; + } + Core::printListItem( + __('Database client version:') . ' ' . $client_version_str, + 'li_mysql_client_version' + ); + + $php_ext_string = __('PHP extension:') . ' '; + + $extensions = Util::listPHPExtensions(); + + foreach ($extensions as $extension) { + $php_ext_string .= ' ' . $extension + . Util::showPHPDocu('book.' . $extension . '.php'); + } + + Core::printListItem( + $php_ext_string, + 'li_used_php_extension' + ); + + $php_version_string = __('PHP version:') . ' ' . phpversion(); + + Core::printListItem( + $php_version_string, + 'li_used_php_version' + ); + } + } + + if ($cfg['ShowPhpInfo']) { + Core::printListItem( + __('Show PHP information'), + 'li_phpinfo', + 'phpinfo.php' . $common_url_query, + null, + '_blank' + ); + } + echo '
      '; + echo '
      '; +} + +echo '
      '; +echo '

      phpMyAdmin

      '; +echo '
        '; +$class = null; +if ($GLOBALS['cfg']['VersionCheck']) { + $class = 'jsversioncheck'; +} +Core::printListItem( + __('Version information:') . ' ' . PMA_VERSION . '', + 'li_pma_version', + null, + null, + null, + null, + $class +); +Core::printListItem( + __('Documentation'), + 'li_pma_docs', + Util::getDocuLink('index'), + null, + '_blank' +); + +// does not work if no target specified, don't know why +Core::printListItem( + __('Official Homepage'), + 'li_pma_homepage', + Core::linkURL('https://www.phpmyadmin.net/'), + null, + '_blank' +); +Core::printListItem( + __('Contribute'), + 'li_pma_contribute', + Core::linkURL('https://www.phpmyadmin.net/contribute/'), + null, + '_blank' +); +Core::printListItem( + __('Get support'), + 'li_pma_support', + Core::linkURL('https://www.phpmyadmin.net/support/'), + null, + '_blank' +); +Core::printListItem( + __('List of changes'), + 'li_pma_changes', + 'changelog.php' . Url::getCommon(), + null, + '_blank' +); +Core::printListItem( + __('License'), + 'li_pma_license', + 'license.php' . Url::getCommon(), + null, + '_blank' +); +echo '
      '; +echo '
      '; + +echo '
      '; + +echo '
      '; + +/** + * mbstring is used for handling multibytes inside parser, so it is good + * to tell user something might be broken without it, see bug #1063149. + */ +if (! extension_loaded('mbstring')) { + trigger_error( + __( + 'The mbstring PHP extension was not found and you seem to be using' + . ' a multibyte charset. Without the mbstring extension phpMyAdmin' + . ' is unable to split strings correctly and it may result in' + . ' unexpected results.' + ), + E_USER_WARNING + ); +} + +/** + * Missing functionality + */ +if (! extension_loaded('curl') && ! ini_get('allow_url_fopen')) { + trigger_error( + __( + 'The curl extension was not found and allow_url_fopen is ' + . 'disabled. Due to this some features such as error reporting ' + . 'or version check are disabled.' + ) + ); +} + +if ($cfg['LoginCookieValidityDisableWarning'] == false) { + /** + * Check whether session.gc_maxlifetime limits session validity. + */ + $gc_time = (int)ini_get('session.gc_maxlifetime'); + if ($gc_time < $GLOBALS['cfg']['LoginCookieValidity'] ) { + trigger_error( + __( + 'Your PHP parameter [a@https://secure.php.net/manual/en/session.' . + 'configuration.php#ini.session.gc-maxlifetime@_blank]session.' . + 'gc_maxlifetime[/a] is lower than cookie validity configured ' . + 'in phpMyAdmin, because of this, your login might expire sooner ' . + 'than configured in phpMyAdmin.' + ), + E_USER_WARNING + ); + } +} + +/** + * Check whether LoginCookieValidity is limited by LoginCookieStore. + */ +if ($GLOBALS['cfg']['LoginCookieStore'] != 0 + && $GLOBALS['cfg']['LoginCookieStore'] < $GLOBALS['cfg']['LoginCookieValidity'] +) { + trigger_error( + __( + 'Login cookie store is lower than cookie validity configured in ' . + 'phpMyAdmin, because of this, your login will expire sooner than ' . + 'configured in phpMyAdmin.' + ), + E_USER_WARNING + ); +} + +/** + * Check if user does not have defined blowfish secret and it is being used. + */ +if (! empty($_SESSION['encryption_key'])) { + if (empty($GLOBALS['cfg']['blowfish_secret'])) { + trigger_error( + __( + 'The configuration file now needs a secret passphrase (blowfish_secret).' + ), + E_USER_WARNING + ); + } elseif (strlen($GLOBALS['cfg']['blowfish_secret']) < 32) { + trigger_error( + __( + 'The secret passphrase in configuration (blowfish_secret) is too short.' + ), + E_USER_WARNING + ); + } +} + +/** + * Check for existence of config directory which should not exist in + * production environment. + */ +if (@file_exists('config')) { + trigger_error( + __( + 'Directory [code]config[/code], which is used by the setup script, ' . + 'still exists in your phpMyAdmin directory. It is strongly ' . + 'recommended to remove it once phpMyAdmin has been configured. ' . + 'Otherwise the security of your server may be compromised by ' . + 'unauthorized people downloading your configuration.' + ), + E_USER_WARNING + ); +} + +$relation = new Relation(); + +if ($server > 0) { + $cfgRelation = $relation->getRelationsParam(); + if (! $cfgRelation['allworks'] + && $cfg['PmaNoRelation_DisableWarning'] == false + ) { + $msg_text = __( + 'The phpMyAdmin configuration storage is not completely ' + . 'configured, some extended features have been deactivated. ' + . '%sFind out why%s. ' + ); + if ($cfg['ZeroConf'] == true) { + $msg_text .= '
      ' . + __( + 'Or alternately go to \'Operations\' tab of any database ' + . 'to set it up there.' + ); + } + $msg = Message::notice($msg_text); + $msg->addParamHtml(''); + $msg->addParamHtml(''); + /* Show error if user has configured something, notice elsewhere */ + if (!empty($cfg['Servers'][$server]['pmadb'])) { + $msg->isError(true); + } + $msg->display(); + } // end if +} + +/** + * Warning about Suhosin only if its simulation mode is not enabled + */ +if ($cfg['SuhosinDisableWarning'] == false + && ini_get('suhosin.request.max_value_length') + && ini_get('suhosin.simulation') == '0' +) { + trigger_error( + sprintf( + __( + 'Server running with Suhosin. Please refer to %sdocumentation%s ' . + 'for possible issues.' + ), + '[doc@faq1-38]', + '[/doc]' + ), + E_USER_WARNING + ); +} + +/* Missing template cache */ +if (is_null($GLOBALS['PMA_Config']->getTempDir('twig'))) { + trigger_error( + sprintf( + __('The $cfg[\'TempDir\'] (%s) is not accessible. phpMyAdmin is not able to cache templates and will be slow because of this.'), + $GLOBALS['PMA_Config']->get('TempDir') + ), + E_USER_WARNING + ); +} + +/** + * Warning about incomplete translations. + * + * The data file is created while creating release by ./scripts/remove-incomplete-mo + */ +if (@file_exists('libraries/language_stats.inc.php')) { + include 'libraries/language_stats.inc.php'; + /* + * This message is intentionally not translated, because we're + * handling incomplete translations here and focus on english + * speaking users. + */ + if (isset($GLOBALS['language_stats'][$lang]) + && $GLOBALS['language_stats'][$lang] < $cfg['TranslationWarningThreshold'] + ) { + trigger_error( + 'You are using an incomplete translation, please help to make it ' + . 'better by [a@https://www.phpmyadmin.net/translate/' + . '@_blank]contributing[/a].', + E_USER_NOTICE + ); + } +} diff --git a/php/apps/phpmyadmin49/js/ajax.js b/php/apps/phpmyadmin49/js/ajax.js new file mode 100644 index 00000000..81976128 --- /dev/null +++ b/php/apps/phpmyadmin49/js/ajax.js @@ -0,0 +1,834 @@ +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * This object handles ajax requests for pages. It also + * handles the reloading of the main menu and scripts. + */ +var AJAX = { + /** + * @var bool active Whether we are busy + */ + active: false, + /** + * @var object source The object whose event initialized the request + */ + source: null, + /** + * @var object xhr A reference to the ajax request that is currently running + */ + xhr: null, + /** + * @var object lockedTargets, list of locked targets + */ + lockedTargets: {}, + /** + * @var function Callback to execute after a successful request + * Used by PMA_commonFunctions from common.js + */ + _callback: function () {}, + /** + * @var bool _debug Makes noise in your Firebug console + */ + _debug: false, + /** + * @var object $msgbox A reference to a jQuery object that links to a message + * box that is generated by PMA_ajaxShowMessage() + */ + $msgbox: null, + /** + * Given the filename of a script, returns a hash to be + * used to refer to all the events registered for the file + * + * @param key string key The filename for which to get the event name + * + * @return int + */ + hash: function (key) { + /* http://burtleburtle.net/bob/hash/doobs.html#one */ + key += ''; + var len = key.length; + var hash = 0; + var i = 0; + for (; i < len; ++i) { + hash += key.charCodeAt(i); + hash += (hash << 10); + hash ^= (hash >> 6); + } + hash += (hash << 3); + hash ^= (hash >> 11); + hash += (hash << 15); + return Math.abs(hash); + }, + /** + * Registers an onload event for a file + * + * @param file string file The filename for which to register the event + * @param func function func The function to execute when the page is ready + * + * @return self For chaining + */ + registerOnload: function (file, func) { + var eventName = 'onload_' + AJAX.hash(file); + $(document).on(eventName, func); + if (this._debug) { + console.log( + // no need to translate + 'Registered event ' + eventName + ' for file ' + file + ); + } + return this; + }, + /** + * Registers a teardown event for a file. This is useful to execute functions + * that unbind events for page elements that are about to be removed. + * + * @param string file The filename for which to register the event + * @param function func The function to execute when + * the page is about to be torn down + * + * @return self For chaining + */ + registerTeardown: function (file, func) { + var eventName = 'teardown_' + AJAX.hash(file); + $(document).on(eventName, func); + if (this._debug) { + console.log( + // no need to translate + 'Registered event ' + eventName + ' for file ' + file + ); + } + return this; + }, + /** + * Called when a page has finished loading, once for every + * file that registered to the onload event of that file. + * + * @param string file The filename for which to fire the event + * + * @return void + */ + fireOnload: function (file) { + var eventName = 'onload_' + AJAX.hash(file); + $(document).trigger(eventName); + if (this._debug) { + console.log( + // no need to translate + 'Fired event ' + eventName + ' for file ' + file + ); + } + }, + /** + * Called just before a page is torn down, once for every + * file that registered to the teardown event of that file. + * + * @param string file The filename for which to fire the event + * + * @return void + */ + fireTeardown: function (file) { + var eventName = 'teardown_' + AJAX.hash(file); + $(document).triggerHandler(eventName); + if (this._debug) { + console.log( + // no need to translate + 'Fired event ' + eventName + ' for file ' + file + ); + } + }, + /** + * function to handle lock page mechanism + * + * @param event the event object + * + * @return void + */ + lockPageHandler: function (event) { + var newHash = null; + var oldHash = null; + var lockId; + // CodeMirror lock + if (event.data.value === 3) { + newHash = event.data.content; + oldHash = true; + lockId = 'cm'; + } else { + // Don't lock on enter. + if (0 === event.charCode) { + return; + } + + lockId = $(this).data('lock-id'); + if (typeof lockId === 'undefined') { + return; + } + /* + * @todo Fix Code mirror does not give correct full value (query) + * in textarea, it returns only the change in content. + */ + if (event.data.value === 1) { + newHash = AJAX.hash($(this).val()); + } else { + newHash = AJAX.hash($(this).is(':checked')); + } + oldHash = $(this).data('val-hash'); + } + // Set lock if old value !== new value + // otherwise release lock + if (oldHash !== newHash) { + AJAX.lockedTargets[lockId] = true; + } else { + delete AJAX.lockedTargets[lockId]; + } + // Show lock icon if locked targets is not empty. + // otherwise remove lock icon + if (!jQuery.isEmptyObject(AJAX.lockedTargets)) { + $('#lock_page_icon').html(PMA_getImage('s_lock', PMA_messages.strLockToolTip).toString()); + } else { + $('#lock_page_icon').html(''); + } + }, + /** + * resets the lock + * + * @return void + */ + resetLock: function () { + AJAX.lockedTargets = {}; + $('#lock_page_icon').html(''); + }, + handleMenu: { + replace: function (content) { + $('#floating_menubar').html(content) + // Remove duplicate wrapper + // TODO: don't send it in the response + .children().first().remove(); + $('#topmenu').menuResizer(PMA_mainMenuResizerCallback); + } + }, + /** + * Event handler for clicks on links and form submissions + * + * @param object e Event data + * + * @return void + */ + requestHandler: function (event) { + // In some cases we don't want to handle the request here and either + // leave the browser deal with it natively (e.g: file download) + // or leave an existing ajax event handler present elsewhere deal with it + var href = $(this).attr('href'); + if (typeof event !== 'undefined' && (event.shiftKey || event.ctrlKey)) { + return true; + } else if ($(this).attr('target')) { + return true; + } else if ($(this).hasClass('ajax') || $(this).hasClass('disableAjax')) { + // reset the lockedTargets object, as specified AJAX operation has finished + AJAX.resetLock(); + return true; + } else if (href && href.match(/^#/)) { + return true; + } else if (href && href.match(/^mailto/)) { + return true; + } else if ($(this).hasClass('ui-datepicker-next') || + $(this).hasClass('ui-datepicker-prev') + ) { + return true; + } + + if (typeof event !== 'undefined') { + event.preventDefault(); + event.stopImmediatePropagation(); + } + + // triggers a confirm dialog if: + // the user has performed some operations on loaded page + // the user clicks on some link, (won't trigger for buttons) + // the click event is not triggered by script + if (typeof event !== 'undefined' && event.type === 'click' && + event.isTrigger !== true && + !jQuery.isEmptyObject(AJAX.lockedTargets) && + confirm(PMA_messages.strConfirmNavigation) === false + ) { + return false; + } + AJAX.resetLock(); + var isLink = !! href || false; + var previousLinkAborted = false; + + if (AJAX.active === true) { + // Cancel the old request if abortable, when the user requests + // something else. Otherwise silently bail out, as there is already + // a request well in progress. + if (AJAX.xhr) { + // In case of a link request, attempt aborting + AJAX.xhr.abort(); + if (AJAX.xhr.status === 0 && AJAX.xhr.statusText === 'abort') { + // If aborted + AJAX.$msgbox = PMA_ajaxShowMessage(PMA_messages.strAbortedRequest); + AJAX.active = false; + AJAX.xhr = null; + previousLinkAborted = true; + } else { + // If can't abort + return false; + } + } else { + // In case submitting a form, don't attempt aborting + return false; + } + } + + AJAX.source = $(this); + + $('html, body').animate({ scrollTop: 0 }, 'fast'); + + var url = isLink ? href : $(this).attr('action'); + var argsep = PMA_commonParams.get('arg_separator'); + var params = 'ajax_request=true' + argsep + 'ajax_page_request=true'; + var dataPost = AJAX.source.getPostData(); + if (! isLink) { + params += argsep + $(this).serialize(); + } else if (dataPost) { + params += argsep + dataPost; + isLink = false; + } + if (! (history && history.pushState)) { + // Add a list of menu hashes that we have in the cache to the request + params += PMA_MicroHistory.menus.getRequestParam(); + } + + if (AJAX._debug) { + console.log('Loading: ' + url); // no need to translate + } + + if (isLink) { + AJAX.active = true; + AJAX.$msgbox = PMA_ajaxShowMessage(); + // Save reference for the new link request + AJAX.xhr = $.get(url, params, AJAX.responseHandler); + if (history && history.pushState) { + var state = { + url : href + }; + if (previousLinkAborted) { + // hack: there is already an aborted entry on stack + // so just modify the aborted one + history.replaceState(state, null, href); + } else { + history.pushState(state, null, href); + } + } + } else { + /** + * Manually fire the onsubmit event for the form, if any. + * The event was saved in the jQuery data object by an onload + * handler defined below. Workaround for bug #3583316 + */ + var onsubmit = $(this).data('onsubmit'); + // Submit the request if there is no onsubmit handler + // or if it returns a value that evaluates to true + if (typeof onsubmit !== 'function' || onsubmit.apply(this, [event])) { + AJAX.active = true; + AJAX.$msgbox = PMA_ajaxShowMessage(); + $.post(url, params, AJAX.responseHandler); + } + } + }, + /** + * Called after the request that was initiated by this.requestHandler() + * has completed successfully or with a caught error. For completely + * failed requests or requests with uncaught errors, see the .ajaxError + * handler at the bottom of this file. + * + * To refer to self use 'AJAX', instead of 'this' as this function + * is called in the jQuery context. + * + * @param object e Event data + * + * @return void + */ + responseHandler: function (data) { + if (typeof data === 'undefined' || data === null) { + return; + } + if (typeof data.success !== 'undefined' && data.success) { + $('html, body').animate({ scrollTop: 0 }, 'fast'); + PMA_ajaxRemoveMessage(AJAX.$msgbox); + + if (data._redirect) { + PMA_ajaxShowMessage(data._redirect, false); + AJAX.active = false; + AJAX.xhr = null; + return; + } + + AJAX.scriptHandler.reset(function () { + if (data._reloadNavigation) { + PMA_reloadNavigation(); + } + if (data._title) { + $('title').replaceWith(data._title); + } + if (data._menu) { + if (history && history.pushState) { + var state = { + url : data._selflink, + menu : data._menu + }; + history.replaceState(state, null); + AJAX.handleMenu.replace(data._menu); + } else { + PMA_MicroHistory.menus.replace(data._menu); + PMA_MicroHistory.menus.add(data._menuHash, data._menu); + } + } else if (data._menuHash) { + if (! (history && history.pushState)) { + PMA_MicroHistory.menus.replace(PMA_MicroHistory.menus.get(data._menuHash)); + } + } + if (data._disableNaviSettings) { + PMA_disableNaviSettings(); + } else { + PMA_ensureNaviSettings(data._selflink); + } + + // Remove all containers that may have + // been added outside of #page_content + $('body').children() + .not('#pma_navigation') + .not('#floating_menubar') + .not('#page_nav_icons') + .not('#page_content') + .not('#selflink') + .not('#pma_header') + .not('#pma_footer') + .not('#pma_demo') + .not('#pma_console_container') + .not('#prefs_autoload') + .remove(); + // Replace #page_content with new content + if (data.message && data.message.length > 0) { + $('#page_content').replaceWith( + '
      ' + data.message + '
      ' + ); + PMA_highlightSQL($('#page_content')); + checkNumberOfFields(); + } + + if (data._selflink) { + var source = data._selflink.split('?')[0]; + // Check for faulty links + $selflink_replace = { + 'import.php': 'tbl_sql.php', + 'tbl_chart.php': 'sql.php', + 'tbl_gis_visualization.php': 'sql.php' + }; + if ($selflink_replace[source]) { + var replacement = $selflink_replace[source]; + data._selflink = data._selflink.replace(source, replacement); + } + $('#selflink').find('> a').attr('href', data._selflink); + } + if (data._params) { + PMA_commonParams.setAll(data._params); + } + if (data._scripts) { + AJAX.scriptHandler.load(data._scripts); + } + if (data._selflink && data._scripts && data._menuHash && data._params) { + if (! (history && history.pushState)) { + PMA_MicroHistory.add( + data._selflink, + data._scripts, + data._menuHash, + data._params, + AJAX.source.attr('rel') + ); + } + } + if (data._displayMessage) { + $('#page_content').prepend(data._displayMessage); + PMA_highlightSQL($('#page_content')); + } + + $('#pma_errors').remove(); + + var msg = ''; + if (data._errSubmitMsg) { + msg = data._errSubmitMsg; + } + if (data._errors) { + $('
      ', { id : 'pma_errors', class : 'clearfloat' }) + .insertAfter('#selflink') + .append(data._errors); + // bind for php error reporting forms (bottom) + $('#pma_ignore_errors_bottom').on('click', function (e) { + e.preventDefault(); + PMA_ignorePhpErrors(); + }); + $('#pma_ignore_all_errors_bottom').on('click', function (e) { + e.preventDefault(); + PMA_ignorePhpErrors(false); + }); + // In case of 'sendErrorReport'='always' + // submit the hidden error reporting form. + if (data._sendErrorAlways === '1' && + data._stopErrorReportLoop !== '1' + ) { + $('#pma_report_errors_form').submit(); + PMA_ajaxShowMessage(PMA_messages.phpErrorsBeingSubmitted, false); + $('html, body').animate({ scrollTop:$(document).height() }, 'slow'); + } else if (data._promptPhpErrors) { + // otherwise just prompt user if it is set so. + msg = msg + PMA_messages.phpErrorsFound; + // scroll to bottom where all the errors are displayed. + $('html, body').animate({ scrollTop:$(document).height() }, 'slow'); + } + } + PMA_ajaxShowMessage(msg, false); + // bind for php error reporting forms (popup) + $('#pma_ignore_errors_popup').on('click', function () { + PMA_ignorePhpErrors(); + }); + $('#pma_ignore_all_errors_popup').on('click', function () { + PMA_ignorePhpErrors(false); + }); + + if (typeof AJAX._callback === 'function') { + AJAX._callback.call(); + } + AJAX._callback = function () {}; + }); + } else { + PMA_ajaxShowMessage(data.error, false); + AJAX.active = false; + AJAX.xhr = null; + PMA_handleRedirectAndReload(data); + if (data.fieldWithError) { + $(':input.error').removeClass('error'); + $('#' + data.fieldWithError).addClass('error'); + } + } + }, + /** + * This object is in charge of downloading scripts, + * keeping track of what's downloaded and firing + * the onload event for them when the page is ready. + */ + scriptHandler: { + /** + * @var array _scripts The list of files already downloaded + */ + _scripts: [], + /** + * @var string _scriptsVersion version of phpMyAdmin from which the + * scripts have been loaded + */ + _scriptsVersion: null, + /** + * @var array _scriptsToBeLoaded The list of files that + * need to be downloaded + */ + _scriptsToBeLoaded: [], + /** + * @var array _scriptsToBeFired The list of files for which + * to fire the onload and unload events + */ + _scriptsToBeFired: [], + _scriptsCompleted: false, + /** + * Records that a file has been downloaded + * + * @param string file The filename + * @param string fire Whether this file will be registering + * onload/teardown events + * + * @return self For chaining + */ + add: function (file, fire) { + this._scripts.push(file); + if (fire) { + // Record whether to fire any events for the file + // This is necessary to correctly tear down the initial page + this._scriptsToBeFired.push(file); + } + return this; + }, + /** + * Download a list of js files in one request + * + * @param array files An array of filenames and flags + * + * @return void + */ + load: function (files, callback) { + var self = this; + var i; + // Clear loaded scripts if they are from another version of phpMyAdmin. + // Depends on common params being set before loading scripts in responseHandler + if (self._scriptsVersion === null) { + self._scriptsVersion = PMA_commonParams.get('PMA_VERSION'); + } else if (self._scriptsVersion !== PMA_commonParams.get('PMA_VERSION')) { + self._scripts = []; + self._scriptsVersion = PMA_commonParams.get('PMA_VERSION'); + } + self._scriptsCompleted = false; + self._scriptsToBeFired = []; + // We need to first complete list of files to load + // as next loop will directly fire requests to load them + // and that triggers removal of them from + // self._scriptsToBeLoaded + for (i in files) { + self._scriptsToBeLoaded.push(files[i].name); + if (files[i].fire) { + self._scriptsToBeFired.push(files[i].name); + } + } + for (i in files) { + var script = files[i].name; + // Only for scripts that we don't already have + if ($.inArray(script, self._scripts) === -1) { + this.add(script); + this.appendScript(script, callback); + } else { + self.done(script, callback); + } + } + // Trigger callback if there is nothing else to load + self.done(null, callback); + }, + /** + * Called whenever all files are loaded + * + * @return void + */ + done: function (script, callback) { + if (typeof ErrorReport !== 'undefined') { + ErrorReport.wrap_global_functions(); + } + if ($.inArray(script, this._scriptsToBeFired)) { + AJAX.fireOnload(script); + } + if ($.inArray(script, this._scriptsToBeLoaded)) { + this._scriptsToBeLoaded.splice($.inArray(script, this._scriptsToBeLoaded), 1); + } + if (script === null) { + this._scriptsCompleted = true; + } + /* We need to wait for last signal (with null) or last script load */ + AJAX.active = (this._scriptsToBeLoaded.length > 0) || ! this._scriptsCompleted; + /* Run callback on last script */ + if (! AJAX.active && $.isFunction(callback)) { + callback(); + } + }, + /** + * Appends a script element to the head to load the scripts + * + * @return void + */ + appendScript: function (name, callback) { + var head = document.head || document.getElementsByTagName('head')[0]; + var script = document.createElement('script'); + var self = this; + + script.type = 'text/javascript'; + script.src = 'js/' + name + '?' + 'v=' + encodeURIComponent(PMA_commonParams.get('PMA_VERSION')); + script.async = false; + script.onload = function () { + self.done(name, callback); + }; + head.appendChild(script); + }, + /** + * Fires all the teardown event handlers for the current page + * and rebinds all forms and links to the request handler + * + * @param function callback The callback to call after resetting + * + * @return void + */ + reset: function (callback) { + for (var i in this._scriptsToBeFired) { + AJAX.fireTeardown(this._scriptsToBeFired[i]); + } + this._scriptsToBeFired = []; + /** + * Re-attach a generic event handler to clicks + * on pages and submissions of forms + */ + $(document).off('click', 'a').on('click', 'a', AJAX.requestHandler); + $(document).off('submit', 'form').on('submit', 'form', AJAX.requestHandler); + if (! (history && history.pushState)) { + PMA_MicroHistory.update(); + } + callback(); + } + } +}; + +/** + * Here we register a function that will remove the onsubmit event from all + * forms that will be handled by the generic page loader. We then save this + * event handler in the "jQuery data", so that we can fire it up later in + * AJAX.requestHandler(). + * + * See bug #3583316 + */ +AJAX.registerOnload('functions.js', function () { + // Registering the onload event for functions.js + // ensures that it will be fired for all pages + $('form').not('.ajax').not('.disableAjax').each(function () { + if ($(this).attr('onsubmit')) { + $(this).data('onsubmit', this.onsubmit).attr('onsubmit', ''); + } + }); + + var $page_content = $('#page_content'); + /** + * Workaround for passing submit button name,value on ajax form submit + * by appending hidden element with submit button name and value. + */ + $page_content.on('click', 'form input[type=submit]', function () { + var buttonName = $(this).attr('name'); + if (typeof buttonName === 'undefined') { + return; + } + $(this).closest('form').append($('', { + 'type' : 'hidden', + 'name' : buttonName, + 'value': $(this).val() + })); + }); + + /** + * Attach event listener to events when user modify visible + * Input,Textarea and select fields to make changes in forms + */ + $page_content.on( + 'keyup change', + 'form.lock-page textarea, ' + + 'form.lock-page input[type="text"], ' + + 'form.lock-page input[type="number"], ' + + 'form.lock-page select', + { value:1 }, + AJAX.lockPageHandler + ); + $page_content.on( + 'change', + 'form.lock-page input[type="checkbox"], ' + + 'form.lock-page input[type="radio"]', + { value:2 }, + AJAX.lockPageHandler + ); + /** + * Reset lock when lock-page form reset event is fired + * Note: reset does not bubble in all browser so attach to + * form directly. + */ + $('form.lock-page').on('reset', function (event) { + AJAX.resetLock(); + }); +}); + +/** + * Page load event handler + */ +$(function () { + var menuContent = $('
      ') + .append($('#serverinfo').clone()) + .append($('#topmenucontainer').clone()) + .html(); + if (history && history.pushState) { + // set initial state reload + var initState = ('state' in window.history && window.history.state !== null); + var initURL = $('#selflink').find('> a').attr('href') || location.href; + var state = { + url : initURL, + menu : menuContent + }; + history.replaceState(state, null); + + $(window).on('popstate', function (event) { + var initPop = (! initState && location.href === initURL); + initState = true; + // check if popstate fired on first page itself + if (initPop) { + return; + } + var state = event.originalEvent.state; + if (state && state.menu) { + AJAX.$msgbox = PMA_ajaxShowMessage(); + var params = 'ajax_request=true' + PMA_commonParams.get('arg_separator') + 'ajax_page_request=true'; + var url = state.url || location.href; + $.get(url, params, AJAX.responseHandler); + // TODO: Check if sometimes menu is not retrieved from server, + // Not sure but it seems menu was missing only for printview which + // been removed lately, so if it's right some dead menu checks/fallbacks + // may need to be removed from this file and Header.php + // AJAX.handleMenu.replace(event.originalEvent.state.menu); + } + }); + } else { + // Fallback to microhistory mechanism + AJAX.scriptHandler + .load([{ 'name' : 'microhistory.js', 'fire' : 1 }], function () { + // The cache primer is set by the footer class + if (PMA_MicroHistory.primer.url) { + PMA_MicroHistory.menus.add( + PMA_MicroHistory.primer.menuHash, + menuContent + ); + } + $(function () { + // Queue up this event twice to make sure that we get a copy + // of the page after all other onload events have been fired + if (PMA_MicroHistory.primer.url) { + PMA_MicroHistory.add( + PMA_MicroHistory.primer.url, + PMA_MicroHistory.primer.scripts, + PMA_MicroHistory.primer.menuHash + ); + } + }); + }); + } +}); + +/** + * Attach a generic event handler to clicks + * on pages and submissions of forms + */ +$(document).on('click', 'a', AJAX.requestHandler); +$(document).on('submit', 'form', AJAX.requestHandler); + +/** + * Gracefully handle fatal server errors + * (e.g: 500 - Internal server error) + */ +$(document).ajaxError(function (event, request, settings) { + if (AJAX._debug) { + console.log('AJAX error: status=' + request.status + ', text=' + request.statusText); + } + // Don't handle aborted requests + if (request.status !== 0 || request.statusText !== 'abort') { + var details = ''; + var state = request.state(); + + if (request.status !== 0) { + details += '
      ' + escapeHtml(PMA_sprintf(PMA_messages.strErrorCode, request.status)) + '
      '; + } + details += '
      ' + escapeHtml(PMA_sprintf(PMA_messages.strErrorText, request.statusText + ' (' + state + ')')) + '
      '; + if (state === 'rejected' || state === 'timeout') { + details += '
      ' + escapeHtml(PMA_messages.strErrorConnection) + '
      '; + } + PMA_ajaxShowMessage( + '
      ' + + PMA_messages.strErrorProcessingRequest + + details + + '
      ', + false + ); + AJAX.active = false; + AJAX.xhr = null; + } +}); diff --git a/php/apps/phpmyadmin49/js/chart.js b/php/apps/phpmyadmin49/js/chart.js new file mode 100644 index 00000000..8145dac2 --- /dev/null +++ b/php/apps/phpmyadmin49/js/chart.js @@ -0,0 +1,676 @@ +/** + * Chart type enumerations + */ +var ChartType = { + LINE : 'line', + SPLINE : 'spline', + AREA : 'area', + BAR : 'bar', + COLUMN : 'column', + PIE : 'pie', + TIMELINE: 'timeline', + SCATTER: 'scatter' +}; + +/** + * Column type enumeration + */ +var ColumnType = { + STRING : 'string', + NUMBER : 'number', + BOOLEAN : 'boolean', + DATE : 'date' +}; + +/** + * Abstract chart factory which defines the contract for chart factories + */ +var ChartFactory = function () { +}; +ChartFactory.prototype = { + createChart : function (type, options) { + throw new Error('createChart must be implemented by a subclass'); + } +}; + +/** + * Abstract chart which defines the contract for charts + * + * @param elementId + * id of the div element the chart is drawn in + */ +var Chart = function (elementId) { + this.elementId = elementId; +}; +Chart.prototype = { + draw : function (data, options) { + throw new Error('draw must be implemented by a subclass'); + }, + redraw : function (options) { + throw new Error('redraw must be implemented by a subclass'); + }, + destroy : function () { + throw new Error('destroy must be implemented by a subclass'); + }, + toImageString : function () { + throw new Error('toImageString must be implemented by a subclass'); + } +}; + +/** + * Abstract representation of charts that operates on DataTable where,
      + *
        + *
      • First column provides index to the data.
      • + *
      • Each subsequent columns are of type + * ColumnType.NUMBER and represents a data series.
      • + *
      + * Line chart, area chart, bar chart, column chart are typical examples. + * + * @param elementId + * id of the div element the chart is drawn in + */ +var BaseChart = function (elementId) { + Chart.call(this, elementId); +}; +BaseChart.prototype = new Chart(); +BaseChart.prototype.constructor = BaseChart; +BaseChart.prototype.validateColumns = function (dataTable) { + var columns = dataTable.getColumns(); + if (columns.length < 2) { + throw new Error('Minimum of two columns are required for this chart'); + } + for (var i = 1; i < columns.length; i++) { + if (columns[i].type !== ColumnType.NUMBER) { + throw new Error('Column ' + (i + 1) + ' should be of type \'Number\''); + } + } + return true; +}; + +/** + * Abstract pie chart + * + * @param elementId + * id of the div element the chart is drawn in + */ +var PieChart = function (elementId) { + BaseChart.call(this, elementId); +}; +PieChart.prototype = new BaseChart(); +PieChart.prototype.constructor = PieChart; +PieChart.prototype.validateColumns = function (dataTable) { + var columns = dataTable.getColumns(); + if (columns.length > 2) { + throw new Error('Pie charts can draw only one series'); + } + return BaseChart.prototype.validateColumns.call(this, dataTable); +}; + +/** + * Abstract timeline chart + * + * @param elementId + * id of the div element the chart is drawn in + */ +var TimelineChart = function (elementId) { + BaseChart.call(this, elementId); +}; +TimelineChart.prototype = new BaseChart(); +TimelineChart.prototype.constructor = TimelineChart; +TimelineChart.prototype.validateColumns = function (dataTable) { + var result = BaseChart.prototype.validateColumns.call(this, dataTable); + if (result) { + var columns = dataTable.getColumns(); + if (columns[0].type !== ColumnType.DATE) { + throw new Error('First column of timeline chart need to be a date column'); + } + } + return result; +}; + +/** + * Abstract scatter chart + * + * @param elementId + * id of the div element the chart is drawn in + */ +var ScatterChart = function (elementId) { + BaseChart.call(this, elementId); +}; +ScatterChart.prototype = new BaseChart(); +ScatterChart.prototype.constructor = ScatterChart; +ScatterChart.prototype.validateColumns = function (dataTable) { + var result = BaseChart.prototype.validateColumns.call(this, dataTable); + if (result) { + var columns = dataTable.getColumns(); + if (columns[0].type !== ColumnType.NUMBER) { + throw new Error('First column of scatter chart need to be a numeric column'); + } + } + return result; +}; + +/** + * The data table contains column information and data for the chart. + */ +var DataTable = function () { + var columns = []; + var data = null; + + this.addColumn = function (type, name) { + columns.push({ + 'type' : type, + 'name' : name + }); + }; + + this.getColumns = function () { + return columns; + }; + + this.setData = function (rows) { + data = rows; + fillMissingValues(); + }; + + this.getData = function () { + return data; + }; + + var fillMissingValues = function () { + if (columns.length === 0) { + throw new Error('Set columns first'); + } + var row; + for (var i = 0; i < data.length; i++) { + row = data[i]; + if (row.length > columns.length) { + row.splice(columns.length - 1, row.length - columns.length); + } else if (row.length < columns.length) { + for (var j = row.length; j < columns.length; j++) { + row.push(null); + } + } + } + }; +}; + +/** ***************************************************************************** + * JQPlot specific code + ******************************************************************************/ + +/** + * Abstract JQplot chart + * + * @param elementId + * id of the div element the chart is drawn in + */ +var JQPlotChart = function (elementId) { + Chart.call(this, elementId); + this.plot = null; + this.validator = null; +}; +JQPlotChart.prototype = new Chart(); +JQPlotChart.prototype.constructor = JQPlotChart; +JQPlotChart.prototype.draw = function (data, options) { + if (this.validator.validateColumns(data)) { + this.plot = $.jqplot(this.elementId, this.prepareData(data), this + .populateOptions(data, options)); + } +}; +JQPlotChart.prototype.destroy = function () { + if (this.plot !== null) { + this.plot.destroy(); + } +}; +JQPlotChart.prototype.redraw = function (options) { + if (this.plot !== null) { + this.plot.replot(options); + } +}; +JQPlotChart.prototype.toImageString = function (options) { + if (this.plot !== null) { + return $('#' + this.elementId).jqplotToImageStr({}); + } +}; +JQPlotChart.prototype.populateOptions = function (dataTable, options) { + throw new Error('populateOptions must be implemented by a subclass'); +}; +JQPlotChart.prototype.prepareData = function (dataTable) { + throw new Error('prepareData must be implemented by a subclass'); +}; + +/** + * JQPlot line chart + * + * @param elementId + * id of the div element the chart is drawn in + */ +var JQPlotLineChart = function (elementId) { + JQPlotChart.call(this, elementId); + this.validator = BaseChart.prototype; +}; +JQPlotLineChart.prototype = new JQPlotChart(); +JQPlotLineChart.prototype.constructor = JQPlotLineChart; + +JQPlotLineChart.prototype.populateOptions = function (dataTable, options) { + var columns = dataTable.getColumns(); + var optional = { + axes : { + xaxis : { + label : columns[0].name, + renderer : $.jqplot.CategoryAxisRenderer, + ticks : [] + }, + yaxis : { + label : (columns.length === 2 ? columns[1].name : 'Values'), + labelRenderer : $.jqplot.CanvasAxisLabelRenderer + } + }, + highlighter: { + show: true, + tooltipAxes: 'y', + formatString:'%d' + }, + series : [] + }; + $.extend(true, optional, options); + + if (optional.series.length === 0) { + for (var i = 1; i < columns.length; i++) { + optional.series.push({ + label : columns[i].name.toString() + }); + } + } + if (optional.axes.xaxis.ticks.length === 0) { + var data = dataTable.getData(); + for (var j = 0; j < data.length; j++) { + optional.axes.xaxis.ticks.push(data[j][0].toString()); + } + } + return optional; +}; + +JQPlotLineChart.prototype.prepareData = function (dataTable) { + var data = dataTable.getData(); + var row; + var retData = []; + var retRow; + for (var i = 0; i < data.length; i++) { + row = data[i]; + for (var j = 1; j < row.length; j++) { + retRow = retData[j - 1]; + if (retRow === undefined) { + retRow = []; + retData[j - 1] = retRow; + } + retRow.push(row[j]); + } + } + return retData; +}; + +/** + * JQPlot spline chart + * + * @param elementId + * id of the div element the chart is drawn in + */ +var JQPlotSplineChart = function (elementId) { + JQPlotLineChart.call(this, elementId); +}; +JQPlotSplineChart.prototype = new JQPlotLineChart(); +JQPlotSplineChart.prototype.constructor = JQPlotSplineChart; + +JQPlotSplineChart.prototype.populateOptions = function (dataTable, options) { + var optional = {}; + var opt = JQPlotLineChart.prototype.populateOptions.call(this, dataTable, + options); + var compulsory = { + seriesDefaults : { + rendererOptions : { + smooth : true + } + } + }; + $.extend(true, optional, opt, compulsory); + return optional; +}; + +/** + * JQPlot scatter chart + * + * @param elementId + * id of the div element the chart is drawn in + */ +var JQPlotScatterChart = function (elementId) { + JQPlotChart.call(this, elementId); + this.validator = ScatterChart.prototype; +}; +JQPlotScatterChart.prototype = new JQPlotChart(); +JQPlotScatterChart.prototype.constructor = JQPlotScatterChart; + +JQPlotScatterChart.prototype.populateOptions = function (dataTable, options) { + var columns = dataTable.getColumns(); + var optional = { + axes : { + xaxis : { + label : columns[0].name + }, + yaxis : { + label : (columns.length === 2 ? columns[1].name : 'Values'), + labelRenderer : $.jqplot.CanvasAxisLabelRenderer + } + }, + highlighter: { + show: true, + tooltipAxes: 'xy', + formatString:'%d, %d' + }, + series : [] + }; + for (var i = 1; i < columns.length; i++) { + optional.series.push({ + label : columns[i].name.toString() + }); + } + + var compulsory = { + seriesDefaults : { + showLine: false, + markerOptions: { + size: 7, + style: 'x' + } + } + }; + + $.extend(true, optional, options, compulsory); + return optional; +}; + +JQPlotScatterChart.prototype.prepareData = function (dataTable) { + var data = dataTable.getData(); + var row; + var retData = []; + var retRow; + for (var i = 0; i < data.length; i++) { + row = data[i]; + if (row[0]) { + for (var j = 1; j < row.length; j++) { + retRow = retData[j - 1]; + if (retRow === undefined) { + retRow = []; + retData[j - 1] = retRow; + } + retRow.push([row[0], row[j]]); + } + } + } + return retData; +}; + +/** + * JQPlot timeline chart + * + * @param elementId + * id of the div element the chart is drawn in + */ +var JQPlotTimelineChart = function (elementId) { + JQPlotLineChart.call(this, elementId); + this.validator = TimelineChart.prototype; +}; +JQPlotTimelineChart.prototype = new JQPlotLineChart(); +JQPlotTimelineChart.prototype.constructor = JQPlotTimelineChart; + +JQPlotTimelineChart.prototype.populateOptions = function (dataTable, options) { + var optional = { + axes : { + xaxis : { + tickOptions : { + formatString: '%b %#d, %y' + } + } + } + }; + var opt = JQPlotLineChart.prototype.populateOptions.call(this, dataTable, options); + var compulsory = { + axes : { + xaxis : { + renderer : $.jqplot.DateAxisRenderer + } + } + }; + $.extend(true, optional, opt, compulsory); + return optional; +}; + +JQPlotTimelineChart.prototype.prepareData = function (dataTable) { + var data = dataTable.getData(); + var row; + var d; + var retData = []; + var retRow; + for (var i = 0; i < data.length; i++) { + row = data[i]; + d = row[0]; + for (var j = 1; j < row.length; j++) { + retRow = retData[j - 1]; + if (retRow === undefined) { + retRow = []; + retData[j - 1] = retRow; + } + if (d !== null) { + retRow.push([d.getTime(), row[j]]); + } + } + } + return retData; +}; + +/** + * JQPlot area chart + * + * @param elementId + * id of the div element the chart is drawn in + */ +var JQPlotAreaChart = function (elementId) { + JQPlotLineChart.call(this, elementId); +}; +JQPlotAreaChart.prototype = new JQPlotLineChart(); +JQPlotAreaChart.prototype.constructor = JQPlotAreaChart; + +JQPlotAreaChart.prototype.populateOptions = function (dataTable, options) { + var optional = { + seriesDefaults : { + fillToZero : true + } + }; + var opt = JQPlotLineChart.prototype.populateOptions.call(this, dataTable, + options); + var compulsory = { + seriesDefaults : { + fill : true + } + }; + $.extend(true, optional, opt, compulsory); + return optional; +}; + +/** + * JQPlot column chart + * + * @param elementId + * id of the div element the chart is drawn in + */ +var JQPlotColumnChart = function (elementId) { + JQPlotLineChart.call(this, elementId); +}; +JQPlotColumnChart.prototype = new JQPlotLineChart(); +JQPlotColumnChart.prototype.constructor = JQPlotColumnChart; + +JQPlotColumnChart.prototype.populateOptions = function (dataTable, options) { + var optional = { + seriesDefaults : { + fillToZero : true + } + }; + var opt = JQPlotLineChart.prototype.populateOptions.call(this, dataTable, + options); + var compulsory = { + seriesDefaults : { + renderer : $.jqplot.BarRenderer + } + }; + $.extend(true, optional, opt, compulsory); + return optional; +}; + +/** + * JQPlot bar chart + * + * @param elementId + * id of the div element the chart is drawn in + */ +var JQPlotBarChart = function (elementId) { + JQPlotLineChart.call(this, elementId); +}; +JQPlotBarChart.prototype = new JQPlotLineChart(); +JQPlotBarChart.prototype.constructor = JQPlotBarChart; + +JQPlotBarChart.prototype.populateOptions = function (dataTable, options) { + var columns = dataTable.getColumns(); + var optional = { + axes : { + yaxis : { + label : columns[0].name, + labelRenderer : $.jqplot.CanvasAxisLabelRenderer, + renderer : $.jqplot.CategoryAxisRenderer, + ticks : [] + }, + xaxis : { + label : (columns.length === 2 ? columns[1].name : 'Values'), + labelRenderer : $.jqplot.CanvasAxisLabelRenderer + } + }, + highlighter: { + show: true, + tooltipAxes: 'x', + formatString:'%d' + }, + series : [], + seriesDefaults : { + fillToZero : true + } + }; + var compulsory = { + seriesDefaults : { + renderer : $.jqplot.BarRenderer, + rendererOptions : { + barDirection : 'horizontal' + } + } + }; + $.extend(true, optional, options, compulsory); + + if (optional.axes.yaxis.ticks.length === 0) { + var data = dataTable.getData(); + for (var i = 0; i < data.length; i++) { + optional.axes.yaxis.ticks.push(data[i][0].toString()); + } + } + if (optional.series.length === 0) { + for (var j = 1; j < columns.length; j++) { + optional.series.push({ + label : columns[j].name.toString() + }); + } + } + return optional; +}; + +/** + * JQPlot pie chart + * + * @param elementId + * id of the div element the chart is drawn in + */ +var JQPlotPieChart = function (elementId) { + JQPlotChart.call(this, elementId); + this.validator = PieChart.prototype; +}; +JQPlotPieChart.prototype = new JQPlotChart(); +JQPlotPieChart.prototype.constructor = JQPlotPieChart; + +JQPlotPieChart.prototype.populateOptions = function (dataTable, options) { + var optional = { + highlighter: { + show: true, + tooltipAxes: 'xy', + formatString:'%s, %d', + useAxesFormatters: false + }, + legend: { + renderer: $.jqplot.EnhancedPieLegendRenderer, + }, + }; + var compulsory = { + seriesDefaults : { + shadow: false, + renderer : $.jqplot.PieRenderer, + rendererOptions: { sliceMargin: 1, showDataLabels: true } + } + }; + $.extend(true, optional, options, compulsory); + return optional; +}; + +JQPlotPieChart.prototype.prepareData = function (dataTable) { + var data = dataTable.getData(); + var row; + var retData = []; + for (var i = 0; i < data.length; i++) { + row = data[i]; + retData.push([row[0], row[1]]); + } + return [retData]; +}; + +/** + * Chart factory that returns JQPlotCharts + */ +var JQPlotChartFactory = function () { +}; +JQPlotChartFactory.prototype = new ChartFactory(); +JQPlotChartFactory.prototype.createChart = function (type, elementId) { + var chart = null; + switch (type) { + case ChartType.LINE: + chart = new JQPlotLineChart(elementId); + break; + case ChartType.SPLINE: + chart = new JQPlotSplineChart(elementId); + break; + case ChartType.TIMELINE: + chart = new JQPlotTimelineChart(elementId); + break; + case ChartType.AREA: + chart = new JQPlotAreaChart(elementId); + break; + case ChartType.BAR: + chart = new JQPlotBarChart(elementId); + break; + case ChartType.COLUMN: + chart = new JQPlotColumnChart(elementId); + break; + case ChartType.PIE: + chart = new JQPlotPieChart(elementId); + break; + case ChartType.SCATTER: + chart = new JQPlotScatterChart(elementId); + break; + } + + return chart; +}; diff --git a/php/apps/phpmyadmin49/js/codemirror/addon/lint/sql-lint.js b/php/apps/phpmyadmin49/js/codemirror/addon/lint/sql-lint.js new file mode 100644 index 00000000..800c8d7a --- /dev/null +++ b/php/apps/phpmyadmin49/js/codemirror/addon/lint/sql-lint.js @@ -0,0 +1,38 @@ +CodeMirror.sqlLint = function (text, updateLinting, options, cm) { + // Skipping check if text box is empty. + if (text.trim() === '') { + updateLinting(cm, []); + return; + } + + function handleResponse (response) { + var found = []; + for (var idx in response) { + found.push({ + from: CodeMirror.Pos( + response[idx].fromLine, response[idx].fromColumn + ), + to: CodeMirror.Pos( + response[idx].toLine, response[idx].toColumn + ), + messageHTML: response[idx].message, + severity : response[idx].severity + }); + } + + updateLinting(cm, found); + } + + $.ajax({ + method: 'POST', + url: 'lint.php', + dataType: 'json', + data: { + sql_query: text, + server: PMA_commonParams.get('server'), + options: options.lintOptions, + no_history: true, + }, + success: handleResponse + }); +}; diff --git a/php/apps/phpmyadmin49/js/common.js b/php/apps/phpmyadmin49/js/common.js new file mode 100644 index 00000000..28b394b5 --- /dev/null +++ b/php/apps/phpmyadmin49/js/common.js @@ -0,0 +1,550 @@ +/* vim: set expandtab sw=4 ts=4 sts=4: */ + +$(function () { + checkNumberOfFields(); +}); + +/** + * Holds common parameters such as server, db, table, etc + * + * The content for this is normally loaded from Header.php or + * Response.php and executed by ajax.js + */ +var PMA_commonParams = (function () { + /** + * @var hash params An associative array of key value pairs + * @access private + */ + var params = {}; + // The returned object is the public part of the module + return { + /** + * Saves all the key value pair that + * are provided in the input array + * + * @param obj hash The input array + * + * @return void + */ + setAll: function (obj) { + var reload = false; + var updateNavigation = false; + for (var i in obj) { + if (params[i] !== undefined && params[i] !== obj[i]) { + if (i === 'db' || i === 'table') { + updateNavigation = true; + } + reload = true; + } + params[i] = obj[i]; + } + if (updateNavigation && + $('#pma_navigation_tree').hasClass('synced') + ) { + PMA_showCurrentNavigation(); + } + }, + /** + * Retrieves a value given its key + * Returns empty string for undefined values + * + * @param name string The key + * + * @return string + */ + get: function (name) { + return params[name]; + }, + /** + * Saves a single key value pair + * + * @param name string The key + * @param value string The value + * + * @return self For chainability + */ + set: function (name, value) { + var updateNavigation = false; + if (name === 'db' || name === 'table' && + params[name] !== value + ) { + updateNavigation = true; + } + params[name] = value; + if (updateNavigation && + $('#pma_navigation_tree').hasClass('synced') + ) { + PMA_showCurrentNavigation(); + } + return this; + }, + /** + * Returns the url query string using the saved parameters + * + * @return string + */ + getUrlQuery: function () { + var common = this.get('common_query'); + var separator = '?'; + var argsep = PMA_commonParams.get('arg_separator'); + if (common.length > 0) { + separator = argsep; + } + return PMA_sprintf( + '%s%sserver=%s' + argsep + 'db=%s' + argsep + 'table=%s', + this.get('common_query'), + separator, + encodeURIComponent(this.get('server')), + encodeURIComponent(this.get('db')), + encodeURIComponent(this.get('table')) + ); + } + }; +}()); + +/** + * Holds common parameters such as server, db, table, etc + * + * The content for this is normally loaded from Header.php or + * Response.php and executed by ajax.js + */ +var PMA_commonActions = { + /** + * Saves the database name when it's changed + * and reloads the query window, if necessary + * + * @param new_db string new_db The name of the new database + * + * @return void + */ + setDb: function (new_db) { + if (new_db !== PMA_commonParams.get('db')) { + PMA_commonParams.setAll({ 'db': new_db, 'table': '' }); + } + }, + /** + * Opens a database in the main part of the page + * + * @param new_db string The name of the new database + * + * @return void + */ + openDb: function (new_db) { + PMA_commonParams + .set('db', new_db) + .set('table', ''); + this.refreshMain( + PMA_commonParams.get('opendb_url') + ); + }, + /** + * Refreshes the main frame + * + * @param mixed url Undefined to refresh to the same page + * String to go to a different page, e.g: 'index.php' + * + * @return void + */ + refreshMain: function (url, callback) { + if (! url) { + url = $('#selflink').find('a').attr('href') || window.location.pathname; + url = url.substring(0, url.indexOf('?')); + } + url += PMA_commonParams.getUrlQuery(); + $('', { href: url }) + .appendTo('body') + .click() + .remove(); + AJAX._callback = callback; + } +}; + +/** + * Class to handle PMA Drag and Drop Import + * feature + */ +PMA_DROP_IMPORT = { + /** + * @var int, count of total uploads in this view + */ + uploadCount: 0, + /** + * @var int, count of live uploads + */ + liveUploadCount: 0, + /** + * @var string array, allowed extensions + */ + allowedExtensions: ['sql', 'xml', 'ldi', 'mediawiki', 'shp'], + /** + * @var string array, allowed extensions for compressed files + */ + allowedCompressedExtensions: ['gz', 'bz2', 'zip'], + /** + * @var obj array to store message returned by import_status.php + */ + importStatus: [], + /** + * Checks if any dropped file has valid extension or not + * + * @param file filename + * + * @return string, extension for valid extension, '' otherwise + */ + _getExtension: function (file) { + var arr = file.split('.'); + ext = arr[arr.length - 1]; + + // check if compressed + if (jQuery.inArray(ext.toLowerCase(), + PMA_DROP_IMPORT.allowedCompressedExtensions) !== -1) { + ext = arr[arr.length - 2]; + } + + // Now check for extension + if (jQuery.inArray(ext.toLowerCase(), + PMA_DROP_IMPORT.allowedExtensions) !== -1) { + return ext; + } + return ''; + }, + /** + * Shows upload progress for different sql uploads + * + * @param: hash (string), hash for specific file upload + * @param: percent (float), file upload percentage + * + * @return void + */ + _setProgress: function (hash, percent) { + $('.pma_sql_import_status div li[data-hash="' + hash + '"]') + .children('progress').val(percent); + }, + /** + * Function to upload the file asynchronously + * + * @param formData FormData object for a specific file + * @param hash hash of the current file upload + * + * @return void + */ + _sendFileToServer: function (formData, hash) { + var uploadURL = './import.php'; // Upload URL + var extraData = {}; + var jqXHR = $.ajax({ + xhr: function () { + var xhrobj = $.ajaxSettings.xhr(); + if (xhrobj.upload) { + xhrobj.upload.addEventListener('progress', function (event) { + var percent = 0; + var position = event.loaded || event.position; + var total = event.total; + if (event.lengthComputable) { + percent = Math.ceil(position / total * 100); + } + // Set progress + PMA_DROP_IMPORT._setProgress(hash, percent); + }, false); + } + return xhrobj; + }, + url: uploadURL, + type: 'POST', + contentType:false, + processData: false, + cache: false, + data: formData, + success: function (data) { + PMA_DROP_IMPORT._importFinished(hash, false, data.success); + if (!data.success) { + PMA_DROP_IMPORT.importStatus[PMA_DROP_IMPORT.importStatus.length] = { + hash: hash, + message: data.error + }; + } + } + }); + + // -- provide link to cancel the upload + $('.pma_sql_import_status div li[data-hash="' + hash + + '"] span.filesize').html('' + + PMA_messages.dropImportMessageCancel + ''); + + // -- add event listener to this link to abort upload operation + $('.pma_sql_import_status div li[data-hash="' + hash + + '"] span.filesize span.pma_drop_file_status') + .on('click', function () { + if ($(this).attr('task') === 'cancel') { + jqXHR.abort(); + $(this).html('' + PMA_messages.dropImportMessageAborted + ''); + PMA_DROP_IMPORT._importFinished(hash, true, false); + } else if ($(this).children('span').html() === + PMA_messages.dropImportMessageFailed) { + // -- view information + var $this = $(this); + $.each(PMA_DROP_IMPORT.importStatus, + function (key, value) { + if (value.hash === hash) { + $('.pma_drop_result:visible').remove(); + var filename = $this.parent('span').attr('data-filename'); + $('body').append('

      ' + + PMA_messages.dropImportImportResultHeader + ' - ' + + filename + 'x

      ' + value.message + '
      '); + $('.pma_drop_result').draggable(); // to make this dialog draggable + } + }); + } + }); + }, + /** + * Triggered when an object is dragged into the PMA UI + * + * @param event obj + * + * @return void + */ + _dragenter : function (event) { + // We don't want to prevent users from using + // browser's default drag-drop feature on some page(s) + if ($('.noDragDrop').length !== 0) { + return; + } + + event.stopPropagation(); + event.preventDefault(); + if (!PMA_DROP_IMPORT._hasFiles(event)) { + return; + } + if (PMA_commonParams.get('db') === '') { + $('.pma_drop_handler').html(PMA_messages.dropImportSelectDB); + } else { + $('.pma_drop_handler').html(PMA_messages.dropImportDropFiles); + } + $('.pma_drop_handler').fadeIn(); + }, + /** + * Check if dragged element contains Files + * + * @param event the event object + * + * @return bool + */ + _hasFiles: function (event) { + return !(typeof event.originalEvent.dataTransfer.types === 'undefined' || + $.inArray('Files', event.originalEvent.dataTransfer.types) < 0 || + $.inArray( + 'application/x-moz-nativeimage', + event.originalEvent.dataTransfer.types + ) >= 0); + }, + /** + * Triggered when dragged file is being dragged over PMA UI + * + * @param event obj + * + * @return void + */ + _dragover: function (event) { + // We don't want to prevent users from using + // browser's default drag-drop feature on some page(s) + if ($('.noDragDrop').length !== 0) { + return; + } + + event.stopPropagation(); + event.preventDefault(); + if (!PMA_DROP_IMPORT._hasFiles(event)) { + return; + } + $('.pma_drop_handler').fadeIn(); + }, + /** + * Triggered when dragged objects are left + * + * @param event obj + * + * @return void + */ + _dragleave: function (event) { + // We don't want to prevent users from using + // browser's default drag-drop feature on some page(s) + if ($('.noDragDrop').length !== 0) { + return; + } + event.stopPropagation(); + event.preventDefault(); + var $pma_drop_handler = $('.pma_drop_handler'); + $pma_drop_handler.clearQueue().stop(); + $pma_drop_handler.fadeOut(); + $pma_drop_handler.html(PMA_messages.dropImportDropFiles); + }, + /** + * Called when upload has finished + * + * @param string, unique hash for a certain upload + * @param bool, true if upload was aborted + * @param bool, status of sql upload, as sent by server + * + * @return void + */ + _importFinished: function (hash, aborted, status) { + $('.pma_sql_import_status div li[data-hash="' + hash + '"]') + .children('progress').hide(); + var icon = 'icon ic_s_success'; + // -- provide link to view upload status + if (!aborted) { + if (status) { + $('.pma_sql_import_status div li[data-hash="' + hash + + '"] span.filesize span.pma_drop_file_status') + .html('' + PMA_messages.dropImportMessageSuccess + '
      '); + } else { + $('.pma_sql_import_status div li[data-hash="' + hash + + '"] span.filesize span.pma_drop_file_status') + .html('' + PMA_messages.dropImportMessageFailed + + ''); + icon = 'icon ic_s_error'; + } + } else { + icon = 'icon ic_s_notice'; + } + $('.pma_sql_import_status div li[data-hash="' + hash + + '"] span.filesize span.pma_drop_file_status') + .attr('task', 'info'); + + // Set icon + $('.pma_sql_import_status div li[data-hash="' + hash + '"]') + .prepend(' '); + + // Decrease liveUploadCount by one + $('.pma_import_count').html(--PMA_DROP_IMPORT.liveUploadCount); + if (!PMA_DROP_IMPORT.liveUploadCount) { + $('.pma_sql_import_status h2 .close').fadeIn(); + } + }, + /** + * Triggered when dragged objects are dropped to UI + * From this function, the AJAX Upload operation is initiated + * + * @param event object + * + * @return void + */ + _drop: function (event) { + // We don't want to prevent users from using + // browser's default drag-drop feature on some page(s) + if ($('.noDragDrop').length !== 0) { + return; + } + + var dbname = PMA_commonParams.get('db'); + var server = PMA_commonParams.get('server'); + + // if no database is selected -- no + if (dbname !== '') { + var files = event.originalEvent.dataTransfer.files; + if (!files || files.length === 0) { + // No files actually transferred + $('.pma_drop_handler').fadeOut(); + event.stopPropagation(); + event.preventDefault(); + return; + } + $('.pma_sql_import_status').slideDown(); + for (var i = 0; i < files.length; i++) { + var ext = (PMA_DROP_IMPORT._getExtension(files[i].name)); + var hash = AJAX.hash(++PMA_DROP_IMPORT.uploadCount); + + var $pma_sql_import_status_div = $('.pma_sql_import_status div'); + $pma_sql_import_status_div.append('
    • ' + + ((ext !== '') ? '' : ' ') + + escapeHtml(files[i].name) + '' + (files[i].size / 1024).toFixed(2) + + ' kb
    • '); + + // scroll the UI to bottom + $pma_sql_import_status_div.scrollTop( + $pma_sql_import_status_div.scrollTop() + 50 + ); // 50 hardcoded for now + + if (ext !== '') { + // Increment liveUploadCount by one + $('.pma_import_count').html(++PMA_DROP_IMPORT.liveUploadCount); + $('.pma_sql_import_status h2 .close').fadeOut(); + + $('.pma_sql_import_status div li[data-hash="' + hash + '"]') + .append('
      '); + + // uploading + var fd = new FormData(); + fd.append('import_file', files[i]); + fd.append('noplugin', Math.random().toString(36).substring(2, 12)); + fd.append('db', dbname); + fd.append('server', server); + fd.append('token', PMA_commonParams.get('token')); + fd.append('import_type', 'database'); + // todo: method to find the value below + fd.append('MAX_FILE_SIZE', '4194304'); + // todo: method to find the value below + fd.append('charset_of_file','utf-8'); + // todo: method to find the value below + fd.append('allow_interrupt', 'yes'); + fd.append('skip_queries', '0'); + fd.append('format',ext); + fd.append('sql_compatibility','NONE'); + fd.append('sql_no_auto_value_on_zero','something'); + fd.append('ajax_request','true'); + fd.append('hash', hash); + + // init uploading + PMA_DROP_IMPORT._sendFileToServer(fd, hash); + } else if (!PMA_DROP_IMPORT.liveUploadCount) { + $('.pma_sql_import_status h2 .close').fadeIn(); + } + } + } + $('.pma_drop_handler').fadeOut(); + event.stopPropagation(); + event.preventDefault(); + } +}; + +/** + * Called when some user drags, dragover, leave + * a file to the PMA UI + * @param object Event data + * @return void + */ +$(document).on('dragenter', PMA_DROP_IMPORT._dragenter); +$(document).on('dragover', PMA_DROP_IMPORT._dragover); +$(document).on('dragleave', '.pma_drop_handler', PMA_DROP_IMPORT._dragleave); + +// when file is dropped to PMA UI +$(document).on('drop', 'body', PMA_DROP_IMPORT._drop); + +// minimizing-maximising the sql ajax upload status +$(document).on('click', '.pma_sql_import_status h2 .minimize', function () { + if ($(this).attr('toggle') === 'off') { + $('.pma_sql_import_status div').css('height','270px'); + $(this).attr('toggle','on'); + $(this).html('-'); // to minimize + } else { + $('.pma_sql_import_status div').css('height','0px'); + $(this).attr('toggle','off'); + $(this).html('+'); // to maximise + } +}); + +// closing sql ajax upload status +$(document).on('click', '.pma_sql_import_status h2 .close', function () { + $('.pma_sql_import_status').fadeOut(function () { + $('.pma_sql_import_status div').html(''); + PMA_DROP_IMPORT.importStatus = []; // clear the message array + }); +}); + +// Closing the import result box +$(document).on('click', '.pma_drop_result h2 .close', function () { + $(this).parent('h2').parent('div').remove(); +}); diff --git a/php/apps/phpmyadmin49/js/config.js b/php/apps/phpmyadmin49/js/config.js new file mode 100644 index 00000000..bd0eab46 --- /dev/null +++ b/php/apps/phpmyadmin49/js/config.js @@ -0,0 +1,886 @@ +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * Functions used in configuration forms and on user preferences pages + */ + +/** + * checks whether browser supports web storage + * + * @param type the type of storage i.e. localStorage or sessionStorage + * + * @returns bool + */ +function isStorageSupported (type, warn) { + try { + window[type].setItem('PMATest', 'test'); + // Check whether key-value pair was set successfully + if (window[type].getItem('PMATest') === 'test') { + // Supported, remove test variable from storage + window[type].removeItem('PMATest'); + return true; + } + } catch (error) { + // Not supported + if (warn) { + PMA_ajaxShowMessage(PMA_messages.strNoLocalStorage, false); + } + } + return false; +} + +/** + * Unbind all event handlers before tearing down a page + */ +AJAX.registerTeardown('config.js', function () { + $('.optbox input[id], .optbox select[id], .optbox textarea[id]').off('change').off('keyup'); + $('.optbox input[type=button][name=submit_reset]').off('click'); + $('div.tabs_contents').off(); + $('#import_local_storage, #export_local_storage').off('click'); + $('form.prefs-form').off('change').off('submit'); + $(document).off('click', 'div.click-hide-message'); + $('#prefs_autoload').find('a').off('click'); +}); + +AJAX.registerOnload('config.js', function () { + var $topmenu_upt = $('#topmenu2.user_prefs_tabs'); + $topmenu_upt.find('li.active a').attr('rel', 'samepage'); + $topmenu_upt.find('li:not(.active) a').attr('rel', 'newpage'); +}); + +// default values for fields +var defaultValues = {}; + +/** + * Returns field type + * + * @param {Element} field + */ +function getFieldType (field) { + var $field = $(field); + var tagName = $field.prop('tagName'); + if (tagName === 'INPUT') { + return $field.attr('type'); + } else if (tagName === 'SELECT') { + return 'select'; + } else if (tagName === 'TEXTAREA') { + return 'text'; + } + return ''; +} + +/** + * Enables or disables the "restore default value" button + * + * @param {Element} field + * @param {boolean} display + */ +function setRestoreDefaultBtn (field, display) { + var $el = $(field).closest('td').find('.restore-default img'); + $el[display ? 'show' : 'hide'](); +} + +/** + * Marks field depending on its value (system default or custom) + * + * @param {Element} field + */ +function markField (field) { + var $field = $(field); + var type = getFieldType($field); + var isDefault = checkFieldDefault($field, type); + + // checkboxes uses parent for marking + var $fieldMarker = (type === 'checkbox') ? $field.parent() : $field; + setRestoreDefaultBtn($field, !isDefault); + $fieldMarker[isDefault ? 'removeClass' : 'addClass']('custom'); +} + +/** + * Sets field value + * + * value must be of type: + * o undefined (omitted) - restore default value (form default, not PMA default) + * o String - if field_type is 'text' + * o boolean - if field_type is 'checkbox' + * o Array of values - if field_type is 'select' + * + * @param {Element} field + * @param {String} field_type see {@link #getFieldType} + * @param {String|Boolean} value + */ +function setFieldValue (field, field_type, value) { + var $field = $(field); + switch (field_type) { + case 'text': + case 'number': + $field.val(value); + break; + case 'checkbox': + $field.prop('checked', value); + break; + case 'select': + var options = $field.prop('options'); + var i; + var imax = options.length; + var i, imax = options.length; + for (i = 0; i < imax; i++) { + options[i].selected = (value.indexOf(options[i].value) != -1); + } + break; + } + markField($field); +} + +/** + * Gets field value + * + * Will return one of: + * o String - if type is 'text' + * o boolean - if type is 'checkbox' + * o Array of values - if type is 'select' + * + * @param {Element} field + * @param {String} field_type returned by {@link #getFieldType} + * @type Boolean|String|String[] + */ +function getFieldValue (field, field_type) { + var $field = $(field); + switch (field_type) { + case 'text': + case 'number': + return $field.prop('value'); + case 'checkbox': + return $field.prop('checked'); + case 'select': + var options = $field.prop('options'); + var i; + var imax = options.length; + var items = []; + for (i = 0; i < imax; i++) { + if (options[i].selected) { + items.push(options[i].value); + } + } + return items; + } + return null; +} + +/** + * Returns values for all fields in fieldsets + */ +function getAllValues () { + var $elements = $('fieldset input, fieldset select, fieldset textarea'); + var values = {}; + var type; + var value; + for (var i = 0; i < $elements.length; i++) { + type = getFieldType($elements[i]); + value = getFieldValue($elements[i], type); + if (typeof value !== 'undefined') { + // we only have single selects, fatten array + if (type === 'select') { + value = value[0]; + } + values[$elements[i].name] = value; + } + } + return values; +} + +/** + * Checks whether field has its default value + * + * @param {Element} field + * @param {String} type + * @return boolean + */ +function checkFieldDefault (field, type) { + var $field = $(field); + var field_id = $field.attr('id'); + if (typeof defaultValues[field_id] === 'undefined') { + return true; + } + var isDefault = true; + var currentValue = getFieldValue($field, type); + if (type !== 'select') { + isDefault = currentValue === defaultValues[field_id]; + } else { + // compare arrays, will work for our representation of select values + if (currentValue.length !== defaultValues[field_id].length) { + isDefault = false; + } else { + for (var i = 0; i < currentValue.length; i++) { + if (currentValue[i] !== defaultValues[field_id][i]) { + isDefault = false; + break; + } + } + } + } + return isDefault; +} + +/** + * Returns element's id prefix + * @param {Element} element + */ +function getIdPrefix (element) { + return $(element).attr('id').replace(/[^-]+$/, ''); +} + +// ------------------------------------------------------------------ +// Form validation and field operations +// + +// form validator assignments +var validate = {}; + +// form validator list +var validators = { + // regexp: numeric value + _regexp_numeric: /^[0-9]+$/, + // regexp: extract parts from PCRE expression + _regexp_pcre_extract: /(.)(.*)\1(.*)?/, + /** + * Validates positive number + * + * @param {boolean} isKeyUp + */ + PMA_validatePositiveNumber: function (isKeyUp) { + if (isKeyUp && this.value === '') { + return true; + } + var result = this.value !== '0' && validators._regexp_numeric.test(this.value); + return result ? true : PMA_messages.error_nan_p; + }, + /** + * Validates non-negative number + * + * @param {boolean} isKeyUp + */ + PMA_validateNonNegativeNumber: function (isKeyUp) { + if (isKeyUp && this.value === '') { + return true; + } + var result = validators._regexp_numeric.test(this.value); + return result ? true : PMA_messages.error_nan_nneg; + }, + /** + * Validates port number + * + * @param {boolean} isKeyUp + */ + PMA_validatePortNumber: function (isKeyUp) { + if (this.value === '') { + return true; + } + var result = validators._regexp_numeric.test(this.value) && this.value !== '0'; + return result && this.value <= 65535 ? true : PMA_messages.error_incorrect_port; + }, + /** + * Validates value according to given regular expression + * + * @param {boolean} isKeyUp + * @param {string} regexp + */ + PMA_validateByRegex: function (isKeyUp, regexp) { + if (isKeyUp && this.value === '') { + return true; + } + // convert PCRE regexp + var parts = regexp.match(validators._regexp_pcre_extract); + var valid = this.value.match(new RegExp(parts[2], parts[3])) !== null; + return valid ? true : PMA_messages.error_invalid_value; + }, + /** + * Validates upper bound for numeric inputs + * + * @param {boolean} isKeyUp + * @param {int} max_value + */ + PMA_validateUpperBound: function (isKeyUp, max_value) { + var val = parseInt(this.value, 10); + if (isNaN(val)) { + return true; + } + return val <= max_value ? true : PMA_sprintf(PMA_messages.error_value_lte, max_value); + }, + // field validators + _field: { + }, + // fieldset validators + _fieldset: { + } +}; + +/** + * Registers validator for given field + * + * @param {String} id field id + * @param {String} type validator (key in validators object) + * @param {boolean} onKeyUp whether fire on key up + * @param {Array} params validation function parameters + */ +function validateField (id, type, onKeyUp, params) { + if (typeof validators[type] === 'undefined') { + return; + } + if (typeof validate[id] === 'undefined') { + validate[id] = []; + } + if (validate[id].length === 0) { + validate[id].push([type, params, onKeyUp]); + } +} + +/** + * Returns validation functions associated with form field + * + * @param {String} field_id form field id + * @param {boolean} onKeyUpOnly see validateField + * @type Array + * @return array of [function, parameters to be passed to function] + */ +function getFieldValidators (field_id, onKeyUpOnly) { + // look for field bound validator + var name = field_id && field_id.match(/[^-]+$/)[0]; + if (typeof validators._field[name] !== 'undefined') { + return [[validators._field[name], null]]; + } + + // look for registered validators + var functions = []; + if (typeof validate[field_id] !== 'undefined') { + // validate[field_id]: array of [type, params, onKeyUp] + for (var i = 0, imax = validate[field_id].length; i < imax; i++) { + if (onKeyUpOnly && !validate[field_id][i][2]) { + continue; + } + functions.push([validators[validate[field_id][i][0]], validate[field_id][i][1]]); + } + } + + return functions; +} + +/** + * Displays errors for given form fields + * + * WARNING: created DOM elements must be identical with the ones made by + * PhpMyAdmin\Config\FormDisplayTemplate::displayInput()! + * + * @param {Object} error_list list of errors in the form {field id: error array} + */ +function displayErrors (error_list) { + var tempIsEmpty = function (item) { + return item !== ''; + }; + + for (var field_id in error_list) { + var errors = error_list[field_id]; + var $field = $('#' + field_id); + var isFieldset = $field.attr('tagName') === 'FIELDSET'; + var $errorCnt; + if (isFieldset) { + $errorCnt = $field.find('dl.errors'); + } else { + $errorCnt = $field.siblings('.inline_errors'); + } + + // remove empty errors (used to clear error list) + errors = $.grep(errors, tempIsEmpty); + + // CSS error class + if (!isFieldset) { + // checkboxes uses parent for marking + var $fieldMarker = ($field.attr('type') === 'checkbox') ? $field.parent() : $field; + $fieldMarker[errors.length ? 'addClass' : 'removeClass']('field-error'); + } + + if (errors.length) { + // if error container doesn't exist, create it + if ($errorCnt.length === 0) { + if (isFieldset) { + $errorCnt = $('
      '); + $field.find('table').before($errorCnt); + } else { + $errorCnt = $('
      '); + $field.closest('td').append($errorCnt); + } + } + + var html = ''; + for (var i = 0, imax = errors.length; i < imax; i++) { + html += '
      ' + errors[i] + '
      '; + } + $errorCnt.html(html); + } else if ($errorCnt !== null) { + // remove useless error container + $errorCnt.remove(); + } + } +} + +/** + * Validates fields and fieldsets and call displayError function as required + */ +function setDisplayError() { + var elements = $('.optbox input[id], .optbox select[id], .optbox textarea[id]'); + // run all field validators + var errors = {}; + for (var i = 0; i < elements.length; i++) { + validate_field(elements[i], false, errors); + } + // run all fieldset validators + $('fieldset.optbox').each(function () { + validate_fieldset(this, false, errors); + }); + displayErrors(errors); +} + +/** + * Validates fieldset and puts errors in 'errors' object + * + * @param {Element} fieldset + * @param {boolean} isKeyUp + * @param {Object} errors + */ +function validate_fieldset (fieldset, isKeyUp, errors) { + var $fieldset = $(fieldset); + if ($fieldset.length && typeof validators._fieldset[$fieldset.attr('id')] !== 'undefined') { + var fieldset_errors = validators._fieldset[$fieldset.attr('id')].apply($fieldset[0], [isKeyUp]); + for (var field_id in fieldset_errors) { + if (typeof errors[field_id] === 'undefined') { + errors[field_id] = []; + } + if (typeof fieldset_errors[field_id] === 'string') { + fieldset_errors[field_id] = [fieldset_errors[field_id]]; + } + $.merge(errors[field_id], fieldset_errors[field_id]); + } + } +} + +/** + * Validates form field and puts errors in 'errors' object + * + * @param {Element} field + * @param {boolean} isKeyUp + * @param {Object} errors + */ +function validate_field (field, isKeyUp, errors) { + var args; + var result; + var $field = $(field); + var field_id = $field.attr('id'); + errors[field_id] = []; + var functions = getFieldValidators(field_id, isKeyUp); + for (var i = 0; i < functions.length; i++) { + if (typeof functions[i][1] !== 'undefined' && functions[i][1] !== null) { + args = functions[i][1].slice(0); + } else { + args = []; + } + args.unshift(isKeyUp); + result = functions[i][0].apply($field[0], args); + if (result !== true) { + if (typeof result === 'string') { + result = [result]; + } + $.merge(errors[field_id], result); + } + } +} + +/** + * Validates form field and parent fieldset + * + * @param {Element} field + * @param {boolean} isKeyUp + */ +function validate_field_and_fieldset (field, isKeyUp) { + var $field = $(field); + var errors = {}; + validate_field($field, isKeyUp, errors); + validate_fieldset($field.closest('fieldset.optbox'), isKeyUp, errors); + displayErrors(errors); +} + +function loadInlineConfig () { + if (!Array.isArray(configInlineParams)) { + return; + } + for (var i = 0; i < configInlineParams.length; ++i) { + if (typeof configInlineParams[i] === 'function') { + configInlineParams[i](); + } + } +} + +function setupValidation () { + validate = {}; + configScriptLoaded = true; + if (configScriptLoaded && typeof configInlineParams !== 'undefined') { + loadInlineConfig(); + } + // register validators and mark custom values + var $elements = $('.optbox input[id], .optbox select[id], .optbox textarea[id]'); + $elements.each(function () { + markField(this); + var $el = $(this); + $el.on('change', function () { + validate_field_and_fieldset(this, false); + markField(this); + }); + var tagName = $el.attr('tagName'); + // text fields can be validated after each change + if (tagName === 'INPUT' && $el.attr('type') === 'text') { + $el.keyup(function () { + validate_field_and_fieldset($el, true); + markField($el); + }); + } + // disable textarea spellcheck + if (tagName === 'TEXTAREA') { + $el.attr('spellcheck', false); + } + }); + + // check whether we've refreshed a page and browser remembered modified + // form values + var $check_page_refresh = $('#check_page_refresh'); + if ($check_page_refresh.length === 0 || $check_page_refresh.val() === '1') { + // run all field validators + var errors = {}; + for (var i = 0; i < $elements.length; i++) { + validate_field($elements[i], false, errors); + } + // run all fieldset validators + $('fieldset.optbox').each(function () { + validate_fieldset(this, false, errors); + }); + + displayErrors(errors); + } else if ($check_page_refresh) { + $check_page_refresh.val('1'); + } +} + +AJAX.registerOnload('config.js', function () { + setupValidation(); +}); + +// +// END: Form validation and field operations +// ------------------------------------------------------------------ + +// ------------------------------------------------------------------ +// Tabbed forms +// + +/** + * Sets active tab + * + * @param {String} tab_id + */ +function setTab (tab_id) { + $('ul.tabs').each(function () { + var $this = $(this); + if (!$this.find('li a[href="#' + tab_id + '"]').length) { + return; + } + $this.find('li').removeClass('active').find('a[href="#' + tab_id + '"]').parent().addClass('active'); + $this.parent().find('div.tabs_contents fieldset').hide().filter('#' + tab_id).show(); + var hashValue = 'tab_' + tab_id; + location.hash = hashValue; + $this.parent().find('input[name=tab_hash]').val(hashValue); + }); +} + +function setupConfigTabs () { + var forms = $('form.config-form'); + forms.each(function () { + var $this = $(this); + var $tabs = $this.find('ul.tabs'); + if (!$tabs.length) { + return; + } + // add tabs events and activate one tab (the first one or indicated by location hash) + $tabs.find('li').removeClass('active'); + $tabs.find('a') + .click(function (e) { + e.preventDefault(); + setTab($(this).attr('href').substr(1)); + }) + .filter(':first') + .parent() + .addClass('active'); + $this.find('div.tabs_contents fieldset').hide().filter(':first').show(); + }); +} + +function adjustPrefsNotification () { + var $prefsAutoLoad = $('#prefs_autoload'); + var $tableNameControl = $('#table_name_col_no'); + var $prefsAutoShowing = ($prefsAutoLoad.css('display') !== 'none'); + + if ($prefsAutoShowing && $tableNameControl.length) { + $tableNameControl.css('top', '55px'); + } +} + +AJAX.registerOnload('config.js', function () { + setupConfigTabs(); + adjustPrefsNotification(); + + // tab links handling, check each 200ms + // (works with history in FF, further browser support here would be an overkill) + var prev_hash; + var tab_check_fnc = function () { + if (location.hash !== prev_hash) { + prev_hash = location.hash; + if (prev_hash.match(/^#tab_[a-zA-Z0-9_]+$/)) { + // session ID is sometimes appended here + var hash = prev_hash.substr(5).split('&')[0]; + if ($('#' + hash).length) { + setTab(hash); + } + } + } + }; + tab_check_fnc(); + setInterval(tab_check_fnc, 200); +}); + +// +// END: Tabbed forms +// ------------------------------------------------------------------ + +// ------------------------------------------------------------------ +// Form reset buttons +// + +AJAX.registerOnload('config.js', function () { + $('.optbox input[type=button][name=submit_reset]').on('click', function () { + var fields = $(this).closest('fieldset').find('input, select, textarea'); + for (var i = 0, imax = fields.length; i < imax; i++) { + setFieldValue(fields[i], getFieldType(fields[i]), defaultValues[fields[i].id]); + } + setDisplayError(); + }); +}); + +// +// END: Form reset buttons +// ------------------------------------------------------------------ + +// ------------------------------------------------------------------ +// "Restore default" and "set value" buttons +// + +/** + * Restores field's default value + * + * @param {String} field_id + */ +function restoreField (field_id) { + var $field = $('#' + field_id); + if ($field.length === 0 || defaultValues[field_id] === undefined) { + return; + } + setFieldValue($field, getFieldType($field), defaultValues[field_id]); +} + +function setupRestoreField () { + $('div.tabs_contents') + .on('mouseenter', '.restore-default, .set-value', function () { + $(this).css('opacity', 1); + }) + .on('mouseleave', '.restore-default, .set-value', function () { + $(this).css('opacity', 0.25); + }) + .on('click', '.restore-default, .set-value', function (e) { + e.preventDefault(); + var href = $(this).attr('href'); + var field_sel; + if ($(this).hasClass('restore-default')) { + field_sel = href; + restoreField(field_sel.substr(1)); + } else { + field_sel = href.match(/^[^=]+/)[0]; + var value = href.match(/\=(.+)$/)[1]; + setFieldValue($(field_sel), 'text', value); + } + $(field_sel).trigger('change'); + }) + .find('.restore-default, .set-value') + // inline-block for IE so opacity inheritance works + .css({ display: 'inline-block', opacity: 0.25 }); +} + +AJAX.registerOnload('config.js', function () { + setupRestoreField(); +}); + +// +// END: "Restore default" and "set value" buttons +// ------------------------------------------------------------------ + +// ------------------------------------------------------------------ +// User preferences import/export +// + +AJAX.registerOnload('config.js', function () { + offerPrefsAutoimport(); + var $radios = $('#import_local_storage, #export_local_storage'); + if (!$radios.length) { + return; + } + + // enable JavaScript dependent fields + $radios + .prop('disabled', false) + .add('#export_text_file, #import_text_file') + .click(function () { + var enable_id = $(this).attr('id'); + var disable_id; + if (enable_id.match(/local_storage$/)) { + disable_id = enable_id.replace(/local_storage$/, 'text_file'); + } else { + disable_id = enable_id.replace(/text_file$/, 'local_storage'); + } + $('#opts_' + disable_id).addClass('disabled').find('input').prop('disabled', true); + $('#opts_' + enable_id).removeClass('disabled').find('input').prop('disabled', false); + }); + + // detect localStorage state + var ls_supported = isStorageSupported('localStorage', true); + var ls_exists = ls_supported ? (window.localStorage.config || false) : false; + $('div.localStorage-' + (ls_supported ? 'un' : '') + 'supported').hide(); + $('div.localStorage-' + (ls_exists ? 'empty' : 'exists')).hide(); + if (ls_exists) { + updatePrefsDate(); + } + $('form.prefs-form').change(function () { + var $form = $(this); + var disabled = false; + if (!ls_supported) { + disabled = $form.find('input[type=radio][value$=local_storage]').prop('checked'); + } else if (!ls_exists && $form.attr('name') === 'prefs_import' && + $('#import_local_storage')[0].checked + ) { + disabled = true; + } + $form.find('input[type=submit]').prop('disabled', disabled); + }).submit(function (e) { + var $form = $(this); + if ($form.attr('name') === 'prefs_export' && $('#export_local_storage')[0].checked) { + e.preventDefault(); + // use AJAX to read JSON settings and save them + savePrefsToLocalStorage($form); + } else if ($form.attr('name') === 'prefs_import' && $('#import_local_storage')[0].checked) { + // set 'json' input and submit form + $form.find('input[name=json]').val(window.localStorage.config); + } + }); + + $(document).on('click', 'div.click-hide-message', function () { + $(this) + .hide() + .parent('.group') + .css('height', '') + .next('form') + .show(); + }); +}); + +/** + * Saves user preferences to localStorage + * + * @param {Element} form + */ +function savePrefsToLocalStorage (form) { + $form = $(form); + var submit = $form.find('input[type=submit]'); + submit.prop('disabled', true); + $.ajax({ + url: 'prefs_manage.php', + cache: false, + type: 'POST', + data: { + ajax_request: true, + server: PMA_commonParams.get('server'), + submit_get_json: true + }, + success: function (data) { + if (typeof data !== 'undefined' && data.success === true) { + window.localStorage.config = data.prefs; + window.localStorage.config_mtime = data.mtime; + window.localStorage.config_mtime_local = (new Date()).toUTCString(); + updatePrefsDate(); + $('div.localStorage-empty').hide(); + $('div.localStorage-exists').show(); + var group = $form.parent('.group'); + group.css('height', group.height() + 'px'); + $form.hide('fast'); + $form.prev('.click-hide-message').show('fast'); + } else { + PMA_ajaxShowMessage(data.error); + } + }, + complete: function () { + submit.prop('disabled', false); + } + }); +} + +/** + * Updates preferences timestamp in Import form + */ +function updatePrefsDate () { + var d = new Date(window.localStorage.config_mtime_local); + var msg = PMA_messages.strSavedOn.replace( + '@DATE@', + PMA_formatDateTime(d) + ); + $('#opts_import_local_storage').find('div.localStorage-exists').html(msg); +} + +/** + * Prepares message which informs that localStorage preferences are available and can be imported or deleted + */ +function offerPrefsAutoimport () { + var has_config = (isStorageSupported('localStorage')) && (window.localStorage.config || false); + var $cnt = $('#prefs_autoload'); + if (!$cnt.length || !has_config) { + return; + } + $cnt.find('a').click(function (e) { + e.preventDefault(); + var $a = $(this); + if ($a.attr('href') === '#no') { + $cnt.remove(); + $.post('index.php', { + server: PMA_commonParams.get('server'), + prefs_autoload: 'hide' + }, null, 'html'); + return; + } else if ($a.attr('href') === '#delete') { + $cnt.remove(); + localStorage.clear(); + $.post('index.php', { + server: PMA_commonParams.get('server'), + prefs_autoload: 'hide' + }, null, 'html'); + return; + } + $cnt.find('input[name=json]').val(window.localStorage.config); + $cnt.find('form').submit(); + }); + $cnt.show(); +} + +// +// END: User preferences import/export +// ------------------------------------------------------------------ diff --git a/php/apps/phpmyadmin49/js/console.js b/php/apps/phpmyadmin49/js/console.js new file mode 100644 index 00000000..74ab018f --- /dev/null +++ b/php/apps/phpmyadmin49/js/console.js @@ -0,0 +1,1495 @@ +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * Used in or for console + * + * @package phpMyAdmin-Console + */ + +/** + * Console object + */ +var PMA_console = { + /** + * @var object, jQuery object, selector is '#pma_console>.content' + * @access private + */ + $consoleContent: null, + /** + * @var object, jQuery object, selector is '#pma_console .content', + * used for resizer + * @access private + */ + $consoleAllContents: null, + /** + * @var object, jQuery object, selector is '#pma_console .toolbar' + * @access private + */ + $consoleToolbar: null, + /** + * @var object, jQuery object, selector is '#pma_console .template' + * @access private + */ + $consoleTemplates: null, + /** + * @var object, jQuery object, form for submit + * @access private + */ + $requestForm: null, + /** + * @var object, contain console config + * @access private + */ + config: null, + /** + * @var bool, if console element exist, it'll be true + * @access public + */ + isEnabled: false, + /** + * @var bool, make sure console events bind only once + * @access private + */ + isInitialized: false, + /** + * Used for console initialize, reinit is ok, just some variable assignment + * + * @return void + */ + initialize: function () { + if ($('#pma_console').length === 0) { + return; + } + + PMA_console.config = configGet('Console', false); + + PMA_console.isEnabled = true; + + // Vars init + PMA_console.$consoleToolbar = $('#pma_console').find('>.toolbar'); + PMA_console.$consoleContent = $('#pma_console').find('>.content'); + PMA_console.$consoleAllContents = $('#pma_console').find('.content'); + PMA_console.$consoleTemplates = $('#pma_console').find('>.templates'); + + // Generate a from for post + PMA_console.$requestForm = $('
      ' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '
      ' + ); + PMA_console.$requestForm.children('[name=token]').val(PMA_commonParams.get('token')); + PMA_console.$requestForm.on('submit', AJAX.requestHandler); + + // Event binds shouldn't run again + if (PMA_console.isInitialized === false) { + // Load config first + if (PMA_console.config.AlwaysExpand === true) { + $('#pma_console_options input[name=always_expand]').prop('checked', true); + } + if (PMA_console.config.StartHistory === true) { + $('#pma_console_options').find('input[name=start_history]').prop('checked', true); + } + if (PMA_console.config.CurrentQuery === true) { + $('#pma_console_options').find('input[name=current_query]').prop('checked', true); + } + if (PMA_console.config.EnterExecutes === true) { + $('#pma_console_options').find('input[name=enter_executes]').prop('checked', true); + } + if (PMA_console.config.DarkTheme === true) { + $('#pma_console_options').find('input[name=dark_theme]').prop('checked', true); + $('#pma_console').find('>.content').addClass('console_dark_theme'); + } + + PMA_consoleResizer.initialize(); + PMA_consoleInput.initialize(); + PMA_consoleMessages.initialize(); + PMA_consoleBookmarks.initialize(); + PMA_consoleDebug.initialize(); + + PMA_console.$consoleToolbar.children('.console_switch').click(PMA_console.toggle); + + $('#pma_console').find('.toolbar').children().mousedown(function (event) { + event.preventDefault(); + event.stopImmediatePropagation(); + }); + + $('#pma_console').find('.button.clear').click(function () { + PMA_consoleMessages.clear(); + }); + + $('#pma_console').find('.button.history').click(function () { + PMA_consoleMessages.showHistory(); + }); + + $('#pma_console').find('.button.options').click(function () { + PMA_console.showCard('#pma_console_options'); + }); + + $('#pma_console').find('.button.debug').click(function () { + PMA_console.showCard('#debug_console'); + }); + + PMA_console.$consoleContent.click(function (event) { + if (event.target === this) { + PMA_consoleInput.focus(); + } + }); + + $('#pma_console').find('.mid_layer').click(function () { + PMA_console.hideCard($(this).parent().children('.card')); + }); + $('#debug_console').find('.switch_button').click(function () { + PMA_console.hideCard($(this).closest('.card')); + }); + $('#pma_bookmarks').find('.switch_button').click(function () { + PMA_console.hideCard($(this).closest('.card')); + }); + $('#pma_console_options').find('.switch_button').click(function () { + PMA_console.hideCard($(this).closest('.card')); + }); + + $('#pma_console_options').find('input[type=checkbox]').change(function () { + PMA_console.updateConfig(); + }); + + $('#pma_console_options').find('.button.default').click(function () { + $('#pma_console_options input[name=always_expand]').prop('checked', false); + $('#pma_console_options').find('input[name=start_history]').prop('checked', false); + $('#pma_console_options').find('input[name=current_query]').prop('checked', true); + $('#pma_console_options').find('input[name=enter_executes]').prop('checked', false); + $('#pma_console_options').find('input[name=dark_theme]').prop('checked', false); + PMA_console.updateConfig(); + }); + + $('#pma_console_options').find('input[name=enter_executes]').change(function () { + PMA_consoleMessages.showInstructions(PMA_console.config.EnterExecutes); + }); + + $(document).ajaxComplete(function (event, xhr, ajaxOptions) { + if (ajaxOptions.dataType && ajaxOptions.dataType.indexOf('json') !== -1) { + return; + } + if (xhr.status !== 200) { + return; + } + try { + var data = JSON.parse(xhr.responseText); + PMA_console.ajaxCallback(data); + } catch (e) { + console.trace(); + console.log('Failed to parse JSON: ' + e.message); + } + }); + + PMA_console.isInitialized = true; + } + + // Change console mode from cookie + switch (PMA_console.config.Mode) { + case 'collapse': + PMA_console.collapse(); + break; + /* jshint -W086 */// no break needed in default section + default: + PMA_console.setConfig('Mode', 'info'); + case 'info': + /* jshint +W086 */ + PMA_console.info(); + break; + case 'show': + PMA_console.show(true); + PMA_console.scrollBottom(); + break; + } + }, + /** + * Execute query and show results in console + * + * @return void + */ + execute: function (queryString, options) { + if (typeof(queryString) !== 'string' || ! /[a-z]|[A-Z]/.test(queryString)) { + return; + } + PMA_console.$requestForm.children('textarea').val(queryString); + PMA_console.$requestForm.children('[name=server]').attr('value', PMA_commonParams.get('server')); + if (options && options.db) { + PMA_console.$requestForm.children('[name=db]').val(options.db); + if (options.table) { + PMA_console.$requestForm.children('[name=table]').val(options.table); + } else { + PMA_console.$requestForm.children('[name=table]').val(''); + } + } else { + PMA_console.$requestForm.children('[name=db]').val( + (PMA_commonParams.get('db').length > 0 ? PMA_commonParams.get('db') : '')); + } + PMA_console.$requestForm.find('[name=profiling]').remove(); + if (options && options.profiling === true) { + PMA_console.$requestForm.append(''); + } + if (! confirmQuery(PMA_console.$requestForm[0], PMA_console.$requestForm.children('textarea')[0].value)) { + return; + } + PMA_console.$requestForm.children('[name=console_message_id]') + .val(PMA_consoleMessages.appendQuery({ sql_query: queryString }).message_id); + PMA_console.$requestForm.trigger('submit'); + PMA_consoleInput.clear(); + PMA_reloadNavigation(); + }, + ajaxCallback: function (data) { + if (data && data.console_message_id) { + PMA_consoleMessages.updateQuery(data.console_message_id, data.success, + (data._reloadQuerywindow ? data._reloadQuerywindow : false)); + } else if (data && data._reloadQuerywindow) { + if (data._reloadQuerywindow.sql_query.length > 0) { + PMA_consoleMessages.appendQuery(data._reloadQuerywindow, 'successed') + .$message.addClass(PMA_console.config.CurrentQuery ? '' : 'hide'); + } + } + }, + /** + * Change console to collapse mode + * + * @return void + */ + collapse: function () { + PMA_console.setConfig('Mode', 'collapse'); + var pmaConsoleHeight = Math.max(92, PMA_console.config.Height); + + PMA_console.$consoleToolbar.addClass('collapsed'); + PMA_console.$consoleAllContents.height(pmaConsoleHeight); + PMA_console.$consoleContent.stop(); + PMA_console.$consoleContent.animate({ 'margin-bottom': -1 * PMA_console.$consoleContent.outerHeight() + 'px' }, + 'fast', 'easeOutQuart', function () { + PMA_console.$consoleContent.css({ display:'none' }); + $(window).trigger('resize'); + }); + PMA_console.hideCard(); + }, + /** + * Show console + * + * @param bool inputFocus If true, focus the input line after show() + * @return void + */ + show: function (inputFocus) { + PMA_console.setConfig('Mode', 'show'); + + var pmaConsoleHeight = Math.max(92, PMA_console.config.Height); + pmaConsoleHeight = Math.min(PMA_console.config.Height, (window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight) - 25); + PMA_console.$consoleContent.css({ display:'block' }); + if (PMA_console.$consoleToolbar.hasClass('collapsed')) { + PMA_console.$consoleToolbar.removeClass('collapsed'); + } + PMA_console.$consoleAllContents.height(pmaConsoleHeight); + PMA_console.$consoleContent.stop(); + PMA_console.$consoleContent.animate({ 'margin-bottom': 0 }, + 'fast', 'easeOutQuart', function () { + $(window).trigger('resize'); + if (inputFocus) { + PMA_consoleInput.focus(); + } + }); + }, + /** + * Change console to SQL information mode + * this mode shows current SQL query + * This mode is the default mode + * + * @return void + */ + info: function () { + // Under construction + PMA_console.collapse(); + }, + /** + * Toggle console mode between collapse/show + * Used for toggle buttons and shortcuts + * + * @return void + */ + toggle: function () { + switch (PMA_console.config.Mode) { + case 'collapse': + case 'info': + PMA_console.show(true); + break; + case 'show': + PMA_console.collapse(); + break; + default: + PMA_consoleInitialize(); + } + }, + /** + * Scroll console to bottom + * + * @return void + */ + scrollBottom: function () { + PMA_console.$consoleContent.scrollTop(PMA_console.$consoleContent.prop('scrollHeight')); + }, + /** + * Show card + * + * @param string cardSelector Selector, select string will be "#pma_console " + cardSelector + * this param also can be JQuery object, if you need. + * + * @return void + */ + showCard: function (cardSelector) { + var $card = null; + if (typeof(cardSelector) !== 'string') { + if (cardSelector.length > 0) { + $card = cardSelector; + } else { + return; + } + } else { + $card = $('#pma_console ' + cardSelector); + } + if ($card.length === 0) { + return; + } + $card.parent().children('.mid_layer').show().fadeTo(0, 0.15); + $card.addClass('show'); + PMA_consoleInput.blur(); + if ($card.parents('.card').length > 0) { + PMA_console.showCard($card.parents('.card')); + } + }, + /** + * Scroll console to bottom + * + * @param object $targetCard Target card JQuery object, if it's empty, function will hide all cards + * @return void + */ + hideCard: function ($targetCard) { + if (! $targetCard) { + $('#pma_console').find('.mid_layer').fadeOut(140); + $('#pma_console').find('.card').removeClass('show'); + } else if ($targetCard.length > 0) { + $targetCard.parent().find('.mid_layer').fadeOut(140); + $targetCard.find('.card').removeClass('show'); + $targetCard.removeClass('show'); + } + }, + /** + * Used for update console config + * + * @return void + */ + updateConfig: function () { + PMA_console.setConfig('AlwaysExpand', $('#pma_console_options input[name=always_expand]').prop('checked')); + PMA_console.setConfig('StartHistory', $('#pma_console_options').find('input[name=start_history]').prop('checked')); + PMA_console.setConfig('CurrentQuery', $('#pma_console_options').find('input[name=current_query]').prop('checked')); + PMA_console.setConfig('EnterExecutes', $('#pma_console_options').find('input[name=enter_executes]').prop('checked')); + PMA_console.setConfig('DarkTheme', $('#pma_console_options').find('input[name=dark_theme]').prop('checked')); + /* Setting the dark theme of the console*/ + if (PMA_console.config.DarkTheme) { + $('#pma_console').find('>.content').addClass('console_dark_theme'); + } else { + $('#pma_console').find('>.content').removeClass('console_dark_theme'); + } + }, + setConfig: function (key, value) { + PMA_console.config[key] = value; + configSet('Console/' + key, value); + }, + isSelect: function (queryString) { + var reg_exp = /^SELECT\s+/i; + return reg_exp.test(queryString); + } +}; + +/** + * Resizer object + * Careful: this object UI logics highly related with functions under PMA_console + * Resizing min-height is 32, if small than it, console will collapse + */ +var PMA_consoleResizer = { + _posY: 0, + _height: 0, + _resultHeight: 0, + /** + * Mousedown event handler for bind to resizer + * + * @return void + */ + _mousedown: function (event) { + if (PMA_console.config.Mode !== 'show') { + return; + } + PMA_consoleResizer._posY = event.pageY; + PMA_consoleResizer._height = PMA_console.$consoleContent.height(); + $(document).mousemove(PMA_consoleResizer._mousemove); + $(document).mouseup(PMA_consoleResizer._mouseup); + // Disable text selection while resizing + $(document).on('selectstart', function () { + return false; + }); + }, + /** + * Mousemove event handler for bind to resizer + * + * @return void + */ + _mousemove: function (event) { + if (event.pageY < 35) { + event.pageY = 35; + } + PMA_consoleResizer._resultHeight = PMA_consoleResizer._height + (PMA_consoleResizer._posY - event.pageY); + // Content min-height is 32, if adjusting height small than it we'll move it out of the page + if (PMA_consoleResizer._resultHeight <= 32) { + PMA_console.$consoleAllContents.height(32); + PMA_console.$consoleContent.css('margin-bottom', PMA_consoleResizer._resultHeight - 32); + } else { + // Logic below makes viewable area always at bottom when adjusting height and content already at bottom + if (PMA_console.$consoleContent.scrollTop() + PMA_console.$consoleContent.innerHeight() + 16 + >= PMA_console.$consoleContent.prop('scrollHeight')) { + PMA_console.$consoleAllContents.height(PMA_consoleResizer._resultHeight); + PMA_console.scrollBottom(); + } else { + PMA_console.$consoleAllContents.height(PMA_consoleResizer._resultHeight); + } + } + }, + /** + * Mouseup event handler for bind to resizer + * + * @return void + */ + _mouseup: function () { + PMA_console.setConfig('Height', PMA_consoleResizer._resultHeight); + PMA_console.show(); + $(document).off('mousemove'); + $(document).off('mouseup'); + $(document).off('selectstart'); + }, + /** + * Used for console resizer initialize + * + * @return void + */ + initialize: function () { + $('#pma_console').find('.toolbar').off('mousedown'); + $('#pma_console').find('.toolbar').mousedown(PMA_consoleResizer._mousedown); + } +}; + + +/** + * Console input object + */ +var PMA_consoleInput = { + /** + * @var array, contains Codemirror objects or input jQuery objects + * @access private + */ + _inputs: null, + /** + * @var bool, if codemirror enabled + * @access private + */ + _codemirror: false, + /** + * @var int, count for history navigation, 0 for current input + * @access private + */ + _historyCount: 0, + /** + * @var string, current input when navigating through history + * @access private + */ + _historyPreserveCurrent: null, + /** + * Used for console input initialize + * + * @return void + */ + initialize: function () { + // _cm object can't be reinitialize + if (PMA_consoleInput._inputs !== null) { + return; + } + if (typeof CodeMirror !== 'undefined') { + PMA_consoleInput._codemirror = true; + } + PMA_consoleInput._inputs = []; + if (PMA_consoleInput._codemirror) { + PMA_consoleInput._inputs.console = CodeMirror($('#pma_console').find('.console_query_input')[0], { + theme: 'pma', + mode: 'text/x-sql', + lineWrapping: true, + extraKeys: { 'Ctrl-Space': 'autocomplete' }, + hintOptions: { 'completeSingle': false, 'completeOnSingleClick': true }, + gutters: ['CodeMirror-lint-markers'], + lint: { + 'getAnnotations': CodeMirror.sqlLint, + 'async': true, + } + }); + PMA_consoleInput._inputs.console.on('inputRead', codemirrorAutocompleteOnInputRead); + PMA_consoleInput._inputs.console.on('keydown', function (instance, event) { + PMA_consoleInput._historyNavigate(event); + }); + if ($('#pma_bookmarks').length !== 0) { + PMA_consoleInput._inputs.bookmark = CodeMirror($('#pma_console').find('.bookmark_add_input')[0], { + theme: 'pma', + mode: 'text/x-sql', + lineWrapping: true, + extraKeys: { 'Ctrl-Space': 'autocomplete' }, + hintOptions: { 'completeSingle': false, 'completeOnSingleClick': true }, + gutters: ['CodeMirror-lint-markers'], + lint: { + 'getAnnotations': CodeMirror.sqlLint, + 'async': true, + } + }); + PMA_consoleInput._inputs.bookmark.on('inputRead', codemirrorAutocompleteOnInputRead); + } + } else { + PMA_consoleInput._inputs.console = + $('\n'; + new_content += getForeignKeyCheckboxLoader(); + new_content += '\n'; + new_content += '\n'; + var $editor_area = $('div#inline_editor'); + if ($editor_area.length === 0) { + $editor_area = $('
      '); + $editor_area.insertBefore($inner_sql); + } + $editor_area.html(new_content); + loadForeignKeyCheckbox(); + $inner_sql.hide(); + + bindCodeMirrorToInlineEditor(); + return false; + }); + + $(document).on('click', 'input#sql_query_edit_save', function () { + // hide already existing success message + var sql_query; + if (codemirror_inline_editor) { + codemirror_inline_editor.save(); + sql_query = codemirror_inline_editor.getValue(); + } else { + sql_query = $(this).parent().find('#sql_query_edit').val(); + } + var fk_check = $(this).parent().find('#fk_checks').is(':checked'); + + var $form = $('a.inline_edit_sql').prev('form'); + var $fake_form = $('
      ', { action: 'import.php', method: 'post' }) + .append($form.find('input[name=server], input[name=db], input[name=table], input[name=token]').clone()) + .append($('', { type: 'hidden', name: 'show_query', value: 1 })) + .append($('', { type: 'hidden', name: 'is_js_confirmed', value: 0 })) + .append($('', { type: 'hidden', name: 'sql_query', value: sql_query })) + .append($('', { type: 'hidden', name: 'fk_checks', value: fk_check ? 1 : 0 })); + if (! checkSqlQuery($fake_form[0])) { + return false; + } + $('.success').hide(); + $fake_form.appendTo($('body')).submit(); + }); + + $(document).on('click', 'input#sql_query_edit_discard', function () { + var $divEditor = $('div#inline_editor_outer'); + $divEditor.siblings('code.sql').show(); + $divEditor.remove(); + }); + + $(document).on('click', 'input.sqlbutton', function (evt) { + insertQuery(evt.target.id); + PMA_handleSimulateQueryButton(); + return false; + }); + + $(document).on('change', '#parameterized', updateQueryParameters); + + var $inputUsername = $('#input_username'); + if ($inputUsername) { + if ($inputUsername.val() === '') { + $inputUsername.trigger('focus'); + } else { + $('#input_password').trigger('focus'); + } + } +}); + +/** + * "inputRead" event handler for CodeMirror SQL query editors for autocompletion + */ +function codemirrorAutocompleteOnInputRead (instance) { + if (!sql_autocomplete_in_progress + && (!instance.options.hintOptions.tables || !sql_autocomplete)) { + if (!sql_autocomplete) { + // Reset after teardown + instance.options.hintOptions.tables = false; + instance.options.hintOptions.defaultTable = ''; + + sql_autocomplete_in_progress = true; + + var href = 'db_sql_autocomplete.php'; + var params = { + 'ajax_request': true, + 'server': PMA_commonParams.get('server'), + 'db': PMA_commonParams.get('db'), + 'no_debug': true + }; + + var columnHintRender = function (elem, self, data) { + $('
      ') + .text(data.columnName) + .appendTo(elem); + $('
      ') + .text(data.columnHint) + .appendTo(elem); + }; + + $.ajax({ + type: 'POST', + url: href, + data: params, + success: function (data) { + if (data.success) { + var tables = JSON.parse(data.tables); + sql_autocomplete_default_table = PMA_commonParams.get('table'); + sql_autocomplete = []; + for (var table in tables) { + if (tables.hasOwnProperty(table)) { + var columns = tables[table]; + table = { + text: table, + columns: [] + }; + for (var column in columns) { + if (columns.hasOwnProperty(column)) { + var displayText = columns[column].Type; + if (columns[column].Key === 'PRI') { + displayText += ' | Primary'; + } else if (columns[column].Key === 'UNI') { + displayText += ' | Unique'; + } + table.columns.push({ + text: column, + displayText: column + ' | ' + displayText, + columnName: column, + columnHint: displayText, + render: columnHintRender + }); + } + } + } + sql_autocomplete.push(table); + } + instance.options.hintOptions.tables = sql_autocomplete; + instance.options.hintOptions.defaultTable = sql_autocomplete_default_table; + } + }, + complete: function () { + sql_autocomplete_in_progress = false; + } + }); + } else { + instance.options.hintOptions.tables = sql_autocomplete; + instance.options.hintOptions.defaultTable = sql_autocomplete_default_table; + } + } + if (instance.state.completionActive) { + return; + } + var cur = instance.getCursor(); + var token = instance.getTokenAt(cur); + var string = ''; + if (token.string.match(/^[.`\w@]\w*$/)) { + string = token.string; + } + if (string.length > 0) { + CodeMirror.commands.autocomplete(instance); + } +} + +/** + * Remove autocomplete information before tearing down a page + */ +AJAX.registerTeardown('functions.js', function () { + sql_autocomplete = false; + sql_autocomplete_default_table = ''; +}); + +/** + * Binds the CodeMirror to the text area used to inline edit a query. + */ +function bindCodeMirrorToInlineEditor () { + var $inline_editor = $('#sql_query_edit'); + if ($inline_editor.length > 0) { + if (typeof CodeMirror !== 'undefined') { + var height = $inline_editor.css('height'); + codemirror_inline_editor = PMA_getSQLEditor($inline_editor); + codemirror_inline_editor.getWrapperElement().style.height = height; + codemirror_inline_editor.refresh(); + codemirror_inline_editor.focus(); + $(codemirror_inline_editor.getWrapperElement()) + .on('keydown', catchKeypressesFromSqlInlineEdit); + } else { + $inline_editor + .focus() + .on('keydown', catchKeypressesFromSqlInlineEdit); + } + } +} + +function catchKeypressesFromSqlInlineEdit (event) { + // ctrl-enter is 10 in chrome and ie, but 13 in ff + if ((event.ctrlKey || event.metaKey) && (event.keyCode === 13 || event.keyCode === 10)) { + $('#sql_query_edit_save').trigger('click'); + } +} + +/** + * Adds doc link to single highlighted SQL element + */ +function PMA_doc_add ($elm, params) { + if (typeof mysql_doc_template === 'undefined') { + return; + } + + var url = PMA_sprintf( + decodeURIComponent(mysql_doc_template), + params[0] + ); + if (params.length > 1) { + url += '#' + params[1]; + } + var content = $elm.text(); + $elm.text(''); + $elm.append('' + content + ''); +} + +/** + * Generates doc links for keywords inside highlighted SQL + */ +function PMA_doc_keyword (idx, elm) { + var $elm = $(elm); + /* Skip already processed ones */ + if ($elm.find('a').length > 0) { + return; + } + var keyword = $elm.text().toUpperCase(); + var $next = $elm.next('.cm-keyword'); + if ($next) { + var next_keyword = $next.text().toUpperCase(); + var full = keyword + ' ' + next_keyword; + + var $next2 = $next.next('.cm-keyword'); + if ($next2) { + var next2_keyword = $next2.text().toUpperCase(); + var full2 = full + ' ' + next2_keyword; + if (full2 in mysql_doc_keyword) { + PMA_doc_add($elm, mysql_doc_keyword[full2]); + PMA_doc_add($next, mysql_doc_keyword[full2]); + PMA_doc_add($next2, mysql_doc_keyword[full2]); + return; + } + } + if (full in mysql_doc_keyword) { + PMA_doc_add($elm, mysql_doc_keyword[full]); + PMA_doc_add($next, mysql_doc_keyword[full]); + return; + } + } + if (keyword in mysql_doc_keyword) { + PMA_doc_add($elm, mysql_doc_keyword[keyword]); + } +} + +/** + * Generates doc links for builtins inside highlighted SQL + */ +function PMA_doc_builtin (idx, elm) { + var $elm = $(elm); + var builtin = $elm.text().toUpperCase(); + if (builtin in mysql_doc_builtin) { + PMA_doc_add($elm, mysql_doc_builtin[builtin]); + } +} + +/** + * Higlights SQL using CodeMirror. + */ +function PMA_highlightSQL ($base) { + var $elm = $base.find('code.sql'); + $elm.each(function () { + var $sql = $(this); + var $pre = $sql.find('pre'); + /* We only care about visible elements to avoid double processing */ + if ($pre.is(':visible')) { + var $highlight = $('
      '); + $sql.append($highlight); + if (typeof CodeMirror !== 'undefined') { + CodeMirror.runMode($sql.text(), 'text/x-mysql', $highlight[0]); + $pre.hide(); + $highlight.find('.cm-keyword').each(PMA_doc_keyword); + $highlight.find('.cm-builtin').each(PMA_doc_builtin); + } + } + }); +} + +/** + * Updates an element containing code. + * + * @param jQuery Object $base base element which contains the raw and the + * highlighted code. + * + * @param string htmlValue code in HTML format, displayed if code cannot be + * highlighted + * + * @param string rawValue raw code, used as a parameter for highlighter + * + * @return bool whether content was updated or not + */ +function PMA_updateCode ($base, htmlValue, rawValue) { + var $code = $base.find('code'); + if ($code.length === 0) { + return false; + } + + // Determines the type of the content and appropriate CodeMirror mode. + var type = ''; + var mode = ''; + if ($code.hasClass('json')) { + type = 'json'; + mode = 'application/json'; + } else if ($code.hasClass('sql')) { + type = 'sql'; + mode = 'text/x-mysql'; + } else if ($code.hasClass('xml')) { + type = 'xml'; + mode = 'application/xml'; + } else { + return false; + } + + // Element used to display unhighlighted code. + var $notHighlighted = $('
      ' + htmlValue + '
      '); + + // Tries to highlight code using CodeMirror. + if (typeof CodeMirror !== 'undefined') { + var $highlighted = $('
      '); + CodeMirror.runMode(rawValue, mode, $highlighted[0]); + $notHighlighted.hide(); + $code.html('').append($notHighlighted, $highlighted[0]); + } else { + $code.html('').append($notHighlighted); + } + + return true; +} + +/** + * Show a message on the top of the page for an Ajax request + * + * Sample usage: + * + * 1) var $msg = PMA_ajaxShowMessage(); + * This will show a message that reads "Loading...". Such a message will not + * disappear automatically and cannot be dismissed by the user. To remove this + * message either the PMA_ajaxRemoveMessage($msg) function must be called or + * another message must be show with PMA_ajaxShowMessage() function. + * + * 2) var $msg = PMA_ajaxShowMessage(PMA_messages.strProcessingRequest); + * This is a special case. The behaviour is same as above, + * just with a different message + * + * 3) var $msg = PMA_ajaxShowMessage('The operation was successful'); + * This will show a message that will disappear automatically and it can also + * be dismissed by the user. + * + * 4) var $msg = PMA_ajaxShowMessage('Some error', false); + * This will show a message that will not disappear automatically, but it + * can be dismissed by the user after he has finished reading it. + * + * @param string message string containing the message to be shown. + * optional, defaults to 'Loading...' + * @param mixed timeout number of milliseconds for the message to be visible + * optional, defaults to 5000. If set to 'false', the + * notification will never disappear + * @param string type string to dictate the type of message shown. + * optional, defaults to normal notification. + * If set to 'error', the notification will show message + * with red background. + * If set to 'success', the notification will show with + * a green background. + * @return jQuery object jQuery Element that holds the message div + * this object can be passed to PMA_ajaxRemoveMessage() + * to remove the notification + */ +function PMA_ajaxShowMessage (message, timeout, type) { + /** + * @var self_closing Whether the notification will automatically disappear + */ + var self_closing = true; + /** + * @var dismissable Whether the user will be able to remove + * the notification by clicking on it + */ + var dismissable = true; + // Handle the case when a empty data.message is passed. + // We don't want the empty message + if (message === '') { + return true; + } else if (! message) { + // If the message is undefined, show the default + message = PMA_messages.strLoading; + dismissable = false; + self_closing = false; + } else if (message === PMA_messages.strProcessingRequest) { + // This is another case where the message should not disappear + dismissable = false; + self_closing = false; + } + // Figure out whether (or after how long) to remove the notification + if (timeout === undefined) { + timeout = 5000; + } else if (timeout === false) { + self_closing = false; + } + // Determine type of message, add styling as required + if (type === 'error') { + message = '
      ' + message + '
      '; + } else if (type === 'success') { + message = '
      ' + message + '
      '; + } + // Create a parent element for the AJAX messages, if necessary + if ($('#loading_parent').length === 0) { + $('
      ') + .prependTo('#page_content'); + } + // Update message count to create distinct message elements every time + ajax_message_count++; + // Remove all old messages, if any + $('span.ajax_notification[id^=ajax_message_num]').remove(); + /** + * @var $retval a jQuery object containing the reference + * to the created AJAX message + */ + var $retval = $( + '' + ) + .hide() + .appendTo('#loading_parent') + .html(message) + .show(); + // If the notification is self-closing we should create a callback to remove it + if (self_closing) { + $retval + .delay(timeout) + .fadeOut('medium', function () { + if ($(this).is(':data(tooltip)')) { + $(this).tooltip('destroy'); + } + // Remove the notification + $(this).remove(); + }); + } + // If the notification is dismissable we need to add the relevant class to it + // and add a tooltip so that the users know that it can be removed + if (dismissable) { + $retval.addClass('dismissable').css('cursor', 'pointer'); + /** + * Add a tooltip to the notification to let the user know that (s)he + * can dismiss the ajax notification by clicking on it. + */ + PMA_tooltip( + $retval, + 'span', + PMA_messages.strDismiss + ); + } + PMA_highlightSQL($retval); + + return $retval; +} + +/** + * Removes the message shown for an Ajax operation when it's completed + * + * @param jQuery object jQuery Element that holds the notification + * + * @return nothing + */ +function PMA_ajaxRemoveMessage ($this_msgbox) { + if ($this_msgbox !== undefined && $this_msgbox instanceof jQuery) { + $this_msgbox + .stop(true, true) + .fadeOut('medium'); + if ($this_msgbox.is(':data(tooltip)')) { + $this_msgbox.tooltip('destroy'); + } else { + $this_msgbox.remove(); + } + } +} + +/** + * Requests SQL for previewing before executing. + * + * @param jQuery Object $form Form containing query data + * + * @return void + */ +function PMA_previewSQL ($form) { + var form_url = $form.attr('action'); + var sep = PMA_commonParams.get('arg_separator'); + var form_data = $form.serialize() + + sep + 'do_save_data=1' + + sep + 'preview_sql=1' + + sep + 'ajax_request=1'; + var $msgbox = PMA_ajaxShowMessage(); + $.ajax({ + type: 'POST', + url: form_url, + data: form_data, + success: function (response) { + PMA_ajaxRemoveMessage($msgbox); + if (response.success) { + var $dialog_content = $('
      ') + .append(response.sql_data); + var button_options = {}; + button_options[PMA_messages.strClose] = function () { + $(this).dialog('close'); + }; + var $response_dialog = $dialog_content.dialog({ + minWidth: 550, + maxHeight: 400, + modal: true, + buttons: button_options, + title: PMA_messages.strPreviewSQL, + close: function () { + $(this).remove(); + }, + open: function () { + // Pretty SQL printing. + PMA_highlightSQL($(this)); + } + }); + } else { + PMA_ajaxShowMessage(response.message); + } + }, + error: function () { + PMA_ajaxShowMessage(PMA_messages.strErrorProcessingRequest); + } + }); +} + +/** + * Callback called when submit/"OK" is clicked on sql preview/confirm modal + * + * @callback onSubmitCallback + * @param {string} url The url + */ + +/** + * + * @param {string} sql_data Sql query to preview + * @param {string} url Url to be sent to callback + * @param {onSubmitCallback} callback On submit callback function + * + * @return void + */ +function PMA_confirmPreviewSQL (sql_data, url, callback) { + var $dialog_content = $('
      '
      +        + sql_data
      +        + '
      ' + ); + var button_options = [ + { + text: PMA_messages.strOK, + class: 'submitOK', + click: function () { + callback(url); + } + }, + { + text: PMA_messages.strCancel, + class: 'submitCancel', + click: function () { + $(this).dialog('close'); + } + } + ]; + var $response_dialog = $dialog_content.dialog({ + minWidth: 550, + maxHeight: 400, + modal: true, + buttons: button_options, + title: PMA_messages.strPreviewSQL, + close: function () { + $(this).remove(); + }, + open: function () { + // Pretty SQL printing. + PMA_highlightSQL($(this)); + } + }); +} + +/** + * check for reserved keyword column name + * + * @param jQuery Object $form Form + * + * @returns true|false + */ + +function PMA_checkReservedWordColumns ($form) { + var is_confirmed = true; + $.ajax({ + type: 'POST', + url: 'tbl_structure.php', + data: $form.serialize() + PMA_commonParams.get('arg_separator') + 'reserved_word_check=1', + success: function (data) { + if (typeof data.success !== 'undefined' && data.success === true) { + is_confirmed = confirm(data.message); + } + }, + async:false + }); + return is_confirmed; +} + +// This event only need to be fired once after the initial page load +$(function () { + /** + * Allows the user to dismiss a notification + * created with PMA_ajaxShowMessage() + */ + $(document).on('click', 'span.ajax_notification.dismissable', function () { + PMA_ajaxRemoveMessage($(this)); + }); + /** + * The below two functions hide the "Dismiss notification" tooltip when a user + * is hovering a link or button that is inside an ajax message + */ + $(document).on('mouseover', 'span.ajax_notification a, span.ajax_notification button, span.ajax_notification input', function () { + if ($(this).parents('span.ajax_notification').is(':data(tooltip)')) { + $(this).parents('span.ajax_notification').tooltip('disable'); + } + }); + $(document).on('mouseout', 'span.ajax_notification a, span.ajax_notification button, span.ajax_notification input', function () { + if ($(this).parents('span.ajax_notification').is(':data(tooltip)')) { + $(this).parents('span.ajax_notification').tooltip('enable'); + } + }); +}); + +/** + * Hides/shows the "Open in ENUM/SET editor" message, depending on the data type of the column currently selected + */ +function PMA_showNoticeForEnum (selectElement) { + var enum_notice_id = selectElement.attr('id').split('_')[1]; + enum_notice_id += '_' + (parseInt(selectElement.attr('id').split('_')[2], 10) + 1); + var selectedType = selectElement.val(); + if (selectedType === 'ENUM' || selectedType === 'SET') { + $('p#enum_notice_' + enum_notice_id).show(); + } else { + $('p#enum_notice_' + enum_notice_id).hide(); + } +} + +/** + * Creates a Profiling Chart. Used in sql.js + * and in server_status_monitor.js + */ +function PMA_createProfilingChart (target, data) { + // create the chart + var factory = new JQPlotChartFactory(); + var chart = factory.createChart(ChartType.PIE, target); + + // create the data table and add columns + var dataTable = new DataTable(); + dataTable.addColumn(ColumnType.STRING, ''); + dataTable.addColumn(ColumnType.NUMBER, ''); + dataTable.setData(data); + + var windowWidth = $(window).width(); + var location = 's'; + if (windowWidth > 768) { + var location = 'se'; + } + + // draw the chart and return the chart object + chart.draw(dataTable, { + seriesDefaults: { + rendererOptions: { + showDataLabels: true + } + }, + highlighter: { + tooltipLocation: 'se', + sizeAdjust: 0, + tooltipAxes: 'pieref', + formatString: '%s, %.9Ps' + }, + legend: { + show: true, + location: location, + rendererOptions: { + numberColumns: 2 + } + }, + // from http://tango.freedesktop.org/Tango_Icon_Theme_Guidelines#Color_Palette + seriesColors: [ + '#fce94f', + '#fcaf3e', + '#e9b96e', + '#8ae234', + '#729fcf', + '#ad7fa8', + '#ef2929', + '#888a85', + '#c4a000', + '#ce5c00', + '#8f5902', + '#4e9a06', + '#204a87', + '#5c3566', + '#a40000', + '#babdb6', + '#2e3436' + ] + }); + return chart; +} + +/** + * Formats a profiling duration nicely (in us and ms time). + * Used in server_status_monitor.js + * + * @param integer Number to be formatted, should be in the range of microsecond to second + * @param integer Accuracy, how many numbers right to the comma should be + * @return string The formatted number + */ +function PMA_prettyProfilingNum (num, acc) { + if (!acc) { + acc = 2; + } + acc = Math.pow(10, acc); + if (num * 1000 < 0.1) { + num = Math.round(acc * (num * 1000 * 1000)) / acc + 'µ'; + } else if (num < 0.1) { + num = Math.round(acc * (num * 1000)) / acc + 'm'; + } else { + num = Math.round(acc * num) / acc; + } + + return num + 's'; +} + + +/** + * Formats a SQL Query nicely with newlines and indentation. Depends on Codemirror and MySQL Mode! + * + * @param string Query to be formatted + * @return string The formatted query + */ +function PMA_SQLPrettyPrint (string) { + if (typeof CodeMirror === 'undefined') { + return string; + } + + var mode = CodeMirror.getMode({}, 'text/x-mysql'); + var stream = new CodeMirror.StringStream(string); + var state = mode.startState(); + var token; + var tokens = []; + var output = ''; + var tabs = function (cnt) { + var ret = ''; + for (var i = 0; i < 4 * cnt; i++) { + ret += ' '; + } + return ret; + }; + + // "root-level" statements + var statements = { + 'select': ['select', 'from', 'on', 'where', 'having', 'limit', 'order by', 'group by'], + 'update': ['update', 'set', 'where'], + 'insert into': ['insert into', 'values'] + }; + // don't put spaces before these tokens + var spaceExceptionsBefore = { ';': true, ',': true, '.': true, '(': true }; + // don't put spaces after these tokens + var spaceExceptionsAfter = { '.': true }; + + // Populate tokens array + var str = ''; + while (! stream.eol()) { + stream.start = stream.pos; + token = mode.token(stream, state); + if (token !== null) { + tokens.push([token, stream.current().toLowerCase()]); + } + } + + var currentStatement = tokens[0][1]; + + if (! statements[currentStatement]) { + return string; + } + // Holds all currently opened code blocks (statement, function or generic) + var blockStack = []; + // Holds the type of block from last iteration (the current is in blockStack[0]) + var previousBlock; + // If a new code block is found, newBlock contains its type for one iteration and vice versa for endBlock + var newBlock; + var endBlock; + // How much to indent in the current line + var indentLevel = 0; + // Holds the "root-level" statements + var statementPart; + var lastStatementPart = statements[currentStatement][0]; + + blockStack.unshift('statement'); + + // Iterate through every token and format accordingly + for (var i = 0; i < tokens.length; i++) { + previousBlock = blockStack[0]; + + // New block => push to stack + if (tokens[i][1] === '(') { + if (i < tokens.length - 1 && tokens[i + 1][0] === 'statement-verb') { + blockStack.unshift(newBlock = 'statement'); + } else if (i > 0 && tokens[i - 1][0] === 'builtin') { + blockStack.unshift(newBlock = 'function'); + } else { + blockStack.unshift(newBlock = 'generic'); + } + } else { + newBlock = null; + } + + // Block end => pop from stack + if (tokens[i][1] === ')') { + endBlock = blockStack[0]; + blockStack.shift(); + } else { + endBlock = null; + } + + // A subquery is starting + if (i > 0 && newBlock === 'statement') { + indentLevel++; + output += '\n' + tabs(indentLevel) + tokens[i][1] + ' ' + tokens[i + 1][1].toUpperCase() + '\n' + tabs(indentLevel + 1); + currentStatement = tokens[i + 1][1]; + i++; + continue; + } + + // A subquery is ending + if (endBlock === 'statement' && indentLevel > 0) { + output += '\n' + tabs(indentLevel); + indentLevel--; + } + + // One less indentation for statement parts (from, where, order by, etc.) and a newline + statementPart = statements[currentStatement].indexOf(tokens[i][1]); + if (statementPart !== -1) { + if (i > 0) { + output += '\n'; + } + output += tabs(indentLevel) + tokens[i][1].toUpperCase(); + output += '\n' + tabs(indentLevel + 1); + lastStatementPart = tokens[i][1]; + // Normal indentation and spaces for everything else + } else { + if (! spaceExceptionsBefore[tokens[i][1]] && + ! (i > 0 && spaceExceptionsAfter[tokens[i - 1][1]]) && + output.charAt(output.length - 1) !== ' ') { + output += ' '; + } + if (tokens[i][0] === 'keyword') { + output += tokens[i][1].toUpperCase(); + } else { + output += tokens[i][1]; + } + } + + // split columns in select and 'update set' clauses, but only inside statements blocks + if ((lastStatementPart === 'select' || lastStatementPart === 'where' || lastStatementPart === 'set') && + tokens[i][1] === ',' && blockStack[0] === 'statement') { + output += '\n' + tabs(indentLevel + 1); + } + + // split conditions in where clauses, but only inside statements blocks + if (lastStatementPart === 'where' && + (tokens[i][1] === 'and' || tokens[i][1] === 'or' || tokens[i][1] === 'xor')) { + if (blockStack[0] === 'statement') { + output += '\n' + tabs(indentLevel + 1); + } + // Todo: Also split and or blocks in newlines & indentation++ + // if (blockStack[0] === 'generic') + // output += ... + } + } + return output; +} + +/** + * jQuery function that uses jQueryUI's dialogs to confirm with user. Does not + * return a jQuery object yet and hence cannot be chained + * + * @param string question + * @param string url URL to be passed to the callbackFn to make + * an Ajax call to + * @param function callbackFn callback to execute after user clicks on OK + * @param function openCallback optional callback to run when dialog is shown + */ + +jQuery.fn.PMA_confirm = function (question, url, callbackFn, openCallback) { + var confirmState = PMA_commonParams.get('confirm'); + if (! confirmState) { + // user does not want to confirm + if ($.isFunction(callbackFn)) { + callbackFn.call(this, url); + return true; + } + } + if (PMA_messages.strDoYouReally === '') { + return true; + } + + /** + * @var button_options Object that stores the options passed to jQueryUI + * dialog + */ + var button_options = [ + { + text: PMA_messages.strOK, + 'class': 'submitOK', + click: function () { + $(this).dialog('close'); + if ($.isFunction(callbackFn)) { + callbackFn.call(this, url); + } + } + }, + { + text: PMA_messages.strCancel, + 'class': 'submitCancel', + click: function () { + $(this).dialog('close'); + } + } + ]; + + $('
      ', { 'id': 'confirm_dialog', 'title': PMA_messages.strConfirm }) + .prepend(question) + .dialog({ + buttons: button_options, + close: function () { + $(this).remove(); + }, + open: openCallback, + modal: true + }); +}; + +/** + * jQuery function to sort a table's body after a new row has been appended to it. + * + * @param string text_selector string to select the sortKey's text + * + * @return jQuery Object for chaining purposes + */ +jQuery.fn.PMA_sort_table = function (text_selector) { + return this.each(function () { + /** + * @var table_body Object referring to the table's element + */ + var table_body = $(this); + /** + * @var rows Object referring to the collection of rows in {@link table_body} + */ + var rows = $(this).find('tr').get(); + + // get the text of the field that we will sort by + $.each(rows, function (index, row) { + row.sortKey = $.trim($(row).find(text_selector).text().toLowerCase()); + }); + + // get the sorted order + rows.sort(function (a, b) { + if (a.sortKey < b.sortKey) { + return -1; + } + if (a.sortKey > b.sortKey) { + return 1; + } + return 0; + }); + + // pull out each row from the table and then append it according to it's order + $.each(rows, function (index, row) { + $(table_body).append(row); + row.sortKey = null; + }); + }); +}; + +/** + * Unbind all event handlers before tearing down a page + */ +AJAX.registerTeardown('functions.js', function () { + $(document).off('submit', '#create_table_form_minimal.ajax'); + $(document).off('submit', 'form.create_table_form.ajax'); + $(document).off('click', 'form.create_table_form.ajax input[name=submit_num_fields]'); + $(document).off('keyup', 'form.create_table_form.ajax input'); + $(document).off('change', 'input[name=partition_count],input[name=subpartition_count],select[name=partition_by]'); +}); + +/** + * jQuery coding for 'Create Table'. Used on db_operations.php, + * db_structure.php and db_tracking.php (i.e., wherever + * PhpMyAdmin\Display\CreateTable is used) + * + * Attach Ajax Event handlers for Create Table + */ +AJAX.registerOnload('functions.js', function () { + /** + * Attach event handler for submission of create table form (save) + */ + $(document).on('submit', 'form.create_table_form.ajax', function (event) { + event.preventDefault(); + + /** + * @var the_form object referring to the create table form + */ + var $form = $(this); + + /* + * First validate the form; if there is a problem, avoid submitting it + * + * checkTableEditForm() needs a pure element and not a jQuery object, + * this is why we pass $form[0] as a parameter (the jQuery object + * is actually an array of DOM elements) + */ + + if (checkTableEditForm($form[0], $form.find('input[name=orig_num_fields]').val())) { + PMA_prepareForAjaxRequest($form); + if (PMA_checkReservedWordColumns($form)) { + PMA_ajaxShowMessage(PMA_messages.strProcessingRequest); + // User wants to submit the form + $.post($form.attr('action'), $form.serialize() + PMA_commonParams.get('arg_separator') + 'do_save_data=1', function (data) { + if (typeof data !== 'undefined' && data.success === true) { + $('#properties_message') + .removeClass('error') + .html(''); + PMA_ajaxShowMessage(data.message); + // Only if the create table dialog (distinct panel) exists + var $createTableDialog = $('#create_table_dialog'); + if ($createTableDialog.length > 0) { + $createTableDialog.dialog('close').remove(); + } + $('#tableslistcontainer').before(data.formatted_sql); + + /** + * @var tables_table Object referring to the element that holds the list of tables + */ + var tables_table = $('#tablesForm').find('tbody').not('#tbl_summary_row'); + // this is the first table created in this db + if (tables_table.length === 0) { + PMA_commonActions.refreshMain( + PMA_commonParams.get('opendb_url') + ); + } else { + /** + * @var curr_last_row Object referring to the last element in {@link tables_table} + */ + var curr_last_row = $(tables_table).find('tr:last'); + /** + * @var curr_last_row_index_string String containing the index of {@link curr_last_row} + */ + var curr_last_row_index_string = $(curr_last_row).find('input:checkbox').attr('id').match(/\d+/)[0]; + /** + * @var curr_last_row_index Index of {@link curr_last_row} + */ + var curr_last_row_index = parseFloat(curr_last_row_index_string); + /** + * @var new_last_row_index Index of the new row to be appended to {@link tables_table} + */ + var new_last_row_index = curr_last_row_index + 1; + /** + * @var new_last_row_id String containing the id of the row to be appended to {@link tables_table} + */ + var new_last_row_id = 'checkbox_tbl_' + new_last_row_index; + + data.new_table_string = data.new_table_string.replace(/checkbox_tbl_/, new_last_row_id); + // append to table + $(data.new_table_string) + .appendTo(tables_table); + + // Sort the table + $(tables_table).PMA_sort_table('th'); + + // Adjust summary row + PMA_adjustTotals(); + } + + // Refresh navigation as a new table has been added + PMA_reloadNavigation(); + // Redirect to table structure page on creation of new table + var argsep = PMA_commonParams.get('arg_separator'); + var params_12 = 'ajax_request=true' + argsep + 'ajax_page_request=true'; + if (! (history && history.pushState)) { + params_12 += PMA_MicroHistory.menus.getRequestParam(); + } + tblStruct_url = 'tbl_structure.php?server=' + data._params.server + + argsep + 'db=' + data._params.db + argsep + 'token=' + data._params.token + + argsep + 'goto=db_structure.php' + argsep + 'table=' + data._params.table + ''; + $.get(tblStruct_url, params_12, AJAX.responseHandler); + } else { + PMA_ajaxShowMessage( + '
      ' + data.error + '
      ', + false + ); + } + }); // end $.post() + } + } // end if (checkTableEditForm() ) + }); // end create table form (save) + + /** + * Submits the intermediate changes in the table creation form + * to refresh the UI accordingly + */ + function submitChangesInCreateTableForm (actionParam) { + /** + * @var the_form object referring to the create table form + */ + var $form = $('form.create_table_form.ajax'); + + var $msgbox = PMA_ajaxShowMessage(PMA_messages.strProcessingRequest); + PMA_prepareForAjaxRequest($form); + + // User wants to add more fields to the table + $.post($form.attr('action'), $form.serialize() + '&' + actionParam, function (data) { + if (typeof data !== 'undefined' && data.success) { + var $pageContent = $('#page_content'); + $pageContent.html(data.message); + PMA_highlightSQL($pageContent); + PMA_verifyColumnsProperties(); + PMA_hideShowConnection($('.create_table_form select[name=tbl_storage_engine]')); + PMA_ajaxRemoveMessage($msgbox); + } else { + PMA_ajaxShowMessage(data.error); + } + }); // end $.post() + } + + /** + * Attach event handler for create table form (add fields) + */ + $(document).on('click', 'form.create_table_form.ajax input[name=submit_num_fields]', function (event) { + event.preventDefault(); + submitChangesInCreateTableForm('submit_num_fields=1'); + }); // end create table form (add fields) + + $(document).on('keydown', 'form.create_table_form.ajax input[name=added_fields]', function (event) { + if (event.keyCode === 13) { + event.preventDefault(); + event.stopImmediatePropagation(); + $(this) + .closest('form') + .find('input[name=submit_num_fields]') + .click(); + } + }); + + /** + * Attach event handler to manage changes in number of partitions and subpartitions + */ + $(document).on('change', 'input[name=partition_count],input[name=subpartition_count],select[name=partition_by]', function (event) { + $this = $(this); + $form = $this.parents('form'); + if ($form.is('.create_table_form.ajax')) { + submitChangesInCreateTableForm('submit_partition_change=1'); + } else { + $form.submit(); + } + }); + + $(document).on('change', 'input[value=AUTO_INCREMENT]', function () { + if (this.checked) { + var col = /\d/.exec($(this).attr('name')); + col = col[0]; + var $selectFieldKey = $('select[name="field_key[' + col + ']"]'); + if ($selectFieldKey.val() === 'none_' + col) { + $selectFieldKey.val('primary_' + col).change(); + } + } + }); + $('body') + .off('click', 'input.preview_sql') + .on('click', 'input.preview_sql', function () { + var $form = $(this).closest('form'); + PMA_previewSQL($form); + }); +}); + + +/** + * Validates the password field in a form + * + * @see PMA_messages.strPasswordEmpty + * @see PMA_messages.strPasswordNotSame + * @param object $the_form The form to be validated + * @return bool + */ +function PMA_checkPassword ($the_form) { + // Did the user select 'no password'? + if ($the_form.find('#nopass_1').is(':checked')) { + return true; + } else { + var $pred = $the_form.find('#select_pred_password'); + if ($pred.length && ($pred.val() === 'none' || $pred.val() === 'keep')) { + return true; + } + } + + var $password = $the_form.find('input[name=pma_pw]'); + var $password_repeat = $the_form.find('input[name=pma_pw2]'); + var alert_msg = false; + + if ($password.val() === '') { + alert_msg = PMA_messages.strPasswordEmpty; + } else if ($password.val() !== $password_repeat.val()) { + alert_msg = PMA_messages.strPasswordNotSame; + } + + if (alert_msg) { + alert(alert_msg); + $password.val(''); + $password_repeat.val(''); + $password.focus(); + return false; + } + return true; +} + +/** + * Attach Ajax event handlers for 'Change Password' on index.php + */ +AJAX.registerOnload('functions.js', function () { + /* Handler for hostname type */ + $(document).on('change', '#select_pred_hostname', function () { + var hostname = $('#pma_hostname'); + if (this.value === 'any') { + hostname.val('%'); + } else if (this.value === 'localhost') { + hostname.val('localhost'); + } else if (this.value === 'thishost' && $(this).data('thishost')) { + hostname.val($(this).data('thishost')); + } else if (this.value === 'hosttable') { + hostname.val('').prop('required', false); + } else if (this.value === 'userdefined') { + hostname.focus().select().prop('required', true); + } + }); + + /* Handler for editing hostname */ + $(document).on('change', '#pma_hostname', function () { + $('#select_pred_hostname').val('userdefined'); + $('#pma_hostname').prop('required', true); + }); + + /* Handler for username type */ + $(document).on('change', '#select_pred_username', function () { + if (this.value === 'any') { + $('#pma_username').val('').prop('required', false); + $('#user_exists_warning').css('display', 'none'); + } else if (this.value === 'userdefined') { + $('#pma_username').focus().select().prop('required', true); + } + }); + + /* Handler for editing username */ + $(document).on('change', '#pma_username', function () { + $('#select_pred_username').val('userdefined'); + $('#pma_username').prop('required', true); + }); + + /* Handler for password type */ + $(document).on('change', '#select_pred_password', function () { + if (this.value === 'none') { + $('#text_pma_pw2').prop('required', false).val(''); + $('#text_pma_pw').prop('required', false).val(''); + } else if (this.value === 'userdefined') { + $('#text_pma_pw2').prop('required', true); + $('#text_pma_pw').prop('required', true).focus().select(); + } else { + $('#text_pma_pw2').prop('required', false); + $('#text_pma_pw').prop('required', false); + } + }); + + /* Handler for editing password */ + $(document).on('change', '#text_pma_pw,#text_pma_pw2', function () { + $('#select_pred_password').val('userdefined'); + $('#text_pma_pw2').prop('required', true); + $('#text_pma_pw').prop('required', true); + }); + + /** + * Unbind all event handlers before tearing down a page + */ + $(document).off('click', '#change_password_anchor.ajax'); + + /** + * Attach Ajax event handler on the change password anchor + */ + + $(document).on('click', '#change_password_anchor.ajax', function (event) { + event.preventDefault(); + + var $msgbox = PMA_ajaxShowMessage(); + + /** + * @var button_options Object containing options to be passed to jQueryUI's dialog + */ + var button_options = {}; + button_options[PMA_messages.strGo] = function () { + event.preventDefault(); + + /** + * @var $the_form Object referring to the change password form + */ + var $the_form = $('#change_password_form'); + + if (! PMA_checkPassword($the_form)) { + return false; + } + + /** + * @var this_value String containing the value of the submit button. + * Need to append this for the change password form on Server Privileges + * page to work + */ + var this_value = $(this).val(); + + var $msgbox = PMA_ajaxShowMessage(PMA_messages.strProcessingRequest); + $the_form.append(''); + + $.post($the_form.attr('action'), $the_form.serialize() + PMA_commonParams.get('arg_separator') + 'change_pw=' + this_value, function (data) { + if (typeof data === 'undefined' || data.success !== true) { + PMA_ajaxShowMessage(data.error, false); + return; + } + + var $pageContent = $('#page_content'); + $pageContent.prepend(data.message); + PMA_highlightSQL($pageContent); + $('#change_password_dialog').hide().remove(); + $('#edit_user_dialog').dialog('close').remove(); + PMA_ajaxRemoveMessage($msgbox); + }); // end $.post() + }; + + button_options[PMA_messages.strCancel] = function () { + $(this).dialog('close'); + }; + $.get($(this).attr('href'), { 'ajax_request': true }, function (data) { + if (typeof data === 'undefined' || !data.success) { + PMA_ajaxShowMessage(data.error, false); + return; + } + + if (data._scripts) { + AJAX.scriptHandler.load(data._scripts); + } + + $('
      ') + .dialog({ + title: PMA_messages.strChangePassword, + width: 600, + close: function (ev, ui) { + $(this).remove(); + }, + buttons: button_options, + modal: true + }) + .append(data.message); + // for this dialog, we remove the fieldset wrapping due to double headings + $('fieldset#fieldset_change_password') + .find('legend').remove().end() + .find('table.noclick').unwrap().addClass('some-margin') + .find('input#text_pma_pw').focus(); + $('#fieldset_change_password_footer').hide(); + PMA_ajaxRemoveMessage($msgbox); + displayPasswordGenerateButton(); + $('#change_password_form').on('submit', function (e) { + e.preventDefault(); + $(this) + .closest('.ui-dialog') + .find('.ui-dialog-buttonpane .ui-button') + .first() + .click(); + }); + }); // end $.get() + }); // end handler for change password anchor +}); // end $() for Change Password + +/** + * Unbind all event handlers before tearing down a page + */ +AJAX.registerTeardown('functions.js', function () { + $(document).off('change', 'select.column_type'); + $(document).off('change', 'select.default_type'); + $(document).off('change', 'select.virtuality'); + $(document).off('change', 'input.allow_null'); + $(document).off('change', '.create_table_form select[name=tbl_storage_engine]'); +}); +/** + * Toggle the hiding/showing of the "Open in ENUM/SET editor" message when + * the page loads and when the selected data type changes + */ +AJAX.registerOnload('functions.js', function () { + // is called here for normal page loads and also when opening + // the Create table dialog + PMA_verifyColumnsProperties(); + // + // needs on() to work also in the Create Table dialog + $(document).on('change', 'select.column_type', function () { + PMA_showNoticeForEnum($(this)); + }); + $(document).on('change', 'select.default_type', function () { + PMA_hideShowDefaultValue($(this)); + }); + $(document).on('change', 'select.virtuality', function () { + PMA_hideShowExpression($(this)); + }); + $(document).on('change', 'input.allow_null', function () { + PMA_validateDefaultValue($(this)); + }); + $(document).on('change', '.create_table_form select[name=tbl_storage_engine]', function () { + PMA_hideShowConnection($(this)); + }); +}); + +/** + * If the chosen storage engine is FEDERATED show connection field. Hide otherwise + * + * @param $engine_selector storage engine selector + */ +function PMA_hideShowConnection ($engine_selector) { + var $connection = $('.create_table_form input[name=connection]'); + var index = $connection.parent('td').index() + 1; + var $labelTh = $connection.parents('tr').prev('tr').children('th:nth-child(' + index + ')'); + if ($engine_selector.val() !== 'FEDERATED') { + $connection + .prop('disabled', true) + .parent('td').hide(); + $labelTh.hide(); + } else { + $connection + .prop('disabled', false) + .parent('td').show(); + $labelTh.show(); + } +} + +/** + * If the column does not allow NULL values, makes sure that default is not NULL + */ +function PMA_validateDefaultValue ($null_checkbox) { + if (! $null_checkbox.prop('checked')) { + var $default = $null_checkbox.closest('tr').find('.default_type'); + if ($default.val() === 'NULL') { + $default.val('NONE'); + } + } +} + +/** + * function to populate the input fields on picking a column from central list + * + * @param string input_id input id of the name field for the column to be populated + * @param integer offset of the selected column in central list of columns + */ +function autoPopulate (input_id, offset) { + var db = PMA_commonParams.get('db'); + var table = PMA_commonParams.get('table'); + input_id = input_id.substring(0, input_id.length - 1); + $('#' + input_id + '1').val(central_column_list[db + '_' + table][offset].col_name); + var col_type = central_column_list[db + '_' + table][offset].col_type.toUpperCase(); + $('#' + input_id + '2').val(col_type); + var $input3 = $('#' + input_id + '3'); + $input3.val(central_column_list[db + '_' + table][offset].col_length); + if (col_type === 'ENUM' || col_type === 'SET') { + $input3.next().show(); + } else { + $input3.next().hide(); + } + var col_default = central_column_list[db + '_' + table][offset].col_default.toUpperCase(); + var $input4 = $('#' + input_id + '4'); + if (col_default !== '' && col_default !== 'NULL' && col_default !== 'CURRENT_TIMESTAMP' && col_default !== 'CURRENT_TIMESTAMP()') { + $input4.val('USER_DEFINED'); + $input4.next().next().show(); + $input4.next().next().val(central_column_list[db + '_' + table][offset].col_default); + } else { + $input4.val(central_column_list[db + '_' + table][offset].col_default); + $input4.next().next().hide(); + } + $('#' + input_id + '5').val(central_column_list[db + '_' + table][offset].col_collation); + var $input6 = $('#' + input_id + '6'); + $input6.val(central_column_list[db + '_' + table][offset].col_attribute); + if (central_column_list[db + '_' + table][offset].col_extra === 'on update CURRENT_TIMESTAMP') { + $input6.val(central_column_list[db + '_' + table][offset].col_extra); + } + if (central_column_list[db + '_' + table][offset].col_extra.toUpperCase() === 'AUTO_INCREMENT') { + $('#' + input_id + '9').prop('checked',true).change(); + } else { + $('#' + input_id + '9').prop('checked',false); + } + if (central_column_list[db + '_' + table][offset].col_isNull !== '0') { + $('#' + input_id + '7').prop('checked',true); + } else { + $('#' + input_id + '7').prop('checked',false); + } +} + +/** + * Unbind all event handlers before tearing down a page + */ +AJAX.registerTeardown('functions.js', function () { + $(document).off('click', 'a.open_enum_editor'); + $(document).off('click', 'input.add_value'); + $(document).off('click', '#enum_editor td.drop'); + $(document).off('click', 'a.central_columns_dialog'); +}); +/** + * @var $enum_editor_dialog An object that points to the jQuery + * dialog of the ENUM/SET editor + */ +var $enum_editor_dialog = null; +/** + * Opens the ENUM/SET editor and controls its functions + */ +AJAX.registerOnload('functions.js', function () { + $(document).on('click', 'a.open_enum_editor', function () { + // Get the name of the column that is being edited + var colname = $(this).closest('tr').find('input:first').val(); + var title; + var i; + // And use it to make up a title for the page + if (colname.length < 1) { + title = PMA_messages.enum_newColumnVals; + } else { + title = PMA_messages.enum_columnVals.replace( + /%s/, + '"' + escapeHtml(decodeURIComponent(colname)) + '"' + ); + } + // Get the values as a string + var inputstring = $(this) + .closest('td') + .find('input') + .val(); + // Escape html entities + inputstring = $('
      ') + .text(inputstring) + .html(); + // Parse the values, escaping quotes and + // slashes on the fly, into an array + var values = []; + var in_string = false; + var curr; + var next; + var buffer = ''; + for (i = 0; i < inputstring.length; i++) { + curr = inputstring.charAt(i); + next = i === inputstring.length ? '' : inputstring.charAt(i + 1); + if (! in_string && curr === '\'') { + in_string = true; + } else if (in_string && curr === '\\' && next === '\\') { + buffer += '\'; + i++; + } else if (in_string && next === '\'' && (curr === '\'' || curr === '\\')) { + buffer += '''; + i++; + } else if (in_string && curr === '\'') { + in_string = false; + values.push(buffer); + buffer = ''; + } else if (in_string) { + buffer += curr; + } + } + if (buffer.length > 0) { + // The leftovers in the buffer are the last value (if any) + values.push(buffer); + } + var fields = ''; + // If there are no values, maybe the user is about to make a + // new list so we add a few for him/her to get started with. + if (values.length === 0) { + values.push('', '', '', ''); + } + // Add the parsed values to the editor + var drop_icon = PMA_getImage('b_drop'); + for (i = 0; i < values.length; i++) { + fields += '' + + '' + + '' + + drop_icon + + ''; + } + /** + * @var dialog HTML code for the ENUM/SET dialog + */ + var dialog = '
      ' + + '
      ' + + '' + title + '' + + '

      ' + PMA_getImage('s_notice') + + PMA_messages.enum_hint + '

      ' + + '' + fields + '
      ' + + '
      ' + + '
      ' + + '
      ' + + '
      ' + + '
      ' + + '
      ' + + '' + + '
      ' + + '
      '; + /** + * @var Defines functions to be called when the buttons in + * the buttonOptions jQuery dialog bar are pressed + */ + var buttonOptions = {}; + buttonOptions[PMA_messages.strGo] = function () { + // When the submit button is clicked, + // put the data back into the original form + var value_array = []; + $(this).find('.values input').each(function (index, elm) { + var val = elm.value.replace(/\\/g, '\\\\').replace(/'/g, '\'\''); + value_array.push('\'' + val + '\''); + }); + // get the Length/Values text field where this value belongs + var values_id = $(this).find('input[type=\'hidden\']').val(); + $('input#' + values_id).val(value_array.join(',')); + $(this).dialog('close'); + }; + buttonOptions[PMA_messages.strClose] = function () { + $(this).dialog('close'); + }; + // Show the dialog + var width = parseInt( + (parseInt($('html').css('font-size'), 10) / 13) * 340, + 10 + ); + if (! width) { + width = 340; + } + $enum_editor_dialog = $(dialog).dialog({ + minWidth: width, + maxHeight: 450, + modal: true, + title: PMA_messages.enum_editor, + buttons: buttonOptions, + open: function () { + // Focus the "Go" button after opening the dialog + $(this).closest('.ui-dialog').find('.ui-dialog-buttonpane button:first').focus(); + }, + close: function () { + $(this).remove(); + } + }); + // slider for choosing how many fields to add + $enum_editor_dialog.find('.slider').slider({ + animate: true, + range: 'min', + value: 1, + min: 1, + max: 9, + slide: function (event, ui) { + $(this).closest('table').find('input[type=submit]').val( + PMA_sprintf(PMA_messages.enum_addValue, ui.value) + ); + } + }); + // Focus the slider, otherwise it looks nearly transparent + $('a.ui-slider-handle').addClass('ui-state-focus'); + return false; + }); + + $(document).on('click', 'a.central_columns_dialog', function (e) { + var href = 'db_central_columns.php'; + var db = PMA_commonParams.get('db'); + var table = PMA_commonParams.get('table'); + var maxRows = $(this).data('maxrows'); + var pick = $(this).data('pick'); + if (pick !== false) { + pick = true; + } + var params = { + 'ajax_request' : true, + 'server' : PMA_commonParams.get('server'), + 'db' : PMA_commonParams.get('db'), + 'cur_table' : PMA_commonParams.get('table'), + 'getColumnList':true + }; + var colid = $(this).closest('td').find('input').attr('id'); + var fields = ''; + if (! (db + '_' + table in central_column_list)) { + central_column_list.push(db + '_' + table); + $.ajax({ + type: 'POST', + url: href, + data: params, + success: function (data) { + central_column_list[db + '_' + table] = JSON.parse(data.message); + }, + async:false + }); + } + var i = 0; + var list_size = central_column_list[db + '_' + table].length; + var min = (list_size <= maxRows) ? list_size : maxRows; + for (i = 0; i < min; i++) { + fields += '
      ' + + escapeHtml(central_column_list[db + '_' + table][i].col_name) + + '
      ' + central_column_list[db + '_' + table][i].col_type; + + if (central_column_list[db + '_' + table][i].col_attribute !== '') { + fields += '(' + escapeHtml(central_column_list[db + '_' + table][i].col_attribute) + ') '; + } + if (central_column_list[db + '_' + table][i].col_length !== '') { + fields += '(' + escapeHtml(central_column_list[db + '_' + table][i].col_length) + ') '; + } + fields += escapeHtml(central_column_list[db + '_' + table][i].col_extra) + '' + + '
      '; + if (pick) { + fields += ''; + } + fields += ''; + } + var result_pointer = i; + var search_in = ''; + if (fields === '') { + fields = PMA_sprintf(PMA_messages.strEmptyCentralList, '\'' + escapeHtml(db) + '\''); + search_in = ''; + } + var seeMore = ''; + if (list_size > maxRows) { + seeMore = '
      ' + + '' + PMA_messages.seeMore + '
      '; + } + var central_columns_dialog = '
      ' + + '
      ' + + search_in + + '' + fields + '
      ' + + '
      ' + + seeMore + + '
      '; + + var width = parseInt( + (parseInt($('html').css('font-size'), 10) / 13) * 500, + 10 + ); + if (! width) { + width = 500; + } + var buttonOptions = {}; + var $central_columns_dialog = $(central_columns_dialog).dialog({ + minWidth: width, + maxHeight: 450, + modal: true, + title: PMA_messages.pickColumnTitle, + buttons: buttonOptions, + open: function () { + $('#col_list').on('click', '.pick', function () { + $central_columns_dialog.remove(); + }); + $('.filter_rows').on('keyup', function () { + $.uiTableFilter($('#col_list'), $(this).val()); + }); + $('#seeMore').click(function () { + fields = ''; + min = (list_size <= maxRows + result_pointer) ? list_size : maxRows + result_pointer; + for (i = result_pointer; i < min; i++) { + fields += '
      ' + + central_column_list[db + '_' + table][i].col_name + + '
      ' + + central_column_list[db + '_' + table][i].col_type; + + if (central_column_list[db + '_' + table][i].col_attribute !== '') { + fields += '(' + central_column_list[db + '_' + table][i].col_attribute + ') '; + } + if (central_column_list[db + '_' + table][i].col_length !== '') { + fields += '(' + central_column_list[db + '_' + table][i].col_length + ') '; + } + fields += central_column_list[db + '_' + table][i].col_extra + '' + + '
      '; + if (pick) { + fields += ''; + } + fields += ''; + } + $('#col_list').append(fields); + result_pointer = i; + if (result_pointer === list_size) { + $('.tblFooters').hide(); + } + return false; + }); + $(this).closest('.ui-dialog').find('.ui-dialog-buttonpane button:first').focus(); + }, + close: function () { + $('#col_list').off('click', '.pick'); + $('.filter_rows').off('keyup'); + $(this).remove(); + } + }); + return false; + }); + + // $(document).on('click', 'a.show_central_list',function(e) { + + // }); + // When "add a new value" is clicked, append an empty text field + $(document).on('click', 'input.add_value', function (e) { + e.preventDefault(); + var num_new_rows = $enum_editor_dialog.find('div.slider').slider('value'); + while (num_new_rows--) { + $enum_editor_dialog.find('.values') + .append( + '' + + '' + + '' + + PMA_getImage('b_drop') + + '' + ) + .find('tr:last') + .show('fast'); + } + }); + + // Removes the specified row from the enum editor + $(document).on('click', '#enum_editor td.drop', function () { + $(this).closest('tr').hide('fast', function () { + $(this).remove(); + }); + }); +}); + +/** + * Ensures indexes names are valid according to their type and, for a primary + * key, lock index name to 'PRIMARY' + * @param string form_id Variable which parses the form name as + * the input + * @return boolean false if there is no index form, true else + */ +function checkIndexName (form_id) { + if ($('#' + form_id).length === 0) { + return false; + } + + // Gets the elements pointers + var $the_idx_name = $('#input_index_name'); + var $the_idx_choice = $('#select_index_choice'); + + // Index is a primary key + if ($the_idx_choice.find('option:selected').val() === 'PRIMARY') { + $the_idx_name.val('PRIMARY'); + $the_idx_name.prop('disabled', true); + } else { + if ($the_idx_name.val() === 'PRIMARY') { + $the_idx_name.val(''); + } + $the_idx_name.prop('disabled', false); + } + + return true; +} // end of the 'checkIndexName()' function + +AJAX.registerTeardown('functions.js', function () { + $(document).off('click', '#index_frm input[type=submit]'); +}); +AJAX.registerOnload('functions.js', function () { + /** + * Handler for adding more columns to an index in the editor + */ + $(document).on('click', '#index_frm input[type=submit]', function (event) { + event.preventDefault(); + var rows_to_add = $(this) + .closest('fieldset') + .find('.slider') + .slider('value'); + + var tempEmptyVal = function () { + $(this).val(''); + }; + + var tempSetFocus = function () { + if ($(this).find('option:selected').val() === '') { + return true; + } + $(this).closest('tr').find('input').focus(); + }; + + while (rows_to_add--) { + var $indexColumns = $('#index_columns'); + var $newrow = $indexColumns + .find('tbody > tr:first') + .clone() + .appendTo( + $indexColumns.find('tbody') + ); + $newrow.find(':input').each(tempEmptyVal); + // focus index size input on column picked + $newrow.find('select').change(tempSetFocus); + } + }); +}); + +function indexEditorDialog (url, title, callback_success, callback_failure) { + /* Remove the hidden dialogs if there are*/ + var $editIndexDialog = $('#edit_index_dialog'); + if ($editIndexDialog.length !== 0) { + $editIndexDialog.remove(); + } + var $div = $('
      '); + + /** + * @var button_options Object that stores the options + * passed to jQueryUI dialog + */ + var button_options = {}; + button_options[PMA_messages.strGo] = function () { + /** + * @var the_form object referring to the export form + */ + var $form = $('#index_frm'); + var $msgbox = PMA_ajaxShowMessage(PMA_messages.strProcessingRequest); + PMA_prepareForAjaxRequest($form); + // User wants to submit the form + $.post($form.attr('action'), $form.serialize() + PMA_commonParams.get('arg_separator') + 'do_save_data=1', function (data) { + var $sqlqueryresults = $('.sqlqueryresults'); + if ($sqlqueryresults.length !== 0) { + $sqlqueryresults.remove(); + } + if (typeof data !== 'undefined' && data.success === true) { + PMA_ajaxShowMessage(data.message); + PMA_highlightSQL($('.result_query')); + $('.result_query .notice').remove(); + /* Reload the field form*/ + $('#table_index').remove(); + $('
      ') + .append(data.index_table) + .find('#table_index') + .insertAfter('#index_header'); + var $editIndexDialog = $('#edit_index_dialog'); + if ($editIndexDialog.length > 0) { + $editIndexDialog.dialog('close'); + } + $('div.no_indexes_defined').hide(); + if (callback_success) { + callback_success(); + } + PMA_reloadNavigation(); + } else { + var $temp_div = $('
      ').append(data.error); + var $error; + if ($temp_div.find('.error code').length !== 0) { + $error = $temp_div.find('.error code').addClass('error'); + } else { + $error = $temp_div; + } + if (callback_failure) { + callback_failure(); + } + PMA_ajaxShowMessage($error, false); + } + }); // end $.post() + }; + button_options[PMA_messages.strPreviewSQL] = function () { + // Function for Previewing SQL + var $form = $('#index_frm'); + PMA_previewSQL($form); + }; + button_options[PMA_messages.strCancel] = function () { + $(this).dialog('close'); + }; + var $msgbox = PMA_ajaxShowMessage(); + $.post('tbl_indexes.php', url, function (data) { + if (typeof data !== 'undefined' && data.success === false) { + // in the case of an error, show the error message returned. + PMA_ajaxShowMessage(data.error, false); + } else { + PMA_ajaxRemoveMessage($msgbox); + // Show dialog if the request was successful + $div + .append(data.message) + .dialog({ + title: title, + width: 'auto', + open: PMA_verifyColumnsProperties, + modal: true, + buttons: button_options, + close: function () { + $(this).remove(); + } + }); + $div.find('.tblFooters').remove(); + showIndexEditDialog($div); + } + }); // end $.get() +} + +function showIndexEditDialog ($outer) { + checkIndexType(); + checkIndexName('index_frm'); + var $indexColumns = $('#index_columns'); + $indexColumns.find('td').each(function () { + $(this).css('width', $(this).width() + 'px'); + }); + $indexColumns.find('tbody').sortable({ + axis: 'y', + containment: $indexColumns.find('tbody'), + tolerance: 'pointer' + }); + PMA_showHints($outer); + PMA_init_slider(); + // Add a slider for selecting how many columns to add to the index + $outer.find('.slider').slider({ + animate: true, + value: 1, + min: 1, + max: 16, + slide: function (event, ui) { + $(this).closest('fieldset').find('input[type=submit]').val( + PMA_sprintf(PMA_messages.strAddToIndex, ui.value) + ); + } + }); + $('div.add_fields').removeClass('hide'); + // focus index size input on column picked + $outer.find('table#index_columns select').change(function () { + if ($(this).find('option:selected').val() === '') { + return true; + } + $(this).closest('tr').find('input').focus(); + }); + // Focus the slider, otherwise it looks nearly transparent + $('a.ui-slider-handle').addClass('ui-state-focus'); + // set focus on index name input, if empty + var input = $outer.find('input#input_index_name'); + if (! input.val()) { + input.focus(); + } +} + +/** + * Function to display tooltips that were + * generated on the PHP side by PhpMyAdmin\Util::showHint() + * + * @param object $div a div jquery object which specifies the + * domain for searching for tooltips. If we + * omit this parameter the function searches + * in the whole body + **/ +function PMA_showHints ($div) { + if ($div === undefined || ! $div instanceof jQuery || $div.length === 0) { + $div = $('body'); + } + $div.find('.pma_hint').each(function () { + PMA_tooltip( + $(this).children('img'), + 'img', + $(this).children('span').html() + ); + }); +} + +AJAX.registerOnload('functions.js', function () { + PMA_showHints(); +}); + +function PMA_mainMenuResizerCallback () { + // 5 px margin for jumping menu in Chrome + return $(document.body).width() - 5; +} +// This must be fired only once after the initial page load +$(function () { + // Initialise the menu resize plugin + $('#topmenu').menuResizer(PMA_mainMenuResizerCallback); + // register resize event + $(window).on('resize', function () { + $('#topmenu').menuResizer('resize'); + }); +}); + +/** + * Get the row number from the classlist (for example, row_1) + */ +function PMA_getRowNumber (classlist) { + return parseInt(classlist.split(/\s+row_/)[1], 10); +} + +/** + * Changes status of slider + */ +function PMA_set_status_label ($element) { + var text; + if ($element.css('display') === 'none') { + text = '+ '; + } else { + text = '- '; + } + $element.closest('.slide-wrapper').prev().find('span').text(text); +} + +/** + * var toggleButton This is a function that creates a toggle + * sliding button given a jQuery reference + * to the correct DOM element + */ +var toggleButton = function ($obj) { + // In rtl mode the toggle switch is flipped horizontally + // so we need to take that into account + var right; + if ($('span.text_direction', $obj).text() === 'ltr') { + right = 'right'; + } else { + right = 'left'; + } + /** + * var h Height of the button, used to scale the + * background image and position the layers + */ + var h = $obj.height(); + $('img', $obj).height(h); + $('table', $obj).css('bottom', h - 1); + /** + * var on Width of the "ON" part of the toggle switch + * var off Width of the "OFF" part of the toggle switch + */ + var on = $('td.toggleOn', $obj).width(); + var off = $('td.toggleOff', $obj).width(); + // Make the "ON" and "OFF" parts of the switch the same size + // + 2 pixels to avoid overflowed + $('td.toggleOn > div', $obj).width(Math.max(on, off) + 2); + $('td.toggleOff > div', $obj).width(Math.max(on, off) + 2); + /** + * var w Width of the central part of the switch + */ + var w = parseInt(($('img', $obj).height() / 16) * 22, 10); + // Resize the central part of the switch on the top + // layer to match the background + $('table td:nth-child(2) > div', $obj).width(w); + /** + * var imgw Width of the background image + * var tblw Width of the foreground layer + * var offset By how many pixels to move the background + * image, so that it matches the top layer + */ + var imgw = $('img', $obj).width(); + var tblw = $('table', $obj).width(); + var offset = parseInt(((imgw - tblw) / 2), 10); + // Move the background to match the layout of the top layer + $obj.find('img').css(right, offset); + /** + * var offw Outer width of the "ON" part of the toggle switch + * var btnw Outer width of the central part of the switch + */ + var offw = $('td.toggleOff', $obj).outerWidth(); + var btnw = $('table td:nth-child(2)', $obj).outerWidth(); + // Resize the main div so that exactly one side of + // the switch plus the central part fit into it. + $obj.width(offw + btnw + 2); + /** + * var move How many pixels to move the + * switch by when toggling + */ + var move = $('td.toggleOff', $obj).outerWidth(); + // If the switch is initialized to the + // OFF state we need to move it now. + if ($('div.container', $obj).hasClass('off')) { + if (right === 'right') { + $('div.container', $obj).animate({ 'left': '-=' + move + 'px' }, 0); + } else { + $('div.container', $obj).animate({ 'left': '+=' + move + 'px' }, 0); + } + } + // Attach an 'onclick' event to the switch + $('div.container', $obj).click(function () { + if ($(this).hasClass('isActive')) { + return false; + } else { + $(this).addClass('isActive'); + } + var $msg = PMA_ajaxShowMessage(); + var $container = $(this); + var callback = $('span.callback', this).text(); + var operator; + var url; + var removeClass; + var addClass; + // Perform the actual toggle + if ($(this).hasClass('on')) { + if (right === 'right') { + operator = '-='; + } else { + operator = '+='; + } + url = $(this).find('td.toggleOff > span').text(); + removeClass = 'on'; + addClass = 'off'; + } else { + if (right === 'right') { + operator = '+='; + } else { + operator = '-='; + } + url = $(this).find('td.toggleOn > span').text(); + removeClass = 'off'; + addClass = 'on'; + } + + var parts = url.split('?'); + $.post(parts[0], parts[1] + '&ajax_request=true', function (data) { + if (typeof data !== 'undefined' && data.success === true) { + PMA_ajaxRemoveMessage($msg); + $container + .removeClass(removeClass) + .addClass(addClass) + .animate({ 'left': operator + move + 'px' }, function () { + $container.removeClass('isActive'); + }); + eval(callback); + } else { + PMA_ajaxShowMessage(data.error, false); + $container.removeClass('isActive'); + } + }); + }); +}; + +/** + * Unbind all event handlers before tearing down a page + */ +AJAX.registerTeardown('functions.js', function () { + $('div.container').off('click'); +}); +/** + * Initialise all toggle buttons + */ +AJAX.registerOnload('functions.js', function () { + $('div.toggleAjax').each(function () { + var $button = $(this).show(); + $button.find('img').each(function () { + if (this.complete) { + toggleButton($button); + } else { + $(this).load(function () { + toggleButton($button); + }); + } + }); + }); +}); + +/** + * Unbind all event handlers before tearing down a page + */ +AJAX.registerTeardown('functions.js', function () { + $(document).off('change', 'select.pageselector'); + $('#update_recent_tables').off('ready'); + $('#sync_favorite_tables').off('ready'); +}); + +AJAX.registerOnload('functions.js', function () { + /** + * Autosubmit page selector + */ + $(document).on('change', 'select.pageselector', function (event) { + event.stopPropagation(); + // Check where to load the new content + if ($(this).closest('#pma_navigation').length === 0) { + // For the main page we don't need to do anything, + $(this).closest('form').submit(); + } else { + // but for the navigation we need to manually replace the content + PMA_navigationTreePagination($(this)); + } + }); + + /** + * Load version information asynchronously. + */ + if ($('li.jsversioncheck').length > 0) { + $.ajax({ + dataType: 'json', + url: 'version_check.php', + method: 'POST', + data: { + 'server': PMA_commonParams.get('server') + }, + success: PMA_current_version + }); + } + + if ($('#is_git_revision').length > 0) { + setTimeout(PMA_display_git_revision, 10); + } + + /** + * Slider effect. + */ + PMA_init_slider(); + + var $updateRecentTables = $('#update_recent_tables'); + if ($updateRecentTables.length) { + $.get( + $updateRecentTables.attr('href'), + { no_debug: true }, + function (data) { + if (typeof data !== 'undefined' && data.success === true) { + $('#pma_recent_list').html(data.list); + } + } + ); + } + + // Sync favorite tables from localStorage to pmadb. + if ($('#sync_favorite_tables').length) { + $.ajax({ + url: $('#sync_favorite_tables').attr('href'), + cache: false, + type: 'POST', + data: { + favorite_tables: (isStorageSupported('localStorage') && typeof window.localStorage.favorite_tables !== 'undefined') + ? window.localStorage.favorite_tables + : '', + server: PMA_commonParams.get('server'), + no_debug: true + }, + success: function (data) { + // Update localStorage. + if (isStorageSupported('localStorage')) { + window.localStorage.favorite_tables = data.favorite_tables; + } + $('#pma_favorite_list').html(data.list); + } + }); + } +}); // end of $() + +/** + * Submits the form placed in place of a link due to the excessive url length + * + * @param $link anchor + * @returns {Boolean} + */ +function submitFormLink ($link) { + if ($link.attr('href').indexOf('=') !== -1) { + var data = $link.attr('href').substr($link.attr('href').indexOf('#') + 1).split('=', 2); + $link.parents('form').append(''); + } + $link.parents('form').submit(); +} + +/** + * Initializes slider effect. + */ +function PMA_init_slider () { + $('div.pma_auto_slider').each(function () { + var $this = $(this); + if ($this.data('slider_init_done')) { + return; + } + var $wrapper = $('
      ', { 'class': 'slide-wrapper' }); + $wrapper.toggle($this.is(':visible')); + $('', { href: '#' + this.id, 'class': 'ajax' }) + .text($this.attr('title')) + .prepend($('')) + .insertBefore($this) + .click(function () { + var $wrapper = $this.closest('.slide-wrapper'); + var visible = $this.is(':visible'); + if (!visible) { + $wrapper.show(); + } + $this[visible ? 'hide' : 'show']('blind', function () { + $wrapper.toggle(!visible); + $wrapper.parent().toggleClass('print_ignore', visible); + PMA_set_status_label($this); + }); + return false; + }); + $this.wrap($wrapper); + $this.removeAttr('title'); + PMA_set_status_label($this); + $this.data('slider_init_done', 1); + }); +} + +/** + * Initializes slider effect. + */ +AJAX.registerOnload('functions.js', function () { + PMA_init_slider(); +}); + +/** + * Restores sliders to the state they were in before initialisation. + */ +AJAX.registerTeardown('functions.js', function () { + $('div.pma_auto_slider').each(function () { + var $this = $(this); + $this.removeData(); + $this.parent().replaceWith($this); + $this.parent().children('a').remove(); + }); +}); + +/** + * Creates a message inside an object with a sliding effect + * + * @param msg A string containing the text to display + * @param $obj a jQuery object containing the reference + * to the element where to put the message + * This is optional, if no element is + * provided, one will be created below the + * navigation links at the top of the page + * + * @return bool True on success, false on failure + */ +function PMA_slidingMessage (msg, $obj) { + if (msg === undefined || msg.length === 0) { + // Don't show an empty message + return false; + } + if ($obj === undefined || ! $obj instanceof jQuery || $obj.length === 0) { + // If the second argument was not supplied, + // we might have to create a new DOM node. + if ($('#PMA_slidingMessage').length === 0) { + $('#page_content').prepend( + '' + ); + } + $obj = $('#PMA_slidingMessage'); + } + if ($obj.has('div').length > 0) { + // If there already is a message inside the + // target object, we must get rid of it + $obj + .find('div') + .first() + .fadeOut(function () { + $obj + .children() + .remove(); + $obj + .append('
      ' + msg + '
      '); + // highlight any sql before taking height; + PMA_highlightSQL($obj); + $obj.find('div') + .first() + .hide(); + $obj + .animate({ + height: $obj.find('div').first().height() + }) + .find('div') + .first() + .fadeIn(); + }); + } else { + // Object does not already have a message + // inside it, so we simply slide it down + $obj.width('100%') + .html('
      ' + msg + '
      '); + // highlight any sql before taking height; + PMA_highlightSQL($obj); + var h = $obj + .find('div') + .first() + .hide() + .height(); + $obj + .find('div') + .first() + .css('height', 0) + .show() + .animate({ + height: h + }, function () { + // Set the height of the parent + // to the height of the child + $obj + .height( + $obj + .find('div') + .first() + .height() + ); + }); + } + return true; +} // end PMA_slidingMessage() + +/** + * Attach CodeMirror2 editor to SQL edit area. + */ +AJAX.registerOnload('functions.js', function () { + var $elm = $('#sqlquery'); + if ($elm.length > 0) { + if (typeof CodeMirror !== 'undefined') { + codemirror_editor = PMA_getSQLEditor($elm); + codemirror_editor.focus(); + codemirror_editor.on('blur', updateQueryParameters); + } else { + // without codemirror + $elm.focus().on('blur', updateQueryParameters); + } + } + PMA_highlightSQL($('body')); +}); +AJAX.registerTeardown('functions.js', function () { + if (codemirror_editor) { + $('#sqlquery').text(codemirror_editor.getValue()); + codemirror_editor.toTextArea(); + codemirror_editor = false; + } +}); +AJAX.registerOnload('functions.js', function () { + // initializes all lock-page elements lock-id and + // val-hash data property + $('#page_content form.lock-page textarea, ' + + '#page_content form.lock-page input[type="text"], ' + + '#page_content form.lock-page input[type="number"], ' + + '#page_content form.lock-page select').each(function (i) { + $(this).data('lock-id', i); + // val-hash is the hash of default value of the field + // so that it can be compared with new value hash + // to check whether field was modified or not. + $(this).data('val-hash', AJAX.hash($(this).val())); + }); + + // initializes lock-page elements (input types checkbox and radio buttons) + // lock-id and val-hash data property + $('#page_content form.lock-page input[type="checkbox"], ' + + '#page_content form.lock-page input[type="radio"]').each(function (i) { + $(this).data('lock-id', i); + $(this).data('val-hash', AJAX.hash($(this).is(':checked'))); + }); +}); + +/** + * jQuery plugin to correctly filter input fields by value, needed + * because some nasty values may break selector syntax + */ +(function ($) { + $.fn.filterByValue = function (value) { + return this.filter(function () { + return $(this).val() === value; + }); + }; +}(jQuery)); + +/** + * Return value of a cell in a table. + */ +function PMA_getCellValue (td) { + var $td = $(td); + if ($td.is('.null')) { + return ''; + } else if ((! $td.is('.to_be_saved') + || $td.is('.set')) + && $td.data('original_data') + ) { + return $td.data('original_data'); + } else { + return $td.text(); + } +} + +$(window).on('popstate', function (event, data) { + $('#printcss').attr('media','print'); + return true; +}); + +/** + * Unbind all event handlers before tearing down a page + */ +AJAX.registerTeardown('functions.js', function () { + $(document).off('click', 'a.themeselect'); + $(document).off('change', '.autosubmit'); + $('a.take_theme').off('click'); +}); + +AJAX.registerOnload('functions.js', function () { + /** + * Theme selector. + */ + $(document).on('click', 'a.themeselect', function (e) { + window.open( + e.target, + 'themes', + 'left=10,top=20,width=510,height=350,scrollbars=yes,status=yes,resizable=yes' + ); + return false; + }); + + /** + * Automatic form submission on change. + */ + $(document).on('change', '.autosubmit', function (e) { + $(this).closest('form').submit(); + }); + + /** + * Theme changer. + */ + $('a.take_theme').click(function (e) { + var what = this.name; + if (window.opener && window.opener.document.forms.setTheme.elements.set_theme) { + window.opener.document.forms.setTheme.elements.set_theme.value = what; + window.opener.document.forms.setTheme.submit(); + window.close(); + return false; + } + return true; + }); +}); + +/** + * Produce print preview + */ +function printPreview () { + $('#printcss').attr('media','all'); + createPrintAndBackButtons(); +} + +/** + * Create print and back buttons in preview page + */ +function createPrintAndBackButtons () { + var back_button = $('',{ + type: 'button', + value: PMA_messages.back, + id: 'back_button_print_view' + }); + back_button.click(removePrintAndBackButton); + back_button.appendTo('#page_content'); + var print_button = $('',{ + type: 'button', + value: PMA_messages.print, + id: 'print_button_print_view' + }); + print_button.click(printPage); + print_button.appendTo('#page_content'); +} + +/** + * Remove print and back buttons and revert to normal view + */ +function removePrintAndBackButton () { + $('#printcss').attr('media','print'); + $('#back_button_print_view').remove(); + $('#print_button_print_view').remove(); +} + +/** + * Print page + */ +function printPage () { + if (typeof(window.print) !== 'undefined') { + window.print(); + } +} + +/** + * Unbind all event handlers before tearing down a page + */ +AJAX.registerTeardown('functions.js', function () { + $('input#print').off('click'); + $(document).off('click', 'a.create_view.ajax'); + $(document).off('keydown', '#createViewDialog input, #createViewDialog select'); + $(document).off('change', '#fkc_checkbox'); +}); + +AJAX.registerOnload('functions.js', function () { + $('input#print').click(printPage); + $('.logout').click(function () { + var form = $( + '
      ' + + '' + + '
      ' + ); + $('body').append(form); + form.submit(); + return false; + }); + /** + * Ajaxification for the "Create View" action + */ + $(document).on('click', 'a.create_view.ajax', function (e) { + e.preventDefault(); + PMA_createViewDialog($(this)); + }); + /** + * Attach Ajax event handlers for input fields in the editor + * and used to submit the Ajax request when the ENTER key is pressed. + */ + if ($('#createViewDialog').length !== 0) { + $(document).on('keydown', '#createViewDialog input, #createViewDialog select', function (e) { + if (e.which === 13) { // 13 is the ENTER key + e.preventDefault(); + + // with preventing default, selection by ' + + '' + + ''; +} + +/** + * Initialize the visualization in the GIS data editor. + */ +function initGISEditorVisualization () { + // Loads either SVG or OSM visualization based on the choice + selectVisualization(); + // Adds necessary styles to the div that coontains the openStreetMap + styleOSM(); + // Loads the SVG element and make a reference to it + loadSVG(); + // Adds controllers for zooming and panning + addZoomPanControllers(); + zoomAndPan(); +} + +/** + * Loads JavaScript files and the GIS editor. + * + * @param value current value of the geometry field + * @param field field name + * @param type geometry type + * @param input_name name of the input field + * @param token token + */ +function loadJSAndGISEditor (value, field, type, input_name) { + var head = document.getElementsByTagName('head')[0]; + var script; + + // Loads a set of small JS file needed for the GIS editor + var smallScripts = ['js/vendor/jquery/jquery.svg.js', + 'js/vendor/jquery/jquery.mousewheel.js', + 'js/vendor/jquery/jquery.event.drag-2.2.js', + 'js/tbl_gis_visualization.js']; + + for (var i = 0; i < smallScripts.length; i++) { + script = document.createElement('script'); + script.type = 'text/javascript'; + script.src = smallScripts[i]; + head.appendChild(script); + } + + // OpenLayers.js is BIG and takes time. So asynchronous loading would not work. + // Load the JS and do a callback to load the content for the GIS Editor. + script = document.createElement('script'); + script.type = 'text/javascript'; + + script.onreadystatechange = function () { + if (this.readyState === 'complete') { + loadGISEditor(value, field, type, input_name); + } + }; + script.onload = function () { + loadGISEditor(value, field, type, input_name); + }; + script.onerror = function () { + loadGISEditor(value, field, type, input_name); + }; + + script.src = 'js/vendor/openlayers/OpenLayers.js'; + head.appendChild(script); + + gisEditorLoaded = true; +} + +/** + * Loads the GIS editor via AJAX + * + * @param value current value of the geometry field + * @param field field name + * @param type geometry type + * @param input_name name of the input field + */ +function loadGISEditor (value, field, type, input_name) { + var $gis_editor = $('#gis_editor'); + $.post('gis_data_editor.php', { + 'field' : field, + 'value' : value, + 'type' : type, + 'input_name' : input_name, + 'get_gis_editor' : true, + 'ajax_request': true, + 'server': PMA_commonParams.get('server') + }, function (data) { + if (typeof data !== 'undefined' && data.success === true) { + $gis_editor.html(data.gis_editor); + initGISEditorVisualization(); + prepareJSVersion(); + } else { + PMA_ajaxShowMessage(data.error, false); + } + }, 'json'); +} + +/** + * Opens up the dialog for the GIS data editor. + */ +function openGISEditor () { + // Center the popup + var windowWidth = document.documentElement.clientWidth; + var windowHeight = document.documentElement.clientHeight; + var popupWidth = windowWidth * 0.9; + var popupHeight = windowHeight * 0.9; + var popupOffsetTop = windowHeight / 2 - popupHeight / 2; + var popupOffsetLeft = windowWidth / 2 - popupWidth / 2; + + var $gis_editor = $('#gis_editor'); + var $backgrouond = $('#popup_background'); + + $gis_editor.css({ 'top': popupOffsetTop, 'left': popupOffsetLeft, 'width': popupWidth, 'height': popupHeight }); + $backgrouond.css({ 'opacity' : '0.7' }); + + $gis_editor.append( + '
      ' + + '' + + '
      ' + ); + + // Make it appear + $backgrouond.fadeIn('fast'); + $gis_editor.fadeIn('fast'); +} + +/** + * Prepare and insert the GIS data in Well Known Text format + * to the input field. + */ +function insertDataAndClose () { + var $form = $('form#gis_data_editor_form'); + var input_name = $form.find('input[name=\'input_name\']').val(); + + var argsep = PMA_commonParams.get('arg_separator'); + $.post('gis_data_editor.php', $form.serialize() + argsep + 'generate=true' + argsep + 'ajax_request=true', function (data) { + if (typeof data !== 'undefined' && data.success === true) { + $('input[name=\'' + input_name + '\']').val(data.result); + } else { + PMA_ajaxShowMessage(data.error, false); + } + }, 'json'); + closeGISEditor(); +} + +/** + * Unbind all event handlers before tearing down a page + */ +AJAX.registerTeardown('gis_data_editor.js', function () { + $(document).off('click', '#gis_editor input[name=\'gis_data[save]\']'); + $(document).off('submit', '#gis_editor'); + $(document).off('change', '#gis_editor input[type=\'text\']'); + $(document).off('change', '#gis_editor select.gis_type'); + $(document).off('click', '#gis_editor a.close_gis_editor, #gis_editor a.cancel_gis_editor'); + $(document).off('click', '#gis_editor a.addJs.addPoint'); + $(document).off('click', '#gis_editor a.addLine.addJs'); + $(document).off('click', '#gis_editor a.addJs.addPolygon'); + $(document).off('click', '#gis_editor a.addJs.addGeom'); +}); + +AJAX.registerOnload('gis_data_editor.js', function () { + /** + * Prepares and insert the GIS data to the input field on clicking 'copy'. + */ + $(document).on('click', '#gis_editor input[name=\'gis_data[save]\']', function (event) { + event.preventDefault(); + insertDataAndClose(); + }); + + /** + * Prepares and insert the GIS data to the input field on pressing 'enter'. + */ + $(document).on('submit', '#gis_editor', function (event) { + event.preventDefault(); + insertDataAndClose(); + }); + + /** + * Trigger asynchronous calls on data change and update the output. + */ + $(document).on('change', '#gis_editor input[type=\'text\']', function () { + var $form = $('form#gis_data_editor_form'); + var argsep = PMA_commonParams.get('arg_separator'); + $.post('gis_data_editor.php', $form.serialize() + argsep + 'generate=true' + argsep + 'ajax_request=true', function (data) { + if (typeof data !== 'undefined' && data.success === true) { + $('#gis_data_textarea').val(data.result); + $('#placeholder').empty().removeClass('hasSVG').html(data.visualization); + $('#openlayersmap').empty(); + /* TODO: the gis_data_editor should rather return JSON than JS code to eval */ + eval(data.openLayers); + initGISEditorVisualization(); + } else { + PMA_ajaxShowMessage(data.error, false); + } + }, 'json'); + }); + + /** + * Update the form on change of the GIS type. + */ + $(document).on('change', '#gis_editor select.gis_type', function (event) { + var $gis_editor = $('#gis_editor'); + var $form = $('form#gis_data_editor_form'); + + var argsep = PMA_commonParams.get('arg_separator'); + $.post('gis_data_editor.php', $form.serialize() + argsep + 'get_gis_editor=true' + argsep + 'ajax_request=true', function (data) { + if (typeof data !== 'undefined' && data.success === true) { + $gis_editor.html(data.gis_editor); + initGISEditorVisualization(); + prepareJSVersion(); + } else { + PMA_ajaxShowMessage(data.error, false); + } + }, 'json'); + }); + + /** + * Handles closing of the GIS data editor. + */ + $(document).on('click', '#gis_editor a.close_gis_editor, #gis_editor a.cancel_gis_editor', function () { + closeGISEditor(); + }); + + /** + * Handles adding data points + */ + $(document).on('click', '#gis_editor a.addJs.addPoint', function () { + var $a = $(this); + var name = $a.attr('name'); + // Eg. name = gis_data[0][MULTIPOINT][add_point] => prefix = gis_data[0][MULTIPOINT] + var prefix = name.substr(0, name.length - 11); + // Find the number of points + var $noOfPointsInput = $('input[name=\'' + prefix + '[no_of_points]' + '\']'); + var noOfPoints = parseInt($noOfPointsInput.val(), 10); + // Add the new data point + var html = addDataPoint(noOfPoints, prefix); + $a.before(html); + $noOfPointsInput.val(noOfPoints + 1); + }); + + /** + * Handles adding linestrings and inner rings + */ + $(document).on('click', '#gis_editor a.addLine.addJs', function () { + var $a = $(this); + var name = $a.attr('name'); + + // Eg. name = gis_data[0][MULTILINESTRING][add_line] => prefix = gis_data[0][MULTILINESTRING] + var prefix = name.substr(0, name.length - 10); + var type = prefix.slice(prefix.lastIndexOf('[') + 1, prefix.lastIndexOf(']')); + + // Find the number of lines + var $noOfLinesInput = $('input[name=\'' + prefix + '[no_of_lines]' + '\']'); + var noOfLines = parseInt($noOfLinesInput.val(), 10); + + // Add the new linesting of inner ring based on the type + var html = '
      '; + var noOfPoints; + if (type === 'MULTILINESTRING') { + html += PMA_messages.strLineString + ' ' + (noOfLines + 1) + ':'; + noOfPoints = 2; + } else { + html += PMA_messages.strInnerRing + ' ' + noOfLines + ':'; + noOfPoints = 4; + } + html += ''; + for (var i = 0; i < noOfPoints; i++) { + html += addDataPoint(i, (prefix + '[' + noOfLines + ']')); + } + html += '
      + ' + + PMA_messages.strAddPoint + '
      '; + + $a.before(html); + $noOfLinesInput.val(noOfLines + 1); + }); + + /** + * Handles adding polygons + */ + $(document).on('click', '#gis_editor a.addJs.addPolygon', function () { + var $a = $(this); + var name = $a.attr('name'); + // Eg. name = gis_data[0][MULTIPOLYGON][add_polygon] => prefix = gis_data[0][MULTIPOLYGON] + var prefix = name.substr(0, name.length - 13); + // Find the number of polygons + var $noOfPolygonsInput = $('input[name=\'' + prefix + '[no_of_polygons]' + '\']'); + var noOfPolygons = parseInt($noOfPolygonsInput.val(), 10); + + // Add the new polygon + var html = PMA_messages.strPolygon + ' ' + (noOfPolygons + 1) + ':
      '; + html += '' + + '
      ' + PMA_messages.strOuterRing + ':' + + ''; + for (var i = 0; i < 4; i++) { + html += addDataPoint(i, (prefix + '[' + noOfPolygons + '][0]')); + } + html += '+ ' + + PMA_messages.strAddPoint + '
      ' + + '+ ' + + PMA_messages.strAddInnerRing + '

      '; + + $a.before(html); + $noOfPolygonsInput.val(noOfPolygons + 1); + }); + + /** + * Handles adding geoms + */ + $(document).on('click', '#gis_editor a.addJs.addGeom', function () { + var $a = $(this); + var prefix = 'gis_data[GEOMETRYCOLLECTION]'; + // Find the number of geoms + var $noOfGeomsInput = $('input[name=\'' + prefix + '[geom_count]' + '\']'); + var noOfGeoms = parseInt($noOfGeomsInput.val(), 10); + + var html1 = PMA_messages.strGeometry + ' ' + (noOfGeoms + 1) + ':
      '; + var $geomType = $('select[name=\'gis_data[' + (noOfGeoms - 1) + '][gis_type]\']').clone(); + $geomType.attr('name', 'gis_data[' + noOfGeoms + '][gis_type]').val('POINT'); + var html2 = '
      ' + PMA_messages.strPoint + ' :' + + '' + + '' + + '' + + '' + + '

      '; + + $a.before(html1); + $geomType.insertBefore($a); + $a.before(html2); + $noOfGeomsInput.val(noOfGeoms + 1); + }); +}); diff --git a/php/apps/phpmyadmin49/js/import.js b/php/apps/phpmyadmin49/js/import.js new file mode 100644 index 00000000..7bd91a2e --- /dev/null +++ b/php/apps/phpmyadmin49/js/import.js @@ -0,0 +1,155 @@ +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * Functions used in the import tab + * + */ + + +/** + * Toggles the hiding and showing of each plugin's options + * according to the currently selected plugin from the dropdown list + */ +function changePluginOpts () { + $('#format_specific_opts').find('div.format_specific_options').each(function () { + $(this).hide(); + }); + var selected_plugin_name = $('#plugins').find('option:selected').val(); + $('#' + selected_plugin_name + '_options').fadeIn('slow'); + if (selected_plugin_name === 'csv') { + $('#import_notification').text(PMA_messages.strImportCSV); + } else { + $('#import_notification').text(''); + } +} + +/** + * Toggles the hiding and showing of each plugin's options and sets the selected value + * in the plugin dropdown list according to the format of the selected file + */ +function matchFile (fname) { + var fname_array = fname.toLowerCase().split('.'); + var len = fname_array.length; + if (len !== 0) { + var extension = fname_array[len - 1]; + if (extension === 'gz' || extension === 'bz2' || extension === 'zip') { + len--; + } + // Only toggle if the format of the file can be imported + if ($('select[name=\'format\'] option').filterByValue(fname_array[len - 1]).length === 1) { + $('select[name=\'format\'] option').filterByValue(fname_array[len - 1]).prop('selected', true); + changePluginOpts(); + } + } +} + +/** + * Unbind all event handlers before tearing down a page + */ +AJAX.registerTeardown('import.js', function () { + $('#plugins').off('change'); + $('#input_import_file').off('change'); + $('#select_local_import_file').off('change'); + $('#input_import_file').off('change').off('focus'); + $('#select_local_import_file').off('focus'); + $('#text_csv_enclosed').add('#text_csv_escaped').off('keyup'); +}); + +AJAX.registerOnload('import.js', function () { + // import_file_form validation. + $(document).on('submit', '#import_file_form', function (event) { + var radioLocalImport = $('#radio_local_import_file'); + var radioImport = $('#radio_import_file'); + var fileMsg = '
      ' + PMA_messages.strImportDialogMessage + '
      '; + + if (radioLocalImport.length !== 0) { + // remote upload. + + if (radioImport.is(':checked') && $('#input_import_file').val() === '') { + $('#input_import_file').focus(); + PMA_ajaxShowMessage(fileMsg, false); + return false; + } + + if (radioLocalImport.is(':checked')) { + if ($('#select_local_import_file').length === 0) { + PMA_ajaxShowMessage('
      ' + PMA_messages.strNoImportFile + '
      ', false); + return false; + } + + if ($('#select_local_import_file').val() === '') { + $('#select_local_import_file').focus(); + PMA_ajaxShowMessage(fileMsg, false); + return false; + } + } + } else { + // local upload. + if ($('#input_import_file').val() === '') { + $('#input_import_file').focus(); + PMA_ajaxShowMessage(fileMsg, false); + return false; + } + } + + // show progress bar. + $('#upload_form_status').css('display', 'inline'); + $('#upload_form_status_info').css('display', 'inline'); + }); + + // Initially display the options for the selected plugin + changePluginOpts(); + + // Whenever the selected plugin changes, change the options displayed + $('#plugins').change(function () { + changePluginOpts(); + }); + + $('#input_import_file').change(function () { + matchFile($(this).val()); + }); + + $('#select_local_import_file').change(function () { + matchFile($(this).val()); + }); + + /* + * When the "Browse the server" form is clicked or the "Select from the web server upload directory" + * form is clicked, the radio button beside it becomes selected and the other form becomes disabled. + */ + $('#input_import_file').on('focus change', function () { + $('#radio_import_file').prop('checked', true); + $('#radio_local_import_file').prop('checked', false); + }); + $('#select_local_import_file').focus(function () { + $('#radio_local_import_file').prop('checked', true); + $('#radio_import_file').prop('checked', false); + }); + + /** + * Set up the interface for Javascript-enabled browsers since the default is for + * Javascript-disabled browsers + */ + $('#scroll_to_options_msg').hide(); + $('#format_specific_opts').find('div.format_specific_options') + .css({ + 'border': 0, + 'margin': 0, + 'padding': 0 + }) + .find('h3') + .remove(); + // $("form[name=import] *").unwrap(); + + /** + * for input element text_csv_enclosed and text_csv_escaped allow just one character to enter. + * as mysql allows just one character for these fields, + * if first character is escape then allow two including escape character. + */ + $('#text_csv_enclosed').add('#text_csv_escaped').on('keyup', function () { + if ($(this).val().length === 2 && $(this).val().charAt(0) !== '\\') { + $(this).val($(this).val().substring(0, 1)); + return false; + } + return true; + }); +}); diff --git a/php/apps/phpmyadmin49/js/indexes.js b/php/apps/phpmyadmin49/js/indexes.js new file mode 100644 index 00000000..65baec1c --- /dev/null +++ b/php/apps/phpmyadmin49/js/indexes.js @@ -0,0 +1,761 @@ +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * @fileoverview function used for index manipulation pages + * @name Table Structure + * + * @requires jQuery + * @requires jQueryUI + * @required js/functions.js + */ + +/** + * Returns the array of indexes based on the index choice + * + * @param index_choice index choice + */ +function PMA_getIndexArray (index_choice) { + var source_array = null; + + switch (index_choice.toLowerCase()) { + case 'primary': + source_array = primary_indexes; + break; + case 'unique': + source_array = unique_indexes; + break; + case 'index': + source_array = indexes; + break; + case 'fulltext': + source_array = fulltext_indexes; + break; + case 'spatial': + source_array = spatial_indexes; + break; + default: + return null; + } + return source_array; +} + +/** + * Hides/shows the inputs and submits appropriately depending + * on whether the index type chosen is 'SPATIAL' or not. + */ +function checkIndexType () { + /** + * @var Object Dropdown to select the index choice. + */ + var $select_index_choice = $('#select_index_choice'); + /** + * @var Object Dropdown to select the index type. + */ + var $select_index_type = $('#select_index_type'); + /** + * @var Object Table header for the size column. + */ + var $size_header = $('#index_columns').find('thead tr th:nth-child(2)'); + /** + * @var Object Inputs to specify the columns for the index. + */ + var $column_inputs = $('select[name="index[columns][names][]"]'); + /** + * @var Object Inputs to specify sizes for columns of the index. + */ + var $size_inputs = $('input[name="index[columns][sub_parts][]"]'); + /** + * @var Object Footer containg the controllers to add more columns + */ + var $add_more = $('#index_frm').find('.add_more'); + + if ($select_index_choice.val() === 'SPATIAL') { + // Disable and hide the size column + $size_header.hide(); + $size_inputs.each(function () { + $(this) + .prop('disabled', true) + .parent('td').hide(); + }); + + // Disable and hide the columns of the index other than the first one + var initial = true; + $column_inputs.each(function () { + $column_input = $(this); + if (! initial) { + $column_input + .prop('disabled', true) + .parent('td').hide(); + } else { + initial = false; + } + }); + + // Hide controllers to add more columns + $add_more.hide(); + } else { + // Enable and show the size column + $size_header.show(); + $size_inputs.each(function () { + $(this) + .prop('disabled', false) + .parent('td').show(); + }); + + // Enable and show the columns of the index + $column_inputs.each(function () { + $(this) + .prop('disabled', false) + .parent('td').show(); + }); + + // Show controllers to add more columns + $add_more.show(); + } + + if ($select_index_choice.val() === 'SPATIAL' || + $select_index_choice.val() === 'FULLTEXT') { + $select_index_type.val('').prop('disabled', true); + } else { + $select_index_type.prop('disabled', false); + } +} + +/** + * Sets current index information into form parameters. + * + * @param array source_array Array containing index columns + * @param string index_choice Choice of index + * + * @return void + */ +function PMA_setIndexFormParameters (source_array, index_choice) { + if (index_choice === 'index') { + $('input[name="indexes"]').val(JSON.stringify(source_array)); + } else { + $('input[name="' + index_choice + '_indexes"]').val(JSON.stringify(source_array)); + } +} + +/** + * Removes a column from an Index. + * + * @param string col_index Index of column in form + * + * @return void + */ +function PMA_removeColumnFromIndex (col_index) { + // Get previous index details. + var previous_index = $('select[name="field_key[' + col_index + ']"]') + .attr('data-index'); + if (previous_index.length) { + previous_index = previous_index.split(','); + var source_array = PMA_getIndexArray(previous_index[0]); + if (source_array === null) { + return; + } + + // Remove column from index array. + var source_length = source_array[previous_index[1]].columns.length; + for (var i = 0; i < source_length; i++) { + if (source_array[previous_index[1]].columns[i].col_index === col_index) { + source_array[previous_index[1]].columns.splice(i, 1); + } + } + + // Remove index completely if no columns left. + if (source_array[previous_index[1]].columns.length === 0) { + source_array.splice(previous_index[1], 1); + } + + // Update current index details. + $('select[name="field_key[' + col_index + ']"]').attr('data-index', ''); + // Update form index parameters. + PMA_setIndexFormParameters(source_array, previous_index[0].toLowerCase()); + } +} + +/** + * Adds a column to an Index. + * + * @param array source_array Array holding corresponding indexes + * @param string array_index Index of an INDEX in array + * @param string index_choice Choice of Index + * @param string col_index Index of column on form + * + * @return void + */ +function PMA_addColumnToIndex (source_array, array_index, index_choice, col_index) { + if (col_index >= 0) { + // Remove column from other indexes (if any). + PMA_removeColumnFromIndex(col_index); + } + var index_name = $('input[name="index[Key_name]"]').val(); + var index_comment = $('input[name="index[Index_comment]"]').val(); + var key_block_size = $('input[name="index[Key_block_size]"]').val(); + var parser = $('input[name="index[Parser]"]').val(); + var index_type = $('select[name="index[Index_type]"]').val(); + var columns = []; + $('#index_columns').find('tbody').find('tr').each(function () { + // Get columns in particular order. + var col_index = $(this).find('select[name="index[columns][names][]"]').val(); + var size = $(this).find('input[name="index[columns][sub_parts][]"]').val(); + columns.push({ + 'col_index': col_index, + 'size': size + }); + }); + + // Update or create an index. + source_array[array_index] = { + 'Key_name': index_name, + 'Index_comment': index_comment, + 'Index_choice': index_choice.toUpperCase(), + 'Key_block_size': key_block_size, + 'Parser': parser, + 'Index_type': index_type, + 'columns': columns + }; + + // Display index name (or column list) + var displayName = index_name; + if (displayName === '') { + var columnNames = []; + $.each(columns, function () { + columnNames.push($('input[name="field_name[' + this.col_index + ']"]').val()); + }); + displayName = '[' + columnNames.join(', ') + ']'; + } + $.each(columns, function () { + var id = 'index_name_' + this.col_index + '_8'; + var $name = $('#' + id); + if ($name.length === 0) { + $name = $(''); + $name.insertAfter($('select[name="field_key[' + this.col_index + ']"]')); + } + var $text = $('').text(displayName); + $name.html($text); + }); + + if (col_index >= 0) { + // Update index details on form. + $('select[name="field_key[' + col_index + ']"]') + .attr('data-index', index_choice + ',' + array_index); + } + PMA_setIndexFormParameters(source_array, index_choice.toLowerCase()); +} + +/** + * Get choices list for a column to create a composite index with. + * + * @param string index_choice Choice of index + * @param array source_array Array hodling columns for particular index + * + * @return jQuery Object + */ +function PMA_getCompositeIndexList (source_array, col_index) { + // Remove any previous list. + if ($('#composite_index_list').length) { + $('#composite_index_list').remove(); + } + + // Html list. + var $composite_index_list = $( + '
        ' + + '
        ' + PMA_messages.strCompositeWith + '
        ' + + '
      ' + ); + + // Add each column to list available for composite index. + var source_length = source_array.length; + var already_present = false; + for (var i = 0; i < source_length; i++) { + var sub_array_len = source_array[i].columns.length; + var column_names = []; + for (var j = 0; j < sub_array_len; j++) { + column_names.push( + $('input[name="field_name[' + source_array[i].columns[j].col_index + ']"]').val() + ); + + if (col_index === source_array[i].columns[j].col_index) { + already_present = true; + } + } + + $composite_index_list.append( + '
    • ' + + '' + + '
    • ' + ); + } + + return $composite_index_list; +} + +/** + * Shows 'Add Index' dialog. + * + * @param array source_array Array holding particluar index + * @param string array_index Index of an INDEX in array + * @param array target_columns Columns for an INDEX + * @param string col_index Index of column on form + * @param object index Index detail object + * + * @return void + */ +function PMA_showAddIndexDialog (source_array, array_index, target_columns, col_index, index) { + // Prepare post-data. + var $table = $('input[name="table"]'); + var table = $table.length > 0 ? $table.val() : ''; + var post_data = { + server: PMA_commonParams.get('server'), + db: $('input[name="db"]').val(), + table: table, + ajax_request: 1, + create_edit_table: 1, + index: index + }; + + var columns = {}; + for (var i = 0; i < target_columns.length; i++) { + var column_name = $('input[name="field_name[' + target_columns[i] + ']"]').val(); + var column_type = $('select[name="field_type[' + target_columns[i] + ']"]').val().toLowerCase(); + columns[column_name] = [column_type, target_columns[i]]; + } + post_data.columns = JSON.stringify(columns); + + var button_options = {}; + button_options[PMA_messages.strGo] = function () { + var is_missing_value = false; + $('select[name="index[columns][names][]"]').each(function () { + if ($(this).val() === '') { + is_missing_value = true; + } + }); + + if (! is_missing_value) { + PMA_addColumnToIndex( + source_array, + array_index, + index.Index_choice, + col_index + ); + } else { + PMA_ajaxShowMessage( + '
      ' + PMA_messages.strMissingColumn + + '
      ', false + ); + + return false; + } + + $(this).dialog('close'); + }; + button_options[PMA_messages.strCancel] = function () { + if (col_index >= 0) { + // Handle state on 'Cancel'. + var $select_list = $('select[name="field_key[' + col_index + ']"]'); + if (! $select_list.attr('data-index').length) { + $select_list.find('option[value*="none"]').attr('selected', 'selected'); + } else { + var previous_index = $select_list.attr('data-index').split(','); + $select_list.find('option[value*="' + previous_index[0].toLowerCase() + '"]') + .attr('selected', 'selected'); + } + } + $(this).dialog('close'); + }; + var $msgbox = PMA_ajaxShowMessage(); + $.post('tbl_indexes.php', post_data, function (data) { + if (data.success === false) { + // in the case of an error, show the error message returned. + PMA_ajaxShowMessage(data.error, false); + } else { + PMA_ajaxRemoveMessage($msgbox); + // Show dialog if the request was successful + var $div = $('
      '); + $div + .append(data.message) + .dialog({ + title: PMA_messages.strAddIndex, + width: 450, + minHeight: 250, + open: function () { + checkIndexName('index_frm'); + PMA_showHints($div); + PMA_init_slider(); + $('#index_columns').find('td').each(function () { + $(this).css('width', $(this).width() + 'px'); + }); + $('#index_columns').find('tbody').sortable({ + axis: 'y', + containment: $('#index_columns').find('tbody'), + tolerance: 'pointer' + }); + // We dont need the slider at this moment. + $(this).find('fieldset.tblFooters').remove(); + }, + modal: true, + buttons: button_options, + close: function () { + $(this).remove(); + } + }); + } + }); +} + +/** + * Creates a advanced index type selection dialog. + * + * @param array source_array Array holding a particular type of indexes + * @param string index_choice Choice of index + * @param string col_index Index of new column on form + * + * @return void + */ +function PMA_indexTypeSelectionDialog (source_array, index_choice, col_index) { + var $single_column_radio = $('' + + ''); + var $composite_index_radio = $('' + + ''); + var $dialog_content = $('
      '); + $dialog_content.append('' + index_choice.toUpperCase() + ''); + + + // For UNIQUE/INDEX type, show choice for single-column and composite index. + $dialog_content.append($single_column_radio); + $dialog_content.append($composite_index_radio); + + var button_options = {}; + // 'OK' operation. + button_options[PMA_messages.strGo] = function () { + if ($('#single_column').is(':checked')) { + var index = { + 'Key_name': (index_choice === 'primary' ? 'PRIMARY' : ''), + 'Index_choice': index_choice.toUpperCase() + }; + PMA_showAddIndexDialog(source_array, (source_array.length), [col_index], col_index, index); + } + + if ($('#composite_index').is(':checked')) { + if ($('input[name="composite_with"]').length !== 0 && $('input[name="composite_with"]:checked').length === 0 + ) { + PMA_ajaxShowMessage( + '
      ' + + PMA_messages.strFormEmpty + + '
      ', + false + ); + return false; + } + + var array_index = $('input[name="composite_with"]:checked').val(); + var source_length = source_array[array_index].columns.length; + var target_columns = []; + for (var i = 0; i < source_length; i++) { + target_columns.push(source_array[array_index].columns[i].col_index); + } + target_columns.push(col_index); + + PMA_showAddIndexDialog(source_array, array_index, target_columns, col_index, + source_array[array_index]); + } + + $(this).remove(); + }; + button_options[PMA_messages.strCancel] = function () { + // Handle state on 'Cancel'. + var $select_list = $('select[name="field_key[' + col_index + ']"]'); + if (! $select_list.attr('data-index').length) { + $select_list.find('option[value*="none"]').attr('selected', 'selected'); + } else { + var previous_index = $select_list.attr('data-index').split(','); + $select_list.find('option[value*="' + previous_index[0].toLowerCase() + '"]') + .attr('selected', 'selected'); + } + $(this).remove(); + }; + var $dialog = $('
      ').append($dialog_content).dialog({ + minWidth: 525, + minHeight: 200, + modal: true, + title: PMA_messages.strAddIndex, + resizable: false, + buttons: button_options, + open: function () { + $('#composite_index').on('change', function () { + if ($(this).is(':checked')) { + $dialog_content.append(PMA_getCompositeIndexList(source_array, col_index)); + } + }); + $('#single_column').on('change', function () { + if ($(this).is(':checked')) { + if ($('#composite_index_list').length) { + $('#composite_index_list').remove(); + } + } + }); + }, + close: function () { + $('#composite_index').off('change'); + $('#single_column').off('change'); + $(this).remove(); + } + }); +} + +/** + * Unbind all event handlers before tearing down a page + */ +AJAX.registerTeardown('indexes.js', function () { + $(document).off('click', '#save_index_frm'); + $(document).off('click', '#preview_index_frm'); + $(document).off('change', '#select_index_choice'); + $(document).off('click', 'a.drop_primary_key_index_anchor.ajax'); + $(document).off('click', '#table_index tbody tr td.edit_index.ajax, #index_div .add_index.ajax'); + $(document).off('click', '#index_frm input[type=submit]'); + $('body').off('change', 'select[name*="field_key"]'); + $(document).off('click', '.show_index_dialog'); +}); + +/** + * @description

      Ajax scripts for table index page

      + * + * Actions ajaxified here: + *
        + *
      • Showing/hiding inputs depending on the index type chosen
      • + *
      • create/edit/drop indexes
      • + *
      + */ +AJAX.registerOnload('indexes.js', function () { + // Re-initialize variables. + primary_indexes = []; + unique_indexes = []; + indexes = []; + fulltext_indexes = []; + spatial_indexes = []; + + // for table creation form + var $engine_selector = $('.create_table_form select[name=tbl_storage_engine]'); + if ($engine_selector.length) { + PMA_hideShowConnection($engine_selector); + } + + var $form = $('#index_frm'); + if ($form.length > 0) { + showIndexEditDialog($form); + } + + $(document).on('click', '#save_index_frm', function (event) { + event.preventDefault(); + var $form = $('#index_frm'); + var argsep = PMA_commonParams.get('arg_separator'); + var submitData = $form.serialize() + argsep + 'do_save_data=1' + argsep + 'ajax_request=true' + argsep + 'ajax_page_request=true'; + var $msgbox = PMA_ajaxShowMessage(PMA_messages.strProcessingRequest); + AJAX.source = $form; + $.post($form.attr('action'), submitData, AJAX.responseHandler); + }); + + $(document).on('click', '#preview_index_frm', function (event) { + event.preventDefault(); + PMA_previewSQL($('#index_frm')); + }); + + $(document).on('change', '#select_index_choice', function (event) { + event.preventDefault(); + checkIndexType(); + checkIndexName('index_frm'); + }); + + /** + * Ajax Event handler for 'Drop Index' + */ + $(document).on('click', 'a.drop_primary_key_index_anchor.ajax', function (event) { + event.preventDefault(); + var $anchor = $(this); + /** + * @var $curr_row Object containing reference to the current field's row + */ + var $curr_row = $anchor.parents('tr'); + /** @var Number of columns in the key */ + var rows = $anchor.parents('td').attr('rowspan') || 1; + /** @var Rows that should be hidden */ + var $rows_to_hide = $curr_row; + for (var i = 1, $last_row = $curr_row.next(); i < rows; i++, $last_row = $last_row.next()) { + $rows_to_hide = $rows_to_hide.add($last_row); + } + + var question = escapeHtml( + $curr_row.children('td') + .children('.drop_primary_key_index_msg') + .val() + ); + + PMA_confirmPreviewSQL(question, $anchor.attr('href'), function (url) { + var $msg = PMA_ajaxShowMessage(PMA_messages.strDroppingPrimaryKeyIndex, false); + var params = getJSConfirmCommonParam(this, $anchor.getPostData()); + $.post(url, params, function (data) { + if (typeof data !== 'undefined' && data.success === true) { + PMA_ajaxRemoveMessage($msg); + var $table_ref = $rows_to_hide.closest('table'); + if ($rows_to_hide.length === $table_ref.find('tbody > tr').length) { + // We are about to remove all rows from the table + $table_ref.hide('medium', function () { + $('div.no_indexes_defined').show('medium'); + $rows_to_hide.remove(); + }); + $table_ref.siblings('div.notice').hide('medium'); + } else { + // We are removing some of the rows only + $rows_to_hide.hide('medium', function () { + $(this).remove(); + }); + } + if ($('.result_query').length) { + $('.result_query').remove(); + } + if (data.sql_query) { + $('
      ') + .html(data.sql_query) + .prependTo('#structure_content'); + PMA_highlightSQL($('#page_content')); + } + PMA_commonActions.refreshMain(false, function () { + $('a.ajax[href^=#indexes]').click(); + }); + PMA_reloadNavigation(); + } else { + PMA_ajaxShowMessage(PMA_messages.strErrorProcessingRequest + ' : ' + data.error, false); + } + }); // end $.post() + }); // end $.PMA_confirm() + }); // end Drop Primary Key/Index + + /** + *Ajax event handler for index edit + **/ + $(document).on('click', '#table_index tbody tr td.edit_index.ajax, #index_div .add_index.ajax', function (event) { + event.preventDefault(); + var url; + var title; + if ($(this).find('a').length === 0) { + // Add index + var valid = checkFormElementInRange( + $(this).closest('form')[0], + 'added_fields', + 'Column count has to be larger than zero.' + ); + if (! valid) { + return; + } + url = $(this).closest('form').serialize(); + title = PMA_messages.strAddIndex; + } else { + // Edit index + url = $(this).find('a').getPostData(); + title = PMA_messages.strEditIndex; + } + url += PMA_commonParams.get('arg_separator') + 'ajax_request=true'; + indexEditorDialog(url, title, function () { + // refresh the page using ajax + PMA_commonActions.refreshMain(false, function () { + $('a.ajax[href^=#indexes]').click(); + }); + }); + }); + + /** + * Ajax event handler for advanced index creation during table creation + * and column addition. + */ + $('body').on('change', 'select[name*="field_key"]', function () { + // Index of column on Table edit and create page. + var col_index = /\d+/.exec($(this).attr('name')); + col_index = col_index[0]; + // Choice of selected index. + var index_choice = /[a-z]+/.exec($(this).val()); + index_choice = index_choice[0]; + // Array containing corresponding indexes. + var source_array = null; + + if (index_choice === 'none') { + PMA_removeColumnFromIndex(col_index); + var id = 'index_name_' + '0' + '_8'; + var $name = $('#' + id); + if ($name.length === 0) { + $name = $(''); + $name.insertAfter($('select[name="field_key[' + '0' + ']"]')); + } + $name.html(''); + return false; + } + + // Select a source array. + source_array = PMA_getIndexArray(index_choice); + if (source_array === null) { + return; + } + + if (source_array.length === 0) { + var index = { + 'Key_name': (index_choice === 'primary' ? 'PRIMARY' : ''), + 'Index_choice': index_choice.toUpperCase() + }; + PMA_showAddIndexDialog(source_array, 0, [col_index], col_index, index); + } else { + if (index_choice === 'primary') { + var array_index = 0; + var source_length = source_array[array_index].columns.length; + var target_columns = []; + for (var i = 0; i < source_length; i++) { + target_columns.push(source_array[array_index].columns[i].col_index); + } + target_columns.push(col_index); + + PMA_showAddIndexDialog(source_array, array_index, target_columns, col_index, + source_array[array_index]); + } else { + // If there are multiple columns selected for an index, show advanced dialog. + PMA_indexTypeSelectionDialog(source_array, index_choice, col_index); + } + } + }); + + $(document).on('click', '.show_index_dialog', function (e) { + e.preventDefault(); + + // Get index details. + var previous_index = $(this).prev('select') + .attr('data-index') + .split(','); + + var index_choice = previous_index[0]; + var array_index = previous_index[1]; + + var source_array = PMA_getIndexArray(index_choice); + if (source_array !== null) { + var source_length = source_array[array_index].columns.length; + + var target_columns = []; + for (var i = 0; i < source_length; i++) { + target_columns.push(source_array[array_index].columns[i].col_index); + } + + PMA_showAddIndexDialog(source_array, array_index, target_columns, -1, source_array[array_index]); + } + }); + + $('#index_frm').on('submit', function () { + if (typeof(this.elements['index[Key_name]'].disabled) !== 'undefined') { + this.elements['index[Key_name]'].disabled = false; + } + }); +}); diff --git a/php/apps/phpmyadmin49/js/jqplot/plugins/jqplot.byteFormatter.js b/php/apps/phpmyadmin49/js/jqplot/plugins/jqplot.byteFormatter.js new file mode 100644 index 00000000..610692d3 --- /dev/null +++ b/php/apps/phpmyadmin49/js/jqplot/plugins/jqplot.byteFormatter.js @@ -0,0 +1,46 @@ +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * jqplot formatter for byte values + * + * @package phpMyAdmin + */ +(function ($) { + 'use strict'; + var formatByte = function (val, index) { + var units = [ + PMA_messages.strB, + PMA_messages.strKiB, + PMA_messages.strMiB, + PMA_messages.strGiB, + PMA_messages.strTiB, + PMA_messages.strPiB, + PMA_messages.strEiB + ]; + while (val >= 1024 && index <= 6) { + val /= 1024; + index++; + } + var format = '%.1f'; + if (Math.floor(val) === val) { + format = '%.0f'; + } + return $.jqplot.sprintf( + format + ' ' + units[index], val + ); + }; + /** + * The index indicates what unit the incoming data will be in. + * 0 for bytes, 1 for kilobytes and so on... + */ + $.jqplot.byteFormatter = function (index) { + index = index || 0; + return function (format, val) { + if (typeof val === 'number') { + val = parseFloat(val) || 0; + return formatByte(val, index); + } else { + return String(val); + } + }; + }; +}(jQuery)); diff --git a/php/apps/phpmyadmin49/js/keyhandler.js b/php/apps/phpmyadmin49/js/keyhandler.js new file mode 100644 index 00000000..a5e459fa --- /dev/null +++ b/php/apps/phpmyadmin49/js/keyhandler.js @@ -0,0 +1,140 @@ +/* vim: set expandtab sw=4 ts=4 sts=4: */ + +// global var that holds: 0- if ctrl key is not pressed 1- if ctrl key is pressed +var ctrlKeyHistory = 0; + +/** + * Allows moving around inputs/select by Ctrl+arrows + * + * @param object event data + */ +function onKeyDownArrowsHandler (e) { + e = e || window.event; + + var o = (e.srcElement || e.target); + if (!o) { + return; + } + if (o.tagName !== 'TEXTAREA' && o.tagName !== 'INPUT' && o.tagName !== 'SELECT') { + return; + } + if ((e.which !== 17) && (e.which !== 37) && (e.which !== 38) && (e.which !== 39) && (e.which !== 40)) { + return; + } + if (!o.id) { + return; + } + + if (e.type === 'keyup') { + if (e.which === 17) { + ctrlKeyHistory = 0; + } + return; + } else if (e.type === 'keydown') { + if (e.which === 17) { + ctrlKeyHistory = 1; + } + } + + if (ctrlKeyHistory !== 1) { + return; + } + + e.preventDefault(); + + var pos = o.id.split('_'); + if (pos[0] !== 'field' || typeof pos[2] === 'undefined') { + return; + } + + var x = pos[2]; + var y = pos[1]; + + switch (e.keyCode) { + case 38: + // up + y--; + break; + case 40: + // down + y++; + break; + case 37: + // left + x--; + break; + case 39: + // right + x++; + break; + default: + return; + } + + var is_firefox = navigator.userAgent.toLowerCase().indexOf('firefox/') > -1; + + var id = 'field_' + y + '_' + x; + + var nO = document.getElementById(id); + if (! nO) { + id = 'field_' + y + '_' + x + '_0'; + nO = document.getElementById(id); + } + + // skip non existent fields + if (! nO) { + return; + } + + // for firefox select tag + var lvalue = o.selectedIndex; + var nOvalue = nO.selectedIndex; + + nO.focus(); + + if (is_firefox) { + var ffcheck = 0; + var ffversion; + for (ffversion = 3 ; ffversion < 25 ; ffversion++) { + var is_firefox_v_24 = navigator.userAgent.toLowerCase().indexOf('firefox/' + ffversion) > -1; + if (is_firefox_v_24) { + ffcheck = 1; + break; + } + } + if (ffcheck === 1) { + if (e.which === 38 || e.which === 37) { + nOvalue++; + } else if (e.which === 40 || e.which === 39) { + nOvalue--; + } + nO.selectedIndex = nOvalue; + } else { + if (e.which === 38 || e.which === 37) { + lvalue++; + } else if (e.which === 40 || e.which === 39) { + lvalue--; + } + o.selectedIndex = lvalue; + } + } + + if (nO.tagName !== 'SELECT') { + nO.select(); + } + e.returnValue = false; +} + +AJAX.registerTeardown('keyhandler.js', function () { + $(document).off('keydown keyup', '#table_columns'); + $(document).off('keydown keyup', 'table.insertRowTable'); +}); + +AJAX.registerOnload('keyhandler.js', function () { + $(document).on('keydown keyup', '#table_columns', function (event) { + onKeyDownArrowsHandler(event.originalEvent); + }); + $(document).on('keydown keyup', 'table.insertRowTable', function (event) { + onKeyDownArrowsHandler(event.originalEvent); + }); +}); diff --git a/php/apps/phpmyadmin49/js/makegrid.js b/php/apps/phpmyadmin49/js/makegrid.js new file mode 100644 index 00000000..08157bcd --- /dev/null +++ b/php/apps/phpmyadmin49/js/makegrid.js @@ -0,0 +1,2301 @@ +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * Create advanced table (resize, reorder, and show/hide columns; and also grid editing). + * This function is designed mainly for table DOM generated from browsing a table in the database. + * For using this function in other table DOM, you may need to: + * - add "draggable" class in the table header , in order to make it resizable, sortable or hidable + * - have at least one non-"draggable" header in the table DOM for placing column visibility drop-down arrow + * - pass the value "false" for the parameter "enableGridEdit" + * - adjust other parameter value, to select which features that will be enabled + * + * @param t the table DOM element + * @param enableResize Optional, if false, column resizing feature will be disabled + * @param enableReorder Optional, if false, column reordering feature will be disabled + * @param enableVisib Optional, if false, show/hide column feature will be disabled + * @param enableGridEdit Optional, if false, grid editing feature will be disabled + */ +function PMA_makegrid (t, enableResize, enableReorder, enableVisib, enableGridEdit) { + var g = { + /** ********* + * Constant + ***********/ + minColWidth: 15, + + + /** ********* + * Variables, assigned with default value, changed later + ***********/ + actionSpan: 5, // number of colspan in Actions header in a table + tableCreateTime: null, // table creation time, used for saving column order and visibility to server, only available in "Browse tab" + + // Column reordering variables + colOrder: [], // array of column order + + // Column visibility variables + colVisib: [], // array of column visibility + showAllColText: '', // string, text for "show all" button under column visibility list + visibleHeadersCount: 0, // number of visible data headers + + // Table hint variables + reorderHint: '', // string, hint for column reordering + sortHint: '', // string, hint for column sorting + markHint: '', // string, hint for column marking + copyHint: '', // string, hint for copy column name + showReorderHint: false, + showSortHint: false, + showMarkHint: false, + + // Grid editing + isCellEditActive: false, // true if current focus is in edit cell + isEditCellTextEditable: false, // true if current edit cell is editable in the text input box (not textarea) + currentEditCell: null, // reference to that currently being edited + cellEditHint: '', // hint shown when doing grid edit + gotoLinkText: '', // "Go to link" text + wasEditedCellNull: false, // true if last value of the edited cell was NULL + maxTruncatedLen: 0, // number of characters that can be displayed in a cell + saveCellsAtOnce: false, // $cfg[saveCellsAtOnce] + isCellEdited: false, // true if at least one cell has been edited + saveCellWarning: '', // string, warning text when user want to leave a page with unsaved edited data + lastXHR : null, // last XHR object used in AJAX request + isSaving: false, // true when currently saving edited data, used to handle double posting caused by pressing ENTER in grid edit text box in Chrome browser + alertNonUnique: '', // string, alert shown when saving edited nonunique table + + // Common hidden inputs + token: null, + server: null, + db: null, + table: null, + + + /** ********** + * Functions + ************/ + + /** + * Start to resize column. Called when clicking on column separator. + * + * @param e event + * @param obj dragged div object + */ + dragStartRsz: function (e, obj) { + var n = $(g.cRsz).find('div').index(obj); // get the index of separator (i.e., column index) + $(obj).addClass('colborder_active'); + g.colRsz = { + x0: e.pageX, + n: n, + obj: obj, + objLeft: $(obj).position().left, + objWidth: $(g.t).find('th.draggable:visible:eq(' + n + ') span').outerWidth() + }; + $(document.body).css('cursor', 'col-resize').noSelect(); + if (g.isCellEditActive) { + g.hideEditCell(); + } + }, + + /** + * Start to reorder column. Called when clicking on table header. + * + * @param e event + * @param obj table header object + */ + dragStartReorder: function (e, obj) { + // prepare the cCpy (column copy) and cPointer (column pointer) from the dragged column + $(g.cCpy).text($(obj).text()); + var objPos = $(obj).position(); + $(g.cCpy).css({ + top: objPos.top + 20, + left: objPos.left, + height: $(obj).height(), + width: $(obj).width() + }); + $(g.cPointer).css({ + top: objPos.top + }); + + // get the column index, zero-based + var n = g.getHeaderIdx(obj); + + g.colReorder = { + x0: e.pageX, + y0: e.pageY, + n: n, + newn: n, + obj: obj, + objTop: objPos.top, + objLeft: objPos.left + }; + + $(document.body).css('cursor', 'move').noSelect(); + if (g.isCellEditActive) { + g.hideEditCell(); + } + }, + + /** + * Handle mousemove event when dragging. + * + * @param e event + */ + dragMove: function (e) { + if (g.colRsz) { + var dx = e.pageX - g.colRsz.x0; + if (g.colRsz.objWidth + dx > g.minColWidth) { + $(g.colRsz.obj).css('left', g.colRsz.objLeft + dx + 'px'); + } + } else if (g.colReorder) { + // dragged column animation + var dx = e.pageX - g.colReorder.x0; + $(g.cCpy) + .css('left', g.colReorder.objLeft + dx) + .show(); + + // pointer animation + var hoveredCol = g.getHoveredCol(e); + if (hoveredCol) { + var newn = g.getHeaderIdx(hoveredCol); + g.colReorder.newn = newn; + if (newn !== g.colReorder.n) { + // show the column pointer in the right place + var colPos = $(hoveredCol).position(); + var newleft = newn < g.colReorder.n ? + colPos.left : + colPos.left + $(hoveredCol).outerWidth(); + $(g.cPointer) + .css({ + left: newleft, + visibility: 'visible' + }); + } else { + // no movement to other column, hide the column pointer + $(g.cPointer).css('visibility', 'hidden'); + } + } + } + }, + + /** + * Stop the dragging action. + * + * @param e event + */ + dragEnd: function (e) { + if (g.colRsz) { + var dx = e.pageX - g.colRsz.x0; + var nw = g.colRsz.objWidth + dx; + if (nw < g.minColWidth) { + nw = g.minColWidth; + } + var n = g.colRsz.n; + // do the resizing + g.resize(n, nw); + + g.reposRsz(); + g.reposDrop(); + g.colRsz = false; + $(g.cRsz).find('div').removeClass('colborder_active'); + rearrangeStickyColumns($(t).prev('.sticky_columns'), $(t)); + } else if (g.colReorder) { + // shift columns + if (g.colReorder.newn !== g.colReorder.n) { + g.shiftCol(g.colReorder.n, g.colReorder.newn); + // assign new position + var objPos = $(g.colReorder.obj).position(); + g.colReorder.objTop = objPos.top; + g.colReorder.objLeft = objPos.left; + g.colReorder.n = g.colReorder.newn; + // send request to server to remember the column order + if (g.tableCreateTime) { + g.sendColPrefs(); + } + g.refreshRestoreButton(); + } + + // animate new column position + $(g.cCpy).stop(true, true) + .animate({ + top: g.colReorder.objTop, + left: g.colReorder.objLeft + }, 'fast') + .fadeOut(); + $(g.cPointer).css('visibility', 'hidden'); + + g.colReorder = false; + rearrangeStickyColumns($(t).prev('.sticky_columns'), $(t)); + } + $(document.body).css('cursor', 'inherit').noSelect(false); + }, + + /** + * Resize column n to new width "nw" + * + * @param n zero-based column index + * @param nw new width of the column in pixel + */ + resize: function (n, nw) { + $(g.t).find('tr').each(function () { + $(this).find('th.draggable:visible:eq(' + n + ') span,' + + 'td:visible:eq(' + (g.actionSpan + n) + ') span') + .css('width', nw); + }); + }, + + /** + * Reposition column resize bars. + */ + reposRsz: function () { + $(g.cRsz).find('div').hide(); + var $firstRowCols = $(g.t).find('tr:first th.draggable:visible'); + var $resizeHandles = $(g.cRsz).find('div').removeClass('condition'); + $(g.t).find('table.pma_table').find('thead th:first').removeClass('before-condition'); + for (var n = 0, l = $firstRowCols.length; n < l; n++) { + var $col = $($firstRowCols[n]); + var colWidth; + if (navigator.userAgent.toLowerCase().indexOf('safari') !== -1) { + colWidth = $col.outerWidth(); + } else { + colWidth = $col.outerWidth(true); + } + $($resizeHandles[n]).css('left', $col.position().left + colWidth) + .show(); + if ($col.hasClass('condition')) { + $($resizeHandles[n]).addClass('condition'); + if (n > 0) { + $($resizeHandles[n - 1]).addClass('condition'); + } + } + } + if ($($resizeHandles[0]).hasClass('condition')) { + $(g.t).find('thead th:first').addClass('before-condition'); + } + $(g.cRsz).css('height', $(g.t).height()); + }, + + /** + * Shift column from index oldn to newn. + * + * @param oldn old zero-based column index + * @param newn new zero-based column index + */ + shiftCol: function (oldn, newn) { + $(g.t).find('tr').each(function () { + if (newn < oldn) { + $(this).find('th.draggable:eq(' + newn + '),' + + 'td:eq(' + (g.actionSpan + newn) + ')') + .before($(this).find('th.draggable:eq(' + oldn + '),' + + 'td:eq(' + (g.actionSpan + oldn) + ')')); + } else { + $(this).find('th.draggable:eq(' + newn + '),' + + 'td:eq(' + (g.actionSpan + newn) + ')') + .after($(this).find('th.draggable:eq(' + oldn + '),' + + 'td:eq(' + (g.actionSpan + oldn) + ')')); + } + }); + // reposition the column resize bars + g.reposRsz(); + + // adjust the column visibility list + if (newn < oldn) { + $(g.cList).find('.lDiv div:eq(' + newn + ')') + .before($(g.cList).find('.lDiv div:eq(' + oldn + ')')); + } else { + $(g.cList).find('.lDiv div:eq(' + newn + ')') + .after($(g.cList).find('.lDiv div:eq(' + oldn + ')')); + } + // adjust the colOrder + var tmp = g.colOrder[oldn]; + g.colOrder.splice(oldn, 1); + g.colOrder.splice(newn, 0, tmp); + // adjust the colVisib + if (g.colVisib.length > 0) { + tmp = g.colVisib[oldn]; + g.colVisib.splice(oldn, 1); + g.colVisib.splice(newn, 0, tmp); + } + }, + + /** + * Find currently hovered table column's header (excluding actions column). + * + * @param e event + * @return the hovered column's th object or undefined if no hovered column found. + */ + getHoveredCol: function (e) { + var hoveredCol; + $headers = $(g.t).find('th.draggable:visible'); + $headers.each(function () { + var left = $(this).offset().left; + var right = left + $(this).outerWidth(); + if (left <= e.pageX && e.pageX <= right) { + hoveredCol = this; + } + }); + return hoveredCol; + }, + + /** + * Get a zero-based index from a tag in a table. + * + * @param obj table header object + * @return zero-based index of the specified table header in the set of table headers (visible or not) + */ + getHeaderIdx: function (obj) { + return $(obj).parents('tr').find('th.draggable').index(obj); + }, + + /** + * Reposition the columns back to normal order. + */ + restoreColOrder: function () { + // use insertion sort, since we already have shiftCol function + for (var i = 1; i < g.colOrder.length; i++) { + var x = g.colOrder[i]; + var j = i - 1; + while (j >= 0 && x < g.colOrder[j]) { + j--; + } + if (j !== i - 1) { + g.shiftCol(i, j + 1); + } + } + if (g.tableCreateTime) { + // send request to server to remember the column order + g.sendColPrefs(); + } + g.refreshRestoreButton(); + }, + + /** + * Send column preferences (column order and visibility) to the server. + */ + sendColPrefs: function () { + if ($(g.t).is('.ajax')) { // only send preferences if ajax class + var post_params = { + ajax_request: true, + db: g.db, + table: g.table, + token: g.token, + server: g.server, + set_col_prefs: true, + table_create_time: g.tableCreateTime + }; + if (g.colOrder.length > 0) { + $.extend(post_params, { col_order: g.colOrder.toString() }); + } + if (g.colVisib.length > 0) { + $.extend(post_params, { col_visib: g.colVisib.toString() }); + } + $.post('sql.php', post_params, function (data) { + if (data.success !== true) { + var $temp_div = $(document.createElement('div')); + $temp_div.html(data.error); + $temp_div.addClass('error'); + PMA_ajaxShowMessage($temp_div, false); + } + }); + } + }, + + /** + * Refresh restore button state. + * Make restore button disabled if the table is similar with initial state. + */ + refreshRestoreButton: function () { + // check if table state is as initial state + var isInitial = true; + for (var i = 0; i < g.colOrder.length; i++) { + if (g.colOrder[i] !== i) { + isInitial = false; + break; + } + } + // check if only one visible column left + var isOneColumn = g.visibleHeadersCount === 1; + // enable or disable restore button + if (isInitial || isOneColumn) { + $(g.o).find('div.restore_column').hide(); + } else { + $(g.o).find('div.restore_column').show(); + } + }, + + /** + * Update current hint using the boolean values (showReorderHint, showSortHint, etc.). + * + */ + updateHint: function () { + var text = ''; + if (!g.colRsz && !g.colReorder) { // if not resizing or dragging + if (g.visibleHeadersCount > 1) { + g.showReorderHint = true; + } + if ($(t).find('th.marker').length > 0) { + g.showMarkHint = true; + } + if (g.showSortHint && g.sortHint) { + text += text.length > 0 ? '
      ' : ''; + text += '- ' + g.sortHint; + } + if (g.showMultiSortHint && g.strMultiSortHint) { + text += text.length > 0 ? '
      ' : ''; + text += '- ' + g.strMultiSortHint; + } + if (g.showMarkHint && + g.markHint && + ! g.showSortHint && // we do not show mark hint, when sort hint is shown + g.showReorderHint && + g.reorderHint + ) { + text += text.length > 0 ? '
      ' : ''; + text += '- ' + g.reorderHint; + text += text.length > 0 ? '
      ' : ''; + text += '- ' + g.markHint; + text += text.length > 0 ? '
      ' : ''; + text += '- ' + g.copyHint; + } + } + return text; + }, + + /** + * Toggle column's visibility. + * After calling this function and it returns true, afterToggleCol() must be called. + * + * @return boolean True if the column is toggled successfully. + */ + toggleCol: function (n) { + if (g.colVisib[n]) { + // can hide if more than one column is visible + if (g.visibleHeadersCount > 1) { + $(g.t).find('tr').each(function () { + $(this).find('th.draggable:eq(' + n + '),' + + 'td:eq(' + (g.actionSpan + n) + ')') + .hide(); + }); + g.colVisib[n] = 0; + $(g.cList).find('.lDiv div:eq(' + n + ') input').prop('checked', false); + } else { + // cannot hide, force the checkbox to stay checked + $(g.cList).find('.lDiv div:eq(' + n + ') input').prop('checked', true); + return false; + } + } else { // column n is not visible + $(g.t).find('tr').each(function () { + $(this).find('th.draggable:eq(' + n + '),' + + 'td:eq(' + (g.actionSpan + n) + ')') + .show(); + }); + g.colVisib[n] = 1; + $(g.cList).find('.lDiv div:eq(' + n + ') input').prop('checked', true); + } + return true; + }, + + /** + * This must be called if toggleCol() returns is true. + * + * This function is separated from toggleCol because, sometimes, we want to toggle + * some columns together at one time and do just one adjustment after it, e.g. in showAllColumns(). + */ + afterToggleCol: function () { + // some adjustments after hiding column + g.reposRsz(); + g.reposDrop(); + g.sendColPrefs(); + + // check visible first row headers count + g.visibleHeadersCount = $(g.t).find('tr:first th.draggable:visible').length; + g.refreshRestoreButton(); + }, + + /** + * Show columns' visibility list. + * + * @param obj The drop down arrow of column visibility list + */ + showColList: function (obj) { + // only show when not resizing or reordering + if (!g.colRsz && !g.colReorder) { + var pos = $(obj).position(); + // check if the list position is too right + if (pos.left + $(g.cList).outerWidth(true) > $(document).width()) { + pos.left = $(document).width() - $(g.cList).outerWidth(true); + } + $(g.cList).css({ + left: pos.left, + top: pos.top + $(obj).outerHeight(true) + }) + .show(); + $(obj).addClass('coldrop-hover'); + } + }, + + /** + * Hide columns' visibility list. + */ + hideColList: function () { + $(g.cList).hide(); + $(g.cDrop).find('.coldrop-hover').removeClass('coldrop-hover'); + }, + + /** + * Reposition the column visibility drop-down arrow. + */ + reposDrop: function () { + var $th = $(t).find('th:not(.draggable)'); + for (var i = 0; i < $th.length; i++) { + var $cd = $(g.cDrop).find('div:eq(' + i + ')'); // column drop-down arrow + var pos = $($th[i]).position(); + $cd.css({ + left: pos.left + $($th[i]).width() - $cd.width(), + top: pos.top + }); + } + }, + + /** + * Show all hidden columns. + */ + showAllColumns: function () { + for (var i = 0; i < g.colVisib.length; i++) { + if (!g.colVisib[i]) { + g.toggleCol(i); + } + } + g.afterToggleCol(); + }, + + /** + * Show edit cell, if it can be shown + * + * @param cell element to be edited + */ + showEditCell: function (cell) { + if ($(cell).is('.grid_edit') && + !g.colRsz && !g.colReorder) { + if (!g.isCellEditActive) { + var $cell = $(cell); + + if ('string' === $cell.attr('data-type') || + 'blob' === $cell.attr('data-type') || + 'json' === $cell.attr('data-type') + ) { + g.cEdit = g.cEditTextarea; + } else { + g.cEdit = g.cEditStd; + } + + // remove all edit area and hide it + $(g.cEdit).find('.edit_area').empty().hide(); + // reposition the cEdit element + $(g.cEdit).css({ + top: $cell.position().top, + left: $cell.position().left + }) + .show() + .find('.edit_box') + .css({ + width: $cell.outerWidth(), + height: $cell.outerHeight() + }); + // fill the cell edit with text from + var value = PMA_getCellValue(cell); + if ($cell.attr('data-type') === 'json' && $cell.is('.truncated') === false) { + try { + value = JSON.stringify(JSON.parse(value), null, 4); + } catch (e) { + // Show as is + } + } + $(g.cEdit).find('.edit_box').val(value); + + g.currentEditCell = cell; + $(g.cEdit).find('.edit_box').focus(); + moveCursorToEnd($(g.cEdit).find('.edit_box')); + $(g.cEdit).find('*').prop('disabled', false); + } + } + + function moveCursorToEnd (input) { + var originalValue = input.val(); + var originallength = originalValue.length; + input.val(''); + input.blur().focus().val(originalValue); + input[0].setSelectionRange(originallength, originallength); + } + }, + + /** + * Remove edit cell and the edit area, if it is shown. + * + * @param force Optional, force to hide edit cell without saving edited field. + * @param data Optional, data from the POST AJAX request to save the edited field + * or just specify "true", if we want to replace the edited field with the new value. + * @param field Optional, the edited . If not specified, the function will + * use currently edited from g.currentEditCell. + * @param field Optional, this object contains a boolean named move (true, if called from move* functions) + * and a to which the grid_edit should move + */ + hideEditCell: function (force, data, field, options) { + if (g.isCellEditActive && !force) { + // cell is being edited, save or post the edited data + if (options !== undefined) { + g.saveOrPostEditedCell(options); + } else { + g.saveOrPostEditedCell(); + } + return; + } + + // cancel any previous request + if (g.lastXHR !== null) { + g.lastXHR.abort(); + g.lastXHR = null; + } + + if (data) { + if (g.currentEditCell) { // save value of currently edited cell + // replace current edited field with the new value + var $this_field = $(g.currentEditCell); + var is_null = $this_field.data('value') === null; + if (is_null) { + $this_field.find('span').html('NULL'); + $this_field.addClass('null'); + } else { + $this_field.removeClass('null'); + var value = data.isNeedToRecheck + ? data.truncatableFieldValue + : $this_field.data('value'); + + // Truncates the text. + $this_field.removeClass('truncated'); + if (PMA_commonParams.get('pftext') === 'P' && value.length > g.maxTruncatedLen) { + $this_field.addClass('truncated'); + value = value.substring(0, g.maxTruncatedLen) + '...'; + } + + // Add
      before carriage return. + new_html = escapeHtml(value); + new_html = new_html.replace(/\n/g, '
      \n'); + + // remove decimal places if column type not supported + if (($this_field.attr('data-decimals') === 0) && ($this_field.attr('data-type').indexOf('time') !== -1)) { + new_html = new_html.substring(0, new_html.indexOf('.')); + } + + // remove addtional decimal places + if (($this_field.attr('data-decimals') > 0) && ($this_field.attr('data-type').indexOf('time') !== -1)) { + new_html = new_html.substring(0, new_html.length - (6 - $this_field.attr('data-decimals'))); + } + + var selector = 'span'; + if ($this_field.hasClass('hex') && $this_field.find('a').length) { + selector = 'a'; + } + + // Updates the code keeping highlighting (if any). + var $target = $this_field.find(selector); + if (!PMA_updateCode($target, new_html, value)) { + $target.html(new_html); + } + } + if ($this_field.is('.bit')) { + $this_field.find('span').text($this_field.data('value')); + } + } + if (data.transformations !== undefined) { + $.each(data.transformations, function (cell_index, value) { + var $this_field = $(g.t).find('.to_be_saved:eq(' + cell_index + ')'); + $this_field.find('span').html(value); + }); + } + if (data.relations !== undefined) { + $.each(data.relations, function (cell_index, value) { + var $this_field = $(g.t).find('.to_be_saved:eq(' + cell_index + ')'); + $this_field.find('span').html(value); + }); + } + + // refresh the grid + g.reposRsz(); + g.reposDrop(); + } + + // hide the cell editing area + $(g.cEdit).hide(); + $(g.cEdit).find('.edit_box').blur(); + g.isCellEditActive = false; + g.currentEditCell = null; + // destroy datepicker in edit area, if exist + var $dp = $(g.cEdit).find('.hasDatepicker'); + if ($dp.length > 0) { + $(document).bind('mousedown', $.datepicker._checkExternalClick); + $dp.datepicker('destroy'); + // change the cursor in edit box back to normal + // (the cursor become a hand pointer when we add datepicker) + $(g.cEdit).find('.edit_box').css('cursor', 'inherit'); + } + }, + + /** + * Show drop-down edit area when edit cell is focused. + */ + showEditArea: function () { + if (!g.isCellEditActive) { // make sure the edit area has not been shown + g.isCellEditActive = true; + g.isEditCellTextEditable = false; + /** + * @var $td current edited cell + */ + var $td = $(g.currentEditCell); + /** + * @var $editArea the editing area + */ + var $editArea = $(g.cEdit).find('.edit_area'); + /** + * @var where_clause WHERE clause for the edited cell + */ + var where_clause = $td.parent('tr').find('.where_clause').val(); + /** + * @var field_name String containing the name of this field. + * @see getFieldName() + */ + var field_name = getFieldName($(t), $td); + /** + * @var relation_curr_value String current value of the field (for fields that are foreign keyed). + */ + var relation_curr_value = $td.text(); + /** + * @var relation_key_or_display_column String relational key if in 'Relational display column' mode, + * relational display column if in 'Relational key' mode (for fields that are foreign keyed). + */ + var relation_key_or_display_column = $td.find('a').attr('title'); + /** + * @var curr_value String current value of the field (for fields that are of type enum or set). + */ + var curr_value = $td.find('span').text(); + + // empty all edit area, then rebuild it based on $td classes + $editArea.empty(); + + // remember this instead of testing more than once + var is_null = $td.is('.null'); + + // add goto link, if this cell contains a link + if ($td.find('a').length > 0) { + var gotoLink = document.createElement('div'); + gotoLink.className = 'goto_link'; + $(gotoLink).append(g.gotoLinkText + ' ').append($td.find('a').clone()); + $editArea.append(gotoLink); + } + + g.wasEditedCellNull = false; + if ($td.is(':not(.not_null)')) { + // append a null checkbox + $editArea.append('
      '); + + var $checkbox = $editArea.find('.null_div input'); + // check if current is NULL + if (is_null) { + $checkbox.prop('checked', true); + g.wasEditedCellNull = true; + } + + // if the select/editor is changed un-check the 'checkbox_null__'. + if ($td.is('.enum, .set')) { + $editArea.on('change', 'select', function () { + $checkbox.prop('checked', false); + }); + } else if ($td.is('.relation')) { + $editArea.on('change', 'select', function () { + $checkbox.prop('checked', false); + }); + $editArea.on('click', '.browse_foreign', function () { + $checkbox.prop('checked', false); + }); + } else { + $(g.cEdit).on('keypress change paste', '.edit_box', function () { + $checkbox.prop('checked', false); + }); + // Capture ctrl+v (on IE and Chrome) + $(g.cEdit).on('keydown', '.edit_box', function (e) { + if (e.ctrlKey && e.which === 86) { + $checkbox.prop('checked', false); + } + }); + $editArea.on('keydown', 'textarea', function () { + $checkbox.prop('checked', false); + }); + } + // if some text is written in textbox automatically unmark the null checkbox and if it is emptied again mark the checkbox. + $(g.cEdit).find('.edit_box').on('input', function () { + if ($(g.cEdit).find('.edit_box').val() !== '') { + $checkbox.prop('checked', false); + } else { + $checkbox.prop('checked', true); + } + }); + // if null checkbox is clicked empty the corresponding select/editor. + $checkbox.click(function () { + if ($td.is('.enum')) { + $editArea.find('select').val(''); + } else if ($td.is('.set')) { + $editArea.find('select').find('option').each(function () { + var $option = $(this); + $option.prop('selected', false); + }); + } else if ($td.is('.relation')) { + // if the dropdown is there to select the foreign value + if ($editArea.find('select').length > 0) { + $editArea.find('select').val(''); + } + } else { + $editArea.find('textarea').val(''); + } + $(g.cEdit).find('.edit_box').val(''); + }); + } + + // reset the position of the edit_area div after closing datetime picker + $(g.cEdit).find('.edit_area').css({ 'top' :'0','position':'' }); + + if ($td.is('.relation')) { + // handle relations + $editArea.addClass('edit_area_loading'); + + // initialize the original data + $td.data('original_data', null); + + /** + * @var post_params Object containing parameters for the POST request + */ + var post_params = { + 'ajax_request' : true, + 'get_relational_values' : true, + 'server' : g.server, + 'db' : g.db, + 'table' : g.table, + 'column' : field_name, + 'curr_value' : relation_curr_value, + 'relation_key_or_display_column' : relation_key_or_display_column + }; + + g.lastXHR = $.post('sql.php', post_params, function (data) { + g.lastXHR = null; + $editArea.removeClass('edit_area_loading'); + if ($(data.dropdown).is('select')) { + // save original_data + var value = $(data.dropdown).val(); + $td.data('original_data', value); + // update the text input field, in case where the "Relational display column" is checked + $(g.cEdit).find('.edit_box').val(value); + } + + $editArea.append(data.dropdown); + $editArea.append('
      ' + g.cellEditHint + '
      '); + + // for 'Browse foreign values' options, + // hide the value next to 'Browse foreign values' link + $editArea.find('span.curr_value').hide(); + // handle update for new values selected from new window + $editArea.find('span.curr_value').change(function () { + $(g.cEdit).find('.edit_box').val($(this).text()); + }); + }); // end $.post() + + $editArea.show(); + $editArea.on('change', 'select', function () { + $(g.cEdit).find('.edit_box').val($(this).val()); + }); + g.isEditCellTextEditable = true; + } else if ($td.is('.enum')) { + // handle enum fields + $editArea.addClass('edit_area_loading'); + + /** + * @var post_params Object containing parameters for the POST request + */ + var post_params = { + 'ajax_request' : true, + 'get_enum_values' : true, + 'server' : g.server, + 'db' : g.db, + 'table' : g.table, + 'column' : field_name, + 'curr_value' : curr_value + }; + g.lastXHR = $.post('sql.php', post_params, function (data) { + g.lastXHR = null; + $editArea.removeClass('edit_area_loading'); + $editArea.append(data.dropdown); + $editArea.append('
      ' + g.cellEditHint + '
      '); + }); // end $.post() + + $editArea.show(); + $editArea.on('change', 'select', function () { + $(g.cEdit).find('.edit_box').val($(this).val()); + }); + } else if ($td.is('.set')) { + // handle set fields + $editArea.addClass('edit_area_loading'); + + /** + * @var post_params Object containing parameters for the POST request + */ + var post_params = { + 'ajax_request' : true, + 'get_set_values' : true, + 'server' : g.server, + 'db' : g.db, + 'table' : g.table, + 'column' : field_name, + 'curr_value' : curr_value + }; + + // if the data is truncated, get the full data + if ($td.is('.truncated')) { + post_params.get_full_values = true; + post_params.where_clause = where_clause; + } + + g.lastXHR = $.post('sql.php', post_params, function (data) { + g.lastXHR = null; + $editArea.removeClass('edit_area_loading'); + $editArea.append(data.select); + $td.data('original_data', $(data.select).val().join()); + $editArea.append('
      ' + g.cellEditHint + '
      '); + }); // end $.post() + + $editArea.show(); + $editArea.on('change', 'select', function () { + $(g.cEdit).find('.edit_box').val($(this).val()); + }); + } else if ($td.is('.truncated, .transformed')) { + if ($td.is('.to_be_saved')) { // cell has been edited + var value = $td.data('value'); + $(g.cEdit).find('.edit_box').val(value); + $editArea.append(''); + $editArea.find('textarea').val(value); + $editArea + .on('keyup', 'textarea', function () { + $(g.cEdit).find('.edit_box').val($(this).val()); + }); + $(g.cEdit).on('keyup', '.edit_box', function () { + $editArea.find('textarea').val($(this).val()); + }); + $editArea.append('
      ' + g.cellEditHint + '
      '); + } else { + // handle truncated/transformed values values + $editArea.addClass('edit_area_loading'); + + // initialize the original data + $td.data('original_data', null); + + /** + * @var sql_query String containing the SQL query used to retrieve value of truncated/transformed data + */ + var sql_query = 'SELECT `' + field_name + '` FROM `' + g.table + '` WHERE ' + where_clause; + + // Make the Ajax call and get the data, wrap it and insert it + g.lastXHR = $.post('sql.php', { + 'server' : g.server, + 'db' : g.db, + 'ajax_request' : true, + 'sql_query' : sql_query, + 'grid_edit' : true + }, function (data) { + g.lastXHR = null; + $editArea.removeClass('edit_area_loading'); + if (typeof data !== 'undefined' && data.success === true) { + if ($td.attr('data-type') === 'json') { + try { + data.value = JSON.stringify(JSON.parse(data.value), null, 4); + } catch (e) { + // Show as is + } + } + $td.data('original_data', data.value); + $(g.cEdit).find('.edit_box').val(data.value); + $editArea.append(''); + $editArea.find('textarea').val(data.value); + $editArea.on('keyup', 'textarea', function () { + $(g.cEdit).find('.edit_box').val($(this).val()); + }); + $(g.cEdit).on('keyup', '.edit_box', function () { + $editArea.find('textarea').val($(this).val()); + }); + $editArea.append('
      ' + g.cellEditHint + '
      '); + $editArea.show(); + } else { + PMA_ajaxShowMessage(data.error, false); + } + }); // end $.post() + } + g.isEditCellTextEditable = true; + } else if ($td.is('.timefield, .datefield, .datetimefield, .timestampfield')) { + var $input_field = $(g.cEdit).find('.edit_box'); + + // remember current datetime value in $input_field, if it is not null + var datetime_value = !is_null ? $input_field.val() : ''; + + var showMillisec = false; + var showMicrosec = false; + var timeFormat = 'HH:mm:ss'; + // check for decimal places of seconds + if (($td.attr('data-decimals') > 0) && ($td.attr('data-type').indexOf('time') !== -1)) { + if (datetime_value && datetime_value.indexOf('.') === false) { + datetime_value += '.'; + } + if ($td.attr('data-decimals') > 3) { + showMillisec = true; + showMicrosec = true; + timeFormat = 'HH:mm:ss.lc'; + + if (datetime_value) { + datetime_value += '000000'; + var datetime_value = datetime_value.substring(0, datetime_value.indexOf('.') + 7); + $input_field.val(datetime_value); + } + } else { + showMillisec = true; + timeFormat = 'HH:mm:ss.l'; + + if (datetime_value) { + datetime_value += '000'; + var datetime_value = datetime_value.substring(0, datetime_value.indexOf('.') + 4); + $input_field.val(datetime_value); + } + } + } + + // add datetime picker + PMA_addDatepicker($input_field, $td.attr('data-type'), { + showMillisec: showMillisec, + showMicrosec: showMicrosec, + timeFormat: timeFormat + }); + + $input_field.on('keyup', function (e) { + if (e.which === 13) { + // post on pressing "Enter" + e.preventDefault(); + e.stopPropagation(); + g.saveOrPostEditedCell(); + } else if (e.which === 27) { + } else { + toggleDatepickerIfInvalid($td, $input_field); + } + }); + + $input_field.datepicker('show'); + toggleDatepickerIfInvalid($td, $input_field); + + // unbind the mousedown event to prevent the problem of + // datepicker getting closed, needs to be checked for any + // change in names when updating + $(document).off('mousedown', $.datepicker._checkExternalClick); + + // move ui-datepicker-div inside cEdit div + var datepicker_div = $('#ui-datepicker-div'); + datepicker_div.css({ 'top': 0, 'left': 0, 'position': 'relative' }); + $(g.cEdit).append(datepicker_div); + + // cancel any click on the datepicker element + $editArea.find('> *').click(function (e) { + e.stopPropagation(); + }); + + g.isEditCellTextEditable = true; + } else { + g.isEditCellTextEditable = true; + // only append edit area hint if there is a null checkbox + if ($editArea.children().length > 0) { + $editArea.append('
      ' + g.cellEditHint + '
      '); + } + } + if ($editArea.children().length > 0) { + $editArea.show(); + } + } + }, + + /** + * Post the content of edited cell. + * + * @param field Optional, this object contains a boolean named move (true, if called from move* functions) + * and a to which the grid_edit should move + */ + postEditedCell: function (options) { + if (g.isSaving) { + return; + } + g.isSaving = true; + /** + * @var relation_fields Array containing the name/value pairs of relational fields + */ + var relation_fields = {}; + /** + * @var relational_display string 'K' if relational key, 'D' if relational display column + */ + var relational_display = $(g.o).find('input[name=relational_display]:checked').val(); + /** + * @var transform_fields Array containing the name/value pairs for transformed fields + */ + var transform_fields = {}; + /** + * @var transformation_fields Boolean, if there are any transformed fields in the edited cells + */ + var transformation_fields = false; + /** + * @var full_sql_query String containing the complete SQL query to update this table + */ + var full_sql_query = ''; + /** + * @var rel_fields_list String, url encoded representation of {@link relations_fields} + */ + var rel_fields_list = ''; + /** + * @var transform_fields_list String, url encoded representation of {@link transform_fields} + */ + var transform_fields_list = ''; + /** + * @var where_clause Array containing where clause for updated fields + */ + var full_where_clause = []; + /** + * @var is_unique Boolean, whether the rows in this table is unique or not + */ + var is_unique = $(g.t).find('td.edit_row_anchor').is('.nonunique') ? 0 : 1; + /** + * multi edit variables + */ + var me_fields_name = []; + var me_fields_type = []; + var me_fields = []; + var me_fields_null = []; + + // alert user if edited table is not unique + if (!is_unique) { + alert(g.alertNonUnique); + } + + // loop each edited row + $(g.t).find('td.to_be_saved').parents('tr').each(function () { + var $tr = $(this); + var where_clause = $tr.find('.where_clause').val(); + if (typeof where_clause === 'undefined') { + where_clause = ''; + } + full_where_clause.push(where_clause); + var condition_array = JSON.parse($tr.find('.condition_array').val()); + + /** + * multi edit variables, for current row + * @TODO array indices are still not correct, they should be md5 of field's name + */ + var fields_name = []; + var fields_type = []; + var fields = []; + var fields_null = []; + + // loop each edited cell in a row + $tr.find('.to_be_saved').each(function () { + /** + * @var $this_field Object referring to the td that is being edited + */ + var $this_field = $(this); + + /** + * @var field_name String containing the name of this field. + * @see getFieldName() + */ + var field_name = getFieldName($(g.t), $this_field); + + /** + * @var this_field_params Array temporary storage for the name/value of current field + */ + var this_field_params = {}; + + if ($this_field.is('.transformed')) { + transformation_fields = true; + } + this_field_params[field_name] = $this_field.data('value'); + + /** + * @var is_null String capturing whether 'checkbox_null__' is checked. + */ + var is_null = this_field_params[field_name] === null; + + fields_name.push(field_name); + + if (is_null) { + fields_null.push('on'); + fields.push(''); + } else { + if ($this_field.is('.bit')) { + fields_type.push('bit'); + } else if ($this_field.hasClass('hex')) { + fields_type.push('hex'); + } + fields_null.push(''); + // Convert \n to \r\n to be consistent with form submitted value. + // The internal browser representation has to be just \n + // while form submitted value \r\n, see specification: + // https://www.w3.org/TR/html5/forms.html#the-textarea-element + fields.push($this_field.data('value').replace(/\n/g, '\r\n')); + + var cell_index = $this_field.index('.to_be_saved'); + if ($this_field.is(':not(.relation, .enum, .set, .bit)')) { + if ($this_field.is('.transformed')) { + transform_fields[cell_index] = {}; + $.extend(transform_fields[cell_index], this_field_params); + } + } else if ($this_field.is('.relation')) { + relation_fields[cell_index] = {}; + $.extend(relation_fields[cell_index], this_field_params); + } + } + // check if edited field appears in WHERE clause + if (where_clause.indexOf(PMA_urlencode(field_name)) > -1) { + var field_str = '`' + g.table + '`.' + '`' + field_name + '`'; + for (var field in condition_array) { + if (field.indexOf(field_str) > -1) { + condition_array[field] = is_null ? 'IS NULL' : '= \'' + this_field_params[field_name].replace(/'/g, '\'\'') + '\''; + break; + } + } + } + }); // end of loop for every edited cells in a row + + // save new_clause + var new_clause = ''; + for (var field in condition_array) { + new_clause += field + ' ' + condition_array[field] + ' AND '; + } + new_clause = new_clause.substring(0, new_clause.length - 5); // remove the last AND + $tr.data('new_clause', new_clause); + // save condition_array + $tr.find('.condition_array').val(JSON.stringify(condition_array)); + + me_fields_name.push(fields_name); + me_fields_type.push(fields_type); + me_fields.push(fields); + me_fields_null.push(fields_null); + }); // end of loop for every edited rows + + rel_fields_list = $.param(relation_fields); + transform_fields_list = $.param(transform_fields); + + // Make the Ajax post after setting all parameters + /** + * @var post_params Object containing parameters for the POST request + */ + var post_params = { 'ajax_request' : true, + 'sql_query' : full_sql_query, + 'server' : g.server, + 'db' : g.db, + 'table' : g.table, + 'clause_is_unique' : is_unique, + 'where_clause' : full_where_clause, + 'fields[multi_edit]' : me_fields, + 'fields_name[multi_edit]' : me_fields_name, + 'fields_type[multi_edit]' : me_fields_type, + 'fields_null[multi_edit]' : me_fields_null, + 'rel_fields_list' : rel_fields_list, + 'do_transformations' : transformation_fields, + 'transform_fields_list' : transform_fields_list, + 'relational_display' : relational_display, + 'goto' : 'sql.php', + 'submit_type' : 'save' + }; + + if (!g.saveCellsAtOnce) { + $(g.cEdit).find('*').prop('disabled', true); + $(g.cEdit).find('.edit_box').addClass('edit_box_posting'); + } else { + $(g.o).find('div.save_edited').addClass('saving_edited_data') + .find('input').prop('disabled', true); // disable the save button + } + + $.ajax({ + type: 'POST', + url: 'tbl_replace.php', + data: post_params, + success: + function (data) { + g.isSaving = false; + if (!g.saveCellsAtOnce) { + $(g.cEdit).find('*').prop('disabled', false); + $(g.cEdit).find('.edit_box').removeClass('edit_box_posting'); + } else { + $(g.o).find('div.save_edited').removeClass('saving_edited_data') + .find('input').prop('disabled', false); // enable the save button back + } + if (typeof data !== 'undefined' && data.success === true) { + if (typeof options === 'undefined' || ! options.move) { + PMA_ajaxShowMessage(data.message); + } + + // update where_clause related data in each edited row + $(g.t).find('td.to_be_saved').parents('tr').each(function () { + var new_clause = $(this).data('new_clause'); + var $where_clause = $(this).find('.where_clause'); + var old_clause = $where_clause.val(); + var decoded_old_clause = old_clause; + var decoded_new_clause = new_clause; + + $where_clause.val(new_clause); + // update Edit, Copy, and Delete links also + $(this).find('a').each(function () { + $(this).attr('href', $(this).attr('href').replace(old_clause, new_clause)); + // update delete confirmation in Delete link + if ($(this).attr('href').indexOf('DELETE') > -1) { + $(this).removeAttr('onclick') + .off('click') + .on('click', function () { + return confirmLink(this, 'DELETE FROM `' + g.db + '`.`' + g.table + '` WHERE ' + + decoded_new_clause + (is_unique ? '' : ' LIMIT 1')); + }); + } + }); + // update the multi edit checkboxes + $(this).find('input[type=checkbox]').each(function () { + var $checkbox = $(this); + var checkbox_name = $checkbox.attr('name'); + var checkbox_value = $checkbox.val(); + + $checkbox.attr('name', checkbox_name.replace(old_clause, new_clause)); + $checkbox.val(checkbox_value.replace(decoded_old_clause, decoded_new_clause)); + }); + }); + // update the display of executed SQL query command + if (typeof data.sql_query !== 'undefined') { + // extract query box + var $result_query = $($.parseHTML(data.sql_query)); + var sqlOuter = $result_query.find('.sqlOuter').wrap('

      ').parent().html(); + var tools = $result_query.find('.tools').wrap('

      ').parent().html(); + // sqlOuter and tools will not be present if 'Show SQL queries' configuration is off + if (typeof sqlOuter !== 'undefined' && typeof tools !== 'undefined') { + $(g.o).find('.result_query:not(:last)').remove(); + var $existing_query = $(g.o).find('.result_query'); + // If two query box exists update query in second else add a second box + if ($existing_query.find('div.sqlOuter').length > 1) { + $existing_query.children(':nth-child(4)').remove(); + $existing_query.children(':nth-child(4)').remove(); + $existing_query.append(sqlOuter + tools); + } else { + $existing_query.append(sqlOuter + tools); + } + PMA_highlightSQL($existing_query); + } + } + // hide and/or update the successfully saved cells + g.hideEditCell(true, data); + + // remove the "Save edited cells" button + $(g.o).find('div.save_edited').hide(); + // update saved fields + $(g.t).find('.to_be_saved') + .removeClass('to_be_saved') + .data('value', null) + .data('original_data', null); + + g.isCellEdited = false; + } else { + PMA_ajaxShowMessage(data.error, false); + if (!g.saveCellsAtOnce) { + $(g.t).find('.to_be_saved') + .removeClass('to_be_saved'); + } + } + } + }).done(function () { + if (options !== undefined && options.move) { + g.showEditCell(options.cell); + } + }); // end $.ajax() + }, + + /** + * Save edited cell, so it can be posted later. + */ + saveEditedCell: function () { + /** + * @var $this_field Object referring to the td that is being edited + */ + var $this_field = $(g.currentEditCell); + var $test_element = ''; // to test the presence of a element + + var need_to_post = false; + + /** + * @var field_name String containing the name of this field. + * @see getFieldName() + */ + var field_name = getFieldName($(g.t), $this_field); + + /** + * @var this_field_params Array temporary storage for the name/value of current field + */ + var this_field_params = {}; + + /** + * @var is_null String capturing whether 'checkbox_null__' is checked. + */ + var is_null = $(g.cEdit).find('input:checkbox').is(':checked'); + + if ($(g.cEdit).find('.edit_area').is('.edit_area_loading')) { + // the edit area is still loading (retrieving cell data), no need to post + need_to_post = false; + } else if (is_null) { + if (!g.wasEditedCellNull) { + this_field_params[field_name] = null; + need_to_post = true; + } + } else { + if ($this_field.is('.bit')) { + this_field_params[field_name] = $(g.cEdit).find('.edit_box').val(); + } else if ($this_field.is('.set')) { + $test_element = $(g.cEdit).find('select'); + this_field_params[field_name] = $test_element.map(function () { + return $(this).val(); + }).get().join(','); + } else if ($this_field.is('.relation, .enum')) { + // for relation and enumeration, take the results from edit box value, + // because selected value from drop-down, new window or multiple + // selection list will always be updated to the edit box + this_field_params[field_name] = $(g.cEdit).find('.edit_box').val(); + } else if ($this_field.hasClass('hex')) { + if ($(g.cEdit).find('.edit_box').val().match(/^(0x)?[a-f0-9]*$/i) !== null) { + this_field_params[field_name] = $(g.cEdit).find('.edit_box').val(); + } else { + var hexError = '

      ' + PMA_messages.strEnterValidHex + '
      '; + PMA_ajaxShowMessage(hexError, false); + this_field_params[field_name] = PMA_getCellValue(g.currentEditCell); + } + } else { + this_field_params[field_name] = $(g.cEdit).find('.edit_box').val(); + } + if (g.wasEditedCellNull || this_field_params[field_name] !== PMA_getCellValue(g.currentEditCell)) { + need_to_post = true; + } + } + + if (need_to_post) { + $(g.currentEditCell).addClass('to_be_saved') + .data('value', this_field_params[field_name]); + if (g.saveCellsAtOnce) { + $(g.o).find('div.save_edited').show(); + } + g.isCellEdited = true; + } + + return need_to_post; + }, + + /** + * Save or post currently edited cell, depending on the "saveCellsAtOnce" configuration. + * + * @param field Optional, this object contains a boolean named move (true, if called from move* functions) + * and a to which the grid_edit should move + */ + saveOrPostEditedCell: function (options) { + var saved = g.saveEditedCell(); + // Check if $cfg['SaveCellsAtOnce'] is false + if (!g.saveCellsAtOnce) { + // Check if need_to_post is true + if (saved) { + // Check if this function called from 'move' functions + if (options !== undefined && options.move) { + g.postEditedCell(options); + } else { + g.postEditedCell(); + } + // need_to_post is false + } else { + // Check if this function called from 'move' functions + if (options !== undefined && options.move) { + g.hideEditCell(true); + g.showEditCell(options.cell); + // NOT called from 'move' functions + } else { + g.hideEditCell(true); + } + } + // $cfg['SaveCellsAtOnce'] is true + } else { + // If need_to_post + if (saved) { + // If this function called from 'move' functions + if (options !== undefined && options.move) { + g.hideEditCell(true, true, false, options); + g.showEditCell(options.cell); + // NOT called from 'move' functions + } else { + g.hideEditCell(true, true); + } + } else { + // If this function called from 'move' functions + if (options !== undefined && options.move) { + g.hideEditCell(true, false, false, options); + g.showEditCell(options.cell); + // NOT called from 'move' functions + } else { + g.hideEditCell(true); + } + } + } + }, + + /** + * Initialize column resize feature. + */ + initColResize: function () { + // create column resizer div + g.cRsz = document.createElement('div'); + g.cRsz.className = 'cRsz'; + + // get data columns in the first row of the table + var $firstRowCols = $(g.t).find('tr:first th.draggable'); + + // create column borders + $firstRowCols.each(function () { + var cb = document.createElement('div'); // column border + $(cb).addClass('colborder') + .mousedown(function (e) { + g.dragStartRsz(e, this); + }); + $(g.cRsz).append(cb); + }); + g.reposRsz(); + + // attach to global div + $(g.gDiv).prepend(g.cRsz); + }, + + /** + * Initialize column reordering feature. + */ + initColReorder: function () { + g.cCpy = document.createElement('div'); // column copy, to store copy of dragged column header + g.cPointer = document.createElement('div'); // column pointer, used when reordering column + + // adjust g.cCpy + g.cCpy.className = 'cCpy'; + $(g.cCpy).hide(); + + // adjust g.cPointer + g.cPointer.className = 'cPointer'; + $(g.cPointer).css('visibility', 'hidden'); // set visibility to hidden instead of calling hide() to force browsers to cache the image in cPointer class + + // assign column reordering hint + g.reorderHint = PMA_messages.strColOrderHint; + + // get data columns in the first row of the table + var $firstRowCols = $(g.t).find('tr:first th.draggable'); + + // initialize column order + $col_order = $(g.o).find('.col_order'); // check if column order is passed from PHP + if ($col_order.length > 0) { + g.colOrder = $col_order.val().split(','); + for (var i = 0; i < g.colOrder.length; i++) { + g.colOrder[i] = parseInt(g.colOrder[i], 10); + } + } else { + g.colOrder = []; + for (var i = 0; i < $firstRowCols.length; i++) { + g.colOrder.push(i); + } + } + + // register events + $(g.t).find('th.draggable') + .mousedown(function (e) { + $(g.o).addClass('turnOffSelect'); + if (g.visibleHeadersCount > 1) { + g.dragStartReorder(e, this); + } + }) + .mouseenter(function () { + if (g.visibleHeadersCount > 1) { + $(this).css('cursor', 'move'); + } else { + $(this).css('cursor', 'inherit'); + } + }) + .mouseleave(function () { + g.showReorderHint = false; + $(this).tooltip('option', { + content: g.updateHint() + }); + }) + .dblclick(function (e) { + e.preventDefault(); + $('
      ') + .prop('title', PMA_messages.strColNameCopyTitle) + .addClass('modal-copy') + .text(PMA_messages.strColNameCopyText) + .append( + $('') + .prop('readonly', true) + .val($(this).data('column')) + ) + .dialog({ + resizable: false, + modal: true + }) + .find('input').focus().select(); + }); + $(g.t).find('th.draggable a') + .dblclick(function (e) { + e.stopPropagation(); + }); + // restore column order when the restore button is clicked + $(g.o).find('div.restore_column').click(function () { + g.restoreColOrder(); + }); + + // attach to global div + $(g.gDiv).append(g.cPointer); + $(g.gDiv).append(g.cCpy); + + // prevent default "dragstart" event when dragging a link + $(g.t).find('th a').on('dragstart', function () { + return false; + }); + + // refresh the restore column button state + g.refreshRestoreButton(); + }, + + /** + * Initialize column visibility feature. + */ + initColVisib: function () { + g.cDrop = document.createElement('div'); // column drop-down arrows + g.cList = document.createElement('div'); // column visibility list + + // adjust g.cDrop + g.cDrop.className = 'cDrop'; + + // adjust g.cList + g.cList.className = 'cList'; + $(g.cList).hide(); + + // assign column visibility related hints + g.showAllColText = PMA_messages.strShowAllCol; + + // get data columns in the first row of the table + var $firstRowCols = $(g.t).find('tr:first th.draggable'); + + var i; + // initialize column visibility + var $col_visib = $(g.o).find('.col_visib'); // check if column visibility is passed from PHP + if ($col_visib.length > 0) { + g.colVisib = $col_visib.val().split(','); + for (i = 0; i < g.colVisib.length; i++) { + g.colVisib[i] = parseInt(g.colVisib[i], 10); + } + } else { + g.colVisib = []; + for (i = 0; i < $firstRowCols.length; i++) { + g.colVisib.push(1); + } + } + + // make sure we have more than one column + if ($firstRowCols.length > 1) { + var $colVisibTh = $(g.t).find('th:not(.draggable)'); + PMA_tooltip( + $colVisibTh, + 'th', + PMA_messages.strColVisibHint + ); + + // create column visibility drop-down arrow(s) + $colVisibTh.each(function () { + var $th = $(this); + var cd = document.createElement('div'); // column drop-down arrow + var pos = $th.position(); + $(cd).addClass('coldrop') + .click(function () { + if (g.cList.style.display === 'none') { + g.showColList(this); + } else { + g.hideColList(); + } + }); + $(g.cDrop).append(cd); + }); + + // add column visibility control + g.cList.innerHTML = '
      '; + var $listDiv = $(g.cList).find('div'); + + var tempClick = function () { + if (g.toggleCol($(this).index())) { + g.afterToggleCol(); + } + }; + + for (i = 0; i < $firstRowCols.length; i++) { + var currHeader = $firstRowCols[i]; + var listElmt = document.createElement('div'); + $(listElmt).text($(currHeader).text()) + .prepend(''); + $listDiv.append(listElmt); + // add event on click + $(listElmt).click(tempClick); + } + // add "show all column" button + var showAll = document.createElement('div'); + $(showAll).addClass('showAllColBtn') + .text(g.showAllColText); + $(g.cList).append(showAll); + $(showAll).click(function () { + g.showAllColumns(); + }); + // prepend "show all column" button at top if the list is too long + if ($firstRowCols.length > 10) { + var clone = showAll.cloneNode(true); + $(g.cList).prepend(clone); + $(clone).click(function () { + g.showAllColumns(); + }); + } + } + + // hide column visibility list if we move outside the list + $(g.t).find('td, th.draggable').mouseenter(function () { + g.hideColList(); + }); + + // attach to global div + $(g.gDiv).append(g.cDrop); + $(g.gDiv).append(g.cList); + + // some adjustment + g.reposDrop(); + }, + + /** + * Move currently Editing Cell to Up + */ + moveUp: function (e) { + e.preventDefault(); + var $this_field = $(g.currentEditCell); + var field_name = getFieldName($(g.t), $this_field); + + var where_clause = $this_field.parents('tr').first().find('.where_clause').val(); + if (typeof where_clause === 'undefined') { + where_clause = ''; + } + var found = false; + var $found_row; + var $prev_row; + var j = 0; + + $this_field.parents('tr').first().parents('tbody').children().each(function () { + if ($(this).find('.where_clause').val() === where_clause) { + found = true; + $found_row = $(this); + } + if (!found) { + $prev_row = $(this); + } + }); + + var new_cell; + + if (found && $prev_row) { + $prev_row.children('td').each(function () { + if (getFieldName($(g.t), $(this)) === field_name) { + new_cell = this; + } + }); + } + + if (new_cell) { + g.hideEditCell(false, false, false, { move : true, cell : new_cell }); + } + }, + + /** + * Move currently Editing Cell to Down + */ + moveDown: function (e) { + e.preventDefault(); + + var $this_field = $(g.currentEditCell); + var field_name = getFieldName($(g.t), $this_field); + + var where_clause = $this_field.parents('tr').first().find('.where_clause').val(); + if (typeof where_clause === 'undefined') { + where_clause = ''; + } + var found = false; + var $found_row; + var $next_row; + var j = 0; + var next_row_found = false; + $this_field.parents('tr').first().parents('tbody').children().each(function () { + if ($(this).find('.where_clause').val() === where_clause) { + found = true; + $found_row = $(this); + } + if (found) { + if (j >= 1 && ! next_row_found) { + $next_row = $(this); + next_row_found = true; + } else { + j++; + } + } + }); + + var new_cell; + if (found && $next_row) { + $next_row.children('td').each(function () { + if (getFieldName($(g.t), $(this)) === field_name) { + new_cell = this; + } + }); + } + + if (new_cell) { + g.hideEditCell(false, false, false, { move : true, cell : new_cell }); + } + }, + + /** + * Move currently Editing Cell to Left + */ + moveLeft: function (e) { + e.preventDefault(); + + var $this_field = $(g.currentEditCell); + var field_name = getFieldName($(g.t), $this_field); + + var where_clause = $this_field.parents('tr').first().find('.where_clause').val(); + if (typeof where_clause === 'undefined') { + where_clause = ''; + } + var found = false; + var $found_row; + var j = 0; + $this_field.parents('tr').first().parents('tbody').children().each(function () { + if ($(this).find('.where_clause').val() === where_clause) { + found = true; + $found_row = $(this); + } + }); + + var left_cell; + var cell_found = false; + if (found) { + $found_row.children('td.grid_edit').each(function () { + if (getFieldName($(g.t), $(this)) === field_name) { + cell_found = true; + } + if (!cell_found) { + left_cell = this; + } + }); + } + + if (left_cell) { + g.hideEditCell(false, false, false, { move : true, cell : left_cell }); + } + }, + + /** + * Move currently Editing Cell to Right + */ + moveRight: function (e) { + e.preventDefault(); + + var $this_field = $(g.currentEditCell); + var field_name = getFieldName($(g.t), $this_field); + + var where_clause = $this_field.parents('tr').first().find('.where_clause').val(); + if (typeof where_clause === 'undefined') { + where_clause = ''; + } + var found = false; + var $found_row; + var j = 0; + $this_field.parents('tr').first().parents('tbody').children().each(function () { + if ($(this).find('.where_clause').val() === where_clause) { + found = true; + $found_row = $(this); + } + }); + + var right_cell; + var cell_found = false; + var next_cell_found = false; + if (found) { + $found_row.children('td.grid_edit').each(function () { + if (getFieldName($(g.t), $(this)) === field_name) { + cell_found = true; + } + if (cell_found) { + if (j >= 1 && ! next_cell_found) { + right_cell = this; + next_cell_found = true; + } else { + j++; + } + } + }); + } + + if (right_cell) { + g.hideEditCell(false, false, false, { move : true, cell : right_cell }); + } + }, + + /** + * Initialize grid editing feature. + */ + initGridEdit: function () { + function startGridEditing (e, cell) { + if (g.isCellEditActive) { + g.saveOrPostEditedCell(); + } else { + g.showEditCell(cell); + } + e.stopPropagation(); + } + + function handleCtrlNavigation (e) { + if ((e.ctrlKey && e.which === 38) || (e.altKey && e.which === 38)) { + g.moveUp(e); + } else if ((e.ctrlKey && e.which === 40) || (e.altKey && e.which === 40)) { + g.moveDown(e); + } else if ((e.ctrlKey && e.which === 37) || (e.altKey && e.which === 37)) { + g.moveLeft(e); + } else if ((e.ctrlKey && e.which === 39) || (e.altKey && e.which === 39)) { + g.moveRight(e); + } + } + + // create cell edit wrapper element + g.cEditStd = document.createElement('div'); + g.cEdit = g.cEditStd; + g.cEditTextarea = document.createElement('div'); + + // adjust g.cEditStd + g.cEditStd.className = 'cEdit'; + $(g.cEditStd).html('
      '); + $(g.cEditStd).hide(); + + // adjust g.cEdit + g.cEditTextarea.className = 'cEdit'; + $(g.cEditTextarea).html('
      '); + $(g.cEditTextarea).hide(); + + // assign cell editing hint + g.cellEditHint = PMA_messages.strCellEditHint; + g.saveCellWarning = PMA_messages.strSaveCellWarning; + g.alertNonUnique = PMA_messages.strAlertNonUnique; + g.gotoLinkText = PMA_messages.strGoToLink; + + // initialize cell editing configuration + g.saveCellsAtOnce = $(g.o).find('.save_cells_at_once').val(); + g.maxTruncatedLen = PMA_commonParams.get('LimitChars'); + + // register events + $(g.t).find('td.data.click1') + .click(function (e) { + startGridEditing(e, this); + // prevent default action when clicking on "link" in a table + if ($(e.target).is('.grid_edit a')) { + e.preventDefault(); + } + }); + + $(g.t).find('td.data.click2') + .click(function (e) { + var $cell = $(this); + // In the case of relational link, We want single click on the link + // to goto the link and double click to start grid-editing. + var $link = $(e.target); + if ($link.is('.grid_edit.relation a')) { + e.preventDefault(); + // get the click count and increase + var clicks = $cell.data('clicks'); + clicks = (typeof clicks === 'undefined') ? 1 : clicks + 1; + + if (clicks === 1) { + // if there are no previous clicks, + // start the single click timer + var timer = setTimeout(function () { + // temporarily remove ajax class so the page loader will not handle it, + // submit and then add it back + $link.removeClass('ajax'); + AJAX.requestHandler.call($link[0]); + $link.addClass('ajax'); + $cell.data('clicks', 0); + }, 700); + $cell.data('clicks', clicks); + $cell.data('timer', timer); + } else { + // this is a double click, cancel the single click timer + // and make the click count 0 + clearTimeout($cell.data('timer')); + $cell.data('clicks', 0); + // start grid-editing + startGridEditing(e, this); + } + } + }) + .dblclick(function (e) { + if ($(e.target).is('.grid_edit a')) { + e.preventDefault(); + } else { + startGridEditing(e, this); + } + }); + + $(g.cEditStd).on('keydown', 'input.edit_box, select', handleCtrlNavigation); + + $(g.cEditStd).find('.edit_box').focus(function () { + g.showEditArea(); + }); + $(g.cEditStd).on('keydown', '.edit_box, select', function (e) { + if (e.which === 13) { + // post on pressing "Enter" + e.preventDefault(); + g.saveOrPostEditedCell(); + } + }); + $(g.cEditStd).keydown(function (e) { + if (!g.isEditCellTextEditable) { + // prevent text editing + e.preventDefault(); + } + }); + + $(g.cEditTextarea).on('keydown', 'textarea.edit_box, select', handleCtrlNavigation); + + $(g.cEditTextarea).find('.edit_box').focus(function () { + g.showEditArea(); + }); + $(g.cEditTextarea).on('keydown', '.edit_box, select', function (e) { + if (e.which === 13 && !e.shiftKey) { + // post on pressing "Enter" + e.preventDefault(); + g.saveOrPostEditedCell(); + } + }); + $(g.cEditTextarea).keydown(function (e) { + if (!g.isEditCellTextEditable) { + // prevent text editing + e.preventDefault(); + } + }); + $('html').click(function (e) { + // hide edit cell if the click is not fromDat edit area + if ($(e.target).parents().index($(g.cEdit)) === -1 && + !$(e.target).parents('.ui-datepicker-header').length && + !$('.browse_foreign_modal.ui-dialog:visible').length && + !$(e.target).closest('.dismissable').length + ) { + g.hideEditCell(); + } + }).keydown(function (e) { + if (e.which === 27 && g.isCellEditActive) { + // cancel on pressing "Esc" + g.hideEditCell(true); + } + }); + $(g.o).find('div.save_edited').click(function () { + g.hideEditCell(); + g.postEditedCell(); + }); + $(window).on('beforeunload', function () { + if (g.isCellEdited) { + return g.saveCellWarning; + } + }); + + // attach to global div + $(g.gDiv).append(g.cEditStd); + $(g.gDiv).append(g.cEditTextarea); + + // add hint for grid editing feature when hovering "Edit" link in each table row + if (PMA_messages.strGridEditFeatureHint !== undefined) { + PMA_tooltip( + $(g.t).find('.edit_row_anchor a'), + 'a', + PMA_messages.strGridEditFeatureHint + ); + } + } + }; + + /** **************** + * Initialize grid + ******************/ + + // wrap all truncated data cells with span indicating the original length + // todo update the original length after a grid edit + $(t).find('td.data.truncated:not(:has(span))') + .wrapInner(function () { + return ''; + }); + + // wrap remaining cells, except actions cell, with span + $(t).find('th, td:not(:has(span))') + .wrapInner(''); + + // create grid elements + g.gDiv = document.createElement('div'); // create global div + + // initialize the table variable + g.t = t; + + // enclosing .sqlqueryresults div + g.o = $(t).parents('.sqlqueryresults'); + + // get data columns in the first row of the table + var $firstRowCols = $(t).find('tr:first th.draggable'); + + // initialize visible headers count + g.visibleHeadersCount = $firstRowCols.filter(':visible').length; + + // assign first column (actions) span + if (! $(t).find('tr:first th:first').hasClass('draggable')) { // action header exist + g.actionSpan = $(t).find('tr:first th:first').prop('colspan'); + } else { + g.actionSpan = 0; + } + + // assign table create time + // table_create_time will only available if we are in "Browse" tab + g.tableCreateTime = $(g.o).find('.table_create_time').val(); + + // assign the hints + g.sortHint = PMA_messages.strSortHint; + g.strMultiSortHint = PMA_messages.strMultiSortHint; + g.markHint = PMA_messages.strColMarkHint; + g.copyHint = PMA_messages.strColNameCopyHint; + + // assign common hidden inputs + var $common_hidden_inputs = $(g.o).find('div.common_hidden_inputs'); + g.server = $common_hidden_inputs.find('input[name=server]').val(); + g.db = $common_hidden_inputs.find('input[name=db]').val(); + g.table = $common_hidden_inputs.find('input[name=table]').val(); + + // add table class + $(t).addClass('pma_table'); + + // add relative position to global div so that resize handlers are correctly positioned + $(g.gDiv).css('position', 'relative'); + + // link the global div + $(t).before(g.gDiv); + $(g.gDiv).append(t); + + // FEATURES + enableResize = enableResize === undefined ? true : enableResize; + enableReorder = enableReorder === undefined ? true : enableReorder; + enableVisib = enableVisib === undefined ? true : enableVisib; + enableGridEdit = enableGridEdit === undefined ? true : enableGridEdit; + if (enableResize) { + g.initColResize(); + } + // disable reordering for result from EXPLAIN or SHOW syntax, which do not have a table navigation panel + if (enableReorder && + $(g.o).find('table.navigation').length > 0) { + g.initColReorder(); + } + if (enableVisib) { + g.initColVisib(); + } + // make sure we have the ajax class + if (enableGridEdit && + $(t).is('.ajax')) { + g.initGridEdit(); + } + + // create tooltip for each with draggable class + PMA_tooltip( + $(t).find('th.draggable'), + 'th', + g.updateHint() + ); + + // register events for hint tooltip (anchors inside draggable th) + $(t).find('th.draggable a') + .mouseenter(function () { + g.showSortHint = true; + g.showMultiSortHint = true; + $(t).find('th.draggable').tooltip('option', { + content: g.updateHint() + }); + }) + .mouseleave(function () { + g.showSortHint = false; + g.showMultiSortHint = false; + $(t).find('th.draggable').tooltip('option', { + content: g.updateHint() + }); + }); + + // register events for dragging-related feature + if (enableResize || enableReorder) { + $(document).mousemove(function (e) { + g.dragMove(e); + }); + $(document).mouseup(function (e) { + $(g.o).removeClass('turnOffSelect'); + g.dragEnd(e); + }); + } + + // some adjustment + $(t).removeClass('data'); + $(g.gDiv).addClass('data'); +} + +/** + * jQuery plugin to cancel selection in HTML code. + */ +(function ($) { + $.fn.noSelect = function (p) { // no select plugin by Paulo P.Marinas + var prevent = (p === null) ? true : p; + var is_msie = navigator.userAgent.indexOf('MSIE') > -1 || !!window.navigator.userAgent.match(/Trident.*rv\:11\./); + var is_firefox = navigator.userAgent.indexOf('Firefox') > -1; + var is_safari = navigator.userAgent.indexOf('Safari') > -1; + var is_opera = navigator.userAgent.indexOf('Presto') > -1; + if (prevent) { + return this.each(function () { + if (is_msie || is_safari) { + $(this).on('selectstart', false); + } else if (is_firefox) { + $(this).css('MozUserSelect', 'none'); + $('body').trigger('focus'); + } else if (is_opera) { + $(this).on('mousedown', false); + } else { + $(this).attr('unselectable', 'on'); + } + }); + } else { + return this.each(function () { + if (is_msie || is_safari) { + $(this).off('selectstart'); + } else if (is_firefox) { + $(this).css('MozUserSelect', 'inherit'); + } else if (is_opera) { + $(this).off('mousedown'); + } else { + $(this).removeAttr('unselectable'); + } + }); + } + }; // end noSelect +}(jQuery)); diff --git a/php/apps/phpmyadmin49/js/menu-resizer.js b/php/apps/phpmyadmin49/js/menu-resizer.js new file mode 100644 index 00000000..d5462fe0 --- /dev/null +++ b/php/apps/phpmyadmin49/js/menu-resizer.js @@ -0,0 +1,222 @@ +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * Handles the resizing of a menu according to the available screen width + * + * Uses themes/original/css/resizable-menu.css.php + * + * To initialise: + * $('#myMenu').menuResizer(function () { + * // This function will be called to find out how much + * // available horizontal space there is for the menu + * return $('body').width() - 5; // Some extra margin for good measure + * }); + * + * To trigger a resize operation: + * $('#myMenu').menuResizer('resize'); // Bind this to $(window).resize() + * + * To restore the menu to a state like before it was initialized: + * $('#myMenu').menuResizer('destroy'); + * + * @package PhpMyAdmin + */ +(function ($) { + function MenuResizer ($container, widthCalculator) { + var self = this; + self.$container = $container; + self.widthCalculator = widthCalculator; + var windowWidth = $(window).width(); + + if (windowWidth < 768) { + $('#pma_navigation_resizer').css({ 'width': '0px' }); + } + // Sets the image for the left and right scroll indicator + $('.scrollindicator--left').html($(PMA_getImage('b_left').toString())); + $('.scrollindicator--right').html($(PMA_getImage('b_right').toString())); + + // Set the width of the navigation bar without scroll indicator + $('.navigationbar').css({ 'width': widthCalculator.call($container) - 60 }); + + // Scroll the navigation bar on click + $('.scrollindicator--right').on('click', function () { + $('.navigationbar').scrollLeft($('.navigationbar').scrollLeft() + 70); + }); + $('.scrollindicator--left').on('click', function () { + $('.navigationbar').scrollLeft($('.navigationbar').scrollLeft() - 70); + }); + + // create submenu container + var link = $('', { href: '#', 'class': 'tab nowrap' }) + .text(PMA_messages.strMore) + .on('click', false); // same as event.preventDefault() + var img = $container.find('li img'); + if (img.length) { + $(PMA_getImage('b_more').toString()).prependTo(link); + } + var $submenu = $('
    • ', { 'class': 'submenu' }) + .append(link) + .append($('
        ')) + .on('mouseenter', function () { + if ($(this).find('ul .tabactive').length === 0) { + $(this) + .addClass('submenuhover') + .find('> a') + .addClass('tabactive'); + } + }) + .on('mouseleave', function () { + if ($(this).find('ul .tabactive').length === 0) { + $(this) + .removeClass('submenuhover') + .find('> a') + .removeClass('tabactive'); + } + }); + $container.children('.clearfloat').remove(); + $container.append($submenu).append('
        '); + setTimeout(function () { + self.resize(); + }, 4); + } + MenuResizer.prototype.resize = function () { + var wmax = this.widthCalculator.call(this.$container); + var windowWidth = $(window).width(); + var $submenu = this.$container.find('.submenu:last'); + var submenu_w = $submenu.outerWidth(true); + var $submenu_ul = $submenu.find('ul'); + var $li = this.$container.find('> li'); + var $li2 = $submenu_ul.find('li'); + var more_shown = $li2.length > 0; + // Calculate the total width used by all the shown tabs + var total_len = more_shown ? submenu_w : 0; + var l = $li.length - 1; + var i; + for (i = 0; i < l; i++) { + total_len += $($li[i]).outerWidth(true); + } + + var hasVScroll = document.body.scrollHeight > document.body.clientHeight; + if (hasVScroll) { + windowWidth += 15; + } + var navigationwidth = wmax; + if (windowWidth < 768) { + wmax = 2000; + } + + // Now hide menu elements that don't fit into the menubar + var hidden = false; // Whether we have hidden any tabs + while (total_len >= wmax && --l >= 0) { // Process the tabs backwards + hidden = true; + var el = $($li[l]); + var el_width = el.outerWidth(true); + el.data('width', el_width); + if (! more_shown) { + total_len -= el_width; + el.prependTo($submenu_ul); + total_len += submenu_w; + more_shown = true; + } else { + total_len -= el_width; + el.prependTo($submenu_ul); + } + } + // If we didn't hide any tabs, then there might be some space to show some + if (! hidden) { + // Show menu elements that do fit into the menubar + for (i = 0, l = $li2.length; i < l; i++) { + total_len += $($li2[i]).data('width'); + // item fits or (it is the last item + // and it would fit if More got removed) + if (total_len < wmax || + (i === $li2.length - 1 && total_len - submenu_w < wmax) + ) { + $($li2[i]).insertBefore($submenu); + } else { + break; + } + } + } + // Show/hide the "More" tab as needed + if (windowWidth < 768) { + $('.navigationbar').css({ 'width': windowWidth - 80 - $('#pma_navigation').width() }); + $submenu.removeClass('shown'); + $('.navigationbar').css({ 'overflow': 'hidden' }); + } else { + $('.navigationbar').css({ 'width': 'auto' }); + $('.navigationbar').css({ 'overflow': 'visible' }); + if ($submenu_ul.find('li').length > 0) { + $submenu.addClass('shown'); + } else { + $submenu.removeClass('shown'); + } + } + if (this.$container.find('> li').length === 1) { + // If there is only the "More" tab left, then we need + // to align the submenu to the left edge of the tab + $submenu_ul.removeClass().addClass('only'); + } else { + // Otherwise we align the submenu to the right edge of the tab + $submenu_ul.removeClass().addClass('notonly'); + } + if ($submenu.find('.tabactive').length) { + $submenu + .addClass('active') + .find('> a') + .removeClass('tab') + .addClass('tabactive'); + } else { + $submenu + .removeClass('active') + .find('> a') + .addClass('tab') + .removeClass('tabactive'); + } + }; + MenuResizer.prototype.destroy = function () { + var $submenu = this.$container.find('li.submenu').removeData(); + $submenu.find('li').appendTo(this.$container); + $submenu.remove(); + }; + + /** Public API */ + var methods = { + init: function (widthCalculator) { + return this.each(function () { + var $this = $(this); + if (! $this.data('menuResizer')) { + $this.data( + 'menuResizer', + new MenuResizer($this, widthCalculator) + ); + } + }); + }, + resize: function () { + return this.each(function () { + var self = $(this).data('menuResizer'); + if (self) { + self.resize(); + } + }); + }, + destroy: function () { + return this.each(function () { + var self = $(this).data('menuResizer'); + if (self) { + self.destroy(); + } + }); + } + }; + + /** Extend jQuery */ + $.fn.menuResizer = function (method) { + if (methods[method]) { + return methods[method].call(this); + } else if (typeof method === 'function') { + return methods.init.apply(this, [method]); + } else { + $.error('Method ' + method + ' does not exist on jQuery.menuResizer'); + } + }; +}(jQuery)); diff --git a/php/apps/phpmyadmin49/js/messages.php b/php/apps/phpmyadmin49/js/messages.php new file mode 100644 index 00000000..f5b81675 --- /dev/null +++ b/php/apps/phpmyadmin49/js/messages.php @@ -0,0 +1,995 @@ +start(); +if (!defined('TESTSUITE')) { + register_shutdown_function( + function () { + echo PhpMyAdmin\OutputBuffering::getInstance()->getContents(); + } + ); +} + +/* For confirmations */ +$js_messages['strConfirm'] = __('Confirm'); +$js_messages['strDoYouReally'] = __('Do you really want to execute "%s"?'); +$js_messages['strDropDatabaseStrongWarning'] + = __('You are about to DESTROY a complete database!'); +$js_messages['strDatabaseRenameToSameName'] + = __('Cannot rename database to the same name. Change the name and try again'); +$js_messages['strDropTableStrongWarning'] + = __('You are about to DESTROY a complete table!'); +$js_messages['strTruncateTableStrongWarning'] + = __('You are about to TRUNCATE a complete table!'); +$js_messages['strDeleteTrackingData'] = __('Delete tracking data for this table?'); +$js_messages['strDeleteTrackingDataMultiple'] + = __('Delete tracking data for these tables?'); +$js_messages['strDeleteTrackingVersion'] + = __('Delete tracking data for this version?'); +$js_messages['strDeleteTrackingVersionMultiple'] + = __('Delete tracking data for these versions?'); +$js_messages['strDeletingTrackingEntry'] = __('Delete entry from tracking report?'); +$js_messages['strDeletingTrackingData'] = __('Deleting tracking data'); +$js_messages['strDroppingPrimaryKeyIndex'] = __('Dropping Primary Key/Index'); +$js_messages['strDroppingForeignKey'] = __('Dropping Foreign key.'); +$js_messages['strOperationTakesLongTime'] + = __('This operation could take a long time. Proceed anyway?'); +$js_messages['strDropUserGroupWarning'] + = __('Do you really want to delete user group "%s"?'); +$js_messages['strConfirmDeleteQBESearch'] + = __('Do you really want to delete the search "%s"?'); +$js_messages['strConfirmNavigation'] + = __('You have unsaved changes; are you sure you want to leave this page?'); +$js_messages['strConfirmRowChange'] + = __('You are trying to reduce the number of rows, but have already entered data in those rows which will be lost. Do you wish to continue?'); +$js_messages['strDropUserWarning'] + = __('Do you really want to revoke the selected user(s) ?'); +$js_messages['strDeleteCentralColumnWarning'] + = __('Do you really want to delete this central column?'); +$js_messages['strDropRTEitems'] + = __('Do you really want to delete the selected items?'); +$js_messages['strDropPartitionWarning'] = __( + 'Do you really want to DROP the selected partition(s)? This will also DELETE ' . + 'the data related to the selected partition(s)!' +); +$js_messages['strTruncatePartitionWarning'] + = __('Do you really want to TRUNCATE the selected partition(s)?'); +$js_messages['strRemovePartitioningWarning'] + = __('Do you really want to remove partitioning?'); +$js_messages['strResetSlaveWarning'] = __('Do you really want to RESET SLAVE?'); +$js_messages['strChangeColumnCollation'] = __( + 'This operation will attempt to convert your data to the new collation. In ' + . 'rare cases, especially where a character doesn\'t exist in the new ' + . 'collation, this process could cause the data to appear incorrectly under ' + . 'the new collation; in this case we suggest you revert to the original ' + . 'collation and refer to the tips at ' +) + . '
        ' . __('Garbled Data') . '.' + . '

        ' + . __('Are you sure you wish to change the collation and convert the data?'); +$js_messages['strChangeAllColumnCollationsWarning'] = __( + 'Through this operation, MySQL attempts to map the data values between ' + . 'collations. If the character sets are incompatible, there may be data loss ' + . 'and this lost data may NOT be recoverable simply by changing back the ' + . 'column collation(s). To convert existing data, it is suggested to use the ' + . 'column(s) editing feature (the "Change" Link) on the table structure page. ' + . '' +) +. '

        ' +. __( + 'Are you sure you wish to change all the column collations and convert the data?' +); + +/* For modal dialog buttons */ +$js_messages['strSaveAndClose'] = __('Save & close'); +$js_messages['strReset'] = __('Reset'); +$js_messages['strResetAll'] = __('Reset all'); + +/* For indexes */ +$js_messages['strFormEmpty'] = __('Missing value in the form!'); +$js_messages['strRadioUnchecked'] = __('Select at least one of the options!'); +$js_messages['strEnterValidNumber'] = __('Please enter a valid number!'); +$js_messages['strEnterValidLength'] = __('Please enter a valid length!'); +$js_messages['strAddIndex'] = __('Add index'); +$js_messages['strEditIndex'] = __('Edit index'); +$js_messages['strAddToIndex'] = __('Add %s column(s) to index'); +$js_messages['strCreateSingleColumnIndex'] = __('Create single-column index'); +$js_messages['strCreateCompositeIndex'] = __('Create composite index'); +$js_messages['strCompositeWith'] = __('Composite with:'); +$js_messages['strMissingColumn'] = __('Please select column(s) for the index.'); + +/* For Preview SQL*/ +$js_messages['strPreviewSQL'] = __('Preview SQL'); + +/* For Simulate DML*/ +$js_messages['strSimulateDML'] = __('Simulate query'); +$js_messages['strMatchedRows'] = __('Matched rows:'); +$js_messages['strSQLQuery'] = __('SQL query:'); + +/* Charts */ +/* l10n: Default label for the y-Axis of Charts */ +$js_messages['strYValues'] = __('Y values'); + +/* Database multi-table query */ +$js_messages['strEmptyQuery'] = __('Please enter the SQL query first.'); + +/* For server_privileges.js */ +$js_messages['strHostEmpty'] = __('The host name is empty!'); +$js_messages['strUserEmpty'] = __('The user name is empty!'); +$js_messages['strPasswordEmpty'] = __('The password is empty!'); +$js_messages['strPasswordNotSame'] = __('The passwords aren\'t the same!'); +$js_messages['strRemovingSelectedUsers'] = __('Removing Selected Users'); +$js_messages['strClose'] = __('Close'); + +/* For export.js */ +$js_messages['strTemplateCreated'] = __('Template was created.'); +$js_messages['strTemplateLoaded'] = __('Template was loaded.'); +$js_messages['strTemplateUpdated'] = __('Template was updated.'); +$js_messages['strTemplateDeleted'] = __('Template was deleted.'); + +/* l10n: Other, small valued, queries */ +$js_messages['strOther'] = __('Other'); +/* l10n: Thousands separator */ +$js_messages['strThousandsSeparator'] = __(','); +/* l10n: Decimal separator */ +$js_messages['strDecimalSeparator'] = __('.'); + +$js_messages['strChartConnectionsTitle'] = __('Connections / Processes'); + +/* server status monitor */ +$js_messages['strIncompatibleMonitorConfig'] + = __('Local monitor configuration incompatible!'); +$js_messages['strIncompatibleMonitorConfigDescription'] = __( + 'The chart arrangement configuration in your browsers local storage is not ' + . 'compatible anymore to the newer version of the monitor dialog. It is very ' + . 'likely that your current configuration will not work anymore. Please reset ' + . 'your configuration to default in the Settings menu.' +); + +$js_messages['strQueryCacheEfficiency'] = __('Query cache efficiency'); +$js_messages['strQueryCacheUsage'] = __('Query cache usage'); +$js_messages['strQueryCacheUsed'] = __('Query cache used'); + +$js_messages['strSystemCPUUsage'] = __('System CPU usage'); +$js_messages['strSystemMemory'] = __('System memory'); +$js_messages['strSystemSwap'] = __('System swap'); + +$js_messages['strAverageLoad'] = __('Average load'); +$js_messages['strTotalMemory'] = __('Total memory'); +$js_messages['strCachedMemory'] = __('Cached memory'); +$js_messages['strBufferedMemory'] = __('Buffered memory'); +$js_messages['strFreeMemory'] = __('Free memory'); +$js_messages['strUsedMemory'] = __('Used memory'); + +$js_messages['strTotalSwap'] = __('Total swap'); +$js_messages['strCachedSwap'] = __('Cached swap'); +$js_messages['strUsedSwap'] = __('Used swap'); +$js_messages['strFreeSwap'] = __('Free swap'); + +$js_messages['strBytesSent'] = __('Bytes sent'); +$js_messages['strBytesReceived'] = __('Bytes received'); +$js_messages['strConnections'] = __('Connections'); +$js_messages['strProcesses'] = __('Processes'); + +/* summary row */ +$js_messages['strB'] = __('B'); +$js_messages['strKiB'] = __('KiB'); +$js_messages['strMiB'] = __('MiB'); +$js_messages['strGiB'] = __('GiB'); +$js_messages['strTiB'] = __('TiB'); +$js_messages['strPiB'] = __('PiB'); +$js_messages['strEiB'] = __('EiB'); +$js_messages['strNTables'] = __('%d table(s)'); + +/* l10n: Questions is the name of a MySQL Status variable */ +$js_messages['strQuestions'] = __('Questions'); +$js_messages['strTraffic'] = __('Traffic'); +$js_messages['strSettings'] = __('Settings'); +$js_messages['strAddChart'] = __('Add chart to grid'); +$js_messages['strClose'] = __('Close'); +$js_messages['strAddOneSeriesWarning'] + = __('Please add at least one variable to the series!'); +$js_messages['strNone'] = __('None'); +$js_messages['strResumeMonitor'] = __('Resume monitor'); +$js_messages['strPauseMonitor'] = __('Pause monitor'); +$js_messages['strStartRefresh'] = __('Start auto refresh'); +$js_messages['strStopRefresh'] = __('Stop auto refresh'); +/* Monitor: Instructions Dialog */ +$js_messages['strBothLogOn'] = __('general_log and slow_query_log are enabled.'); +$js_messages['strGenLogOn'] = __('general_log is enabled.'); +$js_messages['strSlowLogOn'] = __('slow_query_log is enabled.'); +$js_messages['strBothLogOff'] = __('slow_query_log and general_log are disabled.'); +$js_messages['strLogOutNotTable'] = __('log_output is not set to TABLE.'); +$js_messages['strLogOutIsTable'] = __('log_output is set to TABLE.'); +$js_messages['strSmallerLongQueryTimeAdvice'] = __( + 'slow_query_log is enabled, but the server logs only queries that take longer ' + . 'than %d seconds. It is advisable to set this long_query_time 0-2 seconds, ' + . 'depending on your system.' +); +$js_messages['strLongQueryTimeSet'] = __('long_query_time is set to %d second(s).'); +$js_messages['strSettingsAppliedGlobal'] = __( + 'Following settings will be applied globally and reset to default on server ' + . 'restart:' +); +/* l10n: %s is FILE or TABLE */ +$js_messages['strSetLogOutput'] = __('Set log_output to %s'); +/* l10n: Enable in this context means setting a status variable to ON */ +$js_messages['strEnableVar'] = __('Enable %s'); +/* l10n: Disable in this context means setting a status variable to OFF */ +$js_messages['strDisableVar'] = __('Disable %s'); +/* l10n: %d seconds */ +$js_messages['setSetLongQueryTime'] = __('Set long_query_time to %d seconds.'); +$js_messages['strNoSuperUser'] = __( + 'You can\'t change these variables. Please log in as root or contact' + . ' your database administrator.' +); +$js_messages['strChangeSettings'] = __('Change settings'); +$js_messages['strCurrentSettings'] = __('Current settings'); + +$js_messages['strChartTitle'] = __('Chart title'); +/* l10n: As in differential values */ +$js_messages['strDifferential'] = __('Differential'); +$js_messages['strDividedBy'] = __('Divided by %s'); +$js_messages['strUnit'] = __('Unit'); + +$js_messages['strFromSlowLog'] = __('From slow log'); +$js_messages['strFromGeneralLog'] = __('From general log'); +$js_messages['strServerLogError'] = __( + 'The database name is not known for this query in the server\'s logs.' +); +$js_messages['strAnalysingLogsTitle'] = __('Analysing logs'); +$js_messages['strAnalysingLogs'] + = __('Analysing & loading logs. This may take a while.'); +$js_messages['strCancelRequest'] = __('Cancel request'); +$js_messages['strCountColumnExplanation'] = __( + 'This column shows the amount of identical queries that are grouped together. ' + . 'However only the SQL query itself has been used as a grouping criteria, so ' + . 'the other attributes of queries, such as start time, may differ.' +); +$js_messages['strMoreCountColumnExplanation'] = __( + 'Since grouping of INSERTs queries has been selected, INSERT queries into the ' + . 'same table are also being grouped together, disregarding of the inserted ' + . 'data.' +); +$js_messages['strLogDataLoaded'] + = __('Log data loaded. Queries executed in this time span:'); + +$js_messages['strJumpToTable'] = __('Jump to Log table'); +$js_messages['strNoDataFoundTitle'] = __('No data found'); +$js_messages['strNoDataFound'] + = __('Log analysed, but no data found in this time span.'); + +$js_messages['strAnalyzing'] = __('Analyzing…'); +$js_messages['strExplainOutput'] = __('Explain output'); +$js_messages['strStatus'] = __('Status'); +$js_messages['strTime'] = __('Time'); +$js_messages['strTotalTime'] = __('Total time:'); +$js_messages['strProfilingResults'] = __('Profiling results'); +$js_messages['strTable'] = _pgettext('Display format', 'Table'); +$js_messages['strChart'] = __('Chart'); + +$js_messages['strAliasDatabase'] = _pgettext('Alias', 'Database'); +$js_messages['strAliasTable'] = _pgettext('Alias', 'Table'); +$js_messages['strAliasColumn'] = _pgettext('Alias', 'Column'); + +/* l10n: A collection of available filters */ +$js_messages['strFiltersForLogTable'] = __('Log table filter options'); +/* l10n: Filter as in "Start Filtering" */ +$js_messages['strFilter'] = __('Filter'); +$js_messages['strFilterByWordRegexp'] = __('Filter queries by word/regexp:'); +$js_messages['strIgnoreWhereAndGroup'] + = __('Group queries, ignoring variable data in WHERE clauses'); +$js_messages['strSumRows'] = __('Sum of grouped rows:'); +$js_messages['strTotal'] = __('Total:'); + +$js_messages['strLoadingLogs'] = __('Loading logs'); +$js_messages['strRefreshFailed'] = __('Monitor refresh failed'); +$js_messages['strInvalidResponseExplanation'] = __( + 'While requesting new chart data the server returned an invalid response. This ' + . 'is most likely because your session expired. Reloading the page and ' + . 'reentering your credentials should help.' +); +$js_messages['strReloadPage'] = __('Reload page'); + +$js_messages['strAffectedRows'] = __('Affected rows:'); + +$js_messages['strFailedParsingConfig'] = __( + 'Failed parsing config file. It doesn\'t seem to be valid JSON code.' +); +$js_messages['strFailedBuildingGrid'] = __( + 'Failed building chart grid with imported config. Resetting to default config…' +); +$js_messages['strImport'] = __('Import'); +$js_messages['strImportDialogTitle'] = __('Import monitor configuration'); +$js_messages['strImportDialogMessage'] + = __('Please select the file you want to import.'); +$js_messages['strNoImportFile'] = __('No files available on server for import!'); + +$js_messages['strAnalyzeQuery'] = __('Analyse query'); + +/* Server status advisor */ + +$js_messages['strAdvisorSystem'] = __('Advisor system'); +$js_messages['strPerformanceIssues'] = __('Possible performance issues'); +$js_messages['strIssuse'] = __('Issue'); +$js_messages['strRecommendation'] = __('Recommendation'); +$js_messages['strRuleDetails'] = __('Rule details'); +$js_messages['strJustification'] = __('Justification'); +$js_messages['strFormula'] = __('Used variable / formula'); +$js_messages['strTest'] = __('Test'); + +/* For query editor */ +$js_messages['strFormatting'] = __('Formatting SQL…'); +$js_messages['strNoParam'] = __('No parameters found!'); + +/* For inline query editing */ +$js_messages['strGo'] = __('Go'); +$js_messages['strCancel'] = __('Cancel'); + +/* For page-related settings */ +$js_messages['strPageSettings'] = __('Page-related settings'); +$js_messages['strApply'] = __('Apply'); + +/* For Ajax Notifications */ +$js_messages['strLoading'] = __('Loading…'); +$js_messages['strAbortedRequest'] = __('Request aborted!!'); +$js_messages['strProcessingRequest'] = __('Processing request'); +$js_messages['strRequestFailed'] = __('Request failed!!'); +$js_messages['strErrorProcessingRequest'] = __('Error in processing request'); +$js_messages['strErrorCode'] = __('Error code: %s'); +$js_messages['strErrorText'] = __('Error text: %s'); +$js_messages['strErrorConnection'] = __( + 'It seems that the connection to server has been lost. Please check your ' . + 'network connectivity and server status.' +); +$js_messages['strNoDatabasesSelected'] = __('No databases selected.'); +$js_messages['strNoAccountSelected'] = __('No accounts selected.'); +$js_messages['strDroppingColumn'] = __('Dropping column'); +$js_messages['strAddingPrimaryKey'] = __('Adding primary key'); +$js_messages['strOK'] = __('OK'); +$js_messages['strDismiss'] = __('Click to dismiss this notification'); + +/* For db_operations.js */ +$js_messages['strRenamingDatabases'] = __('Renaming databases'); +$js_messages['strCopyingDatabase'] = __('Copying database'); +$js_messages['strChangingCharset'] = __('Changing charset'); +$js_messages['strNo'] = __('No'); + +/* For Foreign key checks */ +$js_messages['strForeignKeyCheck'] = __('Enable foreign key checks'); + +/* For db_stucture.js */ +$js_messages['strErrorRealRowCount'] = __('Failed to get real row count.'); + +/* For db_search.js */ +$js_messages['strSearching'] = __('Searching'); +$js_messages['strHideSearchResults'] = __('Hide search results'); +$js_messages['strShowSearchResults'] = __('Show search results'); +$js_messages['strBrowsing'] = __('Browsing'); +$js_messages['strDeleting'] = __('Deleting'); +$js_messages['strConfirmDeleteResults'] = __('Delete the matches for the %s table?'); + +/* For db_routines.js */ +$js_messages['MissingReturn'] + = __('The definition of a stored function must contain a RETURN statement!'); +$js_messages['strExport'] = __('Export'); +$js_messages['NoExportable'] + = __('No routine is exportable. Required privileges may be lacking.'); + +/* For ENUM/SET editor*/ +$js_messages['enum_editor'] = __('ENUM/SET editor'); +$js_messages['enum_columnVals'] =__('Values for column %s'); +$js_messages['enum_newColumnVals'] = __('Values for a new column'); +$js_messages['enum_hint'] =__('Enter each value in a separate field.'); +$js_messages['enum_addValue'] =__('Add %d value(s)'); + +/* For import.js */ +$js_messages['strImportCSV'] = __( + 'Note: If the file contains multiple tables, they will be combined into one.' +); + +/* For sql.js */ +$js_messages['strHideQueryBox'] = __('Hide query box'); +$js_messages['strShowQueryBox'] = __('Show query box'); +$js_messages['strEdit'] = __('Edit'); +$js_messages['strDelete'] = __('Delete'); +$js_messages['strNotValidRowNumber'] = __('%d is not valid row number.'); +$js_messages['strBrowseForeignValues'] = __('Browse foreign values'); +$js_messages['strNoAutoSavedQuery'] = __('No auto-saved query'); +$js_messages['strBookmarkVariable'] = __('Variable %d:'); + +/* For Central list of columns */ +$js_messages['pickColumn'] = __('Pick'); +$js_messages['pickColumnTitle'] = __('Column selector'); +$js_messages['searchList'] = __('Search this list'); +$js_messages['strEmptyCentralList'] = __( + 'No columns in the central list. Make sure the Central columns list for ' + . 'database %s has columns that are not present in the current table.' +); +$js_messages['seeMore'] = __('See more'); +$js_messages['confirmTitle'] = __('Are you sure?'); +$js_messages['makeConsistentMessage'] = __( + 'This action may change some of the columns definition.
        Are you sure you ' + . 'want to continue?' +); +$js_messages['strContinue'] = __('Continue'); + +/** For normalization */ +$js_messages['strAddPrimaryKey'] = __('Add primary key'); +$js_messages['strPrimaryKeyAdded'] = __('Primary key added.'); +$js_messages['strToNextStep'] = __('Taking you to next step…'); +$js_messages['strFinishMsg'] + = __("The first step of normalization is complete for table '%s'."); +$js_messages['strEndStep'] = __("End of step"); +$js_messages['str2NFNormalization'] = __('Second step of normalization (2NF)'); +$js_messages['strDone'] = __('Done'); +$js_messages['strConfirmPd'] = __('Confirm partial dependencies'); +$js_messages['strSelectedPd'] = __('Selected partial dependencies are as follows:'); +$js_messages['strPdHintNote'] = __( + 'Note: a, b -> d,f implies values of columns a and b combined together can ' + . 'determine values of column d and column f.' +); +$js_messages['strNoPdSelected'] = __('No partial dependencies selected!'); +$js_messages['strBack'] = __('Back'); +$js_messages['strShowPossiblePd'] + = __('Show me the possible partial dependencies based on data in the table'); +$js_messages['strHidePd'] = __('Hide partial dependencies list'); +$js_messages['strWaitForPd'] = __( + 'Sit tight! It may take few seconds depending on data size and column count of ' + . 'the table.' +); +$js_messages['strStep'] = __('Step'); +$js_messages['strMoveRepeatingGroup'] + = '
          ' . __('The following actions will be performed:') . '' + . '
        1. ' . __('DROP columns %s from the table %s') . '
        2. ' + . '
        3. ' . __('Create the following table') . '
        4. '; +$js_messages['strNewTablePlaceholder'] = 'Enter new table name'; +$js_messages['strNewColumnPlaceholder'] = 'Enter column name'; +$js_messages['str3NFNormalization'] = __('Third step of normalization (3NF)'); +$js_messages['strConfirmTd'] = __('Confirm transitive dependencies'); +$js_messages['strSelectedTd'] = __('Selected dependencies are as follows:'); +$js_messages['strNoTdSelected'] = __('No dependencies selected!'); + +/* For server_variables.js */ +$js_messages['strSave'] = __('Save'); + +/* For tbl_select.js */ +$js_messages['strHideSearchCriteria'] = __('Hide search criteria'); +$js_messages['strShowSearchCriteria'] = __('Show search criteria'); +$js_messages['strRangeSearch'] = __('Range search'); +$js_messages['strColumnMax'] = __('Column maximum:'); +$js_messages['strColumnMin'] = __('Column minimum:'); +$js_messages['strMinValue'] = __('Minimum value:'); +$js_messages['strMaxValue'] = __('Maximum value:'); + +/* For tbl_find_replace.js */ +$js_messages['strHideFindNReplaceCriteria'] = __('Hide find and replace criteria'); +$js_messages['strShowFindNReplaceCriteria'] = __('Show find and replace criteria'); + +/* For tbl_zoom_plot_jqplot.js */ +$js_messages['strDisplayHelp'] = '
          • ' + . __('Each point represents a data row.') + . '
          • ' + . __('Hovering over a point will show its label.') + . '
          • ' + . __('To zoom in, select a section of the plot with the mouse.') + . '
          • ' + . __('Click reset zoom button to come back to original state.') + . '
          • ' + . __('Click a data point to view and possibly edit the data row.') + . '
          • ' + . __('The plot can be resized by dragging it along the bottom right corner.') + . '
          '; +$js_messages['strHelpTitle'] = 'Zoom search instructions'; +$js_messages['strInputNull'] = '' . __('Select two columns') . ''; +$js_messages['strSameInputs'] = '' + . __('Select two different columns') + . ''; +$js_messages['strDataPointContent'] = __('Data point content'); + +/* For tbl_change.js */ +$js_messages['strIgnore'] = __('Ignore'); +$js_messages['strCopy'] = __('Copy'); +$js_messages['strX'] = __('X'); +$js_messages['strY'] = __('Y'); +$js_messages['strPoint'] = __('Point'); +$js_messages['strPointN'] = __('Point %d'); +$js_messages['strLineString'] = __('Linestring'); +$js_messages['strPolygon'] = __('Polygon'); +$js_messages['strGeometry'] = __('Geometry'); +$js_messages['strInnerRing'] = __('Inner ring'); +$js_messages['strOuterRing'] = __('Outer ring'); +$js_messages['strAddPoint'] = __('Add a point'); +$js_messages['strAddInnerRing'] = __('Add an inner ring'); +$js_messages['strYes'] = __('Yes'); +$js_messages['strCopyEncryptionKey'] = __('Do you want to copy encryption key?'); +$js_messages['strEncryptionKey'] = __('Encryption key'); + +/* For Tip to be shown on Time field */ +$js_messages['strMysqlAllowedValuesTipTime'] = __( + 'MySQL accepts additional values not selectable by the slider;' + . ' key in those values directly if desired' +); + +/* For Tip to be shown on Date field */ +$js_messages['strMysqlAllowedValuesTipDate'] = __( + 'MySQL accepts additional values not selectable by the datepicker;' + . ' key in those values directly if desired' +); + +/* For Lock symbol Tooltip */ +$js_messages['strLockToolTip'] = __( + 'Indicates that you have made changes to this page;' + . ' you will be prompted for confirmation before abandoning changes' +); + +/* Designer (js/designer/move.js) */ +$js_messages['strSelectReferencedKey'] = __('Select referenced key'); +$js_messages['strSelectForeignKey'] = __('Select Foreign Key'); +$js_messages['strPleaseSelectPrimaryOrUniqueKey'] + = __('Please select the primary key or a unique key!'); +$js_messages['strChangeDisplay'] = __('Choose column to display'); +$js_messages['strLeavingDesigner'] = __( + 'You haven\'t saved the changes in the layout. They will be lost if you' + . ' don\'t save them. Do you want to continue?' +); +$js_messages['strQueryEmpty'] = __('value/subQuery is empty'); +$js_messages['strAddTables'] = __('Add tables from other databases'); +$js_messages['strPageName'] = __('Page name'); +$js_messages['strSavePage'] = __('Save page'); +$js_messages['strSavePageAs'] = __('Save page as'); +$js_messages['strOpenPage'] = __('Open page'); +$js_messages['strDeletePage'] = __('Delete page'); +$js_messages['strUntitled'] = __('Untitled'); +$js_messages['strSelectPage'] = __('Please select a page to continue'); +$js_messages['strEnterValidPageName'] = __('Please enter a valid page name'); +$js_messages['strLeavingPage'] + = __('Do you want to save the changes to the current page?'); +$js_messages['strSuccessfulPageDelete'] = __('Successfully deleted the page'); +$js_messages['strExportRelationalSchema'] = __('Export relational schema'); +$js_messages['strModificationSaved'] = __('Modifications have been saved'); + +/* Visual query builder (js/designer/move.js) */ +$js_messages['strAddOption'] = __('Add an option for column "%s".'); +$js_messages['strObjectsCreated'] = __('%d object(s) created.'); +$js_messages['strSubmit'] = __('Submit'); + +/* For makegrid.js (column reordering, show/hide column, grid editing) */ +$js_messages['strCellEditHint'] = __('Press escape to cancel editing.'); +$js_messages['strSaveCellWarning'] = __( + 'You have edited some data and they have not been saved. Are you sure you want ' + . 'to leave this page before saving the data?' +); +$js_messages['strColOrderHint'] = __('Drag to reorder.'); +$js_messages['strSortHint'] = __('Click to sort results by this column.'); +$js_messages['strMultiSortHint'] = __( + 'Shift+Click to add this column to ORDER BY clause or to toggle ASC/DESC.' + . '
          - Ctrl+Click or Alt+Click (Mac: Shift+Option+Click) to remove column ' + . 'from ORDER BY clause' +); +$js_messages['strColMarkHint'] = __('Click to mark/unmark.'); +$js_messages['strColNameCopyHint'] = __('Double-click to copy column name.'); +$js_messages['strColVisibHint'] = __( + 'Click the drop-down arrow
          to toggle column\'s visibility.' +); +$js_messages['strShowAllCol'] = __('Show all'); +$js_messages['strAlertNonUnique'] = __( + 'This table does not contain a unique column. Features related to the grid ' + . 'edit, checkbox, Edit, Copy and Delete links may not work after saving.' +); +$js_messages['strEnterValidHex'] + = __('Please enter a valid hexadecimal string. Valid characters are 0-9, A-F.'); +$js_messages['strShowAllRowsWarning'] = __( + 'Do you really want to see all of the rows? For a big table this could crash ' + . 'the browser.' +); +$js_messages['strOriginalLength'] = __('Original length'); + +/** Drag & Drop sql import messages */ +$js_messages['dropImportMessageCancel'] = __('cancel'); +$js_messages['dropImportMessageAborted'] = __('Aborted'); +$js_messages['dropImportMessageFailed'] = __('Failed'); +$js_messages['dropImportMessageSuccess'] = __('Success'); +$js_messages['dropImportImportResultHeader'] = __('Import status'); +$js_messages['dropImportDropFiles'] = __('Drop files here'); +$js_messages['dropImportSelectDB'] = __('Select database first'); + +/* For Print view */ +$js_messages['print'] = __('Print'); +$js_messages['back'] = __('Back'); + +// this approach does not work when the parameter is changed via user prefs +switch ($GLOBALS['cfg']['GridEditing']) { +case 'double-click': + $js_messages['strGridEditFeatureHint'] = __( + 'You can also edit most values
          by double-clicking directly on them.' + ); + break; +case 'click': + $js_messages['strGridEditFeatureHint'] = __( + 'You can also edit most values
          by clicking directly on them.' + ); + break; +default: + break; +} +$js_messages['strGoToLink'] = __('Go to link:'); +$js_messages['strColNameCopyTitle'] = __('Copy column name.'); +$js_messages['strColNameCopyText'] + = __('Right-click the column name to copy it to your clipboard.'); + +/* password generation */ +$js_messages['strGeneratePassword'] = __('Generate password'); +$js_messages['strGenerate'] = __('Generate'); +$js_messages['strChangePassword'] = __('Change password'); + +/* navigation tabs */ +$js_messages['strMore'] = __('More'); + +/* navigation panel */ +$js_messages['strShowPanel'] = __('Show panel'); +$js_messages['strHidePanel'] = __('Hide panel'); +$js_messages['strUnhideNavItem'] = __('Show hidden navigation tree items.'); +$js_messages['linkWithMain'] = __('Link with main panel'); +$js_messages['unlinkWithMain'] = __('Unlink from main panel'); + +/* microhistory */ +$js_messages['strInvalidPage'] + = __('The requested page was not found in the history, it may have expired.'); + +/* update */ +$js_messages['strNewerVersion'] = __( + 'A newer version of phpMyAdmin is available and you should consider upgrading. ' + . 'The newest version is %s, released on %s.' +); +/* l10n: Latest available phpMyAdmin version */ +$js_messages['strLatestAvailable'] = __(', latest stable version:'); +$js_messages['strUpToDate'] = __('up to date'); + +$js_messages['strCreateView'] = __('Create view'); + +/* Error Reporting */ +$js_messages['strSendErrorReport'] = __("Send error report"); +$js_messages['strSubmitErrorReport'] = __("Submit error report"); +$js_messages['strErrorOccurred'] = __( + "A fatal JavaScript error has occurred. Would you like to send an error report?" +); +$js_messages['strChangeReportSettings'] = __("Change report settings"); +$js_messages['strShowReportDetails'] = __("Show report details"); +$js_messages['strIgnore'] = __("Ignore"); +$js_messages['strTimeOutError'] = __( + "Your export is incomplete, due to a low execution time limit at the PHP level!" +); + +$js_messages['strTooManyInputs'] = __( + "Warning: a form on this page has more than %d fields. On submission, " + . "some of the fields might be ignored, due to PHP's " + . "max_input_vars configuration." +); + +$js_messages['phpErrorsFound'] = '
          ' + . __('Some errors have been detected on the server!') + . '
          ' + . __('Please look at the bottom of this window.') + . '
          ' + . '' + . '' + . '
          '; + +$js_messages['phpErrorsBeingSubmitted'] = '
          ' + . __('Some errors have been detected on the server!') + . '
          ' + . __( + 'As per your settings, they are being submitted currently, please be ' + . 'patient.' + ) + . '
          ' + . 'ajax clock' + . '
          '; + +// For console +$js_messages['strConsoleRequeryConfirm'] = __('Execute this query again?'); +$js_messages['strConsoleDeleteBookmarkConfirm'] + = __('Do you really want to delete this bookmark?'); +$js_messages['strConsoleDebugError'] + = __('Some error occurred while getting SQL debug info.'); +$js_messages['strConsoleDebugSummary'] + = __('%s queries executed %s times in %s seconds.'); +$js_messages['strConsoleDebugArgsSummary'] = __('%s argument(s) passed'); +$js_messages['strConsoleDebugShowArgs'] = __('Show arguments'); +$js_messages['strConsoleDebugHideArgs'] = __('Hide arguments'); +$js_messages['strConsoleDebugTimeTaken'] = __('Time taken:'); +$js_messages['strNoLocalStorage'] = __('There was a problem accessing your browser storage, some features may not work properly for you. It is likely that the browser doesn\'t support storage or the quota limit has been reached. In Firefox, corrupted storage can also cause such a problem, clearing your "Offline Website Data" might help. In Safari, such problem is commonly caused by "Private Mode Browsing".'); +// For modals in db_structure.php +$js_messages['strCopyTablesTo'] = __('Copy tables to'); +$js_messages['strAddPrefix'] = __('Add table prefix'); +$js_messages['strReplacePrefix'] = __('Replace table with prefix'); +$js_messages['strCopyPrefix'] = __('Copy table with prefix'); + +/* For password strength simulation */ +$js_messages['strExtrWeak'] = __('Extremely weak'); +$js_messages['strVeryWeak'] = __('Very weak'); +$js_messages['strWeak'] = __('Weak'); +$js_messages['strGood'] = __('Good'); +$js_messages['strStrong'] = __('Strong'); + +/* U2F errors */ +$js_messages['strU2FTimeout'] = __('Timed out waiting for security key activation.'); +$js_messages['strU2FError'] = __('Failed security key activation (%s).'); + +/* Designer */ +$js_messages['strTableAlreadyExists'] = _pgettext('The table already exists in the designer and can not be added once more.', 'Table %s already exists!'); +$js_messages['strHide'] = __('Hide'); +$js_messages['strStructure'] = __('Structure'); + +echo "var PMA_messages = new Array();\n"; +foreach ($js_messages as $name => $js_message) { + Sanitize::printJsValue("PMA_messages['" . $name . "']", $js_message); +} + +/* Calendar */ +echo "var themeCalendarImage = '" , $GLOBALS['pmaThemeImage'] + , 'b_calendar.png' , "';\n"; + +/* Image path */ +echo "var pmaThemeImage = '" , $GLOBALS['pmaThemeImage'] , "';\n"; + +echo "var mysql_doc_template = '" , PhpMyAdmin\Util::getMySQLDocuURL('%s') + , "';\n"; + +//Max input vars allowed by PHP. +$maxInputVars = ini_get('max_input_vars'); +echo 'var maxInputVars = ' + , (false === $maxInputVars || '' == $maxInputVars ? 'false' : (int)$maxInputVars) + , ';' . "\n"; + +echo "if ($.datepicker) {\n"; +/* l10n: Display text for calendar close link */ +Sanitize::printJsValue("$.datepicker.regional['']['closeText']", __('Done')); +/* l10n: Display text for previous month link in calendar */ +Sanitize::printJsValue( + "$.datepicker.regional['']['prevText']", + _pgettext('Previous month', 'Prev') +); +/* l10n: Display text for next month link in calendar */ +Sanitize::printJsValue( + "$.datepicker.regional['']['nextText']", + _pgettext('Next month', 'Next') +); +/* l10n: Display text for current month link in calendar */ +Sanitize::printJsValue("$.datepicker.regional['']['currentText']", __('Today')); +Sanitize::printJsValue( + "$.datepicker.regional['']['monthNames']", + array( + __('January'), + __('February'), + __('March'), + __('April'), + __('May'), + __('June'), + __('July'), + __('August'), + __('September'), + __('October'), + __('November'), + __('December') + ) +); +Sanitize::printJsValue( + "$.datepicker.regional['']['monthNamesShort']", + array( + /* l10n: Short month name */ + __('Jan'), + /* l10n: Short month name */ + __('Feb'), + /* l10n: Short month name */ + __('Mar'), + /* l10n: Short month name */ + __('Apr'), + /* l10n: Short month name */ + _pgettext('Short month name', 'May'), + /* l10n: Short month name */ + __('Jun'), + /* l10n: Short month name */ + __('Jul'), + /* l10n: Short month name */ + __('Aug'), + /* l10n: Short month name */ + __('Sep'), + /* l10n: Short month name */ + __('Oct'), + /* l10n: Short month name */ + __('Nov'), + /* l10n: Short month name */ + __('Dec') + ) +); +Sanitize::printJsValue( + "$.datepicker.regional['']['dayNames']", + array( + __('Sunday'), + __('Monday'), + __('Tuesday'), + __('Wednesday'), + __('Thursday'), + __('Friday'), + __('Saturday') + ) +); +Sanitize::printJsValue( + "$.datepicker.regional['']['dayNamesShort']", + array( + /* l10n: Short week day name */ + __('Sun'), + /* l10n: Short week day name */ + __('Mon'), + /* l10n: Short week day name */ + __('Tue'), + /* l10n: Short week day name */ + __('Wed'), + /* l10n: Short week day name */ + __('Thu'), + /* l10n: Short week day name */ + __('Fri'), + /* l10n: Short week day name */ + __('Sat') + ) +); +Sanitize::printJsValue( + "$.datepicker.regional['']['dayNamesMin']", + array( + /* l10n: Minimal week day name */ + __('Su'), + /* l10n: Minimal week day name */ + __('Mo'), + /* l10n: Minimal week day name */ + __('Tu'), + /* l10n: Minimal week day name */ + __('We'), + /* l10n: Minimal week day name */ + __('Th'), + /* l10n: Minimal week day name */ + __('Fr'), + /* l10n: Minimal week day name */ + __('Sa') + ) +); +/* l10n: Column header for week of the year in calendar */ +Sanitize::printJsValue("$.datepicker.regional['']['weekHeader']", __('Wk')); + +Sanitize::printJsValue( + "$.datepicker.regional['']['showMonthAfterYear']", + /* l10n: Month-year order for calendar, use either "calendar-month-year" + * or "calendar-year-month". + */ + (__('calendar-month-year') == 'calendar-year-month') +); +/* l10n: Year suffix for calendar, "none" is empty. */ +$year_suffix = _pgettext('Year suffix', 'none'); +Sanitize::printJsValue( + "$.datepicker.regional['']['yearSuffix']", + ($year_suffix == 'none' ? '' : $year_suffix) +); +?> +$.extend($.datepicker._defaults, $.datepicker.regional['']); +} /* if ($.datepicker) */ + + +$.extend($.timepicker._defaults, $.timepicker.regional['']); +} /* if ($.timepicker) */ + + diff --git a/php/apps/phpmyadmin49/js/microhistory.js b/php/apps/phpmyadmin49/js/microhistory.js new file mode 100644 index 00000000..46c98a88 --- /dev/null +++ b/php/apps/phpmyadmin49/js/microhistory.js @@ -0,0 +1,335 @@ +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * An implementation of a client-side page cache. + * This object also uses the cache to provide a simple microhistory, + * that is the ability to use the back and forward buttons in the browser + */ +PMA_MicroHistory = { + /** + * @var int The maximum number of pages to keep in the cache + */ + MAX: 6, + /** + * @var object A hash used to prime the cache with data about the initially + * loaded page. This is set in the footer, and then loaded + * by a double-queued event further down this file. + */ + primer: {}, + /** + * @var array Stores the content of the cached pages + */ + pages: [], + /** + * @var int The index of the currently loaded page + * This is used to know at which point in the history we are + */ + current: 0, + /** + * Saves a new page in the cache + * + * @param string hash The hash part of the url that is being loaded + * @param array scripts A list of scripts that is required for the page + * @param string menu A hash that links to a menu stored + * in a dedicated menu cache + * @param array params A list of parameters used by PMA_commonParams() + * @param string rel A relationship to the current page: + * 'samepage': Forces the response to be treated as + * the same page as the current one + * 'newpage': Forces the response to be treated as + * a new page + * undefined: Default behaviour, 'samepage' if the + * selflinks of the two pages are the same. + * 'newpage' otherwise + * + * @return void + */ + add: function (hash, scripts, menu, params, rel) { + if (this.pages.length > PMA_MicroHistory.MAX) { + // Trim the cache, to the maximum number of allowed entries + // This way we will have a cached menu for every page + for (var i = 0; i < this.pages.length - this.MAX; i++) { + delete this.pages[i]; + } + } + while (this.current < this.pages.length) { + // trim the cache if we went back in the history + // and are now going forward again + this.pages.pop(); + } + if (rel === 'newpage' || + ( + typeof rel === 'undefined' && ( + typeof this.pages[this.current - 1] === 'undefined' || + this.pages[this.current - 1].hash !== hash + ) + ) + ) { + this.pages.push({ + hash: hash, + content: $('#page_content').html(), + scripts: scripts, + selflink: $('#selflink').html(), + menu: menu, + params: params + }); + PMA_SetUrlHash(this.current, hash); + this.current++; + } + }, + /** + * Restores a page from the cache. This is called when the hash + * part of the url changes and it's structure appears to be valid + * + * @param string index Which page from the history to load + * + * @return void + */ + navigate: function (index) { + if (typeof this.pages[index] === 'undefined' || + typeof this.pages[index].content === 'undefined' || + typeof this.pages[index].menu === 'undefined' || + ! PMA_MicroHistory.menus.get(this.pages[index].menu) + ) { + PMA_ajaxShowMessage( + '
          ' + PMA_messages.strInvalidPage + '
          ', + false + ); + } else { + AJAX.active = true; + var record = this.pages[index]; + AJAX.scriptHandler.reset(function () { + $('#page_content').html(record.content); + $('#selflink').html(record.selflink); + PMA_MicroHistory.menus.replace(PMA_MicroHistory.menus.get(record.menu)); + PMA_commonParams.setAll(record.params); + AJAX.scriptHandler.load(record.scripts); + PMA_MicroHistory.current = ++index; + }); + } + }, + /** + * Resaves the content of the current page in the cache. + * Necessary in order not to show the user some outdated version of the page + * + * @return void + */ + update: function () { + var page = this.pages[this.current - 1]; + if (page) { + page.content = $('#page_content').html(); + } + }, + /** + * @var object Dedicated menu cache + */ + menus: { + /** + * Returns the number of items in an associative array + * + * @return int + */ + size: function (obj) { + var size = 0; + var key; + for (key in obj) { + if (obj.hasOwnProperty(key)) { + size++; + } + } + return size; + }, + /** + * @var hash Stores the content of the cached menus + */ + data: {}, + /** + * Saves a new menu in the cache + * + * @param string hash The hash (trimmed md5) of the menu to be saved + * @param string content The HTML code of the menu to be saved + * + * @return void + */ + add: function (hash, content) { + if (this.size(this.data) > PMA_MicroHistory.MAX) { + // when the cache grows, we remove the oldest entry + var oldest; + var key; + var init = 0; + for (var i in this.data) { + if (this.data[i]) { + if (! init || this.data[i].timestamp.getTime() < oldest.getTime()) { + oldest = this.data[i].timestamp; + key = i; + init = 1; + } + } + } + delete this.data[key]; + } + this.data[hash] = { + content: content, + timestamp: new Date() + }; + }, + /** + * Retrieves a menu given its hash + * + * @param string hash The hash of the menu to be retrieved + * + * @return string + */ + get: function (hash) { + if (this.data[hash]) { + return this.data[hash].content; + } else { + // This should never happen as long as the number of stored menus + // is larger or equal to the number of pages in the page cache + return ''; + } + }, + /** + * Prepares part of the parameter string used during page requests, + * this is necessary to tell the server which menus we have in the cache + * + * @return string + */ + getRequestParam: function () { + var param = ''; + var menuHashes = []; + for (var i in this.data) { + menuHashes.push(i); + } + var menuHashesParam = menuHashes.join('-'); + if (menuHashesParam) { + param = PMA_commonParams.get('arg_separator') + 'menuHashes=' + menuHashesParam; + } + return param; + }, + /** + * Replaces the menu with new content + * + * @return void + */ + replace: function (content) { + $('#floating_menubar').html(content) + // Remove duplicate wrapper + // TODO: don't send it in the response + .children().first().remove(); + $('#topmenu').menuResizer(PMA_mainMenuResizerCallback); + } + } +}; + +/** + * URL hash management module. + * Allows direct bookmarking and microhistory. + */ +PMA_SetUrlHash = (function (jQuery, window) { + 'use strict'; + /** + * Indictaes whether we have already completed + * the initialisation of the hash + * + * @access private + */ + var ready = false; + /** + * Stores a hash that needed to be set when we were not ready + * + * @access private + */ + var savedHash = ''; + /** + * Flag to indicate if the change of hash was triggered + * by a user pressing the back/forward button or if + * the change was triggered internally + * + * @access private + */ + var userChange = true; + + // Fix favicon disappearing in Firefox when setting location.hash + function resetFavicon () { + if (navigator.userAgent.indexOf('Firefox') > -1) { + // Move the link tags for the favicon to the bottom + // of the head element to force a reload of the favicon + $('head > link[href="favicon\\.ico"]').appendTo('head'); + } + } + + /** + * Sets the hash part of the URL + * + * @access public + */ + function setUrlHash (index, hash) { + /* + * Known problem: + * Setting hash leads to reload in webkit: + * http://www.quirksmode.org/bugreports/archives/2005/05/Safari_13_visual_anomaly_with_windowlocationhref.html + * + * so we expect that users are not running an ancient Safari version + */ + + userChange = false; + if (ready) { + window.location.hash = 'PMAURL-' + index + ':' + hash; + resetFavicon(); + } else { + savedHash = 'PMAURL-' + index + ':' + hash; + } + } + /** + * Start initialisation + */ + var urlhash = window.location.hash; + if (urlhash.substring(0, 8) === '#PMAURL-') { + // We have a valid hash, let's redirect the user + // to the page that it's pointing to + var colon_position = urlhash.indexOf(':'); + var questionmark_position = urlhash.indexOf('?'); + if (colon_position !== -1 && questionmark_position !== -1 && colon_position < questionmark_position) { + var hash_url = urlhash.substring(colon_position + 1, questionmark_position); + if (PMA_gotoWhitelist.indexOf(hash_url) !== -1) { + window.location = urlhash.substring( + colon_position + 1 + ); + } + } + } else { + // We don't have a valid hash, so we'll set it up + // when the page finishes loading + jQuery(function () { + /* Check if we should set URL */ + if (savedHash !== '') { + window.location.hash = savedHash; + savedHash = ''; + resetFavicon(); + } + // Indicate that we're done initialising + ready = true; + }); + } + /** + * Register an event handler for when the url hash changes + */ + jQuery(function () { + jQuery(window).hashchange(function () { + if (userChange === false) { + // Ignore internally triggered hash changes + userChange = true; + } else if (/^#PMAURL-\d+:/.test(window.location.hash)) { + // Change page if the hash changed was triggered by a user action + var index = window.location.hash.substring( + 8, window.location.hash.indexOf(':') + ); + PMA_MicroHistory.navigate(index); + } + }); + }); + /** + * Publicly exposes a reference to the otherwise private setUrlHash function + */ + return setUrlHash; +}(jQuery, window)); diff --git a/php/apps/phpmyadmin49/js/multi_column_sort.js b/php/apps/phpmyadmin49/js/multi_column_sort.js new file mode 100644 index 00000000..cc9b9215 --- /dev/null +++ b/php/apps/phpmyadmin49/js/multi_column_sort.js @@ -0,0 +1,84 @@ +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * @fileoverview Implements the shiftkey + click remove column + * from order by clause funcationality + * @name columndelete + * + * @requires jQuery + */ + +function captureURL (url) { + var URL = {}; + url = '' + url; + // Exclude the url part till HTTP + url = url.substr(url.search('sql.php'), url.length); + // The url part between ORDER BY and &session_max_rows needs to be replaced. + URL.head = url.substr(0, url.indexOf('ORDER+BY') + 9); + URL.tail = url.substr(url.indexOf('&session_max_rows'), url.length); + return URL; +} + +/** + * This function is for navigating to the generated URL + * + * @param object target HTMLAnchor element + * @param object parent HTMLDom Object + */ + +function removeColumnFromMultiSort (target, parent) { + var URL = captureURL(target); + var begin = target.indexOf('ORDER+BY') + 8; + var end = target.indexOf(PMA_commonParams.get('arg_separator') + 'session_max_rows'); + // get the names of the columns involved + var between_part = target.substr(begin, end - begin); + var columns = between_part.split('%2C+'); + // If the given column is not part of the order clause exit from this function + var index = parent.find('small').length ? parent.find('small').text() : ''; + if (index === '') { + return ''; + } + // Remove the current clicked column + columns.splice(index - 1, 1); + // If all the columns have been removed dont submit a query with nothing + // After order by clause. + if (columns.length === 0) { + var head = URL.head; + head = head.slice(0,head.indexOf('ORDER+BY')); + URL.head = head; + // removing the last sort order should have priority over what + // is remembered via the RememberSorting directive + URL.tail += PMA_commonParams.get('arg_separator') + 'discard_remembered_sort=1'; + } + URL.head = URL.head.substring(URL.head.indexOf('?') + 1); + var middle_part = columns.join('%2C+'); + params = URL.head + middle_part + URL.tail; + return params; +} + +AJAX.registerOnload('keyhandler.js', function () { + $('th.draggable.column_heading.pointer.marker a').on('click', function (event) { + var url = $(this).parent().find('input').val(); + var argsep = PMA_commonParams.get('arg_separator'); + if (event.ctrlKey || event.altKey) { + event.preventDefault(); + var params = removeColumnFromMultiSort(url, $(this).parent()); + if (params) { + AJAX.source = $(this); + PMA_ajaxShowMessage(); + params += argsep + 'ajax_request=true' + argsep + 'ajax_page_request=true'; + $.post('sql.php', params, AJAX.responseHandler); + } + } else if (event.shiftKey) { + event.preventDefault(); + AJAX.source = $(this); + PMA_ajaxShowMessage(); + var params = url.substring(url.indexOf('?') + 1); + params += argsep + 'ajax_request=true' + argsep + 'ajax_page_request=true'; + $.post('sql.php', params, AJAX.responseHandler); + } + }); +}); + +AJAX.registerTeardown('keyhandler.js', function () { + $(document).off('click', 'th.draggable.column_heading.pointer.marker a'); +}); diff --git a/php/apps/phpmyadmin49/js/navigation.js b/php/apps/phpmyadmin49/js/navigation.js new file mode 100644 index 00000000..6f0d05b8 --- /dev/null +++ b/php/apps/phpmyadmin49/js/navigation.js @@ -0,0 +1,1661 @@ +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * function used in or for navigation panel + * + * @package phpMyAdmin-Navigation + */ + +/** + * updates the tree state in sessionStorage + * + * @returns void + */ +function navTreeStateUpdate () { + // update if session storage is supported + if (isStorageSupported('sessionStorage')) { + var storage = window.sessionStorage; + // try catch necessary here to detect whether + // content to be stored exceeds storage capacity + try { + storage.setItem('navTreePaths', JSON.stringify(traverseNavigationForPaths())); + storage.setItem('server', PMA_commonParams.get('server')); + storage.setItem('token', PMA_commonParams.get('token')); + } catch (error) { + // storage capacity exceeded & old navigation tree + // state is no more valid, so remove it + storage.removeItem('navTreePaths'); + storage.removeItem('server'); + storage.removeItem('token'); + } + } +} + + +/** + * updates the filter state in sessionStorage + * + * @returns void + */ +function navFilterStateUpdate (filterName, filterValue) { + if (isStorageSupported('sessionStorage')) { + var storage = window.sessionStorage; + try { + var currentFilter = $.extend({}, JSON.parse(storage.getItem('navTreeSearchFilters'))); + var filter = {}; + filter[filterName] = filterValue; + currentFilter = $.extend(currentFilter, filter); + storage.setItem('navTreeSearchFilters', JSON.stringify(currentFilter)); + } catch (error) { + storage.removeItem('navTreeSearchFilters'); + } + } +} + + +/** + * restores the filter state on navigation reload + * + * @returns void + */ +function navFilterStateRestore () { + if (isStorageSupported('sessionStorage') + && typeof window.sessionStorage.navTreeSearchFilters !== 'undefined' + ) { + var searchClauses = JSON.parse(window.sessionStorage.navTreeSearchFilters); + if (Object.keys(searchClauses).length < 1) { + return; + } + // restore database filter if present and not empty + if (searchClauses.hasOwnProperty('dbFilter') + && searchClauses.dbFilter.length + ) { + $obj = $('#pma_navigation_tree'); + if (! $obj.data('fastFilter')) { + $obj.data( + 'fastFilter', + new PMA_fastFilter.filter($obj, '') + ); + } + $obj.find('li.fast_filter.db_fast_filter input.searchClause') + .val(searchClauses.dbFilter) + .trigger('keyup'); + } + // find all table filters present in the tree + $tableFilters = $('#pma_navigation_tree li.database') + .children('div.list_container') + .find('li.fast_filter input.searchClause'); + // restore table filters + $tableFilters.each(function () { + $obj = $(this).closest('div.list_container'); + // aPath associated with this filter + var filterName = $(this).siblings('input[name=aPath]').val(); + // if this table's filter has a state stored in storage + if (searchClauses.hasOwnProperty(filterName) + && searchClauses[filterName].length + ) { + // clear state if item is not visible, + // happens when table filter becomes invisible + // as db filter has already been applied + if (! $obj.is(':visible')) { + navFilterStateUpdate(filterName, ''); + return true; + } + if (! $obj.data('fastFilter')) { + $obj.data( + 'fastFilter', + new PMA_fastFilter.filter($obj, '') + ); + } + $(this).val(searchClauses[filterName]) + .trigger('keyup'); + } + }); + } +} + +/** + * Loads child items of a node and executes a given callback + * + * @param isNode + * @param $expandElem expander + * @param callback callback function + * + * @returns void + */ +function loadChildNodes (isNode, $expandElem, callback) { + var $destination = null; + var params = null; + + if (isNode) { + if (!$expandElem.hasClass('expander')) { + return; + } + $destination = $expandElem.closest('li'); + params = { + aPath: $expandElem.find('span.aPath').text(), + vPath: $expandElem.find('span.vPath').text(), + pos: $expandElem.find('span.pos').text(), + pos2_name: $expandElem.find('span.pos2_name').text(), + pos2_value: $expandElem.find('span.pos2_value').text(), + searchClause: '', + searchClause2: '' + }; + if ($expandElem.closest('ul').hasClass('search_results')) { + params.searchClause = PMA_fastFilter.getSearchClause(); + params.searchClause2 = PMA_fastFilter.getSearchClause2($expandElem); + } + } else { + $destination = $('#pma_navigation_tree_content'); + params = { + aPath: $expandElem.attr('aPath'), + vPath: $expandElem.attr('vPath'), + pos: $expandElem.attr('pos'), + pos2_name: '', + pos2_value: '', + searchClause: '', + searchClause2: '' + }; + } + + var url = $('#pma_navigation').find('a.navigation_url').attr('href'); + $.get(url, params, function (data) { + if (typeof data !== 'undefined' && data.success === true) { + $destination.find('div.list_container').remove(); // FIXME: Hack, there shouldn't be a list container there + if (isNode) { + $destination.append(data.message); + $expandElem.addClass('loaded'); + } else { + $destination.html(data.message); + $destination.children() + .first() + .css({ + border: '0px', + margin: '0em', + padding : '0em' + }) + .slideDown('slow'); + } + if (data._errors) { + var $errors = $(data._errors); + if ($errors.children().length > 0) { + $('#pma_errors').replaceWith(data._errors); + } + } + if (callback && typeof callback === 'function') { + callback(data); + } + } else if (data.redirect_flag === '1') { + if (window.location.href.indexOf('?') === -1) { + window.location.href += '?session_expired=1'; + } else { + window.location.href += PMA_commonParams.get('arg_separator') + 'session_expired=1'; + } + window.location.reload(); + } else { + var $throbber = $expandElem.find('img.throbber'); + $throbber.hide(); + var $icon = $expandElem.find('img.ic_b_plus'); + $icon.show(); + PMA_ajaxShowMessage(data.error, false); + } + }); +} + +/** + * Collapses a node in navigation tree. + * + * @param $expandElem expander + * + * @returns void + */ +function collapseTreeNode ($expandElem) { + var $children = $expandElem.closest('li').children('div.list_container'); + var $icon = $expandElem.find('img'); + if ($expandElem.hasClass('loaded')) { + if ($icon.is('.ic_b_minus')) { + $icon.removeClass('ic_b_minus').addClass('ic_b_plus'); + $children.slideUp('fast'); + } + } + $expandElem.blur(); + $children.promise().done(navTreeStateUpdate); +} + +/** + * Traverse the navigation tree backwards to generate all the actual + * and virtual paths, as well as the positions in the pagination at + * various levels, if necessary. + * + * @return Object + */ +function traverseNavigationForPaths () { + var params = { + pos: $('#pma_navigation_tree').find('div.dbselector select').val() + }; + if ($('#navi_db_select').length) { + return params; + } + var count = 0; + $('#pma_navigation_tree').find('a.expander:visible').each(function () { + if ($(this).find('img').is('.ic_b_minus') && + $(this).closest('li').find('div.list_container .ic_b_minus').length === 0 + ) { + params['n' + count + '_aPath'] = $(this).find('span.aPath').text(); + params['n' + count + '_vPath'] = $(this).find('span.vPath').text(); + + var pos2_name = $(this).find('span.pos2_name').text(); + if (! pos2_name) { + pos2_name = $(this) + .parent() + .parent() + .find('span.pos2_name:last') + .text(); + } + var pos2_value = $(this).find('span.pos2_value').text(); + if (! pos2_value) { + pos2_value = $(this) + .parent() + .parent() + .find('span.pos2_value:last') + .text(); + } + + params['n' + count + '_pos2_name'] = pos2_name; + params['n' + count + '_pos2_value'] = pos2_value; + + params['n' + count + '_pos3_name'] = $(this).find('span.pos3_name').text(); + params['n' + count + '_pos3_value'] = $(this).find('span.pos3_value').text(); + count++; + } + }); + return params; +} + +/** + * Executed on page load + */ +$(function () { + if (! $('#pma_navigation').length) { + // Don't bother running any code if the navigation is not even on the page + return; + } + + // Do not let the page reload on submitting the fast filter + $(document).on('submit', '.fast_filter', function (event) { + event.preventDefault(); + }); + + // Fire up the resize handlers + new ResizeHandler(); + + /** + * opens/closes (hides/shows) tree elements + * loads data via ajax + */ + $(document).on('click', '#pma_navigation_tree a.expander', function (event) { + event.preventDefault(); + event.stopImmediatePropagation(); + var $icon = $(this).find('img'); + if ($icon.is('.ic_b_plus')) { + expandTreeNode($(this)); + } else { + collapseTreeNode($(this)); + } + }); + + /** + * Register event handler for click on the reload + * navigation icon at the top of the panel + */ + $(document).on('click', '#pma_navigation_reload', function (event) { + event.preventDefault(); + + // Find the loading symbol and show it + var $icon_throbber_src = $('#pma_navigation').find('.throbber'); + $icon_throbber_src.show(); + // TODO Why is a loading symbol both hidden, and invisible? + $icon_throbber_src.css('visibility', ''); + + // Callback to be used to hide the loading symbol when done reloading + function hideNav () { + $icon_throbber_src.hide(); + } + + // Reload the navigation + PMA_reloadNavigation(hideNav); + }); + + $(document).on('change', '#navi_db_select', function (event) { + if (! $(this).val()) { + PMA_commonParams.set('db', ''); + PMA_reloadNavigation(); + } + $(this).closest('form').trigger('submit'); + }); + + /** + * Register event handler for click on the collapse all + * navigation icon at the top of the navigation tree + */ + $(document).on('click', '#pma_navigation_collapse', function (event) { + event.preventDefault(); + $('#pma_navigation_tree').find('a.expander').each(function () { + var $icon = $(this).find('img'); + if ($icon.is('.ic_b_minus')) { + $(this).click(); + } + }); + }); + + /** + * Register event handler to toggle + * the 'link with main panel' icon on mouseenter. + */ + $(document).on('mouseenter', '#pma_navigation_sync', function (event) { + event.preventDefault(); + var synced = $('#pma_navigation_tree').hasClass('synced'); + var $img = $('#pma_navigation_sync').children('img'); + if (synced) { + $img.removeClass('ic_s_link').addClass('ic_s_unlink'); + } else { + $img.removeClass('ic_s_unlink').addClass('ic_s_link'); + } + }); + + /** + * Register event handler to toggle + * the 'link with main panel' icon on mouseout. + */ + $(document).on('mouseout', '#pma_navigation_sync', function (event) { + event.preventDefault(); + var synced = $('#pma_navigation_tree').hasClass('synced'); + var $img = $('#pma_navigation_sync').children('img'); + if (synced) { + $img.removeClass('ic_s_unlink').addClass('ic_s_link'); + } else { + $img.removeClass('ic_s_link').addClass('ic_s_unlink'); + } + }); + + /** + * Register event handler to toggle + * the linking with main panel behavior + */ + $(document).on('click', '#pma_navigation_sync', function (event) { + event.preventDefault(); + var synced = $('#pma_navigation_tree').hasClass('synced'); + var $img = $('#pma_navigation_sync').children('img'); + if (synced) { + $img + .removeClass('ic_s_unlink') + .addClass('ic_s_link') + .attr('alt', PMA_messages.linkWithMain) + .attr('title', PMA_messages.linkWithMain); + $('#pma_navigation_tree') + .removeClass('synced') + .find('li.selected') + .removeClass('selected'); + } else { + $img + .removeClass('ic_s_link') + .addClass('ic_s_unlink') + .attr('alt', PMA_messages.unlinkWithMain) + .attr('title', PMA_messages.unlinkWithMain); + $('#pma_navigation_tree').addClass('synced'); + PMA_showCurrentNavigation(); + } + }); + + /** + * Bind all "fast filter" events + */ + $(document).on('click', '#pma_navigation_tree li.fast_filter span', PMA_fastFilter.events.clear); + $(document).on('focus', '#pma_navigation_tree li.fast_filter input.searchClause', PMA_fastFilter.events.focus); + $(document).on('blur', '#pma_navigation_tree li.fast_filter input.searchClause', PMA_fastFilter.events.blur); + $(document).on('keyup', '#pma_navigation_tree li.fast_filter input.searchClause', PMA_fastFilter.events.keyup); + + /** + * Ajax handler for pagination + */ + $(document).on('click', '#pma_navigation_tree div.pageselector a.ajax', function (event) { + event.preventDefault(); + PMA_navigationTreePagination($(this)); + }); + + /** + * Node highlighting + */ + $(document).on( + 'mouseover', + '#pma_navigation_tree.highlight li:not(.fast_filter)', + function () { + if ($('li:visible', this).length === 0) { + $(this).addClass('activePointer'); + } + } + ); + $(document).on( + 'mouseout', + '#pma_navigation_tree.highlight li:not(.fast_filter)', + function () { + $(this).removeClass('activePointer'); + } + ); + + /** Create a Routine, Trigger or Event */ + $(document).on('click', 'li.new_procedure a.ajax, li.new_function a.ajax', function (event) { + event.preventDefault(); + var dialog = new RTE.object('routine'); + dialog.editorDialog(1, $(this)); + }); + $(document).on('click', 'li.new_trigger a.ajax', function (event) { + event.preventDefault(); + var dialog = new RTE.object('trigger'); + dialog.editorDialog(1, $(this)); + }); + $(document).on('click', 'li.new_event a.ajax', function (event) { + event.preventDefault(); + var dialog = new RTE.object('event'); + dialog.editorDialog(1, $(this)); + }); + + /** Edit Routines, Triggers or Events */ + $(document).on('click', 'li.procedure > a.ajax, li.function > a.ajax', function (event) { + event.preventDefault(); + var dialog = new RTE.object('routine'); + dialog.editorDialog(0, $(this)); + }); + $(document).on('click', 'li.trigger > a.ajax', function (event) { + event.preventDefault(); + var dialog = new RTE.object('trigger'); + dialog.editorDialog(0, $(this)); + }); + $(document).on('click', 'li.event > a.ajax', function (event) { + event.preventDefault(); + var dialog = new RTE.object('event'); + dialog.editorDialog(0, $(this)); + }); + + /** Execute Routines */ + $(document).on('click', 'li.procedure div a.ajax img,' + + ' li.function div a.ajax img', function (event) { + event.preventDefault(); + var dialog = new RTE.object('routine'); + dialog.executeDialog($(this).parent()); + }); + /** Export Triggers and Events */ + $(document).on('click', 'li.trigger div:eq(1) a.ajax img,' + + ' li.event div:eq(1) a.ajax img', function (event) { + event.preventDefault(); + var dialog = new RTE.object(); + dialog.exportDialog($(this).parent()); + }); + + /** New index */ + $(document).on('click', '#pma_navigation_tree li.new_index a.ajax', function (event) { + event.preventDefault(); + var url = $(this).attr('href').substr( + $(this).attr('href').indexOf('?') + 1 + ) + PMA_commonParams.get('arg_separator') + 'ajax_request=true'; + var title = PMA_messages.strAddIndex; + indexEditorDialog(url, title); + }); + + /** Edit index */ + $(document).on('click', 'li.index a.ajax', function (event) { + event.preventDefault(); + var url = $(this).attr('href').substr( + $(this).attr('href').indexOf('?') + 1 + ) + PMA_commonParams.get('arg_separator') + 'ajax_request=true'; + var title = PMA_messages.strEditIndex; + indexEditorDialog(url, title); + }); + + /** New view */ + $(document).on('click', 'li.new_view a.ajax', function (event) { + event.preventDefault(); + PMA_createViewDialog($(this)); + }); + + /** Hide navigation tree item */ + $(document).on('click', 'a.hideNavItem.ajax', function (event) { + event.preventDefault(); + var argSep = PMA_commonParams.get('arg_separator'); + var params = $(this).getPostData(); + params += argSep + 'ajax_request=true' + argSep + 'server=' + PMA_commonParams.get('server'); + $.ajax({ + type: 'POST', + data: params, + url: $(this).attr('href'), + success: function (data) { + if (typeof data !== 'undefined' && data.success === true) { + PMA_reloadNavigation(); + } else { + PMA_ajaxShowMessage(data.error); + } + } + }); + }); + + /** Display a dialog to choose hidden navigation items to show */ + $(document).on('click', 'a.showUnhide.ajax', function (event) { + event.preventDefault(); + var $msg = PMA_ajaxShowMessage(); + var argSep = PMA_commonParams.get('arg_separator'); + var params = $(this).getPostData(); + params += argSep + 'ajax_request=true'; + $.post($(this).attr('href'), params, function (data) { + if (typeof data !== 'undefined' && data.success === true) { + PMA_ajaxRemoveMessage($msg); + var buttonOptions = {}; + buttonOptions[PMA_messages.strClose] = function () { + $(this).dialog('close'); + }; + $('
          ') + .attr('id', 'unhideNavItemDialog') + .append(data.message) + .dialog({ + width: 400, + minWidth: 200, + modal: true, + buttons: buttonOptions, + title: PMA_messages.strUnhideNavItem, + close: function () { + $(this).remove(); + } + }); + } else { + PMA_ajaxShowMessage(data.error); + } + }); + }); + + /** Show a hidden navigation tree item */ + $(document).on('click', 'a.unhideNavItem.ajax', function (event) { + event.preventDefault(); + var $tr = $(this).parents('tr'); + var $msg = PMA_ajaxShowMessage(); + var argSep = PMA_commonParams.get('arg_separator'); + var params = $(this).getPostData(); + params += argSep + 'ajax_request=true' + argSep + 'server=' + PMA_commonParams.get('server'); + $.ajax({ + type: 'POST', + data: params, + url: $(this).attr('href'), + success: function (data) { + PMA_ajaxRemoveMessage($msg); + if (typeof data !== 'undefined' && data.success === true) { + $tr.remove(); + PMA_reloadNavigation(); + } else { + PMA_ajaxShowMessage(data.error); + } + } + }); + }); + + // Add/Remove favorite table using Ajax. + $(document).on('click', '.favorite_table_anchor', function (event) { + event.preventDefault(); + $self = $(this); + var anchor_id = $self.attr('id'); + if ($self.data('favtargetn') !== null) { + if ($('a[data-favtargets="' + $self.data('favtargetn') + '"]').length > 0) { + $('a[data-favtargets="' + $self.data('favtargetn') + '"]').trigger('click'); + return; + } + } + + $.ajax({ + url: $self.attr('href'), + cache: false, + type: 'POST', + data: { + favorite_tables: (isStorageSupported('localStorage') && typeof window.localStorage.favorite_tables !== 'undefined') + ? window.localStorage.favorite_tables + : '', + server: PMA_commonParams.get('server'), + }, + success: function (data) { + if (data.changes) { + $('#pma_favorite_list').html(data.list); + $('#' + anchor_id).parent().html(data.anchor); + PMA_tooltip( + $('#' + anchor_id), + 'a', + $('#' + anchor_id).attr('title') + ); + // Update localStorage. + if (isStorageSupported('localStorage')) { + window.localStorage.favorite_tables = data.favorite_tables; + } + } else { + PMA_ajaxShowMessage(data.message); + } + } + }); + }); + // Check if session storage is supported + if (isStorageSupported('sessionStorage')) { + var storage = window.sessionStorage; + // remove tree from storage if Navi_panel config form is submitted + $(document).on('submit', 'form.config-form', function (event) { + storage.removeItem('navTreePaths'); + }); + // Initialize if no previous state is defined + if ($('#pma_navigation_tree_content').length && + typeof storage.navTreePaths === 'undefined' + ) { + PMA_reloadNavigation(); + } else if (PMA_commonParams.get('server') === storage.server && + PMA_commonParams.get('token') === storage.token + ) { + // Reload the tree to the state before page refresh + PMA_reloadNavigation(navFilterStateRestore, JSON.parse(storage.navTreePaths)); + } else { + // If the user is different + navTreeStateUpdate(); + } + } +}); + +/** + * Expands a node in navigation tree. + * + * @param $expandElem expander + * @param callback callback function + * + * @returns void + */ +function expandTreeNode ($expandElem, callback) { + var $children = $expandElem.closest('li').children('div.list_container'); + var $icon = $expandElem.find('img'); + if ($expandElem.hasClass('loaded')) { + if ($icon.is('.ic_b_plus')) { + $icon.removeClass('ic_b_plus').addClass('ic_b_minus'); + $children.slideDown('fast'); + } + if (callback && typeof callback === 'function') { + callback.call(); + } + $children.promise().done(navTreeStateUpdate); + } else { + var $throbber = $('#pma_navigation').find('.throbber') + .first() + .clone() + .css({ visibility: 'visible', display: 'block' }) + .click(false); + $icon.hide(); + $throbber.insertBefore($icon); + + loadChildNodes(true, $expandElem, function (data) { + if (typeof data !== 'undefined' && data.success === true) { + var $destination = $expandElem.closest('li'); + $icon.removeClass('ic_b_plus').addClass('ic_b_minus'); + $children = $destination.children('div.list_container'); + $children.slideDown('fast'); + if ($destination.find('ul > li').length === 1) { + $destination.find('ul > li') + .find('a.expander.container') + .click(); + } + if (callback && typeof callback === 'function') { + callback.call(); + } + PMA_showFullName($destination); + } else { + PMA_ajaxShowMessage(data.error, false); + } + $icon.show(); + $throbber.remove(); + $children.promise().done(navTreeStateUpdate); + }); + } + $expandElem.blur(); +} + +/** + * Auto-scrolls the newly chosen database + * + * @param object $element The element to set to view + * @param boolean $forceToTop Whether to force scroll to top + * + */ +function scrollToView ($element, $forceToTop) { + navFilterStateRestore(); + var $container = $('#pma_navigation_tree_content'); + var elemTop = $element.offset().top - $container.offset().top; + var textHeight = 20; + var scrollPadding = 20; // extra padding from top of bottom when scrolling to view + if (elemTop < 0 || $forceToTop) { + $container.stop().animate({ + scrollTop: elemTop + $container.scrollTop() - scrollPadding + }); + } else if (elemTop + textHeight > $container.height()) { + $container.stop().animate({ + scrollTop: elemTop + textHeight - $container.height() + $container.scrollTop() + scrollPadding + }); + } +} + +/** + * Expand the navigation and highlight the current database or table/view + * + * @returns void + */ +function PMA_showCurrentNavigation () { + var db = PMA_commonParams.get('db'); + var table = PMA_commonParams.get('table'); + $('#pma_navigation_tree') + .find('li.selected') + .removeClass('selected'); + if (db) { + var $dbItem = findLoadedItem( + $('#pma_navigation_tree').find('> div'), db, 'database', !table + ); + if ($('#navi_db_select').length && + $('option:selected', $('#navi_db_select')).length + ) { + if (! PMA_selectCurrentDb()) { + return; + } + // If loaded database in navigation is not same as current one + if ($('#pma_navigation_tree_content').find('span.loaded_db:first').text() + !== $('#navi_db_select').val() + ) { + loadChildNodes(false, $('option:selected', $('#navi_db_select')), function (data) { + handleTableOrDb(table, $('#pma_navigation_tree_content')); + var $children = $('#pma_navigation_tree_content').children('div.list_container'); + $children.promise().done(navTreeStateUpdate); + }); + } else { + handleTableOrDb(table, $('#pma_navigation_tree_content')); + } + } else if ($dbItem) { + var $expander = $dbItem.children('div:first').children('a.expander'); + // if not loaded or loaded but collapsed + if (! $expander.hasClass('loaded') || + $expander.find('img').is('.ic_b_plus') + ) { + expandTreeNode($expander, function () { + handleTableOrDb(table, $dbItem); + }); + } else { + handleTableOrDb(table, $dbItem); + } + } + } else if ($('#navi_db_select').length && $('#navi_db_select').val()) { + $('#navi_db_select').val('').hide().trigger('change'); + } + PMA_showFullName($('#pma_navigation_tree')); + + function handleTableOrDb (table, $dbItem) { + if (table) { + loadAndHighlightTableOrView($dbItem, table); + } else { + var $container = $dbItem.children('div.list_container'); + var $tableContainer = $container.children('ul').children('li.tableContainer'); + if ($tableContainer.length > 0) { + var $expander = $tableContainer.children('div:first').children('a.expander'); + $tableContainer.addClass('selected'); + expandTreeNode($expander, function () { + scrollToView($dbItem, true); + }); + } else { + scrollToView($dbItem, true); + } + } + } + + function findLoadedItem ($container, name, clazz, doSelect) { + var ret = false; + $container.children('ul').children('li').each(function () { + var $li = $(this); + // this is a navigation group, recurse + if ($li.is('.navGroup')) { + var $container = $li.children('div.list_container'); + var $childRet = findLoadedItem( + $container, name, clazz, doSelect + ); + if ($childRet) { + ret = $childRet; + return false; + } + } else { // this is a real navigation item + // name and class matches + if (((clazz && $li.is('.' + clazz)) || ! clazz) && + $li.children('a').text() === name) { + if (doSelect) { + $li.addClass('selected'); + } + // taverse up and expand and parent navigation groups + $li.parents('.navGroup').each(function () { + var $cont = $(this).children('div.list_container'); + if (! $cont.is(':visible')) { + $(this) + .children('div:first') + .children('a.expander') + .click(); + } + }); + ret = $li; + return false; + } + } + }); + return ret; + } + + function loadAndHighlightTableOrView ($dbItem, itemName) { + var $container = $dbItem.children('div.list_container'); + var $expander; + var $whichItem = isItemInContainer($container, itemName, 'li.table, li.view'); + // If item already there in some container + if ($whichItem) { + // get the relevant container while may also be a subcontainer + var $relatedContainer = $whichItem.closest('li.subContainer').length + ? $whichItem.closest('li.subContainer') + : $dbItem; + $whichItem = findLoadedItem( + $relatedContainer.children('div.list_container'), + itemName, null, true + ); + // Show directly + showTableOrView($whichItem, $relatedContainer.children('div:first').children('a.expander')); + // else if item not there, try loading once + } else { + var $sub_containers = $dbItem.find('.subContainer'); + // If there are subContainers i.e. tableContainer or viewContainer + if ($sub_containers.length > 0) { + var $containers = []; + $sub_containers.each(function (index) { + $containers[index] = $(this); + $expander = $containers[index] + .children('div:first') + .children('a.expander'); + if (! $expander.hasClass('loaded')) { + loadAndShowTableOrView($expander, $containers[index], itemName); + } + }); + // else if no subContainers + } else { + $expander = $dbItem + .children('div:first') + .children('a.expander'); + if (! $expander.hasClass('loaded')) { + loadAndShowTableOrView($expander, $dbItem, itemName); + } + } + } + } + + function loadAndShowTableOrView ($expander, $relatedContainer, itemName) { + loadChildNodes(true, $expander, function (data) { + var $whichItem = findLoadedItem( + $relatedContainer.children('div.list_container'), + itemName, null, true + ); + if ($whichItem) { + showTableOrView($whichItem, $expander); + } + }); + } + + function showTableOrView ($whichItem, $expander) { + expandTreeNode($expander, function (data) { + if ($whichItem) { + scrollToView($whichItem, false); + } + }); + } + + function isItemInContainer ($container, name, clazz) { + var $whichItem = null; + $items = $container.find(clazz); + var found = false; + $items.each(function () { + if ($(this).children('a').text() === name) { + $whichItem = $(this); + return false; + } + }); + return $whichItem; + } +} + +/** + * Disable navigation panel settings + * + * @return void + */ +function PMA_disableNaviSettings () { + $('#pma_navigation_settings_icon').addClass('hide'); + $('#pma_navigation_settings').remove(); +} + +/** + * Ensure that navigation panel settings is properly setup. + * If not, set it up + * + * @return void + */ +function PMA_ensureNaviSettings (selflink) { + $('#pma_navigation_settings_icon').removeClass('hide'); + + if (!$('#pma_navigation_settings').length) { + var params = { + getNaviSettings: true, + server: PMA_commonParams.get('server'), + }; + var url = $('#pma_navigation').find('a.navigation_url').attr('href'); + $.post(url, params, function (data) { + if (typeof data !== 'undefined' && data.success) { + $('#pma_navi_settings_container').html(data.message); + setupRestoreField(); + setupValidation(); + setupConfigTabs(); + $('#pma_navigation_settings').find('form').attr('action', selflink); + } else { + PMA_ajaxShowMessage(data.error); + } + }); + } else { + $('#pma_navigation_settings').find('form').attr('action', selflink); + } +} + +/** + * Reloads the whole navigation tree while preserving its state + * + * @param function the callback function + * @param Object stored navigation paths + * + * @return void + */ +function PMA_reloadNavigation (callback, paths) { + var params = { + reload: true, + no_debug: true, + server: PMA_commonParams.get('server'), + }; + paths = paths || traverseNavigationForPaths(); + $.extend(params, paths); + if ($('#navi_db_select').length) { + params.db = PMA_commonParams.get('db'); + requestNaviReload(params); + return; + } + requestNaviReload(params); + + function requestNaviReload (params) { + var url = $('#pma_navigation').find('a.navigation_url').attr('href'); + $.post(url, params, function (data) { + if (typeof data !== 'undefined' && data.success) { + $('#pma_navigation_tree').html(data.message).children('div').show(); + if ($('#pma_navigation_tree').hasClass('synced')) { + PMA_selectCurrentDb(); + PMA_showCurrentNavigation(); + } + // Fire the callback, if any + if (typeof callback === 'function') { + callback.call(); + } + navTreeStateUpdate(); + } else { + PMA_ajaxShowMessage(data.error); + } + }); + } +} + +function PMA_selectCurrentDb () { + var $naviDbSelect = $('#navi_db_select'); + + if (!$naviDbSelect.length) { + return false; + } + + if (PMA_commonParams.get('db')) { // db selected + $naviDbSelect.show(); + } + + $naviDbSelect.val(PMA_commonParams.get('db')); + return $naviDbSelect.val() === PMA_commonParams.get('db'); +} + +/** + * Handles any requests to change the page in a branch of a tree + * + * This can be called from link click or select change event handlers + * + * @param object $this A jQuery object that points to the element that + * initiated the action of changing the page + * + * @return void + */ +function PMA_navigationTreePagination ($this) { + var $msgbox = PMA_ajaxShowMessage(); + var isDbSelector = $this.closest('div.pageselector').is('.dbselector'); + var url; + var params; + if ($this[0].tagName === 'A') { + url = $this.attr('href'); + params = 'ajax_request=true'; + } else { // tagName === 'SELECT' + url = 'navigation.php'; + params = $this.closest('form').serialize() + PMA_commonParams.get('arg_separator') + 'ajax_request=true'; + } + var searchClause = PMA_fastFilter.getSearchClause(); + if (searchClause) { + params += PMA_commonParams.get('arg_separator') + 'searchClause=' + encodeURIComponent(searchClause); + } + if (isDbSelector) { + params += PMA_commonParams.get('arg_separator') + 'full=true'; + } else { + var searchClause2 = PMA_fastFilter.getSearchClause2($this); + if (searchClause2) { + params += PMA_commonParams.get('arg_separator') + 'searchClause2=' + encodeURIComponent(searchClause2); + } + } + $.post(url, params, function (data) { + if (typeof data !== 'undefined' && data.success) { + PMA_ajaxRemoveMessage($msgbox); + if (isDbSelector) { + var val = PMA_fastFilter.getSearchClause(); + $('#pma_navigation_tree') + .html(data.message) + .children('div') + .show(); + if (val) { + $('#pma_navigation_tree') + .find('li.fast_filter input.searchClause') + .val(val); + } + } else { + var $parent = $this.closest('div.list_container').parent(); + var val = PMA_fastFilter.getSearchClause2($this); + $this.closest('div.list_container').html( + $(data.message).children().show() + ); + if (val) { + $parent.find('li.fast_filter input.searchClause').val(val); + } + $parent.find('span.pos2_value:first').text( + $parent.find('span.pos2_value:last').text() + ); + $parent.find('span.pos3_value:first').text( + $parent.find('span.pos3_value:last').text() + ); + } + } else { + PMA_ajaxShowMessage(data.error); + PMA_handleRedirectAndReload(data); + } + navTreeStateUpdate(); + }); +} + +/** + * @var ResizeHandler Custom object that manages the resizing of the navigation + * + * XXX: Must only be ever instanciated once + * XXX: Inside event handlers the 'this' object is accessed as 'event.data.resize_handler' + */ +var ResizeHandler = function () { + /** + * @var int panel_width Used by the collapser to know where to go + * back to when uncollapsing the panel + */ + this.panel_width = 0; + /** + * @var string left Used to provide support for RTL languages + */ + this.left = $('html').attr('dir') === 'ltr' ? 'left' : 'right'; + /** + * Adjusts the width of the navigation panel to the specified value + * + * @param int pos Navigation width in pixels + * + * @return void + */ + this.setWidth = function (pos) { + if (typeof pos !== 'number') { + pos = 240; + } + var $resizer = $('#pma_navigation_resizer'); + var resizer_width = $resizer.width(); + var $collapser = $('#pma_navigation_collapser'); + var windowWidth = $(window).width(); + $('#pma_navigation').width(pos); + $('body').css('margin-' + this.left, pos + 'px'); + // Issue #15127 + $('#floating_menubar, #pma_console') + .css('margin-' + this.left, (pos + resizer_width) + 'px'); + $resizer.css(this.left, pos + 'px'); + if (pos === 0) { + $collapser + .css(this.left, pos + resizer_width) + .html(this.getSymbol(pos)) + .prop('title', PMA_messages.strShowPanel); + } else if (windowWidth > 768) { + $collapser + .css(this.left, pos) + .html(this.getSymbol(pos)) + .prop('title', PMA_messages.strHidePanel); + $('#pma_navigation_resizer').css({ 'width': '3px' }); + } else { + $collapser + .css(this.left, windowWidth - 22) + .html(this.getSymbol(100)) + .prop('title', PMA_messages.strHidePanel); + $('#pma_navigation').width(windowWidth); + $('body').css('margin-' + this.left, '0px'); + $('#pma_navigation_resizer').css({ 'width': '0px' }); + } + setTimeout(function () { + $(window).trigger('resize'); + }, 4); + }; + /** + * Returns the horizontal position of the mouse, + * relative to the outer side of the navigation panel + * + * @param int pos Navigation width in pixels + * + * @return void + */ + this.getPos = function (event) { + var pos = event.pageX; + var windowWidth = $(window).width(); + var windowScroll = $(window).scrollLeft(); + pos = pos - windowScroll; + if (this.left !== 'left') { + pos = windowWidth - event.pageX; + } + if (pos < 0) { + pos = 0; + } else if (pos + 100 >= windowWidth) { + pos = windowWidth - 100; + } else { + this.panel_width = 0; + } + return pos; + }; + /** + * Returns the HTML code for the arrow symbol used in the collapser + * + * @param int width The width of the panel + * + * @return string + */ + this.getSymbol = function (width) { + if (this.left === 'left') { + if (width === 0) { + return '→'; + } else { + return '←'; + } + } else { + if (width === 0) { + return '←'; + } else { + return '→'; + } + } + }; + /** + * Event handler for initiating a resize of the panel + * + * @param object e Event data (contains a reference to resizeHandler) + * + * @return void + */ + this.mousedown = function (event) { + event.preventDefault(); + $(document) + .on('mousemove', { 'resize_handler': event.data.resize_handler }, + $.throttle(event.data.resize_handler.mousemove, 4)) + .on('mouseup', { 'resize_handler': event.data.resize_handler }, + event.data.resize_handler.mouseup); + $('body').css('cursor', 'col-resize'); + }; + /** + * Event handler for terminating a resize of the panel + * + * @param object e Event data (contains a reference to resizeHandler) + * + * @return void + */ + this.mouseup = function (event) { + $('body').css('cursor', ''); + configSet('NavigationWidth', event.data.resize_handler.getPos(event)); + $('#topmenu').menuResizer('resize'); + $(document) + .off('mousemove') + .off('mouseup'); + }; + /** + * Event handler for updating the panel during a resize operation + * + * @param object e Event data (contains a reference to resizeHandler) + * + * @return void + */ + this.mousemove = function (event) { + event.preventDefault(); + var pos = event.data.resize_handler.getPos(event); + event.data.resize_handler.setWidth(pos); + if ($('.sticky_columns').length !== 0) { + handleAllStickyColumns(); + } + }; + /** + * Event handler for collapsing the panel + * + * @param object e Event data (contains a reference to resizeHandler) + * + * @return void + */ + this.collapse = function (event) { + event.preventDefault(); + var panel_width = event.data.resize_handler.panel_width; + var width = $('#pma_navigation').width(); + if (width === 0 && panel_width === 0) { + panel_width = 240; + } + configSet('NavigationWidth', panel_width); + event.data.resize_handler.setWidth(panel_width); + event.data.resize_handler.panel_width = width; + }; + /** + * Event handler for resizing the navigation tree height on window resize + * + * @return void + */ + this.treeResize = function (event) { + var $nav = $('#pma_navigation'); + var $nav_tree = $('#pma_navigation_tree'); + var $nav_header = $('#pma_navigation_header'); + var $nav_tree_content = $('#pma_navigation_tree_content'); + $nav_tree.height($nav.height() - $nav_header.height()); + if ($nav_tree_content.length > 0) { + $nav_tree_content.height($nav_tree.height() - $nav_tree_content.position().top); + } else { + // TODO: in fast filter search response there is no #pma_navigation_tree_content, needs to be added in php + $nav_tree.css({ + 'overflow-y': 'auto' + }); + } + // Set content bottom space beacuse of console + $('body').css('margin-bottom', $('#pma_console').height() + 'px'); + }; + // Hide the pma_navigation initially when loaded on mobile + if ($(window).width() < 768) { + this.setWidth(0); + } else { + this.setWidth(configGet('NavigationWidth', false)); + $('#topmenu').menuResizer('resize'); + } + // Register the events for the resizer and the collapser + $(document).on('mousedown', '#pma_navigation_resizer', { 'resize_handler': this }, this.mousedown); + $(document).on('click', '#pma_navigation_collapser', { 'resize_handler': this }, this.collapse); + + // Add the correct arrow symbol to the collapser + $('#pma_navigation_collapser').html(this.getSymbol($('#pma_navigation').width())); + // Fix navigation tree height + $(window).on('resize', this.treeResize); + // need to call this now and then, browser might decide + // to show/hide horizontal scrollbars depending on page content width + setInterval(this.treeResize, 2000); + this.treeResize(); +}; // End of ResizeHandler + +/** + * @var object PMA_fastFilter Handles the functionality that allows filtering + * of the items in a branch of the navigation tree + */ +var PMA_fastFilter = { + /** + * Construct for the asynchronous fast filter functionality + * + * @param object $this A jQuery object pointing to the list container + * which is the nearest parent of the fast filter + * @param string searchClause The query string for the filter + * + * @return new PMA_fastFilter.filter object + */ + filter: function ($this, searchClause) { + /** + * @var object $this A jQuery object pointing to the list container + * which is the nearest parent of the fast filter + */ + this.$this = $this; + /** + * @var bool searchClause The query string for the filter + */ + this.searchClause = searchClause; + /** + * @var object $clone A clone of the original contents + * of the navigation branch before + * the fast filter was applied + */ + this.$clone = $this.clone(); + /** + * @var object xhr A reference to the ajax request that is currently running + */ + this.xhr = null; + /** + * @var int timeout Used to delay the request for asynchronous search + */ + this.timeout = null; + + var $filterInput = $this.find('li.fast_filter input.searchClause'); + if ($filterInput.length !== 0 && + $filterInput.val() !== '' && + $filterInput.val() !== $filterInput[0].defaultValue + ) { + this.request(); + } + }, + /** + * Gets the query string from the database fast filter form + * + * @return string + */ + getSearchClause: function () { + var retval = ''; + var $input = $('#pma_navigation_tree') + .find('li.fast_filter.db_fast_filter input.searchClause'); + if ($input.length && $input.val() !== $input[0].defaultValue) { + retval = $input.val(); + } + return retval; + }, + /** + * Gets the query string from a second level item's fast filter form + * The retrieval is done by trasversing the navigation tree backwards + * + * @return string + */ + getSearchClause2: function ($this) { + var $filterContainer = $this.closest('div.list_container'); + var $filterInput = $([]); + if ($filterContainer + .find('li.fast_filter:not(.db_fast_filter) input.searchClause') + .length !== 0) { + $filterInput = $filterContainer + .find('li.fast_filter:not(.db_fast_filter) input.searchClause'); + } + var searchClause2 = ''; + if ($filterInput.length !== 0 && + $filterInput.first().val() !== $filterInput[0].defaultValue + ) { + searchClause2 = $filterInput.val(); + } + return searchClause2; + }, + /** + * @var hash events A list of functions that are bound to DOM events + * at the top of this file + */ + events: { + focus: function (event) { + var $obj = $(this).closest('div.list_container'); + if (! $obj.data('fastFilter')) { + $obj.data( + 'fastFilter', + new PMA_fastFilter.filter($obj, $(this).val()) + ); + } + if ($(this).val() === this.defaultValue) { + $(this).val(''); + } else { + $(this).select(); + } + }, + blur: function (event) { + if ($(this).val() === '') { + $(this).val(this.defaultValue); + } + var $obj = $(this).closest('div.list_container'); + if ($(this).val() === this.defaultValue && $obj.data('fastFilter')) { + $obj.data('fastFilter').restore(); + } + }, + keyup: function (event) { + var $obj = $(this).closest('div.list_container'); + var str = ''; + if ($(this).val() !== this.defaultValue && $(this).val() !== '') { + $obj.find('div.pageselector').hide(); + str = $(this).val(); + } + + /** + * FIXME at the server level a value match is done while on + * the client side it is a regex match. These two should be aligned + */ + + // regex used for filtering. + var regex; + try { + regex = new RegExp(str, 'i'); + } catch (err) { + return; + } + + // this is the div that houses the items to be filtered by this filter. + var outerContainer; + if ($(this).closest('li.fast_filter').is('.db_fast_filter')) { + outerContainer = $('#pma_navigation_tree_content'); + } else { + outerContainer = $obj; + } + + // filters items that are directly under the div as well as grouped in + // groups. Does not filter child items (i.e. a database search does + // not filter tables) + var item_filter = function ($curr) { + $curr.children('ul').children('li.navGroup').each(function () { + $(this).children('div.list_container').each(function () { + item_filter($(this)); // recursive + }); + }); + $curr.children('ul').children('li').children('a').not('.container').each(function () { + if (regex.test($(this).text())) { + $(this).parent().show().removeClass('hidden'); + } else { + $(this).parent().hide().addClass('hidden'); + } + }); + }; + item_filter(outerContainer); + + // hides containers that does not have any visible children + var container_filter = function ($curr) { + $curr.children('ul').children('li.navGroup').each(function () { + var $group = $(this); + $group.children('div.list_container').each(function () { + container_filter($(this)); // recursive + }); + $group.show().removeClass('hidden'); + if ($group.children('div.list_container').children('ul') + .children('li').not('.hidden').length === 0) { + $group.hide().addClass('hidden'); + } + }); + }; + container_filter(outerContainer); + + if ($(this).val() !== this.defaultValue && $(this).val() !== '') { + if (! $obj.data('fastFilter')) { + $obj.data( + 'fastFilter', + new PMA_fastFilter.filter($obj, $(this).val()) + ); + } else { + if (event.keyCode === 13) { + $obj.data('fastFilter').update($(this).val()); + } + } + } else if ($obj.data('fastFilter')) { + $obj.data('fastFilter').restore(true); + } + // update filter state + var filterName; + if ($(this).attr('name') === 'searchClause2') { + filterName = $(this).siblings('input[name=aPath]').val(); + } else { + filterName = 'dbFilter'; + } + navFilterStateUpdate(filterName, $(this).val()); + }, + clear: function (event) { + event.stopPropagation(); + // Clear the input and apply the fast filter with empty input + var filter = $(this).closest('div.list_container').data('fastFilter'); + if (filter) { + filter.restore(); + } + var value = $(this).prev()[0].defaultValue; + $(this).prev().val(value).trigger('keyup'); + } + } +}; +/** + * Handles a change in the search clause + * + * @param string searchClause The query string for the filter + * + * @return void + */ +PMA_fastFilter.filter.prototype.update = function (searchClause) { + if (this.searchClause !== searchClause) { + this.searchClause = searchClause; + this.request(); + } +}; +/** + * After a delay of 250mS, initiates a request to retrieve search results + * Multiple calls to this function will always abort the previous request + * + * @return void + */ +PMA_fastFilter.filter.prototype.request = function () { + var self = this; + if (self.$this.find('li.fast_filter').find('img.throbber').length === 0) { + self.$this.find('li.fast_filter').append( + $('
          ').append( + $('#pma_navigation_content') + .find('img.throbber') + .clone() + .css({ visibility: 'visible', display: 'block' }) + ) + ); + } + if (self.xhr) { + self.xhr.abort(); + } + var url = $('#pma_navigation').find('a.navigation_url').attr('href'); + var params = self.$this.find('> ul > li > form.fast_filter').first().serialize(); + + if (self.$this.find('> ul > li > form.fast_filter:first input[name=searchClause]').length === 0) { + var $input = $('#pma_navigation_tree').find('li.fast_filter.db_fast_filter input.searchClause'); + if ($input.length && $input.val() !== $input[0].defaultValue) { + params += PMA_commonParams.get('arg_separator') + 'searchClause=' + encodeURIComponent($input.val()); + } + } + self.xhr = $.ajax({ + url: url, + type: 'post', + dataType: 'json', + data: params, + complete: function (jqXHR, status) { + if (status !== 'abort') { + var data = JSON.parse(jqXHR.responseText); + self.$this.find('li.fast_filter').find('div.throbber').remove(); + if (data && data.results) { + self.swap.apply(self, [data.message]); + } + } + } + }); +}; +/** + * Replaces the contents of the navigation branch with the search results + * + * @param string list The search results + * + * @return void + */ +PMA_fastFilter.filter.prototype.swap = function (list) { + this.$this + .html($(list).html()) + .children() + .show() + .end() + .find('li.fast_filter input.searchClause') + .val(this.searchClause); + this.$this.data('fastFilter', this); +}; +/** + * Restores the navigation to the original state after the fast filter is cleared + * + * @param bool focus Whether to also focus the input box of the fast filter + * + * @return void + */ +PMA_fastFilter.filter.prototype.restore = function (focus) { + if (this.$this.children('ul').first().hasClass('search_results')) { + this.$this.html(this.$clone.html()).children().show(); + this.$this.data('fastFilter', this); + if (focus) { + this.$this.find('li.fast_filter input.searchClause').focus(); + } + } + this.searchClause = ''; + this.$this.find('div.pageselector').show(); + this.$this.find('div.throbber').remove(); +}; + +/** + * Show full name when cursor hover and name not shown completely + * + * @param object $containerELem Container element + * + * @return void + */ +function PMA_showFullName ($containerELem) { + $containerELem.find('.hover_show_full').mouseenter(function () { + /** mouseenter */ + var $this = $(this); + var thisOffset = $this.offset(); + if ($this.text() === '') { + return; + } + var $parent = $this.parent(); + if (($parent.offset().left + $parent.outerWidth()) + < (thisOffset.left + $this.outerWidth())) { + var $fullNameLayer = $('#full_name_layer'); + if ($fullNameLayer.length === 0) { + $('body').append('
          '); + $('#full_name_layer').mouseleave(function () { + /** mouseleave */ + $(this).addClass('hide') + .removeClass('hovering'); + }).mouseenter(function () { + /** mouseenter */ + $(this).addClass('hovering'); + }); + $fullNameLayer = $('#full_name_layer'); + } + $fullNameLayer.removeClass('hide'); + $fullNameLayer.css({ left: thisOffset.left, top: thisOffset.top }); + $fullNameLayer.html($this.clone()); + setTimeout(function () { + if (! $fullNameLayer.hasClass('hovering')) { + $fullNameLayer.trigger('mouseleave'); + } + }, 200); + } + }); +} diff --git a/php/apps/phpmyadmin49/js/normalization.js b/php/apps/phpmyadmin49/js/normalization.js new file mode 100644 index 00000000..fa486d2e --- /dev/null +++ b/php/apps/phpmyadmin49/js/normalization.js @@ -0,0 +1,745 @@ +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * @fileoverview events handling from normalization page + * @name normalization + * + * @requires jQuery + */ + +/** + * AJAX scripts for normalization.php + * + */ + +var normalizeto = '1nf'; +var primary_key; +var data_parsed = null; +function appendHtmlColumnsList () { + $.post( + 'normalization.php', + { + 'ajax_request': true, + 'db': PMA_commonParams.get('db'), + 'table': PMA_commonParams.get('table'), + 'server': PMA_commonParams.get('server'), + 'getColumns': true + }, + function (data) { + if (data.success === true) { + $('select[name=makeAtomic]').html(data.message); + } + } + ); +} +function goTo3NFStep1 (newTables) { + if (Object.keys(newTables).length === 1) { + newTables = [PMA_commonParams.get('table')]; + } + $.post( + 'normalization.php', + { + 'ajax_request': true, + 'db': PMA_commonParams.get('db'), + 'server': PMA_commonParams.get('server'), + 'tables': newTables, + 'step': '3.1' + }, function (data) { + $('#page_content').find('h3').html(PMA_messages.str3NFNormalization); + $('#mainContent').find('legend').html(data.legendText); + $('#mainContent').find('h4').html(data.headText); + $('#mainContent').find('p').html(data.subText); + $('#mainContent').find('#extra').html(data.extra); + $('#extra').find('form').each(function () { + var form_id = $(this).attr('id'); + var colname = $(this).data('colname'); + $('#' + form_id + ' input[value=\'' + colname + '\']').next().remove(); + $('#' + form_id + ' input[value=\'' + colname + '\']').remove(); + }); + $('#mainContent').find('#newCols').html(''); + $('.tblFooters').html(''); + + if (data.subText !== '') { + $('') + .attr({ type: 'button', value: PMA_messages.strDone }) + .on('click', function () { + processDependencies('', true); + }) + .appendTo('.tblFooters'); + } + } + ); +} +function goTo2NFStep1 () { + $.post( + 'normalization.php', + { + 'ajax_request': true, + 'db': PMA_commonParams.get('db'), + 'table': PMA_commonParams.get('table'), + 'server': PMA_commonParams.get('server'), + 'step': '2.1' + }, function (data) { + $('#page_content h3').html(PMA_messages.str2NFNormalization); + $('#mainContent legend').html(data.legendText); + $('#mainContent h4').html(data.headText); + $('#mainContent p').html(data.subText); + $('#mainContent #extra').html(data.extra); + $('#mainContent #newCols').html(''); + if (data.subText !== '') { + var doneButton = $('') + .attr({ type: 'submit', value: PMA_messages.strDone, }) + .on('click', function () { + processDependencies(data.primary_key); + }) + .appendTo('.tblFooters'); + } else { + if (normalizeto === '3nf') { + $('#mainContent #newCols').html(PMA_messages.strToNextStep); + setTimeout(function () { + goTo3NFStep1([PMA_commonParams.get('table')]); + }, 3000); + } + } + }); +} + +function goToFinish1NF () { + if (normalizeto !== '1nf') { + goTo2NFStep1(); + return true; + } + $('#mainContent legend').html(PMA_messages.strEndStep); + $('#mainContent h4').html( + '

          ' + PMA_sprintf(PMA_messages.strFinishMsg, escapeHtml(PMA_commonParams.get('table'))) + '

          ' + ); + $('#mainContent p').html(''); + $('#mainContent #extra').html(''); + $('#mainContent #newCols').html(''); + $('.tblFooters').html(''); +} + +function goToStep4 () { + $.post( + 'normalization.php', + { + 'ajax_request': true, + 'db': PMA_commonParams.get('db'), + 'table': PMA_commonParams.get('table'), + 'server': PMA_commonParams.get('server'), + 'step4': true + }, function (data) { + $('#mainContent legend').html(data.legendText); + $('#mainContent h4').html(data.headText); + $('#mainContent p').html(data.subText); + $('#mainContent #extra').html(data.extra); + $('#mainContent #newCols').html(''); + $('.tblFooters').html(''); + for (var pk in primary_key) { + $('#extra input[value=\'' + escapeJsString(primary_key[pk]) + '\']').attr('disabled','disabled'); + } + } + ); +} + +function goToStep3 () { + $.post( + 'normalization.php', + { + 'ajax_request': true, + 'db': PMA_commonParams.get('db'), + 'table': PMA_commonParams.get('table'), + 'server': PMA_commonParams.get('server'), + 'step3': true + }, function (data) { + $('#mainContent legend').html(data.legendText); + $('#mainContent h4').html(data.headText); + $('#mainContent p').html(data.subText); + $('#mainContent #extra').html(data.extra); + $('#mainContent #newCols').html(''); + $('.tblFooters').html(''); + primary_key = JSON.parse(data.primary_key); + for (var pk in primary_key) { + $('#extra input[value=\'' + escapeJsString(primary_key[pk]) + '\']').attr('disabled','disabled'); + } + } + ); +} + +function goToStep2 (extra) { + $.post( + 'normalization.php', + { + 'ajax_request': true, + 'db': PMA_commonParams.get('db'), + 'table': PMA_commonParams.get('table'), + 'server': PMA_commonParams.get('server'), + 'step2': true + }, function (data) { + $('#mainContent legend').html(data.legendText); + $('#mainContent h4').html(data.headText); + $('#mainContent p').html(data.subText); + $('#mainContent #extra,#mainContent #newCols').html(''); + $('.tblFooters').html(''); + if (data.hasPrimaryKey === '1') { + if (extra === 'goToStep3') { + $('#mainContent h4').html(PMA_messages.strPrimaryKeyAdded); + $('#mainContent p').html(PMA_messages.strToNextStep); + } + if (extra === 'goToFinish1NF') { + goToFinish1NF(); + } else { + setTimeout(function () { + goToStep3(); + }, 3000); + } + } else { + // form to select columns to make primary + $('#mainContent #extra').html(data.extra); + } + } + ); +} + +function goTo2NFFinish (pd) { + var tables = {}; + for (var dependson in pd) { + tables[dependson] = $('#extra input[name="' + dependson + '"]').val(); + } + datastring = { + 'ajax_request': true, + 'db': PMA_commonParams.get('db'), + 'table': PMA_commonParams.get('table'), + 'server': PMA_commonParams.get('server'), + 'pd': JSON.stringify(pd), + 'newTablesName':JSON.stringify(tables), + 'createNewTables2NF':1 }; + $.ajax({ + type: 'POST', + url: 'normalization.php', + data: datastring, + async:false, + success: function (data) { + if (data.success === true) { + if (data.queryError === false) { + if (normalizeto === '3nf') { + $('#pma_navigation_reload').click(); + goTo3NFStep1(tables); + return true; + } + $('#mainContent legend').html(data.legendText); + $('#mainContent h4').html(data.headText); + $('#mainContent p').html(''); + $('#mainContent #extra').html(''); + $('.tblFooters').html(''); + } else { + PMA_ajaxShowMessage(data.extra, false); + } + $('#pma_navigation_reload').click(); + } else { + PMA_ajaxShowMessage(data.error, false); + } + } + }); +} + +function goTo3NFFinish (newTables) { + for (var table in newTables) { + for (var newtbl in newTables[table]) { + var updatedname = $('#extra input[name="' + newtbl + '"]').val(); + newTables[table][updatedname] = newTables[table][newtbl]; + if (updatedname !== newtbl) { + delete newTables[table][newtbl]; + } + } + } + datastring = { + 'ajax_request': true, + 'db': PMA_commonParams.get('db'), + 'server': PMA_commonParams.get('server'), + 'newTables':JSON.stringify(newTables), + 'createNewTables3NF':1 }; + $.ajax({ + type: 'POST', + url: 'normalization.php', + data: datastring, + async:false, + success: function (data) { + if (data.success === true) { + if (data.queryError === false) { + $('#mainContent legend').html(data.legendText); + $('#mainContent h4').html(data.headText); + $('#mainContent p').html(''); + $('#mainContent #extra').html(''); + $('.tblFooters').html(''); + } else { + PMA_ajaxShowMessage(data.extra, false); + } + $('#pma_navigation_reload').click(); + } else { + PMA_ajaxShowMessage(data.error, false); + } + } + }); +} +var backup = ''; +function goTo2NFStep2 (pd, primary_key) { + $('#newCols').html(''); + $('#mainContent legend').html(PMA_messages.strStep + ' 2.2 ' + PMA_messages.strConfirmPd); + $('#mainContent h4').html(PMA_messages.strSelectedPd); + $('#mainContent p').html(PMA_messages.strPdHintNote); + var extra = '
          '; + var pdFound = false; + for (var dependson in pd) { + if (dependson !== primary_key) { + pdFound = true; + extra += '

          ' + escapeHtml(dependson) + ' -> ' + escapeHtml(pd[dependson].toString()) + '

          '; + } + } + if (!pdFound) { + extra += '

          ' + PMA_messages.strNoPdSelected + '

          '; + extra += '
          '; + } else { + extra += '
          '; + datastring = { + 'ajax_request': true, + 'db': PMA_commonParams.get('db'), + 'table': PMA_commonParams.get('table'), + 'server': PMA_commonParams.get('server'), + 'pd': JSON.stringify(pd), + 'getNewTables2NF':1 }; + $.ajax({ + type: 'POST', + url: 'normalization.php', + data: datastring, + async:false, + success: function (data) { + if (data.success === true) { + extra += data.message; + } else { + PMA_ajaxShowMessage(data.error, false); + } + } + }); + } + $('#mainContent #extra').html(extra); + $('.tblFooters').html(''); + $('#goTo2NFFinish').click(function () { + goTo2NFFinish(pd); + }); +} + +function goTo3NFStep2 (pd, tablesTds) { + $('#newCols').html(''); + $('#mainContent legend').html(PMA_messages.strStep + ' 3.2 ' + PMA_messages.strConfirmTd); + $('#mainContent h4').html(PMA_messages.strSelectedTd); + $('#mainContent p').html(PMA_messages.strPdHintNote); + var extra = '
          '; + var pdFound = false; + for (var table in tablesTds) { + for (var i in tablesTds[table]) { + dependson = tablesTds[table][i]; + if (dependson !== '' && dependson !== table) { + pdFound = true; + extra += '

          ' + escapeHtml(dependson) + ' -> ' + escapeHtml(pd[dependson].toString()) + '

          '; + } + } + } + if (!pdFound) { + extra += '

          ' + PMA_messages.strNoTdSelected + '

          '; + extra += '
          '; + } else { + extra += '
    • '; + datastring = { + 'ajax_request': true, + 'db': PMA_commonParams.get('db'), + 'tables': JSON.stringify(tablesTds), + 'server': PMA_commonParams.get('server'), + 'pd': JSON.stringify(pd), + 'getNewTables3NF':1 }; + $.ajax({ + type: 'POST', + url: 'normalization.php', + data: datastring, + async:false, + success: function (data) { + data_parsed = data; + if (data.success === true) { + extra += data_parsed.html; + } else { + PMA_ajaxShowMessage(data.error, false); + } + } + }); + } + $('#mainContent #extra').html(extra); + $('.tblFooters').html(''); + $('#goTo3NFFinish').click(function () { + if (!pdFound) { + goTo3NFFinish([]); + } else { + goTo3NFFinish(data_parsed.newTables); + } + }); +} +function processDependencies (primary_key, isTransitive) { + var pd = {}; + var tablesTds = {}; + var dependsOn; + pd[primary_key] = []; + $('#extra form').each(function () { + var tblname; + if (isTransitive === true) { + tblname = $(this).data('tablename'); + primary_key = tblname; + if (!(tblname in tablesTds)) { + tablesTds[tblname] = []; + } + tablesTds[tblname].push(primary_key); + } + var form_id = $(this).attr('id'); + $('#' + form_id + ' input[type=checkbox]:not(:checked)').prop('checked', false); + dependsOn = ''; + $('#' + form_id + ' input[type=checkbox]:checked').each(function () { + dependsOn += $(this).val() + ', '; + $(this).attr('checked','checked'); + }); + if (dependsOn === '') { + dependsOn = primary_key; + } else { + dependsOn = dependsOn.slice(0, -2); + } + if (! (dependsOn in pd)) { + pd[dependsOn] = []; + } + pd[dependsOn].push($(this).data('colname')); + if (isTransitive === true) { + if (!(tblname in tablesTds)) { + tablesTds[tblname] = []; + } + if ($.inArray(dependsOn, tablesTds[tblname]) === -1) { + tablesTds[tblname].push(dependsOn); + } + } + }); + backup = $('#mainContent').html(); + if (isTransitive === true) { + goTo3NFStep2(pd, tablesTds); + } else { + goTo2NFStep2(pd, primary_key); + } + return false; +} + +function moveRepeatingGroup (repeatingCols) { + var newTable = $('input[name=repeatGroupTable]').val(); + var newColumn = $('input[name=repeatGroupColumn]').val(); + if (!newTable) { + $('input[name=repeatGroupTable]').focus(); + return false; + } + if (!newColumn) { + $('input[name=repeatGroupColumn]').focus(); + return false; + } + datastring = { + 'ajax_request': true, + 'db': PMA_commonParams.get('db'), + 'table': PMA_commonParams.get('table'), + 'server': PMA_commonParams.get('server'), + 'repeatingColumns': repeatingCols, + 'newTable':newTable, + 'newColumn':newColumn, + 'primary_columns':primary_key.toString() + }; + $.ajax({ + type: 'POST', + url: 'normalization.php', + data: datastring, + async:false, + success: function (data) { + if (data.success === true) { + if (data.queryError === false) { + goToStep3(); + } + PMA_ajaxShowMessage(data.message, false); + $('#pma_navigation_reload').click(); + } else { + PMA_ajaxShowMessage(data.error, false); + } + } + }); +} +AJAX.registerTeardown('normalization.js', function () { + $('#extra').off('click', '#selectNonAtomicCol'); + $('#splitGo').off('click'); + $('.tblFooters').off('click', '#saveSplit'); + $('#extra').off('click', '#addNewPrimary'); + $('.tblFooters').off('click', '#saveNewPrimary'); + $('#extra').off('click', '#removeRedundant'); + $('#mainContent p').off('click', '#createPrimaryKey'); + $('#mainContent').off('click', '#backEditPd'); + $('#mainContent').off('click', '#showPossiblePd'); + $('#mainContent').off('click', '.pickPd'); +}); + +AJAX.registerOnload('normalization.js', function () { + var selectedCol; + normalizeto = $('#mainContent').data('normalizeto'); + $('#extra').on('click', '#selectNonAtomicCol', function () { + if ($(this).val() === 'no_such_col') { + goToStep2(); + } else { + selectedCol = $(this).val(); + } + }); + + $('#splitGo').click(function () { + if (!selectedCol || selectedCol === '') { + return false; + } + var numField = $('#numField').val(); + $.post( + 'normalization.php', + { + 'ajax_request': true, + 'db': PMA_commonParams.get('db'), + 'table': PMA_commonParams.get('table'), + 'server': PMA_commonParams.get('server'), + 'splitColumn': true, + 'numFields': numField + }, + function (data) { + if (data.success === true) { + $('#newCols').html(data.message); + $('.default_value').hide(); + $('.enum_notice').hide(); + + $('') + .attr({ type: 'submit', id: 'saveSplit', value: PMA_messages.strSave }) + .appendTo('.tblFooters'); + + var cancelSplitButton = $('') + .attr({ type: 'submit', id: 'cancelSplit', value: PMA_messages.strCancel }) + .on('click', function () { + $('#newCols').html(''); + $(this).parent().html(''); + }) + .appendTo('.tblFooters'); + } + } + ); + return false; + }); + $('.tblFooters').on('click','#saveSplit', function () { + central_column_list = []; + if ($('#newCols #field_0_1').val() === '') { + $('#newCols #field_0_1').focus(); + return false; + } + var argsep = PMA_commonParams.get('arg_separator'); + datastring = $('#newCols :input').serialize(); + datastring += argsep + 'ajax_request=1' + argsep + 'do_save_data=1' + argsep + 'field_where=last'; + $.post('tbl_addfield.php', datastring, function (data) { + if (data.success) { + $.post( + 'sql.php', + { + 'ajax_request': true, + 'db': PMA_commonParams.get('db'), + 'table': PMA_commonParams.get('table'), + 'server': PMA_commonParams.get('server'), + 'dropped_column': selectedCol, + 'purge' : 1, + 'sql_query': 'ALTER TABLE `' + PMA_commonParams.get('table') + '` DROP `' + selectedCol + '`;', + 'is_js_confirmed': 1 + }, + function (data) { + if (data.success === true) { + appendHtmlColumnsList(); + $('#newCols').html(''); + $('.tblFooters').html(''); + } else { + PMA_ajaxShowMessage(data.error, false); + } + selectedCol = ''; + } + ); + } else { + PMA_ajaxShowMessage(data.error, false); + } + }); + }); + + $('#extra').on('click', '#addNewPrimary', function () { + $.post( + 'normalization.php', + { + 'ajax_request': true, + 'db': PMA_commonParams.get('db'), + 'table': PMA_commonParams.get('table'), + 'server': PMA_commonParams.get('server'), + 'addNewPrimary': true + }, + function (data) { + if (data.success === true) { + $('#newCols').html(data.message); + $('.default_value').hide(); + $('.enum_notice').hide(); + + $('') + .attr({ type: 'submit', id: 'saveNewPrimary', value: PMA_messages.strSave }) + .appendTo('.tblFooters'); + $('') + .attr({ type: 'submit', id: 'cancelSplit', value: PMA_messages.strCancel }) + .on('click', function () { + $('#newCols').html(''); + $(this).parent().html(''); + }) + .appendTo('.tblFooters'); + } else { + PMA_ajaxShowMessage(data.error, false); + } + } + ); + return false; + }); + $('.tblFooters').on('click', '#saveNewPrimary', function () { + var datastring = $('#newCols :input').serialize(); + var argsep = PMA_commonParams.get('arg_separator'); + datastring += argsep + 'field_key[0]=primary_0' + argsep + 'ajax_request=1' + argsep + 'do_save_data=1' + argsep + 'field_where=last'; + $.post('tbl_addfield.php', datastring, function (data) { + if (data.success === true) { + $('#mainContent h4').html(PMA_messages.strPrimaryKeyAdded); + $('#mainContent p').html(PMA_messages.strToNextStep); + $('#mainContent #extra').html(''); + $('#mainContent #newCols').html(''); + $('.tblFooters').html(''); + setTimeout(function () { + goToStep3(); + }, 2000); + } else { + PMA_ajaxShowMessage(data.error, false); + } + }); + }); + $('#extra').on('click', '#removeRedundant', function () { + var dropQuery = 'ALTER TABLE `' + PMA_commonParams.get('table') + '` '; + $('#extra input[type=checkbox]:checked').each(function () { + dropQuery += 'DROP `' + $(this).val() + '`, '; + }); + dropQuery = dropQuery.slice(0, -2); + $.post( + 'sql.php', + { + 'ajax_request': true, + 'db': PMA_commonParams.get('db'), + 'table': PMA_commonParams.get('table'), + 'server': PMA_commonParams.get('server'), + 'sql_query': dropQuery, + 'is_js_confirmed': 1 + }, + function (data) { + if (data.success === true) { + goToStep2('goToFinish1NF'); + } else { + PMA_ajaxShowMessage(data.error, false); + } + } + ); + }); + $('#extra').on('click', '#moveRepeatingGroup', function () { + var repeatingCols = ''; + $('#extra input[type=checkbox]:checked').each(function () { + repeatingCols += $(this).val() + ', '; + }); + + if (repeatingCols !== '') { + var newColName = $('#extra input[type=checkbox]:checked:first').val(); + repeatingCols = repeatingCols.slice(0, -2); + var confirmStr = PMA_sprintf(PMA_messages.strMoveRepeatingGroup, escapeHtml(repeatingCols), escapeHtml(PMA_commonParams.get('table'))); + confirmStr += '' + + '( ' + escapeHtml(primary_key.toString()) + ', )' + + ''; + $('#newCols').html(confirmStr); + + $('') + .attr({ type: 'submit', value: PMA_messages.strCancel }) + .on('click', function () { + $('#newCols').html(''); + $('#extra input[type=checkbox]').prop('checked', false); + }) + .appendTo('.tblFooters'); + $('') + .attr({ type: 'submit', value: PMA_messages.strGo }) + .on('click', function () { + moveRepeatingGroup(repeatingCols); + }) + .appendTo('.tblFooters'); + } + }); + $('#mainContent p').on('click', '#createPrimaryKey', function (event) { + event.preventDefault(); + var url = { create_index: 1, + server: PMA_commonParams.get('server'), + db: PMA_commonParams.get('db'), + table: PMA_commonParams.get('table'), + added_fields: 1, + add_fields:1, + index: { Key_name:'PRIMARY' }, + ajax_request: true + }; + var title = PMA_messages.strAddPrimaryKey; + indexEditorDialog(url, title, function () { + // on success + $('.sqlqueryresults').remove(); + $('.result_query').remove(); + $('.tblFooters').html(''); + goToStep2('goToStep3'); + }); + return false; + }); + $('#mainContent').on('click', '#backEditPd', function () { + $('#mainContent').html(backup); + }); + $('#mainContent').on('click', '#showPossiblePd', function () { + if ($(this).hasClass('hideList')) { + $(this).html('+ ' + PMA_messages.strShowPossiblePd); + $(this).removeClass('hideList'); + $('#newCols').slideToggle('slow'); + return false; + } + if ($('#newCols').html() !== '') { + $('#showPossiblePd').html('- ' + PMA_messages.strHidePd); + $('#showPossiblePd').addClass('hideList'); + $('#newCols').slideToggle('slow'); + return false; + } + $('#newCols').insertAfter('#mainContent h4'); + $('#newCols').html('
      ' + PMA_messages.strLoading + '
      ' + PMA_messages.strWaitForPd + '
      '); + $.post( + 'normalization.php', + { + 'ajax_request': true, + 'db': PMA_commonParams.get('db'), + 'table': PMA_commonParams.get('table'), + 'server': PMA_commonParams.get('server'), + 'findPdl': true + }, function (data) { + $('#showPossiblePd').html('- ' + PMA_messages.strHidePd); + $('#showPossiblePd').addClass('hideList'); + $('#newCols').html(data.message); + }); + }); + $('#mainContent').on('click', '.pickPd', function () { + var strColsLeft = $(this).next('.determinants').html(); + var colsLeft = strColsLeft.split(','); + var strColsRight = $(this).next().next().html(); + var colsRight = strColsRight.split(','); + for (var i in colsRight) { + $('form[data-colname="' + colsRight[i].trim() + '"] input[type="checkbox"]').prop('checked', false); + for (var j in colsLeft) { + $('form[data-colname="' + colsRight[i].trim() + '"] input[value="' + colsLeft[j].trim() + '"]').prop('checked', true); + } + } + }); +}); diff --git a/php/apps/phpmyadmin49/js/page_settings.js b/php/apps/phpmyadmin49/js/page_settings.js new file mode 100644 index 00000000..7de9c038 --- /dev/null +++ b/php/apps/phpmyadmin49/js/page_settings.js @@ -0,0 +1,59 @@ +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * @fileoverview function used for page-related settings + * @name Page-related settings + * + * @requires jQuery + * @requires jQueryUI + * @required js/functions.js + */ + +function showSettings (selector) { + var buttons = {}; + buttons[PMA_messages.strApply] = function () { + $('.config-form').submit(); + }; + + buttons[PMA_messages.strCancel] = function () { + $(this).dialog('close'); + }; + + // Keeping a clone to restore in case the user cancels the operation + var $clone = $(selector + ' .page_settings').clone(true); + $(selector) + .dialog({ + title: PMA_messages.strPageSettings, + width: 700, + minHeight: 250, + modal: true, + open: function () { + $(this).dialog('option', 'maxHeight', $(window).height() - $(this).offset().top); + }, + close: function () { + $(selector + ' .page_settings').replaceWith($clone); + }, + buttons: buttons + }); +} + +function showPageSettings () { + showSettings('#page_settings_modal'); +} + +function showNaviSettings () { + showSettings('#pma_navigation_settings'); +} + +AJAX.registerTeardown('page_settings.js', function () { + $('#page_settings_icon').css('display', 'none'); + $('#page_settings_icon').off('click'); + $('#pma_navigation_settings_icon').off('click'); +}); + +AJAX.registerOnload('page_settings.js', function () { + if ($('#page_settings_modal').length) { + $('#page_settings_icon').css('display', 'inline'); + $('#page_settings_icon').on('click', showPageSettings); + } + $('#pma_navigation_settings_icon').on('click', showNaviSettings); +}); diff --git a/php/apps/phpmyadmin49/js/replication.js b/php/apps/phpmyadmin49/js/replication.js new file mode 100644 index 00000000..602a5a5e --- /dev/null +++ b/php/apps/phpmyadmin49/js/replication.js @@ -0,0 +1,92 @@ +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * for server_replication.php + * + */ + +var random_server_id = Math.floor(Math.random() * 10000000); +var conf_prefix = 'server-id=' + random_server_id + '\nlog_bin=mysql-bin\nlog_error=mysql-bin.err\n'; + +function update_config () { + var conf_ignore = 'binlog_ignore_db='; + var conf_do = 'binlog_do_db='; + var database_list = ''; + + if ($('#db_select option:selected').size() === 0) { + $('#rep').text(conf_prefix); + } else if ($('#db_type option:selected').val() === 'all') { + $('#db_select option:selected').each(function () { + database_list += conf_ignore + $(this).val() + '\n'; + }); + $('#rep').text(conf_prefix + database_list); + } else { + $('#db_select option:selected').each(function () { + database_list += conf_do + $(this).val() + '\n'; + }); + $('#rep').text(conf_prefix + database_list); + } +} + +/** + * Unbind all event handlers before tearing down a page + */ +AJAX.registerTeardown('replication.js', function () { + $('#db_type').off('change'); + $('#db_select').off('change'); + $('#master_status_href').off('click'); + $('#master_slaves_href').off('click'); + $('#slave_status_href').off('click'); + $('#slave_control_href').off('click'); + $('#slave_errormanagement_href').off('click'); + $('#slave_synchronization_href').off('click'); + $('#db_reset_href').off('click'); + $('#db_select_href').off('click'); + $('#reset_slave').off('click'); +}); + +AJAX.registerOnload('replication.js', function () { + $('#rep').text(conf_prefix); + $('#db_type').change(update_config); + $('#db_select').change(update_config); + + $('#master_status_href').click(function () { + $('#replication_master_section').toggle(); + }); + $('#master_slaves_href').click(function () { + $('#replication_slaves_section').toggle(); + }); + $('#slave_status_href').click(function () { + $('#replication_slave_section').toggle(); + }); + $('#slave_control_href').click(function () { + $('#slave_control_gui').toggle(); + }); + $('#slave_errormanagement_href').click(function () { + $('#slave_errormanagement_gui').toggle(); + }); + $('#slave_synchronization_href').click(function () { + $('#slave_synchronization_gui').toggle(); + }); + $('#db_reset_href').click(function () { + $('#db_select option:selected').prop('selected', false); + $('#db_select').trigger('change'); + }); + $('#db_select_href').click(function () { + $('#db_select option').prop('selected', true); + $('#db_select').trigger('change'); + }); + $('#reset_slave').click(function (e) { + e.preventDefault(); + var $anchor = $(this); + var question = PMA_messages.strResetSlaveWarning; + $anchor.PMA_confirm(question, $anchor.attr('href'), function (url) { + PMA_ajaxShowMessage(); + AJAX.source = $anchor; + var params = getJSConfirmCommonParam({ + 'ajax_page_request': true, + 'ajax_request': true + }, $anchor.getPostData()); + $.post(url, params, AJAX.responseHandler); + }); + }); +}); diff --git a/php/apps/phpmyadmin49/js/rte.js b/php/apps/phpmyadmin49/js/rte.js new file mode 100644 index 00000000..d51538b4 --- /dev/null +++ b/php/apps/phpmyadmin49/js/rte.js @@ -0,0 +1,1075 @@ +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * JavaScript functionality for Routines, Triggers and Events. + * + * @package PhpMyadmin + */ +/** + * @var RTE Contains all the JavaScript functionality + * for Routines, Triggers and Events + */ +var RTE = { + /** + * Construct for the object that provides the + * functionality for Routines, Triggers and Events + */ + object: function (type) { + $.extend(this, RTE.COMMON); + this.editorType = type; + + switch (type) { + case 'routine': + $.extend(this, RTE.ROUTINE); + break; + case 'trigger': + // nothing extra yet for triggers + break; + case 'event': + $.extend(this, RTE.EVENT); + break; + default: + break; + } + }, + /** + * @var string param_template Template for a row in the routine editor + */ + param_template: '' +}; + +/** + * @var RTE.COMMON a JavaScript namespace containing the functionality + * for Routines, Triggers and Events + * + * This namespace is extended by the functionality required + * to handle a specific item (a routine, trigger or event) + * in the relevant javascript files in this folder + */ +RTE.COMMON = { + /** + * @var $ajaxDialog Query object containing the reference to the + * dialog that contains the editor + */ + $ajaxDialog: null, + /** + * @var syntaxHiglighter Reference to the codemirror editor + */ + syntaxHiglighter: null, + /** + * @var buttonOptions Object containing options for + * the jQueryUI dialog buttons + */ + buttonOptions: {}, + /** + * @var editorType Type of the editor + */ + editorType: null, + /** + * Validate editor form fields. + */ + validate: function () { + /** + * @var $elm a jQuery object containing the reference + * to an element that is being validated + */ + var $elm = null; + // Common validation. At the very least the name + // and the definition must be provided for an item + $elm = $('table.rte_table').last().find('input[name=item_name]'); + if ($elm.val() === '') { + $elm.focus(); + alert(PMA_messages.strFormEmpty); + return false; + } + $elm = $('table.rte_table').find('textarea[name=item_definition]'); + if ($elm.val() === '') { + if (this.syntaxHiglighter !== null) { + this.syntaxHiglighter.focus(); + } else { + $('textarea[name=item_definition]').last().focus(); + } + alert(PMA_messages.strFormEmpty); + return false; + } + // The validation has so far passed, so now + // we can validate item-specific fields. + return this.validateCustom(); + }, // end validate() + /** + * Validate custom editor form fields. + * This function can be overridden by + * other files in this folder + */ + validateCustom: function () { + return true; + }, // end validateCustom() + /** + * Execute some code after the ajax + * dialog for the editor is shown. + * This function can be overridden by + * other files in this folder + */ + postDialogShow: function () { + // Nothing by default + }, // end postDialogShow() + + exportDialog: function ($this) { + var $msg = PMA_ajaxShowMessage(); + if ($this.hasClass('mult_submit')) { + var combined = { + success: true, + title: PMA_messages.strExport, + message: '', + error: '' + }; + // export anchors of all selected rows + var export_anchors = $('input.checkall:checked').parents('tr').find('.export_anchor'); + var count = export_anchors.length; + var returnCount = 0; + + // No routine is exportable (due to privilege issues) + if (count === 0) { + PMA_ajaxShowMessage(PMA_messages.NoExportable); + } + + export_anchors.each(function () { + $.get($(this).attr('href'), { 'ajax_request': true }, function (data) { + returnCount++; + if (data.success === true) { + combined.message += '\n' + data.message + '\n'; + if (returnCount === count) { + showExport(combined); + } + } else { + // complain even if one export is failing + combined.success = false; + combined.error += '\n' + data.error + '\n'; + if (returnCount === count) { + showExport(combined); + } + } + }); + }); + } else { + $.get($this.attr('href'), { 'ajax_request': true }, showExport); + } + PMA_ajaxRemoveMessage($msg); + + function showExport (data) { + if (data.success === true) { + PMA_ajaxRemoveMessage($msg); + /** + * @var button_options Object containing options + * for jQueryUI dialog buttons + */ + var button_options = {}; + button_options[PMA_messages.strClose] = function () { + $(this).dialog('close').remove(); + }; + /** + * Display the dialog to the user + */ + data.message = ''; + var $ajaxDialog = $('
      ' + data.message + '
      ').dialog({ + width: 500, + buttons: button_options, + title: data.title + }); + // Attach syntax highlighted editor to export dialog + /** + * @var $elm jQuery object containing the reference + * to the Export textarea. + */ + var $elm = $ajaxDialog.find('textarea'); + PMA_getSQLEditor($elm); + } else { + PMA_ajaxShowMessage(data.error, false); + } + } // end showExport() + }, // end exportDialog() + editorDialog: function (is_new, $this) { + var that = this; + /** + * @var $edit_row jQuery object containing the reference to + * the row of the the item being edited + * from the list of items + */ + var $edit_row = null; + if ($this.hasClass('edit_anchor')) { + // Remeber the row of the item being edited for later, + // so that if the edit is successful, we can replace the + // row with info about the modified item. + $edit_row = $this.parents('tr'); + } + /** + * @var $msg jQuery object containing the reference to + * the AJAX message shown to the user + */ + var $msg = PMA_ajaxShowMessage(); + $.get($this.attr('href'), { 'ajax_request': true }, function (data) { + if (data.success === true) { + // We have successfully fetched the editor form + PMA_ajaxRemoveMessage($msg); + // Now define the function that is called when + // the user presses the "Go" button + that.buttonOptions[PMA_messages.strGo] = function () { + // Move the data from the codemirror editor back to the + // textarea, where it can be used in the form submission. + if (typeof CodeMirror !== 'undefined') { + that.syntaxHiglighter.save(); + } + // Validate editor and submit request, if passed. + if (that.validate()) { + /** + * @var data Form data to be sent in the AJAX request + */ + var data = $('form.rte_form').last().serialize(); + $msg = PMA_ajaxShowMessage( + PMA_messages.strProcessingRequest + ); + var url = $('form.rte_form').last().attr('action'); + $.post(url, data, function (data) { + if (data.success === true) { + // Item created successfully + PMA_ajaxRemoveMessage($msg); + PMA_slidingMessage(data.message); + that.$ajaxDialog.dialog('close'); + // If we are in 'edit' mode, we must + // remove the reference to the old row. + if (mode === 'edit' && $edit_row !== null) { + $edit_row.remove(); + } + // Sometimes, like when moving a trigger from + // a table to another one, the new row should + // not be inserted into the list. In this case + // "data.insert" will be set to false. + if (data.insert) { + // Insert the new row at the correct + // location in the list of items + /** + * @var text Contains the name of an item from + * the list that is used in comparisons + * to find the correct location where + * to insert a new row. + */ + var text = ''; + /** + * @var inserted Whether a new item has been + * inserted in the list or not + */ + var inserted = false; + $('table.data').find('tr').each(function () { + text = $(this) + .children('td') + .eq(0) + .find('strong') + .text() + .toUpperCase(); + text = $.trim(text); + if (text !== '' && text > data.name) { + $(this).before(data.new_row); + inserted = true; + return false; + } + }); + if (! inserted) { + // If we didn't manage to insert the row yet, + // it must belong at the end of the list, + // so we insert it there. + $('table.data').append(data.new_row); + } + // Fade-in the new row + $('tr.ajaxInsert') + .show('slow') + .removeClass('ajaxInsert'); + } else if ($('table.data').find('tr').has('td').length === 0) { + // If we are not supposed to insert the new row, + // we will now check if the table is empty and + // needs to be hidden. This will be the case if + // we were editing the only item in the list, + // which we removed and will not be inserting + // something else in its place. + $('table.data').hide('slow', function () { + $('#nothing2display').show('slow'); + }); + } + // Now we have inserted the row at the correct + // position, but surely at least some row classes + // are wrong now. So we will itirate throught + // all rows and assign correct classes to them + /** + * @var ct Count of processed rows + */ + var ct = 0; + /** + * @var rowclass Class to be attached to the row + * that is being processed + */ + var rowclass = ''; + $('table.data').find('tr').has('td').each(function () { + rowclass = (ct % 2 === 0) ? 'odd' : 'even'; + $(this).removeClass().addClass(rowclass); + ct++; + }); + // If this is the first item being added, remove + // the "No items" message and show the list. + if ($('table.data').find('tr').has('td').length > 0 && + $('#nothing2display').is(':visible') + ) { + $('#nothing2display').hide('slow', function () { + $('table.data').show('slow'); + }); + } + PMA_reloadNavigation(); + } else { + PMA_ajaxShowMessage(data.error, false); + } + }); // end $.post() + } // end "if (that.validate())" + }; // end of function that handles the submission of the Editor + that.buttonOptions[PMA_messages.strClose] = function () { + $(this).dialog('close'); + }; + /** + * Display the dialog to the user + */ + that.$ajaxDialog = $('
      ' + data.message + '
      ').dialog({ + width: 700, + minWidth: 500, + maxHeight: $(window).height(), + buttons: that.buttonOptions, + title: data.title, + modal: true, + open: function () { + if ($('#rteDialog').parents('.ui-dialog').height() > $(window).height()) { + $('#rteDialog').dialog('option', 'height', $(window).height()); + } + $(this).find('input[name=item_name]').focus(); + $(this).find('input.datefield').each(function () { + PMA_addDatepicker($(this).css('width', '95%'), 'date'); + }); + $(this).find('input.datetimefield').each(function () { + PMA_addDatepicker($(this).css('width', '95%'), 'datetime'); + }); + $.datepicker.initialized = false; + }, + close: function () { + $(this).remove(); + } + }); + /** + * @var mode Used to remeber whether the editor is in + * "Edit" or "Add" mode + */ + var mode = 'add'; + if ($('input[name=editor_process_edit]').length > 0) { + mode = 'edit'; + } + // Attach syntax highlighted editor to the definition + /** + * @var elm jQuery object containing the reference to + * the Definition textarea. + */ + var $elm = $('textarea[name=item_definition]').last(); + var linterOptions = {}; + linterOptions[that.editorType + '_editor'] = true; + that.syntaxHiglighter = PMA_getSQLEditor($elm, {}, null, linterOptions); + + // Execute item-specific code + that.postDialogShow(data); + } else { + PMA_ajaxShowMessage(data.error, false); + } + }); // end $.get() + }, + + dropDialog: function ($this) { + /** + * @var $curr_row Object containing reference to the current row + */ + var $curr_row = $this.parents('tr'); + /** + * @var question String containing the question to be asked for confirmation + */ + var question = $('
      ').text( + $curr_row.children('td').children('.drop_sql').html() + ); + // We ask for confirmation first here, before submitting the ajax request + $this.PMA_confirm(question, $this.attr('href'), function (url) { + /** + * @var msg jQuery object containing the reference to + * the AJAX message shown to the user + */ + var $msg = PMA_ajaxShowMessage(PMA_messages.strProcessingRequest); + var params = getJSConfirmCommonParam(this, $this.getPostData()); + $.post(url, params, function (data) { + if (data.success === true) { + /** + * @var $table Object containing reference + * to the main list of elements + */ + var $table = $curr_row.parent(); + // Check how many rows will be left after we remove + // the one that the user has requested us to remove + if ($table.find('tr').length === 3) { + // If there are two rows left, it means that they are + // the header of the table and the rows that we are + // about to remove, so after the removal there will be + // nothing to show in the table, so we hide it. + $table.hide('slow', function () { + $(this).find('tr.even, tr.odd').remove(); + $('.withSelected').remove(); + $('#nothing2display').show('slow'); + }); + } else { + $curr_row.hide('slow', function () { + $(this).remove(); + // Now we have removed the row from the list, but maybe + // some row classes are wrong now. So we will itirate + // throught all rows and assign correct classes to them. + /** + * @var ct Count of processed rows + */ + var ct = 0; + /** + * @var rowclass Class to be attached to the row + * that is being processed + */ + var rowclass = ''; + $table.find('tr').has('td').each(function () { + rowclass = (ct % 2 === 1) ? 'odd' : 'even'; + $(this).removeClass().addClass(rowclass); + ct++; + }); + }); + } + // Get rid of the "Loading" message + PMA_ajaxRemoveMessage($msg); + // Show the query that we just executed + PMA_slidingMessage(data.sql_query); + PMA_reloadNavigation(); + } else { + PMA_ajaxShowMessage(data.error, false); + } + }); // end $.post() + }); // end $.PMA_confirm() + }, + + dropMultipleDialog: function ($this) { + // We ask for confirmation here + $this.PMA_confirm(PMA_messages.strDropRTEitems, '', function (url) { + /** + * @var msg jQuery object containing the reference to + * the AJAX message shown to the user + */ + var $msg = PMA_ajaxShowMessage(PMA_messages.strProcessingRequest); + + // drop anchors of all selected rows + var drop_anchors = $('input.checkall:checked').parents('tr').find('.drop_anchor'); + var success = true; + var count = drop_anchors.length; + var returnCount = 0; + + drop_anchors.each(function () { + var $anchor = $(this); + /** + * @var $curr_row Object containing reference to the current row + */ + var $curr_row = $anchor.parents('tr'); + var params = getJSConfirmCommonParam(this, $anchor.getPostData()); + $.post($anchor.attr('href'), params, function (data) { + returnCount++; + if (data.success === true) { + /** + * @var $table Object containing reference + * to the main list of elements + */ + var $table = $curr_row.parent(); + // Check how many rows will be left after we remove + // the one that the user has requested us to remove + if ($table.find('tr').length === 3) { + // If there are two rows left, it means that they are + // the header of the table and the rows that we are + // about to remove, so after the removal there will be + // nothing to show in the table, so we hide it. + $table.hide('slow', function () { + $(this).find('tr.even, tr.odd').remove(); + $('.withSelected').remove(); + $('#nothing2display').show('slow'); + }); + } else { + $curr_row.hide('fast', function () { + $(this).remove(); + // Now we have removed the row from the list, but maybe + // some row classes are wrong now. So we will itirate + // throught all rows and assign correct classes to them. + /** + * @var ct Count of processed rows + */ + var ct = 0; + /** + * @var rowclass Class to be attached to the row + * that is being processed + */ + var rowclass = ''; + $table.find('tr').has('td').each(function () { + rowclass = (ct % 2 === 1) ? 'odd' : 'even'; + $(this).removeClass().addClass(rowclass); + ct++; + }); + }); + } + if (returnCount === count) { + if (success) { + // Get rid of the "Loading" message + PMA_ajaxRemoveMessage($msg); + $('#rteListForm_checkall').prop({ checked: false, indeterminate: false }); + } + PMA_reloadNavigation(); + } + } else { + PMA_ajaxShowMessage(data.error, false); + success = false; + if (returnCount === count) { + PMA_reloadNavigation(); + } + } + }); // end $.post() + }); // end drop_anchors.each() + }); // end $.PMA_confirm() + } +}; // end RTE namespace + +/** + * @var RTE.EVENT JavaScript functionality for events + */ +RTE.EVENT = { + validateCustom: function () { + /** + * @var elm a jQuery object containing the reference + * to an element that is being validated + */ + var $elm = null; + if (this.$ajaxDialog.find('select[name=item_type]').find(':selected').val() === 'RECURRING') { + // The interval field must not be empty for recurring events + $elm = this.$ajaxDialog.find('input[name=item_interval_value]'); + if ($elm.val() === '') { + $elm.focus(); + alert(PMA_messages.strFormEmpty); + return false; + } + } else { + // The execute_at field must not be empty for "once off" events + $elm = this.$ajaxDialog.find('input[name=item_execute_at]'); + if ($elm.val() === '') { + $elm.focus(); + alert(PMA_messages.strFormEmpty); + return false; + } + } + return true; + } +}; + +/** + * @var RTE.ROUTINE JavaScript functionality for routines + */ +RTE.ROUTINE = { + /** + * Overriding the postDialogShow() function defined in common.js + * + * @param data JSON-encoded data from the ajax request + */ + postDialogShow: function (data) { + // Cache the template for a parameter table row + RTE.param_template = data.param_template; + var that = this; + // Make adjustments in the dialog to make it AJAX compatible + $('td.routine_param_remove').show(); + $('input[name=routine_removeparameter]').remove(); + $('input[name=routine_addparameter]').css('width', '100%'); + // Enable/disable the 'options' dropdowns for parameters as necessary + $('table.routine_params_table').last().find('th[colspan=2]').attr('colspan', '1'); + $('table.routine_params_table').last().find('tr').has('td').each(function () { + that.setOptionsForParameter( + $(this).find('select[name^=item_param_type]'), + $(this).find('input[name^=item_param_length]'), + $(this).find('select[name^=item_param_opts_text]'), + $(this).find('select[name^=item_param_opts_num]') + ); + }); + // Enable/disable the 'options' dropdowns for + // function return value as necessary + this.setOptionsForParameter( + $('table.rte_table').last().find('select[name=item_returntype]'), + $('table.rte_table').last().find('input[name=item_returnlength]'), + $('table.rte_table').last().find('select[name=item_returnopts_text]'), + $('table.rte_table').last().find('select[name=item_returnopts_num]') + ); + // Allow changing parameter order + $('.routine_params_table tbody').sortable({ + containment: '.routine_params_table tbody', + handle: '.dragHandle', + stop: function (event, ui) { + that.reindexParameters(); + }, + }); + }, + /** + * Reindexes the parameters after dropping a parameter or reordering parameters + */ + reindexParameters: function () { + /** + * @var index Counter used for reindexing the input + * fields in the routine parameters table + */ + var index = 0; + $('table.routine_params_table tbody').find('tr').each(function () { + $(this).find(':input').each(function () { + /** + * @var inputname The value of the name attribute of + * the input field being reindexed + */ + var inputname = $(this).attr('name'); + if (inputname.substr(0, 14) === 'item_param_dir') { + $(this).attr('name', inputname.substr(0, 14) + '[' + index + ']'); + } else if (inputname.substr(0, 15) === 'item_param_name') { + $(this).attr('name', inputname.substr(0, 15) + '[' + index + ']'); + } else if (inputname.substr(0, 15) === 'item_param_type') { + $(this).attr('name', inputname.substr(0, 15) + '[' + index + ']'); + } else if (inputname.substr(0, 17) === 'item_param_length') { + $(this).attr('name', inputname.substr(0, 17) + '[' + index + ']'); + $(this).attr('id', 'item_param_length_' + index); + } else if (inputname.substr(0, 20) === 'item_param_opts_text') { + $(this).attr('name', inputname.substr(0, 20) + '[' + index + ']'); + } else if (inputname.substr(0, 19) === 'item_param_opts_num') { + $(this).attr('name', inputname.substr(0, 19) + '[' + index + ']'); + } + }); + index++; + }); + }, + /** + * Overriding the validateCustom() function defined in common.js + */ + validateCustom: function () { + /** + * @var isSuccess Stores the outcome of the validation + */ + var isSuccess = true; + /** + * @var inputname The value of the "name" attribute for + * the field that is being processed + */ + var inputname = ''; + this.$ajaxDialog.find('table.routine_params_table').last().find('tr').each(function () { + // Every parameter of a routine must have + // a non-empty direction, name and type + if (isSuccess) { + $(this).find(':input').each(function () { + inputname = $(this).attr('name'); + if (inputname.substr(0, 14) === 'item_param_dir' || + inputname.substr(0, 15) === 'item_param_name' || + inputname.substr(0, 15) === 'item_param_type') { + if ($(this).val() === '') { + $(this).focus(); + isSuccess = false; + return false; + } + } + }); + } else { + return false; + } + }); + if (! isSuccess) { + alert(PMA_messages.strFormEmpty); + return false; + } + this.$ajaxDialog.find('table.routine_params_table').last().find('tr').each(function () { + // SET, ENUM, VARCHAR and VARBINARY fields must have length/values + var $inputtyp = $(this).find('select[name^=item_param_type]'); + var $inputlen = $(this).find('input[name^=item_param_length]'); + if ($inputtyp.length && $inputlen.length) { + if (($inputtyp.val() === 'ENUM' || $inputtyp.val() === 'SET' || $inputtyp.val().substr(0, 3) === 'VAR') && + $inputlen.val() === '' + ) { + $inputlen.focus(); + isSuccess = false; + return false; + } + } + }); + if (! isSuccess) { + alert(PMA_messages.strFormEmpty); + return false; + } + if (this.$ajaxDialog.find('select[name=item_type]').find(':selected').val() === 'FUNCTION') { + // The length/values of return variable for functions must + // be set, if the type is SET, ENUM, VARCHAR or VARBINARY. + var $returntyp = this.$ajaxDialog.find('select[name=item_returntype]'); + var $returnlen = this.$ajaxDialog.find('input[name=item_returnlength]'); + if (($returntyp.val() === 'ENUM' || $returntyp.val() === 'SET' || $returntyp.val().substr(0, 3) === 'VAR') && + $returnlen.val() === '' + ) { + $returnlen.focus(); + alert(PMA_messages.strFormEmpty); + return false; + } + } + if ($('select[name=item_type]').find(':selected').val() === 'FUNCTION') { + // A function must contain a RETURN statement in its definition + if (this.$ajaxDialog.find('table.rte_table').find('textarea[name=item_definition]').val().toUpperCase().indexOf('RETURN') < 0) { + this.syntaxHiglighter.focus(); + alert(PMA_messages.MissingReturn); + return false; + } + } + return true; + }, + /** + * Enable/disable the "options" dropdown and "length" input for + * parameters and the return variable in the routine editor + * as necessary. + * + * @param type a jQuery object containing the reference + * to the "Type" dropdown box + * @param len a jQuery object containing the reference + * to the "Length" input box + * @param text a jQuery object containing the reference + * to the dropdown box with options for + * parameters of text type + * @param num a jQuery object containing the reference + * to the dropdown box with options for + * parameters of numeric type + */ + setOptionsForParameter: function ($type, $len, $text, $num) { + /** + * @var no_opts a jQuery object containing the reference + * to an element to be displayed when no + * options are available + */ + var $no_opts = $text.parent().parent().find('.no_opts'); + /** + * @var no_len a jQuery object containing the reference + * to an element to be displayed when no + * "length/values" field is available + */ + var $no_len = $len.parent().parent().find('.no_len'); + + // Process for parameter options + switch ($type.val()) { + case 'TINYINT': + case 'SMALLINT': + case 'MEDIUMINT': + case 'INT': + case 'BIGINT': + case 'DECIMAL': + case 'FLOAT': + case 'DOUBLE': + case 'REAL': + $text.parent().hide(); + $num.parent().show(); + $no_opts.hide(); + break; + case 'TINYTEXT': + case 'TEXT': + case 'MEDIUMTEXT': + case 'LONGTEXT': + case 'CHAR': + case 'VARCHAR': + case 'SET': + case 'ENUM': + $text.parent().show(); + $num.parent().hide(); + $no_opts.hide(); + break; + default: + $text.parent().hide(); + $num.parent().hide(); + $no_opts.show(); + break; + } + // Process for parameter length + switch ($type.val()) { + case 'DATE': + case 'TINYBLOB': + case 'TINYTEXT': + case 'BLOB': + case 'TEXT': + case 'MEDIUMBLOB': + case 'MEDIUMTEXT': + case 'LONGBLOB': + case 'LONGTEXT': + $text.closest('tr').find('a:first').hide(); + $len.parent().hide(); + $no_len.show(); + break; + default: + if ($type.val() === 'ENUM' || $type.val() === 'SET') { + $text.closest('tr').find('a:first').show(); + } else { + $text.closest('tr').find('a:first').hide(); + } + $len.parent().show(); + $no_len.hide(); + break; + } + }, + executeDialog: function ($this) { + var that = this; + /** + * @var msg jQuery object containing the reference to + * the AJAX message shown to the user + */ + var $msg = PMA_ajaxShowMessage(); + var params = getJSConfirmCommonParam($this[0], $this.getPostData()); + $.post($this.attr('href'), params, function (data) { + if (data.success === true) { + PMA_ajaxRemoveMessage($msg); + // If 'data.dialog' is true we show a dialog with a form + // to get the input parameters for routine, otherwise + // we just show the results of the query + if (data.dialog) { + // Define the function that is called when + // the user presses the "Go" button + that.buttonOptions[PMA_messages.strGo] = function () { + /** + * @var data Form data to be sent in the AJAX request + */ + var data = $('form.rte_form').last().serialize(); + $msg = PMA_ajaxShowMessage( + PMA_messages.strProcessingRequest + ); + $.post('db_routines.php', data, function (data) { + if (data.success === true) { + // Routine executed successfully + PMA_ajaxRemoveMessage($msg); + PMA_slidingMessage(data.message); + $ajaxDialog.dialog('close'); + } else { + PMA_ajaxShowMessage(data.error, false); + } + }); + }; + that.buttonOptions[PMA_messages.strClose] = function () { + $(this).dialog('close'); + }; + /** + * Display the dialog to the user + */ + var $ajaxDialog = $('
      ' + data.message + '
      ').dialog({ + width: 650, + buttons: that.buttonOptions, + title: data.title, + modal: true, + close: function () { + $(this).remove(); + } + }); + $ajaxDialog.find('input[name^=params]').first().focus(); + /** + * Attach the datepickers to the relevant form fields + */ + $ajaxDialog.find('input.datefield, input.datetimefield').each(function () { + PMA_addDatepicker($(this).css('width', '95%')); + }); + /* + * Define the function if the user presses enter + */ + $('form.rte_form').on('keyup', function (event) { + event.preventDefault(); + if (event.keyCode === 13) { + /** + * @var data Form data to be sent in the AJAX request + */ + var data = $(this).serialize(); + $msg = PMA_ajaxShowMessage( + PMA_messages.strProcessingRequest + ); + var url = $(this).attr('action'); + $.post(url, data, function (data) { + if (data.success === true) { + // Routine executed successfully + PMA_ajaxRemoveMessage($msg); + PMA_slidingMessage(data.message); + $('form.rte_form').off('keyup'); + $ajaxDialog.remove(); + } else { + PMA_ajaxShowMessage(data.error, false); + } + }); + } + }); + } else { + // Routine executed successfully + PMA_slidingMessage(data.message); + } + } else { + PMA_ajaxShowMessage(data.error, false); + } + }); // end $.post() + } +}; + +/** + * Attach Ajax event handlers for the Routines, Triggers and Events editor + */ +$(function () { + /** + * Attach Ajax event handlers for the Add/Edit functionality. + */ + $(document).on('click', 'a.ajax.add_anchor, a.ajax.edit_anchor', function (event) { + event.preventDefault(); + var type = $(this).attr('href').substr(0, $(this).attr('href').indexOf('?')); + if (type.indexOf('routine') !== -1) { + type = 'routine'; + } else if (type.indexOf('trigger') !== -1) { + type = 'trigger'; + } else if (type.indexOf('event') !== -1) { + type = 'event'; + } else { + type = ''; + } + var dialog = new RTE.object(type); + dialog.editorDialog($(this).hasClass('add_anchor'), $(this)); + }); // end $(document).on() + + /** + * Attach Ajax event handlers for the Execute routine functionality + */ + $(document).on('click', 'a.ajax.exec_anchor', function (event) { + event.preventDefault(); + var dialog = new RTE.object('routine'); + dialog.executeDialog($(this)); + }); // end $(document).on() + + /** + * Attach Ajax event handlers for Export of Routines, Triggers and Events + */ + $(document).on('click', 'a.ajax.export_anchor', function (event) { + event.preventDefault(); + var dialog = new RTE.object(); + dialog.exportDialog($(this)); + }); // end $(document).on() + + $(document).on('click', '#rteListForm.ajax .mult_submit[value="export"]', function (event) { + event.preventDefault(); + var dialog = new RTE.object(); + dialog.exportDialog($(this)); + }); // end $(document).on() + + /** + * Attach Ajax event handlers for Drop functionality + * of Routines, Triggers and Events. + */ + $(document).on('click', 'a.ajax.drop_anchor', function (event) { + event.preventDefault(); + var dialog = new RTE.object(); + dialog.dropDialog($(this)); + }); // end $(document).on() + + $(document).on('click', '#rteListForm.ajax .mult_submit[value="drop"]', function (event) { + event.preventDefault(); + var dialog = new RTE.object(); + dialog.dropMultipleDialog($(this)); + }); // end $(document).on() + + /** + * Attach Ajax event handlers for the "Change event/routine type" + * functionality in the events editor, so that the correct + * rows are shown in the editor when changing the event type + */ + $(document).on('change', 'select[name=item_type]', function () { + $(this) + .closest('table') + .find('tr.recurring_event_row, tr.onetime_event_row, tr.routine_return_row, .routine_direction_cell') + .toggle(); + }); // end $(document).on() + + /** + * Attach Ajax event handlers for the "Change parameter type" + * functionality in the routines editor, so that the correct + * option/length fields, if any, are shown when changing + * a parameter type + */ + $(document).on('change', 'select[name^=item_param_type]', function () { + /** + * @var row jQuery object containing the reference to + * a row in the routine parameters table + */ + var $row = $(this).parents('tr').first(); + var rte = new RTE.object('routine'); + rte.setOptionsForParameter( + $row.find('select[name^=item_param_type]'), + $row.find('input[name^=item_param_length]'), + $row.find('select[name^=item_param_opts_text]'), + $row.find('select[name^=item_param_opts_num]') + ); + }); // end $(document).on() + + /** + * Attach Ajax event handlers for the "Change the type of return + * variable of function" functionality, so that the correct fields, + * if any, are shown when changing the function return type type + */ + $(document).on('change', 'select[name=item_returntype]', function () { + var rte = new RTE.object('routine'); + var $table = $(this).closest('table.rte_table'); + rte.setOptionsForParameter( + $table.find('select[name=item_returntype]'), + $table.find('input[name=item_returnlength]'), + $table.find('select[name=item_returnopts_text]'), + $table.find('select[name=item_returnopts_num]') + ); + }); // end $(document).on() + + /** + * Attach Ajax event handlers for the "Add parameter to routine" functionality + */ + $(document).on('click', 'input[name=routine_addparameter]', function (event) { + event.preventDefault(); + /** + * @var routine_params_table jQuery object containing the reference + * to the routine parameters table + */ + var $routine_params_table = $(this).closest('div.ui-dialog').find('.routine_params_table'); + /** + * @var new_param_row A string containing the HTML code for the + * new row for the routine parameters table + */ + var new_param_row = RTE.param_template.replace(/%s/g, $routine_params_table.find('tr').length - 1); + // Append the new row to the parameters table + $routine_params_table.append(new_param_row); + // Make sure that the row is correctly shown according to the type of routine + if ($(this).closest('div.ui-dialog').find('table.rte_table select[name=item_type]').val() === 'FUNCTION') { + $('tr.routine_return_row').show(); + $('td.routine_direction_cell').hide(); + } + /** + * @var newrow jQuery object containing the reference to the newly + * inserted row in the routine parameters table + */ + var $newrow = $(this).closest('div.ui-dialog').find('table.routine_params_table').find('tr').has('td').last(); + // Enable/disable the 'options' dropdowns for parameters as necessary + var rte = new RTE.object('routine'); + rte.setOptionsForParameter( + $newrow.find('select[name^=item_param_type]'), + $newrow.find('input[name^=item_param_length]'), + $newrow.find('select[name^=item_param_opts_text]'), + $newrow.find('select[name^=item_param_opts_num]') + ); + }); // end $(document).on() + + /** + * Attach Ajax event handlers for the + * "Remove parameter from routine" functionality + */ + $(document).on('click', 'a.routine_param_remove_anchor', function (event) { + event.preventDefault(); + $(this).parent().parent().remove(); + // After removing a parameter, the indices of the name attributes in + // the input fields lose the correct order and need to be reordered. + RTE.ROUTINE.reindexParameters(); + }); // end $(document).on() +}); // end of $() diff --git a/php/apps/phpmyadmin49/js/server_databases.js b/php/apps/phpmyadmin49/js/server_databases.js new file mode 100644 index 00000000..a892ca8b --- /dev/null +++ b/php/apps/phpmyadmin49/js/server_databases.js @@ -0,0 +1,150 @@ +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * @fileoverview functions used on the server databases list page + * @name Server Databases + * + * @requires jQuery + * @requires jQueryUI + * @required js/functions.js + */ + +/** + * Unbind all event handlers before tearing down a page + */ +AJAX.registerTeardown('server_databases.js', function () { + $(document).off('submit', '#dbStatsForm'); + $(document).off('submit', '#create_database_form.ajax'); +}); + +/** + * AJAX scripts for server_databases.php + * + * Actions ajaxified here: + * Drop Databases + * + */ +AJAX.registerOnload('server_databases.js', function () { + /** + * Attach Event Handler for 'Drop Databases' + */ + $(document).on('submit', '#dbStatsForm', function (event) { + event.preventDefault(); + + var $form = $(this); + + /** + * @var selected_dbs Array containing the names of the checked databases + */ + var selected_dbs = []; + // loop over all checked checkboxes, except the .checkall_box checkbox + $form.find('input:checkbox:checked:not(.checkall_box)').each(function () { + $(this).closest('tr').addClass('removeMe'); + selected_dbs[selected_dbs.length] = 'DROP DATABASE `' + escapeHtml($(this).val()) + '`;'; + }); + if (! selected_dbs.length) { + PMA_ajaxShowMessage( + $('
      ').text( + PMA_messages.strNoDatabasesSelected + ), + 2000 + ); + return; + } + /** + * @var question String containing the question to be asked for confirmation + */ + var question = PMA_messages.strDropDatabaseStrongWarning + ' ' + + PMA_sprintf(PMA_messages.strDoYouReally, selected_dbs.join('
      ')); + + var argsep = PMA_commonParams.get('arg_separator'); + $(this).PMA_confirm( + question, + $form.prop('action') + '?' + $(this).serialize() + + argsep + 'drop_selected_dbs=1', + function (url) { + PMA_ajaxShowMessage(PMA_messages.strProcessingRequest, false); + + var parts = url.split('?'); + var params = getJSConfirmCommonParam(this, parts[1]); + + $.post(parts[0], params, function (data) { + if (typeof data !== 'undefined' && data.success === true) { + PMA_ajaxShowMessage(data.message); + + var $rowsToRemove = $form.find('tr.removeMe'); + var $databasesCount = $('#filter-rows-count'); + var newCount = parseInt($databasesCount.text(), 10) - $rowsToRemove.length; + $databasesCount.text(newCount); + + $rowsToRemove.remove(); + $form.find('tbody').PMA_sort_table('.name'); + if ($form.find('tbody').find('tr').length === 0) { + // user just dropped the last db on this page + PMA_commonActions.refreshMain(); + } + PMA_reloadNavigation(); + } else { + $form.find('tr.removeMe').removeClass('removeMe'); + PMA_ajaxShowMessage(data.error, false); + } + }); // end $.post() + } + ); // end $.PMA_confirm() + }); // end of Drop Database action + + /** + * Attach Ajax event handlers for 'Create Database'. + */ + $(document).on('submit', '#create_database_form.ajax', function (event) { + event.preventDefault(); + + var $form = $(this); + + // TODO Remove this section when all browsers support HTML5 "required" property + var newDbNameInput = $form.find('input[name=new_db]'); + if (newDbNameInput.val() === '') { + newDbNameInput.focus(); + alert(PMA_messages.strFormEmpty); + return; + } + // end remove + + PMA_ajaxShowMessage(PMA_messages.strProcessingRequest); + PMA_prepareForAjaxRequest($form); + + $.post($form.attr('action'), $form.serialize(), function (data) { + if (typeof data !== 'undefined' && data.success === true) { + PMA_ajaxShowMessage(data.message); + + var $databases_count_object = $('#filter-rows-count'); + var databases_count = parseInt($databases_count_object.text(), 10) + 1; + $databases_count_object.text(databases_count); + PMA_reloadNavigation(); + + // make ajax request to load db structure page - taken from ajax.js + var dbStruct_url = data.url_query; + dbStruct_url = dbStruct_url.replace(/amp;/ig, ''); + var params = 'ajax_request=true' + PMA_commonParams.get('arg_separator') + 'ajax_page_request=true'; + if (! (history && history.pushState)) { + params += PMA_MicroHistory.menus.getRequestParam(); + } + $.get(dbStruct_url, params, AJAX.responseHandler); + } else { + PMA_ajaxShowMessage(data.error, false); + } + }); // end $.post() + }); // end $(document).on() + + /* Don't show filter if number of databases are very few */ + var databasesCount = $('#filter-rows-count').html(); + if (databasesCount <= 10) { + $('#tableFilter').hide(); + } + + var tableRows = $('.server_databases'); + $.each(tableRows, function (index, item) { + $(this).click(function () { + PMA_commonActions.setDb($(this).attr('data')); + }); + }); +}); // end $() diff --git a/php/apps/phpmyadmin49/js/server_plugins.js b/php/apps/phpmyadmin49/js/server_plugins.js new file mode 100644 index 00000000..7baadabd --- /dev/null +++ b/php/apps/phpmyadmin49/js/server_plugins.js @@ -0,0 +1,16 @@ +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * Functions used in server plugins pages + */ +AJAX.registerOnload('server_plugins.js', function () { + // Make columns sortable, but only for tables with more than 1 data row + var $tables = $('#plugins_plugins table:has(tbody tr + tr)'); + $tables.tablesorter({ + sortList: [[0, 0]], + headers: { + 1: { sorter: false } + } + }); + $tables.find('thead th') + .append('
      '); +}); diff --git a/php/apps/phpmyadmin49/js/server_privileges.js b/php/apps/phpmyadmin49/js/server_privileges.js new file mode 100644 index 00000000..e8013d41 --- /dev/null +++ b/php/apps/phpmyadmin49/js/server_privileges.js @@ -0,0 +1,487 @@ +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * @fileoverview functions used in server privilege pages + * @name Server Privileges + * + * @requires jQuery + * @requires jQueryUI + * @requires js/functions.js + * + */ + +/** + * Validates the "add a user" form + * + * @return boolean whether the form is validated or not + */ +function checkAddUser (the_form) { + if (the_form.elements.pred_hostname.value === 'userdefined' && the_form.elements.hostname.value === '') { + alert(PMA_messages.strHostEmpty); + the_form.elements.hostname.focus(); + return false; + } + + if (the_form.elements.pred_username.value === 'userdefined' && the_form.elements.username.value === '') { + alert(PMA_messages.strUserEmpty); + the_form.elements.username.focus(); + return false; + } + + return PMA_checkPassword($(the_form)); +} // end of the 'checkAddUser()' function + +function checkPasswordStrength (value, meter_obj, meter_object_label, username) { + // List of words we don't want to appear in the password + customDict = [ + 'phpmyadmin', + 'mariadb', + 'mysql', + 'php', + 'my', + 'admin', + ]; + if (username !== null) { + customDict.push(username); + } + var zxcvbn_obj = zxcvbn(value, customDict); + var strength = zxcvbn_obj.score; + strength = parseInt(strength); + meter_obj.val(strength); + switch (strength) { + case 0: meter_obj_label.html(PMA_messages.strExtrWeak); + break; + case 1: meter_obj_label.html(PMA_messages.strVeryWeak); + break; + case 2: meter_obj_label.html(PMA_messages.strWeak); + break; + case 3: meter_obj_label.html(PMA_messages.strGood); + break; + case 4: meter_obj_label.html(PMA_messages.strStrong); + } +} + +/** + * AJAX scripts for server_privileges page. + * + * Actions ajaxified here: + * Add user + * Revoke a user + * Edit privileges + * Export privileges + * Paginate table of users + * Flush privileges + * + * @memberOf jQuery + * @name document.ready + */ + + +/** + * Unbind all event handlers before tearing down a page + */ +AJAX.registerTeardown('server_privileges.js', function () { + $('#fieldset_add_user_login').off('change', 'input[name=\'username\']'); + $(document).off('click', '#fieldset_delete_user_footer #buttonGo.ajax'); + $(document).off('click', 'a.edit_user_group_anchor.ajax'); + $(document).off('click', 'button.mult_submit[value=export]'); + $(document).off('click', 'a.export_user_anchor.ajax'); + $(document).off('click', '#initials_table a.ajax'); + $('#checkbox_drop_users_db').off('click'); + $(document).off('click', '.checkall_box'); + $(document).off('change', '#checkbox_SSL_priv'); + $(document).off('change', 'input[name="ssl_type"]'); + $(document).off('change', '#select_authentication_plugin'); +}); + +AJAX.registerOnload('server_privileges.js', function () { + /** + * Display a warning if there is already a user by the name entered as the username. + */ + $('#fieldset_add_user_login').on('change', 'input[name=\'username\']', function () { + var username = $(this).val(); + var $warning = $('#user_exists_warning'); + if ($('#select_pred_username').val() === 'userdefined' && username !== '') { + var href = $('form[name=\'usersForm\']').attr('action'); + var params = { + 'ajax_request' : true, + 'server' : PMA_commonParams.get('server'), + 'validate_username' : true, + 'username' : username + }; + $.get(href, params, function (data) { + if (data.user_exists) { + $warning.show(); + } else { + $warning.hide(); + } + }); + } else { + $warning.hide(); + } + }); + + /** + * Indicating password strength + */ + $('#text_pma_pw').on('keyup', function () { + meter_obj = $('#password_strength_meter'); + meter_obj_label = $('#password_strength'); + username = $('input[name="username"]'); + username = username.val(); + checkPasswordStrength($(this).val(), meter_obj, meter_obj_label, username); + }); + + /** + * Automatically switching to 'Use Text field' from 'No password' once start writing in text area + */ + $('#text_pma_pw').on('input', function () { + if ($('#text_pma_pw').val() !== '') { + $('#select_pred_password').val('userdefined'); + } + }); + + $('#text_pma_change_pw').on('keyup', function () { + meter_obj = $('#change_password_strength_meter'); + meter_obj_label = $('#change_password_strength'); + checkPasswordStrength($(this).val(), meter_obj, meter_obj_label, PMA_commonParams.get('user')); + }); + + /** + * Display a notice if sha256_password is selected + */ + $(document).on('change', '#select_authentication_plugin', function () { + var selected_plugin = $(this).val(); + if (selected_plugin === 'sha256_password') { + $('#ssl_reqd_warning').show(); + } else { + $('#ssl_reqd_warning').hide(); + } + }); + + /** + * AJAX handler for 'Revoke User' + * + * @see PMA_ajaxShowMessage() + * @memberOf jQuery + * @name revoke_user_click + */ + $(document).on('click', '#fieldset_delete_user_footer #buttonGo.ajax', function (event) { + event.preventDefault(); + + var $thisButton = $(this); + var $form = $('#usersForm'); + + $thisButton.PMA_confirm(PMA_messages.strDropUserWarning, $form.attr('action'), function (url) { + var $drop_users_db_checkbox = $('#checkbox_drop_users_db'); + if ($drop_users_db_checkbox.is(':checked')) { + var is_confirmed = confirm(PMA_messages.strDropDatabaseStrongWarning + '\n' + PMA_sprintf(PMA_messages.strDoYouReally, 'DROP DATABASE')); + if (! is_confirmed) { + // Uncheck the drop users database checkbox + $drop_users_db_checkbox.prop('checked', false); + } + } + + PMA_ajaxShowMessage(PMA_messages.strRemovingSelectedUsers); + + var argsep = PMA_commonParams.get('arg_separator'); + $.post(url, $form.serialize() + argsep + 'delete=' + $thisButton.val() + argsep + 'ajax_request=true', function (data) { + if (typeof data !== 'undefined' && data.success === true) { + PMA_ajaxShowMessage(data.message); + // Refresh navigation, if we droppped some databases with the name + // that is the same as the username of the deleted user + if ($('#checkbox_drop_users_db:checked').length) { + PMA_reloadNavigation(); + } + // Remove the revoked user from the users list + $form.find('input:checkbox:checked').parents('tr').slideUp('medium', function () { + var this_user_initial = $(this).find('input:checkbox').val().charAt(0).toUpperCase(); + $(this).remove(); + + // If this is the last user with this_user_initial, remove the link from #initials_table + if ($('#tableuserrights').find('input:checkbox[value^="' + this_user_initial + '"], input:checkbox[value^="' + this_user_initial.toLowerCase() + '"]').length === 0) { + $('#initials_table').find('td > a:contains(' + this_user_initial + ')').parent('td').html(this_user_initial); + } + + // Re-check the classes of each row + $form + .find('tbody').find('tr:odd') + .removeClass('even').addClass('odd') + .end() + .find('tr:even') + .removeClass('odd').addClass('even'); + + // update the checkall checkbox + $(checkboxes_sel).trigger('change'); + }); + } else { + PMA_ajaxShowMessage(data.error, false); + } + }); // end $.post() + }); + }); // end Revoke User + + $(document).on('click', 'a.edit_user_group_anchor.ajax', function (event) { + event.preventDefault(); + $(this).parents('tr').addClass('current_row'); + var $msg = PMA_ajaxShowMessage(); + $.get( + $(this).attr('href'), + { + 'ajax_request': true, + 'edit_user_group_dialog': true + }, + function (data) { + if (typeof data !== 'undefined' && data.success === true) { + PMA_ajaxRemoveMessage($msg); + var buttonOptions = {}; + buttonOptions[PMA_messages.strGo] = function () { + var usrGroup = $('#changeUserGroupDialog') + .find('select[name="userGroup"]') + .val(); + var $message = PMA_ajaxShowMessage(); + var argsep = PMA_commonParams.get('arg_separator'); + $.post( + 'server_privileges.php', + $('#changeUserGroupDialog').find('form').serialize() + argsep + 'ajax_request=1', + function (data) { + PMA_ajaxRemoveMessage($message); + if (typeof data !== 'undefined' && data.success === true) { + $('#usersForm') + .find('.current_row') + .removeClass('current_row') + .find('.usrGroup') + .text(usrGroup); + } else { + PMA_ajaxShowMessage(data.error, false); + $('#usersForm') + .find('.current_row') + .removeClass('current_row'); + } + } + ); + $(this).dialog('close'); + }; + buttonOptions[PMA_messages.strClose] = function () { + $(this).dialog('close'); + }; + var $dialog = $('
      ') + .attr('id', 'changeUserGroupDialog') + .append(data.message) + .dialog({ + width: 500, + minWidth: 300, + modal: true, + buttons: buttonOptions, + title: $('legend', $(data.message)).text(), + close: function () { + $(this).remove(); + } + }); + $dialog.find('legend').remove(); + } else { + PMA_ajaxShowMessage(data.error, false); + $('#usersForm') + .find('.current_row') + .removeClass('current_row'); + } + } + ); + }); + + /** + * AJAX handler for 'Export Privileges' + * + * @see PMA_ajaxShowMessage() + * @memberOf jQuery + * @name export_user_click + */ + $(document).on('click', 'button.mult_submit[value=export]', function (event) { + event.preventDefault(); + // can't export if no users checked + if ($(this.form).find('input:checked').length === 0) { + PMA_ajaxShowMessage(PMA_messages.strNoAccountSelected, 2000, 'success'); + return; + } + var $msgbox = PMA_ajaxShowMessage(); + var button_options = {}; + button_options[PMA_messages.strClose] = function () { + $(this).dialog('close'); + }; + var argsep = PMA_commonParams.get('arg_separator'); + $.post( + $(this.form).prop('action'), + $(this.form).serialize() + argsep + 'submit_mult=export' + argsep + 'ajax_request=true', + function (data) { + if (typeof data !== 'undefined' && data.success === true) { + var $ajaxDialog = $('
      ') + .append(data.message) + .dialog({ + title: data.title, + width: 500, + buttons: button_options, + close: function () { + $(this).remove(); + } + }); + PMA_ajaxRemoveMessage($msgbox); + // Attach syntax highlighted editor to export dialog + PMA_getSQLEditor($ajaxDialog.find('textarea')); + } else { + PMA_ajaxShowMessage(data.error, false); + } + } + ); // end $.post + }); + // if exporting non-ajax, highlight anyways + PMA_getSQLEditor($('textarea.export')); + + $(document).on('click', 'a.export_user_anchor.ajax', function (event) { + event.preventDefault(); + var $msgbox = PMA_ajaxShowMessage(); + /** + * @var button_options Object containing options for jQueryUI dialog buttons + */ + var button_options = {}; + button_options[PMA_messages.strClose] = function () { + $(this).dialog('close'); + }; + $.get($(this).attr('href'), { 'ajax_request': true }, function (data) { + if (typeof data !== 'undefined' && data.success === true) { + var $ajaxDialog = $('
      ') + .append(data.message) + .dialog({ + title: data.title, + width: 500, + buttons: button_options, + close: function () { + $(this).remove(); + } + }); + PMA_ajaxRemoveMessage($msgbox); + // Attach syntax highlighted editor to export dialog + PMA_getSQLEditor($ajaxDialog.find('textarea')); + } else { + PMA_ajaxShowMessage(data.error, false); + } + }); // end $.get + }); // end export privileges + + /** + * AJAX handler to Paginate the Users Table + * + * @see PMA_ajaxShowMessage() + * @name paginate_users_table_click + * @memberOf jQuery + */ + $(document).on('click', '#initials_table a.ajax', function (event) { + event.preventDefault(); + var $msgbox = PMA_ajaxShowMessage(); + $.get($(this).attr('href'), { 'ajax_request' : true }, function (data) { + if (typeof data !== 'undefined' && data.success === true) { + PMA_ajaxRemoveMessage($msgbox); + // This form is not on screen when first entering Privileges + // if there are more than 50 users + $('div.notice').remove(); + $('#usersForm').hide('medium').remove(); + $('#fieldset_add_user').hide('medium').remove(); + $('#initials_table') + .prop('id', 'initials_table_old') + .after(data.message).show('medium') + .siblings('h2').not(':first').remove(); + // prevent double initials table + $('#initials_table_old').remove(); + } else { + PMA_ajaxShowMessage(data.error, false); + } + }); // end $.get + }); // end of the paginate users table + + $(document).on('change', 'input[name="ssl_type"]', function (e) { + var $div = $('#specified_div'); + if ($('#ssl_type_SPECIFIED').is(':checked')) { + $div.find('input').prop('disabled', false); + } else { + $div.find('input').prop('disabled', true); + } + }); + + $(document).on('change', '#checkbox_SSL_priv', function (e) { + var $div = $('#require_ssl_div'); + if ($(this).is(':checked')) { + $div.find('input').prop('disabled', false); + $('#ssl_type_SPECIFIED').trigger('change'); + } else { + $div.find('input').prop('disabled', true); + } + }); + + $('#checkbox_SSL_priv').trigger('change'); + + /* + * Create submenu for simpler interface + */ + var addOrUpdateSubmenu = function () { + var $topmenu2 = $('#topmenu2'); + var $edit_user_dialog = $('#edit_user_dialog'); + var submenu_label; + var submenu_link; + var link_number; + + // if submenu exists yet, remove it first + if ($topmenu2.length > 0) { + $topmenu2.remove(); + } + + // construct a submenu from the existing fieldsets + $topmenu2 = $('
        ').prop('id', 'topmenu2'); + + $('#edit_user_dialog .submenu-item').each(function () { + submenu_label = $(this).find('legend[data-submenu-label]').data('submenu-label'); + + submenu_link = $('') + .prop('href', '#') + .html(submenu_label); + + $('
      • ') + .append(submenu_link) + .appendTo($topmenu2); + }); + + // click handlers for submenu + $topmenu2.find('a').click(function (e) { + e.preventDefault(); + // if already active, ignore click + if ($(this).hasClass('tabactive')) { + return; + } + $topmenu2.find('a').removeClass('tabactive'); + $(this).addClass('tabactive'); + + // which section to show now? + link_number = $topmenu2.find('a').index($(this)); + // hide all sections but the one to show + $('#edit_user_dialog .submenu-item').hide().eq(link_number).show(); + }); + + // make first menu item active + // TODO: support URL hash history + $topmenu2.find('> :first-child a').addClass('tabactive'); + $edit_user_dialog.prepend($topmenu2); + + // hide all sections but the first + $('#edit_user_dialog .submenu-item').hide().eq(0).show(); + + // scroll to the top + $('html, body').animate({ scrollTop: 0 }, 'fast'); + }; + + $('input.autofocus').focus(); + $(checkboxes_sel).trigger('change'); + displayPasswordGenerateButton(); + if ($('#edit_user_dialog').length > 0) { + addOrUpdateSubmenu(); + } + + var windowwidth = $(window).width(); + $('.jsresponsive').css('max-width', (windowwidth - 35) + 'px'); +}); diff --git a/php/apps/phpmyadmin49/js/server_status_advisor.js b/php/apps/phpmyadmin49/js/server_status_advisor.js new file mode 100644 index 00000000..a9f68863 --- /dev/null +++ b/php/apps/phpmyadmin49/js/server_status_advisor.js @@ -0,0 +1,101 @@ +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * Server Status Advisor + * + * @package PhpMyAdmin + */ + +/** + * Unbind all event handlers before tearing down a page + */ +AJAX.registerTeardown('server_status_advisor.js', function () { + $('a[href="#openAdvisorInstructions"]').off('click'); + $('#statustabs_advisor').html(''); + $('#advisorDialog').remove(); + $('#instructionsDialog').remove(); +}); + +AJAX.registerOnload('server_status_advisor.js', function () { + // if no advisor is loaded + if ($('#advisorData').length === 0) { + return; + } + + /** ** Server config advisor ****/ + var $dialog = $('
        ').attr('id', 'advisorDialog'); + var $instructionsDialog = $('
        ') + .attr('id', 'instructionsDialog') + .html($('#advisorInstructionsDialog').html()); + + $('a[href="#openAdvisorInstructions"]').click(function () { + var dlgBtns = {}; + dlgBtns[PMA_messages.strClose] = function () { + $(this).dialog('close'); + }; + $instructionsDialog.dialog({ + title: PMA_messages.strAdvisorSystem, + width: '60%', + buttons: dlgBtns + }); + }); + + var $cnt = $('#statustabs_advisor'); + var $tbody; + var $tr; + var str; + var even = true; + + data = JSON.parse($('#advisorData').text()); + $cnt.html(''); + + if (data.parse.errors.length > 0) { + $cnt.append('Rules file not well formed, following errors were found:
        - '); + $cnt.append(data.parse.errors.join('
        - ')); + $cnt.append('

        '); + } + + if (data.run.errors.length > 0) { + $cnt.append('Errors occurred while executing rule expressions:
        - '); + $cnt.append(data.run.errors.join('
        - ')); + $cnt.append('

        '); + } + + if (data.run.fired.length > 0) { + $cnt.append('

        ' + PMA_messages.strPerformanceIssues + '

        '); + $cnt.append('' + + '
        ' + PMA_messages.strIssuse + '' + PMA_messages.strRecommendation + + '
        '); + $tbody = $cnt.find('table#rulesFired'); + + var rc_stripped; + + $.each(data.run.fired, function (key, value) { + // recommendation may contain links, don't show those in overview table (clicking on them redirects the user) + rc_stripped = $.trim($('
        ').html(value.recommendation).text()); + $tbody.append($tr = $('' + + value.issue + '' + rc_stripped + ' ')); + even = !even; + $tr.data('rule', value); + + $tr.click(function () { + var rule = $(this).data('rule'); + $dialog + .dialog({ title: PMA_messages.strRuleDetails }) + .html( + '

        ' + PMA_messages.strIssuse + ':
        ' + rule.issue + '

        ' + + '

        ' + PMA_messages.strRecommendation + ':
        ' + rule.recommendation + '

        ' + + '

        ' + PMA_messages.strJustification + ':
        ' + rule.justification + '

        ' + + '

        ' + PMA_messages.strFormula + ':
        ' + rule.formula + '

        ' + + '

        ' + PMA_messages.strTest + ':
        ' + rule.test + '

        ' + ); + + var dlgBtns = {}; + dlgBtns[PMA_messages.strClose] = function () { + $(this).dialog('close'); + }; + + $dialog.dialog({ width: 600, buttons: dlgBtns }); + }); + }); + } +}); diff --git a/php/apps/phpmyadmin49/js/server_status_monitor.js b/php/apps/phpmyadmin49/js/server_status_monitor.js new file mode 100644 index 00000000..8bff1cea --- /dev/null +++ b/php/apps/phpmyadmin49/js/server_status_monitor.js @@ -0,0 +1,2197 @@ +/* vim: set expandtab sw=4 ts=4 sts=4: */ +var runtime = {}; +var server_time_diff; +var server_os; +var is_superuser; +var server_db_isLocal; +var chartSize; +AJAX.registerOnload('server_status_monitor.js', function () { + var $js_data_form = $('#js_data'); + server_time_diff = new Date().getTime() - $js_data_form.find('input[name=server_time]').val(); + server_os = $js_data_form.find('input[name=server_os]').val(); + is_superuser = $js_data_form.find('input[name=is_superuser]').val(); + server_db_isLocal = $js_data_form.find('input[name=server_db_isLocal]').val(); +}); + +/** + * Unbind all event handlers before tearing down a page + */ +AJAX.registerTeardown('server_status_monitor.js', function () { + $('#emptyDialog').remove(); + $('#addChartDialog').remove(); + $('a.popupLink').off('click'); + $('body').off('click'); +}); +/** + * Popup behaviour + */ +AJAX.registerOnload('server_status_monitor.js', function () { + $('
        ') + .attr('id', 'emptyDialog') + .appendTo('#page_content'); + $('#addChartDialog') + .appendTo('#page_content'); + + $('a.popupLink').click(function () { + var $link = $(this); + $('div.' + $link.attr('href').substr(1)) + .show() + .offset({ top: $link.offset().top + $link.height() + 5, left: $link.offset().left }) + .addClass('openedPopup'); + + return false; + }); + $('body').click(function (event) { + $('div.openedPopup').each(function () { + var $cnt = $(this); + var pos = $cnt.offset(); + // Hide if the mouseclick is outside the popupcontent + if (event.pageX < pos.left || + event.pageY < pos.top || + event.pageX > pos.left + $cnt.outerWidth() || + event.pageY > pos.top + $cnt.outerHeight() + ) { + $cnt.hide().removeClass('openedPopup'); + } + }); + }); +}); + +AJAX.registerTeardown('server_status_monitor.js', function () { + $('a[href="#rearrangeCharts"], a[href="#endChartEditMode"]').off('click'); + $('div.popupContent select[name="chartColumns"]').off('change'); + $('div.popupContent select[name="gridChartRefresh"]').off('change'); + $('a[href="#addNewChart"]').off('click'); + $('a[href="#exportMonitorConfig"]').off('click'); + $('a[href="#importMonitorConfig"]').off('click'); + $('a[href="#clearMonitorConfig"]').off('click'); + $('a[href="#pauseCharts"]').off('click'); + $('a[href="#monitorInstructionsDialog"]').off('click'); + $('input[name="chartType"]').off('click'); + $('input[name="useDivisor"]').off('click'); + $('input[name="useUnit"]').off('click'); + $('select[name="varChartList"]').off('click'); + $('a[href="#kibDivisor"]').off('click'); + $('a[href="#mibDivisor"]').off('click'); + $('a[href="#submitClearSeries"]').off('click'); + $('a[href="#submitAddSeries"]').off('click'); + // $("input#variableInput").destroy(); + $('#chartPreset').off('click'); + $('#chartStatusVar').off('click'); + destroyGrid(); +}); + +AJAX.registerOnload('server_status_monitor.js', function () { + // Show tab links + $('div.tabLinks').show(); + $('#loadingMonitorIcon').remove(); + // Codemirror is loaded on demand so we might need to initialize it + if (! codemirror_editor) { + var $elm = $('#sqlquery'); + if ($elm.length > 0 && typeof CodeMirror !== 'undefined') { + codemirror_editor = CodeMirror.fromTextArea( + $elm[0], + { + lineNumbers: true, + matchBrackets: true, + indentUnit: 4, + mode: 'text/x-mysql', + lineWrapping: true + } + ); + } + } + // Timepicker is loaded on demand so we need to initialize + // datetime fields from the 'load log' dialog + $('#logAnalyseDialog').find('.datetimefield').each(function () { + PMA_addDatepicker($(this)); + }); + + /** ** Monitor charting implementation ****/ + /* Saves the previous ajax response for differential values */ + var oldChartData = null; + // Holds about to be created chart + var newChart = null; + var chartSpacing; + + // Whenever the monitor object (runtime.charts) or the settings object + // (monitorSettings) changes in a way incompatible to the previous version, + // increase this number. It will reset the users monitor and settings object + // in his localStorage to the default configuration + var monitorProtocolVersion = '1.0'; + + // Runtime parameter of the monitor, is being fully set in initGrid() + runtime = { + // Holds all visible charts in the grid + charts: null, + // Stores the timeout handler so it can be cleared + refreshTimeout: null, + // Stores the GET request to refresh the charts + refreshRequest: null, + // Chart auto increment + chartAI: 0, + // To play/pause the monitor + redrawCharts: false, + // Object that contains a list of nodes that need to be retrieved + // from the server for chart updates + dataList: [], + // Current max points per chart (needed for auto calculation) + gridMaxPoints: 20, + // displayed time frame + xmin: -1, + xmax: -1 + }; + var monitorSettings = null; + + var defaultMonitorSettings = { + columns: 3, + chartSize: { width: 295, height: 250 }, + // Max points in each chart. Settings it to 'auto' sets + // gridMaxPoints to (chartwidth - 40) / 12 + gridMaxPoints: 'auto', + /* Refresh rate of all grid charts in ms */ + gridRefresh: 5000 + }; + + // Allows drag and drop rearrange and print/edit icons on charts + var editMode = false; + + /* List of preconfigured charts that the user may select */ + var presetCharts = { + // Query cache efficiency + 'qce': { + title: PMA_messages.strQueryCacheEfficiency, + series: [{ + label: PMA_messages.strQueryCacheEfficiency + }], + nodes: [{ + dataPoints: [{ type: 'statusvar', name: 'Qcache_hits' }, { type: 'statusvar', name: 'Com_select' }], + transformFn: 'qce' + }], + maxYLabel: 0 + }, + // Query cache usage + 'qcu': { + title: PMA_messages.strQueryCacheUsage, + series: [{ + label: PMA_messages.strQueryCacheUsed + }], + nodes: [{ + dataPoints: [{ type: 'statusvar', name: 'Qcache_free_memory' }, { type: 'servervar', name: 'query_cache_size' }], + transformFn: 'qcu' + }], + maxYLabel: 0 + } + }; + + // time span selection + var selectionTimeDiff = []; + var selectionStartX; + var selectionStartY; + var selectionEndX; + var selectionEndY; + var drawTimeSpan = false; + + // chart tooltip + var tooltipBox; + + /* Add OS specific system info charts to the preset chart list */ + switch (server_os) { + case 'WINNT': + $.extend(presetCharts, { + 'cpu': { + title: PMA_messages.strSystemCPUUsage, + series: [{ + label: PMA_messages.strAverageLoad + }], + nodes: [{ + dataPoints: [{ type: 'cpu', name: 'loadavg' }] + }], + maxYLabel: 100 + }, + + 'memory': { + title: PMA_messages.strSystemMemory, + series: [{ + label: PMA_messages.strTotalMemory, + fill: true + }, { + dataType: 'memory', + label: PMA_messages.strUsedMemory, + fill: true + }], + nodes: [{ dataPoints: [{ type: 'memory', name: 'MemTotal' }], valueDivisor: 1024 }, + { dataPoints: [{ type: 'memory', name: 'MemUsed' }], valueDivisor: 1024 } + ], + maxYLabel: 0 + }, + + 'swap': { + title: PMA_messages.strSystemSwap, + series: [{ + label: PMA_messages.strTotalSwap, + fill: true + }, { + label: PMA_messages.strUsedSwap, + fill: true + }], + nodes: [{ dataPoints: [{ type: 'memory', name: 'SwapTotal' }] }, + { dataPoints: [{ type: 'memory', name: 'SwapUsed' }] } + ], + maxYLabel: 0 + } + }); + break; + + case 'Linux': + $.extend(presetCharts, { + 'cpu': { + title: PMA_messages.strSystemCPUUsage, + series: [{ + label: PMA_messages.strAverageLoad + }], + nodes: [{ dataPoints: [{ type: 'cpu', name: 'irrelevant' }], transformFn: 'cpu-linux' }], + maxYLabel: 0 + }, + 'memory': { + title: PMA_messages.strSystemMemory, + series: [ + { label: PMA_messages.strBufferedMemory, fill: true }, + { label: PMA_messages.strUsedMemory, fill: true }, + { label: PMA_messages.strCachedMemory, fill: true }, + { label: PMA_messages.strFreeMemory, fill: true } + ], + nodes: [ + { dataPoints: [{ type: 'memory', name: 'Buffers' }], valueDivisor: 1024 }, + { dataPoints: [{ type: 'memory', name: 'MemUsed' }], valueDivisor: 1024 }, + { dataPoints: [{ type: 'memory', name: 'Cached' }], valueDivisor: 1024 }, + { dataPoints: [{ type: 'memory', name: 'MemFree' }], valueDivisor: 1024 } + ], + maxYLabel: 0 + }, + 'swap': { + title: PMA_messages.strSystemSwap, + series: [ + { label: PMA_messages.strCachedSwap, fill: true }, + { label: PMA_messages.strUsedSwap, fill: true }, + { label: PMA_messages.strFreeSwap, fill: true } + ], + nodes: [ + { dataPoints: [{ type: 'memory', name: 'SwapCached' }], valueDivisor: 1024 }, + { dataPoints: [{ type: 'memory', name: 'SwapUsed' }], valueDivisor: 1024 }, + { dataPoints: [{ type: 'memory', name: 'SwapFree' }], valueDivisor: 1024 } + ], + maxYLabel: 0 + } + }); + break; + + case 'SunOS': + $.extend(presetCharts, { + 'cpu': { + title: PMA_messages.strSystemCPUUsage, + series: [{ + label: PMA_messages.strAverageLoad + }], + nodes: [{ + dataPoints: [{ type: 'cpu', name: 'loadavg' }] + }], + maxYLabel: 0 + }, + 'memory': { + title: PMA_messages.strSystemMemory, + series: [ + { label: PMA_messages.strUsedMemory, fill: true }, + { label: PMA_messages.strFreeMemory, fill: true } + ], + nodes: [ + { dataPoints: [{ type: 'memory', name: 'MemUsed' }], valueDivisor: 1024 }, + { dataPoints: [{ type: 'memory', name: 'MemFree' }], valueDivisor: 1024 } + ], + maxYLabel: 0 + }, + 'swap': { + title: PMA_messages.strSystemSwap, + series: [ + { label: PMA_messages.strUsedSwap, fill: true }, + { label: PMA_messages.strFreeSwap, fill: true } + ], + nodes: [ + { dataPoints: [{ type: 'memory', name: 'SwapUsed' }], valueDivisor: 1024 }, + { dataPoints: [{ type: 'memory', name: 'SwapFree' }], valueDivisor: 1024 } + ], + maxYLabel: 0 + } + }); + break; + } + + // Default setting for the chart grid + var defaultChartGrid = { + 'c0': { + title: PMA_messages.strQuestions, + series: [ + { label: PMA_messages.strQuestions } + ], + nodes: [ + { dataPoints: [{ type: 'statusvar', name: 'Questions' }], display: 'differential' } + ], + maxYLabel: 0 + }, + 'c1': { + title: PMA_messages.strChartConnectionsTitle, + series: [ + { label: PMA_messages.strConnections }, + { label: PMA_messages.strProcesses } + ], + nodes: [ + { dataPoints: [{ type: 'statusvar', name: 'Connections' }], display: 'differential' }, + { dataPoints: [{ type: 'proc', name: 'processes' }] } + ], + maxYLabel: 0 + }, + 'c2': { + title: PMA_messages.strTraffic, + series: [ + { label: PMA_messages.strBytesSent }, + { label: PMA_messages.strBytesReceived } + ], + nodes: [ + { dataPoints: [{ type: 'statusvar', name: 'Bytes_sent' }], display: 'differential', valueDivisor: 1024 }, + { dataPoints: [{ type: 'statusvar', name: 'Bytes_received' }], display: 'differential', valueDivisor: 1024 } + ], + maxYLabel: 0 + } + }; + + // Server is localhost => We can add cpu/memory/swap to the default chart + if (server_db_isLocal && typeof presetCharts.cpu !== 'undefined') { + defaultChartGrid.c3 = presetCharts.cpu; + defaultChartGrid.c4 = presetCharts.memory; + defaultChartGrid.c5 = presetCharts.swap; + } + + $('a[href="#rearrangeCharts"], a[href="#endChartEditMode"]').click(function (event) { + event.preventDefault(); + editMode = !editMode; + if ($(this).attr('href') === '#endChartEditMode') { + editMode = false; + } + + $('a[href="#endChartEditMode"]').toggle(editMode); + + if (editMode) { + // Close the settings popup + $('div.popupContent').hide().removeClass('openedPopup'); + + $('#chartGrid').sortableTable({ + ignoreRect: { + top: 8, + left: chartSize.width - 63, + width: 54, + height: 24 + } + }); + } else { + $('#chartGrid').sortableTable('destroy'); + } + saveMonitor(); // Save settings + return false; + }); + + // global settings + $('div.popupContent select[name="chartColumns"]').change(function () { + monitorSettings.columns = parseInt(this.value, 10); + + calculateChartSize(); + // Empty cells should keep their size so you can drop onto them + $('#chartGrid').find('tr td').css('width', chartSize.width + 'px'); + $('#chartGrid').find('.monitorChart').css({ + width: chartSize.width + 'px', + height: chartSize.height + 'px' + }); + + /* Reorder all charts that it fills all column cells */ + var numColumns; + var $tr = $('#chartGrid').find('tr:first'); + var row = 0; + + var tempManageCols = function () { + if (numColumns > monitorSettings.columns) { + if ($tr.next().length === 0) { + $tr.after(''); + } + $tr.next().prepend($(this)); + } + numColumns++; + }; + + var tempAddCol = function () { + if ($(this).next().length !== 0) { + $(this).append($(this).next().find('td:first')); + } + }; + + while ($tr.length !== 0) { + numColumns = 1; + // To many cells in one row => put into next row + $tr.find('td').each(tempManageCols); + + // To little cells in one row => for each cell to little, + // move all cells backwards by 1 + if ($tr.next().length > 0) { + var cnt = monitorSettings.columns - $tr.find('td').length; + for (var i = 0; i < cnt; i++) { + $tr.append($tr.next().find('td:first')); + $tr.nextAll().each(tempAddCol); + } + } + + $tr = $tr.next(); + row++; + } + + if (monitorSettings.gridMaxPoints === 'auto') { + runtime.gridMaxPoints = Math.round((chartSize.width - 40) / 12); + } + + runtime.xmin = new Date().getTime() - server_time_diff - runtime.gridMaxPoints * monitorSettings.gridRefresh; + runtime.xmax = new Date().getTime() - server_time_diff + monitorSettings.gridRefresh; + + if (editMode) { + $('#chartGrid').sortableTable('refresh'); + } + + refreshChartGrid(); + saveMonitor(); // Save settings + }); + + $('div.popupContent select[name="gridChartRefresh"]').change(function () { + monitorSettings.gridRefresh = parseInt(this.value, 10) * 1000; + clearTimeout(runtime.refreshTimeout); + + if (runtime.refreshRequest) { + runtime.refreshRequest.abort(); + } + + runtime.xmin = new Date().getTime() - server_time_diff - runtime.gridMaxPoints * monitorSettings.gridRefresh; + // fixing chart shift towards left on refresh rate change + // runtime.xmax = new Date().getTime() - server_time_diff + monitorSettings.gridRefresh; + runtime.refreshTimeout = setTimeout(refreshChartGrid, monitorSettings.gridRefresh); + + saveMonitor(); // Save settings + }); + + $('a[href="#addNewChart"]').click(function (event) { + event.preventDefault(); + var dlgButtons = { }; + + dlgButtons[PMA_messages.strAddChart] = function () { + var type = $('input[name="chartType"]:checked').val(); + + if (type === 'preset') { + newChart = presetCharts[$('#addChartDialog').find('select[name="presetCharts"]').prop('value')]; + } else { + // If user builds his own chart, it's being set/updated + // each time he adds a series + // So here we only warn if he didn't add a series yet + if (! newChart || ! newChart.nodes || newChart.nodes.length === 0) { + alert(PMA_messages.strAddOneSeriesWarning); + return; + } + } + + newChart.title = $('input[name="chartTitle"]').val(); + // Add a cloned object to the chart grid + addChart($.extend(true, {}, newChart)); + + newChart = null; + + saveMonitor(); // Save settings + + $(this).dialog('close'); + }; + + dlgButtons[PMA_messages.strClose] = function () { + newChart = null; + $('span#clearSeriesLink').hide(); + $('#seriesPreview').html(''); + $(this).dialog('close'); + }; + + var $presetList = $('#addChartDialog').find('select[name="presetCharts"]'); + if ($presetList.html().length === 0) { + $.each(presetCharts, function (key, value) { + $presetList.append(''); + }); + $presetList.change(function () { + $('input[name="chartTitle"]').val( + $presetList.find(':selected').text() + ); + $('#chartPreset').prop('checked', true); + }); + $('#chartPreset').click(function () { + $('input[name="chartTitle"]').val( + $presetList.find(':selected').text() + ); + }); + $('#chartStatusVar').click(function () { + $('input[name="chartTitle"]').val( + $('#chartSeries').find(':selected').text().replace(/_/g, ' ') + ); + }); + $('#chartSeries').change(function () { + $('input[name="chartTitle"]').val( + $('#chartSeries').find(':selected').text().replace(/_/g, ' ') + ); + }); + } + + $('#addChartDialog').dialog({ + width: 'auto', + height: 'auto', + buttons: dlgButtons + }); + + $('#seriesPreview').html('' + PMA_messages.strNone + ''); + + return false; + }); + + $('a[href="#exportMonitorConfig"]').click(function (event) { + event.preventDefault(); + var gridCopy = {}; + $.each(runtime.charts, function (key, elem) { + gridCopy[key] = {}; + gridCopy[key].nodes = elem.nodes; + gridCopy[key].series = elem.series; + gridCopy[key].settings = elem.settings; + gridCopy[key].title = elem.title; + gridCopy[key].maxYLabel = elem.maxYLabel; + }); + var exportData = { + monitorCharts: gridCopy, + monitorSettings: monitorSettings + }; + + var blob = new Blob([JSON.stringify(exportData)], { type: 'application/octet-stream' }); + var url = null; + var fileName = 'monitor-config.json'; + if (window.navigator && window.navigator.msSaveOrOpenBlob) { + window.navigator.msSaveOrOpenBlob(blob, fileName); + } else { + url = URL.createObjectURL(blob); + window.location.href = url; + } + setTimeout(function () { + // For some browsers it is necessary to delay revoking the ObjectURL + if (url !== null) { + window.URL.revokeObjectURL(url); + } + url = undefined; + blob = undefined; + }, 100); + }); + + $('a[href="#importMonitorConfig"]').click(function (event) { + event.preventDefault(); + $('#emptyDialog').dialog({ title: PMA_messages.strImportDialogTitle }); + $('#emptyDialog').html(PMA_messages.strImportDialogMessage + ':
        ' + + '
        '); + + var dlgBtns = {}; + + dlgBtns[PMA_messages.strImport] = function () { + var input = $('#emptyDialog').find('#import_file')[0]; + var reader = new FileReader(); + + reader.onerror = function (event) { + alert(PMA_messages.strFailedParsingConfig + '\n' + event.target.error.code); + }; + reader.onload = function (e) { + var data = e.target.result; + var json = null; + // Try loading config + try { + json = JSON.parse(data); + } catch (err) { + alert(PMA_messages.strFailedParsingConfig); + $('#emptyDialog').dialog('close'); + return; + } + + // Basic check, is this a monitor config json? + if (!json || ! json.monitorCharts || ! json.monitorCharts) { + alert(PMA_messages.strFailedParsingConfig); + $('#emptyDialog').dialog('close'); + return; + } + + // If json ok, try applying config + try { + if (isStorageSupported('localStorage')) { + window.localStorage.monitorCharts = JSON.stringify(json.monitorCharts); + window.localStorage.monitorSettings = JSON.stringify(json.monitorSettings); + } + rebuildGrid(); + } catch (err) { + alert(PMA_messages.strFailedBuildingGrid); + // If an exception is thrown, load default again + if (isStorageSupported('localStorage')) { + window.localStorage.removeItem('monitorCharts'); + window.localStorage.removeItem('monitorSettings'); + } + rebuildGrid(); + } + + $('#emptyDialog').dialog('close'); + }; + reader.readAsText(input.files[0]); + }; + + dlgBtns[PMA_messages.strCancel] = function () { + $(this).dialog('close'); + }; + + $('#emptyDialog').dialog({ + width: 'auto', + height: 'auto', + buttons: dlgBtns + }); + }); + + $('a[href="#clearMonitorConfig"]').click(function (event) { + event.preventDefault(); + if (isStorageSupported('localStorage')) { + window.localStorage.removeItem('monitorCharts'); + window.localStorage.removeItem('monitorSettings'); + window.localStorage.removeItem('monitorVersion'); + } + $(this).hide(); + rebuildGrid(); + }); + + $('a[href="#pauseCharts"]').click(function (event) { + event.preventDefault(); + runtime.redrawCharts = ! runtime.redrawCharts; + if (! runtime.redrawCharts) { + $(this).html(PMA_getImage('play') + PMA_messages.strResumeMonitor); + } else { + $(this).html(PMA_getImage('pause') + PMA_messages.strPauseMonitor); + if (! runtime.charts) { + initGrid(); + $('a[href="#settingsPopup"]').show(); + } + } + return false; + }); + + $('a[href="#monitorInstructionsDialog"]').click(function (event) { + event.preventDefault(); + + var $dialog = $('#monitorInstructionsDialog'); + + $dialog.dialog({ + width: '60%', + height: 'auto' + }).find('img.ajaxIcon').show(); + + var loadLogVars = function (getvars) { + var vars = { ajax_request: true, logging_vars: true }; + if (getvars) { + $.extend(vars, getvars); + } + + $.post('server_status_monitor.php' + PMA_commonParams.get('common_query'), vars, + function (data) { + var logVars; + if (typeof data !== 'undefined' && data.success === true) { + logVars = data.message; + } else { + return serverResponseError(); + } + var icon = PMA_getImage('s_success'); + var msg = ''; + var str = ''; + + if (logVars.general_log === 'ON') { + if (logVars.slow_query_log === 'ON') { + msg = PMA_messages.strBothLogOn; + } else { + msg = PMA_messages.strGenLogOn; + } + } + + if (msg.length === 0 && logVars.slow_query_log === 'ON') { + msg = PMA_messages.strSlowLogOn; + } + + if (msg.length === 0) { + icon = PMA_getImage('s_error'); + msg = PMA_messages.strBothLogOff; + } + + str = '' + PMA_messages.strCurrentSettings + '
        '; + str += icon + msg + '
        '; + + if (logVars.log_output !== 'TABLE') { + str += PMA_getImage('s_error') + ' ' + PMA_messages.strLogOutNotTable + '
        '; + } else { + str += PMA_getImage('s_success') + ' ' + PMA_messages.strLogOutIsTable + '
        '; + } + + if (logVars.slow_query_log === 'ON') { + if (logVars.long_query_time > 2) { + str += PMA_getImage('s_attention') + ' '; + str += PMA_sprintf(PMA_messages.strSmallerLongQueryTimeAdvice, logVars.long_query_time); + str += '
        '; + } + + if (logVars.long_query_time < 2) { + str += PMA_getImage('s_success') + ' '; + str += PMA_sprintf(PMA_messages.strLongQueryTimeSet, logVars.long_query_time); + str += '
        '; + } + } + + str += '
        '; + + if (is_superuser) { + str += '

        ' + PMA_messages.strChangeSettings + ''; + str += '
        '; + + $dialog.find('div.monitorUse').toggle( + logVars.log_output === 'TABLE' && (logVars.slow_query_log === 'ON' || logVars.general_log === 'ON') + ); + + $dialog.find('div.ajaxContent').html(str); + $dialog.find('img.ajaxIcon').hide(); + $dialog.find('a.set').click(function () { + var nameValue = $(this).attr('href').split('-'); + loadLogVars({ varName: nameValue[0].substr(1), varValue: nameValue[1] }); + $dialog.find('img.ajaxIcon').show(); + }); + } + ); + }; + + + loadLogVars(); + + return false; + }); + + $('input[name="chartType"]').change(function () { + $('#chartVariableSettings').toggle(this.checked && this.value === 'variable'); + var title = $('input[name="chartTitle"]').val(); + if (title === PMA_messages.strChartTitle || + title === $('label[for="' + $('input[name="chartTitle"]').data('lastRadio') + '"]').text() + ) { + $('input[name="chartTitle"]') + .data('lastRadio', $(this).attr('id')) + .val($('label[for="' + $(this).attr('id') + '"]').text()); + } + }); + + $('input[name="useDivisor"]').change(function () { + $('span.divisorInput').toggle(this.checked); + }); + + $('input[name="useUnit"]').change(function () { + $('span.unitInput').toggle(this.checked); + }); + + $('select[name="varChartList"]').change(function () { + if (this.selectedIndex !== 0) { + $('#variableInput').val(this.value); + } + }); + + $('a[href="#kibDivisor"]').click(function (event) { + event.preventDefault(); + $('input[name="valueDivisor"]').val(1024); + $('input[name="valueUnit"]').val(PMA_messages.strKiB); + $('span.unitInput').toggle(true); + $('input[name="useUnit"]').prop('checked', true); + return false; + }); + + $('a[href="#mibDivisor"]').click(function (event) { + event.preventDefault(); + $('input[name="valueDivisor"]').val(1024 * 1024); + $('input[name="valueUnit"]').val(PMA_messages.strMiB); + $('span.unitInput').toggle(true); + $('input[name="useUnit"]').prop('checked', true); + return false; + }); + + $('a[href="#submitClearSeries"]').click(function (event) { + event.preventDefault(); + $('#seriesPreview').html('' + PMA_messages.strNone + ''); + newChart = null; + $('#clearSeriesLink').hide(); + }); + + $('a[href="#submitAddSeries"]').click(function (event) { + event.preventDefault(); + if ($('#variableInput').val() === '') { + return false; + } + + if (newChart === null) { + $('#seriesPreview').html(''); + + newChart = { + title: $('input[name="chartTitle"]').val(), + nodes: [], + series: [], + maxYLabel: 0 + }; + } + + var serie = { + dataPoints: [{ type: 'statusvar', name: $('#variableInput').val() }], + display: $('input[name="differentialValue"]').prop('checked') ? 'differential' : '' + }; + + if (serie.dataPoints[0].name === 'Processes') { + serie.dataPoints[0].type = 'proc'; + } + + if ($('input[name="useDivisor"]').prop('checked')) { + serie.valueDivisor = parseInt($('input[name="valueDivisor"]').val(), 10); + } + + if ($('input[name="useUnit"]').prop('checked')) { + serie.unit = $('input[name="valueUnit"]').val(); + } + + var str = serie.display === 'differential' ? ', ' + PMA_messages.strDifferential : ''; + str += serie.valueDivisor ? (', ' + PMA_sprintf(PMA_messages.strDividedBy, serie.valueDivisor)) : ''; + str += serie.unit ? (', ' + PMA_messages.strUnit + ': ' + serie.unit) : ''; + + var newSeries = { + label: $('#variableInput').val().replace(/_/g, ' ') + }; + newChart.series.push(newSeries); + $('#seriesPreview').append('- ' + escapeHtml(newSeries.label + str) + '
        '); + newChart.nodes.push(serie); + $('#variableInput').val(''); + $('input[name="differentialValue"]').prop('checked', true); + $('input[name="useDivisor"]').prop('checked', false); + $('input[name="useUnit"]').prop('checked', false); + $('input[name="useDivisor"]').trigger('change'); + $('input[name="useUnit"]').trigger('change'); + $('select[name="varChartList"]').get(0).selectedIndex = 0; + + $('#clearSeriesLink').show(); + + return false; + }); + + $('#variableInput').autocomplete({ + source: variableNames + }); + + /* Initializes the monitor, called only once */ + function initGrid () { + var i; + + /* Apply default values & config */ + if (isStorageSupported('localStorage')) { + if (typeof window.localStorage.monitorCharts !== 'undefined') { + runtime.charts = JSON.parse(window.localStorage.monitorCharts); + } + if (typeof window.localStorage.monitorSettings !== 'undefined') { + monitorSettings = JSON.parse(window.localStorage.monitorSettings); + } + + $('a[href="#clearMonitorConfig"]').toggle(runtime.charts !== null); + + if (runtime.charts !== null + && typeof window.localStorage.monitorVersion !== 'undefined' + && monitorProtocolVersion !== window.localStorage.monitorVersion + ) { + $('#emptyDialog').dialog({ title: PMA_messages.strIncompatibleMonitorConfig }); + $('#emptyDialog').html(PMA_messages.strIncompatibleMonitorConfigDescription); + + var dlgBtns = {}; + dlgBtns[PMA_messages.strClose] = function () { + $(this).dialog('close'); + }; + + $('#emptyDialog').dialog({ + width: 400, + buttons: dlgBtns + }); + } + } + + if (runtime.charts === null) { + runtime.charts = defaultChartGrid; + } + if (monitorSettings === null) { + monitorSettings = defaultMonitorSettings; + } + + $('select[name="gridChartRefresh"]').val(monitorSettings.gridRefresh / 1000); + $('select[name="chartColumns"]').val(monitorSettings.columns); + + if (monitorSettings.gridMaxPoints === 'auto') { + runtime.gridMaxPoints = Math.round((monitorSettings.chartSize.width - 40) / 12); + } else { + runtime.gridMaxPoints = monitorSettings.gridMaxPoints; + } + + runtime.xmin = new Date().getTime() - server_time_diff - runtime.gridMaxPoints * monitorSettings.gridRefresh; + runtime.xmax = new Date().getTime() - server_time_diff + monitorSettings.gridRefresh; + + /* Calculate how much spacing there is between each chart */ + $('#chartGrid').html(''); + chartSpacing = { + width: $('#chartGrid').find('td:nth-child(2)').offset().left - + $('#chartGrid').find('td:nth-child(1)').offset().left, + height: $('#chartGrid').find('tr:nth-child(2) td:nth-child(2)').offset().top - + $('#chartGrid').find('tr:nth-child(1) td:nth-child(1)').offset().top + }; + $('#chartGrid').html(''); + + /* Add all charts - in correct order */ + var keys = []; + $.each(runtime.charts, function (key, value) { + keys.push(key); + }); + keys.sort(); + for (i = 0; i < keys.length; i++) { + addChart(runtime.charts[keys[i]], true); + } + + /* Fill in missing cells */ + var numCharts = $('#chartGrid').find('.monitorChart').length; + var numMissingCells = (monitorSettings.columns - numCharts % monitorSettings.columns) % monitorSettings.columns; + for (i = 0; i < numMissingCells; i++) { + $('#chartGrid').find('tr:last').append(''); + } + + // Empty cells should keep their size so you can drop onto them + calculateChartSize(); + $('#chartGrid').find('tr td').css('width', chartSize.width + 'px'); + + buildRequiredDataList(); + refreshChartGrid(); + } + + /* Calls destroyGrid() and initGrid(), but before doing so it saves the chart + * data from each chart and restores it after the monitor is initialized again */ + function rebuildGrid () { + var oldData = null; + if (runtime.charts) { + oldData = {}; + $.each(runtime.charts, function (key, chartObj) { + for (var i = 0, l = chartObj.nodes.length; i < l; i++) { + oldData[chartObj.nodes[i].dataPoint] = []; + for (var j = 0, ll = chartObj.chart.series[i].data.length; j < ll; j++) { + oldData[chartObj.nodes[i].dataPoint].push([chartObj.chart.series[i].data[j].x, chartObj.chart.series[i].data[j].y]); + } + } + }); + } + + destroyGrid(); + initGrid(); + } + + /* Calculactes the dynamic chart size that depends on the column width */ + function calculateChartSize () { + var panelWidth; + if ($('body').height() > $(window).height()) { // has vertical scroll bar + panelWidth = $('#logTable').innerWidth(); + } else { + panelWidth = $('#logTable').innerWidth() - 10; // leave some space for vertical scroll bar + } + + var wdt = panelWidth; + var windowWidth = $(window).width(); + + if (windowWidth > 768) { + wdt = (panelWidth - monitorSettings.columns * chartSpacing.width) / monitorSettings.columns; + } + + chartSize = { + width: Math.floor(wdt), + height: Math.floor(0.75 * wdt) + }; + } + + /* Adds a chart to the chart grid */ + function addChart (chartObj, initialize) { + var i; + var settings = { + title: escapeHtml(chartObj.title), + grid: { + drawBorder: false, + shadow: false, + background: 'rgba(0,0,0,0)' + }, + axes: { + xaxis: { + renderer: $.jqplot.DateAxisRenderer, + tickOptions: { + formatString: '%H:%M:%S', + showGridline: false + }, + min: runtime.xmin, + max: runtime.xmax + }, + yaxis: { + min: 0, + max: 100, + tickInterval: 20 + } + }, + seriesDefaults: { + rendererOptions: { + smooth: true + }, + showLine: true, + lineWidth: 2, + markerOptions: { + size: 6 + } + }, + highlighter: { + show: true + } + }; + + if (settings.title === PMA_messages.strSystemCPUUsage || + settings.title === PMA_messages.strQueryCacheEfficiency + ) { + settings.axes.yaxis.tickOptions = { + formatString: '%d %%' + }; + } else if (settings.title === PMA_messages.strSystemMemory || + settings.title === PMA_messages.strSystemSwap + ) { + settings.stackSeries = true; + settings.axes.yaxis.tickOptions = { + formatter: $.jqplot.byteFormatter(2) // MiB + }; + } else if (settings.title === PMA_messages.strTraffic) { + settings.axes.yaxis.tickOptions = { + formatter: $.jqplot.byteFormatter(1) // KiB + }; + } else if (settings.title === PMA_messages.strQuestions || + settings.title === PMA_messages.strConnections + ) { + settings.axes.yaxis.tickOptions = { + formatter: function (format, val) { + if (Math.abs(val) >= 1000000) { + return $.jqplot.sprintf('%.3g M', val / 1000000); + } else if (Math.abs(val) >= 1000) { + return $.jqplot.sprintf('%.3g k', val / 1000); + } else { + return $.jqplot.sprintf('%d', val); + } + } + }; + } + + settings.series = chartObj.series; + + if ($('#' + 'gridchart' + runtime.chartAI).length === 0) { + var numCharts = $('#chartGrid').find('.monitorChart').length; + + if (numCharts === 0 || (numCharts % monitorSettings.columns === 0)) { + $('#chartGrid').append(''); + } + + if (!chartSize) { + calculateChartSize(); + } + $('#chartGrid').find('tr:last').append( + '
        ' + + '
        ' + + '
        ' + ); + } + + // Set series' data as [0,0], smooth lines won't plot with data array having null values. + // also chart won't plot initially with no data and data comes on refreshChartGrid() + var series = []; + for (i in chartObj.series) { + series.push([[0, 0]]); + } + + var tempTooltipContentEditor = function (str, seriesIndex, pointIndex, plot) { + var j; + // TODO: move style to theme CSS + var tooltipHtml = '
        '; + // x value i.e. time + var timeValue = str.split(',')[0]; + var seriesValue; + tooltipHtml += 'Time: ' + timeValue; + tooltipHtml += ''; + // Add y values to the tooltip per series + for (j in plot.series) { + // get y value if present + if (plot.series[j].data.length > pointIndex) { + seriesValue = plot.series[j].data[pointIndex][1]; + } else { + return; + } + var seriesLabel = plot.series[j].label; + var seriesColor = plot.series[j].color; + // format y value + if (plot.series[0]._yaxis.tickOptions.formatter) { + // using formatter function + seriesValue = plot.series[0]._yaxis.tickOptions.formatter('%s', seriesValue); + } else if (plot.series[0]._yaxis.tickOptions.formatString) { + // using format string + seriesValue = PMA_sprintf(plot.series[0]._yaxis.tickOptions.formatString, seriesValue); + } + tooltipHtml += '
        ' + + seriesLabel + ': ' + seriesValue + ''; + } + tooltipHtml += '
        '; + return tooltipHtml; + }; + + // set Tooltip for each series + for (i in settings.series) { + settings.series[i].highlighter = { + show: true, + tooltipContentEditor: tempTooltipContentEditor + }; + } + + chartObj.chart = $.jqplot('gridchart' + runtime.chartAI, series, settings); + // remove [0,0] after plotting + for (i in chartObj.chart.series) { + chartObj.chart.series[i].data.shift(); + } + + var $legend = $('
        ').css('padding', '0.5em'); + for (i in chartObj.chart.series) { + $legend.append( + $('
        ').append( + $('
        ').css({ + width: '1em', + height: '1em', + background: chartObj.chart.seriesColors[i] + }).addClass('floatleft') + ).append( + $('
        ').text( + chartObj.chart.series[i].label + ).addClass('floatleft') + ).append( + $('
        ') + ).addClass('floatleft') + ); + } + $('#gridchart' + runtime.chartAI) + .parent() + .append($legend); + + if (initialize !== true) { + runtime.charts['c' + runtime.chartAI] = chartObj; + buildRequiredDataList(); + } + + // time span selection + $('#gridchart' + runtime.chartAI).on('jqplotMouseDown', function (ev, gridpos, datapos, neighbor, plot) { + drawTimeSpan = true; + selectionTimeDiff.push(datapos.xaxis); + if ($('#selection_box').length) { + $('#selection_box').remove(); + } + var selectionBox = $('
        '); + $(document.body).append(selectionBox); + selectionStartX = ev.pageX; + selectionStartY = ev.pageY; + selectionBox + .attr({ id: 'selection_box' }) + .css({ + top: selectionStartY - gridpos.y, + left: selectionStartX + }) + .fadeIn(); + }); + + $('#gridchart' + runtime.chartAI).on('jqplotMouseUp', function (ev, gridpos, datapos, neighbor, plot) { + if (! drawTimeSpan || editMode) { + return; + } + + selectionTimeDiff.push(datapos.xaxis); + + if (selectionTimeDiff[1] <= selectionTimeDiff[0]) { + selectionTimeDiff = []; + return; + } + // get date from timestamp + var min = new Date(Math.ceil(selectionTimeDiff[0])); + var max = new Date(Math.ceil(selectionTimeDiff[1])); + PMA_getLogAnalyseDialog(min, max); + selectionTimeDiff = []; + drawTimeSpan = false; + }); + + $('#gridchart' + runtime.chartAI).on('jqplotMouseMove', function (ev, gridpos, datapos, neighbor, plot) { + if (! drawTimeSpan || editMode) { + return; + } + if (selectionStartX !== undefined) { + $('#selection_box') + .css({ + width: Math.ceil(ev.pageX - selectionStartX) + }) + .fadeIn(); + } + }); + + $('#gridchart' + runtime.chartAI).on('jqplotMouseLeave', function (ev, gridpos, datapos, neighbor, plot) { + drawTimeSpan = false; + }); + + $(document.body).mouseup(function () { + if ($('#selection_box').length) { + $('#selection_box').remove(); + } + }); + + // Edit, Print icon only in edit mode + $('#chartGrid').find('div svg').find('*[zIndex=20], *[zIndex=21], *[zIndex=19]').toggle(editMode); + + runtime.chartAI++; + } + + function PMA_getLogAnalyseDialog (min, max) { + var $logAnalyseDialog = $('#logAnalyseDialog'); + var $dateStart = $logAnalyseDialog.find('input[name="dateStart"]'); + var $dateEnd = $logAnalyseDialog.find('input[name="dateEnd"]'); + $dateStart.prop('readonly', true); + $dateEnd.prop('readonly', true); + + var dlgBtns = { }; + + dlgBtns[PMA_messages.strFromSlowLog] = function () { + loadLog('slow', min, max); + $(this).dialog('close'); + }; + + dlgBtns[PMA_messages.strFromGeneralLog] = function () { + loadLog('general', min, max); + $(this).dialog('close'); + }; + + $logAnalyseDialog.dialog({ + width: 'auto', + height: 'auto', + buttons: dlgBtns + }); + + PMA_addDatepicker($dateStart, 'datetime', { + showMillisec: false, + showMicrosec: false, + timeFormat: 'HH:mm:ss' + }); + PMA_addDatepicker($dateEnd, 'datetime', { + showMillisec: false, + showMicrosec: false, + timeFormat: 'HH:mm:ss' + }); + $dateStart.datepicker('setDate', min); + $dateEnd.datepicker('setDate', max); + } + + function loadLog (type, min, max) { + var dateStart = Date.parse($('#logAnalyseDialog').find('input[name="dateStart"]').datepicker('getDate')) || min; + var dateEnd = Date.parse($('#logAnalyseDialog').find('input[name="dateEnd"]').datepicker('getDate')) || max; + + loadLogStatistics({ + src: type, + start: dateStart, + end: dateEnd, + removeVariables: $('#removeVariables').prop('checked'), + limitTypes: $('#limitTypes').prop('checked') + }); + } + + /* Called in regular intervals, this function updates the values of each chart in the grid */ + function refreshChartGrid () { + /* Send to server */ + runtime.refreshRequest = $.post('server_status_monitor.php' + PMA_commonParams.get('common_query'), { + ajax_request: true, + chart_data: 1, + type: 'chartgrid', + requiredData: JSON.stringify(runtime.dataList), + server: PMA_commonParams.get('server') + }, function (data) { + var chartData; + if (typeof data !== 'undefined' && data.success === true) { + chartData = data.message; + } else { + return serverResponseError(); + } + var value; + var i = 0; + var diff; + var total; + + /* Update values in each graph */ + $.each(runtime.charts, function (orderKey, elem) { + var key = elem.chartID; + // If newly added chart, we have no data for it yet + if (! chartData[key]) { + return; + } + // Draw all series + total = 0; + for (var j = 0; j < elem.nodes.length; j++) { + // Update x-axis + if (i === 0 && j === 0) { + if (oldChartData === null) { + diff = chartData.x - runtime.xmax; + } else { + diff = parseInt(chartData.x - oldChartData.x, 10); + } + + runtime.xmin += diff; + runtime.xmax += diff; + } + + // elem.chart.xAxis[0].setExtremes(runtime.xmin, runtime.xmax, false); + /* Calculate y value */ + + // If transform function given, use it + if (elem.nodes[j].transformFn) { + value = chartValueTransform( + elem.nodes[j].transformFn, + chartData[key][j], + // Check if first iteration (oldChartData==null), or if newly added chart oldChartData[key]==null + ( + oldChartData === null || + oldChartData[key] === null || + oldChartData[key] === undefined ? null : oldChartData[key][j] + ) + ); + + // Otherwise use original value and apply differential and divisor if given, + // in this case we have only one data point per series - located at chartData[key][j][0] + } else { + value = parseFloat(chartData[key][j][0].value); + + if (elem.nodes[j].display === 'differential') { + if (oldChartData === null || + oldChartData[key] === null || + oldChartData[key] === undefined + ) { + continue; + } + value -= oldChartData[key][j][0].value; + } + + if (elem.nodes[j].valueDivisor) { + value = value / elem.nodes[j].valueDivisor; + } + } + + // Set y value, if defined + if (value !== undefined) { + elem.chart.series[j].data.push([chartData.x, value]); + if (value > elem.maxYLabel) { + elem.maxYLabel = value; + } else if (elem.maxYLabel === 0) { + elem.maxYLabel = 0.5; + } + // free old data point values and update maxYLabel + if (elem.chart.series[j].data.length > runtime.gridMaxPoints && + elem.chart.series[j].data[0][0] < runtime.xmin + ) { + // check if the next freeable point is highest + if (elem.maxYLabel <= elem.chart.series[j].data[0][1]) { + elem.chart.series[j].data.splice(0, elem.chart.series[j].data.length - runtime.gridMaxPoints); + elem.maxYLabel = getMaxYLabel(elem.chart.series[j].data); + } else { + elem.chart.series[j].data.splice(0, elem.chart.series[j].data.length - runtime.gridMaxPoints); + } + } + if (elem.title === PMA_messages.strSystemMemory || + elem.title === PMA_messages.strSystemSwap + ) { + total += value; + } + } + } + + // update chart options + // keep ticks number/positioning consistent while refreshrate changes + var tickInterval = (runtime.xmax - runtime.xmin) / 5; + elem.chart.axes.xaxis.ticks = [(runtime.xmax - tickInterval * 4), + (runtime.xmax - tickInterval * 3), (runtime.xmax - tickInterval * 2), + (runtime.xmax - tickInterval), runtime.xmax]; + + if (elem.title !== PMA_messages.strSystemCPUUsage && + elem.title !== PMA_messages.strQueryCacheEfficiency && + elem.title !== PMA_messages.strSystemMemory && + elem.title !== PMA_messages.strSystemSwap + ) { + elem.chart.axes.yaxis.max = Math.ceil(elem.maxYLabel * 1.1); + elem.chart.axes.yaxis.tickInterval = Math.ceil(elem.maxYLabel * 1.1 / 5); + } else if (elem.title === PMA_messages.strSystemMemory || + elem.title === PMA_messages.strSystemSwap + ) { + elem.chart.axes.yaxis.max = Math.ceil(total * 1.1 / 100) * 100; + elem.chart.axes.yaxis.tickInterval = Math.ceil(total * 1.1 / 5); + } + i++; + + if (runtime.redrawCharts) { + elem.chart.replot(); + } + }); + + oldChartData = chartData; + + runtime.refreshTimeout = setTimeout(refreshChartGrid, monitorSettings.gridRefresh); + }); + } + + /* Function to get highest plotted point's y label, to scale the chart, + * TODO: make jqplot's autoscale:true work here + */ + function getMaxYLabel (dataValues) { + var maxY = dataValues[0][1]; + $.each(dataValues, function (k, v) { + maxY = (v[1] > maxY) ? v[1] : maxY; + }); + return maxY; + } + + /* Function that supplies special value transform functions for chart values */ + function chartValueTransform (name, cur, prev) { + switch (name) { + case 'cpu-linux': + if (prev === null) { + return undefined; + } + // cur and prev are datapoint arrays, but containing + // only 1 element for cpu-linux + cur = cur[0]; + prev = prev[0]; + + var diff_total = cur.busy + cur.idle - (prev.busy + prev.idle); + var diff_idle = cur.idle - prev.idle; + return 100 * (diff_total - diff_idle) / diff_total; + + // Query cache efficiency (%) + case 'qce': + if (prev === null) { + return undefined; + } + // cur[0].value is Qcache_hits, cur[1].value is Com_select + var diffQHits = cur[0].value - prev[0].value; + // No NaN please :-) + if (cur[1].value - prev[1].value === 0) { + return 0; + } + + return diffQHits / (cur[1].value - prev[1].value + diffQHits) * 100; + + // Query cache usage (%) + case 'qcu': + if (cur[1].value === 0) { + return 0; + } + // cur[0].value is Qcache_free_memory, cur[1].value is query_cache_size + return 100 - cur[0].value / cur[1].value * 100; + } + return undefined; + } + + /* Build list of nodes that need to be retrieved from server. + * It creates something like a stripped down version of the runtime.charts object. + */ + function buildRequiredDataList () { + runtime.dataList = {}; + // Store an own id, because the property name is subject of reordering, + // thus destroying our mapping with runtime.charts <=> runtime.dataList + var chartID = 0; + $.each(runtime.charts, function (key, chart) { + runtime.dataList[chartID] = []; + for (var i = 0, l = chart.nodes.length; i < l; i++) { + runtime.dataList[chartID][i] = chart.nodes[i].dataPoints; + } + runtime.charts[key].chartID = chartID; + chartID++; + }); + } + + /* Loads the log table data, generates the table and handles the filters */ + function loadLogStatistics (opts) { + var tableStr = ''; + var logRequest = null; + + if (! opts.removeVariables) { + opts.removeVariables = false; + } + if (! opts.limitTypes) { + opts.limitTypes = false; + } + + $('#emptyDialog').dialog({ title: PMA_messages.strAnalysingLogsTitle }); + $('#emptyDialog').html(PMA_messages.strAnalysingLogs + + ' '); + var dlgBtns = {}; + + dlgBtns[PMA_messages.strCancelRequest] = function () { + if (logRequest !== null) { + logRequest.abort(); + } + + $(this).dialog('close'); + }; + + $('#emptyDialog').dialog({ + width: 'auto', + height: 'auto', + buttons: dlgBtns + }); + + logRequest = $.post( + 'server_status_monitor.php' + PMA_commonParams.get('common_query'), + { + ajax_request: true, + log_data: 1, + type: opts.src, + time_start: Math.round(opts.start / 1000), + time_end: Math.round(opts.end / 1000), + removeVariables: opts.removeVariables, + limitTypes: opts.limitTypes + }, + function (data) { + var logData; + var dlgBtns = {}; + if (typeof data !== 'undefined' && data.success === true) { + logData = data.message; + } else { + return serverResponseError(); + } + + if (logData.rows.length === 0) { + $('#emptyDialog').dialog({ title: PMA_messages.strNoDataFoundTitle }); + $('#emptyDialog').html('

        ' + PMA_messages.strNoDataFound + '

        '); + + dlgBtns[PMA_messages.strClose] = function () { + $(this).dialog('close'); + }; + + $('#emptyDialog').dialog('option', 'buttons', dlgBtns); + return; + } + + runtime.logDataCols = buildLogTable(logData, opts.removeVariables); + + /* Show some stats in the dialog */ + $('#emptyDialog').dialog({ title: PMA_messages.strLoadingLogs }); + $('#emptyDialog').html('

        ' + PMA_messages.strLogDataLoaded + '

        '); + $.each(logData.sum, function (key, value) { + key = key.charAt(0).toUpperCase() + key.slice(1).toLowerCase(); + if (key === 'Total') { + key = '' + key + ''; + } + $('#emptyDialog').append(key + ': ' + value + '
        '); + }); + + /* Add filter options if more than a bunch of rows there to filter */ + if (logData.numRows > 12) { + $('#logTable').prepend( + '
        ' + + ' ' + PMA_messages.strFiltersForLogTable + '' + + '
        ' + + ' ' + + ' ' + + '
        ' + + ((logData.numRows > 250) ? '
        ' : '') + + '
        ' + + ' ' + + ' ' + + ' ' + ); + + $('#noWHEREData').change(function () { + filterQueries(true); + }); + + if (logData.numRows > 250) { + $('#startFilterQueryText').click(filterQueries); + } else { + $('#filterQueryText').keyup(filterQueries); + } + } + + dlgBtns[PMA_messages.strJumpToTable] = function () { + $(this).dialog('close'); + $(document).scrollTop($('#logTable').offset().top); + }; + + $('#emptyDialog').dialog('option', 'buttons', dlgBtns); + } + ); + + /* Handles the actions performed when the user uses any of the + * log table filters which are the filter by name and grouping + * with ignoring data in WHERE clauses + * + * @param boolean Should be true when the users enabled or disabled + * to group queries ignoring data in WHERE clauses + */ + function filterQueries (varFilterChange) { + var cell; + var textFilter; + var val = $('#filterQueryText').val(); + + if (val.length === 0) { + textFilter = null; + } else { + try { + textFilter = new RegExp(val, 'i'); + $('#filterQueryText').removeClass('error'); + } catch (e) { + if (e instanceof SyntaxError) { + $('#filterQueryText').addClass('error'); + textFilter = null; + } + } + } + + var rowSum = 0; + var totalSum = 0; + var i = 0; + var q; + var noVars = $('#noWHEREData').prop('checked'); + var equalsFilter = /([^=]+)=(\d+|((\'|"|).*?[^\\])\4((\s+)|$))/gi; + var functionFilter = /([a-z0-9_]+)\(.+?\)/gi; + var filteredQueries = {}; + var filteredQueriesLines = {}; + var hide = false; + var rowData; + var queryColumnName = runtime.logDataCols[runtime.logDataCols.length - 2]; + var sumColumnName = runtime.logDataCols[runtime.logDataCols.length - 1]; + var isSlowLog = opts.src === 'slow'; + var columnSums = {}; + + // For the slow log we have to count many columns (query_time, lock_time, rows_examined, rows_sent, etc.) + var countRow = function (query, row) { + var cells = row.match(/(.*?)<\/td>/gi); + if (!columnSums[query]) { + columnSums[query] = [0, 0, 0, 0]; + } + + // lock_time and query_time and displayed in timespan format + columnSums[query][0] += timeToSec(cells[2].replace(/(|<\/td>)/gi, '')); + columnSums[query][1] += timeToSec(cells[3].replace(/(|<\/td>)/gi, '')); + // rows_examind and rows_sent are just numbers + columnSums[query][2] += parseInt(cells[4].replace(/(|<\/td>)/gi, ''), 10); + columnSums[query][3] += parseInt(cells[5].replace(/(|<\/td>)/gi, ''), 10); + }; + + // We just assume the sql text is always in the second last column, and that the total count is right of it + $('#logTable').find('table tbody tr td:nth-child(' + (runtime.logDataCols.length - 1) + ')').each(function () { + var $t = $(this); + // If query is a SELECT and user enabled or disabled to group + // queries ignoring data in where statements, we + // need to re-calculate the sums of each row + if (varFilterChange && $t.html().match(/^SELECT/i)) { + if (noVars) { + // Group on => Sum up identical columns, and hide all but 1 + + q = $t.text().replace(equalsFilter, '$1=...$6').trim(); + q = q.replace(functionFilter, ' $1(...)'); + + // Js does not specify a limit on property name length, + // so we can abuse it as index :-) + if (filteredQueries[q]) { + filteredQueries[q] += parseInt($t.next().text(), 10); + totalSum += parseInt($t.next().text(), 10); + hide = true; + } else { + filteredQueries[q] = parseInt($t.next().text(), 10); + filteredQueriesLines[q] = i; + $t.text(q); + } + if (isSlowLog) { + countRow(q, $t.parent().html()); + } + } else { + // Group off: Restore original columns + + rowData = $t.parent().data('query'); + // Restore SQL text + $t.text(rowData[queryColumnName]); + // Restore total count + $t.next().text(rowData[sumColumnName]); + // Restore slow log columns + if (isSlowLog) { + $t.parent().children('td:nth-child(3)').text(rowData.query_time); + $t.parent().children('td:nth-child(4)').text(rowData.lock_time); + $t.parent().children('td:nth-child(5)').text(rowData.rows_sent); + $t.parent().children('td:nth-child(6)').text(rowData.rows_examined); + } + } + } + + // If not required to be hidden, do we need + // to hide because of a not matching text filter? + if (! hide && (textFilter !== null && ! textFilter.exec($t.text()))) { + hide = true; + } + + // Now display or hide this column + if (hide) { + $t.parent().css('display', 'none'); + } else { + totalSum += parseInt($t.next().text(), 10); + rowSum++; + $t.parent().css('display', ''); + } + + hide = false; + i++; + }); + + // We finished summarizing counts => Update count values of all grouped entries + if (varFilterChange) { + if (noVars) { + var numCol; + var row; + var $table = $('#logTable').find('table tbody'); + $.each(filteredQueriesLines, function (key, value) { + if (filteredQueries[key] <= 1) { + return; + } + + row = $table.children('tr:nth-child(' + (value + 1) + ')'); + numCol = row.children(':nth-child(' + (runtime.logDataCols.length) + ')'); + numCol.text(filteredQueries[key]); + + if (isSlowLog) { + row.children('td:nth-child(3)').text(secToTime(columnSums[key][0])); + row.children('td:nth-child(4)').text(secToTime(columnSums[key][1])); + row.children('td:nth-child(5)').text(columnSums[key][2]); + row.children('td:nth-child(6)').text(columnSums[key][3]); + } + }); + } + + $('#logTable').find('table').trigger('update'); + setTimeout(function () { + $('#logTable').find('table').trigger('sorton', [[[runtime.logDataCols.length - 1, 1]]]); + }, 0); + } + + // Display some stats at the bottom of the table + $('#logTable').find('table tfoot tr') + .html('' + + PMA_messages.strSumRows + ' ' + rowSum + '' + + PMA_messages.strTotal + '' + totalSum + ''); + } + } + + /* Turns a timespan (12:12:12) into a number */ + function timeToSec (timeStr) { + var time = timeStr.split(':'); + return (parseInt(time[0], 10) * 3600) + (parseInt(time[1], 10) * 60) + parseInt(time[2], 10); + } + + /* Turns a number into a timespan (100 into 00:01:40) */ + function secToTime (timeInt) { + var hours = Math.floor(timeInt / 3600); + timeInt -= hours * 3600; + var minutes = Math.floor(timeInt / 60); + timeInt -= minutes * 60; + + if (hours < 10) { + hours = '0' + hours; + } + if (minutes < 10) { + minutes = '0' + minutes; + } + if (timeInt < 10) { + timeInt = '0' + timeInt; + } + + return hours + ':' + minutes + ':' + timeInt; + } + + /* Constructs the log table out of the retrieved server data */ + function buildLogTable (data, groupInserts) { + var rows = data.rows; + var cols = []; + var $table = $('
        '); + var $tBody; + var $tRow; + var $tCell; + + $('#logTable').html($table); + + var tempPushKey = function (key, value) { + cols.push(key); + }; + + var formatValue = function (name, value) { + if (name === 'user_host') { + return value.replace(/(\[.*?\])+/g, ''); + } + return escapeHtml(value); + }; + + for (var i = 0, l = rows.length; i < l; i++) { + if (i === 0) { + $.each(rows[0], tempPushKey); + $table.append('' + + '' + cols.join('') + '' + + '' + ); + + $table.append($tBody = $('')); + } + + $tBody.append($tRow = $('')); + var cl = ''; + for (var j = 0, ll = cols.length; j < ll; j++) { + // Assuming the query column is the second last + if (j === cols.length - 2 && rows[i][cols[j]].match(/^SELECT/i)) { + $tRow.append($tCell = $('' + formatValue(cols[j], rows[i][cols[j]]) + '')); + $tCell.click(openQueryAnalyzer); + } else { + $tRow.append('' + formatValue(cols[j], rows[i][cols[j]]) + ''); + } + + $tRow.data('query', rows[i]); + } + } + + $table.append('' + + '' + PMA_messages.strSumRows + + ' ' + data.numRows + '' + PMA_messages.strTotal + + '' + data.sum.TOTAL + ''); + + // Append a tooltip to the count column, if there exist one + if ($('#logTable').find('tr:first th:last').text().indexOf('#') > -1) { + $('#logTable').find('tr:first th:last').append(' ' + PMA_getImage('b_help', '', { 'class': 'qroupedQueryInfoIcon' })); + + var tooltipContent = PMA_messages.strCountColumnExplanation; + if (groupInserts) { + tooltipContent += '

        ' + PMA_messages.strMoreCountColumnExplanation + '

        '; + } + + PMA_tooltip( + $('img.qroupedQueryInfoIcon'), + 'img', + tooltipContent + ); + } + + $('#logTable').find('table').tablesorter({ + sortList: [[cols.length - 1, 1]], + widgets: ['fast-zebra'] + }); + + $('#logTable').find('table thead th') + .append('
        '); + + return cols; + } + + /* Opens the query analyzer dialog */ + function openQueryAnalyzer () { + var rowData = $(this).parent().data('query'); + var query = rowData.argument || rowData.sql_text; + + if (codemirror_editor) { + // TODO: somehow PMA_SQLPrettyPrint messes up the query, needs be fixed + // query = PMA_SQLPrettyPrint(query); + codemirror_editor.setValue(query); + // Codemirror is bugged, it doesn't refresh properly sometimes. + // Following lines seem to fix that + setTimeout(function () { + codemirror_editor.refresh(); + }, 50); + } else { + $('#sqlquery').val(query); + } + + var profilingChart = null; + var dlgBtns = {}; + + dlgBtns[PMA_messages.strAnalyzeQuery] = function () { + loadQueryAnalysis(rowData); + }; + dlgBtns[PMA_messages.strClose] = function () { + $(this).dialog('close'); + }; + + $('#queryAnalyzerDialog').dialog({ + width: 'auto', + height: 'auto', + resizable: false, + buttons: dlgBtns, + close: function () { + if (profilingChart !== null) { + profilingChart.destroy(); + } + $('#queryAnalyzerDialog').find('div.placeHolder').html(''); + if (codemirror_editor) { + codemirror_editor.setValue(''); + } else { + $('#sqlquery').val(''); + } + } + }); + } + + /* Loads and displays the analyzed query data */ + function loadQueryAnalysis (rowData) { + var db = rowData.db || ''; + + $('#queryAnalyzerDialog').find('div.placeHolder').html( + PMA_messages.strAnalyzing + ' '); + + $.post('server_status_monitor.php' + PMA_commonParams.get('common_query'), { + ajax_request: true, + query_analyzer: true, + query: codemirror_editor ? codemirror_editor.getValue() : $('#sqlquery').val(), + database: db, + server: PMA_commonParams.get('server') + }, function (data) { + var i; + var l; + if (typeof data !== 'undefined' && data.success === true) { + data = data.message; + } + if (data.error) { + if (data.error.indexOf('1146') !== -1 || data.error.indexOf('1046') !== -1) { + data.error = PMA_messages.strServerLogError; + } + $('#queryAnalyzerDialog').find('div.placeHolder').html('
        ' + data.error + '
        '); + return; + } + var totalTime = 0; + // Float sux, I'll use table :( + $('#queryAnalyzerDialog').find('div.placeHolder') + .html('
        '); + + var explain = '' + PMA_messages.strExplainOutput + ' ' + $('#explain_docu').html(); + if (data.explain.length > 1) { + explain += ' ('; + for (i = 0; i < data.explain.length; i++) { + if (i > 0) { + explain += ', '; + } + explain += '' + i + ''; + } + explain += ')'; + } + explain += '

        '; + + var tempExplain = function (key, value) { + value = (value === null) ? 'null' : escapeHtml(value); + + if (key === 'type' && value.toLowerCase() === 'all') { + value = '' + value + ''; + } + if (key === 'Extra') { + value = value.replace(/(using (temporary|filesort))/gi, '$1'); + } + explain += key + ': ' + value + '
        '; + }; + + for (i = 0, l = data.explain.length; i < l; i++) { + explain += '
        0 ? 'style="display:none;"' : '') + '>'; + $.each(data.explain[i], tempExplain); + explain += '
        '; + } + + explain += '

        ' + PMA_messages.strAffectedRows + ' ' + data.affectedRows; + + $('#queryAnalyzerDialog').find('div.placeHolder td.explain').append(explain); + + $('#queryAnalyzerDialog').find('div.placeHolder a[href*="#showExplain"]').click(function () { + var id = $(this).attr('href').split('-')[1]; + $(this).parent().find('div[class*="explain"]').hide(); + $(this).parent().find('div[class*="explain-' + id + '"]').show(); + }); + + if (data.profiling) { + var chartData = []; + var numberTable = ''; + var duration; + var otherTime = 0; + + for (i = 0, l = data.profiling.length; i < l; i++) { + duration = parseFloat(data.profiling[i].duration); + + totalTime += duration; + + numberTable += ''; + } + + // Only put those values in the pie which are > 2% + for (i = 0, l = data.profiling.length; i < l; i++) { + duration = parseFloat(data.profiling[i].duration); + + if (duration / totalTime > 0.02) { + chartData.push([PMA_prettyProfilingNum(duration, 2) + ' ' + data.profiling[i].state, duration]); + } else { + otherTime += duration; + } + } + + if (otherTime > 0) { + chartData.push([PMA_prettyProfilingNum(otherTime, 2) + ' ' + PMA_messages.strOther, otherTime]); + } + + numberTable += ''; + numberTable += '
        ' + PMA_messages.strStatus + '' + PMA_messages.strTime + '
        ' + data.profiling[i].state + ' ' + PMA_prettyProfilingNum(duration, 2) + '
        ' + PMA_messages.strTotalTime + '' + PMA_prettyProfilingNum(totalTime, 2) + '
        '; + + $('#queryAnalyzerDialog').find('div.placeHolder td.chart').append( + '' + PMA_messages.strProfilingResults + ' ' + $('#profiling_docu').html() + ' ' + + '(' + PMA_messages.strTable + ', ' + PMA_messages.strChart + ')
        ' + + numberTable + '

        '); + + $('#queryAnalyzerDialog').find('div.placeHolder a[href="#showNums"]').click(function () { + $('#queryAnalyzerDialog').find('#queryProfiling').hide(); + $('#queryAnalyzerDialog').find('table.queryNums').show(); + return false; + }); + + $('#queryAnalyzerDialog').find('div.placeHolder a[href="#showChart"]').click(function () { + $('#queryAnalyzerDialog').find('#queryProfiling').show(); + $('#queryAnalyzerDialog').find('table.queryNums').hide(); + return false; + }); + + profilingChart = PMA_createProfilingChart( + 'queryProfiling', + chartData + ); + + // $('#queryProfiling').resizable(); + } + }); + } + + /* Saves the monitor to localstorage */ + function saveMonitor () { + var gridCopy = {}; + + $.each(runtime.charts, function (key, elem) { + gridCopy[key] = {}; + gridCopy[key].nodes = elem.nodes; + gridCopy[key].settings = elem.settings; + gridCopy[key].title = elem.title; + gridCopy[key].series = elem.series; + gridCopy[key].maxYLabel = elem.maxYLabel; + }); + + if (isStorageSupported('localStorage')) { + window.localStorage.monitorCharts = JSON.stringify(gridCopy); + window.localStorage.monitorSettings = JSON.stringify(monitorSettings); + window.localStorage.monitorVersion = monitorProtocolVersion; + } + + $('a[href="#clearMonitorConfig"]').show(); + } +}); + +// Run the monitor once loaded +AJAX.registerOnload('server_status_monitor.js', function () { + $('a[href="#pauseCharts"]').trigger('click'); +}); + +function serverResponseError () { + var btns = {}; + btns[PMA_messages.strReloadPage] = function () { + window.location.reload(); + }; + $('#emptyDialog').dialog({ title: PMA_messages.strRefreshFailed }); + $('#emptyDialog').html( + PMA_getImage('s_attention') + + PMA_messages.strInvalidResponseExplanation + ); + $('#emptyDialog').dialog({ buttons: btns }); +} + +/* Destroys all monitor related resources */ +function destroyGrid () { + if (runtime.charts) { + $.each(runtime.charts, function (key, value) { + try { + value.chart.destroy(); + } catch (err) {} + }); + } + + try { + runtime.refreshRequest.abort(); + } catch (err) {} + try { + clearTimeout(runtime.refreshTimeout); + } catch (err) {} + $('#chartGrid').html(''); + runtime.charts = null; + runtime.chartAI = 0; + monitorSettings = null; // TODO:this not global variable +} diff --git a/php/apps/phpmyadmin49/js/server_status_processes.js b/php/apps/phpmyadmin49/js/server_status_processes.js new file mode 100644 index 00000000..2865bca9 --- /dev/null +++ b/php/apps/phpmyadmin49/js/server_status_processes.js @@ -0,0 +1,189 @@ +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * Server Status Processes + * + * @package PhpMyAdmin + */ + +// object to store process list state information +var processList = { + + // denotes whether auto refresh is on or off + autoRefresh: false, + // stores the GET request which refresh process list + refreshRequest: null, + // stores the timeout id returned by setTimeout + refreshTimeout: null, + // the refresh interval in seconds + refreshInterval: null, + // the refresh URL (required to save last used option) + // i.e. full or sorting url + refreshUrl: null, + + /** + * Handles killing of a process + * + * @return void + */ + init: function () { + processList.setRefreshLabel(); + if (processList.refreshUrl === null) { + processList.refreshUrl = 'server_status_processes.php' + + PMA_commonParams.get('common_query'); + } + if (processList.refreshInterval === null) { + processList.refreshInterval = $('#id_refreshRate').val(); + } else { + $('#id_refreshRate').val(processList.refreshInterval); + } + }, + + /** + * Handles killing of a process + * + * @param object the event object + * + * @return void + */ + killProcessHandler: function (event) { + event.preventDefault(); + var argSep = PMA_commonParams.get('arg_separator'); + var params = $(this).getPostData(); + params += argSep + 'ajax_request=1' + argSep + 'server=' + PMA_commonParams.get('server'); + // Get row element of the process to be killed. + var $tr = $(this).closest('tr'); + $.post($(this).attr('href'), params, function (data) { + // Check if process was killed or not. + if (data.hasOwnProperty('success') && data.success) { + // remove the row of killed process. + $tr.remove(); + // As we just removed a row, reapply odd-even classes + // to keep table stripes consistent + var $tableProcessListTr = $('#tableprocesslist').find('> tbody > tr'); + $tableProcessListTr.filter(':even').removeClass('odd').addClass('even'); + $tableProcessListTr.filter(':odd').removeClass('even').addClass('odd'); + // Show process killed message + PMA_ajaxShowMessage(data.message, false); + } else { + // Show process error message + PMA_ajaxShowMessage(data.error, false); + } + }, 'json'); + }, + + /** + * Handles Auto Refreshing + * + * @param object the event object + * + * @return void + */ + refresh: function (event) { + // abort any previous pending requests + // this is necessary, it may go into + // multiple loops causing unnecessary + // requests even after leaving the page. + processList.abortRefresh(); + // if auto refresh is enabled + if (processList.autoRefresh) { + var interval = parseInt(processList.refreshInterval, 10) * 1000; + var urlParams = processList.getUrlParams(); + processList.refreshRequest = $.post(processList.refreshUrl, + urlParams, + function (data) { + if (data.hasOwnProperty('success') && data.success) { + $newTable = $(data.message); + $('#tableprocesslist').html($newTable.html()); + PMA_highlightSQL($('#tableprocesslist')); + } + processList.refreshTimeout = setTimeout( + processList.refresh, + interval + ); + }); + } + }, + + /** + * Stop current request and clears timeout + * + * @return void + */ + abortRefresh: function () { + if (processList.refreshRequest !== null) { + processList.refreshRequest.abort(); + processList.refreshRequest = null; + } + clearTimeout(processList.refreshTimeout); + }, + + /** + * Set label of refresh button + * change between play & pause + * + * @return void + */ + setRefreshLabel: function () { + var img = 'play'; + var label = PMA_messages.strStartRefresh; + if (processList.autoRefresh) { + img = 'pause'; + label = PMA_messages.strStopRefresh; + processList.refresh(); + } + $('a#toggleRefresh').html(PMA_getImage(img) + escapeHtml(label)); + }, + + /** + * Return the Url Parameters + * for autorefresh request, + * includes showExecuting if the filter is checked + * + * @return urlParams - url parameters with autoRefresh request + */ + getUrlParams: function () { + var urlParams = { 'ajax_request': true, 'refresh': true }; + if ($('#showExecuting').is(':checked')) { + urlParams.showExecuting = true; + return urlParams; + } + return urlParams; + } +}; + +AJAX.registerOnload('server_status_processes.js', function () { + processList.init(); + // Bind event handler for kill_process + $('#tableprocesslist').on( + 'click', + 'a.kill_process', + processList.killProcessHandler + ); + // Bind event handler for toggling refresh of process list + $('a#toggleRefresh').on('click', function (event) { + event.preventDefault(); + processList.autoRefresh = !processList.autoRefresh; + processList.setRefreshLabel(); + }); + // Bind event handler for change in refresh rate + $('#id_refreshRate').on('change', function (event) { + processList.refreshInterval = $(this).val(); + processList.refresh(); + }); + // Bind event handler for table header links + $('#tableprocesslist').on('click', 'thead a', function () { + processList.refreshUrl = $(this).attr('href'); + }); +}); + +/** + * Unbind all event handlers before tearing down a page + */ +AJAX.registerTeardown('server_status_processes.js', function () { + $('#tableprocesslist').off('click', 'a.kill_process'); + $('a#toggleRefresh').off('click'); + $('#id_refreshRate').off('change'); + $('#tableprocesslist').off('click', 'thead a'); + // stop refreshing further + processList.abortRefresh(); +}); diff --git a/php/apps/phpmyadmin49/js/server_status_queries.js b/php/apps/phpmyadmin49/js/server_status_queries.js new file mode 100644 index 00000000..056cffeb --- /dev/null +++ b/php/apps/phpmyadmin49/js/server_status_queries.js @@ -0,0 +1,34 @@ +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + */ + +/** + * Unbind all event handlers before tearing down a page + */ +AJAX.registerTeardown('server_status_queries.js', function () { + var queryPieChart = $('#serverstatusquerieschart').data('queryPieChart'); + if (queryPieChart) { + queryPieChart.destroy(); + } +}); + +AJAX.registerOnload('server_status_queries.js', function () { + // Build query statistics chart + var cdata = []; + try { + $.each($('#serverstatusquerieschart').data('chart'), function (key, value) { + cdata.push([key, parseInt(value, 10)]); + }); + $('#serverstatusquerieschart').data( + 'queryPieChart', + PMA_createProfilingChart( + 'serverstatusquerieschart', + cdata + ) + ); + } catch (exception) { + // Could not load chart, no big deal... + } + + initTableSorter('statustabs_queries'); +}); diff --git a/php/apps/phpmyadmin49/js/server_status_sorter.js b/php/apps/phpmyadmin49/js/server_status_sorter.js new file mode 100644 index 00000000..36c918a8 --- /dev/null +++ b/php/apps/phpmyadmin49/js/server_status_sorter.js @@ -0,0 +1,70 @@ +// TODO: tablesorter shouldn't sort already sorted columns +function initTableSorter (tabid) { + var $table; + var opts; + switch (tabid) { + case 'statustabs_queries': + $table = $('#serverstatusqueriesdetails'); + opts = { + sortList: [[3, 1]], + headers: { + 1: { sorter: 'fancyNumber' }, + 2: { sorter: 'fancyNumber' } + } + }; + break; + } + $table.tablesorter(opts); + $table.find('tr:first th') + .append('
        '); +} + +$(function () { + $.tablesorter.addParser({ + id: 'fancyNumber', + is: function (s) { + return (/^[0-9]?[0-9,\.]*\s?(k|M|G|T|%)?$/).test(s); + }, + format: function (s) { + var num = jQuery.tablesorter.formatFloat( + s.replace(PMA_messages.strThousandsSeparator, '') + .replace(PMA_messages.strDecimalSeparator, '.') + ); + + var factor = 1; + switch (s.charAt(s.length - 1)) { + case '%': + factor = -2; + break; + // Todo: Complete this list (as well as in the regexp a few lines up) + case 'k': + factor = 3; + break; + case 'M': + factor = 6; + break; + case 'G': + factor = 9; + break; + case 'T': + factor = 12; + break; + } + + return num * Math.pow(10, factor); + }, + type: 'numeric' + }); + + $.tablesorter.addParser({ + id: 'withinSpanNumber', + is: function (s) { + return (/(.*)?<\/span>/); + return (res && res.length >= 3) ? res[2] : 0; + }, + type: 'numeric' + }); +}); diff --git a/php/apps/phpmyadmin49/js/server_status_variables.js b/php/apps/phpmyadmin49/js/server_status_variables.js new file mode 100644 index 00000000..4028bc75 --- /dev/null +++ b/php/apps/phpmyadmin49/js/server_status_variables.js @@ -0,0 +1,100 @@ +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * + * + * @package PhpMyAdmin + */ + +/** + * Unbind all event handlers before tearing down a page + */ +AJAX.registerTeardown('server_status_variables.js', function () { + $('#filterAlert').off('change'); + $('#filterText').off('keyup'); + $('#filterCategory').off('change'); + $('#dontFormat').off('change'); +}); + +AJAX.registerOnload('server_status_variables.js', function () { + // Filters for status variables + var textFilter = null; + var alertFilter = $('#filterAlert').prop('checked'); + var categoryFilter = $('#filterCategory').find(':selected').val(); + var text = ''; // Holds filter text + + /* 3 Filtering functions */ + $('#filterAlert').change(function () { + alertFilter = this.checked; + filterVariables(); + }); + + $('#filterCategory').change(function () { + categoryFilter = $(this).val(); + filterVariables(); + }); + + $('#dontFormat').change(function () { + // Hiding the table while changing values speeds up the process a lot + $('#serverstatusvariables').hide(); + $('#serverstatusvariables').find('td.value span.original').toggle(this.checked); + $('#serverstatusvariables').find('td.value span.formatted').toggle(! this.checked); + $('#serverstatusvariables').show(); + }).trigger('change'); + + $('#filterText').keyup(function (e) { + var word = $(this).val().replace(/_/g, ' '); + if (word.length === 0) { + textFilter = null; + } else { + try { + textFilter = new RegExp('(^| )' + word, 'i'); + $(this).removeClass('error'); + } catch (e) { + if (e instanceof SyntaxError) { + $(this).addClass('error'); + textFilter = null; + } + } + } + text = word; + filterVariables(); + }).trigger('keyup'); + + /* Filters the status variables by name/category/alert in the variables tab */ + function filterVariables () { + var useful_links = 0; + var section = text; + + if (categoryFilter.length > 0) { + section = categoryFilter; + } + + if (section.length > 1) { + $('#linkSuggestions').find('span').each(function () { + if ($(this).attr('class').indexOf('status_' + section) !== -1) { + useful_links++; + $(this).css('display', ''); + } else { + $(this).css('display', 'none'); + } + }); + } + + if (useful_links > 0) { + $('#linkSuggestions').css('display', ''); + } else { + $('#linkSuggestions').css('display', 'none'); + } + + $('#serverstatusvariables').find('th.name').each(function () { + if ((textFilter === null || textFilter.exec($(this).text())) && + (! alertFilter || $(this).next().find('span.attention').length > 0) && + (categoryFilter.length === 0 || $(this).parent().hasClass('s_' + categoryFilter)) + ) { + $(this).parent().css('display', ''); + } else { + $(this).parent().css('display', 'none'); + } + }); + } +}); diff --git a/php/apps/phpmyadmin49/js/server_user_groups.js b/php/apps/phpmyadmin49/js/server_user_groups.js new file mode 100644 index 00000000..513777a9 --- /dev/null +++ b/php/apps/phpmyadmin49/js/server_user_groups.js @@ -0,0 +1,41 @@ +/** + * Unbind all event handlers before tearing down a page + */ +AJAX.registerTeardown('server_user_groups.js', function () { + $(document).off('click', 'a.deleteUserGroup.ajax'); +}); + +/** + * Bind event handlers + */ +AJAX.registerOnload('server_user_groups.js', function () { + // update the checkall checkbox on Edit user group page + $(checkboxes_sel).trigger('change'); + + $(document).on('click', 'a.deleteUserGroup.ajax', function (event) { + event.preventDefault(); + var $link = $(this); + var groupName = $link.parents('tr').find('td:first').text(); + var buttonOptions = {}; + buttonOptions[PMA_messages.strGo] = function () { + $(this).dialog('close'); + $link.removeClass('ajax').trigger('click'); + }; + buttonOptions[PMA_messages.strClose] = function () { + $(this).dialog('close'); + }; + $('
        ') + .attr('id', 'confirmUserGroupDeleteDialog') + .append(PMA_sprintf(PMA_messages.strDropUserGroupWarning, escapeHtml(groupName))) + .dialog({ + width: 300, + minWidth: 200, + modal: true, + buttons: buttonOptions, + title: PMA_messages.strConfirm, + close: function () { + $(this).remove(); + } + }); + }); +}); diff --git a/php/apps/phpmyadmin49/js/server_variables.js b/php/apps/phpmyadmin49/js/server_variables.js new file mode 100644 index 00000000..6b7f01fa --- /dev/null +++ b/php/apps/phpmyadmin49/js/server_variables.js @@ -0,0 +1,112 @@ +/* vim: set expandtab sw=4 ts=4 sts=4: */ + +/** + * Unbind all event handlers before tearing down a page + */ +AJAX.registerTeardown('server_variables.js', function () { + $(document).off('click', 'a.editLink'); + $('#serverVariables').find('.var-name').find('a img').remove(); +}); + +AJAX.registerOnload('server_variables.js', function () { + var $editLink = $('a.editLink'); + var $saveLink = $('a.saveLink'); + var $cancelLink = $('a.cancelLink'); + + $('#serverVariables').find('.var-name').find('a').append( + $('#docImage').clone().css('display', 'inline-block') + ); + + /* Launches the variable editor */ + $(document).on('click', 'a.editLink', function (event) { + event.preventDefault(); + editVariable(this); + }); + + /* Allows the user to edit a server variable */ + function editVariable (link) { + var $link = $(link); + var $cell = $link.parent(); + var $valueCell = $link.parents('.var-row').find('.var-value'); + var varName = $link.data('variable'); + + var $mySaveLink = $saveLink.clone().css('display', 'inline-block'); + var $myCancelLink = $cancelLink.clone().css('display', 'inline-block'); + var $msgbox = PMA_ajaxShowMessage(); + var $myEditLink = $cell.find('a.editLink'); + $cell.addClass('edit'); // variable is being edited + $myEditLink.remove(); // remove edit link + + $mySaveLink.click(function () { + var $msgbox = PMA_ajaxShowMessage(PMA_messages.strProcessingRequest); + $.post($(this).attr('href'), { + ajax_request: true, + type: 'setval', + varName: varName, + varValue: $valueCell.find('input').val() + }, function (data) { + if (data.success) { + $valueCell + .html(data.variable) + .data('content', data.variable); + PMA_ajaxRemoveMessage($msgbox); + } else { + if (data.error === '') { + PMA_ajaxShowMessage(PMA_messages.strRequestFailed, false); + } else { + PMA_ajaxShowMessage(data.error, false); + } + $valueCell.html($valueCell.data('content')); + } + $cell.removeClass('edit').html($myEditLink); + }); + return false; + }); + + $myCancelLink.click(function () { + $valueCell.html($valueCell.data('content')); + $cell.removeClass('edit').html($myEditLink); + return false; + }); + + $.get($mySaveLink.attr('href'), { + ajax_request: true, + type: 'getval', + varName: varName + }, function (data) { + if (typeof data !== 'undefined' && data.success === true) { + var $links = $('
        ') + .append($myCancelLink) + .append('   ') + .append($mySaveLink); + var $editor = $('
        ', { 'class': 'serverVariableEditor' }) + .append( + $('
        ').append( + $('', { type: 'text' }).val(data.message) + ) + ); + // Save and replace content + $cell + .html($links) + .children() + .css('display', 'flex'); + $valueCell + .data('content', $valueCell.html()) + .html($editor) + .find('input') + .focus() + .keydown(function (event) { // Keyboard shortcuts + if (event.keyCode === 13) { // Enter key + $mySaveLink.trigger('click'); + } else if (event.keyCode === 27) { // Escape key + $myCancelLink.trigger('click'); + } + }); + PMA_ajaxRemoveMessage($msgbox); + } else { + $cell.removeClass('edit').html($myEditLink); + PMA_ajaxShowMessage(data.error); + } + }); + } +}); diff --git a/php/apps/phpmyadmin49/js/shortcuts_handler.js b/php/apps/phpmyadmin49/js/shortcuts_handler.js new file mode 100644 index 00000000..4fc3c59a --- /dev/null +++ b/php/apps/phpmyadmin49/js/shortcuts_handler.js @@ -0,0 +1,101 @@ +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * @fileoverview Handle shortcuts in various pages + * @name Shortcuts handler + * + * @requires jQuery + * @requires jQueryUI + */ + +/** + * Register key events on load + */ +$(document).ready(function () { + var databaseOp = false; + var tableOp = false; + var keyD = 68; + var keyT = 84; + var keyK = 75; + var keyS = 83; + var keyF = 70; + var keyE = 69; + var keyH = 72; + var keyC = 67; + var keyBackSpace = 8; + $(document).on('keyup', function (e) { + if (e.target.nodeName === 'INPUT' || e.target.nodeName === 'TEXTAREA' || e.target.nodeName === 'SELECT') { + return; + } + + if (e.keyCode === keyD) { + setTimeout(function () { + databaseOp = false; + }, 2000); + } else if (e.keyCode === keyT) { + setTimeout(function () { + tableOp = false; + }, 2000); + } + }); + $(document).on('keydown', function (e) { + if (e.ctrlKey && e.altKey && e.keyCode === keyC) { + PMA_console.toggle(); + } + + if (e.ctrlKey && e.keyCode === keyK) { + e.preventDefault(); + PMA_console.toggle(); + } + + if (e.target.nodeName === 'INPUT' || e.target.nodeName === 'TEXTAREA' || e.target.nodeName === 'SELECT') { + return; + } + + var isTable; + var isDb; + if (e.keyCode === keyD) { + databaseOp = true; + } else if (e.keyCode === keyK) { + e.preventDefault(); + PMA_console.toggle(); + } else if (e.keyCode === keyS) { + if (databaseOp === true) { + isTable = PMA_commonParams.get('table'); + isDb = PMA_commonParams.get('db'); + if (isDb && ! isTable) { + $('.tab .ic_b_props').first().trigger('click'); + } + } else if (tableOp === true) { + isTable = PMA_commonParams.get('table'); + isDb = PMA_commonParams.get('db'); + if (isDb && isTable) { + $('.tab .ic_b_props').first().trigger('click'); + } + } else { + $('#pma_navigation_settings_icon').trigger('click'); + } + } else if (e.keyCode === keyF) { + if (databaseOp === true) { + isTable = PMA_commonParams.get('table'); + isDb = PMA_commonParams.get('db'); + if (isDb && ! isTable) { + $('.tab .ic_b_search').first().trigger('click'); + } + } else if (tableOp === true) { + isTable = PMA_commonParams.get('table'); + isDb = PMA_commonParams.get('db'); + if (isDb && isTable) { + $('.tab .ic_b_search').first().trigger('click'); + } + } + } else if (e.keyCode === keyT) { + tableOp = true; + } else if (e.keyCode === keyE) { + $('.ic_b_export').first().trigger('click'); + } else if (e.keyCode === keyBackSpace) { + window.history.back(); + } else if (e.keyCode === keyH) { + $('.ic_b_home').first().trigger('click'); + } + }); +}); diff --git a/php/apps/phpmyadmin49/js/sql.js b/php/apps/phpmyadmin49/js/sql.js new file mode 100644 index 00000000..d5e80ed4 --- /dev/null +++ b/php/apps/phpmyadmin49/js/sql.js @@ -0,0 +1,1023 @@ +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * @fileoverview functions used wherever an sql query form is used + * + * @requires jQuery + * @requires js/functions.js + * + */ + +var $data_a; +var prevScrollX = 0; + +/** + * decode a string URL_encoded + * + * @param string str + * @return string the URL-decoded string + */ +function PMA_urldecode (str) { + if (typeof str !== 'undefined') { + return decodeURIComponent(str.replace(/\+/g, '%20')); + } +} + +/** + * endecode a string URL_decoded + * + * @param string str + * @return string the URL-encoded string + */ +function PMA_urlencode (str) { + if (typeof str !== 'undefined') { + return encodeURIComponent(str).replace(/\%20/g, '+'); + } +} + +/** + * Saves SQL query in local storage or cookie + * + * @param string SQL query + * @return void + */ +function PMA_autosaveSQL (query) { + if (query) { + if (isStorageSupported('localStorage')) { + window.localStorage.auto_saved_sql = query; + } else { + Cookies.set('auto_saved_sql', query); + } + } +} + +/** + * Saves SQL query with sort in local storage or cookie + * + * @param string SQL query + * @return void + */ +function PMA_autosaveSQLSort (query) { + if (query) { + if (isStorageSupported('localStorage')) { + window.localStorage.auto_saved_sql_sort = query; + } else { + Cookies.set('auto_saved_sql_sort', query); + } + } +} + +/** + * Get the field name for the current field. Required to construct the query + * for grid editing + * + * @param $table_results enclosing results table + * @param $this_field jQuery object that points to the current field's tr + */ +function getFieldName ($table_results, $this_field) { + var this_field_index = $this_field.index(); + // ltr or rtl direction does not impact how the DOM was generated + // check if the action column in the left exist + var left_action_exist = !$table_results.find('th:first').hasClass('draggable'); + // number of column span for checkbox and Actions + var left_action_skip = left_action_exist ? $table_results.find('th:first').attr('colspan') - 1 : 0; + + // If this column was sorted, the text of the a element contains something + // like 1 that is useful to indicate the order in case + // of a sort on multiple columns; however, we dont want this as part + // of the column name so we strip it ( .clone() to .end() ) + var field_name = $table_results + .find('thead') + .find('th:eq(' + (this_field_index - left_action_skip) + ') a') + .clone() // clone the element + .children() // select all the children + .remove() // remove all of them + .end() // go back to the selected element + .text(); // grab the text + // happens when just one row (headings contain no a) + if (field_name === '') { + var $heading = $table_results.find('thead').find('th:eq(' + (this_field_index - left_action_skip) + ')').children('span'); + // may contain column comment enclosed in a span - detach it temporarily to read the column name + var $tempColComment = $heading.children().detach(); + field_name = $heading.text(); + // re-attach the column comment + $heading.append($tempColComment); + } + + field_name = $.trim(field_name); + + return field_name; +} + +/** + * Unbind all event handlers before tearing down a page + */ +AJAX.registerTeardown('sql.js', function () { + $(document).off('click', 'a.delete_row.ajax'); + $(document).off('submit', '.bookmarkQueryForm'); + $('input#bkm_label').off('input'); + $(document).off('makegrid', '.sqlqueryresults'); + $(document).off('stickycolumns', '.sqlqueryresults'); + $('#togglequerybox').off('click'); + $(document).off('click', '#button_submit_query'); + $(document).off('change', '#id_bookmark'); + $('input[name=\'bookmark_variable\']').off('keypress'); + $(document).off('submit', '#sqlqueryform.ajax'); + $(document).off('click', 'input[name=navig].ajax'); + $(document).off('submit', 'form[name=\'displayOptionsForm\'].ajax'); + $(document).off('mouseenter', 'th.column_heading.pointer'); + $(document).off('mouseleave', 'th.column_heading.pointer'); + $(document).off('click', 'th.column_heading.marker'); + $(window).off('scroll'); + $(document).off('keyup', '.filter_rows'); + $(document).off('click', '#printView'); + if (codemirror_editor) { + codemirror_editor.off('change'); + } else { + $('#sqlquery').off('input propertychange'); + } + $('body').off('click', '.navigation .showAllRows'); + $('body').off('click', 'a.browse_foreign'); + $('body').off('click', '#simulate_dml'); + $('body').off('keyup', '#sqlqueryform'); + $('body').off('click', 'form[name="resultsForm"].ajax button[name="submit_mult"], form[name="resultsForm"].ajax input[name="submit_mult"]'); +}); + +/** + * @description

        Ajax scripts for sql and browse pages

        + * + * Actions ajaxified here: + *
          + *
        • Retrieve results of an SQL query
        • + *
        • Paginate the results table
        • + *
        • Sort the results table
        • + *
        • Change table according to display options
        • + *
        • Grid editing of data
        • + *
        • Saving a bookmark
        • + *
        + * + * @name document.ready + * @memberOf jQuery + */ +AJAX.registerOnload('sql.js', function () { + $(function () { + if (codemirror_editor) { + codemirror_editor.on('change', function () { + PMA_autosaveSQL(codemirror_editor.getValue()); + }); + } else { + $('#sqlquery').on('input propertychange', function () { + PMA_autosaveSQL($('#sqlquery').val()); + }); + // Save sql query with sort + if ($('#RememberSorting') !== undefined && $('#RememberSorting').is(':checked')) { + $('select[name="sql_query"]').on('change', function () { + PMA_autosaveSQLSort($('select[name="sql_query"]').val()); + }); + } else { + if (isStorageSupported('localStorage') && window.localStorage.auto_saved_sql_sort !== undefined) { + window.localStorage.removeItem('auto_saved_sql_sort'); + } else { + Cookies.set('auto_saved_sql_sort', ''); + } + } + // If sql query with sort for current table is stored, change sort by key select value + var sortStoredQuery = (isStorageSupported('localStorage') && typeof window.localStorage.auto_saved_sql_sort !== 'undefined') ? window.localStorage.auto_saved_sql_sort : Cookies.get('auto_saved_sql_sort'); + if (typeof sortStoredQuery !== 'undefined' && sortStoredQuery !== $('select[name="sql_query"]').val() && $('select[name="sql_query"] option[value="' + sortStoredQuery + '"]').length !== 0) { + $('select[name="sql_query"]').val(sortStoredQuery).change(); + } + } + }); + + // Delete row from SQL results + $(document).on('click', 'a.delete_row.ajax', function (e) { + e.preventDefault(); + var question = PMA_sprintf(PMA_messages.strDoYouReally, escapeHtml($(this).closest('td').find('div').text())); + var $link = $(this); + $link.PMA_confirm(question, $link.attr('href'), function (url) { + $msgbox = PMA_ajaxShowMessage(); + var argsep = PMA_commonParams.get('arg_separator'); + var params = 'ajax_request=1' + argsep + 'is_js_confirmed=1'; + var postData = $link.getPostData(); + if (postData) { + params += argsep + postData; + } + $.post(url, params, function (data) { + if (data.success) { + PMA_ajaxShowMessage(data.message); + $link.closest('tr').remove(); + } else { + PMA_ajaxShowMessage(data.error, false); + } + }); + }); + }); + + // Ajaxification for 'Bookmark this SQL query' + $(document).on('submit', '.bookmarkQueryForm', function (e) { + e.preventDefault(); + PMA_ajaxShowMessage(); + var argsep = PMA_commonParams.get('arg_separator'); + $.post($(this).attr('action'), 'ajax_request=1' + argsep + $(this).serialize(), function (data) { + if (data.success) { + PMA_ajaxShowMessage(data.message); + } else { + PMA_ajaxShowMessage(data.error, false); + } + }); + }); + + /* Hides the bookmarkoptions checkboxes when the bookmark label is empty */ + $('input#bkm_label').on('input', function () { + $('input#id_bkm_all_users, input#id_bkm_replace') + .parent() + .toggle($(this).val().length > 0); + }).trigger('input'); + + /** + * Attach Event Handler for 'Copy to clipbpard + */ + $(document).on('click', '#copyToClipBoard', function (event) { + event.preventDefault(); + + var textArea = document.createElement('textarea'); + + // + // *** This styling is an extra step which is likely not required. *** + // + // Why is it here? To ensure: + // 1. the element is able to have focus and selection. + // 2. if element was to flash render it has minimal visual impact. + // 3. less flakyness with selection and copying which **might** occur if + // the textarea element is not visible. + // + // The likelihood is the element won't even render, not even a flash, + // so some of these are just precautions. However in IE the element + // is visible whilst the popup box asking the user for permission for + // the web page to copy to the clipboard. + // + + // Place in top-left corner of screen regardless of scroll position. + textArea.style.position = 'fixed'; + textArea.style.top = 0; + textArea.style.left = 0; + + // Ensure it has a small width and height. Setting to 1px / 1em + // doesn't work as this gives a negative w/h on some browsers. + textArea.style.width = '2em'; + textArea.style.height = '2em'; + + // We don't need padding, reducing the size if it does flash render. + textArea.style.padding = 0; + + // Clean up any borders. + textArea.style.border = 'none'; + textArea.style.outline = 'none'; + textArea.style.boxShadow = 'none'; + + // Avoid flash of white box if rendered for any reason. + textArea.style.background = 'transparent'; + + textArea.value = ''; + + $('#serverinfo a').each(function () { + textArea.value += $(this).text().split(':')[1].trim() + '/'; + }); + textArea.value += '\t\t' + window.location.href; + textArea.value += '\n'; + $('.success').each(function () { + textArea.value += $(this).text() + '\n\n'; + }); + + $('.sql pre').each(function () { + textArea.value += $(this).text() + '\n\n'; + }); + + $('.table_results .column_heading a').each(function () { + // Don't copy ordering number text within tag + textArea.value += $(this).clone().find('small').remove().end().text() + '\t'; + }); + + textArea.value += '\n'; + $('.table_results tbody tr').each(function () { + $(this).find('.data span').each(function () { + textArea.value += $(this).text() + '\t'; + }); + textArea.value += '\n'; + }); + + document.body.appendChild(textArea); + + textArea.select(); + + try { + document.execCommand('copy'); + } catch (err) { + alert('Sorry! Unable to copy'); + } + + document.body.removeChild(textArea); + }); // end of Copy to Clipboard action + + /** + * Attach Event Handler for 'Print' link + */ + $(document).on('click', '#printView', function (event) { + event.preventDefault(); + + // Take to preview mode + printPreview(); + }); // end of 'Print' action + + /** + * Attach the {@link makegrid} function to a custom event, which will be + * triggered manually everytime the table of results is reloaded + * @memberOf jQuery + */ + $(document).on('makegrid', '.sqlqueryresults', function () { + $('.table_results').each(function () { + PMA_makegrid(this); + }); + }); + + /* + * Attach a custom event for sticky column headings which will be + * triggered manually everytime the table of results is reloaded + * @memberOf jQuery + */ + $(document).on('stickycolumns', '.sqlqueryresults', function () { + $('.sticky_columns').remove(); + $('.table_results').each(function () { + var $table_results = $(this); + // add sticky columns div + var $stick_columns = initStickyColumns($table_results); + rearrangeStickyColumns($stick_columns, $table_results); + // adjust sticky columns on scroll + $(window).on('scroll', function () { + handleStickyColumns($stick_columns, $table_results); + }); + }); + }); + + /** + * Append the "Show/Hide query box" message to the query input form + * + * @memberOf jQuery + * @name appendToggleSpan + */ + // do not add this link more than once + if (! $('#sqlqueryform').find('a').is('#togglequerybox')) { + $('') + .html(PMA_messages.strHideQueryBox) + .appendTo('#sqlqueryform') + // initially hidden because at this point, nothing else + // appears under the link + .hide(); + + // Attach the toggling of the query box visibility to a click + $('#togglequerybox').bind('click', function () { + var $link = $(this); + $link.siblings().slideToggle('fast'); + if ($link.text() === PMA_messages.strHideQueryBox) { + $link.text(PMA_messages.strShowQueryBox); + // cheap trick to add a spacer between the menu tabs + // and "Show query box"; feel free to improve! + $('#togglequerybox_spacer').remove(); + $link.before('
        '); + } else { + $link.text(PMA_messages.strHideQueryBox); + } + // avoid default click action + return false; + }); + } + + + /** + * Event handler for sqlqueryform.ajax button_submit_query + * + * @memberOf jQuery + */ + $(document).on('click', '#button_submit_query', function (event) { + $('.success,.error').hide(); + // hide already existing error or success message + var $form = $(this).closest('form'); + // the Go button related to query submission was clicked, + // instead of the one related to Bookmarks, so empty the + // id_bookmark selector to avoid misinterpretation in + // import.php about what needs to be done + $form.find('select[name=id_bookmark]').val(''); + // let normal event propagation happen + }); + + /** + * Event handler to show appropiate number of variable boxes + * based on the bookmarked query + */ + $(document).on('change', '#id_bookmark', function (event) { + var varCount = $(this).find('option:selected').data('varcount'); + if (typeof varCount === 'undefined') { + varCount = 0; + } + + var $varDiv = $('#bookmark_variables'); + $varDiv.empty(); + for (var i = 1; i <= varCount; i++) { + $varDiv.append($('')); + $varDiv.append($('')); + } + + if (varCount === 0) { + $varDiv.parent('.formelement').hide(); + } else { + $varDiv.parent('.formelement').show(); + } + }); + + /** + * Event handler for hitting enter on sqlqueryform bookmark_variable + * (the Variable textfield in Bookmarked SQL query section) + * + * @memberOf jQuery + */ + $('input[name=bookmark_variable]').on('keypress', function (event) { + // force the 'Enter Key' to implicitly click the #button_submit_bookmark + var keycode = (event.keyCode ? event.keyCode : (event.which ? event.which : event.charCode)); + if (keycode === 13) { // keycode for enter key + // When you press enter in the sqlqueryform, which + // has 2 submit buttons, the default is to run the + // #button_submit_query, because of the tabindex + // attribute. + // This submits #button_submit_bookmark instead, + // because when you are in the Bookmarked SQL query + // section and hit enter, you expect it to do the + // same action as the Go button in that section. + $('#button_submit_bookmark').click(); + return false; + } else { + return true; + } + }); + + /** + * Ajax Event handler for 'SQL Query Submit' + * + * @see PMA_ajaxShowMessage() + * @memberOf jQuery + * @name sqlqueryform_submit + */ + $(document).on('submit', '#sqlqueryform.ajax', function (event) { + event.preventDefault(); + + var $form = $(this); + if (codemirror_editor) { + $form[0].elements.sql_query.value = codemirror_editor.getValue(); + } + if (! checkSqlQuery($form[0])) { + return false; + } + + // remove any div containing a previous error message + $('div.error').remove(); + + var $msgbox = PMA_ajaxShowMessage(); + var $sqlqueryresultsouter = $('#sqlqueryresultsouter'); + + PMA_prepareForAjaxRequest($form); + + var argsep = PMA_commonParams.get('arg_separator'); + $.post($form.attr('action'), $form.serialize() + argsep + 'ajax_page_request=true', function (data) { + if (typeof data !== 'undefined' && data.success === true) { + // success happens if the query returns rows or not + + // show a message that stays on screen + if (typeof data.action_bookmark !== 'undefined') { + // view only + if ('1' === data.action_bookmark) { + $('#sqlquery').text(data.sql_query); + // send to codemirror if possible + setQuery(data.sql_query); + } + // delete + if ('2' === data.action_bookmark) { + $('#id_bookmark option[value=\'' + data.id_bookmark + '\']').remove(); + // if there are no bookmarked queries now (only the empty option), + // remove the bookmark section + if ($('#id_bookmark option').length === 1) { + $('#fieldsetBookmarkOptions').hide(); + $('#fieldsetBookmarkOptionsFooter').hide(); + } + } + } + $sqlqueryresultsouter + .show() + .html(data.message); + PMA_highlightSQL($sqlqueryresultsouter); + + if (data._menu) { + if (history && history.pushState) { + history.replaceState({ + menu : data._menu + }, + null + ); + AJAX.handleMenu.replace(data._menu); + } else { + PMA_MicroHistory.menus.replace(data._menu); + PMA_MicroHistory.menus.add(data._menuHash, data._menu); + } + } else if (data._menuHash) { + if (! (history && history.pushState)) { + PMA_MicroHistory.menus.replace(PMA_MicroHistory.menus.get(data._menuHash)); + } + } + + if (data._params) { + PMA_commonParams.setAll(data._params); + } + + if (typeof data.ajax_reload !== 'undefined') { + if (data.ajax_reload.reload) { + if (data.ajax_reload.table_name) { + PMA_commonParams.set('table', data.ajax_reload.table_name); + PMA_commonActions.refreshMain(); + } else { + PMA_reloadNavigation(); + } + } + } else if (typeof data.reload !== 'undefined') { + // this happens if a USE or DROP command was typed + PMA_commonActions.setDb(data.db); + var url; + if (data.db) { + if (data.table) { + url = 'table_sql.php'; + } else { + url = 'db_sql.php'; + } + } else { + url = 'server_sql.php'; + } + PMA_commonActions.refreshMain(url, function () { + $('#sqlqueryresultsouter') + .show() + .html(data.message); + PMA_highlightSQL($('#sqlqueryresultsouter')); + }); + } + + $('.sqlqueryresults').trigger('makegrid').trigger('stickycolumns'); + $('#togglequerybox').show(); + PMA_init_slider(); + + if (typeof data.action_bookmark === 'undefined') { + if ($('#sqlqueryform input[name="retain_query_box"]').is(':checked') !== true) { + if ($('#togglequerybox').siblings(':visible').length > 0) { + $('#togglequerybox').trigger('click'); + } + } + } + } else if (typeof data !== 'undefined' && data.success === false) { + // show an error message that stays on screen + $sqlqueryresultsouter + .show() + .html(data.error); + } + PMA_ajaxRemoveMessage($msgbox); + }); // end $.post() + }); // end SQL Query submit + + /** + * Ajax Event handler for the display options + * @memberOf jQuery + * @name displayOptionsForm_submit + */ + $(document).on('submit', 'form[name=\'displayOptionsForm\'].ajax', function (event) { + event.preventDefault(); + + $form = $(this); + + var $msgbox = PMA_ajaxShowMessage(); + var argsep = PMA_commonParams.get('arg_separator'); + $.post($form.attr('action'), $form.serialize() + argsep + 'ajax_request=true', function (data) { + PMA_ajaxRemoveMessage($msgbox); + var $sqlqueryresults = $form.parents('.sqlqueryresults'); + $sqlqueryresults + .html(data.message) + .trigger('makegrid') + .trigger('stickycolumns'); + PMA_init_slider(); + PMA_highlightSQL($sqlqueryresults); + }); // end $.post() + }); // end displayOptionsForm handler + + // Filter row handling. --STARTS-- + $(document).on('keyup', '.filter_rows', function () { + var unique_id = $(this).data('for'); + var $target_table = $('.table_results[data-uniqueId=\'' + unique_id + '\']'); + var $header_cells = $target_table.find('th[data-column]'); + var target_columns = Array(); + // To handle colspan=4, in case of edit,copy etc options. + var dummy_th = ($('.edit_row_anchor').length !== 0 ? + '' + : ''); + // Selecting columns that will be considered for filtering and searching. + $header_cells.each(function () { + target_columns.push($.trim($(this).text())); + }); + + var phrase = $(this).val(); + // Set same value to both Filter rows fields. + $('.filter_rows[data-for=\'' + unique_id + '\']').not(this).val(phrase); + // Handle colspan. + $target_table.find('thead > tr').prepend(dummy_th); + $.uiTableFilter($target_table, phrase, target_columns); + $target_table.find('th.dummy_th').remove(); + }); + // Filter row handling. --ENDS-- + + // Prompt to confirm on Show All + $('body').on('click', '.navigation .showAllRows', function (e) { + e.preventDefault(); + var $form = $(this).parents('form'); + + if (! $(this).is(':checked')) { // already showing all rows + submitShowAllForm(); + } else { + $form.PMA_confirm(PMA_messages.strShowAllRowsWarning, $form.attr('action'), function (url) { + submitShowAllForm(); + }); + } + + function submitShowAllForm () { + var argsep = PMA_commonParams.get('arg_separator'); + var submitData = $form.serialize() + argsep + 'ajax_request=true' + argsep + 'ajax_page_request=true'; + PMA_ajaxShowMessage(); + AJAX.source = $form; + $.post($form.attr('action'), submitData, AJAX.responseHandler); + } + }); + + $('body').on('keyup', '#sqlqueryform', function () { + PMA_handleSimulateQueryButton(); + }); + + /** + * Ajax event handler for 'Simulate DML'. + */ + $('body').on('click', '#simulate_dml', function () { + var $form = $('#sqlqueryform'); + var query = ''; + var delimiter = $('#id_sql_delimiter').val(); + var db_name = $form.find('input[name="db"]').val(); + + if (codemirror_editor) { + query = codemirror_editor.getValue(); + } else { + query = $('#sqlquery').val(); + } + + if (query.length === 0) { + alert(PMA_messages.strFormEmpty); + $('#sqlquery').focus(); + return false; + } + + var $msgbox = PMA_ajaxShowMessage(); + $.ajax({ + type: 'POST', + url: $form.attr('action'), + data: { + server: PMA_commonParams.get('server'), + db: db_name, + ajax_request: '1', + simulate_dml: '1', + sql_query: query, + sql_delimiter: delimiter + }, + success: function (response) { + PMA_ajaxRemoveMessage($msgbox); + if (response.success) { + var dialog_content = '
        '; + if (response.sql_data) { + var len = response.sql_data.length; + for (var i = 0; i < len; i++) { + dialog_content += '' + PMA_messages.strSQLQuery + + '' + response.sql_data[i].sql_query + + PMA_messages.strMatchedRows + + ' ' + response.sql_data[i].matched_rows + '
        '; + if (i < len - 1) { + dialog_content += '
        '; + } + } + } else { + dialog_content += response.message; + } + dialog_content += '
        '; + var $dialog_content = $(dialog_content); + var button_options = {}; + button_options[PMA_messages.strClose] = function () { + $(this).dialog('close'); + }; + var $response_dialog = $('
        ').append($dialog_content).dialog({ + minWidth: 540, + maxHeight: 400, + modal: true, + buttons: button_options, + title: PMA_messages.strSimulateDML, + open: function () { + PMA_highlightSQL($(this)); + }, + close: function () { + $(this).remove(); + } + }); + } else { + PMA_ajaxShowMessage(response.error); + } + }, + error: function (response) { + PMA_ajaxShowMessage(PMA_messages.strErrorProcessingRequest); + } + }); + }); + + /** + * Handles multi submits of results browsing page such as edit, delete and export + */ + $('body').on('click', 'form[name="resultsForm"].ajax button[name="submit_mult"], form[name="resultsForm"].ajax input[name="submit_mult"]', function (e) { + e.preventDefault(); + var $button = $(this); + var $form = $button.closest('form'); + var argsep = PMA_commonParams.get('arg_separator'); + var submitData = $form.serialize() + argsep + 'ajax_request=true' + argsep + 'ajax_page_request=true' + argsep + 'submit_mult=' + $button.val(); + PMA_ajaxShowMessage(); + AJAX.source = $form; + $.post($form.attr('action'), submitData, AJAX.responseHandler); + }); +}); // end $() + +/** + * Starting from some th, change the class of all td under it. + * If isAddClass is specified, it will be used to determine whether to add or remove the class. + */ +function PMA_changeClassForColumn ($this_th, newclass, isAddClass) { + // index 0 is the th containing the big T + var th_index = $this_th.index(); + var has_big_t = $this_th.closest('tr').children(':first').hasClass('column_action'); + // .eq() is zero-based + if (has_big_t) { + th_index--; + } + var $table = $this_th.parents('.table_results'); + if (! $table.length) { + $table = $this_th.parents('table').siblings('.table_results'); + } + var $tds = $table.find('tbody tr').find('td.data:eq(' + th_index + ')'); + if (isAddClass === undefined) { + $tds.toggleClass(newclass); + } else { + $tds.toggleClass(newclass, isAddClass); + } +} + +/** + * Handles browse foreign values modal dialog + * + * @param object $this_a reference to the browse foreign value link + */ +function browseForeignDialog ($this_a) { + var formId = '#browse_foreign_form'; + var showAllId = '#foreign_showAll'; + var tableId = '#browse_foreign_table'; + var filterId = '#input_foreign_filter'; + var $dialog = null; + var argSep = PMA_commonParams.get('arg_separator'); + var params = $this_a.getPostData(); + params += argSep + 'ajax_request=true'; + $.post($this_a.attr('href'), params, function (data) { + // Creates browse foreign value dialog + $dialog = $('
        ').append(data.message).dialog({ + title: PMA_messages.strBrowseForeignValues, + width: Math.min($(window).width() - 100, 700), + maxHeight: $(window).height() - 100, + dialogClass: 'browse_foreign_modal', + close: function (ev, ui) { + // remove event handlers attached to elements related to dialog + $(tableId).off('click', 'td a.foreign_value'); + $(formId).off('click', showAllId); + $(formId).off('submit'); + // remove dialog itself + $(this).remove(); + }, + modal: true + }); + }).done(function () { + var showAll = false; + $(tableId).on('click', 'td a.foreign_value', function (e) { + e.preventDefault(); + var $input = $this_a.prev('input[type=text]'); + // Check if input exists or get CEdit edit_box + if ($input.length === 0) { + $input = $this_a.closest('.edit_area').prev('.edit_box'); + } + // Set selected value as input value + $input.val($(this).data('key')); + $dialog.dialog('close'); + }); + $(formId).on('click', showAllId, function () { + showAll = true; + }); + $(formId).on('submit', function (e) { + e.preventDefault(); + // if filter value is not equal to old value + // then reset page number to 1 + if ($(filterId).val() !== $(filterId).data('old')) { + $(formId).find('select[name=pos]').val('0'); + } + var postParams = $(this).serializeArray(); + // if showAll button was clicked to submit form then + // add showAll button parameter to form + if (showAll) { + postParams.push({ + name: $(showAllId).attr('name'), + value: $(showAllId).val() + }); + } + // updates values in dialog + $.post($(this).attr('action') + '?ajax_request=1', postParams, function (data) { + var $obj = $('
        ').html(data.message); + $(formId).html($obj.find(formId).html()); + $(tableId).html($obj.find(tableId).html()); + }); + showAll = false; + }); + }); +} + +AJAX.registerOnload('sql.js', function () { + $('body').on('click', 'a.browse_foreign', function (e) { + e.preventDefault(); + browseForeignDialog($(this)); + }); + + /** + * vertical column highlighting in horizontal mode when hovering over the column header + */ + $(document).on('mouseenter', 'th.column_heading.pointer', function (e) { + PMA_changeClassForColumn($(this), 'hover', true); + }); + $(document).on('mouseleave', 'th.column_heading.pointer', function (e) { + PMA_changeClassForColumn($(this), 'hover', false); + }); + + /** + * vertical column marking in horizontal mode when clicking the column header + */ + $(document).on('click', 'th.column_heading.marker', function () { + PMA_changeClassForColumn($(this), 'marked'); + }); + + /** + * create resizable table + */ + $('.sqlqueryresults').trigger('makegrid').trigger('stickycolumns'); +}); + +/* + * Profiling Chart + */ +function makeProfilingChart () { + if ($('#profilingchart').length === 0 || + $('#profilingchart').html().length !== 0 || + !$.jqplot || !$.jqplot.Highlighter || !$.jqplot.PieRenderer + ) { + return; + } + + var data = []; + $.each(JSON.parse($('#profilingChartData').html()), function (key, value) { + data.push([key, parseFloat(value)]); + }); + + // Remove chart and data divs contents + $('#profilingchart').html('').show(); + $('#profilingChartData').html(''); + + PMA_createProfilingChart('profilingchart', data); +} + +/* + * initialize profiling data tables + */ +function initProfilingTables () { + if (!$.tablesorter) { + return; + } + + $('#profiletable').tablesorter({ + widgets: ['zebra'], + sortList: [[0, 0]], + textExtraction: function (node) { + if (node.children.length > 0) { + return node.children[0].innerHTML; + } else { + return node.innerHTML; + } + } + }); + + $('#profilesummarytable').tablesorter({ + widgets: ['zebra'], + sortList: [[1, 1]], + textExtraction: function (node) { + if (node.children.length > 0) { + return node.children[0].innerHTML; + } else { + return node.innerHTML; + } + } + }); +} + +/* + * Set position, left, top, width of sticky_columns div + */ +function setStickyColumnsPosition ($sticky_columns, $table_results, position, top, left, margin_left) { + $sticky_columns + .css('position', position) + .css('top', top) + .css('left', left ? left : 'auto') + .css('margin-left', margin_left ? margin_left : '0px') + .css('width', $table_results.width()); +} + +/* + * Initialize sticky columns + */ +function initStickyColumns ($table_results) { + return $('
        ') + .insertBefore($table_results) + .css('position', 'fixed') + .css('z-index', '98') + .css('width', $table_results.width()) + .css('margin-left', $('#page_content').css('margin-left')) + .css('top', $('#floating_menubar').height()) + .css('display', 'none'); +} + +/* + * Arrange/Rearrange columns in sticky header + */ +function rearrangeStickyColumns ($sticky_columns, $table_results) { + var $originalHeader = $table_results.find('thead'); + var $originalColumns = $originalHeader.find('tr:first').children(); + var $clonedHeader = $originalHeader.clone(); + // clone width per cell + $clonedHeader.find('tr:first').children().width(function (i,val) { + var width = $originalColumns.eq(i).width(); + var is_firefox = navigator.userAgent.indexOf('Firefox') > -1; + if (! is_firefox) { + width += 1; + } + return width; + }); + $sticky_columns.empty().append($clonedHeader); +} + +/* + * Adjust sticky columns on horizontal/vertical scroll for all tables + */ +function handleAllStickyColumns () { + $('.sticky_columns').each(function () { + handleStickyColumns($(this), $(this).next('.table_results')); + }); +} + +/* + * Adjust sticky columns on horizontal/vertical scroll + */ +function handleStickyColumns ($sticky_columns, $table_results) { + var currentScrollX = $(window).scrollLeft(); + var windowOffset = $(window).scrollTop(); + var tableStartOffset = $table_results.offset().top; + var tableEndOffset = tableStartOffset + $table_results.height(); + if (windowOffset >= tableStartOffset && windowOffset <= tableEndOffset) { + // for horizontal scrolling + if (prevScrollX !== currentScrollX) { + prevScrollX = currentScrollX; + setStickyColumnsPosition($sticky_columns, $table_results, 'absolute', $('#floating_menubar').height() + windowOffset - tableStartOffset); + // for vertical scrolling + } else { + setStickyColumnsPosition($sticky_columns, $table_results, 'fixed', $('#floating_menubar').height(), $('#pma_navigation').width() - currentScrollX, $('#page_content').css('margin-left')); + } + $sticky_columns.show(); + } else { + $sticky_columns.hide(); + } +} + +AJAX.registerOnload('sql.js', function () { + makeProfilingChart(); + initProfilingTables(); +}); diff --git a/php/apps/phpmyadmin49/js/tbl_change.js b/php/apps/phpmyadmin49/js/tbl_change.js new file mode 100644 index 00000000..18742fd1 --- /dev/null +++ b/php/apps/phpmyadmin49/js/tbl_change.js @@ -0,0 +1,720 @@ +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * @fileoverview function used in table data manipulation pages + * + * @requires jQuery + * @requires jQueryUI + * @requires js/functions.js + * + */ + +/** + * Modify form controls when the "NULL" checkbox is checked + * + * @param theType string the MySQL field type + * @param urlField string the urlencoded field name - OBSOLETE + * @param md5Field string the md5 hashed field name + * @param multi_edit string the multi_edit row sequence number + * + * @return boolean always true + */ +function nullify (theType, urlField, md5Field, multi_edit) { + var rowForm = document.forms.insertForm; + + if (typeof(rowForm.elements['funcs' + multi_edit + '[' + md5Field + ']']) !== 'undefined') { + rowForm.elements['funcs' + multi_edit + '[' + md5Field + ']'].selectedIndex = -1; + } + + // "ENUM" field with more than 20 characters + if (Number(theType) === 1) { + rowForm.elements['fields' + multi_edit + '[' + md5Field + ']'][1].selectedIndex = -1; + // Other "ENUM" field + } else if (Number(theType) === 2) { + var elts = rowForm.elements['fields' + multi_edit + '[' + md5Field + ']']; + // when there is just one option in ENUM: + if (elts.checked) { + elts.checked = false; + } else { + var elts_cnt = elts.length; + for (var i = 0; i < elts_cnt; i++) { + elts[i].checked = false; + } // end for + } // end if + // "SET" field + } else if (Number(theType) === 3) { + rowForm.elements['fields' + multi_edit + '[' + md5Field + '][]'].selectedIndex = -1; + // Foreign key field (drop-down) + } else if (Number(theType) === 4) { + rowForm.elements['fields' + multi_edit + '[' + md5Field + ']'].selectedIndex = -1; + // foreign key field (with browsing icon for foreign values) + } else if (Number(theType) === 6) { + rowForm.elements['fields' + multi_edit + '[' + md5Field + ']'].value = ''; + // Other field types + } else /* if (theType === 5)*/ { + rowForm.elements['fields' + multi_edit + '[' + md5Field + ']'].value = ''; + } // end if... else if... else + + return true; +} // end of the 'nullify()' function + + +/** + * javascript DateTime format validation. + * its used to prevent adding default (0000-00-00 00:00:00) to database when user enter wrong values + * Start of validation part + */ +// function checks the number of days in febuary +function daysInFebruary (year) { + return (((year % 4 === 0) && (((year % 100 !== 0)) || (year % 400 === 0))) ? 29 : 28); +} +// function to convert single digit to double digit +function fractionReplace (num) { + num = parseInt(num, 10); + return num >= 1 && num <= 9 ? '0' + num : '00'; +} + +/* function to check the validity of date +* The following patterns are accepted in this validation (accepted in mysql as well) +* 1) 2001-12-23 +* 2) 2001-1-2 +* 3) 02-12-23 +* 4) And instead of using '-' the following punctuations can be used (+,.,*,^,@,/) All these are accepted by mysql as well. Therefore no issues +*/ +function isDate (val, tmstmp) { + val = val.replace(/[.|*|^|+|//|@]/g, '-'); + var arrayVal = val.split('-'); + for (var a = 0; a < arrayVal.length; a++) { + if (arrayVal[a].length === 1) { + arrayVal[a] = fractionReplace(arrayVal[a]); + } + } + val = arrayVal.join('-'); + var pos = 2; + var dtexp = new RegExp(/^([0-9]{4})-(((01|03|05|07|08|10|12)-((0[0-9])|([1-2][0-9])|(3[0-1])))|((02|04|06|09|11)-((0[0-9])|([1-2][0-9])|30))|((00)-(00)))$/); + if (val.length === 8) { + pos = 0; + } + if (dtexp.test(val)) { + var month = parseInt(val.substring(pos + 3, pos + 5), 10); + var day = parseInt(val.substring(pos + 6, pos + 8), 10); + var year = parseInt(val.substring(0, pos + 2), 10); + if (month === 2 && day > daysInFebruary(year)) { + return false; + } + if (val.substring(0, pos + 2).length === 2) { + year = parseInt('20' + val.substring(0, pos + 2), 10); + } + if (tmstmp === true) { + if (year < 1978) { + return false; + } + if (year > 2038 || (year > 2037 && day > 19 && month >= 1) || (year > 2037 && month > 1)) { + return false; + } + } + } else { + return false; + } + return true; +} + +/* function to check the validity of time +* The following patterns are accepted in this validation (accepted in mysql as well) +* 1) 2:3:4 +* 2) 2:23:43 +* 3) 2:23:43.123456 +*/ +function isTime (val) { + var arrayVal = val.split(':'); + for (var a = 0, l = arrayVal.length; a < l; a++) { + if (arrayVal[a].length === 1) { + arrayVal[a] = fractionReplace(arrayVal[a]); + } + } + val = arrayVal.join(':'); + var tmexp = new RegExp(/^(-)?(([0-7]?[0-9][0-9])|(8[0-2][0-9])|(83[0-8])):((0[0-9])|([1-5][0-9])):((0[0-9])|([1-5][0-9]))(\.[0-9]{1,6}){0,1}$/); + return tmexp.test(val); +} + +/** + * To check whether insert section is ignored or not + */ +function checkForCheckbox (multi_edit) { + if ($('#insert_ignore_' + multi_edit).length) { + return $('#insert_ignore_' + multi_edit).is(':unchecked'); + } + return true; +} + +function verificationsAfterFieldChange (urlField, multi_edit, theType) { + var evt = window.event || arguments.callee.caller.arguments[0]; + var target = evt.target || evt.srcElement; + var $this_input = $(':input[name^=\'fields[multi_edit][' + multi_edit + '][' + + urlField + ']\']'); + // the function drop-down that corresponds to this input field + var $this_function = $('select[name=\'funcs[multi_edit][' + multi_edit + '][' + + urlField + ']\']'); + var function_selected = false; + if (typeof $this_function.val() !== 'undefined' && + $this_function.val() !== null && + $this_function.val().length > 0 + ) { + function_selected = true; + } + + // To generate the textbox that can take the salt + var new_salt_box = '
        '; + + // If encrypting or decrypting functions that take salt as input is selected append the new textbox for salt + if (target.value === 'AES_ENCRYPT' || + target.value === 'AES_DECRYPT' || + target.value === 'DES_ENCRYPT' || + target.value === 'DES_DECRYPT' || + target.value === 'ENCRYPT') { + if (!($('#salt_' + target.id).length)) { + $this_input.after(new_salt_box); + } + } else { + // Remove the textbox for salt + $('#salt_' + target.id).prev('br').remove(); + $('#salt_' + target.id).remove(); + } + + if (target.value === 'AES_DECRYPT' + || target.value === 'AES_ENCRYPT' + || target.value === 'MD5') { + $('#' + target.id).rules('add', { + validationFunctionForFuns: { + param: $this_input, + depends: function () { + return checkForCheckbox(multi_edit); + } + } + }); + } + + // Unchecks the corresponding "NULL" control + $('input[name=\'fields_null[multi_edit][' + multi_edit + '][' + urlField + ']\']').prop('checked', false); + + // Unchecks the Ignore checkbox for the current row + $('input[name=\'insert_ignore_' + multi_edit + '\']').prop('checked', false); + + var charExceptionHandling; + if (theType.substring(0,4) === 'char') { + charExceptionHandling = theType.substring(5,6); + } else if (theType.substring(0,7) === 'varchar') { + charExceptionHandling = theType.substring(8,9); + } + if (function_selected) { + $this_input.removeAttr('min'); + $this_input.removeAttr('max'); + // @todo: put back attributes if corresponding function is deselected + } + + if ($this_input.data('rulesadded') === null && ! function_selected) { + // call validate before adding rules + $($this_input[0].form).validate(); + // validate for date time + if (theType === 'datetime' || theType === 'time' || theType === 'date' || theType === 'timestamp') { + $this_input.rules('add', { + validationFunctionForDateTime: { + param: theType, + depends: function () { + return checkForCheckbox(multi_edit); + } + } + }); + } + // validation for integer type + if ($this_input.data('type') === 'INT') { + var mini = parseInt($this_input.attr('min')); + var maxi = parseInt($this_input.attr('max')); + $this_input.rules('add', { + number: { + param : true, + depends: function () { + return checkForCheckbox(multi_edit); + } + }, + min: { + param: mini, + depends: function () { + if (isNaN($this_input.val())) { + return false; + } else { + return checkForCheckbox(multi_edit); + } + } + }, + max: { + param: maxi, + depends: function () { + if (isNaN($this_input.val())) { + return false; + } else { + return checkForCheckbox(multi_edit); + } + } + } + }); + // validation for CHAR types + } else if ($this_input.data('type') === 'CHAR') { + var maxlen = $this_input.data('maxlength'); + if (typeof maxlen !== 'undefined') { + if (maxlen <= 4) { + maxlen = charExceptionHandling; + } + $this_input.rules('add', { + maxlength: { + param: maxlen, + depends: function () { + return checkForCheckbox(multi_edit); + } + } + }); + } + // validate binary & blob types + } else if ($this_input.data('type') === 'HEX') { + $this_input.rules('add', { + validationFunctionForHex: { + param: true, + depends: function () { + return checkForCheckbox(multi_edit); + } + } + }); + } + $this_input.data('rulesadded', true); + } else if ($this_input.data('rulesadded') === true && function_selected) { + // remove any rules added + $this_input.rules('remove'); + // remove any error messages + $this_input + .removeClass('error') + .removeAttr('aria-invalid') + .siblings('.error') + .remove(); + $this_input.data('rulesadded', null); + } +} +/* End of fields validation*/ + +/** + * Unbind all event handlers before tearing down a page + */ +AJAX.registerTeardown('tbl_change.js', function () { + $(document).off('click', 'span.open_gis_editor'); + $(document).off('click', 'input[name^=\'insert_ignore_\']'); + $(document).off('click', 'input[name=\'gis_data[save]\']'); + $(document).off('click', 'input.checkbox_null'); + $('select[name="submit_type"]').off('change'); + $(document).off('change', '#insert_rows'); +}); + +/** + * Ajax handlers for Change Table page + * + * Actions Ajaxified here: + * Submit Data to be inserted into the table. + * Restart insertion with 'N' rows. + */ +AJAX.registerOnload('tbl_change.js', function () { + if ($('#insertForm').length) { + // validate the comment form when it is submitted + $('#insertForm').validate(); + jQuery.validator.addMethod('validationFunctionForHex', function (value, element) { + return value.match(/^[a-f0-9]*$/i) !== null; + }); + + jQuery.validator.addMethod('validationFunctionForFuns', function (value, element, options) { + if (value.substring(0, 3) === 'AES' && options.data('type') !== 'HEX') { + return false; + } + + return !(value.substring(0, 3) === 'MD5' && + typeof options.data('maxlength') !== 'undefined' && + options.data('maxlength') < 32); + }); + + jQuery.validator.addMethod('validationFunctionForDateTime', function (value, element, options) { + var dt_value = value; + var theType = options; + if (theType === 'date') { + return isDate(dt_value); + } else if (theType === 'time') { + return isTime(dt_value); + } else if (theType === 'datetime' || theType === 'timestamp') { + var tmstmp = false; + dt_value = dt_value.trim(); + if (dt_value === 'CURRENT_TIMESTAMP' || dt_value === 'current_timestamp()') { + return true; + } + if (theType === 'timestamp') { + tmstmp = true; + } + if (dt_value === '0000-00-00 00:00:00') { + return true; + } + var dv = dt_value.indexOf(' '); + if (dv === -1) { // Only the date component, which is valid + return isDate(dt_value, tmstmp); + } + + return isDate(dt_value.substring(0, dv), tmstmp) && + isTime(dt_value.substring(dv + 1)); + } + }); + /* + * message extending script must be run + * after initiation of functions + */ + extendingValidatorMessages(); + } + + $.datepicker.initialized = false; + + $(document).on('click', 'span.open_gis_editor', function (event) { + event.preventDefault(); + + var $span = $(this); + // Current value + var value = $span.parent('td').children('input[type=\'text\']').val(); + // Field name + var field = $span.parents('tr').children('td:first').find('input[type=\'hidden\']').val(); + // Column type + var type = $span.parents('tr').find('span.column_type').text(); + // Names of input field and null checkbox + var input_name = $span.parent('td').children('input[type=\'text\']').attr('name'); + + openGISEditor(); + if (!gisEditorLoaded) { + loadJSAndGISEditor(value, field, type, input_name); + } else { + loadGISEditor(value, field, type, input_name); + } + }); + + /** + * Forced validation check of fields + */ + $(document).on('click','input[name^=\'insert_ignore_\']', function (event) { + $('#insertForm').valid(); + }); + + /** + * Uncheck the null checkbox as geometry data is placed on the input field + */ + $(document).on('click', 'input[name=\'gis_data[save]\']', function (event) { + var input_name = $('form#gis_data_editor_form').find('input[name=\'input_name\']').val(); + var $null_checkbox = $('input[name=\'' + input_name + '\']').parents('tr').find('.checkbox_null'); + $null_checkbox.prop('checked', false); + }); + + /** + * Handles all current checkboxes for Null; this only takes care of the + * checkboxes on currently displayed rows as the rows generated by + * "Continue insertion" are handled in the "Continue insertion" code + * + */ + $(document).on('click', 'input.checkbox_null', function () { + nullify( + // use hidden fields populated by tbl_change.php + $(this).siblings('.nullify_code').val(), + $(this).closest('tr').find('input:hidden').first().val(), + $(this).siblings('.hashed_field').val(), + $(this).siblings('.multi_edit').val() + ); + }); + + /** + * Reset the auto_increment column to 0 when selecting any of the + * insert options in submit_type-dropdown. Only perform the reset + * when we are in edit-mode, and not in insert-mode(no previous value + * available). + */ + $('select[name="submit_type"]').on('change', function () { + var thisElemSubmitTypeVal = $(this).val(); + var $table = $('table.insertRowTable'); + var auto_increment_column = $table.find('input[name^="auto_increment"]'); + auto_increment_column.each(function () { + var $thisElemAIField = $(this); + var thisElemName = $thisElemAIField.attr('name'); + + var prev_value_field = $table.find('input[name="' + thisElemName.replace('auto_increment', 'fields_prev') + '"]'); + var value_field = $table.find('input[name="' + thisElemName.replace('auto_increment', 'fields') + '"]'); + var previous_value = $(prev_value_field).val(); + if (previous_value !== undefined) { + if (thisElemSubmitTypeVal === 'insert' + || thisElemSubmitTypeVal === 'insertignore' + || thisElemSubmitTypeVal === 'showinsert' + ) { + $(value_field).val(0); + } else { + $(value_field).val(previous_value); + } + } + }); + }); + + /** + * Handle ENTER key when press on Continue insert with field + */ + $('#insert_rows').keypress(function (e) { + var key = e.which; + if (key === 13) { + addNewContinueInsertionFiels(e); + } + }); + + /** + * Continue Insertion form + */ + $(document).on('change', '#insert_rows', addNewContinueInsertionFiels); +}); + +function addNewContinueInsertionFiels (event) { + event.preventDefault(); + /** + * @var columnCount Number of number of columns table has. + */ + var columnCount = $('table.insertRowTable:first').find('tr').has('input[name*=\'fields_name\']').length; + /** + * @var curr_rows Number of current insert rows already on page + */ + var curr_rows = $('table.insertRowTable').length; + /** + * @var target_rows Number of rows the user wants + */ + var target_rows = $('#insert_rows').val(); + + // remove all datepickers + $('input.datefield, input.datetimefield').each(function () { + $(this).datepicker('destroy'); + }); + + if (curr_rows < target_rows) { + var tempIncrementIndex = function () { + var $this_element = $(this); + /** + * Extract the index from the name attribute for all input/select fields and increment it + * name is of format funcs[multi_edit][10][] + */ + + /** + * @var this_name String containing name of the input/select elements + */ + var this_name = $this_element.attr('name'); + /** split {@link this_name} at [10], so we have the parts that can be concatenated later */ + var name_parts = this_name.split(/\[\d+\]/); + /** extract the [10] from {@link name_parts} */ + var old_row_index_string = this_name.match(/\[\d+\]/)[0]; + /** extract 10 - had to split into two steps to accomodate double digits */ + var old_row_index = parseInt(old_row_index_string.match(/\d+/)[0], 10); + + /** calculate next index i.e. 11 */ + new_row_index = old_row_index + 1; + /** generate the new name i.e. funcs[multi_edit][11][foobarbaz] */ + var new_name = name_parts[0] + '[' + new_row_index + ']' + name_parts[1]; + + var hashed_field = name_parts[1].match(/\[(.+)\]/)[1]; + $this_element.attr('name', new_name); + + /** If element is select[name*='funcs'], update id */ + if ($this_element.is('select[name*=\'funcs\']')) { + var this_id = $this_element.attr('id'); + var id_parts = this_id.split(/\_/); + var old_id_index = id_parts[1]; + var prevSelectedValue = $('#field_' + old_id_index + '_1').val(); + var new_id_index = parseInt(old_id_index) + columnCount; + var new_id = 'field_' + new_id_index + '_1'; + $this_element.attr('id', new_id); + $this_element.find('option').filter(function () { + return $(this).text() === prevSelectedValue; + }).attr('selected','selected'); + + // If salt field is there then update its id. + var nextSaltInput = $this_element.parent().next('td').next('td').find('input[name*=\'salt\']'); + if (nextSaltInput.length !== 0) { + nextSaltInput.attr('id', 'salt_' + new_id); + } + } + + // handle input text fields and textareas + if ($this_element.is('.textfield') || $this_element.is('.char') || $this_element.is('textarea')) { + // do not remove the 'value' attribute for ENUM columns + // special handling for radio fields after updating ids to unique - see below + if ($this_element.closest('tr').find('span.column_type').html() !== 'enum') { + $this_element.val($this_element.closest('tr').find('span.default_value').html()); + } + $this_element + .off('change') + // Remove onchange attribute that was placed + // by tbl_change.php; it refers to the wrong row index + .attr('onchange', null) + // Keep these values to be used when the element + // will change + .data('hashed_field', hashed_field) + .data('new_row_index', new_row_index) + .on('change', function () { + var $changed_element = $(this); + verificationsAfterFieldChange( + $changed_element.data('hashed_field'), + $changed_element.data('new_row_index'), + $changed_element.closest('tr').find('span.column_type').html() + ); + }); + } + + if ($this_element.is('.checkbox_null')) { + $this_element + // this event was bound earlier by jQuery but + // to the original row, not the cloned one, so unbind() + .off('click') + // Keep these values to be used when the element + // will be clicked + .data('hashed_field', hashed_field) + .data('new_row_index', new_row_index) + .on('click', function () { + var $changed_element = $(this); + nullify( + $changed_element.siblings('.nullify_code').val(), + $this_element.closest('tr').find('input:hidden').first().val(), + $changed_element.data('hashed_field'), + '[multi_edit][' + $changed_element.data('new_row_index') + ']' + ); + }); + } + }; + + var tempReplaceAnchor = function () { + var $anchor = $(this); + var new_value = 'rownumber=' + new_row_index; + // needs improvement in case something else inside + // the href contains this pattern + var new_href = $anchor.attr('href').replace(/rownumber=\d+/, new_value); + $anchor.attr('href', new_href); + }; + + while (curr_rows < target_rows) { + /** + * @var $last_row Object referring to the last row + */ + var $last_row = $('#insertForm').find('.insertRowTable:last'); + + // need to access this at more than one level + // (also needs improvement because it should be calculated + // just once per cloned row, not once per column) + var new_row_index = 0; + + // Clone the insert tables + $last_row + .clone(true, true) + .insertBefore('#actions_panel') + .find('input[name*=multi_edit],select[name*=multi_edit],textarea[name*=multi_edit]') + .each(tempIncrementIndex) + .end() + .find('.foreign_values_anchor') + .each(tempReplaceAnchor); + + // Insert/Clone the ignore checkboxes + if (curr_rows === 1) { + $('') + .insertBefore('table.insertRowTable:last') + .after(''); + } else { + /** + * @var $last_checkbox Object reference to the last checkbox in #insertForm + */ + var $last_checkbox = $('#insertForm').children('input:checkbox:last'); + + /** name of {@link $last_checkbox} */ + var last_checkbox_name = $last_checkbox.attr('name'); + /** index of {@link $last_checkbox} */ + var last_checkbox_index = parseInt(last_checkbox_name.match(/\d+/), 10); + /** name of new {@link $last_checkbox} */ + var new_name = last_checkbox_name.replace(/\d+/, last_checkbox_index + 1); + + $('
        ') + .insertBefore('table.insertRowTable:last'); + + $last_checkbox + .clone() + .attr({ 'id': new_name, 'name': new_name }) + .prop('checked', true) + .insertBefore('table.insertRowTable:last'); + + $('label[for^=insert_ignore]:last') + .clone() + .attr('for', new_name) + .insertBefore('table.insertRowTable:last'); + + $('
        ') + .insertBefore('table.insertRowTable:last'); + } + curr_rows++; + } + // recompute tabindex for text fields and other controls at footer; + // IMO it's not really important to handle the tabindex for + // function and Null + var tabindex = 0; + $('.textfield, .char, textarea') + .each(function () { + tabindex++; + $(this).attr('tabindex', tabindex); + // update the IDs of textfields to ensure that they are unique + $(this).attr('id', 'field_' + tabindex + '_3'); + + // special handling for radio fields after updating ids to unique + if ($(this).closest('tr').find('span.column_type').html() === 'enum') { + if ($(this).val() === $(this).closest('tr').find('span.default_value').html()) { + $(this).prop('checked', true); + } else { + $(this).prop('checked', false); + } + } + }); + $('.control_at_footer') + .each(function () { + tabindex++; + $(this).attr('tabindex', tabindex); + }); + } else if (curr_rows > target_rows) { + /** + * Displays alert if data loss possible on decrease + * of rows. + */ + var checkLock = jQuery.isEmptyObject(AJAX.lockedTargets); + if (checkLock || confirm(PMA_messages.strConfirmRowChange) === true) { + while (curr_rows > target_rows) { + $('input[id^=insert_ignore]:last') + .nextUntil('fieldset') + .addBack() + .remove(); + curr_rows--; + } + } else { + document.getElementById('insert_rows').value = curr_rows; + } + } + // Add all the required datepickers back + addDateTimePicker(); +} + +function changeValueFieldType (elem, searchIndex) { + var fieldsValue = $('select#fieldID_' + searchIndex); + if (0 === fieldsValue.size()) { + return; + } + + var type = $(elem).val(); + if ('IN (...)' === type || + 'NOT IN (...)' === type || + 'BETWEEN' === type || + 'NOT BETWEEN' === type + ) { + $('#fieldID_' + searchIndex).attr('multiple', ''); + } else { + $('#fieldID_' + searchIndex).removeAttr('multiple'); + } +} diff --git a/php/apps/phpmyadmin49/js/tbl_chart.js b/php/apps/phpmyadmin49/js/tbl_chart.js new file mode 100644 index 00000000..b43a2215 --- /dev/null +++ b/php/apps/phpmyadmin49/js/tbl_chart.js @@ -0,0 +1,423 @@ +/* vim: set expandtab sw=4 ts=4 sts=4: */ + +var chart_data = {}; +var temp_chart_title; + +var currentChart = null; +var currentSettings = null; + +var dateTimeCols = []; +var numericCols = []; + +function extractDate (dateString) { + var matches; + var match; + var dateTimeRegExp = /[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}/; + var dateRegExp = /[0-9]{4}-[0-9]{2}-[0-9]{2}/; + + matches = dateTimeRegExp.exec(dateString); + if (matches !== null && matches.length > 0) { + match = matches[0]; + return new Date(match.substr(0, 4), parseInt(match.substr(5, 2), 10) - 1, match.substr(8, 2), match.substr(11, 2), match.substr(14, 2), match.substr(17, 2)); + } else { + matches = dateRegExp.exec(dateString); + if (matches !== null && matches.length > 0) { + match = matches[0]; + return new Date(match.substr(0, 4), parseInt(match.substr(5, 2), 10) - 1, match.substr(8, 2)); + } + } + return null; +} + +function PMA_queryChart (data, columnNames, settings) { + if ($('#querychart').length === 0) { + return; + } + + var plotSettings = { + title : { + text : settings.title, + escapeHtml: true + }, + grid : { + drawBorder : false, + shadow : false, + background : 'rgba(0,0,0,0)' + }, + legend : { + show : true, + placement : 'outsideGrid', + location : 'e', + rendererOptions: { + numberColumns: 2 + } + }, + axes : { + xaxis : { + label : escapeHtml(settings.xaxisLabel) + }, + yaxis : { + label : settings.yaxisLabel + } + }, + stackSeries : settings.stackSeries + }; + + // create the chart + var factory = new JQPlotChartFactory(); + var chart = factory.createChart(settings.type, 'querychart'); + + // create the data table and add columns + var dataTable = new DataTable(); + if (settings.type === 'timeline') { + dataTable.addColumn(ColumnType.DATE, columnNames[settings.mainAxis]); + } else if (settings.type === 'scatter') { + dataTable.addColumn(ColumnType.NUMBER, columnNames[settings.mainAxis]); + } else { + dataTable.addColumn(ColumnType.STRING, columnNames[settings.mainAxis]); + } + + var i; + if (settings.seriesColumn === null) { + $.each(settings.selectedSeries, function (index, element) { + dataTable.addColumn(ColumnType.NUMBER, columnNames[element]); + }); + + // set data to the data table + var columnsToExtract = [settings.mainAxis]; + $.each(settings.selectedSeries, function (index, element) { + columnsToExtract.push(element); + }); + var values = []; + var newRow; + var row; + var col; + for (i = 0; i < data.length; i++) { + row = data[i]; + newRow = []; + for (var j = 0; j < columnsToExtract.length; j++) { + col = columnNames[columnsToExtract[j]]; + if (j === 0) { + if (settings.type === 'timeline') { // first column is date type + newRow.push(extractDate(row[col])); + } else if (settings.type === 'scatter') { + newRow.push(parseFloat(row[col])); + } else { // first column is string type + newRow.push(row[col]); + } + } else { // subsequent columns are of type, number + newRow.push(parseFloat(row[col])); + } + } + values.push(newRow); + } + dataTable.setData(values); + } else { + var seriesNames = {}; + var seriesNumber = 1; + var seriesColumnName = columnNames[settings.seriesColumn]; + for (i = 0; i < data.length; i++) { + if (! seriesNames[data[i][seriesColumnName]]) { + seriesNames[data[i][seriesColumnName]] = seriesNumber; + seriesNumber++; + } + } + + $.each(seriesNames, function (seriesName, seriesNumber) { + dataTable.addColumn(ColumnType.NUMBER, seriesName); + }); + + var valueMap = {}; + var xValue; + var value; + var mainAxisName = columnNames[settings.mainAxis]; + var valueColumnName = columnNames[settings.valueColumn]; + for (i = 0; i < data.length; i++) { + xValue = data[i][mainAxisName]; + value = valueMap[xValue]; + if (! value) { + value = [xValue]; + valueMap[xValue] = value; + } + seriesNumber = seriesNames[data[i][seriesColumnName]]; + value[seriesNumber] = parseFloat(data[i][valueColumnName]); + } + + var values = []; + $.each(valueMap, function (index, value) { + values.push(value); + }); + dataTable.setData(values); + } + + // draw the chart and return the chart object + chart.draw(dataTable, plotSettings); + return chart; +} + +function drawChart () { + currentSettings.width = $('#resizer').width() - 20; + currentSettings.height = $('#resizer').height() - 20; + + // TODO: a better way using .redraw() ? + if (currentChart !== null) { + currentChart.destroy(); + } + + var columnNames = []; + $('select[name="chartXAxis"] option').each(function () { + columnNames.push(escapeHtml($(this).text())); + }); + try { + currentChart = PMA_queryChart(chart_data, columnNames, currentSettings); + if (currentChart !== null) { + $('#saveChart').attr('href', currentChart.toImageString()); + } + } catch (err) { + PMA_ajaxShowMessage(err.message, false); + } +} + +function getSelectedSeries () { + var val = $('select[name="chartSeries"]').val() || []; + var ret = []; + $.each(val, function (i, v) { + ret.push(parseInt(v, 10)); + }); + return ret; +} + +function onXAxisChange () { + var $xAxisSelect = $('select[name="chartXAxis"]'); + currentSettings.mainAxis = parseInt($xAxisSelect.val(), 10); + if (dateTimeCols.indexOf(currentSettings.mainAxis) !== -1) { + $('span.span_timeline').show(); + } else { + $('span.span_timeline').hide(); + if (currentSettings.type === 'timeline') { + $('input#radio_line').prop('checked', true); + currentSettings.type = 'line'; + } + } + if (numericCols.indexOf(currentSettings.mainAxis) !== -1) { + $('span.span_scatter').show(); + } else { + $('span.span_scatter').hide(); + if (currentSettings.type === 'scatter') { + $('input#radio_line').prop('checked', true); + currentSettings.type = 'line'; + } + } + var xaxis_title = $xAxisSelect.children('option:selected').text(); + $('input[name="xaxis_label"]').val(xaxis_title); + currentSettings.xaxisLabel = xaxis_title; +} + +function onDataSeriesChange () { + var $seriesSelect = $('select[name="chartSeries"]'); + currentSettings.selectedSeries = getSelectedSeries(); + var yaxis_title; + if (currentSettings.selectedSeries.length === 1) { + $('span.span_pie').show(); + yaxis_title = $seriesSelect.children('option:selected').text(); + } else { + $('span.span_pie').hide(); + if (currentSettings.type === 'pie') { + $('input#radio_line').prop('checked', true); + currentSettings.type = 'line'; + } + yaxis_title = PMA_messages.strYValues; + } + $('input[name="yaxis_label"]').val(yaxis_title); + currentSettings.yaxisLabel = yaxis_title; +} + +/** + * Unbind all event handlers before tearing down a page + */ +AJAX.registerTeardown('tbl_chart.js', function () { + $('input[name="chartType"]').off('click'); + $('input[name="barStacked"]').off('click'); + $('input[name="chkAlternative"]').off('click'); + $('input[name="chartTitle"]').off('focus').off('keyup').off('blur'); + $('select[name="chartXAxis"]').off('change'); + $('select[name="chartSeries"]').off('change'); + $('select[name="chartSeriesColumn"]').off('change'); + $('select[name="chartValueColumn"]').off('change'); + $('input[name="xaxis_label"]').off('keyup'); + $('input[name="yaxis_label"]').off('keyup'); + $('#resizer').off('resizestop'); + $('#tblchartform').off('submit'); +}); + +AJAX.registerOnload('tbl_chart.js', function () { + // handle manual resize + $('#resizer').on('resizestop', function (event, ui) { + // make room so that the handle will still appear + $('#querychart').height($('#resizer').height() * 0.96); + $('#querychart').width($('#resizer').width() * 0.96); + if (currentChart !== null) { + currentChart.redraw({ + resetAxes : true + }); + } + }); + + // handle chart type changes + $('input[name="chartType"]').click(function () { + var type = currentSettings.type = $(this).val(); + if (type === 'bar' || type === 'column' || type === 'area') { + $('span.barStacked').show(); + } else { + $('input[name="barStacked"]').prop('checked', false); + $.extend(true, currentSettings, { stackSeries : false }); + $('span.barStacked').hide(); + } + drawChart(); + }); + + // handle chosing alternative data format + $('input[name="chkAlternative"]').click(function () { + var $seriesColumn = $('select[name="chartSeriesColumn"]'); + var $valueColumn = $('select[name="chartValueColumn"]'); + var $chartSeries = $('select[name="chartSeries"]'); + if ($(this).is(':checked')) { + $seriesColumn.prop('disabled', false); + $valueColumn.prop('disabled', false); + $chartSeries.prop('disabled', true); + currentSettings.seriesColumn = parseInt($seriesColumn.val(), 10); + currentSettings.valueColumn = parseInt($valueColumn.val(), 10); + } else { + $seriesColumn.prop('disabled', true); + $valueColumn.prop('disabled', true); + $chartSeries.prop('disabled', false); + currentSettings.seriesColumn = null; + currentSettings.valueColumn = null; + } + drawChart(); + }); + + // handle stacking for bar, column and area charts + $('input[name="barStacked"]').click(function () { + if ($(this).is(':checked')) { + $.extend(true, currentSettings, { stackSeries : true }); + } else { + $.extend(true, currentSettings, { stackSeries : false }); + } + drawChart(); + }); + + // handle changes in chart title + $('input[name="chartTitle"]') + .focus(function () { + temp_chart_title = $(this).val(); + }) + .keyup(function () { + currentSettings.title = $('input[name="chartTitle"]').val(); + drawChart(); + }) + .blur(function () { + if ($(this).val() !== temp_chart_title) { + drawChart(); + } + }); + + // handle changing the x-axis + $('select[name="chartXAxis"]').change(function () { + onXAxisChange(); + drawChart(); + }); + + // handle changing the selected data series + $('select[name="chartSeries"]').change(function () { + onDataSeriesChange(); + drawChart(); + }); + + // handle changing the series column + $('select[name="chartSeriesColumn"]').change(function () { + currentSettings.seriesColumn = parseInt($(this).val(), 10); + drawChart(); + }); + + // handle changing the value column + $('select[name="chartValueColumn"]').change(function () { + currentSettings.valueColumn = parseInt($(this).val(), 10); + drawChart(); + }); + + // handle manual changes to the chart x-axis labels + $('input[name="xaxis_label"]').keyup(function () { + currentSettings.xaxisLabel = $(this).val(); + drawChart(); + }); + + // handle manual changes to the chart y-axis labels + $('input[name="yaxis_label"]').keyup(function () { + currentSettings.yaxisLabel = $(this).val(); + drawChart(); + }); + + // handler for ajax form submission + $('#tblchartform').submit(function (event) { + var $form = $(this); + if (codemirror_editor) { + $form[0].elements.sql_query.value = codemirror_editor.getValue(); + } + if (!checkSqlQuery($form[0])) { + return false; + } + + var $msgbox = PMA_ajaxShowMessage(); + PMA_prepareForAjaxRequest($form); + $.post($form.attr('action'), $form.serialize(), function (data) { + if (typeof data !== 'undefined' && + data.success === true && + typeof data.chartData !== 'undefined') { + chart_data = JSON.parse(data.chartData); + drawChart(); + PMA_ajaxRemoveMessage($msgbox); + } else { + PMA_ajaxShowMessage(data.error, false); + } + }, 'json'); // end $.post() + + return false; + }); + + // from jQuery UI + $('#resizer').resizable({ + minHeight: 240, + minWidth: 300 + }) + .width($('#div_view_options').width() - 50) + .trigger('resizestop'); + + currentSettings = { + type : 'line', + width : $('#resizer').width() - 20, + height : $('#resizer').height() - 20, + xaxisLabel : $('input[name="xaxis_label"]').val(), + yaxisLabel : $('input[name="yaxis_label"]').val(), + title : $('input[name="chartTitle"]').val(), + stackSeries : false, + mainAxis : parseInt($('select[name="chartXAxis"]').val(), 10), + selectedSeries : getSelectedSeries(), + seriesColumn : null + }; + + var vals = $('input[name="dateTimeCols"]').val().split(' '); + $.each(vals, function (i, v) { + dateTimeCols.push(parseInt(v, 10)); + }); + + vals = $('input[name="numericCols"]').val().split(' '); + $.each(vals, function (i, v) { + numericCols.push(parseInt(v, 10)); + }); + + onXAxisChange(); + onDataSeriesChange(); + + $('#tblchartform').submit(); +}); diff --git a/php/apps/phpmyadmin49/js/tbl_find_replace.js b/php/apps/phpmyadmin49/js/tbl_find_replace.js new file mode 100644 index 00000000..8b48e53e --- /dev/null +++ b/php/apps/phpmyadmin49/js/tbl_find_replace.js @@ -0,0 +1,46 @@ +/** + * Unbind all event handlers before tearing down a page + */ +AJAX.registerTeardown('tbl_find_replace.js', function () { + $('#find_replace_form').off('submit'); + $('#toggle_find').off('click'); +}); + +/** + * Bind events + */ +AJAX.registerOnload('tbl_find_replace.js', function () { + $('
        ') + .insertAfter('#find_replace_form') + .hide(); + + $('#toggle_find') + .html(PMA_messages.strHideFindNReplaceCriteria) + .click(function () { + var $link = $(this); + $('#find_replace_form').slideToggle(); + if ($link.text() === PMA_messages.strHideFindNReplaceCriteria) { + $link.text(PMA_messages.strShowFindNReplaceCriteria); + } else { + $link.text(PMA_messages.strHideFindNReplaceCriteria); + } + return false; + }); + + $('#find_replace_form').submit(function (e) { + e.preventDefault(); + var findReplaceForm = $('#find_replace_form'); + PMA_prepareForAjaxRequest(findReplaceForm); + var $msgbox = PMA_ajaxShowMessage(); + $.post(findReplaceForm.attr('action'), findReplaceForm.serialize(), function (data) { + PMA_ajaxRemoveMessage($msgbox); + if (data.success === true) { + $('#toggle_find_div').show(); + $('#toggle_find').click(); + $('#sqlqueryresultsouter').html(data.preview); + } else { + $('#sqlqueryresultsouter').html(data.error); + } + }); + }); +}); diff --git a/php/apps/phpmyadmin49/js/tbl_gis_visualization.js b/php/apps/phpmyadmin49/js/tbl_gis_visualization.js new file mode 100644 index 00000000..0cb70ab3 --- /dev/null +++ b/php/apps/phpmyadmin49/js/tbl_gis_visualization.js @@ -0,0 +1,365 @@ +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * @fileoverview functions used for visualizing GIS data + * + * @requires jquery + * @requires vendor/jquery/jquery.svg.js + * @requires vendor/jquery/jquery.mousewheel.js + * @requires vendor/jquery/jquery.event.drag-2.2.js + */ + +// Constants +var zoomFactor = 1.5; +var defaultX = 0; +var defaultY = 0; + +// Variables +var x = 0; +var y = 0; +var scale = 1; + +var svg; + +/** + * Zooms and pans the visualization. + */ +function zoomAndPan () { + var g = svg.getElementById('groupPanel'); + if (!g) { + return; + } + + g.setAttribute('transform', 'translate(' + x + ', ' + y + ') scale(' + scale + ')'); + var id; + var circle; + $('circle.vector').each(function () { + id = $(this).attr('id'); + circle = svg.getElementById(id); + $(svg).change(circle, { + r : (3 / scale), + 'stroke-width' : (2 / scale) + }); + }); + + var line; + $('polyline.vector').each(function () { + id = $(this).attr('id'); + line = svg.getElementById(id); + $(svg).change(line, { + 'stroke-width' : (2 / scale) + }); + }); + + var polygon; + $('path.vector').each(function () { + id = $(this).attr('id'); + polygon = svg.getElementById(id); + $(svg).change(polygon, { + 'stroke-width' : (0.5 / scale) + }); + }); +} + +/** + * Initially loads either SVG or OSM visualization based on the choice. + */ +function selectVisualization () { + if ($('#choice').prop('checked') !== true) { + $('#openlayersmap').hide(); + } else { + $('#placeholder').hide(); + } +} + +/** + * Adds necessary styles to the div that coontains the openStreetMap. + */ +function styleOSM () { + var $placeholder = $('#placeholder'); + var cssObj = { + 'border' : '1px solid #aaa', + 'width' : $placeholder.width(), + 'height' : $placeholder.height(), + 'float' : 'right' + }; + $('#openlayersmap').css(cssObj); +} + +/** + * Loads the SVG element and make a reference to it. + */ +function loadSVG () { + var $placeholder = $('#placeholder'); + + $placeholder.svg({ + onLoad: function (svg_ref) { + svg = svg_ref; + } + }); + + // Removes the second SVG element unnecessarily added due to the above command + $placeholder.find('svg:nth-child(2)').remove(); +} + +/** + * Adds controllers for zooming and panning. + */ +function addZoomPanControllers () { + var $placeholder = $('#placeholder'); + if ($('#placeholder').find('svg').length > 0) { + var pmaThemeImage = $('#pmaThemeImage').val(); + // add panning arrows + $('').appendTo($placeholder); + $('').appendTo($placeholder); + $('').appendTo($placeholder); + $('').appendTo($placeholder); + // add zooming controls + $('').appendTo($placeholder); + $('').appendTo($placeholder); + $('').appendTo($placeholder); + } +} + +/** + * Resizes the GIS visualization to fit into the space available. + */ +function resizeGISVisualization () { + var $placeholder = $('#placeholder'); + var old_width = $placeholder.width(); + var visWidth = $('#div_view_options').width() - 48; + + // Assign new value for width + $placeholder.width(visWidth); + $('svg').attr('width', visWidth); + + // Assign the offset created due to resizing to defaultX and center the svg. + defaultX = (visWidth - old_width) / 2; + x = defaultX; + y = 0; + scale = 1; +} + +/** + * Initialize the GIS visualization. + */ +function initGISVisualization () { + // Loads either SVG or OSM visualization based on the choice + selectVisualization(); + // Resizes the GIS visualization to fit into the space available + resizeGISVisualization(); + if (typeof OpenLayers !== 'undefined') { + // Configure OpenLayers + OpenLayers._getScriptLocation = function () { + return './js/vendor/openlayers/'; + }; + // Adds necessary styles to the div that coontains the openStreetMap + styleOSM(); + // Draws openStreetMap with openLayers + drawOpenLayers(); + } + // Loads the SVG element and make a reference to it + loadSVG(); + // Adds controllers for zooming and panning + addZoomPanControllers(); + zoomAndPan(); +} + +function getRelativeCoords (e) { + var position = $('#placeholder').offset(); + return { + x : e.pageX - position.left, + y : e.pageY - position.top + }; +} + +/** + * Ajax handlers for GIS visualization page + * + * Actions Ajaxified here: + * + * Zooming in and zooming out on mousewheel movement. + * Panning the visualization on dragging. + * Zooming in on double clicking. + * Zooming out on clicking the zoom out button. + * Panning on clicking the arrow buttons. + * Displaying tooltips for GIS objects. + */ + +/** + * Unbind all event handlers before tearing down a page + */ +AJAX.registerTeardown('tbl_gis_visualization.js', function () { + $(document).off('click', '#choice'); + $(document).off('mousewheel', '#placeholder'); + $(document).off('dragstart', 'svg'); + $(document).off('mouseup', 'svg'); + $(document).off('drag', 'svg'); + $(document).off('dblclick', '#placeholder'); + $(document).off('click', '#zoom_in'); + $(document).off('click', '#zoom_world'); + $(document).off('click', '#zoom_out'); + $(document).off('click', '#left_arrow'); + $(document).off('click', '#right_arrow'); + $(document).off('click', '#up_arrow'); + $(document).off('click', '#down_arrow'); + $('.vector').off('mousemove').off('mouseout'); +}); + +AJAX.registerOnload('tbl_gis_visualization.js', function () { + // If we are in GIS visualization, initialize it + if ($('#gis_div').length > 0) { + initGISVisualization(); + } + + if (typeof OpenLayers === 'undefined') { + $('#choice, #labelChoice').hide(); + } + $(document).on('click', '#choice', function () { + if ($(this).prop('checked') === false) { + $('#placeholder').show(); + $('#openlayersmap').hide(); + } else { + $('#placeholder').hide(); + $('#openlayersmap').show(); + } + }); + + $(document).on('mousewheel', '#placeholder', function (event, delta) { + event.preventDefault(); + var relCoords = getRelativeCoords(event); + if (delta > 0) { + // zoom in + scale *= zoomFactor; + // zooming in keeping the position under mouse pointer unmoved. + x = relCoords.x - (relCoords.x - x) * zoomFactor; + y = relCoords.y - (relCoords.y - y) * zoomFactor; + zoomAndPan(); + } else { + // zoom out + scale /= zoomFactor; + // zooming out keeping the position under mouse pointer unmoved. + x = relCoords.x - (relCoords.x - x) / zoomFactor; + y = relCoords.y - (relCoords.y - y) / zoomFactor; + zoomAndPan(); + } + return true; + }); + + var dragX = 0; + var dragY = 0; + + $(document).on('dragstart', 'svg', function (event, dd) { + $('#placeholder').addClass('placeholderDrag'); + dragX = Math.round(dd.offsetX); + dragY = Math.round(dd.offsetY); + }); + + $(document).on('mouseup', 'svg', function (event) { + $('#placeholder').removeClass('placeholderDrag'); + }); + + $(document).on('drag', 'svg', function (event, dd) { + var newX = Math.round(dd.offsetX); + x += newX - dragX; + dragX = newX; + var newY = Math.round(dd.offsetY); + y += newY - dragY; + dragY = newY; + zoomAndPan(); + }); + + $(document).on('dblclick', '#placeholder', function (event) { + scale *= zoomFactor; + // zooming in keeping the position under mouse pointer unmoved. + var relCoords = getRelativeCoords(event); + x = relCoords.x - (relCoords.x - x) * zoomFactor; + y = relCoords.y - (relCoords.y - y) * zoomFactor; + zoomAndPan(); + }); + + $(document).on('click', '#zoom_in', function (e) { + e.preventDefault(); + // zoom in + scale *= zoomFactor; + + var $placeholder = $('#placeholder').find('svg'); + width = $placeholder.attr('width'); + height = $placeholder.attr('height'); + // zooming in keeping the center unmoved. + x = width / 2 - (width / 2 - x) * zoomFactor; + y = height / 2 - (height / 2 - y) * zoomFactor; + zoomAndPan(); + }); + + $(document).on('click', '#zoom_world', function (e) { + e.preventDefault(); + scale = 1; + x = defaultX; + y = defaultY; + zoomAndPan(); + }); + + $(document).on('click', '#zoom_out', function (e) { + e.preventDefault(); + // zoom out + scale /= zoomFactor; + + var $placeholder = $('#placeholder').find('svg'); + width = $placeholder.attr('width'); + height = $placeholder.attr('height'); + // zooming out keeping the center unmoved. + x = width / 2 - (width / 2 - x) / zoomFactor; + y = height / 2 - (height / 2 - y) / zoomFactor; + zoomAndPan(); + }); + + $(document).on('click', '#left_arrow', function (e) { + e.preventDefault(); + x += 100; + zoomAndPan(); + }); + + $(document).on('click', '#right_arrow', function (e) { + e.preventDefault(); + x -= 100; + zoomAndPan(); + }); + + $(document).on('click', '#up_arrow', function (e) { + e.preventDefault(); + y += 100; + zoomAndPan(); + }); + + $(document).on('click', '#down_arrow', function (e) { + e.preventDefault(); + y -= 100; + zoomAndPan(); + }); + + /** + * Detect the mousemove event and show tooltips. + */ + $('.vector').on('mousemove', function (event) { + var contents = $.trim(escapeHtml($(this).attr('name'))); + $('#tooltip').remove(); + if (contents !== '') { + $('
        ' + contents + '
        ').css({ + position : 'absolute', + top : event.pageY + 10, + left : event.pageX + 10, + border : '1px solid #fdd', + padding : '2px', + 'background-color' : '#fee', + opacity : 0.90 + }).appendTo('body').fadeIn(200); + } + }); + + /** + * Detect the mouseout event and hide tooltips. + */ + $('.vector').on('mouseout', function (event) { + $('#tooltip').remove(); + }); +}); diff --git a/php/apps/phpmyadmin49/js/tbl_operations.js b/php/apps/phpmyadmin49/js/tbl_operations.js new file mode 100644 index 00000000..cb818a29 --- /dev/null +++ b/php/apps/phpmyadmin49/js/tbl_operations.js @@ -0,0 +1,323 @@ +/** + * Unbind all event handlers before tearing down a page + */ +AJAX.registerTeardown('tbl_operations.js', function () { + $(document).off('submit', '#copyTable.ajax'); + $(document).off('submit', '#moveTableForm'); + $(document).off('submit', '#tableOptionsForm'); + $(document).off('submit', '#partitionsForm'); + $(document).off('click', '#tbl_maintenance li a.maintain_action.ajax'); + $(document).off('click', '#drop_tbl_anchor.ajax'); + $(document).off('click', '#drop_view_anchor.ajax'); + $(document).off('click', '#truncate_tbl_anchor.ajax'); +}); + +/** + * jQuery coding for 'Table operations'. Used on tbl_operations.php + * Attach Ajax Event handlers for Table operations + */ +AJAX.registerOnload('tbl_operations.js', function () { + /** + *Ajax action for submitting the "Copy table" + **/ + $(document).on('submit', '#copyTable.ajax', function (event) { + event.preventDefault(); + var $form = $(this); + PMA_prepareForAjaxRequest($form); + var argsep = PMA_commonParams.get('arg_separator'); + $.post($form.attr('action'), $form.serialize() + argsep + 'submit_copy=Go', function (data) { + if (typeof data !== 'undefined' && data.success === true) { + if ($form.find('input[name=\'switch_to_new\']').prop('checked')) { + PMA_commonParams.set( + 'db', + $form.find('select[name=\'target_db\']').val() + ); + PMA_commonParams.set( + 'table', + $form.find('input[name=\'new_name\']').val() + ); + PMA_commonActions.refreshMain(false, function () { + PMA_ajaxShowMessage(data.message); + }); + } else { + PMA_ajaxShowMessage(data.message); + } + // Refresh navigation when the table is copied + PMA_reloadNavigation(); + } else { + PMA_ajaxShowMessage(data.error, false); + } + }); // end $.post() + });// end of copyTable ajax submit + + /** + *Ajax action for submitting the "Move table" + */ + $(document).on('submit', '#moveTableForm', function (event) { + event.preventDefault(); + var $form = $(this); + PMA_prepareForAjaxRequest($form); + var argsep = PMA_commonParams.get('arg_separator'); + $.post($form.attr('action'), $form.serialize() + argsep + 'submit_move=1', function (data) { + if (typeof data !== 'undefined' && data.success === true) { + PMA_commonParams.set('db', data._params.db); + PMA_commonParams.set('table', data._params.table); + PMA_commonActions.refreshMain('tbl_sql.php', function () { + PMA_ajaxShowMessage(data.message); + }); + // Refresh navigation when the table is copied + PMA_reloadNavigation(); + } else { + PMA_ajaxShowMessage(data.error, false); + } + }); // end $.post() + }); + + /** + * Ajax action for submitting the "Table options" + */ + $(document).on('submit', '#tableOptionsForm', function (event) { + event.preventDefault(); + event.stopPropagation(); + var $form = $(this); + var $tblNameField = $form.find('input[name=new_name]'); + var $tblCollationField = $form.find('select[name=tbl_collation]'); + var collationOrigValue = $('select[name="tbl_collation"] option[selected]').val(); + var $changeAllColumnCollationsCheckBox = $('#checkbox_change_all_collations'); + var question = PMA_messages.strChangeAllColumnCollationsWarning; + + if ($tblNameField.val() !== $tblNameField[0].defaultValue) { + // reload page and navigation if the table has been renamed + PMA_prepareForAjaxRequest($form); + + if ($tblCollationField.val() !== collationOrigValue && $changeAllColumnCollationsCheckBox.is(':checked')) { + $form.PMA_confirm(question, $form.attr('action'), function (url) { + submitOptionsForm(); + }); + } else { + submitOptionsForm(); + } + } else { + if ($tblCollationField.val() !== collationOrigValue && $changeAllColumnCollationsCheckBox.is(':checked')) { + $form.PMA_confirm(question, $form.attr('action'), function (url) { + $form.removeClass('ajax').submit().addClass('ajax'); + }); + } else { + $form.removeClass('ajax').submit().addClass('ajax'); + } + } + + function submitOptionsForm () { + $.post($form.attr('action'), $form.serialize(), function (data) { + if (typeof data !== 'undefined' && data.success === true) { + PMA_commonParams.set('table', data._params.table); + PMA_commonActions.refreshMain(false, function () { + $('#page_content').html(data.message); + PMA_highlightSQL($('#page_content')); + }); + // Refresh navigation when the table is renamed + PMA_reloadNavigation(); + } else { + PMA_ajaxShowMessage(data.error, false); + } + }); // end $.post() + } + }); + + /** + *Ajax events for actions in the "Table maintenance" + **/ + $(document).on('click', '#tbl_maintenance li a.maintain_action.ajax', function (event) { + event.preventDefault(); + var $link = $(this); + + if ($('.sqlqueryresults').length !== 0) { + $('.sqlqueryresults').remove(); + } + if ($('.result_query').length !== 0) { + $('.result_query').remove(); + } + // variables which stores the common attributes + var params = $.param({ + ajax_request: 1, + server: PMA_commonParams.get('server') + }); + var postData = $link.getPostData(); + if (postData) { + params += PMA_commonParams.get('arg_separator') + postData; + } + + $.post($link.attr('href'), params, function (data) { + function scrollToTop () { + $('html, body').animate({ scrollTop: 0 }); + } + var $temp_div; + if (typeof data !== 'undefined' && data.success === true && data.sql_query !== undefined) { + PMA_ajaxShowMessage(data.message); + $('
        ').prependTo('#page_content'); + $('.sqlqueryresults').html(data.sql_query); + PMA_highlightSQL($('#page_content')); + scrollToTop(); + } else if (typeof data !== 'undefined' && data.success === true) { + $temp_div = $('
        '); + $temp_div.html(data.message); + var $success = $temp_div.find('.result_query .success'); + PMA_ajaxShowMessage($success); + $('
        ').prependTo('#page_content'); + $('.sqlqueryresults').html(data.message); + PMA_highlightSQL($('#page_content')); + PMA_init_slider(); + $('.sqlqueryresults').children('fieldset,br').remove(); + scrollToTop(); + } else { + $temp_div = $('
        '); + $temp_div.html(data.error); + + var $error; + if ($temp_div.find('.error code').length !== 0) { + $error = $temp_div.find('.error code').addClass('error'); + } else { + $error = $temp_div; + } + + PMA_ajaxShowMessage($error, false); + } + }); // end $.post() + });// end of table maintenance ajax click + + /** + * Ajax action for submitting the "Partition Maintenance" + * Also, asks for confirmation when DROP partition is submitted + */ + $(document).on('submit', '#partitionsForm', function (event) { + event.preventDefault(); + var $form = $(this); + + function submitPartitionMaintenance () { + var argsep = PMA_commonParams.get('arg_separator'); + var submitData = $form.serialize() + argsep + 'ajax_request=true' + argsep + 'ajax_page_request=true'; + PMA_ajaxShowMessage(PMA_messages.strProcessingRequest); + AJAX.source = $form; + $.post($form.attr('action'), submitData, AJAX.responseHandler); + } + + if ($('#partition_operation_DROP').is(':checked')) { + var question = PMA_messages.strDropPartitionWarning; + $form.PMA_confirm(question, $form.attr('action'), function (url) { + submitPartitionMaintenance(); + }); + } else if ($('#partition_operation_TRUNCATE').is(':checked')) { + var question = PMA_messages.strTruncatePartitionWarning; + $form.PMA_confirm(question, $form.attr('action'), function (url) { + submitPartitionMaintenance(); + }); + } else { + submitPartitionMaintenance(); + } + }); + + $(document).on('click', '#drop_tbl_anchor.ajax', function (event) { + event.preventDefault(); + var $link = $(this); + /** + * @var question String containing the question to be asked for confirmation + */ + var question = PMA_messages.strDropTableStrongWarning + ' '; + question += PMA_sprintf( + PMA_messages.strDoYouReally, + 'DROP TABLE `' + escapeHtml(PMA_commonParams.get('db')) + '`.`' + escapeHtml(PMA_commonParams.get('table') + '`') + ) + getForeignKeyCheckboxLoader(); + + $(this).PMA_confirm(question, $(this).attr('href'), function (url) { + var $msgbox = PMA_ajaxShowMessage(PMA_messages.strProcessingRequest); + + var params = getJSConfirmCommonParam(this, $link.getPostData()); + + $.post(url, params, function (data) { + if (typeof data !== 'undefined' && data.success === true) { + PMA_ajaxRemoveMessage($msgbox); + // Table deleted successfully, refresh both the frames + PMA_reloadNavigation(); + PMA_commonParams.set('table', ''); + PMA_commonActions.refreshMain( + PMA_commonParams.get('opendb_url'), + function () { + PMA_ajaxShowMessage(data.message); + } + ); + } else { + PMA_ajaxShowMessage(data.error, false); + } + }); // end $.post() + }, loadForeignKeyCheckbox); // end $.PMA_confirm() + }); // end of Drop Table Ajax action + + $(document).on('click', '#drop_view_anchor.ajax', function (event) { + event.preventDefault(); + var $link = $(this); + /** + * @var question String containing the question to be asked for confirmation + */ + var question = PMA_messages.strDropTableStrongWarning + ' '; + question += PMA_sprintf( + PMA_messages.strDoYouReally, + 'DROP VIEW `' + escapeHtml(PMA_commonParams.get('table') + '`') + ); + + $(this).PMA_confirm(question, $(this).attr('href'), function (url) { + var $msgbox = PMA_ajaxShowMessage(PMA_messages.strProcessingRequest); + var params = getJSConfirmCommonParam(this, $link.getPostData()); + $.post(url, params, function (data) { + if (typeof data !== 'undefined' && data.success === true) { + PMA_ajaxRemoveMessage($msgbox); + // Table deleted successfully, refresh both the frames + PMA_reloadNavigation(); + PMA_commonParams.set('table', ''); + PMA_commonActions.refreshMain( + PMA_commonParams.get('opendb_url'), + function () { + PMA_ajaxShowMessage(data.message); + } + ); + } else { + PMA_ajaxShowMessage(data.error, false); + } + }); // end $.post() + }); // end $.PMA_confirm() + }); // end of Drop View Ajax action + + $(document).on('click', '#truncate_tbl_anchor.ajax', function (event) { + event.preventDefault(); + var $link = $(this); + /** + * @var question String containing the question to be asked for confirmation + */ + var question = PMA_messages.strTruncateTableStrongWarning + ' '; + question += PMA_sprintf( + PMA_messages.strDoYouReally, + 'TRUNCATE `' + escapeHtml(PMA_commonParams.get('db')) + '`.`' + escapeHtml(PMA_commonParams.get('table') + '`') + ) + getForeignKeyCheckboxLoader(); + $(this).PMA_confirm(question, $(this).attr('href'), function (url) { + PMA_ajaxShowMessage(PMA_messages.strProcessingRequest); + + var params = getJSConfirmCommonParam(this, $link.getPostData()); + + $.post(url, params, function (data) { + if ($('.sqlqueryresults').length !== 0) { + $('.sqlqueryresults').remove(); + } + if ($('.result_query').length !== 0) { + $('.result_query').remove(); + } + if (typeof data !== 'undefined' && data.success === true) { + PMA_ajaxShowMessage(data.message); + $('
        ').prependTo('#page_content'); + $('.sqlqueryresults').html(data.sql_query); + PMA_highlightSQL($('#page_content')); + } else { + PMA_ajaxShowMessage(data.error, false); + } + }); // end $.post() + }, loadForeignKeyCheckbox); // end $.PMA_confirm() + }); // end of Truncate Table Ajax action +}); // end $(document).ready for 'Table operations' diff --git a/php/apps/phpmyadmin49/js/tbl_relation.js b/php/apps/phpmyadmin49/js/tbl_relation.js new file mode 100644 index 00000000..ffc0aff4 --- /dev/null +++ b/php/apps/phpmyadmin49/js/tbl_relation.js @@ -0,0 +1,243 @@ +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * for tbl_relation.php + * + */ +function show_hide_clauses ($thisDropdown) { + if ($thisDropdown.val() === '') { + $thisDropdown.parent().nextAll('span').hide(); + } else { + if ($thisDropdown.is('select[name^="destination_foreign_column"]')) { + $thisDropdown.parent().nextAll('span').show(); + } + } +} + +/** + * Sets dropdown options to values + */ +function setDropdownValues ($dropdown, values, selectedValue) { + $dropdown.empty(); + var optionsAsString = ''; + // add an empty string to the beginning for empty selection + values.unshift(''); + $.each(values, function () { + optionsAsString += ''; + }); + $dropdown.append($(optionsAsString)); +} + +/** + * Retrieves and populates dropdowns to the left based on the selected value + * + * @param $dropdown the dropdown whose value got changed + */ +function getDropdownValues ($dropdown) { + var foreignDb = null; + var foreignTable = null; + var $databaseDd; + var $tableDd; + var $columnDd; + var foreign = ''; + // if the changed dropdown is for foreign key constraints + if ($dropdown.is('select[name^="destination_foreign"]')) { + $databaseDd = $dropdown.parent().parent().parent().find('select[name^="destination_foreign_db"]'); + $tableDd = $dropdown.parent().parent().parent().find('select[name^="destination_foreign_table"]'); + $columnDd = $dropdown.parent().parent().parent().find('select[name^="destination_foreign_column"]'); + foreign = '_foreign'; + } else { // internal relations + $databaseDd = $dropdown.parent().find('select[name^="destination_db"]'); + $tableDd = $dropdown.parent().find('select[name^="destination_table"]'); + $columnDd = $dropdown.parent().find('select[name^="destination_column"]'); + } + + // if the changed dropdown is a database selector + if ($dropdown.is('select[name^="destination' + foreign + '_db"]')) { + foreignDb = $dropdown.val(); + // if no database is selected empty table and column dropdowns + if (foreignDb === '') { + setDropdownValues($tableDd, []); + setDropdownValues($columnDd, []); + return; + } + } else { // if a table selector + foreignDb = $databaseDd.val(); + foreignTable = $dropdown.val(); + // if no table is selected empty the column dropdown + if (foreignTable === '') { + setDropdownValues($columnDd, []); + return; + } + } + var $msgbox = PMA_ajaxShowMessage(); + var $form = $dropdown.parents('form'); + var argsep = PMA_commonParams.get('arg_separator'); + var params = 'getDropdownValues=true' + argsep + 'ajax_request=true' + + argsep + 'db=' + $form.find('input[name="db"]').val() + + argsep + 'table=' + $form.find('input[name="table"]').val() + + argsep + 'foreign=' + (foreign !== '') + + argsep + 'foreignDb=' + encodeURIComponent(foreignDb) + + (foreignTable !== null ? + argsep + 'foreignTable=' + encodeURIComponent(foreignTable) : '' + ); + var $server = $form.find('input[name="server"]'); + if ($server.length > 0) { + params += argsep + 'server=' + $form.find('input[name="server"]').val(); + } + $.ajax({ + type: 'POST', + url: 'tbl_relation.php', + data: params, + dataType: 'json', + success: function (data) { + PMA_ajaxRemoveMessage($msgbox); + if (typeof data !== 'undefined' && data.success) { + // if the changed dropdown is a database selector + if (foreignTable === null) { + // set values for table and column dropdowns + setDropdownValues($tableDd, data.tables); + setDropdownValues($columnDd, []); + } else { // if a table selector + // set values for the column dropdown + var primary = null; + if (typeof data.primary !== 'undefined' + && 1 === data.primary.length + ) { + primary = data.primary[0]; + } + setDropdownValues($columnDd.first(), data.columns, primary); + setDropdownValues($columnDd.slice(1), data.columns); + } + } else { + PMA_ajaxShowMessage(data.error, false); + } + } + }); +} + +/** + * Unbind all event handlers before tearing down a page + */ +AJAX.registerTeardown('tbl_relation.js', function () { + $('body').off('change', + 'select[name^="destination_db"], ' + + 'select[name^="destination_table"], ' + + 'select[name^="destination_foreign_db"], ' + + 'select[name^="destination_foreign_table"]' + ); + $('body').off('click', 'a.add_foreign_key_field'); + $('body').off('click', 'a.add_foreign_key'); + $('a.drop_foreign_key_anchor.ajax').off('click'); +}); + +AJAX.registerOnload('tbl_relation.js', function () { + /** + * Ajax event handler to fetch table/column dropdown values. + */ + $('body').on('change', + 'select[name^="destination_db"], ' + + 'select[name^="destination_table"], ' + + 'select[name^="destination_foreign_db"], ' + + 'select[name^="destination_foreign_table"]', + function () { + getDropdownValues($(this)); + } + ); + + /** + * Ajax event handler to add a column to a foreign key constraint. + */ + $('body').on('click', 'a.add_foreign_key_field', function (event) { + event.preventDefault(); + event.stopPropagation(); + + // Add field. + $(this) + .prev('span') + .clone(true, true) + .insertBefore($(this)) + .find('select') + .val(''); + + // Add foreign field. + var $source_elem = $('select[name^="destination_foreign_column[' + + $(this).attr('data-index') + ']"]:last').parent(); + $source_elem + .clone(true, true) + .insertAfter($source_elem) + .find('select') + .val(''); + }); + + /** + * Ajax event handler to add a foreign key constraint. + */ + $('body').on('click', 'a.add_foreign_key', function (event) { + event.preventDefault(); + event.stopPropagation(); + + var $prev_row = $(this).closest('tr').prev('tr'); + var $newRow = $prev_row.clone(true, true); + + // Update serial number. + var curr_index = $newRow + .find('a.add_foreign_key_field') + .attr('data-index'); + var new_index = parseInt(curr_index) + 1; + $newRow.find('a.add_foreign_key_field').attr('data-index', new_index); + + // Update form parameter names. + $newRow.find('select[name^="foreign_key_fields_name"]:not(:first), ' + + 'select[name^="destination_foreign_column"]:not(:first)' + ).each(function () { + $(this).parent().remove(); + }); + $newRow.find('input, select').each(function () { + $(this).attr('name', + $(this).attr('name').replace(/\d/, new_index) + ); + }); + $newRow.find('input[type="text"]').each(function () { + $(this).val(''); + }); + // Finally add the row. + $newRow.insertAfter($prev_row); + }); + + /** + * Ajax Event handler for 'Drop Foreign key' + */ + $('a.drop_foreign_key_anchor.ajax').on('click', function (event) { + event.preventDefault(); + var $anchor = $(this); + + // Object containing reference to the current field's row + var $curr_row = $anchor.parents('tr'); + + var drop_query = escapeHtml( + $curr_row.children('td') + .children('.drop_foreign_key_msg') + .val() + ); + + var question = PMA_sprintf(PMA_messages.strDoYouReally, drop_query); + + $anchor.PMA_confirm(question, $anchor.attr('href'), function (url) { + var $msg = PMA_ajaxShowMessage(PMA_messages.strDroppingForeignKey, false); + var params = getJSConfirmCommonParam(this, $anchor.getPostData()); + $.post(url, params, function (data) { + if (data.success === true) { + PMA_ajaxRemoveMessage($msg); + PMA_commonActions.refreshMain(false, function () { + // Do nothing + }); + } else { + PMA_ajaxShowMessage(PMA_messages.strErrorProcessingRequest + ' : ' + data.error, false); + } + }); // end $.post() + }); // end $.PMA_confirm() + }); // end Drop Foreign key + + var windowwidth = $(window).width(); + $('.jsresponsive').css('max-width', (windowwidth - 35) + 'px'); +}); diff --git a/php/apps/phpmyadmin49/js/tbl_select.js b/php/apps/phpmyadmin49/js/tbl_select.js new file mode 100644 index 00000000..1e44db69 --- /dev/null +++ b/php/apps/phpmyadmin49/js/tbl_select.js @@ -0,0 +1,413 @@ +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * @fileoverview JavaScript functions used on tbl_select.php + * + * @requires jQuery + * @requires js/functions.js + */ + +/** + * Ajax event handlers for this page + * + * Actions ajaxified here: + * Table search + */ + +/** + * Checks if given data-type is numeric or date. + * + * @param string data_type Column data-type + * + * @return bool|string + */ +function PMA_checkIfDataTypeNumericOrDate (data_type) { + // To test for numeric data-types. + var numeric_re = new RegExp( + 'TINYINT|SMALLINT|MEDIUMINT|INT|BIGINT|DECIMAL|FLOAT|DOUBLE|REAL', + 'i' + ); + + // To test for date data-types. + var date_re = new RegExp( + 'DATETIME|DATE|TIMESTAMP|TIME|YEAR', + 'i' + ); + + // Return matched data-type + if (numeric_re.test(data_type)) { + return numeric_re.exec(data_type)[0]; + } + + if (date_re.test(data_type)) { + return date_re.exec(data_type)[0]; + } + + return false; +} + +/** + * Unbind all event handlers before tearing down a page + */ +AJAX.registerTeardown('tbl_select.js', function () { + $('#togglesearchformlink').off('click'); + $(document).off('submit', '#tbl_search_form.ajax'); + $('select.geom_func').off('change'); + $(document).off('click', 'span.open_search_gis_editor'); + $('body').off('change', 'select[name*="criteriaColumnOperators"]'); // Fix for bug #13778, changed 'click' to 'change' +}); + +AJAX.registerOnload('tbl_select.js', function () { + /** + * Prepare a div containing a link, otherwise it's incorrectly displayed + * after a couple of clicks + */ + $('
        ') + .insertAfter('#tbl_search_form') + // don't show it until we have results on-screen + .hide(); + + $('#togglesearchformlink') + .html(PMA_messages.strShowSearchCriteria) + .on('click', function () { + var $link = $(this); + $('#tbl_search_form').slideToggle(); + if ($link.text() === PMA_messages.strHideSearchCriteria) { + $link.text(PMA_messages.strShowSearchCriteria); + } else { + $link.text(PMA_messages.strHideSearchCriteria); + } + // avoid default click action + return false; + }); + + var tableRows = $('#fieldset_table_qbe select'); + $.each(tableRows, function (index, item) { + $(item).on('change', function () { + changeValueFieldType(this, index); + }); + }); + + /** + * Ajax event handler for Table search + */ + $(document).on('submit', '#tbl_search_form.ajax', function (event) { + var unaryFunctions = [ + 'IS NULL', + 'IS NOT NULL', + '= \'\'', + '!= \'\'' + ]; + + var geomUnaryFunctions = [ + 'IsEmpty', + 'IsSimple', + 'IsRing', + 'IsClosed', + ]; + + // jQuery object to reuse + var $search_form = $(this); + event.preventDefault(); + + // empty previous search results while we are waiting for new results + $('#sqlqueryresultsouter').empty(); + var $msgbox = PMA_ajaxShowMessage(PMA_messages.strSearching, false); + + PMA_prepareForAjaxRequest($search_form); + + var values = {}; + $search_form.find(':input').each(function () { + var $input = $(this); + if ($input.attr('type') === 'checkbox' || $input.attr('type') === 'radio') { + if ($input.is(':checked')) { + values[this.name] = $input.val(); + } + } else { + values[this.name] = $input.val(); + } + }); + var columnCount = $('select[name="columnsToDisplay[]"] option').length; + // Submit values only for the columns that have unary column operator or a search criteria + for (var a = 0; a < columnCount; a++) { + if ($.inArray(values['criteriaColumnOperators[' + a + ']'], unaryFunctions) >= 0) { + continue; + } + + if (values['geom_func[' + a + ']'] && + $.isArray(values['geom_func[' + a + ']'], geomUnaryFunctions) >= 0) { + continue; + } + + if (values['criteriaValues[' + a + ']'] === '' || values['criteriaValues[' + a + ']'] === null) { + delete values['criteriaValues[' + a + ']']; + delete values['criteriaColumnOperators[' + a + ']']; + delete values['criteriaColumnNames[' + a + ']']; + delete values['criteriaColumnTypes[' + a + ']']; + delete values['criteriaColumnCollations[' + a + ']']; + } + } + // If all columns are selected, use a single parameter to indicate that + if (values['columnsToDisplay[]'] !== null) { + if (values['columnsToDisplay[]'].length === columnCount) { + delete values['columnsToDisplay[]']; + values.displayAllColumns = true; + } + } else { + values.displayAllColumns = true; + } + + $.post($search_form.attr('action'), values, function (data) { + PMA_ajaxRemoveMessage($msgbox); + if (typeof data !== 'undefined' && data.success === true) { + if (typeof data.sql_query !== 'undefined') { // zero rows + $('#sqlqueryresultsouter').html(data.sql_query); + } else { // results found + $('#sqlqueryresultsouter').html(data.message); + $('.sqlqueryresults').trigger('makegrid').trigger('stickycolumns'); + } + $('#tbl_search_form') + // workaround for bug #3168569 - Issue on toggling the "Hide search criteria" in chrome. + .slideToggle() + .hide(); + $('#togglesearchformlink') + // always start with the Show message + .text(PMA_messages.strShowSearchCriteria); + $('#togglesearchformdiv') + // now it's time to show the div containing the link + .show(); + // needed for the display options slider in the results + PMA_init_slider(); + $('html, body').animate({ scrollTop: 0 }, 'fast'); + } else { + $('#sqlqueryresultsouter').html(data.error); + } + PMA_highlightSQL($('#sqlqueryresultsouter')); + }); // end $.post() + }); + + // Following section is related to the 'function based search' for geometry data types. + // Initialy hide all the open_gis_editor spans + $('span.open_search_gis_editor').hide(); + + $('select.geom_func').bind('change', function () { + var $geomFuncSelector = $(this); + + var binaryFunctions = [ + 'Contains', + 'Crosses', + 'Disjoint', + 'Equals', + 'Intersects', + 'Overlaps', + 'Touches', + 'Within', + 'MBRContains', + 'MBRDisjoint', + 'MBREquals', + 'MBRIntersects', + 'MBROverlaps', + 'MBRTouches', + 'MBRWithin', + 'ST_Contains', + 'ST_Crosses', + 'ST_Disjoint', + 'ST_Equals', + 'ST_Intersects', + 'ST_Overlaps', + 'ST_Touches', + 'ST_Within' + ]; + + var tempArray = [ + 'Envelope', + 'EndPoint', + 'StartPoint', + 'ExteriorRing', + 'Centroid', + 'PointOnSurface' + ]; + var outputGeomFunctions = binaryFunctions.concat(tempArray); + + // If the chosen function takes two geometry objects as parameters + var $operator = $geomFuncSelector.parents('tr').find('td:nth-child(5)').find('select'); + if ($.inArray($geomFuncSelector.val(), binaryFunctions) >= 0) { + $operator.prop('readonly', true); + } else { + $operator.prop('readonly', false); + } + + // if the chosen function's output is a geometry, enable GIS editor + var $editorSpan = $geomFuncSelector.parents('tr').find('span.open_search_gis_editor'); + if ($.inArray($geomFuncSelector.val(), outputGeomFunctions) >= 0) { + $editorSpan.show(); + } else { + $editorSpan.hide(); + } + }); + + $(document).on('click', 'span.open_search_gis_editor', function (event) { + event.preventDefault(); + + var $span = $(this); + // Current value + var value = $span.parent('td').children('input[type=\'text\']').val(); + // Field name + var field = 'Parameter'; + // Column type + var geom_func = $span.parents('tr').find('.geom_func').val(); + var type; + if (geom_func === 'Envelope') { + type = 'polygon'; + } else if (geom_func === 'ExteriorRing') { + type = 'linestring'; + } else { + type = 'point'; + } + // Names of input field and null checkbox + var input_name = $span.parent('td').children('input[type=\'text\']').attr('name'); + // Token + + openGISEditor(); + if (!gisEditorLoaded) { + loadJSAndGISEditor(value, field, type, input_name); + } else { + loadGISEditor(value, field, type, input_name); + } + }); + + /** + * Ajax event handler for Range-Search. + */ + $('body').on('change', 'select[name*="criteriaColumnOperators"]', function () { // Fix for bug #13778, changed 'click' to 'change' + $source_select = $(this); + // Get the column name. + var column_name = $(this) + .closest('tr') + .find('th:first') + .text(); + + // Get the data-type of column excluding size. + var data_type = $(this) + .closest('tr') + .find('td[data-type]') + .attr('data-type'); + data_type = PMA_checkIfDataTypeNumericOrDate(data_type); + + // Get the operator. + var operator = $(this).val(); + + if ((operator === 'BETWEEN' || operator === 'NOT BETWEEN') + && data_type + ) { + var $msgbox = PMA_ajaxShowMessage(); + $.ajax({ + url: 'tbl_select.php', + type: 'POST', + data: { + server: PMA_commonParams.get('server'), + ajax_request: 1, + db: $('input[name="db"]').val(), + table: $('input[name="table"]').val(), + column: column_name, + range_search: 1 + }, + success: function (response) { + PMA_ajaxRemoveMessage($msgbox); + if (response.success) { + // Get the column min value. + var min = response.column_data.min + ? '(' + PMA_messages.strColumnMin + + ' ' + response.column_data.min + ')' + : ''; + // Get the column max value. + var max = response.column_data.max + ? '(' + PMA_messages.strColumnMax + + ' ' + response.column_data.max + ')' + : ''; + var button_options = {}; + button_options[PMA_messages.strGo] = function () { + var min_value = $('#min_value').val(); + var max_value = $('#max_value').val(); + var final_value = ''; + if (min_value.length && max_value.length) { + final_value = min_value + ', ' + + max_value; + } + var $target_field = $source_select.closest('tr') + .find('[name*="criteriaValues"]'); + + // If target field is a select list. + if ($target_field.is('select')) { + $target_field.val(final_value); + var $options = $target_field.find('option'); + var $closest_min = null; + var $closest_max = null; + // Find closest min and max value. + $options.each(function () { + if ( + $closest_min === null + || Math.abs($(this).val() - min_value) < Math.abs($closest_min.val() - min_value) + ) { + $closest_min = $(this); + } + + if ( + $closest_max === null + || Math.abs($(this).val() - max_value) < Math.abs($closest_max.val() - max_value) + ) { + $closest_max = $(this); + } + }); + + $closest_min.attr('selected', 'selected'); + $closest_max.attr('selected', 'selected'); + } else { + $target_field.val(final_value); + } + $(this).dialog('close'); + }; + button_options[PMA_messages.strCancel] = function () { + $(this).dialog('close'); + }; + + // Display dialog box. + $('
        ').append( + '
        ' + + '' + operator + '' + + '' + + '' + '
        ' + + '' + min + '' + '
        ' + + '' + + '' + '
        ' + + '' + max + '' + + '
        ' + ).dialog({ + minWidth: 500, + maxHeight: 400, + modal: true, + buttons: button_options, + title: PMA_messages.strRangeSearch, + open: function () { + // Add datepicker wherever required. + PMA_addDatepicker($('#min_value'), data_type); + PMA_addDatepicker($('#max_value'), data_type); + }, + close: function () { + $(this).remove(); + } + }); + } else { + PMA_ajaxShowMessage(response.error); + } + }, + error: function (response) { + PMA_ajaxShowMessage(PMA_messages.strErrorProcessingRequest); + } + }); + } + }); + var windowwidth = $(window).width(); + $('.jsresponsive').css('max-width', (windowwidth - 69) + 'px'); +}); diff --git a/php/apps/phpmyadmin49/js/tbl_structure.js b/php/apps/phpmyadmin49/js/tbl_structure.js new file mode 100644 index 00000000..3f59c245 --- /dev/null +++ b/php/apps/phpmyadmin49/js/tbl_structure.js @@ -0,0 +1,500 @@ +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * @fileoverview functions used on the table structure page + * @name Table Structure + * + * @requires jQuery + * @requires jQueryUI + * @required js/functions.js + */ + +/** + * AJAX scripts for tbl_structure.php + * + * Actions ajaxified here: + * Drop Column + * Add Primary Key + * Drop Primary Key/Index + * + */ + +/** + * Reload fields table + */ +function reloadFieldForm () { + $.post($('#fieldsForm').attr('action'), $('#fieldsForm').serialize() + PMA_commonParams.get('arg_separator') + 'ajax_request=true', function (form_data) { + var $temp_div = $('
        ').append(form_data.message); + $('#fieldsForm').replaceWith($temp_div.find('#fieldsForm')); + $('#addColumns').replaceWith($temp_div.find('#addColumns')); + $('#move_columns_dialog').find('ul').replaceWith($temp_div.find('#move_columns_dialog ul')); + $('#moveColumns').removeClass('move-active'); + }); + $('#page_content').show(); +} + +function checkFirst () { + if ($('select[name=after_field] option:selected').data('pos') === 'first') { + $('input[name=field_where]').val('first'); + } else { + $('input[name=field_where]').val('after'); + } +} +/** + * Unbind all event handlers before tearing down a page + */ +AJAX.registerTeardown('tbl_structure.js', function () { + $(document).off('click', 'a.drop_column_anchor.ajax'); + $(document).off('click', 'a.add_key.ajax'); + $(document).off('click', '#move_columns_anchor'); + $(document).off('click', '#printView'); + $(document).off('submit', '.append_fields_form.ajax'); + $('body').off('click', '#fieldsForm.ajax button[name="submit_mult"], #fieldsForm.ajax input[name="submit_mult"]'); + $(document).off('click', 'a[name^=partition_action].ajax'); + $(document).off('click', '#remove_partitioning.ajax'); +}); + +AJAX.registerOnload('tbl_structure.js', function () { + // Re-initialize variables. + primary_indexes = []; + indexes = []; + fulltext_indexes = []; + spatial_indexes = []; + + /** + *Ajax action for submitting the "Column Change" and "Add Column" form + */ + $('.append_fields_form.ajax').off(); + $(document).on('submit', '.append_fields_form.ajax', function (event) { + event.preventDefault(); + /** + * @var the_form object referring to the export form + */ + var $form = $(this); + var field_cnt = $form.find('input[name=orig_num_fields]').val(); + + + function submitForm () { + $msg = PMA_ajaxShowMessage(PMA_messages.strProcessingRequest); + $.post($form.attr('action'), $form.serialize() + PMA_commonParams.get('arg_separator') + 'do_save_data=1', function (data) { + if ($('.sqlqueryresults').length !== 0) { + $('.sqlqueryresults').remove(); + } else if ($('.error:not(.tab)').length !== 0) { + $('.error:not(.tab)').remove(); + } + if (typeof data.success !== 'undefined' && data.success === true) { + $('#page_content') + .empty() + .append(data.message) + .show(); + PMA_highlightSQL($('#page_content')); + $('.result_query .notice').remove(); + reloadFieldForm(); + $form.remove(); + PMA_ajaxRemoveMessage($msg); + PMA_init_slider(); + PMA_reloadNavigation(); + } else { + PMA_ajaxShowMessage(data.error, false); + } + }); // end $.post() + } + + function checkIfConfirmRequired ($form, $field_cnt) { + var i = 0; + var id; + var elm; + var val; + var name_orig; + var elm_orig; + var val_orig; + var checkRequired = false; + for (i = 0; i < field_cnt; i++) { + id = '#field_' + i + '_5'; + elm = $(id); + val = elm.val(); + + name_orig = 'input[name=field_collation_orig\\[' + i + '\\]]'; + elm_orig = $form.find(name_orig); + val_orig = elm_orig.val(); + + if (val && val_orig && val !== val_orig) { + checkRequired = true; + break; + } + } + return checkRequired; + } + + /* + * First validate the form; if there is a problem, avoid submitting it + * + * checkTableEditForm() needs a pure element and not a jQuery object, + * this is why we pass $form[0] as a parameter (the jQuery object + * is actually an array of DOM elements) + */ + if (checkTableEditForm($form[0], field_cnt)) { + // OK, form passed validation step + + PMA_prepareForAjaxRequest($form); + if (PMA_checkReservedWordColumns($form)) { + // User wants to submit the form + + // If Collation is changed, Warn and Confirm + if (checkIfConfirmRequired($form, field_cnt)) { + var question = sprintf( + PMA_messages.strChangeColumnCollation, 'https://wiki.phpmyadmin.net/pma/Garbled_data' + ); + $form.PMA_confirm(question, $form.attr('action'), function (url) { + submitForm(); + }); + } else { + submitForm(); + } + } + } + }); // end change table button "do_save_data" + + /** + * Attach Event Handler for 'Drop Column' + */ + $(document).on('click', 'a.drop_column_anchor.ajax', function (event) { + event.preventDefault(); + /** + * @var curr_table_name String containing the name of the current table + */ + var curr_table_name = $(this).closest('form').find('input[name=table]').val(); + /** + * @var curr_row Object reference to the currently selected row (i.e. field in the table) + */ + var $curr_row = $(this).parents('tr'); + /** + * @var curr_column_name String containing name of the field referred to by {@link curr_row} + */ + var curr_column_name = $curr_row.children('th').children('label').text().trim(); + curr_column_name = escapeHtml(curr_column_name); + /** + * @var $after_field_item Corresponding entry in the 'After' field. + */ + var $after_field_item = $('select[name=\'after_field\'] option[value=\'' + curr_column_name + '\']'); + /** + * @var question String containing the question to be asked for confirmation + */ + var question = PMA_sprintf(PMA_messages.strDoYouReally, 'ALTER TABLE `' + escapeHtml(curr_table_name) + '` DROP `' + escapeHtml(curr_column_name) + '`;'); + var $this_anchor = $(this); + $this_anchor.PMA_confirm(question, $this_anchor.attr('href'), function (url) { + var $msg = PMA_ajaxShowMessage(PMA_messages.strDroppingColumn, false); + var params = getJSConfirmCommonParam(this, $this_anchor.getPostData()); + params += PMA_commonParams.get('arg_separator') + 'ajax_page_request=1'; + $.post(url, params, function (data) { + if (typeof data !== 'undefined' && data.success === true) { + PMA_ajaxRemoveMessage($msg); + if ($('.result_query').length) { + $('.result_query').remove(); + } + if (data.sql_query) { + $('
        ') + .html(data.sql_query) + .prependTo('#structure_content'); + PMA_highlightSQL($('#page_content')); + } + // Adjust the row numbers + for (var $row = $curr_row.next(); $row.length > 0; $row = $row.next()) { + var new_val = parseInt($row.find('td:nth-child(2)').text(), 10) - 1; + $row.find('td:nth-child(2)').text(new_val); + } + $after_field_item.remove(); + $curr_row.hide('medium').remove(); + + // Remove the dropped column from select menu for 'after field' + $('select[name=after_field]').find( + '[value="' + curr_column_name + '"]' + ).remove(); + + // by default select the (new) last option to add new column + // (in case last column is dropped) + $('select[name=after_field] option:last').attr('selected','selected'); + + // refresh table stats + if (data.tableStat) { + $('#tablestatistics').html(data.tableStat); + } + // refresh the list of indexes (comes from sql.php) + $('.index_info').replaceWith(data.indexes_list); + PMA_reloadNavigation(); + } else { + PMA_ajaxShowMessage(PMA_messages.strErrorProcessingRequest + ' : ' + data.error, false); + } + }); // end $.post() + }); // end $.PMA_confirm() + }); // end of Drop Column Anchor action + + /** + * Attach Event Handler for 'Print' link + */ + $(document).on('click', '#printView', function (event) { + event.preventDefault(); + + // Take to preview mode + printPreview(); + }); // end of Print View action + + /** + * Ajax Event handler for adding keys + */ + $(document).on('click', 'a.add_key.ajax', function (event) { + event.preventDefault(); + + var $this = $(this); + var curr_table_name = $this.closest('form').find('input[name=table]').val(); + var curr_column_name = $this.parents('tr').children('th').children('label').text().trim(); + + var add_clause = ''; + if ($this.is('.add_primary_key_anchor')) { + add_clause = 'ADD PRIMARY KEY'; + } else if ($this.is('.add_index_anchor')) { + add_clause = 'ADD INDEX'; + } else if ($this.is('.add_unique_anchor')) { + add_clause = 'ADD UNIQUE'; + } else if ($this.is('.add_spatial_anchor')) { + add_clause = 'ADD SPATIAL'; + } else if ($this.is('.add_fulltext_anchor')) { + add_clause = 'ADD FULLTEXT'; + } + var question = PMA_sprintf(PMA_messages.strDoYouReally, 'ALTER TABLE `' + + escapeHtml(curr_table_name) + '` ' + add_clause + '(`' + escapeHtml(curr_column_name) + '`);'); + + var $this_anchor = $(this); + + $this_anchor.PMA_confirm(question, $this_anchor.attr('href'), function (url) { + PMA_ajaxShowMessage(); + AJAX.source = $this; + + var params = getJSConfirmCommonParam(this, $this_anchor.getPostData()); + params += PMA_commonParams.get('arg_separator') + 'ajax_page_request=1'; + $.post(url, params, AJAX.responseHandler); + }); // end $.PMA_confirm() + }); // end Add key + + /** + * Inline move columns + **/ + $(document).on('click', '#move_columns_anchor', function (e) { + e.preventDefault(); + + if ($(this).hasClass('move-active')) { + return; + } + + /** + * @var button_options Object that stores the options passed to jQueryUI + * dialog + */ + var button_options = {}; + + button_options[PMA_messages.strGo] = function (event) { + event.preventDefault(); + var $msgbox = PMA_ajaxShowMessage(); + var $this = $(this); + var $form = $this.find('form'); + var serialized = $form.serialize(); + + // check if any columns were moved at all + if (serialized === $form.data('serialized-unmoved')) { + PMA_ajaxRemoveMessage($msgbox); + $this.dialog('close'); + return; + } + + $.post($form.prop('action'), serialized + PMA_commonParams.get('arg_separator') + 'ajax_request=true', function (data) { + if (data.success === false) { + PMA_ajaxRemoveMessage($msgbox); + $this + .clone() + .html(data.error) + .dialog({ + title: $(this).prop('title'), + height: 230, + width: 900, + modal: true, + buttons: button_options_error + }); // end dialog options + } else { + // sort the fields table + var $fields_table = $('table#tablestructure tbody'); + // remove all existing rows and remember them + var $rows = $fields_table.find('tr').remove(); + // loop through the correct order + for (var i in data.columns) { + var the_column = data.columns[i]; + var $the_row = $rows + .find('input:checkbox[value=\'' + the_column + '\']') + .closest('tr'); + // append the row for this column to the table + $fields_table.append($the_row); + } + var $firstrow = $fields_table.find('tr').eq(0); + // Adjust the row numbers and colors + for (var $row = $firstrow; $row.length > 0; $row = $row.next()) { + $row + .find('td:nth-child(2)') + .text($row.index() + 1) + .end() + .removeClass('odd even') + .addClass($row.index() % 2 === 0 ? 'odd' : 'even'); + } + PMA_ajaxShowMessage(data.message); + $this.dialog('close'); + } + }); + }; + button_options[PMA_messages.strCancel] = function () { + $(this).dialog('close'); + }; + + var button_options_error = {}; + button_options_error[PMA_messages.strOK] = function () { + $(this).dialog('close').remove(); + }; + + var columns = []; + + $('#tablestructure').find('tbody tr').each(function () { + var col_name = $(this).find('input:checkbox').eq(0).val(); + var hidden_input = $('') + .prop({ + name: 'move_columns[]', + type: 'hidden' + }) + .val(col_name); + columns[columns.length] = $('
      • ') + .addClass('placeholderDrag') + .text(col_name) + .append(hidden_input); + }); + + var col_list = $('#move_columns_dialog').find('ul') + .find('li').remove().end(); + for (var i in columns) { + col_list.append(columns[i]); + } + col_list.sortable({ + axis: 'y', + containment: $('#move_columns_dialog').find('div'), + tolerance: 'pointer' + }).disableSelection(); + var $form = $('#move_columns_dialog').find('form'); + $form.data('serialized-unmoved', $form.serialize()); + + $('#move_columns_dialog').dialog({ + modal: true, + buttons: button_options, + open: function () { + if ($('#move_columns_dialog').parents('.ui-dialog').height() > $(window).height()) { + $('#move_columns_dialog').dialog('option', 'height', $(window).height()); + } + }, + beforeClose: function () { + $('#move_columns_anchor').removeClass('move-active'); + } + }); + }); + + /** + * Handles multi submits in table structure page such as change, browse, drop, primary etc. + */ + $('body').on('click', '#fieldsForm.ajax button[name="submit_mult"], #fieldsForm.ajax input[name="submit_mult"]', function (e) { + e.preventDefault(); + var $button = $(this); + var $form = $button.parents('form'); + var argsep = PMA_commonParams.get('arg_separator'); + var submitData = $form.serialize() + argsep + 'ajax_request=true' + argsep + 'ajax_page_request=true' + argsep + 'submit_mult=' + $button.val(); + PMA_ajaxShowMessage(); + AJAX.source = $form; + $.post($form.attr('action'), submitData, AJAX.responseHandler); + }); + + /** + * Handles clicks on Action links in partition table + */ + $(document).on('click', 'a[name^=partition_action].ajax', function (e) { + e.preventDefault(); + var $link = $(this); + + function submitPartitionAction (url) { + var params = 'ajax_request=true&ajax_page_request=true&' + $link.getPostData(); + PMA_ajaxShowMessage(); + AJAX.source = $link; + $.post(url, params, AJAX.responseHandler); + } + + if ($link.is('#partition_action_DROP')) { + var question = PMA_messages.strDropPartitionWarning; + $link.PMA_confirm(question, $link.attr('href'), function (url) { + submitPartitionAction(url); + }); + } else if ($link.is('#partition_action_TRUNCATE')) { + var question = PMA_messages.strTruncatePartitionWarning; + $link.PMA_confirm(question, $link.attr('href'), function (url) { + submitPartitionAction(url); + }); + } else { + submitPartitionAction($link.attr('href')); + } + }); + + /** + * Handles remove partitioning + */ + $(document).on('click', '#remove_partitioning.ajax', function (e) { + e.preventDefault(); + var $link = $(this); + var question = PMA_messages.strRemovePartitioningWarning; + $link.PMA_confirm(question, $link.attr('href'), function (url) { + var params = getJSConfirmCommonParam({ + 'ajax_request' : true, + 'ajax_page_request' : true + }, $link.getPostData()); + PMA_ajaxShowMessage(); + AJAX.source = $link; + $.post(url, params, AJAX.responseHandler); + }); + }); + + $(document).on('change', 'select[name=after_field]', function () { + checkFirst(); + }); +}); + +/** Handler for "More" dropdown in structure table rows */ +AJAX.registerOnload('tbl_structure.js', function () { + var windowwidth = $(window).width(); + if (windowwidth > 768) { + if (! $('#fieldsForm').hasClass('HideStructureActions')) { + $('.table-structure-actions').width(function () { + var width = 5; + $(this).find('li').each(function () { + width += $(this).outerWidth(true); + }); + return width; + }); + } + } + + $('.jsresponsive').css('max-width', (windowwidth - 35) + 'px'); + var tableRows = $('.central_columns'); + $.each(tableRows, function (index, item) { + if ($(item).hasClass('add_button')) { + $(item).click(function () { + $('input:checkbox').prop('checked', false); + $('#checkbox_row_' + (index + 1)).prop('checked', true); + $('button[value=add_to_central_columns]').click(); + }); + } else { + $(item).click(function () { + $('input:checkbox').prop('checked', false); + $('#checkbox_row_' + (index + 1)).prop('checked', true); + $('button[value=remove_from_central_columns]').click(); + }); + } + }); +}); diff --git a/php/apps/phpmyadmin49/js/tbl_tracking.js b/php/apps/phpmyadmin49/js/tbl_tracking.js new file mode 100644 index 00000000..cd089573 --- /dev/null +++ b/php/apps/phpmyadmin49/js/tbl_tracking.js @@ -0,0 +1,106 @@ +/** + * Unbind all event handlers before tearing down the page + */ +AJAX.registerTeardown('tbl_tracking.js', function () { + $('body').off('click', '#versionsForm.ajax button[name="submit_mult"], #versionsForm.ajax input[name="submit_mult"]'); + $('body').off('click', 'a.delete_version_anchor.ajax'); + $('body').off('click', 'a.delete_entry_anchor.ajax'); +}); + +/** + * Bind event handlers + */ +AJAX.registerOnload('tbl_tracking.js', function () { + $('#versions tr:first th').append($('
        ')); + $('#versions').tablesorter({ + sortList: [[1, 0]], + headers: { + 0: { sorter: false }, + 1: { sorter: 'integer' }, + 5: { sorter: false }, + 6: { sorter: false } + } + }); + + if ($('#ddl_versions tbody tr').length > 0) { + $('#ddl_versions tr:first th').append($('
        ')); + $('#ddl_versions').tablesorter({ + sortList: [[0, 0]], + headers: { + 0: { sorter: 'integer' }, + 3: { sorter: false }, + 4: { sorter: false } + } + }); + } + + if ($('#dml_versions tbody tr').length > 0) { + $('#dml_versions tr:first th').append($('
        ')); + $('#dml_versions').tablesorter({ + sortList: [[0, 0]], + headers: { + 0: { sorter: 'integer' }, + 3: { sorter: false }, + 4: { sorter: false } + } + }); + } + + /** + * Handles multi submit for tracking versions + */ + $('body').on('click', '#versionsForm.ajax button[name="submit_mult"], #versionsForm.ajax input[name="submit_mult"]', function (e) { + e.preventDefault(); + var $button = $(this); + var $form = $button.parent('form'); + var argsep = PMA_commonParams.get('arg_separator'); + var submitData = $form.serialize() + argsep + 'ajax_request=true' + argsep + 'ajax_page_request=true' + argsep + 'submit_mult=' + $button.val(); + + if ($button.val() === 'delete_version') { + var question = PMA_messages.strDeleteTrackingVersionMultiple; + $button.PMA_confirm(question, $form.attr('action'), function (url) { + PMA_ajaxShowMessage(); + AJAX.source = $form; + $.post(url, submitData, AJAX.responseHandler); + }); + } else { + PMA_ajaxShowMessage(); + AJAX.source = $form; + $.post($form.attr('action'), submitData, AJAX.responseHandler); + } + }); + + /** + * Ajax Event handler for 'Delete version' + */ + $('body').on('click', 'a.delete_version_anchor.ajax', function (e) { + e.preventDefault(); + var $anchor = $(this); + var question = PMA_messages.strDeleteTrackingVersion; + $anchor.PMA_confirm(question, $anchor.attr('href'), function (url) { + PMA_ajaxShowMessage(); + AJAX.source = $anchor; + var argSep = PMA_commonParams.get('arg_separator'); + var params = getJSConfirmCommonParam(this, $anchor.getPostData()); + params += argSep + 'ajax_page_request=1'; + $.post(url, params, AJAX.responseHandler); + }); + }); + + /** + * Ajax Event handler for 'Delete tracking report entry' + */ + $('body').on('click', 'a.delete_entry_anchor.ajax', function (e) { + e.preventDefault(); + var $anchor = $(this); + var question = PMA_messages.strDeletingTrackingEntry; + $anchor.PMA_confirm(question, $anchor.attr('href'), function (url) { + PMA_ajaxShowMessage(); + AJAX.source = $anchor; + var argSep = PMA_commonParams.get('arg_separator'); + var params = getJSConfirmCommonParam(this, $anchor.getPostData()); + params += argSep + 'ajax_page_request=1'; + $.post(url, params, AJAX.responseHandler); + }); + }); +}); diff --git a/php/apps/phpmyadmin49/js/tbl_zoom_plot_jqplot.js b/php/apps/phpmyadmin49/js/tbl_zoom_plot_jqplot.js new file mode 100644 index 00000000..897caed6 --- /dev/null +++ b/php/apps/phpmyadmin49/js/tbl_zoom_plot_jqplot.js @@ -0,0 +1,628 @@ +// TODO: change the axis +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + ** @fileoverview JavaScript functions used on tbl_select.php + ** + ** @requires jQuery + ** @requires js/functions.js + **/ + + +/** + ** Display Help/Info + **/ +function displayHelp () { + $('
        ') + .append(PMA_messages.strDisplayHelp) + .appendTo('#page_content') + .dialog({ + width: 450, + height: 'auto', + title: PMA_messages.strHelpTitle + }); + return false; +} + +/** + ** Extend the array object for max function + ** @param array + **/ +Array.max = function (array) { + return Math.max.apply(Math, array); +}; + +/** + ** Extend the array object for min function + ** @param array + **/ +Array.min = function (array) { + return Math.min.apply(Math, array); +}; + +/** + ** Checks if a string contains only numeric value + ** @param n: String (to be checked) + **/ +function isNumeric (n) { + return !isNaN(parseFloat(n)) && isFinite(n); +} + +/** + ** Checks if an object is empty + ** @param n: Object (to be checked) + **/ +function isEmpty (obj) { + var name; + for (name in obj) { + return false; + } + return true; +} + +/** + ** Converts a date/time into timestamp + ** @param val String Date + ** @param type Sring Field type(datetime/timestamp/time/date) + **/ +function getTimeStamp (val, type) { + if (type.toString().search(/datetime/i) !== -1 || + type.toString().search(/timestamp/i) !== -1 + ) { + return $.datepicker.parseDateTime('yy-mm-dd', 'HH:mm:ss', val); + } else if (type.toString().search(/time/i) !== -1) { + return $.datepicker.parseDateTime('yy-mm-dd', 'HH:mm:ss', '1970-01-01 ' + val); + } else if (type.toString().search(/date/i) !== -1) { + return $.datepicker.parseDate('yy-mm-dd', val); + } +} + +/** + ** Classifies the field type into numeric,timeseries or text + ** @param field: field type (as in database structure) + **/ +function getType (field) { + if (field.toString().search(/int/i) !== -1 || + field.toString().search(/decimal/i) !== -1 || + field.toString().search(/year/i) !== -1 + ) { + return 'numeric'; + } else if (field.toString().search(/time/i) !== -1 || + field.toString().search(/date/i) !== -1 + ) { + return 'time'; + } else { + return 'text'; + } +} + +/** + ** Scrolls the view to the display section + **/ +function scrollToChart () { + var x = $('#dataDisplay').offset().top - 100; // 100 provides buffer in viewport + $('html,body').animate({ scrollTop: x }, 500); +} + +/** + * Unbind all event handlers before tearing down a page + */ +AJAX.registerTeardown('tbl_zoom_plot_jqplot.js', function () { + $('#tableid_0').off('change'); + $('#tableid_1').off('change'); + $('#tableid_2').off('change'); + $('#tableid_3').off('change'); + $('#inputFormSubmitId').off('click'); + $('#togglesearchformlink').off('click'); + $(document).off('keydown', '#dataDisplay :input'); + $('button.button-reset').off('click'); + $('div#resizer').off('resizestop'); + $('div#querychart').off('jqplotDataClick'); +}); + +AJAX.registerOnload('tbl_zoom_plot_jqplot.js', function () { + var cursorMode = ($('input[name=\'mode\']:checked').val() === 'edit') ? 'crosshair' : 'pointer'; + var currentChart = null; + var searchedDataKey = null; + var xLabel = $('#tableid_0').val(); + var yLabel = $('#tableid_1').val(); + // will be updated via Ajax + var xType = $('#types_0').val(); + var yType = $('#types_1').val(); + var dataLabel = $('#dataLabel').val(); + var lastX; + var lastY; + var zoomRatio = 1; + + + // Get query result + var searchedData; + try { + searchedData = JSON.parse($('#querydata').html()); + } catch (err) { + searchedData = null; + } + + /** + ** Input form submit on field change + **/ + + // first column choice corresponds to the X axis + $('#tableid_0').change(function () { + // AJAX request for field type, collation, operators, and value field + $.post('tbl_zoom_select.php', { + 'ajax_request' : true, + 'change_tbl_info' : true, + 'server' : PMA_commonParams.get('server'), + 'db' : PMA_commonParams.get('db'), + 'table' : PMA_commonParams.get('table'), + 'field' : $('#tableid_0').val(), + 'it' : 0 + }, function (data) { + $('#tableFieldsId').find('tr:eq(1) td:eq(0)').html(data.field_type); + $('#tableFieldsId').find('tr:eq(1) td:eq(1)').html(data.field_collation); + $('#tableFieldsId').find('tr:eq(1) td:eq(2)').html(data.field_operators); + $('#tableFieldsId').find('tr:eq(1) td:eq(3)').html(data.field_value); + xLabel = $('#tableid_0').val(); + $('#types_0').val(data.field_type); + xType = data.field_type; + $('#collations_0').val(data.field_collations); + addDateTimePicker(); + }); + }); + + // second column choice corresponds to the Y axis + $('#tableid_1').change(function () { + // AJAX request for field type, collation, operators, and value field + $.post('tbl_zoom_select.php', { + 'ajax_request' : true, + 'change_tbl_info' : true, + 'server' : PMA_commonParams.get('server'), + 'db' : PMA_commonParams.get('db'), + 'table' : PMA_commonParams.get('table'), + 'field' : $('#tableid_1').val(), + 'it' : 1 + }, function (data) { + $('#tableFieldsId').find('tr:eq(2) td:eq(0)').html(data.field_type); + $('#tableFieldsId').find('tr:eq(2) td:eq(1)').html(data.field_collation); + $('#tableFieldsId').find('tr:eq(2) td:eq(2)').html(data.field_operators); + $('#tableFieldsId').find('tr:eq(2) td:eq(3)').html(data.field_value); + yLabel = $('#tableid_1').val(); + $('#types_1').val(data.field_type); + yType = data.field_type; + $('#collations_1').val(data.field_collations); + addDateTimePicker(); + }); + }); + + $('#tableid_2').change(function () { + // AJAX request for field type, collation, operators, and value field + $.post('tbl_zoom_select.php', { + 'ajax_request' : true, + 'change_tbl_info' : true, + 'server' : PMA_commonParams.get('server'), + 'db' : PMA_commonParams.get('db'), + 'table' : PMA_commonParams.get('table'), + 'field' : $('#tableid_2').val(), + 'it' : 2 + }, function (data) { + $('#tableFieldsId').find('tr:eq(4) td:eq(0)').html(data.field_type); + $('#tableFieldsId').find('tr:eq(4) td:eq(1)').html(data.field_collation); + $('#tableFieldsId').find('tr:eq(4) td:eq(2)').html(data.field_operators); + $('#tableFieldsId').find('tr:eq(4) td:eq(3)').html(data.field_value); + $('#types_2').val(data.field_type); + $('#collations_2').val(data.field_collations); + addDateTimePicker(); + }); + }); + + $('#tableid_3').change(function () { + // AJAX request for field type, collation, operators, and value field + $.post('tbl_zoom_select.php', { + 'ajax_request' : true, + 'change_tbl_info' : true, + 'server' : PMA_commonParams.get('server'), + 'db' : PMA_commonParams.get('db'), + 'table' : PMA_commonParams.get('table'), + 'field' : $('#tableid_3').val(), + 'it' : 3 + }, function (data) { + $('#tableFieldsId').find('tr:eq(5) td:eq(0)').html(data.field_type); + $('#tableFieldsId').find('tr:eq(5) td:eq(1)').html(data.field_collation); + $('#tableFieldsId').find('tr:eq(5) td:eq(2)').html(data.field_operators); + $('#tableFieldsId').find('tr:eq(5) td:eq(3)').html(data.field_value); + $('#types_3').val(data.field_type); + $('#collations_3').val(data.field_collations); + addDateTimePicker(); + }); + }); + + /** + * Input form validation + **/ + $('#inputFormSubmitId').click(function () { + if ($('#tableid_0').get(0).selectedIndex === 0 || $('#tableid_1').get(0).selectedIndex === 0) { + PMA_ajaxShowMessage(PMA_messages.strInputNull); + } else if (xLabel === yLabel) { + PMA_ajaxShowMessage(PMA_messages.strSameInputs); + } + }); + + /** + ** Prepare a div containing a link, otherwise it's incorrectly displayed + ** after a couple of clicks + **/ + $('
        ') + .insertAfter('#zoom_search_form') + // don't show it until we have results on-screen + .hide(); + + $('#togglesearchformlink') + .html(PMA_messages.strShowSearchCriteria) + .bind('click', function () { + var $link = $(this); + $('#zoom_search_form').slideToggle(); + if ($link.text() === PMA_messages.strHideSearchCriteria) { + $link.text(PMA_messages.strShowSearchCriteria); + } else { + $link.text(PMA_messages.strHideSearchCriteria); + } + // avoid default click action + return false; + }); + + /** + ** Set dialog properties for the data display form + **/ + var buttonOptions = {}; + /* + * Handle saving of a row in the editor + */ + buttonOptions[PMA_messages.strSave] = function () { + // Find changed values by comparing form values with selectedRow Object + var newValues = {};// Stores the values changed from original + var sqlTypes = {}; + var it = 0; + var xChange = false; + var yChange = false; + var key; + var tempGetVal = function () { + return $(this).val(); + }; + for (key in selectedRow) { + var oldVal = selectedRow[key]; + var newVal = ($('#edit_fields_null_id_' + it).prop('checked')) ? null : $('#edit_fieldID_' + it).val(); + if (newVal instanceof Array) { // when the column is of type SET + newVal = $('#edit_fieldID_' + it).map(tempGetVal).get().join(','); + } + if (oldVal !== newVal) { + selectedRow[key] = newVal; + newValues[key] = newVal; + if (key === xLabel) { + xChange = true; + searchedData[searchedDataKey][xLabel] = newVal; + } else if (key === yLabel) { + yChange = true; + searchedData[searchedDataKey][yLabel] = newVal; + } + } + var $input = $('#edit_fieldID_' + it); + if ($input.hasClass('bit')) { + sqlTypes[key] = 'bit'; + } else { + sqlTypes[key] = null; + } + it++; + } // End data update + + // Update the chart series and replot + if (xChange || yChange) { + // Logic similar to plot generation, replot only if xAxis changes or yAxis changes. + // Code includes a lot of checks so as to replot only when necessary + if (xChange) { + xCord[searchedDataKey] = selectedRow[xLabel]; + // [searchedDataKey][0] contains the x value + if (xType === 'numeric') { + series[0][searchedDataKey][0] = selectedRow[xLabel]; + } else if (xType === 'time') { + series[0][searchedDataKey][0] = + getTimeStamp(selectedRow[xLabel], $('#types_0').val()); + } else { + series[0][searchedDataKey][0] = ''; + // TODO: text values + } + currentChart.series[0].data = series[0]; + // TODO: axis changing + currentChart.replot(); + } + if (yChange) { + yCord[searchedDataKey] = selectedRow[yLabel]; + // [searchedDataKey][1] contains the y value + if (yType === 'numeric') { + series[0][searchedDataKey][1] = selectedRow[yLabel]; + } else if (yType === 'time') { + series[0][searchedDataKey][1] = + getTimeStamp(selectedRow[yLabel], $('#types_1').val()); + } else { + series[0][searchedDataKey][1] = ''; + // TODO: text values + } + currentChart.series[0].data = series[0]; + // TODO: axis changing + currentChart.replot(); + } + } // End plot update + + // Generate SQL query for update + if (!isEmpty(newValues)) { + var sql_query = 'UPDATE `' + PMA_commonParams.get('table') + '` SET '; + for (key in newValues) { + sql_query += '`' + key + '`='; + var value = newValues[key]; + + // null + if (value === null) { + sql_query += 'NULL, '; + + // empty + } else if ($.trim(value) === '') { + sql_query += '\'\', '; + + // other + } else { + // type explicitly identified + if (sqlTypes[key] !== null) { + if (sqlTypes[key] === 'bit') { + sql_query += 'b\'' + value + '\', '; + } + // type not explicitly identified + } else { + if (!isNumeric(value)) { + sql_query += '\'' + value + '\', '; + } else { + sql_query += value + ', '; + } + } + } + } + // remove two extraneous characters ', ' + sql_query = sql_query.substring(0, sql_query.length - 2); + sql_query += ' WHERE ' + PMA_urldecode(searchedData[searchedDataKey].where_clause); + + // Post SQL query to sql.php + $.post('sql.php', { + 'server' : PMA_commonParams.get('server'), + 'db' : PMA_commonParams.get('db'), + 'ajax_request' : true, + 'sql_query' : sql_query, + 'inline_edit' : false + }, function (data) { + if (typeof data !== 'undefined' && data.success === true) { + $('#sqlqueryresultsouter').html(data.sql_query); + PMA_highlightSQL($('#sqlqueryresultsouter')); + } else { + PMA_ajaxShowMessage(data.error, false); + } + }); // End $.post + }// End database update + $('#dataDisplay').dialog('close'); + }; + buttonOptions[PMA_messages.strCancel] = function () { + $(this).dialog('close'); + }; + $('#dataDisplay').dialog({ + autoOpen: false, + title: PMA_messages.strDataPointContent, + modal: true, + buttons: buttonOptions, + width: $('#dataDisplay').width() + 80, + open: function () { + $(this).find('input[type=checkbox]').css('margin', '0.5em'); + } + }); + /** + * Attach Ajax event handlers for input fields + * in the dialog. Used to submit the Ajax + * request when the ENTER key is pressed. + */ + $(document).on('keydown', '#dataDisplay :input', function (e) { + if (e.which === 13) { // 13 is the ENTER key + e.preventDefault(); + if (typeof buttonOptions[PMA_messages.strSave] === 'function') { + buttonOptions[PMA_messages.strSave].call(); + } + } + }); + + + /* + * Generate plot using jqplot + */ + + if (searchedData !== null) { + $('#zoom_search_form') + .slideToggle() + .hide(); + $('#togglesearchformlink') + .text(PMA_messages.strShowSearchCriteria); + $('#togglesearchformdiv').show(); + var selectedRow; + var colorCodes = ['#FF0000', '#00FFFF', '#0000FF', '#0000A0', '#FF0080', '#800080', '#FFFF00', '#00FF00', '#FF00FF']; + var series = []; + var xCord = []; + var yCord = []; + var tempX; + var tempY; + var it = 0; + var xMax; // xAxis extreme max + var xMin; // xAxis extreme min + var yMax; // yAxis extreme max + var yMin; // yAxis extreme min + var xVal; + var yVal; + var format; + + var options = { + series: [ + // for a scatter plot + { showLine: false } + ], + grid: { + drawBorder: false, + shadow: false, + background: 'rgba(0,0,0,0)' + }, + axes: { + xaxis: { + label: $('#tableid_0').val(), + labelRenderer: $.jqplot.CanvasAxisLabelRenderer + }, + yaxis: { + label: $('#tableid_1').val(), + labelRenderer: $.jqplot.CanvasAxisLabelRenderer + } + }, + highlighter: { + show: true, + tooltipAxes: 'y', + yvalues: 2, + // hide the first y value + formatString: '%s%s' + }, + cursor: { + show: true, + zoom: true, + showTooltip: false + } + }; + + // If data label is not set, do not show tooltips + if (dataLabel === '') { + options.highlighter.show = false; + } + + // Classify types as either numeric,time,text + xType = getType(xType); + yType = getType(yType); + + // could have multiple series but we'll have just one + series[0] = []; + + if (xType === 'time') { + var originalXType = $('#types_0').val(); + if (originalXType === 'date') { + format = '%Y-%m-%d'; + } + // TODO: does not seem to work + // else if (originalXType === 'time') { + // format = '%H:%M'; + // } else { + // format = '%Y-%m-%d %H:%M'; + // } + $.extend(options.axes.xaxis, { + renderer: $.jqplot.DateAxisRenderer, + tickOptions: { + formatString: format + } + }); + } + if (yType === 'time') { + var originalYType = $('#types_1').val(); + if (originalYType === 'date') { + format = '%Y-%m-%d'; + } + $.extend(options.axes.yaxis, { + renderer: $.jqplot.DateAxisRenderer, + tickOptions: { + formatString: format + } + }); + } + + $.each(searchedData, function (key, value) { + if (xType === 'numeric') { + xVal = parseFloat(value[xLabel]); + } + if (xType === 'time') { + xVal = getTimeStamp(value[xLabel], originalXType); + } + if (yType === 'numeric') { + yVal = parseFloat(value[yLabel]); + } + if (yType === 'time') { + yVal = getTimeStamp(value[yLabel], originalYType); + } + series[0].push([ + xVal, + yVal, + // extra Y values + value[dataLabel], // for highlighter + // (may set an undefined value) + value.where_clause, // for click on point + key // key from searchedData + ]); + }); + + // under IE 8, the initial display is mangled; after a manual + // resizing, it's ok + // under IE 9, everything is fine + currentChart = $.jqplot('querychart', series, options); + currentChart.resetZoom(); + + $('button.button-reset').click(function (event) { + event.preventDefault(); + currentChart.resetZoom(); + }); + + $('div#resizer').resizable(); + $('div#resizer').bind('resizestop', function (event, ui) { + // make room so that the handle will still appear + $('div#querychart').height($('div#resizer').height() * 0.96); + $('div#querychart').width($('div#resizer').width() * 0.96); + currentChart.replot({ resetAxes: true }); + }); + + $('div#querychart').bind('jqplotDataClick', + function (event, seriesIndex, pointIndex, data) { + searchedDataKey = data[4]; // key from searchedData (global) + var field_id = 0; + var post_params = { + 'ajax_request' : true, + 'get_data_row' : true, + 'server' : PMA_commonParams.get('server'), + 'db' : PMA_commonParams.get('db'), + 'table' : PMA_commonParams.get('table'), + 'where_clause' : data[3] + }; + + $.post('tbl_zoom_select.php', post_params, function (data) { + // Row is contained in data.row_info, + // now fill the displayResultForm with row values + var key; + for (key in data.row_info) { + var $field = $('#edit_fieldID_' + field_id); + var $field_null = $('#edit_fields_null_id_' + field_id); + if (data.row_info[key] === null) { + $field_null.prop('checked', true); + $field.val(''); + } else { + $field_null.prop('checked', false); + if ($field.attr('multiple')) { // when the column is of type SET + $field.val(data.row_info[key].split(',')); + } else { + $field.val(data.row_info[key]); + } + } + field_id++; + } + selectedRow = data.row_info; + }); + + $('#dataDisplay').dialog('open'); + } + ); + } + + $('#help_dialog').click(function () { + displayHelp(); + }); +}); diff --git a/php/apps/phpmyadmin49/js/transformations/image_upload.js b/php/apps/phpmyadmin49/js/transformations/image_upload.js new file mode 100644 index 00000000..6a08d444 --- /dev/null +++ b/php/apps/phpmyadmin49/js/transformations/image_upload.js @@ -0,0 +1,28 @@ +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * Image upload transformations plugin js + * + * @package PhpMyAdmin + */ + +AJAX.registerOnload('transformations/image_upload.js', function () { + // Change thumbnail when image file is selected + // through file upload dialog + $('input.image-upload').on('change', function (event) { + if (this.files && this.files[0]) { + var reader = new FileReader(); + var $input = $(this); + reader.onload = function (e) { + $input.prevAll('img').attr('src', e.target.result); + }; + reader.readAsDataURL(this.files[0]); + } + }); +}); + +/** + * Unbind all event handlers before tearing down a page + */ +AJAX.registerTeardown('transformations/image_upload.js', function () { + $('input.image-upload').off('change'); +}); diff --git a/php/apps/phpmyadmin49/js/transformations/json.js b/php/apps/phpmyadmin49/js/transformations/json.js new file mode 100644 index 00000000..81ddaf22 --- /dev/null +++ b/php/apps/phpmyadmin49/js/transformations/json.js @@ -0,0 +1,18 @@ +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * JSON syntax highlighting transformation plugin + */ +AJAX.registerOnload('transformations/json.js', function () { + var $elm = $('#page_content').find('code.json'); + $elm.each(function () { + var $json = $(this); + var $pre = $json.find('pre'); + /* We only care about visible elements to avoid double processing */ + if ($pre.is(':visible')) { + var $highlight = $('
        '); + $json.append($highlight); + CodeMirror.runMode($json.text(), 'application/json', $highlight[0]); + $pre.hide(); + } + }); +}); diff --git a/php/apps/phpmyadmin49/js/transformations/json_editor.js b/php/apps/phpmyadmin49/js/transformations/json_editor.js new file mode 100644 index 00000000..affae4be --- /dev/null +++ b/php/apps/phpmyadmin49/js/transformations/json_editor.js @@ -0,0 +1,17 @@ +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * JSON syntax highlighting transformation plugin + * + * @package PhpMyAdmin + */ +AJAX.registerOnload('transformations/json_editor.js', function () { + $('textarea.transform_json_editor').each(function () { + CodeMirror.fromTextArea(this, { + lineNumbers: true, + matchBrackets: true, + indentUnit: 4, + mode: 'application/json', + lineWrapping: true + }); + }); +}); diff --git a/php/apps/phpmyadmin49/js/transformations/sql_editor.js b/php/apps/phpmyadmin49/js/transformations/sql_editor.js new file mode 100644 index 00000000..4149b976 --- /dev/null +++ b/php/apps/phpmyadmin49/js/transformations/sql_editor.js @@ -0,0 +1,11 @@ +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * SQL syntax highlighting transformation plugin js + * + * @package PhpMyAdmin + */ +AJAX.registerOnload('transformations/sql_editor.js', function () { + $('textarea.transform_sql_editor').each(function () { + PMA_getSQLEditor($(this), {}, 'both'); + }); +}); diff --git a/php/apps/phpmyadmin49/js/transformations/xml.js b/php/apps/phpmyadmin49/js/transformations/xml.js new file mode 100644 index 00000000..3fdf152e --- /dev/null +++ b/php/apps/phpmyadmin49/js/transformations/xml.js @@ -0,0 +1,18 @@ +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * XML syntax highlighting transformation plugin + */ +AJAX.registerOnload('transformations/xml.js', function () { + var $elm = $('#page_content').find('code.xml'); + $elm.each(function () { + var $json = $(this); + var $pre = $json.find('pre'); + /* We only care about visible elements to avoid double processing */ + if ($pre.is(':visible')) { + var $highlight = $('
        '); + $json.append($highlight); + CodeMirror.runMode($json.text(), 'application/xml', $highlight[0]); + $pre.hide(); + } + }); +}); diff --git a/php/apps/phpmyadmin49/js/transformations/xml_editor.js b/php/apps/phpmyadmin49/js/transformations/xml_editor.js new file mode 100644 index 00000000..7d2533d1 --- /dev/null +++ b/php/apps/phpmyadmin49/js/transformations/xml_editor.js @@ -0,0 +1,16 @@ +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * XML editor plugin + * + * @package PhpMyAdmin + */ +AJAX.registerOnload('transformations/xml_editor.js', function () { + $('textarea.transform_xml_editor').each(function () { + CodeMirror.fromTextArea(this, { + lineNumbers: true, + indentUnit: 4, + mode: 'application/xml', + lineWrapping: true + }); + }); +}); diff --git a/php/apps/phpmyadmin49/js/u2f.js b/php/apps/phpmyadmin49/js/u2f.js new file mode 100644 index 00000000..47f3e8d7 --- /dev/null +++ b/php/apps/phpmyadmin49/js/u2f.js @@ -0,0 +1,59 @@ +/** global: AJAX */ +/** global: PMA_messages */ +/** global: u2f */ +AJAX.registerOnload('u2f.js', function () { + var $inputReg = $('#u2f_registration_response'); + if ($inputReg.length > 0) { + var $formReg = $inputReg.parents('form'); + $formReg.find('input[type=submit]').hide(); + setTimeout(function () { + // A magic JS function that talks to the USB device. This function will keep polling for the USB device until it finds one. + var request = JSON.parse($inputReg.attr('data-request')); + u2f.register(request.appId, [request], JSON.parse($inputReg.attr('data-signatures')), function (data) { + // Handle returning error data + if (data.errorCode && data.errorCode !== 0) { + if (data.errorCode === 5) { + PMA_ajaxShowMessage(PMA_messages.strU2FTimeout, false); + } else { + PMA_ajaxShowMessage( + PMA_sprintf(PMA_messages.strU2FError, data.errorCode), false + ); + } + return; + } + + // Fill and submit form. + $inputReg.val(JSON.stringify(data)); + $formReg.submit(); + }); + }, 1000); + } + var $inputAuth = $('#u2f_authentication_response'); + if ($inputAuth.length > 0) { + var $formAuth = $inputAuth.parents('form'); + $formAuth.find('input[type=submit]').hide(); + setTimeout(function () { + // Magic JavaScript talking to your HID + // appid, challenge, authenticateRequests + var request = JSON.parse($inputAuth.attr('data-request')); + var handles = [request[0].keyHandle]; + u2f.sign(request[0].appId, request[0].challenge, request, function (data) { + // Handle returning error data + if (data.errorCode && data.errorCode !== 0) { + if (data.errorCode === 5) { + PMA_ajaxShowMessage(PMA_messages.strU2FTimeout, false); + } else { + PMA_ajaxShowMessage( + PMA_sprintf(PMA_messages.strU2FError, data.errorCode), false + ); + } + return; + } + + // Fill and submit form. + $inputAuth.val(JSON.stringify(data)); + $formAuth.submit(); + }); + }, 1000); + } +}); diff --git a/php/apps/phpmyadmin49/js/whitelist.php b/php/apps/phpmyadmin49/js/whitelist.php new file mode 100644 index 00000000..10ba2675 --- /dev/null +++ b/php/apps/phpmyadmin49/js/whitelist.php @@ -0,0 +1,46 @@ +start(); +if (!defined('TESTSUITE')) { + register_shutdown_function( + function () { + echo OutputBuffering::getInstance()->getContents(); + } + ); +} + +echo "var PMA_gotoWhitelist = new Array();\n"; +$i = 0; +foreach (Core::$goto_whitelist as $one_whitelist) { + echo 'PMA_gotoWhitelist[' , $i , ']="' , $one_whitelist , '";' , "\n"; + $i++; +} diff --git a/php/apps/phpmyadmin49/libraries/advisory_rules.txt b/php/apps/phpmyadmin49/libraries/advisory_rules.txt new file mode 100644 index 00000000..46446c0b --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/advisory_rules.txt @@ -0,0 +1,452 @@ +# phpMyAdmin Advisory rules file +# +# Use only UNIX style newlines +# +# This file is being parsed by Advisor.php, which should handle syntax +# errors correctly. However, PHP Warnings and the like are being consumed by +# the phpMyAdmin error handler, so those won't show up E.g.: Justification line +# is empty because you used an unescape percent sign, sprintf() returns an +# empty string and no warning/error is shown +# +# Rule Syntax: +# 'rule' identifier[the name of the rule] eexpr [an optional precondition] +# expr [variable or value calculation used for the test] +# expr [test, if evaluted to 'true' it fires the rule. Use 'value' to insert the calculated value (without quotes)] +# string [the issue (what is the problem?)] +# string [the recommendation (how do i fix it?)] +# formatted-string '|' comma-seperated-expr [the justification (result of the calculated value / why did this rule fire?)] + +# comma-seperated-expr: expr(,expr)* +# eexpr: [expr] - expr enclosed in [] +# expr: a php code literal with extras: +# - variable names are replaced with their respective values +# - fired('name of rule') is replaced with true/false when given rule has +# been fired. Note however that this is a very simple rules engine. +# Rules are only checked in sequential order as they are written down +# here. If given rule has not been checked yet, fired() will always +# evaluate to false +# - 'value' is replaced with the calculated value. If it is a string, it +# will be put within single quotes +# - other than that you may use any php function, initialized variable or +# constant +# +# identifier: A string enclosed in single quotes +# string: A quoteless string, may contain HTML. Variable names enclosed in +# curly braces are replaced with links to directly edit this variable. +# e.g. {tmp_table_size} +# formatted-string: You may use classic php sprintf() string formatting here, +# the arguments must be appended after a trailing pipe (|) as +# mentioned in above syntax percent signs (%) are +# automatically escaped (%%) in the following cases: When +# followed by a space, dot or comma and at the end of the +# line) +# +# Comments start with # +# + +# Queries + +rule 'Uptime below one day' + Uptime + value < 86400 + Uptime is less than 1 day, performance tuning may not be accurate. + To have more accurate averages it is recommended to let the server run for longer than a day before running this analyzer + The uptime is only %s | ADVISOR_timespanFormat(Uptime) + +rule 'Questions below 1,000' + Questions + value < 1000 + Fewer than 1,000 questions have been run against this server. The recommendations may not be accurate. + Let the server run for a longer time until it has executed a greater amount of queries. + Current amount of Questions: %s | Questions + +rule 'Percentage of slow queries' [Questions > 0] + Slow_queries / Questions * 100 + value >= 5 + There is a lot of slow queries compared to the overall amount of Queries. + You might want to increase {long_query_time} or optimize the queries listed in the slow query log + The slow query rate should be below 5%, your value is %s%. | round(value,2) + +rule 'Slow query rate' [Questions > 0] + (Slow_queries / Questions * 100) / Uptime + value * 60 * 60 > 1 + There is a high percentage of slow queries compared to the server uptime. + You might want to increase {long_query_time} or optimize the queries listed in the slow query log + You have a slow query rate of %s per hour, you should have less than 1% per hour. | ADVISOR_bytime(value,2) + +rule 'Long query time' + long_query_time + value >= 10 + {long_query_time} is set to 10 seconds or more, thus only slow queries that take above 10 seconds are logged. + It is suggested to set {long_query_time} to a lower value, depending on your environment. Usually a value of 1-5 seconds is suggested. + long_query_time is currently set to %ds. | value + +rule 'Slow query logging' [PMA_MYSQL_INT_VERSION < 50600] + log_slow_queries + value == 'OFF' + The slow query log is disabled. + Enable slow query logging by setting {log_slow_queries} to 'ON'. This will help troubleshooting badly performing queries. + log_slow_queries is set to 'OFF' + +rule 'Slow query logging' [PMA_MYSQL_INT_VERSION >= 50600] + slow_query_log + value == 'OFF' + The slow query log is disabled. + Enable slow query logging by setting {slow_query_log} to 'ON'. This will help troubleshooting badly performing queries. + slow_query_log is set to 'OFF' + +# +# versions +rule 'Release Series' + version + substr(value,0,2) <= '5.' && substr(value,2,1) < 1 + The MySQL server version less than 5.1. + You should upgrade, as MySQL 5.1 has improved performance, and MySQL 5.5 even more so. + Current version: %s | value + +rule 'Minor Version' [! fired('Release Series')] + version + substr(value,0,2) <= '5.' && substr(value,2,1) <= 1 && substr(value,4,2) < 30 + Version less than 5.1.30 (the first GA release of 5.1). + You should upgrade, as recent versions of MySQL 5.1 have improved performance and MySQL 5.5 even more so. + Current version: %s | value + +rule 'Minor Version' [! fired('Release Series')] + version + substr(value,0,1) == 5 && substr(value,2,1) == 5 && substr(value,4,2) < 8 + Version less than 5.5.8 (the first GA release of 5.5). + You should upgrade, to a stable version of MySQL 5.5. + Current version: %s | value + +rule 'Distribution' + version_comment + preg_match('/source/i',value) + Version is compiled from source, not a MySQL official binary. + If you did not compile from source, you may be using a package modified by a distribution. The MySQL manual only is accurate for official MySQL binaries, not any package distributions (such as RedHat, Debian/Ubuntu etc). + 'source' found in version_comment + +rule 'Distribution' + version_comment + preg_match('/percona/i',value) + The MySQL manual only is accurate for official MySQL binaries. + Percona documentation is at https://www.percona.com/software/documentation/ + 'percona' found in version_comment + +rule 'MySQL Architecture' + system_memory + value > 3072*1024 && !preg_match('/64/',version_compile_machine) && !preg_match('/64/',version_compile_os) + MySQL is not compiled as a 64-bit package. + Your memory capacity is above 3 GiB (assuming the Server is on localhost), so MySQL might not be able to access all of your memory. You might want to consider installing the 64-bit version of MySQL. + Available memory on this host: %s | ADVISOR_formatByteDown(value*1024, 2, 2) + +# +# Query cache + +# Lame: 'ON' == 0 is true, so you need to compare 'ON' == '0' +rule 'Query cache disabled' + query_cache_size + value == 0 || query_cache_type == 'OFF' || query_cache_type == '0' + The query cache is not enabled. + The query cache is known to greatly improve performance if configured correctly. Enable it by setting {query_cache_size} to a 2 digit MiB value and setting {query_cache_type} to 'ON'. Note: If you are using memcached, ignore this recommendation. + query_cache_size is set to 0 or query_cache_type is set to 'OFF' + +rule 'Query caching method' [!fired('Query cache disabled')] + Questions / Uptime + value > 100 + Suboptimal caching method. + You are using the MySQL Query cache with a fairly high traffic database. It might be worth considering to use memcached instead of the MySQL Query cache, especially if you have multiple slaves. + The query cache is enabled and the server receives %d queries per second. This rule fires if there is more than 100 queries per second. | round(value,1) + +rule 'Query cache efficiency (%)' [Com_select + Qcache_hits > 0 && !fired('Query cache disabled')] + Qcache_hits / (Com_select + Qcache_hits) * 100 + value < 20 + Query cache not running efficiently, it has a low hit rate. + Consider increasing {query_cache_limit}. + The current query cache hit rate of %s% is below 20% | round(value,1) + +rule 'Query Cache usage' [!fired('Query cache disabled')] + 100 - Qcache_free_memory / query_cache_size * 100 + value < 80 + Less than 80% of the query cache is being utilized. + This might be caused by {query_cache_limit} being too low. Flushing the query cache might help as well. + The current ratio of free query cache memory to total query cache size is %s%. It should be above 80% | round(value,1) + +rule 'Query cache fragmentation' [!fired('Query cache disabled')] + Qcache_free_blocks / (Qcache_total_blocks / 2) * 100 + value > 20 + The query cache is considerably fragmented. + Severe fragmentation is likely to (further) increase Qcache_lowmem_prunes. This might be caused by many Query cache low memory prunes due to {query_cache_size} being too small. For a immediate but short lived fix you can flush the query cache (might lock the query cache for a long time). Carefully adjusting {query_cache_min_res_unit} to a lower value might help too, e.g. you can set it to the average size of your queries in the cache using this formula: (query_cache_size - qcache_free_memory) / qcache_queries_in_cache + The cache is currently fragmented by %s% , with 100% fragmentation meaning that the query cache is an alternating pattern of free and used blocks. This value should be below 20%. | round(value,1) + +rule 'Query cache low memory prunes' [Qcache_inserts > 0 && !fired('Query cache disabled')] + Qcache_lowmem_prunes / Qcache_inserts * 100 + value > 0.1 + Cached queries are removed due to low query cache memory from the query cache. + You might want to increase {query_cache_size}, however keep in mind that the overhead of maintaining the cache is likely to increase with its size, so do this in small increments and monitor the results. + The ratio of removed queries to inserted queries is %s%. The lower this value is, the better (This rules firing limit: 0.1%) | round(value,1) + +rule 'Query cache max size' [!fired('Query cache disabled')] + query_cache_size + value > 1024 * 1024 * 128 + The query cache size is above 128 MiB. Big query caches may cause significant overhead that is required to maintain the cache. + Depending on your environment, it might be performance increasing to reduce this value. + Current query cache size: %s | ADVISOR_formatByteDown(value, 2, 2) + +rule 'Query cache min result size' [!fired('Query cache disabled')] + query_cache_limit + value == 1024*1024 + The max size of the result set in the query cache is the default of 1 MiB. + Changing {query_cache_limit} (usually by increasing) may increase efficiency. This variable determines the maximum size a query result may have to be inserted into the query cache. If there are many query results above 1 MiB that are well cacheable (many reads, little writes) then increasing {query_cache_limit} will increase efficiency. Whereas in the case of many query results being above 1 MiB that are not very well cacheable (often invalidated due to table updates) increasing {query_cache_limit} might reduce efficiency. + query_cache_limit is set to 1 MiB + +# +# Sorts +rule 'Percentage of sorts that cause temporary tables' [Sort_scan + Sort_range > 0] + Sort_merge_passes / (Sort_scan + Sort_range) * 100 + value > 10 + Too many sorts are causing temporary tables. + Consider increasing {sort_buffer_size} and/or {read_rnd_buffer_size}, depending on your system memory limits. + %s% of all sorts cause temporary tables, this value should be lower than 10%. | round(value,1) + +rule 'Rate of sorts that cause temporary tables' + Sort_merge_passes / Uptime + value * 60 * 60 > 1 + Too many sorts are causing temporary tables. + Consider increasing {sort_buffer_size} and/or {read_rnd_buffer_size}, depending on your system memory limits. + Temporary tables average: %s, this value should be less than 1 per hour. | ADVISOR_bytime(value,2) + +rule 'Sort rows' + Sort_rows / Uptime + value * 60 >= 1 + There are lots of rows being sorted. + While there is nothing wrong with a high amount of row sorting, you might want to make sure that the queries which require a lot of sorting use indexed columns in the ORDER BY clause, as this will result in much faster sorting. + Sorted rows average: %s | ADVISOR_bytime(value,2) + +# Joins, scans +rule 'Rate of joins without indexes' + (Select_range_check + Select_scan + Select_full_join) / Uptime + value * 60 * 60 > 1 + There are too many joins without indexes. + This means that joins are doing full table scans. Adding indexes for the columns being used in the join conditions will greatly speed up table joins. + Table joins average: %s, this value should be less than 1 per hour | ADVISOR_bytime(value,2) + +rule 'Rate of reading first index entry' + Handler_read_first / Uptime + value * 60 * 60 > 1 + The rate of reading the first index entry is high. + This usually indicates frequent full index scans. Full index scans are faster than table scans but require lots of CPU cycles in big tables, if those tables that have or had high volumes of UPDATEs and DELETEs, running 'OPTIMIZE TABLE' might reduce the amount of and/or speed up full index scans. Other than that full index scans can only be reduced by rewriting queries. + Index scans average: %s, this value should be less than 1 per hour | ADVISOR_bytime(value,2) + +rule 'Rate of reading fixed position' + Handler_read_rnd / Uptime + value * 60 * 60 > 1 + The rate of reading data from a fixed position is high. + This indicates that many queries need to sort results and/or do a full table scan, including join queries that do not use indexes. Add indexes where applicable. + Rate of reading fixed position average: %s, this value should be less than 1 per hour | ADVISOR_bytime(value,2) + +rule 'Rate of reading next table row' + Handler_read_rnd_next / Uptime + value * 60 * 60 > 1 + The rate of reading the next table row is high. + This indicates that many queries are doing full table scans. Add indexes where applicable. + Rate of reading next table row: %s, this value should be less than 1 per hour | ADVISOR_bytime(value,2) + +# temp tables +rule 'Different tmp_table_size and max_heap_table_size' + tmp_table_size - max_heap_table_size + value !=0 + {tmp_table_size} and {max_heap_table_size} are not the same. + If you have deliberately changed one of either: The server uses the lower value of either to determine the maximum size of in-memory tables. So if you wish to increase the in-memory table limit you will have to increase the other value as well. + Current values are tmp_table_size: %s, max_heap_table_size: %s | ADVISOR_formatByteDown(tmp_table_size, 2, 2), ADVISOR_formatByteDown(max_heap_table_size, 2, 2) + +rule 'Percentage of temp tables on disk' [Created_tmp_tables + Created_tmp_disk_tables > 0] + Created_tmp_disk_tables / (Created_tmp_tables + Created_tmp_disk_tables) * 100 + value > 25 + Many temporary tables are being written to disk instead of being kept in memory. + Increasing {max_heap_table_size} and {tmp_table_size} might help. However some temporary tables are always being written to disk, independent of the value of these variables. To eliminate these you will have to rewrite your queries to avoid those conditions (Within a temporary table: Presence of a BLOB or TEXT column or presence of a column bigger than 512 bytes) as mentioned in the beginning of an Article by the Pythian Group + %s% of all temporary tables are being written to disk, this value should be below 25% | round(value,1) + +rule 'Temp disk rate' [!fired('Percentage of temp tables on disk')] + Created_tmp_disk_tables / Uptime + value * 60 * 60 > 1 + Many temporary tables are being written to disk instead of being kept in memory. + Increasing {max_heap_table_size} and {tmp_table_size} might help. However some temporary tables are always being written to disk, independent of the value of these variables. To eliminate these you will have to rewrite your queries to avoid those conditions (Within a temporary table: Presence of a BLOB or TEXT column or presence of a column bigger than 512 bytes) as mentioned in the MySQL Documentation + Rate of temporary tables being written to disk: %s, this value should be less than 1 per hour | ADVISOR_bytime(value,2) + +# +# MyISAM index cache +rule 'MyISAM key buffer size' + key_buffer_size + value == 0 + Key buffer is not initialized. No MyISAM indexes will be cached. + Set {key_buffer_size} depending on the size of your MyISAM indexes. 64M is a good start. + key_buffer_size is 0 + +rule 'Max % MyISAM key buffer ever used' [key_buffer_size > 0] + Key_blocks_used * key_cache_block_size / key_buffer_size * 100 + value < 95 + MyISAM key buffer (index cache) % used is low. + You may need to decrease the size of {key_buffer_size}, re-examine your tables to see if indexes have been removed, or examine queries and expectations about what indexes are being used. + max % MyISAM key buffer ever used: %s%, this value should be above 95% | round(value,1) + +# Don't fire if above rule fired - we don't need the same advice twice +rule 'Percentage of MyISAM key buffer used' [key_buffer_size > 0 && !fired('Max % MyISAM key buffer ever used')] + ( 1 - Key_blocks_unused * key_cache_block_size / key_buffer_size) * 100 + value < 95 + MyISAM key buffer (index cache) % used is low. + You may need to decrease the size of {key_buffer_size}, re-examine your tables to see if indexes have been removed, or examine queries and expectations about what indexes are being used. + % MyISAM key buffer used: %s%, this value should be above 95% | round(value,1) + +rule 'Percentage of index reads from memory' [Key_read_requests > 0] + 100 - (Key_reads / Key_read_requests * 100) + value < 95 + The % of indexes that use the MyISAM key buffer is low. + You may need to increase {key_buffer_size}. + Index reads from memory: %s%, this value should be above 95% | round(value,1) + +# +# other caches +rule 'Rate of table open' + Opened_tables / Uptime + value*60*60 > 10 + The rate of opening tables is high. + Opening tables requires disk I/O which is costly. Increasing {table_open_cache} might avoid this. + Opened table rate: %s, this value should be less than 10 per hour | ADVISOR_bytime(value,2) + +rule 'Percentage of used open files limit' + Open_files / open_files_limit * 100 + value > 85 + The number of open files is approaching the max number of open files. You may get a "Too many open files" error. + Consider increasing {open_files_limit}, and check the error log when restarting after changing {open_files_limit}. + The number of opened files is at %s% of the limit. It should be below 85% | round(value,1) + +rule 'Rate of open files' + Open_files / Uptime + value * 60 * 60 > 5 + The rate of opening files is high. + Consider increasing {open_files_limit}, and check the error log when restarting after changing {open_files_limit}. + Opened files rate: %s, this value should be less than 5 per hour | ADVISOR_bytime(value,2) + +rule 'Immediate table locks %' [Table_locks_waited + Table_locks_immediate > 0] + Table_locks_immediate / (Table_locks_waited + Table_locks_immediate) * 100 + value < 95 + Too many table locks were not granted immediately. + Optimize queries and/or use InnoDB to reduce lock wait. + Immediate table locks: %s%, this value should be above 95% | round(value,1) + +rule 'Table lock wait rate' + Table_locks_waited / Uptime + value * 60 * 60 > 1 + Too many table locks were not granted immediately. + Optimize queries and/or use InnoDB to reduce lock wait. + Table lock wait rate: %s, this value should be less than 1 per hour | ADVISOR_bytime(value,2) + +rule 'Thread cache' + thread_cache_size + value < 1 + Thread cache is disabled, resulting in more overhead from new connections to MySQL. + Enable the thread cache by setting {thread_cache_size} > 0. + The thread cache is set to 0 + +rule 'Thread cache hit rate %' [thread_cache_size > 0] + 100 - Threads_created / Connections + value < 80 + Thread cache is not efficient. + Increase {thread_cache_size}. + Thread cache hitrate: %s%, this value should be above 80% | round(value,1) + +rule 'Threads that are slow to launch' [slow_launch_time > 0] + Slow_launch_threads + value > 0 + There are too many threads that are slow to launch. + This generally happens in case of general system overload as it is pretty simple operations. You might want to monitor your system load carefully. + %s thread(s) took longer than %s seconds to start, it should be 0 | value, slow_launch_time + +rule 'Slow launch time' + slow_launch_time + value > 2 + Slow_launch_time is above 2s. + Set {slow_launch_time} to 1s or 2s to correctly count threads that are slow to launch. + slow_launch_time is set to %s | value + +# +#Connections +rule 'Percentage of used connections' + Max_used_connections / max_connections * 100 + value > 80 + The maximum amount of used connections is getting close to the value of {max_connections}. + Increase {max_connections}, or decrease {wait_timeout} so that connections that do not close database handlers properly get killed sooner. Make sure the code closes database handlers properly. + Max_used_connections is at %s% of max_connections, it should be below 80% | round(value,1) + +rule 'Percentage of aborted connections' + Aborted_connects / Connections * 100 + value > 1 + Too many connections are aborted. + Connections are usually aborted when they cannot be authorized. This article might help you track down the source. + %s% of all connections are aborted. This value should be below 1% | round(value,1) + +rule 'Rate of aborted connections' + Aborted_connects / Uptime + value * 60 * 60 > 1 + Too many connections are aborted. + Connections are usually aborted when they cannot be authorized. This article might help you track down the source. + Aborted connections rate is at %s, this value should be less than 1 per hour | ADVISOR_bytime(value,2) + +rule 'Percentage of aborted clients' + Aborted_clients / Connections * 100 + value > 2 + Too many clients are aborted. + Clients are usually aborted when they did not close their connection to MySQL properly. This can be due to network issues or code not closing a database handler properly. Check your network and code. + %s% of all clients are aborted. This value should be below 2% | round(value,1) + +rule 'Rate of aborted clients' + Aborted_clients / Uptime + value * 60 * 60 > 1 + Too many clients are aborted. + Clients are usually aborted when they did not close their connection to MySQL properly. This can be due to network issues or code not closing a database handler properly. Check your network and code. + Aborted client rate is at %s, this value should be less than 1 per hour | ADVISOR_bytime(value,2) + +# +# InnoDB +rule 'Is InnoDB disabled?' [PMA_MYSQL_INT_VERSION < 50600] + have_innodb + value != "YES" + You do not have InnoDB enabled. + InnoDB is usually the better choice for table engines. + have_innodb is set to 'value' + +rule 'InnoDB log size' [innodb_buffer_pool_size > 0] + innodb_log_file_size / innodb_buffer_pool_size * 100 + value < 20 && innodb_log_file_size / (1024 * 1024) < 256 + The InnoDB log file size is not an appropriate size, in relation to the InnoDB buffer pool. + Especially on a system with a lot of writes to InnoDB tables you should set {innodb_log_file_size} to 25% of {innodb_buffer_pool_size}. However the bigger this value, the longer the recovery time will be when database crashes, so this value should not be set much higher than 256 MiB. Please note however that you cannot simply change the value of this variable. You need to shutdown the server, remove the InnoDB log files, set the new value in my.cnf, start the server, then check the error logs if everything went fine. See also this blog entry + Your InnoDB log size is at %s% in relation to the InnoDB buffer pool size, it should not be below 20% | round(value,1) + +rule 'Max InnoDB log size' [innodb_buffer_pool_size > 0 && innodb_log_file_size / innodb_buffer_pool_size * 100 < 30] + innodb_log_file_size / (1024 * 1024) + value > 256 + The InnoDB log file size is inadequately large. + It is usually sufficient to set {innodb_log_file_size} to 25% of the size of {innodb_buffer_pool_size}. A very big {innodb_log_file_size} slows down the recovery time after a database crash considerably. See also this Article. You need to shutdown the server, remove the InnoDB log files, set the new value in my.cnf, start the server, then check the error logs if everything went fine. See also this blog entry + Your absolute InnoDB log size is %s MiB | round(value,1) + +rule 'InnoDB buffer pool size' [system_memory > 0] + innodb_buffer_pool_size / system_memory * 100 + value < 60 + Your InnoDB buffer pool is fairly small. + The InnoDB buffer pool has a profound impact on performance for InnoDB tables. Assign all your remaining memory to this buffer. For database servers that use solely InnoDB as storage engine and have no other services (e.g. a web server) running, you may set this as high as 80% of your available memory. If that is not the case, you need to carefully assess the memory consumption of your other services and non-InnoDB-Tables and set this variable accordingly. If it is set too high, your system will start swapping, which decreases performance significantly. See also this article + You are currently using %s% of your memory for the InnoDB buffer pool. This rule fires if you are assigning less than 60%, however this might be perfectly adequate for your system if you don't have much InnoDB tables or other services running on the same machine. | value + +# +# other +rule 'MyISAM concurrent inserts' + concurrent_insert + value === 0 || value === 'NEVER' + Enable {concurrent_insert} by setting it to 1 + Setting {concurrent_insert} to 1 reduces contention between readers and writers for a given table. See also MySQL Documentation + concurrent_insert is set to 0 + +# INSERT DELAYED USAGE +#Delayed_errors 0 +#Delayed_insert_threads 0 +#Delayed_writes 0 +#Not_flushed_delayed_rows diff --git a/php/apps/phpmyadmin49/libraries/certs/12d55845.0 b/php/apps/phpmyadmin49/libraries/certs/12d55845.0 new file mode 100644 index 00000000..b2e43c93 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/certs/12d55845.0 @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDSjCCAjKgAwIBAgIQRK+wgNajJ7qJMDmGLvhAazANBgkqhkiG9w0BAQUFADA/ +MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT +DkRTVCBSb290IENBIFgzMB4XDTAwMDkzMDIxMTIxOVoXDTIxMDkzMDE0MDExNVow +PzEkMCIGA1UEChMbRGlnaXRhbCBTaWduYXR1cmUgVHJ1c3QgQ28uMRcwFQYDVQQD +Ew5EU1QgUm9vdCBDQSBYMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB +AN+v6ZdQCINXtMxiZfaQguzH0yxrMMpb7NnDfcdAwRgUi+DoM3ZJKuM/IUmTrE4O +rz5Iy2Xu/NMhD2XSKtkyj4zl93ewEnu1lcCJo6m67XMuegwGMoOifooUMM0RoOEq +OLl5CjH9UL2AZd+3UWODyOKIYepLYYHsUmu5ouJLGiifSKOeDNoJjj4XLh7dIN9b +xiqKqy69cK3FCxolkHRyxXtqqzTWMIn/5WgTe1QLyNau7Fqckh49ZLOMxt+/yUFw +7BZy1SbsOFU5Q9D8/RhcQPGX69Wam40dutolucbY38EVAjqr2m7xPi71XAicPNaD +aeQQmxkqtilX4+U9m5/wAl0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNV +HQ8BAf8EBAMCAQYwHQYDVR0OBBYEFMSnsaR7LHH62+FLkHX/xBVghYkQMA0GCSqG +SIb3DQEBBQUAA4IBAQCjGiybFwBcqR7uKGY3Or+Dxz9LwwmglSBd49lZRNI+DT69 +ikugdB/OEIKcdBodfpga3csTS7MgROSR6cz8faXbauX+5v3gTt23ADq1cEmv8uXr +AvHRAosZy5Q6XkjEGB5YGV8eAlrwDPGxrancWYaLbumR9YbK+rlmM6pZW87ipxZz +R8srzJmwN0jP41ZL9c8PDHIyh8bwRLtTcm1D9SZImlJnt1ir/md2cXjbDaJWFBM5 +JDGFoqgCWjBH4d1QB7wCCZAA62RjYJsWvIjJEubSfZGL+T0yjWW06XyxV3bqxbYo +Ob8VZRzI9neWagqNdwvYkQsEjgfbKbYK7p2CNTUQ +-----END CERTIFICATE----- diff --git a/php/apps/phpmyadmin49/libraries/certs/2e5ac55d.0 b/php/apps/phpmyadmin49/libraries/certs/2e5ac55d.0 new file mode 100644 index 00000000..b2e43c93 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/certs/2e5ac55d.0 @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDSjCCAjKgAwIBAgIQRK+wgNajJ7qJMDmGLvhAazANBgkqhkiG9w0BAQUFADA/ +MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT +DkRTVCBSb290IENBIFgzMB4XDTAwMDkzMDIxMTIxOVoXDTIxMDkzMDE0MDExNVow +PzEkMCIGA1UEChMbRGlnaXRhbCBTaWduYXR1cmUgVHJ1c3QgQ28uMRcwFQYDVQQD +Ew5EU1QgUm9vdCBDQSBYMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB +AN+v6ZdQCINXtMxiZfaQguzH0yxrMMpb7NnDfcdAwRgUi+DoM3ZJKuM/IUmTrE4O +rz5Iy2Xu/NMhD2XSKtkyj4zl93ewEnu1lcCJo6m67XMuegwGMoOifooUMM0RoOEq +OLl5CjH9UL2AZd+3UWODyOKIYepLYYHsUmu5ouJLGiifSKOeDNoJjj4XLh7dIN9b +xiqKqy69cK3FCxolkHRyxXtqqzTWMIn/5WgTe1QLyNau7Fqckh49ZLOMxt+/yUFw +7BZy1SbsOFU5Q9D8/RhcQPGX69Wam40dutolucbY38EVAjqr2m7xPi71XAicPNaD +aeQQmxkqtilX4+U9m5/wAl0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNV +HQ8BAf8EBAMCAQYwHQYDVR0OBBYEFMSnsaR7LHH62+FLkHX/xBVghYkQMA0GCSqG +SIb3DQEBBQUAA4IBAQCjGiybFwBcqR7uKGY3Or+Dxz9LwwmglSBd49lZRNI+DT69 +ikugdB/OEIKcdBodfpga3csTS7MgROSR6cz8faXbauX+5v3gTt23ADq1cEmv8uXr +AvHRAosZy5Q6XkjEGB5YGV8eAlrwDPGxrancWYaLbumR9YbK+rlmM6pZW87ipxZz +R8srzJmwN0jP41ZL9c8PDHIyh8bwRLtTcm1D9SZImlJnt1ir/md2cXjbDaJWFBM5 +JDGFoqgCWjBH4d1QB7wCCZAA62RjYJsWvIjJEubSfZGL+T0yjWW06XyxV3bqxbYo +Ob8VZRzI9neWagqNdwvYkQsEjgfbKbYK7p2CNTUQ +-----END CERTIFICATE----- diff --git a/php/apps/phpmyadmin49/libraries/certs/4042bcee.0 b/php/apps/phpmyadmin49/libraries/certs/4042bcee.0 new file mode 100644 index 00000000..9548dc1b --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/certs/4042bcee.0 @@ -0,0 +1,31 @@ +-----BEGIN CERTIFICATE----- +MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw +TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh +cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4 +WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu +ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY +MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc +h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+ +0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U +A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW +T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH +B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC +B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv +KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn +OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn +jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw +qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI +rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV +HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq +hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL +ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ +3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK +NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5 +ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur +TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC +jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc +oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq +4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA +mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d +emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc= +-----END CERTIFICATE----- diff --git a/php/apps/phpmyadmin49/libraries/certs/6187b673.0 b/php/apps/phpmyadmin49/libraries/certs/6187b673.0 new file mode 100644 index 00000000..9548dc1b --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/certs/6187b673.0 @@ -0,0 +1,31 @@ +-----BEGIN CERTIFICATE----- +MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw +TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh +cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4 +WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu +ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY +MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc +h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+ +0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U +A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW +T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH +B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC +B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv +KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn +OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn +jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw +qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI +rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV +HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq +hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL +ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ +3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK +NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5 +ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur +TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC +jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc +oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq +4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA +mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d +emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc= +-----END CERTIFICATE----- diff --git a/php/apps/phpmyadmin49/libraries/certs/README.rst b/php/apps/phpmyadmin49/libraries/certs/README.rst new file mode 100644 index 00000000..bc48f6ce --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/certs/README.rst @@ -0,0 +1,16 @@ +phpMyAdmin SSL certificates +=========================== + +This directory contains copy of root certificates used to sign phpmyadmin.net +and reports.phpmyadmin.net websites. It is used to allow operation on systems +where the certificates are missing or wrongly configured (happens on Windows +with wrongly compiled CURL). + +Currently included SSL certificates: + +* ISRG Root X1 +* DST Root CA X3 + +See https://letsencrypt.org/certificates/ for more info on them. + +In case of update, the filenames can be generated using c_rehash tool. diff --git a/php/apps/phpmyadmin49/libraries/certs/cacert.pem b/php/apps/phpmyadmin49/libraries/certs/cacert.pem new file mode 100644 index 00000000..5f2265fc --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/certs/cacert.pem @@ -0,0 +1,51 @@ +-----BEGIN CERTIFICATE----- +MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw +TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh +cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4 +WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu +ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY +MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc +h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+ +0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U +A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW +T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH +B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC +B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv +KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn +OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn +jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw +qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI +rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV +HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq +hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL +ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ +3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK +NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5 +ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur +TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC +jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc +oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq +4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA +mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d +emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIDSjCCAjKgAwIBAgIQRK+wgNajJ7qJMDmGLvhAazANBgkqhkiG9w0BAQUFADA/ +MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT +DkRTVCBSb290IENBIFgzMB4XDTAwMDkzMDIxMTIxOVoXDTIxMDkzMDE0MDExNVow +PzEkMCIGA1UEChMbRGlnaXRhbCBTaWduYXR1cmUgVHJ1c3QgQ28uMRcwFQYDVQQD +Ew5EU1QgUm9vdCBDQSBYMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB +AN+v6ZdQCINXtMxiZfaQguzH0yxrMMpb7NnDfcdAwRgUi+DoM3ZJKuM/IUmTrE4O +rz5Iy2Xu/NMhD2XSKtkyj4zl93ewEnu1lcCJo6m67XMuegwGMoOifooUMM0RoOEq +OLl5CjH9UL2AZd+3UWODyOKIYepLYYHsUmu5ouJLGiifSKOeDNoJjj4XLh7dIN9b +xiqKqy69cK3FCxolkHRyxXtqqzTWMIn/5WgTe1QLyNau7Fqckh49ZLOMxt+/yUFw +7BZy1SbsOFU5Q9D8/RhcQPGX69Wam40dutolucbY38EVAjqr2m7xPi71XAicPNaD +aeQQmxkqtilX4+U9m5/wAl0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNV +HQ8BAf8EBAMCAQYwHQYDVR0OBBYEFMSnsaR7LHH62+FLkHX/xBVghYkQMA0GCSqG +SIb3DQEBBQUAA4IBAQCjGiybFwBcqR7uKGY3Or+Dxz9LwwmglSBd49lZRNI+DT69 +ikugdB/OEIKcdBodfpga3csTS7MgROSR6cz8faXbauX+5v3gTt23ADq1cEmv8uXr +AvHRAosZy5Q6XkjEGB5YGV8eAlrwDPGxrancWYaLbumR9YbK+rlmM6pZW87ipxZz +R8srzJmwN0jP41ZL9c8PDHIyh8bwRLtTcm1D9SZImlJnt1ir/md2cXjbDaJWFBM5 +JDGFoqgCWjBH4d1QB7wCCZAA62RjYJsWvIjJEubSfZGL+T0yjWW06XyxV3bqxbYo +Ob8VZRzI9neWagqNdwvYkQsEjgfbKbYK7p2CNTUQ +-----END CERTIFICATE----- diff --git a/php/apps/phpmyadmin49/libraries/check_user_privileges.inc.php b/php/apps/phpmyadmin49/libraries/check_user_privileges.inc.php new file mode 100644 index 00000000..168a9920 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/check_user_privileges.inc.php @@ -0,0 +1,29 @@ +getCurrentUserAndHost(); +if ($username === '') { // MySQL is started with --skip-grant-tables + $GLOBALS['is_create_db_priv'] = true; + $GLOBALS['is_reload_priv'] = true; + $GLOBALS['db_to_create'] = ''; + $GLOBALS['dbs_where_create_table_allowed'] = array('*'); + $GLOBALS['dbs_to_test'] = false; + $GLOBALS['db_priv'] = true; + $GLOBALS['col_priv'] = true; + $GLOBALS['table_priv'] = true; + $GLOBALS['proc_priv'] = true; +} else { + $checkUserPrivileges->analyseShowGrant(); +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Advisor.php b/php/apps/phpmyadmin49/libraries/classes/Advisor.php new file mode 100644 index 00000000..b9e0e640 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Advisor.php @@ -0,0 +1,648 @@ +dbi = $dbi; + $this->expression = $expression; + /* + * Register functions for ExpressionLanguage, we intentionally + * do not implement support for compile as we do not use it. + */ + $this->expression->register( + 'round', + function (){}, + function ($arguments, $num) { + return round($num); + } + ); + $this->expression->register( + 'substr', + function (){}, + function ($arguments, $string, $start, $length) { + return substr($string, $start, $length); + } + ); + $this->expression->register( + 'preg_match', + function (){}, + function ($arguments, $pattern , $subject) { + return preg_match($pattern, $subject); + } + ); + $this->expression->register( + 'ADVISOR_bytime', + function (){}, + function ($arguments, $num, $precision) { + return self::byTime($num, $precision); + } + ); + $this->expression->register( + 'ADVISOR_timespanFormat', + function (){}, + function ($arguments, $seconds) { + return self::timespanFormat($seconds); + } + ); + $this->expression->register( + 'ADVISOR_formatByteDown', + function (){}, + function ($arguments, $value, $limes = 6, $comma = 0) { + return self::formatByteDown($value, $limes, $comma); + } + ); + $this->expression->register( + 'fired', + function (){}, + function ($arguments, $value) { + if (!isset($this->runResult['fired'])) { + return 0; + } + + // Did matching rule fire? + foreach ($this->runResult['fired'] as $rule) { + if ($rule['id'] == $value) { + return '1'; + } + } + + return '0'; + } + ); + /* Some global variables for advisor */ + $this->globals = array( + 'PMA_MYSQL_INT_VERSION' => $this->dbi->getVersion(), + ); + + } + + /** + * Get variables + * + * @return mixed + */ + public function getVariables() + { + return $this->variables; + } + + /** + * Set variables + * + * @param array $variables Variables + * + * @return Advisor + */ + public function setVariables(array $variables) + { + $this->variables = $variables; + + return $this; + } + + /** + * Set a variable and its value + * + * @param string|int $variable Variable to set + * @param mixed $value Value to set + * + * @return $this + */ + public function setVariable($variable, $value) + { + $this->variables[$variable] = $value; + + return $this; + } + + /** + * Get parseResult + * + * @return mixed + */ + public function getParseResult() + { + return $this->parseResult; + } + + /** + * Set parseResult + * + * @param array $parseResult Parse result + * + * @return Advisor + */ + public function setParseResult(array $parseResult) + { + $this->parseResult = $parseResult; + + return $this; + } + + /** + * Get runResult + * + * @return mixed + */ + public function getRunResult() + { + return $this->runResult; + } + + /** + * Set runResult + * + * @param array $runResult Run result + * + * @return Advisor + */ + public function setRunResult(array $runResult) + { + $this->runResult = $runResult; + + return $this; + } + + /** + * Parses and executes advisor rules + * + * @return array with run and parse results + */ + public function run() + { + // HowTo: A simple Advisory system in 3 easy steps. + + // Step 1: Get some variables to evaluate on + $this->setVariables( + array_merge( + $this->dbi->fetchResult('SHOW GLOBAL STATUS', 0, 1), + $this->dbi->fetchResult('SHOW GLOBAL VARIABLES', 0, 1) + ) + ); + + // Add total memory to variables as well + $sysinfo = SysInfo::get(); + $memory = $sysinfo->memory(); + $this->variables['system_memory'] + = isset($memory['MemTotal']) ? $memory['MemTotal'] : 0; + + // Step 2: Read and parse the list of rules + $this->setParseResult(static::parseRulesFile()); + // Step 3: Feed the variables to the rules and let them fire. Sets + // $runResult + $this->runRules(); + + return array( + 'parse' => array('errors' => $this->parseResult['errors']), + 'run' => $this->runResult + ); + } + + /** + * Stores current error in run results. + * + * @param string $description description of an error. + * @param Exception $exception exception raised + * + * @return void + */ + public function storeError($description, $exception) + { + $this->runResult['errors'][] = $description + . ' ' + . sprintf( + __('Error when evaluating: %s'), + $exception->getMessage() + ); + } + + /** + * Executes advisor rules + * + * @return boolean + */ + public function runRules() + { + $this->setRunResult( + array( + 'fired' => array(), + 'notfired' => array(), + 'unchecked' => array(), + 'errors' => array(), + ) + ); + + foreach ($this->parseResult['rules'] as $rule) { + $this->variables['value'] = 0; + $precond = true; + + if (isset($rule['precondition'])) { + try { + $precond = $this->ruleExprEvaluate($rule['precondition']); + } catch (Exception $e) { + $this->storeError( + sprintf( + __('Failed evaluating precondition for rule \'%s\'.'), + $rule['name'] + ), + $e + ); + continue; + } + } + + if (! $precond) { + $this->addRule('unchecked', $rule); + } else { + try { + $value = $this->ruleExprEvaluate($rule['formula']); + } catch (Exception $e) { + $this->storeError( + sprintf( + __('Failed calculating value for rule \'%s\'.'), + $rule['name'] + ), + $e + ); + continue; + } + + $this->variables['value'] = $value; + + try { + if ($this->ruleExprEvaluate($rule['test'])) { + $this->addRule('fired', $rule); + } else { + $this->addRule('notfired', $rule); + } + } catch (Exception $e) { + $this->storeError( + sprintf( + __('Failed running test for rule \'%s\'.'), + $rule['name'] + ), + $e + ); + } + } + } + + return true; + } + + /** + * Escapes percent string to be used in format string. + * + * @param string $str string to escape + * + * @return string + */ + public static function escapePercent($str) + { + return preg_replace('/%( |,|\.|$|\(|\)|<|>)/', '%%\1', $str); + } + + /** + * Wrapper function for translating. + * + * @param string $str the string + * @param string $param the parameters + * + * @return string + */ + public function translate($str, $param = null) + { + $string = _gettext(self::escapePercent($str)); + if (! is_null($param)) { + $params = $this->ruleExprEvaluate('[' . $param . ']'); + } else { + $params = array(); + } + return vsprintf($string, $params); + } + + /** + * Splits justification to text and formula. + * + * @param array $rule the rule + * + * @return string[] + */ + public static function splitJustification(array $rule) + { + $jst = preg_split('/\s*\|\s*/', $rule['justification'], 2); + if (count($jst) > 1) { + return array($jst[0], $jst[1]); + } + return array($rule['justification']); + } + + /** + * Adds a rule to the result list + * + * @param string $type type of rule + * @param array $rule rule itself + * + * @return void + */ + public function addRule($type, array $rule) + { + switch ($type) { + case 'notfired': + case 'fired': + $jst = self::splitJustification($rule); + if (count($jst) > 1) { + try { + /* Translate */ + $str = $this->translate($jst[0], $jst[1]); + } catch (Exception $e) { + $this->storeError( + sprintf( + __('Failed formatting string for rule \'%s\'.'), + $rule['name'] + ), + $e + ); + return; + } + + $rule['justification'] = $str; + } else { + $rule['justification'] = $this->translate($rule['justification']); + } + $rule['id'] = $rule['name']; + $rule['name'] = $this->translate($rule['name']); + $rule['issue'] = $this->translate($rule['issue']); + + // Replaces {server_variable} with 'server_variable' + // linking to server_variables.php + $rule['recommendation'] = preg_replace_callback( + '/\{([a-z_0-9]+)\}/Ui', + array($this, 'replaceVariable'), + $this->translate($rule['recommendation']) + ); + + // Replaces external Links with Core::linkURL() generated links + $rule['recommendation'] = preg_replace_callback( + '#href=("|\')(https?://[^\1]+)\1#i', + array($this, 'replaceLinkURL'), + $rule['recommendation'] + ); + break; + } + + $this->runResult[$type][] = $rule; + } + + /** + * Callback for wrapping links with Core::linkURL + * + * @param array $matches List of matched elements form preg_replace_callback + * + * @return string Replacement value + */ + private function replaceLinkURL(array $matches) + { + return 'href="' . Core::linkURL($matches[2]) . '" target="_blank" rel="noopener noreferrer"'; + } + + /** + * Callback for wrapping variable edit links + * + * @param array $matches List of matched elements form preg_replace_callback + * + * @return string Replacement value + */ + private function replaceVariable(array $matches) + { + return '' . htmlspecialchars($matches[1]) . ''; + } + + /** + * Runs a code expression, replacing variable names with their respective + * values + * + * @param string $expr expression to evaluate + * + * @return integer result of evaluated expression + * + * @throws Exception + */ + public function ruleExprEvaluate($expr) + { + // Actually evaluate the code + // This can throw exception + $value = $this->expression->evaluate( + $expr, + array_merge($this->variables, $this->globals) + ); + + return $value; + } + + /** + * Reads the rule file into an array, throwing errors messages on syntax + * errors. + * + * @return array with parsed data + */ + public static function parseRulesFile() + { + $filename = 'libraries/advisory_rules.txt'; + $file = file($filename, FILE_IGNORE_NEW_LINES); + + $errors = array(); + $rules = array(); + $lines = array(); + + if ($file === false) { + $errors[] = sprintf( + __('Error in reading file: The file \'%s\' does not exist or is not readable!'), + $filename + ); + return array('rules' => $rules, 'lines' => $lines, 'errors' => $errors); + } + + $ruleSyntax = array( + 'name', 'formula', 'test', 'issue', 'recommendation', 'justification' + ); + $numRules = count($ruleSyntax); + $numLines = count($file); + $ruleNo = -1; + $ruleLine = -1; + + for ($i = 0; $i < $numLines; $i++) { + $line = $file[$i]; + if ($line == "" || $line[0] == '#') { + continue; + } + + // Reading new rule + if (substr($line, 0, 4) == 'rule') { + if ($ruleLine > 0) { + $errors[] = sprintf( + __( + 'Invalid rule declaration on line %1$s, expected line ' + . '%2$s of previous rule.' + ), + $i + 1, + $ruleSyntax[$ruleLine++] + ); + continue; + } + if (preg_match("/rule\s'(.*)'( \[(.*)\])?$/", $line, $match)) { + $ruleLine = 1; + $ruleNo++; + $rules[$ruleNo] = array('name' => $match[1]); + $lines[$ruleNo] = array('name' => $i + 1); + if (isset($match[3])) { + $rules[$ruleNo]['precondition'] = $match[3]; + $lines[$ruleNo]['precondition'] = $i + 1; + } + } else { + $errors[] = sprintf( + __('Invalid rule declaration on line %s.'), + $i + 1 + ); + } + continue; + } else { + if ($ruleLine == -1) { + $errors[] = sprintf( + __('Unexpected characters on line %s.'), + $i + 1 + ); + } + } + + // Reading rule lines + if ($ruleLine > 0) { + if (!isset($line[0])) { + continue; // Empty lines are ok + } + // Non tabbed lines are not + if ($line[0] != "\t") { + $errors[] = sprintf( + __( + 'Unexpected character on line %1$s. Expected tab, but ' + . 'found "%2$s".' + ), + $i + 1, + $line[0] + ); + continue; + } + $rules[$ruleNo][$ruleSyntax[$ruleLine]] = chop( + mb_substr($line, 1) + ); + $lines[$ruleNo][$ruleSyntax[$ruleLine]] = $i + 1; + ++$ruleLine; + } + + // Rule complete + if ($ruleLine == $numRules) { + $ruleLine = -1; + } + } + + return array('rules' => $rules, 'lines' => $lines, 'errors' => $errors); + } + + /** + * Formats interval like 10 per hour + * + * @param integer $num number to format + * @param integer $precision required precision + * + * @return string formatted string + */ + public static function byTime($num, $precision) + { + if ($num >= 1) { // per second + $per = __('per second'); + } elseif ($num * 60 >= 1) { // per minute + $num = $num * 60; + $per = __('per minute'); + } elseif ($num * 60 * 60 >= 1 ) { // per hour + $num = $num * 60 * 60; + $per = __('per hour'); + } else { + $num = $num * 60 * 60 * 24; + $per = __('per day'); + } + + $num = round($num, $precision); + + if ($num == 0) { + $num = '<' . pow(10, -$precision); + } + + return "$num $per"; + } + + /** + * Wrapper for PhpMyAdmin\Util::timespanFormat + * + * This function is used when evaluating advisory_rules.txt + * + * @param int $seconds the timespan + * + * @return string the formatted value + */ + public static function timespanFormat($seconds) + { + return Util::timespanFormat($seconds); + } + + /** + * Wrapper around PhpMyAdmin\Util::formatByteDown + * + * This function is used when evaluating advisory_rules.txt + * + * @param double $value the value to format + * @param int $limes the sensitiveness + * @param int $comma the number of decimals to retain + * + * @return string the formatted value with unit + */ + public static function formatByteDown($value, $limes = 6, $comma = 0) + { + return implode(' ', Util::formatByteDown($value, $limes, $comma)); + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Bookmark.php b/php/apps/phpmyadmin49/libraries/classes/Bookmark.php new file mode 100644 index 00000000..9543f3e2 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Bookmark.php @@ -0,0 +1,376 @@ +dbi = $dbi; + $this->user = $user; + } + + /** + * Returns the ID of the bookmark + * + * @return int + */ + public function getId() + { + return $this->_id; + } + + /** + * Returns the database of the bookmark + * + * @return string + */ + public function getDatabase() + { + return $this->_database; + } + + /** + * Returns the user whom the bookmark belongs to + * + * @return string + */ + public function getUser() + { + return $this->_user; + } + + /** + * Returns the label of the bookmark + * + * @return string + */ + public function getLabel() + { + return $this->_label; + } + + /** + * Returns the query + * + * @return string + */ + public function getQuery() + { + return $this->_query; + } + + /** + * Adds a bookmark + * + * @return boolean whether the INSERT succeeds or not + * + * @access public + */ + public function save() + { + $cfgBookmark = self::getParams($this->user); + if (empty($cfgBookmark)) { + return false; + } + + $query = "INSERT INTO " . Util::backquote($cfgBookmark['db']) + . "." . Util::backquote($cfgBookmark['table']) + . " (id, dbase, user, query, label) VALUES (NULL, " + . "'" . $this->dbi->escapeString($this->_database) . "', " + . "'" . $this->dbi->escapeString($this->_user) . "', " + . "'" . $this->dbi->escapeString($this->_query) . "', " + . "'" . $this->dbi->escapeString($this->_label) . "')"; + return $this->dbi->query($query, DatabaseInterface::CONNECT_CONTROL); + } + + /** + * Deletes a bookmark + * + * @return bool true if successful + * + * @access public + */ + public function delete() + { + $cfgBookmark = self::getParams($this->user); + if (empty($cfgBookmark)) { + return false; + } + + $query = "DELETE FROM " . Util::backquote($cfgBookmark['db']) + . "." . Util::backquote($cfgBookmark['table']) + . " WHERE id = " . $this->_id; + return $this->dbi->tryQuery($query, DatabaseInterface::CONNECT_CONTROL); + } + + /** + * Returns the number of variables in a bookmark + * + * @return number number of variables + */ + public function getVariableCount() + { + $matches = array(); + preg_match_all("/\[VARIABLE[0-9]*\]/", $this->_query, $matches, PREG_SET_ORDER); + return count($matches); + } + + /** + * Replace the placeholders in the bookmark query with variables + * + * @param array $variables array of variables + * + * @return string query with variables applied + */ + public function applyVariables(array $variables) + { + // remove comments that encloses a variable placeholder + $query = preg_replace( + '|/\*(.*\[VARIABLE[0-9]*\].*)\*/|imsU', + '${1}', + $this->_query + ); + // replace variable placeholders with values + $number_of_variables = $this->getVariableCount(); + for ($i = 1; $i <= $number_of_variables; $i++) { + $var = ''; + if (! empty($variables[$i])) { + $var = $this->dbi->escapeString($variables[$i]); + } + $query = str_replace('[VARIABLE' . $i . ']', $var, $query); + // backward compatibility + if ($i == 1) { + $query = str_replace('[VARIABLE]', $var, $query); + } + } + return $query; + } + + /** + * Defines the bookmark parameters for the current user + * + * @return array the bookmark parameters for the current user + * @access public + */ + public static function getParams($user) + { + static $cfgBookmark = null; + + if (null !== $cfgBookmark) { + return $cfgBookmark; + } + + $relation = new Relation(); + $cfgRelation = $relation->getRelationsParam(); + if ($cfgRelation['bookmarkwork']) { + $cfgBookmark = array( + 'user' => $user, + 'db' => $cfgRelation['db'], + 'table' => $cfgRelation['bookmark'], + ); + } else { + $cfgBookmark = false; + } + + return $cfgBookmark; + } + + /** + * Creates a Bookmark object from the parameters + * + * @param array $bkm_fields the properties of the bookmark to add; here, + * $bkm_fields['bkm_sql_query'] is urlencoded + * @param boolean $all_users whether to make the bookmark available + * for all users + * + * @return Bookmark|false + */ + public static function createBookmark( + DatabaseInterface $dbi, + $user, + array $bkm_fields, + $all_users = false + ) { + if (!(isset($bkm_fields['bkm_sql_query']) + && strlen($bkm_fields['bkm_sql_query']) > 0 + && isset($bkm_fields['bkm_label']) + && strlen($bkm_fields['bkm_label']) > 0) + ) { + return false; + } + + $bookmark = new Bookmark($dbi, $user); + $bookmark->_database = $bkm_fields['bkm_database']; + $bookmark->_label = $bkm_fields['bkm_label']; + $bookmark->_query = $bkm_fields['bkm_sql_query']; + $bookmark->_user = $all_users ? '' : $bkm_fields['bkm_user']; + + return $bookmark; + } + + /** + * Gets the list of bookmarks defined for the current database + * + * @param string|bool $db the current database name or false + * + * @return Bookmark[] the bookmarks list + * + * @access public + */ + public static function getList(DatabaseInterface $dbi, $user, $db = false) + { + $cfgBookmark = self::getParams($user); + if (empty($cfgBookmark)) { + return array(); + } + + $query = "SELECT * FROM " . Util::backquote($cfgBookmark['db']) + . "." . Util::backquote($cfgBookmark['table']) + . " WHERE ( `user` = ''" + . " OR `user` = '" . $dbi->escapeString($cfgBookmark['user']) . "' )"; + if ($db !== false) { + $query .= " AND dbase = '" . $dbi->escapeString($db) . "'"; + } + $query .= " ORDER BY label ASC"; + + $result = $dbi->fetchResult( + $query, + null, + null, + DatabaseInterface::CONNECT_CONTROL, + DatabaseInterface::QUERY_STORE + ); + + if (! empty($result)) { + $bookmarks = array(); + foreach ($result as $row) { + $bookmark = new Bookmark($dbi, $user); + $bookmark->_id = $row['id']; + $bookmark->_database = $row['dbase']; + $bookmark->_user = $row['user']; + $bookmark->_label = $row['label']; + $bookmark->_query = $row['query']; + $bookmarks[] = $bookmark; + } + + return $bookmarks; + } + + return array(); + } + + /** + * Retrieve a specific bookmark + * + * @param string $db the current database name + * @param mixed $id an identifier of the bookmark to get + * @param string $id_field which field to look up the identifier + * @param boolean $action_bookmark_all true: get all bookmarks regardless + * of the owning user + * @param boolean $exact_user_match whether to ignore bookmarks with no user + * + * @return Bookmark the bookmark + * + * @access public + * + */ + public static function get( + DatabaseInterface $dbi, + $user, + $db, + $id, + $id_field = 'id', + $action_bookmark_all = false, + $exact_user_match = false + ) { + $cfgBookmark = self::getParams($user); + if (empty($cfgBookmark)) { + return null; + } + + $query = "SELECT * FROM " . Util::backquote($cfgBookmark['db']) + . "." . Util::backquote($cfgBookmark['table']) + . " WHERE dbase = '" . $dbi->escapeString($db) . "'"; + if (! $action_bookmark_all) { + $query .= " AND (user = '" + . $dbi->escapeString($cfgBookmark['user']) . "'"; + if (! $exact_user_match) { + $query .= " OR user = ''"; + } + $query .= ")"; + } + $query .= " AND " . Util::backquote($id_field) + . " = '" . $dbi->escapeString($id) . "' LIMIT 1"; + + $result = $dbi->fetchSingleRow($query, 'ASSOC', DatabaseInterface::CONNECT_CONTROL); + if (! empty($result)) { + $bookmark = new Bookmark($dbi, $user); + $bookmark->_id = $result['id']; + $bookmark->_database = $result['dbase']; + $bookmark->_user = $result['user']; + $bookmark->_label = $result['label']; + $bookmark->_query = $result['query']; + return $bookmark; + } + + return null; + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/BrowseForeigners.php b/php/apps/phpmyadmin49/libraries/classes/BrowseForeigners.php new file mode 100644 index 00000000..8d799740 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/BrowseForeigners.php @@ -0,0 +1,346 @@ +limitChars = (int) $limitChars; + $this->maxRows = (int) $maxRows; + $this->repeatCells = (int) $repeatCells; + $this->showAll = (bool) $showAll; + $this->themeImage = $themeImage; + } + + /** + * Function to get html for one relational key + * + * @param integer $horizontal_count the current horizontal count + * @param string $header table header + * @param array $keys all the keys + * @param integer $indexByKeyname index by keyname + * @param array $descriptions descriptions + * @param integer $indexByDescription index by description + * @param string $current_value current value on the edit form + * + * @return string $html the generated html + */ + private function getHtmlForOneKey( + $horizontal_count, + $header, + array $keys, + $indexByKeyname, + array $descriptions, + $indexByDescription, + $current_value + ) { + $horizontal_count++; + $output = ''; + + // whether the key name corresponds to the selected value in the form + $rightKeynameIsSelected = false; + $leftKeynameIsSelected = false; + + if ($this->repeatCells > 0 && $horizontal_count > $this->repeatCells) { + $output .= $header; + $horizontal_count = 0; + } + + // key names and descriptions for the left section, + // sorted by key names + $leftKeyname = $keys[$indexByKeyname]; + list( + $leftDescription, + $leftDescriptionTitle + ) = $this->getDescriptionAndTitle($descriptions[$indexByKeyname]); + + // key names and descriptions for the right section, + // sorted by descriptions + $rightKeyname = $keys[$indexByDescription]; + list( + $rightDescription, + $rightDescriptionTitle + ) = $this->getDescriptionAndTitle($descriptions[$indexByDescription]); + + $indexByDescription++; + + if (! empty($current_value)) { + $rightKeynameIsSelected = $rightKeyname == $current_value; + $leftKeynameIsSelected = $leftKeyname == $current_value; + } + + $output .= ''; + + $output .= Template::get('table/browse_foreigners/column_element')->render([ + 'keyname' => $leftKeyname, + 'description' => $leftDescription, + 'title' => $leftDescriptionTitle, + 'is_selected' => $leftKeynameIsSelected, + 'nowrap' => true, + ]); + $output .= Template::get('table/browse_foreigners/column_element')->render([ + 'keyname' => $leftKeyname, + 'description' => $leftDescription, + 'title' => $leftDescriptionTitle, + 'is_selected' => $leftKeynameIsSelected, + 'nowrap' => false, + ]); + + $output .= '' + . ''; + + $output .= Template::get('table/browse_foreigners/column_element')->render([ + 'keyname' => $rightKeyname, + 'description' => $rightDescription, + 'title' => $rightDescriptionTitle, + 'is_selected' => $rightKeynameIsSelected, + 'nowrap' => false, + ]); + $output .= Template::get('table/browse_foreigners/column_element')->render([ + 'keyname' => $rightKeyname, + 'description' => $rightDescription, + 'title' => $rightDescriptionTitle, + 'is_selected' => $rightKeynameIsSelected, + 'nowrap' => true, + ]); + + $output .= ''; + + return array($output, $horizontal_count, $indexByDescription); + } + + /** + * Function to get html for relational field selection + * + * @param string $db current database + * @param string $table current table + * @param string $field field + * @param array $foreignData foreign column data + * @param string $fieldkey field key + * @param string $current_value current columns's value + * + * @return string + */ + public function getHtmlForRelationalFieldSelection( + $db, + $table, + $field, + array $foreignData, + $fieldkey, + $current_value + ) { + $gotopage = $this->getHtmlForGotoPage($foreignData); + $foreignShowAll = Template::get('table/browse_foreigners/show_all')->render([ + 'foreign_data' => $foreignData, + 'show_all' => $this->showAll, + 'max_rows' => $this->maxRows, + ]); + + $output = '
        ' + . '
        ' + . Url::getHiddenInputs($db, $table) + . '' + . ''; + + if (isset($_POST['rownumber'])) { + $output .= ''; + } + $filter_value = (isset($_POST['foreign_filter']) + ? htmlspecialchars($_POST['foreign_filter']) + : ''); + $output .= '' + . '' + . '' + . '' + . '' + . '' . $gotopage . '' + . '' . $foreignShowAll . '' + . '
        ' + . '
        '; + + $output .= ''; + + if (!is_array($foreignData['disp_row'])) { + $output .= '' + . '
        '; + + return $output; + } + + $header = ' + ' . __('Keyname') . ' + ' . __('Description') . ' + + ' . __('Description') . ' + ' . __('Keyname') . ' + '; + + $output .= '' . $header . '' . "\n" + . '' . $header . '' . "\n" + . '' . "\n"; + + $descriptions = array(); + $keys = array(); + foreach ($foreignData['disp_row'] as $relrow) { + if ($foreignData['foreign_display'] != false) { + $descriptions[] = $relrow[$foreignData['foreign_display']]; + } else { + $descriptions[] = ''; + } + + $keys[] = $relrow[$foreignData['foreign_field']]; + } + + asort($keys); + + $horizontal_count = 0; + $indexByDescription = 0; + + foreach ($keys as $indexByKeyname => $value) { + list( + $html, + $horizontal_count, + $indexByDescription + ) = $this->getHtmlForOneKey( + $horizontal_count, + $header, + $keys, + $indexByKeyname, + $descriptions, + $indexByDescription, + $current_value + ); + $output .= $html; + } + + $output .= '' + . ''; + + return $output; + } + + /** + * Get the description (possibly truncated) and the title + * + * @param string $description the key name's description + * + * @return array the new description and title + */ + private function getDescriptionAndTitle($description) + { + if (mb_strlen($description) <= $this->limitChars) { + $description = htmlspecialchars( + $description + ); + $descriptionTitle = ''; + } else { + $descriptionTitle = htmlspecialchars( + $description + ); + $description = htmlspecialchars( + mb_substr( + $description, 0, $this->limitChars + ) + . '...' + ); + } + return array($description, $descriptionTitle); + } + + /** + * Function to get html for the goto page option + * + * @param array|null $foreignData foreign data + * + * @return string + */ + private function getHtmlForGotoPage($foreignData) + { + $gotopage = ''; + isset($_POST['pos']) ? $pos = $_POST['pos'] : $pos = 0; + if (!is_array($foreignData['disp_row'])) { + return $gotopage; + } + + $pageNow = @floor($pos / $this->maxRows) + 1; + $nbTotalPage = @ceil($foreignData['the_total'] / $this->maxRows); + + if ($foreignData['the_total'] > $this->maxRows) { + $gotopage = Util::pageselector( + 'pos', + $this->maxRows, + $pageNow, + $nbTotalPage, + 200, + 5, + 5, + 20, + 10, + __('Page number:') + ); + } + + return $gotopage; + } + + /** + * Function to get foreign limit + * + * @param string $foreignShowAll foreign navigation + * + * @return string + */ + public function getForeignLimit($foreignShowAll) + { + if (isset($foreignShowAll) && $foreignShowAll == __('Show all')) { + return null; + } + isset($_POST['pos']) ? $pos = $_POST['pos'] : $pos = 0; + return 'LIMIT ' . $pos . ', ' . $this->maxRows . ' '; + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/CentralColumns.php b/php/apps/phpmyadmin49/libraries/classes/CentralColumns.php new file mode 100644 index 00000000..7379cee4 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/CentralColumns.php @@ -0,0 +1,1453 @@ +dbi = $dbi; + + $this->user = $GLOBALS['cfg']['Server']['user']; + $this->maxRows = (int) $GLOBALS['cfg']['MaxRows']; + $this->charEditing = $GLOBALS['cfg']['CharEditing']; + $this->disableIs = (bool) $GLOBALS['cfg']['Server']['DisableIS']; + + $this->relation = new Relation(); + } + + /** + * Defines the central_columns parameters for the current user + * + * @return array the central_columns parameters for the current user + * @access public + */ + public function getParams() + { + static $cfgCentralColumns = null; + + if (null !== $cfgCentralColumns) { + return $cfgCentralColumns; + } + + $cfgRelation = $this->relation->getRelationsParam(); + + if ($cfgRelation['centralcolumnswork']) { + $cfgCentralColumns = array( + 'user' => $this->user, + 'db' => $cfgRelation['db'], + 'table' => $cfgRelation['central_columns'], + ); + } else { + $cfgCentralColumns = false; + } + + return $cfgCentralColumns; + } + + /** + * get $num columns of given database from central columns list + * starting at offset $from + * + * @param string $db selected database + * @param int $from starting offset of first result + * @param int $num maximum number of results to return + * + * @return array list of $num columns present in central columns list + * starting at offset $from for the given database + */ + public function getColumnsList($db, $from = 0, $num = 25) + { + $cfgCentralColumns = $this->getParams(); + if (empty($cfgCentralColumns)) { + return array(); + } + $pmadb = $cfgCentralColumns['db']; + $this->dbi->selectDb($pmadb, DatabaseInterface::CONNECT_CONTROL); + $central_list_table = $cfgCentralColumns['table']; + //get current values of $db from central column list + if ($num == 0) { + $query = 'SELECT * FROM ' . Util::backquote($central_list_table) . ' ' + . 'WHERE db_name = \'' . $this->dbi->escapeString($db) . '\';'; + } else { + $query = 'SELECT * FROM ' . Util::backquote($central_list_table) . ' ' + . 'WHERE db_name = \'' . $this->dbi->escapeString($db) . '\' ' + . 'LIMIT ' . $from . ', ' . $num . ';'; + } + $has_list = (array) $this->dbi->fetchResult( + $query, null, null, DatabaseInterface::CONNECT_CONTROL + ); + $this->handleColumnExtra($has_list); + return $has_list; + } + + /** + * Get the number of columns present in central list for given db + * + * @param string $db current database + * + * @return int number of columns in central list of columns for $db + */ + public function getCount($db) + { + $cfgCentralColumns = $this->getParams(); + if (empty($cfgCentralColumns)) { + return 0; + } + $pmadb = $cfgCentralColumns['db']; + $this->dbi->selectDb($pmadb, DatabaseInterface::CONNECT_CONTROL); + $central_list_table = $cfgCentralColumns['table']; + $query = 'SELECT count(db_name) FROM ' . + Util::backquote($central_list_table) . ' ' + . 'WHERE db_name = \'' . $this->dbi->escapeString($db) . '\';'; + $res = $this->dbi->fetchResult( + $query, null, null, DatabaseInterface::CONNECT_CONTROL + ); + if (isset($res[0])) { + return $res[0]; + } + + return 0; + } + + /** + * return the existing columns in central list among the given list of columns + * + * @param string $db the selected database + * @param string $cols comma separated list of given columns + * @param boolean $allFields set if need all the fields of existing columns, + * otherwise only column_name is returned + * + * @return array list of columns in central columns among given set of columns + */ + private function findExistingColNames( + $db, + $cols, + $allFields = false + ) { + $cfgCentralColumns = $this->getParams(); + if (empty($cfgCentralColumns)) { + return array(); + } + $pmadb = $cfgCentralColumns['db']; + $this->dbi->selectDb($pmadb, DatabaseInterface::CONNECT_CONTROL); + $central_list_table = $cfgCentralColumns['table']; + if ($allFields) { + $query = 'SELECT * FROM ' . Util::backquote($central_list_table) . ' ' + . 'WHERE db_name = \'' . $this->dbi->escapeString($db) . '\' AND col_name IN (' . $cols . ');'; + $has_list = (array) $this->dbi->fetchResult( + $query, null, null, DatabaseInterface::CONNECT_CONTROL + ); + $this->handleColumnExtra($has_list); + } else { + $query = 'SELECT col_name FROM ' + . Util::backquote($central_list_table) . ' ' + . 'WHERE db_name = \'' . $this->dbi->escapeString($db) . '\' AND col_name IN (' . $cols . ');'; + $has_list = (array) $this->dbi->fetchResult( + $query, null, null, DatabaseInterface::CONNECT_CONTROL + ); + } + + return $has_list; + } + + /** + * return error message to be displayed if central columns + * configuration storage is not completely configured + * + * @return Message + */ + private function configErrorMessage() + { + return Message::error( + __( + 'The configuration storage is not ready for the central list' + . ' of columns feature.' + ) + ); + } + + /** + * build the insert query for central columns list given PMA storage + * db, central_columns table, column name and corresponding definition to be added + * + * @param string $column column to add into central list + * @param array $def list of attributes of the column being added + * @param string $db PMA configuration storage database name + * @param string $central_list_table central columns configuration storage table name + * + * @return string query string to insert the given column + * with definition into central list + */ + private function getInsertQuery( + $column, + array $def, + $db, + $central_list_table + ) { + $type = ""; + $length = 0; + $attribute = ""; + if (isset($def['Type'])) { + $extracted_columnspec = Util::extractColumnSpec($def['Type']); + $attribute = trim($extracted_columnspec[ 'attribute']); + $type = $extracted_columnspec['type']; + $length = $extracted_columnspec['spec_in_brackets']; + } + if (isset($def['Attribute'])) { + $attribute = $def['Attribute']; + }; + $collation = isset($def['Collation'])?$def['Collation']:""; + $isNull = ($def['Null'] == "NO")?0:1; + $extra = isset($def['Extra'])?$def['Extra']:""; + $default = isset($def['Default'])?$def['Default']:""; + $insQuery = 'INSERT INTO ' + . Util::backquote($central_list_table) . ' ' + . 'VALUES ( \'' . $this->dbi->escapeString($db) . '\' ,' + . '\'' . $this->dbi->escapeString($column) . '\',\'' + . $this->dbi->escapeString($type) . '\',' + . '\'' . $this->dbi->escapeString($length) . '\',\'' + . $this->dbi->escapeString($collation) . '\',' + . '\'' . $this->dbi->escapeString($isNull) . '\',' + . '\'' . implode(',', array($extra, $attribute)) + . '\',\'' . $this->dbi->escapeString($default) . '\');'; + return $insQuery; + } + + /** + * If $isTable is true then unique columns from given tables as $field_select + * are added to central list otherwise the $field_select is considered as + * list of columns and these columns are added to central list if not already added + * + * @param array $field_select if $isTable is true selected tables list + * otherwise selected columns list + * @param bool $isTable if passed array is of tables or columns + * @param string $table if $isTable is false, then table name to + * which columns belong + * + * @return true|PhpMyAdmin\Message + */ + public function syncUniqueColumns( + array $field_select, + $isTable = true, + $table = null + ) { + $cfgCentralColumns = $this->getParams(); + if (empty($cfgCentralColumns)) { + return $this->configErrorMessage(); + } + $db = $_POST['db']; + $pmadb = $cfgCentralColumns['db']; + $central_list_table = $cfgCentralColumns['table']; + $this->dbi->selectDb($db); + $existingCols = array(); + $cols = ""; + $insQuery = array(); + $fields = array(); + $message = true; + if ($isTable) { + foreach ($field_select as $table) { + $fields[$table] = (array) $this->dbi->getColumns( + $db, $table, null, true + ); + foreach ($fields[$table] as $field => $def) { + $cols .= "'" . $this->dbi->escapeString($field) . "',"; + } + } + + $has_list = $this->findExistingColNames($db, trim($cols, ',')); + foreach ($field_select as $table) { + foreach ($fields[$table] as $field => $def) { + if (!in_array($field, $has_list)) { + $has_list[] = $field; + $insQuery[] = $this->getInsertQuery( + $field, $def, $db, $central_list_table + ); + } else { + $existingCols[] = "'" . $field . "'"; + } + } + } + } else { + if ($table === null) { + $table = $_POST['table']; + } + foreach ($field_select as $column) { + $cols .= "'" . $this->dbi->escapeString($column) . "',"; + } + $has_list = $this->findExistingColNames($db, trim($cols, ',')); + foreach ($field_select as $column) { + if (!in_array($column, $has_list)) { + $has_list[] = $column; + $field = (array) $this->dbi->getColumns( + $db, $table, $column, + true + ); + $insQuery[] = $this->getInsertQuery( + $column, $field, $db, $central_list_table + ); + } else { + $existingCols[] = "'" . $column . "'"; + } + } + } + if (! empty($existingCols)) { + $existingCols = implode(",", array_unique($existingCols)); + $message = Message::notice( + sprintf( + __( + 'Could not add %1$s as they already exist in central list!' + ), htmlspecialchars($existingCols) + ) + ); + $message->addMessage( + Message::notice( + "Please remove them first " + . "from central list if you want to update above columns" + ) + ); + } + $this->dbi->selectDb($pmadb, DatabaseInterface::CONNECT_CONTROL); + if (! empty($insQuery)) { + foreach ($insQuery as $query) { + if (!$this->dbi->tryQuery($query, DatabaseInterface::CONNECT_CONTROL)) { + $message = Message::error(__('Could not add columns!')); + $message->addMessage( + Message::rawError( + $this->dbi->getError(DatabaseInterface::CONNECT_CONTROL) + ) + ); + break; + } + } + } + return $message; + } + + /** + * if $isTable is true it removes all columns of given tables as $field_select from + * central columns list otherwise $field_select is columns list and it removes + * given columns if present in central list + * + * @param array $field_select if $isTable selected list of tables otherwise + * selected list of columns to remove from central list + * @param bool $isTable if passed array is of tables or columns + * + * @return true|PhpMyAdmin\Message + */ + public function deleteColumnsFromList( + array $field_select, + $isTable = true + ) { + $cfgCentralColumns = $this->getParams(); + if (empty($cfgCentralColumns)) { + return $this->configErrorMessage(); + } + $db = $_POST['db']; + $pmadb = $cfgCentralColumns['db']; + $central_list_table = $cfgCentralColumns['table']; + $this->dbi->selectDb($db); + $message = true; + $colNotExist = array(); + $fields = array(); + if ($isTable) { + $cols = ''; + foreach ($field_select as $table) { + $fields[$table] = (array) $this->dbi->getColumnNames( + $db, $table + ); + foreach ($fields[$table] as $col_select) { + $cols .= '\'' . $this->dbi->escapeString($col_select) . '\','; + } + } + $cols = trim($cols, ','); + $has_list = $this->findExistingColNames($db, $cols); + foreach ($field_select as $table) { + foreach ($fields[$table] as $column) { + if (!in_array($column, $has_list)) { + $colNotExist[] = "'" . $column . "'"; + } + } + } + + } else { + $cols = ''; + foreach ($field_select as $col_select) { + $cols .= '\'' . $this->dbi->escapeString($col_select) . '\','; + } + $cols = trim($cols, ','); + $has_list = $this->findExistingColNames($db, $cols); + foreach ($field_select as $column) { + if (!in_array($column, $has_list)) { + $colNotExist[] = "'" . $column . "'"; + } + } + } + if (!empty($colNotExist)) { + $colNotExist = implode(",", array_unique($colNotExist)); + $message = Message::notice( + sprintf( + __( + 'Couldn\'t remove Column(s) %1$s ' + . 'as they don\'t exist in central columns list!' + ), htmlspecialchars($colNotExist) + ) + ); + } + $this->dbi->selectDb($pmadb, DatabaseInterface::CONNECT_CONTROL); + + $query = 'DELETE FROM ' . Util::backquote($central_list_table) . ' ' + . 'WHERE db_name = \'' . $this->dbi->escapeString($db) . '\' AND col_name IN (' . $cols . ');'; + + if (!$this->dbi->tryQuery($query, DatabaseInterface::CONNECT_CONTROL)) { + $message = Message::error(__('Could not remove columns!')); + $message->addHtml('
        ' . htmlspecialchars($cols) . '
        '); + $message->addMessage( + Message::rawError( + $this->dbi->getError(DatabaseInterface::CONNECT_CONTROL) + ) + ); + } + return $message; + } + + /** + * Make the columns of given tables consistent with central list of columns. + * Updates only those columns which are not being referenced. + * + * @param string $db current database + * @param array $selected_tables list of selected tables. + * + * @return true|PhpMyAdmin\Message + */ + public function makeConsistentWithList( + $db, + array $selected_tables + ) { + $message = true; + foreach ($selected_tables as $table) { + $query = 'ALTER TABLE ' . Util::backquote($table); + $has_list = $this->getFromTable($db, $table, true); + $this->dbi->selectDb($db); + foreach ($has_list as $column) { + $column_status = $this->relation->checkChildForeignReferences( + $db, $table, $column['col_name'] + ); + //column definition can only be changed if + //it is not referenced by another column + if ($column_status['isEditable']) { + $query .= ' MODIFY ' . Util::backquote($column['col_name']) . ' ' + . $this->dbi->escapeString($column['col_type']); + if ($column['col_length']) { + $query .= '(' . $column['col_length'] . ')'; + } + + $query .= ' ' . $column['col_attribute']; + if ($column['col_isNull']) { + $query .= ' NULL'; + } else { + $query .= ' NOT NULL'; + } + + $query .= ' ' . $column['col_extra']; + if ($column['col_default']) { + if ($column['col_default'] != 'CURRENT_TIMESTAMP' + || $column['col_default'] != 'current_timestamp()') { + $query .= ' DEFAULT \'' . $this->dbi->escapeString( + $column['col_default'] + ) . '\''; + } else { + $query .= ' DEFAULT ' . $this->dbi->escapeString( + $column['col_default'] + ); + } + } + $query .= ','; + } + } + $query = trim($query, " ,") . ";"; + if (!$this->dbi->tryQuery($query)) { + if ($message === true) { + $message = Message::error( + $this->dbi->getError() + ); + } else { + $message->addText( + $this->dbi->getError(), + '
        ' + ); + } + } + } + return $message; + } + + /** + * return the columns present in central list of columns for a given + * table of a given database + * + * @param string $db given database + * @param string $table given table + * @param boolean $allFields set if need all the fields of existing columns, + * otherwise only column_name is returned + * + * @return array columns present in central list from given table of given db. + */ + public function getFromTable( + $db, + $table, + $allFields = false + ) { + $cfgCentralColumns = $this->getParams(); + if (empty($cfgCentralColumns)) { + return array(); + } + $this->dbi->selectDb($db); + $fields = (array) $this->dbi->getColumnNames( + $db, $table + ); + $cols = ''; + foreach ($fields as $col_select) { + $cols .= '\'' . $this->dbi->escapeString($col_select) . '\','; + } + $cols = trim($cols, ','); + $has_list = $this->findExistingColNames($db, $cols, $allFields); + if (! empty($has_list)) { + return (array)$has_list; + } + + return array(); + } + + /** + * update a column in central columns list if a edit is requested + * + * @param string $db current database + * @param string $orig_col_name original column name before edit + * @param string $col_name new column name + * @param string $col_type new column type + * @param string $col_attribute new column attribute + * @param string $col_length new column length + * @param int $col_isNull value 1 if new column isNull is true, 0 otherwise + * @param string $collation new column collation + * @param string $col_extra new column extra property + * @param string $col_default new column default value + * + * @return true|PhpMyAdmin\Message + */ + public function updateOneColumn( + $db, + $orig_col_name, + $col_name, + $col_type, + $col_attribute, + $col_length, + $col_isNull, + $collation, + $col_extra, + $col_default + ) { + $cfgCentralColumns = $this->getParams(); + if (empty($cfgCentralColumns)) { + return $this->configErrorMessage(); + } + $centralTable = $cfgCentralColumns['table']; + $this->dbi->selectDb($cfgCentralColumns['db'], DatabaseInterface::CONNECT_CONTROL); + if ($orig_col_name == "") { + $def = array(); + $def['Type'] = $col_type; + if ($col_length) { + $def['Type'] .= '(' . $col_length . ')'; + } + $def['Collation'] = $collation; + $def['Null'] = $col_isNull?__('YES'):__('NO'); + $def['Extra'] = $col_extra; + $def['Attribute'] = $col_attribute; + $def['Default'] = $col_default; + $query = $this->getInsertQuery($col_name, $def, $db, $centralTable); + } else { + $query = 'UPDATE ' . Util::backquote($centralTable) + . ' SET col_type = \'' . $this->dbi->escapeString($col_type) . '\'' + . ', col_name = \'' . $this->dbi->escapeString($col_name) . '\'' + . ', col_length = \'' . $this->dbi->escapeString($col_length) . '\'' + . ', col_isNull = ' . $col_isNull + . ', col_collation = \'' . $this->dbi->escapeString($collation) . '\'' + . ', col_extra = \'' + . implode(',', array($col_extra, $col_attribute)) . '\'' + . ', col_default = \'' . $this->dbi->escapeString($col_default) . '\'' + . ' WHERE db_name = \'' . $this->dbi->escapeString($db) . '\' ' + . 'AND col_name = \'' . $this->dbi->escapeString($orig_col_name) + . '\''; + } + if (!$this->dbi->tryQuery($query, DatabaseInterface::CONNECT_CONTROL)) { + return Message::error( + $this->dbi->getError(DatabaseInterface::CONNECT_CONTROL) + ); + } + return true; + } + + /** + * Update Multiple column in central columns list if a chnage is requested + * + * @return true|PhpMyAdmin\Message + */ + public function updateMultipleColumn() + { + $db = $_POST['db']; + $col_name = $_POST['field_name']; + $orig_col_name = $_POST['orig_col_name']; + $col_default = $_POST['field_default_type']; + $col_length = $_POST['field_length']; + $col_attribute = $_POST['field_attribute']; + $col_type = $_POST['field_type']; + $collation = $_POST['field_collation']; + $col_isNull = array(); + $col_extra = array(); + $num_central_fields = count($orig_col_name); + for ($i = 0; $i < $num_central_fields ; $i++) { + $col_isNull[$i] = isset($_POST['field_null'][$i]) ? 1 : 0; + $col_extra[$i] = isset($_POST['col_extra'][$i]) + ? $_POST['col_extra'][$i] : ''; + + if ($col_default[$i] == 'NONE') { + $col_default[$i] = ""; + } elseif ($col_default[$i] == 'USER_DEFINED') { + $col_default[$i] = $_POST['field_default_value'][$i]; + } + + $message = $this->updateOneColumn( + $db, $orig_col_name[$i], $col_name[$i], $col_type[$i], + $col_attribute[$i], $col_length[$i], $col_isNull[$i], $collation[$i], + $col_extra[$i], $col_default[$i] + ); + if (!is_bool($message)) { + return $message; + } + } + return true; + } + + /** + * get the html for table navigation in Central columns page + * + * @param int $total_rows total number of rows in complete result set + * @param int $pos offset of first result with complete result set + * @param string $db current database + * + * @return string html for table navigation in Central columns page + */ + public function getHtmlForTableNavigation($total_rows, $pos, $db) + { + $pageNow = ($pos / $this->maxRows) + 1; + $nbTotalPage = ceil($total_rows / $this->maxRows); + $page_selector = ($nbTotalPage > 1)?(Util::pageselector( + 'pos', $this->maxRows, $pageNow, $nbTotalPage + )):''; + return Template::get('database/central_columns/table_navigation')->render(array( + "pos" => $pos, + "max_rows" => $this->maxRows, + "db" => $db, + "total_rows" => $total_rows, + "nb_total_page" => $nbTotalPage, + "page_selector" => $page_selector, + )); + } + + /** + * function generate and return the table header for central columns page + * + * @param string $class styling class of 'th' elements + * @param string $title title of the 'th' elements + * @param integer $actionCount number of actions + * + * @return string html for table header in central columns view/edit page + */ + public function getTableHeader($class = '', $title = '', $actionCount = 0) + { + $action = ''; + if ($actionCount > 0) { + $action .= '' + . __('Action') . ''; + } + $tableheader = ''; + $tableheader .= '' + . '' + . '' + . $action + . '' + . __('Name') . '
        ' + . '' + . __('Type') . '
        ' + . '' + . __('Length/Values') . '
        ' + . '' + . __('Default') . '
        ' + . '' . __('Collation') . '
        ' + . '' + . __('Attribute') . '
        ' + . '' + . __('Null') . '
        ' + . '' + . __('A_I') . '
        ' + . ''; + $tableheader .= ''; + return $tableheader; + } + + /** + * Function generate and return the table header for + * multiple edit central columns page + * + * @param array $headers headers list + * + * @return string html for table header in central columns multi edit page + */ + private function getEditTableHeader(array $headers) + { + return Template::get( + 'database/central_columns/edit_table_header' + )->render([ + 'headers' => $headers, + ]); + } + + /** + * build the dropdown select html for tables of given database + * + * @param string $db current database + * + * @return string html dropdown for selecting table + */ + private function getHtmlForTableDropdown($db) + { + $this->dbi->selectDb($db); + $tables = $this->dbi->getTables($db); + $selectHtml = ''; + return $selectHtml; + } + + /** + * build dropdown select html to select column in selected table, + * include only columns which are not already in central list + * + * @param string $db current database to which selected table belongs + * @param string $selected_tbl selected table + * + * @return string html to select column + */ + public function getHtmlForColumnDropdown($db, $selected_tbl) + { + $existing_cols = $this->getFromTable($db, $selected_tbl); + $this->dbi->selectDb($db); + $columns = (array) $this->dbi->getColumnNames( + $db, $selected_tbl + ); + $selectColHtml = ""; + foreach ($columns as $column) { + if (!in_array($column, $existing_cols)) { + $selectColHtml .= ''; + } + } + return $selectColHtml; + } + + /** + * HTML to display the form that let user to add a column on Central columns page + * + * @param int $total_rows total number of rows in complete result set + * @param int $pos offset of first result with complete result set + * @param string $db current database + * + * @return string html to add a column in the central list + */ + public function getHtmlForAddColumn( + $total_rows, + $pos, + $db + ) { + $icon = Util::getIcon( + 'centralColumns_add', + __('Add column') + ); + $table_drop_down = $this->getHtmlForTableDropdown($db); + return Template::get('database/central_columns/add_column')->render(array( + 'icon' => $icon, + 'pos' => $pos, + 'db' => $db, + 'total_rows' => $total_rows, + 'table_drop_down' => $table_drop_down, + )); + } + + /** + * build html for a row in central columns table + * + * @param array $row array contains complete information of a particular row of central list table + * @param int $row_num position the row in the table + * @param string $db current database + * + * @return string html of a particular row in the central columns table. + */ + public function getHtmlForTableRow(array $row, $row_num, $db) + { + $tableHtml = '' + . Url::getHiddenInputs( + $db + ) + . '' + . '' + . '' + . '' + . '' + . '' . Util::getIcon('b_edit', __('Edit')) . '' + . '' + . '' . Util::getIcon('b_drop', __('Delete')) . '' + . '' + . '' + . ''; + + $tableHtml .= + '' + . '' . htmlspecialchars($row['col_name']) . '' + . '' + . Template::get('columns_definitions/column_name')->render(array( + 'column_number' => $row_num, + 'ci' => 0, + 'ci_offset' => 0, + 'column_meta' => array( + 'Field'=>$row['col_name'] + ), + 'cfg_relation' => array( + 'centralcolumnswork' => false + ), + 'max_rows' => $this->maxRows, + )) + . ''; + $tableHtml .= + '' + . htmlspecialchars($row['col_type']) . '' + . Template::get('columns_definitions/column_type') + ->render( + array( + 'column_number' => $row_num, + 'ci' => 1, + 'ci_offset' => 0, + 'type_upper' => mb_strtoupper($row['col_type']), + 'column_meta' => array() + ) + ) + . ''; + $tableHtml .= + '' + . '' . ($row['col_length']?htmlspecialchars($row['col_length']):"") + . '' + . Template::get('columns_definitions/column_length')->render( + array( + 'column_number' => $row_num, + 'ci' => 2, + 'ci_offset' => 0, + 'length_values_input_size' => 8, + 'length_to_display' => $row['col_length'] + ) + ) + . ''; + + $meta = array(); + if (!isset($row['col_default']) || $row['col_default'] == '') { + $meta['DefaultType'] = 'NONE'; + } else { + if ($row['col_default'] == 'CURRENT_TIMESTAMP' + || $row['col_default'] == 'current_timestamp()' + ) { + $meta['DefaultType'] = 'CURRENT_TIMESTAMP'; + } elseif ($row['col_default'] == 'NULL') { + $meta['DefaultType'] = $row['col_default']; + } else { + $meta['DefaultType'] = 'USER_DEFINED'; + $meta['DefaultValue'] = $row['col_default']; + } + } + $tableHtml .= + '' . (isset($row['col_default']) + ? htmlspecialchars($row['col_default']) : 'None') + . '' + . Template::get('columns_definitions/column_default') + ->render( + array( + 'column_number' => $row_num, + 'ci' => 3, + 'ci_offset' => 0, + 'type_upper' => mb_strtoupper($row['col_type']), + 'column_meta' => $meta, + 'char_editing' => $this->charEditing, + ) + ) + . ''; + + $tableHtml .= + '' + . '' . htmlspecialchars($row['col_collation']) . '' + . Charsets::getCollationDropdownBox( + $this->dbi, + $this->disableIs, + 'field_collation[' . $row_num . ']', + 'field_' . $row_num . '_4', $row['col_collation'], false + ) + . ''; + $tableHtml .= + '' + . '' . + ($row['col_attribute'] + ? htmlspecialchars($row['col_attribute']) : "" ) + . '' + . Template::get('columns_definitions/column_attribute') + ->render( + array( + 'column_number' => $row_num, + 'ci' => 5, + 'ci_offset' => 0, + 'extracted_columnspec' => array(), + 'column_meta' => $row['col_attribute'], + 'submit_attribute' => false, + 'attribute_types' => $this->dbi->types->getAttributes(), + ) + ) + . ''; + $tableHtml .= + '' + . '' . ($row['col_isNull'] ? __('Yes') : __('No')) + . '' + . Template::get('columns_definitions/column_null') + ->render( + array( + 'column_number' => $row_num, + 'ci' => 6, + 'ci_offset' => 0, + 'column_meta' => array( + 'Null' => $row['col_isNull'] + ) + ) + ) + . ''; + + $tableHtml .= + '' + . htmlspecialchars($row['col_extra']) . '' + . Template::get('columns_definitions/column_extra')->render( + array( + 'column_number' => $row_num, + 'ci' => 7, + 'ci_offset' => 0, + 'column_meta' => array('Extra'=>$row['col_extra']) + ) + ) + . ''; + + $tableHtml .= ''; + + return $tableHtml; + } + + /** + * build html for editing a row in central columns table + * + * @param array $row array contains complete information of a + * particular row of central list table + * @param int $row_num position the row in the table + * + * @return string html of a particular row in the central columns table. + */ + private function getHtmlForEditTableRow(array $row, $row_num) + { + $tableHtml = '' + . '' + . '' + . Template::get('columns_definitions/column_name')->render(array( + 'column_number' => $row_num, + 'ci' => 0, + 'ci_offset' => 0, + 'column_meta' => array( + 'Field' => $row['col_name'] + ), + 'cfg_relation' => array( + 'centralcolumnswork' => false + ), + 'max_rows' => $this->maxRows, + )) + . ''; + $tableHtml .= + '' + . Template::get('columns_definitions/column_type') + ->render( + array( + 'column_number' => $row_num, + 'ci' => 1, + 'ci_offset' => 0, + 'type_upper' => mb_strtoupper($row['col_type']), + 'column_meta' => array() + ) + ) + . ''; + $tableHtml .= + '' + . Template::get('columns_definitions/column_length')->render( + array( + 'column_number' => $row_num, + 'ci' => 2, + 'ci_offset' => 0, + 'length_values_input_size' => 8, + 'length_to_display' => $row['col_length'] + ) + ) + . ''; + $meta = array(); + if (!isset($row['col_default']) || $row['col_default'] == '') { + $meta['DefaultType'] = 'NONE'; + } else { + if ($row['col_default'] == 'CURRENT_TIMESTAMP' + || $row['col_default'] == 'current_timestamp()' + ) { + $meta['DefaultType'] = 'CURRENT_TIMESTAMP'; + } elseif ($row['col_default'] == 'NULL') { + $meta['DefaultType'] = $row['col_default']; + } else { + $meta['DefaultType'] = 'USER_DEFINED'; + $meta['DefaultValue'] = $row['col_default']; + } + } + $tableHtml .= + '' + . Template::get('columns_definitions/column_default') + ->render( + array( + 'column_number' => $row_num, + 'ci' => 3, + 'ci_offset' => 0, + 'type_upper' => mb_strtoupper($row['col_default']), + 'column_meta' => $meta, + 'char_editing' => $this->charEditing, + ) + ) + . ''; + $tableHtml .= + '' + . Charsets::getCollationDropdownBox( + $this->dbi, + $this->disableIs, + 'field_collation[' . $row_num . ']', + 'field_' . $row_num . '_4', $row['col_collation'], false + ) + . ''; + $tableHtml .= + '' + . Template::get('columns_definitions/column_attribute') + ->render( + array( + 'column_number' => $row_num, + 'ci' => 5, + 'ci_offset' => 0, + 'extracted_columnspec' => array( + 'attribute' => $row['col_attribute'] + ), + 'column_meta' => array(), + 'submit_attribute' => false, + 'attribute_types' => $this->dbi->types->getAttributes(), + ) + ) + . ''; + $tableHtml .= + '' + . Template::get('columns_definitions/column_null') + ->render( + array( + 'column_number' => $row_num, + 'ci' => 6, + 'ci_offset' => 0, + 'column_meta' => array( + 'Null' => $row['col_isNull'] + ) + ) + ) + . ''; + + $tableHtml .= + '' + . Template::get('columns_definitions/column_extra')->render( + array( + 'column_number' => $row_num, + 'ci' => 7, + 'ci_offset' => 0, + 'column_meta' => array('Extra' => $row['col_extra']) + ) + ) + . ''; + $tableHtml .= ''; + return $tableHtml; + } + + /** + * get the list of columns in given database excluding + * the columns present in current table + * + * @param string $db selected database + * @param string $table current table name + * + * @return string encoded list of columns present in central list for the given + * database + */ + public function getListRaw($db, $table) + { + $cfgCentralColumns = $this->getParams(); + if (empty($cfgCentralColumns)) { + return json_encode(array()); + } + $centralTable = $cfgCentralColumns['table']; + if (empty($table) || $table == '') { + $query = 'SELECT * FROM ' . Util::backquote($centralTable) . ' ' + . 'WHERE db_name = \'' . $this->dbi->escapeString($db) . '\';'; + } else { + $this->dbi->selectDb($db); + $columns = (array) $this->dbi->getColumnNames( + $db, $table + ); + $cols = ''; + foreach ($columns as $col_select) { + $cols .= '\'' . $this->dbi->escapeString($col_select) . '\','; + } + $cols = trim($cols, ','); + $query = 'SELECT * FROM ' . Util::backquote($centralTable) . ' ' + . 'WHERE db_name = \'' . $this->dbi->escapeString($db) . '\''; + if ($cols) { + $query .= ' AND col_name NOT IN (' . $cols . ')'; + } + $query .= ';'; + } + $this->dbi->selectDb($cfgCentralColumns['db'], DatabaseInterface::CONNECT_CONTROL); + $columns_list = (array)$this->dbi->fetchResult( + $query, null, null, DatabaseInterface::CONNECT_CONTROL + ); + $this->handleColumnExtra($columns_list); + return json_encode($columns_list); + } + + /** + * Get HTML for "check all" check box with "with selected" dropdown + * + * @param string $pmaThemeImage pma theme image url + * @param string $text_dir url for text directory + * + * @return string $html_output + */ + public function getTableFooter($pmaThemeImage, $text_dir) + { + $html_output = Template::get('select_all') + ->render( + array( + 'pma_theme_image' => $pmaThemeImage, + 'text_dir' => $text_dir, + 'form_name' => 'tableslistcontainer', + ) + ); + $html_output .= Util::getButtonOrImage( + 'edit_central_columns', 'mult_submit change_central_columns', + __('Edit'), 'b_edit', 'edit central columns' + ); + $html_output .= Util::getButtonOrImage( + 'delete_central_columns', 'mult_submit', + __('Delete'), 'b_drop', + 'remove_from_central_columns' + ); + return $html_output; + } + + /** + * function generate and return the table footer for + * multiple edit central columns page + * + * @return string html for table footer in central columns multi edit page + */ + private function getEditTableFooter() + { + $html_output = '
        ' + . '' + . '
        '; + return $html_output; + } + + /** + * Column `col_extra` is used to store both extra and attributes for a column. + * This method separates them. + * + * @param array &$columns_list columns list + * + * @return void + */ + private function handleColumnExtra(array &$columns_list) + { + foreach ($columns_list as &$row) { + $vals = explode(',', $row['col_extra']); + + if (in_array('BINARY', $vals)) { + $row['col_attribute'] = 'BINARY'; + } elseif (in_array('UNSIGNED', $vals)) { + $row['col_attribute'] = 'UNSIGNED'; + } elseif (in_array('UNSIGNED ZEROFILL', $vals)) { + $row['col_attribute'] = 'UNSIGNED ZEROFILL'; + } elseif (in_array('on update CURRENT_TIMESTAMP', $vals)) { + $row['col_attribute'] = 'on update CURRENT_TIMESTAMP'; + } else { + $row['col_attribute'] = ''; + } + + if (in_array('auto_increment', $vals)) { + $row['col_extra'] = 'auto_increment'; + } else { + $row['col_extra'] = ''; + } + } + } + + /** + * build html for adding a new user defined column to central list + * + * @param string $db current database + * @param integer $total_rows number of rows in central columns + * + * @return string html of the form to let user add a new user defined column to the + * list + */ + public function getHtmlForAddNewColumn($db, $total_rows) + { + $addNewColumn = '
        ' + . '+ ' . __('Add new column') . '' + . '
        ' + . Url::getHiddenInputs( + $db + ) + . '' + . '
        ' + . ''; + $addNewColumn .= $this->getTableHeader(); + $addNewColumn .= '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . ' ' + . ''; + $addNewColumn .= '
        ' + . Template::get('columns_definitions/column_name')->render(array( + 'column_number' => 0, + 'ci' => 0, + 'ci_offset' => 0, + 'column_meta' => array(), + 'cfg_relation' => array( + 'centralcolumnswork' => false + ), + 'max_rows' => $this->maxRows, + )) + . '' + . Template::get('columns_definitions/column_type') + ->render( + array( + 'column_number' => 0, + 'ci' => 1, + 'ci_offset' => 0, + 'type_upper' => '', + 'column_meta' => array() + ) + ) + . '' + . Template::get('columns_definitions/column_length')->render( + array( + 'column_number' => 0, + 'ci' => 2, + 'ci_offset' => 0, + 'length_values_input_size' => 8, + 'length_to_display' => '' + ) + ) + . '' + . Template::get('columns_definitions/column_default') + ->render( + array( + 'column_number' => 0, + 'ci' => 3, + 'ci_offset' => 0, + 'type_upper' => '', + 'column_meta' => array(), + 'char_editing' => $this->charEditing, + ) + ) + . '' + . Charsets::getCollationDropdownBox( + $this->dbi, + $this->disableIs, + 'field_collation[0]', + 'field_0_4', null, false + ) + . '' + . Template::get('columns_definitions/column_attribute') + ->render( + array( + 'column_number' => 0, + 'ci' => 5, + 'ci_offset' => 0, + 'extracted_columnspec' => array(), + 'column_meta' => array(), + 'submit_attribute' => false, + 'attribute_types' => $this->dbi->types->getAttributes(), + ) + ) + . '' + . Template::get('columns_definitions/column_null') + ->render( + array( + 'column_number' => 0, + 'ci' => 6, + 'ci_offset' => 0, + 'column_meta' => array() + ) + ) + . '' + . Template::get('columns_definitions/column_extra')->render( + array( + 'column_number' => 0, + 'ci' => 7, + 'ci_offset' => 0, + 'column_meta' => array() + ) + ) + . '' + . '
        '; + return $addNewColumn; + } + + /** + * Get HTML for editing page central columns + * + * @param array $selected_fld Array containing the selected fields + * @param string $selected_db String containing the name of database + * + * @return string HTML for complete editing page for central columns + */ + public function getHtmlForEditingPage(array $selected_fld, $selected_db) + { + $html = '
        '; + $header_cells = array( + __('Name'), __('Type'), __('Length/Values'), __('Default'), + __('Collation'), __('Attributes'), __('Null'), __('A_I') + ); + $html .= $this->getEditTableHeader($header_cells); + $selected_fld_safe = array(); + foreach ($selected_fld as $key) { + $selected_fld_safe[] = $this->dbi->escapeString($key); + } + $columns_list = implode("','", $selected_fld_safe); + $columns_list = "'" . $columns_list . "'"; + $list_detail_cols = $this->findExistingColNames($selected_db, $columns_list, true); + $row_num = 0; + foreach ($list_detail_cols as $row) { + $tableHtmlRow = $this->getHtmlForEditTableRow( + $row, + $row_num + ); + $html .= $tableHtmlRow; + $row_num++; + } + $html .= ''; + $html .= $this->getEditTableFooter(); + $html .= '
        '; + return $html; + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Charsets.php b/php/apps/phpmyadmin49/libraries/classes/Charsets.php new file mode 100644 index 00000000..cfb6cb83 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Charsets.php @@ -0,0 +1,700 @@ + 'big5', + 'cp-866' => 'cp866', + 'euc-jp' => 'ujis', + 'euc-kr' => 'euckr', + 'gb2312' => 'gb2312', + 'gbk' => 'gbk', + 'iso-8859-1' => 'latin1', + 'iso-8859-2' => 'latin2', + 'iso-8859-7' => 'greek', + 'iso-8859-8' => 'hebrew', + 'iso-8859-8-i' => 'hebrew', + 'iso-8859-9' => 'latin5', + 'iso-8859-13' => 'latin7', + 'iso-8859-15' => 'latin1', + 'koi8-r' => 'koi8r', + 'shift_jis' => 'sjis', + 'tis-620' => 'tis620', + 'utf-8' => 'utf8', + 'windows-1250' => 'cp1250', + 'windows-1251' => 'cp1251', + 'windows-1252' => 'latin1', + 'windows-1256' => 'cp1256', + 'windows-1257' => 'cp1257', + ); + + private static $_charsets = array(); + + /** + * The charset for the server + * + * @var string + */ + private static $_charset_server; + + private static $_charsets_descriptions = array(); + private static $_collations = array(); + private static $_default_collations = array(); + + /** + * Loads charset data from the MySQL server. + * + * @param DatabaseInterface $dbi DatabaseInterface instance + * @param boolean $disableIs Disable use of INFORMATION_SCHEMA + * + * @return void + */ + private static function loadCharsets(DatabaseInterface $dbi, $disableIs) + { + /* Data already loaded */ + if (count(self::$_charsets) > 0) { + return; + } + + if ($disableIs) { + $sql = 'SHOW CHARACTER SET'; + } else { + $sql = 'SELECT `CHARACTER_SET_NAME` AS `Charset`,' + . ' `DESCRIPTION` AS `Description`' + . ' FROM `information_schema`.`CHARACTER_SETS`'; + } + $res = $dbi->query($sql); + + self::$_charsets = array(); + while ($row = $dbi->fetchAssoc($res)) { + $name = $row['Charset']; + self::$_charsets[] = $name; + self::$_charsets_descriptions[$name] = $row['Description']; + } + $dbi->freeResult($res); + + sort(self::$_charsets, SORT_STRING); + } + + /** + * Loads collation data from the MySQL server. + * + * @param DatabaseInterface $dbi DatabaseInterface instance + * @param boolean $disableIs Disable use of INFORMATION_SCHEMA + * + * @return void + */ + private static function loadCollations(DatabaseInterface $dbi, $disableIs) + { + /* Data already loaded */ + if (count(self::$_collations) > 0) { + return; + } + + if ($disableIs) { + $sql = 'SHOW COLLATION'; + } else { + $sql = 'SELECT `CHARACTER_SET_NAME` AS `Charset`,' + . ' `COLLATION_NAME` AS `Collation`, `IS_DEFAULT` AS `Default`' + . ' FROM `information_schema`.`COLLATIONS`'; + } + + $res = $dbi->query($sql); + while ($row = $dbi->fetchAssoc($res)) { + $char_set_name = $row['Charset']; + $name = $row['Collation']; + self::$_collations[$char_set_name][] = $name; + if ($row['Default'] == 'Yes' || $row['Default'] == '1') { + self::$_default_collations[$char_set_name] = $name; + } + } + $dbi->freeResult($res); + + foreach (self::$_collations as $key => $value) { + sort(self::$_collations[$key], SORT_STRING); + } + } + + /** + * Get current MySQL server charset. + * + * @param DatabaseInterface $dbi DatabaseInterface instance + * + * @return string + */ + public static function getServerCharset(DatabaseInterface $dbi) + { + if (self::$_charset_server) { + return self::$_charset_server; + } else { + self::$_charset_server = $dbi->getVariable('character_set_server'); + return self::$_charset_server; + } + } + + /** + * Get MySQL charsets + * + * @param DatabaseInterface $dbi DatabaseInterface instance + * @param boolean $disableIs Disable use of INFORMATION_SCHEMA + * + * @return array + */ + public static function getMySQLCharsets(DatabaseInterface $dbi, $disableIs) + { + self::loadCharsets($dbi, $disableIs); + return self::$_charsets; + } + + /** + * Get MySQL charsets descriptions + * + * @param DatabaseInterface $dbi DatabaseInterface instance + * @param boolean $disableIs Disable use of INFORMATION_SCHEMA + * + * @return array + */ + public static function getMySQLCharsetsDescriptions(DatabaseInterface $dbi, $disableIs) + { + self::loadCharsets($dbi, $disableIs); + return self::$_charsets_descriptions; + } + + /** + * Get MySQL collations + * + * @param DatabaseInterface $dbi DatabaseInterface instance + * @param boolean $disableIs Disable use of INFORMATION_SCHEMA + * + * @return array + */ + public static function getMySQLCollations(DatabaseInterface $dbi, $disableIs) + { + self::loadCollations($dbi, $disableIs); + return self::$_collations; + } + + /** + * Get MySQL default collations + * + * @param DatabaseInterface $dbi DatabaseInterface instance + * @param boolean $disableIs Disable use of INFORMATION_SCHEMA + * + * @return array + */ + public static function getMySQLCollationsDefault(DatabaseInterface $dbi, $disableIs) + { + self::loadCollations($dbi, $disableIs); + return self::$_default_collations; + } + + /** + * Generate charset dropdown box + * + * @param DatabaseInterface $dbi DatabaseInterface instance + * @param boolean $disableIs Disable use of INFORMATION_SCHEMA + * @param string $name Element name + * @param string $id Element id + * @param null|string $default Default value + * @param bool $label Label + * @param bool $submitOnChange Submit on change + * + * @return string + */ + public static function getCharsetDropdownBox( + DatabaseInterface $dbi, + $disableIs, + $name = null, + $id = null, + $default = null, + $label = true, + $submitOnChange = false + ) { + self::loadCharsets($dbi, $disableIs); + if (empty($name)) { + $name = 'character_set'; + } + + $return_str = '' . "\n"; + + return $return_str; + } + + /** + * Generate collation dropdown box + * + * @param DatabaseInterface $dbi DatabaseInterface instance + * @param boolean $disableIs Disable use of INFORMATION_SCHEMA + * @param string $name Element name + * @param string $id Element id + * @param null|string $default Default value + * @param bool $label Label + * @param bool $submitOnChange Submit on change + * + * @return string + */ + public static function getCollationDropdownBox( + DatabaseInterface $dbi, + $disableIs, + $name = null, + $id = null, + $default = null, + $label = true, + $submitOnChange = false + ) { + self::loadCharsets($dbi, $disableIs); + self::loadCollations($dbi, $disableIs); + if (empty($name)) { + $name = 'collation'; + } + + $return_str = '' . "\n"; + + return $return_str; + } + + /** + * Returns description for given collation + * + * @param string $collation MySQL collation string + * + * @return string collation description + */ + public static function getCollationDescr($collation) + { + $parts = explode('_', $collation); + + $name = __('Unknown'); + $variant = null; + $suffixes = array(); + $unicode = false; + $unknown = false; + + $level = 0; + foreach ($parts as $part) { + if ($level == 0) { + /* Next will be language */ + $level = 1; + /* First should be charset */ + switch ($part) { + case 'binary': + $name = _pgettext('Collation', 'Binary'); + break; + // Unicode charsets + case 'utf8mb4': + $variant = 'UCA 4.0.0'; + // Fall through to other unicode + case 'ucs2': + case 'utf8': + case 'utf16': + case 'utf16le': + case 'utf16be': + case 'utf32': + $name = _pgettext('Collation', 'Unicode'); + $unicode = true; + break; + // West European charsets + case 'ascii': + case 'cp850': + case 'dec8': + case 'hp8': + case 'latin1': + case 'macroman': + $name = _pgettext('Collation', 'West European'); + break; + // Central European charsets + case 'cp1250': + case 'cp852': + case 'latin2': + case 'macce': + $name = _pgettext('Collation', 'Central European'); + break; + // Russian charsets + case 'cp866': + case 'koi8r': + $name = _pgettext('Collation', 'Russian'); + break; + // Chinese charsets + case 'gb2312': + case 'gbk': + $name = _pgettext('Collation', 'Simplified Chinese'); + break; + case 'big5': + $name = _pgettext('Collation', 'Traditional Chinese'); + break; + case 'gb18030': + $name = _pgettext('Collation', 'Chinese'); + $unicode = true; + break; + // Japanese charsets + case 'sjis': + case 'ujis': + case 'cp932': + case 'eucjpms': + $name = _pgettext('Collation', 'Japanese'); + break; + // Baltic charsets + case 'cp1257': + case 'latin7': + $name = _pgettext('Collation', 'Baltic'); + break; + // Other + case 'armscii8': + case 'armscii': + $name = _pgettext('Collation', 'Armenian'); + break; + case 'cp1251': + $name = _pgettext('Collation', 'Cyrillic'); + break; + case 'cp1256': + $name = _pgettext('Collation', 'Arabic'); + break; + case 'euckr': + $name = _pgettext('Collation', 'Korean'); + break; + case 'hebrew': + $name = _pgettext('Collation', 'Hebrew'); + break; + case 'geostd8': + $name = _pgettext('Collation', 'Georgian'); + break; + case 'greek': + $name = _pgettext('Collation', 'Greek'); + break; + case 'keybcs2': + $name = _pgettext('Collation', 'Czech-Slovak'); + break; + case 'koi8u': + $name = _pgettext('Collation', 'Ukrainian'); + break; + case 'latin5': + $name = _pgettext('Collation', 'Turkish'); + break; + case 'swe7': + $name = _pgettext('Collation', 'Swedish'); + break; + case 'tis620': + $name = _pgettext('Collation', 'Thai'); + break; + default: + $name = _pgettext('Collation', 'Unknown'); + $unknown = true; + break; + } + continue; + } + if ($level == 1) { + /* Next will be variant unless changed later */ + $level = 4; + /* Locale name or code */ + $found = true; + switch ($part) { + case 'general': + break; + case 'bulgarian': + case 'bg': + $name = _pgettext('Collation', 'Bulgarian'); + break; + case 'chinese': + case 'cn': + case 'zh': + if ($unicode) { + $name = _pgettext('Collation', 'Chinese'); + } + break; + case 'croatian': + case 'hr': + $name = _pgettext('Collation', 'Croatian'); + break; + case 'czech': + case 'cs': + $name = _pgettext('Collation', 'Czech'); + break; + case 'danish': + case 'da': + $name = _pgettext('Collation', 'Danish'); + break; + case 'english': + case 'en': + $name = _pgettext('Collation', 'English'); + break; + case 'esperanto': + case 'eo': + $name = _pgettext('Collation', 'Esperanto'); + break; + case 'estonian': + case 'et': + $name = _pgettext('Collation', 'Estonian'); + break; + case 'german1': + $name = _pgettext('Collation', 'German (dictionary order)'); + break; + case 'german2': + $name = _pgettext('Collation', 'German (phone book order)'); + break; + case 'german': + case 'de': + /* Name is set later */ + $level = 2; + break; + case 'hungarian': + case 'hu': + $name = _pgettext('Collation', 'Hungarian'); + break; + case 'icelandic': + case 'is': + $name = _pgettext('Collation', 'Icelandic'); + break; + case 'japanese': + case 'ja': + $name = _pgettext('Collation', 'Japanese'); + break; + case 'la': + $name = _pgettext('Collation', 'Classical Latin'); + break; + case 'latvian': + case 'lv': + $name = _pgettext('Collation', 'Latvian'); + break; + case 'lithuanian': + case 'lt': + $name = _pgettext('Collation', 'Lithuanian'); + break; + case 'korean': + case 'ko': + $name = _pgettext('Collation', 'Korean'); + break; + case 'myanmar': + case 'my': + $name = _pgettext('Collation', 'Burmese'); + break; + case 'persian': + $name = _pgettext('Collation', 'Persian'); + break; + case 'polish': + case 'pl': + $name = _pgettext('Collation', 'Polish'); + break; + case 'roman': + $name = _pgettext('Collation', 'West European'); + break; + case 'romanian': + case 'ro': + $name = _pgettext('Collation', 'Romanian'); + break; + case 'ru': + $name = _pgettext('Collation', 'Russian'); + break; + case 'si': + case 'sinhala': + $name = _pgettext('Collation', 'Sinhalese'); + break; + case 'slovak': + case 'sk': + $name = _pgettext('Collation', 'Slovak'); + break; + case 'slovenian': + case 'sl': + $name = _pgettext('Collation', 'Slovenian'); + break; + case 'spanish': + $name = _pgettext('Collation', 'Spanish (modern)'); + break; + case 'es': + /* Name is set later */ + $level = 3; + break; + case 'spanish2': + $name = _pgettext('Collation', 'Spanish (traditional)'); + break; + case 'swedish': + case 'sv': + $name = _pgettext('Collation', 'Swedish'); + break; + case 'thai': + case 'th': + $name = _pgettext('Collation', 'Thai'); + break; + case 'turkish': + case 'tr': + $name = _pgettext('Collation', 'Turkish'); + break; + case 'ukrainian': + case 'uk': + $name = _pgettext('Collation', 'Ukrainian'); + break; + case 'vietnamese': + case 'vi': + $name = _pgettext('Collation', 'Vietnamese'); + break; + case 'unicode': + if ($unknown) { + $name = _pgettext('Collation', 'Unicode'); + } + break; + default: + $found = false; + } + if ($found) { + continue; + } + // Not parsed token, fall to next level + } + if ($level == 2) { + /* Next will be variant */ + $level = 4; + /* Germal variant */ + if ($part == 'pb') { + $name = _pgettext('Collation', 'German (phone book order)'); + continue; + } + $name = _pgettext('Collation', 'German (dictionary order)'); + // Not parsed token, fall to next level + } + if ($level == 3) { + /* Next will be variant */ + $level = 4; + /* Spanish variant */ + if ($part == 'trad') { + $name = _pgettext('Collation', 'Spanish (traditional)'); + continue; + } + $name = _pgettext('Collation', 'Spanish (modern)'); + // Not parsed token, fall to next level + } + if ($level == 4) { + /* Next will be suffix */ + $level = 5; + /* Variant */ + $found = true; + switch ($part) { + case '0900': + $variant = 'UCA 9.0.0'; + break; + case '520': + $variant = 'UCA 5.2.0'; + break; + case 'mysql561': + $variant = 'MySQL 5.6.1'; + break; + case 'mysql500': + $variant = 'MySQL 5.0.0'; + break; + default: + $found = false; + } + if ($found) { + continue; + } + // Not parsed token, fall to next level + } + if ($level == 5) { + /* Suffixes */ + switch ($part) { + case 'ci': + $suffixes[] = _pgettext('Collation variant', 'case-insensitive'); + break; + case 'cs': + $suffixes[] = _pgettext('Collation variant', 'case-sensitive'); + break; + case 'ai': + $suffixes[] = _pgettext('Collation variant', 'accent-insensitive'); + break; + case 'as': + $suffixes[] = _pgettext('Collation variant', 'accent-sensitive'); + break; + case 'ks': + $suffixes[] = _pgettext('Collation variant', 'kana-sensitive'); + break; + case 'w2': + case 'l2': + $suffixes[] = _pgettext('Collation variant', 'multi-level'); + break; + case 'bin': + $suffixes[] = _pgettext('Collation variant', 'binary'); + break; + case 'nopad': + $suffixes[] = _pgettext('Collation variant', 'no-pad'); + break; + } + } + } + + $result = $name; + if (! is_null($variant)) { + $result .= ' (' . $variant . ')'; + } + if (count($suffixes) > 0) { + $result .= ', ' . implode(', ', $suffixes); + } + return $result; + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/CheckUserPrivileges.php b/php/apps/phpmyadmin49/libraries/classes/CheckUserPrivileges.php new file mode 100644 index 00000000..7b3770d3 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/CheckUserPrivileges.php @@ -0,0 +1,338 @@ +dbi = $dbi; + } + + /** + * Extracts details from a result row of a SHOW GRANT query + * + * @param string $row grant row + * + * @return array + */ + public function getItemsFromShowGrantsRow($row) + { + $db_name_offset = mb_strpos($row, ' ON ') + 4; + + $tblname_end_offset = mb_strpos($row, ' TO '); + $tblname_start_offset = false; + + if ($__tblname_start_offset = mb_strpos($row, '`.', $db_name_offset)) { + if ($__tblname_start_offset < $tblname_end_offset) { + $tblname_start_offset = $__tblname_start_offset + 1; + } + } + + if (!$tblname_start_offset) { + $tblname_start_offset = mb_strpos($row, '.', $db_name_offset); + } + + $show_grants_dbname = mb_substr( + $row, + $db_name_offset, + $tblname_start_offset - $db_name_offset + ); + + $show_grants_dbname = Util::unQuote($show_grants_dbname, '`'); + + $show_grants_str = mb_substr( + $row, + 6, + (mb_strpos($row, ' ON ') - 6) + ); + + $show_grants_tblname = mb_substr( + $row, + $tblname_start_offset + 1, + $tblname_end_offset - $tblname_start_offset - 1 + ); + $show_grants_tblname = Util::unQuote($show_grants_tblname, '`'); + + return array( + $show_grants_str, + $show_grants_dbname, + $show_grants_tblname + ); + } + + /** + * Check if user has required privileges for + * performing 'Adjust privileges' operations + * + * @param string $show_grants_str string containing grants for user + * @param string $show_grants_dbname name of db extracted from grant string + * @param string $show_grants_tblname name of table extracted from grant string + * + * @return void + */ + public function checkRequiredPrivilegesForAdjust( + $show_grants_str, + $show_grants_dbname, + $show_grants_tblname + ) { + // '... ALL PRIVILEGES ON *.* ...' OR '... ALL PRIVILEGES ON `mysql`.* ..' + // OR + // SELECT, INSERT, UPDATE, DELETE .... ON *.* OR `mysql`.* + if ($show_grants_str == 'ALL' + || $show_grants_str == 'ALL PRIVILEGES' + || (mb_strpos( + $show_grants_str, 'SELECT, INSERT, UPDATE, DELETE' + ) !== false) + ) { + if ($show_grants_dbname == '*' + && $show_grants_tblname == '*' + ) { + $GLOBALS['col_priv'] = true; + $GLOBALS['db_priv'] = true; + $GLOBALS['proc_priv'] = true; + $GLOBALS['table_priv'] = true; + + if ($show_grants_str == 'ALL PRIVILEGES' + || $show_grants_str == 'ALL' + ) { + $GLOBALS['is_reload_priv'] = true; + } + } + + // check for specific tables in `mysql` db + // Ex. '... ALL PRIVILEGES on `mysql`.`columns_priv` .. ' + if ($show_grants_dbname == 'mysql') { + switch ($show_grants_tblname) { + case "columns_priv": + $GLOBALS['col_priv'] = true; + break; + case "db": + $GLOBALS['db_priv'] = true; + break; + case "procs_priv": + $GLOBALS['proc_priv'] = true; + break; + case "tables_priv": + $GLOBALS['table_priv'] = true; + break; + case "*": + $GLOBALS['col_priv'] = true; + $GLOBALS['db_priv'] = true; + $GLOBALS['proc_priv'] = true; + $GLOBALS['table_priv'] = true; + break; + default: + } + } + } + } + + /** + * sets privilege information extracted from SHOW GRANTS result + * + * Detection for some CREATE privilege. + * + * Since MySQL 4.1.2, we can easily detect current user's grants using $userlink + * (no control user needed) and we don't have to try any other method for + * detection + * + * @todo fix to get really all privileges, not only explicitly defined for this user + * from MySQL manual: (https://dev.mysql.com/doc/refman/5.0/en/show-grants.html) + * SHOW GRANTS displays only the privileges granted explicitly to the named + * account. Other privileges might be available to the account, but they are not + * displayed. For example, if an anonymous account exists, the named account + * might be able to use its privileges, but SHOW GRANTS will not display them. + * + * @return void + */ + public function analyseShowGrant() + { + if (Util::cacheExists('is_create_db_priv')) { + $GLOBALS['is_create_db_priv'] = Util::cacheGet( + 'is_create_db_priv' + ); + $GLOBALS['is_reload_priv'] = Util::cacheGet( + 'is_reload_priv' + ); + $GLOBALS['db_to_create'] = Util::cacheGet( + 'db_to_create' + ); + $GLOBALS['dbs_where_create_table_allowed'] = Util::cacheGet( + 'dbs_where_create_table_allowed' + ); + $GLOBALS['dbs_to_test'] = Util::cacheGet( + 'dbs_to_test' + ); + + $GLOBALS['db_priv'] = Util::cacheGet( + 'db_priv' + ); + $GLOBALS['col_priv'] = Util::cacheGet( + 'col_priv' + ); + $GLOBALS['table_priv'] = Util::cacheGet( + 'table_priv' + ); + $GLOBALS['proc_priv'] = Util::cacheGet( + 'proc_priv' + ); + + return; + } + + // defaults + $GLOBALS['is_create_db_priv'] = false; + $GLOBALS['is_reload_priv'] = false; + $GLOBALS['db_to_create'] = ''; + $GLOBALS['dbs_where_create_table_allowed'] = array(); + $GLOBALS['dbs_to_test'] = $this->dbi->getSystemSchemas(); + $GLOBALS['proc_priv'] = false; + $GLOBALS['db_priv'] = false; + $GLOBALS['col_priv'] = false; + $GLOBALS['table_priv'] = false; + + $rs_usr = $this->dbi->tryQuery('SHOW GRANTS'); + + if (! $rs_usr) { + return; + } + + $re0 = '(^|(\\\\\\\\)+|[^\\\\])'; // non-escaped wildcards + $re1 = '(^|[^\\\\])(\\\)+'; // escaped wildcards + + while ($row = $this->dbi->fetchRow($rs_usr)) { + list( + $show_grants_str, + $show_grants_dbname, + $show_grants_tblname + ) = $this->getItemsFromShowGrantsRow($row[0]); + + if ($show_grants_dbname == '*') { + if ($show_grants_str != 'USAGE') { + $GLOBALS['dbs_to_test'] = false; + } + } elseif ($GLOBALS['dbs_to_test'] !== false) { + $GLOBALS['dbs_to_test'][] = $show_grants_dbname; + } + + if ( + mb_strpos($show_grants_str,'RELOAD') !== false + ) { + $GLOBALS['is_reload_priv'] = true; + } + + // check for the required privileges for adjust + $this->checkRequiredPrivilegesForAdjust( + $show_grants_str, + $show_grants_dbname, + $show_grants_tblname + ); + + /** + * @todo if we find CREATE VIEW but not CREATE, do not offer + * the create database dialog box + */ + if ($show_grants_str == 'ALL' + || $show_grants_str == 'ALL PRIVILEGES' + || $show_grants_str == 'CREATE' + || strpos($show_grants_str, 'CREATE,') !== false + ) { + if ($show_grants_dbname == '*') { + // a global CREATE privilege + $GLOBALS['is_create_db_priv'] = true; + $GLOBALS['is_reload_priv'] = true; + $GLOBALS['db_to_create'] = ''; + $GLOBALS['dbs_where_create_table_allowed'][] = '*'; + // @todo we should not break here, cause GRANT ALL *.* + // could be revoked by a later rule like GRANT SELECT ON db.* + break; + } else { + // this array may contain wildcards + $GLOBALS['dbs_where_create_table_allowed'][] = $show_grants_dbname; + + $dbname_to_test = Util::backquote($show_grants_dbname); + + if ($GLOBALS['is_create_db_priv']) { + // no need for any more tests if we already know this + continue; + } + + // does this db exist? + if ((preg_match('/' . $re0 . '%|_/', $show_grants_dbname) + && ! preg_match('/\\\\%|\\\\_/', $show_grants_dbname)) + || (! $this->dbi->tryQuery( + 'USE ' . preg_replace( + '/' . $re1 . '(%|_)/', '\\1\\3', $dbname_to_test + ) + ) + && mb_substr($this->dbi->getError(), 1, 4) != 1044) + ) { + /** + * Do not handle the underscore wildcard + * (this case must be rare anyway) + */ + $GLOBALS['db_to_create'] = preg_replace( + '/' . $re0 . '%/', '\\1', + $show_grants_dbname + ); + $GLOBALS['db_to_create'] = preg_replace( + '/' . $re1 . '(%|_)/', '\\1\\3', + $GLOBALS['db_to_create'] + ); + $GLOBALS['is_create_db_priv'] = true; + + /** + * @todo collect $GLOBALS['db_to_create'] into an array, + * to display a drop-down in the "Create database" dialog + */ + // we don't break, we want all possible databases + //break; + } // end if + } // end elseif + } // end if + + } // end while + + $this->dbi->freeResult($rs_usr); + + // must also cacheUnset() them in + // PhpMyAdmin\Plugins\Auth\AuthenticationCookie + Util::cacheSet('is_create_db_priv', $GLOBALS['is_create_db_priv']); + Util::cacheSet('is_reload_priv', $GLOBALS['is_reload_priv']); + Util::cacheSet('db_to_create', $GLOBALS['db_to_create']); + Util::cacheSet( + 'dbs_where_create_table_allowed', + $GLOBALS['dbs_where_create_table_allowed'] + ); + Util::cacheSet('dbs_to_test', $GLOBALS['dbs_to_test']); + + Util::cacheSet('proc_priv', $GLOBALS['proc_priv']); + Util::cacheSet('table_priv', $GLOBALS['table_priv']); + Util::cacheSet('col_priv', $GLOBALS['col_priv']); + Util::cacheSet('db_priv', $GLOBALS['db_priv']); + } // end function +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Config.php b/php/apps/phpmyadmin49/libraries/classes/Config.php new file mode 100644 index 00000000..1c1f3b6d --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Config.php @@ -0,0 +1,1844 @@ +settings = array('is_setup' => false); + + // functions need to refresh in case of config file changed goes in + // PhpMyAdmin\Config::load() + $this->load($source); + + // other settings, independent from config file, comes in + $this->checkSystem(); + + $this->base_settings = $this->settings; + + $this->userPreferences = new UserPreferences(); + } + + /** + * sets system and application settings + * + * @return void + */ + public function checkSystem() + { + $this->set('PMA_VERSION', '4.9.1'); + /* Major version */ + $this->set( + 'PMA_MAJOR_VERSION', + implode('.', array_slice(explode('.', $this->get('PMA_VERSION'), 3), 0, 2)) + ); + + $this->checkWebServerOs(); + $this->checkWebServer(); + $this->checkGd2(); + $this->checkClient(); + $this->checkUpload(); + $this->checkUploadSize(); + $this->checkOutputCompression(); + } + + /** + * whether to use gzip output compression or not + * + * @return void + */ + public function checkOutputCompression() + { + // If zlib output compression is set in the php configuration file, no + // output buffering should be run + if (ini_get('zlib.output_compression')) { + $this->set('OBGzip', false); + } + + // enable output-buffering (if set to 'auto') + if (strtolower($this->get('OBGzip')) == 'auto') { + $this->set('OBGzip', true); + } + } + + /** + * Sets the client platform based on user agent + * + * @param string $user_agent the user agent + * + * @return void + */ + private function _setClientPlatform($user_agent) + { + if (mb_strstr($user_agent, 'Win')) { + $this->set('PMA_USR_OS', 'Win'); + } elseif (mb_strstr($user_agent, 'Mac')) { + $this->set('PMA_USR_OS', 'Mac'); + } elseif (mb_strstr($user_agent, 'Linux')) { + $this->set('PMA_USR_OS', 'Linux'); + } elseif (mb_strstr($user_agent, 'Unix')) { + $this->set('PMA_USR_OS', 'Unix'); + } elseif (mb_strstr($user_agent, 'OS/2')) { + $this->set('PMA_USR_OS', 'OS/2'); + } else { + $this->set('PMA_USR_OS', 'Other'); + } + } + + /** + * Determines platform (OS), browser and version of the user + * Based on a phpBuilder article: + * + * @see http://www.phpbuilder.net/columns/tim20000821.php + * + * @return void + */ + public function checkClient() + { + if (Core::getenv('HTTP_USER_AGENT')) { + $HTTP_USER_AGENT = Core::getenv('HTTP_USER_AGENT'); + } else { + $HTTP_USER_AGENT = ''; + } + + // 1. Platform + $this->_setClientPlatform($HTTP_USER_AGENT); + + // 2. browser and version + // (must check everything else before Mozilla) + + $is_mozilla = preg_match( + '@Mozilla/([0-9]\.[0-9]{1,2})@', + $HTTP_USER_AGENT, + $mozilla_version + ); + + if (preg_match( + '@Opera(/| )([0-9]\.[0-9]{1,2})@', + $HTTP_USER_AGENT, + $log_version + )) { + $this->set('PMA_USR_BROWSER_VER', $log_version[2]); + $this->set('PMA_USR_BROWSER_AGENT', 'OPERA'); + } elseif (preg_match( + '@(MS)?IE ([0-9]{1,2}\.[0-9]{1,2})@', + $HTTP_USER_AGENT, + $log_version + )) { + $this->set('PMA_USR_BROWSER_VER', $log_version[2]); + $this->set('PMA_USR_BROWSER_AGENT', 'IE'); + } elseif (preg_match( + '@Trident/(7)\.0@', + $HTTP_USER_AGENT, + $log_version + )) { + $this->set('PMA_USR_BROWSER_VER', intval($log_version[1]) + 4); + $this->set('PMA_USR_BROWSER_AGENT', 'IE'); + } elseif (preg_match( + '@OmniWeb/([0-9]{1,3})@', + $HTTP_USER_AGENT, + $log_version + )) { + $this->set('PMA_USR_BROWSER_VER', $log_version[1]); + $this->set('PMA_USR_BROWSER_AGENT', 'OMNIWEB'); + // Konqueror 2.2.2 says Konqueror/2.2.2 + // Konqueror 3.0.3 says Konqueror/3 + } elseif (preg_match( + '@(Konqueror/)(.*)(;)@', + $HTTP_USER_AGENT, + $log_version + )) { + $this->set('PMA_USR_BROWSER_VER', $log_version[2]); + $this->set('PMA_USR_BROWSER_AGENT', 'KONQUEROR'); + // must check Chrome before Safari + } elseif ($is_mozilla + && preg_match('@Chrome/([0-9.]*)@', $HTTP_USER_AGENT, $log_version) + ) { + $this->set('PMA_USR_BROWSER_VER', $log_version[1]); + $this->set('PMA_USR_BROWSER_AGENT', 'CHROME'); + // newer Safari + } elseif ($is_mozilla + && preg_match('@Version/(.*) Safari@', $HTTP_USER_AGENT, $log_version) + ) { + $this->set( + 'PMA_USR_BROWSER_VER', $log_version[1] + ); + $this->set('PMA_USR_BROWSER_AGENT', 'SAFARI'); + // older Safari + } elseif ($is_mozilla + && preg_match('@Safari/([0-9]*)@', $HTTP_USER_AGENT, $log_version) + ) { + $this->set( + 'PMA_USR_BROWSER_VER', $mozilla_version[1] . '.' . $log_version[1] + ); + $this->set('PMA_USR_BROWSER_AGENT', 'SAFARI'); + // Firefox + } elseif (! mb_strstr($HTTP_USER_AGENT, 'compatible') + && preg_match('@Firefox/([\w.]+)@', $HTTP_USER_AGENT, $log_version) + ) { + $this->set( + 'PMA_USR_BROWSER_VER', $log_version[1] + ); + $this->set('PMA_USR_BROWSER_AGENT', 'FIREFOX'); + } elseif (preg_match('@rv:1\.9(.*)Gecko@', $HTTP_USER_AGENT)) { + $this->set('PMA_USR_BROWSER_VER', '1.9'); + $this->set('PMA_USR_BROWSER_AGENT', 'GECKO'); + } elseif ($is_mozilla) { + $this->set('PMA_USR_BROWSER_VER', $mozilla_version[1]); + $this->set('PMA_USR_BROWSER_AGENT', 'MOZILLA'); + } else { + $this->set('PMA_USR_BROWSER_VER', 0); + $this->set('PMA_USR_BROWSER_AGENT', 'OTHER'); + } + } + + /** + * Whether GD2 is present + * + * @return void + */ + public function checkGd2() + { + if ($this->get('GD2Available') == 'yes') { + $this->set('PMA_IS_GD2', 1); + return; + } + + if ($this->get('GD2Available') == 'no') { + $this->set('PMA_IS_GD2', 0); + return; + } + + if (!function_exists('imagecreatetruecolor')) { + $this->set('PMA_IS_GD2', 0); + return; + } + + if (function_exists('gd_info')) { + $gd_nfo = gd_info(); + if (mb_strstr($gd_nfo["GD Version"], '2.')) { + $this->set('PMA_IS_GD2', 1); + } else { + $this->set('PMA_IS_GD2', 0); + } + } else { + $this->set('PMA_IS_GD2', 0); + } + } + + /** + * Whether the Web server php is running on is IIS + * + * @return void + */ + public function checkWebServer() + { + // some versions return Microsoft-IIS, some Microsoft/IIS + // we could use a preg_match() but it's slower + if (Core::getenv('SERVER_SOFTWARE') + && stristr(Core::getenv('SERVER_SOFTWARE'), 'Microsoft') + && stristr(Core::getenv('SERVER_SOFTWARE'), 'IIS') + ) { + $this->set('PMA_IS_IIS', 1); + } else { + $this->set('PMA_IS_IIS', 0); + } + } + + /** + * Whether the os php is running on is windows or not + * + * @return void + */ + public function checkWebServerOs() + { + // Default to Unix or Equiv + $this->set('PMA_IS_WINDOWS', 0); + // If PHP_OS is defined then continue + if (defined('PHP_OS')) { + if (stristr(PHP_OS, 'win') && !stristr(PHP_OS, 'darwin')) { + // Is it some version of Windows + $this->set('PMA_IS_WINDOWS', 1); + } elseif (stristr(PHP_OS, 'OS/2')) { + // Is it OS/2 (No file permissions like Windows) + $this->set('PMA_IS_WINDOWS', 1); + } + } + } + + /** + * detects if Git revision + * @param string &$git_location (optional) verified git directory + * @return boolean + */ + public function isGitRevision(&$git_location = NULL) + { + // PMA config check + if (! $this->get('ShowGitRevision')) { + return false; + } + + // caching + if ( + isset($_SESSION['is_git_revision']) + && array_key_exists('git_location', $_SESSION) + ) { + // Define location using cached value + $git_location = $_SESSION['git_location']; + return $_SESSION['is_git_revision']; + } + + // find out if there is a .git folder + // or a .git file (--separate-git-dir) + $git = '.git'; + if (is_dir($git)) { + if (@is_file($git . '/config')) { + $git_location = $git; + } else { + $_SESSION['git_location'] = null; + $_SESSION['is_git_revision'] = false; + return false; + } + } elseif (is_file($git)) { + $contents = file_get_contents($git); + $gitmatch = array(); + // Matches expected format + if (! preg_match('/^gitdir: (.*)$/', + $contents, $gitmatch)) { + $_SESSION['git_location'] = null; + $_SESSION['is_git_revision'] = false; + return false; + } else { + if (@is_dir($gitmatch[1])) { + //Detected git external folder location + $git_location = $gitmatch[1]; + } else { + $_SESSION['git_location'] = null; + $_SESSION['is_git_revision'] = false; + return false; + } + } + } else { + $_SESSION['git_location'] = null; + $_SESSION['is_git_revision'] = false; + return false; + } + // Define session for caching + $_SESSION['git_location'] = $git_location; + $_SESSION['is_git_revision'] = true; + return true; + } + + /** + * detects Git revision, if running inside repo + * + * @return void + */ + public function checkGitRevision() + { + // find out if there is a .git folder + $git_folder = ''; + if (! $this->isGitRevision($git_folder)) { + $this->set('PMA_VERSION_GIT', 0); + return; + } + + if (! $ref_head = @file_get_contents($git_folder . '/HEAD')) { + $this->set('PMA_VERSION_GIT', 0); + return; + } + + if ($common_dir_contents = @file_get_contents($git_folder . '/commondir')) { + $git_folder = $git_folder . DIRECTORY_SEPARATOR . trim($common_dir_contents); + } + + $branch = false; + // are we on any branch? + if (strstr($ref_head, '/')) { + // remove ref: prefix + $ref_head = substr(trim($ref_head), 5); + if (substr($ref_head, 0, 11) === 'refs/heads/') { + $branch = substr($ref_head, 11); + } else { + $branch = basename($ref_head); + } + + $ref_file = $git_folder . '/' . $ref_head; + if (@file_exists($ref_file)) { + $hash = @file_get_contents($ref_file); + if (! $hash) { + $this->set('PMA_VERSION_GIT', 0); + return; + } + $hash = trim($hash); + } else { + // deal with packed refs + $packed_refs = @file_get_contents($git_folder . '/packed-refs'); + if (! $packed_refs) { + $this->set('PMA_VERSION_GIT', 0); + return; + } + // split file to lines + $ref_lines = explode("\n", $packed_refs); + foreach ($ref_lines as $line) { + // skip comments + if ($line[0] == '#') { + continue; + } + // parse line + $parts = explode(' ', $line); + // care only about named refs + if (count($parts) != 2) { + continue; + } + // have found our ref? + if ($parts[1] == $ref_head) { + $hash = $parts[0]; + break; + } + } + if (! isset($hash)) { + $this->set('PMA_VERSION_GIT', 0); + // Could not find ref + return; + } + } + } else { + $hash = trim($ref_head); + } + + $commit = false; + if (! preg_match('/^[0-9a-f]{40}$/i', $hash)) { + $commit = false; + } elseif (isset($_SESSION['PMA_VERSION_COMMITDATA_' . $hash])) { + $commit = $_SESSION['PMA_VERSION_COMMITDATA_' . $hash]; + } elseif (function_exists('gzuncompress')) { + $git_file_name = $git_folder . '/objects/' + . substr($hash, 0, 2) . '/' . substr($hash, 2); + if (@file_exists($git_file_name) ) { + if (! $commit = @file_get_contents($git_file_name)) { + $this->set('PMA_VERSION_GIT', 0); + return; + } + $commit = explode("\0", gzuncompress($commit), 2); + $commit = explode("\n", $commit[1]); + $_SESSION['PMA_VERSION_COMMITDATA_' . $hash] = $commit; + } else { + $pack_names = array(); + // work with packed data + $packs_file = $git_folder . '/objects/info/packs'; + if (@file_exists($packs_file) + && $packs = @file_get_contents($packs_file) + ) { + // File exists. Read it, parse the file to get the names of the + // packs. (to look for them in .git/object/pack directory later) + foreach (explode("\n", $packs) as $line) { + // skip blank lines + if (strlen(trim($line)) == 0) { + continue; + } + // skip non pack lines + if ($line[0] != 'P') { + continue; + } + // parse names + $pack_names[] = substr($line, 2); + } + } else { + // '.git/objects/info/packs' file can be missing + // (atlease in mysGit) + // File missing. May be we can look in the .git/object/pack + // directory for all the .pack files and use that list of + // files instead + $dirIterator = new DirectoryIterator( + $git_folder . '/objects/pack' + ); + foreach ($dirIterator as $file_info) { + $file_name = $file_info->getFilename(); + // if this is a .pack file + if ($file_info->isFile() && substr($file_name, -5) == '.pack' + ) { + $pack_names[] = $file_name; + } + } + } + $hash = strtolower($hash); + foreach ($pack_names as $pack_name) { + $index_name = str_replace('.pack', '.idx', $pack_name); + + // load index + $index_data = @file_get_contents( + $git_folder . '/objects/pack/' . $index_name + ); + if (! $index_data) { + continue; + } + // check format + if (substr($index_data, 0, 4) != "\377tOc") { + continue; + } + // check version + $version = unpack('N', substr($index_data, 4, 4)); + if ($version[1] != 2) { + continue; + } + // parse fanout table + $fanout = unpack( + "N*", + substr($index_data, 8, 256 * 4) + ); + + // find where we should search + $firstbyte = intval(substr($hash, 0, 2), 16); + // array is indexed from 1 and we need to get + // previous entry for start + if ($firstbyte == 0) { + $start = 0; + } else { + $start = $fanout[$firstbyte]; + } + $end = $fanout[$firstbyte + 1]; + + // stupid linear search for our sha + $found = false; + $offset = 8 + (256 * 4); + for ($position = $start; $position < $end; $position++) { + $sha = strtolower( + bin2hex( + substr($index_data, $offset + ($position * 20), 20) + ) + ); + if ($sha == $hash) { + $found = true; + break; + } + } + if (! $found) { + continue; + } + // read pack offset + $offset = 8 + (256 * 4) + (24 * $fanout[256]); + $pack_offset = unpack( + 'N', + substr($index_data, $offset + ($position * 4), 4) + ); + $pack_offset = $pack_offset[1]; + + // open pack file + $pack_file = fopen( + $git_folder . '/objects/pack/' . $pack_name, 'rb' + ); + if ($pack_file === false) { + continue; + } + // seek to start + fseek($pack_file, $pack_offset); + + // parse header + $header = ord(fread($pack_file, 1)); + $type = ($header >> 4) & 7; + $hasnext = ($header & 128) >> 7; + $size = $header & 0xf; + $offset = 4; + + while ($hasnext) { + $byte = ord(fread($pack_file, 1)); + $size |= ($byte & 0x7f) << $offset; + $hasnext = ($byte & 128) >> 7; + $offset += 7; + } + + // we care only about commit objects + if ($type != 1) { + continue; + } + + // read data + $commit = fread($pack_file, $size); + $commit = gzuncompress($commit); + $commit = explode("\n", $commit); + $_SESSION['PMA_VERSION_COMMITDATA_' . $hash] = $commit; + fclose($pack_file); + } + } + } + + $httpRequest = new HttpRequest(); + + // check if commit exists in Github + if ($commit !== false + && isset($_SESSION['PMA_VERSION_REMOTECOMMIT_' . $hash]) + ) { + $is_remote_commit = $_SESSION['PMA_VERSION_REMOTECOMMIT_' . $hash]; + } else { + $link = 'https://www.phpmyadmin.net/api/commit/' . $hash . '/'; + $is_found = $httpRequest->create($link, 'GET'); + switch($is_found) { + case false: + $is_remote_commit = false; + $_SESSION['PMA_VERSION_REMOTECOMMIT_' . $hash] = false; + break; + case null: + // no remote link for now, but don't cache this as Github is down + $is_remote_commit = false; + break; + default: + $is_remote_commit = true; + $_SESSION['PMA_VERSION_REMOTECOMMIT_' . $hash] = true; + if ($commit === false) { + // if no local commit data, try loading from Github + $commit_json = json_decode($is_found); + } + break; + } + } + + $is_remote_branch = false; + if ($is_remote_commit && $branch !== false) { + // check if branch exists in Github + if (isset($_SESSION['PMA_VERSION_REMOTEBRANCH_' . $hash])) { + $is_remote_branch = $_SESSION['PMA_VERSION_REMOTEBRANCH_' . $hash]; + } else { + $link = 'https://www.phpmyadmin.net/api/tree/' . $branch . '/'; + $is_found = $httpRequest->create($link, 'GET', true); + switch($is_found) { + case true: + $is_remote_branch = true; + $_SESSION['PMA_VERSION_REMOTEBRANCH_' . $hash] = true; + break; + case false: + $is_remote_branch = false; + $_SESSION['PMA_VERSION_REMOTEBRANCH_' . $hash] = false; + break; + case null: + // no remote link for now, but don't cache this as Github is down + $is_remote_branch = false; + break; + } + } + } + + if ($commit !== false) { + $author = array('name' => '', 'email' => '', 'date' => ''); + $committer = array('name' => '', 'email' => '', 'date' => ''); + + do { + $dataline = array_shift($commit); + $datalinearr = explode(' ', $dataline, 2); + $linetype = $datalinearr[0]; + if (in_array($linetype, array('author', 'committer'))) { + $user = $datalinearr[1]; + preg_match('/([^<]+)<([^>]+)> ([0-9]+)( [^ ]+)?/', $user, $user); + $user2 = array( + 'name' => trim($user[1]), + 'email' => trim($user[2]), + 'date' => date('Y-m-d H:i:s', $user[3])); + if (isset($user[4])) { + $user2['date'] .= $user[4]; + } + $$linetype = $user2; + } + } while ($dataline != ''); + $message = trim(implode(' ', $commit)); + + } elseif (isset($commit_json) && isset($commit_json->author) && isset($commit_json->committer) && isset($commit_json->message)) { + $author = array( + 'name' => $commit_json->author->name, + 'email' => $commit_json->author->email, + 'date' => $commit_json->author->date); + $committer = array( + 'name' => $commit_json->committer->name, + 'email' => $commit_json->committer->email, + 'date' => $commit_json->committer->date); + $message = trim($commit_json->message); + } else { + $this->set('PMA_VERSION_GIT', 0); + return; + } + + $this->set('PMA_VERSION_GIT', 1); + $this->set('PMA_VERSION_GIT_COMMITHASH', $hash); + $this->set('PMA_VERSION_GIT_BRANCH', $branch); + $this->set('PMA_VERSION_GIT_MESSAGE', $message); + $this->set('PMA_VERSION_GIT_AUTHOR', $author); + $this->set('PMA_VERSION_GIT_COMMITTER', $committer); + $this->set('PMA_VERSION_GIT_ISREMOTECOMMIT', $is_remote_commit); + $this->set('PMA_VERSION_GIT_ISREMOTEBRANCH', $is_remote_branch); + } + + /** + * loads default values from default source + * + * @return boolean success + */ + public function loadDefaults() + { + $cfg = array(); + if (! @file_exists($this->default_source)) { + $this->error_config_default_file = true; + return false; + } + $old_error_reporting = error_reporting(0); + ob_start(); + $GLOBALS['pma_config_loading'] = true; + $eval_result = include $this->default_source; + $GLOBALS['pma_config_loading'] = false; + ob_end_clean(); + error_reporting($old_error_reporting); + + if ($eval_result === false) { + $this->error_config_default_file = true; + return false; + } + + $this->default_source_mtime = filemtime($this->default_source); + + $this->default_server = $cfg['Servers'][1]; + unset($cfg['Servers']); + + $this->default = $cfg; + $this->settings = array_replace_recursive($this->settings, $cfg); + + $this->error_config_default_file = false; + + return true; + } + + /** + * loads configuration from $source, usually the config file + * should be called on object creation + * + * @param string $source config file + * + * @return bool + */ + public function load($source = null) + { + $this->loadDefaults(); + + if (null !== $source) { + $this->setSource($source); + } + + if (! $this->checkConfigSource()) { + return false; + } + + $cfg = array(); + + /** + * Parses the configuration file, we throw away any errors or + * output. + */ + $old_error_reporting = error_reporting(0); + ob_start(); + $GLOBALS['pma_config_loading'] = true; + $eval_result = include $this->getSource(); + $GLOBALS['pma_config_loading'] = false; + ob_end_clean(); + error_reporting($old_error_reporting); + + if ($eval_result === false) { + $this->error_config_file = true; + } else { + $this->error_config_file = false; + $this->source_mtime = filemtime($this->getSource()); + } + + /** + * Ignore keys with / as we do not use these + * + * These can be confusing for user configuration layer as it + * flatten array using / and thus don't see difference between + * $cfg['Export/method'] and $cfg['Export']['method'], while rest + * of thre code uses the setting only in latter form. + * + * This could be removed once we consistently handle both values + * in the functional code as well. + * + * It could use array_filter(...ARRAY_FILTER_USE_KEY), but it's not + * supported on PHP 5.5 and HHVM. + */ + $matched_keys = array_filter( + array_keys($cfg), + function ($key) {return strpos($key, '/') === false;} + ); + + $cfg = array_intersect_key($cfg, array_flip($matched_keys)); + + /** + * Backward compatibility code + */ + if (!empty($cfg['DefaultTabTable'])) { + $cfg['DefaultTabTable'] = str_replace( + '_properties', + '', + str_replace( + 'tbl_properties.php', + 'tbl_sql.php', + $cfg['DefaultTabTable'] + ) + ); + } + if (!empty($cfg['DefaultTabDatabase'])) { + $cfg['DefaultTabDatabase'] = str_replace( + '_details', + '', + str_replace( + 'db_details.php', + 'db_sql.php', + $cfg['DefaultTabDatabase'] + ) + ); + } + + $this->settings = array_replace_recursive($this->settings, $cfg); + + return true; + } + + /** + * Sets the connection collation + * + * @return void + */ + private function _setConnectionCollation() + { + $collation_connection = $this->get('DefaultConnectionCollation'); + if (! empty($collation_connection) + && $collation_connection != $GLOBALS['collation_connection'] + ) { + $GLOBALS['dbi']->setCollation($collation_connection); + } + } + + /** + * Loads user preferences and merges them with current config + * must be called after control connection has been established + * + * @return void + */ + public function loadUserPreferences() + { + // index.php should load these settings, so that phpmyadmin.css.php + // will have everything available in session cache + $server = isset($GLOBALS['server']) + ? $GLOBALS['server'] + : (!empty($GLOBALS['cfg']['ServerDefault']) + ? $GLOBALS['cfg']['ServerDefault'] + : 0); + $cache_key = 'server_' . $server; + if ($server > 0 && !defined('PMA_MINIMUM_COMMON')) { + $config_mtime = max($this->default_source_mtime, $this->source_mtime); + // cache user preferences, use database only when needed + if (! isset($_SESSION['cache'][$cache_key]['userprefs']) + || $_SESSION['cache'][$cache_key]['config_mtime'] < $config_mtime + ) { + $prefs = $this->userPreferences->load(); + $_SESSION['cache'][$cache_key]['userprefs'] + = $this->userPreferences->apply($prefs['config_data']); + $_SESSION['cache'][$cache_key]['userprefs_mtime'] = $prefs['mtime']; + $_SESSION['cache'][$cache_key]['userprefs_type'] = $prefs['type']; + $_SESSION['cache'][$cache_key]['config_mtime'] = $config_mtime; + } + } elseif ($server == 0 + || ! isset($_SESSION['cache'][$cache_key]['userprefs']) + ) { + $this->set('user_preferences', false); + return; + } + $config_data = $_SESSION['cache'][$cache_key]['userprefs']; + // type is 'db' or 'session' + $this->set( + 'user_preferences', + $_SESSION['cache'][$cache_key]['userprefs_type'] + ); + $this->set( + 'user_preferences_mtime', + $_SESSION['cache'][$cache_key]['userprefs_mtime'] + ); + + // load config array + $this->settings = array_replace_recursive($this->settings, $config_data); + $GLOBALS['cfg'] = array_replace_recursive($GLOBALS['cfg'], $config_data); + if (defined('PMA_MINIMUM_COMMON')) { + return; + } + + // settings below start really working on next page load, but + // changes are made only in index.php so everything is set when + // in frames + + // save theme + /** @var ThemeManager $tmanager */ + $tmanager = ThemeManager::getInstance(); + if ($tmanager->getThemeCookie() || isset($_REQUEST['set_theme'])) { + if ((! isset($config_data['ThemeDefault']) + && $tmanager->theme->getId() != 'original') + || isset($config_data['ThemeDefault']) + && $config_data['ThemeDefault'] != $tmanager->theme->getId() + ) { + // new theme was set in common.inc.php + $this->setUserValue( + null, + 'ThemeDefault', + $tmanager->theme->getId(), + 'original' + ); + } + } else { + // no cookie - read default from settings + if ($this->settings['ThemeDefault'] != $tmanager->theme->getId() + && $tmanager->checkTheme($this->settings['ThemeDefault']) + ) { + $tmanager->setActiveTheme($this->settings['ThemeDefault']); + $tmanager->setThemeCookie(); + } + } + + // save language + if (isset($_COOKIE['pma_lang']) || isset($_POST['lang'])) { + if ((! isset($config_data['lang']) + && $GLOBALS['lang'] != 'en') + || isset($config_data['lang']) + && $GLOBALS['lang'] != $config_data['lang'] + ) { + $this->setUserValue(null, 'lang', $GLOBALS['lang'], 'en'); + } + } else { + // read language from settings + if (isset($config_data['lang'])) { + $language = LanguageManager::getInstance()->getLanguage( + $config_data['lang'] + ); + if ($language !== false) { + $language->activate(); + $this->setCookie('pma_lang', $language->getCode()); + } + } + } + + // set connection collation + $this->_setConnectionCollation(); + } + + /** + * Sets config value which is stored in user preferences (if available) + * or in a cookie. + * + * If user preferences are not yet initialized, option is applied to + * global config and added to a update queue, which is processed + * by {@link loadUserPreferences()} + * + * @param string $cookie_name can be null + * @param string $cfg_path configuration path + * @param mixed $new_cfg_value new value + * @param mixed $default_value default value + * + * @return true|PhpMyAdmin\Message + */ + public function setUserValue($cookie_name, $cfg_path, $new_cfg_value, + $default_value = null + ) { + $result = true; + // use permanent user preferences if possible + $prefs_type = $this->get('user_preferences'); + if ($prefs_type) { + if ($default_value === null) { + $default_value = Core::arrayRead($cfg_path, $this->default); + } + $result = $this->userPreferences->persistOption($cfg_path, $new_cfg_value, $default_value); + } + if ($prefs_type != 'db' && $cookie_name) { + // fall back to cookies + if ($default_value === null) { + $default_value = Core::arrayRead($cfg_path, $this->settings); + } + $this->setCookie($cookie_name, $new_cfg_value, $default_value); + } + Core::arrayWrite($cfg_path, $GLOBALS['cfg'], $new_cfg_value); + Core::arrayWrite($cfg_path, $this->settings, $new_cfg_value); + return $result; + } + + /** + * Reads value stored by {@link setUserValue()} + * + * @param string $cookie_name cookie name + * @param mixed $cfg_value config value + * + * @return mixed + */ + public function getUserValue($cookie_name, $cfg_value) + { + $cookie_exists = isset($_COOKIE) && !empty($_COOKIE[$cookie_name]); + $prefs_type = $this->get('user_preferences'); + if ($prefs_type == 'db') { + // permanent user preferences value exists, remove cookie + if ($cookie_exists) { + $this->removeCookie($cookie_name); + } + } elseif ($cookie_exists) { + return $_COOKIE[$cookie_name]; + } + // return value from $cfg array + return $cfg_value; + } + + /** + * set source + * + * @param string $source source + * + * @return void + */ + public function setSource($source) + { + $this->source = trim($source); + } + + /** + * check config source + * + * @return boolean whether source is valid or not + */ + public function checkConfigSource() + { + if (! $this->getSource()) { + // no configuration file set at all + return false; + } + + if (! @file_exists($this->getSource())) { + $this->source_mtime = 0; + return false; + } + + if (! @is_readable($this->getSource())) { + // manually check if file is readable + // might be bug #3059806 Supporting running from CIFS/Samba shares + + $contents = false; + $handle = @fopen($this->getSource(), 'r'); + if ($handle !== false) { + $contents = @fread($handle, 1); // reading 1 byte is enough to test + fclose($handle); + } + if ($contents === false) { + $this->source_mtime = 0; + Core::fatalError( + sprintf( + function_exists('__') + ? __('Existing configuration file (%s) is not readable.') + : 'Existing configuration file (%s) is not readable.', + $this->getSource() + ) + ); + return false; + } + } + + return true; + } + + /** + * verifies the permissions on config file (if asked by configuration) + * (must be called after config.inc.php has been merged) + * + * @return void + */ + public function checkPermissions() + { + // Check for permissions (on platforms that support it): + if ($this->get('CheckConfigurationPermissions') && @file_exists($this->getSource())) { + $perms = @fileperms($this->getSource()); + if (!($perms === false) && ($perms & 2)) { + // This check is normally done after loading configuration + $this->checkWebServerOs(); + if ($this->get('PMA_IS_WINDOWS') == 0) { + $this->source_mtime = 0; + Core::fatalError( + __( + 'Wrong permissions on configuration file, ' + . 'should not be world writable!' + ) + ); + } + } + } + } + + /** + * Checks for errors + * (must be called after config.inc.php has been merged) + * + * @return void + */ + public function checkErrors() + { + if ($this->error_config_default_file) { + Core::fatalError( + sprintf( + __('Could not load default configuration from: %1$s'), + $this->default_source + ) + ); + } + + if ($this->error_config_file) { + $error = '[strong]' . __('Failed to read configuration file!') . '[/strong]' + . '[br][br]' + . __( + 'This usually means there is a syntax error in it, ' + . 'please check any errors shown below.' + ) + . '[br][br]' + . '[conferr]'; + trigger_error($error, E_USER_ERROR); + } + } + + /** + * returns specific config setting + * + * @param string $setting config setting + * + * @return mixed value + */ + public function get($setting) + { + if (isset($this->settings[$setting])) { + return $this->settings[$setting]; + } + return null; + } + + /** + * sets configuration variable + * + * @param string $setting configuration option + * @param mixed $value new value for configuration option + * + * @return void + */ + public function set($setting, $value) + { + if (! isset($this->settings[$setting]) + || $this->settings[$setting] !== $value + ) { + $this->settings[$setting] = $value; + $this->set_mtime = time(); + } + } + + /** + * returns source for current config + * + * @return string config source + */ + public function getSource() + { + return $this->source; + } + + /** + * returns a unique value to force a CSS reload if either the config + * or the theme changes + * + * @return int Summary of unix timestamps and fontsize, + * to be unique on theme parameters change + */ + public function getThemeUniqueValue() + { + if (null !== $this->get('FontSize')) { + $fontsize = intval($this->get('FontSize')); + } else { + $fontsize = 0; + } + return ( + $fontsize + + $this->source_mtime + + $this->default_source_mtime + + $this->get('user_preferences_mtime') + + $GLOBALS['PMA_Theme']->mtime_info + + $GLOBALS['PMA_Theme']->filesize_info); + } + + /** + * checks if upload is enabled + * + * @return void + */ + public function checkUpload() + { + if (!ini_get('file_uploads')) { + $this->set('enable_upload', false); + return; + } + + $this->set('enable_upload', true); + // if set "php_admin_value file_uploads Off" in httpd.conf + // ini_get() also returns the string "Off" in this case: + if ('off' == strtolower(ini_get('file_uploads'))) { + $this->set('enable_upload', false); + } + } + + /** + * Maximum upload size as limited by PHP + * Used with permission from Moodle (https://moodle.org/) by Martin Dougiamas + * + * this section generates $max_upload_size in bytes + * + * @return void + */ + public function checkUploadSize() + { + if (! $filesize = ini_get('upload_max_filesize')) { + $filesize = "5M"; + } + + if ($postsize = ini_get('post_max_size')) { + $this->set( + 'max_upload_size', + min(Core::getRealSize($filesize), Core::getRealSize($postsize)) + ); + } else { + $this->set('max_upload_size', Core::getRealSize($filesize)); + } + } + + /** + * Checks if protocol is https + * + * This function checks if the https protocol on the active connection. + * + * @return bool + */ + public function isHttps() + { + + if (null !== $this->get('is_https')) { + return $this->get('is_https'); + } + + $url = $this->get('PmaAbsoluteUri'); + + $is_https = false; + if (! empty($url) && parse_url($url, PHP_URL_SCHEME) === 'https') { + $is_https = true; + } elseif (strtolower(Core::getenv('HTTP_SCHEME')) == 'https') { + $is_https = true; + } elseif (strtolower(Core::getenv('HTTPS')) == 'on') { + $is_https = true; + } elseif (substr(strtolower(Core::getenv('REQUEST_URI')), 0, 6) == 'https:') { + $is_https = true; + } elseif (strtolower(Core::getenv('HTTP_HTTPS_FROM_LB')) == 'on') { + // A10 Networks load balancer + $is_https = true; + } elseif (strtolower(Core::getenv('HTTP_FRONT_END_HTTPS')) == 'on') { + $is_https = true; + } elseif (strtolower(Core::getenv('HTTP_X_FORWARDED_PROTO')) == 'https') { + $is_https = true; + } elseif (Core::getenv('SERVER_PORT') == 443) { + $is_https = true; + } + + $this->set('is_https', $is_https); + + return $is_https; + } + + /** + * Get phpMyAdmin root path + * + * @return string + */ + public function getRootPath() + { + static $cookie_path = null; + + if (null !== $cookie_path && !defined('TESTSUITE')) { + return $cookie_path; + } + + $url = $this->get('PmaAbsoluteUri'); + + if (! empty($url)) { + $path = parse_url($url, PHP_URL_PATH); + if (! empty($path)) { + if (substr($path, -1) != '/') { + return $path . '/'; + } + return $path; + } + } + + $parsed_url = parse_url($GLOBALS['PMA_PHP_SELF']); + + $parts = explode( + '/', + rtrim(str_replace('\\', '/', $parsed_url['path']), '/') + ); + + /* Remove filename */ + if (substr($parts[count($parts) - 1], -4) == '.php') { + $parts = array_slice($parts, 0, count($parts) - 1); + } + + /* Remove extra path from javascript calls */ + if (defined('PMA_PATH_TO_BASEDIR')) { + $parts = array_slice($parts, 0, count($parts) - 1); + } + + $parts[] = ''; + + return implode('/', $parts); + } + + /** + * enables backward compatibility + * + * @return void + */ + public function enableBc() + { + $GLOBALS['cfg'] = $this->settings; + $GLOBALS['default_server'] = $this->default_server; + unset($this->default_server); + $GLOBALS['is_upload'] = $this->get('enable_upload'); + $GLOBALS['max_upload_size'] = $this->get('max_upload_size'); + $GLOBALS['is_https'] = $this->get('is_https'); + + $defines = array( + 'PMA_VERSION', + 'PMA_MAJOR_VERSION', + 'PMA_THEME_VERSION', + 'PMA_THEME_GENERATION', + 'PMA_IS_WINDOWS', + 'PMA_IS_GD2', + 'PMA_USR_OS', + 'PMA_USR_BROWSER_VER', + 'PMA_USR_BROWSER_AGENT' + ); + + foreach ($defines as $define) { + if (! defined($define)) { + define($define, $this->get($define)); + } + } + } + + /** + * returns options for font size selection + * + * @param string $current_size current selected font size with unit + * + * @return array selectable font sizes + */ + protected static function getFontsizeOptions($current_size = '82%') + { + $unit = preg_replace('/[0-9.]*/', '', $current_size); + $value = preg_replace('/[^0-9.]*/', '', $current_size); + + $factors = array(); + $options = array(); + $options["$value"] = $value . $unit; + + if ($unit === '%') { + $factors[] = 1; + $factors[] = 5; + $factors[] = 10; + $options['100'] = '100%'; + } elseif ($unit === 'em') { + $factors[] = 0.05; + $factors[] = 0.2; + $factors[] = 1; + } elseif ($unit === 'pt') { + $factors[] = 0.5; + $factors[] = 2; + } elseif ($unit === 'px') { + $factors[] = 1; + $factors[] = 5; + $factors[] = 10; + } else { + //unknown font size unit + $factors[] = 0.05; + $factors[] = 0.2; + $factors[] = 1; + $factors[] = 5; + $factors[] = 10; + } + + foreach ($factors as $key => $factor) { + $option_inc = $value + $factor; + $option_dec = $value - $factor; + while (count($options) < 21) { + $options["$option_inc"] = $option_inc . $unit; + if ($option_dec > $factors[0]) { + $options["$option_dec"] = $option_dec . $unit; + } + $option_inc += $factor; + $option_dec -= $factor; + if (isset($factors[$key + 1]) + && $option_inc >= $value + $factors[$key + 1] + ) { + break; + } + } + } + ksort($options); + return $options; + } + + /** + * returns html selectbox for font sizes + * + * @return string html selectbox + */ + protected static function getFontsizeSelection() + { + $current_size = $GLOBALS['PMA_Config']->get('FontSize'); + // for the case when there is no config file (this is supported) + if (empty($current_size)) { + $current_size = '82%'; + } + $options = Config::getFontsizeOptions($current_size); + + $return = '' . "\n" + . ''; + + return $return; + } + + /** + * return complete font size selection form + * + * @return string html selectbox + */ + public static function getFontsizeForm() + { + return '
        ' . "\n" + . Url::getHiddenInputs() . "\n" + . Config::getFontsizeSelection() . "\n" + . '
        '; + } + + /** + * removes cookie + * + * @param string $cookie name of cookie to remove + * + * @return boolean result of setcookie() + */ + public function removeCookie($cookie) + { + if (defined('TESTSUITE')) { + if (isset($_COOKIE[$cookie])) { + unset($_COOKIE[$cookie]); + } + return true; + } + return setcookie( + $cookie, + '', + time() - 3600, + $this->getRootPath(), + '', + $this->isHttps() + ); + } + + /** + * sets cookie if value is different from current cookie value, + * or removes if value is equal to default + * + * @param string $cookie name of cookie to remove + * @param mixed $value new cookie value + * @param string $default default value + * @param int $validity validity of cookie in seconds (default is one month) + * @param bool $httponly whether cookie is only for HTTP (and not for scripts) + * + * @return boolean result of setcookie() + */ + public function setCookie($cookie, $value, $default = null, + $validity = null, $httponly = true + ) { + if (strlen($value) > 0 && null !== $default && $value === $default + ) { + // default value is used + if (isset($_COOKIE[$cookie])) { + // remove cookie + return $this->removeCookie($cookie); + } + return false; + } + + if (strlen($value) === 0 && isset($_COOKIE[$cookie])) { + // remove cookie, value is empty + return $this->removeCookie($cookie); + } + + if (! isset($_COOKIE[$cookie]) || $_COOKIE[$cookie] !== $value) { + // set cookie with new value + /* Calculate cookie validity */ + if ($validity === null) { + /* Valid for one month */ + $validity = time() + 2592000; + } elseif ($validity == 0) { + /* Valid for session */ + $validity = 0; + } else { + $validity = time() + $validity; + } + if (defined('TESTSUITE')) { + $_COOKIE[$cookie] = $value; + return true; + } + return setcookie( + $cookie, + $value, + $validity, + $this->getRootPath(), + '', + $this->isHttps(), + $httponly + ); + } + + // cookie has already $value as value + return true; + } + + + /** + * Error handler to catch fatal errors when loading configuration + * file + * + * + * PMA_Config_fatalErrorHandler + * @return void + */ + public static function fatalErrorHandler() + { + if (!isset($GLOBALS['pma_config_loading']) + || !$GLOBALS['pma_config_loading'] + ) { + return; + } + + $error = error_get_last(); + if ($error === null) { + return; + } + + Core::fatalError( + sprintf( + 'Failed to load phpMyAdmin configuration (%s:%s): %s', + Error::relPath($error['file']), + $error['line'], + $error['message'] + ) + ); + } + + /** + * Wrapper for footer/header rendering + * + * @param string $filename File to check and render + * @param string $id Div ID + * + * @return string + */ + private static function _renderCustom($filename, $id) + { + $retval = ''; + if (@file_exists($filename)) { + $retval .= '
        '; + ob_start(); + include $filename; + $retval .= ob_get_contents(); + ob_end_clean(); + $retval .= '
        '; + } + return $retval; + } + + /** + * Renders user configured footer + * + * @return string + */ + public static function renderFooter() + { + return self::_renderCustom(CUSTOM_FOOTER_FILE, 'pma_footer'); + } + + /** + * Renders user configured footer + * + * @return string + */ + public static function renderHeader() + { + return self::_renderCustom(CUSTOM_HEADER_FILE, 'pma_header'); + } + + /** + * Returns temporary dir path + * + * @param string $name Directory name + * + * @return string|null + */ + public function getTempDir($name) + { + static $temp_dir = array(); + + if (isset($temp_dir[$name]) && !defined('TESTSUITE')) { + return $temp_dir[$name]; + } + + $path = $this->get('TempDir'); + if (empty($path)) { + $path = null; + } else { + $path .= '/' . $name; + if (! @is_dir($path)) { + @mkdir($path, 0770, true); + } + if (! @is_dir($path) || ! @is_writable($path)) { + $path = null; + } + } + + $temp_dir[$name] = $path; + return $path; + } + + /** + * Returns temporary directory + * + * @return string + */ + public function getUploadTempDir() + { + // First try configured temp dir + // Fallback to PHP upload_tmp_dir + $dirs = array( + $this->getTempDir('upload'), + ini_get('upload_tmp_dir'), + sys_get_temp_dir(), + ); + + foreach ($dirs as $dir) { + if (! empty($dir) && @is_writable($dir)) { + return realpath($dir); + } + } + + return null; + } + + /** + * Selects server based on request parameters. + * + * @return integer + */ + public function selectServer() { + $server = 0; + $request = empty($_REQUEST['server']) ? 0 : $_REQUEST['server']; + + /** + * Lookup server by name + * (see FAQ 4.8) + */ + if (! is_numeric($request)) { + foreach ($this->settings['Servers'] as $i => $server) { + $verboseToLower = mb_strtolower($server['verbose']); + $serverToLower = mb_strtolower($request); + if ($server['host'] == $request + || $server['verbose'] == $request + || $verboseToLower == $serverToLower + || md5($verboseToLower) === $serverToLower + ) { + $request = $i; + break; + } + } + if (is_string($request)) { + $request = 0; + } + } + + /** + * If no server is selected, make sure that $this->settings['Server'] is empty (so + * that nothing will work), and skip server authentication. + * We do NOT exit here, but continue on without logging into any server. + * This way, the welcome page will still come up (with no server info) and + * present a choice of servers in the case that there are multiple servers + * and '$this->settings['ServerDefault'] = 0' is set. + */ + + if (is_numeric($request) && ! empty($request) && ! empty($this->settings['Servers'][$request])) { + $server = $request; + $this->settings['Server'] = $this->settings['Servers'][$server]; + } else { + if (!empty($this->settings['Servers'][$this->settings['ServerDefault']])) { + $server = $this->settings['ServerDefault']; + $this->settings['Server'] = $this->settings['Servers'][$server]; + } else { + $server = 0; + $this->settings['Server'] = array(); + } + } + + return $server; + } + + /** + * Checks whether Servers configuration is valid and possibly apply fixups. + * + * @return void + */ + public function checkServers() { + // Do we have some server? + if (! isset($this->settings['Servers']) || count($this->settings['Servers']) == 0) { + // No server => create one with defaults + $this->settings['Servers'] = array(1 => $this->default_server); + } else { + // We have server(s) => apply default configuration + $new_servers = array(); + + foreach ($this->settings['Servers'] as $server_index => $each_server) { + + // Detect wrong configuration + if (!is_int($server_index) || $server_index < 1) { + trigger_error( + sprintf(__('Invalid server index: %s'), $server_index), + E_USER_ERROR + ); + } + + $each_server = array_merge($this->default_server, $each_server); + + // Final solution to bug #582890 + // If we are using a socket connection + // and there is nothing in the verbose server name + // or the host field, then generate a name for the server + // in the form of "Server 2", localized of course! + if (empty($each_server['host']) && empty($each_server['verbose'])) { + $each_server['verbose'] = sprintf(__('Server %d'), $server_index); + } + + $new_servers[$server_index] = $each_server; + } + $this->settings['Servers'] = $new_servers; + } + } +} + +if (!defined('TESTSUITE')) { + register_shutdown_function(array('PhpMyAdmin\Config', 'fatalErrorHandler')); +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Config/ConfigFile.php b/php/apps/phpmyadmin49/libraries/classes/Config/ConfigFile.php new file mode 100644 index 00000000..5e4e712e --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Config/ConfigFile.php @@ -0,0 +1,531 @@ +_defaultCfg; + include './libraries/config.default.php'; + $cfg['fontsize'] = '82%'; + + // load additional config information + $cfg_db = &$this->_cfgDb; + include './libraries/config.values.php'; + + // apply default values overrides + if (count($cfg_db['_overrides'])) { + foreach ($cfg_db['_overrides'] as $path => $value) { + Core::arrayWrite($path, $cfg, $value); + } + } + + $this->_baseCfg = $base_config; + $this->_isInSetup = is_null($base_config); + $this->_id = 'ConfigFile' . $GLOBALS['server']; + if (!isset($_SESSION[$this->_id])) { + $_SESSION[$this->_id] = array(); + } + } + + /** + * Sets names of config options which will be placed in config file even if + * they are set to their default values (use only full paths) + * + * @param array $keys the names of the config options + * + * @return void + */ + public function setPersistKeys(array $keys) + { + // checking key presence is much faster than searching so move values + // to keys + $this->_persistKeys = array_flip($keys); + } + + /** + * Returns flipped array set by {@link setPersistKeys()} + * + * @return array + */ + public function getPersistKeysMap() + { + return $this->_persistKeys; + } + + /** + * By default ConfigFile allows setting of all configuration keys, use + * this method to set up a filter on {@link set()} method + * + * @param array|null $keys array of allowed keys or null to remove filter + * + * @return void + */ + public function setAllowedKeys($keys) + { + if ($keys === null) { + $this->_setFilter = null; + return; + } + // checking key presence is much faster than searching so move values + // to keys + $this->_setFilter = array_flip($keys); + } + + /** + * Sets path mapping for updating config in + * {@link updateWithGlobalConfig()} or reading + * by {@link getConfig()} or {@link getConfigArray()} + * + * @param array $mapping Contains the mapping of "Server/config options" + * to "Server/1/config options" + * + * @return void + */ + public function setCfgUpdateReadMapping(array $mapping) + { + $this->_cfgUpdateReadMapping = $mapping; + } + + /** + * Resets configuration data + * + * @return void + */ + public function resetConfigData() + { + $_SESSION[$this->_id] = array(); + } + + /** + * Sets configuration data (overrides old data) + * + * @param array $cfg Configuration options + * + * @return void + */ + public function setConfigData(array $cfg) + { + $_SESSION[$this->_id] = $cfg; + } + + /** + * Sets config value + * + * @param string $path Path + * @param mixed $value Value + * @param string $canonical_path Canonical path + * + * @return void + */ + public function set($path, $value, $canonical_path = null) + { + if ($canonical_path === null) { + $canonical_path = $this->getCanonicalPath($path); + } + // apply key whitelist + if ($this->_setFilter !== null + && ! isset($this->_setFilter[$canonical_path]) + ) { + return; + } + // if the path isn't protected it may be removed + if (isset($this->_persistKeys[$canonical_path])) { + Core::arrayWrite($path, $_SESSION[$this->_id], $value); + return; + } + + $default_value = $this->getDefault($canonical_path); + $remove_path = $value === $default_value; + if ($this->_isInSetup) { + // remove if it has a default value or is empty + $remove_path = $remove_path + || (empty($value) && empty($default_value)); + } else { + // get original config values not overwritten by user + // preferences to allow for overwriting options set in + // config.inc.php with default values + $instance_default_value = Core::arrayRead( + $canonical_path, + $this->_baseCfg + ); + // remove if it has a default value and base config (config.inc.php) + // uses default value + $remove_path = $remove_path + && ($instance_default_value === $default_value); + } + if ($remove_path) { + Core::arrayRemove($path, $_SESSION[$this->_id]); + return; + } + + Core::arrayWrite($path, $_SESSION[$this->_id], $value); + } + + /** + * Flattens multidimensional array, changes indices to paths + * (eg. 'key/subkey'). + * Used as array_walk() callback. + * + * @param mixed $value Value + * @param mixed $key Key + * @param mixed $prefix Prefix + * + * @return void + */ + private function _flattenArray($value, $key, $prefix) + { + // no recursion for numeric arrays + if (is_array($value) && !isset($value[0])) { + $prefix .= $key . '/'; + array_walk($value, array($this, '_flattenArray'), $prefix); + } else { + $this->_flattenArrayResult[$prefix . $key] = $value; + } + } + + /** + * Returns default config in a flattened array + * + * @return array + */ + public function getFlatDefaultConfig() + { + $this->_flattenArrayResult = array(); + array_walk($this->_defaultCfg, array($this, '_flattenArray'), ''); + $flat_cfg = $this->_flattenArrayResult; + $this->_flattenArrayResult = null; + return $flat_cfg; + } + + /** + * Updates config with values read from given array + * (config will contain differences to defaults from config.defaults.php). + * + * @param array $cfg Configuration + * + * @return void + */ + public function updateWithGlobalConfig(array $cfg) + { + // load config array and flatten it + $this->_flattenArrayResult = array(); + array_walk($cfg, array($this, '_flattenArray'), ''); + $flat_cfg = $this->_flattenArrayResult; + $this->_flattenArrayResult = null; + + // save values map for translating a few user preferences paths, + // should be complemented by code reading from generated config + // to perform inverse mapping + foreach ($flat_cfg as $path => $value) { + if (isset($this->_cfgUpdateReadMapping[$path])) { + $path = $this->_cfgUpdateReadMapping[$path]; + } + $this->set($path, $value, $path); + } + } + + /** + * Returns config value or $default if it's not set + * + * @param string $path Path of config file + * @param mixed $default Default values + * + * @return mixed + */ + public function get($path, $default = null) + { + return Core::arrayRead($path, $_SESSION[$this->_id], $default); + } + + /** + * Returns default config value or $default it it's not set ie. it doesn't + * exist in config.default.php ($cfg) and config.values.php + * ($_cfg_db['_overrides']) + * + * @param string $canonical_path Canonical path + * @param mixed $default Default value + * + * @return mixed + */ + public function getDefault($canonical_path, $default = null) + { + return Core::arrayRead($canonical_path, $this->_defaultCfg, $default); + } + + /** + * Returns config value, if it's not set uses the default one; returns + * $default if the path isn't set and doesn't contain a default value + * + * @param string $path Path + * @param mixed $default Default value + * + * @return mixed + */ + public function getValue($path, $default = null) + { + $v = Core::arrayRead($path, $_SESSION[$this->_id], null); + if ($v !== null) { + return $v; + } + $path = $this->getCanonicalPath($path); + return $this->getDefault($path, $default); + } + + /** + * Returns canonical path + * + * @param string $path Path + * + * @return string + */ + public function getCanonicalPath($path) + { + return preg_replace('#^Servers/([\d]+)/#', 'Servers/1/', $path); + } + + /** + * Returns config database entry for $path ($cfg_db in config_info.php) + * + * @param string $path path of the variable in config db + * @param mixed $default default value + * + * @return mixed + */ + public function getDbEntry($path, $default = null) + { + return Core::arrayRead($path, $this->_cfgDb, $default); + } + + /** + * Returns server count + * + * @return int + */ + public function getServerCount() + { + return isset($_SESSION[$this->_id]['Servers']) + ? count($_SESSION[$this->_id]['Servers']) + : 0; + } + + /** + * Returns server list + * + * @return array|null + */ + public function getServers() + { + return isset($_SESSION[$this->_id]['Servers']) + ? $_SESSION[$this->_id]['Servers'] + : null; + } + + /** + * Returns DSN of given server + * + * @param integer $server server index + * + * @return string + */ + public function getServerDSN($server) + { + if (!isset($_SESSION[$this->_id]['Servers'][$server])) { + return ''; + } + + $path = 'Servers/' . $server; + $dsn = 'mysqli://'; + if ($this->getValue("$path/auth_type") == 'config') { + $dsn .= $this->getValue("$path/user"); + if (! empty($this->getValue("$path/password"))) { + $dsn .= ':***'; + } + $dsn .= '@'; + } + if ($this->getValue("$path/host") != 'localhost') { + $dsn .= $this->getValue("$path/host"); + $port = $this->getValue("$path/port"); + if ($port) { + $dsn .= ':' . $port; + } + } else { + $dsn .= $this->getValue("$path/socket"); + } + return $dsn; + } + + /** + * Returns server name + * + * @param int $id server index + * + * @return string + */ + public function getServerName($id) + { + if (!isset($_SESSION[$this->_id]['Servers'][$id])) { + return ''; + } + $verbose = $this->get("Servers/$id/verbose"); + if (!empty($verbose)) { + return $verbose; + } + $host = $this->get("Servers/$id/host"); + return empty($host) ? 'localhost' : $host; + } + + /** + * Removes server + * + * @param int $server server index + * + * @return void + */ + public function removeServer($server) + { + if (!isset($_SESSION[$this->_id]['Servers'][$server])) { + return; + } + $last_server = $this->getServerCount(); + + for ($i = $server; $i < $last_server; $i++) { + $_SESSION[$this->_id]['Servers'][$i] + = $_SESSION[$this->_id]['Servers'][$i + 1]; + } + unset($_SESSION[$this->_id]['Servers'][$last_server]); + + if (isset($_SESSION[$this->_id]['ServerDefault']) + && $_SESSION[$this->_id]['ServerDefault'] == $last_server + ) { + unset($_SESSION[$this->_id]['ServerDefault']); + } + } + + /** + * Returns configuration array (full, multidimensional format) + * + * @return array + */ + public function getConfig() + { + $c = $_SESSION[$this->_id]; + foreach ($this->_cfgUpdateReadMapping as $map_to => $map_from) { + // if the key $c exists in $map_to + if (Core::arrayRead($map_to, $c) !== null) { + Core::arrayWrite($map_to, $c, Core::arrayRead($map_from, $c)); + Core::arrayRemove($map_from, $c); + } + } + return $c; + } + + /** + * Returns configuration array (flat format) + * + * @return array + */ + public function getConfigArray() + { + $this->_flattenArrayResult = array(); + array_walk($_SESSION[$this->_id], array($this, '_flattenArray'), ''); + $c = $this->_flattenArrayResult; + $this->_flattenArrayResult = null; + + $persistKeys = array_diff( + array_keys($this->_persistKeys), + array_keys($c) + ); + foreach ($persistKeys as $k) { + $c[$k] = $this->getDefault($this->getCanonicalPath($k)); + } + + foreach ($this->_cfgUpdateReadMapping as $map_to => $map_from) { + if (!isset($c[$map_from])) { + continue; + } + $c[$map_to] = $c[$map_from]; + unset($c[$map_from]); + } + return $c; + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Config/Descriptions.php b/php/apps/phpmyadmin49/libraries/classes/Config/Descriptions.php new file mode 100644 index 00000000..37fc5c22 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Config/Descriptions.php @@ -0,0 +1,1493 @@ +LOCK TABLES') + ); + case 'Export_asfile_name': + return __('Save as file'); + case 'Export_charset_name': + return __('Character set of the file'); + case 'Export_codegen_format_name': + return __('Format'); + case 'Export_compression_name': + return __('Compression'); + case 'Export_csv_columns_name': + return __('Put columns names in the first row'); + case 'Export_csv_enclosed_name': + return __('Columns enclosed with'); + case 'Export_csv_escaped_name': + return __('Columns escaped with'); + case 'Export_csv_null_name': + return __('Replace NULL with'); + case 'Export_csv_removeCRLF_name': + return __('Remove CRLF characters within columns'); + case 'Export_csv_separator_name': + return __('Columns terminated with'); + case 'Export_csv_terminated_name': + return __('Lines terminated with'); + case 'Export_excel_columns_name': + return __('Put columns names in the first row'); + case 'Export_excel_edition_name': + return __('Excel edition'); + case 'Export_excel_null_name': + return __('Replace NULL with'); + case 'Export_excel_removeCRLF_name': + return __('Remove CRLF characters within columns'); + case 'Export_file_template_database_name': + return __('Database name template'); + case 'Export_file_template_server_name': + return __('Server name template'); + case 'Export_file_template_table_name': + return __('Table name template'); + case 'Export_format_name': + return __('Format'); + case 'Export_htmlword_columns_name': + return __('Put columns names in the first row'); + case 'Export_htmlword_null_name': + return __('Replace NULL with'); + case 'Export_htmlword_structure_or_data_name': + return __('Dump table'); + case 'Export_latex_caption_name': + return __('Include table caption'); + case 'Export_latex_columns_name': + return __('Put columns names in the first row'); + case 'Export_latex_comments_name': + return __('Comments'); + case 'Export_latex_data_caption_name': + return __('Table caption'); + case 'Export_latex_data_continued_caption_name': + return __('Continued table caption'); + case 'Export_latex_data_label_name': + return __('Label key'); + case 'Export_latex_mime_name': + return __('MIME type'); + case 'Export_latex_null_name': + return __('Replace NULL with'); + case 'Export_latex_relation_name': + return __('Relationships'); + case 'Export_latex_structure_caption_name': + return __('Table caption'); + case 'Export_latex_structure_continued_caption_name': + return __('Continued table caption'); + case 'Export_latex_structure_label_name': + return __('Label key'); + case 'Export_latex_structure_or_data_name': + return __('Dump table'); + case 'Export_method_name': + return __('Export method'); + case 'Export_ods_columns_name': + return __('Put columns names in the first row'); + case 'Export_ods_null_name': + return __('Replace NULL with'); + case 'Export_odt_columns_name': + return __('Put columns names in the first row'); + case 'Export_odt_comments_name': + return __('Comments'); + case 'Export_odt_mime_name': + return __('MIME type'); + case 'Export_odt_null_name': + return __('Replace NULL with'); + case 'Export_odt_relation_name': + return __('Relationships'); + case 'Export_odt_structure_or_data_name': + return __('Dump table'); + case 'Export_onserver_name': + return __('Save on server'); + case 'Export_onserver_overwrite_name': + return __('Overwrite existing file(s)'); + case 'Export_as_separate_files_name': + return __('Export as separate files'); + case 'Export_quick_export_onserver_name': + return __('Save on server'); + case 'Export_quick_export_onserver_overwrite_name': + return __('Overwrite existing file(s)'); + case 'Export_remember_file_template_name': + return __('Remember file name template'); + case 'Export_sql_auto_increment_name': + return __('Add AUTO_INCREMENT value'); + case 'Export_sql_backquotes_name': + return __('Enclose table and column names with backquotes'); + case 'Export_sql_compatibility_name': + return __('SQL compatibility mode'); + case 'Export_sql_dates_name': + return __('Creation/Update/Check dates'); + case 'Export_sql_delayed_name': + return __('Use delayed inserts'); + case 'Export_sql_disable_fk_name': + return __('Disable foreign key checks'); + case 'Export_sql_views_as_tables_name': + return __('Export views as tables'); + case 'Export_sql_metadata_name': + return __('Export related metadata from phpMyAdmin configuration storage'); + case 'Export_sql_create_database_name': + return sprintf(__('Add %s'), 'CREATE DATABASE / USE'); + case 'Export_sql_drop_database_name': + return sprintf(__('Add %s'), 'DROP DATABASE'); + case 'Export_sql_drop_table_name': + return sprintf( + __('Add %s'), 'DROP TABLE / VIEW / PROCEDURE / FUNCTION / EVENT / TRIGGER' + ); + case 'Export_sql_create_table_name': + return sprintf(__('Add %s'), 'CREATE TABLE'); + case 'Export_sql_create_view_name': + return sprintf(__('Add %s'), 'CREATE VIEW'); + case 'Export_sql_create_trigger_name': + return sprintf(__('Add %s'), 'CREATE TRIGGER'); + case 'Export_sql_hex_for_binary_name': + return __('Use hexadecimal for BINARY & BLOB'); + case 'Export_sql_if_not_exists_name': + return __( + 'Add IF NOT EXISTS (less efficient as indexes will be generated during' + . ' table creation)' + ); + case 'Export_sql_ignore_name': + return __('Use ignore inserts'); + case 'Export_sql_include_comments_name': + return __('Comments'); + case 'Export_sql_insert_syntax_name': + return __('Syntax to use when inserting data'); + case 'Export_sql_max_query_size_name': + return __('Maximal length of created query'); + case 'Export_sql_mime_name': + return __('MIME type'); + case 'Export_sql_procedure_function_name': + return sprintf(__('Add %s'), 'CREATE PROCEDURE / FUNCTION / EVENT'); + case 'Export_sql_relation_name': + return __('Relationships'); + case 'Export_sql_structure_or_data_name': + return __('Dump table'); + case 'Export_sql_type_name': + return __('Export type'); + case 'Export_sql_use_transaction_name': + return __('Enclose export in a transaction'); + case 'Export_sql_utc_time_name': + return __('Export time in UTC'); + case 'Export_texytext_columns_name': + return __('Put columns names in the first row'); + case 'Export_texytext_null_name': + return __('Replace NULL with'); + case 'Export_texytext_structure_or_data_name': + return __('Dump table'); + case 'ForeignKeyDropdownOrder_desc': + return __( + 'Sort order for items in a foreign-key dropdown box; [kbd]content[/kbd] is ' + . 'the referenced data, [kbd]id[/kbd] is the key value.' + ); + case 'ForeignKeyDropdownOrder_name': + return __('Foreign key dropdown order'); + case 'ForeignKeyMaxLimit_desc': + return __('A dropdown will be used if fewer items are present.'); + case 'ForeignKeyMaxLimit_name': + return __('Foreign key limit'); + case 'DefaultForeignKeyChecks_desc': + return __('Default value for foreign key checks checkbox for some queries.'); + case 'DefaultForeignKeyChecks_name': + return __('Foreign key checks'); + case 'Form_Browse_name': + return __('Browse mode'); + case 'Form_Browse_desc': + return __('Customize browse mode.'); + case 'Form_CodeGen_name': + return 'CodeGen'; + case 'Form_CodeGen_desc': + return __('Customize default options.'); + case 'Form_Csv_name': + return __('CSV'); + case 'Form_Csv_desc': + return __('Customize default options.'); + case 'Form_Developer_name': + return __('Developer'); + case 'Form_Developer_desc': + return __('Settings for phpMyAdmin developers.'); + case 'Form_Edit_name': + return __('Edit mode'); + case 'Form_Edit_desc': + return __('Customize edit mode.'); + case 'Form_Export_defaults_name': + return __('Export defaults'); + case 'Form_Export_defaults_desc': + return __('Customize default export options.'); + case 'Form_General_name': + return __('General'); + case 'Form_General_desc': + return __('Set some commonly used options.'); + case 'Form_Import_defaults_name': + return __('Import defaults'); + case 'Form_Import_defaults_desc': + return __('Customize default common import options.'); + case 'Form_Import_export_name': + return __('Import / export'); + case 'Form_Import_export_desc': + return __('Set import and export directories and compression options.'); + case 'Form_Latex_name': + return __('LaTeX'); + case 'Form_Latex_desc': + return __('Customize default options.'); + case 'Form_Navi_databases_name': + return __('Databases'); + case 'Form_Navi_databases_desc': + return __('Databases display options.'); + case 'Form_Navi_panel_name': + return __('Navigation panel'); + case 'Form_Navi_panel_desc': + return __('Customize appearance of the navigation panel.'); + case 'Form_Navi_tree_name': + return __('Navigation tree'); + case 'Form_Navi_tree_desc': + return __('Customize the navigation tree.'); + case 'Form_Navi_servers_name': + return __('Servers'); + case 'Form_Navi_servers_desc': + return __('Servers display options.'); + case 'Form_Navi_tables_name': + return __('Tables'); + case 'Form_Navi_tables_desc': + return __('Tables display options.'); + case 'Form_Main_panel_name': + return __('Main panel'); + case 'Form_Microsoft_Office_name': + return __('Microsoft Office'); + case 'Form_Microsoft_Office_desc': + return __('Customize default options.'); + case 'Form_Open_Document_name': + return 'OpenDocument'; + case 'Form_Open_Document_desc': + return __('Customize default options.'); + case 'Form_Other_core_settings_name': + return __('Other core settings'); + case 'Form_Other_core_settings_desc': + return __('Settings that didn\'t fit anywhere else.'); + case 'Form_Page_titles_name': + return __('Page titles'); + case 'Form_Page_titles_desc': + return __( + 'Specify browser\'s title bar text. Refer to ' + . '[doc@faq6-27]documentation[/doc] for magic strings that can be used ' + . 'to get special values.' + ); + case 'Form_Security_name': + return __('Security'); + case 'Form_Security_desc': + return __( + 'Please note that phpMyAdmin is just a user interface and its features do not ' + . 'limit MySQL.' + ); + case 'Form_Server_name': + return __('Basic settings'); + case 'Form_Server_auth_name': + return __('Authentication'); + case 'Form_Server_auth_desc': + return __('Authentication settings.'); + case 'Form_Server_config_name': + return __('Server configuration'); + case 'Form_Server_config_desc': + return __( + 'Advanced server configuration, do not change these options unless you know ' + . 'what they are for.' + ); + case 'Form_Server_desc': + return __('Enter server connection parameters.'); + case 'Form_Server_pmadb_name': + return __('Configuration storage'); + case 'Form_Server_pmadb_desc': + return __( + 'Configure phpMyAdmin configuration storage to gain access to additional ' + . 'features, see [doc@linked-tables]phpMyAdmin configuration storage[/doc] in ' + . 'documentation.' + ); + case 'Form_Server_tracking_name': + return __('Changes tracking'); + case 'Form_Server_tracking_desc': + return __( + 'Tracking of changes made in database. Requires the phpMyAdmin configuration ' + . 'storage.' + ); + case 'Form_Sql_name': + return __('SQL'); + case 'Form_Sql_box_name': + return __('SQL Query box'); + case 'Form_Sql_box_desc': + return __('Customize links shown in SQL Query boxes.'); + case 'Form_Sql_desc': + return __('Customize default options.'); + case 'Form_Sql_queries_name': + return __('SQL queries'); + case 'Form_Sql_queries_desc': + return __('SQL queries settings.'); + case 'Form_Startup_name': + return __('Startup'); + case 'Form_Startup_desc': + return __('Customize startup page.'); + case 'Form_DbStructure_name': + return __('Database structure'); + case 'Form_DbStructure_desc': + return __('Choose which details to show in the database structure (list of tables).'); + case 'Form_TableStructure_name': + return __('Table structure'); + case 'Form_TableStructure_desc': + return __('Settings for the table structure (list of columns).'); + case 'Form_Tabs_name': + return __('Tabs'); + case 'Form_Tabs_desc': + return __('Choose how you want tabs to work.'); + case 'Form_DisplayRelationalSchema_name': + return __('Display relational schema'); + case 'Form_DisplayRelationalSchema_desc': + return ''; + case 'PDFDefaultPageSize_name': + return __('Paper size'); + case 'PDFDefaultPageSize_desc': + return ''; + case 'Form_Databases_name': + return __('Databases'); + case 'Form_Text_fields_name': + return __('Text fields'); + case 'Form_Text_fields_desc': + return __('Customize text input fields.'); + case 'Form_Texy_name': + return __('Texy! text'); + case 'Form_Texy_desc': + return __('Customize default options'); + case 'Form_Warnings_name': + return __('Warnings'); + case 'Form_Warnings_desc': + return __('Disable some of the warnings shown by phpMyAdmin.'); + case 'Form_Console_name': + return __('Console'); + case 'GZipDump_desc': + return __( + 'Enable gzip compression for import ' + . 'and export operations.' + ); + case 'GZipDump_name': + return __('GZip'); + case 'IconvExtraParams_name': + return __('Extra parameters for iconv'); + case 'IgnoreMultiSubmitErrors_desc': + return __( + 'If enabled, phpMyAdmin continues computing multiple-statement queries even if ' + . 'one of the queries failed.' + ); + case 'IgnoreMultiSubmitErrors_name': + return __('Ignore multiple statement errors'); + case 'Import_allow_interrupt_desc': + return __( + 'Allow interrupt of import in case script detects it is close to time limit. ' + . 'This might be a good way to import large files, however it can break ' + . 'transactions.' + ); + case 'Import_allow_interrupt_name': + return __('Partial import: allow interrupt'); + case 'Import_charset_name': + return __('Character set of the file'); + case 'Import_csv_col_names_name': + return __('Lines terminated with'); + case 'Import_csv_enclosed_name': + return __('Columns enclosed with'); + case 'Import_csv_escaped_name': + return __('Columns escaped with'); + case 'Import_csv_ignore_name': + return __('Do not abort on INSERT error'); + case 'Import_csv_replace_name': + return __('Add ON DUPLICATE KEY UPDATE'); + case 'Import_csv_replace_desc': + return __('Update data when duplicate keys found on import'); + case 'Import_csv_terminated_name': + return __('Columns terminated with'); + case 'Import_format_desc': + return __( + 'Default format; be aware that this list depends on location (database, table) ' + . 'and only SQL is always available.' + ); + case 'Import_format_name': + return __('Format of imported file'); + case 'Import_ldi_enclosed_name': + return __('Columns enclosed with'); + case 'Import_ldi_escaped_name': + return __('Columns escaped with'); + case 'Import_ldi_ignore_name': + return __('Do not abort on INSERT error'); + case 'Import_ldi_local_option_name': + return __('Use LOCAL keyword'); + case 'Import_ldi_replace_name': + return __('Add ON DUPLICATE KEY UPDATE'); + case 'Import_ldi_replace_desc': + return __('Update data when duplicate keys found on import'); + case 'Import_ldi_terminated_name': + return __('Columns terminated with'); + case 'Import_ods_col_names_name': + return __('Column names in first row'); + case 'Import_ods_empty_rows_name': + return __('Do not import empty rows'); + case 'Import_ods_recognize_currency_name': + return __('Import currencies ($5.00 to 5.00)'); + case 'Import_ods_recognize_percentages_name': + return __('Import percentages as proper decimals (12.00% to .12)'); + case 'Import_skip_queries_desc': + return __('Number of queries to skip from start.'); + case 'Import_skip_queries_name': + return __('Partial import: skip queries'); + case 'Import_sql_compatibility_name': + return __('SQL compatibility mode'); + case 'Import_sql_no_auto_value_on_zero_name': + return __('Do not use AUTO_INCREMENT for zero values'); + case 'Import_sql_read_as_multibytes_name': + return __('Read as multibytes'); + case 'InitialSlidersState_name': + return __('Initial state for sliders'); + case 'InsertRows_desc': + return __('How many rows can be inserted at one time.'); + case 'InsertRows_name': + return __('Number of inserted rows'); + case 'LimitChars_desc': + return __('Maximum number of characters shown in any non-numeric column on browse view.'); + case 'LimitChars_name': + return __('Limit column characters'); + case 'LoginCookieDeleteAll_desc': + return __( + 'If TRUE, logout deletes cookies for all servers; when set to FALSE, logout ' + . 'only occurs for the current server. Setting this to FALSE makes it easy to ' + . 'forget to log out from other servers when connected to multiple servers.' + ); + case 'LoginCookieDeleteAll_name': + return __('Delete all cookies on logout'); + case 'LoginCookieRecall_desc': + return __( + 'Define whether the previous login should be recalled or not in ' + . '[kbd]cookie[/kbd] authentication mode.' + ); + case 'LoginCookieRecall_name': + return __('Recall user name'); + case 'LoginCookieStore_desc': + return __( + 'Defines how long (in seconds) a login cookie should be stored in browser. ' + . 'The default of 0 means that it will be kept for the existing session only, ' + . 'and will be deleted as soon as you close the browser window. This is ' + . 'recommended for non-trusted environments.' + ); + case 'LoginCookieStore_name': + return __('Login cookie store'); + case 'LoginCookieValidity_desc': + return __('Define how long (in seconds) a login cookie is valid.'); + case 'LoginCookieValidity_name': + return __('Login cookie validity'); + case 'LongtextDoubleTextarea_desc': + return __('Double size of textarea for LONGTEXT columns.'); + case 'LongtextDoubleTextarea_name': + return __('Bigger textarea for LONGTEXT'); + case 'MaxCharactersInDisplayedSQL_desc': + return __('Maximum number of characters used when a SQL query is displayed.'); + case 'MaxCharactersInDisplayedSQL_name': + return __('Maximum displayed SQL length'); + case 'MaxDbList_cmt': + return __('Users cannot set a higher value'); + case 'MaxDbList_desc': + return __('Maximum number of databases displayed in database list.'); + case 'MaxDbList_name': + return __('Maximum databases'); + case 'FirstLevelNavigationItems_desc': + return __( + 'The number of items that can be displayed on each page on the first level' + . ' of the navigation tree.' + ); + case 'FirstLevelNavigationItems_name': + return __('Maximum items on first level'); + case 'MaxNavigationItems_desc': + return __('The number of items that can be displayed on each page of the navigation tree.'); + case 'MaxNavigationItems_name': + return __('Maximum items in branch'); + case 'MaxRows_desc': + return __( + 'Number of rows displayed when browsing a result set. If the result set ' + . 'contains more rows, "Previous" and "Next" links will be ' + . 'shown.' + ); + case 'MaxRows_name': + return __('Maximum number of rows to display'); + case 'MaxTableList_cmt': + return __('Users cannot set a higher value'); + case 'MaxTableList_desc': + return __('Maximum number of tables displayed in table list.'); + case 'MaxTableList_name': + return __('Maximum tables'); + case 'MemoryLimit_desc': + return __( + 'The number of bytes a script is allowed to allocate, eg. [kbd]32M[/kbd] ' + . '([kbd]-1[/kbd] for no limit and [kbd]0[/kbd] for no change).' + ); + case 'MemoryLimit_name': + return __('Memory limit'); + case 'ShowDatabasesNavigationAsTree_desc': + return __('In the navigation panel, replaces the database tree with a selector'); + case 'ShowDatabasesNavigationAsTree_name': + return __('Show databases navigation as tree'); + case 'NavigationWidth_name': + return __('Navigation panel width'); + case 'NavigationWidth_desc': + return __('Set to 0 to collapse navigation panel.'); + case 'NavigationLinkWithMainPanel_desc': + return __('Link with main panel by highlighting the current database or table.'); + case 'NavigationLinkWithMainPanel_name': + return __('Link with main panel'); + case 'NavigationDisplayLogo_desc': + return __('Show logo in navigation panel.'); + case 'NavigationDisplayLogo_name': + return __('Display logo'); + case 'NavigationLogoLink_desc': + return __('URL where logo in the navigation panel will point to.'); + case 'NavigationLogoLink_name': + return __('Logo link URL'); + case 'NavigationLogoLinkWindow_desc': + return __( + 'Open the linked page in the main window ([kbd]main[/kbd]) or in a new one ' + . '([kbd]new[/kbd]).' + ); + case 'NavigationLogoLinkWindow_name': + return __('Logo link target'); + case 'NavigationDisplayServers_desc': + return __('Display server choice at the top of the navigation panel.'); + case 'NavigationDisplayServers_name': + return __('Display servers selection'); + case 'NavigationTreeDefaultTabTable_name': + return __('Target for quick access icon'); + case 'NavigationTreeDefaultTabTable2_name': + return __('Target for second quick access icon'); + case 'NavigationTreeDisplayItemFilterMinimum_desc': + return __( + 'Defines the minimum number of items (tables, views, routines and events) to ' + . 'display a filter box.' + ); + case 'NavigationTreeDisplayItemFilterMinimum_name': + return __('Minimum number of items to display the filter box'); + case 'NavigationTreeDisplayDbFilterMinimum_name': + return __('Minimum number of databases to display the database filter box'); + case 'NavigationTreeEnableGrouping_desc': + return __( + 'Group items in the navigation tree (determined by the separator defined in ' . + 'the Databases and Tables tabs above).' + ); + case 'NavigationTreeEnableGrouping_name': + return __('Group items in the tree'); + case 'NavigationTreeDbSeparator_desc': + return __('String that separates databases into different tree levels.'); + case 'NavigationTreeDbSeparator_name': + return __('Database tree separator'); + case 'NavigationTreeTableSeparator_desc': + return __('String that separates tables into different tree levels.'); + case 'NavigationTreeTableSeparator_name': + return __('Table tree separator'); + case 'NavigationTreeTableLevel_name': + return __('Maximum table tree depth'); + case 'NavigationTreePointerEnable_desc': + return __('Highlight server under the mouse cursor.'); + case 'NavigationTreePointerEnable_name': + return __('Enable highlighting'); + case 'NavigationTreeEnableExpansion_desc': + return __('Whether to offer the possibility of tree expansion in the navigation panel.'); + case 'NavigationTreeEnableExpansion_name': + return __('Enable navigation tree expansion'); + case 'NavigationTreeShowTables_name': + return __('Show tables in tree'); + case 'NavigationTreeShowTables_desc': + return __('Whether to show tables under database in the navigation tree'); + case 'NavigationTreeShowViews_name': + return __('Show views in tree'); + case 'NavigationTreeShowViews_desc': + return __('Whether to show views under database in the navigation tree'); + case 'NavigationTreeShowFunctions_name': + return __('Show functions in tree'); + case 'NavigationTreeShowFunctions_desc': + return __('Whether to show functions under database in the navigation tree'); + case 'NavigationTreeShowProcedures_name': + return __('Show procedures in tree'); + case 'NavigationTreeShowProcedures_desc': + return __('Whether to show procedures under database in the navigation tree'); + case 'NavigationTreeShowEvents_name': + return __('Show events in tree'); + case 'NavigationTreeShowEvents_desc': + return __('Whether to show events under database in the navigation tree'); + case 'NumRecentTables_desc': + return __('Maximum number of recently used tables; set 0 to disable.'); + case 'NumFavoriteTables_desc': + return __('Maximum number of favorite tables; set 0 to disable.'); + case 'NumRecentTables_name': + return __('Recently used tables'); + case 'NumFavoriteTables_name': + return __('Favorite tables'); + case 'RowActionLinks_desc': + return __('These are Edit, Copy and Delete links.'); + case 'RowActionLinks_name': + return __('Where to show the table row links'); + case 'RowActionLinksWithoutUnique_desc': + return __('Whether to show row links even in the absence of a unique key.'); + case 'RowActionLinksWithoutUnique_name': + return __('Show row links anyway'); + case 'DisableShortcutKeys_name': + return __('Disable shortcut keys'); + case 'DisableShortcutKeys_desc': + return __('Disable shortcut keys'); + case 'NaturalOrder_desc': + return __('Use natural order for sorting table and database names.'); + case 'NaturalOrder_name': + return __('Natural order'); + case 'TableNavigationLinksMode_desc': + return __('Use only icons, only text or both.'); + case 'TableNavigationLinksMode_name': + return __('Table navigation bar'); + case 'OBGzip_desc': + return __('Use GZip output buffering for increased speed in HTTP transfers.'); + case 'OBGzip_name': + return __('GZip output buffering'); + case 'Order_desc': + return __( + '[kbd]SMART[/kbd] - i.e. descending order for columns of type TIME, DATE, ' + . 'DATETIME and TIMESTAMP, ascending order otherwise.' + ); + case 'Order_name': + return __('Default sorting order'); + case 'PersistentConnections_desc': + return __('Use persistent connections to MySQL databases.'); + case 'PersistentConnections_name': + return __('Persistent connections'); + case 'PmaNoRelation_DisableWarning_desc': + return __( + 'Disable the default warning that is displayed on the database details ' + . 'Structure page if any of the required tables for the phpMyAdmin ' + . 'configuration storage could not be found.' + ); + case 'PmaNoRelation_DisableWarning_name': + return __('Missing phpMyAdmin configuration storage tables'); + case 'ReservedWordDisableWarning_desc': + return __( + 'Disable the default warning that is displayed on the Structure page if column ' + . 'names in a table are reserved MySQL words.' + ); + case 'ReservedWordDisableWarning_name': + return __('MySQL reserved word warning'); + case 'TabsMode_desc': + return __('Use only icons, only text or both.'); + case 'TabsMode_name': + return __('How to display the menu tabs'); + case 'ActionLinksMode_desc': + return __('Use only icons, only text or both.'); + case 'ActionLinksMode_name': + return __('How to display various action links'); + case 'ProtectBinary_desc': + return __('Disallow BLOB and BINARY columns from editing.'); + case 'ProtectBinary_name': + return __('Protect binary columns'); + case 'QueryHistoryDB_desc': + return __( + 'Enable if you want DB-based query history (requires phpMyAdmin configuration ' + . 'storage). If disabled, this utilizes JS-routines to display query history ' + . '(lost by window close).' + ); + case 'QueryHistoryDB_name': + return __('Permanent query history'); + case 'QueryHistoryMax_cmt': + return __('Users cannot set a higher value'); + case 'QueryHistoryMax_desc': + return __('How many queries are kept in history.'); + case 'QueryHistoryMax_name': + return __('Query history length'); + case 'RecodingEngine_desc': + return __('Select which functions will be used for character set conversion.'); + case 'RecodingEngine_name': + return __('Recoding engine'); + case 'RememberSorting_desc': + return __('When browsing tables, the sorting of each table is remembered.'); + case 'RememberSorting_name': + return __('Remember table\'s sorting'); + case 'TablePrimaryKeyOrder_desc': + return __('Default sort order for tables with a primary key.'); + case 'TablePrimaryKeyOrder_name': + return __('Primary key default sort order'); + case 'RepeatCells_desc': + return __('Repeat the headers every X cells, [kbd]0[/kbd] deactivates this feature.'); + case 'RepeatCells_name': + return __('Repeat headers'); + case 'GridEditing_name': + return __('Grid editing: trigger action'); + case 'RelationalDisplay_name': + return __('Relational display'); + case 'RelationalDisplay_desc': + return __('For display Options'); + case 'SaveCellsAtOnce_name': + return __('Grid editing: save all edited cells at once'); + case 'SaveDir_desc': + return __('Directory where exports can be saved on server.'); + case 'SaveDir_name': + return __('Save directory'); + case 'Servers_AllowDeny_order_desc': + return __('Leave blank if not used.'); + case 'Servers_AllowDeny_order_name': + return __('Host authorization order'); + case 'Servers_AllowDeny_rules_desc': + return __('Leave blank for defaults.'); + case 'Servers_AllowDeny_rules_name': + return __('Host authorization rules'); + case 'Servers_AllowNoPassword_name': + return __('Allow logins without a password'); + case 'Servers_AllowRoot_name': + return __('Allow root login'); + case 'Servers_SessionTimeZone_name': + return __('Session timezone'); + case 'Servers_SessionTimeZone_desc': + return __( + 'Sets the effective timezone; possibly different than the one from your ' + . 'database server' + ); + case 'Servers_auth_http_realm_desc': + return __('HTTP Basic Auth Realm name to display when doing HTTP Auth.'); + case 'Servers_auth_http_realm_name': + return __('HTTP Realm'); + case 'Servers_auth_type_desc': + return __('Authentication method to use.'); + case 'Servers_auth_type_name': + return __('Authentication type'); + case 'Servers_bookmarktable_desc': + return __( + 'Leave blank for no [doc@bookmarks@]bookmark[/doc] ' + . 'support, suggested: [kbd]pma__bookmark[/kbd]' + ); + case 'Servers_bookmarktable_name': + return __('Bookmark table'); + case 'Servers_column_info_desc': + return __( + 'Leave blank for no column comments/mime types, suggested: ' + . '[kbd]pma__column_info[/kbd].' + ); + case 'Servers_column_info_name': + return __('Column information table'); + case 'Servers_compress_desc': + return __('Compress connection to MySQL server.'); + case 'Servers_compress_name': + return __('Compress connection'); + case 'Servers_controlpass_name': + return __('Control user password'); + case 'Servers_controluser_desc': + return __( + 'A special MySQL user configured with limited permissions, more information ' + . 'available on [doc@linked-tables]documentation[/doc].' + ); + case 'Servers_controluser_name': + return __('Control user'); + case 'Servers_controlhost_desc': + return __( + 'An alternate host to hold the configuration storage; leave blank to use the ' + . 'already defined host.' + ); + case 'Servers_controlhost_name': + return __('Control host'); + case 'Servers_controlport_desc': + return __( + 'An alternate port to connect to the host that holds the configuration storage; ' + . 'leave blank to use the default port, or the already defined port, if the ' + . 'controlhost equals host.' + ); + case 'Servers_controlport_name': + return __('Control port'); + case 'Servers_hide_db_desc': + return __('Hide databases matching regular expression (PCRE).'); + case 'Servers_DisableIS_desc': + return __( + 'More information on [a@https://github.com/phpmyadmin/phpmyadmin/issues/8970]phpMyAdmin ' + . 'issue tracker[/a] and [a@https://bugs.mysql.com/19588]MySQL Bugs[/a]' + ); + case 'Servers_DisableIS_name': + return __('Disable use of INFORMATION_SCHEMA'); + case 'Servers_hide_db_name': + return __('Hide databases'); + case 'Servers_history_desc': + return __( + 'Leave blank for no SQL query history support, suggested: ' + . '[kbd]pma__history[/kbd].' + ); + case 'Servers_history_name': + return __('SQL query history table'); + case 'Servers_host_desc': + return __('Hostname where MySQL server is running.'); + case 'Servers_host_name': + return __('Server hostname'); + case 'Servers_LogoutURL_name': + return __('Logout URL'); + case 'Servers_MaxTableUiprefs_desc': + return __( + 'Limits number of table preferences which are stored in database, the oldest ' + . 'records are automatically removed.' + ); + case 'Servers_MaxTableUiprefs_name': + return __('Maximal number of table preferences to store'); + case 'Servers_savedsearches_name': + return __('QBE saved searches table'); + case 'Servers_savedsearches_desc': + return __( + 'Leave blank for no QBE saved searches support, suggested: ' + . '[kbd]pma__savedsearches[/kbd].' + ); + case 'Servers_export_templates_name': + return __('Export templates table'); + case 'Servers_export_templates_desc': + return __( + 'Leave blank for no export template support, suggested: ' + . '[kbd]pma__export_templates[/kbd].' + ); + case 'Servers_central_columns_name': + return __('Central columns table'); + case 'Servers_central_columns_desc': + return __( + 'Leave blank for no central columns support, suggested: ' + . '[kbd]pma__central_columns[/kbd].' + ); + case 'Servers_only_db_desc': + return __( + 'You can use MySQL wildcard characters (% and _), escape them if you want to ' + . 'use their literal instances, i.e. use [kbd]\'my\_db\'[/kbd] and not ' + . '[kbd]\'my_db\'[/kbd].' + ); + case 'Servers_only_db_name': + return __('Show only listed databases'); + case 'Servers_password_desc': + return __('Leave empty if not using config auth.'); + case 'Servers_password_name': + return __('Password for config auth'); + case 'Servers_pdf_pages_desc': + return __('Leave blank for no PDF schema support, suggested: [kbd]pma__pdf_pages[/kbd].'); + case 'Servers_pdf_pages_name': + return __('PDF schema: pages table'); + case 'Servers_pmadb_desc': + return __( + 'Database used for relations, bookmarks, and PDF features. See ' + . '[doc@linked-tables]pmadb[/doc] for complete information. ' + . 'Leave blank for no support. Suggested: [kbd]phpmyadmin[/kbd].' + ); + case 'Servers_pmadb_name': + return __('Database name'); + case 'Servers_port_desc': + return __('Port on which MySQL server is listening, leave empty for default.'); + case 'Servers_port_name': + return __('Server port'); + case 'Servers_recent_desc': + return __( + 'Leave blank for no "persistent" recently used tables across sessions, ' + . 'suggested: [kbd]pma__recent[/kbd].' + ); + case 'Servers_recent_name': + return __('Recently used table'); + case 'Servers_favorite_desc': + return __( + 'Leave blank for no "persistent" favorite tables across sessions, ' + . 'suggested: [kbd]pma__favorite[/kbd].' + ); + case 'Servers_favorite_name': + return __('Favorites table'); + case 'Servers_relation_desc': + return __( + 'Leave blank for no ' + . '[doc@relations@]relation-links[/doc] support, ' + . 'suggested: [kbd]pma__relation[/kbd].' + ); + case 'Servers_relation_name': + return __('Relation table'); + case 'Servers_SignonSession_desc': + return __( + 'See [doc@authentication-modes]authentication ' + . 'types[/doc] for an example.' + ); + case 'Servers_SignonSession_name': + return __('Signon session name'); + case 'Servers_SignonURL_name': + return __('Signon URL'); + case 'Servers_socket_desc': + return __('Socket on which MySQL server is listening, leave empty for default.'); + case 'Servers_socket_name': + return __('Server socket'); + case 'Servers_ssl_desc': + return __('Enable SSL for connection to MySQL server.'); + case 'Servers_ssl_name': + return __('Use SSL'); + case 'Servers_table_coords_desc': + return __('Leave blank for no PDF schema support, suggested: [kbd]pma__table_coords[/kbd].'); + case 'Servers_table_coords_name': + return __('Designer and PDF schema: table coordinates'); + case 'Servers_table_info_desc': + return __( + 'Table to describe the display columns, leave blank for no support; ' + . 'suggested: [kbd]pma__table_info[/kbd].' + ); + case 'Servers_table_info_name': + return __('Display columns table'); + case 'Servers_table_uiprefs_desc': + return __( + 'Leave blank for no "persistent" tables\' UI preferences across sessions, ' + . 'suggested: [kbd]pma__table_uiprefs[/kbd].' + ); + case 'Servers_table_uiprefs_name': + return __('UI preferences table'); + case 'Servers_tracking_add_drop_database_desc': + return __( + 'Whether a DROP DATABASE IF EXISTS statement will be added as first line to ' + . 'the log when creating a database.' + ); + case 'Servers_tracking_add_drop_database_name': + return __('Add DROP DATABASE'); + case 'Servers_tracking_add_drop_table_desc': + return __( + 'Whether a DROP TABLE IF EXISTS statement will be added as first line to the ' + . 'log when creating a table.' + ); + case 'Servers_tracking_add_drop_table_name': + return __('Add DROP TABLE'); + case 'Servers_tracking_add_drop_view_desc': + return __( + 'Whether a DROP VIEW IF EXISTS statement will be added as first line to the ' + . 'log when creating a view.' + ); + case 'Servers_tracking_add_drop_view_name': + return __('Add DROP VIEW'); + case 'Servers_tracking_default_statements_desc': + return __('Defines the list of statements the auto-creation uses for new versions.'); + case 'Servers_tracking_default_statements_name': + return __('Statements to track'); + case 'Servers_tracking_desc': + return __( + 'Leave blank for no SQL query tracking support, suggested: ' + . '[kbd]pma__tracking[/kbd].' + ); + case 'Servers_tracking_name': + return __('SQL query tracking table'); + case 'Servers_tracking_version_auto_create_desc': + return __( + 'Whether the tracking mechanism creates versions for tables and views ' + . 'automatically.' + ); + case 'Servers_tracking_version_auto_create_name': + return __('Automatically create versions'); + case 'Servers_userconfig_desc': + return __( + 'Leave blank for no user preferences storage in database, suggested: ' + . '[kbd]pma__userconfig[/kbd].' + ); + case 'Servers_userconfig_name': + return __('User preferences storage table'); + case 'Servers_users_desc': + return __( + 'Both this table and the user groups table are required to enable the ' . + 'configurable menus feature; leaving either one of them blank will disable ' . + 'this feature, suggested: [kbd]pma__users[/kbd].' + ); + case 'Servers_users_name': + return __('Users table'); + case 'Servers_usergroups_desc': + return __( + 'Both this table and the users table are required to enable the configurable ' . + 'menus feature; leaving either one of them blank will disable this feature, ' . + 'suggested: [kbd]pma__usergroups[/kbd].' + ); + case 'Servers_usergroups_name': + return __('User groups table'); + case 'Servers_navigationhiding_desc': + return __( + 'Leave blank to disable the feature to hide and show navigation items, ' . + 'suggested: [kbd]pma__navigationhiding[/kbd].' + ); + case 'Servers_navigationhiding_name': + return __('Hidden navigation items table'); + case 'Servers_user_desc': + return __('Leave empty if not using config auth.'); + case 'Servers_user_name': + return __('User for config auth'); + case 'Servers_verbose_desc': + return __( + 'A user-friendly description of this server. Leave blank to display the ' . + 'hostname instead.' + ); + case 'Servers_verbose_name': + return __('Verbose name of this server'); + case 'ShowAll_desc': + return __('Whether a user should be displayed a "show all (rows)" button.'); + case 'ShowAll_name': + return __('Allow to display all the rows'); + case 'ShowChgPassword_desc': + return __( + 'Please note that enabling this has no effect with [kbd]config[/kbd] ' . + 'authentication mode because the password is hard coded in the configuration ' . + 'file; this does not limit the ability to execute the same command directly.' + ); + case 'ShowChgPassword_name': + return __('Show password change form'); + case 'ShowCreateDb_name': + return __('Show create database form'); + case 'ShowDbStructureComment_desc': + return __('Show or hide a column displaying the comments for all tables.'); + case 'ShowDbStructureComment_name': + return __('Show table comments'); + case 'ShowDbStructureCreation_desc': + return __('Show or hide a column displaying the Creation timestamp for all tables.'); + case 'ShowDbStructureCreation_name': + return __('Show creation timestamp'); + case 'ShowDbStructureLastUpdate_desc': + return __('Show or hide a column displaying the Last update timestamp for all tables.'); + case 'ShowDbStructureLastUpdate_name': + return __('Show last update timestamp'); + case 'ShowDbStructureLastCheck_desc': + return __('Show or hide a column displaying the Last check timestamp for all tables.'); + case 'ShowDbStructureLastCheck_name': + return __('Show last check timestamp'); + case 'ShowDbStructureCharset_desc': + return __('Show or hide a column displaying the charset for all tables.'); + case 'ShowDbStructureCharset_name': + return __('Show table charset'); + case 'ShowFieldTypesInDataEditView_desc': + return __( + 'Defines whether or not type fields should be initially displayed in ' . + 'edit/insert mode.' + ); + case 'ShowFieldTypesInDataEditView_name': + return __('Show field types'); + case 'ShowFunctionFields_desc': + return __('Display the function fields in edit/insert mode.'); + case 'ShowFunctionFields_name': + return __('Show function fields'); + case 'ShowHint_desc': + return __('Whether to show hint or not.'); + case 'ShowHint_name': + return __('Show hint'); + case 'ShowPhpInfo_desc': + return __( + 'Shows link to [a@https://php.net/manual/function.phpinfo.php]phpinfo()[/a] ' . + 'output.' + ); + case 'ShowPhpInfo_name': + return __('Show phpinfo() link'); + case 'ShowServerInfo_name': + return __('Show detailed MySQL server information'); + case 'ShowSQL_desc': + return __('Defines whether SQL queries generated by phpMyAdmin should be displayed.'); + case 'ShowSQL_name': + return __('Show SQL queries'); + case 'RetainQueryBox_desc': + return __('Defines whether the query box should stay on-screen after its submission.'); + case 'RetainQueryBox_name': + return __('Retain query box'); + case 'ShowStats_desc': + return __('Allow to display database and table statistics (eg. space usage).'); + case 'ShowStats_name': + return __('Show statistics'); + case 'SkipLockedTables_desc': + return __('Mark used tables and make it possible to show databases with locked tables.'); + case 'SkipLockedTables_name': + return __('Skip locked tables'); + case 'SQLQuery_Edit_name': + return __('Edit'); + case 'SQLQuery_Explain_name': + return __('Explain SQL'); + case 'SQLQuery_Refresh_name': + return __('Refresh'); + case 'SQLQuery_ShowAsPHP_name': + return __('Create PHP code'); + case 'SuhosinDisableWarning_desc': + return __( + 'Disable the default warning that is displayed on the main page if Suhosin is ' . + 'detected.' + ); + case 'SuhosinDisableWarning_name': + return __('Suhosin warning'); + case 'LoginCookieValidityDisableWarning_desc': + return __( + 'Disable the default warning that is displayed on the main page if the value ' . + 'of the PHP setting session.gc_maxlifetime is less than the value of ' . + '`LoginCookieValidity`.' + ); + case 'LoginCookieValidityDisableWarning_name': + return __('Login cookie validity warning'); + case 'TextareaCols_desc': + return __( + 'Textarea size (columns) in edit mode, this value will be emphasized for SQL ' . + 'query textareas (*2).' + ); + case 'TextareaCols_name': + return __('Textarea columns'); + case 'TextareaRows_desc': + return __( + 'Textarea size (rows) in edit mode, this value will be emphasized for SQL ' . + 'query textareas (*2).' + ); + case 'TextareaRows_name': + return __('Textarea rows'); + case 'TitleDatabase_desc': + return __('Title of browser window when a database is selected.'); + case 'TitleDatabase_name': + return __('Database'); + case 'TitleDefault_desc': + return __('Title of browser window when nothing is selected.'); + case 'TitleDefault_name': + return __('Default title'); + case 'TitleServer_desc': + return __('Title of browser window when a server is selected.'); + case 'TitleServer_name': + return __('Server'); + case 'TitleTable_desc': + return __('Title of browser window when a table is selected.'); + case 'TitleTable_name': + return __('Table'); + case 'TrustedProxies_desc': + return __( + 'Input proxies as [kbd]IP: trusted HTTP header[/kbd]. The following example ' . + 'specifies that phpMyAdmin should trust a HTTP_X_FORWARDED_FOR ' . + '(X-Forwarded-For) header coming from the proxy 1.2.3.4:[br][kbd]1.2.3.4: ' . + 'HTTP_X_FORWARDED_FOR[/kbd].' + ); + case 'TrustedProxies_name': + return __('List of trusted proxies for IP allow/deny'); + case 'UploadDir_desc': + return __('Directory on server where you can upload files for import.'); + case 'UploadDir_name': + return __('Upload directory'); + case 'UseDbSearch_desc': + return __('Allow for searching inside the entire database.'); + case 'UseDbSearch_name': + return __('Use database search'); + case 'UserprefsDeveloperTab_desc': + return __( + 'When disabled, users cannot set any of the options below, regardless of the ' . + 'checkbox on the right.' + ); + case 'UserprefsDeveloperTab_name': + return __('Enable the Developer tab in settings'); + case 'VersionCheck_desc': + return __('Enables check for latest version on main phpMyAdmin page.'); + case 'VersionCheck_name': + return __('Version check'); + case 'ProxyUrl_desc': + return __( + 'The url of the proxy to be used when retrieving the information about the ' . + 'latest version of phpMyAdmin or when submitting error reports. You need this ' . + 'if the server where phpMyAdmin is installed does not have direct access to ' . + 'the internet. The format is: "hostname:portnumber".' + ); + case 'ProxyUrl_name': + return __('Proxy url'); + case 'ProxyUser_desc': + return __( + 'The username for authenticating with the proxy. By default, no ' . + 'authentication is performed. If a username is supplied, Basic ' . + 'Authentication will be performed. No other types of authentication are ' . + 'currently supported.' + ); + case 'ProxyUser_name': + return __('Proxy username'); + case 'ProxyPass_desc': + return __('The password for authenticating with the proxy.'); + case 'ProxyPass_name': + return __('Proxy password'); + + case 'ZipDump_desc': + return __('Enable ZIP compression for import and export operations.'); + case 'ZipDump_name': + return __('ZIP'); + case 'CaptchaLoginPublicKey_desc': + return __('Enter your public key for your domain reCaptcha service.'); + case 'CaptchaLoginPublicKey_name': + return __('Public key for reCaptcha'); + case 'CaptchaLoginPrivateKey_desc': + return __('Enter your private key for your domain reCaptcha service.'); + case 'CaptchaLoginPrivateKey_name': + return __('Private key for reCaptcha'); + + case 'SendErrorReports_desc': + return __('Choose the default action when sending error reports.'); + case 'SendErrorReports_name': + return __('Send error reports'); + + case 'ConsoleEnterExecutes_desc': + return __( + 'Queries are executed by pressing Enter (instead of Ctrl+Enter). New lines ' . + 'will be inserted with Shift+Enter.' + ); + case 'ConsoleEnterExecutes_name': + return __('Enter executes queries in console'); + + case 'ZeroConf_desc': + return __( + 'Enable Zero Configuration mode which lets you setup phpMyAdmin ' + . 'configuration storage tables automatically.' + ); + case 'ZeroConf_name': + return __('Enable Zero Configuration mode'); + case 'Console_StartHistory_name': + return __('Show query history at start'); + case 'Console_AlwaysExpand_name': + return __('Always expand query messages'); + case 'Console_CurrentQuery_name': + return __('Show current browsing query'); + case 'Console_EnterExecutes_name': + return __('Execute queries on Enter and insert new line with Shift + Enter'); + case 'Console_DarkTheme_name': + return __('Switch to dark theme'); + case 'Console_Height_name': + return __('Console height'); + case 'Console_Mode_name': + return __('Console mode'); + case 'Console_GroupQueries_name': + return __('Group queries'); + case 'Console_Order_name': + return __('Order'); + case 'Console_OrderBy_name': + return __('Order by'); + case 'FontSize_name': + return __('Font size'); + case 'DefaultConnectionCollation_name': + return __('Server connection collation'); + } + return null; + } +} + diff --git a/php/apps/phpmyadmin49/libraries/classes/Config/Form.php b/php/apps/phpmyadmin49/libraries/classes/Config/Form.php new file mode 100644 index 00000000..f7c670fa --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Config/Form.php @@ -0,0 +1,233 @@ +index = $index; + $this->_configFile = $cf; + $this->loadForm($form_name, $form); + } + + /** + * Returns type of given option + * + * @param string $option_name path or field name + * + * @return string|null one of: boolean, integer, double, string, select, array + */ + public function getOptionType($option_name) + { + $key = ltrim( + mb_substr( + $option_name, + mb_strrpos($option_name, '/') + ), + '/' + ); + return isset($this->_fieldsTypes[$key]) + ? $this->_fieldsTypes[$key] + : null; + } + + /** + * Returns allowed values for select fields + * + * @param string $option_path Option path + * + * @return array + */ + public function getOptionValueList($option_path) + { + $value = $this->_configFile->getDbEntry($option_path); + if ($value === null) { + trigger_error("$option_path - select options not defined", E_USER_ERROR); + return array(); + } + if (!is_array($value)) { + trigger_error("$option_path - not a static value list", E_USER_ERROR); + return array(); + } + // convert array('#', 'a', 'b') to array('a', 'b') + if (isset($value[0]) && $value[0] === '#') { + // remove first element ('#') + array_shift($value); + // $value has keys and value names, return it + return $value; + } + + // convert value list array('a', 'b') to array('a' => 'a', 'b' => 'b') + $has_string_keys = false; + $keys = array(); + for ($i = 0, $nb = count($value); $i < $nb; $i++) { + if (!isset($value[$i])) { + $has_string_keys = true; + break; + } + $keys[] = is_bool($value[$i]) ? (int)$value[$i] : $value[$i]; + } + if (! $has_string_keys) { + $value = array_combine($keys, $value); + } + + // $value has keys and value names, return it + return $value; + } + + /** + * array_walk callback function, reads path of form fields from + * array (see docs for \PhpMyAdmin\Config\Forms\BaseForm::getForms) + * + * @param mixed $value Value + * @param mixed $key Key + * @param mixed $prefix Prefix + * + * @return void + */ + private function _readFormPathsCallback($value, $key, $prefix) + { + static $group_counter = 0; + + if (is_array($value)) { + $prefix .= $key . '/'; + array_walk($value, array($this, '_readFormPathsCallback'), $prefix); + return; + } + + if (!is_int($key)) { + $this->default[$prefix . $key] = $value; + $value = $key; + } + // add unique id to group ends + if ($value == ':group:end') { + $value .= ':' . $group_counter++; + } + $this->fields[] = $prefix . $value; + } + + /** + * Reads form paths to {@link $fields} + * + * @param array $form Form + * + * @return void + */ + protected function readFormPaths(array $form) + { + // flatten form fields' paths and save them to $fields + $this->fields = array(); + array_walk($form, array($this, '_readFormPathsCallback'), ''); + + // $this->fields is an array of the form: [0..n] => 'field path' + // change numeric indexes to contain field names (last part of the path) + $paths = $this->fields; + $this->fields = array(); + foreach ($paths as $path) { + $key = ltrim( + mb_substr($path, mb_strrpos($path, '/')), + '/' + ); + $this->fields[$key] = $path; + } + // now $this->fields is an array of the form: 'field name' => 'field path' + } + + /** + * Reads fields' types to $this->_fieldsTypes + * + * @return void + */ + protected function readTypes() + { + $cf = $this->_configFile; + foreach ($this->fields as $name => $path) { + if (mb_strpos($name, ':group:') === 0) { + $this->_fieldsTypes[$name] = 'group'; + continue; + } + $v = $cf->getDbEntry($path); + if ($v !== null) { + $type = is_array($v) ? 'select' : $v; + } else { + $type = gettype($cf->getDefault($path)); + } + $this->_fieldsTypes[$name] = $type; + } + } + + /** + * Reads form settings and prepares class to work with given subset of + * config file + * + * @param string $form_name Form name + * @param array $form Form + * + * @return void + */ + public function loadForm($form_name, array $form) + { + $this->name = $form_name; + $this->readFormPaths($form); + $this->readTypes(); + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Config/FormDisplay.php b/php/apps/phpmyadmin49/libraries/classes/Config/FormDisplay.php new file mode 100644 index 00000000..5d9eedb4 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Config/FormDisplay.php @@ -0,0 +1,880 @@ +_jsLangStrings = array( + 'error_nan_p' => __('Not a positive number!'), + 'error_nan_nneg' => __('Not a non-negative number!'), + 'error_incorrect_port' => __('Not a valid port number!'), + 'error_invalid_value' => __('Incorrect value!'), + 'error_value_lte' => __('Value must be less than or equal to %s!')); + $this->_configFile = $cf; + // initialize validators + Validator::getValidators($this->_configFile); + } + + /** + * Returns {@link ConfigFile} associated with this instance + * + * @return ConfigFile + */ + public function getConfigFile() + { + return $this->_configFile; + } + + /** + * Registers form in form manager + * + * @param string $form_name Form name + * @param array $form Form data + * @param int $server_id 0 if new server, validation; >= 1 if editing a server + * + * @return void + */ + public function registerForm($form_name, array $form, $server_id = null) + { + $this->_forms[$form_name] = new Form( + $form_name, $form, $this->_configFile, $server_id + ); + $this->_isValidated = false; + foreach ($this->_forms[$form_name]->fields as $path) { + $work_path = $server_id === null + ? $path + : str_replace('Servers/1/', "Servers/$server_id/", $path); + $this->_systemPaths[$work_path] = $path; + $this->_translatedPaths[$work_path] = str_replace('/', '-', $work_path); + } + } + + /** + * Processes forms, returns true on successful save + * + * @param bool $allow_partial_save allows for partial form saving + * on failed validation + * @param bool $check_form_submit whether check for $_POST['submit_save'] + * + * @return boolean whether processing was successful + */ + public function process($allow_partial_save = true, $check_form_submit = true) + { + if ($check_form_submit && !isset($_POST['submit_save'])) { + return false; + } + + // save forms + if (count($this->_forms) > 0) { + return $this->save(array_keys($this->_forms), $allow_partial_save); + } + return false; + } + + /** + * Runs validation for all registered forms + * + * @return void + */ + private function _validate() + { + if ($this->_isValidated) { + return; + } + + $paths = array(); + $values = array(); + foreach ($this->_forms as $form) { + /* @var $form Form */ + $paths[] = $form->name; + // collect values and paths + foreach ($form->fields as $path) { + $work_path = array_search($path, $this->_systemPaths); + $values[$path] = $this->_configFile->getValue($work_path); + $paths[] = $path; + } + } + + // run validation + $errors = Validator::validate( + $this->_configFile, $paths, $values, false + ); + + // change error keys from canonical paths to work paths + if (is_array($errors) && count($errors) > 0) { + $this->_errors = array(); + foreach ($errors as $path => $error_list) { + $work_path = array_search($path, $this->_systemPaths); + // field error + if (! $work_path) { + // form error, fix path + $work_path = $path; + } + $this->_errors[$work_path] = $error_list; + } + } + $this->_isValidated = true; + } + + /** + * Outputs HTML for the forms under the menu tab + * + * @param bool $show_restore_default whether to show "restore default" + * button besides the input field + * @param array &$js_default stores JavaScript code + * to be displayed + * @param array &$js will be updated with javascript code + * @param bool $show_buttons whether show submit and reset button + * + * @return string $htmlOutput + */ + private function _displayForms( + $show_restore_default, array &$js_default, array &$js, $show_buttons + ) { + $htmlOutput = ''; + $validators = Validator::getValidators($this->_configFile); + + foreach ($this->_forms as $form) { + /* @var $form Form */ + $form_errors = isset($this->_errors[$form->name]) + ? $this->_errors[$form->name] : null; + $htmlOutput .= FormDisplayTemplate::displayFieldsetTop( + Descriptions::get("Form_{$form->name}"), + Descriptions::get("Form_{$form->name}", 'desc'), + $form_errors, + array('id' => $form->name) + ); + + foreach ($form->fields as $field => $path) { + $work_path = array_search($path, $this->_systemPaths); + $translated_path = $this->_translatedPaths[$work_path]; + // always true/false for user preferences display + // otherwise null + $userprefs_allow = isset($this->_userprefsKeys[$path]) + ? !isset($this->_userprefsDisallow[$path]) + : null; + // display input + $htmlOutput .= $this->_displayFieldInput( + $form, + $field, + $path, + $work_path, + $translated_path, + $show_restore_default, + $userprefs_allow, + $js_default + ); + // register JS validators for this field + if (isset($validators[$path])) { + FormDisplayTemplate::addJsValidate($translated_path, $validators[$path], $js); + } + } + $htmlOutput .= FormDisplayTemplate::displayFieldsetBottom($show_buttons); + } + return $htmlOutput; + } + + /** + * Outputs HTML for forms + * + * @param bool $tabbed_form if true, use a form with tabs + * @param bool $show_restore_default whether show "restore default" button + * besides the input field + * @param bool $show_buttons whether show submit and reset button + * @param string $form_action action attribute for the form + * @param array|null $hidden_fields array of form hidden fields (key: field + * name) + * + * @return string HTML for forms + */ + public function getDisplay( + $tabbed_form = false, + $show_restore_default = false, + $show_buttons = true, + $form_action = null, + $hidden_fields = null + ) { + static $js_lang_sent = false; + + $htmlOutput = ''; + + $js = array(); + $js_default = array(); + + $htmlOutput .= FormDisplayTemplate::displayFormTop($form_action, 'post', $hidden_fields); + + if ($tabbed_form) { + $tabs = array(); + foreach ($this->_forms as $form) { + $tabs[$form->name] = Descriptions::get("Form_$form->name"); + } + $htmlOutput .= FormDisplayTemplate::displayTabsTop($tabs); + } + + // validate only when we aren't displaying a "new server" form + $is_new_server = false; + foreach ($this->_forms as $form) { + /* @var $form Form */ + if ($form->index === 0) { + $is_new_server = true; + break; + } + } + if (! $is_new_server) { + $this->_validate(); + } + + // user preferences + $this->_loadUserprefsInfo(); + + // display forms + $htmlOutput .= $this->_displayForms( + $show_restore_default, $js_default, $js, $show_buttons + ); + + if ($tabbed_form) { + $htmlOutput .= FormDisplayTemplate::displayTabsBottom(); + } + $htmlOutput .= FormDisplayTemplate::displayFormBottom(); + + // if not already done, send strings used for validation to JavaScript + if (! $js_lang_sent) { + $js_lang_sent = true; + $js_lang = array(); + foreach ($this->_jsLangStrings as $strName => $strValue) { + $js_lang[] = "'$strName': '" . Sanitize::jsFormat($strValue, false) . '\''; + } + $js[] = "$.extend(PMA_messages, {\n\t" + . implode(",\n\t", $js_lang) . '})'; + } + + $js[] = "$.extend(defaultValues, {\n\t" + . implode(",\n\t", $js_default) . '})'; + $htmlOutput .= FormDisplayTemplate::displayJavascript($js); + + return $htmlOutput; + } + + /** + * Prepares data for input field display and outputs HTML code + * + * @param Form $form Form object + * @param string $field field name as it appears in $form + * @param string $system_path field path, eg. Servers/1/verbose + * @param string $work_path work path, eg. Servers/4/verbose + * @param string $translated_path work path changed so that it can be + * used as XHTML id + * @param bool $show_restore_default whether show "restore default" button + * besides the input field + * @param bool|null $userprefs_allow whether user preferences are enabled + * for this field (null - no support, + * true/false - enabled/disabled) + * @param array &$js_default array which stores JavaScript code + * to be displayed + * + * @return string HTML for input field + */ + private function _displayFieldInput( + Form $form, $field, $system_path, $work_path, + $translated_path, $show_restore_default, $userprefs_allow, array &$js_default + ) { + $name = Descriptions::get($system_path); + $description = Descriptions::get($system_path, 'desc'); + + $value = $this->_configFile->get($work_path); + $value_default = $this->_configFile->getDefault($system_path); + $value_is_default = false; + if ($value === null || $value === $value_default) { + $value = $value_default; + $value_is_default = true; + } + + $opts = array( + 'doc' => $this->getDocLink($system_path), + 'show_restore_default' => $show_restore_default, + 'userprefs_allow' => $userprefs_allow, + 'userprefs_comment' => Descriptions::get($system_path, 'cmt') + ); + if (isset($form->default[$system_path])) { + $opts['setvalue'] = $form->default[$system_path]; + } + + if (isset($this->_errors[$work_path])) { + $opts['errors'] = $this->_errors[$work_path]; + } + + $type = ''; + switch ($form->getOptionType($field)) { + case 'string': + $type = 'text'; + break; + case 'short_string': + $type = 'short_text'; + break; + case 'double': + case 'integer': + $type = 'number_text'; + break; + case 'boolean': + $type = 'checkbox'; + break; + case 'select': + $type = 'select'; + $opts['values'] = $form->getOptionValueList($form->fields[$field]); + break; + case 'array': + $type = 'list'; + $value = (array) $value; + $value_default = (array) $value_default; + break; + case 'group': + // :group:end is changed to :group:end:{unique id} in Form class + $htmlOutput = ''; + if (mb_substr($field, 7, 4) != 'end:') { + $htmlOutput .= FormDisplayTemplate::displayGroupHeader( + mb_substr($field, 7) + ); + } else { + FormDisplayTemplate::displayGroupFooter(); + } + return $htmlOutput; + case 'NULL': + trigger_error("Field $system_path has no type", E_USER_WARNING); + return null; + } + + // detect password fields + if ($type === 'text' + && (mb_substr($translated_path, -9) === '-password' + || mb_substr($translated_path, -4) === 'pass' + || mb_substr($translated_path, -4) === 'Pass') + ) { + $type = 'password'; + } + + // TrustedProxies requires changes before displaying + if ($system_path == 'TrustedProxies') { + foreach ($value as $ip => &$v) { + if (!preg_match('/^-\d+$/', $ip)) { + $v = $ip . ': ' . $v; + } + } + } + $this->_setComments($system_path, $opts); + + // send default value to form's JS + $js_line = '\'' . $translated_path . '\': '; + switch ($type) { + case 'text': + case 'short_text': + case 'number_text': + case 'password': + $js_line .= '\'' . Sanitize::escapeJsString($value_default) . '\''; + break; + case 'checkbox': + $js_line .= $value_default ? 'true' : 'false'; + break; + case 'select': + $value_default_js = is_bool($value_default) + ? (int) $value_default + : $value_default; + $js_line .= '[\'' . Sanitize::escapeJsString($value_default_js) . '\']'; + break; + case 'list': + $js_line .= '\'' . Sanitize::escapeJsString(implode("\n", $value_default)) + . '\''; + break; + } + $js_default[] = $js_line; + + return FormDisplayTemplate::displayInput( + $translated_path, $name, $type, $value, + $description, $value_is_default, $opts + ); + } + + /** + * Displays errors + * + * @return string HTML for errors + */ + public function displayErrors() + { + $this->_validate(); + if (count($this->_errors) == 0) { + return null; + } + + $htmlOutput = ''; + + foreach ($this->_errors as $system_path => $error_list) { + if (isset($this->_systemPaths[$system_path])) { + $name = Descriptions::get($this->_systemPaths[$system_path]); + } else { + $name = Descriptions::get('Form_' . $system_path); + } + $htmlOutput .= FormDisplayTemplate::displayErrors($name, $error_list); + } + + return $htmlOutput; + } + + /** + * Reverts erroneous fields to their default values + * + * @return void + */ + public function fixErrors() + { + $this->_validate(); + if (count($this->_errors) == 0) { + return; + } + + $cf = $this->_configFile; + foreach (array_keys($this->_errors) as $work_path) { + if (!isset($this->_systemPaths[$work_path])) { + continue; + } + $canonical_path = $this->_systemPaths[$work_path]; + $cf->set($work_path, $cf->getDefault($canonical_path)); + } + } + + /** + * Validates select field and casts $value to correct type + * + * @param string &$value Current value + * @param array $allowed List of allowed values + * + * @return bool + */ + private function _validateSelect(&$value, array $allowed) + { + $value_cmp = is_bool($value) + ? (int) $value + : $value; + foreach ($allowed as $vk => $v) { + // equality comparison only if both values are numeric or not numeric + // (allows to skip 0 == 'string' equalling to true) + // or identity (for string-string) + if (($vk == $value && !(is_numeric($value_cmp) xor is_numeric($vk))) + || $vk === $value + ) { + // keep boolean value as boolean + if (!is_bool($value)) { + settype($value, gettype($vk)); + } + return true; + } + } + return false; + } + + /** + * Validates and saves form data to session + * + * @param array|string $forms array of form names + * @param bool $allow_partial_save allows for partial form saving on + * failed validation + * + * @return boolean true on success (no errors and all saved) + */ + public function save($forms, $allow_partial_save = true) + { + $result = true; + $forms = (array) $forms; + + $values = array(); + $to_save = array(); + $is_setup_script = $GLOBALS['PMA_Config']->get('is_setup'); + if ($is_setup_script) { + $this->_loadUserprefsInfo(); + } + + $this->_errors = array(); + foreach ($forms as $form_name) { + /* @var $form Form */ + if (isset($this->_forms[$form_name])) { + $form = $this->_forms[$form_name]; + } else { + continue; + } + // get current server id + $change_index = $form->index === 0 + ? $this->_configFile->getServerCount() + 1 + : false; + // grab POST values + foreach ($form->fields as $field => $system_path) { + $work_path = array_search($system_path, $this->_systemPaths); + $key = $this->_translatedPaths[$work_path]; + $type = $form->getOptionType($field); + + // skip groups + if ($type == 'group') { + continue; + } + + // ensure the value is set + if (!isset($_POST[$key])) { + // checkboxes aren't set by browsers if they're off + if ($type == 'boolean') { + $_POST[$key] = false; + } else { + $this->_errors[$form->name][] = sprintf( + __('Missing data for %s'), + '' . Descriptions::get($system_path) . '' + ); + $result = false; + continue; + } + } + + // user preferences allow/disallow + if ($is_setup_script + && isset($this->_userprefsKeys[$system_path]) + ) { + if (isset($this->_userprefsDisallow[$system_path]) + && isset($_POST[$key . '-userprefs-allow']) + ) { + unset($this->_userprefsDisallow[$system_path]); + } elseif (!isset($_POST[$key . '-userprefs-allow'])) { + $this->_userprefsDisallow[$system_path] = true; + } + } + + // cast variables to correct type + switch ($type) { + case 'double': + $_POST[$key] = Util::requestString($_POST[$key]); + settype($_POST[$key], 'float'); + break; + case 'boolean': + case 'integer': + if ($_POST[$key] !== '') { + $_POST[$key] = Util::requestString($_POST[$key]); + settype($_POST[$key], $type); + } + break; + case 'select': + $successfully_validated = $this->_validateSelect( + $_POST[$key], + $form->getOptionValueList($system_path) + ); + if (! $successfully_validated) { + $this->_errors[$work_path][] = __('Incorrect value!'); + $result = false; + // "continue" for the $form->fields foreach-loop + continue 2; + } + break; + case 'string': + case 'short_string': + $_POST[$key] = Util::requestString($_POST[$key]); + break; + case 'array': + // eliminate empty values and ensure we have an array + $post_values = is_array($_POST[$key]) + ? $_POST[$key] + : explode("\n", $_POST[$key]); + $_POST[$key] = array(); + $this->_fillPostArrayParameters($post_values, $key); + break; + } + + // now we have value with proper type + $values[$system_path] = $_POST[$key]; + if ($change_index !== false) { + $work_path = str_replace( + "Servers/$form->index/", + "Servers/$change_index/", $work_path + ); + } + $to_save[$work_path] = $system_path; + } + } + + // save forms + if (!$allow_partial_save && !empty($this->_errors)) { + // don't look for non-critical errors + $this->_validate(); + return $result; + } + + foreach ($to_save as $work_path => $path) { + // TrustedProxies requires changes before saving + if ($path == 'TrustedProxies') { + $proxies = array(); + $i = 0; + foreach ($values[$path] as $value) { + $matches = array(); + $match = preg_match( + "/^(.+):(?:[ ]?)(\\w+)$/", $value, $matches + ); + if ($match) { + // correct 'IP: HTTP header' pair + $ip = trim($matches[1]); + $proxies[$ip] = trim($matches[2]); + } else { + // save also incorrect values + $proxies["-$i"] = $value; + $i++; + } + } + $values[$path] = $proxies; + } + $this->_configFile->set($work_path, $values[$path], $path); + } + if ($is_setup_script) { + $this->_configFile->set( + 'UserprefsDisallow', + array_keys($this->_userprefsDisallow) + ); + } + + // don't look for non-critical errors + $this->_validate(); + + return $result; + } + + /** + * Tells whether form validation failed + * + * @return boolean + */ + public function hasErrors() + { + return count($this->_errors) > 0; + } + + + /** + * Returns link to documentation + * + * @param string $path Path to documentation + * + * @return string + */ + public function getDocLink($path) + { + $test = mb_substr($path, 0, 6); + if ($test == 'Import' || $test == 'Export') { + return ''; + } + return Util::getDocuLink( + 'config', + 'cfg_' . $this->_getOptName($path) + ); + } + + /** + * Changes path so it can be used in URLs + * + * @param string $path Path + * + * @return string + */ + private function _getOptName($path) + { + return str_replace(array('Servers/1/', '/'), array('Servers/', '_'), $path); + } + + /** + * Fills out {@link userprefs_keys} and {@link userprefs_disallow} + * + * @return void + */ + private function _loadUserprefsInfo() + { + if ($this->_userprefsKeys !== null) { + return; + } + + $this->_userprefsKeys = array_flip(UserFormList::getFields()); + // read real config for user preferences display + $userprefs_disallow = $GLOBALS['PMA_Config']->get('is_setup') + ? $this->_configFile->get('UserprefsDisallow', array()) + : $GLOBALS['cfg']['UserprefsDisallow']; + $this->_userprefsDisallow = array_flip($userprefs_disallow); + } + + /** + * Sets field comments and warnings based on current environment + * + * @param string $system_path Path to settings + * @param array &$opts Chosen options + * + * @return void + */ + private function _setComments($system_path, array &$opts) + { + // RecodingEngine - mark unavailable types + if ($system_path == 'RecodingEngine') { + $comment = ''; + if (!function_exists('iconv')) { + $opts['values']['iconv'] .= ' (' . __('unavailable') . ')'; + $comment = sprintf( + __('"%s" requires %s extension'), 'iconv', 'iconv' + ); + } + if (!function_exists('recode_string')) { + $opts['values']['recode'] .= ' (' . __('unavailable') . ')'; + $comment .= ($comment ? ", " : '') . sprintf( + __('"%s" requires %s extension'), + 'recode', 'recode' + ); + } + /* mbstring is always there thanks to polyfill */ + $opts['comment'] = $comment; + $opts['comment_warning'] = true; + } + // ZipDump, GZipDump, BZipDump - check function availability + if ($system_path == 'ZipDump' + || $system_path == 'GZipDump' + || $system_path == 'BZipDump' + ) { + $comment = ''; + $funcs = array( + 'ZipDump' => array('zip_open', 'gzcompress'), + 'GZipDump' => array('gzopen', 'gzencode'), + 'BZipDump' => array('bzopen', 'bzcompress')); + if (!function_exists($funcs[$system_path][0])) { + $comment = sprintf( + __( + 'Compressed import will not work due to missing function %s.' + ), + $funcs[$system_path][0] + ); + } + if (!function_exists($funcs[$system_path][1])) { + $comment .= ($comment ? '; ' : '') . sprintf( + __( + 'Compressed export will not work due to missing function %s.' + ), + $funcs[$system_path][1] + ); + } + $opts['comment'] = $comment; + $opts['comment_warning'] = true; + } + if (! $GLOBALS['PMA_Config']->get('is_setup')) { + if (($system_path == 'MaxDbList' || $system_path == 'MaxTableList' + || $system_path == 'QueryHistoryMax') + ) { + $opts['comment'] = sprintf( + __('maximum %s'), $GLOBALS['cfg'][$system_path] + ); + } + } + } + + /** + * Copy items of an array to $_POST variable + * + * @param array $post_values List of parameters + * @param string $key Array key + * + * @return void + */ + private function _fillPostArrayParameters(array $post_values, $key) + { + foreach ($post_values as $v) { + $v = Util::requestString($v); + if ($v !== '') { + $_POST[$key][] = $v; + } + } + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Config/FormDisplayTemplate.php b/php/apps/phpmyadmin49/libraries/classes/Config/FormDisplayTemplate.php new file mode 100644 index 00000000..88dee0c7 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Config/FormDisplayTemplate.php @@ -0,0 +1,493 @@ +'; + $htmlOutput .= ''; + // we do validation on page refresh when browser remembers field values, + // add a field with known value which will be used for checks + if (! $has_check_page_refresh) { + $has_check_page_refresh = true; + $htmlOutput .= '' . "\n"; + } + $htmlOutput .= Url::getHiddenInputs('', '', 0, 'server') . "\n"; + $htmlOutput .= Url::getHiddenFields((array)$hidden_fields); + return $htmlOutput; + } + + /** + * Displays form tabs which are given by an array indexed by fieldset id + * ({@link self::displayFieldsetTop}), with values being tab titles. + * + * @param array $tabs tab names + * + * @return string + */ + public static function displayTabsTop(array $tabs) + { + $items = array(); + foreach ($tabs as $tab_id => $tab_name) { + $items[] = array( + 'content' => htmlspecialchars($tab_name), + 'url' => array( + 'href' => '#' . $tab_id, + ), + ); + } + + $htmlOutput = Template::get('list/unordered')->render( + array( + 'class' => 'tabs responsivetable', + 'items' => $items, + ) + ); + $htmlOutput .= '
        '; + $htmlOutput .= '
        '; + return $htmlOutput; + } + + /** + * Displays top part of a fieldset + * + * @param string $title title of fieldset + * @param string $description description shown on top of fieldset + * @param array|null $errors error messages to display + * @param array $attributes optional extra attributes of fieldset + * + * @return string + */ + public static function displayFieldsetTop( + $title = '', + $description = '', + $errors = null, + array $attributes = array() + ) { + global $_FormDisplayGroup; + + $_FormDisplayGroup = 0; + + $attributes = array_merge(array('class' => 'optbox'), $attributes); + + return Template::get('config/form_display/fieldset_top')->render([ + 'attributes' => $attributes, + 'title' => $title, + 'description' => $description, + 'errors' => $errors, + ]); + } + + /** + * Displays input field + * + * $opts keys: + * o doc - (string) documentation link + * o errors - error array + * o setvalue - (string) shows button allowing to set predefined value + * o show_restore_default - (boolean) whether show "restore default" button + * o userprefs_allow - whether user preferences are enabled for this field + * (null - no support, true/false - enabled/disabled) + * o userprefs_comment - (string) field comment + * o values - key - value pairs for '; + break; + case 'password': + $htmlOutput .= ''; + break; + case 'short_text': + // As seen in the reporting server (#15042) we sometimes receive + // an array here. No clue about its origin nor content, so let's avoid + // a notice on htmlspecialchars(). + if (! is_array($value)) { + $htmlOutput .= ''; + } + break; + case 'number_text': + $htmlOutput .= ''; + break; + case 'checkbox': + $htmlOutput .= ''; + break; + case 'select': + $htmlOutput .= ''; + break; + case 'list': + $htmlOutput .= ''; + break; + } + if (isset($opts['comment']) && $opts['comment']) { + $class = 'field-comment-mark'; + if (isset($opts['comment_warning']) && $opts['comment_warning']) { + $class .= ' field-comment-warning'; + } + $htmlOutput .= 'i'; + } + if ($is_setup_script + && isset($opts['userprefs_comment']) + && $opts['userprefs_comment'] + ) { + $htmlOutput .= '' + . $icons['tblops'] . ''; + } + if (isset($opts['setvalue']) && $opts['setvalue']) { + $htmlOutput .= '' . $icons['edit'] . ''; + } + if (isset($opts['show_restore_default']) && $opts['show_restore_default']) { + $htmlOutput .= '' . $icons['reload'] . ''; + } + // this must match with displayErrors() in scripts/config.js + if ($has_errors) { + $htmlOutput .= "\n
        "; + foreach ($opts['errors'] as $error) { + $htmlOutput .= '
        ' . htmlspecialchars($error) . '
        '; + } + $htmlOutput .= '
        '; + } + $htmlOutput .= ''; + if ($is_setup_script && isset($opts['userprefs_allow'])) { + $htmlOutput .= ''; + $htmlOutput .= 'get('is_setup') ? 3 : 2; + + return Template::get('config/form_display/group_header')->render([ + 'group' => $_FormDisplayGroup, + 'colspan' => $colspan, + 'header_text' => $headerText, + ]); + } + + /** + * Display group footer + * + * @return void + */ + public static function displayGroupFooter() + { + global $_FormDisplayGroup; + + $_FormDisplayGroup--; + } + + /** + * Displays bottom part of a fieldset + * + * @param bool $showButtons Whether show submit and reset button + * + * @return string + */ + public static function displayFieldsetBottom($showButtons = true) + { + return Template::get('config/form_display/fieldset_bottom')->render([ + 'show_buttons' => $showButtons, + 'is_setup' => $GLOBALS['PMA_Config']->get('is_setup'), + ]); + } + + /** + * Closes form tabs + * + * @return string + */ + public static function displayTabsBottom() + { + return Template::get('config/form_display/tabs_bottom')->render(); + } + + /** + * Displays bottom part of the form + * + * @return string + */ + public static function displayFormBottom() + { + return Template::get('config/form_display/form_bottom')->render(); + } + + /** + * Appends JS validation code to $js_array + * + * @param string $field_id ID of field to validate + * @param string|array $validators validators callback + * @param array &$js_array will be updated with javascript code + * + * @return void + */ + public static function addJsValidate($field_id, $validators, array &$js_array) + { + foreach ((array)$validators as $validator) { + $validator = (array)$validator; + $v_name = array_shift($validator); + $v_name = "PMA_" . $v_name; + $v_args = array(); + foreach ($validator as $arg) { + $v_args[] = Sanitize::escapeJsString($arg); + } + $v_args = $v_args ? ", ['" . implode("', '", $v_args) . "']" : ''; + $js_array[] = "validateField('$field_id', '$v_name', true$v_args)"; + } + } + + /** + * Displays JavaScript code + * + * @param array $js_array lines of javascript code + * + * @return string + */ + public static function displayJavascript(array $js_array) + { + if (empty($js_array)) { + return null; + } + + return Template::get('javascript/display')->render( + array('js_array' => $js_array,) + ); + } + + /** + * Displays error list + * + * @param string $name Name of item with errors + * @param array $errorList List of errors to show + * + * @return string HTML for errors + */ + public static function displayErrors($name, array $errorList) + { + return Template::get('config/form_display/errors')->render([ + 'name' => $name, + 'error_list' => $errorList, + ]); + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Config/Forms/BaseForm.php b/php/apps/phpmyadmin49/libraries/classes/Config/Forms/BaseForm.php new file mode 100644 index 00000000..c16fb8b3 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Config/Forms/BaseForm.php @@ -0,0 +1,85 @@ += 1 if editing a server + */ + public function __construct(ConfigFile $cf, $server_id = null) + { + parent::__construct($cf); + foreach (static::getForms() as $form_name => $form) { + $this->registerForm($form_name, $form, $server_id); + } + } + + /** + * List of available forms, each form is described as an array of fields to display. + * Fields MUST have their counterparts in the $cfg array. + * + * To define form field, use the notation below: + * $forms['Form group']['Form name'] = array('Option/path'); + * + * You can assign default values set by special button ("set value: ..."), eg.: + * 'Servers/1/pmadb' => 'phpmyadmin' + * + * To group options, use: + * ':group:' . __('group name') // just define a group + * or + * 'option' => ':group' // group starting from this option + * End group blocks with: + * ':group:end' + * + * @todo This should be abstract, but that does not work in PHP 5 + * + * @return array + */ + public static function getForms() + { + return array(); + } + + /** + * Returns list of fields used in the form. + * + * @return string[] + */ + public static function getFields() + { + $names = []; + foreach (static::getForms() as $form) { + foreach ($form as $k => $v) { + $names[] = is_int($k) ? $v : $k; + } + } + return $names; + } + + /** + * Returns name of the form + * + * @todo This should be abstract, but that does not work in PHP 5 + * + * @return string + */ + public static function getName() + { + return ''; + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Config/Forms/BaseFormList.php b/php/apps/phpmyadmin49/libraries/classes/Config/Forms/BaseFormList.php new file mode 100644 index 00000000..1065c3b3 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Config/Forms/BaseFormList.php @@ -0,0 +1,127 @@ +_forms = array(); + foreach (static::$all as $form) { + $class = static::get($form); + $this->_forms[] = new $class($cf); + } + } + + /** + * Processes forms, returns true on successful save + * + * @param bool $allow_partial_save allows for partial form saving + * on failed validation + * @param bool $check_form_submit whether check for $_POST['submit_save'] + * + * @return boolean whether processing was successful + */ + public function process($allow_partial_save = true, $check_form_submit = true) + { + $ret = true; + foreach ($this->_forms as $form) { + $ret = $ret && $form->process($allow_partial_save, $check_form_submit); + } + return $ret; + } + + /** + * Displays errors + * + * @return string HTML for errors + */ + public function displayErrors() + { + $ret = ''; + foreach ($this->_forms as $form) { + $ret .= $form->displayErrors(); + } + return $ret; + } + + /** + * Reverts erroneous fields to their default values + * + * @return void + */ + public function fixErrors() + { + foreach ($this->_forms as $form) { + $form->fixErrors(); + } + } + + /** + * Tells whether form validation failed + * + * @return boolean + */ + public function hasErrors() + { + $ret = false; + foreach ($this->_forms as $form) { + $ret = $ret || $form->hasErrors(); + } + return $ret; + } + + /** + * Returns list of fields used in the form. + * + * @return string[] + */ + public static function getFields() + { + $names = []; + foreach (static::$all as $form) { + $class = static::get($form); + $names = array_merge($names, $class::getFields()); + } + return $names; + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Config/Forms/Page/BrowseForm.php b/php/apps/phpmyadmin49/libraries/classes/Config/Forms/Page/BrowseForm.php new file mode 100644 index 00000000..e39a7fd2 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Config/Forms/Page/BrowseForm.php @@ -0,0 +1,21 @@ + MainForm::getForms()['Browse'] + ]; + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Config/Forms/Page/DbStructureForm.php b/php/apps/phpmyadmin49/libraries/classes/Config/Forms/Page/DbStructureForm.php new file mode 100644 index 00000000..03c9172a --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Config/Forms/Page/DbStructureForm.php @@ -0,0 +1,22 @@ + MainForm::getForms()['DbStructure'] + ]; + } +} + diff --git a/php/apps/phpmyadmin49/libraries/classes/Config/Forms/Page/EditForm.php b/php/apps/phpmyadmin49/libraries/classes/Config/Forms/Page/EditForm.php new file mode 100644 index 00000000..b7b83b03 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Config/Forms/Page/EditForm.php @@ -0,0 +1,23 @@ + MainForm::getForms()['Edit'], + 'Text_fields' => FeaturesForm::getForms()['Text_fields'], + ]; + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Config/Forms/Page/ExportForm.php b/php/apps/phpmyadmin49/libraries/classes/Config/Forms/Page/ExportForm.php new file mode 100644 index 00000000..824be9c7 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Config/Forms/Page/ExportForm.php @@ -0,0 +1,12 @@ + MainForm::getForms()['TableStructure'] + ]; + } +} + diff --git a/php/apps/phpmyadmin49/libraries/classes/Config/Forms/Setup/ConfigForm.php b/php/apps/phpmyadmin49/libraries/classes/Config/Forms/Setup/ConfigForm.php new file mode 100644 index 00000000..35dfd854 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Config/Forms/Setup/ConfigForm.php @@ -0,0 +1,23 @@ + array( + 'DefaultLang', + 'ServerDefault' + ), + ); + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Config/Forms/Setup/ExportForm.php b/php/apps/phpmyadmin49/libraries/classes/Config/Forms/Setup/ExportForm.php new file mode 100644 index 00000000..09e59a14 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Config/Forms/Setup/ExportForm.php @@ -0,0 +1,12 @@ + ':group', + 'IconvExtraParams', + ':group:end', + 'ZipDump', + 'GZipDump', + 'BZipDump', + 'CompressOnFly' + ); + $result['Security'] = array( + 'blowfish_secret', + 'CheckConfigurationPermissions', + 'TrustedProxies', + 'AllowUserDropDatabase', + 'AllowArbitraryServer', + 'ArbitraryServerRegexp', + 'LoginCookieRecall', + 'LoginCookieStore', + 'LoginCookieDeleteAll', + 'CaptchaLoginPublicKey', + 'CaptchaLoginPrivateKey' + ); + $result['Developer'] = array( + 'UserprefsDeveloperTab', + 'DBG/sql', + ); + $result['Other_core_settings'] = array( + 'OBGzip', + 'PersistentConnections', + 'ExecTimeLimit', + 'MemoryLimit', + 'UseDbSearch', + 'ProxyUrl', + 'ProxyUser', + 'ProxyPass', + 'AllowThirdPartyFraming', + 'ZeroConf', + ); + return $result; + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Config/Forms/Setup/ImportForm.php b/php/apps/phpmyadmin49/libraries/classes/Config/Forms/Setup/ImportForm.php new file mode 100644 index 00000000..cc36b1ba --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Config/Forms/Setup/ImportForm.php @@ -0,0 +1,12 @@ + array('Servers' => array(1 => array( + 'verbose', + 'host', + 'port', + 'socket', + 'ssl', + 'compress'))), + 'Server_auth' => array('Servers' => array(1 => array( + 'auth_type', + ':group:' . __('Config authentication'), + 'user', + 'password', + ':group:end', + ':group:' . __('HTTP authentication'), + 'auth_http_realm', + ':group:end', + ':group:' . __('Signon authentication'), + 'SignonSession', + 'SignonURL', + 'LogoutURL'))), + 'Server_config' => array('Servers' => array(1 => array( + 'only_db', + 'hide_db', + 'AllowRoot', + 'AllowNoPassword', + 'DisableIS', + 'AllowDeny/order', + 'AllowDeny/rules', + 'SessionTimeZone'))), + 'Server_pmadb' => array('Servers' => array(1 => array( + 'pmadb' => 'phpmyadmin', + 'controlhost', + 'controlport', + 'controluser', + 'controlpass', + 'bookmarktable' => 'pma__bookmark', + 'relation' => 'pma__relation', + 'userconfig' => 'pma__userconfig', + 'users' => 'pma__users', + 'usergroups' => 'pma__usergroups', + 'navigationhiding' => 'pma__navigationhiding', + 'table_info' => 'pma__table_info', + 'column_info' => 'pma__column_info', + 'history' => 'pma__history', + 'recent' => 'pma__recent', + 'favorite' => 'pma__favorite', + 'table_uiprefs' => 'pma__table_uiprefs', + 'tracking' => 'pma__tracking', + 'table_coords' => 'pma__table_coords', + 'pdf_pages' => 'pma__pdf_pages', + 'savedsearches' => 'pma__savedsearches', + 'central_columns' => 'pma__central_columns', + 'designer_settings' => 'pma__designer_settings', + 'export_templates' => 'pma__export_templates', + 'MaxTableUiprefs' => 100))), + 'Server_tracking' => array('Servers' => array(1 => array( + 'tracking_version_auto_create', + 'tracking_default_statements', + 'tracking_add_drop_view', + 'tracking_add_drop_table', + 'tracking_add_drop_database', + ))), + ); + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Config/Forms/Setup/SetupFormList.php b/php/apps/phpmyadmin49/libraries/classes/Config/Forms/Setup/SetupFormList.php new file mode 100644 index 00000000..900bf059 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Config/Forms/Setup/SetupFormList.php @@ -0,0 +1,25 @@ + array( + 'Export/method', + ':group:' . __('Quick'), + 'Export/quick_export_onserver', + 'Export/quick_export_onserver_overwrite', + ':group:end', + ':group:' . __('Custom'), + 'Export/format', + 'Export/compression', + 'Export/charset', + 'Export/lock_tables', + 'Export/as_separate_files', + 'Export/asfile' => ':group', + 'Export/onserver', + 'Export/onserver_overwrite', + ':group:end', + 'Export/file_template_table', + 'Export/file_template_database', + 'Export/file_template_server' + ), + 'Sql' => array( + 'Export/sql_include_comments' => ':group', + 'Export/sql_dates', + 'Export/sql_relation', + 'Export/sql_mime', + ':group:end', + 'Export/sql_use_transaction', + 'Export/sql_disable_fk', + 'Export/sql_views_as_tables', + 'Export/sql_metadata', + 'Export/sql_compatibility', + 'Export/sql_structure_or_data', + ':group:' . __('Structure'), + 'Export/sql_drop_database', + 'Export/sql_create_database', + 'Export/sql_drop_table', + 'Export/sql_create_table' => ':group', + 'Export/sql_if_not_exists', + 'Export/sql_auto_increment', + ':group:end', + 'Export/sql_create_view', + 'Export/sql_procedure_function', + 'Export/sql_create_trigger', + 'Export/sql_backquotes', + ':group:end', + ':group:' . __('Data'), + 'Export/sql_delayed', + 'Export/sql_ignore', + 'Export/sql_type', + 'Export/sql_insert_syntax', + 'Export/sql_max_query_size', + 'Export/sql_hex_for_binary', + 'Export/sql_utc_time' + ), + 'CodeGen' => array( + 'Export/codegen_format' + ), + 'Csv' => array( + ':group:' . __('CSV'), + 'Export/csv_separator', + 'Export/csv_enclosed', + 'Export/csv_escaped', + 'Export/csv_terminated', + 'Export/csv_null', + 'Export/csv_removeCRLF', + 'Export/csv_columns', + ':group:end', + ':group:' . __('CSV for MS Excel'), + 'Export/excel_null', + 'Export/excel_removeCRLF', + 'Export/excel_columns', + 'Export/excel_edition' + ), + 'Latex' => array( + 'Export/latex_caption', + 'Export/latex_structure_or_data', + ':group:' . __('Structure'), + 'Export/latex_structure_caption', + 'Export/latex_structure_continued_caption', + 'Export/latex_structure_label', + 'Export/latex_relation', + 'Export/latex_comments', + 'Export/latex_mime', + ':group:end', + ':group:' . __('Data'), + 'Export/latex_columns', + 'Export/latex_data_caption', + 'Export/latex_data_continued_caption', + 'Export/latex_data_label', + 'Export/latex_null' + ), + 'Microsoft_Office' => array( + ':group:' . __('Microsoft Word 2000'), + 'Export/htmlword_structure_or_data', + 'Export/htmlword_null', + 'Export/htmlword_columns'), + 'Open_Document' => array( + ':group:' . __('OpenDocument Spreadsheet'), + 'Export/ods_columns', + 'Export/ods_null', + ':group:end', + ':group:' . __('OpenDocument Text'), + 'Export/odt_structure_or_data', + ':group:' . __('Structure'), + 'Export/odt_relation', + 'Export/odt_comments', + 'Export/odt_mime', + ':group:end', + ':group:' . __('Data'), + 'Export/odt_columns', + 'Export/odt_null' + ), + 'Texy' => array( + 'Export/texytext_structure_or_data', + ':group:' . __('Data'), + 'Export/texytext_null', + 'Export/texytext_columns' + ), + ); + } + + public static function getName() + { + return __('Export'); + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Config/Forms/User/FeaturesForm.php b/php/apps/phpmyadmin49/libraries/classes/Config/Forms/User/FeaturesForm.php new file mode 100644 index 00000000..9a121e9e --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Config/Forms/User/FeaturesForm.php @@ -0,0 +1,84 @@ + array( + 'VersionCheck', + 'NaturalOrder', + 'InitialSlidersState', + 'SkipLockedTables', + 'DisableMultiTableMaintenance', + 'ShowHint', + 'SendErrorReports', + 'ConsoleEnterExecutes', + 'DisableShortcutKeys', + 'FontSize', + ), + 'Databases' => array( + 'Servers/1/only_db', // saves to Server/only_db + 'Servers/1/hide_db', // saves to Server/hide_db + 'MaxDbList', + 'MaxTableList', + 'DefaultConnectionCollation', + ), + 'Text_fields' => array( + 'CharEditing', + 'MinSizeForInputField', + 'MaxSizeForInputField', + 'CharTextareaCols', + 'CharTextareaRows', + 'TextareaCols', + 'TextareaRows', + 'LongtextDoubleTextarea' + ), + 'Page_titles' => array( + 'TitleDefault', + 'TitleTable', + 'TitleDatabase', + 'TitleServer' + ), + 'Warnings' => array( + 'PmaNoRelation_DisableWarning', + 'SuhosinDisableWarning', + 'LoginCookieValidityDisableWarning', + 'ReservedWordDisableWarning' + ), + 'Console' => array( + 'Console/Mode', + 'Console/StartHistory', + 'Console/AlwaysExpand', + 'Console/CurrentQuery', + 'Console/EnterExecutes', + 'Console/DarkTheme', + 'Console/Height', + 'Console/GroupQueries', + 'Console/OrderBy', + 'Console/Order', + ), + ); + // skip Developer form if no setting is available + if ($GLOBALS['cfg']['UserprefsDeveloperTab']) { + $result['Developer'] = array( + 'DBG/sql' + ); + } + return $result; + } + + public static function getName() + { + return __('Features'); + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Config/Forms/User/ImportForm.php b/php/apps/phpmyadmin49/libraries/classes/Config/Forms/User/ImportForm.php new file mode 100644 index 00000000..daf5c43b --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Config/Forms/User/ImportForm.php @@ -0,0 +1,60 @@ + array( + 'Import/format', + 'Import/charset', + 'Import/allow_interrupt', + 'Import/skip_queries' + ), + 'Sql' => array( + 'Import/sql_compatibility', + 'Import/sql_no_auto_value_on_zero', + 'Import/sql_read_as_multibytes' + ), + 'Csv' => array( + ':group:' . __('CSV'), + 'Import/csv_replace', + 'Import/csv_ignore', + 'Import/csv_terminated', + 'Import/csv_enclosed', + 'Import/csv_escaped', + 'Import/csv_col_names', + ':group:end', + ':group:' . __('CSV using LOAD DATA'), + 'Import/ldi_replace', + 'Import/ldi_ignore', + 'Import/ldi_terminated', + 'Import/ldi_enclosed', + 'Import/ldi_escaped', + 'Import/ldi_local_option' + ), + 'Open_Document' => array( + ':group:' . __('OpenDocument Spreadsheet'), + 'Import/ods_col_names', + 'Import/ods_empty_rows', + 'Import/ods_recognize_percentages', + 'Import/ods_recognize_currency' + ), + + ); + } + + public static function getName() + { + return __('Import'); + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Config/Forms/User/MainForm.php b/php/apps/phpmyadmin49/libraries/classes/Config/Forms/User/MainForm.php new file mode 100644 index 00000000..e3c54edd --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Config/Forms/User/MainForm.php @@ -0,0 +1,86 @@ + array( + 'ShowCreateDb', + 'ShowStats', + 'ShowServerInfo' + ), + 'DbStructure' => array( + 'ShowDbStructureCharset', + 'ShowDbStructureComment', + 'ShowDbStructureCreation', + 'ShowDbStructureLastUpdate', + 'ShowDbStructureLastCheck' + ), + 'TableStructure' => array( + 'HideStructureActions', + 'ShowColumnComments', + ':group:' . __('Default transformations'), + 'DefaultTransformations/Hex', + 'DefaultTransformations/Substring', + 'DefaultTransformations/Bool2Text', + 'DefaultTransformations/External', + 'DefaultTransformations/PreApPend', + 'DefaultTransformations/DateFormat', + 'DefaultTransformations/Inline', + 'DefaultTransformations/TextImageLink', + 'DefaultTransformations/TextLink', + ':group:end' + ), + 'Browse' => array( + 'TableNavigationLinksMode', + 'ActionLinksMode', + 'ShowAll', + 'MaxRows', + 'Order', + 'BrowsePointerEnable', + 'BrowseMarkerEnable', + 'GridEditing', + 'SaveCellsAtOnce', + 'RepeatCells', + 'LimitChars', + 'RowActionLinks', + 'RowActionLinksWithoutUnique', + 'TablePrimaryKeyOrder', + 'RememberSorting', + 'RelationalDisplay' + ), + 'Edit' => array( + 'ProtectBinary', + 'ShowFunctionFields', + 'ShowFieldTypesInDataEditView', + 'InsertRows', + 'ForeignKeyDropdownOrder', + 'ForeignKeyMaxLimit' + ), + 'Tabs' => array( + 'TabsMode', + 'DefaultTabServer', + 'DefaultTabDatabase', + 'DefaultTabTable' + ), + 'DisplayRelationalSchema' => array( + 'PDFDefaultPageSize' + ), + ); + } + + public static function getName() + { + return __('Main panel'); + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Config/Forms/User/NaviForm.php b/php/apps/phpmyadmin49/libraries/classes/Config/Forms/User/NaviForm.php new file mode 100644 index 00000000..1d9ff1ff --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Config/Forms/User/NaviForm.php @@ -0,0 +1,61 @@ + array( + 'ShowDatabasesNavigationAsTree', + 'NavigationLinkWithMainPanel', + 'NavigationDisplayLogo', + 'NavigationLogoLink', + 'NavigationLogoLinkWindow', + 'NavigationTreePointerEnable', + 'FirstLevelNavigationItems', + 'NavigationTreeDisplayItemFilterMinimum', + 'NumRecentTables', + 'NumFavoriteTables', + 'NavigationWidth', + ), + 'Navi_tree' => array( + 'MaxNavigationItems', + 'NavigationTreeEnableGrouping', + 'NavigationTreeEnableExpansion', + 'NavigationTreeShowTables', + 'NavigationTreeShowViews', + 'NavigationTreeShowFunctions', + 'NavigationTreeShowProcedures', + 'NavigationTreeShowEvents' + ), + 'Navi_servers' => array( + 'NavigationDisplayServers', + 'DisplayServersList', + ), + 'Navi_databases' => array( + 'NavigationTreeDisplayDbFilterMinimum', + 'NavigationTreeDbSeparator' + ), + 'Navi_tables' => array( + 'NavigationTreeDefaultTabTable', + 'NavigationTreeDefaultTabTable2', + 'NavigationTreeTableSeparator', + 'NavigationTreeTableLevel', + ), + ); + } + + public static function getName() + { + return __('Navigation panel'); + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Config/Forms/User/SqlForm.php b/php/apps/phpmyadmin49/libraries/classes/Config/Forms/User/SqlForm.php new file mode 100644 index 00000000..2058076a --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Config/Forms/User/SqlForm.php @@ -0,0 +1,42 @@ + array( + 'ShowSQL', + 'Confirm', + 'QueryHistoryMax', + 'IgnoreMultiSubmitErrors', + 'MaxCharactersInDisplayedSQL', + 'RetainQueryBox', + 'CodemirrorEnable', + 'LintEnable', + 'EnableAutocompleteForTablesAndColumns', + 'DefaultForeignKeyChecks', + ), + 'Sql_box' => array( + 'SQLQuery/Edit', + 'SQLQuery/Explain', + 'SQLQuery/ShowAsPHP', + 'SQLQuery/Refresh', + ), + ); + } + + public static function getName() + { + return __('SQL queries'); + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Config/Forms/User/UserFormList.php b/php/apps/phpmyadmin49/libraries/classes/Config/Forms/User/UserFormList.php new file mode 100644 index 00000000..f32cae32 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Config/Forms/User/UserFormList.php @@ -0,0 +1,23 @@ +userPreferences = new UserPreferences(); + + $form_class = PageFormList::get($formGroupName); + if (is_null($form_class)) { + return; + } + + if (isset($_REQUEST['printview']) && $_REQUEST['printview'] == '1') { + return; + } + + if (!empty($elemId)) { + $this->_elemId = $elemId; + } + $this->_groupName = $formGroupName; + + $cf = new ConfigFile($GLOBALS['PMA_Config']->base_settings); + $this->userPreferences->pageInit($cf); + + $form_display = new $form_class($cf); + + // Process form + $error = null; + if (isset($_POST['submit_save']) + && $_POST['submit_save'] == $formGroupName + ) { + $this->_processPageSettings($form_display, $cf, $error); + } + + // Display forms + $this->_HTML = $this->_getPageSettingsDisplay($form_display, $error); + } + + /** + * Process response to form + * + * @param FormDisplay &$form_display Form + * @param ConfigFile &$cf Configuration file + * @param Message|null &$error Error message + * + * @return void + */ + private function _processPageSettings(&$form_display, &$cf, &$error) + { + if ($form_display->process(false) && !$form_display->hasErrors()) { + // save settings + $result = $this->userPreferences->save($cf->getConfigArray()); + if ($result === true) { + // reload page + $response = Response::getInstance(); + Core::sendHeaderLocation( + $response->getFooter()->getSelfUrl('unencoded') + ); + exit(); + } else { + $error = $result; + } + } + } + + /** + * Store errors in _errorHTML + * + * @param FormDisplay &$form_display Form + * @param Message|null &$error Error message + * + * @return void + */ + private function _storeError(&$form_display, &$error) + { + $retval = ''; + if ($error) { + $retval .= $error->getDisplay(); + } + if ($form_display->hasErrors()) { + // form has errors + $retval .= '
        ' + . '' . __( + 'Cannot save settings, submitted configuration form contains ' + . 'errors!' + ) . '' + . $form_display->displayErrors() + . '
        '; + } + $this->_errorHTML = $retval; + } + + /** + * Display page-related settings + * + * @param FormDisplay &$form_display Form + * @param Message &$error Error message + * + * @return string + */ + private function _getPageSettingsDisplay(&$form_display, &$error) + { + $response = Response::getInstance(); + + $retval = ''; + + $this->_storeError($form_display, $error); + + $retval .= '
        '; + $retval .= '
        '; + $retval .= $form_display->getDisplay( + true, + true, + false, + $response->getFooter()->getSelfUrl(), + array( + 'submit_save' => $this->_groupName + ) + ); + $retval .= '
        '; + $retval .= '
        '; + + return $retval; + } + + /** + * Get HTML output + * + * @return string + */ + public function getHTML() + { + return $this->_HTML; + } + + /** + * Get error HTML output + * + * @return string + */ + public function getErrorHTML() + { + return $this->_errorHTML; + } + + /** + * Group to show for Page-related settings + * @param string $formGroupName The name of config form group to display + * @return PageSettings + */ + public static function showGroup($formGroupName) + { + $object = new PageSettings($formGroupName); + + $response = Response::getInstance(); + $response->addHTML($object->getErrorHTML()); + $response->addHTML($object->getHTML()); + + return $object; + } + + /** + * Get HTML for navigation settings + * @return string + */ + public static function getNaviSettings() + { + $object = new PageSettings('Navi', 'pma_navigation_settings'); + + $response = Response::getInstance(); + $response->addHTML($object->getErrorHTML()); + return $object->getHTML(); + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Config/ServerConfigChecks.php b/php/apps/phpmyadmin49/libraries/classes/Config/ServerConfigChecks.php new file mode 100644 index 00000000..485c3b42 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Config/ServerConfigChecks.php @@ -0,0 +1,560 @@ +cfg = $cfg; + } + + /** + * Perform config checks + * + * @return void + */ + public function performConfigChecks() + { + $blowfishSecret = $this->cfg->get('blowfish_secret'); + $blowfishSecretSet = false; + $cookieAuthUsed = false; + + list($cookieAuthUsed, $blowfishSecret, $blowfishSecretSet) + = $this->performConfigChecksServers( + $cookieAuthUsed, $blowfishSecret, $blowfishSecretSet + ); + + $this->performConfigChecksCookieAuthUsed( + $cookieAuthUsed, $blowfishSecretSet, + $blowfishSecret + ); + + // + // $cfg['AllowArbitraryServer'] + // should be disabled + // + if ($this->cfg->getValue('AllowArbitraryServer')) { + $sAllowArbitraryServerWarn = sprintf( + __( + 'This %soption%s should be disabled as it allows attackers to ' + . 'bruteforce login to any MySQL server. If you feel this is necessary, ' + . 'use %srestrict login to MySQL server%s or %strusted proxies list%s. ' + . 'However, IP-based protection with trusted proxies list may not be ' + . 'reliable if your IP belongs to an ISP where thousands of users, ' + . 'including you, are connected to.' + ), + '[a@' . Url::getCommon(array('page' => 'form', 'formset' => 'Features')) . '#tab_Security]', + '[/a]', + '[a@' . Url::getCommon(array('page' => 'form', 'formset' => 'Features')) . '#tab_Security]', + '[/a]', + '[a@' . Url::getCommon(array('page' => 'form', 'formset' => 'Features')) . '#tab_Security]', + '[/a]' + ); + SetupIndex::messagesSet( + 'notice', + 'AllowArbitraryServer', + Descriptions::get('AllowArbitraryServer'), + Sanitize::sanitize($sAllowArbitraryServerWarn) + ); + } + + $this->performConfigChecksLoginCookie(); + + $sDirectoryNotice = __( + 'This value should be double checked to ensure that this directory is ' + . 'neither world accessible nor readable or writable by other users on ' + . 'your server.' + ); + + // + // $cfg['SaveDir'] + // should not be world-accessible + // + if ($this->cfg->getValue('SaveDir') != '') { + SetupIndex::messagesSet( + 'notice', + 'SaveDir', + Descriptions::get('SaveDir'), + Sanitize::sanitize($sDirectoryNotice) + ); + } + + // + // $cfg['TempDir'] + // should not be world-accessible + // + if ($this->cfg->getValue('TempDir') != '') { + SetupIndex::messagesSet( + 'notice', + 'TempDir', + Descriptions::get('TempDir'), + Sanitize::sanitize($sDirectoryNotice) + ); + } + + $this->performConfigChecksZips(); + } + + /** + * Check config of servers + * + * @param boolean $cookieAuthUsed Cookie auth is used + * @param string $blowfishSecret Blowfish secret + * @param boolean $blowfishSecretSet Blowfish secret set + * + * @return array + */ + protected function performConfigChecksServers( + $cookieAuthUsed, $blowfishSecret, + $blowfishSecretSet + ) { + $serverCnt = $this->cfg->getServerCount(); + for ($i = 1; $i <= $serverCnt; $i++) { + $cookieAuthServer + = ($this->cfg->getValue("Servers/$i/auth_type") == 'cookie'); + $cookieAuthUsed |= $cookieAuthServer; + $serverName = $this->performConfigChecksServersGetServerName( + $this->cfg->getServerName($i), $i + ); + $serverName = htmlspecialchars($serverName); + + list($blowfishSecret, $blowfishSecretSet) + = $this->performConfigChecksServersSetBlowfishSecret( + $blowfishSecret, $cookieAuthServer, $blowfishSecretSet + ); + + // + // $cfg['Servers'][$i]['ssl'] + // should be enabled if possible + // + if (!$this->cfg->getValue("Servers/$i/ssl")) { + $title = Descriptions::get('Servers/1/ssl') . " ($serverName)"; + SetupIndex::messagesSet( + 'notice', + "Servers/$i/ssl", + $title, + __( + 'You should use SSL connections if your database server ' + . 'supports it.' + ) + ); + } + $sSecurityInfoMsg = Sanitize::sanitize(sprintf( + __( + 'If you feel this is necessary, use additional protection settings - ' + . '%1$shost authentication%2$s settings and %3$strusted proxies list%4%s. ' + . 'However, IP-based protection may not be reliable if your IP belongs ' + . 'to an ISP where thousands of users, including you, are connected to.' + ), + '[a@' . Url::getCommon(array('page' => 'servers', 'mode' => 'edit', 'id' => $i)) . '#tab_Server_config]', + '[/a]', + '[a@' . Url::getCommon(array('page' => 'form', 'formset' => 'Features')) . '#tab_Security]', + '[/a]' + )); + + // + // $cfg['Servers'][$i]['auth_type'] + // warn about full user credentials if 'auth_type' is 'config' + // + if ($this->cfg->getValue("Servers/$i/auth_type") == 'config' + && $this->cfg->getValue("Servers/$i/user") != '' + && $this->cfg->getValue("Servers/$i/password") != '' + ) { + $title = Descriptions::get('Servers/1/auth_type') + . " ($serverName)"; + SetupIndex::messagesSet( + 'notice', + "Servers/$i/auth_type", + $title, + Sanitize::sanitize(sprintf( + __( + 'You set the [kbd]config[/kbd] authentication type and included ' + . 'username and password for auto-login, which is not a desirable ' + . 'option for live hosts. Anyone who knows or guesses your phpMyAdmin ' + . 'URL can directly access your phpMyAdmin panel. Set %1$sauthentication ' + . 'type%2$s to [kbd]cookie[/kbd] or [kbd]http[/kbd].' + ), + '[a@' . Url::getCommon(array('page' => 'servers', 'mode' => 'edit', 'id' => $i)) . '#tab_Server]', + '[/a]' + )) + . ' ' . $sSecurityInfoMsg + ); + } + + // + // $cfg['Servers'][$i]['AllowRoot'] + // $cfg['Servers'][$i]['AllowNoPassword'] + // serious security flaw + // + if ($this->cfg->getValue("Servers/$i/AllowRoot") + && $this->cfg->getValue("Servers/$i/AllowNoPassword") + ) { + $title = Descriptions::get('Servers/1/AllowNoPassword') + . " ($serverName)"; + SetupIndex::messagesSet( + 'notice', + "Servers/$i/AllowNoPassword", + $title, + __('You allow for connecting to the server without a password.') + . ' ' . $sSecurityInfoMsg + ); + } + } + return array($cookieAuthUsed, $blowfishSecret, $blowfishSecretSet); + } + + /** + * Set blowfish secret + * + * @param string $blowfishSecret Blowfish secret + * @param boolean $cookieAuthServer Cookie auth is used + * @param boolean $blowfishSecretSet Blowfish secret set + * + * @return array + */ + protected function performConfigChecksServersSetBlowfishSecret( + $blowfishSecret, $cookieAuthServer, $blowfishSecretSet + ) { + if ($cookieAuthServer && $blowfishSecret === null) { + $blowfishSecretSet = true; + $this->cfg->set('blowfish_secret', Util::generateRandom(32)); + } + return array($blowfishSecret, $blowfishSecretSet); + } + + /** + * Define server name + * + * @param string $serverName Server name + * @param int $serverId Server id + * + * @return string Server name + */ + protected function performConfigChecksServersGetServerName( + $serverName, $serverId + ) { + if ($serverName == 'localhost') { + $serverName .= " [$serverId]"; + return $serverName; + } + return $serverName; + } + + /** + * Perform config checks for zip part. + * + * @return void + */ + protected function performConfigChecksZips() { + $this->performConfigChecksServerGZipdump(); + $this->performConfigChecksServerBZipdump(); + $this->performConfigChecksServersZipdump(); + } + + /** + * Perform config checks for zip part. + * + * @return void + */ + protected function performConfigChecksServersZipdump() { + // + // $cfg['ZipDump'] + // requires zip_open in import + // + if ($this->cfg->getValue('ZipDump') && !$this->functionExists('zip_open')) { + SetupIndex::messagesSet( + 'error', + 'ZipDump_import', + Descriptions::get('ZipDump'), + Sanitize::sanitize(sprintf( + __( + '%sZip decompression%s requires functions (%s) which are unavailable ' + . 'on this system.' + ), + '[a@' . Url::getCommon(array('page' => 'form', 'formset' => 'Features')) . '#tab_Import_export]', + '[/a]', + 'zip_open' + )) + ); + } + + // + // $cfg['ZipDump'] + // requires gzcompress in export + // + if ($this->cfg->getValue('ZipDump') && !$this->functionExists('gzcompress')) { + SetupIndex::messagesSet( + 'error', + 'ZipDump_export', + Descriptions::get('ZipDump'), + Sanitize::sanitize(sprintf( + __( + '%sZip compression%s requires functions (%s) which are unavailable on ' + . 'this system.' + ), + '[a@' . Url::getCommon(array('page' => 'form', 'formset' => 'Features')) . '#tab_Import_export]', + '[/a]', + 'gzcompress' + )) + ); + } + } + + /** + * Check config of servers + * + * @param boolean $cookieAuthUsed Cookie auth is used + * @param boolean $blowfishSecretSet Blowfish secret set + * @param string $blowfishSecret Blowfish secret + * + * @return array + */ + protected function performConfigChecksCookieAuthUsed( + $cookieAuthUsed, $blowfishSecretSet, + $blowfishSecret + ) { + // + // $cfg['blowfish_secret'] + // it's required for 'cookie' authentication + // + if ($cookieAuthUsed) { + if ($blowfishSecretSet) { + // 'cookie' auth used, blowfish_secret was generated + SetupIndex::messagesSet( + 'notice', + 'blowfish_secret_created', + Descriptions::get('blowfish_secret'), + Sanitize::sanitize(__( + 'You didn\'t have blowfish secret set and have enabled ' + . '[kbd]cookie[/kbd] authentication, so a key was automatically ' + . 'generated for you. It is used to encrypt cookies; you don\'t need to ' + . 'remember it.' + )) + ); + } else { + $blowfishWarnings = array(); + // check length + if (strlen($blowfishSecret) < 32) { + // too short key + $blowfishWarnings[] = __( + 'Key is too short, it should have at least 32 characters.' + ); + } + // check used characters + $hasDigits = (bool)preg_match('/\d/', $blowfishSecret); + $hasChars = (bool)preg_match('/\S/', $blowfishSecret); + $hasNonword = (bool)preg_match('/\W/', $blowfishSecret); + if (!$hasDigits || !$hasChars || !$hasNonword) { + $blowfishWarnings[] = Sanitize::sanitize( + __( + 'Key should contain letters, numbers [em]and[/em] ' + . 'special characters.' + ) + ); + } + if (!empty($blowfishWarnings)) { + SetupIndex::messagesSet( + 'error', + 'blowfish_warnings' . count($blowfishWarnings), + Descriptions::get('blowfish_secret'), + implode('
        ', $blowfishWarnings) + ); + } + } + } + } + + /** + * Check configuration for login cookie + * + * @return void + */ + protected function performConfigChecksLoginCookie() { + // + // $cfg['LoginCookieValidity'] + // value greater than session.gc_maxlifetime will cause + // random session invalidation after that time + $loginCookieValidity = $this->cfg->getValue('LoginCookieValidity'); + if ($loginCookieValidity > ini_get('session.gc_maxlifetime') + ) { + SetupIndex::messagesSet( + 'error', + 'LoginCookieValidity', + Descriptions::get('LoginCookieValidity'), + Sanitize::sanitize(sprintf( + __( + '%1$sLogin cookie validity%2$s greater than %3$ssession.gc_maxlifetime%4$s may ' + . 'cause random session invalidation (currently session.gc_maxlifetime ' + . 'is %5$d).' + ), + '[a@' . Url::getCommon(array('page' => 'form', 'formset' => 'Features')) . '#tab_Security]', + '[/a]', + '[a@' . Core::getPHPDocLink('session.configuration.php#ini.session.gc-maxlifetime') . ']', + '[/a]', + ini_get('session.gc_maxlifetime') + )) + ); + } + + // + // $cfg['LoginCookieValidity'] + // should be at most 1800 (30 min) + // + if ($loginCookieValidity > 1800) { + SetupIndex::messagesSet( + 'notice', + 'LoginCookieValidity', + Descriptions::get('LoginCookieValidity'), + Sanitize::sanitize(sprintf( + __( + '%sLogin cookie validity%s should be set to 1800 seconds (30 minutes) ' + . 'at most. Values larger than 1800 may pose a security risk such as ' + . 'impersonation.' + ), + '[a@' . Url::getCommon(array('page' => 'form', 'formset' => 'Features')) . '#tab_Security]', + '[/a]' + )) + ); + } + + // + // $cfg['LoginCookieValidity'] + // $cfg['LoginCookieStore'] + // LoginCookieValidity must be less or equal to LoginCookieStore + // + if (($this->cfg->getValue('LoginCookieStore') != 0) + && ($loginCookieValidity > $this->cfg->getValue('LoginCookieStore')) + ) { + SetupIndex::messagesSet( + 'error', + 'LoginCookieValidity', + Descriptions::get('LoginCookieValidity'), + Sanitize::sanitize(sprintf( + __( + 'If using [kbd]cookie[/kbd] authentication and %sLogin cookie store%s ' + . 'is not 0, %sLogin cookie validity%s must be set to a value less or ' + . 'equal to it.' + ), + '[a@' . Url::getCommon(array('page' => 'form', 'formset' => 'Features')) . '#tab_Security]', + '[/a]', + '[a@' . Url::getCommon(array('page' => 'form', 'formset' => 'Features')) . '#tab_Security]', + '[/a]' + )) + ); + } + } + + /** + * Check GZipDump configuration + * + * @return void + */ + protected function performConfigChecksServerBZipdump() + { + // + // $cfg['BZipDump'] + // requires bzip2 functions + // + if ($this->cfg->getValue('BZipDump') + && (!$this->functionExists('bzopen') || !$this->functionExists('bzcompress')) + ) { + $functions = $this->functionExists('bzopen') + ? '' : + 'bzopen'; + $functions .= $this->functionExists('bzcompress') + ? '' + : ($functions ? ', ' : '') . 'bzcompress'; + SetupIndex::messagesSet( + 'error', + 'BZipDump', + Descriptions::get('BZipDump'), + Sanitize::sanitize( + sprintf( + __( + '%1$sBzip2 compression and decompression%2$s requires functions (%3$s) which ' + . 'are unavailable on this system.' + ), + '[a@' . Url::getCommon(array('page' => 'form', 'formset' => 'Features')) . '#tab_Import_export]', + '[/a]', + $functions + ) + ) + ); + } + } + + /** + * Check GZipDump configuration + * + * @return void + */ + protected function performConfigChecksServerGZipdump() + { + // + // $cfg['GZipDump'] + // requires zlib functions + // + if ($this->cfg->getValue('GZipDump') + && (!$this->functionExists('gzopen') || !$this->functionExists('gzencode')) + ) { + SetupIndex::messagesSet( + 'error', + 'GZipDump', + Descriptions::get('GZipDump'), + Sanitize::sanitize(sprintf( + __( + '%1$sGZip compression and decompression%2$s requires functions (%3$s) which ' + . 'are unavailable on this system.' + ), + '[a@' . Url::getCommon(array('page' => 'form', 'formset' => 'Features')) . '#tab_Import_export]', + '[/a]', + 'gzencode' + )) + ); + } + } + + /** + * Wrapper around function_exists to allow mock in test + * + * @param string $name Function name + * + * @return boolean + */ + protected function functionExists($name) + { + return function_exists($name); + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Config/Validator.php b/php/apps/phpmyadmin49/libraries/classes/Config/Validator.php new file mode 100644 index 00000000..2d0bcf32 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Config/Validator.php @@ -0,0 +1,589 @@ +getDbEntry('_validators', array()); + if ($GLOBALS['PMA_Config']->get('is_setup')) { + return $validators; + } + + // not in setup script: load additional validators for user + // preferences we need original config values not overwritten + // by user preferences, creating a new PhpMyAdmin\Config instance is a + // better idea than hacking into its code + $uvs = $cf->getDbEntry('_userValidators', array()); + foreach ($uvs as $field => $uv_list) { + $uv_list = (array)$uv_list; + foreach ($uv_list as &$uv) { + if (!is_array($uv)) { + continue; + } + for ($i = 1, $nb = count($uv); $i < $nb; $i++) { + if (mb_substr($uv[$i], 0, 6) == 'value:') { + $uv[$i] = Core::arrayRead( + mb_substr($uv[$i], 6), + $GLOBALS['PMA_Config']->base_settings + ); + } + } + } + $validators[$field] = isset($validators[$field]) + ? array_merge((array)$validators[$field], $uv_list) + : $uv_list; + } + return $validators; + } + + /** + * Runs validation $validator_id on values $values and returns error list. + * + * Return values: + * o array, keys - field path or formset id, values - array of errors + * when $isPostSource is true values is an empty array to allow for error list + * cleanup in HTML document + * o false - when no validators match name(s) given by $validator_id + * + * @param ConfigFile $cf Config file instance + * @param string|array $validator_id ID of validator(s) to run + * @param array &$values Values to validate + * @param bool $isPostSource tells whether $values are directly from + * POST request + * + * @return bool|array + */ + public static function validate( + ConfigFile $cf, $validator_id, array &$values, $isPostSource + ) { + // find validators + $validator_id = (array) $validator_id; + $validators = static::getValidators($cf); + $vids = array(); + foreach ($validator_id as &$vid) { + $vid = $cf->getCanonicalPath($vid); + if (isset($validators[$vid])) { + $vids[] = $vid; + } + } + if (empty($vids)) { + return false; + } + + // create argument list with canonical paths and remember path mapping + $arguments = array(); + $key_map = array(); + foreach ($values as $k => $v) { + $k2 = $isPostSource ? str_replace('-', '/', $k) : $k; + $k2 = mb_strpos($k2, '/') + ? $cf->getCanonicalPath($k2) + : $k2; + $key_map[$k2] = $k; + $arguments[$k2] = $v; + } + + // validate + $result = array(); + foreach ($vids as $vid) { + // call appropriate validation functions + foreach ((array)$validators[$vid] as $validator) { + $vdef = (array) $validator; + $vname = array_shift($vdef); + $vname = 'PhpMyAdmin\Config\Validator::' . $vname; + $args = array_merge(array($vid, &$arguments), $vdef); + $r = call_user_func_array($vname, $args); + + // merge results + if (!is_array($r)) { + continue; + } + + foreach ($r as $key => $error_list) { + // skip empty values if $isPostSource is false + if (! $isPostSource && empty($error_list)) { + continue; + } + if (! isset($result[$key])) { + $result[$key] = array(); + } + $result[$key] = array_merge( + $result[$key], (array)$error_list + ); + } + } + } + + // restore original paths + $new_result = array(); + foreach ($result as $k => $v) { + $k2 = isset($key_map[$k]) ? $key_map[$k] : $k; + if (is_array($v)) { + $new_result[$k2] = array_map('htmlspecialchars', $v); + } else { + $new_result[$k2] = htmlspecialchars($v); + } + } + return empty($new_result) ? true : $new_result; + } + + /** + * Test database connection + * + * @param string $host host name + * @param string $port tcp port to use + * @param string $socket socket to use + * @param string $user username to use + * @param string $pass password to use + * @param string $error_key key to use in return array + * + * @return bool|array + */ + public static function testDBConnection( + $host, + $port, + $socket, + $user, + $pass = null, + $error_key = 'Server' + ) { + if ($GLOBALS['cfg']['DBG']['demo']) { + // Connection test disabled on the demo server! + return true; + } + + $error = null; + $host = Core::sanitizeMySQLHost($host); + + if (function_exists('error_clear_last')) { + /* PHP 7 only code */ + error_clear_last(); + } + + if (DatabaseInterface::checkDbExtension('mysqli')) { + $socket = empty($socket) ? null : $socket; + $port = empty($port) ? null : $port; + $extension = 'mysqli'; + } else { + $socket = empty($socket) ? null : ':' . ($socket[0] == '/' ? '' : '/') . $socket; + $port = empty($port) ? null : ':' . $port; + $extension = 'mysql'; + } + + if ($extension == 'mysql') { + $conn = @mysql_connect($host . $port . $socket, $user, $pass); + if (! $conn) { + $error = __('Could not connect to the database server!'); + } else { + mysql_close($conn); + } + } else { + $conn = @mysqli_connect($host, $user, $pass, null, $port, $socket); + if (! $conn) { + $error = __('Could not connect to the database server!'); + } else { + mysqli_close($conn); + } + } + if (! is_null($error)) { + $error .= ' - ' . error_get_last(); + } + return is_null($error) ? true : array($error_key => $error); + } + + /** + * Validate server config + * + * @param string $path path to config, not used + * keep this parameter since the method is invoked using + * reflection along with other similar methods + * @param array $values config values + * + * @return array + */ + public static function validateServer($path, array $values) + { + $result = array( + 'Server' => '', + 'Servers/1/user' => '', + 'Servers/1/SignonSession' => '', + 'Servers/1/SignonURL' => '' + ); + $error = false; + if (empty($values['Servers/1/auth_type'])) { + $values['Servers/1/auth_type'] = ''; + $result['Servers/1/auth_type'] = __('Invalid authentication type!'); + $error = true; + } + if ($values['Servers/1/auth_type'] == 'config' + && empty($values['Servers/1/user']) + ) { + $result['Servers/1/user'] = __( + 'Empty username while using [kbd]config[/kbd] authentication method!' + ); + $error = true; + } + if ($values['Servers/1/auth_type'] == 'signon' + && empty($values['Servers/1/SignonSession']) + ) { + $result['Servers/1/SignonSession'] = __( + 'Empty signon session name ' + . 'while using [kbd]signon[/kbd] authentication method!' + ); + $error = true; + } + if ($values['Servers/1/auth_type'] == 'signon' + && empty($values['Servers/1/SignonURL']) + ) { + $result['Servers/1/SignonURL'] = __( + 'Empty signon URL while using [kbd]signon[/kbd] authentication ' + . 'method!' + ); + $error = true; + } + + if (! $error && $values['Servers/1/auth_type'] == 'config') { + $password = ''; + if (! empty($values['Servers/1/password'])) { + $password = $values['Servers/1/password']; + } + $test = static::testDBConnection( + empty($values['Servers/1/host']) ? '' : $values['Servers/1/host'], + empty($values['Servers/1/port']) ? '' : $values['Servers/1/port'], + empty($values['Servers/1/socket']) ? '' : $values['Servers/1/socket'], + empty($values['Servers/1/user']) ? '' : $values['Servers/1/user'], + $password, + 'Server' + ); + + if ($test !== true) { + $result = array_merge($result, $test); + } + } + return $result; + } + + /** + * Validate pmadb config + * + * @param string $path path to config, not used + * keep this parameter since the method is invoked using + * reflection along with other similar methods + * @param array $values config values + * + * @return array + */ + public static function validatePMAStorage($path, array $values) + { + $result = array( + 'Server_pmadb' => '', + 'Servers/1/controluser' => '', + 'Servers/1/controlpass' => '' + ); + $error = false; + + if (empty($values['Servers/1/pmadb'])) { + return $result; + } + + $result = array(); + if (empty($values['Servers/1/controluser'])) { + $result['Servers/1/controluser'] = __( + 'Empty phpMyAdmin control user while using phpMyAdmin configuration ' + . 'storage!' + ); + $error = true; + } + if (empty($values['Servers/1/controlpass'])) { + $result['Servers/1/controlpass'] = __( + 'Empty phpMyAdmin control user password while using phpMyAdmin ' + . 'configuration storage!' + ); + $error = true; + } + if (! $error) { + $test = static::testDBConnection( + empty($values['Servers/1/host']) ? '' : $values['Servers/1/host'], + empty($values['Servers/1/port']) ? '' : $values['Servers/1/port'], + empty($values['Servers/1/socket']) ? '' : $values['Servers/1/socket'], + empty($values['Servers/1/controluser']) ? '' : $values['Servers/1/controluser'], + empty($values['Servers/1/controlpass']) ? '' : $values['Servers/1/controlpass'], + 'Server_pmadb' + ); + if ($test !== true) { + $result = array_merge($result, $test); + } + } + return $result; + } + + + /** + * Validates regular expression + * + * @param string $path path to config + * @param array $values config values + * + * @return array + */ + public static function validateRegex($path, array $values) + { + $result = array($path => ''); + + if (empty($values[$path])) { + return $result; + } + + if (function_exists('error_clear_last')) { + /* PHP 7 only code */ + error_clear_last(); + $last_error = null; + } else { + // As fallback we trigger another error to ensure + // that preg error will be different + @strpos(); + $last_error = error_get_last(); + } + + $matches = array(); + // in libraries/ListDatabase.php _checkHideDatabase(), + // a '/' is used as the delimiter for hide_db + @preg_match('/' . Util::requestString($values[$path]) . '/', '', $matches); + + $current_error = error_get_last(); + + if ($current_error !== $last_error) { + $error = preg_replace('/^preg_match\(\): /', '', $current_error['message']); + return array($path => $error); + } + + return $result; + } + + /** + * Validates TrustedProxies field + * + * @param string $path path to config + * @param array $values config values + * + * @return array + */ + public static function validateTrustedProxies($path, array $values) + { + $result = array($path => array()); + + if (empty($values[$path])) { + return $result; + } + + if (is_array($values[$path]) || is_object($values[$path])) { + // value already processed by FormDisplay::save + $lines = array(); + foreach ($values[$path] as $ip => $v) { + $v = Util::requestString($v); + $lines[] = preg_match('/^-\d+$/', $ip) + ? $v + : $ip . ': ' . $v; + } + } else { + // AJAX validation + $lines = explode("\n", $values[$path]); + } + foreach ($lines as $line) { + $line = trim($line); + $matches = array(); + // we catch anything that may (or may not) be an IP + if (!preg_match("/^(.+):(?:[ ]?)\\w+$/", $line, $matches)) { + $result[$path][] = __('Incorrect value:') . ' ' + . htmlspecialchars($line); + continue; + } + // now let's check whether we really have an IP address + if (filter_var($matches[1], FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) === false + && filter_var($matches[1], FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) === false + ) { + $ip = htmlspecialchars(trim($matches[1])); + $result[$path][] = sprintf(__('Incorrect IP address: %s'), $ip); + continue; + } + } + + return $result; + } + + /** + * Tests integer value + * + * @param string $path path to config + * @param array $values config values + * @param bool $allow_neg allow negative values + * @param bool $allow_zero allow zero + * @param int $max_value max allowed value + * @param string $error_string error message string + * + * @return string empty string if test is successful + */ + public static function validateNumber( + $path, + array $values, + $allow_neg, + $allow_zero, + $max_value, + $error_string + ) { + if (empty($values[$path])) { + return ''; + } + + $value = Util::requestString($values[$path]); + + if (intval($value) != $value + || (! $allow_neg && $value < 0) + || (! $allow_zero && $value == 0) + || $value > $max_value + ) { + return $error_string; + } + + return ''; + } + + /** + * Validates port number + * + * @param string $path path to config + * @param array $values config values + * + * @return array + */ + public static function validatePortNumber($path, array $values) + { + return array( + $path => static::validateNumber( + $path, + $values, + false, + false, + 65535, + __('Not a valid port number!') + ) + ); + } + + /** + * Validates positive number + * + * @param string $path path to config + * @param array $values config values + * + * @return array + */ + public static function validatePositiveNumber($path, array $values) + { + return array( + $path => static::validateNumber( + $path, + $values, + false, + false, + PHP_INT_MAX, + __('Not a positive number!') + ) + ); + } + + /** + * Validates non-negative number + * + * @param string $path path to config + * @param array $values config values + * + * @return array + */ + public static function validateNonNegativeNumber($path, array $values) + { + return array( + $path => static::validateNumber( + $path, + $values, + false, + true, + PHP_INT_MAX, + __('Not a non-negative number!') + ) + ); + } + + /** + * Validates value according to given regular expression + * Pattern and modifiers must be a valid for PCRE and JavaScript RegExp + * + * @param string $path path to config + * @param array $values config values + * @param string $regex regular expression to match + * + * @return array + */ + public static function validateByRegex($path, array $values, $regex) + { + if (!isset($values[$path])) { + return ''; + } + $result = preg_match($regex, Util::requestString($values[$path])); + return array($path => ($result ? '' : __('Incorrect value!'))); + } + + /** + * Validates upper bound for numeric inputs + * + * @param string $path path to config + * @param array $values config values + * @param int $max_value maximal allowed value + * + * @return array + */ + public static function validateUpperBound($path, array $values, $max_value) + { + $result = $values[$path] <= $max_value; + return array($path => ($result ? '' + : sprintf(__('Value must be less than or equal to %s!'), $max_value))); + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Console.php b/php/apps/phpmyadmin49/libraries/classes/Console.php new file mode 100644 index 00000000..565635b9 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Console.php @@ -0,0 +1,152 @@ +_isEnabled = true; + $this->relation = new Relation(); + } + + /** + * Set the ajax flag to indicate whether + * we are servicing an ajax request + * + * @param bool $isAjax Whether we are servicing an ajax request + * + * @return void + */ + public function setAjax($isAjax) + { + $this->_isAjax = (boolean) $isAjax; + } + + /** + * Disables the rendering of the footer + * + * @return void + */ + public function disable() + { + $this->_isEnabled = false; + } + + /** + * Renders the bookmark content + * + * @access public + * @return string + */ + public static function getBookmarkContent() + { + $cfgBookmark = Bookmark::getParams($GLOBALS['cfg']['Server']['user']); + if ($cfgBookmark) { + $bookmarks = Bookmark::getList( + $GLOBALS['dbi'], + $GLOBALS['cfg']['Server']['user'] + ); + $count_bookmarks = count($bookmarks); + if ($count_bookmarks > 0) { + $welcomeMessage = sprintf( + _ngettext( + 'Showing %1$d bookmark (both private and shared)', + 'Showing %1$d bookmarks (both private and shared)', + $count_bookmarks + ), + $count_bookmarks + ); + } else { + $welcomeMessage = __('No bookmarks'); + } + unset($count_bookmarks, $private_message, $shared_message); + return Template::get('console/bookmark_content') + ->render( + array( + 'welcome_message' => $welcomeMessage, + 'bookmarks' => $bookmarks, + ) + ); + } + return ''; + } + + /** + * Returns the list of JS scripts required by console + * + * @return array list of scripts + */ + public function getScripts() + { + return array('console.js'); + } + + /** + * Renders the console + * + * @access public + * @return string + */ + public function getDisplay() + { + if ((! $this->_isAjax) && $this->_isEnabled) { + $cfgBookmark = Bookmark::getParams( + $GLOBALS['cfg']['Server']['user'] + ); + + $image = Util::getImage('console', __('SQL Query Console')); + $_sql_history = $this->relation->getHistory( + $GLOBALS['cfg']['Server']['user'] + ); + $bookmarkContent = static::getBookmarkContent(); + + return Template::get('console/display')->render([ + 'cfg_bookmark' => $cfgBookmark, + 'image' => $image, + 'sql_history' => $_sql_history, + 'bookmark_content' => $bookmarkContent, + ]); + } + return ''; + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Controllers/Controller.php b/php/apps/phpmyadmin49/libraries/classes/Controllers/Controller.php new file mode 100644 index 00000000..24df284c --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Controllers/Controller.php @@ -0,0 +1,39 @@ +response = $response; + $this->dbi = $dbi; + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Controllers/Database/DatabaseStructureController.php b/php/apps/phpmyadmin49/libraries/classes/Controllers/Database/DatabaseStructureController.php new file mode 100644 index 00000000..81453754 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Controllers/Database/DatabaseStructureController.php @@ -0,0 +1,1115 @@ +relation = new Relation(); + } + + /** + * Retrieves databse information for further use + * + * @param string $sub_part Page part name + * + * @return void + */ + private function _getDbInfo($sub_part) + { + list( + $tables, + $num_tables, + $total_num_tables, + , + $is_show_stats, + $db_is_system_schema, + , + , + $pos + ) = Util::getDbInfo($this->db, $sub_part); + + $this->_tables = $tables; + $this->_num_tables = $num_tables; + $this->_pos = $pos; + $this->_db_is_system_schema = $db_is_system_schema; + $this->_total_num_tables = $total_num_tables; + $this->_is_show_stats = $is_show_stats; + } + + /** + * Index action + * + * @return void + */ + public function indexAction() + { + $response = Response::getInstance(); + + // Add/Remove favorite tables using Ajax request. + if ($response->isAjax() && !empty($_REQUEST['favorite_table'])) { + $this->addRemoveFavoriteTablesAction(); + return; + } + + // If there is an Ajax request for real row count of a table. + if ($response->isAjax() + && isset($_REQUEST['real_row_count']) + && $_REQUEST['real_row_count'] == true + ) { + $this->handleRealRowCountRequestAction(); + return; + } + + // Drops/deletes/etc. multiple tables if required + if ((! empty($_POST['submit_mult']) && isset($_POST['selected_tbl'])) + || isset($_POST['mult_btn']) + ) { + $this->multiSubmitAction(); + } + + $this->response->getHeader()->getScripts()->addFiles( + array( + 'db_structure.js', + 'tbl_change.js', + ) + ); + + // Gets the database structure + $this->_getDbInfo('_structure'); + + // Checks if there are any tables to be shown on current page. + // If there are no tables, the user is redirected to the last page + // having any. + if ($this->_total_num_tables > 0 && $this->_pos > $this->_total_num_tables) { + $uri = './db_structure.php' . Url::getCommonRaw(array( + 'db' => $this->db, + 'pos' => max(0, $this->_total_num_tables - $GLOBALS['cfg']['MaxTableList']), + 'reload' => 1 + )); + Core::sendHeaderLocation($uri); + } + + include_once 'libraries/replication.inc.php'; + + PageSettings::showGroup('DbStructure'); + + // 1. No tables + if ($this->_num_tables == 0) { + $this->response->addHTML( + Message::notice(__('No tables found in database.')) + ); + if (empty($this->_db_is_system_schema)) { + $this->response->addHTML(CreateTable::getHtml($this->db)); + } + return; + } + + // else + // 2. Shows table information + /** + * Displays the tables list + */ + $this->response->addHTML('
        '); + $_url_params = array( + 'pos' => $this->_pos, + 'db' => $this->db); + + // Add the sort options if they exists + if (isset($_REQUEST['sort'])) { + $_url_params['sort'] = $_REQUEST['sort']; + } + + if (isset($_REQUEST['sort_order'])) { + $_url_params['sort_order'] = $_REQUEST['sort_order']; + } + + $this->response->addHTML( + Util::getListNavigator( + $this->_total_num_tables, $this->_pos, $_url_params, + 'db_structure.php', 'frame_content', $GLOBALS['cfg']['MaxTableList'] + ) + ); + + $this->displayTableList(); + + // display again the table list navigator + $this->response->addHTML( + Util::getListNavigator( + $this->_total_num_tables, $this->_pos, $_url_params, + 'db_structure.php', 'frame_content', + $GLOBALS['cfg']['MaxTableList'] + ) + ); + + $this->response->addHTML('

        '); + + /** + * Work on the database + */ + /* DATABASE WORK */ + /* Printable view of a table */ + $this->response->addHTML( + Template::get('database/structure/print_view_data_dictionary_link') + ->render(array('url_query' => Url::getCommon( + array( + 'db' => $this->db, + 'goto' => 'db_structure.php', + ) + ))) + ); + + if (empty($this->_db_is_system_schema)) { + $this->response->addHTML(CreateTable::getHtml($this->db)); + } + } + + /** + * Add or remove favorite tables + * + * @return void + */ + public function addRemoveFavoriteTablesAction() + { + $fav_instance = RecentFavoriteTable::getInstance('favorite'); + if (isset($_REQUEST['favorite_tables'])) { + $favorite_tables = json_decode($_REQUEST['favorite_tables'], true); + } else { + $favorite_tables = array(); + } + // Required to keep each user's preferences separate. + $user = sha1($GLOBALS['cfg']['Server']['user']); + + // Request for Synchronization of favorite tables. + if (isset($_REQUEST['sync_favorite_tables'])) { + $cfgRelation = $this->relation->getRelationsParam(); + if ($cfgRelation['favoritework']) { + $this->synchronizeFavoriteTables($fav_instance, $user, $favorite_tables); + } + return; + } + $changes = true; + $titles = Util::buildActionTitles(); + $favorite_table = $_REQUEST['favorite_table']; + $already_favorite = $this->checkFavoriteTable($favorite_table); + + if (isset($_REQUEST['remove_favorite'])) { + if ($already_favorite) { + // If already in favorite list, remove it. + $fav_instance->remove($this->db, $favorite_table); + $already_favorite = false; // for favorite_anchor template + } + } elseif (isset($_REQUEST['add_favorite'])) { + if (!$already_favorite) { + $nbTables = count($fav_instance->getTables()); + if ($nbTables == $GLOBALS['cfg']['NumFavoriteTables']) { + $changes = false; + } else { + // Otherwise add to favorite list. + $fav_instance->add($this->db, $favorite_table); + $already_favorite = true; // for favorite_anchor template + } + } + } + + $favorite_tables[$user] = $fav_instance->getTables(); + $this->response->addJSON('changes', $changes); + if (!$changes) { + $this->response->addJSON( + 'message', + Template::get('components/error_message') + ->render( + array( + 'msg' => __("Favorite List is full!") + ) + ) + ); + return; + } + // Check if current table is already in favorite list. + $favParams = array('db' => $this->db, + 'ajax_request' => true, + 'favorite_table' => $favorite_table, + (($already_favorite ? 'remove' : 'add') . '_favorite') => true + ); + $this->response->addJSON( + array( + 'user' => $user, + 'favorite_tables' => json_encode($favorite_tables), + 'list' => $fav_instance->getHtmlList(), + 'anchor' => Template::get('database/structure/favorite_anchor') + ->render( + array( + 'table_name_hash' => md5($favorite_table), + 'db_table_name_hash' => md5($this->db . "." . $favorite_table), + 'fav_params' => $favParams, + 'already_favorite' => $already_favorite, + 'titles' => $titles, + ) + ) + ) + ); + } + + /** + * Handles request for real row count on database level view page. + * + * @return boolean true + */ + public function handleRealRowCountRequestAction() + { + $ajax_response = $this->response; + // If there is a request to update all table's row count. + if (!isset($_REQUEST['real_row_count_all'])) { + // Get the real row count for the table. + $real_row_count = $this->dbi + ->getTable($this->db, $_REQUEST['table']) + ->getRealRowCountTable(); + // Format the number. + $real_row_count = Util::formatNumber($real_row_count, 0); + $ajax_response->addJSON('real_row_count', $real_row_count); + return; + } + + // Array to store the results. + $real_row_count_all = array(); + // Iterate over each table and fetch real row count. + foreach ($this->_tables as $table) { + $row_count = $this->dbi + ->getTable($this->db, $table['TABLE_NAME']) + ->getRealRowCountTable(); + $real_row_count_all[] = array( + 'table' => $table['TABLE_NAME'], + 'row_count' => $row_count + ); + } + + $ajax_response->addJSON( + 'real_row_count_all', + json_encode($real_row_count_all) + ); + } + + /** + * Handles actions related to multiple tables + * + * @return void + */ + public function multiSubmitAction() + { + $action = 'db_structure.php'; + $err_url = 'db_structure.php' . Url::getCommon( + array('db' => $this->db) + ); + + // see bug #2794840; in this case, code path is: + // db_structure.php -> libraries/mult_submits.inc.php -> sql.php + // -> db_structure.php and if we got an error on the multi submit, + // we must display it here and not call again mult_submits.inc.php + if (! isset($_POST['error']) || false === $_POST['error']) { + include 'libraries/mult_submits.inc.php'; + } + if (empty($_POST['message'])) { + $_POST['message'] = Message::success(); + } + } + + /** + * Displays the list of tables + * + * @return void + */ + protected function displayTableList() + { + // filtering + $this->response->addHTML( + Template::get('filter')->render(['filter_value' => '']) + ); + + $i = $sum_entries = 0; + $overhead_check = false; + $create_time_all = ''; + $update_time_all = ''; + $check_time_all = ''; + $num_columns = $GLOBALS['cfg']['PropertiesNumColumns'] > 1 + ? ceil($this->_num_tables / $GLOBALS['cfg']['PropertiesNumColumns']) + 1 + : 0; + $row_count = 0; + $sum_size = 0; + $overhead_size = 0; + + $hidden_fields = array(); + $overall_approx_rows = false; + $structure_table_rows = []; + foreach ($this->_tables as $keyname => $current_table) { + // Get valid statistics whatever is the table type + + $drop_query = ''; + $drop_message = ''; + $overhead = ''; + $input_class = ['checkall']; + + $table_is_view = false; + // Sets parameters for links + $tbl_url_query = Url::getCommon( + array('db' => $this->db, 'table' => $current_table['TABLE_NAME']) + ); + // do not list the previous table's size info for a view + + list($current_table, $formatted_size, $unit, $formatted_overhead, + $overhead_unit, $overhead_size, $table_is_view, $sum_size) + = $this->getStuffForEngineTypeTable( + $current_table, $sum_size, $overhead_size + ); + + $curTable = $this->dbi + ->getTable($this->db, $current_table['TABLE_NAME']); + if (!$curTable->isMerge()) { + $sum_entries += $current_table['TABLE_ROWS']; + } + + if (isset($current_table['Collation'])) { + $collation = '' + . $current_table['Collation'] . ''; + } else { + $collation = '---'; + } + + if ($this->_is_show_stats) { + if ($formatted_overhead != '') { + $overhead = '' + . '' . $formatted_overhead . ' ' + . '' . $overhead_unit . '' + . '' . "\n"; + $overhead_check = true; + $input_class[] = 'tbl-overhead'; + } else { + $overhead = '-'; + } + } // end if + + if ($GLOBALS['cfg']['ShowDbStructureCharset']) { + if (isset($current_table['Collation'])) { + $charset = mb_substr($collation, 0, mb_strpos($collation, "_")); + } else { + $charset = ''; + } + } + + if ($GLOBALS['cfg']['ShowDbStructureCreation']) { + $create_time = isset($current_table['Create_time']) + ? $current_table['Create_time'] : ''; + if ($create_time + && (!$create_time_all + || $create_time < $create_time_all) + ) { + $create_time_all = $create_time; + } + } + + if ($GLOBALS['cfg']['ShowDbStructureLastUpdate']) { + $update_time = isset($current_table['Update_time']) + ? $current_table['Update_time'] : ''; + if ($update_time + && (!$update_time_all + || $update_time < $update_time_all) + ) { + $update_time_all = $update_time; + } + } + + if ($GLOBALS['cfg']['ShowDbStructureLastCheck']) { + $check_time = isset($current_table['Check_time']) + ? $current_table['Check_time'] : ''; + if ($check_time + && (!$check_time_all + || $check_time < $check_time_all) + ) { + $check_time_all = $check_time; + } + } + + $truename = (!empty($tooltip_truename) + && isset($tooltip_truename[$current_table['TABLE_NAME']])) + ? $tooltip_truename[$current_table['TABLE_NAME']] + : $current_table['TABLE_NAME']; + + $i++; + + $row_count++; + if ($table_is_view) { + $hidden_fields[] = ''; + } + + /* + * Always activate links for Browse, Search and Empty, even if + * the icons are greyed, because + * 1. for views, we don't know the number of rows at this point + * 2. for tables, another source could have populated them since the + * page was generated + * + * I could have used the PHP ternary conditional operator but I find + * the code easier to read without this operator. + */ + $may_have_rows = $current_table['TABLE_ROWS'] > 0 || $table_is_view; + $titles = Util::buildActionTitles(); + + $browse_table = Template::get('database/structure/browse_table') + ->render( + array( + 'tbl_url_query' => $tbl_url_query, + 'title' => $may_have_rows ? $titles['Browse'] + : $titles['NoBrowse'], + ) + ); + + $search_table = Template::get('database/structure/search_table') + ->render( + array( + 'tbl_url_query' => $tbl_url_query, + 'title' => $may_have_rows ? $titles['Search'] + : $titles['NoSearch'], + ) + ); + + $browse_table_label = Template::get( + 'database/structure/browse_table_label' + ) + ->render( + array( + 'tbl_url_query' => $tbl_url_query, + 'title' => htmlspecialchars( + $current_table['TABLE_COMMENT'] + ), + 'truename' => $truename, + ) + ); + + $empty_table = ''; + if (!$this->_db_is_system_schema) { + $empty_table = ''; + if (!$table_is_view) { + $empty_table = Template::get('database/structure/empty_table') + ->render( + array( + 'tbl_url_query' => $tbl_url_query, + 'sql_query' => urlencode( + 'TRUNCATE ' . Util::backquote( + $current_table['TABLE_NAME'] + ) + ), + 'message_to_show' => urlencode( + sprintf( + __('Table %s has been emptied.'), + htmlspecialchars( + $current_table['TABLE_NAME'] + ) + ) + ), + 'title' => $may_have_rows ? $titles['Empty'] + : $titles['NoEmpty'], + ) + ); + } + $drop_query = sprintf( + 'DROP %s %s', + ($table_is_view || $current_table['ENGINE'] == null) ? 'VIEW' + : 'TABLE', + Util::backquote( + $current_table['TABLE_NAME'] + ) + ); + $drop_message = sprintf( + (($table_is_view || $current_table['ENGINE'] == null) + ? __('View %s has been dropped.') + : __('Table %s has been dropped.')), + str_replace( + ' ', ' ', + htmlspecialchars($current_table['TABLE_NAME']) + ) + ); + } + + if ($num_columns > 0 + && $this->_num_tables > $num_columns + && ($row_count % $num_columns) == 0 + ) { + $row_count = 1; + + $this->response->addHTML( + Template::get('database/structure/table_header')->render([ + 'db' => $this->db, + 'db_is_system_schema' => $this->_db_is_system_schema, + 'replication' => $GLOBALS['replication_info']['slave']['status'], + 'properties_num_columns' => $GLOBALS['cfg']['PropertiesNumColumns'], + 'is_show_stats' => $GLOBALS['is_show_stats'], + 'show_charset' => $GLOBALS['cfg']['ShowDbStructureCharset'], + 'show_comment' => $GLOBALS['cfg']['ShowDbStructureComment'], + 'show_creation' => $GLOBALS['cfg']['ShowDbStructureCreation'], + 'show_last_update' => $GLOBALS['cfg']['ShowDbStructureLastUpdate'], + 'show_last_check' => $GLOBALS['cfg']['ShowDbStructureLastCheck'], + 'num_favorite_tables' => $GLOBALS['cfg']['NumFavoriteTables'], + 'structure_table_rows' => $structure_table_rows, + ]) + ); + $structure_table_rows = []; + } + + list($approx_rows, $show_superscript) = $this->isRowCountApproximated( + $current_table, $table_is_view + ); + + list($do, $ignored) = $this->getReplicationStatus($truename); + + $structure_table_rows[] = [ + 'db' => $this->db, + 'curr' => $i, + 'input_class' => implode(' ', $input_class), + 'table_is_view' => $table_is_view, + 'current_table' => $current_table, + 'browse_table_label' => $browse_table_label, + 'tracking_icon' => $this->getTrackingIcon($truename), + 'server_slave_status' => $GLOBALS['replication_info']['slave']['status'], + 'browse_table' => $browse_table, + 'tbl_url_query' => $tbl_url_query, + 'search_table' => $search_table, + 'db_is_system_schema' => $this->_db_is_system_schema, + 'titles' => $titles, + 'empty_table' => $empty_table, + 'drop_query' => $drop_query, + 'drop_message' => $drop_message, + 'collation' => $collation, + 'formatted_size' => $formatted_size, + 'unit' => $unit, + 'overhead' => $overhead, + 'create_time' => isset($create_time) + ? $create_time : '', + 'update_time' => isset($update_time) + ? $update_time : '', + 'check_time' => isset($check_time) + ? $check_time : '', + 'charset' => isset($charset) + ? $charset : '', + 'is_show_stats' => $this->_is_show_stats, + 'ignored' => $ignored, + 'do' => $do, + 'approx_rows' => $approx_rows, + 'show_superscript' => $show_superscript, + 'already_favorite' => $this->checkFavoriteTable( + $current_table['TABLE_NAME'] + ), + 'num_favorite_tables' => $GLOBALS['cfg']['NumFavoriteTables'], + 'properties_num_columns' => $GLOBALS['cfg']['PropertiesNumColumns'], + 'limit_chars' => $GLOBALS['cfg']['LimitChars'], + 'show_charset' => $GLOBALS['cfg']['ShowDbStructureCharset'], + 'show_comment' => $GLOBALS['cfg']['ShowDbStructureComment'], + 'show_creation' => $GLOBALS['cfg']['ShowDbStructureCreation'], + 'show_last_update' => $GLOBALS['cfg']['ShowDbStructureLastUpdate'], + 'show_last_check' => $GLOBALS['cfg']['ShowDbStructureLastCheck'], + ]; + + $overall_approx_rows = $overall_approx_rows || $approx_rows; + } // end foreach + + $db_collation = $this->dbi->getDbCollation($this->db); + $db_charset = mb_substr($db_collation, 0, mb_strpos($db_collation, "_")); + + // table form + $this->response->addHTML( + Template::get('database/structure/table_header')->render([ + 'db' => $this->db, + 'db_is_system_schema' => $this->_db_is_system_schema, + 'replication' => $GLOBALS['replication_info']['slave']['status'], + 'properties_num_columns' => $GLOBALS['cfg']['PropertiesNumColumns'], + 'is_show_stats' => $GLOBALS['is_show_stats'], + 'show_charset' => $GLOBALS['cfg']['ShowDbStructureCharset'], + 'show_comment' => $GLOBALS['cfg']['ShowDbStructureComment'], + 'show_creation' => $GLOBALS['cfg']['ShowDbStructureCreation'], + 'show_last_update' => $GLOBALS['cfg']['ShowDbStructureLastUpdate'], + 'show_last_check' => $GLOBALS['cfg']['ShowDbStructureLastCheck'], + 'num_favorite_tables' => $GLOBALS['cfg']['NumFavoriteTables'], + 'structure_table_rows' => $structure_table_rows, + 'body_for_table_summary' => [ + 'num_tables' => $this->_num_tables, + 'server_slave_status' => $GLOBALS['replication_info']['slave']['status'], + 'db_is_system_schema' => $this->_db_is_system_schema, + 'sum_entries' => $sum_entries, + 'db_collation' => $db_collation, + 'is_show_stats' => $this->_is_show_stats, + 'db_charset' => $db_charset, + 'sum_size' => $sum_size, + 'overhead_size' => $overhead_size, + 'create_time_all' => $create_time_all, + 'update_time_all' => $update_time_all, + 'check_time_all' => $check_time_all, + 'approx_rows' => $overall_approx_rows, + 'num_favorite_tables' => $GLOBALS['cfg']['NumFavoriteTables'], + 'db' => $GLOBALS['db'], + 'properties_num_columns' => $GLOBALS['cfg']['PropertiesNumColumns'], + 'dbi' => $GLOBALS['dbi'], + 'show_charset' => $GLOBALS['cfg']['ShowDbStructureCharset'], + 'show_comment' => $GLOBALS['cfg']['ShowDbStructureComment'], + 'show_creation' => $GLOBALS['cfg']['ShowDbStructureCreation'], + 'show_last_update' => $GLOBALS['cfg']['ShowDbStructureLastUpdate'], + 'show_last_check' => $GLOBALS['cfg']['ShowDbStructureLastCheck'], + ], + 'check_all_tables' => [ + 'pma_theme_image' => $GLOBALS['pmaThemeImage'], + 'text_dir' => $GLOBALS['text_dir'], + 'overhead_check' => $overhead_check, + 'db_is_system_schema' => $this->_db_is_system_schema, + 'hidden_fields' => $hidden_fields, + 'disable_multi_table' => $GLOBALS['cfg']['DisableMultiTableMaintenance'], + 'central_columns_work' => $GLOBALS['cfgRelation']['centralcolumnswork'], + ], + ]) + ); + } + + /** + * Returns the tracking icon if the table is tracked + * + * @param string $table table name + * + * @return string HTML for tracking icon + */ + protected function getTrackingIcon($table) + { + $tracking_icon = ''; + if (Tracker::isActive()) { + $is_tracked = Tracker::isTracked($this->db, $table); + if ($is_tracked + || Tracker::getVersion($this->db, $table) > 0 + ) { + $tracking_icon = Template::get( + 'database/structure/tracking_icon' + ) + ->render( + array( + 'db' => $this->db, + 'table' => $table, + 'is_tracked' => $is_tracked, + ) + ); + } + } + return $tracking_icon; + } + + /** + * Returns whether the row count is approximated + * + * @param array $current_table array containing details about the table + * @param boolean $table_is_view whether the table is a view + * + * @return array + */ + protected function isRowCountApproximated(array $current_table, $table_is_view) + { + $approx_rows = false; + $show_superscript = ''; + + // there is a null value in the ENGINE + // - when the table needs to be repaired, or + // - when it's a view + // so ensure that we'll display "in use" below for a table + // that needs to be repaired + if (isset($current_table['TABLE_ROWS']) + && ($current_table['ENGINE'] != null || $table_is_view) + ) { + // InnoDB/TokuDB table: we did not get an accurate row count + $approx_rows = !$table_is_view + && in_array($current_table['ENGINE'], array('InnoDB', 'TokuDB')) + && !$current_table['COUNTED']; + + if ($table_is_view + && $current_table['TABLE_ROWS'] >= $GLOBALS['cfg']['MaxExactCountViews'] + ) { + $approx_rows = true; + $show_superscript = Util::showHint( + Sanitize::sanitize( + sprintf( + __( + 'This view has at least this number of ' + . 'rows. Please refer to %sdocumentation%s.' + ), + '[doc@cfg_MaxExactCountViews]', '[/doc]' + ) + ) + ); + } + } + + return array($approx_rows, $show_superscript); + } + + /** + * Returns the replication status of the table. + * + * @param string $table table name + * + * @return array + */ + protected function getReplicationStatus($table) + { + $do = $ignored = false; + if ($GLOBALS['replication_info']['slave']['status']) { + + $nbServSlaveDoDb = count( + $GLOBALS['replication_info']['slave']['Do_DB'] + ); + $nbServSlaveIgnoreDb = count( + $GLOBALS['replication_info']['slave']['Ignore_DB'] + ); + $searchDoDBInTruename = array_search( + $table, $GLOBALS['replication_info']['slave']['Do_DB'] + ); + $searchDoDBInDB = array_search( + $this->db, $GLOBALS['replication_info']['slave']['Do_DB'] + ); + + $do = strlen($searchDoDBInTruename) > 0 + || strlen($searchDoDBInDB) > 0 + || ($nbServSlaveDoDb == 0 && $nbServSlaveIgnoreDb == 0) + || $this->hasTable( + $GLOBALS['replication_info']['slave']['Wild_Do_Table'], + $table + ); + + $searchDb = array_search( + $this->db, + $GLOBALS['replication_info']['slave']['Ignore_DB'] + ); + $searchTable = array_search( + $table, + $GLOBALS['replication_info']['slave']['Ignore_Table'] + ); + $ignored = strlen($searchTable) > 0 + || strlen($searchDb) > 0 + || $this->hasTable( + $GLOBALS['replication_info']['slave']['Wild_Ignore_Table'], + $table + ); + } + + return array($do, $ignored); + } + + /** + * Synchronize favorite tables + * + * + * @param RecentFavoriteTable $fav_instance Instance of this class + * @param string $user The user hash + * @param array $favorite_tables Existing favorites + * + * @return void + */ + protected function synchronizeFavoriteTables( + $fav_instance, + $user, + array $favorite_tables + ) { + $fav_instance_tables = $fav_instance->getTables(); + + if (empty($fav_instance_tables) + && isset($favorite_tables[$user]) + ) { + foreach ($favorite_tables[$user] as $key => $value) { + $fav_instance->add($value['db'], $value['table']); + } + } + $favorite_tables[$user] = $fav_instance->getTables(); + + $this->response->addJSON( + array( + 'favorite_tables' => json_encode($favorite_tables), + 'list' => $fav_instance->getHtmlList() + ) + ); + $server_id = $GLOBALS['server']; + // Set flag when localStorage and pmadb(if present) are in sync. + $_SESSION['tmpval']['favorites_synced'][$server_id] = true; + } + + /** + * Function to check if a table is already in favorite list. + * + * @param string $current_table current table + * + * @return true|false + */ + protected function checkFavoriteTable($current_table) + { + // ensure $_SESSION['tmpval']['favorite_tables'] is initialized + RecentFavoriteTable::getInstance('favorite'); + foreach ( + $_SESSION['tmpval']['favorite_tables'][$GLOBALS['server']] as $value + ) { + if ($value['db'] == $this->db && $value['table'] == $current_table) { + return true; + } + } + return false; + } + + /** + * Find table with truename + * + * @param array $db DB to look into + * @param string $truename Table name + * + * @return bool + */ + protected function hasTable(array $db, $truename) + { + foreach ($db as $db_table) { + if ($this->db == Replication::extractDbOrTable($db_table) + && preg_match( + "@^" . + preg_quote(mb_substr(Replication::extractDbOrTable($db_table, 'table'), 0, -1)) . "@", + $truename + ) + ) { + return true; + } + } + return false; + } + + /** + * Get the value set for ENGINE table, + * + * @param array $current_table current table + * @param integer $sum_size total table size + * @param integer $overhead_size overhead size + * + * @return array + * @internal param bool $table_is_view whether table is view or not + */ + protected function getStuffForEngineTypeTable( + array $current_table, $sum_size, $overhead_size + ) { + $formatted_size = '-'; + $unit = ''; + $formatted_overhead = ''; + $overhead_unit = ''; + $table_is_view = false; + + switch ( $current_table['ENGINE']) { + // MyISAM, ISAM or Heap table: Row count, data size and index size + // are accurate; data size is accurate for ARCHIVE + case 'MyISAM' : + case 'ISAM' : + case 'HEAP' : + case 'MEMORY' : + case 'ARCHIVE' : + case 'Aria' : + case 'Maria' : + list($current_table, $formatted_size, $unit, $formatted_overhead, + $overhead_unit, $overhead_size, $sum_size) + = $this->getValuesForAriaTable( + $current_table, $sum_size, $overhead_size, + $formatted_size, $unit, $formatted_overhead, $overhead_unit + ); + break; + case 'InnoDB' : + case 'PBMS' : + case 'TokuDB' : + // InnoDB table: Row count is not accurate but data and index sizes are. + // PBMS table in Drizzle: TABLE_ROWS is taken from table cache, + // so it may be unavailable + list($current_table, $formatted_size, $unit, $sum_size) + = $this->getValuesForInnodbTable( + $current_table, $sum_size + ); + break; + // Mysql 5.0.x (and lower) uses MRG_MyISAM + // and MySQL 5.1.x (and higher) uses MRG_MYISAM + // Both are aliases for MERGE + case 'MRG_MyISAM' : + case 'MRG_MYISAM' : + case 'MERGE' : + case 'BerkeleyDB' : + // Merge or BerkleyDB table: Only row count is accurate. + if ($this->_is_show_stats) { + $formatted_size = ' - '; + $unit = ''; + } + break; + // for a view, the ENGINE is sometimes reported as null, + // or on some servers it's reported as "SYSTEM VIEW" + case null : + case 'SYSTEM VIEW' : + // possibly a view, do nothing + break; + default : + // Unknown table type. + if ($this->_is_show_stats) { + $formatted_size = __('unknown'); + $unit = ''; + } + } // end switch + + if ($current_table['TABLE_TYPE'] == 'VIEW' + || $current_table['TABLE_TYPE'] == 'SYSTEM VIEW' + ) { + // countRecords() takes care of $cfg['MaxExactCountViews'] + $current_table['TABLE_ROWS'] = $this->dbi + ->getTable($this->db, $current_table['TABLE_NAME']) + ->countRecords(true); + $table_is_view = true; + } + + return array($current_table, $formatted_size, $unit, $formatted_overhead, + $overhead_unit, $overhead_size, $table_is_view, $sum_size + ); + } + + /** + * Get values for ARIA/MARIA tables + * + * @param array $current_table current table + * @param integer $sum_size sum size + * @param integer $overhead_size overhead size + * @param integer $formatted_size formatted size + * @param string $unit unit + * @param integer $formatted_overhead overhead formatted + * @param string $overhead_unit overhead unit + * + * @return array + */ + protected function getValuesForAriaTable( + array $current_table, $sum_size, $overhead_size, $formatted_size, $unit, + $formatted_overhead, $overhead_unit + ) { + if ($this->_db_is_system_schema) { + $current_table['Rows'] = $this->dbi + ->getTable($this->db, $current_table['Name']) + ->countRecords(); + } + + if ($this->_is_show_stats) { + $tblsize = $current_table['Data_length'] + + $current_table['Index_length']; + $sum_size += $tblsize; + list($formatted_size, $unit) = Util::formatByteDown( + $tblsize, 3, ($tblsize > 0) ? 1 : 0 + ); + if (isset($current_table['Data_free']) + && $current_table['Data_free'] > 0 + ) { + list($formatted_overhead, $overhead_unit) + = Util::formatByteDown( + $current_table['Data_free'], 3, + (($current_table['Data_free'] > 0) ? 1 : 0) + ); + $overhead_size += $current_table['Data_free']; + } + } + return array($current_table, $formatted_size, $unit, $formatted_overhead, + $overhead_unit, $overhead_size, $sum_size + ); + } + + /** + * Get values for InnoDB table + * + * @param array $current_table current table + * @param integer $sum_size sum size + * + * @return array + */ + protected function getValuesForInnodbTable( + array $current_table, $sum_size + ) { + $formatted_size = $unit = ''; + + if ((in_array($current_table['ENGINE'], array('InnoDB', 'TokuDB')) + && $current_table['TABLE_ROWS'] < $GLOBALS['cfg']['MaxExactCount']) + || !isset($current_table['TABLE_ROWS']) + ) { + $current_table['COUNTED'] = true; + $current_table['TABLE_ROWS'] = $this->dbi + ->getTable($this->db, $current_table['TABLE_NAME']) + ->countRecords(true); + } else { + $current_table['COUNTED'] = false; + } + + if ($this->_is_show_stats) { + $tblsize = $current_table['Data_length'] + + $current_table['Index_length']; + $sum_size += $tblsize; + list($formatted_size, $unit) = Util::formatByteDown( + $tblsize, 3, (($tblsize > 0) ? 1 : 0) + ); + } + + return array($current_table, $formatted_size, $unit, $sum_size); + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Controllers/DatabaseController.php b/php/apps/phpmyadmin49/libraries/classes/Controllers/DatabaseController.php new file mode 100644 index 00000000..af0ffc10 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Controllers/DatabaseController.php @@ -0,0 +1,30 @@ +db = $db; + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Controllers/Server/ServerBinlogController.php b/php/apps/phpmyadmin49/libraries/classes/Controllers/Server/ServerBinlogController.php new file mode 100644 index 00000000..57983ec3 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Controllers/Server/ServerBinlogController.php @@ -0,0 +1,263 @@ +binary_logs = $this->dbi->fetchResult( + 'SHOW MASTER LOGS', + 'Log_name', + null, + DatabaseInterface::CONNECT_USER, + DatabaseInterface::QUERY_STORE + ); + } + + /** + * Index action + * + * @return void + */ + public function indexAction() + { + /** + * Does the common work + */ + include_once 'libraries/server_common.inc.php'; + + $url_params = array(); + if (! isset($_POST['log']) + || ! array_key_exists($_POST['log'], $this->binary_logs) + ) { + $_POST['log'] = ''; + } else { + $url_params['log'] = $_POST['log']; + } + + if (!empty($_POST['dontlimitchars'])) { + $url_params['dontlimitchars'] = 1; + } + + $this->response->addHTML( + Template::get('server/sub_page_header')->render([ + 'type' => 'binlog', + ]) + ); + $this->response->addHTML($this->_getLogSelector($url_params)); + $this->response->addHTML($this->_getLogInfo($url_params)); + } + + /** + * Returns the html for log selector. + * + * @param array $url_params links parameters + * + * @return string + */ + private function _getLogSelector(array $url_params) + { + return Template::get('server/binlog/log_selector')->render( + array( + 'url_params' => $url_params, + 'binary_logs' => $this->binary_logs, + 'log' => $_POST['log'], + ) + ); + } + + /** + * Returns the html for binary log information. + * + * @param array $url_params links parameters + * + * @return string + */ + private function _getLogInfo(array $url_params) + { + /** + * Need to find the real end of rows? + */ + if (! isset($_POST['pos'])) { + $pos = 0; + } else { + /* We need this to be a integer */ + $pos = (int) $_POST['pos']; + } + + $sql_query = 'SHOW BINLOG EVENTS'; + if (! empty($_POST['log'])) { + $sql_query .= ' IN \'' . $_POST['log'] . '\''; + } + $sql_query .= ' LIMIT ' . $pos . ', ' . intval($GLOBALS['cfg']['MaxRows']); + + /** + * Sends the query + */ + $result = $this->dbi->query($sql_query); + + /** + * prepare some vars for displaying the result table + */ + // Gets the list of fields properties + if (isset($result) && $result) { + $num_rows = $this->dbi->numRows($result); + } else { + $num_rows = 0; + } + + if (empty($_POST['dontlimitchars'])) { + $dontlimitchars = false; + } else { + $dontlimitchars = true; + $url_params['dontlimitchars'] = 1; + } + + //html output + $html = Util::getMessage(Message::success(), $sql_query); + $html .= '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . ''; + + $html .= $this->_getAllLogItemInfo($result, $dontlimitchars); + + $html .= '' + . '
        '; + + $html .= $this->_getNavigationRow($url_params, $pos, $num_rows, $dontlimitchars); + + $html .= '
        ' . __('Log name') . '' . __('Position') . '' . __('Event type') . '' . __('Server ID') . '' . __('Original position') . '' . __('Information') . '
        '; + + return $html; + } + + /** + * Returns the html for Navigation Row. + * + * @param array $url_params Links parameters + * @param int $pos Position to display + * @param int $num_rows Number of results row + * @param bool $dontlimitchars Whether limit chars + * + * @return string + */ + private function _getNavigationRow(array $url_params, $pos, $num_rows, $dontlimitchars) + { + $html = ""; + // we do not know how much rows are in the binlog + // so we can just force 'NEXT' button + if ($pos > 0) { + $this_url_params = $url_params; + if ($pos > $GLOBALS['cfg']['MaxRows']) { + $this_url_params['pos'] = $pos - $GLOBALS['cfg']['MaxRows']; + } + + $html .= ''; + } else { + $html .= '>' . _pgettext('Previous page', 'Previous'); + } // end if... else... + $html .= ' < - '; + } + + $this_url_params = $url_params; + if ($pos > 0) { + $this_url_params['pos'] = $pos; + } + if ($dontlimitchars) { + unset($this_url_params['dontlimitchars']); + $tempTitle = __('Truncate Shown Queries'); + $tempImgMode = 'partial'; + } else { + $this_url_params['dontlimitchars'] = 1; + $tempTitle = __('Show Full Queries'); + $tempImgMode = 'full'; + } + $html .= '' + . '' . $tempTitle . ''; + + // we do not now how much rows are in the binlog + // so we can just force 'NEXT' button + if ($num_rows >= $GLOBALS['cfg']['MaxRows']) { + $this_url_params = $url_params; + $this_url_params['pos'] = $pos + $GLOBALS['cfg']['MaxRows']; + $html .= ' - '; + } else { + $html .= '>' . _pgettext('Next page', 'Next'); + } // end if... else... + $html .= ' > '; + } + + return $html; + } + + /** + * Returns the html for all binary log items. + * + * @param resource $result MySQL Query result + * @param bool $dontlimitchars Whether limit chars + * + * @return string + */ + private function _getAllLogItemInfo($result, $dontlimitchars) + { + $html = ""; + while ($value = $this->dbi->fetchAssoc($result)) { + $html .= Template::get('server/binlog/log_row')->render( + array( + 'value' => $value, + 'dontlimitchars' => $dontlimitchars, + ) + ); + } + return $html; + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Controllers/Server/ServerCollationsController.php b/php/apps/phpmyadmin49/libraries/classes/Controllers/Server/ServerCollationsController.php new file mode 100644 index 00000000..cead8ab1 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Controllers/Server/ServerCollationsController.php @@ -0,0 +1,76 @@ +response->addHTML( + Template::get('server/sub_page_header')->render([ + 'type' => 'collations', + ]) + ); + $this->response->addHTML( + $this->_getHtmlForCharsets( + Charsets::getMySQLCharsets($dbi, $disableIs), + Charsets::getMySQLCollations($dbi, $disableIs), + Charsets::getMySQLCharsetsDescriptions($dbi, $disableIs), + Charsets::getMySQLCollationsDefault($dbi, $disableIs) + ) + ); + } + + /** + * Returns the html for server Character Sets and Collations. + * + * @param array $mysqlCharsets Mysql Charsets list + * @param array $mysqlCollations Mysql Collations list + * @param array $mysqlCharsetsDesc Charsets descriptions + * @param array $mysqlDftCollations Default Collations list + * + * @return string + */ + function _getHtmlForCharsets(array $mysqlCharsets, array $mysqlCollations, + array $mysqlCharsetsDesc, array $mysqlDftCollations + ) { + return Template::get('server/collations/charsets')->render( + array( + 'mysql_charsets' => $mysqlCharsets, + 'mysql_collations' => $mysqlCollations, + 'mysql_charsets_desc' => $mysqlCharsetsDesc, + 'mysql_dft_collations' => $mysqlDftCollations, + ) + ); + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Controllers/Server/ServerDatabasesController.php b/php/apps/phpmyadmin49/libraries/classes/Controllers/Server/ServerDatabasesController.php new file mode 100644 index 00000000..d802d401 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Controllers/Server/ServerDatabasesController.php @@ -0,0 +1,470 @@ +isAjax() + && ($GLOBALS['dbi']->isSuperuser() || $GLOBALS['cfg']['AllowUserDropDatabase']) + ) { + $this->dropDatabasesAction(); + return; + } + + include_once 'libraries/replication.inc.php'; + + if (isset($_POST['new_db']) + && $response->isAjax() + ) { + $this->createDatabaseAction(); + return; + } + + include_once 'libraries/server_common.inc.php'; + + $header = $this->response->getHeader(); + $scripts = $header->getScripts(); + $scripts->addFile('server_databases.js'); + + $this->_setSortDetails(); + $this->_dbstats = ! empty($_POST['dbstats']); + $this->_pos = empty($_REQUEST['pos']) ? 0 : (int) $_REQUEST['pos']; + + /** + * Gets the databases list + */ + if ($GLOBALS['server'] > 0) { + $this->_databases = $this->dbi->getDatabasesFull( + null, $this->_dbstats, DatabaseInterface::CONNECT_USER, $this->_sort_by, + $this->_sort_order, $this->_pos, true + ); + $this->_database_count = count($GLOBALS['dblist']->databases); + } else { + $this->_database_count = 0; + } + + if ($this->_database_count > 0 && ! empty($this->_databases)) { + $databases = $this->_getHtmlForDatabases($replication_types); + } + + $this->response->addHTML(Template::get('server/databases/index')->render([ + 'show_create_db' => $GLOBALS['cfg']['ShowCreateDb'], + 'is_create_db_priv' => $GLOBALS['is_create_db_priv'], + 'dbstats' => $this->_dbstats, + 'db_to_create' => $GLOBALS['db_to_create'], + 'server_collation' => $GLOBALS['dbi']->getServerCollation(), + 'databases' => isset($databases) ? $databases : null, + 'dbi' => $GLOBALS['dbi'], + 'disable_is' => $GLOBALS['cfg']['Server']['DisableIS'], + ])); + } + + /** + * Handles creating a new database + * + * @return void + */ + public function createDatabaseAction() + { + // lower_case_table_names=1 `DB` becomes `db` + if ($GLOBALS['dbi']->getLowerCaseNames() === '1') { + $_POST['new_db'] = mb_strtolower( + $_POST['new_db'] + ); + } + /** + * Builds and executes the db creation sql query + */ + $sql_query = 'CREATE DATABASE ' . Util::backquote($_POST['new_db']); + if (! empty($_POST['db_collation'])) { + list($db_charset) = explode('_', $_POST['db_collation']); + $charsets = Charsets::getMySQLCharsets( + $GLOBALS['dbi'], + $GLOBALS['cfg']['Server']['DisableIS'] + ); + $collations = Charsets::getMySQLCollations( + $GLOBALS['dbi'], + $GLOBALS['cfg']['Server']['DisableIS'] + ); + if (in_array($db_charset, $charsets) + && in_array($_POST['db_collation'], $collations[$db_charset]) + ) { + $sql_query .= ' DEFAULT' + . Util::getCharsetQueryPart($_POST['db_collation']); + } + } + $sql_query .= ';'; + + $result = $GLOBALS['dbi']->tryQuery($sql_query); + + if (! $result) { + // avoid displaying the not-created db name in header or navi panel + $GLOBALS['db'] = ''; + + $message = Message::rawError($GLOBALS['dbi']->getError()); + $this->response->setRequestStatus(false); + $this->response->addJSON('message', $message); + } else { + $GLOBALS['db'] = $_POST['new_db']; + + $message = Message::success(__('Database %1$s has been created.')); + $message->addParam($_POST['new_db']); + $this->response->addJSON('message', $message); + $this->response->addJSON( + 'sql_query', Util::getMessage(null, $sql_query, 'success') + ); + + $this->response->addJSON( + 'url_query', + Util::getScriptNameForOption( + $GLOBALS['cfg']['DefaultTabDatabase'], 'database' + ) + . Url::getCommon(array('db' => $_POST['new_db'])) + ); + } + } + + /** + * Handles dropping multiple databases + * + * @return void + */ + public function dropDatabasesAction() + { + if (! isset($_POST['selected_dbs'])) { + $message = Message::error(__('No databases selected.')); + } else { + $action = 'server_databases.php'; + $err_url = $action . Url::getCommon(); + + $GLOBALS['submit_mult'] = 'drop_db'; + $GLOBALS['mult_btn'] = __('Yes'); + + include 'libraries/mult_submits.inc.php'; + + if (empty($message)) { // no error message + $number_of_databases = count($selected); + $message = Message::success( + _ngettext( + '%1$d database has been dropped successfully.', + '%1$d databases have been dropped successfully.', + $number_of_databases + ) + ); + $message->addParam($number_of_databases); + } + } + + if ($message instanceof Message) { + $this->response->setRequestStatus($message->isSuccess()); + $this->response->addJSON('message', $message); + } + } + + /** + * Extracts parameters $sort_order and $sort_by + * + * @return void + */ + private function _setSortDetails() + { + if (empty($_REQUEST['sort_by'])) { + $this->_sort_by = 'SCHEMA_NAME'; + } else { + $sort_by_whitelist = array( + 'SCHEMA_NAME', + 'DEFAULT_COLLATION_NAME', + 'SCHEMA_TABLES', + 'SCHEMA_TABLE_ROWS', + 'SCHEMA_DATA_LENGTH', + 'SCHEMA_INDEX_LENGTH', + 'SCHEMA_LENGTH', + 'SCHEMA_DATA_FREE' + ); + if (in_array($_REQUEST['sort_by'], $sort_by_whitelist)) { + $this->_sort_by = $_REQUEST['sort_by']; + } else { + $this->_sort_by = 'SCHEMA_NAME'; + } + } + + if (isset($_REQUEST['sort_order']) + && mb_strtolower($_REQUEST['sort_order']) == 'desc' + ) { + $this->_sort_order = 'desc'; + } else { + $this->_sort_order = 'asc'; + } + } + + /** + * Returns the html for Database List + * + * @param array $replication_types replication types + * + * @return string + */ + private function _getHtmlForDatabases(array $replication_types) + { + $first_database = reset($this->_databases); + // table col order + $column_order = $this->_getColumnOrder(); + + // calculate aggregate stats to display in footer + foreach ($this->_databases as $current) { + foreach ($column_order as $stat_name => $stat) { + if (array_key_exists($stat_name, $current) + && is_numeric($stat['footer']) + ) { + $column_order[$stat_name]['footer'] += $current[$stat_name]; + } + } + } + + $_url_params = array( + 'pos' => $this->_pos, + 'dbstats' => $this->_dbstats, + 'sort_by' => $this->_sort_by, + 'sort_order' => $this->_sort_order, + ); + + $html = Template::get('server/databases/databases_header')->render([ + 'database_count' => $this->_database_count, + 'pos' => $this->_pos, + 'url_params' => $_url_params, + 'max_db_list' => $GLOBALS['cfg']['MaxDbList'], + 'sort_by' => $this->_sort_by, + 'sort_order' => $this->_sort_order, + 'column_order' => $column_order, + 'first_database' => $first_database, + 'master_replication' => $GLOBALS['replication_info']['master']['status'], + 'slave_replication' => $GLOBALS['replication_info']['slave']['status'], + 'is_superuser' => $GLOBALS['dbi']->isSuperuser(), + 'allow_user_drop_database' => $GLOBALS['cfg']['AllowUserDropDatabase'], + ]); + + $html .= $this->_getHtmlForTableBody($column_order, $replication_types); + + $html .= Template::get('server/databases/databases_footer')->render([ + 'column_order' => $column_order, + 'first_database' => $first_database, + 'master_replication' => $GLOBALS['replication_info']['master']['status'], + 'slave_replication' => $GLOBALS['replication_info']['slave']['status'], + 'database_count' => $this->_database_count, + 'is_superuser' => $GLOBALS['dbi']->isSuperuser(), + 'allow_user_drop_database' => $GLOBALS['cfg']['AllowUserDropDatabase'], + 'pma_theme_image' => $GLOBALS['pmaThemeImage'], + 'text_dir' => $GLOBALS['text_dir'], + 'dbstats' => $this->_dbstats, + ]); + + return $html; + } + + /** + * Prepares the $column_order array + * + * @return array + */ + private function _getColumnOrder() + { + $column_order = array(); + $column_order['DEFAULT_COLLATION_NAME'] = array( + 'disp_name' => __('Collation'), + 'description_function' => array(Charsets::class, 'getCollationDescr'), + 'format' => 'string', + 'footer' => $this->dbi->getServerCollation(), + ); + $column_order['SCHEMA_TABLES'] = array( + 'disp_name' => __('Tables'), + 'format' => 'number', + 'footer' => 0, + ); + $column_order['SCHEMA_TABLE_ROWS'] = array( + 'disp_name' => __('Rows'), + 'format' => 'number', + 'footer' => 0, + ); + $column_order['SCHEMA_DATA_LENGTH'] = array( + 'disp_name' => __('Data'), + 'format' => 'byte', + 'footer' => 0, + ); + $column_order['SCHEMA_INDEX_LENGTH'] = array( + 'disp_name' => __('Indexes'), + 'format' => 'byte', + 'footer' => 0, + ); + $column_order['SCHEMA_LENGTH'] = array( + 'disp_name' => __('Total'), + 'format' => 'byte', + 'footer' => 0, + ); + $column_order['SCHEMA_DATA_FREE'] = array( + 'disp_name' => __('Overhead'), + 'format' => 'byte', + 'footer' => 0, + ); + + return $column_order; + } + + /** + * Returns the html for Database List + * + * @param array $column_order column order + * @param array $replication_types replication types + * + * @return string + */ + private function _getHtmlForTableBody(array $column_order, array $replication_types) + { + $html = '' . "\n"; + + foreach ($this->_databases as $current) { + $tr_class = ' db-row'; + if ($this->dbi->isSystemSchema($current['SCHEMA_NAME'], true)) { + $tr_class .= ' noclick'; + } + + $generated_html = $this->_buildHtmlForDb( + $current, + $column_order, + $replication_types, + $GLOBALS['replication_info'], + $tr_class + ); + $html .= $generated_html; + } // end foreach ($this->_databases as $key => $current) + $html .= ''; + + return $html; + } + + /** + * Builds the HTML for one database to display in the list + * of databases from server_databases.php + * + * @param array $current current database + * @param array $column_order column order + * @param array $replication_types replication types + * @param array $replication_info replication info + * @param string $tr_class HTMl class for the row + * + * @return array $column_order, $out + */ + function _buildHtmlForDb( + array $current, array $column_order, + array $replication_types, array $replication_info, $tr_class = '' + ) { + $master_replication = $slave_replication = ''; + foreach ($replication_types as $type) { + if ($replication_info[$type]['status']) { + $out = ''; + $key = array_search( + $current["SCHEMA_NAME"], + $replication_info[$type]['Ignore_DB'] + ); + if (strlen($key) > 0) { + $out = Util::getIcon( + 's_cancel', + __('Not replicated') + ); + } else { + $key = array_search( + $current["SCHEMA_NAME"], $replication_info[$type]['Do_DB'] + ); + + if (strlen($key) > 0 + || count($replication_info[$type]['Do_DB']) == 0 + ) { + // if ($key != null) did not work for index "0" + $out = Util::getIcon( + 's_success', + __('Replicated') + ); + } + } + + if ($type == 'master') { + $master_replication = $out; + } elseif ($type == 'slave') { + $slave_replication = $out; + } + } + } + + return Template::get('server/databases/table_row')->render([ + 'current' => $current, + 'tr_class' => $tr_class, + 'column_order' => $column_order, + 'master_replication_status' => $GLOBALS['replication_info']['master']['status'], + 'master_replication' => $master_replication, + 'slave_replication_status' => $GLOBALS['replication_info']['slave']['status'], + 'slave_replication' => $slave_replication, + 'is_superuser' => $GLOBALS['dbi']->isSuperuser(), + 'allow_user_drop_database' => $GLOBALS['cfg']['AllowUserDropDatabase'], + 'is_system_schema' => $GLOBALS['dbi']->isSystemSchema($current['SCHEMA_NAME'], true), + 'default_tab_database' => $GLOBALS['cfg']['DefaultTabDatabase'], + ]); + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Controllers/Server/ServerEnginesController.php b/php/apps/phpmyadmin49/libraries/classes/Controllers/Server/ServerEnginesController.php new file mode 100644 index 00000000..794060d7 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Controllers/Server/ServerEnginesController.php @@ -0,0 +1,100 @@ +response->addHTML( + Template::get('server/sub_page_header')->render([ + 'type' => 'engines', + ]) + ); + + /** + * Did the user request information about a certain storage engine? + */ + if (empty($_REQUEST['engine']) + || ! StorageEngine::isValid($_REQUEST['engine']) + ) { + $this->response->addHTML($this->_getHtmlForAllServerEngines()); + } else { + $engine = StorageEngine::getEngine($_REQUEST['engine']); + $this->response->addHTML($this->_getHtmlForServerEngine($engine)); + } + } + + /** + * Return HTML with all Storage Engine information + * + * @return string + */ + private function _getHtmlForAllServerEngines() + { + return Template::get('server/engines/engines')->render( + array('engines' => StorageEngine::getStorageEngines()) + ); + } + + /** + * Return HTML for a given Storage Engine + * + * @param StorageEngine $engine storage engine + * + * @return string + */ + private function _getHtmlForServerEngine(StorageEngine $engine) + { + $page = isset($_REQUEST['page']) ? $_REQUEST['page'] : ''; + $pageOutput = ! empty($page) ? $engine->getPage($page) : ''; + + /** + * Displays details about a given Storage Engine + */ + return Template::get('server/engines/engine')->render( + array( + 'title' => $engine->getTitle(), + 'help_page' => $engine->getMysqlHelpPage(), + 'comment' => $engine->getComment(), + 'info_pages' => $engine->getInfoPages(), + 'support' => $engine->getSupportInformationMessage(), + 'variables' => $engine->getHtmlVariables(), + 'page_output' => $pageOutput, + 'page' => $page, + 'engine' => $_REQUEST['engine'], + ) + ); + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Controllers/Server/ServerPluginsController.php b/php/apps/phpmyadmin49/libraries/classes/Controllers/Server/ServerPluginsController.php new file mode 100644 index 00000000..00941784 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Controllers/Server/ServerPluginsController.php @@ -0,0 +1,111 @@ +_setServerPlugins(); + } + + /** + * Index action + * + * @return void + */ + public function indexAction() + { + include 'libraries/server_common.inc.php'; + + $header = $this->response->getHeader(); + $scripts = $header->getScripts(); + $scripts->addFile('vendor/jquery/jquery.tablesorter.js'); + $scripts->addFile('server_plugins.js'); + + /** + * Displays the page + */ + $this->response->addHTML( + Template::get('server/sub_page_header')->render([ + 'type' => 'plugins', + ]) + ); + $this->response->addHTML($this->_getPluginsHtml()); + } + + /** + * Sets details about server plugins + * + * @return void + */ + private function _setServerPlugins() + { + $sql = "SELECT plugin_name, + plugin_type, + (plugin_status = 'ACTIVE') AS is_active, + plugin_type_version, + plugin_author, + plugin_description, + plugin_license + FROM information_schema.plugins + ORDER BY plugin_type, plugin_name"; + + $res = $this->dbi->query($sql); + $this->plugins = array(); + while ($row = $this->dbi->fetchAssoc($res)) { + $this->plugins[$row['plugin_type']][] = $row; + } + $this->dbi->freeResult($res); + ksort($this->plugins); + } + + /** + * Returns the html for plugin Tab. + * + * @return string + */ + private function _getPluginsHtml() + { + $html = '
        '; + $html .= Template::get('server/plugins/section_links') + ->render(array('plugins' => $this->plugins)); + + foreach ($this->plugins as $plugin_type => $plugin_list) { + $html .= Template::get('server/plugins/section') + ->render( + array( + 'plugin_type' => $plugin_type, + 'plugin_list' => $plugin_list, + ) + ); + } + $html .= '
        '; + return $html; + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Controllers/Server/ServerVariablesController.php b/php/apps/phpmyadmin49/libraries/classes/Controllers/Server/ServerVariablesController.php new file mode 100644 index 00000000..dca5e322 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Controllers/Server/ServerVariablesController.php @@ -0,0 +1,2837 @@ +variable_doc_links = $this->_getDocumentLinks(); + } + + /** + * Index action + * + * @return void + */ + public function indexAction() + { + $response = Response::getInstance(); + if ($response->isAjax() + && isset($_GET['type']) + && $_GET['type'] === 'getval' + ) { + $this->getValueAction(); + return; + } + + if ($response->isAjax() + && isset($_POST['type']) + && $_POST['type'] === 'setval' + ) { + $this->setValueAction(); + return; + } + + include 'libraries/server_common.inc.php'; + + $header = $this->response->getHeader(); + $scripts = $header->getScripts(); + $scripts->addFile('server_variables.js'); + + /** + * Displays the sub-page heading + */ + $this->response->addHTML( + Template::get('server/sub_page_header')->render([ + 'type' => 'variables', + 'link' => 'server_system_variables', + ]) + ); + + /** + * Sends the queries and buffers the results + */ + $serverVarsResult = $this->dbi->tryQuery('SHOW SESSION VARIABLES;'); + + if ($serverVarsResult !== false) { + + $serverVarsSession = array(); + while ($arr = $this->dbi->fetchRow($serverVarsResult)) { + $serverVarsSession[$arr[0]] = $arr[1]; + } + $this->dbi->freeResult($serverVarsResult); + + $serverVars = $this->dbi->fetchResult('SHOW GLOBAL VARIABLES;', 0, 1); + + /** + * Link templates + */ + $this->response->addHtml($this->_getHtmlForLinkTemplates()); + + /** + * Displays the page + */ + $this->response->addHtml( + $this->_getHtmlForServerVariables($serverVars, $serverVarsSession) + ); + } else { + /** + * Display the error message + */ + $this->response->addHTML( + Message::error( + sprintf( + __( + 'Not enough privilege to view server variables and ' + . 'settings. %s' + ), + Util::showMySQLDocu( + 'server-system-variables', + false, + 'sysvar_show_compatibility_56' + ) + ) + )->getDisplay() + ); + } + } + + /** + * Handle the AJAX request for a single variable value + * + * @return void + */ + public function getValueAction() + { + // Send with correct charset + header('Content-Type: text/html; charset=UTF-8'); + // Do not use double quotes inside the query to avoid a problem + // when server is running in ANSI_QUOTES sql_mode + $varValue = $this->dbi->fetchSingleRow( + 'SHOW GLOBAL VARIABLES WHERE Variable_name=\'' + . $GLOBALS['dbi']->escapeString($_GET['varName']) . '\';', + 'NUM' + ); + + if (isset($this->variable_doc_links[$_GET['varName']][3]) + && $this->variable_doc_links[$_GET['varName']][3] == 'byte' + ) { + $this->response->addJSON( + 'message', + implode( + ' ', Util::formatByteDown($varValue[1], 3, 3) + ) + ); + } else { + $this->response->addJSON( + 'message', + $varValue[1] + ); + } + } + + /** + * Handle the AJAX request for setting value for a single variable + * + * @return void + */ + public function setValueAction() + { + $value = $_POST['varValue']; + $matches = array(); + + if (isset($this->variable_doc_links[$_POST['varName']][3]) + && $this->variable_doc_links[$_POST['varName']][3] == 'byte' + && preg_match( + '/^\s*(\d+(\.\d+)?)\s*(mb|kb|mib|kib|gb|gib)\s*$/i', + $value, + $matches + ) + ) { + $exp = array( + 'kb' => 1, + 'kib' => 1, + 'mb' => 2, + 'mib' => 2, + 'gb' => 3, + 'gib' => 3 + ); + $value = floatval($matches[1]) * pow( + 1024, + $exp[mb_strtolower($matches[3])] + ); + } else { + $value = $GLOBALS['dbi']->escapeString($value); + } + + if (! is_numeric($value)) { + $value="'" . $value . "'"; + } + + if (! preg_match("/[^a-zA-Z0-9_]+/", $_POST['varName']) + && $this->dbi->query( + 'SET GLOBAL ' . $_POST['varName'] . ' = ' . $value + ) + ) { + // Some values are rounded down etc. + $varValue = $this->dbi->fetchSingleRow( + 'SHOW GLOBAL VARIABLES WHERE Variable_name="' + . $GLOBALS['dbi']->escapeString($_POST['varName']) + . '";', 'NUM' + ); + list($formattedValue, $isHtmlFormatted) = $this->_formatVariable( + $_POST['varName'], $varValue[1] + ); + + if ($isHtmlFormatted == false) { + $this->response->addJSON( + 'variable', + htmlspecialchars( + $formattedValue + ) + ); + } else { + $this->response->addJSON( + 'variable', + $formattedValue + ); + } + } else { + $this->response->setRequestStatus(false); + $this->response->addJSON( + 'error', + __('Setting variable failed') + ); + } + } + + /** + * Format Variable + * + * @param string $name variable name + * @param integer $value variable value + * + * @return array formatted string and bool if string is HTML formatted + */ + private function _formatVariable($name, $value) + { + $isHtmlFormatted = false; + $formattedValue = $value; + + if (is_numeric($value)) { + if (isset($this->variable_doc_links[$name][3]) + && $this->variable_doc_links[$name][3] == 'byte' + ) { + $isHtmlFormatted = true; + $formattedValue = '' + . htmlspecialchars( + implode(' ', Util::formatByteDown($value, 3, 3)) + ) + . ''; + } else { + $formattedValue = Util::formatNumber($value, 0); + } + } + + return array( + $formattedValue, + $isHtmlFormatted + ); + } + + /** + * Prints link templates + * + * @return string + */ + private function _getHtmlForLinkTemplates() + { + $url = 'server_variables.php' . Url::getCommon(); + return Template::get('server/variables/link_template') + ->render(array('url' => $url)); + } + + /** + * Prints Html for Server Variables + * + * @param array $serverVars global variables + * @param array $serverVarsSession session variables + * + * @return string + */ + private function _getHtmlForServerVariables(array $serverVars, array $serverVarsSession) + { + // filter + $filterValue = ! empty($_REQUEST['filter']) ? $_REQUEST['filter'] : ''; + $output = Template::get('filter') + ->render(array('filter_value' => $filterValue)); + + $output .= '
        '; + $output .= ''; + $output .= Template::get('server/variables/variable_table_head')->render(); + $output .= ''; + + $output .= $this->_getHtmlForServerVariablesItems( + $serverVars, $serverVarsSession + ); + + $output .= ''; + $output .= '
        '; + $output .= '
        '; + + return $output; + } + + + /** + * Prints Html for Server Variables Items + * + * @param array $serverVars global variables + * @param array $serverVarsSession session variables + * + * @return string + */ + private function _getHtmlForServerVariablesItems( + array $serverVars, array $serverVarsSession + ) { + // list of static (i.e. non-editable) system variables + $static_variables = $this->_getStaticSystemVariables(); + + $output = ''; + foreach ($serverVars as $name => $value) { + $has_session_value = isset($serverVarsSession[$name]) + && $serverVarsSession[$name] != $value; + $row_class = ($has_session_value ? ' diffSession' : ''); + $docLink = isset($this->variable_doc_links[$name]) + ? $this->variable_doc_links[$name] : null; + + list($formattedValue, $isHtmlFormatted) = $this->_formatVariable($name, $value); + + $output .= Template::get('server/variables/variable_row')->render(array( + 'row_class' => $row_class, + 'editable' => ! in_array( + strtolower($name), + $static_variables + ), + 'doc_link' => $docLink, + 'name' => $name, + 'value' => $formattedValue, + 'is_superuser' => $this->dbi->isSuperuser(), + 'is_html_formatted' => $isHtmlFormatted, + )); + + if ($has_session_value) { + list($formattedValue, $isHtmlFormatted)= $this->_formatVariable( + $name, $serverVarsSession[$name] + ); + $output .= Template::get('server/variables/session_variable_row') + ->render( + array( + 'row_class' => $row_class, + 'value' => $formattedValue, + 'is_html_formatted' => $isHtmlFormatted, + ) + ); + } + + } + + return $output; + } + + /** + * Returns Array of documentation links + * + * $variable_doc_links[string $name] = array( + * string $name, + * string $anchor, + * string $chapter, + * string $type, + * string $format); + * string $name: name of the system variable + * string $anchor: anchor to the documentation page + * string $chapter: chapter of "HTML, one page per chapter" documentation + * string $type: type of system variable + * string $format: if set to 'byte' it will format the variable + * with Util::formatByteDown() + * + * @return array + */ + private function _getDocumentLinks() + { + $variable_doc_links = array(); + $variable_doc_links['auto_increment_increment'] = array( + 'auto_increment_increment', + 'replication-options-master', + 'sysvar'); + $variable_doc_links['auto_increment_offset'] = array( + 'auto_increment_offset', + 'replication-options-master', + 'sysvar'); + $variable_doc_links['autocommit'] = array( + 'autocommit', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['automatic_sp_privileges'] = array( + 'automatic_sp_privileges', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['avoid_temporal_upgrade'] = array( + 'avoid_temporal_upgrade', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['back_log'] = array( + 'back_log', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['basedir'] = array( + 'basedir', + 'server-options', + 'option_mysqld'); + $variable_doc_links['big_tables'] = array( + 'big-tables', + 'server-options', + 'option_mysqld'); + $variable_doc_links['bind_address'] = array( + 'bind-address', + 'server-options', + 'option_mysqld'); + $variable_doc_links['binlog_cache_size'] = array( + 'binlog_cache_size', + 'replication-options-binary-log', + 'sysvar', + 'byte'); + $variable_doc_links['binlog_checksum'] = array( + 'binlog_checksum', + 'replication-options-binary-log', + 'sysvar'); + $variable_doc_links['binlog_direct_non_transactional_updates'] = array( + 'binlog_direct_non_transactional_updates', + 'replication-options-binary-log', + 'sysvar'); + $variable_doc_links['binlog_error_action'] = array( + 'binlog_error_action', + 'replication-options-binary-log', + 'sysvar'); + $variable_doc_links['binlog_format'] = array( + 'binlog-format', + 'server-options', + 'option_mysqld'); + $variable_doc_links['binlog_group_commit_sync_delay'] = array( + 'binlog_group_commit_sync_delay', + 'replication-options-binary-log', + 'sysvar'); + $variable_doc_links['binlog_group_commit_sync_no_delay_count'] = array( + 'binlog_group_commit_sync_no_delay_count', + 'replication-options-binary-log', + 'sysvar'); + $variable_doc_links['binlog_gtid_simple_recovery'] = array( + 'binlog_gtid_simple_recovery', + 'replication-options-gtids', + 'sysvar'); + $variable_doc_links['binlog_max_flush_queue_time'] = array( + 'binlog_max_flush_queue_time', + 'replication-options-binary-log', + 'sysvar'); + $variable_doc_links['binlog_order_commits'] = array( + 'binlog_order_commits', + 'replication-options-binary-log', + 'sysvar'); + $variable_doc_links['binlog_row_image'] = array( + 'binlog_row_image', + 'replication-options-binary-log', + 'sysvar'); + $variable_doc_links['binlog_rows_query_log_events'] = array( + 'binlog_rows_query_log_events', + 'replication-options-binary-log', + 'sysvar'); + $variable_doc_links['binlog_stmt_cache_size'] = array( + 'binlog_stmt_cache_size', + 'replication-options-binary-log', + 'sysvar', + 'byte'); + $variable_doc_links['block_encryption_mode'] = array( + 'block_encryption_mode', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['bulk_insert_buffer_size'] = array( + 'bulk_insert_buffer_size', + 'server-system-variables', + 'sysvar', + 'byte'); + $variable_doc_links['character_set_client'] = array( + 'character_set_client', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['character_set_connection'] = array( + 'character_set_connection', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['character_set_database'] = array( + 'character_set_database', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['character_set_filesystem'] = array( + 'character-set-filesystem', + 'server-options', + 'option_mysqld'); + $variable_doc_links['character_set_results'] = array( + 'character_set_results', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['character_set_server'] = array( + 'character-set-server', + 'server-options', + 'option_mysqld'); + $variable_doc_links['character_set_system'] = array( + 'character_set_system', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['character_sets_dir'] = array( + 'character-sets-dir', + 'server-options', + 'option_mysqld'); + $variable_doc_links['check_proxy_users'] = array( + 'check_proxy_users', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['collation_connection'] = array( + 'collation_connection', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['collation_database'] = array( + 'collation_database', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['collation_server'] = array( + 'collation-server', + 'server-options', + 'option_mysqld'); + $variable_doc_links['completion_type'] = array( + 'completion_type', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['concurrent_insert'] = array( + 'concurrent_insert', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['connect_timeout'] = array( + 'connect_timeout', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['core_file'] = array( + 'core_file', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['datadir'] = array( + 'datadir', + 'server-options', + 'option_mysqld'); + $variable_doc_links['date_format'] = array( + 'date_format', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['datetime_format'] = array( + 'datetime_format', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['debug'] = array( + 'debug', + 'server-options', + 'option_mysqld'); + $variable_doc_links['debug_sync'] = array( + 'debug_sync', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['default_authentication_plugin'] = array( + 'default_authentication_plugin', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['default_password_lifetime'] = array( + 'default_password_lifetime', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['default_storage_engine'] = array( + 'default-storage-engine', + 'server-options', + 'option_mysqld'); + $variable_doc_links['default_tmp_storage_engine'] = array( + 'default_tmp_storage_engine', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['default_week_format'] = array( + 'default_week_format', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['delay_key_write'] = array( + 'delay-key-write', + 'server-options', + 'option_mysqld'); + $variable_doc_links['delayed_insert_limit'] = array( + 'delayed_insert_limit', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['delayed_insert_timeout'] = array( + 'delayed_insert_timeout', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['delayed_queue_size'] = array( + 'delayed_queue_size', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['disabled_storage_engines'] = array( + 'disabled_storage_engines', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['disconnect_on_expired_password'] = array( + 'disconnect_on_expired_password', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['div_precision_increment'] = array( + 'div_precision_increment', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['end_markers_in_json'] = array( + 'end_markers_in_json', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['enforce_gtid_consistency'] = array( + 'enforce_gtid_consistency', + 'replication-options-gtids', + 'sysvar'); + $variable_doc_links['eq_range_index_dive_limit'] = array( + 'eq_range_index_dive_limit', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['engine_condition_pushdown'] = array( + 'engine-condition-pushdown', + 'server-options', + 'option_mysqld'); + $variable_doc_links['error_count'] = array( + 'error_count', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['event_scheduler'] = array( + 'event-scheduler', + 'server-options', + 'option_mysqld'); + $variable_doc_links['expire_logs_days'] = array( + 'expire_logs_days', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['explicit_defaults_for_timestamp'] = array( + 'explicit_defaults_for_timestamp', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['external_user'] = array( + 'external_user', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['flush'] = array( + 'flush', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['flush_time'] = array( + 'flush_time', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['foreign_key_checks'] = array( + 'foreign_key_checks', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['ft_boolean_syntax'] = array( + 'ft_boolean_syntax', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['ft_max_word_len'] = array( + 'ft_max_word_len', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['ft_min_word_len'] = array( + 'ft_min_word_len', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['ft_query_expansion_limit'] = array( + 'ft_query_expansion_limit', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['ft_stopword_file'] = array( + 'ft_stopword_file', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['general_log'] = array( + 'general-log', + 'server-options', + 'option_mysqld'); + $variable_doc_links['general_log_file'] = array( + 'general_log_file', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['group_concat_max_len'] = array( + 'group_concat_max_len', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['gtid_executed'] = array( + 'gtid_executed', + 'replication-options-gtids', + 'sysvar'); + $variable_doc_links['gtid_executed_compression_period'] = array( + 'gtid_executed_compression_period', + 'replication-options-gtids', + 'sysvar'); + $variable_doc_links['gtid_mode'] = array( + 'gtid_mode', + 'replication-options-gtids', + 'sysvar'); + $variable_doc_links['gtid_owned'] = array( + 'gtid_owned', + 'replication-options-gtids', + 'sysvar'); + $variable_doc_links['gtid_purged'] = array( + 'gtid_purged', + 'replication-options-gtids', + 'sysvar'); + $variable_doc_links['have_compress'] = array( + 'have_compress', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['have_crypt'] = array( + 'have_crypt', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['have_csv'] = array( + 'have_csv', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['have_dynamic_loading'] = array( + 'have_dynamic_loading', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['have_geometry'] = array( + 'have_geometry', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['have_innodb'] = array( + 'have_innodb', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['have_ndbcluster'] = array( + 'have_ndbcluster', + 'mysql-cluster-system-variables', + 'sysvar'); + $variable_doc_links['have_openssl'] = array( + 'have_openssl', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['have_partitioning'] = array( + 'have_partitioning', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['have_profiling'] = array( + 'have_profiling', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['have_query_cache'] = array( + 'have_query_cache', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['have_rtree_keys'] = array( + 'have_rtree_keys', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['have_ssl'] = array( + 'have_ssl', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['have_statement_timeout'] = array( + 'have_statement_timeout', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['have_symlink'] = array( + 'have_symlink', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['host_cache_size'] = array( + 'host_cache_size', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['hostname'] = array( + 'hostname', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['identity'] = array( + 'identity', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['ignore_builtin_innodb'] = array( + 'ignore-builtin-innodb', + 'innodb-parameters', + 'option_mysqld'); + $variable_doc_links['ignore_db_dirs'] = array( + 'ignore_db_dirs', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['init_connect'] = array( + 'init_connect', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['init_file'] = array( + 'init-file', + 'server-options', + 'option_mysqld'); + $variable_doc_links['init_slave'] = array( + 'init_slave', + 'replication-options-slave', + 'sysvar'); + $variable_doc_links['innodb_adaptive_flushing'] = array( + 'innodb_adaptive_flushing', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_adaptive_flushing_lwm'] = array( + 'innodb_adaptive_flushing_lwm', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_adaptive_hash_index'] = array( + 'innodb_adaptive_hash_index', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_adaptive_hash_index_parts'] = array( + 'innodb_adaptive_hash_index_parts', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_adaptive_max_sleep_delay'] = array( + 'innodb_adaptive_max_sleep_delay', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_additional_mem_pool_size'] = array( + 'innodb_additional_mem_pool_size', + 'innodb-parameters', + 'sysvar', + 'byte'); + $variable_doc_links['innodb_api_bk_commit_interval'] = array( + 'innodb_api_bk_commit_interval', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_api_disable_rowlock'] = array( + 'innodb_api_disable_rowlock', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_api_enable_binlog'] = array( + 'innodb_api_enable_binlog', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_api_enable_mdl'] = array( + 'innodb_api_enable_mdl', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_api_trx_level'] = array( + 'innodb_api_trx_level', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_autoextend_increment'] = array( + 'innodb_autoextend_increment', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_autoinc_lock_mode'] = array( + 'innodb_autoinc_lock_mode', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_buffer_pool_chunk_size'] = array( + 'innodb_buffer_pool_chunk_size', + 'innodb-parameters', + 'sysvar', + 'byte'); + $variable_doc_links['innodb_buffer_pool_dump_at_shutdown'] = array( + 'innodb_buffer_pool_dump_at_shutdown', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_buffer_pool_dump_now'] = array( + 'innodb_buffer_pool_dump_now', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_buffer_pool_dump_pct'] = array( + 'innodb_buffer_pool_dump_pct', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_buffer_pool_filename'] = array( + 'innodb_buffer_pool_filename', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_buffer_pool_instances'] = array( + 'innodb_buffer_pool_instances', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_buffer_pool_load_abort'] = array( + 'innodb_buffer_pool_load_abort', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_buffer_pool_load_at_startup'] = array( + 'innodb_buffer_pool_load_at_startup', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_buffer_pool_load_now'] = array( + 'innodb_buffer_pool_load_now', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_buffer_pool_size'] = array( + 'innodb_buffer_pool_size', + 'innodb-parameters', + 'sysvar', + 'byte'); + $variable_doc_links['innodb_change_buffer_max_size'] = array( + 'innodb_change_buffer_max_size', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_change_buffering'] = array( + 'innodb_change_buffering', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_checksum_algorithm'] = array( + 'innodb_checksum_algorithm', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_checksums'] = array( + 'innodb_checksums', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_cmp_per_index_enabled'] = array( + 'innodb_cmp_per_index_enabled', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_commit_concurrency'] = array( + 'innodb_commit_concurrency', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_compression_failure_threshold_pct'] = array( + 'innodb_compression_failure_threshold_pct', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_compression_level'] = array( + 'innodb_compression_level', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_compression_pad_pct_max'] = array( + 'innodb_compression_pad_pct_max', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_concurrency_tickets'] = array( + 'innodb_concurrency_tickets', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_data_file_path'] = array( + 'innodb_data_file_path', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_data_home_dir'] = array( + 'innodb_data_home_dir', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_disable_sort_file_cache'] = array( + 'innodb_disable_sort_file_cache', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_doublewrite'] = array( + 'innodb_doublewrite', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_fast_shutdown'] = array( + 'innodb_fast_shutdown', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_file_format'] = array( + 'innodb_file_format', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_file_format_check'] = array( + 'innodb_file_format_check', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_file_format_max'] = array( + 'innodb_file_format_max', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_file_per_table'] = array( + 'innodb_file_per_table', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_fill_factor'] = array( + 'innodb_fill_factor', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_flush_log_at_timeout'] = array( + 'innodb_flush_log_at_timeout', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_flush_log_at_trx_commit'] = array( + 'innodb_flush_log_at_trx_commit', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_flush_method'] = array( + 'innodb_flush_method', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_flush_neighbors'] = array( + 'innodb_flush_neighbors', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_flush_sync'] = array( + 'innodb_flush_sync', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_flushing_avg_loops'] = array( + 'innodb_flushing_avg_loops', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_force_load_corrupted'] = array( + 'innodb_force_load_corrupted', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_force_recovery'] = array( + 'innodb_force_recovery', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_ft_aux_table'] = array( + 'innodb_ft_aux_table', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_ft_cache_size'] = array( + 'innodb_ft_cache_size', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_ft_enable_diag_print'] = array( + 'innodb_ft_enable_diag_print', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_ft_enable_stopword'] = array( + 'innodb_ft_enable_stopword', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_ft_max_token_size'] = array( + 'innodb_ft_max_token_size', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_ft_min_token_size'] = array( + 'innodb_ft_min_token_size', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_ft_num_word_optimize'] = array( + 'innodb_ft_num_word_optimize', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_ft_result_cache_limit'] = array( + 'innodb_ft_result_cache_limit', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_ft_server_stopword_table'] = array( + 'innodb_ft_server_stopword_table', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_ft_sort_pll_degree'] = array( + 'innodb_ft_sort_pll_degree', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_ft_total_cache_size'] = array( + 'innodb_ft_total_cache_size', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_ft_user_stopword_table'] = array( + 'innodb_ft_user_stopword_table', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_io_capacity'] = array( + 'innodb_io_capacity', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_io_capacity_max'] = array( + 'innodb_io_capacity_max', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_large_prefix'] = array( + 'innodb_large_prefix', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_lock_wait_timeout'] = array( + 'innodb_lock_wait_timeout', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_locks_unsafe_for_binlog'] = array( + 'innodb_locks_unsafe_for_binlog', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_log_buffer_size'] = array( + 'innodb_log_buffer_size', + 'innodb-parameters', + 'sysvar', + 'byte'); + $variable_doc_links['innodb_log_checksum_algorithm'] = array( + 'innodb_log_checksum_algorithm', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_log_compressed_pages'] = array( + 'innodb_log_compressed_pages', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_log_file_size'] = array( + 'innodb_log_file_size', + 'innodb-parameters', + 'sysvar', + 'byte'); + $variable_doc_links['innodb_log_files_in_group'] = array( + 'innodb_log_files_in_group', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_log_group_home_dir'] = array( + 'innodb_log_group_home_dir', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_log_write_ahead_size'] = array( + 'innodb_log_write_ahead_size', + 'innodb-parameters', + 'sysvar', + 'byte'); + $variable_doc_links['innodb_lru_scan_depth'] = array( + 'innodb_lru_scan_depth', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_max_dirty_pages_pct'] = array( + 'innodb_max_dirty_pages_pct', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_max_dirty_pages_pct_lwm'] = array( + 'innodb_max_dirty_pages_pct_lwm', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_max_purge_lag'] = array( + 'innodb_max_purge_lag', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_max_purge_lag_delay'] = array( + 'innodb_max_purge_lag_delay', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_max_undo_log_size'] = array( + 'innodb_max_undo_log_size', + 'innodb-parameters', + 'sysvar', + 'byte'); + $variable_doc_links['innodb_mirrored_log_groups'] = array( + 'innodb_mirrored_log_groups', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_monitor_disable'] = array( + 'innodb_monitor_disable', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_monitor_enable'] = array( + 'innodb_monitor_enable', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_monitor_reset'] = array( + 'innodb_monitor_reset', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_monitor_reset_all'] = array( + 'innodb_monitor_reset_all', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_old_blocks_pct'] = array( + 'innodb_old_blocks_pct', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_old_blocks_time'] = array( + 'innodb_old_blocks_time', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_online_alter_log_max_size'] = array( + 'innodb_online_alter_log_max_size', + 'innodb-parameters', + 'sysvar', + 'byte'); + $variable_doc_links['innodb_open_files'] = array( + 'innodb_open_files', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_optimize_fulltext_only'] = array( + 'innodb_optimize_fulltext_only', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_page_cleaners'] = array( + 'innodb_page_cleaners', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_page_size'] = array( + 'innodb_page_size', + 'innodb-parameters', + 'sysvar', + 'byte'); + $variable_doc_links['innodb_print_all_deadlocks'] = array( + 'innodb_print_all_deadlocks', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_purge_batch_size'] = array( + 'innodb_purge_batch_size', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_purge_rseg_truncate_frequency'] = array( + 'innodb_purge_rseg_truncate_frequency', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_purge_threads'] = array( + 'innodb_purge_threads', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_random_read_ahead'] = array( + 'innodb_random_read_ahead', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_read_ahead_threshold'] = array( + 'innodb_read_ahead_threshold', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_read_io_threads'] = array( + 'innodb_read_io_threads', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_read_only'] = array( + 'innodb_read_only', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_replication_delay'] = array( + 'innodb_replication_delay', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_rollback_on_timeout'] = array( + 'innodb_rollback_on_timeout', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_rollback_segments'] = array( + 'innodb_rollback_segments', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_sort_buffer_size'] = array( + 'innodb_sort_buffer_size', + 'innodb-parameters', + 'sysvar', + 'byte'); + $variable_doc_links['innodb_spin_wait_delay'] = array( + 'innodb_spin_wait_delay', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_stats_auto_recalc'] = array( + 'innodb_stats_auto_recalc', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_stats_method'] = array( + 'innodb_stats_method', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_stats_on_metadata'] = array( + 'innodb_stats_on_metadata', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_stats_persistent'] = array( + 'innodb_stats_persistent', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_stats_persistent_sample_pages'] = array( + 'innodb_stats_persistent_sample_pages', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_stats_sample_pages'] = array( + 'innodb_stats_sample_pages', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_stats_transient_sample_pages'] = array( + 'innodb_stats_transient_sample_pages', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_status_output'] = array( + 'innodb_status_output', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_status_output_locks'] = array( + 'innodb_status_output_locks', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_strict_mode'] = array( + 'innodb_strict_mode', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_support_xa'] = array( + 'innodb_support_xa', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_sync_array_size'] = array( + 'innodb_sync_array_size', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_sync_spin_loops'] = array( + 'innodb_sync_spin_loops', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_table_locks'] = array( + 'innodb_table_locks', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_temp_data_file_path'] = array( + 'innodb_temp_data_file_path', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_thread_concurrency'] = array( + 'innodb_thread_concurrency', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_thread_sleep_delay'] = array( + 'innodb_thread_sleep_delay', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_undo_directory'] = array( + 'innodb_undo_directory', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_undo_log_truncate'] = array( + 'innodb_undo_log_truncate', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_undo_logs'] = array( + 'innodb_undo_logs', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_undo_tablespaces'] = array( + 'innodb_undo_tablespaces', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_use_native_aio'] = array( + 'innodb_use_native_aio', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_use_sys_malloc'] = array( + 'innodb_use_sys_malloc', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_version'] = array( + 'innodb_version', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['innodb_write_io_threads'] = array( + 'innodb_write_io_threads', + 'innodb-parameters', + 'sysvar'); + $variable_doc_links['insert_id'] = array( + 'insert_id', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['interactive_timeout'] = array( + 'interactive_timeout', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['internal_tmp_disk_storage_engine'] = array( + 'internal_tmp_disk_storage_engine', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['join_buffer_size'] = array( + 'join_buffer_size', + 'server-system-variables', + 'sysvar', + 'byte'); + $variable_doc_links['keep_files_on_create'] = array( + 'keep_files_on_create', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['key_buffer_size'] = array( + 'key_buffer_size', + 'server-system-variables', + 'sysvar', + 'byte'); + $variable_doc_links['key_cache_age_threshold'] = array( + 'key_cache_age_threshold', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['key_cache_block_size'] = array( + 'key_cache_block_size', + 'server-system-variables', + 'sysvar', + 'byte'); + $variable_doc_links['key_cache_division_limit'] = array( + 'key_cache_division_limit', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['language'] = array( + 'language', + 'server-options', + 'option_mysqld'); + $variable_doc_links['large_files_support'] = array( + 'large_files_support', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['large_page_size'] = array( + 'large_page_size', + 'server-system-variables', + 'sysvar', + 'byte'); + $variable_doc_links['large_pages'] = array( + 'large-pages', + 'server-options', + 'option_mysqld'); + $variable_doc_links['last_insert_id'] = array( + 'last_insert_id', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['lc_messages'] = array( + 'lc-messages', + 'server-options', + 'option_mysqld'); + $variable_doc_links['lc_messages_dir'] = array( + 'lc-messages-dir', + 'server-options', + 'option_mysqld'); + $variable_doc_links['lc_time_names'] = array( + 'lc_time_names', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['license'] = array( + 'license', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['local_infile'] = array( + 'local_infile', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['lock_wait_timeout'] = array( + 'lock_wait_timeout', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['locked_in_memory'] = array( + 'locked_in_memory', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['log_backward_compatible_user_definitions'] = array( + 'log_backward_compatible_user_definitions', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['log'] = array( + 'log', + 'server-options', + 'option_mysqld'); + $variable_doc_links['log_bin'] = array( + 'log_bin', + 'replication-options-binary-log', + 'sysvar'); + $variable_doc_links['log-bin'] = array( + 'log-bin', + 'replication-options-binary-log', + 'option_mysqld'); + $variable_doc_links['log_bin_basename'] = array( + 'log_bin_basename', + 'replication-options-binary-log', + 'sysvar'); + $variable_doc_links['log_bin_index'] = array( + 'log_bin_index', + 'replication-options-binary-log', + 'sysvar'); + $variable_doc_links['log_bin_trust_function_creators'] = array( + 'log-bin-trust-function-creators', + 'replication-options-binary-log', + 'option_mysqld'); + $variable_doc_links['log_bin_use_v1_row_events'] = array( + 'log_bin_use_v1_row_events', + 'replication-options-binary-log', + 'sysvar'); + $variable_doc_links['log_error'] = array( + 'log-error', + 'server-options', + 'option_mysqld'); + $variable_doc_links['log_error_verbosity'] = array( + 'log_error_verbosity', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['log_output'] = array( + 'log-output', + 'server-options', + 'option_mysqld'); + $variable_doc_links['log_queries_not_using_indexes'] = array( + 'log-queries-not-using-indexes', + 'server-options', + 'option_mysqld'); + $variable_doc_links['log_slave_updates'] = array( + 'log-slave-updates', + 'replication-options-slave', + 'option_mysqld'); + $variable_doc_links['log_slow_admin_statements'] = array( + 'log_slow_admin_statements', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['log_slow_slave_statements'] = array( + 'log_slow_slave_statements', + 'replication-options-slave', + 'sysvar'); + $variable_doc_links['log_syslog'] = array( + 'log_syslog', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['log_syslog_facility'] = array( + 'log_syslog_facility', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['log_syslog_include_pid'] = array( + 'log_syslog_include_pid', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['log_syslog_tag'] = array( + 'log_syslog_tag', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['log_throttle_queries_not_using_indexes'] = array( + 'log_throttle_queries_not_using_indexes', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['log_timestamps'] = array( + 'log_timestamps', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['log_slow_queries'] = array( + 'log-slow-queries', + 'server-options', + 'option_mysqld'); + $variable_doc_links['log_warnings'] = array( + 'log-warnings', + 'server-options', + 'option_mysqld'); + $variable_doc_links['long_query_time'] = array( + 'long_query_time', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['low_priority_updates'] = array( + 'low-priority-updates', + 'server-options', + 'option_mysqld'); + $variable_doc_links['lower_case_file_system'] = array( + 'lower_case_file_system', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['lower_case_table_names'] = array( + 'lower_case_table_names', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['master_info_repository'] = array( + 'master_info_repository', + 'replication-options-slave', + 'sysvar'); + $variable_doc_links['master_verify_checksum'] = array( + 'master_verify_checksum', + 'replication-options-binary-log', + 'sysvar'); + $variable_doc_links['master-bind'] = array( + '', + 'replication-options', + 0); + $variable_doc_links['max_allowed_packet'] = array( + 'max_allowed_packet', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['max_binlog_cache_size'] = array( + 'max_binlog_cache_size', + 'replication-options-binary-log', + 'sysvar', + 'byte'); + $variable_doc_links['max_binlog_size'] = array( + 'max_binlog_size', + 'replication-options-binary-log', + 'sysvar', + 'byte'); + $variable_doc_links['max_binlog_stmt_cache_size'] = array( + 'max_binlog_stmt_cache_size', + 'replication-options-binary-log', + 'sysvar', + 'byte'); + $variable_doc_links['max_connect_errors'] = array( + 'max_connect_errors', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['max_connections'] = array( + 'max_connections', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['max_delayed_threads'] = array( + 'max_delayed_threads', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['max_digest_length'] = array( + 'max_digest_length', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['max_error_count'] = array( + 'max_error_count', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['max_execution_time'] = array( + 'max_execution_time', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['max_heap_table_size'] = array( + 'max_heap_table_size', + 'server-system-variables', + 'sysvar', + 'byte'); + $variable_doc_links['max_insert_delayed_threads'] = array( + 'max_insert_delayed_threads', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['max_join_size'] = array( + 'max_join_size', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['max_length_for_sort_data'] = array( + 'max_length_for_sort_data', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['max_points_in_geometry'] = array( + 'max_points_in_geometry', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['max_long_data_size'] = array( + 'max_long_data_size', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['max_prepared_stmt_count'] = array( + 'max_prepared_stmt_count', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['max_relay_log_size'] = array( + 'max_relay_log_size', + 'server-system-variables', + 'sysvar', + 'byte'); + $variable_doc_links['max_seeks_for_key'] = array( + 'max_seeks_for_key', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['max_sort_length'] = array( + 'max_sort_length', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['max_sp_recursion_depth'] = array( + 'max_sp_recursion_depth', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['max_tmp_tables'] = array( + 'max_tmp_tables', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['max_user_connections'] = array( + 'max_user_connections', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['max_write_lock_count'] = array( + 'max_write_lock_count', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['metadata_locks_cache_size'] = array( + 'metadata_locks_cache_size', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['metadata_locks_hash_instances'] = array( + 'metadata_locks_hash_instances', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['memlock'] = array( + 'memlock', + 'server-options', + 'option_mysqld'); + $variable_doc_links['min_examined_row_limit'] = array( + 'min-examined-row-limit', + 'server-options', + 'option_mysqld'); + $variable_doc_links['multi_range_count'] = array( + 'multi_range_count', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['myisam_data_pointer_size'] = array( + 'myisam_data_pointer_size', + 'server-system-variables', + 'sysvar', + 'byte'); + $variable_doc_links['myisam_max_sort_file_size'] = array( + 'myisam_max_sort_file_size', + 'server-system-variables', + 'sysvar', + 'byte'); + $variable_doc_links['myisam_mmap_size'] = array( + 'myisam_mmap_size', + 'server-system-variables', + 'sysvar', + 'byte'); + $variable_doc_links['myisam_recover_options'] = array( + 'myisam_recover_options', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['myisam_repair_threads'] = array( + 'myisam_repair_threads', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['myisam_sort_buffer_size'] = array( + 'myisam_sort_buffer_size', + 'server-system-variables', + 'sysvar', + 'byte'); + $variable_doc_links['myisam_stats_method'] = array( + 'myisam_stats_method', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['myisam_use_mmap'] = array( + 'myisam_use_mmap', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['mysql_native_password_proxy_users'] = array( + 'mysql_native_password_proxy_users', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['named_pipe'] = array( + 'named_pipe', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['net_buffer_length'] = array( + 'net_buffer_length', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['net_read_timeout'] = array( + 'net_read_timeout', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['net_retry_count'] = array( + 'net_retry_count', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['net_write_timeout'] = array( + 'net_write_timeout', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['new'] = array( + 'new', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['ngram_token_size'] = array( + 'ngram_token_size', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['offline_mode'] = array( + 'offline_mode', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['old'] = array( + 'old', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['old_alter_table'] = array( + 'old-alter-table', + 'server-options', + 'option_mysqld'); + $variable_doc_links['old_passwords'] = array( + 'old-passwords', + 'server-options', + 'option_mysqld'); + $variable_doc_links['open_files_limit'] = array( + 'open-files-limit', + 'server-options', + 'option_mysqld'); + $variable_doc_links['optimizer_prune_level'] = array( + 'optimizer_prune_level', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['optimizer_search_depth'] = array( + 'optimizer_search_depth', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['optimizer_switch'] = array( + 'optimizer_switch', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['optimizer_trace'] = array( + 'optimizer_trace', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['optimizer_trace_features'] = array( + 'optimizer_trace_features', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['optimizer_trace_limit'] = array( + 'optimizer_trace_limit', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['optimizer_trace_max_mem_size'] = array( + 'optimizer_trace_max_mem_size', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['optimizer_trace_offset'] = array( + 'optimizer_trace_offset', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['partition'] = array( + 'partition', + 'server-options', + 'option_mysqld'); + $variable_doc_links['performance_schema'] = array( + 'performance_schema', + 'performance-schema-system-variables', + 'sysvar'); + $variable_doc_links['performance_schema_accounts_size'] = array( + 'performance_schema_accounts_size', + 'performance-schema-system-variables', + 'sysvar'); + $variable_doc_links['performance_schema_digests_size'] = array( + 'performance_schema_digests_size', + 'performance-schema-system-variables', + 'sysvar'); + $variable_doc_links['performance_schema_events_stages_history_long_size'] + = array( + 'performance_schema_events_stages_history_long_size', + 'performance-schema-system-variables', + 'sysvar', + ); + $variable_doc_links['performance_schema_events_stages_history_size'] = array( + 'performance_schema_events_stages_history_size', + 'performance-schema-system-variables', + 'sysvar'); + $variable_doc_links['performance_schema_events_statements_history_long_size'] + = array( + 'performance_schema_events_statements_history_long_size', + 'performance-schema-system-variables', + 'sysvar', + ); + $variable_doc_links['performance_schema_events_statements_history_size'] + = array( + 'performance_schema_events_statements_history_size', + 'performance-schema-system-variables', + 'sysvar', + ); + $variable_doc_links['performance_schema_events_transactions_history_long_size'] + = array( + 'performance_schema_events_transactions_history_long_size', + 'performance-schema-system-variables', + 'sysvar', + ); + $variable_doc_links['performance_schema_events_transactions_history_size'] + = array( + 'performance_schema_events_transactions_history_size', + 'performance-schema-system-variables', + 'sysvar', + ); + $variable_doc_links['performance_schema_events_waits_history_long_size'] + = array( + 'performance_schema_events_waits_history_long_size', + 'performance-schema-system-variables', + 'sysvar', + ); + $variable_doc_links['performance_schema_events_waits_history_size'] = array( + 'performance_schema_events_waits_history_size', + 'performance-schema-system-variables', + 'sysvar'); + $variable_doc_links['performance_schema_hosts_size'] = array( + 'performance_schema_hosts_size', + 'performance-schema-system-variables', + 'sysvar'); + $variable_doc_links['performance_schema_max_cond_classes'] = array( + 'performance_schema_max_cond_classes', + 'performance-schema-system-variables', + 'sysvar'); + $variable_doc_links['performance_schema_max_cond_instances'] = array( + 'performance_schema_max_cond_instances', + 'performance-schema-system-variables', + 'sysvar'); + $variable_doc_links['performance_schema_max_digest_length'] = array( + 'performance_schema_max_digest_length', + 'performance-schema-system-variables', + 'sysvar'); + $variable_doc_links['performance_schema_max_file_classes'] = array( + 'performance_schema_max_file_classes', + 'performance-schema-system-variables', + 'sysvar'); + $variable_doc_links['performance_schema_max_file_handles'] = array( + 'performance_schema_max_file_handles', + 'performance-schema-system-variables', + 'sysvar'); + $variable_doc_links['performance_schema_max_file_instances'] = array( + 'performance_schema_max_file_instances', + 'performance-schema-system-variables', + 'sysvar'); + $variable_doc_links['performance_schema_max_index_stat'] = array( + 'performance_schema_max_index_stat', + 'performance-schema-system-variables', + 'sysvar'); + $variable_doc_links['performance_schema_max_memory_classes'] = array( + 'performance_schema_max_memory_classes', + 'performance-schema-system-variables', + 'sysvar'); + $variable_doc_links['performance_schema_max_metadata_locks'] = array( + 'performance_schema_max_metadata_locks', + 'performance-schema-system-variables', + 'sysvar'); + $variable_doc_links['performance_schema_max_mutex_classes'] = array( + 'performance_schema_max_mutex_classes', + 'performance-schema-system-variables', + 'sysvar'); + $variable_doc_links['performance_schema_max_mutex_instances'] = array( + 'performance_schema_max_mutex_instances', + 'performance-schema-system-variables', + 'sysvar'); + $variable_doc_links['performance_schema_max_prepared_statements_instances'] = array( + 'performance_schema_max_prepared_statements_instances', + 'performance-schema-system-variables', + 'sysvar'); + $variable_doc_links['performance_schema_max_program_instances'] = array( + 'performance_schema_max_program_instances', + 'performance-schema-system-variables', + 'sysvar'); + $variable_doc_links['performance_schema_max_rwlock_classes'] = array( + 'performance_schema_max_rwlock_classes', + 'performance-schema-system-variables', + 'sysvar'); + $variable_doc_links['performance_schema_max_rwlock_instances'] = array( + 'performance_schema_max_rwlock_instances', + 'performance-schema-system-variables', + 'sysvar'); + $variable_doc_links['performance_schema_max_socket_classes'] = array( + 'performance_schema_max_socket_classes', + 'performance-schema-system-variables', + 'sysvar'); + $variable_doc_links['performance_schema_max_socket_instances'] = array( + 'performance_schema_max_socket_instances', + 'performance-schema-system-variables', + 'sysvar'); + $variable_doc_links['performance_schema_max_sql_text_length'] = array( + 'performance_schema_max_sql_text_length', + 'performance-schema-system-variables', + 'sysvar'); + $variable_doc_links['performance_schema_max_stage_classes'] = array( + 'performance_schema_max_stage_classes', + 'performance-schema-system-variables', + 'sysvar'); + $variable_doc_links['performance_schema_max_statement_classes'] = array( + 'performance_schema_max_statement_classes', + 'performance-schema-system-variables', + 'sysvar'); + $variable_doc_links['performance_schema_max_statement_stack'] = array( + 'performance_schema_max_statement_stack', + 'performance-schema-system-variables', + 'sysvar'); + $variable_doc_links['performance_schema_max_table_handles'] = array( + 'performance_schema_max_table_handles', + 'performance-schema-system-variables', + 'sysvar'); + $variable_doc_links['performance_schema_max_table_instances'] = array( + 'performance_schema_max_table_instances', + 'performance-schema-system-variables', + 'sysvar'); + $variable_doc_links['performance_schema_max_table_lock_stat'] = array( + 'performance_schema_max_table_lock_stat', + 'performance-schema-system-variables', + 'sysvar'); + $variable_doc_links['performance_schema_max_thread_classes'] = array( + 'performance_schema_max_thread_classes', + 'performance-schema-system-variables', + 'sysvar'); + $variable_doc_links['performance_schema_max_thread_instances'] = array( + 'performance_schema_max_thread_instances', + 'performance-schema-system-variables', + 'sysvar'); + $variable_doc_links['performance_schema_session_connect_attrs_size'] = array( + 'performance_schema_session_connect_attrs_size', + 'performance-schema-system-variables', + 'sysvar'); + $variable_doc_links['performance_schema_setup_actors_size'] = array( + 'performance_schema_setup_actors_size', + 'performance-schema-system-variables', + 'sysvar'); + $variable_doc_links['performance_schema_setup_objects_size'] = array( + 'performance_schema_setup_objects_size', + 'performance-schema-system-variables', + 'sysvar'); + $variable_doc_links['performance_schema_users_size'] = array( + 'performance_schema_users_size', + 'performance-schema-system-variables', + 'sysvar'); + $variable_doc_links['pid_file'] = array( + 'pid-file', + 'server-options', + 'option_mysqld'); + $variable_doc_links['plugin_dir'] = array( + 'plugin_dir', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['port'] = array( + 'port', + 'server-options', + 'option_mysqld'); + $variable_doc_links['preload_buffer_size'] = array( + 'preload_buffer_size', + 'server-system-variables', + 'sysvar', + 'byte'); + $variable_doc_links['profiling'] = array( + 'profiling', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['profiling_history_size'] = array( + 'profiling_history_size', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['protocol_version'] = array( + 'protocol_version', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['proxy_user'] = array( + 'proxy_user', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['pseudo_thread_id'] = array( + 'pseudo_thread_id', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['query_alloc_block_size'] = array( + 'query_alloc_block_size', + 'server-system-variables', + 'sysvar', + 'byte'); + $variable_doc_links['query_cache_limit'] = array( + 'query_cache_limit', + 'server-system-variables', + 'sysvar', + 'byte'); + $variable_doc_links['query_cache_min_res_unit'] = array( + 'query_cache_min_res_unit', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['query_cache_size'] = array( + 'query_cache_size', + 'server-system-variables', + 'sysvar', + 'byte'); + $variable_doc_links['query_cache_type'] = array( + 'query_cache_type', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['query_cache_wlock_invalidate'] = array( + 'query_cache_wlock_invalidate', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['query_prealloc_size'] = array( + 'query_prealloc_size', + 'server-system-variables', + 'sysvar', + 'byte'); + $variable_doc_links['rand_seed1'] = array( + 'rand_seed1', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['rand_seed2'] = array( + 'rand_seed2', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['range_alloc_block_size'] = array( + 'range_alloc_block_size', + 'server-system-variables', + 'sysvar', + 'byte'); + $variable_doc_links['rbr_exec_mode'] = array( + 'rbr_exec_mode', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['read_buffer_size'] = array( + 'read_buffer_size', + 'server-system-variables', + 'sysvar', + 'byte'); + $variable_doc_links['read_only'] = array( + 'read_only', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['read_rnd_buffer_size'] = array( + 'read_rnd_buffer_size', + 'server-system-variables', + 'sysvar', + 'byte'); + $variable_doc_links['relay_log'] = array( + 'relay_log', + 'replication-options-slave', + 'sysvar'); + $variable_doc_links['relay_log_basename'] = array( + 'relay_log_basename', + 'replication-options-slave', + 'sysvar'); + $variable_doc_links['relay-log-index'] = array( + 'relay-log-index', + 'replication-options-slave', + 'option_mysqld'); + $variable_doc_links['relay_log_index'] = array( + 'relay_log_index', + 'replication-options-slave', + 'sysvar'); + $variable_doc_links['relay_log_info_file'] = array( + 'relay_log_info_file', + 'replication-options-slave', + 'sysvar'); + $variable_doc_links['relay_log_info_repository'] = array( + 'relay_log_info_repository', + 'replication-options-slave', + 'sysvar'); + $variable_doc_links['relay_log_purge'] = array( + 'relay_log_purge', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['relay_log_recovery'] = array( + 'relay_log_recovery', + 'replication-options-slave', + 'sysvar'); + $variable_doc_links['relay_log_space_limit'] = array( + 'relay_log_space_limit', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['report_host'] = array( + 'report-host', + 'replication-options-slave', + 'option_mysqld'); + $variable_doc_links['report_password'] = array( + 'report-password', + 'replication-options-slave', + 'option_mysqld'); + $variable_doc_links['report_port'] = array( + 'report-port', + 'replication-options-slave', + 'option_mysqld'); + $variable_doc_links['report_user'] = array( + 'report-user', + 'replication-options-slave', + 'option_mysqld'); + $variable_doc_links['require_secure_transport'] = array( + 'require_secure_transport', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['rpl_stop_slave_timeout'] = array( + 'rpl_stop_slave_timeout', + 'replication-options-slave', + 'sysvar'); + $variable_doc_links['rpl_recovery_rank'] = array( + 'rpl_recovery_rank', + 'replication-options-slave', + 'option_mysqld'); + $variable_doc_links['rpl_semi_sync_master_enabled'] = array( + 'rpl_semi_sync_master_enabled', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['rpl_semi_sync_master_timeout'] = array( + 'rpl_semi_sync_master_timeout', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['rpl_semi_sync_master_trace_level'] = array( + 'rpl_semi_sync_master_trace_level', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['rpl_semi_sync_master_wait_no_slave'] = array( + 'rpl_semi_sync_master_wait_no_slave', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['rpl_semi_sync_slave_enabled'] = array( + 'rpl_semi_sync_slave_enabled', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['rpl_semi_sync_slave_trace_level'] = array( + 'rpl_semi_sync_slave_trace_level', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['safe_show_database'] = array( + 'safe-show-database', + 'server-options', + 'option_mysqld'); + $variable_doc_links['secure_auth'] = array( + 'secure-auth', + 'server-options', + 'option_mysqld'); + $variable_doc_links['secure_file_priv'] = array( + 'secure-file-priv', + 'server-options', + 'option_mysqld'); + $variable_doc_links['server_id'] = array( + 'server-id', + 'replication-options', + 'option_mysqld'); + $variable_doc_links['server_id_bits'] = array( + 'server_id_bits', + 'mysql-cluster-system-variables', + 'sysvar'); + $variable_doc_links['server_uuid'] = array( + 'server_uuid', + 'replication-options', + 'sysvar'); + $variable_doc_links['session_track_gtids'] = array( + 'session_track_gtids', + 'server_system_variables', + 'sysvar'); + $variable_doc_links['session_track_schema'] = array( + 'session_track_schema', + 'server_system_variables', + 'sysvar'); + $variable_doc_links['session_track_state_change'] = array( + 'session_track_state_change', + 'server_system_variables', + 'sysvar'); + $variable_doc_links['session_track_system_variables'] = array( + 'session_track_system_variables', + 'session_system_variables', + 'sysvar'); + $variable_doc_links['session_track_transaction_info'] = array( + 'session_track_transaction_info', + 'session_system_variables', + 'sysvar'); + $variable_doc_links['sha256_password_proxy_users'] = array( + 'sha256_password_proxy_users', + 'session_system_variables', + 'sysvar'); + $variable_doc_links['show_compatibility_56'] = array( + 'show_compatibility_56', + 'session_system_variables', + 'sysvar'); + $variable_doc_links['show_old_temporals'] = array( + 'show_old_temporals', + 'session_system_variables', + 'sysvar'); + $variable_doc_links['shared_memory'] = array( + 'shared_memory', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['shared_memory_base_name'] = array( + 'shared_memory_base_name', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['skip_external_locking'] = array( + 'skip-external-locking', + 'server-options', + 'option_mysqld'); + $variable_doc_links['skip_name_resolve'] = array( + 'skip-name-resolve', + 'server-options', + 'option_mysqld'); + $variable_doc_links['skip_networking'] = array( + 'skip-networking', + 'server-options', + 'option_mysqld'); + $variable_doc_links['skip_show_database'] = array( + 'skip-show-database', + 'server-options', + 'option_mysqld'); + $variable_doc_links['slave_allow_batching'] = array( + 'slave_allow_batching', + 'mysql-cluster-system-variables', + 'sysvar'); + $variable_doc_links['slave_checkpoint_group'] = array( + 'slave_checkpoint_group', + 'replication-options-slave', + 'sysvar'); + $variable_doc_links['slave_checkpoint_period'] = array( + 'slave_checkpoint_period', + 'replication-options-slave', + 'sysvar'); + $variable_doc_links['slave_compressed_protocol'] = array( + 'slave_compressed_protocol', + 'replication-options-slave', + 'sysvar'); + $variable_doc_links['slave_exec_mode'] = array( + 'slave_exec_mode', + 'replication-options-slave', + 'sysvar'); + $variable_doc_links['slave_load_tmpdir'] = array( + 'slave-load-tmpdir', + 'replication-options-slave', + 'option_mysqld'); + $variable_doc_links['slave_max_allowed_packet'] = array( + 'slave_max_allowed_packet', + 'replication-options-slave', + 'sysvar'); + $variable_doc_links['slave_net_timeout'] = array( + 'slave-net-timeout', + 'replication-options-slave', + 'option_mysqld'); + $variable_doc_links['slave_parallel_type'] = array( + 'slave_parallel_type', + 'replication-options-slave', + 'sysvar'); + $variable_doc_links['slave_parallel_workers'] = array( + 'slave_parallel_workers', + 'replication-options-slave', + 'sysvar'); + $variable_doc_links['slave_pending_jobs_size_max'] = array( + 'slave_pending_jobs_size_max', + 'replication-options-slave', + 'sysvar', + 'byte'); + $variable_doc_links['slave_preserve_commit_order'] = array( + 'slave_preserve_commit_order', + 'replication-options-slave', + 'sysvar'); + $variable_doc_links['slave_rows_search_algorithms'] = array( + 'slave_rows_search_algorithms', + 'replication-options-slave', + 'sysvar'); + $variable_doc_links['slave_skip_errors'] = array( + 'slave-skip-errors', + 'replication-options-slave', + 'option_mysqld'); + $variable_doc_links['slave_sql_verify_checksum'] = array( + 'slave_sql_verify_checksum', + 'replication-options-slave', + 'sysvar'); + $variable_doc_links['slave_transaction_retries'] = array( + 'slave_transaction_retries', + 'replication-options-slave', + 'sysvar'); + $variable_doc_links['slave_type_conversions'] = array( + 'slave_type_conversions', + 'replication-options-slave', + 'sysvar'); + $variable_doc_links['slow_launch_time'] = array( + 'slow_launch_time', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['slow_query_log'] = array( + 'slow-query-log', + 'server-options', + 'server-system-variables'); + $variable_doc_links['slow_query_log_file'] = array( + 'slow_query_log_file', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['socket'] = array( + 'socket', + 'server-options', + 'option_mysqld'); + $variable_doc_links['sort_buffer_size'] = array( + 'sort_buffer_size', + 'server-system-variables', + 'sysvar', + 'byte'); + $variable_doc_links['sql_auto_is_null'] = array( + 'sql_auto_is_null', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['sql_big_selects'] = array( + 'sql_big_selects', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['sql_big_tables'] = array( + 'big-tables', + 'server-options', + 'server-system-variables'); + $variable_doc_links['sql_buffer_result'] = array( + 'sql_buffer_result', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['sql_log_bin'] = array( + 'sql_log_bin', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['sql_log_off'] = array( + 'sql_log_off', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['sql_log_update'] = array( + 'sql_log_update', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['sql_low_priority_updates'] = array( + 'sql_low_priority_updates', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['sql_max_join_size'] = array( + 'sql_max_join_size', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['sql_mode'] = array( + 'sql-mode', + 'server-options', + 'option_mysqld'); + $variable_doc_links['sql_notes'] = array( + 'sql_notes', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['sql_quote_show_create'] = array( + 'sql_quote_show_create', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['sql_safe_updates'] = array( + 'sql_safe_updates', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['sql_select_limit'] = array( + 'sql_select_limit', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['sql_slave_skip_counter'] = array( + 'sql_slave_skip_counter', + 'replication-options-slave', + 'sysvar'); + $variable_doc_links['sql_warnings'] = array( + 'sql_warnings', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['ssl_ca'] = array( + 'ssl-ca', + 'ssl-options', + 'option_general'); + $variable_doc_links['ssl_capath'] = array( + 'ssl-capath', + 'ssl-options', + 'option_general'); + $variable_doc_links['ssl_cert'] = array( + 'ssl-cert', + 'ssl-options', + 'option_general'); + $variable_doc_links['ssl_cipher'] = array( + 'ssl-cipher', + 'ssl-options', + 'option_general'); + $variable_doc_links['ssl_crl'] = array( + 'ssl_crl', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['ssl_crlpath'] = array( + 'ssl_crlpath', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['ssl_key'] = array( + 'ssl-key', + 'ssl-options', + 'option_general'); + $variable_doc_links['storage_engine'] = array( + 'storage_engine', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['stored_program_cache'] = array( + 'stored_program_cache', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['super_read_only'] = array( + 'super_read_only', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['sync_binlog'] = array( + 'sync_binlog', + 'replication-options-binary-log', + 'sysvar'); + $variable_doc_links['sync_frm'] = array( + 'sync_frm', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['sync_master_info'] = array( + 'sync_master_info', + 'replication-options-slave', + 'sysvar'); + $variable_doc_links['sync_relay_log'] = array( + 'sync_relay_log', + 'replication-options-slave', + 'sysvar'); + $variable_doc_links['sync_relay_log_info'] = array( + 'sync_relay_log_info', + 'replication-options-slave', + 'sysvar'); + $variable_doc_links['system_time_zone'] = array( + 'system_time_zone', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['table_definition_cache'] = array( + 'table_definition_cache', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['table_lock_wait_timeout'] = array( + 'table_lock_wait_timeout', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['table_open_cache'] = array( + 'table_open_cache', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['table_open_cache_instances'] = array( + 'table_open_cache_instances', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['table_type'] = array( + 'table_type', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['thread_cache_size'] = array( + 'thread_cache_size', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['thread_concurrency'] = array( + 'thread_concurrency', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['thread_handling'] = array( + 'thread_handling', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['thread_stack'] = array( + 'thread_stack', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['time_format'] = array( + 'time_format', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['time_zone'] = array( + 'time_zone', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['timed_mutexes'] = array( + 'timed_mutexes', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['timestamp'] = array( + 'timestamp', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['tmp_table_size'] = array( + 'tmp_table_size', + 'server-system-variables', + 'sysvar', + 'byte'); + $variable_doc_links['tmpdir'] = array( + 'tmpdir', + 'server-options', + 'option_mysqld'); + $variable_doc_links['transaction_alloc_block_size'] = array( + 'transaction_alloc_block_size', + 'server-system-variables', + 'sysvar', + 'byte'); + $variable_doc_links['transaction_prealloc_size'] = array( + 'transaction_prealloc_size', + 'server-system-variables', + 'sysvar', + 'byte'); + $variable_doc_links['transaction_write_set_extraction'] = array( + 'transaction_write_set_extraction', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['tx_isolation'] = array( + 'tx_isolation', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['tx_read_only'] = array( + 'tx_read_only', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['unique_checks'] = array( + 'unique_checks', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['updatable_views_with_limit'] = array( + 'updatable_views_with_limit', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['version'] = array( + 'version', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['version_comment'] = array( + 'version_comment', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['version_compile_machine'] = array( + 'version_compile_machine', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['version_compile_os'] = array( + 'version_compile_os', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['wait_timeout'] = array( + 'wait_timeout', + 'server-system-variables', + 'sysvar'); + $variable_doc_links['warning_count'] = array( + 'warning_count', + 'server-system-variables', + 'sysvar'); + return $variable_doc_links; + } + + /** + * Returns array of static(i.e. non-editable/ read-only) global system variables + * + * See https://dev.mysql.com/doc/refman/5.6/en/server-system-variables.html + * + * @return array + */ + private function _getStaticSystemVariables() + { + $static_variables = array( + 'audit_log_buffer_size', + 'audit_log_current_session', + 'audit_log_file', + 'audit_log_format', + 'audit_log_policy', + 'audit_log_strategy', + 'auto_generate_certs', + 'back_log', + 'basedir', + 'bind_address', + 'binlog_gtid_simple_recovery', + 'character_set_system', + 'character_sets_dir', + 'core_file', + 'daemon_memcached_enable_binlog', + 'daemon_memcached_engine_lib_name', + 'daemon_memcached_engine_lib_path', + 'daemon_memcached_option', + 'daemon_memcached_r_batch_size', + 'daemon_memcached_w_batch_size', + 'datadir', + 'date_format', + 'datetime_format', + 'default_authentication_plugin', + 'disabled_storage_engines', + 'explicit_defaults_for_timestamp', + 'ft_max_word_len', + 'ft_min_word_len', + 'ft_query_expansion_limit', + 'ft_stopword_file', + 'gtid_owned', + 'have_compress', + 'have_crypt', + 'have_dynamic_loading', + 'have_geometry', + 'have_openssl', + 'have_profiling', + 'have_query_cache', + 'have_rtree_keys', + 'have_ssl', + 'have_statement_timeout', + 'have_symlink', + 'hostname', + 'ignore_builtin_innodb', + 'ignore_db_dirs', + 'init_file', + 'innodb_adaptive_hash_index_parts', + 'innodb_additional_mem_pool_size', + 'innodb_api_disable_rowlock', + 'innodb_api_enable_binlog', + 'innodb_api_enable_mdl', + 'innodb_autoinc_lock_mode', + 'innodb_buffer_pool_chunk_size', + 'innodb_buffer_pool_instances', + 'innodb_buffer_pool_load_at_startup', + 'innodb_checksums', + 'innodb_data_file_path', + 'innodb_data_home_dir', + 'innodb_doublewrite', + 'innodb_file_format_check', + 'innodb_flush_method', + 'innodb_force_load_corrupted', + 'innodb_force_recovery', + 'innodb_ft_cache_size', + 'innodb_ft_max_token_size', + 'innodb_ft_min_token_size', + 'innodb_ft_sort_pll_degree', + 'innodb_ft_total_cache_size', + 'innodb_locks_unsafe_for_binlog', + 'innodb_log_buffer_size', + 'innodb_log_file_size', + 'innodb_log_files_in_group', + 'innodb_log_group_home_dir', + 'innodb_numa_interleave', + 'innodb_open_files', + 'innodb_page_cleaners', + 'innodb_page_size', + 'innodb_purge_threads', + 'innodb_read_io_threads', + 'innodb_read_only', + 'innodb_rollback_on_timeout', + 'innodb_sort_buffer_size', + 'innodb_sync_array_size', + 'innodb_sync_debug', + 'innodb_temp_data_file_path', + 'innodb_undo_directory', + 'innodb_undo_tablespaces', + 'innodb_use_native_aio', + 'innodb_use_sys_malloc', + 'innodb_version', + 'innodb_write_io_threads', + 'language', + 'large_files_support', + 'large_page_size', + 'large_pages', + 'lc_messages_dir', + 'license', + 'locked_in_memory', + 'log-bin', + 'log_bin', + 'log_bin_basename', + 'log_bin_index', + 'log_bin_use_v1_row_events', + 'log_bin_use_v1_row_events', + 'log_error', + 'log_slave_updates', + 'log_slave_updates', + 'lower_case_file_system', + 'lower_case_table_names', + 'max_digest_length', + 'mecab_rc_file', + 'metadata_locks_cache_size', + 'metadata_locks_hash_instances', + 'myisam_mmap_size', + 'myisam_recover_options', + 'named_pipe', + 'ndb-batch-size', + 'ndb-cluster-connection-pool', + 'ndb-cluster-connection-pool-nodeids', + 'ndb_log_apply_status', + 'ndb_log_apply_status', + 'ndb_log_orig', + 'ndb_log_orig', + 'ndb_log_transaction_id', + 'ndb_log_transaction_id', + 'ndb_optimized_node_selection', + 'Ndb_slave_max_replicated_epoch', + 'ndb_use_copying_alter_table', + 'ndb_version', + 'ndb_version_string', + 'ndb-wait-connected', + 'ndb-wait-setup', + 'ndbinfo_database', + 'ndbinfo_version', + 'ngram_token_size', + 'old', + 'open_files_limit', + 'performance_schema', + 'performance_schema_accounts_size', + 'performance_schema_digests_size', + 'performance_schema_events_stages_history_long_size', + 'performance_schema_events_stages_history_size', + 'performance_schema_events_statements_history_long_size', + 'performance_schema_events_statements_history_size', + 'performance_schema_events_transactions_history_long_size', + 'performance_schema_events_transactions_history_size', + 'performance_schema_events_waits_history_long_size', + 'performance_schema_events_waits_history_size', + 'performance_schema_hosts_size', + 'performance_schema_max_cond_classes', + 'performance_schema_max_cond_instances', + 'performance_schema_max_digest_length', + 'performance_schema_max_file_classes', + 'performance_schema_max_file_handles', + 'performance_schema_max_file_instances', + 'performance_schema_max_index_stat', + 'performance_schema_max_memory_classes', + 'performance_schema_max_metadata_locks', + 'performance_schema_max_mutex_classes', + 'performance_schema_max_mutex_instances', + 'performance_schema_max_prepared_statements_instances', + 'performance_schema_max_program_instances', + 'performance_schema_max_rwlock_classes', + 'performance_schema_max_rwlock_instances', + 'performance_schema_max_socket_classes', + 'performance_schema_max_socket_instances', + 'performance_schema_max_sql_text_length', + 'performance_schema_max_stage_classes', + 'performance_schema_max_statement_classes', + 'performance_schema_max_statement_stack', + 'performance_schema_max_table_handles', + 'performance_schema_max_table_instances', + 'performance_schema_max_table_lock_stat', + 'performance_schema_max_thread_classes', + 'performance_schema_max_thread_instances', + 'performance_schema_session_connect_attrs_size', + 'performance_schema_setup_actors_size', + 'performance_schema_setup_objects_size', + 'performance_schema_users_size', + 'pid_file', + 'plugin_dir', + 'port', + 'protocol_version', + 'relay_log', + 'relay_log_basename', + 'relay_log_index', + 'relay_log_index', + 'relay_log_info_file', + 'relay_log_recovery', + 'relay_log_space_limit', + 'report_host', + 'eport_password', + 'report_port', + 'report_user', + 'secure_file_priv', + 'server_id_bits', + 'server_id_bits', + 'server_uuid', + 'sha256_password_auto_generate_rsa_keys', + 'sha256_password_private_key_path', + 'sha256_password_public_key_path', + 'shared_memory', + 'shared_memory_base_name', + 'simplified_binlog_gtid_recovery', + 'skip_external_locking', + 'skip_name_resolve', + 'skip_networking', + 'skip_show_database', + 'slave_load_tmpdir', + 'slave_skip_errors', + 'slave_type_conversions', + 'socket', + 'ssl_ca', + 'ssl_capath', + 'ssl_cert', + 'ssl_cipher', + 'ssl_crl', + 'ssl_crlpath', + 'ssl_key', + 'system_time_zone', + 'table_open_cache_instances', + 'thread_concurrency', + 'thread_handling', + 'thread_stack', + 'time_format', + 'tls_version', + 'tmpdir', + 'validate_user_plugins', + 'version', + 'version_comment', + 'version_compile_machine', + 'version_compile_os', + 'version_tokens_session_number' + ); + + return $static_variables; + } + +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Controllers/Table/TableChartController.php b/php/apps/phpmyadmin49/libraries/classes/Controllers/Table/TableChartController.php new file mode 100644 index 00000000..82bb1c11 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Controllers/Table/TableChartController.php @@ -0,0 +1,246 @@ +sql_query = $sql_query; + $this->url_query = $url_query; + $this->cfg = $cfg; + } + + /** + * Execute the query and return the result + * + * @return void + */ + public function indexAction() + { + $response = Response::getInstance(); + if ($response->isAjax() + && isset($_REQUEST['pos']) + && isset($_REQUEST['session_max_rows']) + ) { + $this->ajaxAction(); + return; + } + + // Throw error if no sql query is set + if (!isset($this->sql_query) || $this->sql_query == '') { + $this->response->setRequestStatus(false); + $this->response->addHTML( + Message::error(__('No SQL query was set to fetch data.')) + ); + return; + } + + $this->response->getHeader()->getScripts()->addFiles( + array( + 'chart.js', + 'tbl_chart.js', + 'vendor/jqplot/jquery.jqplot.js', + 'vendor/jqplot/plugins/jqplot.barRenderer.js', + 'vendor/jqplot/plugins/jqplot.canvasAxisLabelRenderer.js', + 'vendor/jqplot/plugins/jqplot.canvasTextRenderer.js', + 'vendor/jqplot/plugins/jqplot.categoryAxisRenderer.js', + 'vendor/jqplot/plugins/jqplot.dateAxisRenderer.js', + 'vendor/jqplot/plugins/jqplot.pointLabels.js', + 'vendor/jqplot/plugins/jqplot.pieRenderer.js', + 'vendor/jqplot/plugins/jqplot.enhancedPieLegendRenderer.js', + 'vendor/jqplot/plugins/jqplot.highlighter.js' + ) + ); + + /** + * Extract values for common work + * @todo Extract common files + */ + $db = &$this->db; + $table = &$this->table; + $url_params = array(); + + /** + * Runs common work + */ + if (strlen($this->table) > 0) { + $url_params['goto'] = Util::getScriptNameForOption( + $this->cfg['DefaultTabTable'], 'table' + ); + $url_params['back'] = 'tbl_sql.php'; + include 'libraries/tbl_common.inc.php'; + $GLOBALS['dbi']->selectDb($GLOBALS['db']); + } elseif (strlen($this->db) > 0) { + $url_params['goto'] = Util::getScriptNameForOption( + $this->cfg['DefaultTabDatabase'], 'database' + ); + $url_params['back'] = 'sql.php'; + include 'libraries/db_common.inc.php'; + } else { + $url_params['goto'] = Util::getScriptNameForOption( + $this->cfg['DefaultTabServer'], 'server' + ); + $url_params['back'] = 'sql.php'; + include 'libraries/server_common.inc.php'; + } + + $data = array(); + + $result = $this->dbi->tryQuery($this->sql_query); + $fields_meta = $this->dbi->getFieldsMeta($result); + while ($row = $this->dbi->fetchAssoc($result)) { + $data[] = $row; + } + + $keys = array_keys($data[0]); + + $numeric_types = array('int', 'real'); + $numeric_column_count = 0; + foreach ($keys as $idx => $key) { + if (in_array($fields_meta[$idx]->type, $numeric_types)) { + $numeric_column_count++; + } + } + + if ($numeric_column_count == 0) { + $this->response->setRequestStatus(false); + $this->response->addJSON( + 'message', + __('No numeric columns present in the table to plot.') + ); + return; + } + + $url_params['db'] = $this->db; + $url_params['reload'] = 1; + + /** + * Displays the page + */ + $this->response->addHTML( + Template::get('table/chart/tbl_chart')->render( + array( + 'url_query' => $this->url_query, + 'url_params' => $url_params, + 'keys' => $keys, + 'fields_meta' => $fields_meta, + 'numeric_types' => $numeric_types, + 'numeric_column_count' => $numeric_column_count, + 'sql_query' => $this->sql_query + ) + ) + ); + } + + /** + * Handle ajax request + * + * @return void + */ + public function ajaxAction() + { + /** + * Extract values for common work + * @todo Extract common files + */ + $db = &$this->db; + $table = &$this->table; + + if (strlen($this->table) > 0 && strlen($this->db) > 0) { + include './libraries/tbl_common.inc.php'; + } + + $parser = new Parser($this->sql_query); + $statement = $parser->statements[0]; + if (empty($statement->limit)) { + $statement->limit = new Limit( + $_REQUEST['session_max_rows'], $_REQUEST['pos'] + ); + } else { + $start = $statement->limit->offset + $_REQUEST['pos']; + $rows = min( + $_REQUEST['session_max_rows'], + $statement->limit->rowCount - $_REQUEST['pos'] + ); + $statement->limit = new Limit($rows, $start); + } + $sql_with_limit = $statement->build(); + + $data = array(); + $result = $this->dbi->tryQuery($sql_with_limit); + while ($row = $this->dbi->fetchAssoc($result)) { + $data[] = $row; + } + + if (empty($data)) { + $this->response->setRequestStatus(false); + $this->response->addJSON('message', __('No data to display')); + return; + } + $sanitized_data = array(); + + foreach ($data as $data_row_number => $data_row) { + $tmp_row = array(); + foreach ($data_row as $data_column => $data_value) { + $tmp_row[htmlspecialchars($data_column)] = htmlspecialchars( + $data_value + ); + } + $sanitized_data[] = $tmp_row; + } + $this->response->setRequestStatus(true); + $this->response->addJSON('message', null); + $this->response->addJSON('chartData', json_encode($sanitized_data)); + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Controllers/Table/TableGisVisualizationController.php b/php/apps/phpmyadmin49/libraries/classes/Controllers/Table/TableGisVisualizationController.php new file mode 100644 index 00000000..b0d6d9bd --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Controllers/Table/TableGisVisualizationController.php @@ -0,0 +1,222 @@ +sql_query = $sql_query; + $this->url_params = $url_params; + $this->url_params['goto'] = $goto; + $this->url_params['back'] = $back; + $this->visualizationSettings = $visualizationSettings; + } + + /** + * Save to file + * + * @return void + */ + public function saveToFileAction() + { + $this->response->disable(); + $file_name = $this->visualizationSettings['spatialColumn']; + $save_format = $_GET['fileFormat']; + $this->visualization->toFile($file_name, $save_format); + } + + /** + * Index + * + * @return void + */ + public function indexAction() + { + // Throw error if no sql query is set + if (! isset($this->sql_query) || $this->sql_query == '') { + $this->response->setRequestStatus(false); + $this->response->addHTML( + Message::error(__('No SQL query was set to fetch data.')) + ); + return; + } + + // Execute the query and return the result + $result = $this->dbi->tryQuery($this->sql_query); + // Get the meta data of results + $meta = $this->dbi->getFieldsMeta($result); + + // Find the candidate fields for label column and spatial column + $labelCandidates = array(); + $spatialCandidates = array(); + foreach ($meta as $column_meta) { + if ($column_meta->type == 'geometry') { + $spatialCandidates[] = $column_meta->name; + } else { + $labelCandidates[] = $column_meta->name; + } + } + + // Get settings if any posted + if (Core::isValid($_POST['visualizationSettings'], 'array')) { + $this->visualizationSettings = $_POST['visualizationSettings']; + } + + // Check mysql version + $this->visualizationSettings['mysqlVersion'] = $this->dbi->getVersion(); + + if (!isset($this->visualizationSettings['labelColumn']) + && isset($labelCandidates[0]) + ) { + $this->visualizationSettings['labelColumn'] = ''; + } + + // If spatial column is not set, use first geometric column as spatial column + if (! isset($this->visualizationSettings['spatialColumn'])) { + $this->visualizationSettings['spatialColumn'] = $spatialCandidates[0]; + } + + // Convert geometric columns from bytes to text. + $pos = isset($_GET['pos']) ? $_GET['pos'] + : $_SESSION['tmpval']['pos']; + if (isset($_GET['session_max_rows'])) { + $rows = $_GET['session_max_rows']; + } else { + if ($_SESSION['tmpval']['max_rows'] != 'all') { + $rows = $_SESSION['tmpval']['max_rows']; + } else { + $rows = $GLOBALS['cfg']['MaxRows']; + } + } + $this->visualization = GisVisualization::get( + $this->sql_query, + $this->visualizationSettings, + $rows, + $pos + ); + + if (isset($_GET['saveToFile'])) { + $this->saveToFileAction(); + return; + } + + $this->response->getHeader()->getScripts()->addFiles( + array( + 'vendor/openlayers/OpenLayers.js', + 'vendor/jquery/jquery.svg.js', + 'tbl_gis_visualization.js', + ) + ); + + // If all the rows contain SRID, use OpenStreetMaps on the initial loading. + if (! isset($_POST['displayVisualization'])) { + if ($this->visualization->hasSrid()) { + $this->visualizationSettings['choice'] = 'useBaseLayer'; + } else { + unset($this->visualizationSettings['choice']); + } + } + + $this->visualization->setUserSpecifiedSettings($this->visualizationSettings); + if ($this->visualizationSettings != null) { + foreach ($this->visualization->getSettings() as $setting => $val) { + if (! isset($this->visualizationSettings[$setting])) { + $this->visualizationSettings[$setting] = $val; + } + } + } + + /** + * Displays the page + */ + $this->url_params['sql_query'] = $this->sql_query; + $downloadUrl = 'tbl_gis_visualization.php' . Url::getCommon( + array_merge( + $this->url_params, + array( + 'sql_signature' => Core::signSqlQuery($this->sql_query), + 'saveToFile' => true, + 'session_max_rows' => $rows, + 'pos' => $pos + ) + ) + ); + $html = Template::get('table/gis_visualization/gis_visualization')->render( + array( + 'url_params' => $this->url_params, + 'download_url' => $downloadUrl, + 'label_candidates' => $labelCandidates, + 'spatial_candidates' => $spatialCandidates, + 'visualization_settings' => $this->visualizationSettings, + 'sql_query' => $this->sql_query, + 'visualization' => $this->visualization->toImage('svg'), + 'draw_ol' => $this->visualization->asOl(), + 'pma_theme_image' => $GLOBALS['pmaThemeImage'] + ) + ); + + $this->response->addHTML($html); + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Controllers/Table/TableIndexesController.php b/php/apps/phpmyadmin49/libraries/classes/Controllers/Table/TableIndexesController.php new file mode 100644 index 00000000..080d292a --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Controllers/Table/TableIndexesController.php @@ -0,0 +1,177 @@ +index = $index; + } + + /** + * Index + * + * @return void + */ + public function indexAction() + { + if (isset($_POST['do_save_data'])) { + $this->doSaveDataAction(); + return; + } // end builds the new index + + $this->displayFormAction(); + } + + /** + * Display the form to edit/create an index + * + * @return void + */ + public function displayFormAction() + { + $GLOBALS['dbi']->selectDb($GLOBALS['db']); + $add_fields = 0; + if (isset($_POST['index']) && is_array($_POST['index'])) { + // coming already from form + if (isset($_POST['index']['columns']['names'])) { + $add_fields = count($_POST['index']['columns']['names']) + - $this->index->getColumnCount(); + } + if (isset($_POST['add_fields'])) { + $add_fields += $_POST['added_fields']; + } + } elseif (isset($_POST['create_index'])) { + $add_fields = $_POST['added_fields']; + } // end preparing form values + + // Get fields and stores their name/type + if (isset($_POST['create_edit_table'])) { + $fields = json_decode($_POST['columns'], true); + $index_params = array( + 'Non_unique' => ($_POST['index']['Index_choice'] == 'UNIQUE') + ? '0' : '1', + ); + $this->index->set($index_params); + $add_fields = count($fields); + } else { + $fields = $this->dbi->getTable($this->db, $this->table) + ->getNameAndTypeOfTheColumns(); + } + + $form_params = array( + 'db' => $this->db, + 'table' => $this->table, + ); + + if (isset($_POST['create_index'])) { + $form_params['create_index'] = 1; + } elseif (isset($_POST['old_index'])) { + $form_params['old_index'] = $_POST['old_index']; + } elseif (isset($_POST['index'])) { + $form_params['old_index'] = $_POST['index']; + } + + $this->response->getHeader()->getScripts()->addFile('indexes.js'); + + $this->response->addHTML( + Template::get('table/index_form')->render( + array( + 'fields' => $fields, + 'index' => $this->index, + 'form_params' => $form_params, + 'add_fields' => $add_fields, + 'create_edit_table' => isset($_POST['create_edit_table']) + ) + ) + ); + } + + /** + * Process the data from the edit/create index form, + * run the query to build the new index + * and moves back to "tbl_sql.php" + * + * @return void + */ + public function doSaveDataAction() + { + $error = false; + + $sql_query = $this->dbi->getTable($this->db, $this->table) + ->getSqlQueryForIndexCreateOrEdit($this->index, $error); + + // If there is a request for SQL previewing. + if (isset($_POST['preview_sql'])) { + + $this->response->addJSON( + 'sql_data', + Template::get('preview_sql') + ->render( + array( + 'query_data' => $sql_query + ) + ) + ); + } elseif (!$error) { + + $this->dbi->query($sql_query); + $response = Response::getInstance(); + if ($response->isAjax()) { + $message = Message::success( + __('Table %1$s has been altered successfully.') + ); + $message->addParam($this->table); + $this->response->addJSON( + 'message', Util::getMessage($message, $sql_query, 'success') + ); + $this->response->addJSON( + 'index_table', + Index::getHtmlForIndexes( + $this->table, $this->db + ) + ); + } else { + include 'tbl_structure.php'; + } + } else { + $this->response->setRequestStatus(false); + $this->response->addJSON('message', $error); + } + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Controllers/Table/TableRelationController.php b/php/apps/phpmyadmin49/libraries/classes/Controllers/Table/TableRelationController.php new file mode 100644 index 00000000..f351f85a --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Controllers/Table/TableRelationController.php @@ -0,0 +1,390 @@ +options_array = $options_array; + $this->cfgRelation = $cfgRelation; + $this->tbl_storage_engine = $tbl_storage_engine; + $this->existrel = $existrel; + $this->existrel_foreign = $existrel_foreign; + $this->upd_query = $upd_query; + $this->relation = new Relation(); + } + + /** + * Index + * + * @return void + */ + public function indexAction() + { + // Send table of column names to populate corresponding dropdowns depending + // on the current selection + if (isset($_POST['getDropdownValues']) + && $_POST['getDropdownValues'] === 'true' + ) { + // if both db and table are selected + if (isset($_POST['foreignTable'])) { + $this->getDropdownValueForTableAction(); + } else { // if only the db is selected + $this->getDropdownValueForDbAction(); + } + return; + } + + $this->response->getHeader()->getScripts()->addFiles( + array( + 'tbl_relation.js', + 'indexes.js' + ) + ); + + // Set the database + $this->dbi->selectDb($this->db); + + // updates for Internal relations + if (isset($_POST['destination_db']) && $this->cfgRelation['relwork']) { + $this->updateForInternalRelationAction(); + } + + // updates for foreign keys + $this->updateForForeignKeysAction(); + + // Updates for display field + if ($this->cfgRelation['displaywork'] && isset($_POST['display_field'])) { + $this->updateForDisplayField(); + } + + // If we did an update, refresh our data + if (isset($_POST['destination_db']) && $this->cfgRelation['relwork']) { + $this->existrel = $this->relation->getForeigners( + $this->db, $this->table, '', 'internal' + ); + } + if (isset($_POST['destination_foreign_db']) + && Util::isForeignKeySupported($this->tbl_storage_engine) + ) { + $this->existrel_foreign = $this->relation->getForeigners( + $this->db, $this->table, '', 'foreign' + ); + } + + // display secondary level tabs if necessary + $engine = $this->dbi->getTable($this->db, $this->table)->getStorageEngine(); + + $this->response->addHTML( + Template::get('table/secondary_tabs')->render( + array( + 'url_params' => array( + 'db' => $GLOBALS['db'], + 'table' => $GLOBALS['table'] + ), + 'is_foreign_key_supported' => Util::isForeignKeySupported($engine), + 'cfg_relation' => $this->relation->getRelationsParam(), + ) + ) + ); + $this->response->addHTML('
        '); + + /** + * Dialog + */ + // Now find out the columns of our $table + // need to use DatabaseInterface::QUERY_STORE with $this->dbi->numRows() + // in mysqli + $columns = $this->dbi->getColumns($this->db, $this->table); + + $column_array = array(); + $column_array[''] = ''; + foreach ($columns as $column) { + if (strtoupper($this->tbl_storage_engine) == 'INNODB' + || ! empty($column['Key']) + ) { + $column_array[$column['Field']] = $column['Field']; + } + } + if ($GLOBALS['cfg']['NaturalOrder']) { + uksort($column_array, 'strnatcasecmp'); + } + + // common form + $this->response->addHTML( + Template::get('table/relation/common_form')->render([ + 'db' => $this->db, + 'table' => $this->table, + 'cfg_relation' => $this->cfgRelation, + 'tbl_storage_engine' => $this->tbl_storage_engine, + 'existrel' => isset($this->existrel) ? $this->existrel : array(), + 'existrel_foreign' => isset($this->existrel_foreign) + ? $this->existrel_foreign['foreign_keys_data'] : array(), + 'options_array' => $this->options_array, + 'column_array' => $column_array, + 'save_row' => array_values($columns), + 'url_params' => $GLOBALS['url_params'], + 'databases' => $GLOBALS['dblist']->databases, + 'dbi' => $GLOBALS['dbi'], + ]) + ); + + if (Util::isForeignKeySupported($this->tbl_storage_engine)) { + $this->response->addHTML(Index::getHtmlForDisplayIndexes()); + } + $this->response->addHTML('
        '); + } + + /** + * Update for display field + * + * @return void + */ + public function updateForDisplayField() + { + if ($this->upd_query->updateDisplayField( + $_POST['display_field'], $this->cfgRelation + ) + ) { + $this->response->addHTML( + Util::getMessage( + __('Display column was successfully updated.'), + '', 'success' + ) + ); + } + } + + /** + * Update for FK + * + * @return void + */ + public function updateForForeignKeysAction() + { + $multi_edit_columns_name = isset($_POST['foreign_key_fields_name']) + ? $_POST['foreign_key_fields_name'] + : null; + $preview_sql_data = ''; + $seen_error = false; + + // (for now, one index name only; we keep the definitions if the + // foreign db is not the same) + if (isset($_POST['destination_foreign_db']) + && isset($_POST['destination_foreign_table']) + && isset($_POST['destination_foreign_column'])) { + list($html, $preview_sql_data, $display_query, $seen_error) + = $this->upd_query->updateForeignKeys( + $_POST['destination_foreign_db'], + $multi_edit_columns_name, $_POST['destination_foreign_table'], + $_POST['destination_foreign_column'], $this->options_array, + $this->table, + isset($this->existrel_foreign) + ? $this->existrel_foreign['foreign_keys_data'] + : null + ); + $this->response->addHTML($html); + } + + // If there is a request for SQL previewing. + if (isset($_POST['preview_sql'])) { + Core::previewSQL($preview_sql_data); + } + + if (!empty($display_query) && !$seen_error) { + $GLOBALS['display_query'] = $display_query; + $this->response->addHTML( + Util::getMessage( + __('Your SQL query has been executed successfully.'), + null, 'success' + ) + ); + } + } + + /** + * Update for internal relation + * + * @return void + */ + public function updateForInternalRelationAction() + { + $multi_edit_columns_name = isset($_POST['fields_name']) + ? $_POST['fields_name'] + : null; + + if ($this->upd_query->updateInternalRelations( + $multi_edit_columns_name, + $_POST['destination_db'], + $_POST['destination_table'], + $_POST['destination_column'], + $this->cfgRelation, + isset($this->existrel) ? $this->existrel : null + ) + ) { + $this->response->addHTML( + Util::getMessage( + __('Internal relationships were successfully updated.'), + '', 'success' + ) + ); + } + } + + /** + * Send table columns for foreign table dropdown + * + * @return void + * + */ + public function getDropdownValueForTableAction() + { + $foreignTable = $_POST['foreignTable']; + $table_obj = $this->dbi->getTable($_POST['foreignDb'], $foreignTable); + // Since views do not have keys defined on them provide the full list of + // columns + if ($table_obj->isView()) { + $columnList = $table_obj->getColumns(false, false); + } else { + $columnList = $table_obj->getIndexedColumns(false, false); + } + $columns = array(); + foreach ($columnList as $column) { + $columns[] = htmlspecialchars($column); + } + if ($GLOBALS['cfg']['NaturalOrder']) { + usort($columns, 'strnatcasecmp'); + } + $this->response->addJSON('columns', $columns); + + // @todo should be: $server->db($db)->table($table)->primary() + $primary = Index::getPrimary($foreignTable, $_POST['foreignDb']); + if (false === $primary) { + return; + } + + $this->response->addJSON('primary', array_keys($primary->getColumns())); + } + + /** + * Send database selection values for dropdown + * + * @return void + * + */ + public function getDropdownValueForDbAction() + { + $tables = array(); + $foreign = isset($_POST['foreign']) && $_POST['foreign'] === 'true'; + + if ($foreign) { + $query = 'SHOW TABLE STATUS FROM ' + . Util::backquote($_POST['foreignDb']); + $tables_rs = $this->dbi->query( + $query, + DatabaseInterface::CONNECT_USER, + DatabaseInterface::QUERY_STORE + ); + + while ($row = $this->dbi->fetchArray($tables_rs)) { + if (isset($row['Engine']) + && mb_strtoupper($row['Engine']) == $this->tbl_storage_engine + ) { + $tables[] = htmlspecialchars($row['Name']); + } + } + } else { + $query = 'SHOW TABLES FROM ' + . Util::backquote($_POST['foreignDb']); + $tables_rs = $this->dbi->query( + $query, + DatabaseInterface::CONNECT_USER, + DatabaseInterface::QUERY_STORE + ); + while ($row = $this->dbi->fetchArray($tables_rs)) { + $tables[] = htmlspecialchars($row[0]); + } + } + if ($GLOBALS['cfg']['NaturalOrder']) { + usort($tables, 'strnatcasecmp'); + } + $this->response->addJSON('tables', $tables); + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Controllers/Table/TableSearchController.php b/php/apps/phpmyadmin49/libraries/classes/Controllers/Table/TableSearchController.php new file mode 100644 index 00000000..90f945f0 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Controllers/Table/TableSearchController.php @@ -0,0 +1,1166 @@ +url_query = $url_query; + $this->_searchType = $searchType; + $this->_columnNames = array(); + $this->_columnNullFlags = array(); + $this->_columnTypes = array(); + $this->_columnCollations = array(); + $this->_geomColumnFlag = false; + $this->_foreigners = array(); + $this->relation = new Relation(); + // Loads table's information + $this->_loadTableInfo(); + $this->_connectionCharSet = $this->dbi->fetchValue( + "SELECT @@character_set_connection" + ); + } + + /** + * Gets all the columns of a table along with their types, collations + * and whether null or not. + * + * @return void + */ + private function _loadTableInfo() + { + // Gets the list and number of columns + $columns = $this->dbi->getColumns( + $this->db, $this->table, null, true + ); + // Get details about the geometry functions + $geom_types = Util::getGISDatatypes(); + + foreach ($columns as $row) { + // set column name + $this->_columnNames[] = $row['Field']; + + $type = $row['Type']; + // check whether table contains geometric columns + if (in_array($type, $geom_types)) { + $this->_geomColumnFlag = true; + } + // reformat mysql query output + if (strncasecmp($type, 'set', 3) == 0 + || strncasecmp($type, 'enum', 4) == 0 + ) { + $type = str_replace(',', ', ', $type); + } else { + // strip the "BINARY" attribute, except if we find "BINARY(" because + // this would be a BINARY or VARBINARY column type + if (! preg_match('@BINARY[\(]@i', $type)) { + $type = preg_replace('@BINARY@i', '', $type); + } + $type = preg_replace('@ZEROFILL@i', '', $type); + $type = preg_replace('@UNSIGNED@i', '', $type); + $type = mb_strtolower($type); + } + if (empty($type)) { + $type = ' '; + } + $this->_columnTypes[] = $type; + $this->_columnNullFlags[] = $row['Null']; + $this->_columnCollations[] + = ! empty($row['Collation']) && $row['Collation'] != 'NULL' + ? $row['Collation'] + : ''; + } // end for + + // Retrieve foreign keys + $this->_foreigners = $this->relation->getForeigners($this->db, $this->table); + } + + /** + * Index action + * + * @return void + */ + public function indexAction() + { + switch ($this->_searchType) { + case 'replace': + if (isset($_POST['find'])) { + $this->findAction(); + + return; + } + $this->response + ->getHeader() + ->getScripts() + ->addFile('tbl_find_replace.js'); + + if (isset($_POST['replace'])) { + $this->replaceAction(); + } + + // Displays the find and replace form + $this->displaySelectionFormAction(); + break; + + case 'normal': + $this->response->getHeader() + ->getScripts() + ->addFiles( + array( + 'makegrid.js', + 'sql.js', + 'tbl_select.js', + 'tbl_change.js', + 'vendor/jquery/jquery.uitablefilter.js', + 'gis_data_editor.js', + ) + ); + + if (isset($_POST['range_search'])) { + $this->rangeSearchAction(); + + return; + } + + /** + * No selection criteria received -> display the selection form + */ + if (!isset($_POST['columnsToDisplay']) + && !isset($_POST['displayAllColumns']) + ) { + $this->displaySelectionFormAction(); + } else { + $this->doSelectionAction(); + } + break; + + case 'zoom': + $this->response->getHeader() + ->getScripts() + ->addFiles( + array( + 'makegrid.js', + 'sql.js', + 'vendor/jqplot/jquery.jqplot.js', + 'vendor/jqplot/plugins/jqplot.canvasTextRenderer.js', + 'vendor/jqplot/plugins/jqplot.canvasAxisLabelRenderer.js', + 'vendor/jqplot/plugins/jqplot.dateAxisRenderer.js', + 'vendor/jqplot/plugins/jqplot.highlighter.js', + 'vendor/jqplot/plugins/jqplot.cursor.js', + 'tbl_zoom_plot_jqplot.js', + 'tbl_change.js', + ) + ); + + /** + * Handle AJAX request for data row on point select + * + * @var boolean Object containing parameters for the POST request + */ + if (isset($_POST['get_data_row']) + && $_POST['get_data_row'] == true + ) { + $this->getDataRowAction(); + + return; + } + /** + * Handle AJAX request for changing field information + * (value,collation,operators,field values) in input form + * + * @var boolean Object containing parameters for the POST request + */ + if (isset($_POST['change_tbl_info']) + && $_POST['change_tbl_info'] == true + ) { + $this->changeTableInfoAction(); + + return; + } + + //Set default datalabel if not selected + if (!isset($_POST['zoom_submit']) || $_POST['dataLabel'] == '') { + $dataLabel = $this->relation->getDisplayField($this->db, $this->table); + } else { + $dataLabel = $_POST['dataLabel']; + } + + // Displays the zoom search form + $this->displaySelectionFormAction($dataLabel); + + /* + * Handle the input criteria and generate the query result + * Form for displaying query results + */ + if (isset($_POST['zoom_submit']) + && $_POST['criteriaColumnNames'][0] != 'pma_null' + && $_POST['criteriaColumnNames'][1] != 'pma_null' + && $_POST['criteriaColumnNames'][0] != $_POST['criteriaColumnNames'][1] + ) { + if (! isset($goto)) { + $goto = Util::getScriptNameForOption( + $GLOBALS['cfg']['DefaultTabTable'], 'table' + ); + } + $this->zoomSubmitAction($dataLabel, $goto); + } + break; + } + } + + /** + * Zoom submit action + * + * @param string $dataLabel Data label + * @param string $goto Goto + * + * @return void + */ + public function zoomSubmitAction($dataLabel, $goto) + { + //Query generation part + $sql_query = $this->_buildSqlQuery(); + $sql_query .= ' LIMIT ' . $_POST['maxPlotLimit']; + + //Query execution part + $result = $this->dbi->query( + $sql_query . ";", + DatabaseInterface::CONNECT_USER, + DatabaseInterface::QUERY_STORE + ); + $fields_meta = $this->dbi->getFieldsMeta($result); + $data = array(); + while ($row = $this->dbi->fetchAssoc($result)) { + //Need a row with indexes as 0,1,2 for the getUniqueCondition + // hence using a temporary array + $tmpRow = array(); + foreach ($row as $val) { + $tmpRow[] = $val; + } + //Get unique condition on each row (will be needed for row update) + $uniqueCondition = Util::getUniqueCondition( + $result, // handle + count($this->_columnNames), // fields_cnt + $fields_meta, // fields_meta + $tmpRow, // row + true, // force_unique + false, // restrict_to_table + null // analyzed_sql_results + ); + //Append it to row array as where_clause + $row['where_clause'] = $uniqueCondition[0]; + + $tmpData = array( + $_POST['criteriaColumnNames'][0] => + $row[$_POST['criteriaColumnNames'][0]], + $_POST['criteriaColumnNames'][1] => + $row[$_POST['criteriaColumnNames'][1]], + 'where_clause' => $uniqueCondition[0] + ); + $tmpData[$dataLabel] = ($dataLabel) ? $row[$dataLabel] : ''; + $data[] = $tmpData; + } + unset($tmpData); + + //Displays form for point data and scatter plot + $titles = array( + 'Browse' => Util::getIcon( + 'b_browse', + __('Browse foreign values') + ) + ); + $this->response->addHTML( + Template::get('table/search/zoom_result_form')->render([ + 'db' => $this->db, + 'table' => $this->table, + 'column_names' => $this->_columnNames, + 'foreigners' => $this->_foreigners, + 'column_null_flags' => $this->_columnNullFlags, + 'column_types' => $this->_columnTypes, + 'titles' => $titles, + 'goto' => $goto, + 'data' => $data, + 'data_json' => json_encode($data), + 'zoom_submit' => isset($_POST['zoom_submit']), + 'foreign_max_limit' => $GLOBALS['cfg']['ForeignKeyMaxLimit'], + ]) + ); + } + + /** + * Change table info action + * + * @return void + */ + public function changeTableInfoAction() + { + $field = $_POST['field']; + if ($field == 'pma_null') { + $this->response->addJSON('field_type', ''); + $this->response->addJSON('field_collation', ''); + $this->response->addJSON('field_operators', ''); + $this->response->addJSON('field_value', ''); + return; + } + $key = array_search($field, $this->_columnNames); + $search_index + = ((isset($_POST['it']) && is_numeric($_POST['it'])) + ? intval($_POST['it']) : 0); + + $properties = $this->getColumnProperties($search_index, $key); + $this->response->addJSON( + 'field_type', htmlspecialchars($properties['type']) + ); + $this->response->addJSON('field_collation', $properties['collation']); + $this->response->addJSON('field_operators', $properties['func']); + $this->response->addJSON('field_value', $properties['value']); + } + + /** + * Get data row action + * + * @return void + */ + public function getDataRowAction() + { + $extra_data = array(); + $row_info_query = 'SELECT * FROM `' . $_POST['db'] . '`.`' + . $_POST['table'] . '` WHERE ' . $_POST['where_clause']; + $result = $this->dbi->query( + $row_info_query . ";", + DatabaseInterface::CONNECT_USER, + DatabaseInterface::QUERY_STORE + ); + $fields_meta = $this->dbi->getFieldsMeta($result); + while ($row = $this->dbi->fetchAssoc($result)) { + // for bit fields we need to convert them to printable form + $i = 0; + foreach ($row as $col => $val) { + if ($fields_meta[$i]->type == 'bit') { + $row[$col] = Util::printableBitValue( + $val, $fields_meta[$i]->length + ); + } + $i++; + } + $extra_data['row_info'] = $row; + } + $this->response->addJSON($extra_data); + } + + /** + * Do selection action + * + * @return void + */ + public function doSelectionAction() + { + /** + * Selection criteria have been submitted -> do the work + */ + $sql_query = $this->_buildSqlQuery(); + + /** + * Add this to ensure following procedures included running correctly. + */ + $db = $this->db; + + $sql = new Sql(); + $sql->executeQueryAndSendQueryResponse( + null, // analyzed_sql_results + false, // is_gotofile + $this->db, // db + $this->table, // table + null, // find_real_end + null, // sql_query_for_bookmark + null, // extra_data + null, // message_to_show + null, // message + null, // sql_data + $GLOBALS['goto'], // goto + $GLOBALS['pmaThemeImage'], // pmaThemeImage + null, // disp_query + null, // disp_message + null, // query_type + $sql_query, // sql_query + null, // selectedTables + null // complete_query + ); + } + + /** + * Display selection form action + * + * @param string $dataLabel Data label + * + * @return void + */ + public function displaySelectionFormAction($dataLabel = null) + { + $this->url_query .= '&goto=tbl_select.php&back=tbl_select.php'; + if (! isset($goto)) { + $goto = Util::getScriptNameForOption( + $GLOBALS['cfg']['DefaultTabTable'], 'table' + ); + } + // Displays the table search form + $this->response->addHTML( + Template::get('secondary_tabs') + ->render( + array( + 'url_params' => array( + 'db' => $this->db, + 'table' => $this->table, + ), + 'sub_tabs' => $this->_getSubTabs(), + ) + ) + ); + $this->response->addHTML( + Template::get('table/search/selection_form')->render(array( + 'search_type' => $this->_searchType, + 'db' => $this->db, + 'table' => $this->table, + 'goto' => $goto, + 'self' => $this, + 'geom_column_flag' => $this->_geomColumnFlag, + 'column_names' => $this->_columnNames, + 'column_types' => $this->_columnTypes, + 'column_collations' => $this->_columnCollations, + 'data_label' => $dataLabel, + 'criteria_column_names' => isset($_POST['criteriaColumnNames']) ? $_POST['criteriaColumnNames'] : null, + 'criteria_column_types' => isset($_POST['criteriaColumnTypes']) ? $_POST['criteriaColumnTypes'] : null, + 'sql_types' => $GLOBALS['dbi']->types, + 'max_rows' => intval($GLOBALS['cfg']['MaxRows']), + 'max_plot_limit' => ((! empty($_POST['maxPlotLimit'])) + ? intval($_POST['maxPlotLimit']) + : intval($GLOBALS['cfg']['maxRowPlotLimit'])), + )) + ); + } + + /** + * Range search action + * + * @return void + */ + public function rangeSearchAction() + { + $min_max = $this->getColumnMinMax($_POST['column']); + $this->response->addJSON('column_data', $min_max); + } + + /** + * Find action + * + * @return void + */ + public function findAction() + { + $useRegex = array_key_exists('useRegex', $_POST) + && $_POST['useRegex'] == 'on'; + + $preview = $this->getReplacePreview( + $_POST['columnIndex'], + $_POST['find'], + $_POST['replaceWith'], + $useRegex, + $this->_connectionCharSet + ); + $this->response->addJSON('preview', $preview); + } + + /** + * Replace action + * + * @return void + */ + public function replaceAction() + { + $this->replace( + $_POST['columnIndex'], + $_POST['findString'], + $_POST['replaceWith'], + $_POST['useRegex'], + $this->_connectionCharSet + ); + $this->response->addHTML( + Util::getMessage( + __('Your SQL query has been executed successfully.'), + null, 'success' + ) + ); + } + + /** + * Returns HTML for previewing strings found and their replacements + * + * @param int $columnIndex index of the column + * @param string $find string to find in the column + * @param string $replaceWith string to replace with + * @param boolean $useRegex to use Regex replace or not + * @param string $charSet character set of the connection + * + * @return string HTML for previewing strings found and their replacements + */ + function getReplacePreview( + $columnIndex, $find, $replaceWith, $useRegex, $charSet + ) { + $column = $this->_columnNames[$columnIndex]; + if ($useRegex) { + $result = $this->_getRegexReplaceRows( + $columnIndex, $find, $replaceWith, $charSet + ); + } else { + $sql_query = "SELECT " + . Util::backquote($column) . "," + . " REPLACE(" + . Util::backquote($column) . ", '" . $find . "', '" + . $replaceWith + . "')," + . " COUNT(*)" + . " FROM " . Util::backquote($this->db) + . "." . Util::backquote($this->table) + . " WHERE " . Util::backquote($column) + . " LIKE '%" . $find . "%' COLLATE " . $charSet . "_bin"; // here we + // change the collation of the 2nd operand to a case sensitive + // binary collation to make sure that the comparison + // is case sensitive + $sql_query .= " GROUP BY " . Util::backquote($column) + . " ORDER BY " . Util::backquote($column) . " ASC"; + + $result = $this->dbi->fetchResult($sql_query, 0); + } + + return Template::get('table/search/replace_preview')->render( + array( + 'db' => $this->db, + 'table' => $this->table, + 'column_index' => $columnIndex, + 'find' => $find, + 'replace_with' => $replaceWith, + 'use_regex' => $useRegex, + 'result' => $result + ) + ); + } + + /** + * Finds and returns Regex pattern and their replacements + * + * @param int $columnIndex index of the column + * @param string $find string to find in the column + * @param string $replaceWith string to replace with + * @param string $charSet character set of the connection + * + * @return array Array containing original values, replaced values and count + */ + private function _getRegexReplaceRows( + $columnIndex, $find, $replaceWith, $charSet + ) { + $column = $this->_columnNames[$columnIndex]; + $sql_query = "SELECT " + . Util::backquote($column) . "," + . " 1," // to add an extra column that will have replaced value + . " COUNT(*)" + . " FROM " . Util::backquote($this->db) + . "." . Util::backquote($this->table) + . " WHERE " . Util::backquote($column) + . " RLIKE '" . $GLOBALS['dbi']->escapeString($find) . "' COLLATE " + . $charSet . "_bin"; // here we + // change the collation of the 2nd operand to a case sensitive + // binary collation to make sure that the comparison is case sensitive + $sql_query .= " GROUP BY " . Util::backquote($column) + . " ORDER BY " . Util::backquote($column) . " ASC"; + + $result = $this->dbi->fetchResult($sql_query, 0); + + if (is_array($result)) { + /* Iterate over possible delimiters to get one */ + $delimiters = array('/', '@', '#', '~', '!', '$', '%', '^', '&', '_'); + $found = false; + for ($i = 0, $l = count($delimiters); $i < $l; $i++) { + if (strpos($find, $delimiters[$i]) === false) { + $found = true; + break; + } + } + if (! $found) { + return false; + } + $find = $delimiters[$i] . $find . $delimiters[$i]; + foreach ($result as $index=>$row) { + $result[$index][1] = preg_replace( + $find, + $replaceWith, + $row[0] + ); + } + } + return $result; + } + + /** + * Replaces a given string in a column with a give replacement + * + * @param int $columnIndex index of the column + * @param string $find string to find in the column + * @param string $replaceWith string to replace with + * @param boolean $useRegex to use Regex replace or not + * @param string $charSet character set of the connection + * + * @return void + */ + public function replace($columnIndex, $find, $replaceWith, $useRegex, + $charSet + ) { + $column = $this->_columnNames[$columnIndex]; + if ($useRegex) { + $toReplace = $this->_getRegexReplaceRows( + $columnIndex, $find, $replaceWith, $charSet + ); + $sql_query = "UPDATE " . Util::backquote($this->table) + . " SET " . Util::backquote($column) . " = CASE"; + if (is_array($toReplace)) { + foreach ($toReplace as $row) { + $sql_query .= "\n WHEN " . Util::backquote($column) + . " = '" . $GLOBALS['dbi']->escapeString($row[0]) + . "' THEN '" . $GLOBALS['dbi']->escapeString($row[1]) . "'"; + } + } + $sql_query .= " END" + . " WHERE " . Util::backquote($column) + . " RLIKE '" . $GLOBALS['dbi']->escapeString($find) . "' COLLATE " + . $charSet . "_bin"; // here we + // change the collation of the 2nd operand to a case sensitive + // binary collation to make sure that the comparison + // is case sensitive + } else { + $sql_query = "UPDATE " . Util::backquote($this->table) + . " SET " . Util::backquote($column) . " =" + . " REPLACE(" + . Util::backquote($column) . ", '" . $find . "', '" + . $replaceWith + . "')" + . " WHERE " . Util::backquote($column) + . " LIKE '%" . $find . "%' COLLATE " . $charSet . "_bin"; // here we + // change the collation of the 2nd operand to a case sensitive + // binary collation to make sure that the comparison + // is case sensitive + } + $this->dbi->query( + $sql_query, + DatabaseInterface::CONNECT_USER, + DatabaseInterface::QUERY_STORE + ); + $GLOBALS['sql_query'] = $sql_query; + } + + /** + * Finds minimum and maximum value of a given column. + * + * @param string $column Column name + * + * @return array + */ + public function getColumnMinMax($column) + { + $sql_query = 'SELECT MIN(' . Util::backquote($column) . ') AS `min`, ' + . 'MAX(' . Util::backquote($column) . ') AS `max` ' + . 'FROM ' . Util::backquote($this->db) . '.' + . Util::backquote($this->table); + + $result = $this->dbi->fetchSingleRow($sql_query); + + return $result; + } + + /** + * Returns an array with necessary configurations to create + * sub-tabs in the table_select page. + * + * @return array Array containing configuration (icon, text, link, id, args) + * of sub-tabs + */ + private function _getSubTabs() + { + $subtabs = array(); + $subtabs['search']['icon'] = 'b_search'; + $subtabs['search']['text'] = __('Table search'); + $subtabs['search']['link'] = 'tbl_select.php'; + $subtabs['search']['id'] = 'tbl_search_id'; + $subtabs['search']['args']['pos'] = 0; + + $subtabs['zoom']['icon'] = 'b_select'; + $subtabs['zoom']['link'] = 'tbl_zoom_select.php'; + $subtabs['zoom']['text'] = __('Zoom search'); + $subtabs['zoom']['id'] = 'zoom_search_id'; + + $subtabs['replace']['icon'] = 'b_find_replace'; + $subtabs['replace']['link'] = 'tbl_find_replace.php'; + $subtabs['replace']['text'] = __('Find and replace'); + $subtabs['replace']['id'] = 'find_replace_id'; + + return $subtabs; + } + + /** + * Builds the sql search query from the post parameters + * + * @return string the generated SQL query + */ + private function _buildSqlQuery() + { + $sql_query = 'SELECT '; + + // If only distinct values are needed + $is_distinct = (isset($_POST['distinct'])) ? 'true' : 'false'; + if ($is_distinct == 'true') { + $sql_query .= 'DISTINCT '; + } + + // if all column names were selected to display, we do a 'SELECT *' + // (more efficient and this helps prevent a problem in IE + // if one of the rows is edited and we come back to the Select results) + if (isset($_POST['zoom_submit']) || ! empty($_POST['displayAllColumns'])) { + $sql_query .= '* '; + } else { + $sql_query .= implode( + ', ', + Util::backquote($_POST['columnsToDisplay']) + ); + } // end if + + $sql_query .= ' FROM ' + . Util::backquote($_POST['table']); + $whereClause = $this->_generateWhereClause(); + $sql_query .= $whereClause; + + // if the search results are to be ordered + if (isset($_POST['orderByColumn']) && $_POST['orderByColumn'] != '--nil--') { + $sql_query .= ' ORDER BY ' + . Util::backquote($_POST['orderByColumn']) + . ' ' . $_POST['order']; + } // end if + return $sql_query; + } + + /** + * Provides a column's type, collation, operators list, and criteria value + * to display in table search form + * + * @param integer $search_index Row number in table search form + * @param integer $column_index Column index in ColumnNames array + * + * @return array Array containing column's properties + */ + public function getColumnProperties($search_index, $column_index) + { + $selected_operator = (isset($_POST['criteriaColumnOperators'][$search_index]) + ? $_POST['criteriaColumnOperators'][$search_index] : ''); + $entered_value = (isset($_POST['criteriaValues']) + ? $_POST['criteriaValues'] : ''); + $titles = array( + 'Browse' => Util::getIcon( + 'b_browse', __('Browse foreign values') + ) + ); + //Gets column's type and collation + $type = $this->_columnTypes[$column_index]; + $collation = $this->_columnCollations[$column_index]; + //Gets column's comparison operators depending on column type + $typeOperators = $GLOBALS['dbi']->types->getTypeOperatorsHtml( + preg_replace('@\(.*@s', '', $this->_columnTypes[$column_index]), + $this->_columnNullFlags[$column_index], $selected_operator + ); + $func = Template::get('table/search/column_comparison_operators')->render( + array( + 'search_index' => $search_index, + 'type_operators' => $typeOperators + ) + ); + //Gets link to browse foreign data(if any) and criteria inputbox + $foreignData = $this->relation->getForeignData( + $this->_foreigners, $this->_columnNames[$column_index], false, '', '' + ); + $value = Template::get('table/search/input_box')->render( + array( + 'str' => '', + 'column_type' => (string) $type, + 'column_id' => 'fieldID_', + 'in_zoom_search_edit' => false, + 'foreigners' => $this->_foreigners, + 'column_name' => $this->_columnNames[$column_index], + 'column_name_hash' => md5($this->_columnNames[$column_index]), + 'foreign_data' => $foreignData, + 'table' => $this->table, + 'column_index' => $search_index, + 'foreign_max_limit' => $GLOBALS['cfg']['ForeignKeyMaxLimit'], + 'criteria_values' => $entered_value, + 'db' => $this->db, + 'titles' => $titles, + 'in_fbs' => true + ) + ); + return array( + 'type' => $type, + 'collation' => $collation, + 'func' => $func, + 'value' => $value + ); + } + + /** + * Generates the where clause for the SQL search query to be executed + * + * @return string the generated where clause + */ + private function _generateWhereClause() + { + if (isset($_POST['customWhereClause']) + && trim($_POST['customWhereClause']) != '' + ) { + return ' WHERE ' . $_POST['customWhereClause']; + } + + // If there are no search criteria set or no unary criteria operators, + // return + if (! isset($_POST['criteriaValues']) + && ! isset($_POST['criteriaColumnOperators']) + && ! isset($_POST['geom_func']) + ) { + return ''; + } + + // else continue to form the where clause from column criteria values + $fullWhereClause = array(); + foreach ($_POST['criteriaColumnOperators'] as $column_index => $operator) { + $unaryFlag = $GLOBALS['dbi']->types->isUnaryOperator($operator); + $tmp_geom_func = isset($_POST['geom_func'][$column_index]) + ? $_POST['geom_func'][$column_index] : null; + + $whereClause = $this->_getWhereClause( + $_POST['criteriaValues'][$column_index], + $_POST['criteriaColumnNames'][$column_index], + $_POST['criteriaColumnTypes'][$column_index], + $operator, + $unaryFlag, + $tmp_geom_func + ); + + if ($whereClause) { + $fullWhereClause[] = $whereClause; + } + } // end foreach + + if (!empty($fullWhereClause)) { + return ' WHERE ' . implode(' AND ', $fullWhereClause); + } + return ''; + } + + /** + * Return the where clause in case column's type is ENUM. + * + * @param mixed $criteriaValues Search criteria input + * @param string $func_type Search function/operator + * + * @return string part of where clause. + */ + private function _getEnumWhereClause($criteriaValues, $func_type) + { + if (! is_array($criteriaValues)) { + $criteriaValues = explode(',', $criteriaValues); + } + $enum_selected_count = count($criteriaValues); + if ($func_type == '=' && $enum_selected_count > 1) { + $func_type = 'IN'; + $parens_open = '('; + $parens_close = ')'; + + } elseif ($func_type == '!=' && $enum_selected_count > 1) { + $func_type = 'NOT IN'; + $parens_open = '('; + $parens_close = ')'; + + } else { + $parens_open = ''; + $parens_close = ''; + } + $enum_where = '\'' + . $GLOBALS['dbi']->escapeString($criteriaValues[0]) . '\''; + for ($e = 1; $e < $enum_selected_count; $e++) { + $enum_where .= ', \'' + . $GLOBALS['dbi']->escapeString($criteriaValues[$e]) . '\''; + } + + return ' ' . $func_type . ' ' . $parens_open + . $enum_where . $parens_close; + } + + /** + * Return the where clause for a geometrical column. + * + * @param mixed $criteriaValues Search criteria input + * @param string $names Name of the column on which search is submitted + * @param string $func_type Search function/operator + * @param string $types Type of the field + * @param bool $geom_func Whether geometry functions should be applied + * + * @return string part of where clause. + */ + private function _getGeomWhereClause($criteriaValues, $names, + $func_type, $types, $geom_func = null + ) { + $geom_unary_functions = array( + 'IsEmpty' => 1, + 'IsSimple' => 1, + 'IsRing' => 1, + 'IsClosed' => 1, + ); + $where = ''; + + // Get details about the geometry functions + $geom_funcs = Util::getGISFunctions($types, true, false); + + // If the function takes multiple parameters + if(strpos($func_type, "IS NULL") !== false || strpos($func_type, "IS NOT NULL") !== false) { + $where = Util::backquote($names) . " " . $func_type; + return $where; + } elseif ($geom_funcs[$geom_func]['params'] > 1) { + // create gis data from the criteria input + $gis_data = Util::createGISData($criteriaValues, $this->dbi->getVersion()); + $where = $geom_func . '(' . Util::backquote($names) + . ', ' . $gis_data . ')'; + return $where; + } + + // New output type is the output type of the function being applied + $type = $geom_funcs[$geom_func]['type']; + $geom_function_applied = $geom_func + . '(' . Util::backquote($names) . ')'; + + // If the where clause is something like 'IsEmpty(`spatial_col_name`)' + if (isset($geom_unary_functions[$geom_func]) + && trim($criteriaValues) == '' + ) { + $where = $geom_function_applied; + + } elseif (in_array($type, Util::getGISDatatypes()) + && ! empty($criteriaValues) + ) { + // create gis data from the criteria input + $gis_data = Util::createGISData($criteriaValues, $this->dbi->getVersion()); + $where = $geom_function_applied . " " . $func_type . " " . $gis_data; + + } elseif (strlen($criteriaValues) > 0) { + $where = $geom_function_applied . " " + . $func_type . " '" . $criteriaValues . "'"; + } + return $where; + } + + /** + * Return the where clause for query generation based on the inputs provided. + * + * @param mixed $criteriaValues Search criteria input + * @param string $names Name of the column on which search is submitted + * @param string $types Type of the field + * @param string $func_type Search function/operator + * @param bool $unaryFlag Whether operator unary or not + * @param bool $geom_func Whether geometry functions should be applied + * + * @return string generated where clause. + */ + private function _getWhereClause($criteriaValues, $names, $types, + $func_type, $unaryFlag, $geom_func = null + ) { + // If geometry function is set + if (! empty($geom_func)) { + return $this->_getGeomWhereClause( + $criteriaValues, $names, $func_type, $types, $geom_func + ); + } + + $backquoted_name = Util::backquote($names); + $where = ''; + if ($unaryFlag) { + $where = $backquoted_name . ' ' . $func_type; + } elseif (strncasecmp($types, 'enum', 4) == 0 && (! empty($criteriaValues) || $criteriaValues[0] === '0')) { + $where = $backquoted_name; + $where .= $this->_getEnumWhereClause($criteriaValues, $func_type); + + } elseif ($criteriaValues != '') { + // For these types we quote the value. Even if it's another type + // (like INT), for a LIKE we always quote the value. MySQL converts + // strings to numbers and numbers to strings as necessary + // during the comparison + if (preg_match('@char|binary|blob|text|set|date|time|year@i', $types) + || mb_strpos(' ' . $func_type, 'LIKE') + ) { + $quot = '\''; + } else { + $quot = ''; + } + + // LIKE %...% + if ($func_type == 'LIKE %...%') { + $func_type = 'LIKE'; + $criteriaValues = '%' . $criteriaValues . '%'; + } + if ($func_type == 'REGEXP ^...$') { + $func_type = 'REGEXP'; + $criteriaValues = '^' . $criteriaValues . '$'; + } + + if ('IN (...)' != $func_type + && 'NOT IN (...)' != $func_type + && 'BETWEEN' != $func_type + && 'NOT BETWEEN' != $func_type + ) { + return $backquoted_name . ' ' . $func_type . ' ' . $quot + . $GLOBALS['dbi']->escapeString($criteriaValues) . $quot; + } + $func_type = str_replace(' (...)', '', $func_type); + + //Don't explode if this is already an array + //(Case for (NOT) IN/BETWEEN.) + if (is_array($criteriaValues)) { + $values = $criteriaValues; + } else { + $values = explode(',', $criteriaValues); + } + // quote values one by one + $emptyKey = false; + foreach ($values as $key => &$value) { + if ('' === $value) { + $emptyKey = $key; + $value = 'NULL'; + continue; + } + $value = $quot . $GLOBALS['dbi']->escapeString(trim($value)) + . $quot; + } + + if ('BETWEEN' == $func_type || 'NOT BETWEEN' == $func_type) { + $where = $backquoted_name . ' ' . $func_type . ' ' + . (isset($values[0]) ? $values[0] : '') + . ' AND ' . (isset($values[1]) ? $values[1] : ''); + } else { //[NOT] IN + if (false !== $emptyKey) { + unset($values[$emptyKey]); + } + $wheres = array(); + if (!empty($values)) { + $wheres[] = $backquoted_name . ' ' . $func_type + . ' (' . implode(',', $values) . ')'; + } + if (false !== $emptyKey) { + $wheres[] = $backquoted_name . ' IS NULL'; + } + $where = implode(' OR ', $wheres); + if (1 < count($wheres)) { + $where = '(' . $where . ')'; + } + } + } // end if + + return $where; + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Controllers/Table/TableStructureController.php b/php/apps/phpmyadmin49/libraries/classes/Controllers/Table/TableStructureController.php new file mode 100644 index 00000000..06b26dcd --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Controllers/Table/TableStructureController.php @@ -0,0 +1,1505 @@ +_db_is_system_schema = $db_is_system_schema; + $this->_url_query = Url::getCommonRaw(array('db' => $db, 'table' => $table)); + $this->_tbl_is_view = $tbl_is_view; + $this->_tbl_storage_engine = $tbl_storage_engine; + $this->_table_info_num_rows = $table_info_num_rows; + $this->_tbl_collation = $tbl_collation; + $this->_showtable = $showtable; + $this->table_obj = $this->dbi->getTable($this->db, $this->table); + + $this->createAddField = new CreateAddField($dbi); + $this->relation = new Relation(); + } + + /** + * Index action + * + * @return void + */ + public function indexAction() + { + PageSettings::showGroup('TableStructure'); + + /** + * Function implementations for this script + */ + include_once 'libraries/check_user_privileges.inc.php'; + + $this->response->getHeader()->getScripts()->addFiles( + array( + 'tbl_structure.js', + 'indexes.js' + ) + ); + + /** + * Handle column moving + */ + if (isset($_POST['move_columns']) + && is_array($_POST['move_columns']) + && $this->response->isAjax() + ) { + $this->moveColumns(); + return; + } + + /** + * handle MySQL reserved words columns check + */ + if (isset($_POST['reserved_word_check'])) { + if ($GLOBALS['cfg']['ReservedWordDisableWarning'] === false) { + $columns_names = $_POST['field_name']; + $reserved_keywords_names = array(); + foreach ($columns_names as $column) { + if (Context::isKeyword(trim($column), true)) { + $reserved_keywords_names[] = trim($column); + } + } + if (Context::isKeyword(trim($this->table), true)) { + $reserved_keywords_names[] = trim($this->table); + } + if (count($reserved_keywords_names) == 0) { + $this->response->setRequestStatus(false); + } + $this->response->addJSON( + 'message', sprintf( + _ngettext( + 'The name \'%s\' is a MySQL reserved keyword.', + 'The names \'%s\' are MySQL reserved keywords.', + count($reserved_keywords_names) + ), + implode(',', $reserved_keywords_names) + ) + ); + } else { + $this->response->setRequestStatus(false); + } + return; + } + /** + * A click on Change has been made for one column + */ + if (isset($_GET['change_column'])) { + $this->displayHtmlForColumnChange(null, 'tbl_structure.php'); + return; + } + + /** + * Adding or editing partitioning of the table + */ + if (isset($_POST['edit_partitioning']) + && ! isset($_POST['save_partitioning']) + ) { + $this->displayHtmlForPartitionChange(); + return; + } + + /** + * handle multiple field commands if required + * + * submit_mult_*_x comes from IE if is used + */ + $submit_mult = $this->getMultipleFieldCommandType(); + + if (! empty($submit_mult)) { + if (isset($_POST['selected_fld'])) { + if ($submit_mult == 'browse') { + // browsing the table displaying only selected columns + $this->displayTableBrowseForSelectedColumns( + $GLOBALS['goto'], $GLOBALS['pmaThemeImage'] + ); + } else { + // handle multiple field commands + // handle confirmation of deleting multiple columns + $action = 'tbl_structure.php'; + $GLOBALS['selected'] = $_POST['selected_fld']; + list( + $what_ret, $query_type_ret, $is_unset_submit_mult, + $mult_btn_ret, $centralColsError + ) + = $this->getDataForSubmitMult( + $submit_mult, $_POST['selected_fld'], $action + ); + //update the existing variables + // todo: refactor mult_submits.inc.php such as + // below globals are not needed anymore + if (isset($what_ret)) { + $GLOBALS['what'] = $what_ret; + global $what; + } + if (isset($query_type_ret)) { + $GLOBALS['query_type'] = $query_type_ret; + global $query_type; + } + if ($is_unset_submit_mult) { + unset($submit_mult); + } + if (isset($mult_btn_ret)) { + $GLOBALS['mult_btn'] = $mult_btn_ret; + global $mult_btn; + } + include 'libraries/mult_submits.inc.php'; + /** + * if $submit_mult == 'change', execution will have stopped + * at this point + */ + if (empty($message)) { + $message = Message::success(); + } + $this->response->addHTML( + Util::getMessage($message, $sql_query) + ); + } + } else { + $this->response->setRequestStatus(false); + $this->response->addJSON('message', __('No column selected.')); + } + } + + // display secondary level tabs if necessary + $engine = $this->table_obj->getStorageEngine(); + $this->response->addHTML( + Template::get('table/secondary_tabs')->render( + array( + 'url_params' => array( + 'db' => $this->db, + 'table' => $this->table + ), + 'is_foreign_key_supported' => Util::isForeignKeySupported($engine), + 'cfg_relation' => $this->relation->getRelationsParam(), + ) + ) + ); + $this->response->addHTML('
        '); + + /** + * Modifications have been submitted -> updates the table + */ + if (isset($_POST['do_save_data'])) { + $regenerate = $this->updateColumns(); + if ($regenerate) { + // This happens when updating failed + // @todo: do something appropriate + } else { + // continue to show the table's structure + unset($_POST['selected']); + } + } + + /** + * Modifications to the partitioning have been submitted -> updates the table + */ + if (isset($_POST['save_partitioning'])) { + $this->updatePartitioning(); + } + + /** + * Adding indexes + */ + if (isset($_POST['add_key']) + || isset($_POST['partition_maintenance']) + ) { + //todo: set some variables for sql.php include, to be eliminated + //after refactoring sql.php + $db = $this->db; + $table = $this->table; + $sql_query = $GLOBALS['sql_query']; + $cfg = $GLOBALS['cfg']; + $pmaThemeImage = $GLOBALS['pmaThemeImage']; + include 'sql.php'; + $GLOBALS['reload'] = true; + } + + /** + * Gets the relation settings + */ + $cfgRelation = $this->relation->getRelationsParam(); + + /** + * Runs common work + */ + // set db, table references, for require_once that follows + // got to be eliminated in long run + $db = &$this->db; + $table = &$this->table; + $url_params = array(); + include_once 'libraries/tbl_common.inc.php'; + $this->_db_is_system_schema = $db_is_system_schema; + $this->_url_query = Url::getCommonRaw(array( + 'db' => $db, + 'table' => $table, + 'goto' => 'tbl_structure.php', + 'back' => 'tbl_structure.php', + )); + /* The url_params array is initialized in above include */ + $url_params['goto'] = 'tbl_structure.php'; + $url_params['back'] = 'tbl_structure.php'; + + // 2. Gets table keys and retains them + // @todo should be: $server->db($db)->table($table)->primary() + $primary = Index::getPrimary($this->table, $this->db); + $columns_with_index = $this->dbi + ->getTable($this->db, $this->table) + ->getColumnsWithIndex( + Index::UNIQUE | Index::INDEX | Index::SPATIAL + | Index::FULLTEXT + ); + $columns_with_unique_index = $this->dbi + ->getTable($this->db, $this->table) + ->getColumnsWithIndex(Index::UNIQUE); + + // 3. Get fields + $fields = (array)$this->dbi->getColumns( + $this->db, $this->table, null, true + ); + + foreach ($fields as $key => $row) { + if ('text' === substr($row['Type'], -4)) { + $fields[$key]['Default'] = stripcslashes(substr($row['Default'], 1, -1)); + } + } + + //display table structure + $this->response->addHTML( + $this->displayStructure( + $cfgRelation, $columns_with_unique_index, $url_params, + $primary, $fields, $columns_with_index + ) + ); + + $this->response->addHTML('
        '); + } + + /** + * Moves columns in the table's structure based on $_REQUEST + * + * @return void + */ + protected function moveColumns() + { + $this->dbi->selectDb($this->db); + + /* + * load the definitions for all columns + */ + $columns = $this->dbi->getColumnsFull($this->db, $this->table); + $column_names = array_keys($columns); + $changes = array(); + + // move columns from first to last + for ($i = 0, $l = count($_POST['move_columns']); $i < $l; $i++) { + $column = $_POST['move_columns'][$i]; + // is this column already correctly placed? + if ($column_names[$i] == $column) { + continue; + } + + // it is not, let's move it to index $i + $data = $columns[$column]; + $extracted_columnspec = Util::extractColumnSpec($data['Type']); + if (isset($data['Extra']) + && $data['Extra'] == 'on update CURRENT_TIMESTAMP' + ) { + $extracted_columnspec['attribute'] = $data['Extra']; + unset($data['Extra']); + } + $current_timestamp = ($data['Type'] == 'timestamp' + || $data['Type'] == 'datetime') + && ($data['Default'] == 'CURRENT_TIMESTAMP' + || $data['Default'] == 'current_timestamp()'); + + if ($data['Null'] === 'YES' && $data['Default'] === null) { + $default_type = 'NULL'; + } elseif ($current_timestamp) { + $default_type = 'CURRENT_TIMESTAMP'; + } elseif ($data['Default'] === null) { + $default_type = 'NONE'; + } else { + $default_type = 'USER_DEFINED'; + } + + $virtual = array( + 'VIRTUAL', 'PERSISTENT', 'VIRTUAL GENERATED', 'STORED GENERATED' + ); + $data['Virtuality'] = ''; + $data['Expression'] = ''; + if (isset($data['Extra']) && in_array($data['Extra'], $virtual)) { + $data['Virtuality'] = str_replace(' GENERATED', '', $data['Extra']); + $expressions = $this->table_obj->getColumnGenerationExpression($column); + $data['Expression'] = $expressions[$column]; + } + + $changes[] = 'CHANGE ' . Table::generateAlter( + $column, + $column, + mb_strtoupper($extracted_columnspec['type']), + $extracted_columnspec['spec_in_brackets'], + $extracted_columnspec['attribute'], + isset($data['Collation']) ? $data['Collation'] : '', + $data['Null'] === 'YES' ? 'NULL' : 'NOT NULL', + $default_type, + $current_timestamp ? '' : $data['Default'], + isset($data['Extra']) && $data['Extra'] !== '' ? $data['Extra'] + : false, + isset($data['COLUMN_COMMENT']) && $data['COLUMN_COMMENT'] !== '' + ? $data['COLUMN_COMMENT'] : false, + $data['Virtuality'], + $data['Expression'], + $i === 0 ? '-first' : $column_names[$i - 1] + ); + // update current column_names array, first delete old position + for ($j = 0, $ll = count($column_names); $j < $ll; $j++) { + if ($column_names[$j] == $column) { + unset($column_names[$j]); + } + } + // insert moved column + array_splice($column_names, $i, 0, $column); + } + if (empty($changes)) { // should never happen + $this->response->setRequestStatus(false); + return; + } + // move columns + $this->dbi->tryQuery( + sprintf( + 'ALTER TABLE %s %s', + Util::backquote($this->table), + implode(', ', $changes) + ) + ); + $tmp_error = $this->dbi->getError(); + if ($tmp_error) { + $this->response->setRequestStatus(false); + $this->response->addJSON('message', Message::error($tmp_error)); + } else { + $message = Message::success( + __('The columns have been moved successfully.') + ); + $this->response->addJSON('message', $message); + $this->response->addJSON('columns', $column_names); + } + } + + /** + * Displays HTML for changing one or more columns + * + * @param array $selected the selected columns + * @param string $action target script to call + * + * @return boolean $regenerate true if error occurred + * + */ + protected function displayHtmlForColumnChange($selected, $action) + { + // $selected comes from mult_submits.inc.php + if (empty($selected)) { + $selected[] = $_REQUEST['field']; + $selected_cnt = 1; + } else { // from a multiple submit + $selected_cnt = count($selected); + } + + /** + * @todo optimize in case of multiple fields to modify + */ + $fields_meta = array(); + for ($i = 0; $i < $selected_cnt; $i++) { + $value = $this->dbi->getColumns( + $this->db, $this->table, $this->dbi->escapeString($selected[$i]), true + ); + if (count($value) == 0) { + $message = Message::error( + __('Failed to get description of column %s!') + ); + $message->addParam($selected[$i]); + $this->response->addHTML($message); + + } else { + $fields_meta[] = $value; + } + } + $num_fields = count($fields_meta); + // set these globals because tbl_columns_definition_form.inc.php + // verifies them + // @todo: refactor tbl_columns_definition_form.inc.php so that it uses + // protected function params + $GLOBALS['action'] = $action; + $GLOBALS['num_fields'] = $num_fields; + + /** + * Form for changing properties. + */ + include_once 'libraries/check_user_privileges.inc.php'; + include 'libraries/tbl_columns_definition_form.inc.php'; + } + + /** + * Displays HTML for partition change + * + * @return string HTML for partition change + */ + protected function displayHtmlForPartitionChange() + { + $partitionDetails = null; + if (! isset($_POST['partition_by'])) { + $partitionDetails = $this->_extractPartitionDetails(); + } + + include 'libraries/tbl_partition_definition.inc.php'; + $this->response->addHTML( + Template::get('table/structure/partition_definition_form') + ->render( + array( + 'db' => $this->db, + 'table' => $this->table, + 'partition_details' => $partitionDetails, + ) + ) + ); + } + + /** + * Extracts partition details from CREATE TABLE statement + * + * @return array[] array of partition details + */ + private function _extractPartitionDetails() + { + $createTable = (new Table($this->table, $this->db))->showCreate(); + if (! $createTable) { + return null; + } + + $parser = new Parser($createTable); + /** + * @var $stmt PhpMyAdmin\SqlParser\Statements\CreateStatement + */ + $stmt = $parser->statements[0]; + + $partitionDetails = array(); + + $partitionDetails['partition_by'] = ''; + $partitionDetails['partition_expr'] = ''; + $partitionDetails['partition_count'] = ''; + + if (! empty($stmt->partitionBy)) { + $openPos = strpos($stmt->partitionBy, "("); + $closePos = strrpos($stmt->partitionBy, ")"); + + $partitionDetails['partition_by'] + = trim(substr($stmt->partitionBy, 0, $openPos)); + $partitionDetails['partition_expr'] + = trim(substr($stmt->partitionBy, $openPos + 1, $closePos - ($openPos + 1))); + if (isset($stmt->partitionsNum)) { + $count = $stmt->partitionsNum; + } else { + $count = count($stmt->partitions); + } + $partitionDetails['partition_count'] = $count; + } + + $partitionDetails['subpartition_by'] = ''; + $partitionDetails['subpartition_expr'] = ''; + $partitionDetails['subpartition_count'] = ''; + + if (! empty($stmt->subpartitionBy)) { + $openPos = strpos($stmt->subpartitionBy, "("); + $closePos = strrpos($stmt->subpartitionBy, ")"); + + $partitionDetails['subpartition_by'] + = trim(substr($stmt->subpartitionBy, 0, $openPos)); + $partitionDetails['subpartition_expr'] + = trim(substr($stmt->subpartitionBy, $openPos + 1, $closePos - ($openPos + 1))); + if (isset($stmt->subpartitionsNum)) { + $count = $stmt->subpartitionsNum; + } else { + $count = count($stmt->partitions[0]->subpartitions); + } + $partitionDetails['subpartition_count'] = $count; + } + + // Only LIST and RANGE type parameters allow subpartitioning + $partitionDetails['can_have_subpartitions'] + = $partitionDetails['partition_count'] > 1 + && ($partitionDetails['partition_by'] == 'RANGE' + || $partitionDetails['partition_by'] == 'RANGE COLUMNS' + || $partitionDetails['partition_by'] == 'LIST' + || $partitionDetails['partition_by'] == 'LIST COLUMNS'); + + // Values are specified only for LIST and RANGE type partitions + $partitionDetails['value_enabled'] = isset($partitionDetails['partition_by']) + && ($partitionDetails['partition_by'] == 'RANGE' + || $partitionDetails['partition_by'] == 'RANGE COLUMNS' + || $partitionDetails['partition_by'] == 'LIST' + || $partitionDetails['partition_by'] == 'LIST COLUMNS'); + + $partitionDetails['partitions'] = array(); + + for ($i = 0; $i < intval($partitionDetails['partition_count']); $i++) { + + if (! isset($stmt->partitions[$i])) { + $partitionDetails['partitions'][$i] = array( + 'name' => 'p' . $i, + 'value_type' => '', + 'value' => '', + 'engine' => '', + 'comment' => '', + 'data_directory' => '', + 'index_directory' => '', + 'max_rows' => '', + 'min_rows' => '', + 'tablespace' => '', + 'node_group' => '', + ); + } else { + $p = $stmt->partitions[$i]; + $type = $p->type; + $expr = trim($p->expr, '()'); + if ($expr == 'MAXVALUE') { + $type .= ' MAXVALUE'; + $expr = ''; + } + $partitionDetails['partitions'][$i] = array( + 'name' => $p->name, + 'value_type' => $type, + 'value' => $expr, + 'engine' => $p->options->has('ENGINE', true), + 'comment' => trim($p->options->has('COMMENT', true), "'"), + 'data_directory' => trim($p->options->has('DATA DIRECTORY', true), "'"), + 'index_directory' => trim($p->options->has('INDEX_DIRECTORY', true), "'"), + 'max_rows' => $p->options->has('MAX_ROWS', true), + 'min_rows' => $p->options->has('MIN_ROWS', true), + 'tablespace' => $p->options->has('TABLESPACE', true), + 'node_group' => $p->options->has('NODEGROUP', true), + ); + } + + $partition =& $partitionDetails['partitions'][$i]; + $partition['prefix'] = 'partitions[' . $i . ']'; + + if ($partitionDetails['subpartition_count'] > 1) { + $partition['subpartition_count'] = $partitionDetails['subpartition_count']; + $partition['subpartitions'] = array(); + + for ($j = 0; $j < intval($partitionDetails['subpartition_count']); $j++) { + if (! isset($stmt->partitions[$i]->subpartitions[$j])) { + $partition['subpartitions'][$j] = array( + 'name' => $partition['name'] . '_s' . $j, + 'engine' => '', + 'comment' => '', + 'data_directory' => '', + 'index_directory' => '', + 'max_rows' => '', + 'min_rows' => '', + 'tablespace' => '', + 'node_group' => '', + ); + } else { + $sp = $stmt->partitions[$i]->subpartitions[$j]; + $partition['subpartitions'][$j] = array( + 'name' => $sp->name, + 'engine' => $sp->options->has('ENGINE', true), + 'comment' => trim($sp->options->has('COMMENT', true), "'"), + 'data_directory' => trim($sp->options->has('DATA DIRECTORY', true), "'"), + 'index_directory' => trim($sp->options->has('INDEX_DIRECTORY', true), "'"), + 'max_rows' => $sp->options->has('MAX_ROWS', true), + 'min_rows' => $sp->options->has('MIN_ROWS', true), + 'tablespace' => $sp->options->has('TABLESPACE', true), + 'node_group' => $sp->options->has('NODEGROUP', true), + ); + } + + $subpartition =& $partition['subpartitions'][$j]; + $subpartition['prefix'] = 'partitions[' . $i . ']' + . '[subpartitions][' . $j . ']'; + } + } + } + + return $partitionDetails; + } + + /** + * Update the table's partitioning based on $_REQUEST + * + * @return void + */ + protected function updatePartitioning() + { + $sql_query = "ALTER TABLE " . Util::backquote($this->table) . " " + . $this->createAddField->getPartitionsDefinition(); + + // Execute alter query + $result = $this->dbi->tryQuery($sql_query); + + if ($result !== false) { + $message = Message::success( + __('Table %1$s has been altered successfully.') + ); + $message->addParam($this->table); + $this->response->addHTML( + Util::getMessage($message, $sql_query, 'success') + ); + } else { + $this->response->setRequestStatus(false); + $this->response->addJSON( + 'message', + Message::rawError( + __('Query error') . ':
        ' . $this->dbi->getError() + ) + ); + } + } + + /** + * Function to get the type of command for multiple field handling + * + * @return string + */ + protected function getMultipleFieldCommandType() + { + $types = array( + 'change', 'drop', 'primary', + 'index', 'unique', 'spatial', + 'fulltext', 'browse' + ); + + foreach ($types as $type) { + if (isset($_POST['submit_mult_' . $type . '_x'])) { + return $type; + } + } + + if (isset($_POST['submit_mult'])) { + return $_POST['submit_mult']; + } elseif (isset($_POST['mult_btn']) + && $_POST['mult_btn'] == __('Yes') + ) { + if (isset($_POST['selected'])) { + $_POST['selected_fld'] = $_POST['selected']; + } + return 'row_delete'; + } + + return null; + } + + /** + * Function to display table browse for selected columns + * + * @param string $goto goto page url + * @param string $pmaThemeImage URI of the pma theme image + * + * @return void + */ + protected function displayTableBrowseForSelectedColumns($goto, $pmaThemeImage) + { + $GLOBALS['active_page'] = 'sql.php'; + $fields = array(); + foreach ($_POST['selected_fld'] as $sval) { + $fields[] = Util::backquote($sval); + } + $sql_query = sprintf( + 'SELECT %s FROM %s.%s', + implode(', ', $fields), + Util::backquote($this->db), + Util::backquote($this->table) + ); + + // Parse and analyze the query + $db = &$this->db; + list( + $analyzed_sql_results, + $db, + ) = ParseAnalyze::sqlQuery($sql_query, $db); + // @todo: possibly refactor + extract($analyzed_sql_results); + + $sql = new Sql(); + $this->response->addHTML( + $sql->executeQueryAndGetQueryResponse( + isset($analyzed_sql_results) ? $analyzed_sql_results : '', + false, // is_gotofile + $this->db, // db + $this->table, // table + null, // find_real_end + null, // sql_query_for_bookmark + null, // extra_data + null, // message_to_show + null, // message + null, // sql_data + $goto, // goto + $pmaThemeImage, // pmaThemeImage + null, // disp_query + null, // disp_message + null, // query_type + $sql_query, // sql_query + null, // selectedTables + null // complete_query + ) + ); + } + + /** + * Update the table's structure based on $_REQUEST + * + * @return boolean $regenerate true if error occurred + * + */ + protected function updateColumns() + { + $err_url = 'tbl_structure.php' . Url::getCommon( + array( + 'db' => $this->db, 'table' => $this->table + ) + ); + $regenerate = false; + $field_cnt = count($_POST['field_name']); + $changes = array(); + $adjust_privileges = array(); + + for ($i = 0; $i < $field_cnt; $i++) { + if (!$this->columnNeedsAlterTable($i)) { + continue; + } + + $changes[] = 'CHANGE ' . Table::generateAlter( + Util::getValueByKey($_POST, "field_orig.${i}", ''), + $_POST['field_name'][$i], + $_POST['field_type'][$i], + $_POST['field_length'][$i], + $_POST['field_attribute'][$i], + Util::getValueByKey($_POST, "field_collation.${i}", ''), + Util::getValueByKey($_POST, "field_null.${i}", 'NO'), + $_POST['field_default_type'][$i], + $_POST['field_default_value'][$i], + Util::getValueByKey($_POST, "field_extra.${i}", false), + Util::getValueByKey($_POST, "field_comments.${i}", ''), + Util::getValueByKey($_POST, "field_virtuality.${i}", ''), + Util::getValueByKey($_POST, "field_expression.${i}", ''), + Util::getValueByKey($_POST, "field_move_to.${i}", '') + ); + + // find the remembered sort expression + $sorted_col = $this->table_obj->getUiProp( + Table::PROP_SORTED_COLUMN + ); + // if the old column name is part of the remembered sort expression + if (mb_strpos( + $sorted_col, + Util::backquote($_POST['field_orig'][$i]) + ) !== false) { + // delete the whole remembered sort expression + $this->table_obj->removeUiProp(Table::PROP_SORTED_COLUMN); + } + + if (isset($_POST['field_adjust_privileges'][$i]) + && ! empty($_POST['field_adjust_privileges'][$i]) + && $_POST['field_orig'][$i] != $_POST['field_name'][$i] + ) { + $adjust_privileges[$_POST['field_orig'][$i]] + = $_POST['field_name'][$i]; + } + } // end for + + if (count($changes) > 0 || isset($_POST['preview_sql'])) { + // Builds the primary keys statements and updates the table + $key_query = ''; + /** + * this is a little bit more complex + * + * @todo if someone selects A_I when altering a column we need to check: + * - no other column with A_I + * - the column has an index, if not create one + * + */ + + // To allow replication, we first select the db to use + // and then run queries on this db. + if (!$this->dbi->selectDb($this->db)) { + Util::mysqlDie( + $this->dbi->getError(), + 'USE ' . Util::backquote($this->db) . ';', + false, + $err_url + ); + } + $sql_query = 'ALTER TABLE ' . Util::backquote($this->table) . ' '; + $sql_query .= implode(', ', $changes) . $key_query; + $sql_query .= ';'; + + // If there is a request for SQL previewing. + if (isset($_POST['preview_sql'])) { + Core::previewSQL(count($changes) > 0 ? $sql_query : ''); + } + + $columns_with_index = $this->dbi + ->getTable($this->db, $this->table) + ->getColumnsWithIndex( + Index::PRIMARY | Index::UNIQUE | Index::INDEX + | Index::SPATIAL | Index::FULLTEXT + ); + + $changedToBlob = array(); + // While changing the Column Collation + // First change to BLOB + for ($i = 0; $i < $field_cnt; $i++ ) { + if (isset($_POST['field_collation'][$i]) + && isset($_POST['field_collation_orig'][$i]) + && $_POST['field_collation'][$i] !== $_POST['field_collation_orig'][$i] + && ! in_array($_POST['field_orig'][$i], $columns_with_index) + ) { + $secondary_query = 'ALTER TABLE ' . Util::backquote( + $this->table + ) + . ' CHANGE ' . Util::backquote( + $_POST['field_orig'][$i] + ) + . ' ' . Util::backquote($_POST['field_orig'][$i]) + . ' BLOB'; + + if (isset($_POST['field_virtuality'][$i]) + && isset($_POST['field_expression'][$i])) { + if ($_POST['field_virtuality'][$i]) { + $secondary_query .= ' AS (' . $_POST['field_expression'][$i] . ') ' + . $_POST['field_virtuality'][$i]; + } + } + + $secondary_query .= ';'; + + $this->dbi->query($secondary_query); + $changedToBlob[$i] = true; + } else { + $changedToBlob[$i] = false; + } + } + + // Then make the requested changes + $result = $this->dbi->tryQuery($sql_query); + + if ($result !== false) { + $changed_privileges = $this->adjustColumnPrivileges( + $adjust_privileges + ); + + if ($changed_privileges) { + $message = Message::success( + __( + 'Table %1$s has been altered successfully. Privileges ' . + 'have been adjusted.' + ) + ); + } else { + $message = Message::success( + __('Table %1$s has been altered successfully.') + ); + } + $message->addParam($this->table); + + $this->response->addHTML( + Util::getMessage($message, $sql_query, 'success') + ); + } else { + // An error happened while inserting/updating a table definition + + // Save the Original Error + $orig_error = $this->dbi->getError(); + $changes_revert = array(); + + // Change back to Original Collation and data type + for ($i = 0; $i < $field_cnt; $i++) { + if ($changedToBlob[$i]) { + $changes_revert[] = 'CHANGE ' . Table::generateAlter( + Util::getValueByKey($_POST, "field_orig.${i}", ''), + $_POST['field_name'][$i], + $_POST['field_type_orig'][$i], + $_POST['field_length_orig'][$i], + $_POST['field_attribute_orig'][$i], + Util::getValueByKey($_POST, "field_collation_orig.${i}", ''), + Util::getValueByKey($_POST, "field_null_orig.${i}", 'NO'), + $_POST['field_default_type_orig'][$i], + $_POST['field_default_value_orig'][$i], + Util::getValueByKey($_POST, "field_extra_orig.${i}", false), + Util::getValueByKey($_POST, "field_comments_orig.${i}", ''), + Util::getValueByKey($_POST, "field_virtuality_orig.${i}", ''), + Util::getValueByKey($_POST, "field_expression_orig.${i}", ''), + Util::getValueByKey($_POST, "field_move_to_orig.${i}", '') + ); + } + } + + $revert_query = 'ALTER TABLE ' . Util::backquote($this->table) + . ' '; + $revert_query .= implode(', ', $changes_revert) . ''; + $revert_query .= ';'; + + // Column reverted back to original + $this->dbi->query($revert_query); + + $this->response->setRequestStatus(false); + $this->response->addJSON( + 'message', + Message::rawError( + __('Query error') . ':
        ' . $orig_error + ) + ); + $regenerate = true; + } + } + + // update field names in relation + if (isset($_POST['field_orig']) && is_array($_POST['field_orig'])) { + foreach ($_POST['field_orig'] as $fieldindex => $fieldcontent) { + if ($_POST['field_name'][$fieldindex] != $fieldcontent) { + $this->relation->renameField( + $this->db, $this->table, $fieldcontent, + $_POST['field_name'][$fieldindex] + ); + } + } + } + + // update mime types + if (isset($_POST['field_mimetype']) + && is_array($_POST['field_mimetype']) + && $GLOBALS['cfg']['BrowseMIME'] + ) { + foreach ($_POST['field_mimetype'] as $fieldindex => $mimetype) { + if (isset($_POST['field_name'][$fieldindex]) + && strlen($_POST['field_name'][$fieldindex]) > 0 + ) { + Transformations::setMIME( + $this->db, $this->table, + $_POST['field_name'][$fieldindex], + $mimetype, + $_POST['field_transformation'][$fieldindex], + $_POST['field_transformation_options'][$fieldindex], + $_POST['field_input_transformation'][$fieldindex], + $_POST['field_input_transformation_options'][$fieldindex] + ); + } + } + } + return $regenerate; + } + + /** + * Adjusts the Privileges for all the columns whose names have changed + * + * @param array $adjust_privileges assoc array of old col names mapped to new + * cols + * + * @return boolean $changed boolean whether at least one column privileges + * adjusted + */ + protected function adjustColumnPrivileges(array $adjust_privileges) + { + $changed = false; + + if (Util::getValueByKey($GLOBALS, 'col_priv', false) + && Util::getValueByKey($GLOBALS, 'is_reload_priv', false) + ) { + $this->dbi->selectDb('mysql'); + + // For Column specific privileges + foreach ($adjust_privileges as $oldCol => $newCol) { + + $this->dbi->query( + sprintf( + 'UPDATE %s SET Column_name = "%s" + WHERE Db = "%s" + AND Table_name = "%s" + AND Column_name = "%s";', + Util::backquote('columns_priv'), + $newCol, $this->db, $this->table, $oldCol + ) + ); + + // i.e. if atleast one column privileges adjusted + $changed = true; + } + + if ($changed) { + // Finally FLUSH the new privileges + $this->dbi->query("FLUSH PRIVILEGES;"); + } + } + + return $changed; + } + + /** + * Verifies if some elements of a column have changed + * + * @param integer $i column index in the request + * + * @return boolean $alterTableNeeded true if we need to generate ALTER TABLE + * + */ + protected function columnNeedsAlterTable($i) + { + // these two fields are checkboxes so might not be part of the + // request; therefore we define them to avoid notices below + if (! isset($_POST['field_null'][$i])) { + $_POST['field_null'][$i] = 'NO'; + } + if (! isset($_POST['field_extra'][$i])) { + $_POST['field_extra'][$i] = ''; + } + + // field_name does not follow the convention (corresponds to field_orig) + if ($_POST['field_name'][$i] != $_POST['field_orig'][$i]) { + return true; + } + + $fields = array( + 'field_attribute', 'field_collation', 'field_comments', + 'field_default_value', 'field_default_type', 'field_extra', + 'field_length', 'field_null', 'field_type' + ); + foreach ($fields as $field) { + if ($_POST[$field][$i] != $_POST[$field . '_orig'][$i]) { + return true; + } + } + return !empty($_POST['field_move_to'][$i]); + } + + /** + * Displays the table structure ('show table' works correct since 3.23.03) + * + * @param array $cfgRelation current relation parameters + * @param array $columns_with_unique_index Columns with unique index + * @param mixed $url_params Contains an associative + * array with url params + * @param Index|false $primary_index primary index or false if + * no one exists + * @param array $fields Fields + * @param array $columns_with_index Columns with index + * + * @return string + */ + protected function displayStructure( + array $cfgRelation, array $columns_with_unique_index, $url_params, + $primary_index, array $fields, array $columns_with_index + ) { + // prepare comments + $comments_map = array(); + $mime_map = array(); + + if ($GLOBALS['cfg']['ShowPropertyComments']) { + $comments_map = $this->relation->getComments($this->db, $this->table); + if ($cfgRelation['mimework'] && $GLOBALS['cfg']['BrowseMIME']) { + $mime_map = Transformations::getMIME($this->db, $this->table, true); + } + } + $centralColumns = new CentralColumns($GLOBALS['dbi']); + $central_list = $centralColumns->getFromTable( + $this->db, + $this->table + ); + $columns_list = array(); + + $titles = array( + 'Change' => Util::getIcon('b_edit', __('Change')), + 'Drop' => Util::getIcon('b_drop', __('Drop')), + 'NoDrop' => Util::getIcon('b_drop', __('Drop')), + 'Primary' => Util::getIcon('b_primary', __('Primary')), + 'Index' => Util::getIcon('b_index', __('Index')), + 'Unique' => Util::getIcon('b_unique', __('Unique')), + 'Spatial' => Util::getIcon('b_spatial', __('Spatial')), + 'IdxFulltext' => Util::getIcon('b_ftext', __('Fulltext')), + 'NoPrimary' => Util::getIcon('bd_primary', __('Primary')), + 'NoIndex' => Util::getIcon('bd_index', __('Index')), + 'NoUnique' => Util::getIcon('bd_unique', __('Unique')), + 'NoSpatial' => Util::getIcon('bd_spatial', __('Spatial')), + 'NoIdxFulltext' => Util::getIcon('bd_ftext', __('Fulltext')), + 'DistinctValues' => Util::getIcon('b_browse', __('Distinct values')), + ); + + $edit_view_url = ''; + if ($this->_tbl_is_view && ! $this->_db_is_system_schema) { + $edit_view_url = Url::getCommon( + array('db' => $this->db, 'table' => $this->table) + ); + } + + /** + * Displays Space usage and row statistics + */ + // BEGIN - Calc Table Space + // Get valid statistics whatever is the table type + if ($GLOBALS['cfg']['ShowStats']) { + //get table stats in HTML format + $tablestats = $this->getTableStats(); + //returning the response in JSON format to be used by Ajax + $this->response->addJSON('tableStat', $tablestats); + } + // END - Calc Table Space + + $hideStructureActions = false; + if ($GLOBALS['cfg']['HideStructureActions'] === true) { + $hideStructureActions = true; + } + + return Template::get('table/structure/display_structure')->render( + array( + 'hide_structure_actions' => $hideStructureActions, + 'db' => $this->db, + 'table' => $this->table, + 'db_is_system_schema' => $this->_db_is_system_schema, + 'tbl_is_view' => $this->_tbl_is_view, + 'mime_map' => $mime_map, + 'url_query' => $this->_url_query, + 'titles' => $titles, + 'tbl_storage_engine' => $this->_tbl_storage_engine, + 'primary' => $primary_index, + 'columns_with_unique_index' => $columns_with_unique_index, + 'edit_view_url' => $edit_view_url, + 'columns_list' => $columns_list, + 'table_stats' => isset($tablestats) ? $tablestats : null, + 'fields' => $fields, + 'columns_with_index' => $columns_with_index, + 'central_list' => $central_list, + 'comments_map' => $comments_map, + 'browse_mime' => $GLOBALS['cfg']['BrowseMIME'], + 'show_column_comments' => $GLOBALS['cfg']['ShowColumnComments'], + 'show_stats' => $GLOBALS['cfg']['ShowStats'], + 'relation_commwork' => $GLOBALS['cfgRelation']['commwork'], + 'relation_mimework' => $GLOBALS['cfgRelation']['mimework'], + 'central_columns_work' => $GLOBALS['cfgRelation']['centralcolumnswork'], + 'mysql_int_version' => $GLOBALS['dbi']->getVersion(), + 'is_mariadb' => $GLOBALS['dbi']->isMariaDB(), + 'pma_theme_image' => $GLOBALS['pmaThemeImage'], + 'text_dir' => $GLOBALS['text_dir'], + 'is_active' => Tracker::isActive(), + 'have_partitioning' => Partition::havePartitioning(), + 'partition_names' => Partition::getPartitionNames($this->db, $this->table), + ) + ); + } + + /** + * Get HTML snippet for display table statistics + * + * @return string $html_output + */ + protected function getTableStats() + { + if (empty($this->_showtable)) { + $this->_showtable = $this->dbi->getTable( + $this->db, $this->table + )->getStatusInfo(null, true); + } + + if (empty($this->_showtable['Data_length'])) { + $this->_showtable['Data_length'] = 0; + } + if (empty($this->_showtable['Index_length'])) { + $this->_showtable['Index_length'] = 0; + } + + $is_innodb = (isset($this->_showtable['Type']) + && $this->_showtable['Type'] == 'InnoDB'); + + $mergetable = $this->table_obj->isMerge(); + + // this is to display for example 261.2 MiB instead of 268k KiB + $max_digits = 3; + $decimals = 1; + list($data_size, $data_unit) = Util::formatByteDown( + $this->_showtable['Data_length'], $max_digits, $decimals + ); + if ($mergetable == false) { + list($index_size, $index_unit) = Util::formatByteDown( + $this->_showtable['Index_length'], $max_digits, $decimals + ); + } + // InnoDB returns a huge value in Data_free, do not use it + if (! $is_innodb && isset($this->_showtable['Data_free']) + && $this->_showtable['Data_free'] > 0 + ) { + list($free_size, $free_unit) = Util::formatByteDown( + $this->_showtable['Data_free'], $max_digits, $decimals + ); + list($effect_size, $effect_unit) = Util::formatByteDown( + $this->_showtable['Data_length'] + + $this->_showtable['Index_length'] + - $this->_showtable['Data_free'], + $max_digits, $decimals + ); + } else { + list($effect_size, $effect_unit) = Util::formatByteDown( + $this->_showtable['Data_length'] + + $this->_showtable['Index_length'], + $max_digits, $decimals + ); + } + list($tot_size, $tot_unit) = Util::formatByteDown( + $this->_showtable['Data_length'] + $this->_showtable['Index_length'], + $max_digits, $decimals + ); + if ($this->_table_info_num_rows > 0) { + list($avg_size, $avg_unit) = Util::formatByteDown( + ($this->_showtable['Data_length'] + + $this->_showtable['Index_length']) + / $this->_showtable['Rows'], + 6, + 1 + ); + } else { + $avg_size = $avg_unit = ''; + } + + return Template::get('table/structure/display_table_stats')->render( + array( + 'showtable' => $this->_showtable, + 'table_info_num_rows' => $this->_table_info_num_rows, + 'tbl_is_view' => $this->_tbl_is_view, + 'db_is_system_schema' => $this->_db_is_system_schema, + 'tbl_storage_engine' => $this->_tbl_storage_engine, + 'url_query' => $this->_url_query, + 'tbl_collation' => $this->_tbl_collation, + 'is_innodb' => $is_innodb, + 'mergetable' => $mergetable, + 'avg_size' => isset($avg_size) ? $avg_size : null, + 'avg_unit' => isset($avg_unit) ? $avg_unit : null, + 'data_size' => $data_size, + 'data_unit' => $data_unit, + 'index_size' => isset($index_size) ? $index_size : null, + 'index_unit' => isset($index_unit) ? $index_unit : null, + 'free_size' => isset($free_size) ? $free_size : null, + 'free_unit' => isset($free_unit) ? $free_unit : null, + 'effect_size' => $effect_size, + 'effect_unit' => $effect_unit, + 'tot_size' => $tot_size, + 'tot_unit' => $tot_unit, + 'table' => $GLOBALS['table'] + ) + ); + } + + /** + * Gets table primary key + * + * @return string + */ + protected function getKeyForTablePrimary() + { + $this->dbi->selectDb($this->db); + $result = $this->dbi->query( + 'SHOW KEYS FROM ' . Util::backquote($this->table) . ';' + ); + $primary = ''; + while ($row = $this->dbi->fetchAssoc($result)) { + // Backups the list of primary keys + if ($row['Key_name'] == 'PRIMARY') { + $primary .= $row['Column_name'] . ', '; + } + } // end while + $this->dbi->freeResult($result); + + return $primary; + } + + /** + * Get List of information for Submit Mult + * + * @param string $submit_mult mult_submit type + * @param array $selected the selected columns + * @param string $action action type + * + * @return array + */ + protected function getDataForSubmitMult($submit_mult, $selected, $action) + { + $centralColumns = new CentralColumns($GLOBALS['dbi']); + $what = null; + $query_type = null; + $is_unset_submit_mult = false; + $mult_btn = null; + $centralColsError = null; + switch ($submit_mult) { + case 'drop': + $what = 'drop_fld'; + break; + case 'primary': + // Gets table primary key + $primary = $this->getKeyForTablePrimary(); + if (empty($primary)) { + // no primary key, so we can safely create new + $is_unset_submit_mult = true; + $query_type = 'primary_fld'; + $mult_btn = __('Yes'); + } else { + // primary key exists, so lets as user + $what = 'primary_fld'; + } + break; + case 'index': + $is_unset_submit_mult = true; + $query_type = 'index_fld'; + $mult_btn = __('Yes'); + break; + case 'unique': + $is_unset_submit_mult = true; + $query_type = 'unique_fld'; + $mult_btn = __('Yes'); + break; + case 'spatial': + $is_unset_submit_mult = true; + $query_type = 'spatial_fld'; + $mult_btn = __('Yes'); + break; + case 'ftext': + $is_unset_submit_mult = true; + $query_type = 'fulltext_fld'; + $mult_btn = __('Yes'); + break; + case 'add_to_central_columns': + $centralColsError = $centralColumns->syncUniqueColumns( + $selected, + false + ); + break; + case 'remove_from_central_columns': + $centralColsError = $centralColumns->deleteColumnsFromList( + $selected, + false + ); + break; + case 'change': + $this->displayHtmlForColumnChange($selected, $action); + // execution stops here but PhpMyAdmin\Response correctly finishes + // the rendering + exit; + case 'browse': + // this should already be handled by tbl_structure.php + } + + return array( + $what, $query_type, $is_unset_submit_mult, $mult_btn, + $centralColsError + ); + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Controllers/TableController.php b/php/apps/phpmyadmin49/libraries/classes/Controllers/TableController.php new file mode 100644 index 00000000..60651ab8 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Controllers/TableController.php @@ -0,0 +1,40 @@ +db = $db; + $this->table = $table; + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Core.php b/php/apps/phpmyadmin49/libraries/classes/Core.php new file mode 100644 index 00000000..0d5a7ed6 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Core.php @@ -0,0 +1,1320 @@ + + * // $_REQUEST['db'] not set + * echo Core::ifSetOr($_REQUEST['db'], ''); // '' + * // $_POST['sql_query'] not set + * echo Core::ifSetOr($_POST['sql_query']); // null + * // $cfg['EnableFoo'] not set + * echo Core::ifSetOr($cfg['EnableFoo'], false, 'boolean'); // false + * echo Core::ifSetOr($cfg['EnableFoo']); // null + * // $cfg['EnableFoo'] set to 1 + * echo Core::ifSetOr($cfg['EnableFoo'], false, 'boolean'); // false + * echo Core::ifSetOr($cfg['EnableFoo'], false, 'similar'); // 1 + * echo Core::ifSetOr($cfg['EnableFoo'], false); // 1 + * // $cfg['EnableFoo'] set to true + * echo Core::ifSetOr($cfg['EnableFoo'], false, 'boolean'); // true + * + * + * @param mixed &$var param to check + * @param mixed $default default value + * @param mixed $type var type or array of values to check against $var + * + * @return mixed $var or $default + * + * @see self::isValid() + */ + public static function ifSetOr(&$var, $default = null, $type = 'similar') + { + if (! self::isValid($var, $type, $default)) { + return $default; + } + + return $var; + } + + /** + * checks given $var against $type or $compare + * + * $type can be: + * - false : no type checking + * - 'scalar' : whether type of $var is integer, float, string or boolean + * - 'numeric' : whether type of $var is any number representation + * - 'length' : whether type of $var is scalar with a string length > 0 + * - 'similar' : whether type of $var is similar to type of $compare + * - 'equal' : whether type of $var is identical to type of $compare + * - 'identical' : whether $var is identical to $compare, not only the type! + * - or any other valid PHP variable type + * + * + * // $_REQUEST['doit'] = true; + * Core::isValid($_REQUEST['doit'], 'identical', 'true'); // false + * // $_REQUEST['doit'] = 'true'; + * Core::isValid($_REQUEST['doit'], 'identical', 'true'); // true + * + * + * NOTE: call-by-reference is used to not get NOTICE on undefined vars, + * but the var is not altered inside this function, also after checking a var + * this var exists nut is not set, example: + * + * // $var is not set + * isset($var); // false + * functionCallByReference($var); // false + * isset($var); // true + * functionCallByReference($var); // true + * + * + * to avoid this we set this var to null if not isset + * + * @param mixed &$var variable to check + * @param mixed $type var type or array of valid values to check against $var + * @param mixed $compare var to compare with $var + * + * @return boolean whether valid or not + * + * @todo add some more var types like hex, bin, ...? + * @see https://secure.php.net/gettype + */ + public static function isValid(&$var, $type = 'length', $compare = null) + { + if (! isset($var)) { + // var is not even set + return false; + } + + if ($type === false) { + // no vartype requested + return true; + } + + if (is_array($type)) { + return in_array($var, $type); + } + + // allow some aliases of var types + $type = strtolower($type); + switch ($type) { + case 'identic' : + $type = 'identical'; + break; + case 'len' : + $type = 'length'; + break; + case 'bool' : + $type = 'boolean'; + break; + case 'float' : + $type = 'double'; + break; + case 'int' : + $type = 'integer'; + break; + case 'null' : + $type = 'NULL'; + break; + } + + if ($type === 'identical') { + return $var === $compare; + } + + // whether we should check against given $compare + if ($type === 'similar') { + switch (gettype($compare)) { + case 'string': + case 'boolean': + $type = 'scalar'; + break; + case 'integer': + case 'double': + $type = 'numeric'; + break; + default: + $type = gettype($compare); + } + } elseif ($type === 'equal') { + $type = gettype($compare); + } + + // do the check + if ($type === 'length' || $type === 'scalar') { + $is_scalar = is_scalar($var); + if ($is_scalar && $type === 'length') { + return strlen($var) > 0; + } + return $is_scalar; + } + + if ($type === 'numeric') { + return is_numeric($var); + } + + return gettype($var) === $type; + } + + /** + * Removes insecure parts in a path; used before include() or + * require() when a part of the path comes from an insecure source + * like a cookie or form. + * + * @param string $path The path to check + * + * @return string The secured path + * + * @access public + */ + public static function securePath($path) + { + // change .. to . + $path = preg_replace('@\.\.*@', '.', $path); + + return $path; + } // end function + + /** + * displays the given error message on phpMyAdmin error page in foreign language, + * ends script execution and closes session + * + * loads language file if not loaded already + * + * @param string $error_message the error message or named error message + * @param string|array $message_args arguments applied to $error_message + * + * @return void + */ + public static function fatalError($error_message, $message_args = null) { + /* Use format string if applicable */ + if (is_string($message_args)) { + $error_message = sprintf($error_message, $message_args); + } elseif (is_array($message_args)) { + $error_message = vsprintf($error_message, $message_args); + } + + /* + * Avoid using Response class as config does not have to be loaded yet + * (this can happen on early fatal error) + */ + if (isset($GLOBALS['dbi']) && !is_null($GLOBALS['dbi']) && isset($GLOBALS['PMA_Config']) && $GLOBALS['PMA_Config']->get('is_setup') === false && Response::getInstance()->isAjax()) { + $response = Response::getInstance(); + $response->setRequestStatus(false); + $response->addJSON('message', Message::error($error_message)); + } elseif (! empty($_REQUEST['ajax_request'])) { + // Generate JSON manually + self::headerJSON(); + echo json_encode( + array( + 'success' => false, + 'message' => Message::error($error_message)->getDisplay(), + ) + ); + } else { + $error_message = strtr($error_message, array('
        ' => '[br]')); + $error_header = __('Error'); + $lang = isset($GLOBALS['lang']) ? $GLOBALS['lang'] : 'en'; + $dir = isset($GLOBALS['text_dir']) ? $GLOBALS['text_dir'] : 'ltr'; + + // Displays the error message + include './libraries/error.inc.php'; + } + if (! defined('TESTSUITE')) { + exit; + } + } + + /** + * Returns a link to the PHP documentation + * + * @param string $target anchor in documentation + * + * @return string the URL + * + * @access public + */ + public static function getPHPDocLink($target) + { + /* List of PHP documentation translations */ + $php_doc_languages = array( + 'pt_BR', 'zh', 'fr', 'de', 'it', 'ja', 'pl', 'ro', 'ru', 'fa', 'es', 'tr' + ); + + $lang = 'en'; + if (in_array($GLOBALS['lang'], $php_doc_languages)) { + $lang = $GLOBALS['lang']; + } + + return self::linkURL('https://secure.php.net/manual/' . $lang . '/' . $target); + } + + /** + * Warn or fail on missing extension. + * + * @param string $extension Extension name + * @param bool $fatal Whether the error is fatal. + * @param string $extra Extra string to append to message. + * + * @return void + */ + public static function warnMissingExtension($extension, $fatal = false, $extra = '') + { + /* Gettext does not have to be loaded yet here */ + if (function_exists('__')) { + $message = __( + 'The %s extension is missing. Please check your PHP configuration.' + ); + } else { + $message + = 'The %s extension is missing. Please check your PHP configuration.'; + } + $doclink = self::getPHPDocLink('book.' . $extension . '.php'); + $message = sprintf( + $message, + '[a@' . $doclink . '@Documentation][em]' . $extension . '[/em][/a]' + ); + if ($extra != '') { + $message .= ' ' . $extra; + } + if ($fatal) { + self::fatalError($message); + return; + } + + $GLOBALS['error_handler']->addError( + $message, + E_USER_WARNING, + '', + '', + false + ); + } + + /** + * returns count of tables in given db + * + * @param string $db database to count tables for + * + * @return integer count of tables in $db + */ + public static function getTableCount($db) + { + $tables = $GLOBALS['dbi']->tryQuery( + 'SHOW TABLES FROM ' . Util::backquote($db) . ';', + DatabaseInterface::CONNECT_USER, + DatabaseInterface::QUERY_STORE + ); + if ($tables) { + $num_tables = $GLOBALS['dbi']->numRows($tables); + $GLOBALS['dbi']->freeResult($tables); + } else { + $num_tables = 0; + } + + return $num_tables; + } + + /** + * Converts numbers like 10M into bytes + * Used with permission from Moodle (https://moodle.org) by Martin Dougiamas + * (renamed with PMA prefix to avoid double definition when embedded + * in Moodle) + * + * @param string|int $size size (Default = 0) + * + * @return integer $size + */ + public static function getRealSize($size = 0) + { + if (! $size) { + return 0; + } + + $binaryprefixes = array( + 'T' => 1099511627776, + 't' => 1099511627776, + 'G' => 1073741824, + 'g' => 1073741824, + 'M' => 1048576, + 'm' => 1048576, + 'K' => 1024, + 'k' => 1024, + ); + + if (preg_match('/^([0-9]+)([KMGT])/i', $size, $matches)) { + return $matches[1] * $binaryprefixes[$matches[2]]; + } + + return (int) $size; + } // end getRealSize() + + /** + * boolean phpMyAdmin.Core::checkPageValidity(string &$page, array $whitelist) + * + * checks given $page against given $whitelist and returns true if valid + * it optionally ignores query parameters in $page (script.php?ignored) + * + * @param string &$page page to check + * @param array $whitelist whitelist to check page against + * @param boolean $include whether the page is going to be included + * + * @return boolean whether $page is valid or not (in $whitelist or not) + */ + public static function checkPageValidity(&$page, array $whitelist = [], $include = false) + { + if (empty($whitelist)) { + $whitelist = self::$goto_whitelist; + } + if (! isset($page) || !is_string($page)) { + return false; + } + + if (in_array($page, $whitelist)) { + return true; + } + if ($include) { + return false; + } + + $_page = mb_substr( + $page, + 0, + mb_strpos($page . '?', '?') + ); + if (in_array($_page, $whitelist)) { + return true; + } + + $_page = urldecode($page); + $_page = mb_substr( + $_page, + 0, + mb_strpos($_page . '?', '?') + ); + if (in_array($_page, $whitelist)) { + return true; + } + + return false; + } + + /** + * tries to find the value for the given environment variable name + * + * searches in $_SERVER, $_ENV then tries getenv() and apache_getenv() + * in this order + * + * @param string $var_name variable name + * + * @return string value of $var or empty string + */ + public static function getenv($var_name) + { + if (isset($_SERVER[$var_name])) { + return $_SERVER[$var_name]; + } + + if (isset($_ENV[$var_name])) { + return $_ENV[$var_name]; + } + + if (getenv($var_name)) { + return getenv($var_name); + } + + if (function_exists('apache_getenv') + && apache_getenv($var_name, true) + ) { + return apache_getenv($var_name, true); + } + + return ''; + } + + /** + * Send HTTP header, taking IIS limits into account (600 seems ok) + * + * @param string $uri the header to send + * @param bool $use_refresh whether to use Refresh: header when running on IIS + * + * @return void + */ + public static function sendHeaderLocation($uri, $use_refresh = false) + { + if ($GLOBALS['PMA_Config']->get('PMA_IS_IIS') && mb_strlen($uri) > 600) { + Response::getInstance()->disable(); + + echo Template::get('header_location') + ->render(array('uri' => $uri)); + + return; + } + + /* + * Avoid relative path redirect problems in case user entered URL + * like /phpmyadmin/index.php/ which some web servers happily accept. + */ + if ($uri[0] == '.') { + $uri = $GLOBALS['PMA_Config']->getRootPath() . substr($uri, 2); + } + + $response = Response::getInstance(); + + session_write_close(); + if ($response->headersSent()) { + trigger_error( + 'Core::sendHeaderLocation called when headers are already sent!', + E_USER_ERROR + ); + } + // bug #1523784: IE6 does not like 'Refresh: 0', it + // results in a blank page + // but we need it when coming from the cookie login panel) + if ($GLOBALS['PMA_Config']->get('PMA_IS_IIS') && $use_refresh) { + $response->header('Refresh: 0; ' . $uri); + } else { + $response->header('Location: ' . $uri); + } + } + + /** + * Outputs application/json headers. This includes no caching. + * + * @return void + */ + public static function headerJSON() + { + if (defined('TESTSUITE')) { + return; + } + // No caching + self::noCacheHeader(); + // MIME type + header('Content-Type: application/json; charset=UTF-8'); + // Disable content sniffing in browser + // This is needed in case we include HTML in JSON, browser might assume it's + // html to display + header('X-Content-Type-Options: nosniff'); + } + + /** + * Outputs headers to prevent caching in browser (and on the way). + * + * @return void + */ + public static function noCacheHeader() + { + if (defined('TESTSUITE')) { + return; + } + // rfc2616 - Section 14.21 + header('Expires: ' . gmdate(DATE_RFC1123)); + // HTTP/1.1 + header( + 'Cache-Control: no-store, no-cache, must-revalidate,' + . ' pre-check=0, post-check=0, max-age=0' + ); + + header('Pragma: no-cache'); // HTTP/1.0 + // test case: exporting a database into a .gz file with Safari + // would produce files not having the current time + // (added this header for Safari but should not harm other browsers) + header('Last-Modified: ' . gmdate(DATE_RFC1123)); + } + + + /** + * Sends header indicating file download. + * + * @param string $filename Filename to include in headers if empty, + * none Content-Disposition header will be sent. + * @param string $mimetype MIME type to include in headers. + * @param int $length Length of content (optional) + * @param bool $no_cache Whether to include no-caching headers. + * + * @return void + */ + public static function downloadHeader($filename, $mimetype, $length = 0, $no_cache = true) + { + if ($no_cache) { + self::noCacheHeader(); + } + /* Replace all possibly dangerous chars in filename */ + $filename = Sanitize::sanitizeFilename($filename); + if (!empty($filename)) { + header('Content-Description: File Transfer'); + header('Content-Disposition: attachment; filename="' . $filename . '"'); + } + header('Content-Type: ' . $mimetype); + // inform the server that compression has been done, + // to avoid a double compression (for example with Apache + mod_deflate) + $notChromeOrLessThan43 = PMA_USR_BROWSER_AGENT != 'CHROME' // see bug #4942 + || (PMA_USR_BROWSER_AGENT == 'CHROME' && PMA_USR_BROWSER_VER < 43); + if (strpos($mimetype, 'gzip') !== false && $notChromeOrLessThan43) { + header('Content-Encoding: gzip'); + } + header('Content-Transfer-Encoding: binary'); + if ($length > 0) { + header('Content-Length: ' . $length); + } + } + + /** + * Returns value of an element in $array given by $path. + * $path is a string describing position of an element in an associative array, + * eg. Servers/1/host refers to $array[Servers][1][host] + * + * @param string $path path in the array + * @param array $array the array + * @param mixed $default default value + * + * @return mixed array element or $default + */ + public static function arrayRead($path, array $array, $default = null) + { + $keys = explode('/', $path); + $value =& $array; + foreach ($keys as $key) { + if (! isset($value[$key])) { + return $default; + } + $value =& $value[$key]; + } + return $value; + } + + /** + * Stores value in an array + * + * @param string $path path in the array + * @param array &$array the array + * @param mixed $value value to store + * + * @return void + */ + public static function arrayWrite($path, array &$array, $value) + { + $keys = explode('/', $path); + $last_key = array_pop($keys); + $a =& $array; + foreach ($keys as $key) { + if (! isset($a[$key])) { + $a[$key] = array(); + } + $a =& $a[$key]; + } + $a[$last_key] = $value; + } + + /** + * Removes value from an array + * + * @param string $path path in the array + * @param array &$array the array + * + * @return void + */ + public static function arrayRemove($path, array &$array) + { + $keys = explode('/', $path); + $keys_last = array_pop($keys); + $path = array(); + $depth = 0; + + $path[0] =& $array; + $found = true; + // go as deep as required or possible + foreach ($keys as $key) { + if (! isset($path[$depth][$key])) { + $found = false; + break; + } + $depth++; + $path[$depth] =& $path[$depth - 1][$key]; + } + // if element found, remove it + if ($found) { + unset($path[$depth][$keys_last]); + $depth--; + } + + // remove empty nested arrays + for (; $depth >= 0; $depth--) { + if (! isset($path[$depth+1]) || count($path[$depth+1]) == 0) { + unset($path[$depth][$keys[$depth]]); + } else { + break; + } + } + } + + /** + * Returns link to (possibly) external site using defined redirector. + * + * @param string $url URL where to go. + * + * @return string URL for a link. + */ + public static function linkURL($url) + { + if (!preg_match('#^https?://#', $url)) { + return $url; + } + + $params = array(); + $params['url'] = $url; + + $url = Url::getCommon($params); + //strip off token and such sensitive information. Just keep url. + $arr = parse_url($url); + parse_str($arr["query"], $vars); + $query = http_build_query(array("url" => $vars["url"])); + + if (!is_null($GLOBALS['PMA_Config']) && $GLOBALS['PMA_Config']->get('is_setup')) { + $url = '../url.php?' . $query; + } else { + $url = './url.php?' . $query; + } + + return $url; + } + + /** + * Checks whether domain of URL is whitelisted domain or not. + * Use only for URLs of external sites. + * + * @param string $url URL of external site. + * + * @return boolean True: if domain of $url is allowed domain, + * False: otherwise. + */ + public static function isAllowedDomain($url) + { + $arr = parse_url($url); + // We need host to be set + if (! isset($arr['host']) || strlen($arr['host']) == 0) { + return false; + } + // We do not want these to be present + $blocked = array('user', 'pass', 'port'); + foreach ($blocked as $part) { + if (isset($arr[$part]) && strlen($arr[$part]) != 0) { + return false; + } + } + $domain = $arr["host"]; + $domainWhiteList = array( + /* Include current domain */ + $_SERVER['SERVER_NAME'], + /* phpMyAdmin domains */ + 'wiki.phpmyadmin.net', + 'www.phpmyadmin.net', + 'phpmyadmin.net', + 'demo.phpmyadmin.net', + 'docs.phpmyadmin.net', + /* mysql.com domains */ + 'dev.mysql.com','bugs.mysql.com', + /* mariadb domains */ + 'mariadb.org', 'mariadb.com', + /* php.net domains */ + 'php.net', + 'secure.php.net', + /* Github domains*/ + 'github.com','www.github.com', + /* Percona domains */ + 'www.percona.com', + /* Following are doubtful ones. */ + 'mysqldatabaseadministration.blogspot.com', + ); + + return in_array($domain, $domainWhiteList); + } + + /** + * Replace some html-unfriendly stuff + * + * @param string $buffer String to process + * + * @return string Escaped and cleaned up text suitable for html + */ + public static function mimeDefaultFunction($buffer) + { + $buffer = htmlspecialchars($buffer); + $buffer = str_replace(' ', '  ', $buffer); + $buffer = preg_replace("@((\015\012)|(\015)|(\012))@", '
        ' . "\n", $buffer); + + return $buffer; + } + + /** + * Displays SQL query before executing. + * + * @param array|string $query_data Array containing queries or query itself + * + * @return void + */ + public static function previewSQL($query_data) + { + $retval = '
        '; + if (empty($query_data)) { + $retval .= __('No change'); + } elseif (is_array($query_data)) { + foreach ($query_data as $query) { + $retval .= Util::formatSql($query); + } + } else { + $retval .= Util::formatSql($query_data); + } + $retval .= '
        '; + $response = Response::getInstance(); + $response->addJSON('sql_data', $retval); + exit; + } + + /** + * recursively check if variable is empty + * + * @param mixed $value the variable + * + * @return bool true if empty + */ + public static function emptyRecursive($value) + { + $empty = true; + if (is_array($value)) { + array_walk_recursive( + $value, + function ($item) use (&$empty) { + $empty = $empty && empty($item); + } + ); + } else { + $empty = empty($value); + } + return $empty; + } + + /** + * Creates some globals from $_POST variables matching a pattern + * + * @param array $post_patterns The patterns to search for + * + * @return void + */ + public static function setPostAsGlobal(array $post_patterns) + { + foreach (array_keys($_POST) as $post_key) { + foreach ($post_patterns as $one_post_pattern) { + if (preg_match($one_post_pattern, $post_key)) { + $GLOBALS[$post_key] = $_POST[$post_key]; + } + } + } + } + + /** + * Creates some globals from $_REQUEST + * + * @param string $param db|table + * + * @return void + */ + public static function setGlobalDbOrTable($param) + { + $GLOBALS[$param] = ''; + if (self::isValid($_REQUEST[$param])) { + // can we strip tags from this? + // only \ and / is not allowed in db names for MySQL + $GLOBALS[$param] = $_REQUEST[$param]; + $GLOBALS['url_params'][$param] = $GLOBALS[$param]; + } + } + + /** + * PATH_INFO could be compromised if set, so remove it from PHP_SELF + * and provide a clean PHP_SELF here + * + * @return void + */ + public static function cleanupPathInfo() + { + global $PMA_PHP_SELF; + + $PMA_PHP_SELF = self::getenv('PHP_SELF'); + if (empty($PMA_PHP_SELF)) { + $PMA_PHP_SELF = urldecode(self::getenv('REQUEST_URI')); + } + $_PATH_INFO = self::getenv('PATH_INFO'); + if (! empty($_PATH_INFO) && ! empty($PMA_PHP_SELF)) { + $question_pos = mb_strpos($PMA_PHP_SELF, '?'); + if ($question_pos != false) { + $PMA_PHP_SELF = mb_substr($PMA_PHP_SELF, 0, $question_pos); + } + $path_info_pos = mb_strrpos($PMA_PHP_SELF, $_PATH_INFO); + if ($path_info_pos !== false) { + $path_info_part = mb_substr($PMA_PHP_SELF, $path_info_pos, mb_strlen($_PATH_INFO)); + if ($path_info_part == $_PATH_INFO) { + $PMA_PHP_SELF = mb_substr($PMA_PHP_SELF, 0, $path_info_pos); + } + } + } + + $path = []; + foreach(explode('/', $PMA_PHP_SELF) as $part) { + // ignore parts that have no value + if (empty($part) || $part === '.') continue; + + if ($part !== '..') { + // cool, we found a new part + array_push($path, $part); + } elseif (count($path) > 0) { + // going back up? sure + array_pop($path); + } + // Here we intentionall ignore case where we go too up + // as there is nothing sane to do + } + + $PMA_PHP_SELF = htmlspecialchars('/' . join('/', $path)); + } + + /** + * Checks that required PHP extensions are there. + * @return void + */ + public static function checkExtensions() + { + /** + * Warning about mbstring. + */ + if (! function_exists('mb_detect_encoding')) { + self::warnMissingExtension('mbstring'); + } + + /** + * We really need this one! + */ + if (! function_exists('preg_replace')) { + self::warnMissingExtension('pcre', true); + } + + /** + * JSON is required in several places. + */ + if (! function_exists('json_encode')) { + self::warnMissingExtension('json', true); + } + + /** + * ctype is required for Twig. + */ + if (! function_exists('ctype_alpha')) { + self::warnMissingExtension('ctype', true); + } + + /** + * hash is required for cookie authentication. + */ + if (! function_exists('hash_hmac')) { + self::warnMissingExtension('hash', true); + } + } + + /** + * Gets the "true" IP address of the current user + * + * @return string the ip of the user + * + * @access private + */ + public static function getIp() + { + /* Get the address of user */ + if (empty($_SERVER['REMOTE_ADDR'])) { + /* We do not know remote IP */ + return false; + } + + $direct_ip = $_SERVER['REMOTE_ADDR']; + + /* Do we trust this IP as a proxy? If yes we will use it's header. */ + if (!isset($GLOBALS['cfg']['TrustedProxies'][$direct_ip])) { + /* Return true IP */ + return $direct_ip; + } + + /** + * Parse header in form: + * X-Forwarded-For: client, proxy1, proxy2 + */ + // Get header content + $value = self::getenv($GLOBALS['cfg']['TrustedProxies'][$direct_ip]); + // Grab first element what is client adddress + $value = explode(',', $value)[0]; + // checks that the header contains only one IP address, + $is_ip = filter_var($value, FILTER_VALIDATE_IP); + + if ($is_ip !== false) { + // True IP behind a proxy + return $value; + } + + // We could not parse header + return false; + } // end of the 'getIp()' function + + /** + * Sanitizes MySQL hostname + * + * * strips p: prefix(es) + * + * @param string $name User given hostname + * + * @return string + */ + public static function sanitizeMySQLHost($name) + { + while (strtolower(substr($name, 0, 2)) == 'p:') { + $name = substr($name, 2); + } + + return $name; + } + + /** + * Sanitizes MySQL username + * + * * strips part behind null byte + * + * @param string $name User given username + * + * @return string + */ + public static function sanitizeMySQLUser($name) + { + $position = strpos($name, chr(0)); + if ($position !== false) { + return substr($name, 0, $position); + } + return $name; + } + + /** + * Safe unserializer wrapper + * + * It does not unserialize data containing objects + * + * @param string $data Data to unserialize + * + * @return mixed + */ + public static function safeUnserialize($data) + { + if (! is_string($data)) { + return null; + } + + /* validate serialized data */ + $length = strlen($data); + $depth = 0; + for ($i = 0; $i < $length; $i++) { + $value = $data[$i]; + + switch ($value) + { + case '}': + /* end of array */ + if ($depth <= 0) { + return null; + } + $depth--; + break; + case 's': + /* string */ + // parse sting length + $strlen = intval(substr($data, $i + 2)); + // string start + $i = strpos($data, ':', $i + 2); + if ($i === false) { + return null; + } + // skip string, quotes and ; + $i += 2 + $strlen + 1; + if ($data[$i] != ';') { + return null; + } + break; + + case 'b': + case 'i': + case 'd': + /* bool, integer or double */ + // skip value to sepearator + $i = strpos($data, ';', $i); + if ($i === false) { + return null; + } + break; + case 'a': + /* array */ + // find array start + $i = strpos($data, '{', $i); + if ($i === false) { + return null; + } + // remember nesting + $depth++; + break; + case 'N': + /* null */ + // skip to end + $i = strpos($data, ';', $i); + if ($i === false) { + return null; + } + break; + default: + /* any other elements are not wanted */ + return null; + } + } + + // check unterminated arrays + if ($depth > 0) { + return null; + } + + return unserialize($data); + } + + /** + * Applies changes to PHP configuration. + * + * @return void + */ + public static function configure() + { + /** + * Set utf-8 encoding for PHP + */ + ini_set('default_charset', 'utf-8'); + mb_internal_encoding('utf-8'); + + /** + * Set precision to sane value, with higher values + * things behave slightly unexpectedly, for example + * round(1.2, 2) returns 1.199999999999999956. + */ + ini_set('precision', 14); + + /** + * check timezone setting + * this could produce an E_WARNING - but only once, + * if not done here it will produce E_WARNING on every date/time function + */ + date_default_timezone_set(@date_default_timezone_get()); + } + + /** + * Check whether PHP configuration matches our needs. + * + * @return void + */ + public static function checkConfiguration() + { + /** + * As we try to handle charsets by ourself, mbstring overloads just + * break it, see bug 1063821. + * + * We specifically use empty here as we are looking for anything else than + * empty value or 0. + */ + if (extension_loaded('mbstring') && !empty(ini_get('mbstring.func_overload'))) { + self::fatalError( + __( + 'You have enabled mbstring.func_overload in your PHP ' + . 'configuration. This option is incompatible with phpMyAdmin ' + . 'and might cause some data to be corrupted!' + ) + ); + } + + /** + * The ini_set and ini_get functions can be disabled using + * disable_functions but we're relying quite a lot of them. + */ + if (! function_exists('ini_get') || ! function_exists('ini_set')) { + self::fatalError( + __( + 'You have disabled ini_get and/or ini_set in php.ini. ' + . 'This option is incompatible with phpMyAdmin!' + ) + ); + } + } + + /** + * prints list item for main page + * + * @param string $name displayed text + * @param string $listId id, used for css styles + * @param string $url make item as link with $url as target + * @param string $mysql_help_page display a link to MySQL's manual + * @param string $target special target for $url + * @param string $a_id id for the anchor, + * used for jQuery to hook in functions + * @param string $class class for the li element + * @param string $a_class class for the anchor element + * + * @return void + */ + public static function printListItem($name, $listId = null, $url = null, + $mysql_help_page = null, $target = null, $a_id = null, $class = null, + $a_class = null + ) { + echo Template::get('list/item') + ->render( + array( + 'content' => $name, + 'id' => $listId, + 'class' => $class, + 'url' => array( + 'href' => $url, + 'target' => $target, + 'id' => $a_id, + 'class' => $a_class, + ), + 'mysql_help_page' => $mysql_help_page, + ) + ); + } + + /** + * Checks request and fails with fatal error if something problematic is found + * + * @return void + */ + public static function checkRequest() + { + if (isset($_REQUEST['GLOBALS']) || isset($_FILES['GLOBALS'])) { + self::fatalError(__("GLOBALS overwrite attempt")); + } + + /** + * protect against possible exploits - there is no need to have so much variables + */ + if (count($_REQUEST) > 1000) { + self::fatalError(__('possible exploit')); + } + } + + /** + * Sign the sql query using hmac using the session token + * + * @param string $sqlQuery The sql query + * @return string + */ + public static function signSqlQuery($sqlQuery) + { + /** @var array $cfg */ + global $cfg; + return hash_hmac('sha256', $sqlQuery, $_SESSION[' HMAC_secret '] . $cfg['blowfish_secret']); + } + + /** + * Check that the sql query has a valid hmac signature + * + * @param string $sqlQuery The sql query + * @param string $signature The Signature to check + * @return bool + */ + public static function checkSqlQuerySignature($sqlQuery, $signature) + { + /** @var array $cfg */ + global $cfg; + $hmac = hash_hmac('sha256', $sqlQuery, $_SESSION[' HMAC_secret '] . $cfg['blowfish_secret']); + return hash_equals($hmac, $signature); + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/CreateAddField.php b/php/apps/phpmyadmin49/libraries/classes/CreateAddField.php new file mode 100644 index 00000000..718b34d9 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/CreateAddField.php @@ -0,0 +1,556 @@ +dbi = $dbi; + } + + /** + * Transforms the radio button field_key into 4 arrays + * + * @return array An array of arrays which represents column keys for each index type + */ + private function getIndexedColumns() + { + $fieldCount = count($_POST['field_name']); + $fieldPrimary = json_decode($_POST['primary_indexes'], true); + $fieldIndex = json_decode($_POST['indexes'], true); + $fieldUnique = json_decode($_POST['unique_indexes'], true); + $fieldFullText = json_decode($_POST['fulltext_indexes'], true); + $fieldSpatial = json_decode($_POST['spatial_indexes'], true); + + return [ + $fieldCount, + $fieldPrimary, + $fieldIndex, + $fieldUnique, + $fieldFullText, + $fieldSpatial, + ]; + } + + /** + * Initiate the column creation statement according to the table creation or + * add columns to a existing table + * + * @param int $fieldCount number of columns + * @param boolean $isCreateTable true if requirement is to get the statement + * for table creation + * + * @return array $definitions An array of initial sql statements + * according to the request + */ + private function buildColumnCreationStatement( + $fieldCount, + $isCreateTable = true + ) { + $definitions = []; + $previousField = -1; + for ($i = 0; $i < $fieldCount; ++$i) { + // '0' is also empty for php :-( + if (strlen($_POST['field_name'][$i]) === 0) { + continue; + } + + $definition = $this->getStatementPrefix($isCreateTable) . + Table::generateFieldSpec( + trim($_POST['field_name'][$i]), + $_POST['field_type'][$i], + $_POST['field_length'][$i], + $_POST['field_attribute'][$i], + isset($_POST['field_collation'][$i]) + ? $_POST['field_collation'][$i] + : '', + isset($_POST['field_null'][$i]) + ? $_POST['field_null'][$i] + : 'NOT NULL', + $_POST['field_default_type'][$i], + $_POST['field_default_value'][$i], + isset($_POST['field_extra'][$i]) + ? $_POST['field_extra'][$i] + : false, + isset($_POST['field_comments'][$i]) + ? $_POST['field_comments'][$i] + : '', + isset($_POST['field_virtuality'][$i]) + ? $_POST['field_virtuality'][$i] + : '', + isset($_POST['field_expression'][$i]) + ? $_POST['field_expression'][$i] + : '' + ); + + $definition .= $this->setColumnCreationStatementSuffix($i, $previousField, $isCreateTable); + $previousField = $i; + $definitions[] = $definition; + } // end for + + return $definitions; + } + + /** + * Set column creation suffix according to requested position of the new column + * + * @param int $currentFieldNumber current column number + * @param int $previousField previous field for ALTER statement + * @param boolean $isCreateTable true if requirement is to get the statement + * for table creation + * + * @return string $sqlSuffix suffix + */ + private function setColumnCreationStatementSuffix( + $currentFieldNumber, + $previousField, + $isCreateTable = true + ) { + // no suffix is needed if request is a table creation + $sqlSuffix = ' '; + if ($isCreateTable) { + return $sqlSuffix; + } + + if ((string) $_POST['field_where'] === 'last') { + return $sqlSuffix; + } + + // Only the first field can be added somewhere other than at the end + if ($previousField == -1) { + if ((string) $_POST['field_where'] === 'first') { + $sqlSuffix .= ' FIRST'; + } else if (! empty($_POST['after_field'])) { + $sqlSuffix .= ' AFTER ' + . Util::backquote($_POST['after_field']); + } + } else { + $sqlSuffix .= ' AFTER ' + . Util::backquote( + $_POST['field_name'][$previousField] + ); + } + + return $sqlSuffix; + } + + /** + * Create relevant index statements + * + * @param array $index an array of index columns + * @param string $indexChoice index choice that which represents + * the index type of $indexed_fields + * @param boolean $isCreateTable true if requirement is to get the statement + * for table creation + * + * @return array an array of sql statements for indexes + */ + private function buildIndexStatements( + array $index, + $indexChoice, + $isCreateTable = true + ) { + $statement = []; + if (!count($index)) { + return $statement; + } + + $sqlQuery = $this->getStatementPrefix($isCreateTable) + . ' ' . $indexChoice; + + if (! empty($index['Key_name']) && $index['Key_name'] != 'PRIMARY') { + $sqlQuery .= ' ' . Util::backquote($index['Key_name']); + } + + $indexFields = []; + foreach ($index['columns'] as $key => $column) { + $indexFields[$key] = Util::backquote( + $_POST['field_name'][$column['col_index']] + ); + if ($column['size']) { + $indexFields[$key] .= '(' . $column['size'] . ')'; + } + } + + $sqlQuery .= ' (' . implode(', ', $indexFields) . ')'; + + $keyBlockSizes = $index['Key_block_size']; + if (! empty($keyBlockSizes)) { + $sqlQuery .= " KEY_BLOCK_SIZE = " + . $this->dbi->escapeString($keyBlockSizes); + } + + // specifying index type is allowed only for primary, unique and index only + $type = $index['Index_type']; + if ($index['Index_choice'] != 'SPATIAL' + && $index['Index_choice'] != 'FULLTEXT' + && in_array($type, Index::getIndexTypes()) + ) { + $sqlQuery .= ' USING ' . $type; + } + + $parser = $index['Parser']; + if ($index['Index_choice'] == 'FULLTEXT' && ! empty($parser)) { + $sqlQuery .= " WITH PARSER " . $this->dbi->escapeString($parser); + } + + $comment = $index['Index_comment']; + if (! empty($comment)) { + $sqlQuery .= " COMMENT '" . $this->dbi->escapeString($comment) + . "'"; + } + + $statement[] = $sqlQuery; + + return $statement; + } + + /** + * Statement prefix for the buildColumnCreationStatement() + * + * @param boolean $isCreateTable true if requirement is to get the statement + * for table creation + * + * @return string $sqlPrefix prefix + */ + private function getStatementPrefix($isCreateTable = true) + { + $sqlPrefix = " "; + if (! $isCreateTable) { + $sqlPrefix = ' ADD '; + } + return $sqlPrefix; + } + + /** + * Merge index definitions for one type of index + * + * @param array $definitions the index definitions to merge to + * @param boolean $isCreateTable true if requirement is to get the statement + * for table creation + * @param array $indexedColumns the columns for one type of index + * @param string $indexKeyword the index keyword to use in the definition + * + * @return array $index_definitions + */ + private function mergeIndexStatements( + array $definitions, + $isCreateTable, + array $indexedColumns, + $indexKeyword + ) { + foreach ($indexedColumns as $index) { + $statements = $this->buildIndexStatements( + $index, + " " . $indexKeyword . " ", + $isCreateTable + ); + $definitions = array_merge($definitions, $statements); + } + return $definitions; + } + + /** + * Returns sql statement according to the column and index specifications as + * requested + * + * @param boolean $isCreateTable true if requirement is to get the statement + * for table creation + * + * @return string sql statement + */ + private function getColumnCreationStatements($isCreateTable = true) + { + $sqlStatement = ""; + list( + $fieldCount, + $fieldPrimary, + $fieldIndex, + $fieldUnique, + $fieldFullText, + $fieldSpatial + ) = $this->getIndexedColumns(); + $definitions = $this->buildColumnCreationStatement( + $fieldCount, + $isCreateTable + ); + + // Builds the PRIMARY KEY statements + $primaryKeyStatements = $this->buildIndexStatements( + isset($fieldPrimary[0]) ? $fieldPrimary[0] : [], + " PRIMARY KEY ", + $isCreateTable + ); + $definitions = array_merge($definitions, $primaryKeyStatements); + + // Builds the INDEX statements + $definitions = $this->mergeIndexStatements( + $definitions, + $isCreateTable, + $fieldIndex, + "INDEX" + ); + + // Builds the UNIQUE statements + $definitions = $this->mergeIndexStatements( + $definitions, + $isCreateTable, + $fieldUnique, + "UNIQUE" + ); + + // Builds the FULLTEXT statements + $definitions = $this->mergeIndexStatements( + $definitions, + $isCreateTable, + $fieldFullText, + "FULLTEXT" + ); + + // Builds the SPATIAL statements + $definitions = $this->mergeIndexStatements( + $definitions, + $isCreateTable, + $fieldSpatial, + "SPATIAL" + ); + + if (count($definitions)) { + $sqlStatement = implode(', ', $definitions); + } + $sqlStatement = preg_replace('@, $@', '', $sqlStatement); + + return $sqlStatement; + } + + /** + * Returns the partitioning clause + * + * @return string partitioning clause + */ + public function getPartitionsDefinition() + { + $sqlQuery = ""; + if (! empty($_POST['partition_by']) + && ! empty($_POST['partition_expr']) + && ! empty($_POST['partition_count']) + && $_POST['partition_count'] > 1 + ) { + $sqlQuery .= " PARTITION BY " . $_POST['partition_by'] + . " (" . $_POST['partition_expr'] . ")" + . " PARTITIONS " . $_POST['partition_count']; + } + + if (! empty($_POST['subpartition_by']) + && ! empty($_POST['subpartition_expr']) + && ! empty($_POST['subpartition_count']) + && $_POST['subpartition_count'] > 1 + ) { + $sqlQuery .= " SUBPARTITION BY " . $_POST['subpartition_by'] + . " (" . $_POST['subpartition_expr'] . ")" + . " SUBPARTITIONS " . $_POST['subpartition_count']; + } + + if (! empty($_POST['partitions'])) { + $i = 0; + $partitions = []; + foreach ($_POST['partitions'] as $partition) { + $partitions[] = $this->getPartitionDefinition($partition); + $i++; + } + $sqlQuery .= " (" . implode(", ", $partitions) . ")"; + } + + return $sqlQuery; + } + + /** + * Returns the definition of a partition/subpartition + * + * @param array $partition array of partition/subpartition detiails + * @param boolean $isSubPartition whether a subpartition + * + * @return string partition/subpartition definition + */ + private function getPartitionDefinition(array $partition, $isSubPartition = false) + { + $sqlQuery = " " . ($isSubPartition ? "SUB" : "") . "PARTITION "; + $sqlQuery .= $partition['name']; + + if (! empty($partition['value_type'])) { + $sqlQuery .= " VALUES " . $partition['value_type']; + + if ($partition['value_type'] != 'LESS THAN MAXVALUE') { + $sqlQuery .= " (" . $partition['value'] . ")"; + } + } + + if (! empty($partition['engine'])) { + $sqlQuery .= " ENGINE = " . $partition['engine']; + } + if (! empty($partition['comment'])) { + $sqlQuery .= " COMMENT = '" . $partition['comment'] . "'"; + } + if (! empty($partition['data_directory'])) { + $sqlQuery .= " DATA DIRECTORY = '" . $partition['data_directory'] . "'"; + } + if (! empty($partition['index_directory'])) { + $sqlQuery .= " INDEX_DIRECTORY = '" . $partition['index_directory'] . "'"; + } + if (! empty($partition['max_rows'])) { + $sqlQuery .= " MAX_ROWS = " . $partition['max_rows']; + } + if (! empty($partition['min_rows'])) { + $sqlQuery .= " MIN_ROWS = " . $partition['min_rows']; + } + if (! empty($partition['tablespace'])) { + $sqlQuery .= " TABLESPACE = " . $partition['tablespace']; + } + if (! empty($partition['node_group'])) { + $sqlQuery .= " NODEGROUP = " . $partition['node_group']; + } + + if (! empty($partition['subpartitions'])) { + $j = 0; + $subpartitions = []; + foreach ($partition['subpartitions'] as $subpartition) { + $subpartitions[] = $this->getPartitionDefinition( + $subpartition, + true + ); + $j++; + } + $sqlQuery .= " (" . implode(", ", $subpartitions) . ")"; + } + + return $sqlQuery; + } + + /** + * Function to get table creation sql query + * + * @param string $db database name + * @param string $table table name + * + * @return string + */ + public function getTableCreationQuery($db, $table) + { + // get column addition statements + $sqlStatement = $this->getColumnCreationStatements(true); + + // Builds the 'create table' statement + $sqlQuery = 'CREATE TABLE ' . Util::backquote($db) . '.' + . Util::backquote(trim($table)) . ' (' . $sqlStatement . ')'; + + // Adds table type, character set, comments and partition definition + if (!empty($_POST['tbl_storage_engine']) + && ($_POST['tbl_storage_engine'] != 'Default') + ) { + $sqlQuery .= ' ENGINE = ' . $_POST['tbl_storage_engine']; + } + if (!empty($_POST['tbl_collation'])) { + $sqlQuery .= Util::getCharsetQueryPart($_POST['tbl_collation']); + } + if (! empty($_POST['connection']) + && ! empty($_POST['tbl_storage_engine']) + && $_POST['tbl_storage_engine'] == 'FEDERATED' + ) { + $sqlQuery .= " CONNECTION = '" + . $this->dbi->escapeString($_POST['connection']) . "'"; + } + if (!empty($_POST['comment'])) { + $sqlQuery .= ' COMMENT = \'' + . $this->dbi->escapeString($_POST['comment']) . '\''; + } + $sqlQuery .= $this->getPartitionsDefinition(); + $sqlQuery .= ';'; + + return $sqlQuery; + } + + /** + * Function to get the number of fields for the table creation form + * + * @return int + */ + public function getNumberOfFieldsFromRequest() + { + // Limit to 4096 fields (MySQL maximal value) + $mysqlLimit = 4096; + + if (isset($_POST['submit_num_fields'])) { // adding new fields + $numberOfFields = intval($_POST['orig_num_fields']) + intval($_POST['added_fields']); + } elseif (isset($_POST['orig_num_fields'])) { // retaining existing fields + $numberOfFields = intval($_POST['orig_num_fields']); + } elseif (isset($_POST['num_fields']) + && intval($_POST['num_fields']) > 0 + ) { // new table with specified number of fields + $numberOfFields = intval($_POST['num_fields']); + } else { // new table with unspecified number of fields + $numberOfFields = 4; + } + + return min($numberOfFields, $mysqlLimit); + } + + /** + * Function to execute the column creation statement + * + * @param string $db current database + * @param string $table current table + * @param string $errorUrl error page url + * + * @return array + */ + public function tryColumnCreationQuery($db, $table, $errorUrl) + { + // get column addition statements + $sqlStatement = $this->getColumnCreationStatements(false); + + // To allow replication, we first select the db to use and then run queries + // on this db. + if (!($this->dbi->selectDb($db))) { + Util::mysqlDie( + $this->dbi->getError(), + 'USE ' . Util::backquote($db), + false, + $errorUrl + ); + } + $sqlQuery = 'ALTER TABLE ' . + Util::backquote($table) . ' ' . $sqlStatement . ';'; + // If there is a request for SQL previewing. + if (isset($_POST['preview_sql'])) { + Core::previewSQL($sqlQuery); + } + return [$this->dbi->tryQuery($sqlQuery), $sqlQuery]; + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Database/DatabaseList.php b/php/apps/phpmyadmin49/libraries/classes/Database/DatabaseList.php new file mode 100644 index 00000000..cc383eec --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Database/DatabaseList.php @@ -0,0 +1,58 @@ +getDatabaseList(); + } + + return null; + } + + /** + * Accessor to PMA::$databases + * + * @return ListDatabase + */ + public function getDatabaseList() + { + if (null === $this->databases) { + $this->databases = new ListDatabase(); + } + + return $this->databases; + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Database/Designer.php b/php/apps/phpmyadmin49/libraries/classes/Database/Designer.php new file mode 100644 index 00000000..95e07019 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Database/Designer.php @@ -0,0 +1,422 @@ +relation = new Relation(); + } + + /** + * Function to get html for displaying the page edit/delete form + * + * @param string $db database name + * @param string $operation 'edit' or 'delete' depending on the operation + * + * @return string html content + */ + public function getHtmlForEditOrDeletePages($db, $operation) + { + $cfgRelation = $this->relation->getRelationsParam(); + return Template::get('database/designer/edit_delete_pages')->render([ + 'db' => $db, + 'operation' => $operation, + 'pdfwork' => $cfgRelation['pdfwork'], + 'pages' => $this->getPageIdsAndNames($db), + ]); + } + + /** + * Function to get html for displaying the page save as form + * + * @param string $db database name + * + * @return string html content + */ + public function getHtmlForPageSaveAs($db) + { + $cfgRelation = $this->relation->getRelationsParam(); + return Template::get('database/designer/page_save_as')->render([ + 'db' => $db, + 'pdfwork' => $cfgRelation['pdfwork'], + 'pages' => $this->getPageIdsAndNames($db), + ]); + } + + /** + * Retrieve IDs and names of schema pages + * + * @param string $db database name + * + * @return array array of schema page id and names + */ + private function getPageIdsAndNames($db) + { + $cfgRelation = $this->relation->getRelationsParam(); + $page_query = "SELECT `page_nr`, `page_descr` FROM " + . Util::backquote($cfgRelation['db']) . "." + . Util::backquote($cfgRelation['pdf_pages']) + . " WHERE db_name = '" . $GLOBALS['dbi']->escapeString($db) . "'" + . " ORDER BY `page_descr`"; + $page_rs = $this->relation->queryAsControlUser( + $page_query, + false, + DatabaseInterface::QUERY_STORE + ); + + $result = []; + while ($curr_page = $GLOBALS['dbi']->fetchAssoc($page_rs)) { + $result[intval($curr_page['page_nr'])] = $curr_page['page_descr']; + } + return $result; + } + + /** + * Function to get html for displaying the schema export + * + * @param string $db database name + * @param int $page the page to be exported + * + * @return string + */ + public function getHtmlForSchemaExport($db, $page) + { + /* Scan for schema plugins */ + /* @var $export_list SchemaPlugin[] */ + $export_list = Plugins::getPlugins( + "schema", + 'libraries/classes/Plugins/Schema/', + null + ); + + /* Fail if we didn't find any schema plugin */ + if (empty($export_list)) { + return Message::error( + __('Could not load schema plugins, please check your installation!') + )->getDisplay(); + } + + return Template::get('database/designer/schema_export') + ->render( + [ + 'db' => $db, + 'page' => $page, + 'export_list' => $export_list + ] + ); + } + + /** + * Returns HTML for including some variable to be accessed by JavaScript + * + * @param array $script_tables array on foreign key support for each table + * @param array $script_contr initialization data array + * @param array $script_display_field display fields of each table + * @param int $display_page page number of the selected page + * + * @return string html + */ + public function getHtmlForJsFields( + array $script_tables, + array $script_contr, + array $script_display_field, + $display_page + ) { + $cfgRelation = $this->relation->getRelationsParam(); + $designerConfig = new \stdClass(); + $designerConfig->db = $_GET['db']; + $designerConfig->scriptTables = $script_tables; + $designerConfig->scriptContr = $script_contr; + $designerConfig->server = $GLOBALS['server']; + $designerConfig->scriptDisplayField = $script_display_field; + $designerConfig->displayPage = (int) $display_page; + $designerConfig->tablesEnabled = $cfgRelation['pdfwork']; + return Template::get('database/designer/js_fields')->render([ + 'designer_config' => json_encode($designerConfig) + ]); + } + + /** + * Returns HTML for the menu bar of the designer page + * + * @param boolean $visualBuilder whether this is visual query builder + * @param string $selectedPage name of the selected page + * @param array $paramsArray array with class name for various buttons + * on side menu + * + * @return string html + */ + public function getPageMenu($visualBuilder, $selectedPage, array $paramsArray) + { + return Template::get('database/designer/side_menu')->render([ + 'visual_builder' => $visualBuilder, + 'selected_page' => $selectedPage, + 'params_array' => $paramsArray, + 'theme' => $GLOBALS['PMA_Theme'], + ]); + } + + /** + * Returns array of stored values of Designer Settings + * + * @return array stored values + */ + private function getSideMenuParamsArray() + { + $params = []; + + $cfgRelation = $this->relation->getRelationsParam(); + + if ($GLOBALS['cfgRelation']['designersettingswork']) { + $query = 'SELECT `settings_data` FROM ' + . Util::backquote($cfgRelation['db']) . '.' + . Util::backquote($cfgRelation['designer_settings']) + . ' WHERE ' . Util::backquote('username') . ' = "' + . $GLOBALS['dbi']->escapeString($GLOBALS['cfg']['Server']['user']) + . '";'; + + $result = $GLOBALS['dbi']->fetchSingleRow($query); + + $params = json_decode($result['settings_data'], true); + } + + return $params; + } + + /** + * Returns class names for various buttons on Designer Side Menu + * + * @return array class names of various buttons + */ + public function returnClassNamesFromMenuButtons() + { + $classes_array = []; + $params_array = $this->getSideMenuParamsArray(); + + if (isset($params_array['angular_direct']) + && $params_array['angular_direct'] == 'angular' + ) { + $classes_array['angular_direct'] = 'M_butt_Selected_down'; + } else { + $classes_array['angular_direct'] = 'M_butt'; + } + + if (isset($params_array['snap_to_grid']) + && $params_array['snap_to_grid'] == 'on' + ) { + $classes_array['snap_to_grid'] = 'M_butt_Selected_down'; + } else { + $classes_array['snap_to_grid'] = 'M_butt'; + } + + if (isset($params_array['pin_text']) + && $params_array['pin_text'] == 'true' + ) { + $classes_array['pin_text'] = 'M_butt_Selected_down'; + } else { + $classes_array['pin_text'] = 'M_butt'; + } + + if (isset($params_array['relation_lines']) + && $params_array['relation_lines'] == 'false' + ) { + $classes_array['relation_lines'] = 'M_butt_Selected_down'; + } else { + $classes_array['relation_lines'] = 'M_butt'; + } + + if (isset($params_array['small_big_all']) + && $params_array['small_big_all'] == 'v' + ) { + $classes_array['small_big_all'] = 'M_butt_Selected_down'; + } else { + $classes_array['small_big_all'] = 'M_butt'; + } + + if (isset($params_array['side_menu']) + && $params_array['side_menu'] == 'true' + ) { + $classes_array['side_menu'] = 'M_butt_Selected_down'; + } else { + $classes_array['side_menu'] = 'M_butt'; + } + + return $classes_array; + } + + /** + * Returns HTML for the canvas element + * + * @return string html + */ + public function getHtmlCanvas() + { + return Template::get('database/designer/canvas')->render(); + } + + /** + * Return HTML for the table list + * + * @return string html + */ + public function getHtmlTableList() + { + return Template::get('database/designer/table_list')->render([ + 'theme' => $GLOBALS['PMA_Theme'], + ]); + } + + /** + * Get HTML to display tables on designer page + * + * @param string $db The database name from the request + * @param array $designerTables The designer tables + * @param array $tab_pos tables positions + * @param int $display_page page number of the selected page + * @param array $tab_column table column info + * @param array $tables_all_keys all indices + * @param array $tables_pk_or_unique_keys unique or primary indices + * + * @return string html + */ + public function getDatabaseTables( + $db, + array $designerTables, + array $tab_pos, + $display_page, + array $tab_column, + array $tables_all_keys, + array $tables_pk_or_unique_keys + ) { + return Template::get('database/designer/database_tables')->render([ + 'db' => $GLOBALS['db'], + 'get_db' => $db, + 'has_query' => isset($_REQUEST['query']), + 'tab_pos' => $tab_pos, + 'display_page' => $display_page, + 'tab_column' => $tab_column, + 'tables_all_keys' => $tables_all_keys, + 'tables_pk_or_unique_keys' => $tables_pk_or_unique_keys, + 'tables' => $designerTables, + 'theme' => $GLOBALS['PMA_Theme'], + ]); + } + + /** + * Returns HTML for the new relations panel. + * + * @return string html + */ + public function getNewRelationPanel() + { + return Template::get('database/designer/new_relation_panel') + ->render(); + } + + /** + * Returns HTML for the relations delete panel + * + * @return string html + */ + public function getDeleteRelationPanel() + { + return Template::get('database/designer/delete_relation_panel') + ->render(); + } + + /** + * Returns HTML for the options panel + * + * @return string html + */ + public function getOptionsPanel() + { + return Template::get('database/designer/options_panel')->render(); + } + + /** + * Get HTML for the 'rename to' panel + * + * @return string html + */ + public function getRenameToPanel() + { + return Template::get('database/designer/rename_to_panel') + ->render(); + } + + /** + * Returns HTML for the 'having' panel + * + * @return string html + */ + public function getHavingQueryPanel() + { + return Template::get('database/designer/having_query_panel') + ->render(); + } + + /** + * Returns HTML for the 'aggregate' panel + * + * @return string html + */ + public function getAggregateQueryPanel() + { + return Template::get('database/designer/aggregate_query_panel') + ->render(); + } + + /** + * Returns HTML for the 'where' panel + * + * @return string html + */ + public function getWhereQueryPanel() + { + return Template::get('database/designer/where_query_panel') + ->render(); + } + + /** + * Returns HTML for the query details panel + * + * @param string $db Database name + * + * @return string html + */ + public function getQueryDetails($db) + { + return Template::get('database/designer/query_details')->render([ + 'db' => $db, + ]); + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Database/Designer/Common.php b/php/apps/phpmyadmin49/libraries/classes/Database/Designer/Common.php new file mode 100644 index 00000000..2fec226b --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Database/Designer/Common.php @@ -0,0 +1,784 @@ +relation = new Relation(); + } + + /** + * Retrieves table info and returns it + * + * @param string $db (optional) Filter only a DB ($table is required if you use $db) + * @param string $table (optional) Filter only a table ($db is now required) + * @return DesignerTable[] with table info + */ + public function getTablesInfo($db = null, $table = null) + { + $designerTables = array(); + $db = ($db === null) ? $GLOBALS['db'] : $db; + // seems to be needed later + $GLOBALS['dbi']->selectDb($db); + if ($db === null && $table === null) { + $tables = $GLOBALS['dbi']->getTablesFull($db); + } else { + $tables = $GLOBALS['dbi']->getTablesFull($db, $table); + } + + + foreach ($tables as $one_table) { + $DF = $this->relation->getDisplayField($db, $one_table['TABLE_NAME']); + $DF = is_string($DF) ? $DF : ''; + $DF = ($DF !== '') ? $DF : null; + $designerTables[] = new DesignerTable( + $db, + $one_table['TABLE_NAME'], + $one_table['ENGINE'], + $DF + ); + } + + return $designerTables; + } + + /** + * Retrieves table column info + * + * @param DesignerTable[] $designerTables The designer tables + * @return array table column nfo + */ + public function getColumnsInfo($designerTables) + { + //$GLOBALS['dbi']->selectDb($GLOBALS['db']); + $tabColumn = array(); + + foreach($designerTables as $designerTable) { + $fieldsRs = $GLOBALS['dbi']->query( + $GLOBALS['dbi']->getColumnsSql( + $designerTable->getDatabaseName(), + $designerTable->getTableName(), + null, + true + ), + DatabaseInterface::CONNECT_USER, + DatabaseInterface::QUERY_STORE + ); + $j = 0; + while ($row = $GLOBALS['dbi']->fetchAssoc($fieldsRs)) { + if (! isset($tabColumn[$designerTable->getDbTableString()])) { + $tabColumn[$designerTable->getDbTableString()] = []; + } + $tabColumn[$designerTable->getDbTableString()]['COLUMN_ID'][$j] = $j; + $tabColumn[$designerTable->getDbTableString()]['COLUMN_NAME'][$j] = $row['Field']; + $tabColumn[$designerTable->getDbTableString()]['TYPE'][$j] = $row['Type']; + $tabColumn[$designerTable->getDbTableString()]['NULLABLE'][$j] = $row['Null']; + $j++; + } + } + + return $tabColumn; + } + + /** + * Returns JavaScript code for initializing vars + * + * @param DesignerTable[] $designerTables The designer tables + * @return string JavaScript code + */ + public function getScriptContr($designerTables) + { + $GLOBALS['dbi']->selectDb($GLOBALS['db']); + $con = array(); + $con["C_NAME"] = array(); + $i = 0; + $alltab_rs = $GLOBALS['dbi']->query( + 'SHOW TABLES FROM ' . Util::backquote($GLOBALS['db']), + DatabaseInterface::CONNECT_USER, + DatabaseInterface::QUERY_STORE + ); + while ($val = @$GLOBALS['dbi']->fetchRow($alltab_rs)) { + $row = $this->relation->getForeigners($GLOBALS['db'], $val[0], '', 'internal'); + + if ($row !== false) { + foreach ($row as $field => $value) { + $con['C_NAME'][$i] = ''; + $con['DTN'][$i] = rawurlencode($GLOBALS['db'] . "." . $val[0]); + $con['DCN'][$i] = rawurlencode($field); + $con['STN'][$i] = rawurlencode( + $value['foreign_db'] . "." . $value['foreign_table'] + ); + $con['SCN'][$i] = rawurlencode($value['foreign_field']); + $i++; + } + } + $row = $this->relation->getForeigners($GLOBALS['db'], $val[0], '', 'foreign'); + + // We do not have access to the foreign keys if he user has partial access to the columns + if ($row !== false && isset($row['foreign_keys_data'])) { + foreach ($row['foreign_keys_data'] as $one_key) { + foreach ($one_key['index_list'] as $index => $one_field) { + $con['C_NAME'][$i] = rawurlencode($one_key['constraint']); + $con['DTN'][$i] = rawurlencode($GLOBALS['db'] . "." . $val[0]); + $con['DCN'][$i] = rawurlencode($one_field); + $con['STN'][$i] = rawurlencode( + (isset($one_key['ref_db_name']) ? + $one_key['ref_db_name'] : $GLOBALS['db']) + . "." . $one_key['ref_table_name'] + ); + $con['SCN'][$i] = rawurlencode($one_key['ref_index_list'][$index]); + $i++; + } + } + } + } + + $tableDbNames = []; + foreach($designerTables as $designerTable) { + $tableDbNames[] = $designerTable->getDbTableString(); + } + + $ti = 0; + $retval = array(); + for ($i = 0, $cnt = count($con["C_NAME"]); $i < $cnt; $i++) { + $c_name_i = $con['C_NAME'][$i]; + $dtn_i = $con['DTN'][$i]; + $retval[$ti] = array(); + $retval[$ti][$c_name_i] = array(); + if (in_array($dtn_i, $tableDbNames) && in_array($con['STN'][$i], $tableDbNames)) { + $retval[$ti][$c_name_i][$dtn_i] = array(); + $retval[$ti][$c_name_i][$dtn_i][$con['DCN'][$i]] = array( + 0 => $con['STN'][$i], + 1 => $con['SCN'][$i] + ); + } + $ti++; + } + return $retval; + } + + /** + * Returns UNIQUE and PRIMARY indices + * + * @param DesignerTable[] $designerTables The designer tables + * @return array unique or primary indices + */ + public function getPkOrUniqueKeys($designerTables) + { + return $this->getAllKeys($designerTables, true); + } + + /** + * Returns all indices + * + * @param DesignerTable[] $designerTables The designer tables + * @param bool $unique_only whether to include only unique ones + * + * @return array indices + */ + public function getAllKeys($designerTables, $unique_only = false) + { + $keys = array(); + + foreach ($designerTables as $designerTable) { + $schema = $designerTable->getDatabaseName(); + // for now, take into account only the first index segment + foreach (Index::getFromTable($designerTable->getTableName(), $schema) as $index) { + if ($unique_only && ! $index->isUnique()) { + continue; + } + $columns = $index->getColumns(); + foreach ($columns as $column_name => $dummy) { + $keys[$schema . '.' . $designerTable->getTableName() . '.' . $column_name] = 1; + } + } + } + return $keys; + } + + /** + * Return j_tab and h_tab arrays + * + * @param DesignerTable[] $designerTables The designer tables + * @return array + */ + public function getScriptTabs($designerTables) + { + $retval = array( + 'j_tabs' => array(), + 'h_tabs' => array() + ); + + foreach($designerTables as $designerTable) { + $key = rawurlencode($designerTable->getDbTableString()); + $retval['j_tabs'][$key] = $designerTable->supportsForeignkeys() ? 1 : 0; + $retval['h_tabs'][$key] = 1; + } + + return $retval; + } + + /** + * Returns table positions of a given pdf page + * + * @param int $pg pdf page id + * + * @return array of table positions + */ + public function getTablePositions($pg) + { + $cfgRelation = $this->relation->getRelationsParam(); + if (! $cfgRelation['pdfwork']) { + return array(); + } + + $query = " + SELECT CONCAT_WS('.', `db_name`, `table_name`) AS `name`, + `db_name` as `dbName`, `table_name` as `tableName`, + `x` AS `X`, + `y` AS `Y`, + 1 AS `V`, + 1 AS `H` + FROM " . Util::backquote($cfgRelation['db']) + . "." . Util::backquote($cfgRelation['table_coords']) . " + WHERE pdf_page_number = " . intval($pg); + + $tab_pos = $GLOBALS['dbi']->fetchResult( + $query, + 'name', + null, + DatabaseInterface::CONNECT_CONTROL, + DatabaseInterface::QUERY_STORE + ); + return $tab_pos; + } + + /** + * Returns page name of a given pdf page + * + * @param int $pg pdf page id + * + * @return string|null table name + */ + public function getPageName($pg) + { + $cfgRelation = $this->relation->getRelationsParam(); + if (! $cfgRelation['pdfwork']) { + return null; + } + + $query = "SELECT `page_descr`" + . " FROM " . Util::backquote($cfgRelation['db']) + . "." . Util::backquote($cfgRelation['pdf_pages']) + . " WHERE " . Util::backquote('page_nr') . " = " . intval($pg); + $page_name = $GLOBALS['dbi']->fetchResult( + $query, + null, + null, + DatabaseInterface::CONNECT_CONTROL, + DatabaseInterface::QUERY_STORE + ); + return ( is_array($page_name) && isset($page_name[0]) ) ? $page_name[0] : null; + } + + /** + * Deletes a given pdf page and its corresponding coordinates + * + * @param int $pg page id + * + * @return boolean success/failure + */ + public function deletePage($pg) + { + $cfgRelation = $this->relation->getRelationsParam(); + if (! $cfgRelation['pdfwork']) { + return false; + } + + $query = "DELETE FROM " . Util::backquote($cfgRelation['db']) + . "." . Util::backquote($cfgRelation['table_coords']) + . " WHERE " . Util::backquote('pdf_page_number') . " = " . intval($pg); + $success = $this->relation->queryAsControlUser( + $query, true, DatabaseInterface::QUERY_STORE + ); + + if ($success) { + $query = "DELETE FROM " . Util::backquote($cfgRelation['db']) + . "." . Util::backquote($cfgRelation['pdf_pages']) + . " WHERE " . Util::backquote('page_nr') . " = " . intval($pg); + $success = $this->relation->queryAsControlUser( + $query, true, DatabaseInterface::QUERY_STORE + ); + } + + return (boolean) $success; + } + + /** + * Returns the id of the default pdf page of the database. + * Default page is the one which has the same name as the database. + * + * @param string $db database + * + * @return int id of the default pdf page for the database + */ + public function getDefaultPage($db) + { + $cfgRelation = $this->relation->getRelationsParam(); + if (! $cfgRelation['pdfwork']) { + return -1; + } + + $query = "SELECT `page_nr`" + . " FROM " . Util::backquote($cfgRelation['db']) + . "." . Util::backquote($cfgRelation['pdf_pages']) + . " WHERE `db_name` = '" . $GLOBALS['dbi']->escapeString($db) . "'" + . " AND `page_descr` = '" . $GLOBALS['dbi']->escapeString($db) . "'"; + + $default_page_no = $GLOBALS['dbi']->fetchResult( + $query, + null, + null, + DatabaseInterface::CONNECT_CONTROL, + DatabaseInterface::QUERY_STORE + ); + + if (is_array($default_page_no) && isset($default_page_no[0])) { + return intval($default_page_no[0]); + } + return -1; + } + + /** + * Get the id of the page to load. If a default page exists it will be returned. + * If no such exists, returns the id of the first page of the database. + * + * @param string $db database + * + * @return int id of the page to load + */ + public function getLoadingPage($db) + { + $cfgRelation = $this->relation->getRelationsParam(); + if (! $cfgRelation['pdfwork']) { + return -1; + } + + $page_no = -1; + + $default_page_no = $this->getDefaultPage($db); + if ($default_page_no != -1) { + $page_no = $default_page_no; + } else { + $query = "SELECT MIN(`page_nr`)" + . " FROM " . Util::backquote($cfgRelation['db']) + . "." . Util::backquote($cfgRelation['pdf_pages']) + . " WHERE `db_name` = '" . $GLOBALS['dbi']->escapeString($db) . "'"; + + $min_page_no = $GLOBALS['dbi']->fetchResult( + $query, + null, + null, + DatabaseInterface::CONNECT_CONTROL, + DatabaseInterface::QUERY_STORE + ); + if (is_array($min_page_no) && isset($min_page_no[0])) { + $page_no = $min_page_no[0]; + } + } + return intval($page_no); + } + + /** + * Creates a new page and returns its auto-incrementing id + * + * @param string $pageName name of the page + * @param string $db name of the database + * + * @return int|null + */ + public function createNewPage($pageName, $db) + { + $cfgRelation = $this->relation->getRelationsParam(); + if ($cfgRelation['pdfwork']) { + $pageNumber = $this->relation->createPage( + $pageName, + $cfgRelation, + $db + ); + return $pageNumber; + } + return null; + } + + /** + * Saves positions of table(s) of a given pdf page + * + * @param int $pg pdf page id + * + * @return boolean success/failure + */ + public function saveTablePositions($pg) + { + $pageId = $GLOBALS['dbi']->escapeString($pg); + + $db = $GLOBALS['dbi']->escapeString($_POST['db']); + + $cfgRelation = $this->relation->getRelationsParam(); + if (! $cfgRelation['pdfwork']) { + return false; + } + + $query = "DELETE FROM " + . Util::backquote($GLOBALS['cfgRelation']['db']) + . "." . Util::backquote( + $GLOBALS['cfgRelation']['table_coords'] + ) + . " WHERE `pdf_page_number` = '" . $pageId . "'"; + + $res = $this->relation->queryAsControlUser( + $query, + true, + DatabaseInterface::QUERY_STORE + ); + + if (!$res) { + return (boolean)$res; + } + + foreach ($_POST['t_h'] as $key => $value) { + $DB = $_POST['t_db'][$key]; + $TAB = $_POST['t_tbl'][$key]; + if (!$value) { + continue; + } + + $query = "INSERT INTO " + . Util::backquote($GLOBALS['cfgRelation']['db']) . "." + . Util::backquote($GLOBALS['cfgRelation']['table_coords']) + . " (`db_name`, `table_name`, `pdf_page_number`, `x`, `y`)" + . " VALUES (" + . "'" . $GLOBALS['dbi']->escapeString($DB) . "', " + . "'" . $GLOBALS['dbi']->escapeString($TAB) . "', " + . "'" . $pageId . "', " + . "'" . $GLOBALS['dbi']->escapeString($_POST['t_x'][$key]) . "', " + . "'" . $GLOBALS['dbi']->escapeString($_POST['t_y'][$key]) . "')"; + + $res = $this->relation->queryAsControlUser( + $query, true, DatabaseInterface::QUERY_STORE + ); + } + + return (boolean) $res; + } + + /** + * Saves the display field for a table. + * + * @param string $db database name + * @param string $table table name + * @param string $field display field name + * + * @return boolean + */ + public function saveDisplayField($db, $table, $field) + { + $cfgRelation = $this->relation->getRelationsParam(); + if (!$cfgRelation['displaywork']) { + return false; + } + + $upd_query = new Table($table, $db, $GLOBALS['dbi']); + $upd_query->updateDisplayField($field, $cfgRelation); + + return true; + } + + /** + * Adds a new foreign relation + * + * @param string $db database name + * @param string $T1 foreign table + * @param string $F1 foreign field + * @param string $T2 master table + * @param string $F2 master field + * @param string $on_delete on delete action + * @param string $on_update on update action + * @param string $DB1 database + * @param string $DB2 database + * + * @return array array of success/failure and message + */ + public function addNewRelation($db, $T1, $F1, $T2, $F2, $on_delete, $on_update, $DB1, $DB2) + { + $tables = $GLOBALS['dbi']->getTablesFull($DB1, $T1); + $type_T1 = mb_strtoupper($tables[$T1]['ENGINE']); + $tables = $GLOBALS['dbi']->getTablesFull($DB2, $T2); + $type_T2 = mb_strtoupper($tables[$T2]['ENGINE']); + + // native foreign key + if (Util::isForeignKeySupported($type_T1) + && Util::isForeignKeySupported($type_T2) + && $type_T1 == $type_T2 + ) { + // relation exists? + $existrel_foreign = $this->relation->getForeigners($DB2, $T2, '', 'foreign'); + $foreigner = $this->relation->searchColumnInForeigners($existrel_foreign, $F2); + if ($foreigner + && isset($foreigner['constraint']) + ) { + return array(false, __('Error: relationship already exists.')); + } + // note: in InnoDB, the index does not requires to be on a PRIMARY + // or UNIQUE key + // improve: check all other requirements for InnoDB relations + $result = $GLOBALS['dbi']->query( + 'SHOW INDEX FROM ' . Util::backquote($DB1) + . '.' . Util::backquote($T1) . ';' + ); + + // will be use to emphasis prim. keys in the table view + $index_array1 = array(); + while ($row = $GLOBALS['dbi']->fetchAssoc($result)) { + $index_array1[$row['Column_name']] = 1; + } + $GLOBALS['dbi']->freeResult($result); + + $result = $GLOBALS['dbi']->query( + 'SHOW INDEX FROM ' . Util::backquote($DB2) + . '.' . Util::backquote($T2) . ';' + ); + // will be used to emphasis prim. keys in the table view + $index_array2 = array(); + while ($row = $GLOBALS['dbi']->fetchAssoc($result)) { + $index_array2[$row['Column_name']] = 1; + } + $GLOBALS['dbi']->freeResult($result); + + if (! empty($index_array1[$F1]) && ! empty($index_array2[$F2])) { + $upd_query = 'ALTER TABLE ' . Util::backquote($DB2) + . '.' . Util::backquote($T2) + . ' ADD FOREIGN KEY (' + . Util::backquote($F2) . ')' + . ' REFERENCES ' + . Util::backquote($DB1) . '.' + . Util::backquote($T1) . '(' + . Util::backquote($F1) . ')'; + + if ($on_delete != 'nix') { + $upd_query .= ' ON DELETE ' . $on_delete; + } + if ($on_update != 'nix') { + $upd_query .= ' ON UPDATE ' . $on_update; + } + $upd_query .= ';'; + if ($GLOBALS['dbi']->tryQuery($upd_query)) { + return array(true, __('FOREIGN KEY relationship has been added.')); + } + + $error = $GLOBALS['dbi']->getError(); + return array( + false, + __('Error: FOREIGN KEY relationship could not be added!') + . "
        " . $error + ); + } + + return array(false, __('Error: Missing index on column(s).')); + } + + // internal (pmadb) relation + if ($GLOBALS['cfgRelation']['relwork'] == false) { + return array(false, __('Error: Relational features are disabled!')); + } + + // no need to recheck if the keys are primary or unique at this point, + // this was checked on the interface part + + $q = "INSERT INTO " + . Util::backquote($GLOBALS['cfgRelation']['db']) + . "." + . Util::backquote($GLOBALS['cfgRelation']['relation']) + . "(master_db, master_table, master_field, " + . "foreign_db, foreign_table, foreign_field)" + . " values(" + . "'" . $GLOBALS['dbi']->escapeString($DB2) . "', " + . "'" . $GLOBALS['dbi']->escapeString($T2) . "', " + . "'" . $GLOBALS['dbi']->escapeString($F2) . "', " + . "'" . $GLOBALS['dbi']->escapeString($DB1) . "', " + . "'" . $GLOBALS['dbi']->escapeString($T1) . "', " + . "'" . $GLOBALS['dbi']->escapeString($F1) . "')"; + + if ($this->relation->queryAsControlUser($q, false, DatabaseInterface::QUERY_STORE) + ) { + return array(true, __('Internal relationship has been added.')); + } + + $error = $GLOBALS['dbi']->getError(DatabaseInterface::CONNECT_CONTROL); + return array( + false, + __('Error: Internal relationship could not be added!') + . "
        " . $error + ); + } + + /** + * Removes a foreign relation + * + * @param string $T1 foreign db.table + * @param string $F1 foreign field + * @param string $T2 master db.table + * @param string $F2 master field + * + * @return array array of success/failure and message + */ + public function removeRelation($T1, $F1, $T2, $F2) + { + list($DB1, $T1) = explode(".", $T1); + list($DB2, $T2) = explode(".", $T2); + + $tables = $GLOBALS['dbi']->getTablesFull($DB1, $T1); + $type_T1 = mb_strtoupper($tables[$T1]['ENGINE']); + $tables = $GLOBALS['dbi']->getTablesFull($DB2, $T2); + $type_T2 = mb_strtoupper($tables[$T2]['ENGINE']); + + if (Util::isForeignKeySupported($type_T1) + && Util::isForeignKeySupported($type_T2) + && $type_T1 == $type_T2 + ) { + // InnoDB + $existrel_foreign = $this->relation->getForeigners($DB2, $T2, '', 'foreign'); + $foreigner = $this->relation->searchColumnInForeigners($existrel_foreign, $F2); + + if (isset($foreigner['constraint'])) { + $upd_query = 'ALTER TABLE ' . Util::backquote($DB2) + . '.' . Util::backquote($T2) . ' DROP FOREIGN KEY ' + . Util::backquote($foreigner['constraint']) . ';'; + if ($GLOBALS['dbi']->query($upd_query)) { + return array(true, __('FOREIGN KEY relationship has been removed.')); + } + + $error = $GLOBALS['dbi']->getError(); + return array( + false, + __('Error: FOREIGN KEY relationship could not be removed!') + . "
        " . $error + ); + } + } + + // internal relations + $delete_query = "DELETE FROM " + . Util::backquote($GLOBALS['cfgRelation']['db']) . "." + . $GLOBALS['cfgRelation']['relation'] . " WHERE " + . "master_db = '" . $GLOBALS['dbi']->escapeString($DB2) . "'" + . " AND master_table = '" . $GLOBALS['dbi']->escapeString($T2) . "'" + . " AND master_field = '" . $GLOBALS['dbi']->escapeString($F2) . "'" + . " AND foreign_db = '" . $GLOBALS['dbi']->escapeString($DB1) . "'" + . " AND foreign_table = '" . $GLOBALS['dbi']->escapeString($T1) . "'" + . " AND foreign_field = '" . $GLOBALS['dbi']->escapeString($F1) . "'"; + + $result = $this->relation->queryAsControlUser( + $delete_query, + false, + DatabaseInterface::QUERY_STORE + ); + + if (!$result) { + $error = $GLOBALS['dbi']->getError(DatabaseInterface::CONNECT_CONTROL); + return array( + false, + __('Error: Internal relationship could not be removed!') . "
        " . $error + ); + } + + return array(true, __('Internal relationship has been removed.')); + } + + /** + * Save value for a designer setting + * + * @param string $index setting + * @param string $value value + * + * @return bool whether the operation succeeded + */ + public function saveSetting($index, $value) + { + $cfgRelation = $this->relation->getRelationsParam(); + $success = true; + if ($GLOBALS['cfgRelation']['designersettingswork']) { + + $cfgDesigner = array( + 'user' => $GLOBALS['cfg']['Server']['user'], + 'db' => $cfgRelation['db'], + 'table' => $cfgRelation['designer_settings'] + ); + + $orig_data_query = "SELECT settings_data" + . " FROM " . Util::backquote($cfgDesigner['db']) + . "." . Util::backquote($cfgDesigner['table']) + . " WHERE username = '" + . $GLOBALS['dbi']->escapeString($cfgDesigner['user']) . "';"; + + $orig_data = $GLOBALS['dbi']->fetchSingleRow( + $orig_data_query, 'ASSOC', DatabaseInterface::CONNECT_CONTROL + ); + + if (! empty($orig_data)) { + $orig_data = json_decode($orig_data['settings_data'], true); + $orig_data[$index] = $value; + $orig_data = json_encode($orig_data); + + $save_query = "UPDATE " + . Util::backquote($cfgDesigner['db']) + . "." . Util::backquote($cfgDesigner['table']) + . " SET settings_data = '" . $orig_data . "'" + . " WHERE username = '" + . $GLOBALS['dbi']->escapeString($cfgDesigner['user']) . "';"; + + $success = $this->relation->queryAsControlUser($save_query); + } else { + $save_data = array($index => $value); + + $query = "INSERT INTO " + . Util::backquote($cfgDesigner['db']) + . "." . Util::backquote($cfgDesigner['table']) + . " (username, settings_data)" + . " VALUES('" . $GLOBALS['dbi']->escapeString($cfgDesigner['user']) + . "', '" . json_encode($save_data) . "');"; + + $success = $this->relation->queryAsControlUser($query); + } + } + + return (bool) $success; + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Database/Designer/DesignerTable.php b/php/apps/phpmyadmin49/libraries/classes/Database/Designer/DesignerTable.php new file mode 100644 index 00000000..bfdd48f0 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Database/Designer/DesignerTable.php @@ -0,0 +1,88 @@ +databaseName = $databaseName; + $this->tableName = $tableName; + $this->tableEngine = $tableEngine; + $this->displayField = $displayField; + } + + /** + * The table engine supports or not foreign keys + * + * @return bool + */ + public function supportsForeignkeys() { + return Util::isForeignKeySupported($this->tableEngine); + } + + /** + * Get the database name + * + * @return string + */ + public function getDatabaseName() { + return $this->databaseName; + } + + /** + * Get the table name + * + * @return string + */ + public function getTableName() { + return $this->tableName; + } + + /** + * Get the table engine + * + * @return string + */ + public function getTableEngine() { + return $this->tableEngine; + } + + /** + * Get the db and table speparated with a dot + * + * @return string + */ + public function getDbTableString() { + return $this->databaseName . '.' . $this->tableName; + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Database/MultiTableQuery.php b/php/apps/phpmyadmin49/libraries/classes/Database/MultiTableQuery.php new file mode 100644 index 00000000..a1b1f745 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Database/MultiTableQuery.php @@ -0,0 +1,135 @@ +dbi = $dbi; + $this->db = $dbName; + $this->defaultNoOfColumns = $defaultNoOfColumns; + + $this->tables = $this->dbi->getTables($this->db); + } + + /** + * Get Multi-Table query page HTML + * + * @return string Multi-Table query page HTML + */ + public function getFormHtml() + { + $tables = []; + foreach($this->tables as $table) { + $tables[$table]['hash'] = md5($table); + $tables[$table]['columns'] = array_keys( + $this->dbi->getColumns($this->db, $table) + ); + } + return Template::get('database/multi_table_query/form')->render([ + 'db' => $this->db, + 'tables' => $tables, + 'default_no_of_columns' => $this->defaultNoOfColumns, + ]); + } + + /** + * Displays multi-table query results + * + * @param string $sqlQuery The query to parse + * @param string $db The current database + * @param string $pmaThemeImage Uri of the PMA theme image + * + * @return void + */ + public static function displayResults($sqlQuery, $db, $pmaThemeImage) + { + list( + $analyzedSqlResults, + $db, + $tableFromSql + ) = ParseAnalyze::sqlQuery($sqlQuery, $db); + + extract($analyzedSqlResults); + $goto = 'db_multi_table_query.php'; + $sql = new Sql(); + $sql->executeQueryAndSendQueryResponse( + null, // analyzed_sql_results + false, // is_gotofile + $db, // db + null, // table + null, // find_real_end + null, // sql_query_for_bookmark - see below + null, // extra_data + null, // message_to_show + null, // message + null, // sql_data + $goto, // goto + $pmaThemeImage, // pmaThemeImage + null, // disp_query + null, // disp_message + null, // query_type + $sqlQuery, // sql_query + null, // selectedTables + null // complete_query + ); + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Database/Qbe.php b/php/apps/phpmyadmin49/libraries/classes/Database/Qbe.php new file mode 100644 index 00000000..9f7bd2a9 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Database/Qbe.php @@ -0,0 +1,1961 @@ +_db = $dbname; + $this->_savedSearchList = $savedSearchList; + $this->_currentSearch = $currentSearch; + $this->_loadCriterias(); + // Sets criteria parameters + $this->_setSearchParams(); + $this->_setCriteriaTablesAndColumns(); + $this->relation = new Relation(); + } + + /** + * Initialize criterias + * + * @return static + */ + private function _loadCriterias() + { + if (null === $this->_currentSearch + || null === $this->_currentSearch->getCriterias() + ) { + return $this; + } + + $criterias = $this->_currentSearch->getCriterias(); + $_POST = $criterias + $_POST; + + return $this; + } + + /** + * Getter for current search + * + * @return SavedSearches + */ + private function _getCurrentSearch() + { + return $this->_currentSearch; + } + + /** + * Sets search parameters + * + * @return void + */ + private function _setSearchParams() + { + $criteriaColumnCount = $this->_initializeCriteriasCount(); + + $this->_criteriaColumnInsert = Core::ifSetOr( + $_POST['criteriaColumnInsert'], + null, + 'array' + ); + $this->_criteriaColumnDelete = Core::ifSetOr( + $_POST['criteriaColumnDelete'], + null, + 'array' + ); + + $this->_prev_criteria = isset($_POST['prev_criteria']) + ? $_POST['prev_criteria'] + : array(); + $this->_criteria = isset($_POST['criteria']) + ? $_POST['criteria'] + : array_fill(0, $criteriaColumnCount, ''); + + $this->_criteriaRowInsert = isset($_POST['criteriaRowInsert']) + ? $_POST['criteriaRowInsert'] + : array_fill(0, $criteriaColumnCount, ''); + $this->_criteriaRowDelete = isset($_POST['criteriaRowDelete']) + ? $_POST['criteriaRowDelete'] + : array_fill(0, $criteriaColumnCount, ''); + $this->_criteriaAndOrRow = isset($_POST['criteriaAndOrRow']) + ? $_POST['criteriaAndOrRow'] + : array_fill(0, $criteriaColumnCount, ''); + $this->_criteriaAndOrColumn = isset($_POST['criteriaAndOrColumn']) + ? $_POST['criteriaAndOrColumn'] + : array_fill(0, $criteriaColumnCount, ''); + // sets minimum width + $this->_form_column_width = 12; + $this->_formColumns = array(); + $this->_formSorts = array(); + $this->_formShows = array(); + $this->_formCriterions = array(); + $this->_formAndOrRows = array(); + $this->_formAndOrCols = array(); + } + + /** + * Sets criteria tables and columns + * + * @return void + */ + private function _setCriteriaTablesAndColumns() + { + // The tables list sent by a previously submitted form + if (Core::isValid($_POST['TableList'], 'array')) { + foreach ($_POST['TableList'] as $each_table) { + $this->_criteriaTables[$each_table] = ' selected="selected"'; + } + } // end if + $all_tables = $GLOBALS['dbi']->query( + 'SHOW TABLES FROM ' . Util::backquote($this->_db) . ';', + DatabaseInterface::CONNECT_USER, + DatabaseInterface::QUERY_STORE + ); + $all_tables_count = $GLOBALS['dbi']->numRows($all_tables); + if (0 == $all_tables_count) { + Message::error(__('No tables found in database.'))->display(); + exit; + } + // The tables list gets from MySQL + while (list($table) = $GLOBALS['dbi']->fetchRow($all_tables)) { + $columns = $GLOBALS['dbi']->getColumns($this->_db, $table); + + if (empty($this->_criteriaTables[$table]) + && ! empty($_POST['TableList']) + ) { + $this->_criteriaTables[$table] = ''; + } else { + $this->_criteriaTables[$table] = ' selected="selected"'; + } // end if + + // The fields list per selected tables + if ($this->_criteriaTables[$table] == ' selected="selected"') { + $each_table = Util::backquote($table); + $this->_columnNames[] = $each_table . '.*'; + foreach ($columns as $each_column) { + $each_column = $each_table . '.' + . Util::backquote($each_column['Field']); + $this->_columnNames[] = $each_column; + // increase the width if necessary + $this->_form_column_width = max( + mb_strlen($each_column), + $this->_form_column_width + ); + } // end foreach + } // end if + } // end while + $GLOBALS['dbi']->freeResult($all_tables); + + // sets the largest width found + $this->_realwidth = $this->_form_column_width . 'ex'; + } + /** + * Provides select options list containing column names + * + * @param integer $column_number Column Number (0,1,2) or more + * @param string $selected Selected criteria column name + * + * @return string HTML for select options + */ + private function _showColumnSelectCell($column_number, $selected = '') + { + return Template::get('database/qbe/column_select_cell')->render([ + 'column_number' => $column_number, + 'column_names' => $this->_columnNames, + 'selected' => $selected, + ]); + } + + /** + * Provides select options list containing sort options (ASC/DESC) + * + * @param integer $columnNumber Column Number (0,1,2) or more + * @param string $selected Selected criteria 'ASC' or 'DESC' + * + * @return string HTML for select options + */ + private function _getSortSelectCell( + $columnNumber, + $selected = '' + ) { + return Template::get('database/qbe/sort_select_cell')->render([ + 'real_width' => $this->_realwidth, + 'column_number' => $columnNumber, + 'selected' => $selected, + ]); + } + + /** + * Provides select options list containing sort order + * + * @param integer $columnNumber Column Number (0,1,2) or more + * @param integer $sortOrder Sort order + * + * @return string HTML for select options + */ + private function _getSortOrderSelectCell($columnNumber, $sortOrder) + { + $totalColumnCount = $this->_getNewColumnCount(); + return Template::get('database/qbe/sort_order_select_cell')->render([ + 'total_column_count' => $totalColumnCount, + 'column_number' => $columnNumber, + 'sort_order' => $sortOrder, + ]); + } + + /** + * Returns the new column count after adding and removing columns as instructed + * + * @return int new column count + */ + private function _getNewColumnCount() + { + $totalColumnCount = $this->_criteria_column_count; + if (! empty($this->_criteriaColumnInsert)) { + $totalColumnCount += count($this->_criteriaColumnInsert); + } + if (! empty($this->_criteriaColumnDelete)) { + $totalColumnCount -= count($this->_criteriaColumnDelete); + } + return $totalColumnCount; + } + + /** + * Provides search form's row containing column select options + * + * @return string HTML for search table's row + */ + private function _getColumnNamesRow() + { + $html_output = ''; + $html_output .= '' . __('Column:') . ''; + $new_column_count = 0; + for ( + $column_index = 0; + $column_index < $this->_criteria_column_count; + $column_index++ + ) { + if (isset($this->_criteriaColumnInsert[$column_index]) + && $this->_criteriaColumnInsert[$column_index] == 'on' + ) { + $html_output .= $this->_showColumnSelectCell( + $new_column_count + ); + $new_column_count++; + } + if (! empty($this->_criteriaColumnDelete) + && isset($this->_criteriaColumnDelete[$column_index]) + && $this->_criteriaColumnDelete[$column_index] == 'on' + ) { + continue; + } + $selected = ''; + if (isset($_POST['criteriaColumn'][$column_index])) { + $selected = $_POST['criteriaColumn'][$column_index]; + $this->_formColumns[$new_column_count] + = $_POST['criteriaColumn'][$column_index]; + } + $html_output .= $this->_showColumnSelectCell( + $new_column_count, + $selected + ); + $new_column_count++; + } // end for + $this->_new_column_count = $new_column_count; + $html_output .= ''; + return $html_output; + } + + /** + * Provides search form's row containing column aliases + * + * @return string HTML for search table's row + */ + private function _getColumnAliasRow() + { + $html_output = ''; + $html_output .= '' . __('Alias:') . ''; + $new_column_count = 0; + + for ( + $colInd = 0; + $colInd < $this->_criteria_column_count; + $colInd++ + ) { + if (! empty($this->_criteriaColumnInsert) + && isset($this->_criteriaColumnInsert[$colInd]) + && $this->_criteriaColumnInsert[$colInd] == 'on' + ) { + $html_output .= ''; + $html_output .= ''; + $html_output .= ''; + $new_column_count++; + } // end if + + if (! empty($this->_criteriaColumnDelete) + && isset($this->_criteriaColumnDelete[$colInd]) + && $this->_criteriaColumnDelete[$colInd] == 'on' + ) { + continue; + } + + $tmp_alias = ''; + if (! empty($_POST['criteriaAlias'][$colInd])) { + $tmp_alias + = $this->_formAliases[$new_column_count] + = $_POST['criteriaAlias'][$colInd]; + }// end if + + $html_output .= ''; + $html_output .= ''; + $html_output .= ''; + $new_column_count++; + } // end for + $html_output .= ''; + return $html_output; + } + + /** + * Provides search form's row containing sort(ASC/DESC) select options + * + * @return string HTML for search table's row + */ + private function _getSortRow() + { + $html_output = ''; + $html_output .= '' . __('Sort:') . ''; + $new_column_count = 0; + + for ( + $colInd = 0; + $colInd < $this->_criteria_column_count; + $colInd++ + ) { + if (! empty($this->_criteriaColumnInsert) + && isset($this->_criteriaColumnInsert[$colInd]) + && $this->_criteriaColumnInsert[$colInd] == 'on' + ) { + $html_output .= $this->_getSortSelectCell($new_column_count); + $new_column_count++; + } // end if + + if (! empty($this->_criteriaColumnDelete) + && isset($this->_criteriaColumnDelete[$colInd]) + && $this->_criteriaColumnDelete[$colInd] == 'on' + ) { + continue; + } + // If they have chosen all fields using the * selector, + // then sorting is not available, Fix for Bug #570698 + if (isset($_POST['criteriaSort'][$colInd]) + && isset($_POST['criteriaColumn'][$colInd]) + && mb_substr($_POST['criteriaColumn'][$colInd], -2) == '.*' + ) { + $_POST['criteriaSort'][$colInd] = ''; + } //end if + + $selected = ''; + if (isset($_POST['criteriaSort'][$colInd])) { + $this->_formSorts[$new_column_count] + = $_POST['criteriaSort'][$colInd]; + + if ($_POST['criteriaSort'][$colInd] == 'ASC') { + $selected = 'ASC'; + } elseif ($_POST['criteriaSort'][$colInd] == 'DESC') { + $selected = 'DESC'; + } + } else { + $this->_formSorts[$new_column_count] = ''; + } + + $html_output .= $this->_getSortSelectCell( + $new_column_count, $selected + ); + $new_column_count++; + } // end for + $html_output .= ''; + return $html_output; + } + + /** + * Provides search form's row containing sort order + * + * @return string HTML for search table's row + */ + private function _getSortOrder() + { + $html_output = ''; + $html_output .= '' . __('Sort order:') . ''; + $new_column_count = 0; + + for ( + $colInd = 0; + $colInd < $this->_criteria_column_count; + $colInd++ + ) { + if (! empty($this->_criteriaColumnInsert) + && isset($this->_criteriaColumnInsert[$colInd]) + && $this->_criteriaColumnInsert[$colInd] == 'on' + ) { + $html_output .= $this->_getSortOrderSelectCell( + $new_column_count, null + ); + $new_column_count++; + } // end if + + if (! empty($this->_criteriaColumnDelete) + && isset($this->_criteriaColumnDelete[$colInd]) + && $this->_criteriaColumnDelete[$colInd] == 'on' + ) { + continue; + } + + $sortOrder = null; + if (! empty($_POST['criteriaSortOrder'][$colInd])) { + $sortOrder + = $this->_formSortOrders[$new_column_count] + = $_POST['criteriaSortOrder'][$colInd]; + } + + $html_output .= $this->_getSortOrderSelectCell( + $new_column_count, $sortOrder + ); + $new_column_count++; + } // end for + $html_output .= ''; + return $html_output; + } + + /** + * Provides search form's row containing SHOW checkboxes + * + * @return string HTML for search table's row + */ + private function _getShowRow() + { + $html_output = ''; + $html_output .= '' . __('Show:') . ''; + $new_column_count = 0; + for ( + $column_index = 0; + $column_index < $this->_criteria_column_count; + $column_index++ + ) { + if (! empty($this->_criteriaColumnInsert) + && isset($this->_criteriaColumnInsert[$column_index]) + && $this->_criteriaColumnInsert[$column_index] == 'on' + ) { + $html_output .= ''; + $html_output .= ''; + $html_output .= ''; + $new_column_count++; + } // end if + if (! empty($this->_criteriaColumnDelete) + && isset($this->_criteriaColumnDelete[$column_index]) + && $this->_criteriaColumnDelete[$column_index] == 'on' + ) { + continue; + } + if (isset($_POST['criteriaShow'][$column_index])) { + $checked_options = ' checked="checked"'; + $this->_formShows[$new_column_count] + = $_POST['criteriaShow'][$column_index]; + } else { + $checked_options = ''; + } + $html_output .= ''; + $html_output .= ''; + $html_output .= ''; + $new_column_count++; + } // end for + $html_output .= ''; + return $html_output; + } + + /** + * Provides search form's row containing criteria Inputboxes + * + * @return string HTML for search table's row + */ + private function _getCriteriaInputboxRow() + { + $html_output = ''; + $html_output .= '' . __('Criteria:') . ''; + $new_column_count = 0; + for ( + $column_index = 0; + $column_index < $this->_criteria_column_count; + $column_index++ + ) { + if (! empty($this->_criteriaColumnInsert) + && isset($this->_criteriaColumnInsert[$column_index]) + && $this->_criteriaColumnInsert[$column_index] == 'on' + ) { + $html_output .= ''; + $html_output .= ''; + $html_output .= ''; + $new_column_count++; + } // end if + if (! empty($this->_criteriaColumnDelete) + && isset($this->_criteriaColumnDelete[$column_index]) + && $this->_criteriaColumnDelete[$column_index] == 'on' + ) { + continue; + } + if (isset($this->_criteria[$column_index])) { + $tmp_criteria = $this->_criteria[$column_index]; + } + if ((empty($this->_prev_criteria) + || ! isset($this->_prev_criteria[$column_index])) + || $this->_prev_criteria[$column_index] != htmlspecialchars($tmp_criteria) + ) { + $this->_formCriterions[$new_column_count] = $tmp_criteria; + } else { + $this->_formCriterions[$new_column_count] + = $this->_prev_criteria[$column_index]; + } + $html_output .= ''; + $html_output .= ''; + $html_output .= ''; + $html_output .= ''; + $new_column_count++; + } // end for + $html_output .= ''; + return $html_output; + } + + /** + * Provides footer options for adding/deleting row/columns + * + * @param string $type Whether row or column + * + * @return string HTML for footer options + */ + private function _getFootersOptions($type) + { + return Template::get('database/qbe/footer_options')->render([ + 'type' => $type, + ]); + } + + /** + * Provides search form table's footer options + * + * @return string HTML for table footer + */ + private function _getTableFooters() + { + $html_output = '
        '; + $html_output .= $this->_getFootersOptions("row"); + $html_output .= $this->_getFootersOptions("column"); + $html_output .= '
        '; + $html_output .= ''; + $html_output .= '
        '; + $html_output .= '
        '; + return $html_output; + } + + /** + * Provides a select list of database tables + * + * @return string HTML for table select list + */ + private function _getTablesList() + { + $html_output = '
        '; + $html_output .= '
        '; + $html_output .= '' . __('Use Tables') . ''; + // Build the options list for each table name + $options = ''; + $numTableListOptions = 0; + foreach ($this->_criteriaTables as $key => $val) { + $options .= ''; + $numTableListOptions++; + } + $html_output .= ''; + $html_output .= '
        '; + $html_output .= '
        '; + $html_output .= ''; + $html_output .= '
        '; + $html_output .= '
        '; + return $html_output; + } + + /** + * Provides And/Or modification cell along with Insert/Delete options + * (For modifying search form's table columns) + * + * @param integer $column_number Column Number (0,1,2) or more + * @param array|null $selected Selected criteria column name + * @param bool $last_column Whether this is the last column + * + * @return string HTML for modification cell + */ + private function _getAndOrColCell( + $column_number, $selected = null, $last_column = false + ) { + $html_output = ''; + if (! $last_column) { + $html_output .= '' . __('Or:') . ''; + $html_output .= ''; + $html_output .= '  ' . __('And:') . ''; + $html_output .= ''; + } + $html_output .= '
        ' . __('Ins'); + $html_output .= ''; + $html_output .= '  ' . __('Del'); + $html_output .= ''; + $html_output .= ''; + return $html_output; + } + + /** + * Provides search form's row containing column modifications options + * (For modifying search form's table columns) + * + * @return string HTML for search table's row + */ + private function _getModifyColumnsRow() + { + $html_output = ''; + $html_output .= '' . __('Modify:') . ''; + $new_column_count = 0; + for ( + $column_index = 0; + $column_index < $this->_criteria_column_count; + $column_index++ + ) { + if (! empty($this->_criteriaColumnInsert) + && isset($this->_criteriaColumnInsert[$column_index]) + && $this->_criteriaColumnInsert[$column_index] == 'on' + ) { + $html_output .= $this->_getAndOrColCell($new_column_count); + $new_column_count++; + } // end if + + if (! empty($this->_criteriaColumnDelete) + && isset($this->_criteriaColumnDelete[$column_index]) + && $this->_criteriaColumnDelete[$column_index] == 'on' + ) { + continue; + } + + if (isset($this->_criteriaAndOrColumn[$column_index])) { + $this->_formAndOrCols[$new_column_count] + = $this->_criteriaAndOrColumn[$column_index]; + } + $checked_options = array(); + if (isset($this->_criteriaAndOrColumn[$column_index]) + && $this->_criteriaAndOrColumn[$column_index] == 'or' + ) { + $checked_options['or'] = ' checked="checked"'; + $checked_options['and'] = ''; + } else { + $checked_options['and'] = ' checked="checked"'; + $checked_options['or'] = ''; + } + $html_output .= $this->_getAndOrColCell( + $new_column_count, + $checked_options, + ($column_index + 1 == $this->_criteria_column_count) + ); + $new_column_count++; + } // end for + $html_output .= ''; + return $html_output; + } + + /** + * Provides Insert/Delete options for criteria inputbox + * with AND/OR relationship modification options + * + * @param integer $row_index Number of criteria row + * @param array $checked_options If checked + * + * @return string HTML + */ + private function _getInsDelAndOrCell($row_index, array $checked_options) + { + $html_output = ''; + $html_output .= ''; + $html_output .= ''; + $html_output .= ''; + $html_output .= ''; + $html_output .= ''; + $html_output .= ''; + $html_output .= ''; + $html_output .= ''; + $html_output .= ''; + $html_output .= ''; + $html_output .= ''; + $html_output .= ''; + $html_output .= '
        '; + $html_output .= '' . __('Ins:') . ''; + $html_output .= ''; + $html_output .= ''; + $html_output .= '' . __('And:') . ''; + $html_output .= ''; + $html_output .= ''; + $html_output .= '
        '; + $html_output .= '' . __('Del:') . ''; + $html_output .= ''; + $html_output .= ''; + $html_output .= '' . __('Or:') . ''; + $html_output .= ''; + $html_output .= ''; + $html_output .= '
        '; + $html_output .= ''; + return $html_output; + } + + /** + * Provides rows for criteria inputbox Insert/Delete options + * with AND/OR relationship modification options + * + * @param integer $new_row_index New row index if rows are added/deleted + * + * @return string HTML table rows + */ + private function _getInputboxRow($new_row_index) + { + $html_output = ''; + $new_column_count = 0; + for ( + $column_index = 0; + $column_index < $this->_criteria_column_count; + $column_index++ + ) { + if (!empty($this->_criteriaColumnInsert) + && isset($this->_criteriaColumnInsert[$column_index]) + && $this->_criteriaColumnInsert[$column_index] == 'on' + ) { + $orFieldName = 'Or' . $new_row_index . '[' . $new_column_count . ']'; + $html_output .= ''; + $html_output .= ''; + $html_output .= ''; + $new_column_count++; + } // end if + if (!empty($this->_criteriaColumnDelete) + && isset($this->_criteriaColumnDelete[$column_index]) + && $this->_criteriaColumnDelete[$column_index] == 'on' + ) { + continue; + } + $or = 'Or' . $new_row_index; + if (! empty($_POST[$or]) && isset($_POST[$or][$column_index])) { + $tmp_or = $_POST[$or][$column_index]; + } else { + $tmp_or = ''; + } + $html_output .= ''; + $html_output .= ''; + $html_output .= ''; + if (!empty(${$or}) && isset(${$or}[$column_index])) { + $GLOBALS[${'cur' . $or}][$new_column_count] + = ${$or}[$column_index]; + } + $new_column_count++; + } // end for + return $html_output; + } + + /** + * Provides rows for criteria inputbox Insert/Delete options + * with AND/OR relationship modification options + * + * @return string HTML table rows + */ + private function _getInsDelAndOrCriteriaRows() + { + $html_output = ''; + $new_row_count = 0; + $checked_options = array(); + for ( + $row_index = 0; + $row_index <= $this->_criteria_row_count; + $row_index++ + ) { + if (isset($this->_criteriaRowInsert[$row_index]) + && $this->_criteriaRowInsert[$row_index] == 'on' + ) { + $checked_options['or'] = ' checked="checked"'; + $checked_options['and'] = ''; + $html_output .= ''; + $html_output .= $this->_getInsDelAndOrCell( + $new_row_count, $checked_options + ); + $html_output .= $this->_getInputboxRow( + $new_row_count + ); + $new_row_count++; + $html_output .= ''; + } // end if + if (isset($this->_criteriaRowDelete[$row_index]) + && $this->_criteriaRowDelete[$row_index] == 'on' + ) { + continue; + } + if (isset($this->_criteriaAndOrRow[$row_index])) { + $this->_formAndOrRows[$new_row_count] + = $this->_criteriaAndOrRow[$row_index]; + } + if (isset($this->_criteriaAndOrRow[$row_index]) + && $this->_criteriaAndOrRow[$row_index] == 'and' + ) { + $checked_options['and'] = ' checked="checked"'; + $checked_options['or'] = ''; + } else { + $checked_options['or'] = ' checked="checked"'; + $checked_options['and'] = ''; + } + $html_output .= ''; + $html_output .= $this->_getInsDelAndOrCell( + $new_row_count, $checked_options + ); + $html_output .= $this->_getInputboxRow( + $new_row_count + ); + $new_row_count++; + $html_output .= ''; + } // end for + $this->_new_row_count = $new_row_count; + return $html_output; + } + + /** + * Provides SELECT clause for building SQL query + * + * @return string Select clause + */ + private function _getSelectClause() + { + $select_clause = ''; + $select_clauses = array(); + for ( + $column_index = 0; + $column_index < $this->_criteria_column_count; + $column_index++ + ) { + if (! empty($this->_formColumns[$column_index]) + && isset($this->_formShows[$column_index]) + && $this->_formShows[$column_index] == 'on' + ) { + $select = $this->_formColumns[$column_index]; + if (! empty($this->_formAliases[$column_index])) { + $select .= " AS " + . Util::backquote($this->_formAliases[$column_index]); + } + $select_clauses[] = $select; + } + } // end for + if (!empty($select_clauses)) { + $select_clause = 'SELECT ' + . htmlspecialchars(implode(", ", $select_clauses)) . "\n"; + } + return $select_clause; + } + + /** + * Provides WHERE clause for building SQL query + * + * @return string Where clause + */ + private function _getWhereClause() + { + $where_clause = ''; + $criteria_cnt = 0; + for ( + $column_index = 0; + $column_index < $this->_criteria_column_count; + $column_index++ + ) { + if (! empty($this->_formColumns[$column_index]) + && ! empty($this->_formCriterions[$column_index]) + && $column_index + && isset($last_where) + && isset($this->_formAndOrCols) + ) { + $where_clause .= ' ' + . mb_strtoupper($this->_formAndOrCols[$last_where]) + . ' '; + } + if (! empty($this->_formColumns[$column_index]) + && ! empty($this->_formCriterions[$column_index]) + ) { + $where_clause .= '(' . $this->_formColumns[$column_index] . ' ' + . $this->_formCriterions[$column_index] . ')'; + $last_where = $column_index; + $criteria_cnt++; + } + } // end for + if ($criteria_cnt > 1) { + $where_clause = '(' . $where_clause . ')'; + } + // OR rows ${'cur' . $or}[$column_index] + if (! isset($this->_formAndOrRows)) { + $this->_formAndOrRows = array(); + } + for ( + $row_index = 0; + $row_index <= $this->_criteria_row_count; + $row_index++ + ) { + $criteria_cnt = 0; + $qry_orwhere = ''; + $last_orwhere = ''; + for ( + $column_index = 0; + $column_index < $this->_criteria_column_count; + $column_index++ + ) { + if (! empty($this->_formColumns[$column_index]) + && ! empty($_POST['Or' . $row_index][$column_index]) + && $column_index + ) { + $qry_orwhere .= ' ' + . mb_strtoupper( + $this->_formAndOrCols[$last_orwhere] + ) + . ' '; + } + if (! empty($this->_formColumns[$column_index]) + && ! empty($_POST['Or' . $row_index][$column_index]) + ) { + $qry_orwhere .= '(' . $this->_formColumns[$column_index] + . ' ' + . $_POST['Or' . $row_index][$column_index] + . ')'; + $last_orwhere = $column_index; + $criteria_cnt++; + } + } // end for + if ($criteria_cnt > 1) { + $qry_orwhere = '(' . $qry_orwhere . ')'; + } + if (! empty($qry_orwhere)) { + $where_clause .= "\n" + . mb_strtoupper( + isset($this->_formAndOrRows[$row_index]) + ? $this->_formAndOrRows[$row_index] . ' ' + : '' + ) + . $qry_orwhere; + } // end if + } // end for + + if (! empty($where_clause) && $where_clause != '()') { + $where_clause = 'WHERE ' . htmlspecialchars($where_clause) . "\n"; + } // end if + return $where_clause; + } + + /** + * Provides ORDER BY clause for building SQL query + * + * @return string Order By clause + */ + private function _getOrderByClause() + { + $orderby_clause = ''; + $orderby_clauses = array(); + + // Create copy of instance variables + $columns = $this->_formColumns; + $sort = $this->_formSorts; + $sortOrder = $this->_formSortOrders; + if (!empty($sortOrder) + && count($sortOrder) == count($sort) + && count($sortOrder) == count($columns) + ) { + // Sort all three arrays based on sort order + array_multisort($sortOrder, $sort, $columns); + } + + for ( + $column_index = 0; + $column_index < $this->_criteria_column_count; + $column_index++ + ) { + // if all columns are chosen with * selector, + // then sorting isn't available + // Fix for Bug #570698 + if (empty($columns[$column_index]) + && empty($sort[$column_index]) + ) { + continue; + } + + if (mb_substr($columns[$column_index], -2) == '.*') { + continue; + } + + if (! empty($sort[$column_index])) { + $orderby_clauses[] = $columns[$column_index] . ' ' + . $sort[$column_index]; + } + } // end for + if (!empty($orderby_clauses)) { + $orderby_clause = 'ORDER BY ' + . htmlspecialchars(implode(", ", $orderby_clauses)) . "\n"; + } + return $orderby_clause; + } + + /** + * Provides UNIQUE columns and INDEX columns present in criteria tables + * + * @param array $search_tables Tables involved in the search + * @param array $search_columns Columns involved in the search + * @param array $where_clause_columns Columns having criteria where clause + * + * @return array having UNIQUE and INDEX columns + */ + private function _getIndexes(array $search_tables, array $search_columns, + array $where_clause_columns + ) { + $unique_columns = array(); + $index_columns = array(); + + foreach ($search_tables as $table) { + $indexes = $GLOBALS['dbi']->getTableIndexes($this->_db, $table); + foreach ($indexes as $index) { + $column = $table . '.' . $index['Column_name']; + if (isset($search_columns[$column])) { + if ($index['Non_unique'] == 0) { + if (isset($where_clause_columns[$column])) { + $unique_columns[$column] = 'Y'; + } else { + $unique_columns[$column] = 'N'; + } + } else { + if (isset($where_clause_columns[$column])) { + $index_columns[$column] = 'Y'; + } else { + $index_columns[$column] = 'N'; + } + } + } + } // end while (each index of a table) + } // end while (each table) + + return array( + 'unique' => $unique_columns, + 'index' => $index_columns + ); + } + + /** + * Provides UNIQUE columns and INDEX columns present in criteria tables + * + * @param array $search_tables Tables involved in the search + * @param array $search_columns Columns involved in the search + * @param array $where_clause_columns Columns having criteria where clause + * + * @return array having UNIQUE and INDEX columns + */ + private function _getLeftJoinColumnCandidates(array $search_tables, array $search_columns, + array $where_clause_columns + ) { + $GLOBALS['dbi']->selectDb($this->_db); + + // Get unique columns and index columns + $indexes = $this->_getIndexes( + $search_tables, $search_columns, $where_clause_columns + ); + $unique_columns = $indexes['unique']; + $index_columns = $indexes['index']; + + list($candidate_columns, $needsort) + = $this->_getLeftJoinColumnCandidatesBest( + $search_tables, + $where_clause_columns, + $unique_columns, + $index_columns + ); + + // If we came up with $unique_columns (very good) or $index_columns (still + // good) as $candidate_columns we want to check if we have any 'Y' there + // (that would mean that they were also found in the whereclauses + // which would be great). if yes, we take only those + if ($needsort != 1) { + return $candidate_columns; + } + + $very_good = array(); + $still_good = array(); + foreach ($candidate_columns as $column => $is_where) { + $table = explode('.', $column); + $table = $table[0]; + if ($is_where == 'Y') { + $very_good[$column] = $table; + } else { + $still_good[$column] = $table; + } + } + if (count($very_good) > 0) { + $candidate_columns = $very_good; + // Candidates restricted in index+where + } else { + $candidate_columns = $still_good; + // None of the candidates where in a where-clause + } + + return $candidate_columns; + } + + /** + * Provides the main table to form the LEFT JOIN clause + * + * @param array $search_tables Tables involved in the search + * @param array $search_columns Columns involved in the search + * @param array $where_clause_columns Columns having criteria where clause + * @param array $where_clause_tables Tables having criteria where clause + * + * @return string table name + */ + private function _getMasterTable(array $search_tables, array $search_columns, + array $where_clause_columns, array $where_clause_tables + ) { + if (count($where_clause_tables) == 1) { + // If there is exactly one column that has a decent where-clause + // we will just use this + $master = key($where_clause_tables); + return $master; + } + + // Now let's find out which of the tables has an index + // (When the control user is the same as the normal user + // because he is using one of his databases as pmadb, + // the last db selected is not always the one where we need to work) + $candidate_columns = $this->_getLeftJoinColumnCandidates( + $search_tables, $search_columns, $where_clause_columns + ); + + // Generally, we need to display all the rows of foreign (referenced) + // table, whether they have any matching row in child table or not. + // So we select candidate tables which are foreign tables. + $foreign_tables = array(); + foreach ($candidate_columns as $one_table) { + $foreigners = $this->relation->getForeigners($this->_db, $one_table); + foreach ($foreigners as $key => $foreigner) { + if ($key != 'foreign_keys_data') { + if (in_array($foreigner['foreign_table'], $candidate_columns)) { + $foreign_tables[$foreigner['foreign_table']] + = $foreigner['foreign_table']; + } + continue; + } + foreach ($foreigner as $one_key) { + if (in_array($one_key['ref_table_name'], $candidate_columns)) { + $foreign_tables[$one_key['ref_table_name']] + = $one_key['ref_table_name']; + } + } + } + } + if (count($foreign_tables)) { + $candidate_columns = $foreign_tables; + } + + // If our array of candidates has more than one member we'll just + // find the smallest table. + // Of course the actual query would be faster if we check for + // the Criteria which gives the smallest result set in its table, + // but it would take too much time to check this + if (!(count($candidate_columns) > 1)) { + // Only one single candidate + return reset($candidate_columns); + } + + // Of course we only want to check each table once + $checked_tables = $candidate_columns; + $tsize = array(); + $maxsize = -1; + $result = ''; + foreach ($candidate_columns as $table) { + if ($checked_tables[$table] != 1) { + $_table = new Table($table, $this->_db); + $tsize[$table] = $_table->countRecords(); + $checked_tables[$table] = 1; + } + if ($tsize[$table] > $maxsize) { + $maxsize = $tsize[$table]; + $result = $table; + } + } + // Return largest table + return $result; + } + + /** + * Provides columns and tables that have valid where clause criteria + * + * @return array + */ + private function _getWhereClauseTablesAndColumns() + { + $where_clause_columns = array(); + $where_clause_tables = array(); + + // Now we need all tables that we have in the where clause + for ( + $column_index = 0, $nb = count($this->_criteria); + $column_index < $nb; + $column_index++ + ) { + $current_table = explode('.', $_POST['criteriaColumn'][$column_index]); + if (empty($current_table[0]) || empty($current_table[1])) { + continue; + } // end if + $table = str_replace('`', '', $current_table[0]); + $column = str_replace('`', '', $current_table[1]); + $column = $table . '.' . $column; + // Now we know that our array has the same numbers as $criteria + // we can check which of our columns has a where clause + if (! empty($this->_criteria[$column_index])) { + if (mb_substr($this->_criteria[$column_index], 0, 1) == '=' + || stristr($this->_criteria[$column_index], 'is') + ) { + $where_clause_columns[$column] = $column; + $where_clause_tables[$table] = $table; + } + } // end if + } // end for + return array( + 'where_clause_tables' => $where_clause_tables, + 'where_clause_columns' => $where_clause_columns + ); + } + + /** + * Provides FROM clause for building SQL query + * + * @param array $formColumns List of selected columns in the form + * + * @return string FROM clause + */ + private function _getFromClause(array $formColumns) + { + $from_clause = ''; + if (empty($formColumns)) { + return $from_clause; + } + + // Initialize some variables + $search_tables = $search_columns = array(); + + // We only start this if we have fields, otherwise it would be dumb + foreach ($formColumns as $value) { + $parts = explode('.', $value); + if (! empty($parts[0]) && ! empty($parts[1])) { + $table = str_replace('`', '', $parts[0]); + $search_tables[$table] = $table; + $search_columns[] = $table . '.' . str_replace( + '`', '', $parts[1] + ); + } + } // end while + + // Create LEFT JOINS out of Relations + $from_clause = $this->_getJoinForFromClause( + $search_tables, $search_columns + ); + + // In case relations are not defined, just generate the FROM clause + // from the list of tables, however we don't generate any JOIN + if (empty($from_clause)) { + // Create cartesian product + $from_clause = implode( + ", ", array_map(array('PhpMyAdmin\Util', 'backquote'), $search_tables) + ); + } + + return $from_clause; + } + + /** + * Formulates the WHERE clause by JOINing tables + * + * @param array $searchTables Tables involved in the search + * @param array $searchColumns Columns involved in the search + * + * @return string table name + */ + private function _getJoinForFromClause(array $searchTables, array $searchColumns) + { + // $relations[master_table][foreign_table] => clause + $relations = array(); + + // Fill $relations with inter table relationship data + foreach ($searchTables as $oneTable) { + $this->_loadRelationsForTable($relations, $oneTable); + } + + // Get tables and columns with valid where clauses + $validWhereClauses = $this->_getWhereClauseTablesAndColumns(); + $whereClauseTables = $validWhereClauses['where_clause_tables']; + $whereClauseColumns = $validWhereClauses['where_clause_columns']; + + // Get master table + $master = $this->_getMasterTable( + $searchTables, $searchColumns, + $whereClauseColumns, $whereClauseTables + ); + + // Will include master tables and all tables that can be combined into + // a cluster by their relation + $finalized = array(); + if (strlen($master) > 0) { + // Add master tables + $finalized[$master] = ''; + } + // Fill the $finalized array with JOIN clauses for each table + $this->_fillJoinClauses($finalized, $relations, $searchTables); + + // JOIN clause + $join = ''; + + // Tables that can not be combined with the table cluster + // which includes master table + $unfinalized = array_diff($searchTables, array_keys($finalized)); + if (count($unfinalized) > 0) { + + // We need to look for intermediary tables to JOIN unfinalized tables + // Heuristic to chose intermediary tables is to look for tables + // having relationships with unfinalized tables + foreach ($unfinalized as $oneTable) { + + $references = $this->relation->getChildReferences($this->_db, $oneTable); + foreach ($references as $column => $columnReferences) { + foreach ($columnReferences as $reference) { + + // Only from this schema + if ($reference['table_schema'] != $this->_db) { + continue; + } + + $table = $reference['table_name']; + + $this->_loadRelationsForTable($relations, $table); + + // Make copies + $tempFinalized = $finalized; + $tempSearchTables = $searchTables; + $tempSearchTables[] = $table; + + // Try joining with the added table + $this->_fillJoinClauses( + $tempFinalized, $relations, $tempSearchTables + ); + + $tempUnfinalized = array_diff( + $tempSearchTables, array_keys($tempFinalized) + ); + // Take greedy approach. + // If the unfinalized count drops we keep the new table + // and switch temporary varibles with the original ones + if (count($tempUnfinalized) < count($unfinalized)) { + $finalized = $tempFinalized; + $searchTables = $tempSearchTables; + } + + // We are done if no unfinalized tables anymore + if (count($tempUnfinalized) == 0) { + break 3; + } + } + } + } + + $unfinalized = array_diff($searchTables, array_keys($finalized)); + // If there are still unfinalized tables + if (count($unfinalized) > 0) { + // Add these tables as cartesian product before joined tables + $join .= implode( + ', ', array_map(array('PhpMyAdmin\Util', 'backquote'), $unfinalized) + ); + } + } + + $first = true; + // Add joined tables + foreach ($finalized as $table => $clause) { + if ($first) { + if (! empty($join)) { + $join .= ", "; + } + $join .= Util::backquote($table); + $first = false; + } else { + $join .= "\n LEFT JOIN " . Util::backquote( + $table + ) . " ON " . $clause; + } + } + + return $join; + } + + /** + * Loads relations for a given table into the $relations array + * + * @param array &$relations array of relations + * @param string $oneTable the table + * + * @return void + */ + private function _loadRelationsForTable(array &$relations, $oneTable) + { + $relations[$oneTable] = array(); + + $foreigners = $this->relation->getForeigners($GLOBALS['db'], $oneTable); + foreach ($foreigners as $field => $foreigner) { + // Foreign keys data + if ($field == 'foreign_keys_data') { + foreach ($foreigner as $oneKey) { + $clauses = array(); + // There may be multiple column relations + foreach ($oneKey['index_list'] as $index => $oneField) { + $clauses[] + = Util::backquote($oneTable) . "." + . Util::backquote($oneField) . " = " + . Util::backquote($oneKey['ref_table_name']) . "." + . Util::backquote($oneKey['ref_index_list'][$index]); + } + // Combine multiple column relations with AND + $relations[$oneTable][$oneKey['ref_table_name']] + = implode(" AND ", $clauses); + } + } else { // Internal relations + $relations[$oneTable][$foreigner['foreign_table']] + = Util::backquote($oneTable) . "." + . Util::backquote($field) . " = " + . Util::backquote($foreigner['foreign_table']) . "." + . Util::backquote($foreigner['foreign_field']); + } + } + } + + /** + * Fills the $finalized arrays with JOIN clauses for each of the tables + * + * @param array &$finalized JOIN clauses for each table + * @param array $relations Relations among tables + * @param array $searchTables Tables involved in the search + * + * @return void + */ + private function _fillJoinClauses(array &$finalized, array $relations, array $searchTables) + { + while (true) { + $added = false; + foreach ($searchTables as $masterTable) { + $foreignData = $relations[$masterTable]; + foreach ($foreignData as $foreignTable => $clause) { + if (! isset($finalized[$masterTable]) + && isset($finalized[$foreignTable]) + ) { + $finalized[$masterTable] = $clause; + $added = true; + } elseif (! isset($finalized[$foreignTable]) + && isset($finalized[$masterTable]) + && in_array($foreignTable, $searchTables) + ) { + $finalized[$foreignTable] = $clause; + $added = true; + } + if ($added) { + // We are done if all tables are in $finalized + if (count($finalized) == count($searchTables)) { + return; + } + } + } + } + // If no new tables were added during this iteration, break; + if (! $added) { + return; + } + } + } + + /** + * Provides the generated SQL query + * + * @param array $formColumns List of selected columns in the form + * + * @return string SQL query + */ + private function _getSQLQuery(array $formColumns) + { + $sql_query = ''; + // get SELECT clause + $sql_query .= $this->_getSelectClause(); + // get FROM clause + $from_clause = $this->_getFromClause($formColumns); + if (! empty($from_clause)) { + $sql_query .= 'FROM ' . htmlspecialchars($from_clause) . "\n"; + } + // get WHERE clause + $sql_query .= $this->_getWhereClause(); + // get ORDER BY clause + $sql_query .= $this->_getOrderByClause(); + return $sql_query; + } + + /** + * Provides the generated QBE form + * + * @return string QBE form + */ + public function getSelectionForm() + { + $html_output = '
        '; + $html_output .= '
        '; + $html_output .= '
        '; + + if ($GLOBALS['cfgRelation']['savedsearcheswork']) { + $html_output .= $this->_getSavedSearchesField(); + } + + $html_output .= '
        '; + $html_output .= ''; + // Get table's elements + $html_output .= $this->_getColumnNamesRow(); + $html_output .= $this->_getColumnAliasRow(); + $html_output .= $this->_getShowRow(); + $html_output .= $this->_getSortRow(); + $html_output .= $this->_getSortOrder(); + $html_output .= $this->_getCriteriaInputboxRow(); + $html_output .= $this->_getInsDelAndOrCriteriaRows(); + $html_output .= $this->_getModifyColumnsRow(); + $html_output .= '
        '; + $this->_new_row_count--; + $url_params = array(); + $url_params['db'] = $this->_db; + $url_params['criteriaColumnCount'] = $this->_new_column_count; + $url_params['rows'] = $this->_new_row_count; + $html_output .= Url::getHiddenInputs($url_params); + $html_output .= '
        '; + $html_output .= '
        '; + $html_output .= '
        '; + // get footers + $html_output .= $this->_getTableFooters(); + // get tables select list + $html_output .= $this->_getTablesList(); + $html_output .= '
        '; + $html_output .= '
        '; + $html_output .= Url::getHiddenInputs(array('db' => $this->_db)); + // get SQL query + $html_output .= '
        '; + $html_output .= '
        '; + $html_output .= '' + . sprintf( + __('SQL query on database %s:'), + Util::getDbLink($this->_db) + ); + $html_output .= ''; + $text_dir = 'ltr'; + $html_output .= ''; + $html_output .= '
        '; + // displays form's footers + $html_output .= '
        '; + $html_output .= ''; + $html_output .= ''; + $html_output .= '
        '; + $html_output .= '
        '; + $html_output .= '
        '; + return $html_output; + } + + /** + * Get fields to display + * + * @return string + */ + private function _getSavedSearchesField() + { + $html_output = __('Saved bookmarked search:'); + $html_output .= ' '; + $html_output .= ''; + $html_output .= ''; + $html_output .= ''; + if (null !== $currentSearchId) { + $html_output .= ''; + $html_output .= ''; + } + + return $html_output; + } + + /** + * Initialize _criteria_column_count + * + * @return int Previous number of columns + */ + private function _initializeCriteriasCount() + { + // sets column count + $criteriaColumnCount = Core::ifSetOr( + $_POST['criteriaColumnCount'], + 3, + 'numeric' + ); + $criteriaColumnAdd = Core::ifSetOr( + $_POST['criteriaColumnAdd'], + 0, + 'numeric' + ); + $this->_criteria_column_count = max( + $criteriaColumnCount + $criteriaColumnAdd, + 0 + ); + + // sets row count + $rows = Core::ifSetOr($_POST['rows'], 0, 'numeric'); + $criteriaRowAdd = Core::ifSetOr($_POST['criteriaRowAdd'], 0, 'numeric'); + $this->_criteria_row_count = min( + 100, + max($rows + $criteriaRowAdd, 0) + ); + + return $criteriaColumnCount; + } + + /** + * Get best + * + * @param array $search_tables Tables involved in the search + * @param array $where_clause_columns Columns with where clause + * @param array $unique_columns Unique columns + * @param array $index_columns Indexed columns + * + * @return array + */ + private function _getLeftJoinColumnCandidatesBest( + array $search_tables, array $where_clause_columns, array $unique_columns, array $index_columns + ) { + // now we want to find the best. + if (isset($unique_columns) && count($unique_columns) > 0) { + $candidate_columns = $unique_columns; + $needsort = 1; + return array($candidate_columns, $needsort); + } elseif (isset($index_columns) && count($index_columns) > 0) { + $candidate_columns = $index_columns; + $needsort = 1; + return array($candidate_columns, $needsort); + } elseif (isset($where_clause_columns) && count($where_clause_columns) > 0) { + $candidate_columns = $where_clause_columns; + $needsort = 0; + return array($candidate_columns, $needsort); + } + + $candidate_columns = $search_tables; + $needsort = 0; + return array($candidate_columns, $needsort); + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Database/Search.php b/php/apps/phpmyadmin49/libraries/classes/Database/Search.php new file mode 100644 index 00000000..f5923f9e --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Database/Search.php @@ -0,0 +1,339 @@ +db = $db; + $this->searchTypes = array( + '1' => __('at least one of the words'), + '2' => __('all of the words'), + '3' => __('the exact phrase as substring'), + '4' => __('the exact phrase as whole field'), + '5' => __('as regular expression'), + ); + // Sets criteria parameters + $this->setSearchParams(); + } + + /** + * Sets search parameters + * + * @return void + */ + private function setSearchParams() + { + $this->tablesNamesOnly = $GLOBALS['dbi']->getTables($this->db); + + if (empty($_POST['criteriaSearchType']) + || ! is_string($_POST['criteriaSearchType']) + || ! array_key_exists( + $_POST['criteriaSearchType'], + $this->searchTypes + ) + ) { + $this->criteriaSearchType = 1; + unset($_POST['submit_search']); + } else { + $this->criteriaSearchType = (int) $_POST['criteriaSearchType']; + $this->searchTypeDescription + = $this->searchTypes[$_POST['criteriaSearchType']]; + } + + if (empty($_POST['criteriaSearchString']) + || ! is_string($_POST['criteriaSearchString']) + ) { + $this->criteriaSearchString = ''; + unset($_POST['submit_search']); + } else { + $this->criteriaSearchString = $_POST['criteriaSearchString']; + } + + $this->criteriaTables = array(); + if (empty($_POST['criteriaTables']) + || ! is_array($_POST['criteriaTables']) + ) { + unset($_POST['submit_search']); + } else { + $this->criteriaTables = array_intersect( + $_POST['criteriaTables'], $this->tablesNamesOnly + ); + } + + if (empty($_POST['criteriaColumnName']) + || ! is_string($_POST['criteriaColumnName']) + ) { + unset($this->criteriaColumnName); + } else { + $this->criteriaColumnName = $GLOBALS['dbi']->escapeString( + $_POST['criteriaColumnName'] + ); + } + } + + /** + * Builds the SQL search query + * + * @param string $table The table name + * + * @return array 3 SQL queries (for count, display and delete results) + * + * @todo can we make use of fulltextsearch IN BOOLEAN MODE for this? + * PMA_backquote + * DatabaseInterface::freeResult + * DatabaseInterface::fetchAssoc + * $GLOBALS['db'] + * explode + * count + * strlen + */ + private function getSearchSqls($table) + { + // Statement types + $sqlstr_select = 'SELECT'; + $sqlstr_delete = 'DELETE'; + // Table to use + $sqlstr_from = ' FROM ' + . Util::backquote($GLOBALS['db']) . '.' + . Util::backquote($table); + // Gets where clause for the query + $where_clause = $this->getWhereClause($table); + // Builds complete queries + $sql = array(); + $sql['select_columns'] = $sqlstr_select . ' * ' . $sqlstr_from + . $where_clause; + // here, I think we need to still use the COUNT clause, even for + // VIEWs, anyway we have a WHERE clause that should limit results + $sql['select_count'] = $sqlstr_select . ' COUNT(*) AS `count`' + . $sqlstr_from . $where_clause; + $sql['delete'] = $sqlstr_delete . $sqlstr_from . $where_clause; + + return $sql; + } + + /** + * Provides where clause for building SQL query + * + * @param string $table The table name + * + * @return string The generated where clause + */ + private function getWhereClause($table) + { + // Columns to select + $allColumns = $GLOBALS['dbi']->getColumns($GLOBALS['db'], $table); + $likeClauses = array(); + // Based on search type, decide like/regex & '%'/'' + $like_or_regex = (($this->criteriaSearchType == 5) ? 'REGEXP' : 'LIKE'); + $automatic_wildcard = (($this->criteriaSearchType < 4) ? '%' : ''); + // For "as regular expression" (search option 5), LIKE won't be used + // Usage example: If user is searching for a literal $ in a regexp search, + // he should enter \$ as the value. + $criteriaSearchStringEscaped = $GLOBALS['dbi']->escapeString( + $this->criteriaSearchString + ); + // Extract search words or pattern + $search_words = (($this->criteriaSearchType > 2) + ? array($criteriaSearchStringEscaped) + : explode(' ', $criteriaSearchStringEscaped)); + + foreach ($search_words as $search_word) { + // Eliminates empty values + if (strlen($search_word) === 0) { + continue; + } + $likeClausesPerColumn = array(); + // for each column in the table + foreach ($allColumns as $column) { + if (! isset($this->criteriaColumnName) + || strlen($this->criteriaColumnName) === 0 + || $column['Field'] == $this->criteriaColumnName + ) { + $column = 'CONVERT(' . Util::backquote($column['Field']) + . ' USING utf8)'; + $likeClausesPerColumn[] = $column . ' ' . $like_or_regex . ' ' + . "'" + . $automatic_wildcard . $search_word . $automatic_wildcard + . "'"; + } + } // end for + if (count($likeClausesPerColumn) > 0) { + $likeClauses[] = implode(' OR ', $likeClausesPerColumn); + } + } // end for + // Use 'OR' if 'at least one word' is to be searched, else use 'AND' + $implode_str = ($this->criteriaSearchType == 1 ? ' OR ' : ' AND '); + if (empty($likeClauses)) { + // this could happen when the "inside column" does not exist + // in any selected tables + $where_clause = ' WHERE FALSE'; + } else { + $where_clause = ' WHERE (' + . implode(') ' . $implode_str . ' (', $likeClauses) + . ')'; + } + return $where_clause; + } + + /** + * Displays database search results + * + * @return string HTML for search results + */ + public function getSearchResults() + { + $resultTotal = 0; + $rows = []; + // For each table selected as search criteria + foreach ($this->criteriaTables as $eachTable) { + // Gets the SQL statements + $newSearchSqls = $this->getSearchSqls($eachTable); + // Executes the "COUNT" statement + $resultCount = intval($GLOBALS['dbi']->fetchValue( + $newSearchSqls['select_count'] + )); + $resultTotal += $resultCount; + // Gets the result row's HTML for a table + $rows[] = [ + 'table' => htmlspecialchars($eachTable), + 'new_search_sqls' => $newSearchSqls, + 'result_count' => $resultCount, + ]; + } + + return Template::get('database/search/results')->render([ + 'db' => $this->db, + 'rows' => $rows, + 'result_total' => $resultTotal, + 'criteria_tables' => $this->criteriaTables, + 'criteria_search_string' => htmlspecialchars($this->criteriaSearchString), + 'search_type_description' => $this->searchTypeDescription, + ]); + } + + /** + * Provides the main search form's html + * + * @return string HTML for selection form + */ + public function getSelectionForm() + { + $choices = array( + '1' => $this->searchTypes[1] . ' ' + . Util::showHint( + __('Words are separated by a space character (" ").') + ), + '2' => $this->searchTypes[2] . ' ' + . Util::showHint( + __('Words are separated by a space character (" ").') + ), + '3' => $this->searchTypes[3], + '4' => $this->searchTypes[4], + '5' => $this->searchTypes[5] . ' ' . Util::showMySQLDocu('Regexp') + ); + return Template::get('database/search/selection_form')->render([ + 'db' => $this->db, + 'choices' => $choices, + 'criteria_search_string' => $this->criteriaSearchString, + 'criteria_search_type' => $this->criteriaSearchType, + 'criteria_tables' => $this->criteriaTables, + 'tables_names_only' => $this->tablesNamesOnly, + 'criteria_column_name' => isset($this->criteriaColumnName) + ? $this->criteriaColumnName : null, + ]); + } + + /** + * Provides div tags for browsing search results and sql query form. + * + * @return string div tags + */ + public function getResultDivs() + { + return Template::get('database/search/result_divs')->render(); + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/DatabaseInterface.php b/php/apps/phpmyadmin49/libraries/classes/DatabaseInterface.php new file mode 100644 index 00000000..138b7982 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/DatabaseInterface.php @@ -0,0 +1,3095 @@ +_extension = $ext; + $this->_links = array(); + if (defined('TESTSUITE')) { + $this->_links[DatabaseInterface::CONNECT_USER] = 1; + $this->_links[DatabaseInterface::CONNECT_CONTROL] = 2; + } + $this->_table_cache = array(); + $this->_current_user = array(); + $this->types = new Types($this); + $this->relation = new Relation(); + } + + /** + * Checks whether database extension is loaded + * + * @param string $extension mysql extension to check + * + * @return bool + */ + public static function checkDbExtension($extension = 'mysql') + { + return function_exists($extension . '_connect'); + } + + /** + * runs a query + * + * @param string $query SQL query to execute + * @param mixed $link optional database link to use + * @param int $options optional query options + * @param bool $cache_affected_rows whether to cache affected rows + * + * @return mixed + */ + public function query($query, $link = DatabaseInterface::CONNECT_USER, $options = 0, + $cache_affected_rows = true + ) { + $res = $this->tryQuery($query, $link, $options, $cache_affected_rows) + or Util::mysqlDie($this->getError($link), $query); + return $res; + } + + /** + * Get a cached value from table cache. + * + * @param array $contentPath Array of the name of the target value + * @param mixed $default Return value on cache miss + * + * @return mixed cached value or default + */ + public function getCachedTableContent(array $contentPath, $default = null) + { + return Util::getValueByKey($this->_table_cache, $contentPath, $default); + } + + /** + * Set an item in table cache using dot notation. + * + * @param array $contentPath Array with the target path + * @param mixed $value Target value + * + * @return void + */ + public function cacheTableContent(array $contentPath, $value) + { + $loc = &$this->_table_cache; + + if (!isset($contentPath)) { + $loc = $value; + return; + } + + while (count($contentPath) > 1) { + $key = array_shift($contentPath); + + // If the key doesn't exist at this depth, we will just create an empty + // array to hold the next value, allowing us to create the arrays to hold + // final values at the correct depth. Then we'll keep digging into the + // array. + if (!isset($loc[$key]) || !is_array($loc[$key])) { + $loc[$key] = array(); + } + $loc = &$loc[$key]; + } + + $loc[array_shift($contentPath)] = $value; + } + + /** + * Clear the table cache. + * + * @return void + */ + public function clearTableCache() + { + $this->_table_cache = array(); + } + + /** + * Caches table data so Table does not require to issue + * SHOW TABLE STATUS again + * + * @param array $tables information for tables of some databases + * @param string $table table name + * + * @return void + */ + private function _cacheTableData(array $tables, $table) + { + // Note: I don't see why we would need array_merge_recursive() here, + // as it creates double entries for the same table (for example a double + // entry for Comment when changing the storage engine in Operations) + // Note 2: Instead of array_merge(), simply use the + operator because + // array_merge() renumbers numeric keys starting with 0, therefore + // we would lose a db name that consists only of numbers + + foreach ($tables as $one_database => $its_tables) { + if (isset($this->_table_cache[$one_database])) { + // the + operator does not do the intended effect + // when the cache for one table already exists + if ($table + && isset($this->_table_cache[$one_database][$table]) + ) { + unset($this->_table_cache[$one_database][$table]); + } + $this->_table_cache[$one_database] + = $this->_table_cache[$one_database] + $tables[$one_database]; + } else { + $this->_table_cache[$one_database] = $tables[$one_database]; + } + } + } + + /** + * Stores query data into session data for debugging purposes + * + * @param string $query Query text + * @param integer $link link type + * @param object|boolean $result Query result + * @param integer $time Time to execute query + * + * @return void + */ + private function _dbgQuery($query, $link, $result, $time) + { + $dbgInfo = array(); + $error_message = $this->getError($link); + if ($result == false && is_string($error_message)) { + $dbgInfo['error'] + = '' + . htmlspecialchars($error_message) . ''; + } + $dbgInfo['query'] = htmlspecialchars($query); + $dbgInfo['time'] = $time; + // Get and slightly format backtrace, this is used + // in the javascript console. + // Strip call to _dbgQuery + $dbgInfo['trace'] = Error::processBacktrace( + array_slice(debug_backtrace(), 1) + ); + $dbgInfo['hash'] = md5($query); + + $_SESSION['debug']['queries'][] = $dbgInfo; + } + + /** + * runs a query and returns the result + * + * @param string $query query to run + * @param integer $link link type + * @param integer $options query options + * @param bool $cache_affected_rows whether to cache affected row + * + * @return mixed + */ + public function tryQuery($query, $link = DatabaseInterface::CONNECT_USER, $options = 0, + $cache_affected_rows = true + ) { + $debug = $GLOBALS['cfg']['DBG']['sql']; + if (! isset($this->_links[$link])) { + return false; + } + + if ($debug) { + $time = microtime(true); + } + + $result = $this->_extension->realQuery($query, $this->_links[$link], $options); + + if ($cache_affected_rows) { + $GLOBALS['cached_affected_rows'] = $this->affectedRows($link, false); + } + + if ($debug) { + $time = microtime(true) - $time; + $this->_dbgQuery($query, $link, $result, $time); + if ($GLOBALS['cfg']['DBG']['sqllog']) { + $warningsCount = ''; + if ($options & DatabaseInterface::QUERY_STORE == DatabaseInterface::QUERY_STORE) { + if (isset($this->_links[$link]->warning_count)) { + $warningsCount = $this->_links[$link]->warning_count; + } + } + + openlog('phpMyAdmin', LOG_NDELAY | LOG_PID, LOG_USER); + + syslog( + LOG_INFO, + 'SQL[' . basename($_SERVER['SCRIPT_NAME']) . ']: ' + . sprintf('%0.3f', $time) . '(W:' . $warningsCount . ') > ' . $query + ); + closelog(); + } + } + + if ($result !== false && Tracker::isActive()) { + Tracker::handleQuery($query); + } + + return $result; + } + + /** + * Run multi query statement and return results + * + * @param string $multi_query multi query statement to execute + * @param mysqli $link mysqli object + * + * @return mysqli_result collection | boolean(false) + */ + public function tryMultiQuery($multi_query = '', $link = DatabaseInterface::CONNECT_USER) + { + if (! isset($this->_links[$link])) { + return false; + } + return $this->_extension->realMultiQuery($this->_links[$link], $multi_query); + } + + /** + * returns array with table names for given db + * + * @param string $database name of database + * @param mixed $link mysql link resource|object + * + * @return array tables names + */ + public function getTables($database, $link = DatabaseInterface::CONNECT_USER) + { + $tables = $this->fetchResult( + 'SHOW TABLES FROM ' . Util::backquote($database) . ';', + null, + 0, + $link, + self::QUERY_STORE + ); + if ($GLOBALS['cfg']['NaturalOrder']) { + usort($tables, 'strnatcasecmp'); + } + return $tables; + } + + + /** + * returns + * + * @param string $database name of database + * @param array $tables list of tables to search for for relations + * @param int $link mysql link resource|object + * + * @return array array of found foreign keys + */ + public function getForeignKeyConstrains($database, array $tables, $link = DatabaseInterface::CONNECT_USER) + { + $tablesListForQuery = ''; + foreach($tables as $table){ + $tablesListForQuery .= "'" . $this->escapeString($table) . "',"; + } + $tablesListForQuery = rtrim($tablesListForQuery, ','); + + $foreignKeyConstrains = $this->fetchResult( + "SELECT" + . " TABLE_NAME," + . " COLUMN_NAME," + . " REFERENCED_TABLE_NAME," + . " REFERENCED_COLUMN_NAME" + . " FROM information_schema.key_column_usage" + . " WHERE referenced_table_name IS NOT NULL" + . " AND TABLE_SCHEMA = '" . $this->escapeString($database) . "'" + . " AND TABLE_NAME IN (" . $tablesListForQuery . ")" + . " AND REFERENCED_TABLE_NAME IN (" . $tablesListForQuery . ");", + null, + null, + $link, + self::QUERY_STORE + ); + return $foreignKeyConstrains; + } + + /** + * returns a segment of the SQL WHERE clause regarding table name and type + * + * @param array|string $table table(s) + * @param boolean $tbl_is_group $table is a table group + * @param string $table_type whether table or view + * + * @return string a segment of the WHERE clause + */ + private function _getTableCondition($table, $tbl_is_group, $table_type) + { + // get table information from information_schema + if ($table) { + if (is_array($table)) { + $sql_where_table = 'AND t.`TABLE_NAME` ' + . Util::getCollateForIS() . ' IN (\'' + . implode( + '\', \'', + array_map( + array($this, 'escapeString'), + $table + ) + ) + . '\')'; + } elseif (true === $tbl_is_group) { + $sql_where_table = 'AND t.`TABLE_NAME` LIKE \'' + . Util::escapeMysqlWildcards( + $GLOBALS['dbi']->escapeString($table) + ) + . '%\''; + } else { + $sql_where_table = 'AND t.`TABLE_NAME` ' + . Util::getCollateForIS() . ' = \'' + . $GLOBALS['dbi']->escapeString($table) . '\''; + } + } else { + $sql_where_table = ''; + } + + if ($table_type) { + if ($table_type == 'view') { + $sql_where_table .= " AND t.`TABLE_TYPE` NOT IN ('BASE TABLE', 'SYSTEM VERSIONED')"; + } elseif ($table_type == 'table') { + $sql_where_table .= " AND t.`TABLE_TYPE` IN ('BASE TABLE', 'SYSTEM VERSIONED')"; + } + } + return $sql_where_table; + } + + /** + * returns the beginning of the SQL statement to fetch the list of tables + * + * @param string[] $this_databases databases to list + * @param string $sql_where_table additional condition + * + * @return string the SQL statement + */ + private function _getSqlForTablesFull($this_databases, $sql_where_table) + { + $sql = ' + SELECT *, + `TABLE_SCHEMA` AS `Db`, + `TABLE_NAME` AS `Name`, + `TABLE_TYPE` AS `TABLE_TYPE`, + `ENGINE` AS `Engine`, + `ENGINE` AS `Type`, + `VERSION` AS `Version`, + `ROW_FORMAT` AS `Row_format`, + `TABLE_ROWS` AS `Rows`, + `AVG_ROW_LENGTH` AS `Avg_row_length`, + `DATA_LENGTH` AS `Data_length`, + `MAX_DATA_LENGTH` AS `Max_data_length`, + `INDEX_LENGTH` AS `Index_length`, + `DATA_FREE` AS `Data_free`, + `AUTO_INCREMENT` AS `Auto_increment`, + `CREATE_TIME` AS `Create_time`, + `UPDATE_TIME` AS `Update_time`, + `CHECK_TIME` AS `Check_time`, + `TABLE_COLLATION` AS `Collation`, + `CHECKSUM` AS `Checksum`, + `CREATE_OPTIONS` AS `Create_options`, + `TABLE_COMMENT` AS `Comment` + FROM `information_schema`.`TABLES` t + WHERE `TABLE_SCHEMA` ' . Util::getCollateForIS() . ' + IN (\'' . implode("', '", $this_databases) . '\') + ' . $sql_where_table; + + return $sql; + } + + /** + * returns array of all tables in given db or dbs + * this function expects unquoted names: + * RIGHT: my_database + * WRONG: `my_database` + * WRONG: my\_database + * if $tbl_is_group is true, $table is used as filter for table names + * + * + * $GLOBALS['dbi']->getTablesFull('my_database'); + * $GLOBALS['dbi']->getTablesFull('my_database', 'my_table')); + * $GLOBALS['dbi']->getTablesFull('my_database', 'my_tables_', true)); + * + * + * @param string $database database + * @param string|array $table table name(s) + * @param boolean $tbl_is_group $table is a table group + * @param integer $limit_offset zero-based offset for the count + * @param boolean|integer $limit_count number of tables to return + * @param string $sort_by table attribute to sort by + * @param string $sort_order direction to sort (ASC or DESC) + * @param string $table_type whether table or view + * @param integer $link link type + * + * @todo move into Table + * + * @return array list of tables in given db(s) + */ + public function getTablesFull($database, $table = '', + $tbl_is_group = false, $limit_offset = 0, + $limit_count = false, $sort_by = 'Name', $sort_order = 'ASC', + $table_type = null, $link = DatabaseInterface::CONNECT_USER + ) { + if (true === $limit_count) { + $limit_count = $GLOBALS['cfg']['MaxTableList']; + } + // prepare and check parameters + if (! is_array($database)) { + $databases = array($database); + } else { + $databases = $database; + } + + $tables = array(); + + if (! $GLOBALS['cfg']['Server']['DisableIS']) { + $sql_where_table = $this->_getTableCondition( + $table, $tbl_is_group, $table_type + ); + + // for PMA bc: + // `SCHEMA_FIELD_NAME` AS `SHOW_TABLE_STATUS_FIELD_NAME` + // + // on non-Windows servers, + // added BINARY in the WHERE clause to force a case sensitive + // comparison (if we are looking for the db Aa we don't want + // to find the db aa) + $this_databases = array_map( + array($this, 'escapeString'), + $databases + ); + + $sql = $this->_getSqlForTablesFull($this_databases, $sql_where_table); + + // Sort the tables + $sql .= " ORDER BY $sort_by $sort_order"; + + if ($limit_count) { + $sql .= ' LIMIT ' . $limit_count . ' OFFSET ' . $limit_offset; + } + + $tables = $this->fetchResult( + $sql, array('TABLE_SCHEMA', 'TABLE_NAME'), null, $link + ); + + if ($sort_by == 'Name' && $GLOBALS['cfg']['NaturalOrder']) { + // here, the array's first key is by schema name + foreach ($tables as $one_database_name => $one_database_tables) { + uksort($one_database_tables, 'strnatcasecmp'); + + if ($sort_order == 'DESC') { + $one_database_tables = array_reverse($one_database_tables); + } + $tables[$one_database_name] = $one_database_tables; + } + } elseif ($sort_by == 'Data_length') { + // Size = Data_length + Index_length + foreach ($tables as $one_database_name => $one_database_tables) { + uasort( + $one_database_tables, + function ($a, $b) { + $aLength = $a['Data_length'] + $a['Index_length']; + $bLength = $b['Data_length'] + $b['Index_length']; + return $aLength == $bLength + ? 0 + : ($aLength < $bLength ? -1 : 1); + } + ); + + if ($sort_order == 'DESC') { + $one_database_tables = array_reverse($one_database_tables); + } + $tables[$one_database_name] = $one_database_tables; + } + } + } // end (get information from table schema) + + // If permissions are wrong on even one database directory, + // information_schema does not return any table info for any database + // this is why we fall back to SHOW TABLE STATUS even for MySQL >= 50002 + if (empty($tables)) { + foreach ($databases as $each_database) { + if ($table || (true === $tbl_is_group) || ! empty($table_type)) { + $sql = 'SHOW TABLE STATUS FROM ' + . Util::backquote($each_database) + . ' WHERE'; + $needAnd = false; + if ($table || (true === $tbl_is_group)) { + if (is_array($table)) { + $sql .= ' `Name` IN (\'' + . implode( + '\', \'', + array_map( + array($this, 'escapeString'), + $table, + $link + ) + ) . '\')'; + } else { + $sql .= " `Name` LIKE '" + . Util::escapeMysqlWildcards( + $this->escapeString($table, $link) + ) + . "%'"; + } + $needAnd = true; + } + if (! empty($table_type)) { + if ($needAnd) { + $sql .= " AND"; + } + if ($table_type == 'view') { + $sql .= " `Comment` = 'VIEW'"; + } elseif ($table_type == 'table') { + $sql .= " `Comment` != 'VIEW'"; + } + } + } else { + $sql = 'SHOW TABLE STATUS FROM ' + . Util::backquote($each_database); + } + + $each_tables = $this->fetchResult($sql, 'Name', null, $link); + + // Sort naturally if the config allows it and we're sorting + // the Name column. + if ($sort_by == 'Name' && $GLOBALS['cfg']['NaturalOrder']) { + uksort($each_tables, 'strnatcasecmp'); + + if ($sort_order == 'DESC') { + $each_tables = array_reverse($each_tables); + } + } else { + // Prepare to sort by creating array of the selected sort + // value to pass to array_multisort + + // Size = Data_length + Index_length + if ($sort_by == 'Data_length') { + foreach ($each_tables as $table_name => $table_data) { + ${$sort_by}[$table_name] = strtolower( + $table_data['Data_length'] + + $table_data['Index_length'] + ); + } + } else { + foreach ($each_tables as $table_name => $table_data) { + ${$sort_by}[$table_name] + = strtolower($table_data[$sort_by]); + } + } + + if (! empty($$sort_by)) { + if ($sort_order == 'DESC') { + array_multisort($$sort_by, SORT_DESC, $each_tables); + } else { + array_multisort($$sort_by, SORT_ASC, $each_tables); + } + } + + // cleanup the temporary sort array + unset($$sort_by); + } + + if ($limit_count) { + $each_tables = array_slice( + $each_tables, $limit_offset, $limit_count + ); + } + + foreach ($each_tables as $table_name => $each_table) { + if (! isset($each_tables[$table_name]['Type']) + && isset($each_tables[$table_name]['Engine']) + ) { + // pma BC, same parts of PMA still uses 'Type' + $each_tables[$table_name]['Type'] + =& $each_tables[$table_name]['Engine']; + } elseif (! isset($each_tables[$table_name]['Engine']) + && isset($each_tables[$table_name]['Type']) + ) { + // old MySQL reports Type, newer MySQL reports Engine + $each_tables[$table_name]['Engine'] + =& $each_tables[$table_name]['Type']; + } + + // Compatibility with INFORMATION_SCHEMA output + $each_tables[$table_name]['TABLE_SCHEMA'] + = $each_database; + $each_tables[$table_name]['TABLE_NAME'] + =& $each_tables[$table_name]['Name']; + $each_tables[$table_name]['ENGINE'] + =& $each_tables[$table_name]['Engine']; + $each_tables[$table_name]['VERSION'] + =& $each_tables[$table_name]['Version']; + $each_tables[$table_name]['ROW_FORMAT'] + =& $each_tables[$table_name]['Row_format']; + $each_tables[$table_name]['TABLE_ROWS'] + =& $each_tables[$table_name]['Rows']; + $each_tables[$table_name]['AVG_ROW_LENGTH'] + =& $each_tables[$table_name]['Avg_row_length']; + $each_tables[$table_name]['DATA_LENGTH'] + =& $each_tables[$table_name]['Data_length']; + $each_tables[$table_name]['MAX_DATA_LENGTH'] + =& $each_tables[$table_name]['Max_data_length']; + $each_tables[$table_name]['INDEX_LENGTH'] + =& $each_tables[$table_name]['Index_length']; + $each_tables[$table_name]['DATA_FREE'] + =& $each_tables[$table_name]['Data_free']; + $each_tables[$table_name]['AUTO_INCREMENT'] + =& $each_tables[$table_name]['Auto_increment']; + $each_tables[$table_name]['CREATE_TIME'] + =& $each_tables[$table_name]['Create_time']; + $each_tables[$table_name]['UPDATE_TIME'] + =& $each_tables[$table_name]['Update_time']; + $each_tables[$table_name]['CHECK_TIME'] + =& $each_tables[$table_name]['Check_time']; + $each_tables[$table_name]['TABLE_COLLATION'] + =& $each_tables[$table_name]['Collation']; + $each_tables[$table_name]['CHECKSUM'] + =& $each_tables[$table_name]['Checksum']; + $each_tables[$table_name]['CREATE_OPTIONS'] + =& $each_tables[$table_name]['Create_options']; + $each_tables[$table_name]['TABLE_COMMENT'] + =& $each_tables[$table_name]['Comment']; + + if (strtoupper($each_tables[$table_name]['Comment']) === 'VIEW' + && $each_tables[$table_name]['Engine'] == null + ) { + $each_tables[$table_name]['TABLE_TYPE'] = 'VIEW'; + } elseif ($each_database == 'information_schema') { + $each_tables[$table_name]['TABLE_TYPE'] = 'SYSTEM VIEW'; + } else { + /** + * @todo difference between 'TEMPORARY' and 'BASE TABLE' + * but how to detect? + */ + $each_tables[$table_name]['TABLE_TYPE'] = 'BASE TABLE'; + } + } + + $tables[$each_database] = $each_tables; + } + } + + // cache table data + // so Table does not require to issue SHOW TABLE STATUS again + $this->_cacheTableData($tables, $table); + + if (is_array($database)) { + return $tables; + } + + if (isset($tables[$database])) { + return $tables[$database]; + } + + if (isset($tables[mb_strtolower($database)])) { + // on windows with lower_case_table_names = 1 + // MySQL returns + // with SHOW DATABASES or information_schema.SCHEMATA: `Test` + // but information_schema.TABLES gives `test` + // see https://github.com/phpmyadmin/phpmyadmin/issues/8402 + return $tables[mb_strtolower($database)]; + } + + return $tables; + } + + /** + * Get VIEWs in a particular database + * + * @param string $db Database name to look in + * + * @return array $views Set of VIEWs inside the database + */ + public function getVirtualTables($db) + { + + $tables_full = $this->getTablesFull($db); + $views = array(); + + foreach ($tables_full as $table=>$tmp) { + + $_table = $this->getTable($db, $table); + if ($_table->isView()) { + $views[] = $table; + } + + } + + return $views; + + } + + + /** + * returns array with databases containing extended infos about them + * + * @param string $database database + * @param boolean $force_stats retrieve stats also for MySQL < 5 + * @param integer $link link type + * @param string $sort_by column to order by + * @param string $sort_order ASC or DESC + * @param integer $limit_offset starting offset for LIMIT + * @param bool|int $limit_count row count for LIMIT or true + * for $GLOBALS['cfg']['MaxDbList'] + * + * @todo move into ListDatabase? + * + * @return array $databases + */ + public function getDatabasesFull($database = null, $force_stats = false, + $link = DatabaseInterface::CONNECT_USER, $sort_by = 'SCHEMA_NAME', $sort_order = 'ASC', + $limit_offset = 0, $limit_count = false + ) { + $sort_order = strtoupper($sort_order); + + if (true === $limit_count) { + $limit_count = $GLOBALS['cfg']['MaxDbList']; + } + + $apply_limit_and_order_manual = true; + + if (! $GLOBALS['cfg']['Server']['DisableIS']) { + /** + * if $GLOBALS['cfg']['NaturalOrder'] is enabled, we cannot use LIMIT + * cause MySQL does not support natural ordering, + * we have to do it afterward + */ + $limit = ''; + if (! $GLOBALS['cfg']['NaturalOrder']) { + if ($limit_count) { + $limit = ' LIMIT ' . $limit_count . ' OFFSET ' . $limit_offset; + } + + $apply_limit_and_order_manual = false; + } + + // get table information from information_schema + if (! empty($database)) { + $sql_where_schema = 'WHERE `SCHEMA_NAME` LIKE \'' + . $this->escapeString($database, $link) . '\''; + } else { + $sql_where_schema = ''; + } + + $sql = 'SELECT *, + CAST(BIN_NAME AS CHAR CHARACTER SET utf8) AS SCHEMA_NAME + FROM ('; + $sql .= 'SELECT + BINARY s.SCHEMA_NAME AS BIN_NAME, + s.DEFAULT_COLLATION_NAME'; + if ($force_stats) { + $sql .= ', + COUNT(t.TABLE_SCHEMA) AS SCHEMA_TABLES, + SUM(t.TABLE_ROWS) AS SCHEMA_TABLE_ROWS, + SUM(t.DATA_LENGTH) AS SCHEMA_DATA_LENGTH, + SUM(t.MAX_DATA_LENGTH) AS SCHEMA_MAX_DATA_LENGTH, + SUM(t.INDEX_LENGTH) AS SCHEMA_INDEX_LENGTH, + SUM(t.DATA_LENGTH + t.INDEX_LENGTH) + AS SCHEMA_LENGTH, + SUM(IF(t.ENGINE <> \'InnoDB\', t.DATA_FREE, 0)) + AS SCHEMA_DATA_FREE'; + } + $sql .= ' + FROM `information_schema`.SCHEMATA s '; + if ($force_stats) { + $sql .= ' + LEFT JOIN `information_schema`.TABLES t + ON BINARY t.TABLE_SCHEMA = BINARY s.SCHEMA_NAME'; + } + $sql .= $sql_where_schema . ' + GROUP BY BINARY s.SCHEMA_NAME, s.DEFAULT_COLLATION_NAME + ORDER BY '; + if ($sort_by == 'SCHEMA_NAME' + || $sort_by == 'DEFAULT_COLLATION_NAME' + ) { + $sql .= 'BINARY '; + } + $sql .= Util::backquote($sort_by) + . ' ' . $sort_order + . $limit; + $sql .= ') a'; + + $databases = $this->fetchResult($sql, 'SCHEMA_NAME', null, $link); + + $mysql_error = $this->getError($link); + if (! count($databases) && $GLOBALS['errno']) { + Util::mysqlDie($mysql_error, $sql); + } + + // display only databases also in official database list + // f.e. to apply hide_db and only_db + $drops = array_diff( + array_keys($databases), (array) $GLOBALS['dblist']->databases + ); + foreach ($drops as $drop) { + unset($databases[$drop]); + } + } else { + $databases = array(); + foreach ($GLOBALS['dblist']->databases as $database_name) { + // Compatibility with INFORMATION_SCHEMA output + $databases[$database_name]['SCHEMA_NAME'] = $database_name; + + $databases[$database_name]['DEFAULT_COLLATION_NAME'] + = $this->getDbCollation($database_name); + + if (!$force_stats) { + continue; + } + + // get additional info about tables + $databases[$database_name]['SCHEMA_TABLES'] = 0; + $databases[$database_name]['SCHEMA_TABLE_ROWS'] = 0; + $databases[$database_name]['SCHEMA_DATA_LENGTH'] = 0; + $databases[$database_name]['SCHEMA_MAX_DATA_LENGTH'] = 0; + $databases[$database_name]['SCHEMA_INDEX_LENGTH'] = 0; + $databases[$database_name]['SCHEMA_LENGTH'] = 0; + $databases[$database_name]['SCHEMA_DATA_FREE'] = 0; + + $res = $this->query( + 'SHOW TABLE STATUS FROM ' + . Util::backquote($database_name) . ';' + ); + + if ($res === false) { + unset($res); + continue; + } + + while ($row = $this->fetchAssoc($res)) { + $databases[$database_name]['SCHEMA_TABLES']++; + $databases[$database_name]['SCHEMA_TABLE_ROWS'] + += $row['Rows']; + $databases[$database_name]['SCHEMA_DATA_LENGTH'] + += $row['Data_length']; + $databases[$database_name]['SCHEMA_MAX_DATA_LENGTH'] + += $row['Max_data_length']; + $databases[$database_name]['SCHEMA_INDEX_LENGTH'] + += $row['Index_length']; + + // for InnoDB, this does not contain the number of + // overhead bytes but the total free space + if ('InnoDB' != $row['Engine']) { + $databases[$database_name]['SCHEMA_DATA_FREE'] + += $row['Data_free']; + } + $databases[$database_name]['SCHEMA_LENGTH'] + += $row['Data_length'] + $row['Index_length']; + } + $this->freeResult($res); + unset($res); + } + } + + /** + * apply limit and order manually now + * (caused by older MySQL < 5 or $GLOBALS['cfg']['NaturalOrder']) + */ + if ($apply_limit_and_order_manual) { + $GLOBALS['callback_sort_order'] = $sort_order; + $GLOBALS['callback_sort_by'] = $sort_by; + usort( + $databases, + array(self::class, '_usortComparisonCallback') + ); + unset($GLOBALS['callback_sort_order'], $GLOBALS['callback_sort_by']); + + /** + * now apply limit + */ + if ($limit_count) { + $databases = array_slice($databases, $limit_offset, $limit_count); + } + } + + return $databases; + } + + /** + * usort comparison callback + * + * @param string $a first argument to sort + * @param string $b second argument to sort + * + * @return integer a value representing whether $a should be before $b in the + * sorted array or not + * + * @access private + */ + private static function _usortComparisonCallback($a, $b) + { + if ($GLOBALS['cfg']['NaturalOrder']) { + $sorter = 'strnatcasecmp'; + } else { + $sorter = 'strcasecmp'; + } + /* No sorting when key is not present */ + if (! isset($a[$GLOBALS['callback_sort_by']]) + || ! isset($b[$GLOBALS['callback_sort_by']]) + ) { + return 0; + } + // produces f.e.: + // return -1 * strnatcasecmp($a["SCHEMA_TABLES"], $b["SCHEMA_TABLES"]) + return ($GLOBALS['callback_sort_order'] == 'ASC' ? 1 : -1) * $sorter( + $a[$GLOBALS['callback_sort_by']], $b[$GLOBALS['callback_sort_by']] + ); + } // end of the '_usortComparisonCallback()' method + + /** + * returns detailed array with all columns for sql + * + * @param string $sql_query target SQL query to get columns + * @param array $view_columns alias for columns + * + * @return array + */ + public function getColumnMapFromSql($sql_query, array $view_columns = array()) + { + $result = $this->tryQuery($sql_query); + + if ($result === false) { + return array(); + } + + $meta = $this->getFieldsMeta( + $result + ); + + $nbFields = count($meta); + if ($nbFields <= 0) { + return array(); + } + + $column_map = array(); + $nbColumns = count($view_columns); + + for ($i=0; $i < $nbFields; $i++) { + + $map = array(); + $map['table_name'] = $meta[$i]->table; + $map['refering_column'] = $meta[$i]->name; + + if ($nbColumns > 1) { + $map['real_column'] = $view_columns[$i]; + } + + $column_map[] = $map; + } + + return $column_map; + } + + /** + * returns detailed array with all columns for given table in database, + * or all tables/databases + * + * @param string $database name of database + * @param string $table name of table to retrieve columns from + * @param string $column name of specific column + * @param mixed $link mysql link resource + * + * @return array + */ + public function getColumnsFull($database = null, $table = null, + $column = null, $link = DatabaseInterface::CONNECT_USER + ) { + if (! $GLOBALS['cfg']['Server']['DisableIS']) { + $sql_wheres = array(); + $array_keys = array(); + + // get columns information from information_schema + if (null !== $database) { + $sql_wheres[] = '`TABLE_SCHEMA` = \'' + . $this->escapeString($database, $link) . '\' '; + } else { + $array_keys[] = 'TABLE_SCHEMA'; + } + if (null !== $table) { + $sql_wheres[] = '`TABLE_NAME` = \'' + . $this->escapeString($table, $link) . '\' '; + } else { + $array_keys[] = 'TABLE_NAME'; + } + if (null !== $column) { + $sql_wheres[] = '`COLUMN_NAME` = \'' + . $this->escapeString($column, $link) . '\' '; + } else { + $array_keys[] = 'COLUMN_NAME'; + } + + // for PMA bc: + // `[SCHEMA_FIELD_NAME]` AS `[SHOW_FULL_COLUMNS_FIELD_NAME]` + $sql = ' + SELECT *, + `COLUMN_NAME` AS `Field`, + `COLUMN_TYPE` AS `Type`, + `COLLATION_NAME` AS `Collation`, + `IS_NULLABLE` AS `Null`, + `COLUMN_KEY` AS `Key`, + `COLUMN_DEFAULT` AS `Default`, + `EXTRA` AS `Extra`, + `PRIVILEGES` AS `Privileges`, + `COLUMN_COMMENT` AS `Comment` + FROM `information_schema`.`COLUMNS`'; + + if (count($sql_wheres)) { + $sql .= "\n" . ' WHERE ' . implode(' AND ', $sql_wheres); + } + return $this->fetchResult($sql, $array_keys, null, $link); + } + + $columns = array(); + if (null === $database) { + foreach ($GLOBALS['dblist']->databases as $database) { + $columns[$database] = $this->getColumnsFull( + $database, null, null, $link + ); + } + return $columns; + } elseif (null === $table) { + $tables = $this->getTables($database); + foreach ($tables as $table) { + $columns[$table] = $this->getColumnsFull( + $database, $table, null, $link + ); + } + return $columns; + } + $sql = 'SHOW FULL COLUMNS FROM ' + . Util::backquote($database) . '.' . Util::backquote($table); + if (null !== $column) { + $sql .= " LIKE '" . $this->escapeString($column, $link) . "'"; + } + + $columns = $this->fetchResult($sql, 'Field', null, $link); + $ordinal_position = 1; + foreach ($columns as $column_name => $each_column) { + + // Compatibility with INFORMATION_SCHEMA output + $columns[$column_name]['COLUMN_NAME'] + =& $columns[$column_name]['Field']; + $columns[$column_name]['COLUMN_TYPE'] + =& $columns[$column_name]['Type']; + $columns[$column_name]['COLLATION_NAME'] + =& $columns[$column_name]['Collation']; + $columns[$column_name]['IS_NULLABLE'] + =& $columns[$column_name]['Null']; + $columns[$column_name]['COLUMN_KEY'] + =& $columns[$column_name]['Key']; + $columns[$column_name]['COLUMN_DEFAULT'] + =& $columns[$column_name]['Default']; + $columns[$column_name]['EXTRA'] + =& $columns[$column_name]['Extra']; + $columns[$column_name]['PRIVILEGES'] + =& $columns[$column_name]['Privileges']; + $columns[$column_name]['COLUMN_COMMENT'] + =& $columns[$column_name]['Comment']; + + $columns[$column_name]['TABLE_CATALOG'] = null; + $columns[$column_name]['TABLE_SCHEMA'] = $database; + $columns[$column_name]['TABLE_NAME'] = $table; + $columns[$column_name]['ORDINAL_POSITION'] = $ordinal_position; + $columns[$column_name]['DATA_TYPE'] + = substr( + $columns[$column_name]['COLUMN_TYPE'], + 0, + strpos($columns[$column_name]['COLUMN_TYPE'], '(') + ); + /** + * @todo guess CHARACTER_MAXIMUM_LENGTH from COLUMN_TYPE + */ + $columns[$column_name]['CHARACTER_MAXIMUM_LENGTH'] = null; + /** + * @todo guess CHARACTER_OCTET_LENGTH from CHARACTER_MAXIMUM_LENGTH + */ + $columns[$column_name]['CHARACTER_OCTET_LENGTH'] = null; + $columns[$column_name]['NUMERIC_PRECISION'] = null; + $columns[$column_name]['NUMERIC_SCALE'] = null; + $columns[$column_name]['CHARACTER_SET_NAME'] + = substr( + $columns[$column_name]['COLLATION_NAME'], + 0, + strpos($columns[$column_name]['COLLATION_NAME'], '_') + ); + + $ordinal_position++; + } + + if (null !== $column) { + return reset($columns); + } + + return $columns; + } + + /** + * Returns SQL query for fetching columns for a table + * + * The 'Key' column is not calculated properly, use $GLOBALS['dbi']->getColumns() + * to get correct values. + * + * @param string $database name of database + * @param string $table name of table to retrieve columns from + * @param string $column name of column, null to show all columns + * @param boolean $full whether to return full info or only column names + * + * @see getColumns() + * + * @return string + */ + public function getColumnsSql($database, $table, $column = null, $full = false) + { + $sql = 'SHOW ' . ($full ? 'FULL' : '') . ' COLUMNS FROM ' + . Util::backquote($database) . '.' . Util::backquote($table) + . (($column !== null) ? "LIKE '" + . $GLOBALS['dbi']->escapeString($column) . "'" : ''); + + return $sql; + } + + /** + * Returns descriptions of columns in given table (all or given by $column) + * + * @param string $database name of database + * @param string $table name of table to retrieve columns from + * @param string $column name of column, null to show all columns + * @param boolean $full whether to return full info or only column names + * @param integer $link link type + * + * @return array array indexed by column names or, + * if $column is given, flat array description + */ + public function getColumns($database, $table, $column = null, $full = false, + $link = DatabaseInterface::CONNECT_USER + ) { + $sql = $this->getColumnsSql($database, $table, $column, $full); + $fields = $this->fetchResult($sql, 'Field', null, $link); + if (! is_array($fields) || count($fields) == 0) { + return array(); + } + // Check if column is a part of multiple-column index and set its 'Key'. + $indexes = Index::getFromTable($table, $database); + foreach ($fields as $field => $field_data) { + if (!empty($field_data['Key'])) { + continue; + } + + foreach ($indexes as $index) { + /** @var Index $index */ + if (!$index->hasColumn($field)) { + continue; + } + + $index_columns = $index->getColumns(); + if ($index_columns[$field]->getSeqInIndex() > 1) { + if ($index->isUnique()) { + $fields[$field]['Key'] = 'UNI'; + } else { + $fields[$field]['Key'] = 'MUL'; + } + } + } + } + + return ($column != null) ? array_shift($fields) : $fields; + } + + /** + * Returns all column names in given table + * + * @param string $database name of database + * @param string $table name of table to retrieve columns from + * @param mixed $link mysql link resource + * + * @return null|array + */ + public function getColumnNames($database, $table, $link = DatabaseInterface::CONNECT_USER) + { + $sql = $this->getColumnsSql($database, $table); + // We only need the 'Field' column which contains the table's column names + $fields = array_keys($this->fetchResult($sql, 'Field', null, $link)); + + if (! is_array($fields) || count($fields) == 0) { + return null; + } + return $fields; + } + + /** + * Returns SQL for fetching information on table indexes (SHOW INDEXES) + * + * @param string $database name of database + * @param string $table name of the table whose indexes are to be retrieved + * @param string $where additional conditions for WHERE + * + * @return string SQL for getting indexes + */ + public function getTableIndexesSql($database, $table, $where = null) + { + $sql = 'SHOW INDEXES FROM ' . Util::backquote($database) . '.' + . Util::backquote($table); + if ($where) { + $sql .= ' WHERE (' . $where . ')'; + } + return $sql; + } + + /** + * Returns indexes of a table + * + * @param string $database name of database + * @param string $table name of the table whose indexes are to be retrieved + * @param mixed $link mysql link resource + * + * @return array $indexes + */ + public function getTableIndexes($database, $table, $link = DatabaseInterface::CONNECT_USER) + { + $sql = $this->getTableIndexesSql($database, $table); + $indexes = $this->fetchResult($sql, null, null, $link); + + if (! is_array($indexes) || count($indexes) < 1) { + return array(); + } + return $indexes; + } + + /** + * returns value of given mysql server variable + * + * @param string $var mysql server variable name + * @param int $type DatabaseInterface::GETVAR_SESSION | + * DatabaseInterface::GETVAR_GLOBAL + * @param mixed $link mysql link resource|object + * + * @return mixed value for mysql server variable + */ + public function getVariable( + $var, $type = self::GETVAR_SESSION, $link = DatabaseInterface::CONNECT_USER + ) { + switch ($type) { + case self::GETVAR_SESSION: + $modifier = ' SESSION'; + break; + case self::GETVAR_GLOBAL: + $modifier = ' GLOBAL'; + break; + default: + $modifier = ''; + } + return $this->fetchValue( + 'SHOW' . $modifier . ' VARIABLES LIKE \'' . $var . '\';', 0, 1, $link + ); + } + + /** + * Sets new value for a variable if it is different from the current value + * + * @param string $var variable name + * @param string $value value to set + * @param mixed $link mysql link resource|object + * + * @return bool whether query was a successful + */ + public function setVariable($var, $value, $link = DatabaseInterface::CONNECT_USER) + { + $current_value = $this->getVariable( + $var, self::GETVAR_SESSION, $link + ); + if ($current_value == $value) { + return true; + } + + return $this->query("SET " . $var . " = " . $value . ';', $link); + } + + /** + * Convert version string to integer. + * + * @param string $version MySQL server version + * + * @return int + */ + public static function versionToInt($version) + { + $match = explode('.', $version); + return (int) sprintf('%d%02d%02d', $match[0], $match[1], intval($match[2])); + } + + /** + * Function called just after a connection to the MySQL database server has + * been established. It sets the connection collation, and determines the + * version of MySQL which is running. + * + * @return void + */ + public function postConnect() + { + $version = $this->fetchSingleRow( + 'SELECT @@version, @@version_comment', + 'ASSOC', + DatabaseInterface::CONNECT_USER + ); + + if ($version) { + $this->_version_int = self::versionToInt($version['@@version']); + $this->_version_str = $version['@@version']; + $this->_version_comment = $version['@@version_comment']; + if (stripos($version['@@version'], 'mariadb') !== false) { + $this->_is_mariadb = true; + } + if (stripos($version['@@version_comment'], 'percona') !== false) { + $this->_is_percona = true; + } + } + + if ($this->_version_int > 50503) { + $default_charset = 'utf8mb4'; + $default_collation = 'utf8mb4_general_ci'; + } else { + $default_charset = 'utf8'; + $default_collation = 'utf8_general_ci'; + } + $GLOBALS['collation_connection'] = $default_collation; + $GLOBALS['charset_connection'] = $default_charset; + $this->query( + "SET NAMES '$default_charset' COLLATE '$default_collation';", + DatabaseInterface::CONNECT_USER, + self::QUERY_STORE + ); + + /* Locale for messages */ + $locale = LanguageManager::getInstance()->getCurrentLanguage()->getMySQLLocale(); + if (! empty($locale)) { + $this->query( + "SET lc_messages = '" . $locale . "';", + DatabaseInterface::CONNECT_USER, + self::QUERY_STORE + ); + } + + // Set timezone for the session, if required. + if ($GLOBALS['cfg']['Server']['SessionTimeZone'] != '') { + $sql_query_tz = 'SET ' . Util::backquote('time_zone') . ' = ' + . '\'' + . $this->escapeString($GLOBALS['cfg']['Server']['SessionTimeZone']) + . '\''; + + if (! $this->tryQuery($sql_query_tz)) { + $error_message_tz = sprintf( + __( + 'Unable to use timezone "%1$s" for server %2$d. ' + . 'Please check your configuration setting for ' + . '[em]$cfg[\'Servers\'][%3$d][\'SessionTimeZone\'][/em]. ' + . 'phpMyAdmin is currently using the default time zone ' + . 'of the database server.' + ), + $GLOBALS['cfg']['Server']['SessionTimeZone'], + $GLOBALS['server'], + $GLOBALS['server'] + ); + + trigger_error($error_message_tz, E_USER_WARNING); + } + } + + /* Loads closest context to this version. */ + \PhpMyAdmin\SqlParser\Context::loadClosest( + ($this->_is_mariadb ? 'MariaDb' : 'MySql') . $this->_version_int + ); + + /** + * the DatabaseList class as a stub for the ListDatabase class + */ + $GLOBALS['dblist'] = new DatabaseList(); + } + + /** + * Sets collation connection for user link + * + * @param string $collation collation to set + */ + public function setCollation($collation) + { + $charset = $GLOBALS['charset_connection']; + /* Automatically adjust collation if not supported by server */ + if ($charset == 'utf8' && strncmp('utf8mb4_', $collation, 8) == 0) { + $collation = 'utf8_' . substr($collation, 8); + } + $result = $this->tryQuery( + "SET collation_connection = '" + . $this->escapeString($collation, DatabaseInterface::CONNECT_USER) + . "';", + DatabaseInterface::CONNECT_USER, + self::QUERY_STORE + ); + if ($result === false) { + trigger_error( + __('Failed to set configured collation connection!'), + E_USER_WARNING + ); + } else { + $GLOBALS['collation_connection'] = $collation; + } + } + + /** + * Function called just after a connection to the MySQL database server has + * been established. It sets the connection collation, and determines the + * version of MySQL which is running. + * + * @param integer $link link type + * + * @return void + */ + public function postConnectControl() + { + // If Zero configuration mode enabled, check PMA tables in current db. + if ($GLOBALS['cfg']['ZeroConf'] == true) { + /** + * the DatabaseList class as a stub for the ListDatabase class + */ + $GLOBALS['dblist'] = new DatabaseList(); + + if (strlen($GLOBALS['db'])) { + $cfgRelation = $this->relation->getRelationsParam(); + if (empty($cfgRelation['db'])) { + $this->relation->fixPmaTables($GLOBALS['db'], false); + } + } + $cfgRelation = $this->relation->getRelationsParam(); + if (empty($cfgRelation['db'])) { + if ($GLOBALS['dblist']->databases->exists('phpmyadmin')) { + $this->relation->fixPmaTables('phpmyadmin', false); + } + } + } + } + + /** + * returns a single value from the given result or query, + * if the query or the result has more than one row or field + * the first field of the first row is returned + * + * + * $sql = 'SELECT `name` FROM `user` WHERE `id` = 123'; + * $user_name = $GLOBALS['dbi']->fetchValue($sql); + * // produces + * // $user_name = 'John Doe' + * + * + * @param string $query The query to execute + * @param integer $row_number row to fetch the value from, + * starting at 0, with 0 being default + * @param integer|string $field field to fetch the value from, + * starting at 0, with 0 being default + * @param integer $link link type + * + * @return mixed value of first field in first row from result + * or false if not found + */ + public function fetchValue($query, $row_number = 0, $field = 0, $link = DatabaseInterface::CONNECT_USER) + { + $value = false; + + $result = $this->tryQuery( + $query, + $link, + self::QUERY_STORE, + false + ); + if ($result === false) { + return false; + } + + // return false if result is empty or false + // or requested row is larger than rows in result + if ($this->numRows($result) < ($row_number + 1)) { + return $value; + } + + // if $field is an integer use non associative mysql fetch function + if (is_int($field)) { + $fetch_function = 'fetchRow'; + } else { + $fetch_function = 'fetchAssoc'; + } + + // get requested row + for ($i = 0; $i <= $row_number; $i++) { + $row = $this->$fetch_function($result); + } + $this->freeResult($result); + + // return requested field + if (isset($row[$field])) { + $value = $row[$field]; + } + + return $value; + } + + /** + * returns only the first row from the result + * + * + * $sql = 'SELECT * FROM `user` WHERE `id` = 123'; + * $user = $GLOBALS['dbi']->fetchSingleRow($sql); + * // produces + * // $user = array('id' => 123, 'name' => 'John Doe') + * + * + * @param string $query The query to execute + * @param string $type NUM|ASSOC|BOTH returned array should either numeric + * associative or both + * @param integer $link link type + * + * @return array|boolean first row from result + * or false if result is empty + */ + public function fetchSingleRow($query, $type = 'ASSOC', $link = DatabaseInterface::CONNECT_USER) + { + $result = $this->tryQuery( + $query, + $link, + self::QUERY_STORE, + false + ); + if ($result === false) { + return false; + } + + // return false if result is empty or false + if (! $this->numRows($result)) { + return false; + } + + switch ($type) { + case 'NUM' : + $fetch_function = 'fetchRow'; + break; + case 'ASSOC' : + $fetch_function = 'fetchAssoc'; + break; + case 'BOTH' : + default : + $fetch_function = 'fetchArray'; + break; + } + + $row = $this->$fetch_function($result); + $this->freeResult($result); + return $row; + } + + /** + * Returns row or element of a row + * + * @param array $row Row to process + * @param string|null $value Which column to return + * + * @return mixed + */ + private function _fetchValue(array $row, $value) + { + if (is_null($value)) { + return $row; + } + + return $row[$value]; + } + + /** + * returns all rows in the resultset in one array + * + * + * $sql = 'SELECT * FROM `user`'; + * $users = $GLOBALS['dbi']->fetchResult($sql); + * // produces + * // $users[] = array('id' => 123, 'name' => 'John Doe') + * + * $sql = 'SELECT `id`, `name` FROM `user`'; + * $users = $GLOBALS['dbi']->fetchResult($sql, 'id'); + * // produces + * // $users['123'] = array('id' => 123, 'name' => 'John Doe') + * + * $sql = 'SELECT `id`, `name` FROM `user`'; + * $users = $GLOBALS['dbi']->fetchResult($sql, 0); + * // produces + * // $users['123'] = array(0 => 123, 1 => 'John Doe') + * + * $sql = 'SELECT `id`, `name` FROM `user`'; + * $users = $GLOBALS['dbi']->fetchResult($sql, 'id', 'name'); + * // or + * $users = $GLOBALS['dbi']->fetchResult($sql, 0, 1); + * // produces + * // $users['123'] = 'John Doe' + * + * $sql = 'SELECT `name` FROM `user`'; + * $users = $GLOBALS['dbi']->fetchResult($sql); + * // produces + * // $users[] = 'John Doe' + * + * $sql = 'SELECT `group`, `name` FROM `user`' + * $users = $GLOBALS['dbi']->fetchResult($sql, array('group', null), 'name'); + * // produces + * // $users['admin'][] = 'John Doe' + * + * $sql = 'SELECT `group`, `name` FROM `user`' + * $users = $GLOBALS['dbi']->fetchResult($sql, array('group', 'name'), 'id'); + * // produces + * // $users['admin']['John Doe'] = '123' + * + * + * @param string $query query to execute + * @param string|integer|array $key field-name or offset + * used as key for array + * or array of those + * @param string|integer $value value-name or offset + * used as value for array + * @param integer $link link type + * @param integer $options query options + * + * @return array resultrows or values indexed by $key + */ + public function fetchResult($query, $key = null, $value = null, + $link = DatabaseInterface::CONNECT_USER, $options = 0 + ) { + $resultrows = array(); + + $result = $this->tryQuery($query, $link, $options, false); + + // return empty array if result is empty or false + if ($result === false) { + return $resultrows; + } + + $fetch_function = 'fetchAssoc'; + + // no nested array if only one field is in result + if (null === $key && 1 === $this->numFields($result)) { + $value = 0; + $fetch_function = 'fetchRow'; + } + + // if $key is an integer use non associative mysql fetch function + if (is_int($key)) { + $fetch_function = 'fetchRow'; + } + + if (null === $key) { + while ($row = $this->$fetch_function($result)) { + $resultrows[] = $this->_fetchValue($row, $value); + } + } else { + if (is_array($key)) { + while ($row = $this->$fetch_function($result)) { + $result_target =& $resultrows; + foreach ($key as $key_index) { + if (null === $key_index) { + $result_target =& $result_target[]; + continue; + } + + if (! isset($result_target[$row[$key_index]])) { + $result_target[$row[$key_index]] = array(); + } + $result_target =& $result_target[$row[$key_index]]; + } + $result_target = $this->_fetchValue($row, $value); + } + } else { + while ($row = $this->$fetch_function($result)) { + $resultrows[$row[$key]] = $this->_fetchValue($row, $value); + } + } + } + + $this->freeResult($result); + return $resultrows; + } + + /** + * Get supported SQL compatibility modes + * + * @return array supported SQL compatibility modes + */ + public function getCompatibilities() + { + $compats = array('NONE'); + $compats[] = 'ANSI'; + $compats[] = 'DB2'; + $compats[] = 'MAXDB'; + $compats[] = 'MYSQL323'; + $compats[] = 'MYSQL40'; + $compats[] = 'MSSQL'; + $compats[] = 'ORACLE'; + // removed; in MySQL 5.0.33, this produces exports that + // can't be read by POSTGRESQL (see our bug #1596328) + //$compats[] = 'POSTGRESQL'; + $compats[] = 'TRADITIONAL'; + + return $compats; + } + + /** + * returns warnings for last query + * + * @param integer $link link type + * + * @return array warnings + */ + public function getWarnings($link = DatabaseInterface::CONNECT_USER) + { + return $this->fetchResult('SHOW WARNINGS', null, null, $link); + } + + /** + * returns an array of PROCEDURE or FUNCTION names for a db + * + * @param string $db db name + * @param string $which PROCEDURE | FUNCTION + * @param integer $link link type + * + * @return array the procedure names or function names + */ + public function getProceduresOrFunctions($db, $which, $link = DatabaseInterface::CONNECT_USER) + { + $shows = $this->fetchResult( + 'SHOW ' . $which . ' STATUS;', null, null, $link + ); + $result = array(); + foreach ($shows as $one_show) { + if ($one_show['Db'] == $db && $one_show['Type'] == $which) { + $result[] = $one_show['Name']; + } + } + return($result); + } + + /** + * returns the definition of a specific PROCEDURE, FUNCTION, EVENT or VIEW + * + * @param string $db db name + * @param string $which PROCEDURE | FUNCTION | EVENT | VIEW + * @param string $name the procedure|function|event|view name + * @param integer $link link type + * + * @return string the definition + */ + public function getDefinition($db, $which, $name, $link = DatabaseInterface::CONNECT_USER) + { + $returned_field = array( + 'PROCEDURE' => 'Create Procedure', + 'FUNCTION' => 'Create Function', + 'EVENT' => 'Create Event', + 'VIEW' => 'Create View' + ); + $query = 'SHOW CREATE ' . $which . ' ' + . Util::backquote($db) . '.' + . Util::backquote($name); + return($this->fetchValue($query, 0, $returned_field[$which], $link)); + } + + /** + * returns details about the PROCEDUREs or FUNCTIONs for a specific database + * or details about a specific routine + * + * @param string $db db name + * @param string $which PROCEDURE | FUNCTION or null for both + * @param string $name name of the routine (to fetch a specific routine) + * + * @return array information about ROCEDUREs or FUNCTIONs + */ + public function getRoutines($db, $which = null, $name = '') + { + $routines = array(); + if (! $GLOBALS['cfg']['Server']['DisableIS']) { + $query = "SELECT" + . " `ROUTINE_SCHEMA` AS `Db`," + . " `SPECIFIC_NAME` AS `Name`," + . " `ROUTINE_TYPE` AS `Type`," + . " `DEFINER` AS `Definer`," + . " `LAST_ALTERED` AS `Modified`," + . " `CREATED` AS `Created`," + . " `SECURITY_TYPE` AS `Security_type`," + . " `ROUTINE_COMMENT` AS `Comment`," + . " `CHARACTER_SET_CLIENT` AS `character_set_client`," + . " `COLLATION_CONNECTION` AS `collation_connection`," + . " `DATABASE_COLLATION` AS `Database Collation`," + . " `DTD_IDENTIFIER`" + . " FROM `information_schema`.`ROUTINES`" + . " WHERE `ROUTINE_SCHEMA` " . Util::getCollateForIS() + . " = '" . $GLOBALS['dbi']->escapeString($db) . "'"; + if (Core::isValid($which, array('FUNCTION','PROCEDURE'))) { + $query .= " AND `ROUTINE_TYPE` = '" . $which . "'"; + } + if (! empty($name)) { + $query .= " AND `SPECIFIC_NAME`" + . " = '" . $GLOBALS['dbi']->escapeString($name) . "'"; + } + $result = $this->fetchResult($query); + if (!empty($result)) { + $routines = $result; + } + } else { + if ($which == 'FUNCTION' || $which == null) { + $query = "SHOW FUNCTION STATUS" + . " WHERE `Db` = '" . $GLOBALS['dbi']->escapeString($db) . "'"; + if (! empty($name)) { + $query .= " AND `Name` = '" + . $GLOBALS['dbi']->escapeString($name) . "'"; + } + $result = $this->fetchResult($query); + if (!empty($result)) { + $routines = array_merge($routines, $result); + } + } + if ($which == 'PROCEDURE' || $which == null) { + $query = "SHOW PROCEDURE STATUS" + . " WHERE `Db` = '" . $GLOBALS['dbi']->escapeString($db) . "'"; + if (! empty($name)) { + $query .= " AND `Name` = '" + . $GLOBALS['dbi']->escapeString($name) . "'"; + } + $result = $this->fetchResult($query); + if (!empty($result)) { + $routines = array_merge($routines, $result); + } + } + } + + $ret = array(); + foreach ($routines as $routine) { + $one_result = array(); + $one_result['db'] = $routine['Db']; + $one_result['name'] = $routine['Name']; + $one_result['type'] = $routine['Type']; + $one_result['definer'] = $routine['Definer']; + $one_result['returns'] = isset($routine['DTD_IDENTIFIER']) + ? $routine['DTD_IDENTIFIER'] : ""; + $ret[] = $one_result; + } + + // Sort results by name + $name = array(); + foreach ($ret as $value) { + $name[] = $value['name']; + } + array_multisort($name, SORT_ASC, $ret); + + return($ret); + } + + /** + * returns details about the EVENTs for a specific database + * + * @param string $db db name + * @param string $name event name + * + * @return array information about EVENTs + */ + public function getEvents($db, $name = '') + { + if (! $GLOBALS['cfg']['Server']['DisableIS']) { + $query = "SELECT" + . " `EVENT_SCHEMA` AS `Db`," + . " `EVENT_NAME` AS `Name`," + . " `DEFINER` AS `Definer`," + . " `TIME_ZONE` AS `Time zone`," + . " `EVENT_TYPE` AS `Type`," + . " `EXECUTE_AT` AS `Execute at`," + . " `INTERVAL_VALUE` AS `Interval value`," + . " `INTERVAL_FIELD` AS `Interval field`," + . " `STARTS` AS `Starts`," + . " `ENDS` AS `Ends`," + . " `STATUS` AS `Status`," + . " `ORIGINATOR` AS `Originator`," + . " `CHARACTER_SET_CLIENT` AS `character_set_client`," + . " `COLLATION_CONNECTION` AS `collation_connection`, " + . "`DATABASE_COLLATION` AS `Database Collation`" + . " FROM `information_schema`.`EVENTS`" + . " WHERE `EVENT_SCHEMA` " . Util::getCollateForIS() + . " = '" . $GLOBALS['dbi']->escapeString($db) . "'"; + if (! empty($name)) { + $query .= " AND `EVENT_NAME`" + . " = '" . $GLOBALS['dbi']->escapeString($name) . "'"; + } + } else { + $query = "SHOW EVENTS FROM " . Util::backquote($db); + if (! empty($name)) { + $query .= " AND `Name` = '" + . $GLOBALS['dbi']->escapeString($name) . "'"; + } + } + + $result = array(); + if ($events = $this->fetchResult($query)) { + foreach ($events as $event) { + $one_result = array(); + $one_result['name'] = $event['Name']; + $one_result['type'] = $event['Type']; + $one_result['status'] = $event['Status']; + $result[] = $one_result; + } + } + + // Sort results by name + $name = array(); + foreach ($result as $value) { + $name[] = $value['name']; + } + array_multisort($name, SORT_ASC, $result); + + return $result; + } + + /** + * returns details about the TRIGGERs for a specific table or database + * + * @param string $db db name + * @param string $table table name + * @param string $delimiter the delimiter to use (may be empty) + * + * @return array information about triggers (may be empty) + */ + public function getTriggers($db, $table = '', $delimiter = '//') + { + $result = array(); + if (! $GLOBALS['cfg']['Server']['DisableIS']) { + $query = 'SELECT TRIGGER_SCHEMA, TRIGGER_NAME, EVENT_MANIPULATION' + . ', EVENT_OBJECT_TABLE, ACTION_TIMING, ACTION_STATEMENT' + . ', EVENT_OBJECT_SCHEMA, EVENT_OBJECT_TABLE, DEFINER' + . ' FROM information_schema.TRIGGERS' + . ' WHERE EVENT_OBJECT_SCHEMA ' . Util::getCollateForIS() . '=' + . ' \'' . $GLOBALS['dbi']->escapeString($db) . '\''; + + if (! empty($table)) { + $query .= " AND EVENT_OBJECT_TABLE " . Util::getCollateForIS() + . " = '" . $GLOBALS['dbi']->escapeString($table) . "';"; + } + } else { + $query = "SHOW TRIGGERS FROM " . Util::backquote($db); + if (! empty($table)) { + $query .= " LIKE '" . $GLOBALS['dbi']->escapeString($table) . "';"; + } + } + + if ($triggers = $this->fetchResult($query)) { + foreach ($triggers as $trigger) { + if ($GLOBALS['cfg']['Server']['DisableIS']) { + $trigger['TRIGGER_NAME'] = $trigger['Trigger']; + $trigger['ACTION_TIMING'] = $trigger['Timing']; + $trigger['EVENT_MANIPULATION'] = $trigger['Event']; + $trigger['EVENT_OBJECT_TABLE'] = $trigger['Table']; + $trigger['ACTION_STATEMENT'] = $trigger['Statement']; + $trigger['DEFINER'] = $trigger['Definer']; + } + $one_result = array(); + $one_result['name'] = $trigger['TRIGGER_NAME']; + $one_result['table'] = $trigger['EVENT_OBJECT_TABLE']; + $one_result['action_timing'] = $trigger['ACTION_TIMING']; + $one_result['event_manipulation'] = $trigger['EVENT_MANIPULATION']; + $one_result['definition'] = $trigger['ACTION_STATEMENT']; + $one_result['definer'] = $trigger['DEFINER']; + + // do not prepend the schema name; this way, importing the + // definition into another schema will work + $one_result['full_trigger_name'] = Util::backquote( + $trigger['TRIGGER_NAME'] + ); + $one_result['drop'] = 'DROP TRIGGER IF EXISTS ' + . $one_result['full_trigger_name']; + $one_result['create'] = 'CREATE TRIGGER ' + . $one_result['full_trigger_name'] . ' ' + . $trigger['ACTION_TIMING'] . ' ' + . $trigger['EVENT_MANIPULATION'] + . ' ON ' . Util::backquote($trigger['EVENT_OBJECT_TABLE']) + . "\n" . ' FOR EACH ROW ' + . $trigger['ACTION_STATEMENT'] . "\n" . $delimiter . "\n"; + + $result[] = $one_result; + } + } + + // Sort results by name + $name = array(); + foreach ($result as $value) { + $name[] = $value['name']; + } + array_multisort($name, SORT_ASC, $result); + + return($result); + } + + /** + * Formats database error message in a friendly way. + * This is needed because some errors messages cannot + * be obtained by mysql_error(). + * + * @param int $error_number Error code + * @param string $error_message Error message as returned by server + * + * @return string HML text with error details + */ + public static function formatError($error_number, $error_message) + { + $error_message = htmlspecialchars($error_message); + + $error = '#' . ((string) $error_number); + $separator = ' — '; + + if ($error_number == 2002) { + $error .= ' - ' . $error_message; + $error .= $separator; + $error .= __( + 'The server is not responding (or the local server\'s socket' + . ' is not correctly configured).' + ); + } elseif ($error_number == 2003) { + $error .= ' - ' . $error_message; + $error .= $separator . __('The server is not responding.'); + } elseif ($error_number == 1698 ) { + $error .= ' - ' . $error_message; + $error .= $separator . ''; + $error .= __('Logout and try as another user.') . ''; + } elseif ($error_number == 1005) { + if (strpos($error_message, 'errno: 13') !== false) { + $error .= ' - ' . $error_message; + $error .= $separator + . __( + 'Please check privileges of directory containing database.' + ); + } else { + /* InnoDB constraints, see + * https://dev.mysql.com/doc/refman/5.0/en/ + * innodb-foreign-key-constraints.html + */ + $error .= ' - ' . $error_message . + ' (' . __('Details…') . ')'; + } + } else { + $error .= ' - ' . $error_message; + } + + return $error; + } + + /** + * gets the current user with host + * + * @return string the current user i.e. user@host + */ + public function getCurrentUser() + { + if (Util::cacheExists('mysql_cur_user')) { + return Util::cacheGet('mysql_cur_user'); + } + $user = $this->fetchValue('SELECT CURRENT_USER();'); + if ($user !== false) { + Util::cacheSet('mysql_cur_user', $user); + return $user; + } + return '@'; + } + + /** + * Checks if current user is superuser + * + * @return bool Whether user is a superuser + */ + public function isSuperuser() + { + return self::isUserType('super'); + } + + /** + * Checks if current user has global create user/grant privilege + * or is a superuser (i.e. SELECT on mysql.users) + * while caching the result in session. + * + * @param string $type type of user to check for + * i.e. 'create', 'grant', 'super' + * + * @return bool Whether user is a given type of user + */ + public function isUserType($type) + { + if (Util::cacheExists('is_' . $type . 'user')) { + return Util::cacheGet('is_' . $type . 'user'); + } + + // when connection failed we don't have a $userlink + if (! isset($this->_links[DatabaseInterface::CONNECT_USER])) { + return false; + } + + // checking if user is logged in + if ($type === 'logged') { + return true; + } + + if (! $GLOBALS['cfg']['Server']['DisableIS'] || $type === 'super') { + // Prepare query for each user type check + $query = ''; + if ($type === 'super') { + $query = 'SELECT 1 FROM mysql.user LIMIT 1'; + } elseif ($type === 'create') { + list($user, $host) = $this->getCurrentUserAndHost(); + $query = "SELECT 1 FROM `INFORMATION_SCHEMA`.`USER_PRIVILEGES` " + . "WHERE `PRIVILEGE_TYPE` = 'CREATE USER' AND " + . "'''" . $user . "''@''" . $host . "''' LIKE `GRANTEE` LIMIT 1"; + } elseif ($type === 'grant') { + list($user, $host) = $this->getCurrentUserAndHost(); + $query = "SELECT 1 FROM (" + . "SELECT `GRANTEE`, `IS_GRANTABLE` FROM " + . "`INFORMATION_SCHEMA`.`COLUMN_PRIVILEGES` UNION " + . "SELECT `GRANTEE`, `IS_GRANTABLE` FROM " + . "`INFORMATION_SCHEMA`.`TABLE_PRIVILEGES` UNION " + . "SELECT `GRANTEE`, `IS_GRANTABLE` FROM " + . "`INFORMATION_SCHEMA`.`SCHEMA_PRIVILEGES` UNION " + . "SELECT `GRANTEE`, `IS_GRANTABLE` FROM " + . "`INFORMATION_SCHEMA`.`USER_PRIVILEGES`) t " + . "WHERE `IS_GRANTABLE` = 'YES' AND " + . "'''" . $user . "''@''" . $host . "''' LIKE `GRANTEE` LIMIT 1"; + } + + $is = false; + $result = $this->tryQuery( + $query, + self::CONNECT_USER, + self::QUERY_STORE + ); + if ($result) { + $is = (bool) $this->numRows($result); + } + $this->freeResult($result); + } else { + $is = false; + $grants = $this->fetchResult( + "SHOW GRANTS FOR CURRENT_USER();", + null, + null, + self::CONNECT_USER, + self::QUERY_STORE + ); + if ($grants) { + foreach ($grants as $grant) { + if ($type === 'create') { + if (strpos($grant, "ALL PRIVILEGES ON *.*") !== false + || strpos($grant, "CREATE USER") !== false + ) { + $is = true; + break; + } + } elseif ($type === 'grant') { + if (strpos($grant, "WITH GRANT OPTION") !== false) { + $is = true; + break; + } + } + } + } + } + + Util::cacheSet('is_' . $type . 'user', $is); + return $is; + } + + /** + * Get the current user and host + * + * @return array array of username and hostname + */ + public function getCurrentUserAndHost() + { + if (count($this->_current_user) == 0) { + $user = $this->getCurrentUser(); + $this->_current_user = explode("@", $user); + } + return $this->_current_user; + } + + /** + * Returns value for lower_case_table_names variable + * + * @return string + */ + public function getLowerCaseNames() + { + if (is_null($this->_lower_case_table_names)) { + $this->_lower_case_table_names = $this->fetchValue( + "SELECT @@lower_case_table_names" + ); + } + return $this->_lower_case_table_names; + } + + /** + * Get the list of system schemas + * + * @return array list of system schemas + */ + public function getSystemSchemas() + { + $schemas = array( + 'information_schema', 'performance_schema', 'mysql', 'sys' + ); + $systemSchemas = array(); + foreach ($schemas as $schema) { + if ($this->isSystemSchema($schema, true)) { + $systemSchemas[] = $schema; + } + } + return $systemSchemas; + } + + /** + * Checks whether given schema is a system schema + * + * @param string $schema_name Name of schema (database) to test + * @param bool $testForMysqlSchema Whether 'mysql' schema should + * be treated the same as IS and DD + * + * @return bool + */ + public function isSystemSchema($schema_name, $testForMysqlSchema = false) + { + $schema_name = strtolower($schema_name); + return $schema_name == 'information_schema' + || $schema_name == 'performance_schema' + || ($schema_name == 'mysql' && $testForMysqlSchema) + || $schema_name == 'sys'; + } + + /** + * Return connection parameters for the database server + * + * @param integer $mode Connection mode on of CONNECT_USER, CONNECT_CONTROL + * or CONNECT_AUXILIARY. + * @param array|null $server Server information like host/port/socket/persistent + * + * @return array user, host and server settings array + */ + public function getConnectionParams($mode, $server = null) + { + global $cfg; + + $user = null; + $password = null; + + if ($mode == DatabaseInterface::CONNECT_USER) { + $user = $cfg['Server']['user']; + $password = $cfg['Server']['password']; + $server = $cfg['Server']; + } elseif ($mode == DatabaseInterface::CONNECT_CONTROL) { + $user = $cfg['Server']['controluser']; + $password = $cfg['Server']['controlpass']; + + $server = array(); + + if (! empty($cfg['Server']['controlhost'])) { + $server['host'] = $cfg['Server']['controlhost']; + } else { + $server['host'] = $cfg['Server']['host']; + } + // Share the settings if the host is same + if ($server['host'] == $cfg['Server']['host']) { + $shared = array( + 'port', 'socket', 'compress', + 'ssl', 'ssl_key', 'ssl_cert', 'ssl_ca', + 'ssl_ca_path', 'ssl_ciphers', 'ssl_verify', + ); + foreach ($shared as $item) { + if (isset($cfg['Server'][$item])) { + $server[$item] = $cfg['Server'][$item]; + } + } + } + // Set configured port + if (! empty($cfg['Server']['controlport'])) { + $server['port'] = $cfg['Server']['controlport']; + } + // Set any configuration with control_ prefix + foreach ($cfg['Server'] as $key => $val) { + if (substr($key, 0, 8) === 'control_') { + $server[substr($key, 8)] = $val; + } + } + } else { + if (is_null($server)) { + return array(null, null, null); + } + if (isset($server['user'])) { + $user = $server['user']; + } + if (isset($server['password'])) { + $password = $server['password']; + } + } + + // Perform sanity checks on some variables + if (empty($server['port'])) { + $server['port'] = 0; + } else { + $server['port'] = intval($server['port']); + } + if (empty($server['socket'])) { + $server['socket'] = null; + } + if (empty($server['host'])) { + $server['host'] = 'localhost'; + } + if (!isset($server['ssl'])) { + $server['ssl'] = false; + } + if (!isset($server['compress'])) { + $server['compress'] = false; + } + + return array($user, $password, $server); + } + + /** + * connects to the database server + * + * @param integer $mode Connection mode on of CONNECT_USER, CONNECT_CONTROL + * or CONNECT_AUXILIARY. + * @param array|null $server Server information like host/port/socket/persistent + * @param integer $target How to store connection link, defaults to $mode + * + * @return mixed false on error or a connection object on success + */ + public function connect($mode, $server = null, $target = null) + { + list($user, $password, $server) = $this->getConnectionParams($mode, $server); + + if (is_null($target)) { + $target = $mode; + } + + if (is_null($user) || is_null($password)) { + trigger_error( + __('Missing connection parameters!'), + E_USER_WARNING + ); + return false; + } + + // Do not show location and backtrace for connection errors + $GLOBALS['error_handler']->setHideLocation(true); + $result = $this->_extension->connect( + $user, $password, $server + ); + $GLOBALS['error_handler']->setHideLocation(false); + + if ($result) { + $this->_links[$target] = $result; + /* Run post connect for user connections */ + if ($target == DatabaseInterface::CONNECT_USER) { + $this->postConnect(); + } elseif ($target == DatabaseInterface::CONNECT_CONTROL) { + $this->postConnectControl(); + } + return $result; + } + + if ($mode == DatabaseInterface::CONNECT_CONTROL) { + trigger_error( + __( + 'Connection for controluser as defined in your ' + . 'configuration failed.' + ), + E_USER_WARNING + ); + return false; + } elseif ($mode == DatabaseInterface::CONNECT_AUXILIARY) { + // Do not go back to main login if connection failed + // (currently used only in unit testing) + return false; + } + + return $result; + } + + /** + * selects given database + * + * @param string $dbname database name to select + * @param integer $link link type + * + * @return boolean + */ + public function selectDb($dbname, $link = DatabaseInterface::CONNECT_USER) + { + if (! isset($this->_links[$link])) { + return false; + } + return $this->_extension->selectDb($dbname, $this->_links[$link]); + } + + /** + * returns array of rows with associative and numeric keys from $result + * + * @param object $result result set identifier + * + * @return array + */ + public function fetchArray($result) + { + return $this->_extension->fetchArray($result); + } + + /** + * returns array of rows with associative keys from $result + * + * @param object $result result set identifier + * + * @return array + */ + public function fetchAssoc($result) + { + return $this->_extension->fetchAssoc($result); + } + + /** + * returns array of rows with numeric keys from $result + * + * @param object $result result set identifier + * + * @return array + */ + public function fetchRow($result) + { + return $this->_extension->fetchRow($result); + } + + /** + * Adjusts the result pointer to an arbitrary row in the result + * + * @param object $result database result + * @param integer $offset offset to seek + * + * @return bool true on success, false on failure + */ + public function dataSeek($result, $offset) + { + return $this->_extension->dataSeek($result, $offset); + } + + /** + * Frees memory associated with the result + * + * @param object $result database result + * + * @return void + */ + public function freeResult($result) + { + $this->_extension->freeResult($result); + } + + /** + * Check if there are any more query results from a multi query + * + * @param integer $link link type + * + * @return bool true or false + */ + public function moreResults($link = DatabaseInterface::CONNECT_USER) + { + if (! isset($this->_links[$link])) { + return false; + } + return $this->_extension->moreResults($this->_links[$link]); + } + + /** + * Prepare next result from multi_query + * + * @param integer $link link type + * + * @return bool true or false + */ + public function nextResult($link = DatabaseInterface::CONNECT_USER) + { + if (! isset($this->_links[$link])) { + return false; + } + return $this->_extension->nextResult($this->_links[$link]); + } + + /** + * Store the result returned from multi query + * + * @param integer $link link type + * + * @return mixed false when empty results / result set when not empty + */ + public function storeResult($link = DatabaseInterface::CONNECT_USER) + { + if (! isset($this->_links[$link])) { + return false; + } + return $this->_extension->storeResult($this->_links[$link]); + } + + /** + * Returns a string representing the type of connection used + * + * @param integer $link link type + * + * @return string type of connection used + */ + public function getHostInfo($link = DatabaseInterface::CONNECT_USER) + { + if (! isset($this->_links[$link])) { + return false; + } + return $this->_extension->getHostInfo($this->_links[$link]); + } + + /** + * Returns the version of the MySQL protocol used + * + * @param integer $link link type + * + * @return integer version of the MySQL protocol used + */ + public function getProtoInfo($link = DatabaseInterface::CONNECT_USER) + { + if (! isset($this->_links[$link])) { + return false; + } + return $this->_extension->getProtoInfo($this->_links[$link]); + } + + /** + * returns a string that represents the client library version + * + * @return string MySQL client library version + */ + public function getClientInfo() + { + return $this->_extension->getClientInfo(); + } + + /** + * returns last error message or false if no errors occurred + * + * @param integer $link link type + * + * @return string|bool $error or false + */ + public function getError($link = DatabaseInterface::CONNECT_USER) + { + if (! isset($this->_links[$link])) { + return false; + } + return $this->_extension->getError($this->_links[$link]); + } + + /** + * returns the number of rows returned by last query + * + * @param object $result result set identifier + * + * @return string|int + */ + public function numRows($result) + { + return $this->_extension->numRows($result); + } + + /** + * returns last inserted auto_increment id for given $link + * or $GLOBALS['userlink'] + * + * @param integer $link link type + * + * @return int|boolean + */ + public function insertId($link = DatabaseInterface::CONNECT_USER) + { + // If the primary key is BIGINT we get an incorrect result + // (sometimes negative, sometimes positive) + // and in the present function we don't know if the PK is BIGINT + // so better play safe and use LAST_INSERT_ID() + // + // When no controluser is defined, using mysqli_insert_id($link) + // does not always return the last insert id due to a mixup with + // the tracking mechanism, but this works: + return $this->fetchValue('SELECT LAST_INSERT_ID();', 0, 0, $link); + } + + /** + * returns the number of rows affected by last query + * + * @param integer $link link type + * @param bool $get_from_cache whether to retrieve from cache + * + * @return int|boolean + */ + public function affectedRows($link = DatabaseInterface::CONNECT_USER, $get_from_cache = true) + { + if (! isset($this->_links[$link])) { + return false; + } + + if ($get_from_cache) { + return $GLOBALS['cached_affected_rows']; + } + + return $this->_extension->affectedRows($this->_links[$link]); + } + + /** + * returns metainfo for fields in $result + * + * @param object $result result set identifier + * + * @return array meta info for fields in $result + */ + public function getFieldsMeta($result) + { + $result = $this->_extension->getFieldsMeta($result); + + if ($this->getLowerCaseNames() === '2') { + /** + * Fixup orgtable for lower_case_table_names = 2 + * + * In this setup MySQL server reports table name lower case + * but we still need to operate on original case to properly + * match existing strings + */ + foreach ($result as $value) { + if (strlen($value->orgtable) !== 0 && + mb_strtolower($value->orgtable) === mb_strtolower($value->table)) { + $value->orgtable = $value->table; + } + } + } + + return $result; + } + + /** + * return number of fields in given $result + * + * @param object $result result set identifier + * + * @return int field count + */ + public function numFields($result) + { + return $this->_extension->numFields($result); + } + + /** + * returns the length of the given field $i in $result + * + * @param object $result result set identifier + * @param int $i field + * + * @return int length of field + */ + public function fieldLen($result, $i) + { + return $this->_extension->fieldLen($result, $i); + } + + /** + * returns name of $i. field in $result + * + * @param object $result result set identifier + * @param int $i field + * + * @return string name of $i. field in $result + */ + public function fieldName($result, $i) + { + return $this->_extension->fieldName($result, $i); + } + + /** + * returns concatenated string of human readable field flags + * + * @param object $result result set identifier + * @param int $i field + * + * @return string field flags + */ + public function fieldFlags($result, $i) + { + return $this->_extension->fieldFlags($result, $i); + } + + /** + * returns properly escaped string for use in MySQL queries + * + * @param string $str string to be escaped + * @param mixed $link optional database link to use + * + * @return string a MySQL escaped string + */ + public function escapeString($str, $link = DatabaseInterface::CONNECT_USER) + { + if ($this->_extension === null || !isset($this->_links[$link])) { + return $str; + } + + return $this->_extension->escapeString($this->_links[$link], $str); + } + + /** + * Checks if this database server is running on Amazon RDS. + * + * @return boolean + */ + public function isAmazonRds() + { + if (Util::cacheExists('is_amazon_rds')) { + return Util::cacheGet('is_amazon_rds'); + } + $sql = 'SELECT @@basedir'; + $result = $this->fetchValue($sql); + $rds = (substr($result, 0, 10) == '/rdsdbbin/'); + Util::cacheSet('is_amazon_rds', $rds); + + return $rds; + } + + /** + * Gets SQL for killing a process. + * + * @param int $process Process ID + * + * @return string + */ + public function getKillQuery($process) + { + if ($this->isAmazonRds()) { + return 'CALL mysql.rds_kill(' . $process . ');'; + } + + return 'KILL ' . $process . ';'; + } + + /** + * Get the phpmyadmin database manager + * + * @return SystemDatabase + */ + public function getSystemDatabase() + { + return new SystemDatabase($this); + } + + /** + * Get a table with database name and table name + * + * @param string $db_name DB name + * @param string $table_name Table name + * + * @return Table + */ + public function getTable($db_name, $table_name) + { + return new Table($table_name, $db_name, $this); + } + + /** + * returns collation of given db + * + * @param string $db name of db + * + * @return string collation of $db + */ + public function getDbCollation($db) + { + if ($this->isSystemSchema($db)) { + // We don't have to check the collation of the virtual + // information_schema database: We know it! + return 'utf8_general_ci'; + } + + if (! $GLOBALS['cfg']['Server']['DisableIS']) { + // this is slow with thousands of databases + $sql = 'SELECT DEFAULT_COLLATION_NAME FROM information_schema.SCHEMATA' + . ' WHERE SCHEMA_NAME = \'' . $this->escapeString($db) + . '\' LIMIT 1'; + return $this->fetchValue($sql); + } + + $this->selectDb($db); + $return = $this->fetchValue('SELECT @@collation_database'); + if ($db !== $GLOBALS['db']) { + $this->selectDb($GLOBALS['db']); + } + return $return; + } + + /** + * returns default server collation from show variables + * + * @return string $server_collation + */ + function getServerCollation() + { + return $this->fetchValue('SELECT @@collation_server'); + } + + /** + * Server version as number + * + * @return integer + */ + public function getVersion() + { + return $this->_version_int; + } + + /** + * Server version + * + * @return string + */ + public function getVersionString() + { + return $this->_version_str; + } + + /** + * Server version comment + * + * @return string + */ + public function getVersionComment() + { + return $this->_version_comment; + } + + /** + * Whether connection is MariaDB + * + * @return boolean + */ + public function isMariaDB() + { + return $this->_is_mariadb; + } + + /** + * Whether connection is Percona + * + * @return boolean + */ + public function isPercona() + { + return $this->_is_percona; + } + + /** + * Load correct database driver + * + * @return void + */ + public static function load() + { + if (defined('TESTSUITE')) { + /** + * For testsuite we use dummy driver which can fake some queries. + */ + $extension = new DbiDummy(); + } else { + + /** + * First check for the mysqli extension, as it's the one recommended + * for the MySQL server's version that we support + * (if PHP 7+, it's the only one supported) + */ + $extension = 'mysqli'; + if (! self::checkDbExtension($extension)) { + + $docurl = Util::getDocuLink('faq', 'faqmysql'); + $doclink = sprintf( + __('See %sour documentation%s for more information.'), + '[a@' . $docurl . '@documentation]', + '[/a]' + ); + + if (PHP_VERSION_ID < 70000) { + $extension = 'mysql'; + if (! self::checkDbExtension($extension)) { + // warn about both extensions missing and exit + Core::warnMissingExtension( + 'mysqli', + true, + $doclink + ); + } elseif (empty($_SESSION['mysqlwarning'])) { + trigger_error( + __( + 'You are using the mysql extension which is deprecated in ' + . 'phpMyAdmin. Please consider installing the mysqli ' + . 'extension.' + ) . ' ' . $doclink, + E_USER_WARNING + ); + // tell the user just once per session + $_SESSION['mysqlwarning'] = true; + } + } else { + // mysql extension is not part of PHP 7+, so warn and exit + Core::warnMissingExtension( + 'mysqli', + true, + $doclink + ); + } + } + + /** + * Including The DBI Plugin + */ + switch($extension) { + case 'mysql' : + $extension = new DbiMysql(); + break; + case 'mysqli' : + $extension = new DbiMysqli(); + break; + } + } + $GLOBALS['dbi'] = new DatabaseInterface($extension); + + $container = Container::getDefaultContainer(); + $container->set('PMA_DatabaseInterface', $GLOBALS['dbi']); + $container->alias('dbi', 'PMA_DatabaseInterface'); + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Dbi/DbiDummy.php b/php/apps/phpmyadmin49/libraries/classes/Dbi/DbiDummy.php new file mode 100644 index 00000000..35cd9a0e --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Dbi/DbiDummy.php @@ -0,0 +1,454 @@ +_queries); $i < $nb; $i++) { + if ($this->_queries[$i]['query'] != $query) { + continue; + } + + $this->_queries[$i]['pos'] = 0; + if (!is_array($this->_queries[$i]['result'])) { + return false; + } + + return $i; + } + for ($i = 0, $nb = count($GLOBALS['dummy_queries']); $i < $nb; $i++) { + if ($GLOBALS['dummy_queries'][$i]['query'] != $query) { + continue; + } + + $GLOBALS['dummy_queries'][$i]['pos'] = 0; + if (!is_array($GLOBALS['dummy_queries'][$i]['result'])) { + return false; + } + + return $i + self::OFFSET_GLOBAL; + } + echo "Not supported query: $query\n"; + + return false; + } + + /** + * Run the multi query and output the results + * + * @param resource $link connection object + * @param string $query multi query statement to execute + * + * @return array|bool + */ + public function realMultiQuery($link, $query) + { + return false; + } + + /** + * returns result data from $result + * + * @param object $result MySQL result + * + * @return array + */ + public function fetchAny($result) + { + $query_data = &$this->getQueryData($result); + if ($query_data['pos'] >= count($query_data['result'])) { + return false; + } + $ret = $query_data['result'][$query_data['pos']]; + $query_data['pos'] += 1; + + return $ret; + } + + /** + * returns array of rows with associative and numeric keys from $result + * + * @param object $result result MySQL result + * + * @return array + */ + public function fetchArray($result) + { + $query_data = &$this->getQueryData($result); + $data = $this->fetchAny($result); + if (!is_array($data) + || !isset($query_data['columns']) + ) { + return $data; + } + + foreach ($data as $key => $val) { + $data[$query_data['columns'][$key]] = $val; + } + + return $data; + } + + /** + * returns array of rows with associative keys from $result + * + * @param object $result MySQL result + * + * @return array + */ + public function fetchAssoc($result) + { + $data = $this->fetchAny($result); + $query_data = &$this->getQueryData($result); + if (!is_array($data) || !isset($query_data['columns'])) { + return $data; + } + + $ret = array(); + foreach ($data as $key => $val) { + $ret[$query_data['columns'][$key]] = $val; + } + + return $ret; + } + + /** + * returns array of rows with numeric keys from $result + * + * @param object $result MySQL result + * + * @return array + */ + public function fetchRow($result) + { + $data = $this->fetchAny($result); + + return $data; + } + + /** + * Adjusts the result pointer to an arbitrary row in the result + * + * @param object $result database result + * @param integer $offset offset to seek + * + * @return bool true on success, false on failure + */ + public function dataSeek($result, $offset) + { + $query_data = &$this->getQueryData($result); + if ($offset > count($query_data['result'])) { + return false; + } + $query_data['pos'] = $offset; + + return true; + } + + /** + * Frees memory associated with the result + * + * @param object $result database result + * + * @return void + */ + public function freeResult($result) + { + return; + } + + /** + * Check if there are any more query results from a multi query + * + * @param resource $link the connection object + * + * @return bool false + */ + public function moreResults($link) + { + return false; + } + + /** + * Prepare next result from multi_query + * + * @param resource $link the connection object + * + * @return boolean false + */ + public function nextResult($link) + { + return false; + } + + /** + * Store the result returned from multi query + * + * @param resource $link the connection object + * + * @return mixed false when empty results / result set when not empty + */ + public function storeResult($link) + { + return false; + } + + /** + * Returns a string representing the type of connection used + * + * @param resource $link mysql link + * + * @return string type of connection used + */ + public function getHostInfo($link) + { + return ''; + } + + /** + * Returns the version of the MySQL protocol used + * + * @param resource $link mysql link + * + * @return integer version of the MySQL protocol used + */ + public function getProtoInfo($link) + { + return -1; + } + + /** + * returns a string that represents the client library version + * + * @return string MySQL client library version + */ + public function getClientInfo() + { + return ''; + } + + /** + * returns last error message or false if no errors occurred + * + * @param resource $link connection link + * + * @return string|bool $error or false + */ + public function getError($link) + { + return false; + } + + /** + * returns the number of rows returned by last query + * + * @param object $result MySQL result + * + * @return string|int + */ + public function numRows($result) + { + if (is_bool($result)) { + return 0; + } + + $query_data = &$this->getQueryData($result); + + return count($query_data['result']); + } + + /** + * returns the number of rows affected by last query + * + * @param resource $link the mysql object + * @param bool $get_from_cache whether to retrieve from cache + * + * @return string|int + */ + public function affectedRows($link = null, $get_from_cache = true) + { + return 0; + } + + /** + * returns metainfo for fields in $result + * + * @param object $result result set identifier + * + * @return array meta info for fields in $result + */ + public function getFieldsMeta($result) + { + return array(); + } + + /** + * return number of fields in given $result + * + * @param object $result MySQL result + * + * @return int field count + */ + public function numFields($result) + { + $query_data = &$this->getQueryData($result); + if (!isset($query_data['columns'])) { + return 0; + } + + return count($query_data['columns']); + } + + /** + * returns the length of the given field $i in $result + * + * @param object $result result set identifier + * @param int $i field + * + * @return int length of field + */ + public function fieldLen($result, $i) + { + return -1; + } + + /** + * returns name of $i. field in $result + * + * @param object $result result set identifier + * @param int $i field + * + * @return string name of $i. field in $result + */ + public function fieldName($result, $i) + { + return ''; + } + + /** + * returns concatenated string of human readable field flags + * + * @param object $result result set identifier + * @param int $i field + * + * @return string field flags + */ + public function fieldFlags($result, $i) + { + return ''; + } + + /** + * returns properly escaped string for use in MySQL queries + * + * @param mixed $link database link + * @param string $str string to be escaped + * + * @return string a MySQL escaped string + */ + public function escapeString($link, $str) + { + return $str; + } + + /** + * Adds query result for testing + * + * @param string $query SQL + * @param array $result Expected result + * + * @return void + */ + public function setResult($query, $result) + { + $this->_queries[] = array( + 'query' => $query, + 'result' => $result, + ); + } + + /** + * Return query data for ID + * + * @param object $result result set identifier + * + * @return array + */ + private function &getQueryData($result) + { + if ($result >= self::OFFSET_GLOBAL) { + return $GLOBALS['dummy_queries'][$result - self::OFFSET_GLOBAL]; + } else { + return $this->_queries[$result]; + } + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Dbi/DbiExtension.php b/php/apps/phpmyadmin49/libraries/classes/Dbi/DbiExtension.php new file mode 100644 index 00000000..b1b9ffae --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Dbi/DbiExtension.php @@ -0,0 +1,242 @@ +_realConnect($server_socket, $user, $password, null); + } else { + $link = $this->_realConnect( + $server['host'] . $server_port . $server_socket, + $user, $password, null + ); + } + return $link; + } + + /** + * selects given database + * + * @param string $dbname name of db to select + * @param resource|null $link mysql link resource + * + * @return bool + */ + public function selectDb($dbname, $link) + { + return mysql_select_db($dbname, $link); + } + + /** + * runs a query and returns the result + * + * @param string $query query to run + * @param resource|null $link mysql link resource + * @param int $options query options + * + * @return mixed + */ + public function realQuery($query, $link, $options) + { + if ($options == ($options | DatabaseInterface::QUERY_STORE)) { + return mysql_query($query, $link); + } elseif ($options == ($options | DatabaseInterface::QUERY_UNBUFFERED)) { + return mysql_unbuffered_query($query, $link); + } + + return mysql_query($query, $link); + } + + /** + * returns array of rows with associative and numeric keys from $result + * + * @param resource $result result MySQL result + * + * @return array + */ + public function fetchArray($result) + { + return mysql_fetch_array($result, MYSQL_BOTH); + } + + /** + * returns array of rows with associative keys from $result + * + * @param resource $result MySQL result + * + * @return array + */ + public function fetchAssoc($result) + { + return mysql_fetch_array($result, MYSQL_ASSOC); + } + + /** + * returns array of rows with numeric keys from $result + * + * @param resource $result MySQL result + * + * @return array + */ + public function fetchRow($result) + { + return mysql_fetch_array($result, MYSQL_NUM); + } + + /** + * Adjusts the result pointer to an arbitrary row in the result + * + * @param resource $result database result + * @param integer $offset offset to seek + * + * @return bool true on success, false on failure + */ + public function dataSeek($result, $offset) + { + return mysql_data_seek($result, $offset); + } + + /** + * Frees memory associated with the result + * + * @param resource $result database result + * + * @return void + */ + public function freeResult($result) + { + if (is_resource($result) && get_resource_type($result) === 'mysql result') { + mysql_free_result($result); + } + } + + /** + * Check if there are any more query results from a multi query + * + * @param resource $link the connection object + * + * @return bool false + */ + public function moreResults($link) + { + // N.B.: PHP's 'mysql' extension does not support + // multi_queries so this function will always + // return false. Use the 'mysqli' extension, if + // you need support for multi_queries. + return false; + } + + /** + * Prepare next result from multi_query + * + * @param resource $link the connection object + * + * @return boolean false + */ + public function nextResult($link) + { + // N.B.: PHP's 'mysql' extension does not support + // multi_queries so this function will always + // return false. Use the 'mysqli' extension, if + // you need support for multi_queries. + return false; + } + + /** + * Returns a string representing the type of connection used + * + * @param resource|null $link mysql link + * + * @return string type of connection used + */ + public function getHostInfo($link) + { + return mysql_get_host_info($link); + } + + /** + * Returns the version of the MySQL protocol used + * + * @param resource|null $link mysql link + * + * @return int version of the MySQL protocol used + */ + public function getProtoInfo($link) + { + return mysql_get_proto_info($link); + } + + /** + * returns a string that represents the client library version + * + * @return string MySQL client library version + */ + public function getClientInfo() + { + return mysql_get_client_info(); + } + + /** + * returns last error message or false if no errors occurred + * + * @param resource|null $link mysql link + * + * @return string|bool $error or false + */ + public function getError($link) + { + $GLOBALS['errno'] = 0; + + if (null !== $link && false !== $link) { + $error_number = mysql_errno($link); + $error_message = mysql_error($link); + } else { + $error_number = mysql_errno(); + $error_message = mysql_error(); + } + if (0 == $error_number) { + return false; + } + + // keep the error number for further check after + // the call to getError() + $GLOBALS['errno'] = $error_number; + + return $GLOBALS['dbi']->formatError($error_number, $error_message); + } + + /** + * returns the number of rows returned by last query + * + * @param resource $result MySQL result + * + * @return string|int + */ + public function numRows($result) + { + if (is_bool($result)) { + return 0; + } + + return mysql_num_rows($result); + } + + /** + * returns the number of rows affected by last query + * + * @param resource|null $link the mysql object + * + * @return int + */ + public function affectedRows($link) + { + return mysql_affected_rows($link); + } + + /** + * returns metainfo for fields in $result + * + * @param resource $result MySQL result + * + * @return array meta info for fields in $result + * + * @todo add missing keys like in mysqli_query (decimals) + */ + public function getFieldsMeta($result) + { + $fields = array(); + $num_fields = mysql_num_fields($result); + for ($i = 0; $i < $num_fields; $i++) { + $field = mysql_fetch_field($result, $i); + $field->flags = mysql_field_flags($result, $i); + $field->orgtable = mysql_field_table($result, $i); + $field->orgname = mysql_field_name($result, $i); + $fields[] = $field; + } + return $fields; + } + + /** + * return number of fields in given $result + * + * @param resource $result MySQL result + * + * @return int field count + */ + public function numFields($result) + { + return mysql_num_fields($result); + } + + /** + * returns the length of the given field $i in $result + * + * @param resource $result MySQL result + * @param int $i field + * + * @return int length of field + */ + public function fieldLen($result, $i) + { + return mysql_field_len($result, $i); + } + + /** + * returns name of $i. field in $result + * + * @param resource $result MySQL result + * @param int $i field + * + * @return string name of $i. field in $result + */ + public function fieldName($result, $i) + { + return mysql_field_name($result, $i); + } + + /** + * returns concatenated string of human readable field flags + * + * @param resource $result MySQL result + * @param int $i field + * + * @return string field flags + */ + public function fieldFlags($result, $i) + { + return mysql_field_flags($result, $i); + } + + /** + * Store the result returned from multi query + * + * @param resource $result MySQL result + * + * @return false + */ + public function storeResult($result) + { + return false; + } + + /** + * returns properly escaped string for use in MySQL queries + * + * @param mixed $link database link + * @param string $str string to be escaped + * + * @return string a MySQL escaped string + */ + public function escapeString($link, $str) + { + return mysql_real_escape_string($str, $link); + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Dbi/DbiMysqli.php b/php/apps/phpmyadmin49/libraries/classes/Dbi/DbiMysqli.php new file mode 100644 index 00000000..24c5bfda --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Dbi/DbiMysqli.php @@ -0,0 +1,596 @@ + 'num', + MYSQLI_PART_KEY_FLAG => 'part_key', + MYSQLI_SET_FLAG => 'set', + MYSQLI_TIMESTAMP_FLAG => 'timestamp', + MYSQLI_AUTO_INCREMENT_FLAG => 'auto_increment', + MYSQLI_ENUM_FLAG => 'enum', + MYSQLI_ZEROFILL_FLAG => 'zerofill', + MYSQLI_UNSIGNED_FLAG => 'unsigned', + MYSQLI_BLOB_FLAG => 'blob', + MYSQLI_MULTIPLE_KEY_FLAG => 'multiple_key', + MYSQLI_UNIQUE_KEY_FLAG => 'unique_key', + MYSQLI_PRI_KEY_FLAG => 'primary_key', + MYSQLI_NOT_NULL_FLAG => 'not_null', + ); + + /** + * connects to the database server + * + * @param string $user mysql user name + * @param string $password mysql user password + * @param array $server host/port/socket/persistent + * + * @return mixed false on error or a mysqli object on success + */ + public function connect( + $user, $password, array $server + ) { + if ($server) { + $server['host'] = (empty($server['host'])) + ? 'localhost' + : $server['host']; + } + + // NULL enables connection to the default socket + + $link = mysqli_init(); + + $client_flags = 0; + + /* Optionally compress connection */ + if ($server['compress'] && defined('MYSQLI_CLIENT_COMPRESS')) { + $client_flags |= MYSQLI_CLIENT_COMPRESS; + } + + /* Optionally enable SSL */ + if ($server['ssl']) { + $client_flags |= MYSQLI_CLIENT_SSL; + if (! empty($server['ssl_key']) || + ! empty($server['ssl_cert']) || + ! empty($server['ssl_ca']) || + ! empty($server['ssl_ca_path']) || + ! empty($server['ssl_ciphers']) + ) { + mysqli_ssl_set( + $link, + $server['ssl_key'], + $server['ssl_cert'], + $server['ssl_ca'], + $server['ssl_ca_path'], + $server['ssl_ciphers'] + ); + } + /* + * disables SSL certificate validation on mysqlnd for MySQL 5.6 or later + * @link https://bugs.php.net/bug.php?id=68344 + * @link https://github.com/phpmyadmin/phpmyadmin/pull/11838 + */ + if (! $server['ssl_verify']) { + mysqli_options( + $link, + MYSQLI_OPT_SSL_VERIFY_SERVER_CERT, + $server['ssl_verify'] + ); + $client_flags |= MYSQLI_CLIENT_SSL_DONT_VERIFY_SERVER_CERT; + } + } + + if ($GLOBALS['cfg']['PersistentConnections']) { + $host = 'p:' . $server['host']; + } else { + $host = $server['host']; + } + + $return_value = mysqli_real_connect( + $link, + $host, + $user, + $password, + '', + $server['port'], + $server['socket'], + $client_flags + ); + + if ($return_value === false || is_null($return_value)) { + /* + * Switch to SSL if server asked us to do so, unfortunately + * there are more ways MySQL server can tell this: + * + * - MySQL 8.0 and newer should return error 3159 + * - #2001 - SSL Connection is required. Please specify SSL options and retry. + * - #9002 - SSL connection is required. Please specify SSL options and retry. + */ + $error_number = mysqli_connect_errno(); + $error_message = mysqli_connect_error(); + if (! $server['ssl'] && ($error_number == 3159 || + (($error_number == 2001 || $error_number == 9002) && stripos($error_message, 'SSL Connection is required') !== false)) + ) { + trigger_error( + __('SSL connection enforced by server, automatically enabling it.'), + E_USER_WARNING + ); + $server['ssl'] = true; + return self::connect($user, $password, $server); + } + return false; + } + + if (defined('PMA_ENABLE_LDI')) { + mysqli_options($link, MYSQLI_OPT_LOCAL_INFILE, true); + } else { + mysqli_options($link, MYSQLI_OPT_LOCAL_INFILE, false); + } + + return $link; + } + + /** + * selects given database + * + * @param string $dbname database name to select + * @param mysqli $link the mysqli object + * + * @return boolean + */ + public function selectDb($dbname, $link) + { + return mysqli_select_db($link, $dbname); + } + + /** + * runs a query and returns the result + * + * @param string $query query to execute + * @param mysqli $link mysqli object + * @param int $options query options + * + * @return mysqli_result|bool + */ + public function realQuery($query, $link, $options) + { + if ($options == ($options | DatabaseInterface::QUERY_STORE)) { + $method = MYSQLI_STORE_RESULT; + } elseif ($options == ($options | DatabaseInterface::QUERY_UNBUFFERED)) { + $method = MYSQLI_USE_RESULT; + } else { + $method = 0; + } + + return mysqli_query($link, $query, $method); + } + + /** + * Run the multi query and output the results + * + * @param mysqli $link mysqli object + * @param string $query multi query statement to execute + * + * @return mysqli_result collection | boolean(false) + */ + public function realMultiQuery($link, $query) + { + return mysqli_multi_query($link, $query); + } + + /** + * returns array of rows with associative and numeric keys from $result + * + * @param mysqli_result $result result set identifier + * + * @return array + */ + public function fetchArray($result) + { + return mysqli_fetch_array($result, MYSQLI_BOTH); + } + + /** + * returns array of rows with associative keys from $result + * + * @param mysqli_result $result result set identifier + * + * @return array + */ + public function fetchAssoc($result) + { + return mysqli_fetch_array($result, MYSQLI_ASSOC); + } + + /** + * returns array of rows with numeric keys from $result + * + * @param mysqli_result $result result set identifier + * + * @return array + */ + public function fetchRow($result) + { + return mysqli_fetch_array($result, MYSQLI_NUM); + } + + /** + * Adjusts the result pointer to an arbitrary row in the result + * + * @param mysqli_result $result database result + * @param integer $offset offset to seek + * + * @return bool true on success, false on failure + */ + public function dataSeek($result, $offset) + { + return mysqli_data_seek($result, $offset); + } + + /** + * Frees memory associated with the result + * + * @param mysqli_result $result database result + * + * @return void + */ + public function freeResult($result) + { + if ($result instanceof mysqli_result) { + mysqli_free_result($result); + } + } + + /** + * Check if there are any more query results from a multi query + * + * @param mysqli $link the mysqli object + * + * @return bool true or false + */ + public function moreResults($link) + { + return mysqli_more_results($link); + } + + /** + * Prepare next result from multi_query + * + * @param mysqli $link the mysqli object + * + * @return bool true or false + */ + public function nextResult($link) + { + return mysqli_next_result($link); + } + + /** + * Store the result returned from multi query + * + * @param mysqli $link the mysqli object + * + * @return mixed false when empty results / result set when not empty + */ + public function storeResult($link) + { + return mysqli_store_result($link); + } + + /** + * Returns a string representing the type of connection used + * + * @param resource $link mysql link + * + * @return string type of connection used + */ + public function getHostInfo($link) + { + return mysqli_get_host_info($link); + } + + /** + * Returns the version of the MySQL protocol used + * + * @param resource $link mysql link + * + * @return integer version of the MySQL protocol used + */ + public function getProtoInfo($link) + { + return mysqli_get_proto_info($link); + } + + /** + * returns a string that represents the client library version + * + * @return string MySQL client library version + */ + public function getClientInfo() + { + return mysqli_get_client_info(); + } + + /** + * returns last error message or false if no errors occurred + * + * @param resource $link mysql link + * + * @return string|bool $error or false + */ + public function getError($link) + { + $GLOBALS['errno'] = 0; + + if (null !== $link && false !== $link) { + $error_number = mysqli_errno($link); + $error_message = mysqli_error($link); + } else { + $error_number = mysqli_connect_errno(); + $error_message = mysqli_connect_error(); + } + if (0 == $error_number) { + return false; + } + + // keep the error number for further check after + // the call to getError() + $GLOBALS['errno'] = $error_number; + + return $GLOBALS['dbi']->formatError($error_number, $error_message); + } + + /** + * returns the number of rows returned by last query + * + * @param mysqli_result $result result set identifier + * + * @return string|int + */ + public function numRows($result) + { + // see the note for tryQuery(); + if (is_bool($result)) { + return 0; + } + + return @mysqli_num_rows($result); + } + + /** + * returns the number of rows affected by last query + * + * @param mysqli $link the mysqli object + * + * @return int + */ + public function affectedRows($link) + { + return mysqli_affected_rows($link); + } + + /** + * returns metainfo for fields in $result + * + * @param mysqli_result $result result set identifier + * + * @return array meta info for fields in $result + */ + public function getFieldsMeta($result) + { + // Build an associative array for a type look up + $typeAr = array(); + $typeAr[MYSQLI_TYPE_DECIMAL] = 'real'; + $typeAr[MYSQLI_TYPE_NEWDECIMAL] = 'real'; + $typeAr[MYSQLI_TYPE_BIT] = 'int'; + $typeAr[MYSQLI_TYPE_TINY] = 'int'; + $typeAr[MYSQLI_TYPE_SHORT] = 'int'; + $typeAr[MYSQLI_TYPE_LONG] = 'int'; + $typeAr[MYSQLI_TYPE_FLOAT] = 'real'; + $typeAr[MYSQLI_TYPE_DOUBLE] = 'real'; + $typeAr[MYSQLI_TYPE_NULL] = 'null'; + $typeAr[MYSQLI_TYPE_TIMESTAMP] = 'timestamp'; + $typeAr[MYSQLI_TYPE_LONGLONG] = 'int'; + $typeAr[MYSQLI_TYPE_INT24] = 'int'; + $typeAr[MYSQLI_TYPE_DATE] = 'date'; + $typeAr[MYSQLI_TYPE_TIME] = 'time'; + $typeAr[MYSQLI_TYPE_DATETIME] = 'datetime'; + $typeAr[MYSQLI_TYPE_YEAR] = 'year'; + $typeAr[MYSQLI_TYPE_NEWDATE] = 'date'; + $typeAr[MYSQLI_TYPE_ENUM] = 'unknown'; + $typeAr[MYSQLI_TYPE_SET] = 'unknown'; + $typeAr[MYSQLI_TYPE_TINY_BLOB] = 'blob'; + $typeAr[MYSQLI_TYPE_MEDIUM_BLOB] = 'blob'; + $typeAr[MYSQLI_TYPE_LONG_BLOB] = 'blob'; + $typeAr[MYSQLI_TYPE_BLOB] = 'blob'; + $typeAr[MYSQLI_TYPE_VAR_STRING] = 'string'; + $typeAr[MYSQLI_TYPE_STRING] = 'string'; + // MySQL returns MYSQLI_TYPE_STRING for CHAR + // and MYSQLI_TYPE_CHAR === MYSQLI_TYPE_TINY + // so this would override TINYINT and mark all TINYINT as string + // see https://github.com/phpmyadmin/phpmyadmin/issues/8569 + //$typeAr[MYSQLI_TYPE_CHAR] = 'string'; + $typeAr[MYSQLI_TYPE_GEOMETRY] = 'geometry'; + $typeAr[MYSQLI_TYPE_BIT] = 'bit'; + $typeAr[MYSQLI_TYPE_JSON] = 'json'; + + $fields = mysqli_fetch_fields($result); + + // this happens sometimes (seen under MySQL 4.0.25) + if (!is_array($fields)) { + return false; + } + + foreach ($fields as $k => $field) { + $fields[$k]->_type = $field->type; + $fields[$k]->type = $typeAr[$field->type]; + $fields[$k]->_flags = $field->flags; + $fields[$k]->flags = $this->fieldFlags($result, $k); + + // Enhance the field objects for mysql-extension compatibility + //$flags = explode(' ', $fields[$k]->flags); + //array_unshift($flags, 'dummy'); + $fields[$k]->multiple_key + = (int) (bool) ($fields[$k]->_flags & MYSQLI_MULTIPLE_KEY_FLAG); + $fields[$k]->primary_key + = (int) (bool) ($fields[$k]->_flags & MYSQLI_PRI_KEY_FLAG); + $fields[$k]->unique_key + = (int) (bool) ($fields[$k]->_flags & MYSQLI_UNIQUE_KEY_FLAG); + $fields[$k]->not_null + = (int) (bool) ($fields[$k]->_flags & MYSQLI_NOT_NULL_FLAG); + $fields[$k]->unsigned + = (int) (bool) ($fields[$k]->_flags & MYSQLI_UNSIGNED_FLAG); + $fields[$k]->zerofill + = (int) (bool) ($fields[$k]->_flags & MYSQLI_ZEROFILL_FLAG); + $fields[$k]->numeric + = (int) (bool) ($fields[$k]->_flags & MYSQLI_NUM_FLAG); + $fields[$k]->blob + = (int) (bool) ($fields[$k]->_flags & MYSQLI_BLOB_FLAG); + } + return $fields; + } + + /** + * return number of fields in given $result + * + * @param mysqli_result $result result set identifier + * + * @return int field count + */ + public function numFields($result) + { + return mysqli_num_fields($result); + } + + /** + * returns the length of the given field $i in $result + * + * @param mysqli_result $result result set identifier + * @param int $i field + * + * @return int length of field + */ + public function fieldLen($result, $i) + { + if ($i >= $this->numFields($result)) { + return false; + } + return mysqli_fetch_field_direct($result, $i)->length; + } + + /** + * returns name of $i. field in $result + * + * @param mysqli_result $result result set identifier + * @param int $i field + * + * @return string name of $i. field in $result + */ + public function fieldName($result, $i) + { + if ($i >= $this->numFields($result)) { + return false; + } + return mysqli_fetch_field_direct($result, $i)->name; + } + + /** + * returns concatenated string of human readable field flags + * + * @param mysqli_result $result result set identifier + * @param int $i field + * + * @return string field flags + */ + public function fieldFlags($result, $i) + { + if ($i >= $this->numFields($result)) { + return false; + } + $f = mysqli_fetch_field_direct($result, $i); + $type = $f->type; + $charsetnr = $f->charsetnr; + $f = $f->flags; + $flags = array(); + foreach (self::$pma_mysqli_flag_names as $flag => $name) { + if ($f & $flag) { + $flags[] = $name; + } + } + // See https://dev.mysql.com/doc/refman/6.0/en/c-api-datatypes.html: + // to determine if a string is binary, we should not use MYSQLI_BINARY_FLAG + // but instead the charsetnr member of the MYSQL_FIELD + // structure. Watch out: some types like DATE returns 63 in charsetnr + // so we have to check also the type. + // Unfortunately there is no equivalent in the mysql extension. + if (($type == MYSQLI_TYPE_TINY_BLOB || $type == MYSQLI_TYPE_BLOB + || $type == MYSQLI_TYPE_MEDIUM_BLOB || $type == MYSQLI_TYPE_LONG_BLOB + || $type == MYSQLI_TYPE_VAR_STRING || $type == MYSQLI_TYPE_STRING) + && 63 == $charsetnr + ) { + $flags[] = 'binary'; + } + return implode(' ', $flags); + } + + /** + * returns properly escaped string for use in MySQL queries + * + * @param mixed $link database link + * @param string $str string to be escaped + * + * @return string a MySQL escaped string + */ + public function escapeString($link, $str) + { + return mysqli_real_escape_string($link, $str); + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Di/AliasItem.php b/php/apps/phpmyadmin49/libraries/classes/Di/AliasItem.php new file mode 100644 index 00000000..3f461dd2 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Di/AliasItem.php @@ -0,0 +1,46 @@ +container = $container; + $this->target = $target; + } + + /** + * Get the target item + * + * @param array $params Parameters + * @return mixed + */ + public function get(array $params = array()) + { + return $this->container->get($this->target, $params); + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Di/Container.php b/php/apps/phpmyadmin49/libraries/classes/Di/Container.php new file mode 100644 index 00000000..06fab997 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Di/Container.php @@ -0,0 +1,189 @@ +content = $base->content; + } else { + $this->alias('container', 'Container'); + } + $this->set('Container', $this); + } + + /** + * Get an object with given name and parameters + * + * @param string $name Name + * @param array $params Parameters + * + * @throws NotFoundException No entry was found for **this** identifier. + * @throws ContainerException Error while retrieving the entry. + * + * @return mixed + */ + public function get($name, array $params = array()) + { + if (!$this->has($name)) { + throw new NotFoundException("No entry was found for $name identifier."); + } + + if (isset($this->content[$name])) { + return $this->content[$name]->get($params); + } elseif (isset($GLOBALS[$name])) { + return $GLOBALS[$name]; + } else { + throw new ContainerException("Error while retrieving the entry."); + } + } + + /** + * Returns true if the container can return an entry for the given identifier. + * Returns false otherwise. + * + * `has($name)` returning true does not mean that `get($name)` will not throw an exception. + * It does however mean that `get($name)` will not throw a `NotFoundException`. + * + * @param string $name Identifier of the entry to look for. + * + * @return bool + */ + public function has($name) + { + return isset($this->content[$name]) || isset($GLOBALS[$name]); + } + + /** + * Remove an object from container + * + * @param string $name Name + * + * @return void + */ + public function remove($name) + { + unset($this->content[$name]); + } + + /** + * Rename an object in container + * + * @param string $name Name + * @param string $newName New name + * + * @return void + */ + public function rename($name, $newName) + { + $this->content[$newName] = $this->content[$name]; + $this->remove($name); + } + + /** + * Set values in the container + * + * @param string|array $name Name + * @param mixed $value Value + * + * @return void + */ + public function set($name, $value = null) + { + if (is_array($name)) { + foreach ($name as $key => $val) { + $this->set($key, $val); + } + return; + } + $this->content[$name] = new ValueItem($value); + } + + /** + * Register a service in the container + * + * @param string $name Name + * @param mixed $service Service + * + * @return void + */ + public function service($name, $service = null) + { + if (!isset($service)) { + $service = $name; + } + $this->content[$name] = new ServiceItem($this, $service); + } + + /** + * Register a factory in the container + * + * @param string $name Name + * @param mixed $factory Factory + * + * @return void + */ + public function factory($name, $factory = null) + { + if (!isset($factory)) { + $factory = $name; + } + $this->content[$name] = new FactoryItem($this, $factory); + } + + /** + * Register an alias in the container + * + * @param string $name Name + * @param string $target Target + * + * @return void + */ + public function alias($name, $target) + { + // The target may be not defined yet + $this->content[$name] = new AliasItem($this, $target); + } + + /** + * Get the global default container + * + * @return Container + */ + public static function getDefaultContainer() + { + if (!isset(static::$defaultContainer)) { + static::$defaultContainer = new Container(); + } + return static::$defaultContainer; + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Di/ContainerException.php b/php/apps/phpmyadmin49/libraries/classes/Di/ContainerException.php new file mode 100644 index 00000000..a5a2da0e --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Di/ContainerException.php @@ -0,0 +1,21 @@ +invoke($params); + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Di/Item.php b/php/apps/phpmyadmin49/libraries/classes/Di/Item.php new file mode 100644 index 00000000..9ddd6a8f --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Di/Item.php @@ -0,0 +1,25 @@ +_container = $container; + $this->_reflector = self::_resolveReflector($definition); + } + + /** + * Invoke the reflector with given parameters + * + * @param array $params Parameters + * @return mixed + */ + protected function invoke(array $params = array()) + { + $args = array(); + $reflector = $this->_reflector; + if ($reflector instanceof \ReflectionClass) { + $constructor = $reflector->getConstructor(); + if (isset($constructor)) { + $args = $this->_resolveArgs( + $constructor->getParameters(), + $params + ); + } + return $reflector->newInstanceArgs($args); + } + /** @var \ReflectionFunctionAbstract $reflector */ + $args = $this->_resolveArgs( + $reflector->getParameters(), + $params + ); + if ($reflector instanceof \ReflectionMethod) { + /** @var \ReflectionMethod $reflector */ + return $reflector->invokeArgs(null, $args); + } + /** @var \ReflectionFunction $reflector */ + return $reflector->invokeArgs($args); + } + + /** + * Getting required arguments with given parameters + * + * @param \ReflectionParameter[] $required Arguments + * @param array $params Parameters + * +*@return array + */ + private function _resolveArgs($required, array $params = array()) + { + $args = array(); + foreach ($required as $param) { + $name = $param->getName(); + $type = $param->getClass(); + if (isset($type)) { + $type = $type->getName(); + } + if (isset($params[$name])) { + $args[] = $params[$name]; + } elseif (is_string($type) && isset($params[$type])) { + $args[] = $params[$type]; + } else { + try { + $content = $this->_container->get($name); + if (isset($content)) { + $args[] = $content; + } elseif (is_string($type)) { + $args[] = $this->_container->get($type); + } else { + $args[] = null; + } + } catch (NotFoundException $e) { + $args[] = null; + } + } + } + return $args; + } + + /** + * Resolve the reflection + * + * @param mixed $definition Definition + * + * @return \Reflector + */ + private static function _resolveReflector($definition) + { + if (function_exists($definition)) { + return new \ReflectionFunction($definition); + } + if (is_string($definition)) { + $definition = explode('::', $definition); + } + if (!isset($definition[1])) { + return new \ReflectionClass($definition[0]); + } + return new \ReflectionMethod($definition[0], $definition[1]); + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Di/ServiceItem.php b/php/apps/phpmyadmin49/libraries/classes/Di/ServiceItem.php new file mode 100644 index 00000000..25a4576f --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Di/ServiceItem.php @@ -0,0 +1,34 @@ +instance)) { + $this->instance = $this->invoke(); + } + return $this->instance; + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Di/ValueItem.php b/php/apps/phpmyadmin49/libraries/classes/Di/ValueItem.php new file mode 100644 index 00000000..2997a374 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Di/ValueItem.php @@ -0,0 +1,41 @@ +value = $value; + } + + /** + * Get the value + * + * @param array $params Parameters + * @return mixed + */ + public function get(array $params = array()) + { + return $this->value; + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Display/ChangePassword.php b/php/apps/phpmyadmin49/libraries/classes/Display/ChangePassword.php new file mode 100644 index 00000000..dec25234 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Display/ChangePassword.php @@ -0,0 +1,165 @@ +'; + + $html .= Url::getHiddenInputs(); + + if (strpos($GLOBALS['PMA_PHP_SELF'], 'server_privileges') !== false) { + $html .= '' + . ''; + } + $html .= '
        ' + . '' . __('Change password') . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . ''; + + $serverType = Util::getServerType(); + $serverVersion = $GLOBALS['dbi']->getVersion(); + $orig_auth_plugin = Privileges::getCurrentAuthenticationPlugin( + 'change', + $username, + $hostname + ); + + if (($serverType == 'MySQL' + && $serverVersion >= 50507) + || ($serverType == 'MariaDB' + && $serverVersion >= 50200) + ) { + // Provide this option only for 5.7.6+ + // OR for privileged users in 5.5.7+ + if (($serverType == 'MySQL' + && $serverVersion >= 50706) + || ($GLOBALS['dbi']->isSuperuser() && $mode == 'edit_other') + ) { + $auth_plugin_dropdown = Privileges::getHtmlForAuthPluginsDropdown( + $orig_auth_plugin, 'change_pw', 'new' + ); + + $html .= '' + . '' + . '' + . '
        ' + . '' + . '' + . '
        ' + . '' + . '' + . '' + . __('Enter:') . '     ' + . '' + . 'Strength: ' + . ' ' + . 'Good' + . '
        ' . __('Re-type:') . ' ' + . '' + . '
        ' . __('Password Hashing:') . ''; + $html .= $auth_plugin_dropdown; + $html .= '
        '; + + $html .= '' + . Message::notice( + __( + 'This method requires using an \'SSL connection\' ' + . 'or an \'unencrypted connection that encrypts the ' + . 'password using RSA\'; while connecting to the server.' + ) + . Util::showMySQLDocu( + 'sha256-authentication-plugin' + ) + ) + ->getDisplay() + . '
        '; + } else { + $html .= '' + . ''; + } + } else { + $auth_plugin_dropdown = Privileges::getHtmlForAuthPluginsDropdown( + $orig_auth_plugin, 'change_pw', 'old' + ); + + $html .= '' + . '' . __('Password Hashing:') . ''; + $html .= $auth_plugin_dropdown . '' + . '' + . ''; + } + + $html .= '
      • ' + . '' + . ''; + return $html; + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Display/CreateTable.php b/php/apps/phpmyadmin49/libraries/classes/Display/CreateTable.php new file mode 100644 index 00000000..5114d629 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Display/CreateTable.php @@ -0,0 +1,53 @@ += 4.1.0, we should be able to detect if user has a CREATE + * privilege by looking at SHOW GRANTS output; + * for < 4.1.0, it could be more difficult because the logic tries to + * detect the current host and it might be expressed in many ways; also + * on a shared server, the user might be unable to define a controluser + * that has the proper rights to the "mysql" db; + * so we give up and assume that user has the right to create a table + * + * Note: in this case we could even skip the following "foreach" logic + * + * Addendum, 2006-01-19: ok, I give up. We got some reports about servers + * where the hostname field in mysql.user is not the same as the one + * in mysql.db for a user. In this case, SHOW GRANTS does not return + * the db-specific privileges. And probably, those users are on a shared + * server, so can't set up a control user with rights to the "mysql" db. + * We cannot reliably detect the db-specific privileges, so no more + * warnings about the lack of privileges for CREATE TABLE. Tested + * on MySQL 5.0.18. + * + * @package PhpMyAdmin + */ +namespace PhpMyAdmin\Display; + +use PhpMyAdmin\Template; + +require_once './libraries/check_user_privileges.inc.php'; + +/** + * PhpMyAdmin\Display\CreateTable class + * + * @package PhpMyAdmin + */ +class CreateTable +{ + /** + * Returns the html for create table. + * + * @param string $db database name + * + * @return string + */ + public static function getHtml($db) + { + return Template::get('database/create_table')->render( + array('db' => $db) + ); + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Display/Export.php b/php/apps/phpmyadmin49/libraries/classes/Display/Export.php new file mode 100644 index 00000000..53b6109f --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Display/Export.php @@ -0,0 +1,806 @@ +relation = new Relation(); + } + + /** + * Outputs appropriate checked statement for checkbox. + * + * @param string $str option name + * + * @return boolean + */ + private function checkboxCheck($str) + { + return isset($GLOBALS['cfg']['Export'][$str]) + && $GLOBALS['cfg']['Export'][$str]; + } + + /** + * Prints Html For Export Selection Options + * + * @param string $tmpSelect Tmp selected method of export + * + * @return string + */ + public function getHtmlForSelectOptions($tmpSelect = '') + { + // Check if the selected databases are defined in $_POST + // (from clicking Back button on export.php) + if (isset($_POST['db_select'])) { + $_POST['db_select'] = urldecode($_POST['db_select']); + $_POST['db_select'] = explode(",", $_POST['db_select']); + } + + $databases = []; + foreach ($GLOBALS['dblist']->databases as $currentDb) { + if ($GLOBALS['dbi']->isSystemSchema($currentDb, true)) { + continue; + } + $isSelected = false; + if (isset($_POST['db_select'])) { + if (in_array($currentDb, $_POST['db_select'])) { + $isSelected = true; + } + } elseif (!empty($tmpSelect)) { + if (mb_strpos( + ' ' . $tmpSelect, + '|' . $currentDb . '|' + )) { + $isSelected = true; + } + } else { + $isSelected = true; + } + $databases[] = [ + 'name' => $currentDb, + 'is_selected' => $isSelected, + ]; + } + + return Template::get('display/export/select_options')->render([ + 'databases' => $databases, + ]); + } + + /** + * Prints Html For Export Hidden Input + * + * @param string $exportType Selected Export Type + * @param string $db Selected DB + * @param string $table Selected Table + * @param string $singleTable Single Table + * @param string $sqlQuery SQL Query + * + * @return string + */ + public function getHtmlForHiddenInputs( + $exportType, + $db, + $table, + $singleTable, + $sqlQuery + ) { + global $cfg; + + // If the export method was not set, the default is quick + if (isset($_POST['export_method'])) { + $cfg['Export']['method'] = $_POST['export_method']; + } elseif (! isset($cfg['Export']['method'])) { + $cfg['Export']['method'] = 'quick'; + } + + if (empty($sqlQuery) && isset($_POST['sql_query'])) { + $sqlQuery = $_POST['sql_query']; + } + + return Template::get('display/export/hidden_inputs')->render([ + 'db' => $db, + 'table' => $table, + 'export_type' => $exportType, + 'export_method' => $cfg['Export']['method'], + 'single_table' => $singleTable, + 'sql_query' => $sqlQuery, + 'template_id' => isset($_POST['template_id']) ? $_POST['template_id'] : '', + ]); + } + + /** + * Returns HTML for the options in template dropdown + * + * @param string $exportType export type - server, database, or table + * + * @return string HTML for the options in teplate dropdown + */ + private function getOptionsForTemplates($exportType) + { + // Get the relation settings + $cfgRelation = $this->relation->getRelationsParam(); + + $query = "SELECT `id`, `template_name` FROM " + . Util::backquote($cfgRelation['db']) . '.' + . Util::backquote($cfgRelation['export_templates']) + . " WHERE `username` = " + . "'" . $GLOBALS['dbi']->escapeString($GLOBALS['cfg']['Server']['user']) + . "' AND `export_type` = '" . $GLOBALS['dbi']->escapeString($exportType) . "'" + . " ORDER BY `template_name`;"; + + $result = $this->relation->queryAsControlUser($query); + + $templates = []; + if ($result !== false) { + while ($row = $GLOBALS['dbi']->fetchAssoc($result, DatabaseInterface::CONNECT_CONTROL)) { + $templates[] = [ + 'name' => $row['template_name'], + 'id' => $row['id'], + ]; + } + } + + return Template::get('display/export/template_options')->render([ + 'templates' => $templates, + 'selected_template' => !empty($_POST['template_id']) ? $_POST['template_id'] : null, + ]); + } + + /** + * Prints Html For Export Options Method + * + * @return string + */ + private function getHtmlForOptionsMethod() + { + global $cfg; + if (isset($_POST['quick_or_custom'])) { + $exportMethod = $_POST['quick_or_custom']; + } else { + $exportMethod = $cfg['Export']['method']; + } + + return Template::get('display/export/method')->render([ + 'export_method' => $exportMethod, + ]); + } + + /** + * Prints Html For Export Options Selection + * + * @param string $exportType Selected Export Type + * @param string $multiValues Export Options + * + * @return string + */ + private function getHtmlForOptionsSelection($exportType, $multiValues) + { + return Template::get('display/export/selection')->render([ + 'export_type' => $exportType, + 'multi_values' => $multiValues, + ]); + } + + /** + * Prints Html For Export Options Format dropdown + * + * @param ExportPlugin[] $exportList Export List + * + * @return string + */ + private function getHtmlForOptionsFormatDropdown($exportList) + { + $dropdown = Plugins::getChoice('Export', 'what', $exportList, 'format'); + return Template::get('display/export/format_dropdown')->render([ + 'dropdown' => $dropdown, + ]); + } + + /** + * Prints Html For Export Options Format-specific options + * + * @param ExportPlugin[] $exportList Export List + * + * @return string + */ + private function getHtmlForOptionsFormat($exportList) + { + global $cfg; + $options = Plugins::getOptions('Export', $exportList); + + return Template::get('display/export/options_format')->render([ + 'options' => $options, + 'can_convert_kanji' => Encoding::canConvertKanji(), + 'exec_time_limit' => $cfg['ExecTimeLimit'], + ]); + } + + /** + * Prints Html For Export Options Rows + * + * @param string $db Selected DB + * @param string $table Selected Table + * @param string $unlimNumRows Num of Rows + * + * @return string + */ + private function getHtmlForOptionsRows($db, $table, $unlimNumRows) + { + $tableObject = new Table($table, $db); + $numberOfRows = $tableObject->countRecords(); + + return Template::get('display/export/options_rows')->render([ + 'allrows' => isset($_POST['allrows']) ? $_POST['allrows'] : null, + 'limit_to' => isset($_POST['limit_to']) ? $_POST['limit_to'] : null, + 'limit_from' => isset($_POST['limit_from']) ? $_POST['limit_from'] : null, + 'unlim_num_rows' => $unlimNumRows, + 'number_of_rows' => $numberOfRows, + ]); + } + + /** + * Prints Html For Export Options Quick Export + * + * @return string + */ + private function getHtmlForOptionsQuickExport() + { + global $cfg; + $saveDir = Util::userDir($cfg['SaveDir']); + $exportIsChecked = $this->checkboxCheck( + 'quick_export_onserver' + ); + $exportOverwriteIsChecked = $this->checkboxCheck( + 'quick_export_onserver_overwrite' + ); + + return Template::get('display/export/options_quick_export')->render([ + 'save_dir' => $saveDir, + 'export_is_checked' => $exportIsChecked, + 'export_overwrite_is_checked' => $exportOverwriteIsChecked, + ]); + } + + /** + * Prints Html For Export Options Save Dir + * + * @return string + */ + private function getHtmlForOptionsOutputSaveDir() + { + global $cfg; + $saveDir = Util::userDir($cfg['SaveDir']); + $exportIsChecked = $this->checkboxCheck( + 'onserver' + ); + $exportOverwriteIsChecked = $this->checkboxCheck( + 'onserver_overwrite' + ); + + return Template::get('display/export/options_output_save_dir')->render([ + 'save_dir' => $saveDir, + 'export_is_checked' => $exportIsChecked, + 'export_overwrite_is_checked' => $exportOverwriteIsChecked, + ]); + } + + + /** + * Prints Html For Export Options + * + * @param string $exportType Selected Export Type + * + * @return string + */ + private function getHtmlForOptionsOutputFormat($exportType) + { + $trans = new Message; + $trans->addText(__('@SERVER@ will become the server name')); + if ($exportType == 'database' || $exportType == 'table') { + $trans->addText(__(', @DATABASE@ will become the database name')); + if ($exportType == 'table') { + $trans->addText(__(', @TABLE@ will become the table name')); + } + } + + $msg = new Message( + __( + 'This value is interpreted using %1$sstrftime%2$s, ' + . 'so you can use time formatting strings. ' + . 'Additionally the following transformations will happen: %3$s. ' + . 'Other text will be kept as is. See the %4$sFAQ%5$s for details.' + ) + ); + $msg->addParamHtml( + '' + ); + $msg->addParamHtml(''); + $msg->addParam($trans); + $docUrl = Util::getDocuLink('faq', 'faq6-27'); + $msg->addParamHtml( + '' + ); + $msg->addParamHtml(''); + + if (isset($_POST['filename_template'])) { + $filenameTemplate = $_POST['filename_template']; + } else { + if ($exportType == 'database') { + $filenameTemplate = $GLOBALS['PMA_Config']->getUserValue( + 'pma_db_filename_template', + $GLOBALS['cfg']['Export']['file_template_database'] + ); + } elseif ($exportType == 'table') { + $filenameTemplate = $GLOBALS['PMA_Config']->getUserValue( + 'pma_table_filename_template', + $GLOBALS['cfg']['Export']['file_template_table'] + ); + } else { + $filenameTemplate = $GLOBALS['PMA_Config']->getUserValue( + 'pma_server_filename_template', + $GLOBALS['cfg']['Export']['file_template_server'] + ); + } + } + + return Template::get('display/export/options_output_format')->render([ + 'message' => $msg->getMessage(), + 'filename_template' => $filenameTemplate, + 'is_checked' => $this->checkboxCheck('remember_file_template'), + ]); + } + + /** + * Prints Html For Export Options Charset + * + * @return string + */ + private function getHtmlForOptionsOutputCharset() + { + global $cfg; + + return Template::get('display/export/options_output_charset')->render([ + 'encodings' => Encoding::listEncodings(), + 'export_charset' => $cfg['Export']['charset'], + ]); + } + + /** + * Prints Html For Export Options Compression + * + * @return string + */ + private function getHtmlForOptionsOutputCompression() + { + global $cfg; + if (isset($_POST['compression'])) { + $selectedCompression = $_POST['compression']; + } elseif (isset($cfg['Export']['compression'])) { + $selectedCompression = $cfg['Export']['compression']; + } else { + $selectedCompression = 'none'; + } + + // Since separate files export works with ZIP only + if (isset($cfg['Export']['as_separate_files']) + && $cfg['Export']['as_separate_files'] + ) { + $selectedCompression = 'zip'; + } + + // zip and gzip encode features + $isZip = ($cfg['ZipDump'] && function_exists('gzcompress')); + $isGzip = ($cfg['GZipDump'] && function_exists('gzencode')); + + return Template::get('display/export/options_output_compression')->render([ + 'is_zip' => $isZip, + 'is_gzip' => $isGzip, + 'selected_compression' => $selectedCompression, + ]); + } + + /** + * Prints Html For Export Options Radio + * + * @return string + */ + private function getHtmlForOptionsOutputRadio() + { + return Template::get('display/export/options_output_radio')->render([ + 'has_repopulate' => isset($_POST['repopulate']), + 'export_asfile' => $GLOBALS['cfg']['Export']['asfile'], + ]); + } + + /** + * Prints Html For Export Options Checkbox - Separate files + * + * @param string $exportType Selected Export Type + * + * @return string + */ + private function getHtmlForOptionsOutputSeparateFiles($exportType) + { + $isChecked = $this->checkboxCheck('as_separate_files'); + + return Template::get('display/export/options_output_separate_files')->render([ + 'is_checked' => $isChecked, + 'export_type' => $exportType, + ]); + } + + /** + * Prints Html For Export Options + * + * @param string $exportType Selected Export Type + * + * @return string + */ + private function getHtmlForOptionsOutput($exportType) + { + global $cfg; + + $hasAliases = isset($_SESSION['tmpval']['aliases']) + && !Core::emptyRecursive($_SESSION['tmpval']['aliases']); + unset($_SESSION['tmpval']['aliases']); + + $isCheckedLockTables = $this->checkboxCheck('lock_tables'); + $isCheckedAsfile = $this->checkboxCheck('asfile'); + + $optionsOutputSaveDir = ''; + if (isset($cfg['SaveDir']) && !empty($cfg['SaveDir'])) { + $optionsOutputSaveDir = $this->getHtmlForOptionsOutputSaveDir(); + } + $optionsOutputFormat = $this->getHtmlForOptionsOutputFormat($exportType); + $optionsOutputCharset = ''; + if (Encoding::isSupported()) { + $optionsOutputCharset = $this->getHtmlForOptionsOutputCharset(); + } + $optionsOutputCompression = $this->getHtmlForOptionsOutputCompression(); + $optionsOutputSeparateFiles = ''; + if ($exportType == 'server' || $exportType == 'database') { + $optionsOutputSeparateFiles = $this->getHtmlForOptionsOutputSeparateFiles( + $exportType + ); + } + $optionsOutputRadio = $this->getHtmlForOptionsOutputRadio(); + + return Template::get('display/export/options_output')->render([ + 'has_aliases' => $hasAliases, + 'export_type' => $exportType, + 'is_checked_lock_tables' => $isCheckedLockTables, + 'is_checked_asfile' => $isCheckedAsfile, + 'repopulate' => isset($_POST['repopulate']), + 'lock_tables' => isset($_POST['lock_tables']), + 'save_dir' => isset($cfg['SaveDir']) ? $cfg['SaveDir'] : null, + 'is_encoding_supported' => Encoding::isSupported(), + 'options_output_save_dir' => $optionsOutputSaveDir, + 'options_output_format' => $optionsOutputFormat, + 'options_output_charset' => $optionsOutputCharset, + 'options_output_compression' => $optionsOutputCompression, + 'options_output_separate_files' => $optionsOutputSeparateFiles, + 'options_output_radio' => $optionsOutputRadio, + ]); + } + + /** + * Prints Html For Export Options + * + * @param string $exportType Selected Export Type + * @param string $db Selected DB + * @param string $table Selected Table + * @param string $multiValues Export selection + * @param string $numTables number of tables + * @param ExportPlugin[] $exportList Export List + * @param string $unlimNumRows Number of Rows + * + * @return string + */ + public function getHtmlForOptions( + $exportType, + $db, + $table, + $multiValues, + $numTables, + $exportList, + $unlimNumRows + ) { + global $cfg; + $html = $this->getHtmlForOptionsMethod(); + $html .= $this->getHtmlForOptionsFormatDropdown($exportList); + $html .= $this->getHtmlForOptionsSelection($exportType, $multiValues); + + $tableObject = new Table($table, $db); + if (strlen($table) > 0 && empty($numTables) && ! $tableObject->isMerge()) { + $html .= $this->getHtmlForOptionsRows($db, $table, $unlimNumRows); + } + + if (isset($cfg['SaveDir']) && !empty($cfg['SaveDir'])) { + $html .= $this->getHtmlForOptionsQuickExport(); + } + + $html .= $this->getHtmlForAliasModalDialog(); + $html .= $this->getHtmlForOptionsOutput($exportType); + $html .= $this->getHtmlForOptionsFormat($exportList); + return $html; + } + + /** + * Generate Html For currently defined aliases + * + * @return string + */ + private function getHtmlForCurrentAlias() + { + $result = ''; + + $template = Template::get('export/alias_item'); + if (isset($_SESSION['tmpval']['aliases'])) { + foreach ($_SESSION['tmpval']['aliases'] as $db => $dbData) { + if (isset($dbData['alias'])) { + $result .= $template->render(array( + 'type' => _pgettext('Alias', 'Database'), + 'name' => $db, + 'field' => 'aliases[' . $db . '][alias]', + 'value' => $dbData['alias'], + )); + } + if (! isset($dbData['tables'])) { + continue; + } + foreach ($dbData['tables'] as $table => $tableData) { + if (isset($tableData['alias'])) { + $result .= $template->render(array( + 'type' => _pgettext('Alias', 'Table'), + 'name' => $db . '.' . $table, + 'field' => 'aliases[' . $db . '][tables][' . $table . '][alias]', + 'value' => $tableData['alias'], + )); + } + if (! isset($tableData['columns'])) { + continue; + } + foreach ($tableData['columns'] as $column => $columnName) { + $result .= $template->render(array( + 'type' => _pgettext('Alias', 'Column'), + 'name' => $db . '.' . $table . '.'. $column, + 'field' => 'aliases[' . $db . '][tables][' . $table . '][colums][' . $column . ']', + 'value' => $columnName, + )); + } + } + } + } + + // Empty row for javascript manipulations + $result .= '' . $template->render(array( + 'type' => '', 'name' => '', 'field' => 'aliases_new', 'value' => '' + )) . ''; + + return $result . '
        ' + . __('Defined aliases') + . '
        '; + } + + /** + * Generate Html For Alias Modal Dialog + * + * @return string + */ + public function getHtmlForAliasModalDialog() + { + $title = __('Rename exported databases/tables/columns'); + + $html = '
        '; + $html .= $this->getHtmlForCurrentAlias(); + $html .= Template::get('export/alias_add')->render(); + + $html .= '
        '; + return $html; + } + + /** + * Gets HTML to display export dialogs + * + * @param string $exportType export type: server|database|table + * @param string $db selected DB + * @param string $table selected table + * @param string $sqlQuery SQL query + * @param int $numTables number of tables + * @param int $unlimNumRows unlimited number of rows + * @param string $multiValues selector options + * + * @return string $html + */ + public function getDisplay( + $exportType, + $db, + $table, + $sqlQuery, + $numTables, + $unlimNumRows, + $multiValues + ) { + $cfgRelation = $this->relation->getRelationsParam(); + + if (isset($_POST['single_table'])) { + $GLOBALS['single_table'] = $_POST['single_table']; + } + + // Export a single table + if (isset($_GET['single_table'])) { + $GLOBALS['single_table'] = $_GET['single_table']; + } + + /* Scan for plugins */ + /* @var $exportList ExportPlugin[] */ + $exportList = Plugins::getPlugins( + "export", + 'libraries/classes/Plugins/Export/', + array( + 'export_type' => $exportType, + 'single_table' => isset($GLOBALS['single_table']) + ) + ); + + /* Fail if we didn't find any plugin */ + if (empty($exportList)) { + Message::error( + __('Could not load export plugins, please check your installation!') + )->display(); + exit; + } + + $html = Template::get('display/export/option_header')->render([ + 'export_type' => $exportType, + 'db' => $db, + 'table' => $table, + ]); + + if ($cfgRelation['exporttemplateswork']) { + $html .= Template::get('display/export/template_loading')->render([ + 'options' => $this->getOptionsForTemplates($exportType), + ]); + } + + $html .= '
        '; + + //output Hidden Inputs + $singleTableStr = isset($GLOBALS['single_table']) ? $GLOBALS['single_table'] + : ''; + $html .= $this->getHtmlForHiddenInputs( + $exportType, + $db, + $table, + $singleTableStr, + $sqlQuery + ); + + //output Export Options + $html .= $this->getHtmlForOptions( + $exportType, + $db, + $table, + $multiValues, + $numTables, + $exportList, + $unlimNumRows + ); + + $html .= '
        '; + return $html; + } + + /** + * Handles export template actions + * + * @param array $cfgRelation Relation configuration + * + * @return void + */ + public function handleTemplateActions(array $cfgRelation) + { + if (isset($_POST['templateId'])) { + $id = $GLOBALS['dbi']->escapeString($_POST['templateId']); + } else { + $id = ''; + } + + $templateTable = Util::backquote($cfgRelation['db']) . '.' + . Util::backquote($cfgRelation['export_templates']); + $user = $GLOBALS['dbi']->escapeString($GLOBALS['cfg']['Server']['user']); + + switch ($_POST['templateAction']) { + case 'create': + $query = "INSERT INTO " . $templateTable . "(" + . " `username`, `export_type`," + . " `template_name`, `template_data`" + . ") VALUES (" + . "'" . $user . "', " + . "'" . $GLOBALS['dbi']->escapeString($_POST['exportType']) + . "', '" . $GLOBALS['dbi']->escapeString($_POST['templateName']) + . "', '" . $GLOBALS['dbi']->escapeString($_POST['templateData']) + . "');"; + break; + case 'load': + $query = "SELECT `template_data` FROM " . $templateTable + . " WHERE `id` = " . $id . " AND `username` = '" . $user . "'"; + break; + case 'update': + $query = "UPDATE " . $templateTable . " SET `template_data` = " + . "'" . $GLOBALS['dbi']->escapeString($_POST['templateData']) . "'" + . " WHERE `id` = " . $id . " AND `username` = '" . $user . "'"; + break; + case 'delete': + $query = "DELETE FROM " . $templateTable + . " WHERE `id` = " . $id . " AND `username` = '" . $user . "'"; + break; + default: + $query = ''; + break; + } + + $result = $this->relation->queryAsControlUser($query, false); + + $response = Response::getInstance(); + if (! $result) { + $error = $GLOBALS['dbi']->getError(DatabaseInterface::CONNECT_CONTROL); + $response->setRequestStatus(false); + $response->addJSON('message', $error); + exit; + } + + $response->setRequestStatus(true); + if ('create' == $_POST['templateAction']) { + $response->addJSON( + 'data', + $this->getOptionsForTemplates($_POST['exportType']) + ); + } elseif ('load' == $_POST['templateAction']) { + $data = null; + while ($row = $GLOBALS['dbi']->fetchAssoc( + $result, DatabaseInterface::CONNECT_CONTROL + )) { + $data = $row['template_data']; + } + $response->addJSON('data', $data); + } + $GLOBALS['dbi']->freeResult($result); + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Display/GitRevision.php b/php/apps/phpmyadmin49/libraries/classes/Display/GitRevision.php new file mode 100644 index 00000000..8b350e8c --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Display/GitRevision.php @@ -0,0 +1,98 @@ +checkGitRevision(); + + if (! $GLOBALS['PMA_Config']->get('PMA_VERSION_GIT')) { + $response = Response::getInstance(); + $response->setRequestStatus(false); + return; + } + + // if using a remote commit fast-forwarded, link to GitHub + $commit_hash = substr( + $GLOBALS['PMA_Config']->get('PMA_VERSION_GIT_COMMITHASH'), + 0, + 7 + ); + $commit_hash = '' . $commit_hash . ''; + if ($GLOBALS['PMA_Config']->get('PMA_VERSION_GIT_ISREMOTECOMMIT')) { + $commit_hash = '' . $commit_hash . ''; + } + + $branch = $GLOBALS['PMA_Config']->get('PMA_VERSION_GIT_BRANCH'); + if ($GLOBALS['PMA_Config']->get('PMA_VERSION_GIT_ISREMOTEBRANCH')) { + $branch = '' . $branch . ''; + } + if ($branch !== false) { + $branch = sprintf(__('%1$s from %2$s branch'), $commit_hash, $branch); + } else { + $branch = $commit_hash . ' (' . __('no branch') . ')'; + } + + $committer = $GLOBALS['PMA_Config']->get('PMA_VERSION_GIT_COMMITTER'); + $author = $GLOBALS['PMA_Config']->get('PMA_VERSION_GIT_AUTHOR'); + Core::printListItem( + __('Git revision:') . ' ' + . $branch . ',
        ' + . sprintf( + __('committed on %1$s by %2$s'), + Util::localisedDate(strtotime($committer['date'])), + '' + . htmlspecialchars($committer['name']) . '' + ) + . ($author != $committer + ? ',
        ' + . sprintf( + __('authored on %1$s by %2$s'), + Util::localisedDate(strtotime($author['date'])), + '' + . htmlspecialchars($author['name']) . '' + ) + : ''), + 'li_pma_version_git', null, null, null + ); + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Display/Import.php b/php/apps/phpmyadmin49/libraries/classes/Display/Import.php new file mode 100644 index 00000000..f3cbb6b6 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Display/Import.php @@ -0,0 +1,111 @@ +display(); + exit; + } + + if (Core::isValid($_REQUEST['offset'], 'numeric')) { + $offset = intval($_REQUEST['offset']); + } + if (isset($_REQUEST['timeout_passed'])) { + $timeoutPassed = $_REQUEST['timeout_passed']; + } + + $localImportFile = ''; + if (isset($_REQUEST['local_import_file'])) { + $localImportFile = $_REQUEST['local_import_file']; + } + + // zip, gzip and bzip2 encode features + $compressions = array(); + if ($cfg['GZipDump'] && function_exists('gzopen')) { + $compressions[] = 'gzip'; + } + if ($cfg['BZipDump'] && function_exists('bzopen')) { + $compressions[] = 'bzip2'; + } + if ($cfg['ZipDump'] && function_exists('zip_open')) { + $compressions[] = 'zip'; + } + + return Template::get('display/import/import')->render([ + 'upload_id' => $uploadId, + 'handler' => $_SESSION[$SESSION_KEY]["handler"], + 'id_key' => $_SESSION[$SESSION_KEY]['handler']::getIdKey(), + 'pma_theme_image' => $GLOBALS['pmaThemeImage'], + 'import_type' => $importType, + 'db' => $db, + 'table' => $table, + 'max_upload_size' => $maxUploadSize, + 'import_list' => $importList, + 'local_import_file' => $localImportFile, + 'is_upload' => $GLOBALS['is_upload'], + 'upload_dir' => isset($cfg['UploadDir']) ? $cfg['UploadDir'] : null, + 'timeout_passed_global' => isset($GLOBALS['timeout_passed']) ? $GLOBALS['timeout_passed'] : null, + 'compressions' => $compressions, + 'is_encoding_supported' => Encoding::isSupported(), + 'encodings' => Encoding::listEncodings(), + 'import_charset' => isset($cfg['Import']['charset']) ? $cfg['Import']['charset'] : null, + 'dbi' => $GLOBALS['dbi'], + 'disable_is' => $cfg['Server']['DisableIS'], + 'timeout_passed' => isset($timeoutPassed) ? $timeoutPassed : null, + 'offset' => isset($offset) ? $offset : null, + 'can_convert_kanji' => Encoding::canConvertKanji(), + ]); + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Display/ImportAjax.php b/php/apps/phpmyadmin49/libraries/classes/Display/ImportAjax.php new file mode 100644 index 00000000..abb5e714 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Display/ImportAjax.php @@ -0,0 +1,134 @@ + null, + + /** string Table name */ + 'table' => null, + + /** string the URL to go back in case of errors */ + 'goto' => null, + + /** string the SQL query */ + 'sql_query' => null, + + /** + * integer the total number of rows returned by the SQL query without any + * appended "LIMIT" clause programmatically + */ + 'unlim_num_rows' => null, + + /** array meta information about fields */ + 'fields_meta' => null, + + /** boolean */ + 'is_count' => null, + + /** integer */ + 'is_export' => null, + + /** boolean */ + 'is_func' => null, + + /** integer */ + 'is_analyse' => null, + + /** integer the total number of rows returned by the SQL query */ + 'num_rows' => null, + + /** integer the total number of fields returned by the SQL query */ + 'fields_cnt' => null, + + /** double time taken for execute the SQL query */ + 'querytime' => null, + + /** string path for theme images directory */ + 'pma_theme_image' => null, + + /** string */ + 'text_dir' => null, + + /** boolean */ + 'is_maint' => null, + + /** boolean */ + 'is_explain' => null, + + /** boolean */ + 'is_show' => null, + + /** boolean */ + 'is_browse_distinct' => null, + + /** array table definitions */ + 'showtable' => null, + + /** string */ + 'printview' => null, + + /** string URL query */ + 'url_query' => null, + + /** array column names to highlight */ + 'highlight_columns' => null, + + /** array holding various display information */ + 'display_params' => null, + + /** array mime types information of fields */ + 'mime_map' => null, + + /** boolean */ + 'editable' => null, + + /** random unique ID to distinguish result set */ + 'unique_id' => null, + + /** where clauses for each row, each table in the row */ + 'whereClauseMap' => array(), + ); + + /** + * This variable contains the column transformation information + * for some of the system databases. + * One element of this array represent all relevant columns in all tables in + * one specific database + */ + public $transformation_info; + + /** + * @var Relation $relation + */ + private $relation; + + /** + * Get any property of this class + * + * @param string $property name of the property + * + * @return mixed|void if property exist, value of the relevant property + */ + public function __get($property) + { + if (array_key_exists($property, $this->_property_array)) { + return $this->_property_array[$property]; + } + } + + /** + * Set values for any property of this class + * + * @param string $property name of the property + * @param mixed $value value to set + * + * @return void + */ + public function __set($property, $value) + { + if (array_key_exists($property, $this->_property_array)) { + $this->_property_array[$property] = $value; + } + } + + /** + * Constructor for PhpMyAdmin\Display\Results class + * + * @param string $db the database name + * @param string $table the table name + * @param string $goto the URL to go back in case of errors + * @param string $sql_query the SQL query + * + * @access public + */ + public function __construct($db, $table, $goto, $sql_query) + { + $this->relation = new Relation(); + + $this->_setDefaultTransformations(); + + $this->__set('db', $db); + $this->__set('table', $table); + $this->__set('goto', $goto); + $this->__set('sql_query', $sql_query); + $this->__set('unique_id', rand()); + } + + /** + * Sets default transformations for some columns + * + * @return void + */ + private function _setDefaultTransformations() + { + $json_highlighting_data = array( + 'libraries/classes/Plugins/Transformations/Output/Text_Plain_Json.php', + 'PhpMyAdmin\Plugins\Transformations\Output\Text_Plain_Json', + 'Text_Plain' + ); + $sql_highlighting_data = array( + 'libraries/classes/Plugins/Transformations/Output/Text_Plain_Sql.php', + 'PhpMyAdmin\Plugins\Transformations\Output\Text_Plain_Sql', + 'Text_Plain' + ); + $blob_sql_highlighting_data = array( + 'libraries/classes/Plugins/Transformations/Output/Text_Octetstream_Sql.php', + 'PhpMyAdmin\Plugins\Transformations\Output\Text_Octetstream_Sql', + 'Text_Octetstream' + ); + $link_data = array( + 'libraries/classes/Plugins/Transformations/Text_Plain_Link.php', + 'PhpMyAdmin\Plugins\Transformations\Text_Plain_Link', + 'Text_Plain' + ); + $this->transformation_info = array( + 'information_schema' => array( + 'events' => array( + 'event_definition' => $sql_highlighting_data + ), + 'processlist' => array( + 'info' => $sql_highlighting_data + ), + 'routines' => array( + 'routine_definition' => $sql_highlighting_data + ), + 'triggers' => array( + 'action_statement' => $sql_highlighting_data + ), + 'views' => array( + 'view_definition' => $sql_highlighting_data + ) + ), + 'mysql' => array( + 'event' => array( + 'body' => $blob_sql_highlighting_data, + 'body_utf8' => $blob_sql_highlighting_data + ), + 'general_log' => array( + 'argument' => $sql_highlighting_data + ), + 'help_category' => array( + 'url' => $link_data + ), + 'help_topic' => array( + 'example' => $sql_highlighting_data, + 'url' => $link_data + ), + 'proc' => array( + 'param_list' => $blob_sql_highlighting_data, + 'returns' => $blob_sql_highlighting_data, + 'body' => $blob_sql_highlighting_data, + 'body_utf8' => $blob_sql_highlighting_data + ), + 'slow_log' => array( + 'sql_text' => $sql_highlighting_data + ) + ) + ); + + $cfgRelation = $this->relation->getRelationsParam(); + if ($cfgRelation['db']) { + $this->transformation_info[$cfgRelation['db']] = array(); + $relDb = &$this->transformation_info[$cfgRelation['db']]; + if (! empty($cfgRelation['history'])) { + $relDb[$cfgRelation['history']] = array( + 'sqlquery' => $sql_highlighting_data + ); + } + if (! empty($cfgRelation['bookmark'])) { + $relDb[$cfgRelation['bookmark']] = array( + 'query' => $sql_highlighting_data + ); + } + if (! empty($cfgRelation['tracking'])) { + $relDb[$cfgRelation['tracking']] = array( + 'schema_sql' => $sql_highlighting_data, + 'data_sql' => $sql_highlighting_data + ); + } + if (! empty($cfgRelation['favorite'])) { + $relDb[$cfgRelation['favorite']] = array( + 'tables' => $json_highlighting_data + ); + } + if (! empty($cfgRelation['recent'])) { + $relDb[$cfgRelation['recent']] = array( + 'tables' => $json_highlighting_data + ); + } + if (! empty($cfgRelation['savedsearches'])) { + $relDb[$cfgRelation['savedsearches']] = array( + 'search_data' => $json_highlighting_data + ); + } + if (! empty($cfgRelation['designer_settings'])) { + $relDb[$cfgRelation['designer_settings']] = array( + 'settings_data' => $json_highlighting_data + ); + } + if (! empty($cfgRelation['table_uiprefs'])) { + $relDb[$cfgRelation['table_uiprefs']] = array( + 'prefs' => $json_highlighting_data + ); + } + if (! empty($cfgRelation['userconfig'])) { + $relDb[$cfgRelation['userconfig']] = array( + 'config_data' => $json_highlighting_data + ); + } + if (! empty($cfgRelation['export_templates'])) { + $relDb[$cfgRelation['export_templates']] = array( + 'template_data' => $json_highlighting_data + ); + } + } + } + + /** + * Set properties which were not initialized at the constructor + * + * @param integer $unlim_num_rows the total number of rows returned by + * the SQL query without any appended + * "LIMIT" clause programmatically + * @param array $fields_meta meta information about fields + * @param boolean $is_count statement is SELECT COUNT + * @param integer $is_export statement contains INTO OUTFILE + * @param boolean $is_func statement contains a function like SUM() + * @param integer $is_analyse statement contains PROCEDURE ANALYSE + * @param integer $num_rows total no. of rows returned by SQL query + * @param integer $fields_cnt total no.of fields returned by SQL query + * @param double $querytime time taken for execute the SQL query + * @param string $pmaThemeImage path for theme images directory + * @param string $text_dir text direction + * @param boolean $is_maint statement contains a maintenance command + * @param boolean $is_explain statement contains EXPLAIN + * @param boolean $is_show statement contains SHOW + * @param array $showtable table definitions + * @param string $printview print view was requested + * @param string $url_query URL query + * @param boolean $editable whether the results set is editable + * @param boolean $is_browse_dist whether browsing distinct values + * + * @return void + * + * @see sql.php + */ + public function setProperties( + $unlim_num_rows, $fields_meta, $is_count, $is_export, $is_func, + $is_analyse, $num_rows, $fields_cnt, $querytime, $pmaThemeImage, $text_dir, + $is_maint, $is_explain, $is_show, $showtable, $printview, $url_query, + $editable, $is_browse_dist + ) { + + $this->__set('unlim_num_rows', $unlim_num_rows); + $this->__set('fields_meta', $fields_meta); + $this->__set('is_count', $is_count); + $this->__set('is_export', $is_export); + $this->__set('is_func', $is_func); + $this->__set('is_analyse', $is_analyse); + $this->__set('num_rows', $num_rows); + $this->__set('fields_cnt', $fields_cnt); + $this->__set('querytime', $querytime); + $this->__set('pma_theme_image', $pmaThemeImage); + $this->__set('text_dir', $text_dir); + $this->__set('is_maint', $is_maint); + $this->__set('is_explain', $is_explain); + $this->__set('is_show', $is_show); + $this->__set('showtable', $showtable); + $this->__set('printview', $printview); + $this->__set('url_query', $url_query); + $this->__set('editable', $editable); + $this->__set('is_browse_distinct', $is_browse_dist); + + } // end of the 'setProperties()' function + + + /** + * Defines the parts to display for a print view + * + * @param array $displayParts the parts to display + * + * @return array $displayParts the modified display parts + * + * @access private + * + */ + private function _setDisplayPartsForPrintView(array $displayParts) + { + // set all elements to false! + $displayParts['edit_lnk'] = self::NO_EDIT_OR_DELETE; // no edit link + $displayParts['del_lnk'] = self::NO_EDIT_OR_DELETE; // no delete link + $displayParts['sort_lnk'] = (string) '0'; + $displayParts['nav_bar'] = (string) '0'; + $displayParts['bkm_form'] = (string) '0'; + $displayParts['text_btn'] = (string) '0'; + $displayParts['pview_lnk'] = (string) '0'; + + return $displayParts; + } + + /** + * Defines the parts to display for a SHOW statement + * + * @param array $displayParts the parts to display + * + * @return array $displayParts the modified display parts + * + * @access private + * + */ + private function _setDisplayPartsForShow(array $displayParts) + { + preg_match( + '@^SHOW[[:space:]]+(VARIABLES|(FULL[[:space:]]+)?' + . 'PROCESSLIST|STATUS|TABLE|GRANTS|CREATE|LOGS|DATABASES|FIELDS' + . ')@i', + $this->__get('sql_query'), $which + ); + + $bIsProcessList = isset($which[1]); + if ($bIsProcessList) { + $str = ' ' . strtoupper($which[1]); + $bIsProcessList = $bIsProcessList + && strpos($str, 'PROCESSLIST') > 0; + } + + if ($bIsProcessList) { + // no edit link + $displayParts['edit_lnk'] = self::NO_EDIT_OR_DELETE; + // "kill process" type edit link + $displayParts['del_lnk'] = self::KILL_PROCESS; + } else { + // Default case -> no links + // no edit link + $displayParts['edit_lnk'] = self::NO_EDIT_OR_DELETE; + // no delete link + $displayParts['del_lnk'] = self::NO_EDIT_OR_DELETE; + } + // Other settings + $displayParts['sort_lnk'] = (string) '0'; + $displayParts['nav_bar'] = (string) '0'; + $displayParts['bkm_form'] = (string) '1'; + $displayParts['text_btn'] = (string) '1'; + $displayParts['pview_lnk'] = (string) '1'; + + return $displayParts; + } + + /** + * Defines the parts to display for statements not related to data + * + * @param array $displayParts the parts to display + * + * @return array $displayParts the modified display parts + * + * @access private + * + */ + private function _setDisplayPartsForNonData(array $displayParts) + { + // Statement is a "SELECT COUNT", a + // "CHECK/ANALYZE/REPAIR/OPTIMIZE/CHECKSUM", an "EXPLAIN" one or + // contains a "PROC ANALYSE" part + $displayParts['edit_lnk'] = self::NO_EDIT_OR_DELETE; // no edit link + $displayParts['del_lnk'] = self::NO_EDIT_OR_DELETE; // no delete link + $displayParts['sort_lnk'] = (string) '0'; + $displayParts['nav_bar'] = (string) '0'; + $displayParts['bkm_form'] = (string) '1'; + + if ($this->__get('is_maint')) { + $displayParts['text_btn'] = (string) '1'; + } else { + $displayParts['text_btn'] = (string) '0'; + } + $displayParts['pview_lnk'] = (string) '1'; + + return $displayParts; + } + + /** + * Defines the parts to display for other statements (probably SELECT) + * + * @param array $displayParts the parts to display + * + * @return array $displayParts the modified display parts + * + * @access private + * + */ + private function _setDisplayPartsForSelect(array $displayParts) + { + // Other statements (ie "SELECT" ones) -> updates + // $displayParts['edit_lnk'], $displayParts['del_lnk'] and + // $displayParts['text_btn'] (keeps other default values) + + $fields_meta = $this->__get('fields_meta'); + $prev_table = ''; + $displayParts['text_btn'] = (string) '1'; + $number_of_columns = $this->__get('fields_cnt'); + + for ($i = 0; $i < $number_of_columns; $i++) { + + $is_link = ($displayParts['edit_lnk'] != self::NO_EDIT_OR_DELETE) + || ($displayParts['del_lnk'] != self::NO_EDIT_OR_DELETE) + || ($displayParts['sort_lnk'] != '0'); + + // Displays edit/delete/sort/insert links? + if ($is_link + && $prev_table != '' + && $fields_meta[$i]->table != '' + && $fields_meta[$i]->table != $prev_table + ) { + // don't display links + $displayParts['edit_lnk'] = self::NO_EDIT_OR_DELETE; + $displayParts['del_lnk'] = self::NO_EDIT_OR_DELETE; + /** + * @todo May be problematic with same field names + * in two joined table. + */ + // $displayParts['sort_lnk'] = (string) '0'; + if ($displayParts['text_btn'] == '1') { + break; + } + } // end if + + // Always display print view link + $displayParts['pview_lnk'] = (string) '1'; + if ($fields_meta[$i]->table != '') { + $prev_table = $fields_meta[$i]->table; + } + } // end for + + if ($prev_table == '') { // no table for any of the columns + // don't display links + $displayParts['edit_lnk'] = self::NO_EDIT_OR_DELETE; + $displayParts['del_lnk'] = self::NO_EDIT_OR_DELETE; + } + + return $displayParts; + } + + /** + * Defines the parts to display for the results of a SQL query + * and the total number of rows + * + * @param array $displayParts the parts to display (see a few + * lines above for explanations) + * + * @return array the first element is an array with explicit indexes + * for all the display elements + * the second element is the total number of rows returned + * by the SQL query without any programmatically appended + * LIMIT clause (just a copy of $unlim_num_rows if it exists, + * else computed inside this function) + * + * + * @access private + * + * @see getTable() + */ + private function _setDisplayPartsAndTotal(array $displayParts) + { + $the_total = 0; + + // 1. Following variables are needed for use in isset/empty or + // use with array indexes or safe use in foreach + $db = $this->__get('db'); + $table = $this->__get('table'); + $unlim_num_rows = $this->__get('unlim_num_rows'); + $num_rows = $this->__get('num_rows'); + $printview = $this->__get('printview'); + + // 2. Updates the display parts + if ($printview == '1') { + $displayParts = $this->_setDisplayPartsForPrintView($displayParts); + + } elseif ($this->__get('is_count') || $this->__get('is_analyse') + || $this->__get('is_maint') || $this->__get('is_explain') + ) { + $displayParts = $this->_setDisplayPartsForNonData($displayParts); + + } elseif ($this->__get('is_show')) { + $displayParts = $this->_setDisplayPartsForShow($displayParts); + + } else { + $displayParts = $this->_setDisplayPartsForSelect($displayParts); + } // end if..elseif...else + + // 3. Gets the total number of rows if it is unknown + if (isset($unlim_num_rows) && $unlim_num_rows != '') { + $the_total = $unlim_num_rows; + } elseif ((($displayParts['nav_bar'] == '1') + || ($displayParts['sort_lnk'] == '1')) + && (strlen($db) > 0 && strlen($table) > 0) + ) { + $the_total = $GLOBALS['dbi']->getTable($db, $table)->countRecords(); + } + + // if for COUNT query, number of rows returned more than 1 + // (may be being used GROUP BY) + if ($this->__get('is_count') && isset($num_rows) && $num_rows > 1) { + $displayParts['nav_bar'] = (string) '1'; + $displayParts['sort_lnk'] = (string) '1'; + } + // 4. If navigation bar or sorting fields names URLs should be + // displayed but there is only one row, change these settings to + // false + if ($displayParts['nav_bar'] == '1' || $displayParts['sort_lnk'] == '1') { + + // - Do not display sort links if less than 2 rows. + // - For a VIEW we (probably) did not count the number of rows + // so don't test this number here, it would remove the possibility + // of sorting VIEW results. + $_table = new Table($table, $db); + if (isset($unlim_num_rows) + && ($unlim_num_rows < 2) + && ! $_table->isView() + ) { + $displayParts['sort_lnk'] = (string) '0'; + } + } // end if (3) + + return array($displayParts, $the_total); + + } // end of the 'setDisplayPartsAndTotal()' function + + + /** + * Return true if we are executing a query in the form of + * "SELECT * FROM ..." + * + * @param array $analyzed_sql_results analyzed sql results + * + * @return boolean + * + * @access private + * + * @see _getTableHeaders(), _getColumnParams() + */ + private function _isSelect(array $analyzed_sql_results) + { + return ! ($this->__get('is_count') + || $this->__get('is_export') + || $this->__get('is_func') + || $this->__get('is_analyse')) + && !empty($analyzed_sql_results['select_from']) + && !empty($analyzed_sql_results['statement']->from) + && (count($analyzed_sql_results['statement']->from) == 1) + && !empty($analyzed_sql_results['statement']->from[0]->table); + } + + + /** + * Get a navigation button + * + * @param string $caption iconic caption for button + * @param string $title text for button + * @param integer $pos position for next query + * @param string $html_sql_query query ready for display + * @param boolean $back whether 'begin' or 'previous' + * @param string $onsubmit optional onsubmit clause + * @param string $input_for_real_end optional hidden field for special treatment + * @param string $onclick optional onclick clause + * + * @return string html content + * + * @access private + * + * @see _getMoveBackwardButtonsForTableNavigation(), + * _getMoveForwardButtonsForTableNavigation() + */ + private function _getTableNavigationButton( + $caption, + $title, + $pos, + $html_sql_query, + $back, + $onsubmit = '', + $input_for_real_end = '', + $onclick = '' + ) { + $caption_output = ''; + if ($back) { + if (Util::showIcons('TableNavigationLinksMode')) { + $caption_output .= $caption; + } + if (Util::showText('TableNavigationLinksMode')) { + $caption_output .= ' ' . $title; + } + } else { + if (Util::showText('TableNavigationLinksMode')) { + $caption_output .= $title; + } + if (Util::showIcons('TableNavigationLinksMode')) { + $caption_output .= ' ' . $caption; + } + } + + return Template::get('display/results/table_navigation_button')->render([ + 'db' => $this->__get('db'), + 'table' => $this->__get('table'), + 'sql_query' => $html_sql_query, + 'pos' => $pos, + 'is_browse_distinct' => $this->__get('is_browse_distinct'), + 'goto' => $this->__get('goto'), + 'input_for_real_end' => $input_for_real_end, + 'caption_output' => $caption_output, + 'title' => $title, + 'onsubmit' => $onsubmit, + 'onclick' => $onclick, + ]); + } + + /** + * Possibly return a page selector for table navigation + * + * @param string $table_navigation_html the current navigation HTML + * + * @return array ($table_navigation_html, $nbTotalPage) + * + * @access private + * + */ + private function _getHtmlPageSelector($table_navigation_html) + { + $pageNow = @floor( + $_SESSION['tmpval']['pos'] + / $_SESSION['tmpval']['max_rows'] + ) + 1; + + $nbTotalPage = @ceil( + $this->__get('unlim_num_rows') + / $_SESSION['tmpval']['max_rows'] + ); + + if ($nbTotalPage > 1) { + $table_navigation_html .= ''; + $_url_params = array( + 'db' => $this->__get('db'), + 'table' => $this->__get('table'), + 'sql_query' => $this->__get('sql_query'), + 'goto' => $this->__get('goto'), + 'is_browse_distinct' => $this->__get('is_browse_distinct'), + ); + + //
        to keep the form alignment of button < and << + // and also to know what to execute when the selector changes + $table_navigation_html .= ''; + $table_navigation_html .= Url::getHiddenInputs($_url_params); + + $table_navigation_html .= Util::pageselector( + 'pos', + $_SESSION['tmpval']['max_rows'], + $pageNow, $nbTotalPage, 200, 5, 5, 20, 10 + ); + + $table_navigation_html .= '
        ' + . ''; + } + return array($table_navigation_html, $nbTotalPage); + } + + /** + * Get a navigation bar to browse among the results of a SQL query + * + * @param integer $pos_next the offset for the "next" page + * @param integer $pos_prev the offset for the "previous" page + * @param boolean $is_innodb whether its InnoDB or not + * @param string $sort_by_key_html the sort by key dialog + * + * @return string html content + * + * @access private + * + * @see _getTable() + */ + private function _getTableNavigation( + $pos_next, $pos_prev, $is_innodb, $sort_by_key_html + ) { + + $table_navigation_html = ''; + + // here, using htmlentities() would cause problems if the query + // contains accented characters + $html_sql_query = htmlspecialchars($this->__get('sql_query')); + + // Navigation bar + $table_navigation_html + .= '' + . '' + . ''; + + // Move to the beginning or to the previous page + if ($_SESSION['tmpval']['pos'] + && ($_SESSION['tmpval']['max_rows'] != self::ALL_ROWS) + ) { + + $table_navigation_html + .= $this->_getMoveBackwardButtonsForTableNavigation( + $html_sql_query, $pos_prev + ); + + } // end move back + + $nbTotalPage = 1; + //page redirection + // (unless we are showing all records) + if ($_SESSION['tmpval']['max_rows'] != self::ALL_ROWS) { + list( + $table_navigation_html, + $nbTotalPage + ) = $this->_getHtmlPageSelector($table_navigation_html); + } + + $showing_all = false; + if ($_SESSION['tmpval']['max_rows'] == self::ALL_ROWS) { + $showing_all = true; + } + + // Move to the next page or to the last one + if ($this->__get('unlim_num_rows') === false // view with unknown number of rows + || ($_SESSION['tmpval']['max_rows'] != self::ALL_ROWS + && $_SESSION['tmpval']['pos'] + $_SESSION['tmpval']['max_rows'] < $this->__get('unlim_num_rows') + && $this->__get('num_rows') >= $_SESSION['tmpval']['max_rows']) + ) { + + $table_navigation_html + .= $this->_getMoveForwardButtonsForTableNavigation( + $html_sql_query, $pos_next, $is_innodb + ); + + } // end move toward + + // show separator if pagination happen + if ($nbTotalPage > 1) { + $table_navigation_html + .= ''; + } + + // Display the "Show all" button if allowed + if ($GLOBALS['cfg']['ShowAll'] || ($this->__get('unlim_num_rows') <= 500) ) { + + $table_navigation_html .= $this->_getShowAllCheckboxForTableNavigation( + $showing_all, $html_sql_query + ); + + $table_navigation_html + .= ''; + + } // end show all + + $table_navigation_html .= '' + . ''; + + // if displaying a VIEW, $unlim_num_rows could be zero because + // of $cfg['MaxExactCountViews']; in this case, avoid passing + // the 5th parameter to checkFormElementInRange() + // (this means we can't validate the upper limit + $table_navigation_html .= '' + . '' + . ''; + + $table_navigation_html .= ''; + + $table_navigation_html .= '' + . '' + . ''; + + return $table_navigation_html; + + } // end of the '_getTableNavigation()' function + + + /** + * Prepare move backward buttons - previous and first + * + * @param string $html_sql_query the sql encoded by html special characters + * @param integer $pos_prev the offset for the "previous" page + * + * @return string html content + * + * @access private + * + * @see _getTableNavigation() + */ + private function _getMoveBackwardButtonsForTableNavigation( + $html_sql_query, $pos_prev + ) { + return $this->_getTableNavigationButton( + '<<', _pgettext('First page', 'Begin'), 0, $html_sql_query, true + ) + . $this->_getTableNavigationButton( + '<', _pgettext('Previous page', 'Previous'), $pos_prev, + $html_sql_query, true + ); + } // end of the '_getMoveBackwardButtonsForTableNavigation()' function + + + /** + * Prepare Show All checkbox for table navigation + * + * @param bool $showing_all whether all rows are shown currently + * @param string $html_sql_query the sql encoded by html special characters + * + * @return string html content + * + * @access private + * + * @see _getTableNavigation() + */ + private function _getShowAllCheckboxForTableNavigation( + $showing_all, $html_sql_query + ) { + return Template::get('display/results/show_all_checkbox')->render([ + 'db' => $this->__get('db'), + 'table' => $this->__get('table'), + 'is_browse_distinct' => $this->__get('is_browse_distinct'), + 'goto' => $this->__get('goto'), + 'unique_id' => $this->__get('unique_id'), + 'html_sql_query' => $html_sql_query, + 'showing_all' => $showing_all, + 'max_rows' => intval($GLOBALS['cfg']['MaxRows']), + ]); + } // end of the '_getShowAllButtonForTableNavigation()' function + + + /** + * Prepare move forward buttons - next and last + * + * @param string $html_sql_query the sql encoded by htmlspecialchars() + * @param integer $pos_next the offset for the "next" page + * @param boolean $is_innodb whether it's InnoDB or not + * + * @return string $buttons_html html content + * + * @access private + * + * @see _getTableNavigation() + */ + private function _getMoveForwardButtonsForTableNavigation( + $html_sql_query, $pos_next, $is_innodb + ) { + + // display the Next button + $buttons_html = $this->_getTableNavigationButton( + '>', + _pgettext('Next page', 'Next'), + $pos_next, + $html_sql_query, + false + ); + + // prepare some options for the End button + if ($is_innodb + && $this->__get('unlim_num_rows') > $GLOBALS['cfg']['MaxExactCount'] + ) { + $input_for_real_end = ''; + // no backquote around this message + $onclick = ''; + } else { + $input_for_real_end = $onclick = ''; + } + + $maxRows = $_SESSION['tmpval']['max_rows']; + $onsubmit = 'onsubmit="return ' + . (($_SESSION['tmpval']['pos'] + + $maxRows + < $this->__get('unlim_num_rows') + && $this->__get('num_rows') >= $maxRows) + ? 'true' + : 'false') . '"'; + + // display the End button + $buttons_html .= $this->_getTableNavigationButton( + '>>', + _pgettext('Last page', 'End'), + @((ceil( + $this->__get('unlim_num_rows') + / $_SESSION['tmpval']['max_rows'] + )- 1) * $maxRows), + $html_sql_query, false, $onsubmit, $input_for_real_end, $onclick + ); + + return $buttons_html; + + } // end of the '_getMoveForwardButtonsForTableNavigation()' function + + + /** + * Prepare fields for table navigation + * Number of rows + * + * @param string $sqlQuery the sql encoded by htmlspecialchars() + * + * @return string html content + * + * @access private + * + * @see _getTableNavigation() + */ + private function _getAdditionalFieldsForTableNavigation($sqlQuery) + { + $numberOfRowsPlaceholder = null; + if ($_SESSION['tmpval']['max_rows'] == self::ALL_ROWS) { + $numberOfRowsPlaceholder = __('All'); + } + + $numberOfRowsChoices = array( + '25' => 25, + '50' => 50, + '100' => 100, + '250' => 250, + '500' => 500 + ); + + return Template::get('display/results/additional_fields')->render([ + 'goto' => $this->__get('goto'), + 'is_browse_distinct' => $this->__get('is_browse_distinct'), + 'sql_query' => $sqlQuery, + 'number_of_rows_choices' => $numberOfRowsChoices, + 'number_of_rows_placeholder' => $numberOfRowsPlaceholder, + 'pos' => $_SESSION['tmpval']['pos'], + 'max_rows' => $_SESSION['tmpval']['max_rows'], + ]); + } + + /** + * Get the headers of the results table, for all of the columns + * + * @param array $displayParts which elements to display + * @param array $analyzed_sql_results analyzed sql results + * @param array $sort_expression sort expression + * @param array $sort_expression_nodirection sort expression + * without direction + * @param array $sort_direction sort direction + * @param boolean $is_limited_display with limited operations + * or not + * @param string $unsorted_sql_query query without the sort part + * + * @return string html content + * + * @access private + * + * @see getTableHeaders() + */ + private function _getTableHeadersForColumns( + array $displayParts, array $analyzed_sql_results, array $sort_expression, + array $sort_expression_nodirection, array $sort_direction, $is_limited_display, + $unsorted_sql_query + ) { + $html = ''; + + // required to generate sort links that will remember whether the + // "Show all" button has been clicked + $sql_md5 = md5($this->__get('sql_query')); + $session_max_rows = $is_limited_display + ? 0 + : $_SESSION['tmpval']['query'][$sql_md5]['max_rows']; + + // Following variable are needed for use in isset/empty or + // use with array indexes/safe use in the for loop + $highlight_columns = $this->__get('highlight_columns'); + $fields_meta = $this->__get('fields_meta'); + + // Prepare Display column comments if enabled + // ($GLOBALS['cfg']['ShowBrowseComments']). + $comments_map = $this->_getTableCommentsArray($analyzed_sql_results); + + list($col_order, $col_visib) = $this->_getColumnParams( + $analyzed_sql_results + ); + + // optimize: avoid calling a method on each iteration + $number_of_columns = $this->__get('fields_cnt'); + + for ($j = 0; $j < $number_of_columns; $j++) { + // PHP 7.4 fix for accessing array offset on bool + $col_visib_current = is_array($col_visib) ? $col_visib[$j] : null; + + // assign $i with the appropriate column order + $i = $col_order ? $col_order[$j] : $j; + + // See if this column should get highlight because it's used in the + // where-query. + $name = $fields_meta[$i]->name; + $condition_field = (isset($highlight_columns[$name]) + || isset($highlight_columns[Util::backquote($name)])) + ? true + : false; + + // Prepare comment-HTML-wrappers for each row, if defined/enabled. + $comments = $this->_getCommentForRow($comments_map, $fields_meta[$i]); + $display_params = $this->__get('display_params'); + + if (($displayParts['sort_lnk'] == '1') && ! $is_limited_display) { + + list($order_link, $sorted_header_html) + = $this->_getOrderLinkAndSortedHeaderHtml( + $fields_meta[$i], $sort_expression, + $sort_expression_nodirection, $i, $unsorted_sql_query, + $session_max_rows, $comments, + $sort_direction, $col_visib, + $col_visib_current + ); + + $html .= $sorted_header_html; + + $display_params['desc'][] = ' ' . "\n" . $order_link . $comments . ' ' . "\n"; + } else { + // Results can't be sorted + $html + .= $this->_getDraggableClassForNonSortableColumns( + $col_visib, $col_visib_current, $condition_field, + $fields_meta[$i], $comments + ); + + $display_params['desc'][] = ' ' . ' ' + . htmlspecialchars($fields_meta[$i]->name) + . $comments . ' '; + } // end else + + $this->__set('display_params', $display_params); + + } // end for + return $html; + } + + /** + * Get the headers of the results table + * + * @param array &$displayParts which elements to display + * @param array $analyzed_sql_results analyzed sql results + * @param string $unsorted_sql_query the unsorted sql query + * @param array $sort_expression sort expression + * @param array|string $sort_expression_nodirection sort expression + * without direction + * @param array $sort_direction sort direction + * @param boolean $is_limited_display with limited operations + * or not + * + * @return string html content + * + * @access private + * + * @see getTable() + */ + private function _getTableHeaders( + array &$displayParts, array $analyzed_sql_results, $unsorted_sql_query, + array $sort_expression = array(), $sort_expression_nodirection = '', + array $sort_direction = array(), $is_limited_display = false + ) { + + $table_headers_html = ''; + // Needed for use in isset/empty or + // use with array indexes/safe use in foreach + $printview = $this->__get('printview'); + $display_params = $this->__get('display_params'); + + // Output data needed for grid editing + $table_headers_html .= '' + . '
        ' + . Url::getHiddenInputs( + $this->__get('db'), $this->__get('table') + ) + . '
        '; + + // Output data needed for column reordering and show/hide column + $table_headers_html .= $this->_getDataForResettingColumnOrder($analyzed_sql_results); + + $display_params['emptypre'] = 0; + $display_params['emptyafter'] = 0; + $display_params['textbtn'] = ''; + $full_or_partial_text_link = null; + + $this->__set('display_params', $display_params); + + // Display options (if we are not in print view) + if (! (isset($printview) && ($printview == '1')) && ! $is_limited_display) { + + $table_headers_html .= $this->_getOptionsBlock(); + + // prepare full/partial text button or link + $full_or_partial_text_link = $this->_getFullOrPartialTextButtonOrLink(); + } + + // Start of form for multi-rows edit/delete/export + $table_headers_html .= $this->_getFormForMultiRowOperations( + $displayParts['del_lnk'] + ); + + // 1. Set $colspan and generate html with full/partial + // text button or link + list($colspan, $button_html) + = $this->_getFieldVisibilityParams( + $displayParts, $full_or_partial_text_link + ); + + $table_headers_html .= $button_html; + + // 2. Displays the fields' name + // 2.0 If sorting links should be used, checks if the query is a "JOIN" + // statement (see 2.1.3) + + // See if we have to highlight any header fields of a WHERE query. + // Uses SQL-Parser results. + $this->_setHighlightedColumnGlobalField($analyzed_sql_results); + + // Get the headers for all of the columns + $table_headers_html .= $this->_getTableHeadersForColumns( + $displayParts, $analyzed_sql_results, $sort_expression, + $sort_expression_nodirection, $sort_direction, + $is_limited_display, $unsorted_sql_query + ); + + // Display column at rightside - checkboxes or empty column + if (! $printview) { + $table_headers_html .= $this->_getColumnAtRightSide( + $displayParts, $full_or_partial_text_link, $colspan + ); + } + $table_headers_html .= '' . ''; + + return $table_headers_html; + + } // end of the '_getTableHeaders()' function + + + /** + * Prepare unsorted sql query and sort by key drop down + * + * @param array $analyzed_sql_results analyzed sql results + * @param array|null $sort_expression sort expression + * + * @return array two element array - $unsorted_sql_query, $drop_down_html + * + * @access private + * + * @see _getTableHeaders() + */ + private function _getUnsortedSqlAndSortByKeyDropDown( + array $analyzed_sql_results, array $sort_expression + ) { + $drop_down_html = ''; + + $unsorted_sql_query = Query::replaceClause( + $analyzed_sql_results['statement'], + $analyzed_sql_results['parser']->list, + 'ORDER BY', + '' + ); + + // Data is sorted by indexes only if it there is only one table. + if ($this->_isSelect($analyzed_sql_results)) { + // grab indexes data: + $indexes = Index::getFromTable( + $this->__get('table'), + $this->__get('db') + ); + + // do we have any index? + if (! empty($indexes)) { + $drop_down_html = $this->_getSortByKeyDropDown( + $indexes, $sort_expression, + $unsorted_sql_query + ); + } + } + + return array($unsorted_sql_query, $drop_down_html); + + } // end of the '_getUnsortedSqlAndSortByKeyDropDown()' function + + /** + * Prepare sort by key dropdown - html code segment + * + * @param Index[] $indexes the indexes of the table for sort criteria + * @param array|null $sort_expression the sort expression + * @param string $unsorted_sql_query the unsorted sql query + * + * @return string $drop_down_html html content + * + * @access private + * + * @see _getTableHeaders() + */ + private function _getSortByKeyDropDown( + $indexes, array $sort_expression, $unsorted_sql_query + ) { + + $drop_down_html = ''; + + $drop_down_html .= '
        ' . "\n" + . Url::getHiddenInputs( + $this->__get('db'), $this->__get('table') + ) + . Url::getHiddenFields(array('sort_by_key' => '1')) + . __('Sort by key') + . ': ' . "\n" + . '
        ' . "\n"; + + return $drop_down_html; + + } // end of the '_getSortByKeyDropDown()' function + + + /** + * Set column span, row span and prepare html with full/partial + * text button or link + * + * @param array &$displayParts which elements to display + * @param string $full_or_partial_text_link full/partial link or text button + * + * @return array 2 element array - $colspan, $button_html + * + * @access private + * + * @see _getTableHeaders() + */ + private function _getFieldVisibilityParams( + array &$displayParts, $full_or_partial_text_link + ) { + + $button_html = ''; + $display_params = $this->__get('display_params'); + + // 1. Displays the full/partial text button (part 1)... + $button_html .= '' . "\n"; + + $colspan = (($displayParts['edit_lnk'] != self::NO_EDIT_OR_DELETE) + && ($displayParts['del_lnk'] != self::NO_EDIT_OR_DELETE)) + ? ' colspan="4"' + : ''; + + // ... before the result table + if ((($displayParts['edit_lnk'] == self::NO_EDIT_OR_DELETE) + && ($displayParts['del_lnk'] == self::NO_EDIT_OR_DELETE)) + && ($displayParts['text_btn'] == '1') + ) { + + $display_params['emptypre'] + = (($displayParts['edit_lnk'] != self::NO_EDIT_OR_DELETE) + && ($displayParts['del_lnk'] != self::NO_EDIT_OR_DELETE)) ? 4 : 0; + + } elseif ((($GLOBALS['cfg']['RowActionLinks'] == self::POSITION_LEFT) + || ($GLOBALS['cfg']['RowActionLinks'] == self::POSITION_BOTH)) + && ($displayParts['text_btn'] == '1') + ) { + // ... at the left column of the result table header if possible + // and required + + $display_params['emptypre'] + = (($displayParts['edit_lnk'] != self::NO_EDIT_OR_DELETE) + && ($displayParts['del_lnk'] != self::NO_EDIT_OR_DELETE)) ? 4 : 0; + + $button_html .= '' . $full_or_partial_text_link . ''; + + } elseif ((($GLOBALS['cfg']['RowActionLinks'] == self::POSITION_LEFT) + || ($GLOBALS['cfg']['RowActionLinks'] == self::POSITION_BOTH)) + && (($displayParts['edit_lnk'] != self::NO_EDIT_OR_DELETE) + || ($displayParts['del_lnk'] != self::NO_EDIT_OR_DELETE)) + ) { + // ... elseif no button, displays empty(ies) col(s) if required + + $display_params['emptypre'] + = (($displayParts['edit_lnk'] != self::NO_EDIT_OR_DELETE) + && ($displayParts['del_lnk'] != self::NO_EDIT_OR_DELETE)) ? 4 : 0; + + $button_html .= ''; + + } elseif (($GLOBALS['cfg']['RowActionLinks'] == self::POSITION_NONE)) { + // ... elseif display an empty column if the actions links are + // disabled to match the rest of the table + $button_html .= ''; + } + + $this->__set('display_params', $display_params); + + return array($colspan, $button_html); + + } // end of the '_getFieldVisibilityParams()' function + + + /** + * Get table comments as array + * + * @param array $analyzed_sql_results analyzed sql results + * + * @return array $comments_map table comments + * + * @access private + * + * @see _getTableHeaders() + */ + private function _getTableCommentsArray(array $analyzed_sql_results) + { + if ((!$GLOBALS['cfg']['ShowBrowseComments']) + || (empty($analyzed_sql_results['statement']->from)) + ) { + return array(); + } + + $ret = array(); + foreach ($analyzed_sql_results['statement']->from as $field) { + if (empty($field->table)) { + continue; + } + $ret[$field->table] = $this->relation->getComments( + empty($field->database) ? $this->__get('db') : $field->database, + $field->table + ); + } + + return $ret; + + } // end of the '_getTableCommentsArray()' function + + + /** + * Set global array for store highlighted header fields + * + * @param array $analyzed_sql_results analyzed sql results + * + * @return void + * + * @access private + * + * @see _getTableHeaders() + */ + private function _setHighlightedColumnGlobalField(array $analyzed_sql_results) + { + $highlight_columns = array(); + + if (!empty($analyzed_sql_results['statement']->where)) { + foreach ($analyzed_sql_results['statement']->where as $expr) { + foreach ($expr->identifiers as $identifier) { + $highlight_columns[$identifier] = 'true'; + } + } + } + + $this->__set('highlight_columns', $highlight_columns); + + } // end of the '_setHighlightedColumnGlobalField()' function + + + /** + * Prepare data for column restoring and show/hide + * + * @param array $analyzed_sql_results analyzed sql results + * + * @return string $data_html html content + * + * @access private + * + * @see _getTableHeaders() + */ + private function _getDataForResettingColumnOrder(array $analyzed_sql_results) + { + if (! $this->_isSelect($analyzed_sql_results)) { + return ''; + } + + // generate the column order, if it is set + list($col_order, $col_visib) = $this->_getColumnParams( + $analyzed_sql_results + ); + + $data_html = ''; + + if ($col_order) { + $data_html .= ''; + } + + if ($col_visib) { + $data_html .= ''; + } + + // generate table create time + $table = new Table($this->__get('table'), $this->__get('db')); + if (! $table->isView()) { + $data_html .= ''; + } + + return $data_html; + + } // end of the '_getDataForResettingColumnOrder()' function + + + /** + * Prepare option fields block + * + * @return string html content + * + * @access private + * + * @see _getTableHeaders() + */ + private function _getOptionsBlock() + { + if(isset($_SESSION['tmpval']['possible_as_geometry']) && $_SESSION['tmpval']['possible_as_geometry'] == false) { + if($_SESSION['tmpval']['geoOption'] == self::GEOMETRY_DISP_GEOM) { + $_SESSION['tmpval']['geoOption'] = self::GEOMETRY_DISP_WKT; + } + } + return Template::get('display/results/options_block')->render([ + 'unique_id' => $this->__get('unique_id'), + 'geo_option' => $_SESSION['tmpval']['geoOption'], + 'hide_transformation' => $_SESSION['tmpval']['hide_transformation'], + 'display_blob' => $_SESSION['tmpval']['display_blob'], + 'display_binary' => $_SESSION['tmpval']['display_binary'], + 'relational_display' => $_SESSION['tmpval']['relational_display'], + 'displaywork' => $GLOBALS['cfgRelation']['displaywork'], + 'relwork' => $GLOBALS['cfgRelation']['relwork'], + 'possible_as_geometry' => $_SESSION['tmpval']['possible_as_geometry'], + 'pftext' => $_SESSION['tmpval']['pftext'], + 'db' => $this->__get('db'), + 'table' => $this->__get('table'), + 'sql_query' => $this->__get('sql_query'), + 'goto' => $this->__get('goto'), + ]); + } + + /** + * Get full/partial text button or link + * + * @return string html content + * + * @access private + * + * @see _getTableHeaders() + */ + private function _getFullOrPartialTextButtonOrLink() + { + + $url_params_full_text = array( + 'db' => $this->__get('db'), + 'table' => $this->__get('table'), + 'sql_query' => $this->__get('sql_query'), + 'goto' => $this->__get('goto'), + 'full_text_button' => 1 + ); + + if ($_SESSION['tmpval']['pftext'] == self::DISPLAY_FULL_TEXT) { + // currently in fulltext mode so show the opposite link + $tmp_image_file = $this->__get('pma_theme_image') . 's_partialtext.png'; + $tmp_txt = __('Partial texts'); + $url_params_full_text['pftext'] = self::DISPLAY_PARTIAL_TEXT; + } else { + $tmp_image_file = $this->__get('pma_theme_image') . 's_fulltext.png'; + $tmp_txt = __('Full texts'); + $url_params_full_text['pftext'] = self::DISPLAY_FULL_TEXT; + } + + $tmp_image = ''
+                     . $tmp_txt . ''; + $tmp_url = 'sql.php' . Url::getCommon($url_params_full_text); + + return Util::linkOrButton($tmp_url, $tmp_image); + + } // end of the '_getFullOrPartialTextButtonOrLink()' function + + + /** + * Prepare html form for multi row operations + * + * @param string $deleteLink the delete link of current row + * + * @return string $form_html html content + * + * @access private + * + * @see _getTableHeaders() + */ + private function _getFormForMultiRowOperations($deleteLink) + { + return Template::get('display/results/multi_row_operations_form')->render([ + 'delete_link' => $deleteLink, + 'delete_row' => self::DELETE_ROW, + 'kill_process' => self::KILL_PROCESS, + 'unique_id' => $this->__get('unique_id'), + 'db' => $this->__get('db'), + 'table' => $this->__get('table'), + ]); + } + + /** + * Get comment for row + * + * @param array $commentsMap comments array + * @param array $fieldsMeta set of field properties + * + * @return string html content + * + * @access private + * + * @see _getTableHeaders() + */ + private function _getCommentForRow(array $commentsMap, $fieldsMeta) + { + return Template::get('display/results/comment_for_row')->render([ + 'comments_map' => $commentsMap, + 'fields_meta' => $fieldsMeta, + 'limit_chars' => $GLOBALS['cfg']['LimitChars'], + ]); + } + + /** + * Prepare parameters and html for sorted table header fields + * + * @param array $fields_meta set of field properties + * @param array $sort_expression sort expression + * @param array $sort_expression_nodirection sort expression without direction + * @param integer $column_index the index of the column + * @param string $unsorted_sql_query the unsorted sql query + * @param integer $session_max_rows maximum rows resulted by sql + * @param string $comments comment for row + * @param array $sort_direction sort direction + * @param boolean $col_visib column is visible(false) + * array column isn't visible(string array) + * @param string $col_visib_j element of $col_visib array + * + * @return array 2 element array - $order_link, $sorted_header_html + * + * @access private + * + * @see _getTableHeaders() + */ + private function _getOrderLinkAndSortedHeaderHtml( + $fields_meta, array $sort_expression, array $sort_expression_nodirection, + $column_index, $unsorted_sql_query, $session_max_rows, + $comments, array $sort_direction, $col_visib, $col_visib_j + ) { + + $sorted_header_html = ''; + + // Checks if the table name is required; it's the case + // for a query with a "JOIN" statement and if the column + // isn't aliased, or in queries like + // SELECT `1`.`master_field` , `2`.`master_field` + // FROM `PMA_relation` AS `1` , `PMA_relation` AS `2` + + $sort_tbl = (isset($fields_meta->table) + && strlen($fields_meta->table) > 0 + && $fields_meta->orgname == $fields_meta->name) + ? Util::backquote( + $fields_meta->table + ) . '.' + : ''; + + $name_to_use_in_sort = $fields_meta->name; + + // Generates the orderby clause part of the query which is part + // of URL + list($single_sort_order, $multi_sort_order, $order_img) + = $this->_getSingleAndMultiSortUrls( + $sort_expression, $sort_expression_nodirection, $sort_tbl, + $name_to_use_in_sort, $sort_direction, $fields_meta, $column_index + ); + + if (preg_match( + '@(.*)([[:space:]](LIMIT (.*)|PROCEDURE (.*)|FOR UPDATE|' + . 'LOCK IN SHARE MODE))@is', + $unsorted_sql_query, $regs3 + )) { + $single_sorted_sql_query = $regs3[1] . $single_sort_order . $regs3[2]; + $multi_sorted_sql_query = $regs3[1] . $multi_sort_order . $regs3[2]; + } else { + $single_sorted_sql_query = $unsorted_sql_query . $single_sort_order; + $multi_sorted_sql_query = $unsorted_sql_query . $multi_sort_order; + } + + $_single_url_params = array( + 'db' => $this->__get('db'), + 'table' => $this->__get('table'), + 'sql_query' => $single_sorted_sql_query, + 'session_max_rows' => $session_max_rows, + 'is_browse_distinct' => $this->__get('is_browse_distinct'), + ); + + $_multi_url_params = array( + 'db' => $this->__get('db'), + 'table' => $this->__get('table'), + 'sql_query' => $multi_sorted_sql_query, + 'session_max_rows' => $session_max_rows, + 'is_browse_distinct' => $this->__get('is_browse_distinct'), + ); + $single_order_url = 'sql.php' . Url::getCommon($_single_url_params); + $multi_order_url = 'sql.php' . Url::getCommon($_multi_url_params); + + // Displays the sorting URL + // enable sort order swapping for image + $order_link = $this->_getSortOrderLink( + $order_img, $fields_meta, $single_order_url, $multi_order_url + ); + + $sorted_header_html .= $this->_getDraggableClassForSortableColumns( + $col_visib, $col_visib_j, + $fields_meta, $order_link, $comments + ); + + return array($order_link, $sorted_header_html); + + } // end of the '_getOrderLinkAndSortedHeaderHtml()' function + + /** + * Prepare parameters and html for sorted table header fields + * + * @param array $sort_expression sort expression + * @param array $sort_expression_nodirection sort expression without direction + * @param string $sort_tbl The name of the table to which + * the current column belongs to + * @param string $name_to_use_in_sort The current column under + * consideration + * @param array $sort_direction sort direction + * @param array $fields_meta set of field properties + * @param integer $column_index The index number to current column + * + * @return array 3 element array - $single_sort_order, $sort_order, $order_img + * + * @access private + * + * @see _getOrderLinkAndSortedHeaderHtml() + */ + private function _getSingleAndMultiSortUrls( + array $sort_expression, array $sort_expression_nodirection, $sort_tbl, + $name_to_use_in_sort, array $sort_direction, $fields_meta, $column_index + ) { + $sort_order = ""; + // Check if the current column is in the order by clause + $is_in_sort = $this->_isInSorted( + $sort_expression, $sort_expression_nodirection, + $sort_tbl, $name_to_use_in_sort + ); + $current_name = $name_to_use_in_sort; + if ($sort_expression_nodirection[0] == '' || !$is_in_sort) { + $special_index = $sort_expression_nodirection[0] == '' + ? 0 + : count($sort_expression_nodirection); + $sort_expression_nodirection[$special_index] + = Util::backquote( + $current_name + ); + $sort_direction[$special_index] = (preg_match( + '@time|date@i', + $fields_meta->type + )) ? self::DESCENDING_SORT_DIR : self::ASCENDING_SORT_DIR; + + } + + $sort_expression_nodirection = array_filter($sort_expression_nodirection); + $single_sort_order = null; + foreach ($sort_expression_nodirection as $index=>$expression) { + // check if this is the first clause, + // if it is then we have to add "order by" + $is_first_clause = ($index == 0); + $name_to_use_in_sort = $expression; + $sort_tbl_new = $sort_tbl; + // Test to detect if the column name is a standard name + // Standard name has the table name prefixed to the column name + if (mb_strpos($name_to_use_in_sort, '.') !== false) { + $matches = explode('.', $name_to_use_in_sort); + // Matches[0] has the table name + // Matches[1] has the column name + $name_to_use_in_sort = $matches[1]; + $sort_tbl_new = $matches[0]; + } + + // $name_to_use_in_sort might contain a space due to + // formatting of function expressions like "COUNT(name )" + // so we remove the space in this situation + $name_to_use_in_sort = str_replace(' )', ')', $name_to_use_in_sort); + $name_to_use_in_sort = str_replace('``', '`', $name_to_use_in_sort); + $name_to_use_in_sort = trim($name_to_use_in_sort, '`'); + + // If this the first column name in the order by clause add + // order by clause to the column name + $query_head = $is_first_clause ? "\nORDER BY " : ""; + // Again a check to see if the given column is a aggregate column + if (mb_strpos($name_to_use_in_sort, '(') !== false) { + $sort_order .= $query_head . $name_to_use_in_sort . ' ' ; + } else { + if (strlen($sort_tbl_new) > 0) { + $sort_tbl_new .= "."; + } + $sort_order .= $query_head . $sort_tbl_new + . Util::backquote( + $name_to_use_in_sort + ) . ' ' ; + } + + // For a special case where the code generates two dots between + // column name and table name. + $sort_order = preg_replace("/\.\./", ".", $sort_order); + // Incase this is the current column save $single_sort_order + if ($current_name == $name_to_use_in_sort) { + if (mb_strpos($current_name, '(') !== false) { + $single_sort_order = "\n" . 'ORDER BY ' . Util::backquote($current_name) . ' '; + } else { + $single_sort_order = "\n" . 'ORDER BY ' . $sort_tbl + . Util::backquote( + $current_name + ) . ' '; + } + if ($is_in_sort) { + list($single_sort_order, $order_img) + = $this->_getSortingUrlParams( + $sort_direction, $single_sort_order, $index + ); + } else { + $single_sort_order .= strtoupper($sort_direction[$index]); + } + } + if ($current_name == $name_to_use_in_sort && $is_in_sort) { + // We need to generate the arrow button and related html + list($sort_order, $order_img) = $this->_getSortingUrlParams( + $sort_direction, $sort_order, $index + ); + $order_img .= " " . ($index + 1) . ""; + } else { + $sort_order .= strtoupper($sort_direction[$index]); + } + // Separate columns by a comma + $sort_order .= ", "; + + unset($name_to_use_in_sort); + } + // remove the comma from the last column name in the newly + // constructed clause + $sort_order = mb_substr( + $sort_order, + 0, + mb_strlen($sort_order) - 2 + ); + if (empty($order_img)) { + $order_img = ''; + } + return array($single_sort_order, $sort_order, $order_img); + } + + /** + * Check whether the column is sorted + * + * @param array $sort_expression sort expression + * @param array $sort_expression_nodirection sort expression without direction + * @param string $sort_tbl the table name + * @param string $name_to_use_in_sort the sorting column name + * + * @return boolean $is_in_sort the column sorted or not + * + * @access private + * + * @see _getTableHeaders() + */ + private function _isInSorted( + array $sort_expression, array $sort_expression_nodirection, $sort_tbl, + $name_to_use_in_sort + ) { + + $index_in_expression = 0; + + foreach ($sort_expression_nodirection as $index => $clause) { + if (mb_strpos($clause, '.') !== false) { + $fragments = explode('.', $clause); + $clause2 = $fragments[0] . "." . str_replace('`', '', $fragments[1]); + } else { + $clause2 = $sort_tbl . str_replace('`', '', $clause); + } + if ($clause2 === $sort_tbl . $name_to_use_in_sort) { + $index_in_expression = $index; + break; + } + } + if (empty($sort_expression[$index_in_expression])) { + $is_in_sort = false; + } else { + // Field name may be preceded by a space, or any number + // of characters followed by a dot (tablename.fieldname) + // so do a direct comparison for the sort expression; + // this avoids problems with queries like + // "SELECT id, count(id)..." and clicking to sort + // on id or on count(id). + // Another query to test this: + // SELECT p.*, FROM_UNIXTIME(p.temps) FROM mytable AS p + // (and try clicking on each column's header twice) + $noSortTable = empty($sort_tbl) || mb_strpos( + $sort_expression_nodirection[$index_in_expression], $sort_tbl + ) === false; + $noOpenParenthesis = mb_strpos( + $sort_expression_nodirection[$index_in_expression], '(' + ) === false; + if (! empty($sort_tbl) && $noSortTable && $noOpenParenthesis) { + $new_sort_expression_nodirection = $sort_tbl + . $sort_expression_nodirection[$index_in_expression]; + } else { + $new_sort_expression_nodirection + = $sort_expression_nodirection[$index_in_expression]; + } + + //Back quotes are removed in next comparison, so remove them from value + //to compare. + $name_to_use_in_sort = str_replace('`', '', $name_to_use_in_sort); + + $is_in_sort = false; + $sort_name = str_replace('`', '', $sort_tbl) . $name_to_use_in_sort; + + if ($sort_name == str_replace('`', '', $new_sort_expression_nodirection) + || $sort_name == str_replace('`', '', $sort_expression_nodirection[$index_in_expression]) + ) { + $is_in_sort = true; + } + } + + return $is_in_sort; + + } // end of the '_isInSorted()' function + + + /** + * Get sort url parameters - sort order and order image + * + * @param array $sort_direction the sort direction + * @param string $sort_order the sorting order + * @param integer $index the index of sort direction array. + * + * @return array 2 element array - $sort_order, $order_img + * + * @access private + * + * @see _getSingleAndMultiSortUrls() + */ + private function _getSortingUrlParams(array $sort_direction, $sort_order, $index) + { + if (strtoupper(trim($sort_direction[$index])) == self::DESCENDING_SORT_DIR) { + $sort_order .= ' ASC'; + $order_img = ' ' . Util::getImage( + 's_desc', __('Descending'), + array('class' => "soimg", 'title' => '') + ); + $order_img .= ' ' . Util::getImage( + 's_asc', __('Ascending'), + array('class' => "soimg hide", 'title' => '') + ); + } else { + $sort_order .= ' DESC'; + $order_img = ' ' . Util::getImage( + 's_asc', __('Ascending'), + array('class' => "soimg", 'title' => '') + ); + $order_img .= ' ' . Util::getImage( + 's_desc', __('Descending'), + array('class' => "soimg hide", 'title' => '') + ); + } + return array($sort_order, $order_img); + } // end of the '_getSortingUrlParams()' function + + + /** + * Get sort order link + * + * @param string $order_img the sort order image + * @param array $fields_meta set of field properties + * @param string $order_url the url for sort + * @param string $multi_order_url the url for sort + * + * @return string the sort order link + * + * @access private + * + * @see _getTableHeaders() + */ + private function _getSortOrderLink( + $order_img, $fields_meta, $order_url, $multi_order_url + ) { + $order_link_params = array( + 'class' => 'sortlink' + ); + + $order_link_content = htmlspecialchars($fields_meta->name); + $inner_link_content = $order_link_content . $order_img + . ''; + + return Util::linkOrButton( + $order_url, $inner_link_content, $order_link_params + ); + + } // end of the '_getSortOrderLink()' function + + /** + * Check if the column contains numeric data. If yes, then set the + * column header's alignment right + * + * @param array $fields_meta set of field properties + * @param array &$th_class array containing classes + * + * @return void + * + * @see _getDraggableClassForSortableColumns() + */ + private function _getClassForNumericColumnType($fields_meta, array &$th_class) + { + if (preg_match( + '@int|decimal|float|double|real|bit|boolean|serial@i', + $fields_meta->type + )) { + $th_class[] = 'right'; + } + } + + /** + * Prepare columns to draggable effect for sortable columns + * + * @param boolean $col_visib the column is visible (false) + * array the column is not visible (string array) + * @param string $col_visib_j element of $col_visib array + * @param array $fields_meta set of field properties + * @param string $order_link the order link + * @param string $comments the comment for the column + * + * @return string $draggable_html html content + * + * @access private + * + * @see _getTableHeaders() + */ + private function _getDraggableClassForSortableColumns( + $col_visib, $col_visib_j, $fields_meta, + $order_link, $comments + ) { + + $draggable_html = '_getClassForNumericColumnType($fields_meta, $th_class); + if ($col_visib && !$col_visib_j) { + $th_class[] = 'hide'; + } + + $th_class[] = 'column_heading'; + if ($GLOBALS['cfg']['BrowsePointerEnable'] == true) { + $th_class[] = 'pointer'; + } + + if ($GLOBALS['cfg']['BrowseMarkerEnable'] == true) { + $th_class[] = 'marker'; + } + + $draggable_html .= ' class="' . implode(' ', $th_class) . '"'; + + $draggable_html .= ' data-column="' . htmlspecialchars($fields_meta->name) + . '">' . $order_link . $comments . ''; + + return $draggable_html; + + } // end of the '_getDraggableClassForSortableColumns()' function + + + /** + * Prepare columns to draggable effect for non sortable columns + * + * @param boolean $col_visib the column is visible (false) + * array the column is not visible (string array) + * @param string $col_visib_j element of $col_visib array + * @param boolean $condition_field whether to add CSS class condition + * @param array $fields_meta set of field properties + * @param string $comments the comment for the column + * + * @return string $draggable_html html content + * + * @access private + * + * @see _getTableHeaders() + */ + private function _getDraggableClassForNonSortableColumns( + $col_visib, $col_visib_j, $condition_field, + $fields_meta, $comments + ) { + + $draggable_html = '_getClassForNumericColumnType($fields_meta, $th_class); + if ($col_visib && !$col_visib_j) { + $th_class[] = 'hide'; + } + + if ($condition_field) { + $th_class[] = 'condition'; + } + + $draggable_html .= ' class="' . implode(' ', $th_class) . '"'; + + $draggable_html .= ' data-column="' + . htmlspecialchars($fields_meta->name) . '">'; + + $draggable_html .= htmlspecialchars($fields_meta->name); + + $draggable_html .= "\n" . $comments . ''; + + return $draggable_html; + + } // end of the '_getDraggableClassForNonSortableColumns()' function + + + /** + * Prepare column to show at right side - check boxes or empty column + * + * @param array &$displayParts which elements to display + * @param string $full_or_partial_text_link full/partial link or text button + * @param string $colspan column span of table header + * + * @return string html content + * + * @access private + * + * @see _getTableHeaders() + */ + private function _getColumnAtRightSide( + array &$displayParts, $full_or_partial_text_link, $colspan + ) { + + $right_column_html = ''; + $display_params = $this->__get('display_params'); + + // Displays the needed checkboxes at the right + // column of the result table header if possible and required... + if ((($GLOBALS['cfg']['RowActionLinks'] == self::POSITION_RIGHT) + || ($GLOBALS['cfg']['RowActionLinks'] == self::POSITION_BOTH)) + && (($displayParts['edit_lnk'] != self::NO_EDIT_OR_DELETE) + || ($displayParts['del_lnk'] != self::NO_EDIT_OR_DELETE)) + && ($displayParts['text_btn'] == '1') + ) { + + $display_params['emptyafter'] + = (($displayParts['edit_lnk'] != self::NO_EDIT_OR_DELETE) + && ($displayParts['del_lnk'] != self::NO_EDIT_OR_DELETE)) ? 4 : 1; + + $right_column_html .= "\n" + . '' + . $full_or_partial_text_link + . ''; + + } elseif ((($GLOBALS['cfg']['RowActionLinks'] == self::POSITION_LEFT) + || ($GLOBALS['cfg']['RowActionLinks'] == self::POSITION_BOTH)) + && (($displayParts['edit_lnk'] == self::NO_EDIT_OR_DELETE) + && ($displayParts['del_lnk'] == self::NO_EDIT_OR_DELETE)) + && (! isset($GLOBALS['is_header_sent']) || ! $GLOBALS['is_header_sent']) + ) { + // ... elseif no button, displays empty columns if required + // (unless coming from Browse mode print view) + + $display_params['emptyafter'] + = (($displayParts['edit_lnk'] != self::NO_EDIT_OR_DELETE) + && ($displayParts['del_lnk'] != self::NO_EDIT_OR_DELETE)) ? 4 : 1; + + $right_column_html .= "\n" . ''; + } + + $this->__set('display_params', $display_params); + + return $right_column_html; + + } // end of the '_getColumnAtRightSide()' function + + + /** + * Prepares the display for a value + * + * @param string $class class of table cell + * @param bool $conditionField whether to add CSS class condition + * @param string $value value to display + * + * @return string the td + * + * @access private + * + * @see _getDataCellForGeometryColumns(), + * _getDataCellForNonNumericColumns() + */ + private function _buildValueDisplay($class, $conditionField, $value) + { + return Template::get('display/results/value_display')->render([ + 'class' => $class, + 'condition_field' => $conditionField, + 'value' => $value, + ]); + } + + /** + * Prepares the display for a null value + * + * @param string $class class of table cell + * @param bool $conditionField whether to add CSS class condition + * @param object $meta the meta-information about this field + * @param string $align cell alignment + * + * @return string the td + * + * @access private + * + * @see _getDataCellForNumericColumns(), + * _getDataCellForGeometryColumns(), + * _getDataCellForNonNumericColumns() + */ + private function _buildNullDisplay($class, $conditionField, $meta, $align = '') + { + $classes = $this->_addClass($class, $conditionField, $meta, ''); + + return Template::get('display/results/null_display')->render([ + 'align' => $align, + 'meta' => $meta, + 'classes' => $classes, + ]); + } + + /** + * Prepares the display for an empty value + * + * @param string $class class of table cell + * @param bool $conditionField whether to add CSS class condition + * @param object $meta the meta-information about this field + * @param string $align cell alignment + * + * @return string the td + * + * @access private + * + * @see _getDataCellForNumericColumns(), + * _getDataCellForGeometryColumns(), + * _getDataCellForNonNumericColumns() + */ + private function _buildEmptyDisplay($class, $conditionField, $meta, $align = '') + { + $classes = $this->_addClass($class, $conditionField, $meta, 'nowrap'); + + return Template::get('display/results/empty_display')->render([ + 'align' => $align, + 'classes' => $classes, + ]); + } + + /** + * Adds the relevant classes. + * + * @param string $class class of table cell + * @param bool $condition_field whether to add CSS class + * condition + * @param object $meta the meta-information about the + * field + * @param string $nowrap avoid wrapping + * @param bool $is_field_truncated is field truncated (display ...) + * @param object|string $transformation_plugin transformation plugin. + * Can also be the default function: + * Core::mimeDefaultFunction + * @param string $default_function default transformation function + * + * @return string the list of classes + * + * @access private + * + * @see _buildNullDisplay(), _getRowData() + */ + private function _addClass( + $class, $condition_field, $meta, $nowrap, $is_field_truncated = false, + $transformation_plugin = '', $default_function = '' + ) { + $classes = array( + $class, + $nowrap, + ); + + if (isset($meta->mimetype)) { + $classes[] = preg_replace('/\//', '_', $meta->mimetype); + } + + if ($condition_field) { + $classes[] = 'condition'; + } + + if ($is_field_truncated) { + $classes[] = 'truncated'; + } + + $mime_map = $this->__get('mime_map'); + $orgFullColName = $this->__get('db') . '.' . $meta->orgtable + . '.' . $meta->orgname; + if ($transformation_plugin != $default_function + || !empty($mime_map[$orgFullColName]['input_transformation']) + ) { + $classes[] = 'transformed'; + } + + // Define classes to be added to this data field based on the type of data + $matches = array( + 'enum' => 'enum', + 'set' => 'set', + 'binary' => 'hex', + ); + + foreach ($matches as $key => $value) { + if (mb_strpos($meta->flags, $key) !== false) { + $classes[] = $value; + } + } + + if (mb_strpos($meta->type, 'bit') !== false) { + $classes[] = 'bit'; + } + + return implode(' ', $classes); + } // end of the '_addClass()' function + + /** + * Prepare the body of the results table + * + * @param integer &$dt_result the link id associated to the query + * which results have to be displayed which + * results have to be displayed + * @param array &$displayParts which elements to display + * @param array $map the list of relations + * @param array $analyzed_sql_results analyzed sql results + * @param boolean $is_limited_display with limited operations or not + * + * @return string $table_body_html html content + * + * @global array $row current row data + * + * @access private + * + * @see getTable() + */ + private function _getTableBody( + &$dt_result, array &$displayParts, array $map, array $analyzed_sql_results, + $is_limited_display = false + ) { + + global $row; // mostly because of browser transformations, + // to make the row-data accessible in a plugin + + $table_body_html = ''; + + // query without conditions to shorten URLs when needed, 200 is just + // guess, it should depend on remaining URL length + $url_sql_query = $this->_getUrlSqlQuery($analyzed_sql_results); + + $display_params = $this->__get('display_params'); + + if (! is_array($map)) { + $map = array(); + } + + $row_no = 0; + $display_params['edit'] = array(); + $display_params['copy'] = array(); + $display_params['delete'] = array(); + $display_params['data'] = array(); + $display_params['row_delete'] = array(); + $this->__set('display_params', $display_params); + + // name of the class added to all grid editable elements; + // if we don't have all the columns of a unique key in the result set, + // do not permit grid editing + if ($is_limited_display || ! $this->__get('editable')) { + $grid_edit_class = ''; + } else { + switch ($GLOBALS['cfg']['GridEditing']) { + case 'double-click': + // trying to reduce generated HTML by using shorter + // classes like click1 and click2 + $grid_edit_class = 'grid_edit click2'; + break; + case 'click': + $grid_edit_class = 'grid_edit click1'; + break; + default: // 'disabled' + $grid_edit_class = ''; + break; + } + } + + // prepare to get the column order, if available + list($col_order, $col_visib) = $this->_getColumnParams( + $analyzed_sql_results + ); + + // Correction University of Virginia 19991216 in the while below + // Previous code assumed that all tables have keys, specifically that + // the phpMyAdmin GUI should support row delete/edit only for such + // tables. + // Although always using keys is arguably the prescribed way of + // defining a relational table, it is not required. This will in + // particular be violated by the novice. + // We want to encourage phpMyAdmin usage by such novices. So the code + // below has been changed to conditionally work as before when the + // table being displayed has one or more keys; but to display + // delete/edit options correctly for tables without keys. + + $whereClauseMap = $this->__get('whereClauseMap'); + while ($row = $GLOBALS['dbi']->fetchRow($dt_result)) { + + // add repeating headers + if ((($row_no != 0) && ($_SESSION['tmpval']['repeat_cells'] != 0)) + && !($row_no % $_SESSION['tmpval']['repeat_cells']) + ) { + $table_body_html .= $this->_getRepeatingHeaders( + $display_params + ); + } + + $tr_class = array(); + if ($GLOBALS['cfg']['BrowsePointerEnable'] != true) { + $tr_class[] = 'nopointer'; + } + if ($GLOBALS['cfg']['BrowseMarkerEnable'] != true) { + $tr_class[] = 'nomarker'; + } + + // pointer code part + $classes = (empty($tr_class) ? ' ' : 'class="' . implode(' ', $tr_class) . '"'); + $table_body_html .= ''; + + // 1. Prepares the row + + // In print view these variable needs to be initialized + $del_url = $del_str = $edit_anchor_class + = $edit_str = $js_conf = $copy_url = $copy_str = $edit_url = null; + + // 1.2 Defines the URLs for the modify/delete link(s) + + if (($displayParts['edit_lnk'] != self::NO_EDIT_OR_DELETE) + || ($displayParts['del_lnk'] != self::NO_EDIT_OR_DELETE) + ) { + + // Results from a "SELECT" statement -> builds the + // WHERE clause to use in links (a unique key if possible) + /** + * @todo $where_clause could be empty, for example a table + * with only one field and it's a BLOB; in this case, + * avoid to display the delete and edit links + */ + list($where_clause, $clause_is_unique, $condition_array) + = Util::getUniqueCondition( + $dt_result, // handle + $this->__get('fields_cnt'), // fields_cnt + $this->__get('fields_meta'), // fields_meta + $row, // row + false, // force_unique + $this->__get('table'), // restrict_to_table + $analyzed_sql_results // analyzed_sql_results + ); + $whereClauseMap[$row_no][$this->__get('table')] = $where_clause; + $this->__set('whereClauseMap', $whereClauseMap); + + $where_clause_html = htmlspecialchars($where_clause); + + // 1.2.1 Modify link(s) - update row case + if ($displayParts['edit_lnk'] == self::UPDATE_ROW) { + + list($edit_url, $copy_url, $edit_str, $copy_str, + $edit_anchor_class) + = $this->_getModifiedLinks( + $where_clause, + $clause_is_unique, $url_sql_query + ); + + } // end if (1.2.1) + + // 1.2.2 Delete/Kill link(s) + list($del_url, $del_str, $js_conf) + = $this->_getDeleteAndKillLinks( + $where_clause, $clause_is_unique, + $url_sql_query, $displayParts['del_lnk'], + $row + ); + + // 1.3 Displays the links at left if required + if (($GLOBALS['cfg']['RowActionLinks'] == self::POSITION_LEFT) + || ($GLOBALS['cfg']['RowActionLinks'] == self::POSITION_BOTH) + ) { + + $table_body_html .= $this->_getPlacedLinks( + self::POSITION_LEFT, $del_url, $displayParts, $row_no, + $where_clause, $where_clause_html, $condition_array, + $edit_url, $copy_url, $edit_anchor_class, + $edit_str, $copy_str, $del_str, $js_conf + ); + + } elseif ($GLOBALS['cfg']['RowActionLinks'] == self::POSITION_NONE) { + + $table_body_html .= $this->_getPlacedLinks( + self::POSITION_NONE, $del_url, $displayParts, $row_no, + $where_clause, $where_clause_html, $condition_array, + $edit_url, $copy_url, $edit_anchor_class, + $edit_str, $copy_str, $del_str, $js_conf + ); + + } // end if (1.3) + } // end if (1) + + // 2. Displays the rows' values + if (is_null($this->__get('mime_map'))) { + $this->_setMimeMap(); + } + $table_body_html .= $this->_getRowValues( + $dt_result, + $row, + $row_no, + $col_order, + $map, + $grid_edit_class, + $col_visib, + $url_sql_query, + $analyzed_sql_results + ); + + // 3. Displays the modify/delete links on the right if required + if (($displayParts['edit_lnk'] != self::NO_EDIT_OR_DELETE) + || ($displayParts['del_lnk'] != self::NO_EDIT_OR_DELETE) + ) { + if (($GLOBALS['cfg']['RowActionLinks'] == self::POSITION_RIGHT) + || ($GLOBALS['cfg']['RowActionLinks'] == self::POSITION_BOTH) + ) { + + $table_body_html .= $this->_getPlacedLinks( + self::POSITION_RIGHT, $del_url, $displayParts, $row_no, + $where_clause, $where_clause_html, $condition_array, + $edit_url, $copy_url, $edit_anchor_class, + $edit_str, $copy_str, $del_str, $js_conf + ); + + } + } // end if (3) + + $table_body_html .= ''; + $table_body_html .= "\n"; + $row_no++; + + } // end while + + return $table_body_html; + + } // end of the '_getTableBody()' function + + /** + * Sets the MIME details of the columns in the results set + * + * @return void + */ + private function _setMimeMap() + { + $fields_meta = $this->__get('fields_meta'); + $mimeMap = array(); + $added = array(); + + for ($currentColumn = 0; + $currentColumn < $this->__get('fields_cnt'); + ++$currentColumn) { + + $meta = $fields_meta[$currentColumn]; + $orgFullTableName = $this->__get('db') . '.' . $meta->orgtable; + + if ($GLOBALS['cfgRelation']['commwork'] + && $GLOBALS['cfgRelation']['mimework'] + && $GLOBALS['cfg']['BrowseMIME'] + && ! $_SESSION['tmpval']['hide_transformation'] + && empty($added[$orgFullTableName]) + ) { + $mimeMap = array_merge( + $mimeMap, + Transformations::getMIME($this->__get('db'), $meta->orgtable, false, true) + ); + $added[$orgFullTableName] = true; + } + } + + // special browser transformation for some SHOW statements + if ($this->__get('is_show') + && ! $_SESSION['tmpval']['hide_transformation'] + ) { + preg_match( + '@^SHOW[[:space:]]+(VARIABLES|(FULL[[:space:]]+)?' + . 'PROCESSLIST|STATUS|TABLE|GRANTS|CREATE|LOGS|DATABASES|FIELDS' + . ')@i', + $this->__get('sql_query'), $which + ); + + if (isset($which[1])) { + $str = ' ' . strtoupper($which[1]); + $isShowProcessList = strpos($str, 'PROCESSLIST') > 0; + if ($isShowProcessList) { + $mimeMap['..Info'] = array( + 'mimetype' => 'Text_Plain', + 'transformation' => 'output/Text_Plain_Sql.php', + ); + } + + $isShowCreateTable = preg_match( + '@CREATE[[:space:]]+TABLE@i', $this->__get('sql_query') + ); + if ($isShowCreateTable) { + $mimeMap['..Create Table'] = array( + 'mimetype' => 'Text_Plain', + 'transformation' => 'output/Text_Plain_Sql.php', + ); + } + } + } + + $this->__set('mime_map', $mimeMap); + } + + /** + * Get the values for one data row + * + * @param integer &$dt_result the link id associated to + * the query which results + * have to be displayed which + * results have to be + * displayed + * @param array $row current row data + * @param integer $row_no the index of current row + * @param array|boolean $col_order the column order false when + * a property not found false + * when a property not found + * @param array $map the list of relations + * @param string $grid_edit_class the class for all editable + * columns + * @param boolean|array|string $col_visib column is visible(false); + * column isn't visible(string + * array) + * @param string $url_sql_query the analyzed sql query + * @param array $analyzed_sql_results analyzed sql results + * + * @return string $row_values_html html content + * + * @access private + * + * @see _getTableBody() + */ + private function _getRowValues( + &$dt_result, array $row, $row_no, $col_order, array $map, + $grid_edit_class, $col_visib, + $url_sql_query, array $analyzed_sql_results + ) { + $row_values_html = ''; + + // Following variable are needed for use in isset/empty or + // use with array indexes/safe use in foreach + $sql_query = $this->__get('sql_query'); + $fields_meta = $this->__get('fields_meta'); + $highlight_columns = $this->__get('highlight_columns'); + $mime_map = $this->__get('mime_map'); + + $row_info = $this->_getRowInfoForSpecialLinks($row, $col_order); + + $whereClauseMap = $this->__get('whereClauseMap'); + + $columnCount = $this->__get('fields_cnt'); + for ($currentColumn = 0; + $currentColumn < $columnCount; + ++$currentColumn) { + + // assign $i with appropriate column order + $i = $col_order ? $col_order[$currentColumn] : $currentColumn; + + $meta = $fields_meta[$i]; + $orgFullColName + = $this->__get('db') . '.' . $meta->orgtable . '.' . $meta->orgname; + + $not_null_class = $meta->not_null ? 'not_null' : ''; + $relation_class = isset($map[$meta->name]) ? 'relation' : ''; + $hide_class = ($col_visib && ! $col_visib[$currentColumn]) + ? 'hide' + : ''; + $grid_edit = $meta->orgtable != '' ? $grid_edit_class : ''; + + // handle datetime-related class, for grid editing + $field_type_class + = $this->_getClassForDateTimeRelatedFields($meta->type); + + $is_field_truncated = false; + // combine all the classes applicable to this column's value + $class = $this->_getClassesForColumn( + $grid_edit, $not_null_class, $relation_class, + $hide_class, $field_type_class + ); + + // See if this column should get highlight because it's used in the + // where-query. + $condition_field = (isset($highlight_columns) + && (isset($highlight_columns[$meta->name]) + || isset($highlight_columns[Util::backquote($meta->name)]))) + ? true + : false; + + // Wrap MIME-transformations. [MIME] + $default_function = [Core::class, 'mimeDefaultFunction']; // default_function + $transformation_plugin = $default_function; + $transform_options = array(); + + if ($GLOBALS['cfgRelation']['mimework'] + && $GLOBALS['cfg']['BrowseMIME'] + ) { + + if (isset($mime_map[$orgFullColName]['mimetype']) + && !empty($mime_map[$orgFullColName]['transformation']) + ) { + + $file = $mime_map[$orgFullColName]['transformation']; + $include_file = 'libraries/classes/Plugins/Transformations/' . $file; + + if (@file_exists($include_file)) { + + $class_name = Transformations::getClassName($include_file); + if (class_exists($class_name)) { + // todo add $plugin_manager + $plugin_manager = null; + $transformation_plugin = new $class_name( + $plugin_manager + ); + + $transform_options = Transformations::getOptions( + isset( + $mime_map[$orgFullColName] + ['transformation_options'] + ) + ? $mime_map[$orgFullColName] + ['transformation_options'] + : '' + ); + + $meta->mimetype = str_replace( + '_', '/', + $mime_map[$orgFullColName]['mimetype'] + ); + } + + } // end if file_exists + } // end if transformation is set + } // end if mime/transformation works. + + // Check whether the field needs to display with syntax highlighting + + $dbLower = mb_strtolower($this->__get('db')); + $tblLower = mb_strtolower($meta->orgtable); + $nameLower = mb_strtolower($meta->orgname); + if (! empty($this->transformation_info[$dbLower][$tblLower][$nameLower]) + && (trim($row[$i]) != '') + && ! $_SESSION['tmpval']['hide_transformation'] + ) { + include_once $this->transformation_info + [$dbLower][$tblLower][$nameLower][0]; + $transformation_plugin = new $this->transformation_info + [$dbLower][$tblLower][$nameLower][1](null); + + $transform_options = Transformations::getOptions( + isset($mime_map[$orgFullColName]['transformation_options']) + ? $mime_map[$orgFullColName]['transformation_options'] + : '' + ); + + $meta->mimetype = str_replace( + '_', '/', + $this->transformation_info[$dbLower] + [mb_strtolower($meta->orgtable)] + [mb_strtolower($meta->orgname)][2] + ); + + } + + // Check for the predefined fields need to show as link in schemas + include_once 'libraries/special_schema_links.inc.php'; + + if (isset($GLOBALS['special_schema_links']) + && (! empty($GLOBALS['special_schema_links'][$dbLower][$tblLower][$nameLower])) + ) { + + $linking_url = $this->_getSpecialLinkUrl( + $row[$i], $row_info, mb_strtolower($meta->orgname) + ); + $transformation_plugin = new Text_Plain_Link(); + + $transform_options = array( + 0 => $linking_url, + 2 => true + ); + + $meta->mimetype = str_replace( + '_', '/', + 'Text/Plain' + ); + + } + + /* + * The result set can have columns from more than one table, + * this is why we have to check for the unique conditions + * related to this table; however getUniqueCondition() is + * costly and does not need to be called if we already know + * the conditions for the current table. + */ + if (! isset($whereClauseMap[$row_no][$meta->orgtable])) { + $unique_conditions = Util::getUniqueCondition( + $dt_result, // handle + $this->__get('fields_cnt'), // fields_cnt + $this->__get('fields_meta'), // fields_meta + $row, // row + false, // force_unique + $meta->orgtable, // restrict_to_table + $analyzed_sql_results // analyzed_sql_results + ); + $whereClauseMap[$row_no][$meta->orgtable] = $unique_conditions[0]; + } + + $_url_params = array( + 'db' => $this->__get('db'), + 'table' => $meta->orgtable, + 'where_clause' => $whereClauseMap[$row_no][$meta->orgtable], + 'transform_key' => $meta->orgname + ); + + if (! empty($sql_query)) { + $_url_params['sql_query'] = $url_sql_query; + } + + $transform_options['wrapper_link'] = Url::getCommon($_url_params); + + $display_params = $this->__get('display_params'); + + // in some situations (issue 11406), numeric returns 1 + // even for a string type + // for decimal numeric is returning 1 + // have to improve logic + if (($meta->numeric == 1 && $meta->type != 'string') || $meta->type == 'real') { + // n u m e r i c + + $display_params['data'][$row_no][$i] + = $this->_getDataCellForNumericColumns( + $row[$i], + $class, + $condition_field, + $meta, + $map, + $is_field_truncated, + $analyzed_sql_results, + $transformation_plugin, + $default_function, + $transform_options + ); + + } elseif ($meta->type == self::GEOMETRY_FIELD) { + // g e o m e t r y + + // Remove 'grid_edit' from $class as we do not allow to + // inline-edit geometry data. + $class = str_replace('grid_edit', '', $class); + + $display_params['data'][$row_no][$i] + = $this->_getDataCellForGeometryColumns( + $row[$i], + $class, + $meta, + $map, + $_url_params, + $condition_field, + $transformation_plugin, + $default_function, + $transform_options, + $analyzed_sql_results + ); + + } else { + // n o t n u m e r i c + + $display_params['data'][$row_no][$i] + = $this->_getDataCellForNonNumericColumns( + $row[$i], + $class, + $meta, + $map, + $_url_params, + $condition_field, + $transformation_plugin, + $default_function, + $transform_options, + $is_field_truncated, + $analyzed_sql_results, + $dt_result, + $i + ); + + } + + // output stored cell + $row_values_html .= $display_params['data'][$row_no][$i]; + + if (isset($display_params['rowdata'][$i][$row_no])) { + $display_params['rowdata'][$i][$row_no] + .= $display_params['data'][$row_no][$i]; + } else { + $display_params['rowdata'][$i][$row_no] + = $display_params['data'][$row_no][$i]; + } + + $this->__set('display_params', $display_params); + + } // end for + + return $row_values_html; + + } // end of the '_getRowValues()' function + + /** + * Get link for display special schema links + * + * @param string $column_value column value + * @param array $row_info information about row + * @param string $field_name column name + * + * @return string generated link + */ + private function _getSpecialLinkUrl($column_value, array $row_info, $field_name) + { + + $linking_url_params = array(); + $link_relations = $GLOBALS['special_schema_links'] + [mb_strtolower($this->__get('db'))] + [mb_strtolower($this->__get('table'))] + [$field_name]; + + if (! is_array($link_relations['link_param'])) { + $linking_url_params[$link_relations['link_param']] = $column_value; + } else { + // Consider only the case of creating link for column field + // sql query that needs to be passed as url param + $sql = 'SELECT `' . $column_value . '` FROM `' + . $row_info[$link_relations['link_param'][1]] . '`.`' + . $row_info[$link_relations['link_param'][2]] . '`'; + $linking_url_params[$link_relations['link_param'][0]] = $sql; + } + + $divider = strpos($link_relations['default_page'], '?') ? '&' : '?'; + if (empty($link_relations['link_dependancy_params'])) { + return $link_relations['default_page'] + . Url::getCommonRaw($linking_url_params, $divider); + } + + foreach ($link_relations['link_dependancy_params'] as $new_param) { + + // If param_info is an array, set the key and value + // from that array + if (is_array($new_param['param_info'])) { + $linking_url_params[$new_param['param_info'][0]] + = $new_param['param_info'][1]; + continue; + } + + $linking_url_params[$new_param['param_info']] + = $row_info[mb_strtolower($new_param['column_name'])]; + + // Special case 1 - when executing routines, according + // to the type of the routine, url param changes + if (empty($row_info['routine_type'])) { + continue; + } + } + + return $link_relations['default_page'] + . Url::getCommonRaw($linking_url_params, $divider); + } + + + /** + * Prepare row information for display special links + * + * @param array $row current row data + * @param array|boolean $col_order the column order + * + * @return array $row_info associative array with column nama -> value + */ + private function _getRowInfoForSpecialLinks(array $row, $col_order) + { + + $row_info = array(); + $fields_meta = $this->__get('fields_meta'); + + for ($n = 0; $n < $this->__get('fields_cnt'); ++$n) { + $m = $col_order ? $col_order[$n] : $n; + $row_info[mb_strtolower($fields_meta[$m]->orgname)] + = $row[$m]; + } + + return $row_info; + + } + + /** + * Get url sql query without conditions to shorten URLs + * + * @param array $analyzed_sql_results analyzed sql results + * + * @return string $url_sql analyzed sql query + * + * @access private + * + * @see _getTableBody() + */ + private function _getUrlSqlQuery(array $analyzed_sql_results) + { + if (($analyzed_sql_results['querytype'] != 'SELECT') + || (mb_strlen($this->__get('sql_query')) < 200) + ) { + return $this->__get('sql_query'); + } + + $query = 'SELECT ' . Query::getClause( + $analyzed_sql_results['statement'], + $analyzed_sql_results['parser']->list, + 'SELECT' + ); + + $from_clause = Query::getClause( + $analyzed_sql_results['statement'], + $analyzed_sql_results['parser']->list, + 'FROM' + ); + + if (!empty($from_clause)) { + $query .= ' FROM ' . $from_clause; + } + + return $query; + + } // end of the '_getUrlSqlQuery()' function + + + /** + * Get column order and column visibility + * + * @param array $analyzed_sql_results analyzed sql results + * + * @return array 2 element array - $col_order, $col_visib + * + * @access private + * + * @see _getTableBody() + */ + private function _getColumnParams(array $analyzed_sql_results) + { + if ($this->_isSelect($analyzed_sql_results)) { + $pmatable = new Table($this->__get('table'), $this->__get('db')); + $col_order = $pmatable->getUiProp(Table::PROP_COLUMN_ORDER); + /* Validate the value */ + if ($col_order !== false) { + $fields_cnt = $this->__get('fields_cnt'); + foreach ($col_order as $value) { + if ($value >= $fields_cnt) { + $pmatable->removeUiProp(Table::PROP_COLUMN_ORDER); + $fields_cnt = false; + } + } + } + $col_visib = $pmatable->getUiProp(Table::PROP_COLUMN_VISIB); + } else { + $col_order = false; + $col_visib = false; + } + + return array($col_order, $col_visib); + } // end of the '_getColumnParams()' function + + + /** + * Get HTML for repeating headers + * + * @param array $display_params holds various display info + * + * @return string $header_html html content + * + * @access private + * + * @see _getTableBody() + */ + private function _getRepeatingHeaders( + array $display_params + ) { + $header_html = '' . "\n"; + + if ($display_params['emptypre'] > 0) { + + $header_html .= ' ' + . "\n" . '  ' . "\n"; + + } elseif ($GLOBALS['cfg']['RowActionLinks'] == self::POSITION_NONE) { + $header_html .= ' ' . "\n"; + } + + foreach ($display_params['desc'] as $val) { + $header_html .= $val; + } + + if ($display_params['emptyafter'] > 0) { + $header_html + .= ' ' + . "\n" . '  ' . "\n"; + } + $header_html .= '' . "\n"; + + return $header_html; + + } // end of the '_getRepeatingHeaders()' function + + + /** + * Get modified links + * + * @param string $where_clause the where clause of the sql + * @param boolean $clause_is_unique the unique condition of clause + * @param string $url_sql_query the analyzed sql query + * + * @return array 5 element array - $edit_url, $copy_url, + * $edit_str, $copy_str, $edit_anchor_class + * + * @access private + * + * @see _getTableBody() + */ + private function _getModifiedLinks( + $where_clause, $clause_is_unique, $url_sql_query + ) { + + $_url_params = array( + 'db' => $this->__get('db'), + 'table' => $this->__get('table'), + 'where_clause' => $where_clause, + 'clause_is_unique' => $clause_is_unique, + 'sql_query' => $url_sql_query, + 'goto' => 'sql.php', + ); + + $edit_url = 'tbl_change.php' + . Url::getCommon( + $_url_params + array('default_action' => 'update') + ); + + $copy_url = 'tbl_change.php' + . Url::getCommon( + $_url_params + array('default_action' => 'insert') + ); + + $edit_str = $this->_getActionLinkContent( + 'b_edit', __('Edit') + ); + $copy_str = $this->_getActionLinkContent( + 'b_insrow', __('Copy') + ); + + // Class definitions required for grid editing jQuery scripts + $edit_anchor_class = "edit_row_anchor"; + if ($clause_is_unique == 0) { + $edit_anchor_class .= ' nonunique'; + } + + return array($edit_url, $copy_url, $edit_str, $copy_str, $edit_anchor_class); + + } // end of the '_getModifiedLinks()' function + + + /** + * Get delete and kill links + * + * @param string $where_clause the where clause of the sql + * @param boolean $clause_is_unique the unique condition of clause + * @param string $url_sql_query the analyzed sql query + * @param string $del_lnk the delete link of current row + * @param array $row the current row + * + * @return array 3 element array + * $del_url, $del_str, $js_conf + * + * @access private + * + * @see _getTableBody() + */ + private function _getDeleteAndKillLinks( + $where_clause, $clause_is_unique, $url_sql_query, $del_lnk, array $row + ) { + + $goto = $this->__get('goto'); + + if ($del_lnk == self::DELETE_ROW) { // delete row case + + $_url_params = array( + 'db' => $this->__get('db'), + 'table' => $this->__get('table'), + 'sql_query' => $url_sql_query, + 'message_to_show' => __('The row has been deleted.'), + 'goto' => (empty($goto) ? 'tbl_sql.php' : $goto), + ); + + $lnk_goto = 'sql.php' . Url::getCommonRaw($_url_params); + + $del_query = 'DELETE FROM ' + . Util::backquote($this->__get('table')) + . ' WHERE ' . $where_clause . + ($clause_is_unique ? '' : ' LIMIT 1'); + + $_url_params = array( + 'db' => $this->__get('db'), + 'table' => $this->__get('table'), + 'sql_query' => $del_query, + 'message_to_show' => __('The row has been deleted.'), + 'goto' => $lnk_goto, + ); + $del_url = 'sql.php' . Url::getCommon($_url_params); + + $js_conf = 'DELETE FROM ' . Sanitize::jsFormat($this->__get('table')) + . ' WHERE ' . Sanitize::jsFormat($where_clause, false) + . ($clause_is_unique ? '' : ' LIMIT 1'); + + $del_str = $this->_getActionLinkContent('b_drop', __('Delete')); + + } elseif ($del_lnk == self::KILL_PROCESS) { // kill process case + + $_url_params = array( + 'db' => $this->__get('db'), + 'table' => $this->__get('table'), + 'sql_query' => $url_sql_query, + 'goto' => 'index.php', + ); + + $lnk_goto = 'sql.php' . Url::getCommonRaw($_url_params); + + $kill = $GLOBALS['dbi']->getKillQuery($row[0]); + + $_url_params = array( + 'db' => 'mysql', + 'sql_query' => $kill, + 'goto' => $lnk_goto, + ); + + $del_url = 'sql.php' . Url::getCommon($_url_params); + $js_conf = $kill; + $del_str = Util::getIcon( + 'b_drop', __('Kill') + ); + } else { + $del_url = $del_str = $js_conf = null; + } + + return array($del_url, $del_str, $js_conf); + + } // end of the '_getDeleteAndKillLinks()' function + + + /** + * Get content inside the table row action links (Edit/Copy/Delete) + * + * @param string $icon The name of the file to get + * @param string $display_text The text displaying after the image icon + * + * @return string + * + * @access private + * + * @see _getModifiedLinks(), _getDeleteAndKillLinks() + */ + private function _getActionLinkContent($icon, $display_text) + { + + $linkContent = ''; + + if (isset($GLOBALS['cfg']['RowActionType']) + && $GLOBALS['cfg']['RowActionType'] == self::ACTION_LINK_CONTENT_ICONS + ) { + + $linkContent .= '' + . Util::getImage( + $icon, $display_text + ) + . ''; + + } elseif (isset($GLOBALS['cfg']['RowActionType']) + && $GLOBALS['cfg']['RowActionType'] == self::ACTION_LINK_CONTENT_TEXT + ) { + + $linkContent .= '' . $display_text . ''; + + } else { + + $linkContent .= Util::getIcon( + $icon, $display_text + ); + + } + + return $linkContent; + + } + + + /** + * Prepare placed links + * + * @param string $dir the direction of links should place + * @param string $del_url the url for delete row + * @param array $displayParts which elements to display + * @param integer $row_no the index of current row + * @param string $where_clause the where clause of the sql + * @param string $where_clause_html the html encoded where clause + * @param array $condition_array array of keys (primary, unique, condition) + * @param string $edit_url the url for edit row + * @param string $copy_url the url for copy row + * @param string $edit_anchor_class the class for html element for edit + * @param string $edit_str the label for edit row + * @param string $copy_str the label for copy row + * @param string $del_str the label for delete row + * @param string $js_conf text for the JS confirmation + * + * @return string html content + * + * @access private + * + * @see _getTableBody() + */ + private function _getPlacedLinks( + $dir, $del_url, array $displayParts, $row_no, $where_clause, $where_clause_html, + array $condition_array, $edit_url, $copy_url, + $edit_anchor_class, $edit_str, $copy_str, $del_str, $js_conf + ) { + + if (! isset($js_conf)) { + $js_conf = ''; + } + + return $this->_getCheckboxAndLinks( + $dir, $del_url, $displayParts, + $row_no, $where_clause, $where_clause_html, $condition_array, + $edit_url, $copy_url, $edit_anchor_class, + $edit_str, $copy_str, $del_str, $js_conf + ); + + } // end of the '_getPlacedLinks()' function + + + /** + * Get the combined classes for a column + * + * @param string $grid_edit_class the class for all editable columns + * @param string $not_null_class the class for not null columns + * @param string $relation_class the class for relations in a column + * @param string $hide_class the class for visibility of a column + * @param string $field_type_class the class related to type of the field + * + * @return string $class the combined classes + * + * @access private + * + * @see _getTableBody() + */ + private function _getClassesForColumn( + $grid_edit_class, $not_null_class, $relation_class, + $hide_class, $field_type_class + ) { + $class = 'data ' . $grid_edit_class . ' ' . $not_null_class . ' ' + . $relation_class . ' ' . $hide_class . ' ' . $field_type_class; + + return $class; + + } // end of the '_getClassesForColumn()' function + + + /** + * Get class for datetime related fields + * + * @param string $type the type of the column field + * + * @return string $field_type_class the class for the column + * + * @access private + * + * @see _getTableBody() + */ + private function _getClassForDateTimeRelatedFields($type) + { + if ((substr($type, 0, 9) == self::TIMESTAMP_FIELD) + || ($type == self::DATETIME_FIELD) + ) { + $field_type_class = 'datetimefield'; + } elseif ($type == self::DATE_FIELD) { + $field_type_class = 'datefield'; + } elseif ($type == self::TIME_FIELD) { + $field_type_class = 'timefield'; + } elseif ($type == self::STRING_FIELD) { + $field_type_class = 'text'; + } else { + $field_type_class = ''; + } + return $field_type_class; + } // end of the '_getClassForDateTimeRelatedFields()' function + + + /** + * Prepare data cell for numeric type fields + * + * @param string $column the column's value + * @param string $class the html class for column + * @param boolean $condition_field the column should highlighted + * or not + * @param object $meta the meta-information about this + * field + * @param array $map the list of relations + * @param boolean $is_field_truncated the condition for blob data + * replacements + * @param array $analyzed_sql_results the analyzed query + * @param object|string $transformation_plugin the name of transformation plugin + * @param string $default_function the default transformation + * function + * @param array $transform_options the transformation parameters + * + * @return string $cell the prepared cell, html content + * + * @access private + * + * @see _getTableBody() + */ + private function _getDataCellForNumericColumns( + $column, $class, $condition_field, $meta, array $map, $is_field_truncated, + array $analyzed_sql_results, $transformation_plugin, $default_function, + array $transform_options + ) { + + if (! isset($column) || is_null($column)) { + + $cell = $this->_buildNullDisplay( + 'right ' . $class, $condition_field, $meta, '' + ); + + } elseif ($column != '') { + + $nowrap = ' nowrap'; + $where_comparison = ' = ' . $column; + + $cell = $this->_getRowData( + 'right ' . $class, $condition_field, + $analyzed_sql_results, $meta, $map, $column, $column, + $transformation_plugin, $default_function, $nowrap, + $where_comparison, $transform_options, + $is_field_truncated, '' + ); + } else { + + $cell = $this->_buildEmptyDisplay( + 'right ' . $class, $condition_field, $meta, '' + ); + } + + return $cell; + + } // end of the '_getDataCellForNumericColumns()' function + + + /** + * Get data cell for geometry type fields + * + * @param string $column the relevant column in data row + * @param string $class the html class for column + * @param object $meta the meta-information about + * this field + * @param array $map the list of relations + * @param array $_url_params the parameters for generate url + * @param boolean $condition_field the column should highlighted + * or not + * @param object|string $transformation_plugin the name of transformation + * function + * @param string $default_function the default transformation + * function + * @param string $transform_options the transformation parameters + * @param array $analyzed_sql_results the analyzed query + * + * @return string $cell the prepared data cell, html content + * + * @access private + * + * @see _getTableBody() + */ + private function _getDataCellForGeometryColumns( + $column, $class, $meta, array $map, array $_url_params, $condition_field, + $transformation_plugin, $default_function, $transform_options, + array $analyzed_sql_results + ) { + if (! isset($column) || is_null($column)) { + $cell = $this->_buildNullDisplay($class, $condition_field, $meta); + return $cell; + } + + if ($column == '') { + $cell = $this->_buildEmptyDisplay($class, $condition_field, $meta); + return $cell; + } + + // Display as [GEOMETRY - (size)] + if ($_SESSION['tmpval']['geoOption'] == self::GEOMETRY_DISP_GEOM) { + $geometry_text = $this->_handleNonPrintableContents( + strtoupper(self::GEOMETRY_FIELD), $column, $transformation_plugin, + $transform_options, $default_function, $meta, $_url_params + ); + + $cell = $this->_buildValueDisplay( + $class, $condition_field, $geometry_text + ); + return $cell; + } + + if ($_SESSION['tmpval']['geoOption'] == self::GEOMETRY_DISP_WKT) { + // Prepare in Well Known Text(WKT) format. + $where_comparison = ' = ' . $column; + + // Convert to WKT format + $wktval = Util::asWKT($column); + list( + $is_field_truncated, + $displayedColumn, + // skip 3rd param + ) = $this->_getPartialText($wktval); + + $cell = $this->_getRowData( + $class, $condition_field, $analyzed_sql_results, $meta, $map, + $wktval, $displayedColumn, $transformation_plugin, + $default_function, '', + $where_comparison, $transform_options, + $is_field_truncated, '' + ); + return $cell; + } + + // Prepare in Well Known Binary (WKB) format. + + if ($_SESSION['tmpval']['display_binary']) { + $where_comparison = ' = ' . $column; + + $wkbval = substr(bin2hex($column), 8); + list( + $is_field_truncated, + $displayedColumn, + // skip 3rd param + ) = $this->_getPartialText($wkbval); + + $cell = $this->_getRowData( + $class, $condition_field, + $analyzed_sql_results, $meta, $map, $wkbval, $displayedColumn, + $transformation_plugin, $default_function, '', + $where_comparison, $transform_options, + $is_field_truncated, '' + ); + return $cell; + } + + $wkbval = $this->_handleNonPrintableContents( + self::BINARY_FIELD, $column, $transformation_plugin, + $transform_options, $default_function, $meta, + $_url_params + ); + + $cell = $this->_buildValueDisplay( + $class, $condition_field, $wkbval + ); + + return $cell; + + } // end of the '_getDataCellForGeometryColumns()' function + + + /** + * Get data cell for non numeric type fields + * + * @param string $column the relevant column in data row + * @param string $class the html class for column + * @param object $meta the meta-information about + * the field + * @param array $map the list of relations + * @param array $_url_params the parameters for generate + * url + * @param boolean $condition_field the column should highlighted + * or not + * @param object|string $transformation_plugin the name of transformation + * function + * @param string $default_function the default transformation + * function + * @param string $transform_options the transformation parameters + * @param boolean $is_field_truncated is data truncated due to + * LimitChars + * @param array $analyzed_sql_results the analyzed query + * @param integer &$dt_result the link id associated to + * the query which results + * have to be displayed + * @param integer $col_index the column index + * + * @return string $cell the prepared data cell, html content + * + * @access private + * + * @see _getTableBody() + */ + private function _getDataCellForNonNumericColumns( + $column, $class, $meta, array $map, array $_url_params, $condition_field, + $transformation_plugin, $default_function, $transform_options, + $is_field_truncated, array $analyzed_sql_results, &$dt_result, $col_index + ) { + $original_length = 0; + + $is_analyse = $this->__get('is_analyse'); + $field_flags = $GLOBALS['dbi']->fieldFlags($dt_result, $col_index); + + $bIsText = gettype($transformation_plugin) === 'object' + && strpos($transformation_plugin->getMIMEtype(), 'Text') + === false; + + // disable inline grid editing + // if binary fields are protected + // or transformation plugin is of non text type + // such as image + if ((stristr($field_flags, self::BINARY_FIELD) + && ($GLOBALS['cfg']['ProtectBinary'] === 'all' + || ($GLOBALS['cfg']['ProtectBinary'] === 'noblob' + && !stristr($meta->type, self::BLOB_FIELD)) + || ($GLOBALS['cfg']['ProtectBinary'] === 'blob' + && stristr($meta->type, self::BLOB_FIELD)))) + || $bIsText + ) { + $class = str_replace('grid_edit', '', $class); + } + + if (! isset($column) || is_null($column)) { + $cell = $this->_buildNullDisplay($class, $condition_field, $meta); + return $cell; + } + + if ($column == '') { + $cell = $this->_buildEmptyDisplay($class, $condition_field, $meta); + return $cell; + } + + // Cut all fields to $GLOBALS['cfg']['LimitChars'] + // (unless it's a link-type transformation or binary) + $displayedColumn = $column; + if (!(gettype($transformation_plugin) === "object" + && strpos($transformation_plugin->getName(), 'Link') !== false) + && !stristr($field_flags, self::BINARY_FIELD) + ) { + list( + $is_field_truncated, + $column, + $original_length + ) = $this->_getPartialText($column); + } + + $formatted = false; + if (isset($meta->_type) && $meta->_type === MYSQLI_TYPE_BIT) { + + $displayedColumn = Util::printableBitValue( + $displayedColumn, $meta->length + ); + + // some results of PROCEDURE ANALYSE() are reported as + // being BINARY but they are quite readable, + // so don't treat them as BINARY + } elseif (stristr($field_flags, self::BINARY_FIELD) + && !(isset($is_analyse) && $is_analyse) + ) { + // we show the BINARY or BLOB message and field's size + // (or maybe use a transformation) + $binary_or_blob = self::BLOB_FIELD; + if ($meta->type === self::STRING_FIELD) { + $binary_or_blob = self::BINARY_FIELD; + } + $displayedColumn = $this->_handleNonPrintableContents( + $binary_or_blob, $displayedColumn, $transformation_plugin, + $transform_options, $default_function, + $meta, $_url_params, $is_field_truncated + ); + $class = $this->_addClass( + $class, $condition_field, $meta, '', + $is_field_truncated, $transformation_plugin, $default_function + ); + $result = strip_tags($column); + // disable inline grid editing + // if binary or blob data is not shown + if (stristr($result, $binary_or_blob)) { + $class = str_replace('grid_edit', '', $class); + } + $formatted = true; + } + + if ($formatted) { + $cell = $this->_buildValueDisplay( + $class, $condition_field, $displayedColumn + ); + return $cell; + } + + // transform functions may enable no-wrapping: + $function_nowrap = 'applyTransformationNoWrap'; + + $bool_nowrap = (($default_function != $transformation_plugin) + && function_exists($transformation_plugin->$function_nowrap())) + ? $transformation_plugin->$function_nowrap($transform_options) + : false; + + // do not wrap if date field type + $nowrap = (preg_match('@DATE|TIME@i', $meta->type) + || $bool_nowrap) ? ' nowrap' : ''; + + $where_comparison = ' = \'' + . $GLOBALS['dbi']->escapeString($column) + . '\''; + + $cell = $this->_getRowData( + $class, $condition_field, + $analyzed_sql_results, $meta, $map, $column, $displayedColumn, + $transformation_plugin, $default_function, $nowrap, + $where_comparison, $transform_options, + $is_field_truncated, $original_length + ); + + return $cell; + + } // end of the '_getDataCellForNonNumericColumns()' function + + /** + * Checks the posted options for viewing query results + * and sets appropriate values in the session. + * + * @todo make maximum remembered queries configurable + * @todo move/split into SQL class!? + * @todo currently this is called twice unnecessary + * @todo ignore LIMIT and ORDER in query!? + * + * @return void + * + * @access public + * + * @see sql.php file + */ + public function setConfigParamsForDisplayTable() + { + + $sql_md5 = md5($this->__get('sql_query')); + $query = array(); + if (isset($_SESSION['tmpval']['query'][$sql_md5])) { + $query = $_SESSION['tmpval']['query'][$sql_md5]; + } + + $query['sql'] = $this->__get('sql_query'); + + if (empty($query['repeat_cells'])) { + $query['repeat_cells'] = $GLOBALS['cfg']['RepeatCells']; + } + + // as this is a form value, the type is always string so we cannot + // use Core::isValid($_POST['session_max_rows'], 'integer') + if (Core::isValid($_POST['session_max_rows'], 'numeric')) { + $query['max_rows'] = (int)$_POST['session_max_rows']; + unset($_POST['session_max_rows']); + } elseif ($_POST['session_max_rows'] == self::ALL_ROWS) { + $query['max_rows'] = self::ALL_ROWS; + unset($_POST['session_max_rows']); + } elseif (empty($query['max_rows'])) { + $query['max_rows'] = intval($GLOBALS['cfg']['MaxRows']); + } + + if (Core::isValid($_REQUEST['pos'], 'numeric')) { + $query['pos'] = $_REQUEST['pos']; + unset($_REQUEST['pos']); + } elseif (empty($query['pos'])) { + $query['pos'] = 0; + } + + if (Core::isValid( + $_REQUEST['pftext'], + array( + self::DISPLAY_PARTIAL_TEXT, self::DISPLAY_FULL_TEXT + ) + ) + ) { + $query['pftext'] = $_REQUEST['pftext']; + unset($_REQUEST['pftext']); + } elseif (empty($query['pftext'])) { + $query['pftext'] = self::DISPLAY_PARTIAL_TEXT; + } + + if (Core::isValid( + $_REQUEST['relational_display'], + array( + self::RELATIONAL_KEY, self::RELATIONAL_DISPLAY_COLUMN + ) + ) + ) { + $query['relational_display'] = $_REQUEST['relational_display']; + unset($_REQUEST['relational_display']); + } elseif (empty($query['relational_display'])) { + // The current session value has priority over a + // change via Settings; this change will be apparent + // starting from the next session + $query['relational_display'] = $GLOBALS['cfg']['RelationalDisplay']; + } + + if (Core::isValid( + $_REQUEST['geoOption'], + array( + self::GEOMETRY_DISP_WKT, self::GEOMETRY_DISP_WKB, + self::GEOMETRY_DISP_GEOM + ) + ) + ) { + $query['geoOption'] = $_REQUEST['geoOption']; + unset($_REQUEST['geoOption']); + } elseif (empty($query['geoOption'])) { + $query['geoOption'] = self::GEOMETRY_DISP_GEOM; + } + + if (isset($_REQUEST['display_binary'])) { + $query['display_binary'] = true; + unset($_REQUEST['display_binary']); + } elseif (isset($_REQUEST['display_options_form'])) { + // we know that the checkbox was unchecked + unset($query['display_binary']); + } elseif (isset($_REQUEST['full_text_button'])) { + // do nothing to keep the value that is there in the session + } else { + // selected by default because some operations like OPTIMIZE TABLE + // and all queries involving functions return "binary" contents, + // according to low-level field flags + $query['display_binary'] = true; + } + + if (isset($_REQUEST['display_blob'])) { + $query['display_blob'] = true; + unset($_REQUEST['display_blob']); + } elseif (isset($_REQUEST['display_options_form'])) { + // we know that the checkbox was unchecked + unset($query['display_blob']); + } + + if (isset($_REQUEST['hide_transformation'])) { + $query['hide_transformation'] = true; + unset($_REQUEST['hide_transformation']); + } elseif (isset($_REQUEST['display_options_form'])) { + // we know that the checkbox was unchecked + unset($query['hide_transformation']); + } + + // move current query to the last position, to be removed last + // so only least executed query will be removed if maximum remembered + // queries limit is reached + unset($_SESSION['tmpval']['query'][$sql_md5]); + $_SESSION['tmpval']['query'][$sql_md5] = $query; + + // do not exceed a maximum number of queries to remember + if (count($_SESSION['tmpval']['query']) > 10) { + array_shift($_SESSION['tmpval']['query']); + //echo 'deleting one element ...'; + } + + // populate query configuration + $_SESSION['tmpval']['pftext'] + = $query['pftext']; + $_SESSION['tmpval']['relational_display'] + = $query['relational_display']; + $_SESSION['tmpval']['geoOption'] + = $query['geoOption']; + $_SESSION['tmpval']['display_binary'] = isset( + $query['display_binary'] + ); + $_SESSION['tmpval']['display_blob'] = isset( + $query['display_blob'] + ); + $_SESSION['tmpval']['hide_transformation'] = isset( + $query['hide_transformation'] + ); + $_SESSION['tmpval']['pos'] + = $query['pos']; + $_SESSION['tmpval']['max_rows'] + = $query['max_rows']; + $_SESSION['tmpval']['repeat_cells'] + = $query['repeat_cells']; + } + + /** + * Prepare a table of results returned by a SQL query. + * + * @param integer &$dt_result the link id associated to the query + * which results have to be displayed + * @param array &$displayParts the parts to display + * @param array $analyzed_sql_results analyzed sql results + * @param boolean $is_limited_display With limited operations or not + * + * @return string $table_html Generated HTML content for resulted table + * + * @access public + * + * @see sql.php file + */ + public function getTable( + &$dt_result, array &$displayParts, array $analyzed_sql_results, + $is_limited_display = false + ) { + + /** + * The statement this table is built for. + * @var \PhpMyAdmin\SqlParser\Statements\SelectStatement + */ + if (isset($analyzed_sql_results['statement'])) { + $statement = $analyzed_sql_results['statement']; + } else { + $statement = null; + } + + $table_html = ''; + // Following variable are needed for use in isset/empty or + // use with array indexes/safe use in foreach + $fields_meta = $this->__get('fields_meta'); + $showtable = $this->__get('showtable'); + $printview = $this->__get('printview'); + + /** + * @todo move this to a central place + * @todo for other future table types + */ + $is_innodb = (isset($showtable['Type']) + && $showtable['Type'] == self::TABLE_TYPE_INNO_DB); + + $sql = new Sql(); + if ($is_innodb && $sql->isJustBrowsing($analyzed_sql_results, true)) { + // "j u s t b r o w s i n g" + $pre_count = '~'; + $after_count = Util::showHint( + Sanitize::sanitize( + __('May be approximate. See [doc@faq3-11]FAQ 3.11[/doc].') + ) + ); + } else { + $pre_count = ''; + $after_count = ''; + } + + // 1. ----- Prepares the work ----- + + // 1.1 Gets the information about which functionalities should be + // displayed + + list( + $displayParts, + $total + ) = $this->_setDisplayPartsAndTotal($displayParts); + + // 1.2 Defines offsets for the next and previous pages + if ($displayParts['nav_bar'] == '1') { + list($pos_next, $pos_prev) = $this->_getOffsets(); + } // end if + + // 1.3 Extract sorting expressions. + // we need $sort_expression and $sort_expression_nodirection + // even if there are many table references + $sort_expression = array(); + $sort_expression_nodirection = array(); + $sort_direction = array(); + + if (!is_null($statement) && !empty($statement->order)) { + foreach ($statement->order as $o) { + $sort_expression[] = $o->expr->expr . ' ' . $o->type; + $sort_expression_nodirection[] = $o->expr->expr; + $sort_direction[] = $o->type; + } + } else { + $sort_expression[] = ''; + $sort_expression_nodirection[] = ''; + $sort_direction[] = ''; + } + + $number_of_columns = count($sort_expression_nodirection); + + // 1.4 Prepares display of first and last value of the sorted column + $sorted_column_message = ''; + for ( $i = 0; $i < $number_of_columns; $i++ ) { + $sorted_column_message .= $this->_getSortedColumnMessage( + $dt_result, $sort_expression_nodirection[$i] + ); + } + + // 2. ----- Prepare to display the top of the page ----- + + // 2.1 Prepares a messages with position information + if (($displayParts['nav_bar'] == '1') && isset($pos_next)) { + + $message = $this->_setMessageInformation( + $sorted_column_message, + $analyzed_sql_results, + $total, + $pos_next, + $pre_count, + $after_count + ); + + $table_html .= Util::getMessage( + $message, $this->__get('sql_query'), 'success' + ); + + } elseif ((!isset($printview) || ($printview != '1')) && !$is_limited_display) { + + $table_html .= Util::getMessage( + __('Your SQL query has been executed successfully.'), + $this->__get('sql_query'), 'success' + ); + } + + // 2.3 Prepare the navigation bars + if (strlen($this->__get('table')) === 0) { + + if ($analyzed_sql_results['querytype'] == 'SELECT') { + // table does not always contain a real table name, + // for example in MySQL 5.0.x, the query SHOW STATUS + // returns STATUS as a table name + $this->__set('table', $fields_meta[0]->table); + } else { + $this->__set('table', ''); + } + + } + + // can the result be sorted? + if ($displayParts['sort_lnk'] == '1' && ! is_null($analyzed_sql_results['statement'])) { + + // At this point, $sort_expression is an array + list($unsorted_sql_query, $sort_by_key_html) + = $this->_getUnsortedSqlAndSortByKeyDropDown( + $analyzed_sql_results, $sort_expression + ); + + } else { + $sort_by_key_html = $unsorted_sql_query = ''; + } + + if (($displayParts['nav_bar'] == '1') && !is_null($statement) && (empty($statement->limit))) { + $table_html .= $this->_getPlacedTableNavigations( + $pos_next, $pos_prev, self::PLACE_TOP_DIRECTION_DROPDOWN, + $is_innodb, $sort_by_key_html + ); + } + + // 2b ----- Get field references from Database ----- + // (see the 'relation' configuration variable) + + // initialize map + $map = array(); + + $target = array(); + if (!is_null($statement) && !empty($statement->from)) { + foreach ($statement->from as $field) { + if (!empty($field->table)) { + $target[] = $field->table; + } + } + } + + if (strlen($this->__get('table')) > 0) { + // This method set the values for $map array + $this->_setParamForLinkForeignKeyRelatedTables($map); + + // Coming from 'Distinct values' action of structure page + // We manipulate relations mechanism to show a link to related rows. + if ($this->__get('is_browse_distinct')) { + $map[$fields_meta[1]->name] = array( + $this->__get('table'), + $fields_meta[1]->name, + '', + $this->__get('db') + ); + } + } // end if + // end 2b + + // 3. ----- Prepare the results table ----- + if ($is_limited_display) { + $table_html .= "
        "; + } + + $table_html .= $this->_getTableHeaders( + $displayParts, + $analyzed_sql_results, + $unsorted_sql_query, + $sort_expression, + $sort_expression_nodirection, + $sort_direction, + $is_limited_display + ); + + $table_html .= '' . "\n"; + + $table_html .= $this->_getTableBody( + $dt_result, + $displayParts, + $map, + $analyzed_sql_results, + $is_limited_display + ); + + $this->__set('display_params', null); + + $table_html .= '' . "\n" . '
        '; + + // 4. ----- Prepares the link for multi-fields edit and delete + + if ($displayParts['del_lnk'] == self::DELETE_ROW + && $displayParts['del_lnk'] != self::KILL_PROCESS + ) { + + $table_html .= $this->_getMultiRowOperationLinks( + $dt_result, + $analyzed_sql_results, + $displayParts['del_lnk'] + ); + + } + + // 5. ----- Get the navigation bar at the bottom if required ----- + if (($displayParts['nav_bar'] == '1') && !is_null($statement) && empty($statement->limit)) { + $table_html .= $this->_getPlacedTableNavigations( + $pos_next, $pos_prev, self::PLACE_BOTTOM_DIRECTION_DROPDOWN, + $is_innodb, $sort_by_key_html + ); + } elseif (! isset($printview) || ($printview != '1')) { + $table_html .= "\n" . '

        ' . "\n"; + } + + // 6. ----- Prepare "Query results operations" + if ((! isset($printview) || ($printview != '1')) && ! $is_limited_display) { + $table_html .= $this->_getResultsOperations( + $displayParts, $analyzed_sql_results + ); + } + + return $table_html; + + } // end of the 'getTable()' function + + + /** + * Get offsets for next page and previous page + * + * @return array array with two elements - $pos_next, $pos_prev + * + * @access private + * + * @see getTable() + */ + private function _getOffsets() + { + + if ($_SESSION['tmpval']['max_rows'] == self::ALL_ROWS) { + $pos_next = 0; + $pos_prev = 0; + } else { + + $pos_next = $_SESSION['tmpval']['pos'] + + $_SESSION['tmpval']['max_rows']; + + $pos_prev = $_SESSION['tmpval']['pos'] + - $_SESSION['tmpval']['max_rows']; + + if ($pos_prev < 0) { + $pos_prev = 0; + } + } + + return array($pos_next, $pos_prev); + + } // end of the '_getOffsets()' function + + + /** + * Prepare sorted column message + * + * @param integer &$dt_result the link id associated to the + * query which results have to + * be displayed + * @param string $sort_expression_nodirection sort expression without direction + * + * @return string html content + * null if not found sorted column + * + * @access private + * + * @see getTable() + */ + private function _getSortedColumnMessage( + &$dt_result, $sort_expression_nodirection + ) { + + $fields_meta = $this->__get('fields_meta'); // To use array indexes + + if (empty($sort_expression_nodirection)) { + return null; + } + + if (mb_strpos($sort_expression_nodirection, '.') === false) { + $sort_table = $this->__get('table'); + $sort_column = $sort_expression_nodirection; + } else { + list($sort_table, $sort_column) + = explode('.', $sort_expression_nodirection); + } + + $sort_table = Util::unQuote($sort_table); + $sort_column = Util::unQuote($sort_column); + + // find the sorted column index in row result + // (this might be a multi-table query) + $sorted_column_index = false; + + foreach ($fields_meta as $key => $meta) { + if (($meta->table == $sort_table) && ($meta->name == $sort_column)) { + $sorted_column_index = $key; + break; + } + } + + if ($sorted_column_index === false) { + return null; + } + + // fetch first row of the result set + $row = $GLOBALS['dbi']->fetchRow($dt_result); + + // initializing default arguments + $default_function = [Core::class, 'mimeDefaultFunction']; + $transformation_plugin = $default_function; + $transform_options = array(); + + // check for non printable sorted row data + $meta = $fields_meta[$sorted_column_index]; + + if (stristr($meta->type, self::BLOB_FIELD) + || ($meta->type == self::GEOMETRY_FIELD) + ) { + + $column_for_first_row = $this->_handleNonPrintableContents( + $meta->type, $row[$sorted_column_index], + $transformation_plugin, $transform_options, + $default_function, $meta + ); + + } else { + $column_for_first_row = $row[$sorted_column_index]; + } + + $column_for_first_row = mb_strtoupper( + mb_substr( + $column_for_first_row, 0, $GLOBALS['cfg']['LimitChars'] + ) . '...' + ); + + // fetch last row of the result set + $GLOBALS['dbi']->dataSeek($dt_result, $this->__get('num_rows') - 1); + $row = $GLOBALS['dbi']->fetchRow($dt_result); + + // check for non printable sorted row data + $meta = $fields_meta[$sorted_column_index]; + if (stristr($meta->type, self::BLOB_FIELD) + || ($meta->type == self::GEOMETRY_FIELD) + ) { + + $column_for_last_row = $this->_handleNonPrintableContents( + $meta->type, $row[$sorted_column_index], + $transformation_plugin, $transform_options, + $default_function, $meta + ); + + } else { + $column_for_last_row = $row[$sorted_column_index]; + } + + $column_for_last_row = mb_strtoupper( + mb_substr( + $column_for_last_row, 0, $GLOBALS['cfg']['LimitChars'] + ) . '...' + ); + + // reset to first row for the loop in _getTableBody() + $GLOBALS['dbi']->dataSeek($dt_result, 0); + + // we could also use here $sort_expression_nodirection + return ' [' . htmlspecialchars($sort_column) + . ': ' . htmlspecialchars($column_for_first_row) . ' - ' + . htmlspecialchars($column_for_last_row) . ']'; + } // end of the '_getSortedColumnMessage()' function + + + /** + * Set the content that needs to be shown in message + * + * @param string $sorted_column_message the message for sorted column + * @param array $analyzed_sql_results the analyzed query + * @param integer $total the total number of rows returned by + * the SQL query without any + * programmatically appended LIMIT clause + * @param integer $pos_next the offset for next page + * @param string $pre_count the string renders before row count + * @param string $after_count the string renders after row count + * + * @return Message $message an object of Message + * + * @access private + * + * @see getTable() + */ + private function _setMessageInformation( + $sorted_column_message, array $analyzed_sql_results, $total, + $pos_next, $pre_count, $after_count + ) { + + $unlim_num_rows = $this->__get('unlim_num_rows'); // To use in isset() + + if (!empty($analyzed_sql_results['statement']->limit)) { + + $first_shown_rec = $analyzed_sql_results['statement']->limit->offset; + $row_count = $analyzed_sql_results['statement']->limit->rowCount; + + if ($row_count < $total) { + $last_shown_rec = $first_shown_rec + $row_count - 1; + } else { + $last_shown_rec = $first_shown_rec + $total - 1; + } + + } elseif (($_SESSION['tmpval']['max_rows'] == self::ALL_ROWS) + || ($pos_next > $total) + ) { + + $first_shown_rec = $_SESSION['tmpval']['pos']; + $last_shown_rec = $total - 1; + + } else { + + $first_shown_rec = $_SESSION['tmpval']['pos']; + $last_shown_rec = $pos_next - 1; + + } + + $table = new Table($this->__get('table'), $this->__get('db')); + if ($table->isView() + && ($total == $GLOBALS['cfg']['MaxExactCountViews']) + ) { + + $message = Message::notice( + __( + 'This view has at least this number of rows. ' + . 'Please refer to %sdocumentation%s.' + ) + ); + + $message->addParam('[doc@cfg_MaxExactCount]'); + $message->addParam('[/doc]'); + $message_view_warning = Util::showHint($message); + + } else { + $message_view_warning = false; + } + + $message = Message::success(__('Showing rows %1s - %2s')); + $message->addParam($first_shown_rec); + + if ($message_view_warning !== false) { + $message->addParamHtml('... ' . $message_view_warning); + } else { + $message->addParam($last_shown_rec); + } + + $message->addText('('); + + if ($message_view_warning === false) { + + if (isset($unlim_num_rows) && ($unlim_num_rows != $total)) { + $message_total = Message::notice( + $pre_count . __('%1$d total, %2$d in query') + ); + $message_total->addParam($total); + $message_total->addParam($unlim_num_rows); + } else { + $message_total = Message::notice($pre_count . __('%d total')); + $message_total->addParam($total); + } + + if (!empty($after_count)) { + $message_total->addHtml($after_count); + } + $message->addMessage($message_total, ''); + + $message->addText(', ', ''); + } + + $message_qt = Message::notice(__('Query took %01.4f seconds.') . ')'); + $message_qt->addParam($this->__get('querytime')); + + $message->addMessage($message_qt, ''); + if (! is_null($sorted_column_message)) { + $message->addHtml($sorted_column_message, ''); + } + + return $message; + } // end of the '_setMessageInformation()' function + + /** + * Set the value of $map array for linking foreign key related tables + * + * @param array &$map the list of relations + * + * @return void + * + * @access private + * + * @see getTable() + */ + private function _setParamForLinkForeignKeyRelatedTables(array &$map) + { + // To be able to later display a link to the related table, + // we verify both types of relations: either those that are + // native foreign keys or those defined in the phpMyAdmin + // configuration storage. If no PMA storage, we won't be able + // to use the "column to display" notion (for example show + // the name related to a numeric id). + $exist_rel = $this->relation->getForeigners( + $this->__get('db'), $this->__get('table'), '', self::POSITION_BOTH + ); + + if (! empty($exist_rel)) { + + foreach ($exist_rel as $master_field => $rel) { + if ($master_field != 'foreign_keys_data') { + $display_field = $this->relation->getDisplayField( + $rel['foreign_db'], $rel['foreign_table'] + ); + $map[$master_field] = array( + $rel['foreign_table'], + $rel['foreign_field'], + $display_field, + $rel['foreign_db'] + ); + } else { + foreach ($rel as $key => $one_key) { + foreach ($one_key['index_list'] as $index => $one_field) { + $display_field = $this->relation->getDisplayField( + isset($one_key['ref_db_name']) + ? $one_key['ref_db_name'] + : $GLOBALS['db'], + $one_key['ref_table_name'] + ); + + $map[$one_field] = array( + $one_key['ref_table_name'], + $one_key['ref_index_list'][$index], + $display_field, + isset($one_key['ref_db_name']) + ? $one_key['ref_db_name'] + : $GLOBALS['db'] + ); + } + } + } + } // end while + } // end if + + } // end of the '_setParamForLinkForeignKeyRelatedTables()' function + + + /** + * Prepare multi field edit/delete links + * + * @param integer &$dt_result the link id associated to the query which + * results have to be displayed + * @param array $analyzed_sql_results analyzed sql results + * @param string $del_link the display element - 'del_link' + * + * @return string $links_html html content + * + * @access private + * + * @see getTable() + */ + private function _getMultiRowOperationLinks( + &$dt_result, array $analyzed_sql_results, $del_link + ) { + + $links_html = '\n"; + + $links_html .= '' + . "\n"; + + if (! empty($url_query)) { + $links_html .= '' . "\n"; + } + + // fetch last row of the result set + $GLOBALS['dbi']->dataSeek($dt_result, $this->__get('num_rows') - 1); + $row = $GLOBALS['dbi']->fetchRow($dt_result); + + // @see DbiMysqi::fetchRow & DatabaseInterface::fetchRow + if (! is_array($row)) { + $row = array(); + } + + // $clause_is_unique is needed by getTable() to generate the proper param + // in the multi-edit and multi-delete form + list($where_clause, $clause_is_unique, $condition_array) + = Util::getUniqueCondition( + $dt_result, // handle + $this->__get('fields_cnt'), // fields_cnt + $this->__get('fields_meta'), // fields_meta + $row, // row + false, // force_unique + false, // restrict_to_table + $analyzed_sql_results // analyzed_sql_results + ); + unset($where_clause, $condition_array); + + // reset to first row for the loop in _getTableBody() + $GLOBALS['dbi']->dataSeek($dt_result, 0); + + $links_html .= '' . "\n"; + + $links_html .= '' . "\n"; + + return $links_html; + + } // end of the '_getMultiRowOperationLinks()' function + + + /** + * Prepare table navigation bar at the top or bottom + * + * @param integer $pos_next the offset for the "next" page + * @param integer $pos_prev the offset for the "previous" page + * @param string $place the place to show navigation + * @param boolean $is_innodb whether its InnoDB or not + * @param string $sort_by_key_html the sort by key dialog + * + * @return string html content of navigation bar + * + * @access private + * + * @see _getTable() + */ + private function _getPlacedTableNavigations( + $pos_next, $pos_prev, $place, $is_innodb, $sort_by_key_html + ) { + + $navigation_html = ''; + + if ($place == self::PLACE_BOTTOM_DIRECTION_DROPDOWN) { + $navigation_html .= '
        ' . "\n"; + } + + $navigation_html .= $this->_getTableNavigation( + $pos_next, $pos_prev, $is_innodb, $sort_by_key_html + ); + + if ($place == self::PLACE_TOP_DIRECTION_DROPDOWN) { + $navigation_html .= "\n"; + } + + return $navigation_html; + + } // end of the '_getPlacedTableNavigations()' function + + /** + * Generates HTML to display the Create view in span tag + * + * @param array $analyzed_sql_results analyzed sql results + * @param string $url_query String with URL Parameters + * + * @return string + * + * @access private + * + * @see _getResultsOperations() + */ + private function _getLinkForCreateView(array $analyzed_sql_results, $url_query) + { + $results_operations_html = ''; + if (empty($analyzed_sql_results['procedure'])) { + + $results_operations_html .= '' + . Util::linkOrButton( + 'view_create.php' . $url_query, + Util::getIcon( + 'b_view_add', __('Create view'), true + ), + array('class' => 'create_view ajax') + ) + . '' . "\n"; + } + return $results_operations_html; + + } + + /** + * Calls the _getResultsOperations with $only_view as true + * + * @param array $analyzed_sql_results analyzed sql results + * + * @return string + * + * @access public + * + */ + public function getCreateViewQueryResultOp(array $analyzed_sql_results) + { + + $results_operations_html = ''; + //calling to _getResultOperations with a fake $displayParts + //and setting only_view parameter to be true to generate just view + $results_operations_html .= $this->_getResultsOperations( + array(), + $analyzed_sql_results, + true + ); + return $results_operations_html; + } + + /** + * Get copy to clipboard links for results operations + * + * @return string $html + * + * @access private + */ + private function _getCopytoclipboardLinks() + { + $html = Util::linkOrButton( + '#', + Util::getIcon( + 'b_insrow', __('Copy to clipboard'), true + ), + array('id' => 'copyToClipBoard') + ); + + return $html; + } + + /** + * Get printview links for results operations + * + * @return string $html + * + * @access private + */ + private function _getPrintviewLinks() + { + $html = Util::linkOrButton( + '#', + Util::getIcon( + 'b_print', __('Print'), true + ), + array('id' => 'printView'), + 'print_view' + ); + + return $html; + } + + /** + * Get operations that are available on results. + * + * @param array $displayParts the parts to display + * @param array $analyzed_sql_results analyzed sql results + * @param boolean $only_view Whether to show only view + * + * @return string $results_operations_html html content + * + * @access private + * + * @see getTable() + */ + private function _getResultsOperations( + array $displayParts, array $analyzed_sql_results, $only_view = false + ) { + global $printview; + + $results_operations_html = ''; + $fields_meta = $this->__get('fields_meta'); // To safe use in foreach + $header_shown = false; + $header = '
        '; + } + return $results_operations_html; + } + + // Displays "printable view" link if required + if ($displayParts['pview_lnk'] == '1') { + $results_operations_html .= $this->_getPrintviewLinks(); + $results_operations_html .= $this->_getCopytoclipboardLinks(); + } // end displays "printable view" + + // Export link + // (the url_query has extra parameters that won't be used to export) + // (the single_table parameter is used in Export::getDisplay() + // to hide the SQL and the structure export dialogs) + // If the parser found a PROCEDURE clause + // (most probably PROCEDURE ANALYSE()) it makes no sense to + // display the Export link). + if (($analyzed_sql_results['querytype'] == self::QUERY_TYPE_SELECT) + && ! isset($printview) + && empty($analyzed_sql_results['procedure']) + ) { + + if (count($analyzed_sql_results['select_tables']) == 1) { + $_url_params['single_table'] = 'true'; + } + + if (! $header_shown) { + $results_operations_html .= $header; + $header_shown = true; + } + + $_url_params['unlim_num_rows'] = $this->__get('unlim_num_rows'); + + /** + * At this point we don't know the table name; this can happen + * for example with a query like + * SELECT bike_code FROM (SELECT bike_code FROM bikes) tmp + * As a workaround we set in the table parameter the name of the + * first table of this database, so that tbl_export.php and + * the script it calls do not fail + */ + if (empty($_url_params['table']) && ! empty($_url_params['db'])) { + $_url_params['table'] = $GLOBALS['dbi']->fetchValue("SHOW TABLES"); + /* No result (probably no database selected) */ + if ($_url_params['table'] === false) { + unset($_url_params['table']); + } + } + + $results_operations_html .= Util::linkOrButton( + 'tbl_export.php' . Url::getCommon($_url_params), + Util::getIcon( + 'b_tblexport', __('Export'), true + ) + ) + . "\n"; + + // prepare chart + $results_operations_html .= Util::linkOrButton( + 'tbl_chart.php' . Url::getCommon($_url_params), + Util::getIcon( + 'b_chart', __('Display chart'), true + ) + ) + . "\n"; + + // prepare GIS chart + $geometry_found = false; + // If at least one geometry field is found + foreach ($fields_meta as $meta) { + if ($meta->type == self::GEOMETRY_FIELD) { + $geometry_found = true; + break; + } + } + + if ($geometry_found) { + $results_operations_html + .= Util::linkOrButton( + 'tbl_gis_visualization.php' + . Url::getCommon($_url_params), + Util::getIcon( + 'b_globe', + __('Visualize GIS data'), + true + ) + ) + . "\n"; + } + } + + // CREATE VIEW + /** + * + * @todo detect privileges to create a view + * (but see 2006-01-19 note in PhpMyAdmin\Display\CreateTable, + * I think we cannot detect db-specific privileges reliably) + * Note: we don't display a Create view link if we found a PROCEDURE clause + */ + if (!$header_shown) { + $results_operations_html .= $header; + $header_shown = true; + } + + $results_operations_html .= $this->_getLinkForCreateView( + $analyzed_sql_results, $url_query + ); + + if ($header_shown) { + $results_operations_html .= '
        '; + } + + return $results_operations_html; + + } // end of the '_getResultsOperations()' function + + + /** + * Verifies what to do with non-printable contents (binary or BLOB) + * in Browse mode. + * + * @param string $category BLOB|BINARY|GEOMETRY + * @param string $content the binary content + * @param mixed $transformation_plugin transformation plugin. + * Can also be the default function: + * Core::mimeDefaultFunction + * @param string $transform_options transformation parameters + * @param string $default_function default transformation function + * @param object $meta the meta-information about the field + * @param array $url_params parameters that should go to the + * download link + * @param boolean &$is_truncated the result is truncated or not + * + * @return mixed string or float + * + * @access private + * + * @see _getDataCellForGeometryColumns(), + * _getDataCellForNonNumericColumns(), + * _getSortedColumnMessage() + */ + private function _handleNonPrintableContents( + $category, $content, $transformation_plugin, $transform_options, + $default_function, $meta, array $url_params = array(), &$is_truncated = null + ) { + + $is_truncated = false; + $result = '[' . $category; + + if (isset($content)) { + + $size = strlen($content); + $display_size = Util::formatByteDown($size, 3, 1); + $result .= ' - ' . $display_size[0] . ' ' . $display_size[1]; + + } else { + + $result .= ' - NULL'; + $size = 0; + + } + + $result .= ']'; + + // if we want to use a text transformation on a BLOB column + if (gettype($transformation_plugin) === "object") { + $posMimeOctetstream = strpos( + $transformation_plugin->getMIMESubtype(), + 'Octetstream' + ); + $posMimeText = strpos($transformation_plugin->getMIMEtype(), 'Text'); + if ($posMimeOctetstream + || $posMimeText !== false + ) { + // Applying Transformations on hex string of binary data + // seems more appropriate + $result = pack("H*", bin2hex($content)); + } + } + + if ($size <= 0) { + return($result); + } + + if ($default_function != $transformation_plugin) { + $result = $transformation_plugin->applyTransformation( + $result, + $transform_options, + $meta + ); + return($result); + } + + $result = $default_function($result, array(), $meta); + if (($_SESSION['tmpval']['display_binary'] + && $meta->type === self::STRING_FIELD) + || ($_SESSION['tmpval']['display_blob'] + && stristr($meta->type, self::BLOB_FIELD)) + ) { + // in this case, restart from the original $content + if (mb_check_encoding($content, 'utf-8') + && !preg_match('/[\x00-\x08\x0B\x0C\x0E-\x1F\x80-\x9F]/u', $content) + ) { + // show as text if it's valid utf-8 + $result = htmlspecialchars($content); + } else { + $result = '0x' . bin2hex($content); + } + list( + $is_truncated, + $result, + // skip 3rd param + ) = $this->_getPartialText($result); + } + + /* Create link to download */ + + // in PHP < 5.5, empty() only checks variables + $tmpdb = $this->__get('db'); + if (count($url_params) > 0 + && (!empty($tmpdb) && !empty($meta->orgtable)) + ) { + $result = '
        ' + . $result . ''; + } + + return($result); + + } // end of the '_handleNonPrintableContents()' function + + + /** + * Retrieves the associated foreign key info for a data cell + * + * @param array $map the list of relations + * @param object $meta the meta-information about the field + * @param string $where_comparison data for the where clause + * + * @return string formatted data + * + * @access private + * + */ + private function _getFromForeign(array $map, $meta, $where_comparison) + { + $dispsql = 'SELECT ' + . Util::backquote($map[$meta->name][2]) + . ' FROM ' + . Util::backquote($map[$meta->name][3]) + . '.' + . Util::backquote($map[$meta->name][0]) + . ' WHERE ' + . Util::backquote($map[$meta->name][1]) + . $where_comparison; + + $dispresult = $GLOBALS['dbi']->tryQuery( + $dispsql, + DatabaseInterface::CONNECT_USER, + DatabaseInterface::QUERY_STORE + ); + + if ($dispresult && $GLOBALS['dbi']->numRows($dispresult) > 0) { + list($dispval) = $GLOBALS['dbi']->fetchRow($dispresult, 0); + } else { + $dispval = __('Link not found!'); + } + + $GLOBALS['dbi']->freeResult($dispresult); + + return $dispval; + } + + /** + * Prepares the displayable content of a data cell in Browse mode, + * taking into account foreign key description field and transformations + * + * @param string $class css classes for the td element + * @param bool $condition_field whether the column is a part of + * the where clause + * @param array $analyzed_sql_results the analyzed query + * @param object $meta the meta-information about the + * field + * @param array $map the list of relations + * @param string $data data + * @param string $displayedData data that will be displayed (maybe be chunked) + * @param object|string $transformation_plugin transformation plugin. + * Can also be the default function: + * Core::mimeDefaultFunction + * @param string $default_function default function + * @param string $nowrap 'nowrap' if the content should + * not be wrapped + * @param string $where_comparison data for the where clause + * @param array $transform_options options for transformation + * @param bool $is_field_truncated whether the field is truncated + * @param string $original_length of a truncated column, or '' + * + * @return string formatted data + * + * @access private + * + * @see _getDataCellForNumericColumns(), _getDataCellForGeometryColumns(), + * _getDataCellForNonNumericColumns(), + * + */ + private function _getRowData( + $class, $condition_field, array $analyzed_sql_results, $meta, + array $map, $data, $displayedData, + $transformation_plugin, $default_function, $nowrap, $where_comparison, + array $transform_options, $is_field_truncated, $original_length='' + ) { + $relational_display = $_SESSION['tmpval']['relational_display']; + $printview = $this->__get('printview'); + $decimals = isset($meta->decimals) ? $meta->decimals : '-1'; + $result = '_addClass( + $class, $condition_field, $meta, $nowrap, + $is_field_truncated, $transformation_plugin, $default_function + ) + . '">'; + + if (!empty($analyzed_sql_results['statement']->expr)) { + foreach ($analyzed_sql_results['statement']->expr as $expr) { + if ((empty($expr->alias)) || (empty($expr->column))) { + continue; + } + if (strcasecmp($meta->name, $expr->alias) == 0) { + $meta->name = $expr->column; + } + } + } + + if (isset($map[$meta->name])) { + + // Field to display from the foreign table? + if (isset($map[$meta->name][2]) + && strlen($map[$meta->name][2]) > 0 + ) { + $dispval = $this->_getFromForeign( + $map, $meta, $where_comparison + ); + } else { + $dispval = ''; + } // end if... else... + + if (isset($printview) && ($printview == '1')) { + + $result .= ($transformation_plugin != $default_function + ? $transformation_plugin->applyTransformation( + $data, + $transform_options, + $meta + ) + : $default_function($data) + ) + . ' [->' . $dispval . ']'; + + } else { + + if ($relational_display == self::RELATIONAL_KEY) { + + // user chose "relational key" in the display options, so + // the title contains the display field + $title = (! empty($dispval)) + ? htmlspecialchars($dispval) + : ''; + + } else { + $title = htmlspecialchars($data); + } + + $sqlQuery = 'SELECT * FROM ' + . Util::backquote($map[$meta->name][3]) . '.' + . Util::backquote($map[$meta->name][0]) + . ' WHERE ' + . Util::backquote($map[$meta->name][1]) + . $where_comparison; + + $_url_params = array( + 'db' => $map[$meta->name][3], + 'table' => $map[$meta->name][0], + 'pos' => '0', + 'sql_signature' => Core::signSqlQuery($sqlQuery), + 'sql_query' => $sqlQuery, + ); + + if ($transformation_plugin != $default_function) { + // always apply a transformation on the real data, + // not on the display field + $displayedData = $transformation_plugin->applyTransformation( + $data, + $transform_options, + $meta + ); + } else { + + if ($relational_display == self::RELATIONAL_DISPLAY_COLUMN + && ! empty($map[$meta->name][2]) + ) { + // user chose "relational display field" in the + // display options, so show display field in the cell + $displayedData = $default_function($dispval); + } else { + // otherwise display data in the cell + $displayedData = $default_function($displayedData); + } + + } + + $tag_params = array('title' => $title); + if (strpos($class, 'grid_edit') !== false) { + $tag_params['class'] = 'ajax'; + } + $result .= Util::linkOrButton( + 'sql.php' . Url::getCommon($_url_params), + $displayedData, $tag_params + ); + } + + } else { + $result .= ($transformation_plugin != $default_function + ? $transformation_plugin->applyTransformation( + $data, + $transform_options, + $meta + ) + : $default_function($data) + ); + } + + $result .= '' . "\n"; + + return $result; + + } // end of the '_getRowData()' function + + + /** + * Prepares a checkbox for multi-row submits + * + * @param string $del_url delete url + * @param array $displayParts array with explicit indexes for all + * the display elements + * @param string $row_no the row number + * @param string $where_clause_html url encoded where clause + * @param array $condition_array array of conditions in the where clause + * @param string $id_suffix suffix for the id + * @param string $class css classes for the td element + * + * @return string the generated HTML + * + * @access private + * + * @see _getTableBody(), _getCheckboxAndLinks() + */ + private function _getCheckboxForMultiRowSubmissions( + $del_url, array $displayParts, $row_no, $where_clause_html, array $condition_array, + $id_suffix, $class + ) { + + $ret = ''; + + if (! empty($del_url) && $displayParts['del_lnk'] != self::KILL_PROCESS) { + + $ret .= '' + . '' + . ' '; + } + + return $ret; + + } // end of the '_getCheckboxForMultiRowSubmissions()' function + + + /** + * Prepares an Edit link + * + * @param string $edit_url edit url + * @param string $class css classes for td element + * @param string $edit_str text for the edit link + * @param string $where_clause where clause + * @param string $where_clause_html url encoded where clause + * + * @return string the generated HTML + * + * @access private + * + * @see _getTableBody(), _getCheckboxAndLinks() + */ + private function _getEditLink( + $edit_url, $class, $edit_str, $where_clause, $where_clause_html + ) { + + $ret = ''; + if (! empty($edit_url)) { + + $ret .= '' + . Util::linkOrButton($edit_url, $edit_str); + /* + * Where clause for selecting this row uniquely is provided as + * a hidden input. Used by jQuery scripts for handling grid editing + */ + if (! empty($where_clause)) { + $ret .= ''; + } + $ret .= ''; + } + + return $ret; + + } // end of the '_getEditLink()' function + + + /** + * Prepares an Copy link + * + * @param string $copy_url copy url + * @param string $copy_str text for the copy link + * @param string $where_clause where clause + * @param string $where_clause_html url encoded where clause + * @param string $class css classes for the td element + * + * @return string the generated HTML + * + * @access private + * + * @see _getTableBody(), _getCheckboxAndLinks() + */ + private function _getCopyLink( + $copy_url, $copy_str, $where_clause, $where_clause_html, $class + ) { + + $ret = ''; + if (! empty($copy_url)) { + + $ret .= '' + . Util::linkOrButton($copy_url, $copy_str); + + /* + * Where clause for selecting this row uniquely is provided as + * a hidden input. Used by jQuery scripts for handling grid editing + */ + if (! empty($where_clause)) { + $ret .= ''; + } + $ret .= ''; + } + + return $ret; + + } // end of the '_getCopyLink()' function + + + /** + * Prepares a Delete link + * + * @param string $del_url delete url + * @param string $del_str text for the delete link + * @param string $js_conf text for the JS confirmation + * @param string $class css classes for the td element + * + * @return string the generated HTML + * + * @access private + * + * @see _getTableBody(), _getCheckboxAndLinks() + */ + private function _getDeleteLink($del_url, $del_str, $js_conf, $class) + { + + $ret = ''; + if (empty($del_url)) { + return $ret; + } + + $ret .= '' + . Util::linkOrButton( + $del_url, + $del_str, + array('class' => 'delete_row requireConfirm' . $ajax) + ) + . '
        ' . $js_conf . '
        ' + . ''; + + return $ret; + + } // end of the '_getDeleteLink()' function + + + /** + * Prepare checkbox and links at some position (left or right) + * (only called for horizontal mode) + * + * @param string $position the position of the checkbox and links + * @param string $del_url delete url + * @param array $displayParts array with explicit indexes for all the + * display elements + * @param string $row_no row number + * @param string $where_clause where clause + * @param string $where_clause_html url encoded where clause + * @param array $condition_array array of conditions in the where clause + * @param string $edit_url edit url + * @param string $copy_url copy url + * @param string $class css classes for the td elements + * @param string $edit_str text for the edit link + * @param string $copy_str text for the copy link + * @param string $del_str text for the delete link + * @param string $js_conf text for the JS confirmation + * + * @return string the generated HTML + * + * @access private + * + * @see _getPlacedLinks() + */ + private function _getCheckboxAndLinks( + $position, $del_url, array $displayParts, $row_no, $where_clause, + $where_clause_html, array $condition_array, + $edit_url, $copy_url, $class, $edit_str, $copy_str, $del_str, $js_conf + ) { + + $ret = ''; + + if ($position == self::POSITION_LEFT) { + + $ret .= $this->_getCheckboxForMultiRowSubmissions( + $del_url, $displayParts, $row_no, $where_clause_html, + $condition_array, '_left', '' + ); + + $ret .= $this->_getEditLink( + $edit_url, $class, $edit_str, $where_clause, $where_clause_html + ); + + $ret .= $this->_getCopyLink( + $copy_url, $copy_str, $where_clause, $where_clause_html, '' + ); + + $ret .= $this->_getDeleteLink($del_url, $del_str, $js_conf, ''); + + } elseif ($position == self::POSITION_RIGHT) { + + $ret .= $this->_getDeleteLink($del_url, $del_str, $js_conf, ''); + + $ret .= $this->_getCopyLink( + $copy_url, $copy_str, $where_clause, $where_clause_html, '' + ); + + $ret .= $this->_getEditLink( + $edit_url, $class, $edit_str, $where_clause, $where_clause_html + ); + + $ret .= $this->_getCheckboxForMultiRowSubmissions( + $del_url, $displayParts, $row_no, $where_clause_html, + $condition_array, '_right', '' + ); + + } else { // $position == self::POSITION_NONE + + $ret .= $this->_getCheckboxForMultiRowSubmissions( + $del_url, $displayParts, $row_no, $where_clause_html, + $condition_array, '_left', '' + ); + } + + return $ret; + + } // end of the '_getCheckboxAndLinks()' function + + /** + * Truncates given string based on LimitChars configuration + * and Session pftext variable + * (string is truncated only if necessary) + * + * @param string $str string to be truncated + * + * @return mixed + * + * @access private + * + * @see _handleNonPrintableContents(), _getDataCellForGeometryColumns(), + * _getDataCellForNonNumericColumns + */ + private function _getPartialText($str) + { + $original_length = mb_strlen($str); + if ($original_length > $GLOBALS['cfg']['LimitChars'] + && $_SESSION['tmpval']['pftext'] === self::DISPLAY_PARTIAL_TEXT + ) { + $str = mb_substr( + $str, 0, $GLOBALS['cfg']['LimitChars'] + ) . '...'; + $truncated = true; + } else { + $truncated = false; + } + + return array($truncated, $str, $original_length); + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Encoding.php b/php/apps/phpmyadmin49/libraries/classes/Encoding.php new file mode 100644 index 00000000..f17e3117 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Encoding.php @@ -0,0 +1,334 @@ + array('iconv', self::ENGINE_ICONV, 'iconv'), + 'recode' => array('recode_string', self::ENGINE_RECODE, 'recode'), + 'mb' => array('mb_convert_encoding', self::ENGINE_MB, 'mbstring'), + 'none' => array('isset', self::ENGINE_NONE, ''), + ); + + /** + * Order of automatic detection of engines + * + * @var array + */ + private static $_engineorder = array( + 'iconv', 'mb', 'recode', + ); + + /** + * Kanji encodings list + * + * @var string + */ + private static $_kanji_encodings = 'ASCII,SJIS,EUC-JP,JIS'; + + /** + * Initializes encoding engine detecting available backends. + * + * @return void + */ + public static function initEngine() + { + $engine = 'auto'; + if (isset($GLOBALS['cfg']['RecodingEngine'])) { + $engine = $GLOBALS['cfg']['RecodingEngine']; + } + + /* Use user configuration */ + if (isset(self::$_enginemap[$engine])) { + if (function_exists(self::$_enginemap[$engine][0])) { + self::$_engine = self::$_enginemap[$engine][1]; + return; + } else { + Core::warnMissingExtension(self::$_enginemap[$engine][2]); + } + } + + /* Autodetection */ + foreach (self::$_engineorder as $engine) { + if (function_exists(self::$_enginemap[$engine][0])) { + self::$_engine = self::$_enginemap[$engine][1]; + return; + } + } + + /* Fallback to none conversion */ + self::$_engine = self::ENGINE_NONE; + } + + /** + * Setter for engine. Use with caution, mostly useful for testing. + * + * @param int $engine Engine enconding + * + * @return void + */ + public static function setEngine($engine) + { + self::$_engine = $engine; + } + + /** + * Checks whether there is any charset conversion supported + * + * @return bool + */ + public static function isSupported() + { + if (is_null(self::$_engine)) { + self::initEngine(); + } + return self::$_engine != self::ENGINE_NONE; + } + + /** + * Converts encoding of text according to parameters with detected + * conversion function. + * + * @param string $src_charset source charset + * @param string $dest_charset target charset + * @param string $what what to convert + * + * @return string converted text + * + * @access public + */ + public static function convertString($src_charset, $dest_charset, $what) + { + if ($src_charset == $dest_charset) { + return $what; + } + if (is_null(self::$_engine)) { + self::initEngine(); + } + switch (self::$_engine) { + case self::ENGINE_RECODE: + return recode_string( + $src_charset . '..' . $dest_charset, + $what + ); + case self::ENGINE_ICONV: + return iconv( + $src_charset, + $dest_charset . + (isset($GLOBALS['cfg']['IconvExtraParams']) ? $GLOBALS['cfg']['IconvExtraParams'] : ''), + $what + ); + case self::ENGINE_MB: + return mb_convert_encoding( + $what, + $dest_charset, + $src_charset + ); + default: + return $what; + } + } + + /** + * Detects whether Kanji encoding is available + * + * @return bool + */ + public static function canConvertKanji() + { + return $GLOBALS['lang'] == 'ja'; + } + + /** + * Setter for Kanji encodings. Use with caution, mostly useful for testing. + * + * @return string + */ + public static function getKanjiEncodings() + { + return self::$_kanji_encodings; + } + + /** + * Setter for Kanji encodings. Use with caution, mostly useful for testing. + * + * @param string $value Kanji encodings list + * + * @return void + */ + public static function setKanjiEncodings($value) + { + self::$_kanji_encodings = $value; + } + + /** + * Reverses SJIS & EUC-JP position in the encoding codes list + * + * @return void + */ + public static function kanjiChangeOrder() + { + $parts = explode(',', self::$_kanji_encodings); + if ($parts[1] == 'EUC-JP') { + self::$_kanji_encodings = 'ASCII,SJIS,EUC-JP,JIS'; + } else { + self::$_kanji_encodings = 'ASCII,EUC-JP,SJIS,JIS'; + } + } + + /** + * Kanji string encoding convert + * + * @param string $str the string to convert + * @param string $enc the destination encoding code + * @param string $kana set 'kana' convert to JIS-X208-kana + * + * @return string the converted string + */ + public static function kanjiStrConv($str, $enc, $kana) + { + if ($enc == '' && $kana == '') { + return $str; + } + + $string_encoding = mb_detect_encoding($str, self::$_kanji_encodings); + if ($string_encoding === false) { + $string_encoding = 'utf-8'; + } + + if ($kana == 'kana') { + $dist = mb_convert_kana($str, 'KV', $string_encoding); + $str = $dist; + } + if ($string_encoding != $enc && $enc != '') { + $dist = mb_convert_encoding($str, $enc, $string_encoding); + } else { + $dist = $str; + } + return $dist; + } + + + /** + * Kanji file encoding convert + * + * @param string $file the name of the file to convert + * @param string $enc the destination encoding code + * @param string $kana set 'kana' convert to JIS-X208-kana + * + * @return string the name of the converted file + */ + public static function kanjiFileConv($file, $enc, $kana) + { + if ($enc == '' && $kana == '') { + return $file; + } + $tmpfname = tempnam($GLOBALS['PMA_Config']->getUploadTempDir(), $enc); + $fpd = fopen($tmpfname, 'wb'); + $fps = fopen($file, 'r'); + self::kanjiChangeOrder(); + while (!feof($fps)) { + $line = fgets($fps, 4096); + $dist = self::kanjiStrConv($line, $enc, $kana); + fputs($fpd, $dist); + } // end while + self::kanjiChangeOrder(); + fclose($fps); + fclose($fpd); + unlink($file); + + return $tmpfname; + } + + /** + * Defines radio form fields to switch between encoding modes + * + * @return string xhtml code for the radio controls + */ + public static function kanjiEncodingForm() + { + return Template::get('encoding/kanji_encoding_form')->render(); + } + + /** + * Lists available encodings. + * + * @return array + */ + public static function listEncodings() + { + if (is_null(self::$_engine)) { + self::initEngine(); + } + /* Most engines do not support listing */ + if (self::$_engine != self::ENGINE_MB) { + return $GLOBALS['cfg']['AvailableCharsets']; + } + + return array_intersect( + array_map('strtolower', mb_list_encodings()), + $GLOBALS['cfg']['AvailableCharsets'] + ); + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Engines/Bdb.php b/php/apps/phpmyadmin49/libraries/classes/Engines/Bdb.php new file mode 100644 index 00000000..cde9480c --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Engines/Bdb.php @@ -0,0 +1,75 @@ + array( + 'title' => __('Version information'), + ), + 'bdb_cache_size' => array( + 'type' => PMA_ENGINE_DETAILS_TYPE_SIZE, + ), + 'bdb_home' => array(), + 'bdb_log_buffer_size' => array( + 'type' => PMA_ENGINE_DETAILS_TYPE_SIZE, + ), + 'bdb_logdir' => array(), + 'bdb_max_lock' => array( + 'type' => PMA_ENGINE_DETAILS_TYPE_NUMERIC, + ), + 'bdb_shared_data' => array(), + 'bdb_tmpdir' => array(), + 'bdb_data_direct' => array(), + 'bdb_lock_detect' => array(), + 'bdb_log_direct' => array(), + 'bdb_no_recover' => array(), + 'bdb_no_sync' => array(), + 'skip_sync_bdb_logs' => array(), + 'sync_bdb_logs' => array(), + ); + } + + /** + * Returns the pattern to be used in the query for SQL variables + * related to this storage engine + * + * @return string LIKE pattern + */ + public function getVariablesLikePattern() + { + return '%bdb%'; + } + + /** + * returns string with filename for the MySQL helppage + * about this storage engine + * + * @return string mysql helppage filename + */ + public function getMysqlHelpPage() + { + return 'bdb'; + } +} + diff --git a/php/apps/phpmyadmin49/libraries/classes/Engines/Berkeleydb.php b/php/apps/phpmyadmin49/libraries/classes/Engines/Berkeleydb.php new file mode 100644 index 00000000..ad4f4605 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Engines/Berkeleydb.php @@ -0,0 +1,18 @@ + array( + 'title' => __('Data home directory'), + 'desc' => __( + 'The common part of the directory path for all InnoDB data ' + . 'files.' + ), + ), + 'innodb_data_file_path' => array( + 'title' => __('Data files'), + ), + 'innodb_autoextend_increment' => array( + 'title' => __('Autoextend increment'), + 'desc' => __( + 'The increment size for extending the size of an autoextending ' + . 'tablespace when it becomes full.' + ), + 'type' => PMA_ENGINE_DETAILS_TYPE_NUMERIC, + ), + 'innodb_buffer_pool_size' => array( + 'title' => __('Buffer pool size'), + 'desc' => __( + 'The size of the memory buffer InnoDB uses to cache data and ' + . 'indexes of its tables.' + ), + 'type' => PMA_ENGINE_DETAILS_TYPE_SIZE, + ), + 'innodb_additional_mem_pool_size' => array( + 'title' => 'innodb_additional_mem_pool_size', + 'type' => PMA_ENGINE_DETAILS_TYPE_SIZE, + ), + 'innodb_buffer_pool_awe_mem_mb' => array( + 'type' => PMA_ENGINE_DETAILS_TYPE_SIZE, + ), + 'innodb_checksums' => array(), + 'innodb_commit_concurrency' => array(), + 'innodb_concurrency_tickets' => array( + 'type' => PMA_ENGINE_DETAILS_TYPE_NUMERIC, + ), + 'innodb_doublewrite' => array(), + 'innodb_fast_shutdown' => array(), + 'innodb_file_io_threads' => array( + 'type' => PMA_ENGINE_DETAILS_TYPE_NUMERIC, + ), + 'innodb_file_per_table' => array(), + 'innodb_flush_log_at_trx_commit' => array(), + 'innodb_flush_method' => array(), + 'innodb_force_recovery' => array(), + 'innodb_lock_wait_timeout' => array( + 'type' => PMA_ENGINE_DETAILS_TYPE_NUMERIC, + ), + 'innodb_locks_unsafe_for_binlog' => array(), + 'innodb_log_arch_dir' => array(), + 'innodb_log_archive' => array(), + 'innodb_log_buffer_size' => array( + 'type' => PMA_ENGINE_DETAILS_TYPE_SIZE, + ), + 'innodb_log_file_size' => array( + 'type' => PMA_ENGINE_DETAILS_TYPE_SIZE, + ), + 'innodb_log_files_in_group' => array( + 'type' => PMA_ENGINE_DETAILS_TYPE_NUMERIC, + ), + 'innodb_log_group_home_dir' => array(), + 'innodb_max_dirty_pages_pct' => array( + 'type' => PMA_ENGINE_DETAILS_TYPE_NUMERIC, + ), + 'innodb_max_purge_lag' => array(), + 'innodb_mirrored_log_groups' => array( + 'type' => PMA_ENGINE_DETAILS_TYPE_NUMERIC, + ), + 'innodb_open_files' => array( + 'type' => PMA_ENGINE_DETAILS_TYPE_NUMERIC, + ), + 'innodb_support_xa' => array(), + 'innodb_sync_spin_loops' => array( + 'type' => PMA_ENGINE_DETAILS_TYPE_NUMERIC, + ), + 'innodb_table_locks' => array( + 'type' => PMA_ENGINE_DETAILS_TYPE_BOOLEAN, + ), + 'innodb_thread_concurrency' => array( + 'type' => PMA_ENGINE_DETAILS_TYPE_NUMERIC, + ), + 'innodb_thread_sleep_delay' => array( + 'type' => PMA_ENGINE_DETAILS_TYPE_NUMERIC, + ), + ); + } + + /** + * Returns the pattern to be used in the query for SQL variables + * related to InnoDb storage engine + * + * @return string SQL query LIKE pattern + */ + public function getVariablesLikePattern() + { + return 'innodb\\_%'; + } + + /** + * Get information pages + * + * @return array detail pages + */ + public function getInfoPages() + { + if ($this->support < PMA_ENGINE_SUPPORT_YES) { + return array(); + } + $pages = array(); + $pages['Bufferpool'] = __('Buffer Pool'); + $pages['Status'] = __('InnoDB Status'); + + return $pages; + } + + /** + * returns html tables with stats over inno db buffer pool + * + * @return string html table with stats + */ + public function getPageBufferpool() + { + // The following query is only possible because we know + // that we are on MySQL 5 here (checked above)! + // side note: I love MySQL 5 for this. :-) + $sql + = ' + SHOW STATUS + WHERE Variable_name LIKE \'Innodb\\_buffer\\_pool\\_%\' + OR Variable_name = \'Innodb_page_size\';'; + $status = $GLOBALS['dbi']->fetchResult($sql, 0, 1); + + $output = '' . "\n" + . ' ' . "\n" + . ' ' . "\n" + . ' ' . "\n" + . ' ' . "\n" + . ' ' . "\n" + . ' ' . "\n" + . ' ' . "\n" + . ' ' . "\n" + . ' ' . "\n" + . ' ' . "\n" + . ' ' . "\n" + . ' ' . "\n" + . ' ' . "\n" + . ' ' . "\n" + . ' ' . "\n" + . ' ' . "\n" + . ' ' . "\n" + . ' ' . "\n" + . ' ' . "\n" + . ' ' . "\n" + . ' ' . "\n" + . ' ' . "\n" + . ' ' . "\n" + . ' ' . "\n" + . ' ' . "\n" + . ' ' . "\n" + . ' '; + + // not present at least since MySQL 5.1.40 + if (isset($status['Innodb_buffer_pool_pages_latched'])) { + $output .= ' ' + . ' ' + . ' ' + . ' '; + } + + $output .= ' ' . "\n" + . '
        ' . "\n" + . ' ' . __('Buffer Pool Usage') . "\n" + . '
        ' . "\n" + . ' ' . __('Total') . "\n" + . ' : ' + . Util::formatNumber( + $status['Innodb_buffer_pool_pages_total'], + 0 + ) + . ' ' . __('pages') + . ' / ' + . join( + ' ', + Util::formatByteDown( + $status['Innodb_buffer_pool_pages_total'] + * $status['Innodb_page_size'] + ) + ) . "\n" + . '
        ' . __('Free pages') . '' + . Util::formatNumber( + $status['Innodb_buffer_pool_pages_free'], + 0 + ) + . '
        ' . __('Dirty pages') . '' + . Util::formatNumber( + $status['Innodb_buffer_pool_pages_dirty'], + 0 + ) + . '
        ' . __('Pages containing data') . '' + . Util::formatNumber( + $status['Innodb_buffer_pool_pages_data'], + 0 + ) . "\n" + . '
        ' . __('Pages to be flushed') . '' + . Util::formatNumber( + $status['Innodb_buffer_pool_pages_flushed'], + 0 + ) . "\n" + . '
        ' . __('Busy pages') . '' + . Util::formatNumber( + $status['Innodb_buffer_pool_pages_misc'], + 0 + ) . "\n" + . '
        ' . __('Latched pages') . '' + . Util::formatNumber( + $status['Innodb_buffer_pool_pages_latched'], + 0 + ) + . '
        ' . "\n\n" + . '' . "\n" + . ' ' . "\n" + . ' ' . "\n" + . ' ' . "\n" + . ' ' . "\n" + . ' ' . "\n" + . ' ' . "\n" + . ' ' . "\n" + . ' ' . "\n" + . ' ' . "\n" + . ' ' . "\n" + . ' ' . "\n" + . ' ' . "\n" + . ' ' . "\n" + . ' ' . "\n" + . ' ' . "\n" + . ' ' . "\n" + . ' ' . "\n" + . ' ' . "\n" + . ' ' . "\n" + . ' ' . "\n" + . ' ' . "\n" + . ' ' . "\n" + . ' ' . "\n" + . ' ' . "\n" + . ' ' . "\n" + . ' ' . "\n" + . ' ' . "\n" + . '
        ' . "\n" + . ' ' . __('Buffer Pool Activity') . "\n" + . '
        ' . __('Read requests') . '' + . Util::formatNumber( + $status['Innodb_buffer_pool_read_requests'], + 0 + ) . "\n" + . '
        ' . __('Write requests') . '' + . Util::formatNumber( + $status['Innodb_buffer_pool_write_requests'], + 0 + ) . "\n" + . '
        ' . __('Read misses') . '' + . Util::formatNumber( + $status['Innodb_buffer_pool_reads'], + 0 + ) . "\n" + . '
        ' . __('Write waits') . '' + . Util::formatNumber( + $status['Innodb_buffer_pool_wait_free'], + 0 + ) . "\n" + . '
        ' . __('Read misses in %') . '' + . ($status['Innodb_buffer_pool_read_requests'] == 0 + ? '---' + : htmlspecialchars( + Util::formatNumber( + $status['Innodb_buffer_pool_reads'] * 100 + / $status['Innodb_buffer_pool_read_requests'], + 3, + 2 + ) + ) . ' %') . "\n" + . '
        ' . __('Write waits in %') . '' + . ($status['Innodb_buffer_pool_write_requests'] == 0 + ? '---' + : htmlspecialchars( + Util::formatNumber( + $status['Innodb_buffer_pool_wait_free'] * 100 + / $status['Innodb_buffer_pool_write_requests'], + 3, + 2 + ) + ) . ' %') . "\n" + . '
        ' . "\n"; + + return $output; + } + + /** + * returns InnoDB status + * + * @return string result of SHOW ENGINE INNODB STATUS inside pre tags + */ + public function getPageStatus() + { + return '
        ' . "\n"
        +        . htmlspecialchars(
        +            $GLOBALS['dbi']->fetchValue('SHOW ENGINE INNODB STATUS;', 0, 'Status')
        +        ) . "\n"
        +        . '
        ' . "\n"; + } + + /** + * returns string with filename for the MySQL helppage + * about this storage engine + * + * @return string mysql helppage filename + */ + public function getMysqlHelpPage() + { + return 'innodb-storage-engine'; + } + + /** + * Gets the InnoDB plugin version number + * + * @return string the version number, or empty if not running as a plugin + */ + public function getInnodbPluginVersion() + { + return $GLOBALS['dbi']->fetchValue('SELECT @@innodb_version;'); + } + + /** + * Gets the InnoDB file format + * + * (do not confuse this with phpMyAdmin's storage engine plugins!) + * + * @return string the InnoDB file format + */ + public function getInnodbFileFormat() + { + return $GLOBALS['dbi']->fetchValue( + "SHOW GLOBAL VARIABLES LIKE 'innodb_file_format';", + 0, + 1 + ); + } + + /** + * Verifies if this server supports the innodb_file_per_table feature + * + * (do not confuse this with phpMyAdmin's storage engine plugins!) + * + * @return boolean whether this feature is supported or not + */ + public function supportsFilePerTable() + { + return ( + $GLOBALS['dbi']->fetchValue( + "SHOW GLOBAL VARIABLES LIKE 'innodb_file_per_table';", + 0, + 1 + ) == 'ON' + ); + } +} + diff --git a/php/apps/phpmyadmin49/libraries/classes/Engines/Memory.php b/php/apps/phpmyadmin49/libraries/classes/Engines/Memory.php new file mode 100644 index 00000000..b2a8b9fc --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Engines/Memory.php @@ -0,0 +1,33 @@ + array( + 'type' => PMA_ENGINE_DETAILS_TYPE_SIZE, + ), + ); + } +} + diff --git a/php/apps/phpmyadmin49/libraries/classes/Engines/Merge.php b/php/apps/phpmyadmin49/libraries/classes/Engines/Merge.php new file mode 100644 index 00000000..e610c8b6 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Engines/Merge.php @@ -0,0 +1,20 @@ + array( + 'title' => __('Data pointer size'), + 'desc' => __( + 'The default pointer size in bytes, to be used by CREATE TABLE ' + . 'for MyISAM tables when no MAX_ROWS option is specified.' + ), + 'type' => PMA_ENGINE_DETAILS_TYPE_SIZE, + ), + 'myisam_recover_options' => array( + 'title' => __('Automatic recovery mode'), + 'desc' => __( + 'The mode for automatic recovery of crashed MyISAM tables, as ' + . 'set via the --myisam-recover server startup option.' + ), + ), + 'myisam_max_sort_file_size' => array( + 'title' => __('Maximum size for temporary sort files'), + 'desc' => __( + 'The maximum size of the temporary file MySQL is allowed to use ' + . 'while re-creating a MyISAM index (during REPAIR TABLE, ALTER ' + . 'TABLE, or LOAD DATA INFILE).' + ), + 'type' => PMA_ENGINE_DETAILS_TYPE_SIZE, + ), + 'myisam_max_extra_sort_file_size' => array( + 'title' => __('Maximum size for temporary files on index creation'), + 'desc' => __( + 'If the temporary file used for fast MyISAM index creation ' + . 'would be larger than using the key cache by the amount ' + . 'specified here, prefer the key cache method.' + ), + 'type' => PMA_ENGINE_DETAILS_TYPE_SIZE, + ), + 'myisam_repair_threads' => array( + 'title' => __('Repair threads'), + 'desc' => __( + 'If this value is greater than 1, MyISAM table indexes are ' + . 'created in parallel (each index in its own thread) during ' + . 'the repair by sorting process.' + ), + 'type' => PMA_ENGINE_DETAILS_TYPE_NUMERIC, + ), + 'myisam_sort_buffer_size' => array( + 'title' => __('Sort buffer size'), + 'desc' => __( + 'The buffer that is allocated when sorting MyISAM indexes ' + . 'during a REPAIR TABLE or when creating indexes with CREATE ' + . 'INDEX or ALTER TABLE.' + ), + 'type' => PMA_ENGINE_DETAILS_TYPE_SIZE, + ), + 'myisam_stats_method' => array(), + 'delay_key_write' => array(), + 'bulk_insert_buffer_size' => array( + 'type' => PMA_ENGINE_DETAILS_TYPE_SIZE, + ), + 'skip_external_locking' => array(), + ); + } +} + diff --git a/php/apps/phpmyadmin49/libraries/classes/Engines/Ndbcluster.php b/php/apps/phpmyadmin49/libraries/classes/Engines/Ndbcluster.php new file mode 100644 index 00000000..49e87ae2 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Engines/Ndbcluster.php @@ -0,0 +1,53 @@ + array(), + ); + } + + /** + * Returns the pattern to be used in the query for SQL variables + * related to NDBCLUSTER storage engine + * + * @return string SQL query LIKE pattern + */ + public function getVariablesLikePattern() + { + return 'ndb\\_%'; + } + + /** + * Returns string with filename for the MySQL help page + * about this storage engine + * + * @return string mysql helppage filename + */ + public function getMysqlHelpPage() + { + return 'ndbcluster'; + } +} + diff --git a/php/apps/phpmyadmin49/libraries/classes/Engines/Pbxt.php b/php/apps/phpmyadmin49/libraries/classes/Engines/Pbxt.php new file mode 100644 index 00000000..9be40e7d --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Engines/Pbxt.php @@ -0,0 +1,193 @@ + array( + 'title' => __('Index cache size'), + 'desc' => __( + 'This is the amount of memory allocated to the' + . ' index cache. Default value is 32MB. The memory' + . ' allocated here is used only for caching index pages.' + ), + 'type' => PMA_ENGINE_DETAILS_TYPE_SIZE, + ), + 'pbxt_record_cache_size' => array( + 'title' => __('Record cache size'), + 'desc' => __( + 'This is the amount of memory allocated to the' + . ' record cache used to cache table data. The default' + . ' value is 32MB. This memory is used to cache changes to' + . ' the handle data (.xtd) and row pointer (.xtr) files.' + ), + 'type' => PMA_ENGINE_DETAILS_TYPE_SIZE, + ), + 'pbxt_log_cache_size' => array( + 'title' => __('Log cache size'), + 'desc' => __( + 'The amount of memory allocated to the' + . ' transaction log cache used to cache on transaction log' + . ' data. The default is 16MB.' + ), + 'type' => PMA_ENGINE_DETAILS_TYPE_SIZE, + ), + 'pbxt_log_file_threshold' => array( + 'title' => __('Log file threshold'), + 'desc' => __( + 'The size of a transaction log before rollover,' + . ' and a new log is created. The default value is 16MB.' + ), + 'type' => PMA_ENGINE_DETAILS_TYPE_SIZE, + ), + 'pbxt_transaction_buffer_size' => array( + 'title' => __('Transaction buffer size'), + 'desc' => __( + 'The size of the global transaction log buffer' + . ' (the engine allocates 2 buffers of this size).' + . ' The default is 1MB.' + ), + 'type' => PMA_ENGINE_DETAILS_TYPE_SIZE, + ), + 'pbxt_checkpoint_frequency' => array( + 'title' => __('Checkpoint frequency'), + 'desc' => __( + 'The amount of data written to the transaction' + . ' log before a checkpoint is performed.' + . ' The default value is 24MB.' + ), + 'type' => PMA_ENGINE_DETAILS_TYPE_SIZE, + ), + 'pbxt_data_log_threshold' => array( + 'title' => __('Data log threshold'), + 'desc' => __( + 'The maximum size of a data log file. The default' + . ' value is 64MB. PBXT can create a maximum of 32000 data' + . ' logs, which are used by all tables. So the value of' + . ' this variable can be increased to increase the total' + . ' amount of data that can be stored in the database.' + ), + 'type' => PMA_ENGINE_DETAILS_TYPE_SIZE, + ), + 'pbxt_garbage_threshold' => array( + 'title' => __('Garbage threshold'), + 'desc' => __( + 'The percentage of garbage in a data log file' + . ' before it is compacted. This is a value between 1 and' + . ' 99. The default is 50.' + ), + 'type' => PMA_ENGINE_DETAILS_TYPE_NUMERIC, + ), + 'pbxt_log_buffer_size' => array( + 'title' => __('Log buffer size'), + 'desc' => __( + 'The size of the buffer used when writing a data' + . ' log. The default is 256MB. The engine allocates one' + . ' buffer per thread, but only if the thread is required' + . ' to write a data log.' + ), + 'type' => PMA_ENGINE_DETAILS_TYPE_SIZE, + ), + 'pbxt_data_file_grow_size' => array( + 'title' => __('Data file grow size'), + 'desc' => __('The grow size of the handle data (.xtd) files.'), + 'type' => PMA_ENGINE_DETAILS_TYPE_SIZE, + ), + 'pbxt_row_file_grow_size' => array( + 'title' => __('Row file grow size'), + 'desc' => __('The grow size of the row pointer (.xtr) files.'), + 'type' => PMA_ENGINE_DETAILS_TYPE_SIZE, + ), + 'pbxt_log_file_count' => array( + 'title' => __('Log file count'), + 'desc' => __( + 'This is the number of transaction log files' + . ' (pbxt/system/xlog*.xt) the system will maintain. If the' + . ' number of logs exceeds this value then old logs will be' + . ' deleted, otherwise they are renamed and given the next' + . ' highest number.' + ), + 'type' => PMA_ENGINE_DETAILS_TYPE_NUMERIC, + ), + ); + } + + /** + * returns the pbxt engine specific handling for + * PMA_ENGINE_DETAILS_TYPE_SIZE variables. + * + * @param string $formatted_size the size expression (for example 8MB) + * + * @return string the formatted value and its unit + */ + public function resolveTypeSize($formatted_size) + { + if (preg_match('/^[0-9]+[a-zA-Z]+$/', $formatted_size)) { + $value = Util::extractValueFromFormattedSize( + $formatted_size + ); + } else { + $value = $formatted_size; + } + + return Util::formatByteDown($value); + } + + //-------------------- + /** + * Get information about pages + * + * @return array Information about pages + */ + public function getInfoPages() + { + $pages = array(); + $pages['Documentation'] = __('Documentation'); + + return $pages; + } + + //-------------------- + /** + * Get content of documentation page + * + * @return string + */ + public function getPageDocumentation() + { + $output = '

        ' . sprintf( + __( + 'Documentation and further information about PBXT' + . ' can be found on the %sPrimeBase XT Home Page%s.' + ), + '', + '' + ) + . '

        ' . "\n"; + + return $output; + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Engines/PerformanceSchema.php b/php/apps/phpmyadmin49/libraries/classes/Engines/PerformanceSchema.php new file mode 100644 index 00000000..9c627048 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Engines/PerformanceSchema.php @@ -0,0 +1,29 @@ + 'Internal error', + E_ERROR => 'Error', + E_WARNING => 'Warning', + E_PARSE => 'Parsing Error', + E_NOTICE => 'Notice', + E_CORE_ERROR => 'Core Error', + E_CORE_WARNING => 'Core Warning', + E_COMPILE_ERROR => 'Compile Error', + E_COMPILE_WARNING => 'Compile Warning', + E_USER_ERROR => 'User Error', + E_USER_WARNING => 'User Warning', + E_USER_NOTICE => 'User Notice', + E_STRICT => 'Runtime Notice', + E_DEPRECATED => 'Deprecation Notice', + E_RECOVERABLE_ERROR => 'Catchable Fatal Error', + ); + + /** + * Error levels + * + * @var array + */ + static public $errorlevel = array ( + 0 => 'error', + E_ERROR => 'error', + E_WARNING => 'error', + E_PARSE => 'error', + E_NOTICE => 'notice', + E_CORE_ERROR => 'error', + E_CORE_WARNING => 'error', + E_COMPILE_ERROR => 'error', + E_COMPILE_WARNING => 'error', + E_USER_ERROR => 'error', + E_USER_WARNING => 'error', + E_USER_NOTICE => 'notice', + E_STRICT => 'notice', + E_DEPRECATED => 'notice', + E_RECOVERABLE_ERROR => 'error', + ); + + /** + * The file in which the error occurred + * + * @var string + */ + protected $file = ''; + + /** + * The line in which the error occurred + * + * @var integer + */ + protected $line = 0; + + /** + * Holds the backtrace for this error + * + * @var array + */ + protected $backtrace = array(); + + /** + * Hide location of errors + */ + protected $hide_location = false; + + /** + * Constructor + * + * @param integer $errno error number + * @param string $errstr error message + * @param string $errfile file + * @param integer $errline line + */ + public function __construct($errno, $errstr, $errfile, $errline) + { + $this->setNumber($errno); + $this->setMessage($errstr, false); + $this->setFile($errfile); + $this->setLine($errline); + + // This function can be disabled in php.ini + if (function_exists('debug_backtrace')) { + $backtrace = @debug_backtrace(); + // remove last three calls: + // debug_backtrace(), handleError() and addError() + $backtrace = array_slice($backtrace, 3); + } else { + $backtrace = array(); + } + + $this->setBacktrace($backtrace); + } + + /** + * Process backtrace to avoid path disclossures, objects and so on + * + * @param array $backtrace backtrace + * + * @return array + */ + public static function processBacktrace(array $backtrace) + { + $result = array(); + + $members = array('line', 'function', 'class', 'type'); + + foreach ($backtrace as $idx => $step) { + /* Create new backtrace entry */ + $result[$idx] = array(); + + /* Make path relative */ + if (isset($step['file'])) { + $result[$idx]['file'] = self::relPath($step['file']); + } + + /* Store members we want */ + foreach ($members as $name) { + if (isset($step[$name])) { + $result[$idx][$name] = $step[$name]; + } + } + + /* Store simplified args */ + if (isset($step['args'])) { + foreach ($step['args'] as $key => $arg) { + $result[$idx]['args'][$key] = self::getArg($arg, $step['function']); + } + } + } + + return $result; + } + + /** + * Toggles location hiding + * + * @param boolean $hide Whether to hide + * + * @return void + */ + public function setHideLocation($hide) + { + $this->hide_location = $hide; + } + + /** + * sets PhpMyAdmin\Error::$_backtrace + * + * We don't store full arguments to avoid wakeup or memory problems. + * + * @param array $backtrace backtrace + * + * @return void + */ + public function setBacktrace(array $backtrace) + { + $this->backtrace = self::processBacktrace($backtrace); + } + + /** + * sets PhpMyAdmin\Error::$_line + * + * @param integer $line the line + * + * @return void + */ + public function setLine($line) + { + $this->line = $line; + } + + /** + * sets PhpMyAdmin\Error::$_file + * + * @param string $file the file + * + * @return void + */ + public function setFile($file) + { + $this->file = self::relPath($file); + } + + + /** + * returns unique PhpMyAdmin\Error::$hash, if not exists it will be created + * + * @return string PhpMyAdmin\Error::$hash + */ + public function getHash() + { + try { + $backtrace = serialize($this->getBacktrace()); + } catch(Exception $e) { + $backtrace = ''; + } + if ($this->hash === null) { + $this->hash = md5( + $this->getNumber() . + $this->getMessage() . + $this->getFile() . + $this->getLine() . + $backtrace + ); + } + + return $this->hash; + } + + /** + * returns PhpMyAdmin\Error::$_backtrace for first $count frames + * pass $count = -1 to get full backtrace. + * The same can be done by not passing $count at all. + * + * @param integer $count Number of stack frames. + * + * @return array PhpMyAdmin\Error::$_backtrace + */ + public function getBacktrace($count = -1) + { + if ($count != -1) { + return array_slice($this->backtrace, 0, $count); + } + return $this->backtrace; + } + + /** + * returns PhpMyAdmin\Error::$file + * + * @return string PhpMyAdmin\Error::$file + */ + public function getFile() + { + return $this->file; + } + + /** + * returns PhpMyAdmin\Error::$line + * + * @return integer PhpMyAdmin\Error::$line + */ + public function getLine() + { + return $this->line; + } + + /** + * returns type of error + * + * @return string type of error + */ + public function getType() + { + return self::$errortype[$this->getNumber()]; + } + + /** + * returns level of error + * + * @return string level of error + */ + public function getLevel() + { + return self::$errorlevel[$this->getNumber()]; + } + + /** + * returns title prepared for HTML Title-Tag + * + * @return string HTML escaped and truncated title + */ + public function getHtmlTitle() + { + return htmlspecialchars( + mb_substr($this->getTitle(), 0, 100) + ); + } + + /** + * returns title for error + * + * @return string + */ + public function getTitle() + { + return $this->getType() . ': ' . $this->getMessage(); + } + + /** + * Get HTML backtrace + * + * @return string + */ + public function getBacktraceDisplay() + { + return self::formatBacktrace( + $this->getBacktrace(), + "
        \n", + "
        \n" + ); + } + + /** + * return formatted backtrace field + * + * @param array $backtrace Backtrace data + * @param string $separator Arguments separator to use + * @param string $lines Lines separator to use + * + * @return string formatted backtrace + */ + public static function formatBacktrace(array $backtrace, $separator, $lines) + { + $retval = ''; + + foreach ($backtrace as $step) { + if (isset($step['file']) && isset($step['line'])) { + $retval .= self::relPath($step['file']) + . '#' . $step['line'] . ': '; + } + if (isset($step['class'])) { + $retval .= $step['class'] . $step['type']; + } + $retval .= self::getFunctionCall($step, $separator); + $retval .= $lines; + } + + return $retval; + } + + /** + * Formats function call in a backtrace + * + * @param array $step backtrace step + * @param string $separator Arguments separator to use + * + * @return string + */ + public static function getFunctionCall(array $step, $separator) + { + $retval = $step['function'] . '('; + if (isset($step['args'])) { + if (count($step['args']) > 1) { + $retval .= $separator; + foreach ($step['args'] as $arg) { + $retval .= "\t"; + $retval .= $arg; + $retval .= ',' . $separator; + } + } elseif (count($step['args']) > 0) { + foreach ($step['args'] as $arg) { + $retval .= $arg; + } + } + } + $retval .= ')'; + return $retval; + } + + /** + * Get a single function argument + * + * if $function is one of include/require + * the $arg is converted to a relative path + * + * @param string $arg argument to process + * @param string $function function name + * + * @return string + */ + public static function getArg($arg, $function) + { + $retval = ''; + $include_functions = array( + 'include', + 'include_once', + 'require', + 'require_once', + ); + $connect_functions = array( + 'mysql_connect', + 'mysql_pconnect', + 'mysqli_connect', + 'mysqli_real_connect', + 'connect', + '_realConnect' + ); + + if (in_array($function, $include_functions)) { + $retval .= self::relPath($arg); + } elseif (in_array($function, $connect_functions) + && getType($arg) === 'string' + ) { + $retval .= getType($arg) . ' ********'; + } elseif (is_scalar($arg)) { + $retval .= getType($arg) . ' ' + . htmlspecialchars(var_export($arg, true)); + } elseif (is_object($arg)) { + $retval .= ''; + } else { + $retval .= getType($arg); + } + + return $retval; + } + + /** + * Gets the error as string of HTML + * + * @return string + */ + public function getDisplay() + { + $this->isDisplayed(true); + $retval = '
        '; + if (! $this->isUserError()) { + $retval .= '' . $this->getType() . ''; + $retval .= ' in ' . $this->getFile() . '#' . $this->getLine(); + $retval .= "
        \n"; + } + $retval .= $this->getMessage(); + if (! $this->isUserError()) { + $retval .= "
        \n"; + $retval .= "
        \n"; + $retval .= "Backtrace
        \n"; + $retval .= "
        \n"; + $retval .= $this->getBacktraceDisplay(); + } + $retval .= '
        '; + + return $retval; + } + + /** + * whether this error is a user error + * + * @return boolean + */ + public function isUserError() + { + return $this->hide_location || + ($this->getNumber() & (E_USER_WARNING | E_USER_ERROR | E_USER_NOTICE)); + } + + /** + * return short relative path to phpMyAdmin basedir + * + * prevent path disclosure in error message, + * and make users feel safe to submit error reports + * + * @param string $path path to be shorten + * + * @return string shortened path + */ + public static function relPath($path) + { + $dest = @realpath($path); + + /* Probably affected by open_basedir */ + if ($dest === false) { + return basename($path); + } + + $Ahere = explode( + DIRECTORY_SEPARATOR, + realpath(__DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . '..') + ); + $Adest = explode(DIRECTORY_SEPARATOR, $dest); + + $result = '.'; + // && count ($Adest)>0 && count($Ahere)>0 ) + while (implode(DIRECTORY_SEPARATOR, $Adest) != implode(DIRECTORY_SEPARATOR, $Ahere)) { + if (count($Ahere) > count($Adest)) { + array_pop($Ahere); + $result .= DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . '..'; + } else { + array_pop($Adest); + } + } + $path = $result . str_replace(implode(DIRECTORY_SEPARATOR, $Adest), '', $dest); + return str_replace( + DIRECTORY_SEPARATOR . PATH_SEPARATOR, + DIRECTORY_SEPARATOR, + $path + ); + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/ErrorHandler.php b/php/apps/phpmyadmin49/libraries/classes/ErrorHandler.php new file mode 100644 index 00000000..f99c3d27 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/ErrorHandler.php @@ -0,0 +1,585 @@ +error_reporting = error_reporting(); + } + + /** + * Destructor + * + * stores errors in session + * + */ + public function __destruct() + { + if (isset($_SESSION)) { + if (! isset($_SESSION['errors'])) { + $_SESSION['errors'] = array(); + } + + // remember only not displayed errors + foreach ($this->errors as $key => $error) { + /** + * We don't want to store all errors here as it would + * explode user session. + */ + if (count($_SESSION['errors']) >= 10) { + $error = new Error( + 0, + __('Too many error messages, some are not displayed.'), + __FILE__, + __LINE__ + ); + $_SESSION['errors'][$error->getHash()] = $error; + break; + } elseif (($error instanceof Error) + && ! $error->isDisplayed() + ) { + $_SESSION['errors'][$key] = $error; + } + } + } + } + + /** + * Toggles location hiding + * + * @param boolean $hide Whether to hide + * + * @return void + */ + public function setHideLocation($hide) + { + $this->hide_location = $hide; + } + + /** + * returns array with all errors + * + * @param bool $check Whether to check for session errors + * + * @return Error[] + */ + public function getErrors($check=true) + { + if ($check) { + $this->checkSavedErrors(); + } + return $this->errors; + } + + /** + * returns the errors occurred in the current run only. + * Does not include the errors saved in the SESSION + * + * @return Error[] + */ + public function getCurrentErrors() + { + return $this->errors; + } + + /** + * Pops recent errors from the storage + * + * @param int $count Old error count + * + * @return Error[] + */ + public function sliceErrors($count) + { + $errors = $this->getErrors(false); + $this->errors = array_splice($errors, 0, $count); + return array_splice($errors, $count); + } + + /** + * Error handler - called when errors are triggered/occurred + * + * This calls the addError() function, escaping the error string + * Ignores the errors wherever Error Control Operator (@) is used. + * + * @param integer $errno error number + * @param string $errstr error string + * @param string $errfile error file + * @param integer $errline error line + * + * @return void + */ + public function handleError($errno, $errstr, $errfile, $errline) + { + /** + * Check if Error Control Operator (@) was used, but still show + * user errors even in this case. + */ + if (error_reporting() == 0 && + $this->error_reporting != 0 && + ($errno & (E_USER_WARNING | E_USER_ERROR | E_USER_NOTICE)) == 0 + ) { + return; + } + $this->addError($errstr, $errno, $errfile, $errline, true); + } + + /** + * Add an error; can also be called directly (with or without escaping) + * + * The following error types cannot be handled with a user defined function: + * E_ERROR, E_PARSE, E_CORE_ERROR, E_CORE_WARNING, E_COMPILE_ERROR, + * E_COMPILE_WARNING, + * and most of E_STRICT raised in the file where set_error_handler() is called. + * + * Do not use the context parameter as we want to avoid storing the + * complete $GLOBALS inside $_SESSION['errors'] + * + * @param string $errstr error string + * @param integer $errno error number + * @param string $errfile error file + * @param integer $errline error line + * @param boolean $escape whether to escape the error string + * + * @return void + */ + public function addError($errstr, $errno, $errfile, $errline, $escape = true) + { + if ($escape) { + $errstr = htmlspecialchars($errstr); + } + // create error object + $error = new Error( + $errno, + $errstr, + $errfile, + $errline + ); + $error->setHideLocation($this->hide_location); + + // do not repeat errors + $this->errors[$error->getHash()] = $error; + + switch ($error->getNumber()) { + case E_STRICT: + case E_DEPRECATED: + case E_NOTICE: + case E_WARNING: + case E_CORE_WARNING: + case E_COMPILE_WARNING: + case E_RECOVERABLE_ERROR: + /* Avoid rendering BB code in PHP errors */ + $error->setBBCode(false); + break; + case E_USER_NOTICE: + case E_USER_WARNING: + case E_USER_ERROR: + // just collect the error + // display is called from outside + break; + case E_ERROR: + case E_PARSE: + case E_CORE_ERROR: + case E_COMPILE_ERROR: + default: + // FATAL error, display it and exit + $this->dispFatalError($error); + exit; + } + } + + /** + * trigger a custom error + * + * @param string $errorInfo error message + * @param integer $errorNumber error number + * + * @return void + */ + public function triggerError($errorInfo, $errorNumber = null) + { + // we could also extract file and line from backtrace + // and call handleError() directly + trigger_error($errorInfo, $errorNumber); + } + + /** + * display fatal error and exit + * + * @param Error $error the error + * + * @return void + */ + protected function dispFatalError($error) + { + if (! headers_sent()) { + $this->dispPageStart($error); + } + $error->display(); + $this->dispPageEnd(); + exit; + } + + /** + * Displays user errors not displayed + * + * @return void + */ + public function dispUserErrors() + { + echo $this->getDispUserErrors(); + } + + /** + * Renders user errors not displayed + * + * @return string + */ + public function getDispUserErrors() + { + $retval = ''; + foreach ($this->getErrors() as $error) { + if ($error->isUserError() && ! $error->isDisplayed()) { + $retval .= $error->getDisplay(); + } + } + return $retval; + } + + /** + * display HTML header + * + * @param Error $error the error + * + * @return void + */ + protected function dispPageStart($error = null) + { + Response::getInstance()->disable(); + echo ''; + if ($error) { + echo $error->getTitle(); + } else { + echo 'phpMyAdmin error reporting page'; + } + echo ''; + } + + /** + * display HTML footer + * + * @return void + */ + protected function dispPageEnd() + { + echo ''; + } + + /** + * renders errors not displayed + * + * @return string + */ + public function getDispErrors() + { + $retval = ''; + // display errors if SendErrorReports is set to 'ask'. + if ($GLOBALS['cfg']['SendErrorReports'] != 'never') { + foreach ($this->getErrors() as $error) { + if (! $error->isDisplayed()) { + $retval .= $error->getDisplay(); + } + } + } else { + $retval .= $this->getDispUserErrors(); + } + // if preference is not 'never' and + // there are 'actual' errors to be reported + if ($GLOBALS['cfg']['SendErrorReports'] != 'never' + && $this->countErrors() != $this->countUserErrors() + ) { + // add report button. + $retval .= '
        'php', + 'send_error_report' => '1', + 'server' => $GLOBALS['server'], + )); + $retval .= '' + . '' + . ''; + + if ($GLOBALS['cfg']['SendErrorReports'] == 'ask') { + // add ignore buttons + $retval .= ''; + } + $retval .= ''; + $retval .= '
        '; + } + return $retval; + } + + /** + * displays errors not displayed + * + * @return void + */ + public function dispErrors() + { + echo $this->getDispErrors(); + } + + /** + * look in session for saved errors + * + * @return void + */ + protected function checkSavedErrors() + { + if (isset($_SESSION['errors'])) { + + // restore saved errors + foreach ($_SESSION['errors'] as $hash => $error) { + if ($error instanceof Error && ! isset($this->errors[$hash])) { + $this->errors[$hash] = $error; + } + } + + // delete stored errors + $_SESSION['errors'] = array(); + unset($_SESSION['errors']); + } + } + + /** + * return count of errors + * + * @param bool $check Whether to check for session errors + * + * @return integer number of errors occurred + */ + public function countErrors($check=true) + { + return count($this->getErrors($check)); + } + + /** + * return count of user errors + * + * @return integer number of user errors occurred + */ + public function countUserErrors() + { + $count = 0; + if ($this->countErrors()) { + foreach ($this->getErrors() as $error) { + if ($error->isUserError()) { + $count++; + } + } + } + + return $count; + } + + /** + * whether use errors occurred or not + * + * @return boolean + */ + public function hasUserErrors() + { + return (bool) $this->countUserErrors(); + } + + /** + * whether errors occurred or not + * + * @return boolean + */ + public function hasErrors() + { + return (bool) $this->countErrors(); + } + + /** + * number of errors to be displayed + * + * @return integer number of errors to be displayed + */ + public function countDisplayErrors() + { + if ($GLOBALS['cfg']['SendErrorReports'] != 'never') { + return $this->countErrors(); + } + + return $this->countUserErrors(); + } + + /** + * whether there are errors to display or not + * + * @return boolean + */ + public function hasDisplayErrors() + { + return (bool) $this->countDisplayErrors(); + } + + /** + * Deletes previously stored errors in SESSION. + * Saves current errors in session as previous errors. + * Required to save current errors in case 'ask' + * + * @return void + */ + public function savePreviousErrors() + { + unset($_SESSION['prev_errors']); + $_SESSION['prev_errors'] = $GLOBALS['error_handler']->getCurrentErrors(); + } + + /** + * Function to check if there are any errors to be prompted. + * Needed because user warnings raised are + * also collected by global error handler. + * This distinguishes between the actual errors + * and user errors raised to warn user. + * + *@return boolean true if there are errors to be "prompted", false otherwise + */ + public function hasErrorsForPrompt() + { + return ( + $GLOBALS['cfg']['SendErrorReports'] != 'never' + && $this->countErrors() != $this->countUserErrors() + ); + } + + /** + * Function to report all the collected php errors. + * Must be called at the end of each script + * by the $GLOBALS['error_handler'] only. + * + * @return void + */ + public function reportErrors() + { + // if there're no actual errors, + if (!$this->hasErrors() + || $this->countErrors() == $this->countUserErrors() + ) { + // then simply return. + return; + } + // Delete all the prev_errors in session & store new prev_errors in session + $this->savePreviousErrors(); + $response = Response::getInstance(); + $jsCode = ''; + if ($GLOBALS['cfg']['SendErrorReports'] == 'always') { + if ($response->isAjax()) { + // set flag for automatic report submission. + $response->addJSON('_sendErrorAlways', '1'); + } else { + // send the error reports asynchronously & without asking user + $jsCode .= '$("#pma_report_errors_form").submit();' + . 'PMA_ajaxShowMessage( + PMA_messages["phpErrorsBeingSubmitted"], false + );'; + // js code to appropriate focusing, + $jsCode .= '$("html, body").animate({ + scrollTop:$(document).height() + }, "slow");'; + } + } elseif ($GLOBALS['cfg']['SendErrorReports'] == 'ask') { + //ask user whether to submit errors or not. + if (!$response->isAjax()) { + // js code to show appropriate msgs, event binding & focusing. + $jsCode = 'PMA_ajaxShowMessage(PMA_messages["phpErrorsFound"]);' + . '$("#pma_ignore_errors_popup").bind("click", function() { + PMA_ignorePhpErrors() + });' + . '$("#pma_ignore_all_errors_popup").bind("click", + function() { + PMA_ignorePhpErrors(false) + });' + . '$("#pma_ignore_errors_bottom").bind("click", function(e) { + e.preventDefault(); + PMA_ignorePhpErrors() + });' + . '$("#pma_ignore_all_errors_bottom").bind("click", + function(e) { + e.preventDefault(); + PMA_ignorePhpErrors(false) + });' + . '$("html, body").animate({ + scrollTop:$(document).height() + }, "slow");'; + } + } + // The errors are already sent from the response. + // Just focus on errors division upon load event. + $response->getFooter()->getScripts()->addCode($jsCode); + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/ErrorReport.php b/php/apps/phpmyadmin49/libraries/classes/ErrorReport.php new file mode 100644 index 00000000..d11462a6 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/ErrorReport.php @@ -0,0 +1,271 @@ +httpRequest = $httpRequest; + $this->submissionUrl = 'https://reports.phpmyadmin.net/incidents/create'; + $this->relation = new Relation(); + } + + /** + * Returns the pretty printed error report data collected from the + * current configuration or from the request parameters sent by the + * error reporting js code. + * + * @return string the report + */ + private function getPrettyData() + { + $report = $this->getData(); + + return json_encode($report, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES); + } + + /** + * Returns the error report data collected from the current configuration or + * from the request parameters sent by the error reporting js code. + * + * @param string $exceptionType whether exception is 'js' or 'php' + * + * @return array error report if success, Empty Array otherwise + */ + public function getData($exceptionType = 'js') + { + $relParams = $this->relation->getRelationsParam(); + // common params for both, php & js exceptions + $report = [ + "pma_version" => PMA_VERSION, + "browser_name" => PMA_USR_BROWSER_AGENT, + "browser_version" => PMA_USR_BROWSER_VER, + "user_os" => PMA_USR_OS, + "server_software" => $_SERVER['SERVER_SOFTWARE'], + "user_agent_string" => $_SERVER['HTTP_USER_AGENT'], + "locale" => $_COOKIE['pma_lang'], + "configuration_storage" => + is_null($relParams['db']) ? "disabled" : "enabled", + "php_version" => phpversion() + ]; + + if ($exceptionType == 'js') { + if (empty($_POST['exception'])) { + return []; + } + $exception = $_POST['exception']; + $exception["stack"] = $this->translateStacktrace($exception["stack"]); + + if (isset($exception["url"])) { + list($uri, $scriptName) = $this->sanitizeUrl($exception["url"]); + $exception["uri"] = $uri; + $report["script_name"] = $scriptName; + unset($exception["url"]); + } else if (isset($_POST["url"])) { + list($uri, $scriptName) = $this->sanitizeUrl($_POST["url"]); + $exception["uri"] = $uri; + $report["script_name"] = $scriptName; + unset($_POST["url"]); + } else { + $report["script_name"] = null; + } + + $report["exception_type"] = 'js'; + $report["exception"] = $exception; + if (isset($_POST['microhistory'])) { + $report["microhistory"] = $_POST['microhistory']; + } + + if (! empty($_POST['description'])) { + $report['steps'] = $_POST['description']; + } + } elseif ($exceptionType == 'php') { + $errors = []; + // create php error report + $i = 0; + if (!isset($_SESSION['prev_errors']) + || $_SESSION['prev_errors'] == '' + ) { + return []; + } + foreach ($_SESSION['prev_errors'] as $errorObj) { + /* @var $errorObj PhpMyAdmin\Error */ + if ($errorObj->getLine() + && $errorObj->getType() + && $errorObj->getNumber() != E_USER_WARNING + ) { + $errors[$i++] = [ + "lineNum" => $errorObj->getLine(), + "file" => $errorObj->getFile(), + "type" => $errorObj->getType(), + "msg" => $errorObj->getOnlyMessage(), + "stackTrace" => $errorObj->getBacktrace(5), + "stackhash" => $errorObj->getHash() + ]; + } + } + + // if there were no 'actual' errors to be submitted. + if ($i==0) { + return []; // then return empty array + } + $report["exception_type"] = 'php'; + $report["errors"] = $errors; + } else { + return []; + } + + return $report; + } + + /** + * Sanitize a url to remove the identifiable host name and extract the + * current script name from the url fragment + * + * It returns two things in an array. The first is the uri without the + * hostname and identifying query params. The second is the name of the + * php script in the url + * + * @param string $url the url to sanitize + * + * @return array the uri and script name + */ + private function sanitizeUrl($url) + { + $components = parse_url($url); + if (isset($components["fragment"]) + && preg_match("", $components["fragment"], $matches) + ) { + $uri = str_replace($matches[0], "", $components["fragment"]); + $url = "https://example.com/" . $uri; + $components = parse_url($url); + } + + // get script name + preg_match("<([a-zA-Z\-_\d\.]*\.php|js\/[a-zA-Z\-_\d\/\.]*\.js)$>", $components["path"], $matches); + if (count($matches) < 2) { + $scriptName = 'index.php'; + } else { + $scriptName = $matches[1]; + } + + // remove deployment specific details to make uri more generic + if (isset($components["query"])) { + parse_str($components["query"], $queryArray); + unset($queryArray["db"]); + unset($queryArray["table"]); + unset($queryArray["token"]); + unset($queryArray["server"]); + $query = http_build_query($queryArray); + } else { + $query = ''; + } + + $uri = $scriptName . "?" . $query; + return [$uri, $scriptName]; + } + + /** + * Sends report data to the error reporting server + * + * @param array $report the report info to be sent + * + * @return string|null|bool the reply of the server + */ + public function send(array $report) + { + $response = $this->httpRequest->create( + $this->submissionUrl, + "POST", + false, + json_encode($report), + "Content-Type: application/json" + ); + return $response; + } + + /** + * Translates the cumulative line numbers in the stack trace as well as sanitize + * urls and trim long lines in the context + * + * @param array $stack the stack trace + * + * @return array $stack the modified stack trace + */ + private function translateStacktrace(array $stack) + { + foreach ($stack as &$level) { + foreach ($level["context"] as &$line) { + if (mb_strlen($line) > 80) { + $line = mb_substr($line, 0, 75) . "//..."; + } + } + list($uri, $scriptName) = $this->sanitizeUrl($level["url"]); + $level["uri"] = $uri; + $level["scriptname"] = $scriptName; + unset($level["url"]); + } + unset($level); + return $stack; + } + + /** + * Generates the error report form to collect user description and preview the + * report before being sent + * + * @return string the form + */ + public function getForm() + { + $datas = [ + 'report_data' => $this->getPrettyData(), + 'hidden_inputs' => Url::getHiddenInputs(), + 'hidden_fields' => null, + ]; + + $reportData = $this->getData(); + if (!empty($reportData)) { + $datas['hidden_fields'] = Url::getHiddenFields($reportData); + } + + return Template::get('error/report_form')->render($datas); + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Export.php b/php/apps/phpmyadmin49/libraries/classes/Export.php new file mode 100644 index 00000000..e4d028b2 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Export.php @@ -0,0 +1,1101 @@ += 43; // see bug #4942 + + if (function_exists('gzencode') + && ((! ini_get('zlib.output_compression') + && ! self::isGzHandlerEnabled()) + || $GLOBALS['save_on_server'] + || $chromeAndGreaterThan43) + ) { + return true; + } + + return false; + } + + /** + * Output handler for all exports, if needed buffering, it stores data into + * $dump_buffer, otherwise it prints them out. + * + * @param string $line the insert statement + * + * @return bool Whether output succeeded + */ + public static function outputHandler($line) + { + global $time_start, $dump_buffer, $dump_buffer_len, $save_filename; + + // Kanji encoding convert feature + if ($GLOBALS['output_kanji_conversion']) { + $line = Encoding::kanjiStrConv( + $line, + $GLOBALS['knjenc'], + isset($GLOBALS['xkana']) ? $GLOBALS['xkana'] : '' + ); + } + + // If we have to buffer data, we will perform everything at once at the end + if ($GLOBALS['buffer_needed']) { + + $dump_buffer .= $line; + if ($GLOBALS['onfly_compression']) { + + $dump_buffer_len += strlen($line); + + if ($dump_buffer_len > $GLOBALS['memory_limit']) { + if ($GLOBALS['output_charset_conversion']) { + $dump_buffer = Encoding::convertString( + 'utf-8', + $GLOBALS['charset'], + $dump_buffer + ); + } + if ($GLOBALS['compression'] == 'gzip' + && self::gzencodeNeeded() + ) { + // as a gzipped file + // without the optional parameter level because it bugs + $dump_buffer = gzencode($dump_buffer); + } + if ($GLOBALS['save_on_server']) { + $write_result = @fwrite($GLOBALS['file_handle'], $dump_buffer); + // Here, use strlen rather than mb_strlen to get the length + // in bytes to compare against the number of bytes written. + if ($write_result != strlen($dump_buffer)) { + $GLOBALS['message'] = Message::error( + __('Insufficient space to save the file %s.') + ); + $GLOBALS['message']->addParam($save_filename); + return false; + } + } else { + echo $dump_buffer; + } + $dump_buffer = ''; + $dump_buffer_len = 0; + } + } else { + $time_now = time(); + if ($time_start >= $time_now + 30) { + $time_start = $time_now; + header('X-pmaPing: Pong'); + } // end if + } + } else { + if ($GLOBALS['asfile']) { + if ($GLOBALS['output_charset_conversion']) { + $line = Encoding::convertString( + 'utf-8', + $GLOBALS['charset'], + $line + ); + } + if ($GLOBALS['save_on_server'] && mb_strlen($line) > 0) { + $write_result = @fwrite($GLOBALS['file_handle'], $line); + // Here, use strlen rather than mb_strlen to get the length + // in bytes to compare against the number of bytes written. + if (! $write_result + || $write_result != strlen($line) + ) { + $GLOBALS['message'] = Message::error( + __('Insufficient space to save the file %s.') + ); + $GLOBALS['message']->addParam($save_filename); + return false; + } + $time_now = time(); + if ($time_start >= $time_now + 30) { + $time_start = $time_now; + header('X-pmaPing: Pong'); + } // end if + } else { + // We export as file - output normally + echo $line; + } + } else { + // We export as html - replace special chars + echo htmlspecialchars($line); + } + } + return true; + } // end of the 'self::outputHandler()' function + + /** + * Returns HTML containing the footer for a displayed export + * + * @param string $back_button the link for going Back + * @param string $refreshButton the link for refreshing page + * + * @return string $html the HTML output + */ + public static function getHtmlForDisplayedExportFooter($back_button, $refreshButton) + { + /** + * Close the html tags and add the footers for on-screen export + */ + $html = '' + . ' ' + . '
        ' + // bottom back button + . $back_button + . $refreshButton + . '
        ' + . '' . "\n"; + return $html; + } + + /** + * Computes the memory limit for export + * + * @return int $memory_limit the memory limit + */ + public static function getMemoryLimit() + { + $memory_limit = trim(ini_get('memory_limit')); + $memory_limit_num = (int)substr($memory_limit, 0, -1); + $lowerLastChar = strtolower(substr($memory_limit, -1)); + // 2 MB as default + if (empty($memory_limit) || '-1' == $memory_limit) { + $memory_limit = 2 * 1024 * 1024; + } elseif ($lowerLastChar == 'm') { + $memory_limit = $memory_limit_num * 1024 * 1024; + } elseif ($lowerLastChar == 'k') { + $memory_limit = $memory_limit_num * 1024; + } elseif ($lowerLastChar == 'g') { + $memory_limit = $memory_limit_num * 1024 * 1024 * 1024; + } else { + $memory_limit = (int)$memory_limit; + } + + // Some of memory is needed for other things and as threshold. + // During export I had allocated (see memory_get_usage function) + // approx 1.2MB so this comes from that. + if ($memory_limit > 1500000) { + $memory_limit -= 1500000; + } + + // Some memory is needed for compression, assume 1/3 + $memory_limit /= 8; + return $memory_limit; + } + + /** + * Return the filename and MIME type for export file + * + * @param string $export_type type of export + * @param string $remember_template whether to remember template + * @param ExportPlugin $export_plugin the export plugin + * @param string $compression compression asked + * @param string $filename_template the filename template + * + * @return string[] the filename template and mime type + */ + public static function getFilenameAndMimetype( + $export_type, $remember_template, $export_plugin, $compression, + $filename_template + ) { + if ($export_type == 'server') { + if (! empty($remember_template)) { + $GLOBALS['PMA_Config']->setUserValue( + 'pma_server_filename_template', + 'Export/file_template_server', + $filename_template + ); + } + } elseif ($export_type == 'database') { + if (! empty($remember_template)) { + $GLOBALS['PMA_Config']->setUserValue( + 'pma_db_filename_template', + 'Export/file_template_database', + $filename_template + ); + } + } else { + if (! empty($remember_template)) { + $GLOBALS['PMA_Config']->setUserValue( + 'pma_table_filename_template', + 'Export/file_template_table', + $filename_template + ); + } + } + $filename = Util::expandUserString($filename_template); + // remove dots in filename (coming from either the template or already + // part of the filename) to avoid a remote code execution vulnerability + $filename = Sanitize::sanitizeFilename($filename, $replaceDots = true); + + // Grab basic dump extension and mime type + // Check if the user already added extension; + // get the substring where the extension would be if it was included + $extension_start_pos = mb_strlen($filename) - mb_strlen( + $export_plugin->getProperties()->getExtension() + ) - 1; + $user_extension = mb_substr( + $filename, $extension_start_pos, mb_strlen($filename) + ); + $required_extension = "." . $export_plugin->getProperties()->getExtension(); + if (mb_strtolower($user_extension) != $required_extension) { + $filename .= $required_extension; + } + $mime_type = $export_plugin->getProperties()->getMimeType(); + + // If dump is going to be compressed, set correct mime_type and add + // compression to extension + if ($compression == 'gzip') { + $filename .= '.gz'; + $mime_type = 'application/x-gzip'; + } elseif ($compression == 'zip') { + $filename .= '.zip'; + $mime_type = 'application/zip'; + } + return array($filename, $mime_type); + } + + /** + * Open the export file + * + * @param string $filename the export filename + * @param boolean $quick_export whether it's a quick export or not + * + * @return array the full save filename, possible message and the file handle + */ + public static function openFile($filename, $quick_export) + { + $file_handle = null; + $message = ''; + $doNotSaveItOver = true; + + if(isset($_POST['quick_export_onserver_overwrite'])) { + $doNotSaveItOver = $_POST['quick_export_onserver_overwrite'] != 'saveitover'; + } + + $save_filename = Util::userDir($GLOBALS['cfg']['SaveDir']) + . preg_replace('@[/\\\\]@', '_', $filename); + + if (@file_exists($save_filename) + && ((! $quick_export && empty($_POST['onserver_overwrite'])) + || ($quick_export + && $doNotSaveItOver)) + ) { + $message = Message::error( + __( + 'File %s already exists on server, ' + . 'change filename or check overwrite option.' + ) + ); + $message->addParam($save_filename); + } elseif (@is_file($save_filename) && ! @is_writable($save_filename)) { + $message = Message::error( + __( + 'The web server does not have permission ' + . 'to save the file %s.' + ) + ); + $message->addParam($save_filename); + } elseif (! $file_handle = @fopen($save_filename, 'w')) { + $message = Message::error( + __( + 'The web server does not have permission ' + . 'to save the file %s.' + ) + ); + $message->addParam($save_filename); + } + return array($save_filename, $message, $file_handle); + } + + /** + * Close the export file + * + * @param resource $file_handle the export file handle + * @param string $dump_buffer the current dump buffer + * @param string $save_filename the export filename + * + * @return Message $message a message object (or empty string) + */ + public static function closeFile($file_handle, $dump_buffer, $save_filename) + { + $write_result = @fwrite($file_handle, $dump_buffer); + fclose($file_handle); + // Here, use strlen rather than mb_strlen to get the length + // in bytes to compare against the number of bytes written. + if (strlen($dump_buffer) > 0 + && (! $write_result || $write_result != strlen($dump_buffer)) + ) { + $message = new Message( + __('Insufficient space to save the file %s.'), + Message::ERROR, + array($save_filename) + ); + } else { + $message = new Message( + __('Dump has been saved to file %s.'), + Message::SUCCESS, + array($save_filename) + ); + } + return $message; + } + + /** + * Compress the export buffer + * + * @param array|string $dump_buffer the current dump buffer + * @param string $compression the compression mode + * @param string $filename the filename + * + * @return object $message a message object (or empty string) + */ + public static function compress($dump_buffer, $compression, $filename) + { + if ($compression == 'zip' && function_exists('gzcompress')) { + $zipExtension = new ZipExtension(); + $filename = substr($filename, 0, -4); // remove extension (.zip) + $dump_buffer = $zipExtension->createFile($dump_buffer, $filename); + } elseif ($compression == 'gzip' && self::gzencodeNeeded()) { + // without the optional parameter level because it bugs + $dump_buffer = gzencode($dump_buffer); + } + return $dump_buffer; + } + + /** + * Saves the dump_buffer for a particular table in an array + * Used in separate files export + * + * @param string $object_name the name of current object to be stored + * @param boolean $append optional boolean to append to an existing index or not + * + * @return void + */ + public static function saveObjectInBuffer($object_name, $append = false) + { + + global $dump_buffer_objects, $dump_buffer, $dump_buffer_len; + + if (! empty($dump_buffer)) { + if ($append && isset($dump_buffer_objects[$object_name])) { + $dump_buffer_objects[$object_name] .= $dump_buffer; + } else { + $dump_buffer_objects[$object_name] = $dump_buffer; + } + } + + // Re - initialize + $dump_buffer = ''; + $dump_buffer_len = 0; + + } + + /** + * Returns HTML containing the header for a displayed export + * + * @param string $export_type the export type + * @param string $db the database name + * @param string $table the table name + * + * @return string[] the generated HTML and back button + */ + public static function getHtmlForDisplayedExportHeader($export_type, $db, $table) + { + $html = '
        '; + + /** + * Displays a back button with all the $_POST data in the URL + * (store in a variable to also display after the textarea) + */ + $back_button = '

        [ ' . __('Back') . ' ]

        '; + $html .= '
        '; + $html .= $back_button; + $refreshButton = '
        '; + $refreshButton .= '[ '. __('Refresh') .' ]'; + foreach($_POST as $name => $value) { + if (is_array($value)) { + foreach($value as $val) { + $refreshButton .= ''; + } + } + else { + $refreshButton .= ''; + } + } + $refreshButton .= '
        '; + $html .= $refreshButton + . '
        ' + . '
        ' + . ''; + + return $html_output; + } + + /** + * Get HTML for enum type + * + * @param array $column description of column in given table + * @param string $backup_field hidden input field + * @param string $column_name_appendix the name attribute + * @param array $extracted_columnspec associative array containing type, + * spec_in_brackets and possibly + * enum_set_values (another array) + * @param string $onChangeClause onchange clause for fields + * @param integer $tabindex tab index + * @param integer $tabindex_for_value offset for the values tabindex + * @param integer $idindex id index + * @param mixed $data data to edit + * @param boolean $readOnly is column read only or not + * + * @return string an html snippet + */ + private function getPmaTypeEnum( + array $column, + $backup_field, + $column_name_appendix, + array $extracted_columnspec, + $onChangeClause, + $tabindex, + $tabindex_for_value, + $idindex, + $data, + $readOnly + ) { + $html_output = ''; + if (! isset($column['values'])) { + $column['values'] = $this->getColumnEnumValues( + $column, + $extracted_columnspec + ); + } + $column_enum_values = $column['values']; + $html_output .= ''; + $html_output .= "\n" . ' ' . $backup_field . "\n"; + if (mb_strlen($column['Type']) > 20) { + $html_output .= $this->getDropDownDependingOnLength( + $column, + $column_name_appendix, + $onChangeClause, + $tabindex, + $tabindex_for_value, + $idindex, + $data, + $column_enum_values, + $readOnly + ); + } else { + $html_output .= $this->getRadioButtonDependingOnLength( + $column_name_appendix, + $onChangeClause, + $tabindex, + $column, + $tabindex_for_value, + $idindex, + $data, + $column_enum_values, + $readOnly + ); + } + return $html_output; + } + + /** + * Get column values + * + * @param array $column description of column in given table + * @param array $extracted_columnspec associative array containing type, + * spec_in_brackets and possibly enum_set_values + * (another array) + * + * @return array column values as an associative array + */ + private function getColumnEnumValues(array $column, array $extracted_columnspec) + { + $column['values'] = array(); + foreach ($extracted_columnspec['enum_set_values'] as $val) { + $column['values'][] = array( + 'plain' => $val, + 'html' => htmlspecialchars($val), + ); + } + return $column['values']; + } + + /** + * Get HTML drop down for more than 20 string length + * + * @param array $column description of column in given table + * @param string $column_name_appendix the name attribute + * @param string $onChangeClause onchange clause for fields + * @param integer $tabindex tab index + * @param integer $tabindex_for_value offset for the values tabindex + * @param integer $idindex id index + * @param string $data data to edit + * @param array $column_enum_values $column['values'] + * @param boolean $readOnly is column read only or not + * + * @return string an html snippet + */ + private function getDropDownDependingOnLength( + array $column, + $column_name_appendix, + $onChangeClause, + $tabindex, + $tabindex_for_value, + $idindex, + $data, + array $column_enum_values, + $readOnly + ) { + $html_output = ''; + + //Add hidden input, as disabled '; + } + return $html_output; + } + + /** + * Get HTML radio button for less than 20 string length + * + * @param string $column_name_appendix the name attribute + * @param string $onChangeClause onchange clause for fields + * @param integer $tabindex tab index + * @param array $column description of column in given table + * @param integer $tabindex_for_value offset for the values tabindex + * @param integer $idindex id index + * @param string $data data to edit + * @param array $column_enum_values $column['values'] + * @param boolean $readOnly is column read only or not + * + * @return string an html snippet + */ + private function getRadioButtonDependingOnLength( + $column_name_appendix, + $onChangeClause, + $tabindex, + array $column, + $tabindex_for_value, + $idindex, + $data, + array $column_enum_values, + $readOnly + ) { + $j = 0; + $html_output = ''; + foreach ($column_enum_values as $enum_value) { + $html_output .= ' ' + . ''; + $html_output .= '' . "\n"; + $j++; + } + return $html_output; + } + + /** + * Get the HTML for 'set' pma type + * + * @param array $column description of column in given table + * @param array $extracted_columnspec associative array containing type, + * spec_in_brackets and possibly + * enum_set_values (another array) + * @param string $backup_field hidden input field + * @param string $column_name_appendix the name attribute + * @param string $onChangeClause onchange clause for fields + * @param integer $tabindex tab index + * @param integer $tabindex_for_value offset for the values tabindex + * @param integer $idindex id index + * @param string $data description of the column field + * @param boolean $readOnly is column read only or not + * + * @return string an html snippet + */ + private function getPmaTypeSet( + array $column, + array $extracted_columnspec, + $backup_field, + $column_name_appendix, + $onChangeClause, + $tabindex, + $tabindex_for_value, + $idindex, + $data, + $readOnly + ) { + list($column_set_values, $select_size) = $this->getColumnSetValueAndSelectSize( + $column, + $extracted_columnspec + ); + $vset = array_flip(explode(',', $data)); + $html_output = $backup_field . "\n"; + $html_output .= ''; + $html_output .= ''; + + //Add hidden input, as disabled '; + } + return $html_output; + } + + /** + * Retrieve column 'set' value and select size + * + * @param array $column description of column in given table + * @param array $extracted_columnspec associative array containing type, + * spec_in_brackets and possibly enum_set_values + * (another array) + * + * @return array $column['values'], $column['select_size'] + */ + private function getColumnSetValueAndSelectSize( + array $column, + array $extracted_columnspec + ) { + if (! isset($column['values'])) { + $column['values'] = array(); + foreach ($extracted_columnspec['enum_set_values'] as $val) { + $column['values'][] = array( + 'plain' => $val, + 'html' => htmlspecialchars($val), + ); + } + $column['select_size'] = min(4, count($column['values'])); + } + return array($column['values'], $column['select_size']); + } + + /** + * Get HTML for binary and blob column + * + * @param array $column description of column in given table + * @param string $data data to edit + * @param string $special_chars special characters + * @param integer $biggest_max_file_size biggest max file size for uploading + * @param string $backup_field hidden input field + * @param string $column_name_appendix the name attribute + * @param string $onChangeClause onchange clause for fields + * @param integer $tabindex tab index + * @param integer $tabindex_for_value offset for the values tabindex + * @param integer $idindex id index + * @param string $text_dir text direction + * @param string $special_chars_encoded replaced char if the string starts + * with a \r\n pair (0x0d0a) add an extra \n + * @param string $vkey [multi_edit]['row_id'] + * @param boolean $is_upload is upload or not + * @param boolean $readOnly is column read only or not + * + * @return string an html snippet + */ + private function getBinaryAndBlobColumn( + array $column, + $data, + $special_chars, + $biggest_max_file_size, + $backup_field, + $column_name_appendix, + $onChangeClause, + $tabindex, + $tabindex_for_value, + $idindex, + $text_dir, + $special_chars_encoded, + $vkey, + $is_upload, + $readOnly + ) { + $html_output = ''; + // Add field type : Protected or Hexadecimal + $fields_type_html = ''; + // Default value : hex + $fields_type_val = 'hex'; + if (($GLOBALS['cfg']['ProtectBinary'] === 'blob' && $column['is_blob']) + || ($GLOBALS['cfg']['ProtectBinary'] === 'all') + || ($GLOBALS['cfg']['ProtectBinary'] === 'noblob' && !$column['is_blob']) + ) { + $html_output .= __('Binary - do not edit'); + if (isset($data)) { + $data_size = Util::formatByteDown( + mb_strlen(stripslashes($data)), + 3, + 1 + ); + $html_output .= ' (' . $data_size[0] . ' ' . $data_size[1] . ')'; + unset($data_size); + } + $fields_type_val = 'protected'; + $html_output .= ''; + } elseif ($column['is_blob'] + || ($column['len'] > $GLOBALS['cfg']['LimitChars']) + ) { + $html_output .= "\n" . $this->getTextarea( + $column, + $backup_field, + $column_name_appendix, + $onChangeClause, + $tabindex, + $tabindex_for_value, + $idindex, + $text_dir, + $special_chars_encoded, + 'HEX', + $readOnly + ); + } else { + // field size should be at least 4 and max $GLOBALS['cfg']['LimitChars'] + $fieldsize = min(max($column['len'], 4), $GLOBALS['cfg']['LimitChars']); + $html_output .= "\n" . $backup_field . "\n" . $this->getHtmlInput( + $column, + $column_name_appendix, + $special_chars, + $fieldsize, + $onChangeClause, + $tabindex, + $tabindex_for_value, + $idindex, + 'HEX', + $readOnly + ); + } + $html_output .= sprintf($fields_type_html, $fields_type_val); + + if ($is_upload && $column['is_blob'] && !$readOnly) { + // We don't want to prevent users from using + // browser's default drag-drop feature on some page(s), + // so we add noDragDrop class to the input + $html_output .= '
        ' + . ' '; + list($html_out,) = $this->getMaxUploadSize( + $column, + $biggest_max_file_size + ); + $html_output .= $html_out; + } + + if (!empty($GLOBALS['cfg']['UploadDir']) && !$readOnly) { + $html_output .= $this->getSelectOptionForUpload($vkey, $column); + } + + return $html_output; + } + + /** + * Get HTML input type + * + * @param array $column description of column in given table + * @param string $column_name_appendix the name attribute + * @param string $special_chars special characters + * @param integer $fieldsize html field size + * @param string $onChangeClause onchange clause for fields + * @param integer $tabindex tab index + * @param integer $tabindex_for_value offset for the values tabindex + * @param integer $idindex id index + * @param string $data_type the html5 data-* attribute type + * @param boolean $readOnly is column read only or not + * + * @return string an html snippet + */ + private function getHtmlInput( + array $column, + $column_name_appendix, + $special_chars, + $fieldsize, + $onChangeClause, + $tabindex, + $tabindex_for_value, + $idindex, + $data_type, + $readOnly + ) { + $input_type = 'text'; + // do not use the 'date' or 'time' types here; they have no effect on some + // browsers and create side effects (see bug #4218) + + $the_class = 'textfield'; + // verify True_Type which does not contain the parentheses and length + if ($readOnly) { + //NOOP. Disable date/timepicker + } elseif ($column['True_Type'] === 'date') { + $the_class .= ' datefield'; + } elseif ($column['True_Type'] === 'time') { + $the_class .= ' timefield'; + } elseif ($column['True_Type'] === 'datetime' + || $column['True_Type'] === 'timestamp' + ) { + $the_class .= ' datetimefield'; + } + $input_min_max = false; + if (in_array($column['True_Type'], $this->dbi->types->getIntegerTypes())) { + $extracted_columnspec = Util::extractColumnSpec( + $column['Type'] + ); + $is_unsigned = $extracted_columnspec['unsigned']; + $min_max_values = $this->dbi->types->getIntegerRange( + $column['True_Type'], + ! $is_unsigned + ); + $input_min_max = 'min="' . $min_max_values[0] . '" ' + . 'max="' . $min_max_values[1] . '"'; + $data_type = 'INT'; + } + return ''; + } + + /** + * Get HTML select option for upload + * + * @param string $vkey [multi_edit]['row_id'] + * @param array $column description of column in given table + * + * @return string|void an html snippet + */ + private function getSelectOptionForUpload($vkey, array $column) + { + $files = FileListing::getFileSelectOptions( + Util::userDir($GLOBALS['cfg']['UploadDir']) + ); + + if ($files === false) { + return '' . __('Error') . '
        ' . "\n" + . __('The directory you set for upload work cannot be reached.') . "\n"; + } elseif (!empty($files)) { + return "
        \n" + . '' . __('Or') . '' . ' ' + . __('web server upload directory:') . '
        ' . "\n" + . '' . "\n"; + } + + return null; + } + + /** + * Retrieve the maximum upload file size + * + * @param array $column description of column in given table + * @param integer $biggest_max_file_size biggest max file size for uploading + * + * @return array an html snippet and $biggest_max_file_size + */ + private function getMaxUploadSize(array $column, $biggest_max_file_size) + { + // find maximum upload size, based on field type + /** + * @todo with functions this is not so easy, as you can basically + * process any data with function like MD5 + */ + global $max_upload_size; + $max_field_sizes = array( + 'tinyblob' => '256', + 'blob' => '65536', + 'mediumblob' => '16777216', + 'longblob' => '4294967296' // yeah, really + ); + + $this_field_max_size = $max_upload_size; // from PHP max + if ($this_field_max_size > $max_field_sizes[$column['pma_type']]) { + $this_field_max_size = $max_field_sizes[$column['pma_type']]; + } + $html_output + = Util::getFormattedMaximumUploadSize( + $this_field_max_size + ) . "\n"; + // do not generate here the MAX_FILE_SIZE, because we should + // put only one in the form to accommodate the biggest field + if ($this_field_max_size > $biggest_max_file_size) { + $biggest_max_file_size = $this_field_max_size; + } + return array($html_output, $biggest_max_file_size); + } + + /** + * Get HTML for the Value column of other datatypes + * (here, "column" is used in the sense of HTML column in HTML table) + * + * @param array $column description of column in given table + * @param string $default_char_editing default char editing mode which is stored + * in the config.inc.php script + * @param string $backup_field hidden input field + * @param string $column_name_appendix the name attribute + * @param string $onChangeClause onchange clause for fields + * @param integer $tabindex tab index + * @param string $special_chars special characters + * @param integer $tabindex_for_value offset for the values tabindex + * @param integer $idindex id index + * @param string $text_dir text direction + * @param string $special_chars_encoded replaced char if the string starts + * with a \r\n pair (0x0d0a) add an extra \n + * @param string $data data to edit + * @param array $extracted_columnspec associative array containing type, + * spec_in_brackets and possibly + * enum_set_values (another array) + * @param boolean $readOnly is column read only or not + * + * @return string an html snippet + */ + private function getValueColumnForOtherDatatypes( + array $column, + $default_char_editing, + $backup_field, + $column_name_appendix, + $onChangeClause, + $tabindex, + $special_chars, + $tabindex_for_value, + $idindex, + $text_dir, + $special_chars_encoded, + $data, + array $extracted_columnspec, + $readOnly + ) { + // HTML5 data-* attribute data-type + $data_type = $this->dbi->types->getTypeClass($column['True_Type']); + $fieldsize = $this->getColumnSize($column, $extracted_columnspec); + $html_output = $backup_field . "\n"; + if ($column['is_char'] + && ($GLOBALS['cfg']['CharEditing'] == 'textarea' + || mb_strpos($data, "\n") !== false) + ) { + $html_output .= "\n"; + $GLOBALS['cfg']['CharEditing'] = $default_char_editing; + $html_output .= $this->getTextarea( + $column, + $backup_field, + $column_name_appendix, + $onChangeClause, + $tabindex, + $tabindex_for_value, + $idindex, + $text_dir, + $special_chars_encoded, + $data_type, + $readOnly + ); + } else { + $html_output .= $this->getHtmlInput( + $column, + $column_name_appendix, + $special_chars, + $fieldsize, + $onChangeClause, + $tabindex, + $tabindex_for_value, + $idindex, + $data_type, + $readOnly + ); + + if (preg_match('/(VIRTUAL|PERSISTENT|GENERATED)/', $column['Extra']) && $column['Extra'] !== 'DEFAULT_GENERATED') { + $html_output .= ''; + } + if ($column['Extra'] == 'auto_increment') { + $html_output .= ''; + } + if (substr($column['pma_type'], 0, 9) == 'timestamp') { + $html_output .= ''; + } + if (substr($column['pma_type'], 0, 8) == 'datetime') { + $html_output .= ''; + } + if ($column['True_Type'] == 'bit') { + $html_output .= ''; + } + if ($column['pma_type'] == 'date' + || $column['pma_type'] == 'datetime' + || substr($column['pma_type'], 0, 9) == 'timestamp' + ) { + // the _3 suffix points to the date field + // the _2 suffix points to the corresponding NULL checkbox + // in dateFormat, 'yy' means the year with 4 digits + } + } + return $html_output; + } + + /** + * Get the field size + * + * @param array $column description of column in given table + * @param array $extracted_columnspec associative array containing type, + * spec_in_brackets and possibly enum_set_values + * (another array) + * + * @return integer field size + */ + private function getColumnSize(array $column, array $extracted_columnspec) + { + if ($column['is_char']) { + $fieldsize = $extracted_columnspec['spec_in_brackets']; + if ($fieldsize > $GLOBALS['cfg']['MaxSizeForInputField']) { + /** + * This case happens for CHAR or VARCHAR columns which have + * a size larger than the maximum size for input field. + */ + $GLOBALS['cfg']['CharEditing'] = 'textarea'; + } + } else { + /** + * This case happens for example for INT or DATE columns; + * in these situations, the value returned in $column['len'] + * seems appropriate. + */ + $fieldsize = $column['len']; + } + return min( + max($fieldsize, $GLOBALS['cfg']['MinSizeForInputField']), + $GLOBALS['cfg']['MaxSizeForInputField'] + ); + } + + /** + * Get HTML for gis data types + * + * @return string an html snippet + */ + private function getHtmlForGisDataTypes() + { + $edit_str = Util::getIcon('b_edit', __('Edit/Insert')); + return '' + . Util::linkOrButton( + '#', + $edit_str, + array(), + '_blank' + ) + . ''; + } + + /** + * get html for continue insertion form + * + * @param string $table name of the table + * @param string $db name of the database + * @param array $where_clause_array array of where clauses + * @param string $err_url error url + * + * @return string an html snippet + */ + public function getContinueInsertionForm( + $table, + $db, + array $where_clause_array, + $err_url + ) { + return Template::get('table/insert/continue_insertion_form')->render([ + 'db' => $db, + 'table' => $table, + 'where_clause_array' => $where_clause_array, + 'err_url' => $err_url, + 'goto' => $GLOBALS['goto'], + 'sql_query' => isset($_POST['sql_query']) ? $_POST['sql_query'] : null, + 'has_where_clause' => isset($_POST['where_clause']), + 'insert_rows_default' => $GLOBALS['cfg']['InsertRows'], + ]); + } + + /** + * Get action panel + * + * @param array|null $where_clause where clause + * @param string $after_insert insert mode, e.g. new_insert, same_insert + * @param integer $tabindex tab index + * @param integer $tabindex_for_value offset for the values tabindex + * @param boolean $found_unique_key boolean variable for unique key + * + * @return string an html snippet + */ + public function getActionsPanel( + $where_clause, + $after_insert, + $tabindex, + $tabindex_for_value, + $found_unique_key + ) { + $html_output = '
        ' + . '' + . '' + . '' + . '' + . '' + . ''; + $html_output .='' + . $this->getSubmitAndResetButtonForActionsPanel($tabindex, $tabindex_for_value) + . '' + . '
        ' + . $this->getSubmitTypeDropDown($where_clause, $tabindex, $tabindex_for_value) + . "\n"; + + $html_output .= '' + . '   ' + . __('and then') . '   ' + . '' + . $this->getAfterInsertDropDown( + $where_clause, + $after_insert, + $found_unique_key + ) + . '
        ' + . '
        '; + return $html_output; + } + + /** + * Get a HTML drop down for submit types + * + * @param array|null $where_clause where clause + * @param integer $tabindex tab index + * @param integer $tabindex_for_value offset for the values tabindex + * + * @return string an html snippet + */ + private function getSubmitTypeDropDown( + $where_clause, + $tabindex, + $tabindex_for_value + ) { + $html_output = ''; + return $html_output; + } + + /** + * Get HTML drop down for after insert + * + * @param array|null $where_clause where clause + * @param string $after_insert insert mode, e.g. new_insert, same_insert + * @param boolean $found_unique_key boolean variable for unique key + * + * @return string an html snippet + */ + private function getAfterInsertDropDown($where_clause, $after_insert, $found_unique_key) + { + $html_output = ''; + return $html_output; + } + + /** + * get Submit button and Reset button for action panel + * + * @param integer $tabindex tab index + * @param integer $tabindex_for_value offset for the values tabindex + * + * @return string an html snippet + */ + private function getSubmitAndResetButtonForActionsPanel($tabindex, $tabindex_for_value) + { + return '' + . Util::showHint( + __( + 'Use TAB key to move from value to value,' + . ' or CTRL+arrows to move anywhere.' + ) + ) + . '' + . '' + . '' + . '' + . '' + . ''; + } + + /** + * Get table head and table foot for insert row table + * + * @param array $url_params url parameters + * + * @return string an html snippet + */ + private function getHeadAndFootOfInsertRowTable(array $url_params) + { + $html_output = '
        ' + . '' + . '' + . '' + . ''; + + if ($GLOBALS['cfg']['ShowFieldTypesInDataEditView']) { + $html_output .= $this->showTypeOrFunction('type', $url_params, true); + } + if ($GLOBALS['cfg']['ShowFunctionFields']) { + $html_output .= $this->showTypeOrFunction('function', $url_params, true); + } + + $html_output .= '' + . '' + . '' + . '' + . ' ' + . '' + . '' + . '' + . ''; + return $html_output; + } + + /** + * Prepares the field value and retrieve special chars, backup field and data array + * + * @param array $current_row a row of the table + * @param array $column description of column in given table + * @param array $extracted_columnspec associative array containing type, + * spec_in_brackets and possibly + * enum_set_values (another array) + * @param boolean $real_null_value whether column value null or not null + * @param array $gis_data_types list of GIS data types + * @param string $column_name_appendix string to append to column name in input + * @param bool $as_is use the data as is, used in repopulating + * + * @return array $real_null_value, $data, $special_chars, $backup_field, + * $special_chars_encoded + */ + private function getSpecialCharsAndBackupFieldForExistingRow( + array $current_row, + array $column, + array $extracted_columnspec, + $real_null_value, + array $gis_data_types, + $column_name_appendix, + $as_is + ) { + $special_chars_encoded = ''; + $data = null; + // (we are editing) + if (!isset($current_row[$column['Field']])) { + $real_null_value = true; + $current_row[$column['Field']] = ''; + $special_chars = ''; + $data = $current_row[$column['Field']]; + } elseif ($column['True_Type'] == 'bit') { + $special_chars = $as_is + ? $current_row[$column['Field']] + : Util::printableBitValue( + $current_row[$column['Field']], + $extracted_columnspec['spec_in_brackets'] + ); + } elseif ((substr($column['True_Type'], 0, 9) == 'timestamp' + || $column['True_Type'] == 'datetime' + || $column['True_Type'] == 'time') + && (mb_strpos($current_row[$column['Field']], ".") !== false) + ) { + $current_row[$column['Field']] = $as_is + ? $current_row[$column['Field']] + : Util::addMicroseconds( + $current_row[$column['Field']] + ); + $special_chars = htmlspecialchars($current_row[$column['Field']]); + } elseif (in_array($column['True_Type'], $gis_data_types)) { + // Convert gis data to Well Know Text format + $current_row[$column['Field']] = $as_is + ? $current_row[$column['Field']] + : Util::asWKT( + $current_row[$column['Field']], + true + ); + $special_chars = htmlspecialchars($current_row[$column['Field']]); + } else { + // special binary "characters" + if ($column['is_binary'] + || ($column['is_blob'] && $GLOBALS['cfg']['ProtectBinary'] !== 'all') + ) { + $current_row[$column['Field']] = $as_is + ? $current_row[$column['Field']] + : bin2hex( + $current_row[$column['Field']] + ); + } // end if + $special_chars = htmlspecialchars($current_row[$column['Field']]); + + //We need to duplicate the first \n or otherwise we will lose + //the first newline entered in a VARCHAR or TEXT column + $special_chars_encoded + = Util::duplicateFirstNewline($special_chars); + + $data = $current_row[$column['Field']]; + } // end if... else... + + //when copying row, it is useful to empty auto-increment column + // to prevent duplicate key error + if (isset($_POST['default_action']) + && $_POST['default_action'] === 'insert' + ) { + if ($column['Key'] === 'PRI' + && mb_strpos($column['Extra'], 'auto_increment') !== false + ) { + $data = $special_chars_encoded = $special_chars = null; + } + } + // If a timestamp field value is not included in an update + // statement MySQL auto-update it to the current timestamp; + // however, things have changed since MySQL 4.1, so + // it's better to set a fields_prev in this situation + $backup_field = ''; + + return array( + $real_null_value, + $special_chars_encoded, + $special_chars, + $data, + $backup_field + ); + } + + /** + * display default values + * + * @param array $column description of column in given table + * @param boolean $real_null_value whether column value null or not null + * + * @return array $real_null_value, $data, $special_chars, + * $backup_field, $special_chars_encoded + */ + private function getSpecialCharsAndBackupFieldForInsertingMode( + array $column, + $real_null_value + ) { + if (! isset($column['Default'])) { + $column['Default'] = ''; + $real_null_value = true; + $data = ''; + } else { + $data = $column['Default']; + } + + $trueType = $column['True_Type']; + + if ($trueType == 'bit') { + $special_chars = Util::convertBitDefaultValue( + $column['Default'] + ); + } elseif (substr($trueType, 0, 9) == 'timestamp' + || $trueType == 'datetime' + || $trueType == 'time' + ) { + $special_chars = Util::addMicroseconds($column['Default']); + } elseif ($trueType == 'binary' || $trueType == 'varbinary') { + $special_chars = bin2hex($column['Default']); + } elseif ('text' === substr($trueType, -4)) { + $textDefault = substr($column['Default'], 1, -1); + $special_chars = stripcslashes($textDefault !== false ? $textDefault : $column['Default']); + } else { + $special_chars = htmlspecialchars($column['Default']); + } + $backup_field = ''; + $special_chars_encoded = Util::duplicateFirstNewline( + $special_chars + ); + return array( + $real_null_value, $data, $special_chars, + $backup_field, $special_chars_encoded + ); + } + + /** + * Prepares the update/insert of a row + * + * @return array $loop_array, $using_key, $is_insert, $is_insertignore + */ + public function getParamsForUpdateOrInsert() + { + if (isset($_POST['where_clause'])) { + // we were editing something => use the WHERE clause + $loop_array = is_array($_POST['where_clause']) + ? $_POST['where_clause'] + : array($_POST['where_clause']); + $using_key = true; + $is_insert = isset($_POST['submit_type']) + && ($_POST['submit_type'] == 'insert' + || $_POST['submit_type'] == 'showinsert' + || $_POST['submit_type'] == 'insertignore'); + } else { + // new row => use indexes + $loop_array = array(); + if (! empty($_POST['fields'])) { + foreach ($_POST['fields']['multi_edit'] as $key => $dummy) { + $loop_array[] = $key; + } + } + $using_key = false; + $is_insert = true; + } + $is_insertignore = isset($_POST['submit_type']) + && $_POST['submit_type'] == 'insertignore'; + return array($loop_array, $using_key, $is_insert, $is_insertignore); + } + + /** + * Check wether insert row mode and if so include tbl_changen script and set + * global variables. + * + * @return void + */ + public function isInsertRow() + { + if (isset($_POST['insert_rows']) + && is_numeric($_POST['insert_rows']) + && $_POST['insert_rows'] != $GLOBALS['cfg']['InsertRows'] + ) { + $GLOBALS['cfg']['InsertRows'] = $_POST['insert_rows']; + $response = Response::getInstance(); + $header = $response->getHeader(); + $scripts = $header->getScripts(); + $scripts->addFile('vendor/jquery/additional-methods.js'); + $scripts->addFile('tbl_change.js'); + if (!defined('TESTSUITE')) { + include 'tbl_change.php'; + exit; + } + } + } + + /** + * set $_SESSION for edit_next + * + * @param string $one_where_clause one where clause from where clauses array + * + * @return void + */ + public function setSessionForEditNext($one_where_clause) + { + $local_query = 'SELECT * FROM ' . Util::backquote($GLOBALS['db']) + . '.' . Util::backquote($GLOBALS['table']) . ' WHERE ' + . str_replace('` =', '` >', $one_where_clause) . ' LIMIT 1;'; + + $res = $this->dbi->query($local_query); + $row = $this->dbi->fetchRow($res); + $meta = $this->dbi->getFieldsMeta($res); + // must find a unique condition based on unique key, + // not a combination of all fields + list($unique_condition, $clause_is_unique) + = Util::getUniqueCondition( + $res, // handle + count($meta), // fields_cnt + $meta, // fields_meta + $row, // row + true, // force_unique + false, // restrict_to_table + null // analyzed_sql_results + ); + if (! empty($unique_condition)) { + $_SESSION['edit_next'] = $unique_condition; + } + unset($unique_condition, $clause_is_unique); + } + + /** + * set $goto_include variable for different cases and retrieve like, + * if $GLOBALS['goto'] empty, if $goto_include previously not defined + * and new_insert, same_insert, edit_next + * + * @param string $goto_include store some script for include, otherwise it is + * boolean false + * + * @return string $goto_include + */ + public function getGotoInclude($goto_include) + { + $valid_options = array('new_insert', 'same_insert', 'edit_next'); + if (isset($_POST['after_insert']) + && in_array($_POST['after_insert'], $valid_options) + ) { + $goto_include = 'tbl_change.php'; + } elseif (! empty($GLOBALS['goto'])) { + if (! preg_match('@^[a-z_]+\.php$@', $GLOBALS['goto'])) { + // this should NOT happen + //$GLOBALS['goto'] = false; + $goto_include = false; + } else { + $goto_include = $GLOBALS['goto']; + } + if ($GLOBALS['goto'] == 'db_sql.php' && strlen($GLOBALS['table']) > 0) { + $GLOBALS['table'] = ''; + } + } + if (! $goto_include) { + if (strlen($GLOBALS['table']) === 0) { + $goto_include = 'db_sql.php'; + } else { + $goto_include = 'tbl_sql.php'; + } + } + return $goto_include; + } + + /** + * Defines the url to return in case of failure of the query + * + * @param array $url_params url parameters + * + * @return string error url for query failure + */ + public function getErrorUrl(array $url_params) + { + if (isset($_POST['err_url'])) { + return $_POST['err_url']; + } + + return 'tbl_change.php' . Url::getCommon($url_params); + } + + /** + * Builds the sql query + * + * @param boolean $is_insertignore $_POST['submit_type'] == 'insertignore' + * @param array $query_fields column names array + * @param array $value_sets array of query values + * + * @return array of query + */ + public function buildSqlQuery($is_insertignore, array $query_fields, array $value_sets) + { + if ($is_insertignore) { + $insert_command = 'INSERT IGNORE '; + } else { + $insert_command = 'INSERT '; + } + $query = array( + $insert_command . 'INTO ' + . Util::backquote($GLOBALS['table']) + . ' (' . implode(', ', $query_fields) . ') VALUES (' + . implode('), (', $value_sets) . ')' + ); + unset($insert_command, $query_fields); + return $query; + } + + /** + * Executes the sql query and get the result, then move back to the calling page + * + * @param array $url_params url parameters array + * @param array $query built query from buildSqlQuery() + * + * @return array $url_params, $total_affected_rows, $last_messages + * $warning_messages, $error_messages, $return_to_sql_query + */ + public function executeSqlQuery(array $url_params, array $query) + { + $return_to_sql_query = ''; + if (! empty($GLOBALS['sql_query'])) { + $url_params['sql_query'] = $GLOBALS['sql_query']; + $return_to_sql_query = $GLOBALS['sql_query']; + } + $GLOBALS['sql_query'] = implode('; ', $query) . ';'; + // to ensure that the query is displayed in case of + // "insert as new row" and then "insert another new row" + $GLOBALS['display_query'] = $GLOBALS['sql_query']; + + $total_affected_rows = 0; + $last_messages = array(); + $warning_messages = array(); + $error_messages = array(); + + foreach ($query as $single_query) { + if ($_POST['submit_type'] == 'showinsert') { + $last_messages[] = Message::notice(__('Showing SQL query')); + continue; + } + if ($GLOBALS['cfg']['IgnoreMultiSubmitErrors']) { + $result = $this->dbi->tryQuery($single_query); + } else { + $result = $this->dbi->query($single_query); + } + if (! $result) { + $error_messages[] = $this->dbi->getError(); + } else { + // The next line contains a real assignment, it's not a typo + if ($tmp = @$this->dbi->affectedRows()) { + $total_affected_rows += $tmp; + } + unset($tmp); + + $insert_id = $this->dbi->insertId(); + if ($insert_id != 0) { + // insert_id is id of FIRST record inserted in one insert, so if we + // inserted multiple rows, we had to increment this + + if ($total_affected_rows > 0) { + $insert_id = $insert_id + $total_affected_rows - 1; + } + $last_message = Message::notice(__('Inserted row id: %1$d')); + $last_message->addParam($insert_id); + $last_messages[] = $last_message; + } + $this->dbi->freeResult($result); + } + $warning_messages = $this->getWarningMessages(); + } + return array( + $url_params, + $total_affected_rows, + $last_messages, + $warning_messages, + $error_messages, + $return_to_sql_query + ); + } + + /** + * get the warning messages array + * + * @return array $warning_essages + */ + private function getWarningMessages() + { + $warning_essages = array(); + foreach ($this->dbi->getWarnings() as $warning) { + $warning_essages[] = Message::sanitize( + $warning['Level'] . ': #' . $warning['Code'] . ' ' . $warning['Message'] + ); + } + return $warning_essages; + } + + /** + * Column to display from the foreign table? + * + * @param string $where_comparison string that contain relation field value + * @param array $map all Relations to foreign tables for a given + * table or optionally a given column in a table + * @param string $relation_field relation field + * + * @return string $dispval display value from the foreign table + */ + public function getDisplayValueForForeignTableColumn( + $where_comparison, + array $map, + $relation_field + ) { + $foreigner = $this->relation->searchColumnInForeigners($map, $relation_field); + $display_field = $this->relation->getDisplayField( + $foreigner['foreign_db'], + $foreigner['foreign_table'] + ); + // Field to display from the foreign table? + if (isset($display_field) && strlen($display_field) > 0) { + $dispsql = 'SELECT ' . Util::backquote($display_field) + . ' FROM ' . Util::backquote($foreigner['foreign_db']) + . '.' . Util::backquote($foreigner['foreign_table']) + . ' WHERE ' . Util::backquote($foreigner['foreign_field']) + . $where_comparison; + $dispresult = $this->dbi->tryQuery( + $dispsql, + DatabaseInterface::CONNECT_USER, + DatabaseInterface::QUERY_STORE + ); + if ($dispresult && $this->dbi->numRows($dispresult) > 0) { + list($dispval) = $this->dbi->fetchRow($dispresult, 0); + } else { + $dispval = ''; + } + if ($dispresult) { + $this->dbi->freeResult($dispresult); + } + return $dispval; + } + return ''; + } + + /** + * Display option in the cell according to user choices + * + * @param array $map all Relations to foreign tables for a given + * table or optionally a given column in a table + * @param string $relation_field relation field + * @param string $where_comparison string that contain relation field value + * @param string $dispval display value from the foreign table + * @param string $relation_field_value relation field value + * + * @return string $output HTML tag + */ + public function getLinkForRelationalDisplayField( + array $map, + $relation_field, + $where_comparison, + $dispval, + $relation_field_value + ) { + $foreigner = $this->relation->searchColumnInForeigners($map, $relation_field); + if ('K' == $_SESSION['tmpval']['relational_display']) { + // user chose "relational key" in the display options, so + // the title contains the display field + $title = (! empty($dispval)) + ? ' title="' . htmlspecialchars($dispval) . '"' + : ''; + } else { + $title = ' title="' . htmlspecialchars($relation_field_value) . '"'; + } + $_url_params = array( + 'db' => $foreigner['foreign_db'], + 'table' => $foreigner['foreign_table'], + 'pos' => '0', + 'sql_query' => 'SELECT * FROM ' + . Util::backquote($foreigner['foreign_db']) + . '.' . Util::backquote($foreigner['foreign_table']) + . ' WHERE ' . Util::backquote($foreigner['foreign_field']) + . $where_comparison + ); + $output = ''; + + if ('D' == $_SESSION['tmpval']['relational_display']) { + // user chose "relational display field" in the + // display options, so show display field in the cell + $output .= (!empty($dispval)) ? htmlspecialchars($dispval) : ''; + } else { + // otherwise display data in the cell + $output .= htmlspecialchars($relation_field_value); + } + $output .= ''; + return $output; + } + + /** + * Transform edited values + * + * @param string $db db name + * @param string $table table name + * @param array $transformation mimetypes for all columns of a table + * [field_name][field_key] + * @param array &$edited_values transform columns list and new values + * @param string $file file containing the transformation plugin + * @param string $column_name column name + * @param array $extra_data extra data array + * @param string $type the type of transformation + * + * @return array $extra_data + */ + public function transformEditedValues( + $db, + $table, + array $transformation, + array &$edited_values, + $file, + $column_name, + array $extra_data, + $type + ) { + $include_file = 'libraries/classes/Plugins/Transformations/' . $file; + if (is_file($include_file)) { + $_url_params = array( + 'db' => $db, + 'table' => $table, + 'where_clause' => $_POST['where_clause'], + 'transform_key' => $column_name + ); + $transform_options = Transformations::getOptions( + isset($transformation[$type . '_options']) + ? $transformation[$type . '_options'] + : '' + ); + $transform_options['wrapper_link'] = Url::getCommon($_url_params); + $class_name = Transformations::getClassName($include_file); + if (class_exists($class_name)) { + /** @var TransformationsPlugin $transformation_plugin */ + $transformation_plugin = new $class_name(); + + foreach ($edited_values as $cell_index => $curr_cell_edited_values) { + if (isset($curr_cell_edited_values[$column_name])) { + $edited_values[$cell_index][$column_name] + = $extra_data['transformations'][$cell_index] + = $transformation_plugin->applyTransformation( + $curr_cell_edited_values[$column_name], + $transform_options, + '' + ); + } + } // end of loop for each transformation cell + } + } + return $extra_data; + } + + /** + * Get current value in multi edit mode + * + * @param array $multi_edit_funcs multiple edit functions array + * @param array $multi_edit_salt multiple edit array with encryption salt + * @param array $gis_from_text_functions array that contains gis from text functions + * @param string $current_value current value in the column + * @param array $gis_from_wkb_functions initially $val is $multi_edit_columns[$key] + * @param array $func_optional_param array('RAND','UNIX_TIMESTAMP') + * @param array $func_no_param array of set of string + * @param string $key an md5 of the column name + * + * @return array $cur_value + */ + public function getCurrentValueAsAnArrayForMultipleEdit( + $multi_edit_funcs, + $multi_edit_salt, + $gis_from_text_functions, + $current_value, + $gis_from_wkb_functions, + $func_optional_param, + $func_no_param, + $key + ) { + if (empty($multi_edit_funcs[$key])) { + return $current_value; + } elseif ('UUID' === $multi_edit_funcs[$key]) { + /* This way user will know what UUID new row has */ + $uuid = $this->dbi->fetchValue('SELECT UUID()'); + return "'" . $uuid . "'"; + } elseif ((in_array($multi_edit_funcs[$key], $gis_from_text_functions) + && substr($current_value, 0, 3) == "'''") + || in_array($multi_edit_funcs[$key], $gis_from_wkb_functions) + ) { + // Remove enclosing apostrophes + $current_value = mb_substr($current_value, 1, -1); + // Remove escaping apostrophes + $current_value = str_replace("''", "'", $current_value); + return $multi_edit_funcs[$key] . '(' . $current_value . ')'; + } elseif (! in_array($multi_edit_funcs[$key], $func_no_param) + || ($current_value != "''" + && in_array($multi_edit_funcs[$key], $func_optional_param)) + ) { + if ((isset($multi_edit_salt[$key]) + && ($multi_edit_funcs[$key] == "AES_ENCRYPT" + || $multi_edit_funcs[$key] == "AES_DECRYPT")) + || (! empty($multi_edit_salt[$key]) + && ($multi_edit_funcs[$key] == "DES_ENCRYPT" + || $multi_edit_funcs[$key] == "DES_DECRYPT" + || $multi_edit_funcs[$key] == "ENCRYPT")) + ) { + return $multi_edit_funcs[$key] . '(' . $current_value . ",'" + . $this->dbi->escapeString($multi_edit_salt[$key]) . "')"; + } + + return $multi_edit_funcs[$key] . '(' . $current_value . ')'; + } + + return $multi_edit_funcs[$key] . '()'; + } + + /** + * Get query values array and query fields array for insert and update in multi edit + * + * @param array $multi_edit_columns_name multiple edit columns name array + * @param array $multi_edit_columns_null multiple edit columns null array + * @param string $current_value current value in the column in loop + * @param array $multi_edit_columns_prev multiple edit previous columns array + * @param array $multi_edit_funcs multiple edit functions array + * @param boolean $is_insert boolean value whether insert or not + * @param array $query_values SET part of the sql query + * @param array $query_fields array of query fields + * @param string $current_value_as_an_array current value in the column + * as an array + * @param array $value_sets array of valu sets + * @param string $key an md5 of the column name + * @param array $multi_edit_columns_null_prev array of multiple edit columns + * null previous + * + * @return array ($query_values, $query_fields) + */ + public function getQueryValuesForInsertAndUpdateInMultipleEdit( + $multi_edit_columns_name, + $multi_edit_columns_null, + $current_value, + $multi_edit_columns_prev, + $multi_edit_funcs, + $is_insert, + $query_values, + $query_fields, + $current_value_as_an_array, + $value_sets, + $key, + $multi_edit_columns_null_prev + ) { + // i n s e r t + if ($is_insert) { + // no need to add column into the valuelist + if (strlen($current_value_as_an_array) > 0) { + $query_values[] = $current_value_as_an_array; + // first inserted row so prepare the list of fields + if (empty($value_sets)) { + $query_fields[] = Util::backquote( + $multi_edit_columns_name[$key] + ); + } + } + } elseif (! empty($multi_edit_columns_null_prev[$key]) + && ! isset($multi_edit_columns_null[$key]) + ) { + // u p d a t e + + // field had the null checkbox before the update + // field no longer has the null checkbox + $query_values[] + = Util::backquote($multi_edit_columns_name[$key]) + . ' = ' . $current_value_as_an_array; + } elseif (empty($multi_edit_funcs[$key]) + && isset($multi_edit_columns_prev[$key]) + && (("'" . $this->dbi->escapeString($multi_edit_columns_prev[$key]) . "'" === $current_value) + || ('0x' . $multi_edit_columns_prev[$key] === $current_value)) + ) { + // No change for this column and no MySQL function is used -> next column + } elseif (! empty($current_value)) { + // avoid setting a field to NULL when it's already NULL + // (field had the null checkbox before the update + // field still has the null checkbox) + if (empty($multi_edit_columns_null_prev[$key]) + || empty($multi_edit_columns_null[$key]) + ) { + $query_values[] + = Util::backquote($multi_edit_columns_name[$key]) + . ' = ' . $current_value_as_an_array; + } + } + return array($query_values, $query_fields); + } + + /** + * Get the current column value in the form for different data types + * + * @param string|false $possibly_uploaded_val uploaded file content + * @param string $key an md5 of the column name + * @param array $multi_edit_columns_type array of multi edit column types + * @param string $current_value current column value in the form + * @param array $multi_edit_auto_increment multi edit auto increment + * @param integer $rownumber index of where clause array + * @param array $multi_edit_columns_name multi edit column names array + * @param array $multi_edit_columns_null multi edit columns null array + * @param array $multi_edit_columns_null_prev multi edit columns previous null + * @param boolean $is_insert whether insert or not + * @param boolean $using_key whether editing or new row + * @param string $where_clause where clause + * @param string $table table name + * @param array $multi_edit_funcs multiple edit functions array + * + * @return string $current_value current column value in the form + */ + public function getCurrentValueForDifferentTypes( + $possibly_uploaded_val, + $key, + $multi_edit_columns_type, + $current_value, + $multi_edit_auto_increment, + $rownumber, + $multi_edit_columns_name, + $multi_edit_columns_null, + $multi_edit_columns_null_prev, + $is_insert, + $using_key, + $where_clause, + $table, + $multi_edit_funcs + ) { + // Fetch the current values of a row to use in case we have a protected field + if ($is_insert + && $using_key && isset($multi_edit_columns_type) + && is_array($multi_edit_columns_type) && !empty($where_clause) + ) { + $protected_row = $this->dbi->fetchSingleRow( + 'SELECT * FROM ' . Util::backquote($table) + . ' WHERE ' . $where_clause . ';' + ); + } + + if (false !== $possibly_uploaded_val) { + $current_value = $possibly_uploaded_val; + } elseif (! empty($multi_edit_funcs[$key])) { + $current_value = "'" . $this->dbi->escapeString($current_value) + . "'"; + } else { + // c o l u m n v a l u e i n t h e f o r m + if (isset($multi_edit_columns_type[$key])) { + $type = $multi_edit_columns_type[$key]; + } else { + $type = ''; + } + + if ($type != 'protected' && $type != 'set' && strlen($current_value) === 0) { + // best way to avoid problems in strict mode + // (works also in non-strict mode) + if (isset($multi_edit_auto_increment) + && isset($multi_edit_auto_increment[$key]) + ) { + $current_value = 'NULL'; + } else { + $current_value = "''"; + } + } elseif ($type == 'set') { + if (! empty($_POST['fields']['multi_edit'][$rownumber][$key])) { + $current_value = implode( + ',', + $_POST['fields']['multi_edit'][$rownumber][$key] + ); + $current_value = "'" + . $this->dbi->escapeString($current_value) . "'"; + } else { + $current_value = "''"; + } + } elseif ($type == 'protected') { + // here we are in protected mode (asked in the config) + // so tbl_change has put this special value in the + // columns array, so we do not change the column value + // but we can still handle column upload + + // when in UPDATE mode, do not alter field's contents. When in INSERT + // mode, insert empty field because no values were submitted. + // If protected blobs where set, insert original fields content. + if (! empty($protected_row[$multi_edit_columns_name[$key]])) { + $current_value = '0x' + . bin2hex($protected_row[$multi_edit_columns_name[$key]]); + } else { + $current_value = ''; + } + } elseif ($type === 'hex') { + if (substr($current_value, 0, 2) != '0x') { + $current_value = '0x' . $current_value; + } + } elseif ($type == 'bit') { + $current_value = preg_replace('/[^01]/', '0', $current_value); + $current_value = "b'" . $this->dbi->escapeString($current_value) + . "'"; + } elseif (! ($type == 'datetime' || $type == 'timestamp') + || ($current_value != 'CURRENT_TIMESTAMP' + && $current_value != 'current_timestamp()') + ) { + $current_value = "'" . $this->dbi->escapeString($current_value) + . "'"; + } + + // Was the Null checkbox checked for this field? + // (if there is a value, we ignore the Null checkbox: this could + // be possible if Javascript is disabled in the browser) + if (! empty($multi_edit_columns_null[$key]) + && ($current_value == "''" || $current_value == '') + ) { + $current_value = 'NULL'; + } + + // The Null checkbox was unchecked for this field + if (empty($current_value) + && ! empty($multi_edit_columns_null_prev[$key]) + && ! isset($multi_edit_columns_null[$key]) + ) { + $current_value = "''"; + } + } // end else (column value in the form) + return $current_value; + } + + /** + * Check whether inline edited value can be truncated or not, + * and add additional parameters for extra_data array if needed + * + * @param string $db Database name + * @param string $table Table name + * @param string $column_name Column name + * @param array &$extra_data Extra data for ajax response + * + * @return void + */ + public function verifyWhetherValueCanBeTruncatedAndAppendExtraData( + $db, + $table, + $column_name, + array &$extra_data + ) { + $extra_data['isNeedToRecheck'] = false; + + $sql_for_real_value = 'SELECT ' . Util::backquote($table) . '.' + . Util::backquote($column_name) + . ' FROM ' . Util::backquote($db) . '.' + . Util::backquote($table) + . ' WHERE ' . $_POST['where_clause'][0]; + + $result = $this->dbi->tryQuery($sql_for_real_value); + $fields_meta = $this->dbi->getFieldsMeta($result); + $meta = $fields_meta[0]; + if ($row = $this->dbi->fetchRow($result)) { + $new_value = $row[0]; + if ((substr($meta->type, 0, 9) == 'timestamp') + || ($meta->type == 'datetime') + || ($meta->type == 'time') + ) { + $new_value = Util::addMicroseconds($new_value); + } elseif (mb_strpos($meta->flags, 'binary') !== false) { + $new_value = '0x' . bin2hex($new_value); + } + $extra_data['isNeedToRecheck'] = true; + $extra_data['truncatableFieldValue'] = $new_value; + } + $this->dbi->freeResult($result); + } + + /** + * Function to get the columns of a table + * + * @param string $db current db + * @param string $table current table + * + * @return array + */ + public function getTableColumns($db, $table) + { + $this->dbi->selectDb($db); + return array_values($this->dbi->getColumns($db, $table, null, true)); + } + + /** + * Function to determine Insert/Edit rows + * + * @param string $where_clause where clause + * @param string $db current database + * @param string $table current table + * + * @return mixed + */ + public function determineInsertOrEdit($where_clause, $db, $table) + { + if (isset($_POST['where_clause'])) { + $where_clause = $_POST['where_clause']; + } + if (isset($_SESSION['edit_next'])) { + $where_clause = $_SESSION['edit_next']; + unset($_SESSION['edit_next']); + $after_insert = 'edit_next'; + } + if (isset($_POST['ShowFunctionFields'])) { + $GLOBALS['cfg']['ShowFunctionFields'] = $_POST['ShowFunctionFields']; + } + if (isset($_POST['ShowFieldTypesInDataEditView'])) { + $GLOBALS['cfg']['ShowFieldTypesInDataEditView'] + = $_POST['ShowFieldTypesInDataEditView']; + } + if (isset($_POST['after_insert'])) { + $after_insert = $_POST['after_insert']; + } + + if (isset($where_clause)) { + // we are editing + $insert_mode = false; + $where_clause_array = $this->getWhereClauseArray($where_clause); + list($where_clauses, $result, $rows, $found_unique_key) + = $this->analyzeWhereClauses( + $where_clause_array, + $table, + $db + ); + } else { + // we are inserting + $insert_mode = true; + $where_clause = null; + list($result, $rows) = $this->loadFirstRow($table, $db); + $where_clauses = null; + $where_clause_array = array(); + $found_unique_key = false; + } + + // Copying a row - fetched data will be inserted as a new row, + // therefore the where clause is needless. + if (isset($_POST['default_action']) + && $_POST['default_action'] === 'insert' + ) { + $where_clause = $where_clauses = null; + } + + return array( + $insert_mode, $where_clause, $where_clause_array, $where_clauses, + $result, $rows, $found_unique_key, + isset($after_insert) ? $after_insert : null + ); + } + + /** + * Function to get comments for the table columns + * + * @param string $db current database + * @param string $table current table + * + * @return array $comments_map comments for columns + */ + public function getCommentsMap($db, $table) + { + $comments_map = array(); + + if ($GLOBALS['cfg']['ShowPropertyComments']) { + $comments_map = $this->relation->getComments($db, $table); + } + + return $comments_map; + } + + /** + * Function to get URL parameters + * + * @param string $db current database + * @param string $table current table + * + * @return array $url_params url parameters + */ + public function getUrlParameters($db, $table) + { + /** + * @todo check if we could replace by "db_|tbl_" - please clarify!? + */ + $url_params = array( + 'db' => $db, + 'sql_query' => $_POST['sql_query'] + ); + + if (preg_match('@^tbl_@', $GLOBALS['goto'])) { + $url_params['table'] = $table; + } + + return $url_params; + } + + /** + * Function to get html for the gis editor div + * + * @return string + */ + public function getHtmlForGisEditor() + { + return '
        ' + . '' + . '
        '; + } + + /** + * Function to get html for the ignore option in insert mode + * + * @param int $row_id row id + * @param bool $checked ignore option is checked or not + * + * @return string + */ + public function getHtmlForIgnoreOption($row_id, $checked = true) + { + return '' + . '
        ' . "\n"; + } + + /** + * Function to get html for the function option + * + * @param array $column column + * @param string $column_name_appendix column name appendix + * + * @return String + */ + private function getHtmlForFunctionOption(array $column, $column_name_appendix) + { + return '' + . ''; + } + + /** + * Function to get html for the column type + * + * @param array $column column + * + * @return string + */ + private function getHtmlForInsertEditColumnType(array $column) + { + return ''; + } + + /** + * Function to get html for the insert edit form header + * + * @param bool $has_blob_field whether has blob field + * @param bool $is_upload whether is upload + * + * @return string + */ + public function getHtmlForInsertEditFormHeader($has_blob_field, $is_upload) + { + $html_output ='analyzeTableColumnsArray( + $column, + $comments_map, + $timestamp_seen + ); + } + $as_is = false; + if (!empty($repopulate) && !empty($current_row)) { + $current_row[$column['Field']] = $repopulate[$column['Field_md5']]; + $as_is = true; + } + + $extracted_columnspec + = Util::extractColumnSpec($column['Type']); + + if (-1 === $column['len']) { + $column['len'] = $this->dbi->fieldLen( + $current_result, + $column_number + ); + // length is unknown for geometry fields, + // make enough space to edit very simple WKTs + if (-1 === $column['len']) { + $column['len'] = 30; + } + } + //Call validation when the form submitted... + $onChangeClause = $chg_evt_handler + . "=\"return verificationsAfterFieldChange('" + . Sanitize::escapeJsString($column['Field_md5']) . "', '" + . Sanitize::escapeJsString($jsvkey) . "','" . $column['pma_type'] . "')\""; + + // Use an MD5 as an array index to avoid having special characters + // in the name attribute (see bug #1746964 ) + $column_name_appendix = $vkey . '[' . $column['Field_md5'] . ']'; + + if ($column['Type'] === 'datetime' + && ! isset($column['Default']) + && ! is_null($column['Default']) + && $insert_mode + ) { + $column['Default'] = date('Y-m-d H:i:s', time()); + } + + $html_output = $this->getHtmlForFunctionOption( + $column, + $column_name_appendix + ); + + if ($GLOBALS['cfg']['ShowFieldTypesInDataEditView']) { + $html_output .= $this->getHtmlForInsertEditColumnType($column); + } //End if + + // Get a list of GIS data types. + $gis_data_types = Util::getGISDatatypes(); + + // Prepares the field value + $real_null_value = false; + $special_chars_encoded = ''; + if (!empty($current_row)) { + // (we are editing) + list( + $real_null_value, $special_chars_encoded, $special_chars, + $data, $backup_field + ) + = $this->getSpecialCharsAndBackupFieldForExistingRow( + $current_row, + $column, + $extracted_columnspec, + $real_null_value, + $gis_data_types, + $column_name_appendix, + $as_is + ); + } else { + // (we are inserting) + // display default values + $tmp = $column; + if (isset($repopulate[$column['Field_md5']])) { + $tmp['Default'] = $repopulate[$column['Field_md5']]; + } + list($real_null_value, $data, $special_chars, $backup_field, + $special_chars_encoded + ) + = $this->getSpecialCharsAndBackupFieldForInsertingMode( + $tmp, + $real_null_value + ); + unset($tmp); + } + + $idindex = ($o_rows * $columns_cnt) + $column_number + 1; + $tabindex = $idindex; + + // Get a list of data types that are not yet supported. + $no_support_types = Util::unsupportedDatatypes(); + + // The function column + // ------------------- + $foreignData = $this->relation->getForeignData( + $foreigners, + $column['Field'], + false, + '', + '' + ); + if ($GLOBALS['cfg']['ShowFunctionFields']) { + $html_output .= $this->getFunctionColumn( + $column, + $is_upload, + $column_name_appendix, + $onChangeClause, + $no_support_types, + $tabindex_for_function, + $tabindex, + $idindex, + $insert_mode, + $readOnly, + $foreignData + ); + } + + // The null column + // --------------- + $html_output .= $this->getNullColumn( + $column, + $column_name_appendix, + $real_null_value, + $tabindex, + $tabindex_for_null, + $idindex, + $vkey, + $foreigners, + $foreignData, + $readOnly + ); + + // The value column (depends on type) + // ---------------- + // See bug #1667887 for the reason why we don't use the maxlength + // HTML attribute + + //add data attributes "no of decimals" and "data type" + $no_decimals = 0; + $type = current(explode("(", $column['pma_type'])); + if (preg_match('/\(([^()]+)\)/', $column['pma_type'], $match)) { + $match[0] = trim($match[0], '()'); + $no_decimals = $match[0]; + } + $html_output .= '' . "\n"; + // Will be used by js/tbl_change.js to set the default value + // for the "Continue insertion" feature + $html_output .= '' + . $special_chars . ''; + + // Check input transformation of column + $transformed_html = ''; + if (!empty($column_mime['input_transformation'])) { + $file = $column_mime['input_transformation']; + $include_file = 'libraries/classes/Plugins/Transformations/' . $file; + if (is_file($include_file)) { + $class_name = Transformations::getClassName($include_file); + if (class_exists($class_name)) { + $transformation_plugin = new $class_name(); + $transformation_options = Transformations::getOptions( + $column_mime['input_transformation_options'] + ); + $_url_params = array( + 'db' => $db, + 'table' => $table, + 'transform_key' => $column['Field'], + 'where_clause' => $where_clause + ); + $transformation_options['wrapper_link'] + = Url::getCommon($_url_params); + $current_value = ''; + if (isset($current_row[$column['Field']])) { + $current_value = $current_row[$column['Field']]; + } + if (method_exists($transformation_plugin, 'getInputHtml')) { + $transformed_html = $transformation_plugin->getInputHtml( + $column, + $row_id, + $column_name_appendix, + $transformation_options, + $current_value, + $text_dir, + $tabindex, + $tabindex_for_value, + $idindex + ); + } + if (method_exists($transformation_plugin, 'getScripts')) { + $GLOBALS['plugin_scripts'] = array_merge( + $GLOBALS['plugin_scripts'], + $transformation_plugin->getScripts() + ); + } + } + } + } + if (!empty($transformed_html)) { + $html_output .= $transformed_html; + } else { + $html_output .= $this->getValueColumn( + $column, + $backup_field, + $column_name_appendix, + $onChangeClause, + $tabindex, + $tabindex_for_value, + $idindex, + $data, + $special_chars, + $foreignData, + array($table, $db), + $row_id, + $titles, + $text_dir, + $special_chars_encoded, + $vkey, + $is_upload, + $biggest_max_file_size, + $default_char_editing, + $no_support_types, + $gis_data_types, + $extracted_columnspec, + $readOnly + ); + } + return $html_output; + } + + /** + * Function to get html for each insert/edit row + * + * @param array $url_params url parameters + * @param array $table_columns table columns + * @param array $comments_map comments map + * @param bool $timestamp_seen whether timestamp seen + * @param array $current_result current result + * @param string $chg_evt_handler javascript change event handler + * @param string $jsvkey javascript validation key + * @param string $vkey validation key + * @param bool $insert_mode whether insert mode + * @param array $current_row current row + * @param int &$o_rows row offset + * @param int &$tabindex tab index + * @param int $columns_cnt columns count + * @param bool $is_upload whether upload + * @param int $tabindex_for_function tab index offset for function + * @param array $foreigners foreigners + * @param int $tabindex_for_null tab index offset for null + * @param int $tabindex_for_value tab index offset for value + * @param string $table table + * @param string $db database + * @param int $row_id row id + * @param array $titles titles + * @param int $biggest_max_file_size biggest max file size + * @param string $text_dir text direction + * @param array $repopulate the data to be repopulated + * @param array $where_clause_array the array of where clauses + * + * @return string + */ + public function getHtmlForInsertEditRow( + array $url_params, + array $table_columns, + array $comments_map, + $timestamp_seen, + $current_result, + $chg_evt_handler, + $jsvkey, + $vkey, + $insert_mode, + array $current_row, + &$o_rows, + &$tabindex, + $columns_cnt, + $is_upload, + $tabindex_for_function, + array $foreigners, + $tabindex_for_null, + $tabindex_for_value, + $table, + $db, + $row_id, + array $titles, + $biggest_max_file_size, + $text_dir, + array $repopulate, + array $where_clause_array + ) { + $html_output = $this->getHeadAndFootOfInsertRowTable($url_params) + . ''; + + //store the default value for CharEditing + $default_char_editing = $GLOBALS['cfg']['CharEditing']; + $mime_map = Transformations::getMIME($db, $table); + $where_clause = ''; + if (isset($where_clause_array[$row_id])) { + $where_clause = $where_clause_array[$row_id]; + } + for ($column_number = 0; $column_number < $columns_cnt; $column_number++) { + $table_column = $table_columns[$column_number]; + $column_mime = array(); + if (isset($mime_map[$table_column['Field']])) { + $column_mime = $mime_map[$table_column['Field']]; + } + + $virtual = [ + 'VIRTUAL', + 'PERSISTENT', + 'VIRTUAL GENERATED', + 'STORED GENERATED', + ]; + if (! in_array($table_column['Extra'], $virtual)) { + $html_output .= $this->getHtmlForInsertEditFormColumn( + $table_columns, + $column_number, + $comments_map, + $timestamp_seen, + $current_result, + $chg_evt_handler, + $jsvkey, + $vkey, + $insert_mode, + $current_row, + $o_rows, + $tabindex, + $columns_cnt, + $is_upload, + $tabindex_for_function, + $foreigners, + $tabindex_for_null, + $tabindex_for_value, + $table, + $db, + $row_id, + $titles, + $biggest_max_file_size, + $default_char_editing, + $text_dir, + $repopulate, + $column_mime, + $where_clause + ); + } + } // end for + $o_rows++; + $html_output .= ' ' + . '
        ' . __('Column') . '' . __('Null') . '' . __('Value') . '
        ' + . '' + . '
        ' + . $column['Field_title'] + . '' + . '' + . '' . $column['pma_type'] . '' + . '

        ' + . '
        '; + + return $html_output; + } + + /** + * Returns whether the user has necessary insert/update privileges for the column + * + * @param array $table_column array of column details + * @param bool $insert_mode whether on insert mode + * + * @return boolean whether user has necessary privileges + */ + private function userHasColumnPrivileges(array $table_column, $insert_mode) + { + $privileges = $table_column['Privileges']; + return ($insert_mode && strstr($privileges, 'insert') !== false) + || (! $insert_mode && strstr($privileges, 'update') !== false); + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/IpAllowDeny.php b/php/apps/phpmyadmin49/libraries/classes/IpAllowDeny.php new file mode 100644 index 00000000..dd3ea382 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/IpAllowDeny.php @@ -0,0 +1,306 @@ + -1 + || mb_strpos($ipToTest, ':') > -1 + ) { + // assume IPv6 + $result = self::ipv6MaskTest($testRange, $ipToTest); + } else { + $result = self::ipv4MaskTest($testRange, $ipToTest); + } + + return $result; + } // end of the "self::ipMaskTest()" function + + /** + * Based on IP Pattern Matcher + * Originally by J.Adams + * Found on + * Modified for phpMyAdmin + * + * Matches: + * xxx.xxx.xxx.xxx (exact) + * xxx.xxx.xxx.[yyy-zzz] (range) + * xxx.xxx.xxx.xxx/nn (CIDR) + * + * Does not match: + * xxx.xxx.xxx.xx[yyy-zzz] (range, partial octets not supported) + * + * @param string $testRange string of IP range to match + * @param string $ipToTest string of IP to test against range + * + * @return boolean whether the IP mask matches + * + * @access public + */ + public static function ipv4MaskTest($testRange, $ipToTest) + { + $result = true; + $match = preg_match( + '|([0-9]+)\.([0-9]+)\.([0-9]+)\.([0-9]+)/([0-9]+)|', + $testRange, + $regs + ); + if ($match) { + // performs a mask match + $ipl = ip2long($ipToTest); + $rangel = ip2long( + $regs[1] . '.' . $regs[2] . '.' . $regs[3] . '.' . $regs[4] + ); + + $maskl = 0; + + for ($i = 0; $i < 31; $i++) { + if ($i < $regs[5] - 1) { + $maskl = $maskl + pow(2, (30 - $i)); + } // end if + } // end for + + return ($maskl & $rangel) == ($maskl & $ipl); + } + + // range based + $maskocts = explode('.', $testRange); + $ipocts = explode('.', $ipToTest); + + // perform a range match + for ($i = 0; $i < 4; $i++) { + if (preg_match('|\[([0-9]+)\-([0-9]+)\]|', $maskocts[$i], $regs)) { + if (($ipocts[$i] > $regs[2]) || ($ipocts[$i] < $regs[1])) { + $result = false; + } // end if + } else { + if ($maskocts[$i] <> $ipocts[$i]) { + $result = false; + } // end if + } // end if/else + } //end for + + return $result; + } // end of the "self::ipv4MaskTest()" function + + /** + * IPv6 matcher + * CIDR section taken from https://stackoverflow.com/a/10086404 + * Modified for phpMyAdmin + * + * Matches: + * xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx + * (exact) + * xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:[yyyy-zzzz] + * (range, only at end of IP - no subnets) + * xxxx:xxxx:xxxx:xxxx/nn + * (CIDR) + * + * Does not match: + * xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xx[yyy-zzz] + * (range, partial octets not supported) + * + * @param string $test_range string of IP range to match + * @param string $ip_to_test string of IP to test against range + * + * @return boolean whether the IP mask matches + * + * @access public + */ + public static function ipv6MaskTest($test_range, $ip_to_test) + { + $result = true; + + // convert to lowercase for easier comparison + $test_range = mb_strtolower($test_range); + $ip_to_test = mb_strtolower($ip_to_test); + + $is_cidr = mb_strpos($test_range, '/') > -1; + $is_range = mb_strpos($test_range, '[') > -1; + $is_single = ! $is_cidr && ! $is_range; + + $ip_hex = bin2hex(inet_pton($ip_to_test)); + + if ($is_single) { + $range_hex = bin2hex(inet_pton($test_range)); + $result = hash_equals($ip_hex, $range_hex); + return $result; + } + + if ($is_range) { + // what range do we operate on? + $range_match = array(); + $match = preg_match( + '/\[([0-9a-f]+)\-([0-9a-f]+)\]/', $test_range, $range_match + ); + if ($match) { + $range_start = $range_match[1]; + $range_end = $range_match[2]; + + // get the first and last allowed IPs + $first_ip = str_replace($range_match[0], $range_start, $test_range); + $first_hex = bin2hex(inet_pton($first_ip)); + $last_ip = str_replace($range_match[0], $range_end, $test_range); + $last_hex = bin2hex(inet_pton($last_ip)); + + // check if the IP to test is within the range + $result = ($ip_hex >= $first_hex && $ip_hex <= $last_hex); + } + return $result; + } + + if ($is_cidr) { + // Split in address and prefix length + list($first_ip, $subnet) = explode('/', $test_range); + + // Parse the address into a binary string + $first_bin = inet_pton($first_ip); + $first_hex = bin2hex($first_bin); + + $flexbits = 128 - $subnet; + + // Build the hexadecimal string of the last address + $last_hex = $first_hex; + + $pos = 31; + while ($flexbits > 0) { + // Get the character at this position + $orig = mb_substr($last_hex, $pos, 1); + + // Convert it to an integer + $origval = hexdec($orig); + + // OR it with (2^flexbits)-1, with flexbits limited to 4 at a time + $newval = $origval | (pow(2, min(4, $flexbits)) - 1); + + // Convert it back to a hexadecimal character + $new = dechex($newval); + + // And put that character back in the string + $last_hex = substr_replace($last_hex, $new, $pos, 1); + + // We processed one nibble, move to previous position + $flexbits -= 4; + --$pos; + } + + // check if the IP to test is within the range + $result = ($ip_hex >= $first_hex && $ip_hex <= $last_hex); + } + + return $result; + } // end of the "self::ipv6MaskTest()" function + + /** + * Runs through IP Allow/Deny rules the use of it below for more information + * + * @param string $type 'allow' | 'deny' type of rule to match + * + * @return bool Whether rule has matched + * + * @access public + * + * @see Core::getIp() + */ + public static function allowDeny($type) + { + global $cfg; + + // Grabs true IP of the user and returns if it can't be found + $remote_ip = Core::getIp(); + if (empty($remote_ip)) { + return false; + } + + // copy username + $username = $cfg['Server']['user']; + + // copy rule database + if (isset($cfg['Server']['AllowDeny']['rules'])) { + $rules = $cfg['Server']['AllowDeny']['rules']; + if (! is_array($rules)) { + $rules = array(); + } + } else { + $rules = array(); + } + + // lookup table for some name shortcuts + $shortcuts = array( + 'all' => '0.0.0.0/0', + 'localhost' => '127.0.0.1/8' + ); + + // Provide some useful shortcuts if server gives us address: + if (Core::getenv('SERVER_ADDR')) { + $shortcuts['localnetA'] = Core::getenv('SERVER_ADDR') . '/8'; + $shortcuts['localnetB'] = Core::getenv('SERVER_ADDR') . '/16'; + $shortcuts['localnetC'] = Core::getenv('SERVER_ADDR') . '/24'; + } + + foreach ($rules as $rule) { + // extract rule data + $rule_data = explode(' ', $rule); + + // check for rule type + if ($rule_data[0] != $type) { + continue; + } + + // check for username + if (($rule_data[1] != '%') //wildcarded first + && (! hash_equals($rule_data[1], $username)) + ) { + continue; + } + + // check if the config file has the full string with an extra + // 'from' in it and if it does, just discard it + if ($rule_data[2] == 'from') { + $rule_data[2] = $rule_data[3]; + } + + // Handle shortcuts with above array + if (isset($shortcuts[$rule_data[2]])) { + $rule_data[2] = $shortcuts[$rule_data[2]]; + } + + // Add code for host lookups here + // Excluded for the moment + + // Do the actual matching now + if (self::ipMaskTest($rule_data[2], $remote_ip)) { + return true; + } + } // end while + + return false; + } // end of the "self::allowDeny()" function +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Language.php b/php/apps/phpmyadmin49/libraries/classes/Language.php new file mode 100644 index 00000000..36e65f75 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Language.php @@ -0,0 +1,202 @@ +code = $code; + $this->name = $name; + $this->native = $native; + if (strpos($regex, '[-_]') === false) { + $regex = str_replace('|', '([-_][[:alpha:]]{2,3})?|', $regex); + } + $this->regex = $regex; + $this->mysql = $mysql; + } + + /** + * Returns native name for language + * + * @return string + */ + public function getNativeName() + { + return $this->native; + } + + /** + * Returns English name for language + * + * @return string + */ + public function getEnglishName() + { + return $this->name; + } + + /** + * Returns verbose name for language + * + * @return string + */ + public function getName() + { + if (! empty($this->native)) { + return $this->native . ' - ' . $this->name; + } + + return $this->name; + } + + /** + * Returns language code + * + * @return string + */ + public function getCode() + { + return $this->code; + } + + /** + * Returns MySQL locale code, can be empty + * + * @return string + */ + public function getMySQLLocale() + { + return $this->mysql; + } + + /** + * Compare function used for sorting + * + * @param Language $other Other object to compare + * + * @return int same as strcmp + */ + public function cmp($other) + { + return strcmp($this->name, $other->name); + } + + /** + * Checks whether language is currently active. + * + * @return bool + */ + public function isActive() + { + return $GLOBALS['lang'] == $this->code; + } + + /** + * Checks whether language matches HTTP header Accept-Language. + * + * @param string $header Header content + * + * @return bool + */ + public function matchesAcceptLanguage($header) + { + $pattern = '/^(' + . addcslashes($this->regex, '/') + . ')(;q=[0-9]\\.[0-9])?$/i'; + return preg_match($pattern, $header); + } + + /** + * Checks whether language matches HTTP header User-Agent + * + * @param string $header Header content + * + * @return bool + */ + public function matchesUserAgent($header) + { + $pattern = '/(\(|\[|;[[:space:]])(' + . addcslashes($this->regex, '/') + . ')(;|\]|\))/i'; + return preg_match($pattern, $header); + } + + /** + * Checks whether language is RTL + * + * @return bool + */ + public function isRTL() + { + return in_array($this->code, array('ar', 'fa', 'he', 'ur')); + } + + /** + * Activates given translation + * + * @return bool + */ + public function activate() + { + $GLOBALS['lang'] = $this->code; + + // Set locale + _setlocale(0, $this->code); + _bindtextdomain('phpmyadmin', LOCALE_PATH); + _textdomain('phpmyadmin'); + // Set PHP locale as well + if (function_exists('setlocale')) { + setlocale(0, $this->code); + } + + /* Text direction for language */ + if ($this->isRTL()) { + $GLOBALS['text_dir'] = 'rtl'; + } else { + $GLOBALS['text_dir'] = 'ltr'; + } + + /* TCPDF */ + $GLOBALS['l'] = array(); + + /* TCPDF settings */ + $GLOBALS['l']['a_meta_charset'] = 'UTF-8'; + $GLOBALS['l']['a_meta_dir'] = $GLOBALS['text_dir']; + $GLOBALS['l']['a_meta_language'] = $this->code; + + /* TCPDF translations */ + $GLOBALS['l']['w_page'] = __('Page number:'); + + /* Show possible warnings from langauge selection */ + LanguageManager::getInstance()->showWarnings(); + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/LanguageManager.php b/php/apps/phpmyadmin49/libraries/classes/LanguageManager.php new file mode 100644 index 00000000..6e117df9 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/LanguageManager.php @@ -0,0 +1,966 @@ + array( + 'af', + 'Afrikaans', + '', + 'af|afrikaans', + '', + ), + 'ar' => array( + 'ar', + 'Arabic', + 'العربية', + 'ar|arabic', + 'ar_AE', + ), + 'az' => array( + 'az', + 'Azerbaijani', + 'Azərbaycanca', + 'az|azerbaijani', + '', + ), + 'bn' => array( + 'bn', + 'Bangla', + 'বাংলা', + 'bn|bangla', + '', + ), + 'be' => array( + 'be', + 'Belarusian', + 'Беларуская', + 'be|belarusian', + 'be_BY', + ), + 'be@latin' => array( + 'be@latin', + 'Belarusian (latin)', + 'Biełaruskaja', + 'be[-_]lat|be@latin|belarusian latin', + '', + ), + 'bg' => array( + 'bg', + 'Bulgarian', + 'Български', + 'bg|bulgarian', + 'bg_BG', + ), + 'bs' => array( + 'bs', + 'Bosnian', + 'Bosanski', + 'bs|bosnian', + '', + ), + 'br' => array( + 'br', + 'Breton', + 'Brezhoneg', + 'br|breton', + '', + ), + 'brx' => array( + 'brx', + 'Bodo', + 'बड़ो', + 'brx|bodo', + '', + ), + 'ca' => array( + 'ca', + 'Catalan', + 'Català', + 'ca|catalan', + 'ca_ES', + ), + 'ckb' => array( + 'ckb', + 'Sorani', + 'سۆرانی', + 'ckb|sorani', + '', + ), + 'cs' => array( + 'cs', + 'Czech', + 'Čeština', + 'cs|czech', + 'cs_CZ', + ), + 'cy' => array( + 'cy', + 'Welsh', + 'Cymraeg', + 'cy|welsh', + '', + ), + 'da' => array( + 'da', + 'Danish', + 'Dansk', + 'da|danish', + 'da_DK', + ), + 'de' => array( + 'de', + 'German', + 'Deutsch', + 'de|german', + 'de_DE', + ), + 'el' => array( + 'el', + 'Greek', + 'Ελληνικά', + 'el|greek', + '', + ), + 'en' => array( + 'en', + 'English', + '', + 'en|english', + 'en_US', + ), + 'en_gb' => array( + 'en_GB', + 'English (United Kingdom)', + '', + 'en[_-]gb|english (United Kingdom)', + 'en_GB', + ), + 'eo' => array( + 'eo', + 'Esperanto', + 'Esperanto', + 'eo|esperanto', + '', + ), + 'es' => array( + 'es', + 'Spanish', + 'Español', + 'es|spanish', + 'es_ES', + ), + 'et' => array( + 'et', + 'Estonian', + 'Eesti', + 'et|estonian', + 'et_EE', + ), + 'eu' => array( + 'eu', + 'Basque', + 'Euskara', + 'eu|basque', + 'eu_ES', + ), + 'fa' => array( + 'fa', + 'Persian', + 'فارسی', + 'fa|persian', + '', + ), + 'fi' => array( + 'fi', + 'Finnish', + 'Suomi', + 'fi|finnish', + 'fi_FI', + ), + 'fil' => array( + 'fil', + 'Filipino', + 'Pilipino', + 'fil|filipino', + '', + ), + 'fr' => array( + 'fr', + 'French', + 'Français', + 'fr|french', + 'fr_FR', + ), + 'fy' => array( + 'fy', + 'Frisian', + 'Frysk', + 'fy|frisian', + '', + ), + 'gl' => array( + 'gl', + 'Galician', + 'Galego', + 'gl|galician', + 'gl_ES', + ), + 'gu' => array( + 'gu', + 'Gujarati', + 'ગુજરાતી', + 'gu|gujarati', + 'gu_IN', + ), + 'he' => array( + 'he', + 'Hebrew', + 'עברית', + 'he|hebrew', + 'he_IL', + ), + 'hi' => array( + 'hi', + 'Hindi', + 'हिन्दी', + 'hi|hindi', + 'hi_IN', + ), + 'hr' => array( + 'hr', + 'Croatian', + 'Hrvatski', + 'hr|croatian', + 'hr_HR', + ), + 'hu' => array( + 'hu', + 'Hungarian', + 'Magyar', + 'hu|hungarian', + 'hu_HU', + ), + 'hy' => array( + 'hy', + 'Armenian', + 'Հայերէն', + 'hy|armenian', + '', + ), + 'ia' => array( + 'ia', + 'Interlingua', + '', + 'ia|interlingua', + '', + ), + 'id' => array( + 'id', + 'Indonesian', + 'Bahasa Indonesia', + 'id|indonesian', + 'id_ID', + ), + 'ig' => array( + 'ig', + 'Igbo', + 'Asụsụ Igbo', + 'ig|igbo', + '', + ), + 'it' => array( + 'it', + 'Italian', + 'Italiano', + 'it|italian', + 'it_IT', + ), + 'ja' => array( + 'ja', + 'Japanese', + '日本語', + 'ja|japanese', + 'ja_JP', + ), + 'ko' => array( + 'ko', + 'Korean', + '한국어', + 'ko|korean', + 'ko_KR', + ), + 'ka' => array( + 'ka', + 'Georgian', + 'ქართული', + 'ka|georgian', + '', + ), + 'kab' => array( + 'kab', + 'Kabylian', + 'Taqbaylit', + 'kab|kabylian', + '', + ), + 'kk' => array( + 'kk', + 'Kazakh', + 'Қазақ', + 'kk|kazakh', + '', + ), + 'km' => array( + 'km', + 'Khmer', + 'ខ្មែរ', + 'km|khmer', + '', + ), + 'kn' => array( + 'kn', + 'Kannada', + 'ಕನ್ನಡ', + 'kn|kannada', + '', + ), + 'ksh' => array( + 'ksh', + 'Colognian', + 'Kölsch', + 'ksh|colognian', + '', + ), + 'ku' => array( + 'ku', + 'Kurdish', + 'کوردی', + 'ku|kurdish', + '', + ), + 'ky' => array( + 'ky', + 'Kyrgyz', + 'Кыргызча', + 'ky|kyrgyz', + '', + ), + 'li' => array( + 'li', + 'Limburgish', + 'Lèmbörgs', + 'li|limburgish', + '', + ), + 'lt' => array( + 'lt', + 'Lithuanian', + 'Lietuvių', + 'lt|lithuanian', + 'lt_LT', + ), + 'lv' => array( + 'lv', + 'Latvian', + 'Latviešu', + 'lv|latvian', + 'lv_LV', + ), + 'mk' => array( + 'mk', + 'Macedonian', + 'Macedonian', + 'mk|macedonian', + 'mk_MK', + ), + 'ml' => array( + 'ml', + 'Malayalam', + 'Malayalam', + 'ml|malayalam', + '', + ), + 'mn' => array( + 'mn', + 'Mongolian', + 'Монгол', + 'mn|mongolian', + 'mn_MN', + ), + 'ms' => array( + 'ms', + 'Malay', + 'Bahasa Melayu', + 'ms|malay', + 'ms_MY', + ), + 'my' => array( + 'my', + 'Burmese', + 'မြန်မာ', + 'my|burmese', + '', + ), + 'ne' => array( + 'ne', + 'Nepali', + 'नेपाली', + 'ne|nepali', + '', + ), + 'nb' => array( + 'nb', + 'Norwegian', + 'Norsk', + 'nb|norwegian', + 'nb_NO', + ), + 'nn' => array( + 'nn', + 'Norwegian Nynorsk', + 'Nynorsk', + 'nn|nynorsk', + 'nn_NO', + ), + 'nl' => array( + 'nl', + 'Dutch', + 'Nederlands', + 'nl|dutch', + 'nl_NL', + ), + 'pa' => array( + 'pa', + 'Punjabi', + 'ਪੰਜਾਬੀ', + 'pa|punjabi', + '', + ), + 'pl' => array( + 'pl', + 'Polish', + 'Polski', + 'pl|polish', + 'pl_PL', + ), + 'pt_br' => array( + 'pt_BR', + 'Brazilian Portuguese', + 'Português', + 'pt[-_]br|brazilian portuguese', + 'pt_BR', + ), + 'pt' => array( + 'pt', + 'Portuguese', + 'Português', + 'pt|portuguese', + 'pt_PT', + ), + 'ro' => array( + 'ro', + 'Romanian', + 'Română', + 'ro|romanian', + 'ro_RO', + ), + 'ru' => array( + 'ru', + 'Russian', + 'Русский', + 'ru|russian', + 'ru_RU', + ), + 'si' => array( + 'si', + 'Sinhala', + 'සිංහල', + 'si|sinhala', + '', + ), + 'sk' => array( + 'sk', + 'Slovak', + 'Slovenčina', + 'sk|slovak', + 'sk_SK', + ), + 'sl' => array( + 'sl', + 'Slovenian', + 'Slovenščina', + 'sl|slovenian', + 'sl_SI', + ), + 'sq' => array( + 'sq', + 'Albanian', + 'Shqip', + 'sq|albanian', + 'sq_AL', + ), + 'sr@latin' => array( + 'sr@latin', + 'Serbian (latin)', + 'Srpski', + 'sr[-_]lat|sr@latin|serbian latin', + 'sr_YU', + ), + 'sr' => array( + 'sr', + 'Serbian', + 'Српски', + 'sr|serbian', + 'sr_YU', + ), + 'sv' => array( + 'sv', + 'Swedish', + 'Svenska', + 'sv|swedish', + 'sv_SE', + ), + 'ta' => array( + 'ta', + 'Tamil', + 'தமிழ்', + 'ta|tamil', + 'ta_IN', + ), + 'te' => array( + 'te', + 'Telugu', + 'తెలుగు', + 'te|telugu', + 'te_IN', + ), + 'th' => array( + 'th', + 'Thai', + 'ภาษาไทย', + 'th|thai', + 'th_TH', + ), + 'tk' => array( + 'tk', + 'Turkmen', + 'Türkmençe', + 'tk|turkmen', + '', + ), + 'tr' => array( + 'tr', + 'Turkish', + 'Türkçe', + 'tr|turkish', + 'tr_TR', + ), + 'tt' => array( + 'tt', + 'Tatarish', + 'Tatarça', + 'tt|tatarish', + '', + ), + 'ug' => array( + 'ug', + 'Uyghur', + 'ئۇيغۇرچە', + 'ug|uyghur', + '', + ), + 'uk' => array( + 'uk', + 'Ukrainian', + 'Українська', + 'uk|ukrainian', + 'uk_UA', + ), + 'ur' => array( + 'ur', + 'Urdu', + 'اُردوُ', + 'ur|urdu', + 'ur_PK', + ), + 'uz@latin' => array( + 'uz@latin', + 'Uzbek (latin)', + 'O‘zbekcha', + 'uz[-_]lat|uz@latin|uzbek-latin', + '', + ), + 'uz' => array( + 'uz', + 'Uzbek (cyrillic)', + 'Ўзбекча', + 'uz[-_]cyr|uz@cyrillic|uzbek-cyrillic', + '', + ), + 'vi' => array( + 'vi', + 'Vietnamese', + 'Tiếng Việt', + 'vi|vietnamese', + 'vi_VN', + ), + 'vls' => array( + 'vls', + 'Flemish', + 'West-Vlams', + 'vls|flemish', + '', + ), + 'zh_tw' => array( + 'zh_TW', + 'Chinese traditional', + '中文', + 'zh[-_](tw|hk)|chinese traditional', + 'zh_TW', + ), + // only TW and HK use traditional Chinese while others (CN, SG, MY) + // use simplified Chinese + 'zh_cn' => array( + 'zh_CN', + 'Chinese simplified', + '中文', + 'zh(?![-_](tw|hk))([-_][[:alpha:]]{2,3})?|chinese simplified', + 'zh_CN', + ), + ); + + private $_available_locales; + private $_available_languages; + private $_lang_failed_cfg; + private $_lang_failed_cookie; + private $_lang_failed_request; + private static $instance; + + /** + * Returns LanguageManager singleton + * + * @return LanguageManager + */ + public static function getInstance() + { + if (self::$instance === null) { + self::$instance = new LanguageManager; + } + return self::$instance; + } + + /** + * Returns list of available locales + * + * @return array + */ + public function listLocaleDir() + { + $result = array('en'); + + /* Check for existing directory */ + if (!is_dir(LOCALE_PATH)) { + return $result; + } + + /* Open the directory */ + $handle = @opendir(LOCALE_PATH); + /* This can happen if the kit is English-only */ + if ($handle === false) { + return $result; + } + + /* Process all files */ + while (false !== ($file = readdir($handle))) { + $path = LOCALE_PATH + . '/' . $file + . '/LC_MESSAGES/phpmyadmin.mo'; + if ($file != "." + && $file != ".." + && @file_exists($path) + ) { + $result[] = $file; + } + } + /* Close the handle */ + closedir($handle); + + return $result; + } + + /** + * Returns (cached) list of all available locales + * + * @return array of strings + */ + public function availableLocales() + { + if (! $this->_available_locales) { + + if (! isset($GLOBALS['PMA_Config']) || empty($GLOBALS['PMA_Config']->get('FilterLanguages'))) { + $this->_available_locales = $this->listLocaleDir(); + } else { + $this->_available_locales = preg_grep( + '@' . $GLOBALS['PMA_Config']->get('FilterLanguages') . '@', + $this->listLocaleDir() + ); + } + } + return $this->_available_locales; + } + + /** + * Checks whether there are some languages available + * + * @return boolean + */ + public function hasChoice() + { + return count($this->availableLanguages()) > 1; + } + + /** + * Returns (cached) list of all available languages + * + * @return array of Language objects + */ + public function availableLanguages() + { + if (! $this->_available_languages) { + $this->_available_languages = array(); + + foreach($this->availableLocales() as $lang) { + $lang = strtolower($lang); + if (isset($this::$_language_data[$lang])) { + $data = $this::$_language_data[$lang]; + $this->_available_languages[$lang] = new Language( + $data[0], + $data[1], + $data[2], + $data[3], + $data[4] + ); + } else { + $this->_available_languages[$lang] = new Language( + $lang, + ucfirst($lang), + ucfirst($lang), + $lang, + '' + ); + } + } + } + return $this->_available_languages; + } + + /** + * Returns (cached) list of all available languages sorted + * by name + * + * @return array of Language objects + */ + public function sortedLanguages() + { + $this->availableLanguages(); + uasort($this->_available_languages, function($a, $b) + { + return $a->cmp($b); + } + ); + return $this->_available_languages; + } + + /** + * Return Language object for given code + * + * @param string $code Language code + * + * @return object|false Language object or false on failure + */ + public function getLanguage($code) + { + $code = strtolower($code); + $langs = $this->availableLanguages(); + if (isset($langs[$code])) { + return $langs[$code]; + } + return false; + } + + /** + * Return currently active Language object + * + * @return object Language object + */ + public function getCurrentLanguage() + { + return $this->_available_languages[strtolower($GLOBALS['lang'])]; + } + + /** + * Activates language based on configuration, user preferences or + * browser + * + * @return Language + */ + public function selectLanguage() + { + // check forced language + if (! empty($GLOBALS['PMA_Config']->get('Lang'))) { + $lang = $this->getLanguage($GLOBALS['PMA_Config']->get('Lang')); + if ($lang !== false) { + return $lang; + } + $this->_lang_failed_cfg = true; + } + + // Don't use REQUEST in following code as it might be confused by cookies + // with same name. Check user requested language (POST) + if (! empty($_POST['lang'])) { + $lang = $this->getLanguage($_POST['lang']); + if ($lang !== false) { + return $lang; + } + $this->_lang_failed_request = true; + } + + // check user requested language (GET) + if (! empty($_GET['lang'])) { + $lang = $this->getLanguage($_GET['lang']); + if ($lang !== false) { + return $lang; + } + $this->_lang_failed_request = true; + } + + // check previous set language + if (! empty($_COOKIE['pma_lang'])) { + $lang = $this->getLanguage($_COOKIE['pma_lang']); + if ($lang !== false) { + return $lang; + } + $this->_lang_failed_cookie = true; + } + + $langs = $this->availableLanguages(); + + // try to find out user's language by checking its HTTP_ACCEPT_LANGUAGE variable; + $accepted_languages = Core::getenv('HTTP_ACCEPT_LANGUAGE'); + if ($accepted_languages) { + foreach (explode(',', $accepted_languages) as $header) { + foreach ($langs as $language) { + if ($language->matchesAcceptLanguage($header)) { + return $language; + } + } + } + } + + // try to find out user's language by checking its HTTP_USER_AGENT variable + $user_agent = Core::getenv('HTTP_USER_AGENT'); + if (! empty($user_agent)) { + foreach ($langs as $language) { + if ($language->matchesUserAgent($user_agent)) { + return $language; + } + } + } + + // Didn't catch any valid lang : we use the default settings + if (isset($langs[$GLOBALS['PMA_Config']->get('DefaultLang')])) { + return $langs[$GLOBALS['PMA_Config']->get('DefaultLang')]; + } + + // Fallback to English + return $langs['en']; + } + + /** + * Displays warnings about invalid languages. This needs to be postponed + * to show messages at time when language is initialized. + * + * @return void + */ + public function showWarnings() + { + // now, that we have loaded the language strings we can send the errors + if ($this->_lang_failed_cfg + || $this->_lang_failed_cookie + || $this->_lang_failed_request + ) { + trigger_error( + __('Ignoring unsupported language code.'), + E_USER_ERROR + ); + } + } + + + /** + * Returns HTML code for the language selector + * + * @param boolean $use_fieldset whether to use fieldset for selection + * @param boolean $show_doc whether to show documentation links + * + * @return string + * + * @access public + */ + public function getSelectorDisplay($use_fieldset = false, $show_doc = true) + { + $_form_params = array( + 'db' => $GLOBALS['db'], + 'table' => $GLOBALS['table'], + ); + + // For non-English, display "Language" with emphasis because it's + // not a proper word in the current language; we show it to help + // people recognize the dialog + $language_title = __('Language') + . (__('Language') != 'Language' ? ' - Language' : ''); + if ($show_doc) { + $language_title .= Util::showDocu('faq', 'faq7-2'); + } + + $available_languages = $this->sortedLanguages(); + + return Template::get('select_lang')->render( + array( + 'language_title' => $language_title, + 'use_fieldset' => $use_fieldset, + 'available_languages' => $available_languages, + '_form_params' => $_form_params, + ) + ); + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Linter.php b/php/apps/phpmyadmin49/libraries/classes/Linter.php new file mode 100644 index 00000000..53dd207b --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Linter.php @@ -0,0 +1,179 @@ +length() : strlen($str); + + $lines = array(0); + for ($i = 0; $i < $len; ++$i) { + if ($str[$i] === "\n") { + $lines[] = $i + 1; + } + } + return $lines; + } + + /** + * Computes the number of the line and column given an absolute position. + * + * @param array $lines The starting position of each line. + * @param int $pos The absolute position + * + * @return array + */ + public static function findLineNumberAndColumn(array $lines, $pos) + { + $line = 0; + foreach ($lines as $lineNo => $lineStart) { + if ($lineStart > $pos) { + break; + } + $line = $lineNo; + } + return array($line, $pos - $lines[$line]); + } + + /** + * Runs the linting process. + * + * @param string $query The query to be checked. + * + * @return array + */ + public static function lint($query) + { + // Disabling lint for huge queries to save some resources. + if (mb_strlen($query) > 10000) { + return array( + array( + 'message' => __( + 'Linting is disabled for this query because it exceeds the ' + . 'maximum length.' + ), + 'fromLine' => 0, + 'fromColumn' => 0, + 'toLine' => 0, + 'toColumn' => 0, + 'severity' => 'warning', + ) + ); + } + + /** + * Lexer used for tokenizing the query. + * + * @var Lexer + */ + $lexer = new Lexer($query); + + /** + * Parsed used for analysing the query. + * + * @var Parser + */ + $parser = new Parser($lexer->list); + + /** + * Array containing all errors. + * + * @var array + */ + $errors = ParserError::get(array($lexer, $parser)); + + /** + * The response containing of all errors. + * + * @var array + */ + $response = array(); + + /** + * The starting position for each line. + * + * CodeMirror requires relative position to line, but the parser stores + * only the absolute position of the character in string. + * + * @var array + */ + $lines = static::getLines($query); + + // Building the response. + foreach ($errors as $idx => $error) { + + // Starting position of the string that caused the error. + list($fromLine, $fromColumn) = static::findLineNumberAndColumn( + $lines, $error[3] + ); + + // Ending position of the string that caused the error. + list($toLine, $toColumn) = static::findLineNumberAndColumn( + $lines, $error[3] + mb_strlen($error[2]) + ); + + // Building the response. + $response[] = array( + 'message' => sprintf( + __('%1$s (near %2$s)'), + htmlspecialchars($error[0]), htmlspecialchars($error[2]) + ), + 'fromLine' => $fromLine, + 'fromColumn' => $fromColumn, + 'toLine' => $toLine, + 'toColumn' => $toColumn, + 'severity' => 'error', + ); + } + + // Sending back the answer. + return $response; + } + +} diff --git a/php/apps/phpmyadmin49/libraries/classes/ListAbstract.php b/php/apps/phpmyadmin49/libraries/classes/ListAbstract.php new file mode 100644 index 00000000..9e28490e --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/ListAbstract.php @@ -0,0 +1,121 @@ +item_empty; + } + + /** + * checks if the given db names exists in the current list, if there is + * missing at least one item it returns false otherwise true + * + * @return boolean true if all items exists, otherwise false + */ + public function exists() + { + $this_elements = $this->getArrayCopy(); + foreach (func_get_args() as $result) { + if (! in_array($result, $this_elements)) { + return false; + } + } + + return true; + } + + /** + * returns HTML '; + return $html; + } + + /** + * Gets HTML for replace_prefix_tbl or copy_tbl_change_prefix + * + * @param string $action action type + * @param array $urlParams URL params + * + * @return string + */ + public function getHtmlForReplacePrefixTable($action, array $urlParams) + { + $html = '
        '; + $html .= Url::getHiddenInputs($urlParams); + $html .= '
        '; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= '
        ' . __('From') . ''; + $html .= ''; + $html .= '
        ' . __('To') . ''; + $html .= ''; + $html .= '
        '; + $html .= '
        '; + $html .= ''; + $html .= '
        '; + + return $html; + } + + /** + * Gets HTML for add_prefix_tbl + * + * @param string $action action type + * @param array $urlParams URL params + * + * @return string + */ + public function getHtmlForAddPrefixTable($action, array $urlParams) + { + $html = '
        '; + $html .= Url::getHiddenInputs($urlParams); + $html .= '
        '; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= '
        ' . __('Add prefix') . ''; + $html .= ''; + $html .= '
        '; + $html .= '
        '; + $html .= ''; + $html .= '
        '; + + return $html; + } + + /** + * Gets HTML for other mult_submits actions + * + * @param string $what mult_submit type + * @param string $action action type + * @param array $urlParams URL params + * @param string $fullQuery full sql query string + * + * @return string + */ + public function getHtmlForOtherActions($what, $action, array $urlParams, $fullQuery) + { + $html = '
        '; + $html .= Url::getHiddenInputs($urlParams); + $html .= '
        '; + $html .= ''; + if ($what == 'drop_db') { + $html .= __('You are about to DESTROY a complete database!') . ' '; + } + $html .= __('Do you really want to execute the following query?'); + $html .= ''; + $html .= '' . $fullQuery . ''; + $html .= '
        '; + $html .= '
        '; + // Display option to disable foreign key checks while dropping tables + if ($what === 'drop_tbl' || $what === 'empty_tbl' || $what === 'row_delete') { + $html .= '
        '; + $html .= Util::getFKCheckbox(); + $html .= '
        '; + } + $html .= ''; + $html .= ''; + $html .= '
        '; + $html .= '
        '; + + return $html; + } + + /** + * Get query string from Selected + * + * @param string $what mult_submit type + * @param string $table table name + * @param array $selected the selected columns + * @param array $views table views + * + * @return array + */ + public function getQueryFromSelected($what, $table, array $selected, array $views) + { + $reload = false; + $fullQueryViews = null; + $fullQuery = ''; + + if ($what == 'drop_tbl') { + $fullQueryViews = ''; + } + + $selectedCount = count($selected); + $i = 0; + foreach ($selected as $selectedValue) { + switch ($what) { + case 'row_delete': + $fullQuery .= 'DELETE FROM ' + . Util::backquote(htmlspecialchars($table)) + // Do not append a "LIMIT 1" clause here + // (it's not binlog friendly). + // We don't need the clause because the calling panel permits + // this feature only when there is a unique index. + . ' WHERE ' . htmlspecialchars($selectedValue) + . ';
        '; + break; + case 'drop_db': + $fullQuery .= 'DROP DATABASE ' + . Util::backquote(htmlspecialchars($selectedValue)) + . ';
        '; + $reload = true; + break; + + case 'drop_tbl': + $current = $selectedValue; + if (!empty($views) && in_array($current, $views)) { + $fullQueryViews .= (empty($fullQueryViews) ? 'DROP VIEW ' : ', ') + . Util::backquote(htmlspecialchars($current)); + } else { + $fullQuery .= (empty($fullQuery) ? 'DROP TABLE ' : ', ') + . Util::backquote(htmlspecialchars($current)); + } + break; + + case 'empty_tbl': + $fullQuery .= 'TRUNCATE '; + $fullQuery .= Util::backquote(htmlspecialchars($selectedValue)) + . ';
        '; + break; + + case 'primary_fld': + if ($fullQuery == '') { + $fullQuery .= 'ALTER TABLE ' + . Util::backquote(htmlspecialchars($table)) + . '
          DROP PRIMARY KEY,' + . '
           ADD PRIMARY KEY(' + . '
             ' + . Util::backquote(htmlspecialchars($selectedValue)) + . ','; + } else { + $fullQuery .= '
             ' + . Util::backquote(htmlspecialchars($selectedValue)) + . ','; + } + if ($i == $selectedCount - 1) { + $fullQuery = preg_replace('@,$@', ');
        ', $fullQuery); + } + break; + + case 'drop_fld': + if ($fullQuery == '') { + $fullQuery .= 'ALTER TABLE ' + . Util::backquote(htmlspecialchars($table)); + } + $fullQuery .= '
          DROP ' + . Util::backquote(htmlspecialchars($selectedValue)) + . ','; + if ($i == $selectedCount - 1) { + $fullQuery = preg_replace('@,$@', ';
        ', $fullQuery); + } + break; + } // end switch + $i++; + } + + if ($what == 'drop_tbl') { + if (!empty($fullQuery)) { + $fullQuery .= ';
        ' . "\n"; + } + if (!empty($fullQueryViews)) { + $fullQuery .= $fullQueryViews . ';
        ' . "\n"; + } + unset($fullQueryViews); + } + + $fullQueryViews = isset($fullQueryViews) ? $fullQueryViews : null; + + return [$fullQuery, $reload, $fullQueryViews]; + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Navigation/Navigation.php b/php/apps/phpmyadmin49/libraries/classes/Navigation/Navigation.php new file mode 100644 index 00000000..486bd2f7 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Navigation/Navigation.php @@ -0,0 +1,249 @@ +relation = new Relation(); + } + + /** + * Renders the navigation tree, or part of it + * + * @return string The navigation tree + */ + public function getDisplay() + { + /* Init */ + $retval = ''; + $response = Response::getInstance(); + if (! $response->isAjax()) { + $header = new NavigationHeader(); + $retval = $header->getDisplay(); + } + $tree = new NavigationTree(); + if (! $response->isAjax() + || ! empty($_POST['full']) + || ! empty($_POST['reload']) + ) { + if ($GLOBALS['cfg']['ShowDatabasesNavigationAsTree']) { + // provide database tree in navigation + $navRender = $tree->renderState(); + } else { + // provide legacy pre-4.0 navigation + $navRender = $tree->renderDbSelect(); + } + } else { + $navRender = $tree->renderPath(); + } + if (! $navRender) { + $retval .= Message::error( + __('An error has occurred while loading the navigation display') + )->getDisplay(); + } else { + $retval .= $navRender; + } + + if (! $response->isAjax()) { + // closes the tags that were opened by the navigation header + $retval .= '
        '; // pma_navigation_tree + $retval .= '
        '; + if (!defined('PMA_DISABLE_NAVI_SETTINGS')) { + $retval .= PageSettings::getNaviSettings(); + } + $retval .= '
        '; //pma_navi_settings_container + $retval .= '
        '; // pma_navigation_content + $retval .= $this->_getDropHandler(); + $retval .= '
        '; // pma_navigation + } + + return $retval; + } + + /** + * Add an item of navigation tree to the hidden items list in PMA database. + * + * @param string $itemName name of the navigation tree item + * @param string $itemType type of the navigation tree item + * @param string $dbName database name + * @param string $tableName table name if applicable + * + * @return void + */ + public function hideNavigationItem( + $itemName, $itemType, $dbName, $tableName = null + ) { + $navTable = Util::backquote($GLOBALS['cfgRelation']['db']) + . "." . Util::backquote($GLOBALS['cfgRelation']['navigationhiding']); + $sqlQuery = "INSERT INTO " . $navTable + . "(`username`, `item_name`, `item_type`, `db_name`, `table_name`)" + . " VALUES (" + . "'" . $GLOBALS['dbi']->escapeString($GLOBALS['cfg']['Server']['user']) . "'," + . "'" . $GLOBALS['dbi']->escapeString($itemName) . "'," + . "'" . $GLOBALS['dbi']->escapeString($itemType) . "'," + . "'" . $GLOBALS['dbi']->escapeString($dbName) . "'," + . "'" . (! empty($tableName)? $GLOBALS['dbi']->escapeString($tableName) : "" ) + . "')"; + $this->relation->queryAsControlUser($sqlQuery, false); + } + + /** + * Inserts Drag and Drop Import handler + * + * @return string html code for drop handler + */ + private function _getDropHandler() + { + $retval = ''; + $retval .= '
        ' + . __('Drop files here') + . '
        '; + $retval .= '
        '; + $retval .= '

        SQL upload ( '; + $retval .= '0 '; + $retval .= ') x'; + $retval .= '-

        '; + $retval .= '
        '; + $retval .= '
        '; + return $retval; + } + + /** + * Remove a hidden item of navigation tree from the + * list of hidden items in PMA database. + * + * @param string $itemName name of the navigation tree item + * @param string $itemType type of the navigation tree item + * @param string $dbName database name + * @param string $tableName table name if applicable + * + * @return void + */ + public function unhideNavigationItem( + $itemName, $itemType, $dbName, $tableName = null + ) { + $navTable = Util::backquote($GLOBALS['cfgRelation']['db']) + . "." . Util::backquote($GLOBALS['cfgRelation']['navigationhiding']); + $sqlQuery = "DELETE FROM " . $navTable + . " WHERE" + . " `username`='" + . $GLOBALS['dbi']->escapeString($GLOBALS['cfg']['Server']['user']) . "'" + . " AND `item_name`='" . $GLOBALS['dbi']->escapeString($itemName) . "'" + . " AND `item_type`='" . $GLOBALS['dbi']->escapeString($itemType) . "'" + . " AND `db_name`='" . $GLOBALS['dbi']->escapeString($dbName) . "'" + . (! empty($tableName) + ? " AND `table_name`='" . $GLOBALS['dbi']->escapeString($tableName) . "'" + : "" + ); + $this->relation->queryAsControlUser($sqlQuery, false); + } + + /** + * Returns HTML for the dialog to show hidden navigation items. + * + * @param string $dbName database name + * @param string $itemType type of the items to include + * @param string $tableName table name + * + * @return string HTML for the dialog to show hidden navigation items + */ + public function getItemUnhideDialog($dbName, $itemType = null, $tableName = null) + { + $html = '
        '; + $html .= '
        '; + $html .= Url::getHiddenInputs($dbName, $tableName); + + $navTable = Util::backquote($GLOBALS['cfgRelation']['db']) + . "." . Util::backquote($GLOBALS['cfgRelation']['navigationhiding']); + $sqlQuery = "SELECT `item_name`, `item_type` FROM " . $navTable + . " WHERE `username`='" + . $GLOBALS['dbi']->escapeString($GLOBALS['cfg']['Server']['user']) . "'" + . " AND `db_name`='" . $GLOBALS['dbi']->escapeString($dbName) . "'" + . " AND `table_name`='" + . (! empty($tableName) ? $GLOBALS['dbi']->escapeString($tableName) : '') . "'"; + $result = $this->relation->queryAsControlUser($sqlQuery, false); + + $hidden = array(); + if ($result) { + while ($row = $GLOBALS['dbi']->fetchArray($result)) { + $type = $row['item_type']; + if (! isset($hidden[$type])) { + $hidden[$type] = array(); + } + $hidden[$type][] = $row['item_name']; + } + } + $GLOBALS['dbi']->freeResult($result); + + $typeMap = array( + 'group' => __('Groups:'), + 'event' => __('Events:'), + 'function' => __('Functions:'), + 'procedure' => __('Procedures:'), + 'table' => __('Tables:'), + 'view' => __('Views:'), + ); + if (empty($tableName)) { + $first = true; + foreach ($typeMap as $t => $lable) { + if ((empty($itemType) || $itemType == $t) + && isset($hidden[$t]) + ) { + $html .= (! $first ? '
        ' : '') + . '' . $lable . ''; + $html .= ''; + foreach ($hidden[$t] as $hiddenItem) { + $params = array( + 'unhideNavItem' => true, + 'itemType' => $t, + 'itemName' => $hiddenItem, + 'dbName' => $dbName + ); + + $html .= ''; + $html .= ''; + $html .= ''; + } + $html .= '
        ' . htmlspecialchars($hiddenItem) . '' + . Util::getIcon('show', __('Show')) + . '
        '; + $first = false; + } + } + } + + $html .= '
        '; + $html .= '
        '; + return $html; + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Navigation/NavigationHeader.php b/php/apps/phpmyadmin49/libraries/classes/Navigation/NavigationHeader.php new file mode 100644 index 00000000..b0355250 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Navigation/NavigationHeader.php @@ -0,0 +1,259 @@ + true, + ) + ); + $class = ' class="list_container'; + if ($GLOBALS['cfg']['NavigationLinkWithMainPanel']) { + $class .= ' synced'; + } + if ($GLOBALS['cfg']['NavigationTreePointerEnable']) { + $class .= ' highlight'; + } + $class .= '"'; + $buffer = '
        '; + $buffer .= '
        '; + $buffer .= '
        '; + $buffer .= '
        '; + $buffer .= '
        '; + $buffer .= sprintf( + '', + $link_url + ); + $buffer .= $this->_logo(); + $buffer .= $this->_links(); + $buffer .= $this->_serverChoice(); + $buffer .= Util::getImage( + 'ajax_clock_small', + __('Loading…'), + array( + 'style' => 'visibility: hidden; display:none', + 'class' => 'throbber', + ) + ); + $buffer .= '
        '; // pma_navigation_header + $buffer .= '
        '; + + return $buffer; + } + + /** + * Create the code for displaying the phpMyAdmin + * logo based on configuration settings + * + * @return string HTML code for the logo + */ + private function _logo() + { + $logo = 'phpMyAdmin'; + if (isset($GLOBALS['pmaThemeImage'])) { + $imgTag = ''; + if (@file_exists($GLOBALS['pmaThemeImage'] . 'logo_left.png')) { + $logo = sprintf($imgTag, $GLOBALS['pmaThemeImage'], 'logo_left.png'); + } elseif (@file_exists($GLOBALS['pmaThemeImage'] . 'pma_logo2.png')) { + $logo = sprintf($imgTag, $GLOBALS['pmaThemeImage'], 'pma_logo2.png'); + } + } + + // display Logo, depending on $GLOBALS['cfg']['NavigationDisplayLogo'] + if (!$GLOBALS['cfg']['NavigationDisplayLogo']) { + return Template::get('navigation/logo')->render([ + 'display_logo' => false, + 'use_logo_link' => false, + 'logo_link' => null, + 'link_attribs' => null, + 'logo' => $logo, + ]); + } + + if (!$GLOBALS['cfg']['NavigationLogoLink']) { + return Template::get('navigation/logo')->render([ + 'display_logo' => true, + 'use_logo_link' => false, + 'logo_link' => null, + 'link_attribs' => null, + 'logo' => $logo, + ]); + } + + $useLogoLink = true; + $linkAttriks = null; + $logoLink = trim( + htmlspecialchars($GLOBALS['cfg']['NavigationLogoLink']) + ); + // prevent XSS, see PMASA-2013-9 + // if link has protocol, allow only http and https + if (! Sanitize::checkLink($logoLink, true)) { + $logoLink = 'index.php'; + } + switch ($GLOBALS['cfg']['NavigationLogoLinkWindow']) { + case 'new': + $linkAttriks = 'target="_blank" rel="noopener noreferrer"'; + break; + case 'main': + // do not add our parameters for an external link + $host = parse_url( + $GLOBALS['cfg']['NavigationLogoLink'], + PHP_URL_HOST + ); + if (empty($host)) { + $hasStartChar = strpos($logoLink, '?'); + $logoLink .= Url::getCommon( + array(), + is_bool($hasStartChar) ? '?' : Url::getArgSeparator() + ); + } else { + $linkAttriks = 'target="_blank" rel="noopener noreferrer"'; + } + } + + return Template::get('navigation/logo')->render([ + 'display_logo' => true, + 'use_logo_link' => $useLogoLink, + 'logo_link' => $logoLink, + 'link_attribs' => $linkAttriks, + 'logo' => $logo, + ]); + } + + /** + * Creates the code for displaying the links + * at the top of the navigation panel + * + * @return string HTML code for the links + */ + private function _links() + { + // always iconic + $showIcon = true; + $showText = false; + + $retval = ''; + $retval .= ''; + $retval .= ''; + + return $retval; + } + + /** + * Displays the MySQL servers choice form + * + * @return string HTML code for the MySQL servers choice + */ + private function _serverChoice() + { + $retval = ''; + if ($GLOBALS['cfg']['NavigationDisplayServers'] + && count($GLOBALS['cfg']['Servers']) > 1 + ) { + $retval .= ''; + $retval .= '
        '; + $retval .= Select::render(true, true); + $retval .= '
        '; + $retval .= ''; + } + + return $retval; + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Navigation/NavigationTree.php b/php/apps/phpmyadmin49/libraries/classes/Navigation/NavigationTree.php new file mode 100644 index 00000000..0ff9726d --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Navigation/NavigationTree.php @@ -0,0 +1,1560 @@ +_pos = (int) $_POST['pos']; + } elseif (isset($_GET['pos'])) { + $this->_pos = (int) $_GET['pos']; + } + if (!isset($this->_pos)) { + $this->_pos = $this->_getNavigationDbPos(); + } + // Get the active node + if (isset($_REQUEST['aPath'])) { + $this->_aPath[0] = $this->_parsePath($_REQUEST['aPath']); + $this->_pos2_name[0] = $_REQUEST['pos2_name']; + $this->_pos2_value[0] = $_REQUEST['pos2_value']; + if (isset($_REQUEST['pos3_name'])) { + $this->_pos3_name[0] = $_REQUEST['pos3_name']; + $this->_pos3_value[0] = $_REQUEST['pos3_value']; + } + } else { + if (isset($_POST['n0_aPath'])) { + $count = 0; + while (isset($_POST['n' . $count . '_aPath'])) { + $this->_aPath[$count] = $this->_parsePath( + $_POST['n' . $count . '_aPath'] + ); + $index = 'n' . $count . '_pos2_'; + $this->_pos2_name[$count] = $_POST[$index . 'name']; + $this->_pos2_value[$count] = $_POST[$index . 'value']; + $index = 'n' . $count . '_pos3_'; + if (isset($_POST[$index])) { + $this->_pos3_name[$count] = $_POST[$index . 'name']; + $this->_pos3_value[$count] = $_POST[$index . 'value']; + } + $count++; + } + } + } + if (isset($_REQUEST['vPath'])) { + $this->_vPath[0] = $this->_parsePath($_REQUEST['vPath']); + } else { + if (isset($_POST['n0_vPath'])) { + $count = 0; + while (isset($_POST['n' . $count . '_vPath'])) { + $this->_vPath[$count] = $this->_parsePath( + $_POST['n' . $count . '_vPath'] + ); + $count++; + } + } + } + if (isset($_REQUEST['searchClause'])) { + $this->_searchClause = $_REQUEST['searchClause']; + } + if (isset($_REQUEST['searchClause2'])) { + $this->_searchClause2 = $_REQUEST['searchClause2']; + } + // Initialise the tree by creating a root node + $node = NodeFactory::getInstance('NodeDatabaseContainer', 'root'); + $this->_tree = $node; + if ($GLOBALS['cfg']['NavigationTreeEnableGrouping'] + && $GLOBALS['cfg']['ShowDatabasesNavigationAsTree'] + ) { + $this->_tree->separator = $GLOBALS['cfg']['NavigationTreeDbSeparator']; + $this->_tree->separator_depth = 10000; + } + } + + /** + * Returns the database position for the page selector + * + * @return int + */ + private function _getNavigationDbPos() + { + $retval = 0; + + if (strlen($GLOBALS['db']) == 0) { + return $retval; + } + + /* + * @todo describe a scenario where this code is executed + */ + if (!$GLOBALS['cfg']['Server']['DisableIS']) { + $dbSeparator = $GLOBALS['dbi']->escapeString( + $GLOBALS['cfg']['NavigationTreeDbSeparator'] + ); + $query = "SELECT (COUNT(DB_first_level) DIV %d) * %d "; + $query .= "from ( "; + $query .= " SELECT distinct SUBSTRING_INDEX(SCHEMA_NAME, "; + $query .= " '%s', 1) "; + $query .= " DB_first_level "; + $query .= " FROM INFORMATION_SCHEMA.SCHEMATA "; + $query .= " WHERE `SCHEMA_NAME` < '%s' "; + $query .= ") t "; + + $retval = $GLOBALS['dbi']->fetchValue( + sprintf( + $query, + (int)$GLOBALS['cfg']['FirstLevelNavigationItems'], + (int)$GLOBALS['cfg']['FirstLevelNavigationItems'], + $dbSeparator, + $GLOBALS['dbi']->escapeString($GLOBALS['db']) + ) + ); + + return $retval; + } + + $prefixMap = array(); + if ($GLOBALS['dbs_to_test'] === false) { + $handle = $GLOBALS['dbi']->tryQuery("SHOW DATABASES"); + if ($handle !== false) { + while ($arr = $GLOBALS['dbi']->fetchArray($handle)) { + if (strcasecmp($arr[0], $GLOBALS['db']) >= 0) { + break; + } + + $prefix = strstr( + $arr[0], + $GLOBALS['cfg']['NavigationTreeDbSeparator'], + true + ); + if ($prefix === false) { + $prefix = $arr[0]; + } + $prefixMap[$prefix] = 1; + } + } + } else { + $databases = array(); + foreach ($GLOBALS['dbs_to_test'] as $db) { + $query = "SHOW DATABASES LIKE '" . $db . "'"; + $handle = $GLOBALS['dbi']->tryQuery($query); + if ($handle === false) { + continue; + } + while ($arr = $GLOBALS['dbi']->fetchArray($handle)) { + $databases[] = $arr[0]; + } + } + sort($databases); + foreach ($databases as $database) { + if (strcasecmp($database, $GLOBALS['db']) >= 0) { + break; + } + + $prefix = strstr( + $database, + $GLOBALS['cfg']['NavigationTreeDbSeparator'], + true + ); + if ($prefix === false) { + $prefix = $database; + } + $prefixMap[$prefix] = 1; + } + } + + $navItems = (int)$GLOBALS['cfg']['FirstLevelNavigationItems']; + $retval = floor((count($prefixMap) / $navItems)) * $navItems; + + return $retval; + } + + /** + * Converts an encoded path to a node in string format to an array + * + * @param string $string The path to parse + * + * @return array + */ + private function _parsePath($string) + { + $path = explode('.', $string); + foreach ($path as $key => $value) { + $path[$key] = base64_decode($value); + } + + return $path; + } + + /** + * Generates the tree structure so that it can be rendered later + * + * @return Node|false The active node or false in case of failure + */ + private function _buildPath() + { + $retval = $this->_tree; + + // Add all databases unconditionally + $data = $this->_tree->getData( + 'databases', + $this->_pos, + $this->_searchClause + ); + $hiddenCounts = $this->_tree->getNavigationHidingData(); + foreach ($data as $db) { + $node = NodeFactory::getInstance('NodeDatabase', $db); + if (isset($hiddenCounts[$db])) { + $node->setHiddenCount($hiddenCounts[$db]); + } + $this->_tree->addChild($node); + } + + // Whether build other parts of the tree depends + // on whether we have any paths in $this->_aPath + foreach ($this->_aPath as $key => $path) { + $retval = $this->_buildPathPart( + $path, + $this->_pos2_name[$key], + $this->_pos2_value[$key], + isset($this->_pos3_name[$key]) ? $this->_pos3_name[$key] : '', + isset($this->_pos3_value[$key]) ? $this->_pos3_value[$key] : '' + ); + } + + return $retval; + } + + /** + * Builds a branch of the tree + * + * @param array $path A paths pointing to the branch + * of the tree that needs to be built + * @param string $type2 The type of item being paginated on + * the second level of the tree + * @param int $pos2 The position for the pagination of + * the branch at the second level of the tree + * @param string $type3 The type of item being paginated on + * the third level of the tree + * @param int $pos3 The position for the pagination of + * the branch at the third level of the tree + * + * @return Node|false The active node or false in case of failure + */ + private function _buildPathPart(array $path, $type2, $pos2, $type3, $pos3) + { + if (empty($pos2)) { + $pos2 = 0; + } + if (empty($pos3)) { + $pos3 = 0; + } + + $retval = true; + if (count($path) <= 1) { + return $retval; + } + + array_shift($path); // remove 'root' + /* @var $db NodeDatabase */ + $db = $this->_tree->getChild($path[0]); + $retval = $db; + + if ($db === false) { + return false; + } + + $containers = $this->_addDbContainers($db, $type2, $pos2); + + array_shift($path); // remove db + + if ((count($path) <= 0 || !array_key_exists($path[0], $containers)) + && count($containers) != 1 + ) { + return $retval; + } + + if (count($containers) == 1) { + $container = array_shift($containers); + } else { + $container = $db->getChild($path[0], true); + if ($container === false) { + return false; + } + } + $retval = $container; + + if (count($container->children) <= 1) { + $dbData = $db->getData( + $container->real_name, + $pos2, + $this->_searchClause2 + ); + foreach ($dbData as $item) { + switch ($container->real_name) { + case 'events': + $node = NodeFactory::getInstance( + 'NodeEvent', + $item + ); + break; + case 'functions': + $node = NodeFactory::getInstance( + 'NodeFunction', + $item + ); + break; + case 'procedures': + $node = NodeFactory::getInstance( + 'NodeProcedure', + $item + ); + break; + case 'tables': + $node = NodeFactory::getInstance( + 'NodeTable', + $item + ); + break; + case 'views': + $node = NodeFactory::getInstance( + 'NodeView', + $item + ); + break; + default: + break; + } + if (isset($node)) { + if ($type2 == $container->real_name) { + $node->pos2 = $pos2; + } + $container->addChild($node); + } + } + } + if (count($path) > 1 && $path[0] != 'tables') { + $retval = false; + + return $retval; + } + + array_shift($path); // remove container + if (count($path) <= 0) { + return $retval; + } + + /* @var $table NodeTable */ + $table = $container->getChild($path[0], true); + if ($table === false) { + if (!$db->getPresence('tables', $path[0])) { + return false; + } + + $node = NodeFactory::getInstance( + 'NodeTable', + $path[0] + ); + if ($type2 == $container->real_name) { + $node->pos2 = $pos2; + } + $container->addChild($node); + $table = $container->getChild($path[0], true); + } + $retval = $table; + $containers = $this->_addTableContainers( + $table, + $pos2, + $type3, + $pos3 + ); + array_shift($path); // remove table + if (count($path) <= 0 + || !array_key_exists($path[0], $containers) + ) { + return $retval; + } + + $container = $table->getChild($path[0], true); + $retval = $container; + $tableData = $table->getData( + $container->real_name, + $pos3 + ); + foreach ($tableData as $item) { + switch ($container->real_name) { + case 'indexes': + $node = NodeFactory::getInstance( + 'NodeIndex', + $item + ); + break; + case 'columns': + $node = NodeFactory::getInstance( + 'NodeColumn', + $item + ); + break; + case 'triggers': + $node = NodeFactory::getInstance( + 'NodeTrigger', + $item + ); + break; + default: + break; + } + if (isset($node)) { + $node->pos2 = $container->parent->pos2; + if ($type3 == $container->real_name) { + $node->pos3 = $pos3; + } + $container->addChild($node); + } + } + + return $retval; + } + + /** + * Adds containers to a node that is a table + * + * References to existing children are returned + * if this function is called twice on the same node + * + * @param NodeTable $table The table node, new containers will be + * attached to this node + * @param int $pos2 The position for the pagination of + * the branch at the second level of the tree + * @param string $type3 The type of item being paginated on + * the third level of the tree + * @param int $pos3 The position for the pagination of + * the branch at the third level of the tree + * + * @return array An array of new nodes + */ + private function _addTableContainers($table, $pos2, $type3, $pos3) + { + $retval = array(); + if ($table->hasChildren(true) == 0) { + if ($table->getPresence('columns')) { + $retval['columns'] = NodeFactory::getInstance( + 'NodeColumnContainer' + ); + } + if ($table->getPresence('indexes')) { + $retval['indexes'] = NodeFactory::getInstance( + 'NodeIndexContainer' + ); + } + if ($table->getPresence('triggers')) { + $retval['triggers'] = NodeFactory::getInstance( + 'NodeTriggerContainer' + ); + } + // Add all new Nodes to the tree + foreach ($retval as $node) { + $node->pos2 = $pos2; + if ($type3 == $node->real_name) { + $node->pos3 = $pos3; + } + $table->addChild($node); + } + } else { + foreach ($table->children as $node) { + if ($type3 == $node->real_name) { + $node->pos3 = $pos3; + } + $retval[$node->real_name] = $node; + } + } + + return $retval; + } + + /** + * Adds containers to a node that is a database + * + * References to existing children are returned + * if this function is called twice on the same node + * + * @param NodeDatabase $db The database node, new containers will be + * attached to this node + * @param string $type The type of item being paginated on + * the second level of the tree + * @param int $pos2 The position for the pagination of + * the branch at the second level of the tree + * + * @return array An array of new nodes + */ + private function _addDbContainers($db, $type, $pos2) + { + // Get items to hide + $hidden = $db->getHiddenItems('group'); + if (!$GLOBALS['cfg']['NavigationTreeShowTables'] + && !in_array('tables', $hidden) + ) { + $hidden[] = 'tables'; + } + if (!$GLOBALS['cfg']['NavigationTreeShowViews'] + && !in_array('views', $hidden) + ) { + $hidden[] = 'views'; + } + if (!$GLOBALS['cfg']['NavigationTreeShowFunctions'] + && !in_array('functions', $hidden) + ) { + $hidden[] = 'functions'; + } + if (!$GLOBALS['cfg']['NavigationTreeShowProcedures'] + && !in_array('procedures', $hidden) + ) { + $hidden[] = 'procedures'; + } + if (!$GLOBALS['cfg']['NavigationTreeShowEvents'] + && !in_array('events', $hidden) + ) { + $hidden[] = 'events'; + } + + $retval = array(); + if ($db->hasChildren(true) == 0) { + if (!in_array('tables', $hidden) && $db->getPresence('tables')) { + $retval['tables'] = NodeFactory::getInstance( + 'NodeTableContainer' + ); + } + if (!in_array('views', $hidden) && $db->getPresence('views')) { + $retval['views'] = NodeFactory::getInstance( + 'NodeViewContainer' + ); + } + if (!in_array('functions', $hidden) && $db->getPresence('functions')) { + $retval['functions'] = NodeFactory::getInstance( + 'NodeFunctionContainer' + ); + } + if (!in_array('procedures', $hidden) && $db->getPresence('procedures')) { + $retval['procedures'] = NodeFactory::getInstance( + 'NodeProcedureContainer' + ); + } + if (!in_array('events', $hidden) && $db->getPresence('events')) { + $retval['events'] = NodeFactory::getInstance( + 'NodeEventContainer' + ); + } + // Add all new Nodes to the tree + foreach ($retval as $node) { + if ($type == $node->real_name) { + $node->pos2 = $pos2; + } + $db->addChild($node); + } + } else { + foreach ($db->children as $node) { + if ($type == $node->real_name) { + $node->pos2 = $pos2; + } + $retval[$node->real_name] = $node; + } + } + + return $retval; + } + + /** + * Recursively groups tree nodes given a separator + * + * @param mixed $node The node to group or null + * to group the whole tree. If + * passed as an argument, $node + * must be of type CONTAINER + * + * @return void + */ + public function groupTree($node = null) + { + if (!isset($node)) { + $node = $this->_tree; + } + $this->groupNode($node); + foreach ($node->children as $child) { + $this->groupTree($child); + } + } + + /** + * Recursively groups tree nodes given a separator + * + * @param Node $node The node to group + * + * @return void + */ + public function groupNode($node) + { + if ($node->type != Node::CONTAINER + || !$GLOBALS['cfg']['NavigationTreeEnableExpansion'] + ) { + return; + } + + $separators = array(); + if (is_array($node->separator)) { + $separators = $node->separator; + } else { + if (strlen($node->separator)) { + $separators[] = $node->separator; + } + } + $prefixes = array(); + if ($node->separator_depth > 0) { + foreach ($node->children as $child) { + $prefix_pos = false; + foreach ($separators as $separator) { + $sep_pos = mb_strpos($child->name, $separator); + if ($sep_pos != false + && $sep_pos != mb_strlen($child->name) + && $sep_pos != 0 + && ($prefix_pos == false || $sep_pos < $prefix_pos) + ) { + $prefix_pos = $sep_pos; + } + } + if ($prefix_pos !== false) { + $prefix = mb_substr($child->name, 0, $prefix_pos); + if (!isset($prefixes[$prefix])) { + $prefixes[$prefix] = 1; + } else { + $prefixes[$prefix]++; + } + } + //Bug #4375: Check if prefix is the name of a DB, to create a group. + foreach ($node->children as $otherChild) { + if (array_key_exists($otherChild->name, $prefixes)) { + $prefixes[$otherChild->name]++; + } + } + } + //Check if prefix is the name of a DB, to create a group. + foreach ($node->children as $child) { + if (array_key_exists($child->name, $prefixes)) { + $prefixes[$child->name]++; + } + } + } + // It is not a group if it has only one item + foreach ($prefixes as $key => $value) { + if ($value == 1) { + unset($prefixes[$key]); + } + } + // rfe #1634 Don't group if there's only one group and no other items + if (count($prefixes) == 1) { + $keys = array_keys($prefixes); + $key = $keys[0]; + if ($prefixes[$key] == count($node->children) - 1) { + unset($prefixes[$key]); + } + } + if (count($prefixes)) { + /** @var Node[] $groups */ + $groups = array(); + foreach ($prefixes as $key => $value) { + + // warn about large groups + if ($value > 500 && !$this->_largeGroupWarning) { + trigger_error( + __( + 'There are large item groups in navigation panel which ' + . 'may affect the performance. Consider disabling item ' + . 'grouping in the navigation panel.' + ), + E_USER_WARNING + ); + $this->_largeGroupWarning = true; + } + + $groups[$key] = new Node( + htmlspecialchars($key), + Node::CONTAINER, + true + ); + $groups[$key]->separator = $node->separator; + $groups[$key]->separator_depth = $node->separator_depth - 1; + $groups[$key]->icon = Util::getImage( + 'b_group' + ); + $groups[$key]->pos2 = $node->pos2; + $groups[$key]->pos3 = $node->pos3; + if ($node instanceof NodeTableContainer + || $node instanceof NodeViewContainer + ) { + $tblGroup = '&tbl_group=' . urlencode($key); + $groups[$key]->links = array( + 'text' => $node->links['text'] . $tblGroup, + 'icon' => $node->links['icon'] . $tblGroup, + ); + } + $node->addChild($groups[$key]); + foreach ($separators as $separator) { + $separatorLength = strlen($separator); + // FIXME: this could be more efficient + foreach ($node->children as $child) { + $keySeparatorLength = mb_strlen($key) + $separatorLength; + $name_substring = mb_substr( + $child->name, + 0, + $keySeparatorLength + ); + if (($name_substring != $key . $separator + && $child->name != $key) + || $child->type != Node::OBJECT + ) { + continue; + } + $class = get_class($child); + $className = substr($class, strrpos($class, '\\') + 1); + unset($class); + $new_child = NodeFactory::getInstance( + $className, + mb_substr( + $child->name, + $keySeparatorLength + ) + ); + + if ($new_child instanceof NodeDatabase + && $child->getHiddenCount() > 0 + ) { + $new_child->setHiddenCount($child->getHiddenCount()); + } + + $new_child->real_name = $child->real_name; + $new_child->icon = $child->icon; + $new_child->links = $child->links; + $new_child->pos2 = $child->pos2; + $new_child->pos3 = $child->pos3; + $groups[$key]->addChild($new_child); + foreach ($child->children as $elm) { + $new_child->addChild($elm); + } + $node->removeChild($child->name); + } + } + } + foreach ($prefixes as $key => $value) { + $this->groupNode($groups[$key]); + $groups[$key]->classes = "navGroup"; + } + } + } + + /** + * Renders a state of the tree, used in light mode when + * either JavaScript and/or Ajax are disabled + * + * @return string HTML code for the navigation tree + */ + public function renderState() + { + $this->_buildPath(); + $retval = $this->_quickWarp(); + $retval .= '
        '; + $retval .= '
          '; + $retval .= $this->_fastFilterHtml($this->_tree); + if ($GLOBALS['cfg']['NavigationTreeEnableExpansion'] + ) { + $retval .= $this->_controls(); + } + $retval .= '
        '; + $retval .= $this->_getPageSelector($this->_tree); + $this->groupTree(); + $retval .= "
          "; + $children = $this->_tree->children; + usort( + $children, + array('PhpMyAdmin\\Navigation\\NavigationTree', 'sortNode') + ); + $this->_setVisibility(); + for ($i = 0, $nbChildren = count($children); $i < $nbChildren; $i++) { + if ($i == 0) { + $retval .= $this->_renderNode($children[0], true, 'first'); + } else { + if ($i + 1 != $nbChildren) { + $retval .= $this->_renderNode($children[$i], true); + } else { + $retval .= $this->_renderNode($children[$i], true, 'last'); + } + } + } + $retval .= "
        "; + + return $retval; + } + + /** + * Renders a part of the tree, used for Ajax + * requests in light mode + * + * @return string HTML code for the navigation tree + */ + public function renderPath() + { + $node = $this->_buildPath(); + if ($node === false) { + $retval = false; + } else { + $this->groupTree(); + $retval = "
        "; + if (!empty($this->_searchClause) || !empty($this->_searchClause2)) { + $retval .= "
          "; + } else { + $retval .= "
            "; + } + $listContent = $this->_fastFilterHtml($node); + $listContent .= $this->_getPageSelector($node); + $children = $node->children; + usort( + $children, + array('PhpMyAdmin\\Navigation\\NavigationTree', 'sortNode') + ); + for ($i = 0, $nbChildren = count($children); $i < $nbChildren; $i++) { + if ($i + 1 != $nbChildren) { + $listContent .= $this->_renderNode($children[$i], true); + } else { + $listContent .= $this->_renderNode($children[$i], true, 'last'); + } + } + $retval .= $listContent; + $retval .= "
          "; + if (!$GLOBALS['cfg']['ShowDatabasesNavigationAsTree']) { + $retval .= ""; + $parents = $node->parents(true); + $retval .= urlencode($parents[0]->real_name); + $retval .= ""; + if (empty($listContent)) { + $retval .= "
          "; + $retval .= __('No tables found in database.'); + $retval .= "
          "; + } + } + $retval .= "
        "; + } + + if (!empty($this->_searchClause) || !empty($this->_searchClause2)) { + $results = 0; + if (!empty($this->_searchClause2)) { + if (is_object($node->realParent())) { + $results = $node->realParent() + ->getPresence( + $node->real_name, + $this->_searchClause2 + ); + } + } else { + $results = $this->_tree->getPresence( + 'databases', + $this->_searchClause + ); + } + $results = sprintf( + _ngettext( + '%s result found', + '%s results found', + $results + ), + $results + ); + Response::getInstance() + ->addJSON( + 'results', + $results + ); + } + + return $retval; + } + + /** + * Renders the parameters that are required on the client + * side to know which page(s) we will be requesting data from + * + * @param Node $node The node to create the pagination parameters for + * + * @return string + */ + private function _getPaginationParamsHtml($node) + { + $retval = ''; + $paths = $node->getPaths(); + if (isset($paths['aPath_clean'][2])) { + $retval .= ""; + $retval .= $paths['aPath_clean'][2]; + $retval .= ""; + $retval .= ""; + $retval .= htmlspecialchars($node->pos2); + $retval .= ""; + } + if (isset($paths['aPath_clean'][4])) { + $retval .= ""; + $retval .= $paths['aPath_clean'][4]; + $retval .= ""; + $retval .= ""; + $retval .= htmlspecialchars($node->pos3); + $retval .= ""; + } + + return $retval; + } + + /** + * Finds whether given tree matches this tree. + * + * @param array $tree Tree to check + * @param array $paths Paths to check + * + * @return boolean + */ + private function _findTreeMatch(array $tree, array $paths) + { + $match = false; + foreach ($tree as $path) { + $match = true; + foreach ($paths as $key => $part) { + if (!isset($path[$key]) || $part != $path[$key]) { + $match = false; + break; + } + } + if ($match) { + break; + } + } + + return $match; + } + + /** + * Renders a single node or a branch of the tree + * + * @param Node $node The node to render + * @param bool $recursive Bool: Whether to render a single node or a branch + * @param string $class An additional class for the list item + * + * @return string HTML code for the tree node or branch + */ + private function _renderNode($node, $recursive, $class = '') + { + $retval = ''; + $paths = $node->getPaths(); + if ($node->hasSiblings() + || $node->realParent() === false + ) { + $response = Response::getInstance(); + if ($node->type == Node::CONTAINER + && count($node->children) == 0 + && ! $response->isAjax() + ) { + return ''; + } + $retval .= '
      • '; + $sterile = array( + 'events', + 'triggers', + 'functions', + 'procedures', + 'views', + 'columns', + 'indexes', + ); + $parentName = ''; + $parents = $node->parents(false, true); + if (count($parents)) { + $parentName = $parents[0]->real_name; + } + // if node name itself is in sterile, then allow + if ($node->is_group + || (!in_array($parentName, $sterile) && !$node->isNew) + || (in_array($node->real_name, $sterile)) + ) { + $retval .= "
        "; + $iClass = ''; + if ($class == 'first') { + $iClass = " class='first'"; + } + $retval .= ""; + if (strpos($class, 'last') === false) { + $retval .= ""; + } + + $match = $this->_findTreeMatch( + $this->_vPath, + $paths['vPath_clean'] + ); + + $retval .= '_pos; + $retval .= ""; + $retval .= $this->_getPaginationParamsHtml($node); + if ($GLOBALS['cfg']['ShowDatabasesNavigationAsTree'] + || $parentName != 'root' + ) { + $retval .= $node->getIcon($match); + } + + $retval .= ""; + $retval .= "
        "; + } else { + $retval .= "
        "; + $iClass = ''; + if ($class == 'first') { + $iClass = " class='first'"; + } + $retval .= ""; + $retval .= $this->_getPaginationParamsHtml($node); + $retval .= "
        "; + } + + $linkClass = ''; + $haveAjax = array( + 'functions', + 'procedures', + 'events', + 'triggers', + 'indexes', + ); + $parent = $node->parents(false, true); + $isNewView = $parent[0]->real_name == 'views' && $node->isNew === true; + if ($parent[0]->type == Node::CONTAINER + && (in_array($parent[0]->real_name, $haveAjax) || $isNewView) + ) { + $linkClass = ' ajax'; + } + + if ($node->type == Node::CONTAINER) { + $retval .= ""; + } + + $divClass = ''; + + if (isset($node->links['icon']) && !empty($node->links['icon'])) { + $iconLinks = $node->links['icon']; + $icons = $node->icon; + if (!is_array($iconLinks)) { + $iconLinks = array($iconLinks); + $icons = array($icons); + } + + if (count($icons) > 1) { + $divClass = 'double'; + } + } + + $retval .= "
        "; + + if (isset($node->links['icon']) && !empty($node->links['icon'])) { + $args = array(); + foreach ($node->parents(true) as $parent) { + $args[] = urlencode($parent->real_name); + } + + foreach ($icons as $key => $icon) { + $link = vsprintf($iconLinks[$key], $args); + if ($linkClass != '') { + $retval .= ""; + $retval .= "{$icon}"; + } else { + $retval .= "{$icon}"; + } + } + } else { + $retval .= "{$node->icon}"; + } + $retval .= "
        "; + + if (isset($node->links['text'])) { + $args = array(); + foreach ($node->parents(true) as $parent) {; + $args[] = urlencode($parent->real_name); + } + $link = vsprintf($node->links['text'], $args); + $title = isset($node->links['title']) ? $node->links['title'] : ''; + if ($node->type == Node::CONTAINER) { + $retval .= " "; + $retval .= htmlspecialchars($node->name); + $retval .= ""; + } else { + $retval .= "real_name); + $retval .= ""; + } + } else { + $retval .= " {$node->name}"; + } + $retval .= $node->getHtmlForControlButtons(); + if ($node->type == Node::CONTAINER) { + $retval .= "
        "; + } + $retval .= '
        '; + $wrap = true; + } else { + $node->visible = true; + $wrap = false; + $retval .= $this->_getPaginationParamsHtml($node); + } + + if ($recursive) { + $hide = ''; + if (!$node->visible) { + $hide = " style='display: none;'"; + } + $children = $node->children; + usort( + $children, + array('PhpMyAdmin\\Navigation\\NavigationTree', 'sortNode') + ); + $buffer = ''; + $extra_class = ''; + for ($i = 0, $nbChildren = count($children); $i < $nbChildren; $i++) { + if ($i + 1 == $nbChildren) { + $extra_class = ' last'; + } + $buffer .= $this->_renderNode( + $children[$i], + true, + $children[$i]->classes . $extra_class + ); + } + if (!empty($buffer)) { + if ($wrap) { + $retval .= "
          "; + } + $retval .= $this->_fastFilterHtml($node); + $retval .= $this->_getPageSelector($node); + $retval .= $buffer; + if ($wrap) { + $retval .= "
      "; + } + } + } + if ($node->hasSiblings()) { + $retval .= ""; + } + + return $retval; + } + + /** + * Renders a database select box like the pre-4.0 navigation panel + * + * @return string HTML code + */ + public function renderDbSelect() + { + $this->_buildPath(); + $retval = $this->_quickWarp(); + $this->_tree->is_group = false; + $retval .= '
      '; + // Provide for pagination in database select + $retval .= Util::getListNavigator( + $this->_tree->getPresence('databases', ''), + $this->_pos, + array('server' => $GLOBALS['server']), + 'navigation.php', + 'frame_navigation', + $GLOBALS['cfg']['FirstLevelNavigationItems'], + 'pos', + array('dbselector') + ); + $children = $this->_tree->children; + $url_params = array( + 'server' => $GLOBALS['server'], + ); + $retval .= '
      '; + $retval .= '
      '; + $retval .= Url::getHiddenFields($url_params); + $retval .= '
      '; + $retval .= '
      '; + $retval .= '
        '; + $children = $this->_tree->children; + usort( + $children, + array('PhpMyAdmin\\Navigation\\NavigationTree', 'sortNode') + ); + $this->_setVisibility(); + for ($i = 0, $nbChildren = count($children); $i < $nbChildren; $i++) { + if ($i == 0) { + $retval .= $this->_renderNode($children[0], true, 'first'); + } else { + if ($i + 1 != $nbChildren) { + $retval .= $this->_renderNode($children[$i], true); + } else { + $retval .= $this->_renderNode($children[$i], true, 'last'); + } + } + } + $retval .= '
      '; + + return $retval; + } + + /** + * Makes some nodes visible based on the which node is active + * + * @return void + */ + private function _setVisibility() + { + foreach ($this->_vPath as $path) { + $node = $this->_tree; + foreach ($path as $value) { + $child = $node->getChild($value); + if ($child !== false) { + $child->visible = true; + $node = $child; + } + } + } + } + + /** + * Generates the HTML code for displaying the fast filter for tables + * + * @param Node $node The node for which to generate the fast filter html + * + * @return string LI element used for the fast filter + */ + private function _fastFilterHtml($node) + { + $retval = ''; + $filter_db_min + = (int)$GLOBALS['cfg']['NavigationTreeDisplayDbFilterMinimum']; + $filter_item_min + = (int)$GLOBALS['cfg']['NavigationTreeDisplayItemFilterMinimum']; + if ($node === $this->_tree + && $this->_tree->getPresence() >= $filter_db_min + ) { + $url_params = array( + 'pos' => 0, + ); + $retval .= '
    • '; + $retval .= '
      '; + $retval .= Url::getHiddenInputs($url_params); + $retval .= 'X'; + $retval .= "
      "; + $retval .= "
    • "; + + return $retval; + } + + if (($node->type == Node::CONTAINER + && ($node->real_name == 'tables' + || $node->real_name == 'views' + || $node->real_name == 'functions' + || $node->real_name == 'procedures' + || $node->real_name == 'events')) + && method_exists($node->realParent(), 'getPresence') + && $node->realParent()->getPresence($node->real_name) >= $filter_item_min + ) { + $paths = $node->getPaths(); + $url_params = array( + 'pos' => $this->_pos, + 'aPath' => $paths['aPath'], + 'vPath' => $paths['vPath'], + 'pos2_name' => $node->real_name, + 'pos2_value' => 0, + ); + $retval .= "
    • "; + $retval .= "
      "; + $retval .= Url::getHiddenFields($url_params); + $retval .= ""; + $retval .= "X"; + $retval .= "
      "; + $retval .= "
    • "; + } + + return $retval; + } + + /** + * Creates the code for displaying the controls + * at the top of the navigation tree + * + * @return string HTML code for the controls + */ + private function _controls() + { + // always iconic + $showIcon = true; + $showText = false; + + $retval = ''; + $retval .= ''; + $retval .= ''; + + return $retval; + } + + /** + * Generates the HTML code for displaying the list pagination + * + * @param Node $node The node for whose children the page + * selector will be created + * + * @return string + */ + private function _getPageSelector($node) + { + $retval = ''; + if ($node === $this->_tree) { + $retval .= Util::getListNavigator( + $this->_tree->getPresence('databases', $this->_searchClause), + $this->_pos, + array('server' => $GLOBALS['server']), + 'navigation.php', + 'frame_navigation', + $GLOBALS['cfg']['FirstLevelNavigationItems'], + 'pos', + array('dbselector') + ); + } else { + if ($node->type == Node::CONTAINER && !$node->is_group) { + $paths = $node->getPaths(); + + $level = isset($paths['aPath_clean'][4]) ? 3 : 2; + $_url_params = array( + 'aPath' => $paths['aPath'], + 'vPath' => $paths['vPath'], + 'pos' => $this->_pos, + 'server' => $GLOBALS['server'], + 'pos2_name' => $paths['aPath_clean'][2], + ); + if ($level == 3) { + $pos = $node->pos3; + $_url_params['pos2_value'] = $node->pos2; + $_url_params['pos3_name'] = $paths['aPath_clean'][4]; + } else { + $pos = $node->pos2; + } + $num = $node->realParent() + ->getPresence( + $node->real_name, + $this->_searchClause2 + ); + $retval .= Util::getListNavigator( + $num, + $pos, + $_url_params, + 'navigation.php', + 'frame_navigation', + $GLOBALS['cfg']['MaxNavigationItems'], + 'pos' . $level . '_value' + ); + } + } + + return $retval; + } + + /** + * Called by usort() for sorting the nodes in a container + * + * @param Node $a The first element used in the comparison + * @param Node $b The second element used in the comparison + * + * @return int See strnatcmp() and strcmp() + */ + static public function sortNode($a, $b) + { + if ($a->isNew) { + return -1; + } + + if ($b->isNew) { + return 1; + } + + if ($GLOBALS['cfg']['NaturalOrder']) { + return strnatcasecmp($a->name, $b->name); + } + + return strcasecmp($a->name, $b->name); + } + + /** + * Display quick warp links, contain Recents and Favorites + * + * @return string HTML code + */ + private function _quickWarp() + { + $retval = '
      '; + if ($GLOBALS['cfg']['NumRecentTables'] > 0) { + $retval .= RecentFavoriteTable::getInstance('recent') + ->getHtml(); + } + if ($GLOBALS['cfg']['NumFavoriteTables'] > 0) { + $retval .= RecentFavoriteTable::getInstance('favorite') + ->getHtml(); + } + $retval .= '
      '; + $retval .= '
      '; + + return $retval; + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Navigation/NodeFactory.php b/php/apps/phpmyadmin49/libraries/classes/Navigation/NodeFactory.php new file mode 100644 index 00000000..5c0f4dc9 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Navigation/NodeFactory.php @@ -0,0 +1,91 @@ +name = $name; + $this->real_name = $name; + } + if ($type === Node::CONTAINER) { + $this->type = Node::CONTAINER; + } + $this->is_group = (bool)$is_group; + $this->relation = new Relation(); + } + + /** + * Adds a child node to this node + * + * @param Node $child A child node + * + * @return void + */ + public function addChild($child) + { + $this->children[] = $child; + $child->parent = $this; + } + + /** + * Returns a child node given it's name + * + * @param string $name The name of requested child + * @param bool $real_name Whether to use the "real_name" + * instead of "name" in comparisons + * + * @return false|Node The requested child node or false, + * if the requested node cannot be found + */ + public function getChild($name, $real_name = false) + { + if ($real_name) { + foreach ($this->children as $child) { + if ($child->real_name == $name) { + return $child; + } + } + } else { + foreach ($this->children as $child) { + if ($child->name == $name) { + return $child; + } + } + } + + return false; + } + + /** + * Removes a child node from this node + * + * @param string $name The name of child to be removed + * + * @return void + */ + public function removeChild($name) + { + foreach ($this->children as $key => $child) { + if ($child->name == $name) { + unset($this->children[$key]); + break; + } + } + } + + /** + * Retrieves the parents for a node + * + * @param bool $self Whether to include the Node itself in the results + * @param bool $containers Whether to include nodes of type CONTAINER + * @param bool $groups Whether to include nodes which have $group == true + * + * @return array An array of parent Nodes + */ + public function parents($self = false, $containers = false, $groups = false) + { + $parents = array(); + if ($self + && ($this->type != Node::CONTAINER || $containers) + && (!$this->is_group || $groups) + ) { + $parents[] = $this; + } + $parent = $this->parent; + while (isset($parent)) { + if (($parent->type != Node::CONTAINER || $containers) + && (!$parent->is_group || $groups) + ) { + $parents[] = $parent; + } + $parent = $parent->parent; + } + + return $parents; + } + + /** + * Returns the actual parent of a node. If used twice on an index or columns + * node, it will return the table and database nodes. The names of the returned + * nodes can be used in SQL queries, etc... + * + * @return Node|false + */ + public function realParent() + { + $retval = $this->parents(); + if (count($retval) <= 0) { + return false; + } + + return $retval[0]; + } + + /** + * This function checks if the node has children nodes associated with it + * + * @param bool $count_empty_containers Whether to count empty child + * containers as valid children + * + * @return bool Whether the node has child nodes + */ + public function hasChildren($count_empty_containers = true) + { + $retval = false; + if ($count_empty_containers) { + if (count($this->children)) { + $retval = true; + } + } else { + foreach ($this->children as $child) { + if ($child->type == Node::OBJECT || $child->hasChildren(false)) { + $retval = true; + break; + } + } + } + + return $retval; + } + + /** + * Returns true if the node has some siblings (other nodes on the same tree + * level, in the same branch), false otherwise. + * The only exception is for nodes on + * the third level of the tree (columns and indexes), for which the function + * always returns true. This is because we want to render the containers + * for these nodes + * + * @return bool + */ + public function hasSiblings() + { + $retval = false; + $paths = $this->getPaths(); + if (count($paths['aPath_clean']) > 3) { + $retval = true; + + return $retval; + } + + foreach ($this->parent->children as $child) { + if ($child !== $this + && ($child->type == Node::OBJECT || $child->hasChildren(false)) + ) { + $retval = true; + break; + } + } + + return $retval; + } + + /** + * Returns the number of child nodes that a node has associated with it + * + * @return int The number of children nodes + */ + public function numChildren() + { + $retval = 0; + foreach ($this->children as $child) { + if ($child->type == Node::OBJECT) { + $retval++; + } else { + $retval += $child->numChildren(); + } + } + + return $retval; + } + + /** + * Returns the actual path and the virtual paths for a node + * both as clean arrays and base64 encoded strings + * + * @return array + */ + public function getPaths() + { + $aPath = array(); + $aPath_clean = array(); + foreach ($this->parents(true, true, false) as $parent) { + $aPath[] = base64_encode($parent->real_name); + $aPath_clean[] = $parent->real_name; + } + $aPath = implode('.', array_reverse($aPath)); + $aPath_clean = array_reverse($aPath_clean); + + $vPath = array(); + $vPath_clean = array(); + foreach ($this->parents(true, true, true) as $parent) { + $vPath[] = base64_encode($parent->name); + $vPath_clean[] = $parent->name; + } + $vPath = implode('.', array_reverse($vPath)); + $vPath_clean = array_reverse($vPath_clean); + + return array( + 'aPath' => $aPath, + 'aPath_clean' => $aPath_clean, + 'vPath' => $vPath, + 'vPath_clean' => $vPath_clean, + ); + } + + /** + * Returns the names of children of type $type present inside this container + * This method is overridden by the PhpMyAdmin\Navigation\Nodes\NodeDatabase and PhpMyAdmin\Navigation\Nodes\NodeTable classes + * + * @param string $type The type of item we are looking for + * ('tables', 'views', etc) + * @param int $pos The offset of the list within the results + * @param string $searchClause A string used to filter the results of the query + * + * @return array + */ + public function getData($type, $pos, $searchClause = '') + { + $maxItems = $GLOBALS['cfg']['FirstLevelNavigationItems']; + if (!$GLOBALS['cfg']['NavigationTreeEnableGrouping'] + || !$GLOBALS['cfg']['ShowDatabasesNavigationAsTree'] + ) { + if (isset($GLOBALS['cfg']['Server']['DisableIS']) + && !$GLOBALS['cfg']['Server']['DisableIS'] + ) { + $query = "SELECT `SCHEMA_NAME` "; + $query .= "FROM `INFORMATION_SCHEMA`.`SCHEMATA` "; + $query .= $this->_getWhereClause('SCHEMA_NAME', $searchClause); + $query .= "ORDER BY `SCHEMA_NAME` "; + $query .= "LIMIT $pos, $maxItems"; + $retval = $GLOBALS['dbi']->fetchResult($query); + + return $retval; + } + + if ($GLOBALS['dbs_to_test'] === false) { + $retval = array(); + $query = "SHOW DATABASES "; + $query .= $this->_getWhereClause('Database', $searchClause); + $handle = $GLOBALS['dbi']->tryQuery($query); + if ($handle === false) { + return $retval; + } + + $count = 0; + if (!$GLOBALS['dbi']->dataSeek($handle, $pos)) { + return $retval; + } + + while ($arr = $GLOBALS['dbi']->fetchArray($handle)) { + if ($count < $maxItems) { + $retval[] = $arr[0]; + $count++; + } else { + break; + } + } + + return $retval; + } + + $retval = array(); + $count = 0; + foreach ($this->_getDatabasesToSearch($searchClause) as $db) { + $query = "SHOW DATABASES LIKE '" . $db . "'"; + $handle = $GLOBALS['dbi']->tryQuery($query); + if ($handle === false) { + continue; + } + + while ($arr = $GLOBALS['dbi']->fetchArray($handle)) { + if ($this->_isHideDb($arr[0])) { + continue; + } + if (in_array($arr[0], $retval)) { + continue; + } + + if ($pos <= 0 && $count < $maxItems) { + $retval[] = $arr[0]; + $count++; + } + $pos--; + } + } + sort($retval); + + return $retval; + } + + $dbSeparator = $GLOBALS['cfg']['NavigationTreeDbSeparator']; + if (isset($GLOBALS['cfg']['Server']['DisableIS']) + && !$GLOBALS['cfg']['Server']['DisableIS'] + ) { + $query = "SELECT `SCHEMA_NAME` "; + $query .= "FROM `INFORMATION_SCHEMA`.`SCHEMATA`, "; + $query .= "("; + $query .= "SELECT DB_first_level "; + $query .= "FROM ( "; + $query .= "SELECT DISTINCT SUBSTRING_INDEX(SCHEMA_NAME, "; + $query .= "'" . $GLOBALS['dbi']->escapeString($dbSeparator) . "', 1) "; + $query .= "DB_first_level "; + $query .= "FROM INFORMATION_SCHEMA.SCHEMATA "; + $query .= $this->_getWhereClause('SCHEMA_NAME', $searchClause); + $query .= ") t "; + $query .= "ORDER BY DB_first_level ASC "; + $query .= "LIMIT $pos, $maxItems"; + $query .= ") t2 "; + $query .= $this->_getWhereClause('SCHEMA_NAME', $searchClause); + $query .= "AND 1 = LOCATE(CONCAT(DB_first_level, "; + $query .= "'" . $GLOBALS['dbi']->escapeString($dbSeparator) . "'), "; + $query .= "CONCAT(SCHEMA_NAME, "; + $query .= "'" . $GLOBALS['dbi']->escapeString($dbSeparator) . "')) "; + $query .= "ORDER BY SCHEMA_NAME ASC"; + $retval = $GLOBALS['dbi']->fetchResult($query); + + return $retval; + } + + if ($GLOBALS['dbs_to_test'] === false) { + $query = "SHOW DATABASES "; + $query .= $this->_getWhereClause('Database', $searchClause); + $handle = $GLOBALS['dbi']->tryQuery($query); + $prefixes = array(); + if ($handle !== false) { + $prefixMap = array(); + $total = $pos + $maxItems; + while ($arr = $GLOBALS['dbi']->fetchArray($handle)) { + $prefix = strstr($arr[0], $dbSeparator, true); + if ($prefix === false) { + $prefix = $arr[0]; + } + $prefixMap[$prefix] = 1; + if (sizeof($prefixMap) == $total) { + break; + } + } + $prefixes = array_slice(array_keys($prefixMap), $pos); + } + + $query = "SHOW DATABASES "; + $query .= $this->_getWhereClause('Database', $searchClause); + $query .= "AND ("; + $subClauses = array(); + foreach ($prefixes as $prefix) { + $subClauses[] = " LOCATE('" + . $GLOBALS['dbi']->escapeString($prefix) . $dbSeparator + . "', " + . "CONCAT(`Database`, '" . $dbSeparator . "')) = 1 "; + } + $query .= implode("OR", $subClauses) . ")"; + $retval = $GLOBALS['dbi']->fetchResult($query); + + return $retval; + } + + $retval = array(); + $prefixMap = array(); + $total = $pos + $maxItems; + foreach ($this->_getDatabasesToSearch($searchClause) as $db) { + $query = "SHOW DATABASES LIKE '" . $db . "'"; + $handle = $GLOBALS['dbi']->tryQuery($query); + if ($handle === false) { + continue; + } + + while ($arr = $GLOBALS['dbi']->fetchArray($handle)) { + if ($this->_isHideDb($arr[0])) { + continue; + } + $prefix = strstr($arr[0], $dbSeparator, true); + if ($prefix === false) { + $prefix = $arr[0]; + } + $prefixMap[$prefix] = 1; + if (sizeof($prefixMap) == $total) { + break 2; + } + } + } + $prefixes = array_slice(array_keys($prefixMap), $pos); + + foreach ($this->_getDatabasesToSearch($searchClause) as $db) { + $query = "SHOW DATABASES LIKE '" . $db . "'"; + $handle = $GLOBALS['dbi']->tryQuery($query); + if ($handle === false) { + continue; + } + + while ($arr = $GLOBALS['dbi']->fetchArray($handle)) { + if ($this->_isHideDb($arr[0])) { + continue; + } + if (in_array($arr[0], $retval)) { + continue; + } + + foreach ($prefixes as $prefix) { + $starts_with = strpos( + $arr[0] . $dbSeparator, + $prefix . $dbSeparator + ) === 0; + if ($starts_with) { + $retval[] = $arr[0]; + break; + } + } + } + } + sort($retval); + + return $retval; + } + + /** + * Returns the number of children of type $type present inside this container + * This method is overridden by the PhpMyAdmin\Navigation\Nodes\NodeDatabase and PhpMyAdmin\Navigation\Nodes\NodeTable classes + * + * @param string $type The type of item we are looking for + * ('tables', 'views', etc) + * @param string $searchClause A string used to filter the results of the query + * + * @return int + */ + public function getPresence($type = '', $searchClause = '') + { + if (!$GLOBALS['cfg']['NavigationTreeEnableGrouping'] + || !$GLOBALS['cfg']['ShowDatabasesNavigationAsTree'] + ) { + if (isset($GLOBALS['cfg']['Server']['DisableIS']) + && !$GLOBALS['cfg']['Server']['DisableIS'] + ) { + $query = "SELECT COUNT(*) "; + $query .= "FROM INFORMATION_SCHEMA.SCHEMATA "; + $query .= $this->_getWhereClause('SCHEMA_NAME', $searchClause); + $retval = (int)$GLOBALS['dbi']->fetchValue($query); + + return $retval; + } + + if ($GLOBALS['dbs_to_test'] === false) { + $query = "SHOW DATABASES "; + $query .= $this->_getWhereClause('Database', $searchClause); + $retval = $GLOBALS['dbi']->numRows( + $GLOBALS['dbi']->tryQuery($query) + ); + + return $retval; + } + + $retval = 0; + foreach ($this->_getDatabasesToSearch($searchClause) as $db) { + $query = "SHOW DATABASES LIKE '" . $db . "'"; + $retval += $GLOBALS['dbi']->numRows( + $GLOBALS['dbi']->tryQuery($query) + ); + } + + return $retval; + } + + $dbSeparator = $GLOBALS['cfg']['NavigationTreeDbSeparator']; + if (!$GLOBALS['cfg']['Server']['DisableIS']) { + $query = "SELECT COUNT(*) "; + $query .= "FROM ( "; + $query .= "SELECT DISTINCT SUBSTRING_INDEX(SCHEMA_NAME, "; + $query .= "'$dbSeparator', 1) "; + $query .= "DB_first_level "; + $query .= "FROM INFORMATION_SCHEMA.SCHEMATA "; + $query .= $this->_getWhereClause('SCHEMA_NAME', $searchClause); + $query .= ") t "; + $retval = (int)$GLOBALS['dbi']->fetchValue($query); + + return $retval; + } + + if ($GLOBALS['dbs_to_test'] !== false) { + $prefixMap = array(); + foreach ($this->_getDatabasesToSearch($searchClause) as $db) { + $query = "SHOW DATABASES LIKE '" . $db . "'"; + $handle = $GLOBALS['dbi']->tryQuery($query); + if ($handle === false) { + continue; + } + + while ($arr = $GLOBALS['dbi']->fetchArray($handle)) { + if ($this->_isHideDb($arr[0])) { + continue; + } + $prefix = strstr($arr[0], $dbSeparator, true); + if ($prefix === false) { + $prefix = $arr[0]; + } + $prefixMap[$prefix] = 1; + } + } + $retval = count($prefixMap); + + return $retval; + } + + $prefixMap = array(); + $query = "SHOW DATABASES "; + $query .= $this->_getWhereClause('Database', $searchClause); + $handle = $GLOBALS['dbi']->tryQuery($query); + if ($handle !== false) { + while ($arr = $GLOBALS['dbi']->fetchArray($handle)) { + $prefix = strstr($arr[0], $dbSeparator, true); + if ($prefix === false) { + $prefix = $arr[0]; + } + $prefixMap[$prefix] = 1; + } + } + $retval = count($prefixMap); + + return $retval; + } + + /** + * Detemines whether a given database should be hidden according to 'hide_db' + * + * @param string $db database name + * + * @return boolean whether to hide + */ + private function _isHideDb($db) + { + return !empty($GLOBALS['cfg']['Server']['hide_db']) + && preg_match('/' . $GLOBALS['cfg']['Server']['hide_db'] . '/', $db); + } + + /** + * Get the list of databases for 'SHOW DATABASES LIKE' queries. + * If a search clause is set it gets the highest priority while only_db gets + * the next priority. In case both are empty list of databases determined by + * GRANTs are used + * + * @param string $searchClause search clause + * + * @return array array of databases + */ + private function _getDatabasesToSearch($searchClause) + { + if (!empty($searchClause)) { + $databases = array( + "%" . $GLOBALS['dbi']->escapeString($searchClause) . "%", + ); + } elseif (!empty($GLOBALS['cfg']['Server']['only_db'])) { + $databases = $GLOBALS['cfg']['Server']['only_db']; + } elseif (!empty($GLOBALS['dbs_to_test'])) { + $databases = $GLOBALS['dbs_to_test']; + } + sort($databases); + + return $databases; + } + + /** + * Returns the WHERE clause depending on the $searchClause parameter + * and the hide_db directive + * + * @param string $columnName Column name of the column having database names + * @param string $searchClause A string used to filter the results of the query + * + * @return string + */ + private function _getWhereClause($columnName, $searchClause = '') + { + $whereClause = "WHERE TRUE "; + if (!empty($searchClause)) { + $whereClause .= "AND " . Util::backquote($columnName) + . " LIKE '%"; + $whereClause .= $GLOBALS['dbi']->escapeString($searchClause); + $whereClause .= "%' "; + } + + if (!empty($GLOBALS['cfg']['Server']['hide_db'])) { + $whereClause .= "AND " . Util::backquote($columnName) + . " NOT REGEXP '" + . $GLOBALS['dbi']->escapeString($GLOBALS['cfg']['Server']['hide_db']) + . "' "; + } + + if (!empty($GLOBALS['cfg']['Server']['only_db'])) { + if (is_string($GLOBALS['cfg']['Server']['only_db'])) { + $GLOBALS['cfg']['Server']['only_db'] = array( + $GLOBALS['cfg']['Server']['only_db'], + ); + } + $whereClause .= "AND ("; + $subClauses = array(); + foreach ($GLOBALS['cfg']['Server']['only_db'] as $each_only_db) { + $subClauses[] = " " . Util::backquote($columnName) + . " LIKE '" + . $GLOBALS['dbi']->escapeString($each_only_db) . "' "; + } + $whereClause .= implode("OR", $subClauses) . ") "; + } + + return $whereClause; + } + + /** + * Returns HTML for control buttons displayed infront of a node + * + * @return String HTML for control buttons + */ + public function getHtmlForControlButtons() + { + return ''; + } + + /** + * Returns CSS classes for a node + * + * @param boolean $match Whether the node matched loaded tree + * + * @return String with html classes. + */ + public function getCssClasses($match) + { + if (!$GLOBALS['cfg']['NavigationTreeEnableExpansion'] + ) { + return ''; + } + + $result = array('expander'); + + if ($this->is_group || $match) { + $result[] = 'loaded'; + } + if ($this->type == Node::CONTAINER) { + $result[] = 'container'; + } + + return implode(' ', $result); + } + + /** + * Returns icon for the node + * + * @param boolean $match Whether the node matched loaded tree + * + * @return String with image name + */ + public function getIcon($match) + { + if (!$GLOBALS['cfg']['NavigationTreeEnableExpansion'] + ) { + return ''; + } elseif ($match) { + $this->visible = true; + + return Util::getImage('b_minus'); + } + + return Util::getImage('b_plus', __('Expand/Collapse')); + } + + /** + * Gets the count of hidden elements for each database + * + * @return array array containing the count of hidden elements for each database + */ + public function getNavigationHidingData() + { + $cfgRelation = $this->relation->getRelationsParam(); + if ($cfgRelation['navwork']) { + $navTable = Util::backquote($cfgRelation['db']) + . "." . Util::backquote( + $cfgRelation['navigationhiding'] + ); + $sqlQuery = "SELECT `db_name`, COUNT(*) AS `count` FROM " . $navTable + . " WHERE `username`='" + . $GLOBALS['dbi']->escapeString( + $GLOBALS['cfg']['Server']['user'] + ) . "'" + . " GROUP BY `db_name`"; + $counts = $GLOBALS['dbi']->fetchResult( + $sqlQuery, + 'db_name', + 'count', + DatabaseInterface::CONNECT_CONTROL + ); + + return $counts; + } + + return null; + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Navigation/Nodes/NodeColumn.php b/php/apps/phpmyadmin49/libraries/classes/Navigation/Nodes/NodeColumn.php new file mode 100644 index 00000000..876cb221 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Navigation/Nodes/NodeColumn.php @@ -0,0 +1,41 @@ +icon = Util::getImage('pause', __('Column')); + $this->links = array( + 'text' => 'tbl_structure.php?server=' . $GLOBALS['server'] + . '&db=%3$s&table=%2$s&field=%1$s' + . '&change_column=1', + 'icon' => 'tbl_structure.php?server=' . $GLOBALS['server'] + . '&db=%3$s&table=%2$s&field=%1$s' + . '&change_column=1', + 'title' => __('Structure'), + ); + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Navigation/Nodes/NodeColumnContainer.php b/php/apps/phpmyadmin49/libraries/classes/Navigation/Nodes/NodeColumnContainer.php new file mode 100644 index 00000000..eaa7c9c0 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Navigation/Nodes/NodeColumnContainer.php @@ -0,0 +1,53 @@ +icon = Util::getImage('pause', __('Columns')); + $this->links = array( + 'text' => 'tbl_structure.php?server=' . $GLOBALS['server'] + . '&db=%2$s&table=%1$s', + 'icon' => 'tbl_structure.php?server=' . $GLOBALS['server'] + . '&db=%2$s&table=%1$s', + ); + $this->real_name = 'columns'; + + $new_label = _pgettext('Create new column', 'New'); + $new = NodeFactory::getInstance( + 'Node', + $new_label + ); + $new->isNew = true; + $new->icon = Util::getImage('b_column_add', $new_label); + $new->links = array( + 'text' => 'tbl_addfield.php?server=' . $GLOBALS['server'] + . '&db=%3$s&table=%2$s' + . '&field_where=last&after_field=', + 'icon' => 'tbl_addfield.php?server=' . $GLOBALS['server'] + . '&db=%3$s&table=%2$s' + . '&field_where=last&after_field=', + ); + $new->classes = 'new_column italics'; + $this->addChild($new); + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Navigation/Nodes/NodeDatabase.php b/php/apps/phpmyadmin49/libraries/classes/Navigation/Nodes/NodeDatabase.php new file mode 100644 index 00000000..246000c4 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Navigation/Nodes/NodeDatabase.php @@ -0,0 +1,715 @@ +icon = Util::getImage( + 's_db', + __('Database operations') + ); + + $script_name = Util::getScriptNameForOption( + $GLOBALS['cfg']['DefaultTabDatabase'], + 'database' + ); + $this->links = array( + 'text' => $script_name + . '?server=' . $GLOBALS['server'] + . '&db=%1$s', + 'icon' => 'db_operations.php?server=' . $GLOBALS['server'] + . '&db=%1$s&', + 'title' => __('Structure'), + ); + $this->classes = 'database'; + } + + /** + * Returns the number of children of type $type present inside this container + * This method is overridden by the PhpMyAdmin\Navigation\Nodes\NodeDatabase + * and PhpMyAdmin\Navigation\Nodes\NodeTable classes + * + * @param string $type The type of item we are looking for + * ('tables', 'views', etc) + * @param string $searchClause A string used to filter the results of + * the query + * @param boolean $singleItem Whether to get presence of a single known + * item or false in none + * + * @return int + */ + public function getPresence($type = '', $searchClause = '', $singleItem = false) + { + $retval = 0; + switch ($type) { + case 'tables': + $retval = $this->_getTableCount($searchClause, $singleItem); + break; + case 'views': + $retval = $this->_getViewCount($searchClause, $singleItem); + break; + case 'procedures': + $retval = $this->_getProcedureCount($searchClause, $singleItem); + break; + case 'functions': + $retval = $this->_getFunctionCount($searchClause, $singleItem); + break; + case 'events': + $retval = $this->_getEventCount($searchClause, $singleItem); + break; + default: + break; + } + + return $retval; + } + + /** + * Returns the number of tables or views present inside this database + * + * @param string $which tables|views + * @param string $searchClause A string used to filter the results of + * the query + * @param boolean $singleItem Whether to get presence of a single known + * item or false in none + * + * @return int + */ + private function _getTableOrViewCount($which, $searchClause, $singleItem) + { + $db = $this->real_name; + if ($which == 'tables') { + $condition = 'IN'; + } else { + $condition = 'NOT IN'; + } + + if (! $GLOBALS['cfg']['Server']['DisableIS']) { + $db = $GLOBALS['dbi']->escapeString($db); + $query = "SELECT COUNT(*) "; + $query .= "FROM `INFORMATION_SCHEMA`.`TABLES` "; + $query .= "WHERE `TABLE_SCHEMA`='$db' "; + $query .= "AND `TABLE_TYPE`" . $condition . "('BASE TABLE', 'SYSTEM VERSIONED') "; + if (! empty($searchClause)) { + $query .= "AND " . $this->_getWhereClauseForSearch( + $searchClause, + $singleItem, + 'TABLE_NAME' + ); + } + $retval = (int)$GLOBALS['dbi']->fetchValue($query); + } else { + $query = "SHOW FULL TABLES FROM "; + $query .= Util::backquote($db); + $query .= " WHERE `Table_type`" . $condition . "('BASE TABLE', 'SYSTEM VERSIONED') "; + if (!empty($searchClause)) { + $query .= "AND " . $this->_getWhereClauseForSearch( + $searchClause, + $singleItem, + 'Tables_in_' . $db + ); + } + $retval = $GLOBALS['dbi']->numRows( + $GLOBALS['dbi']->tryQuery($query) + ); + } + + return $retval; + } + + /** + * Returns the number of tables present inside this database + * + * @param string $searchClause A string used to filter the results of + * the query + * @param boolean $singleItem Whether to get presence of a single known + * item or false in none + * + * @return int + */ + private function _getTableCount($searchClause, $singleItem) + { + return $this->_getTableOrViewCount( + 'tables', + $searchClause, + $singleItem + ); + } + + /** + * Returns the number of views present inside this database + * + * @param string $searchClause A string used to filter the results of + * the query + * @param boolean $singleItem Whether to get presence of a single known + * item or false in none + * + * @return int + */ + private function _getViewCount($searchClause, $singleItem) + { + return $this->_getTableOrViewCount( + 'views', + $searchClause, + $singleItem + ); + } + + /** + * Returns the number of procedures present inside this database + * + * @param string $searchClause A string used to filter the results of + * the query + * @param boolean $singleItem Whether to get presence of a single known + * item or false in none + * + * @return int + */ + private function _getProcedureCount($searchClause, $singleItem) + { + $db = $this->real_name; + if (!$GLOBALS['cfg']['Server']['DisableIS']) { + $db = $GLOBALS['dbi']->escapeString($db); + $query = "SELECT COUNT(*) "; + $query .= "FROM `INFORMATION_SCHEMA`.`ROUTINES` "; + $query .= "WHERE `ROUTINE_SCHEMA` " + . Util::getCollateForIS() . "='$db'"; + $query .= "AND `ROUTINE_TYPE`='PROCEDURE' "; + if (!empty($searchClause)) { + $query .= "AND " . $this->_getWhereClauseForSearch( + $searchClause, + $singleItem, + 'ROUTINE_NAME' + ); + } + $retval = (int)$GLOBALS['dbi']->fetchValue($query); + } else { + $db = $GLOBALS['dbi']->escapeString($db); + $query = "SHOW PROCEDURE STATUS WHERE `Db`='$db' "; + if (!empty($searchClause)) { + $query .= "AND " . $this->_getWhereClauseForSearch( + $searchClause, + $singleItem, + 'Name' + ); + } + $retval = $GLOBALS['dbi']->numRows( + $GLOBALS['dbi']->tryQuery($query) + ); + } + + return $retval; + } + + /** + * Returns the number of functions present inside this database + * + * @param string $searchClause A string used to filter the results of + * the query + * @param boolean $singleItem Whether to get presence of a single known + * item or false in none + * + * @return int + */ + private function _getFunctionCount($searchClause, $singleItem) + { + $db = $this->real_name; + if (!$GLOBALS['cfg']['Server']['DisableIS']) { + $db = $GLOBALS['dbi']->escapeString($db); + $query = "SELECT COUNT(*) "; + $query .= "FROM `INFORMATION_SCHEMA`.`ROUTINES` "; + $query .= "WHERE `ROUTINE_SCHEMA` " + . Util::getCollateForIS() . "='$db' "; + $query .= "AND `ROUTINE_TYPE`='FUNCTION' "; + if (!empty($searchClause)) { + $query .= "AND " . $this->_getWhereClauseForSearch( + $searchClause, + $singleItem, + 'ROUTINE_NAME' + ); + } + $retval = (int)$GLOBALS['dbi']->fetchValue($query); + } else { + $db = $GLOBALS['dbi']->escapeString($db); + $query = "SHOW FUNCTION STATUS WHERE `Db`='$db' "; + if (!empty($searchClause)) { + $query .= "AND " . $this->_getWhereClauseForSearch( + $searchClause, + $singleItem, + 'Name' + ); + } + $retval = $GLOBALS['dbi']->numRows( + $GLOBALS['dbi']->tryQuery($query) + ); + } + + return $retval; + } + + /** + * Returns the number of events present inside this database + * + * @param string $searchClause A string used to filter the results of + * the query + * @param boolean $singleItem Whether to get presence of a single known + * item or false in none + * + * @return int + */ + private function _getEventCount($searchClause, $singleItem) + { + $db = $this->real_name; + if (!$GLOBALS['cfg']['Server']['DisableIS']) { + $db = $GLOBALS['dbi']->escapeString($db); + $query = "SELECT COUNT(*) "; + $query .= "FROM `INFORMATION_SCHEMA`.`EVENTS` "; + $query .= "WHERE `EVENT_SCHEMA` " + . Util::getCollateForIS() . "='$db' "; + if (!empty($searchClause)) { + $query .= "AND " . $this->_getWhereClauseForSearch( + $searchClause, + $singleItem, + 'EVENT_NAME' + ); + } + $retval = (int)$GLOBALS['dbi']->fetchValue($query); + } else { + $db = Util::backquote($db); + $query = "SHOW EVENTS FROM $db "; + if (!empty($searchClause)) { + $query .= "WHERE " . $this->_getWhereClauseForSearch( + $searchClause, + $singleItem, + 'Name' + ); + } + $retval = $GLOBALS['dbi']->numRows( + $GLOBALS['dbi']->tryQuery($query) + ); + } + + return $retval; + } + + /** + * Returns the WHERE clause for searching inside a database + * + * @param string $searchClause A string used to filter the results of the query + * @param boolean $singleItem Whether to get presence of a single known item + * @param string $columnName Name of the column in the result set to match + * + * @return string WHERE clause for searching + */ + private function _getWhereClauseForSearch( + $searchClause, + $singleItem, + $columnName + ) { + $query = ''; + if ($singleItem) { + $query .= Util::backquote($columnName) . " = "; + $query .= "'" . $GLOBALS['dbi']->escapeString($searchClause) . "'"; + } else { + $query .= Util::backquote($columnName) . " LIKE "; + $query .= "'%" . $GLOBALS['dbi']->escapeString($searchClause) + . "%'"; + } + + return $query; + } + + /** + * Returns the names of children of type $type present inside this container + * This method is overridden by the PhpMyAdmin\Navigation\Nodes\NodeDatabase + * and PhpMyAdmin\Navigation\Nodes\NodeTable classes + * + * @param string $type The type of item we are looking for + * ('tables', 'views', etc) + * @param int $pos The offset of the list within the results + * @param string $searchClause A string used to filter the results of the query + * + * @return array + */ + public function getData($type, $pos, $searchClause = '') + { + $retval = array(); + switch ($type) { + case 'tables': + $retval = $this->_getTables($pos, $searchClause); + break; + case 'views': + $retval = $this->_getViews($pos, $searchClause); + break; + case 'procedures': + $retval = $this->_getProcedures($pos, $searchClause); + break; + case 'functions': + $retval = $this->_getFunctions($pos, $searchClause); + break; + case 'events': + $retval = $this->_getEvents($pos, $searchClause); + break; + default: + break; + } + + // Remove hidden items so that they are not displayed in navigation tree + $cfgRelation = $this->relation->getRelationsParam(); + if ($cfgRelation['navwork']) { + $hiddenItems = $this->getHiddenItems(substr($type, 0, -1)); + foreach ($retval as $key => $item) { + if (in_array($item, $hiddenItems)) { + unset($retval[$key]); + } + } + } + + return $retval; + } + + /** + * Return list of hidden items of given type + * + * @param string $type The type of items we are looking for + * ('table', 'function', 'group', etc.) + * + * @return array Array containing hidden items of given type + */ + public function getHiddenItems($type) + { + $db = $this->real_name; + $cfgRelation = $this->relation->getRelationsParam(); + if (empty($cfgRelation['navigationhiding'])) { + return array(); + } + $navTable = Util::backquote($cfgRelation['db']) + . "." . Util::backquote($cfgRelation['navigationhiding']); + $sqlQuery = "SELECT `item_name` FROM " . $navTable + . " WHERE `username`='" . $cfgRelation['user'] . "'" + . " AND `item_type`='" . $type + . "'" . " AND `db_name`='" . $GLOBALS['dbi']->escapeString($db) + . "'"; + $result = $this->relation->queryAsControlUser($sqlQuery, false); + $hiddenItems = array(); + if ($result) { + while ($row = $GLOBALS['dbi']->fetchArray($result)) { + $hiddenItems[] = $row[0]; + } + } + $GLOBALS['dbi']->freeResult($result); + + return $hiddenItems; + } + + /** + * Returns the list of tables or views inside this database + * + * @param string $which tables|views + * @param int $pos The offset of the list within the results + * @param string $searchClause A string used to filter the results of the query + * + * @return array + */ + private function _getTablesOrViews($which, $pos, $searchClause) + { + if ($which == 'tables') { + $condition = 'IN'; + } else { + $condition = 'NOT IN'; + } + $maxItems = $GLOBALS['cfg']['MaxNavigationItems']; + $retval = array(); + $db = $this->real_name; + if (! $GLOBALS['cfg']['Server']['DisableIS']) { + $escdDb = $GLOBALS['dbi']->escapeString($db); + $query = "SELECT `TABLE_NAME` AS `name` "; + $query .= "FROM `INFORMATION_SCHEMA`.`TABLES` "; + $query .= "WHERE `TABLE_SCHEMA`='$escdDb' "; + $query .= "AND `TABLE_TYPE`" . $condition . "('BASE TABLE', 'SYSTEM VERSIONED') "; + if (! empty($searchClause)) { + $query .= "AND `TABLE_NAME` LIKE '%"; + $query .= $GLOBALS['dbi']->escapeString($searchClause); + $query .= "%'"; + } + $query .= "ORDER BY `TABLE_NAME` ASC "; + $query .= "LIMIT " . intval($pos) . ", $maxItems"; + $retval = $GLOBALS['dbi']->fetchResult($query); + } else { + $query = " SHOW FULL TABLES FROM "; + $query .= Util::backquote($db); + $query .= " WHERE `Table_type`" . $condition . "('BASE TABLE', 'SYSTEM VERSIONED') "; + if (!empty($searchClause)) { + $query .= "AND " . Util::backquote( + "Tables_in_" . $db + ); + $query .= " LIKE '%" . $GLOBALS['dbi']->escapeString( + $searchClause + ); + $query .= "%'"; + } + $handle = $GLOBALS['dbi']->tryQuery($query); + if ($handle !== false) { + $count = 0; + if ($GLOBALS['dbi']->dataSeek($handle, $pos)) { + while ($arr = $GLOBALS['dbi']->fetchArray($handle)) { + if ($count < $maxItems) { + $retval[] = $arr[0]; + $count++; + } else { + break; + } + } + } + } + } + + return $retval; + } + + /** + * Returns the list of tables inside this database + * + * @param int $pos The offset of the list within the results + * @param string $searchClause A string used to filter the results of the query + * + * @return array + */ + private function _getTables($pos, $searchClause) + { + return $this->_getTablesOrViews('tables', $pos, $searchClause); + } + + /** + * Returns the list of views inside this database + * + * @param int $pos The offset of the list within the results + * @param string $searchClause A string used to filter the results of the query + * + * @return array + */ + private function _getViews($pos, $searchClause) + { + return $this->_getTablesOrViews('views', $pos, $searchClause); + } + + /** + * Returns the list of procedures or functions inside this database + * + * @param string $routineType PROCEDURE|FUNCTION + * @param int $pos The offset of the list within the results + * @param string $searchClause A string used to filter the results of the query + * + * @return array + */ + private function _getRoutines($routineType, $pos, $searchClause) + { + $maxItems = $GLOBALS['cfg']['MaxNavigationItems']; + $retval = array(); + $db = $this->real_name; + if (!$GLOBALS['cfg']['Server']['DisableIS']) { + $escdDb = $GLOBALS['dbi']->escapeString($db); + $query = "SELECT `ROUTINE_NAME` AS `name` "; + $query .= "FROM `INFORMATION_SCHEMA`.`ROUTINES` "; + $query .= "WHERE `ROUTINE_SCHEMA` " + . Util::getCollateForIS() . "='$escdDb'"; + $query .= "AND `ROUTINE_TYPE`='" . $routineType . "' "; + if (!empty($searchClause)) { + $query .= "AND `ROUTINE_NAME` LIKE '%"; + $query .= $GLOBALS['dbi']->escapeString($searchClause); + $query .= "%'"; + } + $query .= "ORDER BY `ROUTINE_NAME` ASC "; + $query .= "LIMIT " . intval($pos) . ", $maxItems"; + $retval = $GLOBALS['dbi']->fetchResult($query); + } else { + $escdDb = $GLOBALS['dbi']->escapeString($db); + $query = "SHOW " . $routineType . " STATUS WHERE `Db`='$escdDb' "; + if (!empty($searchClause)) { + $query .= "AND `Name` LIKE '%"; + $query .= $GLOBALS['dbi']->escapeString($searchClause); + $query .= "%'"; + } + $handle = $GLOBALS['dbi']->tryQuery($query); + if ($handle !== false) { + $count = 0; + if ($GLOBALS['dbi']->dataSeek($handle, $pos)) { + while ($arr = $GLOBALS['dbi']->fetchArray($handle)) { + if ($count < $maxItems) { + $retval[] = $arr['Name']; + $count++; + } else { + break; + } + } + } + } + } + + return $retval; + } + + /** + * Returns the list of procedures inside this database + * + * @param int $pos The offset of the list within the results + * @param string $searchClause A string used to filter the results of the query + * + * @return array + */ + private function _getProcedures($pos, $searchClause) + { + return $this->_getRoutines('PROCEDURE', $pos, $searchClause); + } + + /** + * Returns the list of functions inside this database + * + * @param int $pos The offset of the list within the results + * @param string $searchClause A string used to filter the results of the query + * + * @return array + */ + private function _getFunctions($pos, $searchClause) + { + return $this->_getRoutines('FUNCTION', $pos, $searchClause); + } + + /** + * Returns the list of events inside this database + * + * @param int $pos The offset of the list within the results + * @param string $searchClause A string used to filter the results of the query + * + * @return array + */ + private function _getEvents($pos, $searchClause) + { + $maxItems = $GLOBALS['cfg']['MaxNavigationItems']; + $retval = array(); + $db = $this->real_name; + if (!$GLOBALS['cfg']['Server']['DisableIS']) { + $escdDb = $GLOBALS['dbi']->escapeString($db); + $query = "SELECT `EVENT_NAME` AS `name` "; + $query .= "FROM `INFORMATION_SCHEMA`.`EVENTS` "; + $query .= "WHERE `EVENT_SCHEMA` " + . Util::getCollateForIS() . "='$escdDb' "; + if (!empty($searchClause)) { + $query .= "AND `EVENT_NAME` LIKE '%"; + $query .= $GLOBALS['dbi']->escapeString($searchClause); + $query .= "%'"; + } + $query .= "ORDER BY `EVENT_NAME` ASC "; + $query .= "LIMIT " . intval($pos) . ", $maxItems"; + $retval = $GLOBALS['dbi']->fetchResult($query); + } else { + $escdDb = Util::backquote($db); + $query = "SHOW EVENTS FROM $escdDb "; + if (!empty($searchClause)) { + $query .= "WHERE `Name` LIKE '%"; + $query .= $GLOBALS['dbi']->escapeString($searchClause); + $query .= "%'"; + } + $handle = $GLOBALS['dbi']->tryQuery($query); + if ($handle !== false) { + $count = 0; + if ($GLOBALS['dbi']->dataSeek($handle, $pos)) { + while ($arr = $GLOBALS['dbi']->fetchArray($handle)) { + if ($count < $maxItems) { + $retval[] = $arr['Name']; + $count++; + } else { + break; + } + } + } + } + } + + return $retval; + } + + /** + * Returns HTML for control buttons displayed infront of a node + * + * @return String HTML for control buttons + */ + public function getHtmlForControlButtons() + { + $ret = ''; + $cfgRelation = $this->relation->getRelationsParam(); + if ($cfgRelation['navwork']) { + if ($this->hiddenCount > 0) { + $params = array( + 'showUnhideDialog' => true, + 'dbName' => $this->real_name, + ); + $ret = '' + . '' + . Util::getImage( + 'show', + __('Show hidden items') + ) + . ''; + } + } + + return $ret; + } + + /** + * Sets the number of hidden items in this database + * + * @param int $count hidden item count + * + * @return void + */ + public function setHiddenCount($count) + { + $this->hiddenCount = $count; + } + + /** + * Returns the number of hidden items in this database + * + * @return int hidden item count + */ + public function getHiddenCount() + { + return $this->hiddenCount; + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Navigation/Nodes/NodeDatabaseChild.php b/php/apps/phpmyadmin49/libraries/classes/Navigation/Nodes/NodeDatabaseChild.php new file mode 100644 index 00000000..ecae8fab --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Navigation/Nodes/NodeDatabaseChild.php @@ -0,0 +1,60 @@ +relation->getRelationsParam(); + if ($cfgRelation['navwork']) { + $db = $this->realParent()->real_name; + $item = $this->real_name; + + $params = array( + 'hideNavItem' => true, + 'itemType' => $this->getItemType(), + 'itemName' => $item, + 'dbName' => $db + ); + + $ret = '' + . '' + . Util::getImage('hide', __('Hide')) + . ''; + } + + return $ret; + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Navigation/Nodes/NodeDatabaseChildContainer.php b/php/apps/phpmyadmin49/libraries/classes/Navigation/Nodes/NodeDatabaseChildContainer.php new file mode 100644 index 00000000..e11ff697 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Navigation/Nodes/NodeDatabaseChildContainer.php @@ -0,0 +1,43 @@ +separator = $GLOBALS['cfg']['NavigationTreeTableSeparator']; + $this->separator_depth = (int)( + $GLOBALS['cfg']['NavigationTreeTableLevel'] + ); + } + } + + /** + * Returns the type of the item represented by the node. + * + * @return string type of the item + */ + protected function getItemType() + { + return 'group'; + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Navigation/Nodes/NodeDatabaseContainer.php b/php/apps/phpmyadmin49/libraries/classes/Navigation/Nodes/NodeDatabaseContainer.php new file mode 100644 index 00000000..33a9913d --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Navigation/Nodes/NodeDatabaseContainer.php @@ -0,0 +1,48 @@ +isNew = true; + $new->icon = Util::getImage('b_newdb', ''); + $new->links = array( + 'text' => 'server_databases.php?server=' . $GLOBALS['server'], + 'icon' => 'server_databases.php?server=' . $GLOBALS['server'], + ); + $new->classes = 'new_database italics'; + $this->addChild($new); + } + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Navigation/Nodes/NodeEvent.php b/php/apps/phpmyadmin49/libraries/classes/Navigation/Nodes/NodeEvent.php new file mode 100644 index 00000000..0737fcce --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Navigation/Nodes/NodeEvent.php @@ -0,0 +1,49 @@ +icon = Util::getImage('b_events'); + $this->links = array( + 'text' => 'db_events.php?server=' . $GLOBALS['server'] + . '&db=%2$s&item_name=%1$s&edit_item=1', + 'icon' => 'db_events.php?server=' . $GLOBALS['server'] + . '&db=%2$s&item_name=%1$s&export_item=1', + ); + $this->classes = 'event'; + } + + /** + * Returns the type of the item represented by the node. + * + * @return string type of the item + */ + protected function getItemType() + { + return 'event'; + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Navigation/Nodes/NodeEventContainer.php b/php/apps/phpmyadmin49/libraries/classes/Navigation/Nodes/NodeEventContainer.php new file mode 100644 index 00000000..0231c94d --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Navigation/Nodes/NodeEventContainer.php @@ -0,0 +1,50 @@ +icon = Util::getImage('b_events', ''); + $this->links = array( + 'text' => 'db_events.php?server=' . $GLOBALS['server'] + . '&db=%1$s', + 'icon' => 'db_events.php?server=' . $GLOBALS['server'] + . '&db=%1$s', + ); + $this->real_name = 'events'; + + $new = NodeFactory::getInstance( + 'Node', + _pgettext('Create new event', 'New') + ); + $new->isNew = true; + $new->icon = Util::getImage('b_event_add', ''); + $new->links = array( + 'text' => 'db_events.php?server=' . $GLOBALS['server'] + . '&db=%2$s&add_item=1', + 'icon' => 'db_events.php?server=' . $GLOBALS['server'] + . '&db=%2$s&add_item=1', + ); + $new->classes = 'new_event italics'; + $this->addChild($new); + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Navigation/Nodes/NodeFunction.php b/php/apps/phpmyadmin49/libraries/classes/Navigation/Nodes/NodeFunction.php new file mode 100644 index 00000000..2f5cd381 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Navigation/Nodes/NodeFunction.php @@ -0,0 +1,51 @@ +icon = Util::getImage('b_routines', __('Function')); + $this->links = array( + 'text' => 'db_routines.php?server=' . $GLOBALS['server'] + . '&db=%2$s&item_name=%1$s&item_type=FUNCTION' + . '&edit_item=1', + 'icon' => 'db_routines.php?server=' . $GLOBALS['server'] + . '&db=%2$s&item_name=%1$s&item_type=FUNCTION' + . '&execute_dialog=1', + ); + $this->classes = 'function'; + } + + /** + * Returns the type of the item represented by the node. + * + * @return string type of the item + */ + protected function getItemType() + { + return 'function'; + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Navigation/Nodes/NodeFunctionContainer.php b/php/apps/phpmyadmin49/libraries/classes/Navigation/Nodes/NodeFunctionContainer.php new file mode 100644 index 00000000..bf092edc --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Navigation/Nodes/NodeFunctionContainer.php @@ -0,0 +1,51 @@ +icon = Util::getImage('b_routines', __('Functions')); + $this->links = array( + 'text' => 'db_routines.php?server=' . $GLOBALS['server'] + . '&db=%1$s&type=FUNCTION', + 'icon' => 'db_routines.php?server=' . $GLOBALS['server'] + . '&db=%1$s&type=FUNCTION', + ); + $this->real_name = 'functions'; + + $new_label = _pgettext('Create new function', 'New'); + $new = NodeFactory::getInstance( + 'Node', + $new_label + ); + $new->isNew = true; + $new->icon = Util::getImage('b_routine_add', $new_label); + $new->links = array( + 'text' => 'db_routines.php?server=' . $GLOBALS['server'] + . '&db=%2$s&add_item=1&item_type=FUNCTION', + 'icon' => 'db_routines.php?server=' . $GLOBALS['server'] + . '&db=%2$s&add_item=1&item_type=FUNCTION', + ); + $new->classes = 'new_function italics'; + $this->addChild($new); + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Navigation/Nodes/NodeIndex.php b/php/apps/phpmyadmin49/libraries/classes/Navigation/Nodes/NodeIndex.php new file mode 100644 index 00000000..332cc2ce --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Navigation/Nodes/NodeIndex.php @@ -0,0 +1,39 @@ +icon = Util::getImage('b_index', __('Index')); + $this->links = array( + 'text' => 'tbl_indexes.php?server=' . $GLOBALS['server'] + . '&db=%3$s&table=%2$s&index=%1$s', + 'icon' => 'tbl_indexes.php?server=' . $GLOBALS['server'] + . '&db=%3$s&table=%2$s&index=%1$s', + ); + $this->classes = 'index'; + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Navigation/Nodes/NodeIndexContainer.php b/php/apps/phpmyadmin49/libraries/classes/Navigation/Nodes/NodeIndexContainer.php new file mode 100644 index 00000000..39ba9ee7 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Navigation/Nodes/NodeIndexContainer.php @@ -0,0 +1,53 @@ +icon = Util::getImage('b_index', __('Indexes')); + $this->links = array( + 'text' => 'tbl_structure.php?server=' . $GLOBALS['server'] + . '&db=%2$s&table=%1$s', + 'icon' => 'tbl_structure.php?server=' . $GLOBALS['server'] + . '&db=%2$s&table=%1$s', + ); + $this->real_name = 'indexes'; + + $new_label = _pgettext('Create new index', 'New'); + $new = NodeFactory::getInstance( + 'Node', + $new_label + ); + $new->isNew = true; + $new->icon = Util::getImage('b_index_add', $new_label); + $new->links = array( + 'text' => 'tbl_indexes.php?server=' . $GLOBALS['server'] + . '&create_index=1&added_fields=2' + . '&db=%3$s&table=%2$s', + 'icon' => 'tbl_indexes.php?server=' . $GLOBALS['server'] + . '&create_index=1&added_fields=2' + . '&db=%3$s&table=%2$s', + ); + $new->classes = 'new_index italics'; + $this->addChild($new); + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Navigation/Nodes/NodeProcedure.php b/php/apps/phpmyadmin49/libraries/classes/Navigation/Nodes/NodeProcedure.php new file mode 100644 index 00000000..e9a74eb0 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Navigation/Nodes/NodeProcedure.php @@ -0,0 +1,51 @@ +icon = Util::getImage('b_routines', __('Procedure')); + $this->links = array( + 'text' => 'db_routines.php?server=' . $GLOBALS['server'] + . '&db=%2$s&item_name=%1$s&item_type=PROCEDURE' + . '&edit_item=1', + 'icon' => 'db_routines.php?server=' . $GLOBALS['server'] + . '&db=%2$s&item_name=%1$s&item_type=PROCEDURE' + . '&execute_dialog=1', + ); + $this->classes = 'procedure'; + } + + /** + * Returns the type of the item represented by the node. + * + * @return string type of the item + */ + protected function getItemType() + { + return 'procedure'; + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Navigation/Nodes/NodeProcedureContainer.php b/php/apps/phpmyadmin49/libraries/classes/Navigation/Nodes/NodeProcedureContainer.php new file mode 100644 index 00000000..e4eb0fa8 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Navigation/Nodes/NodeProcedureContainer.php @@ -0,0 +1,51 @@ +icon = Util::getImage('b_routines', __('Procedures')); + $this->links = array( + 'text' => 'db_routines.php?server=' . $GLOBALS['server'] + . '&db=%1$s&type=PROCEDURE', + 'icon' => 'db_routines.php?server=' . $GLOBALS['server'] + . '&db=%1$s&type=PROCEDURE', + ); + $this->real_name = 'procedures'; + + $new_label = _pgettext('Create new procedure', 'New'); + $new = NodeFactory::getInstance( + 'Node', + $new_label + ); + $new->isNew = true; + $new->icon = Util::getImage('b_routine_add', $new_label); + $new->links = array( + 'text' => 'db_routines.php?server=' . $GLOBALS['server'] + . '&db=%2$s&add_item=1', + 'icon' => 'db_routines.php?server=' . $GLOBALS['server'] + . '&db=%2$s&add_item=1', + ); + $new->classes = 'new_procedure italics'; + $this->addChild($new); + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Navigation/Nodes/NodeTable.php b/php/apps/phpmyadmin49/libraries/classes/Navigation/Nodes/NodeTable.php new file mode 100644 index 00000000..cafe3065 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Navigation/Nodes/NodeTable.php @@ -0,0 +1,305 @@ +icon = array(); + $this->_addIcon( + Util::getScriptNameForOption( + $GLOBALS['cfg']['NavigationTreeDefaultTabTable'], + 'table' + ) + ); + $this->_addIcon( + Util::getScriptNameForOption( + $GLOBALS['cfg']['NavigationTreeDefaultTabTable2'], + 'table' + ) + ); + $title = Util::getTitleForTarget( + $GLOBALS['cfg']['DefaultTabTable'] + ); + $this->title = $title; + + $script_name = Util::getScriptNameForOption( + $GLOBALS['cfg']['DefaultTabTable'], + 'table' + ); + $this->links = array( + 'text' => $script_name + . '?server=' . $GLOBALS['server'] + . '&db=%2$s&table=%1$s' + . '&pos=0', + 'icon' => array( + Util::getScriptNameForOption( + $GLOBALS['cfg']['NavigationTreeDefaultTabTable'], + 'table' + ) + . '?server=' . $GLOBALS['server'] + . '&db=%2$s&table=%1$s', + Util::getScriptNameForOption( + $GLOBALS['cfg']['NavigationTreeDefaultTabTable2'], + 'table' + ) + . '?server=' . $GLOBALS['server'] + . '&db=%2$s&table=%1$s', + ), + 'title' => $this->title, + ); + $this->classes = 'table'; + } + + /** + * Returns the number of children of type $type present inside this container + * This method is overridden by the PhpMyAdmin\Navigation\Nodes\NodeDatabase + * and PhpMyAdmin\Navigation\Nodes\NodeTable classes + * + * @param string $type The type of item we are looking for + * ('columns' or 'indexes') + * @param string $searchClause A string used to filter the results of the query + * + * @return int + */ + public function getPresence($type = '', $searchClause = '') + { + $retval = 0; + $db = $this->realParent()->real_name; + $table = $this->real_name; + switch ($type) { + case 'columns': + if (!$GLOBALS['cfg']['Server']['DisableIS']) { + $db = $GLOBALS['dbi']->escapeString($db); + $table = $GLOBALS['dbi']->escapeString($table); + $query = "SELECT COUNT(*) "; + $query .= "FROM `INFORMATION_SCHEMA`.`COLUMNS` "; + $query .= "WHERE `TABLE_NAME`='$table' "; + $query .= "AND `TABLE_SCHEMA`='$db'"; + $retval = (int)$GLOBALS['dbi']->fetchValue($query); + } else { + $db = Util::backquote($db); + $table = Util::backquote($table); + $query = "SHOW COLUMNS FROM $table FROM $db"; + $retval = (int)$GLOBALS['dbi']->numRows( + $GLOBALS['dbi']->tryQuery($query) + ); + } + break; + case 'indexes': + $db = Util::backquote($db); + $table = Util::backquote($table); + $query = "SHOW INDEXES FROM $table FROM $db"; + $retval = (int)$GLOBALS['dbi']->numRows( + $GLOBALS['dbi']->tryQuery($query) + ); + break; + case 'triggers': + if (!$GLOBALS['cfg']['Server']['DisableIS']) { + $db = $GLOBALS['dbi']->escapeString($db); + $table = $GLOBALS['dbi']->escapeString($table); + $query = "SELECT COUNT(*) "; + $query .= "FROM `INFORMATION_SCHEMA`.`TRIGGERS` "; + $query .= "WHERE `EVENT_OBJECT_SCHEMA` " + . Util::getCollateForIS() . "='$db' "; + $query .= "AND `EVENT_OBJECT_TABLE` " + . Util::getCollateForIS() . "='$table'"; + $retval = (int)$GLOBALS['dbi']->fetchValue($query); + } else { + $db = Util::backquote($db); + $table = $GLOBALS['dbi']->escapeString($table); + $query = "SHOW TRIGGERS FROM $db WHERE `Table` = '$table'"; + $retval = (int)$GLOBALS['dbi']->numRows( + $GLOBALS['dbi']->tryQuery($query) + ); + } + break; + default: + break; + } + + return $retval; + } + + /** + * Returns the names of children of type $type present inside this container + * This method is overridden by the PhpMyAdmin\Navigation\Nodes\NodeDatabase + * and PhpMyAdmin\Navigation\Nodes\NodeTable classes + * + * @param string $type The type of item we are looking for + * ('tables', 'views', etc) + * @param int $pos The offset of the list within the results + * @param string $searchClause A string used to filter the results of the query + * + * @return array + */ + public function getData($type, $pos, $searchClause = '') + { + $maxItems = $GLOBALS['cfg']['MaxNavigationItems']; + $retval = array(); + $db = $this->realParent()->real_name; + $table = $this->real_name; + switch ($type) { + case 'columns': + if (!$GLOBALS['cfg']['Server']['DisableIS']) { + $db = $GLOBALS['dbi']->escapeString($db); + $table = $GLOBALS['dbi']->escapeString($table); + $query = "SELECT `COLUMN_NAME` AS `name` "; + $query .= "FROM `INFORMATION_SCHEMA`.`COLUMNS` "; + $query .= "WHERE `TABLE_NAME`='$table' "; + $query .= "AND `TABLE_SCHEMA`='$db' "; + $query .= "ORDER BY `COLUMN_NAME` ASC "; + $query .= "LIMIT " . intval($pos) . ", $maxItems"; + $retval = $GLOBALS['dbi']->fetchResult($query); + break; + } + + $db = Util::backquote($db); + $table = Util::backquote($table); + $query = "SHOW COLUMNS FROM $table FROM $db"; + $handle = $GLOBALS['dbi']->tryQuery($query); + if ($handle === false) { + break; + } + + $count = 0; + if ($GLOBALS['dbi']->dataSeek($handle, $pos)) { + while ($arr = $GLOBALS['dbi']->fetchArray($handle)) { + if ($count < $maxItems) { + $retval[] = $arr['Field']; + $count++; + } else { + break; + } + } + } + break; + case 'indexes': + $db = Util::backquote($db); + $table = Util::backquote($table); + $query = "SHOW INDEXES FROM $table FROM $db"; + $handle = $GLOBALS['dbi']->tryQuery($query); + if ($handle === false) { + break; + } + + $count = 0; + while ($arr = $GLOBALS['dbi']->fetchArray($handle)) { + if (in_array($arr['Key_name'], $retval)) { + continue; + } + if ($pos <= 0 && $count < $maxItems) { + $retval[] = $arr['Key_name']; + $count++; + } + $pos--; + } + break; + case 'triggers': + if (!$GLOBALS['cfg']['Server']['DisableIS']) { + $db = $GLOBALS['dbi']->escapeString($db); + $table = $GLOBALS['dbi']->escapeString($table); + $query = "SELECT `TRIGGER_NAME` AS `name` "; + $query .= "FROM `INFORMATION_SCHEMA`.`TRIGGERS` "; + $query .= "WHERE `EVENT_OBJECT_SCHEMA` " + . Util::getCollateForIS() . "='$db' "; + $query .= "AND `EVENT_OBJECT_TABLE` " + . Util::getCollateForIS() . "='$table' "; + $query .= "ORDER BY `TRIGGER_NAME` ASC "; + $query .= "LIMIT " . intval($pos) . ", $maxItems"; + $retval = $GLOBALS['dbi']->fetchResult($query); + break; + } + + $db = Util::backquote($db); + $table = $GLOBALS['dbi']->escapeString($table); + $query = "SHOW TRIGGERS FROM $db WHERE `Table` = '$table'"; + $handle = $GLOBALS['dbi']->tryQuery($query); + if ($handle === false) { + break; + } + + $count = 0; + if ($GLOBALS['dbi']->dataSeek($handle, $pos)) { + while ($arr = $GLOBALS['dbi']->fetchArray($handle)) { + if ($count < $maxItems) { + $retval[] = $arr['Trigger']; + $count++; + } else { + break; + } + } + } + break; + default: + break; + } + + return $retval; + } + + /** + * Returns the type of the item represented by the node. + * + * @return string type of the item + */ + protected function getItemType() + { + return 'table'; + } + + /** + * Add an icon to navigation tree + * + * @param string $page Page name to redirect + * + * @return void + */ + private function _addIcon($page) + { + if (empty($page)) { + return; + } + + switch ($page) { + case 'tbl_structure.php': + $this->icon[] = Util::getImage('b_props', __('Structure')); + break; + case 'tbl_select.php': + $this->icon[] = Util::getImage('b_search', __('Search')); + break; + case 'tbl_change.php': + $this->icon[] = Util::getImage('b_insrow', __('Insert')); + break; + case 'tbl_sql.php': + $this->icon[] = Util::getImage('b_sql', __('SQL')); + break; + case 'sql.php': + $this->icon[] = Util::getImage('b_browse', __('Browse')); + break; + } + } +} + diff --git a/php/apps/phpmyadmin49/libraries/classes/Navigation/Nodes/NodeTableContainer.php b/php/apps/phpmyadmin49/libraries/classes/Navigation/Nodes/NodeTableContainer.php new file mode 100644 index 00000000..e0a30cf7 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Navigation/Nodes/NodeTableContainer.php @@ -0,0 +1,52 @@ +icon = Util::getImage('b_browse', __('Tables')); + $this->links = array( + 'text' => 'db_structure.php?server=' . $GLOBALS['server'] + . '&db=%1$s&tbl_type=table', + 'icon' => 'db_structure.php?server=' . $GLOBALS['server'] + . '&db=%1$s&tbl_type=table', + ); + $this->real_name = 'tables'; + $this->classes = 'tableContainer subContainer'; + + $new_label = _pgettext('Create new table', 'New'); + $new = NodeFactory::getInstance( + 'Node', + $new_label + ); + $new->isNew = true; + $new->icon = Util::getImage('b_table_add', $new_label); + $new->links = array( + 'text' => 'tbl_create.php?server=' . $GLOBALS['server'] + . '&db=%2$s', + 'icon' => 'tbl_create.php?server=' . $GLOBALS['server'] + . '&db=%2$s', + ); + $new->classes = 'new_table italics'; + $this->addChild($new); + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Navigation/Nodes/NodeTrigger.php b/php/apps/phpmyadmin49/libraries/classes/Navigation/Nodes/NodeTrigger.php new file mode 100644 index 00000000..af88444f --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Navigation/Nodes/NodeTrigger.php @@ -0,0 +1,39 @@ +icon = Util::getImage('b_triggers'); + $this->links = array( + 'text' => 'db_triggers.php?server=' . $GLOBALS['server'] + . '&db=%3$s&item_name=%1$s&edit_item=1', + 'icon' => 'db_triggers.php?server=' . $GLOBALS['server'] + . '&db=%3$s&item_name=%1$s&export_item=1', + ); + $this->classes = 'trigger'; + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Navigation/Nodes/NodeTriggerContainer.php b/php/apps/phpmyadmin49/libraries/classes/Navigation/Nodes/NodeTriggerContainer.php new file mode 100644 index 00000000..77cfc731 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Navigation/Nodes/NodeTriggerContainer.php @@ -0,0 +1,50 @@ +icon = Util::getImage('b_triggers'); + $this->links = array( + 'text' => 'db_triggers.php?server=' . $GLOBALS['server'] + . '&db=%2$s&table=%1$s', + 'icon' => 'db_triggers.php?server=' . $GLOBALS['server'] + . '&db=%2$s&table=%1$s', + ); + $this->real_name = 'triggers'; + + $new = NodeFactory::getInstance( + 'Node', + _pgettext('Create new trigger', 'New') + ); + $new->isNew = true; + $new->icon = Util::getImage('b_trigger_add', ''); + $new->links = array( + 'text' => 'db_triggers.php?server=' . $GLOBALS['server'] + . '&db=%3$s&add_item=1', + 'icon' => 'db_triggers.php?server=' . $GLOBALS['server'] + . '&db=%3$s&add_item=1', + ); + $new->classes = 'new_trigger italics'; + $this->addChild($new); + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Navigation/Nodes/NodeView.php b/php/apps/phpmyadmin49/libraries/classes/Navigation/Nodes/NodeView.php new file mode 100644 index 00000000..e7dd5185 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Navigation/Nodes/NodeView.php @@ -0,0 +1,49 @@ +icon = Util::getImage('b_props', __('View')); + $this->links = array( + 'text' => 'sql.php?server=' . $GLOBALS['server'] + . '&db=%2$s&table=%1$s&pos=0', + 'icon' => 'tbl_structure.php?server=' . $GLOBALS['server'] + . '&db=%2$s&table=%1$s', + ); + $this->classes = 'view'; + } + + /** + * Returns the type of the item represented by the node. + * + * @return string type of the item + */ + protected function getItemType() + { + return 'view'; + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Navigation/Nodes/NodeViewContainer.php b/php/apps/phpmyadmin49/libraries/classes/Navigation/Nodes/NodeViewContainer.php new file mode 100644 index 00000000..dfb97c49 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Navigation/Nodes/NodeViewContainer.php @@ -0,0 +1,52 @@ +icon = Util::getImage('b_views', __('Views')); + $this->links = array( + 'text' => 'db_structure.php?server=' . $GLOBALS['server'] + . '&db=%1$s&tbl_type=view', + 'icon' => 'db_structure.php?server=' . $GLOBALS['server'] + . '&db=%1$s&tbl_type=view', + ); + $this->classes = 'viewContainer subContainer'; + $this->real_name = 'views'; + + $new_label = _pgettext('Create new view', 'New'); + $new = NodeFactory::getInstance( + 'Node', + $new_label + ); + $new->isNew = true; + $new->icon = Util::getImage('b_view_add', $new_label); + $new->links = array( + 'text' => 'view_create.php?server=' . $GLOBALS['server'] + . '&db=%2$s', + 'icon' => 'view_create.php?server=' . $GLOBALS['server'] + . '&db=%2$s', + ); + $new->classes = 'new_view italics'; + $this->addChild($new); + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Normalization.php b/php/apps/phpmyadmin49/libraries/classes/Normalization.php new file mode 100644 index 00000000..76d541d6 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Normalization.php @@ -0,0 +1,1071 @@ +dbi = $dbi; + $this->relation = new Relation(); + } + + /** + * build the html for columns of $colTypeCategory category + * in form of given $listType in a table + * + * @param string $db current database + * @param string $table current table + * @param string $colTypeCategory supported all|Numeric|String|Spatial + * |Date and time using the _pgettext() format + * @param string $listType type of list to build, supported dropdown|checkbox + * + * @return string HTML for list of columns in form of given list types + */ + public function getHtmlForColumnsList( + $db, + $table, + $colTypeCategory = 'all', + $listType = 'dropdown' + ) { + $columnTypeList = []; + if ($colTypeCategory != 'all') { + $types = $this->dbi->types->getColumns(); + $columnTypeList = $types[$colTypeCategory]; + } + $this->dbi->selectDb($db); + $columns = $this->dbi->getColumns( + $db, + $table, + null, + true + ); + $type = ""; + $selectColHtml = ""; + foreach ($columns as $column => $def) { + if (isset($def['Type'])) { + $extractedColumnSpec = Util::extractColumnSpec($def['Type']); + $type = $extractedColumnSpec['type']; + } + if (empty($columnTypeList) + || in_array(mb_strtoupper($type), $columnTypeList) + ) { + if ($listType == 'checkbox') { + $selectColHtml .= '' + . htmlspecialchars($column) . ' [ ' + . htmlspecialchars($def['Type']) . ' ]
      '; + } else { + $selectColHtml .= ''; + } + } + } + return $selectColHtml; + } + + /** + * get the html of the form to add the new column to given table + * + * @param integer $numFields number of columns to add + * @param string $db current database + * @param string $table current table + * @param array $columnMeta array containing default values for the fields + * + * @return string HTML + */ + public function getHtmlForCreateNewColumn( + $numFields, + $db, + $table, + array $columnMeta = [] + ) { + $cfgRelation = $this->relation->getRelationsParam(); + $contentCells = []; + $availableMime = []; + $mimeMap = []; + if ($cfgRelation['mimework'] && $GLOBALS['cfg']['BrowseMIME']) { + $mimeMap = Transformations::getMIME($db, $table); + $availableMime = Transformations::getAvailableMIMEtypes(); + } + $commentsMap = $this->relation->getComments($db, $table); + for ($columnNumber = 0; $columnNumber < $numFields; $columnNumber++) { + $contentCells[$columnNumber] = [ + 'column_number' => $columnNumber, + 'column_meta' => $columnMeta, + 'type_upper' => '', + 'length_values_input_size' => 8, + 'length' => '', + 'extracted_columnspec' => [], + 'submit_attribute' => null, + 'comments_map' => $commentsMap, + 'fields_meta' => null, + 'is_backup' => true, + 'move_columns' => [], + 'cfg_relation' => $cfgRelation, + 'available_mime' => isset($availableMime) ? $availableMime : [], + 'mime_map' => $mimeMap + ]; + } + + return Template::get( + 'columns_definitions/table_fields_definitions' + )->render([ + 'is_backup' => true, + 'fields_meta' => null, + 'mimework' => $cfgRelation['mimework'], + 'content_cells' => $contentCells, + 'change_column' => $_POST['change_column'], + 'is_virtual_columns_supported' => Util::isVirtualColumnsSupported(), + 'browse_mime' => $GLOBALS['cfg']['BrowseMIME'], + 'server_type' => Util::getServerType(), + 'max_rows' => intval($GLOBALS['cfg']['MaxRows']), + 'char_editing' => $GLOBALS['cfg']['CharEditing'], + 'attribute_types' => $this->dbi->types->getAttributes(), + 'privs_available' => $GLOBALS['col_priv'] && $GLOBALS['is_reload_priv'], + 'max_length' => $this->dbi->getVersion() >= 50503 ? 1024 : 255, + 'dbi' => $this->dbi, + 'disable_is' => $GLOBALS['cfg']['Server']['DisableIS'], + ]); + } + + /** + * build the html for step 1.1 of normalization + * + * @param string $db current database + * @param string $table current table + * @param string $normalizedTo up to which step normalization will go, + * possible values 1nf|2nf|3nf + * + * @return string HTML for step 1.1 + */ + public function getHtmlFor1NFStep1($db, $table, $normalizedTo) + { + $step = 1; + $stepTxt = __('Make all columns atomic'); + $html = "

      " + . __('First step of normalization (1NF)') . "

      "; + $html .= "
      " . + "
      " . + "" . __('Step 1.') . $step . " " . $stepTxt . "" . + "

      " . __( + 'Do you have any column which can be split into more than' + . ' one column? ' + . 'For example: address can be split into street, city, country and zip.' + ) + . "
      ( " + . __( + 'Show me the central list of columns that are not already in this table' + ) . " )

      " + . "

      " . __( + 'Select a column which can be split into more ' + . 'than one (on select of \'no such column\', it\'ll move to next step).' + ) + . "

      " + . "
      " + . "" + . "" . __('split into ') + . "" + . "
      " + . "
      " + . "
      " + . "
      " + . "
      "; + return $html; + } + + /** + * build the html contents of various html elements in step 1.2 + * + * @param string $db current database + * @param string $table current table + * + * @return string HTML contents for step 1.2 + */ + public function getHtmlContentsFor1NFStep2($db, $table) + { + $step = 2; + $stepTxt = __('Have a primary key'); + $primary = Index::getPrimary($table, $db); + $hasPrimaryKey = "0"; + $legendText = __('Step 1.') . $step . " " . $stepTxt; + $extra = ''; + if ($primary) { + $headText = __("Primary key already exists."); + $subText = __("Taking you to next step…"); + $hasPrimaryKey = "1"; + } else { + $headText = __( + "There is no primary key; please add one.
      " + . "Hint: A primary key is a column " + . "(or combination of columns) that uniquely identify all rows." + ); + $subText = '' + . Util::getIcon( + 'b_index_add', + __( + 'Add a primary key on existing column(s)' + ) + ) + . ''; + $extra = __( + "If it's not possible to make existing " + . "column combinations as primary key" + ) . "
      " + . '' + . __('+ Add a new primary key column') . ''; + } + $res = [ + 'legendText' => $legendText, + 'headText' => $headText, + 'subText' => $subText, + 'hasPrimaryKey' => $hasPrimaryKey, + 'extra' => $extra + ]; + return $res; + } + + /** + * build the html contents of various html elements in step 1.4 + * + * @param string $db current database + * @param string $table current table + * + * @return string HTML contents for step 1.4 + */ + public function getHtmlContentsFor1NFStep4($db, $table) + { + $step = 4; + $stepTxt = __('Remove redundant columns'); + $legendText = __('Step 1.') . $step . " " . $stepTxt; + $headText = __( + "Do you have a group of columns which on combining gives an existing" + . " column? For example, if you have first_name, last_name and" + . " full_name then combining first_name and last_name gives full_name" + . " which is redundant." + ); + $subText = __( + "Check the columns which are redundant and click on remove. " + . "If no redundant column, click on 'No redundant column'" + ); + $extra = $this->getHtmlForColumnsList($db, $table, 'all', "checkbox") . "
      " + . '' + . ''; + $res = [ + 'legendText' => $legendText, + 'headText' => $headText, + 'subText' => $subText, + 'extra' => $extra + ]; + return $res; + } + + /** + * build the html contents of various html elements in step 1.3 + * + * @param string $db current database + * @param string $table current table + * + * @return string HTML contents for step 1.3 + */ + public function getHtmlContentsFor1NFStep3($db, $table) + { + $step = 3; + $stepTxt = __('Move repeating groups'); + $legendText = __('Step 1.') . $step . " " . $stepTxt; + $headText = __( + "Do you have a group of two or more columns that are closely " + . "related and are all repeating the same attribute? For example, " + . "a table that holds data on books might have columns such as book_id, " + . "author1, author2, author3 and so on which form a " + . "repeating group. In this case a new table (book_id, author) should " + . "be created." + ); + $subText = __( + "Check the columns which form a repeating group. " + . "If no such group, click on 'No repeating group'" + ); + $extra = $this->getHtmlForColumnsList($db, $table, 'all', "checkbox") . "
      " + . '' + . ''; + $primary = Index::getPrimary($table, $db); + $primarycols = $primary->getColumns(); + $pk = []; + foreach ($primarycols as $col) { + $pk[] = $col->getName(); + } + $res = [ + 'legendText' => $legendText, + 'headText' => $headText, + 'subText' => $subText, + 'extra' => $extra, + 'primary_key' => json_encode($pk) + ]; + return $res; + } + + /** + * build html contents for 2NF step 2.1 + * + * @param string $db current database + * @param string $table current table + * + * @return string HTML contents for 2NF step 2.1 + */ + public function getHtmlFor2NFstep1($db, $table) + { + $legendText = __('Step 2.') . "1 " . __('Find partial dependencies'); + $primary = Index::getPrimary($table, $db); + $primarycols = $primary->getColumns(); + $pk = []; + $subText = ''; + $selectPkForm = ""; + $extra = ""; + foreach ($primarycols as $col) { + $pk[] = $col->getName(); + $selectPkForm .= '' + . htmlspecialchars($col->getName()); + } + $key = implode(', ', $pk); + if (count($primarycols) > 1) { + $this->dbi->selectDb($db); + $columns = (array) $this->dbi->getColumnNames( + $db, + $table + ); + if (count($pk) == count($columns)) { + $headText = sprintf( + __( + 'No partial dependencies possible as ' + . 'no non-primary column exists since primary key ( %1$s ) ' + . 'is composed of all the columns in the table.' + ), + htmlspecialchars($key) + ) . '
      '; + $extra = '

      ' . __('Table is already in second normal form.') + . '

      '; + } else { + $headText = sprintf( + __( + 'The primary key ( %1$s ) consists of more than one column ' + . 'so we need to find the partial dependencies.' + ), + htmlspecialchars($key) + ) . '
      ' . __( + 'Please answer the following question(s) ' + . 'carefully to obtain a correct normalization.' + ) + . '
      ' . __( + '+ Show me the possible partial dependencies ' + . 'based on data in the table' + ) . ''; + $subText = __( + 'For each column below, ' + . 'please select the minimal set of columns among given set ' + . 'whose values combined together are sufficient' + . ' to determine the value of the column.' + ); + $cnt = 0; + foreach ($columns as $column) { + if (!in_array($column, $pk)) { + $cnt++; + $extra .= "" . sprintf( + __('\'%1$s\' depends on:'), + htmlspecialchars($column) + ) . "
      "; + $extra .= '
      ' + . $selectPkForm . '


      '; + } + } + } + } else { + $headText = sprintf( + __( + 'No partial dependencies possible as the primary key' + . ' ( %1$s ) has just one column.' + ), + htmlspecialchars($key) + ) . '
      '; + $extra = '

      ' . __('Table is already in second normal form.') . '

      '; + } + $res = [ + 'legendText' => $legendText, + 'headText' => $headText, + 'subText' => $subText, + 'extra' => $extra, + 'primary_key' => $key + ]; + return $res; + } + + /** + * build the html for showing the tables to have in order to put current table in 2NF + * + * @param array $partialDependencies array containing all the dependencies + * @param string $table current table + * + * @return string HTML + */ + public function getHtmlForNewTables2NF(array $partialDependencies, $table) + { + $html = '

      ' . sprintf( + __( + 'In order to put the ' + . 'original table \'%1$s\' into Second normal form we need ' + . 'to create the following tables:' + ), + htmlspecialchars($table) + ) . '

      '; + $tableName = $table; + $i = 1; + foreach ($partialDependencies as $key => $dependents) { + $html .= '

      ' + . '( ' . htmlspecialchars($key) . '' + . (count($dependents)>0?', ':'') + . htmlspecialchars(implode(', ', $dependents)) . ' )'; + $i++; + $tableName = 'table' . $i; + } + return $html; + } + + /** + * create/alter the tables needed for 2NF + * + * @param array $partialDependencies array containing all the partial dependencies + * @param object $tablesName name of new tables + * @param string $table current table + * @param string $db current database + * + * @return array + */ + public function createNewTablesFor2NF(array $partialDependencies, $tablesName, $table, $db) + { + $dropCols = false; + $nonPKCols = []; + $queries = []; + $error = false; + $headText = '

      ' . sprintf( + __('The second step of normalization is complete for table \'%1$s\'.'), + htmlspecialchars($table) + ) . '

      '; + if (count((array)$partialDependencies) == 1) { + return [ + 'legendText'=>__('End of step'), 'headText'=>$headText, + 'queryError'=>$error + ]; + } + $message = ''; + $this->dbi->selectDb($db); + foreach ($partialDependencies as $key => $dependents) { + if ($tablesName->$key != $table) { + $backquotedKey = implode(', ', Util::backquote(explode(', ', $key))); + $queries[] = 'CREATE TABLE ' . Util::backquote($tablesName->$key) + . ' SELECT DISTINCT ' . $backquotedKey + . (count($dependents)>0?', ':'') + . implode(',', Util::backquote($dependents)) + . ' FROM ' . Util::backquote($table) . ';'; + $queries[] = 'ALTER TABLE ' . Util::backquote($tablesName->$key) + . ' ADD PRIMARY KEY(' . $backquotedKey . ');'; + $nonPKCols = array_merge($nonPKCols, $dependents); + } else { + $dropCols = true; + } + } + + if ($dropCols) { + $query = 'ALTER TABLE ' . Util::backquote($table); + foreach ($nonPKCols as $col) { + $query .= ' DROP ' . Util::backquote($col) . ','; + } + $query = trim($query, ', '); + $query .= ';'; + $queries[] = $query; + } else { + $queries[] = 'DROP TABLE ' . Util::backquote($table); + } + foreach ($queries as $query) { + if (!$this->dbi->tryQuery($query)) { + $message = Message::error(__('Error in processing!')); + $message->addMessage( + Message::rawError( + $this->dbi->getError() + ), + '

      ' + ); + $error = true; + break; + } + } + return [ + 'legendText' => __('End of step'), + 'headText' => $headText, + 'queryError' => $error, + 'extra' => $message + ]; + } + + /** + * build the html for showing the new tables to have in order + * to put given tables in 3NF + * + * @param object $dependencies containing all the dependencies + * @param array $tables tables formed after 2NF and need to convert to 3NF + * @param string $db current database + * + * @return array containing html and the list of new tables + */ + public function getHtmlForNewTables3NF($dependencies, array $tables, $db) + { + $html = ""; + $i = 1; + $newTables = []; + foreach ($tables as $table => $arrDependson) { + if (count(array_unique($arrDependson)) == 1) { + continue; + } + $primary = Index::getPrimary($table, $db); + $primarycols = $primary->getColumns(); + $pk = []; + foreach ($primarycols as $col) { + $pk[] = $col->getName(); + } + $html .= '

      ' . sprintf( + __( + 'In order to put the ' + . 'original table \'%1$s\' into Third normal form we need ' + . 'to create the following tables:' + ), + htmlspecialchars($table) + ) . '

      '; + $tableName = $table; + $columnList = []; + foreach ($arrDependson as $key) { + $dependents = $dependencies->$key; + if ($key == $table) { + $key = implode(', ', $pk); + } + $tmpTableCols =array_merge(explode(', ', $key), $dependents); + sort($tmpTableCols); + if (!in_array($tmpTableCols, $columnList)) { + $columnList[] = $tmpTableCols; + $html .= '

      ' + . '( ' . htmlspecialchars($key) . '' + . (count($dependents)>0?', ':'') + . htmlspecialchars(implode(', ', $dependents)) . ' )'; + $newTables[$table][$tableName] = [ + "pk"=>$key, "nonpk"=>implode(', ', $dependents) + ]; + $i++; + $tableName = 'table' . $i; + } + } + } + return ['html' => $html, 'newTables' => $newTables, 'success' => true]; + } + + /** + * create new tables or alter existing to get 3NF + * + * @param array $newTables list of new tables to be created + * @param string $db current database + * + * @return array + */ + public function createNewTablesFor3NF(array $newTables, $db) + { + $queries = []; + $dropCols = false; + $error = false; + $headText = '

      ' . + __('The third step of normalization is complete.') + . '

      '; + if (count((array)$newTables) == 0) { + return [ + 'legendText'=>__('End of step'), 'headText'=>$headText, + 'queryError'=>$error + ]; + } + $message = ''; + $this->dbi->selectDb($db); + foreach ($newTables as $originalTable => $tablesList) { + foreach ($tablesList as $table => $cols) { + if ($table != $originalTable) { + $quotedPk = implode( + ', ', + Util::backquote(explode(', ', $cols->pk)) + ); + $quotedNonpk = implode( + ', ', + Util::backquote(explode(', ', $cols->nonpk)) + ); + $queries[] = 'CREATE TABLE ' . Util::backquote($table) + . ' SELECT DISTINCT ' . $quotedPk + . ', ' . $quotedNonpk + . ' FROM ' . Util::backquote($originalTable) . ';'; + $queries[] = 'ALTER TABLE ' . Util::backquote($table) + . ' ADD PRIMARY KEY(' . $quotedPk . ');'; + } else { + $dropCols = $cols; + } + } + if ($dropCols) { + $columns = (array) $this->dbi->getColumnNames( + $db, + $originalTable + ); + $colPresent = array_merge( + explode(', ', $dropCols->pk), + explode(', ', $dropCols->nonpk) + ); + $query = 'ALTER TABLE ' . Util::backquote($originalTable); + foreach ($columns as $col) { + if (!in_array($col, $colPresent)) { + $query .= ' DROP ' . Util::backquote($col) . ','; + } + } + $query = trim($query, ', '); + $query .= ';'; + $queries[] = $query; + } else { + $queries[] = 'DROP TABLE ' . Util::backquote($originalTable); + } + $dropCols = false; + } + foreach ($queries as $query) { + if (!$this->dbi->tryQuery($query)) { + $message = Message::error(__('Error in processing!')); + $message->addMessage( + Message::rawError( + $this->dbi->getError() + ), + '

      ' + ); + $error = true; + break; + } + } + return [ + 'legendText' => __('End of step'), + 'headText' => $headText, + 'queryError' => $error, + 'extra' => $message + ]; + } + + /** + * move the repeating group of columns to a new table + * + * @param string $repeatingColumns comma separated list of repeating group columns + * @param string $primaryColumns comma separated list of column in primary key + * of $table + * @param string $newTable name of the new table to be created + * @param string $newColumn name of the new column in the new table + * @param string $table current table + * @param string $db current database + * + * @return array + */ + public function moveRepeatingGroup( + $repeatingColumns, + $primaryColumns, + $newTable, + $newColumn, + $table, + $db + ) { + $repeatingColumnsArr = (array)Util::backquote( + explode(', ', $repeatingColumns) + ); + $primaryColumns = implode( + ',', + Util::backquote(explode(',', $primaryColumns)) + ); + $query1 = 'CREATE TABLE ' . Util::backquote($newTable); + $query2 = 'ALTER TABLE ' . Util::backquote($table); + $message = Message::success( + sprintf( + __('Selected repeating group has been moved to the table \'%s\''), + htmlspecialchars($table) + ) + ); + $first = true; + $error = false; + foreach ($repeatingColumnsArr as $repeatingColumn) { + if (!$first) { + $query1 .= ' UNION '; + } + $first = false; + $query1 .= ' SELECT ' . $primaryColumns . ',' . $repeatingColumn + . ' as ' . Util::backquote($newColumn) + . ' FROM ' . Util::backquote($table); + $query2 .= ' DROP ' . $repeatingColumn . ','; + } + $query2 = trim($query2, ','); + $queries = [$query1, $query2]; + $this->dbi->selectDb($db); + foreach ($queries as $query) { + if (!$this->dbi->tryQuery($query)) { + $message = Message::error(__('Error in processing!')); + $message->addMessage( + Message::rawError( + $this->dbi->getError() + ), + '

      ' + ); + $error = true; + break; + } + } + return [ + 'queryError' => $error, 'message' => $message + ]; + } + + /** + * build html for 3NF step 1 to find the transitive dependencies + * + * @param string $db current database + * @param array $tables tables formed after 2NF and need to process for 3NF + * + * @return string + */ + public function getHtmlFor3NFstep1($db, array $tables) + { + $legendText = __('Step 3.') . "1 " . __('Find transitive dependencies'); + $extra = ""; + $headText = __( + 'Please answer the following question(s) ' + . 'carefully to obtain a correct normalization.' + ); + $subText = __( + 'For each column below, ' + . 'please select the minimal set of columns among given set ' + . 'whose values combined together are sufficient' + . ' to determine the value of the column.
      ' + . 'Note: A column may have no transitive dependency, ' + . 'in that case you don\'t have to select any.' + ); + $cnt = 0; + foreach ($tables as $table) { + $primary = Index::getPrimary($table, $db); + $primarycols = $primary->getColumns(); + $selectTdForm = ""; + $pk = []; + foreach ($primarycols as $col) { + $pk[] = $col->getName(); + } + $this->dbi->selectDb($db); + $columns = (array) $this->dbi->getColumnNames( + $db, + $table + ); + if (count($columns) - count($pk) <= 1) { + continue; + } + foreach ($columns as $column) { + if (!in_array($column, $pk)) { + $selectTdForm .= '' + . '' . htmlspecialchars($column) . ''; + } + } + foreach ($columns as $column) { + if (!in_array($column, $pk)) { + $cnt++; + $extra .= "" . sprintf( + __('\'%1$s\' depends on:'), + htmlspecialchars($column) + ) + . "
      "; + $extra .= '
      ' + . $selectTdForm + . '


      '; + } + } + } + if ($extra == "") { + $headText = __( + "No Transitive dependencies possible as the table " + . "doesn't have any non primary key columns" + ); + $subText = ""; + $extra = "

      " . __("Table is already in Third normal form!") . "

      "; + } + $res = [ + 'legendText' => $legendText, + 'headText' => $headText, + 'subText' => $subText, + 'extra' => $extra + ]; + return $res; + } + + /** + * get html for options to normalize table + * + * @return string HTML + */ + public function getHtmlForNormalizeTable() + { + $htmlOutput = '
      ' + . Url::getHiddenInputs($GLOBALS['db'], $GLOBALS['table']) + . ''; + $htmlOutput .= '
      '; + $htmlOutput .= '' + . __('Improve table structure (Normalization):') . ''; + $htmlOutput .= '

      ' . __('Select up to what step you want to normalize') + . '

      '; + $choices = [ + '1nf' => __('First step of normalization (1NF)'), + '2nf' => __('Second step of normalization (1NF+2NF)'), + '3nf' => __('Third step of normalization (1NF+2NF+3NF)')]; + + $htmlOutput .= Util::getRadioFields( + 'normalizeTo', + $choices, + '1nf', + true + ); + $htmlOutput .= '
      ' + . "" . __( + 'Hint: Please follow the procedure carefully in order ' + . 'to obtain correct normalization' + ) . "" + . '' + . '
      ' + . '
      ' + . '
      '; + + return $htmlOutput; + } + + /** + * find all the possible partial dependencies based on data in the table. + * + * @param string $table current table + * @param string $db current database + * + * @return string HTML containing the list of all the possible partial dependencies + */ + public function findPartialDependencies($table, $db) + { + $dependencyList = []; + $this->dbi->selectDb($db); + $columns = (array) $this->dbi->getColumnNames( + $db, + $table + ); + $columns = (array)Util::backquote($columns); + $totalRowsRes = $this->dbi->fetchResult( + 'SELECT COUNT(*) FROM (SELECT * FROM ' + . Util::backquote($table) . ' LIMIT 500) as dt;' + ); + $totalRows = $totalRowsRes[0]; + $primary = Index::getPrimary($table, $db); + $primarycols = $primary->getColumns(); + $pk = []; + foreach ($primarycols as $col) { + $pk[] = Util::backquote($col->getName()); + } + $partialKeys = $this->getAllCombinationPartialKeys($pk); + $distinctValCount = $this->findDistinctValuesCount( + array_unique( + array_merge($columns, $partialKeys) + ), + $table + ); + foreach ($columns as $column) { + if (!in_array($column, $pk)) { + foreach ($partialKeys as $partialKey) { + if ($partialKey + && $this->checkPartialDependency( + $partialKey, + $column, + $table, + $distinctValCount[$partialKey], + $distinctValCount[$column], + $totalRows + ) + ) { + $dependencyList[$partialKey][] = $column; + } + } + } + } + + $html = __( + 'This list is based on a subset of the table\'s data ' + . 'and is not necessarily accurate. ' + ) + . '
      '; + foreach ($dependencyList as $dependon => $colList) { + $html .= '' + . '' + . '' + . htmlspecialchars(str_replace('`', '', $dependon)) . ' -> ' + . '' + . htmlspecialchars(str_replace('`', '', implode(', ', $colList))) + . '' + . ''; + } + if (empty($dependencyList)) { + $html .= '

      ' + . __('No partial dependencies found!') . '

      '; + } + $html .= '
      '; + return $html; + } + + /** + * check whether a particular column is dependent on given subset of primary key + * + * @param string $partialKey the partial key, subset of primary key, + * each column in key supposed to be backquoted + * @param string $column backquoted column on whose dependency being checked + * @param string $table current table + * @param integer $pkCnt distinct value count for given partial key + * @param integer $colCnt distinct value count for given column + * @param integer $totalRows total distinct rows count of the table + * + * @return boolean TRUE if $column is dependent on $partialKey, False otherwise + */ + private function checkPartialDependency( + $partialKey, + $column, + $table, + $pkCnt, + $colCnt, + $totalRows + ) { + $query = 'SELECT ' + . 'COUNT(DISTINCT ' . $partialKey . ',' . $column . ') as pkColCnt ' + . 'FROM (SELECT * FROM ' . Util::backquote($table) + . ' LIMIT 500) as dt' . ';'; + $res = $this->dbi->fetchResult($query, null, null); + $pkColCnt = $res[0]; + if ($pkCnt && $pkCnt == $colCnt && $colCnt == $pkColCnt) { + return true; + } + if ($totalRows && $totalRows == $pkCnt) { + return true; + } + return false; + } + + /** + * function to get distinct values count of all the column in the array $columns + * + * @param array $columns array of backquoted columns whose distinct values + * need to be counted. + * @param string $table table to which these columns belong + * + * @return array associative array containing the count + */ + private function findDistinctValuesCount(array $columns, $table) + { + $result = []; + $query = 'SELECT '; + foreach ($columns as $column) { + if ($column) { //each column is already backquoted + $query .= 'COUNT(DISTINCT ' . $column . ') as \'' + . $column . '_cnt\', '; + } + } + $query = trim($query, ', '); + $query .= ' FROM (SELECT * FROM ' . Util::backquote($table) + . ' LIMIT 500) as dt' . ';'; + $res = $this->dbi->fetchResult($query, null, null); + foreach ($columns as $column) { + if ($column) { + $result[$column] = $res[0][$column . '_cnt']; + } + } + return $result; + } + + /** + * find all the possible partial keys + * + * @param array $primaryKey array containing all the column present in primary key + * + * @return array containing all the possible partial keys(subset of primary key) + */ + private function getAllCombinationPartialKeys(array $primaryKey) + { + $results = ['']; + foreach ($primaryKey as $element) { + foreach ($results as $combination) { + array_push( + $results, + trim($element . ',' . $combination, ',') + ); + } + } + array_pop($results); //remove key which consist of all primary key columns + return $results; + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/OpenDocument.php b/php/apps/phpmyadmin49/libraries/classes/OpenDocument.php new file mode 100644 index 00000000..77104683 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/OpenDocument.php @@ -0,0 +1,177 @@ +' + . '' + . '' + . 'phpMyAdmin ' . PMA_VERSION . '' + . 'phpMyAdmin ' . PMA_VERSION + . '' + . '' . strftime('%Y-%m-%dT%H:%M:%S') + . '' + . '' + . '', + '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '', + '' + . '' + . '' + . '' + . '' + . '' + . '' + ); + + $name = array( + 'mimetype', + 'content.xml', + 'meta.xml', + 'styles.xml', + 'META-INF/manifest.xml' + ); + + $zipExtension = new ZipExtension(); + return $zipExtension->createFile($data, $name); + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Operations.php b/php/apps/phpmyadmin49/libraries/classes/Operations.php new file mode 100644 index 00000000..1cbd8e6a --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Operations.php @@ -0,0 +1,2158 @@ +relation = new Relation(); + } + + /** + * Get HTML output for database comment + * + * @param string $db database name + * + * @return string $html_output + */ + public function getHtmlForDatabaseComment($db) + { + $html_output = '
      ' + . '
      ' + . Url::getHiddenInputs($db) + . '
      ' + . ''; + if (Util::showIcons('ActionLinksMode')) { + $html_output .= Util::getImage('b_comment') . ' '; + } + $html_output .= __('Database comment'); + $html_output .= ''; + $html_output .= '' + . '
      '; + $html_output .= '
      ' + . '' + . '
      ' + . '
      ' + . '
      '; + + return $html_output; + } + + /** + * Get HTML output for rename database + * + * @param string $db database name + * @param string $db_collation dataset collation + * + * @return string $html_output + */ + public function getHtmlForRenameDatabase($db, $db_collation) + { + $html_output = '
      ' + . '
      '; + if (isset($db_collation)) { + $html_output .= '' . "\n"; + } + $html_output .= '' + . '' + . Url::getHiddenInputs($db) + . '
      ' + . ''; + + if (Util::showIcons('ActionLinksMode')) { + $html_output .= Util::getImage('b_edit') . ' '; + } + $html_output .= __('Rename database to') + . ''; + + $html_output .= ''; + $html_output .= '
      '; + + if ($GLOBALS['db_priv'] && $GLOBALS['table_priv'] + && $GLOBALS['col_priv'] && $GLOBALS['proc_priv'] + && $GLOBALS['is_reload_priv'] + ) { + $html_output .= ''; + } else { + $html_output .= ''; + } + + $html_output .= '
      '; + + $html_output .= '' + . '
      ' + . '
      ' + . '' + . '
      ' + . '
      ' + . '
      '; + + return $html_output; + } + + /** + * Get HTML for database drop link + * + * @param string $db database name + * + * @return string $html_output + */ + public function getHtmlForDropDatabaseLink($db) + { + $this_sql_query = 'DROP DATABASE ' . Util::backquote($db); + $this_url_params = array( + 'sql_query' => $this_sql_query, + 'back' => 'db_operations.php', + 'goto' => 'index.php', + 'reload' => '1', + 'purge' => '1', + 'message_to_show' => sprintf( + __('Database %s has been dropped.'), + htmlspecialchars(Util::backquote($db)) + ), + 'db' => null, + ); + + $html_output = '
      ' + . '
      '; + $html_output .= ''; + if (Util::showIcons('ActionLinksMode')) { + $html_output .= Util::getImage('b_deltbl') . ' '; + } + $html_output .= __('Remove database') + . ''; + $html_output .= '
        '; + $html_output .= $this->getDeleteDataOrTablelink( + $this_url_params, + 'DROP_DATABASE', + __('Drop the database (DROP)'), + 'drop_db_anchor' + ); + $html_output .= '
      ' + . '
      '; + + return $html_output; + } + + /** + * Get HTML snippet for copy database + * + * @param string $db database name + * @param string $db_collation dataset collation + * + * @return string $html_output + */ + public function getHtmlForCopyDatabase($db, $db_collation) + { + $drop_clause = 'DROP TABLE / DROP VIEW'; + $choices = array( + 'structure' => __('Structure only'), + 'data' => __('Structure and data'), + 'dataonly' => __('Data only') + ); + + $pma_switch_to_new = isset($_SESSION['pma_switch_to_new']) && $_SESSION['pma_switch_to_new']; + + $html_output = '
      '; + $html_output .= '
      '; + + if (isset($db_collation)) { + $html_output .= '' . "\n"; + } + $html_output .= '' . "\n" + . Url::getHiddenInputs($db); + $html_output .= '
      ' + . ''; + + if (Util::showIcons('ActionLinksMode')) { + $html_output .= Util::getImage('b_edit') . ' '; + } + $html_output .= __('Copy database to') + . '' + . '
      ' + . Util::getRadioFields( + 'what', $choices, 'data', true + ); + $html_output .= '
      '; + $html_output .= ''; + $html_output .= '
      '; + $html_output .= ''; + $html_output .= '
      '; + $html_output .= ''; + $html_output .= '
      '; + $html_output .= ''; + $html_output .= '
      '; + $html_output .= '
      '; + + if ($GLOBALS['db_priv'] && $GLOBALS['table_priv'] + && $GLOBALS['col_priv'] && $GLOBALS['proc_priv'] + && $GLOBALS['is_reload_priv'] + ) { + $html_output .= ''; + } else { + $html_output .= ''; + } + $html_output .= '
      '; + + $html_output .= ''; + $html_output .= '' + . '
      '; + $html_output .= '
      ' + . '' + . '
      ' + . '
      ' + . '
      '; + + return $html_output; + } + + /** + * Get HTML snippet for change database charset + * + * @param string $db database name + * @param string $db_collation dataset collation + * + * @return string $html_output + */ + public function getHtmlForChangeDatabaseCharset($db, $db_collation) + { + $html_output = '
      ' + . '
      '; + if (Util::showIcons('ActionLinksMode')) { + $html_output .= Util::getImage('s_asci') . ' '; + } + $html_output .= '' . "\n" + . '' . "\n" + . Charsets::getCollationDropdownBox( + $GLOBALS['dbi'], + $GLOBALS['cfg']['Server']['DisableIS'], + 'db_collation', + 'select_db_collation', + isset($db_collation) ? $db_collation : '', + false + ) + . '
      ' + . '' + . '' + . '
      ' + . '' + . '' + . '' + . '
      ' + . '' . "\n" + . '
      ' . "\n" + . '
      ' . "\n"; + + return $html_output; + } + + /** + * Run the Procedure definitions and function definitions + * + * to avoid selecting alternatively the current and new db + * we would need to modify the CREATE definitions to qualify + * the db name + * + * @param string $db database name + * + * @return void + */ + public function runProcedureAndFunctionDefinitions($db) + { + $procedure_names = $GLOBALS['dbi']->getProceduresOrFunctions($db, 'PROCEDURE'); + if ($procedure_names) { + foreach ($procedure_names as $procedure_name) { + $GLOBALS['dbi']->selectDb($db); + $tmp_query = $GLOBALS['dbi']->getDefinition( + $db, 'PROCEDURE', $procedure_name + ); + if ($tmp_query !== false) { + // collect for later display + $GLOBALS['sql_query'] .= "\n" . $tmp_query; + $GLOBALS['dbi']->selectDb($_POST['newname']); + $GLOBALS['dbi']->query($tmp_query); + } + } + } + + $function_names = $GLOBALS['dbi']->getProceduresOrFunctions($db, 'FUNCTION'); + if ($function_names) { + foreach ($function_names as $function_name) { + $GLOBALS['dbi']->selectDb($db); + $tmp_query = $GLOBALS['dbi']->getDefinition( + $db, 'FUNCTION', $function_name + ); + if ($tmp_query !== false) { + // collect for later display + $GLOBALS['sql_query'] .= "\n" . $tmp_query; + $GLOBALS['dbi']->selectDb($_POST['newname']); + $GLOBALS['dbi']->query($tmp_query); + } + } + } + } + + /** + * Create database before copy + * + * @return void + */ + public function createDbBeforeCopy() + { + $local_query = 'CREATE DATABASE IF NOT EXISTS ' + . Util::backquote($_POST['newname']); + if (isset($_POST['db_collation'])) { + $local_query .= ' DEFAULT' + . Util::getCharsetQueryPart($_POST['db_collation']); + } + $local_query .= ';'; + $GLOBALS['sql_query'] .= $local_query; + + // save the original db name because Tracker.php which + // may be called under $GLOBALS['dbi']->query() changes $GLOBALS['db'] + // for some statements, one of which being CREATE DATABASE + $original_db = $GLOBALS['db']; + $GLOBALS['dbi']->query($local_query); + $GLOBALS['db'] = $original_db; + + // Set the SQL mode to NO_AUTO_VALUE_ON_ZERO to prevent MySQL from creating + // export statements it cannot import + $sql_set_mode = "SET SQL_MODE='NO_AUTO_VALUE_ON_ZERO'"; + $GLOBALS['dbi']->query($sql_set_mode); + + // rebuild the database list because Table::moveCopy + // checks in this list if the target db exists + $GLOBALS['dblist']->databases->build(); + } + + /** + * Get views as an array and create SQL view stand-in + * + * @param array $tables_full array of all tables in given db or dbs + * @param ExportSql $export_sql_plugin export plugin instance + * @param string $db database name + * + * @return array $views + */ + public function getViewsAndCreateSqlViewStandIn( + array $tables_full, $export_sql_plugin, $db + ) { + $views = array(); + foreach ($tables_full as $each_table => $tmp) { + // to be able to rename a db containing views, + // first all the views are collected and a stand-in is created + // the real views are created after the tables + if ($GLOBALS['dbi']->getTable($db, $each_table)->isView()) { + + // If view exists, and 'add drop view' is selected: Drop it! + if ($_POST['what'] != 'nocopy' + && isset($_POST['drop_if_exists']) + && $_POST['drop_if_exists'] == 'true' + ) { + $drop_query = 'DROP VIEW IF EXISTS ' + . Util::backquote($_POST['newname']) . '.' + . Util::backquote($each_table); + $GLOBALS['dbi']->query($drop_query); + + $GLOBALS['sql_query'] .= "\n" . $drop_query . ';'; + } + + $views[] = $each_table; + // Create stand-in definition to resolve view dependencies + $sql_view_standin = $export_sql_plugin->getTableDefStandIn( + $db, $each_table, "\n" + ); + $GLOBALS['dbi']->selectDb($_POST['newname']); + $GLOBALS['dbi']->query($sql_view_standin); + $GLOBALS['sql_query'] .= "\n" . $sql_view_standin; + } + } + return $views; + } + + /** + * Get sql query for copy/rename table and boolean for whether copy/rename or not + * + * @param array $tables_full array of all tables in given db or dbs + * @param boolean $move whether database name is empty or not + * @param string $db database name + * + * @return array SQL queries for the constraints + */ + public function copyTables(array $tables_full, $move, $db) + { + $sqlContraints = array(); + foreach ($tables_full as $each_table => $tmp) { + // skip the views; we have created stand-in definitions + if ($GLOBALS['dbi']->getTable($db, $each_table)->isView()) { + continue; + } + + // value of $what for this table only + $this_what = $_POST['what']; + + // do not copy the data from a Merge table + // note: on the calling FORM, 'data' means 'structure and data' + if ($GLOBALS['dbi']->getTable($db, $each_table)->isMerge()) { + if ($this_what == 'data') { + $this_what = 'structure'; + } + if ($this_what == 'dataonly') { + $this_what = 'nocopy'; + } + } + + if ($this_what != 'nocopy') { + // keep the triggers from the original db+table + // (third param is empty because delimiters are only intended + // for importing via the mysql client or our Import feature) + $triggers = $GLOBALS['dbi']->getTriggers($db, $each_table, ''); + + if (! Table::moveCopy( + $db, $each_table, $_POST['newname'], $each_table, + (isset($this_what) ? $this_what : 'data'), + $move, 'db_copy' + )) { + $GLOBALS['_error'] = true; + break; + } + // apply the triggers to the destination db+table + if ($triggers) { + $GLOBALS['dbi']->selectDb($_POST['newname']); + foreach ($triggers as $trigger) { + $GLOBALS['dbi']->query($trigger['create']); + $GLOBALS['sql_query'] .= "\n" . $trigger['create'] . ';'; + } + } + + // this does not apply to a rename operation + if (isset($_POST['add_constraints']) + && ! empty($GLOBALS['sql_constraints_query']) + ) { + $sqlContraints[] = $GLOBALS['sql_constraints_query']; + unset($GLOBALS['sql_constraints_query']); + } + } + } + return $sqlContraints; + } + + /** + * Run the EVENT definition for selected database + * + * to avoid selecting alternatively the current and new db + * we would need to modify the CREATE definitions to qualify + * the db name + * + * @param string $db database name + * + * @return void + */ + public function runEventDefinitionsForDb($db) + { + $event_names = $GLOBALS['dbi']->fetchResult( + 'SELECT EVENT_NAME FROM information_schema.EVENTS WHERE EVENT_SCHEMA= \'' + . $GLOBALS['dbi']->escapeString($db) . '\';' + ); + if ($event_names) { + foreach ($event_names as $event_name) { + $GLOBALS['dbi']->selectDb($db); + $tmp_query = $GLOBALS['dbi']->getDefinition($db, 'EVENT', $event_name); + // collect for later display + $GLOBALS['sql_query'] .= "\n" . $tmp_query; + $GLOBALS['dbi']->selectDb($_POST['newname']); + $GLOBALS['dbi']->query($tmp_query); + } + } + } + + /** + * Handle the views, return the boolean value whether table rename/copy or not + * + * @param array $views views as an array + * @param boolean $move whether database name is empty or not + * @param string $db database name + * + * @return void + */ + public function handleTheViews(array $views, $move, $db) + { + // temporarily force to add DROP IF EXIST to CREATE VIEW query, + // to remove stand-in VIEW that was created earlier + // ( $_POST['drop_if_exists'] is used in moveCopy() ) + if (isset($_POST['drop_if_exists'])) { + $temp_drop_if_exists = $_POST['drop_if_exists']; + } + + $_POST['drop_if_exists'] = 'true'; + foreach ($views as $view) { + $copying_succeeded = Table::moveCopy( + $db, $view, $_POST['newname'], $view, 'structure', $move, 'db_copy' + ); + if (! $copying_succeeded) { + $GLOBALS['_error'] = true; + break; + } + } + unset($_POST['drop_if_exists']); + + if (isset($temp_drop_if_exists)) { + // restore previous value + $_POST['drop_if_exists'] = $temp_drop_if_exists; + } + } + + /** + * Adjust the privileges after Renaming the db + * + * @param string $oldDb Database name before renaming + * @param string $newname New Database name requested + * + * @return void + */ + public function adjustPrivilegesMoveDb($oldDb, $newname) + { + if ($GLOBALS['db_priv'] && $GLOBALS['table_priv'] + && $GLOBALS['col_priv'] && $GLOBALS['proc_priv'] + && $GLOBALS['is_reload_priv'] + ) { + $GLOBALS['dbi']->selectDb('mysql'); + $newname = str_replace("_", "\_", $newname); + $oldDb = str_replace("_", "\_", $oldDb); + + // For Db specific privileges + $query_db_specific = 'UPDATE ' . Util::backquote('db') + . 'SET Db = \'' . $GLOBALS['dbi']->escapeString($newname) + . '\' where Db = \'' . $GLOBALS['dbi']->escapeString($oldDb) . '\';'; + $GLOBALS['dbi']->query($query_db_specific); + + // For table specific privileges + $query_table_specific = 'UPDATE ' . Util::backquote('tables_priv') + . 'SET Db = \'' . $GLOBALS['dbi']->escapeString($newname) + . '\' where Db = \'' . $GLOBALS['dbi']->escapeString($oldDb) . '\';'; + $GLOBALS['dbi']->query($query_table_specific); + + // For column specific privileges + $query_col_specific = 'UPDATE ' . Util::backquote('columns_priv') + . 'SET Db = \'' . $GLOBALS['dbi']->escapeString($newname) + . '\' where Db = \'' . $GLOBALS['dbi']->escapeString($oldDb) . '\';'; + $GLOBALS['dbi']->query($query_col_specific); + + // For procedures specific privileges + $query_proc_specific = 'UPDATE ' . Util::backquote('procs_priv') + . 'SET Db = \'' . $GLOBALS['dbi']->escapeString($newname) + . '\' where Db = \'' . $GLOBALS['dbi']->escapeString($oldDb) . '\';'; + $GLOBALS['dbi']->query($query_proc_specific); + + // Finally FLUSH the new privileges + $flush_query = "FLUSH PRIVILEGES;"; + $GLOBALS['dbi']->query($flush_query); + } + } + + /** + * Adjust the privileges after Copying the db + * + * @param string $oldDb Database name before copying + * @param string $newname New Database name requested + * + * @return void + */ + public function adjustPrivilegesCopyDb($oldDb, $newname) + { + if ($GLOBALS['db_priv'] && $GLOBALS['table_priv'] + && $GLOBALS['col_priv'] && $GLOBALS['proc_priv'] + && $GLOBALS['is_reload_priv'] + ) { + $GLOBALS['dbi']->selectDb('mysql'); + $newname = str_replace("_", "\_", $newname); + $oldDb = str_replace("_", "\_", $oldDb); + + $query_db_specific_old = 'SELECT * FROM ' + . Util::backquote('db') . ' WHERE ' + . 'Db = "' . $oldDb . '";'; + + $old_privs_db = $GLOBALS['dbi']->fetchResult($query_db_specific_old, 0); + + foreach ($old_privs_db as $old_priv) { + $newDb_db_privs_query = 'INSERT INTO ' . Util::backquote('db') + . ' VALUES("' . $old_priv[0] . '", "' . $newname . '", "' + . $old_priv[2] . '", "' . $old_priv[3] . '", "' . $old_priv[4] + . '", "' . $old_priv[5] . '", "' . $old_priv[6] . '", "' + . $old_priv[7] . '", "' . $old_priv[8] . '", "' . $old_priv[9] + . '", "' . $old_priv[10] . '", "' . $old_priv[11] . '", "' + . $old_priv[12] . '", "' . $old_priv[13] . '", "' . $old_priv[14] + . '", "' . $old_priv[15] . '", "' . $old_priv[16] . '", "' + . $old_priv[17] . '", "' . $old_priv[18] . '", "' . $old_priv[19] + . '", "' . $old_priv[20] . '", "' . $old_priv[21] . '");'; + + $GLOBALS['dbi']->query($newDb_db_privs_query); + } + + // For Table Specific privileges + $query_table_specific_old = 'SELECT * FROM ' + . Util::backquote('tables_priv') . ' WHERE ' + . 'Db = "' . $oldDb . '";'; + + $old_privs_table = $GLOBALS['dbi']->fetchResult( + $query_table_specific_old, + 0 + ); + + foreach ($old_privs_table as $old_priv) { + $newDb_table_privs_query = 'INSERT INTO ' . Util::backquote( + 'tables_priv' + ) . ' VALUES("' . $old_priv[0] . '", "' . $newname . '", "' + . $old_priv[2] . '", "' . $old_priv[3] . '", "' . $old_priv[4] + . '", "' . $old_priv[5] . '", "' . $old_priv[6] . '", "' + . $old_priv[7] . '");'; + + $GLOBALS['dbi']->query($newDb_table_privs_query); + } + + // For Column Specific privileges + $query_col_specific_old = 'SELECT * FROM ' + . Util::backquote('columns_priv') . ' WHERE ' + . 'Db = "' . $oldDb . '";'; + + $old_privs_col = $GLOBALS['dbi']->fetchResult( + $query_col_specific_old, + 0 + ); + + foreach ($old_privs_col as $old_priv) { + $newDb_col_privs_query = 'INSERT INTO ' . Util::backquote( + 'columns_priv' + ) . ' VALUES("' . $old_priv[0] . '", "' . $newname . '", "' + . $old_priv[2] . '", "' . $old_priv[3] . '", "' . $old_priv[4] + . '", "' . $old_priv[5] . '", "' . $old_priv[6] . '");'; + + $GLOBALS['dbi']->query($newDb_col_privs_query); + } + + // For Procedure Specific privileges + $query_proc_specific_old = 'SELECT * FROM ' + . Util::backquote('procs_priv') . ' WHERE ' + . 'Db = "' . $oldDb . '";'; + + $old_privs_proc = $GLOBALS['dbi']->fetchResult( + $query_proc_specific_old, + 0 + ); + + foreach ($old_privs_proc as $old_priv) { + $newDb_proc_privs_query = 'INSERT INTO ' . Util::backquote( + 'procs_priv' + ) . ' VALUES("' . $old_priv[0] . '", "' . $newname . '", "' + . $old_priv[2] . '", "' . $old_priv[3] . '", "' . $old_priv[4] + . '", "' . $old_priv[5] . '", "' . $old_priv[6] . '", "' + . $old_priv[7] . '");'; + + $GLOBALS['dbi']->query($newDb_proc_privs_query); + } + + // Finally FLUSH the new privileges + $flush_query = "FLUSH PRIVILEGES;"; + $GLOBALS['dbi']->query($flush_query); + } + } + + /** + * Create all accumulated constraints + * + * @param array $sqlConstratints array of sql constraints for the database + * + * @return void + */ + public function createAllAccumulatedConstraints(array $sqlConstratints) + { + $GLOBALS['dbi']->selectDb($_POST['newname']); + foreach ($sqlConstratints as $one_query) { + $GLOBALS['dbi']->query($one_query); + // and prepare to display them + $GLOBALS['sql_query'] .= "\n" . $one_query; + } + } + + /** + * Duplicate the bookmarks for the db (done once for each db) + * + * @param boolean $_error whether table rename/copy or not + * @param string $db database name + * + * @return void + */ + public function duplicateBookmarks($_error, $db) + { + if (! $_error && $db != $_POST['newname']) { + $get_fields = array('user', 'label', 'query'); + $where_fields = array('dbase' => $db); + $new_fields = array('dbase' => $_POST['newname']); + Table::duplicateInfo( + 'bookmarkwork', 'bookmark', $get_fields, + $where_fields, $new_fields + ); + } + } + + /** + * Get the HTML snippet for order the table + * + * @param array $columns columns array + * + * @return string $html_out + */ + public function getHtmlForOrderTheTable(array $columns) + { + $html_output = '
      '; + $html_output .= '
      '; + $html_output .= Url::getHiddenInputs( + $GLOBALS['db'], $GLOBALS['table'] + ); + $html_output .= '
      ' + . '' . __('Alter table order by') . '' + . ' ' . __('(singly)') . ' ' + . '
      ' + . '' + . '' + . '' + . '' + . '
      ' + . '
      ' + . '' + . '' + . '
      ' + . '
      ' + . '
      '; + + return $html_output; + } + + /** + * Get the HTML snippet for move table + * + * @return string $html_output + */ + public function getHtmlForMoveTable() + { + $html_output = '
      '; + $html_output .= '
      ' + . Url::getHiddenInputs($GLOBALS['db'], $GLOBALS['table']); + + $html_output .= '' + . '' + . '
      '; + + $html_output .= '' . __('Move table to (database.table)') + . ''; + + if (count($GLOBALS['dblist']->databases) > $GLOBALS['cfg']['MaxDbList']) { + $html_output .= ''; + } else { + $html_output .= ''; + } + $html_output .= ' . '; + $html_output .= '
      '; + + // starting with MySQL 5.0.24, SHOW CREATE TABLE includes the AUTO_INCREMENT + // next value but users can decide if they want it or not for the operation + + $html_output .= '' + . '
      '; + + if ($GLOBALS['table_priv'] && $GLOBALS['col_priv'] + && $GLOBALS['is_reload_priv'] + ) { + $html_output .= ''; + } else { + $html_output .= ''; + } + $html_output .= '
      '; + + $html_output .= '
      ' + . '' + . '
      ' + . '
      ' + . '
      '; + + return $html_output; + } + + /** + * Get the HTML div for Table option + * + * @param Table $pma_table Table object + * @param string $comment Comment + * @param array $tbl_collation table collation + * @param string $tbl_storage_engine table storage engine + * @param string $pack_keys pack keys + * @param string $auto_increment value of auto increment + * @param string $delay_key_write delay key write + * @param string $transactional value of transactional + * @param string $page_checksum value of page checksum + * @param string $checksum the checksum + * + * @return string $html_output + */ + public function getTableOptionDiv($pma_table, $comment, $tbl_collation, $tbl_storage_engine, + $pack_keys, $auto_increment, $delay_key_write, + $transactional, $page_checksum, $checksum + ) { + $html_output = '
      '; + $html_output .= '
      getTableOptionFieldset( + $pma_table, $comment, $tbl_collation, + $tbl_storage_engine, $pack_keys, + $delay_key_write, $auto_increment, $transactional, $page_checksum, + $checksum + ); + + $html_output .= '
      ' + . '' + . '' + . '
      ' + . '
      ' + . '
      '; + + return $html_output; + } + + /** + * Get HTML for the rename table part of table options + * + * @return string $html_output + */ + private function getHtmlForRenameTable() + { + $html_output = '' . __('Rename table to') . '' + . '' + . '' + . '' + . ''; + + if ($GLOBALS['table_priv'] && $GLOBALS['col_priv'] + && $GLOBALS['is_reload_priv'] + ) { + $html_output .= ''; + } else { + $html_output .= ''; + } + $html_output .= ''; + + $html_output .= ''; + return $html_output; + } + + /** + * Get HTML for the table comments part of table options + * + * @param string $current_value of the table comments + * + * @return string $html_output + */ + private function getHtmlForTableComments($current_value) + { + $commentLength = $GLOBALS['dbi']->getVersion() >= 50503 ? 2048 : 60; + $html_output = '' . __('Table comments') . '' + . '' + . '' + . '' + . ''; + + return $html_output; + } + + /** + * Get HTML for the PACK KEYS part of table options + * + * @param string $current_value of the pack keys option + * + * @return string $html_output + */ + private function getHtmlForPackKeys($current_value) + { + $html_output = '' + . '' + . '' + . '' + . ''; + + if ($pma_table->isEngine(array('MYISAM', 'ARIA', 'ISAM'))) { + $html_output .= $this->getHtmlForPackKeys($pack_keys); + } // end if (MYISAM|ISAM) + + if ($pma_table->isEngine(array('MYISAM', 'ARIA'))) { + $html_output .= $this->getHtmlForTableRow( + 'new_checksum', + 'CHECKSUM', + $checksum + ); + + $html_output .= $this->getHtmlForTableRow( + 'new_delay_key_write', + 'DELAY_KEY_WRITE', + $delay_key_write + ); + } // end if (MYISAM) + + if ($pma_table->isEngine('ARIA')) { + $html_output .= $this->getHtmlForTableRow( + 'new_transactional', + 'TRANSACTIONAL', + $transactional + ); + + $html_output .= $this->getHtmlForTableRow( + 'new_page_checksum', + 'PAGE_CHECKSUM', + $page_checksum + ); + } // end if (ARIA) + + if (strlen($auto_increment) > 0 + && $pma_table->isEngine(array('MYISAM', 'ARIA', 'INNODB', 'PBXT', 'ROCKSDB')) + ) { + $html_output .= '' + . '' + . '' + . ' '; + } // end if (MYISAM|INNODB) + + $possible_row_formats = $this->getPossibleRowFormat(); + + // for MYISAM there is also COMPRESSED but it can be set only by the + // myisampack utility, so don't offer here the choice because if we + // try it inside an ALTER TABLE, MySQL (at least in 5.1.23-maria) + // does not return a warning + // (if the table was compressed, it can be seen on the Structure page) + + if (isset($possible_row_formats[$tbl_storage_engine])) { + $current_row_format + = mb_strtoupper($GLOBALS['showtable']['Row_format']); + $html_output .= '' + . '' + . ''; + $html_output .= Util::getDropdown( + 'new_row_format', $possible_row_formats[$tbl_storage_engine], + $current_row_format, 'new_row_format' + ); + $html_output .= ''; + } + $html_output .= '' + . ''; + + return $html_output; + } + + /** + * Get the common HTML table row (tr) for new_checksum, new_delay_key_write, + * new_transactional and new_page_checksum + * + * @param string $attribute class, name and id attribute + * @param string $label label value + * @param string $val checksum, delay_key_write, transactional, page_checksum + * + * @return string $html_output + */ + private function getHtmlForTableRow($attribute, $label, $val) + { + return '' + . '' + . '' + . '' + . '' + . '' + . '' + . ''; + } + + /** + * Get array of possible row formats + * + * @return array $possible_row_formats + */ + private function getPossibleRowFormat() + { + // the outer array is for engines, the inner array contains the dropdown + // option values as keys then the dropdown option labels + + $possible_row_formats = array( + 'ARCHIVE' => array( + 'COMPRESSED' => 'COMPRESSED', + ), + 'ARIA' => array( + 'FIXED' => 'FIXED', + 'DYNAMIC' => 'DYNAMIC', + 'PAGE' => 'PAGE' + ), + 'MARIA' => array( + 'FIXED' => 'FIXED', + 'DYNAMIC' => 'DYNAMIC', + 'PAGE' => 'PAGE' + ), + 'MYISAM' => array( + 'FIXED' => 'FIXED', + 'DYNAMIC' => 'DYNAMIC' + ), + 'PBXT' => array( + 'FIXED' => 'FIXED', + 'DYNAMIC' => 'DYNAMIC' + ), + 'INNODB' => array( + 'COMPACT' => 'COMPACT', + 'REDUNDANT' => 'REDUNDANT' + ) + ); + + /** @var Innodb $innodbEnginePlugin */ + $innodbEnginePlugin = StorageEngine::getEngine('Innodb'); + $innodbPluginVersion = $innodbEnginePlugin->getInnodbPluginVersion(); + if (!empty($innodbPluginVersion)) { + $innodb_file_format = $innodbEnginePlugin->getInnodbFileFormat(); + } else { + $innodb_file_format = ''; + } + /** + * Newer MySQL/MariaDB always return empty a.k.a '' on $innodb_file_format otherwise + * old versions of MySQL/MariaDB must be returning something or not empty. + * This patch is to support newer MySQL/MariaDB while also for backward compatibilities. + */ + if (( ('Barracuda' == $innodb_file_format) || ($innodb_file_format == '') ) + && $innodbEnginePlugin->supportsFilePerTable() + ) { + $possible_row_formats['INNODB']['DYNAMIC'] = 'DYNAMIC'; + $possible_row_formats['INNODB']['COMPRESSED'] = 'COMPRESSED'; + } + + return $possible_row_formats; + } + + /** + * Get HTML div for copy table + * + * @return string $html_output + */ + public function getHtmlForCopytable() + { + $html_output = '
      '; + $html_output .= '
      ' + . Url::getHiddenInputs($GLOBALS['db'], $GLOBALS['table']) + . ''; + + $html_output .= '
      '; + $html_output .= '' + . __('Copy table to (database.table)') . ''; + + if (count($GLOBALS['dblist']->databases) > $GLOBALS['cfg']['MaxDbList']) { + $html_output .= ''; + } else { + $html_output .= ''; + } + $html_output .= ' . '; + $html_output .= '
      '; + + $choices = array( + 'structure' => __('Structure only'), + 'data' => __('Structure and data'), + 'dataonly' => __('Data only') + ); + + $html_output .= Util::getRadioFields( + 'what', $choices, 'data', true + ); + $html_output .= '
      '; + + $html_output .= '' + . '
      ' + . '' + . '
      '; + + // display "Add constraints" choice only if there are + // foreign keys + if ($this->relation->getForeigners($GLOBALS['db'], $GLOBALS['table'], '', 'foreign')) { + $html_output .= ''; + $html_output .= '
      '; + } // endif + + $html_output .= '
      '; + + if ($GLOBALS['table_priv'] && $GLOBALS['col_priv'] + && $GLOBALS['is_reload_priv'] + ) { + $html_output .= ''; + } else { + $html_output .= ''; + } + $html_output .= '
      '; + + $pma_switch_to_new = isset($_SESSION['pma_switch_to_new']) && $_SESSION['pma_switch_to_new']; + + $html_output .= ''; + $html_output .= '' + . '
      '; + + $html_output .= '
      ' + . '' + . '
      ' + . '
      ' + . '
      '; + + return $html_output; + } + + /** + * Get HTML snippet for table maintenance + * + * @param Table $pma_table Table object + * @param array $url_params array of URL parameters + * + * @return string $html_output + */ + public function getHtmlForTableMaintenance($pma_table, array $url_params) + { + $html_output = '
      '; + $html_output .= '
      ' + . '' . __('Table maintenance') . ''; + $html_output .= '
        '; + + // Note: BERKELEY (BDB) is no longer supported, starting with MySQL 5.1 + $html_output .= $this->getListofMaintainActionLink($pma_table, $url_params); + + $html_output .= '
      ' + . '
      ' + . '
      '; + + return $html_output; + } + + /** + * Get HTML 'li' having a link of maintain action + * + * @param Table $pma_table Table object + * @param array $url_params Array of URL parameters + * + * @return string $html_output + */ + private function getListofMaintainActionLink($pma_table, array $url_params) + { + $html_output = ''; + + // analyze table + if ($pma_table->isEngine(array('MYISAM', 'ARIA', 'INNODB', 'BERKELEYDB', 'TOKUDB'))) { + $params = array( + 'sql_query' => 'ANALYZE TABLE ' + . Util::backquote($GLOBALS['table']), + 'table_maintenance' => 'Go', + ); + $html_output .= $this->getMaintainActionlink( + __('Analyze table'), + $params, + $url_params, + 'ANALYZE_TABLE' + ); + } + + // check table + if ($pma_table->isEngine(array('MYISAM', 'ARIA', 'INNODB', 'TOKUDB'))) { + $params = array( + 'sql_query' => 'CHECK TABLE ' + . Util::backquote($GLOBALS['table']), + 'table_maintenance' => 'Go', + ); + $html_output .= $this->getMaintainActionlink( + __('Check table'), + $params, + $url_params, + 'CHECK_TABLE' + ); + } + + // checksum table + $params = array( + 'sql_query' => 'CHECKSUM TABLE ' + . Util::backquote($GLOBALS['table']), + 'table_maintenance' => 'Go', + ); + $html_output .= $this->getMaintainActionlink( + __('Checksum table'), + $params, + $url_params, + 'CHECKSUM_TABLE' + ); + + // defragment table + if ($pma_table->isEngine(array('INNODB'))) { + $params = array( + 'sql_query' => 'ALTER TABLE ' + . Util::backquote($GLOBALS['table']) + . ' ENGINE = InnoDB;' + ); + $html_output .= $this->getMaintainActionlink( + __('Defragment table'), + $params, + $url_params, + 'InnoDB_File_Defragmenting' + ); + } + + // flush table + $params = array( + 'sql_query' => 'FLUSH TABLE ' + . Util::backquote($GLOBALS['table']), + 'message_to_show' => sprintf( + __('Table %s has been flushed.'), + htmlspecialchars($GLOBALS['table']) + ), + 'reload' => 1, + ); + $html_output .= $this->getMaintainActionlink( + __('Flush the table (FLUSH)'), + $params, + $url_params, + 'FLUSH' + ); + + // optimize table + if ($pma_table->isEngine(array('MYISAM', 'ARIA', 'INNODB', 'BERKELEYDB', 'TOKUDB'))) { + $params = array( + 'sql_query' => 'OPTIMIZE TABLE ' + . Util::backquote($GLOBALS['table']), + 'table_maintenance' => 'Go', + ); + $html_output .= $this->getMaintainActionlink( + __('Optimize table'), + $params, + $url_params, + 'OPTIMIZE_TABLE' + ); + } + + // repair table + if ($pma_table->isEngine(array('MYISAM', 'ARIA'))) { + $params = array( + 'sql_query' => 'REPAIR TABLE ' + . Util::backquote($GLOBALS['table']), + 'table_maintenance' => 'Go', + ); + $html_output .= $this->getMaintainActionlink( + __('Repair table'), + $params, + $url_params, + 'REPAIR_TABLE' + ); + } + + return $html_output; + } + + /** + * Get maintain action HTML link + * + * @param string $action_message action message + * @param array $params url parameters array + * @param array $url_params additional url parameters + * @param string $link contains name of page/anchor that is being linked + * + * @return string $html_output + */ + private function getMaintainActionlink($action_message, array $params, array $url_params, $link) + { + return '
    • ' + . Util::linkOrButton( + 'sql.php' . Url::getCommon(array_merge($url_params, $params)), + $action_message, + ['class' => 'maintain_action ajax'] + ) + . Util::showMySQLDocu($link) + . '
    • '; + } + + /** + * Get HTML for Delete data or table (truncate table, drop table) + * + * @param array $truncate_table_url_params url parameter array for truncate table + * @param array $dropTableUrlParams url parameter array for drop table + * + * @return string $html_output + */ + public function getHtmlForDeleteDataOrTable( + array $truncate_table_url_params, + array $dropTableUrlParams + ) { + $html_output = '
      ' + . '
      ' + . '' . __('Delete data or table') . ''; + + $html_output .= '
        '; + + if (! empty($truncate_table_url_params)) { + $html_output .= $this->getDeleteDataOrTablelink( + $truncate_table_url_params, + 'TRUNCATE_TABLE', + __('Empty the table (TRUNCATE)'), + 'truncate_tbl_anchor' + ); + } + if (!empty($dropTableUrlParams)) { + $html_output .= $this->getDeleteDataOrTablelink( + $dropTableUrlParams, + 'DROP_TABLE', + __('Delete the table (DROP)'), + 'drop_tbl_anchor' + ); + } + $html_output .= '
      '; + + return $html_output; + } + + /** + * Get the HTML link for Truncate table, Drop table and Drop db + * + * @param array $url_params url parameter array for delete data or table + * @param string $syntax TRUNCATE_TABLE or DROP_TABLE or DROP_DATABASE + * @param string $link link to be shown + * @param string $htmlId id of the link + * + * @return string html output + */ + public function getDeleteDataOrTablelink(array $url_params, $syntax, $link, $htmlId) + { + return '
    • ' . Util::linkOrButton( + 'sql.php' . Url::getCommon($url_params), + $link, + array('id' => $htmlId, 'class' => 'ajax') + ) + . Util::showMySQLDocu($syntax) + . '
    • '; + } + + /** + * Get HTML snippet for partition maintenance + * + * @param array $partition_names array of partition names for a specific db/table + * @param array $url_params url parameters + * + * @return string $html_output + */ + public function getHtmlForPartitionMaintenance(array $partition_names, array $url_params) + { + $choices = array( + 'ANALYZE' => __('Analyze'), + 'CHECK' => __('Check'), + 'OPTIMIZE' => __('Optimize'), + 'REBUILD' => __('Rebuild'), + 'REPAIR' => __('Repair'), + 'TRUNCATE' => __('Truncate') + ); + + $partition_method = Partition::getPartitionMethod( + $GLOBALS['db'], $GLOBALS['table'] + ); + // add COALESCE or DROP option to choices array depeding on Partition method + if ($partition_method == 'RANGE' + || $partition_method == 'RANGE COLUMNS' + || $partition_method == 'LIST' + || $partition_method == 'LIST COLUMNS' + ) { + $choices['DROP'] = __('Drop'); + } else { + $choices['COALESCE'] = __('Coalesce'); + } + + $html_output = '
      ' + . '
      ' + . Url::getHiddenInputs($GLOBALS['db'], $GLOBALS['table']) + . '
      ' + . '' + . __('Partition maintenance') + . Util::showMySQLDocu('partitioning_maintenance') + . ''; + + $html_select = '' . "\n"; + $html_output .= sprintf(__('Partition %s'), $html_select); + + $html_output .= '
      '; + $html_output .= Util::getRadioFields( + 'partition_operation', $choices, 'ANALYZE', false, true, 'floatleft' + ); + $this_url_params = array_merge( + $url_params, + array( + 'sql_query' => 'ALTER TABLE ' + . Util::backquote($GLOBALS['table']) + . ' REMOVE PARTITIONING;' + ) + ); + $html_output .= '

      '; + + $html_output .= '' + . __('Remove partitioning') . ''; + + $html_output .= '
      ' + . '
      ' + . '' + . '' + . '
      ' + . '
      ' + . '
      '; + + return $html_output; + } + + /** + * Get the HTML for Referential Integrity check + * + * @param array $foreign all Relations to foreign tables for a given table + * or optionally a given column in a table + * @param array $url_params array of url parameters + * + * @return string $html_output + */ + public function getHtmlForReferentialIntegrityCheck(array $foreign, array $url_params) + { + $html_output = '
      ' + . '
      ' + . '' . __('Check referential integrity:') . ''; + + $html_output .= '
        '; + + foreach ($foreign as $master => $arr) { + $join_query = 'SELECT ' + . Util::backquote($GLOBALS['table']) . '.*' + . ' FROM ' . Util::backquote($GLOBALS['table']) + . ' LEFT JOIN ' + . Util::backquote($arr['foreign_db']) + . '.' + . Util::backquote($arr['foreign_table']); + if ($arr['foreign_table'] == $GLOBALS['table']) { + $foreign_table = $GLOBALS['table'] . '1'; + $join_query .= ' AS ' . Util::backquote($foreign_table); + } else { + $foreign_table = $arr['foreign_table']; + } + $join_query .= ' ON ' + . Util::backquote($GLOBALS['table']) . '.' + . Util::backquote($master) + . ' = ' + . Util::backquote($arr['foreign_db']) + . '.' + . Util::backquote($foreign_table) . '.' + . Util::backquote($arr['foreign_field']) + . ' WHERE ' + . Util::backquote($arr['foreign_db']) + . '.' + . Util::backquote($foreign_table) . '.' + . Util::backquote($arr['foreign_field']) + . ' IS NULL AND ' + . Util::backquote($GLOBALS['table']) . '.' + . Util::backquote($master) + . ' IS NOT NULL'; + $this_url_params = array_merge( + $url_params, + array('sql_query' => $join_query) + ); + + $html_output .= '
      • ' + . '' + . $master . ' -> ' . $arr['foreign_db'] . '.' + . $arr['foreign_table'] . '.' . $arr['foreign_field'] + . '
      • ' . "\n"; + } // foreach $foreign + $html_output .= '
      '; + + return $html_output; + } + + /** + * Reorder table based on request params + * + * @return array SQL query and result + */ + public function getQueryAndResultForReorderingTable() + { + $sql_query = 'ALTER TABLE ' + . Util::backquote($GLOBALS['table']) + . ' ORDER BY ' + . Util::backquote(urldecode($_POST['order_field'])); + if (isset($_POST['order_order']) + && $_POST['order_order'] === 'desc' + ) { + $sql_query .= ' DESC'; + } else { + $sql_query .= ' ASC'; + } + $sql_query .= ';'; + $result = $GLOBALS['dbi']->query($sql_query); + + return array($sql_query, $result); + } + + /** + * Get table alters array + * + * @param Table $pma_table The Table object + * @param string $pack_keys pack keys + * @param string $checksum value of checksum + * @param string $page_checksum value of page checksum + * @param string $delay_key_write delay key write + * @param string $row_format row format + * @param string $newTblStorageEngine table storage engine + * @param string $transactional value of transactional + * @param string $tbl_collation collation of the table + * + * @return array $table_alters + */ + public function getTableAltersArray($pma_table, $pack_keys, + $checksum, $page_checksum, $delay_key_write, + $row_format, $newTblStorageEngine, $transactional, $tbl_collation + ) { + global $auto_increment; + + $table_alters = array(); + + if (isset($_POST['comment']) + && urldecode($_POST['prev_comment']) !== $_POST['comment'] + ) { + $table_alters[] = 'COMMENT = \'' + . $GLOBALS['dbi']->escapeString($_POST['comment']) . '\''; + } + + if (! empty($newTblStorageEngine) + && mb_strtolower($newTblStorageEngine) !== mb_strtolower($GLOBALS['tbl_storage_engine']) + ) { + $table_alters[] = 'ENGINE = ' . $newTblStorageEngine; + } + if (! empty($_POST['tbl_collation']) + && $_POST['tbl_collation'] !== $tbl_collation + ) { + $table_alters[] = 'DEFAULT ' + . Util::getCharsetQueryPart($_POST['tbl_collation']); + } + + if ($pma_table->isEngine(array('MYISAM', 'ARIA', 'ISAM')) + && isset($_POST['new_pack_keys']) + && $_POST['new_pack_keys'] != (string)$pack_keys + ) { + $table_alters[] = 'pack_keys = ' . $_POST['new_pack_keys']; + } + + $_POST['new_checksum'] = empty($_POST['new_checksum']) ? '0' : '1'; + if ($pma_table->isEngine(array('MYISAM', 'ARIA')) + && $_POST['new_checksum'] !== $checksum + ) { + $table_alters[] = 'checksum = ' . $_POST['new_checksum']; + } + + $_POST['new_transactional'] + = empty($_POST['new_transactional']) ? '0' : '1'; + if ($pma_table->isEngine('ARIA') + && $_POST['new_transactional'] !== $transactional + ) { + $table_alters[] = 'TRANSACTIONAL = ' . $_POST['new_transactional']; + } + + $_POST['new_page_checksum'] + = empty($_POST['new_page_checksum']) ? '0' : '1'; + if ($pma_table->isEngine('ARIA') + && $_POST['new_page_checksum'] !== $page_checksum + ) { + $table_alters[] = 'PAGE_CHECKSUM = ' . $_POST['new_page_checksum']; + } + + $_POST['new_delay_key_write'] + = empty($_POST['new_delay_key_write']) ? '0' : '1'; + if ($pma_table->isEngine(array('MYISAM', 'ARIA')) + && $_POST['new_delay_key_write'] !== $delay_key_write + ) { + $table_alters[] = 'delay_key_write = ' . $_POST['new_delay_key_write']; + } + + if ($pma_table->isEngine(array('MYISAM', 'ARIA', 'INNODB', 'PBXT', 'ROCKSDB')) + && ! empty($_POST['new_auto_increment']) + && (! isset($auto_increment) + || $_POST['new_auto_increment'] !== $auto_increment) + ) { + $table_alters[] = 'auto_increment = ' + . $GLOBALS['dbi']->escapeString($_POST['new_auto_increment']); + } + + if (! empty($_POST['new_row_format'])) { + $newRowFormat = $_POST['new_row_format']; + $newRowFormatLower = mb_strtolower($newRowFormat); + if ($pma_table->isEngine(array('MYISAM', 'ARIA', 'INNODB', 'PBXT')) + && (strlen($row_format) === 0 + || $newRowFormatLower !== mb_strtolower($row_format)) + ) { + $table_alters[] = 'ROW_FORMAT = ' + . $GLOBALS['dbi']->escapeString($newRowFormat); + } + } + + return $table_alters; + } + + /** + * Get warning messages array + * + * @return array $warning_messages + */ + public function getWarningMessagesArray() + { + $warning_messages = array(); + foreach ($GLOBALS['dbi']->getWarnings() as $warning) { + // In MariaDB 5.1.44, when altering a table from Maria to MyISAM + // and if TRANSACTIONAL was set, the system reports an error; + // I discussed with a Maria developer and he agrees that this + // should not be reported with a Level of Error, so here + // I just ignore it. But there are other 1478 messages + // that it's better to show. + if (! (isset($_POST['new_tbl_storage_engine']) + && $_POST['new_tbl_storage_engine'] == 'MyISAM' + && $warning['Code'] == '1478' + && $warning['Level'] == 'Error') + ) { + $warning_messages[] = $warning['Level'] . ': #' . $warning['Code'] + . ' ' . $warning['Message']; + } + } + return $warning_messages; + } + + /** + * Get SQL query and result after ran this SQL query for a partition operation + * has been requested by the user + * + * @return array $sql_query, $result + */ + public function getQueryAndResultForPartition() + { + $sql_query = 'ALTER TABLE ' + . Util::backquote($GLOBALS['table']) . ' ' + . $_POST['partition_operation'] + . ' PARTITION '; + + if ($_POST['partition_operation'] == 'COALESCE') { + $sql_query .= count($_POST['partition_name']); + } else { + $sql_query .= implode(', ', $_POST['partition_name']) . ';'; + } + + $result = $GLOBALS['dbi']->query($sql_query); + + return array($sql_query, $result); + } + + /** + * Adjust the privileges after renaming/moving a table + * + * @param string $oldDb Database name before table renaming/moving table + * @param string $oldTable Table name before table renaming/moving table + * @param string $newDb Database name after table renaming/ moving table + * @param string $newTable Table name after table renaming/moving table + * + * @return void + */ + public function adjustPrivilegesRenameOrMoveTable($oldDb, $oldTable, $newDb, $newTable) + { + if ($GLOBALS['table_priv'] && $GLOBALS['col_priv'] + && $GLOBALS['is_reload_priv'] + ) { + $GLOBALS['dbi']->selectDb('mysql'); + + // For table specific privileges + $query_table_specific = 'UPDATE ' . Util::backquote('tables_priv') + . 'SET Db = \'' . $GLOBALS['dbi']->escapeString($newDb) . '\', Table_name = \'' . $GLOBALS['dbi']->escapeString($newTable) + . '\' where Db = \'' . $GLOBALS['dbi']->escapeString($oldDb) . '\' AND Table_name = \'' . $GLOBALS['dbi']->escapeString($oldTable) + . '\';'; + $GLOBALS['dbi']->query($query_table_specific); + + // For column specific privileges + $query_col_specific = 'UPDATE ' . Util::backquote('columns_priv') + . 'SET Db = \'' . $GLOBALS['dbi']->escapeString($newDb) . '\', Table_name = \'' . $GLOBALS['dbi']->escapeString($newTable) + . '\' where Db = \'' . $GLOBALS['dbi']->escapeString($oldDb) . '\' AND Table_name = \'' . $GLOBALS['dbi']->escapeString($oldTable) + . '\';'; + $GLOBALS['dbi']->query($query_col_specific); + + // Finally FLUSH the new privileges + $flush_query = "FLUSH PRIVILEGES;"; + $GLOBALS['dbi']->query($flush_query); + } + } + + /** + * Adjust the privileges after copying a table + * + * @param string $oldDb Database name before table copying + * @param string $oldTable Table name before table copying + * @param string $newDb Database name after table copying + * @param string $newTable Table name after table copying + * + * @return void + */ + public function adjustPrivilegesCopyTable($oldDb, $oldTable, $newDb, $newTable) + { + if ($GLOBALS['table_priv'] && $GLOBALS['col_priv'] + && $GLOBALS['is_reload_priv'] + ) { + $GLOBALS['dbi']->selectDb('mysql'); + + // For Table Specific privileges + $query_table_specific_old = 'SELECT * FROM ' + . Util::backquote('tables_priv') . ' where ' + . 'Db = "' . $oldDb . '" AND Table_name = "' . $oldTable . '";'; + + $old_privs_table = $GLOBALS['dbi']->fetchResult( + $query_table_specific_old, + 0 + ); + + foreach ($old_privs_table as $old_priv) { + $newDb_table_privs_query = 'INSERT INTO ' + . Util::backquote('tables_priv') . ' VALUES("' + . $old_priv[0] . '", "' . $newDb . '", "' . $old_priv[2] . '", "' + . $newTable . '", "' . $old_priv[4] . '", "' . $old_priv[5] + . '", "' . $old_priv[6] . '", "' . $old_priv[7] . '");'; + + $GLOBALS['dbi']->query($newDb_table_privs_query); + } + + // For Column Specific privileges + $query_col_specific_old = 'SELECT * FROM ' + . Util::backquote('columns_priv') . ' WHERE ' + . 'Db = "' . $oldDb . '" AND Table_name = "' . $oldTable . '";'; + + $old_privs_col = $GLOBALS['dbi']->fetchResult( + $query_col_specific_old, + 0 + ); + + foreach ($old_privs_col as $old_priv) { + $newDb_col_privs_query = 'INSERT INTO ' + . Util::backquote('columns_priv') . ' VALUES("' + . $old_priv[0] . '", "' . $newDb . '", "' . $old_priv[2] . '", "' + . $newTable . '", "' . $old_priv[4] . '", "' . $old_priv[5] + . '", "' . $old_priv[6] . '");'; + + $GLOBALS['dbi']->query($newDb_col_privs_query); + } + + // Finally FLUSH the new privileges + $flush_query = "FLUSH PRIVILEGES;"; + $GLOBALS['dbi']->query($flush_query); + } + } + + /** + * Change all collations and character sets of all columns in table + * + * @param string $db Database name + * @param string $table Table name + * @param string $tbl_collation Collation Name + * + * @return void + */ + public function changeAllColumnsCollation($db, $table, $tbl_collation) + { + $GLOBALS['dbi']->selectDb($db); + + $change_all_collations_query = 'ALTER TABLE ' + . Util::backquote($table) + . ' CONVERT TO'; + + list($charset) = explode('_', $tbl_collation); + + $change_all_collations_query .= ' CHARACTER SET ' . $charset + . ($charset == $tbl_collation ? '' : ' COLLATE ' . $tbl_collation); + + $GLOBALS['dbi']->query($change_all_collations_query); + } + + /** + * Move or copy a table + * + * @param string $db current database name + * @param string $table current table name + * + * @return void + */ + public function moveOrCopyTable($db, $table) + { + /** + * Selects the database to work with + */ + $GLOBALS['dbi']->selectDb($db); + + /** + * $_POST['target_db'] could be empty in case we came from an input field + * (when there are many databases, no drop-down) + */ + if (empty($_POST['target_db'])) { + $_POST['target_db'] = $db; + } + + /** + * A target table name has been sent to this script -> do the work + */ + if (Core::isValid($_POST['new_name'])) { + if ($db == $_POST['target_db'] && $table == $_POST['new_name']) { + if (isset($_POST['submit_move'])) { + $message = Message::error(__('Can\'t move table to same one!')); + } else { + $message = Message::error(__('Can\'t copy table to same one!')); + } + } else { + Table::moveCopy( + $db, $table, $_POST['target_db'], $_POST['new_name'], + $_POST['what'], isset($_POST['submit_move']), 'one_table' + ); + + if (isset($_POST['adjust_privileges']) + && ! empty($_POST['adjust_privileges']) + ) { + if (isset($_POST['submit_move'])) { + $this->adjustPrivilegesRenameOrMoveTable( + $db, $table, $_POST['target_db'], $_POST['new_name'] + ); + } else { + $this->adjustPrivilegesCopyTable( + $db, $table, $_POST['target_db'], $_POST['new_name'] + ); + } + + if (isset($_POST['submit_move'])) { + $message = Message::success( + __( + 'Table %s has been moved to %s. Privileges have been ' + . 'adjusted.' + ) + ); + } else { + $message = Message::success( + __( + 'Table %s has been copied to %s. Privileges have been ' + . 'adjusted.' + ) + ); + } + + } else { + if (isset($_POST['submit_move'])) { + $message = Message::success( + __('Table %s has been moved to %s.') + ); + } else { + $message = Message::success( + __('Table %s has been copied to %s.') + ); + } + } + + $old = Util::backquote($db) . '.' + . Util::backquote($table); + $message->addParam($old); + + $new_name = $_POST['new_name']; + if ($GLOBALS['dbi']->getLowerCaseNames() === '1') { + $new_name = strtolower($new_name); + } + + $GLOBALS['table'] = $new_name; + + $new = Util::backquote($_POST['target_db']) . '.' + . Util::backquote($new_name); + $message->addParam($new); + + /* Check: Work on new table or on old table? */ + if (isset($_POST['submit_move']) + || Core::isValid($_POST['switch_to_new']) + ) { + } + } + } else { + /** + * No new name for the table! + */ + $message = Message::error(__('The table name is empty!')); + } + + $response = Response::getInstance(); + if ($response->isAjax()) { + $response->addJSON('message', $message); + if ($message->isSuccess()) { + $response->addJSON('db', $GLOBALS['db']); + } else { + $response->setRequestStatus(false); + } + exit; + } + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/OutputBuffering.php b/php/apps/phpmyadmin49/libraries/classes/OutputBuffering.php new file mode 100644 index 00000000..a66b045f --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/OutputBuffering.php @@ -0,0 +1,139 @@ +_mode = $this->_getMode(); + $this->_on = false; + } + + /** + * This function could be used eventually to support more modes. + * + * @return integer the output buffer mode + */ + private function _getMode() + { + $mode = 0; + if ($GLOBALS['cfg']['OBGzip'] && function_exists('ob_start')) { + if (ini_get('output_handler') == 'ob_gzhandler') { + // If a user sets the output_handler in php.ini to ob_gzhandler, then + // any right frame file in phpMyAdmin will not be handled properly by + // the browser. My fix was to check the ini file within the + // PMA_outBufferModeGet() function. + $mode = 0; + } elseif (function_exists('ob_get_level') && ob_get_level() > 0) { + // happens when php.ini's output_buffering is not Off + ob_end_clean(); + $mode = 1; + } else { + $mode = 1; + } + } + // Zero (0) is no mode or in other words output buffering is OFF. + // Follow 2^0, 2^1, 2^2, 2^3 type values for the modes. + // Useful if we ever decide to combine modes. Then a bitmask field of + // the sum of all modes will be the natural choice. + return $mode; + } + + /** + * Returns the singleton OutputBuffering object + * + * @return OutputBuffering object + */ + public static function getInstance() + { + if (empty(self::$_instance)) { + self::$_instance = new OutputBuffering(); + } + return self::$_instance; + } + + /** + * This function will need to run at the top of all pages if output + * output buffering is turned on. It also needs to be passed $mode from + * the PMA_outBufferModeGet() function or it will be useless. + * + * @return void + */ + public function start() + { + if (! $this->_on) { + if ($this->_mode && function_exists('ob_gzhandler')) { + ob_start('ob_gzhandler'); + } + ob_start(); + if (! defined('TESTSUITE')) { + header('X-ob_mode: ' . $this->_mode); + } + register_shutdown_function( + array(OutputBuffering::class, 'stop') + ); + $this->_on = true; + } + } + + /** + * This function will need to run at the bottom of all pages if output + * buffering is turned on. It also needs to be passed $mode from the + * PMA_outBufferModeGet() function or it will be useless. + * + * @return void + */ + public static function stop() + { + $buffer = OutputBuffering::getInstance(); + if ($buffer->_on) { + $buffer->_on = false; + $buffer->_content = ob_get_contents(); + if (ob_get_length() > 0) { + ob_end_clean(); + } + } + } + + /** + * Gets buffer content + * + * @return string buffer content + */ + public function getContents() + { + return $this->_content; + } + + /** + * Flushes output buffer + * + * @return void + */ + public function flush() + { + if (ob_get_status() && $this->_mode) { + ob_flush(); + } else { + flush(); + } + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/ParseAnalyze.php b/php/apps/phpmyadmin49/libraries/classes/ParseAnalyze.php new file mode 100644 index 00000000..c30af3e2 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/ParseAnalyze.php @@ -0,0 +1,78 @@ + 1) { + + /** + * @todo if there are more than one table name in the Select: + * - do not extract the first table name + * - do not show a table name in the page header + * - do not display the sub-pages links) + */ + $table = ''; + } else { + $table = $analyzed_sql_results['select_tables'][0][0]; + if (!empty($analyzed_sql_results['select_tables'][0][1])) { + $db = $analyzed_sql_results['select_tables'][0][1]; + } + } + // There is no point checking if a reload is required if we already decided + // to reload. Also, no reload is required for AJAX requests. + $response = Response::getInstance(); + if (empty($reload) && ! $response->isAjax()) { + // NOTE: Database names are case-insensitive. + $reload = strcasecmp($db, $prev_db) != 0; + } + + // Updating the array. + $analyzed_sql_results['reload'] = $reload; + } + + return array($analyzed_sql_results, $db, $table); + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Partition.php b/php/apps/phpmyadmin49/libraries/classes/Partition.php new file mode 100644 index 00000000..3d1e1a65 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Partition.php @@ -0,0 +1,268 @@ +name = $row['PARTITION_NAME']; + $this->ordinal = $row['PARTITION_ORDINAL_POSITION']; + $this->method = $row['PARTITION_METHOD']; + $this->expression = $row['PARTITION_EXPRESSION']; + $this->description = $row['PARTITION_DESCRIPTION']; + // no sub partitions, load all data to this object + if (empty($row['SUBPARTITION_NAME'])) { + $this->loadCommonData($row); + } + } + + /** + * Returns the partiotion description + * + * @return string partition description + */ + public function getDescription() + { + return $this->description; + } + + /** + * Add a sub partition + * + * @param SubPartition $partition Sub partition + * + * @return void + */ + public function addSubPartition(SubPartition $partition) + { + $this->subPartitions[] = $partition; + } + + /** + * Whether there are sub partitions + * + * @return boolean + */ + public function hasSubPartitions() + { + return ! empty($this->subPartitions); + } + + /** + * Returns the number of data rows + * + * @return integer number of rows + */ + public function getRows() + { + if (empty($this->subPartitions)) { + return $this->rows; + } + + $rows = 0; + foreach ($this->subPartitions as $subPartition) { + $rows += $subPartition->rows; + } + return $rows; + } + + /** + * Returns the total data length + * + * @return integer data length + */ + public function getDataLength() + { + if (empty($this->subPartitions)) { + return $this->dataLength; + } + + $dataLength = 0; + foreach ($this->subPartitions as $subPartition) { + $dataLength += $subPartition->dataLength; + } + return $dataLength; + } + + /** + * Returns the tatal index length + * + * @return integer index length + */ + public function getIndexLength() + { + if (empty($this->subPartitions)) { + return $this->indexLength; + } + + $indexLength = 0; + foreach ($this->subPartitions as $subPartition) { + $indexLength += $subPartition->indexLength; + } + return $indexLength; + } + + /** + * Returns the list of sub partitions + * + * @return SubPartition[] + */ + public function getSubPartitions() + { + return $this->subPartitions; + } + + /** + * Returns array of partitions for a specific db/table + * + * @param string $db database name + * @param string $table table name + * + * @access public + * @return Partition[] + */ + static public function getPartitions($db, $table) + { + if (Partition::havePartitioning()) { + $result = $GLOBALS['dbi']->fetchResult( + "SELECT * FROM `information_schema`.`PARTITIONS`" + . " WHERE `TABLE_SCHEMA` = '" . $GLOBALS['dbi']->escapeString($db) + . "' AND `TABLE_NAME` = '" . $GLOBALS['dbi']->escapeString($table) . "'" + ); + if ($result) { + $partitionMap = array(); + foreach ($result as $row) { + if (isset($partitionMap[$row['PARTITION_NAME']])) { + $partition = $partitionMap[$row['PARTITION_NAME']]; + } else { + $partition = new Partition($row); + $partitionMap[$row['PARTITION_NAME']] = $partition; + } + + if (! empty($row['SUBPARTITION_NAME'])) { + $parentPartition = $partition; + $partition = new SubPartition($row); + $parentPartition->addSubPartition($partition); + } + } + return array_values($partitionMap); + } + return array(); + } + + return array(); + } + + /** + * returns array of partition names for a specific db/table + * + * @param string $db database name + * @param string $table table name + * + * @access public + * @return array of partition names + */ + static public function getPartitionNames($db, $table) + { + if (Partition::havePartitioning()) { + return $GLOBALS['dbi']->fetchResult( + "SELECT DISTINCT `PARTITION_NAME` FROM `information_schema`.`PARTITIONS`" + . " WHERE `TABLE_SCHEMA` = '" . $GLOBALS['dbi']->escapeString($db) + . "' AND `TABLE_NAME` = '" . $GLOBALS['dbi']->escapeString($table) . "'" + ); + } + + return array(); + } + + /** + * returns the partition method used by the table. + * + * @param string $db database name + * @param string $table table name + * + * @return string partition method + */ + static public function getPartitionMethod($db, $table) + { + if (Partition::havePartitioning()) { + $partition_method = $GLOBALS['dbi']->fetchResult( + "SELECT `PARTITION_METHOD` FROM `information_schema`.`PARTITIONS`" + . " WHERE `TABLE_SCHEMA` = '" . $GLOBALS['dbi']->escapeString($db) . "'" + . " AND `TABLE_NAME` = '" . $GLOBALS['dbi']->escapeString($table) . "'" + . " LIMIT 1" + ); + if (! empty($partition_method)) { + return $partition_method[0]; + } + } + return null; + } + + /** + * checks if MySQL server supports partitioning + * + * @static + * @staticvar boolean $have_partitioning + * @staticvar boolean $already_checked + * @access public + * @return boolean + */ + static public function havePartitioning() + { + static $have_partitioning = false; + static $already_checked = false; + + if (! $already_checked) { + if ($GLOBALS['dbi']->getVersion() < 50600) { + if ($GLOBALS['dbi']->fetchValue( + "SELECT @@have_partitioning;" + )) { + $have_partitioning = true; + } + } else if ($GLOBALS['dbi']->getVersion() >= 80000) { + $have_partitioning = true; + } else { + // see https://dev.mysql.com/doc/refman/5.6/en/partitioning.html + $plugins = $GLOBALS['dbi']->fetchResult("SHOW PLUGINS"); + foreach ($plugins as $value) { + if ($value['Name'] == 'partition') { + $have_partitioning = true; + break; + } + } + } + $already_checked = true; + } + return $have_partitioning; + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Pdf.php b/php/apps/phpmyadmin49/libraries/classes/Pdf.php new file mode 100644 index 00000000..8ab3f1bf --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Pdf.php @@ -0,0 +1,154 @@ +SetAuthor('phpMyAdmin ' . PMA_VERSION); + $this->AddFont('DejaVuSans', '', 'dejavusans.php'); + $this->AddFont('DejaVuSans', 'B', 'dejavusansb.php'); + $this->SetFont(Pdf::PMA_PDF_FONT, '', 14); + $this->setFooterFont(array(Pdf::PMA_PDF_FONT, '', 14)); + } + + /** + * This function must be named "Footer" to work with the TCPDF library + * + * @return void + */ + // @codingStandardsIgnoreLine + public function Footer() + { + // Check if footer for this page already exists + if (!isset($this->footerset[$this->page])) { + $this->SetY(-15); + $this->SetFont(Pdf::PMA_PDF_FONT, '', 14); + $this->Cell( + 0, 6, + __('Page number:') . ' ' + . $this->getAliasNumPage() . '/' . $this->getAliasNbPages(), + 'T', 0, 'C' + ); + $this->Cell(0, 6, Util::localisedDate(), 0, 1, 'R'); + $this->SetY(20); + + // set footerset + $this->footerset[$this->page] = 1; + } + } + + /** + * Function to set alias which will be expanded on page rendering. + * + * @param string $name name of the alias + * @param string $value value of the alias + * + * @return void + */ + public function setAlias($name, $value) + { + $name = TCPDF_FONTS::UTF8ToUTF16BE( + $name, false, true, $this->CurrentFont + ); + $this->Alias[$name] = TCPDF_FONTS::UTF8ToUTF16BE( + $value, false, true, $this->CurrentFont + ); + } + + /** + * Improved with alias expanding. + * + * @return void + */ + public function _putpages() + { + if (count($this->Alias) > 0) { + $nbPages = count($this->pages); + for ($n = 1; $n <= $nbPages; $n++) { + $this->pages[$n] = strtr($this->pages[$n], $this->Alias); + } + } + parent::_putpages(); + } + + /** + * Displays an error message + * + * @param string $error_message the error message + * + * @return void + */ + // @codingStandardsIgnoreLine + public function Error($error_message = '') + { + Message::error( + __('Error while creating PDF:') . ' ' . $error_message + )->display(); + exit; + } + + /** + * Sends file as a download to user. + * + * @param string $filename file name + * + * @return void + */ + public function download($filename) + { + $pdfData = $this->getPDFData(); + Response::getInstance()->disable(); + Core::downloadHeader( + $filename, + 'application/pdf', + strlen($pdfData) + ); + echo $pdfData; + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Plugins.php b/php/apps/phpmyadmin49/libraries/classes/Plugins.php new file mode 100644 index 00000000..b125bcea --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Plugins.php @@ -0,0 +1,584 @@ +getProperties()) { + $plugin_list[] = $plugin; + } + } + } + } + + usort($plugin_list, function($cmp_name_1, $cmp_name_2) { + return strcasecmp( + $cmp_name_1->getProperties()->getText(), + $cmp_name_2->getProperties()->getText() + ); + }); + return $plugin_list; + } + + /** + * Returns locale string for $name or $name if no locale is found + * + * @param string $name for local string + * + * @return string locale string for $name + */ + public static function getString($name) + { + return isset($GLOBALS[$name]) ? $GLOBALS[$name] : $name; + } + + /** + * Returns html input tag option 'checked' if plugin $opt + * should be set by config or request + * + * @param string $section name of config section in + * $GLOBALS['cfg'][$section] for plugin + * @param string $opt name of option + * + * @return string html input tag option 'checked' + */ + public static function checkboxCheck($section, $opt) + { + // If the form is being repopulated using $_GET data, that is priority + if (isset($_GET[$opt]) + || ! isset($_GET['repopulate']) + && ((! empty($GLOBALS['timeout_passed']) && isset($_REQUEST[$opt])) + || ! empty($GLOBALS['cfg'][$section][$opt])) + ) { + return ' checked="checked"'; + } + return ''; + } + + /** + * Returns default value for option $opt + * + * @param string $section name of config section in + * $GLOBALS['cfg'][$section] for plugin + * @param string $opt name of option + * + * @return string default value for option $opt + */ + public static function getDefault($section, $opt) + { + if (isset($_GET[$opt])) { + // If the form is being repopulated using $_GET data, that is priority + return htmlspecialchars($_GET[$opt]); + } + + if (isset($GLOBALS['timeout_passed']) + && $GLOBALS['timeout_passed'] + && isset($_REQUEST[$opt]) + ) { + return htmlspecialchars($_REQUEST[$opt]); + } + + if (!isset($GLOBALS['cfg'][$section][$opt])) { + return ''; + } + + $matches = array(); + /* Possibly replace localised texts */ + if (!preg_match_all( + '/(str[A-Z][A-Za-z0-9]*)/', + $GLOBALS['cfg'][$section][$opt], + $matches + )) { + return htmlspecialchars($GLOBALS['cfg'][$section][$opt]); + } + + $val = $GLOBALS['cfg'][$section][$opt]; + foreach ($matches[0] as $match) { + if (isset($GLOBALS[$match])) { + $val = str_replace($match, $GLOBALS[$match], $val); + } + } + return htmlspecialchars($val); + } + + /** + * Returns html select form element for plugin choice + * and hidden fields denoting whether each plugin must be exported as a file + * + * @param string $section name of config section in + * $GLOBALS['cfg'][$section] for plugin + * @param string $name name of select element + * @param array &$list array with plugin instances + * @param string $cfgname name of config value, if none same as $name + * + * @return string html select tag + */ + public static function getChoice($section, $name, array $list, $cfgname = null) + { + if (! isset($cfgname)) { + $cfgname = $name; + } + $ret = '' . "\n"; + } + $ret .= '' . "\n" . $hidden; + + return $ret; + } + + /** + * Returns single option in a list element + * + * @param string $section name of config section in $GLOBALS['cfg'][$section] for plugin + * @param string $plugin_name unique plugin name + * @param array|\PhpMyAdmin\Properties\PropertyItem &$propertyGroup options property main group instance + * @param boolean $is_subgroup if this group is a subgroup + * + * @return string table row with option + */ + public static function getOneOption( + $section, + $plugin_name, + &$propertyGroup, + $is_subgroup = false + ) { + $ret = "\n"; + + if (! $is_subgroup) { + // for subgroup headers + if (mb_strpos(get_class($propertyGroup), "PropertyItem")) { + $properties = array($propertyGroup); + } else { + // for main groups + $ret .= '
      '; + + if (method_exists($propertyGroup, 'getText')) { + $text = $propertyGroup->getText(); + } + + if ($text != null) { + $ret .= '

      ' . self::getString($text) . '

      '; + } + $ret .= '
        '; + } + } + + if (! isset($properties)) { + $not_subgroup_header = true; + if (method_exists($propertyGroup, 'getProperties')) { + $properties = $propertyGroup->getProperties(); + } + } + + if (isset($properties)) { + /** @var OptionsPropertySubgroup $propertyItem */ + foreach ($properties as $propertyItem) { + $property_class = get_class($propertyItem); + // if the property is a subgroup, we deal with it recursively + if (mb_strpos($property_class, "Subgroup")) { + // for subgroups + // each subgroup can have a header, which may also be a form element + /** @var OptionsPropertyItem $subgroup_header */ + $subgroup_header = $propertyItem->getSubgroupHeader(); + if (isset($subgroup_header)) { + $ret .= self::getOneOption( + $section, + $plugin_name, + $subgroup_header + ); + } + + $ret .= '
      • getName() . '">'; + } else { + $ret .= '>'; + } + + $ret .= self::getOneOption( + $section, + $plugin_name, + $propertyItem, + true + ); + continue; + } + + // single property item + $ret .= self::getHtmlForProperty( + $section, $plugin_name, $propertyItem + ); + } + } + + if ($is_subgroup) { + // end subgroup + $ret .= '
      '; + } else { + // end main group + if (! empty($not_subgroup_header)) { + $ret .= '
      '; + } + } + + if (method_exists($propertyGroup, "getDoc")) { + $doc = $propertyGroup->getDoc(); + if ($doc != null) { + if (count($doc) == 3) { + $ret .= PhpMyAdmin\Util::showMySQLDocu( + $doc[1], + false, + $doc[2] + ); + } elseif (count($doc) == 1) { + $ret .= PhpMyAdmin\Util::showDocu('faq', $doc[0]); + } else { + $ret .= PhpMyAdmin\Util::showMySQLDocu( + $doc[1] + ); + } + } + } + + // Close the list element after $doc link is displayed + if (isset($property_class)) { + if ($property_class == 'PhpMyAdmin\Properties\Options\Items\BoolPropertyItem' + || $property_class == 'PhpMyAdmin\Properties\Options\Items\MessageOnlyPropertyItem' + || $property_class == 'PhpMyAdmin\Properties\Options\Items\SelectPropertyItem' + || $property_class == 'PhpMyAdmin\Properties\Options\Items\TextPropertyItem' + ) { + $ret .= ''; + } + } + $ret .= "\n"; + return $ret; + } + + /** + * Get HTML for properties items + * + * @param string $section name of config section in + * $GLOBALS['cfg'][$section] for plugin + * @param string $plugin_name unique plugin name + * @param OptionsPropertyItem $propertyItem Property item + * + * @return string + */ + public static function getHtmlForProperty( + $section, $plugin_name, $propertyItem + ) { + $ret = null; + $property_class = get_class($propertyItem); + switch ($property_class) { + case 'PhpMyAdmin\Properties\Options\Items\BoolPropertyItem': + $ret .= '
    • ' . "\n"; + $ret .= 'getName() + ); + + if ($propertyItem->getForce() != null) { + // Same code is also few lines lower, update both if needed + $ret .= ' onclick="if (!this.checked && ' + . '(!document.getElementById(\'checkbox_' . $plugin_name + . '_' . $propertyItem->getForce() . '\') ' + . '|| !document.getElementById(\'checkbox_' + . $plugin_name . '_' . $propertyItem->getForce() + . '\').checked)) ' + . 'return false; else return true;"'; + } + $ret .= ' />'; + $ret .= ''; + break; + case 'PhpMyAdmin\Properties\Options\Items\DocPropertyItem': + echo 'PhpMyAdmin\Properties\Options\Items\DocPropertyItem'; + break; + case 'PhpMyAdmin\Properties\Options\Items\HiddenPropertyItem': + $ret .= '
    • '; + break; + case 'PhpMyAdmin\Properties\Options\Items\MessageOnlyPropertyItem': + $ret .= '
    • ' . "\n"; + $ret .= '

      ' . self::getString($propertyItem->getText()) . '

      '; + break; + case 'PhpMyAdmin\Properties\Options\Items\RadioPropertyItem': + $default = self::getDefault( + $section, + $plugin_name . '_' . $propertyItem->getName() + ); + foreach ($propertyItem->getValues() as $key => $val) { + $ret .= '
    • ' + . self::getString($val) . '
    • '; + } + break; + case 'PhpMyAdmin\Properties\Options\Items\SelectPropertyItem': + $ret .= '
    • ' . "\n"; + $ret .= ''; + $ret .= ''; + break; + case 'PhpMyAdmin\Properties\Options\Items\TextPropertyItem': + case 'PhpMyAdmin\Properties\Options\Items\NumberPropertyItem': + $ret .= '
    • ' . "\n"; + $ret .= ''; + $ret .= 'getSize() != null + ? ' size="' . $propertyItem->getSize() . '"' + : '') + . ($propertyItem->getLen() != null + ? ' maxlength="' . $propertyItem->getLen() . '"' + : '') + . ' />'; + break; + default: + break; + } + return $ret; + } + + /** + * Returns html div with editable options for plugin + * + * @param string $section name of config section in $GLOBALS['cfg'][$section] + * @param array &$list array with plugin instances + * + * @return string html fieldset with plugin options + */ + public static function getOptions($section, array $list) + { + $ret = ''; + // Options for plugins that support them + foreach ($list as $plugin) { + $properties = $plugin->getProperties(); + if ($properties != null) { + $text = $properties->getText(); + $options = $properties->getOptions(); + } + + $elem = explode('\\', get_class($plugin)); + $plugin_name = array_pop($elem); + unset($elem); + $plugin_name = mb_strtolower( + mb_substr( + $plugin_name, + mb_strlen($section) + ) + ); + + $ret .= '
      '; + $ret .= '

      ' . self::getString($text) . '

      '; + + $no_options = true; + if (! is_null($options) && count($options) > 0) { + foreach ($options->getProperties() + as $propertyMainGroup + ) { + // check for hidden properties + $no_options = true; + foreach ($propertyMainGroup->getProperties() as $propertyItem) { + if (strcmp('PhpMyAdmin\Properties\Options\Items\HiddenPropertyItem', get_class($propertyItem))) { + $no_options = false; + break; + } + } + + $ret .= self::getOneOption( + $section, + $plugin_name, + $propertyMainGroup + ); + } + } + + if ($no_options) { + $ret .= '

      ' . __('This format has no options') . '

      '; + } + $ret .= '
      '; + } + return $ret; + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Plugins/Auth/AuthenticationConfig.php b/php/apps/phpmyadmin49/libraries/classes/Plugins/Auth/AuthenticationConfig.php new file mode 100644 index 00000000..d9d7e075 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Plugins/Auth/AuthenticationConfig.php @@ -0,0 +1,170 @@ +isAjax()) { + $response->setRequestStatus(false); + // reload_flag removes the token parameter from the URL and reloads + $response->addJSON('reload_flag', '1'); + if (defined('TESTSUITE')) { + return true; + } else { + exit; + } + } + + return true; + } + + /** + * Gets authentication credentials + * + * @return boolean always true + */ + public function readCredentials() + { + if ($GLOBALS['token_provided'] && $GLOBALS['token_mismatch']) { + return false; + } + + $this->user = $GLOBALS['cfg']['Server']['user']; + $this->password = $GLOBALS['cfg']['Server']['password']; + + return true; + } + + /** + * User is not allowed to login to MySQL -> authentication failed + * + * @param string $failure String describing why authentication has failed + * + * @return void + */ + public function showFailure($failure) + { + parent::showFailure($failure); + $conn_error = $GLOBALS['dbi']->getError(); + if (!$conn_error) { + $conn_error = __('Cannot connect: invalid settings.'); + } + + /* HTML header */ + $response = Response::getInstance(); + $response->getFooter() + ->setMinimal(); + $header = $response->getHeader(); + $header->setBodyId('loginform'); + $header->setTitle(__('Access denied!')); + $header->disableMenuAndConsole(); + echo '

      +
      +

      '; + echo sprintf(__('Welcome to %s'), ' phpMyAdmin '); + echo '

      +
      +
      + + + + + + + ' , "\n"; + if (count($GLOBALS['cfg']['Servers']) > 1) { + // offer a chance to login to other servers if the current one failed + echo '' , "\n"; + echo ' ' , "\n"; + echo '' , "\n"; + } + echo '
      '; + if (isset($GLOBALS['allowDeny_forbidden']) + && $GLOBALS['allowDeny_forbidden'] + ) { + trigger_error(__('Access denied!'), E_USER_NOTICE); + } else { + // Check whether user has configured something + if ($GLOBALS['PMA_Config']->source_mtime == 0) { + echo '

      ' , sprintf( + __( + 'You probably did not create a configuration file.' + . ' You might want to use the %1$ssetup script%2$s to' + . ' create one.' + ), + '', + '' + ) , '

      ' , "\n"; + } elseif (!isset($GLOBALS['errno']) + || (isset($GLOBALS['errno']) && $GLOBALS['errno'] != 2002) + && $GLOBALS['errno'] != 2003 + ) { + // if we display the "Server not responding" error, do not confuse + // users by telling them they have a settings problem + // (note: it's true that they could have a badly typed host name, + // but anyway the current message tells that the server + // rejected the connection, which is not really what happened) + // 2002 is the error given by mysqli + // 2003 is the error given by mysql + trigger_error( + __( + 'phpMyAdmin tried to connect to the MySQL server, and the' + . ' server rejected the connection. You should check the' + . ' host, username and password in your configuration and' + . ' make sure that they correspond to the information given' + . ' by the administrator of the MySQL server.' + ), + E_USER_WARNING + ); + } + echo Util::mysqlDie( + $conn_error, + '', + true, + '', + false + ); + } + $GLOBALS['error_handler']->dispUserErrors(); + echo '
      ' , "\n"; + echo '' + , __('Retry to connect') + , '' , "\n"; + echo '
      ' , "\n"; + echo Select::render(true, true); + echo '
      ' , "\n"; + if (!defined('TESTSUITE')) { + exit; + } + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Plugins/Auth/AuthenticationCookie.php b/php/apps/phpmyadmin49/libraries/classes/Plugins/Auth/AuthenticationCookie.php new file mode 100644 index 00000000..a681f60d --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Plugins/Auth/AuthenticationCookie.php @@ -0,0 +1,889 @@ +_use_openssl = ! class_exists('phpseclib\Crypt\Random'); + } + + /** + * Forces (not)using of openSSL + * + * @param boolean $use The flag + * + * @return void + */ + public function setUseOpenSSL($use) + { + $this->_use_openssl = $use; + } + + /** + * Displays authentication form + * + * this function MUST exit/quit the application + * + * @global string $conn_error the last connection error + * + * @return boolean|void + */ + public function showLoginForm() + { + global $conn_error; + + $response = Response::getInstance(); + if ($response->loginPage()) { + if (defined('TESTSUITE')) { + return true; + } else { + exit; + } + } + + // No recall if blowfish secret is not configured as it would produce + // garbage + if ($GLOBALS['cfg']['LoginCookieRecall'] + && ! empty($GLOBALS['cfg']['blowfish_secret']) + ) { + $default_user = $this->user; + $default_server = $GLOBALS['pma_auth_server']; + $autocomplete = ''; + } else { + $default_user = ''; + $default_server = ''; + // skip the IE autocomplete feature. + $autocomplete = ' autocomplete="off"'; + } + + echo Template::get('login/header')->render(['theme' => $GLOBALS['PMA_Theme']]); + + if ($GLOBALS['cfg']['DBG']['demo']) { + echo '
      '; + echo '' , __('phpMyAdmin Demo Server') , ''; + printf( + __( + 'You are using the demo server. You can do anything here, but ' + . 'please do not change root, debian-sys-maint and pma users. ' + . 'More information is available at %s.' + ), + 'demo.phpmyadmin.net' + ); + echo '
      '; + } + + // Show error message + if (! empty($conn_error)) { + Message::rawError($conn_error)->display(); + } elseif (isset($_GET['session_expired']) + && intval($_GET['session_expired']) == 1 + ) { + Message::rawError( + __('Your session has expired. Please log in again.') + )->display(); + } + + // Displays the languages form + $language_manager = LanguageManager::getInstance(); + if (empty($GLOBALS['cfg']['Lang']) && $language_manager->hasChoice()) { + echo "
      "; + // use fieldset, don't show doc link + echo $language_manager->getSelectorDisplay(true, false); + echo '
      '; + } + echo ' +
      + + '; + + if ($GLOBALS['error_handler']->hasDisplayErrors()) { + echo '
      '; + $GLOBALS['error_handler']->dispErrors(); + echo '
      '; + } + echo Template::get('login/footer')->render(); + echo Config::renderFooter(); + if (! defined('TESTSUITE')) { + exit; + } else { + return true; + } + } + + /** + * Gets authentication credentials + * + * this function DOES NOT check authentication - it just checks/provides + * authentication credentials required to connect to the MySQL server + * usually with $GLOBALS['dbi']->connect() + * + * it returns false if something is missing - which usually leads to + * showLoginForm() which displays login form + * + * it returns true if all seems ok which usually leads to auth_set_user() + * + * it directly switches to showFailure() if user inactivity timeout is reached + * + * @return boolean whether we get authentication settings or not + */ + public function readCredentials() + { + global $conn_error; + + // Initialization + /** + * @global $GLOBALS['pma_auth_server'] the user provided server to + * connect to + */ + $GLOBALS['pma_auth_server'] = ''; + + $this->user = $this->password = ''; + $GLOBALS['from_cookie'] = false; + + if (isset($_POST['pma_username']) && strlen($_POST['pma_username']) > 0) { + + // Verify Captcha if it is required. + if (! empty($GLOBALS['cfg']['CaptchaLoginPrivateKey']) + && ! empty($GLOBALS['cfg']['CaptchaLoginPublicKey']) + ) { + if (! empty($_POST["g-recaptcha-response"])) { + if (function_exists('curl_init')) { + $reCaptcha = new ReCaptcha\ReCaptcha( + $GLOBALS['cfg']['CaptchaLoginPrivateKey'], + new ReCaptcha\RequestMethod\CurlPost() + ); + } elseif (ini_get('allow_url_fopen')) { + $reCaptcha = new ReCaptcha\ReCaptcha( + $GLOBALS['cfg']['CaptchaLoginPrivateKey'], + new ReCaptcha\RequestMethod\Post() + ); + } else { + $reCaptcha = new ReCaptcha\ReCaptcha( + $GLOBALS['cfg']['CaptchaLoginPrivateKey'], + new ReCaptcha\RequestMethod\SocketPost() + ); + } + + // verify captcha status. + $resp = $reCaptcha->verify( + $_POST["g-recaptcha-response"], + Core::getIp() + ); + + // Check if the captcha entered is valid, if not stop the login. + if ($resp == null || ! $resp->isSuccess()) { + $codes = $resp->getErrorCodes(); + + if (in_array('invalid-json', $codes)) { + $conn_error = __('Failed to connect to the reCAPTCHA service!'); + } else { + $conn_error = __('Entered captcha is wrong, try again!'); + } + return false; + } + } else { + $conn_error = __('Missing reCAPTCHA verification, maybe it has been blocked by adblock?'); + return false; + } + } + + // The user just logged in + $this->user = Core::sanitizeMySQLUser($_POST['pma_username']); + $this->password = isset($_POST['pma_password']) ? $_POST['pma_password'] : ''; + if ($GLOBALS['cfg']['AllowArbitraryServer'] + && isset($_REQUEST['pma_servername']) + ) { + if ($GLOBALS['cfg']['ArbitraryServerRegexp']) { + $parts = explode(' ', $_REQUEST['pma_servername']); + if (count($parts) == 2) { + $tmp_host = $parts[0]; + } else { + $tmp_host = $_REQUEST['pma_servername']; + } + + $match = preg_match( + $GLOBALS['cfg']['ArbitraryServerRegexp'], $tmp_host + ); + if (! $match) { + $conn_error = __( + 'You are not allowed to log in to this MySQL server!' + ); + return false; + } + } + $GLOBALS['pma_auth_server'] = Core::sanitizeMySQLHost($_REQUEST['pma_servername']); + } + /* Secure current session on login to avoid session fixation */ + Session::secure(); + return true; + } + + // At the end, try to set the $this->user + // and $this->password variables from cookies + + // check cookies + if (empty($_COOKIE['pmaUser-' . $GLOBALS['server']])) { + return false; + } + + $value = $this->cookieDecrypt( + $_COOKIE['pmaUser-' . $GLOBALS['server']], + $this->_getEncryptionSecret() + ); + + if ($value === false) { + return false; + } + + $this->user = $value; + // user was never logged in since session start + if (empty($_SESSION['browser_access_time'])) { + return false; + } + + // User inactive too long + $last_access_time = time() - $GLOBALS['cfg']['LoginCookieValidity']; + foreach ($_SESSION['browser_access_time'] as $key => $value) { + if ($value < $last_access_time) { + unset($_SESSION['browser_access_time'][$key]); + } + } + // All sessions expired + if (empty($_SESSION['browser_access_time'])) { + Util::cacheUnset('is_create_db_priv'); + Util::cacheUnset('is_reload_priv'); + Util::cacheUnset('db_to_create'); + Util::cacheUnset('dbs_where_create_table_allowed'); + Util::cacheUnset('dbs_to_test'); + Util::cacheUnset('db_priv'); + Util::cacheUnset('col_priv'); + Util::cacheUnset('table_priv'); + Util::cacheUnset('proc_priv'); + + $this->showFailure('no-activity'); + if (! defined('TESTSUITE')) { + exit; + } else { + return false; + } + } + + // check password cookie + if (empty($_COOKIE['pmaAuth-' . $GLOBALS['server']])) { + return false; + } + $value = $this->cookieDecrypt( + $_COOKIE['pmaAuth-' . $GLOBALS['server']], + $this->_getSessionEncryptionSecret() + ); + if ($value === false) { + return false; + } + + $auth_data = json_decode($value, true); + + if (! is_array($auth_data) || ! isset($auth_data['password'])) { + return false; + } + $this->password = $auth_data['password']; + if ($GLOBALS['cfg']['AllowArbitraryServer'] && ! empty($auth_data['server'])) { + $GLOBALS['pma_auth_server'] = $auth_data['server']; + } + + $GLOBALS['from_cookie'] = true; + + return true; + } + + /** + * Set the user and password after last checkings if required + * + * @return boolean always true + */ + public function storeCredentials() + { + global $cfg; + + if ($GLOBALS['cfg']['AllowArbitraryServer'] + && ! empty($GLOBALS['pma_auth_server']) + ) { + /* Allow to specify 'host port' */ + $parts = explode(' ', $GLOBALS['pma_auth_server']); + if (count($parts) == 2) { + $tmp_host = $parts[0]; + $tmp_port = $parts[1]; + } else { + $tmp_host = $GLOBALS['pma_auth_server']; + $tmp_port = ''; + } + if ($cfg['Server']['host'] != $GLOBALS['pma_auth_server']) { + $cfg['Server']['host'] = $tmp_host; + if (! empty($tmp_port)) { + $cfg['Server']['port'] = $tmp_port; + } + } + unset($tmp_host, $tmp_port, $parts); + } + + return parent::storeCredentials(); + } + + /** + * Stores user credentials after successful login. + * + * @return void|bool + */ + public function rememberCredentials() + { + // Name and password cookies need to be refreshed each time + // Duration = one month for username + $this->storeUsernameCookie($this->user); + + // Duration = as configured + // Do not store password cookie on password change as we will + // set the cookie again after password has been changed + if (! isset($_POST['change_pw'])) { + $this->storePasswordCookie($this->password); + } + + // Set server cookies if required (once per session) and, in this case, + // force reload to ensure the client accepts cookies + if (! $GLOBALS['from_cookie']) { + // URL where to go: + $redirect_url = './index.php'; + + // any parameters to pass? + $url_params = array(); + if (strlen($GLOBALS['db']) > 0) { + $url_params['db'] = $GLOBALS['db']; + } + if (strlen($GLOBALS['table']) > 0) { + $url_params['table'] = $GLOBALS['table']; + } + // any target to pass? + if (! empty($GLOBALS['target']) + && $GLOBALS['target'] != 'index.php' + ) { + $url_params['target'] = $GLOBALS['target']; + } + + /** + * Clear user cache. + */ + Util::clearUserCache(); + + Response::getInstance() + ->disable(); + + Core::sendHeaderLocation( + $redirect_url . Url::getCommonRaw($url_params), + true + ); + if (! defined('TESTSUITE')) { + exit; + } else { + return false; + } + } // end if + + return true; + } + + /** + * Stores username in a cookie. + * + * @param string $username User name + * + * @return void + */ + public function storeUsernameCookie($username) + { + // Name and password cookies need to be refreshed each time + // Duration = one month for username + $GLOBALS['PMA_Config']->setCookie( + 'pmaUser-' . $GLOBALS['server'], + $this->cookieEncrypt( + $username, + $this->_getEncryptionSecret() + ) + ); + } + + /** + * Stores password in a cookie. + * + * @param string $password Password + * + * @return void + */ + public function storePasswordCookie($password) + { + $payload = array('password' => $password); + if ($GLOBALS['cfg']['AllowArbitraryServer'] && ! empty($GLOBALS['pma_auth_server'])) { + $payload['server'] = $GLOBALS['pma_auth_server']; + } + // Duration = as configured + $GLOBALS['PMA_Config']->setCookie( + 'pmaAuth-' . $GLOBALS['server'], + $this->cookieEncrypt( + json_encode($payload), + $this->_getSessionEncryptionSecret() + ), + null, + $GLOBALS['cfg']['LoginCookieStore'] + ); + } + + /** + * User is not allowed to login to MySQL -> authentication failed + * + * prepares error message and switches to showLoginForm() which display the error + * and the login form + * + * this function MUST exit/quit the application, + * currently done by call to showLoginForm() + * + * @param string $failure String describing why authentication has failed + * + * @return void + */ + public function showFailure($failure) + { + global $conn_error; + + parent::showFailure($failure); + + // Deletes password cookie and displays the login form + $GLOBALS['PMA_Config']->removeCookie('pmaAuth-' . $GLOBALS['server']); + + $conn_error = $this->getErrorMessage($failure); + + $response = Response::getInstance(); + + // needed for PHP-CGI (not need for FastCGI or mod-php) + $response->header('Cache-Control: no-store, no-cache, must-revalidate'); + $response->header('Pragma: no-cache'); + + $this->showLoginForm(); + } + + /** + * Returns blowfish secret or generates one if needed. + * + * @return string + */ + private function _getEncryptionSecret() + { + if (empty($GLOBALS['cfg']['blowfish_secret'])) { + return $this->_getSessionEncryptionSecret(); + } + + return $GLOBALS['cfg']['blowfish_secret']; + } + + /** + * Returns blowfish secret or generates one if needed. + * + * @return string + */ + private function _getSessionEncryptionSecret() + { + if (empty($_SESSION['encryption_key'])) { + if ($this->_use_openssl) { + $_SESSION['encryption_key'] = openssl_random_pseudo_bytes(32); + } else { + $_SESSION['encryption_key'] = Crypt\Random::string(32); + } + } + return $_SESSION['encryption_key']; + } + + /** + * Concatenates secret in order to make it 16 bytes log + * + * This doesn't add any security, just ensures the secret + * is long enough by copying it. + * + * @param string $secret Original secret + * + * @return string + */ + public function enlargeSecret($secret) + { + while (strlen($secret) < 16) { + $secret .= $secret; + } + return substr($secret, 0, 16); + } + + /** + * Derives MAC secret from encryption secret. + * + * @param string $secret the secret + * + * @return string the MAC secret + */ + public function getMACSecret($secret) + { + // Grab first part, up to 16 chars + // The MAC and AES secrets can overlap if original secret is short + $length = strlen($secret); + if ($length > 16) { + return substr($secret, 0, 16); + } + return $this->enlargeSecret( + $length == 1 ? $secret : substr($secret, 0, -1) + ); + } + + /** + * Derives AES secret from encryption secret. + * + * @param string $secret the secret + * + * @return string the AES secret + */ + public function getAESSecret($secret) + { + // Grab second part, up to 16 chars + // The MAC and AES secrets can overlap if original secret is short + $length = strlen($secret); + if ($length > 16) { + return substr($secret, -16); + } + return $this->enlargeSecret( + $length == 1 ? $secret : substr($secret, 1) + ); + } + + /** + * Cleans any SSL errors + * + * This can happen from corrupted cookies, by invalid encryption + * parameters used in older phpMyAdmin versions or by wrong openSSL + * configuration. + * + * In neither case the error is useful to user, but we need to clear + * the error buffer as otherwise the errors would pop up later, for + * example during MySQL SSL setup. + * + * @return void + */ + public function cleanSSLErrors() + { + if (function_exists('openssl_error_string')) { + while (($ssl_err = openssl_error_string()) !== false) { + } + } + } + + /** + * Encryption using openssl's AES or phpseclib's AES + * (phpseclib uses mcrypt when it is available) + * + * @param string $data original data + * @param string $secret the secret + * + * @return string the encrypted result + */ + public function cookieEncrypt($data, $secret) + { + $mac_secret = $this->getMACSecret($secret); + $aes_secret = $this->getAESSecret($secret); + $iv = $this->createIV(); + if ($this->_use_openssl) { + $result = openssl_encrypt( + $data, + 'AES-128-CBC', + $aes_secret, + 0, + $iv + ); + } else { + $cipher = new Crypt\AES(Crypt\Base::MODE_CBC); + $cipher->setIV($iv); + $cipher->setKey($aes_secret); + $result = base64_encode($cipher->encrypt($data)); + } + $this->cleanSSLErrors(); + $iv = base64_encode($iv); + return json_encode( + array( + 'iv' => $iv, + 'mac' => hash_hmac('sha1', $iv . $result, $mac_secret), + 'payload' => $result, + ) + ); + } + + /** + * Decryption using openssl's AES or phpseclib's AES + * (phpseclib uses mcrypt when it is available) + * + * @param string $encdata encrypted data + * @param string $secret the secret + * + * @return string|false original data, false on error + */ + public function cookieDecrypt($encdata, $secret) + { + $data = json_decode($encdata, true); + + if (! is_array($data) || ! isset($data['mac']) || ! isset($data['iv']) || ! isset($data['payload']) + || ! is_string($data['mac']) || ! is_string($data['iv']) || ! is_string($data['payload']) + ) { + return false; + } + + $mac_secret = $this->getMACSecret($secret); + $aes_secret = $this->getAESSecret($secret); + $newmac = hash_hmac('sha1', $data['iv'] . $data['payload'], $mac_secret); + + if (! hash_equals($data['mac'], $newmac)) { + return false; + } + + if ($this->_use_openssl) { + $result = openssl_decrypt( + $data['payload'], + 'AES-128-CBC', + $aes_secret, + 0, + base64_decode($data['iv']) + ); + } else { + $cipher = new Crypt\AES(Crypt\Base::MODE_CBC); + $cipher->setIV(base64_decode($data['iv'])); + $cipher->setKey($aes_secret); + $result = $cipher->decrypt(base64_decode($data['payload'])); + } + $this->cleanSSLErrors(); + return $result; + } + + /** + * Returns size of IV for encryption. + * + * @return int + */ + public function getIVSize() + { + if ($this->_use_openssl) { + return openssl_cipher_iv_length('AES-128-CBC'); + } + $cipher = new Crypt\AES(Crypt\Base::MODE_CBC); + return $cipher->block_size; + } + + /** + * Initialization + * Store the initialization vector because it will be needed for + * further decryption. I don't think necessary to have one iv + * per server so I don't put the server number in the cookie name. + * + * @return string + */ + public function createIV() + { + /* Testsuite shortcut only to allow predictable IV */ + if (! is_null($this->_cookie_iv)) { + return $this->_cookie_iv; + } + if ($this->_use_openssl) { + return openssl_random_pseudo_bytes( + $this->getIVSize() + ); + } + + return Crypt\Random::string( + $this->getIVSize() + ); + } + + /** + * Sets encryption IV to use + * + * This is for testing only! + * + * @param string $vector The IV + * + * @return void + */ + public function setIV($vector) + { + $this->_cookie_iv = $vector; + } + + /** + * Callback when user changes password. + * + * @param string $password New password to set + * + * @return void + */ + public function handlePasswordChange($password) + { + $this->storePasswordCookie($password); + } + + /** + * Perform logout + * + * @return void + */ + public function logOut() + { + // -> delete password cookie(s) + if ($GLOBALS['cfg']['LoginCookieDeleteAll']) { + foreach ($GLOBALS['cfg']['Servers'] as $key => $val) { + $GLOBALS['PMA_Config']->removeCookie('pmaAuth-' . $key); + if (isset($_COOKIE['pmaAuth-' . $key])) { + unset($_COOKIE['pmaAuth-' . $key]); + } + } + } else { + $GLOBALS['PMA_Config']->removeCookie( + 'pmaAuth-' . $GLOBALS['server'] + ); + if (isset($_COOKIE['pmaAuth-' . $GLOBALS['server']])) { + unset($_COOKIE['pmaAuth-' . $GLOBALS['server']]); + } + } + parent::logOut(); + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Plugins/Auth/AuthenticationHttp.php b/php/apps/phpmyadmin49/libraries/classes/Plugins/Auth/AuthenticationHttp.php new file mode 100644 index 00000000..3b8f0d89 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Plugins/Auth/AuthenticationHttp.php @@ -0,0 +1,214 @@ +isAjax()) { + $response->setRequestStatus(false); + // reload_flag removes the token parameter from the URL and reloads + $response->addJSON('reload_flag', '1'); + if (defined('TESTSUITE')) { + return true; + } else { + exit; + } + } + + return $this->authForm(); + } + + /** + * Displays authentication form + * + * @return boolean + */ + public function authForm() + { + if (empty($GLOBALS['cfg']['Server']['auth_http_realm'])) { + if (empty($GLOBALS['cfg']['Server']['verbose'])) { + $server_message = $GLOBALS['cfg']['Server']['host']; + } else { + $server_message = $GLOBALS['cfg']['Server']['verbose']; + } + $realm_message = 'phpMyAdmin ' . $server_message; + } else { + $realm_message = $GLOBALS['cfg']['Server']['auth_http_realm']; + } + + $response = Response::getInstance(); + + // remove non US-ASCII to respect RFC2616 + $realm_message = preg_replace('/[^\x20-\x7e]/i', '', $realm_message); + $response->header('WWW-Authenticate: Basic realm="' . $realm_message . '"'); + $response->setHttpResponseCode(401); + + /* HTML header */ + $footer = $response->getFooter(); + $footer->setMinimal(); + $header = $response->getHeader(); + $header->setTitle(__('Access denied!')); + $header->disableMenuAndConsole(); + $header->setBodyId('loginform'); + + $response->addHTML('

      '); + $response->addHTML(sprintf(__('Welcome to %s'), ' phpMyAdmin')); + $response->addHTML('

      '); + $response->addHTML('

      '); + $response->addHTML( + Message::error( + __('Wrong username/password. Access denied.') + ) + ); + $response->addHTML('

      '); + + $response->addHTML(Config::renderFooter()); + + if (!defined('TESTSUITE')) { + exit; + } else { + return false; + } + } + + /** + * Gets authentication credentials + * + * @return boolean whether we get authentication settings or not + */ + public function readCredentials() + { + // Grabs the $PHP_AUTH_USER variable + if (isset($GLOBALS['PHP_AUTH_USER'])) { + $this->user = $GLOBALS['PHP_AUTH_USER']; + } + if (empty($this->user)) { + if (Core::getenv('PHP_AUTH_USER')) { + $this->user = Core::getenv('PHP_AUTH_USER'); + } elseif (Core::getenv('REMOTE_USER')) { + // CGI, might be encoded, see below + $this->user = Core::getenv('REMOTE_USER'); + } elseif (Core::getenv('REDIRECT_REMOTE_USER')) { + // CGI, might be encoded, see below + $this->user = Core::getenv('REDIRECT_REMOTE_USER'); + } elseif (Core::getenv('AUTH_USER')) { + // WebSite Professional + $this->user = Core::getenv('AUTH_USER'); + } elseif (Core::getenv('HTTP_AUTHORIZATION')) { + // IIS, might be encoded, see below + $this->user = Core::getenv('HTTP_AUTHORIZATION'); + } elseif (Core::getenv('Authorization')) { + // FastCGI, might be encoded, see below + $this->user = Core::getenv('Authorization'); + } + } + // Grabs the $PHP_AUTH_PW variable + if (isset($GLOBALS['PHP_AUTH_PW'])) { + $this->password = $GLOBALS['PHP_AUTH_PW']; + } + if (empty($this->password)) { + if (Core::getenv('PHP_AUTH_PW')) { + $this->password = Core::getenv('PHP_AUTH_PW'); + } elseif (Core::getenv('REMOTE_PASSWORD')) { + // Apache/CGI + $this->password = Core::getenv('REMOTE_PASSWORD'); + } elseif (Core::getenv('AUTH_PASSWORD')) { + // WebSite Professional + $this->password = Core::getenv('AUTH_PASSWORD'); + } + } + // Sanitize empty password login + if (is_null($this->password)) { + $this->password = ''; + } + + // Avoid showing the password in phpinfo()'s output + unset($GLOBALS['PHP_AUTH_PW']); + unset($_SERVER['PHP_AUTH_PW']); + + // Decode possibly encoded information (used by IIS/CGI/FastCGI) + // (do not use explode() because a user might have a colon in his password + if (strcmp(substr($this->user, 0, 6), 'Basic ') == 0) { + $usr_pass = base64_decode(substr($this->user, 6)); + if (!empty($usr_pass)) { + $colon = strpos($usr_pass, ':'); + if ($colon) { + $this->user = substr($usr_pass, 0, $colon); + $this->password = substr($usr_pass, $colon + 1); + } + unset($colon); + } + unset($usr_pass); + } + + // sanitize username + $this->user = Core::sanitizeMySQLUser($this->user); + + // User logged out -> ensure the new username is not the same + $old_usr = isset($_REQUEST['old_usr']) ? $_REQUEST['old_usr'] : ''; + if (! empty($old_usr) + && (isset($this->user) && hash_equals($old_usr, $this->user)) + ) { + $this->user = ''; + } + + // Returns whether we get authentication settings or not + return !empty($this->user); + } + + /** + * User is not allowed to login to MySQL -> authentication failed + * + * @param string $failure String describing why authentication has failed + * + * @return void + */ + public function showFailure($failure) + { + parent::showFailure($failure); + $error = $GLOBALS['dbi']->getError(); + if ($error && $GLOBALS['errno'] != 1045) { + Core::fatalError($error); + } else { + $this->authForm(); + } + } + + /** + * Returns URL for login form. + * + * @return string + */ + public function getLoginFormURL() + { + return './index.php?old_usr=' . $this->user; + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Plugins/Auth/AuthenticationSignon.php b/php/apps/phpmyadmin49/libraries/classes/Plugins/Auth/AuthenticationSignon.php new file mode 100644 index 00000000..a6d1c7bf --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Plugins/Auth/AuthenticationSignon.php @@ -0,0 +1,280 @@ +=')) { + session_set_cookie_params($sessionCookieParams); + } + + session_set_cookie_params( + $sessionCookieParams['lifetime'], + $sessionCookieParams['path'], + $sessionCookieParams['domain'], + $sessionCookieParams['secure'], + $sessionCookieParams['httponly'] + ); + } + + /** + * Gets authentication credentials + * + * @return boolean whether we get authentication settings or not + */ + public function readCredentials() + { + /* Check if we're using same signon server */ + $signon_url = $GLOBALS['cfg']['Server']['SignonURL']; + if (isset($_SESSION['LAST_SIGNON_URL']) + && $_SESSION['LAST_SIGNON_URL'] != $signon_url + ) { + return false; + } + + /* Script name */ + $script_name = $GLOBALS['cfg']['Server']['SignonScript']; + + /* Session name */ + $session_name = $GLOBALS['cfg']['Server']['SignonSession']; + + /* Login URL */ + $signon_url = $GLOBALS['cfg']['Server']['SignonURL']; + + /* Current host */ + $single_signon_host = $GLOBALS['cfg']['Server']['host']; + + /* Current port */ + $single_signon_port = $GLOBALS['cfg']['Server']['port']; + + /* No configuration updates */ + $single_signon_cfgupdate = array(); + + /* Handle script based auth */ + if (!empty($script_name)) { + if (!@file_exists($script_name)) { + Core::fatalError( + __('Can not find signon authentication script:') + . ' ' . $script_name + ); + } + include $script_name; + + list ($this->user, $this->password) + = get_login_credentials($GLOBALS['cfg']['Server']['user']); + } elseif (isset($_COOKIE[$session_name])) { /* Does session exist? */ + /* End current session */ + $old_session = session_name(); + $old_id = session_id(); + $oldCookieParams = session_get_cookie_params(); + if (!defined('TESTSUITE')) { + session_write_close(); + } + /* Load single signon session */ + if (!defined('TESTSUITE')) { + $this->setCookieParams(); + session_name($session_name); + session_id($_COOKIE[$session_name]); + session_start(); + } + + /* Clear error message */ + unset($_SESSION['PMA_single_signon_error_message']); + + /* Grab credentials if they exist */ + if (isset($_SESSION['PMA_single_signon_user'])) { + $this->user = $_SESSION['PMA_single_signon_user']; + } + if (isset($_SESSION['PMA_single_signon_password'])) { + $this->password = $_SESSION['PMA_single_signon_password']; + } + if (isset($_SESSION['PMA_single_signon_host'])) { + $single_signon_host = $_SESSION['PMA_single_signon_host']; + } + + if (isset($_SESSION['PMA_single_signon_port'])) { + $single_signon_port = $_SESSION['PMA_single_signon_port']; + } + + if (isset($_SESSION['PMA_single_signon_cfgupdate'])) { + $single_signon_cfgupdate = $_SESSION['PMA_single_signon_cfgupdate']; + } + + /* Also get token as it is needed to access subpages */ + if (isset($_SESSION['PMA_single_signon_token'])) { + /* No need to care about token on logout */ + $pma_token = $_SESSION['PMA_single_signon_token']; + } + + /* End single signon session */ + if (!defined('TESTSUITE')) { + session_write_close(); + } + + /* Restart phpMyAdmin session */ + if (!defined('TESTSUITE')) { + $this->setCookieParams($oldCookieParams); + session_name($old_session); + if (!empty($old_id)) { + session_id($old_id); + } + session_start(); + } + + /* Set the single signon host */ + $GLOBALS['cfg']['Server']['host'] = $single_signon_host; + + /* Set the single signon port */ + $GLOBALS['cfg']['Server']['port'] = $single_signon_port; + + /* Configuration update */ + $GLOBALS['cfg']['Server'] = array_merge( + $GLOBALS['cfg']['Server'], + $single_signon_cfgupdate + ); + + /* Restore our token */ + if (!empty($pma_token)) { + $_SESSION[' PMA_token '] = $pma_token; + $_SESSION[' HMAC_secret '] = Util::generateRandom(16); + } + + /** + * Clear user cache. + */ + Util::clearUserCache(); + } + + // Returns whether we get authentication settings or not + if (empty($this->user)) { + unset($_SESSION['LAST_SIGNON_URL']); + + return false; + } + + $_SESSION['LAST_SIGNON_URL'] = $GLOBALS['cfg']['Server']['SignonURL']; + + return true; + } + + /** + * User is not allowed to login to MySQL -> authentication failed + * + * @param string $failure String describing why authentication has failed + * + * @return void + */ + public function showFailure($failure) + { + parent::showFailure($failure); + + /* Session name */ + $session_name = $GLOBALS['cfg']['Server']['SignonSession']; + + /* Does session exist? */ + if (isset($_COOKIE[$session_name])) { + if (!defined('TESTSUITE')) { + /* End current session */ + session_write_close(); + + /* Load single signon session */ + $this->setCookieParams(); + session_name($session_name); + session_id($_COOKIE[$session_name]); + session_start(); + } + + /* Set error message */ + $_SESSION['PMA_single_signon_error_message'] = $this->getErrorMessage($failure); + } + $this->showLoginForm(); + } + + /** + * Returns URL for login form. + * + * @return string + */ + public function getLoginFormURL() + { + return $GLOBALS['cfg']['Server']['SignonURL']; + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Plugins/AuthenticationPlugin.php b/php/apps/phpmyadmin49/libraries/classes/Plugins/AuthenticationPlugin.php new file mode 100644 index 00000000..b174df01 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Plugins/AuthenticationPlugin.php @@ -0,0 +1,347 @@ +setSessionAccessTime(); + + $cfg['Server']['user'] = $this->user; + $cfg['Server']['password'] = $this->password; + + return true; + } + + /** + * Stores user credentials after successful login. + * + * @return void + */ + public function rememberCredentials() + { + } + + /** + * User is not allowed to login to MySQL -> authentication failed + * + * @param string $failure String describing why authentication has failed + * + * @return void + */ + public function showFailure($failure) + { + Logging::logUser($this->user, $failure); + } + + /** + * Perform logout + * + * @return void + */ + public function logOut() + { + /* Obtain redirect URL (before doing logout) */ + if (! empty($GLOBALS['cfg']['Server']['LogoutURL'])) { + $redirect_url = $GLOBALS['cfg']['Server']['LogoutURL']; + } else { + $redirect_url = $this->getLoginFormURL(); + } + + /* Clear credentials */ + $this->user = ''; + $this->password = ''; + + /* + * Get a logged-in server count in case of LoginCookieDeleteAll is disabled. + */ + $server = 0; + if ($GLOBALS['cfg']['LoginCookieDeleteAll'] === false + && $GLOBALS['cfg']['Server']['auth_type'] == 'cookie' + ) { + foreach ($GLOBALS['cfg']['Servers'] as $key => $val) { + if (isset($_COOKIE['pmaAuth-' . $key])) { + $server = $key; + } + } + } + + if ($server === 0) { + /* delete user's choices that were stored in session */ + if (! defined('TESTSUITE')) { + session_unset(); + session_destroy(); + } + + /* Redirect to login form (or configured URL) */ + Core::sendHeaderLocation($redirect_url); + } else { + /* Redirect to other autenticated server */ + $_SESSION['partial_logout'] = true; + Core::sendHeaderLocation( + './index.php' . Url::getCommonRaw(array('server' => $server)) + ); + } + } + + /** + * Returns URL for login form. + * + * @return string + */ + public function getLoginFormURL() + { + return './index.php'; + } + + /** + * Returns error message for failed authentication. + * + * @param string $failure String describing why authentication has failed + * + * @return string + */ + public function getErrorMessage($failure) + { + if ($failure == 'empty-denied') { + return __( + 'Login without a password is forbidden by configuration' + . ' (see AllowNoPassword)' + ); + } elseif ($failure == 'root-denied' || $failure == 'allow-denied') { + return __('Access denied!'); + } elseif ($failure == 'no-activity') { + return sprintf( + __('No activity within %s seconds; please log in again.'), + intval($GLOBALS['cfg']['LoginCookieValidity']) + ); + } + + $dbi_error = $GLOBALS['dbi']->getError(); + if (!empty($dbi_error)) { + return htmlspecialchars($dbi_error); + } elseif (isset($GLOBALS['errno'])) { + return '#' . $GLOBALS['errno'] . ' ' + . __('Cannot log in to the MySQL server'); + } + + return __('Cannot log in to the MySQL server'); + } + + /** + * Callback when user changes password. + * + * @param string $password New password to set + * + * @return void + */ + public function handlePasswordChange($password) + { + } + + /** + * Store session access time in session. + * + * Tries to workaround PHP 5 session garbage collection which + * looks at the session file's last modified time + * + * @return void + */ + public function setSessionAccessTime() + { + if (isset($_REQUEST['guid'])) { + $guid = (string)$_REQUEST['guid']; + } else { + $guid = 'default'; + } + if (isset($_REQUEST['access_time'])) { + // Ensure access_time is in range <0, LoginCookieValidity + 1> + // to avoid excessive extension of validity. + // + // Negative values can cause session expiry extension + // Too big values can cause overflow and lead to same + $time = time() - min(max(0, intval($_REQUEST['access_time'])), $GLOBALS['cfg']['LoginCookieValidity'] + 1); + } else { + $time = time(); + } + $_SESSION['browser_access_time'][$guid] = $time; + } + + /** + * High level authentication interface + * + * Gets the credentials or shows login form if necessary + * + * @return void + */ + public function authenticate() + { + $success = $this->readCredentials(); + + /* Show login form (this exits) */ + if (! $success) { + /* Force generating of new session */ + Session::secure(); + $this->showLoginForm(); + } + + /* Store credentials (eg. in cookies) */ + $this->storeCredentials(); + /* Check allow/deny rules */ + $this->checkRules(); + } + + /** + * Check configuration defined restrictions for authentication + * + * @return void + */ + public function checkRules() + { + global $cfg; + + // Check IP-based Allow/Deny rules as soon as possible to reject the + // user based on mod_access in Apache + if (isset($cfg['Server']['AllowDeny']) + && isset($cfg['Server']['AllowDeny']['order']) + ) { + $allowDeny_forbidden = false; // default + if ($cfg['Server']['AllowDeny']['order'] == 'allow,deny') { + $allowDeny_forbidden = true; + if (IpAllowDeny::allowDeny('allow')) { + $allowDeny_forbidden = false; + } + if (IpAllowDeny::allowDeny('deny')) { + $allowDeny_forbidden = true; + } + } elseif ($cfg['Server']['AllowDeny']['order'] == 'deny,allow') { + if (IpAllowDeny::allowDeny('deny')) { + $allowDeny_forbidden = true; + } + if (IpAllowDeny::allowDeny('allow')) { + $allowDeny_forbidden = false; + } + } elseif ($cfg['Server']['AllowDeny']['order'] == 'explicit') { + if (IpAllowDeny::allowDeny('allow') && ! IpAllowDeny::allowDeny('deny')) { + $allowDeny_forbidden = false; + } else { + $allowDeny_forbidden = true; + } + } // end if ... elseif ... elseif + + // Ejects the user if banished + if ($allowDeny_forbidden) { + $this->showFailure('allow-denied'); + } + } // end if + + // is root allowed? + if (! $cfg['Server']['AllowRoot'] && $cfg['Server']['user'] == 'root') { + $this->showFailure('root-denied'); + } + + // is a login without password allowed? + if (! $cfg['Server']['AllowNoPassword'] + && $cfg['Server']['password'] === '' + ) { + $this->showFailure('empty-denied'); + } + } + + /** + * Checks whether two factor authentication is active + * for given user and performs it. + * + * @return void + */ + public function checkTwoFactor() + { + $twofactor = new TwoFactor($this->user); + + /* Do we need to show the form? */ + if ($twofactor->check()) { + return; + } + + $response = Response::getInstance(); + if ($response->loginPage()) { + if (defined('TESTSUITE')) { + return true; + } else { + exit; + } + } + echo Template::get('login/header')->render(['theme' => $GLOBALS['PMA_Theme']]); + Message::rawNotice( + __('You have enabled two factor authentication, please confirm your login.') + )->display(); + echo Template::get('login/twofactor')->render([ + 'form' => $twofactor->render(), + 'show_submit' => $twofactor->showSubmit, + ]); + echo Template::get('login/footer')->render(); + echo Config::renderFooter(); + if (! defined('TESTSUITE')) { + exit; + } + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Plugins/Export/ExportCodegen.php b/php/apps/phpmyadmin49/libraries/classes/Plugins/Export/ExportCodegen.php new file mode 100644 index 00000000..b2a2763f --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Plugins/Export/ExportCodegen.php @@ -0,0 +1,444 @@ +initSpecificVariables(); + $this->setProperties(); + } + + /** + * Initialize the local variables that are used for export CodeGen + * + * @return void + */ + protected function initSpecificVariables() + { + $this->_setCgFormats( + array( + "NHibernate C# DO", + "NHibernate XML", + ) + ); + + $this->_setCgHandlers( + array( + "_handleNHibernateCSBody", + "_handleNHibernateXMLBody", + ) + ); + } + + /** + * Sets the export CodeGen properties + * + * @return void + */ + protected function setProperties() + { + $exportPluginProperties = new ExportPluginProperties(); + $exportPluginProperties->setText('CodeGen'); + $exportPluginProperties->setExtension('cs'); + $exportPluginProperties->setMimeType('text/cs'); + $exportPluginProperties->setOptionsText(__('Options')); + + // create the root group that will be the options field for + // $exportPluginProperties + // this will be shown as "Format specific options" + $exportSpecificOptions = new OptionsPropertyRootGroup( + "Format Specific Options" + ); + + // general options main group + $generalOptions = new OptionsPropertyMainGroup("general_opts"); + // create primary items and add them to the group + $leaf = new HiddenPropertyItem("structure_or_data"); + $generalOptions->addProperty($leaf); + $leaf = new SelectPropertyItem( + "format", + __('Format:') + ); + $leaf->setValues($this->_getCgFormats()); + $generalOptions->addProperty($leaf); + // add the main group to the root group + $exportSpecificOptions->addProperty($generalOptions); + + // set the options for the export plugin property item + $exportPluginProperties->setOptions($exportSpecificOptions); + $this->properties = $exportPluginProperties; + } + + /** + * Outputs export header + * + * @return bool Whether it succeeded + */ + public function exportHeader() + { + return true; + } + + /** + * Outputs export footer + * + * @return bool Whether it succeeded + */ + public function exportFooter() + { + return true; + } + + /** + * Outputs database header + * + * @param string $db Database name + * @param string $db_alias Aliases of db + * + * @return bool Whether it succeeded + */ + public function exportDBHeader($db, $db_alias = '') + { + return true; + } + + /** + * Outputs database footer + * + * @param string $db Database name + * + * @return bool Whether it succeeded + */ + public function exportDBFooter($db) + { + return true; + } + + /** + * Outputs CREATE DATABASE statement + * + * @param string $db Database name + * @param string $export_type 'server', 'database', 'table' + * @param string $db_alias Aliases of db + * + * @return bool Whether it succeeded + */ + public function exportDBCreate($db, $export_type, $db_alias = '') + { + return true; + } + + /** + * Outputs the content of a table in NHibernate format + * + * @param string $db database name + * @param string $table table name + * @param string $crlf the end of line sequence + * @param string $error_url the url to go back in case of error + * @param string $sql_query SQL query for obtaining data + * @param array $aliases Aliases of db/table/columns + * + * @return bool Whether it succeeded + */ + public function exportData( + $db, + $table, + $crlf, + $error_url, + $sql_query, + array $aliases = array() + ) { + $CG_FORMATS = $this->_getCgFormats(); + $CG_HANDLERS = $this->_getCgHandlers(); + + $format = $GLOBALS['codegen_format']; + if (isset($CG_FORMATS[$format])) { + $method = $CG_HANDLERS[$format]; + + return Export::outputHandler( + $this->$method($db, $table, $crlf, $aliases) + ); + } + + return Export::outputHandler(sprintf("%s is not supported.", $format)); + } + + /** + * Used to make identifiers (from table or database names) + * + * @param string $str name to be converted + * @param bool $ucfirst whether to make the first character uppercase + * + * @return string identifier + */ + public static function cgMakeIdentifier($str, $ucfirst = true) + { + // remove unsafe characters + $str = preg_replace('/[^\p{L}\p{Nl}_]/u', '', $str); + // make sure first character is a letter or _ + if (!preg_match('/^\pL/u', $str)) { + $str = '_' . $str; + } + if ($ucfirst) { + $str = ucfirst($str); + } + + return $str; + } + + /** + * C# Handler + * + * @param string $db database name + * @param string $table table name + * @param string $crlf line separator + * @param array $aliases Aliases of db/table/columns + * + * @return string containing C# code lines, separated by "\n" + */ + private function _handleNHibernateCSBody($db, $table, $crlf, array $aliases = array()) + { + $db_alias = $db; + $table_alias = $table; + $this->initAlias($aliases, $db_alias, $table_alias); + $lines = array(); + + $result = $GLOBALS['dbi']->query( + sprintf( + 'DESC %s.%s', + Util::backquote($db), + Util::backquote($table) + ) + ); + if ($result) { + /** @var TableProperty[] $tableProperties */ + $tableProperties = array(); + while ($row = $GLOBALS['dbi']->fetchRow($result)) { + $col_as = $this->getAlias($aliases, $row[0], 'col', $db, $table); + if (!empty($col_as)) { + $row[0] = $col_as; + } + $tableProperties[] = new TableProperty($row); + } + $GLOBALS['dbi']->freeResult($result); + $lines[] = 'using System;'; + $lines[] = 'using System.Collections;'; + $lines[] = 'using System.Collections.Generic;'; + $lines[] = 'using System.Text;'; + $lines[] = 'namespace ' . ExportCodegen::cgMakeIdentifier($db_alias); + $lines[] = '{'; + $lines[] = ' #region ' + . ExportCodegen::cgMakeIdentifier($table_alias); + $lines[] = ' public class ' + . ExportCodegen::cgMakeIdentifier($table_alias); + $lines[] = ' {'; + $lines[] = ' #region Member Variables'; + foreach ($tableProperties as $tableProperty) { + $lines[] = $tableProperty->formatCs( + ' protected #dotNetPrimitiveType# _#name#;' + ); + } + $lines[] = ' #endregion'; + $lines[] = ' #region Constructors'; + $lines[] = ' public ' + . ExportCodegen::cgMakeIdentifier($table_alias) . '() { }'; + $temp = array(); + foreach ($tableProperties as $tableProperty) { + if (!$tableProperty->isPK()) { + $temp[] = $tableProperty->formatCs( + '#dotNetPrimitiveType# #name#' + ); + } + } + $lines[] = ' public ' + . ExportCodegen::cgMakeIdentifier($table_alias) + . '(' + . implode(', ', $temp) + . ')'; + $lines[] = ' {'; + foreach ($tableProperties as $tableProperty) { + if (!$tableProperty->isPK()) { + $lines[] = $tableProperty->formatCs( + ' this._#name#=#name#;' + ); + } + } + $lines[] = ' }'; + $lines[] = ' #endregion'; + $lines[] = ' #region Public Properties'; + foreach ($tableProperties as $tableProperty) { + $lines[] = $tableProperty->formatCs( + ' public virtual #dotNetPrimitiveType# #ucfirstName#' + . "\n" + . ' {' . "\n" + . ' get {return _#name#;}' . "\n" + . ' set {_#name#=value;}' . "\n" + . ' }' + ); + } + $lines[] = ' #endregion'; + $lines[] = ' }'; + $lines[] = ' #endregion'; + $lines[] = '}'; + } + + return implode($crlf, $lines); + } + + /** + * XML Handler + * + * @param string $db database name + * @param string $table table name + * @param string $crlf line separator + * @param array $aliases Aliases of db/table/columns + * + * @return string containing XML code lines, separated by "\n" + */ + private function _handleNHibernateXMLBody( + $db, + $table, + $crlf, + array $aliases = array() + ) { + $db_alias = $db; + $table_alias = $table; + $this->initAlias($aliases, $db_alias, $table_alias); + $lines = array(); + $lines[] = ''; + $lines[] = ''; + $lines[] = ' '; + $result = $GLOBALS['dbi']->query( + sprintf( + "DESC %s.%s", + Util::backquote($db), + Util::backquote($table) + ) + ); + if ($result) { + while ($row = $GLOBALS['dbi']->fetchRow($result)) { + $col_as = $this->getAlias($aliases, $row[0], 'col', $db, $table); + if (!empty($col_as)) { + $row[0] = $col_as; + } + $tableProperty = new TableProperty($row); + if ($tableProperty->isPK()) { + $lines[] = $tableProperty->formatXml( + ' ' . "\n" + . ' ' . "\n" + . ' ' . "\n" + . ' ' + ); + } else { + $lines[] = $tableProperty->formatXml( + ' ' . "\n" + . ' ' . "\n" + . ' ' + ); + } + } + $GLOBALS['dbi']->freeResult($result); + } + $lines[] = ' '; + $lines[] = ''; + + return implode($crlf, $lines); + } + + + /* ~~~~~~~~~~~~~~~~~~~~ Getters and Setters ~~~~~~~~~~~~~~~~~~~~ */ + + /** + * Getter for CodeGen formats + * + * @return array + */ + private function _getCgFormats() + { + return $this->_cgFormats; + } + + /** + * Setter for CodeGen formats + * + * @param array $CG_FORMATS contains CodeGen Formats + * + * @return void + */ + private function _setCgFormats(array $CG_FORMATS) + { + $this->_cgFormats = $CG_FORMATS; + } + + /** + * Getter for CodeGen handlers + * + * @return array + */ + private function _getCgHandlers() + { + return $this->_cgHandlers; + } + + /** + * Setter for CodeGen handlers + * + * @param array $CG_HANDLERS contains CodeGen handler methods + * + * @return void + */ + private function _setCgHandlers(array $CG_HANDLERS) + { + $this->_cgHandlers = $CG_HANDLERS; + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Plugins/Export/ExportCsv.php b/php/apps/phpmyadmin49/libraries/classes/Plugins/Export/ExportCsv.php new file mode 100644 index 00000000..13f2c87b --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Plugins/Export/ExportCsv.php @@ -0,0 +1,332 @@ +setProperties(); + } + + /** + * Sets the export CSV properties + * + * @return void + */ + protected function setProperties() + { + $exportPluginProperties = new ExportPluginProperties(); + $exportPluginProperties->setText('CSV'); + $exportPluginProperties->setExtension('csv'); + $exportPluginProperties->setMimeType('text/comma-separated-values'); + $exportPluginProperties->setOptionsText(__('Options')); + + // create the root group that will be the options field for + // $exportPluginProperties + // this will be shown as "Format specific options" + $exportSpecificOptions = new OptionsPropertyRootGroup( + "Format Specific Options" + ); + + // general options main group + $generalOptions = new OptionsPropertyMainGroup("general_opts"); + // create leaf items and add them to the group + $leaf = new TextPropertyItem( + "separator", + __('Columns separated with:') + ); + $generalOptions->addProperty($leaf); + $leaf = new TextPropertyItem( + "enclosed", + __('Columns enclosed with:') + ); + $generalOptions->addProperty($leaf); + $leaf = new TextPropertyItem( + "escaped", + __('Columns escaped with:') + ); + $generalOptions->addProperty($leaf); + $leaf = new TextPropertyItem( + "terminated", + __('Lines terminated with:') + ); + $generalOptions->addProperty($leaf); + $leaf = new TextPropertyItem( + 'null', + __('Replace NULL with:') + ); + $generalOptions->addProperty($leaf); + $leaf = new BoolPropertyItem( + 'removeCRLF', + __('Remove carriage return/line feed characters within columns') + ); + $generalOptions->addProperty($leaf); + $leaf = new BoolPropertyItem( + 'columns', + __('Put columns names in the first row') + ); + $generalOptions->addProperty($leaf); + $leaf = new HiddenPropertyItem( + 'structure_or_data' + ); + $generalOptions->addProperty($leaf); + // add the main group to the root group + $exportSpecificOptions->addProperty($generalOptions); + + // set the options for the export plugin property item + $exportPluginProperties->setOptions($exportSpecificOptions); + $this->properties = $exportPluginProperties; + } + + /** + * Outputs export header + * + * @return bool Whether it succeeded + */ + public function exportHeader() + { + global $what, $csv_terminated, $csv_separator, $csv_enclosed, $csv_escaped; + + // Here we just prepare some values for export + if ($what == 'excel') { + $csv_terminated = "\015\012"; + switch ($GLOBALS['excel_edition']) { + case 'win': + // as tested on Windows with Excel 2002 and Excel 2007 + $csv_separator = ';'; + break; + case 'mac_excel2003': + $csv_separator = ';'; + break; + case 'mac_excel2008': + $csv_separator = ','; + break; + } + $csv_enclosed = '"'; + $csv_escaped = '"'; + if (isset($GLOBALS['excel_columns'])) { + $GLOBALS['csv_columns'] = 'yes'; + } + } else { + if (empty($csv_terminated) + || mb_strtolower($csv_terminated) == 'auto' + ) { + $csv_terminated = $GLOBALS['crlf']; + } else { + $csv_terminated = str_replace('\\r', "\015", $csv_terminated); + $csv_terminated = str_replace('\\n', "\012", $csv_terminated); + $csv_terminated = str_replace('\\t', "\011", $csv_terminated); + } // end if + $csv_separator = str_replace('\\t', "\011", $csv_separator); + } + + return true; + } + + /** + * Outputs export footer + * + * @return bool Whether it succeeded + */ + public function exportFooter() + { + return true; + } + + /** + * Outputs database header + * + * @param string $db Database name + * @param string $db_alias Alias of db + * + * @return bool Whether it succeeded + */ + public function exportDBHeader($db, $db_alias = '') + { + return true; + } + + /** + * Outputs database footer + * + * @param string $db Database name + * + * @return bool Whether it succeeded + */ + public function exportDBFooter($db) + { + return true; + } + + /** + * Outputs CREATE DATABASE statement + * + * @param string $db Database name + * @param string $export_type 'server', 'database', 'table' + * @param string $db_alias Aliases of db + * + * @return bool Whether it succeeded + */ + public function exportDBCreate($db, $export_type, $db_alias = '') + { + return true; + } + + /** + * Outputs the content of a table in CSV format + * + * @param string $db database name + * @param string $table table name + * @param string $crlf the end of line sequence + * @param string $error_url the url to go back in case of error + * @param string $sql_query SQL query for obtaining data + * @param array $aliases Aliases of db/table/columns + * + * @return bool Whether it succeeded + */ + public function exportData( + $db, + $table, + $crlf, + $error_url, + $sql_query, + array $aliases = array() + ) { + global $what, $csv_terminated, $csv_separator, $csv_enclosed, $csv_escaped; + + $db_alias = $db; + $table_alias = $table; + $this->initAlias($aliases, $db_alias, $table_alias); + + // Gets the data from the database + $result = $GLOBALS['dbi']->query( + $sql_query, + DatabaseInterface::CONNECT_USER, + DatabaseInterface::QUERY_UNBUFFERED + ); + $fields_cnt = $GLOBALS['dbi']->numFields($result); + + // If required, get fields name at the first line + if (isset($GLOBALS['csv_columns'])) { + $schema_insert = ''; + for ($i = 0; $i < $fields_cnt; $i++) { + $col_as = $GLOBALS['dbi']->fieldName($result, $i); + if (!empty($aliases[$db]['tables'][$table]['columns'][$col_as])) { + $col_as = $aliases[$db]['tables'][$table]['columns'][$col_as]; + } + $col_as = stripslashes($col_as); + if ($csv_enclosed == '') { + $schema_insert .= $col_as; + } else { + $schema_insert .= $csv_enclosed + . str_replace( + $csv_enclosed, + $csv_escaped . $csv_enclosed, + $col_as + ) + . $csv_enclosed; + } + $schema_insert .= $csv_separator; + } // end for + $schema_insert = trim(mb_substr($schema_insert, 0, -1)); + if (!Export::outputHandler($schema_insert . $csv_terminated)) { + return false; + } + } // end if + + // Format the data + while ($row = $GLOBALS['dbi']->fetchRow($result)) { + $schema_insert = ''; + for ($j = 0; $j < $fields_cnt; $j++) { + if (!isset($row[$j]) || is_null($row[$j])) { + $schema_insert .= $GLOBALS[$what . '_null']; + } elseif ($row[$j] == '0' || $row[$j] != '') { + // always enclose fields + if ($what == 'excel') { + $row[$j] = preg_replace("/\015(\012)?/", "\012", $row[$j]); + } + // remove CRLF characters within field + if (isset($GLOBALS[$what . '_removeCRLF']) + && $GLOBALS[$what . '_removeCRLF'] + ) { + $row[$j] = str_replace( + "\n", + "", + str_replace( + "\r", + "", + $row[$j] + ) + ); + } + if ($csv_enclosed == '') { + $schema_insert .= $row[$j]; + } else { + // also double the escape string if found in the data + if ($csv_escaped != $csv_enclosed) { + $schema_insert .= $csv_enclosed + . str_replace( + $csv_enclosed, + $csv_escaped . $csv_enclosed, + str_replace( + $csv_escaped, + $csv_escaped . $csv_escaped, + $row[$j] + ) + ) + . $csv_enclosed; + } else { + // avoid a problem when escape string equals enclose + $schema_insert .= $csv_enclosed + . str_replace( + $csv_enclosed, + $csv_escaped . $csv_enclosed, + $row[$j] + ) + . $csv_enclosed; + } + } + } else { + $schema_insert .= ''; + } + if ($j < $fields_cnt - 1) { + $schema_insert .= $csv_separator; + } + } // end for + + if (!Export::outputHandler($schema_insert . $csv_terminated)) { + return false; + } + } // end while + $GLOBALS['dbi']->freeResult($result); + + return true; + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Plugins/Export/ExportExcel.php b/php/apps/phpmyadmin49/libraries/classes/Plugins/Export/ExportExcel.php new file mode 100644 index 00000000..fa4622ad --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Plugins/Export/ExportExcel.php @@ -0,0 +1,88 @@ +setText('CSV for MS Excel'); + $exportPluginProperties->setExtension('csv'); + $exportPluginProperties->setMimeType('text/comma-separated-values'); + $exportPluginProperties->setOptionsText(__('Options')); + + // create the root group that will be the options field for + // $exportPluginProperties + // this will be shown as "Format specific options" + $exportSpecificOptions = new OptionsPropertyRootGroup( + "Format Specific Options" + ); + + // general options main group + $generalOptions = new OptionsPropertyMainGroup("general_opts"); + // create primary items and add them to the group + $leaf = new TextPropertyItem( + 'null', + __('Replace NULL with:') + ); + $generalOptions->addProperty($leaf); + $leaf = new BoolPropertyItem( + 'removeCRLF', + __('Remove carriage return/line feed characters within columns') + ); + $generalOptions->addProperty($leaf); + $leaf = new BoolPropertyItem( + 'columns', + __('Put columns names in the first row') + ); + $generalOptions->addProperty($leaf); + $leaf = new SelectPropertyItem( + 'edition', + __('Excel edition:') + ); + $leaf->setValues( + array( + 'win' => 'Windows', + 'mac_excel2003' => 'Excel 2003 / Macintosh', + 'mac_excel2008' => 'Excel 2008 / Macintosh', + ) + ); + $generalOptions->addProperty($leaf); + $leaf = new HiddenPropertyItem( + 'structure_or_data' + ); + $generalOptions->addProperty($leaf); + // add the main group to the root group + $exportSpecificOptions->addProperty($generalOptions); + + // set the options for the export plugin property item + $exportPluginProperties->setOptions($exportSpecificOptions); + $this->properties = $exportPluginProperties; + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Plugins/Export/ExportHtmlword.php b/php/apps/phpmyadmin49/libraries/classes/Plugins/Export/ExportHtmlword.php new file mode 100644 index 00000000..5aa72935 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Plugins/Export/ExportHtmlword.php @@ -0,0 +1,666 @@ +setProperties(); + } + + /** + * Sets the export HTML-Word properties + * + * @return void + */ + protected function setProperties() + { + $exportPluginProperties = new ExportPluginProperties(); + $exportPluginProperties->setText('Microsoft Word 2000'); + $exportPluginProperties->setExtension('doc'); + $exportPluginProperties->setMimeType('application/vnd.ms-word'); + $exportPluginProperties->setForceFile(true); + $exportPluginProperties->setOptionsText(__('Options')); + + // create the root group that will be the options field for + // $exportPluginProperties + // this will be shown as "Format specific options" + $exportSpecificOptions = new OptionsPropertyRootGroup( + "Format Specific Options" + ); + + // what to dump (structure/data/both) + $dumpWhat = new OptionsPropertyMainGroup( + "dump_what", __('Dump table') + ); + // create primary items and add them to the group + $leaf = new RadioPropertyItem("structure_or_data"); + $leaf->setValues( + array( + 'structure' => __('structure'), + 'data' => __('data'), + 'structure_and_data' => __('structure and data'), + ) + ); + $dumpWhat->addProperty($leaf); + // add the main group to the root group + $exportSpecificOptions->addProperty($dumpWhat); + + // data options main group + $dataOptions = new OptionsPropertyMainGroup( + "dump_what", __('Data dump options') + ); + $dataOptions->setForce('structure'); + // create primary items and add them to the group + $leaf = new TextPropertyItem( + "null", + __('Replace NULL with:') + ); + $dataOptions->addProperty($leaf); + $leaf = new BoolPropertyItem( + "columns", + __('Put columns names in the first row') + ); + $dataOptions->addProperty($leaf); + // add the main group to the root group + $exportSpecificOptions->addProperty($dataOptions); + + // set the options for the export plugin property item + $exportPluginProperties->setOptions($exportSpecificOptions); + $this->properties = $exportPluginProperties; + } + + /** + * Outputs export header + * + * @return bool Whether it succeeded + */ + public function exportHeader() + { + global $charset; + + return Export::outputHandler( + ' + + + + + + + ' + ); + } + + /** + * Outputs export footer + * + * @return bool Whether it succeeded + */ + public function exportFooter() + { + return Export::outputHandler(''); + } + + /** + * Outputs database header + * + * @param string $db Database name + * @param string $db_alias Aliases of db + * + * @return bool Whether it succeeded + */ + public function exportDBHeader($db, $db_alias = '') + { + if (empty($db_alias)) { + $db_alias = $db; + } + + return Export::outputHandler( + '

      ' . __('Database') . ' ' . htmlspecialchars($db_alias) . '

      ' + ); + } + + /** + * Outputs database footer + * + * @param string $db Database name + * + * @return bool Whether it succeeded + */ + public function exportDBFooter($db) + { + return true; + } + + /** + * Outputs CREATE DATABASE statement + * + * @param string $db Database name + * @param string $export_type 'server', 'database', 'table' + * @param string $db_alias Aliases of db + * + * @return bool Whether it succeeded + */ + public function exportDBCreate($db, $export_type, $db_alias = '') + { + return true; + } + + /** + * Outputs the content of a table in HTML-Word format + * + * @param string $db database name + * @param string $table table name + * @param string $crlf the end of line sequence + * @param string $error_url the url to go back in case of error + * @param string $sql_query SQL query for obtaining data + * @param array $aliases Aliases of db/table/columns + * + * @return bool Whether it succeeded + */ + public function exportData( + $db, + $table, + $crlf, + $error_url, + $sql_query, + array $aliases = array() + ) { + global $what; + + $db_alias = $db; + $table_alias = $table; + $this->initAlias($aliases, $db_alias, $table_alias); + + if (!Export::outputHandler( + '

      ' + . __('Dumping data for table') . ' ' . htmlspecialchars($table_alias) + . '

      ' + ) + ) { + return false; + } + if (!Export::outputHandler( + '' + ) + ) { + return false; + } + + // Gets the data from the database + $result = $GLOBALS['dbi']->query( + $sql_query, + DatabaseInterface::CONNECT_USER, + DatabaseInterface::QUERY_UNBUFFERED + ); + $fields_cnt = $GLOBALS['dbi']->numFields($result); + + // If required, get fields name at the first line + if (isset($GLOBALS['htmlword_columns'])) { + $schema_insert = ''; + for ($i = 0; $i < $fields_cnt; $i++) { + $col_as = $GLOBALS['dbi']->fieldName($result, $i); + if (!empty($aliases[$db]['tables'][$table]['columns'][$col_as])) { + $col_as = $aliases[$db]['tables'][$table]['columns'][$col_as]; + } + $col_as = stripslashes($col_as); + $schema_insert .= ''; + } // end for + $schema_insert .= ''; + if (!Export::outputHandler($schema_insert)) { + return false; + } + } // end if + + // Format the data + while ($row = $GLOBALS['dbi']->fetchRow($result)) { + $schema_insert = ''; + for ($j = 0; $j < $fields_cnt; $j++) { + if (!isset($row[$j]) || is_null($row[$j])) { + $value = $GLOBALS[$what . '_null']; + } elseif ($row[$j] == '0' || $row[$j] != '') { + $value = $row[$j]; + } else { + $value = ''; + } + $schema_insert .= ''; + } // end for + $schema_insert .= ''; + if (!Export::outputHandler($schema_insert)) { + return false; + } + } // end while + $GLOBALS['dbi']->freeResult($result); + + return Export::outputHandler('
      '); + } + + /** + * Returns a stand-in CREATE definition to resolve view dependencies + * + * @param string $db the database name + * @param string $view the view name + * @param string $crlf the end of line sequence + * @param array $aliases Aliases of db/table/columns + * + * @return string resulting definition + */ + public function getTableDefStandIn($db, $view, $crlf, $aliases = array()) + { + $schema_insert = '' + . '' + . '' + . '' + . '' + . '' + . ''; + + /** + * Get the unique keys in the view + */ + $unique_keys = array(); + $keys = $GLOBALS['dbi']->getTableIndexes($db, $view); + foreach ($keys as $key) { + if ($key['Non_unique'] == 0) { + $unique_keys[] = $key['Column_name']; + } + } + + $columns = $GLOBALS['dbi']->getColumns($db, $view); + foreach ($columns as $column) { + $col_as = $column['Field']; + if (!empty($aliases[$db]['tables'][$view]['columns'][$col_as])) { + $col_as = $aliases[$db]['tables'][$view]['columns'][$col_as]; + } + $schema_insert .= $this->formatOneColumnDefinition( + $column, + $unique_keys, + $col_as + ); + $schema_insert .= ''; + } + + $schema_insert .= '
      '; + + return $schema_insert; + } + + /** + * Returns $table's CREATE definition + * + * @param string $db the database name + * @param string $table the table name + * @param bool $do_relation whether to include relation comments + * @param bool $do_comments whether to include the pmadb-style column + * comments as comments in the structure; + * this is deprecated but the parameter is + * left here because export.php calls + * PMA_exportStructure() also for other + * export types which use this parameter + * @param bool $do_mime whether to include mime comments + * at the end + * @param bool $view whether we're handling a view + * @param array $aliases Aliases of db/table/columns + * + * @return string resulting schema + */ + public function getTableDef( + $db, + $table, + $do_relation, + $do_comments, + $do_mime, + $view = false, + array $aliases = array() + ) { + // set $cfgRelation here, because there is a chance that it's modified + // since the class initialization + global $cfgRelation; + + $schema_insert = ''; + + /** + * Gets fields properties + */ + $GLOBALS['dbi']->selectDb($db); + + // Check if we can use Relations + list($res_rel, $have_rel) = $this->relation->getRelationsAndStatus( + $do_relation && !empty($cfgRelation['relation']), + $db, + $table + ); + + /** + * Displays the table structure + */ + $schema_insert .= ''; + + $schema_insert .= ''; + $schema_insert .= ''; + $schema_insert .= ''; + $schema_insert .= ''; + $schema_insert .= ''; + if ($do_relation && $have_rel) { + $schema_insert .= ''; + } + if ($do_comments) { + $schema_insert .= ''; + $comments = $this->relation->getComments($db, $table); + } + if ($do_mime && $cfgRelation['mimework']) { + $schema_insert .= ''; + $mime_map = Transformations::getMIME($db, $table, true); + } + $schema_insert .= ''; + + $columns = $GLOBALS['dbi']->getColumns($db, $table); + /** + * Get the unique keys in the table + */ + $unique_keys = array(); + $keys = $GLOBALS['dbi']->getTableIndexes($db, $table); + foreach ($keys as $key) { + if ($key['Non_unique'] == 0) { + $unique_keys[] = $key['Column_name']; + } + } + foreach ($columns as $column) { + $col_as = $column['Field']; + if (!empty($aliases[$db]['tables'][$table]['columns'][$col_as])) { + $col_as = $aliases[$db]['tables'][$table]['columns'][$col_as]; + } + $schema_insert .= $this->formatOneColumnDefinition( + $column, + $unique_keys, + $col_as + ); + $field_name = $column['Field']; + if ($do_relation && $have_rel) { + $schema_insert .= ''; + } + if ($do_comments && $cfgRelation['commwork']) { + $schema_insert .= ''; + } + if ($do_mime && $cfgRelation['mimework']) { + $schema_insert .= ''; + } + + $schema_insert .= ''; + } // end foreach + + $schema_insert .= '
      ' + . htmlspecialchars( + $this->getRelationString( + $res_rel, + $field_name, + $db, + $aliases + ) + ) + . '' + . (isset($comments[$field_name]) + ? htmlspecialchars($comments[$field_name]) + : '') . '' + . (isset($mime_map[$field_name]) ? + htmlspecialchars( + str_replace('_', '/', $mime_map[$field_name]['mimetype']) + ) + : '') . '
      '; + + return $schema_insert; + } + + /** + * Outputs triggers + * + * @param string $db database name + * @param string $table table name + * + * @return string Formatted triggers list + */ + protected function getTriggers($db, $table) + { + $dump = ''; + $dump .= ''; + $dump .= ''; + $dump .= ''; + $dump .= ''; + $dump .= ''; + $dump .= ''; + + $triggers = $GLOBALS['dbi']->getTriggers($db, $table); + + foreach ($triggers as $trigger) { + $dump .= ''; + $dump .= '' + . '' + . '' + . '' + . ''; + } + + $dump .= '
      '; + + return $dump; + } + + /** + * Outputs table's structure + * + * @param string $db database name + * @param string $table table name + * @param string $crlf the end of line sequence + * @param string $error_url the url to go back in case of error + * @param string $export_mode 'create_table', 'triggers', 'create_view', + * 'stand_in' + * @param string $export_type 'server', 'database', 'table' + * @param bool $do_relation whether to include relation comments + * @param bool $do_comments whether to include the pmadb-style column + * comments as comments in the structure; + * this is deprecated but the parameter is + * left here because export.php calls + * PMA_exportStructure() also for other + * export types which use this parameter + * @param bool $do_mime whether to include mime comments + * @param bool $dates whether to include creation/update/check dates + * @param array $aliases Aliases of db/table/columns + * + * @return bool Whether it succeeded + */ + public function exportStructure( + $db, + $table, + $crlf, + $error_url, + $export_mode, + $export_type, + $do_relation = false, + $do_comments = false, + $do_mime = false, + $dates = false, + array $aliases = array() + ) { + $db_alias = $db; + $table_alias = $table; + $this->initAlias($aliases, $db_alias, $table_alias); + + $dump = ''; + + switch ($export_mode) { + case 'create_table': + $dump .= '

      ' + . __('Table structure for table') . ' ' + . htmlspecialchars($table_alias) + . '

      '; + $dump .= $this->getTableDef( + $db, + $table, + $do_relation, + $do_comments, + $do_mime, + false, + $aliases + ); + break; + case 'triggers': + $dump = ''; + $triggers = $GLOBALS['dbi']->getTriggers($db, $table); + if ($triggers) { + $dump .= '

      ' + . __('Triggers') . ' ' . htmlspecialchars($table_alias) + . '

      '; + $dump .= $this->getTriggers($db, $table); + } + break; + case 'create_view': + $dump .= '

      ' + . __('Structure for view') . ' ' . htmlspecialchars($table_alias) + . '

      '; + $dump .= $this->getTableDef( + $db, + $table, + $do_relation, + $do_comments, + $do_mime, + true, + $aliases + ); + break; + case 'stand_in': + $dump .= '

      ' + . __('Stand-in structure for view') . ' ' + . htmlspecialchars($table_alias) + . '

      '; + // export a stand-in definition to resolve view dependencies + $dump .= $this->getTableDefStandIn($db, $table, $crlf, $aliases); + } // end switch + + return Export::outputHandler($dump); + } + + /** + * Formats the definition for one column + * + * @param array $column info about this column + * @param array $unique_keys unique keys of the table + * @param string $col_alias Column Alias + * + * @return string Formatted column definition + */ + protected function formatOneColumnDefinition( + array $column, + array $unique_keys, + $col_alias = '' + ) { + if (empty($col_alias)) { + $col_alias = $column['Field']; + } + $definition = ''; + + $extracted_columnspec = Util::extractColumnSpec($column['Type']); + + $type = htmlspecialchars($extracted_columnspec['print_type']); + if (empty($type)) { + $type = ' '; + } + + if (!isset($column['Default'])) { + if ($column['Null'] != 'NO') { + $column['Default'] = 'NULL'; + } + } + + $fmt_pre = ''; + $fmt_post = ''; + if (in_array($column['Field'], $unique_keys)) { + $fmt_pre = '' . $fmt_pre; + $fmt_post = $fmt_post . ''; + } + if ($column['Key'] == 'PRI') { + $fmt_pre = '' . $fmt_pre; + $fmt_post = $fmt_post . ''; + } + $definition .= '' . $fmt_pre + . htmlspecialchars($col_alias) . $fmt_post . ''; + $definition .= '' . htmlspecialchars($type) . ''; + $definition .= '' + . (($column['Null'] == '' || $column['Null'] == 'NO') + ? __('No') + : __('Yes')) + . ''; + $definition .= '' + . htmlspecialchars(isset($column['Default']) ? $column['Default'] : '') + . ''; + + return $definition; + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Plugins/Export/ExportJson.php b/php/apps/phpmyadmin49/libraries/classes/Plugins/Export/ExportJson.php new file mode 100644 index 00000000..27a2dc32 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Plugins/Export/ExportJson.php @@ -0,0 +1,293 @@ +setProperties(); + } + + /** + * Encodes the data into JSON + * + * @param mixed $data Data to encode + * + * @return string + */ + public function encode($data) + { + $options = 0; + if (isset($GLOBALS['json_pretty_print']) + && $GLOBALS['json_pretty_print'] + ) { + $options |= JSON_PRETTY_PRINT; + } + if (isset($GLOBALS['json_unicode']) + && $GLOBALS['json_unicode'] + ) { + $options |= JSON_UNESCAPED_UNICODE; + } + return json_encode($data, $options); + } + + /** + * Sets the export JSON properties + * + * @return void + */ + protected function setProperties() + { + $exportPluginProperties = new ExportPluginProperties(); + $exportPluginProperties->setText('JSON'); + $exportPluginProperties->setExtension('json'); + $exportPluginProperties->setMimeType('text/plain'); + $exportPluginProperties->setOptionsText(__('Options')); + + // create the root group that will be the options field for + // $exportPluginProperties + // this will be shown as "Format specific options" + $exportSpecificOptions = new OptionsPropertyRootGroup( + "Format Specific Options" + ); + + // general options main group + $generalOptions = new OptionsPropertyMainGroup("general_opts"); + // create primary items and add them to the group + $leaf = new HiddenPropertyItem("structure_or_data"); + $generalOptions->addProperty($leaf); + + $leaf = new BoolPropertyItem( + 'pretty_print', + __('Output pretty-printed JSON (Use human-readable formatting)') + ); + $generalOptions->addProperty($leaf); + + $leaf = new BoolPropertyItem( + 'unicode', + __('Output unicode characters unescaped') + ); + $generalOptions->addProperty($leaf); + + // add the main group to the root group + $exportSpecificOptions->addProperty($generalOptions); + + // set the options for the export plugin property item + $exportPluginProperties->setOptions($exportSpecificOptions); + $this->properties = $exportPluginProperties; + } + + /** + * Outputs export header + * + * @return bool Whether it succeeded + */ + public function exportHeader() + { + global $crlf; + + $meta = array( + 'type' => 'header', + 'version' => PMA_VERSION, + 'comment' => 'Export to JSON plugin for PHPMyAdmin', + ); + + return Export::outputHandler( + '[' . $crlf . $this->encode($meta) . ',' . $crlf + ); + } + + /** + * Outputs export footer + * + * @return bool Whether it succeeded + */ + public function exportFooter() + { + global $crlf; + + return Export::outputHandler(']' . $crlf); + } + + /** + * Outputs database header + * + * @param string $db Database name + * @param string $db_alias Aliases of db + * + * @return bool Whether it succeeded + */ + public function exportDBHeader($db, $db_alias = '') + { + global $crlf; + + if (empty($db_alias)) { + $db_alias = $db; + } + + $meta = array( + 'type' => 'database', + 'name' => $db_alias + ); + + return Export::outputHandler( + $this->encode($meta) . ',' . $crlf + ); + } + + /** + * Outputs database footer + * + * @param string $db Database name + * + * @return bool Whether it succeeded + */ + public function exportDBFooter($db) + { + return true; + } + + /** + * Outputs CREATE DATABASE statement + * + * @param string $db Database name + * @param string $export_type 'server', 'database', 'table' + * @param string $db_alias Aliases of db + * + * @return bool Whether it succeeded + */ + public function exportDBCreate($db, $export_type, $db_alias = '') + { + return true; + } + + /** + * Outputs the content of a table in JSON format + * + * @param string $db database name + * @param string $table table name + * @param string $crlf the end of line sequence + * @param string $error_url the url to go back in case of error + * @param string $sql_query SQL query for obtaining data + * @param array $aliases Aliases of db/table/columns + * + * @return bool Whether it succeeded + */ + public function exportData( + $db, + $table, + $crlf, + $error_url, + $sql_query, + array $aliases = array() + ) { + $db_alias = $db; + $table_alias = $table; + $this->initAlias($aliases, $db_alias, $table_alias); + + if (! $this->first) { + if (!Export::outputHandler(',')) { + return false; + } + } else { + $this->first = false; + } + + $buffer = $this->encode( + array( + 'type' => 'table', + 'name' => $table_alias, + 'database' => $db_alias, + 'data' => "@@DATA@@" + ) + ); + list($header, $footer) = explode('"@@DATA@@"', $buffer); + + if (!Export::outputHandler($header . $crlf . '[' . $crlf)) { + return false; + } + + $result = $GLOBALS['dbi']->query( + $sql_query, + DatabaseInterface::CONNECT_USER, + DatabaseInterface::QUERY_UNBUFFERED + ); + $columns_cnt = $GLOBALS['dbi']->numFields($result); + $fields_meta = $GLOBALS['dbi']->getFieldsMeta($result); + + $columns = array(); + for ($i = 0; $i < $columns_cnt; $i++) { + $col_as = $GLOBALS['dbi']->fieldName($result, $i); + if (!empty($aliases[$db]['tables'][$table]['columns'][$col_as])) { + $col_as = $aliases[$db]['tables'][$table]['columns'][$col_as]; + } + $columns[$i] = stripslashes($col_as); + } + + $record_cnt = 0; + while ($record = $GLOBALS['dbi']->fetchRow($result)) { + + $record_cnt++; + + // Output table name as comment if this is the first record of the table + if ($record_cnt > 1) { + if (!Export::outputHandler(',' . $crlf)) { + return false; + } + } + + $data = array(); + + for ($i = 0; $i < $columns_cnt; $i++) { + if ($fields_meta[$i]->type === 'geometry') { + // export GIS types as hex + $record[$i] = '0x' . bin2hex($record[$i]); + } + $data[$columns[$i]] = $record[$i]; + } + + $encodedData = $this->encode($data); + if (! $encodedData) { + return false; + } + if (! Export::outputHandler($encodedData)) { + return false; + } + } + + if (!Export::outputHandler($crlf . ']' . $crlf . $footer . $crlf)) { + return false; + } + + $GLOBALS['dbi']->freeResult($result); + + return true; + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Plugins/Export/ExportLatex.php b/php/apps/phpmyadmin49/libraries/classes/Plugins/Export/ExportLatex.php new file mode 100644 index 00000000..f9cebe18 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Plugins/Export/ExportLatex.php @@ -0,0 +1,678 @@ +initSpecificVariables(); + $this->setProperties(); + } + + /** + * Initialize the local variables that are used for export Latex + * + * @return void + */ + protected function initSpecificVariables() + { + /* Messages used in default captions */ + $GLOBALS['strLatexContent'] = __('Content of table @TABLE@'); + $GLOBALS['strLatexContinued'] = __('(continued)'); + $GLOBALS['strLatexStructure'] = __('Structure of table @TABLE@'); + } + + /** + * Sets the export Latex properties + * + * @return void + */ + protected function setProperties() + { + global $plugin_param; + $hide_structure = false; + if ($plugin_param['export_type'] == 'table' + && !$plugin_param['single_table'] + ) { + $hide_structure = true; + } + + $exportPluginProperties = new ExportPluginProperties(); + $exportPluginProperties->setText('LaTeX'); + $exportPluginProperties->setExtension('tex'); + $exportPluginProperties->setMimeType('application/x-tex'); + $exportPluginProperties->setOptionsText(__('Options')); + + // create the root group that will be the options field for + // $exportPluginProperties + // this will be shown as "Format specific options" + $exportSpecificOptions = new OptionsPropertyRootGroup( + "Format Specific Options" + ); + + // general options main group + $generalOptions = new OptionsPropertyMainGroup("general_opts"); + // create primary items and add them to the group + $leaf = new BoolPropertyItem( + "caption", + __('Include table caption') + ); + $generalOptions->addProperty($leaf); + // add the main group to the root group + $exportSpecificOptions->addProperty($generalOptions); + + // what to dump (structure/data/both) main group + $dumpWhat = new OptionsPropertyMainGroup( + "dump_what", __('Dump table') + ); + // create primary items and add them to the group + $leaf = new RadioPropertyItem("structure_or_data"); + $leaf->setValues( + array( + 'structure' => __('structure'), + 'data' => __('data'), + 'structure_and_data' => __('structure and data'), + ) + ); + $dumpWhat->addProperty($leaf); + // add the main group to the root group + $exportSpecificOptions->addProperty($dumpWhat); + + // structure options main group + if (!$hide_structure) { + $structureOptions = new OptionsPropertyMainGroup( + "structure", __('Object creation options') + ); + $structureOptions->setForce('data'); + // create primary items and add them to the group + $leaf = new TextPropertyItem( + "structure_caption", + __('Table caption:') + ); + $leaf->setDoc('faq6-27'); + $structureOptions->addProperty($leaf); + $leaf = new TextPropertyItem( + "structure_continued_caption", + __('Table caption (continued):') + ); + $leaf->setDoc('faq6-27'); + $structureOptions->addProperty($leaf); + $leaf = new TextPropertyItem( + "structure_label", + __('Label key:') + ); + $leaf->setDoc('faq6-27'); + $structureOptions->addProperty($leaf); + if (!empty($GLOBALS['cfgRelation']['relation'])) { + $leaf = new BoolPropertyItem( + "relation", + __('Display foreign key relationships') + ); + $structureOptions->addProperty($leaf); + } + $leaf = new BoolPropertyItem( + "comments", + __('Display comments') + ); + $structureOptions->addProperty($leaf); + if (!empty($GLOBALS['cfgRelation']['mimework'])) { + $leaf = new BoolPropertyItem( + "mime", + __('Display MIME types') + ); + $structureOptions->addProperty($leaf); + } + // add the main group to the root group + $exportSpecificOptions->addProperty($structureOptions); + } + + // data options main group + $dataOptions = new OptionsPropertyMainGroup( + "data", __('Data dump options') + ); + $dataOptions->setForce('structure'); + // create primary items and add them to the group + $leaf = new BoolPropertyItem( + "columns", + __('Put columns names in the first row:') + ); + $dataOptions->addProperty($leaf); + $leaf = new TextPropertyItem( + "data_caption", + __('Table caption:') + ); + $leaf->setDoc('faq6-27'); + $dataOptions->addProperty($leaf); + $leaf = new TextPropertyItem( + "data_continued_caption", + __('Table caption (continued):') + ); + $leaf->setDoc('faq6-27'); + $dataOptions->addProperty($leaf); + $leaf = new TextPropertyItem( + "data_label", + __('Label key:') + ); + $leaf->setDoc('faq6-27'); + $dataOptions->addProperty($leaf); + $leaf = new TextPropertyItem( + 'null', + __('Replace NULL with:') + ); + $dataOptions->addProperty($leaf); + // add the main group to the root group + $exportSpecificOptions->addProperty($dataOptions); + + // set the options for the export plugin property item + $exportPluginProperties->setOptions($exportSpecificOptions); + $this->properties = $exportPluginProperties; + } + + /** + * Outputs export header + * + * @return bool Whether it succeeded + */ + public function exportHeader() + { + global $crlf; + global $cfg; + + $head = '% phpMyAdmin LaTeX Dump' . $crlf + . '% version ' . PMA_VERSION . $crlf + . '% https://www.phpmyadmin.net/' . $crlf + . '%' . $crlf + . '% ' . __('Host:') . ' ' . $cfg['Server']['host']; + if (!empty($cfg['Server']['port'])) { + $head .= ':' . $cfg['Server']['port']; + } + $head .= $crlf + . '% ' . __('Generation Time:') . ' ' + . Util::localisedDate() . $crlf + . '% ' . __('Server version:') . ' ' . $GLOBALS['dbi']->getVersionString() . $crlf + . '% ' . __('PHP Version:') . ' ' . phpversion() . $crlf; + + return Export::outputHandler($head); + } + + /** + * Outputs export footer + * + * @return bool Whether it succeeded + */ + public function exportFooter() + { + return true; + } + + /** + * Outputs database header + * + * @param string $db Database name + * @param string $db_alias Aliases of db + * + * @return bool Whether it succeeded + */ + public function exportDBHeader($db, $db_alias = '') + { + if (empty($db_alias)) { + $db_alias = $db; + } + global $crlf; + $head = '% ' . $crlf + . '% ' . __('Database:') . ' ' . '\'' . $db_alias . '\'' . $crlf + . '% ' . $crlf; + + return Export::outputHandler($head); + } + + /** + * Outputs database footer + * + * @param string $db Database name + * + * @return bool Whether it succeeded + */ + public function exportDBFooter($db) + { + return true; + } + + /** + * Outputs CREATE DATABASE statement + * + * @param string $db Database name + * @param string $export_type 'server', 'database', 'table' + * @param string $db_alias Aliases of db + * + * @return bool Whether it succeeded + */ + public function exportDBCreate($db, $export_type, $db_alias = '') + { + return true; + } + + /** + * Outputs the content of a table in JSON format + * + * @param string $db database name + * @param string $table table name + * @param string $crlf the end of line sequence + * @param string $error_url the url to go back in case of error + * @param string $sql_query SQL query for obtaining data + * @param array $aliases Aliases of db/table/columns + * + * @return bool Whether it succeeded + */ + public function exportData( + $db, + $table, + $crlf, + $error_url, + $sql_query, + array $aliases = array() + ) { + $db_alias = $db; + $table_alias = $table; + $this->initAlias($aliases, $db_alias, $table_alias); + + $result = $GLOBALS['dbi']->tryQuery( + $sql_query, + DatabaseInterface::CONNECT_USER, + DatabaseInterface::QUERY_UNBUFFERED + ); + + $columns_cnt = $GLOBALS['dbi']->numFields($result); + $columns = array(); + $columns_alias = array(); + for ($i = 0; $i < $columns_cnt; $i++) { + $columns[$i] = $col_as = $GLOBALS['dbi']->fieldName($result, $i); + if (!empty($aliases[$db]['tables'][$table]['columns'][$col_as])) { + $col_as = $aliases[$db]['tables'][$table]['columns'][$col_as]; + } + $columns_alias[$i] = $col_as; + } + + $buffer = $crlf . '%' . $crlf . '% ' . __('Data:') . ' ' . $table_alias + . $crlf . '%' . $crlf . ' \\begin{longtable}{|'; + + for ($index = 0; $index < $columns_cnt; $index++) { + $buffer .= 'l|'; + } + $buffer .= '} ' . $crlf; + + $buffer .= ' \\hline \\endhead \\hline \\endfoot \\hline ' . $crlf; + if (isset($GLOBALS['latex_caption'])) { + $buffer .= ' \\caption{' + . Util::expandUserString( + $GLOBALS['latex_data_caption'], + array( + 'texEscape', + get_class($this), + ), + array('table' => $table_alias, 'database' => $db_alias) + ) + . '} \\label{' + . Util::expandUserString( + $GLOBALS['latex_data_label'], + null, + array('table' => $table_alias, 'database' => $db_alias) + ) + . '} \\\\'; + } + if (!Export::outputHandler($buffer)) { + return false; + } + + // show column names + if (isset($GLOBALS['latex_columns'])) { + $buffer = '\\hline '; + for ($i = 0; $i < $columns_cnt; $i++) { + $buffer .= '\\multicolumn{1}{|c|}{\\textbf{' + . self::texEscape(stripslashes($columns_alias[$i])) . '}} & '; + } + + $buffer = mb_substr($buffer, 0, -2) . '\\\\ \\hline \hline '; + if (!Export::outputHandler($buffer . ' \\endfirsthead ' . $crlf)) { + return false; + } + if (isset($GLOBALS['latex_caption'])) { + if (!Export::outputHandler( + '\\caption{' + . Util::expandUserString( + $GLOBALS['latex_data_continued_caption'], + array( + 'texEscape', + get_class($this), + ), + array('table' => $table_alias, 'database' => $db_alias) + ) + . '} \\\\ ' + ) + ) { + return false; + } + } + if (!Export::outputHandler($buffer . '\\endhead \\endfoot' . $crlf)) { + return false; + } + } else { + if (!Export::outputHandler('\\\\ \hline')) { + return false; + } + } + + // print the whole table + while ($record = $GLOBALS['dbi']->fetchAssoc($result)) { + $buffer = ''; + // print each row + for ($i = 0; $i < $columns_cnt; $i++) { + if ((!function_exists('is_null') + || !is_null($record[$columns[$i]])) + && isset($record[$columns[$i]]) + ) { + $column_value = self::texEscape( + stripslashes($record[$columns[$i]]) + ); + } else { + $column_value = $GLOBALS['latex_null']; + } + + // last column ... no need for & character + if ($i == ($columns_cnt - 1)) { + $buffer .= $column_value; + } else { + $buffer .= $column_value . " & "; + } + } + $buffer .= ' \\\\ \\hline ' . $crlf; + if (!Export::outputHandler($buffer)) { + return false; + } + } + + $buffer = ' \\end{longtable}' . $crlf; + if (!Export::outputHandler($buffer)) { + return false; + } + + $GLOBALS['dbi']->freeResult($result); + + return true; + } // end getTableLaTeX + + /** + * Outputs table's structure + * + * @param string $db database name + * @param string $table table name + * @param string $crlf the end of line sequence + * @param string $error_url the url to go back in case of error + * @param string $export_mode 'create_table', 'triggers', 'create_view', + * 'stand_in' + * @param string $export_type 'server', 'database', 'table' + * @param bool $do_relation whether to include relation comments + * @param bool $do_comments whether to include the pmadb-style column + * comments as comments in the structure; + * this is deprecated but the parameter is + * left here because export.php calls + * exportStructure() also for other + * export types which use this parameter + * @param bool $do_mime whether to include mime comments + * @param bool $dates whether to include creation/update/check dates + * @param array $aliases Aliases of db/table/columns + * + * @return bool Whether it succeeded + */ + public function exportStructure( + $db, + $table, + $crlf, + $error_url, + $export_mode, + $export_type, + $do_relation = false, + $do_comments = false, + $do_mime = false, + $dates = false, + array $aliases = array() + ) { + $db_alias = $db; + $table_alias = $table; + $this->initAlias($aliases, $db_alias, $table_alias); + + global $cfgRelation; + + /* We do not export triggers */ + if ($export_mode == 'triggers') { + return true; + } + + /** + * Get the unique keys in the table + */ + $unique_keys = array(); + $keys = $GLOBALS['dbi']->getTableIndexes($db, $table); + foreach ($keys as $key) { + if ($key['Non_unique'] == 0) { + $unique_keys[] = $key['Column_name']; + } + } + + /** + * Gets fields properties + */ + $GLOBALS['dbi']->selectDb($db); + + // Check if we can use Relations + list($res_rel, $have_rel) = $this->relation->getRelationsAndStatus( + $do_relation && !empty($cfgRelation['relation']), + $db, + $table + ); + /** + * Displays the table structure + */ + $buffer = $crlf . '%' . $crlf . '% ' . __('Structure:') . ' ' + . $table_alias . $crlf . '%' . $crlf . ' \\begin{longtable}{'; + if (!Export::outputHandler($buffer)) { + return false; + } + + $alignment = '|l|c|c|c|'; + if ($do_relation && $have_rel) { + $alignment .= 'l|'; + } + if ($do_comments) { + $alignment .= 'l|'; + } + if ($do_mime && $cfgRelation['mimework']) { + $alignment .= 'l|'; + } + $buffer = $alignment . '} ' . $crlf; + + $header = ' \\hline '; + $header .= '\\multicolumn{1}{|c|}{\\textbf{' . __('Column') + . '}} & \\multicolumn{1}{|c|}{\\textbf{' . __('Type') + . '}} & \\multicolumn{1}{|c|}{\\textbf{' . __('Null') + . '}} & \\multicolumn{1}{|c|}{\\textbf{' . __('Default') . '}}'; + if ($do_relation && $have_rel) { + $header .= ' & \\multicolumn{1}{|c|}{\\textbf{' . __('Links to') . '}}'; + } + if ($do_comments) { + $header .= ' & \\multicolumn{1}{|c|}{\\textbf{' . __('Comments') . '}}'; + $comments = $this->relation->getComments($db, $table); + } + if ($do_mime && $cfgRelation['mimework']) { + $header .= ' & \\multicolumn{1}{|c|}{\\textbf{MIME}}'; + $mime_map = Transformations::getMIME($db, $table, true); + } + + // Table caption for first page and label + if (isset($GLOBALS['latex_caption'])) { + $buffer .= ' \\caption{' + . Util::expandUserString( + $GLOBALS['latex_structure_caption'], + array( + 'texEscape', + get_class($this), + ), + array('table' => $table_alias, 'database' => $db_alias) + ) + . '} \\label{' + . Util::expandUserString( + $GLOBALS['latex_structure_label'], + null, + array('table' => $table_alias, 'database' => $db_alias) + ) + . '} \\\\' . $crlf; + } + $buffer .= $header . ' \\\\ \\hline \\hline' . $crlf + . '\\endfirsthead' . $crlf; + // Table caption on next pages + if (isset($GLOBALS['latex_caption'])) { + $buffer .= ' \\caption{' + . Util::expandUserString( + $GLOBALS['latex_structure_continued_caption'], + array( + 'texEscape', + get_class($this), + ), + array('table' => $table_alias, 'database' => $db_alias) + ) + . '} \\\\ ' . $crlf; + } + $buffer .= $header . ' \\\\ \\hline \\hline \\endhead \\endfoot ' . $crlf; + + if (!Export::outputHandler($buffer)) { + return false; + } + + $fields = $GLOBALS['dbi']->getColumns($db, $table); + foreach ($fields as $row) { + $extracted_columnspec = Util::extractColumnSpec($row['Type']); + $type = $extracted_columnspec['print_type']; + if (empty($type)) { + $type = ' '; + } + + if (!isset($row['Default'])) { + if ($row['Null'] != 'NO') { + $row['Default'] = 'NULL'; + } + } + + $field_name = $col_as = $row['Field']; + if (!empty($aliases[$db]['tables'][$table]['columns'][$col_as])) { + $col_as = $aliases[$db]['tables'][$table]['columns'][$col_as]; + } + + $local_buffer = $col_as . "\000" . $type . "\000" + . (($row['Null'] == '' || $row['Null'] == 'NO') + ? __('No') : __('Yes')) + . "\000" . (isset($row['Default']) ? $row['Default'] : ''); + + if ($do_relation && $have_rel) { + $local_buffer .= "\000"; + $local_buffer .= $this->getRelationString( + $res_rel, + $field_name, + $db, + $aliases + ); + } + if ($do_comments && $cfgRelation['commwork']) { + $local_buffer .= "\000"; + if (isset($comments[$field_name])) { + $local_buffer .= $comments[$field_name]; + } + } + if ($do_mime && $cfgRelation['mimework']) { + $local_buffer .= "\000"; + if (isset($mime_map[$field_name])) { + $local_buffer .= str_replace( + '_', + '/', + $mime_map[$field_name]['mimetype'] + ); + } + } + $local_buffer = self::texEscape($local_buffer); + if ($row['Key'] == 'PRI') { + $pos = mb_strpos($local_buffer, "\000"); + $local_buffer = '\\textit{' + . + mb_substr($local_buffer, 0, $pos) + . '}' . + mb_substr($local_buffer, $pos); + } + if (in_array($field_name, $unique_keys)) { + $pos = mb_strpos($local_buffer, "\000"); + $local_buffer = '\\textbf{' + . + mb_substr($local_buffer, 0, $pos) + . '}' . + mb_substr($local_buffer, $pos); + } + $buffer = str_replace("\000", ' & ', $local_buffer); + $buffer .= ' \\\\ \\hline ' . $crlf; + + if (!Export::outputHandler($buffer)) { + return false; + } + } // end while + + $buffer = ' \\end{longtable}' . $crlf; + + return Export::outputHandler($buffer); + } // end of the 'exportStructure' method + + /** + * Escapes some special characters for use in TeX/LaTeX + * + * @param string $string the string to convert + * + * @return string the converted string with escape codes + */ + public static function texEscape($string) + { + $escape = array('$', '%', '{', '}', '&', '#', '_', '^'); + $cnt_escape = count($escape); + for ($k = 0; $k < $cnt_escape; $k++) { + $string = str_replace($escape[$k], '\\' . $escape[$k], $string); + } + + return $string; + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Plugins/Export/ExportMediawiki.php b/php/apps/phpmyadmin49/libraries/classes/Plugins/Export/ExportMediawiki.php new file mode 100644 index 00000000..93210d51 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Plugins/Export/ExportMediawiki.php @@ -0,0 +1,381 @@ +setProperties(); + } + + /** + * Sets the export MediaWiki properties + * + * @return void + */ + protected function setProperties() + { + $exportPluginProperties = new ExportPluginProperties(); + $exportPluginProperties->setText('MediaWiki Table'); + $exportPluginProperties->setExtension('mediawiki'); + $exportPluginProperties->setMimeType('text/plain'); + $exportPluginProperties->setOptionsText(__('Options')); + + // create the root group that will be the options field for + // $exportPluginProperties + // this will be shown as "Format specific options" + $exportSpecificOptions = new OptionsPropertyRootGroup( + "Format Specific Options" + ); + + // general options main group + $generalOptions = new OptionsPropertyMainGroup( + "general_opts", __('Dump table') + ); + + // what to dump (structure/data/both) + $subgroup = new OptionsPropertySubgroup( + "dump_table", __("Dump table") + ); + $leaf = new RadioPropertyItem('structure_or_data'); + $leaf->setValues( + array( + 'structure' => __('structure'), + 'data' => __('data'), + 'structure_and_data' => __('structure and data'), + ) + ); + $subgroup->setSubgroupHeader($leaf); + $generalOptions->addProperty($subgroup); + + // export table name + $leaf = new BoolPropertyItem( + "caption", + __('Export table names') + ); + $generalOptions->addProperty($leaf); + + // export table headers + $leaf = new BoolPropertyItem( + "headers", + __('Export table headers') + ); + $generalOptions->addProperty($leaf); + //add the main group to the root group + $exportSpecificOptions->addProperty($generalOptions); + + // set the options for the export plugin property item + $exportPluginProperties->setOptions($exportSpecificOptions); + $this->properties = $exportPluginProperties; + } + + /** + * Outputs export header + * + * @return bool Whether it succeeded + */ + public function exportHeader() + { + return true; + } + + /** + * Outputs export footer + * + * @return bool Whether it succeeded + */ + public function exportFooter() + { + return true; + } + + /** + * Outputs database header + * + * @param string $db Database name + * @param string $db_alias Alias of db + * + * @return bool Whether it succeeded + */ + public function exportDBHeader($db, $db_alias = '') + { + return true; + } + + /** + * Outputs database footer + * + * @param string $db Database name + * + * @return bool Whether it succeeded + */ + public function exportDBFooter($db) + { + return true; + } + + /** + * Outputs CREATE DATABASE statement + * + * @param string $db Database name + * @param string $export_type 'server', 'database', 'table' + * @param string $db_alias Aliases of db + * + * @return bool Whether it succeeded + */ + public function exportDBCreate($db, $export_type, $db_alias = '') + { + return true; + } + + /** + * Outputs table's structure + * + * @param string $db database name + * @param string $table table name + * @param string $crlf the end of line sequence + * @param string $error_url the url to go back in case of error + * @param string $export_mode 'create_table','triggers','create_view', + * 'stand_in' + * @param string $export_type 'server', 'database', 'table' + * @param bool $do_relation whether to include relation comments + * @param bool $do_comments whether to include the pmadb-style column + * comments as comments in the structure; this is + * deprecated but the parameter is left here + * because export.php calls exportStructure() + * also for other export types which use this + * parameter + * @param bool $do_mime whether to include mime comments + * @param bool $dates whether to include creation/update/check dates + * @param array $aliases Aliases of db/table/columns + * + * @return bool Whether it succeeded + */ + public function exportStructure( + $db, + $table, + $crlf, + $error_url, + $export_mode, + $export_type, + $do_relation = false, + $do_comments = false, + $do_mime = false, + $dates = false, + array $aliases = array() + ) { + $db_alias = $db; + $table_alias = $table; + $this->initAlias($aliases, $db_alias, $table_alias); + + $output = ''; + switch ($export_mode) { + case 'create_table': + $columns = $GLOBALS['dbi']->getColumns($db, $table); + $columns = array_values($columns); + $row_cnt = count($columns); + + // Print structure comment + $output = $this->_exportComment( + "Table structure for " + . Util::backquote($table_alias) + ); + + // Begin the table construction + $output .= "{| class=\"wikitable\" style=\"text-align:center;\"" + . $this->_exportCRLF(); + + // Add the table name + if (isset($GLOBALS['mediawiki_caption'])) { + $output .= "|+'''" . $table_alias . "'''" . $this->_exportCRLF(); + } + + // Add the table headers + if (isset($GLOBALS['mediawiki_headers'])) { + $output .= "|- style=\"background:#ffdead;\"" . $this->_exportCRLF(); + $output .= "! style=\"background:#ffffff\" | " + . $this->_exportCRLF(); + for ($i = 0; $i < $row_cnt; ++$i) { + $col_as = $columns[$i]['Field']; + if (!empty($aliases[$db]['tables'][$table]['columns'][$col_as]) + ) { + $col_as + = $aliases[$db]['tables'][$table]['columns'][$col_as]; + } + $output .= " | " . $col_as . $this->_exportCRLF(); + } + } + + // Add the table structure + $output .= "|-" . $this->_exportCRLF(); + $output .= "! Type" . $this->_exportCRLF(); + for ($i = 0; $i < $row_cnt; ++$i) { + $output .= " | " . $columns[$i]['Type'] . $this->_exportCRLF(); + } + + $output .= "|-" . $this->_exportCRLF(); + $output .= "! Null" . $this->_exportCRLF(); + for ($i = 0; $i < $row_cnt; ++$i) { + $output .= " | " . $columns[$i]['Null'] . $this->_exportCRLF(); + } + + $output .= "|-" . $this->_exportCRLF(); + $output .= "! Default" . $this->_exportCRLF(); + for ($i = 0; $i < $row_cnt; ++$i) { + $output .= " | " . $columns[$i]['Default'] . $this->_exportCRLF(); + } + + $output .= "|-" . $this->_exportCRLF(); + $output .= "! Extra" . $this->_exportCRLF(); + for ($i = 0; $i < $row_cnt; ++$i) { + $output .= " | " . $columns[$i]['Extra'] . $this->_exportCRLF(); + } + + $output .= "|}" . str_repeat($this->_exportCRLF(), 2); + break; + } // end switch + + return Export::outputHandler($output); + } + + /** + * Outputs the content of a table in MediaWiki format + * + * @param string $db database name + * @param string $table table name + * @param string $crlf the end of line sequence + * @param string $error_url the url to go back in case of error + * @param string $sql_query SQL query for obtaining data + * @param array $aliases Aliases of db/table/columns + * + * @return bool Whether it succeeded + */ + public function exportData( + $db, + $table, + $crlf, + $error_url, + $sql_query, + array $aliases = array() + ) { + $db_alias = $db; + $table_alias = $table; + $this->initAlias($aliases, $db_alias, $table_alias); + + // Print data comment + $output = $this->_exportComment( + "Table data for " . Util::backquote($table_alias) + ); + + // Begin the table construction + // Use the "wikitable" class for style + // Use the "sortable" class for allowing tables to be sorted by column + $output .= "{| class=\"wikitable sortable\" style=\"text-align:center;\"" + . $this->_exportCRLF(); + + // Add the table name + if (isset($GLOBALS['mediawiki_caption'])) { + $output .= "|+'''" . $table_alias . "'''" . $this->_exportCRLF(); + } + + // Add the table headers + if (isset($GLOBALS['mediawiki_headers'])) { + // Get column names + $column_names = $GLOBALS['dbi']->getColumnNames($db, $table); + + // Add column names as table headers + if (!is_null($column_names)) { + // Use '|-' for separating rows + $output .= "|-" . $this->_exportCRLF(); + + // Use '!' for separating table headers + foreach ($column_names as $column) { + if (!empty($aliases[$db]['tables'][$table]['columns'][$column]) + ) { + $column + = $aliases[$db]['tables'][$table]['columns'][$column]; + } + $output .= " ! " . $column . "" . $this->_exportCRLF(); + } + } + } + + // Get the table data from the database + $result = $GLOBALS['dbi']->query( + $sql_query, + DatabaseInterface::CONNECT_USER, + DatabaseInterface::QUERY_UNBUFFERED + ); + $fields_cnt = $GLOBALS['dbi']->numFields($result); + + while ($row = $GLOBALS['dbi']->fetchRow($result)) { + $output .= "|-" . $this->_exportCRLF(); + + // Use '|' for separating table columns + for ($i = 0; $i < $fields_cnt; ++$i) { + $output .= " | " . $row[$i] . "" . $this->_exportCRLF(); + } + } + + // End table construction + $output .= "|}" . str_repeat($this->_exportCRLF(), 2); + + return Export::outputHandler($output); + } + + /** + * Outputs comments containing info about the exported tables + * + * @param string $text Text of comment + * + * @return string The formatted comment + */ + private function _exportComment($text = '') + { + // see https://www.mediawiki.org/wiki/Help:Formatting + $comment = $this->_exportCRLF(); + $comment .= '' . str_repeat($this->_exportCRLF(), 2); + + return $comment; + } + + /** + * Outputs CRLF + * + * @return string CRLF + */ + private function _exportCRLF() + { + // The CRLF expected by the mediawiki format is "\n" + return "\n"; + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Plugins/Export/ExportOds.php b/php/apps/phpmyadmin49/libraries/classes/Plugins/Export/ExportOds.php new file mode 100644 index 00000000..aa6c1648 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Plugins/Export/ExportOds.php @@ -0,0 +1,342 @@ +setProperties(); + } + + /** + * Sets the export ODS properties + * + * @return void + */ + protected function setProperties() + { + $exportPluginProperties = new ExportPluginProperties(); + $exportPluginProperties->setText('OpenDocument Spreadsheet'); + $exportPluginProperties->setExtension('ods'); + $exportPluginProperties->setMimeType( + 'application/vnd.oasis.opendocument.spreadsheet' + ); + $exportPluginProperties->setForceFile(true); + $exportPluginProperties->setOptionsText(__('Options')); + + // create the root group that will be the options field for + // $exportPluginProperties + // this will be shown as "Format specific options" + $exportSpecificOptions = new OptionsPropertyRootGroup( + "Format Specific Options" + ); + + // general options main group + $generalOptions = new OptionsPropertyMainGroup("general_opts"); + // create primary items and add them to the group + $leaf = new TextPropertyItem( + "null", + __('Replace NULL with:') + ); + $generalOptions->addProperty($leaf); + $leaf = new BoolPropertyItem( + "columns", + __('Put columns names in the first row') + ); + $generalOptions->addProperty($leaf); + $leaf = new HiddenPropertyItem("structure_or_data"); + $generalOptions->addProperty($leaf); + // add the main group to the root group + $exportSpecificOptions->addProperty($generalOptions); + + // set the options for the export plugin property item + $exportPluginProperties->setOptions($exportSpecificOptions); + $this->properties = $exportPluginProperties; + } + + /** + * Outputs export header + * + * @return bool Whether it succeeded + */ + public function exportHeader() + { + $GLOBALS['ods_buffer'] .= '' + . '' + . '' + . '' + . '' + . '/' + . '' + . '/' + . '' + . '' + . '' + . '' + . ':' + . '' + . ':' + . '' + . ' ' + . '' + . '' + . '' + . '' + . '/' + . '' + . '/' + . '' + . ' ' + . '' + . ':' + . '' + . ' ' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . ''; + + return true; + } + + /** + * Outputs export footer + * + * @return bool Whether it succeeded + */ + public function exportFooter() + { + $GLOBALS['ods_buffer'] .= '' + . '' + . ''; + + return Export::outputHandler( + OpenDocument::create( + 'application/vnd.oasis.opendocument.spreadsheet', + $GLOBALS['ods_buffer'] + ) + ); + } + + /** + * Outputs database header + * + * @param string $db Database name + * @param string $db_alias Aliases of db + * + * @return bool Whether it succeeded + */ + public function exportDBHeader($db, $db_alias = '') + { + return true; + } + + /** + * Outputs database footer + * + * @param string $db Database name + * + * @return bool Whether it succeeded + */ + public function exportDBFooter($db) + { + return true; + } + + /** + * Outputs CREATE DATABASE statement + * + * @param string $db Database name + * @param string $export_type 'server', 'database', 'table' + * @param string $db_alias Aliases of db + * + * @return bool Whether it succeeded + */ + public function exportDBCreate($db, $export_type, $db_alias = '') + { + return true; + } + + /** + * Outputs the content of a table in NHibernate format + * + * @param string $db database name + * @param string $table table name + * @param string $crlf the end of line sequence + * @param string $error_url the url to go back in case of error + * @param string $sql_query SQL query for obtaining data + * @param array $aliases Aliases of db/table/columns + * + * @return bool Whether it succeeded + */ + public function exportData( + $db, + $table, + $crlf, + $error_url, + $sql_query, + array $aliases = array() + ) { + global $what; + + $db_alias = $db; + $table_alias = $table; + $this->initAlias($aliases, $db_alias, $table_alias); + // Gets the data from the database + $result = $GLOBALS['dbi']->query( + $sql_query, + DatabaseInterface::CONNECT_USER, + DatabaseInterface::QUERY_UNBUFFERED + ); + $fields_cnt = $GLOBALS['dbi']->numFields($result); + $fields_meta = $GLOBALS['dbi']->getFieldsMeta($result); + $field_flags = array(); + for ($j = 0; $j < $fields_cnt; $j++) { + $field_flags[$j] = $GLOBALS['dbi']->fieldFlags($result, $j); + } + + $GLOBALS['ods_buffer'] + .= ''; + + // If required, get fields name at the first line + if (isset($GLOBALS[$what . '_columns'])) { + $GLOBALS['ods_buffer'] .= ''; + for ($i = 0; $i < $fields_cnt; $i++) { + $col_as = $GLOBALS['dbi']->fieldName($result, $i); + if (!empty($aliases[$db]['tables'][$table]['columns'][$col_as])) { + $col_as = $aliases[$db]['tables'][$table]['columns'][$col_as]; + } + $GLOBALS['ods_buffer'] + .= '' + . '' + . htmlspecialchars( + stripslashes($col_as) + ) + . '' + . ''; + } // end for + $GLOBALS['ods_buffer'] .= ''; + } // end if + + // Format the data + while ($row = $GLOBALS['dbi']->fetchRow($result)) { + $GLOBALS['ods_buffer'] .= ''; + for ($j = 0; $j < $fields_cnt; $j++) { + if ($fields_meta[$j]->type === 'geometry') { + // export GIS types as hex + $row[$j] = '0x' . bin2hex($row[$j]); + } + if (!isset($row[$j]) || is_null($row[$j])) { + $GLOBALS['ods_buffer'] + .= '' + . '' + . htmlspecialchars($GLOBALS[$what . '_null']) + . '' + . ''; + } elseif (stristr($field_flags[$j], 'BINARY') + && $fields_meta[$j]->blob + ) { + // ignore BLOB + $GLOBALS['ods_buffer'] + .= '' + . '' + . ''; + } elseif ($fields_meta[$j]->type == "date") { + $GLOBALS['ods_buffer'] + .= '' + . '' + . htmlspecialchars($row[$j]) + . '' + . ''; + } elseif ($fields_meta[$j]->type == "time") { + $GLOBALS['ods_buffer'] + .= '' + . '' + . htmlspecialchars($row[$j]) + . '' + . ''; + } elseif ($fields_meta[$j]->type == "datetime") { + $GLOBALS['ods_buffer'] + .= '' + . '' + . htmlspecialchars($row[$j]) + . '' + . ''; + } elseif (($fields_meta[$j]->numeric + && $fields_meta[$j]->type != 'timestamp' + && !$fields_meta[$j]->blob) + || $fields_meta[$j]->type == 'real' + ) { + $GLOBALS['ods_buffer'] + .= '' + . '' + . htmlspecialchars($row[$j]) + . '' + . ''; + } else { + $GLOBALS['ods_buffer'] + .= '' + . '' + . htmlspecialchars($row[$j]) + . '' + . ''; + } + } // end for + $GLOBALS['ods_buffer'] .= ''; + } // end while + $GLOBALS['dbi']->freeResult($result); + + $GLOBALS['ods_buffer'] .= ''; + + return true; + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Plugins/Export/ExportOdt.php b/php/apps/phpmyadmin49/libraries/classes/Plugins/Export/ExportOdt.php new file mode 100644 index 00000000..9ea59e39 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Plugins/Export/ExportOdt.php @@ -0,0 +1,809 @@ +setProperties(); + } + + /** + * Sets the export ODT properties + * + * @return void + */ + protected function setProperties() + { + global $plugin_param; + $hide_structure = false; + if ($plugin_param['export_type'] == 'table' + && !$plugin_param['single_table'] + ) { + $hide_structure = true; + } + + $exportPluginProperties = new ExportPluginProperties(); + $exportPluginProperties->setText('OpenDocument Text'); + $exportPluginProperties->setExtension('odt'); + $exportPluginProperties->setMimeType( + 'application/vnd.oasis.opendocument.text' + ); + $exportPluginProperties->setForceFile(true); + $exportPluginProperties->setOptionsText(__('Options')); + + // create the root group that will be the options field for + // $exportPluginProperties + // this will be shown as "Format specific options" + $exportSpecificOptions = new OptionsPropertyRootGroup( + "Format Specific Options" + ); + + // what to dump (structure/data/both) main group + $dumpWhat = new OptionsPropertyMainGroup( + "general_opts", __('Dump table') + ); + // create primary items and add them to the group + $leaf = new RadioPropertyItem("structure_or_data"); + $leaf->setValues( + array( + 'structure' => __('structure'), + 'data' => __('data'), + 'structure_and_data' => __('structure and data'), + ) + ); + $dumpWhat->addProperty($leaf); + // add the main group to the root group + $exportSpecificOptions->addProperty($dumpWhat); + + // structure options main group + if (!$hide_structure) { + $structureOptions = new OptionsPropertyMainGroup( + "structure", __('Object creation options') + ); + $structureOptions->setForce('data'); + // create primary items and add them to the group + if (!empty($GLOBALS['cfgRelation']['relation'])) { + $leaf = new BoolPropertyItem( + "relation", + __('Display foreign key relationships') + ); + $structureOptions->addProperty($leaf); + } + $leaf = new BoolPropertyItem( + "comments", + __('Display comments') + ); + $structureOptions->addProperty($leaf); + if (!empty($GLOBALS['cfgRelation']['mimework'])) { + $leaf = new BoolPropertyItem( + "mime", + __('Display MIME types') + ); + $structureOptions->addProperty($leaf); + } + // add the main group to the root group + $exportSpecificOptions->addProperty($structureOptions); + } + + // data options main group + $dataOptions = new OptionsPropertyMainGroup( + "data", __('Data dump options') + ); + $dataOptions->setForce('structure'); + // create primary items and add them to the group + $leaf = new BoolPropertyItem( + "columns", + __('Put columns names in the first row') + ); + $dataOptions->addProperty($leaf); + $leaf = new TextPropertyItem( + 'null', + __('Replace NULL with:') + ); + $dataOptions->addProperty($leaf); + // add the main group to the root group + $exportSpecificOptions->addProperty($dataOptions); + + // set the options for the export plugin property item + $exportPluginProperties->setOptions($exportSpecificOptions); + $this->properties = $exportPluginProperties; + } + + /** + * Outputs export header + * + * @return bool Whether it succeeded + */ + public function exportHeader() + { + $GLOBALS['odt_buffer'] .= '' + . '' + . '' + . ''; + + return true; + } + + /** + * Outputs export footer + * + * @return bool Whether it succeeded + */ + public function exportFooter() + { + $GLOBALS['odt_buffer'] .= '' + . '' + . ''; + if (!Export::outputHandler( + OpenDocument::create( + 'application/vnd.oasis.opendocument.text', + $GLOBALS['odt_buffer'] + ) + ) + ) { + return false; + } + + return true; + } + + /** + * Outputs database header + * + * @param string $db Database name + * @param string $db_alias Aliases of db + * + * @return bool Whether it succeeded + */ + public function exportDBHeader($db, $db_alias = '') + { + if (empty($db_alias)) { + $db_alias = $db; + } + $GLOBALS['odt_buffer'] + .= '' + . __('Database') . ' ' . htmlspecialchars($db_alias) + . ''; + + return true; + } + + /** + * Outputs database footer + * + * @param string $db Database name + * + * @return bool Whether it succeeded + */ + public function exportDBFooter($db) + { + return true; + } + + /** + * Outputs CREATE DATABASE statement + * + * @param string $db Database name + * @param string $export_type 'server', 'database', 'table' + * @param string $db_alias Aliases of db + * + * @return bool Whether it succeeded + */ + public function exportDBCreate($db, $export_type, $db_alias = '') + { + return true; + } + + /** + * Outputs the content of a table in NHibernate format + * + * @param string $db database name + * @param string $table table name + * @param string $crlf the end of line sequence + * @param string $error_url the url to go back in case of error + * @param string $sql_query SQL query for obtaining data + * @param array $aliases Aliases of db/table/columns + * + * @return bool Whether it succeeded + */ + public function exportData( + $db, + $table, + $crlf, + $error_url, + $sql_query, + array $aliases = array() + ) { + global $what; + + $db_alias = $db; + $table_alias = $table; + $this->initAlias($aliases, $db_alias, $table_alias); + // Gets the data from the database + $result = $GLOBALS['dbi']->query( + $sql_query, + DatabaseInterface::CONNECT_USER, + DatabaseInterface::QUERY_UNBUFFERED + ); + $fields_cnt = $GLOBALS['dbi']->numFields($result); + $fields_meta = $GLOBALS['dbi']->getFieldsMeta($result); + $field_flags = array(); + for ($j = 0; $j < $fields_cnt; $j++) { + $field_flags[$j] = $GLOBALS['dbi']->fieldFlags($result, $j); + } + + $GLOBALS['odt_buffer'] + .= '' + . __('Dumping data for table') . ' ' . htmlspecialchars($table_alias) + . '' + . '' + . ''; + + // If required, get fields name at the first line + if (isset($GLOBALS[$what . '_columns'])) { + $GLOBALS['odt_buffer'] .= ''; + for ($i = 0; $i < $fields_cnt; $i++) { + $col_as = $GLOBALS['dbi']->fieldName($result, $i); + if (!empty($aliases[$db]['tables'][$table]['columns'][$col_as])) { + $col_as = $aliases[$db]['tables'][$table]['columns'][$col_as]; + } + $GLOBALS['odt_buffer'] + .= '' + . '' + . htmlspecialchars( + stripslashes($col_as) + ) + . '' + . ''; + } // end for + $GLOBALS['odt_buffer'] .= ''; + } // end if + + // Format the data + while ($row = $GLOBALS['dbi']->fetchRow($result)) { + $GLOBALS['odt_buffer'] .= ''; + for ($j = 0; $j < $fields_cnt; $j++) { + if ($fields_meta[$j]->type === 'geometry') { + // export GIS types as hex + $row[$j] = '0x' . bin2hex($row[$j]); + } + if (!isset($row[$j]) || is_null($row[$j])) { + $GLOBALS['odt_buffer'] + .= '' + . '' + . htmlspecialchars($GLOBALS[$what . '_null']) + . '' + . ''; + } elseif (stristr($field_flags[$j], 'BINARY') + && $fields_meta[$j]->blob + ) { + // ignore BLOB + $GLOBALS['odt_buffer'] + .= '' + . '' + . ''; + } elseif ($fields_meta[$j]->numeric + && $fields_meta[$j]->type != 'timestamp' + && !$fields_meta[$j]->blob + ) { + $GLOBALS['odt_buffer'] + .= '' + . '' + . htmlspecialchars($row[$j]) + . '' + . ''; + } else { + $GLOBALS['odt_buffer'] + .= '' + . '' + . htmlspecialchars($row[$j]) + . '' + . ''; + } + } // end for + $GLOBALS['odt_buffer'] .= ''; + } // end while + $GLOBALS['dbi']->freeResult($result); + + $GLOBALS['odt_buffer'] .= ''; + + return true; + } + + /** + * Returns a stand-in CREATE definition to resolve view dependencies + * + * @param string $db the database name + * @param string $view the view name + * @param string $crlf the end of line sequence + * @param array $aliases Aliases of db/table/columns + * + * @return string resulting definition + */ + public function getTableDefStandIn($db, $view, $crlf, $aliases = array()) + { + $db_alias = $db; + $view_alias = $view; + $this->initAlias($aliases, $db_alias, $view_alias); + /** + * Gets fields properties + */ + $GLOBALS['dbi']->selectDb($db); + + /** + * Displays the table structure + */ + $GLOBALS['odt_buffer'] + .= ''; + $columns_cnt = 4; + $GLOBALS['odt_buffer'] + .= ''; + /* Header */ + $GLOBALS['odt_buffer'] .= '' + . '' + . '' . __('Column') . '' + . '' + . '' + . '' . __('Type') . '' + . '' + . '' + . '' . __('Null') . '' + . '' + . '' + . '' . __('Default') . '' + . '' + . ''; + + $columns = $GLOBALS['dbi']->getColumns($db, $view); + foreach ($columns as $column) { + $col_as = $column['Field']; + if (!empty($aliases[$db]['tables'][$view]['columns'][$col_as])) { + $col_as = $aliases[$db]['tables'][$view]['columns'][$col_as]; + } + $GLOBALS['odt_buffer'] .= $this->formatOneColumnDefinition( + $column, + $col_as + ); + $GLOBALS['odt_buffer'] .= ''; + } // end foreach + + $GLOBALS['odt_buffer'] .= ''; + + return true; + } + + /** + * Returns $table's CREATE definition + * + * @param string $db the database name + * @param string $table the table name + * @param string $crlf the end of line sequence + * @param string $error_url the url to go back in case of error + * @param bool $do_relation whether to include relation comments + * @param bool $do_comments whether to include the pmadb-style column + * comments as comments in the structure; + * this is deprecated but the parameter is + * left here because export.php calls + * PMA_exportStructure() also for other + * @param bool $do_mime whether to include mime comments + * @param bool $show_dates whether to include creation/update/check dates + * @param bool $add_semicolon whether to add semicolon and end-of-line at + * the end + * @param bool $view whether we're handling a view + * @param array $aliases Aliases of db/table/columns + * + * @return bool true + */ + public function getTableDef( + $db, + $table, + $crlf, + $error_url, + $do_relation, + $do_comments, + $do_mime, + $show_dates = false, + $add_semicolon = true, + $view = false, + array $aliases = array() + ) { + global $cfgRelation; + + $db_alias = $db; + $table_alias = $table; + $this->initAlias($aliases, $db_alias, $table_alias); + /** + * Gets fields properties + */ + $GLOBALS['dbi']->selectDb($db); + + // Check if we can use Relations + list($res_rel, $have_rel) = $this->relation->getRelationsAndStatus( + $do_relation && !empty($cfgRelation['relation']), + $db, + $table + ); + /** + * Displays the table structure + */ + $GLOBALS['odt_buffer'] .= ''; + $columns_cnt = 4; + if ($do_relation && $have_rel) { + $columns_cnt++; + } + if ($do_comments) { + $columns_cnt++; + } + if ($do_mime && $cfgRelation['mimework']) { + $columns_cnt++; + } + $GLOBALS['odt_buffer'] .= ''; + /* Header */ + $GLOBALS['odt_buffer'] .= '' + . '' + . '' . __('Column') . '' + . '' + . '' + . '' . __('Type') . '' + . '' + . '' + . '' . __('Null') . '' + . '' + . '' + . '' . __('Default') . '' + . ''; + if ($do_relation && $have_rel) { + $GLOBALS['odt_buffer'] .= '' + . '' . __('Links to') . '' + . ''; + } + if ($do_comments) { + $GLOBALS['odt_buffer'] .= '' + . '' . __('Comments') . '' + . ''; + $comments = $this->relation->getComments($db, $table); + } + if ($do_mime && $cfgRelation['mimework']) { + $GLOBALS['odt_buffer'] .= '' + . '' . __('MIME type') . '' + . ''; + $mime_map = Transformations::getMIME($db, $table, true); + } + $GLOBALS['odt_buffer'] .= ''; + + $columns = $GLOBALS['dbi']->getColumns($db, $table); + foreach ($columns as $column) { + $col_as = $field_name = $column['Field']; + if (!empty($aliases[$db]['tables'][$table]['columns'][$col_as])) { + $col_as = $aliases[$db]['tables'][$table]['columns'][$col_as]; + } + $GLOBALS['odt_buffer'] .= $this->formatOneColumnDefinition( + $column, + $col_as + ); + if ($do_relation && $have_rel) { + $foreigner = $this->relation->searchColumnInForeigners($res_rel, $field_name); + if ($foreigner) { + $rtable = $foreigner['foreign_table']; + $rfield = $foreigner['foreign_field']; + if (!empty($aliases[$db]['tables'][$rtable]['columns'][$rfield]) + ) { + $rfield + = $aliases[$db]['tables'][$rtable]['columns'][$rfield]; + } + if (!empty($aliases[$db]['tables'][$rtable]['alias'])) { + $rtable = $aliases[$db]['tables'][$rtable]['alias']; + } + $relation = htmlspecialchars($rtable . ' (' . $rfield . ')'); + $GLOBALS['odt_buffer'] + .= '' + . '' + . htmlspecialchars($relation) + . '' + . ''; + } + } + if ($do_comments) { + if (isset($comments[$field_name])) { + $GLOBALS['odt_buffer'] + .= '' + . '' + . htmlspecialchars($comments[$field_name]) + . '' + . ''; + } else { + $GLOBALS['odt_buffer'] + .= '' + . '' + . ''; + } + } + if ($do_mime && $cfgRelation['mimework']) { + if (isset($mime_map[$field_name])) { + $GLOBALS['odt_buffer'] + .= '' + . '' + . htmlspecialchars( + str_replace('_', '/', $mime_map[$field_name]['mimetype']) + ) + . '' + . ''; + } else { + $GLOBALS['odt_buffer'] + .= '' + . '' + . ''; + } + } + $GLOBALS['odt_buffer'] .= ''; + } // end foreach + + $GLOBALS['odt_buffer'] .= ''; + + return true; + } // end of the '$this->getTableDef()' function + + /** + * Outputs triggers + * + * @param string $db database name + * @param string $table table name + * @param array $aliases Aliases of db/table/columns + * + * @return bool true + */ + protected function getTriggers($db, $table, array $aliases = array()) + { + $db_alias = $db; + $table_alias = $table; + $this->initAlias($aliases, $db_alias, $table_alias); + $GLOBALS['odt_buffer'] .= '' + . '' + . '' + . '' + . '' . __('Name') . '' + . '' + . '' + . '' . __('Time') . '' + . '' + . '' + . '' . __('Event') . '' + . '' + . '' + . '' . __('Definition') . '' + . '' + . ''; + + $triggers = $GLOBALS['dbi']->getTriggers($db, $table); + + foreach ($triggers as $trigger) { + $GLOBALS['odt_buffer'] .= ''; + $GLOBALS['odt_buffer'] .= '' + . '' + . htmlspecialchars($trigger['name']) + . '' + . ''; + $GLOBALS['odt_buffer'] .= '' + . '' + . htmlspecialchars($trigger['action_timing']) + . '' + . ''; + $GLOBALS['odt_buffer'] .= '' + . '' + . htmlspecialchars($trigger['event_manipulation']) + . '' + . ''; + $GLOBALS['odt_buffer'] .= '' + . '' + . htmlspecialchars($trigger['definition']) + . '' + . ''; + $GLOBALS['odt_buffer'] .= ''; + } + + $GLOBALS['odt_buffer'] .= ''; + + return true; + } + + /** + * Outputs table's structure + * + * @param string $db database name + * @param string $table table name + * @param string $crlf the end of line sequence + * @param string $error_url the url to go back in case of error + * @param string $export_mode 'create_table', 'triggers', 'create_view', + * 'stand_in' + * @param string $export_type 'server', 'database', 'table' + * @param bool $do_relation whether to include relation comments + * @param bool $do_comments whether to include the pmadb-style column + * comments as comments in the structure; + * this is deprecated but the parameter is + * left here because export.php calls + * PMA_exportStructure() also for other + * @param bool $do_mime whether to include mime comments + * @param bool $dates whether to include creation/update/check dates + * @param array $aliases Aliases of db/table/columns + * + * @return bool Whether it succeeded + */ + public function exportStructure( + $db, + $table, + $crlf, + $error_url, + $export_mode, + $export_type, + $do_relation = false, + $do_comments = false, + $do_mime = false, + $dates = false, + array $aliases = array() + ) { + $db_alias = $db; + $table_alias = $table; + $this->initAlias($aliases, $db_alias, $table_alias); + switch ($export_mode) { + case 'create_table': + $GLOBALS['odt_buffer'] + .= '' + . __('Table structure for table') . ' ' . + htmlspecialchars($table_alias) + . ''; + $this->getTableDef( + $db, + $table, + $crlf, + $error_url, + $do_relation, + $do_comments, + $do_mime, + $dates, + true, + false, + $aliases + ); + break; + case 'triggers': + $triggers = $GLOBALS['dbi']->getTriggers($db, $table, $aliases); + if ($triggers) { + $GLOBALS['odt_buffer'] + .= '' + . __('Triggers') . ' ' + . htmlspecialchars($table_alias) + . ''; + $this->getTriggers($db, $table); + } + break; + case 'create_view': + $GLOBALS['odt_buffer'] + .= '' + . __('Structure for view') . ' ' + . htmlspecialchars($table_alias) + . ''; + $this->getTableDef( + $db, + $table, + $crlf, + $error_url, + $do_relation, + $do_comments, + $do_mime, + $dates, + true, + true, + $aliases + ); + break; + case 'stand_in': + $GLOBALS['odt_buffer'] + .= '' + . __('Stand-in structure for view') . ' ' + . htmlspecialchars($table_alias) + . ''; + // export a stand-in definition to resolve view dependencies + $this->getTableDefStandIn($db, $table, $crlf, $aliases); + } // end switch + + return true; + } // end of the '$this->exportStructure' function + + /** + * Formats the definition for one column + * + * @param array $column info about this column + * @param string $col_as column alias + * + * @return string Formatted column definition + */ + protected function formatOneColumnDefinition($column, $col_as = '') + { + if (empty($col_as)) { + $col_as = $column['Field']; + } + $definition = ''; + $definition .= '' + . '' . htmlspecialchars($col_as) . '' + . ''; + + $extracted_columnspec + = Util::extractColumnSpec($column['Type']); + $type = htmlspecialchars($extracted_columnspec['print_type']); + if (empty($type)) { + $type = ' '; + } + + $definition .= '' + . '' . htmlspecialchars($type) . '' + . ''; + if (!isset($column['Default'])) { + if ($column['Null'] != 'NO') { + $column['Default'] = 'NULL'; + } else { + $column['Default'] = ''; + } + } + $definition .= '' + . '' + . (($column['Null'] == '' || $column['Null'] == 'NO') + ? __('No') + : __('Yes')) + . '' + . ''; + $definition .= '' + . '' . htmlspecialchars($column['Default']) . '' + . ''; + + return $definition; + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Plugins/Export/ExportPdf.php b/php/apps/phpmyadmin49/libraries/classes/Plugins/Export/ExportPdf.php new file mode 100644 index 00000000..ff08fbc9 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Plugins/Export/ExportPdf.php @@ -0,0 +1,385 @@ +initSpecificVariables(); + + $this->setProperties(); + } + + /** + * Initialize the local variables that are used for export PDF + * + * @return void + */ + protected function initSpecificVariables() + { + if (!empty($_POST['pdf_report_title'])) { + $this->_setPdfReportTitle($_POST['pdf_report_title']); + } + $this->_setPdf(new Pdf('L', 'pt', 'A3')); + } + + /** + * Sets the export PDF properties + * + * @return void + */ + protected function setProperties() + { + $exportPluginProperties = new ExportPluginProperties(); + $exportPluginProperties->setText('PDF'); + $exportPluginProperties->setExtension('pdf'); + $exportPluginProperties->setMimeType('application/pdf'); + $exportPluginProperties->setForceFile(true); + $exportPluginProperties->setOptionsText(__('Options')); + + // create the root group that will be the options field for + // $exportPluginProperties + // this will be shown as "Format specific options" + $exportSpecificOptions = new OptionsPropertyRootGroup( + "Format Specific Options" + ); + + // general options main group + $generalOptions = new OptionsPropertyMainGroup("general_opts"); + // create primary items and add them to the group + $leaf = new TextPropertyItem( + "report_title", + __('Report title:') + ); + $generalOptions->addProperty($leaf); + // add the group to the root group + $exportSpecificOptions->addProperty($generalOptions); + + // what to dump (structure/data/both) main group + $dumpWhat = new OptionsPropertyMainGroup( + "dump_what", __('Dump table') + ); + $leaf = new RadioPropertyItem("structure_or_data"); + $leaf->setValues( + array( + 'structure' => __('structure'), + 'data' => __('data'), + 'structure_and_data' => __('structure and data'), + ) + ); + $dumpWhat->addProperty($leaf); + // add the group to the root group + $exportSpecificOptions->addProperty($dumpWhat); + + // set the options for the export plugin property item + $exportPluginProperties->setOptions($exportSpecificOptions); + $this->properties = $exportPluginProperties; + } + + /** + * Outputs export header + * + * @return bool Whether it succeeded + */ + public function exportHeader() + { + $pdf_report_title = $this->_getPdfReportTitle(); + $pdf = $this->_getPdf(); + $pdf->Open(); + + $attr = array('titleFontSize' => 18, 'titleText' => $pdf_report_title); + $pdf->setAttributes($attr); + $pdf->setTopMargin(30); + + return true; + } + + /** + * Outputs export footer + * + * @return bool Whether it succeeded + */ + public function exportFooter() + { + $pdf = $this->_getPdf(); + + // instead of $pdf->Output(): + return Export::outputHandler($pdf->getPDFData()); + } + + /** + * Outputs database header + * + * @param string $db Database name + * @param string $db_alias Aliases of db + * + * @return bool Whether it succeeded + */ + public function exportDBHeader($db, $db_alias = '') + { + return true; + } + + /** + * Outputs database footer + * + * @param string $db Database name + * + * @return bool Whether it succeeded + */ + public function exportDBFooter($db) + { + return true; + } + + /** + * Outputs CREATE DATABASE statement + * + * @param string $db Database name + * @param string $export_type 'server', 'database', 'table' + * @param string $db_alias Aliases of db + * + * @return bool Whether it succeeded + */ + public function exportDBCreate($db, $export_type, $db_alias = '') + { + return true; + } + + /** + * Outputs the content of a table in NHibernate format + * + * @param string $db database name + * @param string $table table name + * @param string $crlf the end of line sequence + * @param string $error_url the url to go back in case of error + * @param string $sql_query SQL query for obtaining data + * @param array $aliases Aliases of db/table/columns + * + * @return bool Whether it succeeded + */ + public function exportData( + $db, + $table, + $crlf, + $error_url, + $sql_query, + array $aliases = array() + ) { + $db_alias = $db; + $table_alias = $table; + $this->initAlias($aliases, $db_alias, $table_alias); + $pdf = $this->_getPdf(); + $attr = array( + 'currentDb' => $db, + 'currentTable' => $table, + 'dbAlias' => $db_alias, + 'tableAlias' => $table_alias, + 'aliases' => $aliases, + ); + $pdf->setAttributes($attr); + $pdf->purpose = __('Dumping data'); + $pdf->mysqlReport($sql_query); + + return true; + } // end of the 'PMA_exportData()' function + + /** + * Outputs table structure + * + * @param string $db database name + * @param string $table table name + * @param string $crlf the end of line sequence + * @param string $error_url the url to go back in case of error + * @param string $export_mode 'create_table', 'triggers', 'create_view', + * 'stand_in' + * @param string $export_type 'server', 'database', 'table' + * @param bool $do_relation whether to include relation comments + * @param bool $do_comments whether to include the pmadb-style column + * comments as comments in the structure; + * this is deprecated but the parameter is + * left here because export.php calls + * PMA_exportStructure() also for other + * export types which use this parameter + * @param bool $do_mime whether to include mime comments + * @param bool $dates whether to include creation/update/check dates + * @param array $aliases aliases for db/table/columns + * + * @return bool Whether it succeeded + */ + public function exportStructure( + $db, + $table, + $crlf, + $error_url, + $export_mode, + $export_type, + $do_relation = false, + $do_comments = false, + $do_mime = false, + $dates = false, + array $aliases = array() + ) { + $db_alias = $db; + $table_alias = $table; + $this->initAlias($aliases, $db_alias, $table_alias); + $pdf = $this->_getPdf(); + // getting purpose to show at top + switch ($export_mode) { + case 'create_table': + $purpose = __('Table structure'); + break; + case 'triggers': + $purpose = __('Triggers'); + break; + case 'create_view': + $purpose = __('View structure'); + break; + case 'stand_in': + $purpose = __('Stand in'); + } // end switch + + $attr = array( + 'currentDb' => $db, + 'currentTable' => $table, + 'dbAlias' => $db_alias, + 'tableAlias' => $table_alias, + 'aliases' => $aliases, + 'purpose' => $purpose, + ); + $pdf->setAttributes($attr); + /** + * comment display set true as presently in pdf + * format, no option is present to take user input. + */ + $do_comments = true; + switch ($export_mode) { + case 'create_table': + $pdf->getTableDef( + $db, + $table, + $do_relation, + $do_comments, + $do_mime, + false, + $aliases + ); + break; + case 'triggers': + $pdf->getTriggers($db, $table); + break; + case 'create_view': + $pdf->getTableDef( + $db, + $table, + $do_relation, + $do_comments, + $do_mime, + false, + $aliases + ); + break; + case 'stand_in': + /* export a stand-in definition to resolve view dependencies + * Yet to develop this function + * $pdf->getTableDefStandIn($db, $table, $crlf); + */ + } // end switch + + return true; + } + + + /* ~~~~~~~~~~~~~~~~~~~~ Getters and Setters ~~~~~~~~~~~~~~~~~~~~ */ + + /** + * Gets the PhpMyAdmin\Plugins\Export\Helpers\Pdf instance + * + * @return Pdf + */ + private function _getPdf() + { + return $this->_pdf; + } + + /** + * Instantiates the PhpMyAdmin\Plugins\Export\Helpers\Pdf class + * + * @param Pdf $pdf The instance + * + * @return void + */ + private function _setPdf($pdf) + { + $this->_pdf = $pdf; + } + + /** + * Gets the PDF report title + * + * @return string + */ + private function _getPdfReportTitle() + { + return $this->_pdfReportTitle; + } + + /** + * Sets the PDF report title + * + * @param string $pdfReportTitle PDF report title + * + * @return void + */ + private function _setPdfReportTitle($pdfReportTitle) + { + $this->_pdfReportTitle = $pdfReportTitle; + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Plugins/Export/ExportPhparray.php b/php/apps/phpmyadmin49/libraries/classes/Plugins/Export/ExportPhparray.php new file mode 100644 index 00000000..051ee3bf --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Plugins/Export/ExportPhparray.php @@ -0,0 +1,257 @@ +setProperties(); + } + + /** + * Sets the export PHP Array properties + * + * @return void + */ + protected function setProperties() + { + $exportPluginProperties = new ExportPluginProperties(); + $exportPluginProperties->setText('PHP array'); + $exportPluginProperties->setExtension('php'); + $exportPluginProperties->setMimeType('text/plain'); + $exportPluginProperties->setOptionsText(__('Options')); + + // create the root group that will be the options field for + // $exportPluginProperties + // this will be shown as "Format specific options" + $exportSpecificOptions = new OptionsPropertyRootGroup( + "Format Specific Options" + ); + + // general options main group + $generalOptions = new OptionsPropertyMainGroup("general_opts"); + // create primary items and add them to the group + $leaf = new HiddenPropertyItem("structure_or_data"); + $generalOptions->addProperty($leaf); + // add the main group to the root group + $exportSpecificOptions->addProperty($generalOptions); + + // set the options for the export plugin property item + $exportPluginProperties->setOptions($exportSpecificOptions); + $this->properties = $exportPluginProperties; + } + + /** + * Removes end of comment from a string + * + * @param string $string String to replace + * + * @return string + */ + public function commentString($string) + { + return strtr($string, '*/', '-'); + } + + + /** + * Outputs export header + * + * @return bool Whether it succeeded + */ + public function exportHeader() + { + Export::outputHandler( + 'commentString(Util::backquote($db_alias)) + . $GLOBALS['crlf'] . ' */' . $GLOBALS['crlf'] + ); + + return true; + } + + /** + * Outputs database footer + * + * @param string $db Database name + * + * @return bool Whether it succeeded + */ + public function exportDBFooter($db) + { + return true; + } + + /** + * Outputs CREATE DATABASE statement + * + * @param string $db Database name + * @param string $export_type 'server', 'database', 'table' + * @param string $db_alias Aliases of db + * + * @return bool Whether it succeeded + */ + public function exportDBCreate($db, $export_type, $db_alias = '') + { + return true; + } + + /** + * Outputs the content of a table in PHP array format + * + * @param string $db database name + * @param string $table table name + * @param string $crlf the end of line sequence + * @param string $error_url the url to go back in case of error + * @param string $sql_query SQL query for obtaining data + * @param array $aliases Aliases of db/table/columns + * + * @return bool Whether it succeeded + */ + public function exportData( + $db, + $table, + $crlf, + $error_url, + $sql_query, + array $aliases = array() + ) { + $db_alias = $db; + $table_alias = $table; + $this->initAlias($aliases, $db_alias, $table_alias); + + $result = $GLOBALS['dbi']->query( + $sql_query, + DatabaseInterface::CONNECT_USER, + DatabaseInterface::QUERY_UNBUFFERED + ); + + $columns_cnt = $GLOBALS['dbi']->numFields($result); + $columns = array(); + for ($i = 0; $i < $columns_cnt; $i++) { + $col_as = $GLOBALS['dbi']->fieldName($result, $i); + if (!empty($aliases[$db]['tables'][$table]['columns'][$col_as])) { + $col_as = $aliases[$db]['tables'][$table]['columns'][$col_as]; + } + $columns[$i] = stripslashes($col_as); + } + + // fix variable names (based on + // https://secure.php.net/manual/language.variables.basics.php) + if (!preg_match( + '/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$/', + $table_alias + ) + ) { + // fix invalid characters in variable names by replacing them with + // underscores + $tablefixed = preg_replace( + '/[^a-zA-Z0-9_\x7f-\xff]/', + '_', + $table_alias + ); + + // variable name must not start with a number or dash... + if (preg_match('/^[a-zA-Z_\x7f-\xff]/', $tablefixed) === 0) { + $tablefixed = '_' . $tablefixed; + } + } else { + $tablefixed = $table; + } + + $buffer = ''; + $record_cnt = 0; + // Output table name as comment + $buffer .= $crlf . '/* ' + . $this->commentString(Util::backquote($db_alias)) . '.' + . $this->commentString(Util::backquote($table_alias)) . ' */' . $crlf; + $buffer .= '$' . $tablefixed . ' = array('; + + while ($record = $GLOBALS['dbi']->fetchRow($result)) { + $record_cnt++; + + if ($record_cnt == 1) { + $buffer .= $crlf . ' array('; + } else { + $buffer .= ',' . $crlf . ' array('; + } + + for ($i = 0; $i < $columns_cnt; $i++) { + $buffer .= var_export($columns[$i], true) + . " => " . var_export($record[$i], true) + . (($i + 1 >= $columns_cnt) ? '' : ','); + } + + $buffer .= ')'; + } + + $buffer .= $crlf . ');' . $crlf; + if (!Export::outputHandler($buffer)) { + return false; + } + + $GLOBALS['dbi']->freeResult($result); + + return true; + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Plugins/Export/ExportSql.php b/php/apps/phpmyadmin49/libraries/classes/Plugins/Export/ExportSql.php new file mode 100644 index 00000000..d1eac7b8 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Plugins/Export/ExportSql.php @@ -0,0 +1,2878 @@ +setProperties(); + + // Avoids undefined variables, use NULL so isset() returns false + if (!isset($GLOBALS['sql_backquotes'])) { + $GLOBALS['sql_backquotes'] = null; + } + } + + /** + * Sets the export SQL properties + * + * @return void + */ + protected function setProperties() + { + global $plugin_param; + + $hide_sql = false; + $hide_structure = false; + if ($plugin_param['export_type'] == 'table' + && !$plugin_param['single_table'] + ) { + $hide_structure = true; + $hide_sql = true; + } + + if (!$hide_sql) { + $exportPluginProperties = new ExportPluginProperties(); + $exportPluginProperties->setText('SQL'); + $exportPluginProperties->setExtension('sql'); + $exportPluginProperties->setMimeType('text/x-sql'); + $exportPluginProperties->setOptionsText(__('Options')); + + // create the root group that will be the options field for + // $exportPluginProperties + // this will be shown as "Format specific options" + $exportSpecificOptions = new OptionsPropertyRootGroup( + "Format Specific Options" + ); + + // general options main group + $generalOptions = new OptionsPropertyMainGroup("general_opts"); + + // comments + $subgroup = new OptionsPropertySubgroup("include_comments"); + $leaf = new BoolPropertyItem( + 'include_comments', + __( + 'Display comments (includes info such as export' + . ' timestamp, PHP version, and server version)' + ) + ); + $subgroup->setSubgroupHeader($leaf); + + $leaf = new TextPropertyItem( + 'header_comment', + __('Additional custom header comment (\n splits lines):') + ); + $subgroup->addProperty($leaf); + $leaf = new BoolPropertyItem( + 'dates', + __( + 'Include a timestamp of when databases were created, last' + . ' updated, and last checked' + ) + ); + $subgroup->addProperty($leaf); + if (!empty($GLOBALS['cfgRelation']['relation'])) { + $leaf = new BoolPropertyItem( + 'relation', + __('Display foreign key relationships') + ); + $subgroup->addProperty($leaf); + } + if (!empty($GLOBALS['cfgRelation']['mimework'])) { + $leaf = new BoolPropertyItem( + 'mime', + __('Display MIME types') + ); + $subgroup->addProperty($leaf); + } + $generalOptions->addProperty($subgroup); + + // enclose in a transaction + $leaf = new BoolPropertyItem( + "use_transaction", + __('Enclose export in a transaction') + ); + $leaf->setDoc( + array( + 'programs', + 'mysqldump', + 'option_mysqldump_single-transaction', + ) + ); + $generalOptions->addProperty($leaf); + + // disable foreign key checks + $leaf = new BoolPropertyItem( + "disable_fk", + __('Disable foreign key checks') + ); + $leaf->setDoc( + array( + 'manual_MySQL_Database_Administration', + 'server-system-variables', + 'sysvar_foreign_key_checks', + ) + ); + $generalOptions->addProperty($leaf); + + // export views as tables + $leaf = new BoolPropertyItem( + "views_as_tables", + __('Export views as tables') + ); + $generalOptions->addProperty($leaf); + + // export metadata + $leaf = new BoolPropertyItem( + "metadata", + __('Export metadata') + ); + $generalOptions->addProperty($leaf); + + // compatibility maximization + $compats = $GLOBALS['dbi']->getCompatibilities(); + if (count($compats) > 0) { + $values = array(); + foreach ($compats as $val) { + $values[$val] = $val; + } + + $leaf = new SelectPropertyItem( + "compatibility", + __( + 'Database system or older MySQL server to maximize output' + . ' compatibility with:' + ) + ); + $leaf->setValues($values); + $leaf->setDoc( + array( + 'manual_MySQL_Database_Administration', + 'Server_SQL_mode', + ) + ); + $generalOptions->addProperty($leaf); + + unset($values); + } + + // what to dump (structure/data/both) + $subgroup = new OptionsPropertySubgroup( + "dump_table", __("Dump table") + ); + $leaf = new RadioPropertyItem('structure_or_data'); + $leaf->setValues( + array( + 'structure' => __('structure'), + 'data' => __('data'), + 'structure_and_data' => __('structure and data'), + ) + ); + $subgroup->setSubgroupHeader($leaf); + $generalOptions->addProperty($subgroup); + + // add the main group to the root group + $exportSpecificOptions->addProperty($generalOptions); + + // structure options main group + if (!$hide_structure) { + $structureOptions = new OptionsPropertyMainGroup( + "structure", __('Object creation options') + ); + $structureOptions->setForce('data'); + + // begin SQL Statements + $subgroup = new OptionsPropertySubgroup(); + $leaf = new MessageOnlyPropertyItem( + 'add_statements', + __('Add statements:') + ); + $subgroup->setSubgroupHeader($leaf); + + // server export options + if ($plugin_param['export_type'] == 'server') { + $leaf = new BoolPropertyItem( + "drop_database", + sprintf(__('Add %s statement'), 'DROP DATABASE IF EXISTS') + ); + $subgroup->addProperty($leaf); + } + + if ($plugin_param['export_type'] == 'database') { + $create_clause = 'CREATE DATABASE / USE'; + $leaf = new BoolPropertyItem( + 'create_database', + sprintf(__('Add %s statement'), $create_clause) + ); + $subgroup->addProperty($leaf); + } + + if ($plugin_param['export_type'] == 'table') { + $drop_clause = $GLOBALS['dbi']->getTable( + $GLOBALS['db'], + $GLOBALS['table'] + )->isView() + ? 'DROP VIEW' + : 'DROP TABLE'; + } else { + $drop_clause = 'DROP TABLE / VIEW / PROCEDURE' + . ' / FUNCTION / EVENT'; + } + + $drop_clause .= ' / TRIGGER'; + + $leaf = new BoolPropertyItem( + 'drop_table', + sprintf(__('Add %s statement'), $drop_clause) + ); + $subgroup->addProperty($leaf); + + $subgroup_create_table = new OptionsPropertySubgroup(); + + // Add table structure option + $leaf = new BoolPropertyItem( + 'create_table', + sprintf(__('Add %s statement'), 'CREATE TABLE') + ); + $subgroup_create_table->setSubgroupHeader($leaf); + + $leaf = new BoolPropertyItem( + 'if_not_exists', + 'IF NOT EXISTS ' . __( + '(less efficient as indexes will be generated during table ' + . 'creation)' + ) + ); + $subgroup_create_table->addProperty($leaf); + + $leaf = new BoolPropertyItem( + 'auto_increment', + sprintf(__('%s value'), 'AUTO_INCREMENT') + ); + $subgroup_create_table->addProperty($leaf); + + $subgroup->addProperty($subgroup_create_table); + + // Add view option + $leaf = new BoolPropertyItem( + 'create_view', + sprintf(__('Add %s statement'), 'CREATE VIEW') + ); + $subgroup->addProperty($leaf); + + $leaf = new BoolPropertyItem( + 'procedure_function', + sprintf( + __('Add %s statement'), + 'CREATE PROCEDURE / FUNCTION / EVENT' + ) + ); + $subgroup->addProperty($leaf); + + // Add triggers option + $leaf = new BoolPropertyItem( + 'create_trigger', + sprintf(__('Add %s statement'), 'CREATE TRIGGER') + ); + $subgroup->addProperty($leaf); + + $structureOptions->addProperty($subgroup); + + $leaf = new BoolPropertyItem( + "backquotes", + __( + 'Enclose table and column names with backquotes ' + . '(Protects column and table names formed with' + . ' special characters or keywords)' + ) + ); + + $structureOptions->addProperty($leaf); + + // add the main group to the root group + $exportSpecificOptions->addProperty($structureOptions); + } + + // begin Data options + $dataOptions = new OptionsPropertyMainGroup( + "data", __('Data creation options') + ); + $dataOptions->setForce('structure'); + $leaf = new BoolPropertyItem( + "truncate", + __('Truncate table before insert') + ); + $dataOptions->addProperty($leaf); + + // begin SQL Statements + $subgroup = new OptionsPropertySubgroup(); + $leaf = new MessageOnlyPropertyItem( + __('Instead of INSERT statements, use:') + ); + $subgroup->setSubgroupHeader($leaf); + + $leaf = new BoolPropertyItem( + "delayed", + __('INSERT DELAYED statements') + ); + $leaf->setDoc( + array( + 'manual_MySQL_Database_Administration', + 'insert_delayed' + ) + ); + $subgroup->addProperty($leaf); + + $leaf = new BoolPropertyItem( + "ignore", + __('INSERT IGNORE statements') + ); + $leaf->setDoc( + array( + 'manual_MySQL_Database_Administration', + 'insert', + ) + ); + $subgroup->addProperty($leaf); + $dataOptions->addProperty($subgroup); + + // Function to use when dumping dat + $leaf = new SelectPropertyItem( + "type", + __('Function to use when dumping data:') + ); + $leaf->setValues( + array( + 'INSERT' => 'INSERT', + 'UPDATE' => 'UPDATE', + 'REPLACE' => 'REPLACE', + ) + ); + $dataOptions->addProperty($leaf); + + /* Syntax to use when inserting data */ + $subgroup = new OptionsPropertySubgroup(); + $leaf = new MessageOnlyPropertyItem( + null, + __('Syntax to use when inserting data:') + ); + $subgroup->setSubgroupHeader($leaf); + $leaf = new RadioPropertyItem( + "insert_syntax", + __('INSERT IGNORE statements') + ); + $leaf->setValues( + array( + 'complete' => __( + 'include column names in every INSERT statement' + . '
            Example: INSERT INTO' + . ' tbl_name (col_A,col_B,col_C) VALUES (1,2,3)' + ), + 'extended' => __( + 'insert multiple rows in every INSERT statement' + . '
            Example: INSERT INTO' + . ' tbl_name VALUES (1,2,3), (4,5,6), (7,8,9)' + ), + 'both' => __( + 'both of the above
            Example:' + . ' INSERT INTO tbl_name (col_A,col_B,col_C) VALUES' + . ' (1,2,3), (4,5,6), (7,8,9)' + ), + 'none' => __( + 'neither of the above
            Example:' + . ' INSERT INTO tbl_name VALUES (1,2,3)' + ), + ) + ); + $subgroup->addProperty($leaf); + $dataOptions->addProperty($subgroup); + + // Max length of query + $leaf = new NumberPropertyItem( + "max_query_size", + __('Maximal length of created query') + ); + $dataOptions->addProperty($leaf); + + // Dump binary columns in hexadecimal + $leaf = new BoolPropertyItem( + "hex_for_binary", + __( + 'Dump binary columns in hexadecimal notation' + . ' (for example, "abc" becomes 0x616263)' + ) + ); + $dataOptions->addProperty($leaf); + + // Dump time in UTC + $leaf = new BoolPropertyItem( + "utc_time", + __( + 'Dump TIMESTAMP columns in UTC (enables TIMESTAMP columns' + . ' to be dumped and reloaded between servers in different' + . ' time zones)' + ) + ); + $dataOptions->addProperty($leaf); + + // add the main group to the root group + $exportSpecificOptions->addProperty($dataOptions); + + // set the options for the export plugin property item + $exportPluginProperties->setOptions($exportSpecificOptions); + $this->properties = $exportPluginProperties; + } + } + + /** + * Generates SQL for routines export + * + * @param string $db Database + * @param array $aliases Aliases of db/table/columns + * @param string $type Type of exported routine + * @param string $name Verbose name of exported routine + * @param array $routines List of routines to export + * @param string $delimiter Delimiter to use in SQL + * + * @return string SQL query + */ + protected function _exportRoutineSQL( + $db, array $aliases, $type, $name, array $routines, $delimiter + ) { + global $crlf; + + $text = $this->_exportComment() + . $this->_exportComment($name) + . $this->_exportComment(); + + $used_alias = false; + $proc_query = ''; + + foreach ($routines as $routine) { + if (!empty($GLOBALS['sql_drop_table'])) { + $proc_query .= 'DROP ' . $type . ' IF EXISTS ' + . Util::backquote($routine) + . $delimiter . $crlf; + } + $create_query = $this->replaceWithAliases( + $GLOBALS['dbi']->getDefinition($db, $type, $routine), + $aliases, + $db, + '', + $flag + ); + // One warning per database + if ($flag) { + $used_alias = true; + } + $proc_query .= $create_query . $delimiter . $crlf . $crlf; + } + if ($used_alias) { + $text .= $this->_exportComment( + __('It appears your database uses routines;') + ) + . $this->_exportComment( + __('alias export may not work reliably in all cases.') + ) + . $this->_exportComment(); + } + $text .= $proc_query; + + return $text; + } + + /** + * Exports routines (procedures and functions) + * + * @param string $db Database + * @param array $aliases Aliases of db/table/columns + * + * @return bool Whether it succeeded + */ + public function exportRoutines($db, array $aliases = array()) + { + global $crlf; + + $db_alias = $db; + $this->initAlias($aliases, $db_alias); + + $text = ''; + $delimiter = '$$'; + + $procedure_names = $GLOBALS['dbi'] + ->getProceduresOrFunctions($db, 'PROCEDURE'); + $function_names = $GLOBALS['dbi']->getProceduresOrFunctions($db, 'FUNCTION'); + + if ($procedure_names || $function_names) { + $text .= $crlf + . 'DELIMITER ' . $delimiter . $crlf; + + if ($procedure_names) { + $text .= $this->_exportRoutineSQL( + $db, + $aliases, + 'PROCEDURE', + __('Procedures'), + $procedure_names, + $delimiter + ); + } + + if ($function_names) { + $text .= $this->_exportRoutineSQL( + $db, + $aliases, + 'FUNCTION', + __('Functions'), + $function_names, + $delimiter + ); + } + + $text .= 'DELIMITER ;' . $crlf; + } + + if (!empty($text)) { + return Export::outputHandler($text); + } + + return false; + } + + /** + * Possibly outputs comment + * + * @param string $text Text of comment + * + * @return string The formatted comment + */ + private function _exportComment($text = '') + { + if (isset($GLOBALS['sql_include_comments']) + && $GLOBALS['sql_include_comments'] + ) { + // see https://dev.mysql.com/doc/refman/5.0/en/ansi-diff-comments.html + if (empty($text)) { + return '--' . $GLOBALS['crlf']; + } + + $lines = preg_split("/\\r\\n|\\r|\\n/", $text); + $result = array(); + foreach ($lines as $line) { + $result[] = '-- ' . $line . $GLOBALS['crlf']; + } + return implode('', $result); + } + + return ''; + } + + /** + * Possibly outputs CRLF + * + * @return string $crlf or nothing + */ + private function _possibleCRLF() + { + if (isset($GLOBALS['sql_include_comments']) + && $GLOBALS['sql_include_comments'] + ) { + return $GLOBALS['crlf']; + } + + return ''; + } + + /** + * Outputs export footer + * + * @return bool Whether it succeeded + */ + public function exportFooter() + { + global $crlf; + + $foot = ''; + + if (isset($GLOBALS['sql_disable_fk'])) { + $foot .= 'SET FOREIGN_KEY_CHECKS=1;' . $crlf; + } + + if (isset($GLOBALS['sql_use_transaction'])) { + $foot .= 'COMMIT;' . $crlf; + } + + // restore connection settings + if ($this->_sent_charset) { + $foot .= $crlf + . '/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;' + . $crlf + . '/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;' + . $crlf + . '/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;' + . $crlf; + $this->_sent_charset = false; + } + + /* Restore timezone */ + if (isset($GLOBALS['sql_utc_time']) && $GLOBALS['sql_utc_time']) { + $GLOBALS['dbi']->query('SET time_zone = "' . $GLOBALS['old_tz'] . '"'); + } + + return Export::outputHandler($foot); + } + + /** + * Outputs export header. It is the first method to be called, so all + * the required variables are initialized here. + * + * @return bool Whether it succeeded + */ + public function exportHeader() + { + global $crlf, $cfg; + + if (isset($GLOBALS['sql_compatibility'])) { + $tmp_compat = $GLOBALS['sql_compatibility']; + if ($tmp_compat == 'NONE') { + $tmp_compat = ''; + } + $GLOBALS['dbi']->tryQuery('SET SQL_MODE="' . $tmp_compat . '"'); + unset($tmp_compat); + } + $head = $this->_exportComment('phpMyAdmin SQL Dump') + . $this->_exportComment('version ' . PMA_VERSION) + . $this->_exportComment('https://www.phpmyadmin.net/') + . $this->_exportComment(); + $host_string = __('Host:') . ' ' . $cfg['Server']['host']; + if (!empty($cfg['Server']['port'])) { + $host_string .= ':' . $cfg['Server']['port']; + } + $head .= $this->_exportComment($host_string); + $head .= $this->_exportComment( + __('Generation Time:') . ' ' + . Util::localisedDate() + ) + . $this->_exportComment( + __('Server version:') . ' ' . $GLOBALS['dbi']->getVersionString() + ) + . $this->_exportComment(__('PHP Version:') . ' ' . phpversion()) + . $this->_possibleCRLF(); + + if (isset($GLOBALS['sql_header_comment']) + && !empty($GLOBALS['sql_header_comment']) + ) { + // '\n' is not a newline (like "\n" would be), it's the characters + // backslash and n, as explained on the export interface + $lines = explode('\n', $GLOBALS['sql_header_comment']); + $head .= $this->_exportComment(); + foreach ($lines as $one_line) { + $head .= $this->_exportComment($one_line); + } + $head .= $this->_exportComment(); + } + + if (isset($GLOBALS['sql_disable_fk'])) { + $head .= 'SET FOREIGN_KEY_CHECKS=0;' . $crlf; + } + + // We want exported AUTO_INCREMENT columns to have still same value, + // do this only for recent MySQL exports + if ((! isset($GLOBALS['sql_compatibility']) + || $GLOBALS['sql_compatibility'] == 'NONE') + ) { + $head .= 'SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";' . $crlf; + } + + if (isset($GLOBALS['sql_use_transaction'])) { + $head .= 'SET AUTOCOMMIT = 0;' . $crlf + . 'START TRANSACTION;' . $crlf; + } + + /* Change timezone if we should export timestamps in UTC */ + if (isset($GLOBALS['sql_utc_time']) && $GLOBALS['sql_utc_time']) { + $head .= 'SET time_zone = "+00:00";' . $crlf; + $GLOBALS['old_tz'] = $GLOBALS['dbi'] + ->fetchValue('SELECT @@session.time_zone'); + $GLOBALS['dbi']->query('SET time_zone = "+00:00"'); + } + + $head .= $this->_possibleCRLF(); + + if (! empty($GLOBALS['asfile'])) { + // we are saving as file, therefore we provide charset information + // so that a utility like the mysql client can interpret + // the file correctly + if (isset($GLOBALS['charset']) + && isset(Charsets::$mysql_charset_map[$GLOBALS['charset']]) + ) { + // we got a charset from the export dialog + $set_names = Charsets::$mysql_charset_map[$GLOBALS['charset']]; + } else { + // by default we use the connection charset + $set_names = Charsets::$mysql_charset_map['utf-8']; + } + if ($set_names == 'utf8' && $GLOBALS['dbi']->getVersion() > 50503) { + $set_names = 'utf8mb4'; + } + $head .= $crlf + . '/*!40101 SET @OLD_CHARACTER_SET_CLIENT=' + . '@@CHARACTER_SET_CLIENT */;' . $crlf + . '/*!40101 SET @OLD_CHARACTER_SET_RESULTS=' + . '@@CHARACTER_SET_RESULTS */;' . $crlf + . '/*!40101 SET @OLD_COLLATION_CONNECTION=' + . '@@COLLATION_CONNECTION */;' . $crlf + . '/*!40101 SET NAMES ' . $set_names . ' */;' . $crlf . $crlf; + $this->_sent_charset = true; + } + + return Export::outputHandler($head); + } + + /** + * Outputs CREATE DATABASE statement + * + * @param string $db Database name + * @param string $export_type 'server', 'database', 'table' + * @param string $db_alias Aliases of db + * + * @return bool Whether it succeeded + */ + public function exportDBCreate($db, $export_type, $db_alias = '') + { + global $crlf; + + if (empty($db_alias)) { + $db_alias = $db; + } + if (isset($GLOBALS['sql_compatibility'])) { + $compat = $GLOBALS['sql_compatibility']; + } else { + $compat = 'NONE'; + } + if (isset($GLOBALS['sql_drop_database'])) { + if (!Export::outputHandler( + 'DROP DATABASE IF EXISTS ' + . Util::backquoteCompat( + $db_alias, + $compat, + isset($GLOBALS['sql_backquotes']) + ) + . ';' . $crlf + ) + ) { + return false; + } + } + if ($export_type == 'database' && !isset($GLOBALS['sql_create_database'])) { + return true; + } + + $create_query = 'CREATE DATABASE IF NOT EXISTS ' + . Util::backquoteCompat( + $db_alias, + $compat, + isset($GLOBALS['sql_backquotes']) + ); + $collation = $GLOBALS['dbi']->getDbCollation($db); + if (mb_strpos($collation, '_')) { + $create_query .= ' DEFAULT CHARACTER SET ' + . mb_substr( + $collation, + 0, + mb_strpos($collation, '_') + ) + . ' COLLATE ' . $collation; + } else { + $create_query .= ' DEFAULT CHARACTER SET ' . $collation; + } + $create_query .= ';' . $crlf; + if (!Export::outputHandler($create_query)) { + return false; + } + + return $this->_exportUseStatement($db_alias, $compat); + } + + /** + * Outputs USE statement + * + * @param string $db db to use + * @param string $compat sql compatibility + * + * @return bool Whether it succeeded + */ + private function _exportUseStatement($db, $compat) + { + global $crlf; + + if (isset($GLOBALS['sql_compatibility']) + && $GLOBALS['sql_compatibility'] == 'NONE' + ) { + $result = Export::outputHandler( + 'USE ' + . Util::backquoteCompat( + $db, + $compat, + isset($GLOBALS['sql_backquotes']) + ) + . ';' . $crlf + ); + } else { + $result = Export::outputHandler('USE ' . $db . ';' . $crlf); + } + + return $result; + } + + /** + * Outputs database header + * + * @param string $db Database name + * @param string $db_alias Alias of db + * + * @return bool Whether it succeeded + */ + public function exportDBHeader($db, $db_alias = '') + { + if (empty($db_alias)) { + $db_alias = $db; + } + if (isset($GLOBALS['sql_compatibility'])) { + $compat = $GLOBALS['sql_compatibility']; + } else { + $compat = 'NONE'; + } + $head = $this->_exportComment() + . $this->_exportComment( + __('Database:') . ' ' + . Util::backquoteCompat( + $db_alias, + $compat, + isset($GLOBALS['sql_backquotes']) + ) + ) + . $this->_exportComment(); + + return Export::outputHandler($head); + } + + /** + * Outputs database footer + * + * @param string $db Database name + * + * @return bool Whether it succeeded + */ + public function exportDBFooter($db) + { + global $crlf; + + $result = true; + + //add indexes to the sql dump file + if (isset($GLOBALS['sql_indexes'])) { + $result = Export::outputHandler($GLOBALS['sql_indexes']); + unset($GLOBALS['sql_indexes']); + } + //add auto increments to the sql dump file + if (isset($GLOBALS['sql_auto_increments'])) { + $result = Export::outputHandler($GLOBALS['sql_auto_increments']); + unset($GLOBALS['sql_auto_increments']); + } + //add constraints to the sql dump file + if (isset($GLOBALS['sql_constraints'])) { + $result = Export::outputHandler($GLOBALS['sql_constraints']); + unset($GLOBALS['sql_constraints']); + } + + return $result; + } + + /** + * Exports events + * + * @param string $db Database + * + * @return bool Whether it succeeded + */ + public function exportEvents($db) + { + global $crlf; + + $text = ''; + $delimiter = '$$'; + + $event_names = $GLOBALS['dbi']->fetchResult( + "SELECT EVENT_NAME FROM information_schema.EVENTS WHERE" + . " EVENT_SCHEMA= '" . $GLOBALS['dbi']->escapeString($db) + . "';" + ); + + if ($event_names) { + $text .= $crlf + . "DELIMITER " . $delimiter . $crlf; + + $text .= $this->_exportComment() + . $this->_exportComment(__('Events')) + . $this->_exportComment(); + + foreach ($event_names as $event_name) { + if (!empty($GLOBALS['sql_drop_table'])) { + $text .= "DROP EVENT " + . Util::backquote($event_name) + . $delimiter . $crlf; + } + $text .= $GLOBALS['dbi']->getDefinition($db, 'EVENT', $event_name) + . $delimiter . $crlf . $crlf; + } + + $text .= "DELIMITER ;" . $crlf; + } + + if (!empty($text)) { + return Export::outputHandler($text); + } + + return false; + } + + /** + * Exports metadata from Configuration Storage + * + * @param string $db database being exported + * @param string|array $tables table(s) being exported + * @param array $metadataTypes types of metadata to export + * + * @return bool Whether it succeeded + */ + public function exportMetadata( + $db, + $tables, + array $metadataTypes + ) { + $cfgRelation = $this->relation->getRelationsParam(); + if (!isset($cfgRelation['db'])) { + return true; + } + + $comment = $this->_possibleCRLF() + . $this->_possibleCRLF() + . $this->_exportComment() + . $this->_exportComment(__('Metadata')) + . $this->_exportComment(); + if (!Export::outputHandler($comment)) { + return false; + } + + if (!$this->_exportUseStatement( + $cfgRelation['db'], + $GLOBALS['sql_compatibility'] + ) + ) { + return false; + } + + $r = true; + if (is_array($tables)) { + // export metadata for each table + foreach ($tables as $table) { + $r &= $this->_exportMetadata($db, $table, $metadataTypes); + } + // export metadata for the database + $r &= $this->_exportMetadata($db, null, $metadataTypes); + } else { + // export metadata for single table + $r &= $this->_exportMetadata($db, $tables, $metadataTypes); + } + + return $r; + } + + /** + * Exports metadata from Configuration Storage + * + * @param string $db database being exported + * @param string $table table being exported + * @param array $metadataTypes types of metadata to export + * + * @return bool Whether it succeeded + */ + private function _exportMetadata( + $db, + $table, + array $metadataTypes + ) { + $cfgRelation = $this->relation->getRelationsParam(); + + if (isset($table)) { + $types = array( + 'column_info' => 'db_name', + 'table_uiprefs' => 'db_name', + 'tracking' => 'db_name', + ); + } else { + $types = array( + 'bookmark' => 'dbase', + 'relation' => 'master_db', + 'pdf_pages' => 'db_name', + 'savedsearches' => 'db_name', + 'central_columns' => 'db_name', + ); + } + + $aliases = array(); + + $comment = $this->_possibleCRLF() + . $this->_exportComment(); + + if (isset($table)) { + $comment .= $this->_exportComment( + sprintf( + __('Metadata for table %s'), + $table + ) + ); + } else { + $comment .= $this->_exportComment( + sprintf( + __('Metadata for database %s'), + $db + ) + ); + } + + $comment .= $this->_exportComment(); + + if (!Export::outputHandler($comment)) { + return false; + } + + foreach ($types as $type => $dbNameColumn) { + if (in_array($type, $metadataTypes) && isset($cfgRelation[$type])) { + + // special case, designer pages and their coordinates + if ($type == 'pdf_pages') { + + $sql_query = "SELECT `page_nr`, `page_descr` FROM " + . Util::backquote($cfgRelation['db']) + . "." . Util::backquote($cfgRelation[$type]) + . " WHERE " . Util::backquote($dbNameColumn) + . " = '" . $GLOBALS['dbi']->escapeString($db) . "'"; + + $result = $GLOBALS['dbi']->fetchResult( + $sql_query, + 'page_nr', + 'page_descr' + ); + + foreach ($result as $page => $name) { + // insert row for pdf_page + $sql_query_row = "SELECT `db_name`, `page_descr` FROM " + . Util::backquote($cfgRelation['db']) + . "." . Util::backquote( + $cfgRelation[$type] + ) + . " WHERE " . Util::backquote( + $dbNameColumn + ) + . " = '" . $GLOBALS['dbi']->escapeString($db) . "'" + . " AND `page_nr` = '" . intval($page) . "'"; + + if (!$this->exportData( + $cfgRelation['db'], + $cfgRelation[$type], + $GLOBALS['crlf'], + '', + $sql_query_row, + $aliases + ) + ) { + return false; + } + + $lastPage = $GLOBALS['crlf'] + . "SET @LAST_PAGE = LAST_INSERT_ID();" + . $GLOBALS['crlf']; + if (!Export::outputHandler($lastPage)) { + return false; + } + + $sql_query_coords = "SELECT `db_name`, `table_name`, " + . "'@LAST_PAGE' AS `pdf_page_number`, `x`, `y` FROM " + . Util::backquote($cfgRelation['db']) + . "." . Util::backquote( + $cfgRelation['table_coords'] + ) + . " WHERE `pdf_page_number` = '" . $page . "'"; + + $GLOBALS['exporting_metadata'] = true; + if (!$this->exportData( + $cfgRelation['db'], + $cfgRelation['table_coords'], + $GLOBALS['crlf'], + '', + $sql_query_coords, + $aliases + ) + ) { + $GLOBALS['exporting_metadata'] = false; + + return false; + } + $GLOBALS['exporting_metadata'] = false; + } + continue; + } + + // remove auto_incrementing id field for some tables + if ($type == 'bookmark') { + $sql_query = "SELECT `dbase`, `user`, `label`, `query` FROM "; + } elseif ($type == 'column_info') { + $sql_query = "SELECT `db_name`, `table_name`, `column_name`," + . " `comment`, `mimetype`, `transformation`," + . " `transformation_options`, `input_transformation`," + . " `input_transformation_options` FROM"; + } elseif ($type == 'savedsearches') { + $sql_query = "SELECT `username`, `db_name`, `search_name`," + . " `search_data` FROM"; + } else { + $sql_query = "SELECT * FROM "; + } + $sql_query .= Util::backquote($cfgRelation['db']) + . '.' . Util::backquote($cfgRelation[$type]) + . " WHERE " . Util::backquote($dbNameColumn) + . " = '" . $GLOBALS['dbi']->escapeString($db) . "'"; + if (isset($table)) { + $sql_query .= " AND `table_name` = '" + . $GLOBALS['dbi']->escapeString($table) . "'"; + } + + if (!$this->exportData( + $cfgRelation['db'], + $cfgRelation[$type], + $GLOBALS['crlf'], + '', + $sql_query, + $aliases + ) + ) { + return false; + } + } + } + + return true; + } + + /** + * Returns a stand-in CREATE definition to resolve view dependencies + * + * @param string $db the database name + * @param string $view the view name + * @param string $crlf the end of line sequence + * @param array $aliases Aliases of db/table/columns + * + * @return string resulting definition + */ + public function getTableDefStandIn($db, $view, $crlf, $aliases = array()) + { + $db_alias = $db; + $view_alias = $view; + $this->initAlias($aliases, $db_alias, $view_alias); + $create_query = ''; + if (!empty($GLOBALS['sql_drop_table'])) { + $create_query .= 'DROP VIEW IF EXISTS ' + . Util::backquote($view_alias) + . ';' . $crlf; + } + + $create_query .= 'CREATE TABLE '; + + if (isset($GLOBALS['sql_if_not_exists']) + && $GLOBALS['sql_if_not_exists'] + ) { + $create_query .= 'IF NOT EXISTS '; + } + $create_query .= Util::backquote($view_alias) . ' (' . $crlf; + $tmp = array(); + $columns = $GLOBALS['dbi']->getColumnsFull($db, $view); + foreach ($columns as $column_name => $definition) { + $col_alias = $column_name; + if (!empty($aliases[$db]['tables'][$view]['columns'][$col_alias])) { + $col_alias = $aliases[$db]['tables'][$view]['columns'][$col_alias]; + } + $tmp[] = Util::backquote($col_alias) . ' ' . + $definition['Type'] . $crlf; + } + $create_query .= implode(',', $tmp) . ');' . $crlf; + + return ($create_query); + } + + /** + * Returns CREATE definition that matches $view's structure + * + * @param string $db the database name + * @param string $view the view name + * @param string $crlf the end of line sequence + * @param bool $add_semicolon whether to add semicolon and end-of-line at + * the end + * @param array $aliases Aliases of db/table/columns + * + * @return string resulting schema + */ + private function _getTableDefForView( + $db, + $view, + $crlf, + $add_semicolon = true, + array $aliases = array() + ) { + $db_alias = $db; + $view_alias = $view; + $this->initAlias($aliases, $db_alias, $view_alias); + $create_query = "CREATE TABLE"; + if (isset($GLOBALS['sql_if_not_exists'])) { + $create_query .= " IF NOT EXISTS "; + } + $create_query .= Util::backquote($view_alias) . "(" . $crlf; + + $columns = $GLOBALS['dbi']->getColumns($db, $view, null, true); + + $firstCol = true; + foreach ($columns as $column) { + $col_alias = $column['Field']; + if (!empty($aliases[$db]['tables'][$view]['columns'][$col_alias])) { + $col_alias = $aliases[$db]['tables'][$view]['columns'][$col_alias]; + } + $extracted_columnspec = Util::extractColumnSpec( + $column['Type'] + ); + + if (!$firstCol) { + $create_query .= "," . $crlf; + } + $create_query .= " " . Util::backquote($col_alias); + $create_query .= " " . $column['Type']; + if ($extracted_columnspec['can_contain_collation'] + && !empty($column['Collation']) + ) { + $create_query .= " COLLATE " . $column['Collation']; + } + if ($column['Null'] == 'NO') { + $create_query .= " NOT NULL"; + } + if (isset($column['Default'])) { + $create_query .= " DEFAULT '" + . $GLOBALS['dbi']->escapeString($column['Default']) . "'"; + } else { + if ($column['Null'] == 'YES') { + $create_query .= " DEFAULT NULL"; + } + } + if (!empty($column['Comment'])) { + $create_query .= " COMMENT '" + . $GLOBALS['dbi']->escapeString($column['Comment']) . "'"; + } + $firstCol = false; + } + $create_query .= $crlf . ")" . ($add_semicolon ? ';' : '') . $crlf; + + if (isset($GLOBALS['sql_compatibility'])) { + $compat = $GLOBALS['sql_compatibility']; + } else { + $compat = 'NONE'; + } + if ($compat == 'MSSQL') { + $create_query = $this->_makeCreateTableMSSQLCompatible( + $create_query + ); + } + + return $create_query; + } + + /** + * Returns $table's CREATE definition + * + * @param string $db the database name + * @param string $table the table name + * @param string $crlf the end of line sequence + * @param string $error_url the url to go back in case + * of error + * @param bool $show_dates whether to include creation/ + * update/check dates + * @param bool $add_semicolon whether to add semicolon and + * end-of-line at the end + * @param bool $view whether we're handling a view + * @param bool $update_indexes_increments whether we need to update + * two global variables + * @param array $aliases Aliases of db/table/columns + * + * @return string resulting schema + */ + public function getTableDef( + $db, + $table, + $crlf, + $error_url, + $show_dates = false, + $add_semicolon = true, + $view = false, + $update_indexes_increments = true, + array $aliases = array() + ) { + global $sql_drop_table, $sql_backquotes, $sql_constraints, + $sql_constraints_query, $sql_indexes, $sql_indexes_query, + $sql_auto_increments, $sql_drop_foreign_keys; + + $db_alias = $db; + $table_alias = $table; + $this->initAlias($aliases, $db_alias, $table_alias); + + $schema_create = ''; + $auto_increment = ''; + $new_crlf = $crlf; + + if (isset($GLOBALS['sql_compatibility'])) { + $compat = $GLOBALS['sql_compatibility']; + } else { + $compat = 'NONE'; + } + + // need to use PhpMyAdmin\DatabaseInterface::QUERY_STORE + // with $GLOBALS['dbi']->numRows() in mysqli + $result = $GLOBALS['dbi']->tryQuery( + 'SHOW TABLE STATUS FROM ' . Util::backquote($db) + . ' WHERE Name = \'' . $GLOBALS['dbi']->escapeString($table) . '\'', + DatabaseInterface::CONNECT_USER, + DatabaseInterface::QUERY_STORE + ); + if ($result != false) { + if ($GLOBALS['dbi']->numRows($result) > 0) { + $tmpres = $GLOBALS['dbi']->fetchAssoc($result); + + // Here we optionally add the AUTO_INCREMENT next value, + // but starting with MySQL 5.0.24, the clause is already included + // in SHOW CREATE TABLE so we'll remove it below + if (isset($GLOBALS['sql_auto_increment']) + && !empty($tmpres['Auto_increment']) + ) { + $auto_increment .= ' AUTO_INCREMENT=' + . $tmpres['Auto_increment'] . ' '; + } + + if ($show_dates + && isset($tmpres['Create_time']) + && !empty($tmpres['Create_time']) + ) { + $schema_create .= $this->_exportComment( + __('Creation:') . ' ' + . Util::localisedDate( + strtotime($tmpres['Create_time']) + ) + ); + $new_crlf = $this->_exportComment() . $crlf; + } + + if ($show_dates + && isset($tmpres['Update_time']) + && !empty($tmpres['Update_time']) + ) { + $schema_create .= $this->_exportComment( + __('Last update:') . ' ' + . Util::localisedDate( + strtotime($tmpres['Update_time']) + ) + ); + $new_crlf = $this->_exportComment() . $crlf; + } + + if ($show_dates + && isset($tmpres['Check_time']) + && !empty($tmpres['Check_time']) + ) { + $schema_create .= $this->_exportComment( + __('Last check:') . ' ' + . Util::localisedDate( + strtotime($tmpres['Check_time']) + ) + ); + $new_crlf = $this->_exportComment() . $crlf; + } + } + $GLOBALS['dbi']->freeResult($result); + } + + $schema_create .= $new_crlf; + + // no need to generate a DROP VIEW here, it was done earlier + if (!empty($sql_drop_table) + && !$GLOBALS['dbi']->getTable($db, $table)->isView() + ) { + $schema_create .= 'DROP TABLE IF EXISTS ' + . Util::backquote($table_alias, $sql_backquotes) . ';' + . $crlf; + } + + // Complete table dump, + // Whether to quote table and column names or not + if ($sql_backquotes) { + $GLOBALS['dbi']->query('SET SQL_QUOTE_SHOW_CREATE = 1'); + } else { + $GLOBALS['dbi']->query('SET SQL_QUOTE_SHOW_CREATE = 0'); + } + + // I don't see the reason why this unbuffered query could cause problems, + // because SHOW CREATE TABLE returns only one row, and we free the + // results below. Nonetheless, we got 2 user reports about this + // (see bug 1562533) so I removed the unbuffered mode. + // $result = $GLOBALS['dbi']->query('SHOW CREATE TABLE ' . backquote($db) + // . '.' . backquote($table), null, DatabaseInterface::QUERY_UNBUFFERED); + // + // Note: SHOW CREATE TABLE, at least in MySQL 5.1.23, does not + // produce a displayable result for the default value of a BIT + // column, nor does the mysqldump command. See MySQL bug 35796 + $GLOBALS['dbi']->tryQuery('USE ' . Util::backquote($db)); + $result = $GLOBALS['dbi']->tryQuery( + 'SHOW CREATE TABLE ' . Util::backquote($db) . '.' + . Util::backquote($table) + ); + // an error can happen, for example the table is crashed + $tmp_error = $GLOBALS['dbi']->getError(); + if ($tmp_error) { + $message = sprintf(__('Error reading structure for table %s:'), "$db.$table"); + $message .= ' ' . $tmp_error; + if (! defined('TESTSUITE')) { + trigger_error($message, E_USER_ERROR); + } + return $this->_exportComment($message); + } + + // Old mode is stored so it can be restored once exporting is done. + $old_mode = Context::$MODE; + + $warning = ''; + if ($result != false && ($row = $GLOBALS['dbi']->fetchRow($result))) { + $create_query = $row[1]; + unset($row); + + // Convert end of line chars to one that we want (note that MySQL + // doesn't return query it will accept in all cases) + if (mb_strpos($create_query, "(\r\n ")) { + $create_query = str_replace("\r\n", $crlf, $create_query); + } elseif (mb_strpos($create_query, "(\n ")) { + $create_query = str_replace("\n", $crlf, $create_query); + } elseif (mb_strpos($create_query, "(\r ")) { + $create_query = str_replace("\r", $crlf, $create_query); + } + + /* + * Drop database name from VIEW creation. + * + * This is a bit tricky, but we need to issue SHOW CREATE TABLE with + * database name, but we don't want name to show up in CREATE VIEW + * statement. + */ + if ($view) { + $create_query = preg_replace( + '/' . preg_quote(Util::backquote($db), '/') . '\./', + '', + $create_query + ); + } + + // Substitute aliases in `CREATE` query. + $create_query = $this->replaceWithAliases( + $create_query, + $aliases, + $db, + $table, + $flag + ); + + // One warning per view. + if ($flag && $view) { + $warning = $this->_exportComment() + . $this->_exportComment( + __('It appears your database uses views;') + ) + . $this->_exportComment( + __('alias export may not work reliably in all cases.') + ) + . $this->_exportComment(); + } + + // Adding IF NOT EXISTS, if required. + if (isset($GLOBALS['sql_if_not_exists'])) { + $create_query = preg_replace( + '/^CREATE TABLE/', + 'CREATE TABLE IF NOT EXISTS', + $create_query + ); + } + + // Making the query MSSQL compatible. + if ($compat == 'MSSQL') { + $create_query = $this->_makeCreateTableMSSQLCompatible( + $create_query + ); + } + + // Views have no constraints, indexes, etc. They do not require any + // analysis. + if (!$view) { + + if (empty($sql_backquotes)) { + // Option "Enclose table and column names with backquotes" + // was checked. + Context::$MODE |= Context::SQL_MODE_NO_ENCLOSING_QUOTES; + } + + // Using appropriate quotes. + if (($compat === 'MSSQL') || ($sql_backquotes === '"')) { + Context::$MODE |= Context::SQL_MODE_ANSI_QUOTES; + } + } + + /** + * Parser used for analysis. + * + * @var Parser + */ + $parser = new Parser($create_query); + + /** + * `CREATE TABLE` statement. + * + * @var SelectStatement + */ + $statement = $parser->statements[0]; + + if (!empty($statement->entityOptions)) { + $engine = $statement->entityOptions->has('ENGINE'); + } else { + $engine = ''; + } + + /* Avoid operation on ARCHIVE tables as those can not be altered */ + if (!empty($statement->fields) && (empty($engine) || strtoupper($engine) != 'ARCHIVE')) { + + /** + * Fragments containining definition of each constraint. + * + * @var array + */ + $constraints = array(); + + /** + * Fragments containining definition of each index. + * + * @var array + */ + $indexes = array(); + + /** + * Fragments containining definition of each FULLTEXT index. + * + * @var array + */ + $indexes_fulltext = array(); + + /** + * Fragments containining definition of each foreign key that will + * be dropped. + * + * @var array + */ + $dropped = array(); + + /** + * Fragment containining definition of the `AUTO_INCREMENT`. + * + * @var array + */ + $auto_increment = array(); + + // Scanning each field of the `CREATE` statement to fill the arrays + // above. + // If the field is used in any of the arrays above, it is removed + // from the original definition. + // Also, AUTO_INCREMENT attribute is removed. + /** @var CreateDefinition $field */ + foreach ($statement->fields as $key => $field) { + + if ($field->isConstraint) { + // Creating the parts that add constraints. + $constraints[] = $field::build($field); + unset($statement->fields[$key]); + } elseif (!empty($field->key)) { + // Creating the parts that add indexes (must not be + // constraints). + if ($field->key->type === 'FULLTEXT KEY') { + $indexes_fulltext[] = $field->build($field); + unset($statement->fields[$key]); + } else { + if (empty($GLOBALS['sql_if_not_exists'])) { + $indexes[] = str_replace( + 'COMMENT=\'', 'COMMENT \'', $field::build($field) + ); + unset($statement->fields[$key]); + } + } + } + + // Creating the parts that drop foreign keys. + if (!empty($field->key)) { + if ($field->key->type === 'FOREIGN KEY') { + $dropped[] = 'FOREIGN KEY ' . Context::escape( + $field->name + ); + unset($statement->fields[$key]); + } + } + + // Dropping AUTO_INCREMENT. + if (!empty($field->options)) { + if ($field->options->has('AUTO_INCREMENT') + && empty($GLOBALS['sql_if_not_exists']) + ) { + + $auto_increment[] = $field::build($field); + $field->options->remove('AUTO_INCREMENT'); + } + } + } + + /** + * The header of the `ALTER` statement (`ALTER TABLE tbl`). + * + * @var string + */ + $alter_header = 'ALTER TABLE ' . + Util::backquoteCompat( + $table_alias, + $compat, + $sql_backquotes + ); + + /** + * The footer of the `ALTER` statement (usually ';') + * + * @var string + */ + $alter_footer = ';' . $crlf; + + // Generating constraints-related query. + if (!empty($constraints)) { + $sql_constraints_query = $alter_header . $crlf . ' ADD ' + . implode(',' . $crlf . ' ADD ', $constraints) + . $alter_footer; + + $sql_constraints = $this->generateComment( + $crlf, + $sql_constraints, + __('Constraints for dumped tables'), + __('Constraints for table'), + $table_alias, + $compat + ) . $sql_constraints_query; + } + + // Generating indexes-related query. + $sql_indexes_query = ''; + + if (!empty($indexes)) { + $sql_indexes_query .= $alter_header . $crlf . ' ADD ' + . implode(',' . $crlf . ' ADD ', $indexes) + . $alter_footer; + } + + if (!empty($indexes_fulltext)) { + // InnoDB supports one FULLTEXT index creation at a time. + // So FULLTEXT indexes are created one-by-one after other + // indexes where created. + $sql_indexes_query .= $alter_header . + ' ADD ' . implode( + $alter_footer . $alter_header . ' ADD ', + $indexes_fulltext + ) . $alter_footer; + } + + if ((!empty($indexes)) || (!empty($indexes_fulltext))) { + $sql_indexes = $this->generateComment( + $crlf, + $sql_indexes, + __('Indexes for dumped tables'), + __('Indexes for table'), + $table_alias, + $compat + ) . $sql_indexes_query; + } + + // Generating drop foreign keys-related query. + if (!empty($dropped)) { + $sql_drop_foreign_keys = $alter_header . $crlf . ' DROP ' + . implode(',' . $crlf . ' DROP ', $dropped) + . $alter_footer; + } + + // Generating auto-increment-related query. + if ((! empty($auto_increment)) && ($update_indexes_increments)) { + $sql_auto_increments_query = $alter_header . $crlf . ' MODIFY ' + . implode(',' . $crlf . ' MODIFY ', $auto_increment); + if (isset($GLOBALS['sql_auto_increment']) + && ($statement->entityOptions->has('AUTO_INCREMENT') !== false) + ) { + if (!isset($GLOBALS['table_data']) + || (isset($GLOBALS['table_data']) + && in_array($table, $GLOBALS['table_data'])) + ) { + $sql_auto_increments_query .= ', AUTO_INCREMENT=' + . $statement->entityOptions->has('AUTO_INCREMENT'); + } + } + $sql_auto_increments_query .= ';' . $crlf; + + $sql_auto_increments = $this->generateComment( + $crlf, + $sql_auto_increments, + __('AUTO_INCREMENT for dumped tables'), + __('AUTO_INCREMENT for table'), + $table_alias, + $compat + ) . $sql_auto_increments_query; + } + + // Removing the `AUTO_INCREMENT` attribute from the `CREATE TABLE` + // too. + if (!empty($statement->entityOptions) + && (empty($GLOBALS['sql_if_not_exists']) + || empty($GLOBALS['sql_auto_increment'])) + ) { + $statement->entityOptions->remove('AUTO_INCREMENT'); + } + + // Rebuilding the query. + $create_query = $statement->build(); + } + + $schema_create .= $create_query; + } + + $GLOBALS['dbi']->freeResult($result); + + // Restoring old mode. + Context::$MODE = $old_mode; + + return $warning . $schema_create . ($add_semicolon ? ';' . $crlf : ''); + } // end of the 'getTableDef()' function + + /** + * Returns $table's comments, relations etc. + * + * @param string $db database name + * @param string $table table name + * @param string $crlf end of line sequence + * @param bool $do_relation whether to include relation comments + * @param bool $do_mime whether to include mime comments + * @param array $aliases Aliases of db/table/columns + * + * @return string resulting comments + */ + private function _getTableComments( + $db, + $table, + $crlf, + $do_relation = false, + $do_mime = false, + array $aliases = array() + ) { + global $cfgRelation, $sql_backquotes; + + $db_alias = $db; + $table_alias = $table; + $this->initAlias($aliases, $db_alias, $table_alias); + + $schema_create = ''; + + // Check if we can use Relations + list($res_rel, $have_rel) = $this->relation->getRelationsAndStatus( + $do_relation && !empty($cfgRelation['relation']), + $db, + $table + ); + + if ($do_mime && $cfgRelation['mimework']) { + if (!($mime_map = Transformations::getMIME($db, $table, true))) { + unset($mime_map); + } + } + + if (isset($mime_map) && count($mime_map) > 0) { + $schema_create .= $this->_possibleCRLF() + . $this->_exportComment() + . $this->_exportComment( + __('MIME TYPES FOR TABLE') . ' ' + . Util::backquote($table, $sql_backquotes) . ':' + ); + foreach ($mime_map as $mime_field => $mime) { + $schema_create .= $this->_exportComment( + ' ' + . Util::backquote($mime_field, $sql_backquotes) + ) + . $this->_exportComment( + ' ' + . Util::backquote( + $mime['mimetype'], + $sql_backquotes + ) + ); + } + $schema_create .= $this->_exportComment(); + } + + if ($have_rel) { + $schema_create .= $this->_possibleCRLF() + . $this->_exportComment() + . $this->_exportComment( + __('RELATIONSHIPS FOR TABLE') . ' ' + . Util::backquote($table_alias, $sql_backquotes) + . ':' + ); + + foreach ($res_rel as $rel_field => $rel) { + if ($rel_field != 'foreign_keys_data') { + $rel_field_alias = !empty( + $aliases[$db]['tables'][$table]['columns'][$rel_field] + ) ? $aliases[$db]['tables'][$table]['columns'][$rel_field] + : $rel_field; + $schema_create .= $this->_exportComment( + ' ' + . Util::backquote( + $rel_field_alias, + $sql_backquotes + ) + ) + . $this->_exportComment( + ' ' + . Util::backquote( + $rel['foreign_table'], + $sql_backquotes + ) + . ' -> ' + . Util::backquote( + $rel['foreign_field'], + $sql_backquotes + ) + ); + } else { + foreach ($rel as $one_key) { + foreach ($one_key['index_list'] as $index => $field) { + $rel_field_alias = !empty( + $aliases[$db]['tables'][$table]['columns'][$field] + ) ? $aliases[$db]['tables'][$table]['columns'][$field] + : $field; + $schema_create .= $this->_exportComment( + ' ' + . Util::backquote( + $rel_field_alias, + $sql_backquotes + ) + ) + . $this->_exportComment( + ' ' + . Util::backquote( + $one_key['ref_table_name'], + $sql_backquotes + ) + . ' -> ' + . Util::backquote( + $one_key['ref_index_list'][$index], + $sql_backquotes + ) + ); + } + } + } + } + $schema_create .= $this->_exportComment(); + } + + return $schema_create; + } // end of the '_getTableComments()' function + + /** + * Outputs table's structure + * + * @param string $db database name + * @param string $table table name + * @param string $crlf the end of line sequence + * @param string $error_url the url to go back in case of error + * @param string $export_mode 'create_table','triggers','create_view', + * 'stand_in' + * @param string $export_type 'server', 'database', 'table' + * @param bool $relation whether to include relation comments + * @param bool $comments whether to include the pmadb-style column + * comments as comments in the structure; this is + * deprecated but the parameter is left here + * because export.php calls exportStructure() + * also for other export types which use this + * parameter + * @param bool $mime whether to include mime comments + * @param bool $dates whether to include creation/update/check dates + * @param array $aliases Aliases of db/table/columns + * + * @return bool Whether it succeeded + */ + public function exportStructure( + $db, + $table, + $crlf, + $error_url, + $export_mode, + $export_type, + $relation = false, + $comments = false, + $mime = false, + $dates = false, + array $aliases = array() + ) { + $db_alias = $db; + $table_alias = $table; + $this->initAlias($aliases, $db_alias, $table_alias); + if (isset($GLOBALS['sql_compatibility'])) { + $compat = $GLOBALS['sql_compatibility']; + } else { + $compat = 'NONE'; + } + + $formatted_table_name = Util::backquoteCompat( + $table_alias, + $compat, + isset($GLOBALS['sql_backquotes']) + ); + $dump = $this->_possibleCRLF() + . $this->_exportComment(str_repeat('-', 56)) + . $this->_possibleCRLF() + . $this->_exportComment(); + + switch ($export_mode) { + case 'create_table': + $dump .= $this->_exportComment( + __('Table structure for table') . ' ' . $formatted_table_name + ); + $dump .= $this->_exportComment(); + $dump .= $this->getTableDef( + $db, + $table, + $crlf, + $error_url, + $dates, + true, + false, + true, + $aliases + ); + $dump .= $this->_getTableComments( + $db, + $table, + $crlf, + $relation, + $mime, + $aliases + ); + break; + case 'triggers': + $dump = ''; + $delimiter = '$$'; + $triggers = $GLOBALS['dbi']->getTriggers($db, $table, $delimiter); + if ($triggers) { + $dump .= $this->_possibleCRLF() + . $this->_exportComment() + . $this->_exportComment( + __('Triggers') . ' ' . $formatted_table_name + ) + . $this->_exportComment(); + $used_alias = false; + $trigger_query = ''; + foreach ($triggers as $trigger) { + if (!empty($GLOBALS['sql_drop_table'])) { + $trigger_query .= $trigger['drop'] . ';' . $crlf; + } + + $trigger_query .= 'DELIMITER ' . $delimiter . $crlf; + $trigger_query .= $this->replaceWithAliases( + $trigger['create'], + $aliases, + $db, + $table, + $flag + ); + if ($flag) { + $used_alias = true; + } + $trigger_query .= 'DELIMITER ;' . $crlf; + } + // One warning per table. + if ($used_alias) { + $dump .= $this->_exportComment( + __('It appears your table uses triggers;') + ) + . $this->_exportComment( + __('alias export may not work reliably in all cases.') + ) + . $this->_exportComment(); + } + $dump .= $trigger_query; + } + break; + case 'create_view': + if (empty($GLOBALS['sql_views_as_tables'])) { + $dump .= $this->_exportComment( + __('Structure for view') + . ' ' + . $formatted_table_name + ) + . $this->_exportComment(); + // delete the stand-in table previously created (if any) + if ($export_type != 'table') { + $dump .= 'DROP TABLE IF EXISTS ' + . Util::backquote($table_alias) . ';' . $crlf; + } + $dump .= $this->getTableDef( + $db, + $table, + $crlf, + $error_url, + $dates, + true, + true, + true, + $aliases + ); + } else { + $dump .= $this->_exportComment( + sprintf( + __('Structure for view %s exported as a table'), + $formatted_table_name + ) + ) + . $this->_exportComment(); + // delete the stand-in table previously created (if any) + if ($export_type != 'table') { + $dump .= 'DROP TABLE IF EXISTS ' + . Util::backquote($table_alias) . ';' . $crlf; + } + $dump .= $this->_getTableDefForView( + $db, + $table, + $crlf, + true, + $aliases + ); + } + break; + case 'stand_in': + $dump .= $this->_exportComment( + __('Stand-in structure for view') . ' ' . $formatted_table_name + ) + . $this->_exportComment( + __('(See below for the actual view)') + ) + . $this->_exportComment(); + // export a stand-in definition to resolve view dependencies + $dump .= $this->getTableDefStandIn($db, $table, $crlf, $aliases); + } // end switch + + // this one is built by getTableDef() to use in table copy/move + // but not in the case of export + unset($GLOBALS['sql_constraints_query']); + + return Export::outputHandler($dump); + } + + /** + * Outputs the content of a table in SQL format + * + * @param string $db database name + * @param string $table table name + * @param string $crlf the end of line sequence + * @param string $error_url the url to go back in case of error + * @param string $sql_query SQL query for obtaining data + * @param array $aliases Aliases of db/table/columns + * + * @return bool Whether it succeeded + */ + public function exportData( + $db, + $table, + $crlf, + $error_url, + $sql_query, + array $aliases = array() + ) { + global $current_row, $sql_backquotes; + + // Do not export data for merge tables + if ($GLOBALS['dbi']->getTable($db, $table)->isMerge()) { + return true; + } + + $db_alias = $db; + $table_alias = $table; + $this->initAlias($aliases, $db_alias, $table_alias); + + if (isset($GLOBALS['sql_compatibility'])) { + $compat = $GLOBALS['sql_compatibility']; + } else { + $compat = 'NONE'; + } + + $formatted_table_name = Util::backquoteCompat( + $table_alias, + $compat, + $sql_backquotes + ); + + // Do not export data for a VIEW, unless asked to export the view as a table + // (For a VIEW, this is called only when exporting a single VIEW) + if ($GLOBALS['dbi']->getTable($db, $table)->isView() + && empty($GLOBALS['sql_views_as_tables']) + ) { + $head = $this->_possibleCRLF() + . $this->_exportComment() + . $this->_exportComment('VIEW ' . ' ' . $formatted_table_name) + . $this->_exportComment(__('Data:') . ' ' . __('None')) + . $this->_exportComment() + . $this->_possibleCRLF(); + + return Export::outputHandler($head); + } + + $result = $GLOBALS['dbi']->tryQuery( + $sql_query, + DatabaseInterface::CONNECT_USER, + DatabaseInterface::QUERY_UNBUFFERED + ); + // a possible error: the table has crashed + $tmp_error = $GLOBALS['dbi']->getError(); + if ($tmp_error) { + $message = sprintf(__('Error reading data for table %s:'), "$db.$table"); + $message .= ' ' . $tmp_error; + if (! defined('TESTSUITE')) { + trigger_error($message, E_USER_ERROR); + } + return Export::outputHandler( + $this->_exportComment($message) + ); + } + + if ($result == false) { + $GLOBALS['dbi']->freeResult($result); + + return true; + } + + $fields_cnt = $GLOBALS['dbi']->numFields($result); + + // Get field information + $fields_meta = $GLOBALS['dbi']->getFieldsMeta($result); + $field_flags = array(); + for ($j = 0; $j < $fields_cnt; $j++) { + $field_flags[$j] = $GLOBALS['dbi']->fieldFlags($result, $j); + } + + $field_set = array(); + for ($j = 0; $j < $fields_cnt; $j++) { + $col_as = $fields_meta[$j]->name; + if (!empty($aliases[$db]['tables'][$table]['columns'][$col_as])) { + $col_as = $aliases[$db]['tables'][$table]['columns'][$col_as]; + } + $field_set[$j] = Util::backquoteCompat( + $col_as, + $compat, + $sql_backquotes + ); + } + + if (isset($GLOBALS['sql_type']) + && $GLOBALS['sql_type'] == 'UPDATE' + ) { + // update + $schema_insert = 'UPDATE '; + if (isset($GLOBALS['sql_ignore'])) { + $schema_insert .= 'IGNORE '; + } + // avoid EOL blank + $schema_insert .= Util::backquoteCompat( + $table_alias, + $compat, + $sql_backquotes + ) . ' SET'; + } else { + // insert or replace + if (isset($GLOBALS['sql_type']) + && $GLOBALS['sql_type'] == 'REPLACE' + ) { + $sql_command = 'REPLACE'; + } else { + $sql_command = 'INSERT'; + } + + // delayed inserts? + if (isset($GLOBALS['sql_delayed'])) { + $insert_delayed = ' DELAYED'; + } else { + $insert_delayed = ''; + } + + // insert ignore? + if (isset($GLOBALS['sql_type']) + && $GLOBALS['sql_type'] == 'INSERT' + && isset($GLOBALS['sql_ignore']) + ) { + $insert_delayed .= ' IGNORE'; + } + //truncate table before insert + if (isset($GLOBALS['sql_truncate']) + && $GLOBALS['sql_truncate'] + && $sql_command == 'INSERT' + ) { + $truncate = 'TRUNCATE TABLE ' + . Util::backquoteCompat( + $table_alias, + $compat, + $sql_backquotes + ) . ";"; + $truncatehead = $this->_possibleCRLF() + . $this->_exportComment() + . $this->_exportComment( + __('Truncate table before insert') . ' ' + . $formatted_table_name + ) + . $this->_exportComment() + . $crlf; + Export::outputHandler($truncatehead); + Export::outputHandler($truncate); + } + + // scheme for inserting fields + if ($GLOBALS['sql_insert_syntax'] == 'complete' + || $GLOBALS['sql_insert_syntax'] == 'both' + ) { + $fields = implode(', ', $field_set); + $schema_insert = $sql_command . $insert_delayed . ' INTO ' + . Util::backquoteCompat( + $table_alias, + $compat, + $sql_backquotes + ) + // avoid EOL blank + . ' (' . $fields . ') VALUES'; + } else { + $schema_insert = $sql_command . $insert_delayed . ' INTO ' + . Util::backquoteCompat( + $table_alias, + $compat, + $sql_backquotes + ) + . ' VALUES'; + } + } + + //\x08\\x09, not required + $current_row = 0; + $query_size = 0; + if (($GLOBALS['sql_insert_syntax'] == 'extended' + || $GLOBALS['sql_insert_syntax'] == 'both') + && (!isset($GLOBALS['sql_type']) + || $GLOBALS['sql_type'] != 'UPDATE') + ) { + $separator = ','; + $schema_insert .= $crlf; + } else { + $separator = ';'; + } + + while ($row = $GLOBALS['dbi']->fetchRow($result)) { + if ($current_row == 0) { + $head = $this->_possibleCRLF() + . $this->_exportComment() + . $this->_exportComment( + __('Dumping data for table') . ' ' + . $formatted_table_name + ) + . $this->_exportComment() + . $crlf; + if (!Export::outputHandler($head)) { + return false; + } + } + // We need to SET IDENTITY_INSERT ON for MSSQL + if (isset($GLOBALS['sql_compatibility']) + && $GLOBALS['sql_compatibility'] == 'MSSQL' + && $current_row == 0 + ) { + if (!Export::outputHandler( + 'SET IDENTITY_INSERT ' + . Util::backquoteCompat( + $table_alias, + $compat, + $sql_backquotes + ) + . ' ON ;' . $crlf + ) + ) { + return false; + } + } + $current_row++; + $values = array(); + for ($j = 0; $j < $fields_cnt; $j++) { + // NULL + if (!isset($row[$j]) || is_null($row[$j])) { + $values[] = 'NULL'; + } elseif ($fields_meta[$j]->numeric + && $fields_meta[$j]->type != 'timestamp' + && !$fields_meta[$j]->blob + ) { + // a number + // timestamp is numeric on some MySQL 4.1, BLOBs are + // sometimes numeric + $values[] = $row[$j]; + } elseif (stristr($field_flags[$j], 'BINARY') !== false + && isset($GLOBALS['sql_hex_for_binary']) + ) { + // a true BLOB + // - mysqldump only generates hex data when the --hex-blob + // option is used, for fields having the binary attribute + // no hex is generated + // - a TEXT field returns type blob but a real blob + // returns also the 'binary' flag + + // empty blobs need to be different, but '0' is also empty + // :-( + if (empty($row[$j]) && $row[$j] != '0') { + $values[] = '\'\''; + } else { + $values[] = '0x' . bin2hex($row[$j]); + } + } elseif ($fields_meta[$j]->type == 'bit') { + // detection of 'bit' works only on mysqli extension + $values[] = "b'" . $GLOBALS['dbi']->escapeString( + Util::printableBitValue( + $row[$j], + $fields_meta[$j]->length + ) + ) + . "'"; + } elseif ($fields_meta[$j]->type === 'geometry') { + // export GIS types as hex + $values[] = '0x' . bin2hex($row[$j]); + } elseif (!empty($GLOBALS['exporting_metadata']) + && $row[$j] == '@LAST_PAGE' + ) { + $values[] = '@LAST_PAGE'; + } else { + // something else -> treat as a string + $values[] = '\'' + . $GLOBALS['dbi']->escapeString($row[$j]) + . '\''; + } // end if + } // end for + + // should we make update? + if (isset($GLOBALS['sql_type']) + && $GLOBALS['sql_type'] == 'UPDATE' + ) { + + $insert_line = $schema_insert; + for ($i = 0; $i < $fields_cnt; $i++) { + if (0 == $i) { + $insert_line .= ' '; + } + if ($i > 0) { + // avoid EOL blank + $insert_line .= ','; + } + $insert_line .= $field_set[$i] . ' = ' . $values[$i]; + } + + list($tmp_unique_condition, $tmp_clause_is_unique) + = Util::getUniqueCondition( + $result, // handle + $fields_cnt, // fields_cnt + $fields_meta, // fields_meta + $row, // row + false, // force_unique + false, // restrict_to_table + null // analyzed_sql_results + ); + $insert_line .= ' WHERE ' . $tmp_unique_condition; + unset($tmp_unique_condition, $tmp_clause_is_unique); + } else { + + // Extended inserts case + if ($GLOBALS['sql_insert_syntax'] == 'extended' + || $GLOBALS['sql_insert_syntax'] == 'both' + ) { + if ($current_row == 1) { + $insert_line = $schema_insert . '(' + . implode(', ', $values) . ')'; + } else { + $insert_line = '(' . implode(', ', $values) . ')'; + $insertLineSize = mb_strlen($insert_line); + $sql_max_size = $GLOBALS['sql_max_query_size']; + if (isset($sql_max_size) + && $sql_max_size > 0 + && $query_size + $insertLineSize > $sql_max_size + ) { + if (!Export::outputHandler(';' . $crlf)) { + return false; + } + $query_size = 0; + $current_row = 1; + $insert_line = $schema_insert . $insert_line; + } + } + $query_size += mb_strlen($insert_line); + // Other inserts case + } else { + $insert_line = $schema_insert + . '(' . implode(', ', $values) . ')'; + } + } + unset($values); + + if (!Export::outputHandler( + ($current_row == 1 ? '' : $separator . $crlf) + . $insert_line + ) + ) { + return false; + } + } // end while + + if ($current_row > 0) { + if (!Export::outputHandler(';' . $crlf)) { + return false; + } + } + + // We need to SET IDENTITY_INSERT OFF for MSSQL + if (isset($GLOBALS['sql_compatibility']) + && $GLOBALS['sql_compatibility'] == 'MSSQL' + && $current_row > 0 + ) { + $outputSucceeded = Export::outputHandler( + $crlf . 'SET IDENTITY_INSERT ' + . Util::backquoteCompat( + $table_alias, + $compat, + $sql_backquotes + ) + . ' OFF;' . $crlf + ); + if (!$outputSucceeded) { + return false; + } + } + + $GLOBALS['dbi']->freeResult($result); + + return true; + } // end of the 'exportData()' function + + /** + * Make a create table statement compatible with MSSQL + * + * @param string $create_query MySQL create table statement + * + * @return string MSSQL compatible create table statement + */ + private function _makeCreateTableMSSQLCompatible($create_query) + { + // In MSSQL + // 1. No 'IF NOT EXISTS' in CREATE TABLE + // 2. DATE field doesn't exists, we will use DATETIME instead + // 3. UNSIGNED attribute doesn't exist + // 4. No length on INT, TINYINT, SMALLINT, BIGINT and no precision on + // FLOAT fields + // 5. No KEY and INDEX inside CREATE TABLE + // 6. DOUBLE field doesn't exists, we will use FLOAT instead + + $create_query = preg_replace( + "/^CREATE TABLE IF NOT EXISTS/", + 'CREATE TABLE', + $create_query + ); + // first we need to replace all lines ended with '" DATE ...,\n' + // last preg_replace preserve us from situation with date text + // inside DEFAULT field value + $create_query = preg_replace( + "/\" date DEFAULT NULL(,)?\n/", + '" datetime DEFAULT NULL$1' . "\n", + $create_query + ); + $create_query = preg_replace( + "/\" date NOT NULL(,)?\n/", + '" datetime NOT NULL$1' . "\n", + $create_query + ); + $create_query = preg_replace( + '/" date NOT NULL DEFAULT \'([^\'])/', + '" datetime NOT NULL DEFAULT \'$1', + $create_query + ); + + // next we need to replace all lines ended with ') UNSIGNED ...,' + // last preg_replace preserve us from situation with unsigned text + // inside DEFAULT field value + $create_query = preg_replace( + "/\) unsigned NOT NULL(,)?\n/", + ') NOT NULL$1' . "\n", + $create_query + ); + $create_query = preg_replace( + "/\) unsigned DEFAULT NULL(,)?\n/", + ') DEFAULT NULL$1' . "\n", + $create_query + ); + $create_query = preg_replace( + '/\) unsigned NOT NULL DEFAULT \'([^\'])/', + ') NOT NULL DEFAULT \'$1', + $create_query + ); + + // we need to replace all lines ended with + // '" INT|TINYINT([0-9]{1,}) ...,' last preg_replace preserve us + // from situation with int([0-9]{1,}) text inside DEFAULT field + // value + $create_query = preg_replace( + '/" (int|tinyint|smallint|bigint)\([0-9]+\) DEFAULT NULL(,)?\n/', + '" $1 DEFAULT NULL$2' . "\n", + $create_query + ); + $create_query = preg_replace( + '/" (int|tinyint|smallint|bigint)\([0-9]+\) NOT NULL(,)?\n/', + '" $1 NOT NULL$2' . "\n", + $create_query + ); + $create_query = preg_replace( + '/" (int|tinyint|smallint|bigint)\([0-9]+\) NOT NULL DEFAULT \'([^\'])/', + '" $1 NOT NULL DEFAULT \'$2', + $create_query + ); + + // we need to replace all lines ended with + // '" FLOAT|DOUBLE([0-9,]{1,}) ...,' + // last preg_replace preserve us from situation with + // float([0-9,]{1,}) text inside DEFAULT field value + $create_query = preg_replace( + '/" (float|double)(\([0-9]+,[0-9,]+\))? DEFAULT NULL(,)?\n/', + '" float DEFAULT NULL$3' . "\n", + $create_query + ); + $create_query = preg_replace( + '/" (float|double)(\([0-9,]+,[0-9,]+\))? NOT NULL(,)?\n/', + '" float NOT NULL$3' . "\n", + $create_query + ); + $create_query = preg_replace( + '/" (float|double)(\([0-9,]+,[0-9,]+\))? NOT NULL DEFAULT \'([^\'])/', + '" float NOT NULL DEFAULT \'$3', + $create_query + ); + + // @todo remove indexes from CREATE TABLE + + return $create_query; + } + + /** + * replaces db/table/column names with their aliases + * + * @param string $sql_query SQL query in which aliases are to be substituted + * @param array $aliases Alias information for db/table/column + * @param string $db the database name + * @param string $table the tablename + * @param string &$flag the flag denoting whether any replacement was done + * + * @return string query replaced with aliases + */ + public function replaceWithAliases( + $sql_query, + array $aliases, + $db, + $table = '', + &$flag = null + ) { + $flag = false; + + /** + * The parser of this query. + * + * @var Parser $parser + */ + $parser = new Parser($sql_query); + + if (empty($parser->statements[0])) { + return $sql_query; + } + + /** + * The statement that represents the query. + * + * @var \PhpMyAdmin\SqlParser\Statements\CreateStatement $statement + */ + $statement = $parser->statements[0]; + + /** + * Old database name. + * + * @var string $old_database + */ + $old_database = $db; + + // Replacing aliases in `CREATE TABLE` statement. + if ($statement->options->has('TABLE')) { + + // Extracting the name of the old database and table from the + // statement to make sure the parameters are corect. + if (!empty($statement->name->database)) { + $old_database = $statement->name->database; + } + + /** + * Old table name. + * + * @var string $old_table + */ + $old_table = $statement->name->table; + + // Finding the aliased database name. + // The database might be empty so we have to add a few checks. + $new_database = null; + if (!empty($statement->name->database)) { + $new_database = $statement->name->database; + if (!empty($aliases[$old_database]['alias'])) { + $new_database = $aliases[$old_database]['alias']; + } + } + + // Finding the aliases table name. + $new_table = $old_table; + if (!empty($aliases[$old_database]['tables'][$old_table]['alias'])) { + $new_table = $aliases[$old_database]['tables'][$old_table]['alias']; + } + + // Replacing new values. + if (($statement->name->database !== $new_database) + || ($statement->name->table !== $new_table) + ) { + $statement->name->database = $new_database; + $statement->name->table = $new_table; + $statement->name->expr = null; // Force rebuild. + $flag = true; + } + + foreach ($statement->fields as $field) { + + // Column name. + if (!empty($field->type)) { + if (!empty($aliases[$old_database]['tables'][$old_table]['columns'][$field->name])) { + $field->name = $aliases[$old_database]['tables'] + [$old_table]['columns'][$field->name]; + $flag = true; + } + } + + // Key's columns. + if (!empty($field->key)) { + foreach ($field->key->columns as $key => $column) { + if (!empty($aliases[$old_database]['tables'][$old_table]['columns'][$column['name']])) { + $field->key->columns[$key]['name'] = $aliases[$old_database] + ['tables'][$old_table]['columns'][$column['name']]; + $flag = true; + } + } + } + + // References. + if (!empty($field->references)) { + $ref_table = $field->references->table->table; + // Replacing table. + if (!empty($aliases[$old_database]['tables'][$ref_table]['alias'])) { + $field->references->table->table + = $aliases[$old_database]['tables'][$ref_table]['alias']; + $field->references->table->expr = null; + $flag = true; + } + // Replacing column names. + foreach ($field->references->columns as $key => $column) { + if (!empty($aliases[$old_database]['tables'][$ref_table]['columns'][$column])) { + $field->references->columns[$key] + = $aliases[$old_database]['tables'][$ref_table]['columns'][$column]; + $flag = true; + } + } + } + } + } elseif ($statement->options->has('TRIGGER')) { + + // Extracting the name of the old database and table from the + // statement to make sure the parameters are corect. + if (!empty($statement->table->database)) { + $old_database = $statement->table->database; + } + + /** + * Old table name. + * + * @var string $old_table + */ + $old_table = $statement->table->table; + + if (!empty($aliases[$old_database]['tables'][$old_table]['alias'])) { + $statement->table->table + = $aliases[$old_database]['tables'][$old_table]['alias']; + $statement->table->expr = null; // Force rebuild. + $flag = true; + } + } + + if (($statement->options->has('TRIGGER')) + || ($statement->options->has('PROCEDURE')) + || ($statement->options->has('FUNCTION')) + || ($statement->options->has('VIEW')) + ) { + + // Repalcing the body. + for ($i = 0, $count = count($statement->body); $i < $count; ++$i) { + + /** + * Token parsed at this moment. + * + * @var Token $token + */ + $token = $statement->body[$i]; + + // Replacing only symbols (that are not variables) and unknown + // identifiers. + if ((($token->type === Token::TYPE_SYMBOL) + && (!($token->flags & Token::FLAG_SYMBOL_VARIABLE))) + || ((($token->type === Token::TYPE_KEYWORD) + && (!($token->flags & Token::FLAG_KEYWORD_RESERVED))) + || ($token->type === Token::TYPE_NONE)) + ) { + $alias = $this->getAlias($aliases, $token->value); + if (!empty($alias)) { + // Replacing the token. + $token->token = Context::escape($alias); + $flag = true; + } + } + } + } + + return $statement->build(); + } + + /** + * Generate comment + * + * @param string $crlf Carriage return character + * @param string $sql_statement SQL statement + * @param string $comment1 Comment for dumped table + * @param string $comment2 Comment for current table + * @param string $table_alias Table alias + * @param string $compat Compatibility mode + * + * @return string + */ + protected function generateComment( + $crlf, + $sql_statement, + $comment1, + $comment2, + $table_alias, + $compat + ) { + if (!isset($sql_statement)) { + if (isset($GLOBALS['no_constraints_comments'])) { + $sql_statement = ''; + } else { + $sql_statement = $crlf + . $this->_exportComment() + . $this->_exportComment($comment1) + . $this->_exportComment(); + } + } + + // comments for current table + if (!isset($GLOBALS['no_constraints_comments'])) { + $sql_statement .= $crlf + . $this->_exportComment() + . $this->_exportComment( + $comment2 . ' ' . Util::backquoteCompat( + $table_alias, + $compat, + isset($GLOBALS['sql_backquotes']) + ) + ) + . $this->_exportComment(); + } + + return $sql_statement; + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Plugins/Export/ExportTexytext.php b/php/apps/phpmyadmin49/libraries/classes/Plugins/Export/ExportTexytext.php new file mode 100644 index 00000000..3100f3aa --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Plugins/Export/ExportTexytext.php @@ -0,0 +1,620 @@ +setProperties(); + } + + /** + * Sets the export Texy! text properties + * + * @return void + */ + protected function setProperties() + { + $exportPluginProperties = new ExportPluginProperties(); + $exportPluginProperties->setText('Texy! text'); + $exportPluginProperties->setExtension('txt'); + $exportPluginProperties->setMimeType('text/plain'); + $exportPluginProperties->setOptionsText(__('Options')); + + // create the root group that will be the options field for + // $exportPluginProperties + // this will be shown as "Format specific options" + $exportSpecificOptions = new OptionsPropertyRootGroup( + "Format Specific Options" + ); + + // what to dump (structure/data/both) main group + $dumpWhat = new OptionsPropertyMainGroup( + "general_opts", __('Dump table') + ); + // create primary items and add them to the group + $leaf = new RadioPropertyItem("structure_or_data"); + $leaf->setValues( + array( + 'structure' => __('structure'), + 'data' => __('data'), + 'structure_and_data' => __('structure and data'), + ) + ); + $dumpWhat->addProperty($leaf); + // add the main group to the root group + $exportSpecificOptions->addProperty($dumpWhat); + + // data options main group + $dataOptions = new OptionsPropertyMainGroup( + "data", __('Data dump options') + ); + $dataOptions->setForce('structure'); + // create primary items and add them to the group + $leaf = new BoolPropertyItem( + "columns", + __('Put columns names in the first row') + ); + $dataOptions->addProperty($leaf); + $leaf = new TextPropertyItem( + 'null', + __('Replace NULL with:') + ); + $dataOptions->addProperty($leaf); + // add the main group to the root group + $exportSpecificOptions->addProperty($dataOptions); + + // set the options for the export plugin property item + $exportPluginProperties->setOptions($exportSpecificOptions); + $this->properties = $exportPluginProperties; + } + + /** + * Outputs export header + * + * @return bool Whether it succeeded + */ + public function exportHeader() + { + return true; + } + + /** + * Outputs export footer + * + * @return bool Whether it succeeded + */ + public function exportFooter() + { + return true; + } + + /** + * Outputs database header + * + * @param string $db Database name + * @param string $db_alias Alias of db + * + * @return bool Whether it succeeded + */ + public function exportDBHeader($db, $db_alias = '') + { + if (empty($db_alias)) { + $db_alias = $db; + } + + return Export::outputHandler( + '===' . __('Database') . ' ' . $db_alias . "\n\n" + ); + } + + /** + * Outputs database footer + * + * @param string $db Database name + * + * @return bool Whether it succeeded + */ + public function exportDBFooter($db) + { + return true; + } + + /** + * Outputs CREATE DATABASE statement + * + * @param string $db Database name + * @param string $export_type 'server', 'database', 'table' + * @param string $db_alias Aliases of db + * + * @return bool Whether it succeeded + */ + public function exportDBCreate($db, $export_type, $db_alias = '') + { + return true; + } + + /** + * Outputs the content of a table in NHibernate format + * + * @param string $db database name + * @param string $table table name + * @param string $crlf the end of line sequence + * @param string $error_url the url to go back in case of error + * @param string $sql_query SQL query for obtaining data + * @param array $aliases Aliases of db/table/columns + * + * @return bool Whether it succeeded + */ + public function exportData( + $db, + $table, + $crlf, + $error_url, + $sql_query, + array $aliases = array() + ) { + global $what; + + $db_alias = $db; + $table_alias = $table; + $this->initAlias($aliases, $db_alias, $table_alias); + + if (!Export::outputHandler( + '== ' . __('Dumping data for table') . ' ' . $table_alias . "\n\n" + ) + ) { + return false; + } + + // Gets the data from the database + $result = $GLOBALS['dbi']->query( + $sql_query, + DatabaseInterface::CONNECT_USER, + DatabaseInterface::QUERY_UNBUFFERED + ); + $fields_cnt = $GLOBALS['dbi']->numFields($result); + + // If required, get fields name at the first line + if (isset($GLOBALS[$what . '_columns'])) { + $text_output = "|------\n"; + for ($i = 0; $i < $fields_cnt; $i++) { + $col_as = $GLOBALS['dbi']->fieldName($result, $i); + if (!empty($aliases[$db]['tables'][$table]['columns'][$col_as])) { + $col_as = $aliases[$db]['tables'][$table]['columns'][$col_as]; + } + $text_output .= '|' + . htmlspecialchars(stripslashes($col_as)); + } // end for + $text_output .= "\n|------\n"; + if (!Export::outputHandler($text_output)) { + return false; + } + } // end if + + // Format the data + while ($row = $GLOBALS['dbi']->fetchRow($result)) { + $text_output = ''; + for ($j = 0; $j < $fields_cnt; $j++) { + if (!isset($row[$j]) || is_null($row[$j])) { + $value = $GLOBALS[$what . '_null']; + } elseif ($row[$j] == '0' || $row[$j] != '') { + $value = $row[$j]; + } else { + $value = ' '; + } + $text_output .= '|' + . str_replace( + '|', + '|', + htmlspecialchars($value) + ); + } // end for + $text_output .= "\n"; + if (!Export::outputHandler($text_output)) { + return false; + } + } // end while + $GLOBALS['dbi']->freeResult($result); + + return true; + } + + /** + * Returns a stand-in CREATE definition to resolve view dependencies + * + * @param string $db the database name + * @param string $view the view name + * @param string $crlf the end of line sequence + * @param array $aliases Aliases of db/table/columns + * + * @return string resulting definition + */ + public function getTableDefStandIn($db, $view, $crlf, $aliases = array()) + { + $text_output = ''; + + /** + * Get the unique keys in the table + */ + $unique_keys = array(); + $keys = $GLOBALS['dbi']->getTableIndexes($db, $view); + foreach ($keys as $key) { + if ($key['Non_unique'] == 0) { + $unique_keys[] = $key['Column_name']; + } + } + + /** + * Gets fields properties + */ + $GLOBALS['dbi']->selectDb($db); + + /** + * Displays the table structure + */ + + $text_output .= "|------\n" + . '|' . __('Column') + . '|' . __('Type') + . '|' . __('Null') + . '|' . __('Default') + . "\n|------\n"; + + $columns = $GLOBALS['dbi']->getColumns($db, $view); + foreach ($columns as $column) { + $col_as = $column['Field']; + if (!empty($aliases[$db]['tables'][$view]['columns'][$col_as])) { + $col_as = $aliases[$db]['tables'][$view]['columns'][$col_as]; + } + $text_output .= $this->formatOneColumnDefinition( + $column, + $unique_keys, + $col_as + ); + $text_output .= "\n"; + } // end foreach + + return $text_output; + } + + /** + * Returns $table's CREATE definition + * + * @param string $db the database name + * @param string $table the table name + * @param string $crlf the end of line sequence + * @param string $error_url the url to go back in case of error + * @param bool $do_relation whether to include relation comments + * @param bool $do_comments whether to include the pmadb-style column + * comments as comments in the structure; + * this is deprecated but the parameter is + * left here because export.php calls + * $this->exportStructure() also for other + * export types which use this parameter + * @param bool $do_mime whether to include mime comments + * @param bool $show_dates whether to include creation/update/check dates + * @param bool $add_semicolon whether to add semicolon and end-of-line + * at the end + * @param bool $view whether we're handling a view + * @param array $aliases Aliases of db/table/columns + * + * @return string resulting schema + */ + public function getTableDef( + $db, + $table, + $crlf, + $error_url, + $do_relation, + $do_comments, + $do_mime, + $show_dates = false, + $add_semicolon = true, + $view = false, + array $aliases = array() + ) { + global $cfgRelation; + + $text_output = ''; + + /** + * Get the unique keys in the table + */ + $unique_keys = array(); + $keys = $GLOBALS['dbi']->getTableIndexes($db, $table); + foreach ($keys as $key) { + if ($key['Non_unique'] == 0) { + $unique_keys[] = $key['Column_name']; + } + } + + /** + * Gets fields properties + */ + $GLOBALS['dbi']->selectDb($db); + + // Check if we can use Relations + list($res_rel, $have_rel) = $this->relation->getRelationsAndStatus( + $do_relation && !empty($cfgRelation['relation']), + $db, + $table + ); + + /** + * Displays the table structure + */ + + $text_output .= "|------\n"; + $text_output .= '|' . __('Column'); + $text_output .= '|' . __('Type'); + $text_output .= '|' . __('Null'); + $text_output .= '|' . __('Default'); + if ($do_relation && $have_rel) { + $text_output .= '|' . __('Links to'); + } + if ($do_comments) { + $text_output .= '|' . __('Comments'); + $comments = $this->relation->getComments($db, $table); + } + if ($do_mime && $cfgRelation['mimework']) { + $text_output .= '|' . htmlspecialchars('MIME'); + $mime_map = Transformations::getMIME($db, $table, true); + } + $text_output .= "\n|------\n"; + + $columns = $GLOBALS['dbi']->getColumns($db, $table); + foreach ($columns as $column) { + $col_as = $column['Field']; + if (!empty($aliases[$db]['tables'][$table]['columns'][$col_as])) { + $col_as = $aliases[$db]['tables'][$table]['columns'][$col_as]; + } + $text_output .= $this->formatOneColumnDefinition( + $column, + $unique_keys, + $col_as + ); + $field_name = $column['Field']; + if ($do_relation && $have_rel) { + $text_output .= '|' . htmlspecialchars( + $this->getRelationString( + $res_rel, + $field_name, + $db, + $aliases + ) + ); + } + if ($do_comments && $cfgRelation['commwork']) { + $text_output .= '|' + . (isset($comments[$field_name]) + ? htmlspecialchars($comments[$field_name]) + : ''); + } + if ($do_mime && $cfgRelation['mimework']) { + $text_output .= '|' + . (isset($mime_map[$field_name]) + ? htmlspecialchars( + str_replace('_', '/', $mime_map[$field_name]['mimetype']) + ) + : ''); + } + + $text_output .= "\n"; + } // end foreach + + return $text_output; + } // end of the '$this->getTableDef()' function + + /** + * Outputs triggers + * + * @param string $db database name + * @param string $table table name + * + * @return string Formatted triggers list + */ + public function getTriggers($db, $table) + { + $dump = "|------\n"; + $dump .= '|' . __('Name'); + $dump .= '|' . __('Time'); + $dump .= '|' . __('Event'); + $dump .= '|' . __('Definition'); + $dump .= "\n|------\n"; + + $triggers = $GLOBALS['dbi']->getTriggers($db, $table); + + foreach ($triggers as $trigger) { + $dump .= '|' . $trigger['name']; + $dump .= '|' . $trigger['action_timing']; + $dump .= '|' . $trigger['event_manipulation']; + $dump .= '|' . + str_replace( + '|', + '|', + htmlspecialchars($trigger['definition']) + ); + $dump .= "\n"; + } + + return $dump; + } + + /** + * Outputs table's structure + * + * @param string $db database name + * @param string $table table name + * @param string $crlf the end of line sequence + * @param string $error_url the url to go back in case of error + * @param string $export_mode 'create_table', 'triggers', 'create_view', + * 'stand_in' + * @param string $export_type 'server', 'database', 'table' + * @param bool $do_relation whether to include relation comments + * @param bool $do_comments whether to include the pmadb-style column + * comments as comments in the structure; + * this is deprecated but the parameter is + * left here because export.php calls + * $this->exportStructure() also for other + * export types which use this parameter + * @param bool $do_mime whether to include mime comments + * @param bool $dates whether to include creation/update/check dates + * @param array $aliases Aliases of db/table/columns + * + * @return bool Whether it succeeded + */ + public function exportStructure( + $db, + $table, + $crlf, + $error_url, + $export_mode, + $export_type, + $do_relation = false, + $do_comments = false, + $do_mime = false, + $dates = false, + array $aliases = array() + ) { + $db_alias = $db; + $table_alias = $table; + $this->initAlias($aliases, $db_alias, $table_alias); + $dump = ''; + + switch ($export_mode) { + case 'create_table': + $dump .= '== ' . __('Table structure for table') . ' ' + . $table_alias . "\n\n"; + $dump .= $this->getTableDef( + $db, + $table, + $crlf, + $error_url, + $do_relation, + $do_comments, + $do_mime, + $dates, + true, + false, + $aliases + ); + break; + case 'triggers': + $dump = ''; + $triggers = $GLOBALS['dbi']->getTriggers($db, $table); + if ($triggers) { + $dump .= '== ' . __('Triggers') . ' ' . $table_alias . "\n\n"; + $dump .= $this->getTriggers($db, $table); + } + break; + case 'create_view': + $dump .= '== ' . __('Structure for view') . ' ' . $table_alias . "\n\n"; + $dump .= $this->getTableDef( + $db, + $table, + $crlf, + $error_url, + $do_relation, + $do_comments, + $do_mime, + $dates, + true, + true, + $aliases + ); + break; + case 'stand_in': + $dump .= '== ' . __('Stand-in structure for view') + . ' ' . $table . "\n\n"; + // export a stand-in definition to resolve view dependencies + $dump .= $this->getTableDefStandIn($db, $table, $crlf, $aliases); + } // end switch + + return Export::outputHandler($dump); + } + + /** + * Formats the definition for one column + * + * @param array $column info about this column + * @param array $unique_keys unique keys for this table + * @param string $col_alias Column Alias + * + * @return string Formatted column definition + */ + public function formatOneColumnDefinition( + $column, + $unique_keys, + $col_alias = '' + ) { + if (empty($col_alias)) { + $col_alias = $column['Field']; + } + $extracted_columnspec + = Util::extractColumnSpec($column['Type']); + $type = $extracted_columnspec['print_type']; + if (empty($type)) { + $type = ' '; + } + + if (!isset($column['Default'])) { + if ($column['Null'] != 'NO') { + $column['Default'] = 'NULL'; + } + } + + $fmt_pre = ''; + $fmt_post = ''; + if (in_array($column['Field'], $unique_keys)) { + $fmt_pre = '**' . $fmt_pre; + $fmt_post = $fmt_post . '**'; + } + if ($column['Key'] == 'PRI') { + $fmt_pre = '//' . $fmt_pre; + $fmt_post = $fmt_post . '//'; + } + $definition = '|' + . $fmt_pre . htmlspecialchars($col_alias) . $fmt_post; + $definition .= '|' . htmlspecialchars($type); + $definition .= '|' + . (($column['Null'] == '' || $column['Null'] == 'NO') + ? __('No') : __('Yes')); + $definition .= '|' + . htmlspecialchars( + isset($column['Default']) ? $column['Default'] : '' + ); + + return $definition; + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Plugins/Export/ExportXml.php b/php/apps/phpmyadmin49/libraries/classes/Plugins/Export/ExportXml.php new file mode 100644 index 00000000..b6ab2d9c --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Plugins/Export/ExportXml.php @@ -0,0 +1,584 @@ +setProperties(); + } + + /** + * Initialize the local variables that are used for export XML + * + * @return void + */ + protected function initSpecificVariables() + { + global $table, $tables; + $this->_setTable($table); + if (is_array($tables)) { + $this->_setTables($tables); + } + } + + /** + * Sets the export XML properties + * + * @return void + */ + protected function setProperties() + { + // create the export plugin property item + $exportPluginProperties = new ExportPluginProperties(); + $exportPluginProperties->setText('XML'); + $exportPluginProperties->setExtension('xml'); + $exportPluginProperties->setMimeType('text/xml'); + $exportPluginProperties->setOptionsText(__('Options')); + + // create the root group that will be the options field for + // $exportPluginProperties + // this will be shown as "Format specific options" + $exportSpecificOptions = new OptionsPropertyRootGroup( + "Format Specific Options" + ); + + // general options main group + $generalOptions = new OptionsPropertyMainGroup("general_opts"); + // create primary items and add them to the group + $leaf = new HiddenPropertyItem("structure_or_data"); + $generalOptions->addProperty($leaf); + // add the main group to the root group + $exportSpecificOptions->addProperty($generalOptions); + + // export structure main group + $structure = new OptionsPropertyMainGroup( + "structure", __('Object creation options (all are recommended)') + ); + + // create primary items and add them to the group + $leaf = new BoolPropertyItem( + "export_events", + __('Events') + ); + $structure->addProperty($leaf); + $leaf = new BoolPropertyItem( + "export_functions", + __('Functions') + ); + $structure->addProperty($leaf); + $leaf = new BoolPropertyItem( + "export_procedures", + __('Procedures') + ); + $structure->addProperty($leaf); + $leaf = new BoolPropertyItem( + "export_tables", + __('Tables') + ); + $structure->addProperty($leaf); + $leaf = new BoolPropertyItem( + "export_triggers", + __('Triggers') + ); + $structure->addProperty($leaf); + $leaf = new BoolPropertyItem( + "export_views", + __('Views') + ); + $structure->addProperty($leaf); + $exportSpecificOptions->addProperty($structure); + + // data main group + $data = new OptionsPropertyMainGroup( + "data", __('Data dump options') + ); + // create primary items and add them to the group + $leaf = new BoolPropertyItem( + "export_contents", + __('Export contents') + ); + $data->addProperty($leaf); + $exportSpecificOptions->addProperty($data); + + // set the options for the export plugin property item + $exportPluginProperties->setOptions($exportSpecificOptions); + $this->properties = $exportPluginProperties; + } + + /** + * Generates output for SQL defintions of routines + * + * @param string $db Database name + * @param string $type Item type to be used in XML output + * @param string $dbitype Item type used in DBI qieries + * + * @return string XML with definitions + */ + private function _exportRoutines($db, $type, $dbitype) + { + // Export routines + $routines = $GLOBALS['dbi']->getProceduresOrFunctions( + $db, + $dbitype + ); + return $this->_exportDefinitions($db, $type, $dbitype, $routines); + } + + /** + * Generates output for SQL defintions + * + * @param string $db Database name + * @param string $type Item type to be used in XML output + * @param string $dbitype Item type used in DBI qieries + * @param array $names Names of items to export + * + * @return string XML with definitions + */ + private function _exportDefinitions($db, $type, $dbitype, array $names) + { + global $crlf; + + $head = ''; + + if ($names) { + foreach ($names as $name) { + $head .= ' ' . $crlf; + + // Do some formatting + $sql = $GLOBALS['dbi']->getDefinition($db, $dbitype, $name); + $sql = htmlspecialchars(rtrim($sql)); + $sql = str_replace("\n", "\n ", $sql); + + $head .= " " . $sql . $crlf; + $head .= ' ' . $crlf; + } + } + + return $head; + } + + /** + * Outputs export header. It is the first method to be called, so all + * the required variables are initialized here. + * + * @return bool Whether it succeeded + */ + public function exportHeader() + { + $this->initSpecificVariables(); + global $crlf, $cfg, $db; + $table = $this->_getTable(); + $tables = $this->_getTables(); + + $export_struct = isset($GLOBALS['xml_export_functions']) + || isset($GLOBALS['xml_export_procedures']) + || isset($GLOBALS['xml_export_tables']) + || isset($GLOBALS['xml_export_triggers']) + || isset($GLOBALS['xml_export_views']); + $export_data = isset($GLOBALS['xml_export_contents']) ? true : false; + + if ($GLOBALS['output_charset_conversion']) { + $charset = $GLOBALS['charset']; + } else { + $charset = 'utf-8'; + } + + $head = '' . $crlf + . '' . $crlf . $crlf; + + $head .= '' . $crlf; + + if ($export_struct) { + $result = $GLOBALS['dbi']->fetchResult( + 'SELECT `DEFAULT_CHARACTER_SET_NAME`, `DEFAULT_COLLATION_NAME`' + . ' FROM `information_schema`.`SCHEMATA` WHERE `SCHEMA_NAME`' + . ' = \'' . $GLOBALS['dbi']->escapeString($db) . '\' LIMIT 1' + ); + $db_collation = $result[0]['DEFAULT_COLLATION_NAME']; + $db_charset = $result[0]['DEFAULT_CHARACTER_SET_NAME']; + + $head .= ' ' . $crlf; + $head .= ' ' . $crlf; + $head .= ' ' . $crlf; + + if (is_null($tables)) { + $tables = array(); + } + + if (count($tables) === 0) { + $tables[] = $table; + } + + foreach ($tables as $table) { + // Export tables and views + $result = $GLOBALS['dbi']->fetchResult( + 'SHOW CREATE TABLE ' . Util::backquote($db) . '.' + . Util::backquote($table), + 0 + ); + $tbl = $result[$table][1]; + + $is_view = $GLOBALS['dbi']->getTable($db, $table) + ->isView(); + + if ($is_view) { + $type = 'view'; + } else { + $type = 'table'; + } + + if ($is_view && !isset($GLOBALS['xml_export_views'])) { + continue; + } + + if (!$is_view && !isset($GLOBALS['xml_export_tables'])) { + continue; + } + + $head .= ' ' + . $crlf; + + $tbl = " " . htmlspecialchars($tbl); + $tbl = str_replace("\n", "\n ", $tbl); + + $head .= $tbl . ';' . $crlf; + $head .= ' ' . $crlf; + + if (isset($GLOBALS['xml_export_triggers']) + && $GLOBALS['xml_export_triggers'] + ) { + // Export triggers + $triggers = $GLOBALS['dbi']->getTriggers($db, $table); + if ($triggers) { + foreach ($triggers as $trigger) { + $code = $trigger['create']; + $head .= ' ' . $crlf; + + // Do some formatting + $code = mb_substr(rtrim($code), 0, -3); + $code = " " . htmlspecialchars($code); + $code = str_replace("\n", "\n ", $code); + + $head .= $code . $crlf; + $head .= ' ' . $crlf; + } + + unset($trigger); + unset($triggers); + } + } + } + + if (isset($GLOBALS['xml_export_functions']) + && $GLOBALS['xml_export_functions'] + ) { + $head .= $this->_exportRoutines($db, 'function', 'FUNCTION'); + } + + if (isset($GLOBALS['xml_export_procedures']) + && $GLOBALS['xml_export_procedures'] + ) { + $head .= $this->_exportRoutines($db, 'procedure', 'PROCEDURE'); + } + + if (isset($GLOBALS['xml_export_events']) + && $GLOBALS['xml_export_events'] + ) { + // Export events + $events = $GLOBALS['dbi']->fetchResult( + "SELECT EVENT_NAME FROM information_schema.EVENTS " + . "WHERE EVENT_SCHEMA='" . $GLOBALS['dbi']->escapeString($db) + . "'" + ); + $head .= $this->_exportDefinitions( + $db, 'event', 'EVENT', $events + ); + } + + unset($result); + + $head .= ' ' . $crlf; + $head .= ' ' . $crlf; + + if ($export_data) { + $head .= $crlf; + } + } + + return Export::outputHandler($head); + } + + /** + * Outputs export footer + * + * @return bool Whether it succeeded + */ + public function exportFooter() + { + $foot = ''; + + return Export::outputHandler($foot); + } + + /** + * Outputs database header + * + * @param string $db Database name + * @param string $db_alias Aliases of db + * + * @return bool Whether it succeeded + */ + public function exportDBHeader($db, $db_alias = '') + { + global $crlf; + + if (empty($db_alias)) { + $db_alias = $db; + } + if (isset($GLOBALS['xml_export_contents']) + && $GLOBALS['xml_export_contents'] + ) { + $head = ' ' . $crlf . ' ' . $crlf; + + return Export::outputHandler($head); + } + + return true; + } + + /** + * Outputs database footer + * + * @param string $db Database name + * + * @return bool Whether it succeeded + */ + public function exportDBFooter($db) + { + global $crlf; + + if (isset($GLOBALS['xml_export_contents']) + && $GLOBALS['xml_export_contents'] + ) { + return Export::outputHandler(' ' . $crlf); + } + + return true; + } + + /** + * Outputs CREATE DATABASE statement + * + * @param string $db Database name + * @param string $export_type 'server', 'database', 'table' + * @param string $db_alias Aliases of db + * + * @return bool Whether it succeeded + */ + public function exportDBCreate($db, $export_type, $db_alias = '') + { + return true; + } + + /** + * Outputs the content of a table in XML format + * + * @param string $db database name + * @param string $table table name + * @param string $crlf the end of line sequence + * @param string $error_url the url to go back in case of error + * @param string $sql_query SQL query for obtaining data + * @param array $aliases Aliases of db/table/columns + * + * @return bool Whether it succeeded + */ + public function exportData( + $db, + $table, + $crlf, + $error_url, + $sql_query, + array $aliases = array() + ) { + // Do not export data for merge tables + if ($GLOBALS['dbi']->getTable($db, $table)->isMerge()) { + return true; + } + + $db_alias = $db; + $table_alias = $table; + $this->initAlias($aliases, $db_alias, $table_alias); + if (isset($GLOBALS['xml_export_contents']) + && $GLOBALS['xml_export_contents'] + ) { + $result = $GLOBALS['dbi']->query( + $sql_query, + DatabaseInterface::CONNECT_USER, + DatabaseInterface::QUERY_UNBUFFERED + ); + + $columns_cnt = $GLOBALS['dbi']->numFields($result); + $columns = array(); + for ($i = 0; $i < $columns_cnt; $i++) { + $columns[$i] = stripslashes($GLOBALS['dbi']->fieldName($result, $i)); + } + unset($i); + + $buffer = ' ' . $crlf; + if (!Export::outputHandler($buffer)) { + return false; + } + + while ($record = $GLOBALS['dbi']->fetchRow($result)) { + $buffer = ' ' . $crlf; + for ($i = 0; $i < $columns_cnt; $i++) { + $col_as = $columns[$i]; + if (!empty($aliases[$db]['tables'][$table]['columns'][$col_as]) + ) { + $col_as + = $aliases[$db]['tables'][$table]['columns'][$col_as]; + } + // If a cell is NULL, still export it to preserve + // the XML structure + if (!isset($record[$i]) || is_null($record[$i])) { + $record[$i] = 'NULL'; + } + $buffer .= ' ' + . htmlspecialchars((string)$record[$i]) + . '' . $crlf; + } + $buffer .= '
      ' . $crlf; + + if (!Export::outputHandler($buffer)) { + return false; + } + } + $GLOBALS['dbi']->freeResult($result); + } + + return true; + } + + + /* ~~~~~~~~~~~~~~~~~~~~ Getters and Setters ~~~~~~~~~~~~~~~~~~~~ */ + + /** + * Gets the table name + * + * @return string + */ + private function _getTable() + { + return $this->_table; + } + + /** + * Sets the table name + * + * @param string $table table name + * + * @return void + */ + private function _setTable($table) + { + $this->_table = $table; + } + + /** + * Gets the table names + * + * @return array + */ + private function _getTables() + { + return $this->_tables; + } + + /** + * Sets the table names + * + * @param array $tables table names + * + * @return void + */ + private function _setTables(array $tables) + { + $this->_tables = $tables; + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Plugins/Export/ExportYaml.php b/php/apps/phpmyadmin49/libraries/classes/Plugins/Export/ExportYaml.php new file mode 100644 index 00000000..0e49fc65 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Plugins/Export/ExportYaml.php @@ -0,0 +1,218 @@ +setProperties(); + } + + /** + * Sets the export YAML properties + * + * @return void + */ + protected function setProperties() + { + $exportPluginProperties = new ExportPluginProperties(); + $exportPluginProperties->setText('YAML'); + $exportPluginProperties->setExtension('yml'); + $exportPluginProperties->setMimeType('text/yaml'); + $exportPluginProperties->setForceFile(true); + $exportPluginProperties->setOptionsText(__('Options')); + + // create the root group that will be the options field for + // $exportPluginProperties + // this will be shown as "Format specific options" + $exportSpecificOptions = new OptionsPropertyRootGroup( + "Format Specific Options" + ); + + // general options main group + $generalOptions = new OptionsPropertyMainGroup("general_opts"); + // create primary items and add them to the group + $leaf = new HiddenPropertyItem("structure_or_data"); + $generalOptions->addProperty($leaf); + // add the main group to the root group + $exportSpecificOptions->addProperty($generalOptions); + + // set the options for the export plugin property item + $exportPluginProperties->setOptions($exportSpecificOptions); + $this->properties = $exportPluginProperties; + } + + /** + * Outputs export header + * + * @return bool Whether it succeeded + */ + public function exportHeader() + { + Export::outputHandler( + '%YAML 1.1' . $GLOBALS['crlf'] . '---' . $GLOBALS['crlf'] + ); + + return true; + } + + /** + * Outputs export footer + * + * @return bool Whether it succeeded + */ + public function exportFooter() + { + Export::outputHandler('...' . $GLOBALS['crlf']); + + return true; + } + + /** + * Outputs database header + * + * @param string $db Database name + * @param string $db_alias Aliases of db + * + * @return bool Whether it succeeded + */ + public function exportDBHeader($db, $db_alias = '') + { + return true; + } + + /** + * Outputs database footer + * + * @param string $db Database name + * + * @return bool Whether it succeeded + */ + public function exportDBFooter($db) + { + return true; + } + + /** + * Outputs CREATE DATABASE statement + * + * @param string $db Database name + * @param string $export_type 'server', 'database', 'table' + * @param string $db_alias Aliases of db + * + * @return bool Whether it succeeded + */ + public function exportDBCreate($db, $export_type, $db_alias = '') + { + return true; + } + + /** + * Outputs the content of a table in JSON format + * + * @param string $db database name + * @param string $table table name + * @param string $crlf the end of line sequence + * @param string $error_url the url to go back in case of error + * @param string $sql_query SQL query for obtaining data + * @param array $aliases Aliases of db/table/columns + * + * @return bool Whether it succeeded + */ + public function exportData( + $db, + $table, + $crlf, + $error_url, + $sql_query, + array $aliases = array() + ) { + $db_alias = $db; + $table_alias = $table; + $this->initAlias($aliases, $db_alias, $table_alias); + $result = $GLOBALS['dbi']->query( + $sql_query, + DatabaseInterface::CONNECT_USER, + DatabaseInterface::QUERY_UNBUFFERED + ); + + $columns_cnt = $GLOBALS['dbi']->numFields($result); + $columns = array(); + for ($i = 0; $i < $columns_cnt; $i++) { + $col_as = $GLOBALS['dbi']->fieldName($result, $i); + if (!empty($aliases[$db]['tables'][$table]['columns'][$col_as])) { + $col_as = $aliases[$db]['tables'][$table]['columns'][$col_as]; + } + $columns[$i] = stripslashes($col_as); + } + + $buffer = ''; + $record_cnt = 0; + while ($record = $GLOBALS['dbi']->fetchRow($result)) { + $record_cnt++; + + // Output table name as comment if this is the first record of the table + if ($record_cnt == 1) { + $buffer = '# ' . $db_alias . '.' . $table_alias . $crlf; + $buffer .= '-' . $crlf; + } else { + $buffer = '-' . $crlf; + } + + for ($i = 0; $i < $columns_cnt; $i++) { + if (!isset($record[$i])) { + continue; + } + + if (is_null($record[$i])) { + $buffer .= ' ' . $columns[$i] . ': null' . $crlf; + continue; + } + + if (is_numeric($record[$i])) { + $buffer .= ' ' . $columns[$i] . ': ' . $record[$i] . $crlf; + continue; + } + + $record[$i] = str_replace( + array('\\', '"', "\n", "\r"), + array('\\\\', '\"', '\n', '\r'), + $record[$i] + ); + $buffer .= ' ' . $columns[$i] . ': "' . $record[$i] . '"' . $crlf; + } + + if (!Export::outputHandler($buffer)) { + return false; + } + } + $GLOBALS['dbi']->freeResult($result); + + return true; + } // end getTableYAML +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Plugins/Export/Helpers/Pdf.php b/php/apps/phpmyadmin49/libraries/classes/Plugins/Export/Helpers/Pdf.php new file mode 100644 index 00000000..4677327d --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Plugins/Export/Helpers/Pdf.php @@ -0,0 +1,851 @@ +relation = new Relation(); + } + + /** + * Add page if needed. + * + * @param float|int $h cell height. Default value: 0 + * @param mixed $y starting y position, leave empty for current + * position + * @param boolean $addpage if true add a page, otherwise only return + * the true/false state + * + * @return boolean true in case of page break, false otherwise. + */ + public function checkPageBreak($h = 0, $y = '', $addpage = true) + { + if (TCPDF_STATIC::empty_string($y)) { + $y = $this->y; + } + $current_page = $this->page; + if ((($y + $h) > $this->PageBreakTrigger) + && (!$this->InFooter) + && ($this->AcceptPageBreak()) + ) { + if ($addpage) { + //Automatic page break + $x = $this->x; + $this->AddPage($this->CurOrientation); + $this->y = $this->dataY; + $oldpage = $this->page - 1; + + $this_page_orm = $this->pagedim[$this->page]['orm']; + $old_page_orm = $this->pagedim[$oldpage]['orm']; + $this_page_olm = $this->pagedim[$this->page]['olm']; + $old_page_olm = $this->pagedim[$oldpage]['olm']; + if ($this->rtl) { + if ($this_page_orm != $old_page_orm) { + $this->x = $x - ($this_page_orm - $old_page_orm); + } else { + $this->x = $x; + } + } else { + if ($this_page_olm != $old_page_olm) { + $this->x = $x + ($this_page_olm - $old_page_olm); + } else { + $this->x = $x; + } + } + } + + return true; + } + + // account for columns mode + return $current_page != $this->page; + } + + /** + * This method is used to render the page header. + * + * @return void + */ + // @codingStandardsIgnoreLine + public function Header() + { + global $maxY; + // We don't want automatic page breaks while generating header + // as this can lead to infinite recursion as auto generated page + // will want header as well causing another page break + // FIXME: Better approach might be to try to compact the content + $this->SetAutoPageBreak(false); + // Check if header for this page already exists + if (!isset($this->headerset[$this->page])) { + $fullwidth = 0; + foreach ($this->tablewidths as $width) { + $fullwidth += $width; + } + $this->SetY(($this->tMargin) - ($this->FontSizePt / $this->k) * 5); + $this->cellFontSize = $this->FontSizePt; + $this->SetFont( + PdfLib::PMA_PDF_FONT, + '', + ($this->titleFontSize + ? $this->titleFontSize + : $this->FontSizePt) + ); + $this->Cell(0, $this->FontSizePt, $this->titleText, 0, 1, 'C'); + $this->SetFont(PdfLib::PMA_PDF_FONT, '', $this->cellFontSize); + $this->SetY(($this->tMargin) - ($this->FontSizePt / $this->k) * 2.5); + $this->Cell( + 0, + $this->FontSizePt, + __('Database:') . ' ' . $this->dbAlias . ', ' + . __('Table:') . ' ' . $this->tableAlias . ', ' + . __('Purpose:') . ' ' . $this->purpose, + 0, + 1, + 'L' + ); + $l = ($this->lMargin); + foreach ($this->colTitles as $col => $txt) { + $this->SetXY($l, ($this->tMargin)); + $this->MultiCell( + $this->tablewidths[$col], + $this->FontSizePt, + $txt + ); + $l += $this->tablewidths[$col]; + $maxY = ($maxY < $this->getY()) ? $this->getY() : $maxY; + } + $this->SetXY($this->lMargin, $this->tMargin); + $this->setFillColor(200, 200, 200); + $l = ($this->lMargin); + foreach ($this->colTitles as $col => $txt) { + $this->SetXY($l, $this->tMargin); + $this->cell( + $this->tablewidths[$col], + $maxY - ($this->tMargin), + '', + 1, + 0, + 'L', + 1 + ); + $this->SetXY($l, $this->tMargin); + $this->MultiCell( + $this->tablewidths[$col], + $this->FontSizePt, + $txt, + 0, + 'C' + ); + $l += $this->tablewidths[$col]; + } + $this->setFillColor(255, 255, 255); + // set headerset + $this->headerset[$this->page] = 1; + } + + $this->dataY = $maxY; + $this->SetAutoPageBreak(true); + } + + /** + * Generate table + * + * @param int $lineheight Height of line + * + * @return void + */ + public function morepagestable($lineheight = 8) + { + // some things to set and 'remember' + $l = $this->lMargin; + $startheight = $h = $this->dataY; + $startpage = $currpage = $this->page; + + // calculate the whole width + $fullwidth = 0; + foreach ($this->tablewidths as $width) { + $fullwidth += $width; + } + + // Now let's start to write the table + $row = 0; + $tmpheight = array(); + $maxpage = $this->page; + + while ($data = $GLOBALS['dbi']->fetchRow($this->results)) { + $this->page = $currpage; + // write the horizontal borders + $this->Line($l, $h, $fullwidth + $l, $h); + // write the content and remember the height of the highest col + foreach ($data as $col => $txt) { + $this->page = $currpage; + $this->SetXY($l, $h); + if ($this->tablewidths[$col] > 0) { + $this->MultiCell( + $this->tablewidths[$col], + $lineheight, + $txt, + 0, + $this->colAlign[$col] + ); + $l += $this->tablewidths[$col]; + } + + if (!isset($tmpheight[$row . '-' . $this->page])) { + $tmpheight[$row . '-' . $this->page] = 0; + } + if ($tmpheight[$row . '-' . $this->page] < $this->GetY()) { + $tmpheight[$row . '-' . $this->page] = $this->GetY(); + } + if ($this->page > $maxpage) { + $maxpage = $this->page; + } + unset($data[$col]); + } + + // get the height we were in the last used page + $h = $tmpheight[$row . '-' . $maxpage]; + // set the "pointer" to the left margin + $l = $this->lMargin; + // set the $currpage to the last page + $currpage = $maxpage; + unset($data[$row]); + $row++; + } + // draw the borders + // we start adding a horizontal line on the last page + $this->page = $maxpage; + $this->Line($l, $h, $fullwidth + $l, $h); + // now we start at the top of the document and walk down + for ($i = $startpage; $i <= $maxpage; $i++) { + $this->page = $i; + $l = $this->lMargin; + $t = ($i == $startpage) ? $startheight : $this->tMargin; + $lh = ($i == $maxpage) ? $h : $this->h - $this->bMargin; + $this->Line($l, $t, $l, $lh); + foreach ($this->tablewidths as $width) { + $l += $width; + $this->Line($l, $t, $l, $lh); + } + } + // set it to the last page, if not it'll cause some problems + $this->page = $maxpage; + } + + /** + * Sets a set of attributes. + * + * @param array $attr array containing the attributes + * + * @return void + */ + public function setAttributes(array $attr = array()) + { + foreach ($attr as $key => $val) { + $this->$key = $val; + } + } + + /** + * Defines the top margin. + * The method can be called before creating the first page. + * + * @param float $topMargin the margin + * + * @return void + */ + public function setTopMargin($topMargin) + { + $this->tMargin = $topMargin; + } + + /** + * Prints triggers + * + * @param string $db database name + * @param string $table table name + * + * @return void + */ + public function getTriggers($db, $table) + { + $i = 0; + $triggers = $GLOBALS['dbi']->getTriggers($db, $table); + foreach ($triggers as $trigger) { + $i++; + break; + } + if ($i == 0) { + return; //prevents printing blank trigger list for any table + } + + unset($this->tablewidths); + unset($this->colTitles); + unset($this->titleWidth); + unset($this->colFits); + unset($this->display_column); + unset($this->colAlign); + + /** + * Making table heading + * Keeping column width constant + */ + $this->colTitles[0] = __('Name'); + $this->tablewidths[0] = 90; + $this->colTitles[1] = __('Time'); + $this->tablewidths[1] = 80; + $this->colTitles[2] = __('Event'); + $this->tablewidths[2] = 40; + $this->colTitles[3] = __('Definition'); + $this->tablewidths[3] = 240; + + for ($columns_cnt = 0; $columns_cnt < 4; $columns_cnt++) { + $this->colAlign[$columns_cnt] = 'L'; + $this->display_column[$columns_cnt] = true; + } + + // Starting to fill table with required info + + $this->setY($this->tMargin); + $this->AddPage(); + $this->SetFont(PdfLib::PMA_PDF_FONT, '', 9); + + $l = $this->lMargin; + $startheight = $h = $this->dataY; + $startpage = $currpage = $this->page; + + // calculate the whole width + $fullwidth = 0; + foreach ($this->tablewidths as $width) { + $fullwidth += $width; + } + + $row = 0; + $tmpheight = array(); + $maxpage = $this->page; + $data = array(); + + $triggers = $GLOBALS['dbi']->getTriggers($db, $table); + + foreach ($triggers as $trigger) { + $data[] = $trigger['name']; + $data[] = $trigger['action_timing']; + $data[] = $trigger['event_manipulation']; + $data[] = $trigger['definition']; + $this->page = $currpage; + // write the horizontal borders + $this->Line($l, $h, $fullwidth + $l, $h); + // write the content and remember the height of the highest col + foreach ($data as $col => $txt) { + $this->page = $currpage; + $this->SetXY($l, $h); + if ($this->tablewidths[$col] > 0) { + $this->MultiCell( + $this->tablewidths[$col], + $this->FontSizePt, + $txt, + 0, + $this->colAlign[$col] + ); + $l += $this->tablewidths[$col]; + } + + if (!isset($tmpheight[$row . '-' . $this->page])) { + $tmpheight[$row . '-' . $this->page] = 0; + } + if ($tmpheight[$row . '-' . $this->page] < $this->GetY()) { + $tmpheight[$row . '-' . $this->page] = $this->GetY(); + } + if ($this->page > $maxpage) { + $maxpage = $this->page; + } + } + // get the height we were in the last used page + $h = $tmpheight[$row . '-' . $maxpage]; + // set the "pointer" to the left margin + $l = $this->lMargin; + // set the $currpage to the last page + $currpage = $maxpage; + unset($data); + $row++; + } + // draw the borders + // we start adding a horizontal line on the last page + $this->page = $maxpage; + $this->Line($l, $h, $fullwidth + $l, $h); + // now we start at the top of the document and walk down + for ($i = $startpage; $i <= $maxpage; $i++) { + $this->page = $i; + $l = $this->lMargin; + $t = ($i == $startpage) ? $startheight : $this->tMargin; + $lh = ($i == $maxpage) ? $h : $this->h - $this->bMargin; + $this->Line($l, $t, $l, $lh); + foreach ($this->tablewidths as $width) { + $l += $width; + $this->Line($l, $t, $l, $lh); + } + } + // set it to the last page, if not it'll cause some problems + $this->page = $maxpage; + } + + /** + * Print $table's CREATE definition + * + * @param string $db the database name + * @param string $table the table name + * @param bool $do_relation whether to include relation comments + * @param bool $do_comments whether to include the pmadb-style column + * comments as comments in the structure; + * this is deprecated but the parameter is + * left here because export.php calls + * PMA_exportStructure() also for other + * export types which use this parameter + * @param bool $do_mime whether to include mime comments + * @param bool $view whether we're handling a view + * @param array $aliases aliases of db/table/columns + * + * @return void + */ + public function getTableDef( + $db, + $table, + $do_relation, + $do_comments, + $do_mime, + $view = false, + array $aliases = array() + ) { + // set $cfgRelation here, because there is a chance that it's modified + // since the class initialization + global $cfgRelation; + + unset($this->tablewidths); + unset($this->colTitles); + unset($this->titleWidth); + unset($this->colFits); + unset($this->display_column); + unset($this->colAlign); + + /** + * Gets fields properties + */ + $GLOBALS['dbi']->selectDb($db); + + /** + * All these three checks do_relation, do_comment and do_mime is + * not required. As presently all are set true by default. + * But when, methods to take user input will be developed, + * it will be of use + */ + // Check if we can use Relations + if ($do_relation) { + // Find which tables are related with the current one and write it in + // an array + $res_rel = $this->relation->getForeigners($db, $table); + $have_rel = !empty($res_rel); + } else { + $have_rel = false; + } // end if + + //column count and table heading + + $this->colTitles[0] = __('Column'); + $this->tablewidths[0] = 90; + $this->colTitles[1] = __('Type'); + $this->tablewidths[1] = 80; + $this->colTitles[2] = __('Null'); + $this->tablewidths[2] = 40; + $this->colTitles[3] = __('Default'); + $this->tablewidths[3] = 120; + + for ($columns_cnt = 0; $columns_cnt < 4; $columns_cnt++) { + $this->colAlign[$columns_cnt] = 'L'; + $this->display_column[$columns_cnt] = true; + } + + if ($do_relation && $have_rel) { + $this->colTitles[$columns_cnt] = __('Links to'); + $this->display_column[$columns_cnt] = true; + $this->colAlign[$columns_cnt] = 'L'; + $this->tablewidths[$columns_cnt] = 120; + $columns_cnt++; + } + if ($do_comments /*&& $cfgRelation['commwork']*/) { + $this->colTitles[$columns_cnt] = __('Comments'); + $this->display_column[$columns_cnt] = true; + $this->colAlign[$columns_cnt] = 'L'; + $this->tablewidths[$columns_cnt] = 120; + $columns_cnt++; + } + if ($do_mime && $cfgRelation['mimework']) { + $this->colTitles[$columns_cnt] = __('MIME'); + $this->display_column[$columns_cnt] = true; + $this->colAlign[$columns_cnt] = 'L'; + $this->tablewidths[$columns_cnt] = 120; + $columns_cnt++; + } + + // Starting to fill table with required info + + $this->setY($this->tMargin); + $this->AddPage(); + $this->SetFont(PdfLib::PMA_PDF_FONT, '', 9); + + // Now let's start to write the table structure + + if ($do_comments) { + $comments = $this->relation->getComments($db, $table); + } + if ($do_mime && $cfgRelation['mimework']) { + $mime_map = Transformations::getMIME($db, $table, true); + } + + $columns = $GLOBALS['dbi']->getColumns($db, $table); + /** + * Get the unique keys in the table. + * Presently, this information is not used. We will have to find out + * way of displaying it. + */ + $unique_keys = array(); + $keys = $GLOBALS['dbi']->getTableIndexes($db, $table); + foreach ($keys as $key) { + if ($key['Non_unique'] == 0) { + $unique_keys[] = $key['Column_name']; + } + } + + // some things to set and 'remember' + $l = $this->lMargin; + $startheight = $h = $this->dataY; + $startpage = $currpage = $this->page; + // calculate the whole width + $fullwidth = 0; + foreach ($this->tablewidths as $width) { + $fullwidth += $width; + } + + $row = 0; + $tmpheight = array(); + $maxpage = $this->page; + $data = array(); + + // fun begin + foreach ($columns as $column) { + $extracted_columnspec + = Util::extractColumnSpec($column['Type']); + + $type = $extracted_columnspec['print_type']; + if (empty($type)) { + $type = ' '; + } + + if (!isset($column['Default'])) { + if ($column['Null'] != 'NO') { + $column['Default'] = 'NULL'; + } + } + $data [] = $column['Field']; + $data [] = $type; + $data [] = ($column['Null'] == '' || $column['Null'] == 'NO') + ? 'No' + : 'Yes'; + $data [] = isset($column['Default']) ? $column['Default'] : ''; + + $field_name = $column['Field']; + + if ($do_relation && $have_rel) { + $data [] = isset($res_rel[$field_name]) + ? $res_rel[$field_name]['foreign_table'] + . ' (' . $res_rel[$field_name]['foreign_field'] + . ')' + : ''; + } + if ($do_comments) { + $data [] = isset($comments[$field_name]) + ? $comments[$field_name] + : ''; + } + if ($do_mime) { + $data [] = isset($mime_map[$field_name]) + ? $mime_map[$field_name]['mimetype'] + : ''; + } + + $this->page = $currpage; + // write the horizontal borders + $this->Line($l, $h, $fullwidth + $l, $h); + // write the content and remember the height of the highest col + foreach ($data as $col => $txt) { + $this->page = $currpage; + $this->SetXY($l, $h); + if ($this->tablewidths[$col] > 0) { + $this->MultiCell( + $this->tablewidths[$col], + $this->FontSizePt, + $txt, + 0, + $this->colAlign[$col] + ); + $l += $this->tablewidths[$col]; + } + + if (!isset($tmpheight[$row . '-' . $this->page])) { + $tmpheight[$row . '-' . $this->page] = 0; + } + if ($tmpheight[$row . '-' . $this->page] < $this->GetY()) { + $tmpheight[$row . '-' . $this->page] = $this->GetY(); + } + if ($this->page > $maxpage) { + $maxpage = $this->page; + } + } + + // get the height we were in the last used page + $h = $tmpheight[$row . '-' . $maxpage]; + // set the "pointer" to the left margin + $l = $this->lMargin; + // set the $currpage to the last page + $currpage = $maxpage; + unset($data); + $row++; + } + // draw the borders + // we start adding a horizontal line on the last page + $this->page = $maxpage; + $this->Line($l, $h, $fullwidth + $l, $h); + // now we start at the top of the document and walk down + for ($i = $startpage; $i <= $maxpage; $i++) { + $this->page = $i; + $l = $this->lMargin; + $t = ($i == $startpage) ? $startheight : $this->tMargin; + $lh = ($i == $maxpage) ? $h : $this->h - $this->bMargin; + $this->Line($l, $t, $l, $lh); + foreach ($this->tablewidths as $width) { + $l += $width; + $this->Line($l, $t, $l, $lh); + } + } + // set it to the last page, if not it'll cause some problems + $this->page = $maxpage; + } + + /** + * MySQL report + * + * @param string $query Query to execute + * + * @return void + */ + public function mysqlReport($query) + { + unset($this->tablewidths); + unset($this->colTitles); + unset($this->titleWidth); + unset($this->colFits); + unset($this->display_column); + unset($this->colAlign); + + /** + * Pass 1 for column widths + */ + $this->results = $GLOBALS['dbi']->query( + $query, + DatabaseInterface::CONNECT_USER, + DatabaseInterface::QUERY_UNBUFFERED + ); + $this->numFields = $GLOBALS['dbi']->numFields($this->results); + $this->fields = $GLOBALS['dbi']->getFieldsMeta($this->results); + + // sColWidth = starting col width (an average size width) + $availableWidth = $this->w - $this->lMargin - $this->rMargin; + $this->sColWidth = $availableWidth / $this->numFields; + $totalTitleWidth = 0; + + // loop through results header and set initial + // col widths/ titles/ alignment + // if a col title is less than the starting col width, + // reduce that column size + $colFits = array(); + $titleWidth = array(); + for ($i = 0; $i < $this->numFields; $i++) { + $col_as = $this->fields[$i]->name; + $db = $this->currentDb; + $table = $this->currentTable; + if (!empty($this->aliases[$db]['tables'][$table]['columns'][$col_as])) { + $col_as = $this->aliases[$db]['tables'][$table]['columns'][$col_as]; + } + $stringWidth = $this->getstringwidth($col_as) + 6; + // save the real title's width + $titleWidth[$i] = $stringWidth; + $totalTitleWidth += $stringWidth; + + // set any column titles less than the start width to + // the column title width + if ($stringWidth < $this->sColWidth) { + $colFits[$i] = $stringWidth; + } + $this->colTitles[$i] = $col_as; + $this->display_column[$i] = true; + + switch ($this->fields[$i]->type) { + case 'int': + $this->colAlign[$i] = 'R'; + break; + case 'blob': + case 'tinyblob': + case 'mediumblob': + case 'longblob': + /** + * @todo do not deactivate completely the display + * but show the field's name and [BLOB] + */ + if (stristr($this->fields[$i]->flags, 'BINARY')) { + $this->display_column[$i] = false; + unset($this->colTitles[$i]); + } + $this->colAlign[$i] = 'L'; + break; + default: + $this->colAlign[$i] = 'L'; + } + } + + // title width verification + if ($totalTitleWidth > $availableWidth) { + $adjustingMode = true; + } else { + $adjustingMode = false; + // we have enough space for all the titles at their + // original width so use the true title's width + foreach ($titleWidth as $key => $val) { + $colFits[$key] = $val; + } + } + + // loop through the data; any column whose contents + // is greater than the column size is resized + /** + * @todo force here a LIMIT to avoid reading all rows + */ + while ($row = $GLOBALS['dbi']->fetchRow($this->results)) { + foreach ($colFits as $key => $val) { + $stringWidth = $this->getstringwidth($row[$key]) + 6; + if ($adjustingMode && ($stringWidth > $this->sColWidth)) { + // any column whose data's width is bigger than + // the start width is now discarded + unset($colFits[$key]); + } else { + // if data's width is bigger than the current column width, + // enlarge the column (but avoid enlarging it if the + // data's width is very big) + if ($stringWidth > $val + && $stringWidth < ($this->sColWidth * 3) + ) { + $colFits[$key] = $stringWidth; + } + } + } + } + + $totAlreadyFitted = 0; + foreach ($colFits as $key => $val) { + // set fitted columns to smallest size + $this->tablewidths[$key] = $val; + // to work out how much (if any) space has been freed up + $totAlreadyFitted += $val; + } + + if ($adjustingMode) { + $surplus = (sizeof($colFits) * $this->sColWidth) - $totAlreadyFitted; + $surplusToAdd = $surplus / ($this->numFields - sizeof($colFits)); + } else { + $surplusToAdd = 0; + } + + for ($i = 0; $i < $this->numFields; $i++) { + if (!in_array($i, array_keys($colFits))) { + $this->tablewidths[$i] = $this->sColWidth + $surplusToAdd; + } + if ($this->display_column[$i] == false) { + $this->tablewidths[$i] = 0; + } + } + + ksort($this->tablewidths); + + $GLOBALS['dbi']->freeResult($this->results); + + // Pass 2 + + $this->results = $GLOBALS['dbi']->query( + $query, + DatabaseInterface::CONNECT_USER, + DatabaseInterface::QUERY_UNBUFFERED + ); + $this->setY($this->tMargin); + $this->AddPage(); + $this->SetFont(PdfLib::PMA_PDF_FONT, '', 9); + $this->morepagestable($this->FontSizePt); + $GLOBALS['dbi']->freeResult($this->results); + } // end of mysqlReport function +} // end of Pdf class diff --git a/php/apps/phpmyadmin49/libraries/classes/Plugins/Export/Helpers/TableProperty.php b/php/apps/phpmyadmin49/libraries/classes/Plugins/Export/Helpers/TableProperty.php new file mode 100644 index 00000000..de8bff20 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Plugins/Export/Helpers/TableProperty.php @@ -0,0 +1,285 @@ +name = trim($row[0]); + $this->type = trim($row[1]); + $this->nullable = trim($row[2]); + $this->key = trim($row[3]); + $this->defaultValue = trim($row[4]); + $this->ext = trim($row[5]); + } + + /** + * Gets the pure type + * + * @return string type + */ + public function getPureType() + { + $pos = mb_strpos($this->type, "("); + if ($pos > 0) { + return mb_substr($this->type, 0, $pos); + } + return $this->type; + } + + /** + * Tells whether the key is null or not + * + * @return bool true if the key is not null, false otherwise + */ + public function isNotNull() + { + return $this->nullable == "NO" ? "true" : "false"; + } + + /** + * Tells whether the key is unique or not + * + * @return bool true if the key is unique, false otherwise + */ + public function isUnique() + { + return $this->key == "PRI" || $this->key == "UNI" ? "true" : "false"; + } + + /** + * Gets the .NET primitive type + * + * @return string type + */ + public function getDotNetPrimitiveType() + { + if (mb_strpos($this->type, "int") === 0) { + return "int"; + } + if (mb_strpos($this->type, "longtext") === 0) { + return "string"; + } + if (mb_strpos($this->type, "long") === 0) { + return "long"; + } + if (mb_strpos($this->type, "char") === 0) { + return "string"; + } + if (mb_strpos($this->type, "varchar") === 0) { + return "string"; + } + if (mb_strpos($this->type, "text") === 0) { + return "string"; + } + if (mb_strpos($this->type, "tinyint") === 0) { + return "bool"; + } + if (mb_strpos($this->type, "datetime") === 0) { + return "DateTime"; + } + return "unknown"; + } + + /** + * Gets the .NET object type + * + * @return string type + */ + public function getDotNetObjectType() + { + if (mb_strpos($this->type, "int") === 0) { + return "Int32"; + } + if (mb_strpos($this->type, "longtext") === 0) { + return "String"; + } + if (mb_strpos($this->type, "long") === 0) { + return "Long"; + } + if (mb_strpos($this->type, "char") === 0) { + return "String"; + } + if (mb_strpos($this->type, "varchar") === 0) { + return "String"; + } + if (mb_strpos($this->type, "text") === 0) { + return "String"; + } + if (mb_strpos($this->type, "tinyint") === 0) { + return "Boolean"; + } + if (mb_strpos($this->type, "datetime") === 0) { + return "DateTime"; + } + return "Unknown"; + } + + /** + * Gets the index name + * + * @return string containing the name of the index + */ + public function getIndexName() + { + if (strlen($this->key) > 0) { + return "index=\"" + . htmlspecialchars($this->name, ENT_COMPAT, 'UTF-8') + . "\""; + } + return ""; + } + + /** + * Tells whether the key is primary or not + * + * @return bool true if the key is primary, false otherwise + */ + public function isPK() + { + return $this->key == "PRI"; + } + + /** + * Formats a string for C# + * + * @param string $text string to be formatted + * + * @return string formatted text + */ + public function formatCs($text) + { + $text = str_replace( + "#name#", + ExportCodegen::cgMakeIdentifier($this->name, false), + $text + ); + return $this->format($text); + } + + /** + * Formats a string for XML + * + * @param string $text string to be formatted + * + * @return string formatted text + */ + public function formatXml($text) + { + $text = str_replace( + "#name#", + htmlspecialchars($this->name, ENT_COMPAT, 'UTF-8'), + $text + ); + $text = str_replace( + "#indexName#", + $this->getIndexName(), + $text + ); + return $this->format($text); + } + + /** + * Formats a string + * + * @param string $text string to be formatted + * + * @return string formatted text + */ + public function format($text) + { + $text = str_replace( + "#ucfirstName#", + ExportCodegen::cgMakeIdentifier($this->name), + $text + ); + $text = str_replace( + "#dotNetPrimitiveType#", + $this->getDotNetPrimitiveType(), + $text + ); + $text = str_replace( + "#dotNetObjectType#", + $this->getDotNetObjectType(), + $text + ); + $text = str_replace( + "#type#", + $this->getPureType(), + $text + ); + $text = str_replace( + "#notNull#", + $this->isNotNull(), + $text + ); + $text = str_replace( + "#unique#", + $this->isUnique(), + $text + ); + return $text; + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Plugins/Export/README b/php/apps/phpmyadmin49/libraries/classes/Plugins/Export/README new file mode 100644 index 00000000..a05dc8a4 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Plugins/Export/README @@ -0,0 +1,257 @@ +This directory holds export plugins for phpMyAdmin. Any new plugin should +basically follow the structure presented here. Official plugins need to +have str* messages with their definition in language files, but if you build +some plugins for your use, you can directly use texts in plugin. + +setProperties(); + } + + // optional - declare global variables and use getters later + /** + * Initialize the local variables that are used specific for export SQL + * + * @global type $global_variable_name + * [..] + * + * @return void + */ + protected function initSpecificVariables() + { + global $global_variable_name; + $this->_setGlobalVariableName($global_variable_name); + } + + /** + * Sets the export plugin properties. + * Called in the constructor. + * + * @return void + */ + protected function setProperties() + { + $exportPluginProperties = new PhpMyAdmin\Properties\Plugins\ExportPluginProperties(); + $exportPluginProperties->setText('[name]'); // the name of your plug-in + $exportPluginProperties->setExtension('[ext]'); // extension this plug-in can handle + $exportPluginProperties->setOptionsText(__('Options')); + + // create the root group that will be the options field for + // $exportPluginProperties + // this will be shown as "Format specific options" + $exportSpecificOptions = new PhpMyAdmin\Properties\Options\Groups\OptionsPropertyRootGroup( + "Format Specific Options" + ); + + // general options main group + $generalOptions = new PhpMyAdmin\Properties\Options\Groups\OptionsPropertyMainGroup( + "general_opts" + ); + + // optional : + // create primary items and add them to the group + // type - one of the classes listed in libraries/properties/options/items/ + // name - form element name + // text - description in GUI + // size - size of text element + // len - maximal size of input + // values - possible values of the item + $leaf = new PhpMyAdmin\Properties\Options\Items\RadioPropertyItem( + "structure_or_data" + ); + $leaf->setValues( + array( + 'structure' => __('structure'), + 'data' => __('data'), + 'structure_and_data' => __('structure and data') + ) + ); + $generalOptions->addProperty($leaf); + + // add the main group to the root group + $exportSpecificOptions->addProperty($generalOptions); + + // set the options for the export plugin property item + $exportPluginProperties->setOptions($exportSpecificOptions); + $this->properties = $exportPluginProperties; + } + + /** + * Outputs export header + * + * @return bool Whether it succeeded + */ + public function exportHeader () + { + // implementation + return true; + } + + /** + * Outputs export footer + * + * @return bool Whether it succeeded + */ + public function exportFooter () + { + // implementation + return true; + } + + /** + * Outputs database header + * + * @param string $db Database name + * @param string $db_alias Aliases of db + * + * @return bool Whether it succeeded + */ + public function exportDBHeader ($db, $db_alias = '') + { + // implementation + return true; + } + + /** + * Outputs database footer + * + * @param string $db Database name + * + * @return bool Whether it succeeded + */ + public function exportDBFooter ($db) + { + // implementation + return true; + } + + /** + * Outputs CREATE DATABASE statement + * + * @param string $db Database name + * @param string $db_alias Aliases of db + * + * @return bool Whether it succeeded + */ + public function exportDBCreate($db, $db_alias = '') + { + // implementation + return true; + } + + /** + * Outputs the content of a table in [Name] format + * + * @param string $db database name + * @param string $table table name + * @param string $crlf the end of line sequence + * @param string $error_url the url to go back in case of error + * @param string $sql_query SQL query for obtaining data + * @param array $aliases Aliases of db/table/columns + * + * @return bool Whether it succeeded + */ + public function exportData( + $db, $table, $crlf, $error_url, $sql_query, $aliases = array() + ) { + // implementation; + return true; + } + + // optional - implement other methods defined in PhpMyAdmin\Plugins\ExportPlugin.class.php: + // - exportRoutines() + // - exportStructure() + // - getTableDefStandIn() + // - getTriggers() + + // optional - implement other private methods in order to avoid + // having huge methods or avoid duplicate code. Make use of them + // as well as of the getters and setters declared both here + // and in the PhpMyAdmin\Plugins\ExportPlugin class + + + // optional: + /* ~~~~~~~~~~~~~~~~~~~~ Getters and Setters ~~~~~~~~~~~~~~~~~~~~ */ + + + /** + * Getter description + * + * @return type + */ + private function _getMyOptionalVariable() + { + return $this->_myOptionalVariable; + } + + /** + * Setter description + * + * @param type $my_optional_variable description + * + * @return void + */ + private function _setMyOptionalVariable($my_optional_variable) + { + $this->_myOptionalVariable = $my_optional_variable; + } + + /** + * Getter description + * + * @return type + */ + private function _getGlobalVariableName() + { + return $this->_globalVariableName; + } + + /** + * Setter description + * + * @param type $global_variable_name description + * + * @return void + */ + private function _setGlobalVariableName($global_variable_name) + { + $this->_globalVariableName = $global_variable_name; + } +} +?> diff --git a/php/apps/phpmyadmin49/libraries/classes/Plugins/ExportPlugin.php b/php/apps/phpmyadmin49/libraries/classes/Plugins/ExportPlugin.php new file mode 100644 index 00000000..56710ad7 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Plugins/ExportPlugin.php @@ -0,0 +1,378 @@ +relation = new Relation(); + } + + /** + * Outputs export header + * + * @return bool Whether it succeeded + */ + abstract public function exportHeader(); + + /** + * Outputs export footer + * + * @return bool Whether it succeeded + */ + abstract public function exportFooter(); + + /** + * Outputs database header + * + * @param string $db Database name + * @param string $db_alias Aliases of db + * + * @return bool Whether it succeeded + */ + abstract public function exportDBHeader($db, $db_alias = ''); + + /** + * Outputs database footer + * + * @param string $db Database name + * + * @return bool Whether it succeeded + */ + abstract public function exportDBFooter($db); + + /** + * Outputs CREATE DATABASE statement + * + * @param string $db Database name + * @param string $export_type 'server', 'database', 'table' + * @param string $db_alias Aliases of db + * + * @return bool Whether it succeeded + */ + abstract public function exportDBCreate($db, $export_type, $db_alias = ''); + + /** + * Outputs the content of a table + * + * @param string $db database name + * @param string $table table name + * @param string $crlf the end of line sequence + * @param string $error_url the url to go back in case of error + * @param string $sql_query SQL query for obtaining data + * @param array $aliases Aliases of db/table/columns + * + * @return bool Whether it succeeded + */ + abstract public function exportData( + $db, + $table, + $crlf, + $error_url, + $sql_query, + array $aliases = array() + ); + + /** + * The following methods are used in export.php or in db_operations.php, + * but they are not implemented by all export plugins + */ + + /** + * Exports routines (procedures and functions) + * + * @param string $db Database + * @param array $aliases Aliases of db/table/columns + * + * @return bool Whether it succeeded + */ + public function exportRoutines($db, array $aliases = array()) + { + ; + } + + /** + * Exports events + * + * @param string $db Database + * + * @return bool Whether it succeeded + */ + public function exportEvents($db) + { + ; + } + + /** + * Outputs table's structure + * + * @param string $db database name + * @param string $table table name + * @param string $crlf the end of line sequence + * @param string $error_url the url to go back in case of error + * @param string $export_mode 'create_table','triggers','create_view', + * 'stand_in' + * @param string $export_type 'server', 'database', 'table' + * @param bool $relation whether to include relation comments + * @param bool $comments whether to include the pmadb-style column comments + * as comments in the structure; this is deprecated + * but the parameter is left here because export.php + * calls exportStructure() also for other export + * types which use this parameter + * @param bool $mime whether to include mime comments + * @param bool $dates whether to include creation/update/check dates + * @param array $aliases Aliases of db/table/columns + * + * @return bool Whether it succeeded + */ + public function exportStructure( + $db, + $table, + $crlf, + $error_url, + $export_mode, + $export_type, + $relation = false, + $comments = false, + $mime = false, + $dates = false, + array $aliases = array() + ) { + ; + } + + /** + * Exports metadata from Configuration Storage + * + * @param string $db database being exported + * @param string|array $tables table(s) being exported + * @param array $metadataTypes types of metadata to export + * + * @return bool Whether it succeeded + */ + public function exportMetadata( + $db, + $tables, + array $metadataTypes + ) { + ; + } + + /** + * Returns a stand-in CREATE definition to resolve view dependencies + * + * @param string $db the database name + * @param string $view the view name + * @param string $crlf the end of line sequence + * @param array $aliases Aliases of db/table/columns + * + * @return string resulting definition + */ + public function getTableDefStandIn($db, $view, $crlf, $aliases = array()) + { + ; + } + + /** + * Outputs triggers + * + * @param string $db database name + * @param string $table table name + * + * @return string Formatted triggers list + */ + protected function getTriggers($db, $table) + { + ; + } + + /** + * Initialize the specific variables for each export plugin + * + * @return void + */ + protected function initSpecificVariables() + { + ; + } + + /* ~~~~~~~~~~~~~~~~~~~~ Getters and Setters ~~~~~~~~~~~~~~~~~~~~ */ + + /** + * Gets the export specific format plugin properties + * + * @return ExportPluginProperties + */ + public function getProperties() + { + return $this->properties; + } + + /** + * Sets the export plugins properties and is implemented by each export + * plugin + * + * @return void + */ + abstract protected function setProperties(); + + /** + * The following methods are implemented here so that they + * can be used by all export plugin without overriding it. + * Note: If you are creating a export plugin then don't include + * below methods unless you want to override them. + */ + + /** + * Initialize aliases + * + * @param array $aliases Alias information for db/table/column + * @param string &$db the database + * @param string &$table the table + * + * @return void + */ + public function initAlias($aliases, &$db, &$table = null) + { + if (!empty($aliases[$db]['tables'][$table]['alias'])) { + $table = $aliases[$db]['tables'][$table]['alias']; + } + if (!empty($aliases[$db]['alias'])) { + $db = $aliases[$db]['alias']; + } + } + + /** + * Search for alias of a identifier. + * + * @param array $aliases Alias information for db/table/column + * @param string $id the identifier to be searched + * @param string $type db/tbl/col or any combination of them + * representing what to be searched + * @param string $db the database in which search is to be done + * @param string $tbl the table in which search is to be done + * + * @return string alias of the identifier if found or '' + */ + public function getAlias(array $aliases, $id, $type = 'dbtblcol', $db = '', $tbl = '') + { + if (!empty($db) && isset($aliases[$db])) { + $aliases = array( + $db => $aliases[$db], + ); + } + // search each database + foreach ($aliases as $db_key => $db) { + // check if id is database and has alias + if (stristr($type, 'db') !== false + && $db_key === $id + && !empty($db['alias']) + ) { + return $db['alias']; + } + if (empty($db['tables'])) { + continue; + } + if (!empty($tbl) && isset($db['tables'][$tbl])) { + $db['tables'] = array( + $tbl => $db['tables'][$tbl], + ); + } + // search each of its tables + foreach ($db['tables'] as $table_key => $table) { + // check if id is table and has alias + if (stristr($type, 'tbl') !== false + && $table_key === $id + && !empty($table['alias']) + ) { + return $table['alias']; + } + if (empty($table['columns'])) { + continue; + } + // search each of its columns + foreach ($table['columns'] as $col_key => $col) { + // check if id is column + if (stristr($type, 'col') !== false + && $col_key === $id + && !empty($col) + ) { + return $col; + } + } + } + } + + return ''; + } + + /** + * Gives the relation string and + * also substitutes with alias if required + * in this format: + * [Foreign Table] ([Foreign Field]) + * + * @param array $res_rel the foreigners array + * @param string $field_name the field name + * @param string $db the field name + * @param array $aliases Alias information for db/table/column + * + * @return string the Relation string + */ + public function getRelationString( + array $res_rel, + $field_name, + $db, + array $aliases = array() + ) { + $relation = ''; + $foreigner = $this->relation->searchColumnInForeigners($res_rel, $field_name); + if ($foreigner) { + $ftable = $foreigner['foreign_table']; + $ffield = $foreigner['foreign_field']; + if (!empty($aliases[$db]['tables'][$ftable]['columns'][$ffield])) { + $ffield = $aliases[$db]['tables'][$ftable]['columns'][$ffield]; + } + if (!empty($aliases[$db]['tables'][$ftable]['alias'])) { + $ftable = $aliases[$db]['tables'][$ftable]['alias']; + } + $relation = $ftable . ' (' . $ffield . ')'; + } + + return $relation; + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Plugins/IOTransformationsPlugin.php b/php/apps/phpmyadmin49/libraries/classes/Plugins/IOTransformationsPlugin.php new file mode 100644 index 00000000..084923dd --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Plugins/IOTransformationsPlugin.php @@ -0,0 +1,96 @@ +error; + } + + /** + * Returns the success status + * + * @return bool + */ + public function isSuccess() + { + return $this->success; + } + + /** + * Resets the object properties + * + * @return void + */ + public function reset() + { + $this->success = true; + $this->error = ''; + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Plugins/Import/AbstractImportCsv.php b/php/apps/phpmyadmin49/libraries/classes/Plugins/Import/AbstractImportCsv.php new file mode 100644 index 00000000..25cdcbc9 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Plugins/Import/AbstractImportCsv.php @@ -0,0 +1,92 @@ +setOptionsText(__('Options')); + + // create the root group that will be the options field for + // $importPluginProperties + // this will be shown as "Format specific options" + $importSpecificOptions = new OptionsPropertyRootGroup( + "Format Specific Options" + ); + + // general options main group + $generalOptions = new OptionsPropertyMainGroup("general_opts"); + + // create common items and add them to the group + $leaf = new BoolPropertyItem( + "replace", + __( + 'Update data when duplicate keys found on import (add ON DUPLICATE ' + . 'KEY UPDATE)' + ) + ); + $generalOptions->addProperty($leaf); + $leaf = new TextPropertyItem( + "terminated", + __('Columns separated with:') + ); + $leaf->setSize(2); + $generalOptions->addProperty($leaf); + $leaf = new TextPropertyItem( + "enclosed", + __('Columns enclosed with:') + ); + $leaf->setSize(2); + $leaf->setLen(2); + $generalOptions->addProperty($leaf); + $leaf = new TextPropertyItem( + "escaped", + __('Columns escaped with:') + ); + $leaf->setSize(2); + $leaf->setLen(2); + $generalOptions->addProperty($leaf); + $leaf = new TextPropertyItem( + "new_line", + __('Lines terminated with:') + ); + $leaf->setSize(2); + $generalOptions->addProperty($leaf); + + // add the main group to the root group + $importSpecificOptions->addProperty($generalOptions); + + // set the options for the import plugin property item + $importPluginProperties->setOptions($importSpecificOptions); + $this->properties = $importPluginProperties; + + return $generalOptions; + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Plugins/Import/ImportCsv.php b/php/apps/phpmyadmin49/libraries/classes/Plugins/Import/ImportCsv.php new file mode 100644 index 00000000..b68fcea0 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Plugins/Import/ImportCsv.php @@ -0,0 +1,718 @@ +setProperties(); + } + + /** + * Sets the import plugin properties. + * Called in the constructor. + * + * @return void + */ + protected function setProperties() + { + $this->_setAnalyze(false); + + if ($GLOBALS['plugin_param'] !== 'table') { + $this->_setAnalyze(true); + } + + $generalOptions = parent::setProperties(); + $this->properties->setText('CSV'); + $this->properties->setExtension('csv'); + + if ($GLOBALS['plugin_param'] !== 'table') { + $leaf = new BoolPropertyItem( + "col_names", + __( + 'The first line of the file contains the table column names' + . ' (if this is unchecked, the first line will become part' + . ' of the data)' + ) + ); + $generalOptions->addProperty($leaf); + } else { + $hint = new Message( + __( + 'If the data in each row of the file is not' + . ' in the same order as in the database, list the corresponding' + . ' column names here. Column names must be separated by commas' + . ' and not enclosed in quotations.' + ) + ); + $leaf = new TextPropertyItem( + "columns", + __('Column names: ') . Util::showHint($hint) + ); + $generalOptions->addProperty($leaf); + } + + $leaf = new BoolPropertyItem( + "ignore", + __('Do not abort on INSERT error') + ); + $generalOptions->addProperty($leaf); + } + + /** + * Handles the whole import logic + * + * @param array &$sql_data 2-element array with sql data + * + * @return void + */ + public function doImport(array &$sql_data = array()) + { + global $db, $table, $csv_terminated, $csv_enclosed, $csv_escaped, + $csv_new_line, $csv_columns, $err_url; + // $csv_replace and $csv_ignore should have been here, + // but we use directly from $_POST + global $error, $timeout_passed, $finished, $message; + + $replacements = array( + '\\n' => "\n", + '\\t' => "\t", + '\\r' => "\r", + ); + $csv_terminated = strtr($csv_terminated, $replacements); + $csv_enclosed = strtr($csv_enclosed, $replacements); + $csv_escaped = strtr($csv_escaped, $replacements); + $csv_new_line = strtr($csv_new_line, $replacements); + + $param_error = false; + if (strlen($csv_terminated) === 0) { + $message = Message::error( + __('Invalid parameter for CSV import: %s') + ); + $message->addParam(__('Columns terminated with')); + $error = true; + $param_error = true; + // The default dialog of MS Excel when generating a CSV produces a + // semi-colon-separated file with no chance of specifying the + // enclosing character. Thus, users who want to import this file + // tend to remove the enclosing character on the Import dialog. + // I could not find a test case where having no enclosing characters + // confuses this script. + // But the parser won't work correctly with strings so we allow just + // one character. + } elseif (mb_strlen($csv_enclosed) > 1) { + $message = Message::error( + __('Invalid parameter for CSV import: %s') + ); + $message->addParam(__('Columns enclosed with')); + $error = true; + $param_error = true; + // I could not find a test case where having no escaping characters + // confuses this script. + // But the parser won't work correctly with strings so we allow just + // one character. + } elseif (mb_strlen($csv_escaped) > 1) { + $message = Message::error( + __('Invalid parameter for CSV import: %s') + ); + $message->addParam(__('Columns escaped with')); + $error = true; + $param_error = true; + } elseif (mb_strlen($csv_new_line) != 1 + && $csv_new_line != 'auto' + ) { + $message = Message::error( + __('Invalid parameter for CSV import: %s') + ); + $message->addParam(__('Lines terminated with')); + $error = true; + $param_error = true; + } + + // If there is an error in the parameters entered, + // indicate that immediately. + if ($param_error) { + Util::mysqlDie( + $message->getMessage(), + '', + false, + $err_url + ); + } + + $buffer = ''; + $required_fields = 0; + + if (!$this->_getAnalyze()) { + $sql_template = 'INSERT'; + if (isset($_POST['csv_ignore'])) { + $sql_template .= ' IGNORE'; + } + $sql_template .= ' INTO ' . Util::backquote($table); + + $tmp_fields = $GLOBALS['dbi']->getColumns($db, $table); + + if (empty($csv_columns)) { + $fields = $tmp_fields; + } else { + $sql_template .= ' ('; + $fields = array(); + $tmp = preg_split('/,( ?)/', $csv_columns); + foreach ($tmp as $key => $val) { + if (count($fields) > 0) { + $sql_template .= ', '; + } + /* Trim also `, if user already included backquoted fields */ + $val = trim($val, " \t\r\n\0\x0B`"); + $found = false; + foreach ($tmp_fields as $field) { + if ($field['Field'] == $val) { + $found = true; + break; + } + } + if (!$found) { + $message = Message::error( + __( + 'Invalid column (%s) specified! Ensure that columns' + . ' names are spelled correctly, separated by commas' + . ', and not enclosed in quotes.' + ) + ); + $message->addParam($val); + $error = true; + break; + } + $fields[] = $field; + $sql_template .= Util::backquote($val); + } + $sql_template .= ') '; + } + + $required_fields = count($fields); + + $sql_template .= ' VALUES ('; + } + + // Defaults for parser + $i = 0; + $len = 0; + $lastlen = null; + $line = 1; + $lasti = -1; + $values = array(); + $csv_finish = false; + + $tempRow = array(); + $rows = array(); + $col_names = array(); + $tables = array(); + + $col_count = 0; + $max_cols = 0; + $csv_terminated_len = mb_strlen($csv_terminated); + while (!($finished && $i >= $len) && !$error && !$timeout_passed) { + $data = Import::getNextChunk(); + if ($data === false) { + // subtract data we didn't handle yet and stop processing + $GLOBALS['offset'] -= strlen($buffer); + break; + } elseif ($data === true) { + // Handle rest of buffer + } else { + // Append new data to buffer + $buffer .= $data; + unset($data); + + // Force a trailing new line at EOF to prevent parsing problems + if ($finished && $buffer) { + $finalch = mb_substr($buffer, -1); + if ($csv_new_line == 'auto' + && $finalch != "\r" + && $finalch != "\n" + ) { + $buffer .= "\n"; + } elseif ($csv_new_line != 'auto' + && $finalch != $csv_new_line + ) { + $buffer .= $csv_new_line; + } + } + + // Do not parse string when we're not at the end + // and don't have new line inside + if (($csv_new_line == 'auto' + && mb_strpos($buffer, "\r") === false + && mb_strpos($buffer, "\n") === false) + || ($csv_new_line != 'auto' + && mb_strpos($buffer, $csv_new_line) === false) + ) { + continue; + } + } + + // Current length of our buffer + $len = mb_strlen($buffer); + // Currently parsed char + + $ch = mb_substr($buffer, $i, 1); + if ($csv_terminated_len > 1 && $ch == $csv_terminated[0]) { + $ch = $this->readCsvTerminatedString( + $buffer, + $ch, + $i, + $csv_terminated_len + ); + $i += $csv_terminated_len - 1; + } + while ($i < $len) { + // Deadlock protection + if ($lasti == $i && $lastlen == $len) { + $message = Message::error( + __('Invalid format of CSV input on line %d.') + ); + $message->addParam($line); + $error = true; + break; + } + $lasti = $i; + $lastlen = $len; + + // This can happen with auto EOL and \r at the end of buffer + if (!$csv_finish) { + // Grab empty field + if ($ch == $csv_terminated) { + if ($i == $len - 1) { + break; + } + $values[] = ''; + $i++; + $ch = mb_substr($buffer, $i, 1); + if ($csv_terminated_len > 1 && $ch == $csv_terminated[0]) { + $ch = $this->readCsvTerminatedString( + $buffer, + $ch, + $i, + $csv_terminated_len + ); + $i += $csv_terminated_len - 1; + } + continue; + } + + // Grab one field + $fallbacki = $i; + if ($ch == $csv_enclosed) { + if ($i == $len - 1) { + break; + } + $need_end = true; + $i++; + $ch = mb_substr($buffer, $i, 1); + if ($csv_terminated_len > 1 && $ch == $csv_terminated[0]) { + $ch = $this->readCsvTerminatedString( + $buffer, + $ch, + $i, + $csv_terminated_len + ); + $i += $csv_terminated_len - 1; + } + } else { + $need_end = false; + } + $fail = false; + $value = ''; + while (($need_end + && ($ch != $csv_enclosed + || $csv_enclosed == $csv_escaped)) + || (!$need_end + && !($ch == $csv_terminated + || $ch == $csv_new_line + || ($csv_new_line == 'auto' + && ($ch == "\r" || $ch == "\n")))) + ) { + if ($ch == $csv_escaped) { + if ($i == $len - 1) { + $fail = true; + break; + } + $i++; + $ch = mb_substr($buffer, $i, 1); + if ($csv_terminated_len > 1 + && $ch == $csv_terminated[0] + ) { + $ch = $this->readCsvTerminatedString( + $buffer, + $ch, + $i, + $csv_terminated_len + ); + $i += $csv_terminated_len - 1; + } + if ($csv_enclosed == $csv_escaped + && ($ch == $csv_terminated + || $ch == $csv_new_line + || ($csv_new_line == 'auto' + && ($ch == "\r" || $ch == "\n"))) + ) { + break; + } + } + $value .= $ch; + if ($i == $len - 1) { + if (!$finished) { + $fail = true; + } + break; + } + $i++; + $ch = mb_substr($buffer, $i, 1); + if ($csv_terminated_len > 1 && $ch == $csv_terminated[0]) { + $ch = $this->readCsvTerminatedString( + $buffer, + $ch, + $i, + $csv_terminated_len + ); + $i += $csv_terminated_len - 1; + } + } + + // unquoted NULL string + if (false === $need_end && $value === 'NULL') { + $value = null; + } + + if ($fail) { + $i = $fallbacki; + $ch = mb_substr($buffer, $i, 1); + if ($csv_terminated_len > 1 && $ch == $csv_terminated[0]) { + $i += $csv_terminated_len - 1; + } + break; + } + // Need to strip trailing enclosing char? + if ($need_end && $ch == $csv_enclosed) { + if ($finished && $i == $len - 1) { + $ch = null; + } elseif ($i == $len - 1) { + $i = $fallbacki; + $ch = mb_substr($buffer, $i, 1); + if ($csv_terminated_len > 1 + && $ch == $csv_terminated[0] + ) { + $i += $csv_terminated_len - 1; + } + break; + } else { + $i++; + $ch = mb_substr($buffer, $i, 1); + if ($csv_terminated_len > 1 + && $ch == $csv_terminated[0] + ) { + $ch = $this->readCsvTerminatedString( + $buffer, + $ch, + $i, + $csv_terminated_len + ); + $i += $csv_terminated_len - 1; + } + } + } + // Are we at the end? + if ($ch == $csv_new_line + || ($csv_new_line == 'auto' && ($ch == "\r" || $ch == "\n")) + || ($finished && $i == $len - 1) + ) { + $csv_finish = true; + } + // Go to next char + if ($ch == $csv_terminated) { + if ($i == $len - 1) { + $i = $fallbacki; + $ch = mb_substr($buffer, $i, 1); + if ($csv_terminated_len > 1 + && $ch == $csv_terminated[0] + ) { + $i += $csv_terminated_len - 1; + } + break; + } + $i++; + $ch = mb_substr($buffer, $i, 1); + if ($csv_terminated_len > 1 + && $ch == $csv_terminated[0] + ) { + $ch = $this->readCsvTerminatedString( + $buffer, + $ch, + $i, + $csv_terminated_len + ); + $i += $csv_terminated_len - 1; + } + } + // If everything went okay, store value + $values[] = $value; + } + + // End of line + if ($csv_finish + || $ch == $csv_new_line + || ($csv_new_line == 'auto' && ($ch == "\r" || $ch == "\n")) + ) { + if ($csv_new_line == 'auto' && $ch == "\r") { // Handle "\r\n" + if ($i >= ($len - 2) && !$finished) { + break; // We need more data to decide new line + } + if (mb_substr($buffer, $i + 1, 1) == "\n") { + $i++; + } + } + // We didn't parse value till the end of line, so there was + // empty one + if (!$csv_finish) { + $values[] = ''; + } + + if ($this->_getAnalyze()) { + foreach ($values as $val) { + $tempRow[] = $val; + ++$col_count; + } + + if ($col_count > $max_cols) { + $max_cols = $col_count; + } + $col_count = 0; + + $rows[] = $tempRow; + $tempRow = array(); + } else { + // Do we have correct count of values? + if (count($values) != $required_fields) { + + // Hack for excel + if ($values[count($values) - 1] == ';') { + unset($values[count($values) - 1]); + } else { + $message = Message::error( + __( + 'Invalid column count in CSV input' + . ' on line %d.' + ) + ); + $message->addParam($line); + $error = true; + break; + } + } + + $first = true; + $sql = $sql_template; + foreach ($values as $key => $val) { + if (!$first) { + $sql .= ', '; + } + if ($val === null) { + $sql .= 'NULL'; + } else { + $sql .= '\'' + . $GLOBALS['dbi']->escapeString($val) + . '\''; + } + + $first = false; + } + $sql .= ')'; + if (isset($_POST['csv_replace'])) { + $sql .= " ON DUPLICATE KEY UPDATE "; + foreach ($fields as $field) { + $fieldName = Util::backquote( + $field['Field'] + ); + $sql .= $fieldName . " = VALUES(" . $fieldName + . "), "; + } + $sql = rtrim($sql, ', '); + } + + /** + * @todo maybe we could add original line to verbose + * SQL in comment + */ + Import::runQuery($sql, $sql, $sql_data); + } + + $line++; + $csv_finish = false; + $values = array(); + $buffer = mb_substr($buffer, $i + 1); + $len = mb_strlen($buffer); + $i = 0; + $lasti = -1; + $ch = mb_substr($buffer, 0, 1); + } + } // End of parser loop + } // End of import loop + + if ($this->_getAnalyze()) { + /* Fill out all rows */ + $num_rows = count($rows); + for ($i = 0; $i < $num_rows; ++$i) { + for ($j = count($rows[$i]); $j < $max_cols; ++$j) { + $rows[$i][] = 'NULL'; + } + } + + if (isset($_REQUEST['csv_col_names'])) { + $col_names = array_splice($rows, 0, 1); + $col_names = $col_names[0]; + // MySQL column names can't end with a space character. + foreach ($col_names as $key => $col_name) { + $col_names[$key] = rtrim($col_name); + } + } + + if ((isset($col_names) && count($col_names) != $max_cols) + || !isset($col_names) + ) { + // Fill out column names + for ($i = 0; $i < $max_cols; ++$i) { + $col_names[] = 'COL ' . ($i + 1); + } + } + + if (mb_strlen($db)) { + $result = $GLOBALS['dbi']->fetchResult('SHOW TABLES'); + $tbl_name = 'TABLE ' . (count($result) + 1); + } else { + $tbl_name = 'TBL_NAME'; + } + + $tables[] = array($tbl_name, $col_names, $rows); + + /* Obtain the best-fit MySQL types for each column */ + $analyses = array(); + $analyses[] = Import::analyzeTable($tables[0]); + + /** + * string $db_name (no backquotes) + * + * array $table = array(table_name, array() column_names, array()() rows) + * array $tables = array of "$table"s + * + * array $analysis = array(array() column_types, array() column_sizes) + * array $analyses = array of "$analysis"s + * + * array $create = array of SQL strings + * + * array $options = an associative array of options + */ + + /* Set database name to the currently selected one, if applicable */ + list($db_name, $options) = $this->getDbnameAndOptions($db, 'CSV_DB'); + + /* Non-applicable parameters */ + $create = null; + + /* Created and execute necessary SQL statements from data */ + Import::buildSql($db_name, $tables, $analyses, $create, $options, $sql_data); + + unset($tables); + unset($analyses); + } + + // Commit any possible data in buffers + Import::runQuery('', '', $sql_data); + + if (count($values) != 0 && !$error) { + $message = Message::error( + __('Invalid format of CSV input on line %d.') + ); + $message->addParam($line); + $error = true; + } + } + + /** + * Read the expected column_separated_with String of length + * $csv_terminated_len from the $buffer + * into variable $ch and return the read string $ch + * + * @param string $buffer The original string buffer read from + * csv file + * @param string $ch Partially read "column Separated with" + * string, also used to return after + * reading length equal $csv_terminated_len + * @param int $i Current read counter of buffer string + * @param int $csv_terminated_len The length of "column separated with" + * String + * + * @return string + */ + public function readCsvTerminatedString($buffer, $ch, $i, $csv_terminated_len) + { + for ($j = 0; $j < $csv_terminated_len - 1; $j++) { + $i++; + $ch .= mb_substr($buffer, $i, 1); + } + + return $ch; + } + /* ~~~~~~~~~~~~~~~~~~~~ Getters and Setters ~~~~~~~~~~~~~~~~~~~~ */ + + /** + * Returns true if the table should be analyzed, false otherwise + * + * @return bool + */ + private function _getAnalyze() + { + return $this->_analyze; + } + + /** + * Sets to true if the table should be analyzed, false otherwise + * + * @param bool $analyze status + * + * @return void + */ + private function _setAnalyze($analyze) + { + $this->_analyze = $analyze; + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Plugins/Import/ImportLdi.php b/php/apps/phpmyadmin49/libraries/classes/Plugins/Import/ImportLdi.php new file mode 100644 index 00000000..e41e6f02 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Plugins/Import/ImportLdi.php @@ -0,0 +1,177 @@ +setProperties(); + } + + /** + * Sets the import plugin properties. + * Called in the constructor. + * + * @return void + */ + protected function setProperties() + { + if ($GLOBALS['cfg']['Import']['ldi_local_option'] == 'auto') { + $GLOBALS['cfg']['Import']['ldi_local_option'] = false; + + $result = $GLOBALS['dbi']->tryQuery( + 'SELECT @@local_infile;' + ); + if ($result != false && $GLOBALS['dbi']->numRows($result) > 0) { + $tmp = $GLOBALS['dbi']->fetchRow($result); + if ($tmp[0] == 'ON') { + $GLOBALS['cfg']['Import']['ldi_local_option'] = true; + } + } + $GLOBALS['dbi']->freeResult($result); + unset($result); + } + + $generalOptions = parent::setProperties(); + $this->properties->setText('CSV using LOAD DATA'); + $this->properties->setExtension('ldi'); + + $leaf = new TextPropertyItem( + "columns", + __('Column names: ') + ); + $generalOptions->addProperty($leaf); + + $leaf = new BoolPropertyItem( + "ignore", + __('Do not abort on INSERT error') + ); + $generalOptions->addProperty($leaf); + + $leaf = new BoolPropertyItem( + "local_option", + __('Use LOCAL keyword') + ); + $generalOptions->addProperty($leaf); + } + + /** + * Handles the whole import logic + * + * @param array &$sql_data 2-element array with sql data + * + * @return void + */ + public function doImport(array &$sql_data = array()) + { + global $finished, $import_file, $charset_conversion, $table; + global $ldi_local_option, $ldi_replace, $ldi_ignore, $ldi_terminated, + $ldi_enclosed, $ldi_escaped, $ldi_new_line, $skip_queries, $ldi_columns; + + $compression = $GLOBALS['import_handle']->getCompression(); + + if ($import_file == 'none' + || $compression != 'none' + || $charset_conversion + ) { + // We handle only some kind of data! + $GLOBALS['message'] = Message::error( + __('This plugin does not support compressed imports!') + ); + $GLOBALS['error'] = true; + + return; + } + + $sql = 'LOAD DATA'; + if (isset($ldi_local_option)) { + $sql .= ' LOCAL'; + } + $sql .= ' INFILE \'' . $GLOBALS['dbi']->escapeString($import_file) + . '\''; + if (isset($ldi_replace)) { + $sql .= ' REPLACE'; + } elseif (isset($ldi_ignore)) { + $sql .= ' IGNORE'; + } + $sql .= ' INTO TABLE ' . Util::backquote($table); + + if (strlen($ldi_terminated) > 0) { + $sql .= ' FIELDS TERMINATED BY \'' . $ldi_terminated . '\''; + } + if (strlen($ldi_enclosed) > 0) { + $sql .= ' ENCLOSED BY \'' + . $GLOBALS['dbi']->escapeString($ldi_enclosed) . '\''; + } + if (strlen($ldi_escaped) > 0) { + $sql .= ' ESCAPED BY \'' + . $GLOBALS['dbi']->escapeString($ldi_escaped) . '\''; + } + if (strlen($ldi_new_line) > 0) { + if ($ldi_new_line == 'auto') { + $ldi_new_line + = (PHP_EOL == "\n") + ? '\n' + : '\r\n'; + } + $sql .= ' LINES TERMINATED BY \'' . $ldi_new_line . '\''; + } + if ($skip_queries > 0) { + $sql .= ' IGNORE ' . $skip_queries . ' LINES'; + $skip_queries = 0; + } + if (strlen($ldi_columns) > 0) { + $sql .= ' ('; + $tmp = preg_split('/,( ?)/', $ldi_columns); + $cnt_tmp = count($tmp); + for ($i = 0; $i < $cnt_tmp; $i++) { + if ($i > 0) { + $sql .= ', '; + } + /* Trim also `, if user already included backquoted fields */ + $sql .= Util::backquote( + trim($tmp[$i], " \t\r\n\0\x0B`") + ); + } // end for + $sql .= ')'; + } + + Import::runQuery($sql, $sql, $sql_data); + Import::runQuery('', '', $sql_data); + $finished = true; + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Plugins/Import/ImportMediawiki.php b/php/apps/phpmyadmin49/libraries/classes/Plugins/Import/ImportMediawiki.php new file mode 100644 index 00000000..852110e4 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Plugins/Import/ImportMediawiki.php @@ -0,0 +1,599 @@ +setProperties(); + } + + /** + * Sets the import plugin properties. + * Called in the constructor. + * + * @return void + */ + protected function setProperties() + { + $this->_setAnalyze(false); + if ($GLOBALS['plugin_param'] !== 'table') { + $this->_setAnalyze(true); + } + + $importPluginProperties = new ImportPluginProperties(); + $importPluginProperties->setText(__('MediaWiki Table')); + $importPluginProperties->setExtension('txt'); + $importPluginProperties->setMimeType('text/plain'); + $importPluginProperties->setOptions(array()); + $importPluginProperties->setOptionsText(__('Options')); + + $this->properties = $importPluginProperties; + } + + /** + * Handles the whole import logic + * + * @param array &$sql_data 2-element array with sql data + * + * @return void + */ + public function doImport(array &$sql_data = array()) + { + global $error, $timeout_passed, $finished; + + // Defaults for parser + + // The buffer that will be used to store chunks read from the imported file + $buffer = ''; + + // Used as storage for the last part of the current chunk data + // Will be appended to the first line of the next chunk, if there is one + $last_chunk_line = ''; + + // Remembers whether the current buffer line is part of a comment + $inside_comment = false; + // Remembers whether the current buffer line is part of a data comment + $inside_data_comment = false; + // Remembers whether the current buffer line is part of a structure comment + $inside_structure_comment = false; + + // MediaWiki only accepts "\n" as row terminator + $mediawiki_new_line = "\n"; + + // Initialize the name of the current table + $cur_table_name = ""; + + while (!$finished && !$error && !$timeout_passed) { + $data = Import::getNextChunk(); + + if ($data === false) { + // Subtract data we didn't handle yet and stop processing + $GLOBALS['offset'] -= mb_strlen($buffer); + break; + } elseif ($data === true) { + // Handle rest of buffer + } else { + // Append new data to buffer + $buffer = $data; + unset($data); + // Don't parse string if we're not at the end + // and don't have a new line inside + if (mb_strpos($buffer, $mediawiki_new_line) === false) { + continue; + } + } + + // Because of reading chunk by chunk, the first line from the buffer + // contains only a portion of an actual line from the imported file. + // Therefore, we have to append it to the last line from the previous + // chunk. If we are at the first chunk, $last_chunk_line should be empty. + $buffer = $last_chunk_line . $buffer; + + // Process the buffer line by line + $buffer_lines = explode($mediawiki_new_line, $buffer); + + $full_buffer_lines_count = count($buffer_lines); + // If the reading is not finalised, the final line of the current chunk + // will not be complete + if (! $finished) { + $last_chunk_line = $buffer_lines[--$full_buffer_lines_count]; + } + + for ($line_nr = 0; $line_nr < $full_buffer_lines_count; ++$line_nr) { + $cur_buffer_line = trim($buffer_lines[$line_nr]); + + // If the line is empty, go to the next one + if ($cur_buffer_line === '') { + continue; + } + + $first_character = $cur_buffer_line[0]; + $matches = array(); + + // Check beginning of comment + if (!strcmp(mb_substr($cur_buffer_line, 0, 4), "") + ) { + // Only data comments are closed. The structure comments + // will be closed when a data comment begins (in order to + // skip structure tables) + if ($inside_data_comment) { + $inside_data_comment = false; + } + + // End comments that are not related to table structure + if (!$inside_structure_comment) { + $inside_comment = false; + } + } else { + // Check table name + $match_table_name = array(); + if (preg_match( + "/^Table data for `(.*)`$/", + $cur_buffer_line, + $match_table_name + ) + ) { + $cur_table_name = $match_table_name[1]; + $inside_data_comment = true; + + $inside_structure_comment + = $this->_mngInsideStructComm( + $inside_structure_comment + ); + } elseif (preg_match( + "/^Table structure for `(.*)`$/", + $cur_buffer_line, + $match_table_name + ) + ) { + // The structure comments will be ignored + $inside_structure_comment = true; + } + } + continue; + } elseif (preg_match('/^\{\|(.*)$/', $cur_buffer_line, $matches)) { + // Check start of table + + // This will store all the column info on all rows from + // the current table read from the buffer + $cur_temp_table = array(); + + // Will be used as storage for the current row in the buffer + // Once all its columns are read, it will be added to + // $cur_temp_table and then it will be emptied + $cur_temp_line = array(); + + // Helps us differentiate the header columns + // from the normal columns + $in_table_header = false; + // End processing because the current line does not + // contain any column information + } elseif (mb_substr($cur_buffer_line, 0, 2) === '|-' + || mb_substr($cur_buffer_line, 0, 2) === '|+' + || mb_substr($cur_buffer_line, 0, 2) === '|}' + ) { + // Check begin row or end table + + // Add current line to the values storage + if (!empty($cur_temp_line)) { + // If the current line contains header cells + // ( marked with '!' ), + // it will be marked as table header + if ($in_table_header) { + // Set the header columns + $cur_temp_table_headers = $cur_temp_line; + } else { + // Normal line, add it to the table + $cur_temp_table [] = $cur_temp_line; + } + } + + // Empty the temporary buffer + $cur_temp_line = array(); + + // No more processing required at the end of the table + if (mb_substr($cur_buffer_line, 0, 2) === '|}') { + $current_table = array( + $cur_table_name, + $cur_temp_table_headers, + $cur_temp_table, + ); + + // Import the current table data into the database + $this->_importDataOneTable($current_table, $sql_data); + + // Reset table name + $cur_table_name = ""; + } + // What's after the row tag is now only attributes + + } elseif (($first_character === '|') || ($first_character === '!')) { + // Check cell elements + + // Header cells + if ($first_character === '!') { + // Mark as table header, but treat as normal row + $cur_buffer_line = str_replace('!!', '||', $cur_buffer_line); + // Will be used to set $cur_temp_line as table header + $in_table_header = true; + } else { + $in_table_header = false; + } + + // Loop through each table cell + $cells = $this->_explodeMarkup($cur_buffer_line); + foreach ($cells as $cell) { + $cell = $this->_getCellData($cell); + + // Delete the beginning of the column, if there is one + $cell = trim($cell); + $col_start_chars = array("|", "!"); + foreach ($col_start_chars as $col_start_char) { + $cell = $this->_getCellContent($cell, $col_start_char); + } + + // Add the cell to the row + $cur_temp_line [] = $cell; + } // foreach $cells + } else { + // If it's none of the above, then the current line has a bad + // format + $message = Message::error( + __('Invalid format of mediawiki input on line:
      %s.') + ); + $message->addParam($cur_buffer_line); + $error = true; + } + } // End treating full buffer lines + } // while - finished parsing buffer + } + + /** + * Imports data from a single table + * + * @param array $table containing all table info: + * + * $table[0] - string containing table name + * $table[1] - array[] of table headers + * $table[2] - array[][] of table content rows + * + * + * @param array &$sql_data 2-element array with sql data + * + * @global bool $analyze whether to scan for column types + * + * @return void + */ + private function _importDataOneTable(array $table, array &$sql_data) + { + $analyze = $this->_getAnalyze(); + if ($analyze) { + // Set the table name + $this->_setTableName($table[0]); + + // Set generic names for table headers if they don't exist + $this->_setTableHeaders($table[1], $table[2][0]); + + // Create the tables array to be used in Import::buildSql() + $tables = array(); + $tables [] = array($table[0], $table[1], $table[2]); + + // Obtain the best-fit MySQL types for each column + $analyses = array(); + $analyses [] = Import::analyzeTable($tables[0]); + + $this->_executeImportTables($tables, $analyses, $sql_data); + } + + // Commit any possible data in buffers + Import::runQuery('', '', $sql_data); + } + + /** + * Sets the table name + * + * @param string &$table_name reference to the name of the table + * + * @return void + */ + private function _setTableName(&$table_name) + { + if (empty($table_name)) { + $result = $GLOBALS['dbi']->fetchResult('SHOW TABLES'); + // todo check if the name below already exists + $table_name = 'TABLE ' . (count($result) + 1); + } + } + + /** + * Set generic names for table headers, if they don't exist + * + * @param array &$table_headers reference to the array containing the headers + * of a table + * @param array $table_row array containing the first content row + * + * @return void + */ + private function _setTableHeaders(array &$table_headers, array $table_row) + { + if (empty($table_headers)) { + // The first table row should contain the number of columns + // If they are not set, generic names will be given (COL 1, COL 2, etc) + $num_cols = count($table_row); + for ($i = 0; $i < $num_cols; ++$i) { + $table_headers [$i] = 'COL ' . ($i + 1); + } + } + } + + /** + * Sets the database name and additional options and calls Import::buildSql() + * Used in PMA_importDataAllTables() and $this->_importDataOneTable() + * + * @param array &$tables structure: + * array( + * array(table_name, array() column_names, array()() + * rows) + * ) + * @param array &$analyses structure: + * $analyses = array( + * array(array() column_types, array() column_sizes) + * ) + * @param array &$sql_data 2-element array with sql data + * + * @global string $db name of the database to import in + * + * @return void + */ + private function _executeImportTables(array &$tables, array &$analyses, array &$sql_data) + { + global $db; + + // $db_name : The currently selected database name, if applicable + // No backquotes + // $options : An associative array of options + list($db_name, $options) = $this->getDbnameAndOptions($db, 'mediawiki_DB'); + + // Array of SQL strings + // Non-applicable parameters + $create = null; + + // Create and execute necessary SQL statements from data + Import::buildSql($db_name, $tables, $analyses, $create, $options, $sql_data); + + unset($tables); + unset($analyses); + } + + /** + * Replaces all instances of the '||' separator between delimiters + * in a given string + * + * @param string $replace the string to be replaced with + * @param string $subject the text to be replaced + * + * @return string with replacements + */ + private function _delimiterReplace($replace, $subject) + { + // String that will be returned + $cleaned = ""; + // Possible states of current character + $inside_tag = false; + $inside_attribute = false; + // Attributes can be declared with either " or ' + $start_attribute_character = false; + + // The full separator is "||"; + // This remembers if the previous character was '|' + $partial_separator = false; + + // Parse text char by char + for ($i = 0; $i < strlen($subject); $i++) { + $cur_char = $subject[$i]; + // Check for separators + if ($cur_char == '|') { + // If we're not inside a tag, then this is part of a real separator, + // so we append it to the current segment + if (!$inside_attribute) { + $cleaned .= $cur_char; + if ($partial_separator) { + $inside_tag = false; + $inside_attribute = false; + } + } elseif ($partial_separator) { + // If we are inside a tag, we replace the current char with + // the placeholder and append that to the current segment + $cleaned .= $replace; + } + + // If the previous character was also '|', then this ends a + // full separator. If not, this may be the beginning of one + $partial_separator = !$partial_separator; + } else { + // If we're inside a tag attribute and the current character is + // not '|', but the previous one was, it means that the single '|' + // was not appended, so we append it now + if ($partial_separator && $inside_attribute) { + $cleaned .= "|"; + } + // If the char is different from "|", no separator can be formed + $partial_separator = false; + + // any other character should be appended to the current segment + $cleaned .= $cur_char; + + if ($cur_char == '<' && !$inside_attribute) { + // start of a tag + $inside_tag = true; + } elseif ($cur_char == '>' && !$inside_attribute) { + // end of a tag + $inside_tag = false; + } elseif (($cur_char == '"' || $cur_char == "'") && $inside_tag) { + // start or end of an attribute + if (!$inside_attribute) { + $inside_attribute = true; + // remember the attribute`s declaration character (" or ') + $start_attribute_character = $cur_char; + } else { + if ($cur_char == $start_attribute_character) { + $inside_attribute = false; + // unset attribute declaration character + $start_attribute_character = false; + } + } + } + } + } // end for each character in $subject + + return $cleaned; + } + + /** + * Separates a string into items, similarly to explode + * Uses the '||' separator (which is standard in the mediawiki format) + * and ignores any instances of it inside markup tags + * Used in parsing buffer lines containing data cells + * + * @param string $text text to be split + * + * @return array + */ + private function _explodeMarkup($text) + { + $separator = "||"; + $placeholder = "\x00"; + + // Remove placeholder instances + $text = str_replace($placeholder, '', $text); + + // Replace instances of the separator inside HTML-like + // tags with the placeholder + $cleaned = $this->_delimiterReplace($placeholder, $text); + // Explode, then put the replaced separators back in + $items = explode($separator, $cleaned); + foreach ($items as $i => $str) { + $items[$i] = str_replace($placeholder, $separator, $str); + } + + return $items; + } + + + /* ~~~~~~~~~~~~~~~~~~~~ Getters and Setters ~~~~~~~~~~~~~~~~~~~~ */ + + /** + * Returns true if the table should be analyzed, false otherwise + * + * @return bool + */ + private function _getAnalyze() + { + return $this->_analyze; + } + + /** + * Sets to true if the table should be analyzed, false otherwise + * + * @param bool $analyze status + * + * @return void + */ + private function _setAnalyze($analyze) + { + $this->_analyze = $analyze; + } + + /** + * Get cell + * + * @param string $cell Cell + * + * @return mixed + */ + private function _getCellData($cell) + { + // A cell could contain both parameters and data + $cell_data = explode('|', $cell, 2); + + // A '|' inside an invalid link should not + // be mistaken as delimiting cell parameters + if (mb_strpos($cell_data[0], '[[') === false) { + return $cell; + } + + if (count($cell_data) == 1) { + return $cell_data[0]; + } + + return $cell_data[1]; + } + + /** + * Manage $inside_structure_comment + * + * @param boolean $inside_structure_comment Value to test + * + * @return bool + */ + private function _mngInsideStructComm($inside_structure_comment) + { + // End ignoring structure rows + if ($inside_structure_comment) { + $inside_structure_comment = false; + } + + return $inside_structure_comment; + } + + /** + * Get cell content + * + * @param string $cell Cell + * @param string $col_start_char Start char + * + * @return string + */ + private function _getCellContent($cell, $col_start_char) + { + if (mb_strpos($cell, $col_start_char) === 0) { + $cell = trim(mb_substr($cell, 1)); + } + + return $cell; + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Plugins/Import/ImportOds.php b/php/apps/phpmyadmin49/libraries/classes/Plugins/Import/ImportOds.php new file mode 100644 index 00000000..7100fadc --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Plugins/Import/ImportOds.php @@ -0,0 +1,422 @@ +setProperties(); + } + + /** + * Sets the import plugin properties. + * Called in the constructor. + * + * @return void + */ + protected function setProperties() + { + $importPluginProperties = new ImportPluginProperties(); + $importPluginProperties->setText('OpenDocument Spreadsheet'); + $importPluginProperties->setExtension('ods'); + $importPluginProperties->setOptionsText(__('Options')); + + // create the root group that will be the options field for + // $importPluginProperties + // this will be shown as "Format specific options" + $importSpecificOptions = new OptionsPropertyRootGroup( + "Format Specific Options" + ); + + // general options main group + $generalOptions = new OptionsPropertyMainGroup("general_opts"); + // create primary items and add them to the group + $leaf = new BoolPropertyItem( + "col_names", + __( + 'The first line of the file contains the table column names' + . ' (if this is unchecked, the first line will become part' + . ' of the data)' + ) + ); + $generalOptions->addProperty($leaf); + $leaf = new BoolPropertyItem( + "empty_rows", + __('Do not import empty rows') + ); + $generalOptions->addProperty($leaf); + $leaf = new BoolPropertyItem( + "recognize_percentages", + __( + 'Import percentages as proper decimals (ex. 12.00% to .12)' + ) + ); + $generalOptions->addProperty($leaf); + $leaf = new BoolPropertyItem( + "recognize_currency", + __('Import currencies (ex. $5.00 to 5.00)') + ); + $generalOptions->addProperty($leaf); + + // add the main group to the root group + $importSpecificOptions->addProperty($generalOptions); + + // set the options for the import plugin property item + $importPluginProperties->setOptions($importSpecificOptions); + $this->properties = $importPluginProperties; + } + + /** + * Handles the whole import logic + * + * @param array &$sql_data 2-element array with sql data + * + * @return void + */ + public function doImport(array &$sql_data = array()) + { + global $db, $error, $timeout_passed, $finished; + + $i = 0; + $len = 0; + $buffer = ""; + + /** + * Read in the file via Import::getNextChunk so that + * it can process compressed files + */ + while (!($finished && $i >= $len) && !$error && !$timeout_passed) { + $data = Import::getNextChunk(); + if ($data === false) { + /* subtract data we didn't handle yet and stop processing */ + $GLOBALS['offset'] -= strlen($buffer); + break; + } elseif ($data === true) { + /* Handle rest of buffer */ + } else { + /* Append new data to buffer */ + $buffer .= $data; + unset($data); + } + } + + unset($data); + + /** + * Disable loading of external XML entities. + */ + libxml_disable_entity_loader(); + + /** + * Load the XML string + * + * The option LIBXML_COMPACT is specified because it can + * result in increased performance without the need to + * alter the code in any way. It's basically a freebee. + */ + $xml = @simplexml_load_string($buffer, "SimpleXMLElement", LIBXML_COMPACT); + + unset($buffer); + + if ($xml === false) { + $sheets = array(); + $GLOBALS['message'] = Message::error( + __( + 'The XML file specified was either malformed or incomplete.' + . ' Please correct the issue and try again.' + ) + ); + $GLOBALS['error'] = true; + } else { + /** @var SimpleXMLElement $root */ + $root = $xml->children('office', true)->{'body'}->{'spreadsheet'}; + if (empty($root)) { + $sheets = array(); + $GLOBALS['message'] = Message::error( + __('Could not parse OpenDocument Spreadsheet!') + ); + $GLOBALS['error'] = true; + } else { + $sheets = $root->children('table', true); + } + } + + $tables = array(); + + $max_cols = 0; + + $col_count = 0; + $col_names = array(); + + $tempRow = array(); + $tempRows = array(); + $rows = array(); + + /* Iterate over tables */ + /** @var SimpleXMLElement $sheet */ + foreach ($sheets as $sheet) { + $col_names_in_first_row = isset($_REQUEST['ods_col_names']); + + /* Iterate over rows */ + /** @var SimpleXMLElement $row */ + foreach ($sheet as $row) { + $type = $row->getName(); + if (strcmp('table-row', $type)) { + continue; + } + /* Iterate over columns */ + $cellCount = count($row); + $a = 0; + /** @var SimpleXMLElement $cell */ + foreach ($row as $cell) { + $a++; + $text = $cell->children('text', true); + $cell_attrs = $cell->attributes('office', true); + + if (count($text) != 0) { + $attr = $cell->attributes('table', true); + $num_repeat = (int)$attr['number-columns-repeated']; + $num_iterations = $num_repeat ? $num_repeat : 1; + + for ($k = 0; $k < $num_iterations; $k++) { + $value = $this->getValue($cell_attrs, $text); + if (!$col_names_in_first_row) { + $tempRow[] = $value; + } else { + // MySQL column names can't end with a space + // character. + $col_names[] = rtrim($value); + } + + ++$col_count; + } + continue; + } + + // skip empty repeats in the last row + if ($a == $cellCount) { + continue; + } + + $attr = $cell->attributes('table', true); + $num_null = (int)$attr['number-columns-repeated']; + + if ($num_null) { + if (!$col_names_in_first_row) { + for ($i = 0; $i < $num_null; ++$i) { + $tempRow[] = 'NULL'; + ++$col_count; + } + } else { + for ($i = 0; $i < $num_null; ++$i) { + $col_names[] = Import::getColumnAlphaName( + $col_count + 1 + ); + ++$col_count; + } + } + } else { + if (!$col_names_in_first_row) { + $tempRow[] = 'NULL'; + } else { + $col_names[] = Import::getColumnAlphaName( + $col_count + 1 + ); + } + + ++$col_count; + } + } //Endforeach + + /* Find the widest row */ + if ($col_count > $max_cols) { + $max_cols = $col_count; + } + + /* Don't include a row that is full of NULL values */ + if (!$col_names_in_first_row) { + if ($_REQUEST['ods_empty_rows']) { + foreach ($tempRow as $cell) { + if (strcmp('NULL', $cell)) { + $tempRows[] = $tempRow; + break; + } + } + } else { + $tempRows[] = $tempRow; + } + } + + $col_count = 0; + $col_names_in_first_row = false; + $tempRow = array(); + } + + /* Skip over empty sheets */ + if (count($tempRows) == 0 || count($tempRows[0]) == 0) { + $col_names = array(); + $tempRow = array(); + $tempRows = array(); + continue; + } + + /** + * Fill out each row as necessary to make + * every one exactly as wide as the widest + * row. This included column names. + */ + + /* Fill out column names */ + for ($i = count($col_names); $i < $max_cols; ++$i) { + $col_names[] = Import::getColumnAlphaName($i + 1); + } + + /* Fill out all rows */ + $num_rows = count($tempRows); + for ($i = 0; $i < $num_rows; ++$i) { + for ($j = count($tempRows[$i]); $j < $max_cols; ++$j) { + $tempRows[$i][] = 'NULL'; + } + } + + /* Store the table name so we know where to place the row set */ + $tbl_attr = $sheet->attributes('table', true); + $tables[] = array((string)$tbl_attr['name']); + + /* Store the current sheet in the accumulator */ + $rows[] = array((string)$tbl_attr['name'], $col_names, $tempRows); + $tempRows = array(); + $col_names = array(); + $max_cols = 0; + } + + unset($tempRow); + unset($tempRows); + unset($col_names); + unset($sheets); + unset($xml); + + /** + * Bring accumulated rows into the corresponding table + */ + $num_tables = count($tables); + for ($i = 0; $i < $num_tables; ++$i) { + $num_rows = count($rows); + for ($j = 0; $j < $num_rows; ++$j) { + if (strcmp($tables[$i][Import::TBL_NAME], $rows[$j][Import::TBL_NAME])) { + continue; + } + + if (!isset($tables[$i][Import::COL_NAMES])) { + $tables[$i][] = $rows[$j][Import::COL_NAMES]; + } + + $tables[$i][Import::ROWS] = $rows[$j][Import::ROWS]; + } + } + + /* No longer needed */ + unset($rows); + + /* Obtain the best-fit MySQL types for each column */ + $analyses = array(); + + $len = count($tables); + for ($i = 0; $i < $len; ++$i) { + $analyses[] = Import::analyzeTable($tables[$i]); + } + + /** + * string $db_name (no backquotes) + * + * array $table = array(table_name, array() column_names, array()() rows) + * array $tables = array of "$table"s + * + * array $analysis = array(array() column_types, array() column_sizes) + * array $analyses = array of "$analysis"s + * + * array $create = array of SQL strings + * + * array $options = an associative array of options + */ + + /* Set database name to the currently selected one, if applicable */ + list($db_name, $options) = $this->getDbnameAndOptions($db, 'ODS_DB'); + + /* Non-applicable parameters */ + $create = null; + + /* Created and execute necessary SQL statements from data */ + Import::buildSql($db_name, $tables, $analyses, $create, $options, $sql_data); + + unset($tables); + unset($analyses); + + /* Commit any possible data in buffers */ + Import::runQuery('', '', $sql_data); + } + + /** + * Get value + * + * @param array $cell_attrs Cell attributes + * @param array $text Texts + * + * @return float|string + */ + protected function getValue($cell_attrs, $text) + { + if ($_REQUEST['ods_recognize_percentages'] + && !strcmp( + 'percentage', + $cell_attrs['value-type'] + ) + ) { + $value = (double)$cell_attrs['value']; + + return $value; + } elseif ($_REQUEST['ods_recognize_currency'] + && !strcmp('currency', $cell_attrs['value-type']) + ) { + $value = (double)$cell_attrs['value']; + + return $value; + } + + /* We need to concatenate all paragraphs */ + $values = array(); + foreach ($text as $paragraph) { + $values[] = (string)$paragraph; + } + $value = implode("\n", $values); + + return $value; + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Plugins/Import/ImportShp.php b/php/apps/phpmyadmin49/libraries/classes/Plugins/Import/ImportShp.php new file mode 100644 index 00000000..c5d8b52c --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Plugins/Import/ImportShp.php @@ -0,0 +1,318 @@ +setProperties(); + if (extension_loaded('zip')) { + $this->zipExtension = new ZipExtension(); + } + } + + /** + * Sets the import plugin properties. + * Called in the constructor. + * + * @return void + */ + protected function setProperties() + { + $importPluginProperties = new ImportPluginProperties(); + $importPluginProperties->setText(__('ESRI Shape File')); + $importPluginProperties->setExtension('shp'); + $importPluginProperties->setOptions(array()); + $importPluginProperties->setOptionsText(__('Options')); + + $this->properties = $importPluginProperties; + } + + /** + * Handles the whole import logic + * + * @param array &$sql_data 2-element array with sql data + * + * @return void + */ + public function doImport(array &$sql_data = array()) + { + global $db, $error, $finished, + $import_file, $local_import_file, $message; + + $GLOBALS['finished'] = false; + + $compression = $GLOBALS['import_handle']->getCompression(); + + $shp = new ShapeFileImport(1); + // If the zip archive has more than one file, + // get the correct content to the buffer from .shp file. + if ($compression == 'application/zip' + && $this->zipExtension->getNumberOfFiles($import_file) > 1 + ) { + if ($GLOBALS['import_handle']->openZip('/^.*\.shp$/i') === false) { + $message = Message::error( + __('There was an error importing the ESRI shape file: "%s".') + ); + $message->addParam($GLOBALS['import_handle']->getError()); + + return; + } + } + + $temp_dbf_file = false; + // We need dbase extension to handle .dbf file + if (extension_loaded('dbase')) { + $temp = $GLOBALS['PMA_Config']->getTempDir('shp'); + // If we can extract the zip archive to 'TempDir' + // and use the files in it for import + if ($compression == 'application/zip' && ! is_null($temp)) { + $dbf_file_name = $this->zipExtension->findFile( + $import_file, + '/^.*\.dbf$/i' + ); + // If the corresponding .dbf file is in the zip archive + if ($dbf_file_name) { + // Extract the .dbf file and point to it. + $extracted = $this->zipExtension->extract( + $import_file, + $dbf_file_name + ); + if ($extracted !== false) { + $dbf_file_path = $temp . (PMA_IS_WINDOWS ? '\\' : '/') + . Sanitize::sanitizeFilename($dbf_file_name, true); + $handle = fopen($dbf_file_path, 'wb'); + if ($handle !== false) { + fwrite($handle, $extracted); + fclose($handle); + $temp_dbf_file = true; + // Replace the .dbf with .*, as required + // by the bsShapeFiles library. + $file_name = substr( + $dbf_file_path, 0, strlen($dbf_file_path) - 4 + ) . '.*'; + $shp->FileName = $file_name; + } + } + } + } elseif (!empty($local_import_file) + && !empty($GLOBALS['cfg']['UploadDir']) + && $compression == 'none' + ) { + // If file is in UploadDir, use .dbf file in the same UploadDir + // to load extra data. + // Replace the .shp with .*, + // so the bsShapeFiles library correctly locates .dbf file. + $file_name = mb_substr( + $import_file, + 0, + mb_strlen($import_file) - 4 + ) . '.*'; + $shp->FileName = $file_name; + } + } + + // Delete the .dbf file extracted to 'TempDir' + if ($temp_dbf_file + && isset($dbf_file_path) + && @file_exists($dbf_file_path) + ) { + unlink($dbf_file_path); + } + + // Load data + $shp->loadFromFile(''); + if ($shp->lastError != "") { + $error = true; + $message = Message::error( + __('There was an error importing the ESRI shape file: "%s".') + ); + $message->addParam($shp->lastError); + + return; + } + + switch ($shp->shapeType) { + // ESRI Null Shape + case 0: + break; + // ESRI Point + case 1: + $gis_type = 'point'; + break; + // ESRI PolyLine + case 3: + $gis_type = 'multilinestring'; + break; + // ESRI Polygon + case 5: + $gis_type = 'multipolygon'; + break; + // ESRI MultiPoint + case 8: + $gis_type = 'multipoint'; + break; + default: + $error = true; + $message = Message::error( + __('MySQL Spatial Extension does not support ESRI type "%s".') + ); + $message->addParam($shp->getShapeName()); + return; + } + + if (isset($gis_type)) { + /** @var GisMultiLineString|\PhpMyAdmin\Gis\GisMultiPoint|\PhpMyAdmin\Gis\GisPoint|GisPolygon $gis_obj */ + $gis_obj = GisFactory::factory($gis_type); + } else { + $gis_obj = null; + } + + $num_rows = count($shp->records); + // If .dbf file is loaded, the number of extra data columns + $num_data_cols = isset($shp->DBFHeader) ? count($shp->DBFHeader) : 0; + + $rows = array(); + $col_names = array(); + if ($num_rows != 0) { + foreach ($shp->records as $record) { + $tempRow = array(); + if ($gis_obj == null) { + $tempRow[] = null; + } else { + $tempRow[] = "GeomFromText('" + . $gis_obj->getShape($record->SHPData) . "')"; + } + + if (isset($shp->DBFHeader)) { + foreach ($shp->DBFHeader as $c) { + $cell = trim($record->DBFData[$c[0]]); + + if (!strcmp($cell, '')) { + $cell = 'NULL'; + } + + $tempRow[] = $cell; + } + } + $rows[] = $tempRow; + } + } + + if (count($rows) == 0) { + $error = true; + $message = Message::error( + __('The imported file does not contain any data!') + ); + + return; + } + + // Column names for spatial column and the rest of the columns, + // if they are available + $col_names[] = 'SPATIAL'; + for ($n = 0; $n < $num_data_cols; $n++) { + $col_names[] = $shp->DBFHeader[$n][0]; + } + + // Set table name based on the number of tables + if (strlen($db) > 0) { + $result = $GLOBALS['dbi']->fetchResult('SHOW TABLES'); + $table_name = 'TABLE ' . (count($result) + 1); + } else { + $table_name = 'TBL_NAME'; + } + $tables = array(array($table_name, $col_names, $rows)); + + // Use data from shape file to chose best-fit MySQL types for each column + $analyses = array(); + $analyses[] = Import::analyzeTable($tables[0]); + + $table_no = 0; + $spatial_col = 0; + $analyses[$table_no][Import::TYPES][$spatial_col] = Import::GEOMETRY; + $analyses[$table_no][Import::FORMATTEDSQL][$spatial_col] = true; + + // Set database name to the currently selected one, if applicable + if (strlen($db) > 0) { + $db_name = $db; + $options = array('create_db' => false); + } else { + $db_name = 'SHP_DB'; + $options = null; + } + + // Created and execute necessary SQL statements from data + $null_param = null; + Import::buildSql($db_name, $tables, $analyses, $null_param, $options, $sql_data); + + unset($tables); + unset($analyses); + + $finished = true; + $error = false; + + // Commit any possible data in buffers + Import::runQuery('', '', $sql_data); + } + + /** + * Returns specified number of bytes from the buffer. + * Buffer automatically fetches next chunk of data when the buffer + * falls short. + * Sets $eof when $GLOBALS['finished'] is set and the buffer falls short. + * + * @param int $length number of bytes + * + * @return string + */ + public static function readFromBuffer($length) + { + global $buffer, $eof; + + if (strlen($buffer) < $length) { + if ($GLOBALS['finished']) { + $eof = true; + } else { + $buffer .= Import::getNextChunk(); + } + } + $result = substr($buffer, 0, $length); + $buffer = substr($buffer, $length); + + return $result; + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Plugins/Import/ImportSql.php b/php/apps/phpmyadmin49/libraries/classes/Plugins/Import/ImportSql.php new file mode 100644 index 00000000..240b8ba3 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Plugins/Import/ImportSql.php @@ -0,0 +1,198 @@ +setProperties(); + } + + /** + * Sets the import plugin properties. + * Called in the constructor. + * + * @return void + */ + protected function setProperties() + { + $importPluginProperties = new ImportPluginProperties(); + $importPluginProperties->setText('SQL'); + $importPluginProperties->setExtension('sql'); + $importPluginProperties->setOptionsText(__('Options')); + + $compats = $GLOBALS['dbi']->getCompatibilities(); + if (count($compats) > 0) { + $values = array(); + foreach ($compats as $val) { + $values[$val] = $val; + } + + // create the root group that will be the options field for + // $importPluginProperties + // this will be shown as "Format specific options" + $importSpecificOptions = new OptionsPropertyRootGroup( + "Format Specific Options" + ); + + // general options main group + $generalOptions = new OptionsPropertyMainGroup("general_opts"); + // create primary items and add them to the group + $leaf = new SelectPropertyItem( + "compatibility", + __('SQL compatibility mode:') + ); + $leaf->setValues($values); + $leaf->setDoc( + array( + 'manual_MySQL_Database_Administration', + 'Server_SQL_mode', + ) + ); + $generalOptions->addProperty($leaf); + $leaf = new BoolPropertyItem( + "no_auto_value_on_zero", + __('Do not use AUTO_INCREMENT for zero values') + ); + $leaf->setDoc( + array( + 'manual_MySQL_Database_Administration', + 'Server_SQL_mode', + 'sqlmode_no_auto_value_on_zero', + ) + ); + $generalOptions->addProperty($leaf); + + // add the main group to the root group + $importSpecificOptions->addProperty($generalOptions); + // set the options for the import plugin property item + $importPluginProperties->setOptions($importSpecificOptions); + } + + $this->properties = $importPluginProperties; + } + + /** + * Handles the whole import logic + * + * @param array &$sql_data 2-element array with sql data + * + * @return void + */ + public function doImport(array &$sql_data = array()) + { + global $error, $timeout_passed; + + // Handle compatibility options. + $this->_setSQLMode($GLOBALS['dbi'], $_REQUEST); + + $bq = new BufferedQuery(); + if (isset($_POST['sql_delimiter'])) { + $bq->setDelimiter($_POST['sql_delimiter']); + } + + /** + * Will be set in Import::getNextChunk(). + * + * @global bool $GLOBALS ['finished'] + */ + $GLOBALS['finished'] = false; + + while ((!$error) && (!$timeout_passed)) { + + // Getting the first statement, the remaining data and the last + // delimiter. + $statement = $bq->extract(); + + // If there is no full statement, we are looking for more data. + if (empty($statement)) { + + // Importing new data. + $newData = Import::getNextChunk(); + + // Subtract data we didn't handle yet and stop processing. + if ($newData === false) { + $GLOBALS['offset'] -= mb_strlen($bq->query); + break; + } + + // Checking if the input buffer has finished. + if ($newData === true) { + $GLOBALS['finished'] = true; + break; + } + + // Convert CR (but not CRLF) to LF otherwise all queries may + // not get executed on some platforms. + $bq->query .= preg_replace("/\r($|[^\n])/", "\n$1", $newData); + + continue; + } + + // Executing the query. + Import::runQuery($statement, $statement, $sql_data); + } + + // Extracting remaining statements. + while ((!$error) && (!$timeout_passed) && (!empty($bq->query))) { + $statement = $bq->extract(true); + if (!empty($statement)) { + Import::runQuery($statement, $statement, $sql_data); + } + } + + // Finishing. + Import::runQuery('', '', $sql_data); + } + + /** + * Handle compatibility options + * + * @param PhpMyAdmin\DatabaseInterface $dbi Database interface + * @param array $request Request array + * + * @return void + */ + private function _setSQLMode($dbi, array $request) + { + $sql_modes = array(); + if (isset($request['sql_compatibility']) + && 'NONE' != $request['sql_compatibility'] + ) { + $sql_modes[] = $request['sql_compatibility']; + } + if (isset($request['sql_no_auto_value_on_zero'])) { + $sql_modes[] = 'NO_AUTO_VALUE_ON_ZERO'; + } + if (count($sql_modes) > 0) { + $dbi->tryQuery( + 'SET SQL_MODE="' . implode(',', $sql_modes) . '"' + ); + } + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Plugins/Import/ImportXml.php b/php/apps/phpmyadmin49/libraries/classes/Plugins/Import/ImportXml.php new file mode 100644 index 00000000..6de2ace1 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Plugins/Import/ImportXml.php @@ -0,0 +1,371 @@ +setProperties(); + } + + /** + * Sets the import plugin properties. + * Called in the constructor. + * + * @return void + */ + protected function setProperties() + { + $importPluginProperties = new ImportPluginProperties(); + $importPluginProperties->setText(__('XML')); + $importPluginProperties->setExtension('xml'); + $importPluginProperties->setMimeType('text/xml'); + $importPluginProperties->setOptions(array()); + $importPluginProperties->setOptionsText(__('Options')); + + $this->properties = $importPluginProperties; + } + + /** + * Handles the whole import logic + * + * @param array &$sql_data 2-element array with sql data + * + * @return void + */ + public function doImport(array &$sql_data = array()) + { + global $error, $timeout_passed, $finished, $db; + + $i = 0; + $len = 0; + $buffer = ""; + + /** + * Read in the file via Import::getNextChunk so that + * it can process compressed files + */ + while (!($finished && $i >= $len) && !$error && !$timeout_passed) { + $data = Import::getNextChunk(); + if ($data === false) { + /* subtract data we didn't handle yet and stop processing */ + $GLOBALS['offset'] -= strlen($buffer); + break; + } elseif ($data === true) { + /* Handle rest of buffer */ + } else { + /* Append new data to buffer */ + $buffer .= $data; + unset($data); + } + } + + unset($data); + + /** + * Disable loading of external XML entities. + */ + libxml_disable_entity_loader(); + + /** + * Load the XML string + * + * The option LIBXML_COMPACT is specified because it can + * result in increased performance without the need to + * alter the code in any way. It's basically a freebee. + */ + $xml = @simplexml_load_string($buffer, "SimpleXMLElement", LIBXML_COMPACT); + + unset($buffer); + + /** + * The XML was malformed + */ + if ($xml === false) { + Message::error( + __( + 'The XML file specified was either malformed or incomplete.' + . ' Please correct the issue and try again.' + ) + ) + ->display(); + unset($xml); + $GLOBALS['finished'] = false; + + return; + } + + /** + * Table accumulator + */ + $tables = array(); + /** + * Row accumulator + */ + $rows = array(); + + /** + * Temp arrays + */ + $tempRow = array(); + $tempCells = array(); + + /** + * CREATE code included (by default: no) + */ + $struct_present = false; + + /** + * Analyze the data in each table + */ + $namespaces = $xml->getNameSpaces(true); + + /** + * Get the database name, collation and charset + */ + $db_attr = $xml->children(isset($namespaces['pma']) ? $namespaces['pma'] : null) + ->{'structure_schemas'}->{'database'}; + + if ($db_attr instanceof SimpleXMLElement) { + $db_attr = $db_attr->attributes(); + $db_name = (string)$db_attr['name']; + $collation = (string)$db_attr['collation']; + $charset = (string)$db_attr['charset']; + } else { + /** + * If the structure section is not present + * get the database name from the data section + */ + $db_attr = $xml->children() + ->attributes(); + $db_name = (string)$db_attr['name']; + $collation = null; + $charset = null; + } + + /** + * The XML was malformed + */ + if ($db_name === null) { + Message::error( + __( + 'The XML file specified was either malformed or incomplete.' + . ' Please correct the issue and try again.' + ) + ) + ->display(); + unset($xml); + $GLOBALS['finished'] = false; + + return; + } + + /** + * Retrieve the structure information + */ + if (isset($namespaces['pma'])) { + /** + * Get structures for all tables + * + * @var SimpleXMLElement $struct + */ + $struct = $xml->children($namespaces['pma']); + + $create = array(); + + /** @var SimpleXMLElement $val1 */ + foreach ($struct as $val1) { + /** @var SimpleXMLElement $val2 */ + foreach ($val1 as $val2) { + // Need to select the correct database for the creation of + // tables, views, triggers, etc. + /** + * @todo Generating a USE here blocks importing of a table + * into another database. + */ + $attrs = $val2->attributes(); + $create[] = "USE " + . Util::backquote( + $attrs["name"] + ); + + foreach ($val2 as $val3) { + /** + * Remove the extra cosmetic spacing + */ + $val3 = str_replace(" ", "", (string)$val3); + $create[] = $val3; + } + } + } + + $struct_present = true; + } + + /** + * Move down the XML tree to the actual data + */ + $xml = $xml->children() + ->children(); + + $data_present = false; + + /** + * Only attempt to analyze/collect data if there is data present + */ + if ($xml && @count($xml->children())) { + $data_present = true; + + /** + * Process all database content + */ + foreach ($xml as $v1) { + $tbl_attr = $v1->attributes(); + + $isInTables = false; + $num_tables = count($tables); + for ($i = 0; $i < $num_tables; ++$i) { + if (!strcmp($tables[$i][Import::TBL_NAME], (string)$tbl_attr['name'])) { + $isInTables = true; + break; + } + } + + if (!$isInTables) { + $tables[] = array((string)$tbl_attr['name']); + } + + foreach ($v1 as $v2) { + $row_attr = $v2->attributes(); + if (!array_search((string)$row_attr['name'], $tempRow)) { + $tempRow[] = (string)$row_attr['name']; + } + $tempCells[] = (string)$v2; + } + + $rows[] = array((string)$tbl_attr['name'], $tempRow, $tempCells); + + $tempRow = array(); + $tempCells = array(); + } + + unset($tempRow); + unset($tempCells); + unset($xml); + + /** + * Bring accumulated rows into the corresponding table + */ + $num_tables = count($tables); + for ($i = 0; $i < $num_tables; ++$i) { + $num_rows = count($rows); + for ($j = 0; $j < $num_rows; ++$j) { + if (!strcmp($tables[$i][Import::TBL_NAME], $rows[$j][Import::TBL_NAME])) { + if (!isset($tables[$i][Import::COL_NAMES])) { + $tables[$i][] = $rows[$j][Import::COL_NAMES]; + } + + $tables[$i][Import::ROWS][] = $rows[$j][Import::ROWS]; + } + } + } + + unset($rows); + + if (!$struct_present) { + $analyses = array(); + + $len = count($tables); + for ($i = 0; $i < $len; ++$i) { + $analyses[] = Import::analyzeTable($tables[$i]); + } + } + } + + unset($xml); + unset($tempCells); + unset($rows); + + /** + * Only build SQL from data if there is data present + */ + if ($data_present) { + /** + * Set values to NULL if they were not present + * to maintain Import::buildSql() call integrity + */ + if (!isset($analyses)) { + $analyses = null; + if (!$struct_present) { + $create = null; + } + } + } + + /** + * string $db_name (no backquotes) + * + * array $table = array(table_name, array() column_names, array()() rows) + * array $tables = array of "$table"s + * + * array $analysis = array(array() column_types, array() column_sizes) + * array $analyses = array of "$analysis"s + * + * array $create = array of SQL strings + * + * array $options = an associative array of options + */ + + /* Set database name to the currently selected one, if applicable */ + if (strlen($db)) { + /* Override the database name in the XML file, if one is selected */ + $db_name = $db; + $options = array('create_db' => false); + } else { + if ($db_name === null) { + $db_name = 'XML_DB'; + } + + /* Set database collation/charset */ + $options = array( + 'db_collation' => $collation, + 'db_charset' => $charset, + ); + } + + /* Created and execute necessary SQL statements from data */ + Import::buildSql($db_name, $tables, $analyses, $create, $options, $sql_data); + + unset($analyses); + unset($tables); + unset($create); + + /* Commit any possible data in buffers */ + Import::runQuery('', '', $sql_data); + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Plugins/Import/README b/php/apps/phpmyadmin49/libraries/classes/Plugins/Import/README new file mode 100644 index 00000000..20b856ee --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Plugins/Import/README @@ -0,0 +1,153 @@ +This directory holds import plugins for phpMyAdmin. Any new plugin should +basically follow the structure presented here. The messages must use our +gettext mechanism, see https://wiki.phpmyadmin.net/pma/Gettext_for_developers. + +setProperties(); + } + + /** + * Sets the import plugin properties. + * Called in the constructor. + * + * @return void + */ + protected function setProperties() + { + $importPluginProperties = new PhpMyAdmin\Properties\Plugins\ImportPluginProperties(); + $importPluginProperties->setText('[name]'); // the name of your plug-in + $importPluginProperties->setExtension('[ext]'); // extension this plug-in can handle + $importPluginProperties->setOptionsText(__('Options')); + + // create the root group that will be the options field for + // $importPluginProperties + // this will be shown as "Format specific options" + $importSpecificOptions = new + PhpMyAdmin\Properties\Options\Groups\OptionsPropertyRootGroup( + "Format Specific Options" + ); + + // general options main group + $generalOptions = new PhpMyAdmin\Properties\Options\Groups\OptionsPropertyMainGroup( + "general_opts" + ); + + // optional : + // create primary items and add them to the group + // type - one of the classes listed in libraries/properties/options/items/ + // name - form element name + // text - description in GUI + // size - size of text element + // len - maximal size of input + // values - possible values of the item + $leaf = new PhpMyAdmin\Properties\Options\Items\RadioPropertyItem( + "structure_or_data" + ); + $leaf->setValues( + array( + 'structure' => __('structure'), + 'data' => __('data'), + 'structure_and_data' => __('structure and data') + ) + ); + $generalOptions->addProperty($leaf); + + // add the main group to the root group + $importSpecificOptions->addProperty($generalOptions); + + // set the options for the import plugin property item + $importPluginProperties->setOptions($importSpecificOptions); + $this->properties = $importPluginProperties; + } + + /** + * Handles the whole import logic + * + * @param array &$sql_data 2-element array with sql data + * + * @return void + */ + public function doImport(&$sql_data = array()) + { + // get globals (others are optional) + global $error, $timeout_passed, $finished; + + $buffer = ''; + while (! ($finished && $i >= $len) && ! $error && ! $timeout_passed) { + $data = Import::getNextChunk(); + if ($data === false) { + // subtract data we didn't handle yet and stop processing + $GLOBALS['offset'] -= strlen($buffer); + break; + } elseif ($data === true) { + // Handle rest of buffer + } else { + // Append new data to buffer + $buffer .= $data; + } + // PARSE $buffer here, post sql queries using: + Import::runQuery($sql, $verbose_sql_with_comments, $sql_data); + } // End of import loop + // Commit any possible data in buffers + Import::runQuery('', '', $sql_data); + } + + + // optional: + /* ~~~~~~~~~~~~~~~~~~~~ Getters and Setters ~~~~~~~~~~~~~~~~~~~~ */ + + + /** + * Getter description + * + * @return type + */ + private function _getMyOptionalVariable() + { + return $this->_myOptionalVariable; + } + + /** + * Setter description + * + * @param type $my_optional_variable description + * + * @return void + */ + private function _setMyOptionalVariable($my_optional_variable) + { + $this->_myOptionalVariable = $my_optional_variable; + } +} +?> diff --git a/php/apps/phpmyadmin49/libraries/classes/Plugins/Import/ShapeFileImport.php b/php/apps/phpmyadmin49/libraries/classes/Plugins/Import/ShapeFileImport.php new file mode 100644 index 00000000..eadb52e1 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Plugins/Import/ShapeFileImport.php @@ -0,0 +1,44 @@ + $id, + 'finished' => false, + 'percent' => 0, + 'total' => 0, + 'complete' => 0, + 'plugin' => UploadApc::getIdKey(), + ); + } + $ret = $_SESSION[$SESSION_KEY][$id]; + + if (!ImportAjax::apcCheck() || $ret['finished']) { + return $ret; + } + $status = apc_fetch('upload_' . $id); + + if ($status) { + $ret['finished'] = (bool)$status['done']; + $ret['total'] = $status['total']; + $ret['complete'] = $status['current']; + + if ($ret['total'] > 0) { + $ret['percent'] = $ret['complete'] / $ret['total'] * 100; + } + + if ($ret['percent'] == 100) { + $ret['finished'] = (bool)true; + } + + $_SESSION[$SESSION_KEY][$id] = $ret; + } + + return $ret; + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Plugins/Import/Upload/UploadNoplugin.php b/php/apps/phpmyadmin49/libraries/classes/Plugins/Import/Upload/UploadNoplugin.php new file mode 100644 index 00000000..b2539fe9 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Plugins/Import/Upload/UploadNoplugin.php @@ -0,0 +1,60 @@ + $id, + 'finished' => false, + 'percent' => 0, + 'total' => 0, + 'complete' => 0, + 'plugin' => UploadNoplugin::getIdKey(), + ); + } + $ret = $_SESSION[$SESSION_KEY][$id]; + + return $ret; + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Plugins/Import/Upload/UploadProgress.php b/php/apps/phpmyadmin49/libraries/classes/Plugins/Import/Upload/UploadProgress.php new file mode 100644 index 00000000..bc68c73d --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Plugins/Import/Upload/UploadProgress.php @@ -0,0 +1,92 @@ + $id, + 'finished' => false, + 'percent' => 0, + 'total' => 0, + 'complete' => 0, + 'plugin' => UploadProgress::getIdKey(), + ); + } + $ret = $_SESSION[$SESSION_KEY][$id]; + + if (!ImportAjax::progressCheck() || $ret['finished']) { + return $ret; + } + + $status = uploadprogress_get_info($id); + + if ($status) { + if ($status['bytes_uploaded'] == $status['bytes_total']) { + $ret['finished'] = true; + } else { + $ret['finished'] = false; + } + $ret['total'] = $status['bytes_total']; + $ret['complete'] = $status['bytes_uploaded']; + + if ($ret['total'] > 0) { + $ret['percent'] = $ret['complete'] / $ret['total'] * 100; + } + } else { + $ret = array( + 'id' => $id, + 'finished' => true, + 'percent' => 100, + 'total' => $ret['total'], + 'complete' => $ret['total'], + 'plugin' => UploadProgress::getIdKey(), + ); + } + + $_SESSION[$SESSION_KEY][$id] = $ret; + + return $ret; + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Plugins/Import/Upload/UploadSession.php b/php/apps/phpmyadmin49/libraries/classes/Plugins/Import/Upload/UploadSession.php new file mode 100644 index 00000000..5c2ded03 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Plugins/Import/Upload/UploadSession.php @@ -0,0 +1,93 @@ + $id, + 'finished' => false, + 'percent' => 0, + 'total' => 0, + 'complete' => 0, + 'plugin' => UploadSession::getIdKey(), + ); + } + $ret = $_SESSION[$SESSION_KEY][$id]; + + if (!ImportAjax::sessionCheck() || $ret['finished']) { + return $ret; + } + + $status = false; + $sessionkey = ini_get('session.upload_progress.prefix') . $id; + + if (isset($_SESSION[$sessionkey])) { + $status = $_SESSION[$sessionkey]; + } + + if ($status) { + $ret['finished'] = $status['done']; + $ret['total'] = $status['content_length']; + $ret['complete'] = $status['bytes_processed']; + + if ($ret['total'] > 0) { + $ret['percent'] = $ret['complete'] / $ret['total'] * 100; + } + } else { + $ret = array( + 'id' => $id, + 'finished' => true, + 'percent' => 100, + 'total' => $ret['total'], + 'complete' => $ret['total'], + 'plugin' => UploadSession::getIdKey(), + ); + } + + $_SESSION[$SESSION_KEY][$id] = $ret; + + return $ret; + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Plugins/ImportPlugin.php b/php/apps/phpmyadmin49/libraries/classes/Plugins/ImportPlugin.php new file mode 100644 index 00000000..42ed2286 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Plugins/ImportPlugin.php @@ -0,0 +1,76 @@ +properties; + } + + /** + * Sets the export plugins properties and is implemented by each import + * plugin + * + * @return void + */ + abstract protected function setProperties(); + + /** + * Define DB name and options + * + * @param string $currentDb DB + * @param string $defaultDb Default DB name + * + * @return array DB name and options (an associative array of options) + */ + protected function getDbnameAndOptions($currentDb, $defaultDb) + { + if (strlen($currentDb) > 0) { + $db_name = $currentDb; + $options = array('create_db' => false); + } else { + $db_name = $defaultDb; + $options = null; + } + + return array($db_name, $options); + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Plugins/Schema/Dia/Dia.php b/php/apps/phpmyadmin49/libraries/classes/Plugins/Schema/Dia/Dia.php new file mode 100644 index 00000000..b1fe5027 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Plugins/Schema/Dia/Dia.php @@ -0,0 +1,188 @@ +openMemory(); + /* + * Set indenting using three spaces, + * so output is formatted + */ + $this->setIndent(true); + $this->setIndentString(' '); + /* + * Create the XML document + */ + $this->startDocument('1.0', 'UTF-8'); + } + + /** + * Starts Dia Document + * + * dia document starts by first initializing dia:diagram tag + * then dia:diagramdata contains all the attributes that needed + * to define the document, then finally a Layer starts which + * holds all the objects. + * + * @param string $paper the size of the paper/document + * @param float $topMargin top margin of the paper/document in cm + * @param float $bottomMargin bottom margin of the paper/document in cm + * @param float $leftMargin left margin of the paper/document in cm + * @param float $rightMargin right margin of the paper/document in cm + * @param string $orientation orientation of the document, portrait or landscape + * + * @return void + * + * @access public + * @see XMLWriter::startElement(),XMLWriter::writeAttribute(), + * XMLWriter::writeRaw() + */ + public function startDiaDoc( + $paper, + $topMargin, + $bottomMargin, + $leftMargin, + $rightMargin, + $orientation + ) { + if ($orientation == 'P') { + $isPortrait = 'true'; + } else { + $isPortrait = 'false'; + } + $this->startElement('dia:diagram'); + $this->writeAttribute('xmlns:dia', 'http://www.lysator.liu.se/~alla/dia/'); + $this->startElement('dia:diagramdata'); + $this->writeRaw( + ' + + + + + + + + + #' . $paper . '# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ' + ); + $this->endElement(); + $this->startElement('dia:layer'); + $this->writeAttribute('name', 'Background'); + $this->writeAttribute('visible', 'true'); + $this->writeAttribute('active', 'true'); + } + + /** + * Ends Dia Document + * + * @return void + * @access public + * @see XMLWriter::endElement(),XMLWriter::endDocument() + */ + public function endDiaDoc() + { + $this->endElement(); + $this->endDocument(); + } + + /** + * Output Dia Document for download + * + * @param string $fileName name of the dia document + * + * @return void + * @access public + * @see XMLWriter::flush() + */ + public function showOutput($fileName) + { + if (ob_get_clean()) { + ob_end_clean(); + } + $output = $this->flush(); + Response::getInstance()->disable(); + Core::downloadHeader( + $fileName, + 'application/x-dia-diagram', + strlen($output) + ); + print $output; + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Plugins/Schema/Dia/DiaRelationSchema.php b/php/apps/phpmyadmin49/libraries/classes/Plugins/Schema/Dia/DiaRelationSchema.php new file mode 100644 index 00000000..8797961c --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Plugins/Schema/Dia/DiaRelationSchema.php @@ -0,0 +1,228 @@ +setShowColor(isset($_REQUEST['dia_show_color'])); + $this->setShowKeys(isset($_REQUEST['dia_show_keys'])); + $this->setOrientation($_REQUEST['dia_orientation']); + $this->setPaper($_REQUEST['dia_paper']); + + $this->diagram->startDiaDoc( + $this->paper, + $this->_topMargin, + $this->_bottomMargin, + $this->_leftMargin, + $this->_rightMargin, + $this->orientation + ); + + $alltables = $this->getTablesFromRequest(); + + foreach ($alltables as $table) { + if (!isset($this->tables[$table])) { + $this->_tables[$table] = new TableStatsDia( + $this->diagram, $this->db, $table, $this->pageNumber, + $this->showKeys, $this->offline + ); + } + } + + $seen_a_relation = false; + foreach ($alltables as $one_table) { + $exist_rel = $this->relation->getForeigners($this->db, $one_table, '', 'both'); + if (!$exist_rel) { + continue; + } + + $seen_a_relation = true; + foreach ($exist_rel as $master_field => $rel) { + /* put the foreign table on the schema only if selected + * by the user + * (do not use array_search() because we would have to + * to do a === false and this is not PHP3 compatible) + */ + if ($master_field != 'foreign_keys_data') { + if (in_array($rel['foreign_table'], $alltables)) { + $this->_addRelation( + $one_table, + $master_field, + $rel['foreign_table'], + $rel['foreign_field'], + $this->showKeys + ); + } + continue; + } + + foreach ($rel as $one_key) { + if (!in_array($one_key['ref_table_name'], $alltables)) { + continue; + } + + foreach ($one_key['index_list'] as $index => $one_field) { + $this->_addRelation( + $one_table, + $one_field, + $one_key['ref_table_name'], + $one_key['ref_index_list'][$index], + $this->showKeys + ); + } + } + } + } + $this->_drawTables(); + + if ($seen_a_relation) { + $this->_drawRelations(); + } + $this->diagram->endDiaDoc(); + } + + /** + * Output Dia Document for download + * + * @return void + * @access public + */ + public function showOutput() + { + $this->diagram->showOutput($this->getFileName('.dia')); + } + + /** + * Defines relation objects + * + * @param string $masterTable The master table name + * @param string $masterField The relation field in the master table + * @param string $foreignTable The foreign table name + * @param string $foreignField The relation field in the foreign table + * @param bool $showKeys Whether to display ONLY keys or not + * + * @return void + * + * @access private + * @see TableStatsDia::__construct(),RelationStatsDia::__construct() + */ + private function _addRelation( + $masterTable, + $masterField, + $foreignTable, + $foreignField, + $showKeys + ) { + if (!isset($this->_tables[$masterTable])) { + $this->_tables[$masterTable] = new TableStatsDia( + $this->diagram, $this->db, $masterTable, $this->pageNumber, $showKeys + ); + } + if (!isset($this->_tables[$foreignTable])) { + $this->_tables[$foreignTable] = new TableStatsDia( + $this->diagram, + $this->db, + $foreignTable, + $this->pageNumber, + $showKeys + ); + } + $this->_relations[] = new RelationStatsDia( + $this->diagram, + $this->_tables[$masterTable], + $masterField, + $this->_tables[$foreignTable], + $foreignField + ); + } + + /** + * Draws relation references + * + * connects master table's master field to + * foreign table's foreign field using Dia object + * type Database - Reference + * + * @return void + * + * @access private + * @see RelationStatsDia::relationDraw() + */ + private function _drawRelations() + { + foreach ($this->_relations as $relation) { + $relation->relationDraw($this->showColor); + } + } + + /** + * Draws tables + * + * Tables are generated using Dia object type Database - Table + * primary fields are underlined and bold in tables + * + * @return void + * + * @access private + * @see TableStatsDia::tableDraw() + */ + private function _drawTables() + { + foreach ($this->_tables as $table) { + $table->tableDraw($this->showColor); + } + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Plugins/Schema/Dia/RelationStatsDia.php b/php/apps/phpmyadmin49/libraries/classes/Plugins/Schema/Dia/RelationStatsDia.php new file mode 100644 index 00000000..d645f667 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Plugins/Schema/Dia/RelationStatsDia.php @@ -0,0 +1,214 @@ +diagram = $diagram; + $src_pos = $this->_getXy($master_table, $master_field); + $dest_pos = $this->_getXy($foreign_table, $foreign_field); + $this->srcConnPointsLeft = $src_pos[0]; + $this->srcConnPointsRight = $src_pos[1]; + $this->destConnPointsLeft = $dest_pos[0]; + $this->destConnPointsRight = $dest_pos[1]; + $this->masterTablePos = $src_pos[2]; + $this->foreignTablePos = $dest_pos[2]; + $this->masterTableId = $master_table->tableId; + $this->foreignTableId = $foreign_table->tableId; + } + + /** + * Each Table object have connection points + * which is used to connect to other objects in Dia + * we detect the position of key in fields and + * then determines its left and right connection + * points. + * + * @param string $table The current table name + * @param string $column The relation column name + * + * @return array Table right,left connection points and key position + * + * @access private + */ + private function _getXy($table, $column) + { + $pos = array_search($column, $table->fields); + // left, right, position + $value = 12; + if ($pos != 0) { + return array($pos + $value + $pos, $pos + $value + $pos + 1, $pos); + } + return array($pos + $value , $pos + $value + 1, $pos); + } + + /** + * Draws relation references + * + * connects master table's master field to foreign table's + * foreign field using Dia object type Database - Reference + * Dia object is used to generate the XML of Dia Document. + * Database reference Object and their attributes are involved + * in the combination of displaying Database - reference on Dia Document. + * + * @param boolean $showColor Whether to use one color per relation or not + * if showColor is true then an array of $listOfColors + * will be used to choose the random colors for + * references lines. we can change/add more colors to + * this + * + * @return boolean|void + * + * @access public + * @see PDF + */ + public function relationDraw($showColor) + { + ++DiaRelationSchema::$objectId; + /* + * if source connection points and destination connection + * points are same then return it false and don't draw that + * relation + */ + if ($this->srcConnPointsRight == $this->destConnPointsRight) { + if ($this->srcConnPointsLeft == $this->destConnPointsLeft) { + return false; + } + } + + if ($showColor) { + $listOfColors = array( + 'FF0000', + '000099', + '00FF00', + ); + shuffle($listOfColors); + $this->referenceColor = '#' . $listOfColors[0] . ''; + } else { + $this->referenceColor = '#000000'; + } + + $this->diagram->writeRaw( + ' + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #1# + + + #n# + + + + + + + + + + + + ' + ); + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Plugins/Schema/Dia/TableStatsDia.php b/php/apps/phpmyadmin49/libraries/classes/Plugins/Schema/Dia/TableStatsDia.php new file mode 100644 index 00000000..470f762c --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Plugins/Schema/Dia/TableStatsDia.php @@ -0,0 +1,229 @@ +tableId = ++DiaRelationSchema::$objectId; + } + + /** + * Displays an error when the table cannot be found. + * + * @return void + */ + protected function showMissingTableError() + { + ExportRelationSchema::dieSchema( + $this->pageNumber, + "DIA", + sprintf(__('The %s table doesn\'t exist!'), $this->tableName) + ); + } + + /** + * Do draw the table + * + * Tables are generated using object type Database - Table + * primary fields are underlined in tables. Dia object + * is used to generate the XML of Dia Document. Database Table + * Object and their attributes are involved in the combination + * of displaying Database - Table on Dia Document. + * + * @param boolean $showColor Whether to show color for tables text or not + * if showColor is true then an array of $listOfColors + * will be used to choose the random colors for tables + * text we can change/add more colors to this array + * + * @return void + * + * @access public + * @see Dia + */ + public function tableDraw($showColor) + { + if ($showColor) { + $listOfColors = array( + 'FF0000', + '000099', + '00FF00' + ); + shuffle($listOfColors); + $this->tableColor = '#' . $listOfColors[0] . ''; + } else { + $this->tableColor = '#000000'; + } + + $factor = 0.1; + + $this->diagram->startElement('dia:object'); + $this->diagram->writeAttribute('type', 'Database - Table'); + $this->diagram->writeAttribute('version', '0'); + $this->diagram->writeAttribute('id', '' . $this->tableId . ''); + $this->diagram->writeRaw( + ' + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #' . $this->tableName . '# + + + ## + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ' + ); + + $this->diagram->startElement('dia:attribute'); + $this->diagram->writeAttribute('name', 'attributes'); + + foreach ($this->fields as $field) { + $this->diagram->writeRaw( + ' + + #' . $field . '# + + + ## + + + ## + ' + ); + unset($pm); + $pm = 'false'; + if (in_array($field, $this->primary)) { + $pm = 'true'; + } + if ($field == $this->displayfield) { + $pm = 'false'; + } + $this->diagram->writeRaw( + ' + + + + + + + + + ' + ); + } + $this->diagram->endElement(); + $this->diagram->endElement(); + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Plugins/Schema/Eps/Eps.php b/php/apps/phpmyadmin49/libraries/classes/Plugins/Schema/Eps/Eps.php new file mode 100644 index 00000000..e71b1357 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Plugins/Schema/Eps/Eps.php @@ -0,0 +1,278 @@ +stringCommands = ""; + $this->stringCommands .= "%!PS-Adobe-3.0 EPSF-3.0 \n"; + } + + /** + * Set document title + * + * @param string $value sets the title text + * + * @return void + */ + public function setTitle($value) + { + $this->stringCommands .= '%%Title: ' . $value . "\n"; + } + + /** + * Set document author + * + * @param string $value sets the author + * + * @return void + */ + public function setAuthor($value) + { + $this->stringCommands .= '%%Creator: ' . $value . "\n"; + } + + /** + * Set document creation date + * + * @param string $value sets the date + * + * @return void + */ + public function setDate($value) + { + $this->stringCommands .= '%%CreationDate: ' . $value . "\n"; + } + + /** + * Set document orientation + * + * @param string $orientation sets the orientation + * + * @return void + */ + public function setOrientation($orientation) + { + $this->stringCommands .= "%%PageOrder: Ascend \n"; + if ($orientation == "L") { + $orientation = "Landscape"; + $this->stringCommands .= '%%Orientation: ' . $orientation . "\n"; + } else { + $orientation = "Portrait"; + $this->stringCommands .= '%%Orientation: ' . $orientation . "\n"; + } + $this->stringCommands .= "%%EndComments \n"; + $this->stringCommands .= "%%Pages 1 \n"; + $this->stringCommands .= "%%BoundingBox: 72 150 144 170 \n"; + } + + /** + * Set the font and size + * + * font can be set whenever needed in EPS + * + * @param string $value sets the font name e.g Arial + * @param integer $size sets the size of the font e.g 10 + * + * @return void + */ + public function setFont($value, $size) + { + $this->font = $value; + $this->fontSize = $size; + $this->stringCommands .= "/" . $value . " findfont % Get the basic font\n"; + $this->stringCommands .= "" + . $size . " scalefont % Scale the font to $size points\n"; + $this->stringCommands + .= "setfont % Make it the current font\n"; + } + + /** + * Get the font + * + * @return string return the font name e.g Arial + */ + public function getFont() + { + return $this->font; + } + + /** + * Get the font Size + * + * @return string return the size of the font e.g 10 + */ + public function getFontSize() + { + return $this->fontSize; + } + + /** + * Draw the line + * + * drawing the lines from x,y source to x,y destination and set the + * width of the line. lines helps in showing relationships of tables + * + * @param integer $x_from The x_from attribute defines the start + * left position of the element + * @param integer $y_from The y_from attribute defines the start + * right position of the element + * @param integer $x_to The x_to attribute defines the end + * left position of the element + * @param integer $y_to The y_to attribute defines the end + * right position of the element + * @param integer $lineWidth Sets the width of the line e.g 2 + * + * @return void + */ + public function line( + $x_from = 0, + $y_from = 0, + $x_to = 0, + $y_to = 0, + $lineWidth = 0 + ) { + $this->stringCommands .= $lineWidth . " setlinewidth \n"; + $this->stringCommands .= $x_from . ' ' . $y_from . " moveto \n"; + $this->stringCommands .= $x_to . ' ' . $y_to . " lineto \n"; + $this->stringCommands .= "stroke \n"; + } + + /** + * Draw the rectangle + * + * drawing the rectangle from x,y source to x,y destination and set the + * width of the line. rectangles drawn around the text shown of fields + * + * @param integer $x_from The x_from attribute defines the start + * left position of the element + * @param integer $y_from The y_from attribute defines the start + * right position of the element + * @param integer $x_to The x_to attribute defines the end + * left position of the element + * @param integer $y_to The y_to attribute defines the end + * right position of the element + * @param integer $lineWidth Sets the width of the line e.g 2 + * + * @return void + */ + public function rect($x_from, $y_from, $x_to, $y_to, $lineWidth) + { + $this->stringCommands .= $lineWidth . " setlinewidth \n"; + $this->stringCommands .= "newpath \n"; + $this->stringCommands .= $x_from . " " . $y_from . " moveto \n"; + $this->stringCommands .= "0 " . $y_to . " rlineto \n"; + $this->stringCommands .= $x_to . " 0 rlineto \n"; + $this->stringCommands .= "0 -" . $y_to . " rlineto \n"; + $this->stringCommands .= "closepath \n"; + $this->stringCommands .= "stroke \n"; + } + + /** + * Set the current point + * + * The moveto operator takes two numbers off the stack and treats + * them as x and y coordinates to which to move. The coordinates + * specified become the current point. + * + * @param integer $x The x attribute defines the left position of the element + * @param integer $y The y attribute defines the right position of the element + * + * @return void + */ + public function moveTo($x, $y) + { + $this->stringCommands .= $x . ' ' . $y . " moveto \n"; + } + + /** + * Output/Display the text + * + * @param string $text The string to be displayed + * + * @return void + */ + public function show($text) + { + $this->stringCommands .= '(' . $text . ") show \n"; + } + + /** + * Output the text at specified co-ordinates + * + * @param string $text String to be displayed + * @param integer $x X attribute defines the left position of the element + * @param integer $y Y attribute defines the right position of the element + * + * @return void + */ + public function showXY($text, $x, $y) + { + $this->moveTo($x, $y); + $this->show($text); + } + + /** + * Ends EPS Document + * + * @return void + */ + public function endEpsDoc() + { + $this->stringCommands .= "showpage \n"; + } + + /** + * Output EPS Document for download + * + * @param string $fileName name of the eps document + * + * @return void + */ + public function showOutput($fileName) + { + // if(ob_get_clean()){ + //ob_end_clean(); + //} + $output = $this->stringCommands; + Response::getInstance() + ->disable(); + Core::downloadHeader( + $fileName, + 'image/x-eps', + strlen($output) + ); + print $output; + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Plugins/Schema/Eps/EpsRelationSchema.php b/php/apps/phpmyadmin49/libraries/classes/Plugins/Schema/Eps/EpsRelationSchema.php new file mode 100644 index 00000000..6056b157 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Plugins/Schema/Eps/EpsRelationSchema.php @@ -0,0 +1,224 @@ +setShowColor(isset($_REQUEST['eps_show_color'])); + $this->setShowKeys(isset($_REQUEST['eps_show_keys'])); + $this->setTableDimension(isset($_REQUEST['eps_show_table_dimension'])); + $this->setAllTablesSameWidth(isset($_REQUEST['eps_all_tables_same_width'])); + $this->setOrientation($_REQUEST['eps_orientation']); + + $this->diagram->setTitle( + sprintf( + __('Schema of the %s database - Page %s'), + $this->db, + $this->pageNumber + ) + ); + $this->diagram->setAuthor('phpMyAdmin ' . PMA_VERSION); + $this->diagram->setDate(date("j F Y, g:i a")); + $this->diagram->setOrientation($this->orientation); + $this->diagram->setFont('Verdana', '10'); + + $alltables = $this->getTablesFromRequest(); + + foreach ($alltables as $table) { + if (! isset($this->_tables[$table])) { + $this->_tables[$table] = new TableStatsEps( + $this->diagram, $this->db, + $table, $this->diagram->getFont(), + $this->diagram->getFontSize(), $this->pageNumber, + $this->_tablewidth, $this->showKeys, + $this->tableDimension, $this->offline + ); + } + + if ($this->sameWide) { + $this->_tables[$table]->width = $this->_tablewidth; + } + } + + $seen_a_relation = false; + foreach ($alltables as $one_table) { + $exist_rel = $this->relation->getForeigners($this->db, $one_table, '', 'both'); + if (!$exist_rel) { + continue; + } + + $seen_a_relation = true; + foreach ($exist_rel as $master_field => $rel) { + /* put the foreign table on the schema only if selected + * by the user + * (do not use array_search() because we would have to + * to do a === false and this is not PHP3 compatible) + */ + if ($master_field != 'foreign_keys_data') { + if (in_array($rel['foreign_table'], $alltables)) { + $this->_addRelation( + $one_table, $this->diagram->getFont(), $this->diagram->getFontSize(), + $master_field, $rel['foreign_table'], + $rel['foreign_field'], $this->tableDimension + ); + } + continue; + } + + foreach ($rel as $one_key) { + if (!in_array($one_key['ref_table_name'], $alltables)) { + continue; + } + + foreach ($one_key['index_list'] + as $index => $one_field + ) { + $this->_addRelation( + $one_table, $this->diagram->getFont(), + $this->diagram->getFontSize(), + $one_field, $one_key['ref_table_name'], + $one_key['ref_index_list'][$index], + $this->tableDimension + ); + } + } + } + } + if ($seen_a_relation) { + $this->_drawRelations(); + } + + $this->_drawTables(); + $this->diagram->endEpsDoc(); + } + + /** + * Output Eps Document for download + * + * @return void + */ + public function showOutput() + { + $this->diagram->showOutput($this->getFileName('.eps')); + } + + /** + * Defines relation objects + * + * @param string $masterTable The master table name + * @param string $font The font + * @param int $fontSize The font size + * @param string $masterField The relation field in the master table + * @param string $foreignTable The foreign table name + * @param string $foreignField The relation field in the foreign table + * @param boolean $tableDimension Whether to display table position or not + * + * @return void + * + * @see _setMinMax,Table_Stats_Eps::__construct(), + * PhpMyAdmin\Plugins\Schema\Eps\RelationStatsEps::__construct() + */ + private function _addRelation( + $masterTable, $font, $fontSize, $masterField, + $foreignTable, $foreignField, $tableDimension + ) { + if (! isset($this->_tables[$masterTable])) { + $this->_tables[$masterTable] = new TableStatsEps( + $this->diagram, $this->db, $masterTable, $font, $fontSize, + $this->pageNumber, $this->_tablewidth, false, $tableDimension + ); + } + if (! isset($this->_tables[$foreignTable])) { + $this->_tables[$foreignTable] = new TableStatsEps( + $this->diagram, $this->db, $foreignTable, $font, $fontSize, + $this->pageNumber, $this->_tablewidth, false, $tableDimension + ); + } + $this->_relations[] = new RelationStatsEps( + $this->diagram, + $this->_tables[$masterTable], + $masterField, + $this->_tables[$foreignTable], + $foreignField + ); + } + + /** + * Draws relation arrows and lines connects master table's master field to + * foreign table's foreign field + * + * @return void + * + * @see Relation_Stats_Eps::relationDraw() + */ + private function _drawRelations() + { + foreach ($this->_relations as $relation) { + $relation->relationDraw(); + } + } + + /** + * Draws tables + * + * @return void + * + * @see Table_Stats_Eps::Table_Stats_tableDraw() + */ + private function _drawTables() + { + foreach ($this->_tables as $table) { + $table->tableDraw($this->showColor); + } + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Plugins/Schema/Eps/RelationStatsEps.php b/php/apps/phpmyadmin49/libraries/classes/Plugins/Schema/Eps/RelationStatsEps.php new file mode 100644 index 00000000..61b7f6c2 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Plugins/Schema/Eps/RelationStatsEps.php @@ -0,0 +1,110 @@ +wTick = 10; + parent::__construct( + $diagram, $master_table, $master_field, $foreign_table, $foreign_field + ); + $this->ySrc += 10; + $this->yDest += 10; + } + + /** + * draws relation links and arrows + * shows foreign key relations + * + * @see PMA_EPS + * + * @return void + */ + public function relationDraw() + { + // draw a line like -- to foreign field + $this->diagram->line( + $this->xSrc, + $this->ySrc, + $this->xSrc + $this->srcDir * $this->wTick, + $this->ySrc, + 1 + ); + // draw a line like -- to master field + $this->diagram->line( + $this->xDest + $this->destDir * $this->wTick, + $this->yDest, + $this->xDest, + $this->yDest, + 1 + ); + // draw a line that connects to master field line and foreign field line + $this->diagram->line( + $this->xSrc + $this->srcDir * $this->wTick, + $this->ySrc, + $this->xDest + $this->destDir * $this->wTick, + $this->yDest, + 1 + ); + $root2 = 2 * sqrt(2); + $this->diagram->line( + $this->xSrc + $this->srcDir * $this->wTick * 0.75, + $this->ySrc, + $this->xSrc + $this->srcDir * (0.75 - 1 / $root2) * $this->wTick, + $this->ySrc + $this->wTick / $root2, + 1 + ); + $this->diagram->line( + $this->xSrc + $this->srcDir * $this->wTick * 0.75, + $this->ySrc, + $this->xSrc + $this->srcDir * (0.75 - 1 / $root2) * $this->wTick, + $this->ySrc - $this->wTick / $root2, + 1 + ); + $this->diagram->line( + $this->xDest + $this->destDir * $this->wTick / 2, + $this->yDest, + $this->xDest + $this->destDir * (0.5 + 1 / $root2) * $this->wTick, + $this->yDest + $this->wTick / $root2, + 1 + ); + $this->diagram->line( + $this->xDest + $this->destDir * $this->wTick / 2, + $this->yDest, + $this->xDest + $this->destDir * (0.5 + 1 / $root2) * $this->wTick, + $this->yDest - $this->wTick / $root2, + 1 + ); + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Plugins/Schema/Eps/TableStatsEps.php b/php/apps/phpmyadmin49/libraries/classes/Plugins/Schema/Eps/TableStatsEps.php new file mode 100644 index 00000000..c1059dee --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Plugins/Schema/Eps/TableStatsEps.php @@ -0,0 +1,182 @@ +_setHeightTable($fontSize); + // setWidth must me after setHeight, because title + // can include table height which changes table width + $this->_setWidthTable($font, $fontSize); + if ($same_wide_width < $this->width) { + $same_wide_width = $this->width; + } + } + + /** + * Displays an error when the table cannot be found. + * + * @return void + */ + protected function showMissingTableError() + { + ExportRelationSchema::dieSchema( + $this->pageNumber, + "EPS", + sprintf(__('The %s table doesn\'t exist!'), $this->tableName) + ); + } + + /** + * Sets the width of the table + * + * @param string $font The font name + * @param integer $fontSize The font size + * + * @return void + * + * @see PMA_EPS + */ + private function _setWidthTable($font, $fontSize) + { + foreach ($this->fields as $field) { + $this->width = max( + $this->width, + Font::getStringWidth($field, $font, $fontSize) + ); + } + $this->width += Font::getStringWidth( + ' ', + $font, + $fontSize + ); + /* + * it is unknown what value must be added, because + * table title is affected by the table width value + */ + while ($this->width + < Font::getStringWidth( + $this->getTitle(), + $font, + $fontSize + )) { + $this->width += 7; + } + } + + /** + * Sets the height of the table + * + * @param integer $fontSize The font size + * + * @return void + */ + private function _setHeightTable($fontSize) + { + $this->heightCell = $fontSize + 4; + $this->height = (count($this->fields) + 1) * $this->heightCell; + } + + /** + * Draw the table + * + * @param boolean $showColor Whether to display color + * + * @return void + * + * @see PMA_EPS,PMA_EPS::line,PMA_EPS::rect + */ + public function tableDraw($showColor) + { + //echo $this->tableName.'
      '; + $this->diagram->rect( + $this->x, + $this->y + 12, + $this->width, + $this->heightCell, + 1 + ); + $this->diagram->showXY($this->getTitle(), $this->x + 5, $this->y + 14); + foreach ($this->fields as $field) { + $this->currentCell += $this->heightCell; + $this->diagram->rect( + $this->x, + $this->y + 12 + $this->currentCell, + $this->width, + $this->heightCell, + 1 + ); + $this->diagram->showXY( + $field, + $this->x + 5, + $this->y + 14 + $this->currentCell + ); + } + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Plugins/Schema/ExportRelationSchema.php b/php/apps/phpmyadmin49/libraries/classes/Plugins/Schema/ExportRelationSchema.php new file mode 100644 index 00000000..ca9ca561 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Plugins/Schema/ExportRelationSchema.php @@ -0,0 +1,307 @@ +db = $db; + $this->diagram = $diagram; + $this->setPageNumber($_REQUEST['page_number']); + $this->setOffline(isset($_REQUEST['offline_export'])); + $this->relation = new Relation(); + } + + /** + * Set Page Number + * + * @param integer $value Page Number of the document to be created + * + * @return void + */ + public function setPageNumber($value) + { + $this->pageNumber = intval($value); + } + + /** + * Returns the schema page number + * + * @return integer schema page number + */ + public function getPageNumber() + { + return $this->pageNumber; + } + + /** + * Sets showColor + * + * @param boolean $value whether to show colors + * + * @return void + */ + public function setShowColor($value) + { + $this->showColor = $value; + } + + /** + * Returns whether to show colors + * + * @return boolean whether to show colors + */ + public function isShowColor() + { + return $this->showColor; + } + + /** + * Set Table Dimension + * + * @param boolean $value show table co-ordinates or not + * + * @return void + */ + public function setTableDimension($value) + { + $this->tableDimension = $value; + } + + /** + * Returns whether to show table dimensions + * + * @return boolean whether to show table dimensions + */ + public function isTableDimension() + { + return $this->tableDimension; + } + + /** + * Set same width of All Tables + * + * @param boolean $value set same width of all tables or not + * + * @return void + */ + public function setAllTablesSameWidth($value) + { + $this->sameWide = $value; + } + + /** + * Returns whether to use same width for all tables or not + * + * @return boolean whether to use same width for all tables or not + */ + public function isAllTableSameWidth() + { + return $this->sameWide; + } + + /** + * Set Show only keys + * + * @param boolean $value show only keys or not + * + * @return void + * + * @access public + */ + public function setShowKeys($value) + { + $this->showKeys = $value; + } + + /** + * Returns whether to show keys + * + * @return boolean whether to show keys + */ + public function isShowKeys() + { + return $this->showKeys; + } + + /** + * Set Orientation + * + * @param string $value Orientation will be portrait or landscape + * + * @return void + * + * @access public + */ + public function setOrientation($value) + { + $this->orientation = ($value == 'P') ? 'P' : 'L'; + } + + /** + * Returns orientation + * + * @return string orientation + */ + public function getOrientation() + { + return $this->orientation; + } + + /** + * Set type of paper + * + * @param string $value paper type can be A4 etc + * + * @return void + * + * @access public + */ + public function setPaper($value) + { + $this->paper = $value; + } + + /** + * Returns the paper size + * + * @return string paper size + */ + public function getPaper() + { + return $this->paper; + } + + /** + * Set whether the document is generated from client side DB + * + * @param boolean $value offline or not + * + * @return void + * + * @access public + */ + public function setOffline($value) + { + $this->offline = $value; + } + + /** + * Returns whether the client side database is used + * + * @return boolean + * + * @access public + */ + public function isOffline() + { + return $this->offline; + } + + /** + * Get the table names from the request + * + * @return array an array of table names + */ + protected function getTablesFromRequest() + { + $tables = []; + if (isset($_POST['t_tbl'])) { + foreach($_POST['t_tbl'] as $table) { + $tables[] = rawurldecode($table); + } + } + return $tables; + } + + /** + * Returns the file name + * + * @param String $extension file extension + * + * @return string file name + */ + protected function getFileName($extension) + { + $filename = $this->db . $extension; + // Get the name of this page to use as filename + if ($this->pageNumber != -1 && !$this->offline) { + $_name_sql = 'SELECT page_descr FROM ' + . Util::backquote($GLOBALS['cfgRelation']['db']) . '.' + . Util::backquote($GLOBALS['cfgRelation']['pdf_pages']) + . ' WHERE page_nr = ' . $this->pageNumber; + $_name_rs = $this->relation->queryAsControlUser($_name_sql); + $_name_row = $GLOBALS['dbi']->fetchRow($_name_rs); + $filename = $_name_row[0] . $extension; + } + + return $filename; + } + + /** + * Displays an error message + * + * @param integer $pageNumber ID of the chosen page + * @param string $type Schema Type + * @param string $error_message The error message + * + * @access public + * + * @return void + */ + public static function dieSchema($pageNumber, $type = '', $error_message = '') + { + echo "

      " , __("SCHEMA ERROR: ") , $type , "

      " , "\n"; + if (!empty($error_message)) { + $error_message = htmlspecialchars($error_message); + } + echo '

      ' , "\n"; + echo ' ' , $error_message , "\n"; + echo '

      ' , "\n"; + echo '' , __('Back') , ''; + echo "\n"; + exit; + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Plugins/Schema/Pdf/Pdf.php b/php/apps/phpmyadmin49/libraries/classes/Plugins/Schema/Pdf/Pdf.php new file mode 100644 index 00000000..138c56c8 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Plugins/Schema/Pdf/Pdf.php @@ -0,0 +1,403 @@ +_pageNumber = $pageNumber; + $this->_withDoc = $withDoc; + $this->_db = $db; + $this->relation = new Relation(); + } + + /** + * Sets the value for margins + * + * @param float $c_margin margin + * + * @return void + */ + public function setCMargin($c_margin) + { + $this->cMargin = $c_margin; + } + + /** + * Sets the scaling factor, defines minimum coordinates and margins + * + * @param float|int $scale The scaling factor + * @param float|int $xMin The minimum X coordinate + * @param float|int $yMin The minimum Y coordinate + * @param float|int $leftMargin The left margin + * @param float|int $topMargin The top margin + * + * @return void + */ + public function setScale($scale = 1, $xMin = 0, $yMin = 0, + $leftMargin = -1, $topMargin = -1 + ) { + $this->scale = $scale; + $this->_xMin = $xMin; + $this->_yMin = $yMin; + if ($this->leftMargin != -1) { + $this->leftMargin = $leftMargin; + } + if ($this->topMargin != -1) { + $this->topMargin = $topMargin; + } + } + + /** + * Outputs a scaled cell + * + * @param float|int $w The cell width + * @param float|int $h The cell height + * @param string $txt The text to output + * @param mixed $border Whether to add borders or not + * @param integer $ln Where to put the cursor once the output is done + * @param string $align Align mode + * @param integer $fill Whether to fill the cell with a color or not + * @param string $link Link + * + * @return void + * + * @see TCPDF::Cell() + */ + public function cellScale($w, $h = 0, $txt = '', $border = 0, $ln = 0, + $align = '', $fill = 0, $link = '' + ) { + $h = $h / $this->scale; + $w = $w / $this->scale; + $this->Cell($w, $h, $txt, $border, $ln, $align, $fill, $link); + } + + /** + * Draws a scaled line + * + * @param float $x1 The horizontal position of the starting point + * @param float $y1 The vertical position of the starting point + * @param float $x2 The horizontal position of the ending point + * @param float $y2 The vertical position of the ending point + * + * @return void + * + * @see TCPDF::Line() + */ + public function lineScale($x1, $y1, $x2, $y2) + { + $x1 = ($x1 - $this->_xMin) / $this->scale + $this->leftMargin; + $y1 = ($y1 - $this->_yMin) / $this->scale + $this->topMargin; + $x2 = ($x2 - $this->_xMin) / $this->scale + $this->leftMargin; + $y2 = ($y2 - $this->_yMin) / $this->scale + $this->topMargin; + $this->Line($x1, $y1, $x2, $y2); + } + + /** + * Sets x and y scaled positions + * + * @param float $x The x position + * @param float $y The y position + * + * @return void + * + * @see TCPDF::SetXY() + */ + public function setXyScale($x, $y) + { + $x = ($x - $this->_xMin) / $this->scale + $this->leftMargin; + $y = ($y - $this->_yMin) / $this->scale + $this->topMargin; + $this->SetXY($x, $y); + } + + /** + * Sets the X scaled positions + * + * @param float $x The x position + * + * @return void + * + * @see TCPDF::SetX() + */ + public function setXScale($x) + { + $x = ($x - $this->_xMin) / $this->scale + $this->leftMargin; + $this->SetX($x); + } + + /** + * Sets the scaled font size + * + * @param float $size The font size (in points) + * + * @return void + * + * @see TCPDF::SetFontSize() + */ + public function setFontSizeScale($size) + { + // Set font size in points + $size = $size / $this->scale; + $this->SetFontSize($size); + } + + /** + * Sets the scaled line width + * + * @param float $width The line width + * + * @return void + * + * @see TCPDF::SetLineWidth() + */ + public function setLineWidthScale($width) + { + $width = $width / $this->scale; + $this->SetLineWidth($width); + } + + /** + * This method is used to render the page header. + * + * @return void + * + * @see TCPDF::Header() + */ + // @codingStandardsIgnoreLine + public function Header() + { + // We only show this if we find something in the new pdf_pages table + + // This function must be named "Header" to work with the TCPDF library + if ($this->_withDoc) { + if ($this->_offline || $this->_pageNumber == -1) { + $pg_name = __("PDF export page"); + } else { + $test_query = 'SELECT * FROM ' + . Util::backquote($GLOBALS['cfgRelation']['db']) . '.' + . Util::backquote($GLOBALS['cfgRelation']['pdf_pages']) + . ' WHERE db_name = \'' . $GLOBALS['dbi']->escapeString($this->_db) + . '\' AND page_nr = \'' . $this->_pageNumber . '\''; + $test_rs = $this->relation->queryAsControlUser($test_query); + $pages = @$GLOBALS['dbi']->fetchAssoc($test_rs); + $pg_name = ucfirst($pages['page_descr']); + } + + $this->SetFont($this->_ff, 'B', 14); + $this->Cell(0, 6, $pg_name, 'B', 1, 'C'); + $this->SetFont($this->_ff, ''); + $this->Ln(); + } + } + + /** + * This function must be named "Footer" to work with the TCPDF library + * + * @return void + * + * @see PDF::Footer() + */ + // @codingStandardsIgnoreLine + public function Footer() + { + if ($this->_withDoc) { + parent::Footer(); + } + } + + /** + * Sets widths + * + * @param array $w array of widths + * + * @return void + */ + public function setWidths(array $w) + { + // column widths + $this->widths = $w; + } + + /** + * Generates table row. + * + * @param array $data Data for table + * @param array $links Links for table cells + * + * @return void + */ + public function row(array $data, array $links) + { + // line height + $nb = 0; + $data_cnt = count($data); + for ($i = 0;$i < $data_cnt;$i++) { + $nb = max($nb, $this->numLines($this->widths[$i], $data[$i])); + } + $il = $this->FontSize; + $h = ($il + 1) * $nb; + // page break if necessary + $this->CheckPageBreak($h); + // draw the cells + $data_cnt = count($data); + for ($i = 0;$i < $data_cnt;$i++) { + $w = $this->widths[$i]; + // save current position + $x = $this->GetX(); + $y = $this->GetY(); + // draw the border + $this->Rect($x, $y, $w, $h); + if (isset($links[$i])) { + $this->Link($x, $y, $w, $h, $links[$i]); + } + // print text + $this->MultiCell($w, $il + 1, $data[$i], 0, 'L'); + // go to right side + $this->SetXY($x + $w, $y); + } + // go to line + $this->Ln($h); + } + + /** + * Compute number of lines used by a multicell of width w + * + * @param int $w width + * @param string $txt text + * + * @return int + */ + public function numLines($w, $txt) + { + $cw = &$this->CurrentFont['cw']; + if ($w == 0) { + $w = $this->w - $this->rMargin - $this->x; + } + $wmax = ($w-2 * $this->cMargin) * 1000 / $this->FontSize; + $s = str_replace("\r", '', $txt); + $nb = strlen($s); + if ($nb > 0 && $s[$nb-1] == "\n") { + $nb--; + } + $sep = -1; + $i = 0; + $j = 0; + $l = 0; + $nl = 1; + while ($i < $nb) { + $c = $s[$i]; + if ($c == "\n") { + $i++; + $sep = -1; + $j = $i; + $l = 0; + $nl++; + continue; + } + if ($c == ' ') { + $sep = $i; + } + $l += isset($cw[mb_ord($c)])?$cw[mb_ord($c)]:0 ; + if ($l > $wmax) { + if ($sep == -1) { + if ($i == $j) { + $i++; + } + } else { + $i = $sep + 1; + } + $sep = -1; + $j = $i; + $l = 0; + $nl++; + } else { + $i++; + } + } + return $nl; + } + + /** + * Set whether the document is generated from client side DB + * + * @param string $value whether offline + * + * @return void + * + * @access private + */ + public function setOffline($value) + { + $this->_offline = $value; + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Plugins/Schema/Pdf/PdfRelationSchema.php b/php/apps/phpmyadmin49/libraries/classes/Plugins/Schema/Pdf/PdfRelationSchema.php new file mode 100644 index 00000000..aa4fc721 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Plugins/Schema/Pdf/PdfRelationSchema.php @@ -0,0 +1,732 @@ +setShowGrid(isset($_REQUEST['pdf_show_grid'])); + $this->setShowColor(isset($_REQUEST['pdf_show_color'])); + $this->setShowKeys(isset($_REQUEST['pdf_show_keys'])); + $this->setTableDimension(isset($_REQUEST['pdf_show_table_dimension'])); + $this->setAllTablesSameWidth(isset($_REQUEST['pdf_all_tables_same_width'])); + $this->setWithDataDictionary(isset($_REQUEST['pdf_with_doc'])); + $this->setTableOrder($_REQUEST['pdf_table_order']); + $this->setOrientation($_REQUEST['pdf_orientation']); + $this->setPaper($_REQUEST['pdf_paper']); + + // Initializes a new document + parent::__construct( + $db, + new Pdf( + $this->orientation, 'mm', $this->paper, + $this->pageNumber, $this->_withDoc, $db + ) + ); + $this->diagram->SetTitle( + sprintf( + __('Schema of the %s database'), + $this->db + ) + ); + $this->diagram->setCMargin(0); + $this->diagram->Open(); + $this->diagram->SetAutoPageBreak('auto'); + $this->diagram->setOffline($this->offline); + + $alltables = $this->getTablesFromRequest(); + if ($this->getTableOrder() == 'name_asc') { + sort($alltables); + } elseif ($this->getTableOrder() == 'name_desc') { + rsort($alltables); + } + + if ($this->_withDoc) { + $this->diagram->SetAutoPageBreak('auto', 15); + $this->diagram->setCMargin(1); + $this->dataDictionaryDoc($alltables); + $this->diagram->SetAutoPageBreak('auto'); + $this->diagram->setCMargin(0); + } + + $this->diagram->Addpage(); + + if ($this->_withDoc) { + $this->diagram->SetLink($this->diagram->PMA_links['RT']['-'], -1); + $this->diagram->Bookmark(__('Relational schema')); + $this->diagram->setAlias('{00}', $this->diagram->PageNo()); + $this->_topMargin = 28; + $this->_bottomMargin = 28; + } + + /* snip */ + foreach ($alltables as $table) { + if (! isset($this->_tables[$table])) { + $this->_tables[$table] = new TableStatsPdf( + $this->diagram, + $this->db, + $table, + null, + $this->pageNumber, + $this->_tablewidth, + $this->showKeys, + $this->tableDimension, + $this->offline + ); + } + if ($this->sameWide) { + $this->_tables[$table]->width = $this->_tablewidth; + } + $this->_setMinMax($this->_tables[$table]); + } + + // Defines the scale factor + $innerWidth = $this->diagram->getPageWidth() - $this->_rightMargin + - $this->_leftMargin; + $innerHeight = $this->diagram->getPageHeight() - $this->_topMargin + - $this->_bottomMargin; + $this->_scale = ceil( + max( + ($this->_xMax - $this->_xMin) / $innerWidth, + ($this->_yMax - $this->_yMin) / $innerHeight + ) * 100 + ) / 100; + + $this->diagram->setScale( + $this->_scale, + $this->_xMin, + $this->_yMin, + $this->_leftMargin, + $this->_topMargin + ); + // Builds and save the PDF document + $this->diagram->setLineWidthScale(0.1); + + if ($this->_showGrid) { + $this->diagram->SetFontSize(10); + $this->_strokeGrid(); + } + $this->diagram->setFontSizeScale(14); + // previous logic was checking master tables and foreign tables + // but I think that looping on every table of the pdf page as a master + // and finding its foreigns is OK (then we can support innodb) + $seen_a_relation = false; + foreach ($alltables as $one_table) { + $exist_rel = $this->relation->getForeigners($this->db, $one_table, '', 'both'); + if (!$exist_rel) { + continue; + } + + $seen_a_relation = true; + foreach ($exist_rel as $master_field => $rel) { + // put the foreign table on the schema only if selected + // by the user + // (do not use array_search() because we would have to + // to do a === false and this is not PHP3 compatible) + if ($master_field != 'foreign_keys_data') { + if (in_array($rel['foreign_table'], $alltables)) { + $this->_addRelation( + $one_table, + $master_field, + $rel['foreign_table'], + $rel['foreign_field'] + ); + } + continue; + } + + foreach ($rel as $one_key) { + if (!in_array($one_key['ref_table_name'], $alltables)) { + continue; + } + + foreach ($one_key['index_list'] + as $index => $one_field + ) { + $this->_addRelation( + $one_table, + $one_field, + $one_key['ref_table_name'], + $one_key['ref_index_list'][$index] + ); + } + } + } // end while + } // end while + + if ($seen_a_relation) { + $this->_drawRelations(); + } + $this->_drawTables(); + } + + /** + * Set Show Grid + * + * @param boolean $value show grid of the document or not + * + * @return void + */ + public function setShowGrid($value) + { + $this->_showGrid = $value; + } + + /** + * Returns whether to show grid + * + * @return boolean whether to show grid + */ + public function isShowGrid() + { + return $this->_showGrid; + } + + /** + * Set Data Dictionary + * + * @param boolean $value show selected database data dictionary or not + * + * @return void + */ + public function setWithDataDictionary($value) + { + $this->_withDoc = $value; + } + + /** + * Return whether to show selected database data dictionary or not + * + * @return boolean whether to show selected database data dictionary or not + */ + public function isWithDataDictionary() + { + return $this->_withDoc; + } + + /** + * Sets the order of the table in data dictionary + * + * @param string $value table order + * + * @return void + */ + public function setTableOrder($value) + { + $this->_tableOrder = $value; + } + + /** + * Returns the order of the table in data dictionary + * + * @return string table order + */ + public function getTableOrder() + { + return $this->_tableOrder; + } + + /** + * Output Pdf Document for download + * + * @return void + */ + public function showOutput() + { + $this->diagram->download($this->getFileName('.pdf')); + } + + /** + * Sets X and Y minimum and maximum for a table cell + * + * @param TableStatsPdf $table The table name of which sets XY co-ordinates + * + * @return void + */ + private function _setMinMax($table) + { + $this->_xMax = max($this->_xMax, $table->x + $table->width); + $this->_yMax = max($this->_yMax, $table->y + $table->height); + $this->_xMin = min($this->_xMin, $table->x); + $this->_yMin = min($this->_yMin, $table->y); + } + + /** + * Defines relation objects + * + * @param string $masterTable The master table name + * @param string $masterField The relation field in the master table + * @param string $foreignTable The foreign table name + * @param string $foreignField The relation field in the foreign table + * + * @return void + * + * @see _setMinMax + */ + private function _addRelation($masterTable, $masterField, $foreignTable, + $foreignField + ) { + if (! isset($this->_tables[$masterTable])) { + $this->_tables[$masterTable] = new TableStatsPdf( + $this->diagram, + $this->db, + $masterTable, + null, + $this->pageNumber, + $this->_tablewidth, + $this->showKeys, + $this->tableDimension + ); + $this->_setMinMax($this->_tables[$masterTable]); + } + if (! isset($this->_tables[$foreignTable])) { + $this->_tables[$foreignTable] = new TableStatsPdf( + $this->diagram, + $this->db, + $foreignTable, + null, + $this->pageNumber, + $this->_tablewidth, + $this->showKeys, + $this->tableDimension + ); + $this->_setMinMax($this->_tables[$foreignTable]); + } + $this->relations[] = new RelationStatsPdf( + $this->diagram, + $this->_tables[$masterTable], + $masterField, + $this->_tables[$foreignTable], + $foreignField + ); + } + + /** + * Draws the grid + * + * @return void + * + * @see PMA_Schema_PDF + */ + private function _strokeGrid() + { + $gridSize = 10; + $labelHeight = 4; + $labelWidth = 5; + if ($this->_withDoc) { + $topSpace = 6; + $bottomSpace = 15; + } else { + $topSpace = 0; + $bottomSpace = 0; + } + + $this->diagram->SetMargins(0, 0); + $this->diagram->SetDrawColor(200, 200, 200); + // Draws horizontal lines + $innerHeight = $this->diagram->getPageHeight() - $topSpace - $bottomSpace; + for ($l = 0, + $size = intval($innerHeight / $gridSize); + $l <= $size; + $l++ + ) { + $this->diagram->line( + 0, $l * $gridSize + $topSpace, + $this->diagram->getPageWidth(), $l * $gridSize + $topSpace + ); + // Avoid duplicates + if ($l > 0 + && $l <= intval(($innerHeight - $labelHeight) / $gridSize) + ) { + $this->diagram->SetXY(0, $l * $gridSize + $topSpace); + $label = (string) sprintf( + '%.0f', + ($l * $gridSize + $topSpace - $this->_topMargin) + * $this->_scale + $this->_yMin + ); + $this->diagram->Cell($labelWidth, $labelHeight, ' ' . $label); + } // end if + } // end for + // Draws vertical lines + for ( + $j = 0, $size = intval($this->diagram->getPageWidth() / $gridSize); + $j <= $size; + $j++ + ) { + $this->diagram->line( + $j * $gridSize, + $topSpace, + $j * $gridSize, + $this->diagram->getPageHeight() - $bottomSpace + ); + $this->diagram->SetXY($j * $gridSize, $topSpace); + $label = (string) sprintf( + '%.0f', + ($j * $gridSize - $this->_leftMargin) * $this->_scale + $this->_xMin + ); + $this->diagram->Cell($labelWidth, $labelHeight, $label); + } + } + + /** + * Draws relation arrows + * + * @return void + * + * @see Relation_Stats_Pdf::relationdraw() + */ + private function _drawRelations() + { + $i = 0; + foreach ($this->relations as $relation) { + $relation->relationDraw($this->showColor, $i); + $i++; + } + } + + /** + * Draws tables + * + * @return void + * + * @see Table_Stats_Pdf::tableDraw() + */ + private function _drawTables() + { + foreach ($this->_tables as $table) { + $table->tableDraw(null, $this->_withDoc, $this->showColor); + } + } + + /** + * Generates data dictionary pages. + * + * @param array $alltables Tables to document. + * + * @return void + */ + public function dataDictionaryDoc(array $alltables) + { + // TOC + $this->diagram->addpage($this->orientation); + $this->diagram->Cell(0, 9, __('Table of contents'), 1, 0, 'C'); + $this->diagram->Ln(15); + $i = 1; + foreach ($alltables as $table) { + $this->diagram->PMA_links['doc'][$table]['-'] + = $this->diagram->AddLink(); + $this->diagram->SetX(10); + // $this->diagram->Ln(1); + $this->diagram->Cell( + 0, 6, __('Page number:') . ' {' . sprintf("%02d", $i) . '}', 0, 0, + 'R', 0, $this->diagram->PMA_links['doc'][$table]['-'] + ); + $this->diagram->SetX(10); + $this->diagram->Cell( + 0, 6, $i . ' ' . $table, 0, 1, + 'L', 0, $this->diagram->PMA_links['doc'][$table]['-'] + ); + // $this->diagram->Ln(1); + $fields = $GLOBALS['dbi']->getColumns($this->db, $table); + foreach ($fields as $row) { + $this->diagram->SetX(20); + $field_name = $row['Field']; + $this->diagram->PMA_links['doc'][$table][$field_name] + = $this->diagram->AddLink(); + //$this->diagram->Cell( + // 0, 6, $field_name, 0, 1, + // 'L', 0, $this->diagram->PMA_links['doc'][$table][$field_name] + //); + } + $i++; + } + $this->diagram->PMA_links['RT']['-'] = $this->diagram->AddLink(); + $this->diagram->SetX(10); + $this->diagram->Cell( + 0, 6, __('Page number:') . ' {00}', 0, 0, + 'R', 0, $this->diagram->PMA_links['RT']['-'] + ); + $this->diagram->SetX(10); + $this->diagram->Cell( + 0, 6, $i . ' ' . __('Relational schema'), 0, 1, + 'L', 0, $this->diagram->PMA_links['RT']['-'] + ); + $z = 0; + foreach ($alltables as $table) { + $z++; + $this->diagram->SetAutoPageBreak(true, 15); + $this->diagram->addpage($this->orientation); + $this->diagram->Bookmark($table); + $this->diagram->setAlias( + '{' . sprintf("%02d", $z) . '}', $this->diagram->PageNo() + ); + $this->diagram->PMA_links['RT'][$table]['-'] + = $this->diagram->AddLink(); + $this->diagram->SetLink( + $this->diagram->PMA_links['doc'][$table]['-'], -1 + ); + $this->diagram->SetFont($this->_ff, 'B', 18); + $this->diagram->Cell( + 0, 8, $z . ' ' . $table, 1, 1, + 'C', 0, $this->diagram->PMA_links['RT'][$table]['-'] + ); + $this->diagram->SetFont($this->_ff, '', 8); + $this->diagram->ln(); + + $cfgRelation = $this->relation->getRelationsParam(); + $comments = $this->relation->getComments($this->db, $table); + if ($cfgRelation['mimework']) { + $mime_map = Transformations::getMIME($this->db, $table, true); + } + + /** + * Gets table information + */ + $showtable = $GLOBALS['dbi']->getTable($this->db, $table) + ->getStatusInfo(); + $show_comment = isset($showtable['Comment']) + ? $showtable['Comment'] + : ''; + $create_time = isset($showtable['Create_time']) + ? Util::localisedDate( + strtotime($showtable['Create_time']) + ) + : ''; + $update_time = isset($showtable['Update_time']) + ? Util::localisedDate( + strtotime($showtable['Update_time']) + ) + : ''; + $check_time = isset($showtable['Check_time']) + ? Util::localisedDate( + strtotime($showtable['Check_time']) + ) + : ''; + + /** + * Gets fields properties + */ + $columns = $GLOBALS['dbi']->getColumns($this->db, $table); + + // Find which tables are related with the current one and write it in + // an array + $res_rel = $this->relation->getForeigners($this->db, $table); + + /** + * Displays the comments of the table if MySQL >= 3.23 + */ + + $break = false; + if (! empty($show_comment)) { + $this->diagram->Cell( + 0, 3, __('Table comments:') . ' ' . $show_comment, 0, 1 + ); + $break = true; + } + + if (! empty($create_time)) { + $this->diagram->Cell( + 0, 3, __('Creation:') . ' ' . $create_time, 0, 1 + ); + $break = true; + } + + if (! empty($update_time)) { + $this->diagram->Cell( + 0, 3, __('Last update:') . ' ' . $update_time, 0, 1 + ); + $break = true; + } + + if (! empty($check_time)) { + $this->diagram->Cell( + 0, 3, __('Last check:') . ' ' . $check_time, 0, 1 + ); + $break = true; + } + + if ($break == true) { + $this->diagram->Cell(0, 3, '', 0, 1); + $this->diagram->Ln(); + } + + $this->diagram->SetFont($this->_ff, 'B'); + if (isset($this->orientation) && $this->orientation == 'L') { + $this->diagram->Cell(25, 8, __('Column'), 1, 0, 'C'); + $this->diagram->Cell(20, 8, __('Type'), 1, 0, 'C'); + $this->diagram->Cell(20, 8, __('Attributes'), 1, 0, 'C'); + $this->diagram->Cell(10, 8, __('Null'), 1, 0, 'C'); + $this->diagram->Cell(20, 8, __('Default'), 1, 0, 'C'); + $this->diagram->Cell(25, 8, __('Extra'), 1, 0, 'C'); + $this->diagram->Cell(45, 8, __('Links to'), 1, 0, 'C'); + + if ($this->paper == 'A4') { + $comments_width = 67; + } else { + // this is really intended for 'letter' + /** + * @todo find optimal width for all formats + */ + $comments_width = 50; + } + $this->diagram->Cell($comments_width, 8, __('Comments'), 1, 0, 'C'); + $this->diagram->Cell(45, 8, 'MIME', 1, 1, 'C'); + $this->diagram->setWidths( + array(25, 20, 20, 10, 20, 25, 45, $comments_width, 45) + ); + } else { + $this->diagram->Cell(20, 8, __('Column'), 1, 0, 'C'); + $this->diagram->Cell(20, 8, __('Type'), 1, 0, 'C'); + $this->diagram->Cell(20, 8, __('Attributes'), 1, 0, 'C'); + $this->diagram->Cell(10, 8, __('Null'), 1, 0, 'C'); + $this->diagram->Cell(15, 8, __('Default'), 1, 0, 'C'); + $this->diagram->Cell(15, 8, __('Extra'), 1, 0, 'C'); + $this->diagram->Cell(30, 8, __('Links to'), 1, 0, 'C'); + $this->diagram->Cell(30, 8, __('Comments'), 1, 0, 'C'); + $this->diagram->Cell(30, 8, 'MIME', 1, 1, 'C'); + $this->diagram->setWidths(array(20, 20, 20, 10, 15, 15, 30, 30, 30)); + } + $this->diagram->SetFont($this->_ff, ''); + + foreach ($columns as $row) { + $extracted_columnspec + = Util::extractColumnSpec($row['Type']); + $type = $extracted_columnspec['print_type']; + $attribute = $extracted_columnspec['attribute']; + if (! isset($row['Default'])) { + if ($row['Null'] != '' && $row['Null'] != 'NO') { + $row['Default'] = 'NULL'; + } + } + $field_name = $row['Field']; + // $this->diagram->Ln(); + $this->diagram->PMA_links['RT'][$table][$field_name] + = $this->diagram->AddLink(); + $this->diagram->Bookmark($field_name, 1, -1); + $this->diagram->SetLink( + $this->diagram->PMA_links['doc'][$table][$field_name], -1 + ); + $foreigner = $this->relation->searchColumnInForeigners($res_rel, $field_name); + + $linksTo = ''; + if ($foreigner) { + $linksTo = '-> '; + if ($foreigner['foreign_db'] != $this->db) { + $linksTo .= $foreigner['foreign_db'] . '.'; + } + $linksTo .= $foreigner['foreign_table'] + . '.' . $foreigner['foreign_field']; + + if (isset($foreigner['on_update'])) { // not set for internal + $linksTo .= "\n" . 'ON UPDATE ' . $foreigner['on_update']; + $linksTo .= "\n" . 'ON DELETE ' . $foreigner['on_delete']; + } + } + + $this->diagram_row = array( + $field_name, + $type, + $attribute, + (($row['Null'] == '' || $row['Null'] == 'NO') + ? __('No') + : __('Yes')), + (isset($row['Default']) ? $row['Default'] : ''), + $row['Extra'], + $linksTo, + (isset($comments[$field_name]) + ? $comments[$field_name] + : ''), + (isset($mime_map) && isset($mime_map[$field_name]) + ? str_replace('_', '/', $mime_map[$field_name]['mimetype']) + : '') + ); + $links = array(); + $links[0] = $this->diagram->PMA_links['RT'][$table][$field_name]; + if ($foreigner + && isset($this->diagram->PMA_links['doc'][$foreigner['foreign_table']][$foreigner['foreign_field']]) + ) { + $links[6] = $this->diagram->PMA_links['doc'] + [$foreigner['foreign_table']][$foreigner['foreign_field']]; + } else { + unset($links[6]); + } + $this->diagram->row($this->diagram_row, $links); + } // end foreach + $this->diagram->SetFont($this->_ff, '', 14); + } //end each + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Plugins/Schema/Pdf/RelationStatsPdf.php b/php/apps/phpmyadmin49/libraries/classes/Plugins/Schema/Pdf/RelationStatsPdf.php new file mode 100644 index 00000000..66a46b55 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Plugins/Schema/Pdf/RelationStatsPdf.php @@ -0,0 +1,130 @@ +wTick = 5; + parent::__construct( + $diagram, $master_table, $master_field, $foreign_table, $foreign_field + ); + } + + /** + * draws relation links and arrows shows foreign key relations + * + * @param boolean $showColor Whether to use one color per relation or not + * @param integer $i The id of the link to draw + * + * @access public + * + * @return void + * + * @see Pdf + */ + public function relationDraw($showColor, $i) + { + if ($showColor) { + $d = $i % 6; + $j = ($i - $d) / 6; + $j = $j % 4; + $j++; + $case = array( + array(1, 0, 0), + array(0, 1, 0), + array(0, 0, 1), + array(1, 1, 0), + array(1, 0, 1), + array(0, 1, 1) + ); + list ($a, $b, $c) = $case[$d]; + $e = (1 - ($j - 1) / 6); + $this->diagram->SetDrawColor($a * 255 * $e, $b * 255 * $e, $c * 255 * $e); + } else { + $this->diagram->SetDrawColor(0); + } + $this->diagram->setLineWidthScale(0.2); + $this->diagram->lineScale( + $this->xSrc, + $this->ySrc, + $this->xSrc + $this->srcDir * $this->wTick, + $this->ySrc + ); + $this->diagram->lineScale( + $this->xDest + $this->destDir * $this->wTick, + $this->yDest, + $this->xDest, + $this->yDest + ); + $this->diagram->setLineWidthScale(0.1); + $this->diagram->lineScale( + $this->xSrc + $this->srcDir * $this->wTick, + $this->ySrc, + $this->xDest + $this->destDir * $this->wTick, + $this->yDest + ); + /* + * Draws arrows -> + */ + $root2 = 2 * sqrt(2); + $this->diagram->lineScale( + $this->xSrc + $this->srcDir * $this->wTick * 0.75, + $this->ySrc, + $this->xSrc + $this->srcDir * (0.75 - 1 / $root2) * $this->wTick, + $this->ySrc + $this->wTick / $root2 + ); + $this->diagram->lineScale( + $this->xSrc + $this->srcDir * $this->wTick * 0.75, + $this->ySrc, + $this->xSrc + $this->srcDir * (0.75 - 1 / $root2) * $this->wTick, + $this->ySrc - $this->wTick / $root2 + ); + + $this->diagram->lineScale( + $this->xDest + $this->destDir * $this->wTick / 2, + $this->yDest, + $this->xDest + $this->destDir * (0.5 + 1 / $root2) * $this->wTick, + $this->yDest + $this->wTick / $root2 + ); + $this->diagram->lineScale( + $this->xDest + $this->destDir * $this->wTick / 2, + $this->yDest, + $this->xDest + $this->destDir * (0.5 + 1 / $root2) * $this->wTick, + $this->yDest - $this->wTick / $root2 + ); + $this->diagram->SetDrawColor(0); + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Plugins/Schema/Pdf/TableStatsPdf.php b/php/apps/phpmyadmin49/libraries/classes/Plugins/Schema/Pdf/TableStatsPdf.php new file mode 100644 index 00000000..9f930ca5 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Plugins/Schema/Pdf/TableStatsPdf.php @@ -0,0 +1,231 @@ +heightCell = 6; + $this->_setHeight(); + /* + * setWidth must me after setHeight, because title + * can include table height which changes table width + */ + $this->_setWidth($fontSize); + if ($sameWideWidth < $this->width) { + $sameWideWidth = $this->width; + } + } + + /** + * Displays an error when the table cannot be found. + * + * @return void + */ + protected function showMissingTableError() + { + ExportRelationSchema::dieSchema( + $this->pageNumber, + "PDF", + sprintf(__('The %s table doesn\'t exist!'), $this->tableName) + ); + } + + /** + * Returns title of the current table, + * title can have the dimensions of the table + * + * @return string + */ + protected function getTitle() + { + $ret = ''; + if ($this->tableDimension) { + $ret = sprintf('%.0fx%0.f', $this->width, $this->height); + } + + return $ret . ' ' . $this->tableName; + } + + /** + * Sets the width of the table + * + * @param integer $fontSize The font size + * + * @access private + * + * @return void + * + * @see PMA_Schema_PDF + */ + private function _setWidth($fontSize) + { + foreach ($this->fields as $field) { + $this->width = max($this->width, $this->diagram->GetStringWidth($field)); + } + $this->width += $this->diagram->GetStringWidth(' '); + $this->diagram->SetFont($this->_ff, 'B', $fontSize); + /* + * it is unknown what value must be added, because + * table title is affected by the table width value + */ + while ($this->width < $this->diagram->GetStringWidth($this->getTitle())) { + $this->width += 5; + } + $this->diagram->SetFont($this->_ff, '', $fontSize); + } + + /** + * Sets the height of the table + * + * @return void + * + * @access private + */ + private function _setHeight() + { + $this->height = (count($this->fields) + 1) * $this->heightCell; + } + + /** + * Do draw the table + * + * @param integer $fontSize The font size + * @param boolean $withDoc Whether to include links to documentation + * @param boolean|integer $setColor Whether to display color + * + * @access public + * + * @return void + * + * @see PMA_Schema_PDF + */ + public function tableDraw($fontSize, $withDoc, $setColor = 0) + { + $this->diagram->setXyScale($this->x, $this->y); + $this->diagram->SetFont($this->_ff, 'B', $fontSize); + if ($setColor) { + $this->diagram->SetTextColor(200); + $this->diagram->SetFillColor(0, 0, 128); + } + if ($withDoc) { + $this->diagram->SetLink( + $this->diagram->PMA_links['RT'][$this->tableName]['-'], + -1 + ); + } else { + $this->diagram->PMA_links['doc'][$this->tableName]['-'] = ''; + } + + $this->diagram->cellScale( + $this->width, + $this->heightCell, + $this->getTitle(), + 1, + 1, + 'C', + $setColor, + $this->diagram->PMA_links['doc'][$this->tableName]['-'] + ); + $this->diagram->setXScale($this->x); + $this->diagram->SetFont($this->_ff, '', $fontSize); + $this->diagram->SetTextColor(0); + $this->diagram->SetFillColor(255); + + foreach ($this->fields as $field) { + if ($setColor) { + if (in_array($field, $this->primary)) { + $this->diagram->SetFillColor(215, 121, 123); + } + if ($field == $this->displayfield) { + $this->diagram->SetFillColor(142, 159, 224); + } + } + if ($withDoc) { + $this->diagram->SetLink( + $this->diagram->PMA_links['RT'][$this->tableName][$field], + -1 + ); + } else { + $this->diagram->PMA_links['doc'][$this->tableName][$field] = ''; + } + + $this->diagram->cellScale( + $this->width, + $this->heightCell, + ' ' . $field, + 1, + 1, + 'L', + $setColor, + $this->diagram->PMA_links['doc'][$this->tableName][$field] + ); + $this->diagram->setXScale($this->x); + $this->diagram->SetFillColor(255); + } + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Plugins/Schema/RelationStats.php b/php/apps/phpmyadmin49/libraries/classes/Plugins/Schema/RelationStats.php new file mode 100644 index 00000000..8fe9f30e --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Plugins/Schema/RelationStats.php @@ -0,0 +1,114 @@ +diagram = $diagram; + + $src_pos = $this->_getXy($master_table, $master_field); + $dest_pos = $this->_getXy($foreign_table, $foreign_field); + /* + * [0] is x-left + * [1] is x-right + * [2] is y + */ + $src_left = $src_pos[0] - $this->wTick; + $src_right = $src_pos[1] + $this->wTick; + $dest_left = $dest_pos[0] - $this->wTick; + $dest_right = $dest_pos[1] + $this->wTick; + + $d1 = abs($src_left - $dest_left); + $d2 = abs($src_right - $dest_left); + $d3 = abs($src_left - $dest_right); + $d4 = abs($src_right - $dest_right); + $d = min($d1, $d2, $d3, $d4); + + if ($d == $d1) { + $this->xSrc = $src_pos[0]; + $this->srcDir = -1; + $this->xDest = $dest_pos[0]; + $this->destDir = -1; + } elseif ($d == $d2) { + $this->xSrc = $src_pos[1]; + $this->srcDir = 1; + $this->xDest = $dest_pos[0]; + $this->destDir = -1; + } elseif ($d == $d3) { + $this->xSrc = $src_pos[0]; + $this->srcDir = -1; + $this->xDest = $dest_pos[1]; + $this->destDir = 1; + } else { + $this->xSrc = $src_pos[1]; + $this->srcDir = 1; + $this->xDest = $dest_pos[1]; + $this->destDir = 1; + } + $this->ySrc = $src_pos[2]; + $this->yDest = $dest_pos[2]; + } + + /** + * Gets arrows coordinates + * + * @param string $table The current table name + * @param string $column The relation column name + * + * @return array Arrows coordinates + * + * @access private + */ + private function _getXy($table, $column) + { + $pos = array_search($column, $table->fields); + + // x_left, x_right, y + return array( + $table->x, + $table->x + $table->width, + $table->y + ($pos + 1.5) * $table->heightCell, + ); + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Plugins/Schema/SchemaDia.php b/php/apps/phpmyadmin49/libraries/classes/Plugins/Schema/SchemaDia.php new file mode 100644 index 00000000..02dd75af --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Plugins/Schema/SchemaDia.php @@ -0,0 +1,97 @@ +setProperties(); + } + + /** + * Sets the schema export Dia properties + * + * @return void + */ + protected function setProperties() + { + $schemaPluginProperties = new SchemaPluginProperties(); + $schemaPluginProperties->setText('Dia'); + $schemaPluginProperties->setExtension('dia'); + $schemaPluginProperties->setMimeType('application/dia'); + + // create the root group that will be the options field for + // $schemaPluginProperties + // this will be shown as "Format specific options" + $exportSpecificOptions = new OptionsPropertyRootGroup( + "Format Specific Options" + ); + + // specific options main group + $specificOptions = new OptionsPropertyMainGroup("general_opts"); + // add options common to all plugins + $this->addCommonOptions($specificOptions); + + $leaf = new SelectPropertyItem( + "orientation", + __('Orientation') + ); + $leaf->setValues( + array( + 'L' => __('Landscape'), + 'P' => __('Portrait'), + ) + ); + $specificOptions->addProperty($leaf); + + $leaf = new SelectPropertyItem( + "paper", + __('Paper size') + ); + $leaf->setValues($this->getPaperSizeArray()); + $specificOptions->addProperty($leaf); + + // add the main group to the root group + $exportSpecificOptions->addProperty($specificOptions); + + // set the options for the schema export plugin property item + $schemaPluginProperties->setOptions($exportSpecificOptions); + $this->properties = $schemaPluginProperties; + } + + /** + * Exports the schema into DIA format. + * + * @param string $db database name + * + * @return bool Whether it succeeded + */ + public function exportSchema($db) + { + $export = new DiaRelationSchema($db); + $export->showOutput(); + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Plugins/Schema/SchemaEps.php b/php/apps/phpmyadmin49/libraries/classes/Plugins/Schema/SchemaEps.php new file mode 100644 index 00000000..1df342f0 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Plugins/Schema/SchemaEps.php @@ -0,0 +1,98 @@ +setProperties(); + } + + /** + * Sets the schema export EPS properties + * + * @return void + */ + protected function setProperties() + { + $schemaPluginProperties = new SchemaPluginProperties(); + $schemaPluginProperties->setText('EPS'); + $schemaPluginProperties->setExtension('eps'); + $schemaPluginProperties->setMimeType('application/eps'); + + // create the root group that will be the options field for + // $schemaPluginProperties + // this will be shown as "Format specific options" + $exportSpecificOptions = new OptionsPropertyRootGroup( + "Format Specific Options" + ); + + // specific options main group + $specificOptions = new OptionsPropertyMainGroup("general_opts"); + // add options common to all plugins + $this->addCommonOptions($specificOptions); + + // create leaf items and add them to the group + $leaf = new BoolPropertyItem( + 'all_tables_same_width', + __('Same width for all tables') + ); + $specificOptions->addProperty($leaf); + + $leaf = new SelectPropertyItem( + "orientation", + __('Orientation') + ); + $leaf->setValues( + array( + 'L' => __('Landscape'), + 'P' => __('Portrait'), + ) + ); + $specificOptions->addProperty($leaf); + + // add the main group to the root group + $exportSpecificOptions->addProperty($specificOptions); + + // set the options for the schema export plugin property item + $schemaPluginProperties->setOptions($exportSpecificOptions); + $this->properties = $schemaPluginProperties; + } + + /** + * Exports the schema into EPS format. + * + * @param string $db database name + * + * @return bool Whether it succeeded + */ + public function exportSchema($db) + { + $export = new EpsRelationSchema($db); + $export->showOutput(); + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Plugins/Schema/SchemaPdf.php b/php/apps/phpmyadmin49/libraries/classes/Plugins/Schema/SchemaPdf.php new file mode 100644 index 00000000..0c12fb45 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Plugins/Schema/SchemaPdf.php @@ -0,0 +1,130 @@ +setProperties(); + } + + /** + * Sets the schema export PDF properties + * + * @return void + */ + protected function setProperties() + { + $schemaPluginProperties = new SchemaPluginProperties(); + $schemaPluginProperties->setText('PDF'); + $schemaPluginProperties->setExtension('pdf'); + $schemaPluginProperties->setMimeType('application/pdf'); + + // create the root group that will be the options field for + // $schemaPluginProperties + // this will be shown as "Format specific options" + $exportSpecificOptions = new OptionsPropertyRootGroup( + "Format Specific Options" + ); + + // specific options main group + $specificOptions = new OptionsPropertyMainGroup("general_opts"); + // add options common to all plugins + $this->addCommonOptions($specificOptions); + + // create leaf items and add them to the group + $leaf = new BoolPropertyItem( + 'all_tables_same_width', + __('Same width for all tables') + ); + $specificOptions->addProperty($leaf); + + $leaf = new SelectPropertyItem( + "orientation", + __('Orientation') + ); + $leaf->setValues( + array( + 'L' => __('Landscape'), + 'P' => __('Portrait'), + ) + ); + $specificOptions->addProperty($leaf); + + $leaf = new SelectPropertyItem( + "paper", + __('Paper size') + ); + $leaf->setValues($this->getPaperSizeArray()); + $specificOptions->addProperty($leaf); + + $leaf = new BoolPropertyItem( + 'show_grid', + __('Show grid') + ); + $specificOptions->addProperty($leaf); + + $leaf = new BoolPropertyItem( + 'with_doc', + __('Data dictionary') + ); + $specificOptions->addProperty($leaf); + + $leaf = new SelectPropertyItem( + "table_order", + __('Order of the tables') + ); + $leaf->setValues( + array( + '' => __('None'), + 'name_asc' => __('Name (Ascending)'), + 'name_desc' => __('Name (Descending)'), + ) + ); + $specificOptions->addProperty($leaf); + + // add the main group to the root group + $exportSpecificOptions->addProperty($specificOptions); + + // set the options for the schema export plugin property item + $schemaPluginProperties->setOptions($exportSpecificOptions); + $this->properties = $schemaPluginProperties; + } + + /** + * Exports the schema into PDF format. + * + * @param string $db database name + * + * @return bool Whether it succeeded + */ + public function exportSchema($db) + { + $export = new PdfRelationSchema($db); + $export->showOutput(); + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Plugins/Schema/SchemaSvg.php b/php/apps/phpmyadmin49/libraries/classes/Plugins/Schema/SchemaSvg.php new file mode 100644 index 00000000..fce81042 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Plugins/Schema/SchemaSvg.php @@ -0,0 +1,85 @@ +setProperties(); + } + + /** + * Sets the schema export SVG properties + * + * @return void + */ + protected function setProperties() + { + $schemaPluginProperties = new SchemaPluginProperties(); + $schemaPluginProperties->setText('SVG'); + $schemaPluginProperties->setExtension('svg'); + $schemaPluginProperties->setMimeType('application/svg'); + + // create the root group that will be the options field for + // $schemaPluginProperties + // this will be shown as "Format specific options" + $exportSpecificOptions = new OptionsPropertyRootGroup( + "Format Specific Options" + ); + + // specific options main group + $specificOptions = new OptionsPropertyMainGroup("general_opts"); + // add options common to all plugins + $this->addCommonOptions($specificOptions); + + // create leaf items and add them to the group + $leaf = new BoolPropertyItem( + 'all_tables_same_width', + __('Same width for all tables') + ); + $specificOptions->addProperty($leaf); + + // add the main group to the root group + $exportSpecificOptions->addProperty($specificOptions); + + // set the options for the schema export plugin property item + $schemaPluginProperties->setOptions($exportSpecificOptions); + $this->properties = $schemaPluginProperties; + } + + /** + * Exports the schema into SVG format. + * + * @param string $db database name + * + * @return bool Whether it succeeded + */ + public function exportSchema($db) + { + $export = new SvgRelationSchema($db); + $export->showOutput(); + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Plugins/Schema/Svg/RelationStatsSvg.php b/php/apps/phpmyadmin49/libraries/classes/Plugins/Schema/Svg/RelationStatsSvg.php new file mode 100644 index 00000000..b0f44d4e --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Plugins/Schema/Svg/RelationStatsSvg.php @@ -0,0 +1,138 @@ +wTick = 10; + parent::__construct( + $diagram, + $master_table, + $master_field, + $foreign_table, + $foreign_field + ); + } + + /** + * draws relation links and arrows shows foreign key relations + * + * @param boolean $showColor Whether to use one color per relation or not + * + * @return void + * @access public + * + * @see PMA_SVG + */ + public function relationDraw($showColor) + { + if ($showColor) { + $listOfColors = array( + '#c00', + '#bbb', + '#333', + '#cb0', + '#0b0', + '#0bf', + '#b0b', + ); + shuffle($listOfColors); + $color = $listOfColors[0]; + } else { + $color = '#333'; + } + + $this->diagram->printElementLine( + 'line', + $this->xSrc, + $this->ySrc, + $this->xSrc + $this->srcDir * $this->wTick, + $this->ySrc, + 'stroke:' . $color . ';stroke-width:1;' + ); + $this->diagram->printElementLine( + 'line', + $this->xDest + $this->destDir * $this->wTick, + $this->yDest, + $this->xDest, + $this->yDest, + 'stroke:' . $color . ';stroke-width:1;' + ); + $this->diagram->printElementLine( + 'line', + $this->xSrc + $this->srcDir * $this->wTick, + $this->ySrc, + $this->xDest + $this->destDir * $this->wTick, + $this->yDest, + 'stroke:' . $color . ';stroke-width:1;' + ); + $root2 = 2 * sqrt(2); + $this->diagram->printElementLine( + 'line', + $this->xSrc + $this->srcDir * $this->wTick * 0.75, + $this->ySrc, + $this->xSrc + $this->srcDir * (0.75 - 1 / $root2) * $this->wTick, + $this->ySrc + $this->wTick / $root2, + 'stroke:' . $color . ';stroke-width:2;' + ); + $this->diagram->printElementLine( + 'line', + $this->xSrc + $this->srcDir * $this->wTick * 0.75, + $this->ySrc, + $this->xSrc + $this->srcDir * (0.75 - 1 / $root2) * $this->wTick, + $this->ySrc - $this->wTick / $root2, + 'stroke:' . $color . ';stroke-width:2;' + ); + $this->diagram->printElementLine( + 'line', + $this->xDest + $this->destDir * $this->wTick / 2, + $this->yDest, + $this->xDest + $this->destDir * (0.5 + 1 / $root2) * $this->wTick, + $this->yDest + $this->wTick / $root2, + 'stroke:' . $color . ';stroke-width:2;' + ); + $this->diagram->printElementLine( + 'line', + $this->xDest + $this->destDir * $this->wTick / 2, + $this->yDest, + $this->xDest + $this->destDir * (0.5 + 1 / $root2) * $this->wTick, + $this->yDest - $this->wTick / $root2, + 'stroke:' . $color . ';stroke-width:2;' + ); + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Plugins/Schema/Svg/Svg.php b/php/apps/phpmyadmin49/libraries/classes/Plugins/Schema/Svg/Svg.php new file mode 100644 index 00000000..624bf106 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Plugins/Schema/Svg/Svg.php @@ -0,0 +1,279 @@ +openMemory(); + /* + * Set indenting using three spaces, + * so output is formatted + */ + + $this->setIndent(true); + $this->setIndentString(' '); + /* + * Create the XML document + */ + + $this->startDocument('1.0', 'UTF-8'); + $this->startDtd( + 'svg', + '-//W3C//DTD SVG 1.1//EN', + 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd' + ); + $this->endDtd(); + } + + /** + * Set document title + * + * @param string $value sets the title text + * + * @return void + */ + public function setTitle($value) + { + $this->title = $value; + } + + /** + * Set document author + * + * @param string $value sets the author + * + * @return void + */ + public function setAuthor($value) + { + $this->author = $value; + } + + /** + * Set document font + * + * @param string $value sets the font e.g Arial, Sans-serif etc + * + * @return void + */ + public function setFont($value) + { + $this->font = $value; + } + + /** + * Get document font + * + * @return string returns the font name + */ + public function getFont() + { + return $this->font; + } + + /** + * Set document font size + * + * @param integer $value sets the font size in pixels + * + * @return void + */ + public function setFontSize($value) + { + $this->fontSize = $value; + } + + /** + * Get document font size + * + * @return integer returns the font size + */ + public function getFontSize() + { + return $this->fontSize; + } + + /** + * Starts RelationStatsSvg Document + * + * svg document starts by first initializing svg tag + * which contains all the attributes and namespace that needed + * to define the svg document + * + * @param integer $width total width of the RelationStatsSvg document + * @param integer $height total height of the RelationStatsSvg document + * @param integer $x min-x of the view box + * @param integer $y min-y of the view box + * + * @return void + * + * @see XMLWriter::startElement(),XMLWriter::writeAttribute() + */ + public function startSvgDoc($width, $height, $x = 0, $y = 0) + { + $this->startElement('svg'); + + if (!is_int($width)) { + $width = intval($width); + } + + if (!is_int($height)) { + $height = intval($height); + } + + if ($x != 0 || $y != 0) { + $this->writeAttribute('viewBox', "$x $y $width $height"); + } + $this->writeAttribute('width', ($width - $x) . 'px'); + $this->writeAttribute('height', ($height - $y) . 'px'); + $this->writeAttribute('xmlns', 'http://www.w3.org/2000/svg'); + $this->writeAttribute('version', '1.1'); + } + + /** + * Ends RelationStatsSvg Document + * + * @return void + * @see XMLWriter::endElement(),XMLWriter::endDocument() + */ + public function endSvgDoc() + { + $this->endElement(); + $this->endDocument(); + } + + /** + * output RelationStatsSvg Document + * + * svg document prompted to the user for download + * RelationStatsSvg document saved in .svg extension and can be + * easily changeable by using any svg IDE + * + * @param string $fileName file name + * + * @return void + * @see XMLWriter::startElement(),XMLWriter::writeAttribute() + */ + public function showOutput($fileName) + { + //ob_get_clean(); + $output = $this->flush(); + Response::getInstance()->disable(); + Core::downloadHeader( + $fileName, + 'image/svg+xml', + strlen($output) + ); + print $output; + } + + /** + * Draws RelationStatsSvg elements + * + * SVG has some predefined shape elements like rectangle & text + * and other elements who have x,y co-ordinates are drawn. + * specify their width and height and can give styles too. + * + * @param string $name RelationStatsSvg element name + * @param int $x The x attr defines the left position of the element + * (e.g. x="0" places the element 0 pixels from the + * left of the browser window) + * @param integer $y The y attribute defines the top position of the + * element (e.g. y="0" places the element 0 pixels + * from the top of the browser window) + * @param int|string $width The width attribute defines the width the element + * @param int|string $height The height attribute defines the height the element + * @param string $text The text attribute defines the text the element + * @param string $styles The style attribute defines the style the element + * styles can be defined like CSS styles + * + * @return void + * + * @see XMLWriter::startElement(), XMLWriter::writeAttribute(), + * XMLWriter::text(), XMLWriter::endElement() + */ + public function printElement( + $name, + $x, + $y, + $width = '', + $height = '', + $text = '', + $styles = '' + ) { + $this->startElement($name); + $this->writeAttribute('width', $width); + $this->writeAttribute('height', $height); + $this->writeAttribute('x', $x); + $this->writeAttribute('y', $y); + $this->writeAttribute('style', $styles); + if (isset($text)) { + $this->writeAttribute('font-family', $this->font); + $this->writeAttribute('font-size', $this->fontSize . 'px'); + $this->text($text); + } + $this->endElement(); + } + + /** + * Draws RelationStatsSvg Line element + * + * RelationStatsSvg line element is drawn for connecting the tables. + * arrows are also drawn by specify its start and ending + * co-ordinates + * + * @param string $name RelationStatsSvg element name i.e line + * @param integer $x1 Defines the start of the line on the x-axis + * @param integer $y1 Defines the start of the line on the y-axis + * @param integer $x2 Defines the end of the line on the x-axis + * @param integer $y2 Defines the end of the line on the y-axis + * @param string $styles The style attribute defines the style the element + * styles can be defined like CSS styles + * + * @return void + * + * @see XMLWriter::startElement(), XMLWriter::writeAttribute(), + * XMLWriter::endElement() + */ + public function printElementLine($name, $x1, $y1, $x2, $y2, $styles) + { + $this->startElement($name); + $this->writeAttribute('x1', $x1); + $this->writeAttribute('y1', $y1); + $this->writeAttribute('x2', $x2); + $this->writeAttribute('y2', $y2); + $this->writeAttribute('style', $styles); + $this->endElement(); + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Plugins/Schema/Svg/SvgRelationSchema.php b/php/apps/phpmyadmin49/libraries/classes/Plugins/Schema/Svg/SvgRelationSchema.php new file mode 100644 index 00000000..846b8fa1 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Plugins/Schema/Svg/SvgRelationSchema.php @@ -0,0 +1,268 @@ +setShowColor(isset($_REQUEST['svg_show_color'])); + $this->setShowKeys(isset($_REQUEST['svg_show_keys'])); + $this->setTableDimension(isset($_REQUEST['svg_show_table_dimension'])); + $this->setAllTablesSameWidth(isset($_REQUEST['svg_all_tables_same_width'])); + + $this->diagram->setTitle( + sprintf( + __('Schema of the %s database - Page %s'), + $this->db, + $this->pageNumber + ) + ); + $this->diagram->SetAuthor('phpMyAdmin ' . PMA_VERSION); + $this->diagram->setFont('Arial'); + $this->diagram->setFontSize(16); + + $alltables = $this->getTablesFromRequest(); + + foreach ($alltables as $table) { + if (!isset($this->_tables[$table])) { + $this->_tables[$table] = new TableStatsSvg( + $this->diagram, $this->db, + $table, $this->diagram->getFont(), + $this->diagram->getFontSize(), $this->pageNumber, + $this->_tablewidth, $this->showKeys, $this->tableDimension, + $this->offline + ); + } + + if ($this->sameWide) { + $this->_tables[$table]->width = &$this->_tablewidth; + } + $this->_setMinMax($this->_tables[$table]); + } + + $border = 15; + $this->diagram->startSvgDoc( + $this->_xMax + $border, + $this->_yMax + $border, + $this->_xMin - $border, + $this->_yMin - $border + ); + + $seen_a_relation = false; + foreach ($alltables as $one_table) { + $exist_rel = $this->relation->getForeigners($this->db, $one_table, '', 'both'); + if (!$exist_rel) { + continue; + } + + $seen_a_relation = true; + foreach ($exist_rel as $master_field => $rel) { + /* put the foreign table on the schema only if selected + * by the user + * (do not use array_search() because we would have to + * to do a === false and this is not PHP3 compatible) + */ + if ($master_field != 'foreign_keys_data') { + if (in_array($rel['foreign_table'], $alltables)) { + $this->_addRelation( + $one_table, + $this->diagram->getFont(), + $this->diagram->getFontSize(), + $master_field, + $rel['foreign_table'], + $rel['foreign_field'], + $this->tableDimension + ); + } + continue; + } + + foreach ($rel as $one_key) { + if (!in_array($one_key['ref_table_name'], $alltables)) { + continue; + } + + foreach ( + $one_key['index_list'] + as $index => $one_field + ) { + $this->_addRelation( + $one_table, + $this->diagram->getFont(), + $this->diagram->getFontSize(), + $one_field, + $one_key['ref_table_name'], + $one_key['ref_index_list'][$index], + $this->tableDimension + ); + } + } + } + } + if ($seen_a_relation) { + $this->_drawRelations(); + } + + $this->_drawTables(); + $this->diagram->endSvgDoc(); + } + + /** + * Output RelationStatsSvg Document for download + * + * @return void + */ + public function showOutput() + { + $this->diagram->showOutput($this->getFileName('.svg')); + } + + /** + * Sets X and Y minimum and maximum for a table cell + * + * @param string $table The table name + * + * @return void + */ + private function _setMinMax($table) + { + $this->_xMax = max($this->_xMax, $table->x + $table->width); + $this->_yMax = max($this->_yMax, $table->y + $table->height); + $this->_xMin = min($this->_xMin, $table->x); + $this->_yMin = min($this->_yMin, $table->y); + } + + /** + * Defines relation objects + * + * @param string $masterTable The master table name + * @param string $font The font face + * @param int $fontSize Font size + * @param string $masterField The relation field in the master table + * @param string $foreignTable The foreign table name + * @param string $foreignField The relation field in the foreign table + * @param boolean $tableDimension Whether to display table position or not + * + * @return void + * + * @see _setMinMax,Table_Stats_Svg::__construct(), + * PhpMyAdmin\Plugins\Schema\Svg\RelationStatsSvg::__construct() + */ + private function _addRelation( + $masterTable, + $font, + $fontSize, + $masterField, + $foreignTable, + $foreignField, + $tableDimension + ) { + if (!isset($this->_tables[$masterTable])) { + $this->_tables[$masterTable] = new TableStatsSvg( + $this->diagram, $this->db, + $masterTable, $font, $fontSize, $this->pageNumber, + $this->_tablewidth, false, $tableDimension + ); + $this->_setMinMax($this->_tables[$masterTable]); + } + if (!isset($this->_tables[$foreignTable])) { + $this->_tables[$foreignTable] = new TableStatsSvg( + $this->diagram, $this->db, + $foreignTable, $font, $fontSize, $this->pageNumber, + $this->_tablewidth, false, $tableDimension + ); + $this->_setMinMax($this->_tables[$foreignTable]); + } + $this->_relations[] = new RelationStatsSvg( + $this->diagram, + $this->_tables[$masterTable], + $masterField, + $this->_tables[$foreignTable], + $foreignField + ); + } + + /** + * Draws relation arrows and lines + * connects master table's master field to + * foreign table's foreign field + * + * @return void + * + * @see Relation_Stats_Svg::relationDraw() + */ + private function _drawRelations() + { + foreach ($this->_relations as $relation) { + $relation->relationDraw($this->showColor); + } + } + + /** + * Draws tables + * + * @return void + * + * @see Table_Stats_Svg::Table_Stats_tableDraw() + */ + private function _drawTables() + { + foreach ($this->_tables as $table) { + $table->tableDraw($this->showColor); + } + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Plugins/Schema/Svg/TableStatsSvg.php b/php/apps/phpmyadmin49/libraries/classes/Plugins/Schema/Svg/TableStatsSvg.php new file mode 100644 index 00000000..29cbc9a9 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Plugins/Schema/Svg/TableStatsSvg.php @@ -0,0 +1,202 @@ +_setHeightTable($fontSize); + // setWidth must me after setHeight, because title + // can include table height which changes table width + $this->_setWidthTable($font, $fontSize); + if ($same_wide_width < $this->width) { + $same_wide_width = $this->width; + } + } + + /** + * Displays an error when the table cannot be found. + * + * @return void + */ + protected function showMissingTableError() + { + ExportRelationSchema::dieSchema( + $this->pageNumber, + "SVG", + sprintf(__('The %s table doesn\'t exist!'), $this->tableName) + ); + } + + /** + * Sets the width of the table + * + * @param string $font The font size + * @param integer $fontSize The font size + * + * @return void + * @access private + * + * @see PMA_SVG + */ + private function _setWidthTable($font, $fontSize) + { + foreach ($this->fields as $field) { + $this->width = max( + $this->width, + Font::getStringWidth($field, $font, $fontSize) + ); + } + $this->width += Font::getStringWidth(' ', $font, $fontSize); + + /* + * it is unknown what value must be added, because + * table title is affected by the table width value + */ + while ($this->width + < Font::getStringWidth($this->getTitle(), $font, $fontSize) + ) { + $this->width += 7; + } + } + + /** + * Sets the height of the table + * + * @param integer $fontSize font size + * + * @return void + */ + private function _setHeightTable($fontSize) + { + $this->heightCell = $fontSize + 4; + $this->height = (count($this->fields) + 1) * $this->heightCell; + } + + /** + * draw the table + * + * @param boolean $showColor Whether to display color + * + * @access public + * @return void + * + * @see PMA_SVG,PMA_SVG::printElement + */ + public function tableDraw($showColor) + { + $this->diagram->printElement( + 'rect', + $this->x, + $this->y, + $this->width, + $this->heightCell, + null, + 'fill:#007;stroke:black;' + ); + $this->diagram->printElement( + 'text', + $this->x + 5, + $this->y + 14, + $this->width, + $this->heightCell, + $this->getTitle(), + 'fill:#fff;' + ); + foreach ($this->fields as $field) { + $this->currentCell += $this->heightCell; + $fillColor = 'none'; + if ($showColor) { + if (in_array($field, $this->primary)) { + $fillColor = '#aea'; + } + if ($field == $this->displayfield) { + $fillColor = 'none'; + } + } + $this->diagram->printElement( + 'rect', + $this->x, + $this->y + $this->currentCell, + $this->width, + $this->heightCell, + null, + 'fill:' . $fillColor . ';stroke:black;' + ); + $this->diagram->printElement( + 'text', + $this->x + 5, + $this->y + 14 + $this->currentCell, + $this->width, + $this->heightCell, + $field, + 'fill:black;' + ); + } + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Plugins/Schema/TableStats.php b/php/apps/phpmyadmin49/libraries/classes/Plugins/Schema/TableStats.php new file mode 100644 index 00000000..420618de --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Plugins/Schema/TableStats.php @@ -0,0 +1,191 @@ +diagram = $diagram; + $this->db = $db; + $this->pageNumber = $pageNumber; + $this->tableName = $tableName; + + $this->showKeys = $showKeys; + $this->tableDimension = $tableDimension; + + $this->offline = $offline; + + $this->relation = new Relation(); + + // checks whether the table exists + // and loads fields + $this->validateTableAndLoadFields(); + // load table coordinates + $this->loadCoordinates(); + // loads display field + $this->loadDisplayField(); + // loads primary keys + $this->loadPrimaryKey(); + } + + /** + * Validate whether the table exists. + * + * @return void + */ + protected function validateTableAndLoadFields() + { + $sql = 'DESCRIBE ' . Util::backquote($this->tableName); + $result = $GLOBALS['dbi']->tryQuery( + $sql, + DatabaseInterface::CONNECT_USER, + DatabaseInterface::QUERY_STORE + ); + if (! $result || ! $GLOBALS['dbi']->numRows($result)) { + $this->showMissingTableError(); + } + + if ($this->showKeys) { + $indexes = Index::getFromTable($this->tableName, $this->db); + $all_columns = array(); + foreach ($indexes as $index) { + $all_columns = array_merge( + $all_columns, + array_flip(array_keys($index->getColumns())) + ); + } + $this->fields = array_keys($all_columns); + } else { + while ($row = $GLOBALS['dbi']->fetchRow($result)) { + $this->fields[] = $row[0]; + } + } + } + + /** + * Displays an error when the table cannot be found. + * + * @return void + * @abstract + */ + protected abstract function showMissingTableError(); + + /** + * Loads coordinates of a table + * + * @return void + */ + protected function loadCoordinates() + { + if (isset($_POST['t_h'])) { + foreach ($_POST['t_h'] as $key => $value) { + $db = rawurldecode($_POST['t_db'][$key]); + $tbl = rawurldecode($_POST['t_tbl'][$key]); + if ($this->db . '.' . $this->tableName === $db . '.' . $tbl) { + $this->x = (double) $_POST['t_x'][$key]; + $this->y = (double) $_POST['t_y'][$key]; + break; + } + } + } + } + + /** + * Loads the table's display field + * + * @return void + */ + protected function loadDisplayField() + { + $this->displayfield = $this->relation->getDisplayField($this->db, $this->tableName); + } + + /** + * Loads the PRIMARY key. + * + * @return void + */ + protected function loadPrimaryKey() + { + $result = $GLOBALS['dbi']->query( + 'SHOW INDEX FROM ' . Util::backquote($this->tableName) . ';', + DatabaseInterface::CONNECT_USER, + DatabaseInterface::QUERY_STORE + ); + if ($GLOBALS['dbi']->numRows($result) > 0) { + while ($row = $GLOBALS['dbi']->fetchAssoc($result)) { + if ($row['Key_name'] == 'PRIMARY') { + $this->primary[] = $row['Column_name']; + } + } + } + } + + /** + * Returns title of the current table, + * title can have the dimensions/co-ordinates of the table + * + * @return string title of the current table + */ + protected function getTitle() + { + return ($this->tableDimension + ? sprintf('%.0fx%0.f', $this->width, $this->heightCell) + : '' + ) + . ' ' . $this->tableName; + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Plugins/SchemaPlugin.php b/php/apps/phpmyadmin49/libraries/classes/Plugins/SchemaPlugin.php new file mode 100644 index 00000000..7be1959e --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Plugins/SchemaPlugin.php @@ -0,0 +1,89 @@ +properties; + } + + /** + * Sets the export plugins properties and is implemented by + * each schema export plugin + * + * @return void + */ + protected abstract function setProperties(); + + /** + * Exports the schema into the specified format. + * + * @param string $db database name + * + * @return bool Whether it succeeded + */ + public abstract function exportSchema($db); + + /** + * Adds export options common to all plugins. + * + * @param \PhpMyAdmin\Properties\Options\Groups\OptionsPropertyMainGroup $propertyGroup property group + * + * @return void + */ + protected function addCommonOptions(OptionsPropertyMainGroup $propertyGroup) + { + $leaf = new BoolPropertyItem('show_color', __('Show color')); + $propertyGroup->addProperty($leaf); + $leaf = new BoolPropertyItem('show_keys', __('Only show keys')); + $propertyGroup->addProperty($leaf); + } + + /** + * Returns the array of paper sizes + * + * @return array array of paper sizes + */ + protected function getPaperSizeArray() + { + $ret = array(); + foreach ($GLOBALS['cfg']['PDFPageSizes'] as $val) { + $ret[$val] = $val; + } + + return $ret; + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Plugins/Transformations/Abs/Bool2TextTransformationsPlugin.php b/php/apps/phpmyadmin49/libraries/classes/Plugins/Transformations/Abs/Bool2TextTransformationsPlugin.php new file mode 100644 index 00000000..49a1c099 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Plugins/Transformations/Abs/Bool2TextTransformationsPlugin.php @@ -0,0 +1,66 @@ +getOptions($options, $cfg['DefaultTransformations']['Bool2Text']); + + if ($buffer == '0') { + return $options[1]; // return false label + } + + return $options[0]; // or true one if nonzero + } + + /* ~~~~~~~~~~~~~~~~~~~~ Getters and Setters ~~~~~~~~~~~~~~~~~~~~ */ + + /** + * Gets the transformation name of the specific plugin + * + * @return string + */ + public static function getName() + { + return "Bool2Text"; + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Plugins/Transformations/Abs/CodeMirrorEditorTransformationPlugin.php b/php/apps/phpmyadmin49/libraries/classes/Plugins/Transformations/Abs/CodeMirrorEditorTransformationPlugin.php new file mode 100644 index 00000000..129da8ea --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Plugins/Transformations/Abs/CodeMirrorEditorTransformationPlugin.php @@ -0,0 +1,72 @@ +'; + } + $class = 'transform_' . strtolower(static::getName()) . '_editor'; + $html .= ''; + + return $html; + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Plugins/Transformations/Abs/DateFormatTransformationsPlugin.php b/php/apps/phpmyadmin49/libraries/classes/Plugins/Transformations/Abs/DateFormatTransformationsPlugin.php new file mode 100644 index 00000000..595872cc --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Plugins/Transformations/Abs/DateFormatTransformationsPlugin.php @@ -0,0 +1,161 @@ +getOptions($options, $cfg['DefaultTransformations']['DateFormat']); + + // further operations on $buffer using the $options[] array. + $options[2] = mb_strtolower($options[2]); + + if (empty($options[1])) { + if ($options[2] == 'local') { + $options[1] = __('%B %d, %Y at %I:%M %p'); + } else { + $options[1] = 'Y-m-d H:i:s'; + } + } + + $timestamp = -1; + + // INT columns will be treated as UNIX timestamps + // and need to be detected before the verification for + // MySQL TIMESTAMP + if ($meta->type == 'int') { + $timestamp = $buffer; + + // Detect TIMESTAMP(6 | 8 | 10 | 12 | 14) + // TIMESTAMP (2 | 4) not supported here. + // (Note: prior to MySQL 4.1, TIMESTAMP has a display size + // for example TIMESTAMP(8) means YYYYMMDD) + } else { + if (preg_match('/^(\d{2}){3,7}$/', $buffer)) { + + if (mb_strlen($buffer) == 14 || mb_strlen($buffer) == 8) { + $offset = 4; + } else { + $offset = 2; + } + + $aDate = array(); + $aDate['year'] = (int) + mb_substr($buffer, 0, $offset); + $aDate['month'] = (int) + mb_substr($buffer, $offset, 2); + $aDate['day'] = (int) + mb_substr($buffer, $offset + 2, 2); + $aDate['hour'] = (int) + mb_substr($buffer, $offset + 4, 2); + $aDate['minute'] = (int) + mb_substr($buffer, $offset + 6, 2); + $aDate['second'] = (int) + mb_substr($buffer, $offset + 8, 2); + + if (checkdate($aDate['month'], $aDate['day'], $aDate['year'])) { + $timestamp = mktime( + $aDate['hour'], + $aDate['minute'], + $aDate['second'], + $aDate['month'], + $aDate['day'], + $aDate['year'] + ); + } + // If all fails, assume one of the dozens of valid strtime() syntaxes + // (https://www.gnu.org/manual/tar-1.12/html_chapter/tar_7.html) + } else { + if (preg_match('/^[0-9]\d{1,9}$/', $buffer)) { + $timestamp = (int)$buffer; + } else { + $timestamp = strtotime($buffer); + } + } + } + + // If all above failed, maybe it's a Unix timestamp already? + if ($timestamp < 0 && preg_match('/^[1-9]\d{1,9}$/', $buffer)) { + $timestamp = $buffer; + } + + // Reformat a valid timestamp + if ($timestamp >= 0) { + $timestamp -= $options[0] * 60 * 60; + $source = $buffer; + if ($options[2] == 'local') { + $text = Util::localisedDate( + $timestamp, + $options[1] + ); + } elseif ($options[2] == 'utc') { + $text = gmdate($options[1], $timestamp); + } else { + $text = 'INVALID DATE TYPE'; + } + return '' . htmlspecialchars($text) . ''; + } + + return htmlspecialchars($buffer); + } + + /* ~~~~~~~~~~~~~~~~~~~~ Getters and Setters ~~~~~~~~~~~~~~~~~~~~ */ + + /** + * Gets the transformation name of the specific plugin + * + * @return string + */ + public static function getName() + { + return "Date Format"; + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Plugins/Transformations/Abs/DownloadTransformationsPlugin.php b/php/apps/phpmyadmin49/libraries/classes/Plugins/Transformations/Abs/DownloadTransformationsPlugin.php new file mode 100644 index 00000000..d205b9f0 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Plugins/Transformations/Abs/DownloadTransformationsPlugin.php @@ -0,0 +1,90 @@ + $val) { + if ($val->name == $options[1]) { + $pos = $key; + break; + } + } + if (isset($pos)) { + $cn = $row[$pos]; + } + } + if (empty($cn)) { + $cn = 'binary_file.dat'; + } + } + + return sprintf( + '%s', + $options['wrapper_link'], + htmlspecialchars(urlencode($cn)), + htmlspecialchars($cn), + htmlspecialchars($cn) + ); + } + + + /* ~~~~~~~~~~~~~~~~~~~~ Getters and Setters ~~~~~~~~~~~~~~~~~~~~ */ + + /** + * Gets the transformation name of the specific plugin + * + * @return string + */ + public static function getName() + { + return "Download"; + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Plugins/Transformations/Abs/ExternalTransformationsPlugin.php b/php/apps/phpmyadmin49/libraries/classes/Plugins/Transformations/Abs/ExternalTransformationsPlugin.php new file mode 100644 index 00000000..d3286aa5 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Plugins/Transformations/Abs/ExternalTransformationsPlugin.php @@ -0,0 +1,151 @@ +getOptions( + $options, + $cfg['DefaultTransformations']['External'] + ); + + if (isset($allowed_programs[$options[0]])) { + $program = $allowed_programs[$options[0]]; + } else { + $program = $allowed_programs[0]; + } + + // needs PHP >= 4.3.0 + $newstring = ''; + $descriptorspec = array( + 0 => array("pipe", "r"), + 1 => array("pipe", "w"), + ); + $process = proc_open($program . ' ' . $options[1], $descriptorspec, $pipes); + if (is_resource($process)) { + fwrite($pipes[0], $buffer); + fclose($pipes[0]); + + while (!feof($pipes[1])) { + $newstring .= fgets($pipes[1], 1024); + } + fclose($pipes[1]); + // we don't currently use the return value + proc_close($process); + } + + if ($options[2] == 1 || $options[2] == '2') { + $retstring = htmlspecialchars($newstring); + } else { + $retstring = $newstring; + } + + return $retstring; + } + + + /* ~~~~~~~~~~~~~~~~~~~~ Getters and Setters ~~~~~~~~~~~~~~~~~~~~ */ + + /** + * Gets the transformation name of the specific plugin + * + * @return string + */ + public static function getName() + { + return "External"; + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Plugins/Transformations/Abs/FormattedTransformationsPlugin.php b/php/apps/phpmyadmin49/libraries/classes/Plugins/Transformations/Abs/FormattedTransformationsPlugin.php new file mode 100644 index 00000000..c97f1268 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Plugins/Transformations/Abs/FormattedTransformationsPlugin.php @@ -0,0 +1,62 @@ +'; + } + + + /* ~~~~~~~~~~~~~~~~~~~~ Getters and Setters ~~~~~~~~~~~~~~~~~~~~ */ + + /** + * Gets the transformation name of the specific plugin + * + * @return string + */ + public static function getName() + { + return "Formatted"; + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Plugins/Transformations/Abs/HexTransformationsPlugin.php b/php/apps/phpmyadmin49/libraries/classes/Plugins/Transformations/Abs/HexTransformationsPlugin.php new file mode 100644 index 00000000..627de1f8 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Plugins/Transformations/Abs/HexTransformationsPlugin.php @@ -0,0 +1,68 @@ +getOptions($options, $cfg['DefaultTransformations']['Hex']); + $options[0] = intval($options[0]); + + if ($options[0] < 1) { + return bin2hex($buffer); + } else { + return chunk_split(bin2hex($buffer), $options[0], ' '); + } + } + + /* ~~~~~~~~~~~~~~~~~~~~ Getters and Setters ~~~~~~~~~~~~~~~~~~~~ */ + + /** + * Gets the transformation name of the specific plugin + * + * @return string + */ + public static function getName() + { + return "Hex"; + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Plugins/Transformations/Abs/ImageLinkTransformationsPlugin.php b/php/apps/phpmyadmin49/libraries/classes/Plugins/Transformations/Abs/ImageLinkTransformationsPlugin.php new file mode 100644 index 00000000..6dca346a --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Plugins/Transformations/Abs/ImageLinkTransformationsPlugin.php @@ -0,0 +1,64 @@ +[BLOB]'; + } + + /* ~~~~~~~~~~~~~~~~~~~~ Getters and Setters ~~~~~~~~~~~~~~~~~~~~ */ + + /** + * Gets the transformation name of the specific plugin + * + * @return string + */ + public static function getName() + { + return "ImageLink"; + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Plugins/Transformations/Abs/ImageUploadTransformationsPlugin.php b/php/apps/phpmyadmin49/libraries/classes/Plugins/Transformations/Abs/ImageUploadTransformationsPlugin.php new file mode 100644 index 00000000..3909149d --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Plugins/Transformations/Abs/ImageUploadTransformationsPlugin.php @@ -0,0 +1,118 @@ +'; + $html .= ''; + $src = 'transformation_wrapper.php' . $options['wrapper_link']; + } + $html .= ''
+            . __('Image preview here') . ''; + $html .= '
      '; + + return $html; + } + + /** + * Returns the array of scripts (filename) required for plugin + * initialization and handling + * + * @return array javascripts to be included + */ + public function getScripts() + { + return array( + 'transformations/image_upload.js', + ); + } + + /* ~~~~~~~~~~~~~~~~~~~~ Getters and Setters ~~~~~~~~~~~~~~~~~~~~ */ + + /** + * Gets the transformation name of the specific plugin + * + * @return string + */ + public static function getName() + { + return "Image upload"; + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Plugins/Transformations/Abs/InlineTransformationsPlugin.php b/php/apps/phpmyadmin49/libraries/classes/Plugins/Transformations/Abs/InlineTransformationsPlugin.php new file mode 100644 index 00000000..a56fd80d --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Plugins/Transformations/Abs/InlineTransformationsPlugin.php @@ -0,0 +1,79 @@ +getOptions($options, $cfg['DefaultTransformations']['Inline']); + + if (PMA_IS_GD2) { + return '[' . htmlspecialchars($buffer) . ']'; + } else { + return '[' . htmlspecialchars($buffer) . ']'; + } + } + + + + /* ~~~~~~~~~~~~~~~~~~~~ Getters and Setters ~~~~~~~~~~~~~~~~~~~~ */ + + /** + * Gets the transformation name of the specific plugin + * + * @return string + */ + public static function getName() + { + return "Inline"; + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Plugins/Transformations/Abs/LongToIPv4TransformationsPlugin.php b/php/apps/phpmyadmin49/libraries/classes/Plugins/Transformations/Abs/LongToIPv4TransformationsPlugin.php new file mode 100644 index 00000000..8498d1b7 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Plugins/Transformations/Abs/LongToIPv4TransformationsPlugin.php @@ -0,0 +1,63 @@ + 4294967295) { + return htmlspecialchars($buffer); + } + + return long2ip($buffer); + } + + + /* ~~~~~~~~~~~~~~~~~~~~ Getters and Setters ~~~~~~~~~~~~~~~~~~~~ */ + + /** + * Gets the transformation name of the specific plugin + * + * @return string + */ + public static function getName() + { + return "Long To IPv4"; + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Plugins/Transformations/Abs/PreApPendTransformationsPlugin.php b/php/apps/phpmyadmin49/libraries/classes/Plugins/Transformations/Abs/PreApPendTransformationsPlugin.php new file mode 100644 index 00000000..401e34ba --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Plugins/Transformations/Abs/PreApPendTransformationsPlugin.php @@ -0,0 +1,65 @@ +getOptions($options, $cfg['DefaultTransformations']['PreApPend']); + + //just prepend and/or append the options to the original text + return htmlspecialchars($options[0]) . htmlspecialchars($buffer) + . htmlspecialchars($options[1]); + } + + /* ~~~~~~~~~~~~~~~~~~~~ Getters and Setters ~~~~~~~~~~~~~~~~~~~~ */ + + /** + * Gets the transformation name of the specific plugin + * + * @return string + */ + public static function getName() + { + return "PreApPend"; + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Plugins/Transformations/Abs/RegexValidationTransformationsPlugin.php b/php/apps/phpmyadmin49/libraries/classes/Plugins/Transformations/Abs/RegexValidationTransformationsPlugin.php new file mode 100644 index 00000000..158cad71 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Plugins/Transformations/Abs/RegexValidationTransformationsPlugin.php @@ -0,0 +1,71 @@ +reset(); + if (!empty($options[0]) && !preg_match($options[0], $buffer)) { + $this->success = false; + $this->error = sprintf( + __('Validation failed for the input string %s.'), + htmlspecialchars($buffer) + ); + } + + return $buffer; + } + + /* ~~~~~~~~~~~~~~~~~~~~ Getters and Setters ~~~~~~~~~~~~~~~~~~~~ */ + + /** + * Gets the transformation name of the specific plugin + * + * @return string + */ + public static function getName() + { + return "Regex Validation"; + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Plugins/Transformations/Abs/SQLTransformationsPlugin.php b/php/apps/phpmyadmin49/libraries/classes/Plugins/Transformations/Abs/SQLTransformationsPlugin.php new file mode 100644 index 00000000..bd1ad742 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Plugins/Transformations/Abs/SQLTransformationsPlugin.php @@ -0,0 +1,62 @@ +getOptions($options, $cfg['DefaultTransformations']['Substring']); + + if ($options[1] != 'all') { + $newtext = mb_substr( + $buffer, + $options[0], + $options[1] + ); + } else { + $newtext = mb_substr($buffer, $options[0]); + } + + $length = mb_strlen($newtext); + $baselength = mb_strlen($buffer); + if ($length != $baselength) { + if ($options[0] != 0) { + $newtext = $options[2] . $newtext; + } + + if (($length + $options[0]) != $baselength) { + $newtext .= $options[2]; + } + } + + return htmlspecialchars($newtext); + } + + + /* ~~~~~~~~~~~~~~~~~~~~ Getters and Setters ~~~~~~~~~~~~~~~~~~~~ */ + + /** + * Gets the transformation name of the specific plugin + * + * @return string + */ + public static function getName() + { + return "Substring"; + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Plugins/Transformations/Abs/TextFileUploadTransformationsPlugin.php b/php/apps/phpmyadmin49/libraries/classes/Plugins/Transformations/Abs/TextFileUploadTransformationsPlugin.php new file mode 100644 index 00000000..ff90bf9f --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Plugins/Transformations/Abs/TextFileUploadTransformationsPlugin.php @@ -0,0 +1,100 @@ +'; + $html .= ''; + } + $html .= ''; + + return $html; + } + + /* ~~~~~~~~~~~~~~~~~~~~ Getters and Setters ~~~~~~~~~~~~~~~~~~~~ */ + + /** + * Gets the transformation name of the specific plugin + * + * @return string + */ + public static function getName() + { + return "Text file upload"; + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Plugins/Transformations/Abs/TextImageLinkTransformationsPlugin.php b/php/apps/phpmyadmin49/libraries/classes/Plugins/Transformations/Abs/TextImageLinkTransformationsPlugin.php new file mode 100644 index 00000000..1229f84a --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Plugins/Transformations/Abs/TextImageLinkTransformationsPlugin.php @@ -0,0 +1,76 @@ +getOptions($options, $cfg['DefaultTransformations']['TextImageLink']); + $url = $options[0] . $buffer; + /* Do not allow javascript links */ + if (! Sanitize::checkLink($url, true, true)) { + return htmlspecialchars($url); + } + return '' + . htmlspecialchars($buffer) . ''; + } + + + /* ~~~~~~~~~~~~~~~~~~~~ Getters and Setters ~~~~~~~~~~~~~~~~~~~~ */ + + /** + * Gets the transformation name of the specific plugin + * + * @return string + */ + public static function getName() + { + return "Image Link"; + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Plugins/Transformations/Abs/TextLinkTransformationsPlugin.php b/php/apps/phpmyadmin49/libraries/classes/Plugins/Transformations/Abs/TextLinkTransformationsPlugin.php new file mode 100644 index 00000000..29a4d74c --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Plugins/Transformations/Abs/TextLinkTransformationsPlugin.php @@ -0,0 +1,78 @@ +getOptions($options, $cfg['DefaultTransformations']['TextLink']); + $url = (isset($options[0]) ? $options[0] : '') . ((isset($options[2]) && $options[2]) ? '' : $buffer); + /* Do not allow javascript links */ + if (! Sanitize::checkLink($url, true, true)) { + return htmlspecialchars($url); + } + return '' + . htmlspecialchars(isset($options[1]) ? $options[1] : $buffer) + . ''; + } + + + /* ~~~~~~~~~~~~~~~~~~~~ Getters and Setters ~~~~~~~~~~~~~~~~~~~~ */ + + /** + * Gets the transformation name of the specific plugin + * + * @return string + */ + public static function getName() + { + return "TextLink"; + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Plugins/Transformations/Input/Image_JPEG_Upload.php b/php/apps/phpmyadmin49/libraries/classes/Plugins/Transformations/Input/Image_JPEG_Upload.php new file mode 100644 index 00000000..2478658e --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Plugins/Transformations/Input/Image_JPEG_Upload.php @@ -0,0 +1,42 @@ +'; + } + $class = 'transform_IPToBin'; + $html .= ''; + + return $html; + } + + /* ~~~~~~~~~~~~~~~~~~~~ Getters and Setters ~~~~~~~~~~~~~~~~~~~~ */ + + /** + * Gets the transformation name of the plugin + * + * @return string + */ + public static function getName() + { + return "IPv4/IPv6 To Binary"; + } + + /** + * Gets the plugin`s MIME type + * + * @return string + */ + public static function getMIMEType() + { + return "Text"; + } + + /** + * Gets the plugin`s MIME subtype + * + * @return string + */ + public static function getMIMESubtype() + { + return "Plain"; + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Plugins/Transformations/Input/Text_Plain_JsonEditor.php b/php/apps/phpmyadmin49/libraries/classes/Plugins/Transformations/Input/Text_Plain_JsonEditor.php new file mode 100644 index 00000000..75ae8a5e --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Plugins/Transformations/Input/Text_Plain_JsonEditor.php @@ -0,0 +1,83 @@ +getHeader() + ->getScripts(); + $scripts->addFile('vendor/codemirror/lib/codemirror.js'); + $scripts->addFile('vendor/codemirror/mode/javascript/javascript.js'); + $scripts->addFile('vendor/codemirror/addon/runmode/runmode.js'); + $scripts->addFile('transformations/json.js'); + } + } + + /** + * Gets the transformation description of the specific plugin + * + * @return string + */ + public static function getInfo() + { + return __( + 'Formats text as JSON with syntax highlighting.' + ); + } + + /** + * Does the actual work of each specific transformations plugin. + * + * @param string $buffer text to be transformed + * @param array $options transformation options + * @param string $meta meta information + * + * @return string + */ + public function applyTransformation($buffer, array $options = array(), $meta = '') + { + return '
      ' . "\n"
      +        . htmlspecialchars($buffer) . "\n"
      +        . '
      '; + } + + /* ~~~~~~~~~~~~~~~~~~~~ Getters and Setters ~~~~~~~~~~~~~~~~~~~~ */ + + /** + * Gets the plugin`s MIME type + * + * @return string + */ + public static function getMIMEType() + { + return "Text"; + } + + /** + * Gets the plugin`s MIME subtype + * + * @return string + */ + public static function getMIMESubtype() + { + return "Plain"; + } + + /** + * Gets the transformation name of the specific plugin + * + * @return string + */ + public static function getName() + { + return "JSON"; + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Plugins/Transformations/Output/Text_Plain_Sql.php b/php/apps/phpmyadmin49/libraries/classes/Plugins/Transformations/Output/Text_Plain_Sql.php new file mode 100644 index 00000000..ebd9c5ec --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Plugins/Transformations/Output/Text_Plain_Sql.php @@ -0,0 +1,58 @@ +getHeader() + ->getScripts(); + $scripts->addFile('vendor/codemirror/lib/codemirror.js'); + $scripts->addFile('vendor/codemirror/mode/sql/sql.js'); + $scripts->addFile('vendor/codemirror/addon/runmode/runmode.js'); + $scripts->addFile('functions.js'); + } + } + + /** + * Gets the plugin`s MIME type + * + * @return string + */ + public static function getMIMEType() + { + return "Text"; + } + + /** + * Gets the plugin`s MIME subtype + * + * @return string + */ + public static function getMIMESubtype() + { + return "Plain"; + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Plugins/Transformations/Output/Text_Plain_Xml.php b/php/apps/phpmyadmin49/libraries/classes/Plugins/Transformations/Output/Text_Plain_Xml.php new file mode 100644 index 00000000..44bf6c0d --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Plugins/Transformations/Output/Text_Plain_Xml.php @@ -0,0 +1,98 @@ +getHeader() + ->getScripts(); + $scripts->addFile('vendor/codemirror/lib/codemirror.js'); + $scripts->addFile('vendor/codemirror/mode/xml/xml.js'); + $scripts->addFile('vendor/codemirror/addon/runmode/runmode.js'); + $scripts->addFile('transformations/xml.js'); + } + } + + /** + * Gets the transformation description of the specific plugin + * + * @return string + */ + public static function getInfo() + { + return __( + 'Formats text as XML with syntax highlighting.' + ); + } + + /** + * Does the actual work of each specific transformations plugin. + * + * @param string $buffer text to be transformed + * @param array $options transformation options + * @param string $meta meta information + * + * @return string + */ + public function applyTransformation($buffer, array $options = array(), $meta = '') + { + return '
      ' . "\n"
      +        . htmlspecialchars($buffer) . "\n"
      +        . '
      '; + } + + /* ~~~~~~~~~~~~~~~~~~~~ Getters and Setters ~~~~~~~~~~~~~~~~~~~~ */ + + /** + * Gets the plugin`s MIME type + * + * @return string + */ + public static function getMIMEType() + { + return "Text"; + } + + /** + * Gets the plugin`s MIME subtype + * + * @return string + */ + public static function getMIMESubtype() + { + return "Plain"; + } + + /** + * Gets the transformation name of the specific plugin + * + * @return string + */ + public static function getName() + { + return "XML"; + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Plugins/Transformations/README b/php/apps/phpmyadmin49/libraries/classes/Plugins/Transformations/README new file mode 100644 index 00000000..7d7a1255 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Plugins/Transformations/README @@ -0,0 +1,4 @@ +TRANSFORMATION USAGE (Garvin Hicking, ) +==================== + +See the documentation for complete instructions on how to use transformation plugins. diff --git a/php/apps/phpmyadmin49/libraries/classes/Plugins/Transformations/TEMPLATE b/php/apps/phpmyadmin49/libraries/classes/Plugins/Transformations/TEMPLATE new file mode 100644 index 00000000..0f9c48d3 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Plugins/Transformations/TEMPLATE @@ -0,0 +1,47 @@ + diff --git a/php/apps/phpmyadmin49/libraries/classes/Plugins/Transformations/TEMPLATE_ABSTRACT b/php/apps/phpmyadmin49/libraries/classes/Plugins/Transformations/TEMPLATE_ABSTRACT new file mode 100644 index 00000000..54ef4901 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Plugins/Transformations/TEMPLATE_ABSTRACT @@ -0,0 +1,74 @@ +mimetype contains the original MimeType of the field (i.e. 'text/plain', 'image/jpeg' etc.) + + return $buffer; + } + + /* ~~~~~~~~~~~~~~~~~~~~ Getters and Setters ~~~~~~~~~~~~~~~~~~~~ */ + + + /** + * Gets the TransformationName of the specific plugin + * + * @return string + */ + public static function getName() + { + return "[TransformationName]"; + } +} +?> diff --git a/php/apps/phpmyadmin49/libraries/classes/Plugins/Transformations/Text_Plain_Link.php b/php/apps/phpmyadmin49/libraries/classes/Plugins/Transformations/Text_Plain_Link.php new file mode 100644 index 00000000..90c4bd3b --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Plugins/Transformations/Text_Plain_Link.php @@ -0,0 +1,41 @@ + $value) { + if (isset($options[$key]) && $options[$key] !== '') { + $result[$key] = $options[$key]; + } else { + $result[$key] = $value; + } + } + + return $result; + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Plugins/TwoFactor/Application.php b/php/apps/phpmyadmin49/libraries/classes/Plugins/TwoFactor/Application.php new file mode 100644 index 00000000..c739b980 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Plugins/TwoFactor/Application.php @@ -0,0 +1,158 @@ +_google2fa = new Google2FA(); + $this->_google2fa->setWindow(8); + if (!isset($this->_twofactor->config['settings']['secret'])) { + $this->_twofactor->config['settings']['secret'] = ''; + } + } + + /** + * Get any property of this class + * + * @param string $property name of the property + * + * @return mixed|void if property exist, value of the relevant property + */ + public function __get($property) + { + switch ($property) { + case 'google2fa': + return $this->_google2fa; + } + } + + /** + * Checks authentication, returns true on success + * + * @return boolean + */ + public function check() + { + $this->_provided = false; + if (!isset($_POST['2fa_code'])) { + return false; + } + $this->_provided = true; + return $this->_google2fa->verifyKey( + $this->_twofactor->config['settings']['secret'], $_POST['2fa_code'] + ); + } + + /** + * Renders user interface to enter two-factor authentication + * + * @return string HTML code + */ + public function render() + { + return Template::get('login/twofactor/application')->render(); + } + + /** + * Renders user interface to configure two-factor authentication + * + * @return string HTML code + */ + public function setup() + { + $secret = $this->_twofactor->config['settings']['secret']; + $renderArray = ['secret' => $secret]; + if (extension_loaded('gd')) { + $inlineUrl = $this->_google2fa->getQRCodeInline( + 'phpMyAdmin (' . $this->getAppId(false) . ')', + $this->_twofactor->user, + $secret + ); + $renderArray['image'] = $inlineUrl; + } else { + $inlineUrl = $this->_google2fa->getQRCodeUrl( + 'phpMyAdmin (' . $this->getAppId(false) . ')', + $this->_twofactor->user, + $secret + ); + trigger_error( + __( + 'The gd PHP extension was not found.' + . ' The QRcode can not be displayed without the gd PHP extension.' + ), + E_USER_WARNING + ); + $renderArray['url'] = $inlineUrl; + } + return Template::get('login/twofactor/application_configure')->render($renderArray); + } + + /** + * Performs backend configuration + * + * @return boolean + */ + public function configure() + { + if (! isset($_SESSION['2fa_application_key'])) { + $_SESSION['2fa_application_key'] = $this->_google2fa->generateSecretKey(); + } + $this->_twofactor->config['settings']['secret'] = $_SESSION['2fa_application_key']; + + $result = $this->check(); + if ($result) { + unset($_SESSION['2fa_application_key']); + } + return $result; + } + + /** + * Get user visible name + * + * @return string + */ + public static function getName() + { + return __('Authentication Application (2FA)'); + } + + /** + * Get user visible description + * + * @return string + */ + public static function getDescription() + { + return __('Provides authentication using HOTP and TOTP applications such as FreeOTP, Google Authenticator or Authy.'); + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Plugins/TwoFactor/Invalid.php b/php/apps/phpmyadmin49/libraries/classes/Plugins/TwoFactor/Invalid.php new file mode 100644 index 00000000..b4fbfd5a --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Plugins/TwoFactor/Invalid.php @@ -0,0 +1,65 @@ +render(); + } + + /** + * Get user visible name + * + * @return string + */ + public static function getName() + { + return 'Invalid two-factor authentication'; + } + + /** + * Get user visible description + * + * @return string + */ + public static function getDescription() + { + return 'Error fallback only!'; + } +} + diff --git a/php/apps/phpmyadmin49/libraries/classes/Plugins/TwoFactor/Key.php b/php/apps/phpmyadmin49/libraries/classes/Plugins/TwoFactor/Key.php new file mode 100644 index 00000000..3ce4b236 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Plugins/TwoFactor/Key.php @@ -0,0 +1,198 @@ +_twofactor->config['settings']['registrations'])) { + $this->_twofactor->config['settings']['registrations'] = []; + } + } + + /** + * Returns array of U2F registration objects + * + * @return array + */ + public function getRegistrations() + { + $result = []; + foreach ($this->_twofactor->config['settings']['registrations'] as $index => $data) { + $reg = new \StdClass; + $reg->keyHandle = $data['keyHandle']; + $reg->publicKey = $data['publicKey']; + $reg->certificate = $data['certificate']; + $reg->counter = $data['counter']; + $reg->index = $index; + $result[] = $reg; + } + return $result; + } + + /** + * Checks authentication, returns true on success + * + * @return boolean + */ + public function check() + { + $this->_provided = false; + if (!isset($_POST['u2f_authentication_response']) || !isset($_SESSION['authenticationRequest'])) { + return false; + } + $this->_provided = true; + try { + $response = json_decode($_POST['u2f_authentication_response']); + if (is_null($response)) { + return false; + } + $authentication = U2FServer::authenticate( + $_SESSION['authenticationRequest'], + $this->getRegistrations(), + $response + ); + $this->_twofactor->config['settings']['registrations'][$authentication->index]['counter'] = $authentication->counter; + $this->_twofactor->save(); + return true; + } catch (U2FException $e) { + $this->_message = $e->getMessage(); + return false; + } + } + + /** + * Loads needed javascripts into the page + * + * @return void + */ + public function loadScripts() + { + $response = Response::getInstance(); + $scripts = $response->getHeader()->getScripts(); + $scripts->addFile('vendor/u2f-api-polyfill.js'); + $scripts->addFile('u2f.js'); + } + + /** + * Renders user interface to enter two-factor authentication + * + * @return string HTML code + */ + public function render() + { + $request = U2FServer::makeAuthentication( + $this->getRegistrations(), + $this->getAppId(true) + ); + $_SESSION['authenticationRequest'] = $request; + $this->loadScripts(); + return Template::get('login/twofactor/key')->render([ + 'request' => json_encode($request), + 'is_https' => $GLOBALS['PMA_Config']->isHttps(), + ]); + } + + /** + * Renders user interface to configure two-factor authentication + * + * @return string HTML code + */ + public function setup() + { + $registrationData = U2FServer::makeRegistration( + $this->getAppId(true), + $this->getRegistrations() + ); + $_SESSION['registrationRequest'] = $registrationData['request']; + + $this->loadScripts(); + return Template::get('login/twofactor/key_configure')->render([ + 'request' => json_encode($registrationData['request']), + 'signatures' => json_encode($registrationData['signatures']), + 'is_https' => $GLOBALS['PMA_Config']->isHttps(), + ]); + } + + /** + * Performs backend configuration + * + * @return boolean + */ + public function configure() + { + $this->_provided = false; + if (! isset($_POST['u2f_registration_response']) || ! isset($_SESSION['registrationRequest'])) { + return false; + } + $this->_provided = true; + try { + $response = json_decode($_POST['u2f_registration_response']); + if (is_null($response)) { + return false; + } + $registration = U2FServer::register( + $_SESSION['registrationRequest'], $response + ); + $this->_twofactor->config['settings']['registrations'][] = [ + 'keyHandle' => $registration->getKeyHandle(), + 'publicKey' => $registration->getPublicKey(), + 'certificate' => $registration->getCertificate(), + 'counter' => $registration->getCounter(), + ]; + return true; + } catch (U2FException $e) { + $this->_message = $e->getMessage(); + return false; + } + } + + /** + * Get user visible name + * + * @return string + */ + public static function getName() + { + return __('Hardware Security Key (FIDO U2F)'); + } + + /** + * Get user visible description + * + * @return string + */ + public static function getDescription() + { + return __('Provides authentication using hardware security tokens supporting FIDO U2F.'); + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Plugins/TwoFactor/Simple.php b/php/apps/phpmyadmin49/libraries/classes/Plugins/TwoFactor/Simple.php new file mode 100644 index 00000000..70d3a614 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Plugins/TwoFactor/Simple.php @@ -0,0 +1,64 @@ +render(); + } + + /** + * Get user visible name + * + * @return string + */ + public static function getName() + { + return __('Simple two-factor authentication'); + } + + /** + * Get user visible description + * + * @return string + */ + public static function getDescription() + { + return __('For testing purposes only!'); + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Plugins/TwoFactorPlugin.php b/php/apps/phpmyadmin49/libraries/classes/Plugins/TwoFactorPlugin.php new file mode 100644 index 00000000..8e91b786 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Plugins/TwoFactorPlugin.php @@ -0,0 +1,170 @@ +_twofactor = $twofactor; + $this->_provided = false; + $this->_message = ''; + } + + /** + * Returns authentication error message + * + * @return string + */ + public function getError() + { + if ($this->_provided) { + if (!empty($this->_message)) { + return Message::rawError( + sprintf(__('Two-factor authentication failed: %s'), $this->_message) + )->getDisplay(); + } + return Message::rawError( + __('Two-factor authentication failed.') + )->getDisplay(); + } + return ''; + } + + /** + * Checks authentication, returns true on success + * + * @return boolean + */ + public function check() + { + return true; + } + + /** + * Renders user interface to enter two-factor authentication + * + * @return string HTML code + */ + public function render() + { + return ''; + } + + /** + * Renders user interface to configure two-factor authentication + * + * @return string HTML code + */ + public function setup() + { + return ''; + } + + /** + * Performs backend configuration + * + * @return boolean + */ + public function configure() + { + return true; + } + + /** + * Get user visible name + * + * @return string + */ + public static function getName() + { + return __('No Two-Factor'); + } + + /** + * Get user visible description + * + * @return string + */ + public static function getDescription() + { + return __('Login using password only.'); + } + + /** + * Return an applicaiton ID + * + * Either hostname or hostname with scheme. + * + * @param boolean $return_url Whether to generate URL + * + * @return string + */ + public function getAppId($return_url) + { + global $PMA_Config; + + $url = $PMA_Config->get('PmaAbsoluteUri'); + $parsed = []; + if (!empty($url)) { + $parsed = parse_url($url); + } + if (empty($parsed['scheme'])) { + $parsed['scheme'] = $PMA_Config->isHttps() ? 'https' : 'http'; + } + if (empty($parsed['host'])) { + $parsed['host'] = Core::getenv('HTTP_HOST'); + } + if ($return_url) { + return $parsed['scheme'] . '://' . $parsed['host'] . (!empty($parsed['port']) ? ':' . $parsed['port'] : ''); + } else { + return $parsed['host']; + } + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Plugins/UploadInterface.php b/php/apps/phpmyadmin49/libraries/classes/Plugins/UploadInterface.php new file mode 100644 index 00000000..28bf9d0a --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Plugins/UploadInterface.php @@ -0,0 +1,33 @@ +upload plugins + * + * @package PhpMyAdmin + */ +namespace PhpMyAdmin\Plugins; + +/** + * Provides a common interface that will have to implemented by all of the + * import->upload plugins. + * + * @package PhpMyAdmin + */ +interface UploadInterface +{ + /** + * Gets the specific upload ID Key + * + * @return string ID Key + */ + public static function getIdKey(); + + /** + * Returns upload status. + * + * @param string $id upload id + * + * @return array|null + */ + public static function getUploadStatus($id); +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Properties/Options/Groups/OptionsPropertyMainGroup.php b/php/apps/phpmyadmin49/libraries/classes/Properties/Options/Groups/OptionsPropertyMainGroup.php new file mode 100644 index 00000000..d976da5a --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Properties/Options/Groups/OptionsPropertyMainGroup.php @@ -0,0 +1,33 @@ +_subgroupHeader; + } + + /** + * Sets the subgroup header + * + * @param \PhpMyAdmin\Properties\PropertyItem $subgroupHeader subgroup header + * + * @return void + */ + public function setSubgroupHeader($subgroupHeader) + { + $this->_subgroupHeader = $subgroupHeader; + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Properties/Options/Items/BoolPropertyItem.php b/php/apps/phpmyadmin49/libraries/classes/Properties/Options/Items/BoolPropertyItem.php new file mode 100644 index 00000000..2a45cf0b --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Properties/Options/Items/BoolPropertyItem.php @@ -0,0 +1,33 @@ +getProperties() == null + && in_array($property, $this->getProperties(), true) + ) { + return; + } + $this->_properties [] = $property; + } + + /** + * Removes a property from the group of properties + * + * @param OptionsPropertyItem $property the property instance to be removed + * from the group + * + * @return void + */ + public function removeProperty($property) + { + $this->_properties = array_diff( + $this->getProperties(), + array($property) + ); + } + + + /* ~~~~~~~~~~~~~~~~~~~~ Getters and Setters ~~~~~~~~~~~~~~~~~~~~ */ + + /** + * Gets the instance of the class + * + * @return array + */ + public function getGroup() + { + return $this; + } + + /** + * Gets the group of properties + * + * @return array + */ + public function getProperties() + { + return $this->_properties; + } + + /** + * Gets the number of properties + * + * @return int + */ + public function getNrOfProperties() + { + if (is_null($this->_properties)) { + return 0; + } + return count($this->_properties); + } + + /** + * Countable interface implementation. + * + * @return int + */ + public function count() { + return $this->getNrOfProperties(); + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Properties/Options/OptionsPropertyItem.php b/php/apps/phpmyadmin49/libraries/classes/Properties/Options/OptionsPropertyItem.php new file mode 100644 index 00000000..2e4d6ac0 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Properties/Options/OptionsPropertyItem.php @@ -0,0 +1,134 @@ +_name = $name; + } + if ($text) { + $this->_text = $text; + } + } + + /* ~~~~~~~~~~~~~~~~~~~~ Getters and Setters ~~~~~~~~~~~~~~~~~~~~ */ + + /** + * Gets the name + * + * @return string + */ + public function getName() + { + return $this->_name; + } + + /** + * Sets the name + * + * @param string $name name + * + * @return void + */ + public function setName($name) + { + $this->_name = $name; + } + + /** + * Gets the text + * + * @return string + */ + public function getText() + { + return $this->_text; + } + + /** + * Sets the text + * + * @param string $text text + * + * @return void + */ + public function setText($text) + { + $this->_text = $text; + } + + /** + * Gets the force parameter + * + * @return string + */ + public function getForce() + { + return $this->_force; + } + + /** + * Sets the force parameter + * + * @param string $force force parameter + * + * @return void + */ + public function setForce($force) + { + $this->_force = $force; + } + + /** + * Returns the property type ( either "options", or "plugin" ). + * + * @return string + */ + public function getPropertyType() + { + return "options"; + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Properties/Options/OptionsPropertyOneItem.php b/php/apps/phpmyadmin49/libraries/classes/Properties/Options/OptionsPropertyOneItem.php new file mode 100644 index 00000000..d44a7fe6 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Properties/Options/OptionsPropertyOneItem.php @@ -0,0 +1,159 @@ +_force_one; + } + + /** + * Sets the force parameter + * + * @param bool $force force parameter + * + * @return void + */ + public function setForce($force) + { + $this->_force_one = $force; + } + + /** + * Gets the values + * + * @return string + */ + public function getValues() + { + return $this->_values; + } + + /** + * Sets the values + * + * @param array $values values + * + * @return void + */ + public function setValues(array $values) + { + $this->_values = $values; + } + + /** + * Gets MySQL documentation pointer + * + * @return array + */ + public function getDoc() + { + return $this->_doc; + } + + /** + * Sets the doc + * + * @param string $doc MySQL documentation pointer + * + * @return void + */ + public function setDoc($doc) + { + $this->_doc = $doc; + } + + /** + * Gets the length + * + * @return int + */ + public function getLen() + { + return $this->_len; + } + + /** + * Sets the length + * + * @param int $len length + * + * @return void + */ + public function setLen($len) + { + $this->_len = $len; + } + + /** + * Gets the size + * + * @return int + */ + public function getSize() + { + return $this->_size; + } + + /** + * Sets the size + * + * @param int $size size + * + * @return void + */ + public function setSize($size) + { + $this->_size = $size; + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Properties/Plugins/ExportPluginProperties.php b/php/apps/phpmyadmin49/libraries/classes/Properties/Plugins/ExportPluginProperties.php new file mode 100644 index 00000000..a5a19da9 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Properties/Plugins/ExportPluginProperties.php @@ -0,0 +1,62 @@ +_forceFile; + } + + /** + * Sets the force file parameter + * + * @param bool $forceFile the force file parameter + * + * @return void + */ + public function setForceFile($forceFile) + { + $this->_forceFile = $forceFile; + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Properties/Plugins/ImportPluginProperties.php b/php/apps/phpmyadmin49/libraries/classes/Properties/Plugins/ImportPluginProperties.php new file mode 100644 index 00000000..bbcdd59e --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Properties/Plugins/ImportPluginProperties.php @@ -0,0 +1,31 @@ +_text; + } + + /** + * Sets the text + * + * @param string $text text + * + * @return void + */ + public function setText($text) + { + $this->_text = $text; + } + + /** + * Gets the extension + * + * @return string + */ + public function getExtension() + { + return $this->_extension; + } + + /** + * Sets the extension + * + * @param string $extension extension + * + * @return void + */ + public function setExtension($extension) + { + $this->_extension = $extension; + } + + /** + * Gets the options + * + * @return OptionsPropertyRootGroup + */ + public function getOptions() + { + return $this->_options; + } + + /** + * Sets the options + * + * @param OptionsPropertyRootGroup $options options + * + * @return void + */ + public function setOptions($options) + { + $this->_options = $options; + } + + /** + * Gets the options text + * + * @return string + */ + public function getOptionsText() + { + return $this->_optionsText; + } + + /** + * Sets the options text + * + * @param string $optionsText optionsText + * + * @return void + */ + public function setOptionsText($optionsText) + { + $this->_optionsText = $optionsText; + } + + /** + * Gets the MIME type + * + * @return string + */ + public function getMimeType() + { + return $this->_mimeType; + } + + /** + * Sets the MIME type + * + * @param string $mimeType MIME type + * + * @return void + */ + public function setMimeType($mimeType) + { + $this->_mimeType = $mimeType; + } + + /** + * Returns the property type ( either "options", or "plugin" ). + * + * @return string + */ + public function getPropertyType() + { + return "plugin"; + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Properties/Plugins/SchemaPluginProperties.php b/php/apps/phpmyadmin49/libraries/classes/Properties/Plugins/SchemaPluginProperties.php new file mode 100644 index 00000000..e3635337 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Properties/Plugins/SchemaPluginProperties.php @@ -0,0 +1,44 @@ +relation = new Relation(); + $this->_tableType = $type; + $server_id = $GLOBALS['server']; + if (! isset($_SESSION['tmpval'][$this->_tableType . '_tables'][$server_id]) + ) { + $_SESSION['tmpval'][$this->_tableType . '_tables'][$server_id] + = $this->_getPmaTable() ? $this->getFromDb() : array(); + } + $this->_tables + =& $_SESSION['tmpval'][$this->_tableType . '_tables'][$server_id]; + } + + /** + * Returns class instance. + * + * @param string $type the table type + * + * @return RecentFavoriteTable + */ + public static function getInstance($type) + { + if (! array_key_exists($type, self::$_instances)) { + self::$_instances[$type] = new RecentFavoriteTable($type); + } + return self::$_instances[$type]; + } + + /** + * Returns the recent/favorite tables array + * + * @return array + */ + public function getTables() + { + return $this->_tables; + } + + /** + * Returns recently used tables or favorite from phpMyAdmin database. + * + * @return array + */ + public function getFromDb() + { + // Read from phpMyAdmin database, if recent tables is not in session + $sql_query + = " SELECT `tables` FROM " . $this->_getPmaTable() . + " WHERE `username` = '" . $GLOBALS['dbi']->escapeString($GLOBALS['cfg']['Server']['user']) . "'"; + + $return = array(); + $result = $this->relation->queryAsControlUser($sql_query, false); + if ($result) { + $row = $GLOBALS['dbi']->fetchArray($result); + if (isset($row[0])) { + $return = json_decode($row[0], true); + } + } + return $return; + } + + /** + * Save recent/favorite tables into phpMyAdmin database. + * + * @return true|Message + */ + public function saveToDb() + { + $username = $GLOBALS['cfg']['Server']['user']; + $sql_query + = " REPLACE INTO " . $this->_getPmaTable() . " (`username`, `tables`)" . + " VALUES ('" . $GLOBALS['dbi']->escapeString($username) . "', '" + . $GLOBALS['dbi']->escapeString( + json_encode($this->_tables) + ) . "')"; + + $success = $GLOBALS['dbi']->tryQuery($sql_query, DatabaseInterface::CONNECT_CONTROL); + + if (! $success) { + $error_msg = ''; + switch ($this->_tableType) { + case 'recent': + $error_msg = __('Could not save recent table!'); + break; + + case 'favorite': + $error_msg = __('Could not save favorite table!'); + break; + } + $message = Message::error($error_msg); + $message->addMessage( + Message::rawError( + $GLOBALS['dbi']->getError(DatabaseInterface::CONNECT_CONTROL) + ), + '

      ' + ); + return $message; + } + return true; + } + + /** + * Trim recent.favorite table according to the + * NumRecentTables/NumFavoriteTables configuration. + * + * @return boolean True if trimming occurred + */ + public function trim() + { + $max = max( + $GLOBALS['cfg']['Num' . ucfirst($this->_tableType) . 'Tables'], 0 + ); + $trimming_occurred = count($this->_tables) > $max; + while (count($this->_tables) > $max) { + array_pop($this->_tables); + } + return $trimming_occurred; + } + + /** + * Return HTML ul. + * + * @return string + */ + public function getHtmlList() + { + $html = ''; + if (count($this->_tables)) { + if ($this->_tableType == 'recent') { + foreach ($this->_tables as $table) { + $html .= '
    • '; + } + } else { + foreach ($this->_tables as $table) { + $html .= ''; + } + } + } else { + $html .= ''; + } + return $html; + } + + /** + * Return HTML. + * + * @return string + */ + public function getHtml() + { + $html = '
      '; + if ($this->_tableType == 'recent') { + $html .= '' + . __('Recent') . '
        '; + } else { + $html .= '' + . __('Favorites') . '
          '; + } + $html .= $this->getHtmlList(); + $html .= '
      '; + return $html; + } + + /** + * Add recently used or favorite tables. + * + * @param string $db database name where the table is located + * @param string $table table name + * + * @return true|Message True if success, Message if not + */ + public function add($db, $table) + { + // If table does not exist, do not add._getPmaTable() + if (! $GLOBALS['dbi']->getColumns($db, $table)) { + return true; + } + + $table_arr = array(); + $table_arr['db'] = $db; + $table_arr['table'] = $table; + + // add only if this is new table + if (! isset($this->_tables[0]) || $this->_tables[0] != $table_arr) { + array_unshift($this->_tables, $table_arr); + $this->_tables = array_merge(array_unique($this->_tables, SORT_REGULAR)); + $this->trim(); + if ($this->_getPmaTable()) { + return $this->saveToDb(); + } + } + return true; + } + + /** + * Removes recent/favorite tables that don't exist. + * + * @param string $db database + * @param string $table table + * + * @return boolean|Message True if invalid and removed, False if not invalid, + * Message if error while removing + */ + public function removeIfInvalid($db, $table) + { + foreach ($this->_tables as $tbl) { + if ($tbl['db'] == $db && $tbl['table'] == $table) { + // TODO Figure out a better way to find the existence of a table + if (! $GLOBALS['dbi']->getColumns($tbl['db'], $tbl['table'])) { + return $this->remove($tbl['db'], $tbl['table']); + } + } + } + return false; + } + + /** + * Remove favorite tables. + * + * @param string $db database name where the table is located + * @param string $table table name + * + * @return true|Message True if success, Message if not + */ + public function remove($db, $table) + { + $table_arr = array(); + $table_arr['db'] = $db; + $table_arr['table'] = $table; + foreach ($this->_tables as $key => $value) { + if ($value['db'] == $db && $value['table'] == $table) { + unset($this->_tables[$key]); + } + } + if ($this->_getPmaTable()) { + return $this->saveToDb(); + } + return true; + } + + /** + * Generate Html for sync Favorite tables anchor. (from localStorage to pmadb) + * + * @return string + */ + public function getHtmlSyncFavoriteTables() + { + $retval = ''; + $server_id = $GLOBALS['server']; + if ($server_id == 0) { + return ''; + } + $cfgRelation = $this->relation->getRelationsParam(); + // Not to show this once list is synchronized. + if ($cfgRelation['favoritework'] && ! isset($_SESSION['tmpval']['favorites_synced'][$server_id])) { + $params = array('ajax_request' => true, 'favorite_table' => true, + 'sync_favorite_tables' => true); + $url = 'db_structure.php' . Url::getCommon($params); + $retval = ''; + } + return $retval; + } + + /** + * Generate Html to update recent tables. + * + * @return string html + */ + public static function getHtmlUpdateRecentTables() + { + $params = array('ajax_request' => true, 'recent_table' => true); + $url = 'index.php' . Url::getCommon($params); + $retval = ''; + return $retval; + } + + /** + * Reutrn the name of the configuration storage table + * + * @return string pma table name + */ + private function _getPmaTable() + { + $cfgRelation = $this->relation->getRelationsParam(); + if (! empty($cfgRelation['db']) + && ! empty($cfgRelation[$this->_tableType]) + ) { + return Util::backquote($cfgRelation['db']) . "." + . Util::backquote($cfgRelation[$this->_tableType]); + } + return null; + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Relation.php b/php/apps/phpmyadmin49/libraries/classes/Relation.php new file mode 100644 index 00000000..75ff20a8 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Relation.php @@ -0,0 +1,2117 @@ +query( + $sql, + DatabaseInterface::CONNECT_CONTROL, + $options, + $cache_affected_rows + ); + } else { + $result = @$GLOBALS['dbi']->tryQuery( + $sql, + DatabaseInterface::CONNECT_CONTROL, + $options, + $cache_affected_rows + ); + } // end if... else... + + if ($result) { + return $result; + } + + return false; + } + + /** + * Returns current relation parameters + * + * @return array $cfgRelation + */ + public function getRelationsParam() + { + if (empty($_SESSION['relation'][$GLOBALS['server']]) + || (empty($_SESSION['relation'][$GLOBALS['server']]['PMA_VERSION'])) + || $_SESSION['relation'][$GLOBALS['server']]['PMA_VERSION'] != PMA_VERSION + ) { + $_SESSION['relation'][$GLOBALS['server']] = $this->checkRelationsParam(); + } + + // just for BC but needs to be before getRelationsParamDiagnostic() + // which uses it + $GLOBALS['cfgRelation'] = $_SESSION['relation'][$GLOBALS['server']]; + + return $_SESSION['relation'][$GLOBALS['server']]; + } + + /** + * prints out diagnostic info for pma relation feature + * + * @param array $cfgRelation Relation configuration + * + * @return string + */ + public function getRelationsParamDiagnostic(array $cfgRelation) + { + $retval = '
      '; + + $messages = array(); + $messages['error'] = '' + . __('not OK') + . ''; + + $messages['ok'] = '' + . _pgettext('Correctly working', 'OK') + . ''; + + $messages['enabled'] = '' . __('Enabled') . ''; + $messages['disabled'] = '' . __('Disabled') . ''; + + if (strlen($cfgRelation['db']) == 0) { + $retval .= __('Configuration of pmadb…') . ' ' + . $messages['error'] + . Util::showDocu('setup', 'linked-tables') + . '
      ' . "\n" + . __('General relation features') + . ' ' . __('Disabled') + . '' . "\n"; + if ($GLOBALS['cfg']['ZeroConf']) { + if (strlen($GLOBALS['db']) == 0) { + $retval .= $this->getHtmlFixPmaTables(true, true); + } else { + $retval .= $this->getHtmlFixPmaTables(true); + } + } + } else { + $retval .= '' . "\n"; + + if (! $cfgRelation['allworks'] + && $GLOBALS['cfg']['ZeroConf'] + // Avoid showing a "Create missing tables" link if it's a + // problem of missing definition + && $this->arePmadbTablesDefined() + ) { + $retval .= $this->getHtmlFixPmaTables(false); + $retval .= '
      '; + } + + $retval .= $this->getDiagMessageForParameter( + 'pmadb', + $cfgRelation['db'], + $messages, + 'pmadb' + ); + $retval .= $this->getDiagMessageForParameter( + 'relation', + isset($cfgRelation['relation']), + $messages, + 'relation' + ); + $retval .= $this->getDiagMessageForFeature( + __('General relation features'), + 'relwork', + $messages + ); + $retval .= $this->getDiagMessageForParameter( + 'table_info', + isset($cfgRelation['table_info']), + $messages, + 'table_info' + ); + $retval .= $this->getDiagMessageForFeature( + __('Display Features'), + 'displaywork', + $messages + ); + $retval .= $this->getDiagMessageForParameter( + 'table_coords', + isset($cfgRelation['table_coords']), + $messages, + 'table_coords' + ); + $retval .= $this->getDiagMessageForParameter( + 'pdf_pages', + isset($cfgRelation['pdf_pages']), + $messages, + 'pdf_pages' + ); + $retval .= $this->getDiagMessageForFeature( + __('Designer and creation of PDFs'), + 'pdfwork', + $messages + ); + $retval .= $this->getDiagMessageForParameter( + 'column_info', + isset($cfgRelation['column_info']), + $messages, + 'column_info' + ); + $retval .= $this->getDiagMessageForFeature( + __('Displaying Column Comments'), + 'commwork', + $messages, + false + ); + $retval .= $this->getDiagMessageForFeature( + __('Browser transformation'), + 'mimework', + $messages + ); + if ($cfgRelation['commwork'] && ! $cfgRelation['mimework']) { + $retval .= ''; + } + $retval .= $this->getDiagMessageForParameter( + 'bookmarktable', + isset($cfgRelation['bookmark']), + $messages, + 'bookmark' + ); + $retval .= $this->getDiagMessageForFeature( + __('Bookmarked SQL query'), + 'bookmarkwork', + $messages + ); + $retval .= $this->getDiagMessageForParameter( + 'history', + isset($cfgRelation['history']), + $messages, + 'history' + ); + $retval .= $this->getDiagMessageForFeature( + __('SQL history'), + 'historywork', + $messages + ); + $retval .= $this->getDiagMessageForParameter( + 'recent', + isset($cfgRelation['recent']), + $messages, + 'recent' + ); + $retval .= $this->getDiagMessageForFeature( + __('Persistent recently used tables'), + 'recentwork', + $messages + ); + $retval .= $this->getDiagMessageForParameter( + 'favorite', + isset($cfgRelation['favorite']), + $messages, + 'favorite' + ); + $retval .= $this->getDiagMessageForFeature( + __('Persistent favorite tables'), + 'favoritework', + $messages + ); + $retval .= $this->getDiagMessageForParameter( + 'table_uiprefs', + isset($cfgRelation['table_uiprefs']), + $messages, + 'table_uiprefs' + ); + $retval .= $this->getDiagMessageForFeature( + __('Persistent tables\' UI preferences'), + 'uiprefswork', + $messages + ); + $retval .= $this->getDiagMessageForParameter( + 'tracking', + isset($cfgRelation['tracking']), + $messages, + 'tracking' + ); + $retval .= $this->getDiagMessageForFeature( + __('Tracking'), + 'trackingwork', + $messages + ); + $retval .= $this->getDiagMessageForParameter( + 'userconfig', + isset($cfgRelation['userconfig']), + $messages, + 'userconfig' + ); + $retval .= $this->getDiagMessageForFeature( + __('User preferences'), + 'userconfigwork', + $messages + ); + $retval .= $this->getDiagMessageForParameter( + 'users', + isset($cfgRelation['users']), + $messages, + 'users' + ); + $retval .= $this->getDiagMessageForParameter( + 'usergroups', + isset($cfgRelation['usergroups']), + $messages, + 'usergroups' + ); + $retval .= $this->getDiagMessageForFeature( + __('Configurable menus'), + 'menuswork', + $messages + ); + $retval .= $this->getDiagMessageForParameter( + 'navigationhiding', + isset($cfgRelation['navigationhiding']), + $messages, + 'navigationhiding' + ); + $retval .= $this->getDiagMessageForFeature( + __('Hide/show navigation items'), + 'navwork', + $messages + ); + $retval .= $this->getDiagMessageForParameter( + 'savedsearches', + isset($cfgRelation['savedsearches']), + $messages, + 'savedsearches' + ); + $retval .= $this->getDiagMessageForFeature( + __('Saving Query-By-Example searches'), + 'savedsearcheswork', + $messages + ); + $retval .= $this->getDiagMessageForParameter( + 'central_columns', + isset($cfgRelation['central_columns']), + $messages, + 'central_columns' + ); + $retval .= $this->getDiagMessageForFeature( + __('Managing Central list of columns'), + 'centralcolumnswork', + $messages + ); + $retval .= $this->getDiagMessageForParameter( + 'designer_settings', + isset($cfgRelation['designer_settings']), + $messages, + 'designer_settings' + ); + $retval .= $this->getDiagMessageForFeature( + __('Remembering Designer Settings'), + 'designersettingswork', + $messages + ); + $retval .= $this->getDiagMessageForParameter( + 'export_templates', + isset($cfgRelation['export_templates']), + $messages, + 'export_templates' + ); + $retval .= $this->getDiagMessageForFeature( + __('Saving export templates'), + 'exporttemplateswork', + $messages + ); + $retval .= '
      '; + $retval .= __( + 'Please see the documentation on how to' + . ' update your column_info table.' + ); + $retval .= Util::showDocu( + 'config', + 'cfg_Servers_column_info' + ); + $retval .= '
      ' . "\n"; + + if (! $cfgRelation['allworks']) { + + $retval .= '

      ' . __('Quick steps to set up advanced features:') + . '

      '; + + $items = array(); + $items[] = sprintf( + __( + 'Create the needed tables with the ' + . '%screate_tables.sql.' + ), + htmlspecialchars(SQL_DIR) + ) . ' ' . Util::showDocu('setup', 'linked-tables'); + $items[] = __('Create a pma user and give access to these tables.') . ' ' + . Util::showDocu('config', 'cfg_Servers_controluser'); + $items[] = __( + 'Enable advanced features in configuration file ' + . '(config.inc.php), for example by ' + . 'starting from config.sample.inc.php.' + ) . ' ' . Util::showDocu('setup', 'quick-install'); + $items[] = __( + 'Re-login to phpMyAdmin to load the updated configuration file.' + ); + + $retval .= Template::get('list/unordered')->render( + array('items' => $items,) + ); + } + } + + return $retval; + } + + /** + * prints out one diagnostic message for a feature + * + * @param string $feature_name feature name in a message string + * @param string $relation_parameter the $GLOBALS['cfgRelation'] parameter to check + * @param array $messages utility messages + * @param boolean $skip_line whether to skip a line after the message + * + * @return string + */ + public function getDiagMessageForFeature($feature_name, + $relation_parameter, array $messages, $skip_line = true + ) { + $retval = ' ' . $feature_name . ': '; + if (isset($GLOBALS['cfgRelation'][$relation_parameter]) + && $GLOBALS['cfgRelation'][$relation_parameter] + ) { + $retval .= $messages['enabled']; + } else { + $retval .= $messages['disabled']; + } + $retval .= ''; + if ($skip_line) { + $retval .= ' '; + } + return $retval; + } + + /** + * prints out one diagnostic message for a configuration parameter + * + * @param string $parameter config parameter name to display + * @param boolean $relationParameterSet whether this parameter is set + * @param array $messages utility messages + * @param string $docAnchor anchor in documentation + * + * @return string + */ + public function getDiagMessageForParameter($parameter, + $relationParameterSet, array $messages, $docAnchor + ) { + $retval = ''; + $retval .= '$cfg[\'Servers\'][$i][\'' . $parameter . '\'] ... '; + $retval .= ''; + if ($relationParameterSet) { + $retval .= $messages['ok']; + } else { + $retval .= sprintf( + $messages['error'], + Util::getDocuLink('config', 'cfg_Servers_' . $docAnchor) + ); + } + $retval .= '' . "\n"; + return $retval; + } + + /** + * Defines the relation parameters for the current user + * just a copy of the functions used for relations ;-) + * but added some stuff to check what will work + * + * @access protected + * @return array the relation parameters for the current user + */ + public function checkRelationsParam() + { + $cfgRelation = array(); + $cfgRelation['PMA_VERSION'] = PMA_VERSION; + + $workToTable = array( + 'relwork' => 'relation', + 'displaywork' => array('relation', 'table_info'), + 'bookmarkwork' => 'bookmarktable', + 'pdfwork' => array('table_coords', 'pdf_pages'), + 'commwork' => 'column_info', + 'mimework' => 'column_info', + 'historywork' => 'history', + 'recentwork' => 'recent', + 'favoritework' => 'favorite', + 'uiprefswork' => 'table_uiprefs', + 'trackingwork' => 'tracking', + 'userconfigwork' => 'userconfig', + 'menuswork' => array('users', 'usergroups'), + 'navwork' => 'navigationhiding', + 'savedsearcheswork' => 'savedsearches', + 'centralcolumnswork' => 'central_columns', + 'designersettingswork' => 'designer_settings', + 'exporttemplateswork' => 'export_templates', + ); + + foreach ($workToTable as $work => $table) { + $cfgRelation[$work] = false; + } + $cfgRelation['allworks'] = false; + $cfgRelation['user'] = null; + $cfgRelation['db'] = null; + + if ($GLOBALS['server'] == 0 + || empty($GLOBALS['cfg']['Server']['pmadb']) + || ! $GLOBALS['dbi']->selectDb( + $GLOBALS['cfg']['Server']['pmadb'], DatabaseInterface::CONNECT_CONTROL + ) + ) { + // No server selected -> no bookmark table + // we return the array with the falses in it, + // to avoid some 'Uninitialized string offset' errors later + $GLOBALS['cfg']['Server']['pmadb'] = false; + return $cfgRelation; + } + + $cfgRelation['user'] = $GLOBALS['cfg']['Server']['user']; + $cfgRelation['db'] = $GLOBALS['cfg']['Server']['pmadb']; + + // Now I just check if all tables that i need are present so I can for + // example enable relations but not pdf... + // I was thinking of checking if they have all required columns but I + // fear it might be too slow + + $tab_query = 'SHOW TABLES FROM ' + . Util::backquote( + $GLOBALS['cfg']['Server']['pmadb'] + ); + $tab_rs = $this->queryAsControlUser( + $tab_query, false, DatabaseInterface::QUERY_STORE + ); + + if (! $tab_rs) { + // query failed ... ? + //$GLOBALS['cfg']['Server']['pmadb'] = false; + return $cfgRelation; + } + + while ($curr_table = @$GLOBALS['dbi']->fetchRow($tab_rs)) { + if ($curr_table[0] == $GLOBALS['cfg']['Server']['bookmarktable']) { + $cfgRelation['bookmark'] = $curr_table[0]; + } elseif ($curr_table[0] == $GLOBALS['cfg']['Server']['relation']) { + $cfgRelation['relation'] = $curr_table[0]; + } elseif ($curr_table[0] == $GLOBALS['cfg']['Server']['table_info']) { + $cfgRelation['table_info'] = $curr_table[0]; + } elseif ($curr_table[0] == $GLOBALS['cfg']['Server']['table_coords']) { + $cfgRelation['table_coords'] = $curr_table[0]; + } elseif ($curr_table[0] == $GLOBALS['cfg']['Server']['column_info']) { + $cfgRelation['column_info'] = $curr_table[0]; + } elseif ($curr_table[0] == $GLOBALS['cfg']['Server']['pdf_pages']) { + $cfgRelation['pdf_pages'] = $curr_table[0]; + } elseif ($curr_table[0] == $GLOBALS['cfg']['Server']['history']) { + $cfgRelation['history'] = $curr_table[0]; + } elseif ($curr_table[0] == $GLOBALS['cfg']['Server']['recent']) { + $cfgRelation['recent'] = $curr_table[0]; + } elseif ($curr_table[0] == $GLOBALS['cfg']['Server']['favorite']) { + $cfgRelation['favorite'] = $curr_table[0]; + } elseif ($curr_table[0] == $GLOBALS['cfg']['Server']['table_uiprefs']) { + $cfgRelation['table_uiprefs'] = $curr_table[0]; + } elseif ($curr_table[0] == $GLOBALS['cfg']['Server']['tracking']) { + $cfgRelation['tracking'] = $curr_table[0]; + } elseif ($curr_table[0] == $GLOBALS['cfg']['Server']['userconfig']) { + $cfgRelation['userconfig'] = $curr_table[0]; + } elseif ($curr_table[0] == $GLOBALS['cfg']['Server']['users']) { + $cfgRelation['users'] = $curr_table[0]; + } elseif ($curr_table[0] == $GLOBALS['cfg']['Server']['usergroups']) { + $cfgRelation['usergroups'] = $curr_table[0]; + } elseif ($curr_table[0] == $GLOBALS['cfg']['Server']['navigationhiding']) { + $cfgRelation['navigationhiding'] = $curr_table[0]; + } elseif ($curr_table[0] == $GLOBALS['cfg']['Server']['savedsearches']) { + $cfgRelation['savedsearches'] = $curr_table[0]; + } elseif ($curr_table[0] == $GLOBALS['cfg']['Server']['central_columns']) { + $cfgRelation['central_columns'] = $curr_table[0]; + } elseif ($curr_table[0] == $GLOBALS['cfg']['Server']['designer_settings']) { + $cfgRelation['designer_settings'] = $curr_table[0]; + } elseif ($curr_table[0] == $GLOBALS['cfg']['Server']['export_templates']) { + $cfgRelation['export_templates'] = $curr_table[0]; + } + } // end while + $GLOBALS['dbi']->freeResult($tab_rs); + + if (isset($cfgRelation['relation'])) { + $cfgRelation['relwork'] = true; + } + + if (isset($cfgRelation['relation']) && isset($cfgRelation['table_info'])) { + $cfgRelation['displaywork'] = true; + } + + if (isset($cfgRelation['table_coords']) && isset($cfgRelation['pdf_pages'])) { + $cfgRelation['pdfwork'] = true; + } + + if (isset($cfgRelation['column_info'])) { + $cfgRelation['commwork'] = true; + // phpMyAdmin 4.3+ + // Check for input transformations upgrade. + $cfgRelation['mimework'] = $this->tryUpgradeTransformations(); + } + + if (isset($cfgRelation['history'])) { + $cfgRelation['historywork'] = true; + } + + if (isset($cfgRelation['recent'])) { + $cfgRelation['recentwork'] = true; + } + + if (isset($cfgRelation['favorite'])) { + $cfgRelation['favoritework'] = true; + } + + if (isset($cfgRelation['table_uiprefs'])) { + $cfgRelation['uiprefswork'] = true; + } + + if (isset($cfgRelation['tracking'])) { + $cfgRelation['trackingwork'] = true; + } + + if (isset($cfgRelation['userconfig'])) { + $cfgRelation['userconfigwork'] = true; + } + + if (isset($cfgRelation['bookmark'])) { + $cfgRelation['bookmarkwork'] = true; + } + + if (isset($cfgRelation['users']) && isset($cfgRelation['usergroups'])) { + $cfgRelation['menuswork'] = true; + } + + if (isset($cfgRelation['navigationhiding'])) { + $cfgRelation['navwork'] = true; + } + + if (isset($cfgRelation['savedsearches'])) { + $cfgRelation['savedsearcheswork'] = true; + } + + if (isset($cfgRelation['central_columns'])) { + $cfgRelation['centralcolumnswork'] = true; + } + + if (isset($cfgRelation['designer_settings'])) { + $cfgRelation['designersettingswork'] = true; + } + + if (isset($cfgRelation['export_templates'])) { + $cfgRelation['exporttemplateswork'] = true; + } + + $allWorks = true; + foreach ($workToTable as $work => $table) { + if (! $cfgRelation[$work]) { + if (is_string($table)) { + if (isset($GLOBALS['cfg']['Server'][$table]) + && $GLOBALS['cfg']['Server'][$table] !== false + ) { + $allWorks = false; + break; + } + } elseif (is_array($table)) { + $oneNull = false; + foreach ($table as $t) { + if (isset($GLOBALS['cfg']['Server'][$t]) + && $GLOBALS['cfg']['Server'][$t] === false + ) { + $oneNull = true; + break; + } + } + if (! $oneNull) { + $allWorks = false; + break; + } + } + } + } + $cfgRelation['allworks'] = $allWorks; + + return $cfgRelation; + } + + /** + * Check whether column_info table input transformation + * upgrade is required and try to upgrade silently + * + * @return bool false if upgrade failed + * + * @access public + */ + public function tryUpgradeTransformations() + { + // From 4.3, new input oriented transformation feature was introduced. + // Check whether column_info table has input transformation columns + $new_cols = array( + "input_transformation", + "input_transformation_options" + ); + $query = 'SHOW COLUMNS FROM ' + . Util::backquote($GLOBALS['cfg']['Server']['pmadb']) + . '.' . Util::backquote( + $GLOBALS['cfg']['Server']['column_info'] + ) + . ' WHERE Field IN (\'' . implode('\', \'', $new_cols) . '\')'; + $result = $this->queryAsControlUser( + $query, false, DatabaseInterface::QUERY_STORE + ); + if ($result) { + $rows = $GLOBALS['dbi']->numRows($result); + $GLOBALS['dbi']->freeResult($result); + // input transformations are present + // no need to upgrade + if ($rows === 2) { + return true; + // try silent upgrade without disturbing the user + } + + // read upgrade query file + $query = @file_get_contents(SQL_DIR . 'upgrade_column_info_4_3_0+.sql'); + // replace database name from query to with set in config.inc.php + $query = str_replace( + '`phpmyadmin`', + Util::backquote($GLOBALS['cfg']['Server']['pmadb']), + $query + ); + // replace pma__column_info table name from query + // to with set in config.inc.php + $query = str_replace( + '`pma__column_info`', + Util::backquote( + $GLOBALS['cfg']['Server']['column_info'] + ), + $query + ); + $GLOBALS['dbi']->tryMultiQuery($query, DatabaseInterface::CONNECT_CONTROL); + // skips result sets of query as we are not interested in it + while ($GLOBALS['dbi']->moreResults(DatabaseInterface::CONNECT_CONTROL) + && $GLOBALS['dbi']->nextResult(DatabaseInterface::CONNECT_CONTROL) + ) { + } + $error = $GLOBALS['dbi']->getError(DatabaseInterface::CONNECT_CONTROL); + // return true if no error exists otherwise false + return empty($error); + } + // some failure, either in upgrading or something else + // make some noise, time to wake up user. + return false; + } + + /** + * Gets all Relations to foreign tables for a given table or + * optionally a given column in a table + * + * @param string $db the name of the db to check for + * @param string $table the name of the table to check for + * @param string $column the name of the column to check for + * @param string $source the source for foreign key information + * + * @return array db,table,column + * + * @access public + */ + public function getForeigners($db, $table, $column = '', $source = 'both') + { + $cfgRelation = $this->getRelationsParam(); + $foreign = array(); + + if ($cfgRelation['relwork'] && ($source == 'both' || $source == 'internal')) { + $rel_query = ' + SELECT `master_field`, + `foreign_db`, + `foreign_table`, + `foreign_field` + FROM ' . Util::backquote($cfgRelation['db']) + . '.' . Util::backquote($cfgRelation['relation']) . ' + WHERE `master_db` = \'' . $GLOBALS['dbi']->escapeString($db) . '\' + AND `master_table` = \'' . $GLOBALS['dbi']->escapeString($table) + . '\' '; + if (strlen($column) > 0) { + $rel_query .= ' AND `master_field` = ' + . '\'' . $GLOBALS['dbi']->escapeString($column) . '\''; + } + $foreign = $GLOBALS['dbi']->fetchResult( + $rel_query, 'master_field', null, DatabaseInterface::CONNECT_CONTROL + ); + } + + if (($source == 'both' || $source == 'foreign') && strlen($table) > 0) { + $tableObj = new Table($table, $db); + $show_create_table = $tableObj->showCreate(); + if ($show_create_table) { + $parser = new Parser($show_create_table); + /** + * @var \PhpMyAdmin\SqlParser\Statements\CreateStatement $stmt + */ + $stmt = $parser->statements[0]; + $foreign['foreign_keys_data'] = TableUtils::getForeignKeys( + $stmt + ); + } + } + + /** + * Emulating relations for some information_schema tables + */ + $isInformationSchema = mb_strtolower($db) == 'information_schema'; + $isMysql = mb_strtolower($db) == 'mysql'; + if (($isInformationSchema || $isMysql) + && ($source == 'internal' || $source == 'both') + ) { + if ($isInformationSchema) { + $relations_key = 'information_schema_relations'; + include_once './libraries/information_schema_relations.inc.php'; + } else { + $relations_key = 'mysql_relations'; + include_once './libraries/mysql_relations.inc.php'; + } + if (isset($GLOBALS[$relations_key][$table])) { + foreach ($GLOBALS[$relations_key][$table] as $field => $relations) { + if ((strlen($column) === 0 || $column == $field) + && (! isset($foreign[$field]) + || strlen($foreign[$field]) === 0) + ) { + $foreign[$field] = $relations; + } + } + } + } + + return $foreign; + } + + /** + * Gets the display field of a table + * + * @param string $db the name of the db to check for + * @param string $table the name of the table to check for + * + * @return string field name + * + * @access public + */ + public function getDisplayField($db, $table) + { + $cfgRelation = $this->getRelationsParam(); + + /** + * Try to fetch the display field from DB. + */ + if ($cfgRelation['displaywork']) { + $disp_query = ' + SELECT `display_field` + FROM ' . Util::backquote($cfgRelation['db']) + . '.' . Util::backquote($cfgRelation['table_info']) . ' + WHERE `db_name` = \'' . $GLOBALS['dbi']->escapeString($db) . '\' + AND `table_name` = \'' . $GLOBALS['dbi']->escapeString($table) + . '\''; + + $row = $GLOBALS['dbi']->fetchSingleRow( + $disp_query, 'ASSOC', DatabaseInterface::CONNECT_CONTROL + ); + if (isset($row['display_field'])) { + return $row['display_field']; + } + } + + /** + * Emulating the display field for some information_schema tables. + */ + if ($db == 'information_schema') { + switch ($table) { + case 'CHARACTER_SETS': + return 'DESCRIPTION'; + case 'TABLES': + return 'TABLE_COMMENT'; + } + } + + /** + * Pick first char field + */ + $columns = $GLOBALS['dbi']->getColumnsFull($db, $table); + if ($columns) { + foreach ($columns as $column) { + if ($GLOBALS['dbi']->types->getTypeClass($column['DATA_TYPE']) == 'CHAR') { + return $column['COLUMN_NAME']; + } + } + } + return false; + } + + /** + * Gets the comments for all columns of a table or the db itself + * + * @param string $db the name of the db to check for + * @param string $table the name of the table to check for + * + * @return array [column_name] = comment + * + * @access public + */ + public function getComments($db, $table = '') + { + $comments = array(); + + if ($table != '') { + // MySQL native column comments + $columns = $GLOBALS['dbi']->getColumns($db, $table, null, true); + if ($columns) { + foreach ($columns as $column) { + if (! empty($column['Comment'])) { + $comments[$column['Field']] = $column['Comment']; + } + } + } + } else { + $comments[] = $this->getDbComment($db); + } + + return $comments; + } + + /** + * Gets the comment for a db + * + * @param string $db the name of the db to check for + * + * @return string comment + * + * @access public + */ + public function getDbComment($db) + { + $cfgRelation = $this->getRelationsParam(); + $comment = ''; + + if ($cfgRelation['commwork']) { + // pmadb internal db comment + $com_qry = " + SELECT `comment` + FROM " . Util::backquote($cfgRelation['db']) + . "." . Util::backquote($cfgRelation['column_info']) + . " + WHERE db_name = '" . $GLOBALS['dbi']->escapeString($db) . "' + AND table_name = '' + AND column_name = '(db_comment)'"; + $com_rs = $this->queryAsControlUser( + $com_qry, true, DatabaseInterface::QUERY_STORE + ); + + if ($com_rs && $GLOBALS['dbi']->numRows($com_rs) > 0) { + $row = $GLOBALS['dbi']->fetchAssoc($com_rs); + $comment = $row['comment']; + } + $GLOBALS['dbi']->freeResult($com_rs); + } + + return $comment; + } + + /** + * Gets the comment for a db + * + * @access public + * + * @return string comment + */ + public function getDbComments() + { + $cfgRelation = $this->getRelationsParam(); + $comments = array(); + + if ($cfgRelation['commwork']) { + // pmadb internal db comment + $com_qry = " + SELECT `db_name`, `comment` + FROM " . Util::backquote($cfgRelation['db']) + . "." . Util::backquote($cfgRelation['column_info']) + . " + WHERE `column_name` = '(db_comment)'"; + $com_rs = $this->queryAsControlUser( + $com_qry, true, DatabaseInterface::QUERY_STORE + ); + + if ($com_rs && $GLOBALS['dbi']->numRows($com_rs) > 0) { + while ($row = $GLOBALS['dbi']->fetchAssoc($com_rs)) { + $comments[$row['db_name']] = $row['comment']; + } + } + $GLOBALS['dbi']->freeResult($com_rs); + } + + return $comments; + } + + /** + * Set a database comment to a certain value. + * + * @param string $db the name of the db + * @param string $comment the value of the column + * + * @return boolean true, if comment-query was made. + * + * @access public + */ + public function setDbComment($db, $comment = '') + { + $cfgRelation = $this->getRelationsParam(); + + if (! $cfgRelation['commwork']) { + return false; + } + + if (strlen($comment) > 0) { + $upd_query = 'INSERT INTO ' + . Util::backquote($cfgRelation['db']) . '.' + . Util::backquote($cfgRelation['column_info']) + . ' (`db_name`, `table_name`, `column_name`, `comment`)' + . ' VALUES (\'' + . $GLOBALS['dbi']->escapeString($db) + . "', '', '(db_comment)', '" + . $GLOBALS['dbi']->escapeString($comment) + . "') " + . ' ON DUPLICATE KEY UPDATE ' + . "`comment` = '" . $GLOBALS['dbi']->escapeString($comment) . "'"; + } else { + $upd_query = 'DELETE FROM ' + . Util::backquote($cfgRelation['db']) . '.' + . Util::backquote($cfgRelation['column_info']) + . ' WHERE `db_name` = \'' . $GLOBALS['dbi']->escapeString($db) + . '\' + AND `table_name` = \'\' + AND `column_name` = \'(db_comment)\''; + } + + if (isset($upd_query)) { + return $this->queryAsControlUser($upd_query); + } + + return false; + } + + /** + * Set a SQL history entry + * + * @param string $db the name of the db + * @param string $table the name of the table + * @param string $username the username + * @param string $sqlquery the sql query + * + * @return void + * + * @access public + */ + public function setHistory($db, $table, $username, $sqlquery) + { + $maxCharactersInDisplayedSQL = $GLOBALS['cfg']['MaxCharactersInDisplayedSQL']; + // Prevent to run this automatically on Footer class destroying in testsuite + if (defined('TESTSUITE') + || mb_strlen($sqlquery) > $maxCharactersInDisplayedSQL + ) { + return; + } + + $cfgRelation = $this->getRelationsParam(); + + if (! isset($_SESSION['sql_history'])) { + $_SESSION['sql_history'] = array(); + } + + $_SESSION['sql_history'][] = array( + 'db' => $db, + 'table' => $table, + 'sqlquery' => $sqlquery, + ); + + if (count($_SESSION['sql_history']) > $GLOBALS['cfg']['QueryHistoryMax']) { + // history should not exceed a maximum count + array_shift($_SESSION['sql_history']); + } + + if (! $cfgRelation['historywork'] || ! $GLOBALS['cfg']['QueryHistoryDB']) { + return; + } + + $this->queryAsControlUser( + 'INSERT INTO ' + . Util::backquote($cfgRelation['db']) . '.' + . Util::backquote($cfgRelation['history']) . ' + (`username`, + `db`, + `table`, + `timevalue`, + `sqlquery`) + VALUES + (\'' . $GLOBALS['dbi']->escapeString($username) . '\', + \'' . $GLOBALS['dbi']->escapeString($db) . '\', + \'' . $GLOBALS['dbi']->escapeString($table) . '\', + NOW(), + \'' . $GLOBALS['dbi']->escapeString($sqlquery) . '\')' + ); + + $this->purgeHistory($username); + } + + /** + * Gets a SQL history entry + * + * @param string $username the username + * + * @return array list of history items + * + * @access public + */ + public function getHistory($username) + { + $cfgRelation = $this->getRelationsParam(); + + if (! $cfgRelation['historywork']) { + return false; + } + + /** + * if db-based history is disabled but there exists a session-based + * history, use it + */ + if (! $GLOBALS['cfg']['QueryHistoryDB']) { + if (isset($_SESSION['sql_history'])) { + return array_reverse($_SESSION['sql_history']); + } + return false; + } + + $hist_query = ' + SELECT `db`, + `table`, + `sqlquery`, + `timevalue` + FROM ' . Util::backquote($cfgRelation['db']) + . '.' . Util::backquote($cfgRelation['history']) . ' + WHERE `username` = \'' . $GLOBALS['dbi']->escapeString($username) . '\' + ORDER BY `id` DESC'; + + return $GLOBALS['dbi']->fetchResult( + $hist_query, null, null, DatabaseInterface::CONNECT_CONTROL + ); + } + + /** + * purges SQL history + * + * deletes entries that exceeds $cfg['QueryHistoryMax'], oldest first, for the + * given user + * + * @param string $username the username + * + * @return void + * + * @access public + */ + public function purgeHistory($username) + { + $cfgRelation = $this->getRelationsParam(); + if (! $GLOBALS['cfg']['QueryHistoryDB'] || ! $cfgRelation['historywork']) { + return; + } + + if (! $cfgRelation['historywork']) { + return; + } + + $search_query = ' + SELECT `timevalue` + FROM ' . Util::backquote($cfgRelation['db']) + . '.' . Util::backquote($cfgRelation['history']) . ' + WHERE `username` = \'' . $GLOBALS['dbi']->escapeString($username) . '\' + ORDER BY `timevalue` DESC + LIMIT ' . $GLOBALS['cfg']['QueryHistoryMax'] . ', 1'; + + if ($max_time = $GLOBALS['dbi']->fetchValue( + $search_query, 0, 0, DatabaseInterface::CONNECT_CONTROL + )) { + $this->queryAsControlUser( + 'DELETE FROM ' + . Util::backquote($cfgRelation['db']) . '.' + . Util::backquote($cfgRelation['history']) . ' + WHERE `username` = \'' . $GLOBALS['dbi']->escapeString($username) + . '\' + AND `timevalue` <= \'' . $max_time . '\'' + ); + } + } + + /** + * Prepares the dropdown for one mode + * + * @param array $foreign the keys and values for foreigns + * @param string $data the current data of the dropdown + * @param string $mode the needed mode + * + * @return array the '; + } elseif ($mode == 'id-content') { + $reloptions[] = $reloption . '>' + . htmlspecialchars($key) . ' - ' . $value . ''; + } elseif ($mode == 'id-only') { + $reloptions[] = $reloption . '>' + . htmlspecialchars($key) . ''; + } + } // end foreach + + return $reloptions; + } + + /** + * Outputs dropdown with values of foreign fields + * + * @param array $disp_row array of the displayed row + * @param string $foreign_field the foreign field + * @param string $foreign_display the foreign field to display + * @param string $data the current data of the dropdown (field in row) + * @param int $max maximum number of items in the dropdown + * + * @return string the '; + $top_count = count($top); + if ($max == -1 || $top_count < $max) { + $ret .= implode('', $top); + if ($foreign_display && $top_count > 0) { + // this empty option is to visually mark the beginning of the + // second series of values (bottom) + $ret .= ''; + } + } + if ($foreign_display) { + $ret .= implode('', $bottom); + } + + return $ret; + } + + /** + * Gets foreign keys in preparation for a drop-down selector + * + * @param array|boolean $foreigners array of the foreign keys + * @param string $field the foreign field name + * @param bool $override_total whether to override the total + * @param string $foreign_filter a possible filter + * @param string $foreign_limit a possible LIMIT clause + * @param bool $get_total optional, whether to get total num of rows + * in $foreignData['the_total;] + * (has an effect of performance) + * + * @return array data about the foreign keys + * + * @access public + */ + public function getForeignData( + $foreigners, $field, $override_total, + $foreign_filter, $foreign_limit, $get_total=false + ) { + // we always show the foreign field in the drop-down; if a display + // field is defined, we show it besides the foreign field + $foreign_link = false; + do { + if (! $foreigners) { + break; + } + $foreigner = $this->searchColumnInForeigners($foreigners, $field); + if ($foreigner != false) { + $foreign_db = $foreigner['foreign_db']; + $foreign_table = $foreigner['foreign_table']; + $foreign_field = $foreigner['foreign_field']; + } else { + break; + } + + // Count number of rows in the foreign table. Currently we do + // not use a drop-down if more than ForeignKeyMaxLimit rows in the + // foreign table, + // for speed reasons and because we need a better interface for this. + // + // We could also do the SELECT anyway, with a LIMIT, and ensure that + // the current value of the field is one of the choices. + + // Check if table has more rows than specified by + // $GLOBALS['cfg']['ForeignKeyMaxLimit'] + $moreThanLimit = $GLOBALS['dbi']->getTable($foreign_db, $foreign_table) + ->checkIfMinRecordsExist($GLOBALS['cfg']['ForeignKeyMaxLimit']); + + if ($override_total == true + || !$moreThanLimit + ) { + // foreign_display can be false if no display field defined: + $foreign_display = $this->getDisplayField($foreign_db, $foreign_table); + + $f_query_main = 'SELECT ' . Util::backquote($foreign_field) + . ( + ($foreign_display == false) + ? '' + : ', ' . Util::backquote($foreign_display) + ); + $f_query_from = ' FROM ' . Util::backquote($foreign_db) + . '.' . Util::backquote($foreign_table); + $f_query_filter = empty($foreign_filter) ? '' : ' WHERE ' + . Util::backquote($foreign_field) + . ' LIKE "%' . $GLOBALS['dbi']->escapeString($foreign_filter) . '%"' + . ( + ($foreign_display == false) + ? '' + : ' OR ' . Util::backquote($foreign_display) + . ' LIKE "%' . $GLOBALS['dbi']->escapeString($foreign_filter) + . '%"' + ); + $f_query_order = ($foreign_display == false) ? '' :' ORDER BY ' + . Util::backquote($foreign_table) . '.' + . Util::backquote($foreign_display); + + $f_query_limit = ! empty($foreign_limit) ? ($foreign_limit) : ''; + + if (!empty($foreign_filter)) { + $the_total = $GLOBALS['dbi']->fetchValue( + 'SELECT COUNT(*)' . $f_query_from . $f_query_filter + ); + if ($the_total === false) { + $the_total = 0; + } + } + + $disp = $GLOBALS['dbi']->tryQuery( + $f_query_main . $f_query_from . $f_query_filter + . $f_query_order . $f_query_limit + ); + if ($disp && $GLOBALS['dbi']->numRows($disp) > 0) { + // If a resultset has been created, pre-cache it in the $disp_row + // array. This helps us from not needing to use mysql_data_seek by + // accessing a pre-cached PHP array. Usually those resultsets are + // not that big, so a performance hit should not be expected. + $disp_row = array(); + while ($single_disp_row = @$GLOBALS['dbi']->fetchAssoc($disp)) { + $disp_row[] = $single_disp_row; + } + @$GLOBALS['dbi']->freeResult($disp); + } else { + // Either no data in the foreign table or + // user does not have select permission to foreign table/field + // Show an input field with a 'Browse foreign values' link + $disp_row = null; + $foreign_link = true; + } + } else { + $disp_row = null; + $foreign_link = true; + } + } while (false); + + if ($get_total) { + $the_total = $GLOBALS['dbi']->getTable($foreign_db, $foreign_table) + ->countRecords(true); + } + + $foreignData = array(); + $foreignData['foreign_link'] = $foreign_link; + $foreignData['the_total'] = isset($the_total) ? $the_total : null; + $foreignData['foreign_display'] = ( + isset($foreign_display) ? $foreign_display : null + ); + $foreignData['disp_row'] = isset($disp_row) ? $disp_row : null; + $foreignData['foreign_field'] = isset($foreign_field) ? $foreign_field : null; + + return $foreignData; + } + + /** + * Rename a field in relation tables + * + * usually called after a column in a table was renamed + * + * @param string $db database name + * @param string $table table name + * @param string $field old field name + * @param string $new_name new field name + * + * @return void + */ + public function renameField($db, $table, $field, $new_name) + { + $cfgRelation = $this->getRelationsParam(); + + if ($cfgRelation['displaywork']) { + $table_query = 'UPDATE ' + . Util::backquote($cfgRelation['db']) . '.' + . Util::backquote($cfgRelation['table_info']) + . ' SET display_field = \'' . $GLOBALS['dbi']->escapeString( + $new_name + ) . '\'' + . ' WHERE db_name = \'' . $GLOBALS['dbi']->escapeString($db) + . '\'' + . ' AND table_name = \'' . $GLOBALS['dbi']->escapeString($table) + . '\'' + . ' AND display_field = \'' . $GLOBALS['dbi']->escapeString($field) + . '\''; + $this->queryAsControlUser($table_query); + } + + if ($cfgRelation['relwork']) { + $table_query = 'UPDATE ' + . Util::backquote($cfgRelation['db']) . '.' + . Util::backquote($cfgRelation['relation']) + . ' SET master_field = \'' . $GLOBALS['dbi']->escapeString( + $new_name + ) . '\'' + . ' WHERE master_db = \'' . $GLOBALS['dbi']->escapeString($db) + . '\'' + . ' AND master_table = \'' . $GLOBALS['dbi']->escapeString($table) + . '\'' + . ' AND master_field = \'' . $GLOBALS['dbi']->escapeString($field) + . '\''; + $this->queryAsControlUser($table_query); + + $table_query = 'UPDATE ' + . Util::backquote($cfgRelation['db']) . '.' + . Util::backquote($cfgRelation['relation']) + . ' SET foreign_field = \'' . $GLOBALS['dbi']->escapeString( + $new_name + ) . '\'' + . ' WHERE foreign_db = \'' . $GLOBALS['dbi']->escapeString($db) + . '\'' + . ' AND foreign_table = \'' . $GLOBALS['dbi']->escapeString($table) + . '\'' + . ' AND foreign_field = \'' . $GLOBALS['dbi']->escapeString($field) + . '\''; + $this->queryAsControlUser($table_query); + } + } + + + /** + * Performs SQL query used for renaming table. + * + * @param string $table Relation table to use + * @param string $source_db Source database name + * @param string $target_db Target database name + * @param string $source_table Source table name + * @param string $target_table Target table name + * @param string $db_field Name of database field + * @param string $table_field Name of table field + * + * @return void + */ + public function renameSingleTable($table, + $source_db, $target_db, + $source_table, $target_table, + $db_field, $table_field + ) { + $query = 'UPDATE ' + . Util::backquote($GLOBALS['cfgRelation']['db']) . '.' + . Util::backquote($GLOBALS['cfgRelation'][$table]) + . ' SET ' + . $db_field . ' = \'' . $GLOBALS['dbi']->escapeString($target_db) + . '\', ' + . $table_field . ' = \'' . $GLOBALS['dbi']->escapeString($target_table) + . '\'' + . ' WHERE ' + . $db_field . ' = \'' . $GLOBALS['dbi']->escapeString($source_db) . '\'' + . ' AND ' + . $table_field . ' = \'' . $GLOBALS['dbi']->escapeString($source_table) + . '\''; + $this->queryAsControlUser($query); + } + + + /** + * Rename a table in relation tables + * + * usually called after table has been moved + * + * @param string $source_db Source database name + * @param string $target_db Target database name + * @param string $source_table Source table name + * @param string $target_table Target table name + * + * @return void + */ + public function renameTable($source_db, $target_db, $source_table, $target_table) + { + // Move old entries from PMA-DBs to new table + if ($GLOBALS['cfgRelation']['commwork']) { + $this->renameSingleTable( + 'column_info', + $source_db, $target_db, + $source_table, $target_table, + 'db_name', 'table_name' + ); + } + + // updating bookmarks is not possible since only a single table is + // moved, and not the whole DB. + + if ($GLOBALS['cfgRelation']['displaywork']) { + $this->renameSingleTable( + 'table_info', + $source_db, $target_db, + $source_table, $target_table, + 'db_name', 'table_name' + ); + } + + if ($GLOBALS['cfgRelation']['relwork']) { + $this->renameSingleTable( + 'relation', + $source_db, $target_db, + $source_table, $target_table, + 'foreign_db', 'foreign_table' + ); + + $this->renameSingleTable( + 'relation', + $source_db, $target_db, + $source_table, $target_table, + 'master_db', 'master_table' + ); + } + + if ($GLOBALS['cfgRelation']['pdfwork']) { + if ($source_db == $target_db) { + // rename within the database can be handled + $this->renameSingleTable( + 'table_coords', + $source_db, $target_db, + $source_table, $target_table, + 'db_name', 'table_name' + ); + } else { + // if the table is moved out of the database we can no loger keep the + // record for table coordinate + $remove_query = "DELETE FROM " + . Util::backquote($GLOBALS['cfgRelation']['db']) . "." + . Util::backquote($GLOBALS['cfgRelation']['table_coords']) + . " WHERE db_name = '" . $GLOBALS['dbi']->escapeString($source_db) . "'" + . " AND table_name = '" . $GLOBALS['dbi']->escapeString($source_table) + . "'"; + $this->queryAsControlUser($remove_query); + } + } + + if ($GLOBALS['cfgRelation']['uiprefswork']) { + $this->renameSingleTable( + 'table_uiprefs', + $source_db, $target_db, + $source_table, $target_table, + 'db_name', 'table_name' + ); + } + + if ($GLOBALS['cfgRelation']['navwork']) { + // update hidden items inside table + $this->renameSingleTable( + 'navigationhiding', + $source_db, $target_db, + $source_table, $target_table, + 'db_name', 'table_name' + ); + + // update data for hidden table + $query = "UPDATE " + . Util::backquote($GLOBALS['cfgRelation']['db']) . "." + . Util::backquote( + $GLOBALS['cfgRelation']['navigationhiding'] + ) + . " SET db_name = '" . $GLOBALS['dbi']->escapeString($target_db) + . "'," + . " item_name = '" . $GLOBALS['dbi']->escapeString($target_table) + . "'" + . " WHERE db_name = '" . $GLOBALS['dbi']->escapeString($source_db) + . "'" + . " AND item_name = '" . $GLOBALS['dbi']->escapeString($source_table) + . "'" + . " AND item_type = 'table'"; + $this->queryAsControlUser($query); + } + } + + /** + * Create a PDF page + * + * @param string $newpage name of the new PDF page + * @param array $cfgRelation Relation configuration + * @param string $db database name + * + * @return int $pdf_page_number + */ + public function createPage($newpage, array $cfgRelation, $db) + { + if (! isset($newpage) || $newpage == '') { + $newpage = __('no description'); + } + $ins_query = 'INSERT INTO ' + . Util::backquote($GLOBALS['cfgRelation']['db']) . '.' + . Util::backquote($cfgRelation['pdf_pages']) + . ' (db_name, page_descr)' + . ' VALUES (\'' + . $GLOBALS['dbi']->escapeString($db) . '\', \'' + . $GLOBALS['dbi']->escapeString($newpage) . '\')'; + $this->queryAsControlUser($ins_query, false); + + return $GLOBALS['dbi']->insertId(DatabaseInterface::CONNECT_CONTROL); + } + + /** + * Get child table references for a table column. + * This works only if 'DisableIS' is false. An empty array is returned otherwise. + * + * @param string $db name of master table db. + * @param string $table name of master table. + * @param string $column name of master table column. + * + * @return array $child_references + */ + public function getChildReferences($db, $table, $column = '') + { + $child_references = array(); + if (! $GLOBALS['cfg']['Server']['DisableIS']) { + $rel_query = "SELECT `column_name`, `table_name`," + . " `table_schema`, `referenced_column_name`" + . " FROM `information_schema`.`key_column_usage`" + . " WHERE `referenced_table_name` = '" + . $GLOBALS['dbi']->escapeString($table) . "'" + . " AND `referenced_table_schema` = '" + . $GLOBALS['dbi']->escapeString($db) . "'"; + if ($column) { + $rel_query .= " AND `referenced_column_name` = '" + . $GLOBALS['dbi']->escapeString($column) . "'"; + } + + $child_references = $GLOBALS['dbi']->fetchResult( + $rel_query, array('referenced_column_name', null) + ); + } + return $child_references; + } + + /** + * Check child table references and foreign key for a table column. + * + * @param string $db name of master table db. + * @param string $table name of master table. + * @param string $column name of master table column. + * @param array|null $foreigners_full foreiners array for the whole table. + * @param array|null $child_references_full child references for the whole table. + * + * @return array $column_status telling about references if foreign key. + */ + public function checkChildForeignReferences( + $db, $table, $column, $foreigners_full = null, $child_references_full = null + ) { + $column_status = array(); + $column_status['isEditable'] = false; + $column_status['isReferenced'] = false; + $column_status['isForeignKey'] = false; + $column_status['references'] = array(); + + $foreigners = array(); + if ($foreigners_full !== null) { + if (isset($foreigners_full[$column])) { + $foreigners[$column] = $foreigners_full[$column]; + } + if (isset($foreigners_full['foreign_keys_data'])) { + $foreigners['foreign_keys_data'] = $foreigners_full['foreign_keys_data']; + } + } else { + $foreigners = $this->getForeigners($db, $table, $column, 'foreign'); + } + $foreigner = $this->searchColumnInForeigners($foreigners, $column); + + $child_references = array(); + if ($child_references_full !== null) { + if (isset($child_references_full[$column])) { + $child_references = $child_references_full[$column]; + } + } else { + $child_references = $this->getChildReferences($db, $table, $column); + } + + if (sizeof($child_references, 0) > 0 + || $foreigner + ) { + if (sizeof($child_references, 0) > 0) { + $column_status['isReferenced'] = true; + foreach ($child_references as $columns) { + array_push( + $column_status['references'], + Util::backquote($columns['table_schema']) + . '.' . Util::backquote($columns['table_name']) + ); + } + } + + if ($foreigner) { + $column_status['isForeignKey'] = true; + } + } else { + $column_status['isEditable'] = true; + } + + return $column_status; + } + + /** + * Search a table column in foreign data. + * + * @param array $foreigners Table Foreign data + * @param string $column Column name + * + * @return bool|array + */ + public function searchColumnInForeigners(array $foreigners, $column) + { + if (isset($foreigners[$column])) { + return $foreigners[$column]; + } + + $foreigner = array(); + foreach ($foreigners['foreign_keys_data'] as $one_key) { + $column_index = array_search($column, $one_key['index_list']); + if ($column_index !== false) { + $foreigner['foreign_field'] + = $one_key['ref_index_list'][$column_index]; + $foreigner['foreign_db'] = isset($one_key['ref_db_name']) + ? $one_key['ref_db_name'] + : $GLOBALS['db']; + $foreigner['foreign_table'] = $one_key['ref_table_name']; + $foreigner['constraint'] = $one_key['constraint']; + $foreigner['on_update'] = isset($one_key['on_update']) + ? $one_key['on_update'] + : 'RESTRICT'; + $foreigner['on_delete'] = isset($one_key['on_delete']) + ? $one_key['on_delete'] + : 'RESTRICT'; + + return $foreigner; + } + } + + return false; + } + + /** + * Returns default PMA table names and their create queries. + * + * @return array table name, create query + */ + public function getDefaultPmaTableNames() + { + $pma_tables = array(); + $create_tables_file = file_get_contents( + SQL_DIR . 'create_tables.sql' + ); + + $queries = explode(';', $create_tables_file); + + foreach ($queries as $query) { + if (preg_match( + '/CREATE TABLE IF NOT EXISTS `(.*)` \(/', + $query, + $table + ) + ) { + $pma_tables[$table[1]] = $query . ';'; + } + } + + return $pma_tables; + } + + /** + * Create a table named phpmyadmin to be used as configuration storage + * + * @return bool + */ + public function createPmaDatabase() + { + $GLOBALS['dbi']->tryQuery("CREATE DATABASE IF NOT EXISTS `phpmyadmin`"); + if ($error = $GLOBALS['dbi']->getError()) { + if ($GLOBALS['errno'] == 1044) { + $GLOBALS['message'] = __( + 'You do not have necessary privileges to create a database named' + . ' \'phpmyadmin\'. You may go to \'Operations\' tab of any' + . ' database to set up the phpMyAdmin configuration storage there.' + ); + } else { + $GLOBALS['message'] = $error; + } + return false; + } + return true; + } + + /** + * Creates PMA tables in the given db, updates if already exists. + * + * @param string $db database + * @param boolean $create whether to create tables if they don't exist. + * + * @return void + */ + public function fixPmaTables($db, $create = true) + { + $tablesToFeatures = array( + 'pma__bookmark' => 'bookmarktable', + 'pma__relation' => 'relation', + 'pma__table_info' => 'table_info', + 'pma__table_coords' => 'table_coords', + 'pma__pdf_pages' => 'pdf_pages', + 'pma__column_info' => 'column_info', + 'pma__history' => 'history', + 'pma__recent' => 'recent', + 'pma__favorite' => 'favorite', + 'pma__table_uiprefs' => 'table_uiprefs', + 'pma__tracking' => 'tracking', + 'pma__userconfig' => 'userconfig', + 'pma__users' => 'users', + 'pma__usergroups' => 'usergroups', + 'pma__navigationhiding' => 'navigationhiding', + 'pma__savedsearches' => 'savedsearches', + 'pma__central_columns' => 'central_columns', + 'pma__designer_settings' => 'designer_settings', + 'pma__export_templates' => 'export_templates', + ); + + $existingTables = $GLOBALS['dbi']->getTables($db, DatabaseInterface::CONNECT_CONTROL); + + $createQueries = null; + $foundOne = false; + foreach ($tablesToFeatures as $table => $feature) { + if (! in_array($table, $existingTables)) { + if ($create) { + if ($createQueries == null) { // first create + $createQueries = $this->getDefaultPmaTableNames(); + $GLOBALS['dbi']->selectDb($db); + } + $GLOBALS['dbi']->tryQuery($createQueries[$table]); + if ($error = $GLOBALS['dbi']->getError()) { + $GLOBALS['message'] = $error; + return; + } + $foundOne = true; + $GLOBALS['cfg']['Server'][$feature] = $table; + } + } else { + $foundOne = true; + $GLOBALS['cfg']['Server'][$feature] = $table; + } + } + + if (! $foundOne) { + return; + } + $GLOBALS['cfg']['Server']['pmadb'] = $db; + $_SESSION['relation'][$GLOBALS['server']] = $this->checkRelationsParam(); + + $cfgRelation = $this->getRelationsParam(); + if ($cfgRelation['recentwork'] || $cfgRelation['favoritework']) { + // Since configuration storage is updated, we need to + // re-initialize the favorite and recent tables stored in the + // session from the current configuration storage. + if ($cfgRelation['favoritework']) { + $fav_tables = RecentFavoriteTable::getInstance('favorite'); + $_SESSION['tmpval']['favorite_tables'][$GLOBALS['server']] + = $fav_tables->getFromDb(); + } + + if ($cfgRelation['recentwork']) { + $recent_tables = RecentFavoriteTable::getInstance('recent'); + $_SESSION['tmpval']['recent_tables'][$GLOBALS['server']] + = $recent_tables->getFromDb(); + } + + // Reload navi panel to update the recent/favorite lists. + $GLOBALS['reload'] = true; + } + } + + /** + * Get Html for PMA tables fixing anchor. + * + * @param boolean $allTables whether to create all tables + * @param boolean $createDb whether to create the pmadb also + * + * @return string Html + */ + public function getHtmlFixPmaTables($allTables, $createDb = false) + { + $retval = ''; + + $url_query = Url::getCommon(array('db' => $GLOBALS['db']), ''); + if ($allTables) { + if ($createDb) { + $url_query .= '&goto=db_operations.php&create_pmadb=1'; + $message = Message::notice( + __( + '%sCreate%s a database named \'phpmyadmin\' and setup ' + . 'the phpMyAdmin configuration storage there.' + ) + ); + } else { + $url_query .= '&goto=db_operations.php&fixall_pmadb=1'; + $message = Message::notice( + __( + '%sCreate%s the phpMyAdmin configuration storage in the ' + . 'current database.' + ) + ); + } + } else { + $url_query .= '&goto=db_operations.php&fix_pmadb=1'; + $message = Message::notice( + __('%sCreate%s missing phpMyAdmin configuration storage tables.') + ); + } + $message->addParamHtml(''); + $message->addParamHtml(''); + + $retval .= $message->getDisplay(); + + return $retval; + } + + /** + * Gets the relations info and status, depending on the condition + * + * @param boolean $condition whether to look for foreigners or not + * @param string $db database name + * @param string $table table name + * + * @return array ($res_rel, $have_rel) + */ + public function getRelationsAndStatus($condition, $db, $table) + { + if ($condition) { + // Find which tables are related with the current one and write it in + // an array + $res_rel = $this->getForeigners($db, $table); + + if (count($res_rel) > 0) { + $have_rel = true; + } else { + $have_rel = false; + } + } else { + $have_rel = false; + $res_rel = array(); + } // end if + return(array($res_rel, $have_rel)); + } + + /** + * Verifies if all the pmadb tables are defined + * + * @return boolean + */ + public function arePmadbTablesDefined() + { + if (empty($GLOBALS['cfg']['Server']['bookmarktable']) + || empty($GLOBALS['cfg']['Server']['relation']) + || empty($GLOBALS['cfg']['Server']['table_info']) + || empty($GLOBALS['cfg']['Server']['table_coords']) + || empty($GLOBALS['cfg']['Server']['column_info']) + || empty($GLOBALS['cfg']['Server']['pdf_pages']) + || empty($GLOBALS['cfg']['Server']['history']) + || empty($GLOBALS['cfg']['Server']['recent']) + || empty($GLOBALS['cfg']['Server']['favorite']) + || empty($GLOBALS['cfg']['Server']['table_uiprefs']) + || empty($GLOBALS['cfg']['Server']['tracking']) + || empty($GLOBALS['cfg']['Server']['userconfig']) + || empty($GLOBALS['cfg']['Server']['users']) + || empty($GLOBALS['cfg']['Server']['usergroups']) + || empty($GLOBALS['cfg']['Server']['navigationhiding']) + || empty($GLOBALS['cfg']['Server']['savedsearches']) + || empty($GLOBALS['cfg']['Server']['central_columns']) + || empty($GLOBALS['cfg']['Server']['designer_settings']) + || empty($GLOBALS['cfg']['Server']['export_templates']) + ) { + return false; + } + + return true; + } + + /** + * Get tables for foreign key constraint + * + * @param string $foreignDb Database name + * @param string $tblStorageEngine Table storage engine + * + * @return array Table names + */ + public function getTables($foreignDb, $tblStorageEngine) + { + $tables = array(); + $tablesRows = $GLOBALS['dbi']->query( + 'SHOW TABLE STATUS FROM ' . Util::backquote($foreignDb), + DatabaseInterface::CONNECT_USER, + DatabaseInterface::QUERY_STORE + ); + while ($row = $GLOBALS['dbi']->fetchRow($tablesRows)) { + if (isset($row[1]) && mb_strtoupper($row[1]) == $tblStorageEngine) { + $tables[] = $row[0]; + } + } + if ($GLOBALS['cfg']['NaturalOrder']) { + usort($tables, 'strnatcasecmp'); + } + return $tables; + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/RelationCleanup.php b/php/apps/phpmyadmin49/libraries/classes/RelationCleanup.php new file mode 100644 index 00000000..0f2d0225 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/RelationCleanup.php @@ -0,0 +1,371 @@ +getRelationsParam(); + + if ($cfgRelation['commwork']) { + $remove_query = 'DELETE FROM ' + . Util::backquote($cfgRelation['db']) + . '.' . Util::backquote($cfgRelation['column_info']) + . ' WHERE db_name = \'' . $GLOBALS['dbi']->escapeString($db) . '\'' + . ' AND table_name = \'' . $GLOBALS['dbi']->escapeString($table) + . '\'' + . ' AND column_name = \'' . $GLOBALS['dbi']->escapeString($column) + . '\''; + $relation->queryAsControlUser($remove_query); + } + + if ($cfgRelation['displaywork']) { + $remove_query = 'DELETE FROM ' + . Util::backquote($cfgRelation['db']) + . '.' . Util::backquote($cfgRelation['table_info']) + . ' WHERE db_name = \'' . $GLOBALS['dbi']->escapeString($db) . '\'' + . ' AND table_name = \'' . $GLOBALS['dbi']->escapeString($table) + . '\'' + . ' AND display_field = \'' . $GLOBALS['dbi']->escapeString($column) + . '\''; + $relation->queryAsControlUser($remove_query); + } + + if ($cfgRelation['relwork']) { + $remove_query = 'DELETE FROM ' + . Util::backquote($cfgRelation['db']) + . '.' . Util::backquote($cfgRelation['relation']) + . ' WHERE master_db = \'' . $GLOBALS['dbi']->escapeString($db) + . '\'' + . ' AND master_table = \'' . $GLOBALS['dbi']->escapeString($table) + . '\'' + . ' AND master_field = \'' . $GLOBALS['dbi']->escapeString($column) + . '\''; + $relation->queryAsControlUser($remove_query); + + $remove_query = 'DELETE FROM ' + . Util::backquote($cfgRelation['db']) + . '.' . Util::backquote($cfgRelation['relation']) + . ' WHERE foreign_db = \'' . $GLOBALS['dbi']->escapeString($db) + . '\'' + . ' AND foreign_table = \'' . $GLOBALS['dbi']->escapeString($table) + . '\'' + . ' AND foreign_field = \'' . $GLOBALS['dbi']->escapeString($column) + . '\''; + $relation->queryAsControlUser($remove_query); + } + } + + /** + * Cleanup table related relation stuff + * + * @param string $db database name + * @param string $table table name + * + * @return void + */ + public static function table($db, $table) + { + $relation = new Relation(); + $cfgRelation = $relation->getRelationsParam(); + + if ($cfgRelation['commwork']) { + $remove_query = 'DELETE FROM ' + . Util::backquote($cfgRelation['db']) + . '.' . Util::backquote($cfgRelation['column_info']) + . ' WHERE db_name = \'' . $GLOBALS['dbi']->escapeString($db) . '\'' + . ' AND table_name = \'' . $GLOBALS['dbi']->escapeString($table) + . '\''; + $relation->queryAsControlUser($remove_query); + } + + if ($cfgRelation['displaywork']) { + $remove_query = 'DELETE FROM ' + . Util::backquote($cfgRelation['db']) + . '.' . Util::backquote($cfgRelation['table_info']) + . ' WHERE db_name = \'' . $GLOBALS['dbi']->escapeString($db) . '\'' + . ' AND table_name = \'' . $GLOBALS['dbi']->escapeString($table) + . '\''; + $relation->queryAsControlUser($remove_query); + } + + if ($cfgRelation['pdfwork']) { + $remove_query = 'DELETE FROM ' + . Util::backquote($cfgRelation['db']) + . '.' . Util::backquote($cfgRelation['table_coords']) + . ' WHERE db_name = \'' . $GLOBALS['dbi']->escapeString($db) . '\'' + . ' AND table_name = \'' . $GLOBALS['dbi']->escapeString($table) + . '\''; + $relation->queryAsControlUser($remove_query); + } + + if ($cfgRelation['relwork']) { + $remove_query = 'DELETE FROM ' + . Util::backquote($cfgRelation['db']) + . '.' . Util::backquote($cfgRelation['relation']) + . ' WHERE master_db = \'' . $GLOBALS['dbi']->escapeString($db) + . '\'' + . ' AND master_table = \'' . $GLOBALS['dbi']->escapeString($table) + . '\''; + $relation->queryAsControlUser($remove_query); + + $remove_query = 'DELETE FROM ' + . Util::backquote($cfgRelation['db']) + . '.' . Util::backquote($cfgRelation['relation']) + . ' WHERE foreign_db = \'' . $GLOBALS['dbi']->escapeString($db) + . '\'' + . ' AND foreign_table = \'' . $GLOBALS['dbi']->escapeString($table) + . '\''; + $relation->queryAsControlUser($remove_query); + } + + if ($cfgRelation['uiprefswork']) { + $remove_query = 'DELETE FROM ' + . Util::backquote($cfgRelation['db']) + . '.' . Util::backquote($cfgRelation['table_uiprefs']) + . ' WHERE db_name = \'' . $GLOBALS['dbi']->escapeString($db) . '\'' + . ' AND table_name = \'' . $GLOBALS['dbi']->escapeString($table) + . '\''; + $relation->queryAsControlUser($remove_query); + } + + if ($cfgRelation['navwork']) { + $remove_query = 'DELETE FROM ' + . Util::backquote($cfgRelation['db']) + . '.' . Util::backquote($cfgRelation['navigationhiding']) + . ' WHERE db_name = \'' . $GLOBALS['dbi']->escapeString($db) . '\'' + . ' AND (table_name = \'' . $GLOBALS['dbi']->escapeString($table) + . '\'' + . ' OR (item_name = \'' . $GLOBALS['dbi']->escapeString($table) + . '\'' + . ' AND item_type = \'table\'))'; + $relation->queryAsControlUser($remove_query); + } + } + + /** + * Cleanup database related relation stuff + * + * @param string $db database name + * + * @return void + */ + public static function database($db) + { + $relation = new Relation(); + $cfgRelation = $relation->getRelationsParam(); + + if ($cfgRelation['commwork']) { + $remove_query = 'DELETE FROM ' + . Util::backquote($cfgRelation['db']) + . '.' . Util::backquote($cfgRelation['column_info']) + . ' WHERE db_name = \'' . $GLOBALS['dbi']->escapeString($db) . '\''; + $relation->queryAsControlUser($remove_query); + } + + if ($cfgRelation['bookmarkwork']) { + $remove_query = 'DELETE FROM ' + . Util::backquote($cfgRelation['db']) + . '.' . Util::backquote($cfgRelation['bookmark']) + . ' WHERE dbase = \'' . $GLOBALS['dbi']->escapeString($db) . '\''; + $relation->queryAsControlUser($remove_query); + } + + if ($cfgRelation['displaywork']) { + $remove_query = 'DELETE FROM ' + . Util::backquote($cfgRelation['db']) + . '.' . Util::backquote($cfgRelation['table_info']) + . ' WHERE db_name = \'' . $GLOBALS['dbi']->escapeString($db) . '\''; + $relation->queryAsControlUser($remove_query); + } + + if ($cfgRelation['pdfwork']) { + $remove_query = 'DELETE FROM ' + . Util::backquote($cfgRelation['db']) + . '.' . Util::backquote($cfgRelation['pdf_pages']) + . ' WHERE db_name = \'' . $GLOBALS['dbi']->escapeString($db) . '\''; + $relation->queryAsControlUser($remove_query); + + $remove_query = 'DELETE FROM ' + . Util::backquote($cfgRelation['db']) + . '.' . Util::backquote($cfgRelation['table_coords']) + . ' WHERE db_name = \'' . $GLOBALS['dbi']->escapeString($db) . '\''; + $relation->queryAsControlUser($remove_query); + } + + if ($cfgRelation['relwork']) { + $remove_query = 'DELETE FROM ' + . Util::backquote($cfgRelation['db']) + . '.' . Util::backquote($cfgRelation['relation']) + . ' WHERE master_db = \'' + . $GLOBALS['dbi']->escapeString($db) . '\''; + $relation->queryAsControlUser($remove_query); + + $remove_query = 'DELETE FROM ' + . Util::backquote($cfgRelation['db']) + . '.' . Util::backquote($cfgRelation['relation']) + . ' WHERE foreign_db = \'' . $GLOBALS['dbi']->escapeString($db) + . '\''; + $relation->queryAsControlUser($remove_query); + } + + if ($cfgRelation['uiprefswork']) { + $remove_query = 'DELETE FROM ' + . Util::backquote($cfgRelation['db']) + . '.' . Util::backquote($cfgRelation['table_uiprefs']) + . ' WHERE db_name = \'' . $GLOBALS['dbi']->escapeString($db) . '\''; + $relation->queryAsControlUser($remove_query); + } + + if ($cfgRelation['navwork']) { + $remove_query = 'DELETE FROM ' + . Util::backquote($cfgRelation['db']) + . '.' . Util::backquote($cfgRelation['navigationhiding']) + . ' WHERE db_name = \'' . $GLOBALS['dbi']->escapeString($db) . '\''; + $relation->queryAsControlUser($remove_query); + } + + if ($cfgRelation['savedsearcheswork']) { + $remove_query = 'DELETE FROM ' + . Util::backquote($cfgRelation['db']) + . '.' . Util::backquote($cfgRelation['savedsearches']) + . ' WHERE db_name = \'' . $GLOBALS['dbi']->escapeString($db) . '\''; + $relation->queryAsControlUser($remove_query); + } + + if ($cfgRelation['centralcolumnswork']) { + $remove_query = 'DELETE FROM ' + . Util::backquote($cfgRelation['db']) + . '.' . Util::backquote($cfgRelation['central_columns']) + . ' WHERE db_name = \'' . $GLOBALS['dbi']->escapeString($db) . '\''; + $relation->queryAsControlUser($remove_query); + } + } + + /** + * Cleanup user related relation stuff + * + * @param string $username username + * + * @return void + */ + public static function user($username) + { + $relation = new Relation(); + $cfgRelation = $relation->getRelationsParam(); + + if ($cfgRelation['bookmarkwork']) { + $remove_query = "DELETE FROM " + . Util::backquote($cfgRelation['db']) + . "." . Util::backquote($cfgRelation['bookmark']) + . " WHERE `user` = '" . $GLOBALS['dbi']->escapeString($username) + . "'"; + $relation->queryAsControlUser($remove_query); + } + + if ($cfgRelation['historywork']) { + $remove_query = "DELETE FROM " + . Util::backquote($cfgRelation['db']) + . "." . Util::backquote($cfgRelation['history']) + . " WHERE `username` = '" . $GLOBALS['dbi']->escapeString($username) + . "'"; + $relation->queryAsControlUser($remove_query); + } + + if ($cfgRelation['recentwork']) { + $remove_query = "DELETE FROM " + . Util::backquote($cfgRelation['db']) + . "." . Util::backquote($cfgRelation['recent']) + . " WHERE `username` = '" . $GLOBALS['dbi']->escapeString($username) + . "'"; + $relation->queryAsControlUser($remove_query); + } + + if ($cfgRelation['favoritework']) { + $remove_query = "DELETE FROM " + . Util::backquote($cfgRelation['db']) + . "." . Util::backquote($cfgRelation['favorite']) + . " WHERE `username` = '" . $GLOBALS['dbi']->escapeString($username) + . "'"; + $relation->queryAsControlUser($remove_query); + } + + if ($cfgRelation['uiprefswork']) { + $remove_query = "DELETE FROM " + . Util::backquote($cfgRelation['db']) + . "." . Util::backquote($cfgRelation['table_uiprefs']) + . " WHERE `username` = '" . $GLOBALS['dbi']->escapeString($username) + . "'"; + $relation->queryAsControlUser($remove_query); + } + + if ($cfgRelation['userconfigwork']) { + $remove_query = "DELETE FROM " + . Util::backquote($cfgRelation['db']) + . "." . Util::backquote($cfgRelation['userconfig']) + . " WHERE `username` = '" . $GLOBALS['dbi']->escapeString($username) + . "'"; + $relation->queryAsControlUser($remove_query); + } + + if ($cfgRelation['menuswork']) { + $remove_query = "DELETE FROM " + . Util::backquote($cfgRelation['db']) + . "." . Util::backquote($cfgRelation['users']) + . " WHERE `username` = '" . $GLOBALS['dbi']->escapeString($username) + . "'"; + $relation->queryAsControlUser($remove_query); + } + + if ($cfgRelation['navwork']) { + $remove_query = "DELETE FROM " + . Util::backquote($cfgRelation['db']) + . "." . Util::backquote($cfgRelation['navigationhiding']) + . " WHERE `username` = '" . $GLOBALS['dbi']->escapeString($username) + . "'"; + $relation->queryAsControlUser($remove_query); + } + + if ($cfgRelation['savedsearcheswork']) { + $remove_query = "DELETE FROM " + . Util::backquote($cfgRelation['db']) + . "." . Util::backquote($cfgRelation['savedsearches']) + . " WHERE `username` = '" . $GLOBALS['dbi']->escapeString($username) + . "'"; + $relation->queryAsControlUser($remove_query); + } + + if ($cfgRelation['designersettingswork']) { + $remove_query = "DELETE FROM " + . Util::backquote($cfgRelation['db']) + . "." . Util::backquote($cfgRelation['designer_settings']) + . " WHERE `username` = '" . $GLOBALS['dbi']->escapeString($username) + . "'"; + $relation->queryAsControlUser($remove_query); + } + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Replication.php b/php/apps/phpmyadmin49/libraries/classes/Replication.php new file mode 100644 index 00000000..d53fada5 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Replication.php @@ -0,0 +1,173 @@ +tryQuery($action . " SLAVE " . $control . ";", $link); + } + + /** + * Changes master for replication slave + * + * @param string $user replication user on master + * @param string $password password for the user + * @param string $host master's hostname or IP + * @param int $port port, where mysql is running + * @param array $pos position of mysql replication, + * array should contain fields File and Position + * @param bool $stop shall we stop slave? + * @param bool $start shall we start slave? + * @param mixed $link mysql link + * + * @return string output of CHANGE MASTER mysql command + */ + public static function slaveChangeMaster($user, $password, $host, $port, + array $pos, $stop = true, $start = true, $link = null + ) { + if ($stop) { + self::slaveControl("STOP", null, $link); + } + + $out = $GLOBALS['dbi']->tryQuery( + 'CHANGE MASTER TO ' . + 'MASTER_HOST=\'' . $host . '\',' . + 'MASTER_PORT=' . ($port * 1) . ',' . + 'MASTER_USER=\'' . $user . '\',' . + 'MASTER_PASSWORD=\'' . $password . '\',' . + 'MASTER_LOG_FILE=\'' . $pos["File"] . '\',' . + 'MASTER_LOG_POS=' . $pos["Position"] . ';', $link + ); + + if ($start) { + self::slaveControl("START", null, $link); + } + + return $out; + } + + /** + * This function provides connection to remote mysql server + * + * @param string $user mysql username + * @param string $password password for the user + * @param string $host mysql server's hostname or IP + * @param int $port mysql remote port + * @param string $socket path to unix socket + * + * @return mixed $link mysql link on success + */ + public static function connectToMaster( + $user, $password, $host = null, $port = null, $socket = null + ) { + $server = array(); + $server['user'] = $user; + $server['password'] = $password; + $server["host"] = Core::sanitizeMySQLHost($host); + $server["port"] = $port; + $server["socket"] = $socket; + + // 5th parameter set to true means that it's an auxiliary connection + // and we must not go back to login page if it fails + return $GLOBALS['dbi']->connect(DatabaseInterface::CONNECT_AUXILIARY, $server); + } + + /** + * Fetches position and file of current binary log on master + * + * @param mixed $link mysql link + * + * @return array an array containing File and Position in MySQL replication + * on master server, useful for self::slaveChangeMaster + */ + public static function slaveBinLogMaster($link = null) + { + $data = $GLOBALS['dbi']->fetchResult('SHOW MASTER STATUS', null, null, $link); + $output = array(); + + if (! empty($data)) { + $output["File"] = $data[0]["File"]; + $output["Position"] = $data[0]["Position"]; + } + return $output; + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/ReplicationGui.php b/php/apps/phpmyadmin49/libraries/classes/ReplicationGui.php new file mode 100644 index 00000000..38155fbf --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/ReplicationGui.php @@ -0,0 +1,1089 @@ +getDisplay(); + $_SESSION['replication']['sr_action_status'] = 'unknown'; + } elseif ($_SESSION['replication']['sr_action_status'] == 'success') { + $success_message = $_SESSION['replication']['sr_action_info']; + $html .= Message::success($success_message)->getDisplay(); + $_SESSION['replication']['sr_action_status'] = 'unknown'; + } + } + return $html; + } + + /** + * returns HTML for master replication + * + * @return String HTML code + */ + public static function getHtmlForMasterReplication() + { + $html = ''; + if (! isset($_POST['repl_clear_scr'])) { + $html .= '
      '; + $html .= '' . __('Master replication') . ''; + $html .= __('This server is configured as master in a replication process.'); + $html .= '
        '; + $html .= '
      • '; + $html .= __('Show master status') . ''; + $html .= self::getHtmlForReplicationStatusTable('master', true, false); + $html .= '
      • '; + + $html .= '
      • '; + $html .= __('Show connected slaves') . ''; + $html .= self::getHtmlForReplicationSlavesTable(true); + $html .= '
      • '; + + $_url_params = $GLOBALS['url_params']; + $_url_params['mr_adduser'] = true; + $_url_params['repl_clear_scr'] = true; + + $html .= '
      • '; + $html .= __('Add slave replication user') . '
      • '; + } + + // Display 'Add replication slave user' form + if (isset($_POST['mr_adduser'])) { + $html .= self::getHtmlForReplicationMasterAddSlaveUser(); + } elseif (! isset($_POST['repl_clear_scr'])) { + $html .= "
      "; + $html .= "
      "; + } + + return $html; + } + + /** + * returns HTML for master replication configuration + * + * @return String HTML code + */ + public static function getHtmlForMasterConfiguration() + { + $html = '
      '; + $html .= '' . __('Master configuration') . ''; + $html .= __( + 'This server is not configured as a master server in a ' + . 'replication process. You can choose from either replicating ' + . 'all databases and ignoring some of them (useful if you want to ' + . 'replicate a majority of the databases) or you can choose to ignore ' + . 'all databases by default and allow only certain databases to be ' + . 'replicated. Please select the mode:' + ) . '

      '; + + $html .= ''; + $html .= '

      '; + $html .= __('Please select databases:') . '
      '; + $html .= self::getHtmlForReplicationDbMultibox(); + $html .= '

      '; + $html .= __( + 'Now, add the following lines at the end of [mysqld] section' + . ' in your my.cnf and please restart the MySQL server afterwards.' + ) . '
      '; + $html .= '
      ';
      +        $html .= __(
      +            'Once you restarted MySQL server, please click on Go button. '
      +            . 'Afterwards, you should see a message informing you, that this server'
      +            . ' is configured as master.'
      +        );
      +        $html .= '
      '; + $html .= '
      '; + $html .= '
      '; + $html .= Url::getHiddenInputs('', ''); + $html .= ' '; + $html .= '
      '; + $html .= '
      '; + + return $html; + } + + /** + * returns HTML for slave replication configuration + * + * @param bool $server_slave_status Whether it is Master or Slave + * @param array $server_slave_replication Slave replication + * + * @return String HTML code + */ + public static function getHtmlForSlaveConfiguration( + $server_slave_status, array $server_slave_replication + ) { + $html = '
      '; + $html .= '' . __('Slave replication') . ''; + /** + * check for multi-master replication functionality + */ + $server_slave_multi_replication = $GLOBALS['dbi']->fetchResult( + 'SHOW ALL SLAVES STATUS' + ); + if ($server_slave_multi_replication) { + $html .= __('Master connection:'); + $html .= '
      '; + $html .= Url::getHiddenInputs($GLOBALS['url_params']); + $html .= ' '; + $html .= ' '; + $html .= '
      '; + $html .= '

      '; + } + if ($server_slave_status) { + $html .= '
      '; + + $_url_params = $GLOBALS['url_params']; + $_url_params['sr_take_action'] = true; + $_url_params['sr_slave_server_control'] = true; + + if ($server_slave_replication[0]['Slave_IO_Running'] == 'No') { + $_url_params['sr_slave_action'] = 'start'; + } else { + $_url_params['sr_slave_action'] = 'stop'; + } + + $_url_params['sr_slave_control_parm'] = 'IO_THREAD'; + $slave_control_io_link = Url::getCommon($_url_params, ''); + + if ($server_slave_replication[0]['Slave_SQL_Running'] == 'No') { + $_url_params['sr_slave_action'] = 'start'; + } else { + $_url_params['sr_slave_action'] = 'stop'; + } + + $_url_params['sr_slave_control_parm'] = 'SQL_THREAD'; + $slave_control_sql_link = Url::getCommon($_url_params, ''); + + if ($server_slave_replication[0]['Slave_IO_Running'] == 'No' + || $server_slave_replication[0]['Slave_SQL_Running'] == 'No' + ) { + $_url_params['sr_slave_action'] = 'start'; + } else { + $_url_params['sr_slave_action'] = 'stop'; + } + + $_url_params['sr_slave_control_parm'] = null; + $slave_control_full_link = Url::getCommon($_url_params, ''); + + $_url_params['sr_slave_action'] = 'reset'; + $slave_control_reset_link = Url::getCommon($_url_params, ''); + + $_url_params = $GLOBALS['url_params']; + $_url_params['sr_take_action'] = true; + $_url_params['sr_slave_skip_error'] = true; + $slave_skip_error_link = Url::getCommon($_url_params, ''); + + if ($server_slave_replication[0]['Slave_SQL_Running'] == 'No') { + $html .= Message::error( + __('Slave SQL Thread not running!') + )->getDisplay(); + } + if ($server_slave_replication[0]['Slave_IO_Running'] == 'No') { + $html .= Message::error( + __('Slave IO Thread not running!') + )->getDisplay(); + } + + $_url_params = $GLOBALS['url_params']; + $_url_params['sl_configure'] = true; + $_url_params['repl_clear_scr'] = true; + + $reconfiguremaster_link = Url::getCommon($_url_params, ''); + + $html .= __( + 'Server is configured as slave in a replication process. Would you ' . + 'like to:' + ); + $html .= '
      '; + $html .= ''; + $html .= '
      '; + + } elseif (! isset($_POST['sl_configure'])) { + $_url_params = $GLOBALS['url_params']; + $_url_params['sl_configure'] = true; + $_url_params['repl_clear_scr'] = true; + + $html .= sprintf( + __( + 'This server is not configured as slave in a replication process. ' + . 'Would you like to %sconfigure%s it?' + ), + '', + '' + ); + } + $html .= '
      '; + + return $html; + } + + /** + * returns HTML for Slave Error Management + * + * @param String $slave_skip_error_link error link + * + * @return String HTML code + */ + public static function getHtmlForSlaveErrorManagement($slave_skip_error_link) + { + $html = ''; + $html .= __('Error management:') . ''; + $html .= '
      '; + $html .= Message::error( + __('Skipping errors might lead into unsynchronized master and slave!') + )->getDisplay(); + $html .= '
        '; + $html .= '
      • '; + $html .= __('Skip current error') . '
      • '; + $html .= '
      • '; + $html .= '
        '; + $html .= Url::getHiddenInputs('', ''); + $html .= sprintf( + __('Skip next %s errors.'), + '' + ); + $html .= ' '; + $html .= ' '; + $html .= '
      • '; + $html .= '
      '; + $html .= '
      '; + return $html; + } + + /** + * returns HTML for not configure for a server replication + * + * @return String HTML code + */ + public static function getHtmlForNotServerReplication() + { + $_url_params = $GLOBALS['url_params']; + $_url_params['mr_configure'] = true; + + $html = '
      '; + $html .= '' . __('Master replication') . ''; + $html .= sprintf( + __( + 'This server is not configured as master in a replication process. ' + . 'Would you like to %sconfigure%s it?' + ), + '', + '' + ); + $html .= '
      '; + return $html; + } + + /** + * returns HTML code for selecting databases + * + * @return String HTML code + */ + public static function getHtmlForReplicationDbMultibox() + { + $multi_values = ''; + $multi_values .= '
      '; + $multi_values .= '' . __('Select all') . ''; + $multi_values .= ' / '; + $multi_values .= '' . __('Unselect all') . ''; + + return $multi_values; + } + + /** + * returns HTML for changing master + * + * @param String $submitname - submit button name + * + * @return String HTML code + */ + public static function getHtmlForReplicationChangeMaster($submitname) + { + $html = ''; + list($username_length, $hostname_length) + = self::getUsernameHostnameLength(); + + $html .= '
      '; + $html .= Url::getHiddenInputs('', ''); + $html .= '
      '; + $html .= ' ' . __('Slave configuration'); + $html .= ' - ' . __('Change or reconfigure master server') . ''; + $html .= __( + 'Make sure you have a unique server-id in your configuration file (my.cnf). ' + . 'If not, please add the following line into [mysqld] section:' + ); + $html .= '
      '; + $html .= '
      server-id=' . time() . '
      '; + + $html .= self::getHtmlForAddUserInputDiv( + array('text'=>__('User name:'), 'for'=>"text_username"), + array( + 'type'=>'text', + 'name'=>'username', + 'id'=>'text_username', + 'maxlength'=>$username_length, + 'title'=>__('User name'), + 'required'=>'required' + ) + ); + + $html .= self::getHtmlForAddUserInputDiv( + array('text'=>__('Password:'), 'for'=>"text_pma_pw"), + array( + 'type'=>'password', + 'name'=>'pma_pw', + 'id'=>'text_pma_pw', + 'title'=>__('Password'), + 'required'=>'required' + ) + ); + + $html .= self::getHtmlForAddUserInputDiv( + array('text'=>__('Host:'), 'for'=>"text_hostname"), + array( + 'type'=>'text', + 'name'=>'hostname', + 'id'=>'text_hostname', + 'maxlength'=>$hostname_length, + 'value'=>'', + 'required'=>'required' + ) + ); + + $html .= self::getHtmlForAddUserInputDiv( + array('text'=>__('Port:'), 'for'=>"text_port"), + array( + 'type'=>'number', + 'name'=>'text_port', + 'id'=>'text_port', + 'maxlength'=>6, + 'value'=>'3306', + 'required'=>'required' + ) + ); + + $html .= '
      '; + $html .= ' '; + $html .= '
      '; + + return $html; + } + + /** + * returns HTML code for Add user input div + * + * @param array $label_array label tag elements + * @param array $input_array input tag elements + * + * @return String HTML code + */ + public static function getHtmlForAddUserInputDiv(array $label_array, array $input_array) + { + $html = '
      '; + $html .= ' '; + + $html .= ' $value) { + $html .= ' ' . $key . '="' . $value . '" '; + } + $html .= ' />'; + $html .= '
      '; + return $html; + } + + /** + * This function returns html code for table with replication status. + * + * @param string $type either master or slave + * @param boolean $hidden if true, then default style is set to hidden, + * default value false + * @param boolean $title if true, then title is displayed, default true + * + * @return String HTML code + */ + public static function getHtmlForReplicationStatusTable($type, $hidden = false, $title = true) + { + global ${"{$type}_variables"}; + global ${"{$type}_variables_alerts"}; + global ${"{$type}_variables_oks"}; + global ${"server_{$type}_replication"}; + global ${"strReplicationStatus_{$type}"}; + + $html = ''; + + // TODO check the Masters server id? + // seems to default to '1' when queried via SHOW VARIABLES , + // but resulted in error on the master when slave connects + // [ERROR] Error reading packet from server: Misconfigured master + // - server id was not set ( server_errno=1236) + // [ERROR] Got fatal error 1236: 'Misconfigured master + // - server id was not set' from master when reading data from binary log + // + //$server_id = $GLOBALS['dbi']->fetchValue( + // "SHOW VARIABLES LIKE 'server_id'", 0, 1 + //); + + $html .= '
      '; + + if ($title) { + if ($type == 'master') { + $html .= '

      '; + $html .= __('Master status') . '

      '; + } else { + $html .= '

      '; + $html .= __('Slave status') . '

      '; + } + } else { + $html .= '
      '; + } + + $html .= ' '; + $html .= ' '; + $html .= ' '; + $html .= ' '; + $html .= ' '; + $html .= ' '; + $html .= ' '; + $html .= ' '; + + foreach (${"{$type}_variables"} as $variable) { + $html .= ' '; + $html .= ' '; + $html .= ' '; + $html .= ' '; + } + + $html .= ' '; + $html .= '
      ' . __('Variable') . '' . __('Value') . '
      '; + $html .= htmlspecialchars($variable); + $html .= ' '; + + // TODO change to regexp or something, to allow for negative match + if (isset(${"{$type}_variables_alerts"}[$variable]) + && ${"{$type}_variables_alerts"}[$variable] == ${"server_{$type}_replication"}[0][$variable] + ) { + $html .= ''; + + } elseif (isset(${"{$type}_variables_oks"}[$variable]) + && ${"{$type}_variables_oks"}[$variable] == ${"server_{$type}_replication"}[0][$variable] + ) { + $html .= ''; + } else { + $html .= ''; + } + // allow wrapping long table lists into multiple lines + static $variables_wrap = array( + 'Replicate_Do_DB', 'Replicate_Ignore_DB', + 'Replicate_Do_Table', 'Replicate_Ignore_Table', + 'Replicate_Wild_Do_Table', 'Replicate_Wild_Ignore_Table'); + if (in_array($variable, $variables_wrap)) { + $html .= htmlspecialchars(str_replace( + ',', + ', ', + ${"server_{$type}_replication"}[0][$variable] + )); + } else { + $html .= htmlspecialchars(${"server_{$type}_replication"}[0][$variable]); + } + $html .= ''; + + $html .= '
      '; + $html .= '
      '; + $html .= '
      '; + + return $html; + } + + /** + * returns html code for table with slave users connected to this master + * + * @param boolean $hidden - if true, then default style is set to hidden, + * - default value false + * + * @return string + */ + public static function getHtmlForReplicationSlavesTable($hidden = false) + { + $html = ''; + // Fetch data + $data = $GLOBALS['dbi']->fetchResult('SHOW SLAVE HOSTS', null, null); + + $html .= '
      '; + $html .= '
      '; + $html .= ' '; + $html .= ' '; + $html .= ' '; + $html .= ' '; + $html .= ' '; + $html .= ' '; + $html .= ' '; + $html .= ' '; + + foreach ($data as $slave) { + $html .= ' '; + $html .= ' '; + $html .= ' '; + $html .= ' '; + } + + $html .= ' '; + $html .= '
      ' . __('Server ID') . '' . __('Host') . '
      ' . $slave['Server_id'] . '' . $slave['Host'] . '
      '; + $html .= '
      '; + $html .= Message::notice( + __( + 'Only slaves started with the ' + . '--report-host=host_name option are visible in this list.' + ) + )->getDisplay(); + $html .= '
      '; + $html .= '
      '; + + return $html; + } + + /** + * get the correct username and hostname lengths for this MySQL server + * + * @return array username length, hostname length + */ + public static function getUsernameHostnameLength() + { + $fields_info = $GLOBALS['dbi']->getColumns('mysql', 'user'); + $username_length = 16; + $hostname_length = 41; + foreach ($fields_info as $val) { + if ($val['Field'] == 'User') { + strtok($val['Type'], '()'); + $v = strtok('()'); + if (is_int($v)) { + $username_length = $v; + } + } elseif ($val['Field'] == 'Host') { + strtok($val['Type'], '()'); + $v = strtok('()'); + if (is_int($v)) { + $hostname_length = $v; + } + } + } + return array($username_length, $hostname_length); + } + + /** + * returns html code to add a replication slave user to the master + * + * @return String HTML code + */ + public static function getHtmlForReplicationMasterAddSlaveUser() + { + $html = ''; + list($username_length, $hostname_length) + = self::getUsernameHostnameLength(); + + if (isset($_POST['username']) && strlen($_POST['username']) === 0) { + $GLOBALS['pred_username'] = 'any'; + } + $html .= '
      '; + $html .= '
      ' . __('Add slave replication user') . '' + . self::getHtmlForAddUserLoginForm($username_length) + . '
      ' + . '' + . '' + . ' ' + . '' + . '' + . ' ' + . '' + . '
      ' + . '' + . '' + . ' ' + . '' + . '' + . '
      '; + + return $html; + } + + /** + * returns HTML for TableInfoForm + * + * @param int $hostname_length Selected hostname length + * + * @return String HTML code + */ + public static function getHtmlForTableInfoForm($hostname_length) + { + $html = ' ' + . ' ' + . ' ' + . '
      ' + . '' + . Util::showHint( + __( + 'When Host table is used, this field is ignored ' + . 'and values stored in Host table are used instead.' + ) + ) + . '
      ' + . '
      ' + . '' + . '' + . ' ' + . '' + . '' + . '
      ' + . '
      ' + . '' + . ' ' + . '' + . '
      ' + . '
      ' + . '' + . '' + . ' ' + . '' + . '' + . '
      ' + . ''; + $html .= ''; + return $html; + } + + /** + * handle control requests + * + * @return NULL + */ + public static function handleControlRequest() + { + if (isset($_POST['sr_take_action'])) { + $refresh = false; + $result = false; + $messageSuccess = null; + $messageError = null; + + if (isset($_POST['slave_changemaster']) && ! $GLOBALS['cfg']['AllowArbitraryServer']) { + $_SESSION['replication']['sr_action_status'] = 'error'; + $_SESSION['replication']['sr_action_info'] = __('Connection to server is disabled, please enable $cfg[\'AllowArbitraryServer\'] in phpMyAdmin configuration.'); + } elseif (isset($_POST['slave_changemaster'])) { + $result = self::handleRequestForSlaveChangeMaster(); + } elseif (isset($_POST['sr_slave_server_control'])) { + $result = self::handleRequestForSlaveServerControl(); + $refresh = true; + + switch ($_POST['sr_slave_action']) { + case 'start': + $messageSuccess = __('Replication started successfully.'); + $messageError = __('Error starting replication.'); + break; + case 'stop': + $messageSuccess = __('Replication stopped successfully.'); + $messageError = __('Error stopping replication.'); + break; + case 'reset': + $messageSuccess = __('Replication resetting successfully.'); + $messageError = __('Error resetting replication.'); + break; + default: + $messageSuccess = __('Success.'); + $messageError = __('Error.'); + break; + } + } elseif (isset($_POST['sr_slave_skip_error'])) { + $result = self::handleRequestForSlaveSkipError(); + } + + if ($refresh) { + $response = Response::getInstance(); + if ($response->isAjax()) { + $response->setRequestStatus($result); + $response->addJSON( + 'message', + $result + ? Message::success($messageSuccess) + : Message::error($messageError) + ); + } else { + Core::sendHeaderLocation( + './server_replication.php' + . Url::getCommonRaw($GLOBALS['url_params']) + ); + } + } + unset($refresh); + } + } + + /** + * handle control requests for Slave Change Master + * + * @return boolean + */ + public static function handleRequestForSlaveChangeMaster() + { + $sr = array(); + $_SESSION['replication']['m_username'] = $sr['username'] + = $GLOBALS['dbi']->escapeString($_POST['username']); + $_SESSION['replication']['m_password'] = $sr['pma_pw'] + = $GLOBALS['dbi']->escapeString($_POST['pma_pw']); + $_SESSION['replication']['m_hostname'] = $sr['hostname'] + = $GLOBALS['dbi']->escapeString($_POST['hostname']); + $_SESSION['replication']['m_port'] = $sr['port'] + = $GLOBALS['dbi']->escapeString($_POST['text_port']); + $_SESSION['replication']['m_correct'] = ''; + $_SESSION['replication']['sr_action_status'] = 'error'; + $_SESSION['replication']['sr_action_info'] = __('Unknown error'); + + // Attempt to connect to the new master server + $link_to_master = Replication::connectToMaster( + $sr['username'], $sr['pma_pw'], $sr['hostname'], $sr['port'] + ); + + if (! $link_to_master) { + $_SESSION['replication']['sr_action_status'] = 'error'; + $_SESSION['replication']['sr_action_info'] = sprintf( + __('Unable to connect to master %s.'), + htmlspecialchars($sr['hostname']) + ); + } else { + // Read the current master position + $position = Replication::slaveBinLogMaster($link_to_master); + + if (empty($position)) { + $_SESSION['replication']['sr_action_status'] = 'error'; + $_SESSION['replication']['sr_action_info'] + = __( + 'Unable to read master log position. ' + . 'Possible privilege problem on master.' + ); + } else { + $_SESSION['replication']['m_correct'] = true; + + if (! Replication::slaveChangeMaster( + $sr['username'], + $sr['pma_pw'], + $sr['hostname'], + $sr['port'], + $position, + true, + false + ) + ) { + $_SESSION['replication']['sr_action_status'] = 'error'; + $_SESSION['replication']['sr_action_info'] + = __('Unable to change master!'); + } else { + $_SESSION['replication']['sr_action_status'] = 'success'; + $_SESSION['replication']['sr_action_info'] = sprintf( + __('Master server changed successfully to %s.'), + htmlspecialchars($sr['hostname']) + ); + } + } + } + + return $_SESSION['replication']['sr_action_status'] === 'success'; + } + + /** + * handle control requests for Slave Server Control + * + * @return boolean + */ + public static function handleRequestForSlaveServerControl() + { + if (empty($_POST['sr_slave_control_parm'])) { + $_POST['sr_slave_control_parm'] = null; + } + if ($_POST['sr_slave_action'] == 'reset') { + $qStop = Replication::slaveControl("STOP"); + $qReset = $GLOBALS['dbi']->tryQuery("RESET SLAVE;"); + $qStart = Replication::slaveControl("START"); + + $result = ($qStop !== false && $qStop !== -1 && + $qReset !== false && $qReset !== -1 && + $qStart !== false && $qStart !== -1); + } else { + $qControl = Replication::slaveControl( + $_POST['sr_slave_action'], + $_POST['sr_slave_control_parm'] + ); + + $result = ($qControl !== false && $qControl !== -1); + } + + return $result; + } + + /** + * handle control requests for Slave Skip Error + * + * @return boolean + */ + public static function handleRequestForSlaveSkipError() + { + $count = 1; + if (isset($_POST['sr_skip_errors_count'])) { + $count = $_POST['sr_skip_errors_count'] * 1; + } + + $qStop = Replication::slaveControl("STOP"); + $qSkip = $GLOBALS['dbi']->tryQuery( + "SET GLOBAL SQL_SLAVE_SKIP_COUNTER = " . $count . ";" + ); + $qStart = Replication::slaveControl("START"); + + $result = ($qStop !== false && $qStop !== -1 && + $qSkip !== false && $qSkip !== -1 && + $qStart !== false && $qStart !== -1); + + return $result; + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Response.php b/php/apps/phpmyadmin49/libraries/classes/Response.php new file mode 100644 index 00000000..4594e464 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Response.php @@ -0,0 +1,572 @@ +start(); + register_shutdown_function(array($this, 'response')); + } + $this->_header = new Header(); + $this->_HTML = ''; + $this->_JSON = array(); + $this->_footer = new Footer(); + + $this->_isSuccess = true; + $this->_isDisabled = false; + $this->setAjax(! empty($_REQUEST['ajax_request'])); + $this->_CWD = getcwd(); + } + + /** + * Set the ajax flag to indicate whether + * we are servicing an ajax request + * + * @param bool $isAjax Whether we are servicing an ajax request + * + * @return void + */ + public function setAjax($isAjax) + { + $this->_isAjax = (boolean) $isAjax; + $this->_header->setAjax($this->_isAjax); + $this->_footer->setAjax($this->_isAjax); + } + + /** + * Returns the singleton Response object + * + * @return Response object + */ + public static function getInstance() + { + if (empty(self::$_instance)) { + self::$_instance = new Response(); + } + return self::$_instance; + } + + /** + * Set the status of an ajax response, + * whether it is a success or an error + * + * @param bool $state Whether the request was successfully processed + * + * @return void + */ + public function setRequestStatus($state) + { + $this->_isSuccess = ($state == true); + } + + /** + * Returns true or false depending on whether + * we are servicing an ajax request + * + * @return bool + */ + public function isAjax() + { + return $this->_isAjax; + } + + /** + * Returns the path to the current working directory + * Necessary to work around a PHP bug where the CWD is + * reset after the initial script exits + * + * @return string + */ + public function getCWD() + { + return $this->_CWD; + } + + /** + * Disables the rendering of the header + * and the footer in responses + * + * @return void + */ + public function disable() + { + $this->_header->disable(); + $this->_footer->disable(); + $this->_isDisabled = true; + } + + /** + * Returns a PhpMyAdmin\Header object + * + * @return Header + */ + public function getHeader() + { + return $this->_header; + } + + /** + * Returns a PhpMyAdmin\Footer object + * + * @return Footer + */ + public function getFooter() + { + return $this->_footer; + } + + /** + * Add HTML code to the response + * + * @param string $content A string to be appended to + * the current output buffer + * + * @return void + */ + public function addHTML($content) + { + if (is_array($content)) { + foreach ($content as $msg) { + $this->addHTML($msg); + } + } elseif ($content instanceof Message) { + $this->_HTML .= $content->getDisplay(); + } else { + $this->_HTML .= $content; + } + } + + /** + * Add JSON code to the response + * + * @param mixed $json Either a key (string) or an + * array or key-value pairs + * @param mixed $value Null, if passing an array in $json otherwise + * it's a string value to the key + * + * @return void + */ + public function addJSON($json, $value = null) + { + if (is_array($json)) { + foreach ($json as $key => $value) { + $this->addJSON($key, $value); + } + } else { + if ($value instanceof Message) { + $this->_JSON[$json] = $value->getDisplay(); + } else { + $this->_JSON[$json] = $value; + } + } + + } + + /** + * Renders the HTML response text + * + * @return string + */ + private function _getDisplay() + { + // The header may contain nothing at all, + // if its content was already rendered + // and, in this case, the header will be + // in the content part of the request + $retval = $this->_header->getDisplay(); + $retval .= $this->_HTML; + $retval .= $this->_footer->getDisplay(); + return $retval; + } + + /** + * Sends an HTML response to the browser + * + * @return void + */ + private function _htmlResponse() + { + echo $this->_getDisplay(); + } + + /** + * Sends a JSON response to the browser + * + * @return void + */ + private function _ajaxResponse() + { + /* Avoid wrapping in case we're disabled */ + if ($this->_isDisabled) { + echo $this->_getDisplay(); + return; + } + + if (! isset($this->_JSON['message'])) { + $this->_JSON['message'] = $this->_getDisplay(); + } elseif ($this->_JSON['message'] instanceof Message) { + $this->_JSON['message'] = $this->_JSON['message']->getDisplay(); + } + + if ($this->_isSuccess) { + $this->_JSON['success'] = true; + } else { + $this->_JSON['success'] = false; + $this->_JSON['error'] = $this->_JSON['message']; + unset($this->_JSON['message']); + } + + if ($this->_isSuccess) { + $this->addJSON('_title', $this->getHeader()->getTitleTag()); + + if (isset($GLOBALS['dbi'])) { + $menuHash = $this->getHeader()->getMenu()->getHash(); + $this->addJSON('_menuHash', $menuHash); + $hashes = array(); + if (isset($_REQUEST['menuHashes'])) { + $hashes = explode('-', $_REQUEST['menuHashes']); + } + if (! in_array($menuHash, $hashes)) { + $this->addJSON( + '_menu', + $this->getHeader() + ->getMenu() + ->getDisplay() + ); + } + } + + $this->addJSON('_scripts', $this->getHeader()->getScripts()->getFiles()); + $this->addJSON('_selflink', $this->getFooter()->getSelfUrl()); + $this->addJSON('_displayMessage', $this->getHeader()->getMessage()); + + $debug = $this->_footer->getDebugMessage(); + if (empty($_REQUEST['no_debug']) + && strlen($debug) > 0 + ) { + $this->addJSON('_debug', $debug); + } + + $errors = $this->_footer->getErrorMessages(); + if (strlen($errors) > 0) { + $this->addJSON('_errors', $errors); + } + $promptPhpErrors = $GLOBALS['error_handler']->hasErrorsForPrompt(); + $this->addJSON('_promptPhpErrors', $promptPhpErrors); + + if (empty($GLOBALS['error_message'])) { + // set current db, table and sql query in the querywindow + // (this is for the bottom console) + $query = ''; + $maxChars = $GLOBALS['cfg']['MaxCharactersInDisplayedSQL']; + if (isset($GLOBALS['sql_query']) + && mb_strlen($GLOBALS['sql_query']) < $maxChars + ) { + $query = $GLOBALS['sql_query']; + } + $this->addJSON( + '_reloadQuerywindow', + array( + 'db' => Core::ifSetOr($GLOBALS['db'], ''), + 'table' => Core::ifSetOr($GLOBALS['table'], ''), + 'sql_query' => $query + ) + ); + if (! empty($GLOBALS['focus_querywindow'])) { + $this->addJSON('_focusQuerywindow', $query); + } + if (! empty($GLOBALS['reload'])) { + $this->addJSON('_reloadNavigation', 1); + } + $this->addJSON('_params', $this->getHeader()->getJsParams()); + } + } + + // Set the Content-Type header to JSON so that jQuery parses the + // response correctly. + Core::headerJSON(); + + $result = json_encode($this->_JSON); + if ($result === false) { + switch (json_last_error()) { + case JSON_ERROR_NONE: + $error = 'No errors'; + break; + case JSON_ERROR_DEPTH: + $error = 'Maximum stack depth exceeded'; + break; + case JSON_ERROR_STATE_MISMATCH: + $error = 'Underflow or the modes mismatch'; + break; + case JSON_ERROR_CTRL_CHAR: + $error = 'Unexpected control character found'; + break; + case JSON_ERROR_SYNTAX: + $error = 'Syntax error, malformed JSON'; + break; + case JSON_ERROR_UTF8: + $error = 'Malformed UTF-8 characters, possibly incorrectly encoded'; + break; + case JSON_ERROR_RECURSION: + $error = 'One or more recursive references in the value to be encoded'; + break; + case JSON_ERROR_INF_OR_NAN: + $error = 'One or more NAN or INF values in the value to be encoded'; + break; + case JSON_ERROR_UNSUPPORTED_TYPE: + $error = 'A value of a type that cannot be encoded was given'; + default: + $error = 'Unknown error'; + break; + } + echo json_encode( + array( + 'success' => false, + 'error' => 'JSON encoding failed: ' . $error, + ) + ); + } else { + echo $result; + } + } + + /** + * Sends an HTML response to the browser + * + * @return void + */ + public function response() + { + chdir($this->getCWD()); + $buffer = OutputBuffering::getInstance(); + if (empty($this->_HTML)) { + $this->_HTML = $buffer->getContents(); + } + if ($this->isAjax()) { + $this->_ajaxResponse(); + } else { + $this->_htmlResponse(); + } + $buffer->flush(); + exit; + } + + /** + * Wrapper around PHP's header() function. + * + * @param string $text header string + * + * @return void + */ + public function header($text) + { + header($text); + } + + /** + * Wrapper around PHP's headers_sent() function. + * + * @return bool + */ + public function headersSent() + { + return headers_sent(); + } + + /** + * Wrapper around PHP's http_response_code() function. + * + * @param int $response_code will set the response code. + * + * @return void + */ + public function httpResponseCode($response_code) + { + http_response_code($response_code); + } + + /** + * Sets http response code. + * + * @param int $response_code will set the response code. + * + * @return void + */ + public function setHttpResponseCode($response_code) + { + $this->httpResponseCode($response_code); + switch ($response_code) { + case 100: $httpStatusMsg = ' Continue'; break; + case 101: $httpStatusMsg = ' Switching Protocols'; break; + case 200: $httpStatusMsg = ' OK'; break; + case 201: $httpStatusMsg = ' Created'; break; + case 202: $httpStatusMsg = ' Accepted'; break; + case 203: $httpStatusMsg = ' Non-Authoritative Information'; break; + case 204: $httpStatusMsg = ' No Content'; break; + case 205: $httpStatusMsg = ' Reset Content'; break; + case 206: $httpStatusMsg = ' Partial Content'; break; + case 300: $httpStatusMsg = ' Multiple Choices'; break; + case 301: $httpStatusMsg = ' Moved Permanently'; break; + case 302: $httpStatusMsg = ' Moved Temporarily'; break; + case 303: $httpStatusMsg = ' See Other'; break; + case 304: $httpStatusMsg = ' Not Modified'; break; + case 305: $httpStatusMsg = ' Use Proxy'; break; + case 400: $httpStatusMsg = ' Bad Request'; break; + case 401: $httpStatusMsg = ' Unauthorized'; break; + case 402: $httpStatusMsg = ' Payment Required'; break; + case 403: $httpStatusMsg = ' Forbidden'; break; + case 404: $httpStatusMsg = ' Not Found'; break; + case 405: $httpStatusMsg = ' Method Not Allowed'; break; + case 406: $httpStatusMsg = ' Not Acceptable'; break; + case 407: $httpStatusMsg = ' Proxy Authentication Required'; break; + case 408: $httpStatusMsg = ' Request Time-out'; break; + case 409: $httpStatusMsg = ' Conflict'; break; + case 410: $httpStatusMsg = ' Gone'; break; + case 411: $httpStatusMsg = ' Length Required'; break; + case 412: $httpStatusMsg = ' Precondition Failed'; break; + case 413: $httpStatusMsg = ' Request Entity Too Large'; break; + case 414: $httpStatusMsg = ' Request-URI Too Large'; break; + case 415: $httpStatusMsg = ' Unsupported Media Type'; break; + case 500: $httpStatusMsg = ' Internal Server Error'; break; + case 501: $httpStatusMsg = ' Not Implemented'; break; + case 502: $httpStatusMsg = ' Bad Gateway'; break; + case 503: $httpStatusMsg = ' Service Unavailable'; break; + case 504: $httpStatusMsg = ' Gateway Time-out'; break; + case 505: $httpStatusMsg = ' HTTP Version not supported'; break; + default: $httpStatusMsg = ' Web server is down'; break; + } + if (php_sapi_name() !== 'cgi-fcgi') { + $this->header('status: ' . $response_code . $httpStatusMsg); + } + } + + /** + * Generate header for 303 + * + * @param string $location will set location to redirect. + * + * @return void + */ + public function generateHeader303($location) + { + $this->setHttpResponseCode(303); + $this->header('Location: '.$location); + if (!defined('TESTSUITE')) { + exit; + } + } + + /** + * Configures response for the login page + * + * @return bool Whether caller should exit + */ + public function loginPage() + { + /* Handle AJAX redirection */ + if ($this->isAjax()) { + $this->setRequestStatus(false); + // redirect_flag redirects to the login page + $this->addJSON('redirect_flag', '1'); + return true; + } + + $this->getFooter()->setMinimal(); + $header = $this->getHeader(); + $header->setBodyId('loginform'); + $header->setTitle('phpMyAdmin'); + $header->disableMenuAndConsole(); + $header->disableWarnings(); + return false; + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Rte/Events.php b/php/apps/phpmyadmin49/libraries/classes/Rte/Events.php new file mode 100644 index 00000000..0306b1d3 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Rte/Events.php @@ -0,0 +1,629 @@ + array('ENABLE', + 'DISABLE', + 'DISABLE ON SLAVE'), + 'display' => array('ENABLED', + 'DISABLED', + 'SLAVESIDE_DISABLED') + ); + $event_type = array('RECURRING', + 'ONE TIME'); + $event_interval = array('YEAR', + 'QUARTER', + 'MONTH', + 'DAY', + 'HOUR', + 'MINUTE', + 'WEEK', + 'SECOND', + 'YEAR_MONTH', + 'DAY_HOUR', + 'DAY_MINUTE', + 'DAY_SECOND', + 'HOUR_MINUTE', + 'HOUR_SECOND', + 'MINUTE_SECOND'); + } + + /** + * Main function for the events functionality + * + * @return void + */ + public static function main() + { + global $db; + + self::setGlobals(); + /** + * Process all requests + */ + self::handleEditor(); + Export::events(); + /** + * Display a list of available events + */ + $items = $GLOBALS['dbi']->getEvents($db); + echo RteList::get('event', $items); + /** + * Display a link for adding a new event, if + * the user has the privileges and a link to + * toggle the state of the event scheduler. + */ + echo Footer::events(); + } // end self::main() + + /** + * Handles editor requests for adding or editing an item + * + * @return void + */ + public static function handleEditor() + { + global $_REQUEST, $_POST, $errors, $db; + + if (! empty($_POST['editor_process_add']) + || ! empty($_POST['editor_process_edit']) + ) { + $sql_query = ''; + + $item_query = self::getQueryFromRequest(); + + if (! count($errors)) { // set by PhpMyAdmin\Rte\Routines::getQueryFromRequest() + // Execute the created query + if (! empty($_POST['editor_process_edit'])) { + // Backup the old trigger, in case something goes wrong + $create_item = $GLOBALS['dbi']->getDefinition( + $db, + 'EVENT', + $_POST['item_original_name'] + ); + $drop_item = "DROP EVENT " + . Util::backquote($_POST['item_original_name']) + . ";\n"; + $result = $GLOBALS['dbi']->tryQuery($drop_item); + if (! $result) { + $errors[] = sprintf( + __('The following query has failed: "%s"'), + htmlspecialchars($drop_item) + ) + . '
      ' + . __('MySQL said: ') . $GLOBALS['dbi']->getError(); + } else { + $result = $GLOBALS['dbi']->tryQuery($item_query); + if (! $result) { + $errors[] = sprintf( + __('The following query has failed: "%s"'), + htmlspecialchars($item_query) + ) + . '
      ' + . __('MySQL said: ') . $GLOBALS['dbi']->getError(); + // We dropped the old item, but were unable to create + // the new one. Try to restore the backup query + $result = $GLOBALS['dbi']->tryQuery($create_item); + $errors = General::checkResult( + $result, + __( + 'Sorry, we failed to restore the dropped event.' + ), + $create_item, + $errors + ); + } else { + $message = Message::success( + __('Event %1$s has been modified.') + ); + $message->addParam( + Util::backquote($_POST['item_name']) + ); + $sql_query = $drop_item . $item_query; + } + } + } else { + // 'Add a new item' mode + $result = $GLOBALS['dbi']->tryQuery($item_query); + if (! $result) { + $errors[] = sprintf( + __('The following query has failed: "%s"'), + htmlspecialchars($item_query) + ) + . '

      ' + . __('MySQL said: ') . $GLOBALS['dbi']->getError(); + } else { + $message = Message::success( + __('Event %1$s has been created.') + ); + $message->addParam( + Util::backquote($_POST['item_name']) + ); + $sql_query = $item_query; + } + } + } + + if (count($errors)) { + $message = Message::error( + '' + . __( + 'One or more errors have occurred while processing your request:' + ) + . '' + ); + $message->addHtml('
        '); + foreach ($errors as $string) { + $message->addHtml('
      • ' . $string . '
      • '); + } + $message->addHtml('
      '); + } + + $output = Util::getMessage($message, $sql_query); + $response = Response::getInstance(); + if ($response->isAjax()) { + if ($message->isSuccess()) { + $events = $GLOBALS['dbi']->getEvents($db, $_POST['item_name']); + $event = $events[0]; + $response->addJSON( + 'name', + htmlspecialchars( + mb_strtoupper($_POST['item_name']) + ) + ); + if (! empty($event)) { + $response->addJSON('new_row', RteList::getEventRow($event)); + } + $response->addJSON('insert', ! empty($event)); + $response->addJSON('message', $output); + } else { + $response->setRequestStatus(false); + $response->addJSON('message', $message); + } + exit; + } + } + /** + * Display a form used to add/edit a trigger, if necessary + */ + if (count($errors) + || (empty($_POST['editor_process_add']) + && empty($_POST['editor_process_edit']) + && (! empty($_REQUEST['add_item']) + || ! empty($_REQUEST['edit_item']) + || ! empty($_POST['item_changetype']))) + ) { // FIXME: this must be simpler than that + $operation = ''; + if (! empty($_POST['item_changetype'])) { + $operation = 'change'; + } + // Get the data for the form (if any) + if (! empty($_REQUEST['add_item'])) { + $title = Words::get('add'); + $item = self::getDataFromRequest(); + $mode = 'add'; + } elseif (! empty($_REQUEST['edit_item'])) { + $title = __("Edit event"); + if (! empty($_REQUEST['item_name']) + && empty($_POST['editor_process_edit']) + && empty($_POST['item_changetype']) + ) { + $item = self::getDataFromName($_REQUEST['item_name']); + if ($item !== false) { + $item['item_original_name'] = $item['item_name']; + } + } else { + $item = self::getDataFromRequest(); + } + $mode = 'edit'; + } + General::sendEditor('EVN', $mode, $item, $title, $db, $operation); + } + } // end self::handleEditor() + + /** + * This function will generate the values that are required to for the editor + * + * @return array Data necessary to create the editor. + */ + public static function getDataFromRequest() + { + $retval = array(); + $indices = array('item_name', + 'item_original_name', + 'item_status', + 'item_execute_at', + 'item_interval_value', + 'item_interval_field', + 'item_starts', + 'item_ends', + 'item_definition', + 'item_preserve', + 'item_comment', + 'item_definer'); + foreach ($indices as $index) { + $retval[$index] = isset($_POST[$index]) ? $_POST[$index] : ''; + } + $retval['item_type'] = 'ONE TIME'; + $retval['item_type_toggle'] = 'RECURRING'; + if (isset($_POST['item_type']) && $_POST['item_type'] == 'RECURRING') { + $retval['item_type'] = 'RECURRING'; + $retval['item_type_toggle'] = 'ONE TIME'; + } + return $retval; + } // end self::getDataFromRequest() + + /** + * This function will generate the values that are required to complete + * the "Edit event" form given the name of a event. + * + * @param string $name The name of the event. + * + * @return array Data necessary to create the editor. + */ + public static function getDataFromName($name) + { + global $db; + + $retval = array(); + $columns = "`EVENT_NAME`, `STATUS`, `EVENT_TYPE`, `EXECUTE_AT`, " + . "`INTERVAL_VALUE`, `INTERVAL_FIELD`, `STARTS`, `ENDS`, " + . "`EVENT_DEFINITION`, `ON_COMPLETION`, `DEFINER`, `EVENT_COMMENT`"; + $where = "EVENT_SCHEMA " . Util::getCollateForIS() . "=" + . "'" . $GLOBALS['dbi']->escapeString($db) . "' " + . "AND EVENT_NAME='" . $GLOBALS['dbi']->escapeString($name) . "'"; + $query = "SELECT $columns FROM `INFORMATION_SCHEMA`.`EVENTS` WHERE $where;"; + $item = $GLOBALS['dbi']->fetchSingleRow($query); + if (! $item) { + return false; + } + $retval['item_name'] = $item['EVENT_NAME']; + $retval['item_status'] = $item['STATUS']; + $retval['item_type'] = $item['EVENT_TYPE']; + if ($retval['item_type'] == 'RECURRING') { + $retval['item_type_toggle'] = 'ONE TIME'; + } else { + $retval['item_type_toggle'] = 'RECURRING'; + } + $retval['item_execute_at'] = $item['EXECUTE_AT']; + $retval['item_interval_value'] = $item['INTERVAL_VALUE']; + $retval['item_interval_field'] = $item['INTERVAL_FIELD']; + $retval['item_starts'] = $item['STARTS']; + $retval['item_ends'] = $item['ENDS']; + $retval['item_preserve'] = ''; + if ($item['ON_COMPLETION'] == 'PRESERVE') { + $retval['item_preserve'] = " checked='checked'"; + } + $retval['item_definition'] = $item['EVENT_DEFINITION']; + $retval['item_definer'] = $item['DEFINER']; + $retval['item_comment'] = $item['EVENT_COMMENT']; + + return $retval; + } // end self::getDataFromName() + + /** + * Displays a form used to add/edit an event + * + * @param string $mode If the editor will be used to edit an event + * or add a new one: 'edit' or 'add'. + * @param string $operation If the editor was previously invoked with + * JS turned off, this will hold the name of + * the current operation + * @param array $item Data for the event returned by + * self::getDataFromRequest() or + * self::getDataFromName() + * + * @return string HTML code for the editor. + */ + public static function getEditorForm($mode, $operation, array $item) + { + global $db, $table, $event_status, $event_type, $event_interval; + + $modeToUpper = mb_strtoupper($mode); + + $response = Response::getInstance(); + + // Escape special characters + $need_escape = array( + 'item_original_name', + 'item_name', + 'item_type', + 'item_execute_at', + 'item_interval_value', + 'item_starts', + 'item_ends', + 'item_definition', + 'item_definer', + 'item_comment' + ); + foreach ($need_escape as $index) { + $item[$index] = htmlentities($item[$index], ENT_QUOTES); + } + $original_data = ''; + if ($mode == 'edit') { + $original_data = "\n"; + } + // Handle some logic first + if ($operation == 'change') { + if ($item['item_type'] == 'RECURRING') { + $item['item_type'] = 'ONE TIME'; + $item['item_type_toggle'] = 'RECURRING'; + } else { + $item['item_type'] = 'RECURRING'; + $item['item_type_toggle'] = 'ONE TIME'; + } + } + if ($item['item_type'] == 'ONE TIME') { + $isrecurring_class = ' hide'; + $isonetime_class = ''; + } else { + $isrecurring_class = ''; + $isonetime_class = ' hide'; + } + // Create the output + $retval = ""; + $retval .= "\n\n"; + $retval .= "\n"; + $retval .= "\n"; + $retval .= $original_data; + $retval .= Url::getHiddenInputs($db, $table) . "\n"; + $retval .= "
      \n"; + $retval .= "" . __('Details') . "\n"; + $retval .= "\n"; + $retval .= "\n"; + $retval .= " \n"; + $retval .= " \n"; + $retval .= "\n"; + + $retval .= "\n"; + $retval .= " \n"; + $retval .= " \n"; + $retval .= "\n"; + $retval .= "\n"; + $retval .= " \n"; + $retval .= " \n"; + $retval .= "\n"; + $retval .= "\n"; + $retval .= " \n"; + $retval .= " \n"; + $retval .= " \n"; + $retval .= " \n"; + $retval .= " \n"; + $retval .= " \n"; + $retval .= " \n"; + $retval .= " \n"; + $retval .= " \n"; + $retval .= " \n"; + $retval .= " \n"; + $retval .= " \n"; + $retval .= " \n"; + + return $retval; + } // end self::getParameterRow() + + /** + * Displays a form used to add/edit a routine + * + * @param string $mode If the editor will be used to edit a routine + * or add a new one: 'edit' or 'add'. + * @param string $operation If the editor was previously invoked with + * JS turned off, this will hold the name of + * the current operation + * @param array $routine Data for the routine returned by + * self::getDataFromRequest() or + * self::getDataFromName() + * + * @return string HTML code for the editor. + */ + public static function getEditorForm($mode, $operation, array $routine) + { + global $db, $errors, $param_sqldataaccess, $param_opts_num; + + $response = Response::getInstance(); + + // Escape special characters + $need_escape = array( + 'item_original_name', + 'item_name', + 'item_returnlength', + 'item_definition', + 'item_definer', + 'item_comment' + ); + foreach ($need_escape as $key => $index) { + $routine[$index] = htmlentities($routine[$index], ENT_QUOTES, 'UTF-8'); + } + for ($i = 0; $i < $routine['item_num_params']; $i++) { + $routine['item_param_name'][$i] = htmlentities( + $routine['item_param_name'][$i], + ENT_QUOTES + ); + $routine['item_param_length'][$i] = htmlentities( + $routine['item_param_length'][$i], + ENT_QUOTES + ); + } + + // Handle some logic first + if ($operation == 'change') { + if ($routine['item_type'] == 'PROCEDURE') { + $routine['item_type'] = 'FUNCTION'; + $routine['item_type_toggle'] = 'PROCEDURE'; + } else { + $routine['item_type'] = 'PROCEDURE'; + $routine['item_type_toggle'] = 'FUNCTION'; + } + } elseif ($operation == 'add' + || ($routine['item_num_params'] == 0 && $mode == 'add' && ! $errors) + ) { + $routine['item_param_dir'][] = ''; + $routine['item_param_name'][] = ''; + $routine['item_param_type'][] = ''; + $routine['item_param_length'][] = ''; + $routine['item_param_opts_num'][] = ''; + $routine['item_param_opts_text'][] = ''; + $routine['item_num_params']++; + } elseif ($operation == 'remove') { + unset($routine['item_param_dir'][$routine['item_num_params'] - 1]); + unset($routine['item_param_name'][$routine['item_num_params'] - 1]); + unset($routine['item_param_type'][$routine['item_num_params'] - 1]); + unset($routine['item_param_length'][$routine['item_num_params'] - 1]); + unset($routine['item_param_opts_num'][$routine['item_num_params'] - 1]); + unset($routine['item_param_opts_text'][$routine['item_num_params'] - 1]); + $routine['item_num_params']--; + } + $disableRemoveParam = ''; + if (! $routine['item_num_params']) { + $disableRemoveParam = " color: gray;' disabled='disabled"; + } + $original_routine = ''; + if ($mode == 'edit') { + $original_routine = "\n" + . "\n"; + } + $isfunction_class = ''; + $isprocedure_class = ''; + $isfunction_select = ''; + $isprocedure_select = ''; + if ($routine['item_type'] == 'PROCEDURE') { + $isfunction_class = ' hide'; + $isprocedure_select = " selected='selected'"; + } else { + $isprocedure_class = ' hide'; + $isfunction_select = " selected='selected'"; + } + + // Create the output + $retval = ""; + $retval .= "\n\n"; + $retval .= "\n"; + $retval .= "\n"; + $retval .= $original_routine; + $retval .= Url::getHiddenInputs($db) . "\n"; + $retval .= "
      \n"; + $retval .= "" . __('Details') . "\n"; + $retval .= "
      " . __('Event name') . "\n"; + $retval .= " \n"; + $retval .= " \n"; + $retval .= "
      " . __('Event type') . "\n"; + if ($response->isAjax()) { + $retval .= " \n"; + } else { + $retval .= " \n"; + $retval .= " {$item['item_type']}\n"; + $retval .= " \n"; + $retval .= " \n"; + $retval .= " \n"; + $retval .= " \n"; + $retval .= " \n"; + $retval .= " $value) { + $selected = ""; + if (! empty($item['item_interval_field']) + && $item['item_interval_field'] == $value + ) { + $selected = " selected='selected'"; + } + $retval .= "$value"; + } + $retval .= " \n"; + $retval .= "
      " . _pgettext('Start of recurring event', 'Start'); + $retval .= " \n"; + $retval .= " \n"; + $retval .= " \n"; + $retval .= " \n"; + $retval .= "
      " . __('On completion preserve') . "\n"; + $retval .= " \n"; + $retval .= " isAjax()) { + $retval .= "\n"; + $retval .= "\n"; + } + $retval .= "\n\n"; + $retval .= "\n\n"; + + return $retval; + } // end self::getEditorForm() + + /** + * Composes the query necessary to create an event from an HTTP request. + * + * @return string The CREATE EVENT query. + */ + public static function getQueryFromRequest() + { + global $_REQUEST, $errors, $event_status, $event_type, $event_interval; + + $query = 'CREATE '; + if (! empty($_POST['item_definer'])) { + if (mb_strpos($_POST['item_definer'], '@') !== false + ) { + $arr = explode('@', $_POST['item_definer']); + $query .= 'DEFINER=' . Util::backquote($arr[0]); + $query .= '@' . Util::backquote($arr[1]) . ' '; + } else { + $errors[] = __('The definer must be in the "username@hostname" format!'); + } + } + $query .= 'EVENT '; + if (! empty($_POST['item_name'])) { + $query .= Util::backquote($_POST['item_name']) . ' '; + } else { + $errors[] = __('You must provide an event name!'); + } + $query .= 'ON SCHEDULE '; + if (! empty($_POST['item_type']) + && in_array($_POST['item_type'], $event_type) + ) { + if ($_POST['item_type'] == 'RECURRING') { + if (! empty($_POST['item_interval_value']) + && !empty($_POST['item_interval_field']) + && in_array($_POST['item_interval_field'], $event_interval) + ) { + $query .= 'EVERY ' . intval($_POST['item_interval_value']) . ' '; + $query .= $_POST['item_interval_field'] . ' '; + } else { + $errors[] + = __('You must provide a valid interval value for the event.'); + } + if (! empty($_POST['item_starts'])) { + $query .= "STARTS '" + . $GLOBALS['dbi']->escapeString($_POST['item_starts']) + . "' "; + } + if (! empty($_POST['item_ends'])) { + $query .= "ENDS '" + . $GLOBALS['dbi']->escapeString($_POST['item_ends']) + . "' "; + } + } else { + if (! empty($_POST['item_execute_at'])) { + $query .= "AT '" + . $GLOBALS['dbi']->escapeString($_POST['item_execute_at']) + . "' "; + } else { + $errors[] + = __('You must provide a valid execution time for the event.'); + } + } + } else { + $errors[] = __('You must provide a valid type for the event.'); + } + $query .= 'ON COMPLETION '; + if (empty($_POST['item_preserve'])) { + $query .= 'NOT '; + } + $query .= 'PRESERVE '; + if (! empty($_POST['item_status'])) { + foreach ($event_status['display'] as $key => $value) { + if ($value == $_POST['item_status']) { + $query .= $event_status['query'][$key] . ' '; + break; + } + } + } + if (! empty($_POST['item_comment'])) { + $query .= "COMMENT '" . $GLOBALS['dbi']->escapeString( + $_POST['item_comment'] + ) . "' "; + } + $query .= 'DO '; + if (! empty($_POST['item_definition'])) { + $query .= $_POST['item_definition']; + } else { + $errors[] = __('You must provide an event definition.'); + } + + return $query; + } // end self::getQueryFromRequest() +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Rte/Export.php b/php/apps/phpmyadmin49/libraries/classes/Rte/Export.php new file mode 100644 index 00000000..731b0505 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Rte/Export.php @@ -0,0 +1,145 @@ +isAjax()) { + $response->addJSON('message', $export_data); + $response->addJSON('title', $title); + exit; + } else { + $export_data = ''; + echo "
      \n" + , "$title\n" + , $export_data + , "
      \n"; + } + } else { + $_db = htmlspecialchars(Util::backquote($db)); + $message = __('Error in processing request:') . ' ' + . sprintf(Words::get('no_view'), $item_name, $_db); + $message = Message::error($message); + + if ($response->isAjax()) { + $response->setRequestStatus(false); + $response->addJSON('message', $message); + exit; + } else { + $message->display(); + } + } + } // end self::handle() + + /** + * If necessary, prepares event information and passes + * it to self::handle() for the actual export. + * + * @return void + */ + public static function events() + { + global $_GET, $db; + + if (! empty($_GET['export_item']) && ! empty($_GET['item_name'])) { + $item_name = $_GET['item_name']; + $export_data = $GLOBALS['dbi']->getDefinition($db, 'EVENT', $item_name); + if (! $export_data) { + $export_data = false; + } + self::handle($export_data); + } + } // end self::events() + + /** + * If necessary, prepares routine information and passes + * it to self::handle() for the actual export. + * + * @return void + */ + public static function routines() + { + global $_GET, $db; + + if (! empty($_GET['export_item']) + && ! empty($_GET['item_name']) + && ! empty($_GET['item_type']) + ) { + if ($_GET['item_type'] == 'FUNCTION' || $_GET['item_type'] == 'PROCEDURE') { + $rtn_definition + = $GLOBALS['dbi']->getDefinition( + $db, + $_GET['item_type'], + $_GET['item_name'] + ); + if (! $rtn_definition) { + $export_data = false; + } else { + $export_data = "DELIMITER $$\n" + . $rtn_definition + . "$$\nDELIMITER ;\n"; + } + + self::handle($export_data); + } + } + } // end self::routines() + + /** + * If necessary, prepares trigger information and passes + * it to self::handle() for the actual export. + * + * @return void + */ + public static function triggers() + { + global $_GET, $db, $table; + + if (! empty($_GET['export_item']) && ! empty($_GET['item_name'])) { + $item_name = $_GET['item_name']; + $triggers = $GLOBALS['dbi']->getTriggers($db, $table, ''); + $export_data = false; + foreach ($triggers as $trigger) { + if ($trigger['name'] === $item_name) { + $export_data = $trigger['create']; + break; + } + } + self::handle($export_data); + } + } // end self::triggers() +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Rte/Footer.php b/php/apps/phpmyadmin49/libraries/classes/Rte/Footer.php new file mode 100644 index 00000000..2fdb2583 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Rte/Footer.php @@ -0,0 +1,137 @@ +\n"; + $retval .= "
      \n"; + $retval .= "" . _pgettext('Create new procedure', 'New') . "\n"; + $retval .= " \n"; + $retval .= "
      \n"; + $retval .= "\n\n"; + + return $retval; + } // end self::getLinks() + + /** + * Creates a fieldset for adding a new routine, if the user has the privileges. + * + * @return string HTML code with containing the footer fieldset + */ + public static function routines() + { + return self::getLinks('CREATE_PROCEDURE', 'CREATE ROUTINE', 'ROUTINE'); + }// end self::routines() + + /** + * Creates a fieldset for adding a new trigger, if the user has the privileges. + * + * @return string HTML code with containing the footer fieldset + */ + public static function triggers() + { + return self::getLinks('CREATE_TRIGGER', 'TRIGGER', 'TRIGGER'); + } // end self::triggers() + + /** + * Creates a fieldset for adding a new event, if the user has the privileges. + * + * @return string HTML code with containing the footer fieldset + */ + public static function events() + { + global $db, $url_query; + + /** + * For events, we show the usual 'Add event' form and also + * a form for toggling the state of the event scheduler + */ + // Init options for the event scheduler toggle functionality + $es_state = $GLOBALS['dbi']->fetchValue( + "SHOW GLOBAL VARIABLES LIKE 'event_scheduler'", + 0, + 1 + ); + $es_state = mb_strtolower($es_state); + $options = array( + 0 => array( + 'label' => __('OFF'), + 'value' => "SET GLOBAL event_scheduler=\"OFF\"", + 'selected' => ($es_state != 'on') + ), + 1 => array( + 'label' => __('ON'), + 'value' => "SET GLOBAL event_scheduler=\"ON\"", + 'selected' => ($es_state == 'on') + ) + ); + // Generate output + $retval = "\n"; + $retval .= "
      \n"; + // show the usual footer + $retval .= self::getLinks('CREATE_EVENT', 'EVENT', 'EVENT'); + $retval .= "
      \n"; + $retval .= " \n"; + $retval .= " " . __('Event scheduler status') . "\n"; + $retval .= " \n"; + $retval .= "
      \n"; + // show the toggle button + $retval .= Util::toggleButton( + "sql.php$url_query&goto=db_events.php" . urlencode("?db=$db"), + 'sql_query', + $options, + 'PMA_slidingMessage(data.sql_query);' + ); + $retval .= "
      \n"; + $retval .= "
      \n"; + $retval .= "
      \n"; + $retval .= "
      "; + $retval .= "\n"; + + return $retval; + } // end self::events() +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Rte/General.php b/php/apps/phpmyadmin49/libraries/classes/Rte/General.php new file mode 100644 index 00000000..9b13380d --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Rte/General.php @@ -0,0 +1,100 @@ +' + . __('The backed up query was:') + . "\"" . htmlspecialchars($createStatement) . "\"" . '
      ' + . __('MySQL said: ') . $GLOBALS['dbi']->getError(); + + return $errors; + } + + /** + * Send TRI or EVN editor via ajax or by echoing. + * + * @param string $type TRI or EVN + * @param string $mode Editor mode 'add' or 'edit' + * @param array $item Data necessary to create the editor + * @param string $title Title of the editor + * @param string $db Database + * @param string $operation Operation 'change' or '' + * + * @return void + */ + public static function sendEditor($type, $mode, array $item, $title, $db, $operation = null) + { + $response = Response::getInstance(); + if ($item !== false) { + // Show form + if ($type == 'TRI') { + $editor = Triggers::getEditorForm($mode, $item); + } else { // EVN + $editor = Events::getEditorForm($mode, $operation, $item); + } + if ($response->isAjax()) { + $response->addJSON('message', $editor); + $response->addJSON('title', $title); + } else { + echo "\n\n

      $title

      \n\n$editor"; + unset($_POST); + } + exit; + } else { + $message = __('Error in processing request:') . ' '; + $message .= sprintf( + Words::get('not_found'), + htmlspecialchars(Util::backquote($_REQUEST['item_name'])), + htmlspecialchars(Util::backquote($db)) + ); + $message = Message::error($message); + if ($response->isAjax()) { + $response->setRequestStatus(false); + $response->addJSON('message', $message); + exit; + } else { + $message->display(); + } + } + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Rte/Routines.php b/php/apps/phpmyadmin49/libraries/classes/Rte/Routines.php new file mode 100644 index 00000000..adba6f1b --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Rte/Routines.php @@ -0,0 +1,1721 @@ +getRoutines($db, $type); + echo RteList::get('routine', $items); + /** + * Display the form for adding a new routine, if the user has the privileges. + */ + echo Footer::routines(); + /** + * Display a warning for users with PHP's old "mysql" extension. + */ + if (! DatabaseInterface::checkDbExtension('mysqli')) { + trigger_error( + __( + 'You are using PHP\'s deprecated \'mysql\' extension, ' + . 'which is not capable of handling multi queries. ' + . '[strong]The execution of some stored routines may fail![/strong] ' + . 'Please use the improved \'mysqli\' extension to ' + . 'avoid any problems.' + ), + E_USER_WARNING + ); + } + } // end self::main() + + /** + * Handles editor requests for adding or editing an item + * + * @return void + */ + public static function handleEditor() + { + global $_GET, $_POST, $_REQUEST, $GLOBALS, $db, $errors; + + $errors = self::handleRequestCreateOrEdit($errors, $db); + $response = Response::getInstance(); + + /** + * Display a form used to add/edit a routine, if necessary + */ + // FIXME: this must be simpler than that + if (count($errors) + || ( empty($_POST['editor_process_add']) + && empty($_POST['editor_process_edit']) + && (! empty($_REQUEST['add_item']) || ! empty($_REQUEST['edit_item']) + || ! empty($_POST['routine_addparameter']) + || ! empty($_POST['routine_removeparameter']) + || ! empty($_POST['routine_changetype']))) + ) { + // Handle requests to add/remove parameters and changing routine type + // This is necessary when JS is disabled + $operation = ''; + if (! empty($_POST['routine_addparameter'])) { + $operation = 'add'; + } elseif (! empty($_POST['routine_removeparameter'])) { + $operation = 'remove'; + } elseif (! empty($_POST['routine_changetype'])) { + $operation = 'change'; + } + // Get the data for the form (if any) + if (! empty($_REQUEST['add_item'])) { + $title = Words::get('add'); + $routine = self::getDataFromRequest(); + $mode = 'add'; + } elseif (! empty($_REQUEST['edit_item'])) { + $title = __("Edit routine"); + if (! $operation && ! empty($_GET['item_name']) + && empty($_POST['editor_process_edit']) + ) { + $routine = self::getDataFromName( + $_GET['item_name'], $_GET['item_type'] + ); + if ($routine !== false) { + $routine['item_original_name'] = $routine['item_name']; + $routine['item_original_type'] = $routine['item_type']; + } + } else { + $routine = self::getDataFromRequest(); + } + $mode = 'edit'; + } + if ($routine !== false) { + // Show form + $editor = self::getEditorForm($mode, $operation, $routine); + if ($response->isAjax()) { + $response->addJSON('message', $editor); + $response->addJSON('title', $title); + $response->addJSON('param_template', self::getParameterRow()); + $response->addJSON('type', $routine['item_type']); + } else { + echo "\n\n

      $title

      \n\n$editor"; + } + exit; + } else { + $message = __('Error in processing request:') . ' '; + $message .= sprintf( + Words::get('no_edit'), + htmlspecialchars( + Util::backquote($_REQUEST['item_name']) + ), + htmlspecialchars(Util::backquote($db)) + ); + + $message = Message::error($message); + if ($response->isAjax()) { + $response->setRequestStatus(false); + $response->addJSON('message', $message); + exit; + } else { + $message->display(); + } + } + } + } + + /** + * Handle request to create or edit a routine + * + * @param array $errors Errors + * @param string $db DB name + * + * @return array + */ + public static function handleRequestCreateOrEdit(array $errors, $db) + { + if (empty($_POST['editor_process_add']) + && empty($_POST['editor_process_edit']) + ) { + return $errors; + } + + $sql_query = ''; + $routine_query = self::getQueryFromRequest(); + if (!count($errors)) { // set by self::getQueryFromRequest() + // Execute the created query + if (!empty($_POST['editor_process_edit'])) { + $isProcOrFunc = in_array( + $_POST['item_original_type'], + array('PROCEDURE', 'FUNCTION') + ); + + if (!$isProcOrFunc) { + $errors[] = sprintf( + __('Invalid routine type: "%s"'), + htmlspecialchars($_POST['item_original_type']) + ); + } else { + // Backup the old routine, in case something goes wrong + $create_routine = $GLOBALS['dbi']->getDefinition( + $db, + $_POST['item_original_type'], + $_POST['item_original_name'] + ); + + $privilegesBackup = self::backupPrivileges(); + + $drop_routine = "DROP {$_POST['item_original_type']} " + . Util::backquote($_POST['item_original_name']) + . ";\n"; + $result = $GLOBALS['dbi']->tryQuery($drop_routine); + if (!$result) { + $errors[] = sprintf( + __('The following query has failed: "%s"'), + htmlspecialchars($drop_routine) + ) + . '
      ' + . __('MySQL said: ') . $GLOBALS['dbi']->getError(); + } else { + list($newErrors, $message) = self::create( + $routine_query, + $create_routine, + $privilegesBackup + ); + if (empty($newErrors)) { + $sql_query = $drop_routine . $routine_query; + } else { + $errors = array_merge($errors, $newErrors); + } + unset($newErrors); + if (null === $message) { + unset($message); + } + } + } + } else { + // 'Add a new routine' mode + $result = $GLOBALS['dbi']->tryQuery($routine_query); + if (!$result) { + $errors[] = sprintf( + __('The following query has failed: "%s"'), + htmlspecialchars($routine_query) + ) + . '

      ' + . __('MySQL said: ') . $GLOBALS['dbi']->getError(); + } else { + $message = Message::success( + __('Routine %1$s has been created.') + ); + $message->addParam( + Util::backquote($_POST['item_name']) + ); + $sql_query = $routine_query; + } + } + } + + if (count($errors)) { + $message = Message::error( + __( + 'One or more errors have occurred while' + . ' processing your request:' + ) + ); + $message->addHtml('
        '); + foreach ($errors as $string) { + $message->addHtml('
      • ' . $string . '
      • '); + } + $message->addHtml('
      '); + } + + $output = Util::getMessage($message, $sql_query); + $response = Response::getInstance(); + if (!$response->isAjax()) { + return $errors; + } + + if (!$message->isSuccess()) { + $response->setRequestStatus(false); + $response->addJSON('message', $output); + exit; + } + + $routines = $GLOBALS['dbi']->getRoutines( + $db, + $_POST['item_type'], + $_POST['item_name'] + ); + $routine = $routines[0]; + $response->addJSON( + 'name', + htmlspecialchars( + mb_strtoupper($_POST['item_name']) + ) + ); + $response->addJSON('new_row', RteList::getRoutineRow($routine)); + $response->addJSON('insert', !empty($routine)); + $response->addJSON('message', $output); + exit; + } + + /** + * Backup the privileges + * + * @return array + */ + public static function backupPrivileges() + { + if (! $GLOBALS['proc_priv'] || ! $GLOBALS['is_reload_priv']) { + return array(); + } + + // Backup the Old Privileges before dropping + // if $_POST['item_adjust_privileges'] set + if (! isset($_POST['item_adjust_privileges']) + || empty($_POST['item_adjust_privileges']) + ) { + return array(); + } + + $privilegesBackupQuery = 'SELECT * FROM ' . Util::backquote( + 'mysql' + ) + . '.' . Util::backquote('procs_priv') + . ' where Routine_name = "' . $_POST['item_original_name'] + . '" AND Routine_type = "' . $_POST['item_original_type'] + . '";'; + + $privilegesBackup = $GLOBALS['dbi']->fetchResult( + $privilegesBackupQuery, + 0 + ); + + return $privilegesBackup; + } + + /** + * Create the routine + * + * @param string $routine_query Query to create routine + * @param string $create_routine Query to restore routine + * @param array $privilegesBackup Privileges backup + * + * @return array + */ + public static function create( + $routine_query, + $create_routine, + array $privilegesBackup + ) { + $result = $GLOBALS['dbi']->tryQuery($routine_query); + if (!$result) { + $errors = array(); + $errors[] = sprintf( + __('The following query has failed: "%s"'), + htmlspecialchars($routine_query) + ) + . '
      ' + . __('MySQL said: ') . $GLOBALS['dbi']->getError(); + // We dropped the old routine, + // but were unable to create the new one + // Try to restore the backup query + $result = $GLOBALS['dbi']->tryQuery($create_routine); + $errors = General::checkResult( + $result, + __( + 'Sorry, we failed to restore' + . ' the dropped routine.' + ), + $create_routine, + $errors + ); + + return array($errors, null); + } + + // Default value + $resultAdjust = false; + + if ($GLOBALS['proc_priv'] + && $GLOBALS['is_reload_priv'] + ) { + // Insert all the previous privileges + // but with the new name and the new type + foreach ($privilegesBackup as $priv) { + $adjustProcPrivilege = 'INSERT INTO ' + . Util::backquote('mysql') . '.' + . Util::backquote('procs_priv') + . ' VALUES("' . $priv[0] . '", "' + . $priv[1] . '", "' . $priv[2] . '", "' + . $_POST['item_name'] . '", "' + . $_POST['item_type'] . '", "' + . $priv[5] . '", "' + . $priv[6] . '", "' + . $priv[7] . '");'; + $resultAdjust = $GLOBALS['dbi']->query( + $adjustProcPrivilege + ); + } + } + + $message = self::flushPrivileges($resultAdjust); + + return array(array(), $message); + } + + /** + * Flush privileges and get message + * + * @param bool $flushPrivileges Flush privileges + * + * @return Message + */ + public static function flushPrivileges($flushPrivileges) + { + if ($flushPrivileges) { + // Flush the Privileges + $flushPrivQuery = 'FLUSH PRIVILEGES;'; + $GLOBALS['dbi']->query($flushPrivQuery); + + $message = Message::success( + __( + 'Routine %1$s has been modified. Privileges have been adjusted.' + ) + ); + } else { + $message = Message::success( + __('Routine %1$s has been modified.') + ); + } + $message->addParam( + Util::backquote($_POST['item_name']) + ); + + return $message; + } // end self::handleEditor() + + /** + * This function will generate the values that are required to + * complete the editor form. It is especially necessary to handle + * the 'Add another parameter', 'Remove last parameter' and + * 'Change routine type' functionalities when JS is disabled. + * + * @return array Data necessary to create the routine editor. + */ + public static function getDataFromRequest() + { + global $_REQUEST, $param_directions, $param_sqldataaccess; + + $retval = array(); + $indices = array('item_name', + 'item_original_name', + 'item_returnlength', + 'item_returnopts_num', + 'item_returnopts_text', + 'item_definition', + 'item_comment', + 'item_definer'); + foreach ($indices as $index) { + $retval[$index] = isset($_POST[$index]) ? $_POST[$index] : ''; + } + + $retval['item_type'] = 'PROCEDURE'; + $retval['item_type_toggle'] = 'FUNCTION'; + if (isset($_REQUEST['item_type']) && $_REQUEST['item_type'] == 'FUNCTION') { + $retval['item_type'] = 'FUNCTION'; + $retval['item_type_toggle'] = 'PROCEDURE'; + } + $retval['item_original_type'] = 'PROCEDURE'; + if (isset($_POST['item_original_type']) + && $_POST['item_original_type'] == 'FUNCTION' + ) { + $retval['item_original_type'] = 'FUNCTION'; + } + $retval['item_num_params'] = 0; + $retval['item_param_dir'] = array(); + $retval['item_param_name'] = array(); + $retval['item_param_type'] = array(); + $retval['item_param_length'] = array(); + $retval['item_param_opts_num'] = array(); + $retval['item_param_opts_text'] = array(); + if (isset($_POST['item_param_name']) + && isset($_POST['item_param_type']) + && isset($_POST['item_param_length']) + && isset($_POST['item_param_opts_num']) + && isset($_POST['item_param_opts_text']) + && is_array($_POST['item_param_name']) + && is_array($_POST['item_param_type']) + && is_array($_POST['item_param_length']) + && is_array($_POST['item_param_opts_num']) + && is_array($_POST['item_param_opts_text']) + ) { + if ($_POST['item_type'] == 'PROCEDURE') { + $retval['item_param_dir'] = $_POST['item_param_dir']; + foreach ($retval['item_param_dir'] as $key => $value) { + if (! in_array($value, $param_directions, true)) { + $retval['item_param_dir'][$key] = ''; + } + } + } + $retval['item_param_name'] = $_POST['item_param_name']; + $retval['item_param_type'] = $_POST['item_param_type']; + foreach ($retval['item_param_type'] as $key => $value) { + if (! in_array($value, Util::getSupportedDatatypes(), true)) { + $retval['item_param_type'][$key] = ''; + } + } + $retval['item_param_length'] = $_POST['item_param_length']; + $retval['item_param_opts_num'] = $_POST['item_param_opts_num']; + $retval['item_param_opts_text'] = $_POST['item_param_opts_text']; + $retval['item_num_params'] = max( + count($retval['item_param_name']), + count($retval['item_param_type']), + count($retval['item_param_length']), + count($retval['item_param_opts_num']), + count($retval['item_param_opts_text']) + ); + } + $retval['item_returntype'] = ''; + if (isset($_POST['item_returntype']) + && in_array($_POST['item_returntype'], Util::getSupportedDatatypes()) + ) { + $retval['item_returntype'] = $_POST['item_returntype']; + } + + $retval['item_isdeterministic'] = ''; + if (isset($_POST['item_isdeterministic']) + && mb_strtolower($_POST['item_isdeterministic']) == 'on' + ) { + $retval['item_isdeterministic'] = " checked='checked'"; + } + $retval['item_securitytype_definer'] = ''; + $retval['item_securitytype_invoker'] = ''; + if (isset($_POST['item_securitytype'])) { + if ($_POST['item_securitytype'] === 'DEFINER') { + $retval['item_securitytype_definer'] = " selected='selected'"; + } elseif ($_POST['item_securitytype'] === 'INVOKER') { + $retval['item_securitytype_invoker'] = " selected='selected'"; + } + } + $retval['item_sqldataaccess'] = ''; + if (isset($_POST['item_sqldataaccess']) + && in_array($_POST['item_sqldataaccess'], $param_sqldataaccess, true) + ) { + $retval['item_sqldataaccess'] = $_POST['item_sqldataaccess']; + } + + return $retval; + } // end self::getDataFromRequest() + + /** + * This function will generate the values that are required to complete + * the "Edit routine" form given the name of a routine. + * + * @param string $name The name of the routine. + * @param string $type Type of routine (ROUTINE|PROCEDURE) + * @param bool $all Whether to return all data or just the info about parameters. + * + * @return array Data necessary to create the routine editor. + */ + public static function getDataFromName($name, $type, $all = true) + { + global $db; + + $retval = array(); + + // Build and execute the query + $fields = "SPECIFIC_NAME, ROUTINE_TYPE, DTD_IDENTIFIER, " + . "ROUTINE_DEFINITION, IS_DETERMINISTIC, SQL_DATA_ACCESS, " + . "ROUTINE_COMMENT, SECURITY_TYPE"; + $where = "ROUTINE_SCHEMA " . Util::getCollateForIS() . "=" + . "'" . $GLOBALS['dbi']->escapeString($db) . "' " + . "AND SPECIFIC_NAME='" . $GLOBALS['dbi']->escapeString($name) . "'" + . "AND ROUTINE_TYPE='" . $GLOBALS['dbi']->escapeString($type) . "'"; + $query = "SELECT $fields FROM INFORMATION_SCHEMA.ROUTINES WHERE $where;"; + + $routine = $GLOBALS['dbi']->fetchSingleRow($query, 'ASSOC'); + + if (! $routine) { + return false; + } + + // Get required data + $retval['item_name'] = $routine['SPECIFIC_NAME']; + $retval['item_type'] = $routine['ROUTINE_TYPE']; + + $definition + = $GLOBALS['dbi']->getDefinition( + $db, + $routine['ROUTINE_TYPE'], + $routine['SPECIFIC_NAME'] + ); + + if ($definition == null) { + return false; + } + + $parser = new Parser($definition); + + /** + * @var CreateStatement $stmt + */ + $stmt = $parser->statements[0]; + + $params = Routine::getParameters($stmt); + $retval['item_num_params'] = $params['num']; + $retval['item_param_dir'] = $params['dir']; + $retval['item_param_name'] = $params['name']; + $retval['item_param_type'] = $params['type']; + $retval['item_param_length'] = $params['length']; + $retval['item_param_length_arr'] = $params['length_arr']; + $retval['item_param_opts_num'] = $params['opts']; + $retval['item_param_opts_text'] = $params['opts']; + + // Get extra data + if (!$all) { + return $retval; + } + + if ($retval['item_type'] == 'FUNCTION') { + $retval['item_type_toggle'] = 'PROCEDURE'; + } else { + $retval['item_type_toggle'] = 'FUNCTION'; + } + $retval['item_returntype'] = ''; + $retval['item_returnlength'] = ''; + $retval['item_returnopts_num'] = ''; + $retval['item_returnopts_text'] = ''; + + if (! empty($routine['DTD_IDENTIFIER'])) { + $options = array(); + foreach ($stmt->return->options->options as $opt) { + $options[] = is_string($opt) ? $opt : $opt['value']; + } + + $retval['item_returntype'] = $stmt->return->name; + $retval['item_returnlength'] = implode(',', $stmt->return->parameters); + $retval['item_returnopts_num'] = implode(' ', $options); + $retval['item_returnopts_text'] = implode(' ', $options); + } + + $retval['item_definer'] = $stmt->options->has('DEFINER'); + $retval['item_definition'] = $routine['ROUTINE_DEFINITION']; + $retval['item_isdeterministic'] = ''; + if ($routine['IS_DETERMINISTIC'] == 'YES') { + $retval['item_isdeterministic'] = " checked='checked'"; + } + $retval['item_securitytype_definer'] = ''; + $retval['item_securitytype_invoker'] = ''; + if ($routine['SECURITY_TYPE'] == 'DEFINER') { + $retval['item_securitytype_definer'] = " selected='selected'"; + } elseif ($routine['SECURITY_TYPE'] == 'INVOKER') { + $retval['item_securitytype_invoker'] = " selected='selected'"; + } + $retval['item_sqldataaccess'] = $routine['SQL_DATA_ACCESS']; + $retval['item_comment'] = $routine['ROUTINE_COMMENT']; + + return $retval; + } // self::getDataFromName() + + /** + * Creates one row for the parameter table used in the routine editor. + * + * @param array $routine Data for the routine returned by + * self::getDataFromRequest() or + * self::getDataFromName() + * @param mixed $index Either a numeric index of the row being processed + * or NULL to create a template row for AJAX request + * @param string $class Class used to hide the direction column, if the + * row is for a stored function. + * + * @return string HTML code of one row of parameter table for the editor. + */ + public static function getParameterRow(array $routine = array(), $index = null, $class = '') + { + global $param_directions, $param_opts_num, $titles; + + if ($index === null) { + // template row for AJAX request + $i = 0; + $index = '%s'; + $drop_class = ''; + $routine = array( + 'item_param_dir' => array(0 => ''), + 'item_param_name' => array(0 => ''), + 'item_param_type' => array(0 => ''), + 'item_param_length' => array(0 => ''), + 'item_param_opts_num' => array(0 => ''), + 'item_param_opts_text' => array(0 => '') + ); + } elseif (! empty($routine)) { + // regular row for routine editor + $drop_class = ' hide'; + $i = $index; + } else { + // No input data. This shouldn't happen, + // but better be safe than sorry. + return ''; + } + + // Create the output + $retval = ""; + $retval .= "
      " + . "" + . "\n"; + $retval .= " \n"; + $retval .= " \n"; + $retval .= " \n"; + $retval .= " \n"; + $retval .= " ---\n"; + $retval .= Charsets::getCharsetDropdownBox( + $GLOBALS['dbi'], + $GLOBALS['cfg']['Server']['DisableIS'], + "item_param_opts_text[$index]", + null, + $routine['item_param_opts_text'][$i] + ); + $retval .= " ---\n"; + $retval .= " \n"; + $retval .= " \n"; + $retval .= " \n"; + $retval .= " {$titles['Drop']}\n"; + $retval .= " \n"; + $retval .= "
      \n"; + $retval .= "\n"; + $retval .= " \n"; + $retval .= " \n"; + $retval .= "\n"; + $retval .= "\n"; + $retval .= " \n"; + $retval .= " "; + $retval .= ""; + $retval .= ""; + $retval .= " "; + $retval .= " "; + $retval .= ""; + // parameter handling end + $retval .= ""; + $retval .= " "; + $retval .= " "; + $retval .= ""; + $retval .= ""; + $retval .= " "; + $retval .= " "; + $retval .= " "; + $retval .= ""; + $retval .= ""; + $retval .= " "; + $retval .= " "; + $retval .= ""; + $retval .= ""; + $retval .= " "; + $retval .= " "; + $retval .= ""; + $retval .= ""; + $retval .= " "; + $retval .= " "; + $retval .= ""; + if (isset($_REQUEST['edit_item']) + && ! empty($_REQUEST['edit_item']) + ) { + $retval .= ""; + $retval .= " "; + if ($GLOBALS['proc_priv'] + && $GLOBALS['is_reload_priv'] + ) { + $retval .= " "; + } else { + $retval .= " "; + } + $retval .= ""; + } + + $retval .= ""; + $retval .= " "; + $retval .= " "; + $retval .= ""; + $retval .= ""; + $retval .= " "; + $retval .= " "; + $retval .= ""; + $retval .= ""; + $retval .= " "; + $retval .= " "; + $retval .= ""; + $retval .= ""; + $retval .= " "; + $retval .= " "; + $retval .= ""; + $retval .= "
      " . __('Routine name') . "\n"; + $retval .= " \n"; + if ($response->isAjax()) { + $retval .= " \n"; + } else { + $retval .= "\n" + . "
      \n" + . $routine['item_type'] . "\n" + . "
      \n" + . "\n"; + } + $retval .= "
      " . __('Parameters') . "\n"; + // parameter handling start + $retval .= " \n"; + $retval .= " \n"; + $retval .= " \n"; + $retval .= " \n"; + $retval .= " \n"; + $retval .= " \n"; + $retval .= " \n"; + $retval .= " \n"; + $retval .= " \n"; + $retval .= " \n"; + $retval .= " "; + $retval .= " \n"; + $retval .= " \n"; + for ($i = 0; $i < $routine['item_num_params']; $i++) { // each parameter + $retval .= self::getParameterRow($routine, $i, $isprocedure_class); + } + $retval .= " \n"; + $retval .= "
      " + . __('Direction') . "" . __('Name') . "" . __('Type') . "" . __('Length/Values') . "" . __('Options') . " 
      "; + $retval .= "
       "; + $retval .= " "; + $retval .= " "; + $retval .= "
      " . __('Return type') . "
      " . __('Return length/values') . "---
      " . __('Return options') . "
      "; + $retval .= Charsets::getCharsetDropdownBox( + $GLOBALS['dbi'], + $GLOBALS['cfg']['Server']['DisableIS'], + "item_returnopts_text", + null, + $routine['item_returnopts_text'] + ); + $retval .= "
      "; + $retval .= "
      "; + $retval .= "
      ---
      "; + $retval .= "
      " . __('Definition') . "
      " . __('Is deterministic') . "
      " . __('Adjust privileges'); + $retval .= Util::showDocu('faq', 'faq6-39'); + $retval .= "
      " . __('Definer') . "
      " . __('Security type') . "
      " . __('SQL data access') . "
      " . __('Comment') . "
      "; + $retval .= "
      "; + if ($response->isAjax()) { + $retval .= ""; + $retval .= ""; + } + $retval .= ""; + $retval .= ""; + + return $retval; + } // end self::getEditorForm() + + /** + * Composes the query necessary to create a routine from an HTTP request. + * + * @return string The CREATE [ROUTINE | PROCEDURE] query. + */ + public static function getQueryFromRequest() + { + global $_REQUEST, $errors, $param_sqldataaccess, $param_directions, $dbi; + + $_POST['item_type'] = isset($_POST['item_type']) + ? $_POST['item_type'] : ''; + + $query = 'CREATE '; + if (! empty($_POST['item_definer'])) { + if (mb_strpos($_POST['item_definer'], '@') !== false) { + $arr = explode('@', $_POST['item_definer']); + + $do_backquote = true; + if (substr($arr[0], 0, 1) === "`" + && substr($arr[0], -1) === "`" + ) { + $do_backquote = false; + } + $query .= 'DEFINER=' . Util::backquote($arr[0], $do_backquote); + + $do_backquote = true; + if (substr($arr[1], 0, 1) === "`" + && substr($arr[1], -1) === "`" + ) { + $do_backquote = false; + } + $query .= '@' . Util::backquote($arr[1], $do_backquote) . ' '; + } else { + $errors[] = __('The definer must be in the "username@hostname" format!'); + } + } + if ($_POST['item_type'] == 'FUNCTION' + || $_POST['item_type'] == 'PROCEDURE' + ) { + $query .= $_POST['item_type'] . ' '; + } else { + $errors[] = sprintf( + __('Invalid routine type: "%s"'), + htmlspecialchars($_POST['item_type']) + ); + } + if (! empty($_POST['item_name'])) { + $query .= Util::backquote($_POST['item_name']); + } else { + $errors[] = __('You must provide a routine name!'); + } + $params = ''; + $warned_about_dir = false; + $warned_about_length = false; + + if (! empty($_POST['item_param_name']) + && ! empty($_POST['item_param_type']) + && ! empty($_POST['item_param_length']) + && is_array($_POST['item_param_name']) + && is_array($_POST['item_param_type']) + && is_array($_POST['item_param_length']) + ) { + $item_param_name = $_POST['item_param_name']; + $item_param_type = $_POST['item_param_type']; + $item_param_length = $_POST['item_param_length']; + + for ($i=0, $nb = count($item_param_name); $i < $nb; $i++) { + if (! empty($item_param_name[$i]) + && ! empty($item_param_type[$i]) + ) { + if ($_POST['item_type'] == 'PROCEDURE' + && ! empty($_POST['item_param_dir'][$i]) + && in_array($_POST['item_param_dir'][$i], $param_directions) + ) { + $params .= $_POST['item_param_dir'][$i] . " " + . Util::backquote($item_param_name[$i]) + . " " . $item_param_type[$i]; + } elseif ($_POST['item_type'] == 'FUNCTION') { + $params .= Util::backquote($item_param_name[$i]) + . " " . $item_param_type[$i]; + } elseif (! $warned_about_dir) { + $warned_about_dir = true; + $errors[] = sprintf( + __('Invalid direction "%s" given for parameter.'), + htmlspecialchars($_POST['item_param_dir'][$i]) + ); + } + if ($item_param_length[$i] != '' + && !preg_match( + '@^(DATE|TINYBLOB|TINYTEXT|BLOB|TEXT|' + . 'MEDIUMBLOB|MEDIUMTEXT|LONGBLOB|LONGTEXT|' + . 'SERIAL|BOOLEAN)$@i', + $item_param_type[$i] + ) + ) { + $params .= "(" . $item_param_length[$i] . ")"; + } elseif ($item_param_length[$i] == '' + && preg_match( + '@^(ENUM|SET|VARCHAR|VARBINARY)$@i', + $item_param_type[$i] + ) + ) { + if (! $warned_about_length) { + $warned_about_length = true; + $errors[] = __( + 'You must provide length/values for routine parameters' + . ' of type ENUM, SET, VARCHAR and VARBINARY.' + ); + } + } + if (! empty($_POST['item_param_opts_text'][$i])) { + if ($dbi->types->getTypeClass($item_param_type[$i]) == 'CHAR') { + if(! in_array($item_param_type[$i], array('VARBINARY', 'BINARY'))) { + $params .= ' CHARSET ' + . mb_strtolower( + $_POST['item_param_opts_text'][$i] + ); + } + } + } + if (! empty($_POST['item_param_opts_num'][$i])) { + if ($dbi->types->getTypeClass($item_param_type[$i]) == 'NUMBER') { + $params .= ' ' + . mb_strtoupper( + $_POST['item_param_opts_num'][$i] + ); + } + } + if ($i != (count($item_param_name) - 1)) { + $params .= ", "; + } + } else { + $errors[] = __( + 'You must provide a name and a type for each routine parameter.' + ); + break; + } + } + } + $query .= "(" . $params . ") "; + if ($_POST['item_type'] == 'FUNCTION') { + $item_returntype = isset($_POST['item_returntype']) + ? $_POST['item_returntype'] + : null; + + if (! empty($item_returntype) + && in_array( + $item_returntype, Util::getSupportedDatatypes() + ) + ) { + $query .= "RETURNS " . $item_returntype; + } else { + $errors[] = __('You must provide a valid return type for the routine.'); + } + if (! empty($_POST['item_returnlength']) + && !preg_match( + '@^(DATE|DATETIME|TIME|TINYBLOB|TINYTEXT|BLOB|TEXT|' + . 'MEDIUMBLOB|MEDIUMTEXT|LONGBLOB|LONGTEXT|SERIAL|BOOLEAN)$@i', + $item_returntype + ) + ) { + $query .= "(" . $_POST['item_returnlength'] . ")"; + } elseif (empty($_POST['item_returnlength']) + && preg_match( + '@^(ENUM|SET|VARCHAR|VARBINARY)$@i', $item_returntype + ) + ) { + if (! $warned_about_length) { + $errors[] = __( + 'You must provide length/values for routine parameters' + . ' of type ENUM, SET, VARCHAR and VARBINARY.' + ); + } + } + if (! empty($_POST['item_returnopts_text'])) { + if ($dbi->types->getTypeClass($item_returntype) == 'CHAR') { + $query .= ' CHARSET ' + . mb_strtolower($_POST['item_returnopts_text']); + } + } + if (! empty($_POST['item_returnopts_num'])) { + if ($dbi->types->getTypeClass($item_returntype) == 'NUMBER') { + $query .= ' ' + . mb_strtoupper($_POST['item_returnopts_num']); + } + } + $query .= ' '; + } + if (! empty($_POST['item_comment'])) { + $query .= "COMMENT '" . $GLOBALS['dbi']->escapeString($_POST['item_comment']) + . "' "; + } + if (isset($_POST['item_isdeterministic'])) { + $query .= 'DETERMINISTIC '; + } else { + $query .= 'NOT DETERMINISTIC '; + } + if (! empty($_POST['item_sqldataaccess']) + && in_array($_POST['item_sqldataaccess'], $param_sqldataaccess) + ) { + $query .= $_POST['item_sqldataaccess'] . ' '; + } + if (! empty($_POST['item_securitytype'])) { + if ($_POST['item_securitytype'] == 'DEFINER' + || $_POST['item_securitytype'] == 'INVOKER' + ) { + $query .= 'SQL SECURITY ' . $_POST['item_securitytype'] . ' '; + } + } + if (! empty($_POST['item_definition'])) { + $query .= $_POST['item_definition']; + } else { + $errors[] = __('You must provide a routine definition.'); + } + + return $query; + } // end self::getQueryFromRequest() + + /** + * Handles requests for executing a routine + * + * @return void + */ + public static function handleExecute() + { + global $_GET, $_POST, $_REQUEST, $GLOBALS, $db; + + $response = Response::getInstance(); + + /** + * Handle all user requests other than the default of listing routines + */ + if (! empty($_POST['execute_routine']) && ! empty($_POST['item_name'])) { + // Build the queries + $routine = self::getDataFromName( + $_POST['item_name'], $_POST['item_type'], false + ); + if ($routine === false) { + $message = __('Error in processing request:') . ' '; + $message .= sprintf( + Words::get('not_found'), + htmlspecialchars(Util::backquote($_POST['item_name'])), + htmlspecialchars(Util::backquote($db)) + ); + $message = Message::error($message); + if ($response->isAjax()) { + $response->setRequestStatus(false); + $response->addJSON('message', $message); + exit; + } else { + echo $message->getDisplay(); + unset($_POST); + } + } + + $queries = array(); + $end_query = array(); + $args = array(); + $all_functions = $GLOBALS['dbi']->types->getAllFunctions(); + for ($i = 0; $i < $routine['item_num_params']; $i++) { + if (isset($_POST['params'][$routine['item_param_name'][$i]])) { + $value = $_POST['params'][$routine['item_param_name'][$i]]; + if (is_array($value)) { // is SET type + $value = implode(',', $value); + } + $value = $GLOBALS['dbi']->escapeString($value); + if (! empty($_POST['funcs'][$routine['item_param_name'][$i]]) + && in_array( + $_POST['funcs'][$routine['item_param_name'][$i]], + $all_functions + ) + ) { + $queries[] = "SET @p$i=" + . $_POST['funcs'][$routine['item_param_name'][$i]] + . "('$value');\n"; + } else { + $queries[] = "SET @p$i='$value';\n"; + } + $args[] = "@p$i"; + } else { + $args[] = "@p$i"; + } + if ($routine['item_type'] == 'PROCEDURE') { + if ($routine['item_param_dir'][$i] == 'OUT' + || $routine['item_param_dir'][$i] == 'INOUT' + ) { + $end_query[] = "@p$i AS " + . Util::backquote($routine['item_param_name'][$i]); + } + } + } + if ($routine['item_type'] == 'PROCEDURE') { + $queries[] = "CALL " . Util::backquote($routine['item_name']) + . "(" . implode(', ', $args) . ");\n"; + if (count($end_query)) { + $queries[] = "SELECT " . implode(', ', $end_query) . ";\n"; + } + } else { + $queries[] = "SELECT " . Util::backquote($routine['item_name']) + . "(" . implode(', ', $args) . ") " + . "AS " . Util::backquote($routine['item_name']) + . ";\n"; + } + + // Get all the queries as one SQL statement + $multiple_query = implode("", $queries); + + $outcome = true; + $affected = 0; + + // Execute query + if (! $GLOBALS['dbi']->tryMultiQuery($multiple_query)) { + $outcome = false; + } + + // Generate output + if ($outcome) { + + // Pass the SQL queries through the "pretty printer" + $output = Util::formatSql(implode($queries, "\n")); + + // Display results + $output .= "
      "; + $output .= sprintf( + __('Execution results of routine %s'), + Util::backquote(htmlspecialchars($routine['item_name'])) + ); + $output .= ""; + + $nbResultsetToDisplay = 0; + + do { + + $result = $GLOBALS['dbi']->storeResult(); + $num_rows = $GLOBALS['dbi']->numRows($result); + + if (($result !== false) && ($num_rows > 0)) { + + $output .= ""; + foreach ($GLOBALS['dbi']->getFieldsMeta($result) as $field) { + $output .= ""; + } + $output .= ""; + + while ($row = $GLOBALS['dbi']->fetchAssoc($result)) { + $output .= "" . self::browseRow($row) . ""; + } + + $output .= "
      "; + $output .= htmlspecialchars($field->name); + $output .= "
      "; + $nbResultsetToDisplay++; + $affected = $num_rows; + + } + + if (! $GLOBALS['dbi']->moreResults()) { + break; + } + + $output .= "
      "; + + $GLOBALS['dbi']->freeResult($result); + + } while ($outcome = $GLOBALS['dbi']->nextResult()); + } + + if ($outcome) { + + $output .= "
      "; + + $message = __('Your SQL query has been executed successfully.'); + if ($routine['item_type'] == 'PROCEDURE') { + $message .= '
      '; + + // TODO : message need to be modified according to the + // output from the routine + $message .= sprintf( + _ngettext( + '%d row affected by the last statement inside the ' + . 'procedure.', + '%d rows affected by the last statement inside the ' + . 'procedure.', + $affected + ), + $affected + ); + } + $message = Message::success($message); + + if ($nbResultsetToDisplay == 0) { + $notice = __( + 'MySQL returned an empty result set (i.e. zero rows).' + ); + $output .= Message::notice($notice)->getDisplay(); + } + + } else { + $output = ''; + $message = Message::error( + sprintf( + __('The following query has failed: "%s"'), + htmlspecialchars($multiple_query) + ) + . '

      ' + . __('MySQL said: ') . $GLOBALS['dbi']->getError() + ); + } + + // Print/send output + if ($response->isAjax()) { + $response->setRequestStatus($message->isSuccess()); + $response->addJSON('message', $message->getDisplay() . $output); + $response->addJSON('dialog', false); + exit; + } else { + echo $message->getDisplay() , $output; + if ($message->isError()) { + // At least one query has failed, so shouldn't + // execute any more queries, so we quit. + exit; + } + unset($_POST); + // Now deliberately fall through to displaying the routines list + } + return; + } elseif (! empty($_GET['execute_dialog']) && ! empty($_GET['item_name'])) { + /** + * Display the execute form for a routine. + */ + $routine = self::getDataFromName( + $_GET['item_name'], $_GET['item_type'], true + ); + if ($routine !== false) { + $form = self::getExecuteForm($routine); + if ($response->isAjax()) { + $title = __("Execute routine") . " " . Util::backquote( + htmlentities($_GET['item_name'], ENT_QUOTES) + ); + $response->addJSON('message', $form); + $response->addJSON('title', $title); + $response->addJSON('dialog', true); + } else { + echo "\n\n

      " . __("Execute routine") . "

      \n\n"; + echo $form; + } + exit; + } elseif (($response->isAjax())) { + $message = __('Error in processing request:') . ' '; + $message .= sprintf( + Words::get('not_found'), + htmlspecialchars(Util::backquote($_GET['item_name'])), + htmlspecialchars(Util::backquote($db)) + ); + $message = Message::error($message); + + $response->setRequestStatus(false); + $response->addJSON('message', $message); + exit; + } + } + } + + /** + * Browse row array + * + * @param array $row Columns + * + * @return string + */ + private static function browseRow(array $row) + { + $output = null; + foreach ($row as $value) { + if ($value === null) { + $value = 'NULL'; + } else { + $value = htmlspecialchars($value); + } + $output .= "" . $value . ""; + } + return $output; + } + + /** + * Creates the HTML code that shows the routine execution dialog. + * + * @param array $routine Data for the routine returned by + * self::getDataFromName() + * + * @return string HTML code for the routine execution dialog. + */ + public static function getExecuteForm(array $routine) + { + global $db, $cfg; + + $response = Response::getInstance(); + + // Escape special characters + $routine['item_name'] = htmlentities($routine['item_name'], ENT_QUOTES); + for ($i = 0; $i < $routine['item_num_params']; $i++) { + $routine['item_param_name'][$i] = htmlentities( + $routine['item_param_name'][$i], + ENT_QUOTES + ); + } + + // Create the output + $retval = ""; + $retval .= "\n\n"; + $retval .= "
      isAjax()) { + $retval .= "{$routine['item_name']}\n"; + $retval .= "\n"; + $retval .= "\n"; + } else { + $retval .= "" . __('Routine parameters') . "\n"; + $retval .= "
      \n"; + $retval .= __('Routine parameters'); + $retval .= "
      \n"; + } + $retval .= "\n"; + $retval .= "\n"; + $retval .= "\n"; + if ($cfg['ShowFunctionFields']) { + $retval .= "\n"; + } + $retval .= "\n"; + $retval .= "\n"; + // Get a list of data types that are not yet supported. + $no_support_types = Util::unsupportedDatatypes(); + for ($i = 0; $i < $routine['item_num_params']; $i++) { // Each parameter + if ($routine['item_type'] == 'PROCEDURE' + && $routine['item_param_dir'][$i] == 'OUT' + ) { + continue; + } + $retval .= "\n\n"; + $retval .= "\n"; + $retval .= "\n"; + if ($cfg['ShowFunctionFields']) { + $retval .= "\n"; + } + // Append a class to date/time fields so that + // jQuery can attach a datepicker to them + $class = ''; + if ($routine['item_param_type'][$i] == 'DATETIME' + || $routine['item_param_type'][$i] == 'TIMESTAMP' + ) { + $class = 'datetimefield'; + } elseif ($routine['item_param_type'][$i] == 'DATE') { + $class = 'datefield'; + } + $retval .= "\n"; + $retval .= "\n"; + } + $retval .= "\n
      " . __('Name') . "" . __('Type') . "" . __('Function') . "" . __('Value') . "
      {$routine['item_param_name'][$i]}{$routine['item_param_type'][$i]}\n"; + if (stristr($routine['item_param_type'][$i], 'enum') + || stristr($routine['item_param_type'][$i], 'set') + || in_array( + mb_strtolower($routine['item_param_type'][$i]), + $no_support_types + ) + ) { + $retval .= "--\n"; + } else { + $field = array( + 'True_Type' => mb_strtolower( + $routine['item_param_type'][$i] + ), + 'Type' => '', + 'Key' => '', + 'Field' => '', + 'Default' => '', + 'first_timestamp' => false + ); + $retval .= ""; + } + $retval .= "\n"; + if (in_array($routine['item_param_type'][$i], array('ENUM', 'SET'))) { + if ($routine['item_param_type'][$i] == 'ENUM') { + $input_type = 'radio'; + } else { + $input_type = 'checkbox'; + } + foreach ($routine['item_param_length_arr'][$i] as $value) { + $value = htmlentities(Util::unquote($value), ENT_QUOTES); + $retval .= "" + . $value . "
      \n"; + } + } elseif (in_array( + mb_strtolower($routine['item_param_type'][$i]), + $no_support_types + )) { + $retval .= "\n"; + } else { + $retval .= "\n"; + } + $retval .= "
      \n"; + if (! $response->isAjax()) { + $retval .= "\n\n"; + $retval .= "
      \n"; + $retval .= " \n"; + $retval .= "
      \n"; + } else { + $retval .= ""; + $retval .= ""; + } + $retval .= "
      \n\n"; + $retval .= "\n\n"; + + return $retval; + } // end self::getExecuteForm() +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Rte/RteList.php b/php/apps/phpmyadmin49/libraries/classes/Rte/RteList.php new file mode 100644 index 00000000..fedf3278 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Rte/RteList.php @@ -0,0 +1,486 @@ +\n"; + $retval .= '
      '; + $retval .= Url::getHiddenInputs($GLOBALS['db'], $GLOBALS['table']); + $retval .= "
      \n"; + $retval .= " \n"; + $retval .= " " . Words::get('title') . "\n"; + $retval .= " " + . Util::showMySQLDocu(Words::get('docu')) . "\n"; + $retval .= " \n"; + $retval .= "
      \n"; + $retval .= " " . Words::get('nothing') . "\n"; + $retval .= "
      \n"; + $retval .= " \n"; + $retval .= " \n"; + $retval .= " \n"; + // th cells with a colspan need corresponding td cells, according to W3C + switch ($type) { + case 'routine': + $retval .= " \n"; + $retval .= " \n"; + $retval .= " \n"; + $retval .= " \n"; + $retval .= " \n"; + $retval .= " \n"; + $retval .= " \n"; // see comment above + for ($i = 0; $i < 7; $i++) { + $retval .= " \n"; + } + break; + case 'trigger': + $retval .= " \n"; + $retval .= " \n"; + if (empty($table)) { + $retval .= " \n"; + } + $retval .= " \n"; + $retval .= " \n"; + $retval .= " \n"; + $retval .= " \n"; + $retval .= " \n"; // see comment above + for ($i = 0; $i < (empty($table) ? 7 : 6); $i++) { + $retval .= " \n"; + } + break; + case 'event': + $retval .= " \n"; + $retval .= " \n"; + $retval .= " \n"; + $retval .= " \n"; + $retval .= " \n"; + $retval .= " \n"; + $retval .= " \n"; // see comment above + for ($i = 0; $i < 6; $i++) { + $retval .= " \n"; + } + break; + default: + break; + } + $retval .= " \n"; + $retval .= " \n"; + $count = 0; + $response = Response::getInstance(); + foreach ($items as $item) { + if ($response->isAjax() && empty($_REQUEST['ajax_page_request'])) { + $rowclass = 'ajaxInsert hide'; + } else { + $rowclass = ''; + } + // Get each row from the correct function + switch ($type) { + case 'routine': + $retval .= self::getRoutineRow($item, $rowclass); + break; + case 'trigger': + $retval .= self::getTriggerRow($item, $rowclass); + break; + case 'event': + $retval .= self::getEventRow($item, $rowclass); + break; + default: + break; + } + $count++; + } + $retval .= "
      " . __('Name') . "" . __('Action') . "" . __('Type') . "" . __('Returns') . "
      " . __('Name') . "" . __('Table') . "" . __('Action') . "" . __('Time') . "" . __('Event') . "
      " . __('Name') . "" . __('Status') . "" . __('Action') . "" . __('Type') . "
      \n"; + + if (count($items)) { + $retval .= '
      '; + $retval .= Template::get('select_all') + ->render( + array( + 'pma_theme_image' => $GLOBALS['pmaThemeImage'], + 'text_dir' => $GLOBALS['text_dir'], + 'form_name' => 'rteListForm', + ) + ); + $retval .= Util::getButtonOrImage( + 'submit_mult', 'mult_submit', + __('Export'), 'b_export', 'export' + ); + $retval .= Util::getButtonOrImage( + 'submit_mult', 'mult_submit', + __('Drop'), 'b_drop', 'drop' + ); + $retval .= '
      '; + } + + $retval .= "
      \n"; + $retval .= "
      \n"; + $retval .= "\n"; + + return $retval; + } // end self::get() + + /** + * Creates the contents for a row in the list of routines + * + * @param array $routine An array of routine data + * @param string $rowclass Additional class + * + * @return string HTML code of a row for the list of routines + */ + public static function getRoutineRow(array $routine, $rowclass = '') + { + global $url_query, $db, $titles; + + $sql_drop = sprintf( + 'DROP %s IF EXISTS %s', + $routine['type'], + Util::backquote($routine['name']) + ); + $type_link = "item_type={$routine['type']}"; + + $retval = " \n"; + $retval .= " \n"; + $retval .= ' '; + $retval .= " \n"; + $retval .= " \n"; + $retval .= " " + . htmlspecialchars($sql_drop) . "\n"; + $retval .= " \n"; + $retval .= " " + . htmlspecialchars($routine['name']) . "\n"; + $retval .= " \n"; + $retval .= " \n"; + $retval .= " \n"; + + // this is for our purpose to decide whether to + // show the edit link or not, so we need the DEFINER for the routine + $where = "ROUTINE_SCHEMA " . Util::getCollateForIS() . "=" + . "'" . $GLOBALS['dbi']->escapeString($db) . "' " + . "AND SPECIFIC_NAME='" . $GLOBALS['dbi']->escapeString($routine['name']) . "'" + . "AND ROUTINE_TYPE='" . $GLOBALS['dbi']->escapeString($routine['type']) . "'"; + $query = "SELECT `DEFINER` FROM INFORMATION_SCHEMA.ROUTINES WHERE $where;"; + $routine_definer = $GLOBALS['dbi']->fetchValue($query); + + $curr_user = $GLOBALS['dbi']->getCurrentUser(); + + // Since editing a procedure involved dropping and recreating, check also for + // CREATE ROUTINE privilege to avoid lost procedures. + if ((Util::currentUserHasPrivilege('CREATE ROUTINE', $db) + && $curr_user == $routine_definer) + || $GLOBALS['dbi']->isSuperuser() + ) { + $retval .= ' ' . $titles['Edit'] . "\n"; + } else { + $retval .= " {$titles['NoEdit']}\n"; + } + $retval .= " \n"; + $retval .= " \n"; + + // There is a problem with Util::currentUserHasPrivilege(): + // it does not detect all kinds of privileges, for example + // a direct privilege on a specific routine. So, at this point, + // we show the Execute link, hoping that the user has the correct rights. + // Also, information_schema might be hiding the ROUTINE_DEFINITION + // but a routine with no input parameters can be nonetheless executed. + + // Check if the routine has any input parameters. If it does, + // we will show a dialog to get values for these parameters, + // otherwise we can execute it directly. + + $definition = $GLOBALS['dbi']->getDefinition( + $db, $routine['type'], $routine['name'] + ); + if ($definition !== false) { + $parser = new Parser($definition); + + /** + * @var CreateStatement $stmt + */ + $stmt = $parser->statements[0]; + + $params = Routine::getParameters($stmt); + + if (Util::currentUserHasPrivilege('EXECUTE', $db)) { + $execute_action = 'execute_routine'; + for ($i = 0; $i < $params['num']; $i++) { + if ($routine['type'] == 'PROCEDURE' + && $params['dir'][$i] == 'OUT' + ) { + continue; + } + $execute_action = 'execute_dialog'; + break; + } + $query_part = $execute_action . '=1&item_name=' + . urlencode($routine['name']) . '&' . $type_link; + $retval .= ' ' . $titles['Execute'] . "\n"; + } else { + $retval .= " {$titles['NoExecute']}\n"; + } + } + + $retval .= " \n"; + $retval .= " \n"; + if ((Util::currentUserHasPrivilege('CREATE ROUTINE', $db) + && $curr_user == $routine_definer) + || $GLOBALS['dbi']->isSuperuser() + ) { + $retval .= ' ' . $titles['Export'] . "\n"; + } else { + $retval .= " {$titles['NoExport']}\n"; + } + $retval .= " \n"; + $retval .= " \n"; + $retval .= Util::linkOrButton( + 'sql.php' . $url_query . '&sql_query=' . urlencode($sql_drop) . '&goto=db_routines.php' . urlencode("?db={$db}"), + $titles['Drop'], + ['class' => 'ajax drop_anchor'] + ); + $retval .= " \n"; + $retval .= " \n"; + $retval .= " {$routine['type']}\n"; + $retval .= " \n"; + $retval .= " \n"; + $retval .= " " + . htmlspecialchars($routine['returns']) . "\n"; + $retval .= " \n"; + $retval .= " \n"; + + return $retval; + } // end self::getRoutineRow() + + /** + * Creates the contents for a row in the list of triggers + * + * @param array $trigger An array of routine data + * @param string $rowclass Additional class + * + * @return string HTML code of a cell for the list of triggers + */ + public static function getTriggerRow(array $trigger, $rowclass = '') + { + global $url_query, $db, $table, $titles; + + $retval = " \n"; + $retval .= " \n"; + $retval .= ' '; + $retval .= " \n"; + $retval .= " \n"; + $retval .= " " + . htmlspecialchars($trigger['drop']) . "\n"; + $retval .= " \n"; + $retval .= " " . htmlspecialchars($trigger['name']) . "\n"; + $retval .= " \n"; + $retval .= " \n"; + if (empty($table)) { + $retval .= " \n"; + $retval .= "" + . htmlspecialchars($trigger['table']) . ""; + $retval .= " \n"; + } + $retval .= " \n"; + if (Util::currentUserHasPrivilege('TRIGGER', $db, $table)) { + $retval .= ' ' . $titles['Edit'] . "\n"; + } else { + $retval .= " {$titles['NoEdit']}\n"; + } + $retval .= " \n"; + $retval .= " \n"; + $retval .= ' ' . $titles['Export'] . "\n"; + $retval .= " \n"; + $retval .= " \n"; + if (Util::currentUserHasPrivilege('TRIGGER', $db)) { + $retval .= Util::linkOrButton( + 'sql.php' . $url_query . '&sql_query=' . urlencode($trigger['drop']) . '&goto=db_triggers.php' . urlencode("?db={$db}"), + $titles['Drop'], + ['class' => 'ajax drop_anchor'] + ); + } else { + $retval .= " {$titles['NoDrop']}\n"; + } + $retval .= " \n"; + $retval .= " \n"; + $retval .= " {$trigger['action_timing']}\n"; + $retval .= " \n"; + $retval .= " \n"; + $retval .= " {$trigger['event_manipulation']}\n"; + $retval .= " \n"; + $retval .= " \n"; + + return $retval; + } // end self::getTriggerRow() + + /** + * Creates the contents for a row in the list of events + * + * @param array $event An array of routine data + * @param string $rowclass Additional class + * + * @return string HTML code of a cell for the list of events + */ + public static function getEventRow(array $event, $rowclass = '') + { + global $url_query, $db, $titles; + + $sql_drop = sprintf( + 'DROP EVENT IF EXISTS %s', + Util::backquote($event['name']) + ); + + $retval = " \n"; + $retval .= " \n"; + $retval .= ' '; + $retval .= " \n"; + $retval .= " \n"; + $retval .= " " + . htmlspecialchars($sql_drop) . "\n"; + $retval .= " \n"; + $retval .= " " + . htmlspecialchars($event['name']) . "\n"; + $retval .= " \n"; + $retval .= " \n"; + $retval .= " \n"; + $retval .= " {$event['status']}\n"; + $retval .= " \n"; + $retval .= " \n"; + if (Util::currentUserHasPrivilege('EVENT', $db)) { + $retval .= ' ' . $titles['Edit'] . "\n"; + } else { + $retval .= " {$titles['NoEdit']}\n"; + } + $retval .= " \n"; + $retval .= " \n"; + $retval .= ' ' . $titles['Export'] . "\n"; + $retval .= " \n"; + $retval .= " \n"; + if (Util::currentUserHasPrivilege('EVENT', $db)) { + $retval .= Util::linkOrButton( + 'sql.php' . $url_query . '&sql_query=' . urlencode($sql_drop) . '&goto=db_events.php' . urlencode("?db={$db}"), + $titles['Drop'], + ['class' => 'ajax drop_anchor'] + ); + } else { + $retval .= " {$titles['NoDrop']}\n"; + } + $retval .= " \n"; + $retval .= " \n"; + $retval .= " {$event['type']}\n"; + $retval .= " \n"; + $retval .= " \n"; + + return $retval; + } // end self::getEventRow() +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Rte/Triggers.php b/php/apps/phpmyadmin49/libraries/classes/Rte/Triggers.php new file mode 100644 index 00000000..c79d3eca --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Rte/Triggers.php @@ -0,0 +1,478 @@ +getTriggers($db, $table); + echo RteList::get('trigger', $items); + /** + * Display a link for adding a new trigger, + * if the user has the necessary privileges + */ + echo Footer::triggers(); + } // end self::main() + + /** + * Handles editor requests for adding or editing an item + * + * @return void + */ + public static function handleEditor() + { + global $_REQUEST, $_POST, $errors, $db, $table; + + if (! empty($_POST['editor_process_add']) + || ! empty($_POST['editor_process_edit']) + ) { + $sql_query = ''; + + $item_query = self::getQueryFromRequest(); + + if (! count($errors)) { // set by PhpMyAdmin\Rte\Routines::getQueryFromRequest() + // Execute the created query + if (! empty($_POST['editor_process_edit'])) { + // Backup the old trigger, in case something goes wrong + $trigger = self::getDataFromName($_POST['item_original_name']); + $create_item = $trigger['create']; + $drop_item = $trigger['drop'] . ';'; + $result = $GLOBALS['dbi']->tryQuery($drop_item); + if (! $result) { + $errors[] = sprintf( + __('The following query has failed: "%s"'), + htmlspecialchars($drop_item) + ) + . '
      ' + . __('MySQL said: ') . $GLOBALS['dbi']->getError(); + } else { + $result = $GLOBALS['dbi']->tryQuery($item_query); + if (! $result) { + $errors[] = sprintf( + __('The following query has failed: "%s"'), + htmlspecialchars($item_query) + ) + . '
      ' + . __('MySQL said: ') . $GLOBALS['dbi']->getError(); + // We dropped the old item, but were unable to create the + // new one. Try to restore the backup query. + $result = $GLOBALS['dbi']->tryQuery($create_item); + + $errors = General::checkResult( + $result, + __( + 'Sorry, we failed to restore the dropped trigger.' + ), + $create_item, + $errors + ); + } else { + $message = Message::success( + __('Trigger %1$s has been modified.') + ); + $message->addParam( + Util::backquote($_POST['item_name']) + ); + $sql_query = $drop_item . $item_query; + } + } + } else { + // 'Add a new item' mode + $result = $GLOBALS['dbi']->tryQuery($item_query); + if (! $result) { + $errors[] = sprintf( + __('The following query has failed: "%s"'), + htmlspecialchars($item_query) + ) + . '

      ' + . __('MySQL said: ') . $GLOBALS['dbi']->getError(); + } else { + $message = Message::success( + __('Trigger %1$s has been created.') + ); + $message->addParam( + Util::backquote($_POST['item_name']) + ); + $sql_query = $item_query; + } + } + } + + if (count($errors)) { + $message = Message::error( + '' + . __( + 'One or more errors have occurred while processing your request:' + ) + . '' + ); + $message->addHtml('
        '); + foreach ($errors as $string) { + $message->addHtml('
      • ' . $string . '
      • '); + } + $message->addHtml('
      '); + } + + $output = Util::getMessage($message, $sql_query); + $response = Response::getInstance(); + if ($response->isAjax()) { + if ($message->isSuccess()) { + $items = $GLOBALS['dbi']->getTriggers($db, $table, ''); + $trigger = false; + foreach ($items as $value) { + if ($value['name'] == $_POST['item_name']) { + $trigger = $value; + } + } + $insert = false; + if (empty($table) + || ($trigger !== false && $table == $trigger['table']) + ) { + $insert = true; + $response->addJSON('new_row', RteList::getTriggerRow($trigger)); + $response->addJSON( + 'name', + htmlspecialchars( + mb_strtoupper( + $_POST['item_name'] + ) + ) + ); + } + $response->addJSON('insert', $insert); + $response->addJSON('message', $output); + } else { + $response->addJSON('message', $message); + $response->setRequestStatus(false); + } + exit; + } + } + + /** + * Display a form used to add/edit a trigger, if necessary + */ + if (count($errors) + || (empty($_POST['editor_process_add']) + && empty($_POST['editor_process_edit']) + && (! empty($_REQUEST['add_item']) + || ! empty($_REQUEST['edit_item']))) // FIXME: this must be simpler than that + ) { + // Get the data for the form (if any) + if (! empty($_REQUEST['add_item'])) { + $title = Words::get('add'); + $item = self::getDataFromRequest(); + $mode = 'add'; + } elseif (! empty($_REQUEST['edit_item'])) { + $title = __("Edit trigger"); + if (! empty($_REQUEST['item_name']) + && empty($_POST['editor_process_edit']) + ) { + $item = self::getDataFromName($_REQUEST['item_name']); + if ($item !== false) { + $item['item_original_name'] = $item['item_name']; + } + } else { + $item = self::getDataFromRequest(); + } + $mode = 'edit'; + } + General::sendEditor('TRI', $mode, $item, $title, $db); + } + } // end self::handleEditor() + + /** + * This function will generate the values that are required to for the editor + * + * @return array Data necessary to create the editor. + */ + public static function getDataFromRequest() + { + $retval = array(); + $indices = array('item_name', + 'item_table', + 'item_original_name', + 'item_action_timing', + 'item_event_manipulation', + 'item_definition', + 'item_definer'); + foreach ($indices as $index) { + $retval[$index] = isset($_POST[$index]) ? $_POST[$index] : ''; + } + return $retval; + } // end self::getDataFromRequest() + + /** + * This function will generate the values that are required to complete + * the "Edit trigger" form given the name of a trigger. + * + * @param string $name The name of the trigger. + * + * @return array Data necessary to create the editor. + */ + public static function getDataFromName($name) + { + global $db, $table, $_REQUEST; + + $temp = array(); + $items = $GLOBALS['dbi']->getTriggers($db, $table, ''); + foreach ($items as $value) { + if ($value['name'] == $name) { + $temp = $value; + } + } + if (empty($temp)) { + return false; + } else { + $retval = array(); + $retval['create'] = $temp['create']; + $retval['drop'] = $temp['drop']; + $retval['item_name'] = $temp['name']; + $retval['item_table'] = $temp['table']; + $retval['item_action_timing'] = $temp['action_timing']; + $retval['item_event_manipulation'] = $temp['event_manipulation']; + $retval['item_definition'] = $temp['definition']; + $retval['item_definer'] = $temp['definer']; + return $retval; + } + } // end self::getDataFromName() + + /** + * Displays a form used to add/edit a trigger + * + * @param string $mode If the editor will be used to edit a trigger + * or add a new one: 'edit' or 'add'. + * @param array $item Data for the trigger returned by self::getDataFromRequest() + * or self::getDataFromName() + * + * @return string HTML code for the editor. + */ + public static function getEditorForm($mode, array $item) + { + global $db, $table, $event_manipulations, $action_timings; + + $modeToUpper = mb_strtoupper($mode); + $response = Response::getInstance(); + + // Escape special characters + $need_escape = array( + 'item_original_name', + 'item_name', + 'item_definition', + 'item_definer' + ); + foreach ($need_escape as $key => $index) { + $item[$index] = htmlentities($item[$index], ENT_QUOTES, 'UTF-8'); + } + $original_data = ''; + if ($mode == 'edit') { + $original_data = "\n"; + } + $query = "SELECT `TABLE_NAME` FROM `INFORMATION_SCHEMA`.`TABLES` "; + $query .= "WHERE `TABLE_SCHEMA`='" . $GLOBALS['dbi']->escapeString($db) . "' "; + $query .= "AND `TABLE_TYPE` IN ('BASE TABLE', 'SYSTEM VERSIONED')"; + $tables = $GLOBALS['dbi']->fetchResult($query); + + // Create the output + $retval = ""; + $retval .= "\n\n"; + $retval .= "
      \n"; + $retval .= "\n"; + $retval .= $original_data; + $retval .= Url::getHiddenInputs($db, $table) . "\n"; + $retval .= "
      \n"; + $retval .= "" . __('Details') . "\n"; + $retval .= "\n"; + $retval .= "\n"; + $retval .= " \n"; + $retval .= " \n"; + $retval .= "\n"; + $retval .= "\n"; + $retval .= " \n"; + $retval .= " \n"; + $retval .= "\n"; + $retval .= "\n"; + $retval .= " \n"; + $retval .= " \n"; + $retval .= "\n"; + $retval .= "\n"; + $retval .= " \n"; + $retval .= " \n"; + $retval .= "\n"; + $retval .= "\n"; + $retval .= " \n"; + $retval .= " '; + + $html_output .= ''; + + $html_output .= '' + . '' + . '' + . '' + . ''; + $current_user = $row['User']; + $current_host = $row['Host']; + $routine = $row['Routine_name']; + $html_output .= ''; + $html_output .= ''; + + $html_output .= ''; + + } + return $html_output; + } + + /** + * Get the HTML for user form and check the privileges for a particular database. + * + * @param string $db database name + * + * @return string $html_output + */ + public static function getHtmlForSpecificDbPrivileges($db) + { + $html_output = ''; + + if ($GLOBALS['dbi']->isSuperuser()) { + // check the privileges for a particular database. + $html_output = ''; + $html_output .= Url::getHiddenInputs($db); + $html_output .= '
      '; + $html_output .= '
      '; + $html_output .= '' . "\n" + . Util::getIcon('b_usrcheck') + . ' ' + . sprintf( + __('Users having access to "%s"'), + '' + . htmlspecialchars($db) + . '' + ) + . "\n" + . '' . "\n"; + + $html_output .= '
      '; + $html_output .= '
      " . __('Trigger name') . "\n"; + $retval .= " \n"; + $retval .= " \n"; + $retval .= "
      " . _pgettext('Trigger action time', 'Time') . "
      " . __('Event') . "
      " . __('Definition') . "
      " . __('Definer') . "isAjax()) { + $retval .= "\n"; + $retval .= "\n"; + } + $retval .= "\n\n"; + $retval .= "\n\n"; + + return $retval; + } // end self::getEditorForm() + + /** + * Composes the query necessary to create a trigger from an HTTP request. + * + * @return string The CREATE TRIGGER query. + */ + public static function getQueryFromRequest() + { + global $_REQUEST, $db, $errors, $action_timings, $event_manipulations; + + $query = 'CREATE '; + if (! empty($_POST['item_definer'])) { + if (mb_strpos($_POST['item_definer'], '@') !== false + ) { + $arr = explode('@', $_POST['item_definer']); + $query .= 'DEFINER=' . Util::backquote($arr[0]); + $query .= '@' . Util::backquote($arr[1]) . ' '; + } else { + $errors[] = __('The definer must be in the "username@hostname" format!'); + } + } + $query .= 'TRIGGER '; + if (! empty($_POST['item_name'])) { + $query .= Util::backquote($_POST['item_name']) . ' '; + } else { + $errors[] = __('You must provide a trigger name!'); + } + if (! empty($_POST['item_timing']) + && in_array($_POST['item_timing'], $action_timings) + ) { + $query .= $_POST['item_timing'] . ' '; + } else { + $errors[] = __('You must provide a valid timing for the trigger!'); + } + if (! empty($_POST['item_event']) + && in_array($_POST['item_event'], $event_manipulations) + ) { + $query .= $_POST['item_event'] . ' '; + } else { + $errors[] = __('You must provide a valid event for the trigger!'); + } + $query .= 'ON '; + if (! empty($_POST['item_table']) + && in_array($_POST['item_table'], $GLOBALS['dbi']->getTables($db)) + ) { + $query .= Util::backquote($_POST['item_table']); + } else { + $errors[] = __('You must provide a valid table name!'); + } + $query .= ' FOR EACH ROW '; + if (! empty($_POST['item_definition'])) { + $query .= $_POST['item_definition']; + } else { + $errors[] = __('You must provide a trigger definition.'); + } + + return $query; + } // end self::getQueryFromRequest() +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Rte/Words.php b/php/apps/phpmyadmin49/libraries/classes/Rte/Words.php new file mode 100644 index 00000000..ce3c305e --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Rte/Words.php @@ -0,0 +1,87 @@ + __('Add routine'), + 'docu' => 'STORED_ROUTINES', + 'export' => __('Export of routine %s'), + 'human' => __('routine'), + 'no_create' => __( + 'You do not have the necessary privileges to create a routine.' + ), + 'no_edit' => __( + 'No routine with name %1$s found in database %2$s. ' + . 'You might be lacking the necessary privileges to edit this routine.' + ), + 'no_view' => __( + 'No routine with name %1$s found in database %2$s. ' + . 'You might be lacking the necessary privileges to view/export this routine.' + ), + 'not_found' => __('No routine with name %1$s found in database %2$s.'), + 'nothing' => __('There are no routines to display.'), + 'title' => __('Routines'), + ); + break; + case 'TRI': + $words = array( + 'add' => __('Add trigger'), + 'docu' => 'TRIGGERS', + 'export' => __('Export of trigger %s'), + 'human' => __('trigger'), + 'no_create' => __( + 'You do not have the necessary privileges to create a trigger.' + ), + 'not_found' => __('No trigger with name %1$s found in database %2$s.'), + 'nothing' => __('There are no triggers to display.'), + 'title' => __('Triggers'), + ); + break; + case 'EVN': + $words = array( + 'add' => __('Add event'), + 'docu' => 'EVENTS', + 'export' => __('Export of event %s'), + 'human' => __('event'), + 'no_create' => __( + 'You do not have the necessary privileges to create an event.' + ), + 'not_found' => __('No event with name %1$s found in database %2$s.'), + 'nothing' => __('There are no events to display.'), + 'title' => __('Events'), + ); + break; + default: + $words = array(); + break; + } + + return isset($words[$index]) ? $words[$index] : ''; + } // end self::get() +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Sanitize.php b/php/apps/phpmyadmin49/libraries/classes/Sanitize.php new file mode 100644 index 00000000..e8ee32d5 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Sanitize.php @@ -0,0 +1,466 @@ +get('is_setup'); + // Adjust path to setup script location + if ($is_setup) { + foreach ($valid_starts as $key => $value) { + if (substr($value, 0, 2) === './') { + $valid_starts[$key] = '.' . $value; + } + } + } + if ($other) { + $valid_starts[] = 'mailto:'; + $valid_starts[] = 'ftp://'; + } + if ($http) { + $valid_starts[] = 'http://'; + } + if ($is_setup) { + $valid_starts[] = '?page=form&'; + $valid_starts[] = '?page=servers&'; + } + foreach ($valid_starts as $val) { + if (substr($url, 0, strlen($val)) == $val) { + return true; + } + } + return false; + } + + /** + * Callback function for replacing [a@link@target] links in bb code. + * + * @param array $found Array of preg matches + * + * @return string Replaced string + */ + public static function replaceBBLink(array $found) + { + /* Check for valid link */ + if (! self::checkLink($found[1])) { + return $found[0]; + } + /* a-z and _ allowed in target */ + if (! empty($found[3]) && preg_match('/[^a-z_]+/i', $found[3])) { + return $found[0]; + } + + /* Construct target */ + $target = ''; + if (! empty($found[3])) { + $target = ' target="' . $found[3] . '"'; + if ($found[3] == '_blank') { + $target .= ' rel="noopener noreferrer"'; + } + } + + /* Construct url */ + if (substr($found[1], 0, 4) == 'http') { + $url = Core::linkURL($found[1]); + } else { + $url = $found[1]; + } + + return ''; + } + + /** + * Callback function for replacing [doc@anchor] links in bb code. + * + * @param array $found Array of preg matches + * + * @return string Replaced string + */ + public static function replaceDocLink(array $found) + { + if (count($found) >= 4) { + $page = $found[1]; + $anchor = $found[3]; + } else { + $anchor = $found[1]; + if (strncmp('faq', $anchor, 3) == 0) { + $page = 'faq'; + } elseif (strncmp('cfg', $anchor, 3) == 0) { + $page = 'config'; + } else { + /* Guess */ + $page = 'setup'; + } + } + $link = Util::getDocuLink($page, $anchor); + return ''; + } + + /** + * Sanitizes $message, taking into account our special codes + * for formatting. + * + * If you want to include result in element attribute, you should escape it. + * + * Examples: + * + *

      + * + *
      bar + * + * @param string $message the message + * @param boolean $escape whether to escape html in result + * @param boolean $safe whether string is safe (can keep < and > chars) + * + * @return string the sanitized message + */ + public static function sanitize($message, $escape = false, $safe = false) + { + if (!$safe) { + $message = strtr($message, array('<' => '<', '>' => '>')); + } + + /* Interpret bb code */ + $replace_pairs = array( + '[em]' => '', + '[/em]' => '', + '[strong]' => '', + '[/strong]' => '', + '[code]' => '', + '[/code]' => '', + '[kbd]' => '', + '[/kbd]' => '', + '[br]' => '
      ', + '[/a]' => '', + '[/doc]' => '', + '[sup]' => '', + '[/sup]' => '', + // used in common.inc.php: + '[conferr]' => '', + // used in libraries/Util.php + '[dochelpicon]' => Util::getImage('b_help', __('Documentation')), + ); + + $message = strtr($message, $replace_pairs); + + /* Match links in bb code ([a@url@target], where @target is options) */ + $pattern = '/\[a@([^]"@]*)(@([^]"]*))?\]/'; + + /* Find and replace all links */ + $message = preg_replace_callback($pattern, function($match){ + return self::replaceBBLink($match); + }, $message); + + /* Replace documentation links */ + $message = preg_replace_callback( + '/\[doc@([a-zA-Z0-9_-]+)(@([a-zA-Z0-9_-]*))?\]/', + function($match){ + return self::replaceDocLink($match); + }, + $message + ); + + /* Possibly escape result */ + if ($escape) { + $message = htmlspecialchars($message); + } + + return $message; + } + + + /** + * Sanitize a filename by removing anything besides legit characters + * + * Intended usecase: + * When using a filename in a Content-Disposition header + * the value should not contain ; or " + * + * When exporting, avoiding generation of an unexpected double-extension file + * + * @param string $filename The filename + * @param boolean $replaceDots Whether to also replace dots + * + * @return string the sanitized filename + * + */ + public static function sanitizeFilename($filename, $replaceDots = false) + { + $pattern = '/[^A-Za-z0-9_'; + // if we don't have to replace dots + if (! $replaceDots) { + // then add the dot to the list of legit characters + $pattern .= '.'; + } + $pattern .= '-]/'; + $filename = preg_replace($pattern, '_', $filename); + return $filename; + } + + /** + * Format a string so it can be a string inside JavaScript code inside an + * eventhandler (onclick, onchange, on..., ). + * This function is used to displays a javascript confirmation box for + * "DROP/DELETE/ALTER" queries. + * + * @param string $a_string the string to format + * @param boolean $add_backquotes whether to add backquotes to the string or not + * + * @return string the formatted string + * + * @access public + */ + public static function jsFormat($a_string = '', $add_backquotes = true) + { + $a_string = htmlspecialchars($a_string); + $a_string = self::escapeJsString($a_string); + // Needed for inline javascript to prevent some browsers + // treating it as a anchor + $a_string = str_replace('#', '\\#', $a_string); + + return $add_backquotes + ? Util::backquote($a_string) + : $a_string; + } // end of the 'jsFormat' function + + /** + * escapes a string to be inserted as string a JavaScript block + * enclosed by + * this requires only to escape ' with \' and end of script block + * + * We also remove NUL byte as some browsers (namely MSIE) ignore it and + * inserting it anywhere inside '', + '\\' => '\\\\', + '\'' => '\\\'', + '"' => '\"', + "\n" => '\n', + "\r" => '\r' + ) + ) + ); + } + + /** + * Formats a value for javascript code. + * + * @param string $value String to be formatted. + * + * @return string formatted value. + */ + public static function formatJsVal($value) + { + if (is_bool($value)) { + if ($value) { + return 'true'; + } + + return 'false'; + } + + if (is_int($value)) { + return (int)$value; + } + + return '"' . self::escapeJsString($value) . '"'; + } + + /** + * Formats an javascript assignment with proper escaping of a value + * and support for assigning array of strings. + * + * @param string $key Name of value to set + * @param mixed $value Value to set, can be either string or array of strings + * @param bool $escape Whether to escape value or keep it as it is + * (for inclusion of js code) + * + * @return string Javascript code. + */ + public static function getJsValue($key, $value, $escape = true) + { + $result = $key . ' = '; + if (!$escape) { + $result .= $value; + } elseif (is_array($value)) { + $result .= '['; + foreach ($value as $val) { + $result .= self::formatJsVal($val) . ","; + } + $result .= "];\n"; + } else { + $result .= self::formatJsVal($value) . ";\n"; + } + return $result; + } + + /** + * Prints an javascript assignment with proper escaping of a value + * and support for assigning array of strings. + * + * @param string $key Name of value to set + * @param mixed $value Value to set, can be either string or array of strings + * + * @return void + */ + public static function printJsValue($key, $value) + { + echo self::getJsValue($key, $value); + } + + /** + * Formats javascript assignment for form validation api + * with proper escaping of a value. + * + * @param string $key Name of value to set + * @param string $value Value to set + * @param boolean $addOn Check if $.validator.format is required or not + * @param boolean $comma Check if comma is required + * + * @return string Javascript code. + */ + public static function getJsValueForFormValidation($key, $value, $addOn, $comma) + { + $result = $key . ': '; + if ($addOn) { + $result .= '$.validator.format('; + } + $result .= self::formatJsVal($value); + if ($addOn) { + $result .= ')'; + } + if ($comma) { + $result .= ', '; + } + return $result; + } + + /** + * Prints javascript assignment for form validation api + * with proper escaping of a value. + * + * @param string $key Name of value to set + * @param string $value Value to set + * @param boolean $addOn Check if $.validator.format is required or not + * @param boolean $comma Check if comma is required + * + * @return void + */ + public static function printJsValueForFormValidation($key, $value, $addOn=false, $comma=true) + { + echo self::getJsValueForFormValidation($key, $value, $addOn, $comma); + } + + /** + * Removes all variables from request except whitelisted ones. + * + * @param string &$whitelist list of variables to allow + * + * @return void + * @access public + */ + public static function removeRequestVars(&$whitelist) + { + // do not check only $_REQUEST because it could have been overwritten + // and use type casting because the variables could have become + // strings + if (! isset($_REQUEST)) { + $_REQUEST = array(); + } + if (! isset($_GET)) { + $_GET = array(); + } + if (! isset($_POST)) { + $_POST = array(); + } + if (! isset($_COOKIE)) { + $_COOKIE = array(); + } + $keys = array_keys( + array_merge((array)$_REQUEST, (array)$_GET, (array)$_POST, (array)$_COOKIE) + ); + + foreach ($keys as $key) { + if (! in_array($key, $whitelist)) { + unset($_REQUEST[$key], $_GET[$key], $_POST[$key]); + continue; + } + + // allowed stuff could be compromised so escape it + // we require it to be a string + if (isset($_REQUEST[$key]) && ! is_string($_REQUEST[$key])) { + unset($_REQUEST[$key]); + } + if (isset($_POST[$key]) && ! is_string($_POST[$key])) { + unset($_POST[$key]); + } + if (isset($_COOKIE[$key]) && ! is_string($_COOKIE[$key])) { + unset($_COOKIE[$key]); + } + if (isset($_GET[$key]) && ! is_string($_GET[$key])) { + unset($_GET[$key]); + } + } + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/SavedSearches.php b/php/apps/phpmyadmin49/libraries/classes/SavedSearches.php new file mode 100644 index 00000000..968abf4a --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/SavedSearches.php @@ -0,0 +1,468 @@ +setConfig($config); + $this->relation = new Relation(); + } + + /** + * Setter of id + * + * @param int|null $searchId Id of search + * + * @return static + */ + public function setId($searchId) + { + $searchId = (int)$searchId; + if (empty($searchId)) { + $searchId = null; + } + + $this->_id = $searchId; + return $this; + } + + /** + * Getter of id + * + * @return int|null + */ + public function getId() + { + return $this->_id; + } + + /** + * Setter of searchName + * + * @param string $searchName Saved search name + * + * @return static + */ + public function setSearchName($searchName) + { + $this->_searchName = $searchName; + return $this; + } + + /** + * Getter of searchName + * + * @return string + */ + public function getSearchName() + { + return $this->_searchName; + } + + /** + * Setter of config + * + * @param array $config Global configuration + * + * @return static + */ + public function setConfig(array $config) + { + $this->_config = $config; + return $this; + } + + /** + * Getter of config + * + * @return array + */ + public function getConfig() + { + return $this->_config; + } + + /** + * Setter for criterias + * + * @param array|string $criterias Criterias of saved searches + * @param bool $json Criterias are in JSON format + * + * @return static + */ + public function setCriterias($criterias, $json = false) + { + if (true === $json && is_string($criterias)) { + $this->_criterias = json_decode($criterias, true); + return $this; + } + + $aListFieldsToGet = array( + 'criteriaColumn', + 'criteriaSort', + 'criteriaShow', + 'criteria', + 'criteriaAndOrRow', + 'criteriaAndOrColumn', + 'rows', + 'TableList' + ); + + $data = array(); + + $data['criteriaColumnCount'] = count($criterias['criteriaColumn']); + + foreach ($aListFieldsToGet as $field) { + if (isset($criterias[$field])) { + $data[$field] = $criterias[$field]; + } + } + + /* Limit amount of rows */ + if (!isset($data['rows'])) { + $data['rows'] = 0; + } else { + $data['rows'] = min( + max(0, intval($data['rows'])), + 100 + ); + } + + for ($i = 0; $i <= $data['rows']; $i++) { + $data['Or' . $i] = $criterias['Or' . $i]; + } + + $this->_criterias = $data; + return $this; + } + + /** + * Getter for criterias + * + * @return array + */ + public function getCriterias() + { + return $this->_criterias; + } + + /** + * Setter for username + * + * @param string $username Username + * + * @return static + */ + public function setUsername($username) + { + $this->_username = $username; + return $this; + } + + /** + * Getter for username + * + * @return string + */ + public function getUsername() + { + return $this->_username; + } + + /** + * Setter for DB name + * + * @param string $dbname DB name + * + * @return static + */ + public function setDbname($dbname) + { + $this->_dbname = $dbname; + return $this; + } + + /** + * Getter for DB name + * + * @return string + */ + public function getDbname() + { + return $this->_dbname; + } + + /** + * Save the search + * + * @return boolean + */ + public function save() + { + if (null == $this->getSearchName()) { + $message = Message::error( + __('Please provide a name for this bookmarked search.') + ); + $response = Response::getInstance(); + $response->setRequestStatus($message->isSuccess()); + $response->addJSON('fieldWithError', 'searchName'); + $response->addJSON('message', $message); + exit; + } + + if (null == $this->getUsername() + || null == $this->getDbname() + || null == $this->getSearchName() + || null == $this->getCriterias() + ) { + $message = Message::error( + __('Missing information to save the bookmarked search.') + ); + $response = Response::getInstance(); + $response->setRequestStatus($message->isSuccess()); + $response->addJSON('message', $message); + exit; + } + + $savedSearchesTbl + = Util::backquote($this->_config['cfgRelation']['db']) . "." + . Util::backquote($this->_config['cfgRelation']['savedsearches']); + + //If it's an insert. + if (null === $this->getId()) { + $wheres = array( + "search_name = '" . $GLOBALS['dbi']->escapeString($this->getSearchName()) + . "'" + ); + $existingSearches = $this->getList($wheres); + + if (!empty($existingSearches)) { + $message = Message::error( + __('An entry with this name already exists.') + ); + $response = Response::getInstance(); + $response->setRequestStatus($message->isSuccess()); + $response->addJSON('fieldWithError', 'searchName'); + $response->addJSON('message', $message); + exit; + } + + $sqlQuery = "INSERT INTO " . $savedSearchesTbl + . "(`username`, `db_name`, `search_name`, `search_data`)" + . " VALUES (" + . "'" . $GLOBALS['dbi']->escapeString($this->getUsername()) . "'," + . "'" . $GLOBALS['dbi']->escapeString($this->getDbname()) . "'," + . "'" . $GLOBALS['dbi']->escapeString($this->getSearchName()) . "'," + . "'" . $GLOBALS['dbi']->escapeString(json_encode($this->getCriterias())) + . "')"; + + $result = (bool) $this->relation->queryAsControlUser($sqlQuery); + if (!$result) { + return false; + } + + $this->setId($GLOBALS['dbi']->insertId()); + + return true; + } + + //Else, it's an update. + $wheres = array( + "id != " . $this->getId(), + "search_name = '" . $GLOBALS['dbi']->escapeString($this->getSearchName()) . "'" + ); + $existingSearches = $this->getList($wheres); + + if (!empty($existingSearches)) { + $message = Message::error( + __('An entry with this name already exists.') + ); + $response = Response::getInstance(); + $response->setRequestStatus($message->isSuccess()); + $response->addJSON('fieldWithError', 'searchName'); + $response->addJSON('message', $message); + exit; + } + + $sqlQuery = "UPDATE " . $savedSearchesTbl + . "SET `search_name` = '" + . $GLOBALS['dbi']->escapeString($this->getSearchName()) . "', " + . "`search_data` = '" + . $GLOBALS['dbi']->escapeString(json_encode($this->getCriterias())) . "' " + . "WHERE id = " . $this->getId(); + return (bool) $this->relation->queryAsControlUser($sqlQuery); + } + + /** + * Delete the search + * + * @return boolean + */ + public function delete() + { + if (null == $this->getId()) { + $message = Message::error( + __('Missing information to delete the search.') + ); + $response = Response::getInstance(); + $response->setRequestStatus($message->isSuccess()); + $response->addJSON('fieldWithError', 'searchId'); + $response->addJSON('message', $message); + exit; + } + + $savedSearchesTbl + = Util::backquote($this->_config['cfgRelation']['db']) . "." + . Util::backquote($this->_config['cfgRelation']['savedsearches']); + + $sqlQuery = "DELETE FROM " . $savedSearchesTbl + . "WHERE id = '" . $GLOBALS['dbi']->escapeString($this->getId()) . "'"; + + return (bool) $this->relation->queryAsControlUser($sqlQuery); + } + + /** + * Load the current search from an id. + * + * @return bool Success + */ + public function load() + { + if (null == $this->getId()) { + $message = Message::error( + __('Missing information to load the search.') + ); + $response = Response::getInstance(); + $response->setRequestStatus($message->isSuccess()); + $response->addJSON('fieldWithError', 'searchId'); + $response->addJSON('message', $message); + exit; + } + + $savedSearchesTbl = Util::backquote($this->_config['cfgRelation']['db']) + . "." + . Util::backquote($this->_config['cfgRelation']['savedsearches']); + $sqlQuery = "SELECT id, search_name, search_data " + . "FROM " . $savedSearchesTbl . " " + . "WHERE id = '" . $GLOBALS['dbi']->escapeString($this->getId()) . "' "; + + $resList = $this->relation->queryAsControlUser($sqlQuery); + + if (false === ($oneResult = $GLOBALS['dbi']->fetchArray($resList))) { + $message = Message::error(__('Error while loading the search.')); + $response = Response::getInstance(); + $response->setRequestStatus($message->isSuccess()); + $response->addJSON('fieldWithError', 'searchId'); + $response->addJSON('message', $message); + exit; + } + + $this->setSearchName($oneResult['search_name']) + ->setCriterias($oneResult['search_data'], true); + + return true; + } + + /** + * Get the list of saved searches of a user on a DB + * + * @param string[] $wheres List of filters + * + * @return array List of saved searches or empty array on failure + */ + public function getList(array $wheres = array()) + { + if (null == $this->getUsername() + || null == $this->getDbname() + ) { + return array(); + } + + $savedSearchesTbl = Util::backquote($this->_config['cfgRelation']['db']) + . "." + . Util::backquote($this->_config['cfgRelation']['savedsearches']); + $sqlQuery = "SELECT id, search_name " + . "FROM " . $savedSearchesTbl . " " + . "WHERE " + . "username = '" . $GLOBALS['dbi']->escapeString($this->getUsername()) . "' " + . "AND db_name = '" . $GLOBALS['dbi']->escapeString($this->getDbname()) . "' "; + + foreach ($wheres as $where) { + $sqlQuery .= "AND " . $where . " "; + } + + $sqlQuery .= "order by search_name ASC "; + + $resList = $this->relation->queryAsControlUser($sqlQuery); + + $list = array(); + while ($oneResult = $GLOBALS['dbi']->fetchArray($resList)) { + $list[$oneResult['id']] = $oneResult['search_name']; + } + + return $list; + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Scripts.php b/php/apps/phpmyadmin49/libraries/classes/Scripts.php new file mode 100644 index 00000000..481ebff1 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Scripts.php @@ -0,0 +1,214 @@ + PMA_VERSION)); + $result .= "\n"; + } else { + $result .= '' . "\n"; + } + } + return $result; + } + + /** + * Generates new Scripts objects + * + */ + public function __construct() + { + $this->_files = array(); + $this->_code = ''; + + } + + /** + * Adds a new file to the list of scripts + * + * @param string $filename The name of the file to include + * @param array $params Additional parameters to pass to the file + * + * @return void + */ + public function addFile( + $filename, + array $params = array() + ) { + $hash = md5($filename); + if (!empty($this->_files[$hash])) { + return; + } + + $has_onload = $this->_eventBlacklist($filename); + $this->_files[$hash] = array( + 'has_onload' => $has_onload, + 'filename' => $filename, + 'params' => $params, + ); + } + + /** + * Add new files to the list of scripts + * + * @param array $filelist The array of file names + * + * @return void + */ + public function addFiles(array $filelist) + { + foreach ($filelist as $filename) { + $this->addFile($filename); + } + } + + /** + * Determines whether to fire up an onload event for a file + * + * @param string $filename The name of the file to be checked + * against the blacklist + * + * @return int 1 to fire up the event, 0 not to + */ + private function _eventBlacklist($filename) + { + if (strpos($filename, 'jquery') !== false + || strpos($filename, 'codemirror') !== false + || strpos($filename, 'messages.php') !== false + || strpos($filename, 'ajax.js') !== false + || strpos($filename, 'cross_framing_protection.js') !== false + ) { + return 0; + } + + return 1; + } + + /** + * Adds a new code snippet to the code to be executed + * + * @param string $code The JS code to be added + * + * @return void + */ + public function addCode($code) + { + $this->_code .= "$code\n"; + } + + /** + * Returns a list with filenames and a flag to indicate + * whether to register onload events for this file + * + * @return array + */ + public function getFiles() + { + $retval = array(); + foreach ($this->_files as $file) { + //If filename contains a "?", continue. + if (strpos($file['filename'], "?") !== false) { + continue; + } + $retval[] = array( + 'name' => $file['filename'], + 'fire' => $file['has_onload'] + ); + + } + return $retval; + } + + /** + * Renders all the JavaScript file inclusions, code and events + * + * @return string + */ + public function getDisplay() + { + $retval = ''; + + if (count($this->_files) > 0) { + $retval .= $this->_includeFiles( + $this->_files + ); + } + + $code = 'AJAX.scriptHandler'; + foreach ($this->_files as $file) { + $code .= sprintf( + '.add("%s",%d)', + Sanitize::escapeJsString($file['filename']), + $file['has_onload'] ? 1 : 0 + ); + } + $code .= ';'; + $this->addCode($code); + + $code = '$(function() {'; + foreach ($this->_files as $file) { + if ($file['has_onload']) { + $code .= 'AJAX.fireOnload("'; + $code .= Sanitize::escapeJsString($file['filename']); + $code .= '");'; + } + } + $code .= '});'; + $this->addCode($code); + + $retval .= ''; + + return $retval; + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Server/Privileges.php b/php/apps/phpmyadmin49/libraries/classes/Server/Privileges.php new file mode 100644 index 00000000..c731bdcc --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Server/Privileges.php @@ -0,0 +1,5438 @@ +isAjax()) { + $response->addJSON('message', $dialog); + exit; + } else { + $html .= $dialog; + } + } + + return $html; + } + + /** + * Escapes wildcard in a database+table specification + * before using it in a GRANT statement. + * + * Escaping a wildcard character in a GRANT is only accepted at the global + * or database level, not at table level; this is why I remove + * the escaping character. Internally, in mysql.tables_priv.Db there are + * no escaping (for example test_db) but in mysql.db you'll see test\_db + * for a db-specific privilege. + * + * @param string $dbname Database name + * @param string $tablename Table name + * + * @return string the escaped (if necessary) database.table + */ + public static function wildcardEscapeForGrant($dbname, $tablename) + { + if (strlen($dbname) === 0) { + $db_and_table = '*.*'; + } else { + if (strlen($tablename) > 0) { + $db_and_table = Util::backquote( + Util::unescapeMysqlWildcards($dbname) + ) + . '.' . Util::backquote($tablename); + } else { + $db_and_table = Util::backquote($dbname) . '.*'; + } + } + return $db_and_table; + } + + /** + * Generates a condition on the user name + * + * @param string $initial the user's initial + * + * @return string the generated condition + */ + public static function rangeOfUsers($initial = '') + { + // strtolower() is used because the User field + // might be BINARY, so LIKE would be case sensitive + if ($initial === null || $initial === '') { + return ''; + } + + $ret = " WHERE `User` LIKE '" + . $GLOBALS['dbi']->escapeString($initial) . "%'" + . " OR `User` LIKE '" + . $GLOBALS['dbi']->escapeString(mb_strtolower($initial)) + . "%'"; + return $ret; + } // end function + + /** + * Formats privilege name for a display + * + * @param array $privilege Privilege information + * @param boolean $html Whether to use HTML + * + * @return string + */ + public static function formatPrivilege(array $privilege, $html) + { + if ($html) { + return '' + . $privilege[1] . ''; + } + + return $privilege[1]; + } + + /** + * Parses privileges into an array, it modifies the array + * + * @param array &$row Results row from + * + * @return void + */ + public static function fillInTablePrivileges(array &$row) + { + $row1 = $GLOBALS['dbi']->fetchSingleRow( + 'SHOW COLUMNS FROM `mysql`.`tables_priv` LIKE \'Table_priv\';', + 'ASSOC' + ); + // note: in MySQL 5.0.3 we get "Create View', 'Show view'; + // the View for Create is spelled with uppercase V + // the view for Show is spelled with lowercase v + // and there is a space between the words + + $av_grants = explode( + '\',\'', + mb_substr( + $row1['Type'], + mb_strpos($row1['Type'], '(') + 2, + mb_strpos($row1['Type'], ')') + - mb_strpos($row1['Type'], '(') - 3 + ) + ); + + $users_grants = explode(',', $row['Table_priv']); + + foreach ($av_grants as $current_grant) { + $row[$current_grant . '_priv'] + = in_array($current_grant, $users_grants) ? 'Y' : 'N'; + } + unset($row['Table_priv']); + } + + + /** + * Extracts the privilege information of a priv table row + * + * @param array|null $row the row + * @param boolean $enableHTML add tag with tooltips + * @param boolean $tablePrivs whether row contains table privileges + * + * @global resource $user_link the database connection + * + * @return array + */ + public static function extractPrivInfo($row = null, $enableHTML = false, $tablePrivs = false) + { + if ($tablePrivs) { + $grants = self::getTableGrantsArray(); + } else { + $grants = self::getGrantsArray(); + } + + if (! is_null($row) && isset($row['Table_priv'])) { + self::fillInTablePrivileges($row); + } + + $privs = array(); + $allPrivileges = true; + foreach ($grants as $current_grant) { + if ((! is_null($row) && isset($row[$current_grant[0]])) + || (is_null($row) && isset($GLOBALS[$current_grant[0]])) + ) { + if ((! is_null($row) && $row[$current_grant[0]] == 'Y') + || (is_null($row) + && ($GLOBALS[$current_grant[0]] == 'Y' + || (is_array($GLOBALS[$current_grant[0]]) + && count($GLOBALS[$current_grant[0]]) == $_REQUEST['column_count'] + && empty($GLOBALS[$current_grant[0] . '_none'])))) + ) { + $privs[] = self::formatPrivilege($current_grant, $enableHTML); + } elseif (! empty($GLOBALS[$current_grant[0]]) + && is_array($GLOBALS[$current_grant[0]]) + && empty($GLOBALS[$current_grant[0] . '_none']) + ) { + // Required for proper escaping of ` (backtick) in a column name + $grant_cols = array_map( + function($val) { + return Util::backquote($val); + }, + $GLOBALS[$current_grant[0]] + ); + + $privs[] = self::formatPrivilege($current_grant, $enableHTML) + . ' (' . join(', ', $grant_cols) . ')'; + } else { + $allPrivileges = false; + } + } + } + if (empty($privs)) { + if ($enableHTML) { + $privs[] = 'USAGE'; + } else { + $privs[] = 'USAGE'; + } + } elseif ($allPrivileges + && (! isset($_POST['grant_count']) || count($privs) == $_POST['grant_count']) + ) { + if ($enableHTML) { + $privs = array('ALL PRIVILEGES' + ); + } else { + $privs = array('ALL PRIVILEGES'); + } + } + return $privs; + } // end of the 'self::extractPrivInfo()' function + + /** + * Returns an array of table grants and their descriptions + * + * @return array array of table grants + */ + public static function getTableGrantsArray() + { + return array( + array( + 'Delete', + 'DELETE', + $GLOBALS['strPrivDescDelete'] + ), + array( + 'Create', + 'CREATE', + $GLOBALS['strPrivDescCreateTbl'] + ), + array( + 'Drop', + 'DROP', + $GLOBALS['strPrivDescDropTbl'] + ), + array( + 'Index', + 'INDEX', + $GLOBALS['strPrivDescIndex'] + ), + array( + 'Alter', + 'ALTER', + $GLOBALS['strPrivDescAlter'] + ), + array( + 'Create View', + 'CREATE_VIEW', + $GLOBALS['strPrivDescCreateView'] + ), + array( + 'Show view', + 'SHOW_VIEW', + $GLOBALS['strPrivDescShowView'] + ), + array( + 'Trigger', + 'TRIGGER', + $GLOBALS['strPrivDescTrigger'] + ), + ); + } + + /** + * Get the grants array which contains all the privilege types + * and relevant grant messages + * + * @return array + */ + public static function getGrantsArray() + { + return array( + array( + 'Select_priv', + 'SELECT', + __('Allows reading data.') + ), + array( + 'Insert_priv', + 'INSERT', + __('Allows inserting and replacing data.') + ), + array( + 'Update_priv', + 'UPDATE', + __('Allows changing data.') + ), + array( + 'Delete_priv', + 'DELETE', + __('Allows deleting data.') + ), + array( + 'Create_priv', + 'CREATE', + __('Allows creating new databases and tables.') + ), + array( + 'Drop_priv', + 'DROP', + __('Allows dropping databases and tables.') + ), + array( + 'Reload_priv', + 'RELOAD', + __('Allows reloading server settings and flushing the server\'s caches.') + ), + array( + 'Shutdown_priv', + 'SHUTDOWN', + __('Allows shutting down the server.') + ), + array( + 'Process_priv', + 'PROCESS', + __('Allows viewing processes of all users.') + ), + array( + 'File_priv', + 'FILE', + __('Allows importing data from and exporting data into files.') + ), + array( + 'References_priv', + 'REFERENCES', + __('Has no effect in this MySQL version.') + ), + array( + 'Index_priv', + 'INDEX', + __('Allows creating and dropping indexes.') + ), + array( + 'Alter_priv', + 'ALTER', + __('Allows altering the structure of existing tables.') + ), + array( + 'Show_db_priv', + 'SHOW DATABASES', + __('Gives access to the complete list of databases.') + ), + array( + 'Super_priv', + 'SUPER', + __( + 'Allows connecting, even if maximum number of connections ' + . 'is reached; required for most administrative operations ' + . 'like setting global variables or killing threads of other users.' + ) + ), + array( + 'Create_tmp_table_priv', + 'CREATE TEMPORARY TABLES', + __('Allows creating temporary tables.') + ), + array( + 'Lock_tables_priv', + 'LOCK TABLES', + __('Allows locking tables for the current thread.') + ), + array( + 'Repl_slave_priv', + 'REPLICATION SLAVE', + __('Needed for the replication slaves.') + ), + array( + 'Repl_client_priv', + 'REPLICATION CLIENT', + __('Allows the user to ask where the slaves / masters are.') + ), + array( + 'Create_view_priv', + 'CREATE VIEW', + __('Allows creating new views.') + ), + array( + 'Event_priv', + 'EVENT', + __('Allows to set up events for the event scheduler.') + ), + array( + 'Trigger_priv', + 'TRIGGER', + __('Allows creating and dropping triggers.') + ), + // for table privs: + array( + 'Create View_priv', + 'CREATE VIEW', + __('Allows creating new views.') + ), + array( + 'Show_view_priv', + 'SHOW VIEW', + __('Allows performing SHOW CREATE VIEW queries.') + ), + // for table privs: + array( + 'Show view_priv', + 'SHOW VIEW', + __('Allows performing SHOW CREATE VIEW queries.') + ), + array( + 'Delete_history_priv', + 'DELETE HISTORY', + $GLOBALS['strPrivDescDeleteHistoricalRows'] + ), + array( + 'Delete versioning rows_priv', + 'DELETE HISTORY', + $GLOBALS['strPrivDescDeleteHistoricalRows'] + ), + array( + 'Create_routine_priv', + 'CREATE ROUTINE', + __('Allows creating stored routines.') + ), + array( + 'Alter_routine_priv', + 'ALTER ROUTINE', + __('Allows altering and dropping stored routines.') + ), + array( + 'Create_user_priv', + 'CREATE USER', + __('Allows creating, dropping and renaming user accounts.') + ), + array( + 'Execute_priv', + 'EXECUTE', + __('Allows executing stored routines.') + ), + ); + } + + /** + * Displays on which column(s) a table-specific privilege is granted + * + * @param array $columns columns array + * @param array $row first row from result or boolean false + * @param string $name_for_select privilege types - Select_priv, Insert_priv + * Update_priv, References_priv + * @param string $priv_for_header privilege for header + * @param string $name privilege name: insert, select, update, references + * @param string $name_for_dfn name for dfn + * @param string $name_for_current name for current + * + * @return string $html_output html snippet + */ + public static function getHtmlForColumnPrivileges(array $columns, array $row, $name_for_select, + $priv_for_header, $name, $name_for_dfn, $name_for_current + ) { + $data = array( + 'columns' => $columns, + 'row' => $row, + 'name_for_select' => $name_for_select, + 'priv_for_header' => $priv_for_header, + 'name' => $name, + 'name_for_dfn' => $name_for_dfn, + 'name_for_current' => $name_for_current + ); + + $html_output = Template::get('privileges/column_privileges') + ->render($data); + + return $html_output; + } // end function + + /** + * Get sql query for display privileges table + * + * @param string $db the database + * @param string $table the table + * @param string $username username for database connection + * @param string $hostname hostname for database connection + * + * @return string sql query + */ + public static function getSqlQueryForDisplayPrivTable($db, $table, $username, $hostname) + { + if ($db == '*') { + return "SELECT * FROM `mysql`.`user`" + . " WHERE `User` = '" . $GLOBALS['dbi']->escapeString($username) . "'" + . " AND `Host` = '" . $GLOBALS['dbi']->escapeString($hostname) . "';"; + } elseif ($table == '*') { + return "SELECT * FROM `mysql`.`db`" + . " WHERE `User` = '" . $GLOBALS['dbi']->escapeString($username) . "'" + . " AND `Host` = '" . $GLOBALS['dbi']->escapeString($hostname) . "'" + . " AND '" . $GLOBALS['dbi']->escapeString(Util::unescapeMysqlWildcards($db)) . "'" + . " LIKE `Db`;"; + } + return "SELECT `Table_priv`" + . " FROM `mysql`.`tables_priv`" + . " WHERE `User` = '" . $GLOBALS['dbi']->escapeString($username) . "'" + . " AND `Host` = '" . $GLOBALS['dbi']->escapeString($hostname) . "'" + . " AND `Db` = '" . $GLOBALS['dbi']->escapeString(Util::unescapeMysqlWildcards($db)) . "'" + . " AND `Table_name` = '" . $GLOBALS['dbi']->escapeString($table) . "';"; + } + + /** + * Displays a dropdown to select the user group + * with menu items configured to each of them. + * + * @param string $username username + * + * @return string html to select the user group + */ + public static function getHtmlToChooseUserGroup($username) + { + $relation = new Relation(); + $cfgRelation = $relation->getRelationsParam(); + $groupTable = Util::backquote($cfgRelation['db']) + . "." . Util::backquote($cfgRelation['usergroups']); + $userTable = Util::backquote($cfgRelation['db']) + . "." . Util::backquote($cfgRelation['users']); + + $userGroup = ''; + if (isset($GLOBALS['username'])) { + $sql_query = "SELECT `usergroup` FROM " . $userTable + . " WHERE `username` = '" . $GLOBALS['dbi']->escapeString($username) . "'"; + $userGroup = $GLOBALS['dbi']->fetchValue( + $sql_query, 0, 0, DatabaseInterface::CONNECT_CONTROL + ); + } + + $allUserGroups = array('' => ''); + $sql_query = "SELECT DISTINCT `usergroup` FROM " . $groupTable; + $result = $relation->queryAsControlUser($sql_query, false); + if ($result) { + while ($row = $GLOBALS['dbi']->fetchRow($result)) { + $allUserGroups[$row[0]] = $row[0]; + } + } + $GLOBALS['dbi']->freeResult($result); + + // render the template + $data = array( + 'all_user_groups' => $allUserGroups, + 'user_group' => $userGroup, + 'params' => array('username' => $username) + ); + $html_output = Template::get('privileges/choose_user_group') + ->render($data); + + return $html_output; + } + + /** + * Sets the user group from request values + * + * @param string $username username + * @param string $userGroup user group to set + * + * @return void + */ + public static function setUserGroup($username, $userGroup) + { + $relation = new Relation(); + $cfgRelation = $relation->getRelationsParam(); + if (empty($cfgRelation['db']) || empty($cfgRelation['users']) || empty($cfgRelation['usergroups'])) { + return; + } + + $userTable = Util::backquote($cfgRelation['db']) + . "." . Util::backquote($cfgRelation['users']); + + $sql_query = "SELECT `usergroup` FROM " . $userTable + . " WHERE `username` = '" . $GLOBALS['dbi']->escapeString($username) . "'"; + $oldUserGroup = $GLOBALS['dbi']->fetchValue( + $sql_query, 0, 0, DatabaseInterface::CONNECT_CONTROL + ); + + if ($oldUserGroup === false) { + $upd_query = "INSERT INTO " . $userTable . "(`username`, `usergroup`)" + . " VALUES ('" . $GLOBALS['dbi']->escapeString($username) . "', " + . "'" . $GLOBALS['dbi']->escapeString($userGroup) . "')"; + } else { + if (empty($userGroup)) { + $upd_query = "DELETE FROM " . $userTable + . " WHERE `username`='" . $GLOBALS['dbi']->escapeString($username) . "'"; + } elseif ($oldUserGroup != $userGroup) { + $upd_query = "UPDATE " . $userTable + . " SET `usergroup`='" . $GLOBALS['dbi']->escapeString($userGroup) . "'" + . " WHERE `username`='" . $GLOBALS['dbi']->escapeString($username) . "'"; + } + } + if (isset($upd_query)) { + $relation->queryAsControlUser($upd_query); + } + } + + /** + * Displays the privileges form table + * + * @param string $db the database + * @param string $table the table + * @param boolean $submit whether to display the submit button or not + * + * @global array $cfg the phpMyAdmin configuration + * @global resource $user_link the database connection + * + * @return string html snippet + */ + public static function getHtmlToDisplayPrivilegesTable($db = '*', + $table = '*', $submit = true + ) { + $html_output = ''; + $sql_query = ''; + + if ($db == '*') { + $table = '*'; + } + + if (isset($GLOBALS['username'])) { + $username = $GLOBALS['username']; + $hostname = $GLOBALS['hostname']; + $sql_query = self::getSqlQueryForDisplayPrivTable( + $db, $table, $username, $hostname + ); + $row = $GLOBALS['dbi']->fetchSingleRow($sql_query); + } + if (empty($row)) { + if ($table == '*' && $GLOBALS['dbi']->isSuperuser()) { + $row = array(); + if ($db == '*') { + $sql_query = 'SHOW COLUMNS FROM `mysql`.`user`;'; + } elseif ($table == '*') { + $sql_query = 'SHOW COLUMNS FROM `mysql`.`db`;'; + } + $res = $GLOBALS['dbi']->query($sql_query); + while ($row1 = $GLOBALS['dbi']->fetchRow($res)) { + if (mb_substr($row1[0], 0, 4) == 'max_') { + $row[$row1[0]] = 0; + } elseif (mb_substr($row1[0], 0, 5) == 'x509_' + || mb_substr($row1[0], 0, 4) == 'ssl_' + ) { + $row[$row1[0]] = ''; + } else { + $row[$row1[0]] = 'N'; + } + } + $GLOBALS['dbi']->freeResult($res); + } elseif ($table == '*') { + $row = array(); + } else { + $row = array('Table_priv' => ''); + } + } + if (isset($row['Table_priv'])) { + self::fillInTablePrivileges($row); + + // get columns + $res = $GLOBALS['dbi']->tryQuery( + 'SHOW COLUMNS FROM ' + . Util::backquote( + Util::unescapeMysqlWildcards($db) + ) + . '.' . Util::backquote($table) . ';' + ); + $columns = array(); + if ($res) { + while ($row1 = $GLOBALS['dbi']->fetchRow($res)) { + $columns[$row1[0]] = array( + 'Select' => false, + 'Insert' => false, + 'Update' => false, + 'References' => false + ); + } + $GLOBALS['dbi']->freeResult($res); + } + unset($res, $row1); + } + // table-specific privileges + if (! empty($columns)) { + $html_output .= self::getHtmlForTableSpecificPrivileges( + $username, $hostname, $db, $table, $columns, $row + ); + } else { + // global or db-specific + $html_output .= self::getHtmlForGlobalOrDbSpecificPrivs($db, $table, $row); + } + $html_output .= '' . "\n"; + if ($submit) { + $html_output .= '' . "\n"; + } + return $html_output; + } // end of the 'PMA_displayPrivTable()' function + + /** + * Get HTML for "Require" + * + * @param array $row privilege array + * + * @return string html snippet + */ + public static function getHtmlForRequires(array $row) + { + $specified = (isset($row['ssl_type']) && $row['ssl_type'] == 'SPECIFIED'); + $require_options = array( + array( + 'name' => 'ssl_type', + 'value' => 'NONE', + 'description' => __( + 'Does not require SSL-encrypted connections.' + ), + 'label' => 'REQUIRE NONE', + 'checked' => ((isset($row['ssl_type']) + && ($row['ssl_type'] == 'NONE' + || $row['ssl_type'] == '')) + ? 'checked="checked"' + : '' + ), + 'disabled' => false, + 'radio' => true + ), + array( + 'name' => 'ssl_type', + 'value' => 'ANY', + 'description' => __( + 'Requires SSL-encrypted connections.' + ), + 'label' => 'REQUIRE SSL', + 'checked' => (isset($row['ssl_type']) && ($row['ssl_type'] == 'ANY') + ? 'checked="checked"' + : '' + ), + 'disabled' => false, + 'radio' => true + ), + array( + 'name' => 'ssl_type', + 'value' => 'X509', + 'description' => __( + 'Requires a valid X509 certificate.' + ), + 'label' => 'REQUIRE X509', + 'checked' => (isset($row['ssl_type']) && ($row['ssl_type'] == 'X509') + ? 'checked="checked"' + : '' + ), + 'disabled' => false, + 'radio' => true + ), + array( + 'name' => 'ssl_type', + 'value' => 'SPECIFIED', + 'description' => '', + 'label' => 'SPECIFIED', + 'checked' => ($specified ? 'checked="checked"' : ''), + 'disabled' => false, + 'radio' => true + ), + array( + 'name' => 'ssl_cipher', + 'value' => (isset($row['ssl_cipher']) + ? htmlspecialchars($row['ssl_cipher']) : '' + ), + 'description' => __( + 'Requires that a specific cipher method be used for a connection.' + ), + 'label' => 'REQUIRE CIPHER', + 'checked' => '', + 'disabled' => ! $specified, + 'radio' => false + ), + array( + 'name' => 'x509_issuer', + 'value' => (isset($row['x509_issuer']) + ? htmlspecialchars($row['x509_issuer']) : '' + ), + 'description' => __( + 'Requires that a valid X509 certificate issued by this CA be presented.' + ), + 'label' => 'REQUIRE ISSUER', + 'checked' => '', + 'disabled' => ! $specified, + 'radio' => false + ), + array( + 'name' => 'x509_subject', + 'value' => (isset($row['x509_subject']) + ? htmlspecialchars($row['x509_subject']) : '' + ), + 'description' => __( + 'Requires that a valid X509 certificate with this subject be presented.' + ), + 'label' => 'REQUIRE SUBJECT', + 'checked' => '', + 'disabled' => ! $specified, + 'radio' => false + ), + ); + + $html_output = Template::get('privileges/require_options') + ->render(array('require_options' => $require_options)); + + return $html_output; + } + + /** + * Get HTML for "Resource limits" + * + * @param array $row first row from result or boolean false + * + * @return string html snippet + */ + public static function getHtmlForResourceLimits(array $row) + { + $limits = array( + array( + 'input_name' => 'max_questions', + 'name_main' => 'MAX QUERIES PER HOUR', + 'value' => (isset($row['max_questions']) ? $row['max_questions'] : '0'), + 'description' => __( + 'Limits the number of queries the user may send to the server per hour.' + ) + ), + array( + 'input_name' => 'max_updates', + 'name_main' => 'MAX UPDATES PER HOUR', + 'value' => (isset($row['max_updates']) ? $row['max_updates'] : '0'), + 'description' => __( + 'Limits the number of commands that change any table ' + . 'or database the user may execute per hour.' + ) + ), + array( + 'input_name' => 'max_connections', + 'name_main' => 'MAX CONNECTIONS PER HOUR', + 'value' => (isset($row['max_connections']) ? $row['max_connections'] : '0'), + 'description' => __( + 'Limits the number of new connections the user may open per hour.' + ) + ), + array( + 'input_name' => 'max_user_connections', + 'name_main' => 'MAX USER_CONNECTIONS', + 'value' => (isset($row['max_user_connections']) ? + $row['max_user_connections'] : '0'), + 'description' => __( + 'Limits the number of simultaneous connections ' + . 'the user may have.' + ) + ) + ); + + return Template::get('privileges/resource_limits') + ->render(array('limits' => $limits)); + } + + /** + * Get the HTML snippet for routine specific privileges + * + * @param string $username username for database connection + * @param string $hostname hostname for database connection + * @param string $db the database + * @param string $routine the routine + * @param string $url_dbname url encoded db name + * + * @return string $html_output + */ + public static function getHtmlForRoutineSpecificPrivileges( + $username, $hostname, $db, $routine, $url_dbname + ) { + $header = self::getHtmlHeaderForUserProperties( + false, $url_dbname, $db, $username, $hostname, + $routine, 'routine' + ); + + $sql = "SELECT `Proc_priv`" + . " FROM `mysql`.`procs_priv`" + . " WHERE `User` = '" . $GLOBALS['dbi']->escapeString($username) . "'" + . " AND `Host` = '" . $GLOBALS['dbi']->escapeString($hostname) . "'" + . " AND `Db` = '" + . $GLOBALS['dbi']->escapeString(Util::unescapeMysqlWildcards($db)) . "'" + . " AND `Routine_name` LIKE '" . $GLOBALS['dbi']->escapeString($routine) . "';"; + $res = $GLOBALS['dbi']->fetchValue($sql); + + $privs = self::parseProcPriv($res); + + $routineArray = array(self::getTriggerPrivilegeTable()); + $privTableNames = array(__('Routine')); + $privCheckboxes = self::getHtmlForGlobalPrivTableWithCheckboxes( + $routineArray, $privTableNames, $privs + ); + + $data = array( + 'username' => $username, + 'hostname' => $hostname, + 'database' => $db, + 'routine' => $routine, + 'grant_count' => count($privs), + 'priv_checkboxes' => $privCheckboxes, + 'header' => $header, + ); + $html_output = Template::get('privileges/edit_routine_privileges') + ->render($data); + + return $html_output; + } + + /** + * Get routine privilege table as an array + * + * @return privilege type array + */ + public static function getTriggerPrivilegeTable() + { + $routinePrivTable = array( + array( + 'Grant', + 'GRANT', + __( + 'Allows user to give to other users or remove from other users ' + . 'privileges that user possess on this routine.' + ) + ), + array( + 'Alter_routine', + 'ALTER ROUTINE', + __('Allows altering and dropping this routine.') + ), + array( + 'Execute', + 'EXECUTE', + __('Allows executing this routine.') + ) + ); + return $routinePrivTable; + } + + /** + * Get the HTML snippet for table specific privileges + * + * @param string $username username for database connection + * @param string $hostname hostname for database connection + * @param string $db the database + * @param string $table the table + * @param array $columns columns array + * @param array $row current privileges row + * + * @return string $html_output + */ + public static function getHtmlForTableSpecificPrivileges( + $username, $hostname, $db, $table, array $columns, array $row + ) { + $res = $GLOBALS['dbi']->query( + 'SELECT `Column_name`, `Column_priv`' + . ' FROM `mysql`.`columns_priv`' + . ' WHERE `User`' + . ' = \'' . $GLOBALS['dbi']->escapeString($username) . "'" + . ' AND `Host`' + . ' = \'' . $GLOBALS['dbi']->escapeString($hostname) . "'" + . ' AND `Db`' + . ' = \'' . $GLOBALS['dbi']->escapeString( + Util::unescapeMysqlWildcards($db) + ) . "'" + . ' AND `Table_name`' + . ' = \'' . $GLOBALS['dbi']->escapeString($table) . '\';' + ); + + while ($row1 = $GLOBALS['dbi']->fetchRow($res)) { + $row1[1] = explode(',', $row1[1]); + foreach ($row1[1] as $current) { + $columns[$row1[0]][$current] = true; + } + } + $GLOBALS['dbi']->freeResult($res); + unset($res, $row1, $current); + + $html_output = '' . "\n" + . '' . "\n" + . '
      ' . "\n" + . '' . __('Table-specific privileges') + . '' + . '

      ' + . __('Note: MySQL privilege names are expressed in English.') + . '

      '; + + // privs that are attached to a specific column + $html_output .= self::getHtmlForAttachedPrivilegesToTableSpecificColumn( + $columns, $row + ); + + // privs that are not attached to a specific column + $html_output .= '
      ' . "\n" + . self::getHtmlForNotAttachedPrivilegesToTableSpecificColumn($row) + . '
      ' . "\n"; + + // for Safari 2.0.2 + $html_output .= '
      ' . "\n"; + + return $html_output; + } + + /** + * Get HTML snippet for privileges that are attached to a specific column + * + * @param array $columns columns array + * @param array $row first row from result or boolean false + * + * @return string $html_output + */ + public static function getHtmlForAttachedPrivilegesToTableSpecificColumn(array $columns, array $row) + { + $html_output = self::getHtmlForColumnPrivileges( + $columns, $row, 'Select_priv', 'SELECT', + 'select', __('Allows reading data.'), 'Select' + ); + + $html_output .= self::getHtmlForColumnPrivileges( + $columns, $row, 'Insert_priv', 'INSERT', + 'insert', __('Allows inserting and replacing data.'), 'Insert' + ); + + $html_output .= self::getHtmlForColumnPrivileges( + $columns, $row, 'Update_priv', 'UPDATE', + 'update', __('Allows changing data.'), 'Update' + ); + + $html_output .= self::getHtmlForColumnPrivileges( + $columns, $row, 'References_priv', 'REFERENCES', 'references', + __('Has no effect in this MySQL version.'), 'References' + ); + return $html_output; + } + + /** + * Get HTML for privileges that are not attached to a specific column + * + * @param array $row first row from result or boolean false + * + * @return string $html_output + */ + public static function getHtmlForNotAttachedPrivilegesToTableSpecificColumn(array $row) + { + $html_output = ''; + + foreach ($row as $current_grant => $current_grant_value) { + $grant_type = substr($current_grant, 0, -5); + if (in_array($grant_type, array('Select', 'Insert', 'Update', 'References')) + ) { + continue; + } + // make a substitution to match the messages variables; + // also we must substitute the grant we get, because we can't generate + // a form variable containing blanks (those would get changed to + // an underscore when receiving the POST) + if ($current_grant == 'Create View_priv') { + $tmp_current_grant = 'CreateView_priv'; + $current_grant = 'Create_view_priv'; + } elseif ($current_grant == 'Show view_priv') { + $tmp_current_grant = 'ShowView_priv'; + $current_grant = 'Show_view_priv'; + } elseif ($current_grant == 'Delete versioning rows_priv') { + $tmp_current_grant = 'DeleteHistoricalRows_priv'; + $current_grant = 'Delete_history_priv'; + } else { + $tmp_current_grant = $current_grant; + } + + $html_output .= '
      ' . "\n" + . '' . "\n"; + + $privGlobalName1 = 'strPrivDesc' + . mb_substr( + $tmp_current_grant, + 0, + - 5 + ); + $html_output .= '' . "\n" + . '
      ' . "\n"; + } // end foreach () + return $html_output; + } + + /** + * Get HTML for global or database specific privileges + * + * @param string $db the database + * @param string $table the table + * @param array $row first row from result or boolean false + * + * @return string $html_output + */ + public static function getHtmlForGlobalOrDbSpecificPrivs($db, $table, array $row) + { + $privTable_names = array(0 => __('Data'), + 1 => __('Structure'), + 2 => __('Administration') + ); + $privTable = array(); + // d a t a + $privTable[0] = self::getDataPrivilegeTable($db); + + // s t r u c t u r e + $privTable[1] = self::getStructurePrivilegeTable($table, $row); + + // a d m i n i s t r a t i o n + $privTable[2] = self::getAdministrationPrivilegeTable($db); + + $html_output = ''; + if ($db == '*') { + $legend = __('Global privileges'); + $menu_label = __('Global'); + } elseif ($table == '*') { + $legend = __('Database-specific privileges'); + $menu_label = __('Database'); + } else { + $legend = __('Table-specific privileges'); + $menu_label = __('Table'); + } + $html_output .= '
      ' + . '' . $legend + . ' ' + . ' ' + . '' + . '

      ' + . __('Note: MySQL privilege names are expressed in English.') + . '

      '; + + // Output the Global privilege tables with checkboxes + $html_output .= self::getHtmlForGlobalPrivTableWithCheckboxes( + $privTable, $privTable_names, $row + ); + + // The "Resource limits" box is not displayed for db-specific privs + if ($db == '*') { + $html_output .= self::getHtmlForResourceLimits($row); + $html_output .= self::getHtmlForRequires($row); + } + // for Safari 2.0.2 + $html_output .= '
      '; + + return $html_output; + } + + /** + * Get data privilege table as an array + * + * @param string $db the database + * + * @return string data privilege table + */ + public static function getDataPrivilegeTable($db) + { + $data_privTable = array( + array('Select', 'SELECT', __('Allows reading data.')), + array('Insert', 'INSERT', __('Allows inserting and replacing data.')), + array('Update', 'UPDATE', __('Allows changing data.')), + array('Delete', 'DELETE', __('Allows deleting data.')) + ); + if ($db == '*') { + $data_privTable[] + = array('File', + 'FILE', + __('Allows importing data from and exporting data into files.') + ); + } + return $data_privTable; + } + + /** + * Get structure privilege table as an array + * + * @param string $table the table + * @param array $row first row from result or boolean false + * + * @return string structure privilege table + */ + public static function getStructurePrivilegeTable($table, array $row) + { + $structure_privTable = array( + array('Create', + 'CREATE', + ($table == '*' + ? __('Allows creating new databases and tables.') + : __('Allows creating new tables.') + ) + ), + array('Alter', + 'ALTER', + __('Allows altering the structure of existing tables.') + ), + array('Index', 'INDEX', __('Allows creating and dropping indexes.')), + array('Drop', + 'DROP', + ($table == '*' + ? __('Allows dropping databases and tables.') + : __('Allows dropping tables.') + ) + ), + array('Create_tmp_table', + 'CREATE TEMPORARY TABLES', + __('Allows creating temporary tables.') + ), + array('Show_view', + 'SHOW VIEW', + __('Allows performing SHOW CREATE VIEW queries.') + ), + array('Create_routine', + 'CREATE ROUTINE', + __('Allows creating stored routines.') + ), + array('Alter_routine', + 'ALTER ROUTINE', + __('Allows altering and dropping stored routines.') + ), + array('Execute', 'EXECUTE', __('Allows executing stored routines.')), + ); + // this one is for a db-specific priv: Create_view_priv + if (isset($row['Create_view_priv'])) { + $structure_privTable[] = array('Create_view', + 'CREATE VIEW', + __('Allows creating new views.') + ); + } + // this one is for a table-specific priv: Create View_priv + if (isset($row['Create View_priv'])) { + $structure_privTable[] = array('Create View', + 'CREATE VIEW', + __('Allows creating new views.') + ); + } + if (isset($row['Event_priv'])) { + // MySQL 5.1.6 + $structure_privTable[] = array('Event', + 'EVENT', + __('Allows to set up events for the event scheduler.') + ); + $structure_privTable[] = array('Trigger', + 'TRIGGER', + __('Allows creating and dropping triggers.') + ); + } + return $structure_privTable; + } + + /** + * Get administration privilege table as an array + * + * @param string $db the table + * + * @return string administration privilege table + */ + public static function getAdministrationPrivilegeTable($db) + { + if ($db == '*') { + $adminPrivTable = array( + array('Grant', + 'GRANT', + __( + 'Allows adding users and privileges ' + . 'without reloading the privilege tables.' + ) + ), + ); + $adminPrivTable[] = array('Super', + 'SUPER', + __( + 'Allows connecting, even if maximum number ' + . 'of connections is reached; required for ' + . 'most administrative operations like ' + . 'setting global variables or killing threads of other users.' + ) + ); + $adminPrivTable[] = array('Process', + 'PROCESS', + __('Allows viewing processes of all users.') + ); + $adminPrivTable[] = array('Reload', + 'RELOAD', + __('Allows reloading server settings and flushing the server\'s caches.') + ); + $adminPrivTable[] = array('Shutdown', + 'SHUTDOWN', + __('Allows shutting down the server.') + ); + $adminPrivTable[] = array('Show_db', + 'SHOW DATABASES', + __('Gives access to the complete list of databases.') + ); + } + else { + $adminPrivTable = array( + array('Grant', + 'GRANT', + __( + 'Allows user to give to other users or remove from other' + . ' users the privileges that user possess yourself.' + ) + ), + ); + } + $adminPrivTable[] = array('Lock_tables', + 'LOCK TABLES', + __('Allows locking tables for the current thread.') + ); + $adminPrivTable[] = array('References', + 'REFERENCES', + __('Has no effect in this MySQL version.') + ); + if ($db == '*') { + $adminPrivTable[] = array('Repl_client', + 'REPLICATION CLIENT', + __('Allows the user to ask where the slaves / masters are.') + ); + $adminPrivTable[] = array('Repl_slave', + 'REPLICATION SLAVE', + __('Needed for the replication slaves.') + ); + $adminPrivTable[] = array('Create_user', + 'CREATE USER', + __('Allows creating, dropping and renaming user accounts.') + ); + } + return $adminPrivTable; + } + + /** + * Get HTML snippet for global privileges table with check boxes + * + * @param array $privTable privileges table array + * @param array $privTableNames names of the privilege tables + * (Data, Structure, Administration) + * @param array $row first row from result or boolean false + * + * @return string $html_output + */ + public static function getHtmlForGlobalPrivTableWithCheckboxes( + array $privTable, array $privTableNames, array $row + ) { + return Template::get('privileges/global_priv_table')->render(array( + 'priv_table' => $privTable, + 'priv_table_names' => $privTableNames, + 'row' => $row, + )); + } + + /** + * Gets the currently active authentication plugins + * + * @param string $orig_auth_plugin Default Authentication plugin + * @param string $mode are we creating a new user or are we just + * changing one? + * (allowed values: 'new', 'edit', 'change_pw') + * @param string $versions Is MySQL version newer or older than 5.5.7 + * + * @return string $html_output + */ + public static function getHtmlForAuthPluginsDropdown( + $orig_auth_plugin, + $mode = 'new', + $versions = 'new' + ) { + $select_id = 'select_authentication_plugin' + . ($mode =='change_pw' ? '_cp' : ''); + + if ($versions == 'new') { + $active_auth_plugins = self::getActiveAuthPlugins(); + + if (isset($active_auth_plugins['mysql_old_password'])) { + unset($active_auth_plugins['mysql_old_password']); + } + } else { + $active_auth_plugins = array( + 'mysql_native_password' => __('Native MySQL authentication') + ); + } + + $html_output = Util::getDropdown( + 'authentication_plugin', + $active_auth_plugins, + $orig_auth_plugin, + $select_id + ); + + return $html_output; + } + + /** + * Gets the currently active authentication plugins + * + * @return array $result array of plugin names and descriptions + */ + public static function getActiveAuthPlugins() + { + $get_plugins_query = "SELECT `PLUGIN_NAME`, `PLUGIN_DESCRIPTION`" + . " FROM `information_schema`.`PLUGINS` " + . "WHERE `PLUGIN_TYPE` = 'AUTHENTICATION';"; + $resultset = $GLOBALS['dbi']->query($get_plugins_query); + + $result = array(); + + while ($row = $GLOBALS['dbi']->fetchAssoc($resultset)) { + // if description is known, enable its translation + if ('mysql_native_password' == $row['PLUGIN_NAME']) { + $row['PLUGIN_DESCRIPTION'] = __('Native MySQL authentication'); + } elseif ('sha256_password' == $row['PLUGIN_NAME']) { + $row['PLUGIN_DESCRIPTION'] = __('SHA256 password authentication'); + } + + $result[$row['PLUGIN_NAME']] = $row['PLUGIN_DESCRIPTION']; + } + + return $result; + } + + /** + * Displays the fields used by the "new user" form as well as the + * "change login information / copy user" form. + * + * @param string $mode are we creating a new user or are we just + * changing one? (allowed values: 'new', 'change') + * @param string $username User name + * @param string $hostname Host name + * + * @global array $cfg the phpMyAdmin configuration + * @global resource $user_link the database connection + * + * @return string $html_output a HTML snippet + */ + public static function getHtmlForLoginInformationFields( + $mode = 'new', + $username = null, + $hostname = null + ) { + list($username_length, $hostname_length) = self::getUsernameAndHostnameLength(); + + if (isset($GLOBALS['username']) && strlen($GLOBALS['username']) === 0) { + $GLOBALS['pred_username'] = 'any'; + } + $html_output = '
      ' . "\n" + . '' . __('Login Information') . '' . "\n" + . '
      ' . "\n" + . '' . "\n" + . '' . "\n"; + + $html_output .= '' . "\n" + . '' . "\n"; + + $html_output .= '' . "\n"; + + $html_output .= '
      ' + . Message::notice( + __( + 'An account already exists with the same username ' + . 'but possibly a different hostname.' + ) + )->getDisplay() + . '
      '; + $html_output .= '
      '; + + $html_output .= '
      ' . "\n" + . '' . "\n"; + + $html_output .= '' . "\n" + . ' ' . "\n" + . '' . "\n"; + + $html_output .= '' . "\n" + . Util::showHint( + __( + 'When Host table is used, this field is ignored ' + . 'and values stored in Host table are used instead.' + ) + ) + . '
      ' . "\n"; + + $html_output .= '
      ' . "\n" + . '' . "\n" + . '' . "\n" + . '' . "\n" + . '' . "\n" + . '' . "\n" + . 'Strength: ' + . ' ' + . '' . "\n" + . '
      ' . "\n"; + + $html_output .= '
      ' . "\n" + . '' . "\n" + . ' ' . "\n" + . '' . "\n" + . '
      ' . "\n" + . '
      ' + . ' ' . "\n"; + + $auth_plugin_dropdown = self::getHtmlForAuthPluginsDropdown( + $orig_auth_plugin, $mode, 'new' + ); + } else { + $html_output .= __('Password Hashing Method') + . ' ' . "\n"; + $auth_plugin_dropdown = self::getHtmlForAuthPluginsDropdown( + $orig_auth_plugin, $mode, 'old' + ); + } + $html_output .= $auth_plugin_dropdown; + + $html_output .= '' + . Message::notice( + __( + 'This method requires using an \'SSL connection\' ' + . 'or an \'unencrypted connection that encrypts the password ' + . 'using RSA\'; while connecting to the server.' + ) + . Util::showMySQLDocu('sha256-authentication-plugin') + ) + ->getDisplay() + . '
      '; + + $html_output .= '' . "\n" + // Generate password added here via jQuery + . '
      ' . "\n"; + + return $html_output; + } // end of the 'self::getHtmlForLoginInformationFields()' function + + /** + * Get username and hostname length + * + * @return array username length and hostname length + */ + public static function getUsernameAndHostnameLength() + { + /* Fallback values */ + $username_length = 16; + $hostname_length = 41; + + /* Try to get real lengths from the database */ + $fields_info = $GLOBALS['dbi']->fetchResult( + 'SELECT COLUMN_NAME, CHARACTER_MAXIMUM_LENGTH ' + . 'FROM information_schema.columns ' + . "WHERE table_schema = 'mysql' AND table_name = 'user' " + . "AND COLUMN_NAME IN ('User', 'Host')" + ); + foreach ($fields_info as $val) { + if ($val['COLUMN_NAME'] == 'User') { + $username_length = $val['CHARACTER_MAXIMUM_LENGTH']; + } elseif ($val['COLUMN_NAME'] == 'Host') { + $hostname_length = $val['CHARACTER_MAXIMUM_LENGTH']; + } + } + return array($username_length, $hostname_length); + } + + /** + * Get current authentication plugin in use - for a user or globally + * + * @param string $mode are we creating a new user or are we just + * changing one? (allowed values: 'new', 'change') + * @param string $username User name + * @param string $hostname Host name + * + * @return string authentication plugin in use + */ + public static function getCurrentAuthenticationPlugin( + $mode = 'new', + $username = null, + $hostname = null + ) { + /* Fallback (standard) value */ + $authentication_plugin = 'mysql_native_password'; + $serverVersion = $GLOBALS['dbi']->getVersion(); + + if (isset($username) && isset($hostname) + && $mode == 'change' + ) { + $row = $GLOBALS['dbi']->fetchSingleRow( + 'SELECT `plugin` FROM `mysql`.`user` WHERE ' + . '`User` = "' . $username . '" AND `Host` = "' . $hostname . '" LIMIT 1' + ); + // Table 'mysql'.'user' may not exist for some previous + // versions of MySQL - in that case consider fallback value + if (isset($row) && $row) { + $authentication_plugin = $row['plugin']; + } + } elseif ($mode == 'change') { + list($username, $hostname) = $GLOBALS['dbi']->getCurrentUserAndHost(); + + $row = $GLOBALS['dbi']->fetchSingleRow( + 'SELECT `plugin` FROM `mysql`.`user` WHERE ' + . '`User` = "' . $username . '" AND `Host` = "' . $hostname . '"' + ); + if (isset($row) && $row && ! empty($row['plugin'])) { + $authentication_plugin = $row['plugin']; + } + } elseif ($serverVersion >= 50702) { + $row = $GLOBALS['dbi']->fetchSingleRow( + 'SELECT @@default_authentication_plugin' + ); + $authentication_plugin = is_array($row) ? $row['@@default_authentication_plugin'] : null; + } + + return $authentication_plugin; + } + + /** + * Returns all the grants for a certain user on a certain host + * Used in the export privileges for all users section + * + * @param string $user User name + * @param string $host Host name + * + * @return string containing all the grants text + */ + public static function getGrants($user, $host) + { + $grants = $GLOBALS['dbi']->fetchResult( + "SHOW GRANTS FOR '" + . $GLOBALS['dbi']->escapeString($user) . "'@'" + . $GLOBALS['dbi']->escapeString($host) . "'" + ); + $response = ''; + foreach ($grants as $one_grant) { + $response .= $one_grant . ";\n\n"; + } + return $response; + } // end of the 'self::getGrants()' function + + /** + * Update password and get message for password updating + * + * @param string $err_url error url + * @param string $username username + * @param string $hostname hostname + * + * @return string $message success or error message after updating password + */ + public static function updatePassword($err_url, $username, $hostname) + { + // similar logic in user_password.php + $message = ''; + + if (empty($_POST['nopass']) + && isset($_POST['pma_pw']) + && isset($_POST['pma_pw2']) + ) { + if ($_POST['pma_pw'] != $_POST['pma_pw2']) { + $message = Message::error(__('The passwords aren\'t the same!')); + } elseif (empty($_POST['pma_pw']) || empty($_POST['pma_pw2'])) { + $message = Message::error(__('The password is empty!')); + } + } + + // here $nopass could be == 1 + if (empty($message)) { + $hashing_function = 'PASSWORD'; + $serverType = Util::getServerType(); + $serverVersion = $GLOBALS['dbi']->getVersion(); + $authentication_plugin + = (isset($_POST['authentication_plugin']) + ? $_POST['authentication_plugin'] + : self::getCurrentAuthenticationPlugin( + 'change', + $username, + $hostname + )); + + // Use 'ALTER USER ...' syntax for MySQL 5.7.6+ + if ($serverType == 'MySQL' + && $serverVersion >= 50706 + ) { + if ($authentication_plugin != 'mysql_old_password') { + $query_prefix = "ALTER USER '" + . $GLOBALS['dbi']->escapeString($username) + . "'@'" . $GLOBALS['dbi']->escapeString($hostname) . "'" + . " IDENTIFIED WITH " + . $authentication_plugin + . " BY '"; + } else { + $query_prefix = "ALTER USER '" + . $GLOBALS['dbi']->escapeString($username) + . "'@'" . $GLOBALS['dbi']->escapeString($hostname) . "'" + . " IDENTIFIED BY '"; + } + + // in $sql_query which will be displayed, hide the password + $sql_query = $query_prefix . "*'"; + + $local_query = $query_prefix + . $GLOBALS['dbi']->escapeString($_POST['pma_pw']) . "'"; + } elseif ($serverType == 'MariaDB' && $serverVersion >= 10000) { + // MariaDB uses "SET PASSWORD" syntax to change user password. + // On Galera cluster only DDL queries are replicated, since + // users are stored in MyISAM storage engine. + $query_prefix = "SET PASSWORD FOR '" + . $GLOBALS['dbi']->escapeString($username) + . "'@'" . $GLOBALS['dbi']->escapeString($hostname) . "'" + . " = PASSWORD ('"; + $sql_query = $local_query = $query_prefix + . $GLOBALS['dbi']->escapeString($_POST['pma_pw']) . "')"; + } elseif ($serverType == 'MariaDB' + && $serverVersion >= 50200 + && $GLOBALS['dbi']->isSuperuser() + ) { + // Use 'UPDATE `mysql`.`user` ...' Syntax for MariaDB 5.2+ + if ($authentication_plugin == 'mysql_native_password') { + // Set the hashing method used by PASSWORD() + // to be 'mysql_native_password' type + $GLOBALS['dbi']->tryQuery('SET old_passwords = 0;'); + + } elseif ($authentication_plugin == 'sha256_password') { + // Set the hashing method used by PASSWORD() + // to be 'sha256_password' type + $GLOBALS['dbi']->tryQuery('SET `old_passwords` = 2;'); + } + + $hashedPassword = self::getHashedPassword($_POST['pma_pw']); + + $sql_query = 'SET PASSWORD FOR \'' + . $GLOBALS['dbi']->escapeString($username) + . '\'@\'' . $GLOBALS['dbi']->escapeString($hostname) . '\' = ' + . (($_POST['pma_pw'] == '') + ? '\'\'' + : $hashing_function . '(\'' + . preg_replace('@.@s', '*', $_POST['pma_pw']) . '\')'); + + $local_query = "UPDATE `mysql`.`user` SET " + . " `authentication_string` = '" . $hashedPassword + . "', `Password` = '', " + . " `plugin` = '" . $authentication_plugin . "'" + . " WHERE `User` = '" . $username . "' AND Host = '" + . $hostname . "';"; + } else { + // USE 'SET PASSWORD ...' syntax for rest of the versions + // Backup the old value, to be reset later + $row = $GLOBALS['dbi']->fetchSingleRow( + 'SELECT @@old_passwords;' + ); + $orig_value = $row['@@old_passwords']; + $update_plugin_query = "UPDATE `mysql`.`user` SET" + . " `plugin` = '" . $authentication_plugin . "'" + . " WHERE `User` = '" . $username . "' AND Host = '" + . $hostname . "';"; + + // Update the plugin for the user + if (!($GLOBALS['dbi']->tryQuery($update_plugin_query))) { + Util::mysqlDie( + $GLOBALS['dbi']->getError(), + $update_plugin_query, + false, $err_url + ); + } + $GLOBALS['dbi']->tryQuery("FLUSH PRIVILEGES;"); + + if ($authentication_plugin == 'mysql_native_password') { + // Set the hashing method used by PASSWORD() + // to be 'mysql_native_password' type + $GLOBALS['dbi']->tryQuery('SET old_passwords = 0;'); + } elseif ($authentication_plugin == 'sha256_password') { + // Set the hashing method used by PASSWORD() + // to be 'sha256_password' type + $GLOBALS['dbi']->tryQuery('SET `old_passwords` = 2;'); + } + $sql_query = 'SET PASSWORD FOR \'' + . $GLOBALS['dbi']->escapeString($username) + . '\'@\'' . $GLOBALS['dbi']->escapeString($hostname) . '\' = ' + . (($_POST['pma_pw'] == '') + ? '\'\'' + : $hashing_function . '(\'' + . preg_replace('@.@s', '*', $_POST['pma_pw']) . '\')'); + + $local_query = 'SET PASSWORD FOR \'' + . $GLOBALS['dbi']->escapeString($username) + . '\'@\'' . $GLOBALS['dbi']->escapeString($hostname) . '\' = ' + . (($_POST['pma_pw'] == '') ? '\'\'' : $hashing_function + . '(\'' . $GLOBALS['dbi']->escapeString($_POST['pma_pw']) . '\')'); + } + + if (!($GLOBALS['dbi']->tryQuery($local_query))) { + Util::mysqlDie( + $GLOBALS['dbi']->getError(), $sql_query, false, $err_url + ); + } + // Flush privileges after successful password change + $GLOBALS['dbi']->tryQuery("FLUSH PRIVILEGES;"); + + $message = Message::success( + __('The password for %s was changed successfully.') + ); + $message->addParam('\'' . $username . '\'@\'' . $hostname . '\''); + if (isset($orig_value)) { + $GLOBALS['dbi']->tryQuery( + 'SET `old_passwords` = ' . $orig_value . ';' + ); + } + } + return $message; + } + + /** + * Revokes privileges and get message and SQL query for privileges revokes + * + * @param string $dbname database name + * @param string $tablename table name + * @param string $username username + * @param string $hostname host name + * @param string $itemType item type + * + * @return array ($message, $sql_query) + */ + public static function getMessageAndSqlQueryForPrivilegesRevoke($dbname, + $tablename, $username, $hostname, $itemType + ) { + $db_and_table = self::wildcardEscapeForGrant($dbname, $tablename); + + $sql_query0 = 'REVOKE ALL PRIVILEGES ON ' . $itemType . ' ' . $db_and_table + . ' FROM \'' + . $GLOBALS['dbi']->escapeString($username) . '\'@\'' + . $GLOBALS['dbi']->escapeString($hostname) . '\';'; + + $sql_query1 = 'REVOKE GRANT OPTION ON ' . $itemType . ' ' . $db_and_table + . ' FROM \'' . $GLOBALS['dbi']->escapeString($username) . '\'@\'' + . $GLOBALS['dbi']->escapeString($hostname) . '\';'; + + $GLOBALS['dbi']->query($sql_query0); + if (! $GLOBALS['dbi']->tryQuery($sql_query1)) { + // this one may fail, too... + $sql_query1 = ''; + } + $sql_query = $sql_query0 . ' ' . $sql_query1; + $message = Message::success( + __('You have revoked the privileges for %s.') + ); + $message->addParam('\'' . $username . '\'@\'' . $hostname . '\''); + + return array($message, $sql_query); + } + + /** + * Get REQUIRE cluase + * + * @return string REQUIRE clause + */ + public static function getRequireClause() + { + $arr = isset($_POST['ssl_type']) ? $_POST : $GLOBALS; + if (isset($arr['ssl_type']) && $arr['ssl_type'] == 'SPECIFIED') { + $require = array(); + if (! empty($arr['ssl_cipher'])) { + $require[] = "CIPHER '" + . $GLOBALS['dbi']->escapeString($arr['ssl_cipher']) . "'"; + } + if (! empty($arr['x509_issuer'])) { + $require[] = "ISSUER '" + . $GLOBALS['dbi']->escapeString($arr['x509_issuer']) . "'"; + } + if (! empty($arr['x509_subject'])) { + $require[] = "SUBJECT '" + . $GLOBALS['dbi']->escapeString($arr['x509_subject']) . "'"; + } + if (count($require)) { + $require_clause = " REQUIRE " . implode(" AND ", $require); + } else { + $require_clause = " REQUIRE NONE"; + } + } elseif (isset($arr['ssl_type']) && $arr['ssl_type'] == 'X509') { + $require_clause = " REQUIRE X509"; + } elseif (isset($arr['ssl_type']) && $arr['ssl_type'] == 'ANY') { + $require_clause = " REQUIRE SSL"; + } else { + $require_clause = " REQUIRE NONE"; + } + + return $require_clause; + } + + /** + * Get a WITH clause for 'update privileges' and 'add user' + * + * @return string $sql_query + */ + public static function getWithClauseForAddUserAndUpdatePrivs() + { + $sql_query = ''; + if (((isset($_POST['Grant_priv']) && $_POST['Grant_priv'] == 'Y') + || (isset($GLOBALS['Grant_priv']) && $GLOBALS['Grant_priv'] == 'Y')) + && ! ((Util::getServerType() == 'MySQL' || Util::getServerType() == 'Percona Server') + && $GLOBALS['dbi']->getVersion() >= 80011) + ) { + $sql_query .= ' GRANT OPTION'; + } + if (isset($_POST['max_questions']) || isset($GLOBALS['max_questions'])) { + $max_questions = isset($_POST['max_questions']) + ? (int)$_POST['max_questions'] : (int)$GLOBALS['max_questions']; + $max_questions = max(0, $max_questions); + $sql_query .= ' MAX_QUERIES_PER_HOUR ' . $max_questions; + } + if (isset($_POST['max_connections']) || isset($GLOBALS['max_connections'])) { + $max_connections = isset($_POST['max_connections']) + ? (int)$_POST['max_connections'] : (int)$GLOBALS['max_connections']; + $max_connections = max(0, $max_connections); + $sql_query .= ' MAX_CONNECTIONS_PER_HOUR ' . $max_connections; + } + if (isset($_POST['max_updates']) || isset($GLOBALS['max_updates'])) { + $max_updates = isset($_POST['max_updates']) + ? (int)$_POST['max_updates'] : (int)$GLOBALS['max_updates']; + $max_updates = max(0, $max_updates); + $sql_query .= ' MAX_UPDATES_PER_HOUR ' . $max_updates; + } + if (isset($_POST['max_user_connections']) + || isset($GLOBALS['max_user_connections']) + ) { + $max_user_connections = isset($_POST['max_user_connections']) + ? (int)$_POST['max_user_connections'] + : (int)$GLOBALS['max_user_connections']; + $max_user_connections = max(0, $max_user_connections); + $sql_query .= ' MAX_USER_CONNECTIONS ' . $max_user_connections; + } + return ((!empty($sql_query)) ? ' WITH' . $sql_query : ''); + } + + /** + * Get HTML for addUsersForm, This function call if isset($_GET['adduser']) + * + * @param string $dbname database name + * + * @return string HTML for addUserForm + */ + public static function getHtmlForAddUser($dbname) + { + $html_output = '

      ' . "\n" + . Util::getIcon('b_usradd') . __('Add user account') . "\n" + . '

      ' . "\n" + . '
      ' . "\n" + . Url::getHiddenInputs('', '') + . self::getHtmlForLoginInformationFields('new'); + + $html_output .= '
      ' . "\n" + . '' . __('Database for user account') . '' . "\n"; + + $html_output .= Template::get('checkbox') + ->render( + array( + 'html_field_name' => 'createdb-1', + 'label' => __('Create database with same name and grant all privileges.'), + 'checked' => false, + 'onclick' => false, + 'html_field_id' => 'createdb-1', + ) + ); + $html_output .= '
      ' . "\n"; + $html_output .= Template::get('checkbox') + ->render( + array( + 'html_field_name' => 'createdb-2', + 'label' => __('Grant all privileges on wildcard name (username\\_%).'), + 'checked' => false, + 'onclick' => false, + 'html_field_id' => 'createdb-2', + ) + ); + $html_output .= '
      ' . "\n"; + + if (! empty($dbname) ) { + $html_output .= Template::get('checkbox') + ->render( + array( + 'html_field_name' => 'createdb-3', + 'label' => sprintf(__('Grant all privileges on database %s.'), htmlspecialchars($dbname)), + 'checked' => true, + 'onclick' => false, + 'html_field_id' => 'createdb-3', + ) + ); + $html_output .= '' . "\n"; + $html_output .= '
      ' . "\n"; + } + + $html_output .= '
      ' . "\n"; + if ($GLOBALS['is_grantuser']) { + $html_output .= self::getHtmlToDisplayPrivilegesTable('*', '*', false); + } + $html_output .= '' . "\n" + . '
      ' . "\n"; + + return $html_output; + } + + /** + * Get the list of privileges and list of compared privileges as strings + * and return a array that contains both strings + * + * @return array $list_of_privileges, $list_of_compared_privileges + */ + public static function getListOfPrivilegesAndComparedPrivileges() + { + $list_of_privileges + = '`User`, ' + . '`Host`, ' + . '`Select_priv`, ' + . '`Insert_priv`, ' + . '`Update_priv`, ' + . '`Delete_priv`, ' + . '`Create_priv`, ' + . '`Drop_priv`, ' + . '`Grant_priv`, ' + . '`Index_priv`, ' + . '`Alter_priv`, ' + . '`References_priv`, ' + . '`Create_tmp_table_priv`, ' + . '`Lock_tables_priv`, ' + . '`Create_view_priv`, ' + . '`Show_view_priv`, ' + . '`Create_routine_priv`, ' + . '`Alter_routine_priv`, ' + . '`Execute_priv`'; + + $listOfComparedPrivs + = '`Select_priv` = \'N\'' + . ' AND `Insert_priv` = \'N\'' + . ' AND `Update_priv` = \'N\'' + . ' AND `Delete_priv` = \'N\'' + . ' AND `Create_priv` = \'N\'' + . ' AND `Drop_priv` = \'N\'' + . ' AND `Grant_priv` = \'N\'' + . ' AND `References_priv` = \'N\'' + . ' AND `Create_tmp_table_priv` = \'N\'' + . ' AND `Lock_tables_priv` = \'N\'' + . ' AND `Create_view_priv` = \'N\'' + . ' AND `Show_view_priv` = \'N\'' + . ' AND `Create_routine_priv` = \'N\'' + . ' AND `Alter_routine_priv` = \'N\'' + . ' AND `Execute_priv` = \'N\''; + + $list_of_privileges .= + ', `Event_priv`, ' + . '`Trigger_priv`'; + $listOfComparedPrivs .= + ' AND `Event_priv` = \'N\'' + . ' AND `Trigger_priv` = \'N\''; + return array($list_of_privileges, $listOfComparedPrivs); + } + + /** + * Get the HTML for routine based privileges + * + * @param string $db database name + * @param string $index_checkbox starting index for rows to be added + * + * @return string $html_output + */ + public static function getHtmlTableBodyForSpecificDbRoutinePrivs($db, $index_checkbox) + { + $sql_query = 'SELECT * FROM `mysql`.`procs_priv` WHERE Db = \'' . $GLOBALS['dbi']->escapeString($db) . '\';'; + $res = $GLOBALS['dbi']->query($sql_query); + $html_output = ''; + while ($row = $GLOBALS['dbi']->fetchAssoc($res)) { + + $html_output .= '
      ' . htmlspecialchars($row['User']) + . '' . htmlspecialchars($row['Host']) + . '' . 'routine' + . '' . '' . htmlspecialchars($row['Routine_name']) . '' + . '' . 'Yes' + . ''; + if ($GLOBALS['is_grantuser']) { + $specific_db = (isset($row['Db']) && $row['Db'] != '*') + ? $row['Db'] : ''; + $specific_table = (isset($row['Table_name']) + && $row['Table_name'] != '*') + ? $row['Table_name'] : ''; + $html_output .= self::getUserLink( + 'edit', + $current_user, + $current_host, + $specific_db, + $specific_table, + $routine + ); + } + $html_output .= ''; + $html_output .= self::getUserLink( + 'export', + $current_user, + $current_host, + $specific_db, + $specific_table, + $routine + ); + $html_output .= '
      '; + $html_output .= self::getHtmlForPrivsTableHead(); + $privMap = self::getPrivMap($db); + $html_output .= self::getHtmlTableBodyForSpecificDbOrTablePrivs($privMap, $db); + $html_output .= '
      '; + $html_output .= '
      '; + + $html_output .= '
      '; + $html_output .= Template::get('select_all') + ->render( + array( + 'pma_theme_image' => $GLOBALS['pmaThemeImage'], + 'text_dir' => $GLOBALS['text_dir'], + 'form_name' => "usersForm", + ) + ); + $html_output .= Util::getButtonOrImage( + 'submit_mult', 'mult_submit', + __('Export'), 'b_tblexport', 'export' + ); + + $html_output .= ''; + $html_output .= '
      '; + $html_output .= ''; + } else { + $html_output .= self::getHtmlForViewUsersError(); + } + + $response = Response::getInstance(); + if ($response->isAjax() == true + && empty($_REQUEST['ajax_page_request']) + ) { + $message = Message::success(__('User has been added.')); + $response->addJSON('message', $message); + $response->addJSON('user_form', $html_output); + exit; + } else { + // Offer to create a new user for the current database + $html_output .= self::getAddUserHtmlFieldset($db); + } + return $html_output; + } + + /** + * Get the HTML for user form and check the privileges for a particular table. + * + * @param string $db database name + * @param string $table table name + * + * @return string $html_output + */ + public static function getHtmlForSpecificTablePrivileges($db, $table) + { + $html_output = ''; + if ($GLOBALS['dbi']->isSuperuser()) { + // check the privileges for a particular table. + $html_output = '
      '; + $html_output .= Url::getHiddenInputs($db, $table); + $html_output .= '
      '; + $html_output .= '' + . Util::getIcon('b_usrcheck') + . sprintf( + __('Users having access to "%s"'), + '' + . htmlspecialchars($db) . '.' . htmlspecialchars($table) + . '' + ) + . ''; + + $html_output .= '
      '; + $html_output .= ''; + $html_output .= self::getHtmlForPrivsTableHead(); + $privMap = self::getPrivMap($db); + $sql_query = "SELECT `User`, `Host`, `Db`," + . " 't' AS `Type`, `Table_name`, `Table_priv`" + . " FROM `mysql`.`tables_priv`" + . " WHERE '" . $GLOBALS['dbi']->escapeString($db) . "' LIKE `Db`" + . " AND '" . $GLOBALS['dbi']->escapeString($table) . "' LIKE `Table_name`" + . " AND NOT (`Table_priv` = '' AND Column_priv = '')" + . " ORDER BY `User` ASC, `Host` ASC, `Db` ASC, `Table_priv` ASC;"; + $res = $GLOBALS['dbi']->query($sql_query); + self::mergePrivMapFromResult($privMap, $res); + $html_output .= self::getHtmlTableBodyForSpecificDbOrTablePrivs($privMap, $db); + $html_output .= '
      '; + + $html_output .= '
      '; + $html_output .= Template::get('select_all') + ->render( + array( + 'pma_theme_image' => $GLOBALS['pmaThemeImage'], + 'text_dir' => $GLOBALS['text_dir'], + 'form_name' => "usersForm", + ) + ); + $html_output .= Util::getButtonOrImage( + 'submit_mult', 'mult_submit', + __('Export'), 'b_tblexport', 'export' + ); + + $html_output .= '
      '; + $html_output .= '
      '; + } else { + $html_output .= self::getHtmlForViewUsersError(); + } + // Offer to create a new user for the current database + $html_output .= self::getAddUserHtmlFieldset($db, $table); + return $html_output; + } + + /** + * gets privilege map + * + * @param string $db the database + * + * @return array $privMap the privilege map + */ + public static function getPrivMap($db) + { + list($listOfPrivs, $listOfComparedPrivs) + = self::getListOfPrivilegesAndComparedPrivileges(); + $sql_query + = "(" + . " SELECT " . $listOfPrivs . ", '*' AS `Db`, 'g' AS `Type`" + . " FROM `mysql`.`user`" + . " WHERE NOT (" . $listOfComparedPrivs . ")" + . ")" + . " UNION " + . "(" + . " SELECT " . $listOfPrivs . ", `Db`, 'd' AS `Type`" + . " FROM `mysql`.`db`" + . " WHERE '" . $GLOBALS['dbi']->escapeString($db) . "' LIKE `Db`" + . " AND NOT (" . $listOfComparedPrivs . ")" + . ")" + . " ORDER BY `User` ASC, `Host` ASC, `Db` ASC;"; + $res = $GLOBALS['dbi']->query($sql_query); + $privMap = array(); + self::mergePrivMapFromResult($privMap, $res); + return $privMap; + } + + /** + * merge privilege map and rows from resultset + * + * @param array &$privMap the privilege map reference + * @param object $result the resultset of query + * + * @return void + */ + public static function mergePrivMapFromResult(array &$privMap, $result) + { + while ($row = $GLOBALS['dbi']->fetchAssoc($result)) { + $user = $row['User']; + $host = $row['Host']; + if (! isset($privMap[$user])) { + $privMap[$user] = array(); + } + if (! isset($privMap[$user][$host])) { + $privMap[$user][$host] = array(); + } + $privMap[$user][$host][] = $row; + } + } + + /** + * Get HTML snippet for privileges table head + * + * @return string $html_output + */ + public static function getHtmlForPrivsTableHead() + { + return '' + . '' + . '' + . '' . __('User name') . '' + . '' . __('Host name') . '' + . '' . __('Type') . '' + . '' . __('Privileges') . '' + . '' . __('Grant') . '' + . '' . __('Action') . '' + . '' + . ''; + } + + /** + * Get HTML error for View Users form + * For non superusers such as grant/create users + * + * @return string $html_output + */ + public static function getHtmlForViewUsersError() + { + return Message::error( + __('Not enough privilege to view users.') + )->getDisplay(); + } + + /** + * Get HTML snippet for table body of specific database or table privileges + * + * @param array $privMap privilege map + * @param string $db database + * + * @return string $html_output + */ + public static function getHtmlTableBodyForSpecificDbOrTablePrivs($privMap, $db) + { + $html_output = ''; + $index_checkbox = 0; + if (empty($privMap)) { + $html_output .= '' + . '' + . __('No user found.') + . '' + . '' + . ''; + return $html_output; + } + + foreach ($privMap as $current_user => $val) { + foreach ($val as $current_host => $current_privileges) { + $nbPrivileges = count($current_privileges); + $html_output .= ''; + + $value = htmlspecialchars($current_user . '&#27;' . $current_host); + $html_output .= ' 1) { + $html_output .= ' rowspan="' . $nbPrivileges . '"'; + } + $html_output .= '>'; + $html_output .= '' . "\n"; + + // user + $html_output .= ' 1) { + $html_output .= ' rowspan="' . $nbPrivileges . '"'; + } + $html_output .= '>'; + if (empty($current_user)) { + $html_output .= '' + . __('Any') . ''; + } else { + $html_output .= htmlspecialchars($current_user); + } + $html_output .= ''; + + // host + $html_output .= ' 1) { + $html_output .= ' rowspan="' . $nbPrivileges . '"'; + } + $html_output .= '>'; + $html_output .= htmlspecialchars($current_host); + $html_output .= ''; + + $html_output .= self::getHtmlListOfPrivs( + $db, $current_privileges, $current_user, + $current_host + ); + } + } + + //For fetching routine based privileges + $html_output .= self::getHtmlTableBodyForSpecificDbRoutinePrivs($db, $index_checkbox); + $html_output .= ''; + + return $html_output; + } + + /** + * Get HTML to display privileges + * + * @param string $db Database name + * @param array $current_privileges List of privileges + * @param string $current_user Current user + * @param string $current_host Current host + * + * @return string HTML to display privileges + */ + public static function getHtmlListOfPrivs( + $db, array $current_privileges, $current_user, + $current_host + ) { + $nbPrivileges = count($current_privileges); + $html_output = null; + for ($i = 0; $i < $nbPrivileges; $i++) { + $current = $current_privileges[$i]; + + // type + $html_output .= ''; + if ($current['Type'] == 'g') { + $html_output .= __('global'); + } elseif ($current['Type'] == 'd') { + if ($current['Db'] == Util::escapeMysqlWildcards($db)) { + $html_output .= __('database-specific'); + } else { + $html_output .= __('wildcard') . ': ' + . '' + . htmlspecialchars($current['Db']) + . ''; + } + } elseif ($current['Type'] == 't') { + $html_output .= __('table-specific'); + } + $html_output .= ''; + + // privileges + $html_output .= ''; + if (isset($current['Table_name'])) { + $privList = explode(',', $current['Table_priv']); + $privs = array(); + $grantsArr = self::getTableGrantsArray(); + foreach ($grantsArr as $grant) { + $privs[$grant[0]] = 'N'; + foreach ($privList as $priv) { + if ($grant[0] == $priv) { + $privs[$grant[0]] = 'Y'; + } + } + } + $html_output .= '' + . join( + ',', + self::extractPrivInfo($privs, true, true) + ) + . ''; + } else { + $html_output .= '' + . join( + ',', + self::extractPrivInfo($current, true, false) + ) + . ''; + } + $html_output .= ''; + + // grant + $html_output .= ''; + $containsGrant = false; + if (isset($current['Table_name'])) { + $privList = explode(',', $current['Table_priv']); + foreach ($privList as $priv) { + if ($priv == 'Grant') { + $containsGrant = true; + } + } + } else { + $containsGrant = $current['Grant_priv'] == 'Y'; + } + $html_output .= ($containsGrant ? __('Yes') : __('No')); + $html_output .= ''; + + // action + $html_output .= ''; + $specific_db = (isset($current['Db']) && $current['Db'] != '*') + ? $current['Db'] : ''; + $specific_table = (isset($current['Table_name']) + && $current['Table_name'] != '*') + ? $current['Table_name'] : ''; + if ($GLOBALS['is_grantuser']) { + $html_output .= self::getUserLink( + 'edit', + $current_user, + $current_host, + $specific_db, + $specific_table + ); + } + $html_output .= ''; + $html_output .= '' + . self::getUserLink( + 'export', + $current_user, + $current_host, + $specific_db, + $specific_table + ) + . ''; + + $html_output .= ''; + if (($i + 1) < $nbPrivileges) { + $html_output .= ''; + } + } + return $html_output; + } + + /** + * Returns edit, revoke or export link for a user. + * + * @param string $linktype The link type (edit | revoke | export) + * @param string $username User name + * @param string $hostname Host name + * @param string $dbname Database name + * @param string $tablename Table name + * @param string $routinename Routine name + * @param string $initial Initial value + * + * @return string HTML code with link + */ + public static function getUserLink( + $linktype, $username, $hostname, $dbname = '', + $tablename = '', $routinename = '', $initial = '' + ) { + $html = ' $username, + 'hostname' => $hostname + ); + switch($linktype) { + case 'edit': + $params['dbname'] = $dbname; + $params['tablename'] = $tablename; + $params['routinename'] = $routinename; + break; + case 'revoke': + $params['dbname'] = $dbname; + $params['tablename'] = $tablename; + $params['routinename'] = $routinename; + $params['revokeall'] = 1; + break; + case 'export': + $params['initial'] = $initial; + $params['export'] = 1; + break; + } + + $html .= ' href="server_privileges.php'; + if ($linktype == 'revoke') { + $html .= '" data-post="' . Url::getCommon($params, ''); + } else { + $html .= Url::getCommon($params); + } + $html .= '">'; + + switch($linktype) { + case 'edit': + $html .= Util::getIcon('b_usredit', __('Edit privileges')); + break; + case 'revoke': + $html .= Util::getIcon('b_usrdrop', __('Revoke')); + break; + case 'export': + $html .= Util::getIcon('b_tblexport', __('Export')); + break; + } + $html .= ''; + + return $html; + } + + /** + * Returns user group edit link + * + * @param string $username User name + * + * @return string HTML code with link + */ + public static function getUserGroupEditLink($username) + { + return '' + . Util::getIcon('b_usrlist', __('Edit user group')) + . ''; + } + + /** + * Returns number of defined user groups + * + * @return integer $user_group_count + */ + public static function getUserGroupCount() + { + $relation = new Relation(); + $cfgRelation = $relation->getRelationsParam(); + $user_group_table = Util::backquote($cfgRelation['db']) + . '.' . Util::backquote($cfgRelation['usergroups']); + $sql_query = 'SELECT COUNT(*) FROM ' . $user_group_table; + $user_group_count = $GLOBALS['dbi']->fetchValue( + $sql_query, 0, 0, DatabaseInterface::CONNECT_CONTROL + ); + + return $user_group_count; + } + + /** + * Returns name of user group that user is part of + * + * @param string $username User name + * + * @return mixed usergroup if found or null if not found + */ + public static function getUserGroupForUser($username) + { + $relation = new Relation(); + $cfgRelation = $relation->getRelationsParam(); + + if (empty($cfgRelation['db']) + || empty($cfgRelation['users']) + ) { + return null; + } + + $user_table = Util::backquote($cfgRelation['db']) + . '.' . Util::backquote($cfgRelation['users']); + $sql_query = 'SELECT `usergroup` FROM ' . $user_table + . ' WHERE `username` = \'' . $username . '\'' + . ' LIMIT 1'; + + $usergroup = $GLOBALS['dbi']->fetchValue( + $sql_query, 0, 0, DatabaseInterface::CONNECT_CONTROL + ); + + if ($usergroup === false) { + return null; + } + + return $usergroup; + } + + /** + * This function return the extra data array for the ajax behavior + * + * @param string $password password + * @param string $sql_query sql query + * @param string $hostname hostname + * @param string $username username + * + * @return array $extra_data + */ + public static function getExtraDataForAjaxBehavior( + $password, $sql_query, $hostname, $username + ) { + $relation = new Relation(); + if (isset($GLOBALS['dbname'])) { + //if (preg_match('/\\\\(?:_|%)/i', $dbname)) { + if (preg_match('/(? 0) { + $extra_data['sql_query'] = Util::getMessage(null, $sql_query); + } + + if (isset($_POST['change_copy'])) { + /** + * generate html on the fly for the new user that was just created. + */ + $new_user_string = '' . "\n" + . ' ' + . '' . "\n" + . '' . "\n" + . '' . htmlspecialchars($hostname) . '' . "\n"; + + $new_user_string .= ''; + + if (! empty($password) || isset($_POST['pma_pw'])) { + $new_user_string .= __('Yes'); + } else { + $new_user_string .= '' + . __('No') + . ''; + }; + + $new_user_string .= '' . "\n"; + $new_user_string .= '' + . '' . join(', ', self::extractPrivInfo(null, true)) . '' + . ''; //Fill in privileges here + + // if $cfg['Servers'][$i]['users'] and $cfg['Servers'][$i]['usergroups'] are + // enabled + $cfgRelation = $relation->getRelationsParam(); + if (!empty($cfgRelation['users']) && !empty($cfgRelation['usergroups'])) { + $new_user_string .= ''; + } + + $new_user_string .= ''; + if ((isset($_POST['Grant_priv']) && $_POST['Grant_priv'] == 'Y')) { + $new_user_string .= __('Yes'); + } else { + $new_user_string .= __('No'); + } + $new_user_string .=''; + + if ($GLOBALS['is_grantuser']) { + $new_user_string .= '' + . self::getUserLink('edit', $username, $hostname) + . '' . "\n"; + } + + if ($cfgRelation['menuswork'] && $user_group_count > 0) { + $new_user_string .= '' + . self::getUserGroupEditLink($username) + . '' . "\n"; + } + + $new_user_string .= '' + . self::getUserLink( + 'export', + $username, + $hostname, + '', + '', + '', + isset($_GET['initial']) ? $_GET['initial'] : '' + ) + . '' . "\n"; + + $new_user_string .= ''; + + $extra_data['new_user_string'] = $new_user_string; + + /** + * Generate the string for this alphabet's initial, to update the user + * pagination + */ + $new_user_initial = mb_strtoupper( + mb_substr($username, 0, 1) + ); + $newUserInitialString = '' + . $new_user_initial . ''; + $extra_data['new_user_initial'] = $new_user_initial; + $extra_data['new_user_initial_string'] = $newUserInitialString; + } + + if (isset($_POST['update_privs'])) { + $extra_data['db_specific_privs'] = false; + $extra_data['db_wildcard_privs'] = false; + if (isset($dbname_is_wildcard)) { + $extra_data['db_specific_privs'] = ! $dbname_is_wildcard; + $extra_data['db_wildcard_privs'] = $dbname_is_wildcard; + } + $new_privileges = join(', ', self::extractPrivInfo(null, true)); + + $extra_data['new_privileges'] = $new_privileges; + } + + if (isset($_GET['validate_username'])) { + $sql_query = "SELECT * FROM `mysql`.`user` WHERE `User` = '" + . $_GET['username'] . "';"; + $res = $GLOBALS['dbi']->query($sql_query); + $row = $GLOBALS['dbi']->fetchRow($res); + if (empty($row)) { + $extra_data['user_exists'] = false; + } else { + $extra_data['user_exists'] = true; + } + } + + return $extra_data; + } + + /** + * Get the HTML snippet for change user login information + * + * @param string $username username + * @param string $hostname host name + * + * @return string HTML snippet + */ + public static function getChangeLoginInformationHtmlForm($username, $hostname) + { + $choices = array( + '4' => __('… keep the old one.'), + '1' => __('… delete the old one from the user tables.'), + '2' => __( + '… revoke all active privileges from ' + . 'the old one and delete it afterwards.' + ), + '3' => __( + '… delete the old one from the user tables ' + . 'and reload the privileges afterwards.' + ) + ); + + $html_output = '' . "\n"; + + return $html_output; + } + + /** + * Provide a line with links to the relevant database and table + * + * @param string $url_dbname url database name that urlencode() string + * @param string $dbname database name + * @param string $tablename table name + * + * @return string HTML snippet + */ + public static function getLinkToDbAndTable($url_dbname, $dbname, $tablename) + { + $html_output = '[ ' . __('Database') + . ' ' + . htmlspecialchars(Util::unescapeMysqlWildcards($dbname)) . ': ' + . Util::getTitleForTarget( + $GLOBALS['cfg']['DefaultTabDatabase'] + ) + . " ]\n"; + + if (strlen($tablename) > 0) { + $html_output .= ' [ ' . __('Table') . ' ' . htmlspecialchars($tablename) . ': ' + . Util::getTitleForTarget( + $GLOBALS['cfg']['DefaultTabTable'] + ) + . " ]\n"; + } + return $html_output; + } + + /** + * no db name given, so we want all privs for the given user + * db name was given, so we want all user specific rights for this db + * So this function returns user rights as an array + * + * @param string $username username + * @param string $hostname host name + * @param string $type database or table + * @param string $dbname database name + * + * @return array $db_rights database rights + */ + public static function getUserSpecificRights($username, $hostname, $type, $dbname = '') + { + $user_host_condition = " WHERE `User`" + . " = '" . $GLOBALS['dbi']->escapeString($username) . "'" + . " AND `Host`" + . " = '" . $GLOBALS['dbi']->escapeString($hostname) . "'"; + + if ($type == 'database') { + $tables_to_search_for_users = array( + 'tables_priv', 'columns_priv', 'procs_priv' + ); + $dbOrTableName = 'Db'; + } elseif ($type == 'table') { + $user_host_condition .= " AND `Db` LIKE '" + . $GLOBALS['dbi']->escapeString($dbname) . "'"; + $tables_to_search_for_users = array('columns_priv',); + $dbOrTableName = 'Table_name'; + } else { // routine + $user_host_condition .= " AND `Db` LIKE '" + . $GLOBALS['dbi']->escapeString($dbname) . "'"; + $tables_to_search_for_users = array('procs_priv',); + $dbOrTableName = 'Routine_name'; + } + + // we also want privileges for this user not in table `db` but in other table + $tables = $GLOBALS['dbi']->fetchResult('SHOW TABLES FROM `mysql`;'); + + $db_rights_sqls = array(); + foreach ($tables_to_search_for_users as $table_search_in) { + if (in_array($table_search_in, $tables)) { + $db_rights_sqls[] = ' + SELECT DISTINCT `' . $dbOrTableName . '` + FROM `mysql`.' . Util::backquote($table_search_in) + . $user_host_condition; + } + } + + $user_defaults = array( + $dbOrTableName => '', + 'Grant_priv' => 'N', + 'privs' => array('USAGE'), + 'Column_priv' => true, + ); + + // for the rights + $db_rights = array(); + + $db_rights_sql = '(' . implode(') UNION (', $db_rights_sqls) . ')' + . ' ORDER BY `' . $dbOrTableName . '` ASC'; + + $db_rights_result = $GLOBALS['dbi']->query($db_rights_sql); + + while ($db_rights_row = $GLOBALS['dbi']->fetchAssoc($db_rights_result)) { + $db_rights_row = array_merge($user_defaults, $db_rights_row); + if ($type == 'database') { + // only Db names in the table `mysql`.`db` uses wildcards + // as we are in the db specific rights display we want + // all db names escaped, also from other sources + $db_rights_row['Db'] = Util::escapeMysqlWildcards( + $db_rights_row['Db'] + ); + } + $db_rights[$db_rights_row[$dbOrTableName]] = $db_rights_row; + } + + $GLOBALS['dbi']->freeResult($db_rights_result); + + if ($type == 'database') { + $sql_query = 'SELECT * FROM `mysql`.`db`' + . $user_host_condition . ' ORDER BY `Db` ASC'; + } elseif ($type == 'table') { + $sql_query = 'SELECT `Table_name`,' + . ' `Table_priv`,' + . ' IF(`Column_priv` = _latin1 \'\', 0, 1)' + . ' AS \'Column_priv\'' + . ' FROM `mysql`.`tables_priv`' + . $user_host_condition + . ' ORDER BY `Table_name` ASC;'; + } else { + $sql_query = "SELECT `Routine_name`, `Proc_priv`" + . " FROM `mysql`.`procs_priv`" + . $user_host_condition + . " ORDER BY `Routine_name`"; + + } + + $result = $GLOBALS['dbi']->query($sql_query); + + while ($row = $GLOBALS['dbi']->fetchAssoc($result)) { + if (isset($db_rights[$row[$dbOrTableName]])) { + $db_rights[$row[$dbOrTableName]] + = array_merge($db_rights[$row[$dbOrTableName]], $row); + } else { + $db_rights[$row[$dbOrTableName]] = $row; + } + if ($type == 'database') { + // there are db specific rights for this user + // so we can drop this db rights + $db_rights[$row['Db']]['can_delete'] = true; + } + } + $GLOBALS['dbi']->freeResult($result); + return $db_rights; + } + + /** + * Parses Proc_priv data + * + * @param string $privs Proc_priv + * + * @return array + */ + public static function parseProcPriv($privs) + { + $result = array( + 'Alter_routine_priv' => 'N', + 'Execute_priv' => 'N', + 'Grant_priv' => 'N', + ); + foreach (explode(',', $privs) as $priv) { + if ($priv == 'Alter Routine') { + $result['Alter_routine_priv'] = 'Y'; + } else { + $result[$priv . '_priv'] = 'Y'; + } + } + return $result; + } + + /** + * Get a HTML table for display user's tabel specific or database specific rights + * + * @param string $username username + * @param string $hostname host name + * @param string $type database, table or routine + * @param string $dbname database name + * + * @return array $html_output + */ + public static function getHtmlForAllTableSpecificRights( + $username, $hostname, $type, $dbname = '' + ) { + $uiData = array( + 'database' => array( + 'form_id' => 'database_specific_priv', + 'sub_menu_label' => __('Database'), + 'legend' => __('Database-specific privileges'), + 'type_label' => __('Database'), + ), + 'table' => array( + 'form_id' => 'table_specific_priv', + 'sub_menu_label' => __('Table'), + 'legend' => __('Table-specific privileges'), + 'type_label' => __('Table'), + ), + 'routine' => array( + 'form_id' => 'routine_specific_priv', + 'sub_menu_label' => __('Routine'), + 'legend' => __('Routine-specific privileges'), + 'type_label' => __('Routine'), + ), + ); + + /** + * no db name given, so we want all privs for the given user + * db name was given, so we want all user specific rights for this db + */ + $db_rights = self::getUserSpecificRights($username, $hostname, $type, $dbname); + ksort($db_rights); + + $foundRows = array(); + $privileges = array(); + foreach ($db_rights as $row) { + $onePrivilege = array(); + + $paramTableName = ''; + $paramRoutineName = ''; + + if ($type == 'database') { + $name = $row['Db']; + $onePrivilege['grant'] = $row['Grant_priv'] == 'Y'; + $onePrivilege['table_privs'] = ! empty($row['Table_priv']) + || ! empty($row['Column_priv']); + $onePrivilege['privileges'] = join(',', self::extractPrivInfo($row, true)); + + $paramDbName = $row['Db']; + + } elseif ($type == 'table') { + $name = $row['Table_name']; + $onePrivilege['grant'] = in_array( + 'Grant', + explode(',', $row['Table_priv']) + ); + $onePrivilege['column_privs'] = ! empty($row['Column_priv']); + $onePrivilege['privileges'] = join(',', self::extractPrivInfo($row, true)); + + $paramDbName = $dbname; + $paramTableName = $row['Table_name']; + + } else { // routine + $name = $row['Routine_name']; + $onePrivilege['grant'] = in_array( + 'Grant', + explode(',', $row['Proc_priv']) + ); + + $privs = self::parseProcPriv($row['Proc_priv']); + $onePrivilege['privileges'] = join( + ',', + self::extractPrivInfo($privs, true) + ); + + $paramDbName = $dbname; + $paramRoutineName = $row['Routine_name']; + } + + $foundRows[] = $name; + $onePrivilege['name'] = $name; + + $onePrivilege['edit_link'] = ''; + if ($GLOBALS['is_grantuser']) { + $onePrivilege['edit_link'] = self::getUserLink( + 'edit', + $username, + $hostname, + $paramDbName, + $paramTableName, + $paramRoutineName + ); + } + + $onePrivilege['revoke_link'] = ''; + if ($type != 'database' || ! empty($row['can_delete'])) { + $onePrivilege['revoke_link'] = self::getUserLink( + 'revoke', + $username, + $hostname, + $paramDbName, + $paramTableName, + $paramRoutineName + ); + } + + $privileges[] = $onePrivilege; + } + + $data = $uiData[$type]; + $data['privileges'] = $privileges; + $data['username'] = $username; + $data['hostname'] = $hostname; + $data['database'] = $dbname; + $data['type'] = $type; + + if ($type == 'database') { + + // we already have the list of databases from libraries/common.inc.php + // via $pma = new PMA; + $pred_db_array = $GLOBALS['dblist']->databases; + $databases_to_skip = array('information_schema', 'performance_schema'); + + $databases = array(); + if (! empty($pred_db_array)) { + foreach ($pred_db_array as $current_db) { + if (in_array($current_db, $databases_to_skip)) { + continue; + } + $current_db_escaped = Util::escapeMysqlWildcards($current_db); + // cannot use array_diff() once, outside of the loop, + // because the list of databases has special characters + // already escaped in $foundRows, + // contrary to the output of SHOW DATABASES + if (! in_array($current_db_escaped, $foundRows)) { + $databases[] = $current_db; + } + } + } + $data['databases'] = $databases; + + } elseif ($type == 'table') { + $result = @$GLOBALS['dbi']->tryQuery( + "SHOW TABLES FROM " . Util::backquote($dbname), + DatabaseInterface::CONNECT_USER, + DatabaseInterface::QUERY_STORE + ); + + $tables = array(); + if ($result) { + while ($row = $GLOBALS['dbi']->fetchRow($result)) { + if (! in_array($row[0], $foundRows)) { + $tables[] = $row[0]; + } + } + $GLOBALS['dbi']->freeResult($result); + } + $data['tables'] = $tables; + + } else { // routine + $routineData = $GLOBALS['dbi']->getRoutines($dbname); + + $routines = array(); + foreach ($routineData as $routine) { + if (! in_array($routine['name'], $foundRows)) { + $routines[] = $routine['name']; + } + } + $data['routines'] = $routines; + } + + $html_output = Template::get('privileges/privileges_summary') + ->render($data); + + return $html_output; + } + + /** + * Get HTML for display the users overview + * (if less than 50 users, display them immediately) + * + * @param array $result ran sql query + * @param array $db_rights user's database rights array + * @param string $pmaThemeImage a image source link + * @param string $text_dir text directory + * + * @return string HTML snippet + */ + public static function getUsersOverview($result, array $db_rights, $pmaThemeImage, $text_dir) + { + while ($row = $GLOBALS['dbi']->fetchAssoc($result)) { + $row['privs'] = self::extractPrivInfo($row, true); + $db_rights[$row['User']][$row['Host']] = $row; + } + $GLOBALS['dbi']->freeResult($result); + $user_group_count = 0; + if ($GLOBALS['cfgRelation']['menuswork']) { + $user_group_count = self::getUserGroupCount(); + } + + $html_output + = '
      ' . "\n" + . Url::getHiddenInputs('', '') + . '
      ' + . '' . "\n" + . '' . "\n" + . '' . "\n" + . '' . "\n" + . '' . "\n" + . '' . "\n" + . '' . "\n"; + if ($GLOBALS['cfgRelation']['menuswork']) { + $html_output .= '' . "\n"; + } + $html_output .= '' . "\n" + . '' . "\n" + . '' . "\n" + . '' . "\n"; + + $html_output .= '' . "\n"; + $html_output .= self::getHtmlTableBodyForUserRights($db_rights); + $html_output .= '' + . '
      ' . __('User name') . '' . __('Host name') . '' . __('Password') . '' . __('Global privileges') . ' ' + . Util::showHint( + __('Note: MySQL privilege names are expressed in English.') + ) + . '' . __('User group') . '' . __('Grant') . '' + . __('Action') . '
      ' . "\n"; + + $html_output .= '
      ' + . Template::get('select_all') + ->render( + array( + 'pma_theme_image' => $pmaThemeImage, + 'text_dir' => $text_dir, + 'form_name' => 'usersForm', + ) + ) . "\n"; + $html_output .= Util::getButtonOrImage( + 'submit_mult', 'mult_submit', + __('Export'), 'b_tblexport', 'export' + ); + $html_output .= ''; + $html_output .= '
      ' + . '
      '; + + // add/delete user fieldset + $html_output .= self::getFieldsetForAddDeleteUser(); + $html_output .= '
      ' . "\n"; + + return $html_output; + } + + /** + * Get table body for 'tableuserrights' table in userform + * + * @param array $db_rights user's database rights array + * + * @return string HTML snippet + */ + public static function getHtmlTableBodyForUserRights(array $db_rights) + { + $relation = new Relation(); + $cfgRelation = $relation->getRelationsParam(); + if ($cfgRelation['menuswork']) { + $users_table = Util::backquote($cfgRelation['db']) + . "." . Util::backquote($cfgRelation['users']); + $sql_query = 'SELECT * FROM ' . $users_table; + $result = $relation->queryAsControlUser($sql_query, false); + $group_assignment = array(); + if ($result) { + while ($row = $GLOBALS['dbi']->fetchAssoc($result)) { + $group_assignment[$row['username']] = $row['usergroup']; + } + } + $GLOBALS['dbi']->freeResult($result); + + $user_group_count = self::getUserGroupCount(); + } + + $index_checkbox = 0; + $html_output = ''; + foreach ($db_rights as $user) { + ksort($user); + foreach ($user as $host) { + $index_checkbox++; + $html_output .= '' + . "\n"; + $html_output .= '' + . '' . "\n"; + + $html_output .= '' . "\n" + . '' . htmlspecialchars($host['Host']) . '' . "\n"; + + $html_output .= ''; + + $password_column = 'Password'; + + $check_plugin_query = "SELECT * FROM `mysql`.`user` WHERE " + . "`User` = '" . $host['User'] . "' AND `Host` = '" + . $host['Host'] . "'"; + $res = $GLOBALS['dbi']->fetchSingleRow($check_plugin_query); + + if ((isset($res['authentication_string']) + && ! empty($res['authentication_string'])) + || (isset($res['Password']) + && ! empty($res['Password'])) + ) { + $host[$password_column] = 'Y'; + } else { + $host[$password_column] = 'N'; + } + + switch ($host[$password_column]) { + case 'Y': + $html_output .= __('Yes'); + break; + case 'N': + $html_output .= '' . __('No') + . ''; + break; + // this happens if this is a definition not coming from mysql.user + default: + $html_output .= '--'; // in future version, replace by "not present" + break; + } // end switch + + if (! isset($host['Select_priv'])) { + $html_output .= Util::showHint( + __('The selected user was not found in the privilege table.') + ); + } + + $html_output .= '' . "\n"; + + $html_output .= '' . "\n" + . '' . implode(',' . "\n" . ' ', $host['privs']) . "\n" + . '' . "\n"; + if ($cfgRelation['menuswork']) { + $html_output .= '' . "\n" + . (isset($group_assignment[$host['User']]) + ? htmlspecialchars($group_assignment[$host['User']]) + : '' + ) + . '' . "\n"; + } + $html_output .= '' + . ($host['Grant_priv'] == 'Y' ? __('Yes') : __('No')) + . '' . "\n"; + + if ($GLOBALS['is_grantuser']) { + $html_output .= '' + . self::getUserLink( + 'edit', + $host['User'], + $host['Host'] + ) + . ''; + } + if ($cfgRelation['menuswork'] && $user_group_count > 0) { + if (empty($host['User'])) { + $html_output .= ''; + } else { + $html_output .= '' + . self::getUserGroupEditLink($host['User']) + . ''; + } + } + $html_output .= '' + . self::getUserLink( + 'export', + $host['User'], + $host['Host'], + '', + '', + '', + isset($_GET['initial']) ? $_GET['initial'] : '' + ) + . ''; + $html_output .= ''; + } + } + return $html_output; + } + + /** + * Get HTML fieldset for Add/Delete user + * + * @return string HTML snippet + */ + public static function getFieldsetForAddDeleteUser() + { + $html_output = self::getAddUserHtmlFieldset(); + + $html_output .= Template::get('privileges/delete_user_fieldset') + ->render(array()); + + return $html_output; + } + + /** + * Get HTML for Displays the initials + * + * @param array $array_initials array for all initials, even non A-Z + * + * @return string HTML snippet + */ + public static function getHtmlForInitials(array $array_initials) + { + // initialize to false the letters A-Z + for ($letter_counter = 1; $letter_counter < 27; $letter_counter++) { + if (! isset($array_initials[mb_chr($letter_counter + 64)])) { + $array_initials[mb_chr($letter_counter + 64)] = false; + } + } + + $initials = $GLOBALS['dbi']->tryQuery( + 'SELECT DISTINCT UPPER(LEFT(`User`,1)) FROM `user`' + . ' ORDER BY UPPER(LEFT(`User`,1)) ASC', + DatabaseInterface::CONNECT_USER, + DatabaseInterface::QUERY_STORE + ); + if ($initials) { + while (list($tmp_initial) = $GLOBALS['dbi']->fetchRow($initials)) { + $array_initials[$tmp_initial] = true; + } + } + + // Display the initials, which can be any characters, not + // just letters. For letters A-Z, we add the non-used letters + // as greyed out. + + uksort($array_initials, "strnatcasecmp"); + + $html_output = Template::get('privileges/initials_row') + ->render( + array( + 'array_initials' => $array_initials, + 'initial' => isset($_GET['initial']) ? $_GET['initial'] : null, + ) + ); + + return $html_output; + } + + /** + * Get the database rights array for Display user overview + * + * @return array $db_rights database rights array + */ + public static function getDbRightsForUserOverview() + { + // we also want users not in table `user` but in other table + $tables = $GLOBALS['dbi']->fetchResult('SHOW TABLES FROM `mysql`;'); + + $tablesSearchForUsers = array( + 'user', 'db', 'tables_priv', 'columns_priv', 'procs_priv', + ); + + $db_rights_sqls = array(); + foreach ($tablesSearchForUsers as $table_search_in) { + if (in_array($table_search_in, $tables)) { + $db_rights_sqls[] = 'SELECT DISTINCT `User`, `Host` FROM `mysql`.`' + . $table_search_in . '` ' + . (isset($_GET['initial']) + ? self::rangeOfUsers($_GET['initial']) + : ''); + } + } + $user_defaults = array( + 'User' => '', + 'Host' => '%', + 'Password' => '?', + 'Grant_priv' => 'N', + 'privs' => array('USAGE'), + ); + + // for the rights + $db_rights = array(); + + $db_rights_sql = '(' . implode(') UNION (', $db_rights_sqls) . ')' + . ' ORDER BY `User` ASC, `Host` ASC'; + + $db_rights_result = $GLOBALS['dbi']->query($db_rights_sql); + + while ($db_rights_row = $GLOBALS['dbi']->fetchAssoc($db_rights_result)) { + $db_rights_row = array_merge($user_defaults, $db_rights_row); + $db_rights[$db_rights_row['User']][$db_rights_row['Host']] + = $db_rights_row; + } + $GLOBALS['dbi']->freeResult($db_rights_result); + ksort($db_rights); + + return $db_rights; + } + + /** + * Delete user and get message and sql query for delete user in privileges + * + * @param array $queries queries + * + * @return array Message + */ + public static function deleteUser(array $queries) + { + $sql_query = ''; + if (empty($queries)) { + $message = Message::error(__('No users selected for deleting!')); + } else { + if ($_POST['mode'] == 3) { + $queries[] = '# ' . __('Reloading the privileges') . ' …'; + $queries[] = 'FLUSH PRIVILEGES;'; + } + $drop_user_error = ''; + foreach ($queries as $sql_query) { + if ($sql_query[0] != '#') { + if (! $GLOBALS['dbi']->tryQuery($sql_query)) { + $drop_user_error .= $GLOBALS['dbi']->getError() . "\n"; + } + } + } + // tracking sets this, causing the deleted db to be shown in navi + unset($GLOBALS['db']); + + $sql_query = join("\n", $queries); + if (! empty($drop_user_error)) { + $message = Message::rawError($drop_user_error); + } else { + $message = Message::success( + __('The selected users have been deleted successfully.') + ); + } + } + return array($sql_query, $message); + } + + /** + * Update the privileges and return the success or error message + * + * @param string $username username + * @param string $hostname host name + * @param string $tablename table name + * @param string $dbname database name + * @param string $itemType item type + * + * @return Message success message or error message for update + */ + public static function updatePrivileges($username, $hostname, $tablename, $dbname, $itemType) + { + $db_and_table = self::wildcardEscapeForGrant($dbname, $tablename); + + $sql_query0 = 'REVOKE ALL PRIVILEGES ON ' . $itemType . ' ' . $db_and_table + . ' FROM \'' . $GLOBALS['dbi']->escapeString($username) + . '\'@\'' . $GLOBALS['dbi']->escapeString($hostname) . '\';'; + + if (! isset($_POST['Grant_priv']) || $_POST['Grant_priv'] != 'Y') { + $sql_query1 = 'REVOKE GRANT OPTION ON ' . $itemType . ' ' . $db_and_table + . ' FROM \'' . $GLOBALS['dbi']->escapeString($username) . '\'@\'' + . $GLOBALS['dbi']->escapeString($hostname) . '\';'; + } else { + $sql_query1 = ''; + } + + // Should not do a GRANT USAGE for a table-specific privilege, it + // causes problems later (cannot revoke it) + if (! (strlen($tablename) > 0 + && 'USAGE' == implode('', self::extractPrivInfo())) + ) { + $sql_query2 = 'GRANT ' . join(', ', self::extractPrivInfo()) + . ' ON ' . $itemType . ' ' . $db_and_table + . ' TO \'' . $GLOBALS['dbi']->escapeString($username) . '\'@\'' + . $GLOBALS['dbi']->escapeString($hostname) . '\''; + + if (strlen($dbname) === 0) { + // add REQUIRE clause + $sql_query2 .= self::getRequireClause(); + } + + if ((isset($_POST['Grant_priv']) && $_POST['Grant_priv'] == 'Y') + || (strlen($dbname) === 0 + && (isset($_POST['max_questions']) || isset($_POST['max_connections']) + || isset($_POST['max_updates']) + || isset($_POST['max_user_connections']))) + ) { + $sql_query2 .= self::getWithClauseForAddUserAndUpdatePrivs(); + } + $sql_query2 .= ';'; + } + if (! $GLOBALS['dbi']->tryQuery($sql_query0)) { + // This might fail when the executing user does not have + // ALL PRIVILEGES himself. + // See https://github.com/phpmyadmin/phpmyadmin/issues/9673 + $sql_query0 = ''; + } + if (! empty($sql_query1) && ! $GLOBALS['dbi']->tryQuery($sql_query1)) { + // this one may fail, too... + $sql_query1 = ''; + } + if (! empty($sql_query2)) { + $GLOBALS['dbi']->query($sql_query2); + } else { + $sql_query2 = ''; + } + $sql_query = $sql_query0 . ' ' . $sql_query1 . ' ' . $sql_query2; + $message = Message::success(__('You have updated the privileges for %s.')); + $message->addParam('\'' . $username . '\'@\'' . $hostname . '\''); + + return array($sql_query, $message); + } + + /** + * Get List of information: Changes / copies a user + * + * @return array + */ + public static function getDataForChangeOrCopyUser() + { + $queries = null; + $password = null; + + if (isset($_POST['change_copy'])) { + $user_host_condition = ' WHERE `User` = ' + . "'" . $GLOBALS['dbi']->escapeString($_POST['old_username']) . "'" + . ' AND `Host` = ' + . "'" . $GLOBALS['dbi']->escapeString($_POST['old_hostname']) . "';"; + $row = $GLOBALS['dbi']->fetchSingleRow( + 'SELECT * FROM `mysql`.`user` ' . $user_host_condition + ); + if (! $row) { + $response = Response::getInstance(); + $response->addHTML( + Message::notice(__('No user found.'))->getDisplay() + ); + unset($_POST['change_copy']); + } else { + extract($row, EXTR_OVERWRITE); + foreach ($row as $key => $value) { + $GLOBALS[$key] = $value; + } + $serverVersion = $GLOBALS['dbi']->getVersion(); + // Recent MySQL versions have the field "Password" in mysql.user, + // so the previous extract creates $Password but this script + // uses $password + if (! isset($password) && isset($Password)) { + $password = $Password; + } + if (Util::getServerType() == 'MySQL' + && $serverVersion >= 50606 + && $serverVersion < 50706 + && ((isset($authentication_string) + && empty($password)) + || (isset($plugin) + && $plugin == 'sha256_password')) + ) { + $password = $authentication_string; + } + + if (Util::getServerType() == 'MariaDB' + && $serverVersion >= 50500 + && isset($authentication_string) + && empty($password) + ) { + $password = $authentication_string; + } + + // Always use 'authentication_string' column + // for MySQL 5.7.6+ since it does not have + // the 'password' column at all + if (in_array(Util::getServerType(), array('MySQL', 'Percona Server')) + && $serverVersion >= 50706 + && isset($authentication_string) + ) { + $password = $authentication_string; + } + + $queries = array(); + } + } + + return array($queries, $password); + } + + /** + * Update Data for information: Deletes users + * + * @param array $queries queries array + * + * @return array + */ + public static function getDataForDeleteUsers($queries) + { + if (isset($_POST['change_copy'])) { + $selected_usr = array( + $_POST['old_username'] . '&#27;' . $_POST['old_hostname'] + ); + } else { + $selected_usr = $_POST['selected_usr']; + $queries = array(); + } + + // this happens, was seen in https://reports.phpmyadmin.net/reports/view/17146 + if (! is_array($selected_usr)) { + return array(); + } + + foreach ($selected_usr as $each_user) { + list($this_user, $this_host) = explode('&#27;', $each_user); + $queries[] = '# ' + . sprintf( + __('Deleting %s'), + '\'' . $this_user . '\'@\'' . $this_host . '\'' + ) + . ' ...'; + $queries[] = 'DROP USER \'' + . $GLOBALS['dbi']->escapeString($this_user) + . '\'@\'' . $GLOBALS['dbi']->escapeString($this_host) . '\';'; + RelationCleanup::user($this_user); + + if (isset($_POST['drop_users_db'])) { + $queries[] = 'DROP DATABASE IF EXISTS ' + . Util::backquote($this_user) . ';'; + $GLOBALS['reload'] = true; + } + } + return $queries; + } + + /** + * update Message For Reload + * + * @return array + */ + public static function updateMessageForReload() + { + $message = null; + if (isset($_GET['flush_privileges'])) { + $sql_query = 'FLUSH PRIVILEGES;'; + $GLOBALS['dbi']->query($sql_query); + $message = Message::success( + __('The privileges were reloaded successfully.') + ); + } + + if (isset($_GET['validate_username'])) { + $message = Message::success(); + } + + return $message; + } + + /** + * update Data For Queries from queries_for_display + * + * @param array $queries queries array + * @param array|null $queries_for_display queries array for display + * + * @return null + */ + public static function getDataForQueries(array $queries, $queries_for_display) + { + $tmp_count = 0; + foreach ($queries as $sql_query) { + if ($sql_query[0] != '#') { + $GLOBALS['dbi']->query($sql_query); + } + // when there is a query containing a hidden password, take it + // instead of the real query sent + if (isset($queries_for_display[$tmp_count])) { + $queries[$tmp_count] = $queries_for_display[$tmp_count]; + } + $tmp_count++; + } + + return $queries; + } + + /** + * update Data for information: Adds a user + * + * @param string $dbname db name + * @param string $username user name + * @param string $hostname host name + * @param string $password password + * @param bool $is_menuwork is_menuwork set? + * + * @return array + */ + public static function addUser( + $dbname, $username, $hostname, + $password, $is_menuwork + ) { + $_add_user_error = false; + $message = null; + $queries = null; + $queries_for_display = null; + $sql_query = null; + + if (!isset($_POST['adduser_submit']) && !isset($_POST['change_copy'])) { + return array( + $message, $queries, $queries_for_display, $sql_query, $_add_user_error + ); + } + + $sql_query = ''; + if ($_POST['pred_username'] == 'any') { + $username = ''; + } + switch ($_POST['pred_hostname']) { + case 'any': + $hostname = '%'; + break; + case 'localhost': + $hostname = 'localhost'; + break; + case 'hosttable': + $hostname = ''; + break; + case 'thishost': + $_user_name = $GLOBALS['dbi']->fetchValue('SELECT USER()'); + $hostname = mb_substr( + $_user_name, + (mb_strrpos($_user_name, '@') + 1) + ); + unset($_user_name); + break; + } + $sql = "SELECT '1' FROM `mysql`.`user`" + . " WHERE `User` = '" . $GLOBALS['dbi']->escapeString($username) . "'" + . " AND `Host` = '" . $GLOBALS['dbi']->escapeString($hostname) . "';"; + if ($GLOBALS['dbi']->fetchValue($sql) == 1) { + $message = Message::error(__('The user %s already exists!')); + $message->addParam('[em]\'' . $username . '\'@\'' . $hostname . '\'[/em]'); + $_GET['adduser'] = true; + $_add_user_error = true; + + return array( + $message, + $queries, + $queries_for_display, + $sql_query, + $_add_user_error + ); + } + + list( + $create_user_real, $create_user_show, $real_sql_query, $sql_query, + $password_set_real, $password_set_show, + $alter_real_sql_query, + $alter_sql_query + ) = self::getSqlQueriesForDisplayAndAddUser( + $username, $hostname, (isset($password) ? $password : '') + ); + + if (empty($_POST['change_copy'])) { + $_error = false; + + if (isset($create_user_real)) { + if (!$GLOBALS['dbi']->tryQuery($create_user_real)) { + $_error = true; + } + if (isset($password_set_real) && !empty($password_set_real) + && isset($_POST['authentication_plugin']) + ) { + self::setProperPasswordHashing( + $_POST['authentication_plugin'] + ); + if ($GLOBALS['dbi']->tryQuery($password_set_real)) { + $sql_query .= $password_set_show; + } + } + $sql_query = $create_user_show . $sql_query; + } + + list($sql_query, $message) = self::addUserAndCreateDatabase( + $_error, + $real_sql_query, + $sql_query, + $username, + $hostname, + isset($dbname) ? $dbname : null, + $alter_real_sql_query, + $alter_sql_query + ); + if (!empty($_POST['userGroup']) && $is_menuwork) { + self::setUserGroup($GLOBALS['username'], $_POST['userGroup']); + } + + return array( + $message, + $queries, + $queries_for_display, + $sql_query, + $_add_user_error + ); + } + + // Copy the user group while copying a user + $old_usergroup = + isset($_POST['old_usergroup']) ? $_POST['old_usergroup'] : null; + self::setUserGroup($_POST['username'], $old_usergroup); + + if (isset($create_user_real)) { + $queries[] = $create_user_real; + } + $queries[] = $real_sql_query; + + if (isset($password_set_real) && ! empty($password_set_real) + && isset($_POST['authentication_plugin']) + ) { + self::setProperPasswordHashing( + $_POST['authentication_plugin'] + ); + + $queries[] = $password_set_real; + } + // we put the query containing the hidden password in + // $queries_for_display, at the same position occupied + // by the real query in $queries + $tmp_count = count($queries); + if (isset($create_user_real)) { + $queries_for_display[$tmp_count - 2] = $create_user_show; + } + if (isset($password_set_real) && ! empty($password_set_real)) { + $queries_for_display[$tmp_count - 3] = $create_user_show; + $queries_for_display[$tmp_count - 2] = $sql_query; + $queries_for_display[$tmp_count - 1] = $password_set_show; + } else { + $queries_for_display[$tmp_count - 1] = $sql_query; + } + + return array( + $message, $queries, $queries_for_display, $sql_query, $_add_user_error + ); + } + + /** + * Sets proper value of `old_passwords` according to + * the authentication plugin selected + * + * @param string $auth_plugin authentication plugin selected + * + * @return void + */ + public static function setProperPasswordHashing($auth_plugin) + { + // Set the hashing method used by PASSWORD() + // to be of type depending upon $authentication_plugin + if ($auth_plugin == 'sha256_password') { + $GLOBALS['dbi']->tryQuery('SET `old_passwords` = 2;'); + } elseif ($auth_plugin == 'mysql_old_password') { + $GLOBALS['dbi']->tryQuery('SET `old_passwords` = 1;'); + } else { + $GLOBALS['dbi']->tryQuery('SET `old_passwords` = 0;'); + } + } + + /** + * Update DB information: DB, Table, isWildcard + * + * @return array + */ + public static function getDataForDBInfo() + { + $username = null; + $hostname = null; + $dbname = null; + $tablename = null; + $routinename = null; + $dbname_is_wildcard = null; + + if (isset($_REQUEST['username'])) { + $username = $_REQUEST['username']; + } + if (isset($_REQUEST['hostname'])) { + $hostname = $_REQUEST['hostname']; + } + /** + * Checks if a dropdown box has been used for selecting a database / table + */ + if (Core::isValid($_POST['pred_tablename'])) { + $tablename = $_POST['pred_tablename']; + } elseif (Core::isValid($_REQUEST['tablename'])) { + $tablename = $_REQUEST['tablename']; + } else { + unset($tablename); + } + + if (Core::isValid($_POST['pred_routinename'])) { + $routinename = $_POST['pred_routinename']; + } elseif (Core::isValid($_REQUEST['routinename'])) { + $routinename = $_REQUEST['routinename']; + } else { + unset($routinename); + } + + if (isset($_POST['pred_dbname'])) { + $is_valid_pred_dbname = true; + foreach ($_POST['pred_dbname'] as $key => $db_name) { + if (! Core::isValid($db_name)) { + $is_valid_pred_dbname = false; + break; + } + } + } + + if (isset($_REQUEST['dbname'])) { + $is_valid_dbname = true; + if (is_array($_REQUEST['dbname'])) { + foreach ($_REQUEST['dbname'] as $key => $db_name) { + if (! Core::isValid($db_name)) { + $is_valid_dbname = false; + break; + } + } + } else { + if (! Core::isValid($_REQUEST['dbname'])) { + $is_valid_dbname = false; + } + } + } + + if (isset($is_valid_pred_dbname) && $is_valid_pred_dbname) { + $dbname = $_POST['pred_dbname']; + // If dbname contains only one database. + if (count($dbname) == 1) { + $dbname = $dbname[0]; + } + } elseif (isset($is_valid_dbname) && $is_valid_dbname) { + $dbname = $_REQUEST['dbname']; + } else { + unset($dbname); + unset($tablename); + } + + if (isset($dbname)) { + if (is_array($dbname)) { + $db_and_table = $dbname; + foreach ($db_and_table as $key => $db_name) { + $db_and_table[$key] .= '.'; + } + } else { + $unescaped_db = Util::unescapeMysqlWildcards($dbname); + $db_and_table = Util::backquote($unescaped_db) . '.'; + } + if (isset($tablename)) { + $db_and_table .= Util::backquote($tablename); + } else { + if (is_array($db_and_table)) { + foreach ($db_and_table as $key => $db_name) { + $db_and_table[$key] .= '*'; + } + } else { + $db_and_table .= '*'; + } + } + } else { + $db_and_table = '*.*'; + } + + // check if given $dbname is a wildcard or not + if (isset($dbname)) { + //if (preg_match('/\\\\(?:_|%)/i', $dbname)) { + if (! is_array($dbname) && preg_match('/(?'; + + if (isset($_POST['selected_usr'])) { + // export privileges for selected users + $title = __('Privileges'); + + //For removing duplicate entries of users + $_POST['selected_usr'] = array_unique($_POST['selected_usr']); + + foreach ($_POST['selected_usr'] as $export_user) { + $export_username = mb_substr( + $export_user, 0, mb_strpos($export_user, '&') + ); + $export_hostname = mb_substr( + $export_user, mb_strrpos($export_user, ';') + 1 + ); + $export .= '# ' + . sprintf( + __('Privileges for %s'), + '`' . htmlspecialchars($export_username) + . '`@`' . htmlspecialchars($export_hostname) . '`' + ) + . "\n\n"; + $export .= self::getGrants($export_username, $export_hostname) . "\n"; + } + } else { + // export privileges for a single user + $title = __('User') . ' `' . htmlspecialchars($username) + . '`@`' . htmlspecialchars($hostname) . '`'; + $export .= self::getGrants($username, $hostname); + } + // remove trailing whitespace + $export = trim($export); + + $export .= ''; + + return array($title, $export); + } + + /** + * Get HTML for display Add userfieldset + * + * @param string $db the database + * @param string $table the table name + * + * @return string html output + */ + public static function getAddUserHtmlFieldset($db = '', $table = '') + { + if (!$GLOBALS['is_createuser']) { + return ''; + } + $rel_params = array(); + $url_params = array( + 'adduser' => 1 + ); + if (!empty($db)) { + $url_params['dbname'] + = $rel_params['checkprivsdb'] + = $db; + } + if (!empty($table)) { + $url_params['tablename'] + = $rel_params['checkprivstable'] + = $table; + } + + return Template::get('privileges/add_user_fieldset') + ->render( + array( + 'url_params' => $url_params, + 'rel_params' => $rel_params + ) + ); + } + + /** + * Get HTML header for display User's properties + * + * @param boolean $dbname_is_wildcard whether database name is wildcard or not + * @param string $url_dbname url database name that urlencode() string + * @param string $dbname database name + * @param string $username username + * @param string $hostname host name + * @param string $entity_name entity (table or routine) name + * @param string $entity_type optional, type of entity ('table' or 'routine') + * + * @return string $html_output + */ + public static function getHtmlHeaderForUserProperties( + $dbname_is_wildcard, $url_dbname, $dbname, + $username, $hostname, $entity_name, $entity_type='table' + ) { + $html_output = '

      ' . "\n" + . Util::getIcon('b_usredit') + . __('Edit privileges:') . ' ' + . __('User account'); + + if (! empty($dbname)) { + $html_output .= ' \'' . htmlspecialchars($username) + . '\'@\'' . htmlspecialchars($hostname) + . '\'' . "\n"; + + $html_output .= ' - '; + $html_output .= ($dbname_is_wildcard + || is_array($dbname) && count($dbname) > 1) + ? __('Databases') : __('Database'); + if (! empty($entity_name) && $entity_type === 'table') { + $html_output .= ' ' . htmlspecialchars($dbname) + . ''; + + $html_output .= ' - ' . __('Table') + . ' ' . htmlspecialchars($entity_name) . ''; + } elseif (! empty($entity_name)) { + $html_output .= ' ' . htmlspecialchars($dbname) + . ''; + + $html_output .= ' - ' . __('Routine') + . ' ' . htmlspecialchars($entity_name) . ''; + } else { + if (! is_array($dbname)) { + $dbname = array($dbname); + } + $html_output .= ' ' + . htmlspecialchars(implode(', ', $dbname)) + . ''; + } + + } else { + $html_output .= ' \'' . htmlspecialchars($username) + . '\'@\'' . htmlspecialchars($hostname) + . '\'' . "\n"; + + } + $html_output .= '

      ' . "\n"; + $cur_user = $GLOBALS['dbi']->getCurrentUser(); + $user = $username . '@' . $hostname; + // Add a short notice for the user + // to remind him that he is editing his own privileges + if ($user === $cur_user) { + $html_output .= Message::notice( + __( + 'Note: You are attempting to edit privileges of the ' + . 'user with which you are currently logged in.' + ) + )->getDisplay(); + } + return $html_output; + } + + /** + * Get HTML snippet for display user overview page + * + * @param string $pmaThemeImage a image source link + * @param string $text_dir text directory + * + * @return string $html_output + */ + public static function getHtmlForUserOverview($pmaThemeImage, $text_dir) + { + $html_output = '

      ' . "\n" + . Util::getIcon('b_usrlist') + . __('User accounts overview') . "\n" + . '

      ' . "\n"; + + $password_column = 'Password'; + $server_type = Util::getServerType(); + $serverVersion = $GLOBALS['dbi']->getVersion(); + if (($server_type == 'MySQL' || $server_type == 'Percona Server') + && $serverVersion >= 50706 + ) { + $password_column = 'authentication_string'; + } + // $sql_query is for the initial-filtered, + // $sql_query_all is for counting the total no. of users + + $sql_query = $sql_query_all = 'SELECT *,' . + " IF(`" . $password_column . "` = _latin1 '', 'N', 'Y') AS 'Password'" . + ' FROM `mysql`.`user`'; + + $sql_query .= (isset($_GET['initial']) + ? self::rangeOfUsers($_GET['initial']) + : ''); + + $sql_query .= ' ORDER BY `User` ASC, `Host` ASC;'; + $sql_query_all .= ' ;'; + + $res = $GLOBALS['dbi']->tryQuery( + $sql_query, + DatabaseInterface::CONNECT_USER, + DatabaseInterface::QUERY_STORE + ); + $res_all = $GLOBALS['dbi']->tryQuery( + $sql_query_all, + DatabaseInterface::CONNECT_USER, + DatabaseInterface::QUERY_STORE + ); + + if (! $res) { + // the query failed! This may have two reasons: + // - the user does not have enough privileges + // - the privilege tables use a structure of an earlier version. + // so let's try a more simple query + + $GLOBALS['dbi']->freeResult($res); + $GLOBALS['dbi']->freeResult($res_all); + $sql_query = 'SELECT * FROM `mysql`.`user`'; + $res = $GLOBALS['dbi']->tryQuery( + $sql_query, + DatabaseInterface::CONNECT_USER, + DatabaseInterface::QUERY_STORE + ); + + if (! $res) { + $html_output .= self::getHtmlForViewUsersError(); + $html_output .= self::getAddUserHtmlFieldset(); + } else { + // This message is hardcoded because I will replace it by + // a automatic repair feature soon. + $raw = 'Your privilege table structure seems to be older than' + . ' this MySQL version!
      ' + . 'Please run the mysql_upgrade command' + . ' that should be included in your MySQL server distribution' + . ' to solve this problem!'; + $html_output .= Message::rawError($raw)->getDisplay(); + } + $GLOBALS['dbi']->freeResult($res); + } else { + $db_rights = self::getDbRightsForUserOverview(); + // for all initials, even non A-Z + $array_initials = array(); + + foreach ($db_rights as $right) { + foreach ($right as $account) { + if (empty($account['User']) && $account['Host'] == 'localhost') { + $html_output .= Message::notice( + __( + 'A user account allowing any user from localhost to ' + . 'connect is present. This will prevent other users ' + . 'from connecting if the host part of their account ' + . 'allows a connection from any (%) host.' + ) + . Util::showMySQLDocu('problems-connecting') + )->getDisplay(); + break 2; + } + } + } + + /** + * Displays the initials + * Also not necessary if there is less than 20 privileges + */ + if ($GLOBALS['dbi']->numRows($res_all) > 20) { + $html_output .= self::getHtmlForInitials($array_initials); + } + + /** + * Display the user overview + * (if less than 50 users, display them immediately) + */ + if (isset($_GET['initial']) + || isset($_GET['showall']) + || $GLOBALS['dbi']->numRows($res) < 50 + ) { + $html_output .= self::getUsersOverview( + $res, $db_rights, $pmaThemeImage, $text_dir + ); + } else { + $html_output .= self::getAddUserHtmlFieldset(); + } // end if (display overview) + + $response = Response::getInstance(); + if (! $response->isAjax() + || ! empty($_REQUEST['ajax_page_request']) + ) { + if ($GLOBALS['is_reload_priv']) { + $flushnote = new Message( + __( + 'Note: phpMyAdmin gets the users’ privileges directly ' + . 'from MySQL’s privilege tables. The content of these ' + . 'tables may differ from the privileges the server uses, ' + . 'if they have been changed manually. In this case, ' + . 'you should %sreload the privileges%s before you continue.' + ), + Message::NOTICE + ); + $flushnote->addParamHtml( + '' + ); + $flushnote->addParamHtml(''); + } else { + $flushnote = new Message( + __( + 'Note: phpMyAdmin gets the users’ privileges directly ' + . 'from MySQL’s privilege tables. The content of these ' + . 'tables may differ from the privileges the server uses, ' + . 'if they have been changed manually. In this case, ' + . 'the privileges have to be reloaded but currently, you ' + . 'don\'t have the RELOAD privilege.' + ) + . Util::showMySQLDocu( + 'privileges-provided', + false, + 'priv_reload' + ), + Message::NOTICE + ); + } + $html_output .= $flushnote->getDisplay(); + } + } + + return $html_output; + } + + /** + * Get HTML snippet for display user properties + * + * @param boolean $dbname_is_wildcard whether database name is wildcard or not + * @param string $url_dbname url database name that urlencode() string + * @param string $username username + * @param string $hostname host name + * @param string $dbname database name + * @param string $tablename table name + * + * @return string $html_output + */ + public static function getHtmlForUserProperties($dbname_is_wildcard, $url_dbname, + $username, $hostname, $dbname, $tablename + ) { + $html_output = '
      '; + $html_output .= self::getHtmlHeaderForUserProperties( + $dbname_is_wildcard, $url_dbname, $dbname, $username, $hostname, + $tablename, 'table' + ); + + $sql = "SELECT '1' FROM `mysql`.`user`" + . " WHERE `User` = '" . $GLOBALS['dbi']->escapeString($username) . "'" + . " AND `Host` = '" . $GLOBALS['dbi']->escapeString($hostname) . "';"; + + $user_does_not_exists = (bool) ! $GLOBALS['dbi']->fetchValue($sql); + + if ($user_does_not_exists) { + $html_output .= Message::error( + __('The selected user was not found in the privilege table.') + )->getDisplay(); + $html_output .= self::getHtmlForLoginInformationFields(); + } + + $_params = array( + 'username' => $username, + 'hostname' => $hostname, + ); + if (! is_array($dbname) && strlen($dbname) > 0) { + $_params['dbname'] = $dbname; + if (strlen($tablename) > 0) { + $_params['tablename'] = $tablename; + } + } else { + $_params['dbname'] = $dbname; + } + + $html_output .= '' . "\n"; + + if (! is_array($dbname) && strlen($tablename) === 0 + && empty($dbname_is_wildcard) + ) { + // no table name was given, display all table specific rights + // but only if $dbname contains no wildcards + if (strlen($dbname) === 0) { + $html_output .= self::getHtmlForAllTableSpecificRights( + $username, $hostname, 'database' + ); + } else { + // unescape wildcards in dbname at table level + $unescaped_db = Util::unescapeMysqlWildcards($dbname); + + $html_output .= self::getHtmlForAllTableSpecificRights( + $username, $hostname, 'table', $unescaped_db + ); + $html_output .= self::getHtmlForAllTableSpecificRights( + $username, $hostname, 'routine', $unescaped_db + ); + } + } + + // Provide a line with links to the relevant database and table + if (! is_array($dbname) && strlen($dbname) > 0 && empty($dbname_is_wildcard)) { + $html_output .= self::getLinkToDbAndTable($url_dbname, $dbname, $tablename); + + } + + if (! is_array($dbname) && strlen($dbname) === 0 && ! $user_does_not_exists) { + //change login information + $html_output .= ChangePassword::getHtml( + 'edit_other', + $username, + $hostname + ); + $html_output .= self::getChangeLoginInformationHtmlForm($username, $hostname); + } + $html_output .= '
      '; + + return $html_output; + } + + /** + * Get queries for Table privileges to change or copy user + * + * @param string $user_host_condition user host condition to + * select relevant table privileges + * @param array $queries queries array + * @param string $username username + * @param string $hostname host name + * + * @return array $queries + */ + public static function getTablePrivsQueriesForChangeOrCopyUser($user_host_condition, + array $queries, $username, $hostname + ) { + $res = $GLOBALS['dbi']->query( + 'SELECT `Db`, `Table_name`, `Table_priv` FROM `mysql`.`tables_priv`' + . $user_host_condition, + DatabaseInterface::CONNECT_USER, + DatabaseInterface::QUERY_STORE + ); + while ($row = $GLOBALS['dbi']->fetchAssoc($res)) { + + $res2 = $GLOBALS['dbi']->query( + 'SELECT `Column_name`, `Column_priv`' + . ' FROM `mysql`.`columns_priv`' + . ' WHERE `User`' + . ' = \'' . $GLOBALS['dbi']->escapeString($_POST['old_username']) . "'" + . ' AND `Host`' + . ' = \'' . $GLOBALS['dbi']->escapeString($_POST['old_username']) . '\'' + . ' AND `Db`' + . ' = \'' . $GLOBALS['dbi']->escapeString($row['Db']) . "'" + . ' AND `Table_name`' + . ' = \'' . $GLOBALS['dbi']->escapeString($row['Table_name']) . "'" + . ';', + DatabaseInterface::CONNECT_USER, + DatabaseInterface::QUERY_STORE + ); + + $tmp_privs1 = self::extractPrivInfo($row); + $tmp_privs2 = array( + 'Select' => array(), + 'Insert' => array(), + 'Update' => array(), + 'References' => array() + ); + + while ($row2 = $GLOBALS['dbi']->fetchAssoc($res2)) { + $tmp_array = explode(',', $row2['Column_priv']); + if (in_array('Select', $tmp_array)) { + $tmp_privs2['Select'][] = $row2['Column_name']; + } + if (in_array('Insert', $tmp_array)) { + $tmp_privs2['Insert'][] = $row2['Column_name']; + } + if (in_array('Update', $tmp_array)) { + $tmp_privs2['Update'][] = $row2['Column_name']; + } + if (in_array('References', $tmp_array)) { + $tmp_privs2['References'][] = $row2['Column_name']; + } + } + if (count($tmp_privs2['Select']) > 0 && ! in_array('SELECT', $tmp_privs1)) { + $tmp_privs1[] = 'SELECT (`' . join('`, `', $tmp_privs2['Select']) . '`)'; + } + if (count($tmp_privs2['Insert']) > 0 && ! in_array('INSERT', $tmp_privs1)) { + $tmp_privs1[] = 'INSERT (`' . join('`, `', $tmp_privs2['Insert']) . '`)'; + } + if (count($tmp_privs2['Update']) > 0 && ! in_array('UPDATE', $tmp_privs1)) { + $tmp_privs1[] = 'UPDATE (`' . join('`, `', $tmp_privs2['Update']) . '`)'; + } + if (count($tmp_privs2['References']) > 0 + && ! in_array('REFERENCES', $tmp_privs1) + ) { + $tmp_privs1[] + = 'REFERENCES (`' . join('`, `', $tmp_privs2['References']) . '`)'; + } + + $queries[] = 'GRANT ' . join(', ', $tmp_privs1) + . ' ON ' . Util::backquote($row['Db']) . '.' + . Util::backquote($row['Table_name']) + . ' TO \'' . $GLOBALS['dbi']->escapeString($username) + . '\'@\'' . $GLOBALS['dbi']->escapeString($hostname) . '\'' + . (in_array('Grant', explode(',', $row['Table_priv'])) + ? ' WITH GRANT OPTION;' + : ';'); + } + return $queries; + } + + /** + * Get queries for database specific privileges for change or copy user + * + * @param array $queries queries array with string + * @param string $username username + * @param string $hostname host name + * + * @return array $queries + */ + public static function getDbSpecificPrivsQueriesForChangeOrCopyUser( + array $queries, $username, $hostname + ) { + $user_host_condition = ' WHERE `User`' + . ' = \'' . $GLOBALS['dbi']->escapeString($_POST['old_username']) . "'" + . ' AND `Host`' + . ' = \'' . $GLOBALS['dbi']->escapeString($_POST['old_hostname']) . '\';'; + + $res = $GLOBALS['dbi']->query( + 'SELECT * FROM `mysql`.`db`' . $user_host_condition + ); + + while ($row = $GLOBALS['dbi']->fetchAssoc($res)) { + $queries[] = 'GRANT ' . join(', ', self::extractPrivInfo($row)) + . ' ON ' . Util::backquote($row['Db']) . '.*' + . ' TO \'' . $GLOBALS['dbi']->escapeString($username) + . '\'@\'' . $GLOBALS['dbi']->escapeString($hostname) . '\'' + . ($row['Grant_priv'] == 'Y' ? ' WITH GRANT OPTION;' : ';'); + } + $GLOBALS['dbi']->freeResult($res); + + $queries = self::getTablePrivsQueriesForChangeOrCopyUser( + $user_host_condition, $queries, $username, $hostname + ); + + return $queries; + } + + /** + * Prepares queries for adding users and + * also create database and return query and message + * + * @param boolean $_error whether user create or not + * @param string $real_sql_query SQL query for add a user + * @param string $sql_query SQL query to be displayed + * @param string $username username + * @param string $hostname host name + * @param string $dbname database name + * @param string $alter_real_sql_query SQL query for ALTER USER + * @param string $alter_sql_query SQL query for ALTER USER to be displayed + * + * @return array $sql_query, $message + */ + public static function addUserAndCreateDatabase( + $_error, + $real_sql_query, + $sql_query, + $username, + $hostname, + $dbname, + $alter_real_sql_query, + $alter_sql_query + ) { + if ($_error || (!empty($real_sql_query) + && !$GLOBALS['dbi']->tryQuery($real_sql_query)) + ) { + $_POST['createdb-1'] = $_POST['createdb-2'] + = $_POST['createdb-3'] = null; + $message = Message::rawError($GLOBALS['dbi']->getError()); + } elseif ($alter_real_sql_query !== '' && !$GLOBALS['dbi']->tryQuery($alter_real_sql_query)) { + $_POST['createdb-1'] = $_POST['createdb-2'] + = $_POST['createdb-3'] = null; + $message = Message::rawError($GLOBALS['dbi']->getError()); + } else { + $sql_query .= $alter_sql_query; + $message = Message::success(__('You have added a new user.')); + } + + if (isset($_POST['createdb-1'])) { + // Create database with same name and grant all privileges + $q = 'CREATE DATABASE IF NOT EXISTS ' + . Util::backquote( + $GLOBALS['dbi']->escapeString($username) + ) . ';'; + $sql_query .= $q; + if (! $GLOBALS['dbi']->tryQuery($q)) { + $message = Message::rawError($GLOBALS['dbi']->getError()); + } + + /** + * Reload the navigation + */ + $GLOBALS['reload'] = true; + $GLOBALS['db'] = $username; + + $q = 'GRANT ALL PRIVILEGES ON ' + . Util::backquote( + Util::escapeMysqlWildcards( + $GLOBALS['dbi']->escapeString($username) + ) + ) . '.* TO \'' + . $GLOBALS['dbi']->escapeString($username) + . '\'@\'' . $GLOBALS['dbi']->escapeString($hostname) . '\';'; + $sql_query .= $q; + if (! $GLOBALS['dbi']->tryQuery($q)) { + $message = Message::rawError($GLOBALS['dbi']->getError()); + } + } + + if (isset($_POST['createdb-2'])) { + // Grant all privileges on wildcard name (username\_%) + $q = 'GRANT ALL PRIVILEGES ON ' + . Util::backquote( + Util::escapeMysqlWildcards( + $GLOBALS['dbi']->escapeString($username) + ) . '\_%' + ) . '.* TO \'' + . $GLOBALS['dbi']->escapeString($username) + . '\'@\'' . $GLOBALS['dbi']->escapeString($hostname) . '\';'; + $sql_query .= $q; + if (! $GLOBALS['dbi']->tryQuery($q)) { + $message = Message::rawError($GLOBALS['dbi']->getError()); + } + } + + if (isset($_POST['createdb-3'])) { + // Grant all privileges on the specified database to the new user + $q = 'GRANT ALL PRIVILEGES ON ' + . Util::backquote( + $GLOBALS['dbi']->escapeString($dbname) + ) . '.* TO \'' + . $GLOBALS['dbi']->escapeString($username) + . '\'@\'' . $GLOBALS['dbi']->escapeString($hostname) . '\';'; + $sql_query .= $q; + if (! $GLOBALS['dbi']->tryQuery($q)) { + $message = Message::rawError($GLOBALS['dbi']->getError()); + } + } + return array($sql_query, $message); + } + + /** + * Get the hashed string for password + * + * @param string $password password + * + * @return string $hashedPassword + */ + public static function getHashedPassword($password) + { + $password = $GLOBALS['dbi']->escapeString($password); + $result = $GLOBALS['dbi']->fetchSingleRow( + "SELECT PASSWORD('" . $password . "') AS `password`;" + ); + + $hashedPassword = $result['password']; + + return $hashedPassword; + } + + /** + * Check if MariaDB's 'simple_password_check' + * OR 'cracklib_password_check' is ACTIVE + * + * @return boolean if atleast one of the plugins is ACTIVE + */ + public static function checkIfMariaDBPwdCheckPluginActive() + { + $serverVersion = $GLOBALS['dbi']->getVersion(); + if (!(Util::getServerType() == 'MariaDB' && $serverVersion >= 100002)) { + return false; + } + + $result = $GLOBALS['dbi']->tryQuery( + 'SHOW PLUGINS SONAME LIKE \'%_password_check%\'' + ); + + /* Plugins are not working, for example directory does not exists */ + if ($result === false) { + return false; + } + + while ($row = $GLOBALS['dbi']->fetchAssoc($result)) { + if ($row['Status'] === 'ACTIVE') { + return true; + } + } + + return false; + } + + + /** + * Get SQL queries for Display and Add user + * + * @param string $username username + * @param string $hostname host name + * @param string $password password + * + * @return array ($create_user_real, $create_user_show, $real_sql_query, $sql_query + * $password_set_real, $password_set_show, $alter_real_sql_query, $alter_sql_query) + */ + public static function getSqlQueriesForDisplayAndAddUser($username, $hostname, $password) + { + $slashedUsername = $GLOBALS['dbi']->escapeString($username); + $slashedHostname = $GLOBALS['dbi']->escapeString($hostname); + $slashedPassword = $GLOBALS['dbi']->escapeString($password); + $serverType = Util::getServerType(); + $serverVersion = $GLOBALS['dbi']->getVersion(); + + $create_user_stmt = sprintf( + 'CREATE USER \'%s\'@\'%s\'', + $slashedUsername, + $slashedHostname + ); + $isMariaDBPwdPluginActive = self::checkIfMariaDBPwdCheckPluginActive(); + + // See https://github.com/phpmyadmin/phpmyadmin/pull/11560#issuecomment-147158219 + // for details regarding details of syntax usage for various versions + + // 'IDENTIFIED WITH auth_plugin' + // is supported by MySQL 5.5.7+ + if (($serverType == 'MySQL' || $serverType == 'Percona Server') + && $serverVersion >= 50507 + && isset($_POST['authentication_plugin']) + ) { + $create_user_stmt .= ' IDENTIFIED WITH ' + . $_POST['authentication_plugin']; + } + + // 'IDENTIFIED VIA auth_plugin' + // is supported by MariaDB 5.2+ + if ($serverType == 'MariaDB' + && $serverVersion >= 50200 + && isset($_POST['authentication_plugin']) + && ! $isMariaDBPwdPluginActive + ) { + $create_user_stmt .= ' IDENTIFIED VIA ' + . $_POST['authentication_plugin']; + } + + $create_user_real = $create_user_show = $create_user_stmt; + + $password_set_stmt = 'SET PASSWORD FOR \'%s\'@\'%s\' = \'%s\''; + $password_set_show = sprintf( + $password_set_stmt, + $slashedUsername, + $slashedHostname, + '***' + ); + + $sql_query_stmt = sprintf( + 'GRANT %s ON *.* TO \'%s\'@\'%s\'', + join(', ', self::extractPrivInfo()), + $slashedUsername, + $slashedHostname + ); + $real_sql_query = $sql_query = $sql_query_stmt; + + // Set the proper hashing method + if (isset($_POST['authentication_plugin'])) { + self::setProperPasswordHashing( + $_POST['authentication_plugin'] + ); + } + + // Use 'CREATE USER ... WITH ... AS ..' syntax for + // newer MySQL versions + // and 'CREATE USER ... VIA .. USING ..' syntax for + // newer MariaDB versions + if ((($serverType == 'MySQL' || $serverType == 'Percona Server') + && $serverVersion >= 50706) + || ($serverType == 'MariaDB' + && $serverVersion >= 50200) + ) { + $password_set_real = null; + + // Required for binding '%' with '%s' + $create_user_stmt = str_replace( + '%', '%%', $create_user_stmt + ); + + // MariaDB uses 'USING' whereas MySQL uses 'AS' + // but MariaDB with validation plugin needs cleartext password + if ($serverType == 'MariaDB' + && ! $isMariaDBPwdPluginActive + ) { + $create_user_stmt .= ' USING \'%s\''; + } elseif ($serverType == 'MariaDB') { + $create_user_stmt .= ' IDENTIFIED BY \'%s\''; + } elseif (($serverType == 'MySQL' || $serverType == 'Percona Server') && $serverVersion >= 80011) { + $create_user_stmt .= ' BY \'%s\''; + } else { + $create_user_stmt .= ' AS \'%s\''; + } + + if ($_POST['pred_password'] == 'keep') { + $create_user_real = sprintf( + $create_user_stmt, + $slashedPassword + ); + $create_user_show = sprintf( + $create_user_stmt, + '***' + ); + } elseif ($_POST['pred_password'] == 'none') { + $create_user_real = sprintf( + $create_user_stmt, + null + ); + $create_user_show = sprintf( + $create_user_stmt, + '***' + ); + } else { + if (! (($serverType == 'MariaDB' && $isMariaDBPwdPluginActive) + || ($serverType == 'MySQL' || $serverType == 'Percona Server') && $serverVersion >= 80011)) { + $hashedPassword = self::getHashedPassword($_POST['pma_pw']); + } else { + // MariaDB with validation plugin needs cleartext password + $hashedPassword = $_POST['pma_pw']; + } + $create_user_real = sprintf( + $create_user_stmt, + $hashedPassword + ); + $create_user_show = sprintf( + $create_user_stmt, + '***' + ); + } + } else { + // Use 'SET PASSWORD' syntax for pre-5.7.6 MySQL versions + // and pre-5.2.0 MariaDB versions + if ($_POST['pred_password'] == 'keep') { + $password_set_real = sprintf( + $password_set_stmt, + $slashedUsername, + $slashedHostname, + $slashedPassword + ); + } elseif ($_POST['pred_password'] == 'none') { + $password_set_real = sprintf( + $password_set_stmt, + $slashedUsername, + $slashedHostname, + null + ); + } else { + $hashedPassword = self::getHashedPassword($_POST['pma_pw']); + $password_set_real = sprintf( + $password_set_stmt, + $slashedUsername, + $slashedHostname, + $hashedPassword + ); + } + } + + $alter_real_sql_query = ''; + $alter_sql_query = ''; + if (($serverType == 'MySQL' || $serverType == 'Percona Server') && $serverVersion >= 80011) { + $sql_query_stmt = ''; + if ((isset($_POST['Grant_priv']) && $_POST['Grant_priv'] == 'Y') + || (isset($GLOBALS['Grant_priv']) && $GLOBALS['Grant_priv'] == 'Y') + ) { + $sql_query_stmt = ' WITH GRANT OPTION'; + } + $real_sql_query .= $sql_query_stmt; + $sql_query .= $sql_query_stmt; + + $alter_sql_query_stmt = sprintf( + 'ALTER USER \'%s\'@\'%s\'', + $slashedUsername, + $slashedHostname + ); + $alter_real_sql_query = $alter_sql_query_stmt; + $alter_sql_query = $alter_sql_query_stmt; + } + + // add REQUIRE clause + $require_clause = self::getRequireClause(); + $with_clause = self::getWithClauseForAddUserAndUpdatePrivs(); + + if (($serverType == 'MySQL' || $serverType == 'Percona Server') && $serverVersion >= 80011) { + $alter_real_sql_query .= $require_clause; + $alter_sql_query .= $require_clause; + $alter_real_sql_query .= $with_clause; + $alter_sql_query .= $with_clause; + } else { + $real_sql_query .= $require_clause; + $sql_query .= $require_clause; + $real_sql_query .= $with_clause; + $sql_query .= $with_clause; + } + + if (isset($create_user_real)) { + $create_user_real .= ';'; + $create_user_show .= ';'; + } + if ($alter_real_sql_query !== '') { + $alter_real_sql_query .= ';'; + $alter_sql_query .= ';'; + } + $real_sql_query .= ';'; + $sql_query .= ';'; + // No Global GRANT_OPTION privilege + if (!$GLOBALS['is_grantuser']) { + $real_sql_query = ''; + $sql_query = ''; + } + + // Use 'SET PASSWORD' for pre-5.7.6 MySQL versions + // and pre-5.2.0 MariaDB + if (($serverType == 'MySQL' + && $serverVersion >= 50706) + || ($serverType == 'MariaDB' + && $serverVersion >= 50200) + ) { + $password_set_real = null; + $password_set_show = null; + } else { + if ($password_set_real !== null) { + $password_set_real .= ";"; + } + $password_set_show .= ";"; + } + + return array( + $create_user_real, + $create_user_show, + $real_sql_query, + $sql_query, + $password_set_real, + $password_set_show, + $alter_real_sql_query, + $alter_sql_query + ); + } + + /** + * Returns the type ('PROCEDURE' or 'FUNCTION') of the routine + * + * @param string $dbname database + * @param string $routineName routine + * + * @return string type + */ + public static function getRoutineType($dbname, $routineName) + { + $routineData = $GLOBALS['dbi']->getRoutines($dbname); + + foreach ($routineData as $routine) { + if ($routine['name'] === $routineName) { + return $routine['type']; + } + } + return ''; + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Server/Select.php b/php/apps/phpmyadmin49/libraries/classes/Server/Select.php new file mode 100644 index 00000000..b5b434de --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Server/Select.php @@ -0,0 +1,125 @@ +'; + + if (! $omit_fieldset) { + $retval .= '
      '; + } + + $retval .= Url::getHiddenFields(array()); + $retval .= ' '; + + $retval .= ''; + if (! $omit_fieldset) { + $retval .= '
      '; + } + $retval .= ''; + } elseif ($list) { + $retval .= ''; + } + + return $retval; + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Server/Status.php b/php/apps/phpmyadmin49/libraries/classes/Server/Status.php new file mode 100644 index 00000000..d6705435 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Server/Status.php @@ -0,0 +1,335 @@ +fetchValue( + 'SELECT UNIX_TIMESTAMP() - ' . $serverStatusData->status['Uptime'] + ); + + $retval = '

      '; + $bytes_received = $serverStatusData->status['Bytes_received']; + $bytes_sent = $serverStatusData->status['Bytes_sent']; + $retval .= sprintf( + __('Network traffic since startup: %s'), + implode( + ' ', + Util::formatByteDown( + $bytes_received + $bytes_sent, + 3, + 1 + ) + ) + ); + $retval .= '

      '; + $retval .= '

      '; + $retval .= sprintf( + __('This MySQL server has been running for %1$s. It started up on %2$s.'), + Util::timespanFormat($serverStatusData->status['Uptime']), + Util::localisedDate($start_time) + ) . "\n"; + $retval .= '

      '; + + return $retval; + } + + /** + * Returns HTML to display replication information + * + * @return string HTML on replication + */ + public static function getHtmlForReplicationInfo() + { + $retval = '

      '; + if ($GLOBALS['replication_info']['master']['status'] + && $GLOBALS['replication_info']['slave']['status'] + ) { + $retval .= __( + 'This MySQL server works as master and ' + . 'slave in replication process.' + ); + } elseif ($GLOBALS['replication_info']['master']['status']) { + $retval .= __( + 'This MySQL server works as master ' + . 'in replication process.' + ); + } elseif ($GLOBALS['replication_info']['slave']['status']) { + $retval .= __( + 'This MySQL server works as slave ' + . 'in replication process.' + ); + } + $retval .= '

      '; + + /* + * if the server works as master or slave in replication process, + * display useful information + */ + $retval .= '
      '; + $retval .= '

      '; + $retval .= __('Replication status'); + $retval .= '

      '; + foreach ($GLOBALS['replication_types'] as $type) { + if (isset($GLOBALS['replication_info'][$type]['status']) + && $GLOBALS['replication_info'][$type]['status'] + ) { + $retval .= ReplicationGui::getHtmlForReplicationStatusTable($type); + } + } + + return $retval; + } + + /** + * Prints server state traffic information + * + * @param Data $serverStatusData Server status data + * + * @return string + */ + public static function getHtmlForServerStateTraffic(Data $serverStatusData) + { + $hour_factor = 3600 / $serverStatusData->status['Uptime']; + $retval = ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= '
      '; + $retval .= __('Traffic') . ' '; + $retval .= Util::showHint( + __( + 'On a busy server, the byte counters may overrun, so those statistics ' + . 'as reported by the MySQL server may be incorrect.' + ) + ); + $retval .= '#ø ' . __('per hour') . '
      ' . __('Received') . ''; + $retval .= implode( + ' ', + Util::formatByteDown( + $serverStatusData->status['Bytes_received'], 3, 1 + ) + ); + $retval .= ''; + $retval .= implode( + ' ', + Util::formatByteDown( + $serverStatusData->status['Bytes_received'] * $hour_factor, 3, 1 + ) + ); + $retval .= '
      ' . __('Sent') . ''; + $retval .= implode( + ' ', + Util::formatByteDown( + $serverStatusData->status['Bytes_sent'], 3, 1 + ) + ); + $retval .= ''; + $retval .= implode( + ' ', + Util::formatByteDown( + $serverStatusData->status['Bytes_sent'] * $hour_factor, 3, 1 + ) + ); + $retval .= '
      ' . __('Total') . ''; + $bytes_received = $serverStatusData->status['Bytes_received']; + $bytes_sent = $serverStatusData->status['Bytes_sent']; + $retval .= implode( + ' ', + Util::formatByteDown( + $bytes_received + $bytes_sent, 3, 1 + ) + ); + $retval .= ''; + $bytes_received = $serverStatusData->status['Bytes_received']; + $bytes_sent = $serverStatusData->status['Bytes_sent']; + $retval .= implode( + ' ', + Util::formatByteDown( + ($bytes_received + $bytes_sent) * $hour_factor, 3, 1 + ) + ); + $retval .= '
      '; + return $retval; + } + + /** + * Prints server state connections information + * + * @param Data $serverStatusData Server status data + * + * @return string + */ + public static function getHtmlForServerStateConnections(Data $serverStatusData) + { + $hour_factor = 3600 / $serverStatusData->status['Uptime']; + $retval = ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= '
      ' . __('Connections') . '#ø ' . __('per hour') . '%
      ' . __('Max. concurrent connections') . ''; + $retval .= Util::formatNumber( + $serverStatusData->status['Max_used_connections'], 0 + ); + $retval .= '--- ---
      ' . __('Failed attempts') . ''; + $retval .= Util::formatNumber( + $serverStatusData->status['Aborted_connects'], 4, 1, true + ); + $retval .= ''; + $retval .= Util::formatNumber( + $serverStatusData->status['Aborted_connects'] * $hour_factor, 4, 2, true + ); + $retval .= ''; + if ($serverStatusData->status['Connections'] > 0) { + $abortNum = $serverStatusData->status['Aborted_connects']; + $connectNum = $serverStatusData->status['Connections']; + + $retval .= Util::formatNumber( + $abortNum * 100 / $connectNum, + 0, 2, true + ); + $retval .= '%'; + } else { + $retval .= '--- '; + } + $retval .= '
      ' . __('Aborted') . ''; + $retval .= Util::formatNumber( + $serverStatusData->status['Aborted_clients'], 4, 1, true + ); + $retval .= ''; + $retval .= Util::formatNumber( + $serverStatusData->status['Aborted_clients'] * $hour_factor, 4, 2, true + ); + $retval .= ''; + if ($serverStatusData->status['Connections'] > 0) { + $abortNum = $serverStatusData->status['Aborted_clients']; + $connectNum = $serverStatusData->status['Connections']; + + $retval .= Util::formatNumber( + $abortNum * 100 / $connectNum, + 0, 2, true + ); + $retval .= '%'; + } else { + $retval .= '--- '; + } + $retval .= '
      ' . __('Total') . ''; + $retval .= Util::formatNumber( + $serverStatusData->status['Connections'], 4, 0 + ); + $retval .= ''; + $retval .= Util::formatNumber( + $serverStatusData->status['Connections'] * $hour_factor, 4, 2 + ); + $retval .= ''; + $retval .= Util::formatNumber(100, 0, 2); + $retval .= '%
      '; + + return $retval; + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Server/Status/Advisor.php b/php/apps/phpmyadmin49/libraries/classes/Server/Status/Advisor.php new file mode 100644 index 00000000..2356c710 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Server/Status/Advisor.php @@ -0,0 +1,73 @@ +'; + $output .= Util::getIcon('b_help', __('Instructions')); + $output .= ''; + $output .= '
      '; + $output .= '
      '; + $output .= '

      '; + $output .= __( + 'The Advisor system can provide recommendations ' + . 'on server variables by analyzing the server status variables.' + ); + $output .= '

      '; + $output .= '

      '; + $output .= __( + 'Do note however that this system provides recommendations ' + . 'based on simple calculations and by rule of thumb which may ' + . 'not necessarily apply to your system.' + ); + $output .= '

      '; + $output .= '

      '; + $output .= __( + 'Prior to changing any of the configuration, be sure to know ' + . 'what you are changing (by reading the documentation) and how ' + . 'to undo the change. Wrong tuning can have a very negative ' + . 'effect on performance.' + ); + $output .= '

      '; + $output .= '

      '; + $output .= __( + 'The best way to tune your system would be to change only one ' + . 'setting at a time, observe or benchmark your database, and undo ' + . 'the change if there was no clearly measurable improvement.' + ); + $output .= '

      '; + $output .= '
      '; + $output .= '
      '; + $advisor = new PmaAdvisor($GLOBALS['dbi'], new ExpressionLanguage()); + $output .= htmlspecialchars( + json_encode( + $advisor->run() + ) + ); + $output .= '
      '; + + return $output; + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Server/Status/Data.php b/php/apps/phpmyadmin49/libraries/classes/Server/Status/Data.php new file mode 100644 index 00000000..0192657d --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Server/Status/Data.php @@ -0,0 +1,504 @@ + section + // variable names match when they begin with the given string + + 'Com_' => 'com', + 'Innodb_' => 'innodb', + 'Ndb_' => 'ndb', + 'Handler_' => 'handler', + 'Qcache_' => 'qcache', + 'Threads_' => 'threads', + 'Slow_launch_threads' => 'threads', + + 'Binlog_cache_' => 'binlog_cache', + 'Created_tmp_' => 'created_tmp', + 'Key_' => 'key', + + 'Delayed_' => 'delayed', + 'Not_flushed_delayed_rows' => 'delayed', + + 'Flush_commands' => 'query', + 'Last_query_cost' => 'query', + 'Slow_queries' => 'query', + 'Queries' => 'query', + 'Prepared_stmt_count' => 'query', + + 'Select_' => 'select', + 'Sort_' => 'sort', + + 'Open_tables' => 'table', + 'Opened_tables' => 'table', + 'Open_table_definitions' => 'table', + 'Opened_table_definitions' => 'table', + 'Table_locks_' => 'table', + + 'Rpl_status' => 'repl', + 'Slave_' => 'repl', + + 'Tc_' => 'tc', + + 'Ssl_' => 'ssl', + + 'Open_files' => 'files', + 'Open_streams' => 'files', + 'Opened_files' => 'files', + ); + } + + /** + * Gets the sections for constructor + * + * @return array + */ + private function _getSections() + { + return array( + // section => section name (description) + 'com' => 'Com', + 'query' => __('SQL query'), + 'innodb' => 'InnoDB', + 'ndb' => 'NDB', + 'handler' => __('Handler'), + 'qcache' => __('Query cache'), + 'threads' => __('Threads'), + 'binlog_cache' => __('Binary log'), + 'created_tmp' => __('Temporary data'), + 'delayed' => __('Delayed inserts'), + 'key' => __('Key cache'), + 'select' => __('Joins'), + 'repl' => __('Replication'), + 'sort' => __('Sorting'), + 'table' => __('Tables'), + 'tc' => __('Transaction coordinator'), + 'files' => __('Files'), + 'ssl' => 'SSL', + 'other' => __('Other') + ); + } + + /** + * Gets the links for constructor + * + * @return array + */ + private function _getLinks() + { + $links = array(); + // variable or section name => (name => url) + + $links['table'][__('Flush (close) all tables')] = [ + 'url' => $this->selfUrl, + 'params' => Url::getCommon(['flush' => 'TABLES'], ''), + ]; + $links['table'][__('Show open tables')] = [ + 'url' => 'sql.php', + 'params' => Url::getCommon([ + 'sql_query' => 'SHOW OPEN TABLES', + 'goto' => $this->selfUrl, + ], ''), + ]; + + if ($GLOBALS['replication_info']['master']['status']) { + $links['repl'][__('Show slave hosts')] = [ + 'url' => 'sql.php', + 'params' => Url::getCommon([ + 'sql_query' => 'SHOW SLAVE HOSTS', + 'goto' => $this->selfUrl, + ], ''), + ]; + $links['repl'][__('Show master status')] = [ + 'url' => '#replication_master', + 'params' => '', + ]; + } + if ($GLOBALS['replication_info']['slave']['status']) { + $links['repl'][__('Show slave status')] = [ + 'url' => '#replication_slave', + 'params' => '', + ]; + } + + $links['repl']['doc'] = 'replication'; + + $links['qcache'][__('Flush query cache')] = [ + 'url' => $this->selfUrl, + 'params' => Url::getCommon(['flush' => 'QUERY CACHE'], ''), + ]; + $links['qcache']['doc'] = 'query_cache'; + + $links['threads']['doc'] = 'mysql_threads'; + + $links['key']['doc'] = 'myisam_key_cache'; + + $links['binlog_cache']['doc'] = 'binary_log'; + + $links['Slow_queries']['doc'] = 'slow_query_log'; + + $links['innodb'][__('Variables')] = [ + 'url' => 'server_engines.php', + 'params' => Url::getCommon(['engine' => 'InnoDB'], ''), + ]; + $links['innodb'][__('InnoDB Status')] = [ + 'url' => 'server_engines.php', + 'params' => Url::getCommon([ + 'engine' => 'InnoDB', + 'page' => 'Status', + ], ''), + ]; + $links['innodb']['doc'] = 'innodb'; + + return($links); + } + + /** + * Calculate some values + * + * @param array $server_status contains results of SHOW GLOBAL STATUS + * @param array $server_variables contains results of SHOW GLOBAL VARIABLES + * + * @return array $server_status + */ + private function _calculateValues(array $server_status, array $server_variables) + { + // Key_buffer_fraction + if (isset($server_status['Key_blocks_unused']) + && isset($server_variables['key_cache_block_size']) + && isset($server_variables['key_buffer_size']) + && $server_variables['key_buffer_size'] != 0 + ) { + $server_status['Key_buffer_fraction_%'] + = 100 + - $server_status['Key_blocks_unused'] + * $server_variables['key_cache_block_size'] + / $server_variables['key_buffer_size'] + * 100; + } elseif (isset($server_status['Key_blocks_used']) + && isset($server_variables['key_buffer_size']) + && $server_variables['key_buffer_size'] != 0 + ) { + $server_status['Key_buffer_fraction_%'] + = $server_status['Key_blocks_used'] + * 1024 + / $server_variables['key_buffer_size']; + } + + // Ratio for key read/write + if (isset($server_status['Key_writes']) + && isset($server_status['Key_write_requests']) + && $server_status['Key_write_requests'] > 0 + ) { + $key_writes = $server_status['Key_writes']; + $key_write_requests = $server_status['Key_write_requests']; + $server_status['Key_write_ratio_%'] + = 100 * $key_writes / $key_write_requests; + } + + if (isset($server_status['Key_reads']) + && isset($server_status['Key_read_requests']) + && $server_status['Key_read_requests'] > 0 + ) { + $key_reads = $server_status['Key_reads']; + $key_read_requests = $server_status['Key_read_requests']; + $server_status['Key_read_ratio_%'] + = 100 * $key_reads / $key_read_requests; + } + + // Threads_cache_hitrate + if (isset($server_status['Threads_created']) + && isset($server_status['Connections']) + && $server_status['Connections'] > 0 + ) { + + $server_status['Threads_cache_hitrate_%'] + = 100 - $server_status['Threads_created'] + / $server_status['Connections'] * 100; + } + return $server_status; + } + + /** + * Sort variables into arrays + * + * @param array $server_status contains results of SHOW GLOBAL STATUS + * @param array $allocations allocations for sections + * @param array $allocationMap map variables to their section + * @param array $sectionUsed is a section used? + * @param array $used_queries used queries + * + * @return array ($allocationMap, $sectionUsed, $used_queries) + */ + private function _sortVariables( + array $server_status, array $allocations, array $allocationMap, array $sectionUsed, + array $used_queries + ) { + foreach ($server_status as $name => $value) { + $section_found = false; + foreach ($allocations as $filter => $section) { + if (mb_strpos($name, $filter) !== false) { + $allocationMap[$name] = $section; + $sectionUsed[$section] = true; + $section_found = true; + if ($section == 'com' && $value > 0) { + $used_queries[$name] = $value; + } + break; // Only exits inner loop + } + } + if (! $section_found) { + $allocationMap[$name] = 'other'; + $sectionUsed['other'] = true; + } + } + return array($allocationMap, $sectionUsed, $used_queries); + } + + /** + * Constructor + */ + public function __construct() + { + $this->selfUrl = basename($GLOBALS['PMA_PHP_SELF']); + + // get status from server + $server_status_result = $GLOBALS['dbi']->tryQuery('SHOW GLOBAL STATUS'); + $server_status = array(); + if ($server_status_result === false) { + $this->dataLoaded = false; + } else { + $this->dataLoaded = true; + while ($arr = $GLOBALS['dbi']->fetchRow($server_status_result)) { + $server_status[$arr[0]] = $arr[1]; + } + $GLOBALS['dbi']->freeResult($server_status_result); + } + + // for some calculations we require also some server settings + $server_variables = $GLOBALS['dbi']->fetchResult( + 'SHOW GLOBAL VARIABLES', 0, 1 + ); + + // cleanup of some deprecated values + $server_status = self::cleanDeprecated($server_status); + + // calculate some values + $server_status = $this->_calculateValues( + $server_status, $server_variables + ); + + // split variables in sections + $allocations = $this->_getAllocations(); + + $sections = $this->_getSections(); + + // define some needful links/commands + $links = $this->_getLinks(); + + // Variable to contain all com_ variables (query statistics) + $used_queries = array(); + + // Variable to map variable names to their respective section name + // (used for js category filtering) + $allocationMap = array(); + + // Variable to mark used sections + $sectionUsed = array(); + + // sort vars into arrays + list( + $allocationMap, $sectionUsed, $used_queries + ) = $this->_sortVariables( + $server_status, $allocations, $allocationMap, $sectionUsed, + $used_queries + ); + + // admin commands are not queries (e.g. they include COM_PING, + // which is excluded from $server_status['Questions']) + unset($used_queries['Com_admin_commands']); + + // Set all class properties + $this->db_isLocal = false; + $serverHostToLower = mb_strtolower( + $GLOBALS['cfg']['Server']['host'] + ); + if ($serverHostToLower === 'localhost' + || $GLOBALS['cfg']['Server']['host'] === '127.0.0.1' + || $GLOBALS['cfg']['Server']['host'] === '::1' + ) { + $this->db_isLocal = true; + } + $this->status = $server_status; + $this->sections = $sections; + $this->variables = $server_variables; + $this->used_queries = $used_queries; + $this->allocationMap = $allocationMap; + $this->links = $links; + $this->sectionUsed = $sectionUsed; + } + + /** + * cleanup of some deprecated values + * + * @param array $server_status status array to process + * + * @return array + */ + public static function cleanDeprecated(array $server_status) + { + $deprecated = array( + 'Com_prepare_sql' => 'Com_stmt_prepare', + 'Com_execute_sql' => 'Com_stmt_execute', + 'Com_dealloc_sql' => 'Com_stmt_close', + ); + foreach ($deprecated as $old => $new) { + if (isset($server_status[$old]) && isset($server_status[$new])) { + unset($server_status[$old]); + } + } + return $server_status; + } + + /** + * Generates menu HTML + * + * @return string + */ + public function getMenuHtml() + { + $url_params = Url::getCommon(); + $items = array( + array( + 'name' => __('Server'), + 'url' => 'server_status.php' + ), + array( + 'name' => __('Processes'), + 'url' => 'server_status_processes.php' + ), + array( + 'name' => __('Query statistics'), + 'url' => 'server_status_queries.php' + ), + array( + 'name' => __('All status variables'), + 'url' => 'server_status_variables.php' + ), + array( + 'name' => __('Monitor'), + 'url' => 'server_status_monitor.php' + ), + array( + 'name' => __('Advisor'), + 'url' => 'server_status_advisor.php' + ) + ); + + $retval = '
        '; + foreach ($items as $item) { + $class = ''; + if ($item['url'] === $this->selfUrl) { + $class = ' class="tabactive"'; + } + $retval .= '
      • '; + $retval .= ''; + $retval .= $item['name']; + $retval .= ''; + $retval .= '
      • '; + } + $retval .= '
      '; + $retval .= '
      '; + + return $retval; + } + + /** + * Builds a '; + foreach ($refreshRates as $rate) { + $selected = ($rate == $defaultRate)?' selected="selected"':''; + $return .= ''; + } + $return .= ''; + return $return; + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Server/Status/Monitor.php b/php/apps/phpmyadmin49/libraries/classes/Server/Status/Monitor.php new file mode 100644 index 00000000..b040193b --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Server/Status/Monitor.php @@ -0,0 +1,829 @@ +'; + $retval .= '
      '; + $retval .= '
      '; + $retval .= '
      '; + + $retval .= ''; + + return $retval; + } + + /** + * Returns html for Analyse Dialog + * + * @return string + */ + public static function getHtmlForAnalyseDialog() + { + $retval = '
      '; + $retval .= '

      ' . __('Selected time range:'); + $retval .= ' - '; + $retval .= ''; + $retval .= '

      '; + $retval .= ''; + $retval .= ''; + $retval .= '
      '; + $retval .= ''; + $retval .= ''; + $retval .= '

      '; + $retval .= __( + 'Choose from which log you want the statistics to be generated from.' + ); + $retval .= '

      '; + $retval .= '

      '; + $retval .= __('Results are grouped by query text.'); + $retval .= '

      '; + $retval .= '
      '; + $retval .= '
      '; + $retval .= ''; + $retval .= '

      '; + $retval .= '
      '; + $retval .= '
      '; + + return $retval; + } + + /** + * Returns html for Instructions Dialog + * + * @return string + */ + public static function getHtmlForInstructionsDialog() + { + $retval = '
      '; + $retval .= __( + 'The phpMyAdmin Monitor can assist you in optimizing the server' + . ' configuration and track down time intensive queries. For the latter you' + . ' will need to set log_output to \'TABLE\' and have either the' + . ' slow_query_log or general_log enabled. Note however, that the' + . ' general_log produces a lot of data and increases server load' + . ' by up to 15%.' + ); + + $retval .= '

      '; + $retval .= ''; + $retval .= '
      '; + $retval .= '
      '; + $retval .= '

      '; + $retval .= ''; + $retval .= __('Using the monitor:'); + $retval .= '

      '; + $retval .= __( + 'Your browser will refresh all displayed charts in a regular interval.' + . ' You may add charts and change the refresh rate under \'Settings\',' + . ' or remove any chart using the cog icon on each respective chart.' + ); + $retval .= '

      '; + $retval .= __( + 'To display queries from the logs, select the relevant time span on any' + . ' chart by holding down the left mouse button and panning over the' + . ' chart. Once confirmed, this will load a table of grouped queries,' + . ' there you may click on any occurring SELECT statements to further' + . ' analyze them.' + ); + $retval .= '

      '; + $retval .= '

      '; + $retval .= Util::getImage('s_attention'); + $retval .= ''; + $retval .= __('Please note:'); + $retval .= '
      '; + $retval .= __( + 'Enabling the general_log may increase the server load by' + . ' 5-15%. Also be aware that generating statistics from the logs is a' + . ' load intensive task, so it is advisable to select only a small time' + . ' span and to disable the general_log and empty its table once' + . ' monitoring is not required any more.' + ); + $retval .= '

      '; + $retval .= '
      '; + $retval .= '
      '; + + return $retval; + } + + /** + * Returns html for addChartDialog + * + * @return string + */ + public static function getHtmlForAddChartDialog() + { + $retval = '
      '; + $retval .= '
      '; + $retval .= '

      '; + $retval .= ''; + $retval .= ''; + $retval .= '
      '; + $retval .= ''; + $retval .= '
      '; + $retval .= '
      '; + $retval .= '
      '; + $retval .= '
      '; + $retval .= ''; + $retval .= ''; + $retval .= '

      '; + $retval .= ''; + $retval .= '
      '; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= '(' . __('KiB') . ', '; + $retval .= '' . __('MiB') . ')'; + $retval .= '
      '; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= '

      '; + $retval .= '' . __('Add this series') . ''; + $retval .= ''; + $retval .= ' | ' . __('Clear series') . ''; + $retval .= ''; + $retval .= '

      '; + $retval .= __('Series in chart:'); + $retval .= '
      '; + $retval .= ''; + $retval .= '' . __('None') . ''; + $retval .= ''; + $retval .= '
      '; + $retval .= '
      '; + $retval .= '
      '; + + return $retval; + } + + /** + * Returns html with Tab Links + * + * @return string + */ + public static function getHtmlForTabLinks() + { + $retval = ''; + + return $retval; + } + + /** + * Returns html with Settings dialog + * + * @return string + */ + public static function getHtmlForSettingsDialog() + { + $retval = '
      '; + $retval .= ''; + $retval .= Util::getImage('b_chart') . __('Add chart'); + $retval .= ''; + $retval .= ''; + $retval .= Util::getImage('b_tblops') + . __('Enable charts dragging'); + $retval .= ''; + $retval .= '
      '; + $retval .= '
      '; + $retval .= __('Refresh rate') . '
      '; + $retval .= Data::getHtmlForRefreshList( + 'gridChartRefresh', + 5, + Array(2, 3, 4, 5, 10, 20, 40, 60, 120, 300, 600, 1200) + ); + $retval .= '
      '; + $retval .= '
      '; + $retval .= '
      '; + $retval .= __('Chart columns'); + $retval .= '
      '; + $retval .= ''; + $retval .= '
      '; + $retval .= '
      '; + $retval .= '' . __('Chart arrangement') . ' '; + $retval .= Util::showHint( + __( + 'The arrangement of the charts is stored to the browsers local storage. ' + . 'You may want to export it if you have a complicated set up.' + ) + ); + $retval .= '
      '; + $retval .= ''; + $retval .= __('Import'); + $retval .= ''; + $retval .= '  '; + $retval .= ''; + $retval .= __('Export'); + $retval .= ''; + $retval .= '  '; + $retval .= ''; + $retval .= __('Reset to default'); + $retval .= ''; + $retval .= '
      '; + $retval .= '
      '; + + return $retval; + } + + + /** + * Define some data and links needed on the client side + * + * @param Data $serverStatusData Server status data + * + * @return string + */ + public static function getHtmlForClientSideDataAndLinks(Data $serverStatusData) + { + /** + * Define some data needed on the client side + */ + $input = ''; + $form = '
      '; + $form .= sprintf($input, 'server_time', microtime(true) * 1000); + $form .= sprintf($input, 'server_os', SysInfo::getOs()); + $form .= sprintf($input, 'is_superuser', $GLOBALS['dbi']->isSuperuser()); + $form .= sprintf($input, 'server_db_isLocal', $serverStatusData->db_isLocal); + $form .= '
      '; + /** + * Define some links used on client side + */ + $links = '
      '; + $links .= Util::showMySQLDocu('general-thread-states'); + $links .= '
      '; + $links .= '
      '; + $links .= Util::showMySQLDocu('explain-output'); + $links .= '
      '; + + return $form . $links; + } + + /***************************Ajax request function***********************************/ + + /** + * Returns JSon for real-time charting data + * + * @return array + */ + public static function getJsonForChartingData() + { + $ret = json_decode($_POST['requiredData'], true); + $statusVars = array(); + $serverVars = array(); + $sysinfo = $cpuload = $memory = 0; + + /* Accumulate all required variables and data */ + list($serverVars, $statusVars, $ret) = self::getJsonForChartingDataGet( + $ret, $serverVars, $statusVars, $sysinfo, $cpuload, $memory + ); + + // Retrieve all required status variables + if (count($statusVars)) { + $statusVarValues = $GLOBALS['dbi']->fetchResult( + "SHOW GLOBAL STATUS WHERE Variable_name='" + . implode("' OR Variable_name='", $statusVars) . "'", + 0, + 1 + ); + } else { + $statusVarValues = array(); + } + + // Retrieve all required server variables + if (count($serverVars)) { + $serverVarValues = $GLOBALS['dbi']->fetchResult( + "SHOW GLOBAL VARIABLES WHERE Variable_name='" + . implode("' OR Variable_name='", $serverVars) . "'", + 0, + 1 + ); + } else { + $serverVarValues = array(); + } + + // ...and now assign them + $ret = self::getJsonForChartingDataSet($ret, $statusVarValues, $serverVarValues); + + $ret['x'] = microtime(true) * 1000; + return $ret; + } + + /** + * Assign the variables for real-time charting data + * + * @param array $ret Real-time charting data + * @param array $statusVarValues Status variable values + * @param array $serverVarValues Server variable values + * + * @return array + */ + public static function getJsonForChartingDataSet(array $ret, array $statusVarValues, array $serverVarValues) + { + foreach ($ret as $chart_id => $chartNodes) { + foreach ($chartNodes as $node_id => $nodeDataPoints) { + foreach ($nodeDataPoints as $point_id => $dataPoint) { + switch ($dataPoint['type']) { + case 'statusvar': + $ret[$chart_id][$node_id][$point_id]['value'] + = $statusVarValues[$dataPoint['name']]; + break; + case 'servervar': + $ret[$chart_id][$node_id][$point_id]['value'] + = $serverVarValues[$dataPoint['name']]; + break; + } + } + } + } + return $ret; + } + + /** + * Get called to get JSON for charting data + * + * @param array $ret Real-time charting data + * @param array $serverVars Server variable values + * @param array $statusVars Status variable values + * @param mixed $sysinfo System info + * @param mixed $cpuload CPU load + * @param mixed $memory Memory + * + * @return array + */ + public static function getJsonForChartingDataGet( + array $ret, array $serverVars, array $statusVars, $sysinfo, $cpuload, $memory + ) { + // For each chart + foreach ($ret as $chart_id => $chartNodes) { + // For each data series + foreach ($chartNodes as $node_id => $nodeDataPoints) { + // For each data point in the series (usually just 1) + foreach ($nodeDataPoints as $point_id => $dataPoint) { + list($serverVars, $statusVars, $ret[$chart_id][$node_id][$point_id]) + = self::getJsonForChartingDataSwitch( + $dataPoint['type'], $dataPoint['name'], $serverVars, + $statusVars, $ret[$chart_id][$node_id][$point_id], + $sysinfo, $cpuload, $memory + ); + } /* foreach */ + } /* foreach */ + } + return array($serverVars, $statusVars, $ret); + } + + /** + * Switch called to get JSON for charting data + * + * @param string $type Type + * @param string $pName Name + * @param array $serverVars Server variable values + * @param array $statusVars Status variable values + * @param array $ret Real-time charting data + * @param mixed $sysinfo System info + * @param mixed $cpuload CPU load + * @param mixed $memory Memory + * + * @return array + */ + public static function getJsonForChartingDataSwitch( + $type, $pName, array $serverVars, array $statusVars, array $ret, + $sysinfo, $cpuload, $memory + ) { + switch ($type) { + /* We only collect the status and server variables here to + * read them all in one query, + * and only afterwards assign them. + * Also do some white list filtering on the names + */ + case 'servervar': + if (!preg_match('/[^a-zA-Z_]+/', $pName)) { + $serverVars[] = $pName; + } + break; + + case 'statusvar': + if (!preg_match('/[^a-zA-Z_]+/', $pName)) { + $statusVars[] = $pName; + } + break; + + case 'proc': + $result = $GLOBALS['dbi']->query('SHOW PROCESSLIST'); + $ret['value'] = $GLOBALS['dbi']->numRows($result); + break; + + case 'cpu': + if (!$sysinfo) { + $sysinfo = SysInfo::get(); + } + if (!$cpuload) { + $cpuload = $sysinfo->loadavg(); + } + + if (SysInfo::getOs() == 'Linux') { + $ret['idle'] = $cpuload['idle']; + $ret['busy'] = $cpuload['busy']; + } else { + $ret['value'] = $cpuload['loadavg']; + } + + break; + + case 'memory': + if (!$sysinfo) { + $sysinfo = SysInfo::get(); + } + if (!$memory) { + $memory = $sysinfo->memory(); + } + + $ret['value'] = isset($memory[$pName]) ? $memory[$pName] : 0; + break; + } + + return array($serverVars, $statusVars, $ret); + } + + /** + * Returns JSon for log data with type: slow + * + * @param int $start Unix Time: Start time for query + * @param int $end Unix Time: End time for query + * + * @return array + */ + public static function getJsonForLogDataTypeSlow($start, $end) + { + $query = 'SELECT start_time, user_host, '; + $query .= 'Sec_to_Time(Sum(Time_to_Sec(query_time))) as query_time, '; + $query .= 'Sec_to_Time(Sum(Time_to_Sec(lock_time))) as lock_time, '; + $query .= 'SUM(rows_sent) AS rows_sent, '; + $query .= 'SUM(rows_examined) AS rows_examined, db, sql_text, '; + $query .= 'COUNT(sql_text) AS \'#\' '; + $query .= 'FROM `mysql`.`slow_log` '; + $query .= 'WHERE start_time > FROM_UNIXTIME(' . $start . ') '; + $query .= 'AND start_time < FROM_UNIXTIME(' . $end . ') GROUP BY sql_text'; + + $result = $GLOBALS['dbi']->tryQuery($query); + + $return = array('rows' => array(), 'sum' => array()); + + while ($row = $GLOBALS['dbi']->fetchAssoc($result)) { + $type = mb_strtolower( + mb_substr( + $row['sql_text'], + 0, + mb_strpos($row['sql_text'], ' ') + ) + ); + + switch($type) { + case 'insert': + case 'update': + //Cut off big inserts and updates, but append byte count instead + if (mb_strlen($row['sql_text']) > 220) { + $implode_sql_text = implode( + ' ', + Util::formatByteDown( + mb_strlen($row['sql_text']), 2, 2 + ) + ); + $row['sql_text'] = mb_substr($row['sql_text'], 0, 200) + . '... [' . $implode_sql_text . ']'; + } + break; + default: + break; + } + + if (! isset($return['sum'][$type])) { + $return['sum'][$type] = 0; + } + $return['sum'][$type] += $row['#']; + $return['rows'][] = $row; + } + + $return['sum']['TOTAL'] = array_sum($return['sum']); + $return['numRows'] = count($return['rows']); + + $GLOBALS['dbi']->freeResult($result); + return $return; + } + + /** + * Returns JSon for log data with type: general + * + * @param int $start Unix Time: Start time for query + * @param int $end Unix Time: End time for query + * + * @return array + */ + public static function getJsonForLogDataTypeGeneral($start, $end) + { + $limitTypes = ''; + if (isset($_POST['limitTypes']) && $_POST['limitTypes']) { + $limitTypes + = 'AND argument REGEXP \'^(INSERT|SELECT|UPDATE|DELETE)\' '; + } + + $query = 'SELECT TIME(event_time) as event_time, user_host, thread_id, '; + $query .= 'server_id, argument, count(argument) as \'#\' '; + $query .= 'FROM `mysql`.`general_log` '; + $query .= 'WHERE command_type=\'Query\' '; + $query .= 'AND event_time > FROM_UNIXTIME(' . $start . ') '; + $query .= 'AND event_time < FROM_UNIXTIME(' . $end . ') '; + $query .= $limitTypes . 'GROUP by argument'; // HAVING count > 1'; + + $result = $GLOBALS['dbi']->tryQuery($query); + + $return = array('rows' => array(), 'sum' => array()); + $insertTables = array(); + $insertTablesFirst = -1; + $i = 0; + $removeVars = isset($_POST['removeVariables']) + && $_POST['removeVariables']; + + while ($row = $GLOBALS['dbi']->fetchAssoc($result)) { + preg_match('/^(\w+)\s/', $row['argument'], $match); + $type = mb_strtolower($match[1]); + + if (! isset($return['sum'][$type])) { + $return['sum'][$type] = 0; + } + $return['sum'][$type] += $row['#']; + + switch($type) { + /** @noinspection PhpMissingBreakStatementInspection */ + case 'insert': + // Group inserts if selected + if ($removeVars + && preg_match( + '/^INSERT INTO (`|\'|"|)([^\s\\1]+)\\1/i', + $row['argument'], $matches + ) + ) { + $insertTables[$matches[2]]++; + if ($insertTables[$matches[2]] > 1) { + $return['rows'][$insertTablesFirst]['#'] + = $insertTables[$matches[2]]; + + // Add a ... to the end of this query to indicate that + // there's been other queries + $temp = $return['rows'][$insertTablesFirst]['argument']; + $return['rows'][$insertTablesFirst]['argument'] + .= self::getSuspensionPoints( + $temp[strlen($temp) - 1] + ); + + // Group this value, thus do not add to the result list + continue 2; + } else { + $insertTablesFirst = $i; + $insertTables[$matches[2]] += $row['#'] - 1; + } + } + // No break here + + case 'update': + // Cut off big inserts and updates, + // but append byte count therefor + if (mb_strlen($row['argument']) > 220) { + $row['argument'] = mb_substr($row['argument'], 0, 200) + . '... [' + . implode( + ' ', + Util::formatByteDown( + mb_strlen($row['argument']), + 2, + 2 + ) + ) + . ']'; + } + break; + + default: + break; + } + + $return['rows'][] = $row; + $i++; + } + + $return['sum']['TOTAL'] = array_sum($return['sum']); + $return['numRows'] = count($return['rows']); + + $GLOBALS['dbi']->freeResult($result); + + return $return; + } + + /** + * Return suspension points if needed + * + * @param string $lastChar Last char + * + * @return null|string Return suspension points if needed + */ + public static function getSuspensionPoints($lastChar) + { + if ($lastChar != '.') { + return '
      ...'; + } + + return null; + } + + /** + * Returns JSon for logging vars + * + * @return array + */ + public static function getJsonForLoggingVars() + { + if (isset($_POST['varName']) && isset($_POST['varValue'])) { + $value = $GLOBALS['dbi']->escapeString($_POST['varValue']); + if (! is_numeric($value)) { + $value="'" . $value . "'"; + } + + if (! preg_match("/[^a-zA-Z0-9_]+/", $_POST['varName'])) { + $GLOBALS['dbi']->query( + 'SET GLOBAL ' . $_POST['varName'] . ' = ' . $value + ); + } + + } + + $loggingVars = $GLOBALS['dbi']->fetchResult( + 'SHOW GLOBAL VARIABLES WHERE Variable_name IN' + . ' ("general_log","slow_query_log","long_query_time","log_output")', + 0, + 1 + ); + return $loggingVars; + } + + /** + * Returns JSon for query_analyzer + * + * @return array + */ + public static function getJsonForQueryAnalyzer() + { + $return = array(); + + if (strlen($_POST['database']) > 0) { + $GLOBALS['dbi']->selectDb($_POST['database']); + } + + if ($profiling = Util::profilingSupported()) { + $GLOBALS['dbi']->query('SET PROFILING=1;'); + } + + // Do not cache query + $query = preg_replace( + '/^(\s*SELECT)/i', + '\\1 SQL_NO_CACHE', + $_POST['query'] + ); + + $GLOBALS['dbi']->tryQuery($query); + $return['affectedRows'] = $GLOBALS['cached_affected_rows']; + + $result = $GLOBALS['dbi']->tryQuery('EXPLAIN ' . $query); + while ($row = $GLOBALS['dbi']->fetchAssoc($result)) { + $return['explain'][] = $row; + } + + // In case an error happened + $return['error'] = $GLOBALS['dbi']->getError(); + + $GLOBALS['dbi']->freeResult($result); + + if ($profiling) { + $return['profiling'] = array(); + $result = $GLOBALS['dbi']->tryQuery( + 'SELECT seq,state,duration FROM INFORMATION_SCHEMA.PROFILING' + . ' WHERE QUERY_ID=1 ORDER BY seq' + ); + while ($row = $GLOBALS['dbi']->fetchAssoc($result)) { + $return['profiling'][]= $row; + } + $GLOBALS['dbi']->freeResult($result); + } + return $return; + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Server/Status/Processes.php b/php/apps/phpmyadmin49/libraries/classes/Server/Status/Processes.php new file mode 100644 index 00000000..336b28b6 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Server/Status/Processes.php @@ -0,0 +1,305 @@ +getDisplay(); + $retval = $notice . ''; + return $retval; + } + + /** + * Prints Server Process list + * + * @return string + */ + public static function getHtmlForServerProcesslist() + { + $show_full_sql = ! empty($_POST['full']); + + // This array contains display name and real column name of each + // sortable column in the table + $sortable_columns = array( + array( + 'column_name' => __('ID'), + 'order_by_field' => 'Id' + ), + array( + 'column_name' => __('User'), + 'order_by_field' => 'User' + ), + array( + 'column_name' => __('Host'), + 'order_by_field' => 'Host' + ), + array( + 'column_name' => __('Database'), + 'order_by_field' => 'db' + ), + array( + 'column_name' => __('Command'), + 'order_by_field' => 'Command' + ), + array( + 'column_name' => __('Time'), + 'order_by_field' => 'Time' + ), + array( + 'column_name' => __('Status'), + 'order_by_field' => 'State' + ), + array( + 'column_name' => __('Progress'), + 'order_by_field' => 'Progress' + ), + array( + 'column_name' => __('SQL query'), + 'order_by_field' => 'Info' + ) + ); + $sortableColCount = count($sortable_columns); + + $sql_query = $show_full_sql + ? 'SHOW FULL PROCESSLIST' + : 'SHOW PROCESSLIST'; + if ((! empty($_POST['order_by_field']) + && ! empty($_POST['sort_order'])) + || (! empty($_POST['showExecuting'])) + ) { + $sql_query = 'SELECT * FROM `INFORMATION_SCHEMA`.`PROCESSLIST` '; + } + if (! empty($_POST['showExecuting'])) { + $sql_query .= ' WHERE state != "" '; + } + if (!empty($_POST['order_by_field']) && !empty($_POST['sort_order'])) { + $sql_query .= ' ORDER BY ' + . Util::backquote($_POST['order_by_field']) + . ' ' . $_POST['sort_order']; + } + + $result = $GLOBALS['dbi']->query($sql_query); + + $retval = '
      '; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + foreach ($sortable_columns as $column) { + + $is_sorted = ! empty($_POST['order_by_field']) + && ! empty($_POST['sort_order']) + && ($_POST['order_by_field'] == $column['order_by_field']); + + $column['sort_order'] = 'ASC'; + if ($is_sorted && $_POST['sort_order'] === 'ASC') { + $column['sort_order'] = 'DESC'; + } + if (isset($_POST['showExecuting'])) { + $column['showExecuting'] = 'on'; + } + + $retval .= ''; + } + + $retval .= ''; + $retval .= ''; + $retval .= ''; + + while ($process = $GLOBALS['dbi']->fetchAssoc($result)) { + $retval .= self::getHtmlForServerProcessItem( + $process, + $show_full_sql + ); + } + $retval .= ''; + $retval .= '
      ' . __('Processes') . ''; + $columnUrl = Url::getCommon($column); + $retval .= ''; + + $retval .= $column['column_name']; + + if ($is_sorted) { + $asc_display_style = 'inline'; + $desc_display_style = 'none'; + if ($_POST['sort_order'] === 'DESC') { + $desc_display_style = 'inline'; + $asc_display_style = 'none'; + } + $retval .= ''
+                    . __('Descending') . ''; + $retval .= ''
+                    . __('Ascending') . ''; + } + + $retval .= ''; + + if (0 === --$sortableColCount) { + $url_params = array(); + if ($show_full_sql) { + $url_params['full'] = ''; + } else { + $url_params['full'] = 1; + } + if (isset($_POST['showExecuting'])) { + $url_params['showExecuting'] = 'on'; + } + if (isset($_POST['order_by_field'])) { + $url_params['order_by_field'] = $_POST['order_by_field']; + } + if (isset($_POST['sort_order'])) { + $url_params['sort_order'] = $_POST['sort_order']; + } + $retval .= ''; + if ($show_full_sql) { + $retval .= Util::getImage('s_partialtext', + __('Truncate Shown Queries'), ['class' => 'icon_fulltext']); + } else { + $retval .= Util::getImage('s_fulltext', + __('Show Full Queries'), ['class' => 'icon_fulltext']); + } + $retval .= ''; + } + $retval .= '
      '; + $retval .= '
      '; + + return $retval; + } + + /** + * Returns the html for the list filter + * + * @return string + */ + public static function getHtmlForProcessListFilter() + { + $showExecuting = ''; + if (! empty($_POST['showExecuting'])) { + $showExecuting = ' checked="checked"'; + } + + $url_params = array( + 'ajax_request' => true, + 'full' => (isset($_POST['full']) ? $_POST['full'] : ''), + 'column_name' => (isset($_POST['column_name']) ? $_POST['column_name'] : ''), + 'order_by_field' + => (isset($_POST['order_by_field']) ? $_POST['order_by_field'] : ''), + 'sort_order' => (isset($_POST['sort_order']) ? $_POST['sort_order'] : ''), + ); + + $retval = ''; + $retval .= '
      '; + $retval .= '' . __('Filters') . ''; + $retval .= '
      '; + $retval .= Url::getHiddenInputs($url_params); + $retval .= ''; + $retval .= '
      '; + $retval .= ''; + $retval .= ''; + $retval .= '
      '; + $retval .= '
      '; + $retval .= '
      '; + + return $retval; + } + + /** + * Prints Every Item of Server Process + * + * @param array $process data of Every Item of Server Process + * @param bool $show_full_sql show full sql or not + * + * @return string + */ + public static function getHtmlForServerProcessItem(array $process, $show_full_sql) + { + // Array keys need to modify due to the way it has used + // to display column values + if ((! empty($_POST['order_by_field']) && ! empty($_POST['sort_order'])) + || (! empty($_POST['showExecuting'])) + ) { + foreach (array_keys($process) as $key) { + $new_key = ucfirst(mb_strtolower($key)); + if ($new_key !== $key) { + $process[$new_key] = $process[$key]; + unset($process[$key]); + } + } + } + + $retval = ''; + $retval .= '' + . __('Kill') . ''; + $retval .= '' . $process['Id'] . ''; + $retval .= '' . htmlspecialchars($process['User']) . ''; + $retval .= '' . htmlspecialchars($process['Host']) . ''; + $retval .= '' . ((! isset($process['db']) + || strlen($process['db']) === 0) + ? '' . __('None') . '' + : htmlspecialchars($process['db'])) . ''; + $retval .= '' . htmlspecialchars($process['Command']) . ''; + $retval .= '' . $process['Time'] . ''; + $processStatusStr = empty($process['State']) ? '---' : $process['State']; + $retval .= '' . $processStatusStr . ''; + $processProgress = empty($process['Progress']) ? '---' : $process['Progress']; + $retval .= '' . $processProgress . ''; + $retval .= ''; + + if (empty($process['Info'])) { + $retval .= '---'; + } else { + $retval .= Util::formatSql($process['Info'], ! $show_full_sql); + } + $retval .= ''; + $retval .= ''; + + return $retval; + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Server/Status/Queries.php b/php/apps/phpmyadmin49/libraries/classes/Server/Status/Queries.php new file mode 100644 index 00000000..948aca47 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Server/Status/Queries.php @@ -0,0 +1,156 @@ +status['Uptime']; + $used_queries = $serverStatusData->used_queries; + $total_queries = array_sum($used_queries); + + $retval .= '

      '; + /* l10n: Questions is the name of a MySQL Status variable */ + $retval .= sprintf( + __('Questions since startup: %s'), + Util::formatNumber($total_queries, 0) + ); + $retval .= ' '; + $retval .= Util::showMySQLDocu( + 'server-status-variables', + false, + 'statvar_Questions' + ); + $retval .= '
      '; + $retval .= ''; + $retval .= 'ø ' . __('per hour:') . ' '; + $retval .= Util::formatNumber($total_queries * $hour_factor, 0); + $retval .= '
      '; + $retval .= 'ø ' . __('per minute:') . ' '; + $retval .= Util::formatNumber( + $total_queries * 60 / $serverStatusData->status['Uptime'], + 0 + ); + $retval .= '
      '; + if ($total_queries / $serverStatusData->status['Uptime'] >= 1) { + $retval .= 'ø ' . __('per second:') . ' '; + $retval .= Util::formatNumber( + $total_queries / $serverStatusData->status['Uptime'], + 0 + ); + } + $retval .= '
      '; + $retval .= '

      '; + + $retval .= self::getHtmlForDetails($serverStatusData); + + return $retval; + } + + /** + * Returns the html content for the query details + * + * @param Data $serverStatusData Server status data + * + * @return string + */ + public static function getHtmlForDetails(Data $serverStatusData) + { + $hour_factor = 3600 / $serverStatusData->status['Uptime']; + $used_queries = $serverStatusData->used_queries; + $total_queries = array_sum($used_queries); + // reverse sort by value to show most used statements first + arsort($used_queries); + + //(- $serverStatusData->status['Connections']); + $perc_factor = 100 / $total_queries; + + $retval = ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + + $chart_json = array(); + $query_sum = array_sum($used_queries); + $other_sum = 0; + foreach ($used_queries as $name => $value) { + // For the percentage column, use Questions - Connections, because + // the number of connections is not an item of the Query types + // but is included in Questions. Then the total of the percentages is 100. + $name = str_replace(array('Com_', '_'), array('', ' '), $name); + // Group together values that make out less than 2% into "Other", but only + // if we have more than 6 fractions already + if ($value < $query_sum * 0.02 && count($chart_json)>6) { + $other_sum += $value; + } else { + $chart_json[$name] = $value; + } + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + } + $retval .= ''; + $retval .= '
      ' . __('Statements') . ''; + /* l10n: # = Amount of queries */ + $retval .= __('#'); + $retval .= 'ø ' . __('per hour') + . '%
      ' . htmlspecialchars($name) . ''; + $retval .= htmlspecialchars( + Util::formatNumber($value, 5, 0, true) + ); + $retval .= ''; + $retval .= htmlspecialchars( + Util::formatNumber($value * $hour_factor, 4, 1, true) + ); + $retval .= ''; + $retval .= htmlspecialchars( + Util::formatNumber($value * $perc_factor, 0, 2) + ); + $retval .= '
      '; + + $retval .= '
      '; + + return $retval; + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Server/Status/Variables.php b/php/apps/phpmyadmin49/libraries/classes/Server/Status/Variables.php new file mode 100644 index 00000000..2fcdea7c --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Server/Status/Variables.php @@ -0,0 +1,776 @@ +'; + $retval .= '' . __('Filters') . ''; + $retval .= '
      '; + $retval .= Url::getHiddenInputs(); + $retval .= ''; + $retval .= '
      '; + $retval .= ''; + $retval .= ''; + $retval .= '
      '; + $retval .= '
      '; + $retval .= ''; + $retval .= ''; + $retval .= '
      '; + $retval .= '
      '; + $retval .= ''; + $retval .= '
      '; + $retval .= '
      '; + $retval .= ''; + $retval .= ''; + $retval .= '
      '; + $retval .= '
      '; + $retval .= ''; + + return $retval; + } + + /** + * Prints the suggestion links + * + * @param Data $serverStatusData Server status data + * + * @return string + */ + public static function getHtmlForLinkSuggestions(Data $serverStatusData) + { + $retval = ''; + + return $retval; + } + + /** + * Returns a table with variables information + * + * @param Data $serverStatusData Server status data + * + * @return string + */ + public static function getHtmlForVariablesList(Data $serverStatusData) + { + $retval = ''; + $strShowStatus = self::getDescriptions(); + /** + * define some alerts + */ + // name => max value before alert + $alerts = array( + // lower is better + // variable => max value + 'Aborted_clients' => 0, + 'Aborted_connects' => 0, + + 'Binlog_cache_disk_use' => 0, + + 'Created_tmp_disk_tables' => 0, + + 'Handler_read_rnd' => 0, + 'Handler_read_rnd_next' => 0, + + 'Innodb_buffer_pool_pages_dirty' => 0, + 'Innodb_buffer_pool_reads' => 0, + 'Innodb_buffer_pool_wait_free' => 0, + 'Innodb_log_waits' => 0, + 'Innodb_row_lock_time_avg' => 10, // ms + 'Innodb_row_lock_time_max' => 50, // ms + 'Innodb_row_lock_waits' => 0, + + 'Slow_queries' => 0, + 'Delayed_errors' => 0, + 'Select_full_join' => 0, + 'Select_range_check' => 0, + 'Sort_merge_passes' => 0, + 'Opened_tables' => 0, + 'Table_locks_waited' => 0, + 'Qcache_lowmem_prunes' => 0, + + 'Qcache_free_blocks' => + isset($serverStatusData->status['Qcache_total_blocks']) + ? $serverStatusData->status['Qcache_total_blocks'] / 5 + : 0, + 'Slow_launch_threads' => 0, + + // depends on Key_read_requests + // normally lower then 1:0.01 + 'Key_reads' => isset($serverStatusData->status['Key_read_requests']) + ? (0.01 * $serverStatusData->status['Key_read_requests']) : 0, + // depends on Key_write_requests + // normally nearly 1:1 + 'Key_writes' => isset($serverStatusData->status['Key_write_requests']) + ? (0.9 * $serverStatusData->status['Key_write_requests']) : 0, + + 'Key_buffer_fraction' => 0.5, + + // alert if more than 95% of thread cache is in use + 'Threads_cached' => isset($serverStatusData->variables['thread_cache_size']) + ? 0.95 * $serverStatusData->variables['thread_cache_size'] : 0 + + // higher is better + // variable => min value + //'Handler read key' => '> ', + ); + + $retval .= self::getHtmlForRenderVariables( + $serverStatusData, + $alerts, + $strShowStatus + ); + + return $retval; + } + + /** + * Returns HTML for render variables list + * + * @param Data $serverStatusData Server status data + * @param array $alerts Alert Array + * @param array $strShowStatus Status Array + * + * @return string + */ + public static function getHtmlForRenderVariables(Data $serverStatusData, array $alerts, array $strShowStatus) + { + $retval = '
      '; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + $retval .= ''; + + foreach ($serverStatusData->status as $name => $value) { + $retval .= ''; + + $retval .= ''; + + $retval .= ''; + $retval .= ''; + $retval .= ''; + } + $retval .= ''; + $retval .= '
      ' . __('Variable') . '' . __('Value') . '' . __('Description') . '
      '; + $retval .= htmlspecialchars(str_replace('_', ' ', $name)); + // Fields containing % are calculated, + // they can not be described in MySQL documentation + if (mb_strpos($name, '%') === false) { + $retval .= Util::showMySQLDocu( + 'server-status-variables', + false, + 'statvar_' . $name + ); + } + $retval .= ''; + if (isset($alerts[$name])) { + if ($value > $alerts[$name]) { + $retval .= ''; + } else { + $retval .= ''; + } + } + if (substr($name, -1) === '%') { + $retval .= htmlspecialchars( + Util::formatNumber($value, 0, 2) + ) . ' %'; + } elseif (strpos($name, 'Uptime') !== false) { + $retval .= htmlspecialchars( + Util::timespanFormat($value) + ); + } elseif (is_numeric($value) && $value > 1000) { + $retval .= '' + . htmlspecialchars(Util::formatNumber($value, 3, 1)) + . ''; + } elseif (is_numeric($value)) { + $retval .= htmlspecialchars( + Util::formatNumber($value, 3, 1) + ); + } else { + $retval .= htmlspecialchars($value); + } + if (isset($alerts[$name])) { + $retval .= ''; + } + $retval .= ''; + $retval .= ''; + if (isset($alerts[$name])) { + if ($value > $alerts[$name]) { + $retval .= ''; + } else { + $retval .= ''; + } + } + $retval .= htmlspecialchars($value); + if (isset($alerts[$name])) { + $retval .= ''; + } + $retval .= ''; + $retval .= ''; + + if (isset($strShowStatus[$name])) { + $retval .= $strShowStatus[$name]; + } + + if (isset($serverStatusData->links[$name])) { + foreach ($serverStatusData->links[$name] as $link_name => $link_url) { + if ('doc' == $link_name) { + $retval .= Util::showMySQLDocu($link_url); + } else { + $retval .= ' ' + . $link_name . ''; + } + } + unset($link_url, $link_name); + } + $retval .= '
      '; + $retval .= '
      '; + + return $retval; + } + + /** + * Returns a list of variable descriptions + * + * @return array + */ + public static function getDescriptions() + { + /** + * Messages are built using the message name + */ + return array( + 'Aborted_clients' => __( + 'The number of connections that were aborted because the client died' + . ' without closing the connection properly.' + ), + 'Aborted_connects' => __( + 'The number of failed attempts to connect to the MySQL server.' + ), + 'Binlog_cache_disk_use' => __( + 'The number of transactions that used the temporary binary log cache' + . ' but that exceeded the value of binlog_cache_size and used a' + . ' temporary file to store statements from the transaction.' + ), + 'Binlog_cache_use' => __( + 'The number of transactions that used the temporary binary log cache.' + ), + 'Connections' => __( + 'The number of connection attempts (successful or not)' + . ' to the MySQL server.' + ), + 'Created_tmp_disk_tables' => __( + 'The number of temporary tables on disk created automatically by' + . ' the server while executing statements. If' + . ' Created_tmp_disk_tables is big, you may want to increase the' + . ' tmp_table_size value to cause temporary tables to be' + . ' memory-based instead of disk-based.' + ), + 'Created_tmp_files' => __( + 'How many temporary files mysqld has created.' + ), + 'Created_tmp_tables' => __( + 'The number of in-memory temporary tables created automatically' + . ' by the server while executing statements.' + ), + 'Delayed_errors' => __( + 'The number of rows written with INSERT DELAYED for which some' + . ' error occurred (probably duplicate key).' + ), + 'Delayed_insert_threads' => __( + 'The number of INSERT DELAYED handler threads in use. Every' + . ' different table on which one uses INSERT DELAYED gets' + . ' its own thread.' + ), + 'Delayed_writes' => __( + 'The number of INSERT DELAYED rows written.' + ), + 'Flush_commands' => __( + 'The number of executed FLUSH statements.' + ), + 'Handler_commit' => __( + 'The number of internal COMMIT statements.' + ), + 'Handler_delete' => __( + 'The number of times a row was deleted from a table.' + ), + 'Handler_discover' => __( + 'The MySQL server can ask the NDB Cluster storage engine if it' + . ' knows about a table with a given name. This is called discovery.' + . ' Handler_discover indicates the number of time tables have been' + . ' discovered.' + ), + 'Handler_read_first' => __( + 'The number of times the first entry was read from an index. If this' + . ' is high, it suggests that the server is doing a lot of full' + . ' index scans; for example, SELECT col1 FROM foo, assuming that' + . ' col1 is indexed.' + ), + 'Handler_read_key' => __( + 'The number of requests to read a row based on a key. If this is' + . ' high, it is a good indication that your queries and tables' + . ' are properly indexed.' + ), + 'Handler_read_next' => __( + 'The number of requests to read the next row in key order. This is' + . ' incremented if you are querying an index column with a range' + . ' constraint or if you are doing an index scan.' + ), + 'Handler_read_prev' => __( + 'The number of requests to read the previous row in key order.' + . ' This read method is mainly used to optimize ORDER BY … DESC.' + ), + 'Handler_read_rnd' => __( + 'The number of requests to read a row based on a fixed position.' + . ' This is high if you are doing a lot of queries that require' + . ' sorting of the result. You probably have a lot of queries that' + . ' require MySQL to scan whole tables or you have joins that' + . ' don\'t use keys properly.' + ), + 'Handler_read_rnd_next' => __( + 'The number of requests to read the next row in the data file.' + . ' This is high if you are doing a lot of table scans. Generally' + . ' this suggests that your tables are not properly indexed or that' + . ' your queries are not written to take advantage of the indexes' + . ' you have.' + ), + 'Handler_rollback' => __( + 'The number of internal ROLLBACK statements.' + ), + 'Handler_update' => __( + 'The number of requests to update a row in a table.' + ), + 'Handler_write' => __( + 'The number of requests to insert a row in a table.' + ), + 'Innodb_buffer_pool_pages_data' => __( + 'The number of pages containing data (dirty or clean).' + ), + 'Innodb_buffer_pool_pages_dirty' => __( + 'The number of pages currently dirty.' + ), + 'Innodb_buffer_pool_pages_flushed' => __( + 'The number of buffer pool pages that have been requested' + . ' to be flushed.' + ), + 'Innodb_buffer_pool_pages_free' => __( + 'The number of free pages.' + ), + 'Innodb_buffer_pool_pages_latched' => __( + 'The number of latched pages in InnoDB buffer pool. These are pages' + . ' currently being read or written or that can\'t be flushed or' + . ' removed for some other reason.' + ), + 'Innodb_buffer_pool_pages_misc' => __( + 'The number of pages busy because they have been allocated for' + . ' administrative overhead such as row locks or the adaptive' + . ' hash index. This value can also be calculated as' + . ' Innodb_buffer_pool_pages_total - Innodb_buffer_pool_pages_free' + . ' - Innodb_buffer_pool_pages_data.' + ), + 'Innodb_buffer_pool_pages_total' => __( + 'Total size of buffer pool, in pages.' + ), + 'Innodb_buffer_pool_read_ahead_rnd' => __( + 'The number of "random" read-aheads InnoDB initiated. This happens' + . ' when a query is to scan a large portion of a table but in' + . ' random order.' + ), + 'Innodb_buffer_pool_read_ahead_seq' => __( + 'The number of sequential read-aheads InnoDB initiated. This' + . ' happens when InnoDB does a sequential full table scan.' + ), + 'Innodb_buffer_pool_read_requests' => __( + 'The number of logical read requests InnoDB has done.' + ), + 'Innodb_buffer_pool_reads' => __( + 'The number of logical reads that InnoDB could not satisfy' + . ' from buffer pool and had to do a single-page read.' + ), + 'Innodb_buffer_pool_wait_free' => __( + 'Normally, writes to the InnoDB buffer pool happen in the' + . ' background. However, if it\'s necessary to read or create a page' + . ' and no clean pages are available, it\'s necessary to wait for' + . ' pages to be flushed first. This counter counts instances of' + . ' these waits. If the buffer pool size was set properly, this' + . ' value should be small.' + ), + 'Innodb_buffer_pool_write_requests' => __( + 'The number writes done to the InnoDB buffer pool.' + ), + 'Innodb_data_fsyncs' => __( + 'The number of fsync() operations so far.' + ), + 'Innodb_data_pending_fsyncs' => __( + 'The current number of pending fsync() operations.' + ), + 'Innodb_data_pending_reads' => __( + 'The current number of pending reads.' + ), + 'Innodb_data_pending_writes' => __( + 'The current number of pending writes.' + ), + 'Innodb_data_read' => __( + 'The amount of data read so far, in bytes.' + ), + 'Innodb_data_reads' => __( + 'The total number of data reads.' + ), + 'Innodb_data_writes' => __( + 'The total number of data writes.' + ), + 'Innodb_data_written' => __( + 'The amount of data written so far, in bytes.' + ), + 'Innodb_dblwr_pages_written' => __( + 'The number of pages that have been written for' + . ' doublewrite operations.' + ), + 'Innodb_dblwr_writes' => __( + 'The number of doublewrite operations that have been performed.' + ), + 'Innodb_log_waits' => __( + 'The number of waits we had because log buffer was too small and' + . ' we had to wait for it to be flushed before continuing.' + ), + 'Innodb_log_write_requests' => __( + 'The number of log write requests.' + ), + 'Innodb_log_writes' => __( + 'The number of physical writes to the log file.' + ), + 'Innodb_os_log_fsyncs' => __( + 'The number of fsync() writes done to the log file.' + ), + 'Innodb_os_log_pending_fsyncs' => __( + 'The number of pending log file fsyncs.' + ), + 'Innodb_os_log_pending_writes' => __( + 'Pending log file writes.' + ), + 'Innodb_os_log_written' => __( + 'The number of bytes written to the log file.' + ), + 'Innodb_pages_created' => __( + 'The number of pages created.' + ), + 'Innodb_page_size' => __( + 'The compiled-in InnoDB page size (default 16KB). Many values are' + . ' counted in pages; the page size allows them to be easily' + . ' converted to bytes.' + ), + 'Innodb_pages_read' => __( + 'The number of pages read.' + ), + 'Innodb_pages_written' => __( + 'The number of pages written.' + ), + 'Innodb_row_lock_current_waits' => __( + 'The number of row locks currently being waited for.' + ), + 'Innodb_row_lock_time_avg' => __( + 'The average time to acquire a row lock, in milliseconds.' + ), + 'Innodb_row_lock_time' => __( + 'The total time spent in acquiring row locks, in milliseconds.' + ), + 'Innodb_row_lock_time_max' => __( + 'The maximum time to acquire a row lock, in milliseconds.' + ), + 'Innodb_row_lock_waits' => __( + 'The number of times a row lock had to be waited for.' + ), + 'Innodb_rows_deleted' => __( + 'The number of rows deleted from InnoDB tables.' + ), + 'Innodb_rows_inserted' => __( + 'The number of rows inserted in InnoDB tables.' + ), + 'Innodb_rows_read' => __( + 'The number of rows read from InnoDB tables.' + ), + 'Innodb_rows_updated' => __( + 'The number of rows updated in InnoDB tables.' + ), + 'Key_blocks_not_flushed' => __( + 'The number of key blocks in the key cache that have changed but' + . ' haven\'t yet been flushed to disk. It used to be known as' + . ' Not_flushed_key_blocks.' + ), + 'Key_blocks_unused' => __( + 'The number of unused blocks in the key cache. You can use this' + . ' value to determine how much of the key cache is in use.' + ), + 'Key_blocks_used' => __( + 'The number of used blocks in the key cache. This value is a' + . ' high-water mark that indicates the maximum number of blocks' + . ' that have ever been in use at one time.' + ), + 'Key_buffer_fraction_%' => __( + 'Percentage of used key cache (calculated value)' + ), + 'Key_read_requests' => __( + 'The number of requests to read a key block from the cache.' + ), + 'Key_reads' => __( + 'The number of physical reads of a key block from disk. If Key_reads' + . ' is big, then your key_buffer_size value is probably too small.' + . ' The cache miss rate can be calculated as' + . ' Key_reads/Key_read_requests.' + ), + 'Key_read_ratio_%' => __( + 'Key cache miss calculated as rate of physical reads compared' + . ' to read requests (calculated value)' + ), + 'Key_write_requests' => __( + 'The number of requests to write a key block to the cache.' + ), + 'Key_writes' => __( + 'The number of physical writes of a key block to disk.' + ), + 'Key_write_ratio_%' => __( + 'Percentage of physical writes compared' + . ' to write requests (calculated value)' + ), + 'Last_query_cost' => __( + 'The total cost of the last compiled query as computed by the query' + . ' optimizer. Useful for comparing the cost of different query' + . ' plans for the same query. The default value of 0 means that' + . ' no query has been compiled yet.' + ), + 'Max_used_connections' => __( + 'The maximum number of connections that have been in use' + . ' simultaneously since the server started.' + ), + 'Not_flushed_delayed_rows' => __( + 'The number of rows waiting to be written in INSERT DELAYED queues.' + ), + 'Opened_tables' => __( + 'The number of tables that have been opened. If opened tables is' + . ' big, your table cache value is probably too small.' + ), + 'Open_files' => __( + 'The number of files that are open.' + ), + 'Open_streams' => __( + 'The number of streams that are open (used mainly for logging).' + ), + 'Open_tables' => __( + 'The number of tables that are open.' + ), + 'Qcache_free_blocks' => __( + 'The number of free memory blocks in query cache. High numbers can' + . ' indicate fragmentation issues, which may be solved by issuing' + . ' a FLUSH QUERY CACHE statement.' + ), + 'Qcache_free_memory' => __( + 'The amount of free memory for query cache.' + ), + 'Qcache_hits' => __( + 'The number of cache hits.' + ), + 'Qcache_inserts' => __( + 'The number of queries added to the cache.' + ), + 'Qcache_lowmem_prunes' => __( + 'The number of queries that have been removed from the cache to' + . ' free up memory for caching new queries. This information can' + . ' help you tune the query cache size. The query cache uses a' + . ' least recently used (LRU) strategy to decide which queries' + . ' to remove from the cache.' + ), + 'Qcache_not_cached' => __( + 'The number of non-cached queries (not cachable, or not cached' + . ' due to the query_cache_type setting).' + ), + 'Qcache_queries_in_cache' => __( + 'The number of queries registered in the cache.' + ), + 'Qcache_total_blocks' => __( + 'The total number of blocks in the query cache.' + ), + 'Rpl_status' => __( + 'The status of failsafe replication (not yet implemented).' + ), + 'Select_full_join' => __( + 'The number of joins that do not use indexes. If this value is' + . ' not 0, you should carefully check the indexes of your tables.' + ), + 'Select_full_range_join' => __( + 'The number of joins that used a range search on a reference table.' + ), + 'Select_range_check' => __( + 'The number of joins without keys that check for key usage after' + . ' each row. (If this is not 0, you should carefully check the' + . ' indexes of your tables.)' + ), + 'Select_range' => __( + 'The number of joins that used ranges on the first table. (It\'s' + . ' normally not critical even if this is big.)' + ), + 'Select_scan' => __( + 'The number of joins that did a full scan of the first table.' + ), + 'Slave_open_temp_tables' => __( + 'The number of temporary tables currently' + . ' open by the slave SQL thread.' + ), + 'Slave_retried_transactions' => __( + 'Total (since startup) number of times the replication slave SQL' + . ' thread has retried transactions.' + ), + 'Slave_running' => __( + 'This is ON if this server is a slave that is connected to a master.' + ), + 'Slow_launch_threads' => __( + 'The number of threads that have taken more than slow_launch_time' + . ' seconds to create.' + ), + 'Slow_queries' => __( + 'The number of queries that have taken more than long_query_time' + . ' seconds.' + ), + 'Sort_merge_passes' => __( + 'The number of merge passes the sort algorithm has had to do.' + . ' If this value is large, you should consider increasing the' + . ' value of the sort_buffer_size system variable.' + ), + 'Sort_range' => __( + 'The number of sorts that were done with ranges.' + ), + 'Sort_rows' => __( + 'The number of sorted rows.' + ), + 'Sort_scan' => __( + 'The number of sorts that were done by scanning the table.' + ), + 'Table_locks_immediate' => __( + 'The number of times that a table lock was acquired immediately.' + ), + 'Table_locks_waited' => __( + 'The number of times that a table lock could not be acquired' + . ' immediately and a wait was needed. If this is high, and you have' + . ' performance problems, you should first optimize your queries,' + . ' and then either split your table or tables or use replication.' + ), + 'Threads_cached' => __( + 'The number of threads in the thread cache. The cache hit rate can' + . ' be calculated as Threads_created/Connections. If this value is' + . ' red you should raise your thread_cache_size.' + ), + 'Threads_connected' => __( + 'The number of currently open connections.' + ), + 'Threads_created' => __( + 'The number of threads created to handle connections. If' + . ' Threads_created is big, you may want to increase the' + . ' thread_cache_size value. (Normally this doesn\'t give a notable' + . ' performance improvement if you have a good thread' + . ' implementation.)' + ), + 'Threads_cache_hitrate_%' => __( + 'Thread cache hit rate (calculated value)' + ), + 'Threads_running' => __( + 'The number of threads that are not sleeping.' + ) + ); + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Server/UserGroups.php b/php/apps/phpmyadmin49/libraries/classes/Server/UserGroups.php new file mode 100644 index 00000000..337cd9a9 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Server/UserGroups.php @@ -0,0 +1,379 @@ +' + . sprintf(__('Users of \'%s\' user group'), htmlspecialchars($userGroup)) + . ''; + + $cfgRelation = $relation->getRelationsParam(); + $usersTable = Util::backquote($cfgRelation['db']) + . "." . Util::backquote($cfgRelation['users']); + $sql_query = "SELECT `username` FROM " . $usersTable + . " WHERE `usergroup`='" . $GLOBALS['dbi']->escapeString($userGroup) + . "'"; + $result = $relation->queryAsControlUser($sql_query, false); + if ($result) { + if ($GLOBALS['dbi']->numRows($result) == 0) { + $html_output .= '

      ' + . __('No users were found belonging to this user group.') + . '

      '; + } else { + $html_output .= '' + . '' + . ''; + $i = 0; + while ($row = $GLOBALS['dbi']->fetchRow($result)) { + $i++; + $html_output .= '' + . '' + . '' + . ''; + } + $html_output .= '' + . '
      #' . __('User') . '
      ' . $i . ' ' . htmlspecialchars($row[0]) . '
      '; + } + } + $GLOBALS['dbi']->freeResult($result); + return $html_output; + } + + /** + * Returns HTML for the 'user groups' table + * + * @return string HTML for the 'user groups' table + */ + public static function getHtmlForUserGroupsTable() + { + $relation = new Relation(); + $html_output = '

      ' . __('User groups') . '

      '; + $cfgRelation = $relation->getRelationsParam(); + $groupTable = Util::backquote($cfgRelation['db']) + . "." . Util::backquote($cfgRelation['usergroups']); + $sql_query = "SELECT * FROM " . $groupTable . " ORDER BY `usergroup` ASC"; + $result = $relation->queryAsControlUser($sql_query, false); + + if ($result && $GLOBALS['dbi']->numRows($result)) { + $html_output .= '
      '; + $html_output .= Url::getHiddenInputs(); + $html_output .= ''; + $html_output .= ''; + $html_output .= ''; + $html_output .= ''; + $html_output .= ''; + $html_output .= ''; + $html_output .= ''; + $html_output .= ''; + $html_output .= ''; + + $userGroups = array(); + while ($row = $GLOBALS['dbi']->fetchAssoc($result)) { + $groupName = $row['usergroup']; + if (! isset($userGroups[$groupName])) { + $userGroups[$groupName] = array(); + } + $userGroups[$groupName][$row['tab']] = $row['allowed']; + } + foreach ($userGroups as $groupName => $tabs) { + $html_output .= ''; + $html_output .= ''; + $html_output .= ''; + $html_output .= ''; + $html_output .= ''; + + $html_output .= ''; + + $html_output .= ''; + } + + $html_output .= ''; + $html_output .= '
      ' + . __('User group') . '' . __('Server level tabs') . '' . __('Database level tabs') . '' . __('Table level tabs') . '' . __('Action') . '
      ' . htmlspecialchars($groupName) . '' . self::getAllowedTabNames($tabs, 'server') . '' . self::getAllowedTabNames($tabs, 'db') . '' . self::getAllowedTabNames($tabs, 'table') . ''; + $html_output .= '' + . Util::getIcon('b_usrlist', __('View users')) + . ''; + $html_output .= '  '; + $html_output .= '' + . Util::getIcon('b_edit', __('Edit')) . ''; + $html_output .= '  '; + $html_output .= '' + . Util::getIcon('b_drop', __('Delete')) . ''; + $html_output .= '
      '; + $html_output .= '
      '; + } + $GLOBALS['dbi']->freeResult($result); + + $html_output .= '
      '; + $html_output .= '' + . Util::getIcon('b_usradd') + . __('Add user group') . ''; + $html_output .= '
      '; + + return $html_output; + } + + /** + * Returns the list of allowed menu tab names + * based on a data row from usergroup table. + * + * @param array $row row of usergroup table + * @param string $level 'server', 'db' or 'table' + * + * @return string comma separated list of allowed menu tab names + */ + public static function getAllowedTabNames(array $row, $level) + { + $tabNames = array(); + $tabs = Util::getMenuTabList($level); + foreach ($tabs as $tab => $tabName) { + if (! isset($row[$level . '_' . $tab]) + || $row[$level . '_' . $tab] == 'Y' + ) { + $tabNames[] = $tabName; + } + } + return implode(', ', $tabNames); + } + + /** + * Deletes a user group + * + * @param string $userGroup user group name + * + * @return void + */ + public static function delete($userGroup) + { + $relation = new Relation(); + $cfgRelation = $relation->getRelationsParam(); + $userTable = Util::backquote($cfgRelation['db']) + . "." . Util::backquote($cfgRelation['users']); + $groupTable = Util::backquote($cfgRelation['db']) + . "." . Util::backquote($cfgRelation['usergroups']); + $sql_query = "DELETE FROM " . $userTable + . " WHERE `usergroup`='" . $GLOBALS['dbi']->escapeString($userGroup) + . "'"; + $relation->queryAsControlUser($sql_query, true); + $sql_query = "DELETE FROM " . $groupTable + . " WHERE `usergroup`='" . $GLOBALS['dbi']->escapeString($userGroup) + . "'"; + $relation->queryAsControlUser($sql_query, true); + } + + /** + * Returns HTML for add/edit user group dialog + * + * @param string $userGroup name of the user group in case of editing + * + * @return string HTML for add/edit user group dialog + */ + public static function getHtmlToEditUserGroup($userGroup = null) + { + $relation = new Relation(); + $html_output = ''; + if ($userGroup == null) { + $html_output .= '

      ' . __('Add user group') . '

      '; + } else { + $html_output .= '

      ' + . sprintf(__('Edit user group: \'%s\''), htmlspecialchars($userGroup)) + . '

      '; + } + + $html_output .= '
      '; + $urlParams = array(); + if ($userGroup != null) { + $urlParams['userGroup'] = $userGroup; + $urlParams['editUserGroupSubmit'] = '1'; + } else { + $urlParams['addUserGroupSubmit'] = '1'; + } + $html_output .= Url::getHiddenInputs($urlParams); + + $html_output .= '
      '; + $html_output .= '' . __('User group menu assignments') + . '   ' + . '' + . '' + . ''; + + if ($userGroup == null) { + $html_output .= ''; + $html_output .= ''; + $html_output .= '
      '; + } + + $allowedTabs = array( + 'server' => array(), + 'db' => array(), + 'table' => array() + ); + if ($userGroup != null) { + $cfgRelation = $relation->getRelationsParam(); + $groupTable = Util::backquote($cfgRelation['db']) + . "." . Util::backquote($cfgRelation['usergroups']); + $sql_query = "SELECT * FROM " . $groupTable + . " WHERE `usergroup`='" . $GLOBALS['dbi']->escapeString($userGroup) + . "'"; + $result = $relation->queryAsControlUser($sql_query, false); + if ($result) { + while ($row = $GLOBALS['dbi']->fetchAssoc($result)) { + $key = $row['tab']; + $value = $row['allowed']; + if (substr($key, 0, 7) == 'server_' && $value == 'Y') { + $allowedTabs['server'][] = mb_substr($key, 7); + } elseif (substr($key, 0, 3) == 'db_' && $value == 'Y') { + $allowedTabs['db'][] = mb_substr($key, 3); + } elseif (substr($key, 0, 6) == 'table_' + && $value == 'Y' + ) { + $allowedTabs['table'][] = mb_substr($key, 6); + } + } + } + $GLOBALS['dbi']->freeResult($result); + } + + $html_output .= self::getTabList( + __('Server-level tabs'), 'server', $allowedTabs['server'] + ); + $html_output .= self::getTabList( + __('Database-level tabs'), 'db', $allowedTabs['db'] + ); + $html_output .= self::getTabList( + __('Table-level tabs'), 'table', $allowedTabs['table'] + ); + + $html_output .= '
      '; + + $html_output .= ''; + + return $html_output; + } + + /** + * Returns HTML for checkbox groups to choose + * tabs of 'server', 'db' or 'table' levels. + * + * @param string $title title of the checkbox group + * @param string $level 'server', 'db' or 'table' + * @param array $selected array of selected allowed tabs + * + * @return string HTML for checkbox groups + */ + public static function getTabList($title, $level, array $selected) + { + $tabs = Util::getMenuTabList($level); + $html_output = '
      '; + $html_output .= '' . $title . ''; + foreach ($tabs as $tab => $tabName) { + $html_output .= '
      '; + $html_output .= ''; + $html_output .= ''; + $html_output .= '
      '; + } + $html_output .= '
      '; + return $html_output; + } + + /** + * Add/update a user group with allowed menu tabs. + * + * @param string $userGroup user group name + * @param boolean $new whether this is a new user group + * + * @return void + */ + public static function edit($userGroup, $new = false) + { + $relation = new Relation(); + $tabs = Util::getMenuTabList(); + $cfgRelation = $relation->getRelationsParam(); + $groupTable = Util::backquote($cfgRelation['db']) + . "." . Util::backquote($cfgRelation['usergroups']); + + if (! $new) { + $sql_query = "DELETE FROM " . $groupTable + . " WHERE `usergroup`='" . $GLOBALS['dbi']->escapeString($userGroup) + . "';"; + $relation->queryAsControlUser($sql_query, true); + } + + $sql_query = "INSERT INTO " . $groupTable + . "(`usergroup`, `tab`, `allowed`)" + . " VALUES "; + $first = true; + foreach ($tabs as $tabGroupName => $tabGroup) { + foreach ($tabGroup as $tab => $tabName) { + if (! $first) { + $sql_query .= ", "; + } + $tabName = $tabGroupName . '_' . $tab; + $allowed = isset($_POST[$tabName]) && $_POST[$tabName] == 'Y'; + $sql_query .= "('" . $GLOBALS['dbi']->escapeString($userGroup) . "', '" . $tabName . "', '" + . ($allowed ? "Y" : "N") . "')"; + $first = false; + } + } + $sql_query .= ";"; + $relation->queryAsControlUser($sql_query, true); + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Server/Users.php b/php/apps/phpmyadmin49/libraries/classes/Server/Users.php new file mode 100644 index 00000000..bd505898 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Server/Users.php @@ -0,0 +1,62 @@ + __('User accounts overview'), + 'url' => 'server_privileges.php', + 'params' => Url::getCommon(array('viewing_mode' => 'server')), + ) + ); + + if ($GLOBALS['dbi']->isSuperuser()) { + $items[] = array( + 'name' => __('User groups'), + 'url' => 'server_user_groups.php', + 'params' => Url::getCommon(), + ); + } + + $retval = '
        '; + foreach ($items as $item) { + $class = ''; + if ($item['url'] === $selfUrl) { + $class = ' class="tabactive"'; + } + $retval .= '
      • '; + $retval .= ''; + $retval .= $item['name']; + $retval .= ''; + $retval .= '
      • '; + } + $retval .= '
      '; + $retval .= '
      '; + + return $retval; + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Session.php b/php/apps/phpmyadmin49/libraries/classes/Session.php new file mode 100644 index 00000000..7e4cf628 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Session.php @@ -0,0 +1,236 @@ +getMessage()) + ); + } + + /* + * Session initialization is done before selecting language, so we + * can not use translations here. + */ + Core::fatalError( + 'Error during session start; please check your PHP and/or ' + . 'webserver log file and configure your PHP ' + . 'installation properly. Also ensure that cookies are enabled ' + . 'in your browser.' + . '

      ' + . implode('

      ', $messages) + ); + } + + /** + * Set up session + * + * @param PhpMyAdmin\Config $config Configuration handler + * @param PhpMyAdmin\ErrorHandler $errorHandler Error handler + * @return void + */ + public static function setUp(Config $config, ErrorHandler $errorHandler) + { + // verify if PHP supports session, die if it does not + if (!function_exists('session_name')) { + Core::warnMissingExtension('session', true); + } elseif (! empty(ini_get('session.auto_start')) + && session_name() != 'phpMyAdmin' + && !empty(session_id())) { + // Do not delete the existing non empty session, it might be used by + // other applications; instead just close it. + if (empty($_SESSION)) { + // Ignore errors as this might have been destroyed in other + // request meanwhile + @session_destroy(); + } elseif (function_exists('session_abort')) { + // PHP 5.6 and newer + session_abort(); + } else { + session_write_close(); + } + } + + // session cookie settings + session_set_cookie_params( + 0, $config->getRootPath(), + '', $config->isHttps(), true + ); + + // cookies are safer (use ini_set() in case this function is disabled) + ini_set('session.use_cookies', 'true'); + + // optionally set session_save_path + $path = $config->get('SessionSavePath'); + if (!empty($path)) { + session_save_path($path); + // We can not do this unconditionally as this would break + // any more complex setup (eg. cluster), see + // https://github.com/phpmyadmin/phpmyadmin/issues/8346 + ini_set('session.save_handler', 'files'); + } + + // use cookies only + ini_set('session.use_only_cookies', '1'); + // strict session mode (do not accept random string as session ID) + ini_set('session.use_strict_mode', '1'); + // make the session cookie HttpOnly + ini_set('session.cookie_httponly', '1'); + // do not force transparent session ids + ini_set('session.use_trans_sid', '0'); + + // delete session/cookies when browser is closed + ini_set('session.cookie_lifetime', '0'); + + // warn but don't work with bug + ini_set('session.bug_compat_42', 'false'); + ini_set('session.bug_compat_warn', 'true'); + + // use more secure session ids + ini_set('session.hash_function', '1'); + + // some pages (e.g. stylesheet) may be cached on clients, but not in shared + // proxy servers + session_cache_limiter('private'); + + $session_name = 'phpMyAdmin'; + @session_name($session_name); + + // Restore correct sesion ID (it might have been reset by auto started session + if (isset($_COOKIE['phpMyAdmin'])) { + session_id($_COOKIE['phpMyAdmin']); + } + + // on first start of session we check for errors + // f.e. session dir cannot be accessed - session file not created + $orig_error_count = $errorHandler->countErrors(false); + + $session_result = session_start(); + + if ($session_result !== true + || $orig_error_count != $errorHandler->countErrors(false) + ) { + setcookie($session_name, '', 1); + $errors = $errorHandler->sliceErrors($orig_error_count); + self::sessionFailed($errors); + } + unset($orig_error_count, $session_result); + + /** + * Disable setting of session cookies for further session_start() calls. + */ + if(session_status() !== PHP_SESSION_ACTIVE) { + ini_set('session.use_cookies', 'true'); + } + + /** + * Token which is used for authenticating access queries. + * (we use "space PMA_token space" to prevent overwriting) + */ + if (empty($_SESSION[' PMA_token '])) { + self::generateToken(); + + /** + * Check for disk space on session storage by trying to write it. + * + * This seems to be most reliable approach to test if sessions are working, + * otherwise the check would fail with custom session backends. + */ + $orig_error_count = $errorHandler->countErrors(); + session_write_close(); + if ($errorHandler->countErrors() > $orig_error_count) { + $errors = $errorHandler->sliceErrors($orig_error_count); + self::sessionFailed($errors); + } + session_start(); + if (empty($_SESSION[' PMA_token '])) { + Core::fatalError( + 'Failed to store CSRF token in session! ' . + 'Probably sessions are not working properly.' + ); + } + } + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Sql.php b/php/apps/phpmyadmin49/libraries/classes/Sql.php new file mode 100644 index 00000000..58cceb74 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Sql.php @@ -0,0 +1,2326 @@ +relation = new Relation(); + } + + /** + * Parses and analyzes the given SQL query. + * + * @param string $sql_query SQL query + * @param string $db DB name + * + * @return mixed + */ + public function parseAndAnalyze($sql_query, $db = null) + { + if (is_null($db) && isset($GLOBALS['db']) && strlen($GLOBALS['db'])) { + $db = $GLOBALS['db']; + } + list($analyzed_sql_results,,) = ParseAnalyze::sqlQuery($sql_query, $db); + return $analyzed_sql_results; + } + + /** + * Handle remembered sorting order, only for single table query + * + * @param string $db database name + * @param string $table table name + * @param array &$analyzed_sql_results the analyzed query results + * @param string &$full_sql_query SQL query + * + * @return void + */ + private function handleSortOrder( + $db, $table, array &$analyzed_sql_results, &$full_sql_query + ) { + $pmatable = new Table($table, $db); + + if (empty($analyzed_sql_results['order'])) { + + // Retrieving the name of the column we should sort after. + $sortCol = $pmatable->getUiProp(Table::PROP_SORTED_COLUMN); + if (empty($sortCol)) { + return; + } + + // Remove the name of the table from the retrieved field name. + $sortCol = str_replace( + Util::backquote($table) . '.', + '', + $sortCol + ); + + // Create the new query. + $full_sql_query = Query::replaceClause( + $analyzed_sql_results['statement'], + $analyzed_sql_results['parser']->list, + 'ORDER BY ' . $sortCol + ); + + // TODO: Avoid reparsing the query. + $analyzed_sql_results = Query::getAll($full_sql_query); + } else { + // Store the remembered table into session. + $pmatable->setUiProp( + Table::PROP_SORTED_COLUMN, + Query::getClause( + $analyzed_sql_results['statement'], + $analyzed_sql_results['parser']->list, + 'ORDER BY' + ) + ); + } + } + + /** + * Append limit clause to SQL query + * + * @param array &$analyzed_sql_results the analyzed query results + * + * @return string limit clause appended SQL query + */ + private function getSqlWithLimitClause(array &$analyzed_sql_results) + { + return Query::replaceClause( + $analyzed_sql_results['statement'], + $analyzed_sql_results['parser']->list, + 'LIMIT ' . $_SESSION['tmpval']['pos'] . ', ' + . $_SESSION['tmpval']['max_rows'] + ); + } + + /** + * Verify whether the result set has columns from just one table + * + * @param array $fields_meta meta fields + * + * @return boolean whether the result set has columns from just one table + */ + private function resultSetHasJustOneTable(array $fields_meta) + { + $just_one_table = true; + $prev_table = ''; + foreach ($fields_meta as $one_field_meta) { + if ($one_field_meta->table != '' + && $prev_table != '' + && $one_field_meta->table != $prev_table + ) { + $just_one_table = false; + } + if ($one_field_meta->table != '') { + $prev_table = $one_field_meta->table; + } + } + return $just_one_table && $prev_table != ''; + } + + /** + * Verify whether the result set contains all the columns + * of at least one unique key + * + * @param string $db database name + * @param string $table table name + * @param array $fields_meta meta fields + * + * @return boolean whether the result set contains a unique key + */ + private function resultSetContainsUniqueKey($db, $table, array $fields_meta) + { + $columns = $GLOBALS['dbi']->getColumns($db, $table); + $resultSetColumnNames = array(); + foreach ($fields_meta as $oneMeta) { + $resultSetColumnNames[] = $oneMeta->name; + } + foreach (Index::getFromTable($table, $db) as $index) { + if ($index->isUnique()) { + $indexColumns = $index->getColumns(); + $numberFound = 0; + foreach ($indexColumns as $indexColumnName => $dummy) { + if (in_array($indexColumnName, $resultSetColumnNames)) { + $numberFound++; + } else if (!in_array($indexColumnName, $columns)) { + $numberFound++; + } else if (strpos($columns[$indexColumnName]['Extra'], 'INVISIBLE') !== false) { + $numberFound++; + } + } + if ($numberFound == count($indexColumns)) { + return true; + } + } + } + return false; + } + + /** + * Get the HTML for relational column dropdown + * During grid edit, if we have a relational field, returns the html for the + * dropdown + * + * @param string $db current database + * @param string $table current table + * @param string $column current column + * @param string $curr_value current selected value + * + * @return string $dropdown html for the dropdown + */ + private function getHtmlForRelationalColumnDropdown($db, $table, $column, $curr_value) + { + $foreigners = $this->relation->getForeigners($db, $table, $column); + + $foreignData = $this->relation->getForeignData($foreigners, $column, false, '', ''); + + if ($foreignData['disp_row'] == null) { + //Handle the case when number of values + //is more than $cfg['ForeignKeyMaxLimit'] + $_url_params = array( + 'db' => $db, + 'table' => $table, + 'field' => $column + ); + + $dropdown = '' + . htmlspecialchars($_POST['curr_value']) + . '' + . '' + . __('Browse foreign values') + . ''; + } else { + $dropdown = $this->relation->foreignDropdown( + $foreignData['disp_row'], + $foreignData['foreign_field'], + $foreignData['foreign_display'], + $curr_value, + $GLOBALS['cfg']['ForeignKeyMaxLimit'] + ); + $dropdown = ''; + } + + return $dropdown; + } + + /** + * Get the HTML for the profiling table and accompanying chart if profiling is set. + * Otherwise returns null + * + * @param string $url_query url query + * @param string $db current database + * @param array $profiling_results array containing the profiling info + * + * @return string $profiling_table html for the profiling table and chart + */ + private function getHtmlForProfilingChart($url_query, $db, $profiling_results) + { + if (! empty($profiling_results)) { + $url_query = isset($url_query) + ? $url_query + : Url::getCommon(array('db' => $db)); + + $profiling_table = ''; + $profiling_table .= '
      ' . __('Profiling') + . '' . "\n"; + $profiling_table .= '
      '; + $profiling_table .= '

      ' . __('Detailed profile') . '

      '; + $profiling_table .= '' . "\n"; + $profiling_table .= ' ' . "\n"; + $profiling_table .= ' ' . "\n"; + $profiling_table .= ' ' . "\n"; + $profiling_table .= ' ' . "\n"; + $profiling_table .= ' ' . "\n"; + list($detailed_table, $chart_json, $profiling_stats) + = $this->analyzeAndGetTableHtmlForProfilingResults($profiling_results); + $profiling_table .= $detailed_table; + $profiling_table .= '
      ' . __('Order') + . '
      ' . __('State') + . Util::showMySQLDocu('general-thread-states') + . '
      ' . __('Time') + . '
      ' . "\n"; + $profiling_table .= '
      '; + + $profiling_table .= '
      '; + $profiling_table .= '

      ' . __('Summary by state') . '

      '; + $profiling_table .= '' . "\n"; + $profiling_table .= ' ' . "\n"; + $profiling_table .= ' ' . "\n"; + $profiling_table .= ' ' . "\n"; + $profiling_table .= ' ' . "\n"; + $profiling_table .= ' ' . "\n"; + $profiling_table .= ' ' . "\n"; + $profiling_table .= ' ' . "\n"; + $profiling_table .= $this->getTableHtmlForProfilingSummaryByState( + $profiling_stats + ); + $profiling_table .= '
      ' . __('State') + . Util::showMySQLDocu('general-thread-states') + . '
      ' . __('Total Time') + . '
      ' . __('% Time') + . '
      ' . __('Calls') + . '
      ' . __('ø Time') + . '
      ' . "\n"; + + $profiling_table .= << + url_query = '$url_query'; + +EOT; + $profiling_table .= "
      "; + $profiling_table .= "
      "; + + //require_once 'libraries/chart.lib.php'; + $profiling_table .= '
      '; + $profiling_table .= json_encode($chart_json); + $profiling_table .= '
      '; + $profiling_table .= '
      '; + $profiling_table .= '
      '; + $profiling_table .= ''; + $profiling_table .= '
      ' . "\n"; + } else { + $profiling_table = null; + } + return $profiling_table; + } + + /** + * Function to get HTML for detailed profiling results table, profiling stats, and + * $chart_json for displaying the chart. + * + * @param array $profiling_results profiling results + * + * @return mixed + */ + private function analyzeAndGetTableHtmlForProfilingResults( + $profiling_results + ) { + $profiling_stats = array( + 'total_time' => 0, + 'states' => array(), + ); + $chart_json = Array(); + $i = 1; + $table = ''; + foreach ($profiling_results as $one_result) { + if (isset($profiling_stats['states'][ucwords($one_result['Status'])])) { + $states = $profiling_stats['states']; + $states[ucwords($one_result['Status'])]['total_time'] + += $one_result['Duration']; + $states[ucwords($one_result['Status'])]['calls']++; + } else { + $profiling_stats['states'][ucwords($one_result['Status'])] = array( + 'total_time' => $one_result['Duration'], + 'calls' => 1, + ); + } + $profiling_stats['total_time'] += $one_result['Duration']; + + $table .= ' ' . "\n"; + $table .= '' . $i++ . '' . "\n"; + $table .= '' . ucwords($one_result['Status']) + . '' . "\n"; + $table .= '' + . (Util::formatNumber($one_result['Duration'], 3, 1)) + . 's' + . $one_result['Duration'] . '' . "\n"; + if (isset($chart_json[ucwords($one_result['Status'])])) { + $chart_json[ucwords($one_result['Status'])] + += $one_result['Duration']; + } else { + $chart_json[ucwords($one_result['Status'])] + = $one_result['Duration']; + } + } + return array($table, $chart_json, $profiling_stats); + } + + /** + * Function to get HTML for summary by state table + * + * @param array $profiling_stats profiling stats + * + * @return string $table html for the table + */ + private function getTableHtmlForProfilingSummaryByState(array $profiling_stats) + { + $table = ''; + foreach ($profiling_stats['states'] as $name => $stats) { + $table .= ' ' . "\n"; + $table .= '' . $name . '' . "\n"; + $table .= '' + . Util::formatNumber($stats['total_time'], 3, 1) + . 's' + . $stats['total_time'] . '' . "\n"; + $table .= '' + . Util::formatNumber( + 100 * ($stats['total_time'] / $profiling_stats['total_time']), + 0, 2 + ) + . '%' . "\n"; + $table .= '' . $stats['calls'] . '' + . "\n"; + $table .= '' + . Util::formatNumber( + $stats['total_time'] / $stats['calls'], 3, 1 + ) + . 's' + . number_format($stats['total_time'] / $stats['calls'], 8, '.', '') + . '' . "\n"; + $table .= ' ' . "\n"; + } + return $table; + } + + /** + * Get the HTML for the enum column dropdown + * During grid edit, if we have a enum field, returns the html for the + * dropdown + * + * @param string $db current database + * @param string $table current table + * @param string $column current column + * @param string $curr_value currently selected value + * + * @return string $dropdown html for the dropdown + */ + private function getHtmlForEnumColumnDropdown($db, $table, $column, $curr_value) + { + $values = $this->getValuesForColumn($db, $table, $column); + $dropdown = ''; + $dropdown .= $this->getHtmlForOptionsList($values, array($curr_value)); + $dropdown = ''; + return $dropdown; + } + + /** + * Get value of a column for a specific row (marked by $where_clause) + * + * @param string $db current database + * @param string $table current table + * @param string $column current column + * @param string $where_clause where clause to select a particular row + * + * @return string with value + */ + private function getFullValuesForSetColumn($db, $table, $column, $where_clause) + { + $result = $GLOBALS['dbi']->fetchSingleRow( + "SELECT `$column` FROM `$db`.`$table` WHERE $where_clause" + ); + + return $result[$column]; + } + + /** + * Get the HTML for the set column dropdown + * During grid edit, if we have a set field, returns the html for the + * dropdown + * + * @param string $db current database + * @param string $table current table + * @param string $column current column + * @param string $curr_value currently selected value + * + * @return string $dropdown html for the set column + */ + private function getHtmlForSetColumn($db, $table, $column, $curr_value) + { + $values = $this->getValuesForColumn($db, $table, $column); + $dropdown = ''; + $full_values = + isset($_POST['get_full_values']) ? $_POST['get_full_values'] : false; + $where_clause = + isset($_POST['where_clause']) ? $_POST['where_clause'] : null; + + // If the $curr_value was truncated, we should + // fetch the correct full values from the table + if ($full_values && ! empty($where_clause)) { + $curr_value = $this->getFullValuesForSetColumn( + $db, $table, $column, $where_clause + ); + } + + //converts characters of $curr_value to HTML entities + $converted_curr_value = htmlentities( + $curr_value, ENT_COMPAT, "UTF-8" + ); + + $selected_values = explode(',', $converted_curr_value); + + $dropdown .= $this->getHtmlForOptionsList($values, $selected_values); + + $select_size = (sizeof($values) > 10) ? 10 : sizeof($values); + $dropdown = ''; + + return $dropdown; + } + + /** + * Get all the values for a enum column or set column in a table + * + * @param string $db current database + * @param string $table current table + * @param string $column current column + * + * @return array $values array containing the value list for the column + */ + private function getValuesForColumn($db, $table, $column) + { + $field_info_query = $GLOBALS['dbi']->getColumnsSql($db, $table, $column); + + $field_info_result = $GLOBALS['dbi']->fetchResult( + $field_info_query, + null, + null, + DatabaseInterface::CONNECT_USER, + DatabaseInterface::QUERY_STORE + ); + + $values = Util::parseEnumSetValues($field_info_result[0]['Type']); + + return $values; + } + + /** + * Get HTML for options list + * + * @param array $values set of values + * @param array $selected_values currently selected values + * + * @return string $options HTML for options list + */ + private function getHtmlForOptionsList(array $values, array $selected_values) + { + $options = ''; + foreach ($values as $value) { + $options .= '
      '; + + } else { + $html = null; + } + + return $html; + } + + /** + * Function to check whether to remember the sorting order or not + * + * @param array $analyzed_sql_results the analyzed query and other variables set + * after analyzing the query + * + * @return boolean + */ + private function isRememberSortingOrder(array $analyzed_sql_results) + { + return $GLOBALS['cfg']['RememberSorting'] + && ! ($analyzed_sql_results['is_count'] + || $analyzed_sql_results['is_export'] + || $analyzed_sql_results['is_func'] + || $analyzed_sql_results['is_analyse']) + && $analyzed_sql_results['select_from'] + && isset($analyzed_sql_results['select_expr']) + && isset($analyzed_sql_results['select_tables']) + && ((empty($analyzed_sql_results['select_expr'])) + || ((count($analyzed_sql_results['select_expr']) == 1) + && ($analyzed_sql_results['select_expr'][0] == '*'))) + && count($analyzed_sql_results['select_tables']) == 1; + } + + /** + * Function to check whether the LIMIT clause should be appended or not + * + * @param array $analyzed_sql_results the analyzed query and other variables set + * after analyzing the query + * + * @return boolean + */ + private function isAppendLimitClause(array $analyzed_sql_results) + { + // Assigning LIMIT clause to an syntactically-wrong query + // is not needed. Also we would want to show the true query + // and the true error message to the query executor + + return (isset($analyzed_sql_results['parser']) + && count($analyzed_sql_results['parser']->errors) === 0) + && ($_SESSION['tmpval']['max_rows'] != 'all') + && ! ($analyzed_sql_results['is_export'] + || $analyzed_sql_results['is_analyse']) + && ($analyzed_sql_results['select_from'] + || $analyzed_sql_results['is_subquery']) + && empty($analyzed_sql_results['limit']); + } + + /** + * Function to check whether this query is for just browsing + * + * @param array $analyzed_sql_results the analyzed query and other variables set + * after analyzing the query + * @param boolean $find_real_end whether the real end should be found + * + * @return boolean + */ + public function isJustBrowsing(array $analyzed_sql_results, $find_real_end) + { + return ! $analyzed_sql_results['is_group'] + && ! $analyzed_sql_results['is_func'] + && empty($analyzed_sql_results['union']) + && empty($analyzed_sql_results['distinct']) + && $analyzed_sql_results['select_from'] + && (count($analyzed_sql_results['select_tables']) === 1) + && (empty($analyzed_sql_results['statement']->where) + || (count($analyzed_sql_results['statement']->where) == 1 + && $analyzed_sql_results['statement']->where[0]->expr ==='1')) + && empty($analyzed_sql_results['group']) + && ! isset($find_real_end) + && ! $analyzed_sql_results['is_subquery'] + && ! $analyzed_sql_results['join'] + && empty($analyzed_sql_results['having']); + } + + /** + * Function to check whether the related transformation information should be deleted + * + * @param array $analyzed_sql_results the analyzed query and other variables set + * after analyzing the query + * + * @return boolean + */ + private function isDeleteTransformationInfo(array $analyzed_sql_results) + { + return !empty($analyzed_sql_results['querytype']) + && (($analyzed_sql_results['querytype'] == 'ALTER') + || ($analyzed_sql_results['querytype'] == 'DROP')); + } + + /** + * Function to check whether the user has rights to drop the database + * + * @param array $analyzed_sql_results the analyzed query and other variables set + * after analyzing the query + * @param boolean $allowUserDropDatabase whether the user is allowed to drop db + * @param boolean $is_superuser whether this user is a superuser + * + * @return boolean + */ + public function hasNoRightsToDropDatabase(array $analyzed_sql_results, + $allowUserDropDatabase, $is_superuser + ) { + return ! $allowUserDropDatabase + && isset($analyzed_sql_results['drop_database']) + && $analyzed_sql_results['drop_database'] + && ! $is_superuser; + } + + /** + * Function to set a column property + * + * @param Table $pmatable Table instance + * @param string $request_index col_order|col_visib + * + * @return boolean $retval + */ + private function setColumnProperty($pmatable, $request_index) + { + $property_value = array_map('intval', explode(',', $_POST[$request_index])); + switch($request_index) { + case 'col_order': + $property_to_set = Table::PROP_COLUMN_ORDER; + break; + case 'col_visib': + $property_to_set = Table::PROP_COLUMN_VISIB; + break; + default: + $property_to_set = ''; + } + $retval = $pmatable->setUiProp( + $property_to_set, + $property_value, + $_POST['table_create_time'] + ); + if (gettype($retval) != 'boolean') { + $response = Response::getInstance(); + $response->setRequestStatus(false); + $response->addJSON('message', $retval->getString()); + exit; + } + + return $retval; + } + + /** + * Function to check the request for setting the column order or visibility + * + * @param string $table the current table + * @param string $db the current database + * + * @return void + */ + public function setColumnOrderOrVisibility($table, $db) + { + $pmatable = new Table($table, $db); + $retval = false; + + // set column order + if (isset($_POST['col_order'])) { + $retval = $this->setColumnProperty($pmatable, 'col_order'); + } + + // set column visibility + if ($retval === true && isset($_POST['col_visib'])) { + $retval = $this->setColumnProperty($pmatable, 'col_visib'); + } + + $response = Response::getInstance(); + $response->setRequestStatus($retval == true); + exit; + } + + /** + * Function to add a bookmark + * + * @param string $goto goto page URL + * + * @return void + */ + public function addBookmark($goto) + { + $bookmark = Bookmark::createBookmark( + $GLOBALS['dbi'], + $GLOBALS['cfg']['Server']['user'], + $_POST['bkm_fields'], + (isset($_POST['bkm_all_users']) + && $_POST['bkm_all_users'] == 'true' ? true : false + ) + ); + $result = $bookmark->save(); + $response = Response::getInstance(); + if ($response->isAjax()) { + if ($result) { + $msg = Message::success(__('Bookmark %s has been created.')); + $msg->addParam($_POST['bkm_fields']['bkm_label']); + $response->addJSON('message', $msg); + } else { + $msg = Message::error(__('Bookmark not created!')); + $response->setRequestStatus(false); + $response->addJSON('message', $msg); + } + exit; + } else { + // go back to sql.php to redisplay query; do not use & in this case: + /** + * @todo In which scenario does this happen? + */ + Core::sendHeaderLocation( + './' . $goto + . '&label=' . $_POST['bkm_fields']['bkm_label'] + ); + } + } + + /** + * Function to find the real end of rows + * + * @param string $db the current database + * @param string $table the current table + * + * @return mixed the number of rows if "retain" param is true, otherwise true + */ + public function findRealEndOfRows($db, $table) + { + $unlim_num_rows = $GLOBALS['dbi']->getTable($db, $table)->countRecords(true); + $_SESSION['tmpval']['pos'] = $this->getStartPosToDisplayRow($unlim_num_rows); + + return $unlim_num_rows; + } + + /** + * Function to get values for the relational columns + * + * @param string $db the current database + * @param string $table the current table + * + * @return void + */ + public function getRelationalValues($db, $table) + { + $column = $_POST['column']; + if ($_SESSION['tmpval']['relational_display'] == 'D' + && isset($_POST['relation_key_or_display_column']) + && $_POST['relation_key_or_display_column'] + ) { + $curr_value = $_POST['relation_key_or_display_column']; + } else { + $curr_value = $_POST['curr_value']; + } + $dropdown = $this->getHtmlForRelationalColumnDropdown( + $db, $table, $column, $curr_value + ); + $response = Response::getInstance(); + $response->addJSON('dropdown', $dropdown); + exit; + } + + /** + * Function to get values for Enum or Set Columns + * + * @param string $db the current database + * @param string $table the current table + * @param string $columnType whether enum or set + * + * @return void + */ + public function getEnumOrSetValues($db, $table, $columnType) + { + $column = $_POST['column']; + $curr_value = $_POST['curr_value']; + $response = Response::getInstance(); + if ($columnType == "enum") { + $dropdown = $this->getHtmlForEnumColumnDropdown( + $db, $table, $column, $curr_value + ); + $response->addJSON('dropdown', $dropdown); + } else { + $select = $this->getHtmlForSetColumn( + $db, $table, $column, $curr_value + ); + $response->addJSON('select', $select); + } + exit; + } + + /** + * Function to get the default sql query for browsing page + * + * @param string $db the current database + * @param string $table the current table + * + * @return string $sql_query the default $sql_query for browse page + */ + public function getDefaultSqlQueryForBrowse($db, $table) + { + $bookmark = Bookmark::get( + $GLOBALS['dbi'], + $GLOBALS['cfg']['Server']['user'], + $db, + $table, + 'label', + false, + true + ); + + if (! empty($bookmark) && ! empty($bookmark->getQuery())) { + $GLOBALS['using_bookmark_message'] = Message::notice( + __('Using bookmark "%s" as default browse query.') + ); + $GLOBALS['using_bookmark_message']->addParam($table); + $GLOBALS['using_bookmark_message']->addHtml( + Util::showDocu('faq', 'faq6-22') + ); + $sql_query = $bookmark->getQuery(); + } else { + + $defaultOrderByClause = ''; + + if (isset($GLOBALS['cfg']['TablePrimaryKeyOrder']) + && ($GLOBALS['cfg']['TablePrimaryKeyOrder'] !== 'NONE') + ) { + + $primaryKey = null; + $primary = Index::getPrimary($table, $db); + + if ($primary !== false) { + + $primarycols = $primary->getColumns(); + + foreach ($primarycols as $col) { + $primaryKey = $col->getName(); + break; + } + + if ($primaryKey != null) { + $defaultOrderByClause = ' ORDER BY ' + . Util::backquote($table) . '.' + . Util::backquote($primaryKey) . ' ' + . $GLOBALS['cfg']['TablePrimaryKeyOrder']; + } + + } + + } + + $sql_query = 'SELECT * FROM ' . Util::backquote($table) + . $defaultOrderByClause; + + } + + return $sql_query; + } + + /** + * Responds an error when an error happens when executing the query + * + * @param boolean $is_gotofile whether goto file or not + * @param string $error error after executing the query + * @param string $full_sql_query full sql query + * + * @return void + */ + private function handleQueryExecuteError($is_gotofile, $error, $full_sql_query) + { + if ($is_gotofile) { + $message = Message::rawError($error); + $response = Response::getInstance(); + $response->setRequestStatus(false); + $response->addJSON('message', $message); + } else { + Util::mysqlDie($error, $full_sql_query, '', ''); + } + exit; + } + + /** + * Function to store the query as a bookmark + * + * @param string $db the current database + * @param string $bkm_user the bookmarking user + * @param string $sql_query_for_bookmark the query to be stored in bookmark + * @param string $bkm_label bookmark label + * @param boolean $bkm_replace whether to replace existing bookmarks + * + * @return void + */ + public function storeTheQueryAsBookmark($db, $bkm_user, $sql_query_for_bookmark, + $bkm_label, $bkm_replace + ) { + $bfields = array( + 'bkm_database' => $db, + 'bkm_user' => $bkm_user, + 'bkm_sql_query' => $sql_query_for_bookmark, + 'bkm_label' => $bkm_label, + ); + + // Should we replace bookmark? + if (isset($bkm_replace)) { + $bookmarks = Bookmark::getList( + $GLOBALS['dbi'], + $GLOBALS['cfg']['Server']['user'], + $db + ); + foreach ($bookmarks as $bookmark) { + if ($bookmark->getLabel() == $bkm_label) { + $bookmark->delete(); + } + } + } + + $bookmark = Bookmark::createBookmark( + $GLOBALS['dbi'], + $GLOBALS['cfg']['Server']['user'], + $bfields, + isset($_POST['bkm_all_users']) + ); + $bookmark->save(); + } + + /** + * Executes the SQL query and measures its execution time + * + * @param string $full_sql_query the full sql query + * + * @return array ($result, $querytime) + */ + private function executeQueryAndMeasureTime($full_sql_query) + { + // close session in case the query takes too long + session_write_close(); + + // Measure query time. + $querytime_before = array_sum(explode(' ', microtime())); + + $result = @$GLOBALS['dbi']->tryQuery( + $full_sql_query, DatabaseInterface::CONNECT_USER, DatabaseInterface::QUERY_STORE + ); + $querytime_after = array_sum(explode(' ', microtime())); + + // reopen session + session_start(); + + return array($result, $querytime_after - $querytime_before); + } + + /** + * Function to get the affected or changed number of rows after executing a query + * + * @param boolean $is_affected whether the query affected a table + * @param mixed $result results of executing the query + * + * @return int $num_rows number of rows affected or changed + */ + private function getNumberOfRowsAffectedOrChanged($is_affected, $result) + { + if (! $is_affected) { + $num_rows = ($result) ? @$GLOBALS['dbi']->numRows($result) : 0; + } else { + $num_rows = @$GLOBALS['dbi']->affectedRows(); + } + + return $num_rows; + } + + /** + * Checks if the current database has changed + * This could happen if the user sends a query like "USE `database`;" + * + * @param string $db the database in the query + * + * @return int $reload whether to reload the navigation(1) or not(0) + */ + private function hasCurrentDbChanged($db) + { + if (strlen($db) > 0) { + $current_db = $GLOBALS['dbi']->fetchValue('SELECT DATABASE()'); + // $current_db is false, except when a USE statement was sent + return ($current_db != false) && ($db !== $current_db); + } + + return false; + } + + /** + * If a table, database or column gets dropped, clean comments. + * + * @param string $db current database + * @param string $table current table + * @param string $column current column + * @param bool $purge whether purge set or not + * + * @return array $extra_data + */ + private function cleanupRelations($db, $table, $column, $purge) + { + if (! empty($purge) && strlen($db) > 0) { + if (strlen($table) > 0) { + if (isset($column) && strlen($column) > 0) { + RelationCleanup::column($db, $table, $column); + } else { + RelationCleanup::table($db, $table); + } + } else { + RelationCleanup::database($db); + } + } + } + + /** + * Function to count the total number of rows for the same 'SELECT' query without + * the 'LIMIT' clause that may have been programatically added + * + * @param int $num_rows number of rows affected/changed by the query + * @param bool $justBrowsing whether just browsing or not + * @param string $db the current database + * @param string $table the current table + * @param array $analyzed_sql_results the analyzed query and other variables set + * after analyzing the query + * + * @return int $unlim_num_rows unlimited number of rows + */ + private function countQueryResults( + $num_rows, $justBrowsing, $db, $table, array $analyzed_sql_results + ) { + + /* Shortcut for not analyzed/empty query */ + if (empty($analyzed_sql_results)) { + return 0; + } + + if (!$this->isAppendLimitClause($analyzed_sql_results)) { + // if we did not append a limit, set this to get a correct + // "Showing rows..." message + // $_SESSION['tmpval']['max_rows'] = 'all'; + $unlim_num_rows = $num_rows; + } elseif ($this->isAppendLimitClause($analyzed_sql_results) && $_SESSION['tmpval']['max_rows'] > $num_rows) { + // When user has not defined a limit in query and total rows in + // result are less than max_rows to display, there is no need + // to count total rows for that query again + $unlim_num_rows = $_SESSION['tmpval']['pos'] + $num_rows; + } elseif ($analyzed_sql_results['querytype'] == 'SELECT' + || $analyzed_sql_results['is_subquery'] + ) { + // c o u n t q u e r y + + // If we are "just browsing", there is only one table (and no join), + // and no WHERE clause (or just 'WHERE 1 '), + // we do a quick count (which uses MaxExactCount) because + // SQL_CALC_FOUND_ROWS is not quick on large InnoDB tables + + // However, do not count again if we did it previously + // due to $find_real_end == true + if ($justBrowsing) { + // Get row count (is approximate for InnoDB) + $unlim_num_rows = $GLOBALS['dbi']->getTable($db, $table)->countRecords(); + /** + * @todo Can we know at this point that this is InnoDB, + * (in this case there would be no need for getting + * an exact count)? + */ + if ($unlim_num_rows < $GLOBALS['cfg']['MaxExactCount']) { + // Get the exact count if approximate count + // is less than MaxExactCount + /** + * @todo In countRecords(), MaxExactCount is also verified, + * so can we avoid checking it twice? + */ + $unlim_num_rows = $GLOBALS['dbi']->getTable($db, $table) + ->countRecords(true); + } + + } else { + + // The SQL_CALC_FOUND_ROWS option of the SELECT statement is used. + + // For UNION statements, only a SQL_CALC_FOUND_ROWS is required + // after the first SELECT. + + $count_query = Query::replaceClause( + $analyzed_sql_results['statement'], + $analyzed_sql_results['parser']->list, + 'SELECT SQL_CALC_FOUND_ROWS', + null, + true + ); + + // Another LIMIT clause is added to avoid long delays. + // A complete result will be returned anyway, but the LIMIT would + // stop the query as soon as the result that is required has been + // computed. + + if (empty($analyzed_sql_results['union'])) { + $count_query .= ' LIMIT 1'; + } + + // Running the count query. + $GLOBALS['dbi']->tryQuery($count_query); + + $unlim_num_rows = $GLOBALS['dbi']->fetchValue('SELECT FOUND_ROWS()'); + } // end else "just browsing" + } else {// not $is_select + $unlim_num_rows = 0; + } + + return $unlim_num_rows; + } + + /** + * Function to handle all aspects relating to executing the query + * + * @param array $analyzed_sql_results analyzed sql results + * @param string $full_sql_query full sql query + * @param boolean $is_gotofile whether to go to a file + * @param string $db current database + * @param string $table current table + * @param boolean $find_real_end whether to find the real end + * @param string $sql_query_for_bookmark sql query to be stored as bookmark + * @param array $extra_data extra data + * + * @return mixed + */ + private function executeTheQuery(array $analyzed_sql_results, $full_sql_query, $is_gotofile, + $db, $table, $find_real_end, $sql_query_for_bookmark, $extra_data + ) { + $response = Response::getInstance(); + $response->getHeader()->getMenu()->setTable($table); + + // Only if we ask to see the php code + if (isset($GLOBALS['show_as_php'])) { + $result = null; + $num_rows = 0; + $unlim_num_rows = 0; + } else { // If we don't ask to see the php code + if (isset($_SESSION['profiling']) + && Util::profilingSupported() + ) { + $GLOBALS['dbi']->query('SET PROFILING=1;'); + } + + list( + $result, + $GLOBALS['querytime'] + ) = $this->executeQueryAndMeasureTime($full_sql_query); + + // Displays an error message if required and stop parsing the script + $error = $GLOBALS['dbi']->getError(); + if ($error && $GLOBALS['cfg']['IgnoreMultiSubmitErrors']) { + $extra_data['error'] = $error; + } elseif ($error) { + $this->handleQueryExecuteError($is_gotofile, $error, $full_sql_query); + } + + // If there are no errors and bookmarklabel was given, + // store the query as a bookmark + if (! empty($_POST['bkm_label']) && ! empty($sql_query_for_bookmark)) { + $cfgBookmark = Bookmark::getParams($GLOBALS['cfg']['Server']['user']); + $this->storeTheQueryAsBookmark( + $db, $cfgBookmark['user'], + $sql_query_for_bookmark, $_POST['bkm_label'], + isset($_POST['bkm_replace']) ? $_POST['bkm_replace'] : null + ); + } // end store bookmarks + + // Gets the number of rows affected/returned + // (This must be done immediately after the query because + // mysql_affected_rows() reports about the last query done) + $num_rows = $this->getNumberOfRowsAffectedOrChanged( + $analyzed_sql_results['is_affected'], $result + ); + + // Grabs the profiling results + if (isset($_SESSION['profiling']) + && Util::profilingSupported() + ) { + $profiling_results = $GLOBALS['dbi']->fetchResult('SHOW PROFILE;'); + } + + $justBrowsing = $this->isJustBrowsing( + $analyzed_sql_results, isset($find_real_end) ? $find_real_end : null + ); + + $unlim_num_rows = $this->countQueryResults( + $num_rows, $justBrowsing, $db, $table, $analyzed_sql_results + ); + + $this->cleanupRelations( + isset($db) ? $db : '', + isset($table) ? $table : '', + isset($_POST['dropped_column']) ? $_POST['dropped_column'] : null, + isset($_POST['purge']) ? $_POST['purge'] : null + ); + + if (isset($_POST['dropped_column']) + && strlen($db) > 0 + && strlen($table) > 0 + ) { + // to refresh the list of indexes (Ajax mode) + $extra_data['indexes_list'] = Index::getHtmlForIndexes( + $table, + $db + ); + } + } + + return array($result, $num_rows, $unlim_num_rows, + isset($profiling_results) ? $profiling_results : null, $extra_data + ); + } + /** + * Delete related transformation information + * + * @param string $db current database + * @param string $table current table + * @param array $analyzed_sql_results analyzed sql results + * + * @return void + */ + private function deleteTransformationInfo($db, $table, array $analyzed_sql_results) + { + if (! isset($analyzed_sql_results['statement'])) { + return; + } + $statement = $analyzed_sql_results['statement']; + if ($statement instanceof AlterStatement) { + if (!empty($statement->altered[0]) + && $statement->altered[0]->options->has('DROP') + ) { + if (!empty($statement->altered[0]->field->column)) { + Transformations::clear( + $db, + $table, + $statement->altered[0]->field->column + ); + } + } + } elseif ($statement instanceof DropStatement) { + Transformations::clear($db, $table); + } + } + + /** + * Function to get the message for the no rows returned case + * + * @param string $message_to_show message to show + * @param array $analyzed_sql_results analyzed sql results + * @param int $num_rows number of rows + * + * @return string $message + */ + private function getMessageForNoRowsReturned($message_to_show, + array $analyzed_sql_results, $num_rows + ) { + if ($analyzed_sql_results['querytype'] == 'DELETE"') { + $message = Message::getMessageForDeletedRows($num_rows); + } elseif ($analyzed_sql_results['is_insert']) { + if ($analyzed_sql_results['querytype'] == 'REPLACE') { + // For REPLACE we get DELETED + INSERTED row count, + // so we have to call it affected + $message = Message::getMessageForAffectedRows($num_rows); + } else { + $message = Message::getMessageForInsertedRows($num_rows); + } + $insert_id = $GLOBALS['dbi']->insertId(); + if ($insert_id != 0) { + // insert_id is id of FIRST record inserted in one insert, + // so if we inserted multiple rows, we had to increment this + $message->addText('[br]'); + // need to use a temporary because the Message class + // currently supports adding parameters only to the first + // message + $_inserted = Message::notice(__('Inserted row id: %1$d')); + $_inserted->addParam($insert_id + $num_rows - 1); + $message->addMessage($_inserted); + } + } elseif ($analyzed_sql_results['is_affected']) { + $message = Message::getMessageForAffectedRows($num_rows); + + // Ok, here is an explanation for the !$is_select. + // The form generated by PhpMyAdmin\SqlQueryForm + // and db_sql.php has many submit buttons + // on the same form, and some confusion arises from the + // fact that $message_to_show is sent for every case. + // The $message_to_show containing a success message and sent with + // the form should not have priority over errors + } elseif (! empty($message_to_show) + && $analyzed_sql_results['querytype'] != 'SELECT' + ) { + $message = Message::rawSuccess(htmlspecialchars($message_to_show)); + } elseif (! empty($GLOBALS['show_as_php'])) { + $message = Message::success(__('Showing as PHP code')); + } elseif (isset($GLOBALS['show_as_php'])) { + /* User disable showing as PHP, query is only displayed */ + $message = Message::notice(__('Showing SQL query')); + } else { + $message = Message::success( + __('MySQL returned an empty result set (i.e. zero rows).') + ); + } + + if (isset($GLOBALS['querytime'])) { + $_querytime = Message::notice( + '(' . __('Query took %01.4f seconds.') . ')' + ); + $_querytime->addParam($GLOBALS['querytime']); + $message->addMessage($_querytime); + } + + // In case of ROLLBACK, notify the user. + if (isset($_POST['rollback_query'])) { + $message->addText(__('[ROLLBACK occurred.]')); + } + + return $message; + } + + /** + * Function to respond back when the query returns zero rows + * This method is called + * 1-> When browsing an empty table + * 2-> When executing a query on a non empty table which returns zero results + * 3-> When executing a query on an empty table + * 4-> When executing an INSERT, UPDATE, DELETE query from the SQL tab + * 5-> When deleting a row from BROWSE tab + * 6-> When searching using the SEARCH tab which returns zero results + * 7-> When changing the structure of the table except change operation + * + * @param array $analyzed_sql_results analyzed sql results + * @param string $db current database + * @param string $table current table + * @param string $message_to_show message to show + * @param int $num_rows number of rows + * @param DisplayResults $displayResultsObject DisplayResult instance + * @param array $extra_data extra data + * @param string $pmaThemeImage uri of the theme image + * @param object $result executed query results + * @param string $sql_query sql query + * @param string $complete_query complete sql query + * + * @return string html + */ + private function getQueryResponseForNoResultsReturned(array $analyzed_sql_results, $db, + $table, $message_to_show, $num_rows, $displayResultsObject, $extra_data, + $pmaThemeImage, $result, $sql_query, $complete_query + ) { + if ($this->isDeleteTransformationInfo($analyzed_sql_results)) { + $this->deleteTransformationInfo($db, $table, $analyzed_sql_results); + } + + if (isset($extra_data['error'])) { + $message = Message::rawError($extra_data['error']); + } else { + $message = $this->getMessageForNoRowsReturned( + isset($message_to_show) ? $message_to_show : null, + $analyzed_sql_results, $num_rows + ); + } + + $html_output = ''; + $html_message = Util::getMessage( + $message, $GLOBALS['sql_query'], 'success' + ); + $html_output .= $html_message; + if (!isset($GLOBALS['show_as_php'])) { + + if (! empty($GLOBALS['reload'])) { + $extra_data['reload'] = 1; + $extra_data['db'] = $GLOBALS['db']; + } + + // For ajax requests add message and sql_query as JSON + if (empty($_REQUEST['ajax_page_request'])) { + $extra_data['message'] = $message; + if ($GLOBALS['cfg']['ShowSQL']) { + $extra_data['sql_query'] = $html_message; + } + } + + $response = Response::getInstance(); + $response->addJSON(isset($extra_data) ? $extra_data : array()); + + if (!empty($analyzed_sql_results['is_select']) && + !isset($extra_data['error'])) { + $url_query = isset($url_query) ? $url_query : null; + + $displayParts = array( + 'edit_lnk' => null, + 'del_lnk' => null, + 'sort_lnk' => '1', + 'nav_bar' => '0', + 'bkm_form' => '1', + 'text_btn' => '1', + 'pview_lnk' => '1' + ); + + $html_output .= $this->getHtmlForSqlQueryResultsTable( + $displayResultsObject, + $pmaThemeImage, $url_query, $displayParts, + false, 0, $num_rows, true, $result, + $analyzed_sql_results, true + ); + + $html_output .= $displayResultsObject->getCreateViewQueryResultOp( + $analyzed_sql_results + ); + + $cfgBookmark = Bookmark::getParams($GLOBALS['cfg']['Server']['user']); + if ($cfgBookmark) { + $html_output .= $this->getHtmlForBookmark( + $displayParts, + $cfgBookmark, + $sql_query, $db, $table, + isset($complete_query) ? $complete_query : $sql_query, + $cfgBookmark['user'] + ); + } + } + } + + return $html_output; + } + + /** + * Function to send response for ajax grid edit + * + * @param object $result result of the executed query + * + * @return void + */ + private function sendResponseForGridEdit($result) + { + $row = $GLOBALS['dbi']->fetchRow($result); + $field_flags = $GLOBALS['dbi']->fieldFlags($result, 0); + if (stristr($field_flags, DisplayResults::BINARY_FIELD)) { + $row[0] = bin2hex($row[0]); + } + $response = Response::getInstance(); + $response->addJSON('value', $row[0]); + exit; + } + + /** + * Function to get html for the sql query results div + * + * @param string $previous_update_query_html html for the previously executed query + * @param string $profiling_chart_html html for profiling + * @param Message $missing_unique_column_msg message for the missing unique column + * @param Message $bookmark_created_msg message for bookmark creation + * @param string $table_html html for the table for displaying sql + * results + * @param string $indexes_problems_html html for displaying errors in indexes + * @param string $bookmark_support_html html for displaying bookmark form + * + * @return string $html_output + */ + private function getHtmlForSqlQueryResults($previous_update_query_html, + $profiling_chart_html, $missing_unique_column_msg, $bookmark_created_msg, + $table_html, $indexes_problems_html, $bookmark_support_html + ) { + //begin the sqlqueryresults div here. container div + $html_output = '
      '; + $html_output .= isset($previous_update_query_html) + ? $previous_update_query_html : ''; + $html_output .= isset($profiling_chart_html) ? $profiling_chart_html : ''; + $html_output .= isset($missing_unique_column_msg) + ? $missing_unique_column_msg->getDisplay() : ''; + $html_output .= isset($bookmark_created_msg) + ? $bookmark_created_msg->getDisplay() : ''; + $html_output .= $table_html; + $html_output .= isset($indexes_problems_html) ? $indexes_problems_html : ''; + $html_output .= isset($bookmark_support_html) ? $bookmark_support_html : ''; + $html_output .= '
      '; // end sqlqueryresults div + + return $html_output; + } + + /** + * Returns a message for successful creation of a bookmark or null if a bookmark + * was not created + * + * @return Message $bookmark_created_msg + */ + private function getBookmarkCreatedMessage() + { + if (isset($_GET['label'])) { + $bookmark_created_msg = Message::success( + __('Bookmark %s has been created.') + ); + $bookmark_created_msg->addParam($_GET['label']); + } else { + $bookmark_created_msg = null; + } + + return $bookmark_created_msg; + } + + /** + * Function to get html for the sql query results table + * + * @param DisplayResults $displayResultsObject instance of DisplayResult + * @param string $pmaThemeImage theme image uri + * @param string $url_query url query + * @param array $displayParts the parts to display + * @param bool $editable whether the result table is + * editable or not + * @param int $unlim_num_rows unlimited number of rows + * @param int $num_rows number of rows + * @param bool $showtable whether to show table or not + * @param object $result result of the executed query + * @param array $analyzed_sql_results analyzed sql results + * @param bool $is_limited_display Show only limited operations or not + * + * @return string + */ + private function getHtmlForSqlQueryResultsTable($displayResultsObject, + $pmaThemeImage, $url_query, array $displayParts, + $editable, $unlim_num_rows, $num_rows, $showtable, $result, + array $analyzed_sql_results, $is_limited_display = false + ) { + $printview = isset($_POST['printview']) && $_POST['printview'] == '1' ? '1' : null; + $table_html = ''; + $browse_dist = ! empty($_POST['is_browse_distinct']); + + if ($analyzed_sql_results['is_procedure']) { + + do { + if (! isset($result)) { + $result = $GLOBALS['dbi']->storeResult(); + } + $num_rows = $GLOBALS['dbi']->numRows($result); + + if ($result !== false && $num_rows > 0) { + + $fields_meta = $GLOBALS['dbi']->getFieldsMeta($result); + if (! is_array($fields_meta)) { + $fields_cnt = 0; + } else { + $fields_cnt = count($fields_meta); + } + + $displayResultsObject->setProperties( + $num_rows, + $fields_meta, + $analyzed_sql_results['is_count'], + $analyzed_sql_results['is_export'], + $analyzed_sql_results['is_func'], + $analyzed_sql_results['is_analyse'], + $num_rows, + $fields_cnt, + $GLOBALS['querytime'], + $pmaThemeImage, + $GLOBALS['text_dir'], + $analyzed_sql_results['is_maint'], + $analyzed_sql_results['is_explain'], + $analyzed_sql_results['is_show'], + $showtable, + $printview, + $url_query, + $editable, + $browse_dist + ); + + $displayParts = array( + 'edit_lnk' => $displayResultsObject::NO_EDIT_OR_DELETE, + 'del_lnk' => $displayResultsObject::NO_EDIT_OR_DELETE, + 'sort_lnk' => '1', + 'nav_bar' => '1', + 'bkm_form' => '1', + 'text_btn' => '1', + 'pview_lnk' => '1' + ); + + $table_html .= $displayResultsObject->getTable( + $result, + $displayParts, + $analyzed_sql_results, + $is_limited_display + ); + } + + $GLOBALS['dbi']->freeResult($result); + unset($result); + + } while ($GLOBALS['dbi']->moreResults() && $GLOBALS['dbi']->nextResult()); + + } else { + $fields_meta = array(); + if (isset($result) && ! is_bool($result)) { + $fields_meta = $GLOBALS['dbi']->getFieldsMeta($result); + } + $fields_cnt = count($fields_meta); + $_SESSION['is_multi_query'] = false; + $displayResultsObject->setProperties( + $unlim_num_rows, + $fields_meta, + $analyzed_sql_results['is_count'], + $analyzed_sql_results['is_export'], + $analyzed_sql_results['is_func'], + $analyzed_sql_results['is_analyse'], + $num_rows, + $fields_cnt, $GLOBALS['querytime'], + $pmaThemeImage, $GLOBALS['text_dir'], + $analyzed_sql_results['is_maint'], + $analyzed_sql_results['is_explain'], + $analyzed_sql_results['is_show'], + $showtable, + $printview, + $url_query, + $editable, + $browse_dist + ); + + if (! is_bool($result)) { + $table_html .= $displayResultsObject->getTable( + $result, + $displayParts, + $analyzed_sql_results, + $is_limited_display + ); + } + $GLOBALS['dbi']->freeResult($result); + } + + return $table_html; + } + + /** + * Function to get html for the previous query if there is such. If not will return + * null + * + * @param string $disp_query display query + * @param bool $showSql whether to show sql + * @param array $sql_data sql data + * @param string $disp_message display message + * + * @return string $previous_update_query_html + */ + private function getHtmlForPreviousUpdateQuery($disp_query, $showSql, $sql_data, + $disp_message + ) { + // previous update query (from tbl_replace) + if (isset($disp_query) && ($showSql == true) && empty($sql_data)) { + $previous_update_query_html = Util::getMessage( + $disp_message, $disp_query, 'success' + ); + } else { + $previous_update_query_html = null; + } + + return $previous_update_query_html; + } + + /** + * To get the message if a column index is missing. If not will return null + * + * @param string $table current table + * @param string $db current database + * @param boolean $editable whether the results table can be editable or not + * @param boolean $has_unique whether there is a unique key + * + * @return Message $message + */ + private function getMessageIfMissingColumnIndex($table, $db, $editable, $has_unique) + { + if (!empty($table) && ($GLOBALS['dbi']->isSystemSchema($db) || !$editable)) { + $missing_unique_column_msg = Message::notice( + sprintf( + __( + 'Current selection does not contain a unique column.' + . ' Grid edit, checkbox, Edit, Copy and Delete features' + . ' are not available. %s' + ), + Util::showDocu( + 'config', + 'cfg_RowActionLinksWithoutUnique' + ) + ) + ); + } elseif (! empty($table) && ! $has_unique) { + $missing_unique_column_msg = Message::notice( + sprintf( + __( + 'Current selection does not contain a unique column.' + . ' Grid edit, Edit, Copy and Delete features may result in' + . ' undesired behavior. %s' + ), + Util::showDocu( + 'config', + 'cfg_RowActionLinksWithoutUnique' + ) + ) + ); + } else { + $missing_unique_column_msg = null; + } + + return $missing_unique_column_msg; + } + + /** + * Function to get html to display problems in indexes + * + * @param string $query_type query type + * @param array|null $selectedTables array of table names selected from the + * database structure page, for an action + * like check table, optimize table, + * analyze table or repair table + * @param string $db current database + * + * @return string + */ + private function getHtmlForIndexesProblems($query_type, $selectedTables, $db) + { + // BEGIN INDEX CHECK See if indexes should be checked. + if (isset($query_type) + && $query_type == 'check_tbl' + && isset($selectedTables) + && is_array($selectedTables) + ) { + $indexes_problems_html = ''; + foreach ($selectedTables as $tbl_name) { + $check = Index::findDuplicates($tbl_name, $db); + if (! empty($check)) { + $indexes_problems_html .= sprintf( + __('Problems with indexes of table `%s`'), $tbl_name + ); + $indexes_problems_html .= $check; + } + } + } else { + $indexes_problems_html = null; + } + + return $indexes_problems_html; + } + + /** + * Function to display results when the executed query returns non empty results + * + * @param object $result executed query results + * @param array $analyzed_sql_results analysed sql results + * @param string $db current database + * @param string $table current table + * @param string $message message to show + * @param array $sql_data sql data + * @param DisplayResults $displayResultsObject Instance of DisplayResults + * @param string $pmaThemeImage uri of the theme image + * @param int $unlim_num_rows unlimited number of rows + * @param int $num_rows number of rows + * @param string $disp_query display query + * @param string $disp_message display message + * @param array $profiling_results profiling results + * @param string $query_type query type + * @param array|null $selectedTables array of table names selected + * from the database structure page, for + * an action like check table, + * optimize table, analyze table or + * repair table + * @param string $sql_query sql query + * @param string $complete_query complete sql query + * + * @return string html + */ + private function getQueryResponseForResultsReturned($result, array $analyzed_sql_results, + $db, $table, $message, $sql_data, $displayResultsObject, $pmaThemeImage, + $unlim_num_rows, $num_rows, $disp_query, $disp_message, $profiling_results, + $query_type, $selectedTables, $sql_query, $complete_query + ) { + // If we are retrieving the full value of a truncated field or the original + // value of a transformed field, show it here + if (isset($_POST['grid_edit']) && $_POST['grid_edit'] == true) { + $this->sendResponseForGridEdit($result); + // script has exited at this point + } + + // Gets the list of fields properties + if (isset($result) && $result) { + $fields_meta = $GLOBALS['dbi']->getFieldsMeta($result); + } + + // Should be initialized these parameters before parsing + $showtable = isset($showtable) ? $showtable : null; + $url_query = isset($url_query) ? $url_query : null; + + $response = Response::getInstance(); + $header = $response->getHeader(); + $scripts = $header->getScripts(); + + $just_one_table = $this->resultSetHasJustOneTable($fields_meta); + + // hide edit and delete links: + // - for information_schema + // - if the result set does not contain all the columns of a unique key + // (unless this is an updatable view) + // - if the SELECT query contains a join or a subquery + + $updatableView = false; + + $statement = isset($analyzed_sql_results['statement']) ? $analyzed_sql_results['statement'] : null; + if ($statement instanceof SelectStatement) { + if (!empty($statement->expr)) { + if ($statement->expr[0]->expr === '*') { + $_table = new Table($table, $db); + $updatableView = $_table->isUpdatableView(); + } + } + + if ($analyzed_sql_results['join'] + || $analyzed_sql_results['is_subquery'] + || count($analyzed_sql_results['select_tables']) !== 1 + ) { + $just_one_table = false; + } + } + + $has_unique = $this->resultSetContainsUniqueKey( + $db, $table, $fields_meta + ); + + $editable = ($has_unique + || $GLOBALS['cfg']['RowActionLinksWithoutUnique'] + || $updatableView) + && $just_one_table; + + $_SESSION['tmpval']['possible_as_geometry'] = $editable; + + $displayParts = array( + 'edit_lnk' => $displayResultsObject::UPDATE_ROW, + 'del_lnk' => $displayResultsObject::DELETE_ROW, + 'sort_lnk' => '1', + 'nav_bar' => '1', + 'bkm_form' => '1', + 'text_btn' => '0', + 'pview_lnk' => '1' + ); + + if ($GLOBALS['dbi']->isSystemSchema($db) || !$editable) { + $displayParts = array( + 'edit_lnk' => $displayResultsObject::NO_EDIT_OR_DELETE, + 'del_lnk' => $displayResultsObject::NO_EDIT_OR_DELETE, + 'sort_lnk' => '1', + 'nav_bar' => '1', + 'bkm_form' => '1', + 'text_btn' => '1', + 'pview_lnk' => '1' + ); + + } + if (isset($_POST['printview']) && $_POST['printview'] == '1') { + $displayParts = array( + 'edit_lnk' => $displayResultsObject::NO_EDIT_OR_DELETE, + 'del_lnk' => $displayResultsObject::NO_EDIT_OR_DELETE, + 'sort_lnk' => '0', + 'nav_bar' => '0', + 'bkm_form' => '0', + 'text_btn' => '0', + 'pview_lnk' => '0' + ); + } + + if (isset($_POST['table_maintenance'])) { + $scripts->addFile('makegrid.js'); + $scripts->addFile('sql.js'); + $table_maintenance_html = ''; + if (isset($message)) { + $message = Message::success($message); + $table_maintenance_html = Util::getMessage( + $message, $GLOBALS['sql_query'], 'success' + ); + } + $table_maintenance_html .= $this->getHtmlForSqlQueryResultsTable( + $displayResultsObject, + $pmaThemeImage, $url_query, $displayParts, + false, $unlim_num_rows, $num_rows, $showtable, $result, + $analyzed_sql_results + ); + if (empty($sql_data) || ($sql_data['valid_queries'] = 1)) { + $response->addHTML($table_maintenance_html); + exit(); + } + } + + if (!isset($_POST['printview']) || $_POST['printview'] != '1') { + $scripts->addFile('makegrid.js'); + $scripts->addFile('sql.js'); + unset($GLOBALS['message']); + //we don't need to buffer the output in getMessage here. + //set a global variable and check against it in the function + $GLOBALS['buffer_message'] = false; + } + + $previous_update_query_html = $this->getHtmlForPreviousUpdateQuery( + isset($disp_query) ? $disp_query : null, + $GLOBALS['cfg']['ShowSQL'], isset($sql_data) ? $sql_data : null, + isset($disp_message) ? $disp_message : null + ); + + $profiling_chart_html = $this->getHtmlForProfilingChart( + $url_query, $db, isset($profiling_results) ? $profiling_results :array() + ); + + $missing_unique_column_msg = $this->getMessageIfMissingColumnIndex( + $table, $db, $editable, $has_unique + ); + + $bookmark_created_msg = $this->getBookmarkCreatedMessage(); + + $table_html = $this->getHtmlForSqlQueryResultsTable( + $displayResultsObject, + $pmaThemeImage, $url_query, $displayParts, + $editable, $unlim_num_rows, $num_rows, $showtable, $result, + $analyzed_sql_results + ); + + $indexes_problems_html = $this->getHtmlForIndexesProblems( + isset($query_type) ? $query_type : null, + isset($selectedTables) ? $selectedTables : null, $db + ); + + $cfgBookmark = Bookmark::getParams($GLOBALS['cfg']['Server']['user']); + if ($cfgBookmark) { + $bookmark_support_html = $this->getHtmlForBookmark( + $displayParts, + $cfgBookmark, + $sql_query, $db, $table, + isset($complete_query) ? $complete_query : $sql_query, + $cfgBookmark['user'] + ); + } else { + $bookmark_support_html = ''; + } + + $html_output = isset($table_maintenance_html) ? $table_maintenance_html : ''; + + $html_output .= $this->getHtmlForSqlQueryResults( + $previous_update_query_html, $profiling_chart_html, + $missing_unique_column_msg, $bookmark_created_msg, + $table_html, $indexes_problems_html, $bookmark_support_html + ); + + return $html_output; + } + + /** + * Function to execute the query and send the response + * + * @param array $analyzed_sql_results analysed sql results + * @param bool $is_gotofile whether goto file or not + * @param string $db current database + * @param string $table current table + * @param bool|null $find_real_end whether to find real end or not + * @param string $sql_query_for_bookmark the sql query to be stored as bookmark + * @param array|null $extra_data extra data + * @param string $message_to_show message to show + * @param string $message message + * @param array|null $sql_data sql data + * @param string $goto goto page url + * @param string $pmaThemeImage uri of the PMA theme image + * @param string $disp_query display query + * @param string $disp_message display message + * @param string $query_type query type + * @param string $sql_query sql query + * @param array|null $selectedTables array of table names selected from the + * database structure page, for an action + * like check table, optimize table, + * analyze table or repair table + * @param string $complete_query complete query + * + * @return void + */ + public function executeQueryAndSendQueryResponse($analyzed_sql_results, + $is_gotofile, $db, $table, $find_real_end, $sql_query_for_bookmark, + $extra_data, $message_to_show, $message, $sql_data, $goto, $pmaThemeImage, + $disp_query, $disp_message, $query_type, $sql_query, $selectedTables, + $complete_query + ) { + if ($analyzed_sql_results == null) { + // Parse and analyze the query + list( + $analyzed_sql_results, + $db, + $table_from_sql + ) = ParseAnalyze::sqlQuery($sql_query, $db); + // @todo: possibly refactor + extract($analyzed_sql_results); + + if ($table != $table_from_sql && !empty($table_from_sql)) { + $table = $table_from_sql; + } + } + + $html_output = $this->executeQueryAndGetQueryResponse( + $analyzed_sql_results, // analyzed_sql_results + $is_gotofile, // is_gotofile + $db, // db + $table, // table + $find_real_end, // find_real_end + $sql_query_for_bookmark, // sql_query_for_bookmark + $extra_data, // extra_data + $message_to_show, // message_to_show + $message, // message + $sql_data, // sql_data + $goto, // goto + $pmaThemeImage, // pmaThemeImage + $disp_query, // disp_query + $disp_message, // disp_message + $query_type, // query_type + $sql_query, // sql_query + $selectedTables, // selectedTables + $complete_query // complete_query + ); + + $response = Response::getInstance(); + $response->addHTML($html_output); + } + + /** + * Function to execute the query and send the response + * + * @param array $analyzed_sql_results analysed sql results + * @param bool $is_gotofile whether goto file or not + * @param string $db current database + * @param string $table current table + * @param bool|null $find_real_end whether to find real end or not + * @param string $sql_query_for_bookmark the sql query to be stored as bookmark + * @param array|null $extra_data extra data + * @param string $message_to_show message to show + * @param string $message message + * @param array|null $sql_data sql data + * @param string $goto goto page url + * @param string $pmaThemeImage uri of the PMA theme image + * @param string $disp_query display query + * @param string $disp_message display message + * @param string $query_type query type + * @param string $sql_query sql query + * @param array|null $selectedTables array of table names selected from the + * database structure page, for an action + * like check table, optimize table, + * analyze table or repair table + * @param string $complete_query complete query + * + * @return string html + */ + public function executeQueryAndGetQueryResponse(array $analyzed_sql_results, + $is_gotofile, $db, $table, $find_real_end, $sql_query_for_bookmark, + $extra_data, $message_to_show, $message, $sql_data, $goto, $pmaThemeImage, + $disp_query, $disp_message, $query_type, $sql_query, $selectedTables, + $complete_query + ) { + // Handle disable/enable foreign key checks + $default_fk_check = Util::handleDisableFKCheckInit(); + + // Handle remembered sorting order, only for single table query. + // Handling is not required when it's a union query + // (the parser never sets the 'union' key to 0). + // Handling is also not required if we came from the "Sort by key" + // drop-down. + if (! empty($analyzed_sql_results) + && $this->isRememberSortingOrder($analyzed_sql_results) + && empty($analyzed_sql_results['union']) + && ! isset($_POST['sort_by_key']) + ) { + if (! isset($_SESSION['sql_from_query_box'])) { + $this->handleSortOrder($db, $table, $analyzed_sql_results, $sql_query); + } else { + unset($_SESSION['sql_from_query_box']); + } + + } + + $displayResultsObject = new DisplayResults( + $GLOBALS['db'], $GLOBALS['table'], $goto, $sql_query + ); + $displayResultsObject->setConfigParamsForDisplayTable(); + + // assign default full_sql_query + $full_sql_query = $sql_query; + + // Do append a "LIMIT" clause? + if ($this->isAppendLimitClause($analyzed_sql_results)) { + $full_sql_query = $this->getSqlWithLimitClause($analyzed_sql_results); + } + + $GLOBALS['reload'] = $this->hasCurrentDbChanged($db); + $GLOBALS['dbi']->selectDb($db); + + // Execute the query + list($result, $num_rows, $unlim_num_rows, $profiling_results, $extra_data) + = $this->executeTheQuery( + $analyzed_sql_results, + $full_sql_query, + $is_gotofile, + $db, + $table, + isset($find_real_end) ? $find_real_end : null, + isset($sql_query_for_bookmark) ? $sql_query_for_bookmark : null, + isset($extra_data) ? $extra_data : null + ); + + if ($GLOBALS['dbi']->moreResults()) { + $GLOBALS['dbi']->nextResult(); + } + + $operations = new Operations(); + $warning_messages = $operations->getWarningMessagesArray(); + + // No rows returned -> move back to the calling page + if ((0 == $num_rows && 0 == $unlim_num_rows) + || $analyzed_sql_results['is_affected'] + ) { + $html_output = $this->getQueryResponseForNoResultsReturned( + $analyzed_sql_results, $db, $table, + isset($message_to_show) ? $message_to_show : null, + $num_rows, $displayResultsObject, $extra_data, + $pmaThemeImage, isset($result) ? $result : null, + $sql_query, isset($complete_query) ? $complete_query : null + ); + } else { + // At least one row is returned -> displays a table with results + $html_output = $this->getQueryResponseForResultsReturned( + isset($result) ? $result : null, + $analyzed_sql_results, + $db, + $table, + isset($message) ? $message : null, + isset($sql_data) ? $sql_data : null, + $displayResultsObject, + $pmaThemeImage, + $unlim_num_rows, + $num_rows, + isset($disp_query) ? $disp_query : null, + isset($disp_message) ? $disp_message : null, + $profiling_results, + isset($query_type) ? $query_type : null, + isset($selectedTables) ? $selectedTables : null, + $sql_query, + isset($complete_query) ? $complete_query : null + ); + } + + // Handle disable/enable foreign key checks + Util::handleDisableFKCheckCleanup($default_fk_check); + + foreach ($warning_messages as $warning) { + $message = Message::notice(Message::sanitize($warning)); + $html_output .= $message->getDisplay(); + } + + return $html_output; + } + + /** + * Function to define pos to display a row + * + * @param int $number_of_line Number of the line to display + * @param int $max_rows Number of rows by page + * + * @return int Start position to display the line + */ + private function getStartPosToDisplayRow($number_of_line, $max_rows = null) + { + if (null === $max_rows) { + $max_rows = $_SESSION['tmpval']['max_rows']; + } + + return @((ceil($number_of_line / $max_rows) - 1) * $max_rows); + } + + /** + * Function to calculate new pos if pos is higher than number of rows + * of displayed table + * + * @param string $db Database name + * @param string $table Table name + * @param int|null $pos Initial position + * + * @return int Number of pos to display last page + */ + public function calculatePosForLastPage($db, $table, $pos) + { + if (null === $pos) { + $pos = $_SESSION['tmpval']['pos']; + } + + $_table = new Table($table, $db); + $unlim_num_rows = $_table->countRecords(true); + //If position is higher than number of rows + if ($unlim_num_rows <= $pos && 0 != $pos) { + $pos = $this->getStartPosToDisplayRow($unlim_num_rows); + } + + return $pos; + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/SqlQueryForm.php b/php/apps/phpmyadmin49/libraries/classes/SqlQueryForm.php new file mode 100644 index 00000000..497d4794 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/SqlQueryForm.php @@ -0,0 +1,446 @@ +' . "\n"; + + $html .= '' + . "\n" . Url::getHiddenInputs($db, $table) . "\n" + . '' . "\n" + . '' . "\n" + . '' + . "\n" . '' . "\n"; + + // display querybox + if ($display_tab === 'full' || $display_tab === 'sql') { + $html .= self::getHtmlForInsert( + $query, $delimiter + ); + } + + // Bookmark Support + if ($display_tab === 'full') { + $cfgBookmark = Bookmark::getParams($GLOBALS['cfg']['Server']['user']); + if ($cfgBookmark) { + $html .= self::getHtmlForBookmark(); + } + } + + // Japanese encoding setting + if (Encoding::canConvertKanji()) { + $html .= Encoding::kanjiEncodingForm(); + } + + $html .= '' . "\n"; + // print an empty div, which will be later filled with + // the sql query results by ajax + $html .= '
      '; + + return $html; + } + + /** + * Get initial values for Sql Query Form Insert + * + * @param string $query query to display in the textarea + * + * @return array ($legend, $query, $columns_list) + * + * @usedby self::getHtmlForInsert() + */ + public static function init($query) + { + $columns_list = array(); + if (strlen($GLOBALS['db']) === 0) { + // prepare for server related + $legend = sprintf( + __('Run SQL query/queries on server “%s”'), + htmlspecialchars( + ! empty($GLOBALS['cfg']['Servers'][$GLOBALS['server']]['verbose']) + ? $GLOBALS['cfg']['Servers'][$GLOBALS['server']]['verbose'] + : $GLOBALS['cfg']['Servers'][$GLOBALS['server']]['host'] + ) + ); + } elseif (strlen($GLOBALS['table']) === 0) { + // prepare for db related + $db = $GLOBALS['db']; + // if you want navigation: + $tmp_db_link = ''; + $legend = sprintf(__('Run SQL query/queries on database %s'), $tmp_db_link); + if (empty($query)) { + $query = Util::expandUserString( + $GLOBALS['cfg']['DefaultQueryDatabase'], 'backquote' + ); + } + } else { + $db = $GLOBALS['db']; + $table = $GLOBALS['table']; + // Get the list and number of fields + // we do a try_query here, because we could be in the query window, + // trying to synchronize and the table has not yet been created + $columns_list = $GLOBALS['dbi']->getColumns( + $db, $GLOBALS['table'], null, true + ); + + $tmp_tbl_link = ''; + $tmp_tbl_link .= htmlspecialchars($db) + . '.' . htmlspecialchars($table) . ''; + $legend = sprintf(__('Run SQL query/queries on table %s'), $tmp_tbl_link); + if (empty($query)) { + $query = Util::expandUserString( + $GLOBALS['cfg']['DefaultQueryTable'], 'backquote' + ); + } + } + $legend .= ': ' . Util::showMySQLDocu('SELECT'); + + return array($legend, $query, $columns_list); + } + + /** + * return HTML for Sql Query Form Insert + * + * @param string $query query to display in the textarea + * @param string $delimiter default delimiter to use + * + * @return string + * + * @usedby self::getHtml() + */ + public static function getHtmlForInsert( + $query = '', $delimiter = ';' + ) { + // enable auto select text in textarea + if ($GLOBALS['cfg']['TextareaAutoSelect']) { + $auto_sel = ' onclick="selectContent(this, sql_box_locked, true);"'; + } else { + $auto_sel = ''; + } + + $locking = ''; + $height = $GLOBALS['cfg']['TextareaRows'] * 2; + + list($legend, $query, $columns_list) = self::init($query); + + if (! empty($columns_list)) { + $sqlquerycontainer_id = 'sqlquerycontainer'; + } else { + $sqlquerycontainer_id = 'sqlquerycontainerfull'; + } + + $html = '' + . '
      ' + . '
      '; + $html .= '' . $legend . ''; + $html .= '
      '; + $html .= '
      ' + . ''; + $html .= '
      '; + // Add buttons to generate query easily for + // select all, single select, insert, update and delete + if (! empty($columns_list)) { + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + } + $html .= ''; + if ($GLOBALS['cfg']['CodemirrorEnable']) { + $html .= ''; + } + $html .= ''; + + // parameter binding + $html .= '
      '; + $html .= ''; + $html .= ''; + $html .= Util::showDocu('faq', 'faq6-40'); + $html .= '
      '; + $html .= '
      '; + + $html .= '
      ' . "\n"; + + if (! empty($columns_list)) { + $html .= '
      ' + . '' + . '' + . '
      '; + if (Util::showIcons('ActionLinksMode')) { + $html .= ''; + } else { + $html .= ''; + } + $html .= '
      ' . "\n" + . '
      ' . "\n"; + } + + $html .= '
      ' . "\n"; + $html .= '
      ' . "\n"; + + $cfgBookmark = Bookmark::getParams($GLOBALS['cfg']['Server']['user']); + if ($cfgBookmark) { + $html .= '
      '; + $html .= '
      '; + $html .= ''; + $html .= ''; + $html .= '
      '; + $html .= '
      '; + $html .= ''; + $html .= ''; + $html .= '
      '; + $html .= '
      '; + $html .= ''; + $html .= ''; + $html .= '
      '; + $html .= '
      '; + } + + $html .= '
      ' . "\n"; + $html .= '
      ' . "\n" + . '
      ' . "\n"; + + $html .= '
      ' . "\n"; + $html .= '
      ' . "\n"; + $html .= '
      ' . "\n"; + + $html .= '
      '; + $html .= '' . "\n"; + $html .= ' ]'; + $html .= '
      '; + + $html .= '
      '; + $html .= '' + . ''; + $html .= '
      '; + + $html .= '
      '; + $html .= '' + . ''; + $html .= '
      '; + + $html .= '
      '; + $html .= '' + . ''; + $html .= '
      '; + + // Disable/Enable foreign key checks + $html .= '
      '; + $html .= Util::getFKCheckbox(); + $html .= '
      '; + + $html .= '' . "\n"; + $html .= '
      ' . "\n"; + $html .= '
      ' . "\n"; + + return $html; + } + + /** + * return HTML for sql Query Form Bookmark + * + * @return string|null + * + * @usedby self::getHtml() + */ + public static function getHtmlForBookmark() + { + $bookmark_list = Bookmark::getList( + $GLOBALS['dbi'], + $GLOBALS['cfg']['Server']['user'], + $GLOBALS['db'] + ); + if (empty($bookmark_list) || count($bookmark_list) < 1) { + return null; + } + + $html = '
      '; + $html .= ''; + $html .= __('Bookmarked SQL query') . '' . "\n"; + $html .= '
      '; + $html .= ' ' . "\n"; + $html .= '
      ' . "\n"; + $html .= '
      ' . "\n"; + $html .= '' + . '' . "\n"; + $html .= '' + . '' . "\n"; + $html .= '' + . '' . "\n"; + $html .= '
      ' . "\n"; + $html .= '
      ' . "\n"; + $html .= '
      ' . "\n"; + $html .= __('Variables'); + $html .= Util::showDocu('faq', 'faqbookmark'); + $html .= '
      '; + $html .= '
      ' . "\n"; + $html .= '
      ' . "\n"; + + $html .= '
      '; + $html .= ''; + $html .= '
      ' . "\n"; + $html .= '
      ' . "\n"; + + return $html; + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/StorageEngine.php b/php/apps/phpmyadmin49/libraries/classes/StorageEngine.php new file mode 100644 index 00000000..c0e8662f --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/StorageEngine.php @@ -0,0 +1,461 @@ +engine = $engine; + $this->title = $storage_engines[$engine]['Engine']; + $this->comment = (isset($storage_engines[$engine]['Comment']) + ? $storage_engines[$engine]['Comment'] + : ''); + switch ($storage_engines[$engine]['Support']) { + case 'DEFAULT': + $this->support = PMA_ENGINE_SUPPORT_DEFAULT; + break; + case 'YES': + $this->support = PMA_ENGINE_SUPPORT_YES; + break; + case 'DISABLED': + $this->support = PMA_ENGINE_SUPPORT_DISABLED; + break; + case 'NO': + default: + $this->support = PMA_ENGINE_SUPPORT_NO; + } + } + } + + /** + * Returns array of storage engines + * + * @static + * @staticvar array $storage_engines storage engines + * @access public + * @return string[] array of storage engines + */ + static public function getStorageEngines() + { + static $storage_engines = null; + + if (null == $storage_engines) { + $storage_engines + = $GLOBALS['dbi']->fetchResult('SHOW STORAGE ENGINES', 'Engine'); + if ($GLOBALS['dbi']->getVersion() >= 50708) { + $disabled = Util::cacheGet( + 'disabled_storage_engines', + function () { + return $GLOBALS['dbi']->fetchValue( + 'SELECT @@disabled_storage_engines' + ); + } + ); + foreach (explode(",", $disabled) as $engine) { + if (isset($storage_engines[$engine])) { + $storage_engines[$engine]['Support'] = 'DISABLED'; + } + } + } + } + + return $storage_engines; + } + + /** + * Returns HTML code for storage engine select box + * + * @param string $name The name of the select form element + * @param string $id The ID of the form field + * @param string $selected The selected engine + * @param boolean $offerUnavailableEngines Should unavailable storage + * engines be offered? + * @param boolean $addEmpty Whether to provide empty option + * + * @static + * @return string html selectbox + */ + static public function getHtmlSelect( + $name = 'engine', $id = null, + $selected = null, $offerUnavailableEngines = false, + $addEmpty = false + ) { + $selected = mb_strtolower($selected); + $output = '' . "\n"; + return $output; + } + + /** + * Loads the corresponding engine plugin, if available. + * + * @param string $engine The engine ID + * + * @return StorageEngine The engine plugin + * @static + */ + static public function getEngine($engine) + { + switch(mb_strtolower($engine)) { + case 'bdb': + return new Bdb($engine); + case 'berkeleydb': + return new Berkeleydb($engine); + case 'binlog': + return new Binlog($engine); + case 'innobase': + return new Innobase($engine); + case 'innodb': + return new Innodb($engine); + case 'memory': + return new Memory($engine); + case 'merge': + return new Merge($engine); + case 'mrg_myisam': + return new MrgMyisam($engine); + case 'myisam': + return new Myisam($engine); + case 'ndbcluster': + return new Ndbcluster($engine); + case 'pbxt': + return new Pbxt($engine); + case 'performance_schema': + return new PerformanceSchema($engine); + default: + return new StorageEngine($engine); + } + } + + /** + * Returns true if given engine name is supported/valid, otherwise false + * + * @param string $engine name of engine + * + * @static + * @return boolean whether $engine is valid or not + */ + static public function isValid($engine) + { + if ($engine == "PBMS") { + return true; + } + $storage_engines = self::getStorageEngines(); + return isset($storage_engines[$engine]); + } + + /** + * Returns as HTML table of the engine's server variables + * + * @return string The table that was generated based on the retrieved + * information + */ + public function getHtmlVariables() + { + $ret = ''; + + foreach ($this->getVariablesStatus() as $details) { + $ret .= '' . "\n" + . ' ' . "\n"; + if (! empty($details['desc'])) { + $ret .= ' ' + . Util::showHint($details['desc']) + . "\n"; + } + $ret .= ' ' . "\n" + . ' ' . htmlspecialchars($details['title']) . '' + . "\n" + . ' '; + switch ($details['type']) { + case PMA_ENGINE_DETAILS_TYPE_SIZE: + $parsed_size = $this->resolveTypeSize($details['value']); + $ret .= $parsed_size[0] . ' ' . $parsed_size[1]; + unset($parsed_size); + break; + case PMA_ENGINE_DETAILS_TYPE_NUMERIC: + $ret .= Util::formatNumber($details['value']) . ' '; + break; + default: + $ret .= htmlspecialchars($details['value']) . ' '; + } + $ret .= '' . "\n" + . '' . "\n"; + } + + if (! $ret) { + $ret = '

      ' . "\n" + . ' ' + . __( + 'There is no detailed status information available for this ' + . 'storage engine.' + ) + . "\n" + . '

      ' . "\n"; + } else { + $ret = '' . "\n" . $ret . '
      ' . "\n"; + } + + return $ret; + } + + /** + * Returns the engine specific handling for + * PMA_ENGINE_DETAILS_TYPE_SIZE type variables. + * + * This function should be overridden when + * PMA_ENGINE_DETAILS_TYPE_SIZE type needs to be + * handled differently for a particular engine. + * + * @param integer $value Value to format + * + * @return string the formatted value and its unit + */ + public function resolveTypeSize($value) + { + return Util::formatByteDown($value); + } + + /** + * Returns array with detailed info about engine specific server variables + * + * @return array array with detailed info about specific engine server variables + */ + public function getVariablesStatus() + { + $variables = $this->getVariables(); + $like = $this->getVariablesLikePattern(); + + if ($like) { + $like = " LIKE '" . $like . "' "; + } else { + $like = ''; + } + + $mysql_vars = array(); + + $sql_query = 'SHOW GLOBAL VARIABLES ' . $like . ';'; + $res = $GLOBALS['dbi']->query($sql_query); + while ($row = $GLOBALS['dbi']->fetchAssoc($res)) { + if (isset($variables[$row['Variable_name']])) { + $mysql_vars[$row['Variable_name']] + = $variables[$row['Variable_name']]; + } elseif (! $like + && mb_strpos(mb_strtolower($row['Variable_name']), mb_strtolower($this->engine)) !== 0 + ) { + continue; + } + $mysql_vars[$row['Variable_name']]['value'] = $row['Value']; + + if (empty($mysql_vars[$row['Variable_name']]['title'])) { + $mysql_vars[$row['Variable_name']]['title'] = $row['Variable_name']; + } + + if (! isset($mysql_vars[$row['Variable_name']]['type'])) { + $mysql_vars[$row['Variable_name']]['type'] + = PMA_ENGINE_DETAILS_TYPE_PLAINTEXT; + } + } + $GLOBALS['dbi']->freeResult($res); + + return $mysql_vars; + } + + /** + * Reveals the engine's title + * + * @return string The title + */ + public function getTitle() + { + return $this->title; + } + + /** + * Fetches the server's comment about this engine + * + * @return string The comment + */ + public function getComment() + { + return $this->comment; + } + + /** + * Information message on whether this storage engine is supported + * + * @return string The localized message. + */ + public function getSupportInformationMessage() + { + switch ($this->support) { + case PMA_ENGINE_SUPPORT_DEFAULT: + $message = __('%s is the default storage engine on this MySQL server.'); + break; + case PMA_ENGINE_SUPPORT_YES: + $message = __('%s is available on this MySQL server.'); + break; + case PMA_ENGINE_SUPPORT_DISABLED: + $message = __('%s has been disabled for this MySQL server.'); + break; + case PMA_ENGINE_SUPPORT_NO: + default: + $message = __( + 'This MySQL server does not support the %s storage engine.' + ); + } + return sprintf($message, htmlspecialchars($this->title)); + } + + /** + * Generates a list of MySQL variables that provide information about this + * engine. This function should be overridden when extending this class + * for a particular engine. + * + * @return array The list of variables. + */ + public function getVariables() + { + return array(); + } + + /** + * Returns string with filename for the MySQL helppage + * about this storage engine + * + * @return string MySQL help page filename + */ + public function getMysqlHelpPage() + { + return $this->engine . '-storage-engine'; + } + + /** + * Returns the pattern to be used in the query for SQL variables + * related to the storage engine + * + * @return string SQL query LIKE pattern + */ + public function getVariablesLikePattern() + { + return ''; + } + + /** + * Returns a list of available information pages with labels + * + * @return string[] The list + */ + public function getInfoPages() + { + return array(); + } + + /** + * Generates the requested information page + * + * @param string $id page id + * + * @return string html output + */ + public function getPage($id) + { + if (! array_key_exists($id, $this->getInfoPages())) { + return ''; + } + + $id = 'getPage' . $id; + + return $this->$id(); + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/SubPartition.php b/php/apps/phpmyadmin49/libraries/classes/SubPartition.php new file mode 100644 index 00000000..d3f6be1e --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/SubPartition.php @@ -0,0 +1,180 @@ +db = $row['TABLE_SCHEMA']; + $this->table = $row['TABLE_NAME']; + $this->loadData($row); + } + + /** + * Loads data from the fetched row from information_schema.PARTITIONS + * + * @param array $row fetched row + * + * @return void + */ + protected function loadData(array $row) + { + $this->name = $row['SUBPARTITION_NAME']; + $this->ordinal = $row['SUBPARTITION_ORDINAL_POSITION']; + $this->method = $row['SUBPARTITION_METHOD']; + $this->expression = $row['SUBPARTITION_EXPRESSION']; + $this->loadCommonData($row); + } + + /** + * Loads some data that is common to both partitions and sub partitions + * + * @param array $row fetched row + * + * @return void + */ + protected function loadCommonData(array $row) + { + $this->rows = $row['TABLE_ROWS']; + $this->dataLength = $row['DATA_LENGTH']; + $this->indexLength = $row['INDEX_LENGTH']; + $this->comment = $row['PARTITION_COMMENT']; + } + + /** + * Return the partition name + * + * @return string partition name + */ + public function getName() + { + return $this->name; + } + + /** + * Return the ordinal of the partition + * + * @return number the ordinal + */ + public function getOrdinal() + { + return $this->ordinal; + } + + /** + * Returns the partition method + * + * @return string partition method + */ + public function getMethod() + { + return $this->method; + } + + /** + * Returns the partition expression + * + * @return string partition expression + */ + public function getExpression() + { + return $this->expression; + } + + /** + * Returns the number of data rows + * + * @return integer number of rows + */ + public function getRows() + { + return $this->rows; + } + + /** + * Returns the data length + * + * @return integer data length + */ + public function getDataLength() + { + return $this->dataLength; + } + + /** + * Returns the index length + * + * @return integer index length + */ + public function getIndexLength() + { + return $this->indexLength; + } + + /** + * Returns the partition comment + * + * @return string partition comment + */ + public function getComment() + { + return $this->comment; + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/SysInfo.php b/php/apps/phpmyadmin49/libraries/classes/SysInfo.php new file mode 100644 index 00000000..f33d9e12 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/SysInfo.php @@ -0,0 +1,64 @@ +supported()) { + return $ret; + } + } + + return new SysInfoBase(); + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/SysInfoBase.php b/php/apps/phpmyadmin49/libraries/classes/SysInfoBase.php new file mode 100644 index 00000000..3052b1e4 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/SysInfoBase.php @@ -0,0 +1,48 @@ + 0); + } + + /** + * Gets information about memory usage + * + * @return array with memory usage data + */ + public function memory() + { + return array(); + } + + /** + * Checks whether class is supported in this environment + * + * @return true on success + */ + public function supported() + { + return true; + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/SysInfoLinux.php b/php/apps/phpmyadmin49/libraries/classes/SysInfoLinux.php new file mode 100644 index 00000000..3fc4f612 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/SysInfoLinux.php @@ -0,0 +1,94 @@ + $nums[1] + $nums[2] + $nums[3], + 'idle' => intval($nums[4]), + ); + } + + /** + * Checks whether class is supported in this environment + * + * @return true on success + */ + public function supported() + { + return @is_readable('/proc/meminfo') && @is_readable('/proc/stat'); + } + + /** + * Gets information about memory usage + * + * @return array with memory usage data + */ + function memory() + { + preg_match_all( + SysInfo::MEMORY_REGEXP, + file_get_contents('/proc/meminfo'), + $matches + ); + + $mem = array_combine($matches[1], $matches[2]); + + $defaults = array( + 'MemTotal' => 0, + 'MemFree' => 0, + 'Cached' => 0, + 'Buffers' => 0, + 'SwapTotal' => 0, + 'SwapFree' => 0, + 'SwapCached' => 0, + ); + + $mem = array_merge($defaults, $mem); + + foreach ($mem as $idx => $value) { + $mem[$idx] = intval($value); + } + + $mem['MemUsed'] = $mem['MemTotal'] + - $mem['MemFree'] - $mem['Cached'] - $mem['Buffers']; + + $mem['SwapUsed'] = $mem['SwapTotal'] + - $mem['SwapFree'] - $mem['SwapCached']; + + return $mem; + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/SysInfoSunOS.php b/php/apps/phpmyadmin49/libraries/classes/SysInfoSunOS.php new file mode 100644 index 00000000..99390e85 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/SysInfoSunOS.php @@ -0,0 +1,82 @@ +_kstat('unix:0:system_misc:avenrun_1min'); + + return array('loadavg' => $load1); + } + + /** + * Checks whether class is supported in this environment + * + * @return true on success + */ + public function supported() + { + return @is_readable('/proc/meminfo'); + } + + /** + * Gets information about memory usage + * + * @return array with memory usage data + */ + public function memory() + { + $pagesize = $this->_kstat('unix:0:seg_cache:slab_size'); + $mem = array(); + $mem['MemTotal'] + = $this->_kstat('unix:0:system_pages:pagestotal') * $pagesize; + $mem['MemUsed'] + = $this->_kstat('unix:0:system_pages:pageslocked') * $pagesize; + $mem['MemFree'] + = $this->_kstat('unix:0:system_pages:pagesfree') * $pagesize; + $mem['SwapTotal'] = $this->_kstat('unix:0:vminfo:swap_avail') / 1024; + $mem['SwapUsed'] = $this->_kstat('unix:0:vminfo:swap_alloc') / 1024; + $mem['SwapFree'] = $this->_kstat('unix:0:vminfo:swap_free') / 1024; + + return $mem; + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/SysInfoWINNT.php b/php/apps/phpmyadmin49/libraries/classes/SysInfoWINNT.php new file mode 100644 index 00000000..921d933d --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/SysInfoWINNT.php @@ -0,0 +1,131 @@ +_wmi = null; + } else { + // initialize the wmi object + $objLocator = new COM('WbemScripting.SWbemLocator'); + $this->_wmi = $objLocator->ConnectServer(); + } + } + + /** + * Gets load information + * + * @return array with load data + */ + function loadavg() + { + $loadavg = ""; + $sum = 0; + $buffer = $this->_getWMI('Win32_Processor', array('LoadPercentage')); + + foreach ($buffer as $load) { + $value = $load['LoadPercentage']; + $loadavg .= $value . ' '; + $sum += $value; + } + + return array('loadavg' => $sum / count($buffer)); + } + + /** + * Checks whether class is supported in this environment + * + * @return true on success + */ + public function supported() + { + return !is_null($this->_wmi); + } + + /** + * Reads data from WMI + * + * @param string $strClass Class to read + * @param array $strValue Values to read + * + * @return array with results + */ + private function _getWMI($strClass, array $strValue = array()) + { + $arrData = array(); + + $objWEBM = $this->_wmi->Get($strClass); + $arrProp = $objWEBM->Properties_; + $arrWEBMCol = $objWEBM->Instances_(); + foreach ($arrWEBMCol as $objItem) { + $arrInstance = array(); + foreach ($arrProp as $propItem) { + $name = $propItem->Name; + if (empty($strValue) || in_array($name, $strValue)) { + $value = $objItem->$name; + if (is_string($value)) { + $arrInstance[$name] = trim($value); + } else { + $arrInstance[$name] = $value; + } + } + } + $arrData[] = $arrInstance; + } + + return $arrData; + } + + /** + * Gets information about memory usage + * + * @return array with memory usage data + */ + function memory() + { + $buffer = $this->_getWMI( + "Win32_OperatingSystem", + array('TotalVisibleMemorySize', 'FreePhysicalMemory') + ); + $mem = Array(); + $mem['MemTotal'] = $buffer[0]['TotalVisibleMemorySize']; + $mem['MemFree'] = $buffer[0]['FreePhysicalMemory']; + $mem['MemUsed'] = $mem['MemTotal'] - $mem['MemFree']; + + $buffer = $this->_getWMI('Win32_PageFileUsage'); + + $mem['SwapTotal'] = 0; + $mem['SwapUsed'] = 0; + $mem['SwapPeak'] = 0; + + foreach ($buffer as $swapdevice) { + $mem['SwapTotal'] += $swapdevice['AllocatedBaseSize'] * 1024; + $mem['SwapUsed'] += $swapdevice['CurrentUsage'] * 1024; + $mem['SwapPeak'] += $swapdevice['PeakUsage'] * 1024; + } + + return $mem; + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/SystemDatabase.php b/php/apps/phpmyadmin49/libraries/classes/SystemDatabase.php new file mode 100644 index 00000000..2242beda --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/SystemDatabase.php @@ -0,0 +1,133 @@ +dbi = $dbi; + $this->relation = new Relation(); + } + + /** + * Get existing data on transformations applied for + * columns in a particular table + * + * @param string $db Database name looking for + * + * @return \mysqli_result Result of executed SQL query + */ + public function getExistingTransformationData($db) + { + $cfgRelation = $this->relation->getRelationsParam(); + + // Get the existing transformation details of the same database + // from pma__column_info table + $pma_transformation_sql = sprintf( + "SELECT * FROM %s.%s WHERE `db_name` = '%s'", + Util::backquote($cfgRelation['db']), + Util::backquote($cfgRelation['column_info']), + $GLOBALS['dbi']->escapeString($db) + ); + + return $this->dbi->tryQuery($pma_transformation_sql); + } + + /** + * Get SQL query for store new transformation details of a VIEW + * + * @param object $pma_transformation_data Result set of SQL execution + * @param array $column_map Details of VIEW columns + * @param string $view_name Name of the VIEW + * @param string $db Database name of the VIEW + * + * @return string $new_transformations_sql SQL query for new transformations + */ + function getNewTransformationDataSql( + $pma_transformation_data, array $column_map, $view_name, $db + ) { + $cfgRelation = $this->relation->getRelationsParam(); + + // Need to store new transformation details for VIEW + $new_transformations_sql = sprintf( + "INSERT INTO %s.%s (" + . "`db_name`, `table_name`, `column_name`, " + . "`comment`, `mimetype`, `transformation`, " + . "`transformation_options`) VALUES", + Util::backquote($cfgRelation['db']), + Util::backquote($cfgRelation['column_info']) + ); + + $column_count = 0; + $add_comma = false; + + while ($data_row = $this->dbi->fetchAssoc($pma_transformation_data)) { + + foreach ($column_map as $column) { + + if ($data_row['table_name'] != $column['table_name'] + || $data_row['column_name'] != $column['refering_column'] + ) { + continue; + } + + $new_transformations_sql .= sprintf( + "%s ('%s', '%s', '%s', '%s', '%s', '%s', '%s')", + $add_comma ? ', ' : '', + $db, + $view_name, + isset($column['real_column']) + ? $column['real_column'] + : $column['refering_column'], + $data_row['comment'], + $data_row['mimetype'], + $data_row['transformation'], + $GLOBALS['dbi']->escapeString( + $data_row['transformation_options'] + ) + ); + + $add_comma = true; + $column_count++; + break; + } + + if ($column_count == count($column_map)) { + break; + } + } + + return ($column_count > 0) ? $new_transformations_sql : ''; + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Table.php b/php/apps/phpmyadmin49/libraries/classes/Table.php new file mode 100644 index 00000000..b9f7233f --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Table.php @@ -0,0 +1,2648 @@ +_dbi = $dbi; + $this->_name = $table_name; + $this->_db_name = $db_name; + $this->relation = new Relation(); + } + + /** + * returns table name + * + * @see Table::getName() + * @return string table name + */ + public function __toString() + { + return $this->getName(); + } + + /** + * Table getter + * + * @param string $table_name table name + * @param string $db_name database name + * @param DatabaseInterface $dbi database interface for the table + * + * @return Table + */ + public static function get($table_name, $db_name, DatabaseInterface $dbi = null) + { + return new Table($table_name, $db_name, $dbi); + } + + /** + * return the last error + * + * @return string the last error + */ + public function getLastError() + { + return end($this->errors); + } + + /** + * return the last message + * + * @return string the last message + */ + public function getLastMessage() + { + return end($this->messages); + } + + /** + * returns table name + * + * @param boolean $backquoted whether to quote name with backticks `` + * + * @return string table name + */ + public function getName($backquoted = false) + { + if ($backquoted) { + return Util::backquote($this->_name); + } + return $this->_name; + } + + /** + * returns database name for this table + * + * @param boolean $backquoted whether to quote name with backticks `` + * + * @return string database name for this table + */ + public function getDbName($backquoted = false) + { + if ($backquoted) { + return Util::backquote($this->_db_name); + } + return $this->_db_name; + } + + /** + * returns full name for table, including database name + * + * @param boolean $backquoted whether to quote name with backticks `` + * + * @return string + */ + public function getFullName($backquoted = false) + { + return $this->getDbName($backquoted) . '.' + . $this->getName($backquoted); + } + + + /** + * Checks the storage engine used to create table + * + * @param array|string $engine Checks the table engine against an + * array of engine strings or a single string, should be uppercase + * + * @return bool True, if $engine matches the storage engine for the table, + * False otherwise. + */ + public function isEngine($engine) + { + $tbl_storage_engine = $this->getStorageEngine(); + + if (is_array($engine)){ + foreach($engine as $e){ + if($e == $tbl_storage_engine){ + return true; + } + } + return false; + }else{ + return $tbl_storage_engine == $engine; + } + } + + /** + * returns whether the table is actually a view + * + * @return boolean whether the given is a view + */ + public function isView() + { + $db = $this->_db_name; + $table = $this->_name; + if (empty($db) || empty($table)) { + return false; + } + + // use cached data or load information with SHOW command + if ($this->_dbi->getCachedTableContent(array($db, $table)) != null + || $GLOBALS['cfg']['Server']['DisableIS'] + ) { + $type = $this->getStatusInfo('TABLE_TYPE'); + return $type == 'VIEW' || $type == 'SYSTEM VIEW'; + } + + // information_schema tables are 'SYSTEM VIEW's + if ($db == 'information_schema') { + return true; + } + + // query information_schema + $result = $this->_dbi->fetchResult( + "SELECT TABLE_NAME + FROM information_schema.VIEWS + WHERE TABLE_SCHEMA = '" . $GLOBALS['dbi']->escapeString($db) . "' + AND TABLE_NAME = '" . $GLOBALS['dbi']->escapeString($table) . "'" + ); + return $result ? true : false; + } + + /** + * Returns whether the table is actually an updatable view + * + * @return boolean whether the given is an updatable view + */ + public function isUpdatableView() + { + if (empty($this->_db_name) || empty($this->_name)) { + return false; + } + + $result = $this->_dbi->fetchResult( + "SELECT TABLE_NAME + FROM information_schema.VIEWS + WHERE TABLE_SCHEMA = '" . $GLOBALS['dbi']->escapeString($this->_db_name) . "' + AND TABLE_NAME = '" . $GLOBALS['dbi']->escapeString($this->_name) . "' + AND IS_UPDATABLE = 'YES'" + ); + return $result ? true : false; + } + + /** + * Checks if this is a merge table + * + * If the ENGINE of the table is MERGE or MRG_MYISAM (alias), + * this is a merge table. + * + * @return boolean true if it is a merge table + */ + public function isMerge() + { + return $this->isEngine(array('MERGE', 'MRG_MYISAM')); + } + + /** + * Returns full table status info, or specific if $info provided + * this info is collected from information_schema + * + * @param string $info specific information to be fetched + * @param boolean $force_read read new rather than serving from cache + * @param boolean $disable_error if true, disables error message + * + * @todo DatabaseInterface::getTablesFull needs to be merged + * somehow into this class or at least better documented + * + * @return mixed + */ + public function getStatusInfo( + $info = null, + $force_read = false, + $disable_error = false + ) { + $db = $this->_db_name; + $table = $this->_name; + + if (! empty($_SESSION['is_multi_query'])) { + $disable_error = true; + } + + // sometimes there is only one entry (ExactRows) so + // we have to get the table's details + if ($this->_dbi->getCachedTableContent(array($db, $table)) == null + || $force_read + || count($this->_dbi->getCachedTableContent(array($db, $table))) == 1 + ) { + $this->_dbi->getTablesFull($db, $table); + } + + if ($this->_dbi->getCachedTableContent(array($db, $table)) == null) { + // happens when we enter the table creation dialog + // or when we really did not get any status info, for example + // when $table == 'TABLE_NAMES' after the user tried SHOW TABLES + return ''; + } + + if (null === $info) { + return $this->_dbi->getCachedTableContent(array($db, $table)); + } + + // array_key_exists allows for null values + if (!array_key_exists( + $info, $this->_dbi->getCachedTableContent(array($db, $table)) + ) + ) { + if (! $disable_error) { + trigger_error( + __('Unknown table status:') . ' ' . $info, + E_USER_WARNING + ); + } + return false; + } + + return $this->_dbi->getCachedTableContent(array($db, $table, $info)); + } + + /** + * Returns the Table storage Engine for current table. + * + * @return string Return storage engine info if it is set for + * the selected table else return blank. + */ + public function getStorageEngine() { + $table_storage_engine = $this->getStatusInfo('ENGINE', false, true); + if ($table_storage_engine === false) { + return ''; + } + return strtoupper($table_storage_engine); + } + + /** + * Returns the comments for current table. + * + * @return string Return comment info if it is set for the selected table or return blank. + */ + public function getComment() { + $table_comment = $this->getStatusInfo('TABLE_COMMENT', false, true); + if ($table_comment === false) { + return ''; + } + return $table_comment; + } + + /** + * Returns the collation for current table. + * + * @return string Return blank if collation is empty else return the collation info from table info. + */ + public function getCollation() { + $table_collation = $this->getStatusInfo('TABLE_COLLATION', false, true); + if ($table_collation === false) { + return ''; + } + return $table_collation; + } + + /** + * Returns the info about no of rows for current table. + * + * @return integer Return no of rows info if it is not null for the selected table or return 0. + */ + public function getNumRows() { + $table_num_row_info = $this->getStatusInfo('TABLE_ROWS', false, true); + if (false === $table_num_row_info) { + $table_num_row_info = $this->_dbi->getTable($this->_db_name, $showtable['Name']) + ->countRecords(true); + } + return $table_num_row_info ? $table_num_row_info : 0 ; + } + + /** + * Returns the Row format for current table. + * + * @return string Return table row format info if it is set for the selected table or return blank. + */ + public function getRowFormat() { + $table_row_format = $this->getStatusInfo('ROW_FORMAT', false, true); + if ($table_row_format === false) { + return ''; + } + return $table_row_format; + } + + /** + * Returns the auto increment option for current table. + * + * @return integer Return auto increment info if it is set for the selected table or return blank. + */ + public function getAutoIncrement() { + $table_auto_increment = $this->getStatusInfo('AUTO_INCREMENT', false, true); + return isset($table_auto_increment) ? $table_auto_increment : ''; + } + + /** + * Returns the array for CREATE statement for current table. + * @return array Return options array info if it is set for the selected table or return blank. + */ + public function getCreateOptions() { + $table_options = $this->getStatusInfo('CREATE_OPTIONS', false, true); + $create_options_tmp = empty($table_options) ? array() : explode(' ', $table_options); + $create_options = array(); + // export create options by its name as variables into global namespace + // f.e. pack_keys=1 becomes available as $pack_keys with value of '1' + // unset($pack_keys); + foreach ($create_options_tmp as $each_create_option) { + $each_create_option = explode('=', $each_create_option); + if (isset($each_create_option[1])) { + // ensure there is no ambiguity for PHP 5 and 7 + $create_options[$each_create_option[0]] = $each_create_option[1]; + } + } + // we need explicit DEFAULT value here (different from '0') + $create_options['pack_keys'] = (! isset($create_options['pack_keys']) || strlen($create_options['pack_keys']) == 0) + ? 'DEFAULT' + : $create_options['pack_keys']; + return $create_options; + } + + /** + * generates column specification for ALTER or CREATE TABLE syntax + * + * @param string $name name + * @param string $type type ('INT', 'VARCHAR', 'BIT', ...) + * @param string $length length ('2', '5,2', '', ...) + * @param string $attribute attribute + * @param string $collation collation + * @param bool|string $null with 'NULL' or 'NOT NULL' + * @param string $default_type whether default is CURRENT_TIMESTAMP, + * NULL, NONE, USER_DEFINED + * @param string $default_value default value for USER_DEFINED + * default type + * @param string $extra 'AUTO_INCREMENT' + * @param string $comment field comment + * @param string $virtuality virtuality of the column + * @param string $expression expression for the virtual column + * @param string $move_to new position for column + * + * @todo move into class PMA_Column + * @todo on the interface, some js to clear the default value when the + * default current_timestamp is checked + * + * @return string field specification + */ + static function generateFieldSpec($name, $type, $length = '', + $attribute = '', $collation = '', $null = false, + $default_type = 'USER_DEFINED', $default_value = '', $extra = '', + $comment = '', $virtuality = '', $expression = '', $move_to = '' + ) { + $is_timestamp = mb_strpos( + mb_strtoupper($type), + 'TIMESTAMP' + ) !== false; + + $query = Util::backquote($name) . ' ' . $type; + + // allow the possibility of a length for TIME, DATETIME and TIMESTAMP + // (will work on MySQL >= 5.6.4) + // + // MySQL permits a non-standard syntax for FLOAT and DOUBLE, + // see https://dev.mysql.com/doc/refman/5.5/en/floating-point-types.html + // + $pattern = '@^(DATE|TINYBLOB|TINYTEXT|BLOB|TEXT|' + . 'MEDIUMBLOB|MEDIUMTEXT|LONGBLOB|LONGTEXT|SERIAL|BOOLEAN|UUID)$@i'; + if (strlen($length) !== 0 && ! preg_match($pattern, $type)) { + // Note: The variable $length here can contain several other things + // besides length - ENUM/SET value or length of DECIMAL (eg. 12,3) + // so we can't just convert it to integer + $query .= '(' . $length . ')'; + } + if ($attribute != '') { + $query .= ' ' . $attribute; + + if ($is_timestamp + && preg_match('/TIMESTAMP/i', $attribute) + && strlen($length) !== 0 + && $length !== 0 + ) { + $query .= '(' . $length . ')'; + } + } + + // if column is virtual, check if server type is Mysql as only Mysql server + // supports extra column properties + $isVirtualColMysql = $virtuality && in_array(Util::getServerType(), array('MySQL', 'Percona Server')); + // if column is virtual, check if server type is MariaDB as MariaDB server + // supports no extra virtual column properties except CHARACTER SET for text column types + $isVirtualColMariaDB = $virtuality && Util::getServerType() === 'MariaDB'; + + $matches = preg_match( + '@^(TINYTEXT|TEXT|MEDIUMTEXT|LONGTEXT|VARCHAR|CHAR|ENUM|SET)$@i', + $type + ); + if (! empty($collation) && $collation != 'NULL' && $matches) { + $query .= Util::getCharsetQueryPart( + $isVirtualColMariaDB ? preg_replace('~_.+~s', '', $collation) : $collation, + true + ); + } + + if ($virtuality) { + $query .= ' AS (' . $expression . ') ' . $virtuality; + } + + if (! $virtuality || $isVirtualColMysql) { + if ($null !== false) { + if ($null == 'YES') { + $query .= ' NULL'; + } else { + $query .= ' NOT NULL'; + } + } + + if (! $virtuality) { + switch ($default_type) { + case 'USER_DEFINED' : + if ($is_timestamp && $default_value === '0') { + // a TIMESTAMP does not accept DEFAULT '0' + // but DEFAULT 0 works + $query .= ' DEFAULT 0'; + } elseif ($type == 'BIT') { + $query .= ' DEFAULT b\'' + . preg_replace('/[^01]/', '0', $default_value) + . '\''; + } elseif ($type == 'BOOLEAN') { + if (preg_match('/^1|T|TRUE|YES$/i', $default_value)) { + $query .= ' DEFAULT TRUE'; + } elseif (preg_match('/^0|F|FALSE|NO$/i', $default_value)) { + $query .= ' DEFAULT FALSE'; + } else { + // Invalid BOOLEAN value + $query .= ' DEFAULT \'' + . $GLOBALS['dbi']->escapeString($default_value) . '\''; + } + } elseif ($type == 'BINARY' || $type == 'VARBINARY') { + $query .= ' DEFAULT 0x' . $default_value; + } else { + $query .= ' DEFAULT \'' + . $GLOBALS['dbi']->escapeString($default_value) . '\''; + } + break; + /** @noinspection PhpMissingBreakStatementInspection */ + case 'NULL' : + // If user uncheck null checkbox and not change default value null, + // default value will be ignored. + if ($null !== false && $null !== 'YES') { + break; + } + // else fall-through intended, no break here + case 'CURRENT_TIMESTAMP' : + case 'current_timestamp()': + $query .= ' DEFAULT ' . $default_type; + + if (strlen($length) !== 0 + && $length !== 0 + && $is_timestamp + && $default_type !== 'NULL' // Not to be added in case of NULL + ) { + $query .= '(' . $length . ')'; + } + break; + case 'NONE' : + default : + break; + } + } + + if (!empty($extra)) { + if ($virtuality) { + $extra = trim(preg_replace('~^\s*AUTO_INCREMENT\s*~is', ' ', $extra)); + } + + $query .= ' ' . $extra; + } + } + + if (!empty($comment)) { + $query .= " COMMENT '" . $GLOBALS['dbi']->escapeString($comment) . "'"; + } + + // move column + if ($move_to == '-first') { // dash can't appear as part of column name + $query .= ' FIRST'; + } elseif ($move_to != '') { + $query .= ' AFTER ' . Util::backquote($move_to); + } + return $query; + } // end function + + /** + * Checks if the number of records in a table is at least equal to + * $min_records + * + * @param int $min_records Number of records to check for in a table + * + * @return bool True, if at least $min_records exist, False otherwise. + */ + public function checkIfMinRecordsExist($min_records = 0) + { + $check_query = 'SELECT '; + $fieldsToSelect = ''; + + $uniqueFields = $this->getUniqueColumns(true, false); + if (count($uniqueFields) > 0) { + $fieldsToSelect = implode(', ', $uniqueFields); + } else { + $indexedCols = $this->getIndexedColumns(true, false); + if (count($indexedCols) > 0) { + $fieldsToSelect = implode(', ', $indexedCols); + } else { + $fieldsToSelect = '*'; + } + } + + $check_query .= $fieldsToSelect + . ' FROM ' . $this->getFullName(true) + . ' LIMIT ' . $min_records; + + $res = $GLOBALS['dbi']->tryQuery( + $check_query + ); + + if ($res !== false) { + $num_records = $GLOBALS['dbi']->numRows($res); + if ($num_records >= $min_records) { + return true; + } + } + + return false; + } + + /** + * Counts and returns (or displays) the number of records in a table + * + * @param bool $force_exact whether to force an exact count + * + * @return mixed the number of records if "retain" param is true, + * otherwise true + */ + public function countRecords($force_exact = false) + { + $is_view = $this->isView(); + $db = $this->_db_name; + $table = $this->_name; + + if ($this->_dbi->getCachedTableContent(array($db, $table, 'ExactRows')) != null) { + $row_count = $this->_dbi->getCachedTableContent( + array($db, $table, 'ExactRows') + ); + return $row_count; + } + $row_count = false; + + if (! $force_exact) { + if (($this->_dbi->getCachedTableContent(array($db, $table, 'Rows')) == null) + && !$is_view + ) { + $tmp_tables = $this->_dbi->getTablesFull($db, $table); + if (isset($tmp_tables[$table])) { + $this->_dbi->cacheTableContent( + array($db, $table), + $tmp_tables[$table] + ); + } + } + if ($this->_dbi->getCachedTableContent(array($db, $table, 'Rows')) != null) { + $row_count = $this->_dbi->getCachedTableContent( + array($db, $table, 'Rows') + ); + } else { + $row_count = false; + } + } + // for a VIEW, $row_count is always false at this point + if (false !== $row_count + && $row_count >= $GLOBALS['cfg']['MaxExactCount'] + ) { + return $row_count; + } + + if (! $is_view) { + $row_count = $this->_dbi->fetchValue( + 'SELECT COUNT(*) FROM ' . Util::backquote($db) . '.' + . Util::backquote($table) + ); + } else { + // For complex views, even trying to get a partial record + // count could bring down a server, so we offer an + // alternative: setting MaxExactCountViews to 0 will bypass + // completely the record counting for views + + if ($GLOBALS['cfg']['MaxExactCountViews'] == 0) { + $row_count = false; + } else { + // Counting all rows of a VIEW could be too long, + // so use a LIMIT clause. + // Use try_query because it can fail (when a VIEW is + // based on a table that no longer exists) + $result = $this->_dbi->tryQuery( + 'SELECT 1 FROM ' . Util::backquote($db) . '.' + . Util::backquote($table) . ' LIMIT ' + . $GLOBALS['cfg']['MaxExactCountViews'], + DatabaseInterface::CONNECT_USER, + DatabaseInterface::QUERY_STORE + ); + if (!$this->_dbi->getError()) { + $row_count = $this->_dbi->numRows($result); + $this->_dbi->freeResult($result); + } + } + } + if ($row_count) { + $this->_dbi->cacheTableContent(array($db, $table, 'ExactRows'), $row_count); + } + + return $row_count; + } // end of the 'Table::countRecords()' function + + /** + * Generates column specification for ALTER syntax + * + * @param string $oldcol old column name + * @param string $newcol new column name + * @param string $type type ('INT', 'VARCHAR', 'BIT', ...) + * @param string $length length ('2', '5,2', '', ...) + * @param string $attribute attribute + * @param string $collation collation + * @param bool|string $null with 'NULL' or 'NOT NULL' + * @param string $default_type whether default is CURRENT_TIMESTAMP, + * NULL, NONE, USER_DEFINED + * @param string $default_value default value for USER_DEFINED default + * type + * @param string $extra 'AUTO_INCREMENT' + * @param string $comment field comment + * @param string $virtuality virtuality of the column + * @param string $expression expression for the virtual column + * @param string $move_to new position for column + * + * @see Table::generateFieldSpec() + * + * @return string field specification + */ + public static function generateAlter($oldcol, $newcol, $type, $length, + $attribute, $collation, $null, $default_type, $default_value, + $extra, $comment, $virtuality, $expression, $move_to + ) { + return Util::backquote($oldcol) . ' ' + . self::generateFieldSpec( + $newcol, $type, $length, $attribute, + $collation, $null, $default_type, $default_value, $extra, + $comment, $virtuality, $expression, $move_to + ); + } // end function + + /** + * Inserts existing entries in a PMA_* table by reading a value from an old + * entry + * + * @param string $work The array index, which Relation feature to + * check ('relwork', 'commwork', ...) + * @param string $pma_table The array index, which PMA-table to update + * ('bookmark', 'relation', ...) + * @param array $get_fields Which fields will be SELECT'ed from the old entry + * @param array $where_fields Which fields will be used for the WHERE query + * (array('FIELDNAME' => 'FIELDVALUE')) + * @param array $new_fields Which fields will be used as new VALUES. + * These are the important keys which differ + * from the old entry + * (array('FIELDNAME' => 'NEW FIELDVALUE')) + * + * @global relation variable + * + * @return int|boolean + */ + public static function duplicateInfo($work, $pma_table, array $get_fields, + array $where_fields, array $new_fields + ) { + $relation = new Relation(); + $last_id = -1; + + if (!isset($GLOBALS['cfgRelation']) || !$GLOBALS['cfgRelation'][$work]) { + return true; + } + + $select_parts = array(); + $row_fields = array(); + foreach ($get_fields as $get_field) { + $select_parts[] = Util::backquote($get_field); + $row_fields[$get_field] = 'cc'; + } + + $where_parts = array(); + foreach ($where_fields as $_where => $_value) { + $where_parts[] = Util::backquote($_where) . ' = \'' + . $GLOBALS['dbi']->escapeString($_value) . '\''; + } + + $new_parts = array(); + $new_value_parts = array(); + foreach ($new_fields as $_where => $_value) { + $new_parts[] = Util::backquote($_where); + $new_value_parts[] = $GLOBALS['dbi']->escapeString($_value); + } + + $table_copy_query = ' + SELECT ' . implode(', ', $select_parts) . ' + FROM ' . Util::backquote($GLOBALS['cfgRelation']['db']) . '.' + . Util::backquote($GLOBALS['cfgRelation'][$pma_table]) . ' + WHERE ' . implode(' AND ', $where_parts); + + // must use DatabaseInterface::QUERY_STORE here, since we execute + // another query inside the loop + $table_copy_rs = $relation->queryAsControlUser( + $table_copy_query, true, DatabaseInterface::QUERY_STORE + ); + + while ($table_copy_row = @$GLOBALS['dbi']->fetchAssoc($table_copy_rs)) { + $value_parts = array(); + foreach ($table_copy_row as $_key => $_val) { + if (isset($row_fields[$_key]) && $row_fields[$_key] == 'cc') { + $value_parts[] = $GLOBALS['dbi']->escapeString($_val); + } + } + + $new_table_query = 'INSERT IGNORE INTO ' + . Util::backquote($GLOBALS['cfgRelation']['db']) + . '.' . Util::backquote($GLOBALS['cfgRelation'][$pma_table]) + . ' (' . implode(', ', $select_parts) . ', ' + . implode(', ', $new_parts) . ') VALUES (\'' + . implode('\', \'', $value_parts) . '\', \'' + . implode('\', \'', $new_value_parts) . '\')'; + + $relation->queryAsControlUser($new_table_query); + $last_id = $GLOBALS['dbi']->insertId(); + } // end while + + $GLOBALS['dbi']->freeResult($table_copy_rs); + + return $last_id; + } // end of 'Table::duplicateInfo()' function + + /** + * Copies or renames table + * + * @param string $source_db source database + * @param string $source_table source table + * @param string $target_db target database + * @param string $target_table target table + * @param string $what what to be moved or copied (data, dataonly) + * @param bool $move whether to move + * @param string $mode mode + * + * @return bool true if success, false otherwise + */ + public static function moveCopy($source_db, $source_table, $target_db, + $target_table, $what, $move, $mode + ) { + global $err_url; + + $relation = new Relation(); + + // Try moving the tables directly, using native `RENAME` statement. + if ($move && $what == 'data') { + $tbl = new Table($source_table, $source_db); + if ($tbl->rename($target_table, $target_db)) { + $GLOBALS['message'] = $tbl->getLastMessage(); + return true; + } + } + + // Setting required export settings. + $GLOBALS['sql_backquotes'] = 1; + $GLOBALS['asfile'] = 1; + + // Ensuring the target database is valid. + if (! $GLOBALS['dblist']->databases->exists($source_db, $target_db)) { + if (! $GLOBALS['dblist']->databases->exists($source_db)) { + $GLOBALS['message'] = Message::rawError( + sprintf( + __('Source database `%s` was not found!'), + htmlspecialchars($source_db) + ) + ); + } + if (! $GLOBALS['dblist']->databases->exists($target_db)) { + $GLOBALS['message'] = Message::rawError( + sprintf( + __('Target database `%s` was not found!'), + htmlspecialchars($target_db) + ) + ); + } + return false; + } + + /** + * The full name of source table, quoted. + * @var string $source + */ + $source = Util::backquote($source_db) + . '.' . Util::backquote($source_table); + + // If the target database is not specified, the operation is taking + // place in the same database. + if (! isset($target_db) || strlen($target_db) === 0) { + $target_db = $source_db; + } + + // Selecting the database could avoid some problems with replicated + // databases, when moving table from replicated one to not replicated one. + $GLOBALS['dbi']->selectDb($target_db); + + /** + * The full name of target table, quoted. + * @var string $target + */ + $target = Util::backquote($target_db) + . '.' . Util::backquote($target_table); + + // No table is created when this is a data-only operation. + if ($what != 'dataonly') { + /** + * Instance used for exporting the current structure of the table. + * + * @var PhpMyAdmin\Plugins\Export\ExportSql + */ + $export_sql_plugin = Plugins::getPlugin( + "export", + "sql", + 'libraries/classes/Plugins/Export/', + array( + 'export_type' => 'table', + 'single_table' => false, + ) + ); + + $no_constraints_comments = true; + $GLOBALS['sql_constraints_query'] = ''; + // set the value of global sql_auto_increment variable + if (isset($_POST['sql_auto_increment'])) { + $GLOBALS['sql_auto_increment'] = $_POST['sql_auto_increment']; + } + + /** + * The old structure of the table.. + * @var string $sql_structure + */ + $sql_structure = $export_sql_plugin->getTableDef( + $source_db, $source_table, "\n", $err_url, false, false + ); + + unset($no_constraints_comments); + + // ----------------------------------------------------------------- + // Phase 0: Preparing structures used. + + /** + * The destination where the table is moved or copied to. + * @var Expression + */ + $destination = new Expression( + $target_db, $target_table, '' + ); + + // Find server's SQL mode so the builder can generate correct + // queries. + // One of the options that alters the behaviour is `ANSI_QUOTES`. + Context::setMode( + $GLOBALS['dbi']->fetchValue("SELECT @@sql_mode") + ); + + // ----------------------------------------------------------------- + // Phase 1: Dropping existent element of the same name (if exists + // and required). + + if (isset($_POST['drop_if_exists']) + && $_POST['drop_if_exists'] == 'true' + ) { + + /** + * Drop statement used for building the query. + * @var DropStatement $statement + */ + $statement = new DropStatement(); + + $tbl = new Table($target_db, $target_table); + + $statement->options = new OptionsArray( + array( + $tbl->isView() ? 'VIEW' : 'TABLE', + 'IF EXISTS', + ) + ); + + $statement->fields = array($destination); + + // Building the query. + $drop_query = $statement->build() . ';'; + + // Executing it. + $GLOBALS['dbi']->query($drop_query); + $GLOBALS['sql_query'] .= "\n" . $drop_query; + + // If an existing table gets deleted, maintain any entries for + // the PMA_* tables. + $maintain_relations = true; + } + + // ----------------------------------------------------------------- + // Phase 2: Generating the new query of this structure. + + /** + * The parser responsible for parsing the old queries. + * @var Parser $parser + */ + $parser = new Parser($sql_structure); + + if (!empty($parser->statements[0])) { + + /** + * The CREATE statement of this structure. + * @var \PhpMyAdmin\SqlParser\Statements\CreateStatement $statement + */ + $statement = $parser->statements[0]; + + // Changing the destination. + $statement->name = $destination; + + // Building back the query. + $sql_structure = $statement->build() . ';'; + + // Executing it. + $GLOBALS['dbi']->query($sql_structure); + $GLOBALS['sql_query'] .= "\n" . $sql_structure; + } + + // ----------------------------------------------------------------- + // Phase 3: Adding constraints. + // All constraint names are removed because they must be unique. + + if (($move || isset($GLOBALS['add_constraints'])) + && !empty($GLOBALS['sql_constraints_query']) + ) { + + $parser = new Parser($GLOBALS['sql_constraints_query']); + + /** + * The ALTER statement that generates the constraints. + * @var \PhpMyAdmin\SqlParser\Statements\AlterStatement $statement + */ + $statement = $parser->statements[0]; + + // Changing the altered table to the destination. + $statement->table = $destination; + + // Removing the name of the constraints. + foreach ($statement->altered as $idx => $altered) { + // All constraint names are removed because they must be unique. + if ($altered->options->has('CONSTRAINT')) { + $altered->field = null; + } + } + + // Building back the query. + $GLOBALS['sql_constraints_query'] = $statement->build() . ';'; + + // Executing it. + if ($mode == 'one_table') { + $GLOBALS['dbi']->query($GLOBALS['sql_constraints_query']); + } + $GLOBALS['sql_query'] .= "\n" . $GLOBALS['sql_constraints_query']; + if ($mode == 'one_table') { + unset($GLOBALS['sql_constraints_query']); + } + } + + // ----------------------------------------------------------------- + // Phase 4: Adding indexes. + // View phase 3. + + if (!empty($GLOBALS['sql_indexes'])) { + + $parser = new Parser($GLOBALS['sql_indexes']); + + $GLOBALS['sql_indexes'] = ''; + /** + * The ALTER statement that generates the indexes. + * @var \PhpMyAdmin\SqlParser\Statements\AlterStatement $statement + */ + foreach ($parser->statements as $statement) { + + // Changing the altered table to the destination. + $statement->table = $destination; + + // Removing the name of the constraints. + foreach ($statement->altered as $idx => $altered) { + // All constraint names are removed because they must be unique. + if ($altered->options->has('CONSTRAINT')) { + $altered->field = null; + } + } + + // Building back the query. + $sql_index = $statement->build() . ';'; + + // Executing it. + if ($mode == 'one_table' || $mode == 'db_copy') { + $GLOBALS['dbi']->query($sql_index); + } + + $GLOBALS['sql_indexes'] .= $sql_index; + } + + $GLOBALS['sql_query'] .= "\n" . $GLOBALS['sql_indexes']; + if ($mode == 'one_table' || $mode == 'db_copy') { + unset($GLOBALS['sql_indexes']); + } + } + + // ----------------------------------------------------------------- + // Phase 5: Adding AUTO_INCREMENT. + + if (! empty($GLOBALS['sql_auto_increments'])) { + if ($mode == 'one_table' || $mode == 'db_copy') { + + $parser = new Parser($GLOBALS['sql_auto_increments']); + + /** + * The ALTER statement that alters the AUTO_INCREMENT value. + * @var \PhpMyAdmin\SqlParser\Statements\AlterStatement $statement + */ + $statement = $parser->statements[0]; + + // Changing the altered table to the destination. + $statement->table = $destination; + + // Building back the query. + $GLOBALS['sql_auto_increments'] = $statement->build() . ';'; + + // Executing it. + $GLOBALS['dbi']->query($GLOBALS['sql_auto_increments']); + $GLOBALS['sql_query'] .= "\n" . $GLOBALS['sql_auto_increments']; + unset($GLOBALS['sql_auto_increments']); + } + } + } else { + $GLOBALS['sql_query'] = ''; + } + + $_table = new Table($target_table, $target_db); + // Copy the data unless this is a VIEW + if (($what == 'data' || $what == 'dataonly') + && ! $_table->isView() + ) { + $sql_set_mode = "SET SQL_MODE='NO_AUTO_VALUE_ON_ZERO'"; + $GLOBALS['dbi']->query($sql_set_mode); + $GLOBALS['sql_query'] .= "\n\n" . $sql_set_mode . ';'; + + $_old_table = new Table($source_table, $source_db); + $nonGeneratedCols = $_old_table->getNonGeneratedColumns(true); + if (count($nonGeneratedCols) > 0) { + $sql_insert_data = 'INSERT INTO ' . $target . '(' + . implode(', ', $nonGeneratedCols) + . ') SELECT ' . implode(', ', $nonGeneratedCols) + . ' FROM ' . $source; + + $GLOBALS['dbi']->query($sql_insert_data); + $GLOBALS['sql_query'] .= "\n\n" . $sql_insert_data . ';'; + } + } + + $relation->getRelationsParam(); + + // Drops old table if the user has requested to move it + if ($move) { + + // This could avoid some problems with replicated databases, when + // moving table from replicated one to not replicated one + $GLOBALS['dbi']->selectDb($source_db); + + $_source_table = new Table($source_table, $source_db); + if ($_source_table->isView()) { + $sql_drop_query = 'DROP VIEW'; + } else { + $sql_drop_query = 'DROP TABLE'; + } + $sql_drop_query .= ' ' . $source; + $GLOBALS['dbi']->query($sql_drop_query); + + // Renable table in configuration storage + $relation->renameTable( + $source_db, $target_db, + $source_table, $target_table + ); + + $GLOBALS['sql_query'] .= "\n\n" . $sql_drop_query . ';'; + // end if ($move) + return true; + } + + // we are copying + // Create new entries as duplicates from old PMA DBs + if ($what == 'dataonly' || isset($maintain_relations)) { + return true; + } + + if ($GLOBALS['cfgRelation']['commwork']) { + // Get all comments and MIME-Types for current table + $comments_copy_rs = $relation->queryAsControlUser( + 'SELECT column_name, comment' + . ($GLOBALS['cfgRelation']['mimework'] + ? ', mimetype, transformation, transformation_options' + : '') + . ' FROM ' + . Util::backquote($GLOBALS['cfgRelation']['db']) + . '.' + . Util::backquote($GLOBALS['cfgRelation']['column_info']) + . ' WHERE ' + . ' db_name = \'' + . $GLOBALS['dbi']->escapeString($source_db) . '\'' + . ' AND ' + . ' table_name = \'' + . $GLOBALS['dbi']->escapeString($source_table) . '\'' + ); + + // Write every comment as new copied entry. [MIME] + while ($comments_copy_row + = $GLOBALS['dbi']->fetchAssoc($comments_copy_rs)) { + $new_comment_query = 'REPLACE INTO ' + . Util::backquote($GLOBALS['cfgRelation']['db']) + . '.' . Util::backquote( + $GLOBALS['cfgRelation']['column_info'] + ) + . ' (db_name, table_name, column_name, comment' + . ($GLOBALS['cfgRelation']['mimework'] + ? ', mimetype, transformation, transformation_options' + : '') + . ') ' . ' VALUES(' . '\'' . $GLOBALS['dbi']->escapeString($target_db) + . '\',\'' . $GLOBALS['dbi']->escapeString($target_table) . '\',\'' + . $GLOBALS['dbi']->escapeString($comments_copy_row['column_name']) + . '\',\'' + . $GLOBALS['dbi']->escapeString($comments_copy_row['comment']) + . '\'' + . ($GLOBALS['cfgRelation']['mimework'] + ? ',\'' . $GLOBALS['dbi']->escapeString( + $comments_copy_row['mimetype'] + ) + . '\',' . '\'' . $GLOBALS['dbi']->escapeString( + $comments_copy_row['transformation'] + ) + . '\',' . '\'' . $GLOBALS['dbi']->escapeString( + $comments_copy_row['transformation_options'] + ) + . '\'' + : '') + . ')'; + $relation->queryAsControlUser($new_comment_query); + } // end while + $GLOBALS['dbi']->freeResult($comments_copy_rs); + unset($comments_copy_rs); + } + + // duplicating the bookmarks must not be done here, but + // just once per db + + $get_fields = array('display_field'); + $where_fields = array( + 'db_name' => $source_db, + 'table_name' => $source_table + ); + $new_fields = array( + 'db_name' => $target_db, + 'table_name' => $target_table + ); + self::duplicateInfo( + 'displaywork', + 'table_info', + $get_fields, + $where_fields, + $new_fields + ); + + /** + * @todo revise this code when we support cross-db relations + */ + $get_fields = array( + 'master_field', + 'foreign_table', + 'foreign_field' + ); + $where_fields = array( + 'master_db' => $source_db, + 'master_table' => $source_table + ); + $new_fields = array( + 'master_db' => $target_db, + 'foreign_db' => $target_db, + 'master_table' => $target_table + ); + self::duplicateInfo( + 'relwork', + 'relation', + $get_fields, + $where_fields, + $new_fields + ); + + $get_fields = array( + 'foreign_field', + 'master_table', + 'master_field' + ); + $where_fields = array( + 'foreign_db' => $source_db, + 'foreign_table' => $source_table + ); + $new_fields = array( + 'master_db' => $target_db, + 'foreign_db' => $target_db, + 'foreign_table' => $target_table + ); + self::duplicateInfo( + 'relwork', + 'relation', + $get_fields, + $where_fields, + $new_fields + ); + + /** + * @todo Can't get duplicating PDFs the right way. The + * page numbers always get screwed up independently from + * duplication because the numbers do not seem to be stored on a + * per-database basis. Would the author of pdf support please + * have a look at it? + * + $get_fields = array('page_descr'); + $where_fields = array('db_name' => $source_db); + $new_fields = array('db_name' => $target_db); + $last_id = self::duplicateInfo( + 'pdfwork', + 'pdf_pages', + $get_fields, + $where_fields, + $new_fields + ); + + if (isset($last_id) && $last_id >= 0) { + $get_fields = array('x', 'y'); + $where_fields = array( + 'db_name' => $source_db, + 'table_name' => $source_table + ); + $new_fields = array( + 'db_name' => $target_db, + 'table_name' => $target_table, + 'pdf_page_number' => $last_id + ); + self::duplicateInfo( + 'pdfwork', + 'table_coords', + $get_fields, + $where_fields, + $new_fields + ); + } + */ + + return true; + } + + /** + * checks if given name is a valid table name, + * currently if not empty, trailing spaces, '.', '/' and '\' + * + * @param string $table_name name to check + * @param boolean $is_backquoted whether this name is used inside backquotes or not + * + * @todo add check for valid chars in filename on current system/os + * @see https://dev.mysql.com/doc/refman/5.0/en/legal-names.html + * + * @return boolean whether the string is valid or not + */ + static function isValidName($table_name, $is_backquoted = false) + { + if ($table_name !== rtrim($table_name)) { + // trailing spaces not allowed even in backquotes + return false; + } + + if (strlen($table_name) === 0) { + // zero length + return false; + } + + if (! $is_backquoted && $table_name !== trim($table_name)) { + // spaces at the start or in between only allowed inside backquotes + return false; + } + + if (! $is_backquoted && preg_match('/^[a-zA-Z0-9_$]+$/', $table_name)) { + // only allow the above regex in unquoted identifiers + // see : https://dev.mysql.com/doc/refman/5.7/en/identifiers.html + return true; + } elseif ($is_backquoted) { + // If backquoted, all characters should be allowed (except w/ trailing spaces) + return true; + } + + // If not backquoted and doesn't follow the above regex + return false; + } + + /** + * renames table + * + * @param string $new_name new table name + * @param string $new_db new database name + * + * @return bool success + */ + public function rename($new_name, $new_db = null) + { + if ($GLOBALS['dbi']->getLowerCaseNames() === '1') { + $new_name = strtolower($new_name); + } + + if (null !== $new_db && $new_db !== $this->getDbName()) { + // Ensure the target is valid + if (! $GLOBALS['dblist']->databases->exists($new_db)) { + $this->errors[] = __('Invalid database:') . ' ' . $new_db; + return false; + } + } else { + $new_db = $this->getDbName(); + } + + $new_table = new Table($new_name, $new_db); + + if ($this->getFullName() === $new_table->getFullName()) { + return true; + } + + // Allow whitespaces (not trailing) in $new_name, + // since we are using $backquoted in getting the fullName of table + // below to be used in the query + if (! self::isValidName($new_name, true)) { + $this->errors[] = __('Invalid table name:') . ' ' + . $new_table->getFullName(); + return false; + } + + // If the table is moved to a different database drop its triggers first + $triggers = $this->_dbi->getTriggers( + $this->getDbName(), $this->getName(), '' + ); + $handle_triggers = $this->getDbName() != $new_db && $triggers; + if ($handle_triggers) { + foreach ($triggers as $trigger) { + $sql = 'DROP TRIGGER IF EXISTS ' + . Util::backquote($this->getDbName()) + . '.' . Util::backquote($trigger['name']) . ';'; + $this->_dbi->query($sql); + } + } + + /* + * tested also for a view, in MySQL 5.0.92, 5.1.55 and 5.5.13 + */ + $GLOBALS['sql_query'] = ' + RENAME TABLE ' . $this->getFullName(true) . ' + TO ' . $new_table->getFullName(true) . ';'; + // I don't think a specific error message for views is necessary + if (! $this->_dbi->query($GLOBALS['sql_query'])) { + // Restore triggers in the old database + if ($handle_triggers) { + $this->_dbi->selectDb($this->getDbName()); + foreach ($triggers as $trigger) { + $this->_dbi->query($trigger['create']); + } + } + $this->errors[] = sprintf( + __('Failed to rename table %1$s to %2$s!'), + $this->getFullName(), + $new_table->getFullName() + ); + return false; + } + + $old_name = $this->getName(); + $old_db = $this->getDbName(); + $this->_name = $new_name; + $this->_db_name = $new_db; + + // Renable table in configuration storage + $this->relation->renameTable( + $old_db, $new_db, + $old_name, $new_name + ); + + $this->messages[] = sprintf( + __('Table %1$s has been renamed to %2$s.'), + htmlspecialchars($old_name), + htmlspecialchars($new_name) + ); + return true; + } + + /** + * Get all unique columns + * + * returns an array with all columns with unique content, in fact these are + * all columns being single indexed in PRIMARY or UNIQUE + * + * e.g. + * - PRIMARY(id) // id + * - UNIQUE(name) // name + * - PRIMARY(fk_id1, fk_id2) // NONE + * - UNIQUE(x,y) // NONE + * + * @param bool $backquoted whether to quote name with backticks `` + * @param bool $fullName whether to include full name of the table as a prefix + * + * @return array + */ + public function getUniqueColumns($backquoted = true, $fullName = true) + { + $sql = $this->_dbi->getTableIndexesSql( + $this->getDbName(), + $this->getName(), + 'Non_unique = 0' + ); + $uniques = $this->_dbi->fetchResult( + $sql, + array('Key_name', null), + 'Column_name' + ); + + $return = array(); + foreach ($uniques as $index) { + if (count($index) > 1) { + continue; + } + if ($fullName) { + $possible_column = $this->getFullName($backquoted) . '.'; + } else { + $possible_column = ''; + } + if ($backquoted) { + $possible_column .= Util::backquote($index[0]); + } else { + $possible_column .= $index[0]; + } + // a column might have a primary and an unique index on it + if (! in_array($possible_column, $return)) { + $return[] = $possible_column; + } + } + + return $return; + } + + /** + * Formats lists of columns + * + * returns an array with all columns that make use of an index + * + * e.g. index(col1, col2) would return col1, col2 + * + * @param array $indexed column data + * @param bool $backquoted whether to quote name with backticks `` + * @param bool $fullName whether to include full name of the table as a prefix + * + * @return array + */ + private function _formatColumns(array $indexed, $backquoted, $fullName) + { + $return = array(); + foreach ($indexed as $column) { + $return[] = ($fullName ? $this->getFullName($backquoted) . '.' : '') + . ($backquoted ? Util::backquote($column) : $column); + } + + return $return; + } + + /** + * Get all indexed columns + * + * returns an array with all columns that make use of an index + * + * e.g. index(col1, col2) would return col1, col2 + * + * @param bool $backquoted whether to quote name with backticks `` + * @param bool $fullName whether to include full name of the table as a prefix + * + * @return array + */ + public function getIndexedColumns($backquoted = true, $fullName = true) + { + $sql = $this->_dbi->getTableIndexesSql( + $this->getDbName(), + $this->getName(), + '' + ); + $indexed = $this->_dbi->fetchResult($sql, 'Column_name', 'Column_name'); + + return $this->_formatColumns($indexed, $backquoted, $fullName); + } + + /** + * Get all columns + * + * returns an array with all columns + * + * @param bool $backquoted whether to quote name with backticks `` + * @param bool $fullName whether to include full name of the table as a prefix + * + * @return array + */ + public function getColumns($backquoted = true, $fullName = true) + { + $sql = 'SHOW COLUMNS FROM ' . $this->getFullName(true); + $indexed = $this->_dbi->fetchResult($sql, 'Field', 'Field'); + + return $this->_formatColumns($indexed, $backquoted, $fullName); + } + + /** + * Get meta info for fields in table + * + * @return mixed + */ + public function getColumnsMeta() + { + $move_columns_sql_query = sprintf( + 'SELECT * FROM %s.%s LIMIT 1', + Util::backquote($this->_db_name), + Util::backquote($this->_name) + ); + $move_columns_sql_result = $this->_dbi->tryQuery($move_columns_sql_query); + if ($move_columns_sql_result !== false) { + return $this->_dbi->getFieldsMeta($move_columns_sql_result); + } else { + // unsure how to reproduce but it was seen on the reporting server + return array(); + } + } + + /** + * Get non-generated columns in table + * + * @param bool $backquoted whether to quote name with backticks `` + * + * @return array + */ + public function getNonGeneratedColumns($backquoted = true) + { + $columns_meta_query = 'SHOW COLUMNS FROM ' . $this->getFullName(true); + $ret = array(); + + $columns_meta_query_result = $this->_dbi->fetchResult( + $columns_meta_query + ); + + if ($columns_meta_query_result + && $columns_meta_query_result !== false + ) { + foreach ($columns_meta_query_result as $column) { + $value = $column['Field']; + if ($backquoted === true) { + $value = Util::backquote($value); + } + + if (( + strpos($column['Extra'], 'GENERATED') === false + && strpos($column['Extra'], 'VIRTUAL') === false + ) || $column['Extra'] === 'DEFAULT_GENERATED') { + array_push($ret, $value); + } + } + } + + return $ret; + } + + /** + * Return UI preferences for this table from phpMyAdmin database. + * + * @return array + */ + protected function getUiPrefsFromDb() + { + $cfgRelation = $this->relation->getRelationsParam(); + $pma_table = Util::backquote($cfgRelation['db']) . "." + . Util::backquote($cfgRelation['table_uiprefs']); + + // Read from phpMyAdmin database + $sql_query = " SELECT `prefs` FROM " . $pma_table + . " WHERE `username` = '" . $GLOBALS['dbi']->escapeString($GLOBALS['cfg']['Server']['user']) . "'" + . " AND `db_name` = '" . $GLOBALS['dbi']->escapeString($this->_db_name) . "'" + . " AND `table_name` = '" . $GLOBALS['dbi']->escapeString($this->_name) . "'"; + + $row = $this->_dbi->fetchArray($this->relation->queryAsControlUser($sql_query)); + if (isset($row[0])) { + return json_decode($row[0], true); + } + + return array(); + } + + /** + * Save this table's UI preferences into phpMyAdmin database. + * + * @return true|Message + */ + protected function saveUiPrefsToDb() + { + $cfgRelation = $this->relation->getRelationsParam(); + $pma_table = Util::backquote($cfgRelation['db']) . "." + . Util::backquote($cfgRelation['table_uiprefs']); + + $secureDbName = $GLOBALS['dbi']->escapeString($this->_db_name); + + $username = $GLOBALS['cfg']['Server']['user']; + $sql_query = " REPLACE INTO " . $pma_table + . " (username, db_name, table_name, prefs) VALUES ('" + . $GLOBALS['dbi']->escapeString($username) . "', '" . $secureDbName + . "', '" . $GLOBALS['dbi']->escapeString($this->_name) . "', '" + . $GLOBALS['dbi']->escapeString(json_encode($this->uiprefs)) . "')"; + + $success = $this->_dbi->tryQuery($sql_query, DatabaseInterface::CONNECT_CONTROL); + + if (!$success) { + $message = Message::error( + __('Could not save table UI preferences!') + ); + $message->addMessage( + Message::rawError( + $this->_dbi->getError(DatabaseInterface::CONNECT_CONTROL) + ), + '

      ' + ); + return $message; + } + + // Remove some old rows in table_uiprefs if it exceeds the configured + // maximum rows + $sql_query = 'SELECT COUNT(*) FROM ' . $pma_table; + $rows_count = $this->_dbi->fetchValue($sql_query); + $max_rows = $GLOBALS['cfg']['Server']['MaxTableUiprefs']; + if ($rows_count > $max_rows) { + $num_rows_to_delete = $rows_count - $max_rows; + $sql_query + = ' DELETE FROM ' . $pma_table . + ' ORDER BY last_update ASC' . + ' LIMIT ' . $num_rows_to_delete; + $success = $this->_dbi->tryQuery( + $sql_query, DatabaseInterface::CONNECT_CONTROL + ); + + if (!$success) { + $message = Message::error( + sprintf( + __( + 'Failed to cleanup table UI preferences (see ' . + '$cfg[\'Servers\'][$i][\'MaxTableUiprefs\'] %s)' + ), + Util::showDocu('config', 'cfg_Servers_MaxTableUiprefs') + ) + ); + $message->addMessage( + Message::rawError( + $this->_dbi->getError(DatabaseInterface::CONNECT_CONTROL) + ), + '

      ' + ); + return $message; + } + } + + return true; + } + + /** + * Loads the UI preferences for this table. + * If pmadb and table_uiprefs is set, it will load the UI preferences from + * phpMyAdmin database. + * + * @return void + */ + protected function loadUiPrefs() + { + $cfgRelation = $this->relation->getRelationsParam(); + $server_id = $GLOBALS['server']; + + // set session variable if it's still undefined + if (!isset($_SESSION['tmpval']['table_uiprefs'][$server_id][$this->_db_name][$this->_name])) { + // check whether we can get from pmadb + $_SESSION['tmpval']['table_uiprefs'][$server_id][$this->_db_name] + [$this->_name] = $cfgRelation['uiprefswork'] + ? $this->getUiPrefsFromDb() + : array(); + } + $this->uiprefs =& $_SESSION['tmpval']['table_uiprefs'][$server_id] + [$this->_db_name][$this->_name]; + } + + /** + * Get a property from UI preferences. + * Return false if the property is not found. + * Available property: + * - PROP_SORTED_COLUMN + * - PROP_COLUMN_ORDER + * - PROP_COLUMN_VISIB + * + * @param string $property property + * + * @return mixed + */ + public function getUiProp($property) + { + if (! isset($this->uiprefs)) { + $this->loadUiPrefs(); + } + + // do checking based on property + if ($property == self::PROP_SORTED_COLUMN) { + if (!isset($this->uiprefs[$property])) { + return false; + } + + if (!isset($_POST['discard_remembered_sort'])) { + // check if the column name exists in this table + $tmp = explode(' ', $this->uiprefs[$property]); + $colname = $tmp[0]; + //remove backquoting from colname + $colname = str_replace('`', '', $colname); + //get the available column name without backquoting + $avail_columns = $this->getColumns(false); + + foreach ($avail_columns as $each_col) { + // check if $each_col ends with $colname + if (substr_compare( + $each_col, + $colname, + mb_strlen($each_col) - mb_strlen($colname) + ) === 0 + ) { + return $this->uiprefs[$property]; + } + } + } + // remove the property, since it no longer exists in database + $this->removeUiProp($property); + return false; + } + + if ($property == self::PROP_COLUMN_ORDER + || $property == self::PROP_COLUMN_VISIB + ) { + if ($this->isView() || !isset($this->uiprefs[$property])) { + return false; + } + + // check if the table has not been modified + if ($this->getStatusInfo('Create_time') == $this->uiprefs['CREATE_TIME'] + ) { + return array_map('intval', $this->uiprefs[$property]); + } + + // remove the property, since the table has been modified + $this->removeUiProp($property); + return false; + } + + // default behaviour for other property: + return isset($this->uiprefs[$property]) ? $this->uiprefs[$property] : false; + } + + /** + * Set a property from UI preferences. + * If pmadb and table_uiprefs is set, it will save the UI preferences to + * phpMyAdmin database. + * Available property: + * - PROP_SORTED_COLUMN + * - PROP_COLUMN_ORDER + * - PROP_COLUMN_VISIB + * + * @param string $property Property + * @param mixed $value Value for the property + * @param string $table_create_time Needed for PROP_COLUMN_ORDER + * and PROP_COLUMN_VISIB + * + * @return boolean|Message + */ + public function setUiProp($property, $value, $table_create_time = null) + { + if (! isset($this->uiprefs)) { + $this->loadUiPrefs(); + } + // we want to save the create time if the property is PROP_COLUMN_ORDER + if (! $this->isView() + && ($property == self::PROP_COLUMN_ORDER + || $property == self::PROP_COLUMN_VISIB) + ) { + $curr_create_time = $this->getStatusInfo('CREATE_TIME'); + if (isset($table_create_time) + && $table_create_time == $curr_create_time + ) { + $this->uiprefs['CREATE_TIME'] = $curr_create_time; + } else { + // there is no $table_create_time, or + // supplied $table_create_time is older than current create time, + // so don't save + return Message::error( + sprintf( + __( + 'Cannot save UI property "%s". The changes made will ' . + 'not be persistent after you refresh this page. ' . + 'Please check if the table structure has been changed.' + ), + $property + ) + ); + } + } + // save the value + $this->uiprefs[$property] = $value; + + // check if pmadb is set + $cfgRelation = $this->relation->getRelationsParam(); + if ($cfgRelation['uiprefswork']) { + return $this->saveUiprefsToDb(); + } + return true; + } + + /** + * Remove a property from UI preferences. + * + * @param string $property the property + * + * @return true|Message + */ + public function removeUiProp($property) + { + if (! isset($this->uiprefs)) { + $this->loadUiPrefs(); + } + if (isset($this->uiprefs[$property])) { + unset($this->uiprefs[$property]); + + // check if pmadb is set + $cfgRelation = $this->relation->getRelationsParam(); + if ($cfgRelation['uiprefswork']) { + return $this->saveUiprefsToDb(); + } + } + return true; + } + + /** + * Get all column names which are MySQL reserved words + * + * @return array + * @access public + */ + public function getReservedColumnNames() + { + $columns = $this->getColumns(false); + $return = array(); + foreach ($columns as $column) { + $temp = explode('.', $column); + $column_name = $temp[2]; + if (Context::isKeyword($column_name, true)) { + $return[] = $column_name; + } + } + return $return; + } + + /** + * Function to get the name and type of the columns of a table + * + * @return array + */ + public function getNameAndTypeOfTheColumns() + { + $columns = array(); + foreach ($this->_dbi->getColumnsFull( + $this->_db_name, $this->_name + ) as $row) { + if (preg_match('@^(set|enum)\((.+)\)$@i', $row['Type'], $tmp)) { + $tmp[2] = mb_substr( + preg_replace('@([^,])\'\'@', '\\1\\\'', ',' . $tmp[2]), 1 + ); + $columns[$row['Field']] = $tmp[1] . '(' + . str_replace(',', ', ', $tmp[2]) . ')'; + } else { + $columns[$row['Field']] = $row['Type']; + } + } + return $columns; + } + + /** + * Get index with index name + * + * @param string $index Index name + * + * @return Index + */ + public function getIndex($index) + { + return Index::singleton($this->_db_name, $this->_name, $index); + } + + /** + * Function to get the sql query for index creation or edit + * + * @param Index $index current index + * @param bool &$error whether error occurred or not + * + * @return string + */ + public function getSqlQueryForIndexCreateOrEdit($index, &$error) + { + // $sql_query is the one displayed in the query box + $sql_query = sprintf( + 'ALTER TABLE %s.%s', + Util::backquote($this->_db_name), + Util::backquote($this->_name) + ); + + // Drops the old index + if (! empty($_POST['old_index'])) { + if ($_POST['old_index'] == 'PRIMARY') { + $sql_query .= ' DROP PRIMARY KEY,'; + } else { + $sql_query .= sprintf( + ' DROP INDEX %s,', + Util::backquote($_POST['old_index']) + ); + } + } // end if + + // Builds the new one + switch ($index->getChoice()) { + case 'PRIMARY': + if ($index->getName() == '') { + $index->setName('PRIMARY'); + } elseif ($index->getName() != 'PRIMARY') { + $error = Message::error( + __('The name of the primary key must be "PRIMARY"!') + ); + } + $sql_query .= ' ADD PRIMARY KEY'; + break; + case 'FULLTEXT': + case 'UNIQUE': + case 'INDEX': + case 'SPATIAL': + if ($index->getName() == 'PRIMARY') { + $error = Message::error( + __('Can\'t rename index to PRIMARY!') + ); + } + $sql_query .= sprintf( + ' ADD %s ', + $index->getChoice() + ); + if ($index->getName()) { + $sql_query .= Util::backquote($index->getName()); + } + break; + } // end switch + + $index_fields = array(); + foreach ($index->getColumns() as $key => $column) { + $index_fields[$key] = Util::backquote($column->getName()); + if ($column->getSubPart()) { + $index_fields[$key] .= '(' . $column->getSubPart() . ')'; + } + } // end while + + if (empty($index_fields)) { + $error = Message::error(__('No index parts defined!')); + } else { + $sql_query .= ' (' . implode(', ', $index_fields) . ')'; + } + + $keyBlockSizes = $index->getKeyBlockSize(); + if (! empty($keyBlockSizes)) { + $sql_query .= sprintf( + ' KEY_BLOCK_SIZE = %s', + $GLOBALS['dbi']->escapeString($keyBlockSizes) + ); + } + + // specifying index type is allowed only for primary, unique and index only + // TokuDB is using Fractal Tree, Using Type is not useless + // Ref: https://mariadb.com/kb/en/mariadb/storage-engine-index-types/ + $type = $index->getType(); + if ($index->getChoice() != 'SPATIAL' + && $index->getChoice() != 'FULLTEXT' + && in_array($type, Index::getIndexTypes()) + && ! $this->isEngine(array('TOKUDB')) + ) { + $sql_query .= ' USING ' . $type; + } + + $parser = $index->getParser(); + if ($index->getChoice() == 'FULLTEXT' && ! empty($parser)) { + $sql_query .= ' WITH PARSER ' . $GLOBALS['dbi']->escapeString($parser); + } + + $comment = $index->getComment(); + if (! empty($comment)) { + $sql_query .= sprintf( + " COMMENT '%s'", + $GLOBALS['dbi']->escapeString($comment) + ); + } + + $sql_query .= ';'; + + return $sql_query; + } + + /** + * Function to handle update for display field + * + * @param string $display_field display field + * @param array $cfgRelation configuration relation + * + * @return boolean True on update succeed or False on failure + */ + public function updateDisplayField($display_field, array $cfgRelation) + { + $upd_query = false; + if ($display_field == '') { + $upd_query = 'DELETE FROM ' + . Util::backquote($GLOBALS['cfgRelation']['db']) + . '.' . Util::backquote($cfgRelation['table_info']) + . ' WHERE db_name = \'' + . $GLOBALS['dbi']->escapeString($this->_db_name) . '\'' + . ' AND table_name = \'' + . $GLOBALS['dbi']->escapeString($this->_name) . '\''; + } else { + $upd_query = 'REPLACE INTO ' + . Util::backquote($GLOBALS['cfgRelation']['db']) + . '.' . Util::backquote($cfgRelation['table_info']) + . '(db_name, table_name, display_field) VALUES(' + . '\'' . $GLOBALS['dbi']->escapeString($this->_db_name) . '\',' + . '\'' . $GLOBALS['dbi']->escapeString($this->_name) . '\',' + . '\'' . $GLOBALS['dbi']->escapeString($display_field) . '\')'; + } + + if ($upd_query) { + $this->_dbi->query( + $upd_query, + DatabaseInterface::CONNECT_CONTROL, + 0, + false + ); + return true; + } + return false; + } + + /** + * Function to get update query for updating internal relations + * + * @param array $multi_edit_columns_name multi edit column names + * @param array $destination_db destination tables + * @param array $destination_table destination tables + * @param array $destination_column destination columns + * @param array $cfgRelation configuration relation + * @param array|null $existrel db, table, column + * + * @return boolean + */ + public function updateInternalRelations(array $multi_edit_columns_name, + array $destination_db, array $destination_table, array $destination_column, + array $cfgRelation, $existrel + ) { + $updated = false; + foreach ($destination_db as $master_field_md5 => $foreign_db) { + $upd_query = null; + // Map the fieldname's md5 back to its real name + $master_field = $multi_edit_columns_name[$master_field_md5]; + $foreign_table = $destination_table[$master_field_md5]; + $foreign_field = $destination_column[$master_field_md5]; + if (! empty($foreign_db) + && ! empty($foreign_table) + && ! empty($foreign_field) + ) { + if (! isset($existrel[$master_field])) { + $upd_query = 'INSERT INTO ' + . Util::backquote($GLOBALS['cfgRelation']['db']) + . '.' . Util::backquote($cfgRelation['relation']) + . '(master_db, master_table, master_field, foreign_db,' + . ' foreign_table, foreign_field)' + . ' values(' + . '\'' . $GLOBALS['dbi']->escapeString($this->_db_name) . '\', ' + . '\'' . $GLOBALS['dbi']->escapeString($this->_name) . '\', ' + . '\'' . $GLOBALS['dbi']->escapeString($master_field) . '\', ' + . '\'' . $GLOBALS['dbi']->escapeString($foreign_db) . '\', ' + . '\'' . $GLOBALS['dbi']->escapeString($foreign_table) . '\',' + . '\'' . $GLOBALS['dbi']->escapeString($foreign_field) . '\')'; + + } elseif ($existrel[$master_field]['foreign_db'] != $foreign_db + || $existrel[$master_field]['foreign_table'] != $foreign_table + || $existrel[$master_field]['foreign_field'] != $foreign_field + ) { + $upd_query = 'UPDATE ' + . Util::backquote($GLOBALS['cfgRelation']['db']) + . '.' . Util::backquote($cfgRelation['relation']) + . ' SET foreign_db = \'' + . $GLOBALS['dbi']->escapeString($foreign_db) . '\', ' + . ' foreign_table = \'' + . $GLOBALS['dbi']->escapeString($foreign_table) . '\', ' + . ' foreign_field = \'' + . $GLOBALS['dbi']->escapeString($foreign_field) . '\' ' + . ' WHERE master_db = \'' + . $GLOBALS['dbi']->escapeString($this->_db_name) . '\'' + . ' AND master_table = \'' + . $GLOBALS['dbi']->escapeString($this->_name) . '\'' + . ' AND master_field = \'' + . $GLOBALS['dbi']->escapeString($master_field) . '\''; + } // end if... else.... + } elseif (isset($existrel[$master_field])) { + $upd_query = 'DELETE FROM ' + . Util::backquote($GLOBALS['cfgRelation']['db']) + . '.' . Util::backquote($cfgRelation['relation']) + . ' WHERE master_db = \'' + . $GLOBALS['dbi']->escapeString($this->_db_name) . '\'' + . ' AND master_table = \'' + . $GLOBALS['dbi']->escapeString($this->_name) . '\'' + . ' AND master_field = \'' + . $GLOBALS['dbi']->escapeString($master_field) . '\''; + } // end if... else.... + + if (isset($upd_query)) { + $this->_dbi->query( + $upd_query, + DatabaseInterface::CONNECT_CONTROL, + 0, + false + ); + $updated = true; + } + } + return $updated; + } + + /** + * Function to handle foreign key updates + * + * @param array $destination_foreign_db destination foreign database + * @param array $multi_edit_columns_name multi edit column names + * @param array $destination_foreign_table destination foreign table + * @param array $destination_foreign_column destination foreign column + * @param array $options_array options array + * @param string $table current table + * @param array $existrel_foreign db, table, column + * + * @return array + */ + public function updateForeignKeys(array $destination_foreign_db, + array $multi_edit_columns_name, array $destination_foreign_table, + array $destination_foreign_column, array $options_array, $table, array $existrel_foreign + ) { + $html_output = ''; + $preview_sql_data = ''; + $display_query = ''; + $seen_error = false; + + foreach ($destination_foreign_db as $master_field_md5 => $foreign_db) { + $create = false; + $drop = false; + + // Map the fieldname's md5 back to its real name + $master_field = $multi_edit_columns_name[$master_field_md5]; + + $foreign_table = $destination_foreign_table[$master_field_md5]; + $foreign_field = $destination_foreign_column[$master_field_md5]; + + if (isset($existrel_foreign[$master_field_md5]['ref_db_name'])) { + $ref_db_name = $existrel_foreign[$master_field_md5]['ref_db_name']; + } else { + $ref_db_name = $GLOBALS['db']; + } + + $empty_fields = false; + foreach ($master_field as $key => $one_field) { + if ((! empty($one_field) && empty($foreign_field[$key])) + || (empty($one_field) && ! empty($foreign_field[$key])) + ) { + $empty_fields = true; + } + + if (empty($one_field) && empty($foreign_field[$key])) { + unset($master_field[$key]); + unset($foreign_field[$key]); + } + } + + if (! empty($foreign_db) + && ! empty($foreign_table) + && ! $empty_fields + ) { + if (isset($existrel_foreign[$master_field_md5])) { + $constraint_name + = $existrel_foreign[$master_field_md5]['constraint']; + $on_delete = !empty( + $existrel_foreign[$master_field_md5]['on_delete'] + ) + ? $existrel_foreign[$master_field_md5]['on_delete'] + : 'RESTRICT'; + $on_update = ! empty( + $existrel_foreign[$master_field_md5]['on_update'] + ) + ? $existrel_foreign[$master_field_md5]['on_update'] + : 'RESTRICT'; + + if ($ref_db_name != $foreign_db + || $existrel_foreign[$master_field_md5]['ref_table_name'] != $foreign_table + || $existrel_foreign[$master_field_md5]['ref_index_list'] != $foreign_field + || $existrel_foreign[$master_field_md5]['index_list'] != $master_field + || $_POST['constraint_name'][$master_field_md5] != $constraint_name + || ($_POST['on_delete'][$master_field_md5] != $on_delete) + || ($_POST['on_update'][$master_field_md5] != $on_update) + ) { + // another foreign key is already defined for this field + // or an option has been changed for ON DELETE or ON UPDATE + $drop = true; + $create = true; + } // end if... else.... + } else { + // no key defined for this field(s) + $create = true; + } + } elseif (isset($existrel_foreign[$master_field_md5])) { + $drop = true; + } // end if... else.... + + $tmp_error_drop = false; + if ($drop) { + $drop_query = 'ALTER TABLE ' . Util::backquote($table) + . ' DROP FOREIGN KEY ' + . Util::backquote( + $existrel_foreign[$master_field_md5]['constraint'] + ) + . ';'; + + if (! isset($_POST['preview_sql'])) { + $display_query .= $drop_query . "\n"; + $this->_dbi->tryQuery($drop_query); + $tmp_error_drop = $this->_dbi->getError(); + + if (! empty($tmp_error_drop)) { + $seen_error = true; + $html_output .= Util::mysqlDie( + $tmp_error_drop, $drop_query, false, '', false + ); + continue; + } + } else { + $preview_sql_data .= $drop_query . "\n"; + } + } + $tmp_error_create = false; + if (!$create) { + continue; + } + + $create_query = $this->_getSQLToCreateForeignKey( + $table, $master_field, $foreign_db, $foreign_table, $foreign_field, + $_POST['constraint_name'][$master_field_md5], + $options_array[$_POST['on_delete'][$master_field_md5]], + $options_array[$_POST['on_update'][$master_field_md5]] + ); + + if (! isset($_POST['preview_sql'])) { + $display_query .= $create_query . "\n"; + $this->_dbi->tryQuery($create_query); + $tmp_error_create = $this->_dbi->getError(); + if (! empty($tmp_error_create)) { + $seen_error = true; + + if (substr($tmp_error_create, 1, 4) == '1005') { + $message = Message::error( + __( + 'Error creating foreign key on %1$s (check data ' . + 'types)' + ) + ); + $message->addParam(implode(', ', $master_field)); + $html_output .= $message->getDisplay(); + } else { + $html_output .= Util::mysqlDie( + $tmp_error_create, $create_query, false, '', false + ); + } + $html_output .= Util::showMySQLDocu( + 'InnoDB_foreign_key_constraints' + ) . "\n"; + } + } else { + $preview_sql_data .= $create_query . "\n"; + } + + // this is an alteration and the old constraint has been dropped + // without creation of a new one + if ($drop && $create && empty($tmp_error_drop) + && ! empty($tmp_error_create) + ) { + // a rollback may be better here + $sql_query_recreate = '# Restoring the dropped constraint...' . "\n"; + $sql_query_recreate .= $this->_getSQLToCreateForeignKey( + $table, + $master_field, + $existrel_foreign[$master_field_md5]['ref_db_name'], + $existrel_foreign[$master_field_md5]['ref_table_name'], + $existrel_foreign[$master_field_md5]['ref_index_list'], + $existrel_foreign[$master_field_md5]['constraint'], + $options_array[$existrel_foreign[$master_field_md5]['on_delete']], + $options_array[$existrel_foreign[$master_field_md5]['on_update']] + ); + if (! isset($_POST['preview_sql'])) { + $display_query .= $sql_query_recreate . "\n"; + $this->_dbi->tryQuery($sql_query_recreate); + } else { + $preview_sql_data .= $sql_query_recreate; + } + } + } // end foreach + + return array( + $html_output, + $preview_sql_data, + $display_query, + $seen_error + ); + } + + /** + * Returns the SQL query for foreign key constraint creation + * + * @param string $table table name + * @param array $field field names + * @param string $foreignDb foreign database name + * @param string $foreignTable foreign table name + * @param array $foreignField foreign field names + * @param string $name name of the constraint + * @param string $onDelete on delete action + * @param string $onUpdate on update action + * + * @return string SQL query for foreign key constraint creation + */ + private function _getSQLToCreateForeignKey( + $table, + array $field, + $foreignDb, + $foreignTable, + array $foreignField, + $name = null, + $onDelete = null, + $onUpdate = null + ) { + $sql_query = 'ALTER TABLE ' . Util::backquote($table) . ' ADD '; + // if user entered a constraint name + if (! empty($name)) { + $sql_query .= ' CONSTRAINT ' . Util::backquote($name); + } + + foreach ($field as $key => $one_field) { + $field[$key] = Util::backquote($one_field); + } + foreach ($foreignField as $key => $one_field) { + $foreignField[$key] = Util::backquote($one_field); + } + $sql_query .= ' FOREIGN KEY (' . implode(', ', $field) . ') REFERENCES ' + . ($this->_db_name != $foreignDb + ? Util::backquote($foreignDb) . '.' : '') + . Util::backquote($foreignTable) + . '(' . implode(', ', $foreignField) . ')'; + + if (! empty($onDelete)) { + $sql_query .= ' ON DELETE ' . $onDelete; + } + if (! empty($onUpdate)) { + $sql_query .= ' ON UPDATE ' . $onUpdate; + } + $sql_query .= ';'; + + return $sql_query; + } + + /** + * Returns the generation expression for virtual columns + * + * @param string $column name of the column + * + * @return array|boolean associative array of column name and their expressions + * or false on failure + */ + public function getColumnGenerationExpression($column = null) + { + $serverType = Util::getServerType(); + if ($serverType == 'MySQL' + && $GLOBALS['dbi']->getVersion() > 50705 + && ! $GLOBALS['cfg']['Server']['DisableIS'] + ) { + $sql + = "SELECT + `COLUMN_NAME` AS `Field`, + `GENERATION_EXPRESSION` AS `Expression` + FROM + `information_schema`.`COLUMNS` + WHERE + `TABLE_SCHEMA` = '" . $GLOBALS['dbi']->escapeString($this->_db_name) . "' + AND `TABLE_NAME` = '" . $GLOBALS['dbi']->escapeString($this->_name) . "'"; + if ($column != null) { + $sql .= " AND `COLUMN_NAME` = '" . $GLOBALS['dbi']->escapeString($column) + . "'"; + } + $columns = $this->_dbi->fetchResult($sql, 'Field', 'Expression'); + return $columns; + } + + $createTable = $this->showCreate(); + if (!$createTable) { + return false; + } + + $parser = new Parser($createTable); + /** + * @var \PhpMyAdmin\SqlParser\Statements\CreateStatement $stmt + */ + $stmt = $parser->statements[0]; + $fields = TableUtils::getFields($stmt); + if ($column != null) { + $expression = isset($fields[$column]['expr']) ? + substr($fields[$column]['expr'], 1, -1) : ''; + return array($column => $expression); + } + + $ret = array(); + foreach ($fields as $field => $options) { + if (isset($options['expr'])) { + $ret[$field] = substr($options['expr'], 1, -1); + } + } + return $ret; + } + + /** + * Returns the CREATE statement for this table + * + * @return mixed + */ + public function showCreate() + { + return $this->_dbi->fetchValue( + 'SHOW CREATE TABLE ' . Util::backquote($this->_db_name) . '.' + . Util::backquote($this->_name), + 0, 1 + ); + } + + /** + * Returns the real row count for a table + * + * @return number + */ + public function getRealRowCountTable() + { + // SQL query to get row count for a table. + $result = $this->_dbi->fetchSingleRow( + sprintf( + 'SELECT COUNT(*) AS %s FROM %s.%s', + Util::backquote('row_count'), + Util::backquote($this->_db_name), + Util::backquote($this->_name) + ) + ); + return $result['row_count']; + } + + /** + * Get columns with indexes + * + * @param int $types types bitmask + * + * @return array an array of columns + */ + public function getColumnsWithIndex($types) + { + $columns_with_index = array(); + foreach ( + Index::getFromTableByChoice( + $this->_name, + $this->_db_name, + $types + ) as $index + ) { + $columns = $index->getColumns(); + foreach ($columns as $column_name => $dummy) { + $columns_with_index[] = $column_name; + } + } + return $columns_with_index; + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Template.php b/php/apps/phpmyadmin49/libraries/classes/Template.php new file mode 100644 index 00000000..02c9f6cf --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Template.php @@ -0,0 +1,135 @@ +name = $name; + + if (is_null($this::$twig)) { + $loader = new FilesystemLoader(static::BASE_PATH); + $cache_dir = $GLOBALS['PMA_Config']->getTempDir('twig'); + /* Twig expects false when cache is not configured */ + if (is_null($cache_dir)) { + $cache_dir = false; + } + $twig = new Environment($loader, array( + 'auto_reload' => true, + 'cache' => $cache_dir, + 'debug' => false, + )); + $twig->addExtension(new CharsetsExtension()); + $twig->addExtension(new CoreExtension()); + $twig->addExtension(new I18nExtension()); + $twig->addExtension(new IndexExtension()); + $twig->addExtension(new MessageExtension()); + $twig->addExtension(new PartitionExtension()); + $twig->addExtension(new PhpFunctionsExtension()); + $twig->addExtension(new PluginsExtension()); + $twig->addExtension(new RelationExtension()); + $twig->addExtension(new SanitizeExtension()); + $twig->addExtension(new ServerPrivilegesExtension()); + $twig->addExtension(new StorageEngineExtension()); + $twig->addExtension(new TrackerExtension()); + $twig->addExtension(new TableExtension()); + $twig->addExtension(new TransformationsExtension()); + $twig->addExtension(new UrlExtension()); + $twig->addExtension(new UtilExtension()); + $this::$twig = $twig; + } + } + + /** + * Template getter + * + * @param string $name Template name + * + * @return Template + */ + public static function get($name) + { + return new Template($name); + } + + /** + * Render template + * + * @param array $data Variables to be provided to the template + * + * @return string + */ + public function render(array $data = array()) + { + try { + $template = $this::$twig->load($this->name . '.twig'); + } catch (\RuntimeException $e) { + /* Retry with disabled cache */ + $this::$twig->setCache(false); + $template = $this::$twig->load($this->name . '.twig'); + /* + * The trigger error is intentionally after second load + * to avoid triggering error when disabling cache does not + * solve it. + */ + trigger_error( + sprintf( + __('Error while working with template cache: %s'), + $e->getMessage() + ), + E_USER_WARNING + ); + } + return $template->render($data); + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Theme.php b/php/apps/phpmyadmin49/libraries/classes/Theme.php new file mode 100644 index 00000000..d9ef6528 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Theme.php @@ -0,0 +1,465 @@ +getPath() . '/theme.json'; + if (! @file_exists($infofile)) { + return false; + } + + if ($this->mtime_info === filemtime($infofile)) { + return true; + } + $content = @file_get_contents($infofile); + if ($content === false) { + return false; + } + $data = json_decode($content, true); + + // Did we get expected data? + if (! is_array($data)) { + return false; + } + // Check that all required data are there + $members = array('name', 'version', 'supports'); + foreach ($members as $member) { + if (! isset($data[$member])) { + return false; + } + } + + // Version check + if (! is_array($data['supports'])) { + return false; + } + if (! in_array(PMA_MAJOR_VERSION, $data['supports'])) { + return false; + } + + $this->mtime_info = filemtime($infofile); + $this->filesize_info = filesize($infofile); + + $this->setVersion($data['version']); + $this->setName($data['name']); + + return true; + } + + /** + * returns theme object loaded from given folder + * or false if theme is invalid + * + * @param string $folder path to theme + * + * @return Theme|false + * @static + * @access public + */ + static public function load($folder) + { + $theme = new Theme(); + + $theme->setPath($folder); + + if (! $theme->loadInfo()) { + return false; + } + + $theme->checkImgPath(); + + return $theme; + } + + /** + * checks image path for existence - if not found use img from fallback theme + * + * @access public + * @return bool + */ + public function checkImgPath() + { + // try current theme first + if (is_dir($this->getPath() . '/img/')) { + $this->setImgPath($this->getPath() . '/img/'); + return true; + } + + // try fallback theme + $fallback = './themes/' . ThemeManager::FALLBACK_THEME . '/img/'; + if (is_dir($fallback)) { + $this->setImgPath($fallback); + return true; + } + + // we failed + trigger_error( + sprintf( + __('No valid image path for theme %s found!'), + $this->getName() + ), + E_USER_ERROR + ); + return false; + } + + /** + * returns path to theme + * + * @access public + * @return string path to theme + */ + public function getPath() + { + return $this->path; + } + + /** + * returns layout file + * + * @access public + * @return string layout file + */ + public function getLayoutFile() + { + return $this->getPath() . '/layout.inc.php'; + } + + /** + * set path to theme + * + * @param string $path path to theme + * + * @return void + * @access public + */ + public function setPath($path) + { + $this->path = trim($path); + } + + /** + * sets version + * + * @param string $version version to set + * + * @return void + * @access public + */ + public function setVersion($version) + { + $this->version = trim($version); + } + + /** + * returns version + * + * @return string version + * @access public + */ + public function getVersion() + { + return $this->version; + } + + /** + * checks theme version against $version + * returns true if theme version is equal or higher to $version + * + * @param string $version version to compare to + * + * @return boolean true if theme version is equal or higher to $version + * @access public + */ + public function checkVersion($version) + { + return version_compare($this->getVersion(), $version, 'lt'); + } + + /** + * sets name + * + * @param string $name name to set + * + * @return void + * @access public + */ + public function setName($name) + { + $this->name = trim($name); + } + + /** + * returns name + * + * @access public + * @return string name + */ + public function getName() + { + return $this->name; + } + + /** + * sets id + * + * @param string $id new id + * + * @return void + * @access public + */ + public function setId($id) + { + $this->id = trim($id); + } + + /** + * returns id + * + * @return string id + * @access public + */ + public function getId() + { + return $this->id; + } + + /** + * Sets path to images for the theme + * + * @param string $path path to images for this theme + * + * @return void + * @access public + */ + public function setImgPath($path) + { + $this->img_path = $path; + } + + /** + * Returns the path to image for the theme. + * If filename is given, it possibly fallbacks to fallback + * theme for it if image does not exist. + * + * @param string $file file name for image + * @param string $fallback fallback image + * + * @access public + * @return string image path for this theme + */ + public function getImgPath($file = null, $fallback = null) + { + if (is_null($file)) { + return $this->img_path; + } + + if (is_readable($this->img_path . $file)) { + return $this->img_path . $file; + } + + if (! is_null($fallback)) { + return $this->getImgPath($fallback); + } + + return './themes/' . ThemeManager::FALLBACK_THEME . '/img/' . $file; + } + + /** + * load css (send to stdout, normally the browser) + * + * @return bool + * @access public + */ + public function loadCss() + { + $success = true; + + /* Variables to be used by the themes: */ + $theme = $this; + if ($GLOBALS['text_dir'] === 'ltr') { + $right = 'right'; + $left = 'left'; + } else { + $right = 'left'; + $left = 'right'; + } + + foreach ($this->_cssFiles as $file) { + $path = $this->getPath() . "/css/$file.css.php"; + $fallback = "./themes/" + . ThemeManager::FALLBACK_THEME . "/css/$file.css.php"; + + if (is_readable($path)) { + echo "\n/* FILE: " , $file , ".css.php */\n"; + include $path; + } elseif (is_readable($fallback)) { + echo "\n/* FILE: " , $file , ".css.php */\n"; + include $fallback; + } else { + $success = false; + } + } + return $success; + } + + /** + * Renders the preview for this theme + * + * @return string + * @access public + */ + public function getPrintPreview() + { + $url_params = ['set_theme' => $this->getId()]; + $screen = null; + $path = $this->getPath() . '/screen.png'; + if (@file_exists($path)) { + $screen = $path; + } + + return Template::get('theme_preview')->render([ + 'url_params' => $url_params, + 'name' => $this->getName(), + 'version' => $this->getVersion(), + 'id' => $this->getId(), + 'screen' => $screen, + ]); + } + + /** + * Gets currently configured font size. + * + * @return String with font size. + */ + function getFontSize() + { + $fs = $GLOBALS['PMA_Config']->get('FontSize'); + if (!is_null($fs)) { + return $fs; + } + return '82%'; + } + + /** + * Generates code for CSS gradient using various browser extensions. + * + * @param string $start_color Color of gradient start, hex value without # + * @param string $end_color Color of gradient end, hex value without # + * + * @return string CSS code. + */ + function getCssGradient($start_color, $end_color) + { + $result = array(); + // Opera 9.5+, IE 9 + $result[] = 'background-image: url(./themes/svg_gradient.php?from=' + . $start_color . '&to=' . $end_color . ');'; + $result[] = 'background-size: 100% 100%;'; + // Safari 4-5, Chrome 1-9 + $result[] = 'background: ' + . '-webkit-gradient(linear, left top, left bottom, from(#' + . $start_color . '), to(#' . $end_color . '));'; + // Safari 5.1, Chrome 10+ + $result[] = 'background: -webkit-linear-gradient(top, #' + . $start_color . ', #' . $end_color . ');'; + // Firefox 3.6+ + $result[] = 'background: -moz-linear-gradient(top, #' + . $start_color . ', #' . $end_color . ');'; + // IE 10 + $result[] = 'background: -ms-linear-gradient(top, #' + . $start_color . ', #' . $end_color . ');'; + // Opera 11.10 + $result[] = 'background: -o-linear-gradient(top, #' + . $start_color . ', #' . $end_color . ');'; + return implode("\n", $result); + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/ThemeManager.php b/php/apps/phpmyadmin49/libraries/classes/ThemeManager.php new file mode 100644 index 00000000..9d7ff2c2 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/ThemeManager.php @@ -0,0 +1,453 @@ +themes = array(); + $this->theme_default = self::FALLBACK_THEME; + $this->active_theme = ''; + + if (! $this->setThemesPath('./themes/')) { + return; + } + + $this->setThemePerServer($GLOBALS['cfg']['ThemePerServer']); + + $this->loadThemes(); + + $this->theme = new Theme; + + if (! $this->checkTheme($GLOBALS['cfg']['ThemeDefault'])) { + trigger_error( + sprintf( + __('Default theme %s not found!'), + htmlspecialchars($GLOBALS['cfg']['ThemeDefault']) + ), + E_USER_ERROR + ); + $GLOBALS['cfg']['ThemeDefault'] = false; + } + + $this->theme_default = $GLOBALS['cfg']['ThemeDefault']; + + // check if user have a theme cookie + $cookie_theme = $this->getThemeCookie(); + if (! $cookie_theme || ! $this->setActiveTheme($cookie_theme)) { + if ($GLOBALS['cfg']['ThemeDefault']) { + // otherwise use default theme + $this->setActiveTheme($this->theme_default); + } else { + // or fallback theme + $this->setActiveTheme(self::FALLBACK_THEME); + } + } + } + + /** + * Returns the singleton Response object + * + * @return Response object + */ + public static function getInstance() + { + if (empty(self::$_instance)) { + self::$_instance = new ThemeManager(); + } + return self::$_instance; + } + + /** + * sets path to folder containing the themes + * + * @param string $path path to themes folder + * + * @access public + * @return boolean success + */ + public function setThemesPath($path) + { + if (! $this->_checkThemeFolder($path)) { + return false; + } + + $this->_themes_path = trim($path); + return true; + } + + /** + * sets if there are different themes per server + * + * @param boolean $per_server Whether to enable per server flag + * + * @access public + * @return void + */ + public function setThemePerServer($per_server) + { + $this->per_server = (bool) $per_server; + } + + /** + * Sets active theme + * + * @param string $theme theme name + * + * @access public + * @return bool true on success + */ + public function setActiveTheme($theme = null) + { + if (! $this->checkTheme($theme)) { + trigger_error( + sprintf( + __('Theme %s not found!'), + htmlspecialchars($theme) + ), + E_USER_ERROR + ); + return false; + } + + $this->active_theme = $theme; + $this->theme = $this->themes[$theme]; + + // need to set later + //$this->setThemeCookie(); + + return true; + } + + /** + * Returns name for storing theme + * + * @return string cookie name + * @access public + */ + public function getThemeCookieName() + { + // Allow different theme per server + if (isset($GLOBALS['server']) && $this->per_server) { + return $this->cookie_name . '-' . $GLOBALS['server']; + } + + return $this->cookie_name; + } + + /** + * returns name of theme stored in the cookie + * + * @return string theme name from cookie + * @access public + */ + public function getThemeCookie() + { + $name = $this->getThemeCookieName(); + if (isset($_COOKIE[$name])) { + return $_COOKIE[$name]; + } + + return false; + } + + /** + * save theme in cookie + * + * @return bool true + * @access public + */ + public function setThemeCookie() + { + $GLOBALS['PMA_Config']->setCookie( + $this->getThemeCookieName(), + $this->theme->id, + $this->theme_default + ); + // force a change of a dummy session variable to avoid problems + // with the caching of phpmyadmin.css.php + $GLOBALS['PMA_Config']->set('theme-update', $this->theme->id); + return true; + } + + /** + * Checks whether folder is valid for storing themes + * + * @param string $folder Folder name to test + * + * @return boolean + * @access private + */ + private function _checkThemeFolder($folder) + { + if (! is_dir($folder)) { + trigger_error( + sprintf( + __('Theme path not found for theme %s!'), + htmlspecialchars($folder) + ), + E_USER_ERROR + ); + return false; + } + + return true; + } + + /** + * read all themes + * + * @return bool true + * @access public + */ + public function loadThemes() + { + $this->themes = array(); + + if (false === ($handleThemes = opendir($this->_themes_path))) { + trigger_error( + 'phpMyAdmin-ERROR: cannot open themes folder: ' + . $this->_themes_path, + E_USER_WARNING + ); + return false; + } + + // check for themes directory + while (false !== ($PMA_Theme = readdir($handleThemes))) { + // Skip non dirs, . and .. + if ($PMA_Theme == '.' + || $PMA_Theme == '..' + || ! @is_dir($this->_themes_path . $PMA_Theme) + ) { + continue; + } + if (array_key_exists($PMA_Theme, $this->themes)) { + continue; + } + $new_theme = Theme::load( + $this->_themes_path . $PMA_Theme + ); + if ($new_theme) { + $new_theme->setId($PMA_Theme); + $this->themes[$PMA_Theme] = $new_theme; + } + } // end get themes + closedir($handleThemes); + + ksort($this->themes); + return true; + } + + /** + * checks if given theme name is a known theme + * + * @param string $theme name fo theme to check for + * + * @return bool + * @access public + */ + public function checkTheme($theme) + { + return array_key_exists($theme, $this->themes); + } + + /** + * returns HTML selectbox, with or without form enclosed + * + * @param boolean $form whether enclosed by from tags or not + * + * @return string + * @access public + */ + public function getHtmlSelectBox($form = true) + { + $select_box = ''; + + if ($form) { + $select_box .= '
      '; + $select_box .= $theme_preview_href . __('Theme:') . '' . "\n"; + + $select_box .= ''; + + if ($form) { + $select_box .= '
      '; + } + + return $select_box; + } + + /** + * Renders the previews for all themes + * + * @return string + * @access public + */ + public function getPrintPreviews() + { + $retval = ''; + foreach ($this->themes as $each_theme) { + $retval .= $each_theme->getPrintPreview(); + } // end 'open themes' + return $retval; + } + + /** + * returns Theme object for fall back theme + * + * @return Theme fall back theme + * @access public + */ + public function getFallBackTheme() + { + if (isset($this->themes[self::FALLBACK_THEME])) { + return $this->themes[self::FALLBACK_THEME]; + } + + return false; + } + + /** + * prints css data + * + * @return bool + * @access public + */ + public function printCss() + { + if ($this->theme->loadCss()) { + return true; + } + + // if loading css for this theme failed, try default theme css + $fallback_theme = $this->getFallBackTheme(); + if ($fallback_theme && $fallback_theme->loadCss()) { + return true; + } + + return false; + } + + /** + * Theme initialization + * + * @return void + * @access public + */ + public static function initializeTheme() + { + $tmanager = self::getInstance(); + + /** + * the theme object + * + * @global Theme $GLOBALS['PMA_Theme'] + */ + $GLOBALS['PMA_Theme'] = $tmanager->theme; + + // BC + /** + * the theme path + * @global string $GLOBALS['pmaThemePath'] + */ + $GLOBALS['pmaThemePath'] = $GLOBALS['PMA_Theme']->getPath(); + /** + * the theme image path + * @global string $GLOBALS['pmaThemeImage'] + */ + $GLOBALS['pmaThemeImage'] = $GLOBALS['PMA_Theme']->getImgPath(); + + /** + * load layout file if exists + */ + if (@file_exists($GLOBALS['PMA_Theme']->getLayoutFile())) { + include $GLOBALS['PMA_Theme']->getLayoutFile(); + } + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Tracker.php b/php/apps/phpmyadmin49/libraries/classes/Tracker.php new file mode 100644 index 00000000..027fb1af --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Tracker.php @@ -0,0 +1,924 @@ +getRelationsParam(); + /* Restore original state */ + self::$enabled = true; + if (! $cfgRelation['trackingwork']) { + return false; + } + + $pma_table = self::_getTrackingTable(); + + return isset($pma_table); + } + + /** + * Parses the name of a table from a SQL statement substring. + * + * @param string $string part of SQL statement + * + * @static + * + * @return string the name of table + */ + static protected function getTableName($string) + { + if (mb_strstr($string, '.')) { + $temp = explode('.', $string); + $tablename = $temp[1]; + } else { + $tablename = $string; + } + + $str = explode("\n", $tablename); + $tablename = $str[0]; + + $tablename = str_replace(';', '', $tablename); + $tablename = str_replace('`', '', $tablename); + $tablename = trim($tablename); + + return $tablename; + } + + + /** + * Gets the tracking status of a table, is it active or deactive ? + * + * @param string $dbname name of database + * @param string $tablename name of table + * + * @static + * + * @return boolean true or false + */ + public static function isTracked($dbname, $tablename) + { + if (! self::$enabled) { + return false; + } + + if (isset(self::$_tracking_cache[$dbname][$tablename])) { + return self::$_tracking_cache[$dbname][$tablename]; + } + /* We need to avoid attempt to track any queries + * from Relation::getRelationsParam + */ + self::$enabled = false; + $relation = new Relation(); + $cfgRelation = $relation->getRelationsParam(); + /* Restore original state */ + self::$enabled = true; + if (! $cfgRelation['trackingwork']) { + return false; + } + + $sql_query = " SELECT tracking_active FROM " . self::_getTrackingTable() . + " WHERE db_name = '" . $GLOBALS['dbi']->escapeString($dbname) . "' " . + " AND table_name = '" . $GLOBALS['dbi']->escapeString($tablename) . "' " . + " ORDER BY version DESC LIMIT 1"; + + $result = $GLOBALS['dbi']->fetchValue($sql_query, 0, 0, DatabaseInterface::CONNECT_CONTROL) == 1; + + self::$_tracking_cache[$dbname][$tablename] = $result; + + return $result; + } + + /** + * Returns the comment line for the log. + * + * @return string Comment, contains date and username + */ + public static function getLogComment() + { + $date = Util::date('Y-m-d H:i:s'); + $user = preg_replace('/\s+/', ' ', $GLOBALS['cfg']['Server']['user']); + + return "# log " . $date . " " . $user . "\n"; + } + + /** + * Creates tracking version of a table / view + * (in other words: create a job to track future changes on the table). + * + * @param string $dbname name of database + * @param string $tablename name of table + * @param string $version version + * @param string $tracking_set set of tracking statements + * @param bool $is_view if table is a view + * + * @static + * + * @return int result of version insertion + */ + public static function createVersion($dbname, $tablename, $version, + $tracking_set = '', $is_view = false + ) { + global $sql_backquotes, $export_type; + + $relation = new Relation(); + + if ($tracking_set == '') { + $tracking_set + = $GLOBALS['cfg']['Server']['tracking_default_statements']; + } + + // get Export SQL instance + /* @var $export_sql_plugin PhpMyAdmin\Plugins\Export\ExportSql */ + $export_sql_plugin = Plugins::getPlugin( + "export", + "sql", + 'libraries/classes/Plugins/Export/', + array( + 'export_type' => $export_type, + 'single_table' => false, + ) + ); + + $sql_backquotes = true; + + $date = Util::date('Y-m-d H:i:s'); + + // Get data definition snapshot of table + + $columns = $GLOBALS['dbi']->getColumns($dbname, $tablename, null, true); + // int indices to reduce size + $columns = array_values($columns); + // remove Privileges to reduce size + for ($i = 0, $nb = count($columns); $i < $nb; $i++) { + unset($columns[$i]['Privileges']); + } + + $indexes = $GLOBALS['dbi']->getTableIndexes($dbname, $tablename); + + $snapshot = array('COLUMNS' => $columns, 'INDEXES' => $indexes); + $snapshot = serialize($snapshot); + + // Get DROP TABLE / DROP VIEW and CREATE TABLE SQL statements + $sql_backquotes = true; + + $create_sql = ""; + + if ($GLOBALS['cfg']['Server']['tracking_add_drop_table'] == true + && $is_view == false + ) { + $create_sql .= self::getLogComment() + . 'DROP TABLE IF EXISTS ' . Util::backquote($tablename) . ";\n"; + + } + + if ($GLOBALS['cfg']['Server']['tracking_add_drop_view'] == true + && $is_view == true + ) { + $create_sql .= self::getLogComment() + . 'DROP VIEW IF EXISTS ' . Util::backquote($tablename) . ";\n"; + } + + $create_sql .= self::getLogComment() . + $export_sql_plugin->getTableDef($dbname, $tablename, "\n", ""); + + // Save version + + $sql_query = "/*NOTRACK*/\n" . + "INSERT INTO " . self::_getTrackingTable() . " (" . + "db_name, " . + "table_name, " . + "version, " . + "date_created, " . + "date_updated, " . + "schema_snapshot, " . + "schema_sql, " . + "data_sql, " . + "tracking " . + ") " . + "values ( + '" . $GLOBALS['dbi']->escapeString($dbname) . "', + '" . $GLOBALS['dbi']->escapeString($tablename) . "', + '" . $GLOBALS['dbi']->escapeString($version) . "', + '" . $GLOBALS['dbi']->escapeString($date) . "', + '" . $GLOBALS['dbi']->escapeString($date) . "', + '" . $GLOBALS['dbi']->escapeString($snapshot) . "', + '" . $GLOBALS['dbi']->escapeString($create_sql) . "', + '" . $GLOBALS['dbi']->escapeString("\n") . "', + '" . $GLOBALS['dbi']->escapeString($tracking_set) + . "' )"; + + $result = $relation->queryAsControlUser($sql_query); + + if ($result) { + // Deactivate previous version + self::deactivateTracking($dbname, $tablename, ($version - 1)); + } + + return $result; + } + + + /** + * Removes all tracking data for a table or a version of a table + * + * @param string $dbname name of database + * @param string $tablename name of table + * @param string $version version + * + * @static + * + * @return int result of version insertion + */ + public static function deleteTracking($dbname, $tablename, $version = '') + { + $relation = new Relation(); + + $sql_query = "/*NOTRACK*/\n" + . "DELETE FROM " . self::_getTrackingTable() + . " WHERE `db_name` = '" + . $GLOBALS['dbi']->escapeString($dbname) . "'" + . " AND `table_name` = '" + . $GLOBALS['dbi']->escapeString($tablename) . "'"; + if ($version) { + $sql_query .= " AND `version` = '" + . $GLOBALS['dbi']->escapeString($version) . "'"; + } + $result = $relation->queryAsControlUser($sql_query); + + return $result; + } + + /** + * Creates tracking version of a database + * (in other words: create a job to track future changes on the database). + * + * @param string $dbname name of database + * @param string $version version + * @param string $query query + * @param string $tracking_set set of tracking statements + * + * @static + * + * @return int result of version insertion + */ + public static function createDatabaseVersion($dbname, $version, $query, + $tracking_set = 'CREATE DATABASE,ALTER DATABASE,DROP DATABASE' + ) { + $relation = new Relation(); + + $date = Util::date('Y-m-d H:i:s'); + + if ($tracking_set == '') { + $tracking_set + = $GLOBALS['cfg']['Server']['tracking_default_statements']; + } + + $create_sql = ""; + + if ($GLOBALS['cfg']['Server']['tracking_add_drop_database'] == true) { + $create_sql .= self::getLogComment() + . 'DROP DATABASE IF EXISTS ' . Util::backquote($dbname) . ";\n"; + } + + $create_sql .= self::getLogComment() . $query; + + // Save version + $sql_query = "/*NOTRACK*/\n" . + "INSERT INTO " . self::_getTrackingTable() . " (" . + "db_name, " . + "table_name, " . + "version, " . + "date_created, " . + "date_updated, " . + "schema_snapshot, " . + "schema_sql, " . + "data_sql, " . + "tracking " . + ") " . + "values ( + '" . $GLOBALS['dbi']->escapeString($dbname) . "', + '" . $GLOBALS['dbi']->escapeString('') . "', + '" . $GLOBALS['dbi']->escapeString($version) . "', + '" . $GLOBALS['dbi']->escapeString($date) . "', + '" . $GLOBALS['dbi']->escapeString($date) . "', + '" . $GLOBALS['dbi']->escapeString('') . "', + '" . $GLOBALS['dbi']->escapeString($create_sql) . "', + '" . $GLOBALS['dbi']->escapeString("\n") . "', + '" . $GLOBALS['dbi']->escapeString($tracking_set) + . "' )"; + + $result = $relation->queryAsControlUser($sql_query); + + return $result; + } + + + + /** + * Changes tracking of a table. + * + * @param string $dbname name of database + * @param string $tablename name of table + * @param string $version version + * @param integer $new_state the new state of tracking + * + * @static + * + * @return int result of SQL query + */ + static private function _changeTracking($dbname, $tablename, + $version, $new_state + ) { + $relation = new Relation(); + + $sql_query = " UPDATE " . self::_getTrackingTable() . + " SET `tracking_active` = '" . $new_state . "' " . + " WHERE `db_name` = '" . $GLOBALS['dbi']->escapeString($dbname) . "' " . + " AND `table_name` = '" . $GLOBALS['dbi']->escapeString($tablename) . "' " . + " AND `version` = '" . $GLOBALS['dbi']->escapeString($version) . "' "; + + $result = $relation->queryAsControlUser($sql_query); + + return $result; + } + + /** + * Changes tracking data of a table. + * + * @param string $dbname name of database + * @param string $tablename name of table + * @param string $version version + * @param string $type type of data(DDL || DML) + * @param string|array $new_data the new tracking data + * + * @static + * + * @return bool result of change + */ + public static function changeTrackingData($dbname, $tablename, + $version, $type, $new_data + ) { + $relation = new Relation(); + + if ($type == 'DDL') { + $save_to = 'schema_sql'; + } elseif ($type == 'DML') { + $save_to = 'data_sql'; + } else { + return false; + } + $date = Util::date('Y-m-d H:i:s'); + + $new_data_processed = ''; + if (is_array($new_data)) { + foreach ($new_data as $data) { + $new_data_processed .= '# log ' . $date . ' ' . $data['username'] + . $GLOBALS['dbi']->escapeString($data['statement']) . "\n"; + } + } else { + $new_data_processed = $new_data; + } + + $sql_query = " UPDATE " . self::_getTrackingTable() . + " SET `" . $save_to . "` = '" . $new_data_processed . "' " . + " WHERE `db_name` = '" . $GLOBALS['dbi']->escapeString($dbname) . "' " . + " AND `table_name` = '" . $GLOBALS['dbi']->escapeString($tablename) . "' " . + " AND `version` = '" . $GLOBALS['dbi']->escapeString($version) . "' "; + + $result = $relation->queryAsControlUser($sql_query); + + return (boolean) $result; + } + + /** + * Activates tracking of a table. + * + * @param string $dbname name of database + * @param string $tablename name of table + * @param string $version version + * + * @static + * + * @return int result of SQL query + */ + public static function activateTracking($dbname, $tablename, $version) + { + return self::_changeTracking($dbname, $tablename, $version, 1); + } + + + /** + * Deactivates tracking of a table. + * + * @param string $dbname name of database + * @param string $tablename name of table + * @param string $version version + * + * @static + * + * @return int result of SQL query + */ + public static function deactivateTracking($dbname, $tablename, $version) + { + return self::_changeTracking($dbname, $tablename, $version, 0); + } + + + /** + * Gets the newest version of a tracking job + * (in other words: gets the HEAD version). + * + * @param string $dbname name of database + * @param string $tablename name of table + * @param string $statement tracked statement + * + * @static + * + * @return int (-1 if no version exists | > 0 if a version exists) + */ + public static function getVersion($dbname, $tablename, $statement = null) + { + $relation = new Relation(); + + $sql_query = " SELECT MAX(version) FROM " . self::_getTrackingTable() . + " WHERE `db_name` = '" . $GLOBALS['dbi']->escapeString($dbname) . "' " . + " AND `table_name` = '" . $GLOBALS['dbi']->escapeString($tablename) . "' "; + + if ($statement != "") { + $sql_query .= " AND FIND_IN_SET('" + . $statement . "',tracking) > 0" ; + } + $row = $GLOBALS['dbi']->fetchArray($relation->queryAsControlUser($sql_query)); + return isset($row[0]) + ? $row[0] + : -1; + } + + + /** + * Gets the record of a tracking job. + * + * @param string $dbname name of database + * @param string $tablename name of table + * @param string $version version number + * + * @static + * + * @return mixed record DDM log, DDL log, structure snapshot, tracked + * statements. + */ + public static function getTrackedData($dbname, $tablename, $version) + { + $relation = new Relation(); + + $sql_query = " SELECT * FROM " . self::_getTrackingTable() . + " WHERE `db_name` = '" . $GLOBALS['dbi']->escapeString($dbname) . "' "; + if (! empty($tablename)) { + $sql_query .= " AND `table_name` = '" + . $GLOBALS['dbi']->escapeString($tablename) . "' "; + } + $sql_query .= " AND `version` = '" . $GLOBALS['dbi']->escapeString($version) + . "' " . " ORDER BY `version` DESC LIMIT 1"; + + $mixed = $GLOBALS['dbi']->fetchAssoc($relation->queryAsControlUser($sql_query)); + + // PHP 7.4 fix for accessing array offset on null + if (!is_array($mixed)) { + $mixed = array( + 'schema_sql' => null, + 'data_sql' => null, + 'tracking' => null, + 'schema_snapshot' => null, + ); + } + + // Parse log + $log_schema_entries = explode('# log ', $mixed['schema_sql']); + $log_data_entries = explode('# log ', $mixed['data_sql']); + + $ddl_date_from = $date = Util::date('Y-m-d H:i:s'); + + $ddlog = array(); + $first_iteration = true; + + // Iterate tracked data definition statements + // For each log entry we want to get date, username and statement + foreach ($log_schema_entries as $log_entry) { + if (trim($log_entry) != '') { + $date = mb_substr($log_entry, 0, 19); + $username = mb_substr( + $log_entry, 20, mb_strpos($log_entry, "\n") - 20 + ); + if ($first_iteration) { + $ddl_date_from = $date; + $first_iteration = false; + } + $statement = rtrim(mb_strstr($log_entry, "\n")); + + $ddlog[] = array( 'date' => $date, + 'username'=> $username, + 'statement' => $statement ); + } + } + + $date_from = $ddl_date_from; + $ddl_date_to = $date; + + $dml_date_from = $date_from; + + $dmlog = array(); + $first_iteration = true; + + // Iterate tracked data manipulation statements + // For each log entry we want to get date, username and statement + foreach ($log_data_entries as $log_entry) { + if (trim($log_entry) != '') { + $date = mb_substr($log_entry, 0, 19); + $username = mb_substr( + $log_entry, 20, mb_strpos($log_entry, "\n") - 20 + ); + if ($first_iteration) { + $dml_date_from = $date; + $first_iteration = false; + } + $statement = rtrim(mb_strstr($log_entry, "\n")); + + $dmlog[] = array( 'date' => $date, + 'username' => $username, + 'statement' => $statement ); + } + } + + $dml_date_to = $date; + + // Define begin and end of date range for both logs + $data = array(); + if (strtotime($ddl_date_from) <= strtotime($dml_date_from)) { + $data['date_from'] = $ddl_date_from; + } else { + $data['date_from'] = $dml_date_from; + } + if (strtotime($ddl_date_to) >= strtotime($dml_date_to)) { + $data['date_to'] = $ddl_date_to; + } else { + $data['date_to'] = $dml_date_to; + } + $data['ddlog'] = $ddlog; + $data['dmlog'] = $dmlog; + $data['tracking'] = $mixed['tracking']; + $data['schema_snapshot'] = $mixed['schema_snapshot']; + + return $data; + } + + + /** + * Parses a query. Gets + * - statement identifier (UPDATE, ALTER TABLE, ...) + * - type of statement, is it part of DDL or DML ? + * - tablename + * + * @param string $query query + * + * @static + * @todo: using PMA SQL Parser when possible + * @todo: support multi-table/view drops + * + * @return mixed Array containing identifier, type and tablename. + * + */ + public static function parseQuery($query) + { + // Usage of PMA_SQP does not work here + // + // require_once("libraries/sqlparser.lib.php"); + // $parsed_sql = PMA_SQP_parse($query); + // $sql_info = PMA_SQP_analyze($parsed_sql); + + $parser = new Parser($query); + + $tokens = $parser->list->tokens; + + // Parse USE statement, need it for SQL dump imports + if ($tokens[0]->value == 'USE') { + $GLOBALS['db'] = $tokens[2]->value; + } + + $result = array(); + + if (!empty($parser->statements)) { + $statement = $parser->statements[0]; + $options = isset($statement->options) ? $statement->options->options : null; + + /* + * DDL statements + */ + $result['type'] = 'DDL'; + + // Parse CREATE statement + if ($statement instanceof CreateStatement) { + if (empty($options) || !isset($options[6])) { + return $result; + } + + if ($options[6] == 'VIEW' || $options[6] == 'TABLE') { + $result['identifier'] = 'CREATE ' . $options[6]; + $result['tablename'] = $statement->name->table ; + } elseif ($options[6] == 'DATABASE') { + $result['identifier'] = 'CREATE DATABASE' ; + $result['tablename'] = '' ; + + // In case of CREATE DATABASE, table field of the CreateStatement is actually name of the database + $GLOBALS['db'] = $statement->name->table; + } elseif ($options[6] == 'INDEX' + || $options[6] == 'UNIQUE INDEX' + || $options[6] == 'FULLTEXT INDEX' + || $options[6] == 'SPATIAL INDEX' + ){ + $result['identifier'] = 'CREATE INDEX'; + + // In case of CREATE INDEX, we have to get the table name from body of the statement + $result['tablename'] = $statement->body[3]->value == '.' ? $statement->body[4]->value + : $statement->body[2]->value ; + } + } + + // Parse ALTER statement + elseif ($statement instanceof AlterStatement) { + if (empty($options) || !isset($options[3])) { + return $result; + } + + if ($options[3] == 'VIEW' || $options[3] == 'TABLE') { + $result['identifier'] = 'ALTER ' . $options[3] ; + $result['tablename'] = $statement->table->table ; + } elseif ($options[3] == 'DATABASE') { + $result['identifier'] = 'ALTER DATABASE' ; + $result['tablename'] = '' ; + + $GLOBALS['db'] = $statement->table->table ; + } + } + + // Parse DROP statement + elseif ($statement instanceof DropStatement) { + if (empty($options) || !isset($options[1])) { + return $result; + } + + if ($options[1] == 'VIEW' || $options[1] == 'TABLE') { + $result['identifier'] = 'DROP ' . $options[1] ; + $result['tablename'] = $statement->fields[0]->table; + } elseif ($options[1] == 'DATABASE') { + $result['identifier'] = 'DROP DATABASE' ; + $result['tablename'] = ''; + + $GLOBALS['db'] = $statement->fields[0]->table; + } elseif ($options[1] == 'INDEX') { + $result['identifier'] = 'DROP INDEX' ; + $result['tablename'] = $statement->table->table; + } + } + + // Prase RENAME statement + elseif ($statement instanceof RenameStatement) { + $result['identifier'] = 'RENAME TABLE'; + $result['tablename'] = $statement->renames[0]->old->table; + $result['tablename_after_rename'] = $statement->renames[0]->new->table; + } + + if (isset($result['identifier'])) { + return $result ; + } + + /* + * DML statements + */ + $result['type'] = 'DML'; + + // Parse UPDATE statement + if ($statement instanceof UpdateStatement) { + $result['identifier'] = 'UPDATE'; + $result['tablename'] = $statement->tables[0]->table; + } + + // Parse INSERT INTO statement + if ($statement instanceof InsertStatement) { + $result['identifier'] = 'INSERT'; + $result['tablename'] = $statement->into->dest->table; + } + + // Parse DELETE statement + if ($statement instanceof DeleteStatement) { + $result['identifier'] = 'DELETE'; + $result['tablename'] = $statement->from[0]->table; + } + + // Parse TRUNCATE statement + if ($statement instanceof TruncateStatement) { + $result['identifier'] = 'TRUNCATE' ; + $result['tablename'] = $statement->table->table; + } + } + + return $result; + } + + + /** + * Analyzes a given SQL statement and saves tracking data. + * + * @param string $query a SQL query + * + * @static + * + * @return void + */ + public static function handleQuery($query) + { + $relation = new Relation(); + + // If query is marked as untouchable, leave + if (mb_strstr($query, "/*NOTRACK*/")) { + return; + } + + if (! (substr($query, -1) == ';')) { + $query = $query . ";\n"; + } + // Get some information about query + $result = self::parseQuery($query); + + // Get database name + $dbname = trim(isset($GLOBALS['db']) ? $GLOBALS['db'] : '', '`'); + // $dbname can be empty, for example when coming from Synchronize + // and this is a query for the remote server + if (empty($dbname)) { + return; + } + + // If we found a valid statement + if (isset($result['identifier'])) { + if (! self::isTracked($dbname, $result['tablename'])) { + return; + } + + $version = self::getVersion( + $dbname, $result['tablename'], $result['identifier'] + ); + + // If version not exists and auto-creation is enabled + if ($GLOBALS['cfg']['Server']['tracking_version_auto_create'] == true + && $version == -1 + ) { + // Create the version + + switch ($result['identifier']) { + case 'CREATE TABLE': + self::createVersion($dbname, $result['tablename'], '1'); + break; + case 'CREATE VIEW': + self::createVersion( + $dbname, $result['tablename'], '1', '', true + ); + break; + case 'CREATE DATABASE': + self::createDatabaseVersion($dbname, '1', $query); + break; + } // end switch + } + + // If version exists + if ($version != -1) { + if ($result['type'] == 'DDL') { + $save_to = 'schema_sql'; + } elseif ($result['type'] == 'DML') { + $save_to = 'data_sql'; + } else { + $save_to = ''; + } + $date = Util::date('Y-m-d H:i:s'); + + // Cut off `dbname`. from query + $query = preg_replace( + '/`' . preg_quote($dbname, '/') . '`\s?\./', + '', + $query + ); + + // Add log information + $query = self::getLogComment() . $query ; + + // Mark it as untouchable + $sql_query = " /*NOTRACK*/\n" + . " UPDATE " . self::_getTrackingTable() + . " SET " . Util::backquote($save_to) + . " = CONCAT( " . Util::backquote($save_to) . ",'\n" + . $GLOBALS['dbi']->escapeString($query) . "') ," + . " `date_updated` = '" . $date . "' "; + + // If table was renamed we have to change + // the tablename attribute in pma_tracking too + if ($result['identifier'] == 'RENAME TABLE') { + $sql_query .= ', `table_name` = \'' + . $GLOBALS['dbi']->escapeString($result['tablename_after_rename']) + . '\' '; + } + + // Save the tracking information only for + // 1. the database + // 2. the table / view + // 3. the statements + // we want to track + $sql_query .= + " WHERE FIND_IN_SET('" . $result['identifier'] . "',tracking) > 0" . + " AND `db_name` = '" . $GLOBALS['dbi']->escapeString($dbname) . "' " . + " AND `table_name` = '" + . $GLOBALS['dbi']->escapeString($result['tablename']) . "' " . + " AND `version` = '" . $GLOBALS['dbi']->escapeString($version) . "' "; + + $relation->queryAsControlUser($sql_query); + } + } + } + + /** + * Returns the tracking table + * + * @return string tracking table + */ + private static function _getTrackingTable() + { + $relation = new Relation(); + $cfgRelation = $relation->getRelationsParam(); + return Util::backquote($cfgRelation['db']) + . '.' . Util::backquote($cfgRelation['tracking']); + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Tracking.php b/php/apps/phpmyadmin49/libraries/classes/Tracking.php new file mode 100644 index 00000000..82c8b25a --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Tracking.php @@ -0,0 +1,1274 @@ += $filter_ts_from + && $timestamp <= $filter_ts_to + && (in_array('*', $filter_users) || $filtered_user) + ) { + $tmp_entries[] = array( + 'id' => $id, + 'timestamp' => $timestamp, + 'username' => $entry['username'], + 'statement' => $entry['statement'] + ); + } + $id++; + } + return($tmp_entries); + } + + /** + * Function to get html for data definition and data manipulation statements + * + * @param string $urlQuery url query + * @param int $lastVersion last version + * @param string $db database + * @param array $selected selected tables + * @param string $type type of the table; table, view or both + * + * @return string HTML + */ + public static function getHtmlForDataDefinitionAndManipulationStatements( + $urlQuery, + $lastVersion, + $db, + array $selected, + $type = 'both' + ) { + return Template::get('table/tracking/create_version')->render([ + 'url_query' => $urlQuery, + 'last_version' => $lastVersion, + 'db' => $db, + 'selected' => $selected, + 'type' => $type, + 'default_statements' => $GLOBALS['cfg']['Server']['tracking_default_statements'], + ]); + } + + /** + * Function to get html for activate/deactivate tracking + * + * @param string $action activate|deactivate + * @param string $urlQuery url query + * @param int $lastVersion last version + * + * @return string HTML + */ + public static function getHtmlForActivateDeactivateTracking( + $action, + $urlQuery, + $lastVersion + ) { + return Template::get('table/tracking/activate_deactivate')->render([ + 'action' => $action, + 'url_query' => $urlQuery, + 'last_version' => $lastVersion, + 'db' => $GLOBALS['db'], + 'table' => $GLOBALS['table'], + ]); + } + + /** + * Function to get the list versions of the table + * + * @return array + */ + public static function getListOfVersionsOfTable() + { + $relation = new Relation(); + $cfgRelation = $relation->getRelationsParam(); + $sql_query = " SELECT * FROM " . + Util::backquote($cfgRelation['db']) . "." . + Util::backquote($cfgRelation['tracking']) . + " WHERE db_name = '" . $GLOBALS['dbi']->escapeString($GLOBALS['db']) . + "' " . + " AND table_name = '" . + $GLOBALS['dbi']->escapeString($GLOBALS['table']) . "' " . + " ORDER BY version DESC "; + + return $relation->queryAsControlUser($sql_query); + } + + /** + * Function to get html for displaying last version number + * + * @param array $sql_result sql result + * @param int $last_version last version + * @param array $url_params url parameters + * @param string $url_query url query + * @param string $pmaThemeImage path to theme's image folder + * @param string $text_dir text direction + * + * @return string + */ + public static function getHtmlForTableVersionDetails( + $sql_result, $last_version, array $url_params, + $url_query, $pmaThemeImage, $text_dir + ) { + $tracking_active = false; + + $html = '
      '; + $html .= Url::getHiddenInputs($GLOBALS['db'], $GLOBALS['table']); + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + + $GLOBALS['dbi']->dataSeek($sql_result, 0); + $delete = Util::getIcon('b_drop', __('Delete version')); + $report = Util::getIcon('b_report', __('Tracking report')); + $structure = Util::getIcon('b_props', __('Structure snapshot')); + + while ($version = $GLOBALS['dbi']->fetchArray($sql_result)) { + if ($version['version'] == $last_version) { + if ($version['tracking_active'] == 1) { + $tracking_active = true; + } else { + $tracking_active = false; + } + } + $checkbox_id = 'selected_versions_' . htmlspecialchars($version['version']); + + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + } + + $html .= ''; + $html .= '
      ' . __('Version') . '' . __('Created') . '' . __('Updated') . '' . __('Status') . '' . __('Action') . '' . __('Show') . '
      '; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= '' . htmlspecialchars($version['date_created']) . '' . htmlspecialchars($version['date_updated']) . '' . self::getVersionStatus($version) . '' . $delete . '' . $report . ''; + $html .= '  '; + $html .= '' . $structure . ''; + $html .= '
      '; + + $html .= Template::get('select_all') + ->render( + array( + 'pma_theme_image' => $pmaThemeImage, + 'text_dir' => $text_dir, + 'form_name' => 'versionsForm', + ) + ); + $html .= Util::getButtonOrImage( + 'submit_mult', 'mult_submit', + __('Delete version'), 'b_drop', 'delete_version' + ); + + $html .= '
      '; + + if ($tracking_active) { + $html .= self::getHtmlForActivateDeactivateTracking( + 'deactivate', $url_query, $last_version + ); + } else { + $html .= self::getHtmlForActivateDeactivateTracking( + 'activate', $url_query, $last_version + ); + } + + return $html; + } + + /** + * Function to get the last version number of a table + * + * @param array $sql_result sql result + * + * @return int + */ + public static function getTableLastVersionNumber($sql_result) + { + $maxversion = $GLOBALS['dbi']->fetchArray($sql_result); + return intval(is_array($maxversion) ? $maxversion['version'] : null); + } + + /** + * Function to get sql results for selectable tables + * + * @return array + */ + public static function getSqlResultForSelectableTables() + { + $relation = new Relation(); + $cfgRelation = $relation->getRelationsParam(); + + $sql_query = " SELECT DISTINCT db_name, table_name FROM " . + Util::backquote($cfgRelation['db']) . "." . + Util::backquote($cfgRelation['tracking']) . + " WHERE db_name = '" . $GLOBALS['dbi']->escapeString($GLOBALS['db']) . + "' " . + " ORDER BY db_name, table_name"; + + return $relation->queryAsControlUser($sql_query); + } + + /** + * Function to get html for selectable table rows + * + * @param array $selectableTablesSqlResult sql results for selectable rows + * @param string $urlQuery url query + * + * @return string + */ + public static function getHtmlForSelectableTables( + $selectableTablesSqlResult, + $urlQuery + ) { + $entries = []; + while ($entry = $GLOBALS['dbi']->fetchArray($selectableTablesSqlResult)) { + $entry['is_tracked'] = Tracker::isTracked( + $entry['db_name'], + $entry['table_name'] + ); + $entries[] = $entry; + } + + return Template::get('table/tracking/selectable_tables')->render([ + 'url_query' => $urlQuery, + 'db' => $GLOBALS['db'], + 'table' => $GLOBALS['table'], + 'entries' => $entries, + 'selected_table' => isset($_POST['table']) ? $_POST['table'] : null, + ]); + } + + /** + * Function to get html for tracking report and tracking report export + * + * @param string $url_query url query + * @param array $data data + * @param array $url_params url params + * @param boolean $selection_schema selection schema + * @param boolean $selection_data selection data + * @param boolean $selection_both selection both + * @param int $filter_ts_to filter time stamp from + * @param int $filter_ts_from filter time stamp tp + * @param array $filter_users filter users + * + * @return string + */ + public static function getHtmlForTrackingReport($url_query, array $data, array $url_params, + $selection_schema, $selection_data, $selection_both, $filter_ts_to, + $filter_ts_from, array $filter_users + ) { + $html = '

      ' . __('Tracking report') + . ' [' . __('Close') + . ']

      '; + + $html .= '' . __('Tracking statements') . ' ' + . htmlspecialchars($data['tracking']) . '
      '; + $html .= '
      '; + + list($str1, $str2, $str3, $str4, $str5) = self::getHtmlForElementsOfTrackingReport( + $selection_schema, $selection_data, $selection_both + ); + + // Prepare delete link content here + $drop_image_or_text = ''; + if (Util::showIcons('ActionLinksMode')) { + $drop_image_or_text .= Util::getImage( + 'b_drop', __('Delete tracking data row from report') + ); + } + if (Util::showText('ActionLinksMode')) { + $drop_image_or_text .= __('Delete'); + } + + /* + * First, list tracked data definition statements + */ + if (count($data['ddlog']) == 0 && count($data['dmlog']) == 0) { + $msg = Message::notice(__('No data')); + $msg->display(); + } + + $html .= self::getHtmlForTrackingReportExportForm1( + $data, $url_params, $selection_schema, $selection_data, $selection_both, + $filter_ts_to, $filter_ts_from, $filter_users, $str1, $str2, $str3, + $str4, $str5, $drop_image_or_text + ); + + $html .= self::getHtmlForTrackingReportExportForm2( + $url_params, $str1, $str2, $str3, $str4, $str5 + ); + + $html .= "



      \n"; + + return $html; + } + + /** + * Generate HTML element for report form + * + * @param boolean $selection_schema selection schema + * @param boolean $selection_data selection data + * @param boolean $selection_both selection both + * + * @return array + */ + public static function getHtmlForElementsOfTrackingReport( + $selection_schema, $selection_data, $selection_both + ) { + $str1 = ''; + $str2 = ''; + $str3 = ''; + $str4 = ''; + $str5 = '' + . ''; + return array($str1, $str2, $str3, $str4, $str5); + } + + /** + * Generate HTML for export form + * + * @param array $data data + * @param array $url_params url params + * @param boolean $selection_schema selection schema + * @param boolean $selection_data selection data + * @param boolean $selection_both selection both + * @param int $filter_ts_to filter time stamp from + * @param int $filter_ts_from filter time stamp tp + * @param array $filter_users filter users + * @param string $str1 HTML for logtype select + * @param string $str2 HTML for "from date" + * @param string $str3 HTML for "to date" + * @param string $str4 HTML for user + * @param string $str5 HTML for "list report" + * @param string $drop_image_or_text HTML for image or text + * + * @return string HTML for form + */ + public static function getHtmlForTrackingReportExportForm1( + array $data, array $url_params, $selection_schema, $selection_data, $selection_both, + $filter_ts_to, $filter_ts_from, array $filter_users, $str1, $str2, $str3, + $str4, $str5, $drop_image_or_text + ) { + $ddlog_count = 0; + + $html = '
      '; + $html .= Url::getHiddenInputs($url_params + [ + 'report' => 'true', + 'version' => $_POST['version'], + ]); + + $html .= sprintf( + __('Show %1$s with dates from %2$s to %3$s by user %4$s %5$s'), + $str1, $str2, $str3, $str4, $str5 + ); + + if ($selection_schema || $selection_both && count($data['ddlog']) > 0) { + list($temp, $ddlog_count) = self::getHtmlForDataDefinitionStatements( + $data, $filter_users, $filter_ts_from, $filter_ts_to, $url_params, + $drop_image_or_text + ); + $html .= $temp; + unset($temp); + } //endif + + /* + * Secondly, list tracked data manipulation statements + */ + if (($selection_data || $selection_both) && count($data['dmlog']) > 0) { + $html .= self::getHtmlForDataManipulationStatements( + $data, $filter_users, $filter_ts_from, $filter_ts_to, $url_params, + $ddlog_count, $drop_image_or_text + ); + } + $html .= '
      '; + return $html; + } + + /** + * Generate HTML for export form + * + * @param array $url_params Parameters + * @param string $str1 HTML for logtype select + * @param string $str2 HTML for "from date" + * @param string $str3 HTML for "to date" + * @param string $str4 HTML for user + * @param string $str5 HTML for "list report" + * + * @return string HTML for form + */ + public static function getHtmlForTrackingReportExportForm2( + array $url_params, $str1, $str2, $str3, $str4, $str5 + ) { + $html = '
      '; + $html .= Url::getHiddenInputs($url_params + [ + 'report' => 'true', + 'version' => $_POST['version'], + ]); + + $html .= sprintf( + __('Show %1$s with dates from %2$s to %3$s by user %4$s %5$s'), + $str1, $str2, $str3, $str4, $str5 + ); + $html .= '
      '; + + $html .= '
      '; + $html .= Url::getHiddenInputs($url_params + [ + 'report' => 'true', + 'version' => $_POST['version'], + 'logtype' => $_POST['logtype'], + 'date_from' => $_POST['date_from'], + 'date_to' => $_POST['date_to'], + 'users' => $_POST['users'], + 'report_export' => 'true', + ]); + + $str_export1 = ''; + + $str_export2 = ''; + + $html .= "
      " . sprintf(__('Export as %s'), $str_export1) + . $str_export2 . "
      "; + $html .= '
      '; + return $html; + } + + /** + * Function to get html for data manipulation statements + * + * @param array $data data + * @param array $filter_users filter users + * @param int $filter_ts_from filter time staml from + * @param int $filter_ts_to filter time stamp to + * @param array $url_params url parameters + * @param int $ddlog_count data definition log count + * @param string $drop_image_or_text drop image or text + * + * @return string + */ + public static function getHtmlForDataManipulationStatements(array $data, array $filter_users, + $filter_ts_from, $filter_ts_to, array $url_params, $ddlog_count, + $drop_image_or_text + ) { + // no need for the secondth returned parameter + list($html,) = self::getHtmlForDataStatements( + $data, $filter_users, $filter_ts_from, $filter_ts_to, $url_params, + $drop_image_or_text, 'dmlog', __('Data manipulation statement'), + $ddlog_count, 'dml_versions' + ); + + return $html; + } + + /** + * Function to get html for data definition statements in schema snapshot + * + * @param array $data data + * @param array $filter_users filter users + * @param int $filter_ts_from filter time stamp from + * @param int $filter_ts_to filter time stamp to + * @param array $url_params url parameters + * @param string $drop_image_or_text drop image or text + * + * @return array + */ + public static function getHtmlForDataDefinitionStatements(array $data, array $filter_users, + $filter_ts_from, $filter_ts_to, array $url_params, $drop_image_or_text + ) { + list($html, $line_number) = self::getHtmlForDataStatements( + $data, $filter_users, $filter_ts_from, $filter_ts_to, $url_params, + $drop_image_or_text, 'ddlog', __('Data definition statement'), + 1, 'ddl_versions' + ); + + return array($html, $line_number); + } + + /** + * Function to get html for data statements in schema snapshot + * + * @param array $data data + * @param array $filterUsers filter users + * @param int $filterTsFrom filter time stamp from + * @param int $filterTsTo filter time stamp to + * @param array $urlParams url parameters + * @param string $dropImageOrText drop image or text + * @param string $whichLog dmlog|ddlog + * @param string $headerMessage message for this section + * @param int $lineNumber line number + * @param string $tableId id for the table element + * + * @return array [$html, $lineNumber] + */ + private static function getHtmlForDataStatements( + array $data, + array $filterUsers, + $filterTsFrom, + $filterTsTo, + array $urlParams, + $dropImageOrText, + $whichLog, + $headerMessage, + $lineNumber, + $tableId + ) { + $offset = $lineNumber; + $entries = []; + foreach ($data[$whichLog] as $entry) { + $timestamp = strtotime($entry['date']); + if ($timestamp >= $filterTsFrom + && $timestamp <= $filterTsTo + && (in_array('*', $filterUsers) + || in_array($entry['username'], $filterUsers)) + ) { + $entry['formated_statement'] = Util::formatSql($entry['statement'], true); + $deleteParam = 'delete_' . $whichLog; + $entry['url_params'] = Url::getCommon($urlParams + [ + 'report' => 'true', + 'version' => $_POST['version'], + $deleteParam => ($lineNumber - $offset), + ], ''); + $entry['line_number'] = $lineNumber; + $entries[] = $entry; + } + $lineNumber++; + } + + $html = Template::get('table/tracking/report_table')->render([ + 'table_id' => $tableId, + 'header_message' => $headerMessage, + 'entries' => $entries, + 'drop_image_or_text' => $dropImageOrText, + ]); + + return [$html, $lineNumber]; + } + + /** + * Function to get html for schema snapshot + * + * @param string $url_query url query + * + * @return string + */ + public static function getHtmlForSchemaSnapshot($url_query) + { + $html = '

      ' . __('Structure snapshot') + . ' [' . __('Close') + . ']

      '; + $data = Tracker::getTrackedData( + $_POST['db'], $_POST['table'], $_POST['version'] + ); + + // Get first DROP TABLE/VIEW and CREATE TABLE/VIEW statements + $drop_create_statements = $data['ddlog'][0]['statement']; + + if (mb_strstr($data['ddlog'][0]['statement'], 'DROP TABLE') + || mb_strstr($data['ddlog'][0]['statement'], 'DROP VIEW') + ) { + $drop_create_statements .= $data['ddlog'][1]['statement']; + } + // Print SQL code + $html .= Util::getMessage( + sprintf( + __('Version %s snapshot (SQL code)'), + htmlspecialchars($_POST['version']) + ), + $drop_create_statements + ); + + // Unserialize snapshot + $temp = Core::safeUnserialize($data['schema_snapshot']); + if ($temp === null) { + $temp = array('COLUMNS' => array(), 'INDEXES' => array()); + } + $columns = $temp['COLUMNS']; + $indexes = $temp['INDEXES']; + $html .= self::getHtmlForColumns($columns); + + if (count($indexes) > 0) { + $html .= self::getHtmlForIndexes($indexes); + } // endif + $html .= '


      '; + + return $html; + } + + /** + * Function to get html for displaying columns in the schema snapshot + * + * @param array $columns columns + * + * @return string + */ + public static function getHtmlForColumns(array $columns) + { + return Template::get('table/tracking/structure_snapshot_columns')->render([ + 'columns' => $columns, + ]); + } + + /** + * Function to get html for the indexes in schema snapshot + * + * @param array $indexes indexes + * + * @return string + */ + public static function getHtmlForIndexes(array $indexes) + { + return Template::get('table/tracking/structure_snapshot_indexes')->render([ + 'indexes' => $indexes, + ]);; + } + + /** + * Function to handle the tracking report + * + * @param array &$data tracked data + * + * @return string HTML for the message + */ + public static function deleteTrackingReportRows(array &$data) + { + $html = ''; + if (isset($_POST['delete_ddlog'])) { + // Delete ddlog row data + $html .= self::deleteFromTrackingReportLog( + $data, + 'ddlog', + 'DDL', + __('Tracking data definition successfully deleted') + ); + } + + if (isset($_POST['delete_dmlog'])) { + // Delete dmlog row data + $html .= self::deleteFromTrackingReportLog( + $data, + 'dmlog', + 'DML', + __('Tracking data manipulation successfully deleted') + ); + } + return $html; + } + + /** + * Function to delete from a tracking report log + * + * @param array &$data tracked data + * @param string $which_log ddlog|dmlog + * @param string $type DDL|DML + * @param string $message success message + * + * @return string HTML for the message + */ + public static function deleteFromTrackingReportLog(array &$data, $which_log, $type, $message) + { + $html = ''; + $delete_id = $_POST['delete_' . $which_log]; + + // Only in case of valid id + if ($delete_id == (int)$delete_id) { + unset($data[$which_log][$delete_id]); + + $successfullyDeleted = Tracker::changeTrackingData( + $GLOBALS['db'], + $GLOBALS['table'], + $_POST['version'], + $type, + $data[$which_log] + ); + if ($successfullyDeleted) { + $msg = Message::success($message); + } else { + $msg = Message::rawError(__('Query error')); + } + $html .= $msg->getDisplay(); + } + return $html; + } + + /** + * Function to export as sql dump + * + * @param array $entries entries + * + * @return string HTML SQL query form + */ + public static function exportAsSqlDump(array $entries) + { + $html = ''; + $new_query = "# " + . __( + 'You can execute the dump by creating and using a temporary database. ' + . 'Please ensure that you have the privileges to do so.' + ) + . "\n" + . "# " . __('Comment out these two lines if you do not need them.') . "\n" + . "\n" + . "CREATE database IF NOT EXISTS pma_temp_db; \n" + . "USE pma_temp_db; \n" + . "\n"; + + foreach ($entries as $entry) { + $new_query .= $entry['statement']; + } + $msg = Message::success( + __('SQL statements exported. Please copy the dump or execute it.') + ); + $html .= $msg->getDisplay(); + + $db_temp = $GLOBALS['db']; + $table_temp = $GLOBALS['table']; + + $GLOBALS['db'] = $GLOBALS['table'] = ''; + + $html .= SqlQueryForm::getHtml($new_query, 'sql'); + + $GLOBALS['db'] = $db_temp; + $GLOBALS['table'] = $table_temp; + + return $html; + } + + /** + * Function to export as sql execution + * + * @param array $entries entries + * + * @return array + */ + public static function exportAsSqlExecution(array $entries) + { + $sql_result = array(); + foreach ($entries as $entry) { + $sql_result = $GLOBALS['dbi']->query("/*NOTRACK*/\n" . $entry['statement']); + } + + return $sql_result; + } + + /** + * Function to export as entries + * + * @param array $entries entries + * + * @return void + */ + public static function exportAsFileDownload(array $entries) + { + ini_set('url_rewriter.tags', ''); + + // Replace all multiple whitespaces by a single space + $table = htmlspecialchars(preg_replace('/\s+/', ' ', $_POST['table'])); + $dump = "# " . sprintf( + __('Tracking report for table `%s`'), $table + ) + . "\n" . "# " . date('Y-m-d H:i:s') . "\n"; + foreach ($entries as $entry) { + $dump .= $entry['statement']; + } + $filename = 'log_' . $table . '.sql'; + Response::getInstance()->disable(); + Core::downloadHeader( + $filename, + 'text/x-sql', + strlen($dump) + ); + echo $dump; + + exit(); + } + + /** + * Function to activate or deactivate tracking + * + * @param string $action activate|deactivate + * + * @return string HTML for the success message + */ + public static function changeTracking($action) + { + $html = ''; + if ($action == 'activate') { + $method = 'activateTracking'; + $message = __('Tracking for %1$s was activated at version %2$s.'); + } else { + $method = 'deactivateTracking'; + $message = __('Tracking for %1$s was deactivated at version %2$s.'); + } + $status = Tracker::$method( + $GLOBALS['db'], $GLOBALS['table'], $_POST['version'] + ); + if ($status) { + $msg = Message::success( + sprintf( + $message, + htmlspecialchars($GLOBALS['db'] . '.' . $GLOBALS['table']), + htmlspecialchars($_POST['version']) + ) + ); + $html .= $msg->getDisplay(); + } + + return $html; + } + + /** + * Function to get tracking set + * + * @return string + */ + public static function getTrackingSet() + { + $tracking_set = ''; + + // a key is absent from the request if it has been removed from + // tracking_default_statements in the config + if (isset($_POST['alter_table']) && $_POST['alter_table'] == true) { + $tracking_set .= 'ALTER TABLE,'; + } + if (isset($_POST['rename_table']) && $_POST['rename_table'] == true) { + $tracking_set .= 'RENAME TABLE,'; + } + if (isset($_POST['create_table']) && $_POST['create_table'] == true) { + $tracking_set .= 'CREATE TABLE,'; + } + if (isset($_POST['drop_table']) && $_POST['drop_table'] == true) { + $tracking_set .= 'DROP TABLE,'; + } + if (isset($_POST['alter_view']) && $_POST['alter_view'] == true) { + $tracking_set .= 'ALTER VIEW,'; + } + if (isset($_POST['create_view']) && $_POST['create_view'] == true) { + $tracking_set .= 'CREATE VIEW,'; + } + if (isset($_POST['drop_view']) && $_POST['drop_view'] == true) { + $tracking_set .= 'DROP VIEW,'; + } + if (isset($_POST['create_index']) && $_POST['create_index'] == true) { + $tracking_set .= 'CREATE INDEX,'; + } + if (isset($_POST['drop_index']) && $_POST['drop_index'] == true) { + $tracking_set .= 'DROP INDEX,'; + } + if (isset($_POST['insert']) && $_POST['insert'] == true) { + $tracking_set .= 'INSERT,'; + } + if (isset($_POST['update']) && $_POST['update'] == true) { + $tracking_set .= 'UPDATE,'; + } + if (isset($_POST['delete']) && $_POST['delete'] == true) { + $tracking_set .= 'DELETE,'; + } + if (isset($_POST['truncate']) && $_POST['truncate'] == true) { + $tracking_set .= 'TRUNCATE,'; + } + $tracking_set = rtrim($tracking_set, ','); + + return $tracking_set; + } + + /** + * Deletes a tracking version + * + * @param string $version tracking version + * + * @return string HTML of the success message + */ + public static function deleteTrackingVersion($version) + { + $html = ''; + $versionDeleted = Tracker::deleteTracking( + $GLOBALS['db'], + $GLOBALS['table'], + $version + ); + if ($versionDeleted) { + $msg = Message::success( + sprintf( + __('Version %1$s of %2$s was deleted.'), + htmlspecialchars($version), + htmlspecialchars($GLOBALS['db'] . '.' . $GLOBALS['table']) + ) + ); + $html .= $msg->getDisplay(); + } + + return $html; + } + + /** + * Function to create the tracking version + * + * @return string HTML of the success message + */ + public static function createTrackingVersion() + { + $html = ''; + $tracking_set = self::getTrackingSet(); + + $versionCreated = Tracker::createVersion( + $GLOBALS['db'], + $GLOBALS['table'], + $_POST['version'], + $tracking_set, + $GLOBALS['dbi']->getTable($GLOBALS['db'], $GLOBALS['table'])->isView() + ); + if ($versionCreated) { + $msg = Message::success( + sprintf( + __('Version %1$s was created, tracking for %2$s is active.'), + htmlspecialchars($_POST['version']), + htmlspecialchars($GLOBALS['db'] . '.' . $GLOBALS['table']) + ) + ); + $html .= $msg->getDisplay(); + } + + return $html; + } + + /** + * Create tracking version for multiple tables + * + * @param array $selected list of selected tables + * + * @return void + */ + public static function createTrackingForMultipleTables(array $selected) + { + $tracking_set = self::getTrackingSet(); + + foreach ($selected as $selected_table) { + Tracker::createVersion( + $GLOBALS['db'], + $selected_table, + $_POST['version'], + $tracking_set, + $GLOBALS['dbi']->getTable($GLOBALS['db'], $selected_table)->isView() + ); + } + } + + /** + * Function to get the entries + * + * @param array $data data + * @param int $filter_ts_from filter time stamp from + * @param int $filter_ts_to filter time stamp to + * @param array $filter_users filter users + * + * @return array + */ + public static function getEntries(array $data, $filter_ts_from, $filter_ts_to, array $filter_users) + { + $entries = array(); + // Filtering data definition statements + if ($_POST['logtype'] == 'schema' + || $_POST['logtype'] == 'schema_and_data' + ) { + $entries = array_merge( + $entries, + self::filterTracking( + $data['ddlog'], $filter_ts_from, $filter_ts_to, $filter_users + ) + ); + } + + // Filtering data manipulation statements + if ($_POST['logtype'] == 'data' + || $_POST['logtype'] == 'schema_and_data' + ) { + $entries = array_merge( + $entries, + self::filterTracking( + $data['dmlog'], $filter_ts_from, $filter_ts_to, $filter_users + ) + ); + } + + // Sort it + $ids = $timestamps = $usernames = $statements = array(); + foreach ($entries as $key => $row) { + $ids[$key] = $row['id']; + $timestamps[$key] = $row['timestamp']; + $usernames[$key] = $row['username']; + $statements[$key] = $row['statement']; + } + + array_multisort( + $timestamps, SORT_ASC, $ids, SORT_ASC, $usernames, + SORT_ASC, $statements, SORT_ASC, $entries + ); + + return $entries; + } + + /** + * Function to get version status + * + * @param array $version version info + * + * @return string $version_status The status message + */ + public static function getVersionStatus(array $version) + { + if ($version['tracking_active'] == 1) { + return __('active'); + } + + return __('not active'); + } + + /** + * Get HTML for untracked tables + * + * @param string $db current database + * @param array $untrackedTables untracked tables + * @param string $urlQuery url query string + * @param string $pmaThemeImage path to theme's image folder + * @param string $textDir text direction + * + * @return string HTML + */ + public static function getHtmlForUntrackedTables( + $db, + array $untrackedTables, + $urlQuery, + $pmaThemeImage, + $textDir + ) { + return Template::get('database/tracking/untracked_tables')->render([ + 'db' => $db, + 'untracked_tables' => $untrackedTables, + 'url_query' => $urlQuery, + 'pma_theme_image' => $pmaThemeImage, + 'text_dir' => $textDir, + ]); + } + + /** + * Helper function: Recursive function for getting table names from $table_list + * + * @param array $table_list Table list + * @param string $db Current database + * @param boolean $testing Testing + * + * @return array $untracked_tables + */ + public static function extractTableNames(array $table_list, $db, $testing = false) + { + $untracked_tables = array(); + $sep = $GLOBALS['cfg']['NavigationTreeTableSeparator']; + + foreach ($table_list as $key => $value) { + if (is_array($value) && array_key_exists(('is' . $sep . 'group'), $value) + && $value['is' . $sep . 'group'] + ) { + $untracked_tables = array_merge(self::extractTableNames($value, $db), $untracked_tables); //Recursion step + } + else { + if (is_array($value) && ($testing || Tracker::getVersion($db, $value['Name']) == -1)) { + $untracked_tables[] = $value['Name']; + } + } + } + return $untracked_tables; + } + + + /** + * Get untracked tables + * + * @param string $db current database + * + * @return array $untracked_tables + */ + public static function getUntrackedTables($db) + { + $table_list = Util::getTableList($db); + $untracked_tables = self::extractTableNames($table_list, $db); //Use helper function to get table list recursively. + return $untracked_tables; + } + + /** + * Get tracked tables + * + * @param string $db current database + * @param object $allTablesResult result set of tracked tables + * @param string $urlQuery url query string + * @param string $pmaThemeImage path to theme's image folder + * @param string $textDir text direction + * @param array $cfgRelation configuration storage info + * + * @return string HTML + */ + public static function getHtmlForTrackedTables( + $db, + $allTablesResult, + $urlQuery, + $pmaThemeImage, + $textDir, + array $cfgRelation + ) { + $relation = new Relation(); + $versions = []; + while ($oneResult = $GLOBALS['dbi']->fetchArray($allTablesResult)) { + list($tableName, $versionNumber) = $oneResult; + $tableQuery = ' SELECT * FROM ' . + Util::backquote($cfgRelation['db']) . '.' . + Util::backquote($cfgRelation['tracking']) . + ' WHERE `db_name` = \'' + . $GLOBALS['dbi']->escapeString($GLOBALS['db']) + . '\' AND `table_name` = \'' + . $GLOBALS['dbi']->escapeString($tableName) + . '\' AND `version` = \'' . $versionNumber . '\''; + + $tableResult = $relation->queryAsControlUser($tableQuery); + $versionData = $GLOBALS['dbi']->fetchArray($tableResult); + $versionData['status_button'] = self::getStatusButton( + $versionData, + $urlQuery + ); + $versions[] = $versionData; + } + return Template::get('database/tracking/tracked_tables')->render([ + 'db' => $db, + 'versions' => $versions, + 'text_dir' => $textDir, + 'pma_theme_image' => $pmaThemeImage, + ]); + } + + /** + * Get tracking status button + * + * @param array $versionData data about tracking versions + * @param string $urlQuery url query string + * + * @return string HTML + */ + private static function getStatusButton(array $versionData, $urlQuery) + { + $state = self::getVersionStatus($versionData); + $options = array( + 0 => array( + 'label' => __('not active'), + 'value' => 'deactivate_now', + 'selected' => ($state != 'active') + ), + 1 => array( + 'label' => __('active'), + 'value' => 'activate_now', + 'selected' => ($state == 'active') + ) + ); + $link = 'tbl_tracking.php' . $urlQuery . '&table=' + . htmlspecialchars($versionData['table_name']) + . '&version=' . $versionData['version']; + + return Util::toggleButton( + $link, + 'toggle_activation', + $options, + null + ); + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Transformations.php b/php/apps/phpmyadmin49/libraries/classes/Transformations.php new file mode 100644 index 00000000..9c527500 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Transformations.php @@ -0,0 +1,469 @@ + + * getOptions("'option ,, quoted',abd,'2,3',"); + * // array { + * // 'option ,, quoted', + * // 'abc', + * // '2,3', + * // '', + * // } + *
      + * + * @param string $option_string comma separated options + * + * @return array options + */ + public static function getOptions($option_string) + { + $result = array(); + + if (strlen($option_string) === 0 + || ! $transform_options = preg_split('/,/', $option_string) + ) { + return $result; + } + + while (($option = array_shift($transform_options)) !== null) { + $trimmed = trim($option); + if (strlen($trimmed) > 1 + && $trimmed[0] == "'" + && $trimmed[strlen($trimmed) - 1] == "'" + ) { + // '...' + $option = mb_substr($trimmed, 1, -1); + } elseif (isset($trimmed[0]) && $trimmed[0] == "'") { + // '..., + $trimmed = ltrim($option); + while (($option = array_shift($transform_options)) !== null) { + // ..., + $trimmed .= ',' . $option; + $rtrimmed = rtrim($trimmed); + if ($rtrimmed[strlen($rtrimmed) - 1] == "'") { + // ,...' + break; + } + } + $option = mb_substr($rtrimmed, 1, -1); + } + $result[] = stripslashes($option); + } + + return $result; + } + + /** + * Gets all available MIME-types + * + * @access public + * @staticvar array mimetypes + * @return array array[mimetype], array[transformation] + */ + public static function getAvailableMIMEtypes() + { + static $stack = null; + + if (null !== $stack) { + return $stack; + } + + $stack = array(); + $sub_dirs = array( + 'Input/' => 'input_', + 'Output/' => '', + '' => '' + ); + + foreach ($sub_dirs as $sd => $prefix) { + $handle = opendir('libraries/classes/Plugins/Transformations/' . $sd); + + if (! $handle) { + $stack[$prefix . 'transformation'] = array(); + $stack[$prefix . 'transformation_file'] = array(); + continue; + } + + $filestack = array(); + while ($file = readdir($handle)) { + // Ignore hidden files + if ($file[0] == '.') { + continue; + } + // Ignore old plugins (.class in filename) + if (strpos($file, '.class') !== false) { + continue; + } + $filestack[] = $file; + } + + closedir($handle); + sort($filestack); + + foreach ($filestack as $file) { + if (preg_match('|^[^.].*_.*_.*\.php$|', $file)) { + // File contains transformation functions. + $parts = explode('_', str_replace('.php', '', $file)); + $mimetype = $parts[0] . "/" . $parts[1]; + $stack['mimetype'][$mimetype] = $mimetype; + + $stack[$prefix . 'transformation'][] = $mimetype . ': ' . $parts[2]; + $stack[$prefix . 'transformation_file'][] = $sd . $file; + if ($sd === '') { + $stack['input_transformation'][] = $mimetype . ': ' . $parts[2]; + $stack['input_transformation_file'][] = $sd . $file; + } + + } elseif (preg_match('|^[^.].*\.php$|', $file)) { + // File is a plain mimetype, no functions. + $base = str_replace('.php', '', $file); + + if ($base != 'global') { + $mimetype = str_replace('_', '/', $base); + $stack['mimetype'][$mimetype] = $mimetype; + $stack['empty_mimetype'][$mimetype] = $mimetype; + } + } + } + } + return $stack; + } + + /** + * Returns the class name of the transformation + * + * @param string $filename transformation file name + * + * @return string the class name of transformation + */ + public static function getClassName($filename) + { + // get the transformation class name + $class_name = explode(".php", $filename); + $class_name = 'PhpMyAdmin\\' . str_replace('/', '\\', mb_substr($class_name[0], 18)); + + return $class_name; + } + + /** + * Returns the description of the transformation + * + * @param string $file transformation file + * + * @return string the description of the transformation + */ + public static function getDescription($file) + { + $include_file = 'libraries/classes/Plugins/Transformations/' . $file; + /* @var $class_name \PhpMyAdmin\Plugins\TransformationsInterface */ + $class_name = self::getClassName($include_file); + if (class_exists($class_name)) { + return $class_name::getInfo(); + } + return ''; + } + + /** + * Returns the name of the transformation + * + * @param string $file transformation file + * + * @return string the name of the transformation + */ + public static function getName($file) + { + $include_file = 'libraries/classes/Plugins/Transformations/' . $file; + /* @var $class_name \PhpMyAdmin\Plugins\TransformationsInterface */ + $class_name = self::getClassName($include_file); + if (class_exists($class_name)) { + return $class_name::getName(); + } + return ''; + } + + /** + * Fixups old MIME or transformation name to new one + * + * - applies some hardcoded fixups + * - adds spaces after _ and numbers + * - capitalizes words + * - removes back spaces + * + * @param string $value Value to fixup + * + * @return string + */ + static function fixupMIME($value) + { + $value = str_replace( + array("jpeg", "png"), array("JPEG", "PNG"), $value + ); + return str_replace( + ' ', + '', + ucwords( + preg_replace('/([0-9_]+)/', '$1 ', $value) + ) + ); + } + + /** + * Gets the mimetypes for all columns of a table + * + * @param string $db the name of the db to check for + * @param string $table the name of the table to check for + * @param boolean $strict whether to include only results having a mimetype set + * @param boolean $fullName whether to use full column names as the key + * + * @access public + * + * @return array [field_name][field_key] = field_value + */ + public static function getMIME($db, $table, $strict = false, $fullName = false) + { + $relation = new Relation(); + $cfgRelation = $relation->getRelationsParam(); + + if (! $cfgRelation['mimework']) { + return false; + } + + $com_qry = ''; + if ($fullName) { + $com_qry .= "SELECT CONCAT(" + . "`db_name`, '.', `table_name`, '.', `column_name`" + . ") AS column_name, "; + } else { + $com_qry = "SELECT `column_name`, "; + } + $com_qry .= '`mimetype`, + `transformation`, + `transformation_options`, + `input_transformation`, + `input_transformation_options` + FROM ' . Util::backquote($cfgRelation['db']) . '.' + . Util::backquote($cfgRelation['column_info']) . ' + WHERE `db_name` = \'' . $GLOBALS['dbi']->escapeString($db) . '\' + AND `table_name` = \'' . $GLOBALS['dbi']->escapeString($table) . '\' + AND ( `mimetype` != \'\'' . (!$strict ? ' + OR `transformation` != \'\' + OR `transformation_options` != \'\' + OR `input_transformation` != \'\' + OR `input_transformation_options` != \'\'' : '') . ')'; + $result = $GLOBALS['dbi']->fetchResult( + $com_qry, 'column_name', null, DatabaseInterface::CONNECT_CONTROL + ); + + foreach ($result as $column => $values) { + // convert mimetype to new format (f.e. Text_Plain, etc) + $delimiter_space = '- '; + $delimiter = "_"; + $values['mimetype'] = self::fixupMIME($values['mimetype']); + + // For transformation of form + // output/image_jpeg__inline.inc.php + // extract dir part. + $dir = explode('/', $values['transformation']); + $subdir = ''; + if (count($dir) === 2) { + $subdir = ucfirst($dir[0]) . '/'; + $values['transformation'] = $dir[1]; + } + + $values['transformation'] = self::fixupMIME($values['transformation']); + $values['transformation'] = $subdir . $values['transformation']; + $result[$column] = $values; + } + + return $result; + } // end of the 'getMIME()' function + + /** + * Set a single mimetype to a certain value. + * + * @param string $db the name of the db + * @param string $table the name of the table + * @param string $key the name of the column + * @param string $mimetype the mimetype of the column + * @param string $transformation the transformation of the column + * @param string $transformationOpts the transformation options of the column + * @param string $inputTransform the input transformation of the column + * @param string $inputTransformOpts the input transformation options of the column + * @param boolean $forcedelete force delete, will erase any existing + * comments for this column + * + * @access public + * + * @return boolean true, if comment-query was made. + */ + public static function setMIME($db, $table, $key, $mimetype, $transformation, + $transformationOpts, $inputTransform, $inputTransformOpts, $forcedelete = false + ) { + $relation = new Relation(); + $cfgRelation = $relation->getRelationsParam(); + + if (! $cfgRelation['mimework']) { + return false; + } + + // lowercase mimetype & transformation + $mimetype = mb_strtolower($mimetype); + $transformation = mb_strtolower($transformation); + + // Do we have any parameter to set? + $has_value = ( + strlen($mimetype) > 0 || + strlen($transformation) > 0 || + strlen($transformationOpts) > 0 || + strlen($inputTransform) > 0 || + strlen($inputTransformOpts) > 0 + ); + + $test_qry = ' + SELECT `mimetype`, + `comment` + FROM ' . Util::backquote($cfgRelation['db']) . '.' + . Util::backquote($cfgRelation['column_info']) . ' + WHERE `db_name` = \'' . $GLOBALS['dbi']->escapeString($db) . '\' + AND `table_name` = \'' . $GLOBALS['dbi']->escapeString($table) . '\' + AND `column_name` = \'' . $GLOBALS['dbi']->escapeString($key) . '\''; + + $test_rs = $relation->queryAsControlUser( + $test_qry, true, DatabaseInterface::QUERY_STORE + ); + + if ($test_rs && $GLOBALS['dbi']->numRows($test_rs) > 0) { + $row = @$GLOBALS['dbi']->fetchAssoc($test_rs); + $GLOBALS['dbi']->freeResult($test_rs); + + if (! $forcedelete && ($has_value || strlen($row['comment']) > 0)) { + $upd_query = 'UPDATE ' + . Util::backquote($cfgRelation['db']) . '.' + . Util::backquote($cfgRelation['column_info']) + . ' SET ' + . '`mimetype` = \'' + . $GLOBALS['dbi']->escapeString($mimetype) . '\', ' + . '`transformation` = \'' + . $GLOBALS['dbi']->escapeString($transformation) . '\', ' + . '`transformation_options` = \'' + . $GLOBALS['dbi']->escapeString($transformationOpts) . '\', ' + . '`input_transformation` = \'' + . $GLOBALS['dbi']->escapeString($inputTransform) . '\', ' + . '`input_transformation_options` = \'' + . $GLOBALS['dbi']->escapeString($inputTransformOpts) . '\''; + } else { + $upd_query = 'DELETE FROM ' + . Util::backquote($cfgRelation['db']) + . '.' . Util::backquote($cfgRelation['column_info']); + } + $upd_query .= ' + WHERE `db_name` = \'' . $GLOBALS['dbi']->escapeString($db) . '\' + AND `table_name` = \'' . $GLOBALS['dbi']->escapeString($table) + . '\' + AND `column_name` = \'' . $GLOBALS['dbi']->escapeString($key) + . '\''; + } elseif ($has_value) { + + $upd_query = 'INSERT INTO ' + . Util::backquote($cfgRelation['db']) + . '.' . Util::backquote($cfgRelation['column_info']) + . ' (db_name, table_name, column_name, mimetype, ' + . 'transformation, transformation_options, ' + . 'input_transformation, input_transformation_options) ' + . ' VALUES(' + . '\'' . $GLOBALS['dbi']->escapeString($db) . '\',' + . '\'' . $GLOBALS['dbi']->escapeString($table) . '\',' + . '\'' . $GLOBALS['dbi']->escapeString($key) . '\',' + . '\'' . $GLOBALS['dbi']->escapeString($mimetype) . '\',' + . '\'' . $GLOBALS['dbi']->escapeString($transformation) . '\',' + . '\'' . $GLOBALS['dbi']->escapeString($transformationOpts) . '\',' + . '\'' . $GLOBALS['dbi']->escapeString($inputTransform) . '\',' + . '\'' . $GLOBALS['dbi']->escapeString($inputTransformOpts) . '\')'; + } + + if (isset($upd_query)) { + return $relation->queryAsControlUser($upd_query); + } + + return false; + } // end of 'setMIME()' function + + + /** + * GLOBAL Plugin functions + */ + + /** + * Delete related transformation details + * after deleting database. table or column + * + * @param string $db Database name + * @param string $table Table name + * @param string $column Column name + * + * @return boolean State of the query execution + */ + public static function clear($db, $table = '', $column = '') + { + $relation = new Relation(); + $cfgRelation = $relation->getRelationsParam(); + + if (! isset($cfgRelation['column_info'])) { + return false; + } + + $delete_sql = 'DELETE FROM ' + . Util::backquote($cfgRelation['db']) . '.' + . Util::backquote($cfgRelation['column_info']) + . ' WHERE '; + + if (($column != '') && ($table != '')) { + + $delete_sql .= '`db_name` = \'' . $db . '\' AND ' + . '`table_name` = \'' . $table . '\' AND ' + . '`column_name` = \'' . $column . '\' '; + + } elseif ($table != '') { + + $delete_sql .= '`db_name` = \'' . $db . '\' AND ' + . '`table_name` = \'' . $table . '\' '; + + } else { + $delete_sql .= '`db_name` = \'' . $db . '\' '; + } + + return $GLOBALS['dbi']->tryQuery($delete_sql); + + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Twig/CharsetsExtension.php b/php/apps/phpmyadmin49/libraries/classes/Twig/CharsetsExtension.php new file mode 100644 index 00000000..0bdd2c75 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Twig/CharsetsExtension.php @@ -0,0 +1,44 @@ + array('html')) + ), + new TwigFunction( + 'Charsets_getCollationDropdownBox', + 'PhpMyAdmin\Charsets::getCollationDropdownBox', + array('is_safe' => array('html')) + ), + ); + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Twig/CoreExtension.php b/php/apps/phpmyadmin49/libraries/classes/Twig/CoreExtension.php new file mode 100644 index 00000000..138fdcdf --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Twig/CoreExtension.php @@ -0,0 +1,35 @@ + array('html')) + ), + ); + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Twig/I18n/NodeTrans.php b/php/apps/phpmyadmin49/libraries/classes/Twig/I18n/NodeTrans.php new file mode 100644 index 00000000..a24fe58a --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Twig/I18n/NodeTrans.php @@ -0,0 +1,169 @@ +node). + * The attributes are automatically made available as array items ($this['name']). + * + * @param Node $body Body of node trans + * @param Node $plural Node plural + * @param AbstractExpression $count Node count + * @param Node $context Node context + * @param Node $notes Node notes + * @param int $lineno The line number + * @param string $tag The tag name associated with the Node + */ + public function __construct( + Node $body, + Node $plural = null, + AbstractExpression $count = null, + Node $context = null, + Node $notes = null, + $lineno, + $tag = null + ) { + $nodes = array('body' => $body); + if (null !== $count) { + $nodes['count'] = $count; + } + if (null !== $plural) { + $nodes['plural'] = $plural; + } + if (null !== $context) { + $nodes['context'] = $context; + } + if (null !== $notes) { + $nodes['notes'] = $notes; + } + + Node::__construct($nodes, array(), $lineno, $tag); + } + + /** + * Compiles the node to PHP. + * + * @param Compiler $compiler Node compiler + * + * @return void + */ + public function compile(Compiler $compiler) + { + $compiler->addDebugInfo($this); + + list($msg, $vars) = $this->compileString($this->getNode('body')); + + if ($this->hasNode('plural')) { + list($msg1, $vars1) = $this->compileString($this->getNode('plural')); + + $vars = array_merge($vars, $vars1); + } + + $function = $this->getTransFunction( + $this->hasNode('plural'), + $this->hasNode('context') + ); + + if ($this->hasNode('notes')) { + $message = trim($this->getNode('notes')->getAttribute('data')); + + // line breaks are not allowed cause we want a single line comment + $message = str_replace(array("\n", "\r"), ' ', $message); + $compiler->write("// l10n: {$message}\n"); + } + + if ($vars) { + $compiler + ->write('echo strtr(' . $function . '(') + ->subcompile($msg) + ; + + if ($this->hasNode('plural')) { + $compiler + ->raw(', ') + ->subcompile($msg1) + ->raw(', abs(') + ->subcompile($this->hasNode('count') ? $this->getNode('count') : null) + ->raw(')') + ; + } + + $compiler->raw('), array('); + + foreach ($vars as $var) { + if ('count' === $var->getAttribute('name')) { + $compiler + ->string('%count%') + ->raw(' => abs(') + ->subcompile($this->hasNode('count') ? $this->getNode('count') : null) + ->raw('), ') + ; + } else { + $compiler + ->string('%' . $var->getAttribute('name') . '%') + ->raw(' => ') + ->subcompile($var) + ->raw(', ') + ; + } + } + + $compiler->raw("));\n"); + } else { + $compiler->write('echo ' . $function . '('); + + if ($this->hasNode('context')) { + $context = trim($this->getNode('context')->getAttribute('data')); + $compiler->write('"' . $context . '", '); + } + + $compiler->subcompile($msg); + + if ($this->hasNode('plural')) { + $compiler + ->raw(', ') + ->subcompile($msg1) + ->raw(', abs(') + ->subcompile($this->hasNode('count') ? $this->getNode('count') : null) + ->raw(')') + ; + } + + $compiler->raw(");\n"); + } + } + + /** + * @param bool $plural Return plural or singular function to use + * @param bool $hasMsgContext It has message context? + * + * @return string + */ + protected function getTransFunction($plural, $hasMsgContext = false) + { + if ($hasMsgContext) { + return $plural ? '_ngettext' : '_pgettext'; + } + + return $plural ? '_ngettext' : '_gettext'; + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Twig/I18n/TokenParserTrans.php b/php/apps/phpmyadmin49/libraries/classes/Twig/I18n/TokenParserTrans.php new file mode 100644 index 00000000..44743e99 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Twig/I18n/TokenParserTrans.php @@ -0,0 +1,81 @@ +getLine(); + $stream = $this->parser->getStream(); + $count = null; + $plural = null; + $notes = null; + $context = null; + + if (!$stream->test(Token::BLOCK_END_TYPE)) { + $body = $this->parser->getExpressionParser()->parseExpression(); + } else { + $stream->expect(Token::BLOCK_END_TYPE); + $body = $this->parser->subparse(array($this, 'decideForFork')); + $next = $stream->next()->getValue(); + + if ('plural' === $next) { + $count = $this->parser->getExpressionParser()->parseExpression(); + $stream->expect(Token::BLOCK_END_TYPE); + $plural = $this->parser->subparse(array($this, 'decideForFork')); + + if ('notes' === $stream->next()->getValue()) { + $stream->expect(Token::BLOCK_END_TYPE); + $notes = $this->parser->subparse(array($this, 'decideForEnd'), true); + } + } elseif ('context' === $next) { + $stream->expect(Token::BLOCK_END_TYPE); + $context = $this->parser->subparse(array($this, 'decideForEnd'), true); + } elseif ('notes' === $next) { + $stream->expect(Token::BLOCK_END_TYPE); + $notes = $this->parser->subparse(array($this, 'decideForEnd'), true); + } + } + + $stream->expect(Token::BLOCK_END_TYPE); + + $this->checkTransString($body, $lineno); + + return new NodeTrans($body, $plural, $count, $context, $notes, $lineno, $this->getTag()); + } + + /** + * Tests the current token for a type. + * + * @param Token $token Twig token to test + * + * @return bool + */ + public function decideForFork(Token $token) + { + return $token->test(array('plural', 'context', 'notes', 'endtrans')); + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Twig/I18nExtension.php b/php/apps/phpmyadmin49/libraries/classes/Twig/I18nExtension.php new file mode 100644 index 00000000..dc668304 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Twig/I18nExtension.php @@ -0,0 +1,42 @@ + array('html')) + ), + ); + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Twig/MessageExtension.php b/php/apps/phpmyadmin49/libraries/classes/Twig/MessageExtension.php new file mode 100644 index 00000000..c2d0796d --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Twig/MessageExtension.php @@ -0,0 +1,45 @@ +getDisplay(); + }, + array('is_safe' => array('html')) + ), + new TwigFunction( + 'Message_error', + function ($string) { + return Message::error($string)->getDisplay(); + }, + array('is_safe' => array('html')) + ), + ); + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Twig/PartitionExtension.php b/php/apps/phpmyadmin49/libraries/classes/Twig/PartitionExtension.php new file mode 100644 index 00000000..d4d49cd7 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Twig/PartitionExtension.php @@ -0,0 +1,35 @@ + array('html')) + ), + ); + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Twig/PhpFunctionsExtension.php b/php/apps/phpmyadmin49/libraries/classes/Twig/PhpFunctionsExtension.php new file mode 100644 index 00000000..4cf7fc26 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Twig/PhpFunctionsExtension.php @@ -0,0 +1,39 @@ + array('html')) + ), + new TwigFunction( + 'Plugins_getChoice', + 'PhpMyAdmin\Plugins::getChoice', + array('is_safe' => array('html')) + ), + new TwigFunction( + 'Plugins_getDefault', + 'PhpMyAdmin\Plugins::getDefault', + array('is_safe' => array('html')) + ), + new TwigFunction( + 'Plugins_getOptions', + 'PhpMyAdmin\Plugins::getOptions', + array('is_safe' => array('html')) + ), + ); + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Twig/RelationExtension.php b/php/apps/phpmyadmin49/libraries/classes/Twig/RelationExtension.php new file mode 100644 index 00000000..ce69ffd0 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Twig/RelationExtension.php @@ -0,0 +1,54 @@ + array('html')) + ), + new TwigFunction( + 'Relation_getDisplayField', + [$relation, 'getDisplayField'], + array('is_safe' => array('html')) + ), + new TwigFunction( + 'Relation_getForeignData', + [$relation, 'getForeignData'] + ), + new TwigFunction( + 'Relation_getTables', + [$relation, 'getTables'] + ), + new TwigFunction( + 'Relation_searchColumnInForeigners', + [$relation, 'searchColumnInForeigners'] + ), + ); + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Twig/SanitizeExtension.php b/php/apps/phpmyadmin49/libraries/classes/Twig/SanitizeExtension.php new file mode 100644 index 00000000..668e2aeb --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Twig/SanitizeExtension.php @@ -0,0 +1,45 @@ + array('html')) + ), + new TwigFunction( + 'Sanitize_jsFormat', + 'PhpMyAdmin\Sanitize::jsFormat', + array('is_safe' => array('html')) + ), + new TwigFunction( + 'Sanitize_sanitize', + 'PhpMyAdmin\Sanitize::sanitize', + array('is_safe' => array('html')) + ), + ); + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Twig/ServerPrivilegesExtension.php b/php/apps/phpmyadmin49/libraries/classes/Twig/ServerPrivilegesExtension.php new file mode 100644 index 00000000..df7d4b85 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Twig/ServerPrivilegesExtension.php @@ -0,0 +1,35 @@ + array('html')) + ), + ); + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Twig/StorageEngineExtension.php b/php/apps/phpmyadmin49/libraries/classes/Twig/StorageEngineExtension.php new file mode 100644 index 00000000..9ce01f95 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Twig/StorageEngineExtension.php @@ -0,0 +1,35 @@ + array('html')) + ), + ); + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Twig/TableExtension.php b/php/apps/phpmyadmin49/libraries/classes/Twig/TableExtension.php new file mode 100644 index 00000000..c736e40a --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Twig/TableExtension.php @@ -0,0 +1,34 @@ + array('html')) + ), + new TwigFunction( + 'Url_getHiddenFields', + 'PhpMyAdmin\Url::getHiddenFields', + array('is_safe' => array('html')) + ), + new TwigFunction( + 'Url_getCommon', + 'PhpMyAdmin\Url::getCommon', + array('is_safe' => array('html')) + ), + new TwigFunction( + 'Url_getCommonRaw', + 'PhpMyAdmin\Url::getCommonRaw', + array('is_safe' => array('html')) + ), + new TwigFunction( + 'Url_link', + 'PhpMyAdmin\Core::linkURL' + ), + ); + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Twig/UtilExtension.php b/php/apps/phpmyadmin49/libraries/classes/Twig/UtilExtension.php new file mode 100644 index 00000000..3a483d07 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Twig/UtilExtension.php @@ -0,0 +1,189 @@ + array('html')) + ), + new TwigFunction( + 'Util_convertBitDefaultValue', + 'PhpMyAdmin\Util::convertBitDefaultValue' + ), + new TwigFunction( + 'Util_escapeMysqlWildcards', + 'PhpMyAdmin\Util::escapeMysqlWildcards' + ), + new TwigFunction( + 'Util_extractColumnSpec', + 'PhpMyAdmin\Util::extractColumnSpec' + ), + new TwigFunction( + 'Util_formatByteDown', + 'PhpMyAdmin\Util::formatByteDown' + ), + new TwigFunction( + 'Util_formatNumber', + 'PhpMyAdmin\Util::formatNumber' + ), + new TwigFunction( + 'Util_formatSql', + 'PhpMyAdmin\Util::formatSql', + array('is_safe' => array('html')) + ), + new TwigFunction( + 'Util_getButtonOrImage', + 'PhpMyAdmin\Util::getButtonOrImage', + array('is_safe' => array('html')) + ), + new TwigFunction( + 'Util_getClassForType', + 'PhpMyAdmin\Util::getClassForType', + array('is_safe' => array('html')) + ), + new TwigFunction( + 'Util_getDivForSliderEffect', + 'PhpMyAdmin\Util::getDivForSliderEffect', + array('is_safe' => array('html')) + ), + new TwigFunction( + 'Util_getDocuLink', + 'PhpMyAdmin\Util::getDocuLink', + array('is_safe' => array('html')) + ), + new TwigFunction( + 'Util_getListNavigator', + 'PhpMyAdmin\Util::getListNavigator', + array('is_safe' => array('html')) + ), + new TwigFunction( + 'Util_showDocu', + 'PhpMyAdmin\Util::showDocu', + array('is_safe' => array('html')) + ), + new TwigFunction( + 'Util_getDropdown', + 'PhpMyAdmin\Util::getDropdown', + array('is_safe' => array('html')) + ), + new TwigFunction( + 'Util_getFKCheckbox', + 'PhpMyAdmin\Util::getFKCheckbox', + array('is_safe' => array('html')) + ), + new TwigFunction( + 'Util_getGISDatatypes', + 'PhpMyAdmin\Util::getGISDatatypes' + ), + new TwigFunction( + 'Util_getGISFunctions', + 'PhpMyAdmin\Util::getGISFunctions' + ), + new TwigFunction( + 'Util_getHtmlTab', + 'PhpMyAdmin\Util::getHtmlTab', + array('is_safe' => array('html')) + ), + new TwigFunction( + 'Util_getIcon', + 'PhpMyAdmin\Util::getIcon', + array('is_safe' => array('html')) + ), + new TwigFunction( + 'Util_getImage', + 'PhpMyAdmin\Util::getImage', + array('is_safe' => array('html')) + ), + new TwigFunction( + 'Util_getRadioFields', + 'PhpMyAdmin\Util::getRadioFields', + array('is_safe' => array('html')) + ), + new TwigFunction( + 'Util_getSelectUploadFileBlock', + 'PhpMyAdmin\Util::getSelectUploadFileBlock', + array('is_safe' => array('html')) + ), + new TwigFunction( + 'Util_getScriptNameForOption', + 'PhpMyAdmin\Util::getScriptNameForOption', + array('is_safe' => array('html')) + ), + new TwigFunction( + 'Util_getStartAndNumberOfRowsPanel', + 'PhpMyAdmin\Util::getStartAndNumberOfRowsPanel', + array('is_safe' => array('html')) + ), + new TwigFunction( + 'Util_getSupportedDatatypes', + 'PhpMyAdmin\Util::getSupportedDatatypes', + array('is_safe' => array('html')) + ), + new TwigFunction( + 'Util_isForeignKeySupported', + 'PhpMyAdmin\Util::isForeignKeySupported' + ), + new TwigFunction( + 'Util_linkOrButton', + 'PhpMyAdmin\Util::linkOrButton', + array('is_safe' => array('html')) + ), + new TwigFunction( + 'Util_localisedDate', + 'PhpMyAdmin\Util::localisedDate' + ), + new TwigFunction( + 'Util_showHint', + 'PhpMyAdmin\Util::showHint', + array('is_safe' => array('html')) + ), + new TwigFunction( + 'Util_showDocu', + 'PhpMyAdmin\Util::showDocu', + array('is_safe' => array('html')) + ), + new TwigFunction( + 'Util_showIcons', + 'PhpMyAdmin\Util::showIcons' + ), + new TwigFunction( + 'Util_showMySQLDocu', + 'PhpMyAdmin\Util::showMySQLDocu', + array('is_safe' => array('html')) + ), + new TwigFunction( + 'Util_sortableTableHeader', + 'PhpMyAdmin\Util::sortableTableHeader', + array('is_safe' => array('html')) + ), + ); + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/TwoFactor.php b/php/apps/phpmyadmin49/libraries/classes/TwoFactor.php new file mode 100644 index 00000000..9ac4ee20 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/TwoFactor.php @@ -0,0 +1,288 @@ +userPreferences = new UserPreferences(); + $this->user = $user; + $this->_available = $this->getAvailable(); + $this->config = $this->readConfig(); + $this->_writable = ($this->config['type'] == 'db'); + $this->_backend = $this->getBackend(); + } + + /** + * Reads the configuration + * + * @return array + */ + public function readConfig() + { + $result = []; + $config = $this->userPreferences->load(); + if (isset($config['config_data']['2fa'])) { + $result = $config['config_data']['2fa']; + } + $result['type'] = $config['type']; + if (! isset($result['backend'])) { + $result['backend'] = ''; + } + if (! isset($result['settings'])) { + $result['settings'] = []; + } + return $result; + } + + /** + * Get any property of this class + * + * @param string $property name of the property + * + * @return mixed|void if property exist, value of the relevant property + */ + public function __get($property) + { + switch ($property) { + case 'backend': + return $this->_backend; + case 'available': + return $this->_available; + case 'writable': + return $this->_writable; + case 'showSubmit': + $backend = $this->_backend; + return $backend::$showSubmit; + } + } + + /** + * Returns list of available backends + * + * @return array + */ + public function getAvailable() + { + $result = []; + if ($GLOBALS['cfg']['DBG']['simple2fa']) { + $result[] = 'simple'; + } + if (class_exists('PragmaRX\Google2FA\Google2FA') && class_exists('BaconQrCode\Renderer\Image\Png')) { + $result[] = 'application'; + } + if (class_exists('Samyoul\U2F\U2FServer\U2FServer')) { + $result[] = 'key'; + } + return $result; + } + + /** + * Returns list of missing dependencies + * + * @return array + */ + public function getMissingDeps() + { + $result = []; + if (!class_exists('PragmaRX\Google2FA\Google2FA')) { + $result[] = [ + 'class' => \PhpMyAdmin\Plugins\TwoFactor\Application::getName(), + 'dep' => 'pragmarx/google2fa', + ]; + } + if (!class_exists('BaconQrCode\Renderer\Image\Png')) { + $result[] = [ + 'class' => \PhpMyAdmin\Plugins\TwoFactor\Application::getName(), + 'dep' => 'bacon/bacon-qr-code', + ]; + } + if (!class_exists('Samyoul\U2F\U2FServer\U2FServer')) { + $result[] = [ + 'class' => \PhpMyAdmin\Plugins\TwoFactor\Key::getName(), + 'dep' => 'samyoul/u2f-php-server', + ]; + } + return $result; + } + + /** + * Returns class name for given name + * + * @param string $name Backend name + * + * @return string + */ + public function getBackendClass($name) + { + $result = 'PhpMyAdmin\\Plugins\\TwoFactorPlugin'; + if (in_array($name, $this->_available)) { + $result = 'PhpMyAdmin\\Plugins\\TwoFactor\\' . ucfirst($name); + } elseif (! empty($name)) { + $result = 'PhpMyAdmin\\Plugins\\TwoFactor\\Invalid'; + } + return $result; + } + + /** + * Returns backend for current user + * + * @return PhpMyAdmin\Plugins\TwoFactorPlugin + */ + public function getBackend() + { + $name = $this->getBackendClass($this->config['backend']); + return new $name($this); + } + + /** + * Checks authentication, returns true on success + * + * @param boolean $skip_session Skip session cache + * + * @return boolean + */ + public function check($skip_session = false) + { + if ($skip_session) { + return $this->_backend->check(); + } + if (empty($_SESSION['two_factor_check'])) { + $_SESSION['two_factor_check'] = $this->_backend->check(); + } + return $_SESSION['two_factor_check']; + } + + /** + * Renders user interface to enter two-factor authentication + * + * @return string HTML code + */ + public function render() + { + return $this->_backend->getError() . $this->_backend->render(); + } + + /** + * Renders user interface to configure two-factor authentication + * + * @return string HTML code + */ + public function setup() + { + return $this->_backend->getError() . $this->_backend->setup(); + } + + /** + * Saves current configuration. + * + * @return true|PhpMyAdmin\Message + */ + public function save() + { + return $this->userPreferences->persistOption('2fa', $this->config, null); + } + + /** + * Changes two-factor authentication settings + * + * The object might stay in partialy changed setup + * if configuration fails. + * + * @param string $name Backend name + * + * @return boolean + */ + public function configure($name) + { + $this->config = [ + 'backend' => $name + ]; + if ($name === '') { + $cls = $this->getBackendClass($name); + $this->config['settings'] = []; + $this->_backend = new $cls($this); + } else { + if (! in_array($name, $this->_available)) { + return false; + } + $cls = $this->getBackendClass($name); + $this->config['settings'] = []; + $this->_backend = new $cls($this); + if (! $this->_backend->configure()) { + return false; + } + } + $result = $this->save(); + if ($result !== true) { + $result->display(); + } + return true; + } + + /** + * Returns array with all available backends + * + * @return array + */ + public function getAllBackends() + { + $all = array_merge([''], $this->available); + $backends = []; + foreach ($all as $name) { + $cls = $this->getBackendClass($name); + $backends[] = [ + 'id' => $cls::$id, + 'name' => $cls::getName(), + 'description' => $cls::getDescription(), + ]; + } + return $backends; + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Types.php b/php/apps/phpmyadmin49/libraries/classes/Types.php new file mode 100644 index 00000000..d53bcc27 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Types.php @@ -0,0 +1,828 @@ +_dbi = $dbi; + } + + /** + * Returns list of unary operators. + * + * @return string[] + */ + public function getUnaryOperators() + { + return array( + 'IS NULL', + 'IS NOT NULL', + "= ''", + "!= ''", + ); + } + + /** + * Check whether operator is unary. + * + * @param string $op operator name + * + * @return boolean + */ + public function isUnaryOperator($op) + { + return in_array($op, $this->getUnaryOperators()); + } + + /** + * Returns list of operators checking for NULL. + * + * @return string[] + */ + public function getNullOperators() + { + return array( + 'IS NULL', + 'IS NOT NULL', + ); + } + + /** + * ENUM search operators + * + * @return string[] + */ + public function getEnumOperators() + { + return array( + '=', + '!=', + ); + } + + /** + * TEXT search operators + * + * @return string[] + */ + public function getTextOperators() + { + return array( + 'LIKE', + 'LIKE %...%', + 'NOT LIKE', + '=', + '!=', + 'REGEXP', + 'REGEXP ^...$', + 'NOT REGEXP', + "= ''", + "!= ''", + 'IN (...)', + 'NOT IN (...)', + 'BETWEEN', + 'NOT BETWEEN', + ); + } + + /** + * Number search operators + * + * @return string[] + */ + public function getNumberOperators() + { + return array( + '=', + '>', + '>=', + '<', + '<=', + '!=', + 'LIKE', + 'LIKE %...%', + 'NOT LIKE', + 'IN (...)', + 'NOT IN (...)', + 'BETWEEN', + 'NOT BETWEEN', + ); + } + + /** + * Returns operators for given type + * + * @param string $type Type of field + * @param boolean $null Whether field can be NULL + * + * @return string[] + */ + public function getTypeOperators($type, $null) + { + $ret = array(); + $class = $this->getTypeClass($type); + + if (strncasecmp($type, 'enum', 4) == 0) { + $ret = array_merge($ret, $this->getEnumOperators()); + } elseif ($class == 'CHAR') { + $ret = array_merge($ret, $this->getTextOperators()); + } else { + $ret = array_merge($ret, $this->getNumberOperators()); + } + + if ($null) { + $ret = array_merge($ret, $this->getNullOperators()); + } + + return $ret; + } + + /** + * Returns operators for given type as html options + * + * @param string $type Type of field + * @param boolean $null Whether field can be NULL + * @param string $selectedOperator Option to be selected + * + * @return string Generated Html + */ + public function getTypeOperatorsHtml($type, $null, $selectedOperator = null) + { + $html = ''; + + foreach ($this->getTypeOperators($type, $null) as $fc) { + if (isset($selectedOperator) && $selectedOperator == $fc) { + $selected = ' selected="selected"'; + } else { + $selected = ''; + } + $html .= ''; + } + + return $html; + } + + /** + * Returns the data type description. + * + * @param string $type The data type to get a description. + * + * @return string + * + */ + public function getTypeDescription($type) + { + $type = mb_strtoupper($type); + switch ($type) { + case 'TINYINT': + return __( + 'A 1-byte integer, signed range is -128 to 127, unsigned range is ' . + '0 to 255' + ); + case 'SMALLINT': + return __( + 'A 2-byte integer, signed range is -32,768 to 32,767, unsigned ' . + 'range is 0 to 65,535' + ); + case 'MEDIUMINT': + return __( + 'A 3-byte integer, signed range is -8,388,608 to 8,388,607, ' . + 'unsigned range is 0 to 16,777,215' + ); + case 'INT': + return __( + 'A 4-byte integer, signed range is ' . + '-2,147,483,648 to 2,147,483,647, unsigned range is 0 to ' . + '4,294,967,295' + ); + case 'BIGINT': + return __( + 'An 8-byte integer, signed range is -9,223,372,036,854,775,808 ' . + 'to 9,223,372,036,854,775,807, unsigned range is 0 to ' . + '18,446,744,073,709,551,615' + ); + case 'DECIMAL': + return __( + 'A fixed-point number (M, D) - the maximum number of digits (M) ' . + 'is 65 (default 10), the maximum number of decimals (D) is 30 ' . + '(default 0)' + ); + case 'FLOAT': + return __( + 'A small floating-point number, allowable values are ' . + '-3.402823466E+38 to -1.175494351E-38, 0, and 1.175494351E-38 to ' . + '3.402823466E+38' + ); + case 'DOUBLE': + return __( + 'A double-precision floating-point number, allowable values are ' . + '-1.7976931348623157E+308 to -2.2250738585072014E-308, 0, and ' . + '2.2250738585072014E-308 to 1.7976931348623157E+308' + ); + case 'REAL': + return __( + 'Synonym for DOUBLE (exception: in REAL_AS_FLOAT SQL mode it is ' . + 'a synonym for FLOAT)' + ); + case 'BIT': + return __( + 'A bit-field type (M), storing M of bits per value (default is 1, ' . + 'maximum is 64)' + ); + case 'BOOLEAN': + return __( + 'A synonym for TINYINT(1), a value of zero is considered false, ' . + 'nonzero values are considered true' + ); + case 'SERIAL': + return __('An alias for BIGINT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE'); + case 'DATE': + return sprintf( + __('A date, supported range is %1$s to %2$s'), '1000-01-01', + '9999-12-31' + ); + case 'DATETIME': + return sprintf( + __('A date and time combination, supported range is %1$s to %2$s'), + '1000-01-01 00:00:00', '9999-12-31 23:59:59' + ); + case 'TIMESTAMP': + return __( + 'A timestamp, range is 1970-01-01 00:00:01 UTC to 2038-01-09 ' . + '03:14:07 UTC, stored as the number of seconds since the epoch ' . + '(1970-01-01 00:00:00 UTC)' + ); + case 'TIME': + return sprintf( + __('A time, range is %1$s to %2$s'), '-838:59:59', '838:59:59' + ); + case 'YEAR': + return __( + "A year in four-digit (4, default) or two-digit (2) format, the " . + "allowable values are 70 (1970) to 69 (2069) or 1901 to 2155 and " . + "0000" + ); + case 'CHAR': + return __( + 'A fixed-length (0-255, default 1) string that is always ' . + 'right-padded with spaces to the specified length when stored' + ); + case 'VARCHAR': + return sprintf( + __( + 'A variable-length (%s) string, the effective maximum length ' . + 'is subject to the maximum row size' + ), '0-65,535' + ); + case 'TINYTEXT': + return __( + 'A TEXT column with a maximum length of 255 (2^8 - 1) characters, ' . + 'stored with a one-byte prefix indicating the length of the value ' . + 'in bytes' + ); + case 'TEXT': + return __( + 'A TEXT column with a maximum length of 65,535 (2^16 - 1) ' . + 'characters, stored with a two-byte prefix indicating the length ' . + 'of the value in bytes' + ); + case 'MEDIUMTEXT': + return __( + 'A TEXT column with a maximum length of 16,777,215 (2^24 - 1) ' . + 'characters, stored with a three-byte prefix indicating the ' . + 'length of the value in bytes' + ); + case 'LONGTEXT': + return __( + 'A TEXT column with a maximum length of 4,294,967,295 or 4GiB ' . + '(2^32 - 1) characters, stored with a four-byte prefix indicating ' . + 'the length of the value in bytes' + ); + case 'BINARY': + return __( + 'Similar to the CHAR type, but stores binary byte strings rather ' . + 'than non-binary character strings' + ); + case 'VARBINARY': + return __( + 'Similar to the VARCHAR type, but stores binary byte strings ' . + 'rather than non-binary character strings' + ); + case 'TINYBLOB': + return __( + 'A BLOB column with a maximum length of 255 (2^8 - 1) bytes, ' . + 'stored with a one-byte prefix indicating the length of the value' + ); + case 'MEDIUMBLOB': + return __( + 'A BLOB column with a maximum length of 16,777,215 (2^24 - 1) ' . + 'bytes, stored with a three-byte prefix indicating the length of ' . + 'the value' + ); + case 'BLOB': + return __( + 'A BLOB column with a maximum length of 65,535 (2^16 - 1) bytes, ' . + 'stored with a two-byte prefix indicating the length of the value' + ); + case 'LONGBLOB': + return __( + 'A BLOB column with a maximum length of 4,294,967,295 or 4GiB ' . + '(2^32 - 1) bytes, stored with a four-byte prefix indicating the ' . + 'length of the value' + ); + case 'ENUM': + return __( + "An enumeration, chosen from the list of up to 65,535 values or " . + "the special '' error value" + ); + case 'SET': + return __("A single value chosen from a set of up to 64 members"); + case 'GEOMETRY': + return __('A type that can store a geometry of any type'); + case 'POINT': + return __('A point in 2-dimensional space'); + case 'LINESTRING': + return __('A curve with linear interpolation between points'); + case 'POLYGON': + return __('A polygon'); + case 'MULTIPOINT': + return __('A collection of points'); + case 'MULTILINESTRING': + return __( + 'A collection of curves with linear interpolation between points' + ); + case 'MULTIPOLYGON': + return __('A collection of polygons'); + case 'GEOMETRYCOLLECTION': + return __('A collection of geometry objects of any type'); + case 'JSON': + return __( + 'Stores and enables efficient access to data in JSON' + . ' (JavaScript Object Notation) documents' + ); + } + return ''; + } + + /** + * Returns class of a type, used for functions available for type + * or default values. + * + * @param string $type The data type to get a class. + * + * @return string + * + */ + public function getTypeClass($type) + { + $type = mb_strtoupper($type); + switch ($type) { + case 'TINYINT': + case 'SMALLINT': + case 'MEDIUMINT': + case 'INT': + case 'BIGINT': + case 'DECIMAL': + case 'FLOAT': + case 'DOUBLE': + case 'REAL': + case 'BIT': + case 'BOOLEAN': + case 'SERIAL': + return 'NUMBER'; + + case 'DATE': + case 'DATETIME': + case 'TIMESTAMP': + case 'TIME': + case 'YEAR': + return 'DATE'; + + case 'CHAR': + case 'VARCHAR': + case 'TINYTEXT': + case 'TEXT': + case 'MEDIUMTEXT': + case 'LONGTEXT': + case 'BINARY': + case 'VARBINARY': + case 'TINYBLOB': + case 'MEDIUMBLOB': + case 'BLOB': + case 'LONGBLOB': + case 'ENUM': + case 'SET': + return 'CHAR'; + + case 'GEOMETRY': + case 'POINT': + case 'LINESTRING': + case 'POLYGON': + case 'MULTIPOINT': + case 'MULTILINESTRING': + case 'MULTIPOLYGON': + case 'GEOMETRYCOLLECTION': + return 'SPATIAL'; + + case 'JSON': + return 'JSON'; + } + + return ''; + } + + /** + * Returns array of functions available for a class. + * + * @param string $class The class to get function list. + * + * @return string[] + * + */ + public function getFunctionsClass($class) + { + $isMariaDB = $this->_dbi->isMariaDB(); + $serverVersion = $this->_dbi->getVersion(); + + switch ($class) { + case 'CHAR': + $ret = array( + 'AES_DECRYPT', + 'AES_ENCRYPT', + 'BIN', + 'CHAR', + 'COMPRESS', + 'CURRENT_USER', + 'DATABASE', + 'DAYNAME', + 'DES_DECRYPT', + 'DES_ENCRYPT', + 'ENCRYPT', + 'HEX', + 'INET6_NTOA', + 'INET_NTOA', + 'LOAD_FILE', + 'LOWER', + 'LTRIM', + 'MD5', + 'MONTHNAME', + 'OLD_PASSWORD', + 'PASSWORD', + 'QUOTE', + 'REVERSE', + 'RTRIM', + 'SHA1', + 'SOUNDEX', + 'SPACE', + 'TRIM', + 'UNCOMPRESS', + 'UNHEX', + 'UPPER', + 'USER', + 'UUID', + 'VERSION', + ); + + if (($isMariaDB && $serverVersion < 100012) + || $serverVersion < 50603 + ) { + $ret = array_diff($ret, array('INET6_NTOA')); + } + return $ret; + + case 'DATE': + return array( + 'CURRENT_DATE', + 'CURRENT_TIME', + 'DATE', + 'FROM_DAYS', + 'FROM_UNIXTIME', + 'LAST_DAY', + 'NOW', + 'SEC_TO_TIME', + 'SYSDATE', + 'TIME', + 'TIMESTAMP', + 'UTC_DATE', + 'UTC_TIME', + 'UTC_TIMESTAMP', + 'YEAR', + ); + + case 'NUMBER': + $ret = array( + 'ABS', + 'ACOS', + 'ASCII', + 'ASIN', + 'ATAN', + 'BIT_LENGTH', + 'BIT_COUNT', + 'CEILING', + 'CHAR_LENGTH', + 'CONNECTION_ID', + 'COS', + 'COT', + 'CRC32', + 'DAYOFMONTH', + 'DAYOFWEEK', + 'DAYOFYEAR', + 'DEGREES', + 'EXP', + 'FLOOR', + 'HOUR', + 'INET6_ATON', + 'INET_ATON', + 'LENGTH', + 'LN', + 'LOG', + 'LOG2', + 'LOG10', + 'MICROSECOND', + 'MINUTE', + 'MONTH', + 'OCT', + 'ORD', + 'PI', + 'QUARTER', + 'RADIANS', + 'RAND', + 'ROUND', + 'SECOND', + 'SIGN', + 'SIN', + 'SQRT', + 'TAN', + 'TO_DAYS', + 'TO_SECONDS', + 'TIME_TO_SEC', + 'UNCOMPRESSED_LENGTH', + 'UNIX_TIMESTAMP', + 'UUID_SHORT', + 'WEEK', + 'WEEKDAY', + 'WEEKOFYEAR', + 'YEARWEEK', + ); + if (($isMariaDB && $serverVersion < 100012) + || $serverVersion < 50603 + ) { + $ret = array_diff($ret, array('INET6_ATON')); + } + return $ret; + + case 'SPATIAL': + if ($serverVersion >= 50600) { + return array( + 'ST_GeomFromText', + 'ST_GeomFromWKB', + + 'ST_GeomCollFromText', + 'ST_LineFromText', + 'ST_MLineFromText', + 'ST_PointFromText', + 'ST_MPointFromText', + 'ST_PolyFromText', + 'ST_MPolyFromText', + + 'ST_GeomCollFromWKB', + 'ST_LineFromWKB', + 'ST_MLineFromWKB', + 'ST_PointFromWKB', + 'ST_MPointFromWKB', + 'ST_PolyFromWKB', + 'ST_MPolyFromWKB', + ); + } else { + return array( + 'GeomFromText', + 'GeomFromWKB', + + 'GeomCollFromText', + 'LineFromText', + 'MLineFromText', + 'PointFromText', + 'MPointFromText', + 'PolyFromText', + 'MPolyFromText', + + 'GeomCollFromWKB', + 'LineFromWKB', + 'MLineFromWKB', + 'PointFromWKB', + 'MPointFromWKB', + 'PolyFromWKB', + 'MPolyFromWKB', + ); + + } + } + return array(); + } + + /** + * Returns array of functions available for a type. + * + * @param string $type The data type to get function list. + * + * @return string[] + * + */ + public function getFunctions($type) + { + $class = $this->getTypeClass($type); + return $this->getFunctionsClass($class); + } + + /** + * Returns array of all functions available. + * + * @return string[] + * + */ + public function getAllFunctions() + { + $ret = array_merge( + $this->getFunctionsClass('CHAR'), + $this->getFunctionsClass('NUMBER'), + $this->getFunctionsClass('DATE'), + $this->getFunctionsClass('UUID') + ); + sort($ret); + return $ret; + } + + /** + * Returns array of all attributes available. + * + * @return string[] + * + */ + public function getAttributes() + { + return array( + '', + 'BINARY', + 'UNSIGNED', + 'UNSIGNED ZEROFILL', + 'on update CURRENT_TIMESTAMP', + ); + } + + /** + * Returns array of all column types available. + * + * VARCHAR, TINYINT, TEXT and DATE are listed first, based on + * estimated popularity. + * + * @return string[] + * + */ + public function getColumns() + { + $isMariaDB = $this->_dbi->isMariaDB(); + $serverVersion = $this->_dbi->getVersion(); + + // most used types + $ret = array( + 'INT', + 'VARCHAR', + 'TEXT', + 'DATE', + ); + // numeric + $ret[_pgettext('numeric types', 'Numeric')] = array( + 'TINYINT', + 'SMALLINT', + 'MEDIUMINT', + 'INT', + 'BIGINT', + '-', + 'DECIMAL', + 'FLOAT', + 'DOUBLE', + 'REAL', + '-', + 'BIT', + 'BOOLEAN', + 'SERIAL', + ); + + // Date/Time + $ret[_pgettext('date and time types', 'Date and time')] = array( + 'DATE', + 'DATETIME', + 'TIMESTAMP', + 'TIME', + 'YEAR', + ); + + // Text + $ret[_pgettext('string types', 'String')] = array( + 'CHAR', + 'VARCHAR', + '-', + 'TINYTEXT', + 'TEXT', + 'MEDIUMTEXT', + 'LONGTEXT', + '-', + 'BINARY', + 'VARBINARY', + '-', + 'TINYBLOB', + 'MEDIUMBLOB', + 'BLOB', + 'LONGBLOB', + '-', + 'ENUM', + 'SET', + ); + + $ret[_pgettext('spatial types', 'Spatial')] = array( + 'GEOMETRY', + 'POINT', + 'LINESTRING', + 'POLYGON', + 'MULTIPOINT', + 'MULTILINESTRING', + 'MULTIPOLYGON', + 'GEOMETRYCOLLECTION', + ); + + if (($isMariaDB && $serverVersion > 100207) + || (!$isMariaDB && $serverVersion >= 50708)) { + $ret['JSON'] = array( + 'JSON', + ); + } + + return $ret; + } + + /** + * Returns an array of integer types + * + * @return string[] integer types + */ + public function getIntegerTypes() + { + return array('tinyint', 'smallint', 'mediumint', 'int', 'bigint'); + } + + /** + * Returns the min and max values of a given integer type + * + * @param string $type integer type + * @param boolean $signed whether signed + * + * @return string[] min and max values + */ + public function getIntegerRange($type, $signed = true) + { + static $min_max_data = array( + 'unsigned' => array( + 'tinyint' => array('0', '255'), + 'smallint' => array('0', '65535'), + 'mediumint' => array('0', '16777215'), + 'int' => array('0', '4294967295'), + 'bigint' => array('0', '18446744073709551615') + ), + 'signed' => array( + 'tinyint' => array('-128', '127'), + 'smallint' => array('-32768', '32767'), + 'mediumint' => array('-8388608', '8388607'), + 'int' => array('-2147483648', '2147483647'), + 'bigint' => array('-9223372036854775808', '9223372036854775807') + ) + ); + $relevantArray = $signed + ? $min_max_data['signed'] + : $min_max_data['unsigned']; + return isset($relevantArray[$type]) ? $relevantArray[$type] : array('', ''); + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Url.php b/php/apps/phpmyadmin49/libraries/classes/Url.php new file mode 100644 index 00000000..6733d126 --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Url.php @@ -0,0 +1,267 @@ + 0) { + $params['db'] = $db; + } + if (strlen($table) > 0) { + $params['table'] = $table; + } + } + + if (! empty($GLOBALS['server']) + && $GLOBALS['server'] != $GLOBALS['cfg']['ServerDefault'] + ) { + $params['server'] = $GLOBALS['server']; + } + if (empty($_COOKIE['pma_lang']) && ! empty($GLOBALS['lang'])) { + $params['lang'] = $GLOBALS['lang']; + } + + if (! is_array($skip)) { + if (isset($params[$skip])) { + unset($params[$skip]); + } + } else { + foreach ($skip as $skipping) { + if (isset($params[$skipping])) { + unset($params[$skipping]); + } + } + } + + return Url::getHiddenFields($params); + } + + /** + * create hidden form fields from array with name => value + * + * + * $values = array( + * 'aaa' => aaa, + * 'bbb' => array( + * 'bbb_0', + * 'bbb_1', + * ), + * 'ccc' => array( + * 'a' => 'ccc_a', + * 'b' => 'ccc_b', + * ), + * ); + * echo Url::getHiddenFields($values); + * + * // produces: + * + * + * + * + * + * + * + * @param array $values hidden values + * @param string $pre prefix + * + * @return string form fields of type hidden + */ + public static function getHiddenFields(array $values, $pre = '') + { + $fields = ''; + + /* Always include token in plain forms */ + if ($pre === '') { + $values['token'] = $_SESSION[' PMA_token ']; + } + + foreach ($values as $name => $value) { + if (! empty($pre)) { + $name = $pre . '[' . $name . ']'; + } + + if (is_array($value)) { + $fields .= Url::getHiddenFields($value, $name); + } else { + // do not generate an ending "\n" because + // Url::getHiddenInputs() is sometimes called + // from a JS document.write() + $fields .= ''; + } + } + + return $fields; + } + + /** + * Generates text with URL parameters. + * + * + * $params['myparam'] = 'myvalue'; + * $params['db'] = 'mysql'; + * $params['table'] = 'rights'; + * // note the missing ? + * echo 'script.php' . Url::getCommon($params); + * // produces with cookies enabled: + * // script.php?myparam=myvalue&db=mysql&table=rights + * // with cookies disabled: + * // script.php?server=1&lang=en&myparam=myvalue&db=mysql + * // &table=rights + * + * // note the missing ? + * echo 'script.php' . Url::getCommon(); + * // produces with cookies enabled: + * // script.php + * // with cookies disabled: + * // script.php?server=1&lang=en + * + * + * @param mixed $params optional, Contains an associative array with url params + * @param string $divider optional character to use instead of '?' + * + * @return string string with URL parameters + * @access public + */ + public static function getCommon($params = array(), $divider = '?') + { + return htmlspecialchars( + Url::getCommonRaw($params, $divider) + ); + } + + /** + * Generates text with URL parameters. + * + * + * $params['myparam'] = 'myvalue'; + * $params['db'] = 'mysql'; + * $params['table'] = 'rights'; + * // note the missing ? + * echo 'script.php' . Url::getCommon($params); + * // produces with cookies enabled: + * // script.php?myparam=myvalue&db=mysql&table=rights + * // with cookies disabled: + * // script.php?server=1&lang=en&myparam=myvalue&db=mysql + * // &table=rights + * + * // note the missing ? + * echo 'script.php' . Url::getCommon(); + * // produces with cookies enabled: + * // script.php + * // with cookies disabled: + * // script.php?server=1&lang=en + * + * + * @param mixed $params optional, Contains an associative array with url params + * @param string $divider optional character to use instead of '?' + * + * @return string string with URL parameters + * @access public + */ + public static function getCommonRaw($params = array(), $divider = '?') + { + $separator = Url::getArgSeparator(); + + // avoid overwriting when creating navi panel links to servers + if (isset($GLOBALS['server']) + && $GLOBALS['server'] != $GLOBALS['cfg']['ServerDefault'] + && ! isset($params['server']) + && ! $GLOBALS['PMA_Config']->get('is_setup') + ) { + $params['server'] = $GLOBALS['server']; + } + + if (empty($_COOKIE['pma_lang']) && ! empty($GLOBALS['lang'])) { + $params['lang'] = $GLOBALS['lang']; + } + + $query = http_build_query($params, null, $separator); + + if ($divider != '?' || strlen($query) > 0) { + return $divider . $query; + } + + return ''; + } + + /** + * Returns url separator + * + * extracted from arg_separator.input as set in php.ini + * we do not use arg_separator.output to avoid problems with & and & + * + * @param string $encode whether to encode separator or not, + * currently 'none' or 'html' + * + * @return string character used for separating url parts usually ; or & + * @access public + */ + public static function getArgSeparator($encode = 'none') + { + static $separator = null; + static $html_separator = null; + + if (null === $separator) { + // use separators defined by php, but prefer ';' + // as recommended by W3C + // (see https://www.w3.org/TR/1999/REC-html401-19991224/appendix + // /notes.html#h-B.2.2) + $arg_separator = ini_get('arg_separator.input'); + if (mb_strpos($arg_separator, ';') !== false) { + $separator = ';'; + } elseif (strlen($arg_separator) > 0) { + $separator = $arg_separator[0]; + } else { + $separator = '&'; + } + $html_separator = htmlentities($separator); + } + + switch ($encode) { + case 'html': + return $html_separator; + case 'text' : + case 'none' : + default : + return $separator; + } + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/UserPassword.php b/php/apps/phpmyadmin49/libraries/classes/UserPassword.php new file mode 100644 index 00000000..585463fe --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/UserPassword.php @@ -0,0 +1,251 @@ +isAjax()) { + /** + * If in an Ajax request, we don't need to show the rest of the page + */ + if ($change_password_message['error']) { + $response->addJSON('message', $change_password_message['msg']); + $response->setRequestStatus(false); + } else { + $sql_query = Util::getMessage( + $change_password_message['msg'], + $sql_query, + 'success' + ); + $response->addJSON('message', $sql_query); + } + exit; + } + } + + /** + * Generate the message + * + * @return array error value and message + */ + public function setChangePasswordMsg() + { + $error = false; + $message = Message::success(__('The profile has been updated.')); + + if (($_POST['nopass'] != '1')) { + if (strlen($_POST['pma_pw']) === 0 || strlen($_POST['pma_pw2']) === 0) { + $message = Message::error(__('The password is empty!')); + $error = true; + } elseif ($_POST['pma_pw'] !== $_POST['pma_pw2']) { + $message = Message::error( + __('The passwords aren\'t the same!') + ); + $error = true; + } elseif (strlen($_POST['pma_pw']) > 256) { + $message = Message::error(__('Password is too long!')); + $error = true; + } + } + return array('error' => $error, 'msg' => $message); + } + + /** + * Change the password + * + * @param string $password New password + * @param string $message Message + * @param array $change_password_message Message to show + * + * @return void + */ + public function changePassword($password, $message, array $change_password_message) + { + global $auth_plugin; + + $hashing_function = $this->changePassHashingFunction(); + + list($username, $hostname) = $GLOBALS['dbi']->getCurrentUserAndHost(); + + $serverType = Util::getServerType(); + $serverVersion = $GLOBALS['dbi']->getVersion(); + + if (isset($_POST['authentication_plugin']) + && ! empty($_POST['authentication_plugin']) + ) { + $orig_auth_plugin = $_POST['authentication_plugin']; + } else { + $orig_auth_plugin = Privileges::getCurrentAuthenticationPlugin( + 'change', $username, $hostname + ); + } + + $sql_query = 'SET password = ' + . (($password == '') ? '\'\'' : $hashing_function . '(\'***\')'); + + if ($serverType == 'MySQL' + && $serverVersion >= 50706 + ) { + $sql_query = 'ALTER USER \'' . $username . '\'@\'' . $hostname + . '\' IDENTIFIED WITH ' . $orig_auth_plugin . ' BY ' + . (($password == '') ? '\'\'' : '\'***\''); + } elseif (($serverType == 'MySQL' + && $serverVersion >= 50507) + || ($serverType == 'MariaDB' + && $serverVersion >= 50200) + ) { + // For MySQL versions 5.5.7+ and MariaDB versions 5.2+, + // explicitly set value of `old_passwords` so that + // it does not give an error while using + // the PASSWORD() function + if ($orig_auth_plugin == 'sha256_password') { + $value = 2; + } else { + $value = 0; + } + $GLOBALS['dbi']->tryQuery('SET `old_passwords` = ' . $value . ';'); + } + + $this->changePassUrlParamsAndSubmitQuery( + $username, $hostname, $password, + $sql_query, $hashing_function, $orig_auth_plugin + ); + + $auth_plugin->handlePasswordChange($password); + $this->getChangePassMessage($change_password_message, $sql_query); + $this->changePassDisplayPage($message, $sql_query); + } + + /** + * Generate the hashing function + * + * @return string $hashing_function + */ + private function changePassHashingFunction() + { + if (Core::isValid( + $_POST['authentication_plugin'], 'identical', 'mysql_old_password' + )) { + $hashing_function = 'OLD_PASSWORD'; + } else { + $hashing_function = 'PASSWORD'; + } + return $hashing_function; + } + + /** + * Changes password for a user + * + * @param string $username Username + * @param string $hostname Hostname + * @param string $password Password + * @param string $sql_query SQL query + * @param string $hashing_function Hashing function + * @param string $orig_auth_plugin Original Authentication Plugin + * + * @return void + */ + private function changePassUrlParamsAndSubmitQuery( + $username, $hostname, $password, $sql_query, $hashing_function, $orig_auth_plugin + ) { + $err_url = 'user_password.php' . Url::getCommon(); + + $serverType = Util::getServerType(); + $serverVersion = $GLOBALS['dbi']->getVersion(); + + if ($serverType == 'MySQL' && $serverVersion >= 50706) { + $local_query = 'ALTER USER \'' . $username . '\'@\'' . $hostname . '\'' + . ' IDENTIFIED with ' . $orig_auth_plugin . ' BY ' + . (($password == '') + ? '\'\'' + : '\'' . $GLOBALS['dbi']->escapeString($password) . '\''); + } elseif ($serverType == 'MariaDB' + && $serverVersion >= 50200 + && $serverVersion < 100100 + && $orig_auth_plugin !== '' + ) { + if ($orig_auth_plugin == 'mysql_native_password') { + // Set the hashing method used by PASSWORD() + // to be 'mysql_native_password' type + $GLOBALS['dbi']->tryQuery('SET old_passwords = 0;'); + } elseif ($orig_auth_plugin == 'sha256_password') { + // Set the hashing method used by PASSWORD() + // to be 'sha256_password' type + $GLOBALS['dbi']->tryQuery('SET `old_passwords` = 2;'); + } + + $hashedPassword = Privileges::getHashedPassword($_POST['pma_pw']); + + $local_query = "UPDATE `mysql`.`user` SET" + . " `authentication_string` = '" . $hashedPassword + . "', `Password` = '', " + . " `plugin` = '" . $orig_auth_plugin . "'" + . " WHERE `User` = '" . $username . "' AND Host = '" + . $hostname . "';"; + } else { + $local_query = 'SET password = ' . (($password == '') + ? '\'\'' + : $hashing_function . '(\'' + . $GLOBALS['dbi']->escapeString($password) . '\')'); + } + if (! @$GLOBALS['dbi']->tryQuery($local_query)) { + Util::mysqlDie( + $GLOBALS['dbi']->getError(), + $sql_query, + false, + $err_url + ); + } + + // Flush privileges after successful password change + $GLOBALS['dbi']->tryQuery("FLUSH PRIVILEGES;"); + } + + /** + * Display the page + * + * @param string $message Message + * @param string $sql_query SQL query + * + * @return void + */ + private function changePassDisplayPage($message, $sql_query) + { + echo '

      ' , __('Change password') , '

      ' , "\n\n"; + echo Util::getMessage( + $message, $sql_query, 'success' + ); + echo '' , "\n" + , '' , __('Back') , ''; + exit; + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/UserPreferences.php b/php/apps/phpmyadmin49/libraries/classes/UserPreferences.php new file mode 100644 index 00000000..76723fcd --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/UserPreferences.php @@ -0,0 +1,274 @@ +relation = new Relation(); + } + + /** + * Common initialization for user preferences modification pages + * + * @param ConfigFile $cf Config file instance + * + * @return void + */ + public function pageInit(ConfigFile $cf) + { + $forms_all_keys = UserFormList::getFields(); + $cf->resetConfigData(); // start with a clean instance + $cf->setAllowedKeys($forms_all_keys); + $cf->setCfgUpdateReadMapping( + array( + 'Server/hide_db' => 'Servers/1/hide_db', + 'Server/only_db' => 'Servers/1/only_db' + ) + ); + $cf->updateWithGlobalConfig($GLOBALS['cfg']); + } + + /** + * Loads user preferences + * + * Returns an array: + * * config_data - path => value pairs + * * mtime - last modification time + * * type - 'db' (config read from pmadb) or 'session' (read from user session) + * + * @return array + */ + public function load() + { + $cfgRelation = $this->relation->getRelationsParam(); + if (! $cfgRelation['userconfigwork']) { + // no pmadb table, use session storage + if (! isset($_SESSION['userconfig'])) { + $_SESSION['userconfig'] = array( + 'db' => array(), + 'ts' => time()); + } + return array( + 'config_data' => $_SESSION['userconfig']['db'], + 'mtime' => $_SESSION['userconfig']['ts'], + 'type' => 'session'); + } + // load configuration from pmadb + $query_table = Util::backquote($cfgRelation['db']) . '.' + . Util::backquote($cfgRelation['userconfig']); + $query = 'SELECT `config_data`, UNIX_TIMESTAMP(`timevalue`) ts' + . ' FROM ' . $query_table + . ' WHERE `username` = \'' + . $GLOBALS['dbi']->escapeString($cfgRelation['user']) + . '\''; + $row = $GLOBALS['dbi']->fetchSingleRow($query, 'ASSOC', DatabaseInterface::CONNECT_CONTROL); + + return array( + 'config_data' => $row ? json_decode($row['config_data'], true) : array(), + 'mtime' => $row ? $row['ts'] : time(), + 'type' => 'db'); + } + + /** + * Saves user preferences + * + * @param array $config_array configuration array + * + * @return true|PhpMyAdmin\Message + */ + public function save(array $config_array) + { + $cfgRelation = $this->relation->getRelationsParam(); + $server = isset($GLOBALS['server']) + ? $GLOBALS['server'] + : $GLOBALS['cfg']['ServerDefault']; + $cache_key = 'server_' . $server; + if (! $cfgRelation['userconfigwork']) { + // no pmadb table, use session storage + $_SESSION['userconfig'] = array( + 'db' => $config_array, + 'ts' => time()); + if (isset($_SESSION['cache'][$cache_key]['userprefs'])) { + unset($_SESSION['cache'][$cache_key]['userprefs']); + } + return true; + } + + // save configuration to pmadb + $query_table = Util::backquote($cfgRelation['db']) . '.' + . Util::backquote($cfgRelation['userconfig']); + $query = 'SELECT `username` FROM ' . $query_table + . ' WHERE `username` = \'' + . $GLOBALS['dbi']->escapeString($cfgRelation['user']) + . '\''; + + $has_config = $GLOBALS['dbi']->fetchValue( + $query, 0, 0, DatabaseInterface::CONNECT_CONTROL + ); + $config_data = json_encode($config_array); + if ($has_config) { + $query = 'UPDATE ' . $query_table + . ' SET `timevalue` = NOW(), `config_data` = \'' + . $GLOBALS['dbi']->escapeString($config_data) + . '\'' + . ' WHERE `username` = \'' + . $GLOBALS['dbi']->escapeString($cfgRelation['user']) + . '\''; + } else { + $query = 'INSERT INTO ' . $query_table + . ' (`username`, `timevalue`,`config_data`) ' + . 'VALUES (\'' + . $GLOBALS['dbi']->escapeString($cfgRelation['user']) . '\', NOW(), ' + . '\'' . $GLOBALS['dbi']->escapeString($config_data) . '\')'; + } + if (isset($_SESSION['cache'][$cache_key]['userprefs'])) { + unset($_SESSION['cache'][$cache_key]['userprefs']); + } + if (!$GLOBALS['dbi']->tryQuery($query, DatabaseInterface::CONNECT_CONTROL)) { + $message = Message::error(__('Could not save configuration')); + $message->addMessage( + Message::rawError( + $GLOBALS['dbi']->getError(DatabaseInterface::CONNECT_CONTROL) + ), + '

      ' + ); + return $message; + } + return true; + } + + /** + * Returns a user preferences array filtered by $cfg['UserprefsDisallow'] + * (blacklist) and keys from user preferences form (whitelist) + * + * @param array $config_data path => value pairs + * + * @return array + */ + public function apply(array $config_data) + { + $cfg = array(); + $blacklist = array_flip($GLOBALS['cfg']['UserprefsDisallow']); + $whitelist = array_flip(UserFormList::getFields()); + // whitelist some additional fields which are custom handled + $whitelist['ThemeDefault'] = true; + $whitelist['lang'] = true; + $whitelist['Server/hide_db'] = true; + $whitelist['Server/only_db'] = true; + $whitelist['2fa'] = true; + foreach ($config_data as $path => $value) { + if (! isset($whitelist[$path]) || isset($blacklist[$path])) { + continue; + } + Core::arrayWrite($path, $cfg, $value); + } + return $cfg; + } + + /** + * Updates one user preferences option (loads and saves to database). + * + * No validation is done! + * + * @param string $path configuration + * @param mixed $value value + * @param mixed $default_value default value + * + * @return true|PhpMyAdmin\Message + */ + public function persistOption($path, $value, $default_value) + { + $prefs = $this->load(); + if ($value === $default_value) { + if (isset($prefs['config_data'][$path])) { + unset($prefs['config_data'][$path]); + } else { + return true; + } + } else { + $prefs['config_data'][$path] = $value; + } + return $this->save($prefs['config_data']); + } + + /** + * Redirects after saving new user preferences + * + * @param string $file_name Filename + * @param array|null $params URL parameters + * @param string $hash Hash value + * + * @return void + */ + public function redirect($file_name, + $params = null, $hash = null + ) { + // redirect + $url_params = array('saved' => 1); + if (is_array($params)) { + $url_params = array_merge($params, $url_params); + } + if ($hash) { + $hash = '#' . urlencode($hash); + } + Core::sendHeaderLocation('./' . $file_name + . Url::getCommonRaw($url_params) . $hash + ); + } + + /** + * Shows form which allows to quickly load + * settings stored in browser's local storage + * + * @return string + */ + public function autoloadGetHeader() + { + if (isset($_REQUEST['prefs_autoload']) + && $_REQUEST['prefs_autoload'] == 'hide' + ) { + $_SESSION['userprefs_autoload'] = true; + return ''; + } + + $script_name = basename(basename($GLOBALS['PMA_PHP_SELF'])); + $return_url = $script_name . '?' . http_build_query($_GET, '', '&'); + + return Template::get('prefs_autoload') + ->render( + array( + 'hidden_inputs' => Url::getHiddenInputs(), + 'return_url' => $return_url, + ) + ); + } +} diff --git a/php/apps/phpmyadmin49/libraries/classes/Util.php b/php/apps/phpmyadmin49/libraries/classes/Util.php new file mode 100644 index 00000000..65c889ab --- /dev/null +++ b/php/apps/phpmyadmin49/libraries/classes/Util.php @@ -0,0 +1,4744 @@ +'; + if ($include_icon) { + $button .= self::getImage($icon, $alternate); + } + if ($include_icon && $include_text) { + $button .= ' '; + } + if ($include_text) { + $button .= $alternate; + } + $button .= $menu_icon ? '' : ''; + + return $button; + } + + /** + * Returns an HTML IMG tag for a particular image from a theme + * + * The image name should match CSS class defined in icons.css.php + * + * @param string $image The name of the file to get + * @param string $alternate Used to set 'alt' and 'title' attributes + * of the image + * @param array $attributes An associative array of other attributes + * + * @return string an html IMG tag + */ + public static function getImage($image, $alternate = '', array $attributes = array()) + { + $alternate = htmlspecialchars($alternate); + + // Set $url accordingly + if (isset($GLOBALS['pmaThemeImage'])) { + $url = $GLOBALS['pmaThemeImage'] . $image; + } else { + $url = './themes/pmahomme/' . $image; + } + + if (isset($attributes['class'])) { + $attributes['class'] = "icon ic_$image " . $attributes['class']; + } else { + $attributes['class'] = "icon ic_$image"; + } + + // set all other attributes + $attr_str = ''; + foreach ($attributes as $key => $value) { + if (! in_array($key, array('alt', 'title'))) { + $attr_str .= " $key=\"$value\""; + } + } + + // override the alt attribute + if (isset($attributes['alt'])) { + $alt = $attributes['alt']; + } else { + $alt = $alternate; + } + + // override the title attribute + if (isset($attributes['title'])) { + $title = $attributes['title']; + } else { + $title = $alternate; + } + + // generate the IMG tag + $template = '%s'; + $retval = sprintf($template, $title, $alt, $attr_str); + + return $retval; + } + + /** + * Returns the formatted maximum size for an upload + * + * @param integer $max_upload_size the size + * + * @return string the message + * + * @access public + */ + public static function getFormattedMaximumUploadSize($max_upload_size) + { + // I have to reduce the second parameter (sensitiveness) from 6 to 4 + // to avoid weird results like 512 kKib + list($max_size, $max_unit) = self::formatByteDown($max_upload_size, 4); + return '(' . sprintf(__('Max: %s%s'), $max_size, $max_unit) . ')'; + } + + /** + * Generates a hidden field which should indicate to the browser + * the maximum size for upload + * + * @param integer $max_size the size + * + * @return string the INPUT field + * + * @access public + */ + public static function generateHiddenMaxFileSize($max_size) + { + return ''; + } + + /** + * Add slashes before "_" and "%" characters for using them in MySQL + * database, table and field names. + * Note: This function does not escape backslashes! + * + * @param string $name the string to escape + * + * @return string the escaped string + * + * @access public + */ + public static function escapeMysqlWildcards($name) + { + return strtr($name, array('_' => '\\_', '%' => '\\%')); + } // end of the 'escapeMysqlWildcards()' function + + /** + * removes slashes before "_" and "%" characters + * Note: This function does not unescape backslashes! + * + * @param string $name the string to escape + * + * @return string the escaped string + * + * @access public + */ + public static function unescapeMysqlWildcards($name) + { + return strtr($name, array('\\_' => '_', '\\%' => '%')); + } // end of the 'unescapeMysqlWildcards()' function + + /** + * removes quotes (',",`) from a quoted string + * + * checks if the string is quoted and removes this quotes + * + * @param string $quoted_string string to remove quotes from + * @param string $quote type of quote to remove + * + * @return string unqoted string + */ + public static function unQuote($quoted_string, $quote = null) + { + $quotes = array(); + + if ($quote === null) { + $quotes[] = '`'; + $quotes[] = '"'; + $quotes[] = "'"; + } else { + $quotes[] = $quote; + } + + foreach ($quotes as $quote) { + if (mb_substr($quoted_string, 0, 1) === $quote + && mb_substr($quoted_string, -1, 1) === $quote + ) { + $unquoted_string = mb_substr($quoted_string, 1, -1); + // replace escaped quotes + $unquoted_string = str_replace( + $quote . $quote, + $quote, + $unquoted_string + ); + return $unquoted_string; + } + } + + return $quoted_string; + } + + /** + * format sql strings + * + * @param string $sqlQuery raw SQL string + * @param boolean $truncate truncate the query if it is too long + * + * @return string the formatted sql + * + * @global array $cfg the configuration array + * + * @access public + * @todo move into PMA_Sql + */ + public static function formatSql($sqlQuery, $truncate = false) + { + global $cfg; + + if ($truncate + && mb_strlen($sqlQuery) > $cfg['MaxCharactersInDisplayedSQL'] + ) { + $sqlQuery = mb_substr( + $sqlQuery, + 0, + $cfg['MaxCharactersInDisplayedSQL'] + ) . '[...]'; + } + return '
      ' . "\n"
      +            . htmlspecialchars($sqlQuery) . "\n"
      +            . '
      '; + } // end of the "formatSql()" function + + /** + * Displays a link to the documentation as an icon + * + * @param string $link documentation link + * @param string $target optional link target + * @param boolean $bbcode optional flag indicating whether to output bbcode + * + * @return string the html link + * + * @access public + */ + public static function showDocLink($link, $target = 'documentation', $bbcode = false) + { + if($bbcode){ + return "[a@$link@$target][dochelpicon][/a]"; + } + + return '' + . self::getImage('b_help', __('Documentation')) + . ''; + } // end of the 'showDocLink()' function + + /** + * Get a URL link to the official MySQL documentation + * + * @param string $link contains name of page/anchor that is being linked + * @param string $anchor anchor to page part + * + * @return string the URL link + * + * @access public + */ + public static function getMySQLDocuURL($link, $anchor = '') + { + // Fixup for newly used names: + $link = str_replace('_', '-', mb_strtolower($link)); + + if (empty($link)) { + $link = 'index'; + } + $mysql = '5.5'; + $lang = 'en'; + if (isset($GLOBALS['dbi'])) { + $serverVersion = $GLOBALS['dbi']->getVersion(); + if ($serverVersion >= 50700) { + $mysql = '5.7'; + } elseif ($serverVersion >= 50600) { + $mysql = '5.6'; + } elseif ($serverVersion >= 50500) { + $mysql = '5.5'; + } + } + $url = 'https://dev.mysql.com/doc/refman/' + . $mysql . '/' . $lang . '/' . $link . '.html'; + if (! empty($anchor)) { + $url .= '#' . $anchor; + } + + return Core::linkURL($url); + } + + /** + * Displays a link to the official MySQL documentation + * + * @param string $link contains name of page/anchor that is being linked + * @param bool $big_icon whether to use big icon (like in left frame) + * @param string $anchor anchor to page part + * @param bool $just_open whether only the opening tag should be returned + * + * @return string the html link + * + * @access public + */ + public static function showMySQLDocu( + $link, + $big_icon = false, + $anchor = '', + $just_open = false + ) { + $url = self::getMySQLDocuURL($link, $anchor); + $open_link = ''; + if ($just_open) { + return $open_link; + } elseif ($big_icon) { + return $open_link + . self::getImage('b_sqlhelp', __('Documentation')) . ''; + } + + return self::showDocLink($url, 'mysql_doc'); + } // end of the 'showMySQLDocu()' function + + /** + * Returns link to documentation. + * + * @param string $page Page in documentation + * @param string $anchor Optional anchor in page + * + * @return string URL + */ + public static function getDocuLink($page, $anchor = '') + { + /* Construct base URL */ + $url = $page . '.html'; + if (!empty($anchor)) { + $url .= '#' . $anchor; + } + + /* Check if we have built local documentation, however + * provide consistent URL for testsuite + */ + if (! defined('TESTSUITE') && @file_exists('doc/html/index.html')) { + if ($GLOBALS['PMA_Config']->get('is_setup')) { + return '../doc/html/' . $url; + } + + return './doc/html/' . $url; + } + + return Core::linkURL('https://docs.phpmyadmin.net/en/latest/' . $url); + } + + /** + * Displays a link to the phpMyAdmin documentation + * + * @param string $page Page in documentation + * @param string $anchor Optional anchor in page + * @param boolean $bbcode Optional flag indicating whether to output bbcode + * + * @return string the html link + * + * @access public + */ + public static function showDocu($page, $anchor = '', $bbcode = false) + { + return self::showDocLink(self::getDocuLink($page, $anchor), 'documentation', $bbcode); + } // end of the 'showDocu()' function + + /** + * Displays a link to the PHP documentation + * + * @param string $target anchor in documentation + * + * @return string the html link + * + * @access public + */ + public static function showPHPDocu($target) + { + $url = Core::getPHPDocLink($target); + + return self::showDocLink($url); + } // end of the 'showPHPDocu()' function + + /** + * Returns HTML code for a tooltip + * + * @param string $message the message for the tooltip + * + * @return string + * + * @access public + */ + public static function showHint($message) + { + if ($GLOBALS['cfg']['ShowHint']) { + $classClause = ' class="pma_hint"'; + } else { + $classClause = ''; + } + return '' + . self::getImage('b_help') + . '' . $message . '' + . ''; + } + + /** + * Displays a MySQL error message in the main panel when $exit is true. + * Returns the error message otherwise. + * + * @param string|bool $server_msg Server's error message. + * @param string $sql_query The SQL query that failed. + * @param bool $is_modify_link Whether to show a "modify" link or not. + * @param string $back_url URL for the "back" link (full path is + * not required). + * @param bool $exit Whether execution should be stopped or + * the error message should be returned. + * + * @return string + * + * @global string $table The current table. + * @global string $db The current database. + * + * @access public + */ + public static function mysqlDie( + $server_msg = '', + $sql_query = '', + $is_modify_link = true, + $back_url = '', + $exit = true + ) { + global $table, $db; + + /** + * Error message to be built. + * @var string $error_msg + */ + $error_msg = ''; + + // Checking for any server errors. + if (empty($server_msg)) { + $server_msg = $GLOBALS['dbi']->getError(); + } + + // Finding the query that failed, if not specified. + if ((empty($sql_query) && (!empty($GLOBALS['sql_query'])))) { + $sql_query = $GLOBALS['sql_query']; + } + $sql_query = trim($sql_query); + + /** + * The lexer used for analysis. + * @var Lexer $lexer + */ + $lexer = new Lexer($sql_query); + + /** + * The parser used for analysis. + * @var Parser $parser + */ + $parser = new Parser($lexer->list); + + /** + * The errors found by the lexer and the parser. + * @var array $errors + */ + $errors = ParserError::get(array($lexer, $parser)); + + if (empty($sql_query)) { + $formatted_sql = ''; + } elseif (count($errors)) { + $formatted_sql = htmlspecialchars($sql_query); + } else { + $formatted_sql = self::formatSql($sql_query, true); + } + + $error_msg .= '

      ' . __('Error') . '

      '; + + // For security reasons, if the MySQL refuses the connection, the query + // is hidden so no details are revealed. + if ((!empty($sql_query)) && (!(mb_strstr($sql_query, 'connect')))) { + // Static analysis errors. + if (!empty($errors)) { + $error_msg .= '

      ' . __('Static analysis:') + . '

      '; + $error_msg .= '

      ' . sprintf( + __('%d errors were found during analysis.'), + count($errors) + ) . '

      '; + $error_msg .= '

        '; + $error_msg .= implode( + ParserError::format( + $errors, + '
      1. %2$s (near "%4$s" at position %5$d)
      2. ' + ) + ); + $error_msg .= '

      '; + } + + // Display the SQL query and link to MySQL documentation. + $error_msg .= '

      ' . __('SQL query:') . '' . "\n"; + $formattedSqlToLower = mb_strtolower($formatted_sql); + + // TODO: Show documentation for all statement types. + if (mb_strstr($formattedSqlToLower, 'select')) { + // please show me help to the error on select + $error_msg .= self::showMySQLDocu('SELECT'); + } + + if ($is_modify_link) { + $_url_params = array( + 'sql_query' => $sql_query, + 'show_query' => 1, + ); + if (strlen($table) > 0) { + $_url_params['db'] = $db; + $_url_params['table'] = $table; + $doedit_goto = ''; + } elseif (strlen($db) > 0) { + $_url_params['db'] = $db; + $doedit_goto = ''; + } else { + $doedit_goto = ''; + } + + $error_msg .= $doedit_goto + . self::getIcon('b_edit', __('Edit')) + . ''; + } + + $error_msg .= '

      ' . "\n" + . '

      ' . "\n" + . $formatted_sql . "\n" + . '

      ' . "\n"; + } + + // Display server's error. + if (!empty($server_msg)) { + $server_msg = preg_replace( + "@((\015\012)|(\015)|(\012)){3,}@", + "\n\n", + $server_msg + ); + + // Adds a link to MySQL documentation. + $error_msg .= '

      ' . "\n" + . ' ' . __('MySQL said: ') . '' + . self::showMySQLDocu('Error-messages-server') + . "\n" + . '

      ' . "\n"; + + // The error message will be displayed within a CODE segment. + // To preserve original formatting, but allow word-wrapping, + // a couple of replacements are done. + // All non-single blanks and TAB-characters are replaced with their + // HTML-counterpart + $server_msg = str_replace( + array(' ', "\t"), + array('  ', '    '), + $server_msg + ); + + // Replace line breaks + $server_msg = nl2br($server_msg); + + $error_msg .= '' . $server_msg . '
      '; + } + + $error_msg .= '
      '; + $_SESSION['Import_message']['message'] = $error_msg; + + if (!$exit) { + return $error_msg; + } + + /** + * If this is an AJAX request, there is no "Back" link and + * `Response()` is used to send the response. + */ + $response = Response::getInstance(); + if ($response->isAjax()) { + $response->setRequestStatus(false); + $response->addJSON('message', $error_msg); + exit; + } + + if (!empty($back_url)) { + if (mb_strstr($back_url, '?')) { + $back_url .= '&no_history=true'; + } else { + $back_url .= '?no_history=true'; + } + + $_SESSION['Import_message']['go_back_url'] = $back_url; + + $error_msg .= '
      ' + . '[ ' . __('Back') . ' ]' + . '
      ' . "\n\n"; + } + + exit($error_msg); + } + + /** + * Check the correct row count + * + * @param string $db the db name + * @param array $table the table infos + * + * @return int $rowCount the possibly modified row count + * + */ + private static function _checkRowCount($db, array $table) + { + $rowCount = 0; + + if ($table['Rows'] === null) { + // Do not check exact row count here, + // if row count is invalid possibly the table is defect + // and this would break the navigation panel; + // but we can check row count if this is a view or the + // information_schema database + // since Table::countRecords() returns a limited row count + // in this case. + + // set this because Table::countRecords() can use it + $tbl_is_view = $table['TABLE_TYPE'] == 'VIEW'; + + if ($tbl_is_view || $GLOBALS['dbi']->isSystemSchema($db)) { + $rowCount = $GLOBALS['dbi'] + ->getTable($db, $table['Name']) + ->countRecords(); + } + } + return $rowCount; + } + + /** + * returns array with tables of given db with extended information and grouped + * + * @param string $db name of db + * @param string $tables name of tables + * @param integer $limit_offset list offset + * @param int|bool $limit_count max tables to return + * + * @return array (recursive) grouped table list + */ + public static function getTableList( + $db, + $tables = null, + $limit_offset = 0, + $limit_count = false + ) { + $sep = $GLOBALS['cfg']['NavigationTreeTableSeparator']; + + if ($tables === null) { + $tables = $GLOBALS['dbi']->getTablesFull( + $db, + '', + false, + $limit_offset, + $limit_count + ); + if ($GLOBALS['cfg']['NaturalOrder']) { + uksort($tables, 'strnatcasecmp'); + } + } + + if (count($tables) < 1) { + return $tables; + } + + $default = array( + 'Name' => '', + 'Rows' => 0, + 'Comment' => '', + 'disp_name' => '', + ); + + $table_groups = array(); + + foreach ($tables as $table_name => $table) { + $table['Rows'] = self::_checkRowCount($db, $table); + + // in $group we save the reference to the place in $table_groups + // where to store the table info + if ($GLOBALS['cfg']['NavigationTreeEnableGrouping'] + && $sep && mb_strstr($table_name, $sep) + ) { + $parts = explode($sep, $table_name); + + $group =& $table_groups; + $i = 0; + $group_name_full = ''; + $parts_cnt = count($parts) - 1; + + while (($i < $parts_cnt) + && ($i < $GLOBALS['cfg']['NavigationTreeTableLevel']) + ) { + $group_name = $parts[$i] . $sep; + $group_name_full .= $group_name; + + if (! isset($group[$group_name])) { + $group[$group_name] = array(); + $group[$group_name]['is' . $sep . 'group'] = true; + $group[$group_name]['tab' . $sep . 'count'] = 1; + $group[$group_name]['tab' . $sep . 'group'] + = $group_name_full; + + } elseif (! isset($group[$group_name]['is' . $sep . 'group'])) { + $table = $group[$group_name]; + $group[$group_name] = array(); + $group[$group_name][$group_name] = $table; + $group[$group_name]['is' . $sep . 'group'] = true; + $group[$group_name]['tab' . $sep . 'count'] = 1; + $group[$group_name]['tab' . $sep . 'group'] + = $group_name_full; + + } else { + $group[$group_name]['tab' . $sep . 'count']++; + } + + $group =& $group[$group_name]; + $i++; + } + + } else { + if (! isset($table_groups[$table_name])) { + $table_groups[$table_name] = array(); + } + $group =& $table_groups; + } + + $table['disp_name'] = $table['Name']; + $group[$table_name] = array_merge($default, $table); + } + + return $table_groups; + } + + /* ----------------------- Set of misc functions ----------------------- */ + + /** + * Adds backquotes on both sides of a database, table or field name. + * and escapes backquotes inside the name with another backquote + * + * example: + * + * echo backquote('owner`s db'); // `owner``s db` + * + * + * + * @param mixed $a_name the database, table or field name to "backquote" + * or array of it + * @param boolean $do_it a flag to bypass this function (used by dump + * functions) + * + * @return mixed the "backquoted" database, table or field name + * + * @access public + */ + public static function backquote($a_name, $do_it = true) + { + if (is_array($a_name)) { + foreach ($a_name as &$data) { + $data = self::backquote($data, $do_it); + } + return $a_name; + } + + if (! $do_it) { + if (!(Context::isKeyword($a_name) & Token::FLAG_KEYWORD_RESERVED) + ) { + return $a_name; + } + } + + // '0' is also empty for php :-( + if (strlen($a_name) > 0 && $a_name !== '*') { + return '`' . str_replace('`', '``', $a_name) . '`'; + } + + return $a_name; + } // end of the 'backquote()' function + + /** + * Adds backquotes on both sides of a database, table or field name. + * in compatibility mode + * + * example: + * + * echo backquoteCompat('owner`s db'); // `owner``s db` + * + * + * + * @param mixed $a_name the database, table or field name to + * "backquote" or array of it + * @param string $compatibility string compatibility mode (used by dump + * functions) + * @param boolean $do_it a flag to bypass this function (used by dump + * functions) + * + * @return mixed the "backquoted" database, table or field name + * + * @access public + */ + public static function backquoteCompat( + $a_name, + $compatibility = 'MSSQL', + $do_it = true + ) { + if (is_array($a_name)) { + foreach ($a_name as &$data) { + $data = self::backquoteCompat($data, $compatibility, $do_it); + } + return $a_name; + } + + if (! $do_it) { + if (!Context::isKeyword($a_name)) { + return $a_name; + } + } + + // @todo add more compatibility cases (ORACLE for example) + switch ($compatibility) { + case 'MSSQL': + $quote = '"'; + break; + default: + $quote = "`"; + break; + } + + // '0' is also empty for php :-( + if (strlen($a_name) > 0 && $a_name !== '*') { + return $quote . $a_name . $quote; + } + + return $a_name; + } // end of the 'backquoteCompat()' function + + /** + * Prepare the message and the query + * usually the message is the result of the query executed + * + * @param Message|string $message the message to display + * @param string $sql_query the query to display + * @param string $type the type (level) of the message + * + * @return string + * + * @access public + */ + public static function getMessage( + $message, + $sql_query = null, + $type = 'notice' + ) { + global $cfg; + $retval = ''; + + if (null === $sql_query) { + if (! empty($GLOBALS['display_query'])) { + $sql_query = $GLOBALS['display_query']; + } elseif (! empty($GLOBALS['unparsed_sql'])) { + $sql_query = $GLOBALS['unparsed_sql']; + } elseif (! empty($GLOBALS['sql_query'])) { + $sql_query = $GLOBALS['sql_query']; + } else { + $sql_query = ''; + } + } + + $render_sql = $cfg['ShowSQL'] == true && ! empty($sql_query) && $sql_query !== ';'; + + if (isset($GLOBALS['using_bookmark_message'])) { + $retval .= $GLOBALS['using_bookmark_message']->getDisplay(); + unset($GLOBALS['using_bookmark_message']); + } + + if ($render_sql) { + $retval .= '
      ' . "\n"; + } + + if ($message instanceof Message) { + if (isset($GLOBALS['special_message'])) { + $message->addText($GLOBALS['special_message']); + unset($GLOBALS['special_message']); + } + $retval .= $message->getDisplay(); + } else { + $retval .= '
      '; + $retval .= Sanitize::sanitize($message); + if (isset($GLOBALS['special_message'])) { + $retval .= Sanitize::sanitize($GLOBALS['special_message']); + unset($GLOBALS['special_message']); + } + $retval .= '
      '; + } + + if ($render_sql) { + $query_too_big = false; + + $queryLength = mb_strlen($sql_query); + if ($queryLength > $cfg['MaxCharactersInDisplayedSQL']) { + // when the query is large (for example an INSERT of binary + // data), the parser chokes; so avoid parsing the query + $query_too_big = true; + $query_base = mb_substr( + $sql_query, + 0, + $cfg['MaxCharactersInDisplayedSQL'] + ) . '[...]'; + } else { + $query_base = $sql_query; + } + + // Html format the query to be displayed + // If we want to show some sql code it is easiest to create it here + /* SQL-Parser-Analyzer */ + + if (! empty($GLOBALS['show_as_php'])) { + $new_line = '\\n"
      ' . "\n" . '    . "'; + $query_base = htmlspecialchars(addslashes($query_base)); + $query_base = preg_replace( + '/((\015\012)|(\015)|(\012))/', + $new_line, + $query_base + ); + $query_base = '
      ' . "\n"
      +                    . '$sql = "' . $query_base . '";' . "\n"
      +                    . '
      '; + } elseif ($query_too_big) { + $query_base = '
      ' . "\n" .
      +                    htmlspecialchars($query_base) .
      +                    '
      '; + } else { + $query_base = self::formatSql($query_base); + } + + // Prepares links that may be displayed to edit/explain the query + // (don't go to default pages, we must go to the page + // where the query box is available) + + // Basic url query part + $url_params = array(); + if (! isset($GLOBALS['db'])) { + $GLOBALS['db'] = ''; + } + if (strlen($GLOBALS['db']) > 0) { + $url_params['db'] = $GLOBALS['db']; + if (strlen($GLOBALS['table']) > 0) { + $url_params['table'] = $GLOBALS['table']; + $edit_link = 'tbl_sql.php'; + } else { + $edit_link = 'db_sql.php'; + } + } else { + $edit_link = 'server_sql.php'; + } + + // Want to have the query explained + // but only explain a SELECT (that has not been explained) + /* SQL-Parser-Analyzer */ + $explain_link = ''; + $is_select = preg_match('@^SELECT[[:space:]]+@i', $sql_query); + if (! empty($cfg['SQLQuery']['Explain']) && ! $query_too_big) { + $explain_params = $url_params; + if ($is_select) { + $explain_params['sql_query'] = 'EXPLAIN ' . $sql_query; + $explain_link = ' [ ' + . self::linkOrButton( + 'import.php' . Url::getCommon($explain_params), + __('Explain SQL') + ) . ' ]'; + } elseif (preg_match( + '@^EXPLAIN[[:space:]]+SELECT[[:space:]]+@i', + $sql_query + )) { + $explain_params['sql_query'] + = mb_substr($sql_query, 8); + $explain_link = ' [ ' + . self::linkOrButton( + 'import.php' . Url::getCommon($explain_params), + __('Skip Explain SQL') + ) . ']'; + $url = 'https://mariadb.org/explain_analyzer/analyze/' + . '?client=phpMyAdmin&raw_explain=' + . urlencode(self::_generateRowQueryOutput($sql_query)); + $explain_link .= ' [' + . self::linkOrButton( + htmlspecialchars('url.php?url=' . urlencode($url)), + sprintf(__('Analyze Explain at %s'), 'mariadb.org'), + array(), + '_blank' + ) . ' ]'; + } + } //show explain + + $url_params['sql_query'] = $sql_query; + $url_params['show_query'] = 1; + + // even if the query is big and was truncated, offer the chance + // to edit it (unless it's enormous, see linkOrButton() ) + if (! empty($cfg['SQLQuery']['Edit']) + && empty($GLOBALS['show_as_php']) + ) { + $edit_link .= Url::getCommon($url_params); + $edit_link = ' [ ' + . self::linkOrButton($edit_link, __('Edit')) + . ' ]'; + } else { + $edit_link = ''; + } + + // Also we would like to get the SQL formed in some nice + // php-code + if (! empty($cfg['SQLQuery']['ShowAsPHP']) && ! $query_too_big) { + + if (! empty($GLOBALS['show_as_php'])) { + $php_link = ' [ ' + . self::linkOrButton( + 'import.php' . Url::getCommon($url_params), + __('Without PHP code') + ) + . ' ]'; + + $php_link .= ' [ ' + . self::linkOrButton( + 'import.php' . Url::getCommon($url_params), + __('Submit query') + ) + . ' ]'; + } else { + $php_params = $url_params; + $php_params['show_as_php'] = 1; + $php_link = ' [ ' + . self::linkOrButton( + 'import.php' . Url::getCommon($php_params), + __('Create PHP code') + ) + . ' ]'; + } + } else { + $php_link = ''; + } //show as php + + // Refresh query + if (! empty($cfg['SQLQuery']['Refresh']) + && ! isset($GLOBALS['show_as_php']) // 'Submit query' does the same + && preg_match('@^(SELECT|SHOW)[[:space:]]+@i', $sql_query) + ) { + $refresh_link = 'import.php' . Url::getCommon($url_params); + $refresh_link = ' [ ' + . self::linkOrButton($refresh_link, __('Refresh')) . ']'; + } else { + $refresh_link = ''; + } //refresh + + $retval .= '
      '; + $retval .= $query_base; + $retval .= '
      '; + + $retval .= ''; + + $retval .= '
      '; + } + + return $retval; + } // end of the 'getMessage()' function + + /** + * Execute an EXPLAIN query and formats results similar to MySQL command line + * utility. + * + * @param string $sqlQuery EXPLAIN query + * + * @return string query resuls + */ + private static function _generateRowQueryOutput($sqlQuery) + { + $ret = ''; + $result = $GLOBALS['dbi']->query($sqlQuery); + if ($result) { + $devider = '+'; + $columnNames = '|'; + $fieldsMeta = $GLOBALS['dbi']->getFieldsMeta($result); + foreach ($fieldsMeta as $meta) { + $devider .= '---+'; + $columnNames .= ' ' . $meta->name . ' |'; + } + $devider .= "\n"; + + $ret .= $devider . $columnNames . "\n" . $devider; + while ($row = $GLOBALS['dbi']->fetchRow($result)) { + $values = '|'; + foreach ($row as $value) { + if (is_null($value)) { + $value = 'NULL'; + } + $values .= ' ' . $value . ' |'; + } + $ret .= $values . "\n"; + } + $ret .= $devider; + } + return $ret; + } + + /** + * Verifies if current MySQL server supports profiling + * + * @access public + * + * @return boolean whether profiling is supported + */ + public static function profilingSupported() + { + if (!self::cacheExists('profiling_supported')) { + // 5.0.37 has profiling but for example, 5.1.20 does not + // (avoid a trip to the server for MySQL before 5.0.37) + // and do not set a constant as we might be switching servers + if ($GLOBALS['dbi']->fetchValue("SELECT @@have_profiling") + ) { + self::cacheSet('profiling_supported', true); + } else { + self::cacheSet('profiling_supported', false); + } + } + + return self::cacheGet('profiling_supported'); + } + + /** + * Formats $value to byte view + * + * @param double|int $value the value to format + * @param int $limes the sensitiveness + * @param int $comma the number of decimals to retain + * + * @return array the formatted value and its unit + * + * @access public + */ + public static function formatByteDown($value, $limes = 6, $comma = 0) + { + if ($value === null) { + return null; + } + + $byteUnits = array( + /* l10n: shortcuts for Byte */ + __('B'), + /* l10n: shortcuts for Kilobyte */ + __('KiB'), + /* l10n: shortcuts for Megabyte */ + __('MiB'), + /* l10n: shortcuts for Gigabyte */ + __('GiB'), + /* l10n: shortcuts for Terabyte */ + __('TiB'), + /* l10n: shortcuts for Petabyte */ + __('PiB'), + /* l10n: shortcuts for Exabyte */ + __('EiB') + ); + + $dh = pow(10, $comma); + $li = pow(10, $limes); + $unit = $byteUnits[0]; + + for ($d = 6, $ex = 15; $d >= 1; $d--, $ex-=3) { + $unitSize = $li * pow(10, $ex); + if (isset($byteUnits[$d]) && $value >= $unitSize) { + // use 1024.0 to avoid integer overflow on 64-bit machines + $value = round($value / (pow(1024, $d) / $dh)) /$dh; + $unit = $byteUnits[$d]; + break 1; + } // end if + } // end for + + if ($unit != $byteUnits[0]) { + // if the unit is not bytes (as represented in current language) + // reformat with max length of 5 + // 4th parameter=true means do not reformat if value < 1 + $return_value = self::formatNumber($value, 5, $comma, true); + } else { + // do not reformat, just handle the locale + $return_value = self::formatNumber($value, 0); + } + + return array(trim($return_value), $unit); + } // end of the 'formatByteDown' function + + + /** + * Formats $value to the given length and appends SI prefixes + * with a $length of 0 no truncation occurs, number is only formatted + * to the current locale + * + * examples: + * + * echo formatNumber(123456789, 6); // 123,457 k + * echo formatNumber(-123456789, 4, 2); // -123.46 M + * echo formatNumber(-0.003, 6); // -3 m + * echo formatNumber(0.003, 3, 3); // 0.003 + * echo formatNumber(0.00003, 3, 2); // 0.03 m + * echo formatNumber(0, 6); // 0 + * + * + * @param double $value the value to format + * @param integer $digits_left number of digits left of the comma + * @param integer $digits_right number of digits right of the comma + * @param boolean $only_down do not reformat numbers below 1 + * @param boolean $noTrailingZero removes trailing zeros right of the comma + * (default: true) + * + * @return string the formatted value and its unit + * + * @access public + */ + public static function formatNumber( + $value, + $digits_left = 3, + $digits_right = 0, + $only_down = false, + $noTrailingZero = true + ) { + if ($value == 0) { + return '0'; + } + + $originalValue = $value; + //number_format is not multibyte safe, str_replace is safe + if ($digits_left === 0) { + $value = number_format( + $value, + $digits_right, + /* l10n: Decimal separator */ + __('.'), + /* l10n: Thousands separator */ + __(',') + ); + if (($originalValue != 0) && (floatval($value) == 0)) { + $value = ' <' . (1 / pow(10, $digits_right)); + } + return $value; + } + + // this units needs no translation, ISO + $units = array( + -8 => 'y', + -7 => 'z', + -6 => 'a', + -5 => 'f', + -4 => 'p', + -3 => 'n', + -2 => 'µ', + -1 => 'm', + 0 => ' ', + 1 => 'k', + 2 => 'M', + 3 => 'G', + 4 => 'T', + 5 => 'P', + 6 => 'E', + 7 => 'Z', + 8 => 'Y' + ); + /* l10n: Decimal separator */ + $decimal_sep = __('.'); + /* l10n: Thousands separator */ + $thousands_sep = __(','); + + // check for negative value to retain sign + if ($value < 0) { + $sign = '-'; + $value = abs($value); + } else { + $sign = ''; + } + + $dh = pow(10, $digits_right); + + /* + * This gives us the right SI prefix already, + * but $digits_left parameter not incorporated + */ + $d = floor(log10($value) / 3); + /* + * Lowering the SI prefix by 1 gives us an additional 3 zeros + * So if we have 3,6,9,12.. free digits ($digits_left - $cur_digits) + * to use, then lower the SI prefix + */ + $cur_digits = floor(log10($value / pow(1000, $d))+1); + if ($digits_left > $cur_digits) { + $d -= floor(($digits_left - $cur_digits)/3); + } + + if ($d < 0 && $only_down) { + $d = 0; + } + + $value = round($value / (pow(1000, $d) / $dh)) /$dh; + $unit = $units[$d]; + + // number_format is not multibyte safe, str_replace is safe + $formattedValue = number_format( + $value, + $digits_right, + $decimal_sep, + $thousands_sep + ); + // If we don't want any zeros, remove them now + if ($noTrailingZero && strpos($formattedValue, $decimal_sep) !== false) { + $formattedValue = preg_replace('/' . preg_quote($decimal_sep) . '?0+$/', '', $formattedValue); + } + + if ($originalValue != 0 && floatval($value) == 0) { + return ' <' . number_format( + (1 / pow(10, $digits_right)), + $digits_right, + $decimal_sep, + $thousands_sep + ) + . ' ' . $unit; + } + + return $sign . $formattedValue . ' ' . $unit; + } // end of the 'formatNumber' function + + /** + * Returns the number of bytes when a formatted size is given + * + * @param string $formatted_size the size expression (for example 8MB) + * + * @return integer The numerical part of the expression (for example 8) + */ + public static function extractValueFromFormattedSize($formatted_size) + { + $return_value = -1; + + if (preg_match('/^[0-9]+GB$/', $formatted_size)) { + $return_value = mb_substr($formatted_size, 0, -2) + * pow(1024, 3); + } elseif (preg_match('/^[0-9]+MB$/', $formatted_size)) { + $return_value = mb_substr($formatted_size, 0, -2) + * pow(1024, 2); + } elseif (preg_match('/^[0-9]+K$/', $formatted_size)) { + $return_value = mb_substr($formatted_size, 0, -1) + * pow(1024, 1); + } + return $return_value; + }// end of the 'extractValueFromFormattedSize' function + + /** + * Writes localised date + * + * @param integer $timestamp the current timestamp + * @param string $format format + * + * @return string the formatted date + * + * @access public + */ + public static function localisedDate($timestamp = -1, $format = '') + { + $month = array( + /* l10n: Short month name */ + __('Jan'), + /* l10n: Short month name */ + __('Feb'), + /* l10n: Short month name */ + __('Mar'), + /* l10n: Short month name */ + __('Apr'), + /* l10n: Short month name */ + _pgettext('Short month name', 'May'), + /* l10n: Short month name */ + __('Jun'), + /* l10n: Short month name */ + __('Jul'), + /* l10n: Short month name */ + __('Aug'), + /* l10n: Short month name */ + __('Sep'), + /* l10n: Short month name */ + __('Oct'), + /* l10n: Short month name */ + __('Nov'), + /* l10n: Short month name */ + __('Dec')); + $day_of_week = array( + /* l10n: Short week day name */ + _pgettext('Short week day name', 'Sun'), + /* l10n: Short week day name */ + __('Mon'), + /* l10n: Short week day name */ + __('Tue'), + /* l10n: Short week day name */ + __('Wed'), + /* l10n: Short week day name */ + __('Thu'), + /* l10n: Short week day name */ + __('Fri'), + /* l10n: Short week day name */ + __('Sat')); + + if ($format == '') { + /* l10n: See https://secure.php.net/manual/en/function.strftime.php */ + $format = __('%B %d, %Y at %I:%M %p'); + } + + if ($timestamp == -1) { + $timestamp = time(); + } + + $date = preg_replace( + '@%[aA]@', + $day_of_week[(int)strftime('%w', $timestamp)], + $format + ); + $date = preg_replace( + '@%[bB]@', + $month[(int)strftime('%m', $timestamp)-1], + $date + ); + + /* Fill in AM/PM */ + $hours = (int)date('H', $timestamp); + if ($hours >= 12) { + $am_pm = _pgettext('AM/PM indication in time', 'PM'); + } else { + $am_pm = _pgettext('AM/PM indication in time', 'AM'); + } + $date = preg_replace('@%[pP]@', $am_pm, $date); + + $ret = strftime($date, $timestamp); + // Some OSes such as Win8.1 Traditional Chinese version did not produce UTF-8 + // output here. See https://github.com/phpmyadmin/phpmyadmin/issues/10598 + if (mb_detect_encoding($ret, 'UTF-8', true) != 'UTF-8') { + $ret = date('Y-m-d H:i:s', $timestamp); + } + + return $ret; + } // end of the 'localisedDate()' function + + /** + * returns a tab for tabbed navigation. + * If the variables $link and $args ar left empty, an inactive tab is created + * + * @param array $tab array with all options + * @param array $url_params tab specific URL parameters + * + * @return string html code for one tab, a link if valid otherwise a span + * + * @access public + */ + public static function getHtmlTab(array $tab, array $url_params = array()) + { + // default values + $defaults = array( + 'text' => '', + 'class' => '', + 'active' => null, + 'link' => '', + 'sep' => '?', + 'attr' => '', + 'args' => '', + 'warning' => '', + 'fragment' => '', + 'id' => '', + ); + + $tab = array_merge($defaults, $tab); + + // determine additional style-class + if (empty($tab['class'])) { + if (! empty($tab['active']) + || Core::isValid($GLOBALS['active_page'], 'identical', $tab['link']) + ) { + $tab['class'] = 'active'; + } elseif (is_null($tab['active']) && empty($GLOBALS['active_page']) + && (basename($GLOBALS['PMA_PHP_SELF']) == $tab['link']) + ) { + $tab['class'] = 'active'; + } + } + + // build the link + if (! empty($tab['link'])) { + // If there are any tab specific URL parameters, merge those with + // the general URL parameters + if (! empty($tab['args']) && is_array($tab['args'])) { + $url_params = array_merge($url_params, $tab['args']); + } + $tab['link'] = htmlentities($tab['link']) . Url::getCommon($url_params); + } + + if (! empty($tab['fragment'])) { + $tab['link'] .= $tab['fragment']; + } + + // display icon + if (isset($tab['icon'])) { + // avoid generating an alt tag, because it only illustrates + // the text that follows and if browser does not display + // images, the text is duplicated + $tab['text'] = self::getIcon( + $tab['icon'], + $tab['text'], + false, + true, + 'TabsMode' + ); + + } elseif (empty($tab['text'])) { + // check to not display an empty link-text + $tab['text'] = '?'; + trigger_error( + 'empty linktext in function ' . __FUNCTION__ . '()', + E_USER_NOTICE + ); + } + + //Set the id for the tab, if set in the params + $tabId = (empty($tab['id']) ? null : $tab['id']); + + $item = array(); + if (!empty($tab['link'])) { + $item = array( + 'content' => $tab['text'], + 'url' => array( + 'href' => empty($tab['link']) ? null : $tab['link'], + 'id' => $tabId, + 'class' => 'tab' . htmlentities($tab['class']), + ), + ); + } else { + $item['content'] = '' . $tab['text'] . ''; + } + + $item['class'] = $tab['class'] == 'active' ? 'active' : ''; + + return Template::get('list/item') + ->render($item); + } // end of the 'getHtmlTab()' function + + /** + * returns html-code for a tab navigation + * + * @param array $tabs one element per tab + * @param array $url_params additional URL parameters + * @param string $menu_id HTML id attribute for the menu container + * @param bool $resizable whether to add a "resizable" class + * + * @return string html-code for tab-navigation + */ + public static function getHtmlTabs( + array $tabs, + array $url_params, + $menu_id, + $resizable = false + ) { + $class = ''; + if ($resizable) { + $class = ' class="resizable-menu"'; + } + + $tab_navigation = '' . "\n"; + + return $tab_navigation; + } + + /** + * Displays a link, or a link with code to trigger POST request. + * + * POST is used in following cases: + * + * - URL is too long + * - URL components are over Suhosin limits + * - There is SQL query in the parameters + * + * @param string $url the URL + * @param string $message the link message + * @param mixed $tag_params string: js confirmation; array: additional tag + * params (f.e. style="") + * @param string $target target + * + * @return string the results to be echoed or saved in an array + */ + public static function linkOrButton( + $url, $message, $tag_params = array(), $target = '' + ) { + $url_length = strlen($url); + + if (! is_array($tag_params)) { + $tmp = $tag_params; + $tag_params = array(); + if (! empty($tmp)) { + $tag_params['onclick'] = 'return confirmLink(this, \'' + . Sanitize::escapeJsString($tmp) . '\')'; + } + unset($tmp); + } + if (! empty($target)) { + $tag_params['target'] = $target; + if ($target === '_blank' && strncmp($url, 'url.php?', 8) == 0) { + $tag_params['rel'] = 'noopener noreferrer'; + } + } + + // Suhosin: Check that each query parameter is not above maximum + $in_suhosin_limits = true; + if ($url_length <= $GLOBALS['cfg']['LinkLengthLimit']) { + $suhosin_get_MaxValueLength = ini_get('suhosin.get.max_value_length'); + if ($suhosin_get_MaxValueLength) { + $query_parts = self::splitURLQuery($url); + foreach ($query_parts as $query_pair) { + if (strpos($query_pair, '=') === false) { + continue; + } + + list(, $eachval) = explode('=', $query_pair); + if (strlen($eachval) > $suhosin_get_MaxValueLength + ) { + $in_suhosin_limits = false; + break; + } + } + } + } + + $tag_params_strings = array(); + if (($url_length > $GLOBALS['cfg']['LinkLengthLimit']) + || ! $in_suhosin_limits + // Has as sql_query without a signature + || ( strpos($url, 'sql_query=') !== false && strpos($url, 'sql_signature=') === false) + || strpos($url, 'view[as]=') !== false + ) { + $parts = explode('?', $url, 2); + /* + * The data-post indicates that client should do POST + * this is handled in js/ajax.js + */ + $tag_params_strings[] = 'data-post="' . (isset($parts[1]) ? $parts[1] : '') . '"'; + $url = $parts[0]; + if(array_key_exists('class', $tag_params) + && strpos($tag_params['class'], 'create_view') !== false + ) { + $url .= '?' . explode('&', $parts[1], 2)[0]; + } + + } + + foreach ($tag_params as $par_name => $par_value) { + $tag_params_strings[] = $par_name . '="' . htmlspecialchars($par_value) . '"'; + } + + // no whitespace within an else Safari will make it part of the link + return '' + . $message . ''; + } // end of the 'linkOrButton()' function + + /** + * Splits a URL string by parameter + * + * @param string $url the URL + * + * @return array the parameter/value pairs, for example [0] db=sakila + */ + public static function splitURLQuery($url) + { + // decode encoded url separators + $separator = Url::getArgSeparator(); + // on most places separator is still hard coded ... + if ($separator !== '&') { + // ... so always replace & with $separator + $url = str_replace(htmlentities('&'), $separator, $url); + $url = str_replace('&', $separator, $url); + } + + $url = str_replace(htmlentities($separator), $separator, $url); + // end decode + + $url_parts = parse_url($url); + + if (! empty($url_parts['query'])) { + return explode($separator, $url_parts['query']); + } + + return array(); + } + + /** + * Returns a given timespan value in a readable format. + * + * @param int $seconds the timespan + * + * @return string the formatted value + */ + public static function timespanFormat($seconds) + { + $days = floor($seconds / 86400); + if ($days > 0) { + $seconds -= $days * 86400; + } + + $hours = floor($seconds / 3600); + if ($days > 0 || $hours > 0) { + $seconds -= $hours * 3600; + } + + $minutes = floor($seconds / 60); + if ($days > 0 || $hours > 0 || $minutes > 0) { + $seconds -= $minutes * 60; + } + + return sprintf( + __('%s days, %s hours, %s minutes and %s seconds'), + (string)$days, + (string)$hours, + (string)$minutes, + (string)$seconds + ); + } + + /** + * Function added to avoid path disclosures. + * Called by each script that needs parameters, it displays + * an error message and, by default, stops the execution. + * + * @param string[] $params The names of the parameters needed by the calling + * script + * @param boolean $request Check parameters in request + * + * @return void + * + * @access public + */ + public static function checkParameters($params, $request=false) + { + $reported_script_name = basename($GLOBALS['PMA_PHP_SELF']); + $found_error = false; + $error_message = ''; + if ($request) { + $array = $_REQUEST; + } else { + $array = $GLOBALS; + } + + foreach ($params as $param) { + if (! isset($array[$param])) { + $error_message .= $reported_script_name + . ': ' . __('Missing parameter:') . ' ' + . $param + . self::showDocu('faq', 'faqmissingparameters',true) + . '[br]'; + $found_error = true; + } + } + if ($found_error) { + Core::fatalError($error_message); + } + } // end function + + /** + * Function to generate unique condition for specified row. + * + * @param resource $handle current query result + * @param integer $fields_cnt number of fields + * @param array $fields_meta meta information about fields + * @param array $row current row + * @param boolean $force_unique generate condition only on pk + * or unique + * @param string|boolean $restrict_to_table restrict the unique condition + * to this table or false if + * none + * @param array|null $analyzed_sql_results the analyzed query + * + * @access public + * + * @return array the calculated condition and whether condition is unique + */ + public static function getUniqueCondition( + $handle, $fields_cnt, array $fields_meta, array $row, $force_unique = false, + $restrict_to_table = false, $analyzed_sql_results = null + ) { + $primary_key = ''; + $unique_key = ''; + $nonprimary_condition = ''; + $preferred_condition = ''; + $primary_key_array = array(); + $unique_key_array = array(); + $nonprimary_condition_array = array(); + $condition_array = array(); + + for ($i = 0; $i < $fields_cnt; ++$i) { + + $con_val = ''; + $field_flags = $GLOBALS['dbi']->fieldFlags($handle, $i); + $meta = $fields_meta[$i]; + + // do not use a column alias in a condition + if (! isset($meta->orgname) || strlen($meta->orgname) === 0) { + $meta->orgname = $meta->name; + + if (!empty($analyzed_sql_results['statement']->expr)) { + foreach ($analyzed_sql_results['statement']->expr as $expr) { + if ((empty($expr->alias)) || (empty($expr->column))) { + continue; + } + if (strcasecmp($meta->name, $expr->alias) == 0) { + $meta->orgname = $expr->column; + break; + } + } + } + } + + // Do not use a table alias in a condition. + // Test case is: + // select * from galerie x WHERE + //(select count(*) from galerie y where y.datum=x.datum)>1 + // + // But orgtable is present only with mysqli extension so the + // fix is only for mysqli. + // Also, do not use the original table name if we are dealing with + // a view because this view might be updatable. + // (The isView() verification should not be costly in most cases + // because there is some caching in the function). + if (isset($meta->orgtable) + && ($meta->table != $meta->orgtable) + && ! $GLOBALS['dbi']->getTable($GLOBALS['db'], $meta->table)->isView() + ) { + $meta->table = $meta->orgtable; + } + + // If this field is not from the table which the unique clause needs + // to be restricted to. + if ($restrict_to_table && $restrict_to_table != $meta->table) { + continue; + } + + // to fix the bug where float fields (primary or not) + // can't be matched because of the imprecision of + // floating comparison, use CONCAT + // (also, the syntax "CONCAT(field) IS NULL" + // that we need on the next "if" will work) + if ($meta->type == 'real') { + $con_key = 'CONCAT(' . self::backquote($meta->table) . '.' + . self::backquote($meta->orgname) . ')'; + } else { + $con_key = self::backquote($meta->table) . '.' + . self::backquote($meta->orgname); + } // end if... else... + $condition = ' ' . $con_key . ' '; + + if (! isset($row[$i]) || is_null($row[$i])) { + $con_val = 'IS NULL'; + } else { + // timestamp is numeric on some MySQL 4.1 + // for real we use CONCAT above and it should compare to string + if ($meta->numeric + && ($meta->type != 'timestamp') + && ($meta->type != 'real') + ) { + $con_val = '= ' . $row[$i]; + } elseif ((($meta->type == 'blob') || ($meta->type == 'string')) + && stristr($field_flags, 'BINARY') + && ! empty($row[$i]) + ) { + // hexify only if this is a true not empty BLOB or a BINARY + + // do not waste memory building a too big condition + if (mb_strlen($row[$i]) < 1000) { + // use a CAST if possible, to avoid problems + // if the field contains wildcard characters % or _ + $con_val = '= CAST(0x' . bin2hex($row[$i]) . ' AS BINARY)'; + } elseif ($fields_cnt == 1) { + // when this blob is the only field present + // try settling with length comparison + $condition = ' CHAR_LENGTH(' . $con_key . ') '; + $con_val = ' = ' . mb_strlen($row[$i]); + } else { + // this blob won't be part of the final condition + $con_val = null; + } + } elseif (in_array($meta->type, self::getGISDatatypes()) + && ! empty($row[$i]) + ) { + // do not build a too big condition + if (mb_strlen($row[$i]) < 5000) { + $condition .= '=0x' . bin2hex($row[$i]) . ' AND'; + } else { + $condition = ''; + } + } elseif ($meta->type == 'bit') { + $con_val = "= b'" + . self::printableBitValue($row[$i], $meta->length) . "'"; + } else { + $con_val = '= \'' + . $GLOBALS['dbi']->escapeString($row[$i]) . '\''; + } + } + + if ($con_val != null) { + + $condition .= $con_val . ' AND'; + + if ($meta->primary_key > 0) { + $primary_key .= $condition; + $primary_key_array[$con_key] = $con_val; + } elseif ($meta->unique_key > 0) { + $unique_key .= $condition; + $unique_key_array[$con_key] = $con_val; + } + + $nonprimary_condition .= $condition; + $nonprimary_condition_array[$con_key] = $con_val; + } + } // end for + + // Correction University of Virginia 19991216: + // prefer primary or unique keys for condition, + // but use conjunction of all values if no primary key + $clause_is_unique = true; + + if ($primary_key) { + $preferred_condition = $primary_key; + $condition_array = $primary_key_array; + + } elseif ($unique_key) { + $preferred_condition = $unique_key; + $condition_array = $unique_key_array; + + } elseif (! $force_unique) { + $preferred_condition = $nonprimary_condition; + $condition_array = $nonprimary_condition_array; + $clause_is_unique = false; + } + + $where_clause = trim(preg_replace('|\s?AND$|', '', $preferred_condition)); + return(array($where_clause, $clause_is_unique, $condition_array)); + } // end function + + /** + * Generate the charset query part + * + * @param string $collation Collation + * @param boolean optional $override force 'CHARACTER SET' keyword + * + * @return string + */ + static function getCharsetQueryPart($collation, $override = false) + { + list($charset) = explode('_', $collation); + $keyword = ' CHARSET='; + + if ($override) { + $keyword = ' CHARACTER SET '; + } + return $keyword . $charset + . ($charset == $collation ? '' : ' COLLATE ' . $collation); + } + + /** + * Generate a button or image tag + * + * @param string $button_name name of button element + * @param string $button_class class of button or image element + * @param string $text text to display + * @param string $image image to display + * @param string $value value + * + * @return string html content + * + * @access public + */ + public static function getButtonOrImage( + $button_name, $button_class, $text, $image, $value = '' + ) { + if ($value == '') { + $value = $text; + } + if ($GLOBALS['cfg']['ActionLinksMode'] == 'text') { + return ' ' . "\n"; + } + return '' . "\n"; + } // end function + + /** + * Generate a pagination selector for browsing resultsets + * + * @param string $name The name for the request parameter + * @param int $rows Number of rows in the pagination set + * @param int $pageNow current page number + * @param int $nbTotalPage number of total pages + * @param int $showAll If the number of pages is lower than this + * variable, no pages will be omitted in pagination + * @param int $sliceStart How many rows at the beginning should always + * be shown? + * @param int $sliceEnd How many rows at the end should always be shown? + * @param int $percent Percentage of calculation page offsets to hop to a + * next page + * @param int $range Near the current page, how many pages should + * be considered "nearby" and displayed as well? + * @param string $prompt The prompt to display (sometimes empty) + * + * @return string + * + * @access public + */ + public static function pageselector( + $name, $rows, $pageNow = 1, $nbTotalPage = 1, $showAll = 200, + $sliceStart = 5, + $sliceEnd = 5, $percent = 20, $range = 10, $prompt = '' + ) { + $increment = floor($nbTotalPage / $percent); + $pageNowMinusRange = ($pageNow - $range); + $pageNowPlusRange = ($pageNow + $range); + + $gotopage = $prompt . ' '; + + return $gotopage; + } // end function + + + /** + * Calculate page number through position + * @param int $pos position of first item + * @param int $max_count number of items per page + * @return int $page_num + * @access public + */ + public static function getPageFromPosition($pos, $max_count) + { + return floor($pos / $max_count) + 1; + } + + /** + * Prepare navigation for a list + * + * @param int $count number of elements in the list + * @param int $pos current position in the list + * @param array $_url_params url parameters + * @param string $script script name for form target + * @param string $frame target frame + * @param int $max_count maximum number of elements to display from + * the list + * @param string $name the name for the request parameter + * @param string[] $classes additional classes for the container + * + * @return string $list_navigator_html the html content + * + * @access public + * + * @todo use $pos from $_url_params + */ + + public static function getListNavigator( + $count, $pos, array $_url_params, $script, $frame, $max_count, $name = 'pos', + $classes = array() + ) { + + // This is often coming from $cfg['MaxTableList'] and + // people sometimes set it to empty string + $max_count = intval($max_count); + if ($max_count <= 0) { + $max_count = 250; + } + + $class = $frame == 'frame_navigation' ? ' class="ajax"' : ''; + + $list_navigator_html = ''; + + if ($max_count < $count) { + + $classes[] = 'pageselector'; + $list_navigator_html .= '
      '; + + if ($frame != 'frame_navigation') { + $list_navigator_html .= __('Page number:'); + } + + // Move to the beginning or to the previous page + if ($pos > 0) { + $caption1 = ''; $caption2 = ''; + if (self::showIcons('TableNavigationLinksMode')) { + $caption1 .= '<< '; + $caption2 .= '< '; + } + if (self::showText('TableNavigationLinksMode')) { + $caption1 .= _pgettext('First page', 'Begin'); + $caption2 .= _pgettext('Previous page', 'Previous'); + } + $title1 = ' title="' . _pgettext('First page', 'Begin') . '"'; + $title2 = ' title="' . _pgettext('Previous page', 'Previous') . '"'; + + $_url_params[$name] = 0; + $list_navigator_html .= '' . $caption1 + . ''; + + $_url_params[$name] = $pos - $max_count; + $list_navigator_html .= ' ' + . $caption2 . ''; + } + + $list_navigator_html .= '
      '; + + $list_navigator_html .= Url::getHiddenInputs($_url_params); + $list_navigator_html .= self::pageselector( + $name, + $max_count, + self::getPageFromPosition($pos, $max_count), + ceil($count / $max_count) + ); + $list_navigator_html .= '
      '; + + if ($pos + $max_count < $count) { + $caption3 = ''; $caption4 = ''; + if (self::showText('TableNavigationLinksMode')) { + $caption3 .= _pgettext('Next page', 'Next'); + $caption4 .= _pgettext('Last page', 'End'); + } + if (self::showIcons('TableNavigationLinksMode')) { + $caption3 .= ' >'; + $caption4 .= ' >>'; + if (! self::showText('TableNavigationLinksMode')) { + + } + } + $title3 = ' title="' . _pgettext('Next page', 'Next') . '"'; + $title4 = ' title="' . _pgettext('Last page', 'End') . '"'; + + $_url_params[$name] = $pos + $max_count; + $list_navigator_html .= '' . $caption3 + . ''; + + $_url_params[$name] = floor($count / $max_count) * $max_count; + if ($_url_params[$name] == $count) { + $_url_params[$name] = $count - $max_count; + } + + $list_navigator_html .= ' ' + . $caption4 . ''; + } + $list_navigator_html .= '
      ' . "\n"; + } + + return $list_navigator_html; + } + + /** + * replaces %u in given path with current user name + * + * example: + * + * $user_dir = userDir('/var/pma_tmp/%u/'); // '/var/pma_tmp/root/' + * + * + * + * @param string $dir with wildcard for user + * + * @return string per user directory + */ + public static function userDir($dir) + { + // add trailing slash + if (mb_substr($dir, -1) != '/') { + $dir .= '/'; + } + + return str_replace('%u', Core::securePath($GLOBALS['cfg']['Server']['user']), $dir); + } + + /** + * returns html code for db link to default db page + * + * @param string $database database + * + * @return string html link to default db page + */ + public static function getDbLink($database = null) + { + if (strlen($database) === 0) { + if (strlen($GLOBALS['db']) === 0) { + return ''; + } + $database = $GLOBALS['db']; + } else { + $database = self::unescapeMysqlWildcards($database); + } + + return '' . htmlspecialchars($database) . ''; + } + + /** + * Prepare a lightbulb hint explaining a known external bug + * that affects a functionality + * + * @param string $functionality localized message explaining the func. + * @param string $component 'mysql' (eventually, 'php') + * @param string $minimum_version of this component + * @param string $bugref bug reference for this component + * + * @return String + */ + public static function getExternalBug( + $functionality, $component, $minimum_version, $bugref + ) { + $ext_but_html = ''; + if (($component == 'mysql') && ($GLOBALS['dbi']->getVersion() < $minimum_version)) { + $ext_but_html .= self::showHint( + sprintf( + __('The %s functionality is affected by a known bug, see %s'), + $functionality, + Core::linkURL('https://bugs.mysql.com/') . $bugref + ) + ); + } + return $ext_but_html; + } + + /** + * Generates a set of radio HTML fields + * + * @param string $html_field_name the radio HTML field + * @param array $choices the choices values and labels + * @param string $checked_choice the choice to check by default + * @param boolean $line_break whether to add HTML line break after a choice + * @param boolean $escape_label whether to use htmlspecialchars() on label + * @param string $class enclose each choice with a div of this class + * @param string $id_prefix prefix for the id attribute, name will be + * used if this is not supplied + * + * @return string set of html radio fiels + */ + public static function getRadioFields( + $html_field_name, array $choices, $checked_choice = '', + $line_break = true, $escape_label = true, $class = '', + $id_prefix = '' + ) { + $radio_html = ''; + + foreach ($choices as $choice_value => $choice_label) { + + if (! $id_prefix) { + $id_prefix = $html_field_name; + } + $html_field_id = $id_prefix . '_' . $choice_value; + + if ($choice_value == $checked_choice){ + $checked = 1; + } + else{ + $checked = 0; + } + $radio_html .= Template::get('radio_fields')->render([ + 'class' => $class, + 'html_field_name' => $html_field_name, + 'html_field_id' => $html_field_id, + 'choice_value' => $choice_value, + 'is_line_break' => $line_break, + 'choice_label' => $choice_label, + 'escape_label' => $escape_label, + 'checked' => $checked + ]); + } + + return $radio_html; + + } + + /** + * Generates and returns an HTML dropdown + * + * @param string $select_name name for the select element + * @param array $choices choices values + * @param string $active_choice the choice to select by default + * @param string $id id of the select element; can be different in + * case the dropdown is present more than once + * on the page + * @param string $class class for the select element + * @param string $placeholder Placeholder for dropdown if nothing else + * is selected + * + * @return string html content + * + * @todo support titles + */ + public static function getDropdown( + $select_name, array $choices, $active_choice, $id, $class = '', $placeholder = null + ) { + $resultOptions = []; + $selected = false; + + foreach ($choices as $one_choice_value => $one_choice_label) { + $resultOptions[$one_choice_value]['value'] = $one_choice_value; + $resultOptions[$one_choice_value]['selected'] = false; + + if ($one_choice_value == $active_choice) { + $resultOptions[$one_choice_value]['selected'] = true; + $selected = true; + } + $resultOptions[$one_choice_value]['label'] = $one_choice_label; + } + return Template::get('dropdown')->render([ + 'select_name' => $select_name, + 'id' => $id, + 'class' => $class, + 'placeholder' => $placeholder, + 'selected' => $selected, + 'result_options' => $resultOptions, + ]); + } + + /** + * Generates a slider effect (jQjuery) + * Takes care of generating the initial
      and the link + * controlling the slider; you have to generate the
      yourself + * after the sliding section. + * + * @param string $id the id of the
      on which to apply the effect + * @param string $message the message to show as a link + * @param string|null $overrideDefault override InitialSlidersState config + * + * @return string html div element + * + */ + public static function getDivForSliderEffect($id = '', $message = '', $overrideDefault = null) + { + return Template::get('div_for_slider_effect')->render([ + 'id' => $id, + 'initial_sliders_state' => ($overrideDefault != null) ? $overrideDefault : $GLOBALS['cfg']['InitialSlidersState'], + 'message' => $message, + ]); + } + + /** + * Creates an AJAX sliding toggle button + * (or and equivalent form when AJAX is disabled) + * + * @param string $action The URL for the request to be executed + * @param string $select_name The name for the dropdown box + * @param array $options An array of options (see PhpMyAdmin\Rte\Footer) + * @param string $callback A JS snippet to execute when the request is + * successfully processed + * + * @return string HTML code for the toggle button + */ + public static function toggleButton($action, $select_name, array $options, $callback) + { + // Do the logic first + $link = "$action&" . urlencode($select_name) . "="; + $link_on = $link . urlencode($options[1]['value']); + $link_off = $link . urlencode($options[0]['value']); + + if ($options[1]['selected'] == true) { + $state = 'on'; + } elseif ($options[0]['selected'] == true) { + $state = 'off'; + } else { + $state = 'on'; + } + + return Template::get('toggle_button')->render( + [ + 'pma_theme_image' => $GLOBALS['pmaThemeImage'], + 'text_dir' => $GLOBALS['text_dir'], + 'link_on' => $link_on, + 'link_off' => $link_off, + 'toggle_on' => $options[1]['label'], + 'toggle_off' => $options[0]['label'], + 'callback' => $callback, + 'state' => $state + ]); + } // end toggleButton() + + /** + * Clears cache content which needs to be refreshed on user change. + * + * @return void + */ + public static function clearUserCache() + { + self::cacheUnset('is_superuser'); + self::cacheUnset('is_createuser'); + self::cacheUnset('is_grantuser'); + } + + /** + * Calculates session cache key + * + * @return string + */ + public static function cacheKey() + { + if (isset($GLOBALS['cfg']['Server']['user'])) { + return 'server_' . $GLOBALS['server'] . '_' . $GLOBALS['cfg']['Server']['user']; + } + + return 'server_' . $GLOBALS['server']; + } + + /** + * Verifies if something is cached in the session + * + * @param string $var variable name + * + * @return boolean + */ + public static function cacheExists($var) + { + return isset($_SESSION['cache'][self::cacheKey()][$var]); + } + + /** + * Gets cached information from the session + * + * @param string $var variable name + * @param \Closure $callback callback to fetch the value + * + * @return mixed + */ + public static function cacheGet($var, $callback = null) + { + if (self::cacheExists($var)) { + return $_SESSION['cache'][self::cacheKey()][$var]; + } + + if ($callback) { + $val = $callback(); + self::cacheSet($var, $val); + return $val; + } + return null; + } + + /** + * Caches information in the session + * + * @param string $var variable name + * @param mixed $val value + * + * @return mixed + */ + public static function cacheSet($var, $val = null) + { + $_SESSION['cache'][self::cacheKey()][$var] = $val; + } + + /** + * Removes cached information from the session + * + * @param string $var variable name + * + * @return void + */ + public static function cacheUnset($var) + { + unset($_SESSION['cache'][self::cacheKey()][$var]); + } + + /** + * Converts a bit value to printable format; + * in MySQL a BIT field can be from 1 to 64 bits so we need this + * function because in PHP, decbin() supports only 32 bits + * on 32-bit servers + * + * @param integer $value coming from a BIT field + * @param integer $length length + * + * @return string the printable value + */ + public static function printableBitValue($value, $length) + { + // if running on a 64-bit server or the length is safe for decbin() + if (PHP_INT_SIZE == 8 || $length < 33) { + $printable = decbin($value); + } else { + // FIXME: does not work for the leftmost bit of a 64-bit value + $i = 0; + $printable = ''; + while ($value >= pow(2, $i)) { + ++$i; + } + if ($i != 0) { + --$i; + } + + while ($i >= 0) { + if ($value - pow(2, $i) < 0) { + $printable = '0' . $printable; + } else { + $printable = '1' . $printable; + $value = $value - pow(2, $i); + } + --$i; + } + $printable = strrev($printable); + } + $printable = str_pad($printable, $length, '0', STR_PAD_LEFT); + return $printable; + } + + /** + * Verifies whether the value contains a non-printable character + * + * @param string $value value + * + * @return integer + */ + public static function containsNonPrintableAscii($value) + { + return preg_match('@[^[:print:]]@', $value); + } + + /** + * Converts a BIT type default value + * for example, b'010' becomes 010 + * + * @param string $bit_default_value value + * + * @return string the converted value + */ + public static function convertBitDefaultValue($bit_default_value) + { + return rtrim(ltrim(htmlspecialchars_decode($bit_default_value, ENT_QUOTES), "b'"), "'"); + } + + /** + * Extracts the various parts from a column spec + * + * @param string $columnspec Column specification + * + * @return array associative array containing type, spec_in_brackets + * and possibly enum_set_values (another array) + */ + public static function extractColumnSpec($columnspec) + { + $first_bracket_pos = mb_strpos($columnspec, '('); + if ($first_bracket_pos) { + $spec_in_brackets = chop( + mb_substr( + $columnspec, + $first_bracket_pos + 1, + mb_strrpos($columnspec, ')') - $first_bracket_pos - 1 + ) + ); + // convert to lowercase just to be sure + $type = mb_strtolower( + chop(mb_substr($columnspec, 0, $first_bracket_pos)) + ); + } else { + // Split trailing attributes such as unsigned, + // binary, zerofill and get data type name + $type_parts = explode(' ', $columnspec); + $type = mb_strtolower($type_parts[0]); + $spec_in_brackets = ''; + } + + if ('enum' == $type || 'set' == $type) { + // Define our working vars + $enum_set_values = self::parseEnumSetValues($columnspec, false); + $printtype = $type + . '(' . str_replace("','", "', '", $spec_in_brackets) . ')'; + $binary = false; + $unsigned = false; + $zerofill = false; + } else { + $enum_set_values = array(); + + /* Create printable type name */ + $printtype = mb_strtolower($columnspec); + + // Strip the "BINARY" attribute, except if we find "BINARY(" because + // this would be a BINARY or VARBINARY column type; + // by the way, a BLOB should not show the BINARY attribute + // because this is not accepted in MySQL syntax. + if (preg_match('@binary@', $printtype) + && ! preg_match('@binary[\(]@', $printtype) + ) { + $printtype = preg_replace('@binary@', '', $printtype); + $binary = true; + } else { + $binary = false; + } + + $printtype = preg_replace( + '@zerofill@', '', $printtype, -1, $zerofill_cnt + ); + $zerofill = ($zerofill_cnt > 0); + $printtype = preg_replace( + '@unsigned@', '', $printtype, -1, $unsigned_cnt + ); + $unsigned = ($unsigned_cnt > 0); + $printtype = trim($printtype); + } + + $attribute = ' '; + if ($binary) { + $attribute = 'BINARY'; + } + if ($unsigned) { + $attribute = 'UNSIGNED'; + } + if ($zerofill) { + $attribute = 'UNSIGNED ZEROFILL'; + } + + $can_contain_collation = false; + if (! $binary + && preg_match( + "@^(char|varchar|text|tinytext|mediumtext|longtext|set|enum)@", $type + ) + ) { + $can_contain_collation = true; + } + + // for the case ENUM('–','“') + $displayed_type = htmlspecialchars($printtype); + if (mb_strlen($printtype) > $GLOBALS['cfg']['LimitChars']) { + $displayed_type = ''; + $displayed_type .= htmlspecialchars( + mb_substr( + $printtype, 0, $GLOBALS['cfg']['LimitChars'] + ) . '...' + ); + $displayed_type .= ''; + } + + return array( + 'type' => $type, + 'spec_in_brackets' => $spec_in_brackets, + 'enum_set_values' => $enum_set_values, + 'print_type' => $printtype, + 'binary' => $binary, + 'unsigned' => $unsigned, + 'zerofill' => $zerofill, + 'attribute' => $attribute, + 'can_contain_collation' => $can_contain_collation, + 'displayed_type' => $displayed_type + ); + } + + /** + * Verifies if this table's engine supports foreign keys + * + * @param string $engine engine + * + * @return boolean + */ + public static function isForeignKeySupported($engine) + { + $engine = strtoupper($engine); + if (($engine == 'INNODB') || ($engine == 'PBXT')) { + return true; + } elseif ($engine == 'NDBCLUSTER' || $engine == 'NDB') { + $ndbver = strtolower( + $GLOBALS['dbi']->fetchValue("SELECT @@ndb_version_string") + ); + if (substr($ndbver, 0, 4) == 'ndb-') { + $ndbver = substr($ndbver, 4); + } + return version_compare($ndbver, 7.3, '>='); + } + + return false; + } + + /** + * Is Foreign key check enabled? + * + * @return bool + */ + public static function isForeignKeyCheck() + { + if ($GLOBALS['cfg']['DefaultForeignKeyChecks'] === 'enable') { + return true; + } elseif ($GLOBALS['cfg']['DefaultForeignKeyChecks'] === 'disable') { + return false; + } + return ($GLOBALS['dbi']->getVariable('FOREIGN_KEY_CHECKS') == 'ON'); + } + + /** + * Get HTML for Foreign key check checkbox + * + * @return string HTML for checkbox + */ + public static function getFKCheckbox() + { + return Template::get('fk_checkbox')->render([ + 'checked' => self::isForeignKeyCheck(), + ]); + } + + /** + * Handle foreign key check request + * + * @return bool Default foreign key checks value + */ + public static function handleDisableFKCheckInit() + { + $default_fk_check_value + = $GLOBALS['dbi']->getVariable('FOREIGN_KEY_CHECKS') == 'ON'; + if (isset($_REQUEST['fk_checks'])) { + if (empty($_REQUEST['fk_checks'])) { + // Disable foreign key checks + $GLOBALS['dbi']->setVariable('FOREIGN_KEY_CHECKS', 'OFF'); + } else { + // Enable foreign key checks + $GLOBALS['dbi']->setVariable('FOREIGN_KEY_CHECKS', 'ON'); + } + } // else do nothing, go with default + return $default_fk_check_value; + } + + /** + * Cleanup changes done for foreign key check + * + * @param bool $default_fk_check_value original value for 'FOREIGN_KEY_CHECKS' + * + * @return void + */ + public static function handleDisableFKCheckCleanup($default_fk_check_value) + { + $GLOBALS['dbi']->setVariable( + 'FOREIGN_KEY_CHECKS', $default_fk_check_value ? 'ON' : 'OFF' + ); + } + + /** + * Converts GIS data to Well Known Text format + * + * @param string $data GIS data + * @param bool $includeSRID Add SRID to the WKT + * + * @return string GIS data in Well Know Text format + */ + public static function asWKT($data, $includeSRID = false) + { + // Convert to WKT format + $hex = bin2hex($data); + $spatialAsText = 'ASTEXT'; + $spatialSrid = 'SRID'; + if ($GLOBALS['dbi']->getVersion() >= 50600) { + $spatialAsText = 'ST_ASTEXT'; + $spatialSrid = 'ST_SRID'; + } + $wktsql = "SELECT $spatialAsText(x'" . $hex . "')"; + if ($includeSRID) { + $wktsql .= ", $spatialSrid(x'" . $hex . "')"; + } + + $wktresult = $GLOBALS['dbi']->tryQuery( + $wktsql + ); + $wktarr = $GLOBALS['dbi']->fetchRow($wktresult, 0); + $wktval = $wktarr[0]; + + if ($includeSRID) { + $srid = $wktarr[1]; + $wktval = "'" . $wktval . "'," . $srid; + } + @$GLOBALS['dbi']->freeResult($wktresult); + + return $wktval; + } + + /** + * If the string starts with a \r\n pair (0x0d0a) add an extra \n + * + * @param string $string string + * + * @return string with the chars replaced + */ + public static function duplicateFirstNewline($string) + { + $first_occurence = mb_strpos($string, "\r\n"); + if ($first_occurence === 0) { + $string = "\n" . $string; + } + return $string; + } + + /** + * Get the action word corresponding to a script name + * in order to display it as a title in navigation panel + * + * @param string $target a valid value for $cfg['NavigationTreeDefaultTabTable'], + * $cfg['NavigationTreeDefaultTabTable2'], + * $cfg['DefaultTabTable'] or $cfg['DefaultTabDatabase'] + * + * @return string Title for the $cfg value + */ + public static function getTitleForTarget($target) + { + $mapping = array( + 'structure' => __('Structure'), + 'sql' => __('SQL'), + 'search' =>__('Search'), + 'insert' =>__('Insert'), + 'browse' => __('Browse'), + 'operations' => __('Operations'), + + // For backward compatiblity + + // Values for $cfg['DefaultTabTable'] + 'tbl_structure.php' => __('Structure'), + 'tbl_sql.php' => __('SQL'), + 'tbl_select.php' =>__('Search'), + 'tbl_change.php' =>__('Insert'), + 'sql.php' => __('Browse'), + // Values for $cfg['DefaultTabDatabase'] + 'db_structure.php' => __('Structure'), + 'db_sql.php' => __('SQL'), + 'db_search.php' => __('Search'), + 'db_operations.php' => __('Operations'), + ); + return isset($mapping[$target]) ? $mapping[$target] : false; + } + + /** + * Get the script name corresponding to a plain English config word + * in order to append in links on navigation and main panel + * + * @param string $target a valid value for + * $cfg['NavigationTreeDefaultTabTable'], + * $cfg['NavigationTreeDefaultTabTable2'], + * $cfg['DefaultTabTable'], $cfg['DefaultTabDatabase'] or + * $cfg['DefaultTabServer'] + * @param string $location one out of 'server', 'table', 'database' + * + * @return string script name corresponding to the config word + */ + public static function getScriptNameForOption($target, $location) + { + if ($location == 'server') { + // Values for $cfg['DefaultTabServer'] + switch ($target) { + case 'welcome': + return 'index.php'; + case 'databases': + return 'server_databases.php'; + case 'status': + return 'server_status.php'; + case 'variables': + return 'server_variables.php'; + case 'privileges': + return 'server_privileges.php'; + } + } elseif ($location == 'database') { + // Values for $cfg['DefaultTabDatabase'] + switch ($target) { + case 'structure': + return 'db_structure.php'; + case 'sql': + return 'db_sql.php'; + case 'search': + return 'db_search.php'; + case 'operations': + return 'db_operations.php'; + } + } elseif ($location == 'table') { + // Values for $cfg['DefaultTabTable'], + // $cfg['NavigationTreeDefaultTabTable'] and + // $cfg['NavigationTreeDefaultTabTable2'] + switch ($target) { + case 'structure': + return 'tbl_structure.php'; + case 'sql': + return 'tbl_sql.php'; + case 'search': + return 'tbl_select.php'; + case 'insert': + return 'tbl_change.php'; + case 'browse': + return 'sql.php'; + } + } + + return $target; + } + + /** + * Formats user string, expanding @VARIABLES@, accepting strftime format + * string. + * + * @param string $string Text where to do expansion. + * @param array|string $escape Function to call for escaping variable values. + * Can also be an array of: + * - the escape method name + * - the class that contains the method + * - location of the class (for inclusion) + * @param array $updates Array with overrides for default parameters + * (obtained from GLOBALS). + * + * @return string + */ + public static function expandUserString( + $string, $escape = null, array $updates = array() + ) { + /* Content */ + $vars = array(); + $vars['http_host'] = Core::getenv('HTTP_HOST'); + $vars['server_name'] = $GLOBALS['cfg']['Server']['host']; + $vars['server_verbose'] = $GLOBALS['cfg']['Server']['verbose']; + + if (empty($GLOBALS['cfg']['Server']['verbose'])) { + $vars['server_verbose_or_name'] = $GLOBALS['cfg']['Server']['host']; + } else { + $vars['server_verbose_or_name'] = $GLOBALS['cfg']['Server']['verbose']; + } + + $vars['database'] = $GLOBALS['db']; + $vars['table'] = $GLOBALS['table']; + $vars['phpmyadmin_version'] = 'phpMyAdmin ' . PMA_VERSION; + + /* Update forced variables */ + foreach ($updates as $key => $val) { + $vars[$key] = $val; + } + + /* Replacement mapping */ + /* + * The __VAR__ ones are for backward compatibility, because user + * might still have it in cookies. + */ + $replace = array( + '@HTTP_HOST@' => $vars['http_host'], + '@SERVER@' => $vars['server_name'], + '__SERVER__' => $vars['server_name'], + '@VERBOSE@' => $vars['server_verbose'], + '@VSERVER@' => $vars['server_verbose_or_name'], + '@DATABASE@' => $vars['database'], + '__DB__' => $vars['database'], + '@TABLE@' => $vars['table'], + '__TABLE__' => $vars['table'], + '@PHPMYADMIN@' => $vars['phpmyadmin_version'], + ); + + /* Optional escaping */ + if (! is_null($escape)) { + if (is_array($escape)) { + $escape_class = new $escape[1]; + $escape_method = $escape[0]; + } + foreach ($replace as $key => $val) { + if (is_array($escape)) { + $replace[$key] = $escape_class->$escape_method($val); + } else { + $replace[$key] = ($escape == 'backquote') + ? self::$escape($val) + : $escape($val); + } + } + } + + /* Backward compatibility in 3.5.x */ + if (mb_strpos($string, '@FIELDS@') !== false) { + $string = strtr($string, array('@FIELDS@' => '@COLUMNS@')); + } + + /* Fetch columns list if required */ + if (mb_strpos($string, '@COLUMNS@') !== false) { + $columns_list = $GLOBALS['dbi']->getColumns( + $GLOBALS['db'], $GLOBALS['table'] + ); + + // sometimes the table no longer exists at this point + if (! is_null($columns_list)) { + $column_names = array(); + foreach ($columns_list as $column) { + if (! is_null($escape)) { + $column_names[] = self::$escape($column['Field']); + } else { + $column_names[] = $column['Field']; + } + } + $replace['@COLUMNS@'] = implode(',', $column_names); + } else { + $replace['@COLUMNS@'] = '*'; + } + } + + /* Do the replacement */ + return strtr(strftime($string), $replace); + } + + /** + * Prepare the form used to browse anywhere on the local server for a file to + * import + * + * @param string $max_upload_size maximum upload size + * + * @return String + */ + public static function getBrowseUploadFileBlock($max_upload_size) + { + $block_html = ''; + + if ($GLOBALS['is_upload'] && ! empty($GLOBALS['cfg']['UploadDir'])) { + $block_html .= '
      {% endif %} + {% endfor %} + +
      + +
      + +
      + {% spaceless %} + + {% endspaceless %} +
      + + +
      + + +
      + +
      {# Slider div #} +
      diff --git a/php/apps/phpmyadmin49/templates/database/qbe/column_select_cell.twig b/php/apps/phpmyadmin49/templates/database/qbe/column_select_cell.twig new file mode 100644 index 00000000..071249f8 --- /dev/null +++ b/php/apps/phpmyadmin49/templates/database/qbe/column_select_cell.twig @@ -0,0 +1,11 @@ + + + diff --git a/php/apps/phpmyadmin49/templates/database/qbe/footer_options.twig b/php/apps/phpmyadmin49/templates/database/qbe/footer_options.twig new file mode 100644 index 00000000..7f43f8de --- /dev/null +++ b/php/apps/phpmyadmin49/templates/database/qbe/footer_options.twig @@ -0,0 +1,16 @@ +
      + {% if type == 'row' %} + {% trans 'Add/Delete criteria rows' %}: + {% else %} + {% trans 'Add/Delete columns' %}: + {% endif %} + +
      diff --git a/php/apps/phpmyadmin49/templates/database/qbe/sort_order_select_cell.twig b/php/apps/phpmyadmin49/templates/database/qbe/sort_order_select_cell.twig new file mode 100644 index 00000000..7a63d772 --- /dev/null +++ b/php/apps/phpmyadmin49/templates/database/qbe/sort_order_select_cell.twig @@ -0,0 +1,10 @@ + + + diff --git a/php/apps/phpmyadmin49/templates/database/qbe/sort_select_cell.twig b/php/apps/phpmyadmin49/templates/database/qbe/sort_select_cell.twig new file mode 100644 index 00000000..4f0989e8 --- /dev/null +++ b/php/apps/phpmyadmin49/templates/database/qbe/sort_select_cell.twig @@ -0,0 +1,9 @@ + + + diff --git a/php/apps/phpmyadmin49/templates/database/search/result_divs.twig b/php/apps/phpmyadmin49/templates/database/search/result_divs.twig new file mode 100644 index 00000000..9f5f19ff --- /dev/null +++ b/php/apps/phpmyadmin49/templates/database/search/result_divs.twig @@ -0,0 +1,13 @@ +{# These two table-image and table-link elements display the table name in browse search results #} +
      + +
      +{# Div for browsing results #} +
      + {# This browse-results div is used to load the browse and delete results in the db search #} +
      +
      + {# This sqlqueryform div is used to load the delete form in the db search #} +
      +{# Toggle query box link #} + diff --git a/php/apps/phpmyadmin49/templates/database/search/results.twig b/php/apps/phpmyadmin49/templates/database/search/results.twig new file mode 100644 index 00000000..efe55296 --- /dev/null +++ b/php/apps/phpmyadmin49/templates/database/search/results.twig @@ -0,0 +1,62 @@ + + + {% for row in rows %} + + + {% if row.result_count > 0 %} + {% set url_params = { + 'db': db, + 'table': row.table, + 'goto': 'db_sql.php', + 'pos': 0, + 'is_js_confirmed': 0 + } %} + + + {% else %} + + + {% endif %} + + {% endfor %} +
      + {{ 'Search results for "%s" %s:'|format( + criteria_search_string, + search_type_description + )|raw }} +
      + {% set result_message %} + {% trans %} + %1$s match in %2$s + {% plural row.result_count %} + %1$s matches in %2$s + {% endtrans %} + {% endset %} + {{ result_message|format(row.result_count, row.table)|raw }} + + + {% trans 'Browse' %} + + + + {% trans 'Delete' %} + +
      + +{% if criteria_tables|length > 1 %} +

      + {% trans %} + Total: {{ count }} match + {% plural result_total %} + Total: {{ count }} matches + {% endtrans %} +

      +{% endif %} diff --git a/php/apps/phpmyadmin49/templates/database/search/selection_form.twig b/php/apps/phpmyadmin49/templates/database/search/selection_form.twig new file mode 100644 index 00000000..2a7ed8b4 --- /dev/null +++ b/php/apps/phpmyadmin49/templates/database/search/selection_form.twig @@ -0,0 +1,63 @@ + +
      + {{ Url_getHiddenInputs(db) }} +
      + {% trans 'Search in database' %} +

      + + +

      + +
      + {% trans 'Find:' %} + {# 4th parameter set to true to add line breaks #} + {# 5th parameter set to false to avoid htmlspecialchars() escaping + in the label since we have some HTML in some labels #} + {{ Util_getRadioFields( + 'criteriaSearchType', + choices, + criteria_search_type, + true, + false + ) }} +
      + +
      + {% trans 'Inside tables:' %} +

      + + {% trans 'Select all' %} + / + + {% trans 'Unselect all' %} + +

      + +
      + +

      + {# Inputbox for column name entry #} + + +

      +
      +
      + +
      +
      +
      + +
      diff --git a/php/apps/phpmyadmin49/templates/database/structure/body_for_table_summary.twig b/php/apps/phpmyadmin49/templates/database/structure/body_for_table_summary.twig new file mode 100644 index 00000000..a06dc9ea --- /dev/null +++ b/php/apps/phpmyadmin49/templates/database/structure/body_for_table_summary.twig @@ -0,0 +1,95 @@ + + + + + {% set num_tables_trans -%} + {% trans %}%s table{% plural num_tables %}%s tables{% endtrans %} + {%- endset %} + {{ num_tables_trans|format(Util_formatNumber(num_tables, 0)) }} + + {% if server_slave_status %} + {% trans 'Replication' %} + {% endif %} + {% set sum_colspan = db_is_system_schema ? 4 : 7 %} + {% if num_favorite_tables == 0 %} + {% set sum_colspan = sum_colspan - 1 %} + {% endif %} + {% trans 'Sum' %} + {% set row_count_sum = Util_formatNumber(sum_entries, 0) %} + {# If a table shows approximate rows count, display update-all-real-count anchor. #} + {% set row_sum_url = [] %} + {% if approx_rows is defined %} + {% set row_sum_url = { + 'ajax_request': true, + 'db': db, + 'real_row_count': 'true', + 'real_row_count_all': 'true' + } %} + {% endif %} + {% if approx_rows %} + {% set cell_text -%} + ~ + {{- row_count_sum -}} + + {%- endset %} + {% else %} + {% set cell_text = row_count_sum %} + {% endif %} + {{ cell_text }} + {% if not (properties_num_columns > 1) %} + {# MySQL <= 5.5.2 #} + {% set default_engine = dbi.fetchValue('SELECT @@storage_engine;') %} + {% if default_engine is empty %} + {# MySQL >= 5.5.3 #} + {% set default_engine = dbi.fetchValue('SELECT @@default_storage_engine;') %} + {% endif %} + + + {{ default_engine }} + + + + {% if db_collation is not empty %} + + {{ db_collation }} + + {% endif %} + + {% endif %} + + {% if is_show_stats %} + {% set sum = Util_formatByteDown(sum_size, 3, 1) %} + {% set sum_formatted = sum[0] %} + {% set sum_unit = sum[1] %} + {{ sum_formatted }} {{ sum_unit }} + + {% set overhead = Util_formatByteDown(overhead_size, 3, 1) %} + {% set overhead_formatted = overhead[0] %} + {% set overhead_unit = overhead[1] %} + {{ overhead_formatted }} {{ overhead_unit }} + {% endif %} + + {% if show_charset %} + {{ db_charset }} + {% endif %} + {% if show_comment %} + + {% endif %} + {% if show_creation %} + + {{ create_time_all ? Util_localisedDate(strtotime(create_time_all)) : '-' }} + + {% endif %} + {% if show_last_update %} + + {{ update_time_all ? Util_localisedDate(strtotime(update_time_all)) : '-' }} + + {% endif %} + {% if show_last_check %} + + {{ check_time_all ? Util_localisedDate(strtotime(check_time_all)) : '-' }} + + {% endif %} + + diff --git a/php/apps/phpmyadmin49/templates/database/structure/browse_table.twig b/php/apps/phpmyadmin49/templates/database/structure/browse_table.twig new file mode 100644 index 00000000..826a599e --- /dev/null +++ b/php/apps/phpmyadmin49/templates/database/structure/browse_table.twig @@ -0,0 +1,3 @@ + + {{ title|raw }} + diff --git a/php/apps/phpmyadmin49/templates/database/structure/browse_table_label.twig b/php/apps/phpmyadmin49/templates/database/structure/browse_table_label.twig new file mode 100644 index 00000000..5cb86179 --- /dev/null +++ b/php/apps/phpmyadmin49/templates/database/structure/browse_table_label.twig @@ -0,0 +1,3 @@ + + {{ truename }} + diff --git a/php/apps/phpmyadmin49/templates/database/structure/check_all_tables.twig b/php/apps/phpmyadmin49/templates/database/structure/check_all_tables.twig new file mode 100644 index 00000000..25323f91 --- /dev/null +++ b/php/apps/phpmyadmin49/templates/database/structure/check_all_tables.twig @@ -0,0 +1,40 @@ + diff --git a/php/apps/phpmyadmin49/templates/database/structure/empty_table.twig b/php/apps/phpmyadmin49/templates/database/structure/empty_table.twig new file mode 100644 index 00000000..753556b0 --- /dev/null +++ b/php/apps/phpmyadmin49/templates/database/structure/empty_table.twig @@ -0,0 +1,4 @@ + + {{ title|raw }} + diff --git a/php/apps/phpmyadmin49/templates/database/structure/favorite_anchor.twig b/php/apps/phpmyadmin49/templates/database/structure/favorite_anchor.twig new file mode 100644 index 00000000..34a97b39 --- /dev/null +++ b/php/apps/phpmyadmin49/templates/database/structure/favorite_anchor.twig @@ -0,0 +1,7 @@ + + {{ already_favorite ? titles['Favorite']|raw : titles['NoFavorite']|raw }} + diff --git a/php/apps/phpmyadmin49/templates/database/structure/print_view_data_dictionary_link.twig b/php/apps/phpmyadmin49/templates/database/structure/print_view_data_dictionary_link.twig new file mode 100644 index 00000000..81d52035 --- /dev/null +++ b/php/apps/phpmyadmin49/templates/database/structure/print_view_data_dictionary_link.twig @@ -0,0 +1,8 @@ + diff --git a/php/apps/phpmyadmin49/templates/database/structure/search_table.twig b/php/apps/phpmyadmin49/templates/database/structure/search_table.twig new file mode 100644 index 00000000..0bc2892a --- /dev/null +++ b/php/apps/phpmyadmin49/templates/database/structure/search_table.twig @@ -0,0 +1,3 @@ + + {{ title|raw }} + diff --git a/php/apps/phpmyadmin49/templates/database/structure/show_create.twig b/php/apps/phpmyadmin49/templates/database/structure/show_create.twig new file mode 100644 index 00000000..520e9744 --- /dev/null +++ b/php/apps/phpmyadmin49/templates/database/structure/show_create.twig @@ -0,0 +1,31 @@ +
      +

      {% trans 'Showing create queries' %}

      + {% set views = [] %} + {% set tables = [] %} + {% for object in db_objects %} + {% if dbi.getTable(db, object).isView() %} + {% set views = views|merge([object]) %} + {% else %} + {% set tables = tables|merge([object]) %} + {% endif %} + {% endfor %} + {% if tables is not empty %} + {% include 'database/structure/show_create_row.twig' with { + 'db': db, + 'title': 'Tables'|trans, + 'raw_title': 'Table', + 'db_objects': tables, + 'dbi': dbi + } only %} + {% endif %} + + {% if views is not empty %} + {% include 'database/structure/show_create_row.twig' with { + 'db': db, + 'title': 'Views'|trans, + 'raw_title': 'View', + 'db_objects': views, + 'dbi': dbi + } only %} + {% endif %} +
      diff --git a/php/apps/phpmyadmin49/templates/database/structure/show_create_row.twig b/php/apps/phpmyadmin49/templates/database/structure/show_create_row.twig new file mode 100644 index 00000000..b0ce9007 --- /dev/null +++ b/php/apps/phpmyadmin49/templates/database/structure/show_create_row.twig @@ -0,0 +1,19 @@ +
      + {{ title }} + + + + + + + + + {% for object in db_objects %} + + + + + {% endfor %} + +
      {{ raw_title }}{{ 'Create %s'|trans|format(raw_title) }}
      {{ Core_mimeDefaultFunction(object) }}{{ Core_mimeDefaultFunction(dbi.getTable(db, object).showCreate()) }}
      +
      diff --git a/php/apps/phpmyadmin49/templates/database/structure/structure_table_row.twig b/php/apps/phpmyadmin49/templates/database/structure/structure_table_row.twig new file mode 100644 index 00000000..0926eb70 --- /dev/null +++ b/php/apps/phpmyadmin49/templates/database/structure/structure_table_row.twig @@ -0,0 +1,225 @@ + + + + + + {{ browse_table_label|raw }} + {{ tracking_icon|raw }} + + {% if server_slave_status %} + + {{ ignored ? Util_getImage('s_cancel', 'Not replicated'|trans) }} + {{ do ? Util_getImage('s_success', 'Replicated'|trans) }} + + {% endif %} + + {# Favorite table anchor #} + {% if num_favorite_tables > 0 %} + + {# Check if current table is already in favorite list #} + {% set fav_params = { + 'db': db, + 'ajax_request': true, + 'favorite_table': current_table['TABLE_NAME'], + ((already_favorite ? 'remove' : 'add') ~ '_favorite'): true + } %} + {% include 'database/structure/favorite_anchor.twig' with { + 'table_name_hash': md5(current_table['TABLE_NAME']), + 'db_table_name_hash': md5(db ~ '.' ~ current_table['TABLE_NAME']), + 'fav_params': fav_params, + 'already_favorite': already_favorite, + 'titles': titles + } only %} + + {% endif %} + + + {{ browse_table|raw }} + + + + {{ titles['Structure']|raw }} + + + + {{ search_table|raw }} + + + {% if not db_is_system_schema %} + + {{ titles['Insert']|raw }} + + {{ empty_table|raw }} + + + {{ titles['Drop']|raw }} + + + {% endif %} + + {% if current_table['TABLE_ROWS'] is defined + and (current_table['ENGINE'] != null or table_is_view) %} + {# Get the row count #} + {% set row_count = Util_formatNumber(current_table['TABLE_ROWS'], 0) %} + + {# Content to be appended into 'tbl_rows' cell. + If row count is approximate, display it as an anchor to get real count. #} + + {% if approx_rows %} + + + ~{{ row_count }} + + + {% else %} + {{ row_count }} + {% endif %} + {{ show_superscript|raw }} + + + {% if not (properties_num_columns > 1) %} + + {% if current_table['ENGINE'] is not empty %} + {{ current_table['ENGINE'] }} + {% elseif table_is_view %} + {% trans 'View' %} + {% endif %} + + {% if collation|length > 0 %} + + {{ collation|raw }} + + {% endif %} + {% endif %} + + {% if is_show_stats %} + + + {{ formatted_size }} + {{ unit }} + + + + {{ overhead|raw }} + + {% endif %} + + {% if not (show_charset > 1) %} + {% if charset|length > 0 %} + + {{ charset|raw }} + + {% endif %} + {% endif %} + + {% if show_comment %} + {% set comment = current_table['Comment'] %} + + {% if comment|length > limit_chars %} + + {{ comment|slice(0, limit_chars) }} + ... + + {% else %} + {{ comment }} + {% endif %} + + {% endif %} + + {% if show_creation %} + + {{ create_time ? Util_localisedDate(strtotime(create_time)) : '-' }} + + {% endif %} + + {% if show_last_update %} + + {{ update_time ? Util_localisedDate(strtotime(update_time)) : '-'}} + + {% endif %} + + {% if show_last_check %} + + {{ check_time ? Util_localisedDate(strtotime(check_time)) : '-' }} + + {% endif %} + + {% elseif table_is_view %} + - + + {% trans 'View' %} + + --- + {% if is_show_stats %} + - + - + {% endif %} + {% if show_charset %} + + {% endif %} + {% if show_comment %} + + {% endif %} + {% if show_creation %} + - + {% endif %} + {% if show_last_update %} + - + {% endif %} + {% if show_last_check %} + - + {% endif %} + + {% else %} + {% set count = 0 %} + {% if properties_num_columns %} + {% set count = count + 2 %} + {% endif %} + {% if is_show_stats %} + {% set count = count + 2 %} + {% endif %} + {% if show_charset %} + {% set count = count + 1 %} + {% endif %} + {% if show_comment %} + {% set count = count + 1 %} + {% endif %} + {% if show_creation %} + {% set count = count + 1 %} + {% endif %} + {% if show_last_update %} + {% set count = count + 1 %} + {% endif %} + {% if show_last_check %} + {% set count = count + 1 %} + {% endif %} + + {% if db_is_system_schema %} + {% set action_colspan = 3 %} + {% else %} + {% set action_colspan = 6 %} + {% endif %} + {% if num_favorite_tables > 0 %} + {% set action_colspan = action_colspan + 1 %} + {% endif %} + + {% set colspan_for_structure = action_colspan + 3 %} + + {% trans 'in use' %} + + {% endif %} + diff --git a/php/apps/phpmyadmin49/templates/database/structure/table_header.twig b/php/apps/phpmyadmin49/templates/database/structure/table_header.twig new file mode 100644 index 00000000..19818a9e --- /dev/null +++ b/php/apps/phpmyadmin49/templates/database/structure/table_header.twig @@ -0,0 +1,80 @@ +
      +{{ Url_getHiddenInputs(db) }} +
      + + + + + + {% if replication %} + + {% endif %} + + {% if db_is_system_schema %} + {% set action_colspan = 3 %} + {% else %} + {% set action_colspan = 6 %} + {% endif %} + {% if num_favorite_tables > 0 %} + {% set action_colspan = action_colspan + 1 %} + {% endif %} + + {# larger values are more interesting so default sort order is DESC #} + + {% if not (properties_num_columns > 1) %} + + + {% endif %} + + {% if is_show_stats %} + {# larger values are more interesting so default sort order is DESC #} + + {# larger values are more interesting so default sort order is DESC #} + + {% endif %} + + {% if show_charset %} + + {% endif %} + + {% if show_comment %} + + {% endif %} + + {% if show_creation %} + {# newer values are more interesting so default sort order is DESC #} + + {% endif %} + + {% if show_last_update %} + {# newer values are more interesting so default sort order is DESC #} + + {% endif %} + + {% if show_last_check %} + {# newer values are more interesting so default sort order is DESC #} + + {% endif %} + + + + {% for structure_table_row in structure_table_rows %} + {% include 'database/structure/structure_table_row.twig' with structure_table_row only %} + {% endfor %} + + {% if body_for_table_summary %} + {% include 'database/structure/body_for_table_summary.twig' with body_for_table_summary only %} + {% endif %} +
      {{ Util_sortableTableHeader('Table'|trans, 'table') }}{% trans 'Replication' %} + {% trans 'Action' %} + + {{ Util_sortableTableHeader('Rows'|trans, 'records', 'DESC') }} + {{ Util_showHint(Sanitize_sanitize( + 'May be approximate. Click on the number to get the exact count. See [doc@faq3-11]FAQ 3.11[/doc].'|trans + )) }} + {{ Util_sortableTableHeader('Type'|trans, 'type') }}{{ Util_sortableTableHeader('Collation'|trans, 'collation') }}{{ Util_sortableTableHeader('Size'|trans, 'size', 'DESC') }}{{ Util_sortableTableHeader('Overhead'|trans, 'overhead', 'DESC') }}{{ Util_sortableTableHeader('Charset'|trans, 'charset') }}{{ Util_sortableTableHeader('Comment'|trans, 'comment') }}{{ Util_sortableTableHeader('Creation'|trans, 'creation', 'DESC') }}{{ Util_sortableTableHeader('Last update'|trans, 'last_update', 'DESC') }}{{ Util_sortableTableHeader('Last check'|trans, 'last_check', 'DESC') }}
      +
      +{% if check_all_tables %} + {% include 'database/structure/check_all_tables.twig' with check_all_tables only %} +{% endif %} +
      diff --git a/php/apps/phpmyadmin49/templates/database/structure/tracking_icon.twig b/php/apps/phpmyadmin49/templates/database/structure/tracking_icon.twig new file mode 100644 index 00000000..ca7e894b --- /dev/null +++ b/php/apps/phpmyadmin49/templates/database/structure/tracking_icon.twig @@ -0,0 +1,7 @@ + + {% if is_tracked -%} + {{ Util_getImage('eye', 'Tracking is active.'|trans) }} + {%- else -%} + {{ Util_getImage('eye_grey', 'Tracking is not active.'|trans) }} + {%- endif %} + diff --git a/php/apps/phpmyadmin49/templates/database/tracking/tracked_tables.twig b/php/apps/phpmyadmin49/templates/database/tracking/tracked_tables.twig new file mode 100644 index 00000000..9f7a755f --- /dev/null +++ b/php/apps/phpmyadmin49/templates/database/tracking/tracked_tables.twig @@ -0,0 +1,107 @@ +
      +

      {% trans 'Tracked tables' %}

      + +
      + {{ Url_getHiddenInputs(db) }} + + + + + + + + + + + + + + + {% for version in versions %} + + + + + + + + + + + {% endfor %} + +
      {% trans 'Table' %}{% trans 'Last version' %}{% trans 'Created' %}{% trans 'Updated' %}{% trans 'Status' %}{% trans 'Action' %}{% trans 'Show' %}
      + + + + + {{ version.version }} + + {{ version.date_created }} + + {{ version.date_updated }} + + {{ version.status_button|raw }} + + + {{ Util_getIcon('b_drop', 'Delete tracking'|trans) }} + + + + {{ Util_getIcon('b_versions', 'Versions'|trans) }} + + + {{ Util_getIcon('b_report', 'Tracking report'|trans) }} + + + {{ Util_getIcon('b_props', 'Structure snapshot'|trans) }} + +
      + {% include 'select_all.twig' with { + 'pma_theme_image': pma_theme_image, + 'text_dir': text_dir, + 'form_name': 'trackedForm' + } only %} + {{ Util_getButtonOrImage( + 'submit_mult', + 'mult_submit', + 'Delete tracking'|trans, + 'b_drop', + 'delete_tracking' + ) }} +
      +
      diff --git a/php/apps/phpmyadmin49/templates/database/tracking/untracked_tables.twig b/php/apps/phpmyadmin49/templates/database/tracking/untracked_tables.twig new file mode 100644 index 00000000..8077c763 --- /dev/null +++ b/php/apps/phpmyadmin49/templates/database/tracking/untracked_tables.twig @@ -0,0 +1,47 @@ +

      {% trans 'Untracked tables' %}

      +
      + {{ Url_getHiddenInputs(db) }} + + + + + + + + + + {% for table_name in untracked_tables if Tracker_getVersion(db, table_name) == -1 %} + + + + + + {% endfor %} + +
      {% trans 'Table' %}{% trans 'Action' %}
      + + + + + + {{ Util_getIcon('eye', 'Track table'|trans) }} + +
      + {% include 'select_all.twig' with { + 'pma_theme_image': pma_theme_image, + 'text_dir': text_dir, + 'form_name': 'untrackedForm' + } only %} + {{ Util_getButtonOrImage( + 'submit_mult', + 'mult_submit', + 'Track table'|trans, + 'eye', + 'track' + ) }} +
      diff --git a/php/apps/phpmyadmin49/templates/display/export/format_dropdown.twig b/php/apps/phpmyadmin49/templates/display/export/format_dropdown.twig new file mode 100644 index 00000000..adda19ba --- /dev/null +++ b/php/apps/phpmyadmin49/templates/display/export/format_dropdown.twig @@ -0,0 +1,4 @@ +
      +

      {% trans 'Format:' %}

      + {{ dropdown|raw }} +
      diff --git a/php/apps/phpmyadmin49/templates/display/export/hidden_inputs.twig b/php/apps/phpmyadmin49/templates/display/export/hidden_inputs.twig new file mode 100644 index 00000000..fa01a187 --- /dev/null +++ b/php/apps/phpmyadmin49/templates/display/export/hidden_inputs.twig @@ -0,0 +1,23 @@ +{% if export_type == 'server' %} + {{ Url_getHiddenInputs('', '', 1) }} +{% elseif export_type == 'database' %} + {{ Url_getHiddenInputs(db, '', 1) }} +{% else %} + {{ Url_getHiddenInputs(db, table, 1) }} +{% endif %} + +{# Just to keep this value for possible next display of this form after saving on server #} +{% if single_table is not empty %} + +{% endif %} + + + +{# The export method (quick, custom or custom-no-form) #} + + +{% if sql_query is not empty %} + +{% endif %} + + diff --git a/php/apps/phpmyadmin49/templates/display/export/method.twig b/php/apps/phpmyadmin49/templates/display/export/method.twig new file mode 100644 index 00000000..5521f574 --- /dev/null +++ b/php/apps/phpmyadmin49/templates/display/export/method.twig @@ -0,0 +1,22 @@ +{% if export_method != 'custom-no-form' %} +
      +

      {% trans 'Export method:' %}

      +
        +
      • + + +
      • + +
      • + + +
      • +
      +
      +{% endif %} diff --git a/php/apps/phpmyadmin49/templates/display/export/option_header.twig b/php/apps/phpmyadmin49/templates/display/export/option_header.twig new file mode 100644 index 00000000..074586a7 --- /dev/null +++ b/php/apps/phpmyadmin49/templates/display/export/option_header.twig @@ -0,0 +1,12 @@ + diff --git a/php/apps/phpmyadmin49/templates/display/export/options_format.twig b/php/apps/phpmyadmin49/templates/display/export/options_format.twig new file mode 100644 index 00000000..7cfbe13b --- /dev/null +++ b/php/apps/phpmyadmin49/templates/display/export/options_format.twig @@ -0,0 +1,24 @@ +
      +

      {% trans 'Format-specific options:' %}

      +

      + {% trans 'Scroll down to fill in the options for the selected format and ignore the options for other formats.' %} +

      + {{ options|raw }} +
      + +{% if can_convert_kanji %} + {# Japanese encoding setting #} +
      +

      {% trans 'Encoding Conversion:' %}

      + {% include 'encoding/kanji_encoding_form.twig' %} +
      +{% endif %} + +
      + 0 %} + onclick="check_time_out({{ exec_time_limit }})" + {%- endif %}> +
      diff --git a/php/apps/phpmyadmin49/templates/display/export/options_output.twig b/php/apps/phpmyadmin49/templates/display/export/options_output.twig new file mode 100644 index 00000000..60d721ca --- /dev/null +++ b/php/apps/phpmyadmin49/templates/display/export/options_output.twig @@ -0,0 +1,54 @@ +
      +

      {% trans 'Output:' %}

      +
        +
      • + + +
      • + + {% if export_type != 'server' %} +
      • + + +
      • + {% endif %} + +
      • + + +
          + {% if save_dir is not empty %} + {{ options_output_save_dir|raw }} + {% endif %} + + {{ options_output_format|raw }} + + {% if is_encoding_supported %} + {{ options_output_charset|raw }} + {% endif %} + + {{ options_output_compression|raw }} + + {% if export_type == 'server' or export_type == 'database' %} + {{ options_output_separate_files|raw }} + {% endif %} +
        +
      • + + {{ options_output_radio|raw }} +
      + + ' + )|raw }} +
      diff --git a/php/apps/phpmyadmin49/templates/display/export/options_output_charset.twig b/php/apps/phpmyadmin49/templates/display/export/options_output_charset.twig new file mode 100644 index 00000000..bd316bf9 --- /dev/null +++ b/php/apps/phpmyadmin49/templates/display/export/options_output_charset.twig @@ -0,0 +1,16 @@ +
    • + + +
    • diff --git a/php/apps/phpmyadmin49/templates/display/export/options_output_compression.twig b/php/apps/phpmyadmin49/templates/display/export/options_output_compression.twig new file mode 100644 index 00000000..19059811 --- /dev/null +++ b/php/apps/phpmyadmin49/templates/display/export/options_output_compression.twig @@ -0,0 +1,24 @@ +{% if is_zip or is_gzip %} +
    • + + +
    • +{% else %} + +{% endif %} diff --git a/php/apps/phpmyadmin49/templates/display/export/options_output_format.twig b/php/apps/phpmyadmin49/templates/display/export/options_output_format.twig new file mode 100644 index 00000000..0039d87c --- /dev/null +++ b/php/apps/phpmyadmin49/templates/display/export/options_output_format.twig @@ -0,0 +1,13 @@ +
    • + + + + +
    • diff --git a/php/apps/phpmyadmin49/templates/display/export/options_output_radio.twig b/php/apps/phpmyadmin49/templates/display/export/options_output_radio.twig new file mode 100644 index 00000000..7adf21ee --- /dev/null +++ b/php/apps/phpmyadmin49/templates/display/export/options_output_radio.twig @@ -0,0 +1,7 @@ +
    • + + +
    • diff --git a/php/apps/phpmyadmin49/templates/display/export/options_output_save_dir.twig b/php/apps/phpmyadmin49/templates/display/export/options_output_save_dir.twig new file mode 100644 index 00000000..b4526aa4 --- /dev/null +++ b/php/apps/phpmyadmin49/templates/display/export/options_output_save_dir.twig @@ -0,0 +1,15 @@ +
    • + + +
    • +
    • + + +
    • diff --git a/php/apps/phpmyadmin49/templates/display/export/options_output_separate_files.twig b/php/apps/phpmyadmin49/templates/display/export/options_output_separate_files.twig new file mode 100644 index 00000000..fec85c4d --- /dev/null +++ b/php/apps/phpmyadmin49/templates/display/export/options_output_separate_files.twig @@ -0,0 +1,12 @@ +
    • + + +
    • diff --git a/php/apps/phpmyadmin49/templates/display/export/options_quick_export.twig b/php/apps/phpmyadmin49/templates/display/export/options_quick_export.twig new file mode 100644 index 00000000..b3bd159f --- /dev/null +++ b/php/apps/phpmyadmin49/templates/display/export/options_quick_export.twig @@ -0,0 +1,20 @@ +
      +

      {% trans 'Output:' %}

      +
        +
      • + + +
      • +
      • + + +
      • +
      +
      diff --git a/php/apps/phpmyadmin49/templates/display/export/options_rows.twig b/php/apps/phpmyadmin49/templates/display/export/options_rows.twig new file mode 100644 index 00000000..5a7e39ed --- /dev/null +++ b/php/apps/phpmyadmin49/templates/display/export/options_rows.twig @@ -0,0 +1,35 @@ +
      +

      {% trans 'Rows:' %}

      +
        +
      • + + +
          +
        • + + +
        • +
        • + + +
        • +
        +
      • +
      • + + +
      • +
      +
      diff --git a/php/apps/phpmyadmin49/templates/display/export/select_options.twig b/php/apps/phpmyadmin49/templates/display/export/select_options.twig new file mode 100644 index 00000000..7b153a3d --- /dev/null +++ b/php/apps/phpmyadmin49/templates/display/export/select_options.twig @@ -0,0 +1,19 @@ +
      +

      + + {% trans 'Select all' %} + + / + + {% trans 'Unselect all' %} + +

      + + +
      diff --git a/php/apps/phpmyadmin49/templates/display/export/selection.twig b/php/apps/phpmyadmin49/templates/display/export/selection.twig new file mode 100644 index 00000000..6f691f5a --- /dev/null +++ b/php/apps/phpmyadmin49/templates/display/export/selection.twig @@ -0,0 +1,10 @@ +
      + {% if export_type == 'server' %} +

      {% trans 'Databases:' %}

      + {% elseif export_type == 'database' %} +

      {% trans 'Tables:' %}

      + {% endif %} + {% if multi_values is not empty %} + {{ multi_values|raw }} + {% endif %} +
      diff --git a/php/apps/phpmyadmin49/templates/display/export/template_loading.twig b/php/apps/phpmyadmin49/templates/display/export/template_loading.twig new file mode 100644 index 00000000..e1f57d83 --- /dev/null +++ b/php/apps/phpmyadmin49/templates/display/export/template_loading.twig @@ -0,0 +1,27 @@ +
      +

      {% trans 'Export templates:' %}

      + +
      +
      +

      {% trans 'New template:' %}

      + + +
      +
      + +
      +
      +

      {% trans 'Existing templates:' %}

      + + + + +
      +
      + +
      +
      diff --git a/php/apps/phpmyadmin49/templates/display/export/template_options.twig b/php/apps/phpmyadmin49/templates/display/export/template_options.twig new file mode 100644 index 00000000..ddcd4f5b --- /dev/null +++ b/php/apps/phpmyadmin49/templates/display/export/template_options.twig @@ -0,0 +1,7 @@ + + +{% for template in templates %} + +{% endfor %} diff --git a/php/apps/phpmyadmin49/templates/display/import/import.twig b/php/apps/phpmyadmin49/templates/display/import/import.twig new file mode 100644 index 00000000..331045a7 --- /dev/null +++ b/php/apps/phpmyadmin49/templates/display/import/import.twig @@ -0,0 +1,194 @@ + +
      +
      + ajax clock + + + +
      + + + {% if import_type == 'server' %} + {{ Url_getHiddenInputs('', '', 1) }} + {% elseif import_type == 'database' %} + {{ Url_getHiddenInputs(db, '', 1) }} + {% else %} + {{ Url_getHiddenInputs(db, table, 1) }} + {% endif %} + + + + +
      +

      {% trans 'File to import:' %}

      + + {# We don't have show anything about compression, when no supported #} + {% if compressions is not empty %} +
      +

      + {{ 'File may be compressed (%s) or uncompressed.'|trans|format(compressions|join(', ')) }} +
      + {% trans 'A compressed file\'s name must end in .[format].[compression]. Example: .sql.zip' %} +

      +
      + {% endif %} + +
      + {% if is_upload and upload_dir is not empty %} +
        +
      • + + {{ Util_getBrowseUploadFileBlock(max_upload_size) }} + {% trans 'You may also drag and drop a file on any page.' %} +
      • +
      • + + {{ Util_getSelectUploadFileBlock( + import_list, + upload_dir + ) }} +
      • +
      + {% elseif is_upload %} + {{ Util_getBrowseUploadFileBlock(max_upload_size) }} +

      {% trans 'You may also drag and drop a file on any page.' %}

      + {% elseif not is_upload %} + {{ Message_notice('File uploads are not allowed on this server.'|trans) }} + {% elseif upload_dir is not empty %} + {{ Util_getSelectUploadFileBlock( + import_list, + upload_dir + ) }} + {% endif %} +
      + +
      + {# Charset of file #} + + {% if is_encoding_supported %} + + {% else %} + {{ Charsets_getCharsetDropdownBox( + dbi, + disable_is, + 'charset_of_file', + 'charset_of_file', + 'utf8', + false + ) }} + {% endif %} +
      +
      + +
      +

      {% trans 'Partial import:' %}

      + + {% if timeout_passed is defined and timeout_passed %} +
      + + {{ 'Previous import timed out, after resubmitting will continue from position %d.'|trans|format(offset) }} +
      + {% endif %} + +
      + + +
      + + {% if not (timeout_passed is defined and timeout_passed) %} +
      + + +
      + {% else %} + {# If timeout has passed, + do not show the Skip dialog to avoid the risk of someone + entering a value here that would interfere with "skip" #} + + {% endif %} +
      + +
      +

      {% trans 'Other options:' %}

      +
      + {{ Util_getFKCheckbox() }} +
      +
      + +
      +

      {% trans 'Format:' %}

      + {{ Plugins_getChoice('Import', 'format', import_list) }} +
      +
      + +
      +

      {% trans 'Format-specific options:' %}

      +

      + {% trans 'Scroll down to fill in the options for the selected format and ignore the options for other formats.' %} +

      + {{ Plugins_getOptions('Import', import_list) }} +
      +
      + + {# Japanese encoding setting #} + {% if can_convert_kanji %} +
      +

      {% trans 'Encoding Conversion:' %}

      + {% include 'encoding/kanji_encoding_form.twig' %} +
      + {% endif %} + +
      + +
      +
      +
      diff --git a/php/apps/phpmyadmin49/templates/display/import/javascript.twig b/php/apps/phpmyadmin49/templates/display/import/javascript.twig new file mode 100644 index 00000000..01bd199f --- /dev/null +++ b/php/apps/phpmyadmin49/templates/display/import/javascript.twig @@ -0,0 +1,171 @@ +$( function() { + {# Add event when user click on "Go" button #} + $("#buttonGo").bind("click", function() { + {# Hide form #} + $("#upload_form_form").css("display", "none"); + + {% if handler != 'PhpMyAdmin\\Plugins\\Import\\Upload\\UploadNoplugin' %} + {# Some variable for javascript #} + {% set ajax_url = 'import_status.php?id=' ~ upload_id ~ '&' ~ Url_getCommonRaw({ + 'import_status': 1 + }) %} + {% set promot_str = Sanitize_jsFormat( + 'The file being uploaded is probably larger than the maximum allowed size or this is a known bug in webkit based (Safari, Google Chrome, Arora etc.) browsers.'|trans, + false + ) %} + {% set statustext_str = Sanitize_escapeJsString('%s of %s'|trans) %} + {% set second_str = Sanitize_jsFormat('%s/sec.'|trans, false) %} + {% set remaining_min = Sanitize_jsFormat('About %MIN min. %SEC sec. remaining.'|trans, false) %} + {% set remaining_second = Sanitize_jsFormat('About %SEC sec. remaining.'|trans, false) %} + {% set processed_str = Sanitize_jsFormat( + 'The file is being processed, please be patient.'|trans, + false + ) %} + {% set import_url = Url_getCommonRaw({'import_status': 1}) %} + + {% set upload_html %} + {% spaceless %} +
      +
      +
      +
      +
      +
      +
      +
      + ajax clock {{ Sanitize_jsFormat('Uploading your import file…'|trans, false) -}} +
      +
      +
      + {% endspaceless %} + {% endset %} + + {# Start output #} + var finished = false; + var percent = 0.0; + var total = 0; + var complete = 0; + var original_title = parent && parent.document ? parent.document.title : false; + var import_start; + + var perform_upload = function () { + new $.getJSON( + "{{ ajax_url|raw }}", + {}, + function(response) { + finished = response.finished; + percent = response.percent; + total = response.total; + complete = response.complete; + + if (total==0 && complete==0 && percent==0) { + $("#upload_form_status_info").html('ajax clock {{ promot_str|raw }}'); + $("#upload_form_status").css("display", "none"); + } else { + var now = new Date(); + now = Date.UTC( + now.getFullYear(), + now.getMonth(), + now.getDate(), + now.getHours(), + now.getMinutes(), + now.getSeconds()) + + now.getMilliseconds() - 1000; + var statustext = PMA_sprintf( + "{{ statustext_str|raw }}", + formatBytes( + complete, 1, PMA_messages.strDecimalSeparator + ), + formatBytes( + total, 1, PMA_messages.strDecimalSeparator + ) + ); + + if ($("#importmain").is(":visible")) { + {# Show progress UI #} + $("#importmain").hide(); + $("#import_form_status") + .html('{{ upload_html|raw }}') + .show(); + import_start = now; + } + else if (percent > 9 || complete > 2000000) { + {# Calculate estimated time #} + var used_time = now - import_start; + var seconds = parseInt(((total - complete) / complete) * used_time / 1000); + var speed = PMA_sprintf( + "{{ second_str|raw }}", + formatBytes(complete / used_time * 1000, 1, PMA_messages.strDecimalSeparator) + ); + + var minutes = parseInt(seconds / 60); + seconds %= 60; + var estimated_time; + if (minutes > 0) { + estimated_time = "{{ remaining_min|raw }}" + .replace("%MIN", minutes) + .replace("%SEC", seconds); + } + else { + estimated_time = "{{ remaining_second|raw }}" + .replace("%SEC", seconds); + } + + statustext += "
      " + speed + "

      " + estimated_time; + } + + var percent_str = Math.round(percent) + "%"; + $("#status").animate({width: percent_str}, 150); + $(".percentage").text(percent_str); + + {# Show percent in window title #} + if (original_title !== false) { + parent.document.title + = percent_str + " - " + original_title; + } + else { + document.title + = percent_str + " - " + original_title; + } + $("#statustext").html(statustext); + } + + if (finished == true) { + if (original_title !== false) { + parent.document.title = original_title; + } + else { + document.title = original_title; + } + $("#importmain").hide(); + {# Loads the message, either success or mysql error #} + $("#import_form_status") + .html('ajax clock {{ processed_str|raw }}') + .show(); + $("#import_form_status").load("import_status.php?message=true&{{ import_url|raw }}"); + PMA_reloadNavigation(); + + {# If finished #} + } + else { + setTimeout(perform_upload, 1000); + } + }); + }; + setTimeout(perform_upload, 1000); + {% else %} + {# No plugin available #} + {% set image_tag -%} + ajax clock + {{- Sanitize_jsFormat( + 'Please be patient, the file is being uploaded. Details about the upload are not available.'|trans, + false + ) -}} + {{- Util_showDocu('faq', 'faq2-9') -}} + {%- endset %} + $('#upload_form_status_info').html('{{ image_tag|raw }}'); + $("#upload_form_status").css("display", "none"); + {% endif %} + }); +}); diff --git a/php/apps/phpmyadmin49/templates/display/results/additional_fields.twig b/php/apps/phpmyadmin49/templates/display/results/additional_fields.twig new file mode 100644 index 00000000..745b3bbd --- /dev/null +++ b/php/apps/phpmyadmin49/templates/display/results/additional_fields.twig @@ -0,0 +1,14 @@ + + +{# Do not change the position when changing the number of rows #} + + +{% trans 'Number of rows:' %} +{{ Util_getDropdown( + 'session_max_rows', + number_of_rows_choices, + max_rows, + '', + 'autosubmit', + number_of_rows_placeholder +) }} diff --git a/php/apps/phpmyadmin49/templates/display/results/comment_for_row.twig b/php/apps/phpmyadmin49/templates/display/results/comment_for_row.twig new file mode 100644 index 00000000..5380b71d --- /dev/null +++ b/php/apps/phpmyadmin49/templates/display/results/comment_for_row.twig @@ -0,0 +1,10 @@ +{% if comments_map[fields_meta.table] is defined + and comments_map[fields_meta.table][fields_meta.name] is defined %} + + {% if comments_map[fields_meta.table][fields_meta.name]|length > limit_chars %} + {{ comments_map[fields_meta.table][fields_meta.name]|slice(0, limit_chars) }}… + {% else %} + {{ comments_map[fields_meta.table][fields_meta.name] }} + {% endif %} + +{% endif %} diff --git a/php/apps/phpmyadmin49/templates/display/results/empty_display.twig b/php/apps/phpmyadmin49/templates/display/results/empty_display.twig new file mode 100644 index 00000000..cd43ebc7 --- /dev/null +++ b/php/apps/phpmyadmin49/templates/display/results/empty_display.twig @@ -0,0 +1 @@ + diff --git a/php/apps/phpmyadmin49/templates/display/results/multi_row_operations_form.twig b/php/apps/phpmyadmin49/templates/display/results/multi_row_operations_form.twig new file mode 100644 index 00000000..bf87c7e0 --- /dev/null +++ b/php/apps/phpmyadmin49/templates/display/results/multi_row_operations_form.twig @@ -0,0 +1,12 @@ +{% if delete_link == delete_row or delete_link == kill_process %} +
      + {{ Url_getHiddenInputs(db, table, 1) }} + +{% endif %} + +
      + diff --git a/php/apps/phpmyadmin49/templates/display/results/null_display.twig b/php/apps/phpmyadmin49/templates/display/results/null_display.twig new file mode 100644 index 00000000..f2ea2e5f --- /dev/null +++ b/php/apps/phpmyadmin49/templates/display/results/null_display.twig @@ -0,0 +1,7 @@ + diff --git a/php/apps/phpmyadmin49/templates/display/results/options_block.twig b/php/apps/phpmyadmin49/templates/display/results/options_block.twig new file mode 100644 index 00000000..465f9e34 --- /dev/null +++ b/php/apps/phpmyadmin49/templates/display/results/options_block.twig @@ -0,0 +1,117 @@ + + {{ Url_getHiddenInputs({ + 'db': db, + 'table': table, + 'sql_query': sql_query, + 'goto': goto, + 'display_options_form': 1 + }) }} + + {{ Util_getDivForSliderEffect('', 'Options'|trans) }} +
      +
      + {# pftext means "partial or full texts" (done to reduce line lengths #} + {{ Util_getRadioFields( + 'pftext', + { + 'P': 'Partial texts'|trans, + 'F': 'Full texts'|trans + }, + pftext, + true, + true, + '', + 'pftext_' ~ unique_id + ) }} +
      + + {% if relwork and displaywork %} +
      + {{ Util_getRadioFields( + 'relational_display', + { + 'K': 'Relational key'|trans, + 'D': 'Display column for relationships'|trans + }, + relational_display, + true, + true, + '', + 'relational_display_' ~ unique_id + ) }} +
      + {% endif %} + +
      + {% include 'checkbox.twig' with { + 'html_field_name': 'display_binary', + 'label': 'Show binary contents'|trans, + 'checked': display_binary is not empty, + 'onclick': false, + 'html_field_id': 'display_binary_' ~ unique_id + } only %} + {% include 'checkbox.twig' with { + 'html_field_name': 'display_blob', + 'label': 'Show BLOB contents'|trans, + 'checked': display_blob is not empty, + 'onclick': false, + 'html_field_id': 'display_blob_' ~ unique_id + } only %} +
      + + {# I would have preferred to name this "display_transformation". + This is the only way I found to be able to keep this setting sticky + per SQL query, and at the same time have a default that displays + the transformations. #} +
      + {% include 'checkbox.twig' with { + 'html_field_name': 'hide_transformation', + 'label': 'Hide browser transformation'|trans, + 'checked': hide_transformation is not empty, + 'onclick': false, + 'html_field_id': 'hide_transformation_' ~ unique_id + } only %} +
      + + + {% if possible_as_geometry %} +
      + {{ Util_getRadioFields( + 'geoOption', + { + 'GEOM': 'Geometry'|trans, + 'WKT': 'Well Known Text'|trans, + 'WKB': 'Well Known Binary'|trans + }, + geo_option, + true, + true, + '', + 'geoOption_' ~ unique_id + ) }} +
      + {% else %} +
      + {{ possible_as_geometry }} + {{ Util_getRadioFields( + 'geoOption', + { + 'WKT': 'Well Known Text'|trans, + 'WKB': 'Well Known Binary'|trans + }, + geo_option, + true, + true, + '', + 'geoOption_' ~ unique_id + ) }} +
      + {% endif %} +
      +
      + +
      + +
      + {# slider effect div #} + diff --git a/php/apps/phpmyadmin49/templates/display/results/show_all_checkbox.twig b/php/apps/phpmyadmin49/templates/display/results/show_all_checkbox.twig new file mode 100644 index 00000000..b2674d38 --- /dev/null +++ b/php/apps/phpmyadmin49/templates/display/results/show_all_checkbox.twig @@ -0,0 +1,13 @@ + diff --git a/php/apps/phpmyadmin49/templates/display/results/table_navigation_button.twig b/php/apps/phpmyadmin49/templates/display/results/table_navigation_button.twig new file mode 100644 index 00000000..eb4fa42e --- /dev/null +++ b/php/apps/phpmyadmin49/templates/display/results/table_navigation_button.twig @@ -0,0 +1,12 @@ + diff --git a/php/apps/phpmyadmin49/templates/display/results/value_display.twig b/php/apps/phpmyadmin49/templates/display/results/value_display.twig new file mode 100644 index 00000000..35640b91 --- /dev/null +++ b/php/apps/phpmyadmin49/templates/display/results/value_display.twig @@ -0,0 +1,3 @@ + diff --git a/php/apps/phpmyadmin49/templates/div_for_slider_effect.twig b/php/apps/phpmyadmin49/templates/div_for_slider_effect.twig new file mode 100644 index 00000000..64539993 --- /dev/null +++ b/php/apps/phpmyadmin49/templates/div_for_slider_effect.twig @@ -0,0 +1,16 @@ +{% if initial_sliders_state == 'disabled' %} + +{% else %} + {# + Bad hack on the next line. document.write() conflicts with jQuery, + hence, opening the
      with PHP itself instead of JavaScript. + + @todo find a better solution that uses $.append(), the recommended + method maybe by using an additional param, the id of the div to + append to + #} +
      + NULL + +
      + {{ Url_getHiddenInputs(db, table) }} + + + + + + + +
      +
      +
      + {{ Url_getHiddenInputs(db, table) }} + + + + + {{ input_for_real_end|raw }} + +
      +
      + {{ value|raw }} +
      + + + + + + + + + + + + + + + + + + + + + + + +
      {% trans 'Define new aliases' %}
      + + + + + + + +
      + + + + + + + +
      + + + + + + + +
      diff --git a/php/apps/phpmyadmin49/templates/export/alias_item.twig b/php/apps/phpmyadmin49/templates/export/alias_item.twig new file mode 100644 index 00000000..b016861b --- /dev/null +++ b/php/apps/phpmyadmin49/templates/export/alias_item.twig @@ -0,0 +1,10 @@ + + {{ type }} + {{ name }} + + + + + + + diff --git a/php/apps/phpmyadmin49/templates/filter.twig b/php/apps/phpmyadmin49/templates/filter.twig new file mode 100644 index 00000000..572ec465 --- /dev/null +++ b/php/apps/phpmyadmin49/templates/filter.twig @@ -0,0 +1,8 @@ +
      + {% trans "Filters" %} +
      + + +
      +
      diff --git a/php/apps/phpmyadmin49/templates/fk_checkbox.twig b/php/apps/phpmyadmin49/templates/fk_checkbox.twig new file mode 100644 index 00000000..afcc6156 --- /dev/null +++ b/php/apps/phpmyadmin49/templates/fk_checkbox.twig @@ -0,0 +1,4 @@ + + + diff --git a/php/apps/phpmyadmin49/templates/header_location.twig b/php/apps/phpmyadmin49/templates/header_location.twig new file mode 100644 index 00000000..113dab3d --- /dev/null +++ b/php/apps/phpmyadmin49/templates/header_location.twig @@ -0,0 +1,22 @@ +{# Manage HTML redirection #} + + + - - - + + + + + + + + + + diff --git a/php/apps/phpmyadmin49/templates/javascript/display.twig b/php/apps/phpmyadmin49/templates/javascript/display.twig new file mode 100644 index 00000000..12027efc --- /dev/null +++ b/php/apps/phpmyadmin49/templates/javascript/display.twig @@ -0,0 +1,7 @@ + diff --git a/php/apps/phpmyadmin49/templates/list/item.twig b/php/apps/phpmyadmin49/templates/list/item.twig new file mode 100644 index 00000000..cfbc7729 --- /dev/null +++ b/php/apps/phpmyadmin49/templates/list/item.twig @@ -0,0 +1,19 @@ + + + {% if url is defined and url is iterable and url['href'] is not empty %} + + {% endif %} + {{ content|raw }} + {% if url is defined and url is iterable and url['href'] is not empty %} + + {% endif %} + {% if mysql_help_page is not empty %} + {{ Util_showMySQLDocu(mysql_help_page) }} + {% endif %} + diff --git a/php/apps/phpmyadmin49/templates/list/unordered.twig b/php/apps/phpmyadmin49/templates/list/unordered.twig new file mode 100644 index 00000000..11f114e1 --- /dev/null +++ b/php/apps/phpmyadmin49/templates/list/unordered.twig @@ -0,0 +1,14 @@ + + + {% if items is not empty %} + {% for item in items %} + {% if item is not iterable %} + {% set item = {'content': item} %} + {% endif %} + {% include 'list/item.twig' with item only %} + {% endfor %} + {% elseif content is not empty %} + {{ content|raw }} + {% endif %} + diff --git a/php/apps/phpmyadmin49/templates/login/footer.twig b/php/apps/phpmyadmin49/templates/login/footer.twig new file mode 100644 index 00000000..04f5b844 --- /dev/null +++ b/php/apps/phpmyadmin49/templates/login/footer.twig @@ -0,0 +1 @@ +
      diff --git a/php/apps/phpmyadmin49/templates/login/header.twig b/php/apps/phpmyadmin49/templates/login/header.twig new file mode 100644 index 00000000..e274ebc9 --- /dev/null +++ b/php/apps/phpmyadmin49/templates/login/header.twig @@ -0,0 +1,13 @@ +
      + +

      {{ 'Welcome to %s'|trans|format('phpMyAdmin')|raw }}

      + + + +
      +{{ Message_error("There is mismatch between HTTPS indicated on the server and client. This can lead to non working phpMyAdmin or a security risk. Please fix your server configuration to indicate HTTPS properly."|trans) }} +
      diff --git a/php/apps/phpmyadmin49/templates/login/twofactor.twig b/php/apps/phpmyadmin49/templates/login/twofactor.twig new file mode 100644 index 00000000..52ca9c88 --- /dev/null +++ b/php/apps/phpmyadmin49/templates/login/twofactor.twig @@ -0,0 +1,7 @@ +
      +{{ Url_getHiddenInputs() }} +{{ form|raw }} +{% if show_submit %} + +{% endif %} +
      diff --git a/php/apps/phpmyadmin49/templates/login/twofactor/application.twig b/php/apps/phpmyadmin49/templates/login/twofactor/application.twig new file mode 100644 index 00000000..8c0d7480 --- /dev/null +++ b/php/apps/phpmyadmin49/templates/login/twofactor/application.twig @@ -0,0 +1,4 @@ +

      + +

      +

      {% trans "Open the two-factor authentication app on your device to view your authentication code and verify your identity." %}

      diff --git a/php/apps/phpmyadmin49/templates/login/twofactor/application_configure.twig b/php/apps/phpmyadmin49/templates/login/twofactor/application_configure.twig new file mode 100644 index 00000000..97b069da --- /dev/null +++ b/php/apps/phpmyadmin49/templates/login/twofactor/application_configure.twig @@ -0,0 +1,22 @@ +{{ Url_getHiddenInputs() }} +{% if image is defined %} +

      + {% trans "Please scan following QR code into the two-factor authentication app on your device and enter authentication code it generates." %} +

      +

      + +

      +{% else %} +

      + {% trans "Please enter following secret/key into the two-factor authentication app on your device and enter authentication code it generates." %} +

      +

      + {% trans "OTP url:" %} {{ url }} +

      +{% endif %} +

      + {% trans "Secret/key:" %} {{ secret }} +

      +

      + +

      diff --git a/php/apps/phpmyadmin49/templates/login/twofactor/invalid.twig b/php/apps/phpmyadmin49/templates/login/twofactor/invalid.twig new file mode 100644 index 00000000..568dd94a --- /dev/null +++ b/php/apps/phpmyadmin49/templates/login/twofactor/invalid.twig @@ -0,0 +1,3 @@ +
      +{% trans "The configured two factor authentication is not available, please install missing dependencies." %} +
      diff --git a/php/apps/phpmyadmin49/templates/login/twofactor/key-https-warning.twig b/php/apps/phpmyadmin49/templates/login/twofactor/key-https-warning.twig new file mode 100644 index 00000000..d3f9c04c --- /dev/null +++ b/php/apps/phpmyadmin49/templates/login/twofactor/key-https-warning.twig @@ -0,0 +1,5 @@ +{% if not is_https %} +
      +{% trans "You are not using https to access phpMyAdmin, therefore FIDO U2F device will most likely refuse to authenticate you." %} +
      +{% endif %} diff --git a/php/apps/phpmyadmin49/templates/login/twofactor/key.twig b/php/apps/phpmyadmin49/templates/login/twofactor/key.twig new file mode 100644 index 00000000..3781aaf2 --- /dev/null +++ b/php/apps/phpmyadmin49/templates/login/twofactor/key.twig @@ -0,0 +1,5 @@ +{% include 'login/twofactor/key-https-warning.twig' %} +

      +{% trans "Please connect your FIDO U2F device into your computer's USB port. Then confirm login on the device." %} +

      + diff --git a/php/apps/phpmyadmin49/templates/login/twofactor/key_configure.twig b/php/apps/phpmyadmin49/templates/login/twofactor/key_configure.twig new file mode 100644 index 00000000..d89803da --- /dev/null +++ b/php/apps/phpmyadmin49/templates/login/twofactor/key_configure.twig @@ -0,0 +1,5 @@ +{% include 'login/twofactor/key-https-warning.twig' %} +

      +{% trans "Please connect your FIDO U2F device into your computer's USB port. Then confirm registration on the device." %} +

      + diff --git a/php/apps/phpmyadmin49/templates/login/twofactor/simple.twig b/php/apps/phpmyadmin49/templates/login/twofactor/simple.twig new file mode 100644 index 00000000..7fd98241 --- /dev/null +++ b/php/apps/phpmyadmin49/templates/login/twofactor/simple.twig @@ -0,0 +1 @@ + diff --git a/php/apps/phpmyadmin49/templates/navigation/logo.twig b/php/apps/phpmyadmin49/templates/navigation/logo.twig new file mode 100644 index 00000000..d4a01f7e --- /dev/null +++ b/php/apps/phpmyadmin49/templates/navigation/logo.twig @@ -0,0 +1,12 @@ +{% if display_logo %} + +{% endif %} diff --git a/php/apps/phpmyadmin49/templates/prefs_autoload.twig b/php/apps/phpmyadmin49/templates/prefs_autoload.twig new file mode 100644 index 00000000..efe44a9e --- /dev/null +++ b/php/apps/phpmyadmin49/templates/prefs_autoload.twig @@ -0,0 +1,15 @@ + diff --git a/php/apps/phpmyadmin49/templates/prefs_twofactor.twig b/php/apps/phpmyadmin49/templates/prefs_twofactor.twig new file mode 100644 index 00000000..e32ec21e --- /dev/null +++ b/php/apps/phpmyadmin49/templates/prefs_twofactor.twig @@ -0,0 +1,58 @@ +
      +

      +{% trans "Two-factor authentication status" %} +{{ Util_showDocu('two_factor') }} +

      +
      +{% if enabled %} +{% if num_backends == 0 %} +

      {% trans "Two-factor authentication is not available, please install optional dependencies to enable authentication backends." %}

      +

      {% trans "Following composer packages are missing:" %}

      +
        +{% for item in missing %} +
      • {{ item.dep }} ({{ item.class }})
      • +{% endfor %} +
      +{% else %} +{% if backend_id %} +

      {% trans "Two-factor authentication is available and configured for this account." %}

      +{% else %} +

      {% trans "Two-factor authentication is available, but not configured for this account." %}

      +{% endif %} +{% endif %} +{% else %} +

      {% trans "Two-factor authentication is not available, enable phpMyAdmin configuration storage to use it." %}

      +{% endif %} +
      +
      + +{% if backend_id %} +
      +

      {{ backend_name }}

      +
      +

      {% trans "You have enabled two factor authentication." %}

      +

      {{ backend_description }}

      +
      +{{ Url_getHiddenInputs() }} + +
      +
      +
      +{% elseif num_backends > 0 %} +
      +

      {% trans "Configure two-factor authentication" %}

      +
      +
      +{{ Url_getHiddenInputs() }} +{% for backend in backends %} + +{% endfor %} + +
      +
      +
      +{% endif %} diff --git a/php/apps/phpmyadmin49/templates/prefs_twofactor_configure.twig b/php/apps/phpmyadmin49/templates/prefs_twofactor_configure.twig new file mode 100644 index 00000000..ce597a35 --- /dev/null +++ b/php/apps/phpmyadmin49/templates/prefs_twofactor_configure.twig @@ -0,0 +1,13 @@ +
      +

      {% trans "Configure two-factor authentication" %}

      +
      +
      +{{ Url_getHiddenInputs() }} + +{{ form|raw }} + +
      +
      +
      + + diff --git a/php/apps/phpmyadmin49/templates/prefs_twofactor_confirm.twig b/php/apps/phpmyadmin49/templates/prefs_twofactor_confirm.twig new file mode 100644 index 00000000..7d785a7f --- /dev/null +++ b/php/apps/phpmyadmin49/templates/prefs_twofactor_confirm.twig @@ -0,0 +1,12 @@ +
      +

      {% trans "Confirm disabling two-factor authentication" %}

      +
      +
      +{{ Message_notice("By disabling two factor authentication you will be again able to login using password only."|trans) }} +{{ Url_getHiddenInputs() }} +{{ form|raw }} + + +
      +
      +
      diff --git a/php/apps/phpmyadmin49/templates/preview_sql.twig b/php/apps/phpmyadmin49/templates/preview_sql.twig new file mode 100644 index 00000000..9219a17a --- /dev/null +++ b/php/apps/phpmyadmin49/templates/preview_sql.twig @@ -0,0 +1,11 @@ +
      + {% if query_data is empty %} + {% trans 'No change' %} + {% elseif query_data is iterable %} + {% for query in query_data %} + {{ Util_formatSql(query) }} + {% endfor %} + {% else %} + {{ Util_formatSql(query_data) }} + {% endif %} +
      diff --git a/php/apps/phpmyadmin49/templates/privileges/add_privileges_database.twig b/php/apps/phpmyadmin49/templates/privileges/add_privileges_database.twig new file mode 100644 index 00000000..d48723fc --- /dev/null +++ b/php/apps/phpmyadmin49/templates/privileges/add_privileges_database.twig @@ -0,0 +1,14 @@ + + +{%- if databases is not empty %} + +{% endif -%} + + +{{ Util_showHint("Wildcards % and _ should be escaped with a \\ to use them literally."|trans) }} diff --git a/php/apps/phpmyadmin49/templates/privileges/add_privileges_routine.twig b/php/apps/phpmyadmin49/templates/privileges/add_privileges_routine.twig new file mode 100644 index 00000000..4e101291 --- /dev/null +++ b/php/apps/phpmyadmin49/templates/privileges/add_privileges_routine.twig @@ -0,0 +1,14 @@ + + + + +{%- if routines is not empty %} + +{% endif -%} + + diff --git a/php/apps/phpmyadmin49/templates/privileges/add_privileges_table.twig b/php/apps/phpmyadmin49/templates/privileges/add_privileges_table.twig new file mode 100644 index 00000000..bdcaeb7e --- /dev/null +++ b/php/apps/phpmyadmin49/templates/privileges/add_privileges_table.twig @@ -0,0 +1,14 @@ + + + + +{%- if tables is not empty %} + +{% endif -%} + + diff --git a/php/apps/phpmyadmin49/templates/privileges/add_user_fieldset.twig b/php/apps/phpmyadmin49/templates/privileges/add_user_fieldset.twig new file mode 100644 index 00000000..6483ec85 --- /dev/null +++ b/php/apps/phpmyadmin49/templates/privileges/add_user_fieldset.twig @@ -0,0 +1,8 @@ +
      + {% trans %}New{% context %}Create new user{% endtrans %} + + {{ Util_getIcon('b_usradd') }}{% trans 'Add user account' %} +
      diff --git a/php/apps/phpmyadmin49/templates/privileges/choose_user_group.twig b/php/apps/phpmyadmin49/templates/privileges/choose_user_group.twig new file mode 100644 index 00000000..64a2e223 --- /dev/null +++ b/php/apps/phpmyadmin49/templates/privileges/choose_user_group.twig @@ -0,0 +1,9 @@ +
      + {{ Url_getHiddenInputs(params) }} +
      + {% trans 'User group' %} + {% trans 'User group' %}: + {{ Util_getDropdown('userGroup', all_user_groups, user_group, 'userGroup_select') }} + +
      +
      diff --git a/php/apps/phpmyadmin49/templates/privileges/column_privileges.twig b/php/apps/phpmyadmin49/templates/privileges/column_privileges.twig new file mode 100644 index 00000000..50af9c2a --- /dev/null +++ b/php/apps/phpmyadmin49/templates/privileges/column_privileges.twig @@ -0,0 +1,24 @@ +
      + + + + + {% trans 'Or' %} + +
      diff --git a/php/apps/phpmyadmin49/templates/privileges/delete_user_fieldset.twig b/php/apps/phpmyadmin49/templates/privileges/delete_user_fieldset.twig new file mode 100644 index 00000000..d948afea --- /dev/null +++ b/php/apps/phpmyadmin49/templates/privileges/delete_user_fieldset.twig @@ -0,0 +1,17 @@ +
      + + {{ Util_getIcon('b_usrdrop') }}{% trans 'Remove selected user accounts' %} + + +

      ({% trans 'Revoke all active privileges from the users and delete them afterwards.' %})

      + + +
      + + diff --git a/php/apps/phpmyadmin49/templates/privileges/edit_routine_privileges.twig b/php/apps/phpmyadmin49/templates/privileges/edit_routine_privileges.twig new file mode 100644 index 00000000..be3c0b18 --- /dev/null +++ b/php/apps/phpmyadmin49/templates/privileges/edit_routine_privileges.twig @@ -0,0 +1,26 @@ +
      + {{ header|raw }} + +
      diff --git a/php/apps/phpmyadmin49/templates/privileges/global_priv_table.twig b/php/apps/phpmyadmin49/templates/privileges/global_priv_table.twig new file mode 100644 index 00000000..8a6d1155 --- /dev/null +++ b/php/apps/phpmyadmin49/templates/privileges/global_priv_table.twig @@ -0,0 +1,18 @@ +{% for key, table in priv_table %} +
      + + + + + {% for priv in table %} + {% set checked = row[priv[0] ~ '_priv'] is defined and row[priv[0] ~ '_priv'] == 'Y' ? ' checked="checked"' %} + {% set formatted_priv = ServerPrivileges_formatPrivilege(priv, true) %} + {% include 'privileges/global_priv_tbl_item.twig' with { + 'checked': checked, + 'formatted_priv': formatted_priv, + 'priv': priv + } only %} + {% endfor %} +
      +{% endfor %} diff --git a/php/apps/phpmyadmin49/templates/privileges/global_priv_tbl_item.twig b/php/apps/phpmyadmin49/templates/privileges/global_priv_tbl_item.twig new file mode 100644 index 00000000..5a5c3b6c --- /dev/null +++ b/php/apps/phpmyadmin49/templates/privileges/global_priv_tbl_item.twig @@ -0,0 +1,9 @@ +
      + + +
      diff --git a/php/apps/phpmyadmin49/templates/privileges/initials_row.twig b/php/apps/phpmyadmin49/templates/privileges/initials_row.twig new file mode 100644 index 00000000..a36e1421 --- /dev/null +++ b/php/apps/phpmyadmin49/templates/privileges/initials_row.twig @@ -0,0 +1,24 @@ + + + {% for tmp_initial, initial_was_found in array_initials if tmp_initial is not same as(null) %} + {% if initial_was_found %} + + {% else %} + + {% endif %} + {% endfor %} + + +
      + + {{- tmp_initial|raw -}} + + {{ tmp_initial|raw }} + + {% trans 'Show all' %} + +
      diff --git a/php/apps/phpmyadmin49/templates/privileges/privileges_summary.twig b/php/apps/phpmyadmin49/templates/privileges/privileges_summary.twig new file mode 100644 index 00000000..31f2173d --- /dev/null +++ b/php/apps/phpmyadmin49/templates/privileges/privileges_summary.twig @@ -0,0 +1,62 @@ + diff --git a/php/apps/phpmyadmin49/templates/privileges/privileges_summary_row.twig b/php/apps/phpmyadmin49/templates/privileges/privileges_summary_row.twig new file mode 100644 index 00000000..4470ca51 --- /dev/null +++ b/php/apps/phpmyadmin49/templates/privileges/privileges_summary_row.twig @@ -0,0 +1,14 @@ + + {{ name }} + {{ privileges|raw }} + {{ grant ? 'Yes'|trans : 'No'|trans }} + + {% if type == 'database' %} + {{ table_privs ? 'Yes'|trans : 'No'|trans }} + {% elseif type == 'table' %} + {{ column_privs ? 'Yes'|trans : 'No'|trans }} + {% endif %} + + {{ edit_link|raw }} + {{ revoke_link|raw }} + diff --git a/php/apps/phpmyadmin49/templates/privileges/require_options.twig b/php/apps/phpmyadmin49/templates/privileges/require_options.twig new file mode 100644 index 00000000..157f2723 --- /dev/null +++ b/php/apps/phpmyadmin49/templates/privileges/require_options.twig @@ -0,0 +1,14 @@ +
      + SSL +
      + {% for require_option in require_options %} + {% if require_option['name'] is same as('ssl_cipher') %} +
      + {% endif %} + {% include 'privileges/require_options_item.twig' with { + 'require_option': require_option + } only %} + {% endfor %} +
      {# END specified_div #} +
      {# END require_ssl_div #} +
      diff --git a/php/apps/phpmyadmin49/templates/privileges/require_options_item.twig b/php/apps/phpmyadmin49/templates/privileges/require_options_item.twig new file mode 100644 index 00000000..9270daae --- /dev/null +++ b/php/apps/phpmyadmin49/templates/privileges/require_options_item.twig @@ -0,0 +1,23 @@ +
      + {% if require_option['radio'] %} + + + {% else %} + + + {% endif %} +
      diff --git a/php/apps/phpmyadmin49/templates/privileges/resource_limit_item.twig b/php/apps/phpmyadmin49/templates/privileges/resource_limit_item.twig new file mode 100644 index 00000000..af0a3583 --- /dev/null +++ b/php/apps/phpmyadmin49/templates/privileges/resource_limit_item.twig @@ -0,0 +1,11 @@ +
      + + +
      diff --git a/php/apps/phpmyadmin49/templates/privileges/resource_limits.twig b/php/apps/phpmyadmin49/templates/privileges/resource_limits.twig new file mode 100644 index 00000000..8d61ac59 --- /dev/null +++ b/php/apps/phpmyadmin49/templates/privileges/resource_limits.twig @@ -0,0 +1,13 @@ +
      + {% trans 'Resource limits' %} +

      + + {% trans 'Note: Setting these options to 0 (zero) removes the limit.' %} + +

      + {% for limit in limits %} + {% include 'privileges/resource_limit_item.twig' with { + 'limit': limit + } only %} + {% endfor %} +
      diff --git a/php/apps/phpmyadmin49/templates/radio_fields.twig b/php/apps/phpmyadmin49/templates/radio_fields.twig new file mode 100644 index 00000000..46d3a3cb --- /dev/null +++ b/php/apps/phpmyadmin49/templates/radio_fields.twig @@ -0,0 +1,11 @@ +{% if class is not empty %} +
      +{% endif %} + + +{% if is_line_break %} +
      +{% endif %} +{% if class is not empty %} +
      +{% endif %} diff --git a/php/apps/phpmyadmin49/templates/secondary_tabs.twig b/php/apps/phpmyadmin49/templates/secondary_tabs.twig new file mode 100644 index 00000000..20c9c11a --- /dev/null +++ b/php/apps/phpmyadmin49/templates/secondary_tabs.twig @@ -0,0 +1,6 @@ +
        + {% for tab in sub_tabs %} + {{ Util_getHtmlTab(tab, url_params) }} + {% endfor %} +
      +
      diff --git a/php/apps/phpmyadmin49/templates/select_all.twig b/php/apps/phpmyadmin49/templates/select_all.twig new file mode 100644 index 00000000..b9c21021 --- /dev/null +++ b/php/apps/phpmyadmin49/templates/select_all.twig @@ -0,0 +1,6 @@ +{% trans 'With selected:' %} + + +{% trans 'With selected:' %} diff --git a/php/apps/phpmyadmin49/templates/select_lang.twig b/php/apps/phpmyadmin49/templates/select_lang.twig new file mode 100644 index 00000000..98b5863f --- /dev/null +++ b/php/apps/phpmyadmin49/templates/select_lang.twig @@ -0,0 +1,32 @@ +
      + {{ Url_getHiddenInputs(_form_params) }} + + {% if use_fieldset %} +
      + {{ language_title|raw }} + {% else %} + + + + {% endif %} + + + + {% if use_fieldset %} +
      + {% endif %} + +
      diff --git a/php/apps/phpmyadmin49/templates/server/binlog/log_row.twig b/php/apps/phpmyadmin49/templates/server/binlog/log_row.twig new file mode 100644 index 00000000..a5ffabd8 --- /dev/null +++ b/php/apps/phpmyadmin49/templates/server/binlog/log_row.twig @@ -0,0 +1,10 @@ + + {{ value['Log_name'] }} + {{ value['Pos'] }} + {{ value['Event_type'] }} + {{ value['Server_id'] }} + + {{- value['Orig_log_pos'] is defined ? value['Orig_log_pos'] : value['End_log_pos'] -}} + + {{ Util_formatSql(value['Info'], not dontlimitchars) }} + diff --git a/php/apps/phpmyadmin49/templates/server/binlog/log_selector.twig b/php/apps/phpmyadmin49/templates/server/binlog/log_selector.twig new file mode 100644 index 00000000..bbdc382b --- /dev/null +++ b/php/apps/phpmyadmin49/templates/server/binlog/log_selector.twig @@ -0,0 +1,29 @@ +
      + {{ Url_getHiddenInputs(url_params) }} +
      + + {% trans 'Select binary log to view' %} + + {% set full_size = 0 %} + + {{ binary_logs|length }} + {% trans 'Files' %}, + {% if full_size > 0 %} + {{ Util_formatByteDown(full_size)|join(' ') }} + {% endif %} +
      +
      + +
      +
      diff --git a/php/apps/phpmyadmin49/templates/server/collations/charsets.twig b/php/apps/phpmyadmin49/templates/server/collations/charsets.twig new file mode 100644 index 00000000..e23cf3c3 --- /dev/null +++ b/php/apps/phpmyadmin49/templates/server/collations/charsets.twig @@ -0,0 +1,26 @@ +
      + + + + + + + + {% for current_charset in mysql_charsets %} + + + + {% for current_collation in mysql_collations[current_charset] %} + + + + + {% endfor %} + {% endfor %} +
      {% trans 'Collation' %}{% trans 'Description' %}
      + {{ current_charset }} + {% if mysql_charsets_desc[current_charset] is not empty %} + ({{ mysql_charsets_desc[current_charset] }}) + {% endif %} +
      {{ current_collation }}{{ Charsets_getCollationDescr(current_collation) }}
      +
      diff --git a/php/apps/phpmyadmin49/templates/server/databases/create.twig b/php/apps/phpmyadmin49/templates/server/databases/create.twig new file mode 100644 index 00000000..fff3c2dc --- /dev/null +++ b/php/apps/phpmyadmin49/templates/server/databases/create.twig @@ -0,0 +1,50 @@ +
        +
      • + {% if is_create_db_priv %} +
        +

        + + {{ Util_showMySQLDocu('CREATE_DATABASE') }} +

        + + {{ Url_getHiddenInputs('', '', 5) }} + + {% if dbstats is not empty %} + + {% endif %} + + + {{ Charsets_getCollationDropdownBox( + dbi, + disable_is, + 'db_collation', + null, + server_collation, + true + ) }} + +
        + {% else %} + {# db creation no privileges message #} +

        + {{ Util_getImage('b_newdb') }} + {% trans 'Create database' %} + {{ Util_showMySQLDocu('CREATE_DATABASE') }} +

        + + + {{ Util_getImage( + 's_error', + '', + {'hspace': 2, 'border': 0, 'align': 'middle'} + ) }} + {% trans 'No Privileges' %} + + {% endif %} +
      • +
      diff --git a/php/apps/phpmyadmin49/templates/server/databases/databases_footer.twig b/php/apps/phpmyadmin49/templates/server/databases/databases_footer.twig new file mode 100644 index 00000000..31204823 --- /dev/null +++ b/php/apps/phpmyadmin49/templates/server/databases/databases_footer.twig @@ -0,0 +1,75 @@ + + + {% if is_superuser or allow_user_drop_database %} + + {% endif %} + + {% trans 'Total' %}: + {{- database_count -}} + + + {% for stat_name, stat in column_order if stat_name in first_database|keys %} + {% if stat['format'] is same as('byte') %} + {% set byte_format = Util_formatByteDown(stat['footer'], 3, 1) %} + {% set value = byte_format[0] %} + {% set unit = byte_format[1] %} + {% elseif stat['format'] is same as('number') %} + {% set value = Util_formatNumber(stat['footer'], 0) %} + {% else %} + {% set value = htmlentities(stat['footer'], 0) %} + {% endif %} + + + {% if stat['description_function'] is defined %} + + {{ value }} + + {% else %} + {{ value }} + {% endif %} + + {% if stat['format'] is same as('byte') %} + {{ unit }} + {% endif %} + {% endfor %} + {% if master_replication %} + + {% endif %} + {% if slave_replication %} + + {% endif %} + + + + +
      + +{# Footer buttons #} +{% if is_superuser or allow_user_drop_database %} + {% include 'select_all.twig' with { + 'pma_theme_image': pma_theme_image, + 'text_dir': text_dir, + 'form_name': 'dbStatsForm' + } only %} + + {{ Util_getButtonOrImage( + '', + 'mult_submit ajax', + 'Drop'|trans, + 'b_deltbl' + ) }} +{% endif %} + +{# Enable statistics #} +{% if dbstats is empty %} + {{ Message_notice('Note: Enabling the database statistics here might cause heavy traffic between the web server and the MySQL server.'|trans) }} + +{% endif %} + +
      diff --git a/php/apps/phpmyadmin49/templates/server/databases/databases_header.twig b/php/apps/phpmyadmin49/templates/server/databases/databases_header.twig new file mode 100644 index 00000000..996a8514 --- /dev/null +++ b/php/apps/phpmyadmin49/templates/server/databases/databases_header.twig @@ -0,0 +1,29 @@ +
      + {{ Util_getListNavigator( + database_count, + pos, + url_params, + 'server_databases.php', + 'frame_content', + max_db_list + ) }} +
      + {{ Url_getHiddenInputs(url_params) }} + {% set url_params = url_params|merge({ + 'sort_by': 'SCHEMA_NAME', + 'sort_order': sort_by == 'SCHEMA_NAME' and sort_order == 'asc' ? 'desc' : 'asc' + }) %} +
      + + {% include 'server/databases/table_header.twig' with { + 'url_params': url_params, + 'sort_by': sort_by, + 'sort_order': sort_order, + 'sort_order_text': sort_order == 'asc' ? 'Ascending'|trans : 'Descending'|trans, + 'column_order': column_order, + 'first_database': first_database, + 'master_replication': master_replication, + 'slave_replication': slave_replication, + 'is_superuser': is_superuser, + 'allow_user_drop_database': allow_user_drop_database + } only %} diff --git a/php/apps/phpmyadmin49/templates/server/databases/index.twig b/php/apps/phpmyadmin49/templates/server/databases/index.twig new file mode 100644 index 00000000..7d465e2b --- /dev/null +++ b/php/apps/phpmyadmin49/templates/server/databases/index.twig @@ -0,0 +1,25 @@ +{# Displays the sub-page heading #} +{% include 'server/sub_page_header.twig' with { + 'type': dbstats ? 'database_statistics' : 'databases' +} only %} + +{# Displays For Create database #} +{% if show_create_db %} + {% include 'server/databases/create.twig' with { + 'is_create_db_priv': is_create_db_priv, + 'dbstats': dbstats, + 'db_to_create': db_to_create, + 'server_collation': server_collation, + 'dbi': dbi, + 'disable_is': disable_is + } only %} +{% endif %} + +{% include 'filter.twig' with {'filter_value': ''} only %} + +{# Displays the page #} +{% if databases is not null %} + {{ databases|raw }} +{% else %} +

      {% trans 'No databases' %}

      +{% endif %} diff --git a/php/apps/phpmyadmin49/templates/server/databases/table_header.twig b/php/apps/phpmyadmin49/templates/server/databases/table_header.twig new file mode 100644 index 00000000..29455ab4 --- /dev/null +++ b/php/apps/phpmyadmin49/templates/server/databases/table_header.twig @@ -0,0 +1,39 @@ + + + {% if is_superuser or allow_user_drop_database %} + + {% endif %} + + {% for stat_name, stat in column_order if stat_name in first_database|keys %} + {% set url_params = url_params|merge({ + 'sort_by': stat_name, + 'sort_order': sort_by == stat_name and sort_order == 'desc' ? 'asc' : 'desc' + }) %} + + + + {{ stat['disp_name'] }} + {{ sort_by == stat_name ? Util_getImage( + 's_' ~ sort_order, + sort_order_text + ) }} + + + {% endfor %} + {% if master_replication %} + + {% endif %} + {% if slave_replication %} + + {% endif %} + + + diff --git a/php/apps/phpmyadmin49/templates/server/databases/table_row.twig b/php/apps/phpmyadmin49/templates/server/databases/table_row.twig new file mode 100644 index 00000000..03bdd57e --- /dev/null +++ b/php/apps/phpmyadmin49/templates/server/databases/table_row.twig @@ -0,0 +1,65 @@ + + {% if is_superuser or allow_user_drop_database %} + + {% endif %} + + {% for stat_name, stat in column_order if stat_name in current|keys %} + {% if stat['format'] is same as('byte') %} + {% set byte_format = Util_formatByteDown(current[stat_name], 3, 1) %} + {% set value = byte_format[0] %} + {% set unit = byte_format[1] %} + {% elseif stat['format'] is same as('number') %} + {% set value = Util_formatNumber(current[stat_name], 0) %} + {% else %} + {% set value = htmlentities(current[stat_name], 0) %} + {% endif %} + + + {% if stat['format'] is same as('byte') %} + + {% endif %} + {% endfor %} + + {% if master_replication_status %} + + {% endif %} + + {% if slave_replication_status %} + + {% endif %} + + + diff --git a/php/apps/phpmyadmin49/templates/server/engines/engine.twig b/php/apps/phpmyadmin49/templates/server/engines/engine.twig new file mode 100644 index 00000000..7744ed1c --- /dev/null +++ b/php/apps/phpmyadmin49/templates/server/engines/engine.twig @@ -0,0 +1,39 @@ +

      + {{ Util_getImage('b_engine') }} + {{ title }} + {{ Util_showMySQLDocu(help_page) }} +

      +

      {{ comment }}

      + +{% if info_pages is not empty and info_pages is iterable %} +

      + [ + {% if page is empty %} + {% trans 'Variables' %} + {% else %} + + {% trans 'Variables' %} + + {% endif %} + {% for current, label in info_pages %} + | + {% if page is defined and page == current %} + {{ label }} + {% else %} + + {{ label }} + + {% endif %} + {% endfor %} + ] +

      +{% endif %} + +{% if page_output is not empty %} + {{ page_output|raw }} +{% else %} +

      {{ support }}

      + {{ variables|raw }} +{% endif %} diff --git a/php/apps/phpmyadmin49/templates/server/engines/engines.twig b/php/apps/phpmyadmin49/templates/server/engines/engines.twig new file mode 100644 index 00000000..5a3d68c1 --- /dev/null +++ b/php/apps/phpmyadmin49/templates/server/engines/engines.twig @@ -0,0 +1,22 @@ +
      + + {% trans 'Database' %} + {{ sort_by == 'SCHEMA_NAME' ? Util_getImage( + 's_' ~ sort_order, + sort_order_text + ) }} + + {% trans 'Master replication' %}{% trans 'Slave replication' %}{% trans 'Action' %}
      + + + + {{ current['SCHEMA_NAME'] }} + + + {% if stat['description_function'] is defined %} + + {{ value }} + + {% else %} + {{ value }} + {% endif %} + {{ unit }} + {{ master_replication|raw }} + + {{ slave_replication|raw }} + + + {{ Util_getIcon('s_rights', 'Check privileges'|trans) }} + +
      + + + + + + + + {% for engine, details in engines %} + + + + + {% endfor %} + +
      {% trans 'Storage Engine' %}{% trans 'Description' %}
      + + {{ details['Engine'] }} + + {{ details['Comment'] }}
      diff --git a/php/apps/phpmyadmin49/templates/server/plugins/section.twig b/php/apps/phpmyadmin49/templates/server/plugins/section.twig new file mode 100644 index 00000000..bca286d4 --- /dev/null +++ b/php/apps/phpmyadmin49/templates/server/plugins/section.twig @@ -0,0 +1,35 @@ +
      + + + + + + + + + + + + + {% for plugin in plugin_list %} + + + + + + + + {% endfor %} + +
      + {{ plugin_type }} +
      {% trans 'Plugin' %}{% trans 'Description' %}{% trans 'Version' %}{% trans 'Author' %}{% trans 'License' %}
      + {{ plugin['plugin_name'] }} + {% if not plugin['is_active'] %} + + {% trans 'disabled' %} + + {% endif %} + {{ plugin['plugin_description'] }}{{ plugin['plugin_type_version'] }}{{ plugin['plugin_author'] }}{{ plugin['plugin_license'] }}
      +
      diff --git a/php/apps/phpmyadmin49/templates/server/plugins/section_links.twig b/php/apps/phpmyadmin49/templates/server/plugins/section_links.twig new file mode 100644 index 00000000..f6862330 --- /dev/null +++ b/php/apps/phpmyadmin49/templates/server/plugins/section_links.twig @@ -0,0 +1,7 @@ + diff --git a/php/apps/phpmyadmin49/templates/server/sub_page_header.twig b/php/apps/phpmyadmin49/templates/server/sub_page_header.twig new file mode 100644 index 00000000..eb85c712 --- /dev/null +++ b/php/apps/phpmyadmin49/templates/server/sub_page_header.twig @@ -0,0 +1,48 @@ +{# array contains Sub page icon and text #} +{% set header = { + 'variables': { + 'image': 's_vars', + 'text': 'Server variables and settings'|trans + }, + 'engines': { + 'image': 'b_engine', + 'text': 'Storage engines'|trans + }, + 'plugins': { + 'image': 'b_plugin', + 'text': 'Plugins'|trans + }, + 'binlog': { + 'image': 's_tbl', + 'text': 'Binary log'|trans + }, + 'collations': { + 'image': 's_asci', + 'text': 'Character sets and collations'|trans + }, + 'replication': { + 'image': 's_replication', + 'text': 'Replication'|trans + }, + 'database_statistics': { + 'image': 's_db', + 'text': 'Databases statistics'|trans + }, + 'databases': { + 'image': 's_db', + 'text': 'Databases'|trans + }, + 'privileges': { + 'image': 'b_usrlist', + 'text': 'Privileges'|trans + } +} %} +

      + {% if is_image|default(true) %} + {{ Util_getImage(header[type]['image']) }} + {% else %} + {{ Util_getIcon(header[type]['image']) }} + {% endif %} + {{ header[type]['text'] }} + {{ link is defined ? Util_showMySQLDocu(link) }} +

      diff --git a/php/apps/phpmyadmin49/templates/server/variables/link_template.twig b/php/apps/phpmyadmin49/templates/server/variables/link_template.twig new file mode 100644 index 00000000..4bcbfb15 --- /dev/null +++ b/php/apps/phpmyadmin49/templates/server/variables/link_template.twig @@ -0,0 +1,10 @@ + + {{ Util_getIcon('b_save', 'Save'|trans) }} + + + {{ Util_getIcon('b_close', 'Cancel'|trans) }} + +{{ Util_getImage('b_help', 'Documentation'|trans, { + 'class': 'hide', + 'id': 'docImage' +}) }} diff --git a/php/apps/phpmyadmin49/templates/server/variables/session_variable_row.twig b/php/apps/phpmyadmin49/templates/server/variables/session_variable_row.twig new file mode 100644 index 00000000..f6f286af --- /dev/null +++ b/php/apps/phpmyadmin49/templates/server/variables/session_variable_row.twig @@ -0,0 +1,5 @@ + + + ({% trans 'Session value' %}) +  {{ value }} + diff --git a/php/apps/phpmyadmin49/templates/server/variables/variable_row.twig b/php/apps/phpmyadmin49/templates/server/variables/variable_row.twig new file mode 100644 index 00000000..174ac61b --- /dev/null +++ b/php/apps/phpmyadmin49/templates/server/variables/variable_row.twig @@ -0,0 +1,29 @@ + + + {% if editable %} + {{ Util_getIcon('b_edit', 'Edit'|trans) }} + {% else %} + + {{ Util_getIcon('bd_edit', 'Edit'|trans) }} + + {% endif %} + + + {% if doc_link != null %} + + {{ Util_showMySQLDocu(doc_link[1], false, doc_link[2] ~ '_' ~ doc_link[0], true) }} + {{ name|e|replace({'_': ' '})|raw }} + + + {% else %} + {{ name|replace({'_': ' '}) }} + {% endif %} + + + {% if is_html_formatted == false %} + {{ value|e|replace({',': ',​'})|raw }} + {% else %} + {{ value|raw }} + {% endif %} + + diff --git a/php/apps/phpmyadmin49/templates/server/variables/variable_table_head.twig b/php/apps/phpmyadmin49/templates/server/variables/variable_table_head.twig new file mode 100644 index 00000000..9badd76a --- /dev/null +++ b/php/apps/phpmyadmin49/templates/server/variables/variable_table_head.twig @@ -0,0 +1,7 @@ + + + {% trans 'Action' %} + {% trans 'Variable' %} + {% trans 'Value' %} + + diff --git a/php/apps/phpmyadmin49/templates/start_and_number_of_rows_panel.twig b/php/apps/phpmyadmin49/templates/start_and_number_of_rows_panel.twig new file mode 100644 index 00000000..7d27f8f5 --- /dev/null +++ b/php/apps/phpmyadmin49/templates/start_and_number_of_rows_panel.twig @@ -0,0 +1,20 @@ +
      +
      + + 0 -%} + max="{{ unlim_num_rows - 1 }}" + {%- endif %} + value="{{ pos }}" /> + + + + + + +
      +
      diff --git a/php/apps/phpmyadmin49/templates/table/browse_foreigners/column_element.twig b/php/apps/phpmyadmin49/templates/table/browse_foreigners/column_element.twig new file mode 100644 index 00000000..f2ec2ba7 --- /dev/null +++ b/php/apps/phpmyadmin49/templates/table/browse_foreigners/column_element.twig @@ -0,0 +1,12 @@ + + {{ is_selected ? '' }} + + {% if nowrap %} + {{ keyname }} + {% else %} + {{ description }} + {% endif %} + + {{ is_selected ? '' }} + diff --git a/php/apps/phpmyadmin49/templates/table/browse_foreigners/show_all.twig b/php/apps/phpmyadmin49/templates/table/browse_foreigners/show_all.twig new file mode 100644 index 00000000..12a54419 --- /dev/null +++ b/php/apps/phpmyadmin49/templates/table/browse_foreigners/show_all.twig @@ -0,0 +1,5 @@ +{% if foreign_data.disp_row is iterable and + (show_all and foreign_data.the_total > max_rows) %} + +{% endif %} diff --git a/php/apps/phpmyadmin49/templates/table/chart/tbl_chart.twig b/php/apps/phpmyadmin49/templates/table/chart/tbl_chart.twig new file mode 100644 index 00000000..1458690b --- /dev/null +++ b/php/apps/phpmyadmin49/templates/table/chart/tbl_chart.twig @@ -0,0 +1,161 @@ + +{# Display Chart options #} +
      + + {{ Url_getHiddenInputs(url_params) }} +
      + + {% trans 'Display chart' %} + +
      +
      + + +
      +
      + + +
      +
      + + +
      +
      + + +
      +
      + + +
      + + + + + + + + + + + + +

      + + + + +

      + + +
      + {% set xaxis = null %} +
      + + +
      + + + + +
      +
      + + +
      + + +
      +
      +
      +
      + + +
      + + + + +
      + {{ Util_getStartAndNumberOfRowsPanel(sql_query) }} +
      + +
      + +
      diff --git a/php/apps/phpmyadmin49/templates/table/gis_visualization/gis_visualization.twig b/php/apps/phpmyadmin49/templates/table/gis_visualization/gis_visualization.twig new file mode 100644 index 00000000..49a7ce17 --- /dev/null +++ b/php/apps/phpmyadmin49/templates/table/gis_visualization/gis_visualization.twig @@ -0,0 +1,80 @@ +
      +
      + {% trans 'Display GIS Visualization' %} +
      +
      + {{ Url_getHiddenInputs(url_params) }} + + + + + + + + + + + + {{ Util_getStartAndNumberOfRowsPanel(sql_query) }} +
      + +
      +
      + + {{ Util_getImage('b_saveimage', 'Save'|trans) }} + + +
      +
      +
      + +
      + +
      + {{ visualization|raw }} +
      +
      + + +
      +
      diff --git a/php/apps/phpmyadmin49/templates/table/index_form.twig b/php/apps/phpmyadmin49/templates/table/index_form.twig new file mode 100644 index 00000000..72f47741 --- /dev/null +++ b/php/apps/phpmyadmin49/templates/table/index_form.twig @@ -0,0 +1,219 @@ +
      + + {{ Url_getHiddenInputs(form_params) }} + +
      +
      +
      +
      + + + +
      + + +
      + +
      +
      + + + +
      + {{ index.generateIndexChoiceSelector(create_edit_table)|raw }} +
      + + {{ Util_getDivForSliderEffect('indexoptions', 'Advanced Options'|trans) }} + +
      +
      + + + +
      + + +
      + +
      + +
      + + + +
      + {{ index.generateIndexTypeSelector()|raw }} +
      + +
      +
      + + + +
      + + +
      + +
      +
      + + + +
      + + +
      +
      + + +
      + + + + + + + + + + {% set spatial_types = [ + 'geometry', + 'point', + 'linestring', + 'polygon', + 'multipoint', + 'multilinestring', + 'multipolygon', + 'geomtrycollection' + ] %} + + {% for column in index.getColumns() %} + + + + + + {% endfor %} + {% if add_fields > 0 %} + {% for i in range(1, add_fields) %} + + + + + + {% endfor %} + {% endif %} + +
      + {% trans 'Column' %} + + {% trans 'Size' %} +
      + + + + + +
      + + + + + +
      +
      + +
      +
      + +
      +
      +
      +
      + + +
      +
      diff --git a/php/apps/phpmyadmin49/templates/table/insert/continue_insertion_form.twig b/php/apps/phpmyadmin49/templates/table/insert/continue_insertion_form.twig new file mode 100644 index 00000000..c39a96f3 --- /dev/null +++ b/php/apps/phpmyadmin49/templates/table/insert/continue_insertion_form.twig @@ -0,0 +1,19 @@ +
      + {{ Url_getHiddenInputs(db, table) }} + + + + + {% if has_where_clause %} + {% for key_id, where_clause in where_clause_array %} + + {% endfor %} + {% endif %} + + {% set insert_rows %} + + {% endset %} + {{ 'Continue insertion with %s rows'|trans|format(insert_rows)|raw }} +
      diff --git a/php/apps/phpmyadmin49/templates/table/relation/common_form.twig b/php/apps/phpmyadmin49/templates/table/relation/common_form.twig new file mode 100644 index 00000000..8dd30698 --- /dev/null +++ b/php/apps/phpmyadmin49/templates/table/relation/common_form.twig @@ -0,0 +1,191 @@ +
      + {{ Url_getHiddenInputs(db, table) }} + {# InnoDB #} + {% if Util_isForeignKeySupported(tbl_storage_engine) %} +
      + {% trans 'Foreign key constraints' %} +
      + + + + + {% if tbl_storage_engine|upper == 'INNODB' %} + + {% else %} + + {% endif %} + + + + + + + + + + + {% set i = 0 %} + {% if existrel_foreign is not empty %} + {% for key, one_key in existrel_foreign %} + {# Foreign database dropdown #} + {% set foreign_db = one_key['ref_db_name'] is defined + and one_key['ref_db_name'] is not null + ? one_key['ref_db_name'] : db %} + {% set foreign_table = false %} + {% if foreign_db %} + {% set foreign_table = one_key['ref_table_name'] is defined + and one_key['ref_table_name'] is not null + ? one_key['ref_table_name'] : false %} + {% endif %} + {% set unique_columns = [] %} + {% if foreign_db and foreign_table %} + {% set table_obj = Table_get(foreign_table, foreign_db) %} + {% set unique_columns = table_obj.getUniqueColumns(false, false) %} + {% endif %} + {% include 'table/relation/foreign_key_row.twig' with { + 'i': i, + 'one_key': one_key, + 'column_array': column_array, + 'options_array': options_array, + 'tbl_storage_engine': tbl_storage_engine, + 'db': db, + 'table': table, + 'url_params': url_params, + 'databases': databases, + 'foreign_db': foreign_db, + 'foreign_table': foreign_table, + 'unique_columns': unique_columns + } only %} + {% set i = i + 1 %} + {% endfor %} + {% endif %} + {% include 'table/relation/foreign_key_row.twig' with { + 'i': i, + 'one_key': [], + 'column_array': column_array, + 'options_array': options_array, + 'tbl_storage_engine': tbl_storage_engine, + 'db': db, + 'table': table, + 'url_params': url_params, + 'databases': databases, + 'foreign_db': foreign_db, + 'foreign_table': foreign_table, + 'unique_columns': unique_columns + } only %} + {% set i = i + 1 %} + + +
      {% trans 'Actions' %}{% trans 'Constraint properties' %} + {% trans 'Column' %} + {{ Util_showHint('Creating a foreign key over a non-indexed column would automatically create an index on it. Alternatively, you can define an index below, before creating the foreign key.'|trans) }} + + {% trans 'Column' %} + {{ Util_showHint('Only columns with index will be displayed. You can define an index below.'|trans) }} + + {% trans 'Foreign key constraint' %} + ({{ tbl_storage_engine }}) +
      {% trans 'Database' %}{% trans 'Table' %}{% trans 'Column' %}
      + + {% trans '+ Add constraint' %} + +
      +
      +
      + {% endif %} + + {% if cfg_relation['relwork'] %} + {% if Util_isForeignKeySupported(tbl_storage_engine) %} + {{ Util_getDivForSliderEffect('ir_div', 'Internal relationships'|trans) }} + {% endif %} + +
      + + {% trans 'Internal relationships' %} + {{ Util_showDocu('config', 'cfg_Servers_relation') }} + + + + + + {% set saved_row_cnt = save_row|length - 1 %} + {% for i in 0..saved_row_cnt %} + {% set myfield = save_row[i]['Field'] %} + {# Use an md5 as array index to avoid having special characters + in the name attribute (see bug #1746964 ) #} + {% set myfield_md5 = md5(myfield) %} + + {% set foreign_table = false %} + {% set foreign_column = false %} + + {# Database dropdown #} + {% if existrel[myfield] is defined %} + {% set foreign_db = existrel[myfield]['foreign_db'] %} + {% else %} + {% set foreign_db = db %} + {% endif %} + + {# Table dropdown #} + {% set tables = [] %} + {% if foreign_db %} + {% if existrel[myfield] is defined %} + {% set foreign_table = existrel[myfield]['foreign_table'] %} + {% endif %} + {% set tables = dbi.getTables(foreign_db) %} + {% endif %} + + {# Column dropdown #} + {% set unique_columns = [] %} + {% if foreign_db and foreign_table %} + {% if existrel[myfield] is defined %} + {% set foreign_column = existrel[myfield]['foreign_field'] %} + {% endif %} + {% set table_obj = Table_get(foreign_table, foreign_db) %} + {% set unique_columns = table_obj.getUniqueColumns(false, false) %} + {% endif %} + + {% include 'table/relation/internal_relational_row.twig' with { + 'myfield': myfield, + 'myfield_md5': myfield_md5, + 'databases': databases, + 'tables': tables, + 'columns': unique_columns, + 'foreign_db': foreign_db, + 'foreign_table': foreign_table, + 'foreign_column': foreign_column + } only %} + {% endfor %} +
      {% trans 'Column' %}{% trans 'Internal relation' %} + {% if Util_isForeignKeySupported(tbl_storage_engine) %} + {{ Util_showHint('An internal relation is not necessary when a corresponding FOREIGN KEY relation exists.'|trans) }} + {% endif %} +
      +
      + {% if Util_isForeignKeySupported(tbl_storage_engine) %} +
      + {% endif %} + {% endif %} + + {% if cfg_relation['displaywork'] %} + {% set disp = Relation_getDisplayField(db, table) %} +
      + + +
      + {% endif %} + +
      + + +
      + diff --git a/php/apps/phpmyadmin49/templates/table/relation/dropdown_generate.twig b/php/apps/phpmyadmin49/templates/table/relation/dropdown_generate.twig new file mode 100644 index 00000000..ff179bc9 --- /dev/null +++ b/php/apps/phpmyadmin49/templates/table/relation/dropdown_generate.twig @@ -0,0 +1,9 @@ +{{ dropdown_question is not empty ? dropdown_question -}} + diff --git a/php/apps/phpmyadmin49/templates/table/relation/foreign_key_row.twig b/php/apps/phpmyadmin49/templates/table/relation/foreign_key_row.twig new file mode 100644 index 00000000..4e73bf11 --- /dev/null +++ b/php/apps/phpmyadmin49/templates/table/relation/foreign_key_row.twig @@ -0,0 +1,140 @@ + + {# Drop key anchor #} + + {% set js_msg = '' %} + {% set this_params = null %} + {% if one_key['constraint'] is defined %} + {% set drop_fk_query = 'ALTER TABLE ' ~ Util_backquote(db) ~ '.' ~ Util_backquote(table) + ~ ' DROP FOREIGN KEY ' + ~ Util_backquote(one_key['constraint']) ~ ';' + %} + {% set this_params = url_params %} + {% set this_params = { + 'goto': 'tbl_relation.php', + 'back': 'tbl_relation.php', + 'sql_query': drop_fk_query, + 'message_to_show': 'Foreign key constraint %s has been dropped'|trans|format( + one_key['constraint'] + ) + } %} + {% set js_msg = Sanitize_jsFormat( + 'ALTER TABLE ' ~ db ~ '.' ~ table + ~ ' DROP FOREIGN KEY ' + ~ one_key['constraint'] ~ ';' + ) %} + {% endif %} + {% if one_key['constraint'] is defined %} + + {% set drop_url = 'sql.php' ~ Url_getCommon(this_params) %} + {% set drop_str = Util_getIcon('b_drop', 'Drop'|trans) %} + {{ Util_linkOrButton(drop_url, drop_str, {'class': 'drop_foreign_key_anchor ajax'}) }} + {% endif %} + + + + + +
      + {# For ON DELETE and ON UPDATE, the default action + is RESTRICT as per MySQL doc; however, a SHOW CREATE TABLE + won't display the clause if it's set as RESTRICT. #} + {% set on_delete = one_key['on_delete'] is defined + ? one_key['on_delete'] : 'RESTRICT' %} + {% set on_update = one_key['on_update'] is defined + ? one_key['on_update'] : 'RESTRICT' %} + + {% include 'table/relation/dropdown_generate.twig' with { + 'dropdown_question': 'ON DELETE', + 'select_name': 'on_delete[' ~ i ~ ']', + 'choices': options_array, + 'selected_value': on_delete + } only %} + + + {% include 'table/relation/dropdown_generate.twig' with { + 'dropdown_question': 'ON UPDATE', + 'select_name': 'on_update[' ~ i ~ ']', + 'choices': options_array, + 'selected_value': on_update + } only %} + +
      + + + {% if one_key['index_list'] is defined %} + {% for key, column in one_key['index_list'] %} + + {% include 'table/relation/dropdown_generate.twig' with { + 'dropdown_question': '', + 'select_name': 'foreign_key_fields_name[' ~ i ~ '][]', + 'choices': column_array, + 'selected_value': column + } only %} + + {% endfor %} + {% else %} + + {% include 'table/relation/dropdown_generate.twig' with { + 'dropdown_question': '', + 'select_name': 'foreign_key_fields_name[' ~ i ~ '][]', + 'choices': column_array, + 'selected_value': '' + } only %} + + {% endif %} + + {% trans '+ Add column' %} + + + {% set tables = [] %} + {% if foreign_db %} + {% set tables = Relation_getTables(foreign_db, tbl_storage_engine) %} + {% endif %} + + + {% include 'table/relation/relational_dropdown.twig' with { + 'name': 'destination_foreign_db[' ~ i ~ ']', + 'title': 'Database'|trans, + 'values': databases, + 'foreign': foreign_db + } only %} + + + + + {% include 'table/relation/relational_dropdown.twig' with { + 'name': 'destination_foreign_table[' ~ i ~ ']', + 'title': 'Table'|trans, + 'values': tables, + 'foreign': foreign_table + } only %} + + + + {% if foreign_db and foreign_table %} + {% for foreign_column in one_key['ref_index_list'] %} + + {% include 'table/relation/relational_dropdown.twig' with { + 'name': 'destination_foreign_column[' ~ i ~ '][]', + 'title': 'Column'|trans, + 'values': unique_columns, + 'foreign': foreign_column + } only %} + + {% endfor %} + {% else %} + + {% include 'table/relation/relational_dropdown.twig' with { + 'name': 'destination_foreign_column[' ~ i ~ '][]', + 'title': 'Column'|trans, + 'values': [], + 'foreign': '' + } only %} + + {% endif %} + + diff --git a/php/apps/phpmyadmin49/templates/table/relation/internal_relational_row.twig b/php/apps/phpmyadmin49/templates/table/relation/internal_relational_row.twig new file mode 100644 index 00000000..f588f229 --- /dev/null +++ b/php/apps/phpmyadmin49/templates/table/relation/internal_relational_row.twig @@ -0,0 +1,30 @@ + + + {{ myfield }} + + + + + {% include 'table/relation/relational_dropdown.twig' with { + 'name': 'destination_db[' ~ myfield_md5 ~ ']', + 'title': 'Database'|trans, + 'values': databases, + 'foreign': foreign_db + } only %} + + {% include 'table/relation/relational_dropdown.twig' with { + 'name': 'destination_table[' ~ myfield_md5 ~ ']', + 'title': 'Table'|trans, + 'values': tables, + 'foreign': foreign_table + } only %} + + {% include 'table/relation/relational_dropdown.twig' with { + 'name': 'destination_column[' ~ myfield_md5 ~ ']', + 'title': 'Column'|trans, + 'values': columns, + 'foreign': foreign_column + } only %} + + diff --git a/php/apps/phpmyadmin49/templates/table/relation/relational_dropdown.twig b/php/apps/phpmyadmin49/templates/table/relation/relational_dropdown.twig new file mode 100644 index 00000000..9d5f3c97 --- /dev/null +++ b/php/apps/phpmyadmin49/templates/table/relation/relational_dropdown.twig @@ -0,0 +1,18 @@ + diff --git a/php/apps/phpmyadmin49/templates/table/search/column_comparison_operators.twig b/php/apps/phpmyadmin49/templates/table/search/column_comparison_operators.twig new file mode 100644 index 00000000..b72e530c --- /dev/null +++ b/php/apps/phpmyadmin49/templates/table/search/column_comparison_operators.twig @@ -0,0 +1,3 @@ + diff --git a/php/apps/phpmyadmin49/templates/table/search/fields_table.twig b/php/apps/phpmyadmin49/templates/table/search/fields_table.twig new file mode 100644 index 00000000..c2da41a6 --- /dev/null +++ b/php/apps/phpmyadmin49/templates/table/search/fields_table.twig @@ -0,0 +1,23 @@ + + {% include 'table/search/table_header.twig' with { + 'geom_column_flag': geom_column_flag + } only %} + + {% if search_type == 'zoom' %} + {% include 'table/search/rows_zoom.twig' with { + 'self': self, + 'column_names': column_names, + 'criteria_column_names': criteria_column_names, + 'criteria_column_types': criteria_column_types + } only %} + {% else %} + {% include 'table/search/rows_normal.twig' with { + 'self': self, + 'geom_column_flag': geom_column_flag, + 'column_names': column_names, + 'column_types': column_types, + 'column_collations': column_collations + } only %} + {% endif %} + +
      diff --git a/php/apps/phpmyadmin49/templates/table/search/form_tag.twig b/php/apps/phpmyadmin49/templates/table/search/form_tag.twig new file mode 100644 index 00000000..827c051f --- /dev/null +++ b/php/apps/phpmyadmin49/templates/table/search/form_tag.twig @@ -0,0 +1,4 @@ +
      + {{ Url_getHiddenInputs(db, table) }} + + diff --git a/php/apps/phpmyadmin49/templates/table/search/geom_func.twig b/php/apps/phpmyadmin49/templates/table/search/geom_func.twig new file mode 100644 index 00000000..3ac3000a --- /dev/null +++ b/php/apps/phpmyadmin49/templates/table/search/geom_func.twig @@ -0,0 +1,19 @@ +{# Displays 'Function' column if it is present #} + + {% set geom_types = Util_getGISDatatypes() %} + {% if column_types[column_index] in geom_types %} + + {% else %} +   + {% endif %} + diff --git a/php/apps/phpmyadmin49/templates/table/search/input_box.twig b/php/apps/phpmyadmin49/templates/table/search/input_box.twig new file mode 100644 index 00000000..d82c9a84 --- /dev/null +++ b/php/apps/phpmyadmin49/templates/table/search/input_box.twig @@ -0,0 +1,97 @@ +{# Get inputbox based on different column types (Foreign key, geometrical, enum) #} +{% if foreigners and Relation_searchColumnInForeigners(foreigners, column_name) %} + {% if foreign_data['disp_row'] is iterable %} + + {% elseif foreign_data['foreign_link'] == true %} + + + {{ titles['Browse']|replace({"'": "\\'"})|raw }} + + {% endif %} +{% elseif column_type in Util_getGISDatatypes() %} + + {% if in_fbs %} + {% set edit_url = 'gis_data_editor.php' ~ Url_getCommon() %} + {% set edit_str = Util_getIcon('b_edit', 'Edit/Insert'|trans) %} + + {{ Util_linkOrButton(edit_url, edit_str, [], '_blank') }} + + {% endif %} +{% elseif column_type starts with 'enum' + or (column_type starts with 'set' and in_zoom_search_edit) %} + {% set in_zoom_search_edit = false %} + {% set value = column_type|e|slice(5, -1)|replace({''': ''})|split(', ') %} + {% set cnt_value = value|length %} + {# + Enum in edit mode --> dropdown + Enum in search mode --> multiselect + Set in edit mode --> multiselect + Set in search mode --> input (skipped here, so the 'else' section would handle it) + #} + {% if (column_type starts with 'enum' and not in_zoom_search_edit) + or (column_type starts with 'set' and in_zoom_search_edit) %} + + {% endif %} + {# Add select options #} + + {% for i in 0..cnt_value - 1 %} + {% if criteria_values[column_index] is defined + and criteria_values[column_index] is iterable + and value[i] in criteria_values[column_index] %} + + {% else %} + + {% endif %} + {% endfor %} + +{% else %} + {% set the_class = 'textfield' %} + {% if column_type == 'date' %} + {% set the_class = the_class ~ ' datefield' %} + {% elseif column_type == 'datetime' or column_type starts with 'timestamp' %} + {% set the_class = the_class ~ ' datetimefield' %} + {% elseif column_type starts with 'bit' %} + {% set the_class = the_class ~ ' bit' %} + {% endif %} + +{% endif %} diff --git a/php/apps/phpmyadmin49/templates/table/search/options.twig b/php/apps/phpmyadmin49/templates/table/search/options.twig new file mode 100644 index 00000000..99ae9851 --- /dev/null +++ b/php/apps/phpmyadmin49/templates/table/search/options.twig @@ -0,0 +1,67 @@ +{{ Util_getDivForSliderEffect('searchoptions', 'Options'|trans) }} + +{# Displays columns select list for selecting distinct columns in the search #} +
      + + {% trans 'Select columns (at least one):' %} + + + + +
      + +{# Displays input box for custom 'Where' clause to be used in the search #} +
      + + {% trans 'Or' %} + {% trans 'Add search conditions (body of the "where" clause):' %} + + {{ Util_showMySQLDocu('Functions') }} + +
      + +{# Displays option of changing default number of rows displayed per page #} +
      + {% trans 'Number of rows per page' %} + +
      + +{# Displays option for ordering search results by a column value (Asc or Desc) #} +
      + {% trans 'Display order:' %} + + + {{ Util_getRadioFields( + 'order', + { + 'ASC': 'Ascending'|trans, + 'DESC': 'Descending'|trans + }, + 'ASC', + false, + true, + 'formelement' + ) }} + +
      +
      diff --git a/php/apps/phpmyadmin49/templates/table/search/options_zoom.twig b/php/apps/phpmyadmin49/templates/table/search/options_zoom.twig new file mode 100644 index 00000000..328eb4ae --- /dev/null +++ b/php/apps/phpmyadmin49/templates/table/search/options_zoom.twig @@ -0,0 +1,43 @@ + + {# Select options for data label #} + + + + + {# Inputbox for changing default maximum rows to plot #} + + + + +
      + + + +
      + + + +
      diff --git a/php/apps/phpmyadmin49/templates/table/search/replace_preview.twig b/php/apps/phpmyadmin49/templates/table/search/replace_preview.twig new file mode 100644 index 00000000..dab10f08 --- /dev/null +++ b/php/apps/phpmyadmin49/templates/table/search/replace_preview.twig @@ -0,0 +1,39 @@ + + {{ Url_getHiddenInputs(db, table) }} + + + + + + +
      + {% trans 'Find and replace - preview' %} + + + + + + + + + + {% if result is iterable %} + {% for row in result %} + + {# count #} + {# original #} + {# replaced #} + + {% endfor %} + {% endif %} + +
      {% trans 'Count' %}{% trans 'Original string' %}{% trans 'Replaced string' %}
      {{ row[2] }}{{ row[0] }}{{ row[1] }}
      +
      + +
      + +
      +
      diff --git a/php/apps/phpmyadmin49/templates/table/search/rows_normal.twig b/php/apps/phpmyadmin49/templates/table/search/rows_normal.twig new file mode 100644 index 00000000..37e3089c --- /dev/null +++ b/php/apps/phpmyadmin49/templates/table/search/rows_normal.twig @@ -0,0 +1,39 @@ +{% for column_index in 0..column_names|length - 1 %} + + {# If 'Function' column is present trying to change comment #} + {% if geom_column_flag %} + {% include 'table/search/geom_func.twig' with { + 'column_index': column_index, + 'column_types': column_types + } only %} + {% endif %} + {# Displays column's name, type, collation and value #} + + {{ column_names[column_index] }} + + {% set properties = self.getColumnProperties(column_index, column_index) %} + + {{ properties['type'] }} + + + {{ properties['collation'] }} + + + {{ properties['func']|raw }} + + {# here, the data-type attribute is needed for a date/time picker #} + + {{ properties['value']|raw }} + {# Displays hidden fields #} + + + + + +{% endfor %} diff --git a/php/apps/phpmyadmin49/templates/table/search/rows_zoom.twig b/php/apps/phpmyadmin49/templates/table/search/rows_zoom.twig new file mode 100644 index 00000000..79ef78f3 --- /dev/null +++ b/php/apps/phpmyadmin49/templates/table/search/rows_zoom.twig @@ -0,0 +1,74 @@ +{# Get already set search criteria (if any) #} +{% set type = [] %} +{% set collation = [] %} +{% set func = [] %} +{% set value = [] %} + +{% for i in 0..3 %} + {# After X-Axis and Y-Axis column rows, display additional criteria option #} + {% if i == 2 %} + + + {% trans 'Additional search criteria' %} + + + {% endif %} + + + + + {% if criteria_column_names is defined + and criteria_column_names[i] != 'pma_null' %} + {% set key = array_search(criteria_column_names[i], column_names) %} + {% set properties = self.getColumnProperties(i, key) %} + {% set type = type|merge({i: properties['type']}) %} + {% set collation = collation|merge({i: properties['collation']}) %} + {% set func = func|merge({i: properties['func']}) %} + {% set value = value|merge({i: properties['value']}) %} + {% endif %} + {# Column type #} + + {{ type[i] is defined ? type[i] }} + + {# Column Collation #} + + {{ collation[i] is defined ? collation[i] }} + + {# Select options for column operators #} + + {{ func[i] is defined ? func[i]|raw }} + + {# Inputbox for search criteria value #} + + + + {{ value[i] is defined ? value[i]|raw }} + {# Displays hidden fields #} + + + + +{% endfor %} diff --git a/php/apps/phpmyadmin49/templates/table/search/search_and_replace.twig b/php/apps/phpmyadmin49/templates/table/search/search_and_replace.twig new file mode 100644 index 00000000..530ea6df --- /dev/null +++ b/php/apps/phpmyadmin49/templates/table/search/search_and_replace.twig @@ -0,0 +1,25 @@ +{% trans 'Find:' %} + +{% trans 'Replace with:' %} + + +{% trans 'Column:' %} + + +{% include 'checkbox.twig' with { + 'html_field_id': 'useRegex', + 'html_field_name': 'useRegex', + 'label': 'Use regular expression'|trans, + 'checked': false, + 'onclick': false +} only %} diff --git a/php/apps/phpmyadmin49/templates/table/search/selection_form.twig b/php/apps/phpmyadmin49/templates/table/search/selection_form.twig new file mode 100644 index 00000000..64d99dc8 --- /dev/null +++ b/php/apps/phpmyadmin49/templates/table/search/selection_form.twig @@ -0,0 +1,102 @@ +{% if search_type == 'zoom' %} + {% include 'table/search/form_tag.twig' with { + 'script_name': 'tbl_zoom_select.php', + 'form_id': 'zoom_search_form', + 'db': db, + 'table': table, + 'goto': goto + } only %} + +{% elseif search_type == 'normal' %} + {% include 'table/search/form_tag.twig' with { + 'script_name': 'tbl_select.php', + 'form_id': 'tbl_search_form', + 'db': db, + 'table': table, + 'goto': goto + } only %} + +{% elseif search_type == 'replace' %} + {% include 'table/search/form_tag.twig' with { + 'script_name': 'tbl_find_replace.php', + 'form_id': 'find_replace_form', + 'db': db, + 'table': table, + 'goto': goto + } only %} +
      +
      + + {% trans 'Find and replace' %} + + {% include 'table/search/search_and_replace.twig' with { + 'column_names': column_names, + 'column_types': column_types, + 'sql_types': sql_types + } only %} +
      +
      +{% else %} + {% include 'table/search/form_tag.twig' with { + 'script_name': '', + 'form_id': '', + 'db': db, + 'table': table, + 'goto': goto + } only %} +{% endif %} + +{# Displays selection form's footer elements #} +
      + +
      + +
      diff --git a/php/apps/phpmyadmin49/templates/table/search/table_header.twig b/php/apps/phpmyadmin49/templates/table/search/table_header.twig new file mode 100644 index 00000000..4093f5bf --- /dev/null +++ b/php/apps/phpmyadmin49/templates/table/search/table_header.twig @@ -0,0 +1,12 @@ + + + {% if geom_column_flag %} + {% trans 'Function' %} + {% endif %} + {% trans 'Column' %} + {% trans 'Type' %} + {% trans 'Collation' %} + {% trans 'Operator' %} + {% trans 'Value' %} + + diff --git a/php/apps/phpmyadmin49/templates/table/search/zoom_result_form.twig b/php/apps/phpmyadmin49/templates/table/search/zoom_result_form.twig new file mode 100644 index 00000000..d76ae19a --- /dev/null +++ b/php/apps/phpmyadmin49/templates/table/search/zoom_result_form.twig @@ -0,0 +1,85 @@ +
      + {{ Url_getHiddenInputs(db, table) }} + + + +
      + {% trans 'Browse/Edit the points' %} + + {# JSON encode the data(query result) #} +
      + {% if zoom_submit and data is not empty %} +
      +
      + + {% trans 'How to use' %} + +
      +
      + {{ data_json }} +
      +
      + +
      + {% endif %} +
      + + {# Displays rows in point edit form #} +
      + + + + + + + + + + {% for column_index in 0..column_names|length - 1 %} + {% set field_popup = column_names[column_index] %} + {% set foreign_data = Relation_getForeignData( + foreigners, + field_popup, + false, + '', + '' + ) %} + + + {# Null checkbox if column can be null #} + + {# Column's Input box #} + + + {% endfor %} + +
      {% trans 'Column' %}{% trans 'Null' %}{% trans 'Value' %}
      {{ column_names[column_index] }} + {% if column_null_flags[column_index] == 'YES' %} + + {% endif %} + + {% include 'table/search/input_box.twig' with { + 'str': '', + 'column_type': column_types[column_index], + 'column_id': column_types[column_index] ? 'edit_fieldID_' : 'fieldID_', + 'in_zoom_search_edit': true, + 'foreigners': foreigners, + 'column_name': field_popup, + 'column_name_hash': md5(field_popup), + 'foreign_data': foreign_data, + 'table': table, + 'column_index': column_index, + 'foreign_max_limit': foreign_max_limit, + 'criteria_values': '', + 'db': db, + 'titles': titles, + 'in_fbs': false + } only %} +
      +
      + + diff --git a/php/apps/phpmyadmin49/templates/table/secondary_tabs.twig b/php/apps/phpmyadmin49/templates/table/secondary_tabs.twig new file mode 100644 index 00000000..a38b039e --- /dev/null +++ b/php/apps/phpmyadmin49/templates/table/secondary_tabs.twig @@ -0,0 +1,17 @@ +{% if cfg_relation['relwork'] or is_foreign_key_supported %} +
        + {{ Util_getHtmlTab({ + 'icon': 'b_props', + 'link': 'tbl_structure.php', + 'text': 'Table structure'|trans, + 'id': 'table_strucuture_id' + }, url_params) }} + {{ Util_getHtmlTab({ + 'icon': 'b_relations', + 'link': 'tbl_relation.php', + 'text': 'Relation view'|trans, + 'id': 'table_relation_id' + }, url_params) }} +
      +
      +{% endif %} diff --git a/php/apps/phpmyadmin49/templates/table/structure/action_row_in_structure_table.twig b/php/apps/phpmyadmin49/templates/table/structure/action_row_in_structure_table.twig new file mode 100644 index 00000000..45bf4a3f --- /dev/null +++ b/php/apps/phpmyadmin49/templates/table/structure/action_row_in_structure_table.twig @@ -0,0 +1,31 @@ +
    • +{% if type == 'text' + or type == 'blob' + or tbl_storage_engine == 'ARCHIVE' + or has_field %} + {{ titles['No' ~ action]|raw }} +{% else %} + + {{ titles[action]|raw }} + +{% endif %} +
    • diff --git a/php/apps/phpmyadmin49/templates/table/structure/actions_in_table_structure.twig b/php/apps/phpmyadmin49/templates/table/structure/actions_in_table_structure.twig new file mode 100644 index 00000000..6607f851 --- /dev/null +++ b/php/apps/phpmyadmin49/templates/table/structure/actions_in_table_structure.twig @@ -0,0 +1,140 @@ +
        + {% if hide_structure_actions %} + + {% endif %} +
      diff --git a/php/apps/phpmyadmin49/templates/table/structure/add_column.twig b/php/apps/phpmyadmin49/templates/table/structure/add_column.twig new file mode 100644 index 00000000..1d53cc96 --- /dev/null +++ b/php/apps/phpmyadmin49/templates/table/structure/add_column.twig @@ -0,0 +1,24 @@ +
      + {{ Url_getHiddenInputs(db, table) }} + {% if Util_showIcons('ActionLinksMode') %} + {{ Util_getImage('b_insrow', 'Add column'|trans) }}  + {% endif %} + {% set num_fields -%} + + {%- endset %} + {{ 'Add %s column(s)'|trans|format(num_fields)|raw }} +   + {# I tried displaying the drop-down inside the label but with Firefox the drop-down was blinking #} + + +
      diff --git a/php/apps/phpmyadmin49/templates/table/structure/check_all_table_column.twig b/php/apps/phpmyadmin49/templates/table/structure/check_all_table_column.twig new file mode 100644 index 00000000..57e6e112 --- /dev/null +++ b/php/apps/phpmyadmin49/templates/table/structure/check_all_table_column.twig @@ -0,0 +1,93 @@ + diff --git a/php/apps/phpmyadmin49/templates/table/structure/display_partitions.twig b/php/apps/phpmyadmin49/templates/table/structure/display_partitions.twig new file mode 100644 index 00000000..3bcbbe2f --- /dev/null +++ b/php/apps/phpmyadmin49/templates/table/structure/display_partitions.twig @@ -0,0 +1,145 @@ +
      +
      + + {% trans 'Partitions' %} + {{ Util_showMySQLDocu('partitioning') }} + + {% if partitions is empty %} + {{ Message_notice('No partitioning defined!'|trans) }} + {% else %} +

      + {% trans 'Partitioned by:' %} + {{ partition_method }}({{ partition_expression }}) +

      + {% if has_sub_partitions %} +

      + {% trans 'Sub partitioned by:' %} + {{ sub_partition_method }}({{ sub_partition_expression }}) +

      + {% endif %} + + + + + + {% if has_description %} + + {% endif %} + + + + + + + + + {% for partition in partitions %} + + {% if has_sub_partitions %} + + + {% else %} + + {% endif %} + + {% if has_description %} + + {% endif %} + + + + + {% for action, icon in action_icons %} + + {% endfor %} + + {% if has_sub_partitions %} + {% for sub_partition in partition.getSubPartitions() %} + + + + + {% if has_description %} + + {% endif %} + + + + + + + {% endfor %} + {% endif %} + + {% endfor %} + +
      #{% trans 'Partition' %}{% trans 'Expression' %}{% trans 'Rows' %}{% trans 'Data length' %}{% trans 'Index length' %}{% trans 'Comment' %} + {% trans 'Action' %} +
      {{ partition.getOrdinal() }}{{ partition.getOrdinal() }}{{ partition.getName() }} + + {{- partition.getExpression() -}} + {{- partition.getMethod() == 'LIST' ? ' IN (' : ' < ' -}} + {{- partition.getDescription() -}} + {{- partition.getMethod() == 'LIST' ? ')' -}} + + {{ partition.getRows() }} + {% set data_length = Util_formatByteDown( + partition.getDataLength(), + 3, + 1 + ) %} + {{ data_length[0] }} + {{ data_length[1] }} + + {% set index_length = Util_formatByteDown( + partition.getIndexLength(), + 3, + 1 + ) %} + {{ index_length[0] }} + {{ index_length[1] }} + {{ partition.getComment() }} + + {{ icon|raw }} + +
      {{ sub_partition.getOrdinal() }}{{ sub_partition.getName() }}{{ sub_partition.getRows() }} + {% set data_length = Util_formatByteDown( + sub_partition.getDataLength(), + 3, + 1 + ) %} + {{ data_length[0] }} + {{ data_length[1] }} + + {% set index_length = Util_formatByteDown( + sub_partition.getIndexLength(), + 3, + 1 + ) %} + {{ index_length[0] }} + {{ index_length[1] }} + {{ sub_partition.getComment() }}
      + {% endif %} +

      + +
      diff --git a/php/apps/phpmyadmin49/templates/table/structure/display_structure.twig b/php/apps/phpmyadmin49/templates/table/structure/display_structure.twig new file mode 100644 index 00000000..82a8205c --- /dev/null +++ b/php/apps/phpmyadmin49/templates/table/structure/display_structure.twig @@ -0,0 +1,228 @@ +
      + {{ Url_getHiddenInputs(db, table) }} + +
      + + {# Table header #} + {% include 'table/structure/table_structure_header.twig' with { + 'db_is_system_schema': db_is_system_schema, + 'tbl_is_view': tbl_is_view, + 'show_column_comments': show_column_comments + } only %} + + {# Table body #} + {% set rownum = 0 %} + {% set columns_list = [] %} + {% for row in fields %} + {% set rownum = rownum + 1 %} + {% set columns_list = columns_list|merge([row['Field']]) %} + {% set field_charset = row['Collation'] %} + + {% set extracted_columnspec = Util_extractColumnSpec(row['Type']) %} + {% set attribute = extracted_columnspec['attribute'] %} + {% if strpos(row['Extra'], 'on update CURRENT_TIMESTAMP') + is not same as(false) %} + {% set attribute = 'on update CURRENT_TIMESTAMP' %} + {% endif %} + + {% if row['Default'] is null %} + {% if row['Null'] == 'YES' %} + {% set row = row|merge({'Default': 'NULL'}) %} + {% endif %} + {% else %} + {% set row = row|merge({'Default': row['Default']|e}) %} + {% endif %} + + {% set field_name = row['Field']|e %} + {% set displayed_field_name = field_name %} + {# For column comments #} + {% set comments = '' %} + {# Underline commented fields and display a hover-title (CSS only) #} + + {% if comments_map[row['Field']] is defined %} + {% set displayed_field_name -%} + + {{- field_name|raw -}} + + {%- endset %} + {% set comments = comments_map[row['Field']] %} + {% endif %} + + {% if primary and primary.hasColumn(field_name) %} + {% set displayed_field_name = displayed_field_name ~ Util_getImage( + 'b_primary', 'Primary'|trans + ) %} + {% endif %} + {% if field_name in columns_with_index %} + {% set displayed_field_name = displayed_field_name ~ Util_getImage( + 'bd_primary', 'Index'|trans + ) %} + {% endif %} + + {% include 'table/structure/table_structure_row.twig' with { + 'row': row, + 'rownum': rownum, + 'displayed_field_name': preg_replace( + '/[\\x00-\\x1F]/', + '⁑', + displayed_field_name + ), + 'type_nowrap': Util_getClassForType(extracted_columnspec['type']), + 'extracted_columnspec': extracted_columnspec, + 'attribute': attribute, + 'tbl_is_view': tbl_is_view, + 'db_is_system_schema': db_is_system_schema, + 'url_query': url_query, + 'titles': titles, + 'table': table, + 'tbl_storage_engine': tbl_storage_engine, + 'field_charset': field_charset, + 'comments': comments, + 'show_column_comments': show_column_comments, + 'relation_commwork': relation_commwork, + 'relation_mimework': relation_mimework, + 'browse_mime': browse_mime + } only %} + {% if not tbl_is_view and not db_is_system_schema %} + {% include 'table/structure/actions_in_table_structure.twig' with { + 'row': row, + 'rownum': rownum, + 'extracted_columnspec': extracted_columnspec, + 'type': extracted_columnspec['print_type'] is not empty ? extracted_columnspec['print_type'], + 'tbl_storage_engine': tbl_storage_engine, + 'primary': primary, + 'field_name': field_name, + 'url_query': url_query, + 'titles': titles, + 'columns_with_unique_index': columns_with_unique_index, + 'is_in_central_columns': row['Field'] in central_list ? true : false, + 'central_columns_work': central_columns_work, + 'table': table, + 'hide_structure_actions': hide_structure_actions, + 'mysql_int_version': mysql_int_version + } only %} + {% endif %} + + {% endfor %} + +
      +
      + {% include 'table/structure/check_all_table_column.twig' with { + 'pma_theme_image': pma_theme_image, + 'text_dir': text_dir, + 'tbl_is_view': tbl_is_view, + 'db_is_system_schema': db_is_system_schema, + 'tbl_storage_engine': tbl_storage_engine, + 'central_columns_work': central_columns_work + } only %} +
      + +{% include 'table/structure/move_columns_dialog.twig' with { + 'db': db, + 'table': table +} only %} +{# Work on the table #} + +{% if not tbl_is_view and not db_is_system_schema %} + {% include 'table/structure/add_column.twig' with { + 'columns_list': columns_list, + 'db': db, + 'table': table + } only %} +{% endif %} + +{# Displays indexes #} +{% if not tbl_is_view and not db_is_system_schema + and 'ARCHIVE' != tbl_storage_engine %} + {{ Index_getHtmlForDisplayIndexes() }} +{% endif %} + +{# Display partition details #} +{% if have_partitioning %} + {# Detect partitioning #} + {% if partition_names is not empty and partition_names[0] is not null %} + {% set partitions = Partition_getPartitions(db, table) %} + {% set first_partition = partitions[0] %} + {% set range_or_list = first_partition.getMethod() == 'RANGE' + or first_partition.getMethod() == 'RANGE COLUMNS' + or first_partition.getMethod() == 'LIST' + or first_partition.getMethod() == 'LIST COLUMNS' %} + {% set sub_partitions = first_partition.getSubPartitions() %} + {% set has_sub_partitions = first_partition.hasSubPartitions() %} + {% if has_sub_partitions %} + {% set first_sub_partition = sub_partitions[0] %} + {% endif %} + + {% set action_icons = { + 'ANALYZE': Util_getIcon('b_search', 'Analyze'|trans), + 'CHECK': Util_getIcon('eye', 'Check'|trans), + 'OPTIMIZE': Util_getIcon('normalize', 'Optimize'|trans), + 'REBUILD': Util_getIcon('s_tbl', 'Rebuild'|trans), + 'REPAIR': Util_getIcon('b_tblops', 'Repair'|trans), + 'TRUNCATE': Util_getIcon('b_empty', 'Truncate'|trans), + } %} + {% if range_or_list %} + {% set action_icons = action_icons|merge({'DROP': Util_getIcon('b_drop', 'Drop'|trans)}) %} + {% endif %} + + {{ Util_getDivForSliderEffect('partitions', 'Partitions'|trans) }} + + {% set remove_sql = 'ALTER TABLE ' ~ Util_backquote(table) ~ ' REMOVE PARTITIONING' %} + {% set remove_url = 'sql.php' ~ url_query ~ '&sql_query=' ~ remove_sql|url_encode %} + + {% include 'table/structure/display_partitions.twig' with { + 'db': db, + 'table': table, + 'url_query': url_query, + 'partitions': partitions, + 'partition_method': first_partition.getMethod(), + 'partition_expression': first_partition.getExpression(), + 'has_description': first_partition.getDescription() is not empty, + 'has_sub_partitions': has_sub_partitions, + 'sub_partition_method': has_sub_partitions ? first_sub_partition.getMethod(), + 'sub_partition_expression': has_sub_partitions ? first_sub_partition.getExpression(), + 'action_icons': action_icons, + 'range_or_list': range_or_list, + 'remove_url': remove_url + } only %} + {% else %} + {% include 'table/structure/display_partitions.twig' with { + 'db': db, + 'table': table + } only %} + {% endif %} + {# For closing Slider effect div #} +
      +{% endif %} + +{# Displays Space usage and row statistics #} +{% if show_stats %} + {{ table_stats|raw }} +{% endif %} +
      diff --git a/php/apps/phpmyadmin49/templates/table/structure/display_table_stats.twig b/php/apps/phpmyadmin49/templates/table/structure/display_table_stats.twig new file mode 100644 index 00000000..14e6ab33 --- /dev/null +++ b/php/apps/phpmyadmin49/templates/table/structure/display_table_stats.twig @@ -0,0 +1,79 @@ +
      +
      + {% trans 'Information' %} + {% if showtable['TABLE_COMMENT'] %} +

      + {% trans 'Table comments:' %} + {{ showtable['TABLE_COMMENT'] }} +

      + {% endif %} + + + {% if not tbl_is_view and not db_is_system_schema %} + + + + + + + + + + {% if index_size is defined %} + + + + + + {% endif %} + + {% if free_size is defined %} + + + + + + + + + + + {% endif %} + + {% if tot_size is defined and mergetable == false %} + + + + + + {% endif %} + + {# Optimize link if overhead #} + {% if free_size is defined + and (tbl_storage_engine == 'MYISAM' + or tbl_storage_engine == 'ARIA' + or tbl_storage_engine == 'MARIA' + or tbl_storage_engine == 'BDB') %} + + + + {% endif %} + +
      {% trans 'Space usage' %}
      {% trans 'Data' %}{{ data_size }}{{ data_unit }}
      {% trans 'Index' %}{{ index_size }}{{ index_unit }}
      {% trans 'Overhead' %}{{ free_size }}{{ free_unit }}
      {% trans 'Effective' %}{{ effect_size }}{{ effect_unit }}
      {% trans 'Total' %}{{ tot_size }}{{ tot_unit }}
      + {% endif %} + + {% include 'table/structure/row_stats_table.twig' with { + 'showtable': showtable, + 'tbl_collation': tbl_collation, + 'is_innodb': is_innodb, + 'mergetable': mergetable, + 'avg_size': avg_size is defined ? avg_size : null, + 'avg_unit': avg_unit is defined ? avg_unit : null + } only %} +
      +
      diff --git a/php/apps/phpmyadmin49/templates/table/structure/move_columns_dialog.twig b/php/apps/phpmyadmin49/templates/table/structure/move_columns_dialog.twig new file mode 100644 index 00000000..7e7a8e26 --- /dev/null +++ b/php/apps/phpmyadmin49/templates/table/structure/move_columns_dialog.twig @@ -0,0 +1,9 @@ +
      +

      {% trans 'Move the columns by dragging them up and down.' %}

      +
      +
      + {{ Url_getHiddenInputs(db, table) }} +
        +
        +
        +
        diff --git a/php/apps/phpmyadmin49/templates/table/structure/optional_action_links.twig b/php/apps/phpmyadmin49/templates/table/structure/optional_action_links.twig new file mode 100644 index 00000000..262b612c --- /dev/null +++ b/php/apps/phpmyadmin49/templates/table/structure/optional_action_links.twig @@ -0,0 +1,34 @@ +{{ Util_getIcon('b_print', 'Print'|trans, true) }} +{% if not tbl_is_view and not db_is_system_schema %} + {# Only display propose table structure for MySQL < 8.0 #} + {% if mysql_int_version < 80000 or is_mariadb %} + + {{ Util_getIcon( + 'b_tblanalyse', + 'Propose table structure'|trans, + true + ) }} + + {{ Util_showMySQLDocu('procedure_analyse') }} + {% endif %} + {% if is_active %} + + {{ Util_getIcon('eye', 'Track table'|trans, true) }} + + {% endif %} + + {{ Util_getIcon('b_move', 'Move columns'|trans, true) }} + + + {{ Util_getIcon('normalize', 'Normalize'|trans, true) }} + +{% endif %} +{% if tbl_is_view and not db_is_system_schema %} + {% if is_active %} + + {{ Util_getIcon('eye', 'Track view'|trans, true) }} + + {% endif %} +{% endif %} diff --git a/php/apps/phpmyadmin49/templates/table/structure/partition_definition_form.twig b/php/apps/phpmyadmin49/templates/table/structure/partition_definition_form.twig new file mode 100644 index 00000000..186a6c71 --- /dev/null +++ b/php/apps/phpmyadmin49/templates/table/structure/partition_definition_form.twig @@ -0,0 +1,14 @@ +
        + {{ Url_getHiddenInputs(db, table) }} + + +
        + {% trans 'Edit partitioning' %} + {% include 'columns_definitions/partitions.twig' with { + 'partition_details': partition_details + } only %} +
        +
        + +
        +
        diff --git a/php/apps/phpmyadmin49/templates/table/structure/row_stats_table.twig b/php/apps/phpmyadmin49/templates/table/structure/row_stats_table.twig new file mode 100644 index 00000000..e176e904 --- /dev/null +++ b/php/apps/phpmyadmin49/templates/table/structure/row_stats_table.twig @@ -0,0 +1,95 @@ + + + + {% if showtable['Row_format'] is defined %} + + + {% if showtable['Row_format'] == 'Fixed' %} + + {% elseif showtable['Row_format'] == 'Dynamic' %} + + {% else %} + + {% endif %} + + {% endif %} + + {% if showtable['Create_options'] is not empty %} + + + {% if showtable['Create_options'] == 'partitioned' %} + + {% else %} + + {% endif %} + + {% endif %} + + {% if tbl_collation is not empty %} + + + + + {% endif %} + + {% if not is_innodb and showtable['Rows'] is defined %} + + + + + {% endif %} + + {% if not is_innodb + and showtable['Avg_row_length'] is defined + and showtable['Avg_row_length'] > 0 %} + + + {% set avg_row_length = Util_formatByteDown(showtable['Avg_row_length'], 6, 1) %} + + + {% endif %} + + {% if not is_innodb + and showtable['Data_length'] is defined + and showtable['Rows'] is defined + and showtable['Rows'] > 0 + and mergetable == false %} + + + + + {% endif %} + + {% if showtable['Auto_increment'] is defined %} + + + + + {% endif %} + + {% if showtable['Create_time'] is defined %} + + + + + {% endif %} + + {% if showtable['Update_time'] is defined %} + + + + + {% endif %} + + {% if showtable['Check_time'] is defined %} + + + + + {% endif %} + +
        {% trans 'Row statistics' %}
        {% trans 'Format' %}{% trans 'static' %}{% trans 'dynamic' %}{{ showtable['Row_format'] }}
        {% trans 'Options' %}{% trans 'partitioned' %}{{ showtable['Create_options'] }}
        {% trans 'Collation' %} + + {{ tbl_collation }} + +
        {% trans 'Rows' %}{{ Util_formatNumber(showtable['Rows'], 0) }}
        {% trans 'Row length' %}{{ avg_row_length[0] }} {{ avg_row_length[1] }}
        {% trans 'Row size' %}{{ avg_size }} {{ avg_unit }}
        {% trans 'Next autoindex' %}{{ Util_formatNumber(showtable['Auto_increment'], 0) }}
        {% trans 'Creation' %}{{ Util_localisedDate(showtable['Create_time']|date('U')) }}
        {% trans 'Last update' %}{{ Util_localisedDate(showtable['Update_time']|date('U')) }}
        {% trans 'Last check' %}{{ Util_localisedDate(showtable['Check_time']|date('U')) }}
        diff --git a/php/apps/phpmyadmin49/templates/table/structure/table_structure_header.twig b/php/apps/phpmyadmin49/templates/table/structure/table_structure_header.twig new file mode 100644 index 00000000..5af12980 --- /dev/null +++ b/php/apps/phpmyadmin49/templates/table/structure/table_structure_header.twig @@ -0,0 +1,21 @@ + + + + # + {% trans 'Name' %} + {% trans 'Type' %} + {% trans 'Collation' %} + {% trans 'Attributes' %} + {% trans 'Null' %} + {% trans 'Default' %} + {% if show_column_comments -%} + {% trans 'Comments' %} + {%- endif %} + {% trans 'Extra' %} + {# @see tbl_structure.js, function moreOptsMenuResize() #} + {% if not db_is_system_schema and not tbl_is_view %} + {% trans 'Action' %} + {% endif %} + + diff --git a/php/apps/phpmyadmin49/templates/table/structure/table_structure_row.twig b/php/apps/phpmyadmin49/templates/table/structure/table_structure_row.twig new file mode 100644 index 00000000..e6942d7c --- /dev/null +++ b/php/apps/phpmyadmin49/templates/table/structure/table_structure_row.twig @@ -0,0 +1,59 @@ + + + +{{ rownum }} + + + + + + {{ extracted_columnspec['displayed_type']|raw }} + {% if relation_commwork and relation_mimework and browse_mime + and mime_map[row['Field']]['mimetype'] is defined %} +
        MIME: {{ mime_map[row['Field']]['mimetype']|replace({'_': '/'})|lower }} + {% endif %} +
        + + +{% if field_charset is not empty %} + {{ field_charset }} +{% endif %} + +{{ attribute }} +{{ row['Null'] == 'YES' ? 'Yes'|trans : 'No'|trans }} + + {% if row['Default'] is not null %} + {% if extracted_columnspec['type'] == 'bit' %} + {{ Util_convertBitDefaultValue(row['Default']) }} + {% else %} + {{ row['Default']|raw }} + {% endif %} + {% else %} + {% trans %}None{% context %}None for default{% endtrans %} + {% endif %} + +{% if show_column_comments %} + + {{ comments }} + +{% endif %} +{{ row['Extra']|upper }} +{% if not tbl_is_view and not db_is_system_schema %} + + + {{ titles['Change']|raw }} + + + + + {{ titles['Drop']|raw }} + + +{% endif %} diff --git a/php/apps/phpmyadmin49/templates/table/tracking/activate_deactivate.twig b/php/apps/phpmyadmin49/templates/table/tracking/activate_deactivate.twig new file mode 100644 index 00000000..5817e420 --- /dev/null +++ b/php/apps/phpmyadmin49/templates/table/tracking/activate_deactivate.twig @@ -0,0 +1,27 @@ +
        +
        + {{ Url_getHiddenInputs(db, table) }} +
        + + {% if action == 'activate' %} + {% set legend = 'Activate tracking for %s'|trans %} + {% set value = 'activate_now' %} + {% set button = 'Activate now'|trans %} + {% elseif action == 'deactivate' %} + {% set legend = 'Deactivate tracking for %s'|trans %} + {% set value = 'deactivate_now' %} + {% set button = 'Deactivate now'|trans %} + {% else %} + {% set legend = '' %} + {% set value = '' %} + {% set button = '' %} + {% endif %} + + {{ legend|format(db ~ '.' ~ table) }} + + + + +
        +
        +
        diff --git a/php/apps/phpmyadmin49/templates/table/tracking/create_version.twig b/php/apps/phpmyadmin49/templates/table/tracking/create_version.twig new file mode 100644 index 00000000..4a655769 --- /dev/null +++ b/php/apps/phpmyadmin49/templates/table/tracking/create_version.twig @@ -0,0 +1,79 @@ +
        +
        + {{ Url_getHiddenInputs(db) }} + {% for selected_table in selected %} + + {% endfor %} + +
        + + {% if selected|length == 1 %} + {{ 'Create version %1$s of %2$s'|trans|format( + last_version + 1, + db ~ '.' ~ selected[0] + ) }} + {% else %} + {{ 'Create version %1$s'|trans|format(last_version + 1) }} + {% endif %} + + +

        {% trans 'Track these data definition statements:' %}

        + + {% if type == 'both' or type == 'table' %} + + ALTER TABLE
        + + RENAME TABLE
        + + CREATE TABLE
        + + DROP TABLE
        + {% endif %} + {% if type == 'both' %} +
        + {% endif %} + {% if type == 'both' or type == 'view' %} + + ALTER VIEW
        + + CREATE VIEW
        + + DROP VIEW
        + {% endif %} +
        + + + CREATE INDEX
        + + DROP INDEX
        + +

        {% trans 'Track these data manipulation statements:' %}

        + + INSERT
        + + UPDATE
        + + DELETE
        + + TRUNCATE
        +
        + +
        + + +
        +
        +
        diff --git a/php/apps/phpmyadmin49/templates/table/tracking/report_table.twig b/php/apps/phpmyadmin49/templates/table/tracking/report_table.twig new file mode 100644 index 00000000..fe005266 --- /dev/null +++ b/php/apps/phpmyadmin49/templates/table/tracking/report_table.twig @@ -0,0 +1,27 @@ + + + + + + + + + + + + {% for entry in entries %} + + + + + + + + {% endfor %} + +
        {% trans %}#{% context %}Number{% endtrans %}{% trans 'Date' %}{% trans 'Username' %}{{ header_message }}{% trans 'Action' %}
        {{ entry.line_number }}{{ entry.date }}{{ entry.username }}{{ entry.formated_statement|raw }} + + {{ drop_image_or_text|raw }} + +
        diff --git a/php/apps/phpmyadmin49/templates/table/tracking/selectable_tables.twig b/php/apps/phpmyadmin49/templates/table/tracking/selectable_tables.twig new file mode 100644 index 00000000..eca71bf1 --- /dev/null +++ b/php/apps/phpmyadmin49/templates/table/tracking/selectable_tables.twig @@ -0,0 +1,17 @@ +
        + {{ Url_getHiddenInputs(db, table) }} + + +
        diff --git a/php/apps/phpmyadmin49/templates/table/tracking/structure_snapshot_columns.twig b/php/apps/phpmyadmin49/templates/table/tracking/structure_snapshot_columns.twig new file mode 100644 index 00000000..c98dfbe3 --- /dev/null +++ b/php/apps/phpmyadmin49/templates/table/tracking/structure_snapshot_columns.twig @@ -0,0 +1,56 @@ +

        {% trans 'Structure' %}

        + + + + + + + + + + + + + + + {% set index = 1 %} + {% for field in columns %} + + + {% set index = index + 1 %} + + + + + + + + + {% endfor %} + +
        {% trans %}#{% context %}Number{% endtrans %}{% trans 'Column' %}{% trans 'Type' %}{% trans 'Collation' %}{% trans 'Null' %}{% trans 'Default' %}{% trans 'Extra' %}{% trans 'Comment' %}
        {{ index }} + + {{ field['Field'] }} + {% if field['Key'] == 'PRI' %} + {{ Util_getImage('b_primary', 'Primary'|trans) }} + {% elseif field['Key'] is not empty %} + {{ Util_getImage('bd_primary', 'Index'|trans) }} + {% endif %} + + {{ field['Type'] }}{{ field['Collation'] }}{{ field['Null'] == 'YES' ? 'Yes'|trans : 'No'|trans }} + {% if field['Default'] is defined %} + {% set extracted_columnspec = Util_extractColumnSpec(field['Type']) %} + {% if extracted_columnspec['type'] == 'bit' %} + {# here, $field['Default'] contains something like b'010' #} + {{ Util_convertBitDefaultValue(field['Default']) }} + {% else %} + {{ field['Default'] }} + {% endif %} + {% else %} + {% if field['Null'] == 'YES' %} + NULL + {% else %} + {% trans %}None{% context %}None for default{% endtrans %} + {% endif %} + {% endif %} + {{ field['Extra'] }}{{ field['Comment'] }}
        diff --git a/php/apps/phpmyadmin49/templates/table/tracking/structure_snapshot_indexes.twig b/php/apps/phpmyadmin49/templates/table/tracking/structure_snapshot_indexes.twig new file mode 100644 index 00000000..a919252f --- /dev/null +++ b/php/apps/phpmyadmin49/templates/table/tracking/structure_snapshot_indexes.twig @@ -0,0 +1,33 @@ +

        {% trans 'Indexes' %}

        + + + + + + + + + + + + + + + + {% for index in indexes %} + + + + + + + + + + + + {% endfor %} + +
        {% trans 'Keyname' %}{% trans 'Type' %}{% trans 'Unique' %}{% trans 'Packed' %}{% trans 'Column' %}{% trans 'Cardinality' %}{% trans 'Collation' %}{% trans 'Null' %}{% trans 'Comment' %}
        + {{ index['Key_name'] }} + {{ index['Index_type'] }}{{ index['Non_unique'] == 0 ? 'Yes'|trans : 'No'|trans }}{{ index['Packed'] != '' ? 'Yes'|trans : 'No'|trans }}{{ index['Column_name'] }}{{ index['Cardinality'] }}{{ index['Collation'] }}{{ index['Null'] }}{{ index['Comment'] }}
        diff --git a/php/apps/phpmyadmin49/templates/test/add_data.twig b/php/apps/phpmyadmin49/templates/test/add_data.twig new file mode 100644 index 00000000..d562612b --- /dev/null +++ b/php/apps/phpmyadmin49/templates/test/add_data.twig @@ -0,0 +1,2 @@ +{{ variable1 }} +{{ variable2 }} diff --git a/php/apps/phpmyadmin49/templates/test/echo.twig b/php/apps/phpmyadmin49/templates/test/echo.twig new file mode 100644 index 00000000..9c5fc602 --- /dev/null +++ b/php/apps/phpmyadmin49/templates/test/echo.twig @@ -0,0 +1 @@ +{{ variable -}} diff --git a/php/apps/phpmyadmin49/templates/test/gettext/gettext.twig b/php/apps/phpmyadmin49/templates/test/gettext/gettext.twig new file mode 100644 index 00000000..4fcf61b8 --- /dev/null +++ b/php/apps/phpmyadmin49/templates/test/gettext/gettext.twig @@ -0,0 +1 @@ +{% trans "Text" %} diff --git a/php/apps/phpmyadmin49/templates/test/gettext/notes.twig b/php/apps/phpmyadmin49/templates/test/gettext/notes.twig new file mode 100644 index 00000000..e5a38e4a --- /dev/null +++ b/php/apps/phpmyadmin49/templates/test/gettext/notes.twig @@ -0,0 +1,5 @@ +{% trans %} +Text +{% notes %} +Notes +{% endtrans %} diff --git a/php/apps/phpmyadmin49/templates/test/gettext/pgettext.twig b/php/apps/phpmyadmin49/templates/test/gettext/pgettext.twig new file mode 100644 index 00000000..53650277 --- /dev/null +++ b/php/apps/phpmyadmin49/templates/test/gettext/pgettext.twig @@ -0,0 +1,5 @@ +{% trans %} +Text +{% context %} +Text context +{% endtrans %} diff --git a/php/apps/phpmyadmin49/templates/test/gettext/plural.twig b/php/apps/phpmyadmin49/templates/test/gettext/plural.twig new file mode 100644 index 00000000..5bc44e9e --- /dev/null +++ b/php/apps/phpmyadmin49/templates/test/gettext/plural.twig @@ -0,0 +1,5 @@ +{% trans %} +One table +{% plural table_count %} +{{ count }} tables +{% endtrans %} diff --git a/php/apps/phpmyadmin49/templates/test/gettext/plural_notes.twig b/php/apps/phpmyadmin49/templates/test/gettext/plural_notes.twig new file mode 100644 index 00000000..0c6da182 --- /dev/null +++ b/php/apps/phpmyadmin49/templates/test/gettext/plural_notes.twig @@ -0,0 +1,7 @@ +{% trans %} +One table +{% plural table_count %} +{{ count }} tables +{% notes %} +Number of tables +{% endtrans %} diff --git a/php/apps/phpmyadmin49/templates/test/static.twig b/php/apps/phpmyadmin49/templates/test/static.twig new file mode 100644 index 00000000..703390d1 --- /dev/null +++ b/php/apps/phpmyadmin49/templates/test/static.twig @@ -0,0 +1 @@ +static content \ No newline at end of file diff --git a/php/apps/phpmyadmin49/templates/theme_preview.twig b/php/apps/phpmyadmin49/templates/theme_preview.twig new file mode 100644 index 00000000..9ec60eac --- /dev/null +++ b/php/apps/phpmyadmin49/templates/theme_preview.twig @@ -0,0 +1,16 @@ + diff --git a/php/apps/phpmyadmin49/templates/toggle_button.twig b/php/apps/phpmyadmin49/templates/toggle_button.twig new file mode 100644 index 00000000..2a7cc1ef --- /dev/null +++ b/php/apps/phpmyadmin49/templates/toggle_button.twig @@ -0,0 +1,24 @@ +
        +
        +
        + + + + + + + + + +
        + {{ link_on|raw }} +
        {{ toggle_on }}
        +
         
        + {{ link_off|raw }} +
        {{ toggle_off }}
        +
        + {{ callback }} + {{ text_dir }} +
        +
        +
        diff --git a/php/apps/phpmyadmin49/templates/view_create.twig b/php/apps/phpmyadmin49/templates/view_create.twig new file mode 100644 index 00000000..a89b7951 --- /dev/null +++ b/php/apps/phpmyadmin49/templates/view_create.twig @@ -0,0 +1,120 @@ + +
        +
        + {{ Url_getHiddenInputs(url_params) }} +
        + + {% if ajax_dialog %} + {% trans 'Details' %} + {% else %} + {% if view['operation'] == 'create' %} + {% trans 'Create view' %} + {% else %} + {% trans 'Edit view' %} + {% endif %} + {% endif %} + + + {% if view['operation'] == 'create' %} + + + + + {% endif %} + + + + + + + + + + + + + + + + + {% if view['operation'] == 'create' %} + + + + + {% else %} + + + + {% endif %} + + + + + + + + + + + + + + + + +
        + +
        + +
        {% trans 'Definer' %}
        SQL SECURITY + +
        {% trans 'VIEW name' %} + +
        + +
        {% trans 'Column names' %} + +
        AS +
        + + +
        WITH CHECK OPTION + +
        +
        + + + + + {% if ajax_dialog == false %} + + + {% endif %} + +
        +
        diff --git a/php/apps/phpmyadmin49/themes.php b/php/apps/phpmyadmin49/themes.php new file mode 100644 index 00000000..760015b1 --- /dev/null +++ b/php/apps/phpmyadmin49/themes.php @@ -0,0 +1,35 @@ +getFooter()->setMinimal(); +$header = $response->getHeader(); +$header->setBodyId('bodythemes'); +$header->setTitle('phpMyAdmin - ' . __('Theme')); +$header->disableMenuAndConsole(); + +$hash = '#pma_' . preg_replace('/([0-9]*)\.([0-9]*)\..*/', '\1_\2', PMA_VERSION); +$url = Core::linkURL('https://www.phpmyadmin.net/themes/') . $hash; +$output = '

        phpMyAdmin - ' . __('Theme') . '

        '; +$output .= '

        '; +$output .= ''; +$output .= __('Get more themes!'); +$output .= ''; +$output .= '

        '; +$output .= ThemeManager::getInstance()->getPrintPreviews(); + +$response->addHTML($output); diff --git a/php/apps/phpmyadmin49/themes/dot.gif b/php/apps/phpmyadmin49/themes/dot.gif new file mode 100644 index 00000000..35d42e80 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/dot.gif differ diff --git a/php/apps/phpmyadmin49/themes/original/css/common.css.php b/php/apps/phpmyadmin49/themes/original/css/common.css.php new file mode 100644 index 00000000..88f36e50 --- /dev/null +++ b/php/apps/phpmyadmin49/themes/original/css/common.css.php @@ -0,0 +1,3440 @@ + +/******************************************************************************/ + +/* general tags */ +html { + font-size: getFontSize(); ?> +} + +input, +select, +textarea { + font-size: 1em; +} + + +body { + + font-family: ; + + padding: 0; + margin: 0; + margin-: 240px; + color: ; + background: ; +} + +body#loginform { + margin: 0; +} + +#page_content { + margin: 0 .5em; +} + +.desktop50 { + width: 50%; +} + +.all100 { + width: 100%; +} + +.all85{ + width: 85%; +} + +.auth_config_tbl{ + margin: 0 auto; +} + + + textarea, tt, pre, code { + font-family: ; + } + +h1 { + font-size: 140%; + font-weight: bold; +} + +h2 { + font-size: 120%; + font-weight: bold; +} + +h3 { + font-weight: bold; +} + +a, a:link, +a:visited, +a:active, +button.mult_submit, +.checkall_box+label { + text-decoration: none; + color: #0000FF; + cursor: pointer; +} + +a:hover, +button.mult_submit:hover, +button.mult_submit:focus, +.checkall_box+label:hover { + text-decoration: underline; + color: #FF0000; +} + +dfn { + font-style: normal; +} + +dfn:hover { + font-style: normal; + cursor: help; +} + +th { + font-weight: bold; + color: ; + background: ; +} + +a img { + border: 0; +} + +hr { + color: ; + background-color: ; + border: 0; + height: 1px; +} + +form { + padding: 0; + margin: 0; + display: inline; +} + +textarea { + overflow: visible; +} + +fieldset { + margin-top: 1em; + border: solid 1px; + padding: 0.5em; + background: ; +} + +fieldset fieldset { + margin: 0.8em; +} + +fieldset legend { + font-weight: bold; + color: #444444; + background-color: ; +} + +.some-margin { + margin: .5em; + margin-top: 1em; +} + +/* buttons in some browsers (eg. Konqueror) are block elements, + this breaks design */ +button { + display: inline; +} + +table caption, +table th, +table td { + padding: 0.1em 0.5em 0.1em 0.5em; + margin: 0.1em; + vertical-align: top; +} + +img, +input, +select, +button { + vertical-align: middle; +} + +/******************************************************************************/ +/* classes */ +.clearfloat { + clear: both; +} + +.floatleft { + float: ; + margin-: 1em; +} + +.floatright { + float: ; +} + +table.nospacing { + border-spacing: 0; +} + +table.nopadding tr th, table.nopadding tr td { + padding: 0; +} + +th.left, td.left { + text-align: left; +} + +th.center, td.center { + text-align: center; +} + +th.right, td.right { + text-align: right; + padding-right: 1em; +} + +tr.vtop, th.vtop, td.vtop { + vertical-align: top; +} + +tr.vmiddle, th.vmiddle, td.vmiddle { + vertical-align: middle; +} + +tr.vbottom, th.vbottom, td.vbottom { + vertical-align: bottom; +} + +.paddingtop { + padding-top: 1em; +} + +div.tools { + border: 1px solid #000000; + padding: 0.2em; +} + +div.tools, +fieldset.tblFooters { + margin-top: 0; + margin-bottom: 0.5em; + /* avoid a thick line since this should be used under another fieldset */ + border-top: 0; + text-align: ; + float: none; + clear: both; +} + +div.null_div { + height: 20px; + text-align: center; + font-style:normal; + min-width:50px; +} + +fieldset .formelement { + float: ; + margin-: 0.5em; + /* IE */ + white-space: nowrap; +} + +/* revert for Gecko */ +fieldset div[class=formelement] { + white-space: normal; +} + +button.mult_submit { + border: none; + background-color: transparent; +} + +/* odd items 1,3,5,7,... */ +table tr:nth-child(odd), +#table_index tbody:nth-of-type(odd) tr, +#table_index tbody:nth-of-type(odd) th { + background: ; +} + +/* even items 2,4,6,8,... */ +table tr:nth-child(even), +#table_index tbody:nth-of-type(even) tr, +#table_index tbody:nth-of-type(even) th { + background: ; +} + +table tr th, +table tr { + text-align: ; +} + +/* marked table rows */ +td.marked:not(.nomarker), +table tr.marked:not(.nomarker) td, +table tr.marked:not(.nomarker) th, +table tr.marked:not(.nomarker) { + background: ; + color: ; +} + +/* hovered items */ +table tr:not(.nopointer):hover, +.hover:not(.nopointer) { + background: ; + color: ; +} + +/* hovered table rows */ +#table_index tbody:hover tr, +#table_index tbody:hover th, +table tr.hover:not(.nopointer) th { + background: ; + color: ; +} + +/** + * marks table rows/cells if the db field is in a where condition + */ +td.condition, +th.condition { + border: 1px solid ; +} + +/** + * cells with the value NULL + */ +td.null { + font-style: italic; + color: #7d7d7d; +} + +table .valueHeader { + text-align: ; + white-space: normal; +} +table .value { + text-align: ; + white-space: normal; +} +/* IE doesnt handles 'pre' right */ +table [class=value] { + white-space: normal; +} + + + + .value { + font-family: ; + } + +.attention { + color: red; + font-weight: bold; +} +.allfine { + color: green; +} + + +img.lightbulb { + cursor: pointer; +} + +.pdflayout { + overflow: hidden; + clip: inherit; + background-color: #FFFFFF; + display: none; + border: 1px solid #000000; + position: relative; +} + +.pdflayout_table { + background: #D3DCE3; + color: #000000; + overflow: hidden; + clip: inherit; + z-index: 2; + display: inline; + visibility: inherit; + cursor: move; + position: absolute; + font-size: 80%; + border: 1px dashed #000000; +} + +/* Doc links in SQL */ +.cm-sql-doc { + text-decoration: none; + border-bottom: 1px dotted #000; + color: inherit !important; +} + +/* leave some space between icons and text */ +.icon { + image-rendering: pixelated; + vertical-align: middle; + margin-: 0.3em; + background-repeat: no-repeat; + background-position: center; +} + +/* no extra space in table cells */ +td .icon { + margin: 0; +} + +.selectallarrow { + margin-: 0.3em; + margin-: 0.6em; +} + +/* message boxes: error, confirmation */ +#pma_errors, #pma_demo, #pma_footer { + position: relative; + padding: 0 0.5em; +} + +.success h1, +.notice h1, +div.error h1 { + border-bottom: 2px solid; + font-weight: bold; + text-align: ; + margin: 0 0 0.2em 0; +} + +div.success, +div.notice, +div.error { + margin: 0.3em 0 0 0; + border: 2px solid; + background-repeat: no-repeat; + clear: both; + + background-position: 10px 50%; + padding: 0.1em 0.1em 0.1em 42px; + + background-position: 99% 50%; + padding: 0.1em 40px 0.1em 0.1em; + +} +div.success img.icon, +div.notice img.icon, +div.error img.icon { + display: block; + float: ; + margin-: -22px; +} + +.success { + color: #000000; + background-color: #f0fff0; +} +h1.success, +div.success { + border-color: #00FF00; +} +.success h1 { + border-color: #00FF00; +} + +.notice { + color: #000000; + background-color: #FFFFDD; +} +h1.notice, +div.notice { + border-color: #FFD700; +} +.notice h1 { + border-color: #FFD700; +} + +.error { + background-color: #FFFFCC; + color: #ff0000; +} + +h1.error, +div.error { + border-color: #ff0000; +} +div.error h1 { + border-color: #ff0000; +} + +.confirmation { + background-color: #FFFFCC; +} +fieldset.confirmation { + border: 0.1em solid #FF0000; +} +fieldset.confirmation legend { + border-left: 0.1em solid #FF0000; + border-right: 0.1em solid #FF0000; + font-weight: bold; + background-image: url(getImgPath('s_really.png');?>); + background-repeat: no-repeat; + + background-position: 5px 50%; + padding: 0.2em 0.2em 0.2em 25px; + + background-position: 97% 50%; + padding: 0.2em 25px 0.2em 0.2em; + +} +/* end messageboxes */ + +.new_central_col{ + width: 100%; +} + +.tblcomment { + font-size: 70%; + font-weight: normal; + color: #000099; +} + +.tblHeaders { + font-weight: bold; + color: ; + background: ; +} + +div.tools, +.tblFooters { + font-weight: normal; + color: ; + background: ; +} + +.tblHeaders a:link, +.tblHeaders a:active, +.tblHeaders a:visited, +div.tools a:link, +div.tools a:visited, +div.tools a:active, +.tblFooters a:link, +.tblFooters a:active, +.tblFooters a:visited { + color: #0000FF; +} + +.tblHeaders a:hover, +div.tools a:hover, +.tblFooters a:hover { + color: #FF0000; +} + +/* forbidden, no privilegs */ +.noPrivileges { + color: #FF0000; + font-weight: bold; +} + +/* disabled text */ +.disabled, +.disabled a:link, +.disabled a:active, +.disabled a:visited { + color: #666666; +} + +.disabled a:hover { + color: #666666; + text-decoration: none; +} + +tr.disabled td, +td.disabled { + background-color: #cccccc; +} + +.nowrap { + white-space: nowrap; +} + +/** + * zoom search + */ +div#resizer { + width: 600px; + height: 400px; +} +div#querychart { + float: left; + width: 600px; +} + +/** + * login form + */ +body#loginform h1, +body#loginform a.logo { + display: block; + text-align: center; +} + +body#loginform { + margin-top: 1em; + text-align: center; +} + +body#loginform div.container { + text-align: ; + width: 30em; + margin: 0 auto; +} + +form.login label { + float: ; + width: 10em; + font-weight: bolder; +} + +.commented_column { + border-bottom: 1px dashed black; +} + +.column_attribute { + font-size: 70%; +} + +.cfg_dbg_demo{ + margin: 0.5em 1em 0.5em 1em; +} + +.central_columns_navigation{ + padding:1.5% 0em !important; +} + +.central_columns_add_column{ + display:inline-block; + margin-left:1%; + max-width:50% +} + +.message_errors_found{ + margin-top: 20px; +} + +.repl_gui_skip_err_cnt{ + width: 30px; +} + +.font_weight_bold{ + font-weight: bold; +} + +.color_gray{ + color: gray; +} + +.pma_sliding_message{ + display: inline-block; +} + +/******************************************************************************/ +/* specific elements */ + +/* topmenu */ +ul#topmenu, ul#topmenu2, ul.tabs { + font-weight: bold; + list-style-type: none; + margin: 0; + padding: 0; +} + +ul#topmenu2 { + margin: 0.25em 0.5em 0; + height: 2em; + clear: both; +} + +ul#topmenu li, ul#topmenu2 li { + float: ; + margin: 0; + padding: 0; + vertical-align: middle; +} + +#topmenu img, #topmenu2 img { + vertical-align: middle; + margin-: 0.1em; +} + +/* default tab styles */ +ul#topmenu a, ul#topmenu span { + display: block; + margin: 2px 2px 0; + padding: 2px 2px 0; + white-space: nowrap; +} + +ul#topmenu2 a { + display: block; + margin: 0.1em; + padding: 0.2em; + white-space: nowrap; +} + +span.caution { + color: #FF0000; +} +fieldset.caution a { + color: #FF0000; +} +fieldset.caution a:hover { + color: #ffffff; + background-color: #FF0000; +} + +#topmenu { + margin-top: 0.5em; + padding: 0.1em 0.3em 0.1em 0.3em; +} + +ul#topmenu ul { + -moz-box-shadow: 2px 2px 3px #666; + -webkit-box-shadow: 2px 2px 3px #666; + box-shadow: 2px 2px 3px #666; +} + +ul#topmenu > li { + border-bottom: 1pt solid black; +} + +/* default tab styles */ +ul#topmenu a, ul#topmenu span { + background-color: ; + border: 0 solid ; + border-width: 1pt 1pt 0 1pt; + -moz-border-radius: 0.4em 0.4em 0 0; + border-radius: 0.4em 0.4em 0 0; +} + +ul#topmenu ul a { + border-width: 1pt 0 0 0; + -moz-border-radius: 0; + border-radius: 0; +} + +ul#topmenu ul li:first-child a { + border-width: 0; +} + +/* enabled hover/active tabs */ +ul#topmenu > li > a:hover, +ul#topmenu > li > .tabactive { + margin: 0; + padding: 2px 4px; + text-decoration: none; +} + +ul#topmenu ul a:hover, +ul#topmenu ul .tabactive { + text-decoration: none; +} + +ul#topmenu a.tab:hover, +ul#topmenu .tabactive { + background-color: ; +} + +ul#topmenu2 a.tab:hover, +ul#topmenu2 a.tabactive { + background-color: ; + -moz-border-radius: 0.3em; + border-radius: 0.3em; + text-decoration: none; +} + +/* to be able to cancel the bottom border, use
      • */ +ul#topmenu > li.active { + border-bottom: 1pt solid ; +} +/* end topmenu */ + +/* zoom search */ +div#dataDisplay input, div#dataDisplay select { + margin: 0; + margin-: 0.5em; +} +div#dataDisplay th { + line-height: 2em; +} +table#tableFieldsId { + width: 100%; +} + +/* Calendar */ +table.calendar { + width: 100%; +} +table.calendar td { + text-align: center; +} +table.calendar td a { + display: block; +} + +table.calendar td a:hover { + background-color: #CCFFCC; +} + +table.calendar th { + background-color: #D3DCE3; +} + +table.calendar td.selected { + background-color: #FFCC99; +} + +img.calendar { + border: none; +} +form.clock { + text-align: center; +} +/* end Calendar */ + + +/* table stats */ +div#tablestatistics table { + float: ; + margin-top: 0.5em; + margin-bottom: 0.5em; + margin-: 0.5em; + min-width: 16em; +} +/* end table stats */ + + +/* server privileges */ +#tableuserrights td, +#tablespecificuserrights td, +#tabledatabases td { + vertical-align: middle; +} +/* end server privileges */ + + + +/* Heading */ +#topmenucontainer { + background: white; + padding-: 1em; + width: 100%; +} + +#serverinfo { + background: white; + font-weight: bold; + padding-bottom: 0.5em; + padding-: 2.2em; + width: 10000px; + overflow: hidden; +} + +#serverinfo .item { + white-space: nowrap; +} + +#page_nav_icons { + position: fixed; + top: 0; + : 0; + z-index: 99; + padding: .1em 0; +} + +#goto_pagetop, #lock_page_icon, #page_settings_icon { + padding: .3em; + background: white; +} + +#page_settings_icon { + cursor: pointer; + display: none; +} + +#page_settings_modal { + display: none; +} + +#pma_navigation_settings { + display: none; +} + +#span_table_comment { + font-weight: bold; + font-style: italic; + white-space: nowrap; + margin-left: 10px; + color: #D6D6D6; + text-shadow: none; +} + +#serverinfo img { + margin: 0 0.1em 0 0.2em; +} + + +#textSQLDUMP { + width: 95%; + height: 95%; + font-family: "Courier New", Courier, mono; + font-size: 110%; +} + +#TooltipContainer { + position: absolute; + z-index: 99; + width: 20em; + height: auto; + overflow: visible; + visibility: hidden; + background-color: #ffffcc; + color: #006600; + border: 0.1em solid #000000; + padding: 0.5em; +} + +/* user privileges */ +#fieldset_add_user_login div.item { + border-bottom: 1px solid silver; + padding-bottom: 0.3em; + margin-bottom: 0.3em; +} + +#fieldset_add_user_login label { + float: ; + display: block; + width: 15em; + max-width: 100%; + text-align: ; + padding-: 0.5em; +} + +#fieldset_add_user_login span.options #select_pred_username, +#fieldset_add_user_login span.options #select_pred_hostname, +#fieldset_add_user_login span.options #select_pred_password { + width: 100%; + max-width: 100%; +} + +#fieldset_add_user_login span.options { + float: ; + display: block; + width: 12em; + max-width: 100%; + padding-: 0.5em; +} + +#fieldset_add_user_login input { + width: 12em; + clear: ; + max-width: 100%; +} + +#fieldset_add_user_login span.options input { + width: auto; +} + +#fieldset_user_priv div.item { + float: ; + width: 9em; + max-width: 100%; +} + +#fieldset_user_priv div.item div.item { + float: none; +} + +#fieldset_user_priv div.item label { + white-space: nowrap; +} + +#fieldset_user_priv div.item select { + width: 100%; +} + +#fieldset_user_global_rights fieldset { + float: ; +} + +#fieldset_user_group_rights fieldset { + float: ; +} + +#fieldset_user_global_rights>legend input { + margin-: 2em; +} +/* end user privileges */ + + +/* serverstatus */ + +.linkElem:hover { + text-decoration: underline; + color: #235a81; + cursor: pointer; +} + +h3#serverstatusqueries span { + font-size:60%; + display:inline; +} + +.buttonlinks { + float: ; + white-space: nowrap; +} + +/* Also used for the variables page */ +fieldset#tableFilter { + padding: 0.1em 1em; +} + +div#serverStatusTabs { + margin-top:1em; +} + +caption a.top { + float: ; +} + +div#serverstatusquerieschart { + float: ; + width: 500px; + height: 350px; + margin-: 50px; +} + +div#serverstatus table#serverstatusqueriesdetails { + float: ; +} + +table#serverstatustraffic { + float: ; +} +table#serverstatusconnections { + float: ; + margin-: 30px; +} + +table#serverstatusvariables { + width: 100%; + margin-bottom: 1em; +} +table#serverstatusvariables .name { + width: 18em; + white-space:nowrap; +} +table#serverstatusvariables .value { + width: 6em; +} +table#serverstatusconnections { + float: ; + margin-: 30px; +} + +div#serverstatus table tbody td.descr a, +div#serverstatus table .tblFooters a { + white-space: nowrap; +} + +div.liveChart { + clear:both; + min-width:500px; + height:400px; + padding-bottom:80px; +} + +#addChartDialog input[type="text"] { + margin: 0; + padding:3px; +} + +div#chartVariableSettings { + border:1px solid #ddd; + background-color:#E6E6E6; + margin-left:10px; +} + +table#chartGrid td { + padding: 3px; + margin: 0; +} + +table#chartGrid div.monitorChart { + background: #EBEBEB; + overflow: hidden; + border: none; +} + +div.tabLinks { + margin-left: 0.3em; + float: ; + padding: 5px 0; +} + +div.tabLinks a, div.tabLinks label { + margin-right: 7px; +} + +div.tabLinks .icon { + margin: -0.2em 0.3em 0 0; +} + +.popupContent { + display: none; + position: absolute; + border: 1px solid #CCC; + margin:0; + padding:3px; + -moz-box-shadow: 1px 1px 6px #ddd; + -webkit-box-shadow: 2px 2px 3px #666; + box-shadow: 2px 2px 3px #666; + background-color:white; + z-index: 2; +} + +div#logTable { + padding-top: 10px; + clear: both; +} + +div#logTable table { + width:100%; +} + +.smallIndent { + padding-left: 7px; +} + +/* end serverstatus */ + +#sectionlinks { + margin-bottom: 15px; + padding: 10px; + border: 1px solid #CCC; +} +#sectionlinks a { + margin-: 7px; +} + +/* server variables */ +#serverVariables { + width: 100%; +} +#serverVariables .var-row > tr { + line-height: 2em; +} + +#serverVariables .var-header { + font-weight: bold; + color: ; + background: ; + text-align: ; +} +#serverVariables .var-row { + padding: 0.5em; + min-height: 18px; +} +#serverVariables .var-name { + font-weight: bold; +} +#serverVariables .var-name.session { + font-weight: normal; + font-style: italic; +} +#serverVariables .var-value { + text-align: ; + float: ; +} + +/* server variables editor */ +#serverVariables .editLink { + padding-: 1em; + font-family: sans-serif; +} +#serverVariables .serverVariableEditor { + width: 100%; + overflow: hidden; +} +#serverVariables .serverVariableEditor input { + width: 100%; + margin: 0 0.5em; + box-sizing: border-box; + -ms-box-sizing: border-box; + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + height: 2.2em; +} +#serverVariables .serverVariableEditor div { + display: block; + overflow: hidden; + padding-: 1em; +} +#serverVariables .serverVariableEditor a { + margin: 0 0.5em; + line-height: 2em; +} +/* end server variables */ + +/* profiling */ + +div#profilingchart { + width: 850px; + height: 370px; + float: left; +} + +#profilingchart .jqplot-highlighter-tooltip{ + top: auto !important; + left: 11px; + bottom:24px; +} +/* end profiling */ + +/* table charting */ +.chartOption { + float: ; + margin-: 40px; +} +/* end table charting */ + +/* querybox */ + +div#sqlquerycontainer { + float: ; + width: 69%; + /* height: 15em; */ +} + +div#tablefieldscontainer { + float: ; + width: 29%; + /* height: 15em; */ +} + +div#tablefieldscontainer select { + width: 100%; + /* height: 12em; */ +} + +textarea#sqlquery { + width: 100%; + /* height: 100%; */ +} +textarea#sql_query_edit { + height: 7em; + width: 95%; + display: block; +} +div#queryboxcontainer div#bookmarkoptions { + margin-top: .5em; +} +/* end querybox */ + +/* main page */ +#maincontainer { + background-image: url(getImgPath('logo_right.png');?>); + background-position: bottom; + background-repeat: no-repeat; +} + +#mysqlmaininformation, +#pmamaininformation { + float: ; + width: 49%; +} + +#maincontainer ul { + list-style-type: disc; + vertical-align: middle; +} + +#maincontainer li { + margin: 0.2em 0; +} + +#full_name_layer { + position: absolute; + padding: 2px; + margin-top: -3px; + z-index: 801; + + border: solid 1px #888; + background: #fff; + +} +/* end main page */ + +/* iconic view for ul items */ + +li.no_bullets { + list-style-type:none !important; + margin-: -25px !important; //align with other list items which have bullets +} + +/* end iconic view for ul items */ + +#body_browse_foreigners { + background: ; + margin: .5em .5em 0 .5em; +} + +#bodythemes { + width: 500px; + margin: auto; + text-align: center; +} + +#bodythemes img { + border: .1em solid #000; +} + +#bodythemes a:hover img { + border: .1em solid red; +} + +#fieldset_select_fields { + float: ; +} + +#selflink { + clear: both; + display: block; + margin-top: 1em; + margin-bottom: 1em; + width: 98%; + margin-left: 1%; + border-top: .1em solid silver; + text-align: ; +} + +#table_innodb_bufferpool_usage, +#table_innodb_bufferpool_activity { + float: ; +} + +#div_mysql_charset_collations table { + float: ; +} + +#div_mysql_charset_collations table th, +#div_mysql_charset_collations table td { + padding: 0.4em; +} + +#div_mysql_charset_collations table th#collationHeader { + width: 35%; +} + +#qbe_div_table_list { + float: ; +} + +#qbe_div_sql_query { + float: ; +} + +label.desc { + width: 30em; + float: ; +} + +label.desc sup { + position: absolute; +} + +code.php { + display: block; + padding-left: 0.3em; + margin-top: 0; + margin-bottom: 0; + max-height: 10em; + overflow: auto; + direction: ltr; +} + +code.sql, +div.sqlvalidate { + display: block; + padding: 0.3em; + margin-top: 0; + margin-bottom: 0; + max-height: 10em; + overflow: auto; + direction: ltr; +} + +.result_query div.sqlOuter, +div.sqlvalidate { + border: solid 1px; + border-top: 0; + border-bottom: 0; + background: ; +} + +.result_query div.sqlOuter { + text-align: ; +} + +#PMA_slidingMessage code.sql { + border: solid 1px; + border-top: 0; + background: ; +} + +#main_pane_left { + width: 60%; + min-width: 260px; + float: ; + padding-top: 1em; +} + +#main_pane_right { + overflow: hidden; + min-width: 160px; + padding-top: 1em; + padding-: 1em; +} + +.group { + border-: 0.3em solid ; + margin-bottom: 1em; +} + +.group h2 { + background: ; + padding: 0.1em 0.3em; + margin-top: 0; +} + +.group-cnt { + padding: 0 0 0 0.5em; + display: inline-block; + width: 98%; +} + +textarea#partitiondefinition { + height:3em; +} + + +/* for elements that should be revealed only via js */ +.hide { + display: none; +} + +#list_server { + list-style-type: none; + padding: 0; +} + +/** + * Progress bar styles + */ +div.upload_progress +{ + width: 400px; + margin: 3em auto; + text-align: center; +} + +div.upload_progress_bar_outer +{ + border: 1px solid #000; + width: 202px; + position: relative; + margin: 0 auto 1em; + color: ; +} + +div.upload_progress_bar_inner +{ + background-color: ; + width: 0; + height: 12px; + margin: 1px; + overflow: hidden; + color: ; + position: relative; +} + +div.upload_progress_bar_outer div.percentage +{ + position: absolute; + top: 0; + left: 0; + width: 202px; +} + +div.upload_progress_bar_inner div.percentage +{ + top: -1px; + left: -1px; +} + +div#statustext { + margin-top: .5em; +} + +table#serverconnection_src_remote, +table#serverconnection_trg_remote, +table#serverconnection_src_local, +table#serverconnection_trg_local { + float: left; +} +/** + * Validation error message styles + */ +input[type=text].invalid_value, +input[type=password].invalid_value, +input[type=number].invalid_value, +input[type=date].invalid_value, +select.invalid_value, +.invalid_value { + background: #FFCCCC; +} + +/** + * Ajax notification styling + */ + .ajax_notification { + top: 0; /** The notification needs to be shown on the top of the page */ + position: fixed; + margin-top: 0; + margin-right: auto; + margin-bottom: 0; + margin-left: auto; + padding: 3px 5px; /** Keep a little space on the sides of the text */ + width: 350px; + background-color: #FFD700; + z-index: 1100; /** If this is not kept at a high z-index, the jQueryUI modal dialogs (z-index:1000) might hide this */ + text-align: center; + display: block; + left: 0; + right: 0; + background-image: url(getImgPath('ajax_clock_small.gif');?>); + background-repeat: no-repeat; + background-position: 2%; + } + + #loading_parent { + /** Need this parent to properly center the notification division */ + position: relative; + width: 100%; + } +/** + * Export and Import styles + */ + +.export_table_list_container { + display: inline-block; + max-height: 20em; + overflow-y: scroll; +} + +.export_table_select th { + text-align: center; + vertical-align: middle; +} + +.export_table_select .all { + font-weight: bold; + border-bottom: 1px solid black; +} + +.export_structure, .export_data { + text-align: center; +} + +.export_table_name { + vertical-align: middle; +} + +.exportoptions h2 { + word-wrap: break-word; +} + +.exportoptions h3, .importoptions h3 { + border-bottom: 1px #999999 solid; + font-size: 110%; +} + +.exportoptions ul, .importoptions ul, .format_specific_options ul { + list-style-type: none; + margin-bottom: 15px; +} + +.exportoptions li, .importoptions li { + margin: 7px; +} +.exportoptions label, .importoptions label, .exportoptions p, .importoptions p { + margin: 5px; + float: none; +} + +#csv_options label.desc, #ldi_options label.desc, #latex_options label.desc, #output label.desc{ + float: left; + width: 15em; +} + +.exportoptions, .importoptions { + margin: 20px 30px 30px 10px +} + +.format_specific_options h3 { + margin: 10px 0 0 10px; + border: 0; +} + +.format_specific_options { + border: 1px solid #999; + margin: 7px 0; + padding: 3px; +} + +p.desc { + margin: 5px; +} + +/** + * Export styles only + */ +select#db_select, +select#table_select { + width: 400px; +} + +.export_sub_options { + margin: 20px 0 0 30px; +} + +.export_sub_options h4 { + border-bottom: 1px #999 solid; +} + +.export_sub_options li.subgroup { + display: inline-block; + margin-top: 0; +} + +.export_sub_options li { + margin-bottom: 0; +} + +#output_quick_export { + display: none; +} +/** + * Import styles only + */ + +.importoptions #import_notification { + margin: 10px 0; + font-style: italic; +} + +input#input_import_file { + margin: 5px; +} + +.formelementrow { + margin: 5px 0 5px 0; +} + +#filterText { + vertical-align: baseline; +} + +#popup_background { + display: none; + position: fixed; + _position: absolute; /* hack for IE6 */ + width: 100%; + height: 100%; + top: 0; + left: 0; + background: #000; + z-index: 1000; + overflow: hidden; +} + +/** + * Create table styles + */ +#create_table_form table.table-name td { + vertical-align: middle; +} + +/** + * Table structure styles + */ +#fieldsForm ul.table-structure-actions { + margin: 0; + padding: 0; + list-style: none; +} +#fieldsForm ul.table-structure-actions li { + float: ; + margin-: 0.5em; /* same as padding of "table td" */ +} +#fieldsForm ul.table-structure-actions .submenu li { + padding: 0.3em; + margin: 0.1em; +} +#structure-action-links a { + margin-: 1em; +} +#addColumns input[type="radio"] { + margin: 0; + margin-: 1em; +} +#addColumns input[type="submit"] { + margin-: 1em; +} + +/** + * Indexes + */ +#index_frm .index_info input[type="text"], +#index_frm .index_info select { + width: 100%; + box-sizing: border-box; + -ms-box-sizing: border-box; + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; +} + +#index_frm .slider { + width: 10em; + margin: .6em; + float: ; +} + +#index_frm .add_fields { + float: ; +} + +#index_frm .add_fields input { + margin-: 1em; +} + +#index_frm input { + margin: 0; +} + +#index_frm td { + vertical-align: middle; +} + +table#index_columns { + width: 100%; +} + +table#index_columns select { + width: 85%; + float: right; +} + +#move_columns_dialog div { + padding: 1em; +} + +#move_columns_dialog ul { + list-style: none; + margin: 0; + padding: 0; +} + +#move_columns_dialog li { + background: ; + border: 1px solid #aaa; + color: ; + font-weight: bold; + margin: .4em; + padding: .2em; + -webkit-border-radius: 2px; + -moz-border-radius: 2px; + border-radius: 2px; +} + +/* config forms */ +.config-form ul.tabs { + margin: 1.1em 0.2em 0; + padding: 0 0 0.3em 0; + list-style: none; + font-weight: bold; +} + +.config-form ul.tabs li { + float: ; +} + +.config-form ul.tabs li a { + display: block; + margin: 0.1em 0.2em 0; + padding: 0.1em 0.4em; + white-space: nowrap; + text-decoration: none; + border: 1px solid ; + border-bottom: none; +} + +.config-form ul.tabs li a:hover, +.config-form ul.tabs li a:active, +.config-form ul.tabs li a.active { + margin: 0; + padding: 0.1em 0.6em 0.2em; +} + +.config-form ul.tabs li.active a { + background-color: ; +} + +.config-form fieldset { + margin-top: 0; + padding: 0; + clear: both; + /*border-color: ;*/ +} + +.config-form legend { + display: none; +} + +.config-form fieldset p { + margin: 0; + padding: 0.5em; + background: ; +} + +.config-form fieldset .errors { /* form error list */ + margin: 0 -2px 1em -2px; + padding: .5em 1.5em; + background: #FBEAD9; + border: 0 #C83838 solid; + border-width: 1px 0; + list-style: none; + font-family: sans-serif; + font-size: small; +} + +.config-form fieldset .inline_errors { /* field error list */ + margin: .3em .3em .3em 0; + padding: 0; + list-style: none; + color: #9A0000; + font-size: small; +} + +.config-form fieldset th { + padding: .3em .3em .3em .5em; + text-align: left; + vertical-align: top; + width: 40%; + background: transparent; +} + +.config-form fieldset .doc, +.config-form fieldset .disabled-notice { + margin-left: 1em; +} + +.config-form fieldset .disabled-notice { + font-size: 80%; + text-transform: uppercase; + color: #E00; + cursor: help; +} + +.config-form fieldset td { + padding-top: .3em; + padding-bottom: .3em; + vertical-align: top; +} + +.config-form fieldset th small { + display: block; + font-weight: normal; + font-family: sans-serif; + font-size: x-small; + color: #444; +} + +.config-form fieldset th, +.config-form fieldset td { + border-top: 1px solid; +} + +fieldset .group-header th { + background: ; +} + +fieldset .group-header + tr th { + padding-top: .6em; +} + +fieldset .group-field-1 th, +fieldset .group-header-2 th { + padding-left: 1.5em; +} + +fieldset .group-field-2 th, +fieldset .group-header-3 th { + padding-left: 3em; +} + +fieldset .group-field-3 th { + padding-left: 4.5em; +} + +fieldset .disabled-field th, +fieldset .disabled-field th small, +fieldset .disabled-field td { + color: #666; + background-color: #ddd; +} + +.config-form .lastrow { + border-top: 1px #000 solid; +} + +.config-form .lastrow { + background: ; + padding: .5em; + text-align: center; +} + +.config-form .lastrow input { + font-weight: bold; +} + +/* form elements */ + +.config-form span.checkbox { + padding: 2px; + display: inline-block; +} + +.config-form .custom { /* customized field */ + background: #FFC; +} + +.config-form span.checkbox.custom { + padding: 1px; + border: 1px #EDEC90 solid; + background: #FFC; +} + +.config-form .field-error { + border-color: #A11 !important; +} + +.config-form input[type="text"], +.config-form input[type="password"], +.config-form input[type="number"], +.config-form select, +.config-form textarea { + border: 1px #A7A6AA solid; + height: auto; +} + +.config-form input[type="text"]:focus, +.config-form input[type="password"]:focus, +.config-form input[type="number"]:focus, +.config-form select:focus, +.config-form textarea:focus { + border: 1px #6676FF solid; + background: #F7FBFF; +} + +.config-form .field-comment-mark { + font-family: serif; + color: #007; + cursor: help; + padding: 0 0.2em; + font-weight: bold; + font-style: italic; +} + +.config-form .field-comment-warning { + color: #A00; +} + +/* error list */ +.config-form dd { + margin-left: .5em; +} + +.config-form dd:before { + content: "\25B8 "; +} + +.click-hide-message { + cursor: pointer; +} + +.prefsmanage_opts { + margin-: 2em; +} + +#prefs_autoload { + margin-bottom: .5em; + margin-left: .5em; +} + +#placeholder .button { + position: absolute; + cursor: pointer; +} + +#placeholder div.button { + font-size: smaller; + color: #999; + background-color: #eee; + padding: 2px; +} + +.wrapper { + float: ; + margin-bottom: 0.5em; +} +.toggleButton { + position: relative; + cursor: pointer; + font-size: .8em; + text-align: center; + line-height: 1.4em; + height: 1.55em; + overflow: hidden; + border-right: .1em solid #888; + border-left: .1em solid #888; +} +.toggleButton table, +.toggleButton td, +.toggleButton img { + padding: 0; + position: relative; +} +.toggleButton .container { + position: absolute; +} +.toggleButton .container td, +.toggleButton .container tr { + background-image: none; + background: none !important; +} +.toggleButton .toggleOn { + color: #fff; + padding: 0 1em; +} +.toggleButton .toggleOff { + padding: 0 1em; +} + +.doubleFieldset fieldset { + width: 48%; + float: ; + padding: 0; +} +.doubleFieldset fieldset.left { + margin-: 1%; +} +.doubleFieldset fieldset.right { + margin-: 1%; +} +.doubleFieldset legend { + margin-: 0.5em; +} +.doubleFieldset div.wrap { + padding: 0.5em; +} + +#table_name_col_no_outer { + margin-top: 30px; +} + +#table_name_col_no { + position: fixed; + top: 44px; + width: 100%; + background: ; +} + +#table_columns input[type="text"], +#table_columns input[type="password"], +#table_columns input[type="number"], +#table_columns input[type="date"], +#table_columns select { + width: 10em; + box-sizing: border-box; + -ms-box-sizing: border-box; + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; +} + +input#auto_increment_opt { + width: min-content; +} + +#placeholder { + position: relative; + border: 1px solid #aaa; + float: ; + overflow: hidden; + width: 450px; + height: 300px; +} + +#openlayersmap{ + width: 450px; + height: 300px; +} + +.placeholderDrag { + cursor: move; +} + +#placeholder .button { + position: absolute; +} + +#left_arrow { + left: 8px; + top: 26px; +} + +#right_arrow { + left: 26px; + top: 26px; +} + +#up_arrow { + left: 17px; + top: 8px; +} + +#down_arrow { + left: 17px; + top: 44px; +} + +#zoom_in { + left: 17px; + top: 67px; +} + +#zoom_world { + left: 17px; + top: 85px; +} + +#zoom_out { + left: 17px; + top: 103px; +} + +.colborder { + cursor: col-resize; + height: 100%; + margin-left: -5px; + position: absolute; + width: 5px; +} + +.colborder_active { + border-right: 2px solid #a44; +} + +.pma_table td { + position: static; +} + +.pma_table th.draggable span, +.sticky_columns th.draggable span, +.pma_table tbody td span { + display: block; + overflow: hidden; +} + +.pma_table tbody td span code span { + display: inline; +} + +.modal-copy input { + display: block; + width: 100%; + margin-top: 1.5em; + padding: .3em 0; +} + +.cRsz { + position: absolute; +} + +.draggable { + cursor: move; +} + +.cCpy { + background: #000; + color: #FFF; + font-weight: bold; + margin: 0.1em; + padding: 0.3em; + position: absolute; +} + +.cPointer { + background: url(getImgPath('col_pointer.png');?>); + height: 20px; + margin-left: -5px; /* must be minus half of its width */ + margin-top: -10px; + position: absolute; + width: 10px; +} + +.tooltip { + background: #333 !important; + opacity: .8 !important; + border: 1px solid #000 !important; + -moz-border-radius: .3em !important; + -webkit-border-radius: .3em !important; + border-radius: .3em !important; + text-shadow: -1px -1px #000 !important; + font-size: .8em !important; + font-weight: bold !important; + padding: 1px 3px !important; +} + +.tooltip * { + background: none !important; + color: #FFF !important; +} + + +.data_full_width { + width: 100%; +} + +.cDrop { + left: 0; + position: absolute; + top: 0; +} + +.coldrop { + background: url(getImgPath('col_drop.png');?>); + cursor: pointer; + height: 16px; + margin-left: 0.5em; + margin-top: 0.3em; + position: absolute; + width: 16px; +} + +.coldrop:hover, +.coldrop-hover { + background-color: #999; +} + +.cList { + background: #EEE; + border: solid 1px #999; + position: absolute; +} + +.cList .lDiv div { + padding: .2em .5em .2em .2em; +} + +.cList .lDiv div:hover { + background: #DDD; + cursor: pointer; +} + +.cList .lDiv div input { + cursor: pointer; +} + +.showAllColBtn { + border-bottom: solid 1px #999; + border-top: solid 1px #999; + cursor: pointer; + font-size: .9em; + font-weight: bold; + padding: .35em 1em; + text-align: center; +} + +.showAllColBtn:hover { + background: #DDD; +} + +.navigation { + background: #E5E5E5; + border: 1px solid black; + margin: 0.8em 0; +} + +.navigation td { + margin: 0; + padding: 0; + vertical-align: middle; + white-space: nowrap; +} + +.navigation_separator { + color: #555; + display: inline-block; + text-align: center; + width: 1.2em; + text-shadow: 1px 0 #FFF; +} + +.navigation input[type=submit] { + background: none; + border: 0; + margin: 0; + padding: 0.3em 0.5em; + min-width: 1.5em; + font-weight: bold; +} + +.navigation input[type=submit]:hover, .navigation input.edit_mode_active { + background: #333; + color: white; + cursor: pointer; +} + +.navigation select { + margin: 0 .8em; +} + +.cEdit { + margin: 0; + padding: 0; + position: absolute; +} + +.cEdit input[type=text], +.cEdit input[type=password], +.cEdit input[type=number] { + background: #FFF; + height: 100%; + margin: 0; + padding: 0; +} + +.cEdit .edit_area { + background: #FFF; + border: 1px solid #999; + min-width: 10em; + padding: .3em .5em; +} + +.cEdit .edit_area select, +.cEdit .edit_area textarea { + width: 97%; +} + +.cEdit .cell_edit_hint { + color: #555; + font-size: .8em; + margin: .3em .2em; +} + +.cEdit .edit_box { + overflow-x: hidden; + overflow-y: scroll; + padding: 0; +} + +.cEdit .edit_box_posting { + background: #FFF url(getImgPath('ajax_clock_small.gif');?>) no-repeat right center; + padding-right: 1.5em; +} + +.cEdit .edit_area_loading { + background: #FFF url(getImgPath('ajax_clock_small.gif');?>) no-repeat center; + height: 10em; +} + +.cEdit .goto_link { + background: #EEE; + color: #555; + padding: .2em .3em; +} + +.saving_edited_data { + background: url(getImgPath('ajax_clock_small.gif');?>) no-repeat left; + padding-left: 20px; +} + +/* css for timepicker */ +.ui-timepicker-div .ui-widget-header { margin-bottom: 8px; } +.ui-timepicker-div dl { text-align: ; } +.ui-timepicker-div dl dt { height: 25px; margin-bottom: -25px; } +.ui-timepicker-div dl dd { margin: 0 10px 10px 85px; } +.ui-timepicker-div td { font-size: 90%; } +.ui-tpicker-grid-label { background: none; border: none; margin: 0; padding: 0; } +.ui-timepicker-rtl { direction: rtl; } +.ui-timepicker-rtl dl { text-align: right; } +.ui-timepicker-rtl dl dd { margin: 0 65px 10px 10px; } + +input.btn { + color: #333; + background-color: #D0DCE0; +} + +body .ui-widget { + font-size: 1em; +} + +.ui-dialog fieldset legend a { + color: #0000FF; +} + +.ui-draggable { + z-index: 801; +} + +/* jqPlot */ + +/*rules for the plot target div. These will be cascaded down to all plot elements +according to css rules*/ +.jqplot-target { + position: relative; + color: #222222; + font-family: "Trebuchet MS", Arial, Helvetica, sans-serif; + font-size: 1em; +/* height: 300px; + width: 400px;*/ +} + +/*rules applied to all axes*/ +.jqplot-axis { + font-size: 0.75em; +} + +.jqplot-xaxis { + margin-top: 10px; +} + +.jqplot-x2axis { + margin-bottom: 10px; +} + +.jqplot-yaxis { + margin-right: 10px; +} + +.jqplot-y2axis, .jqplot-y3axis, .jqplot-y4axis, .jqplot-y5axis, .jqplot-y6axis, +.jqplot-y7axis, .jqplot-y8axis, .jqplot-y9axis, .jqplot-yMidAxis { + margin-left: 10px; + margin-right: 10px; +} + +/*rules applied to all axis tick divs*/ +.jqplot-axis-tick, .jqplot-xaxis-tick, .jqplot-yaxis-tick, .jqplot-x2axis-tick, +.jqplot-y2axis-tick, .jqplot-y3axis-tick, .jqplot-y4axis-tick, .jqplot-y5axis-tick, +.jqplot-y6axis-tick, .jqplot-y7axis-tick, .jqplot-y8axis-tick, .jqplot-y9axis-tick, +.jqplot-yMidAxis-tick { + position: absolute; + white-space: pre; +} + + +.jqplot-xaxis-tick { + top: 0; + /* initial position untill tick is drawn in proper place */ + left: 15px; + vertical-align: top; +} + +.jqplot-x2axis-tick { + bottom: 0; + /* initial position untill tick is drawn in proper place */ + left: 15px; + vertical-align: bottom; +} + +.jqplot-yaxis-tick { + right: 0; + /* initial position untill tick is drawn in proper place */ + top: 15px; + text-align: right; +} + +.jqplot-yaxis-tick.jqplot-breakTick { + right: -20px; + margin-right: 0; + padding:1px 5px 1px 5px; + z-index: 2; + font-size: 1.5em; +} + +.jqplot-y2axis-tick, .jqplot-y3axis-tick, .jqplot-y4axis-tick, .jqplot-y5axis-tick, +.jqplot-y6axis-tick, .jqplot-y7axis-tick, .jqplot-y8axis-tick, .jqplot-y9axis-tick { + left: 0; + /* initial position until tick is drawn in proper place */ + top: 15px; + text-align: left; +} + +.jqplot-yMidAxis-tick { + text-align: center; + white-space: nowrap; +} + +.jqplot-xaxis-label { + margin-top: 10px; + font-size: 11pt; + position: absolute; +} + +.jqplot-x2axis-label { + margin-bottom: 10px; + font-size: 11pt; + position: absolute; +} + +.jqplot-yaxis-label { + margin-right: 10px; +/* text-align: center;*/ + font-size: 11pt; + position: absolute; +} + +.jqplot-yMidAxis-label { + font-size: 11pt; + position: absolute; +} + +.jqplot-y2axis-label, .jqplot-y3axis-label, .jqplot-y4axis-label, +.jqplot-y5axis-label, .jqplot-y6axis-label, .jqplot-y7axis-label, +.jqplot-y8axis-label, .jqplot-y9axis-label { +/* text-align: center;*/ + font-size: 11pt; + margin-left: 10px; + position: absolute; +} + +.jqplot-meterGauge-tick { + font-size: 0.75em; + color: #999999; +} + +.jqplot-meterGauge-label { + font-size: 1em; + color: #999999; +} + +table.jqplot-table-legend { + margin-top: 12px; + margin-bottom: 12px; + margin-left: 12px; + margin-right: 12px; +} + +table.jqplot-table-legend, table.jqplot-cursor-legend { + background-color: rgba(255,255,255,0.6); + border: 1px solid #cccccc; + position: absolute; + font-size: 0.75em; +} + +td.jqplot-table-legend { + vertical-align:middle; +} + +/* +These rules could be used instead of assigning +element styles and relying on js object properties. +*/ + +/* +td.jqplot-table-legend-swatch { + padding-top: 0.5em; + text-align: center; +} + +tr.jqplot-table-legend:first td.jqplot-table-legend-swatch { + padding-top: 0px; +} +*/ + +td.jqplot-seriesToggle:hover, td.jqplot-seriesToggle:active { + cursor: pointer; +} + +.jqplot-table-legend .jqplot-series-hidden { + text-decoration: line-through; +} + +div.jqplot-table-legend-swatch-outline { + border: 1px solid #cccccc; + padding:1px; +} + +div.jqplot-table-legend-swatch { + width:0; + height:0; + border-top-width: 5px; + border-bottom-width: 5px; + border-left-width: 6px; + border-right-width: 6px; + border-top-style: solid; + border-bottom-style: solid; + border-left-style: solid; + border-right-style: solid; +} + +.jqplot-title { + top: 0; + left: 0; + padding-bottom: 0.5em; + font-size: 1.2em; +} + +table.jqplot-cursor-tooltip { + border: 1px solid #cccccc; + font-size: 0.75em; +} + + +.jqplot-cursor-tooltip { + border: 1px solid #cccccc; + font-size: 0.75em; + white-space: nowrap; + background: rgba(208,208,208,0.5); + padding: 1px; +} + +.jqplot-highlighter-tooltip, .jqplot-canvasOverlay-tooltip { + border: 1px solid #cccccc; + font-size: 0.75em; + white-space: nowrap; + background: rgba(208,208,208,0.5); + padding: 1px; +} + +.jqplot-point-label { + font-size: 0.75em; + z-index: 2; +} + +td.jqplot-cursor-legend-swatch { + vertical-align: middle; + text-align: center; +} + +div.jqplot-cursor-legend-swatch { + width: 1.2em; + height: 0.7em; +} + +.jqplot-error { +/* Styles added to the plot target container when there is an error go here.*/ + text-align: center; +} + +.jqplot-error-message { +/* Styling of the custom error message div goes here.*/ + position: relative; + top: 46%; + display: inline-block; +} + +div.jqplot-bubble-label { + font-size: 0.8em; +/* background: rgba(90%, 90%, 90%, 0.15);*/ + padding-left: 2px; + padding-right: 2px; + color: rgb(20%, 20%, 20%); +} + +div.jqplot-bubble-label.jqplot-bubble-label-highlight { + background: rgba(90%, 90%, 90%, 0.7); +} + +div.jqplot-noData-container { + text-align: center; + background-color: rgba(96%, 96%, 96%, 0.3); +} + +.relationalTable td { + vertical-align: top; +} + +.relationalTable select { + width: 125px; + margin-right: 5px; +} + +.report-data { + height:13em; + overflow:scroll; + width:570px; + border: solid 1px; + background: white; + padding: 2px; +} + +.report-description { + height:10em; + width:570px; +} + +div#page_content div#tableslistcontainer table.data { + border-top: 0.1px solid #EEEEEE; +} + +div#page_content div#tableslistcontainer, div#page_content div.notice, div#page_content div.result_query { + margin-top: 1em; +} + +table.show_create { + margin-top: 1em; +} + +table.show_create td { + border-right: 1px solid #bbb; +} + +#alias_modal table { + width: 100%; +} + +#alias_modal label { + font-weight: bold; +} + +.ui-dialog { + position: fixed; +} + +.small_font { + font-size: smaller; +} + +/* Console styles */ +#pma_console_container { + width: 100%; + position: fixed; + bottom: 0; + : 0; + z-index: 100; +} +#pma_console { + position: relative; + margin-: 240px; + z-index: 100; +} +#pma_console .templates { + display: none; +} +#pma_console .mid_text, +#pma_console .toolbar span { + vertical-align: middle; +} +#pma_console .toolbar { + position: relative; + background: #ccc; + border-top: solid 1px #aaa; + cursor: n-resize; +} +#pma_console .toolbar.collapsed:not(:hover) { + display: inline-block; + border-top--radius: 3px; + border-: solid 1px #aaa; +} +#pma_console .toolbar.collapsed { + cursor: default; +} +#pma_console .toolbar.collapsed>.button { + display: none; +} +#pma_console .message span.text, +#pma_console .message span.action, +#pma_console .toolbar .button, +#pma_console .toolbar .text, +#pma_console .switch_button { + padding: 0 3px; + display: inline-block; +} +#pma_console .message span.action, +#pma_console .toolbar .button, +#pma_console .switch_button { + cursor: pointer; +} +#pma_console .message span.action:hover, +#pma_console .toolbar .button:hover, +#pma_console .switch_button:hover, +#pma_console .toolbar .button.active { + background: #ddd; +} +#pma_console .toolbar .text { + font-weight: bold; +} +#pma_console .toolbar .button, +#pma_console .toolbar .text { + margin-: .4em; +} +#pma_console .toolbar .button, +#pma_console .toolbar .text { + float: ; +} +#pma_console .content { + overflow-x: hidden; + overflow-y: auto; + margin-bottom: -65px; + border-top: solid 1px #aaa; + background: #fff; + padding-top: .4em; +} +#pma_console .content.console_dark_theme { + background: #000; + color: #fff; +} +#pma_console .content.console_dark_theme .CodeMirror-wrap { + background: #000; + color: #fff; +} +#pma_console .content.console_dark_theme .action_content { + color: #000; +} +#pma_console .content.console_dark_theme .message { + border-color: #373B41; +} +#pma_console .content.console_dark_theme .CodeMirror-cursor { + border-color: #fff; +} +#pma_console .content.console_dark_theme .cm-keyword { + color: #de935f; +} +#pma_console .message, +#pma_console .query_input { + position: relative; + font-family: Monaco, Consolas, monospace; + cursor: text; + margin: 0 10px .2em 1.4em; +} +#pma_console .message { + border-bottom: solid 1px #ccc; + padding-bottom: .2em; +} +#pma_console .message.expanded>.action_content { + position: relative; +} +#pma_console .message:before, +#pma_console .query_input:before { + left: -0.7em; + position: absolute; + content: ">"; +} +#pma_console .query_input:before { + top: -2px; +} +#pma_console .query_input textarea { + width: 100%; + height: 4em; + resize: vertical; +} +#pma_console .message:hover:before { + color: #7cf; + font-weight: bold; +} +#pma_console .message.expanded:before { + content: "]"; +} +#pma_console .message.welcome:before { + display: none; +} +#pma_console .message.failed:before, +#pma_console .message.failed.expanded:before, +#pma_console .message.failed:hover:before { + content: "="; + color: #944; +} +#pma_console .message.pending:before { + opacity: .3; +} +#pma_console .message.collapsed>.query { + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; +} +#pma_console .message.expanded>.query { + display: block; + white-space: pre; + word-wrap: break-word; +} +#pma_console .message .text.targetdb, +#pma_console .message.collapsed .action.collapse, +#pma_console .message.expanded .action.expand, +#pma_console .message .action.requery, +#pma_console .message .action.profiling, +#pma_console .message .action.explain, +#pma_console .message .action.bookmark { + display: none; +} +#pma_console .message.select .action.profiling, +#pma_console .message.select .action.explain, +#pma_console .message.history .text.targetdb, +#pma_console .message.successed .text.targetdb, +#pma_console .message.history .action.requery, +#pma_console .message.history .action.bookmark, +#pma_console .message.bookmark .action.requery, +#pma_console .message.bookmark .action.bookmark, +#pma_console .message.successed .action.requery, +#pma_console .message.successed .action.bookmark { + display: inline-block; +} +#pma_console .message .action_content { + position: absolute; + bottom: 100%; + background: #ccc; + border: solid 1px #aaa; + border-top--radius: 3px; +} +html.ie8 #pma_console .message .action_content { + position: relative!important; +} +#pma_console .message.bookmark .text.targetdb, +#pma_console .message .text.query_time { + margin: 0; + display: inline-block; +} +#pma_console .message.failed .text.query_time, +#pma_console .message .text.failed { + display: none; +} +#pma_console .message.failed .text.failed { + display: inline-block; +} +#pma_console .message .text { + background: #fff; +} +#pma_console .message.collapsed>.action_content { + display: none; +} +#pma_console .message.collapsed:hover>.action_content { + display: block; +} +#pma_console .message .bookmark_label { + padding: 0 4px; + top: 0; + background: #369; + color: #fff; + border-radius: 3px; +} +#pma_console .message .bookmark_label.shared { + background: #396; +} +#pma_console .message.expanded .bookmark_label { + border-top-left-radius: 0; + border-top-right-radius: 0; +} +#pma_console .query_input { + position: relative; +} +#pma_console .mid_layer { + height: 100%; + width: 100%; + position: absolute; + top: 0; + /* For support IE8, this layer doesn't use filter:opacity or opacity, + js code will fade this layer opacity to 0.18(using animation) */ + background: #666; + display: none; + cursor: pointer; + z-index: 200; +} +#pma_console .card { + position: absolute; + width: 94%; + height: 100%; + min-height: 48px; + : 100%; + top: 0; + border-: solid 1px #999; + z-index: 300; + transition: 0.2s; + -ms-transition: 0.2s; + -webkit-transition: 0.2s; + -moz-transition: 0.2s; +} +#pma_console .card.show { + : 6%; + box-shadow: -2px 1px 4px -1px #999; +} + +html.ie7 #pma_console .query_input { + display: none; +} + +#pma_bookmarks .content.add_bookmark, +#pma_console_options .content { + padding: 4px 6px; +} +#pma_bookmarks .content.add_bookmark .options { + margin-: 1.4em; + padding-bottom: .4em; + margin-bottom: .4em; + border-bottom: solid 1px #ccc; +} +#pma_bookmarks .content.add_bookmark .options button { + margin: 0 7px; + vertical-align: bottom; +} +#pma_bookmarks .content.add_bookmark input[type=text] { + margin: 0; + padding: 2px 4px; +} +#pma_console .button.hide, +#pma_console .message span.text.hide { + display: none; +} +#debug_console.grouped .ungroup_queries, +#debug_console.ungrouped .group_queries { + display: inline-block; +} +#debug_console.ungrouped .ungroup_queries, +#debug_console.ungrouped .sort_count, +#debug_console.grouped .group_queries { + display: none; +} +#debug_console .count { + margin-right: 8px; +} +#debug_console .show_trace .trace, +#debug_console .show_args .args { + display: block; +} +#debug_console .hide_trace .trace, +#debug_console .hide_args .args, +#debug_console .show_trace .action.dbg_show_trace, +#debug_console .hide_trace .action.dbg_hide_trace, +#debug_console .traceStep.hide_args .action.dbg_hide_args, +#debug_console .traceStep.show_args .action.dbg_show_args { + display: none; +} + +#debug_console .traceStep:after, +#debug_console .trace.welcome:after, +#debug_console .debug>.welcome:after { + content: ""; + display: table; + clear: both; +} +#debug_console .debug_summary { + float: left; +} +#debug_console .trace.welcome .time { + float: right; +} +#debug_console .traceStep .file, +#debug_console .script_name { + float: right; +} +#debug_console .traceStep .args pre { + margin: 0; +} + +/* Code mirror console style*/ + +.cm-s-pma .CodeMirror-code pre, +.cm-s-pma .CodeMirror-code { + font-family: Monaco, Consolas, monospace; +} +.cm-s-pma .CodeMirror-measure>pre, +.cm-s-pma .CodeMirror-code>pre, +.cm-s-pma .CodeMirror-lines { + padding: 0; +} +.cm-s-pma.CodeMirror { + resize: none; + height: auto; + width: 100%; + min-height: initial; + max-height: initial; +} +.cm-s-pma .CodeMirror-scroll { + cursor: text; +} + +/* PMA drop-improt style */ + +.pma_drop_handler { + display: none; + position: fixed; + top: 0; + left: 0; + width: 100%; + background: rgba(0, 0, 0, 0.6); + height: 100%; + z-index: 999; + color: white; + font-size: 30pt; + text-align: center; + padding-top: 20%; +} + +.pma_sql_import_status { + display: none; + position: fixed; + bottom: 0; + right: 25px; + width: 400px; + border: 1px solid #999; + background: #f3f3f3; + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + border-radius: 4px; + -moz-box-shadow: 2px 2px 5px #ccc; + -webkit-box-shadow: 2px 2px 5px #ccc; + box-shadow: 2px 2px 5px #ccc; +} + +.pma_sql_import_status h2, +.pma_drop_result h2 { + background-color: #bbb; + padding: .1em .3em; + margin-top: 0; + margin-bottom: 0; + color: #fff; + font-size: 1.6em; + font-weight: normal; + text-shadow: 0 1px 0 #777; + -moz-box-shadow: 1px 1px 15px #999 inset; + -webkit-box-shadow: 1px 1px 15px #999 inset; + box-shadow: 1px 1px 15px #999 inset; +} + +.pma_sql_import_status div { + height: 270px; + overflow-y:auto; + overflow-x:hidden; + list-style-type: none; +} + +.pma_sql_import_status div li { + padding: 8px 10px; + border-bottom: 1px solid #bbb; + color: rgb(148, 14, 14); + background: white; +} + +.pma_sql_import_status div li .filesize { + float: right; +} + +.pma_sql_import_status h2 .minimize { + float: right; + margin-right: 5px; + padding: 0 10px; +} + +.pma_sql_import_status h2 .close { + float: right; + margin-right: 5px; + padding: 0 10px; + display: none; +} + +.pma_sql_import_status h2 .minimize:hover, +.pma_sql_import_status h2 .close:hover, +.pma_drop_result h2 .close:hover { + background: rgba(155, 149, 149, 0.78); + cursor: pointer; +} + +.pma_drop_file_status { + color: #235a81; +} + +.pma_drop_file_status span.underline:hover { + cursor: pointer; + text-decoration: underline; +} + +.pma_drop_result { + position: fixed; + top: 10%; + left: 20%; + width: 60%; + background: white; + min-height: 300px; + z-index: 800; + -webkit-box-shadow: 0 0 15px #999; + border-radius: 10px; + cursor: move; +} + +.pma_drop_result h2 .close { + float: right; + margin-right: 5px; + padding: 0 10px; +} + +#composite_index_list { + list-style-type: none; + list-style-position: inside; +} + +span.drag_icon { + display: inline-block; + background-image: url('getImgPath('s_sortable.png');?>'); + background-position: center center; + background-repeat: no-repeat; + width: 1em; + height: 3em; + cursor: move; +} + +.topmargin { + margin-top: 1em; +} + +/* styles for sortable tables created with tablesorter jquery plugin */ +th.header { + cursor: pointer; + color: #0000FF; +} + +th.header:hover { + text-decoration: underline; +} + +th.header .sorticon { + width: 16px; + height: 16px; + background-repeat: no-repeat; + background-position: right center; + display: inline-table; + vertical-align: middle; + float: right; +} + +th.headerSortUp .sorticon, th.headerSortDown:hover .sorticon { + background-image: url(getImgPath('s_desc.png');?>); +} + +th.headerSortDown .sorticon, th.headerSortUp:hover .sorticon { + background-image: url(getImgPath('s_asc.png');?>); +} +/* end of styles of sortable tables */ + +/* styles for jQuery-ui to support rtl languages */ +body .ui-dialog .ui-dialog-titlebar-close { + : .3em; + : initial; +} + +body .ui-dialog .ui-dialog-title { + float: ; +} + +body .ui-dialog .ui-dialog-buttonpane .ui-dialog-buttonset { + float: ; +} +/* end of styles for jQuery-ui to support rtl languages */ + +/* Override some jQuery-ui styling to have square corners */ +body .ui-corner-all, +body .ui-corner-top, +body .ui-corner-left, +body .ui-corner-tl { + border-top-left-radius: 0; +} +body .ui-corner-all, +body .ui-corner-top, +body .ui-corner-right, +body .ui-corner-tr { + border-top-right-radius: 0; +} +body .ui-corner-all, +body .ui-corner-bottom, +body .ui-corner-left, +body .ui-corner-bl { + border-bottom-left-radius: 0; +} +body .ui-corner-all, +body .ui-corner-bottom, +body .ui-corner-right, +body .ui-corner-br { + border-bottom-right-radius: 0; +} +/* Override jQuery-ui styling for ui-dialog */ +body .ui-dialog { + padding: 0; + border-color: #000000; +} +body .ui-dialog .ui-dialog-titlebar { + padding: .3em .5em; + border: none; + border-bottom: 1px solid #000000; +} +body .ui-dialog .ui-dialog-titlebar button { + border: 1px solid #999999; +} +body .ui-dialog .ui-dialog-content { + padding: .2em .4em; +} +body .ui-dialog .ui-dialog-buttonpane { + background: #D3DCE3; + border-top: 1px solid #000000; +} +body .ui-dialog .ui-dialog-buttonpane button { + margin: .1em 0 .1em .4em; + border: 1px solid #999999; + color: #000000; +} +body .ui-dialog .ui-button-text-only .ui-button-text { + padding: .2em .6em; +} + +.scrollindicator { + display: none; +} + +@media only screen and (max-width: 768px) { + /* For mobile phones: */ + #main_pane_left { + width: 100%; + } + + #main_pane_right { + padding-top: 0; + padding-: 1px; + padding-: 1px; + } + + ul#topmenu, + ul.tabs { + display: flex; + } + + .navigationbar { + display: inline-flex; + margin: 0 !important; + border-radius: 0 !important; + overflow: auto; + } + + .scrollindicator { + padding: 5px; + cursor: pointer; + display: inline; + } + + .responsivetable { + overflow-x: auto; + } + + body#loginform div.container { + width: 100%; + } + + .largescreenonly { + display: none; + } + + .width100, .desktop50 { + width: 100%; + } + + .width96 { + width: 96% !important; + } + + #page_nav_icons { + display: none; + } + + table#serverstatusconnections { + margin-left: 0; + } + + #table_name_col_no { + top: 62px + } + + .tdblock tr td { + display: block; + } + + #table_columns { + margin-top: 60px; + } + + #table_columns .tablesorter { + min-width: 100%; + } + + .doubleFieldset fieldset { + width: 98%; + } + + div#serverstatusquerieschart { + width: 100%; + height: 450px; + } + + .ui-dialog { + margin: 1%; + width: 95% !important; + } + + #serverinfo .item { + margin: 4px; + } +} + +/* templates/database/designer */ +/* side menu */ +#name-panel { + overflow:hidden; +} diff --git a/php/apps/phpmyadmin49/themes/original/css/navigation.css.php b/php/apps/phpmyadmin49/themes/original/css/navigation.css.php new file mode 100644 index 00000000..9666b02a --- /dev/null +++ b/php/apps/phpmyadmin49/themes/original/css/navigation.css.php @@ -0,0 +1,424 @@ + + +/******************************************************************************/ +/* Navigation */ + +#pma_navigation { + background: ; + color: ; + width: px; + overflow: hidden; + position: fixed; + top: 0; + : 0; + height: 100vh; + border-: 1px solid gray; + z-index: 800; +} + +#pma_navigation_content { + width: 100%; + height: 100%; + position: absolute; + top: 0; + : 0; + z-index: 0; + padding-bottom: 1em; +} + +#pma_navigation ul { + margin: 0; +} + +#pma_navigation form { + margin: 0; + padding: 0; + display: inline; +} + +#pma_navigation select#select_server, +#pma_navigation select#lightm_db { + width: 100%; +} + +/******************************************************************************/ +/* specific elements */ + +#pma_navigation div.pageselector { + text-align: center; + margin: 0 0 0; + margin-: 0.75em; + border-: 1px solid #666; +} + +#pma_navigation div#pmalogo { + + background-color: ; + padding: .3em; +} + +#pma_navigation div#recentTableList, +#pma_navigation div#FavoriteTableList { + text-align: center; + margin-bottom: 0.5em; +} + +#pma_navigation #recentTable, +#pma_navigation #FavoriteTable { + width: 200px; +} + +#pma_navigation #pmalogo, +#pma_navigation #serverChoice, +#pma_navigation #navipanellinks, +#pma_navigation #recentTableList, +#pma_navigation #FavoriteTableList, +#pma_navigation #databaseList, +#pma_navigation div.pageselector.dbselector { + text-align: center; + margin-bottom: 0.3em; + padding-bottom: 0.3em; + border: 0; +} + +#pma_navigation #recentTableList select, +#pma_navigation #FavoriteTableList select, +#pma_navigation #serverChoice select + { + width: 80%; +} + +#pma_navigation #recentTableList, +#pma_navigation #FavoriteTableList { + margin-bottom: 0; + padding-bottom: 0; +} + +#pma_navigation_content > img.throbber { + display: block; + margin: 0 auto; +} + +/* Navigation tree*/ +#pma_navigation_tree { + margin: 0; + margin-: 1em; + color: #444; + height: 74%; + position: relative; +} + +#pma_navigation_select_database { + text-align: left; + padding: 0 0 0; + border: 0; + margin: 0; +} + +#pma_navigation_db_select { + margin-top: 0.5em; + margin-: 0.75em; +} +#pma_navigation_db_select select { + background: url("./themes/pmahomme/img/select_bg.png") repeat scroll 0 0; + -webkit-border-radius: 2px; + border-radius: 2px; + border: 1px solid #bbb; + border-top: 1px solid #bbb; + color: #333; + padding: 4px 6px; + margin: 0 0 0; + width: 92%; + font-size: 1.11em; +} + +#pma_navigation_tree_content { + width: 100%; + overflow: hidden; + overflow-y: auto; + position: absolute; + height: 100%; +} +#pma_navigation_tree_content a.hover_show_full { + position: relative; + z-index: 100; + vertical-align: sub; +} +#pma_navigation_tree a { + color: ; +} +#pma_navigation_tree a:hover { + text-decoration: underline; +} +#pma_navigation_tree li.activePointer { + color: ; + background-color: ; +} +#pma_navigation_tree li.selected { + color: ; + background-color: ; +} +#pma_navigation_tree li .dbItemControls { + padding-left: 4px; +} +#pma_navigation_tree li .navItemControls { + display: none; + padding-left: 4px; +} +#pma_navigation_tree li.activePointer .navItemControls { + display: inline; + opacity: 0.5; +} +#pma_navigation_tree li.activePointer .navItemControls:hover { + display: inline; + opacity: 1.0; +} +#pma_navigation_tree ul { + clear: both; + padding: 0; + list-style-type: none; + margin: 0; +} +#pma_navigation_tree ul ul { + position: relative; +} +#pma_navigation_tree li { + white-space: nowrap; + clear: both; + min-height: 16px; +} +#pma_navigation_tree img { + margin: 0; +} +#pma_navigation_tree i { + display: block; +} +#pma_navigation_tree div.block { + position: relative; + width:1.5em; + height:1.5em; + min-width: 16px; + min-height: 16px; + float: ; +} +#pma_navigation_tree div.block.double { + width: 3em; +} +#pma_navigation_tree div.block i, +#pma_navigation_tree div.block b { + width: 1.5em; + height: 1.7em; + min-width: 16px; + min-height: 8px; + position: absolute; + bottom: 0.7em; + : 0.75em; + z-index: 0; +} +#pma_navigation_tree div.block i { + border-: 1px solid #666; + border-bottom: 1px solid #666; + position: relative; + z-index: 0; +} +#pma_navigation_tree div.block i.first { /* Removes top segment */ + border-: 0; +} +/* Bottom segment for the tree element connections */ +#pma_navigation_tree div.block b { + display: block; + height: 0.75em; + bottom: 0; + : 0.75em; + border-: 1px solid #666; +} +#pma_navigation_tree div.block a, +#pma_navigation_tree div.block u { + position: absolute; + : 50%; + top: 50%; + z-index: 10; +}#pma_navigation_tree div.block a + a { + : 100%; +} +#pma_navigation_tree div.block.double a, +#pma_navigation_tree div.block.double u { + : 25%; +} +#pma_navigation_tree div.block.double a + a { + : 70%; +} +#pma_navigation_tree div.block img { + position: relative; + top: -0.6em; + : 0; + margin-: -5px; +} +#pma_navigation_tree li.last > ul { + background: none; +} +#pma_navigation_tree li > a, #pma_navigation_tree li > i { + line-height: 1.5em; + height: 1.5em; + padding-: 0.3em; +} +#pma_navigation_tree .list_container { + border-: 1px solid #666; + margin-: 0.75em; + padding-: 0.75em; +} +#pma_navigation_tree .last > .list_container { + border-: 0 solid #666; +} + +/* Fast filter */ +li.fast_filter { + padding-: 0.75em; + margin-: 0.75em; + padding-: 35px; + border-: 1px solid #666; +} +li.fast_filter input { + padding-: 1.7em; + width: 100%; +} +li.fast_filter span { + position: relative; + : 1.5em; + padding: 0.2em; + cursor: pointer; + font-weight: bold; + color: #800; +} +/* IE10+ has its own reset X */ +html.ie li.fast_filter span { + display: none; +} +html.ie.ie9 li.fast_filter span, +html.ie.ie8 li.fast_filter span { + display: auto; +} +html.ie li.fast_filter input { + padding-: .2em; +} +html.ie.ie9 li.fast_filter input, +html.ie.ie8 li.fast_filter input { + padding-: 1.7em; +} +li.fast_filter.db_fast_filter { + border: 0; +} + +/* Resize handler */ +#pma_navigation_resizer { + width: 3px; + height: 100%; + background-color: #aaa; + cursor: col-resize; + position: fixed; + top: 0; + : 240px; + z-index: 801; +} +#pma_navigation_collapser { + width: 20px; + height: 22px; + line-height: 22px; + background: #eee; + color: #555; + font-weight: bold; + position: fixed; + top: 0; + : px; + text-align: center; + cursor: pointer; + z-index: 800; + text-shadow: 0 1px 0 #fff; + filter: dropshadow(color=#fff, offx=0, offy=1); + border: 1px solid #888; +} + +#navigation_controls_outer { + min-height: 21px !important; +} + +#navigation_controls_outer.activePointer { + background-color: transparent !important; +} + +#navigation_controls { + float: right; + padding-right: 23px; +} + +/* Quick warp links */ +.pma_quick_warp { + margin-top: 5px; + margin-: 2px; + position: relative; +} +.pma_quick_warp .drop_list { + float: ; + margin-: 3px; + padding: 2px 0; +} +.pma_quick_warp .drop_button{ + padding: 0 .3em; + border: 1px solid #ddd; + background: #f2f2f2; + cursor: pointer; +} +.pma_quick_warp .drop_list:hover .drop_button { + background: #fff; +} +.pma_quick_warp .drop_list ul { + position: absolute; + margin: 0; + padding: 0; + overflow: hidden; + overflow-y: auto; + list-style: none; + background: #fff; + border: 1px solid #ddd; + border-top--radius: 0; + border-bottom--radius: 0; + top: 100%; + : 3px; + : 0; + display: none; + z-index: 802; +} +.pma_quick_warp .drop_list:hover ul { + display: block; +} +.pma_quick_warp .drop_list li { + white-space: nowrap; +} +.pma_quick_warp .drop_list li img { + vertical-align: sub; +} +.pma_quick_warp .drop_list li:hover { + background: #f2f2f2; +} +.pma_quick_warp .drop_list a { + display: block; + padding: .1em .3em; +} +.pma_quick_warp .drop_list a.favorite_table_anchor { + clear: left; + float: left; + padding: .1em .3em 0; +} diff --git a/php/apps/phpmyadmin49/themes/original/css/printview.css b/php/apps/phpmyadmin49/themes/original/css/printview.css new file mode 100644 index 00000000..809cbdd0 --- /dev/null +++ b/php/apps/phpmyadmin49/themes/original/css/printview.css @@ -0,0 +1,169 @@ +@media print { + #back_button_print_view, #print_button_print_view { + display: none; + } +} + +/* For removing element from Print View */ +.print_ignore { + display: none; +} + +.nowrap { + white-space: nowrap; +} + +.hide { + display: none; +} + +/* Standard CSS */ +body, table, th, td { + color: #000; + background-color: #fff; + font-size: 12px; +} + +/* To remove link text decoration */ +a:link { + color:#000; + text-decoration:none +} + +/* To remove any image borders */ +img { + border: 0; +} + +/* Table specific */ +table, th, td { + border: .1em solid #000; + background-color: #fff; +} + +table { + border-collapse: collapse; + border-spacing: 0.2em; +} + +thead { + border-collapse: collapse; + border-spacing: 0.2em; + border: .1em solid #000; + font-weight: 900; +} + +th, td { + padding: 0.2em; +} + +thead th { + font-weight: bold; + background-color: #e5e5e5; + border: .1em solid #000; +} + +th.vtop, td.vtop { + vertical-align: top; +} + +th.vbottom, td.vbottom { + vertical-align: bottom; +} + +/* Common Elements not to be included */ +/* Hide Navigation and Top Menu bar */ +#pma_navigation, #floating_menubar { + display: none; +} +/* Hide console */ +#pma_console_container { + display: none; +} + +/* Hide Navigation items (like Goto Top) */ +#page_nav_icons { + display: none; +} + +/* Hide the Create Table form */ +#create_table_form_minimal { + display: none; +} + +/* Hide the Page Settings Modal box */ +#page_settings_modal { + display: none; +} + +/* Hide footer, Demo notice, errors div */ +#pma_footer, #pma_demo, #pma_errors { + display: none; +} + +/* Hide the #selflink div */ +#selflink { + display: none; +} + +/* Position the main content */ +#page_content { + position: absolute; + left: 0; + top: 0; + width: 95%; + float: none; +} + +/* Specific Class for overriding while Print */ +.print { + background-color: #000; +} + +/* For the Success message div */ +div.success { + background-color: #fff; +} + +.sqlOuter { + color: black; + background-color: #000; +} + +/* For hiding 'Open a New phpMyAdmin Window' button */ +.ic_window-new, .ic_s_cog { + display: none; +} + +.sticky_columns tr { + display: none; +} + +#structure-action-links, #addColumns { + display: none; +} + +/* Hide extra menu on tbl_structure.php */ +#topmenu2 { + display: none; +} + +.cDrop, .cEdit, .cList, .cCpy, .cPointer { + display: none; +} + +/* odd items 1,3,5,7,... */ +table tbody:first-of-type tr:nth-child(odd), +table tbody:first-of-type tr:nth-child(odd) th { + background: #fff; +} + +/* even items 2,4,6,8,... */ +table tbody:first-of-type tr:nth-child(even), +table tbody:first-of-type tr:nth-child(even) th { + background: #DFDFDF; +} + +.column_attribute { + font-size: 100%; +} diff --git a/php/apps/phpmyadmin49/themes/original/img/ajax_clock_small.gif b/php/apps/phpmyadmin49/themes/original/img/ajax_clock_small.gif new file mode 100644 index 00000000..bde4932c Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/ajax_clock_small.gif differ diff --git a/php/apps/phpmyadmin49/themes/original/img/arrow_ltr.png b/php/apps/phpmyadmin49/themes/original/img/arrow_ltr.png new file mode 100644 index 00000000..cd79ab42 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/arrow_ltr.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/arrow_rtl.png b/php/apps/phpmyadmin49/themes/original/img/arrow_rtl.png new file mode 100644 index 00000000..d035b9d9 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/arrow_rtl.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/b_bookmark.png b/php/apps/phpmyadmin49/themes/original/img/b_bookmark.png new file mode 100644 index 00000000..09b1b586 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/b_bookmark.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/b_browse.png b/php/apps/phpmyadmin49/themes/original/img/b_browse.png new file mode 100644 index 00000000..440b2e63 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/b_browse.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/b_calendar.png b/php/apps/phpmyadmin49/themes/original/img/b_calendar.png new file mode 100644 index 00000000..8dfe6287 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/b_calendar.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/b_chart.png b/php/apps/phpmyadmin49/themes/original/img/b_chart.png new file mode 100644 index 00000000..a8c43e65 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/b_chart.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/b_close.png b/php/apps/phpmyadmin49/themes/original/img/b_close.png new file mode 100644 index 00000000..8d8e98ec Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/b_close.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/b_column_add.png b/php/apps/phpmyadmin49/themes/original/img/b_column_add.png new file mode 100644 index 00000000..57e2a8ad Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/b_column_add.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/b_comment.png b/php/apps/phpmyadmin49/themes/original/img/b_comment.png new file mode 100644 index 00000000..fa2ee942 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/b_comment.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/b_dbstatistics.png b/php/apps/phpmyadmin49/themes/original/img/b_dbstatistics.png new file mode 100644 index 00000000..fca48f9a Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/b_dbstatistics.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/b_deltbl.png b/php/apps/phpmyadmin49/themes/original/img/b_deltbl.png new file mode 100644 index 00000000..2144bce4 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/b_deltbl.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/b_docs.png b/php/apps/phpmyadmin49/themes/original/img/b_docs.png new file mode 100644 index 00000000..bd47ebc1 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/b_docs.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/b_drop.png b/php/apps/phpmyadmin49/themes/original/img/b_drop.png new file mode 100644 index 00000000..42bea8d1 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/b_drop.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/b_edit.png b/php/apps/phpmyadmin49/themes/original/img/b_edit.png new file mode 100644 index 00000000..2abf0818 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/b_edit.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/b_empty.png b/php/apps/phpmyadmin49/themes/original/img/b_empty.png new file mode 100644 index 00000000..c5071858 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/b_empty.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/b_engine.png b/php/apps/phpmyadmin49/themes/original/img/b_engine.png new file mode 100644 index 00000000..12efc889 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/b_engine.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/b_event_add.png b/php/apps/phpmyadmin49/themes/original/img/b_event_add.png new file mode 100644 index 00000000..1f31c7cb Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/b_event_add.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/b_events.png b/php/apps/phpmyadmin49/themes/original/img/b_events.png new file mode 100644 index 00000000..07c60ff8 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/b_events.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/b_export.png b/php/apps/phpmyadmin49/themes/original/img/b_export.png new file mode 100644 index 00000000..9a43efdf Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/b_export.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/b_favorite.png b/php/apps/phpmyadmin49/themes/original/img/b_favorite.png new file mode 100644 index 00000000..d4f65788 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/b_favorite.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/b_find_replace.png b/php/apps/phpmyadmin49/themes/original/img/b_find_replace.png new file mode 100644 index 00000000..a0773af8 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/b_find_replace.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/b_ftext.png b/php/apps/phpmyadmin49/themes/original/img/b_ftext.png new file mode 100644 index 00000000..2ae04d77 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/b_ftext.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/b_globe.gif b/php/apps/phpmyadmin49/themes/original/img/b_globe.gif new file mode 100644 index 00000000..ef03dcf0 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/b_globe.gif differ diff --git a/php/apps/phpmyadmin49/themes/original/img/b_group.png b/php/apps/phpmyadmin49/themes/original/img/b_group.png new file mode 100644 index 00000000..4dd9d0b5 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/b_group.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/b_help.png b/php/apps/phpmyadmin49/themes/original/img/b_help.png new file mode 100644 index 00000000..eddf4d92 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/b_help.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/b_home.png b/php/apps/phpmyadmin49/themes/original/img/b_home.png new file mode 100644 index 00000000..5bfd8ad6 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/b_home.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/b_import.png b/php/apps/phpmyadmin49/themes/original/img/b_import.png new file mode 100644 index 00000000..ee313da9 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/b_import.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/b_index.png b/php/apps/phpmyadmin49/themes/original/img/b_index.png new file mode 100644 index 00000000..8a76003c Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/b_index.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/b_index_add.png b/php/apps/phpmyadmin49/themes/original/img/b_index_add.png new file mode 100644 index 00000000..78b1216a Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/b_index_add.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/b_inline_edit.png b/php/apps/phpmyadmin49/themes/original/img/b_inline_edit.png new file mode 100644 index 00000000..0d2dab05 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/b_inline_edit.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/b_insrow.png b/php/apps/phpmyadmin49/themes/original/img/b_insrow.png new file mode 100644 index 00000000..ce32a0a1 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/b_insrow.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/b_key.png b/php/apps/phpmyadmin49/themes/original/img/b_key.png new file mode 100644 index 00000000..9af08692 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/b_key.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/b_minus.png b/php/apps/phpmyadmin49/themes/original/img/b_minus.png new file mode 100644 index 00000000..668f0da1 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/b_minus.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/b_more.png b/php/apps/phpmyadmin49/themes/original/img/b_more.png new file mode 100644 index 00000000..9d84943e Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/b_more.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/b_move.png b/php/apps/phpmyadmin49/themes/original/img/b_move.png new file mode 100644 index 00000000..7ed238cf Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/b_move.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/b_newdb.png b/php/apps/phpmyadmin49/themes/original/img/b_newdb.png new file mode 100644 index 00000000..94166608 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/b_newdb.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/b_newtbl.png b/php/apps/phpmyadmin49/themes/original/img/b_newtbl.png new file mode 100644 index 00000000..f675656f Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/b_newtbl.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/b_nextpage.png b/php/apps/phpmyadmin49/themes/original/img/b_nextpage.png new file mode 100644 index 00000000..75a2c67c Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/b_nextpage.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/b_no_favorite.png b/php/apps/phpmyadmin49/themes/original/img/b_no_favorite.png new file mode 100644 index 00000000..0217dc3b Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/b_no_favorite.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/b_plugin.png b/php/apps/phpmyadmin49/themes/original/img/b_plugin.png new file mode 100644 index 00000000..205b9dab Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/b_plugin.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/b_plus.png b/php/apps/phpmyadmin49/themes/original/img/b_plus.png new file mode 100644 index 00000000..22bb1a9f Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/b_plus.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/b_primary.png b/php/apps/phpmyadmin49/themes/original/img/b_primary.png new file mode 100644 index 00000000..25a24ca2 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/b_primary.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/b_print.png b/php/apps/phpmyadmin49/themes/original/img/b_print.png new file mode 100644 index 00000000..892e51fa Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/b_print.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/b_props.png b/php/apps/phpmyadmin49/themes/original/img/b_props.png new file mode 100644 index 00000000..07ad49c6 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/b_props.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/b_relations.png b/php/apps/phpmyadmin49/themes/original/img/b_relations.png new file mode 100644 index 00000000..7c16044b Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/b_relations.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/b_report.png b/php/apps/phpmyadmin49/themes/original/img/b_report.png new file mode 100644 index 00000000..f5b57cdd Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/b_report.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/b_routine_add.png b/php/apps/phpmyadmin49/themes/original/img/b_routine_add.png new file mode 100644 index 00000000..78517c10 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/b_routine_add.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/b_routines.png b/php/apps/phpmyadmin49/themes/original/img/b_routines.png new file mode 100644 index 00000000..439899be Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/b_routines.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/b_save.png b/php/apps/phpmyadmin49/themes/original/img/b_save.png new file mode 100644 index 00000000..c35f004a Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/b_save.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/b_saveimage.png b/php/apps/phpmyadmin49/themes/original/img/b_saveimage.png new file mode 100644 index 00000000..0bc614a4 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/b_saveimage.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/b_sbrowse.png b/php/apps/phpmyadmin49/themes/original/img/b_sbrowse.png new file mode 100644 index 00000000..f69952c5 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/b_sbrowse.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/b_search.png b/php/apps/phpmyadmin49/themes/original/img/b_search.png new file mode 100644 index 00000000..6f34d0a5 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/b_search.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/b_select.png b/php/apps/phpmyadmin49/themes/original/img/b_select.png new file mode 100644 index 00000000..a83a0136 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/b_select.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/b_snewtbl.png b/php/apps/phpmyadmin49/themes/original/img/b_snewtbl.png new file mode 100644 index 00000000..f881dfd3 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/b_snewtbl.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/b_spatial.png b/php/apps/phpmyadmin49/themes/original/img/b_spatial.png new file mode 100644 index 00000000..6498e451 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/b_spatial.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/b_sql.png b/php/apps/phpmyadmin49/themes/original/img/b_sql.png new file mode 100644 index 00000000..218a64ce Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/b_sql.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/b_sqlhelp.png b/php/apps/phpmyadmin49/themes/original/img/b_sqlhelp.png new file mode 100644 index 00000000..3f4f59de Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/b_sqlhelp.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/b_table_add.png b/php/apps/phpmyadmin49/themes/original/img/b_table_add.png new file mode 100644 index 00000000..783e08bb Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/b_table_add.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/b_tblanalyse.png b/php/apps/phpmyadmin49/themes/original/img/b_tblanalyse.png new file mode 100644 index 00000000..6809a2d6 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/b_tblanalyse.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/b_tblexport.png b/php/apps/phpmyadmin49/themes/original/img/b_tblexport.png new file mode 100644 index 00000000..9886204b Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/b_tblexport.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/b_tblimport.png b/php/apps/phpmyadmin49/themes/original/img/b_tblimport.png new file mode 100644 index 00000000..8d0c3d9e Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/b_tblimport.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/b_tblops.png b/php/apps/phpmyadmin49/themes/original/img/b_tblops.png new file mode 100644 index 00000000..b71f7e0f Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/b_tblops.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/b_tbloptimize.png b/php/apps/phpmyadmin49/themes/original/img/b_tbloptimize.png new file mode 100644 index 00000000..0c8425ce Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/b_tbloptimize.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/b_tipp.png b/php/apps/phpmyadmin49/themes/original/img/b_tipp.png new file mode 100644 index 00000000..2cc774f7 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/b_tipp.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/b_trigger_add.png b/php/apps/phpmyadmin49/themes/original/img/b_trigger_add.png new file mode 100644 index 00000000..920a2a4f Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/b_trigger_add.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/b_triggers.png b/php/apps/phpmyadmin49/themes/original/img/b_triggers.png new file mode 100644 index 00000000..2ee1dc6f Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/b_triggers.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/b_undo.png b/php/apps/phpmyadmin49/themes/original/img/b_undo.png new file mode 100644 index 00000000..fef0fa4e Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/b_undo.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/b_unique.png b/php/apps/phpmyadmin49/themes/original/img/b_unique.png new file mode 100644 index 00000000..dc850775 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/b_unique.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/b_usradd.png b/php/apps/phpmyadmin49/themes/original/img/b_usradd.png new file mode 100644 index 00000000..5596b867 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/b_usradd.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/b_usrcheck.png b/php/apps/phpmyadmin49/themes/original/img/b_usrcheck.png new file mode 100644 index 00000000..dae06dab Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/b_usrcheck.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/b_usrdrop.png b/php/apps/phpmyadmin49/themes/original/img/b_usrdrop.png new file mode 100644 index 00000000..b407e7bf Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/b_usrdrop.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/b_usredit.png b/php/apps/phpmyadmin49/themes/original/img/b_usredit.png new file mode 100644 index 00000000..ed6c75a6 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/b_usredit.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/b_usrlist.png b/php/apps/phpmyadmin49/themes/original/img/b_usrlist.png new file mode 100644 index 00000000..0b43fa07 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/b_usrlist.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/b_versions.png b/php/apps/phpmyadmin49/themes/original/img/b_versions.png new file mode 100644 index 00000000..c253dc06 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/b_versions.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/b_view.png b/php/apps/phpmyadmin49/themes/original/img/b_view.png new file mode 100644 index 00000000..204b10d6 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/b_view.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/b_view_add.png b/php/apps/phpmyadmin49/themes/original/img/b_view_add.png new file mode 100644 index 00000000..2a6cfaf0 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/b_view_add.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/b_views.png b/php/apps/phpmyadmin49/themes/original/img/b_views.png new file mode 100644 index 00000000..a368d6a8 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/b_views.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/bd_browse.png b/php/apps/phpmyadmin49/themes/original/img/bd_browse.png new file mode 100644 index 00000000..f9547867 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/bd_browse.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/bd_deltbl.png b/php/apps/phpmyadmin49/themes/original/img/bd_deltbl.png new file mode 100644 index 00000000..b05b74bb Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/bd_deltbl.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/bd_drop.png b/php/apps/phpmyadmin49/themes/original/img/bd_drop.png new file mode 100644 index 00000000..4591b9da Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/bd_drop.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/bd_edit.png b/php/apps/phpmyadmin49/themes/original/img/bd_edit.png new file mode 100644 index 00000000..722afc17 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/bd_edit.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/bd_empty.png b/php/apps/phpmyadmin49/themes/original/img/bd_empty.png new file mode 100644 index 00000000..f1800d06 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/bd_empty.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/bd_export.png b/php/apps/phpmyadmin49/themes/original/img/bd_export.png new file mode 100644 index 00000000..bfcacf46 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/bd_export.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/bd_ftext.png b/php/apps/phpmyadmin49/themes/original/img/bd_ftext.png new file mode 100644 index 00000000..8e1a1153 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/bd_ftext.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/bd_index.png b/php/apps/phpmyadmin49/themes/original/img/bd_index.png new file mode 100644 index 00000000..b47a488b Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/bd_index.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/bd_insrow.png b/php/apps/phpmyadmin49/themes/original/img/bd_insrow.png new file mode 100644 index 00000000..b325ffa9 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/bd_insrow.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/bd_nextpage.png b/php/apps/phpmyadmin49/themes/original/img/bd_nextpage.png new file mode 100644 index 00000000..fd121254 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/bd_nextpage.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/bd_primary.png b/php/apps/phpmyadmin49/themes/original/img/bd_primary.png new file mode 100644 index 00000000..e1d9152a Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/bd_primary.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/bd_routine_add.png b/php/apps/phpmyadmin49/themes/original/img/bd_routine_add.png new file mode 100644 index 00000000..6f45cbd0 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/bd_routine_add.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/bd_sbrowse.png b/php/apps/phpmyadmin49/themes/original/img/bd_sbrowse.png new file mode 100644 index 00000000..c820422c Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/bd_sbrowse.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/bd_select.png b/php/apps/phpmyadmin49/themes/original/img/bd_select.png new file mode 100644 index 00000000..2cc94983 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/bd_select.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/bd_spatial.png b/php/apps/phpmyadmin49/themes/original/img/bd_spatial.png new file mode 100644 index 00000000..16f3b38c Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/bd_spatial.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/bd_unique.png b/php/apps/phpmyadmin49/themes/original/img/bd_unique.png new file mode 100644 index 00000000..7aa38dba Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/bd_unique.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/centralColumns.png b/php/apps/phpmyadmin49/themes/original/img/centralColumns.png new file mode 100644 index 00000000..4b94c9f7 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/centralColumns.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/centralColumns_add.png b/php/apps/phpmyadmin49/themes/original/img/centralColumns_add.png new file mode 100644 index 00000000..72825645 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/centralColumns_add.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/centralColumns_delete.png b/php/apps/phpmyadmin49/themes/original/img/centralColumns_delete.png new file mode 100644 index 00000000..98d6b05d Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/centralColumns_delete.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/cleardot.gif b/php/apps/phpmyadmin49/themes/original/img/cleardot.gif new file mode 100644 index 00000000..a9d7bea4 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/cleardot.gif differ diff --git a/php/apps/phpmyadmin49/themes/original/img/col_drop.png b/php/apps/phpmyadmin49/themes/original/img/col_drop.png new file mode 100644 index 00000000..9d84943e Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/col_drop.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/col_pointer.png b/php/apps/phpmyadmin49/themes/original/img/col_pointer.png new file mode 100644 index 00000000..041fbf2b Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/col_pointer.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/col_pointer_ver.png b/php/apps/phpmyadmin49/themes/original/img/col_pointer_ver.png new file mode 100644 index 00000000..79766f26 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/col_pointer_ver.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/console.png b/php/apps/phpmyadmin49/themes/original/img/console.png new file mode 100644 index 00000000..ad351da5 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/console.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/east-mini.png b/php/apps/phpmyadmin49/themes/original/img/east-mini.png new file mode 100644 index 00000000..6685939e Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/east-mini.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/error.ico b/php/apps/phpmyadmin49/themes/original/img/error.ico new file mode 100644 index 00000000..b5c06183 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/error.ico differ diff --git a/php/apps/phpmyadmin49/themes/original/img/eye.png b/php/apps/phpmyadmin49/themes/original/img/eye.png new file mode 100644 index 00000000..6c9972e0 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/eye.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/eye_grey.png b/php/apps/phpmyadmin49/themes/original/img/eye_grey.png new file mode 100644 index 00000000..0dc92a90 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/eye_grey.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/hide.png b/php/apps/phpmyadmin49/themes/original/img/hide.png new file mode 100644 index 00000000..2c914431 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/hide.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/lightbulb.png b/php/apps/phpmyadmin49/themes/original/img/lightbulb.png new file mode 100644 index 00000000..0cead20e Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/lightbulb.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/lightbulb_off.png b/php/apps/phpmyadmin49/themes/original/img/lightbulb_off.png new file mode 100644 index 00000000..dd3632d1 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/lightbulb_off.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/logo_left.png b/php/apps/phpmyadmin49/themes/original/img/logo_left.png new file mode 100644 index 00000000..004e7050 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/logo_left.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/logo_right.png b/php/apps/phpmyadmin49/themes/original/img/logo_right.png new file mode 100644 index 00000000..a4902891 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/logo_right.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/more.png b/php/apps/phpmyadmin49/themes/original/img/more.png new file mode 100644 index 00000000..ac803c44 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/more.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/new_data.png b/php/apps/phpmyadmin49/themes/original/img/new_data.png new file mode 100644 index 00000000..c173bc03 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/new_data.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/new_data_hovered.png b/php/apps/phpmyadmin49/themes/original/img/new_data_hovered.png new file mode 100644 index 00000000..73b09a63 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/new_data_hovered.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/new_data_selected.png b/php/apps/phpmyadmin49/themes/original/img/new_data_selected.png new file mode 100644 index 00000000..a75abe34 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/new_data_selected.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/new_data_selected_hovered.png b/php/apps/phpmyadmin49/themes/original/img/new_data_selected_hovered.png new file mode 100644 index 00000000..7091ae36 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/new_data_selected_hovered.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/new_struct.png b/php/apps/phpmyadmin49/themes/original/img/new_struct.png new file mode 100644 index 00000000..79fe646c Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/new_struct.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/new_struct_hovered.png b/php/apps/phpmyadmin49/themes/original/img/new_struct_hovered.png new file mode 100644 index 00000000..b29aaed3 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/new_struct_hovered.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/new_struct_selected.png b/php/apps/phpmyadmin49/themes/original/img/new_struct_selected.png new file mode 100644 index 00000000..bc61749a Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/new_struct_selected.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/new_struct_selected_hovered.png b/php/apps/phpmyadmin49/themes/original/img/new_struct_selected_hovered.png new file mode 100644 index 00000000..9a82bc42 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/new_struct_selected_hovered.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/normalize.png b/php/apps/phpmyadmin49/themes/original/img/normalize.png new file mode 100644 index 00000000..5f8e95f5 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/normalize.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/north-mini.png b/php/apps/phpmyadmin49/themes/original/img/north-mini.png new file mode 100644 index 00000000..c3b60620 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/north-mini.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/pause.png b/php/apps/phpmyadmin49/themes/original/img/pause.png new file mode 100644 index 00000000..0271217a Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/pause.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/play.png b/php/apps/phpmyadmin49/themes/original/img/play.png new file mode 100644 index 00000000..75a2c67c Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/play.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/s_asc.png b/php/apps/phpmyadmin49/themes/original/img/s_asc.png new file mode 100644 index 00000000..42ec9553 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/s_asc.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/s_asci.png b/php/apps/phpmyadmin49/themes/original/img/s_asci.png new file mode 100644 index 00000000..57267f43 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/s_asci.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/s_attention.png b/php/apps/phpmyadmin49/themes/original/img/s_attention.png new file mode 100644 index 00000000..5a536e81 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/s_attention.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/s_cancel.png b/php/apps/phpmyadmin49/themes/original/img/s_cancel.png new file mode 100644 index 00000000..f101a884 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/s_cancel.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/s_cog.png b/php/apps/phpmyadmin49/themes/original/img/s_cog.png new file mode 100644 index 00000000..5f40f737 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/s_cog.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/s_collapseall.png b/php/apps/phpmyadmin49/themes/original/img/s_collapseall.png new file mode 100644 index 00000000..3d56b7c5 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/s_collapseall.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/s_db.png b/php/apps/phpmyadmin49/themes/original/img/s_db.png new file mode 100644 index 00000000..2e35e05b Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/s_db.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/s_desc.png b/php/apps/phpmyadmin49/themes/original/img/s_desc.png new file mode 100644 index 00000000..cc8804c1 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/s_desc.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/s_error.png b/php/apps/phpmyadmin49/themes/original/img/s_error.png new file mode 100644 index 00000000..448225cf Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/s_error.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/s_fulltext.png b/php/apps/phpmyadmin49/themes/original/img/s_fulltext.png new file mode 100644 index 00000000..b810b0cc Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/s_fulltext.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/s_host.png b/php/apps/phpmyadmin49/themes/original/img/s_host.png new file mode 100644 index 00000000..4ff1ce85 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/s_host.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/s_info.png b/php/apps/phpmyadmin49/themes/original/img/s_info.png new file mode 100644 index 00000000..b697dddd Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/s_info.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/s_lang.png b/php/apps/phpmyadmin49/themes/original/img/s_lang.png new file mode 100644 index 00000000..abbb88a4 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/s_lang.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/s_link.png b/php/apps/phpmyadmin49/themes/original/img/s_link.png new file mode 100644 index 00000000..609970a3 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/s_link.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/s_lock.png b/php/apps/phpmyadmin49/themes/original/img/s_lock.png new file mode 100644 index 00000000..55258949 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/s_lock.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/s_loggoff.png b/php/apps/phpmyadmin49/themes/original/img/s_loggoff.png new file mode 100644 index 00000000..ac5ccde8 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/s_loggoff.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/s_notice.png b/php/apps/phpmyadmin49/themes/original/img/s_notice.png new file mode 100644 index 00000000..64d1722e Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/s_notice.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/s_okay.png b/php/apps/phpmyadmin49/themes/original/img/s_okay.png new file mode 100644 index 00000000..9bf92354 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/s_okay.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/s_partialtext.png b/php/apps/phpmyadmin49/themes/original/img/s_partialtext.png new file mode 100644 index 00000000..a8fbb343 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/s_partialtext.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/s_passwd.png b/php/apps/phpmyadmin49/themes/original/img/s_passwd.png new file mode 100644 index 00000000..c705bd55 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/s_passwd.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/s_really.png b/php/apps/phpmyadmin49/themes/original/img/s_really.png new file mode 100644 index 00000000..e48c9cd4 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/s_really.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/s_reload.png b/php/apps/phpmyadmin49/themes/original/img/s_reload.png new file mode 100644 index 00000000..cc0b9e55 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/s_reload.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/s_replication.png b/php/apps/phpmyadmin49/themes/original/img/s_replication.png new file mode 100644 index 00000000..c5c5a5c5 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/s_replication.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/s_rights.png b/php/apps/phpmyadmin49/themes/original/img/s_rights.png new file mode 100644 index 00000000..bae32e79 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/s_rights.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/s_sortable.png b/php/apps/phpmyadmin49/themes/original/img/s_sortable.png new file mode 100644 index 00000000..c64bd358 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/s_sortable.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/s_status.png b/php/apps/phpmyadmin49/themes/original/img/s_status.png new file mode 100644 index 00000000..d53c1ebe Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/s_status.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/s_success.png b/php/apps/phpmyadmin49/themes/original/img/s_success.png new file mode 100644 index 00000000..fd2cee9d Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/s_success.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/s_sync.png b/php/apps/phpmyadmin49/themes/original/img/s_sync.png new file mode 100644 index 00000000..5ced64c5 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/s_sync.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/s_tbl.png b/php/apps/phpmyadmin49/themes/original/img/s_tbl.png new file mode 100644 index 00000000..a3782a90 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/s_tbl.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/s_theme.png b/php/apps/phpmyadmin49/themes/original/img/s_theme.png new file mode 100644 index 00000000..156cd435 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/s_theme.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/s_top.png b/php/apps/phpmyadmin49/themes/original/img/s_top.png new file mode 100644 index 00000000..dd57623d Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/s_top.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/s_unlink.png b/php/apps/phpmyadmin49/themes/original/img/s_unlink.png new file mode 100644 index 00000000..551679e9 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/s_unlink.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/s_vars.png b/php/apps/phpmyadmin49/themes/original/img/s_vars.png new file mode 100644 index 00000000..b7ff721b Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/s_vars.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/s_views.png b/php/apps/phpmyadmin49/themes/original/img/s_views.png new file mode 100644 index 00000000..568d3c3e Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/s_views.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/show.png b/php/apps/phpmyadmin49/themes/original/img/show.png new file mode 100644 index 00000000..666653f6 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/show.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/south-mini.png b/php/apps/phpmyadmin49/themes/original/img/south-mini.png new file mode 100644 index 00000000..654673b3 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/south-mini.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/spacer.png b/php/apps/phpmyadmin49/themes/original/img/spacer.png new file mode 100644 index 00000000..240ca4f8 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/spacer.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/toggle-ltr.png b/php/apps/phpmyadmin49/themes/original/img/toggle-ltr.png new file mode 100644 index 00000000..2ea417f4 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/toggle-ltr.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/toggle-rtl.png b/php/apps/phpmyadmin49/themes/original/img/toggle-rtl.png new file mode 100644 index 00000000..399b06dc Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/toggle-rtl.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/vertical_line.png b/php/apps/phpmyadmin49/themes/original/img/vertical_line.png new file mode 100644 index 00000000..a88a7c7b Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/vertical_line.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/west-mini.png b/php/apps/phpmyadmin49/themes/original/img/west-mini.png new file mode 100644 index 00000000..61ad92e0 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/west-mini.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/window-new.png b/php/apps/phpmyadmin49/themes/original/img/window-new.png new file mode 100644 index 00000000..d18722fd Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/window-new.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/zoom-minus-mini.png b/php/apps/phpmyadmin49/themes/original/img/zoom-minus-mini.png new file mode 100644 index 00000000..1b8d84dc Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/zoom-minus-mini.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/zoom-plus-mini.png b/php/apps/phpmyadmin49/themes/original/img/zoom-plus-mini.png new file mode 100644 index 00000000..466cc7bc Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/zoom-plus-mini.png differ diff --git a/php/apps/phpmyadmin49/themes/original/img/zoom-world-mini.png b/php/apps/phpmyadmin49/themes/original/img/zoom-world-mini.png new file mode 100644 index 00000000..dcc60f15 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/img/zoom-world-mini.png differ diff --git a/php/apps/phpmyadmin49/themes/original/jquery/images/ui-bg_glass_55_fbf9ee_1x400.png b/php/apps/phpmyadmin49/themes/original/jquery/images/ui-bg_glass_55_fbf9ee_1x400.png new file mode 100644 index 00000000..534c590f Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/jquery/images/ui-bg_glass_55_fbf9ee_1x400.png differ diff --git a/php/apps/phpmyadmin49/themes/original/jquery/images/ui-bg_glass_65_ffffff_1x400.png b/php/apps/phpmyadmin49/themes/original/jquery/images/ui-bg_glass_65_ffffff_1x400.png new file mode 100644 index 00000000..3e56dbdc Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/jquery/images/ui-bg_glass_65_ffffff_1x400.png differ diff --git a/php/apps/phpmyadmin49/themes/original/jquery/images/ui-bg_glass_75_dadada_1x400.png b/php/apps/phpmyadmin49/themes/original/jquery/images/ui-bg_glass_75_dadada_1x400.png new file mode 100644 index 00000000..6b8b33a1 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/jquery/images/ui-bg_glass_75_dadada_1x400.png differ diff --git a/php/apps/phpmyadmin49/themes/original/jquery/images/ui-bg_glass_75_e6e6e6_1x400.png b/php/apps/phpmyadmin49/themes/original/jquery/images/ui-bg_glass_75_e6e6e6_1x400.png new file mode 100644 index 00000000..81e2065b Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/jquery/images/ui-bg_glass_75_e6e6e6_1x400.png differ diff --git a/php/apps/phpmyadmin49/themes/original/jquery/images/ui-bg_glass_95_fef1ec_1x400.png b/php/apps/phpmyadmin49/themes/original/jquery/images/ui-bg_glass_95_fef1ec_1x400.png new file mode 100644 index 00000000..7172755b Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/jquery/images/ui-bg_glass_95_fef1ec_1x400.png differ diff --git a/php/apps/phpmyadmin49/themes/original/jquery/images/ui-bg_highlight-soft_75_cccccc_1x100.png b/php/apps/phpmyadmin49/themes/original/jquery/images/ui-bg_highlight-soft_75_cccccc_1x100.png new file mode 100644 index 00000000..ae3ccae0 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/jquery/images/ui-bg_highlight-soft_75_cccccc_1x100.png differ diff --git a/php/apps/phpmyadmin49/themes/original/jquery/images/ui-icons_222222_256x240.png b/php/apps/phpmyadmin49/themes/original/jquery/images/ui-icons_222222_256x240.png new file mode 100644 index 00000000..3201d9a1 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/jquery/images/ui-icons_222222_256x240.png differ diff --git a/php/apps/phpmyadmin49/themes/original/jquery/images/ui-icons_2e83ff_256x240.png b/php/apps/phpmyadmin49/themes/original/jquery/images/ui-icons_2e83ff_256x240.png new file mode 100644 index 00000000..1d920d7d Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/jquery/images/ui-icons_2e83ff_256x240.png differ diff --git a/php/apps/phpmyadmin49/themes/original/jquery/images/ui-icons_454545_256x240.png b/php/apps/phpmyadmin49/themes/original/jquery/images/ui-icons_454545_256x240.png new file mode 100644 index 00000000..47da8e5d Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/jquery/images/ui-icons_454545_256x240.png differ diff --git a/php/apps/phpmyadmin49/themes/original/jquery/images/ui-icons_888888_256x240.png b/php/apps/phpmyadmin49/themes/original/jquery/images/ui-icons_888888_256x240.png new file mode 100644 index 00000000..95e0c3ef Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/jquery/images/ui-icons_888888_256x240.png differ diff --git a/php/apps/phpmyadmin49/themes/original/jquery/images/ui-icons_cd0a0a_256x240.png b/php/apps/phpmyadmin49/themes/original/jquery/images/ui-icons_cd0a0a_256x240.png new file mode 100644 index 00000000..0ea8a5a2 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/original/jquery/images/ui-icons_cd0a0a_256x240.png differ diff --git a/php/apps/phpmyadmin49/themes/original/jquery/jquery-ui.css b/php/apps/phpmyadmin49/themes/original/jquery/jquery-ui.css new file mode 100644 index 00000000..1ebcbc90 --- /dev/null +++ b/php/apps/phpmyadmin49/themes/original/jquery/jquery-ui.css @@ -0,0 +1,1312 @@ +/*! jQuery UI - v1.12.1 - 2016-12-21 +* http://jqueryui.com +* Includes: draggable.css, core.css, resizable.css, selectable.css, sortable.css, accordion.css, autocomplete.css, menu.css, button.css, controlgroup.css, checkboxradio.css, datepicker.css, dialog.css, progressbar.css, selectmenu.css, slider.css, spinner.css, tabs.css, tooltip.css, theme.css +* To view and modify this theme, visit http://jqueryui.com/themeroller/?scope=&folderName=smoothness&cornerRadiusShadow=8px&offsetLeftShadow=-8px&offsetTopShadow=-8px&thicknessShadow=8px&opacityShadow=30&bgImgOpacityShadow=0&bgTextureShadow=flat&bgColorShadow=aaaaaa&opacityOverlay=30&bgImgOpacityOverlay=0&bgTextureOverlay=flat&bgColorOverlay=aaaaaa&iconColorError=cd0a0a&fcError=cd0a0a&borderColorError=cd0a0a&bgImgOpacityError=95&bgTextureError=glass&bgColorError=fef1ec&iconColorHighlight=2e83ff&fcHighlight=363636&borderColorHighlight=fcefa1&bgImgOpacityHighlight=55&bgTextureHighlight=glass&bgColorHighlight=fbf9ee&iconColorActive=454545&fcActive=212121&borderColorActive=aaaaaa&bgImgOpacityActive=65&bgTextureActive=glass&bgColorActive=ffffff&iconColorHover=454545&fcHover=212121&borderColorHover=999999&bgImgOpacityHover=75&bgTextureHover=glass&bgColorHover=dadada&iconColorDefault=888888&fcDefault=555555&borderColorDefault=d3d3d3&bgImgOpacityDefault=75&bgTextureDefault=glass&bgColorDefault=e6e6e6&iconColorContent=222222&fcContent=222222&borderColorContent=aaaaaa&bgImgOpacityContent=75&bgTextureContent=flat&bgColorContent=ffffff&iconColorHeader=222222&fcHeader=222222&borderColorHeader=aaaaaa&bgImgOpacityHeader=75&bgTextureHeader=highlight_soft&bgColorHeader=cccccc&cornerRadius=4px&fsDefault=1.1em&fwDefault=normal&ffDefault=Verdana%2CArial%2Csans-serif +* Copyright jQuery Foundation and other contributors; Licensed MIT */ + +.ui-draggable-handle { + -ms-touch-action: none; + touch-action: none; +} +/* Layout helpers +----------------------------------*/ +.ui-helper-hidden { + display: none; +} +.ui-helper-hidden-accessible { + border: 0; + clip: rect(0 0 0 0); + height: 1px; + margin: -1px; + overflow: hidden; + padding: 0; + position: absolute; + width: 1px; +} +.ui-helper-reset { + margin: 0; + padding: 0; + border: 0; + outline: 0; + line-height: 1.3; + text-decoration: none; + font-size: 100%; + list-style: none; +} +.ui-helper-clearfix:before, +.ui-helper-clearfix:after { + content: ""; + display: table; + border-collapse: collapse; +} +.ui-helper-clearfix:after { + clear: both; +} +.ui-helper-zfix { + width: 100%; + height: 100%; + top: 0; + left: 0; + position: absolute; + opacity: 0; + filter:Alpha(Opacity=0); /* support: IE8 */ +} + +.ui-front { + z-index: 100; +} + + +/* Interaction Cues +----------------------------------*/ +.ui-state-disabled { + cursor: default !important; + pointer-events: none; +} + + +/* Icons +----------------------------------*/ +.ui-icon { + display: inline-block; + vertical-align: middle; + margin-top: -.25em; + position: relative; + text-indent: -99999px; + overflow: hidden; + background-repeat: no-repeat; +} + +.ui-widget-icon-block { + left: 50%; + margin-left: -8px; + display: block; +} + +/* Misc visuals +----------------------------------*/ + +/* Overlays */ +.ui-widget-overlay { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; +} +.ui-resizable { + position: relative; +} +.ui-resizable-handle { + position: absolute; + font-size: 0.1px; + display: block; + -ms-touch-action: none; + touch-action: none; +} +.ui-resizable-disabled .ui-resizable-handle, +.ui-resizable-autohide .ui-resizable-handle { + display: none; +} +.ui-resizable-n { + cursor: n-resize; + height: 7px; + width: 100%; + top: -5px; + left: 0; +} +.ui-resizable-s { + cursor: s-resize; + height: 7px; + width: 100%; + bottom: -5px; + left: 0; +} +.ui-resizable-e { + cursor: e-resize; + width: 7px; + right: -5px; + top: 0; + height: 100%; +} +.ui-resizable-w { + cursor: w-resize; + width: 7px; + left: -5px; + top: 0; + height: 100%; +} +.ui-resizable-se { + cursor: se-resize; + width: 12px; + height: 12px; + right: 1px; + bottom: 1px; +} +.ui-resizable-sw { + cursor: sw-resize; + width: 9px; + height: 9px; + left: -5px; + bottom: -5px; +} +.ui-resizable-nw { + cursor: nw-resize; + width: 9px; + height: 9px; + left: -5px; + top: -5px; +} +.ui-resizable-ne { + cursor: ne-resize; + width: 9px; + height: 9px; + right: -5px; + top: -5px; +} +.ui-selectable { + -ms-touch-action: none; + touch-action: none; +} +.ui-selectable-helper { + position: absolute; + z-index: 100; + border: 1px dotted black; +} +.ui-sortable-handle { + -ms-touch-action: none; + touch-action: none; +} +.ui-accordion .ui-accordion-header { + display: block; + cursor: pointer; + position: relative; + margin: 2px 0 0 0; + padding: .5em .5em .5em .7em; + font-size: 100%; +} +.ui-accordion .ui-accordion-content { + padding: 1em 2.2em; + border-top: 0; + overflow: auto; +} +.ui-autocomplete { + position: absolute; + top: 0; + left: 0; + cursor: default; +} +.ui-menu { + list-style: none; + padding: 0; + margin: 0; + display: block; + outline: 0; +} +.ui-menu .ui-menu { + position: absolute; +} +.ui-menu .ui-menu-item { + margin: 0; + cursor: pointer; + /* support: IE10, see #8844 */ + list-style-image: url("data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7"); +} +.ui-menu .ui-menu-item-wrapper { + position: relative; + padding: 3px 1em 3px .4em; +} +.ui-menu .ui-menu-divider { + margin: 5px 0; + height: 0; + font-size: 0; + line-height: 0; + border-width: 1px 0 0 0; +} +.ui-menu .ui-state-focus, +.ui-menu .ui-state-active { + margin: -1px; +} + +/* icon support */ +.ui-menu-icons { + position: relative; +} +.ui-menu-icons .ui-menu-item-wrapper { + padding-left: 2em; +} + +/* left-aligned */ +.ui-menu .ui-icon { + position: absolute; + top: 0; + bottom: 0; + left: .2em; + margin: auto 0; +} + +/* right-aligned */ +.ui-menu .ui-menu-icon { + left: auto; + right: 0; +} +.ui-button { + padding: .4em 1em; + display: inline-block; + position: relative; + line-height: normal; + margin-right: .1em; + cursor: pointer; + vertical-align: middle; + text-align: center; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + + /* Support: IE <= 11 */ + overflow: visible; +} + +.ui-button, +.ui-button:link, +.ui-button:visited, +.ui-button:hover, +.ui-button:active { + text-decoration: none; +} + +/* to make room for the icon, a width needs to be set here */ +.ui-button-icon-only { + width: 2em; + box-sizing: border-box; + text-indent: -9999px; + white-space: nowrap; +} + +/* no icon support for input elements */ +input.ui-button.ui-button-icon-only { + text-indent: 0; +} + +/* button icon element(s) */ +.ui-button-icon-only .ui-icon { + position: absolute; + top: 50%; + left: 50%; + margin-top: -8px; + margin-left: -8px; +} + +.ui-button.ui-icon-notext .ui-icon { + padding: 0; + width: 2.1em; + height: 2.1em; + text-indent: -9999px; + white-space: nowrap; + +} + +input.ui-button.ui-icon-notext .ui-icon { + width: auto; + height: auto; + text-indent: 0; + white-space: normal; + padding: .4em 1em; +} + +/* workarounds */ +/* Support: Firefox 5 - 40 */ +input.ui-button::-moz-focus-inner, +button.ui-button::-moz-focus-inner { + border: 0; + padding: 0; +} +.ui-controlgroup { + vertical-align: middle; + display: inline-block; +} +.ui-controlgroup > .ui-controlgroup-item { + float: left; + margin-left: 0; + margin-right: 0; +} +.ui-controlgroup > .ui-controlgroup-item:focus, +.ui-controlgroup > .ui-controlgroup-item.ui-visual-focus { + z-index: 9999; +} +.ui-controlgroup-vertical > .ui-controlgroup-item { + display: block; + float: none; + width: 100%; + margin-top: 0; + margin-bottom: 0; + text-align: left; +} +.ui-controlgroup-vertical .ui-controlgroup-item { + box-sizing: border-box; +} +.ui-controlgroup .ui-controlgroup-label { + padding: .4em 1em; +} +.ui-controlgroup .ui-controlgroup-label span { + font-size: 80%; +} +.ui-controlgroup-horizontal .ui-controlgroup-label + .ui-controlgroup-item { + border-left: none; +} +.ui-controlgroup-vertical .ui-controlgroup-label + .ui-controlgroup-item { + border-top: none; +} +.ui-controlgroup-horizontal .ui-controlgroup-label.ui-widget-content { + border-right: none; +} +.ui-controlgroup-vertical .ui-controlgroup-label.ui-widget-content { + border-bottom: none; +} + +/* Spinner specific style fixes */ +.ui-controlgroup-vertical .ui-spinner-input { + + /* Support: IE8 only, Android < 4.4 only */ + width: 75%; + width: calc( 100% - 2.4em ); +} +.ui-controlgroup-vertical .ui-spinner .ui-spinner-up { + border-top-style: solid; +} + +.ui-checkboxradio-label .ui-icon-background { + box-shadow: inset 1px 1px 1px #ccc; + border-radius: .12em; + border: none; +} +.ui-checkboxradio-radio-label .ui-icon-background { + width: 16px; + height: 16px; + border-radius: 1em; + overflow: visible; + border: none; +} +.ui-checkboxradio-radio-label.ui-checkboxradio-checked .ui-icon, +.ui-checkboxradio-radio-label.ui-checkboxradio-checked:hover .ui-icon { + background-image: none; + width: 8px; + height: 8px; + border-width: 4px; + border-style: solid; +} +.ui-checkboxradio-disabled { + pointer-events: none; +} +.ui-datepicker { + width: 17em; + padding: .2em .2em 0; + display: none; +} +.ui-datepicker .ui-datepicker-header { + position: relative; + padding: .2em 0; +} +.ui-datepicker .ui-datepicker-prev, +.ui-datepicker .ui-datepicker-next { + position: absolute; + top: 2px; + width: 1.8em; + height: 1.8em; +} +.ui-datepicker .ui-datepicker-prev-hover, +.ui-datepicker .ui-datepicker-next-hover { + top: 1px; +} +.ui-datepicker .ui-datepicker-prev { + left: 2px; +} +.ui-datepicker .ui-datepicker-next { + right: 2px; +} +.ui-datepicker .ui-datepicker-prev-hover { + left: 1px; +} +.ui-datepicker .ui-datepicker-next-hover { + right: 1px; +} +.ui-datepicker .ui-datepicker-prev span, +.ui-datepicker .ui-datepicker-next span { + display: block; + position: absolute; + left: 50%; + margin-left: -8px; + top: 50%; + margin-top: -8px; +} +.ui-datepicker .ui-datepicker-title { + margin: 0 2.3em; + line-height: 1.8em; + text-align: center; +} +.ui-datepicker .ui-datepicker-title select { + font-size: 1em; + margin: 1px 0; +} +.ui-datepicker select.ui-datepicker-month, +.ui-datepicker select.ui-datepicker-year { + width: 45%; +} +.ui-datepicker table { + width: 100%; + font-size: .9em; + border-collapse: collapse; + margin: 0 0 .4em; +} +.ui-datepicker th { + padding: .7em .3em; + text-align: center; + font-weight: bold; + border: 0; +} +.ui-datepicker td { + border: 0; + padding: 1px; +} +.ui-datepicker td span, +.ui-datepicker td a { + display: block; + padding: .2em; + text-align: right; + text-decoration: none; +} +.ui-datepicker .ui-datepicker-buttonpane { + background-image: none; + margin: .7em 0 0 0; + padding: 0 .2em; + border-left: 0; + border-right: 0; + border-bottom: 0; +} +.ui-datepicker .ui-datepicker-buttonpane button { + float: right; + margin: .5em .2em .4em; + cursor: pointer; + padding: .2em .6em .3em .6em; + width: auto; + overflow: visible; +} +.ui-datepicker .ui-datepicker-buttonpane button.ui-datepicker-current { + float: left; +} + +/* with multiple calendars */ +.ui-datepicker.ui-datepicker-multi { + width: auto; +} +.ui-datepicker-multi .ui-datepicker-group { + float: left; +} +.ui-datepicker-multi .ui-datepicker-group table { + width: 95%; + margin: 0 auto .4em; +} +.ui-datepicker-multi-2 .ui-datepicker-group { + width: 50%; +} +.ui-datepicker-multi-3 .ui-datepicker-group { + width: 33.3%; +} +.ui-datepicker-multi-4 .ui-datepicker-group { + width: 25%; +} +.ui-datepicker-multi .ui-datepicker-group-last .ui-datepicker-header, +.ui-datepicker-multi .ui-datepicker-group-middle .ui-datepicker-header { + border-left-width: 0; +} +.ui-datepicker-multi .ui-datepicker-buttonpane { + clear: left; +} +.ui-datepicker-row-break { + clear: both; + width: 100%; + font-size: 0; +} + +/* RTL support */ +.ui-datepicker-rtl { + direction: rtl; +} +.ui-datepicker-rtl .ui-datepicker-prev { + right: 2px; + left: auto; +} +.ui-datepicker-rtl .ui-datepicker-next { + left: 2px; + right: auto; +} +.ui-datepicker-rtl .ui-datepicker-prev:hover { + right: 1px; + left: auto; +} +.ui-datepicker-rtl .ui-datepicker-next:hover { + left: 1px; + right: auto; +} +.ui-datepicker-rtl .ui-datepicker-buttonpane { + clear: right; +} +.ui-datepicker-rtl .ui-datepicker-buttonpane button { + float: left; +} +.ui-datepicker-rtl .ui-datepicker-buttonpane button.ui-datepicker-current, +.ui-datepicker-rtl .ui-datepicker-group { + float: right; +} +.ui-datepicker-rtl .ui-datepicker-group-last .ui-datepicker-header, +.ui-datepicker-rtl .ui-datepicker-group-middle .ui-datepicker-header { + border-right-width: 0; + border-left-width: 1px; +} + +/* Icons */ +.ui-datepicker .ui-icon { + display: block; + text-indent: -99999px; + overflow: hidden; + background-repeat: no-repeat; + left: .5em; + top: .3em; +} +.ui-dialog { + position: absolute; + top: 0; + left: 0; + padding: .2em; + outline: 0; +} +.ui-dialog .ui-dialog-titlebar { + padding: .4em 1em; + position: relative; +} +.ui-dialog .ui-dialog-title { + float: left; + margin: .1em 0; + white-space: nowrap; + width: 90%; + overflow: hidden; + text-overflow: ellipsis; +} +.ui-dialog .ui-dialog-titlebar-close { + position: absolute; + right: .3em; + top: 50%; + width: 20px; + margin: -10px 0 0 0; + padding: 1px; + height: 20px; +} +.ui-dialog .ui-dialog-content { + position: relative; + border: 0; + padding: .5em 1em; + background: none; + overflow: auto; +} +.ui-dialog .ui-dialog-buttonpane { + text-align: left; + border-width: 1px 0 0 0; + background-image: none; + margin-top: .5em; + padding: .3em 1em .5em .4em; +} +.ui-dialog .ui-dialog-buttonpane .ui-dialog-buttonset { + float: right; +} +.ui-dialog .ui-dialog-buttonpane button { + margin: .5em .4em .5em 0; + cursor: pointer; +} +.ui-dialog .ui-resizable-n { + height: 2px; + top: 0; +} +.ui-dialog .ui-resizable-e { + width: 2px; + right: 0; +} +.ui-dialog .ui-resizable-s { + height: 2px; + bottom: 0; +} +.ui-dialog .ui-resizable-w { + width: 2px; + left: 0; +} +.ui-dialog .ui-resizable-se, +.ui-dialog .ui-resizable-sw, +.ui-dialog .ui-resizable-ne, +.ui-dialog .ui-resizable-nw { + width: 7px; + height: 7px; +} +.ui-dialog .ui-resizable-se { + right: 0; + bottom: 0; +} +.ui-dialog .ui-resizable-sw { + left: 0; + bottom: 0; +} +.ui-dialog .ui-resizable-ne { + right: 0; + top: 0; +} +.ui-dialog .ui-resizable-nw { + left: 0; + top: 0; +} +.ui-draggable .ui-dialog-titlebar { + cursor: move; +} +.ui-progressbar { + height: 2em; + text-align: left; + overflow: hidden; +} +.ui-progressbar .ui-progressbar-value { + margin: -1px; + height: 100%; +} +.ui-progressbar .ui-progressbar-overlay { + background: url("data:image/gif;base64,R0lGODlhKAAoAIABAAAAAP///yH/C05FVFNDQVBFMi4wAwEAAAAh+QQJAQABACwAAAAAKAAoAAACkYwNqXrdC52DS06a7MFZI+4FHBCKoDeWKXqymPqGqxvJrXZbMx7Ttc+w9XgU2FB3lOyQRWET2IFGiU9m1frDVpxZZc6bfHwv4c1YXP6k1Vdy292Fb6UkuvFtXpvWSzA+HycXJHUXiGYIiMg2R6W459gnWGfHNdjIqDWVqemH2ekpObkpOlppWUqZiqr6edqqWQAAIfkECQEAAQAsAAAAACgAKAAAApSMgZnGfaqcg1E2uuzDmmHUBR8Qil95hiPKqWn3aqtLsS18y7G1SzNeowWBENtQd+T1JktP05nzPTdJZlR6vUxNWWjV+vUWhWNkWFwxl9VpZRedYcflIOLafaa28XdsH/ynlcc1uPVDZxQIR0K25+cICCmoqCe5mGhZOfeYSUh5yJcJyrkZWWpaR8doJ2o4NYq62lAAACH5BAkBAAEALAAAAAAoACgAAAKVDI4Yy22ZnINRNqosw0Bv7i1gyHUkFj7oSaWlu3ovC8GxNso5fluz3qLVhBVeT/Lz7ZTHyxL5dDalQWPVOsQWtRnuwXaFTj9jVVh8pma9JjZ4zYSj5ZOyma7uuolffh+IR5aW97cHuBUXKGKXlKjn+DiHWMcYJah4N0lYCMlJOXipGRr5qdgoSTrqWSq6WFl2ypoaUAAAIfkECQEAAQAsAAAAACgAKAAAApaEb6HLgd/iO7FNWtcFWe+ufODGjRfoiJ2akShbueb0wtI50zm02pbvwfWEMWBQ1zKGlLIhskiEPm9R6vRXxV4ZzWT2yHOGpWMyorblKlNp8HmHEb/lCXjcW7bmtXP8Xt229OVWR1fod2eWqNfHuMjXCPkIGNileOiImVmCOEmoSfn3yXlJWmoHGhqp6ilYuWYpmTqKUgAAIfkECQEAAQAsAAAAACgAKAAAApiEH6kb58biQ3FNWtMFWW3eNVcojuFGfqnZqSebuS06w5V80/X02pKe8zFwP6EFWOT1lDFk8rGERh1TTNOocQ61Hm4Xm2VexUHpzjymViHrFbiELsefVrn6XKfnt2Q9G/+Xdie499XHd2g4h7ioOGhXGJboGAnXSBnoBwKYyfioubZJ2Hn0RuRZaflZOil56Zp6iioKSXpUAAAh+QQJAQABACwAAAAAKAAoAAACkoQRqRvnxuI7kU1a1UU5bd5tnSeOZXhmn5lWK3qNTWvRdQxP8qvaC+/yaYQzXO7BMvaUEmJRd3TsiMAgswmNYrSgZdYrTX6tSHGZO73ezuAw2uxuQ+BbeZfMxsexY35+/Qe4J1inV0g4x3WHuMhIl2jXOKT2Q+VU5fgoSUI52VfZyfkJGkha6jmY+aaYdirq+lQAACH5BAkBAAEALAAAAAAoACgAAAKWBIKpYe0L3YNKToqswUlvznigd4wiR4KhZrKt9Upqip61i9E3vMvxRdHlbEFiEXfk9YARYxOZZD6VQ2pUunBmtRXo1Lf8hMVVcNl8JafV38aM2/Fu5V16Bn63r6xt97j09+MXSFi4BniGFae3hzbH9+hYBzkpuUh5aZmHuanZOZgIuvbGiNeomCnaxxap2upaCZsq+1kAACH5BAkBAAEALAAAAAAoACgAAAKXjI8By5zf4kOxTVrXNVlv1X0d8IGZGKLnNpYtm8Lr9cqVeuOSvfOW79D9aDHizNhDJidFZhNydEahOaDH6nomtJjp1tutKoNWkvA6JqfRVLHU/QUfau9l2x7G54d1fl995xcIGAdXqMfBNadoYrhH+Mg2KBlpVpbluCiXmMnZ2Sh4GBqJ+ckIOqqJ6LmKSllZmsoq6wpQAAAh+QQJAQABACwAAAAAKAAoAAAClYx/oLvoxuJDkU1a1YUZbJ59nSd2ZXhWqbRa2/gF8Gu2DY3iqs7yrq+xBYEkYvFSM8aSSObE+ZgRl1BHFZNr7pRCavZ5BW2142hY3AN/zWtsmf12p9XxxFl2lpLn1rseztfXZjdIWIf2s5dItwjYKBgo9yg5pHgzJXTEeGlZuenpyPmpGQoKOWkYmSpaSnqKileI2FAAACH5BAkBAAEALAAAAAAoACgAAAKVjB+gu+jG4kORTVrVhRlsnn2dJ3ZleFaptFrb+CXmO9OozeL5VfP99HvAWhpiUdcwkpBH3825AwYdU8xTqlLGhtCosArKMpvfa1mMRae9VvWZfeB2XfPkeLmm18lUcBj+p5dnN8jXZ3YIGEhYuOUn45aoCDkp16hl5IjYJvjWKcnoGQpqyPlpOhr3aElaqrq56Bq7VAAAOw=="); + height: 100%; + filter: alpha(opacity=25); /* support: IE8 */ + opacity: 0.25; +} +.ui-progressbar-indeterminate .ui-progressbar-value { + background-image: none; +} +.ui-selectmenu-menu { + padding: 0; + margin: 0; + position: absolute; + top: 0; + left: 0; + display: none; +} +.ui-selectmenu-menu .ui-menu { + overflow: auto; + overflow-x: hidden; + padding-bottom: 1px; +} +.ui-selectmenu-menu .ui-menu .ui-selectmenu-optgroup { + font-size: 1em; + font-weight: bold; + line-height: 1.5; + padding: 2px 0.4em; + margin: 0.5em 0 0 0; + height: auto; + border: 0; +} +.ui-selectmenu-open { + display: block; +} +.ui-selectmenu-text { + display: block; + margin-right: 20px; + overflow: hidden; + text-overflow: ellipsis; +} +.ui-selectmenu-button.ui-button { + text-align: left; + white-space: nowrap; + width: 14em; +} +.ui-selectmenu-icon.ui-icon { + float: right; + margin-top: 0; +} +.ui-slider { + position: relative; + text-align: left; +} +.ui-slider .ui-slider-handle { + position: absolute; + z-index: 2; + width: 1.2em; + height: 1.2em; + cursor: default; + -ms-touch-action: none; + touch-action: none; +} +.ui-slider .ui-slider-range { + position: absolute; + z-index: 1; + font-size: .7em; + display: block; + border: 0; + background-position: 0 0; +} + +/* support: IE8 - See #6727 */ +.ui-slider.ui-state-disabled .ui-slider-handle, +.ui-slider.ui-state-disabled .ui-slider-range { + filter: inherit; +} + +.ui-slider-horizontal { + height: .8em; +} +.ui-slider-horizontal .ui-slider-handle { + top: -.3em; + margin-left: -.6em; +} +.ui-slider-horizontal .ui-slider-range { + top: 0; + height: 100%; +} +.ui-slider-horizontal .ui-slider-range-min { + left: 0; +} +.ui-slider-horizontal .ui-slider-range-max { + right: 0; +} + +.ui-slider-vertical { + width: .8em; + height: 100px; +} +.ui-slider-vertical .ui-slider-handle { + left: -.3em; + margin-left: 0; + margin-bottom: -.6em; +} +.ui-slider-vertical .ui-slider-range { + left: 0; + width: 100%; +} +.ui-slider-vertical .ui-slider-range-min { + bottom: 0; +} +.ui-slider-vertical .ui-slider-range-max { + top: 0; +} +.ui-spinner { + position: relative; + display: inline-block; + overflow: hidden; + padding: 0; + vertical-align: middle; +} +.ui-spinner-input { + border: none; + background: none; + color: inherit; + padding: .222em 0; + margin: .2em 0; + vertical-align: middle; + margin-left: .4em; + margin-right: 2em; +} +.ui-spinner-button { + width: 1.6em; + height: 50%; + font-size: .5em; + padding: 0; + margin: 0; + text-align: center; + position: absolute; + cursor: default; + display: block; + overflow: hidden; + right: 0; +} +/* more specificity required here to override default borders */ +.ui-spinner a.ui-spinner-button { + border-top-style: none; + border-bottom-style: none; + border-right-style: none; +} +.ui-spinner-up { + top: 0; +} +.ui-spinner-down { + bottom: 0; +} +.ui-tabs { + position: relative;/* position: relative prevents IE scroll bug (element with position: relative inside container with overflow: auto appear as "fixed") */ + padding: .2em; +} +.ui-tabs .ui-tabs-nav { + margin: 0; + padding: .2em .2em 0; +} +.ui-tabs .ui-tabs-nav li { + list-style: none; + float: left; + position: relative; + top: 0; + margin: 1px .2em 0 0; + border-bottom-width: 0; + padding: 0; + white-space: nowrap; +} +.ui-tabs .ui-tabs-nav .ui-tabs-anchor { + float: left; + padding: .5em 1em; + text-decoration: none; +} +.ui-tabs .ui-tabs-nav li.ui-tabs-active { + margin-bottom: -1px; + padding-bottom: 1px; +} +.ui-tabs .ui-tabs-nav li.ui-tabs-active .ui-tabs-anchor, +.ui-tabs .ui-tabs-nav li.ui-state-disabled .ui-tabs-anchor, +.ui-tabs .ui-tabs-nav li.ui-tabs-loading .ui-tabs-anchor { + cursor: text; +} +.ui-tabs-collapsible .ui-tabs-nav li.ui-tabs-active .ui-tabs-anchor { + cursor: pointer; +} +.ui-tabs .ui-tabs-panel { + display: block; + border-width: 0; + padding: 1em 1.4em; + background: none; +} +.ui-tooltip { + padding: 8px; + position: absolute; + z-index: 9999; + max-width: 300px; +} +body .ui-tooltip { + border-width: 2px; +} + +/* Component containers +----------------------------------*/ +.ui-widget { + font-family: Verdana,Arial,sans-serif; + font-size: 1.1em; +} +.ui-widget .ui-widget { + font-size: 1em; +} +.ui-widget input, +.ui-widget select, +.ui-widget textarea, +.ui-widget button { + font-family: Verdana,Arial,sans-serif; + font-size: 1em; +} +.ui-widget.ui-widget-content { + border: 1px solid #d3d3d3; +} +.ui-widget-content { + border: 1px solid #aaaaaa; + background: #ffffff; + color: #222222; +} +.ui-widget-content a { + color: #222222; +} +.ui-widget-header { + border: 1px solid #aaaaaa; + background: #cccccc url("images/ui-bg_highlight-soft_75_cccccc_1x100.png") 50% 50% repeat-x; + color: #222222; + font-weight: bold; +} +.ui-widget-header a { + color: #222222; +} + +/* Interaction states +----------------------------------*/ +.ui-state-default, +.ui-widget-content .ui-state-default, +.ui-widget-header .ui-state-default, +.ui-button, + +/* We use html here because we need a greater specificity to make sure disabled +works properly when clicked or hovered */ +html .ui-button.ui-state-disabled:hover, +html .ui-button.ui-state-disabled:active { + border: 1px solid #d3d3d3; + background: #e6e6e6 url("images/ui-bg_glass_75_e6e6e6_1x400.png") 50% 50% repeat-x; + font-weight: normal; + color: #555555; +} +.ui-state-default a, +.ui-state-default a:link, +.ui-state-default a:visited, +a.ui-button, +a:link.ui-button, +a:visited.ui-button, +.ui-button { + color: #555555; + text-decoration: none; +} +.ui-state-hover, +.ui-widget-content .ui-state-hover, +.ui-widget-header .ui-state-hover, +.ui-state-focus, +.ui-widget-content .ui-state-focus, +.ui-widget-header .ui-state-focus, +.ui-button:hover, +.ui-button:focus { + border: 1px solid #999999; + background: #dadada url("images/ui-bg_glass_75_dadada_1x400.png") 50% 50% repeat-x; + font-weight: normal; + color: #212121; +} +.ui-state-hover a, +.ui-state-hover a:hover, +.ui-state-hover a:link, +.ui-state-hover a:visited, +.ui-state-focus a, +.ui-state-focus a:hover, +.ui-state-focus a:link, +.ui-state-focus a:visited, +a.ui-button:hover, +a.ui-button:focus { + color: #212121; + text-decoration: none; +} + +.ui-visual-focus { + box-shadow: 0 0 3px 1px rgb(94, 158, 214); +} +.ui-state-active, +.ui-widget-content .ui-state-active, +.ui-widget-header .ui-state-active, +a.ui-button:active, +.ui-button:active, +.ui-button.ui-state-active:hover { + border: 1px solid #aaaaaa; + background: #ffffff url("images/ui-bg_glass_65_ffffff_1x400.png") 50% 50% repeat-x; + font-weight: normal; + color: #212121; +} +.ui-icon-background, +.ui-state-active .ui-icon-background { + border: #aaaaaa; + background-color: #212121; +} +.ui-state-active a, +.ui-state-active a:link, +.ui-state-active a:visited { + color: #212121; + text-decoration: none; +} + +/* Interaction Cues +----------------------------------*/ +.ui-state-highlight, +.ui-widget-content .ui-state-highlight, +.ui-widget-header .ui-state-highlight { + border: 1px solid #fcefa1; + background: #fbf9ee url("images/ui-bg_glass_55_fbf9ee_1x400.png") 50% 50% repeat-x; + color: #363636; +} +.ui-state-checked { + border: 1px solid #fcefa1; + background: #fbf9ee; +} +.ui-state-highlight a, +.ui-widget-content .ui-state-highlight a, +.ui-widget-header .ui-state-highlight a { + color: #363636; +} +.ui-state-error, +.ui-widget-content .ui-state-error, +.ui-widget-header .ui-state-error { + border: 1px solid #cd0a0a; + background: #fef1ec url("images/ui-bg_glass_95_fef1ec_1x400.png") 50% 50% repeat-x; + color: #cd0a0a; +} +.ui-state-error a, +.ui-widget-content .ui-state-error a, +.ui-widget-header .ui-state-error a { + color: #cd0a0a; +} +.ui-state-error-text, +.ui-widget-content .ui-state-error-text, +.ui-widget-header .ui-state-error-text { + color: #cd0a0a; +} +.ui-priority-primary, +.ui-widget-content .ui-priority-primary, +.ui-widget-header .ui-priority-primary { + font-weight: bold; +} +.ui-priority-secondary, +.ui-widget-content .ui-priority-secondary, +.ui-widget-header .ui-priority-secondary { + opacity: .7; + filter:Alpha(Opacity=70); /* support: IE8 */ + font-weight: normal; +} +.ui-state-disabled, +.ui-widget-content .ui-state-disabled, +.ui-widget-header .ui-state-disabled { + opacity: .35; + filter:Alpha(Opacity=35); /* support: IE8 */ + background-image: none; +} +.ui-state-disabled .ui-icon { + filter:Alpha(Opacity=35); /* support: IE8 - See #6059 */ +} + +/* Icons +----------------------------------*/ + +/* states and images */ +.ui-icon { + width: 16px; + height: 16px; +} +.ui-icon, +.ui-widget-content .ui-icon { + background-image: url("images/ui-icons_222222_256x240.png"); +} +.ui-widget-header .ui-icon { + background-image: url("images/ui-icons_222222_256x240.png"); +} +.ui-state-hover .ui-icon, +.ui-state-focus .ui-icon, +.ui-button:hover .ui-icon, +.ui-button:focus .ui-icon { + background-image: url("images/ui-icons_454545_256x240.png"); +} +.ui-state-active .ui-icon, +.ui-button:active .ui-icon { + background-image: url("images/ui-icons_454545_256x240.png"); +} +.ui-state-highlight .ui-icon, +.ui-button .ui-state-highlight.ui-icon { + background-image: url("images/ui-icons_2e83ff_256x240.png"); +} +.ui-state-error .ui-icon, +.ui-state-error-text .ui-icon { + background-image: url("images/ui-icons_cd0a0a_256x240.png"); +} +.ui-button .ui-icon { + background-image: url("images/ui-icons_888888_256x240.png"); +} + +/* positioning */ +.ui-icon-blank { background-position: 16px 16px; } +.ui-icon-caret-1-n { background-position: 0 0; } +.ui-icon-caret-1-ne { background-position: -16px 0; } +.ui-icon-caret-1-e { background-position: -32px 0; } +.ui-icon-caret-1-se { background-position: -48px 0; } +.ui-icon-caret-1-s { background-position: -65px 0; } +.ui-icon-caret-1-sw { background-position: -80px 0; } +.ui-icon-caret-1-w { background-position: -96px 0; } +.ui-icon-caret-1-nw { background-position: -112px 0; } +.ui-icon-caret-2-n-s { background-position: -128px 0; } +.ui-icon-caret-2-e-w { background-position: -144px 0; } +.ui-icon-triangle-1-n { background-position: 0 -16px; } +.ui-icon-triangle-1-ne { background-position: -16px -16px; } +.ui-icon-triangle-1-e { background-position: -32px -16px; } +.ui-icon-triangle-1-se { background-position: -48px -16px; } +.ui-icon-triangle-1-s { background-position: -65px -16px; } +.ui-icon-triangle-1-sw { background-position: -80px -16px; } +.ui-icon-triangle-1-w { background-position: -96px -16px; } +.ui-icon-triangle-1-nw { background-position: -112px -16px; } +.ui-icon-triangle-2-n-s { background-position: -128px -16px; } +.ui-icon-triangle-2-e-w { background-position: -144px -16px; } +.ui-icon-arrow-1-n { background-position: 0 -32px; } +.ui-icon-arrow-1-ne { background-position: -16px -32px; } +.ui-icon-arrow-1-e { background-position: -32px -32px; } +.ui-icon-arrow-1-se { background-position: -48px -32px; } +.ui-icon-arrow-1-s { background-position: -65px -32px; } +.ui-icon-arrow-1-sw { background-position: -80px -32px; } +.ui-icon-arrow-1-w { background-position: -96px -32px; } +.ui-icon-arrow-1-nw { background-position: -112px -32px; } +.ui-icon-arrow-2-n-s { background-position: -128px -32px; } +.ui-icon-arrow-2-ne-sw { background-position: -144px -32px; } +.ui-icon-arrow-2-e-w { background-position: -160px -32px; } +.ui-icon-arrow-2-se-nw { background-position: -176px -32px; } +.ui-icon-arrowstop-1-n { background-position: -192px -32px; } +.ui-icon-arrowstop-1-e { background-position: -208px -32px; } +.ui-icon-arrowstop-1-s { background-position: -224px -32px; } +.ui-icon-arrowstop-1-w { background-position: -240px -32px; } +.ui-icon-arrowthick-1-n { background-position: 1px -48px; } +.ui-icon-arrowthick-1-ne { background-position: -16px -48px; } +.ui-icon-arrowthick-1-e { background-position: -32px -48px; } +.ui-icon-arrowthick-1-se { background-position: -48px -48px; } +.ui-icon-arrowthick-1-s { background-position: -64px -48px; } +.ui-icon-arrowthick-1-sw { background-position: -80px -48px; } +.ui-icon-arrowthick-1-w { background-position: -96px -48px; } +.ui-icon-arrowthick-1-nw { background-position: -112px -48px; } +.ui-icon-arrowthick-2-n-s { background-position: -128px -48px; } +.ui-icon-arrowthick-2-ne-sw { background-position: -144px -48px; } +.ui-icon-arrowthick-2-e-w { background-position: -160px -48px; } +.ui-icon-arrowthick-2-se-nw { background-position: -176px -48px; } +.ui-icon-arrowthickstop-1-n { background-position: -192px -48px; } +.ui-icon-arrowthickstop-1-e { background-position: -208px -48px; } +.ui-icon-arrowthickstop-1-s { background-position: -224px -48px; } +.ui-icon-arrowthickstop-1-w { background-position: -240px -48px; } +.ui-icon-arrowreturnthick-1-w { background-position: 0 -64px; } +.ui-icon-arrowreturnthick-1-n { background-position: -16px -64px; } +.ui-icon-arrowreturnthick-1-e { background-position: -32px -64px; } +.ui-icon-arrowreturnthick-1-s { background-position: -48px -64px; } +.ui-icon-arrowreturn-1-w { background-position: -64px -64px; } +.ui-icon-arrowreturn-1-n { background-position: -80px -64px; } +.ui-icon-arrowreturn-1-e { background-position: -96px -64px; } +.ui-icon-arrowreturn-1-s { background-position: -112px -64px; } +.ui-icon-arrowrefresh-1-w { background-position: -128px -64px; } +.ui-icon-arrowrefresh-1-n { background-position: -144px -64px; } +.ui-icon-arrowrefresh-1-e { background-position: -160px -64px; } +.ui-icon-arrowrefresh-1-s { background-position: -176px -64px; } +.ui-icon-arrow-4 { background-position: 0 -80px; } +.ui-icon-arrow-4-diag { background-position: -16px -80px; } +.ui-icon-extlink { background-position: -32px -80px; } +.ui-icon-newwin { background-position: -48px -80px; } +.ui-icon-refresh { background-position: -64px -80px; } +.ui-icon-shuffle { background-position: -80px -80px; } +.ui-icon-transfer-e-w { background-position: -96px -80px; } +.ui-icon-transferthick-e-w { background-position: -112px -80px; } +.ui-icon-folder-collapsed { background-position: 0 -96px; } +.ui-icon-folder-open { background-position: -16px -96px; } +.ui-icon-document { background-position: -32px -96px; } +.ui-icon-document-b { background-position: -48px -96px; } +.ui-icon-note { background-position: -64px -96px; } +.ui-icon-mail-closed { background-position: -80px -96px; } +.ui-icon-mail-open { background-position: -96px -96px; } +.ui-icon-suitcase { background-position: -112px -96px; } +.ui-icon-comment { background-position: -128px -96px; } +.ui-icon-person { background-position: -144px -96px; } +.ui-icon-print { background-position: -160px -96px; } +.ui-icon-trash { background-position: -176px -96px; } +.ui-icon-locked { background-position: -192px -96px; } +.ui-icon-unlocked { background-position: -208px -96px; } +.ui-icon-bookmark { background-position: -224px -96px; } +.ui-icon-tag { background-position: -240px -96px; } +.ui-icon-home { background-position: 0 -112px; } +.ui-icon-flag { background-position: -16px -112px; } +.ui-icon-calendar { background-position: -32px -112px; } +.ui-icon-cart { background-position: -48px -112px; } +.ui-icon-pencil { background-position: -64px -112px; } +.ui-icon-clock { background-position: -80px -112px; } +.ui-icon-disk { background-position: -96px -112px; } +.ui-icon-calculator { background-position: -112px -112px; } +.ui-icon-zoomin { background-position: -128px -112px; } +.ui-icon-zoomout { background-position: -144px -112px; } +.ui-icon-search { background-position: -160px -112px; } +.ui-icon-wrench { background-position: -176px -112px; } +.ui-icon-gear { background-position: -192px -112px; } +.ui-icon-heart { background-position: -208px -112px; } +.ui-icon-star { background-position: -224px -112px; } +.ui-icon-link { background-position: -240px -112px; } +.ui-icon-cancel { background-position: 0 -128px; } +.ui-icon-plus { background-position: -16px -128px; } +.ui-icon-plusthick { background-position: -32px -128px; } +.ui-icon-minus { background-position: -48px -128px; } +.ui-icon-minusthick { background-position: -64px -128px; } +.ui-icon-close { background-position: -80px -128px; } +.ui-icon-closethick { background-position: -96px -128px; } +.ui-icon-key { background-position: -112px -128px; } +.ui-icon-lightbulb { background-position: -128px -128px; } +.ui-icon-scissors { background-position: -144px -128px; } +.ui-icon-clipboard { background-position: -160px -128px; } +.ui-icon-copy { background-position: -176px -128px; } +.ui-icon-contact { background-position: -192px -128px; } +.ui-icon-image { background-position: -208px -128px; } +.ui-icon-video { background-position: -224px -128px; } +.ui-icon-script { background-position: -240px -128px; } +.ui-icon-alert { background-position: 0 -144px; } +.ui-icon-info { background-position: -16px -144px; } +.ui-icon-notice { background-position: -32px -144px; } +.ui-icon-help { background-position: -48px -144px; } +.ui-icon-check { background-position: -64px -144px; } +.ui-icon-bullet { background-position: -80px -144px; } +.ui-icon-radio-on { background-position: -96px -144px; } +.ui-icon-radio-off { background-position: -112px -144px; } +.ui-icon-pin-w { background-position: -128px -144px; } +.ui-icon-pin-s { background-position: -144px -144px; } +.ui-icon-play { background-position: 0 -160px; } +.ui-icon-pause { background-position: -16px -160px; } +.ui-icon-seek-next { background-position: -32px -160px; } +.ui-icon-seek-prev { background-position: -48px -160px; } +.ui-icon-seek-end { background-position: -64px -160px; } +.ui-icon-seek-start { background-position: -80px -160px; } +/* ui-icon-seek-first is deprecated, use ui-icon-seek-start instead */ +.ui-icon-seek-first { background-position: -80px -160px; } +.ui-icon-stop { background-position: -96px -160px; } +.ui-icon-eject { background-position: -112px -160px; } +.ui-icon-volume-off { background-position: -128px -160px; } +.ui-icon-volume-on { background-position: -144px -160px; } +.ui-icon-power { background-position: 0 -176px; } +.ui-icon-signal-diag { background-position: -16px -176px; } +.ui-icon-signal { background-position: -32px -176px; } +.ui-icon-battery-0 { background-position: -48px -176px; } +.ui-icon-battery-1 { background-position: -64px -176px; } +.ui-icon-battery-2 { background-position: -80px -176px; } +.ui-icon-battery-3 { background-position: -96px -176px; } +.ui-icon-circle-plus { background-position: 0 -192px; } +.ui-icon-circle-minus { background-position: -16px -192px; } +.ui-icon-circle-close { background-position: -32px -192px; } +.ui-icon-circle-triangle-e { background-position: -48px -192px; } +.ui-icon-circle-triangle-s { background-position: -64px -192px; } +.ui-icon-circle-triangle-w { background-position: -80px -192px; } +.ui-icon-circle-triangle-n { background-position: -96px -192px; } +.ui-icon-circle-arrow-e { background-position: -112px -192px; } +.ui-icon-circle-arrow-s { background-position: -128px -192px; } +.ui-icon-circle-arrow-w { background-position: -144px -192px; } +.ui-icon-circle-arrow-n { background-position: -160px -192px; } +.ui-icon-circle-zoomin { background-position: -176px -192px; } +.ui-icon-circle-zoomout { background-position: -192px -192px; } +.ui-icon-circle-check { background-position: -208px -192px; } +.ui-icon-circlesmall-plus { background-position: 0 -208px; } +.ui-icon-circlesmall-minus { background-position: -16px -208px; } +.ui-icon-circlesmall-close { background-position: -32px -208px; } +.ui-icon-squaresmall-plus { background-position: -48px -208px; } +.ui-icon-squaresmall-minus { background-position: -64px -208px; } +.ui-icon-squaresmall-close { background-position: -80px -208px; } +.ui-icon-grip-dotted-vertical { background-position: 0 -224px; } +.ui-icon-grip-dotted-horizontal { background-position: -16px -224px; } +.ui-icon-grip-solid-vertical { background-position: -32px -224px; } +.ui-icon-grip-solid-horizontal { background-position: -48px -224px; } +.ui-icon-gripsmall-diagonal-se { background-position: -64px -224px; } +.ui-icon-grip-diagonal-se { background-position: -80px -224px; } + + +/* Misc visuals +----------------------------------*/ + +/* Corner radius */ +.ui-corner-all, +.ui-corner-top, +.ui-corner-left, +.ui-corner-tl { + border-top-left-radius: 4px; +} +.ui-corner-all, +.ui-corner-top, +.ui-corner-right, +.ui-corner-tr { + border-top-right-radius: 4px; +} +.ui-corner-all, +.ui-corner-bottom, +.ui-corner-left, +.ui-corner-bl { + border-bottom-left-radius: 4px; +} +.ui-corner-all, +.ui-corner-bottom, +.ui-corner-right, +.ui-corner-br { + border-bottom-right-radius: 4px; +} + +/* Overlays */ +.ui-widget-overlay { + background: #aaaaaa; + opacity: .3; + filter: Alpha(Opacity=30); /* support: IE8 */ +} +.ui-widget-shadow { + -webkit-box-shadow: -8px -8px 8px #aaaaaa; + box-shadow: -8px -8px 8px #aaaaaa; +} diff --git a/php/apps/phpmyadmin49/themes/original/layout.inc.php b/php/apps/phpmyadmin49/themes/original/layout.inc.php new file mode 100644 index 00000000..cfa1dc0f --- /dev/null +++ b/php/apps/phpmyadmin49/themes/original/layout.inc.php @@ -0,0 +1,76 @@ + + +.CodeMirror { + height: em; + direction: ltr; +} +#inline_editor_outer .CodeMirror { + height: em; +} +.insertRowTable .CodeMirror { + height: em; + width: em; + border: 1px solid #a9a9a9; +} +#pma_console .CodeMirror-gutters { + background-color: initial; + border: none; +} +span.cm-keyword, span.cm-statement-verb { + color: #909; +} +span.cm-variable { + color: black; +} +span.cm-comment { + color: #808000; +} +span.cm-mysql-string { + color: #008000; +} +span.cm-operator { + color: fuchsia; +} +span.cm-mysql-word { + color: black; +} +span.cm-builtin { + color: #f00; +} +span.cm-variable-2 { + color: #f90; +} +span.cm-variable-3 { + color: #00f; +} +span.cm-separator { + color: fuchsia; +} +span.cm-number { + color: teal; +} +.autocomplete-column-name { + display: inline-block; +} +.autocomplete-column-hint { + display: inline-block; + float: right; + color: #666; + margin-left: 1em; +} +.CodeMirror-hints { + z-index: 999; +} +.CodeMirror-lint-tooltip { + z-index: 200; + font-family: inherit; +} +.CodeMirror-lint-tooltip code { + font-family: monospace; + font-weight: bold; +} diff --git a/php/apps/phpmyadmin49/themes/pmahomme/css/common.css.php b/php/apps/phpmyadmin49/themes/pmahomme/css/common.css.php new file mode 100644 index 00000000..140d627e --- /dev/null +++ b/php/apps/phpmyadmin49/themes/pmahomme/css/common.css.php @@ -0,0 +1,3693 @@ + +/******************************************************************************/ +/* general tags */ +html { + font-size: getFontSize(); ?> +} + +input, +select, +textarea { + font-size: 1em; +} + + +body { + + font-family: ; + + padding: 0; + margin: 0; + margin-: 240px; + color: ; + background: ; +} + +body#loginform { + margin: 0; +} + +#page_content { + margin: 0 .5em; +} + +.desktop50 { + width: 50%; +} + +.all100 { + width: 100%; +} + +.all85{ + width: 85%; +} + +.auth_config_tbl{ + margin: 0 auto; +} + + + textarea, + tt, + pre, + code { + font-family: ; + } + + + +h1 { + font-size: 140%; + font-weight: bold; +} + +h2 { + font-size: 2em; + font-weight: normal; + text-shadow: 0 1px 0 #fff; + padding: 10px 0 10px; + padding-: 3px; + color: #777; +} + +/* Hiding icons in the page titles */ +h2 img { + display: none; +} + +h2 a img { + display: inline; +} + +.data, +.data_full_width { + margin: 0 0 12px; +} + +.data_full_width { + width: 100%; +} + +h3 { + font-weight: bold; +} + +a, +a:link, +a:visited, +a:active, +button.mult_submit, +.checkall_box+label { + text-decoration: none; + color: #235a81; + cursor: pointer; + outline: none; + +} + +a:hover, +button.mult_submit:hover, +button.mult_submit:focus, +.checkall_box+label:hover { + text-decoration: underline; + color: #235a81; +} + +#initials_table { + background: #f3f3f3; + border: 1px solid #aaa; + margin-bottom: 10px; + -moz-border-radius: 5px; + -webkit-border-radius: 5px; + border-radius: 5px; +} + +#initials_table td { + padding: 8px !important; +} + +#initials_table a { + border: 1px solid #aaa; + background: #fff; + padding: 4px 8px; + -moz-border-radius: 5px; + -webkit-border-radius: 5px; + border-radius: 5px; + getCssGradient('ffffff', 'e0e0e0'); ?> +} + +#initials_table a.active { + border: 1px solid #666; + box-shadow: 0 0 2px #999; + getCssGradient('bbbbbb', 'ffffff'); ?> +} + +dfn { + font-style: normal; +} + +dfn:hover { + font-style: normal; + cursor: help; +} + +th { + font-weight: bold; + color: ; + background: #f3f3f3; + getCssGradient('ffffff', 'cccccc'); ?> +} + +a img { + border: 0; +} + +hr { + color: ; + background-color: ; + border: 0; + height: 1px; +} + +form { + padding: 0; + margin: 0; + display: inline; +} + + +input, +select { + /* Fix outline in Chrome: */ + outline: none; +} + +input[type=text], +input[type=password], +input[type=number], +input[type=date] { + border-radius: 2px; + -moz-border-radius: 2px; + -webkit-border-radius: 2px; + + + background: white; + border: 1px solid #aaa; + color: #555; + padding: 4px; +} + +input[type=text], +input[type=password], +input[type=number], +input[type=date], +input[type=checkbox], +select { + margin: 6px; +} + +input[type=number] { + width: 50px; +} + +input#auto_increment_opt { + width: min-content; +} + +input[type=text], +input[type=password], +input[type=number], +input[type=date], +select { + transition: all 0.2s; + -ms-transition: all 0.2s; + -webkit-transition: all 0.2s; + -moz-transition: all 0.2s; +} + +input[type=text][disabled], +input[type=text][disabled]:hover, +input[type=password][disabled], +input[type=password][disabled]:hover, +input[type=number][disabled], +input[type=number][disabled]:hover, +input[type=date][disabled], +input[type=date][disabled]:hover, +select[disabled], +select[disabled]:hover { + background: #e8e8e8; + box-shadow: none; + -webkit-box-shadow: none; + -moz-box-shadow: none; +} + +input[type=text]:hover, +input[type=text]:focus, +input[type=password]:hover, +input[type=password]:focus, +input[type=number]:hover, +input[type=number]:focus, +input[type=date]:hover, +input[type=date]:focus, +select:focus { + border: 1px solid #7c7c7c; + background: #fff; +} + +input[type=text]:hover, +input[type=password]:hover, +input[type=number]:hover, +input[type=date]:hover { + box-shadow: 0 1px 3px #aaa; + -webkit-box-shadow: 0 1px 3px #aaa; + -moz-box-shadow: 0 1px 3px #aaa; +} + +input[type=submit], +input[type=button], +button[type=submit]:not(.mult_submit) { + font-weight: bold !important; +} + +input[type=submit], +input[type=button], +button[type=submit]:not(.mult_submit), +input[type=reset], +input[name=submit_reset], +input.button { + margin: 6px 14px; + border: 1px solid #aaa; + padding: 3px 7px; + color: #111; + text-decoration: none; + background: #ddd; + + border-radius: 12px; + -webkit-border-radius: 12px; + -moz-border-radius: 12px; + + text-shadow: 0 1px 0 #fff; + + getCssGradient('f8f8f8', 'd8d8d8'); ?> +} + +input[type=submit]:hover, +input[type=button]:hover, +button[type=submit]:not(.mult_submit):hover, +input[type=reset]:hover, +input[name=submit_reset]:hover, +input.button:hover { + position: relative; + getCssGradient('fff', 'ddd'); ?> + cursor: pointer; +} + +input[type=submit]:active, +input[type=button]:active, +button[type=submit]:not(.mult_submit):active, +input[type=reset]:active, +input[name=submit_reset]:active, +input.button:active { + position: relative; + getCssGradient('eee', 'ddd'); ?> + box-shadow: 0 1px 6px -2px #333 inset; + text-shadow: none; +} + +input[type=submit]:disabled, +input[type=button]:disabled, +button[type=submit]:not(.mult_submit):disabled, +input[type=reset]:disabled, +input[name=submit_reset]:disabled, +input.button:disabled { + background: #ccc; + color: #666; + text-shadow: none; +} + +textarea { + overflow: visible; + margin: 6px; +} + +textarea.char { + margin: 6px; +} + +fieldset, .preview_sql { + margin-top: 1em; + border-radius: 4px 4px 0 0; + -moz-border-radius: 4px 4px 0 0; + -webkit-border-radius: 4px 4px 0 0; + border: #aaa solid 1px; + padding: 0.5em; + background: #eee; + text-shadow: 1px 1px 2px #fff inset; + -moz-box-shadow: 1px 1px 2px #fff inset; + -webkit-box-shadow: 1px 1px 2px #fff inset; + box-shadow: 1px 1px 2px #fff inset; +} + +fieldset fieldset { + margin: .8em; + background: #fff; + border: 1px solid #aaa; + background: #E8E8E8; + +} + +fieldset legend { + font-weight: bold; + color: #444; + padding: 5px 10px; + border-radius: 2px; + -moz-border-radius: 2px; + -webkit-border-radius: 2px; + border: 1px solid #aaa; + background-color: #fff; + -moz-box-shadow: 3px 3px 15px #bbb; + -webkit-box-shadow: 3px 3px 15px #bbb; + box-shadow: 3px 3px 15px #bbb; + max-width: 100%; +} + +.some-margin { + margin: 1.5em; +} + +/* buttons in some browsers (eg. Konqueror) are block elements, this breaks design */ +button { + display: inline; +} + +table caption, +table th, +table td { + padding: .1em .3em; + margin: .1em; + vertical-align: middle; + text-shadow: 0 1px 0 #fff; +} + +/* 3.4 */ +.datatable{ + table-layout: fixed; +} + +table { + border-collapse: collapse; +} + +thead th { + border-right: 1px solid #fff; +} + +th { + text-align: left; +} + + +img, +button { + vertical-align: middle; +} + +input[type="checkbox"], +input[type="radio"] { + vertical-align: -11%; +} + + +select { + -moz-border-radius: 2px; + -webkit-border-radius: 2px; + border-radius: 2px; + + border: 1px solid #bbb; + color: #333; + padding: 3px; + background: white; + margin:6px; +} + +select[multiple] { + getCssGradient('ffffff', 'f2f2f2'); ?> +} + +/******************************************************************************/ +/* classes */ +.clearfloat { + clear: both; +} + +.floatleft { + float: ; + margin-: 1em; +} + +.floatright { + float: ; +} + +.center { + text-align: center; +} + +.displayblock { + display: block; +} + +table.nospacing { + border-spacing: 0; +} + +table.nopadding tr th, table.nopadding tr td { + padding: 0; +} + +th.left, td.left { + text-align: left; +} + +th.center, td.center { + text-align: center; +} + +th.right, td.right { + text-align: right; + padding-right: 1em; +} + +tr.vtop th, tr.vtop td, th.vtop, td.vtop { + vertical-align: top; +} + +tr.vmiddle th, tr.vmiddle td, th.vmiddle, td.vmiddle { + vertical-align: middle; +} + +tr.vbottom th, tr.vbottom td, th.vbottom, td.vbottom { + vertical-align: bottom; +} + +.paddingtop { + padding-top: 1em; +} + +.separator { + color: #fff; + text-shadow: 0 1px 0 #000; +} + +div.tools { + /* border: 1px solid #000; */ + padding: .2em; +} + +div.tools a { + color: #3a7ead !important; +} + +div.tools, +fieldset.tblFooters { + margin-top: 0; + margin-bottom: .5em; + /* avoid a thick line since this should be used under another fieldset */ + border-top: 0; + text-align: ; + float: none; + clear: both; + -webkit-border-radius: 0 0 4px 4px; + -moz-border-radius: 0 0 4px 4px; + border-radius: 0 0 4px 5px; +} + +div.null_div { + height: 20px; + text-align: center; + font-style: normal; + min-width: 50px; +} + +fieldset .formelement { + float: ; + margin-: .5em; + /* IE */ + white-space: nowrap; +} + +/* revert for Gecko */ +fieldset div[class=formelement] { + white-space: normal; +} + +button.mult_submit { + border: none; + background-color: transparent; +} + +/* odd items 1,3,5,7,... */ +table tbody:first-of-type tr:nth-child(odd), +table tbody:first-of-type tr:nth-child(odd) th, +#table_index tbody:nth-of-type(odd) tr, +#table_index tbody:nth-of-type(odd) th { + background: #fff; +} + +/* even items 2,4,6,8,... */ +table tbody:first-of-type tr:nth-child(even), +table tbody:first-of-type tr:nth-child(even) th, +#table_index tbody:nth-of-type(even) tr, +#table_index tbody:nth-of-type(even) th { + background: #DFDFDF; +} + +table tr th, +table tr { + text-align: ; +} + +/* marked table rows */ +td.marked:not(.nomarker), +table tr.marked:not(.nomarker) td, +table tbody:first-of-type tr.marked:not(.nomarker) th, +table tr.marked:not(.nomarker) { + getCssGradient('ced6df', 'b6c6d7'); ?> + color: ; +} + +/* hovered items */ +table tbody:first-of-type tr:not(.nopointer):hover, +table tbody:first-of-type tr:not(.nopointer):hover th, +.hover:not(.nopointer) { + getCssGradient('ced6df', 'b6c6d7'); ?> + color: ; +} + +/* hovered table rows */ +#table_index tbody:hover tr, +#table_index tbody:hover th, +table tr.hover:not(.nopointer) th { + getCssGradient('ced6df', 'b6c6d7'); ?> + color: ; +} + +/** + * marks table rows/cells if the db field is in a where condition + */ +.condition { + border-color: !important; +} + +th.condition { + border-width: 1px 1px 0 1px; + border-style: solid; +} + +td.condition { + border-width: 0 1px 0 1px; + border-style: solid; +} + +tr:last-child td.condition { + border-width: 0 1px 1px 1px; +} + + + /* for first th which must have right border set (ltr only) */ + .before-condition { + border-right: 1px solid ; + } + + +/** + * cells with the value NULL + */ +td.null { + font-style: italic; + color: #7d7d7d; +} + +table .valueHeader { + text-align: ; + white-space: normal; +} +table .value { + text-align: ; + white-space: normal; +} +/* IE doesnt handles 'pre' right */ +table [class=value] { + white-space: normal; +} + + + + .value { + font-family: ; + } + +.attention { + color: red; + font-weight: bold; +} +.allfine { + color: green; +} + + +img.lightbulb { + cursor: pointer; +} + +.pdflayout { + overflow: hidden; + clip: inherit; + background-color: #fff; + display: none; + border: 1px solid #000; + position: relative; +} + +.pdflayout_table { + background: #D3DCE3; + color: #000; + overflow: hidden; + clip: inherit; + z-index: 2; + display: inline; + visibility: inherit; + cursor: move; + position: absolute; + font-size: 80%; + border: 1px dashed #000; +} + +/* Doc links in SQL */ +.cm-sql-doc { + text-decoration: none; + border-bottom: 1px dotted #000; + color: inherit !important; +} + +/* no extra space in table cells */ +td .icon { + image-rendering: pixelated; + margin: 0; +} + +.selectallarrow { + margin-: .3em; + margin-: .6em; +} + +/* message boxes: error, confirmation */ +#pma_errors, #pma_demo, #pma_footer { + position: relative; + padding: 0 0.5em; +} + +.success h1, +.notice h1, +div.error h1 { + border-bottom: 2px solid; + font-weight: bold; + text-align: ; + margin: 0 0 .2em 0; +} + +div.success, +div.notice, +div.error { + margin: .5em 0 0.5em; + border: 1px solid; + background-repeat: no-repeat; + + background-position: 10px 50%; + padding: 10px 10px 10px 10px; + + background-position: 99% 50%; + padding: 10px 35px 10px 10px; + + + -moz-border-radius: 5px; + -webkit-border-radius: 5px; + border-radius: 5px; + + -moz-box-shadow: 0 1px 1px #fff inset; + -webkit-box-shadow: 0 1px 1px #fff inset; + box-shadow: 0 1px 1px #fff inset; +} + +.success a, +.notice a, +.error a { + text-decoration: underline; +} + +.success { + color: #000; + background-color: #ebf8a4; +} + +h1.success, +div.success { + border-color: #a2d246; +} +.success h1 { + border-color: #00FF00; +} + +.notice { + color: #000; + background-color: #e8eef1; +} + +h1.notice, +div.notice { + border-color: #3a6c7e; +} + +.notice h1 { + border-color: #ffb10a; +} + +.error { + border: 1px solid maroon !important; + color: #000; + background: pink; +} + +h1.error, +div.error { + border-color: #333; +} + +div.error h1 { + border-color: #ff0000; +} + +.confirmation { + color: #000; + background-color: pink; +} + +fieldset.confirmation { +} + +fieldset.confirmation legend { +} + +/* end messageboxes */ + +.new_central_col{ + width: 100%; +} + +.tblcomment { + font-size: 70%; + font-weight: normal; + color: #000099; +} + +.tblHeaders { + font-weight: bold; + color: ; + background: ; +} + +div.tools, +.tblFooters { + font-weight: normal; + color: ; + background: ; +} + +.tblHeaders a:link, +.tblHeaders a:active, +.tblHeaders a:visited, +div.tools a:link, +div.tools a:visited, +div.tools a:active, +.tblFooters a:link, +.tblFooters a:active, +.tblFooters a:visited { + color: #0000FF; +} + +.tblHeaders a:hover, +div.tools a:hover, +.tblFooters a:hover { + color: #FF0000; +} + +/* forbidden, no privileges */ +.noPrivileges { + color: #FF0000; + font-weight: bold; +} + +/* disabled text */ +.disabled, +.disabled a:link, +.disabled a:active, +.disabled a:visited { + color: #666; +} + +.disabled a:hover { + color: #666; + text-decoration: none; +} + +tr.disabled td, +td.disabled { + background-color: #f3f3f3; + color: #aaa; +} + +.nowrap { + white-space: nowrap; +} + +/** + * login form + */ +body#loginform h1, +body#loginform a.logo { + display: block; + text-align: center; +} + +body#loginform { + margin-top: 1em; + text-align: center; +} + +body#loginform div.container { + text-align: ; + width: 30em; + margin: 0 auto; +} + +form.login label { + width: 10em; + font-weight: bolder; + display: inline-block; + margin-: 2em; +} + +form.login input[type=text], +form.login input[type=password], +form.login select { + box-sizing: border-box; + width: 14em; +} + +.commented_column { + border-bottom: 1px dashed #000; +} + +.column_attribute { + font-size: 70%; +} + +.cfg_dbg_demo{ + margin: 0.5em 1em 0.5em 1em; +} + +.central_columns_navigation{ + padding:1.5% 0em !important; +} + +.central_columns_add_column{ + display:inline-block; + margin-left:1%; + max-width:50% +} + +.message_errors_found{ + margin-top: 20px; +} + +.repl_gui_skip_err_cnt{ + width: 30px; +} + +.font_weight_bold{ + font-weight: bold; +} + +.color_gray{ + color: gray; +} + +.pma_sliding_message{ + display: inline-block; +} + +li.last.database{ + margin-bottom: 15px +} +/******************************************************************************/ +/* specific elements */ + +/* topmenu */ +#topmenu a { + text-shadow: 0 1px 0 #fff; +} + +#topmenu .error { + background: #eee;border: 0 !important;color: #aaa; +} + +ul#topmenu, +ul#topmenu2, +ul.tabs { + font-weight: bold; + list-style-type: none; + margin: 0; + padding: 0; +} + +ul#topmenu2 { + margin: .25em .5em 0; + height: 2em; + clear: both; +} + +ul#topmenu li, +ul#topmenu2 li { + float: ; + margin: 0; + vertical-align: middle; +} + +#topmenu img, +#topmenu2 img { + margin-right: .5em; + vertical-align: -3px; +} + +.menucontainer { + getCssGradient('ffffff', 'dcdcdc'); ?> + border-top: 1px solid #aaa; +} + +.scrollindicator { + display: none; +} + +/* default tab styles */ +.tabactive { + background: #fff !important; +} + +ul#topmenu2 a { + display: block; + margin: 7px 6px 7px; + margin-: 0; + padding: 4px 10px; + white-space: nowrap; + border: 1px solid #ddd; + border-radius: 20px; + -moz-border-radius: 20px; + -webkit-border-radius: 20px; + background: #f2f2f2; + +} + +span.caution { + color: #FF0000; +} +fieldset.caution a { + color: #FF0000; +} +fieldset.caution a:hover { + color: #fff; + background-color: #FF0000; +} + +#topmenu { + margin-top: .5em; + padding: .1em .3em; +} + +ul#topmenu ul { + -moz-box-shadow: 1px 1px 6px #ddd; + -webkit-box-shadow: 2px 2px 3px #666; + box-shadow: 2px 2px 3px #666; +} + +ul#topmenu ul.only { + : 0; +} + +ul#topmenu > li { + border-right: 1px solid #fff; + border-left: 1px solid #ccc; + border-bottom: 1px solid #ccc; +} + +ul#topmenu > li:first-child { + border-left: 0; +} + +/* default tab styles */ +ul#topmenu a, +ul#topmenu span { + padding: .6em; +} + +ul#topmenu ul a { + border-width: 1pt 0 0 0; + -moz-border-radius: 0; + -webkit-border-radius: 0; + border-radius: 0; +} + +ul#topmenu ul li:first-child a { + border-width: 0; +} + +/* enabled hover/active tabs */ +ul#topmenu > li > a:hover, +ul#topmenu > li > .tabactive { + text-decoration: none; +} + +ul#topmenu ul a:hover, +ul#topmenu ul .tabactive { + text-decoration: none; +} + +ul#topmenu a.tab:hover, +ul#topmenu .tabactive { + /* background-color: ; */ +} + +ul#topmenu2 a.tab:hover, +ul#topmenu2 a.tabactive { + background-color: ; + border-radius: .3em; + -moz-border-radius: .3em; + -webkit-border-radius: .3em; + text-decoration: none; +} + +/* to be able to cancel the bottom border, use
      • */ +ul#topmenu > li.active { + /* border-bottom: 0pt solid ; */ + border-right: 0; + border-bottom-color: #fff; +} +/* end topmenu */ + +/* zoom search */ +div#dataDisplay input, +div#dataDisplay select { + margin: 0; + margin-: .5em; +} +div#dataDisplay th { + line-height: 2em; +} +table#tableFieldsId { + width: 100%; +} + +/* Calendar */ +table.calendar { + width: 100%; +} +table.calendar td { + text-align: center; +} +table.calendar td a { + display: block; +} + +table.calendar td a:hover { + background-color: #CCFFCC; +} + +table.calendar th { + background-color: #D3DCE3; +} + +table.calendar td.selected { + background-color: #FFCC99; +} + +img.calendar { + border: none; +} +form.clock { + text-align: center; +} +/* end Calendar */ + + +/* table stats */ +div#tablestatistics table { + float: ; + margin-bottom: .5em; + margin-: 1.5em; + margin-top: .5em; + min-width: 16em; +} + +/* end table stats */ + + +/* server privileges */ +#tableuserrights td, +#tablespecificuserrights td, +#tabledatabases td { + vertical-align: middle; +} +/* end server privileges */ + + +/* Heading */ +#topmenucontainer { + padding-: 1em; + width: 100%; +} + +#serverinfo { + background: #888; + padding: .3em .9em; + padding-: 2.2em; + text-shadow: 0 1px 0 #000; + max-width: 100%; + max-height: 16px; + overflow: hidden; +} + +#serverinfo .item { + white-space: nowrap; + color: #fff; +} + +#page_nav_icons { + position: fixed; + top: 0; + : 0; + z-index: 99; + padding: .25em 0; +} + +#goto_pagetop, #lock_page_icon, #page_settings_icon { + padding: .25em; + background: #888; +} + +#page_settings_icon { + cursor: pointer; + display: none; +} + +#page_settings_modal { + display: none; +} + +#pma_navigation_settings { + display: none; +} + +#span_table_comment { + font-weight: bold; + font-style: italic; + white-space: nowrap; + margin-left: 10px; + color: #D6D6D6; + text-shadow: none; +} + +#serverinfo img { + margin: 0 .1em 0; + margin-: .2em; +} + + +#textSQLDUMP { + width: 95%; + height: 95%; + font-family: Consolas, "Courier New", Courier, mono; + font-size: 110%; +} + +#TooltipContainer { + position: absolute; + z-index: 99; + width: 20em; + height: auto; + overflow: visible; + visibility: hidden; + background-color: #ffffcc; + color: #006600; + border: .1em solid #000; + padding: .5em; +} + +/* user privileges */ +#fieldset_add_user_login div.item { + border-bottom: 1px solid silver; + padding-bottom: .3em; + margin-bottom: .3em; +} + +#fieldset_add_user_login label { + float: ; + display: block; + width: 10em; + max-width: 100%; + text-align: ; + padding-: .5em; +} + +#fieldset_add_user_login span.options #select_pred_username, +#fieldset_add_user_login span.options #select_pred_hostname, +#fieldset_add_user_login span.options #select_pred_password { + width: 100%; + max-width: 100%; +} + +#fieldset_add_user_login span.options { + float: ; + display: block; + width: 12em; + max-width: 100%; + padding-: .5em; +} + +#fieldset_add_user_login input { + width: 12em; + clear: ; + max-width: 100%; +} + +#fieldset_add_user_login span.options input { + width: auto; +} + +#fieldset_user_priv div.item { + float: ; + width: 9em; + max-width: 100%; +} + +#fieldset_user_priv div.item div.item { + float: none; +} + +#fieldset_user_priv div.item label { + white-space: nowrap; +} + +#fieldset_user_priv div.item select { + width: 100%; +} + +#fieldset_user_global_rights fieldset { + float: ; +} + +#fieldset_user_group_rights fieldset { + float: ; +} + +#fieldset_user_global_rights>legend input { + margin-: 2em; +} +/* end user privileges */ + + +/* serverstatus */ + +.linkElem:hover { + text-decoration: underline; + color: #235a81; + cursor: pointer; +} + +h3#serverstatusqueries span { + font-size: 60%; + display: inline; +} + +.buttonlinks { + float: ; + white-space: nowrap; +} + +/* Also used for the variables page */ +fieldset#tableFilter { + padding: 0.1em 1em; +} + +div#serverStatusTabs { + margin-top: 1em; +} + +caption a.top { + float: ; +} + +div#serverstatusquerieschart { + float: ; + width: 500px; + height: 350px; + margin-: 50px; +} + +table#serverstatusqueriesdetails, +table#serverstatustraffic { + float: ; +} + +table#serverstatusqueriesdetails th { + min-width: 35px; +} + +table#serverstatusvariables { + width: 100%; + margin-bottom: 1em; +} +table#serverstatusvariables .name { + width: 18em; + white-space: nowrap; +} +table#serverstatusvariables .value { + width: 6em; +} +table#serverstatusconnections { + float: ; + margin-: 30px; +} + +div#serverstatus table tbody td.descr a, +div#serverstatus table .tblFooters a { + white-space: nowrap; +} + +div.liveChart { + clear: both; + min-width: 500px; + height: 400px; + padding-bottom: 80px; +} + +#addChartDialog input[type="text"] { + margin: 0; + padding: 3px; +} + +div#chartVariableSettings { + border: 1px solid #ddd; + background-color: #E6E6E6; + margin-left: 10px; +} + +table#chartGrid td { + padding: 3px; + margin: 0; +} + +table#chartGrid div.monitorChart { + background: #EBEBEB; + overflow: hidden; + border: none; +} + +div.tabLinks { + margin-left: 0.3em; + float: ; + padding: 5px 0; +} + +div.tabLinks a, div.tabLinks label { + margin-right: 7px; +} + +div.tabLinks .icon { + margin: -0.2em 0.3em 0 0; +} + +.popupContent { + display: none; + position: absolute; + border: 1px solid #CCC; + margin: 0; + padding: 3px; + -moz-box-shadow: 2px 2px 3px #666; + -webkit-box-shadow: 2px 2px 3px #666; + box-shadow: 2px 2px 3px #666; + background-color: #fff; + z-index: 2; +} + +div#logTable { + padding-top: 10px; + clear: both; +} + +div#logTable table { + width: 100%; +} + +div#queryAnalyzerDialog { + min-width: 700px; +} + +div#queryAnalyzerDialog div.CodeMirror-scroll { + height: auto; +} + +div#queryAnalyzerDialog div#queryProfiling { + height: 300px; +} + +div#queryAnalyzerDialog td.explain { + width: 250px; +} + +div#queryAnalyzerDialog table.queryNums { + display: none; + border: 0; + text-align: left; +} + +.smallIndent { + padding-: 7px; +} + +/* end serverstatus */ + +/* server variables */ +#serverVariables { + width: 100%; +} +#serverVariables .var-row > td { + line-height: 2em; +} +#serverVariables .var-header { + color: ; + background: #f3f3f3; + getCssGradient('ffffff', 'cccccc'); ?> + font-weight: bold; + text-align: ; +} +#serverVariables .var-row { + padding: 0.5em; + min-height: 18px; +} +#serverVariables .var-name { + font-weight: bold; +} +#serverVariables .var-name.session { + font-weight: normal; + font-style: italic; +} +#serverVariables .var-value { + float: ; + text-align: ; +} +#serverVariables .var-doc { + overflow:visible; + float: ; +} + +/* server variables editor */ +#serverVariables .editLink { + padding-: 1em; + font-family: sans-serif; +} +#serverVariables .serverVariableEditor { + width: 100%; + overflow: hidden; +} +#serverVariables .serverVariableEditor input { + width: 100%; + margin: 0 0.5em; + box-sizing: border-box; + -ms-box-sizing: border-box; + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + height: 2.2em; +} +#serverVariables .serverVariableEditor div { + display: block; + overflow: hidden; + padding-: 1em; +} +#serverVariables .serverVariableEditor a { + margin: 0 0.5em; + line-height: 2em; +} +/* end server variables */ + + +p.notice { + margin: 1.5em 0; + border: 1px solid #000; + background-repeat: no-repeat; + + background-position: 10px 50%; + padding: 10px 10px 10px 25px; + + background-position: 99% 50%; + padding: 25px 10px 10px 10px + + -moz-border-radius: 5px; + -webkit-border-radius: 5px; + border-radius: 5px; + -moz-box-shadow: 0 1px 2px #fff inset; + -webkit-box-shadow: 0 1px 2px #fff inset; + box-shadow: 0 1px 2px #fff inset; + background: #555; + color: #d4fb6a; +} + +p.notice a { + color: #fff; + text-decoration: underline; +} + +/* profiling */ + +div#profilingchart { + width: 850px; + height: 370px; + float: ; +} + +#profilingchart .jqplot-highlighter-tooltip{ + top: auto !important; + left: 11px; + bottom:24px; +} +/* end profiling */ + +/* table charting */ +#resizer { + border: 1px solid silver; +} +#inner-resizer { /* make room for the resize handle */ + padding: 10px; +} +.chartOption { + float: ; + margin-: 40px; +} +/* end table charting */ + +/* querybox */ + +#togglequerybox { + margin: 0 10px; +} + +#serverstatus h3 +{ + margin: 15px 0; + font-weight: normal; + color: #999; + font-size: 1.7em; +} +#sectionlinks { + margin-bottom: 15px; + padding: 16px; + background: #f3f3f3; + border: 1px solid #aaa; + border-radius: 5px; + -webkit-border-radius: 5px; + -moz-border-radius: 5px; + box-shadow: 0 1px 1px #fff inset; + -webkit-box-shadow: 0 1px 1px #fff inset; + -moz-box-shadow: 0 1px 1px #fff inset; +} +#sectionlinks a, +.buttonlinks a, +a.button { + font-weight: bold; + text-shadow: 0 1px 0 #fff; + line-height: 35px; + margin-: 7px; + border: 1px solid #aaa; + padding: 3px 7px; + color: #111 !important; + text-decoration: none; + background: #ddd; + white-space: nowrap; + border-radius: 20px; + -webkit-border-radius: 20px; + -moz-border-radius: 20px; + getCssGradient('f8f8f8', 'd8d8d8'); ?> +} +#sectionlinks a:hover, +.buttonlinks a:hover, +a.button:hover { + getCssGradient('ffffff', 'dddddd'); ?> +} + +div#sqlquerycontainer { + float: ; + width: 69%; + /* height: 15em; */ +} + +div#tablefieldscontainer { + float: ; + width: 29%; + margin-top: -20px; + /* height: 15em; */ +} + +div#tablefieldscontainer select { + width: 100%; + background: #fff; + /* height: 12em; */ +} + +textarea#sqlquery { + width: 100%; + /* height: 100%; */ + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + border-radius: 4px; + border: 1px solid #aaa; + padding: 5px; + font-family: inherit; +} +textarea#sql_query_edit { + height: 7em; + width: 95%; + display: block; +} +div#queryboxcontainer div#bookmarkoptions { + margin-top: .5em; +} +/* end querybox */ + +/* main page */ +#maincontainer { + /* background-image: url(getImgPath('logo_right.png');?>); */ + /* background-position: bottom; */ + /* background-repeat: no-repeat; */ +} + +#mysqlmaininformation, +#pmamaininformation { + float: ; + width: 49%; +} + +#maincontainer ul { + list-style-type: disc; + vertical-align: middle; +} + +#maincontainer li { + margin-bottom: .3em; +} + +#full_name_layer { + position: absolute; + padding: 2px; + margin-top: -3px; + z-index: 801; + + border-radius: 3px; + border: solid 1px #888; + background: #fff; + +} +/* end main page */ + + +/* iconic view for ul items */ + +li.no_bullets { + list-style-type:none !important; + margin-left: -25px !important; //align with other list items which have bullets +} + +/* end iconic view for ul items */ + +#body_browse_foreigners { + background: ; + margin: .5em .5em 0 .5em; +} + +#bodythemes { + width: 500px; + margin: auto; + text-align: center; +} + +#bodythemes img { + border: .1em solid #000; +} + +#bodythemes a:hover img { + border: .1em solid red; +} + +#fieldset_select_fields { + float: ; +} + +#selflink { + clear: both; + display: block; + margin-top: 1em; + margin-bottom: 1em; + width: 98%; + margin-: 1%; + border-top: .1em solid silver; + text-align: ; +} + +#table_innodb_bufferpool_usage, +#table_innodb_bufferpool_activity { + float: ; +} + +#div_mysql_charset_collations table { + float: ; +} + +#div_mysql_charset_collations table th, +#div_mysql_charset_collations table td { + padding: 0.4em; +} + +#div_mysql_charset_collations table th#collationHeader { + width: 35%; +} + +#qbe_div_table_list { + float: ; +} + +#qbe_div_sql_query { + float: ; +} + +label.desc { + width: 30em; + float: ; +} + +label.desc sup { + position: absolute; +} + +code.php { + display: block; + padding-left: 1em; + margin-top: 0; + margin-bottom: 0; + max-height: 10em; + overflow: auto; + direction: ltr; +} + +code.sql, +div.sqlvalidate { + display: block; + padding: 1em; + margin-top: 0; + margin-bottom: 0; + max-height: 10em; + overflow: auto; + direction: ltr; +} + +.result_query div.sqlOuter { + background: ; + text-align: ; +} + +.result_query .success, .result_query .error { + margin-bottom: 0; + border-bottom: none !important; + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; + padding-bottom: 5px; +} + +#PMA_slidingMessage code.sql, +div.sqlvalidate { + background: ; +} + +#main_pane_left { + width: 60%; + min-width: 260px; + float: ; + padding-top: 1em; +} + +#main_pane_right { + overflow: hidden; + min-width: 160px; + padding-top: 1em; + padding-: 1em; + padding-: .5em; +} + +.group { + + border: 1px solid #999; + background: #f3f3f3; + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + border-radius: 4px; + -moz-box-shadow: 2px 2px 5px #ccc; + -webkit-box-shadow: 2px 2px 5px #ccc; + box-shadow: 2px 2px 5px #ccc; + margin-bottom: 1em; + padding-bottom: 1em; +} + +.group h2 { + background-color: #bbb; + padding: .1em .3em; + margin-top: 0; + color: #fff; + font-size: 1.6em; + font-weight: normal; + text-shadow: 0 1px 0 #777; + -moz-box-shadow: 1px 1px 15px #999 inset; + -webkit-box-shadow: 1px 1px 15px #999 inset; + box-shadow: 1px 1px 15px #999 inset; +} + +.group-cnt { + padding: 0; + padding-: .5em; + display: inline-block; + width: 98%; +} + +textarea#partitiondefinition { + height: 3em; +} + + +/* for elements that should be revealed only via js */ +.hide { + display: none; +} + +#list_server { + list-style-type: none; + padding: 0; +} + +/** + * Progress bar styles + */ +div.upload_progress +{ + width: 400px; + margin: 3em auto; + text-align: center; +} + +div.upload_progress_bar_outer +{ + border: 1px solid #000; + width: 202px; + position: relative; + margin: 0 auto 1em; + color: ; +} + +div.upload_progress_bar_inner +{ + background-color: ; + width: 0; + height: 12px; + margin: 1px; + overflow: hidden; + color: ; + position: relative; +} + +div.upload_progress_bar_outer div.percentage +{ + position: absolute; + top: 0; + : 0; + width: 202px; +} + +div.upload_progress_bar_inner div.percentage +{ + top: -1px; + : -1px; +} + +div#statustext { + margin-top: .5em; +} + +table#serverconnection_src_remote, +table#serverconnection_trg_remote, +table#serverconnection_src_local, +table#serverconnection_trg_local { + float: ; +} +/** + * Validation error message styles + */ +input[type=text].invalid_value, +input[type=password].invalid_value, +input[type=number].invalid_value, +input[type=date].invalid_value, +select.invalid_value, +.invalid_value { + background: #FFCCCC; +} + +/** + * Ajax notification styling + */ + .ajax_notification { + top: 0; /** The notification needs to be shown on the top of the page */ + position: fixed; + margin-top: 0; + margin-right: auto; + margin-bottom: 0; + margin-: auto; + padding: 5px; /** Keep a little space on the sides of the text */ + width: 350px; + + z-index: 1100; /** If this is not kept at a high z-index, the jQueryUI modal dialogs (z-index: 1000) might hide this */ + text-align: center; + display: inline; + left: 0; + right: 0; + background-image: url(getImgPath('ajax_clock_small.gif');?>); + background-repeat: no-repeat; + background-position: 2%; + border: 1px solid #e2b709; + } + +/* additional styles */ +.ajax_notification { + margin-top: 200px; + background: #ffe57e; + border-radius: 5px; + -moz-border-radius: 5px; + -webkit-border-radius: 5px; + box-shadow: 0 5px 90px #888; + -moz-box-shadow: 0 5px 90px #888; + -webkit-box-shadow: 0 5px 90px #888; +} + +#loading_parent { + /** Need this parent to properly center the notification division */ + position: relative; + width: 100%; + } +/** + * Export and Import styles + */ + +.export_table_list_container { + display: inline-block; + max-height: 20em; + overflow-y: scroll; +} + +.export_table_select th { + text-align: center; + vertical-align: middle; +} + +.export_table_select .all { + font-weight: bold; + border-bottom: 1px solid black; +} + +.export_structure, .export_data { + text-align: center; +} + +.export_table_name { + vertical-align: middle; +} + +.exportoptions h2 { + word-wrap: break-word; +} + +.exportoptions h3, +.importoptions h3 { + border-bottom: 1px #999 solid; + font-size: 110%; +} + +.exportoptions ul, +.importoptions ul, +.format_specific_options ul { + list-style-type: none; + margin-bottom: 15px; +} + +.exportoptions li, +.importoptions li { + margin: 7px; +} +.exportoptions label, +.importoptions label, +.exportoptions p, +.importoptions p { + margin: 5px; + float: none; +} + +#csv_options label.desc, +#ldi_options label.desc, +#latex_options label.desc, +#output label.desc { + float: ; + width: 15em; +} + +.exportoptions, +.importoptions { + margin: 20px 30px 30px; + margin-: 10px; +} + +.exportoptions #buttonGo, +.importoptions #buttonGo { + font-weight: bold; + margin-: 14px; + border: 1px solid #aaa; + padding: 5px 12px; + color: #111; + text-decoration: none; + + border-radius: 12px; + -webkit-border-radius: 12px; + -moz-border-radius: 12px; + + text-shadow: 0 1px 0 #fff; + + getCssGradient('ffffff', 'cccccc'); ?> + cursor: pointer; +} + +.format_specific_options h3 { + margin: 10px 0 0; + margin-: 10px; + border: 0; +} + +.format_specific_options { + border: 1px solid #999; + margin: 7px 0; + padding: 3px; +} + +p.desc { + margin: 5px; +} + +/** + * Export styles only + */ +select#db_select, +select#table_select { + width: 400px; +} + +.export_sub_options { + margin: 20px 0 0; + margin-: 30px; +} + +.export_sub_options h4 { + border-bottom: 1px #999 solid; +} + +.export_sub_options li.subgroup { + display: inline-block; + margin-top: 0; +} + +.export_sub_options li { + margin-bottom: 0; +} +#export_refresh_form { + margin-left: 20px; +} +#export_back_button { + display: inline; +} +#output_quick_export { + display: none; +} +/** + * Import styles only + */ + +.importoptions #import_notification { + margin: 10px 0; + font-style: italic; +} + +input#input_import_file { + margin: 5px; +} + +.formelementrow { + margin: 5px 0 5px 0; +} + +#filterText { + vertical-align: baseline; +} + +#popup_background { + display: none; + position: fixed; + _position: absolute; /* hack for IE6 */ + width: 100%; + height: 100%; + top: 0; + : 0; + background: #000; + z-index: 1000; + overflow: hidden; +} + +/** + * Table structure styles + */ +#fieldsForm ul.table-structure-actions { + margin: 0; + padding: 0; + list-style: none; +} +#fieldsForm ul.table-structure-actions li { + float: ; + margin-: 0.3em; /* same as padding of "table td" */ +} +#fieldsForm ul.table-structure-actions .submenu li { + padding: 0; + margin: 0; +} +#fieldsForm ul.table-structure-actions .submenu li span { + padding: 0.3em; + margin: 0.1em; +} +#structure-action-links a { + margin-: 1em; +} +#addColumns input[type="radio"] { + margin: 3px 0 0; + margin-: 1em; +} +/** + * Indexes + */ +#index_frm .index_info input[type="text"], +#index_frm .index_info select { + width: 100%; + margin: 0; + box-sizing: border-box; + -ms-box-sizing: border-box; + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; +} + +#index_frm .index_info div { + padding: .2em 0; +} + +#index_frm .index_info .label { + float: ; + min-width: 12em; +} + +#index_frm .slider { + width: 10em; + margin: .6em; + float: ; +} + +#index_frm .add_fields { + float: ; +} + +#index_frm .add_fields input { + margin-: 1em; +} + +#index_frm input { + margin: 0; +} + +#index_frm td { + vertical-align: middle; +} + +table#index_columns { + width: 100%; +} + +table#index_columns select { + width: 85%; + float: ; +} + +#move_columns_dialog div { + padding: 1em; +} + +#move_columns_dialog ul { + list-style: none; + margin: 0; + padding: 0; +} + +#move_columns_dialog li { + background: ; + border: 1px solid #aaa; + color: ; + font-weight: bold; + margin: .4em; + padding: .2em; + -webkit-border-radius: 2px; + -moz-border-radius: 2px; + border-radius: 2px; +} + +/* config forms */ +.config-form ul.tabs { + margin: 1.1em .2em 0; + padding: 0 0 .3em 0; + list-style: none; + font-weight: bold; +} + +.config-form ul.tabs li { + float: ; + margin-bottom: -1px; +} + +.config-form ul.tabs li a { + display: block; + margin: .1em .2em 0; + white-space: nowrap; + text-decoration: none; + border: 1px solid ; + border-bottom: 1px solid #aaa; +} + +.config-form ul.tabs li a { + padding: 7px 10px; + -webkit-border-radius: 5px 5px 0 0; + -moz-border-radius: 5px 5px 0 0; + border-radius: 5px 5px 0 0; + background: #f2f2f2; + color: #555; + text-shadow: 0 1px 0 #fff; +} + +.config-form ul.tabs li a:hover, +.config-form ul.tabs li a:active { + background: #e5e5e5; +} + +.config-form ul.tabs li.active a { + background-color: #fff; + margin-top: 1px; + color: #000; + text-shadow: none; + border-color: #aaa; + border-bottom: 1px solid #fff; +} + +.config-form fieldset { + margin-top: 0; + padding: 0; + clear: both; + -webkit-border-radius: 0; + -moz-border-radius: 0; + border-radius: 0; +} + +.config-form legend { + display: none; +} + +.config-form fieldset p { + margin: 0; + padding: .5em; + background: #fff; + border-top: 0; +} + +.config-form fieldset .errors { /* form error list */ + margin: 0 -2px 1em; + padding: .5em 1.5em; + background: #FBEAD9; + border: 0 #C83838 solid; + border-width: 1px 0; + list-style: none; + font-family: sans-serif; + font-size: small; +} + +.config-form fieldset .inline_errors { /* field error list */ + margin: .3em .3em .3em; + margin-: 0; + padding: 0; + list-style: none; + color: #9A0000; + font-size: small; +} + +.config-form fieldset th { + padding: .3em .3em .3em; + padding-: .5em; + text-align: ; + vertical-align: top; + width: 40%; + background: transparent; + filter: none; +} + +.config-form fieldset .doc, +.config-form fieldset .disabled-notice { + margin-: 1em; +} + +.config-form fieldset .disabled-notice { + font-size: 80%; + text-transform: uppercase; + color: #E00; + cursor: help; +} + +.config-form fieldset td { + padding-top: .3em; + padding-bottom: .3em; + vertical-align: top; +} + +.config-form fieldset th small { + display: block; + font-weight: normal; + font-family: sans-serif; + font-size: x-small; + color: #444; +} + +.config-form fieldset th, +.config-form fieldset td { + border-top: 1px solid; + border-: none; +} + +fieldset .group-header th { + background: ; +} + +fieldset .group-header + tr th { + padding-top: .6em; +} + +fieldset .group-field-1 th, +fieldset .group-header-2 th { + padding-: 1.5em; +} + +fieldset .group-field-2 th, +fieldset .group-header-3 th { + padding-: 3em; +} + +fieldset .group-field-3 th { + padding-: 4.5em; +} + +fieldset .disabled-field th, +fieldset .disabled-field th small, +fieldset .disabled-field td { + color: #666; + background-color: #ddd; +} + +.config-form .lastrow { + border-top: 1px #000 solid; +} + +.config-form .lastrow { + background: ; + padding: .5em; + text-align: center; +} + +.config-form .lastrow input { + font-weight: bold; +} + +/* form elements */ + +.config-form span.checkbox { + padding: 2px; + display: inline-block; +} + +.config-form .custom { /* customized field */ + background: #FFC; +} + +.config-form span.checkbox.custom { + padding: 1px; + border: 1px #EDEC90 solid; + background: #FFC; +} + +.config-form .field-error { + border-color: #A11 !important; +} + +.config-form input[type="text"], +.config-form input[type="password"], +.config-form input[type="number"], +.config-form select, +.config-form textarea { + border: 1px #A7A6AA solid; + height: auto; +} + +.config-form input[type="text"]:focus, +.config-form input[type="password"]:focus, +.config-form input[type="number"]:focus, +.config-form select:focus, +.config-form textarea:focus { + border: 1px #6676FF solid; + background: #F7FBFF; +} + +.config-form .field-comment-mark { + font-family: serif; + color: #007; + cursor: help; + padding: 0 .2em; + font-weight: bold; + font-style: italic; +} + +.config-form .field-comment-warning { + color: #A00; +} + +/* error list */ +.config-form dd { + margin-: .5em; +} + +.config-form dd:before { + content: "\25B8 "; +} + +.click-hide-message { + cursor: pointer; +} + +.prefsmanage_opts { + margin-: 2em; +} + +#prefs_autoload { + margin-bottom: .5em; + margin-left: .5em; +} + +#placeholder .button { + position: absolute; + cursor: pointer; +} + +#placeholder div.button { + font-size: smaller; + color: #999; + background-color: #eee; + padding: 2px; +} + +.wrapper { + float: ; + margin-bottom: 1.5em; +} +.toggleButton { + position: relative; + cursor: pointer; + font-size: .8em; + text-align: center; + line-height: 1.4em; + height: 1.55em; + overflow: hidden; + border-right: .1em solid #888; + border-left: .1em solid #888; + -webkit-border-radius: .3em; + -moz-border-radius: .3em; + border-radius: .3em; +} +.toggleButton table, +.toggleButton td, +.toggleButton img { + padding: 0; + position: relative; +} +.toggleButton .container { + position: absolute; +} +.toggleButton .container td, +.toggleButton .container tr { + background-image: none; + background: none !important; +} +.toggleButton .toggleOn { + color: #fff; + padding: 0 1em; + text-shadow: 0 0 .2em #000; +} +.toggleButton .toggleOff { + padding: 0 1em; +} + +.doubleFieldset fieldset { + width: 48%; + float: ; + padding: 0; +} +.doubleFieldset fieldset.left { + margin-: 1%; +} +.doubleFieldset fieldset.right { + margin-: 1%; +} +.doubleFieldset legend { + margin-: 1.5em; +} +.doubleFieldset div.wrap { + padding: 1.5em; +} + +#table_name_col_no_outer { + margin-top: 45px; +} + +#table_name_col_no { + position: fixed; + top: 55px; + width: 100%; + background: #ffffff; +} + +#table_columns input[type="text"], +#table_columns input[type="password"], +#table_columns input[type="number"], +#table_columns select { + width: 10em; + box-sizing: border-box; + -ms-box-sizing: border-box; + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; +} + +#placeholder { + position: relative; + border: 1px solid #aaa; + float: ; + overflow: hidden; + width: 450px; + height: 300px; +} + +#openlayersmap{ + width: 450px; + height: 300px; +} + +.placeholderDrag { + cursor: move; +} + +#placeholder .button { + position: absolute; +} + +#left_arrow { + left: 8px; + top: 26px; +} + +#right_arrow { + left: 26px; + top: 26px; +} + +#up_arrow { + left: 17px; + top: 8px; +} + +#down_arrow { + left: 17px; + top: 44px; +} + +#zoom_in { + left: 17px; + top: 67px; +} + +#zoom_world { + left: 17px; + top: 85px; +} + +#zoom_out { + left: 17px; + top: 103px; +} + +.colborder { + cursor: col-resize; + height: 100%; + margin-: -6px; + position: absolute; + width: 5px; +} + +.colborder_active { + border-: 2px solid #a44; +} + +.pma_table td { + position: static; +} + +.pma_table th.draggable span, +.sticky_columns th.draggable span, +.pma_table tbody td span { + display: block; + overflow: hidden; +} + +.pma_table tbody td span code span { + display: inline; +} + +.pma_table th.draggable.right span { + margin-: 0px; +} + +.pma_table th.draggable span { + margin-: 10px; +} + +.modal-copy input { + display: block; + width: 100%; + margin-top: 1.5em; + padding: .3em 0; +} + +.cRsz { + position: absolute; +} + +.cCpy { + background: #333; + color: #FFF; + font-weight: bold; + margin: .1em; + padding: .3em; + position: absolute; + text-shadow: -1px -1px #000; + + -moz-box-shadow: 0 0 .7em #000; + -webkit-box-shadow: 0 0 .7em #000; + box-shadow: 0 0 .7em #000; + -moz-border-radius: .3em; + -webkit-border-radius: .3em; + border-radius: .3em; +} + +.cPointer { + background: url(getImgPath('col_pointer.png');?>); + height: 20px; + margin-: -5px; /* must be minus half of its width */ + margin-top: -10px; + position: absolute; + width: 10px; +} + +.tooltip { + background: #333 !important; + opacity: .8 !important; + border: 1px solid #000 !important; + -moz-border-radius: .3em !important; + -webkit-border-radius: .3em !important; + border-radius: .3em !important; + text-shadow: -1px -1px #000 !important; + font-size: .8em !important; + font-weight: bold !important; + padding: 1px 3px !important; +} + +.tooltip * { + background: none !important; + color: #FFF !important; +} + +.cDrop { + left: 0; + position: absolute; + top: 0; +} + +.coldrop { + background: url(getImgPath('col_drop.png');?>); + cursor: pointer; + height: 16px; + margin-: .3em; + margin-top: .3em; + position: absolute; + width: 16px; +} + +.coldrop:hover, +.coldrop-hover { + background-color: #999; +} + +.cList { + background: #EEE; + border: solid 1px #999; + position: absolute; + -moz-box-shadow: 0 .2em .5em #333; + -webkit-box-shadow: 0 .2em .5em #333; + box-shadow: 0 .2em .5em #333; +} + +.cList .lDiv div { + padding: .2em .5em .2em; + padding-: .2em; +} + +.cList .lDiv div:hover { + background: #DDD; + cursor: pointer; +} + +.cList .lDiv div input { + cursor: pointer; +} + +.showAllColBtn { + border-bottom: solid 1px #999; + border-top: solid 1px #999; + cursor: pointer; + font-size: .9em; + font-weight: bold; + padding: .35em 1em; + text-align: center; +} + +.showAllColBtn:hover { + background: #DDD; +} + +.turnOffSelect { + -moz-user-select: none; + -khtml-user-select: none; + -webkit-user-select: none; + user-select: none; +} + +.navigation { + margin: .8em 0; + + border-radius: 5px; + -webkit-border-radius: 5px; + -moz-border-radius: 5px; + + getCssGradient('eeeeee', 'cccccc'); ?> +} + +.navigation td { + margin: 0; + padding: 0; + vertical-align: middle; + white-space: nowrap; +} + +.navigation_separator { + color: #999; + display: inline-block; + font-size: 1.5em; + text-align: center; + height: 1.4em; + width: 1.2em; + text-shadow: 1px 0 #FFF; +} + +.navigation input[type=submit] { + background: none; + border: 0; + filter: none; + margin: 0; + padding: .8em .5em; + + border-radius: 0; + -webkit-border-radius: 0; + -moz-border-radius: 0; +} + +.navigation input[type=submit]:hover, +.navigation input.edit_mode_active { + color: #fff; + cursor: pointer; + text-shadow: none; + + getCssGradient('333333', '555555'); ?> +} + +.navigation select { + margin: 0 .8em; +} + +.cEdit { + margin: 0; + padding: 0; + position: absolute; +} + +.cEdit input[type=text] { + background: #FFF; + height: 100%; + margin: 0; + padding: 0; +} + +.cEdit .edit_area { + background: #FFF; + border: 1px solid #999; + min-width: 10em; + padding: .3em .5em; +} + +.cEdit .edit_area select, +.cEdit .edit_area textarea { + width: 97%; +} + +.cEdit .cell_edit_hint { + color: #555; + font-size: .8em; + margin: .3em .2em; +} + +.cEdit .edit_box { + overflow-x: hidden; + overflow-y: scroll; + padding: 0; + margin: 0; +} + +.cEdit .edit_box_posting { + background: #FFF url(getImgPath('ajax_clock_small.gif');?>) no-repeat right center; + padding-: 1.5em; +} + +.cEdit .edit_area_loading { + background: #FFF url(getImgPath('ajax_clock_small.gif');?>) no-repeat center; + height: 10em; +} + +.cEdit .goto_link { + background: #EEE; + color: #555; + padding: .2em .3em; +} + +.saving_edited_data { + background: url(getImgPath('ajax_clock_small.gif');?>) no-repeat left; + padding-: 20px; +} + +.relationalTable td { + vertical-align: top; +} + +.relationalTable select { + width: 125px; + margin-right: 5px; +} + +/* css for timepicker */ +.ui-timepicker-div .ui-widget-header { margin-bottom: 8px; } +.ui-timepicker-div dl { text-align: ; } +.ui-timepicker-div dl dt { height: 25px; margin-bottom: -25px; } +.ui-timepicker-div dl dd { margin: 0 10px 10px 85px; } +.ui-timepicker-div td { font-size: 90%; } +.ui-tpicker-grid-label { background: none; border: none; margin: 0; padding: 0; } +.ui-timepicker-rtl { direction: rtl; } +.ui-timepicker-rtl dl { text-align: right; } +.ui-timepicker-rtl dl dd { margin: 0 65px 10px 10px; } + +input.btn { + color: #333; + background-color: #D0DCE0; +} + +body .ui-widget { + font-size: 1em; +} + +.ui-dialog fieldset legend a { + color: #235A81; +} + +.ui-draggable { + z-index: 801; +} + +/* over-riding jqplot-yaxis class */ +.jqplot-yaxis { + left:0 !important; + min-width:25px; + width:auto; +} +.jqplot-axis { + overflow:hidden; +} + +.report-data { + height:13em; + overflow:scroll; + width:570px; + border: solid 1px; + background: white; + padding: 2px; +} + +.report-description { + height:10em; + width:570px; +} + +div#page_content div#tableslistcontainer table.data { + border-top: 0.1px solid #EEEEEE; +} + +div#page_content div#tableslistcontainer, div#page_content div.notice, div#page_content div.result_query { + margin-top: 1em; +} + +table.show_create { + margin-top: 1em; +} + +table.show_create td { + border-right: 1px solid #bbb; +} + +#alias_modal table { + width: 100%; +} + +#alias_modal label { + font-weight: bold; +} + +.ui-dialog { + position: fixed; +} + + +.small_font { + font-size: smaller; +} + +/* Console styles */ +#pma_console_container { + width: 100%; + position: fixed; + bottom: 0; + : 0; + z-index: 100; +} +#pma_console { + position: relative; + margin-: 240px; +} +#pma_console .templates { + display: none; +} +#pma_console .mid_text, +#pma_console .toolbar span { + vertical-align: middle; +} +#pma_console .toolbar { + position: relative; + background: #ccc; + border-top: solid 1px #aaa; + cursor: n-resize; +} +#pma_console .toolbar.collapsed:not(:hover) { + display: inline-block; + border-top--radius: 3px; + border-: solid 1px #aaa; +} +#pma_console .toolbar.collapsed { + cursor: default; +} +#pma_console .toolbar.collapsed>.button { + display: none; +} +#pma_console .message span.text, +#pma_console .message span.action, +#pma_console .toolbar .button, +#pma_console .toolbar .text, +#pma_console .switch_button { + padding: 0 3px; + display: inline-block; +} +#pma_console .message span.action, +#pma_console .toolbar .button, +#pma_console .switch_button { + cursor: pointer; +} +#pma_console .message span.action:hover, +#pma_console .toolbar .button:hover, +#pma_console .switch_button:hover, +#pma_console .toolbar .button.active { + background: #ddd; +} +#pma_console .toolbar .text { + font-weight: bold; +} +#pma_console .toolbar .button, +#pma_console .toolbar .text { + margin-: .4em; +} +#pma_console .toolbar .button, +#pma_console .toolbar .text { + float: ; +} +#pma_console .content { + overflow-x: hidden; + overflow-y: auto; + margin-bottom: -65px; + border-top: solid 1px #aaa; + background: #fff; + padding-top: .4em; +} +#pma_console .content.console_dark_theme { + background: #000; + color: #fff; +} +#pma_console .content.console_dark_theme .CodeMirror-wrap { + background: #000; + color: #fff; +} +#pma_console .content.console_dark_theme .action_content { + color: #000; +} +#pma_console .content.console_dark_theme .message { + border-color: #373B41; +} +#pma_console .content.console_dark_theme .CodeMirror-cursor { + border-color: #fff; +} +#pma_console .content.console_dark_theme .cm-keyword { + color: #de935f; +} +#pma_console .message, +#pma_console .query_input { + position: relative; + font-family: Monaco, Consolas, monospace; + cursor: text; + margin: 0 10px .2em 1.4em; +} +#pma_console .message { + border-bottom: solid 1px #ccc; + padding-bottom: .2em; +} +#pma_console .message.expanded>.action_content { + position: relative; +} +#pma_console .message:before, +#pma_console .query_input:before { + left: -0.7em; + position: absolute; + content: ">"; +} +#pma_console .query_input:before { + top: -2px; +} +#pma_console .query_input textarea { + width: 100%; + height: 4em; + resize: vertical; +} +#pma_console .message:hover:before { + color: #7cf; + font-weight: bold; +} +#pma_console .message.expanded:before { + content: "]"; +} +#pma_console .message.welcome:before { + display: none; +} +#pma_console .message.failed:before, +#pma_console .message.failed.expanded:before, +#pma_console .message.failed:hover:before { + content: "="; + color: #944; +} +#pma_console .message.pending:before { + opacity: .3; +} +#pma_console .message.collapsed>.query { + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; +} +#pma_console .message.expanded>.query { + display: block; + white-space: pre; + word-wrap: break-word; +} +#pma_console .message .text.targetdb, +#pma_console .message.collapsed .action.collapse, +#pma_console .message.expanded .action.expand, +#pma_console .message .action.requery, +#pma_console .message .action.profiling, +#pma_console .message .action.explain, +#pma_console .message .action.bookmark { + display: none; +} +#pma_console .message.select .action.profiling, +#pma_console .message.select .action.explain, +#pma_console .message.history .text.targetdb, +#pma_console .message.successed .text.targetdb, +#pma_console .message.history .action.requery, +#pma_console .message.history .action.bookmark, +#pma_console .message.bookmark .action.requery, +#pma_console .message.bookmark .action.bookmark, +#pma_console .message.successed .action.requery, +#pma_console .message.successed .action.bookmark { + display: inline-block; +} +#pma_console .message .action_content { + position: absolute; + bottom: 100%; + background: #ccc; + border: solid 1px #aaa; + border-top--radius: 3px; +} +html.ie8 #pma_console .message .action_content { + position: relative!important; +} +#pma_console .message.bookmark .text.targetdb, +#pma_console .message .text.query_time { + margin: 0; + display: inline-block; +} +#pma_console .message.failed .text.query_time, +#pma_console .message .text.failed { + display: none; +} +#pma_console .message.failed .text.failed { + display: inline-block; +} +#pma_console .message .text { + background: #fff; +} +#pma_console .message.collapsed>.action_content { + display: none; +} +#pma_console .message.collapsed:hover>.action_content { + display: block; +} +#pma_console .message .bookmark_label { + padding: 0 4px; + top: 0; + background: #369; + color: #fff; + border-radius: 3px; +} +#pma_console .message .bookmark_label.shared { + background: #396; +} +#pma_console .message.expanded .bookmark_label { + border-top-left-radius: 0; + border-top-right-radius: 0; +} +#pma_console .query_input { + position: relative; +} +#pma_console .mid_layer { + height: 100%; + width: 100%; + position: absolute; + top: 0; + /* For support IE8, this layer doesn't use filter:opacity or opacity, + js code will fade this layer opacity to 0.18(using animation) */ + background: #666; + display: none; + cursor: pointer; + z-index: 200; +} +#pma_console .card { + position: absolute; + width: 94%; + height: 100%; + min-height: 48px; + : 100%; + top: 0; + border-: solid 1px #999; + z-index: 300; + transition: 0.2s; + -ms-transition: 0.2s; + -webkit-transition: 0.2s; + -moz-transition: 0.2s; +} +#pma_console .card.show { + : 6%; + box-shadow: -2px 1px 4px -1px #999; +} + +html.ie7 #pma_console .query_input { + display: none; +} + +#pma_bookmarks .content.add_bookmark, +#pma_console_options .content { + padding: 4px 6px; +} +#pma_bookmarks .content.add_bookmark .options { + margin-: 1.4em; + padding-bottom: .4em; + margin-bottom: .4em; + border-bottom: solid 1px #ccc; +} +#pma_bookmarks .content.add_bookmark .options button { + margin: 0 7px; + vertical-align: bottom; +} +#pma_bookmarks .content.add_bookmark input[type=text] { + margin: 0; + padding: 2px 4px; +} +#pma_console .button.hide, +#pma_console .message span.text.hide { + display: none; +} +#debug_console.grouped .ungroup_queries, +#debug_console.ungrouped .group_queries { + display: inline-block; +} +#debug_console.ungrouped .ungroup_queries, +#debug_console.ungrouped .sort_count, +#debug_console.grouped .group_queries { + display: none; +} +#debug_console .count { + margin-right: 8px; +} +#debug_console .show_trace .trace, +#debug_console .show_args .args { + display: block; +} +#debug_console .hide_trace .trace, +#debug_console .hide_args .args, +#debug_console .show_trace .action.dbg_show_trace, +#debug_console .hide_trace .action.dbg_hide_trace, +#debug_console .traceStep.hide_args .action.dbg_hide_args, +#debug_console .traceStep.show_args .action.dbg_show_args { + display: none; +} + +#debug_console .traceStep:after, +#debug_console .trace.welcome:after, +#debug_console .debug>.welcome:after { + content: ""; + display: table; + clear: both; +} +#debug_console .debug_summary { + float: left; +} +#debug_console .trace.welcome .time { + float: right; +} +#debug_console .traceStep .file, +#debug_console .script_name { + float: right; +} +#debug_console .traceStep .args pre { + margin: 0; +} + +/* Code mirror console style*/ + +.cm-s-pma .CodeMirror-code pre, +.cm-s-pma .CodeMirror-code { + font-family: Monaco, Consolas, monospace; +} +.cm-s-pma .CodeMirror-measure>pre, +.cm-s-pma .CodeMirror-code>pre, +.cm-s-pma .CodeMirror-lines { + padding: 0; +} +.cm-s-pma.CodeMirror { + resize: none; + height: auto; + width: 100%; + min-height: initial; + max-height: initial; +} +.cm-s-pma .CodeMirror-scroll { + cursor: text; +} + +/* PMA drop-improt style */ + +.pma_drop_handler { + display: none; + position: fixed; + top: 0; + left: 0; + width: 100%; + background: rgba(0, 0, 0, 0.6); + height: 100%; + z-index: 999; + color: white; + font-size: 30pt; + text-align: center; + padding-top: 20%; +} + +.pma_sql_import_status { + display: none; + position: fixed; + bottom: 0; + right: 25px; + width: 400px; + border: 1px solid #999; + background: #f3f3f3; + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + border-radius: 4px; + -moz-box-shadow: 2px 2px 5px #ccc; + -webkit-box-shadow: 2px 2px 5px #ccc; + box-shadow: 2px 2px 5px #ccc; +} + +.pma_sql_import_status h2, +.pma_drop_result h2 { + background-color: #bbb; + padding: .1em .3em; + margin-top: 0; + margin-bottom: 0; + color: #fff; + font-size: 1.6em; + font-weight: normal; + text-shadow: 0 1px 0 #777; + -moz-box-shadow: 1px 1px 15px #999 inset; + -webkit-box-shadow: 1px 1px 15px #999 inset; + box-shadow: 1px 1px 15px #999 inset; +} + +.pma_sql_import_status div { + height: 270px; + overflow-y:auto; + overflow-x:hidden; + list-style-type: none; +} + +.pma_sql_import_status div li { + padding: 8px 10px; + border-bottom: 1px solid #bbb; + color: rgb(148, 14, 14); + background: white; +} + +.pma_sql_import_status div li .filesize { + float: right; +} + +.pma_sql_import_status h2 .minimize { + float: right; + margin-right: 5px; + padding: 0 10px; +} + +.pma_sql_import_status h2 .close { + float: right; + margin-right: 5px; + padding: 0 10px; + display: none; +} + +.pma_sql_import_status h2 .minimize:hover, +.pma_sql_import_status h2 .close:hover, +.pma_drop_result h2 .close:hover { + background: rgba(155, 149, 149, 0.78); + cursor: pointer; +} + +.pma_drop_file_status { + color: #235a81; +} + +.pma_drop_file_status span.underline:hover { + cursor: pointer; + text-decoration: underline; +} + +.pma_drop_result { + position: fixed; + top: 10%; + left: 20%; + width: 60%; + background: white; + min-height: 300px; + z-index: 800; + -webkit-box-shadow: 0 0 15px #999; + border-radius: 10px; + cursor: move; +} + +.pma_drop_result h2 .close { + float: right; + margin-right: 5px; + padding: 0 10px; +} + +.dependencies_box { + background-color: white; + border: 3px ridge black; +} + +#composite_index_list { + list-style-type: none; + list-style-position: inside; +} + +span.drag_icon { + display: inline-block; + background-image: url('getImgPath('s_sortable.png');?>'); + background-position: center center; + background-repeat: no-repeat; + width: 1em; + height: 3em; + cursor: move; +} + +.topmargin { + margin-top: 1em; +} + +meter[value="1"]::-webkit-meter-optimum-value { + background: linear-gradient(white 3%, #E32929 5%, transparent 10%, #E32929); +} +meter[value="2"]::-webkit-meter-optimum-value { + background: linear-gradient(white 3%, #FF6600 5%, transparent 10%, #FF6600); +} +meter[value="3"]::-webkit-meter-optimum-value { + background: linear-gradient(white 3%, #FFD700 5%, transparent 10%, #FFD700); +} + +/* styles for sortable tables created with tablesorter jquery plugin */ +th.header { + cursor: pointer; + color: #235a81; +} + +th.header:hover { + text-decoration: underline; +} + +th.header .sorticon { + width: 16px; + height: 16px; + background-repeat: no-repeat; + background-position: right center; + display: inline-table; + vertical-align: middle; + float: right; +} + +th.headerSortUp .sorticon, th.headerSortDown:hover .sorticon { + background-image: url(getImgPath('s_desc.png');?>); +} + +th.headerSortDown .sorticon, th.headerSortUp:hover .sorticon { + background-image: url(getImgPath('s_asc.png');?>); +} +/* end of styles of sortable tables */ + +/* styles for jQuery-ui to support rtl languages */ +body .ui-dialog .ui-dialog-titlebar-close { + : .3em; + : initial; +} + +body .ui-dialog .ui-dialog-title { + float: ; +} + +body .ui-dialog .ui-dialog-buttonpane .ui-dialog-buttonset { + float: ; +} +/* end of styles for jQuery-ui to support rtl languages */ + +@media only screen and (max-width: 768px) { + /* For mobile phones: */ + #main_pane_left { + width: 100%; + } + + #main_pane_right { + padding-top: 0; + padding-: 1px; + padding-: 1px; + } + + ul#topmenu, + ul.tabs { + display: flex; + } + + .navigationbar { + display: inline-flex; + margin: 0 !important; + border-radius: 0 !important; + overflow: auto; + } + + .scrollindicator { + padding: 5px; + cursor: pointer; + display: inline; + } + + .responsivetable { + overflow-x: auto; + } + + body#loginform div.container { + width: 100%; + } + + .largescreenonly { + display: none; + } + + .width100, .desktop50 { + width: 100%; + } + + .width96 { + width: 96% !important; + } + + #page_nav_icons { + display: none; + } + + table#serverstatusconnections { + margin-left: 0; + } + + #table_name_col_no { + top: 62px + } + + .tdblock tr td { + display: block; + } + + #table_columns { + margin-top: 60px; + } + + #table_columns .tablesorter { + min-width: 100%; + } + + .doubleFieldset fieldset { + width: 98%; + } + + div#serverstatusquerieschart { + width: 100%; + height: 450px; + } + + .ui-dialog { + margin: 1%; + width: 95% !important; + } +} +/* templates/database/designer */ +/* side menu */ +#name-panel { + overflow:hidden; +} diff --git a/php/apps/phpmyadmin49/themes/pmahomme/css/designer.css.php b/php/apps/phpmyadmin49/themes/pmahomme/css/designer.css.php new file mode 100644 index 00000000..d407ad14 --- /dev/null +++ b/php/apps/phpmyadmin49/themes/pmahomme/css/designer.css.php @@ -0,0 +1,515 @@ +getImgPath('designer/Header.png'); +$headerLinkedImg = $theme->getImgPath('designer/Header_Linked.png'); +$minusImg = $theme->getImgPath('designer/minus.png'); +$plusImg = $theme->getImgPath('designer/plus.png'); +$leftPanelButtonImg = $theme->getImgPath('designer/left_panel_butt.png'); +$topPanelImg = $theme->getImgPath('designer/top_panel.png'); +$smallTabImg = $theme->getImgPath('designer/small_tab.png'); +$frams1Img = $theme->getImgPath('designer/1.png'); +$frams2Img = $theme->getImgPath('designer/2.png'); +$frams3Img = $theme->getImgPath('designer/3.png'); +$frams4Img = $theme->getImgPath('designer/4.png'); +$frams5Img = $theme->getImgPath('designer/5.png'); +$frams6Img = $theme->getImgPath('designer/6.png'); +$frams7Img = $theme->getImgPath('designer/7.png'); +$frams8Img = $theme->getImgPath('designer/8.png'); +$resizeImg = $theme->getImgPath('designer/resize.png'); +?> + +/* Designer */ +.input_tab { + background-color: #A6C7E1; + color: #000; +} + +.content_fullscreen { + position: relative; + overflow: auto; +} + +#canvas_outer { + position: relative; + width: 100%; + display: block; +} + +#canvas { + background-color: #fff; + color: #000; +} + +canvas.designer { + display: inline-block; + overflow: hidden; + text-align: left; +} + +canvas.designer * { + behavior: url(#default#VML); +} + +.designer_tab { + background-color: #fff; + color: #000; + border-collapse: collapse; + border: 1px solid #aaa; + z-index: 1; + -moz-user-select: none; +} + +.designer_tab .header { + background-image: url(); + background-repeat: repeat-x; +} + +.tab_zag { + text-align: center; + cursor: move; + padding: 1px; + font-weight: bold; +} + +.tab_zag_2 { + background-image: url(); + background-repeat: repeat-x; + text-align: center; + cursor: move; + padding: 1px; + font-weight: bold; +} + +.tab_field { + background: #fff; + color: #000; + cursor: default; +} + +.tab_field:hover, .tab_field_3:hover { + background-color: #CCFFCC; + color: #000; + background-repeat: repeat-x; + cursor: default; +} + +.tab_field_3 { + background-color: #FFE6E6; /*#DDEEFF*/ + color: #000; + cursor: default; +} + +#designer_hint { + white-space: nowrap; + position: absolute; + background-color: #99FF99; + color: #000; + z-index: 3; + border: #00CC66 solid 1px; + display: none; +} + +.scroll_tab { + overflow: auto; + width: 100%; + height: 500px; +} + +.designer_Tabs { + cursor: default; + color: #0055bb; + white-space: nowrap; + text-decoration: none; + text-indent: 3px; + font-weight: bold; + margin-left: 2px; + text-align: ; + background-color: #fff; + background-image: url(); + border: #ccc solid 1px; +} + +.designer_Tabs:hover { + cursor: default; + color: #0055bb; + background: #FFEE99; + text-indent: 3px; + font-weight: bold; + white-space: nowrap; + text-decoration: none; + border: #9999FF solid 1px; + text-align: ; +} + +.owner { + font-weight: normal; + color: #888; +} + +.option_tab { + padding-left: 2px; + padding-right: 2px; + width: 5px; +} + +.select_all { + vertical-align: top; + padding-left: 2px; + padding-right: 2px; + cursor: default; + width: 1px; + color: #000; + background-image: url(); + background-repeat: repeat-x; +} + +.small_tab { + vertical-align: top; + background-color: #0064ea; + color: #fff; + background-image: url(); + cursor: default; + text-align: center; + font-weight: bold; + padding-left: 2px; + padding-right: 2px; + width: 1px; + text-decoration: none; +} + +.small_tab:hover { + vertical-align: top; + color: #fff; + background-color: #FF9966; + cursor: default; + padding-left: 2px; + padding-right: 2px; + text-align: center; + font-weight: bold; + width: 1px; + text-decoration: none; +} + +.small_tab_pref { + background-image: url(); + background-repeat: repeat-x; + text-align: center; + width: 1px; +} + +.small_tab_pref:hover { + vertical-align: top; + color: #fff; + background-color: #FF9966; + cursor: default; + text-align: center; + font-weight: bold; + width: 1px; + text-decoration: none; +} + +.butt { + border: #4477aa solid 1px; + font-weight: bold; + height: 19px; + width: 70px; + background-color: #fff; + color: #000; + vertical-align: baseline; +} + +.L_butt2_1 { + padding: 1px; + text-decoration: none; + vertical-align: middle; + cursor: default; +} + +.L_butt2_1:hover { + padding: 0; + border: #0099CC solid 1px; + background: #FFEE99; + color: #000; + text-decoration: none; + vertical-align: middle; + cursor: default; +} + +/* ---------------------------------------------------------------------------*/ +.bor { + width: 10px; + height: 10px; +} + +.frams1 { + background: url() no-repeat right bottom; +} + +.frams2 { + background: url() no-repeat left bottom; +} + +.frams3 { + background: url() no-repeat left top; +} + +.frams4 { + background: url() no-repeat right top; +} + +.frams5 { + background: url() repeat-x center bottom; +} + +.frams6 { + background: url() repeat-y left; +} + +.frams7 { + background: url() repeat-x top; +} + +.frams8 { + background: url() repeat-y right; +} + +#osn_tab { + position: absolute; + background-color: #fff; + color: #000; +} + +.designer_header { + background-color: #EAEEF0; + color: #000; + text-align: center; + font-weight: bold; + margin: 0; + padding: 0; + background-image: url(); + background-position: top; + background-repeat: repeat-x; + border-right: #999 solid 1px; + border-left: #999 solid 1px; + height: 28px; + z-index: 101; + width: 100%; + position: fixed; +} + +.designer_header a, .designer_header span{ + display: block; + float: ; + margin: 3px 1px 4px; + height: 20px; + border: 1px dotted #fff; +} + +.designer_header .M_bord { + display: block; + float: ; + margin: 4px; + height: 20px; + width: 2px; +} + +.designer_header a.first { + margin-right: 1em; +} + +.designer_header a.last { + margin-left: 1em; +} + +a.M_butt_Selected_down_IE, +a.M_butt_Selected_down { + border: 1px solid #C0C0BB; + background-color: #99FF99; + color: #000; +} + +a.M_butt_Selected_down_IE:hover, +a.M_butt_Selected_down:hover, +a.M_butt:hover { + border: 1px solid #0099CC; + background-color: #FFEE99; + color: #000; +} + +#layer_menu { + z-index: 98; + position: relative; + float: right; + background-color: #EAEEF0; + border: #999 solid 1px; +} + +#layer_menu.left { + float: left; +} + +#layer_upd_relation { + position: absolute; + : 637px; + top: 224px; + z-index: 100; +} + +#layer_new_relation { + position: absolute; + : 636px; + top: 85px; + z-index: 100; + width: 153px; +} + +#designer_optionse { + position: absolute; + : 636px; + top: 85px; + z-index: 100; + width: 153px; +} + +#layer_menu_sizer { + background-image: url(); + cursor: ew-resize; +} + +#layer_menu_sizer .icon { + margin: 0; +} + +.panel { + position: fixed; + top: 60px; + : 0; + width: 350px; + max-height: 500px; + display: none; + overflow: auto; + padding-top: 34px; + z-index: 102; +} + +a.trigger { + position: fixed; + text-decoration: none; + top: 60px; + : 0; + color: #fff; + padding: 10px 40px 10px 15px; + background: #333 url() 85% 55% no-repeat; + border: 1px solid #444; + display: block; + z-index: 102; +} + +a.trigger:hover { + color: #080808; + background: #fff696 url() 85% 55% no-repeat; + border: 1px solid #999; +} + +a.active.trigger { + background: #222 url() 85% 55% no-repeat; + z-index: 999; +} + +a.active.trigger:hover { + background: #fff696 url() 85% 55% no-repeat; +} + +.toggle_container .block { + background-color: #DBE4E8; + border-top: 1px solid #999; +} + +.history_table { + text-align: center; + cursor: pointer; + background-color: #DBE4E8; +} + +.history_table:hover { + background-color: #9999CC; +} + +#ab { + min-width: 300px; +} + +#ab .ui-accordion-content { + padding: 0; +} + +#box { + display: none; +} + +#foreignkeychk { + text-align: ; + position: absolute; + cursor: pointer; +} + +.side-menu { + float: left; + position: fixed; + width: auto; + height: auto; + background: #efefef; + border: 1px solid grey; + overflow: hidden; + z-index: 50; + padding: 2px; +} + +.side-menu.right { + float: right; + right: 0; +} + +.side-menu .hide { + display: none; +} + +.side-menu a { + display: block; + float: none; + overflow: hidden; +} + +.side-menu img, +.side-menu .text { + float: left; +} + +#name-panel { + border-bottom: 1px solid grey; + text-align: center; + background: #efefef; + width: 100%; + font-size: 1.2em; + padding: 10px; + font-weight: bold; +} + +#container-form { + width: 100%; + position: absolute; + left: 0; +} diff --git a/php/apps/phpmyadmin49/themes/pmahomme/css/enum_editor.css.php b/php/apps/phpmyadmin49/themes/pmahomme/css/enum_editor.css.php new file mode 100644 index 00000000..60429533 --- /dev/null +++ b/php/apps/phpmyadmin49/themes/pmahomme/css/enum_editor.css.php @@ -0,0 +1,80 @@ + + +/** + * ENUM/SET editor styles + */ +p.enum_notice { + margin: 5px 2px; + font-size: 80%; +} + +#enum_editor p { + margin-top: 0; + font-style: italic; +} + +#enum_editor .values, +#enum_editor .add { + width: 100%; +} + +#enum_editor .add td { + vertical-align: middle; + width: 50%; + padding: 0 0 0; + padding-: 1em; +} + +#enum_editor .values td.drop { + width: 1.8em; + cursor: pointer; + vertical-align: middle; +} + +#enum_editor .values input { + margin: .1em 0; + padding-: 2em; + width: 100%; +} + +#enum_editor .values img { + width: 1.8em; + vertical-align: middle; +} + +#enum_editor input.add_value { + margin: 0; + margin-: 0.4em; +} + +#enum_editor_output textarea { + width: 100%; + float: ; + margin: 1em 0 0 0; +} + +/** + * ENUM/SET editor integration for the routines editor + */ +.enum_hint { + position: relative; +} + +.enum_hint a { + position: absolute; + : 81%; + bottom: .35em; +} diff --git a/php/apps/phpmyadmin49/themes/pmahomme/css/gis.css.php b/php/apps/phpmyadmin49/themes/pmahomme/css/gis.css.php new file mode 100644 index 00000000..5f7979a7 --- /dev/null +++ b/php/apps/phpmyadmin49/themes/pmahomme/css/gis.css.php @@ -0,0 +1,52 @@ + + +/** + * GIS data editor styles + */ +a.close_gis_editor { + float: ; +} + +#gis_editor { + display: none; + position: fixed; + _position: absolute; /* hack for IE */ + z-index: 1001; + overflow-y: auto; + overflow-x: hidden; +} + +#gis_data { + min-height: 230px; +} + +#gis_data_textarea { + height: 6em; +} + +#gis_data_editor { + background: #D0DCE0; + padding: 15px; + min-height: 500px; +} + +#gis_data_editor .choice { + display: none; +} + +#gis_data_editor input[type="text"] { + width: 75px; +} diff --git a/php/apps/phpmyadmin49/themes/pmahomme/css/icons.css.php b/php/apps/phpmyadmin49/themes/pmahomme/css/icons.css.php new file mode 100644 index 00000000..352ca563 --- /dev/null +++ b/php/apps/phpmyadmin49/themes/pmahomme/css/icons.css.php @@ -0,0 +1,194 @@ +getImgPath('designer/left_panel_butt.png'); + +// unplanned execution path +if (! defined('PHPMYADMIN') && ! defined('TESTSUITE')) { + exit(); +} +?> +.icon { + margin: 0; + margin-left: .3em; + padding: 0 !important; + width: 16px; + height: 16px; +} +.icon_fulltext { + width: 50px; + height: 19px; +} +.ic_asc_order { background-image: url('getImgPath('asc_order.png'); ?>'); } +.ic_b_bookmark { background-image: url('getImgPath('b_bookmark.png'); ?>'); } +.ic_b_browse { background-image: url('getImgPath('b_browse.png'); ?>'); } +.ic_b_calendar { background-image: url('getImgPath('b_calendar.png'); ?>'); } +.ic_b_chart { background-image: url('getImgPath('b_chart.png'); ?>'); } +.ic_b_close { background-image: url('getImgPath('b_close.png'); ?>'); } +.ic_b_column_add { background-image: url('getImgPath('b_column_add.png'); ?>'); } +.ic_b_comment { background-image: url('getImgPath('b_comment.png'); ?>'); } +.ic_b_dbstatistics { background-image: url('getImgPath('b_dbstatistics.png'); ?>'); } +.ic_b_deltbl { background-image: url('getImgPath('b_deltbl.png'); ?>'); } +.ic_b_docs { background-image: url('getImgPath('b_docs.png'); ?>'); } +.ic_b_docsql { background-image: url('getImgPath('b_docsql.png'); ?>'); } +.ic_b_drop { background-image: url('getImgPath('b_drop.png'); ?>'); } +.ic_b_edit { background-image: url('getImgPath('b_edit.png'); ?>'); } +.ic_b_empty { background-image: url('getImgPath('b_empty.png'); ?>'); } +.ic_b_engine { background-image: url('getImgPath('b_engine.png'); ?>'); } +.ic_b_event_add { background-image: url('getImgPath('b_event_add.png'); ?>'); } +.ic_b_events { background-image: url('getImgPath('b_events.png'); ?>'); } +.ic_b_export { background-image: url('getImgPath('b_export.png'); ?>'); } +.ic_b_favorite { background-image: url('getImgPath('b_favorite.png'); ?>'); } +.ic_b_find_replace { background-image: url('getImgPath('b_find_replace.png'); ?>'); } +.ic_b_firstpage { background-image: url('getImgPath('b_firstpage.png'); ?>'); } +.ic_b_ftext { background-image: url('getImgPath('b_ftext.png'); ?>'); } +.ic_b_globe { background-image: url('getImgPath('b_globe.gif'); ?>'); } +.ic_b_group { background-image: url('getImgPath('b_group.png'); ?>'); } +.ic_b_help { background-image: url('getImgPath('b_help.png'); ?>'); } +.ic_b_home { background-image: url('getImgPath('b_home.png'); ?>'); } +.ic_b_import { background-image: url('getImgPath('b_import.png'); ?>'); } +.ic_b_index { background-image: url('getImgPath('b_index.png'); ?>'); } +.ic_b_index_add { background-image: url('getImgPath('b_index_add.png'); ?>'); } +.ic_b_info { background-image: url('getImgPath('b_info.png'); ?>'); width: 11px; height: 11px; } +.ic_b_inline_edit { background-image: url('getImgPath('b_inline_edit.png'); ?>'); } +.ic_b_insrow { background-image: url('getImgPath('b_insrow.png'); ?>'); } +.ic_b_lastpage { background-image: url('getImgPath('b_lastpage.png'); ?>'); } +.ic_b_minus { background-image: url('getImgPath('b_minus.png'); ?>'); } +.ic_b_more { background-image: url('getImgPath('b_more.png'); ?>'); } +.ic_b_move { background-image: url('getImgPath('b_move.png'); ?>'); } +.ic_b_newdb { background-image: url('getImgPath('b_newdb.png'); ?>'); } +.ic_b_newtbl { background-image: url('getImgPath('b_newtbl.png'); ?>'); } +.ic_b_nextpage { background-image: url('getImgPath('b_nextpage.png'); ?>'); } +.ic_b_no_favorite { background-image: url('getImgPath('b_no_favorite.png'); ?>'); } +.ic_b_pdfdoc { background-image: url('getImgPath('b_pdfdoc.png'); ?>'); } +.ic_b_plugin { background-image: url('getImgPath('b_plugin.png'); ?>'); } +.ic_b_plus { background-image: url('getImgPath('b_plus.png'); ?>'); } +.ic_b_prevpage { background-image: url('getImgPath('b_prevpage.png'); ?>'); } +.ic_b_primary { background-image: url('getImgPath('b_primary.png'); ?>'); } +.ic_b_print { background-image: url('getImgPath('b_print.png'); ?>'); } +.ic_b_props { background-image: url('getImgPath('b_props.png'); ?>'); } +.ic_b_relations { background-image: url('getImgPath('b_relations.png'); ?>'); } +.ic_b_report { background-image: url('getImgPath('b_report.png'); ?>'); } +.ic_b_routine_add { background-image: url('getImgPath('b_routine_add.png'); ?>'); } +.ic_b_routines { background-image: url('getImgPath('b_routines.png'); ?>'); } +.ic_b_save { background-image: url('getImgPath('b_save.png'); ?>'); } +.ic_b_saveimage { background-image: url('getImgPath('b_saveimage.png'); ?>'); } +.ic_b_sbrowse { background-image: url('getImgPath('b_sbrowse.png'); ?>'); } +.ic_b_sdb { background-image: url('getImgPath('b_sdb.png'); ?>'); width: 10px; height: 10px; } +.ic_b_search { background-image: url('getImgPath('b_search.png'); ?>'); } +.ic_b_select { background-image: url('getImgPath('b_select.png'); ?>'); } +.ic_b_snewtbl { background-image: url('getImgPath('b_snewtbl.png'); ?>'); } +.ic_b_spatial { background-image: url('getImgPath('b_spatial.png'); ?>'); } +.ic_b_sql { background-image: url('getImgPath('b_sql.png'); ?>'); } +.ic_b_sqldoc { background-image: url('getImgPath('b_sqldoc.png'); ?>'); } +.ic_b_sqlhelp { background-image: url('getImgPath('b_sqlhelp.png'); ?>'); } +.ic_b_table_add { background-image: url('getImgPath('b_table_add.png'); ?>'); } +.ic_b_tblanalyse { background-image: url('getImgPath('b_tblanalyse.png'); ?>'); } +.ic_b_tblexport { background-image: url('getImgPath('b_tblexport.png'); ?>'); } +.ic_b_tblimport { background-image: url('getImgPath('b_tblimport.png'); ?>'); } +.ic_b_tblops { background-image: url('getImgPath('b_tblops.png'); ?>'); } +.ic_b_tbloptimize { background-image: url('getImgPath('b_tbloptimize.png'); ?>'); } +.ic_b_tipp { background-image: url('getImgPath('b_tipp.png'); ?>'); } +.ic_b_trigger_add { background-image: url('getImgPath('b_trigger_add.png'); ?>'); } +.ic_b_triggers { background-image: url('getImgPath('b_triggers.png'); ?>'); } +.ic_b_undo { background-image: url('getImgPath('b_undo.png'); ?>'); } +.ic_b_unique { background-image: url('getImgPath('b_unique.png'); ?>'); } +.ic_b_usradd { background-image: url('getImgPath('b_usradd.png'); ?>'); } +.ic_b_usrcheck { background-image: url('getImgPath('b_usrcheck.png'); ?>'); } +.ic_b_usrdrop { background-image: url('getImgPath('b_usrdrop.png'); ?>'); } +.ic_b_usredit { background-image: url('getImgPath('b_usredit.png'); ?>'); } +.ic_b_usrlist { background-image: url('getImgPath('b_usrlist.png'); ?>'); } +.ic_b_versions { background-image: url('getImgPath('b_versions.png'); ?>'); } +.ic_b_view { background-image: url('getImgPath('b_view.png'); ?>'); } +.ic_b_view_add { background-image: url('getImgPath('b_view_add.png'); ?>'); } +.ic_b_views { background-image: url('getImgPath('b_views.png'); ?>'); } +.ic_b_left { background-image: url('getImgPath('b_left.png'); ?>'); } +.ic_b_right { background-image: url('getImgPath('b_right.png'); ?>'); } +.ic_bd_browse { background-image: url('getImgPath('bd_browse.png'); ?>'); } +.ic_bd_deltbl { background-image: url('getImgPath('bd_deltbl.png'); ?>'); } +.ic_bd_drop { background-image: url('getImgPath('bd_drop.png'); ?>'); } +.ic_bd_edit { background-image: url('getImgPath('bd_edit.png'); ?>'); } +.ic_bd_empty { background-image: url('getImgPath('bd_empty.png'); ?>'); } +.ic_bd_export { background-image: url('getImgPath('bd_export.png'); ?>'); } +.ic_bd_firstpage { background-image: url('getImgPath('bd_firstpage.png'); ?>'); } +.ic_bd_ftext { background-image: url('getImgPath('bd_ftext.png'); ?>'); } +.ic_bd_index { background-image: url('getImgPath('bd_index.png'); ?>'); } +.ic_bd_insrow { background-image: url('getImgPath('bd_insrow.png'); ?>'); } +.ic_bd_lastpage { background-image: url('getImgPath('bd_lastpage.png'); ?>'); } +.ic_bd_nextpage { background-image: url('getImgPath('bd_nextpage.png'); ?>'); } +.ic_bd_prevpage { background-image: url('getImgPath('bd_prevpage.png'); ?>'); } +.ic_bd_primary { background-image: url('getImgPath('bd_primary.png'); ?>'); } +.ic_bd_routine_add { background-image: url('getImgPath('bd_routine_add.png'); ?>'); } +.ic_bd_sbrowse { background-image: url('getImgPath('bd_sbrowse.png'); ?>'); } +.ic_bd_select { background-image: url('getImgPath('bd_select.png'); ?>'); } +.ic_bd_spatial { background-image: url('getImgPath('bd_spatial.png'); ?>'); } +.ic_bd_unique { background-image: url('getImgPath('bd_unique.png'); ?>'); } +.ic_centralColumns { background-image: url('getImgPath('centralColumns.png'); ?>'); } +.ic_centralColumns_add { background-image: url('getImgPath('centralColumns_add.png'); ?>'); } +.ic_centralColumns_delete { background-image: url('getImgPath('centralColumns_delete.png'); ?>'); } +.ic_col_drop { background-image: url('getImgPath('col_drop.png'); ?>'); } +.ic_console { background-image: url('getImgPath('console.png'); ?>'); } +.ic_database { background-image: url('getImgPath('database.png'); ?>'); } +.ic_eye { background-image: url('getImgPath('eye.png'); ?>'); } +.ic_eye_grey { background-image: url('getImgPath('eye_grey.png'); ?>'); } +.ic_hide { background-image: url('getImgPath('hide.png'); ?>'); } +.ic_item { background-image: url('getImgPath('item.png'); ?>'); width: 9px; height: 9px; } +.ic_lightbulb { background-image: url('getImgPath('lightbulb.png'); ?>'); } +.ic_lightbulb_off { background-image: url('getImgPath('lightbulb_off.png'); ?>'); } +.ic_more { background-image: url('getImgPath('more.png'); ?>'); width: 13px; } +.ic_new_data { background-image: url('getImgPath('new_data.png'); ?>'); } +.ic_new_data_hovered { background-image: url('getImgPath('new_data_hovered.png'); ?>'); } +.ic_new_data_selected { background-image: url('getImgPath('new_data_selected.png'); ?>'); } +.ic_new_data_selected_hovered { background-image: url('getImgPath('new_data_selected_hovered.png'); ?>'); } +.ic_new_struct { background-image: url('getImgPath('new_struct.png'); ?>'); } +.ic_new_struct_hovered { background-image: url('getImgPath('new_struct_hovered.png'); ?>'); } +.ic_new_struct_selected { background-image: url('getImgPath('new_struct_selected.png'); ?>'); } +.ic_new_struct_selected_hovered { background-image: url('getImgPath('new_struct_selected_hovered.png'); ?>'); } +.ic_normalize { background-image: url('getImgPath('normalize.png'); ?>'); } +.ic_pause { background-image: url('getImgPath('pause.png'); ?>'); } +.ic_php_sym { background-image: url('getImgPath('php_sym.png'); ?>'); } +.ic_play { background-image: url('getImgPath('play.png'); ?>'); } +.ic_s_asc { background-image: url('getImgPath('s_asc.png'); ?>'); } +.ic_s_asci { background-image: url('getImgPath('s_asci.png'); ?>'); } +.ic_s_attention { background-image: url('getImgPath('s_attention.png'); ?>'); } +.ic_s_cancel { background-image: url('getImgPath('s_cancel.png'); ?>'); } +.ic_s_cancel2 { background-image: url('getImgPath('s_cancel2.png'); ?>'); } +.ic_s_cog { background-image: url('getImgPath('s_cog.png'); ?>'); } +.ic_s_db { background-image: url('getImgPath('s_db.png'); ?>'); } +.ic_s_desc { background-image: url('getImgPath('s_desc.png'); ?>'); } +.ic_s_error { background-image: url('getImgPath('s_error.png'); ?>'); } +.ic_s_host { background-image: url('getImgPath('s_host.png'); ?>'); } +.ic_s_info { background-image: url('getImgPath('s_info.png'); ?>'); } +.ic_s_lang { background-image: url('getImgPath('s_lang.png'); ?>'); } +.ic_s_link { background-image: url('getImgPath('s_link.png'); ?>'); } +.ic_s_lock { background-image: url('getImgPath('s_lock.png'); ?>'); } +.ic_s_loggoff { background-image: url('getImgPath('s_loggoff.png'); ?>'); } +.ic_s_notice { background-image: url('getImgPath('s_notice.png'); ?>'); } +.ic_s_okay { background-image: url('getImgPath('s_okay.png'); ?>'); } +.ic_s_passwd { background-image: url('getImgPath('s_passwd.png'); ?>'); } +.ic_s_process { background-image: url('getImgPath('s_process.png'); ?>'); } +.ic_s_really { background-image: url('getImgPath('s_really.png'); ?>'); width: 11px; height: 11px; } +.ic_s_reload { background-image: url('getImgPath('s_reload.png'); ?>'); } +.ic_s_replication { background-image: url('getImgPath('s_replication.png'); ?>'); } +.ic_s_rights { background-image: url('getImgPath('s_rights.png'); ?>'); } +.ic_s_sortable { background-image: url('getImgPath('s_sortable.png'); ?>'); } +.ic_s_status { background-image: url('getImgPath('s_status.png'); ?>'); } +.ic_s_success { background-image: url('getImgPath('s_success.png'); ?>'); } +.ic_s_sync { background-image: url('getImgPath('s_sync.png'); ?>'); } +.ic_s_tbl { background-image: url('getImgPath('s_tbl.png'); ?>'); } +.ic_s_theme { background-image: url('getImgPath('s_theme.png'); ?>'); } +.ic_s_top { background-image: url('getImgPath('s_top.png'); ?>'); } +.ic_s_unlink { background-image: url('getImgPath('s_unlink.png'); ?>'); } +.ic_s_vars { background-image: url('getImgPath('s_vars.png'); ?>'); } +.ic_s_views { background-image: url('getImgPath('s_views.png'); ?>'); } +.ic_show { background-image: url('getImgPath('show.png'); ?>'); } +.ic_window-new { background-image: url('getImgPath('window-new.png'); ?>'); } +.ic_ajax_clock_small { background-image: url('getImgPath('ajax_clock_small.gif'); ?>'); } +.ic_s_partialtext { background-image: url('getImgPath('s_partialtext.png'); ?>'); } +.ic_s_fulltext { background-image: url('getImgPath('s_fulltext.png'); ?>'); } diff --git a/php/apps/phpmyadmin49/themes/pmahomme/css/jqplot.css.php b/php/apps/phpmyadmin49/themes/pmahomme/css/jqplot.css.php new file mode 100644 index 00000000..1c5c5ac9 --- /dev/null +++ b/php/apps/phpmyadmin49/themes/pmahomme/css/jqplot.css.php @@ -0,0 +1,273 @@ + + +/* jqPlot */ + +/*rules for the plot target div. These will be cascaded down to all plot elements according to css rules*/ +.jqplot-target { + position: relative; + color: #222222; + font-family: "Trebuchet MS", Arial, Helvetica, sans-serif; + font-size: 1em; +/* height: 300px; + width: 590px;*/ +} + +/*rules applied to all axes*/ +.jqplot-axis { + font-size: 0.75em; +} + +.jqplot-xaxis { + margin-top: 10px; +} + +.jqplot-x2axis { + margin-bottom: 10px; +} + +.jqplot-yaxis { + margin-: 10px; +} + +.jqplot-y2axis, .jqplot-y3axis, .jqplot-y4axis, .jqplot-y5axis, .jqplot-y6axis, .jqplot-y7axis, .jqplot-y8axis, .jqplot-y9axis, .jqplot-yMidAxis { + margin-left: 10px; + margin-right: 10px; +} + +/*rules applied to all axis tick divs*/ +.jqplot-axis-tick, .jqplot-xaxis-tick, .jqplot-yaxis-tick, .jqplot-x2axis-tick, .jqplot-y2axis-tick, .jqplot-y3axis-tick, .jqplot-y4axis-tick, .jqplot-y5axis-tick, .jqplot-y6axis-tick, .jqplot-y7axis-tick, .jqplot-y8axis-tick, .jqplot-y9axis-tick, .jqplot-yMidAxis-tick { + position: absolute; + white-space: pre; +} + + +.jqplot-xaxis-tick { + top: 0; + /* initial position untill tick is drawn in proper place */ + : 15px; + vertical-align: top; +} + +.jqplot-x2axis-tick { + bottom: 0; + /* initial position untill tick is drawn in proper place */ + : 15px; + vertical-align: bottom; +} + +.jqplot-yaxis-tick { + : 0px; + /* initial position untill tick is drawn in proper place */ + top: 15px; + text-align: ; +} + +.jqplot-yaxis-tick.jqplot-breakTick { + : -20px; + margin-: 0; + padding:1px 5px 1px; + z-index: 2; + font-size: 1.5em; +} + +.jqplot-y2axis-tick, .jqplot-y3axis-tick, .jqplot-y4axis-tick, .jqplot-y5axis-tick, .jqplot-y6axis-tick, .jqplot-y7axis-tick, .jqplot-y8axis-tick, .jqplot-y9axis-tick { + : 0px; + /* initial position untill tick is drawn in proper place */ + top: 15px; +/* padding-left: 10px;*/ +/* padding-right: 15px;*/ + text-align: ; +} + +.jqplot-yMidAxis-tick { + text-align: center; + white-space: nowrap; +} + +.jqplot-xaxis-label { + margin-top: 10px; + font-size: 11pt; + position: absolute; +} + +.jqplot-x2axis-label { + margin-bottom: 10px; + font-size: 11pt; + position: absolute; +} + +.jqplot-yaxis-label { + margin-right: 10px; +/* text-align: center;*/ + font-size: 11pt; + position: absolute; +} + +.jqplot-yMidAxis-label { + font-size: 11pt; + position: absolute; +} + +.jqplot-y2axis-label, .jqplot-y3axis-label, .jqplot-y4axis-label, .jqplot-y5axis-label, .jqplot-y6axis-label, .jqplot-y7axis-label, .jqplot-y8axis-label, .jqplot-y9axis-label { +/* text-align: center;*/ + font-size: 11pt; + margin-: 10px; + position: absolute; +} + +.jqplot-meterGauge-tick { + font-size: 0.75em; + color: #999999; +} + +.jqplot-meterGauge-label { + font-size: 1em; + color: #999999; +} + +table.jqplot-table-legend { + margin-top: 12px; + margin-bottom: 12px; + margin-left: 12px; + margin-right: 12px; +} + +table.jqplot-table-legend, table.jqplot-cursor-legend { + background-color: rgba(255,255,255,0.6); + border: 1px solid #cccccc; + position: absolute; + font-size: 0.75em; +} + +td.jqplot-table-legend { + vertical-align: middle; +} + +/* +These rules could be used instead of assigning +element styles and relying on js object properties. +*/ + +/* +td.jqplot-table-legend-swatch { + padding-top: 0.5em; + text-align: center; +} + +tr.jqplot-table-legend:first td.jqplot-table-legend-swatch { + padding-top: 0px; +} +*/ + +td.jqplot-seriesToggle:hover, td.jqplot-seriesToggle:active { + cursor: pointer; +} + +.jqplot-table-legend .jqplot-series-hidden { + text-decoration: line-through; +} + +div.jqplot-table-legend-swatch-outline { + border: 1px solid #cccccc; + padding: 1px; +} + +div.jqplot-table-legend-swatch { + width: 0; + height: 0; + border-top-width: 5px; + border-bottom-width: 5px; + border-left-width: 6px; + border-right-width: 6px; + border-top-style: solid; + border-bottom-style: solid; + border-left-style: solid; + border-right-style: solid; +} + +.jqplot-title { + top: 0; + : 0px; + padding-bottom: 0.5em; + font-size: 1.2em; +} + +table.jqplot-cursor-tooltip { + border: 1px solid #cccccc; + font-size: 0.75em; +} + + +.jqplot-cursor-tooltip { + border: 1px solid #cccccc; + font-size: 0.75em; + white-space: nowrap; + background: rgba(208,208,208,0.5); + padding: 1px; +} + +.jqplot-highlighter-tooltip, .jqplot-canvasOverlay-tooltip { + border: 1px solid #cccccc; + font-size: 0.75em; + white-space: nowrap; + background: rgba(208,208,208,0.5); + padding: 1px; +} + +.jqplot-point-label { + font-size: 0.75em; + z-index: 2; +} + +td.jqplot-cursor-legend-swatch { + vertical-align: middle; + text-align: center; +} + +div.jqplot-cursor-legend-swatch { + width: 1.2em; + height: 0.7em; +} + +.jqplot-error { +/* Styles added to the plot target container when there is an error go here.*/ + text-align: center; +} + +.jqplot-error-message { +/* Styling of the custom error message div goes here.*/ + position: relative; + top: 46%; + display: inline-block; +} + +div.jqplot-bubble-label { + font-size: 0.8em; +/* background: rgba(90%, 90%, 90%, 0.15);*/ + padding-left: 2px; + padding-right: 2px; + color: rgb(20%, 20%, 20%); +} + +div.jqplot-bubble-label.jqplot-bubble-label-highlight { + background: rgba(90%, 90%, 90%, 0.7); +} + +div.jqplot-noData-container { + text-align: center; + background-color: rgba(96%, 96%, 96%, 0.3); +} diff --git a/php/apps/phpmyadmin49/themes/pmahomme/css/navigation.css.php b/php/apps/phpmyadmin49/themes/pmahomme/css/navigation.css.php new file mode 100644 index 00000000..e93c0a12 --- /dev/null +++ b/php/apps/phpmyadmin49/themes/pmahomme/css/navigation.css.php @@ -0,0 +1,429 @@ + + +/******************************************************************************/ +/* Navigation */ + +#pma_navigation { + width: px; + position: fixed; + top: 0; + : 0; + height: 100vh; + background: url(./themes/pmahomme/img/left_nav_bg.png) repeat-y right 0 ; + color: ; + z-index: 800; +} + +#pma_navigation_header { + overflow: hidden; +} + +#pma_navigation_content { + width: 100%; + height: 100%; + position: absolute; + top: 0; + : 0; + z-index: 0; +} + +#pma_navigation ul { + margin: 0; +} + +#pma_navigation form { + margin: 0; + padding: 0; + display: inline; +} + +#pma_navigation select#select_server, +#pma_navigation select#lightm_db { + width: 100%; +} + +/******************************************************************************/ +/* specific elements */ + +#pma_navigation div.pageselector { + text-align: center; + margin: 0; + margin-: 0.75em; + border-: 1px solid #666; +} + +#pma_navigation div#pmalogo { + +} + +#pma_navigation #pmalogo, +#pma_navigation #serverChoice, +#pma_navigation #navipanellinks, +#pma_navigation #recentTableList, +#pma_navigation #favoriteTableList, +#pma_navigation #databaseList, +#pma_navigation div.pageselector.dbselector { + text-align: center; + padding: 5px 10px 0; + border: 0; +} + +#pma_navigation #recentTable, +#pma_navigation #favoriteTable { + width: 200px; +} + +#pma_navigation #favoriteTableList select, +#pma_navigation #serverChoice select + { + width: 80%; +} + +#pma_navigation_content > img.throbber { + display: none; + margin: .3em auto 0; +} + +/* Navigation tree*/ +#pma_navigation_tree { + margin: 0; + margin-: 5px; + overflow: hidden; + color: #444; + height: 74%; + position: relative; +} +#pma_navigation_select_database { + text-align: left; + padding: 0 0 0; + border: 0; + margin: 0; +} + +#pma_navigation_db_select { + margin-top: 0.5em; + margin-: 0.75em; +} +#pma_navigation_db_select select { + background: url("./themes/pmahomme/img/select_bg.png") repeat scroll 0 0; + -webkit-border-radius: 2px; + border-radius: 2px; + border: 1px solid #bbb; + border-top: 1px solid #bbb; + color: #333; + padding: 4px 6px; + margin: 0 0 0; + width: 92%; + font-size: 1.11em; +} + +#pma_navigation_tree_content { + width: 100%; + overflow: hidden; + overflow-y: auto; + position: absolute; + height: 100%; +} +#pma_navigation_tree_content a.hover_show_full { + position: relative; + z-index: 100; + vertical-align: sub; +} +#pma_navigation_tree a { + color: ; +} +#pma_navigation_tree a:hover { + text-decoration: underline; +} +#pma_navigation_tree li.activePointer { + color: ; + background-color: ; +} +#pma_navigation_tree li.selected { + color: ; + background-color: ; +} +#pma_navigation_tree li .dbItemControls { + padding-left: 4px; +} +#pma_navigation_tree li .navItemControls { + display: none; + padding-left: 4px; +} +#pma_navigation_tree li.activePointer .navItemControls { + display: inline; + opacity: 0.5; +} +#pma_navigation_tree li.activePointer .navItemControls:hover { + display: inline; + opacity: 1.0; +} +#pma_navigation_tree ul { + clear: both; + padding: 0; + list-style-type: none; + margin: 0; +} +#pma_navigation_tree ul ul { + position: relative; +} +#pma_navigation_tree li, +#pma_navigation_tree li.fast_filter { + white-space: nowrap; + clear: both; + min-height: 16px; +} +#pma_navigation_tree img { + margin: 0; +} +#pma_navigation_tree i { + display: block; +} +#pma_navigation_tree div.block { + position: relative; + width: 1.5em; + height: 1.5em; + min-width: 16px; + min-height: 16px; + float: ; +} +#pma_navigation_tree div.block.double { + width: 2.5em; +} +#pma_navigation_tree div.block i, +#pma_navigation_tree div.block b { + width: 1.5em; + height: 1.7em; + min-width: 16px; + min-height: 8px; + position: absolute; + bottom: 0.7em; + : 0.75em; + z-index: 0; +} +#pma_navigation_tree div.block i { /* Top and right segments for the tree element connections */ + display: block; + border-: 1px solid #666; + border-bottom: 1px solid #666; + position: relative; + z-index: 0; +} +#pma_navigation_tree div.block i.first { /* Removes top segment */ + border-: 0; +} +#pma_navigation_tree div.block b { /* Bottom segment for the tree element connections */ + display: block; + height: 0.75em; + bottom: 0; + : 0.75em; + border-: 1px solid #666; +} +#pma_navigation_tree div.block a, +#pma_navigation_tree div.block u { + position: absolute; + : 50%; + top: 50%; + z-index: 10; +} +#pma_navigation_tree div.block a + a { + : 100%; +} +#pma_navigation_tree div.block.double a, +#pma_navigation_tree div.block.double u { + : 33%; +} +#pma_navigation_tree div.block.double a + a { + : 85%; +} +#pma_navigation_tree div.block img { + position: relative; + top: -0.6em; + : 0; + margin-: -7px; +} +#pma_navigation_tree div.throbber img { + top: 2px; + : 2px; +} +#pma_navigation_tree li.last > ul { + background: none; +} +#pma_navigation_tree li > a, #pma_navigation_tree li > i { + line-height: 1.5em; + height: 1.5em; + padding-: 0.3em; +} +#pma_navigation_tree .list_container { + border-: 1px solid #666; + margin-: 0.75em; + padding-: 0.75em; +} +#pma_navigation_tree .last > .list_container { + border-: 0 solid #666; +} + +/* Fast filter */ +li.fast_filter { + padding-: 0.75em; + margin-: 0.75em; + padding-: 35px; + border-: 1px solid #666; + list-style: none; +} +li.fast_filter input { + margin: 3px 0 0 0; + font-size: 0.7em; + padding-top: 2px; + padding-bottom: 2px; + padding-: 4px; + padding-: 1.7em; + width: 100%; +} +li.fast_filter span { + position: relative; + : 1.5em; + padding: 0.2em; + cursor: pointer; + font-weight: bold; + color: #800; + font-size: 0.7em; +} +/* IE10+ has its own reset X */ +html.ie li.fast_filter span { + display: none; +} +html.ie.ie9 li.fast_filter span, +html.ie.ie8 li.fast_filter span { + display: auto; +} +html.ie li.fast_filter input { + padding-: .2em; +} +html.ie.ie9 li.fast_filter input, +html.ie.ie8 li.fast_filter input { + padding-: 1.7em; +} +li.fast_filter.db_fast_filter { + border: 0; + margin-left: 0; + margin-right: 10px; +} + +#navigation_controls_outer { + min-height: 21px !important; +} + +#navigation_controls_outer.activePointer { + background-color: transparent !important; +} + +#navigation_controls { + float: right; + padding-right: 23px; +} + +/* Resize handler */ +#pma_navigation_resizer { + width: 3px; + height: 100%; + background-color: #aaa; + cursor: col-resize; + position: fixed; + top: 0; + : 240px; + z-index: 801; +} +#pma_navigation_collapser { + width: 20px; + height: 22px; + line-height: 22px; + background: #eee; + color: #555; + font-weight: bold; + position: fixed; + top: 0; + : px; + text-align: center; + cursor: pointer; + z-index: 800; + text-shadow: 0 1px 0 #fff; + filter: dropshadow(color=#fff, offx=0, offy=1); + border: 1px solid #888; +} + +/* Quick warp links */ +.pma_quick_warp { + margin-top: 5px; + margin-: 2px; + position: relative; +} +.pma_quick_warp .drop_list { + float: ; + margin-: 3px; + padding: 2px 0; +} +.pma_quick_warp .drop_button { + padding: 0 .3em; + border: 1px solid #ddd; + border-radius: .3em; + background: #f2f2f2; + cursor: pointer; +} +.pma_quick_warp .drop_list:hover .drop_button { + background: #fff; +} +.pma_quick_warp .drop_list ul { + position: absolute; + margin: 0; + padding: 0; + overflow: hidden; + overflow-y: auto; + list-style: none; + background: #fff; + border: 1px solid #ddd; + border-radius: .3em; + border-top--radius: 0; + border-bottom--radius: 0; + box-shadow: 0 0 5px #ccc; + top: 100%; + : 3px; + : 0; + display: none; + z-index: 802; +} +.pma_quick_warp .drop_list:hover ul { + display: block; +} +.pma_quick_warp .drop_list li { + white-space: nowrap; + padding: 0; + border-radius: 0; +} +.pma_quick_warp .drop_list li img { + vertical-align: sub; +} +.pma_quick_warp .drop_list li:hover { + background: #f2f2f2; +} +.pma_quick_warp .drop_list a { + display: block; + padding: .2em .3em; +} +.pma_quick_warp .drop_list a.favorite_table_anchor { + clear: left; + float: left; + padding: .1em .3em 0; +} diff --git a/php/apps/phpmyadmin49/themes/pmahomme/css/printview.css b/php/apps/phpmyadmin49/themes/pmahomme/css/printview.css new file mode 100644 index 00000000..809cbdd0 --- /dev/null +++ b/php/apps/phpmyadmin49/themes/pmahomme/css/printview.css @@ -0,0 +1,169 @@ +@media print { + #back_button_print_view, #print_button_print_view { + display: none; + } +} + +/* For removing element from Print View */ +.print_ignore { + display: none; +} + +.nowrap { + white-space: nowrap; +} + +.hide { + display: none; +} + +/* Standard CSS */ +body, table, th, td { + color: #000; + background-color: #fff; + font-size: 12px; +} + +/* To remove link text decoration */ +a:link { + color:#000; + text-decoration:none +} + +/* To remove any image borders */ +img { + border: 0; +} + +/* Table specific */ +table, th, td { + border: .1em solid #000; + background-color: #fff; +} + +table { + border-collapse: collapse; + border-spacing: 0.2em; +} + +thead { + border-collapse: collapse; + border-spacing: 0.2em; + border: .1em solid #000; + font-weight: 900; +} + +th, td { + padding: 0.2em; +} + +thead th { + font-weight: bold; + background-color: #e5e5e5; + border: .1em solid #000; +} + +th.vtop, td.vtop { + vertical-align: top; +} + +th.vbottom, td.vbottom { + vertical-align: bottom; +} + +/* Common Elements not to be included */ +/* Hide Navigation and Top Menu bar */ +#pma_navigation, #floating_menubar { + display: none; +} +/* Hide console */ +#pma_console_container { + display: none; +} + +/* Hide Navigation items (like Goto Top) */ +#page_nav_icons { + display: none; +} + +/* Hide the Create Table form */ +#create_table_form_minimal { + display: none; +} + +/* Hide the Page Settings Modal box */ +#page_settings_modal { + display: none; +} + +/* Hide footer, Demo notice, errors div */ +#pma_footer, #pma_demo, #pma_errors { + display: none; +} + +/* Hide the #selflink div */ +#selflink { + display: none; +} + +/* Position the main content */ +#page_content { + position: absolute; + left: 0; + top: 0; + width: 95%; + float: none; +} + +/* Specific Class for overriding while Print */ +.print { + background-color: #000; +} + +/* For the Success message div */ +div.success { + background-color: #fff; +} + +.sqlOuter { + color: black; + background-color: #000; +} + +/* For hiding 'Open a New phpMyAdmin Window' button */ +.ic_window-new, .ic_s_cog { + display: none; +} + +.sticky_columns tr { + display: none; +} + +#structure-action-links, #addColumns { + display: none; +} + +/* Hide extra menu on tbl_structure.php */ +#topmenu2 { + display: none; +} + +.cDrop, .cEdit, .cList, .cCpy, .cPointer { + display: none; +} + +/* odd items 1,3,5,7,... */ +table tbody:first-of-type tr:nth-child(odd), +table tbody:first-of-type tr:nth-child(odd) th { + background: #fff; +} + +/* even items 2,4,6,8,... */ +table tbody:first-of-type tr:nth-child(even), +table tbody:first-of-type tr:nth-child(even) th { + background: #DFDFDF; +} + +.column_attribute { + font-size: 100%; +} diff --git a/php/apps/phpmyadmin49/themes/pmahomme/css/resizable-menu.css.php b/php/apps/phpmyadmin49/themes/pmahomme/css/resizable-menu.css.php new file mode 100644 index 00000000..6cebc896 --- /dev/null +++ b/php/apps/phpmyadmin49/themes/pmahomme/css/resizable-menu.css.php @@ -0,0 +1,57 @@ + +ul.resizable-menu a, +ul.resizable-menu span { + display: block; + margin: 0; + padding: 0; + white-space: nowrap; +} + +ul.resizable-menu .submenu { + display: none; + position: relative; +} + +ul.resizable-menu .shown { + display: inline-block; +} + +ul.resizable-menu ul { + margin: 0; + padding: 0; + position: absolute; + list-style-type: none; + display: none; + border: 1px #ddd solid; + z-index: 2; + : 0; +} + +ul.resizable-menu li:hover { + getCssGradient('ffffff', 'e5e5e5'); ?> +} + +ul.resizable-menu li:hover ul, +ul.resizable-menu .submenuhover ul { + display: block; + background: #fff; +} + +ul.resizable-menu ul li { + width: 100%; +} diff --git a/php/apps/phpmyadmin49/themes/pmahomme/css/rte.css.php b/php/apps/phpmyadmin49/themes/pmahomme/css/rte.css.php new file mode 100644 index 00000000..f70ffe27 --- /dev/null +++ b/php/apps/phpmyadmin49/themes/pmahomme/css/rte.css.php @@ -0,0 +1,50 @@ + + +.rte_table { + table-layout: fixed; +} + +.rte_table td { + vertical-align: middle; + padding: 0.2em; +} + +.rte_table tr td:nth-child(1) { + font-weight: bold; +} + +.rte_table input, +.rte_table select, +.rte_table textarea { + width: 100%; + margin: 0; + box-sizing: border-box; + -ms-box-sizing: border-box; + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; +} + +.rte_table input[type=button], +.rte_table input[type=checkbox], +.rte_table input[type=radio] { + width: auto; + margin-right: 6px; +} + +.rte_table .routine_params_table { + width: 100%; +} diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/ajax_clock_small.gif b/php/apps/phpmyadmin49/themes/pmahomme/img/ajax_clock_small.gif new file mode 100644 index 00000000..bde4932c Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/ajax_clock_small.gif differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/arrow_ltr.png b/php/apps/phpmyadmin49/themes/pmahomme/img/arrow_ltr.png new file mode 100644 index 00000000..cd79ab42 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/arrow_ltr.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/arrow_rtl.png b/php/apps/phpmyadmin49/themes/pmahomme/img/arrow_rtl.png new file mode 100644 index 00000000..d035b9d9 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/arrow_rtl.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/asc_order.png b/php/apps/phpmyadmin49/themes/pmahomme/img/asc_order.png new file mode 100644 index 00000000..51ce21b1 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/asc_order.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/b_bookmark.png b/php/apps/phpmyadmin49/themes/pmahomme/img/b_bookmark.png new file mode 100644 index 00000000..275cfa6c Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/b_bookmark.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/b_browse.png b/php/apps/phpmyadmin49/themes/pmahomme/img/b_browse.png new file mode 100644 index 00000000..8fd62b45 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/b_browse.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/b_calendar.png b/php/apps/phpmyadmin49/themes/pmahomme/img/b_calendar.png new file mode 100644 index 00000000..0eed98c3 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/b_calendar.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/b_chart.png b/php/apps/phpmyadmin49/themes/pmahomme/img/b_chart.png new file mode 100644 index 00000000..c97c2787 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/b_chart.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/b_close.png b/php/apps/phpmyadmin49/themes/pmahomme/img/b_close.png new file mode 100644 index 00000000..8d8e98ec Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/b_close.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/b_column_add.png b/php/apps/phpmyadmin49/themes/pmahomme/img/b_column_add.png new file mode 100644 index 00000000..70fa63ec Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/b_column_add.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/b_comment.png b/php/apps/phpmyadmin49/themes/pmahomme/img/b_comment.png new file mode 100644 index 00000000..e95e887d Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/b_comment.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/b_dbstatistics.png b/php/apps/phpmyadmin49/themes/pmahomme/img/b_dbstatistics.png new file mode 100644 index 00000000..c97c2787 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/b_dbstatistics.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/b_deltbl.png b/php/apps/phpmyadmin49/themes/pmahomme/img/b_deltbl.png new file mode 100644 index 00000000..de936288 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/b_deltbl.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/b_docs.png b/php/apps/phpmyadmin49/themes/pmahomme/img/b_docs.png new file mode 100644 index 00000000..54d12151 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/b_docs.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/b_docsql.png b/php/apps/phpmyadmin49/themes/pmahomme/img/b_docsql.png new file mode 100644 index 00000000..2f6f0561 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/b_docsql.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/b_drop.png b/php/apps/phpmyadmin49/themes/pmahomme/img/b_drop.png new file mode 100644 index 00000000..672e3586 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/b_drop.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/b_edit.png b/php/apps/phpmyadmin49/themes/pmahomme/img/b_edit.png new file mode 100644 index 00000000..003b5d77 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/b_edit.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/b_empty.png b/php/apps/phpmyadmin49/themes/pmahomme/img/b_empty.png new file mode 100644 index 00000000..91e22572 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/b_empty.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/b_engine.png b/php/apps/phpmyadmin49/themes/pmahomme/img/b_engine.png new file mode 100644 index 00000000..3a92d770 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/b_engine.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/b_event_add.png b/php/apps/phpmyadmin49/themes/pmahomme/img/b_event_add.png new file mode 100644 index 00000000..1f31c7cb Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/b_event_add.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/b_events.png b/php/apps/phpmyadmin49/themes/pmahomme/img/b_events.png new file mode 100644 index 00000000..07c60ff8 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/b_events.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/b_export.png b/php/apps/phpmyadmin49/themes/pmahomme/img/b_export.png new file mode 100644 index 00000000..ba8ecf53 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/b_export.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/b_favorite.png b/php/apps/phpmyadmin49/themes/pmahomme/img/b_favorite.png new file mode 100644 index 00000000..c4e1787e Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/b_favorite.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/b_find_replace.png b/php/apps/phpmyadmin49/themes/pmahomme/img/b_find_replace.png new file mode 100644 index 00000000..aff56f58 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/b_find_replace.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/b_firstpage.png b/php/apps/phpmyadmin49/themes/pmahomme/img/b_firstpage.png new file mode 100644 index 00000000..8e768efe Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/b_firstpage.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/b_ftext.png b/php/apps/phpmyadmin49/themes/pmahomme/img/b_ftext.png new file mode 100644 index 00000000..0d2cde23 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/b_ftext.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/b_globe.gif b/php/apps/phpmyadmin49/themes/pmahomme/img/b_globe.gif new file mode 100644 index 00000000..ef03dcf0 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/b_globe.gif differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/b_group.png b/php/apps/phpmyadmin49/themes/pmahomme/img/b_group.png new file mode 100644 index 00000000..4dd9d0b5 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/b_group.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/b_help.png b/php/apps/phpmyadmin49/themes/pmahomme/img/b_help.png new file mode 100644 index 00000000..54d12151 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/b_help.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/b_home.png b/php/apps/phpmyadmin49/themes/pmahomme/img/b_home.png new file mode 100644 index 00000000..d9fc35ef Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/b_home.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/b_import.png b/php/apps/phpmyadmin49/themes/pmahomme/img/b_import.png new file mode 100644 index 00000000..a3b80bd8 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/b_import.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/b_index.png b/php/apps/phpmyadmin49/themes/pmahomme/img/b_index.png new file mode 100644 index 00000000..a7a11a15 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/b_index.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/b_index_add.png b/php/apps/phpmyadmin49/themes/pmahomme/img/b_index_add.png new file mode 100644 index 00000000..2cef18ac Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/b_index_add.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/b_inline_edit.png b/php/apps/phpmyadmin49/themes/pmahomme/img/b_inline_edit.png new file mode 100644 index 00000000..c8fd6062 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/b_inline_edit.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/b_insrow.png b/php/apps/phpmyadmin49/themes/pmahomme/img/b_insrow.png new file mode 100644 index 00000000..ce32a0a1 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/b_insrow.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/b_key.png b/php/apps/phpmyadmin49/themes/pmahomme/img/b_key.png new file mode 100644 index 00000000..d1b15d9c Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/b_key.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/b_lastpage.png b/php/apps/phpmyadmin49/themes/pmahomme/img/b_lastpage.png new file mode 100644 index 00000000..1b4ac092 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/b_lastpage.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/b_left.png b/php/apps/phpmyadmin49/themes/pmahomme/img/b_left.png new file mode 100644 index 00000000..db427f96 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/b_left.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/b_minus.png b/php/apps/phpmyadmin49/themes/pmahomme/img/b_minus.png new file mode 100644 index 00000000..19ad78b6 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/b_minus.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/b_more.png b/php/apps/phpmyadmin49/themes/pmahomme/img/b_more.png new file mode 100644 index 00000000..9d84943e Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/b_more.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/b_move.png b/php/apps/phpmyadmin49/themes/pmahomme/img/b_move.png new file mode 100644 index 00000000..aeb7a55d Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/b_move.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/b_newdb.png b/php/apps/phpmyadmin49/themes/pmahomme/img/b_newdb.png new file mode 100644 index 00000000..25312edd Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/b_newdb.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/b_newtbl.png b/php/apps/phpmyadmin49/themes/pmahomme/img/b_newtbl.png new file mode 100644 index 00000000..f675656f Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/b_newtbl.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/b_nextpage.png b/php/apps/phpmyadmin49/themes/pmahomme/img/b_nextpage.png new file mode 100644 index 00000000..75a2c67c Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/b_nextpage.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/b_no_favorite.png b/php/apps/phpmyadmin49/themes/pmahomme/img/b_no_favorite.png new file mode 100644 index 00000000..925a17f4 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/b_no_favorite.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/b_pdfdoc.png b/php/apps/phpmyadmin49/themes/pmahomme/img/b_pdfdoc.png new file mode 100644 index 00000000..a8074cf4 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/b_pdfdoc.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/b_plugin.png b/php/apps/phpmyadmin49/themes/pmahomme/img/b_plugin.png new file mode 100644 index 00000000..205b9dab Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/b_plugin.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/b_plus.png b/php/apps/phpmyadmin49/themes/pmahomme/img/b_plus.png new file mode 100644 index 00000000..c26fb249 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/b_plus.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/b_prevpage.png b/php/apps/phpmyadmin49/themes/pmahomme/img/b_prevpage.png new file mode 100644 index 00000000..752478ae Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/b_prevpage.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/b_primary.png b/php/apps/phpmyadmin49/themes/pmahomme/img/b_primary.png new file mode 100644 index 00000000..7333d891 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/b_primary.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/b_print.png b/php/apps/phpmyadmin49/themes/pmahomme/img/b_print.png new file mode 100644 index 00000000..18a28a85 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/b_print.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/b_props.png b/php/apps/phpmyadmin49/themes/pmahomme/img/b_props.png new file mode 100644 index 00000000..58906696 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/b_props.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/b_relations.png b/php/apps/phpmyadmin49/themes/pmahomme/img/b_relations.png new file mode 100644 index 00000000..7c16044b Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/b_relations.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/b_report.png b/php/apps/phpmyadmin49/themes/pmahomme/img/b_report.png new file mode 100644 index 00000000..f5b57cdd Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/b_report.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/b_right.png b/php/apps/phpmyadmin49/themes/pmahomme/img/b_right.png new file mode 100644 index 00000000..462d1fee Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/b_right.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/b_routine_add.png b/php/apps/phpmyadmin49/themes/pmahomme/img/b_routine_add.png new file mode 100644 index 00000000..78517c10 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/b_routine_add.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/b_routines.png b/php/apps/phpmyadmin49/themes/pmahomme/img/b_routines.png new file mode 100644 index 00000000..439899be Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/b_routines.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/b_save.png b/php/apps/phpmyadmin49/themes/pmahomme/img/b_save.png new file mode 100644 index 00000000..e82bfed1 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/b_save.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/b_saveimage.png b/php/apps/phpmyadmin49/themes/pmahomme/img/b_saveimage.png new file mode 100644 index 00000000..0bc614a4 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/b_saveimage.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/b_sbrowse.png b/php/apps/phpmyadmin49/themes/pmahomme/img/b_sbrowse.png new file mode 100644 index 00000000..8fd62b45 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/b_sbrowse.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/b_sdb.png b/php/apps/phpmyadmin49/themes/pmahomme/img/b_sdb.png new file mode 100644 index 00000000..ceb79349 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/b_sdb.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/b_search.png b/php/apps/phpmyadmin49/themes/pmahomme/img/b_search.png new file mode 100644 index 00000000..1527a0a7 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/b_search.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/b_select.png b/php/apps/phpmyadmin49/themes/pmahomme/img/b_select.png new file mode 100644 index 00000000..821d30f1 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/b_select.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/b_snewtbl.png b/php/apps/phpmyadmin49/themes/pmahomme/img/b_snewtbl.png new file mode 100644 index 00000000..b2863b4e Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/b_snewtbl.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/b_spatial.png b/php/apps/phpmyadmin49/themes/pmahomme/img/b_spatial.png new file mode 100644 index 00000000..6498e451 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/b_spatial.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/b_sql.png b/php/apps/phpmyadmin49/themes/pmahomme/img/b_sql.png new file mode 100644 index 00000000..e52ff7fb Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/b_sql.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/b_sqldoc.png b/php/apps/phpmyadmin49/themes/pmahomme/img/b_sqldoc.png new file mode 100644 index 00000000..7762f499 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/b_sqldoc.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/b_sqlhelp.png b/php/apps/phpmyadmin49/themes/pmahomme/img/b_sqlhelp.png new file mode 100644 index 00000000..dbeacb64 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/b_sqlhelp.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/b_table_add.png b/php/apps/phpmyadmin49/themes/pmahomme/img/b_table_add.png new file mode 100644 index 00000000..06b6d57c Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/b_table_add.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/b_tblanalyse.png b/php/apps/phpmyadmin49/themes/pmahomme/img/b_tblanalyse.png new file mode 100644 index 00000000..6809a2d6 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/b_tblanalyse.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/b_tblexport.png b/php/apps/phpmyadmin49/themes/pmahomme/img/b_tblexport.png new file mode 100644 index 00000000..ba8ecf53 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/b_tblexport.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/b_tblimport.png b/php/apps/phpmyadmin49/themes/pmahomme/img/b_tblimport.png new file mode 100644 index 00000000..a3b80bd8 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/b_tblimport.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/b_tblops.png b/php/apps/phpmyadmin49/themes/pmahomme/img/b_tblops.png new file mode 100644 index 00000000..82b88dda Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/b_tblops.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/b_tbloptimize.png b/php/apps/phpmyadmin49/themes/pmahomme/img/b_tbloptimize.png new file mode 100644 index 00000000..0c8425ce Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/b_tbloptimize.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/b_tipp.png b/php/apps/phpmyadmin49/themes/pmahomme/img/b_tipp.png new file mode 100644 index 00000000..0cead20e Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/b_tipp.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/b_trigger_add.png b/php/apps/phpmyadmin49/themes/pmahomme/img/b_trigger_add.png new file mode 100644 index 00000000..920a2a4f Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/b_trigger_add.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/b_triggers.png b/php/apps/phpmyadmin49/themes/pmahomme/img/b_triggers.png new file mode 100644 index 00000000..2ee1dc6f Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/b_triggers.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/b_undo.png b/php/apps/phpmyadmin49/themes/pmahomme/img/b_undo.png new file mode 100644 index 00000000..fef0fa4e Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/b_undo.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/b_unique.png b/php/apps/phpmyadmin49/themes/pmahomme/img/b_unique.png new file mode 100644 index 00000000..94a520f0 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/b_unique.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/b_usradd.png b/php/apps/phpmyadmin49/themes/pmahomme/img/b_usradd.png new file mode 100644 index 00000000..eb96e6cd Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/b_usradd.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/b_usrcheck.png b/php/apps/phpmyadmin49/themes/pmahomme/img/b_usrcheck.png new file mode 100644 index 00000000..7fabcb2b Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/b_usrcheck.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/b_usrdrop.png b/php/apps/phpmyadmin49/themes/pmahomme/img/b_usrdrop.png new file mode 100644 index 00000000..2e0e7d63 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/b_usrdrop.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/b_usredit.png b/php/apps/phpmyadmin49/themes/pmahomme/img/b_usredit.png new file mode 100644 index 00000000..143a3976 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/b_usredit.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/b_usrlist.png b/php/apps/phpmyadmin49/themes/pmahomme/img/b_usrlist.png new file mode 100644 index 00000000..07a620ec Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/b_usrlist.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/b_versions.png b/php/apps/phpmyadmin49/themes/pmahomme/img/b_versions.png new file mode 100644 index 00000000..c253dc06 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/b_versions.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/b_view.png b/php/apps/phpmyadmin49/themes/pmahomme/img/b_view.png new file mode 100644 index 00000000..204b10d6 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/b_view.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/b_view_add.png b/php/apps/phpmyadmin49/themes/pmahomme/img/b_view_add.png new file mode 100644 index 00000000..ebded825 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/b_view_add.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/b_views.png b/php/apps/phpmyadmin49/themes/pmahomme/img/b_views.png new file mode 100644 index 00000000..65f64d77 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/b_views.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/bd_browse.png b/php/apps/phpmyadmin49/themes/pmahomme/img/bd_browse.png new file mode 100644 index 00000000..5a28d46e Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/bd_browse.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/bd_deltbl.png b/php/apps/phpmyadmin49/themes/pmahomme/img/bd_deltbl.png new file mode 100644 index 00000000..fb518fc7 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/bd_deltbl.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/bd_drop.png b/php/apps/phpmyadmin49/themes/pmahomme/img/bd_drop.png new file mode 100644 index 00000000..19ccf881 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/bd_drop.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/bd_edit.png b/php/apps/phpmyadmin49/themes/pmahomme/img/bd_edit.png new file mode 100644 index 00000000..b8844896 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/bd_edit.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/bd_empty.png b/php/apps/phpmyadmin49/themes/pmahomme/img/bd_empty.png new file mode 100644 index 00000000..e6a855ac Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/bd_empty.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/bd_export.png b/php/apps/phpmyadmin49/themes/pmahomme/img/bd_export.png new file mode 100644 index 00000000..1fbb6042 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/bd_export.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/bd_firstpage.png b/php/apps/phpmyadmin49/themes/pmahomme/img/bd_firstpage.png new file mode 100644 index 00000000..9abaad5f Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/bd_firstpage.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/bd_ftext.png b/php/apps/phpmyadmin49/themes/pmahomme/img/bd_ftext.png new file mode 100644 index 00000000..818fcd4d Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/bd_ftext.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/bd_index.png b/php/apps/phpmyadmin49/themes/pmahomme/img/bd_index.png new file mode 100644 index 00000000..fbade943 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/bd_index.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/bd_insrow.png b/php/apps/phpmyadmin49/themes/pmahomme/img/bd_insrow.png new file mode 100644 index 00000000..b325ffa9 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/bd_insrow.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/bd_lastpage.png b/php/apps/phpmyadmin49/themes/pmahomme/img/bd_lastpage.png new file mode 100644 index 00000000..48985174 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/bd_lastpage.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/bd_nextpage.png b/php/apps/phpmyadmin49/themes/pmahomme/img/bd_nextpage.png new file mode 100644 index 00000000..5373f9ab Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/bd_nextpage.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/bd_prevpage.png b/php/apps/phpmyadmin49/themes/pmahomme/img/bd_prevpage.png new file mode 100644 index 00000000..f1e98321 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/bd_prevpage.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/bd_primary.png b/php/apps/phpmyadmin49/themes/pmahomme/img/bd_primary.png new file mode 100644 index 00000000..d1b15d9c Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/bd_primary.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/bd_routine_add.png b/php/apps/phpmyadmin49/themes/pmahomme/img/bd_routine_add.png new file mode 100644 index 00000000..6f45cbd0 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/bd_routine_add.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/bd_sbrowse.png b/php/apps/phpmyadmin49/themes/pmahomme/img/bd_sbrowse.png new file mode 100644 index 00000000..5a28d46e Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/bd_sbrowse.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/bd_select.png b/php/apps/phpmyadmin49/themes/pmahomme/img/bd_select.png new file mode 100644 index 00000000..f9a23ef3 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/bd_select.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/bd_spatial.png b/php/apps/phpmyadmin49/themes/pmahomme/img/bd_spatial.png new file mode 100644 index 00000000..ea16fedf Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/bd_spatial.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/bd_unique.png b/php/apps/phpmyadmin49/themes/pmahomme/img/bd_unique.png new file mode 100644 index 00000000..ebacdf49 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/bd_unique.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/centralColumns.png b/php/apps/phpmyadmin49/themes/pmahomme/img/centralColumns.png new file mode 100644 index 00000000..4b94c9f7 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/centralColumns.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/centralColumns_add.png b/php/apps/phpmyadmin49/themes/pmahomme/img/centralColumns_add.png new file mode 100644 index 00000000..72825645 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/centralColumns_add.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/centralColumns_delete.png b/php/apps/phpmyadmin49/themes/pmahomme/img/centralColumns_delete.png new file mode 100644 index 00000000..98d6b05d Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/centralColumns_delete.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/col_drop.png b/php/apps/phpmyadmin49/themes/pmahomme/img/col_drop.png new file mode 100644 index 00000000..9d84943e Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/col_drop.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/col_pointer.png b/php/apps/phpmyadmin49/themes/pmahomme/img/col_pointer.png new file mode 100644 index 00000000..00d78c7c Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/col_pointer.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/col_pointer_ver.png b/php/apps/phpmyadmin49/themes/pmahomme/img/col_pointer_ver.png new file mode 100644 index 00000000..0b735526 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/col_pointer_ver.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/console.png b/php/apps/phpmyadmin49/themes/pmahomme/img/console.png new file mode 100644 index 00000000..f84ce2c1 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/console.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/database.png b/php/apps/phpmyadmin49/themes/pmahomme/img/database.png new file mode 100644 index 00000000..0d0e388f Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/database.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/designer/1.png b/php/apps/phpmyadmin49/themes/pmahomme/img/designer/1.png new file mode 100644 index 00000000..a9da1dc1 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/designer/1.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/designer/2.png b/php/apps/phpmyadmin49/themes/pmahomme/img/designer/2.png new file mode 100644 index 00000000..c153fd1d Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/designer/2.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/designer/2leftarrow.png b/php/apps/phpmyadmin49/themes/pmahomme/img/designer/2leftarrow.png new file mode 100644 index 00000000..77d5edd5 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/designer/2leftarrow.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/designer/2leftarrow_m.png b/php/apps/phpmyadmin49/themes/pmahomme/img/designer/2leftarrow_m.png new file mode 100644 index 00000000..9ab18073 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/designer/2leftarrow_m.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/designer/2rightarrow.png b/php/apps/phpmyadmin49/themes/pmahomme/img/designer/2rightarrow.png new file mode 100644 index 00000000..f59f2f3a Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/designer/2rightarrow.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/designer/2rightarrow_m.png b/php/apps/phpmyadmin49/themes/pmahomme/img/designer/2rightarrow_m.png new file mode 100644 index 00000000..61af4d3a Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/designer/2rightarrow_m.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/designer/3.png b/php/apps/phpmyadmin49/themes/pmahomme/img/designer/3.png new file mode 100644 index 00000000..cf1d45e1 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/designer/3.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/designer/4.png b/php/apps/phpmyadmin49/themes/pmahomme/img/designer/4.png new file mode 100644 index 00000000..bdb1ad05 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/designer/4.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/designer/5.png b/php/apps/phpmyadmin49/themes/pmahomme/img/designer/5.png new file mode 100644 index 00000000..1e35a89f Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/designer/5.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/designer/6.png b/php/apps/phpmyadmin49/themes/pmahomme/img/designer/6.png new file mode 100644 index 00000000..c5da1cf9 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/designer/6.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/designer/7.png b/php/apps/phpmyadmin49/themes/pmahomme/img/designer/7.png new file mode 100644 index 00000000..2b4f80b3 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/designer/7.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/designer/8.png b/php/apps/phpmyadmin49/themes/pmahomme/img/designer/8.png new file mode 100644 index 00000000..292fdd09 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/designer/8.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/designer/FieldKey_small.png b/php/apps/phpmyadmin49/themes/pmahomme/img/designer/FieldKey_small.png new file mode 100644 index 00000000..0b588949 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/designer/FieldKey_small.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/designer/Field_small.png b/php/apps/phpmyadmin49/themes/pmahomme/img/designer/Field_small.png new file mode 100644 index 00000000..c396eb3b Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/designer/Field_small.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/designer/Field_small_char.png b/php/apps/phpmyadmin49/themes/pmahomme/img/designer/Field_small_char.png new file mode 100644 index 00000000..e35c2a37 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/designer/Field_small_char.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/designer/Field_small_date.png b/php/apps/phpmyadmin49/themes/pmahomme/img/designer/Field_small_date.png new file mode 100644 index 00000000..21df4e27 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/designer/Field_small_date.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/designer/Field_small_int.png b/php/apps/phpmyadmin49/themes/pmahomme/img/designer/Field_small_int.png new file mode 100644 index 00000000..6d6fe88a Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/designer/Field_small_int.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/designer/Header.png b/php/apps/phpmyadmin49/themes/pmahomme/img/designer/Header.png new file mode 100644 index 00000000..2d2090e9 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/designer/Header.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/designer/Header_Linked.png b/php/apps/phpmyadmin49/themes/pmahomme/img/designer/Header_Linked.png new file mode 100644 index 00000000..afd3f045 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/designer/Header_Linked.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/designer/anchor.png b/php/apps/phpmyadmin49/themes/pmahomme/img/designer/anchor.png new file mode 100644 index 00000000..576d4355 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/designer/anchor.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/designer/and_icon.png b/php/apps/phpmyadmin49/themes/pmahomme/img/designer/and_icon.png new file mode 100644 index 00000000..2ecce01a Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/designer/and_icon.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/designer/ang_direct.png b/php/apps/phpmyadmin49/themes/pmahomme/img/designer/ang_direct.png new file mode 100644 index 00000000..3ce29a6f Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/designer/ang_direct.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/designer/bord.png b/php/apps/phpmyadmin49/themes/pmahomme/img/designer/bord.png new file mode 100644 index 00000000..0b3191de Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/designer/bord.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/designer/bottom.png b/php/apps/phpmyadmin49/themes/pmahomme/img/designer/bottom.png new file mode 100644 index 00000000..19cbbcdc Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/designer/bottom.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/designer/def.png b/php/apps/phpmyadmin49/themes/pmahomme/img/designer/def.png new file mode 100644 index 00000000..ea026854 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/designer/def.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/designer/display_field.png b/php/apps/phpmyadmin49/themes/pmahomme/img/designer/display_field.png new file mode 100644 index 00000000..9e5e2dfe Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/designer/display_field.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/designer/downarrow1.png b/php/apps/phpmyadmin49/themes/pmahomme/img/designer/downarrow1.png new file mode 100644 index 00000000..a2d09541 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/designer/downarrow1.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/designer/downarrow2.png b/php/apps/phpmyadmin49/themes/pmahomme/img/designer/downarrow2.png new file mode 100644 index 00000000..60827b3d Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/designer/downarrow2.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/designer/downarrow2_m.png b/php/apps/phpmyadmin49/themes/pmahomme/img/designer/downarrow2_m.png new file mode 100644 index 00000000..70f7f926 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/designer/downarrow2_m.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/designer/exec.png b/php/apps/phpmyadmin49/themes/pmahomme/img/designer/exec.png new file mode 100644 index 00000000..06ce466a Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/designer/exec.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/designer/exec_small.png b/php/apps/phpmyadmin49/themes/pmahomme/img/designer/exec_small.png new file mode 100644 index 00000000..357166fe Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/designer/exec_small.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/designer/exitFullscreen.png b/php/apps/phpmyadmin49/themes/pmahomme/img/designer/exitFullscreen.png new file mode 100644 index 00000000..4397aede Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/designer/exitFullscreen.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/designer/export.png b/php/apps/phpmyadmin49/themes/pmahomme/img/designer/export.png new file mode 100644 index 00000000..64b58e3e Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/designer/export.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/designer/favicon.ico b/php/apps/phpmyadmin49/themes/pmahomme/img/designer/favicon.ico new file mode 100644 index 00000000..29c2595b Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/designer/favicon.ico differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/designer/grid.png b/php/apps/phpmyadmin49/themes/pmahomme/img/designer/grid.png new file mode 100644 index 00000000..f581a68d Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/designer/grid.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/designer/help.png b/php/apps/phpmyadmin49/themes/pmahomme/img/designer/help.png new file mode 100644 index 00000000..facf1a1a Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/designer/help.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/designer/help_relation.png b/php/apps/phpmyadmin49/themes/pmahomme/img/designer/help_relation.png new file mode 100644 index 00000000..321f0745 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/designer/help_relation.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/designer/left_panel_butt.png b/php/apps/phpmyadmin49/themes/pmahomme/img/designer/left_panel_butt.png new file mode 100644 index 00000000..03317301 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/designer/left_panel_butt.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/designer/left_panel_tab.png b/php/apps/phpmyadmin49/themes/pmahomme/img/designer/left_panel_tab.png new file mode 100644 index 00000000..c596f92d Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/designer/left_panel_tab.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/designer/minus.png b/php/apps/phpmyadmin49/themes/pmahomme/img/designer/minus.png new file mode 100644 index 00000000..2feb9355 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/designer/minus.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/designer/or_icon.png b/php/apps/phpmyadmin49/themes/pmahomme/img/designer/or_icon.png new file mode 100644 index 00000000..75be472b Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/designer/or_icon.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/designer/other_table.png b/php/apps/phpmyadmin49/themes/pmahomme/img/designer/other_table.png new file mode 100644 index 00000000..96453969 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/designer/other_table.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/designer/page_add.png b/php/apps/phpmyadmin49/themes/pmahomme/img/designer/page_add.png new file mode 100644 index 00000000..5d8a1d1a Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/designer/page_add.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/designer/page_delete.png b/php/apps/phpmyadmin49/themes/pmahomme/img/designer/page_delete.png new file mode 100644 index 00000000..a512c62c Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/designer/page_delete.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/designer/page_edit.png b/php/apps/phpmyadmin49/themes/pmahomme/img/designer/page_edit.png new file mode 100644 index 00000000..4f0ce4a0 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/designer/page_edit.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/designer/pdf.png b/php/apps/phpmyadmin49/themes/pmahomme/img/designer/pdf.png new file mode 100644 index 00000000..616060a2 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/designer/pdf.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/designer/plus.png b/php/apps/phpmyadmin49/themes/pmahomme/img/designer/plus.png new file mode 100644 index 00000000..ac3e6020 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/designer/plus.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/designer/query_builder.png b/php/apps/phpmyadmin49/themes/pmahomme/img/designer/query_builder.png new file mode 100644 index 00000000..6e54e8e5 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/designer/query_builder.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/designer/relation.png b/php/apps/phpmyadmin49/themes/pmahomme/img/designer/relation.png new file mode 100644 index 00000000..6c09c7f1 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/designer/relation.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/designer/reload.png b/php/apps/phpmyadmin49/themes/pmahomme/img/designer/reload.png new file mode 100644 index 00000000..6b5cda5b Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/designer/reload.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/designer/resize.png b/php/apps/phpmyadmin49/themes/pmahomme/img/designer/resize.png new file mode 100644 index 00000000..b3f4732f Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/designer/resize.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/designer/resizeright.png b/php/apps/phpmyadmin49/themes/pmahomme/img/designer/resizeright.png new file mode 100644 index 00000000..e984170c Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/designer/resizeright.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/designer/rightarrow1.png b/php/apps/phpmyadmin49/themes/pmahomme/img/designer/rightarrow1.png new file mode 100644 index 00000000..d2641d73 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/designer/rightarrow1.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/designer/rightarrow2.png b/php/apps/phpmyadmin49/themes/pmahomme/img/designer/rightarrow2.png new file mode 100644 index 00000000..76f2fe08 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/designer/rightarrow2.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/designer/save.png b/php/apps/phpmyadmin49/themes/pmahomme/img/designer/save.png new file mode 100644 index 00000000..6d5c0525 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/designer/save.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/designer/save_as.png b/php/apps/phpmyadmin49/themes/pmahomme/img/designer/save_as.png new file mode 100644 index 00000000..8c4d1518 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/designer/save_as.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/designer/small_tab.png b/php/apps/phpmyadmin49/themes/pmahomme/img/designer/small_tab.png new file mode 100644 index 00000000..fb84f54c Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/designer/small_tab.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/designer/table.png b/php/apps/phpmyadmin49/themes/pmahomme/img/designer/table.png new file mode 100644 index 00000000..aa2b1c71 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/designer/table.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/designer/toggle_lines.png b/php/apps/phpmyadmin49/themes/pmahomme/img/designer/toggle_lines.png new file mode 100644 index 00000000..46d28c09 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/designer/toggle_lines.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/designer/top_panel.png b/php/apps/phpmyadmin49/themes/pmahomme/img/designer/top_panel.png new file mode 100644 index 00000000..8a5dab9c Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/designer/top_panel.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/designer/uparrow2_m.png b/php/apps/phpmyadmin49/themes/pmahomme/img/designer/uparrow2_m.png new file mode 100644 index 00000000..5d1342d4 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/designer/uparrow2_m.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/designer/viewInFullscreen.png b/php/apps/phpmyadmin49/themes/pmahomme/img/designer/viewInFullscreen.png new file mode 100644 index 00000000..a6c76917 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/designer/viewInFullscreen.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/east-mini.png b/php/apps/phpmyadmin49/themes/pmahomme/img/east-mini.png new file mode 100644 index 00000000..6685939e Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/east-mini.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/error.ico b/php/apps/phpmyadmin49/themes/pmahomme/img/error.ico new file mode 100644 index 00000000..8f4d509d Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/error.ico differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/eye.png b/php/apps/phpmyadmin49/themes/pmahomme/img/eye.png new file mode 100644 index 00000000..6c9972e0 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/eye.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/eye_grey.png b/php/apps/phpmyadmin49/themes/pmahomme/img/eye_grey.png new file mode 100644 index 00000000..0dc92a90 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/eye_grey.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/hide.png b/php/apps/phpmyadmin49/themes/pmahomme/img/hide.png new file mode 100644 index 00000000..2c914431 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/hide.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/item.png b/php/apps/phpmyadmin49/themes/pmahomme/img/item.png new file mode 100644 index 00000000..f2c33904 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/item.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/left_nav_bg.png b/php/apps/phpmyadmin49/themes/pmahomme/img/left_nav_bg.png new file mode 100644 index 00000000..42b6f382 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/left_nav_bg.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/lightbulb.png b/php/apps/phpmyadmin49/themes/pmahomme/img/lightbulb.png new file mode 100644 index 00000000..0cead20e Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/lightbulb.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/lightbulb_off.png b/php/apps/phpmyadmin49/themes/pmahomme/img/lightbulb_off.png new file mode 100644 index 00000000..dd3632d1 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/lightbulb_off.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/logo_left.png b/php/apps/phpmyadmin49/themes/pmahomme/img/logo_left.png new file mode 100644 index 00000000..004e7050 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/logo_left.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/logo_right.png b/php/apps/phpmyadmin49/themes/pmahomme/img/logo_right.png new file mode 100644 index 00000000..a4902891 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/logo_right.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/more.png b/php/apps/phpmyadmin49/themes/pmahomme/img/more.png new file mode 100644 index 00000000..ac803c44 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/more.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/new_data.png b/php/apps/phpmyadmin49/themes/pmahomme/img/new_data.png new file mode 100644 index 00000000..c173bc03 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/new_data.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/new_data_hovered.png b/php/apps/phpmyadmin49/themes/pmahomme/img/new_data_hovered.png new file mode 100644 index 00000000..73b09a63 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/new_data_hovered.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/new_data_selected.png b/php/apps/phpmyadmin49/themes/pmahomme/img/new_data_selected.png new file mode 100644 index 00000000..a75abe34 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/new_data_selected.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/new_data_selected_hovered.png b/php/apps/phpmyadmin49/themes/pmahomme/img/new_data_selected_hovered.png new file mode 100644 index 00000000..7091ae36 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/new_data_selected_hovered.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/new_struct.png b/php/apps/phpmyadmin49/themes/pmahomme/img/new_struct.png new file mode 100644 index 00000000..79fe646c Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/new_struct.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/new_struct_hovered.png b/php/apps/phpmyadmin49/themes/pmahomme/img/new_struct_hovered.png new file mode 100644 index 00000000..b29aaed3 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/new_struct_hovered.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/new_struct_selected.png b/php/apps/phpmyadmin49/themes/pmahomme/img/new_struct_selected.png new file mode 100644 index 00000000..bc61749a Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/new_struct_selected.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/new_struct_selected_hovered.png b/php/apps/phpmyadmin49/themes/pmahomme/img/new_struct_selected_hovered.png new file mode 100644 index 00000000..9a82bc42 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/new_struct_selected_hovered.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/normalize.png b/php/apps/phpmyadmin49/themes/pmahomme/img/normalize.png new file mode 100644 index 00000000..5475f342 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/normalize.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/north-mini.png b/php/apps/phpmyadmin49/themes/pmahomme/img/north-mini.png new file mode 100644 index 00000000..c3b60620 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/north-mini.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/pause.png b/php/apps/phpmyadmin49/themes/pmahomme/img/pause.png new file mode 100644 index 00000000..0271217a Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/pause.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/php_sym.png b/php/apps/phpmyadmin49/themes/pmahomme/img/php_sym.png new file mode 100644 index 00000000..20b8350f Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/php_sym.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/play.png b/php/apps/phpmyadmin49/themes/pmahomme/img/play.png new file mode 100644 index 00000000..75a2c67c Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/play.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/pma_logo2.png b/php/apps/phpmyadmin49/themes/pmahomme/img/pma_logo2.png new file mode 100644 index 00000000..30f22de6 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/pma_logo2.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/s_asc.png b/php/apps/phpmyadmin49/themes/pmahomme/img/s_asc.png new file mode 100644 index 00000000..51cf60dc Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/s_asc.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/s_asci.png b/php/apps/phpmyadmin49/themes/pmahomme/img/s_asci.png new file mode 100644 index 00000000..6a56ea19 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/s_asci.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/s_attention.png b/php/apps/phpmyadmin49/themes/pmahomme/img/s_attention.png new file mode 100644 index 00000000..5a536e81 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/s_attention.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/s_cancel.png b/php/apps/phpmyadmin49/themes/pmahomme/img/s_cancel.png new file mode 100644 index 00000000..65a63461 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/s_cancel.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/s_cancel2.png b/php/apps/phpmyadmin49/themes/pmahomme/img/s_cancel2.png new file mode 100644 index 00000000..dd5dccc7 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/s_cancel2.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/s_cog.png b/php/apps/phpmyadmin49/themes/pmahomme/img/s_cog.png new file mode 100644 index 00000000..a9cbc244 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/s_cog.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/s_collapseall.png b/php/apps/phpmyadmin49/themes/pmahomme/img/s_collapseall.png new file mode 100644 index 00000000..3d56b7c5 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/s_collapseall.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/s_db.png b/php/apps/phpmyadmin49/themes/pmahomme/img/s_db.png new file mode 100644 index 00000000..0d0e388f Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/s_db.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/s_desc.png b/php/apps/phpmyadmin49/themes/pmahomme/img/s_desc.png new file mode 100644 index 00000000..51ce21b1 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/s_desc.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/s_error.png b/php/apps/phpmyadmin49/themes/pmahomme/img/s_error.png new file mode 100644 index 00000000..71d13a10 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/s_error.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/s_fulltext.png b/php/apps/phpmyadmin49/themes/pmahomme/img/s_fulltext.png new file mode 100644 index 00000000..b810b0cc Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/s_fulltext.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/s_host.png b/php/apps/phpmyadmin49/themes/pmahomme/img/s_host.png new file mode 100644 index 00000000..20a9b45c Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/s_host.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/s_info.png b/php/apps/phpmyadmin49/themes/pmahomme/img/s_info.png new file mode 100644 index 00000000..f3d221fe Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/s_info.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/s_lang.png b/php/apps/phpmyadmin49/themes/pmahomme/img/s_lang.png new file mode 100644 index 00000000..5a1d7ab4 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/s_lang.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/s_link.png b/php/apps/phpmyadmin49/themes/pmahomme/img/s_link.png new file mode 100644 index 00000000..609970a3 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/s_link.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/s_lock.png b/php/apps/phpmyadmin49/themes/pmahomme/img/s_lock.png new file mode 100644 index 00000000..55258949 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/s_lock.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/s_loggoff.png b/php/apps/phpmyadmin49/themes/pmahomme/img/s_loggoff.png new file mode 100644 index 00000000..f2581cb0 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/s_loggoff.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/s_notice.png b/php/apps/phpmyadmin49/themes/pmahomme/img/s_notice.png new file mode 100644 index 00000000..5a536e81 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/s_notice.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/s_okay.png b/php/apps/phpmyadmin49/themes/pmahomme/img/s_okay.png new file mode 100644 index 00000000..9bf92354 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/s_okay.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/s_partialtext.png b/php/apps/phpmyadmin49/themes/pmahomme/img/s_partialtext.png new file mode 100644 index 00000000..a8fbb343 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/s_partialtext.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/s_passwd.png b/php/apps/phpmyadmin49/themes/pmahomme/img/s_passwd.png new file mode 100644 index 00000000..c705bd55 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/s_passwd.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/s_process.png b/php/apps/phpmyadmin49/themes/pmahomme/img/s_process.png new file mode 100644 index 00000000..a9cbc244 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/s_process.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/s_really.png b/php/apps/phpmyadmin49/themes/pmahomme/img/s_really.png new file mode 100644 index 00000000..e48c9cd4 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/s_really.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/s_reload.png b/php/apps/phpmyadmin49/themes/pmahomme/img/s_reload.png new file mode 100644 index 00000000..656519c5 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/s_reload.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/s_replication.png b/php/apps/phpmyadmin49/themes/pmahomme/img/s_replication.png new file mode 100644 index 00000000..c5c5a5c5 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/s_replication.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/s_rights.png b/php/apps/phpmyadmin49/themes/pmahomme/img/s_rights.png new file mode 100644 index 00000000..407a62fe Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/s_rights.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/s_sortable.png b/php/apps/phpmyadmin49/themes/pmahomme/img/s_sortable.png new file mode 100644 index 00000000..879c3b59 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/s_sortable.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/s_status.png b/php/apps/phpmyadmin49/themes/pmahomme/img/s_status.png new file mode 100644 index 00000000..aee657c0 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/s_status.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/s_success.png b/php/apps/phpmyadmin49/themes/pmahomme/img/s_success.png new file mode 100644 index 00000000..4c79cd9e Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/s_success.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/s_sync.png b/php/apps/phpmyadmin49/themes/pmahomme/img/s_sync.png new file mode 100644 index 00000000..5ced64c5 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/s_sync.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/s_tbl.png b/php/apps/phpmyadmin49/themes/pmahomme/img/s_tbl.png new file mode 100644 index 00000000..6632aa29 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/s_tbl.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/s_theme.png b/php/apps/phpmyadmin49/themes/pmahomme/img/s_theme.png new file mode 100644 index 00000000..a18b1033 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/s_theme.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/s_top.png b/php/apps/phpmyadmin49/themes/pmahomme/img/s_top.png new file mode 100644 index 00000000..dd57623d Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/s_top.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/s_unlink.png b/php/apps/phpmyadmin49/themes/pmahomme/img/s_unlink.png new file mode 100644 index 00000000..551679e9 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/s_unlink.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/s_vars.png b/php/apps/phpmyadmin49/themes/pmahomme/img/s_vars.png new file mode 100644 index 00000000..b62dcd23 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/s_vars.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/s_views.png b/php/apps/phpmyadmin49/themes/pmahomme/img/s_views.png new file mode 100644 index 00000000..65f64d77 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/s_views.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/select_bg.png b/php/apps/phpmyadmin49/themes/pmahomme/img/select_bg.png new file mode 100644 index 00000000..22c3ea6c Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/select_bg.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/show.png b/php/apps/phpmyadmin49/themes/pmahomme/img/show.png new file mode 100644 index 00000000..666653f6 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/show.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/south-mini.png b/php/apps/phpmyadmin49/themes/pmahomme/img/south-mini.png new file mode 100644 index 00000000..654673b3 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/south-mini.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/spacer.png b/php/apps/phpmyadmin49/themes/pmahomme/img/spacer.png new file mode 100644 index 00000000..240ca4f8 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/spacer.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/toggle-ltr.png b/php/apps/phpmyadmin49/themes/pmahomme/img/toggle-ltr.png new file mode 100644 index 00000000..2dd681f6 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/toggle-ltr.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/toggle-rtl.png b/php/apps/phpmyadmin49/themes/pmahomme/img/toggle-rtl.png new file mode 100644 index 00000000..a50f5bb3 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/toggle-rtl.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/vertical_line.png b/php/apps/phpmyadmin49/themes/pmahomme/img/vertical_line.png new file mode 100644 index 00000000..a88a7c7b Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/vertical_line.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/west-mini.png b/php/apps/phpmyadmin49/themes/pmahomme/img/west-mini.png new file mode 100644 index 00000000..61ad92e0 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/west-mini.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/window-new.png b/php/apps/phpmyadmin49/themes/pmahomme/img/window-new.png new file mode 100644 index 00000000..d18722fd Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/window-new.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/zoom-minus-mini.png b/php/apps/phpmyadmin49/themes/pmahomme/img/zoom-minus-mini.png new file mode 100644 index 00000000..1b8d84dc Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/zoom-minus-mini.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/zoom-plus-mini.png b/php/apps/phpmyadmin49/themes/pmahomme/img/zoom-plus-mini.png new file mode 100644 index 00000000..466cc7bc Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/zoom-plus-mini.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/img/zoom-world-mini.png b/php/apps/phpmyadmin49/themes/pmahomme/img/zoom-world-mini.png new file mode 100644 index 00000000..dcc60f15 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/img/zoom-world-mini.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/jquery/images/ui-bg_glass_55_fbf9ee_1x400.png b/php/apps/phpmyadmin49/themes/pmahomme/jquery/images/ui-bg_glass_55_fbf9ee_1x400.png new file mode 100644 index 00000000..534c590f Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/jquery/images/ui-bg_glass_55_fbf9ee_1x400.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/jquery/images/ui-bg_glass_65_ffffff_1x400.png b/php/apps/phpmyadmin49/themes/pmahomme/jquery/images/ui-bg_glass_65_ffffff_1x400.png new file mode 100644 index 00000000..3e56dbdc Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/jquery/images/ui-bg_glass_65_ffffff_1x400.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/jquery/images/ui-bg_glass_75_dadada_1x400.png b/php/apps/phpmyadmin49/themes/pmahomme/jquery/images/ui-bg_glass_75_dadada_1x400.png new file mode 100644 index 00000000..6b8b33a1 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/jquery/images/ui-bg_glass_75_dadada_1x400.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/jquery/images/ui-bg_glass_75_e6e6e6_1x400.png b/php/apps/phpmyadmin49/themes/pmahomme/jquery/images/ui-bg_glass_75_e6e6e6_1x400.png new file mode 100644 index 00000000..81e2065b Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/jquery/images/ui-bg_glass_75_e6e6e6_1x400.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/jquery/images/ui-bg_glass_95_fef1ec_1x400.png b/php/apps/phpmyadmin49/themes/pmahomme/jquery/images/ui-bg_glass_95_fef1ec_1x400.png new file mode 100644 index 00000000..7172755b Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/jquery/images/ui-bg_glass_95_fef1ec_1x400.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/jquery/images/ui-bg_highlight-soft_75_cccccc_1x100.png b/php/apps/phpmyadmin49/themes/pmahomme/jquery/images/ui-bg_highlight-soft_75_cccccc_1x100.png new file mode 100644 index 00000000..ae3ccae0 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/jquery/images/ui-bg_highlight-soft_75_cccccc_1x100.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/jquery/images/ui-icons_222222_256x240.png b/php/apps/phpmyadmin49/themes/pmahomme/jquery/images/ui-icons_222222_256x240.png new file mode 100644 index 00000000..3201d9a1 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/jquery/images/ui-icons_222222_256x240.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/jquery/images/ui-icons_2e83ff_256x240.png b/php/apps/phpmyadmin49/themes/pmahomme/jquery/images/ui-icons_2e83ff_256x240.png new file mode 100644 index 00000000..1d920d7d Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/jquery/images/ui-icons_2e83ff_256x240.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/jquery/images/ui-icons_454545_256x240.png b/php/apps/phpmyadmin49/themes/pmahomme/jquery/images/ui-icons_454545_256x240.png new file mode 100644 index 00000000..47da8e5d Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/jquery/images/ui-icons_454545_256x240.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/jquery/images/ui-icons_888888_256x240.png b/php/apps/phpmyadmin49/themes/pmahomme/jquery/images/ui-icons_888888_256x240.png new file mode 100644 index 00000000..95e0c3ef Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/jquery/images/ui-icons_888888_256x240.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/jquery/images/ui-icons_cd0a0a_256x240.png b/php/apps/phpmyadmin49/themes/pmahomme/jquery/images/ui-icons_cd0a0a_256x240.png new file mode 100644 index 00000000..0ea8a5a2 Binary files /dev/null and b/php/apps/phpmyadmin49/themes/pmahomme/jquery/images/ui-icons_cd0a0a_256x240.png differ diff --git a/php/apps/phpmyadmin49/themes/pmahomme/jquery/jquery-ui.css b/php/apps/phpmyadmin49/themes/pmahomme/jquery/jquery-ui.css new file mode 100644 index 00000000..1ebcbc90 --- /dev/null +++ b/php/apps/phpmyadmin49/themes/pmahomme/jquery/jquery-ui.css @@ -0,0 +1,1312 @@ +/*! jQuery UI - v1.12.1 - 2016-12-21 +* http://jqueryui.com +* Includes: draggable.css, core.css, resizable.css, selectable.css, sortable.css, accordion.css, autocomplete.css, menu.css, button.css, controlgroup.css, checkboxradio.css, datepicker.css, dialog.css, progressbar.css, selectmenu.css, slider.css, spinner.css, tabs.css, tooltip.css, theme.css +* To view and modify this theme, visit http://jqueryui.com/themeroller/?scope=&folderName=smoothness&cornerRadiusShadow=8px&offsetLeftShadow=-8px&offsetTopShadow=-8px&thicknessShadow=8px&opacityShadow=30&bgImgOpacityShadow=0&bgTextureShadow=flat&bgColorShadow=aaaaaa&opacityOverlay=30&bgImgOpacityOverlay=0&bgTextureOverlay=flat&bgColorOverlay=aaaaaa&iconColorError=cd0a0a&fcError=cd0a0a&borderColorError=cd0a0a&bgImgOpacityError=95&bgTextureError=glass&bgColorError=fef1ec&iconColorHighlight=2e83ff&fcHighlight=363636&borderColorHighlight=fcefa1&bgImgOpacityHighlight=55&bgTextureHighlight=glass&bgColorHighlight=fbf9ee&iconColorActive=454545&fcActive=212121&borderColorActive=aaaaaa&bgImgOpacityActive=65&bgTextureActive=glass&bgColorActive=ffffff&iconColorHover=454545&fcHover=212121&borderColorHover=999999&bgImgOpacityHover=75&bgTextureHover=glass&bgColorHover=dadada&iconColorDefault=888888&fcDefault=555555&borderColorDefault=d3d3d3&bgImgOpacityDefault=75&bgTextureDefault=glass&bgColorDefault=e6e6e6&iconColorContent=222222&fcContent=222222&borderColorContent=aaaaaa&bgImgOpacityContent=75&bgTextureContent=flat&bgColorContent=ffffff&iconColorHeader=222222&fcHeader=222222&borderColorHeader=aaaaaa&bgImgOpacityHeader=75&bgTextureHeader=highlight_soft&bgColorHeader=cccccc&cornerRadius=4px&fsDefault=1.1em&fwDefault=normal&ffDefault=Verdana%2CArial%2Csans-serif +* Copyright jQuery Foundation and other contributors; Licensed MIT */ + +.ui-draggable-handle { + -ms-touch-action: none; + touch-action: none; +} +/* Layout helpers +----------------------------------*/ +.ui-helper-hidden { + display: none; +} +.ui-helper-hidden-accessible { + border: 0; + clip: rect(0 0 0 0); + height: 1px; + margin: -1px; + overflow: hidden; + padding: 0; + position: absolute; + width: 1px; +} +.ui-helper-reset { + margin: 0; + padding: 0; + border: 0; + outline: 0; + line-height: 1.3; + text-decoration: none; + font-size: 100%; + list-style: none; +} +.ui-helper-clearfix:before, +.ui-helper-clearfix:after { + content: ""; + display: table; + border-collapse: collapse; +} +.ui-helper-clearfix:after { + clear: both; +} +.ui-helper-zfix { + width: 100%; + height: 100%; + top: 0; + left: 0; + position: absolute; + opacity: 0; + filter:Alpha(Opacity=0); /* support: IE8 */ +} + +.ui-front { + z-index: 100; +} + + +/* Interaction Cues +----------------------------------*/ +.ui-state-disabled { + cursor: default !important; + pointer-events: none; +} + + +/* Icons +----------------------------------*/ +.ui-icon { + display: inline-block; + vertical-align: middle; + margin-top: -.25em; + position: relative; + text-indent: -99999px; + overflow: hidden; + background-repeat: no-repeat; +} + +.ui-widget-icon-block { + left: 50%; + margin-left: -8px; + display: block; +} + +/* Misc visuals +----------------------------------*/ + +/* Overlays */ +.ui-widget-overlay { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; +} +.ui-resizable { + position: relative; +} +.ui-resizable-handle { + position: absolute; + font-size: 0.1px; + display: block; + -ms-touch-action: none; + touch-action: none; +} +.ui-resizable-disabled .ui-resizable-handle, +.ui-resizable-autohide .ui-resizable-handle { + display: none; +} +.ui-resizable-n { + cursor: n-resize; + height: 7px; + width: 100%; + top: -5px; + left: 0; +} +.ui-resizable-s { + cursor: s-resize; + height: 7px; + width: 100%; + bottom: -5px; + left: 0; +} +.ui-resizable-e { + cursor: e-resize; + width: 7px; + right: -5px; + top: 0; + height: 100%; +} +.ui-resizable-w { + cursor: w-resize; + width: 7px; + left: -5px; + top: 0; + height: 100%; +} +.ui-resizable-se { + cursor: se-resize; + width: 12px; + height: 12px; + right: 1px; + bottom: 1px; +} +.ui-resizable-sw { + cursor: sw-resize; + width: 9px; + height: 9px; + left: -5px; + bottom: -5px; +} +.ui-resizable-nw { + cursor: nw-resize; + width: 9px; + height: 9px; + left: -5px; + top: -5px; +} +.ui-resizable-ne { + cursor: ne-resize; + width: 9px; + height: 9px; + right: -5px; + top: -5px; +} +.ui-selectable { + -ms-touch-action: none; + touch-action: none; +} +.ui-selectable-helper { + position: absolute; + z-index: 100; + border: 1px dotted black; +} +.ui-sortable-handle { + -ms-touch-action: none; + touch-action: none; +} +.ui-accordion .ui-accordion-header { + display: block; + cursor: pointer; + position: relative; + margin: 2px 0 0 0; + padding: .5em .5em .5em .7em; + font-size: 100%; +} +.ui-accordion .ui-accordion-content { + padding: 1em 2.2em; + border-top: 0; + overflow: auto; +} +.ui-autocomplete { + position: absolute; + top: 0; + left: 0; + cursor: default; +} +.ui-menu { + list-style: none; + padding: 0; + margin: 0; + display: block; + outline: 0; +} +.ui-menu .ui-menu { + position: absolute; +} +.ui-menu .ui-menu-item { + margin: 0; + cursor: pointer; + /* support: IE10, see #8844 */ + list-style-image: url("data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7"); +} +.ui-menu .ui-menu-item-wrapper { + position: relative; + padding: 3px 1em 3px .4em; +} +.ui-menu .ui-menu-divider { + margin: 5px 0; + height: 0; + font-size: 0; + line-height: 0; + border-width: 1px 0 0 0; +} +.ui-menu .ui-state-focus, +.ui-menu .ui-state-active { + margin: -1px; +} + +/* icon support */ +.ui-menu-icons { + position: relative; +} +.ui-menu-icons .ui-menu-item-wrapper { + padding-left: 2em; +} + +/* left-aligned */ +.ui-menu .ui-icon { + position: absolute; + top: 0; + bottom: 0; + left: .2em; + margin: auto 0; +} + +/* right-aligned */ +.ui-menu .ui-menu-icon { + left: auto; + right: 0; +} +.ui-button { + padding: .4em 1em; + display: inline-block; + position: relative; + line-height: normal; + margin-right: .1em; + cursor: pointer; + vertical-align: middle; + text-align: center; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + + /* Support: IE <= 11 */ + overflow: visible; +} + +.ui-button, +.ui-button:link, +.ui-button:visited, +.ui-button:hover, +.ui-button:active { + text-decoration: none; +} + +/* to make room for the icon, a width needs to be set here */ +.ui-button-icon-only { + width: 2em; + box-sizing: border-box; + text-indent: -9999px; + white-space: nowrap; +} + +/* no icon support for input elements */ +input.ui-button.ui-button-icon-only { + text-indent: 0; +} + +/* button icon element(s) */ +.ui-button-icon-only .ui-icon { + position: absolute; + top: 50%; + left: 50%; + margin-top: -8px; + margin-left: -8px; +} + +.ui-button.ui-icon-notext .ui-icon { + padding: 0; + width: 2.1em; + height: 2.1em; + text-indent: -9999px; + white-space: nowrap; + +} + +input.ui-button.ui-icon-notext .ui-icon { + width: auto; + height: auto; + text-indent: 0; + white-space: normal; + padding: .4em 1em; +} + +/* workarounds */ +/* Support: Firefox 5 - 40 */ +input.ui-button::-moz-focus-inner, +button.ui-button::-moz-focus-inner { + border: 0; + padding: 0; +} +.ui-controlgroup { + vertical-align: middle; + display: inline-block; +} +.ui-controlgroup > .ui-controlgroup-item { + float: left; + margin-left: 0; + margin-right: 0; +} +.ui-controlgroup > .ui-controlgroup-item:focus, +.ui-controlgroup > .ui-controlgroup-item.ui-visual-focus { + z-index: 9999; +} +.ui-controlgroup-vertical > .ui-controlgroup-item { + display: block; + float: none; + width: 100%; + margin-top: 0; + margin-bottom: 0; + text-align: left; +} +.ui-controlgroup-vertical .ui-controlgroup-item { + box-sizing: border-box; +} +.ui-controlgroup .ui-controlgroup-label { + padding: .4em 1em; +} +.ui-controlgroup .ui-controlgroup-label span { + font-size: 80%; +} +.ui-controlgroup-horizontal .ui-controlgroup-label + .ui-controlgroup-item { + border-left: none; +} +.ui-controlgroup-vertical .ui-controlgroup-label + .ui-controlgroup-item { + border-top: none; +} +.ui-controlgroup-horizontal .ui-controlgroup-label.ui-widget-content { + border-right: none; +} +.ui-controlgroup-vertical .ui-controlgroup-label.ui-widget-content { + border-bottom: none; +} + +/* Spinner specific style fixes */ +.ui-controlgroup-vertical .ui-spinner-input { + + /* Support: IE8 only, Android < 4.4 only */ + width: 75%; + width: calc( 100% - 2.4em ); +} +.ui-controlgroup-vertical .ui-spinner .ui-spinner-up { + border-top-style: solid; +} + +.ui-checkboxradio-label .ui-icon-background { + box-shadow: inset 1px 1px 1px #ccc; + border-radius: .12em; + border: none; +} +.ui-checkboxradio-radio-label .ui-icon-background { + width: 16px; + height: 16px; + border-radius: 1em; + overflow: visible; + border: none; +} +.ui-checkboxradio-radio-label.ui-checkboxradio-checked .ui-icon, +.ui-checkboxradio-radio-label.ui-checkboxradio-checked:hover .ui-icon { + background-image: none; + width: 8px; + height: 8px; + border-width: 4px; + border-style: solid; +} +.ui-checkboxradio-disabled { + pointer-events: none; +} +.ui-datepicker { + width: 17em; + padding: .2em .2em 0; + display: none; +} +.ui-datepicker .ui-datepicker-header { + position: relative; + padding: .2em 0; +} +.ui-datepicker .ui-datepicker-prev, +.ui-datepicker .ui-datepicker-next { + position: absolute; + top: 2px; + width: 1.8em; + height: 1.8em; +} +.ui-datepicker .ui-datepicker-prev-hover, +.ui-datepicker .ui-datepicker-next-hover { + top: 1px; +} +.ui-datepicker .ui-datepicker-prev { + left: 2px; +} +.ui-datepicker .ui-datepicker-next { + right: 2px; +} +.ui-datepicker .ui-datepicker-prev-hover { + left: 1px; +} +.ui-datepicker .ui-datepicker-next-hover { + right: 1px; +} +.ui-datepicker .ui-datepicker-prev span, +.ui-datepicker .ui-datepicker-next span { + display: block; + position: absolute; + left: 50%; + margin-left: -8px; + top: 50%; + margin-top: -8px; +} +.ui-datepicker .ui-datepicker-title { + margin: 0 2.3em; + line-height: 1.8em; + text-align: center; +} +.ui-datepicker .ui-datepicker-title select { + font-size: 1em; + margin: 1px 0; +} +.ui-datepicker select.ui-datepicker-month, +.ui-datepicker select.ui-datepicker-year { + width: 45%; +} +.ui-datepicker table { + width: 100%; + font-size: .9em; + border-collapse: collapse; + margin: 0 0 .4em; +} +.ui-datepicker th { + padding: .7em .3em; + text-align: center; + font-weight: bold; + border: 0; +} +.ui-datepicker td { + border: 0; + padding: 1px; +} +.ui-datepicker td span, +.ui-datepicker td a { + display: block; + padding: .2em; + text-align: right; + text-decoration: none; +} +.ui-datepicker .ui-datepicker-buttonpane { + background-image: none; + margin: .7em 0 0 0; + padding: 0 .2em; + border-left: 0; + border-right: 0; + border-bottom: 0; +} +.ui-datepicker .ui-datepicker-buttonpane button { + float: right; + margin: .5em .2em .4em; + cursor: pointer; + padding: .2em .6em .3em .6em; + width: auto; + overflow: visible; +} +.ui-datepicker .ui-datepicker-buttonpane button.ui-datepicker-current { + float: left; +} + +/* with multiple calendars */ +.ui-datepicker.ui-datepicker-multi { + width: auto; +} +.ui-datepicker-multi .ui-datepicker-group { + float: left; +} +.ui-datepicker-multi .ui-datepicker-group table { + width: 95%; + margin: 0 auto .4em; +} +.ui-datepicker-multi-2 .ui-datepicker-group { + width: 50%; +} +.ui-datepicker-multi-3 .ui-datepicker-group { + width: 33.3%; +} +.ui-datepicker-multi-4 .ui-datepicker-group { + width: 25%; +} +.ui-datepicker-multi .ui-datepicker-group-last .ui-datepicker-header, +.ui-datepicker-multi .ui-datepicker-group-middle .ui-datepicker-header { + border-left-width: 0; +} +.ui-datepicker-multi .ui-datepicker-buttonpane { + clear: left; +} +.ui-datepicker-row-break { + clear: both; + width: 100%; + font-size: 0; +} + +/* RTL support */ +.ui-datepicker-rtl { + direction: rtl; +} +.ui-datepicker-rtl .ui-datepicker-prev { + right: 2px; + left: auto; +} +.ui-datepicker-rtl .ui-datepicker-next { + left: 2px; + right: auto; +} +.ui-datepicker-rtl .ui-datepicker-prev:hover { + right: 1px; + left: auto; +} +.ui-datepicker-rtl .ui-datepicker-next:hover { + left: 1px; + right: auto; +} +.ui-datepicker-rtl .ui-datepicker-buttonpane { + clear: right; +} +.ui-datepicker-rtl .ui-datepicker-buttonpane button { + float: left; +} +.ui-datepicker-rtl .ui-datepicker-buttonpane button.ui-datepicker-current, +.ui-datepicker-rtl .ui-datepicker-group { + float: right; +} +.ui-datepicker-rtl .ui-datepicker-group-last .ui-datepicker-header, +.ui-datepicker-rtl .ui-datepicker-group-middle .ui-datepicker-header { + border-right-width: 0; + border-left-width: 1px; +} + +/* Icons */ +.ui-datepicker .ui-icon { + display: block; + text-indent: -99999px; + overflow: hidden; + background-repeat: no-repeat; + left: .5em; + top: .3em; +} +.ui-dialog { + position: absolute; + top: 0; + left: 0; + padding: .2em; + outline: 0; +} +.ui-dialog .ui-dialog-titlebar { + padding: .4em 1em; + position: relative; +} +.ui-dialog .ui-dialog-title { + float: left; + margin: .1em 0; + white-space: nowrap; + width: 90%; + overflow: hidden; + text-overflow: ellipsis; +} +.ui-dialog .ui-dialog-titlebar-close { + position: absolute; + right: .3em; + top: 50%; + width: 20px; + margin: -10px 0 0 0; + padding: 1px; + height: 20px; +} +.ui-dialog .ui-dialog-content { + position: relative; + border: 0; + padding: .5em 1em; + background: none; + overflow: auto; +} +.ui-dialog .ui-dialog-buttonpane { + text-align: left; + border-width: 1px 0 0 0; + background-image: none; + margin-top: .5em; + padding: .3em 1em .5em .4em; +} +.ui-dialog .ui-dialog-buttonpane .ui-dialog-buttonset { + float: right; +} +.ui-dialog .ui-dialog-buttonpane button { + margin: .5em .4em .5em 0; + cursor: pointer; +} +.ui-dialog .ui-resizable-n { + height: 2px; + top: 0; +} +.ui-dialog .ui-resizable-e { + width: 2px; + right: 0; +} +.ui-dialog .ui-resizable-s { + height: 2px; + bottom: 0; +} +.ui-dialog .ui-resizable-w { + width: 2px; + left: 0; +} +.ui-dialog .ui-resizable-se, +.ui-dialog .ui-resizable-sw, +.ui-dialog .ui-resizable-ne, +.ui-dialog .ui-resizable-nw { + width: 7px; + height: 7px; +} +.ui-dialog .ui-resizable-se { + right: 0; + bottom: 0; +} +.ui-dialog .ui-resizable-sw { + left: 0; + bottom: 0; +} +.ui-dialog .ui-resizable-ne { + right: 0; + top: 0; +} +.ui-dialog .ui-resizable-nw { + left: 0; + top: 0; +} +.ui-draggable .ui-dialog-titlebar { + cursor: move; +} +.ui-progressbar { + height: 2em; + text-align: left; + overflow: hidden; +} +.ui-progressbar .ui-progressbar-value { + margin: -1px; + height: 100%; +} +.ui-progressbar .ui-progressbar-overlay { + background: url("data:image/gif;base64,R0lGODlhKAAoAIABAAAAAP///yH/C05FVFNDQVBFMi4wAwEAAAAh+QQJAQABACwAAAAAKAAoAAACkYwNqXrdC52DS06a7MFZI+4FHBCKoDeWKXqymPqGqxvJrXZbMx7Ttc+w9XgU2FB3lOyQRWET2IFGiU9m1frDVpxZZc6bfHwv4c1YXP6k1Vdy292Fb6UkuvFtXpvWSzA+HycXJHUXiGYIiMg2R6W459gnWGfHNdjIqDWVqemH2ekpObkpOlppWUqZiqr6edqqWQAAIfkECQEAAQAsAAAAACgAKAAAApSMgZnGfaqcg1E2uuzDmmHUBR8Qil95hiPKqWn3aqtLsS18y7G1SzNeowWBENtQd+T1JktP05nzPTdJZlR6vUxNWWjV+vUWhWNkWFwxl9VpZRedYcflIOLafaa28XdsH/ynlcc1uPVDZxQIR0K25+cICCmoqCe5mGhZOfeYSUh5yJcJyrkZWWpaR8doJ2o4NYq62lAAACH5BAkBAAEALAAAAAAoACgAAAKVDI4Yy22ZnINRNqosw0Bv7i1gyHUkFj7oSaWlu3ovC8GxNso5fluz3qLVhBVeT/Lz7ZTHyxL5dDalQWPVOsQWtRnuwXaFTj9jVVh8pma9JjZ4zYSj5ZOyma7uuolffh+IR5aW97cHuBUXKGKXlKjn+DiHWMcYJah4N0lYCMlJOXipGRr5qdgoSTrqWSq6WFl2ypoaUAAAIfkECQEAAQAsAAAAACgAKAAAApaEb6HLgd/iO7FNWtcFWe+ufODGjRfoiJ2akShbueb0wtI50zm02pbvwfWEMWBQ1zKGlLIhskiEPm9R6vRXxV4ZzWT2yHOGpWMyorblKlNp8HmHEb/lCXjcW7bmtXP8Xt229OVWR1fod2eWqNfHuMjXCPkIGNileOiImVmCOEmoSfn3yXlJWmoHGhqp6ilYuWYpmTqKUgAAIfkECQEAAQAsAAAAACgAKAAAApiEH6kb58biQ3FNWtMFWW3eNVcojuFGfqnZqSebuS06w5V80/X02pKe8zFwP6EFWOT1lDFk8rGERh1TTNOocQ61Hm4Xm2VexUHpzjymViHrFbiELsefVrn6XKfnt2Q9G/+Xdie499XHd2g4h7ioOGhXGJboGAnXSBnoBwKYyfioubZJ2Hn0RuRZaflZOil56Zp6iioKSXpUAAAh+QQJAQABACwAAAAAKAAoAAACkoQRqRvnxuI7kU1a1UU5bd5tnSeOZXhmn5lWK3qNTWvRdQxP8qvaC+/yaYQzXO7BMvaUEmJRd3TsiMAgswmNYrSgZdYrTX6tSHGZO73ezuAw2uxuQ+BbeZfMxsexY35+/Qe4J1inV0g4x3WHuMhIl2jXOKT2Q+VU5fgoSUI52VfZyfkJGkha6jmY+aaYdirq+lQAACH5BAkBAAEALAAAAAAoACgAAAKWBIKpYe0L3YNKToqswUlvznigd4wiR4KhZrKt9Upqip61i9E3vMvxRdHlbEFiEXfk9YARYxOZZD6VQ2pUunBmtRXo1Lf8hMVVcNl8JafV38aM2/Fu5V16Bn63r6xt97j09+MXSFi4BniGFae3hzbH9+hYBzkpuUh5aZmHuanZOZgIuvbGiNeomCnaxxap2upaCZsq+1kAACH5BAkBAAEALAAAAAAoACgAAAKXjI8By5zf4kOxTVrXNVlv1X0d8IGZGKLnNpYtm8Lr9cqVeuOSvfOW79D9aDHizNhDJidFZhNydEahOaDH6nomtJjp1tutKoNWkvA6JqfRVLHU/QUfau9l2x7G54d1fl995xcIGAdXqMfBNadoYrhH+Mg2KBlpVpbluCiXmMnZ2Sh4GBqJ+ckIOqqJ6LmKSllZmsoq6wpQAAAh+QQJAQABACwAAAAAKAAoAAAClYx/oLvoxuJDkU1a1YUZbJ59nSd2ZXhWqbRa2/gF8Gu2DY3iqs7yrq+xBYEkYvFSM8aSSObE+ZgRl1BHFZNr7pRCavZ5BW2142hY3AN/zWtsmf12p9XxxFl2lpLn1rseztfXZjdIWIf2s5dItwjYKBgo9yg5pHgzJXTEeGlZuenpyPmpGQoKOWkYmSpaSnqKileI2FAAACH5BAkBAAEALAAAAAAoACgAAAKVjB+gu+jG4kORTVrVhRlsnn2dJ3ZleFaptFrb+CXmO9OozeL5VfP99HvAWhpiUdcwkpBH3825AwYdU8xTqlLGhtCosArKMpvfa1mMRae9VvWZfeB2XfPkeLmm18lUcBj+p5dnN8jXZ3YIGEhYuOUn45aoCDkp16hl5IjYJvjWKcnoGQpqyPlpOhr3aElaqrq56Bq7VAAAOw=="); + height: 100%; + filter: alpha(opacity=25); /* support: IE8 */ + opacity: 0.25; +} +.ui-progressbar-indeterminate .ui-progressbar-value { + background-image: none; +} +.ui-selectmenu-menu { + padding: 0; + margin: 0; + position: absolute; + top: 0; + left: 0; + display: none; +} +.ui-selectmenu-menu .ui-menu { + overflow: auto; + overflow-x: hidden; + padding-bottom: 1px; +} +.ui-selectmenu-menu .ui-menu .ui-selectmenu-optgroup { + font-size: 1em; + font-weight: bold; + line-height: 1.5; + padding: 2px 0.4em; + margin: 0.5em 0 0 0; + height: auto; + border: 0; +} +.ui-selectmenu-open { + display: block; +} +.ui-selectmenu-text { + display: block; + margin-right: 20px; + overflow: hidden; + text-overflow: ellipsis; +} +.ui-selectmenu-button.ui-button { + text-align: left; + white-space: nowrap; + width: 14em; +} +.ui-selectmenu-icon.ui-icon { + float: right; + margin-top: 0; +} +.ui-slider { + position: relative; + text-align: left; +} +.ui-slider .ui-slider-handle { + position: absolute; + z-index: 2; + width: 1.2em; + height: 1.2em; + cursor: default; + -ms-touch-action: none; + touch-action: none; +} +.ui-slider .ui-slider-range { + position: absolute; + z-index: 1; + font-size: .7em; + display: block; + border: 0; + background-position: 0 0; +} + +/* support: IE8 - See #6727 */ +.ui-slider.ui-state-disabled .ui-slider-handle, +.ui-slider.ui-state-disabled .ui-slider-range { + filter: inherit; +} + +.ui-slider-horizontal { + height: .8em; +} +.ui-slider-horizontal .ui-slider-handle { + top: -.3em; + margin-left: -.6em; +} +.ui-slider-horizontal .ui-slider-range { + top: 0; + height: 100%; +} +.ui-slider-horizontal .ui-slider-range-min { + left: 0; +} +.ui-slider-horizontal .ui-slider-range-max { + right: 0; +} + +.ui-slider-vertical { + width: .8em; + height: 100px; +} +.ui-slider-vertical .ui-slider-handle { + left: -.3em; + margin-left: 0; + margin-bottom: -.6em; +} +.ui-slider-vertical .ui-slider-range { + left: 0; + width: 100%; +} +.ui-slider-vertical .ui-slider-range-min { + bottom: 0; +} +.ui-slider-vertical .ui-slider-range-max { + top: 0; +} +.ui-spinner { + position: relative; + display: inline-block; + overflow: hidden; + padding: 0; + vertical-align: middle; +} +.ui-spinner-input { + border: none; + background: none; + color: inherit; + padding: .222em 0; + margin: .2em 0; + vertical-align: middle; + margin-left: .4em; + margin-right: 2em; +} +.ui-spinner-button { + width: 1.6em; + height: 50%; + font-size: .5em; + padding: 0; + margin: 0; + text-align: center; + position: absolute; + cursor: default; + display: block; + overflow: hidden; + right: 0; +} +/* more specificity required here to override default borders */ +.ui-spinner a.ui-spinner-button { + border-top-style: none; + border-bottom-style: none; + border-right-style: none; +} +.ui-spinner-up { + top: 0; +} +.ui-spinner-down { + bottom: 0; +} +.ui-tabs { + position: relative;/* position: relative prevents IE scroll bug (element with position: relative inside container with overflow: auto appear as "fixed") */ + padding: .2em; +} +.ui-tabs .ui-tabs-nav { + margin: 0; + padding: .2em .2em 0; +} +.ui-tabs .ui-tabs-nav li { + list-style: none; + float: left; + position: relative; + top: 0; + margin: 1px .2em 0 0; + border-bottom-width: 0; + padding: 0; + white-space: nowrap; +} +.ui-tabs .ui-tabs-nav .ui-tabs-anchor { + float: left; + padding: .5em 1em; + text-decoration: none; +} +.ui-tabs .ui-tabs-nav li.ui-tabs-active { + margin-bottom: -1px; + padding-bottom: 1px; +} +.ui-tabs .ui-tabs-nav li.ui-tabs-active .ui-tabs-anchor, +.ui-tabs .ui-tabs-nav li.ui-state-disabled .ui-tabs-anchor, +.ui-tabs .ui-tabs-nav li.ui-tabs-loading .ui-tabs-anchor { + cursor: text; +} +.ui-tabs-collapsible .ui-tabs-nav li.ui-tabs-active .ui-tabs-anchor { + cursor: pointer; +} +.ui-tabs .ui-tabs-panel { + display: block; + border-width: 0; + padding: 1em 1.4em; + background: none; +} +.ui-tooltip { + padding: 8px; + position: absolute; + z-index: 9999; + max-width: 300px; +} +body .ui-tooltip { + border-width: 2px; +} + +/* Component containers +----------------------------------*/ +.ui-widget { + font-family: Verdana,Arial,sans-serif; + font-size: 1.1em; +} +.ui-widget .ui-widget { + font-size: 1em; +} +.ui-widget input, +.ui-widget select, +.ui-widget textarea, +.ui-widget button { + font-family: Verdana,Arial,sans-serif; + font-size: 1em; +} +.ui-widget.ui-widget-content { + border: 1px solid #d3d3d3; +} +.ui-widget-content { + border: 1px solid #aaaaaa; + background: #ffffff; + color: #222222; +} +.ui-widget-content a { + color: #222222; +} +.ui-widget-header { + border: 1px solid #aaaaaa; + background: #cccccc url("images/ui-bg_highlight-soft_75_cccccc_1x100.png") 50% 50% repeat-x; + color: #222222; + font-weight: bold; +} +.ui-widget-header a { + color: #222222; +} + +/* Interaction states +----------------------------------*/ +.ui-state-default, +.ui-widget-content .ui-state-default, +.ui-widget-header .ui-state-default, +.ui-button, + +/* We use html here because we need a greater specificity to make sure disabled +works properly when clicked or hovered */ +html .ui-button.ui-state-disabled:hover, +html .ui-button.ui-state-disabled:active { + border: 1px solid #d3d3d3; + background: #e6e6e6 url("images/ui-bg_glass_75_e6e6e6_1x400.png") 50% 50% repeat-x; + font-weight: normal; + color: #555555; +} +.ui-state-default a, +.ui-state-default a:link, +.ui-state-default a:visited, +a.ui-button, +a:link.ui-button, +a:visited.ui-button, +.ui-button { + color: #555555; + text-decoration: none; +} +.ui-state-hover, +.ui-widget-content .ui-state-hover, +.ui-widget-header .ui-state-hover, +.ui-state-focus, +.ui-widget-content .ui-state-focus, +.ui-widget-header .ui-state-focus, +.ui-button:hover, +.ui-button:focus { + border: 1px solid #999999; + background: #dadada url("images/ui-bg_glass_75_dadada_1x400.png") 50% 50% repeat-x; + font-weight: normal; + color: #212121; +} +.ui-state-hover a, +.ui-state-hover a:hover, +.ui-state-hover a:link, +.ui-state-hover a:visited, +.ui-state-focus a, +.ui-state-focus a:hover, +.ui-state-focus a:link, +.ui-state-focus a:visited, +a.ui-button:hover, +a.ui-button:focus { + color: #212121; + text-decoration: none; +} + +.ui-visual-focus { + box-shadow: 0 0 3px 1px rgb(94, 158, 214); +} +.ui-state-active, +.ui-widget-content .ui-state-active, +.ui-widget-header .ui-state-active, +a.ui-button:active, +.ui-button:active, +.ui-button.ui-state-active:hover { + border: 1px solid #aaaaaa; + background: #ffffff url("images/ui-bg_glass_65_ffffff_1x400.png") 50% 50% repeat-x; + font-weight: normal; + color: #212121; +} +.ui-icon-background, +.ui-state-active .ui-icon-background { + border: #aaaaaa; + background-color: #212121; +} +.ui-state-active a, +.ui-state-active a:link, +.ui-state-active a:visited { + color: #212121; + text-decoration: none; +} + +/* Interaction Cues +----------------------------------*/ +.ui-state-highlight, +.ui-widget-content .ui-state-highlight, +.ui-widget-header .ui-state-highlight { + border: 1px solid #fcefa1; + background: #fbf9ee url("images/ui-bg_glass_55_fbf9ee_1x400.png") 50% 50% repeat-x; + color: #363636; +} +.ui-state-checked { + border: 1px solid #fcefa1; + background: #fbf9ee; +} +.ui-state-highlight a, +.ui-widget-content .ui-state-highlight a, +.ui-widget-header .ui-state-highlight a { + color: #363636; +} +.ui-state-error, +.ui-widget-content .ui-state-error, +.ui-widget-header .ui-state-error { + border: 1px solid #cd0a0a; + background: #fef1ec url("images/ui-bg_glass_95_fef1ec_1x400.png") 50% 50% repeat-x; + color: #cd0a0a; +} +.ui-state-error a, +.ui-widget-content .ui-state-error a, +.ui-widget-header .ui-state-error a { + color: #cd0a0a; +} +.ui-state-error-text, +.ui-widget-content .ui-state-error-text, +.ui-widget-header .ui-state-error-text { + color: #cd0a0a; +} +.ui-priority-primary, +.ui-widget-content .ui-priority-primary, +.ui-widget-header .ui-priority-primary { + font-weight: bold; +} +.ui-priority-secondary, +.ui-widget-content .ui-priority-secondary, +.ui-widget-header .ui-priority-secondary { + opacity: .7; + filter:Alpha(Opacity=70); /* support: IE8 */ + font-weight: normal; +} +.ui-state-disabled, +.ui-widget-content .ui-state-disabled, +.ui-widget-header .ui-state-disabled { + opacity: .35; + filter:Alpha(Opacity=35); /* support: IE8 */ + background-image: none; +} +.ui-state-disabled .ui-icon { + filter:Alpha(Opacity=35); /* support: IE8 - See #6059 */ +} + +/* Icons +----------------------------------*/ + +/* states and images */ +.ui-icon { + width: 16px; + height: 16px; +} +.ui-icon, +.ui-widget-content .ui-icon { + background-image: url("images/ui-icons_222222_256x240.png"); +} +.ui-widget-header .ui-icon { + background-image: url("images/ui-icons_222222_256x240.png"); +} +.ui-state-hover .ui-icon, +.ui-state-focus .ui-icon, +.ui-button:hover .ui-icon, +.ui-button:focus .ui-icon { + background-image: url("images/ui-icons_454545_256x240.png"); +} +.ui-state-active .ui-icon, +.ui-button:active .ui-icon { + background-image: url("images/ui-icons_454545_256x240.png"); +} +.ui-state-highlight .ui-icon, +.ui-button .ui-state-highlight.ui-icon { + background-image: url("images/ui-icons_2e83ff_256x240.png"); +} +.ui-state-error .ui-icon, +.ui-state-error-text .ui-icon { + background-image: url("images/ui-icons_cd0a0a_256x240.png"); +} +.ui-button .ui-icon { + background-image: url("images/ui-icons_888888_256x240.png"); +} + +/* positioning */ +.ui-icon-blank { background-position: 16px 16px; } +.ui-icon-caret-1-n { background-position: 0 0; } +.ui-icon-caret-1-ne { background-position: -16px 0; } +.ui-icon-caret-1-e { background-position: -32px 0; } +.ui-icon-caret-1-se { background-position: -48px 0; } +.ui-icon-caret-1-s { background-position: -65px 0; } +.ui-icon-caret-1-sw { background-position: -80px 0; } +.ui-icon-caret-1-w { background-position: -96px 0; } +.ui-icon-caret-1-nw { background-position: -112px 0; } +.ui-icon-caret-2-n-s { background-position: -128px 0; } +.ui-icon-caret-2-e-w { background-position: -144px 0; } +.ui-icon-triangle-1-n { background-position: 0 -16px; } +.ui-icon-triangle-1-ne { background-position: -16px -16px; } +.ui-icon-triangle-1-e { background-position: -32px -16px; } +.ui-icon-triangle-1-se { background-position: -48px -16px; } +.ui-icon-triangle-1-s { background-position: -65px -16px; } +.ui-icon-triangle-1-sw { background-position: -80px -16px; } +.ui-icon-triangle-1-w { background-position: -96px -16px; } +.ui-icon-triangle-1-nw { background-position: -112px -16px; } +.ui-icon-triangle-2-n-s { background-position: -128px -16px; } +.ui-icon-triangle-2-e-w { background-position: -144px -16px; } +.ui-icon-arrow-1-n { background-position: 0 -32px; } +.ui-icon-arrow-1-ne { background-position: -16px -32px; } +.ui-icon-arrow-1-e { background-position: -32px -32px; } +.ui-icon-arrow-1-se { background-position: -48px -32px; } +.ui-icon-arrow-1-s { background-position: -65px -32px; } +.ui-icon-arrow-1-sw { background-position: -80px -32px; } +.ui-icon-arrow-1-w { background-position: -96px -32px; } +.ui-icon-arrow-1-nw { background-position: -112px -32px; } +.ui-icon-arrow-2-n-s { background-position: -128px -32px; } +.ui-icon-arrow-2-ne-sw { background-position: -144px -32px; } +.ui-icon-arrow-2-e-w { background-position: -160px -32px; } +.ui-icon-arrow-2-se-nw { background-position: -176px -32px; } +.ui-icon-arrowstop-1-n { background-position: -192px -32px; } +.ui-icon-arrowstop-1-e { background-position: -208px -32px; } +.ui-icon-arrowstop-1-s { background-position: -224px -32px; } +.ui-icon-arrowstop-1-w { background-position: -240px -32px; } +.ui-icon-arrowthick-1-n { background-position: 1px -48px; } +.ui-icon-arrowthick-1-ne { background-position: -16px -48px; } +.ui-icon-arrowthick-1-e { background-position: -32px -48px; } +.ui-icon-arrowthick-1-se { background-position: -48px -48px; } +.ui-icon-arrowthick-1-s { background-position: -64px -48px; } +.ui-icon-arrowthick-1-sw { background-position: -80px -48px; } +.ui-icon-arrowthick-1-w { background-position: -96px -48px; } +.ui-icon-arrowthick-1-nw { background-position: -112px -48px; } +.ui-icon-arrowthick-2-n-s { background-position: -128px -48px; } +.ui-icon-arrowthick-2-ne-sw { background-position: -144px -48px; } +.ui-icon-arrowthick-2-e-w { background-position: -160px -48px; } +.ui-icon-arrowthick-2-se-nw { background-position: -176px -48px; } +.ui-icon-arrowthickstop-1-n { background-position: -192px -48px; } +.ui-icon-arrowthickstop-1-e { background-position: -208px -48px; } +.ui-icon-arrowthickstop-1-s { background-position: -224px -48px; } +.ui-icon-arrowthickstop-1-w { background-position: -240px -48px; } +.ui-icon-arrowreturnthick-1-w { background-position: 0 -64px; } +.ui-icon-arrowreturnthick-1-n { background-position: -16px -64px; } +.ui-icon-arrowreturnthick-1-e { background-position: -32px -64px; } +.ui-icon-arrowreturnthick-1-s { background-position: -48px -64px; } +.ui-icon-arrowreturn-1-w { background-position: -64px -64px; } +.ui-icon-arrowreturn-1-n { background-position: -80px -64px; } +.ui-icon-arrowreturn-1-e { background-position: -96px -64px; } +.ui-icon-arrowreturn-1-s { background-position: -112px -64px; } +.ui-icon-arrowrefresh-1-w { background-position: -128px -64px; } +.ui-icon-arrowrefresh-1-n { background-position: -144px -64px; } +.ui-icon-arrowrefresh-1-e { background-position: -160px -64px; } +.ui-icon-arrowrefresh-1-s { background-position: -176px -64px; } +.ui-icon-arrow-4 { background-position: 0 -80px; } +.ui-icon-arrow-4-diag { background-position: -16px -80px; } +.ui-icon-extlink { background-position: -32px -80px; } +.ui-icon-newwin { background-position: -48px -80px; } +.ui-icon-refresh { background-position: -64px -80px; } +.ui-icon-shuffle { background-position: -80px -80px; } +.ui-icon-transfer-e-w { background-position: -96px -80px; } +.ui-icon-transferthick-e-w { background-position: -112px -80px; } +.ui-icon-folder-collapsed { background-position: 0 -96px; } +.ui-icon-folder-open { background-position: -16px -96px; } +.ui-icon-document { background-position: -32px -96px; } +.ui-icon-document-b { background-position: -48px -96px; } +.ui-icon-note { background-position: -64px -96px; } +.ui-icon-mail-closed { background-position: -80px -96px; } +.ui-icon-mail-open { background-position: -96px -96px; } +.ui-icon-suitcase { background-position: -112px -96px; } +.ui-icon-comment { background-position: -128px -96px; } +.ui-icon-person { background-position: -144px -96px; } +.ui-icon-print { background-position: -160px -96px; } +.ui-icon-trash { background-position: -176px -96px; } +.ui-icon-locked { background-position: -192px -96px; } +.ui-icon-unlocked { background-position: -208px -96px; } +.ui-icon-bookmark { background-position: -224px -96px; } +.ui-icon-tag { background-position: -240px -96px; } +.ui-icon-home { background-position: 0 -112px; } +.ui-icon-flag { background-position: -16px -112px; } +.ui-icon-calendar { background-position: -32px -112px; } +.ui-icon-cart { background-position: -48px -112px; } +.ui-icon-pencil { background-position: -64px -112px; } +.ui-icon-clock { background-position: -80px -112px; } +.ui-icon-disk { background-position: -96px -112px; } +.ui-icon-calculator { background-position: -112px -112px; } +.ui-icon-zoomin { background-position: -128px -112px; } +.ui-icon-zoomout { background-position: -144px -112px; } +.ui-icon-search { background-position: -160px -112px; } +.ui-icon-wrench { background-position: -176px -112px; } +.ui-icon-gear { background-position: -192px -112px; } +.ui-icon-heart { background-position: -208px -112px; } +.ui-icon-star { background-position: -224px -112px; } +.ui-icon-link { background-position: -240px -112px; } +.ui-icon-cancel { background-position: 0 -128px; } +.ui-icon-plus { background-position: -16px -128px; } +.ui-icon-plusthick { background-position: -32px -128px; } +.ui-icon-minus { background-position: -48px -128px; } +.ui-icon-minusthick { background-position: -64px -128px; } +.ui-icon-close { background-position: -80px -128px; } +.ui-icon-closethick { background-position: -96px -128px; } +.ui-icon-key { background-position: -112px -128px; } +.ui-icon-lightbulb { background-position: -128px -128px; } +.ui-icon-scissors { background-position: -144px -128px; } +.ui-icon-clipboard { background-position: -160px -128px; } +.ui-icon-copy { background-position: -176px -128px; } +.ui-icon-contact { background-position: -192px -128px; } +.ui-icon-image { background-position: -208px -128px; } +.ui-icon-video { background-position: -224px -128px; } +.ui-icon-script { background-position: -240px -128px; } +.ui-icon-alert { background-position: 0 -144px; } +.ui-icon-info { background-position: -16px -144px; } +.ui-icon-notice { background-position: -32px -144px; } +.ui-icon-help { background-position: -48px -144px; } +.ui-icon-check { background-position: -64px -144px; } +.ui-icon-bullet { background-position: -80px -144px; } +.ui-icon-radio-on { background-position: -96px -144px; } +.ui-icon-radio-off { background-position: -112px -144px; } +.ui-icon-pin-w { background-position: -128px -144px; } +.ui-icon-pin-s { background-position: -144px -144px; } +.ui-icon-play { background-position: 0 -160px; } +.ui-icon-pause { background-position: -16px -160px; } +.ui-icon-seek-next { background-position: -32px -160px; } +.ui-icon-seek-prev { background-position: -48px -160px; } +.ui-icon-seek-end { background-position: -64px -160px; } +.ui-icon-seek-start { background-position: -80px -160px; } +/* ui-icon-seek-first is deprecated, use ui-icon-seek-start instead */ +.ui-icon-seek-first { background-position: -80px -160px; } +.ui-icon-stop { background-position: -96px -160px; } +.ui-icon-eject { background-position: -112px -160px; } +.ui-icon-volume-off { background-position: -128px -160px; } +.ui-icon-volume-on { background-position: -144px -160px; } +.ui-icon-power { background-position: 0 -176px; } +.ui-icon-signal-diag { background-position: -16px -176px; } +.ui-icon-signal { background-position: -32px -176px; } +.ui-icon-battery-0 { background-position: -48px -176px; } +.ui-icon-battery-1 { background-position: -64px -176px; } +.ui-icon-battery-2 { background-position: -80px -176px; } +.ui-icon-battery-3 { background-position: -96px -176px; } +.ui-icon-circle-plus { background-position: 0 -192px; } +.ui-icon-circle-minus { background-position: -16px -192px; } +.ui-icon-circle-close { background-position: -32px -192px; } +.ui-icon-circle-triangle-e { background-position: -48px -192px; } +.ui-icon-circle-triangle-s { background-position: -64px -192px; } +.ui-icon-circle-triangle-w { background-position: -80px -192px; } +.ui-icon-circle-triangle-n { background-position: -96px -192px; } +.ui-icon-circle-arrow-e { background-position: -112px -192px; } +.ui-icon-circle-arrow-s { background-position: -128px -192px; } +.ui-icon-circle-arrow-w { background-position: -144px -192px; } +.ui-icon-circle-arrow-n { background-position: -160px -192px; } +.ui-icon-circle-zoomin { background-position: -176px -192px; } +.ui-icon-circle-zoomout { background-position: -192px -192px; } +.ui-icon-circle-check { background-position: -208px -192px; } +.ui-icon-circlesmall-plus { background-position: 0 -208px; } +.ui-icon-circlesmall-minus { background-position: -16px -208px; } +.ui-icon-circlesmall-close { background-position: -32px -208px; } +.ui-icon-squaresmall-plus { background-position: -48px -208px; } +.ui-icon-squaresmall-minus { background-position: -64px -208px; } +.ui-icon-squaresmall-close { background-position: -80px -208px; } +.ui-icon-grip-dotted-vertical { background-position: 0 -224px; } +.ui-icon-grip-dotted-horizontal { background-position: -16px -224px; } +.ui-icon-grip-solid-vertical { background-position: -32px -224px; } +.ui-icon-grip-solid-horizontal { background-position: -48px -224px; } +.ui-icon-gripsmall-diagonal-se { background-position: -64px -224px; } +.ui-icon-grip-diagonal-se { background-position: -80px -224px; } + + +/* Misc visuals +----------------------------------*/ + +/* Corner radius */ +.ui-corner-all, +.ui-corner-top, +.ui-corner-left, +.ui-corner-tl { + border-top-left-radius: 4px; +} +.ui-corner-all, +.ui-corner-top, +.ui-corner-right, +.ui-corner-tr { + border-top-right-radius: 4px; +} +.ui-corner-all, +.ui-corner-bottom, +.ui-corner-left, +.ui-corner-bl { + border-bottom-left-radius: 4px; +} +.ui-corner-all, +.ui-corner-bottom, +.ui-corner-right, +.ui-corner-br { + border-bottom-right-radius: 4px; +} + +/* Overlays */ +.ui-widget-overlay { + background: #aaaaaa; + opacity: .3; + filter: Alpha(Opacity=30); /* support: IE8 */ +} +.ui-widget-shadow { + -webkit-box-shadow: -8px -8px 8px #aaaaaa; + box-shadow: -8px -8px 8px #aaaaaa; +} diff --git a/php/apps/phpmyadmin49/themes/pmahomme/layout.inc.php b/php/apps/phpmyadmin49/themes/pmahomme/layout.inc.php new file mode 100644 index 00000000..6ac5ddce --- /dev/null +++ b/php/apps/phpmyadmin49/themes/pmahomme/layout.inc.php @@ -0,0 +1,77 @@ + array('regexp' => '/^[a-z0-9]+$/i')); + $color = filter_input(INPUT_GET, $get_name, FILTER_VALIDATE_REGEXP, $opts); + if (preg_match('/^[a-f0-9]{6}$/', $color)) { + return '#' . $color; + } + return $color ? $color : $default; +} + +$from = PMA_gradientGetColor('from', 'white'); +$to = PMA_gradientGetColor('to', 'blank'); + +echo ''; +?> + + + + + + + + + diff --git a/php/apps/phpmyadmin49/tmp/twig/05/058ca0a8a75258a784fcb1d1b730ae625babab17d0b5862bfb34361faadd6d59.php b/php/apps/phpmyadmin49/tmp/twig/05/058ca0a8a75258a784fcb1d1b730ae625babab17d0b5862bfb34361faadd6d59.php new file mode 100644 index 00000000..d84de0f7 --- /dev/null +++ b/php/apps/phpmyadmin49/tmp/twig/05/058ca0a8a75258a784fcb1d1b730ae625babab17d0b5862bfb34361faadd6d59.php @@ -0,0 +1,83 @@ +source = $this->getSourceContext(); + + $this->parent = false; + + $this->blocks = [ + ]; + } + + protected function doDisplay(array $context, array $blocks = []) + { + $macros = $this->macros; + // line 1 + if ((twig_get_attribute($this->env, $this->source, ($context["comments_map"] ?? null), twig_get_attribute($this->env, $this->source, ($context["fields_meta"] ?? null), "table", [], "any", false, false, false, 1), [], "array", true, true, false, 1) && twig_get_attribute($this->env, $this->source, twig_get_attribute($this->env, $this->source, // line 2 +($context["comments_map"] ?? null), twig_get_attribute($this->env, $this->source, ($context["fields_meta"] ?? null), "table", [], "any", false, false, false, 2), [], "array", false, true, false, 2), twig_get_attribute($this->env, $this->source, ($context["fields_meta"] ?? null), "name", [], "any", false, false, false, 2), [], "array", true, true, false, 2))) { + // line 3 + echo " env, (($__internal_f607aeef2c31a95a7bf963452dff024ffaeb6aafbe4603f9ca3bec57be8633f4 = (($__internal_62824350bc4502ee19dbc2e99fc6bdd3bd90e7d8dd6e72f42c35efd048542144 = ($context["comments_map"] ?? null)) && is_array($__internal_62824350bc4502ee19dbc2e99fc6bdd3bd90e7d8dd6e72f42c35efd048542144) || $__internal_62824350bc4502ee19dbc2e99fc6bdd3bd90e7d8dd6e72f42c35efd048542144 instanceof ArrayAccess ? ($__internal_62824350bc4502ee19dbc2e99fc6bdd3bd90e7d8dd6e72f42c35efd048542144[twig_get_attribute($this->env, $this->source, ($context["fields_meta"] ?? null), "table", [], "any", false, false, false, 3)] ?? null) : null)) && is_array($__internal_f607aeef2c31a95a7bf963452dff024ffaeb6aafbe4603f9ca3bec57be8633f4) || $__internal_f607aeef2c31a95a7bf963452dff024ffaeb6aafbe4603f9ca3bec57be8633f4 instanceof ArrayAccess ? ($__internal_f607aeef2c31a95a7bf963452dff024ffaeb6aafbe4603f9ca3bec57be8633f4[twig_get_attribute($this->env, $this->source, ($context["fields_meta"] ?? null), "name", [], "any", false, false, false, 3)] ?? null) : null), "html", null, true); + echo "\"> + "; + // line 4 + if ((twig_length_filter($this->env, (($__internal_1cfccaec8dd2e8578ccb026fbe7f2e7e29ac2ed5deb976639c5fc99a6ea8583b = (($__internal_68aa442c1d43d3410ea8f958ba9090f3eaa9a76f8de8fc9be4d6c7389ba28002 = ($context["comments_map"] ?? null)) && is_array($__internal_68aa442c1d43d3410ea8f958ba9090f3eaa9a76f8de8fc9be4d6c7389ba28002) || $__internal_68aa442c1d43d3410ea8f958ba9090f3eaa9a76f8de8fc9be4d6c7389ba28002 instanceof ArrayAccess ? ($__internal_68aa442c1d43d3410ea8f958ba9090f3eaa9a76f8de8fc9be4d6c7389ba28002[twig_get_attribute($this->env, $this->source, ($context["fields_meta"] ?? null), "table", [], "any", false, false, false, 4)] ?? null) : null)) && is_array($__internal_1cfccaec8dd2e8578ccb026fbe7f2e7e29ac2ed5deb976639c5fc99a6ea8583b) || $__internal_1cfccaec8dd2e8578ccb026fbe7f2e7e29ac2ed5deb976639c5fc99a6ea8583b instanceof ArrayAccess ? ($__internal_1cfccaec8dd2e8578ccb026fbe7f2e7e29ac2ed5deb976639c5fc99a6ea8583b[twig_get_attribute($this->env, $this->source, ($context["fields_meta"] ?? null), "name", [], "any", false, false, false, 4)] ?? null) : null)) > ($context["limit_chars"] ?? null))) { + // line 5 + echo " "; + echo twig_escape_filter($this->env, twig_slice($this->env, (($__internal_d7fc55f1a54b629533d60b43063289db62e68921ee7a5f8de562bd9d4a2b7ad4 = (($__internal_01476f8db28655ee4ee02ea2d17dd5a92599be76304f08cd8bc0e05aced30666 = ($context["comments_map"] ?? null)) && is_array($__internal_01476f8db28655ee4ee02ea2d17dd5a92599be76304f08cd8bc0e05aced30666) || $__internal_01476f8db28655ee4ee02ea2d17dd5a92599be76304f08cd8bc0e05aced30666 instanceof ArrayAccess ? ($__internal_01476f8db28655ee4ee02ea2d17dd5a92599be76304f08cd8bc0e05aced30666[twig_get_attribute($this->env, $this->source, ($context["fields_meta"] ?? null), "table", [], "any", false, false, false, 5)] ?? null) : null)) && is_array($__internal_d7fc55f1a54b629533d60b43063289db62e68921ee7a5f8de562bd9d4a2b7ad4) || $__internal_d7fc55f1a54b629533d60b43063289db62e68921ee7a5f8de562bd9d4a2b7ad4 instanceof ArrayAccess ? ($__internal_d7fc55f1a54b629533d60b43063289db62e68921ee7a5f8de562bd9d4a2b7ad4[twig_get_attribute($this->env, $this->source, ($context["fields_meta"] ?? null), "name", [], "any", false, false, false, 5)] ?? null) : null), 0, ($context["limit_chars"] ?? null)), "html", null, true); + echo "… + "; + } else { + // line 7 + echo " "; + echo twig_escape_filter($this->env, (($__internal_01c35b74bd85735098add188b3f8372ba465b232ab8298cb582c60f493d3c22e = (($__internal_63ad1f9a2bf4db4af64b010785e9665558fdcac0e8db8b5b413ed986c62dbb52 = ($context["comments_map"] ?? null)) && is_array($__internal_63ad1f9a2bf4db4af64b010785e9665558fdcac0e8db8b5b413ed986c62dbb52) || $__internal_63ad1f9a2bf4db4af64b010785e9665558fdcac0e8db8b5b413ed986c62dbb52 instanceof ArrayAccess ? ($__internal_63ad1f9a2bf4db4af64b010785e9665558fdcac0e8db8b5b413ed986c62dbb52[twig_get_attribute($this->env, $this->source, ($context["fields_meta"] ?? null), "table", [], "any", false, false, false, 7)] ?? null) : null)) && is_array($__internal_01c35b74bd85735098add188b3f8372ba465b232ab8298cb582c60f493d3c22e) || $__internal_01c35b74bd85735098add188b3f8372ba465b232ab8298cb582c60f493d3c22e instanceof ArrayAccess ? ($__internal_01c35b74bd85735098add188b3f8372ba465b232ab8298cb582c60f493d3c22e[twig_get_attribute($this->env, $this->source, ($context["fields_meta"] ?? null), "name", [], "any", false, false, false, 7)] ?? null) : null), "html", null, true); + echo " + "; + } + // line 9 + echo " +"; + } + } + + public function getTemplateName() + { + return "display/results/comment_for_row.twig"; + } + + public function isTraitable() + { + return false; + } + + public function getDebugInfo() + { + return array ( 59 => 9, 53 => 7, 47 => 5, 45 => 4, 40 => 3, 38 => 2, 37 => 1,); + } + + public function getSourceContext() + { + return new Source("", "display/results/comment_for_row.twig", "/var/www/public/phpmyadmin49/templates/display/results/comment_for_row.twig"); + } +} diff --git a/php/apps/phpmyadmin49/tmp/twig/14/141117632bde69303f2606c4fc821de3ed31df244dce3e06fddf2d0ad5c66c6c.php b/php/apps/phpmyadmin49/tmp/twig/14/141117632bde69303f2606c4fc821de3ed31df244dce3e06fddf2d0ad5c66c6c.php new file mode 100644 index 00000000..58e8e0e3 --- /dev/null +++ b/php/apps/phpmyadmin49/tmp/twig/14/141117632bde69303f2606c4fc821de3ed31df244dce3e06fddf2d0ad5c66c6c.php @@ -0,0 +1,97 @@ +source = $this->getSourceContext(); + + $this->parent = false; + + $this->blocks = [ + ]; + } + + protected function doDisplay(array $context, array $blocks = []) + { + $macros = $this->macros; + // line 1 + echo " +
        + "; + // line 3 + echo PhpMyAdmin\Url::getHiddenInputs(($context["db"] ?? null), ($context["table"] ?? null)); + echo " + + + env, ($context["is_browse_distinct"] ?? null), "html", null, true); + echo "\" /> + env, ($context["max_rows"] ?? null), "html", null, true)))); + echo "\" /> + env, ($context["goto"] ?? null), "html", null, true); + echo "\" /> + env, ($context["unique_id"] ?? null), "html", null, true); + echo "\" class=\"showAllRows\""; + // line 10 + echo ((($context["showing_all"] ?? null)) ? (" checked=\"checked\"") : ("")); + echo " value=\"all\" /> + +
        + +"; + } + + public function getTemplateName() + { + return "display/results/show_all_checkbox.twig"; + } + + public function isTraitable() + { + return false; + } + + public function getDebugInfo() + { + return array ( 69 => 11, 65 => 10, 62 => 9, 58 => 8, 54 => 7, 50 => 6, 45 => 4, 41 => 3, 37 => 1,); + } + + public function getSourceContext() + { + return new Source("", "display/results/show_all_checkbox.twig", "/var/www/public/phpmyadmin49/templates/display/results/show_all_checkbox.twig"); + } +} diff --git a/php/apps/phpmyadmin49/tmp/twig/1a/1a5515d147f9a13fa13b01e8a06ce87b10ff82a3fda74b72ddc563fa0fb0f8bf.php b/php/apps/phpmyadmin49/tmp/twig/1a/1a5515d147f9a13fa13b01e8a06ce87b10ff82a3fda74b72ddc563fa0fb0f8bf.php new file mode 100644 index 00000000..158569d3 --- /dev/null +++ b/php/apps/phpmyadmin49/tmp/twig/1a/1a5515d147f9a13fa13b01e8a06ce87b10ff82a3fda74b72ddc563fa0fb0f8bf.php @@ -0,0 +1,89 @@ +source = $this->getSourceContext(); + + $this->parent = false; + + $this->blocks = [ + ]; + } + + protected function doDisplay(array $context, array $blocks = []) + { + $macros = $this->macros; + // line 1 + $context["colspan"] = 2; + // line 2 + if (($context["is_setup"] ?? null)) { + // line 3 + echo " "; + $context["colspan"] = (($context["colspan"] ?? null) + 1); + } + // line 5 + if (($context["show_buttons"] ?? null)) { + // line 6 + echo " + env, ($context["colspan"] ?? null), "html", null, true); + echo "\" class=\"lastrow\"> + + + + +"; + } + // line 13 + echo " + +"; + } + + public function getTemplateName() + { + return "config/form_display/fieldset_bottom.twig"; + } + + public function isTraitable() + { + return false; + } + + public function getDebugInfo() + { + return array ( 65 => 13, 58 => 9, 54 => 8, 50 => 7, 47 => 6, 45 => 5, 41 => 3, 39 => 2, 37 => 1,); + } + + public function getSourceContext() + { + return new Source("", "config/form_display/fieldset_bottom.twig", "/var/www/public/phpMyAdmin49/templates/config/form_display/fieldset_bottom.twig"); + } +} diff --git a/php/apps/phpmyadmin49/tmp/twig/1b/1ba3e90da72b2aad73685b0fc9ddfba0fd32570151b5ac803138dde4cda2191b.php b/php/apps/phpmyadmin49/tmp/twig/1b/1ba3e90da72b2aad73685b0fc9ddfba0fd32570151b5ac803138dde4cda2191b.php new file mode 100644 index 00000000..c7af53d8 --- /dev/null +++ b/php/apps/phpmyadmin49/tmp/twig/1b/1ba3e90da72b2aad73685b0fc9ddfba0fd32570151b5ac803138dde4cda2191b.php @@ -0,0 +1,85 @@ +source = $this->getSourceContext(); + + $this->parent = false; + + $this->blocks = [ + ]; + } + + protected function doDisplay(array $context, array $blocks = []) + { + $macros = $this->macros; + // line 1 + echo " +env, ($context["goto"] ?? null), "html", null, true); + echo "\" /> +"; + // line 4 + echo "env, ($context["pos"] ?? null), "html", null, true); + echo "\" /> +env, ($context["is_browse_distinct"] ?? null), "html", null, true); + echo "\" /> +"; + // line 6 + echo _gettext("Number of rows:"); + // line 7 + echo PhpMyAdmin\Util::getDropdown("session_max_rows", // line 9 +($context["number_of_rows_choices"] ?? null), // line 10 +($context["max_rows"] ?? null), "", "autosubmit", // line 13 +($context["number_of_rows_placeholder"] ?? null)); + // line 14 + echo " +"; + } + + public function getTemplateName() + { + return "display/results/additional_fields.twig"; + } + + public function isTraitable() + { + return false; + } + + public function getDebugInfo() + { + return array ( 62 => 14, 60 => 13, 59 => 10, 58 => 9, 57 => 7, 55 => 6, 51 => 5, 46 => 4, 42 => 2, 37 => 1,); + } + + public function getSourceContext() + { + return new Source("", "display/results/additional_fields.twig", "/var/www/public/phpmyadmin49/templates/display/results/additional_fields.twig"); + } +} diff --git a/php/apps/phpmyadmin49/tmp/twig/26/263cdf47d6051db6e51a60a97587f841561675f0985061489fceda5d8b29cf90.php b/php/apps/phpmyadmin49/tmp/twig/26/263cdf47d6051db6e51a60a97587f841561675f0985061489fceda5d8b29cf90.php new file mode 100644 index 00000000..7c79bb0d --- /dev/null +++ b/php/apps/phpmyadmin49/tmp/twig/26/263cdf47d6051db6e51a60a97587f841561675f0985061489fceda5d8b29cf90.php @@ -0,0 +1,131 @@ +source = $this->getSourceContext(); + + $this->parent = false; + + $this->blocks = [ + ]; + } + + protected function doDisplay(array $context, array $blocks = []) + { + $macros = $this->macros; + // line 1 + echo "
        + "; + // line 2 + echo PhpMyAdmin\Url::getHiddenInputs(($context["_form_params"] ?? null)); + echo " + + "; + // line 4 + if (($context["use_fieldset"] ?? null)) { + // line 5 + echo "
        + "; + // line 6 + echo ($context["language_title"] ?? null); + echo " + "; + } else { + // line 8 + echo " + + + "; + } + // line 12 + echo " + + + "; + // line 28 + if (($context["use_fieldset"] ?? null)) { + // line 29 + echo "
        + "; + } + // line 31 + echo " +
        +"; + } + + public function getTemplateName() + { + return "select_lang.twig"; + } + + public function isTraitable() + { + return false; + } + + public function getDebugInfo() + { + return array ( 107 => 31, 103 => 29, 101 => 28, 96 => 25, 87 => 22, 84 => 21, 81 => 19, 79 => 18, 75 => 17, 73 => 16, 69 => 15, 64 => 12, 58 => 9, 55 => 8, 50 => 6, 47 => 5, 45 => 4, 40 => 2, 37 => 1,); + } + + public function getSourceContext() + { + return new Source("", "select_lang.twig", "/var/www/public/phpMyAdmin49/templates/select_lang.twig"); + } +} diff --git a/php/apps/phpmyadmin49/tmp/twig/2a/2a62911bdb6699ee16f748468a35a75d2e596dbd72768282bdaee16a0bee5c78.php b/php/apps/phpmyadmin49/tmp/twig/2a/2a62911bdb6699ee16f748468a35a75d2e596dbd72768282bdaee16a0bee5c78.php new file mode 100644 index 00000000..dcaba460 --- /dev/null +++ b/php/apps/phpmyadmin49/tmp/twig/2a/2a62911bdb6699ee16f748468a35a75d2e596dbd72768282bdaee16a0bee5c78.php @@ -0,0 +1,108 @@ +source = $this->getSourceContext(); + + $this->parent = false; + + $this->blocks = [ + ]; + } + + protected function doDisplay(array $context, array $blocks = []) + { + $macros = $this->macros; + // line 1 + echo " +"; + } + + public function getTemplateName() + { + return "dropdown.twig"; + } + + public function isTraitable() + { + return false; + } + + public function getDebugInfo() + { + return array ( 85 => 11, 75 => 9, 71 => 8, 67 => 7, 58 => 5, 56 => 4, 54 => 3, 46 => 2, 37 => 1,); + } + + public function getSourceContext() + { + return new Source("", "dropdown.twig", "/var/www/public/phpmyadmin49/templates/dropdown.twig"); + } +} diff --git a/php/apps/phpmyadmin49/tmp/twig/2f/2f0c997563dbb54536f60d9e2f26b7e80b00728805629efc065e1692d1f05acc.php b/php/apps/phpmyadmin49/tmp/twig/2f/2f0c997563dbb54536f60d9e2f26b7e80b00728805629efc065e1692d1f05acc.php new file mode 100644 index 00000000..6fbed67a --- /dev/null +++ b/php/apps/phpmyadmin49/tmp/twig/2f/2f0c997563dbb54536f60d9e2f26b7e80b00728805629efc065e1692d1f05acc.php @@ -0,0 +1,72 @@ +source = $this->getSourceContext(); + + $this->parent = false; + + $this->blocks = [ + ]; + } + + protected function doDisplay(array $context, array $blocks = []) + { + $macros = $this->macros; + // line 1 + echo "env, ($context["group"] ?? null), "html", null, true); + echo "\"> + env, ($context["colspan"] ?? null), "html", null, true); + echo "\"> + "; + // line 3 + echo twig_escape_filter($this->env, ($context["header_text"] ?? null), "html", null, true); + echo " + + +"; + } + + public function getTemplateName() + { + return "config/form_display/group_header.twig"; + } + + public function isTraitable() + { + return false; + } + + public function getDebugInfo() + { + return array ( 46 => 3, 42 => 2, 37 => 1,); + } + + public function getSourceContext() + { + return new Source("", "config/form_display/group_header.twig", "/var/www/public/phpMyAdmin49/templates/config/form_display/group_header.twig"); + } +} diff --git a/php/apps/phpmyadmin49/tmp/twig/32/320e492353d1340511a3c0b503625188a5a1fae6a47220c1d746cca6eea873cf.php b/php/apps/phpmyadmin49/tmp/twig/32/320e492353d1340511a3c0b503625188a5a1fae6a47220c1d746cca6eea873cf.php new file mode 100644 index 00000000..07b793ff --- /dev/null +++ b/php/apps/phpmyadmin49/tmp/twig/32/320e492353d1340511a3c0b503625188a5a1fae6a47220c1d746cca6eea873cf.php @@ -0,0 +1,55 @@ +source = $this->getSourceContext(); + + $this->parent = false; + + $this->blocks = [ + ]; + } + + protected function doDisplay(array $context, array $blocks = []) + { + $macros = $this->macros; + // line 1 + echo "
      • +"; + } + + public function getTemplateName() + { + return "login/footer.twig"; + } + + public function getDebugInfo() + { + return array ( 37 => 1,); + } + + public function getSourceContext() + { + return new Source("", "login/footer.twig", "/var/www/public/phpMyAdmin49/templates/login/footer.twig"); + } +} diff --git a/php/apps/phpmyadmin49/tmp/twig/33/33c337624145b77c1814cc15901b6de0c201e51b0b75e29ec4c7dfe387a377cd.php b/php/apps/phpmyadmin49/tmp/twig/33/33c337624145b77c1814cc15901b6de0c201e51b0b75e29ec4c7dfe387a377cd.php new file mode 100644 index 00000000..7e5d6930 --- /dev/null +++ b/php/apps/phpmyadmin49/tmp/twig/33/33c337624145b77c1814cc15901b6de0c201e51b0b75e29ec4c7dfe387a377cd.php @@ -0,0 +1,100 @@ +source = $this->getSourceContext(); + + $this->parent = false; + + $this->blocks = [ + ]; + } + + protected function doDisplay(array $context, array $blocks = []) + { + $macros = $this->macros; + // line 1 + echo "
        +
        + + "; + // line 4 + if (PhpMyAdmin\Util::showIcons("ActionLinksMode")) { + // line 5 + echo PhpMyAdmin\Util::getImage("b_table_add"); + } + // line 7 + echo " "; + echo _gettext("Create table"); + // line 8 + echo " + "; + // line 9 + echo PhpMyAdmin\Url::getHiddenInputs(($context["db"] ?? null)); + echo " +
        + "; + // line 11 + echo _gettext("Name"); + echo ": + +
        +
        + "; + // line 15 + echo _gettext("Number of columns"); + echo ": + +
        +
        +
        +
        + +
        +
        +"; + } + + public function getTemplateName() + { + return "database/create_table.twig"; + } + + public function isTraitable() + { + return false; + } + + public function getDebugInfo() + { + return array ( 74 => 21, 65 => 15, 58 => 11, 53 => 9, 50 => 8, 47 => 7, 44 => 5, 42 => 4, 37 => 1,); + } + + public function getSourceContext() + { + return new Source("", "database/create_table.twig", "/var/www/public/phpmyadmin49/templates/database/create_table.twig"); + } +} diff --git a/php/apps/phpmyadmin49/tmp/twig/3b/3bcff584c0aea657804b23a06709d424abbba7a9f46b816c6a2745c9d251131a.php b/php/apps/phpmyadmin49/tmp/twig/3b/3bcff584c0aea657804b23a06709d424abbba7a9f46b816c6a2745c9d251131a.php new file mode 100644 index 00000000..dbe6b76e --- /dev/null +++ b/php/apps/phpmyadmin49/tmp/twig/3b/3bcff584c0aea657804b23a06709d424abbba7a9f46b816c6a2745c9d251131a.php @@ -0,0 +1,110 @@ +source = $this->getSourceContext(); + + $this->parent = false; + + $this->blocks = [ + ]; + } + + protected function doDisplay(array $context, array $blocks = []) + { + $macros = $this->macros; + // line 1 + echo "env, ($context["id"] ?? null), "html", null, true); + echo "\""; + } + // line 2 + if ( !twig_test_empty(($context["class"] ?? null))) { + echo " class=\""; + echo twig_escape_filter($this->env, ($context["class"] ?? null), "html", null, true); + echo "\""; + } + echo "> + + "; + // line 4 + if ( !twig_test_empty(($context["items"] ?? null))) { + // line 5 + echo " "; + $context['_parent'] = $context; + $context['_seq'] = twig_ensure_traversable(($context["items"] ?? null)); + foreach ($context['_seq'] as $context["_key"] => $context["item"]) { + // line 6 + echo " "; + if ( !twig_test_iterable($context["item"])) { + // line 7 + echo " "; + $context["item"] = ["content" => $context["item"]]; + // line 8 + echo " "; + } + // line 9 + echo " "; + $this->loadTemplate("list/item.twig", "list/unordered.twig", 9)->display(twig_to_array($context["item"])); + // line 10 + echo " "; + } + $_parent = $context['_parent']; + unset($context['_seq'], $context['_iterated'], $context['_key'], $context['item'], $context['_parent'], $context['loop']); + $context = array_intersect_key($context, $_parent) + $_parent; + // line 11 + echo " "; + } elseif ( !twig_test_empty(($context["content"] ?? null))) { + // line 12 + echo " "; + echo ($context["content"] ?? null); + echo " + "; + } + // line 14 + echo " +"; + } + + public function getTemplateName() + { + return "list/unordered.twig"; + } + + public function isTraitable() + { + return false; + } + + public function getDebugInfo() + { + return array ( 87 => 14, 81 => 12, 78 => 11, 72 => 10, 69 => 9, 66 => 8, 63 => 7, 60 => 6, 55 => 5, 53 => 4, 44 => 2, 37 => 1,); + } + + public function getSourceContext() + { + return new Source("", "list/unordered.twig", "/var/www/public/phpMyAdmin49/templates/list/unordered.twig"); + } +} diff --git a/php/apps/phpmyadmin49/tmp/twig/3e/3eb2127d2c87af7afa1e8cdcfab503cfb9cf6045f6ef4c492e6e1638ecdfc9db.php b/php/apps/phpmyadmin49/tmp/twig/3e/3eb2127d2c87af7afa1e8cdcfab503cfb9cf6045f6ef4c492e6e1638ecdfc9db.php new file mode 100644 index 00000000..823db6a8 --- /dev/null +++ b/php/apps/phpmyadmin49/tmp/twig/3e/3eb2127d2c87af7afa1e8cdcfab503cfb9cf6045f6ef4c492e6e1638ecdfc9db.php @@ -0,0 +1,88 @@ +source = $this->getSourceContext(); + + $this->parent = false; + + $this->blocks = [ + ]; + } + + protected function doDisplay(array $context, array $blocks = []) + { + $macros = $this->macros; + // line 1 + if (($context["display_logo"] ?? null)) { + // line 2 + echo " +"; + } + } + + public function getTemplateName() + { + return "navigation/logo.twig"; + } + + public function isTraitable() + { + return false; + } + + public function getDebugInfo() + { + return array ( 64 => 11, 60 => 9, 58 => 8, 53 => 7, 48 => 5, 44 => 4, 42 => 3, 39 => 2, 37 => 1,); + } + + public function getSourceContext() + { + return new Source("", "navigation/logo.twig", "/var/www/public/phpMyAdmin49/templates/navigation/logo.twig"); + } +} diff --git a/php/apps/phpmyadmin49/tmp/twig/3f/3fec17ed589c14a76baf1841f1ec06d457d76d6a0f8418c7d22cf4f66ec0545b.php b/php/apps/phpmyadmin49/tmp/twig/3f/3fec17ed589c14a76baf1841f1ec06d457d76d6a0f8418c7d22cf4f66ec0545b.php new file mode 100644 index 00000000..143e728e --- /dev/null +++ b/php/apps/phpmyadmin49/tmp/twig/3f/3fec17ed589c14a76baf1841f1ec06d457d76d6a0f8418c7d22cf4f66ec0545b.php @@ -0,0 +1,55 @@ +source = $this->getSourceContext(); + + $this->parent = false; + + $this->blocks = [ + ]; + } + + protected function doDisplay(array $context, array $blocks = []) + { + $macros = $this->macros; + // line 1 + echo "
        +"; + } + + public function getTemplateName() + { + return "config/form_display/tabs_bottom.twig"; + } + + public function getDebugInfo() + { + return array ( 37 => 1,); + } + + public function getSourceContext() + { + return new Source("", "config/form_display/tabs_bottom.twig", "/var/www/public/phpMyAdmin49/templates/config/form_display/tabs_bottom.twig"); + } +} diff --git a/php/apps/phpmyadmin49/tmp/twig/43/43449ac78fdb83587323b168f3ce46f564136171357bc69791c78066fc59c6cb.php b/php/apps/phpmyadmin49/tmp/twig/43/43449ac78fdb83587323b168f3ce46f564136171357bc69791c78066fc59c6cb.php new file mode 100644 index 00000000..618279d2 --- /dev/null +++ b/php/apps/phpmyadmin49/tmp/twig/43/43449ac78fdb83587323b168f3ce46f564136171357bc69791c78066fc59c6cb.php @@ -0,0 +1,87 @@ +source = $this->getSourceContext(); + + $this->parent = false; + + $this->blocks = [ + ]; + } + + protected function doDisplay(array $context, array $blocks = []) + { + $macros = $this->macros; + // line 1 + echo "
        +env, PhpMyAdmin\Core::linkURL("https://www.phpmyadmin.net/"), "html", null, true); + echo "\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"logo\"> +env, twig_get_attribute($this->env, $this->source, ($context["theme"] ?? null), "getImgPath", [0 => "logo_right.png", 1 => "pma_logo.png"], "method", false, false, false, 3), "html", null, true); + echo "\" id=\"imLogo\" name=\"imLogo\" alt=\"phpMyAdmin\" border=\"0\" /> + +

        "; + // line 5 + echo sprintf(_gettext("Welcome to %s"), "phpMyAdmin"); + echo "

        + + + +
        +"; + // line 12 + echo call_user_func_array($this->env->getFunction('Message_error')->getCallable(), [_gettext("There is mismatch between HTTPS indicated on the server and client. This can lead to non working phpMyAdmin or a security risk. Please fix your server configuration to indicate HTTPS properly.")]); + echo " +
        +"; + } + + public function getTemplateName() + { + return "login/header.twig"; + } + + public function isTraitable() + { + return false; + } + + public function getDebugInfo() + { + return array ( 62 => 12, 55 => 8, 49 => 5, 44 => 3, 40 => 2, 37 => 1,); + } + + public function getSourceContext() + { + return new Source("", "login/header.twig", "/var/www/public/phpMyAdmin49/templates/login/header.twig"); + } +} diff --git a/php/apps/phpmyadmin49/tmp/twig/45/459c3f79a8b9e46113e71a89be591ddaafe30ce13e1c032a680618aafe3e3fdf.php b/php/apps/phpmyadmin49/tmp/twig/45/459c3f79a8b9e46113e71a89be591ddaafe30ce13e1c032a680618aafe3e3fdf.php new file mode 100644 index 00000000..ce8c8099 --- /dev/null +++ b/php/apps/phpmyadmin49/tmp/twig/45/459c3f79a8b9e46113e71a89be591ddaafe30ce13e1c032a680618aafe3e3fdf.php @@ -0,0 +1,68 @@ +source = $this->getSourceContext(); + + $this->parent = false; + + $this->blocks = [ + ]; + } + + protected function doDisplay(array $context, array $blocks = []) + { + $macros = $this->macros; + // line 1 + echo " + + +"; + } + + public function getTemplateName() + { + return "fk_checkbox.twig"; + } + + public function isTraitable() + { + return false; + } + + public function getDebugInfo() + { + return array ( 44 => 4, 40 => 3, 37 => 1,); + } + + public function getSourceContext() + { + return new Source("", "fk_checkbox.twig", "/var/www/public/phpmyadmin49/templates/fk_checkbox.twig"); + } +} diff --git a/php/apps/phpmyadmin49/tmp/twig/4b/4b23d354297e9bd00908c9149bd991dda8c6d0c674de5d9c4bdaf8287e5e545d.php b/php/apps/phpmyadmin49/tmp/twig/4b/4b23d354297e9bd00908c9149bd991dda8c6d0c674de5d9c4bdaf8287e5e545d.php new file mode 100644 index 00000000..e1e359ec --- /dev/null +++ b/php/apps/phpmyadmin49/tmp/twig/4b/4b23d354297e9bd00908c9149bd991dda8c6d0c674de5d9c4bdaf8287e5e545d.php @@ -0,0 +1,113 @@ +source = $this->getSourceContext(); + + $this->parent = false; + + $this->blocks = [ + ]; + } + + protected function doDisplay(array $context, array $blocks = []) + { + $macros = $this->macros; + // line 1 + echo " $context["value"]) { + // line 3 + echo " "; + echo twig_escape_filter($this->env, $context["key"], "html", null, true); + echo "=\""; + echo twig_escape_filter($this->env, $context["value"], "html", null, true); + echo "\""; + } + $_parent = $context['_parent']; + unset($context['_seq'], $context['_iterated'], $context['key'], $context['value'], $context['_parent'], $context['loop']); + $context = array_intersect_key($context, $_parent) + $_parent; + // line 4 + echo "> +"; + // line 5 + echo twig_escape_filter($this->env, ($context["title"] ?? null), "html", null, true); + echo " +"; + // line 6 + if ( !twig_test_empty(($context["description"] ?? null))) { + // line 7 + echo "

        "; + echo ($context["description"] ?? null); + echo "

        +"; + } + // line 10 + if ((twig_test_iterable(($context["errors"] ?? null)) && (twig_length_filter($this->env, ($context["errors"] ?? null)) > 0))) { + // line 11 + echo "
        + "; + // line 12 + $context['_parent'] = $context; + $context['_seq'] = twig_ensure_traversable(($context["errors"] ?? null)); + foreach ($context['_seq'] as $context["_key"] => $context["error"]) { + // line 13 + echo "
        "; + echo twig_escape_filter($this->env, $context["error"], "html", null, true); + echo "
        + "; + } + $_parent = $context['_parent']; + unset($context['_seq'], $context['_iterated'], $context['_key'], $context['error'], $context['_parent'], $context['loop']); + $context = array_intersect_key($context, $_parent) + $_parent; + // line 15 + echo "
        +"; + } + // line 17 + echo " +"; + } + + public function getTemplateName() + { + return "config/form_display/fieldset_top.twig"; + } + + public function isTraitable() + { + return false; + } + + public function getDebugInfo() + { + return array ( 90 => 17, 86 => 15, 77 => 13, 73 => 12, 70 => 11, 68 => 10, 62 => 7, 60 => 6, 56 => 5, 53 => 4, 43 => 3, 39 => 2, 37 => 1,); + } + + public function getSourceContext() + { + return new Source("", "config/form_display/fieldset_top.twig", "/var/www/public/phpMyAdmin49/templates/config/form_display/fieldset_top.twig"); + } +} diff --git a/php/apps/phpmyadmin49/tmp/twig/5b/5b43fc44e68a2abf7c77fd07b189a44278fd4bf18e41355f6187ca0dfd4359e3.php b/php/apps/phpmyadmin49/tmp/twig/5b/5b43fc44e68a2abf7c77fd07b189a44278fd4bf18e41355f6187ca0dfd4359e3.php new file mode 100644 index 00000000..0a64863f --- /dev/null +++ b/php/apps/phpmyadmin49/tmp/twig/5b/5b43fc44e68a2abf7c77fd07b189a44278fd4bf18e41355f6187ca0dfd4359e3.php @@ -0,0 +1,94 @@ +source = $this->getSourceContext(); + + $this->parent = false; + + $this->blocks = [ + ]; + } + + protected function doDisplay(array $context, array $blocks = []) + { + $macros = $this->macros; + // line 1 + if ((($context["initial_sliders_state"] ?? null) == "disabled")) { + // line 2 + echo " env, ($context["id"] ?? null), "html", null, true); + echo "\""; + } + echo "> +"; + } else { + // line 4 + echo " "; + // line 12 + echo " env, ($context["id"] ?? null), "html", null, true); + echo "\""; + } + // line 13 + echo " "; + if ((($context["initial_sliders_state"] ?? null) == "closed")) { + // line 14 + echo "style=\"display: none; overflow:auto;\""; + } + echo " class=\"pma_auto_slider\""; + // line 15 + if ((isset($context["message"]) || array_key_exists("message", $context))) { + echo " title=\""; + echo twig_escape_filter($this->env, ($context["message"] ?? null), "html", null, true); + echo "\""; + } + echo "> +"; + } + } + + public function getTemplateName() + { + return "div_for_slider_effect.twig"; + } + + public function isTraitable() + { + return false; + } + + public function getDebugInfo() + { + return array ( 65 => 15, 61 => 14, 58 => 13, 51 => 12, 49 => 4, 39 => 2, 37 => 1,); + } + + public function getSourceContext() + { + return new Source("", "div_for_slider_effect.twig", "/var/www/public/phpmyadmin49/templates/div_for_slider_effect.twig"); + } +} diff --git a/php/apps/phpmyadmin49/tmp/twig/5d/5d3664d16c30638dac996b9b048f550e165d208f54cfb6588cc187e5cbdfeff6.php b/php/apps/phpmyadmin49/tmp/twig/5d/5d3664d16c30638dac996b9b048f550e165d208f54cfb6588cc187e5cbdfeff6.php new file mode 100644 index 00000000..03107085 --- /dev/null +++ b/php/apps/phpmyadmin49/tmp/twig/5d/5d3664d16c30638dac996b9b048f550e165d208f54cfb6588cc187e5cbdfeff6.php @@ -0,0 +1,99 @@ +source = $this->getSourceContext(); + + $this->parent = false; + + $this->blocks = [ + ]; + } + + protected function doDisplay(array $context, array $blocks = []) + { + $macros = $this->macros; + // line 1 + echo "
        env, ($context["parent_div_classes"] ?? null), "html", null, true); + echo "\"> + "; + // line 2 + $context['_parent'] = $context; + $context['_seq'] = twig_ensure_traversable(($context["content_array"] ?? null)); + foreach ($context['_seq'] as $context["_key"] => $context["content"]) { + // line 3 + echo " "; + if ((isset($context["content"]) || array_key_exists("content", $context))) { + // line 4 + echo " env, (($__internal_f607aeef2c31a95a7bf963452dff024ffaeb6aafbe4603f9ca3bec57be8633f4 = $context["content"]) && is_array($__internal_f607aeef2c31a95a7bf963452dff024ffaeb6aafbe4603f9ca3bec57be8633f4) || $__internal_f607aeef2c31a95a7bf963452dff024ffaeb6aafbe4603f9ca3bec57be8633f4 instanceof ArrayAccess ? ($__internal_f607aeef2c31a95a7bf963452dff024ffaeb6aafbe4603f9ca3bec57be8633f4[0] ?? null) : null), "html", null, true); + echo "\"> + "; + // line 5 + echo twig_escape_filter($this->env, (($__internal_62824350bc4502ee19dbc2e99fc6bdd3bd90e7d8dd6e72f42c35efd048542144 = $context["content"]) && is_array($__internal_62824350bc4502ee19dbc2e99fc6bdd3bd90e7d8dd6e72f42c35efd048542144) || $__internal_62824350bc4502ee19dbc2e99fc6bdd3bd90e7d8dd6e72f42c35efd048542144 instanceof ArrayAccess ? ($__internal_62824350bc4502ee19dbc2e99fc6bdd3bd90e7d8dd6e72f42c35efd048542144[1] ?? null) : null), "html", null, true); + echo " + "; + // line 6 + if (twig_get_attribute($this->env, $this->source, $context["content"], "extraSpan", [], "array", true, true, false, 6)) { + // line 7 + echo " : "; + echo twig_escape_filter($this->env, (($__internal_1cfccaec8dd2e8578ccb026fbe7f2e7e29ac2ed5deb976639c5fc99a6ea8583b = $context["content"]) && is_array($__internal_1cfccaec8dd2e8578ccb026fbe7f2e7e29ac2ed5deb976639c5fc99a6ea8583b) || $__internal_1cfccaec8dd2e8578ccb026fbe7f2e7e29ac2ed5deb976639c5fc99a6ea8583b instanceof ArrayAccess ? ($__internal_1cfccaec8dd2e8578ccb026fbe7f2e7e29ac2ed5deb976639c5fc99a6ea8583b["extraSpan"] ?? null) : null), "html", null, true); + echo " + "; + } + // line 9 + echo " + "; + } + // line 11 + echo " "; + } + $_parent = $context['_parent']; + unset($context['_seq'], $context['_iterated'], $context['_key'], $context['content'], $context['_parent'], $context['loop']); + $context = array_intersect_key($context, $_parent) + $_parent; + // line 12 + echo "
        +"; + } + + public function getTemplateName() + { + return "console/query_action.twig"; + } + + public function isTraitable() + { + return false; + } + + public function getDebugInfo() + { + return array ( 76 => 12, 70 => 11, 66 => 9, 60 => 7, 58 => 6, 54 => 5, 49 => 4, 46 => 3, 42 => 2, 37 => 1,); + } + + public function getSourceContext() + { + return new Source("", "console/query_action.twig", "/var/www/public/phpMyAdmin49/templates/console/query_action.twig"); + } +} diff --git a/php/apps/phpmyadmin49/tmp/twig/63/63168af0b8a5a02ef578db3929dcc1e4a8437b0d0b5e3e076bc9698084d22d9e.php b/php/apps/phpmyadmin49/tmp/twig/63/63168af0b8a5a02ef578db3929dcc1e4a8437b0d0b5e3e076bc9698084d22d9e.php new file mode 100644 index 00000000..2d0c0cdc --- /dev/null +++ b/php/apps/phpmyadmin49/tmp/twig/63/63168af0b8a5a02ef578db3929dcc1e4a8437b0d0b5e3e076bc9698084d22d9e.php @@ -0,0 +1,87 @@ +source = $this->getSourceContext(); + + $this->parent = false; + + $this->blocks = [ + ]; + } + + protected function doDisplay(array $context, array $blocks = []) + { + $macros = $this->macros; + // line 1 + echo "env, ($context["html_field_name"] ?? null), "html", null, true); + echo "\""; + // line 2 + if ((isset($context["html_field_id"]) || array_key_exists("html_field_id", $context))) { + echo " id=\""; + echo twig_escape_filter($this->env, ($context["html_field_id"] ?? null), "html", null, true); + echo "\""; + } + // line 3 + if (((isset($context["checked"]) || array_key_exists("checked", $context)) && ($context["checked"] ?? null))) { + echo " checked=\"checked\""; + } + // line 4 + if (((isset($context["onclick"]) || array_key_exists("onclick", $context)) && ($context["onclick"] ?? null))) { + echo " class=\"autosubmit\""; + } + echo " />env, ($context["html_field_id"] ?? null), "html", null, true); + echo "\""; + } + // line 6 + echo ">"; + echo twig_escape_filter($this->env, ($context["label"] ?? null), "html", null, true); + echo " +"; + } + + public function getTemplateName() + { + return "checkbox.twig"; + } + + public function isTraitable() + { + return false; + } + + public function getDebugInfo() + { + return array ( 62 => 6, 56 => 5, 51 => 4, 47 => 3, 41 => 2, 37 => 1,); + } + + public function getSourceContext() + { + return new Source("", "checkbox.twig", "/var/www/public/phpmyadmin49/templates/checkbox.twig"); + } +} diff --git a/php/apps/phpmyadmin49/tmp/twig/6d/6d1b7d19bccd0138ac3346425e5b788e3726ef38d05b21831f374e4af4dcd5a6.php b/php/apps/phpmyadmin49/tmp/twig/6d/6d1b7d19bccd0138ac3346425e5b788e3726ef38d05b21831f374e4af4dcd5a6.php new file mode 100644 index 00000000..5543c0e3 --- /dev/null +++ b/php/apps/phpmyadmin49/tmp/twig/6d/6d1b7d19bccd0138ac3346425e5b788e3726ef38d05b21831f374e4af4dcd5a6.php @@ -0,0 +1,70 @@ +source = $this->getSourceContext(); + + $this->parent = false; + + $this->blocks = [ + ]; + } + + protected function doDisplay(array $context, array $blocks = []) + { + $macros = $this->macros; + // line 1 + echo " +"; + } + + public function getTemplateName() + { + return "javascript/display.twig"; + } + + public function isTraitable() + { + return false; + } + + public function getDebugInfo() + { + return array ( 42 => 4, 37 => 1,); + } + + public function getSourceContext() + { + return new Source("", "javascript/display.twig", "/var/www/public/phpMyAdmin49/templates/javascript/display.twig"); + } +} diff --git a/php/apps/phpmyadmin49/tmp/twig/6d/6da1155b5050f06dd1b3c3f7dc8412b5310cea78eaa0c547019d60825ca9eb99.php b/php/apps/phpmyadmin49/tmp/twig/6d/6da1155b5050f06dd1b3c3f7dc8412b5310cea78eaa0c547019d60825ca9eb99.php new file mode 100644 index 00000000..04759bda --- /dev/null +++ b/php/apps/phpmyadmin49/tmp/twig/6d/6da1155b5050f06dd1b3c3f7dc8412b5310cea78eaa0c547019d60825ca9eb99.php @@ -0,0 +1,398 @@ +source = $this->getSourceContext(); + + $this->parent = false; + + $this->blocks = [ + ]; + } + + protected function doDisplay(array $context, array $blocks = []) + { + $macros = $this->macros; + // line 1 + $this->loadTemplate("secondary_tabs.twig", "database/multi_table_query/form.twig", 1)->display(twig_to_array(["url_params" => ["db" => // line 3 +($context["db"] ?? null)], "sub_tabs" => [0 => ["link" => "db_multi_table_query.php", "text" => _gettext("Multi-table query")], 1 => ["link" => "db_qbe.php", "text" => _gettext("Query by example")]]])); + // line 16 + echo PhpMyAdmin\Util::getDivForSliderEffect("query_div", _gettext("Query window"), "open"); + echo " +
        + env, ($context["db"] ?? null), "html", null, true); + echo "\"> +
        + "; + // line 20 + $context['_parent'] = $context; + $context['_seq'] = twig_ensure_traversable(($context["tables"] ?? null)); + foreach ($context['_seq'] as $context["_key"] => $context["table"]) { + // line 21 + echo "
        env, twig_get_attribute($this->env, $this->source, $context["table"], "hash", [], "any", false, false, false, 21), "html", null, true); + echo "\"> + + "; + // line 23 + $context['_parent'] = $context; + $context['_seq'] = twig_ensure_traversable(twig_get_attribute($this->env, $this->source, $context["table"], "columns", [], "any", false, false, false, 23)); + foreach ($context['_seq'] as $context["_key"] => $context["column"]) { + // line 24 + echo " + "; + } + $_parent = $context['_parent']; + unset($context['_seq'], $context['_iterated'], $context['_key'], $context['column'], $context['_parent'], $context['loop']); + $context = array_intersect_key($context, $_parent) + $_parent; + // line 26 + echo "
        + "; + } + $_parent = $context['_parent']; + unset($context['_seq'], $context['_iterated'], $context['_key'], $context['table'], $context['_parent'], $context['loop']); + $context = array_intersect_key($context, $_parent) + $_parent; + // line 28 + echo " + "; + // line 29 + $context['_parent'] = $context; + $context['_seq'] = twig_ensure_traversable(range(0, ($context["default_no_of_columns"] ?? null))); + $context['loop'] = [ + 'parent' => $context['_parent'], + 'index0' => 0, + 'index' => 1, + 'first' => true, + ]; + if (is_array($context['_seq']) || (is_object($context['_seq']) && $context['_seq'] instanceof \Countable)) { + $length = count($context['_seq']); + $context['loop']['revindex0'] = $length - 1; + $context['loop']['revindex'] = $length; + $context['loop']['length'] = $length; + $context['loop']['last'] = 1 === $length; + } + foreach ($context['_seq'] as $context["_key"] => $context["id"]) { + // line 30 + echo " "; + if (($context["id"] == 0)) { + echo "
        "; + } + // line 31 + echo "
        + + . + +
        + + "; + // line 44 + echo _gettext("Show"); + echo " +
        + + +
        + + "; + // line 52 + $this->loadTemplate("div_for_slider_effect.twig", "database/multi_table_query/form.twig", 52)->display(twig_array_merge($context, ["id" => ("criteria_div" . // line 53 +$context["id"]), "initial_sliders_state" => "closed", "message" => _gettext("criteria")])); + // line 57 + echo "
        +
        + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        "; + // line 61 + echo _gettext("Sort"); + echo "env, $context["id"], "html", null, true); + echo "]\">"; + echo _gettext("Ascending"); + echo "env, $context["id"], "html", null, true); + echo "]\">"; + echo _gettext("Descending"); + echo "
        "; + // line 67 + echo _gettext("Add as"); + echo " + env, $context["id"], "html", null, true); + echo "]\" + value=\"AND\" + class=\"logical_op\" + checked=\"checked\"> + AND + + env, $context["id"], "html", null, true); + echo "]\" + value=\"OR\" + class=\"logical_op\"> + OR +
        Op + + + +
        + . + + +
        + +
        +
        +
        + + X + + + "; + // line 154 + if (($context["id"] == 0)) { + echo "
        "; + } + // line 155 + echo " "; + ++$context['loop']['index0']; + ++$context['loop']['index']; + $context['loop']['first'] = false; + if (isset($context['loop']['length'])) { + --$context['loop']['revindex0']; + --$context['loop']['revindex']; + $context['loop']['last'] = 0 === $context['loop']['revindex0']; + } + } + $_parent = $context['_parent']; + unset($context['_seq'], $context['_iterated'], $context['_key'], $context['id'], $context['_parent'], $context['loop']); + $context = array_intersect_key($context, $_parent) + $_parent; + // line 156 + echo " +
        + +
        + +
        + "; + // line 162 + ob_start(function () { return ''; }); + // line 163 + echo " + "; + echo trim(preg_replace('/>\s+<', ob_get_clean())); + // line 171 + echo "
        + + +
        + + +
        + +
        "; + // line 180 + echo "
        +"; + } + + public function getTemplateName() + { + return "database/multi_table_query/form.twig"; + } + + public function isTraitable() + { + return false; + } + + public function getDebugInfo() + { + return array ( 375 => 180, 369 => 176, 365 => 175, 359 => 171, 349 => 163, 347 => 162, 340 => 158, 336 => 156, 322 => 155, 318 => 154, 309 => 148, 298 => 140, 284 => 129, 278 => 125, 267 => 123, 263 => 122, 259 => 121, 247 => 112, 243 => 111, 207 => 78, 196 => 70, 190 => 67, 181 => 63, 175 => 62, 171 => 61, 165 => 57, 163 => 53, 162 => 52, 157 => 50, 151 => 47, 147 => 46, 142 => 44, 135 => 40, 130 => 37, 119 => 35, 115 => 34, 111 => 33, 107 => 31, 102 => 30, 85 => 29, 82 => 28, 75 => 26, 64 => 24, 60 => 23, 54 => 21, 50 => 20, 45 => 18, 40 => 16, 38 => 3, 37 => 1,); + } + + public function getSourceContext() + { + return new Source("", "database/multi_table_query/form.twig", "/var/www/public/phpMyAdmin49/templates/database/multi_table_query/form.twig"); + } +} diff --git a/php/apps/phpmyadmin49/tmp/twig/75/75c317b9f49b72eb12b5a8c65f34e7103df2950d7bfd5d5ceea16aa3a7bb08f6.php b/php/apps/phpmyadmin49/tmp/twig/75/75c317b9f49b72eb12b5a8c65f34e7103df2950d7bfd5d5ceea16aa3a7bb08f6.php new file mode 100644 index 00000000..aec866a9 --- /dev/null +++ b/php/apps/phpmyadmin49/tmp/twig/75/75c317b9f49b72eb12b5a8c65f34e7103df2950d7bfd5d5ceea16aa3a7bb08f6.php @@ -0,0 +1,77 @@ +source = $this->getSourceContext(); + + $this->parent = false; + + $this->blocks = [ + ]; + } + + protected function doDisplay(array $context, array $blocks = []) + { + $macros = $this->macros; + // line 1 + echo "
          + "; + // line 2 + $context['_parent'] = $context; + $context['_seq'] = twig_ensure_traversable(($context["sub_tabs"] ?? null)); + foreach ($context['_seq'] as $context["_key"] => $context["tab"]) { + // line 3 + echo " "; + echo PhpMyAdmin\Util::getHtmlTab($context["tab"], ($context["url_params"] ?? null)); + echo " + "; + } + $_parent = $context['_parent']; + unset($context['_seq'], $context['_iterated'], $context['_key'], $context['tab'], $context['_parent'], $context['loop']); + $context = array_intersect_key($context, $_parent) + $_parent; + // line 5 + echo "
        +
        +"; + } + + public function getTemplateName() + { + return "secondary_tabs.twig"; + } + + public function isTraitable() + { + return false; + } + + public function getDebugInfo() + { + return array ( 53 => 5, 44 => 3, 40 => 2, 37 => 1,); + } + + public function getSourceContext() + { + return new Source("", "secondary_tabs.twig", "/var/www/public/phpMyAdmin49/templates/secondary_tabs.twig"); + } +} diff --git a/php/apps/phpmyadmin49/tmp/twig/80/8033bcfc00c34c8ad0a828910c80ea584fecbcb3b6e2cf97c41a68ca1d662ed3.php b/php/apps/phpmyadmin49/tmp/twig/80/8033bcfc00c34c8ad0a828910c80ea584fecbcb3b6e2cf97c41a68ca1d662ed3.php new file mode 100644 index 00000000..b5604bb6 --- /dev/null +++ b/php/apps/phpmyadmin49/tmp/twig/80/8033bcfc00c34c8ad0a828910c80ea584fecbcb3b6e2cf97c41a68ca1d662ed3.php @@ -0,0 +1,171 @@ +source = $this->getSourceContext(); + + $this->parent = false; + + $this->blocks = [ + ]; + } + + protected function doDisplay(array $context, array $blocks = []) + { + $macros = $this->macros; + // line 1 + echo " +
        + "; + // line 3 + echo PhpMyAdmin\Url::getHiddenInputs(($context["db"] ?? null)); + echo " +
        + "; + // line 5 + echo _gettext("Search in database"); + echo " +

        + + env, ($context["criteria_search_string"] ?? null), "html", null, true); + echo "\"> +

        + +
        + "; + // line 15 + echo _gettext("Find:"); + echo " + "; + // line 17 + echo " "; + // line 19 + echo " "; + echo PhpMyAdmin\Util::getRadioFields("criteriaSearchType", // line 21 +($context["choices"] ?? null), // line 22 +($context["criteria_search_type"] ?? null), true, false); + // line 25 + echo " +
        + +
        + "; + // line 29 + echo _gettext("Inside tables:"); + echo " +

        + + "; + // line 32 + echo _gettext("Select all"); + // line 33 + echo " / + + "; + // line 35 + echo _gettext("Unselect all"); + // line 36 + echo " +

        + +
        + +

        + "; + // line 50 + echo " + env, ($context["criteria_column_name"] ?? null), "html", null, true))) : (print (""))); + echo "\"> +

        +
        +
        + +
        +
        +
        + +
        +"; + } + + public function getTemplateName() + { + return "database/search/selection_form.twig"; + } + + public function isTraitable() + { + return false; + } + + public function getDebugInfo() + { + return array ( 142 => 58, 135 => 54, 132 => 52, 130 => 51, 127 => 50, 121 => 45, 112 => 42, 108 => 41, 104 => 40, 100 => 39, 95 => 36, 93 => 35, 89 => 33, 87 => 32, 81 => 29, 75 => 25, 73 => 22, 72 => 21, 70 => 19, 68 => 17, 64 => 15, 57 => 11, 54 => 9, 52 => 8, 46 => 5, 41 => 3, 37 => 1,); + } + + public function getSourceContext() + { + return new Source("", "database/search/selection_form.twig", "/var/www/public/phpMyAdmin49/templates/database/search/selection_form.twig"); + } +} diff --git a/php/apps/phpmyadmin49/tmp/twig/8e/8e28c550f898c6040717b6b311dd9e3b036993fa8cdc9af78d10e2e3d857f293.php b/php/apps/phpmyadmin49/tmp/twig/8e/8e28c550f898c6040717b6b311dd9e3b036993fa8cdc9af78d10e2e3d857f293.php new file mode 100644 index 00000000..9f2064e3 --- /dev/null +++ b/php/apps/phpmyadmin49/tmp/twig/8e/8e28c550f898c6040717b6b311dd9e3b036993fa8cdc9af78d10e2e3d857f293.php @@ -0,0 +1,99 @@ +source = $this->getSourceContext(); + + $this->parent = false; + + $this->blocks = [ + ]; + } + + protected function doDisplay(array $context, array $blocks = []) + { + $macros = $this->macros; + // line 1 + echo " +
        + "; + // line 3 + echo PhpMyAdmin\Url::getHiddenInputs(($context["db"] ?? null), ($context["table"] ?? null)); + echo " + + env, ($context["pos"] ?? null), "html", null, true); + echo "\" /> + env, ($context["is_browse_distinct"] ?? null), "html", null, true); + echo "\" /> + env, ($context["goto"] ?? null), "html", null, true); + echo "\" /> + "; + // line 8 + echo ($context["input_for_real_end"] ?? null); + echo " + env, ($context["title"] ?? null), "html", null, true); + echo "\""; + // line 10 + echo ($context["onclick"] ?? null); + echo " /> +
        + +"; + } + + public function getTemplateName() + { + return "display/results/table_navigation_button.twig"; + } + + public function isTraitable() + { + return false; + } + + public function getDebugInfo() + { + return array ( 73 => 10, 68 => 9, 64 => 8, 60 => 7, 56 => 6, 52 => 5, 48 => 4, 44 => 3, 40 => 2, 37 => 1,); + } + + public function getSourceContext() + { + return new Source("", "display/results/table_navigation_button.twig", "/var/www/public/phpmyadmin49/templates/display/results/table_navigation_button.twig"); + } +} diff --git a/php/apps/phpmyadmin49/tmp/twig/90/906d1d568cb0a925ef6c7529a8f316326074322fedf958be1b7d190fa3581aef.php b/php/apps/phpmyadmin49/tmp/twig/90/906d1d568cb0a925ef6c7529a8f316326074322fedf958be1b7d190fa3581aef.php new file mode 100644 index 00000000..be091d68 --- /dev/null +++ b/php/apps/phpmyadmin49/tmp/twig/90/906d1d568cb0a925ef6c7529a8f316326074322fedf958be1b7d190fa3581aef.php @@ -0,0 +1,55 @@ +source = $this->getSourceContext(); + + $this->parent = false; + + $this->blocks = [ + ]; + } + + protected function doDisplay(array $context, array $blocks = []) + { + $macros = $this->macros; + // line 1 + echo " +"; + } + + public function getTemplateName() + { + return "config/form_display/form_bottom.twig"; + } + + public function getDebugInfo() + { + return array ( 37 => 1,); + } + + public function getSourceContext() + { + return new Source("", "config/form_display/form_bottom.twig", "/var/www/public/phpMyAdmin49/templates/config/form_display/form_bottom.twig"); + } +} diff --git a/php/apps/phpmyadmin49/tmp/twig/9d/9d888ea4d7211a4f1609fe0516dee73092479718d9ce178aa6bd8e01addfb4c7.php b/php/apps/phpmyadmin49/tmp/twig/9d/9d888ea4d7211a4f1609fe0516dee73092479718d9ce178aa6bd8e01addfb4c7.php new file mode 100644 index 00000000..561872d1 --- /dev/null +++ b/php/apps/phpmyadmin49/tmp/twig/9d/9d888ea4d7211a4f1609fe0516dee73092479718d9ce178aa6bd8e01addfb4c7.php @@ -0,0 +1,94 @@ +source = $this->getSourceContext(); + + $this->parent = false; + + $this->blocks = [ + ]; + } + + protected function doDisplay(array $context, array $blocks = []) + { + $macros = $this->macros; + // line 1 + if ( !twig_test_empty(($context["class"] ?? null))) { + // line 2 + echo "
        env, ($context["class"] ?? null), "html", null, true); + echo "\"> +"; + } + // line 4 + echo "env, ($context["html_field_name"] ?? null), "html", null, true); + echo "\" id=\""; + echo ($context["html_field_id"] ?? null); + echo "\" value=\""; + echo twig_escape_filter($this->env, ($context["choice_value"] ?? null), "html", null, true); + echo "\""; + echo ((($context["checked"] ?? null)) ? (" checked=\"checked\"") : ("")); + echo " /> + +"; + // line 6 + if (($context["is_line_break"] ?? null)) { + // line 7 + echo "
        +"; + } + // line 9 + if ( !twig_test_empty(($context["class"] ?? null))) { + // line 10 + echo "
        +"; + } + } + + public function getTemplateName() + { + return "radio_fields.twig"; + } + + public function isTraitable() + { + return false; + } + + public function getDebugInfo() + { + return array ( 70 => 10, 68 => 9, 64 => 7, 62 => 6, 56 => 5, 45 => 4, 39 => 2, 37 => 1,); + } + + public function getSourceContext() + { + return new Source("", "radio_fields.twig", "/var/www/public/phpmyadmin49/templates/radio_fields.twig"); + } +} diff --git a/php/apps/phpmyadmin49/tmp/twig/b7/b718e1607529288b527b72a2a496b57a89d7820baf538acc261a876b9772a480.php b/php/apps/phpmyadmin49/tmp/twig/b7/b718e1607529288b527b72a2a496b57a89d7820baf538acc261a876b9772a480.php new file mode 100644 index 00000000..23265b4f --- /dev/null +++ b/php/apps/phpmyadmin49/tmp/twig/b7/b718e1607529288b527b72a2a496b57a89d7820baf538acc261a876b9772a480.php @@ -0,0 +1,263 @@ +source = $this->getSourceContext(); + + $this->parent = false; + + $this->blocks = [ + ]; + } + + protected function doDisplay(array $context, array $blocks = []) + { + $macros = $this->macros; + // line 1 + echo "
        +
        + "; + // line 4 + echo " "; + $this->loadTemplate("console/toolbar.twig", "console/display.twig", 4)->display(twig_to_array(["parent_div_classes" => "collapsed", "content_array" => [0 => [0 => "switch_button console_switch", 1 => _gettext("Console"), "image" => // line 7 +($context["image"] ?? null)], 1 => [0 => "button clear", 1 => _gettext("Clear")], 2 => [0 => "button history", 1 => _gettext("History")], 3 => [0 => "button options", 1 => _gettext("Options")], 4 => (( // line 11 +(isset($context["cfg_bookmark"]) || array_key_exists("cfg_bookmark", $context))) ? ([0 => "button bookmarks", 1 => _gettext("Bookmarks")]) : (null)), 5 => [0 => "button debug hide", 1 => _gettext("Debug SQL")]]])); + // line 15 + echo " "; + // line 16 + echo "
        +
        +
        + + "; + // line 20 + echo _gettext("Press Ctrl+Enter to execute query"); + // line 21 + echo " + + "; + // line 23 + echo _gettext("Press Enter to execute query"); + // line 24 + echo " +
        + "; + // line 26 + if ( !twig_test_empty(($context["sql_history"] ?? null))) { + // line 27 + echo " "; + $context['_parent'] = $context; + $context['_seq'] = twig_ensure_traversable(twig_reverse_filter($this->env, ($context["sql_history"] ?? null))); + foreach ($context['_seq'] as $context["_key"] => $context["record"]) { + // line 28 + echo "
        env, (($__internal_62824350bc4502ee19dbc2e99fc6bdd3bd90e7d8dd6e72f42c35efd048542144 = $context["record"]) && is_array($__internal_62824350bc4502ee19dbc2e99fc6bdd3bd90e7d8dd6e72f42c35efd048542144) || $__internal_62824350bc4502ee19dbc2e99fc6bdd3bd90e7d8dd6e72f42c35efd048542144 instanceof ArrayAccess ? ($__internal_62824350bc4502ee19dbc2e99fc6bdd3bd90e7d8dd6e72f42c35efd048542144["db"] ?? null) : null), "html", null, true); + echo "\" targettable=\""; + echo twig_escape_filter($this->env, (($__internal_1cfccaec8dd2e8578ccb026fbe7f2e7e29ac2ed5deb976639c5fc99a6ea8583b = $context["record"]) && is_array($__internal_1cfccaec8dd2e8578ccb026fbe7f2e7e29ac2ed5deb976639c5fc99a6ea8583b) || $__internal_1cfccaec8dd2e8578ccb026fbe7f2e7e29ac2ed5deb976639c5fc99a6ea8583b instanceof ArrayAccess ? ($__internal_1cfccaec8dd2e8578ccb026fbe7f2e7e29ac2ed5deb976639c5fc99a6ea8583b["table"] ?? null) : null), "html", null, true); + echo "\"> + "; + // line 31 + $this->loadTemplate("console/query_action.twig", "console/display.twig", 31)->display(twig_to_array(["parent_div_classes" => "action_content", "content_array" => [0 => [0 => "action collapse", 1 => _gettext("Collapse")], 1 => [0 => "action expand", 1 => _gettext("Expand")], 2 => [0 => "action requery", 1 => _gettext("Requery")], 3 => [0 => "action edit", 1 => _gettext("Edit")], 4 => [0 => "action explain", 1 => _gettext("Explain")], 5 => [0 => "action profiling", 1 => _gettext("Profiling")], 6 => (( // line 40 +(isset($context["cfg_bookmark"]) || array_key_exists("cfg_bookmark", $context))) ? ([0 => "action bookmark", 1 => _gettext("Bookmark")]) : (null)), 7 => [0 => "text failed", 1 => _gettext("Query failed")], 8 => [0 => "text targetdb", 1 => _gettext("Database"), "extraSpan" => (($__internal_68aa442c1d43d3410ea8f958ba9090f3eaa9a76f8de8fc9be4d6c7389ba28002 = // line 42 +$context["record"]) && is_array($__internal_68aa442c1d43d3410ea8f958ba9090f3eaa9a76f8de8fc9be4d6c7389ba28002) || $__internal_68aa442c1d43d3410ea8f958ba9090f3eaa9a76f8de8fc9be4d6c7389ba28002 instanceof ArrayAccess ? ($__internal_68aa442c1d43d3410ea8f958ba9090f3eaa9a76f8de8fc9be4d6c7389ba28002["db"] ?? null) : null)], 9 => [0 => "text query_time", 1 => _gettext("Queried time"), "extraSpan" => ((twig_get_attribute($this->env, $this->source, // line 46 +$context["record"], "timevalue", [], "array", true, true, false, 46)) ? ((($__internal_d7fc55f1a54b629533d60b43063289db62e68921ee7a5f8de562bd9d4a2b7ad4 = // line 47 +$context["record"]) && is_array($__internal_d7fc55f1a54b629533d60b43063289db62e68921ee7a5f8de562bd9d4a2b7ad4) || $__internal_d7fc55f1a54b629533d60b43063289db62e68921ee7a5f8de562bd9d4a2b7ad4 instanceof ArrayAccess ? ($__internal_d7fc55f1a54b629533d60b43063289db62e68921ee7a5f8de562bd9d4a2b7ad4["timevalue"] ?? null) : null)) : (_gettext("During current session")))]]])); + // line 51 + echo " "; + echo twig_escape_filter($this->env, (($__internal_01476f8db28655ee4ee02ea2d17dd5a92599be76304f08cd8bc0e05aced30666 = $context["record"]) && is_array($__internal_01476f8db28655ee4ee02ea2d17dd5a92599be76304f08cd8bc0e05aced30666) || $__internal_01476f8db28655ee4ee02ea2d17dd5a92599be76304f08cd8bc0e05aced30666 instanceof ArrayAccess ? ($__internal_01476f8db28655ee4ee02ea2d17dd5a92599be76304f08cd8bc0e05aced30666["sqlquery"] ?? null) : null), "html", null, true); + echo " +
        + "; + } + $_parent = $context['_parent']; + unset($context['_seq'], $context['_iterated'], $context['_key'], $context['record'], $context['_parent'], $context['loop']); + $context = array_intersect_key($context, $_parent) + $_parent; + // line 54 + echo " "; + } + // line 55 + echo "
        +
        + +
        +
        + "; + // line 61 + echo "
        + "; + // line 63 + echo "
        + "; + // line 64 + $this->loadTemplate("console/toolbar.twig", "console/display.twig", 64)->display(twig_to_array(["parent_div_classes" => "", "content_array" => [0 => [0 => "button order order_asc", 1 => _gettext("ascending")], 1 => [0 => "button order order_desc", 1 => _gettext("descending")], 2 => [0 => "text", 1 => _gettext("Order:")], 3 => [0 => "switch_button", 1 => _gettext("Debug SQL")], 4 => [0 => "button order_by sort_count", 1 => _gettext("Count")], 5 => [0 => "button order_by sort_exec", 1 => _gettext("Execution order")], 6 => [0 => "button order_by sort_time", 1 => _gettext("Time taken")], 7 => [0 => "text", 1 => _gettext("Order by:")], 8 => [0 => "button group_queries", 1 => _gettext("Group queries")], 9 => [0 => "button ungroup_queries", 1 => _gettext("Ungroup queries")]]])); + // line 79 + echo "
        +
        +
        +
        +
        + "; + // line 84 + $this->loadTemplate("console/query_action.twig", "console/display.twig", 84)->display(twig_to_array(["parent_div_classes" => "debug_query action_content", "content_array" => [0 => [0 => "action collapse", 1 => _gettext("Collapse")], 1 => [0 => "action expand", 1 => _gettext("Expand")], 2 => [0 => "action dbg_show_trace", 1 => _gettext("Show trace")], 3 => [0 => "action dbg_hide_trace", 1 => _gettext("Hide trace")], 4 => [0 => "text count hide", 1 => _gettext("Count")], 5 => [0 => "text time", 1 => _gettext("Time taken")]]])); + // line 95 + echo "
        +
        + "; + // line 97 + if (($context["cfg_bookmark"] ?? null)) { + // line 98 + echo "
        + "; + // line 99 + $this->loadTemplate("console/toolbar.twig", "console/display.twig", 99)->display(twig_to_array(["parent_div_classes" => "", "content_array" => [0 => [0 => "switch_button", 1 => _gettext("Bookmarks")], 1 => [0 => "button refresh", 1 => _gettext("Refresh")], 2 => [0 => "button add", 1 => _gettext("Add")]]])); + // line 107 + echo "
        + "; + // line 108 + echo ($context["bookmark_content"] ?? null); + echo " +
        +
        +
        + "; + // line 112 + $this->loadTemplate("console/toolbar.twig", "console/display.twig", 112)->display(twig_to_array(["parent_div_classes" => "", "content_array" => [0 => [0 => "switch_button", 1 => _gettext("Add bookmark")]]])); + // line 118 + echo "
        +
        + + + + +
        +
        + +
        +
        +
        +
        + "; + } + // line 138 + echo " "; + // line 139 + echo "
        + "; + // line 140 + $this->loadTemplate("console/toolbar.twig", "console/display.twig", 140)->display(twig_to_array(["parent_div_classes" => "", "content_array" => [0 => [0 => "switch_button", 1 => _gettext("Options")], 1 => [0 => "button default", 1 => _gettext("Set default")]]])); + // line 147 + echo "
        + +
        + +
        + +
        + +
        + +
        +
        +
        +
        + "; + // line 175 + echo " "; + $this->loadTemplate("console/query_action.twig", "console/display.twig", 175)->display(twig_to_array(["parent_div_classes" => "query_actions", "content_array" => [0 => [0 => "action collapse", 1 => _gettext("Collapse")], 1 => [0 => "action expand", 1 => _gettext("Expand")], 2 => [0 => "action requery", 1 => _gettext("Requery")], 3 => [0 => "action edit", 1 => _gettext("Edit")], 4 => [0 => "action explain", 1 => _gettext("Explain")], 5 => [0 => "action profiling", 1 => _gettext("Profiling")], 6 => (( // line 184 +(isset($context["cfg_bookmark"]) || array_key_exists("cfg_bookmark", $context))) ? ([0 => "action bookmark", 1 => _gettext("Bookmark")]) : (null)), 7 => [0 => "text failed", 1 => _gettext("Query failed")], 8 => [0 => "text targetdb", 1 => _gettext("Database"), "extraSpan" => ""], 9 => [0 => "text query_time", 1 => _gettext("Queried time"), "extraSpan" => ""]]])); + // line 190 + echo "
        +
        +
        +"; + } + + public function getTemplateName() + { + return "console/display.twig"; + } + + public function isTraitable() + { + return false; + } + + public function getDebugInfo() + { + return array ( 238 => 190, 236 => 184, 234 => 175, 227 => 169, 225 => 168, 220 => 165, 218 => 162, 212 => 158, 210 => 157, 205 => 154, 203 => 153, 198 => 150, 196 => 149, 192 => 147, 190 => 140, 187 => 139, 185 => 138, 173 => 129, 170 => 128, 168 => 127, 162 => 124, 156 => 121, 151 => 118, 149 => 112, 142 => 108, 139 => 107, 137 => 99, 134 => 98, 132 => 97, 128 => 95, 126 => 84, 119 => 79, 117 => 64, 114 => 63, 111 => 61, 104 => 55, 101 => 54, 91 => 51, 89 => 47, 88 => 46, 87 => 42, 86 => 40, 85 => 31, 79 => 30, 75 => 29, 73 => 28, 68 => 27, 66 => 26, 62 => 24, 60 => 23, 56 => 21, 54 => 20, 48 => 16, 46 => 15, 44 => 11, 43 => 7, 41 => 4, 37 => 1,); + } + + public function getSourceContext() + { + return new Source("", "console/display.twig", "/var/www/public/phpMyAdmin49/templates/console/display.twig"); + } +} diff --git a/php/apps/phpmyadmin49/tmp/twig/b9/b90bb06fbdd721d50b2948bf0b9d46196f29c907963d34efec74df09816d9ce1.php b/php/apps/phpmyadmin49/tmp/twig/b9/b90bb06fbdd721d50b2948bf0b9d46196f29c907963d34efec74df09816d9ce1.php new file mode 100644 index 00000000..a290b668 --- /dev/null +++ b/php/apps/phpmyadmin49/tmp/twig/b9/b90bb06fbdd721d50b2948bf0b9d46196f29c907963d34efec74df09816d9ce1.php @@ -0,0 +1,83 @@ +source = $this->getSourceContext(); + + $this->parent = false; + + $this->blocks = [ + ]; + } + + protected function doDisplay(array $context, array $blocks = []) + { + $macros = $this->macros; + // line 1 + if (((($context["delete_link"] ?? null) == ($context["delete_row"] ?? null)) || (($context["delete_link"] ?? null) == ($context["kill_process"] ?? null)))) { + // line 2 + echo "
        env, ($context["unique_id"] ?? null), "html", null, true); + echo "\" + class=\"ajax\"> + "; + // line 7 + echo PhpMyAdmin\Url::getHiddenInputs(($context["db"] ?? null), ($context["table"] ?? null), 1); + echo " + +"; + } + // line 10 + echo " +
        + env, ($context["unique_id"] ?? null), "html", null, true); + echo "\"> +"; + } + + public function getTemplateName() + { + return "display/results/multi_row_operations_form.twig"; + } + + public function isTraitable() + { + return false; + } + + public function getDebugInfo() + { + return array ( 59 => 12, 55 => 10, 49 => 7, 44 => 5, 39 => 2, 37 => 1,); + } + + public function getSourceContext() + { + return new Source("", "display/results/multi_row_operations_form.twig", "/var/www/public/phpmyadmin49/templates/display/results/multi_row_operations_form.twig"); + } +} diff --git a/php/apps/phpmyadmin49/tmp/twig/e0/e0ec177d7095831dfbca8bdcabc5a5f802c1a4412cab427eeec7b4c65ebd4c8b.php b/php/apps/phpmyadmin49/tmp/twig/e0/e0ec177d7095831dfbca8bdcabc5a5f802c1a4412cab427eeec7b4c65ebd4c8b.php new file mode 100644 index 00000000..36b49c9b --- /dev/null +++ b/php/apps/phpmyadmin49/tmp/twig/e0/e0ec177d7095831dfbca8bdcabc5a5f802c1a4412cab427eeec7b4c65ebd4c8b.php @@ -0,0 +1,136 @@ +source = $this->getSourceContext(); + + $this->parent = false; + + $this->blocks = [ + ]; + } + + protected function doDisplay(array $context, array $blocks = []) + { + $macros = $this->macros; + // line 1 + echo "env, ($context["id"] ?? null), "html", null, true); + echo "\""; + } + // line 2 + if ( !twig_test_empty(($context["class"] ?? null))) { + echo " class=\""; + echo twig_escape_filter($this->env, ($context["class"] ?? null), "html", null, true); + echo "\""; + } + echo "> + + "; + // line 4 + if ((((isset($context["url"]) || array_key_exists("url", $context)) && twig_test_iterable(($context["url"] ?? null))) && !twig_test_empty((($__internal_f607aeef2c31a95a7bf963452dff024ffaeb6aafbe4603f9ca3bec57be8633f4 = ($context["url"] ?? null)) && is_array($__internal_f607aeef2c31a95a7bf963452dff024ffaeb6aafbe4603f9ca3bec57be8633f4) || $__internal_f607aeef2c31a95a7bf963452dff024ffaeb6aafbe4603f9ca3bec57be8633f4 instanceof ArrayAccess ? ($__internal_f607aeef2c31a95a7bf963452dff024ffaeb6aafbe4603f9ca3bec57be8633f4["href"] ?? null) : null)))) { + // line 5 + echo " env, (($__internal_d7fc55f1a54b629533d60b43063289db62e68921ee7a5f8de562bd9d4a2b7ad4 = ($context["url"] ?? null)) && is_array($__internal_d7fc55f1a54b629533d60b43063289db62e68921ee7a5f8de562bd9d4a2b7ad4) || $__internal_d7fc55f1a54b629533d60b43063289db62e68921ee7a5f8de562bd9d4a2b7ad4 instanceof ArrayAccess ? ($__internal_d7fc55f1a54b629533d60b43063289db62e68921ee7a5f8de562bd9d4a2b7ad4["target"] ?? null) : null), "html", null, true); + echo "\""; + } + // line 7 + if (( !twig_test_empty((($__internal_01476f8db28655ee4ee02ea2d17dd5a92599be76304f08cd8bc0e05aced30666 = ($context["url"] ?? null)) && is_array($__internal_01476f8db28655ee4ee02ea2d17dd5a92599be76304f08cd8bc0e05aced30666) || $__internal_01476f8db28655ee4ee02ea2d17dd5a92599be76304f08cd8bc0e05aced30666 instanceof ArrayAccess ? ($__internal_01476f8db28655ee4ee02ea2d17dd5a92599be76304f08cd8bc0e05aced30666["target"] ?? null) : null)) && ((($__internal_01c35b74bd85735098add188b3f8372ba465b232ab8298cb582c60f493d3c22e = ($context["url"] ?? null)) && is_array($__internal_01c35b74bd85735098add188b3f8372ba465b232ab8298cb582c60f493d3c22e) || $__internal_01c35b74bd85735098add188b3f8372ba465b232ab8298cb582c60f493d3c22e instanceof ArrayAccess ? ($__internal_01c35b74bd85735098add188b3f8372ba465b232ab8298cb582c60f493d3c22e["target"] ?? null) : null) == "_blank"))) { + echo " rel=\"noopener noreferrer\""; + } + // line 8 + if ( !twig_test_empty((($__internal_63ad1f9a2bf4db4af64b010785e9665558fdcac0e8db8b5b413ed986c62dbb52 = ($context["url"] ?? null)) && is_array($__internal_63ad1f9a2bf4db4af64b010785e9665558fdcac0e8db8b5b413ed986c62dbb52) || $__internal_63ad1f9a2bf4db4af64b010785e9665558fdcac0e8db8b5b413ed986c62dbb52 instanceof ArrayAccess ? ($__internal_63ad1f9a2bf4db4af64b010785e9665558fdcac0e8db8b5b413ed986c62dbb52["id"] ?? null) : null))) { + echo " id=\""; + echo twig_escape_filter($this->env, (($__internal_f10a4cc339617934220127f034125576ed229e948660ebac906a15846d52f136 = ($context["url"] ?? null)) && is_array($__internal_f10a4cc339617934220127f034125576ed229e948660ebac906a15846d52f136) || $__internal_f10a4cc339617934220127f034125576ed229e948660ebac906a15846d52f136 instanceof ArrayAccess ? ($__internal_f10a4cc339617934220127f034125576ed229e948660ebac906a15846d52f136["id"] ?? null) : null), "html", null, true); + echo "\""; + } + // line 9 + if ( !twig_test_empty((($__internal_887a873a4dc3cf8bd4f99c487b4c7727999c350cc3a772414714e49a195e4386 = ($context["url"] ?? null)) && is_array($__internal_887a873a4dc3cf8bd4f99c487b4c7727999c350cc3a772414714e49a195e4386) || $__internal_887a873a4dc3cf8bd4f99c487b4c7727999c350cc3a772414714e49a195e4386 instanceof ArrayAccess ? ($__internal_887a873a4dc3cf8bd4f99c487b4c7727999c350cc3a772414714e49a195e4386["class"] ?? null) : null))) { + echo " class=\""; + echo twig_escape_filter($this->env, (($__internal_d527c24a729d38501d770b40a0d25e1ce8a7f0bff897cc4f8f449ba71fcff3d9 = ($context["url"] ?? null)) && is_array($__internal_d527c24a729d38501d770b40a0d25e1ce8a7f0bff897cc4f8f449ba71fcff3d9) || $__internal_d527c24a729d38501d770b40a0d25e1ce8a7f0bff897cc4f8f449ba71fcff3d9 instanceof ArrayAccess ? ($__internal_d527c24a729d38501d770b40a0d25e1ce8a7f0bff897cc4f8f449ba71fcff3d9["class"] ?? null) : null), "html", null, true); + echo "\""; + } + // line 10 + if ( !twig_test_empty((($__internal_f6dde3a1020453fdf35e718e94f93ce8eb8803b28cc77a665308e14bbe8572ae = ($context["url"] ?? null)) && is_array($__internal_f6dde3a1020453fdf35e718e94f93ce8eb8803b28cc77a665308e14bbe8572ae) || $__internal_f6dde3a1020453fdf35e718e94f93ce8eb8803b28cc77a665308e14bbe8572ae instanceof ArrayAccess ? ($__internal_f6dde3a1020453fdf35e718e94f93ce8eb8803b28cc77a665308e14bbe8572ae["title"] ?? null) : null))) { + echo " title=\""; + echo twig_escape_filter($this->env, (($__internal_25c0fab8152b8dd6b90603159c0f2e8a936a09ab76edb5e4d7bc95d9a8d2dc8f = ($context["url"] ?? null)) && is_array($__internal_25c0fab8152b8dd6b90603159c0f2e8a936a09ab76edb5e4d7bc95d9a8d2dc8f) || $__internal_25c0fab8152b8dd6b90603159c0f2e8a936a09ab76edb5e4d7bc95d9a8d2dc8f instanceof ArrayAccess ? ($__internal_25c0fab8152b8dd6b90603159c0f2e8a936a09ab76edb5e4d7bc95d9a8d2dc8f["title"] ?? null) : null), "html", null, true); + echo "\""; + } + echo "> + "; + } + // line 12 + echo " "; + echo ($context["content"] ?? null); + echo " + "; + // line 13 + if ((((isset($context["url"]) || array_key_exists("url", $context)) && twig_test_iterable(($context["url"] ?? null))) && !twig_test_empty((($__internal_f769f712f3484f00110c86425acea59f5af2752239e2e8596bcb6effeb425b40 = ($context["url"] ?? null)) && is_array($__internal_f769f712f3484f00110c86425acea59f5af2752239e2e8596bcb6effeb425b40) || $__internal_f769f712f3484f00110c86425acea59f5af2752239e2e8596bcb6effeb425b40 instanceof ArrayAccess ? ($__internal_f769f712f3484f00110c86425acea59f5af2752239e2e8596bcb6effeb425b40["href"] ?? null) : null)))) { + // line 14 + echo " + "; + } + // line 16 + echo " "; + if ( !twig_test_empty(($context["mysql_help_page"] ?? null))) { + // line 17 + echo " "; + echo PhpMyAdmin\Util::showMySQLDocu(($context["mysql_help_page"] ?? null)); + echo " + "; + } + // line 19 + echo " +"; + } + + public function getTemplateName() + { + return "list/item.twig"; + } + + public function isTraitable() + { + return false; + } + + public function getDebugInfo() + { + return array ( 113 => 19, 107 => 17, 104 => 16, 100 => 14, 98 => 13, 93 => 12, 84 => 10, 78 => 9, 72 => 8, 68 => 7, 62 => 6, 55 => 5, 53 => 4, 44 => 2, 37 => 1,); + } + + public function getSourceContext() + { + return new Source("", "list/item.twig", "/var/www/public/phpMyAdmin49/templates/list/item.twig"); + } +} diff --git a/php/apps/phpmyadmin49/tmp/twig/e2/e24d2d7656273ae235b34954bb74137058b4d4bc73e056f7f65e9bdeb0b6fd3c.php b/php/apps/phpmyadmin49/tmp/twig/e2/e24d2d7656273ae235b34954bb74137058b4d4bc73e056f7f65e9bdeb0b6fd3c.php new file mode 100644 index 00000000..6ad9a244 --- /dev/null +++ b/php/apps/phpmyadmin49/tmp/twig/e2/e24d2d7656273ae235b34954bb74137058b4d4bc73e056f7f65e9bdeb0b6fd3c.php @@ -0,0 +1,93 @@ +source = $this->getSourceContext(); + + $this->parent = false; + + $this->blocks = [ + ]; + } + + protected function doDisplay(array $context, array $blocks = []) + { + $macros = $this->macros; + // line 1 + echo "
        env, ($context["parent_div_classes"] ?? null), "html", null, true); + echo "\"> + "; + // line 2 + $context['_parent'] = $context; + $context['_seq'] = twig_ensure_traversable(($context["content_array"] ?? null)); + foreach ($context['_seq'] as $context["_key"] => $context["content"]) { + // line 3 + echo " "; + if ((isset($context["content"]) || array_key_exists("content", $context))) { + // line 4 + echo "
        env, (($__internal_f607aeef2c31a95a7bf963452dff024ffaeb6aafbe4603f9ca3bec57be8633f4 = $context["content"]) && is_array($__internal_f607aeef2c31a95a7bf963452dff024ffaeb6aafbe4603f9ca3bec57be8633f4) || $__internal_f607aeef2c31a95a7bf963452dff024ffaeb6aafbe4603f9ca3bec57be8633f4 instanceof ArrayAccess ? ($__internal_f607aeef2c31a95a7bf963452dff024ffaeb6aafbe4603f9ca3bec57be8633f4[0] ?? null) : null), "html", null, true); + echo "\"> + "; + // line 5 + echo ((twig_get_attribute($this->env, $this->source, $context["content"], "image", [], "array", true, true, false, 5)) ? ((($__internal_62824350bc4502ee19dbc2e99fc6bdd3bd90e7d8dd6e72f42c35efd048542144 = $context["content"]) && is_array($__internal_62824350bc4502ee19dbc2e99fc6bdd3bd90e7d8dd6e72f42c35efd048542144) || $__internal_62824350bc4502ee19dbc2e99fc6bdd3bd90e7d8dd6e72f42c35efd048542144 instanceof ArrayAccess ? ($__internal_62824350bc4502ee19dbc2e99fc6bdd3bd90e7d8dd6e72f42c35efd048542144["image"] ?? null) : null)) : ("")); + echo " + "; + // line 6 + echo twig_escape_filter($this->env, (($__internal_1cfccaec8dd2e8578ccb026fbe7f2e7e29ac2ed5deb976639c5fc99a6ea8583b = $context["content"]) && is_array($__internal_1cfccaec8dd2e8578ccb026fbe7f2e7e29ac2ed5deb976639c5fc99a6ea8583b) || $__internal_1cfccaec8dd2e8578ccb026fbe7f2e7e29ac2ed5deb976639c5fc99a6ea8583b instanceof ArrayAccess ? ($__internal_1cfccaec8dd2e8578ccb026fbe7f2e7e29ac2ed5deb976639c5fc99a6ea8583b[1] ?? null) : null), "html", null, true); + echo " +
        + "; + } + // line 9 + echo " "; + } + $_parent = $context['_parent']; + unset($context['_seq'], $context['_iterated'], $context['_key'], $context['content'], $context['_parent'], $context['loop']); + $context = array_intersect_key($context, $_parent) + $_parent; + // line 10 + echo "
        +"; + } + + public function getTemplateName() + { + return "console/toolbar.twig"; + } + + public function isTraitable() + { + return false; + } + + public function getDebugInfo() + { + return array ( 70 => 10, 64 => 9, 58 => 6, 54 => 5, 49 => 4, 46 => 3, 42 => 2, 37 => 1,); + } + + public function getSourceContext() + { + return new Source("", "console/toolbar.twig", "/var/www/public/phpMyAdmin49/templates/console/toolbar.twig"); + } +} diff --git a/php/apps/phpmyadmin49/tmp/twig/ed/edf7aabede60b9fce0b0a893cb36c39346b7cad6c39cfd8964e8180ffa510faa.php b/php/apps/phpmyadmin49/tmp/twig/ed/edf7aabede60b9fce0b0a893cb36c39346b7cad6c39cfd8964e8180ffa510faa.php new file mode 100644 index 00000000..4425c81f --- /dev/null +++ b/php/apps/phpmyadmin49/tmp/twig/ed/edf7aabede60b9fce0b0a893cb36c39346b7cad6c39cfd8964e8180ffa510faa.php @@ -0,0 +1,173 @@ +source = $this->getSourceContext(); + + $this->parent = false; + + $this->blocks = [ + ]; + } + + protected function doDisplay(array $context, array $blocks = []) + { + $macros = $this->macros; + // line 1 + echo " + "; + // line 2 + echo PhpMyAdmin\Url::getHiddenInputs(["db" => // line 3 +($context["db"] ?? null), "table" => // line 4 +($context["table"] ?? null), "sql_query" => // line 5 +($context["sql_query"] ?? null), "goto" => // line 6 +($context["goto"] ?? null), "display_options_form" => 1]); + // line 8 + echo " + + "; + // line 10 + echo PhpMyAdmin\Util::getDivForSliderEffect("", _gettext("Options")); + echo " +
        +
        + "; + // line 14 + echo " "; + echo PhpMyAdmin\Util::getRadioFields("pftext", ["P" => _gettext("Partial texts"), "F" => _gettext("Full texts")], // line 20 +($context["pftext"] ?? null), true, true, "", ("pftext_" . // line 24 +($context["unique_id"] ?? null))); + // line 25 + echo " +
        + + "; + // line 28 + if ((($context["relwork"] ?? null) && ($context["displaywork"] ?? null))) { + // line 29 + echo "
        + "; + // line 30 + echo PhpMyAdmin\Util::getRadioFields("relational_display", ["K" => _gettext("Relational key"), "D" => _gettext("Display column for relationships")], // line 36 +($context["relational_display"] ?? null), true, true, "", ("relational_display_" . // line 40 +($context["unique_id"] ?? null))); + // line 41 + echo " +
        + "; + } + // line 44 + echo " +
        + "; + // line 46 + $this->loadTemplate("checkbox.twig", "display/results/options_block.twig", 46)->display(twig_to_array(["html_field_name" => "display_binary", "label" => _gettext("Show binary contents"), "checked" => !twig_test_empty( // line 49 +($context["display_binary"] ?? null)), "onclick" => false, "html_field_id" => ("display_binary_" . // line 51 +($context["unique_id"] ?? null))])); + // line 53 + echo " "; + $this->loadTemplate("checkbox.twig", "display/results/options_block.twig", 53)->display(twig_to_array(["html_field_name" => "display_blob", "label" => _gettext("Show BLOB contents"), "checked" => !twig_test_empty( // line 56 +($context["display_blob"] ?? null)), "onclick" => false, "html_field_id" => ("display_blob_" . // line 58 +($context["unique_id"] ?? null))])); + // line 60 + echo "
        + + "; + // line 66 + echo "
        + "; + // line 67 + $this->loadTemplate("checkbox.twig", "display/results/options_block.twig", 67)->display(twig_to_array(["html_field_name" => "hide_transformation", "label" => _gettext("Hide browser transformation"), "checked" => !twig_test_empty( // line 70 +($context["hide_transformation"] ?? null)), "onclick" => false, "html_field_id" => ("hide_transformation_" . // line 72 +($context["unique_id"] ?? null))])); + // line 74 + echo "
        + + + "; + // line 77 + if (($context["possible_as_geometry"] ?? null)) { + // line 78 + echo "
        + "; + // line 79 + echo PhpMyAdmin\Util::getRadioFields("geoOption", ["GEOM" => _gettext("Geometry"), "WKT" => _gettext("Well Known Text"), "WKB" => _gettext("Well Known Binary")], // line 86 +($context["geo_option"] ?? null), true, true, "", ("geoOption_" . // line 90 +($context["unique_id"] ?? null))); + // line 91 + echo " +
        + "; + } else { + // line 94 + echo "
        + "; + // line 95 + echo twig_escape_filter($this->env, ($context["possible_as_geometry"] ?? null), "html", null, true); + echo " + "; + // line 96 + echo PhpMyAdmin\Util::getRadioFields("geoOption", ["WKT" => _gettext("Well Known Text"), "WKB" => _gettext("Well Known Binary")], // line 102 +($context["geo_option"] ?? null), true, true, "", ("geoOption_" . // line 106 +($context["unique_id"] ?? null))); + // line 107 + echo " +
        + "; + } + // line 110 + echo "
        +
        + +
        + +
        + "; + // line 117 + echo " +"; + } + + public function getTemplateName() + { + return "display/results/options_block.twig"; + } + + public function isTraitable() + { + return false; + } + + public function getDebugInfo() + { + return array ( 150 => 117, 145 => 114, 139 => 110, 134 => 107, 132 => 106, 131 => 102, 130 => 96, 126 => 95, 123 => 94, 118 => 91, 116 => 90, 115 => 86, 114 => 79, 111 => 78, 109 => 77, 104 => 74, 102 => 72, 101 => 70, 100 => 67, 97 => 66, 93 => 60, 91 => 58, 90 => 56, 88 => 53, 86 => 51, 85 => 49, 84 => 46, 80 => 44, 75 => 41, 73 => 40, 72 => 36, 71 => 30, 68 => 29, 66 => 28, 61 => 25, 59 => 24, 58 => 20, 56 => 14, 50 => 10, 46 => 8, 44 => 6, 43 => 5, 42 => 4, 41 => 3, 40 => 2, 37 => 1,); + } + + public function getSourceContext() + { + return new Source("", "display/results/options_block.twig", "/var/www/public/phpmyadmin49/templates/display/results/options_block.twig"); + } +} diff --git a/php/apps/phpmyadmin49/tmp/twig/f1/f1dfd5bbcfc7bfd077c3d7e8ba37fc58bcd401bb140c3fd5aabeac1a9d45f380.php b/php/apps/phpmyadmin49/tmp/twig/f1/f1dfd5bbcfc7bfd077c3d7e8ba37fc58bcd401bb140c3fd5aabeac1a9d45f380.php new file mode 100644 index 00000000..58286b4f --- /dev/null +++ b/php/apps/phpmyadmin49/tmp/twig/f1/f1dfd5bbcfc7bfd077c3d7e8ba37fc58bcd401bb140c3fd5aabeac1a9d45f380.php @@ -0,0 +1,70 @@ +source = $this->getSourceContext(); + + $this->parent = false; + + $this->blocks = [ + ]; + } + + protected function doDisplay(array $context, array $blocks = []) + { + $macros = $this->macros; + // line 2 + echo "
        + +
        +"; + // line 6 + echo "
        + "; + // line 8 + echo "
        +
        + "; + // line 11 + echo "
        +"; + // line 13 + echo " +"; + } + + public function getTemplateName() + { + return "database/search/result_divs.twig"; + } + + public function getDebugInfo() + { + return array ( 52 => 13, 49 => 11, 45 => 8, 42 => 6, 37 => 2,); + } + + public function getSourceContext() + { + return new Source("", "database/search/result_divs.twig", "/var/www/public/phpMyAdmin49/templates/database/search/result_divs.twig"); + } +} diff --git a/php/apps/phpmyadmin49/transformation_overview.php b/php/apps/phpmyadmin49/transformation_overview.php new file mode 100644 index 00000000..761e5db8 --- /dev/null +++ b/php/apps/phpmyadmin49/transformation_overview.php @@ -0,0 +1,73 @@ +getHeader(); +$header->disableMenuAndConsole(); + +$types = Transformations::getAvailableMIMEtypes(); +?> + +

        + $mimetype) { + + if (isset($types['empty_mimetype'][$mimetype])) { + echo '' , htmlspecialchars($mimetype) , '
        '; + } else { + echo htmlspecialchars($mimetype) , '
        '; + } + +} +$transformation_types = array( + 'transformation', 'input_transformation' +); +$label = array( + 'transformation' => __('Available browser display transformations'), + 'input_transformation' => __('Available input transformations') +); +$th = array( + 'transformation' => __('Browser display transformation'), + 'input_transformation' => __('Input transformation') +); +?> +
        + + +

        +
        + + + + + + + + $transform) { + $desc = Transformations::getDescription($types[$ttype . '_file'][$key]); + ?> + + + + + + +
        + getRelationsParam(); + +/** + * Ensures db and table are valid, else moves to the "parent" script + */ +require_once './libraries/db_table_exists.inc.php'; + + +/** + * Sets globals from $_REQUEST + */ +$request_params = array( + 'cn', + 'ct', + 'sql_query', + 'transform_key', + 'where_clause' +); +$size_params = array( + 'newHeight', + 'newWidth', +); +foreach ($request_params as $one_request_param) { + if (isset($_REQUEST[$one_request_param])) { + if (in_array($one_request_param, $size_params)) { + $GLOBALS[$one_request_param] = intval($_REQUEST[$one_request_param]); + if ($GLOBALS[$one_request_param] > 2000) { + $GLOBALS[$one_request_param] = 2000; + } + } else { + $GLOBALS[$one_request_param] = $_REQUEST[$one_request_param]; + } + } +} + + +/** + * Get the list of the fields of the current table + */ +$GLOBALS['dbi']->selectDb($db); +if (isset($where_clause)) { + $result = $GLOBALS['dbi']->query( + 'SELECT * FROM ' . PhpMyAdmin\Util::backquote($table) + . ' WHERE ' . $where_clause . ';', + PhpMyAdmin\DatabaseInterface::CONNECT_USER, + PhpMyAdmin\DatabaseInterface::QUERY_STORE + ); + $row = $GLOBALS['dbi']->fetchAssoc($result); +} else { + $result = $GLOBALS['dbi']->query( + 'SELECT * FROM ' . PhpMyAdmin\Util::backquote($table) . ' LIMIT 1;', + PhpMyAdmin\DatabaseInterface::CONNECT_USER, + PhpMyAdmin\DatabaseInterface::QUERY_STORE + ); + $row = $GLOBALS['dbi']->fetchAssoc($result); +} + +// No row returned +if (! $row) { + exit; +} // end if (no record returned) + +$default_ct = 'application/octet-stream'; + +if ($cfgRelation['commwork'] && $cfgRelation['mimework']) { + $mime_map = Transformations::getMIME($db, $table); + $mime_options = Transformations::getOptions( + isset($mime_map[$transform_key]['transformation_options']) + ? $mime_map[$transform_key]['transformation_options'] : '' + ); + + foreach ($mime_options as $key => $option) { + if (substr($option, 0, 10) == '; charset=') { + $mime_options['charset'] = $option; + } + } +} + +// Only output the http headers +$response = Response::getInstance(); +$response->getHeader()->sendHttpHeaders(); + +// [MIME] +if (isset($ct) && ! empty($ct)) { + $mime_type = $ct; +} else { + $mime_type = (!empty($mime_map[$transform_key]['mimetype']) + ? str_replace('_', '/', $mime_map[$transform_key]['mimetype']) + : $default_ct) + . (isset($mime_options['charset']) ? $mime_options['charset'] : ''); +} + +Core::downloadHeader($cn, $mime_type); + +if (! isset($_REQUEST['resize'])) { + if (stripos($mime_type, 'html') === false) { + echo $row[$transform_key]; + } else { + echo htmlspecialchars($row[$transform_key]); + } +} else { + // if image_*__inline.inc.php finds that we can resize, + // it sets the resize parameter to jpeg or png + + $srcImage = imagecreatefromstring($row[$transform_key]); + $srcWidth = ImageSX($srcImage); + $srcHeight = ImageSY($srcImage); + + // Check to see if the width > height or if width < height + // if so adjust accordingly to make sure the image + // stays smaller than the new width and new height + + $ratioWidth = $srcWidth/$_REQUEST['newWidth']; + $ratioHeight = $srcHeight/$_REQUEST['newHeight']; + + if ($ratioWidth < $ratioHeight) { + $destWidth = $srcWidth/$ratioHeight; + $destHeight = $_REQUEST['newHeight']; + } else { + $destWidth = $_REQUEST['newWidth']; + $destHeight = $srcHeight/$ratioWidth; + } + + if ($_REQUEST['resize']) { + $destImage = ImageCreateTrueColor($destWidth, $destHeight); + } + + // ImageCopyResized($destImage, $srcImage, 0, 0, 0, 0, + // $destWidth, $destHeight, $srcWidth, $srcHeight); + // better quality but slower: + ImageCopyResampled( + $destImage, $srcImage, 0, 0, 0, 0, $destWidth, + $destHeight, $srcWidth, $srcHeight + ); + + if ($_REQUEST['resize'] == 'jpeg') { + ImageJPEG($destImage, null, 75); + } + if ($_REQUEST['resize'] == 'png') { + ImagePNG($destImage); + } + ImageDestroy($srcImage); + ImageDestroy($destImage); +} diff --git a/php/apps/phpmyadmin49/url.php b/php/apps/phpmyadmin49/url.php new file mode 100644 index 00000000..edaab5ac --- /dev/null +++ b/php/apps/phpmyadmin49/url.php @@ -0,0 +1,43 @@ +getHeader()->sendHttpHeaders(); +$response->disable(); + +if (! Core::isValid($_GET['url']) + || ! preg_match('/^https:\/\/[^\n\r]*$/', $_GET['url']) + || ! Core::isAllowedDomain($_GET['url']) +) { + Core::sendHeaderLocation('./'); +} else { + // JavaScript redirection is necessary. Because if header() is used + // then web browser sometimes does not change the HTTP_REFERER + // field and so with old URL as Referer, token also goes to + // external site. + echo ""; + // Display redirecting msg on screen. + // Do not display the value of $_GET['url'] to avoid showing injected content + echo __('Taking you to the target site.'); +} +die(); diff --git a/php/apps/phpmyadmin49/user_password.php b/php/apps/phpmyadmin49/user_password.php new file mode 100644 index 00000000..10663e93 --- /dev/null +++ b/php/apps/phpmyadmin49/user_password.php @@ -0,0 +1,73 @@ +getHeader(); +$scripts = $header->getScripts(); +$scripts->addFile('server_privileges.js'); +$scripts->addFile('vendor/zxcvbn.js'); + +$userPassword = new UserPassword(); + +/** + * Displays an error message and exits if the user isn't allowed to use this + * script + */ +if (! $GLOBALS['cfg']['ShowChgPassword']) { + $GLOBALS['cfg']['ShowChgPassword'] = $GLOBALS['dbi']->selectDb('mysql'); +} +if ($cfg['Server']['auth_type'] == 'config' || ! $cfg['ShowChgPassword']) { + Message::error( + __('You don\'t have sufficient privileges to be here right now!') + )->display(); + exit; +} // end if + +/** + * If the "change password" form has been submitted, checks for valid values + * and submit the query or logout + */ +if (isset($_POST['nopass'])) { + if ($_POST['nopass'] == '1') { + $password = ''; + } else { + $password = $_POST['pma_pw']; + } + $change_password_message = $userPassword->setChangePasswordMsg(); + $msg = $change_password_message['msg']; + if (! $change_password_message['error']) { + $userPassword->changePassword($password, $msg, $change_password_message); + } else { + $userPassword->getChangePassMessage($change_password_message); + } +} + +/** + * If the "change password" form hasn't been submitted or the values submitted + * aren't valid -> displays the form + */ + +// Displays an error message if required +if (isset($msg)) { + $msg->display(); + unset($msg); +} + +echo ChangePassword::getHtml('change_pw', $username, $hostname); +exit; diff --git a/php/apps/phpmyadmin49/version_check.php b/php/apps/phpmyadmin49/version_check.php new file mode 100644 index 00000000..4805e573 --- /dev/null +++ b/php/apps/phpmyadmin49/version_check.php @@ -0,0 +1,44 @@ +disable(); + +// Always send the correct headers +Core::headerJSON(); + +$versionInformation = new VersionInformation(); +$versionDetails = $versionInformation->getLatestVersion(); + +if (empty($versionDetails)) { + echo json_encode(array()); +} else { + $latestCompatible = $versionInformation->getLatestCompatibleVersion( + $versionDetails->releases + ); + $version = ''; + $date = ''; + if ($latestCompatible != null) { + $version = $latestCompatible['version']; + $date = $latestCompatible['date']; + } + echo json_encode( + array( + 'version' => (! empty($version) ? $version : ''), + 'date' => (! empty($date) ? $date : ''), + ) + ); +} diff --git a/php/apps/phpmyadmin49/view_create.php b/php/apps/phpmyadmin49/view_create.php new file mode 100644 index 00000000..d9dfbc20 --- /dev/null +++ b/php/apps/phpmyadmin49/view_create.php @@ -0,0 +1,234 @@ +addJSON( + 'message', + $message + ); + $response->setRequestStatus(false); + exit; +} + +if (isset($_POST['createview']) || isset($_POST['alterview'])) { + /** + * Creates the view + */ + $sep = "\r\n"; + + if (isset($_POST['createview'])) { + $sql_query = 'CREATE'; + if (isset($_POST['view']['or_replace'])) { + $sql_query .= ' OR REPLACE'; + } + } else { + $sql_query = 'ALTER'; + } + + if (Core::isValid($_POST['view']['algorithm'], $view_algorithm_options)) { + $sql_query .= $sep . ' ALGORITHM = ' . $_POST['view']['algorithm']; + } + + if (! empty($_POST['view']['definer'])) { + if (strpos($_POST['view']['definer'], '@') === false) { + $sql_query .= $sep . 'DEFINER=' + . PhpMyAdmin\Util::backquote($_POST['view']['definer']); + } else { + $arr = explode('@', $_POST['view']['definer']); + $sql_query .= $sep . 'DEFINER=' . PhpMyAdmin\Util::backquote($arr[0]); + $sql_query .= '@' . PhpMyAdmin\Util::backquote($arr[1]) . ' '; + } + } + + if (isset($_POST['view']['sql_security'])) { + if (in_array($_POST['view']['sql_security'], $view_security_options)) { + $sql_query .= $sep . ' SQL SECURITY ' + . $_POST['view']['sql_security']; + } + } + + $sql_query .= $sep . ' VIEW ' + . PhpMyAdmin\Util::backquote($_POST['view']['name']); + + if (! empty($_POST['view']['column_names'])) { + $sql_query .= $sep . ' (' . $_POST['view']['column_names'] . ')'; + } + + $sql_query .= $sep . ' AS ' . $_POST['view']['as']; + + if (isset($_POST['view']['with'])) { + if (in_array($_POST['view']['with'], $view_with_options)) { + $sql_query .= $sep . ' WITH ' . $_POST['view']['with'] + . ' CHECK OPTION'; + } + } + + if (!$GLOBALS['dbi']->tryQuery($sql_query)) { + if (! isset($_POST['ajax_dialog'])) { + $message = PhpMyAdmin\Message::rawError($GLOBALS['dbi']->getError()); + return; + } + + $response->addJSON( + 'message', + PhpMyAdmin\Message::error( + "" . htmlspecialchars($sql_query) . "

        " + . $GLOBALS['dbi']->getError() + ) + ); + $response->setRequestStatus(false); + exit; + } + + // If different column names defined for VIEW + $view_columns = array(); + if (isset($_POST['view']['column_names'])) { + $view_columns = explode(',', $_POST['view']['column_names']); + } + + $column_map = $GLOBALS['dbi']->getColumnMapFromSql( + $_POST['view']['as'], $view_columns + ); + + $systemDb = $GLOBALS['dbi']->getSystemDatabase(); + $pma_transformation_data = $systemDb->getExistingTransformationData( + $GLOBALS['db'] + ); + + if ($pma_transformation_data !== false) { + + // SQL for store new transformation details of VIEW + $new_transformations_sql = $systemDb->getNewTransformationDataSql( + $pma_transformation_data, $column_map, + $_POST['view']['name'], $GLOBALS['db'] + ); + + // Store new transformations + if ($new_transformations_sql != '') { + $GLOBALS['dbi']->tryQuery($new_transformations_sql); + } + + } + unset($pma_transformation_data); + + if (! isset($_POST['ajax_dialog'])) { + $message = PhpMyAdmin\Message::success(); + include 'tbl_structure.php'; + } else { + $response->addJSON( + 'message', + PhpMyAdmin\Util::getMessage( + PhpMyAdmin\Message::success(), + $sql_query + ) + ); + $response->setRequestStatus(true); + } + + exit; +} + +$sql_query = ! empty($_POST['sql_query']) ? $_POST['sql_query'] : ''; + +// prefill values if not already filled from former submission +$view = array( + 'operation' => 'create', + 'or_replace' => '', + 'algorithm' => '', + 'definer' => '', + 'sql_security' => '', + 'name' => '', + 'column_names' => '', + 'as' => $sql_query, + 'with' => '', + 'algorithm' => '', +); + +// Used to prefill the fields when editing a view +if (isset($_GET['db']) && isset($_GET['table'])) { + $item = $GLOBALS['dbi']->fetchSingleRow( + sprintf( + "SELECT `VIEW_DEFINITION`, `CHECK_OPTION`, `DEFINER`, + `SECURITY_TYPE` + FROM `INFORMATION_SCHEMA`.`VIEWS` + WHERE TABLE_SCHEMA='%s' + AND TABLE_NAME='%s';", + $GLOBALS['dbi']->escapeString($_GET['db']), + $GLOBALS['dbi']->escapeString($_GET['table']) + ) + ); + $createView = $GLOBALS['dbi']->getTable($_GET['db'], $_GET['table']) + ->showCreate(); + + // CREATE ALGORITHM= DE... + $parts = explode(" ", substr($createView, 17)); + $item['ALGORITHM'] = $parts[0]; + + $view['operation'] = 'alter'; + $view['definer'] = $item['DEFINER']; + $view['sql_security'] = $item['SECURITY_TYPE']; + $view['name'] = $_GET['table']; + $view['as'] = $item['VIEW_DEFINITION']; + $view['with'] = $item['CHECK_OPTION']; + $view['algorithm'] = $item['ALGORITHM']; + +} + +if (Core::isValid($_POST['view'], 'array')) { + $view = array_merge($view, $_POST['view']); +} + +$url_params['db'] = $GLOBALS['db']; +$url_params['reload'] = 1; + +echo Template::get('view_create')->render([ + 'ajax_dialog' => isset($_POST['ajax_dialog']), + 'text_dir' => $text_dir, + 'url_params' => $url_params, + 'view' => $view, + 'view_algorithm_options' => $view_algorithm_options, + 'view_with_options' => $view_with_options, + 'view_security_options' => $view_security_options, +]); diff --git a/php/apps/phpmyadmin49/view_operations.php b/php/apps/phpmyadmin49/view_operations.php new file mode 100644 index 00000000..5fd9ab8f --- /dev/null +++ b/php/apps/phpmyadmin49/view_operations.php @@ -0,0 +1,149 @@ +getHeader(); +$scripts = $header->getScripts(); +$scripts->addFile('tbl_operations.js'); + +/** + * Runs common work + */ +require './libraries/tbl_common.inc.php'; +$url_query .= '&goto=view_operations.php&back=view_operations.php'; +$url_params['goto'] = $url_params['back'] = 'view_operations.php'; + +$operations = new Operations(); + +/** + * Updates if required + */ +$_message = new Message; +$_type = 'success'; +if (isset($_POST['submitoptions'])) { + + if (isset($_POST['new_name'])) { + if ($pma_table->rename($_POST['new_name'])) { + $_message->addText($pma_table->getLastMessage()); + $result = true; + $GLOBALS['table'] = $pma_table->getName(); + /* Force reread after rename */ + $pma_table->getStatusInfo(null, true); + $reload = true; + } else { + $_message->addText($pma_table->getLastError()); + $result = false; + } + } + + $warning_messages = $operations->getWarningMessagesArray(); +} + +if (isset($result)) { + // set to success by default, because result set could be empty + // (for example, a table rename) + if (empty($_message->getString())) { + if ($result) { + $_message->addText( + __('Your SQL query has been executed successfully.') + ); + } else { + $_message->addText(__('Error')); + } + // $result should exist, regardless of $_message + $_type = $result ? 'success' : 'error'; + } + if (! empty($warning_messages)) { + $_message->addMessagesString($warning_messages); + $_message->isError(true); + unset($warning_messages); + } + echo Util::getMessage( + $_message, $sql_query, $_type + ); +} +unset($_message, $_type); + +$url_params['goto'] = 'view_operations.php'; +$url_params['back'] = 'view_operations.php'; + +/** + * Displays the page + */ +?> + +
        +
        + + +
        + + + + + + + +
        +
        +
        +
        + + +
        +
        +
        + 'DROP VIEW ' . Util::backquote( + $GLOBALS['table'] + ), + 'goto' => 'tbl_structure.php', + 'reload' => '1', + 'purge' => '1', + 'message_to_show' => sprintf( + __('View %s has been dropped.'), + htmlspecialchars($GLOBALS['table']) + ), + 'table' => $GLOBALS['table'] + ) +); +echo '
        '; +echo '
        '; +echo '' , __('Delete data or table') , ''; + +echo '
          '; +echo $operations->getDeleteDataOrTablelink( + $drop_view_url_params, + 'DROP VIEW', + __('Delete the view (DROP)'), + 'drop_view_anchor' +); +echo '
        '; +echo '
        '; +echo '
        '; diff --git a/php/apps/phpmyadmin49/yarn.lock b/php/apps/phpmyadmin49/yarn.lock new file mode 100644 index 00000000..5890527e --- /dev/null +++ b/php/apps/phpmyadmin49/yarn.lock @@ -0,0 +1,856 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@babel/code-frame@^7.0.0": + version "7.5.5" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.5.5.tgz#bc0782f6d69f7b7d49531219699b988f669a8f9d" + integrity sha512-27d4lZoomVyo51VegxI20xZPuSHusqbQag/ztrBC7wegWoQ1nLREPVSKSW8byhTlzTKyNE4ifaTA6lCp7JjpFw== + dependencies: + "@babel/highlight" "^7.0.0" + +"@babel/highlight@^7.0.0": + version "7.5.0" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.5.0.tgz#56d11312bd9248fa619591d02472be6e8cb32540" + integrity sha512-7dV4eu9gBxoM0dAnj/BCFDW9LFU0zvTrkq0ugM7pnHEgguOEeOz1so2ZghEdzviYzQEED0r4EAgpsBChKy1TRQ== + dependencies: + chalk "^2.0.0" + esutils "^2.0.2" + js-tokens "^4.0.0" + +acorn-jsx@^5.0.2: + version "5.0.2" + resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.0.2.tgz#84b68ea44b373c4f8686023a551f61a21b7c4a4f" + integrity sha512-tiNTrP1MP0QrChmD2DdupCr6HWSFeKVw5d/dHTu4Y7rkAkRhU/Dt7dphAfIUyxtHpl/eBVip5uTNSpQJHylpAw== + +acorn@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.0.0.tgz#26b8d1cd9a9b700350b71c0905546f64d1284e7a" + integrity sha512-PaF/MduxijYYt7unVGRuds1vBC9bFxbNf+VWqhOClfdgy7RlVkQqt610ig1/yxTgsDIfW1cWDel5EBbOy3jdtQ== + +ajv@^6.10.0, ajv@^6.10.2: + version "6.10.2" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.10.2.tgz#d3cea04d6b017b2894ad69040fec8b623eb4bd52" + integrity sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw== + dependencies: + fast-deep-equal "^2.0.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" + +ansi-escapes@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.2.0.tgz#8780b98ff9dbf5638152d1f1fe5c1d7b4442976b" + integrity sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ== + +ansi-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998" + integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg= + +ansi-regex@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997" + integrity sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg== + +ansi-styles@^3.2.0, ansi-styles@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" + integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== + dependencies: + color-convert "^1.9.0" + +argparse@^1.0.7: + version "1.0.10" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" + integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== + dependencies: + sprintf-js "~1.0.2" + +astral-regex@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-1.0.0.tgz#6c8c3fb827dd43ee3918f27b82782ab7658a6fd9" + integrity sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg== + +balanced-match@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" + integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +callsites@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" + integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== + +chalk@^2.0.0, chalk@^2.1.0, chalk@^2.4.2: + version "2.4.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" + integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== + dependencies: + ansi-styles "^3.2.1" + escape-string-regexp "^1.0.5" + supports-color "^5.3.0" + +chardet@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e" + integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA== + +cli-cursor@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-2.1.0.tgz#b35dac376479facc3e94747d41d0d0f5238ffcb5" + integrity sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU= + dependencies: + restore-cursor "^2.0.0" + +cli-width@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.0.tgz#ff19ede8a9a5e579324147b0c11f0fbcbabed639" + integrity sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk= + +codemirror@5.48.4: + version "5.48.4" + resolved "https://registry.yarnpkg.com/codemirror/-/codemirror-5.48.4.tgz#4210fbe92be79a88f0eea348fab3ae78da85ce47" + integrity sha512-pUhZXDQ6qXSpWdwlgAwHEkd4imA0kf83hINmUEzJpmG80T/XLtDDEzZo8f6PQLuRCcUQhmzqqIo3ZPTRaWByRA== + +color-convert@^1.9.0: + version "1.9.3" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" + integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== + dependencies: + color-name "1.1.3" + +color-name@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" + integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= + +cross-spawn@^6.0.5: + version "6.0.5" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" + integrity sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ== + dependencies: + nice-try "^1.0.4" + path-key "^2.0.1" + semver "^5.5.0" + shebang-command "^1.2.0" + which "^1.2.9" + +debug@^4.0.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791" + integrity sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw== + dependencies: + ms "^2.1.1" + +deep-is@~0.1.3: + version "0.1.3" + resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" + integrity sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ= + +doctrine@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961" + integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w== + dependencies: + esutils "^2.0.2" + +emoji-regex@^7.0.1: + version "7.0.3" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156" + integrity sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA== + +escape-string-regexp@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= + +eslint-scope@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.0.0.tgz#e87c8887c73e8d1ec84f1ca591645c358bfc8fb9" + integrity sha512-oYrhJW7S0bxAFDvWqzvMPRm6pcgcnWc4QnofCAqRTRfQC0JcwenzGglTtsLyIuuWFfkqDG9vz67cnttSd53djw== + dependencies: + esrecurse "^4.1.0" + estraverse "^4.1.1" + +eslint-utils@^1.4.2: + version "1.4.2" + resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-1.4.2.tgz#166a5180ef6ab7eb462f162fd0e6f2463d7309ab" + integrity sha512-eAZS2sEUMlIeCjBeubdj45dmBHQwPHWyBcT1VSYB7o9x9WRRqKxyUoiXlRjyAwzN7YEzHJlYg0NmzDRWx6GP4Q== + dependencies: + eslint-visitor-keys "^1.0.0" + +eslint-visitor-keys@^1.0.0, eslint-visitor-keys@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.1.0.tgz#e2a82cea84ff246ad6fb57f9bde5b46621459ec2" + integrity sha512-8y9YjtM1JBJU/A9Kc+SbaOV4y29sSWckBwMHa+FGtVj5gN/sbnKDf6xJUl+8g7FAij9LVaP8C24DUiH/f/2Z9A== + +eslint@^6.2.2: + version "6.2.2" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-6.2.2.tgz#03298280e7750d81fcd31431f3d333e43d93f24f" + integrity sha512-mf0elOkxHbdyGX1IJEUsNBzCDdyoUgljF3rRlgfyYh0pwGnreLc0jjD6ZuleOibjmnUWZLY2eXwSooeOgGJ2jw== + dependencies: + "@babel/code-frame" "^7.0.0" + ajv "^6.10.0" + chalk "^2.1.0" + cross-spawn "^6.0.5" + debug "^4.0.1" + doctrine "^3.0.0" + eslint-scope "^5.0.0" + eslint-utils "^1.4.2" + eslint-visitor-keys "^1.1.0" + espree "^6.1.1" + esquery "^1.0.1" + esutils "^2.0.2" + file-entry-cache "^5.0.1" + functional-red-black-tree "^1.0.1" + glob-parent "^5.0.0" + globals "^11.7.0" + ignore "^4.0.6" + import-fresh "^3.0.0" + imurmurhash "^0.1.4" + inquirer "^6.4.1" + is-glob "^4.0.0" + js-yaml "^3.13.1" + json-stable-stringify-without-jsonify "^1.0.1" + levn "^0.3.0" + lodash "^4.17.14" + minimatch "^3.0.4" + mkdirp "^0.5.1" + natural-compare "^1.4.0" + optionator "^0.8.2" + progress "^2.0.0" + regexpp "^2.0.1" + semver "^6.1.2" + strip-ansi "^5.2.0" + strip-json-comments "^3.0.1" + table "^5.2.3" + text-table "^0.2.0" + v8-compile-cache "^2.0.3" + +espree@^6.1.1: + version "6.1.1" + resolved "https://registry.yarnpkg.com/espree/-/espree-6.1.1.tgz#7f80e5f7257fc47db450022d723e356daeb1e5de" + integrity sha512-EYbr8XZUhWbYCqQRW0duU5LxzL5bETN6AjKBGy1302qqzPaCH10QbRg3Wvco79Z8x9WbiE8HYB4e75xl6qUYvQ== + dependencies: + acorn "^7.0.0" + acorn-jsx "^5.0.2" + eslint-visitor-keys "^1.1.0" + +esprima@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" + integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== + +esquery@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.0.1.tgz#406c51658b1f5991a5f9b62b1dc25b00e3e5c708" + integrity sha512-SmiyZ5zIWH9VM+SRUReLS5Q8a7GxtRdxEBVZpm98rJM7Sb+A9DVCndXfkeFUd3byderg+EbDkfnevfCwynWaNA== + dependencies: + estraverse "^4.0.0" + +esrecurse@^4.1.0: + version "4.2.1" + resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.2.1.tgz#007a3b9fdbc2b3bb87e4879ea19c92fdbd3942cf" + integrity sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ== + dependencies: + estraverse "^4.1.0" + +estraverse@^4.0.0, estraverse@^4.1.0, estraverse@^4.1.1: + version "4.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" + integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== + +esutils@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" + integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== + +external-editor@^3.0.3: + version "3.1.0" + resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-3.1.0.tgz#cb03f740befae03ea4d283caed2741a83f335495" + integrity sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew== + dependencies: + chardet "^0.7.0" + iconv-lite "^0.4.24" + tmp "^0.0.33" + +fast-deep-equal@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz#7b05218ddf9667bf7f370bf7fdb2cb15fdd0aa49" + integrity sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk= + +fast-json-stable-stringify@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz#d5142c0caee6b1189f87d3a76111064f86c8bbf2" + integrity sha1-1RQsDK7msRifh9OnYREGT4bIu/I= + +fast-levenshtein@~2.0.4: + version "2.0.6" + resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" + integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= + +figures@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/figures/-/figures-2.0.0.tgz#3ab1a2d2a62c8bfb431a0c94cb797a2fce27c962" + integrity sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI= + dependencies: + escape-string-regexp "^1.0.5" + +file-entry-cache@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-5.0.1.tgz#ca0f6efa6dd3d561333fb14515065c2fafdf439c" + integrity sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g== + dependencies: + flat-cache "^2.0.1" + +flat-cache@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-2.0.1.tgz#5d296d6f04bda44a4630a301413bdbc2ec085ec0" + integrity sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA== + dependencies: + flatted "^2.0.0" + rimraf "2.6.3" + write "1.0.3" + +flatted@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-2.0.1.tgz#69e57caa8f0eacbc281d2e2cb458d46fdb449e08" + integrity sha512-a1hQMktqW9Nmqr5aktAux3JMNqaucxGcjtjWnZLHX7yyPCmlSV3M54nGYbqT8K+0GhF3NBgmJCc3ma+WOgX8Jg== + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= + +functional-red-black-tree@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" + integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc= + +glob-parent@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.0.0.tgz#1dc99f0f39b006d3e92c2c284068382f0c20e954" + integrity sha512-Z2RwiujPRGluePM6j699ktJYxmPpJKCfpGA13jz2hmFZC7gKetzrWvg5KN3+OsIFmydGyZ1AVwERCq1w/ZZwRg== + dependencies: + is-glob "^4.0.1" + +glob@^7.1.3: + version "7.1.4" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.4.tgz#aa608a2f6c577ad357e1ae5a5c26d9a8d1969255" + integrity sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + +globals@^11.7.0: + version "11.12.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" + integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== + +has-flag@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" + integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= + +iconv-lite@^0.4.24: + version "0.4.24" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" + integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== + dependencies: + safer-buffer ">= 2.1.2 < 3" + +ignore@^4.0.6: + version "4.0.6" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc" + integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg== + +import-fresh@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.1.0.tgz#6d33fa1dcef6df930fae003446f33415af905118" + integrity sha512-PpuksHKGt8rXfWEr9m9EHIpgyyaltBy8+eF6GJM0QCAxMgxCfucMF3mjecK2QsJr0amJW7gTqh5/wht0z2UhEQ== + dependencies: + parent-module "^1.0.0" + resolve-from "^4.0.0" + +imurmurhash@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" + integrity sha1-khi5srkoojixPcT7a21XbyMUU+o= + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +inquirer@^6.4.1: + version "6.5.2" + resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-6.5.2.tgz#ad50942375d036d327ff528c08bd5fab089928ca" + integrity sha512-cntlB5ghuB0iuO65Ovoi8ogLHiWGs/5yNrtUcKjFhSSiVeAIVpD7koaSU9RM8mpXw5YDi9RdYXGQMaOURB7ycQ== + dependencies: + ansi-escapes "^3.2.0" + chalk "^2.4.2" + cli-cursor "^2.1.0" + cli-width "^2.0.0" + external-editor "^3.0.3" + figures "^2.0.0" + lodash "^4.17.12" + mute-stream "0.0.7" + run-async "^2.2.0" + rxjs "^6.4.0" + string-width "^2.1.0" + strip-ansi "^5.1.0" + through "^2.3.6" + +is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= + +is-fullwidth-code-point@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" + integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8= + +is-glob@^4.0.0, is-glob@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc" + integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg== + dependencies: + is-extglob "^2.1.1" + +is-promise@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.1.0.tgz#79a2a9ece7f096e80f36d2b2f3bc16c1ff4bf3fa" + integrity sha1-eaKp7OfwlugPNtKy87wWwf9L8/o= + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= + +jquery-migrate@3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/jquery-migrate/-/jquery-migrate-3.1.0.tgz#8158de74cca39fb7fc2635db570151ae7ce3ddea" + integrity sha512-u/MtE1ST2pCr3rCyouJG2xMiw/k3OzLNeRKprjKTeHUezCGr0DyEgeXFdqFLmQfxfR5EsVu+mGo/sCcYdiYcIQ== + +jquery-mousewheel@3.1.13: + version "3.1.13" + resolved "https://registry.yarnpkg.com/jquery-mousewheel/-/jquery-mousewheel-3.1.13.tgz#06f0335f16e353a695e7206bf50503cb523a6ee5" + integrity sha1-BvAzXxbjU6aV5yBr9QUDy1I6buU= + +jquery-ui-timepicker-addon@1.6.3: + version "1.6.3" + resolved "https://registry.yarnpkg.com/jquery-ui-timepicker-addon/-/jquery-ui-timepicker-addon-1.6.3.tgz#8037c39b0b630282dd0b37dd8ad7fc5e1163377f" + integrity sha1-gDfDmwtjAoLdCzfditf8XhFjN38= + +jquery-ui@1.12.1: + version "1.12.1" + resolved "https://registry.yarnpkg.com/jquery-ui/-/jquery-ui-1.12.1.tgz#bcb4045c8dd0539c134bc1488cdd3e768a7a9e51" + integrity sha1-vLQEXI3QU5wTS8FIjN0+dop6nlE= + +jquery-validation@1.19.1: + version "1.19.1" + resolved "https://registry.yarnpkg.com/jquery-validation/-/jquery-validation-1.19.1.tgz#a85043467dc2b70d9fff05778646d150e747742f" + integrity sha512-QNnrZBqSltWUEJx+shOY5WtfrIb0gWmDjFfQP8rZKqMMSfpRSwEkSqhfHPvDfkObD8Hnv5KHSYI8yg73sVFdqA== + +jquery.event.drag@2.2.2: + version "2.2.2" + resolved "https://registry.yarnpkg.com/jquery.event.drag/-/jquery.event.drag-2.2.2.tgz#11bbbf83f4c6ef5f3b5065564663913c9f964be1" + integrity sha1-Ebu/g/TG7187UGVWRmORPJ+WS+E= + +jquery@3.4.1: + version "3.4.1" + resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.4.1.tgz#714f1f8d9dde4bdfa55764ba37ef214630d80ef2" + integrity sha512-36+AdBzCL+y6qjw5Tx7HgzeGCzC81MDDgaUP8ld2zhx58HdqXGoBd+tHdrBMiyjGQs0Hxs/MLZTu/eHNJJuWPw== + +js-cookie@2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/js-cookie/-/js-cookie-2.2.1.tgz#69e106dc5d5806894562902aa5baec3744e9b2b8" + integrity sha512-HvdH2LzI/EAZcUwA8+0nKNtWHqS+ZmijLA30RwZA0bo7ToCckjK5MkGhjED9KoRcXO6BaGI3I9UIzSA1FKFPOQ== + +js-tokens@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + +js-yaml@^3.13.1: + version "3.13.1" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.13.1.tgz#aff151b30bfdfa8e49e05da22e7415e9dfa37847" + integrity sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw== + dependencies: + argparse "^1.0.7" + esprima "^4.0.0" + +json-schema-traverse@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" + integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== + +json-stable-stringify-without-jsonify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" + integrity sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE= + +levn@^0.3.0, levn@~0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee" + integrity sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4= + dependencies: + prelude-ls "~1.1.2" + type-check "~0.3.2" + +lodash@^4.17.12, lodash@^4.17.14: + version "4.17.15" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548" + integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A== + +mimic-fn@^1.0.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.2.0.tgz#820c86a39334640e99516928bd03fca88057d022" + integrity sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ== + +minimatch@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" + integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== + dependencies: + brace-expansion "^1.1.7" + +minimist@0.0.8: + version "0.0.8" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" + integrity sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0= + +mkdirp@^0.5.1: + version "0.5.1" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" + integrity sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM= + dependencies: + minimist "0.0.8" + +ms@^2.1.1: + version "2.1.2" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== + +mute-stream@0.0.7: + version "0.0.7" + resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab" + integrity sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s= + +natural-compare@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" + integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc= + +nice-try@^1.0.4: + version "1.0.5" + resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" + integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== + +once@^1.3.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= + dependencies: + wrappy "1" + +onetime@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-2.0.1.tgz#067428230fd67443b2794b22bba528b6867962d4" + integrity sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ= + dependencies: + mimic-fn "^1.0.0" + +optionator@^0.8.2: + version "0.8.2" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.2.tgz#364c5e409d3f4d6301d6c0b4c05bba50180aeb64" + integrity sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q= + dependencies: + deep-is "~0.1.3" + fast-levenshtein "~2.0.4" + levn "~0.3.0" + prelude-ls "~1.1.2" + type-check "~0.3.2" + wordwrap "~1.0.0" + +os-tmpdir@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" + integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ= + +parent-module@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" + integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== + dependencies: + callsites "^3.0.0" + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= + +path-key@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" + integrity sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A= + +prelude-ls@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" + integrity sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ= + +progress@^2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" + integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== + +punycode@^2.1.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" + integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== + +regexpp@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-2.0.1.tgz#8d19d31cf632482b589049f8281f93dbcba4d07f" + integrity sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw== + +resolve-from@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" + integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== + +restore-cursor@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-2.0.0.tgz#9f7ee287f82fd326d4fd162923d62129eee0dfaf" + integrity sha1-n37ih/gv0ybU/RYpI9YhKe7g368= + dependencies: + onetime "^2.0.0" + signal-exit "^3.0.2" + +rimraf@2.6.3: + version "2.6.3" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab" + integrity sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA== + dependencies: + glob "^7.1.3" + +run-async@^2.2.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.3.0.tgz#0371ab4ae0bdd720d4166d7dfda64ff7a445a6c0" + integrity sha1-A3GrSuC91yDUFm19/aZP96RFpsA= + dependencies: + is-promise "^2.1.0" + +rxjs@^6.4.0: + version "6.5.2" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.5.2.tgz#2e35ce815cd46d84d02a209fb4e5921e051dbec7" + integrity sha512-HUb7j3kvb7p7eCUHE3FqjoDsC1xfZQ4AHFWfTKSpZ+sAhhz5X1WX0ZuUqWbzB2QhSLp3DoLUG+hMdEDKqWo2Zg== + dependencies: + tslib "^1.9.0" + +"safer-buffer@>= 2.1.2 < 3": + version "2.1.2" + resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" + integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== + +semver@^5.5.0: + version "5.7.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" + integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== + +semver@^6.1.2: + version "6.3.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" + integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== + +shebang-command@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" + integrity sha1-RKrGW2lbAzmJaMOfNj/uXer98eo= + dependencies: + shebang-regex "^1.0.0" + +shebang-regex@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" + integrity sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM= + +signal-exit@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d" + integrity sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0= + +slice-ansi@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-2.1.0.tgz#cacd7693461a637a5788d92a7dd4fba068e81636" + integrity sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ== + dependencies: + ansi-styles "^3.2.0" + astral-regex "^1.0.0" + is-fullwidth-code-point "^2.0.0" + +sprintf-js@~1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" + integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= + +string-width@^2.1.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" + integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw== + dependencies: + is-fullwidth-code-point "^2.0.0" + strip-ansi "^4.0.0" + +string-width@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-3.1.0.tgz#22767be21b62af1081574306f69ac51b62203961" + integrity sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w== + dependencies: + emoji-regex "^7.0.1" + is-fullwidth-code-point "^2.0.0" + strip-ansi "^5.1.0" + +strip-ansi@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f" + integrity sha1-qEeQIusaw2iocTibY1JixQXuNo8= + dependencies: + ansi-regex "^3.0.0" + +strip-ansi@^5.1.0, strip-ansi@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae" + integrity sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA== + dependencies: + ansi-regex "^4.1.0" + +strip-json-comments@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.0.1.tgz#85713975a91fb87bf1b305cca77395e40d2a64a7" + integrity sha512-VTyMAUfdm047mwKl+u79WIdrZxtFtn+nBxHeb844XBQ9uMNTuTHdx2hc5RiAJYqwTj3wc/xe5HLSdJSkJ+WfZw== + +supports-color@^5.3.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" + integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== + dependencies: + has-flag "^3.0.0" + +table@^5.2.3: + version "5.4.6" + resolved "https://registry.yarnpkg.com/table/-/table-5.4.6.tgz#1292d19500ce3f86053b05f0e8e7e4a3bb21079e" + integrity sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug== + dependencies: + ajv "^6.10.2" + lodash "^4.17.14" + slice-ansi "^2.1.0" + string-width "^3.0.0" + +text-table@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" + integrity sha1-f17oI66AUgfACvLfSoTsP8+lcLQ= + +through@^2.3.6: + version "2.3.8" + resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" + integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU= + +tmp@^0.0.33: + version "0.0.33" + resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" + integrity sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw== + dependencies: + os-tmpdir "~1.0.2" + +tracekit@0.4.5: + version "0.4.5" + resolved "https://registry.yarnpkg.com/tracekit/-/tracekit-0.4.5.tgz#55d40ea76ddf2bde137d9aaeb94933b5e94c5991" + integrity sha512-LAb1udnpvhpgcx6/gmv7s6RO5lBwQGgAT/1VW0egSNSMvH/3xU3xKLoJ3pc+nkJ5AMv9qgTBnCkrUzbrHmCLpg== + +tslib@^1.9.0: + version "1.10.0" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.10.0.tgz#c3c19f95973fb0a62973fb09d90d961ee43e5c8a" + integrity sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ== + +type-check@~0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72" + integrity sha1-WITKtRLPHTVeP7eE8wgEsrUg23I= + dependencies: + prelude-ls "~1.1.2" + +updated-jqplot@1.0.9: + version "1.0.9" + resolved "https://registry.yarnpkg.com/updated-jqplot/-/updated-jqplot-1.0.9.tgz#3daeda1a74ea991256749364dc9225af038280b7" + integrity sha1-Pa7aGnTqmRJWdJNk3JIlrwOCgLc= + +uri-js@^4.2.2: + version "4.2.2" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.2.2.tgz#94c540e1ff772956e2299507c010aea6c8838eb0" + integrity sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ== + dependencies: + punycode "^2.1.0" + +v8-compile-cache@^2.0.3: + version "2.1.0" + resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.1.0.tgz#e14de37b31a6d194f5690d67efc4e7f6fc6ab30e" + integrity sha512-usZBT3PW+LOjM25wbqIlZwPeJV+3OSz3M1k1Ws8snlW39dZyYL9lOGC5FgPVHfk0jKmjiDV8Z0mIbVQPiwFs7g== + +which@^1.2.9: + version "1.3.1" + resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" + integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== + dependencies: + isexe "^2.0.0" + +wordwrap@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" + integrity sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus= + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= + +write@1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/write/-/write-1.0.3.tgz#0800e14523b923a387e415123c865616aae0f5c3" + integrity sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig== + dependencies: + mkdirp "^0.5.1" + +zxcvbn@4.4.2: + version "4.4.2" + resolved "https://registry.yarnpkg.com/zxcvbn/-/zxcvbn-4.4.2.tgz#28ec17cf09743edcab056ddd8b1b06262cc73c30" + integrity sha1-KOwXzwl0PtyrBW3dixsGJizHPDA= diff --git a/php/docker-compose.yml b/php/docker-compose.yml index 64b95290..74ffab69 100644 --- a/php/docker-compose.yml +++ b/php/docker-compose.yml @@ -1,7 +1,6 @@ -version: "3.4" +version: '3.4' services: - agent: image: datadog/agent:latest volumes: @@ -124,7 +123,31 @@ services: environment: DD_AGENT_HOST: agent DD_TRACE_APP_NAME: symfony_php56_apache - DD_TRACE_DEBUG: "true" + DD_TRACE_DEBUG: 'true' + + nginx: + build: + context: Dockerfiles/nginx + volumes: + - ./apps:/var/www/public + - ./Dockerfiles/nginx/default.conf:/etc/nginx/conf.d/default.conf + - ./Dockerfiles/nginx/fastcgi_params_shared:/etc/nginx/fastcgi_params_shared + depends_on: + - php-fpm-73 + ports: + - '5678:80' + + php-fpm-73: + build: + context: Dockerfiles/php-fpm + dockerfile: Dockerfile73 + args: + - DD_TRACE_VERSION=${DD_TRACE_VERSION} + depends_on: + - agent + - mysql + volumes: + - ./apps:/var/www/public volumes: composer-cache: